summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
Diffstat (limited to 'sql')
-rw-r--r--sql/CMakeLists.txt404
-rw-r--r--sql/MSG00001.binbin0 -> 184 bytes
-rwxr-xr-xsql/add_errmsg17
-rw-r--r--sql/authors.h188
-rw-r--r--sql/bounded_queue.h195
-rw-r--r--sql/client_settings.h53
-rw-r--r--sql/compat56.cc445
-rw-r--r--sql/compat56.h46
-rw-r--r--sql/contributors.h64
-rw-r--r--sql/create_options.cc794
-rw-r--r--sql/create_options.h104
-rw-r--r--sql/custom_conf.h28
-rw-r--r--sql/datadict.cc176
-rw-r--r--sql/datadict.h49
-rw-r--r--sql/debug_sync.cc1603
-rw-r--r--sql/debug_sync.h50
-rw-r--r--sql/derror.cc265
-rw-r--r--sql/derror.h25
-rw-r--r--sql/des_key_file.cc106
-rw-r--r--sql/des_key_file.h40
-rw-r--r--sql/discover.cc270
-rw-r--r--sql/discover.h41
-rw-r--r--sql/event_data_objects.cc1540
-rw-r--r--sql/event_data_objects.h195
-rw-r--r--sql/event_db_repository.cc1246
-rw-r--r--sql/event_db_repository.h127
-rw-r--r--sql/event_parse_data.cc582
-rw-r--r--sql/event_parse_data.h132
-rw-r--r--sql/event_queue.cc837
-rw-r--r--sql/event_queue.h135
-rw-r--r--sql/event_scheduler.cc850
-rw-r--r--sql/event_scheduler.h157
-rw-r--r--sql/events.cc1180
-rw-r--r--sql/events.h155
-rw-r--r--sql/examples/CMakeLists.txt23
-rw-r--r--sql/field.cc10152
-rw-r--r--sql/field.h3054
-rw-r--r--sql/field_conv.cc960
-rw-r--r--sql/filesort.cc2118
-rw-r--r--sql/filesort.h36
-rw-r--r--sql/filesort_utils.cc143
-rw-r--r--sql/filesort_utils.h129
-rw-r--r--sql/gcalc_slicescan.cc1998
-rw-r--r--sql/gcalc_slicescan.h600
-rw-r--r--sql/gcalc_tools.cc1425
-rw-r--r--sql/gcalc_tools.h348
-rw-r--r--sql/gen_lex_hash.cc479
-rw-r--r--sql/gen_lex_token.cc353
-rw-r--r--sql/gstream.cc145
-rw-r--r--sql/gstream.h93
-rw-r--r--sql/ha_ndbcluster.cc11061
-rw-r--r--sql/ha_ndbcluster.h599
-rw-r--r--sql/ha_ndbcluster_binlog.cc4426
-rw-r--r--sql/ha_ndbcluster_binlog.h239
-rw-r--r--sql/ha_ndbcluster_cond.cc1476
-rw-r--r--sql/ha_ndbcluster_cond.h500
-rw-r--r--sql/ha_ndbcluster_tables.h29
-rw-r--r--sql/ha_partition.cc9122
-rw-r--r--sql/ha_partition.h1290
-rw-r--r--sql/handler.cc6323
-rw-r--r--sql/handler.h4153
-rw-r--r--sql/hash_filo.cc33
-rw-r--r--sql/hash_filo.h215
-rw-r--r--sql/hostname.cc1009
-rw-r--r--sql/hostname.h184
-rw-r--r--sql/init.cc53
-rw-r--r--sql/init.h24
-rw-r--r--sql/innodb_priv.h36
-rw-r--r--sql/item.cc9845
-rw-r--r--sql/item.h4899
-rw-r--r--sql/item_buff.cc176
-rw-r--r--sql/item_cmpfunc.cc6522
-rw-r--r--sql/item_cmpfunc.h2139
-rw-r--r--sql/item_create.cc6293
-rw-r--r--sql/item_create.h199
-rw-r--r--sql/item_func.cc7018
-rw-r--r--sql/item_func.h2209
-rw-r--r--sql/item_geofunc.cc1759
-rw-r--r--sql/item_geofunc.h518
-rw-r--r--sql/item_inetfunc.cc831
-rw-r--r--sql/item_inetfunc.h244
-rw-r--r--sql/item_row.cc234
-rw-r--r--sql/item_row.h91
-rw-r--r--sql/item_strfunc.cc5185
-rw-r--r--sql/item_strfunc.h1232
-rw-r--r--sql/item_subselect.cc6531
-rw-r--r--sql/item_subselect.h1467
-rw-r--r--sql/item_sum.cc3660
-rw-r--r--sql/item_sum.h1492
-rw-r--r--sql/item_timefunc.cc3240
-rw-r--r--sql/item_timefunc.h1092
-rw-r--r--sql/item_xmlfunc.cc2973
-rw-r--r--sql/item_xmlfunc.h123
-rw-r--r--sql/key.cc919
-rw-r--r--sql/key.h46
-rw-r--r--sql/keycaches.cc231
-rw-r--r--sql/keycaches.h58
-rw-r--r--sql/lex.h711
-rw-r--r--sql/lex_symbol.h48
-rw-r--r--sql/lock.cc1120
-rw-r--r--sql/lock.h51
-rw-r--r--sql/log.cc9852
-rw-r--r--sql/log.h1094
-rw-r--r--sql/log_event.cc12703
-rw-r--r--sql/log_event.h4807
-rw-r--r--sql/log_event_old.cc2850
-rw-r--r--sql/log_event_old.h573
-rw-r--r--sql/log_slow.h38
-rw-r--r--sql/main.cc26
-rw-r--r--sql/mdl.cc3225
-rw-r--r--sql/mdl.h1000
-rw-r--r--sql/mem_root_array.h175
-rw-r--r--sql/message.h77
-rw-r--r--sql/message.mc16
-rw-r--r--sql/message.rc2
-rw-r--r--sql/mf_iocache.cc98
-rw-r--r--sql/multi_range_read.cc1870
-rw-r--r--sql/multi_range_read.h659
-rw-r--r--sql/my_apc.cc270
-rw-r--r--sql/my_apc.h142
-rw-r--r--sql/my_decimal.cc422
-rw-r--r--sql/my_decimal.h493
-rw-r--r--sql/mysql_install_db.cc662
-rw-r--r--sql/mysql_upgrade_service.cc522
-rw-r--r--sql/mysqld.cc9740
-rw-r--r--sql/mysqld.h760
-rw-r--r--sql/mysqld_suffix.h34
-rw-r--r--sql/net_serv.cc1248
-rw-r--r--sql/nt_servc.cc569
-rw-r--r--sql/nt_servc.h115
-rw-r--r--sql/opt_index_cond_pushdown.cc446
-rw-r--r--sql/opt_range.cc15248
-rw-r--r--sql/opt_range.h1070
-rw-r--r--sql/opt_range_mrr.cc364
-rw-r--r--sql/opt_subselect.cc5708
-rw-r--r--sql/opt_subselect.h399
-rw-r--r--sql/opt_sum.cc1064
-rw-r--r--sql/opt_table_elimination.cc1905
-rw-r--r--sql/parse_file.cc950
-rw-r--r--sql/parse_file.h116
-rw-r--r--sql/partition_element.h141
-rw-r--r--sql/partition_info.cc3154
-rw-r--r--sql/partition_info.h414
-rw-r--r--sql/password.c544
-rw-r--r--sql/plistsort.c166
-rw-r--r--sql/procedure.cc102
-rw-r--r--sql/procedure.h167
-rw-r--r--sql/protocol.cc1563
-rw-r--r--sql/protocol.h221
-rw-r--r--sql/records.cc713
-rw-r--r--sql/records.h85
-rw-r--r--sql/repl_failsafe.cc278
-rw-r--r--sql/repl_failsafe.h48
-rw-r--r--sql/replication.h538
-rw-r--r--sql/rpl_constants.h74
-rw-r--r--sql/rpl_filter.cc796
-rw-r--r--sql/rpl_filter.h146
-rw-r--r--sql/rpl_gtid.cc2364
-rw-r--r--sql/rpl_gtid.h301
-rw-r--r--sql/rpl_handler.cc536
-rw-r--r--sql/rpl_handler.h213
-rw-r--r--sql/rpl_injector.cc251
-rw-r--r--sql/rpl_injector.h337
-rw-r--r--sql/rpl_mi.cc1391
-rw-r--r--sql/rpl_mi.h248
-rw-r--r--sql/rpl_parallel.cc2294
-rw-r--r--sql/rpl_parallel.h320
-rw-r--r--sql/rpl_record.cc465
-rw-r--r--sql/rpl_record.h43
-rw-r--r--sql/rpl_record_old.cc200
-rw-r--r--sql/rpl_record_old.h35
-rw-r--r--sql/rpl_reporting.cc82
-rw-r--r--sql/rpl_reporting.h109
-rw-r--r--sql/rpl_rli.cc2002
-rw-r--r--sql/rpl_rli.h847
-rw-r--r--sql/rpl_tblmap.cc176
-rw-r--r--sql/rpl_tblmap.h112
-rw-r--r--sql/rpl_utility.cc1255
-rw-r--r--sql/rpl_utility.h307
-rw-r--r--sql/scheduler.cc153
-rw-r--r--sql/scheduler.h111
-rw-r--r--sql/set_var.cc901
-rw-r--r--sql/set_var.h349
-rw-r--r--sql/sha2.cc68
-rw-r--r--sql/share/CMakeLists.txt55
-rw-r--r--sql/share/charsets/Index.xml599
-rw-r--r--sql/share/charsets/README39
-rw-r--r--sql/share/charsets/armscii8.xml139
-rw-r--r--sql/share/charsets/ascii.xml139
-rw-r--r--sql/share/charsets/cp1250.xml183
-rw-r--r--sql/share/charsets/cp1251.xml214
-rw-r--r--sql/share/charsets/cp1256.xml142
-rw-r--r--sql/share/charsets/cp1257.xml228
-rw-r--r--sql/share/charsets/cp850.xml139
-rw-r--r--sql/share/charsets/cp852.xml139
-rw-r--r--sql/share/charsets/cp866.xml142
-rw-r--r--sql/share/charsets/dec8.xml140
-rw-r--r--sql/share/charsets/geostd8.xml139
-rw-r--r--sql/share/charsets/greek.xml144
-rw-r--r--sql/share/charsets/hebrew.xml140
-rw-r--r--sql/share/charsets/hp8.xml140
-rw-r--r--sql/share/charsets/keybcs2.xml140
-rw-r--r--sql/share/charsets/koi8r.xml139
-rw-r--r--sql/share/charsets/koi8u.xml140
-rw-r--r--sql/share/charsets/languages.html274
-rw-r--r--sql/share/charsets/latin1.xml253
-rw-r--r--sql/share/charsets/latin2.xml186
-rw-r--r--sql/share/charsets/latin5.xml139
-rw-r--r--sql/share/charsets/latin7.xml187
-rw-r--r--sql/share/charsets/macce.xml207
-rw-r--r--sql/share/charsets/macroman.xml200
-rw-r--r--sql/share/charsets/swe7.xml141
-rw-r--r--sql/share/errmsg-utf8.txt7113
-rw-r--r--sql/signal_handler.cc276
-rw-r--r--sql/slave.cc6979
-rw-r--r--sql/slave.h274
-rw-r--r--sql/sp.cc2278
-rw-r--r--sql/sp.h217
-rw-r--r--sql/sp_cache.cc315
-rw-r--r--sql/sp_cache.h68
-rw-r--r--sql/sp_head.cc4293
-rw-r--r--sql/sp_head.h1414
-rw-r--r--sql/sp_pcontext.cc485
-rw-r--r--sql/sp_pcontext.h565
-rw-r--r--sql/sp_rcontext.cc558
-rw-r--r--sql/sp_rcontext.h469
-rw-r--r--sql/spatial.cc2607
-rw-r--r--sql/spatial.h592
-rw-r--r--sql/sql_acl.cc12462
-rw-r--r--sql/sql_acl.h409
-rw-r--r--sql/sql_admin.cc1285
-rw-r--r--sql/sql_admin.h125
-rw-r--r--sql/sql_alter.cc347
-rw-r--r--sql/sql_alter.h429
-rw-r--r--sql/sql_analyse.cc1242
-rw-r--r--sql/sql_analyse.h368
-rw-r--r--sql/sql_array.h240
-rw-r--r--sql/sql_audit.cc585
-rw-r--r--sql/sql_audit.h309
-rw-r--r--sql/sql_base.cc9567
-rw-r--r--sql/sql_base.h640
-rw-r--r--sql/sql_binlog.cc280
-rw-r--r--sql/sql_binlog.h23
-rw-r--r--sql/sql_bitmap.h187
-rw-r--r--sql/sql_bootstrap.cc119
-rw-r--r--sql/sql_bootstrap.h47
-rw-r--r--sql/sql_builtin.cc.in39
-rw-r--r--sql/sql_cache.cc5246
-rw-r--r--sql/sql_cache.h600
-rw-r--r--sql/sql_callback.h42
-rw-r--r--sql/sql_class.cc6694
-rw-r--r--sql/sql_class.h5036
-rw-r--r--sql/sql_client.cc44
-rw-r--r--sql/sql_cmd.h165
-rw-r--r--sql/sql_connect.cc1397
-rw-r--r--sql/sql_connect.h73
-rw-r--r--sql/sql_const.h265
-rw-r--r--sql/sql_crypt.cc77
-rw-r--r--sql/sql_crypt.h45
-rw-r--r--sql/sql_cursor.cc424
-rw-r--r--sql/sql_cursor.h66
-rw-r--r--sql/sql_db.cc1838
-rw-r--r--sql/sql_db.h47
-rw-r--r--sql/sql_delete.cc1296
-rw-r--r--sql/sql_delete.h35
-rw-r--r--sql/sql_derived.cc984
-rw-r--r--sql/sql_derived.h40
-rw-r--r--sql/sql_digest.cc683
-rw-r--r--sql/sql_digest.h130
-rw-r--r--sql/sql_digest_stream.h51
-rw-r--r--sql/sql_do.cc51
-rw-r--r--sql/sql_do.h26
-rw-r--r--sql/sql_error.cc1039
-rw-r--r--sql/sql_error.h976
-rw-r--r--sql/sql_explain.cc950
-rw-r--r--sql/sql_explain.h550
-rw-r--r--sql/sql_expression_cache.cc325
-rw-r--r--sql/sql_expression_cache.h111
-rw-r--r--sql/sql_get_diagnostics.cc342
-rw-r--r--sql/sql_get_diagnostics.h318
-rw-r--r--sql/sql_handler.cc1192
-rw-r--r--sql/sql_handler.h80
-rw-r--r--sql/sql_help.cc825
-rw-r--r--sql/sql_help.h28
-rw-r--r--sql/sql_hset.h103
-rw-r--r--sql/sql_insert.cc4327
-rw-r--r--sql/sql_insert.h48
-rw-r--r--sql/sql_join_cache.cc4668
-rw-r--r--sql/sql_join_cache.h1435
-rw-r--r--sql/sql_lex.cc4460
-rw-r--r--sql/sql_lex.h2967
-rw-r--r--sql/sql_lifo_buffer.h359
-rw-r--r--sql/sql_list.cc72
-rw-r--r--sql/sql_list.h815
-rw-r--r--sql/sql_load.cc2094
-rw-r--r--sql/sql_load.h34
-rw-r--r--sql/sql_locale.cc3518
-rw-r--r--sql/sql_locale.h78
-rw-r--r--sql/sql_manager.cc170
-rw-r--r--sql/sql_manager.h23
-rw-r--r--sql/sql_parse.cc8671
-rw-r--r--sql/sql_parse.h210
-rw-r--r--sql/sql_partition.cc8358
-rw-r--r--sql/sql_partition.h290
-rw-r--r--sql/sql_partition_admin.cc849
-rw-r--r--sql/sql_partition_admin.h269
-rw-r--r--sql/sql_plist.h294
-rw-r--r--sql/sql_plugin.cc4090
-rw-r--r--sql/sql_plugin.h193
-rw-r--r--sql/sql_plugin_compat.h65
-rw-r--r--sql/sql_plugin_services.h103
-rw-r--r--sql/sql_prepare.cc4632
-rw-r--r--sql/sql_prepare.h360
-rw-r--r--sql/sql_priv.h407
-rw-r--r--sql/sql_profile.cc748
-rw-r--r--sql/sql_profile.h293
-rw-r--r--sql/sql_reload.cc595
-rw-r--r--sql/sql_reload.h26
-rw-r--r--sql/sql_rename.cc363
-rw-r--r--sql/sql_rename.h24
-rw-r--r--sql/sql_repl.cc4200
-rw-r--r--sql/sql_repl.h85
-rw-r--r--sql/sql_select.cc25270
-rw-r--r--sql/sql_select.h1909
-rw-r--r--sql/sql_servers.cc1334
-rw-r--r--sql/sql_servers.h53
-rw-r--r--sql/sql_show.cc9626
-rw-r--r--sql/sql_show.h161
-rw-r--r--sql/sql_signal.cc542
-rw-r--r--sql/sql_signal.h147
-rw-r--r--sql/sql_sort.h115
-rw-r--r--sql/sql_state.c55
-rw-r--r--sql/sql_statistics.cc3718
-rw-r--r--sql/sql_statistics.h428
-rw-r--r--sql/sql_string.cc1179
-rw-r--r--sql/sql_string.h598
-rw-r--r--sql/sql_table.cc9814
-rw-r--r--sql/sql_table.h287
-rw-r--r--sql/sql_tablespace.cc77
-rw-r--r--sql/sql_tablespace.h24
-rw-r--r--sql/sql_test.cc647
-rw-r--r--sql/sql_test.h39
-rw-r--r--sql/sql_time.cc1341
-rw-r--r--sql/sql_time.h163
-rw-r--r--sql/sql_trigger.cc2439
-rw-r--r--sql/sql_trigger.h258
-rw-r--r--sql/sql_truncate.cc571
-rw-r--r--sql/sql_truncate.h73
-rw-r--r--sql/sql_udf.cc615
-rw-r--r--sql/sql_udf.h147
-rw-r--r--sql/sql_union.cc1074
-rw-r--r--sql/sql_union.h31
-rw-r--r--sql/sql_update.cc2525
-rw-r--r--sql/sql_update.h44
-rw-r--r--sql/sql_view.cc2128
-rw-r--r--sql/sql_view.h62
-rw-r--r--sql/sql_yacc.yy16420
-rw-r--r--sql/strfunc.cc402
-rw-r--r--sql/strfunc.h49
-rw-r--r--sql/structs.h521
-rw-r--r--sql/sys_vars.cc4816
-rw-r--r--sql/sys_vars.h2288
-rw-r--r--sql/sys_vars_shared.h86
-rw-r--r--sql/table.cc7210
-rw-r--r--sql/table.h2645
-rw-r--r--sql/table_cache.cc1220
-rw-r--r--sql/table_cache.h137
-rw-r--r--sql/thr_malloc.cc148
-rw-r--r--sql/thr_malloc.h35
-rw-r--r--sql/threadpool.h70
-rw-r--r--sql/threadpool_common.cc282
-rw-r--r--sql/threadpool_unix.cc1685
-rw-r--r--sql/threadpool_win.cc747
-rw-r--r--sql/transaction.cc915
-rw-r--r--sql/transaction.h47
-rw-r--r--sql/tzfile.h142
-rw-r--r--sql/tztime.cc2976
-rw-r--r--sql/tztime.h94
-rw-r--r--sql/udf_example.c1152
-rw-r--r--sql/udf_example.def29
-rw-r--r--sql/uniques.cc788
-rw-r--r--sql/unireg.cc979
-rw-r--r--sql/unireg.h215
-rw-r--r--sql/winservice.c300
-rw-r--r--sql/winservice.h40
385 files changed, 522407 insertions, 0 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
new file mode 100644
index 00000000000..88ec26eb6f6
--- /dev/null
+++ b/sql/CMakeLists.txt
@@ -0,0 +1,404 @@
+# Copyright (c) 2006, 2014, Oracle and/or its affiliates.
+# Copyright (c) 2010, 2015, MariaDB
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+INCLUDE_DIRECTORIES(
+${CMAKE_SOURCE_DIR}/include
+${CMAKE_SOURCE_DIR}/sql
+${PCRE_INCLUDES}
+${ZLIB_INCLUDE_DIR}
+${SSL_INCLUDE_DIRS}
+${CMAKE_BINARY_DIR}/sql
+)
+
+SET(GEN_SOURCES
+${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h
+${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc
+${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h
+)
+SET(GEN_DIGEST_SOURCES
+ ${CMAKE_CURRENT_BINARY_DIR}/lex_token.h
+)
+
+SET_SOURCE_FILES_PROPERTIES(${GEN_SOURCES}
+ ${GEN_DIGEST_SOURCES}
+ PROPERTIES GENERATED 1)
+
+# Gen_lex_token
+# Make sure sql_yacc.h is generated before compiling gen_lex_token
+IF(NOT CMAKE_CROSSCOMPILING)
+ ADD_EXECUTABLE(gen_lex_token gen_lex_token.cc)
+ ADD_DEPENDENCIES(gen_lex_token GenServerSource)
+ENDIF()
+
+ADD_CUSTOM_COMMAND(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lex_token.h
+ COMMAND gen_lex_token > lex_token.h
+ DEPENDS gen_lex_token
+)
+
+ADD_DEFINITIONS(-DMYSQL_SERVER -DHAVE_EVENT_SCHEDULER)
+
+IF(SSL_DEFINES)
+ ADD_DEFINITIONS(${SSL_DEFINES})
+ENDIF()
+
+SET (SQL_SOURCE
+ ../sql-common/client.c compat56.cc derror.cc des_key_file.cc
+ discover.cc ../libmysql/errmsg.c field.cc field_conv.cc
+ filesort_utils.cc
+ filesort.cc gstream.cc sha2.cc
+ signal_handler.cc
+ handler.cc hash_filo.h sql_plugin_services.h
+ hostname.cc init.cc item.cc item_buff.cc item_cmpfunc.cc
+ item_create.cc item_func.cc item_geofunc.cc item_row.cc
+ item_strfunc.cc item_subselect.cc item_sum.cc item_timefunc.cc
+ key.cc log.cc lock.cc
+ log_event.cc rpl_record.cc rpl_reporting.cc
+ log_event_old.cc rpl_record_old.cc
+ message.h mf_iocache.cc my_decimal.cc ../sql-common/my_time.c
+ mysqld.cc net_serv.cc keycaches.cc
+ ../sql-common/client_plugin.c
+ opt_range.cc opt_range.h opt_sum.cc
+ ../sql-common/pack.c parse_file.cc password.c procedure.cc
+ protocol.cc records.cc repl_failsafe.cc rpl_filter.cc set_var.cc
+ slave.cc sp.cc sp_cache.cc sp_head.cc sp_pcontext.cc
+ sp_rcontext.cc spatial.cc sql_acl.cc sql_analyse.cc sql_base.cc
+ sql_cache.cc sql_class.cc sql_client.cc sql_crypt.cc sql_crypt.h
+ sql_cursor.cc sql_db.cc sql_delete.cc sql_derived.cc
+ sql_digest.cc sql_do.cc
+ sql_error.cc sql_handler.cc sql_get_diagnostics.cc
+ sql_help.cc sql_insert.cc sql_lex.cc
+ sql_list.cc sql_load.cc sql_manager.cc
+ sql_parse.cc sql_bootstrap.cc sql_bootstrap.h
+ sql_partition.cc sql_plugin.cc sql_prepare.cc sql_rename.cc
+ debug_sync.cc debug_sync.h
+ sql_repl.cc sql_select.cc sql_show.cc sql_state.c
+ sql_statistics.cc sql_string.cc
+ sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc
+ sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc
+ sql_time.cc tztime.cc uniques.cc unireg.cc item_xmlfunc.cc
+ rpl_tblmap.cc sql_binlog.cc event_scheduler.cc event_data_objects.cc
+ event_queue.cc event_db_repository.cc
+ sql_tablespace.cc events.cc ../sql-common/my_user.c
+ partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc
+ rpl_rli.cc rpl_mi.cc sql_servers.cc sql_audit.cc
+ sql_connect.cc scheduler.cc sql_partition_admin.cc
+ sql_profile.cc event_parse_data.cc sql_alter.cc
+ sql_signal.cc rpl_handler.cc mdl.cc sql_admin.cc
+ transaction.cc sys_vars.cc sql_truncate.cc datadict.cc
+ sql_reload.cc sql_cmd.h item_inetfunc.cc
+
+ # added in MariaDB:
+ sql_explain.h sql_explain.cc
+ sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc
+ create_options.cc multi_range_read.cc
+ opt_index_cond_pushdown.cc opt_subselect.cc
+ opt_table_elimination.cc sql_expression_cache.cc
+ gcalc_slicescan.cc gcalc_tools.cc
+ threadpool_common.cc
+ ../sql-common/mysql_async.c
+ my_apc.cc my_apc.h
+ rpl_gtid.cc rpl_parallel.cc
+ table_cache.cc
+ ${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc
+ ${GEN_SOURCES}
+ ${GEN_DIGEST_SOURCES}
+ ${MYSYS_LIBWRAP_SOURCE}
+ )
+
+IF (CMAKE_SYSTEM_NAME MATCHES "Linux" OR
+ CMAKE_SYSTEM_NAME MATCHES "Windows" OR
+ CMAKE_SYSTEM_NAME MATCHES "SunOS" OR
+ HAVE_KQUEUE)
+ ADD_DEFINITIONS(-DHAVE_POOL_OF_THREADS)
+ IF(WIN32)
+ SET(SQL_SOURCE ${SQL_SOURCE} threadpool_win.cc)
+ ELSE()
+ SET(SQL_SOURCE ${SQL_SOURCE} threadpool_unix.cc)
+ ENDIF()
+ENDIF()
+
+MYSQL_ADD_PLUGIN(partition ha_partition.cc STORAGE_ENGINE DEFAULT STATIC_ONLY
+RECOMPILE_FOR_EMBEDDED)
+
+ADD_LIBRARY(sql STATIC ${SQL_SOURCE})
+ADD_DEPENDENCIES(sql GenServerSource)
+ADD_DEPENDENCIES(sql GenDigestServerSource)
+DTRACE_INSTRUMENT(sql)
+TARGET_LINK_LIBRARIES(sql ${MYSQLD_STATIC_PLUGIN_LIBS}
+ mysys mysys_ssl dbug strings vio pcre ${LIBJEMALLOC}
+ ${LIBWRAP} ${LIBCRYPT} ${LIBDL} ${CMAKE_THREAD_LIBS_INIT}
+ ${SSL_LIBRARIES})
+
+IF(WIN32)
+ SET(MYSQLD_SOURCE main.cc nt_servc.cc nt_servc.h message.rc)
+ TARGET_LINK_LIBRARIES(sql psapi)
+ELSE()
+ SET(MYSQLD_SOURCE main.cc ${DTRACE_PROBES_ALL})
+ENDIF()
+
+MYSQL_ADD_EXECUTABLE(mysqld ${MYSQLD_SOURCE} DESTINATION ${INSTALL_SBINDIR} COMPONENT Server)
+
+IF(APPLE)
+ # Add CoreServices framework since some dloadable plugins may need it
+ FIND_LIBRARY(CORESERVICES NAMES CoreServices)
+ IF(CORESERVICES)
+ TARGET_LINK_LIBRARIES(mysqld ${CORESERVICES})
+ ENDIF()
+ENDIF()
+
+IF(NOT WITHOUT_DYNAMIC_PLUGINS)
+ SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE)
+ GET_TARGET_PROPERTY(mysqld_link_flags mysqld LINK_FLAGS)
+ IF(NOT mysqld_link_flags)
+ SET(mysqld_link_flags)
+ ENDIF()
+ IF (MINGW OR CYGWIN)
+ SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${mysqld_link_flags} -Wl,--export-all-symbols")
+ ENDIF()
+ IF(MSVC)
+ # Set module definition file. Also use non-incremental linker,
+ # incremental appears to crash from time to time,if used with /DEF option
+ SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${mysqld_link_flags} /DEF:mysqld.def /INCREMENTAL:NO")
+
+ FOREACH (CORELIB sql mysys mysys_ssl dbug strings)
+ GET_TARGET_PROPERTY(LOC ${CORELIB} LOCATION)
+ FILE(TO_NATIVE_PATH ${LOC} LOC)
+ SET (LIB_LOCATIONS ${LIB_LOCATIONS} ${LOC})
+ ENDFOREACH (CORELIB ${MYSQLD_CORE_LIBS})
+ SET(_PLATFORM x86)
+ IF(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ SET(_PLATFORM x64)
+ ENDIF()
+ ADD_CUSTOM_COMMAND(TARGET mysqld PRE_LINK
+ COMMAND echo ${_PLATFORM} && cscript ARGS //nologo ${PROJECT_SOURCE_DIR}/win/create_def_file.js
+ ${_PLATFORM} ${LIB_LOCATIONS} > mysqld.def
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+ ADD_DEPENDENCIES(sql GenError)
+ ENDIF(MSVC)
+ENDIF(NOT WITHOUT_DYNAMIC_PLUGINS)
+
+SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE)
+TARGET_LINK_LIBRARIES(mysqld sql)
+
+# Provide plugins with minimal set of libraries
+SET(INTERFACE_LIBS ${LIBRT})
+IF(INTERFACE_LIBS)
+ SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_INTERFACE_LIBRARIES
+ "${INTERFACE_LIBS}")
+ENDIF()
+
+# On Solaris, some extra effort is required in order to get dtrace probes
+# from static libraries
+DTRACE_INSTRUMENT_STATIC_LIBS(mysqld
+ "sql;mysys;mysys_ssl;${MYSQLD_STATIC_PLUGIN_LIBS}")
+
+
+SET(WITH_MYSQLD_LDFLAGS "" CACHE STRING "Additional linker flags for mysqld")
+MARK_AS_ADVANCED(WITH_MYSQLD_LDFLAGS)
+IF(WITH_MYSQLD_LDFLAGS)
+ GET_TARGET_PROPERTY(mysqld LINK_FLAGS MYSQLD_LINK_FLAGS)
+ IF(NOT MYSQLD_LINK_FLAGS)
+ SET(MYSQLD_LINK_FLAGS)
+ ENDIF()
+ SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS
+ "${MYSQLD_LINK_FLAGS} ${WITH_MYSQLD_LDFLAGS}")
+ENDIF()
+INSTALL_DEBUG_TARGET(mysqld
+ DESTINATION ${INSTALL_SBINDIR}
+ PDB_DESTINATION ${INSTALL_SBINDIR}/debug
+ RENAME mysqld-debug)
+
+INCLUDE(${CMAKE_SOURCE_DIR}/cmake/bison.cmake)
+
+# Handle out-of-source build from source package with possibly broken
+# bison. Copy bison output to from source to build directory, if not already
+# there
+IF (NOT BISON_USABLE)
+IF (NOT ${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
+ IF(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.cc)
+ IF(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc)
+ CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.cc
+ ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc COPYONLY)
+ CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.h
+ ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h COPYONLY)
+ ENDIF()
+ ENDIF()
+ENDIF()
+ENDIF()
+
+RUN_BISON(
+ ${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.yy
+ ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc
+ ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h
+)
+
+# Gen_lex_hash
+IF(NOT CMAKE_CROSSCOMPILING)
+ ADD_EXECUTABLE(gen_lex_hash gen_lex_hash.cc)
+ENDIF()
+
+ADD_CUSTOM_COMMAND(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h
+ COMMAND gen_lex_hash > lex_hash.h
+ DEPENDS gen_lex_hash
+)
+
+MYSQL_ADD_EXECUTABLE(mysql_tzinfo_to_sql tztime.cc COMPONENT Server)
+SET_TARGET_PROPERTIES(mysql_tzinfo_to_sql PROPERTIES COMPILE_FLAGS "-DTZINFO2SQL")
+TARGET_LINK_LIBRARIES(mysql_tzinfo_to_sql mysys mysys_ssl)
+
+ADD_CUSTOM_TARGET(
+ GenServerSource
+ DEPENDS ${GEN_SOURCES}
+)
+
+ADD_CUSTOM_TARGET(
+ GenDigestServerSource
+ DEPENDS ${GEN_DIGEST_SOURCES}
+)
+
+#Need this only for embedded
+SET_TARGET_PROPERTIES(GenServerSource PROPERTIES EXCLUDE_FROM_ALL TRUE)
+
+IF(WIN32 OR HAVE_DLOPEN AND NOT DISABLE_SHARED)
+ ADD_LIBRARY(udf_example MODULE udf_example.c)
+ SET_TARGET_PROPERTIES(udf_example PROPERTIES PREFIX "")
+ # udf_example depends on strings
+ IF(WIN32)
+ IF(MSVC)
+ SET_TARGET_PROPERTIES(udf_example PROPERTIES LINK_FLAGS "/DEF:${CMAKE_CURRENT_SOURCE_DIR}/udf_example.def")
+ ENDIF()
+ TARGET_LINK_LIBRARIES(udf_example strings)
+ ELSE()
+ # udf_example is using safemutex exported by mysqld
+ TARGET_LINK_LIBRARIES(udf_example mysqld)
+ ENDIF()
+ENDIF()
+
+FOREACH(tool glibtoolize libtoolize aclocal autoconf autoheader automake gtar
+ tar git)
+ STRING(TOUPPER ${tool} TOOL)
+ FIND_PROGRAM(${TOOL}_EXECUTABLE ${tool} DOC "path to the executable")
+ MARK_AS_ADVANCED(${TOOL}_EXECUTABLE)
+ENDFOREACH()
+
+CONFIGURE_FILE(
+ ${CMAKE_SOURCE_DIR}/cmake/make_dist.cmake.in
+ ${CMAKE_BINARY_DIR}/make_dist.cmake @ONLY)
+
+ADD_CUSTOM_TARGET(dist
+ COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/make_dist.cmake
+ DEPENDS ${CMAKE_BINARY_DIR}/sql/sql_yacc.cc ${CMAKE_BINARY_DIR}/sql/sql_yacc.h
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+)
+
+ADD_CUSTOM_TARGET(distclean
+ COMMAND ${CMAKE_COMMAND} -E echo WARNING: distclean target is not functional
+ COMMAND ${CMAKE_COMMAND} -E echo Use 'git clean -fdx' instead
+ VERBATIM
+ )
+
+IF(INSTALL_LAYOUT STREQUAL "STANDALONE")
+
+# Copy db.opt into data/test/
+SET(DBOPT_FILE ${CMAKE_SOURCE_DIR}/support-files/db.opt )
+INSTALL(FILES ${DBOPT_FILE} DESTINATION data/test COMPONENT DataFiles)
+
+# Install initial database on windows
+IF(NOT CMAKE_CROSSCOMPILING)
+ GET_TARGET_PROPERTY(MYSQLD_EXECUTABLE mysqld LOCATION)
+ENDIF()
+IF(WIN32 AND MYSQLD_EXECUTABLE)
+ CONFIGURE_FILE(
+ ${CMAKE_SOURCE_DIR}/cmake/create_initial_db.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/create_initial_db.cmake
+ @ONLY
+ )
+
+ IF(MSVC_IDE OR CMAKE_GENERATOR MATCHES "Xcode")
+ SET (CONFIG_PARAM -DCONFIG=${CMAKE_CFG_INTDIR})
+ ENDIF()
+ MAKE_DIRECTORY(${CMAKE_CURRENT_BINARY_DIR}/data)
+ ADD_CUSTOM_COMMAND(
+ OUTPUT initdb.dep
+ COMMAND ${CMAKE_COMMAND}
+ ${CONFIG_PARAM} -P ${CMAKE_CURRENT_BINARY_DIR}/create_initial_db.cmake
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data
+ DEPENDS mysqld
+ )
+ ADD_CUSTOM_TARGET(initial_database
+ ALL
+ DEPENDS initdb.dep
+ )
+ INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data DESTINATION .
+ COMPONENT DataFiles
+ PATTERN "initdb.dep" EXCLUDE
+ PATTERN "bootstrap.sql" EXCLUDE
+ PATTERN "aria*" EXCLUDE
+ )
+ELSE()
+ # Not windows or cross compiling, just install an empty directory
+ INSTALL(FILES ${DUMMY_FILE} DESTINATION data/mysql COMPONENT DataFiles)
+ENDIF(WIN32 AND MYSQLD_EXECUTABLE)
+ENDIF(INSTALL_LAYOUT STREQUAL "STANDALONE")
+
+IF(WIN32)
+ SET(my_bootstrap_sql ${CMAKE_CURRENT_BINARY_DIR}/my_bootstrap.sql)
+ FILE(TO_NATIVE_PATH ${my_bootstrap_sql} native_outfile)
+
+ # Create bootstrapper SQL script
+ ADD_CUSTOM_COMMAND(OUTPUT
+ ${my_bootstrap_sql}
+ COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR}/scripts
+ cmd /c copy mysql_system_tables.sql+mysql_system_tables_data.sql+fill_help_tables.sql+mysql_performance_tables.sql ${native_outfile}
+ DEPENDS
+ ${CMAKE_SOURCE_DIR}/scripts/mysql_system_tables.sql
+ ${CMAKE_SOURCE_DIR}/scripts/mysql_system_tables_data.sql
+ ${CMAKE_SOURCE_DIR}/scripts/fill_help_tables.sql
+ ${CMAKE_SOURCE_DIR}/scripts/mysql_performance_tables.sql
+ )
+
+ ADD_CUSTOM_COMMAND(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mysql_bootstrap_sql.c
+ COMMAND comp_sql
+ mysql_bootstrap_sql
+ ${CMAKE_CURRENT_BINARY_DIR}/my_bootstrap.sql
+ mysql_bootstrap_sql.c
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS comp_sql ${my_bootstrap_sql}
+ )
+
+ MYSQL_ADD_EXECUTABLE(mysql_install_db
+ mysql_install_db.cc
+ ${CMAKE_CURRENT_BINARY_DIR}/mysql_bootstrap_sql.c
+ COMPONENT Server
+ )
+ TARGET_LINK_LIBRARIES(mysql_install_db mysys)
+
+ ADD_LIBRARY(winservice STATIC winservice.c)
+ TARGET_LINK_LIBRARIES(winservice shell32)
+ MYSQL_ADD_EXECUTABLE(mysql_upgrade_service
+ mysql_upgrade_service.cc
+ COMPONENT Server)
+ TARGET_LINK_LIBRARIES(mysql_upgrade_service mysys winservice)
+ENDIF(WIN32)
+
+INSTALL(DIRECTORY . DESTINATION ${INSTALL_INCLUDEDIR}/private COMPONENT Development
+ FILES_MATCHING PATTERN "*.h"
+ PATTERN examples EXCLUDE
+ PATTERN share EXCLUDE
+ PATTERN CMakeFiles EXCLUDE)
diff --git a/sql/MSG00001.bin b/sql/MSG00001.bin
new file mode 100644
index 00000000000..89f547694f5
--- /dev/null
+++ b/sql/MSG00001.bin
Binary files differ
diff --git a/sql/add_errmsg b/sql/add_errmsg
new file mode 100755
index 00000000000..86226926d38
--- /dev/null
+++ b/sql/add_errmsg
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+if test $# -ne 1
+then
+ echo "Copies # error messages from share/english/errmsg.txt to other message files"
+ echo "Usage: $0 number_of_messages_to_copy"
+ exit 1;
+fi
+
+FILE=/tmp/add.$$
+tail -$1 share/english/errmsg-utf8.txt > $FILE
+for i in `ls share/*/errmsg-utf8.txt | grep -v english`
+do
+ cat $FILE >> $i
+done
+rm $FILE
+
diff --git a/sql/authors.h b/sql/authors.h
new file mode 100644
index 00000000000..cc9889bcdbc
--- /dev/null
+++ b/sql/authors.h
@@ -0,0 +1,188 @@
+#ifndef AUTHORS_INCLUDED
+#define AUTHORS_INCLUDED
+
+/* Copyright (c) 2005, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+/* Structure of the name list */
+
+struct show_table_authors_st {
+ const char *name;
+ const char *location;
+ const char *comment;
+};
+
+/*
+ Output from "SHOW AUTHORS"
+
+ If you can update it, you get to be in it :)
+
+ Don't be offended if your name is not in here, just add it!
+
+ Active people in the MariaDB are listed first, active people in MySQL
+ then, not active last.
+
+ Names should be encoded using UTF-8.
+
+ See also https://mariadb.com/kb/en/log-of-mariadb-contributions/
+*/
+
+struct show_table_authors_st show_table_authors[]= {
+ /* Active people on MariaDB */
+ { "Michael (Monty) Widenius", "Tusby, Finland",
+ "Lead developer and main author" },
+ { "Sergei Golubchik", "Kerpen, Germany",
+ "Architect, Full-text search, precision math, plugin framework, merges etc" },
+ { "Igor Babaev", "Bellevue, USA", "Optimizer, keycache, core work"},
+ { "Sergey Petrunia", "St. Petersburg, Russia", "Optimizer"},
+ { "Oleksandr Byelkin", "Lugansk, Ukraine",
+ "Query Cache (4.0), Subqueries (4.1), Views (5.0)" },
+ { "Timour Katchaounov", "Sofia , Bulgaria", "Optimizer"},
+ { "Kristian Nielsen", "Copenhagen, Denmark",
+ "Replication, Async client prototocol, General buildbot stuff" },
+ { "Alexander (Bar) Barkov", "Izhevsk, Russia",
+ "Unicode and character sets" },
+ { "Alexey Botchkov (Holyfoot)", "Izhevsk, Russia",
+ "GIS extensions, embedded server, precision math"},
+ { "Daniel Bartholomew", "Raleigh, USA", "MariaDB documentation"},
+ { "Colin Charles", "Selangor, Malesia", "MariaDB documentation, talks at a LOT of conferences"},
+ { "Sergey Vojtovich", "Izhevsk, Russia",
+ "initial implementation of plugin architecture, maintained native storage engines (MyISAM, MEMORY, ARCHIVE, etc), rewrite of table cache"},
+ { "Vladislav Vaintroub", "Mannheim, Germany", "MariaDB Java connector, new thread pool, Windows optimizations"},
+ { "Elena Stepanova", "Sankt Petersburg, Russia", "QA, test cases"},
+ { "Georg Richter", "Heidelberg, Germany", "New LGPL C connector, PHP connector"},
+ { "Jan Lindström", "Ylämylly, Finland", "Working on InnoDB"},
+ { "Lixun Peng", "Hangzhou, China", "Multi Source replication" },
+ { "Olivier Bertrand", "Paris, France", "CONNECT storage engine"},
+ { "Kentoku Shiba", "Tokyo, Japan", "Spider storage engine, metadata_lock_info Information schema"},
+ { "Percona", "CA, USA", "XtraDB, microslow patches, extensions to slow log"},
+ { "Vicentiu Ciorbaru", "Bucharest, Romania", "Roles"},
+ { "Sudheera Palihakkara", "", "PCRE Regular Expressions" },
+ { "Pavel Ivanov", "USA", "Some patches and bug fixes"},
+ { "Konstantin Osipov", "Moscow, Russia",
+ "Prepared statements (4.1), Cursors (5.0), GET_LOCK (10.0)" },
+
+ /* People working on MySQL code base (not NDB) */
+ { "Guilhem Bichot", "Bordeaux, France", "Replication (since 4.0)" },
+ { "Andrei Elkin", "Espoo, Finland", "Replication" },
+ { "Dmitri Lenev", "Moscow, Russia",
+ "Time zones support (4.1), Triggers (5.0)" },
+ { "Marc Alff", "Denver, CO, USA", "Signal, Resignal, Performance schema" },
+ { "Mikael Ronström", "Stockholm, Sweden",
+ "NDB Cluster, Partitioning, online alter table" },
+ { "Ingo Strüwing", "Berlin, Germany",
+ "Bug fixing in MyISAM, Merge tables etc" },
+ {"Marko Mäkelä", "Helsinki, Finland", "InnoDB core developer"},
+
+ /* People not active anymore */
+ { "David Axmark", "London, England",
+ "MySQL founder; Small stuff long time ago, Monty ripped it out!" },
+ { "Brian (Krow) Aker", "Seattle, WA, USA",
+ "Architecture, archive, blackhole, federated, bunch of little stuff :)" },
+ { "Venu Anuganti", "", "Client/server protocol (4.1)" },
+ { "Omer BarNir", "Sunnyvale, CA, USA",
+ "Testing (sometimes) and general QA stuff" },
+ { "John Birrell", "", "Emulation of pthread_mutex() for OS/2" },
+ { "Andreas F. Bobak", "", "AGGREGATE extension to user-defined functions" },
+ { "Reggie Burnett", "Nashville, TN, USA", "Windows development, Connectors" },
+ { "Kent Boortz", "Orebro, Sweden", "Test platform, and general build stuff" },
+ { "Tim Bunce", "", "mysqlhotcopy" },
+ { "Yves Carlier", "", "mysqlaccess" },
+ { "Joshua Chamas", "Cupertino, CA, USA",
+ "Concurrent insert, extended date syntax" },
+ { "Petr Chardin", "Moscow, Russia",
+ "Instance Manager (5.0), Server log tables (5.1)" },
+ { "Wei-Jou Chen", "", "Chinese (Big5) character set" },
+ { "Albert Chin-A-Young", "",
+ "Tru64 port, large file support, better TCP wrappers support" },
+ { "Jorge del Conde", "Mexico City, Mexico", "Windows development" },
+ { "Antony T. Curtis", "Norwalk, CA, USA",
+ "Parser, port to OS/2, storage engines and some random stuff" },
+ { "Yuri Dario", "", "OS/2 port" },
+ { "Patrick Galbraith", "Sharon, NH", "Federated Engine, mysqlslap" },
+ { "Lenz Grimmer", "Hamburg, Germany",
+ "Production (build and release) engineering" },
+ { "Nikolay Grishakin", "Austin, TX, USA", "Testing - Server" },
+ { "Wei He", "", "Chinese (GBK) character set" },
+ { "Eric Herman", "Amsterdam, Netherlands", "Bug fixing - federated" },
+ { "Andrey Hristov", "Walldorf, Germany", "Event scheduler (5.1)" },
+ { "Alexander (Alexi) Ivanov", "St. Petersburg, Russia", "Replication" },
+ { "Mattias Jonsson", "Uppsala, Sweden", "Partitioning" },
+ { "Alexander (Salle) Keremidarski", "Sofia, Bulgaria",
+ "Bug fixing" },
+ { "Mats Kindahl", "Storvreta, Sweden", "Replication" },
+ { "Serge Kozlov", "Velikie Luki, Russia", "Testing - Cluster" },
+ { "Hakan Küçükyılmaz", "Walldorf, Germany", "Testing - Server" },
+ { "Matthias Leich", "Berlin, Germany", "Testing - Server" },
+ { "Arjen Lentz", "Brisbane, Australia",
+ "Documentation (2001-2004), Dutch error messages, LOG2()" },
+ { "Marc Liyanage", "", "Created Mac OS X packages" },
+ { "Kelly Long", "Denver, CO, USA", "Pool Of Threads" },
+ { "Zarko Mocnik", "", "Sorting for Slovenian language" },
+ { "Per-Erik Martin", "Uppsala, Sweden", "Stored Procedures (5.0)" },
+ { "Alexis Mikhailov", "", "User-defined functions" },
+ { "Sinisa Milivojevic", "Larnaca, Cyprus",
+ "UNION (4.0), Subqueries in FROM clause (4.1), many other features" },
+ { "Jonathan (Jeb) Miller", "Kyle, TX, USA",
+ "Testing - Cluster, Replication" },
+ { "Elliot Murphy", "Cocoa, FL, USA", "Replication and backup" },
+ { "Pekka Nouisiainen", "Stockholm, Sweden",
+ "NDB Cluster: BLOB support, character set support, ordered indexes" },
+ { "Alexander Nozdrin", "Moscow, Russia",
+ "Bug fixing (Stored Procedures, 5.0)" },
+ { "Per Eric Olsson", "", "Testing of dynamic record format" },
+ { "Jonas Oreland", "Stockholm, Sweden",
+ "NDB Cluster, Online Backup, lots of other things" },
+ { "Alexander (Sasha) Pachev", "Provo, UT, USA",
+ "Statement-based replication, SHOW CREATE TABLE, mysql-bench" },
+ { "Irena Pancirov", "", "Port to Windows with Borland compiler" },
+ { "Jan Pazdziora", "", "Czech sorting order" },
+ { "Benjamin Pflugmann", "",
+ "Extended MERGE storage engine to handle INSERT" },
+ { "Igor Romanenko", "",
+ "mysqldump" },
+ { "Tõnu Samuel", "Estonia",
+ "VIO interface, other miscellaneous features" },
+ { "Carsten Segieth (Pino)", "Fredersdorf, Germany", "Testing - Server"},
+ { "Martin Sköld", "Stockholm, Sweden",
+ "NDB Cluster: Unique indexes, integration into MySQL" },
+ { "Timothy Smith", "Auckland, New Zealand",
+ "Dynamic character sets, parts of the build system, libmysqld"},
+ { "Miguel Solorzano", "Florianopolis, Santa Catarina, Brazil",
+ "Windows development, Windows NT service"},
+ { "Punita Srivastava", "Austin, TX, USA", "Testing - Merlin"},
+ { "Alexey Stroganov (Ranger)", "Lugansk, Ukraine", "Testing - Benchmarks"},
+ { "Magnus Svensson", "Öregrund, Sweden",
+ "NDB Cluster: Integration into MySQL, test framework" },
+ { "Zeev Suraski", "", "FROM_UNIXTIME(), ENCRYPT()" },
+ { "TAMITO", "",
+ "The _MB character set macros and UJIS and SJIS character sets" },
+ { "Jani Tolonen", "Helsinki, Finland",
+ "mysqlimport, extensions to command-line clients, PROCEDURE ANALYSE()" },
+ { "Lars Thalmann", "Stockholm, Sweden",
+ "Replication and cluster development" },
+ { "Tomas Ulin", "Stockholm, Sweden",
+ "NDB Cluster: Configuration, installation" },
+ { "Gianmassimo Vigazzola", "", "Initial Windows port" },
+ { "Sergey Vojtovich", "Izhevsk, Russia", "Plugins infrastructure (5.1)" },
+ { "Matt Wagner", "Northfield, MN, USA", "Bug fixing" },
+ { "Jim Winstead Jr.", "Los Angeles, CA, USA", "Bug fixing" },
+ { "Peter Zaitsev", "Tacoma, WA, USA",
+ "SHA1(), AES_ENCRYPT(), AES_DECRYPT(), bug fixing" },
+ {"Mark Mark Callaghan", "Texas, USA", "Statistics patches"},
+ {NULL, NULL, NULL}
+};
+
+#endif /* AUTHORS_INCLUDED */
diff --git a/sql/bounded_queue.h b/sql/bounded_queue.h
new file mode 100644
index 00000000000..2d4e6cff96d
--- /dev/null
+++ b/sql/bounded_queue.h
@@ -0,0 +1,195 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#ifndef BOUNDED_QUEUE_INCLUDED
+#define BOUNDED_QUEUE_INCLUDED
+
+#include <string.h>
+#include "my_global.h"
+#include "my_base.h"
+#include "my_sys.h"
+#include "queues.h"
+
+class Sort_param;
+
+/**
+ A priority queue with a fixed, limited size.
+
+ This is a wrapper on top of QUEUE and the queue_xxx() functions.
+ It keeps the top-N elements which are inserted.
+
+ Elements of type Element_type are pushed into the queue.
+ For each element, we call a user-supplied keymaker_function,
+ to generate a key of type Key_type for the element.
+ Instances of Key_type are compared with the user-supplied compare_function.
+
+ The underlying QUEUE implementation needs one extra element for replacing
+ the lowest/highest element when pushing into a full queue.
+ */
+template<typename Element_type, typename Key_type>
+class Bounded_queue
+{
+public:
+ Bounded_queue()
+ {
+ memset(&m_queue, 0, sizeof(m_queue));
+ }
+
+ ~Bounded_queue()
+ {
+ delete_queue(&m_queue);
+ }
+
+ /**
+ Function for making sort-key from input data.
+ @param param Sort parameters.
+ @param to Where to put the key.
+ @param from The input data.
+ */
+ typedef void (*keymaker_function)(Sort_param *param,
+ Key_type *to,
+ Element_type *from);
+
+ /**
+ Function for comparing two keys.
+ @param n Pointer to number of bytes to compare.
+ @param a First key.
+ @param b Second key.
+ @retval -1, 0, or 1 depending on whether the left argument is
+ less than, equal to, or greater than the right argument.
+ */
+ typedef int (*compare_function)(size_t *n, Key_type **a, Key_type **b);
+
+ /**
+ Initialize the queue.
+
+ @param max_elements The size of the queue.
+ @param max_at_top Set to true if you want biggest element on top.
+ false: We keep the n largest elements.
+ pop() will return the smallest key in the result set.
+ true: We keep the n smallest elements.
+ pop() will return the largest key in the result set.
+ @param compare Compare function for elements, takes 3 arguments.
+ If NULL, we use get_ptr_compare(compare_length).
+ @param compare_length Length of the data (i.e. the keys) used for sorting.
+ @param keymaker Function which generates keys for elements.
+ @param sort_param Sort parameters.
+ @param sort_keys Array of pointers to keys to sort.
+
+ @retval 0 OK, 1 Could not allocate memory.
+
+ We do *not* take ownership of any of the input pointer arguments.
+ */
+ int init(ha_rows max_elements, bool max_at_top,
+ compare_function compare, size_t compare_length,
+ keymaker_function keymaker, Sort_param *sort_param,
+ Key_type **sort_keys);
+
+ /**
+ Pushes an element on the queue.
+ If the queue is already full, we discard one element.
+ Calls keymaker_function to generate a key for the element.
+
+ @param element The element to be pushed.
+ */
+ void push(Element_type *element);
+
+ /**
+ Removes the top element from the queue.
+
+ @retval Pointer to the (key of the) removed element.
+
+ @note This function is for unit testing, where we push elements into to the
+ queue, and test that the appropriate keys are retained.
+ Interleaving of push() and pop() operations has not been tested.
+ */
+ Key_type **pop()
+ {
+ // Don't return the extra element to the client code.
+ if (queue_is_full((&m_queue)))
+ queue_remove(&m_queue, 0);
+ DBUG_ASSERT(m_queue.elements > 0);
+ if (m_queue.elements == 0)
+ return NULL;
+ return reinterpret_cast<Key_type**>(queue_remove(&m_queue, 0));
+ }
+
+ /**
+ The number of elements in the queue.
+ */
+ uint num_elements() const { return m_queue.elements; }
+
+ /**
+ Is the queue initialized?
+ */
+ bool is_initialized() const { return m_queue.max_elements > 0; }
+
+private:
+ Key_type **m_sort_keys;
+ size_t m_compare_length;
+ keymaker_function m_keymaker;
+ Sort_param *m_sort_param;
+ st_queue m_queue;
+};
+
+
+template<typename Element_type, typename Key_type>
+int Bounded_queue<Element_type, Key_type>::init(ha_rows max_elements,
+ bool max_at_top,
+ compare_function compare,
+ size_t compare_length,
+ keymaker_function keymaker,
+ Sort_param *sort_param,
+ Key_type **sort_keys)
+{
+ DBUG_ASSERT(sort_keys != NULL);
+
+ m_sort_keys= sort_keys;
+ m_compare_length= compare_length;
+ m_keymaker= keymaker;
+ m_sort_param= sort_param;
+ // init_queue() takes an uint, and also does (max_elements + 1)
+ if (max_elements >= (UINT_MAX - 1))
+ return 1;
+ if (compare == NULL)
+ compare=
+ reinterpret_cast<compare_function>(get_ptr_compare(compare_length));
+ // We allocate space for one extra element, for replace when queue is full.
+ return init_queue(&m_queue, (uint) max_elements + 1,
+ 0, max_at_top,
+ reinterpret_cast<queue_compare>(compare),
+ &m_compare_length, 0, 0);
+}
+
+
+template<typename Element_type, typename Key_type>
+void Bounded_queue<Element_type, Key_type>::push(Element_type *element)
+{
+ DBUG_ASSERT(is_initialized());
+ if (queue_is_full((&m_queue)))
+ {
+ // Replace top element with new key, and re-order the queue.
+ Key_type **pq_top= reinterpret_cast<Key_type **>(queue_top(&m_queue));
+ (*m_keymaker)(m_sort_param, *pq_top, element);
+ queue_replace_top(&m_queue);
+ } else {
+ // Insert new key into the queue.
+ (*m_keymaker)(m_sort_param, m_sort_keys[m_queue.elements], element);
+ queue_insert(&m_queue,
+ reinterpret_cast<uchar*>(&m_sort_keys[m_queue.elements]));
+ }
+}
+
+#endif // BOUNDED_QUEUE_INCLUDED
diff --git a/sql/client_settings.h b/sql/client_settings.h
new file mode 100644
index 00000000000..d6a157f71fd
--- /dev/null
+++ b/sql/client_settings.h
@@ -0,0 +1,53 @@
+/* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+#ifndef CLIENT_SETTINGS_INCLUDED
+#define CLIENT_SETTINGS_INCLUDED
+#else
+#error You have already included an client_settings.h and it should not be included twice
+#endif /* CLIENT_SETTINGS_INCLUDED */
+
+#include <thr_alarm.h>
+#include <sql_common.h>
+
+/*
+ Note: CLIENT_CAPABILITIES is also defined in libmysql/client_settings.h.
+ When adding capabilities here, consider if they should be also added to
+ the libmysql version.
+*/
+#define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | \
+ CLIENT_LONG_FLAG | \
+ CLIENT_TRANSACTIONS | \
+ CLIENT_PROTOCOL_41 | \
+ CLIENT_SECURE_CONNECTION | \
+ CLIENT_PLUGIN_AUTH | \
+ CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | \
+ CLIENT_CONNECT_ATTRS)
+
+#define read_user_name(A) {}
+#undef _CUSTOMCONFIG_
+
+#define mysql_server_init(a,b,c) mysql_client_plugin_init()
+#define mysql_server_end() mysql_client_plugin_deinit()
+
+#ifdef HAVE_REPLICATION
+C_MODE_START
+void slave_io_thread_detach_vio();
+C_MODE_END
+#else
+#define slave_io_thread_detach_vio()
+#endif
+
diff --git a/sql/compat56.cc b/sql/compat56.cc
new file mode 100644
index 00000000000..3bd6b21a154
--- /dev/null
+++ b/sql/compat56.cc
@@ -0,0 +1,445 @@
+/*
+ Copyright (c) 2004, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2013, MariaDB Foundation.
+
+ 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 "my_global.h"
+#include "compat56.h"
+#include "myisampack.h"
+#include "my_time.h"
+
+/*** MySQL56 TIME low-level memory and disk representation routines ***/
+
+/*
+ In-memory format:
+
+ 1 bit sign (Used for sign, when on disk)
+ 1 bit unused (Reserved for wider hour range, e.g. for intervals)
+ 10 bit hour (0-836)
+ 6 bit minute (0-59)
+ 6 bit second (0-59)
+ 24 bits microseconds (0-999999)
+
+ Total: 48 bits = 6 bytes
+ Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
+*/
+
+
+/**
+ Convert time value to MySQL56 numeric packed representation.
+
+ @param ltime The value to convert.
+ @return Numeric packed representation.
+*/
+longlong TIME_to_longlong_time_packed(const MYSQL_TIME *ltime)
+{
+ /* If month is 0, we mix day with hours: "1 00:10:10" -> "24:00:10" */
+ long hms= (((ltime->month ? 0 : ltime->day * 24) + ltime->hour) << 12) |
+ (ltime->minute << 6) | ltime->second;
+ longlong tmp= MY_PACKED_TIME_MAKE(hms, ltime->second_part);
+ return ltime->neg ? -tmp : tmp;
+}
+
+
+
+/**
+ Convert MySQL56 time packed numeric representation to time.
+
+ @param OUT ltime The MYSQL_TIME variable to set.
+ @param tmp The packed numeric representation.
+*/
+void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong tmp)
+{
+ long hms;
+ if ((ltime->neg= (tmp < 0)))
+ tmp= -tmp;
+ hms= MY_PACKED_TIME_GET_INT_PART(tmp);
+ ltime->year= (uint) 0;
+ ltime->month= (uint) 0;
+ ltime->day= (uint) 0;
+ ltime->hour= (uint) (hms >> 12) % (1 << 10); /* 10 bits starting at 12th */
+ ltime->minute= (uint) (hms >> 6) % (1 << 6); /* 6 bits starting at 6th */
+ ltime->second= (uint) hms % (1 << 6); /* 6 bits starting at 0th */
+ ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp);
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+}
+
+
+/**
+ Calculate binary size of MySQL56 packed numeric time representation.
+
+ @param dec Precision.
+*/
+uint my_time_binary_length(uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ return 3 + (dec + 1) / 2;
+}
+
+
+/*
+ On disk we convert from signed representation to unsigned
+ representation using TIMEF_OFS, so all values become binary comparable.
+*/
+#define TIMEF_OFS 0x800000000000LL
+#define TIMEF_INT_OFS 0x800000LL
+
+
+/**
+ Convert MySQL56 in-memory numeric time representation to on-disk representation
+
+ @param nr Value in packed numeric time format.
+ @param OUT ptr The buffer to put value at.
+ @param dec Precision.
+*/
+void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ /* Make sure the stored value was previously properly rounded or truncated */
+ DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) %
+ (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
+
+ switch (dec)
+ {
+ case 0:
+ default:
+ mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
+ break;
+
+ case 1:
+ case 2:
+ mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
+ ptr[3]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000);
+ break;
+
+ case 4:
+ case 3:
+ mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
+ mi_int2store(ptr + 3, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100);
+ break;
+
+ case 5:
+ case 6:
+ mi_int6store(ptr, nr + TIMEF_OFS);
+ break;
+ }
+}
+
+
+/**
+ Convert MySQL56 on-disk time representation to in-memory packed numeric
+ representation.
+
+ @param ptr The pointer to read the value at.
+ @param dec Precision.
+ @return Packed numeric time representation.
+*/
+longlong my_time_packed_from_binary(const uchar *ptr, uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+
+ switch (dec)
+ {
+ case 0:
+ default:
+ {
+ longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
+ return MY_PACKED_TIME_MAKE_INT(intpart);
+ }
+ case 1:
+ case 2:
+ {
+ longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
+ int frac= (uint) ptr[3];
+ if (intpart < 0 && frac)
+ {
+ /*
+ Negative values are stored with reverse fractional part order,
+ for binary sort compatibility.
+
+ Disk value intpart frac Time value Memory value
+ 800000.00 0 0 00:00:00.00 0000000000.000000
+ 7FFFFF.FF -1 255 -00:00:00.01 FFFFFFFFFF.FFD8F0
+ 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0
+ 7FFFFF.00 -1 0 -00:00:01.00 FFFFFFFFFF.000000
+ 7FFFFE.FF -1 255 -00:00:01.01 FFFFFFFFFE.FFD8F0
+ 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960
+
+ Formula to convert fractional part from disk format
+ (now stored in "frac" variable) to absolute value: "0x100 - frac".
+ To reconstruct in-memory value, we shift
+ to the next integer value and then substruct fractional part.
+ */
+ intpart++; /* Shift to the next integer value */
+ frac-= 0x100; /* -(0x100 - frac) */
+ }
+ return MY_PACKED_TIME_MAKE(intpart, frac * 10000);
+ }
+
+ case 3:
+ case 4:
+ {
+ longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
+ int frac= mi_uint2korr(ptr + 3);
+ if (intpart < 0 && frac)
+ {
+ /*
+ Fix reverse fractional part order: "0x10000 - frac".
+ See comments for FSP=1 and FSP=2 above.
+ */
+ intpart++; /* Shift to the next integer value */
+ frac-= 0x10000; /* -(0x10000-frac) */
+ }
+ return MY_PACKED_TIME_MAKE(intpart, frac * 100);
+ }
+
+ case 5:
+ case 6:
+ return ((longlong) mi_uint6korr(ptr)) - TIMEF_OFS;
+ }
+}
+
+
+/*** MySQL56 DATETIME low-level memory and disk representation routines ***/
+
+/*
+ 1 bit sign (used when on disk)
+ 17 bits year*13+month (year 0-9999, month 0-12)
+ 5 bits day (0-31)
+ 5 bits hour (0-23)
+ 6 bits minute (0-59)
+ 6 bits second (0-59)
+ 24 bits microseconds (0-999999)
+
+ Total: 64 bits = 8 bytes
+
+ SYYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
+*/
+
+/**
+ Convert datetime to MySQL56 packed numeric datetime representation.
+ @param ltime The value to convert.
+ @return Packed numeric representation of ltime.
+*/
+longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *ltime)
+{
+ longlong ymd= ((ltime->year * 13 + ltime->month) << 5) | ltime->day;
+ longlong hms= (ltime->hour << 12) | (ltime->minute << 6) | ltime->second;
+ longlong tmp= MY_PACKED_TIME_MAKE(((ymd << 17) | hms), ltime->second_part);
+ DBUG_ASSERT(!check_datetime_range(ltime)); /* Make sure no overflow */
+ return ltime->neg ? -tmp : tmp;
+}
+
+
+/**
+ Convert MySQL56 packed numeric datetime representation to MYSQL_TIME.
+ @param OUT ltime The datetime variable to convert to.
+ @param tmp The packed numeric datetime value.
+*/
+void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong tmp)
+{
+ longlong ymd, hms;
+ longlong ymdhms, ym;
+ if ((ltime->neg= (tmp < 0)))
+ tmp= -tmp;
+
+ ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp);
+ ymdhms= MY_PACKED_TIME_GET_INT_PART(tmp);
+
+ ymd= ymdhms >> 17;
+ ym= ymd >> 5;
+ hms= ymdhms % (1 << 17);
+
+ ltime->day= ymd % (1 << 5);
+ ltime->month= ym % 13;
+ ltime->year= ym / 13;
+
+ ltime->second= hms % (1 << 6);
+ ltime->minute= (hms >> 6) % (1 << 6);
+ ltime->hour= (hms >> 12);
+
+ ltime->time_type= MYSQL_TIMESTAMP_DATETIME;
+}
+
+
+/**
+ Calculate binary size of MySQL56 packed datetime representation.
+ @param dec Precision.
+*/
+uint my_datetime_binary_length(uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ return 5 + (dec + 1) / 2;
+}
+
+
+/*
+ On disk we store as unsigned number with DATETIMEF_INT_OFS offset,
+ for HA_KETYPE_BINARY compatibilty purposes.
+*/
+#define DATETIMEF_INT_OFS 0x8000000000LL
+
+
+/**
+ Convert MySQL56 on-disk datetime representation
+ to in-memory packed numeric representation.
+
+ @param ptr The pointer to read value at.
+ @param dec Precision.
+ @return In-memory packed numeric datetime representation.
+*/
+longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec)
+{
+ longlong intpart= mi_uint5korr(ptr) - DATETIMEF_INT_OFS;
+ int frac;
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ switch (dec)
+ {
+ case 0:
+ default:
+ return MY_PACKED_TIME_MAKE_INT(intpart);
+ case 1:
+ case 2:
+ frac= ((int) (signed char) ptr[5]) * 10000;
+ break;
+ case 3:
+ case 4:
+ frac= mi_sint2korr(ptr + 5) * 100;
+ break;
+ case 5:
+ case 6:
+ frac= mi_sint3korr(ptr + 5);
+ break;
+ }
+ return MY_PACKED_TIME_MAKE(intpart, frac);
+}
+
+
+/**
+ Store MySQL56 in-memory numeric packed datetime representation to disk.
+
+ @param nr In-memory numeric packed datetime representation.
+ @param OUT ptr The pointer to store at.
+ @param dec Precision, 1-6.
+*/
+void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ /* The value being stored must have been properly rounded or truncated */
+ DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) %
+ (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
+
+ mi_int5store(ptr, MY_PACKED_TIME_GET_INT_PART(nr) + DATETIMEF_INT_OFS);
+ switch (dec)
+ {
+ case 0:
+ default:
+ break;
+ case 1:
+ case 2:
+ ptr[5]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000);
+ break;
+ case 3:
+ case 4:
+ mi_int2store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100);
+ break;
+ case 5:
+ case 6:
+ mi_int3store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr));
+ }
+}
+
+
+/*** MySQL56 TIMESTAMP low-level memory and disk representation routines ***/
+
+/**
+ Calculate on-disk size of a timestamp value.
+
+ @param dec Precision.
+*/
+uint my_timestamp_binary_length(uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ return 4 + (dec + 1) / 2;
+}
+
+
+/**
+ Convert MySQL56 binary timestamp representation to in-memory representation.
+
+ @param OUT tm The variable to convert to.
+ @param ptr The pointer to read the value from.
+ @param dec Precision.
+*/
+void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ tm->tv_sec= mi_uint4korr(ptr);
+ switch (dec)
+ {
+ case 0:
+ default:
+ tm->tv_usec= 0;
+ break;
+ case 1:
+ case 2:
+ tm->tv_usec= ((int) ptr[4]) * 10000;
+ break;
+ case 3:
+ case 4:
+ tm->tv_usec= mi_sint2korr(ptr + 4) * 100;
+ break;
+ case 5:
+ case 6:
+ tm->tv_usec= mi_sint3korr(ptr + 4);
+ }
+}
+
+
+/**
+ Convert MySQL56 in-memory timestamp representation to on-disk representation.
+
+ @param tm The value to convert.
+ @param OUT ptr The pointer to store the value to.
+ @param dec Precision.
+*/
+void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec)
+{
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ /* Stored value must have been previously properly rounded or truncated */
+ DBUG_ASSERT((tm->tv_usec %
+ (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
+ mi_int4store(ptr, tm->tv_sec);
+ switch (dec)
+ {
+ case 0:
+ default:
+ break;
+ case 1:
+ case 2:
+ ptr[4]= (unsigned char) (char) (tm->tv_usec / 10000);
+ break;
+ case 3:
+ case 4:
+ mi_int2store(ptr + 4, tm->tv_usec / 100);
+ break;
+ /* Impossible second precision. Fall through */
+ case 5:
+ case 6:
+ mi_int3store(ptr + 4, tm->tv_usec);
+ }
+}
+
+/****************************************/
diff --git a/sql/compat56.h b/sql/compat56.h
new file mode 100644
index 00000000000..bb5e2670f7d
--- /dev/null
+++ b/sql/compat56.h
@@ -0,0 +1,46 @@
+#ifndef COMPAT56_H_INCLUDED
+#define COMPAT56_H_INCLUDED
+/*
+ Copyright (c) 2004, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2013 MariaDB Foundation.
+
+ 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 */
+
+
+/** MySQL56 routines and macros **/
+#define MY_PACKED_TIME_GET_INT_PART(x) ((x) >> 24)
+#define MY_PACKED_TIME_GET_FRAC_PART(x) ((x) % (1LL << 24))
+#define MY_PACKED_TIME_MAKE(i, f) ((((longlong) (i)) << 24) + (f))
+#define MY_PACKED_TIME_MAKE_INT(i) ((((longlong) (i)) << 24))
+
+longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *);
+longlong TIME_to_longlong_time_packed(const MYSQL_TIME *);
+
+void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong nr);
+void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong nr);
+
+void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec);
+longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec);
+uint my_datetime_binary_length(uint dec);
+
+void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec);
+longlong my_time_packed_from_binary(const uchar *ptr, uint dec);
+uint my_time_binary_length(uint dec);
+
+void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec);
+void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec);
+uint my_timestamp_binary_length(uint dec);
+/** End of MySQL routines and macros **/
+
+#endif /* COMPAT56_H_INCLUDED */
diff --git a/sql/contributors.h b/sql/contributors.h
new file mode 100644
index 00000000000..2479f611727
--- /dev/null
+++ b/sql/contributors.h
@@ -0,0 +1,64 @@
+#ifndef CONTRIBUTORS_INCLUDED
+#define CONTRIBUTORS_INCLUDED
+
+/* Copyright (c) 2006 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+/* Structure of the name list */
+
+struct show_table_contributors_st {
+ const char *name;
+ const char *location;
+ const char *comment;
+};
+
+/*
+ Output from "SHOW CONTRIBUTORS"
+
+ Get permission before editing.
+
+ Names should be encoded using UTF-8.
+
+ See also https://mariadb.com/kb/en/log-of-mariadb-contributions/
+*/
+
+struct show_table_contributors_st show_table_contributors[]= {
+ /* MariaDB foundation members, in contribution, size , time order */
+ {"Booking.com", "http://www.booking.com", "Founding member of the MariaDB foundation"},
+ {"SkySQL Ab", "http://www.skysql.com", "Founding member of the MariaDB foundation"},
+ {"Auttomatic", "http://automattic.com", "Member of the MariaDB foundation"},
+ {"Parallels", "http://www.parallels.com/products/plesk", "Founding member of the MariaDB foundation"},
+
+ /* Smaller sponsors, newer per year */
+ {"Verkkokauppa.com", "Finland", "Sponsor of the MariaDB foundation"},
+ {"Webyog", "Bangalor", "Sponsor of the MariaDB foundation"},
+ {"Percona", "USA", "Sponsor of the MariaDB foundation"},
+ {"Jelastic.com", "Russia", "Sponsor of the MariaDB foundation"},
+ {"Planetta.net", "Finland", "Sponsor of the MariaDB foundation"},
+ {"Open query", "Australia", "Sponsor of the MariaDB foundation"},
+
+ /* Sponsors of important features */
+ {"Google", "USA", "Sponsoring parallel replication and GTID" },
+ {"Facebook", "USA", "Sponsoring non-blocking API, LIMIT ROWS EXAMINED etc"},
+
+ /* Individual contributors, names in historical order, newer first */
+ {"Ronald Bradford", "Brisbane, Australia", "EFF contribution for UC2006 Auction"},
+ {"Sheeri Kritzer", "Boston, Mass. USA", "EFF contribution for UC2006 Auction"},
+ {"Mark Shuttleworth", "London, UK.", "EFF contribution for UC2006 Auction"},
+ {NULL, NULL, NULL}
+};
+
+#endif /* CONTRIBUTORS_INCLUDED */
diff --git a/sql/create_options.cc b/sql/create_options.cc
new file mode 100644
index 00000000000..09153f7e35c
--- /dev/null
+++ b/sql/create_options.cc
@@ -0,0 +1,794 @@
+/* Copyright (C) 2010 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @file
+
+ Engine defined options of tables/fields/keys in CREATE/ALTER TABLE.
+*/
+
+#include "create_options.h"
+#include <my_getopt.h>
+#include "set_var.h"
+
+#define FRM_QUOTED_VALUE 0x8000
+
+/**
+ Links this item to the given list end
+
+ @param start The list beginning or NULL
+ @param end The list last element or does not matter
+*/
+
+void engine_option_value::link(engine_option_value **start,
+ engine_option_value **end)
+{
+ DBUG_ENTER("engine_option_value::link");
+ DBUG_PRINT("enter", ("name: '%s' (%u) value: '%s' (%u)",
+ name.str, (uint) name.length,
+ value.str, (uint) value.length));
+ engine_option_value *opt;
+ /* check duplicates to avoid writing them to frm*/
+ for(opt= *start;
+ opt && ((opt->parsed && !opt->value.str) ||
+ my_strnncoll(system_charset_info,
+ (uchar *)name.str, name.length,
+ (uchar*)opt->name.str, opt->name.length));
+ opt= opt->next) /* no-op */;
+ if (opt)
+ {
+ opt->value.str= NULL; /* remove previous value */
+ opt->parsed= TRUE; /* and don't issue warnings for it anymore */
+ }
+ /*
+ Add this option to the end of the list
+
+ @note: We add even if it is opt->value.str == NULL because it can be
+ ALTER TABLE to remove the option.
+ */
+ if (*start)
+ {
+ (*end)->next= this;
+ *end= this;
+ }
+ else
+ {
+ /*
+ note that is *start == 0, the value of *end does not matter,
+ it can be uninitialized.
+ */
+ *start= *end= this;
+ }
+ DBUG_VOID_RETURN;
+}
+
+static bool report_wrong_value(THD *thd, const char *name, const char *val,
+ bool suppress_warning)
+{
+ if (suppress_warning)
+ return 0;
+
+ if (!(thd->variables.sql_mode & MODE_IGNORE_BAD_TABLE_OPTIONS) &&
+ !thd->slave_thread)
+ {
+ my_error(ER_BAD_OPTION_VALUE, MYF(0), val, name);
+ return 1;
+ }
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BAD_OPTION_VALUE,
+ ER(ER_BAD_OPTION_VALUE), val, name);
+ return 0;
+}
+
+static bool report_unknown_option(THD *thd, engine_option_value *val,
+ bool suppress_warning)
+{
+ DBUG_ENTER("report_unknown_option");
+
+ if (val->parsed || suppress_warning)
+ {
+ DBUG_PRINT("info", ("parsed => exiting"));
+ DBUG_RETURN(FALSE);
+ }
+
+ if (!(thd->variables.sql_mode & MODE_IGNORE_BAD_TABLE_OPTIONS) &&
+ !thd->slave_thread)
+ {
+ my_error(ER_UNKNOWN_OPTION, MYF(0), val->name.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_OPTION, ER(ER_UNKNOWN_OPTION), val->name.str);
+ DBUG_RETURN(FALSE);
+}
+
+#define value_ptr(STRUCT,OPT) ((char*)(STRUCT) + (OPT)->offset)
+
+static bool set_one_value(ha_create_table_option *opt,
+ THD *thd, const LEX_STRING *value, void *base,
+ bool suppress_warning,
+ MEM_ROOT *root)
+{
+ DBUG_ENTER("set_one_value");
+ DBUG_PRINT("enter", ("opt: 0x%lx type: %u name '%s' value: '%s'",
+ (ulong) opt,
+ opt->type, opt->name,
+ (value->str ? value->str : "<DEFAULT>")));
+ switch (opt->type)
+ {
+ case HA_OPTION_TYPE_SYSVAR:
+ DBUG_ASSERT(0); // HA_OPTION_TYPE_SYSVAR's are replaced in resolve_sysvars()
+ case HA_OPTION_TYPE_ULL:
+ {
+ ulonglong *val= (ulonglong*)value_ptr(base, opt);
+ if (!value->str)
+ {
+ *val= opt->def_value;
+ DBUG_RETURN(0);
+ }
+
+ my_option optp=
+ { opt->name, 1, 0, (uchar **)val, 0, 0, GET_ULL,
+ REQUIRED_ARG, (longlong)opt->def_value, (longlong)opt->min_value,
+ opt->max_value, 0, (long) opt->block_size, 0};
+
+ ulonglong orig_val= strtoull(value->str, NULL, 10);
+ my_bool unused;
+ *val= orig_val;
+ *val= getopt_ull_limit_value(*val, &optp, &unused);
+ if (*val == orig_val)
+ DBUG_RETURN(0);
+
+ DBUG_RETURN(report_wrong_value(thd, opt->name, value->str,
+ suppress_warning));
+ }
+ case HA_OPTION_TYPE_STRING:
+ {
+ char **val= (char **)value_ptr(base, opt);
+ if (!value->str)
+ {
+ *val= 0;
+ DBUG_RETURN(0);
+ }
+
+ if (!(*val= strmake_root(root, value->str, value->length)))
+ DBUG_RETURN(1);
+ DBUG_RETURN(0);
+ }
+ case HA_OPTION_TYPE_ENUM:
+ {
+ uint *val= (uint *)value_ptr(base, opt), num;
+
+ *val= (uint) opt->def_value;
+ if (!value->str)
+ DBUG_RETURN(0);
+
+ const char *start= opt->values, *end;
+
+ num= 0;
+ while (*start)
+ {
+ for (end=start;
+ *end && *end != ',';
+ end+= my_mbcharlen(system_charset_info, *end)) /* no-op */;
+ if (!my_strnncoll(system_charset_info,
+ (uchar*)start, end-start,
+ (uchar*)value->str, value->length))
+ {
+ *val= num;
+ DBUG_RETURN(0);
+ }
+ if (*end)
+ end++;
+ start= end;
+ num++;
+ }
+
+ DBUG_RETURN(report_wrong_value(thd, opt->name, value->str,
+ suppress_warning));
+ }
+ case HA_OPTION_TYPE_BOOL:
+ {
+ bool *val= (bool *)value_ptr(base, opt);
+ *val= opt->def_value;
+
+ if (!value->str)
+ DBUG_RETURN(0);
+
+ if (!my_strnncoll(system_charset_info,
+ (const uchar*)"NO", 2,
+ (uchar *)value->str, value->length) ||
+ !my_strnncoll(system_charset_info,
+ (const uchar*)"OFF", 3,
+ (uchar *)value->str, value->length) ||
+ !my_strnncoll(system_charset_info,
+ (const uchar*)"0", 1,
+ (uchar *)value->str, value->length))
+ {
+ *val= FALSE;
+ DBUG_RETURN(FALSE);
+ }
+
+ if (!my_strnncoll(system_charset_info,
+ (const uchar*)"YES", 3,
+ (uchar *)value->str, value->length) ||
+ !my_strnncoll(system_charset_info,
+ (const uchar*)"ON", 2,
+ (uchar *)value->str, value->length) ||
+ !my_strnncoll(system_charset_info,
+ (const uchar*)"1", 1,
+ (uchar *)value->str, value->length))
+ {
+ *val= TRUE;
+ DBUG_RETURN(FALSE);
+ }
+
+ DBUG_RETURN(report_wrong_value(thd, opt->name, value->str,
+ suppress_warning));
+ }
+ }
+ DBUG_ASSERT(0);
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ DBUG_RETURN(1);
+}
+
+static const size_t ha_option_type_sizeof[]=
+{ sizeof(ulonglong), sizeof(char *), sizeof(uint), sizeof(bool)};
+
+/**
+ Creates option structure and parses list of options in it
+
+ @param thd thread handler
+ @param option_struct where to store pointer on the option struct
+ @param option_list list of options given by user
+ @param rules list of option description by engine
+ @param suppress_warning second parse so we do not need warnings
+ @param root MEM_ROOT where allocate memory
+
+ @retval TRUE Error
+ @retval FALSE OK
+*/
+
+bool parse_option_list(THD* thd, handlerton *hton, void *option_struct_arg,
+ engine_option_value **option_list,
+ ha_create_table_option *rules,
+ bool suppress_warning, MEM_ROOT *root)
+{
+ ha_create_table_option *opt;
+ size_t option_struct_size= 0;
+ engine_option_value *val, *last;
+ void **option_struct= (void**)option_struct_arg;
+ DBUG_ENTER("parse_option_list");
+ DBUG_PRINT("enter",
+ ("struct: %p list: %p rules: %p suppress_warning: %u root: %p",
+ *option_struct, *option_list, rules,
+ (uint) suppress_warning, root));
+
+ if (rules)
+ {
+ for (opt= rules; opt->name; opt++)
+ set_if_bigger(option_struct_size, opt->offset +
+ ha_option_type_sizeof[opt->type]);
+
+ *option_struct= alloc_root(root, option_struct_size);
+ }
+
+ for (opt= rules; rules && opt->name; opt++)
+ {
+ bool seen=false;
+ for (val= *option_list; val; val= val->next)
+ {
+ last= val;
+ if (my_strnncoll(system_charset_info,
+ (uchar*)opt->name, opt->name_length,
+ (uchar*)val->name.str, val->name.length))
+ continue;
+
+ seen=true;
+
+ if (val->parsed && !val->value.str)
+ continue;
+
+ if (set_one_value(opt, thd, &val->value,
+ *option_struct, suppress_warning || val->parsed, root))
+ DBUG_RETURN(TRUE);
+ val->parsed= true;
+ break;
+ }
+ if (!seen)
+ {
+ LEX_STRING default_val= null_lex_str;
+
+ /*
+ If it's CREATE/ALTER TABLE parsing mode (options are created in the
+ transient thd->mem_root, not in the long living TABLE_SHARE::mem_root),
+ and variable-backed option was not explicitly set.
+
+ If it's not create, but opening of the existing frm (that was,
+ probably, created with the older version of the storage engine and
+ does not have this option stored), we take the *default* value of the
+ sysvar, not the *current* value. Because we don't want to have
+ different option values for the same table if it's opened many times.
+ */
+ if (root == thd->mem_root && opt->var)
+ {
+ // take a value from the variable and add it to the list
+ sys_var *sysvar= find_hton_sysvar(hton, opt->var);
+ DBUG_ASSERT(sysvar);
+
+ char buf[256];
+ String sbuf(buf, sizeof(buf), system_charset_info), *str;
+ if ((str= sysvar->val_str(&sbuf, thd, OPT_SESSION, 0)))
+ {
+ LEX_STRING name= { const_cast<char*>(opt->name), opt->name_length };
+ default_val.str= strmake_root(root, str->ptr(), str->length());
+ default_val.length= str->length();
+ val= new (root) engine_option_value(name, default_val, true,
+ option_list, &last);
+ val->parsed= true;
+ }
+ }
+ set_one_value(opt, thd, &default_val, *option_struct,
+ suppress_warning, root);
+ }
+ }
+
+ for (val= *option_list; val; val= val->next)
+ {
+ if (report_unknown_option(thd, val, suppress_warning))
+ DBUG_RETURN(TRUE);
+ val->parsed= true;
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Resolves all HA_OPTION_TYPE_SYSVAR elements.
+
+ This is done when an engine is loaded.
+*/
+static bool resolve_sysvars(handlerton *hton, ha_create_table_option *rules)
+{
+ for (ha_create_table_option *opt= rules; rules && opt->name; opt++)
+ {
+ if (opt->type == HA_OPTION_TYPE_SYSVAR)
+ {
+ struct my_option optp;
+ plugin_opt_set_limits(&optp, opt->var);
+ switch(optp.var_type) {
+ case GET_ULL:
+ case GET_ULONG:
+ case GET_UINT:
+ opt->type= HA_OPTION_TYPE_ULL;
+ opt->def_value= (ulonglong)optp.def_value;
+ opt->min_value= (ulonglong)optp.min_value;
+ opt->max_value= (ulonglong)optp.max_value;
+ opt->block_size= (ulonglong)optp.block_size;
+ break;
+ case GET_STR:
+ case GET_STR_ALLOC:
+ opt->type= HA_OPTION_TYPE_STRING;
+ break;
+ case GET_BOOL:
+ opt->type= HA_OPTION_TYPE_BOOL;
+ opt->def_value= optp.def_value;
+ break;
+ case GET_ENUM:
+ {
+ opt->type= HA_OPTION_TYPE_ENUM;
+ opt->def_value= optp.def_value;
+
+ char buf[256];
+ String str(buf, sizeof(buf), system_charset_info);
+ str.length(0);
+ for (const char **s= optp.typelib->type_names; *s; s++)
+ {
+ if (str.append(*s) || str.append(','))
+ return 1;
+ }
+ DBUG_ASSERT(str.length());
+ opt->values= my_strndup(str.ptr(), str.length()-1, MYF(MY_WME));
+ if (!opt->values)
+ return 1;
+ break;
+ }
+ default:
+ DBUG_ASSERT(0);
+ }
+ }
+ }
+ return 0;
+}
+
+bool resolve_sysvar_table_options(handlerton *hton)
+{
+ return resolve_sysvars(hton, hton->table_options) ||
+ resolve_sysvars(hton, hton->field_options) ||
+ resolve_sysvars(hton, hton->index_options);
+}
+
+/*
+ Restore HA_OPTION_TYPE_SYSVAR options back as they were
+ before resolve_sysvars().
+
+ This is done when the engine is unloaded, so that we could
+ call resolve_sysvars() if the engine is installed again.
+*/
+static void free_sysvars(handlerton *hton, ha_create_table_option *rules)
+{
+ for (ha_create_table_option *opt= rules; rules && opt->name; opt++)
+ {
+ if (opt->var)
+ {
+ my_free(const_cast<char*>(opt->values));
+ opt->type= HA_OPTION_TYPE_SYSVAR;
+ opt->def_value= 0;
+ opt->min_value= 0;
+ opt->max_value= 0;
+ opt->block_size= 0;
+ opt->values= 0;
+ }
+ }
+}
+
+void free_sysvar_table_options(handlerton *hton)
+{
+ free_sysvars(hton, hton->table_options);
+ free_sysvars(hton, hton->field_options);
+ free_sysvars(hton, hton->index_options);
+}
+
+
+/**
+ Parses all table/fields/keys options
+
+ @param thd thread handler
+ @param file handler of the table
+ @parem share descriptor of the table
+
+ @retval TRUE Error
+ @retval FALSE OK
+*/
+
+bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share)
+{
+ MEM_ROOT *root= &share->mem_root;
+ DBUG_ENTER("parse_engine_table_options");
+
+ if (parse_option_list(thd, ht, &share->option_struct, & share->option_list,
+ ht->table_options, TRUE, root))
+ DBUG_RETURN(TRUE);
+
+ for (Field **field= share->field; *field; field++)
+ {
+ if (parse_option_list(thd, ht, &(*field)->option_struct,
+ & (*field)->option_list,
+ ht->field_options, TRUE, root))
+ DBUG_RETURN(TRUE);
+ }
+
+ for (uint index= 0; index < share->keys; index ++)
+ {
+ if (parse_option_list(thd, ht, &share->key_info[index].option_struct,
+ & share->key_info[index].option_list,
+ ht->index_options, TRUE, root))
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+bool engine_options_differ(void *old_struct, void *new_struct,
+ ha_create_table_option *rules)
+{
+ ha_create_table_option *opt;
+ for (opt= rules; rules && opt->name; opt++)
+ {
+ char **old_val= (char**)value_ptr(old_struct, opt);
+ char **new_val= (char**)value_ptr(new_struct, opt);
+ int neq;
+ if (opt->type == HA_OPTION_TYPE_STRING)
+ neq= (*old_val && *new_val) ? strcmp(*old_val, *new_val) : *old_val != *new_val;
+ else
+ neq= memcmp(old_val, new_val, ha_option_type_sizeof[opt->type]);
+ if (neq)
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ Returns representation length of key and value in the frm file
+*/
+
+uint engine_option_value::frm_length()
+{
+ /*
+ 1 byte - name length
+ 2 bytes - value length
+
+ if value.str is NULL, this option is not written to frm (=DEFAULT)
+ */
+ return value.str ? 1 + name.length + 2 + value.length : 0;
+}
+
+
+/**
+ Returns length of representation of option list in the frm file
+*/
+
+static uint option_list_frm_length(engine_option_value *opt)
+{
+ uint res= 0;
+
+ for (; opt; opt= opt->next)
+ res+= opt->frm_length();
+
+ return res;
+}
+
+
+/**
+ Calculates length of options image in the .frm
+
+ @param table_option_list list of table options
+ @param create_fields field descriptors list
+ @param keys number of keys
+ @param key_info array of key descriptors
+
+ @returns length of image in frm
+*/
+
+uint engine_table_options_frm_length(engine_option_value *table_option_list,
+ List<Create_field> &create_fields,
+ uint keys, KEY *key_info)
+{
+ List_iterator<Create_field> it(create_fields);
+ Create_field *field;
+ uint res, index;
+ DBUG_ENTER("engine_table_options_frm_length");
+
+ res= option_list_frm_length(table_option_list);
+
+ while ((field= it++))
+ res+= option_list_frm_length(field->option_list);
+
+ for (index= 0; index < keys; index++, key_info++)
+ res+= option_list_frm_length(key_info->option_list);
+
+ /*
+ if there's at least one option somewhere (res > 0)
+ we write option lists for all fields and keys, zero-terminated.
+ If there're no options we write nothing at all (backward compatibility)
+ */
+ DBUG_RETURN(res ? res + 1 + create_fields.elements + keys : 0);
+}
+
+
+/**
+ Writes image of the key and value to the frm image buffer
+
+ @param buff pointer to the buffer free space beginning
+
+ @returns pointer to byte after last recorded in the buffer
+*/
+
+uchar *engine_option_value::frm_image(uchar *buff)
+{
+ if (value.str)
+ {
+ *buff++= name.length;
+ memcpy(buff, name.str, name.length);
+ buff+= name.length;
+ int2store(buff, value.length | (quoted_value ? FRM_QUOTED_VALUE : 0));
+ buff+= 2;
+ memcpy(buff, (const uchar *) value.str, value.length);
+ buff+= value.length;
+ }
+ return buff;
+}
+
+/**
+ Writes image of the key and value to the frm image buffer
+
+ @param buff pointer to the buffer to store the options in
+ @param opt list of options;
+
+ @returns pointer to the end of the stored data in the buffer
+*/
+static uchar *option_list_frm_image(uchar *buff, engine_option_value *opt)
+{
+ for (; opt; opt= opt->next)
+ buff= opt->frm_image(buff);
+
+ *buff++= 0;
+ return buff;
+}
+
+
+/**
+ Writes options image in the .frm buffer
+
+ @param buff pointer to the buffer
+ @param table_option_list list of table options
+ @param create_fields field descriptors list
+ @param keys number of keys
+ @param key_info array of key descriptors
+
+ @returns pointer to byte after last recorded in the buffer
+*/
+
+uchar *engine_table_options_frm_image(uchar *buff,
+ engine_option_value *table_option_list,
+ List<Create_field> &create_fields,
+ uint keys, KEY *key_info)
+{
+ List_iterator<Create_field> it(create_fields);
+ Create_field *field;
+ KEY *key_info_end= key_info + keys;
+ DBUG_ENTER("engine_table_options_frm_image");
+
+ buff= option_list_frm_image(buff, table_option_list);
+
+ while ((field= it++))
+ buff= option_list_frm_image(buff, field->option_list);
+
+ while (key_info < key_info_end)
+ buff= option_list_frm_image(buff, (key_info++)->option_list);
+
+ DBUG_RETURN(buff);
+}
+
+/**
+ Reads name and value from buffer, then link it in the list
+
+ @param buff the buffer to read from
+ @param start The list beginning or NULL
+ @param end The list last element or does not matter
+ @param root MEM_ROOT for allocating
+
+ @returns pointer to byte after last recorded in the buffer
+*/
+uchar *engine_option_value::frm_read(const uchar *buff, engine_option_value **start,
+ engine_option_value **end, MEM_ROOT *root)
+{
+ LEX_STRING name, value;
+ uint len;
+
+ name.length= buff[0];
+ buff++;
+ if (!(name.str= strmake_root(root, (const char*)buff, name.length)))
+ return NULL;
+ buff+= name.length;
+ len= uint2korr(buff);
+ value.length= len & ~FRM_QUOTED_VALUE;
+ buff+= 2;
+ if (!(value.str= strmake_root(root, (const char*)buff, value.length)))
+ return NULL;
+ buff+= value.length;
+
+ engine_option_value *ptr=new (root)
+ engine_option_value(name, value, len & FRM_QUOTED_VALUE, start, end);
+ if (!ptr)
+ return NULL;
+
+ return (uchar *)buff;
+}
+
+
+/**
+ Reads options from this buffer
+
+ @param buff the buffer to read from
+ @param length buffer length
+ @param share table descriptor
+ @param root MEM_ROOT for allocating
+
+ @retval TRUE Error
+ @retval FALSE OK
+*/
+
+bool engine_table_options_frm_read(const uchar *buff, uint length,
+ TABLE_SHARE *share)
+{
+ const uchar *buff_end= buff + length;
+ engine_option_value *UNINIT_VAR(end);
+ MEM_ROOT *root= &share->mem_root;
+ uint count;
+ DBUG_ENTER("engine_table_options_frm_read");
+
+ while (buff < buff_end && *buff)
+ {
+ if (!(buff= engine_option_value::frm_read(buff, &share->option_list, &end,
+ root)))
+ DBUG_RETURN(TRUE);
+ }
+ buff++;
+
+ for (count=0; count < share->fields; count++)
+ {
+ while (buff < buff_end && *buff)
+ {
+ if (!(buff= engine_option_value::frm_read(buff,
+ &share->field[count]->option_list,
+ &end, root)))
+ DBUG_RETURN(TRUE);
+ }
+ buff++;
+ }
+
+ for (count=0; count < share->keys; count++)
+ {
+ while (buff < buff_end && *buff)
+ {
+ if (!(buff= engine_option_value::frm_read(buff,
+ &share->key_info[count].option_list,
+ &end, root)))
+ DBUG_RETURN(TRUE);
+ }
+ buff++;
+ }
+
+ if (buff < buff_end)
+ sql_print_warning("Table '%s' was created in a later MariaDB version - "
+ "unknown table attributes were ignored",
+ share->table_name.str);
+
+ DBUG_RETURN(buff > buff_end);
+}
+
+/**
+ Merges two lists of engine_option_value's with duplicate removal.
+*/
+
+engine_option_value *merge_engine_table_options(engine_option_value *first,
+ engine_option_value *second,
+ MEM_ROOT *root)
+{
+ engine_option_value *end, *opt;
+ DBUG_ENTER("merge_engine_table_options");
+ LINT_INIT(end);
+
+ /* Create copy of first list */
+ for (opt= first, first= 0; opt; opt= opt->next)
+ new (root) engine_option_value(opt, &first, &end);
+
+ for (opt= second; opt; opt= opt->next)
+ new (root) engine_option_value(opt->name, opt->value, opt->quoted_value,
+ &first, &end);
+ DBUG_RETURN(first);
+}
+
+bool is_engine_option_known(engine_option_value *opt,
+ ha_create_table_option *rules)
+{
+ if (!rules)
+ return false;
+
+ for (; rules->name; rules++)
+ {
+ if (!my_strnncoll(system_charset_info,
+ (uchar*)rules->name, rules->name_length,
+ (uchar*)opt->name.str, opt->name.length))
+ return true;
+ }
+ return false;
+}
+
diff --git a/sql/create_options.h b/sql/create_options.h
new file mode 100644
index 00000000000..eb21f291ff4
--- /dev/null
+++ b/sql/create_options.h
@@ -0,0 +1,104 @@
+/* Copyright (C) 2010 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @file
+
+ Engine defined options of tables/fields/keys in CREATE/ALTER TABLE.
+*/
+
+#ifndef SQL_CREATE_OPTIONS_INCLUDED
+#define SQL_CREATE_OPTIONS_INCLUDED
+
+#include "sql_class.h"
+//#include "handler.h"
+
+class engine_option_value: public Sql_alloc
+{
+ public:
+ LEX_STRING name;
+ LEX_STRING value;
+ engine_option_value *next; ///< parser puts them in a FIFO linked list
+ bool parsed; ///< to detect unrecognized options
+ bool quoted_value; ///< option=VAL vs. option='VAL'
+
+ engine_option_value(engine_option_value *src,
+ engine_option_value **start, engine_option_value **end) :
+ name(src->name), value(src->value),
+ next(NULL), parsed(src->parsed), quoted_value(src->quoted_value)
+ {
+ link(start, end);
+ }
+ engine_option_value(LEX_STRING &name_arg, LEX_STRING &value_arg, bool quoted,
+ engine_option_value **start, engine_option_value **end) :
+ name(name_arg), value(value_arg),
+ next(NULL), parsed(false), quoted_value(quoted)
+ {
+ link(start, end);
+ }
+ engine_option_value(LEX_STRING &name_arg,
+ engine_option_value **start, engine_option_value **end) :
+ name(name_arg), value(null_lex_str),
+ next(NULL), parsed(false), quoted_value(false)
+ {
+ link(start, end);
+ }
+ engine_option_value(LEX_STRING &name_arg, ulonglong value_arg,
+ engine_option_value **start, engine_option_value **end,
+ MEM_ROOT *root) :
+ name(name_arg), next(NULL), parsed(false), quoted_value(false)
+ {
+ if ((value.str= (char *)alloc_root(root, 22)))
+ {
+ value.length= longlong10_to_str(value_arg, value.str, 10) - value.str;
+ link(start, end);
+ }
+ }
+ static uchar *frm_read(const uchar *buff, engine_option_value **start,
+ engine_option_value **end, MEM_ROOT *root);
+ void link(engine_option_value **start, engine_option_value **end);
+ uint frm_length();
+ uchar *frm_image(uchar *buff);
+};
+
+typedef struct st_key KEY;
+class Create_field;
+
+bool resolve_sysvar_table_options(handlerton *hton);
+void free_sysvar_table_options(handlerton *hton);
+bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share);
+bool parse_option_list(THD* thd, handlerton *hton, void *option_struct,
+ engine_option_value **option_list,
+ ha_create_table_option *rules,
+ bool suppress_warning, MEM_ROOT *root);
+bool engine_table_options_frm_read(const uchar *buff, uint length,
+ TABLE_SHARE *share);
+engine_option_value *merge_engine_table_options(engine_option_value *source,
+ engine_option_value *changes,
+ MEM_ROOT *root);
+
+uint engine_table_options_frm_length(engine_option_value *table_option_list,
+ List<Create_field> &create_fields,
+ uint keys, KEY *key_info);
+uchar *engine_table_options_frm_image(uchar *buff,
+ engine_option_value *table_option_list,
+ List<Create_field> &create_fields,
+ uint keys, KEY *key_info);
+
+bool engine_options_differ(void *old_struct, void *new_struct,
+ ha_create_table_option *rules);
+bool is_engine_option_known(engine_option_value *opt,
+ ha_create_table_option *rules);
+#endif
diff --git a/sql/custom_conf.h b/sql/custom_conf.h
new file mode 100644
index 00000000000..62fdb619c27
--- /dev/null
+++ b/sql/custom_conf.h
@@ -0,0 +1,28 @@
+/* Copyright (c) 2000, 2006 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#ifndef __MYSQL_CUSTOM_BUILD_CONFIG__
+#define __MYSQL_CUSTOM_BUILD_CONFIG__
+
+#define MYSQL_PORT 5002
+#ifdef __WIN__
+#define MYSQL_NAMEDPIPE "SwSqlServer"
+#define MYSQL_SERVICENAME "SwSqlServer"
+#define KEY_SERVICE_PARAMETERS
+"SYSTEM\\CurrentControlSet\\Services\\SwSqlServer\\Parameters"
+#endif
+
+#endif /* __MYSQL_CUSTOM_BUILD_CONFIG__ */
diff --git a/sql/datadict.cc b/sql/datadict.cc
new file mode 100644
index 00000000000..62d60ed15a1
--- /dev/null
+++ b/sql/datadict.cc
@@ -0,0 +1,176 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "datadict.h"
+#include "sql_priv.h"
+#include "sql_class.h"
+#include "sql_table.h"
+
+static int read_string(File file, uchar**to, size_t length)
+{
+ DBUG_ENTER("read_string");
+
+ my_free(*to);
+ if (!(*to= (uchar*) my_malloc(length+1,MYF(MY_WME))) ||
+ mysql_file_read(file, *to, length, MYF(MY_NABP)))
+ {
+ my_free(*to);
+ *to= 0;
+ DBUG_RETURN(1);
+ }
+ *((char*) *to+length)= '\0'; // C-style safety
+ DBUG_RETURN (0);
+}
+
+
+/**
+ Check type of .frm if we are not going to parse it.
+
+ @param[in] thd The current session.
+ @param[in] path path to FRM file.
+ @param[out] dbt db_type of the table if FRMTYPE_TABLE, otherwise undefined.
+
+ @retval FRMTYPE_ERROR error
+ @retval FRMTYPE_TABLE table
+ @retval FRMTYPE_VIEW view
+*/
+
+frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt)
+{
+ File file;
+ uchar header[10]; //"TYPE=VIEW\n" it is 10 characters
+ size_t error;
+ frm_type_enum type= FRMTYPE_ERROR;
+ DBUG_ENTER("dd_frm_type");
+
+ *dbt= DB_TYPE_UNKNOWN;
+
+ if ((file= mysql_file_open(key_file_frm, path, O_RDONLY | O_SHARE, MYF(0))) < 0)
+ DBUG_RETURN(FRMTYPE_ERROR);
+ error= mysql_file_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP));
+
+ if (error)
+ goto err;
+ if (!strncmp((char*) header, "TYPE=VIEW\n", sizeof(header)))
+ {
+ type= FRMTYPE_VIEW;
+ goto err;
+ }
+
+ type= FRMTYPE_TABLE;
+
+ /*
+ This is just a check for DB_TYPE. We'll return default unknown type
+ if the following test is true (arg #3). This should not have effect
+ on return value from this function (default FRMTYPE_TABLE)
+ */
+ if (!is_binary_frm_header(header))
+ goto err;
+
+ *dbt= (enum legacy_db_type) (uint) *(header + 3);
+
+ if (*dbt >= DB_TYPE_FIRST_DYNAMIC) /* read the true engine name */
+ {
+ MY_STAT state;
+ uchar *frm_image= 0;
+ uint n_length;
+
+ if (mysql_file_fstat(file, &state, MYF(MY_WME)))
+ goto err;
+
+ if (mysql_file_seek(file, 0, SEEK_SET, MYF(MY_WME)))
+ goto err;
+
+ if (read_string(file, &frm_image, state.st_size))
+ goto err;
+
+ if ((n_length= uint4korr(frm_image+55)))
+ {
+ uint record_offset= uint2korr(frm_image+6)+
+ ((uint2korr(frm_image+14) == 0xffff ?
+ uint4korr(frm_image+47) : uint2korr(frm_image+14)));
+ uint reclength= uint2korr(frm_image+16);
+
+ uchar *next_chunk= frm_image + record_offset + reclength;
+ uchar *buff_end= next_chunk + n_length;
+ uint connect_string_length= uint2korr(next_chunk);
+ next_chunk+= connect_string_length + 2;
+ if (next_chunk + 2 < buff_end)
+ {
+ uint str_db_type_length= uint2korr(next_chunk);
+ LEX_STRING name;
+ name.str= (char*) next_chunk + 2;
+ name.length= str_db_type_length;
+ plugin_ref tmp_plugin= ha_resolve_by_name(thd, &name);
+ if (tmp_plugin)
+ *dbt= plugin_data(tmp_plugin, handlerton *)->db_type;
+ else
+ *dbt= DB_TYPE_UNKNOWN;
+ }
+ }
+
+ my_free(frm_image);
+ }
+
+ /* Probably a table. */
+err:
+ mysql_file_close(file, MYF(MY_WME));
+ DBUG_RETURN(type);
+}
+
+
+/*
+ Regenerate a metadata locked table.
+
+ @param thd Thread context.
+ @param db Name of the database to which the table belongs to.
+ @param name Table name.
+ @param path For temporary tables only - path to table files.
+ Otherwise NULL (the path is calculated from db and table names).
+
+ @retval FALSE Success.
+ @retval TRUE Error.
+*/
+
+bool dd_recreate_table(THD *thd, const char *db, const char *table_name,
+ const char *path)
+{
+ bool error= TRUE;
+ HA_CREATE_INFO create_info;
+ char path_buf[FN_REFLEN + 1];
+ DBUG_ENTER("dd_recreate_table");
+
+ memset(&create_info, 0, sizeof(create_info));
+
+ if (path)
+ create_info.options|= HA_LEX_CREATE_TMP_TABLE;
+ else
+ {
+ build_table_filename(path_buf, sizeof(path_buf) - 1,
+ db, table_name, "", 0);
+ path= path_buf;
+
+ /* There should be a exclusive metadata lock on the table. */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
+ MDL_EXCLUSIVE));
+ }
+
+ /* Attempt to reconstruct the table. */
+ error= ha_create_table(thd, path, db, table_name, &create_info, NULL);
+
+ DBUG_RETURN(error);
+}
+
diff --git a/sql/datadict.h b/sql/datadict.h
new file mode 100644
index 00000000000..dd80942daca
--- /dev/null
+++ b/sql/datadict.h
@@ -0,0 +1,49 @@
+#ifndef DATADICT_INCLUDED
+#define DATADICT_INCLUDED
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "handler.h"
+
+/*
+ Data dictionary API.
+*/
+
+enum frm_type_enum
+{
+ FRMTYPE_ERROR= 0,
+ FRMTYPE_TABLE,
+ FRMTYPE_VIEW
+};
+
+/*
+ Take extra care when using dd_frm_type() - it only checks the .frm file,
+ and it won't work for any engine that supports discovery.
+
+ Prefer to use ha_table_exists() instead.
+ To check whether it's an frm of a view, use dd_frm_is_view().
+*/
+frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt);
+
+static inline bool dd_frm_is_view(THD *thd, char *path)
+{
+ enum legacy_db_type not_used;
+ return dd_frm_type(thd, path, &not_used) == FRMTYPE_VIEW;
+}
+
+bool dd_recreate_table(THD *thd, const char *db, const char *table_name,
+ const char *path = NULL);
+
+#endif // DATADICT_INCLUDED
diff --git a/sql/debug_sync.cc b/sql/debug_sync.cc
new file mode 100644
index 00000000000..5802d726aa2
--- /dev/null
+++ b/sql/debug_sync.cc
@@ -0,0 +1,1603 @@
+/* Copyright (c) 2009, 2013, Oracle and/or its affiliates.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+/* see include/mysql/service_debug_sync.h for debug sync documentation */
+
+#include <my_global.h>
+#include "debug_sync.h"
+
+#if defined(ENABLED_DEBUG_SYNC)
+
+/*
+ Due to weaknesses in our include files, we need to include
+ sql_priv.h here. To have THD declared, we need to include
+ sql_class.h. This includes log_event.h, which in turn requires
+ declarations from sql_priv.h (e.g. OPTION_AUTO_IS_NULL).
+ sql_priv.h includes almost everything, so is sufficient here.
+*/
+#include "sql_priv.h"
+#include "sql_parse.h"
+
+/*
+ Action to perform at a synchronization point.
+ NOTE: This structure is moved around in memory by realloc(), qsort(),
+ and memmove(). Do not add objects with non-trivial constuctors
+ or destructors, which might prevent moving of this structure
+ with these functions.
+*/
+struct st_debug_sync_action
+{
+ ulong activation_count; /* MY_MAX(hit_limit, execute) */
+ ulong hit_limit; /* hits before kill query */
+ ulong execute; /* executes before self-clear */
+ ulong timeout; /* wait_for timeout */
+ String signal; /* signal to emit */
+ String wait_for; /* signal to wait for */
+ String sync_point; /* sync point name */
+ bool need_sort; /* if new action, array needs sort */
+};
+
+/* Debug sync control. Referenced by THD. */
+struct st_debug_sync_control
+{
+ st_debug_sync_action *ds_action; /* array of actions */
+ uint ds_active; /* # active actions */
+ uint ds_allocated; /* # allocated actions */
+ ulonglong dsp_hits; /* statistics */
+ ulonglong dsp_executed; /* statistics */
+ ulonglong dsp_max_active; /* statistics */
+ /*
+ thd->proc_info points at unsynchronized memory.
+ It must not go away as long as the thread exists.
+ */
+ char ds_proc_info[80]; /* proc_info string */
+};
+
+
+/**
+ Definitions for the debug sync facility.
+ 1. Global string variable to hold a "signal" ("signal post", "flag mast").
+ 2. Global condition variable for signaling and waiting.
+ 3. Global mutex to synchronize access to the above.
+*/
+struct st_debug_sync_globals
+{
+ String ds_signal; /* signal variable */
+ mysql_cond_t ds_cond; /* condition variable */
+ mysql_mutex_t ds_mutex; /* mutex variable */
+ ulonglong dsp_hits; /* statistics */
+ ulonglong dsp_executed; /* statistics */
+ ulonglong dsp_max_active; /* statistics */
+};
+static st_debug_sync_globals debug_sync_global; /* All globals in one object */
+
+/**
+ Callbacks from C files.
+*/
+C_MODE_START
+static void debug_sync(THD *thd, const char *sync_point_name, size_t name_len);
+static int debug_sync_qsort_cmp(const void *, const void *);
+C_MODE_END
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_debug_sync_globals_ds_mutex;
+
+static PSI_mutex_info all_debug_sync_mutexes[]=
+{
+ { &key_debug_sync_globals_ds_mutex, "DEBUG_SYNC::mutex", PSI_FLAG_GLOBAL}
+};
+
+static PSI_cond_key key_debug_sync_globals_ds_cond;
+
+static PSI_cond_info all_debug_sync_conds[]=
+{
+ { &key_debug_sync_globals_ds_cond, "DEBUG_SYNC::cond", PSI_FLAG_GLOBAL}
+};
+
+static void init_debug_sync_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ count= array_elements(all_debug_sync_mutexes);
+ mysql_mutex_register(category, all_debug_sync_mutexes, count);
+
+ count= array_elements(all_debug_sync_conds);
+ mysql_cond_register(category, all_debug_sync_conds, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+
+/**
+ Initialize the debug sync facility at server start.
+
+ @return status
+ @retval 0 ok
+ @retval != 0 error
+*/
+
+int debug_sync_init(void)
+{
+ DBUG_ENTER("debug_sync_init");
+
+#ifdef HAVE_PSI_INTERFACE
+ init_debug_sync_psi_keys();
+#endif
+
+ if (opt_debug_sync_timeout)
+ {
+ int rc;
+
+ /* Initialize the global variables. */
+ debug_sync_global.ds_signal.length(0);
+ if ((rc= mysql_cond_init(key_debug_sync_globals_ds_cond,
+ &debug_sync_global.ds_cond, NULL)) ||
+ (rc= mysql_mutex_init(key_debug_sync_globals_ds_mutex,
+ &debug_sync_global.ds_mutex,
+ MY_MUTEX_INIT_FAST)))
+ DBUG_RETURN(rc); /* purecov: inspected */
+
+ /* Set the call back pointer in C files. */
+ debug_sync_C_callback_ptr= debug_sync;
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ End the debug sync facility.
+
+ @description
+ This is called at server shutdown or after a thread initialization error.
+*/
+
+void debug_sync_end(void)
+{
+ DBUG_ENTER("debug_sync_end");
+
+ /* End the facility only if it had been initialized. */
+ if (debug_sync_C_callback_ptr)
+ {
+ /* Clear the call back pointer in C files. */
+ debug_sync_C_callback_ptr= NULL;
+
+ /* Destroy the global variables. */
+ debug_sync_global.ds_signal.free();
+ mysql_cond_destroy(&debug_sync_global.ds_cond);
+ mysql_mutex_destroy(&debug_sync_global.ds_mutex);
+
+ /* Print statistics. */
+ {
+ char llbuff[22];
+ sql_print_information("Debug sync points hit: %22s",
+ llstr(debug_sync_global.dsp_hits, llbuff));
+ sql_print_information("Debug sync points executed: %22s",
+ llstr(debug_sync_global.dsp_executed, llbuff));
+ sql_print_information("Debug sync points max active per thread: %22s",
+ llstr(debug_sync_global.dsp_max_active, llbuff));
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/* purecov: begin tested */
+
+/**
+ Disable the facility after lack of memory if no error can be returned.
+
+ @note
+ Do not end the facility here because the global variables can
+ be in use by other threads.
+*/
+
+static void debug_sync_emergency_disable(void)
+{
+ DBUG_ENTER("debug_sync_emergency_disable");
+
+ opt_debug_sync_timeout= 0;
+
+ DBUG_PRINT("debug_sync",
+ ("Debug Sync Facility disabled due to lack of memory."));
+ sql_print_error("Debug Sync Facility disabled due to lack of memory.");
+
+ DBUG_VOID_RETURN;
+}
+
+/* purecov: end */
+
+
+/**
+ Initialize the debug sync facility at thread start.
+
+ @param[in] thd thread handle
+*/
+
+void debug_sync_init_thread(THD *thd)
+{
+ DBUG_ENTER("debug_sync_init_thread");
+ DBUG_ASSERT(thd);
+
+ if (opt_debug_sync_timeout)
+ {
+ thd->debug_sync_control= (st_debug_sync_control*)
+ my_malloc(sizeof(st_debug_sync_control),
+ MYF(MY_WME | MY_ZEROFILL | MY_THREAD_SPECIFIC));
+ if (!thd->debug_sync_control)
+ {
+ /*
+ Error is reported by my_malloc().
+ We must disable the facility. We have no way to return an error.
+ */
+ debug_sync_emergency_disable(); /* purecov: tested */
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ End the debug sync facility at thread end.
+
+ @param[in] thd thread handle
+*/
+
+void debug_sync_end_thread(THD *thd)
+{
+ DBUG_ENTER("debug_sync_end_thread");
+ DBUG_ASSERT(thd);
+
+ if (thd->debug_sync_control)
+ {
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+
+ /*
+ This synchronization point can be used to synchronize on thread end.
+ This is the latest point in a THD's life, where this can be done.
+ */
+ DEBUG_SYNC(thd, "thread_end");
+
+ if (ds_control->ds_action)
+ {
+ st_debug_sync_action *action= ds_control->ds_action;
+ st_debug_sync_action *action_end= action + ds_control->ds_allocated;
+ for (; action < action_end; action++)
+ {
+ action->signal.free();
+ action->wait_for.free();
+ action->sync_point.free();
+ }
+ my_free(ds_control->ds_action);
+ }
+
+ /* Statistics. */
+ mysql_mutex_lock(&debug_sync_global.ds_mutex);
+ debug_sync_global.dsp_hits+= ds_control->dsp_hits;
+ debug_sync_global.dsp_executed+= ds_control->dsp_executed;
+ if (debug_sync_global.dsp_max_active < ds_control->dsp_max_active)
+ debug_sync_global.dsp_max_active= ds_control->dsp_max_active;
+ mysql_mutex_unlock(&debug_sync_global.ds_mutex);
+
+ my_free(ds_control);
+ thd->debug_sync_control= NULL;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Move a string by length.
+
+ @param[out] to buffer for the resulting string
+ @param[in] to_end end of buffer
+ @param[in] from source string
+ @param[in] length number of bytes to copy
+
+ @return pointer to end of copied string
+*/
+
+static char *debug_sync_bmove_len(char *to, char *to_end,
+ const char *from, size_t length)
+{
+ DBUG_ASSERT(to);
+ DBUG_ASSERT(to_end);
+ DBUG_ASSERT(!length || from);
+ set_if_smaller(length, (size_t) (to_end - to));
+ memcpy(to, from, length);
+ return (to + length);
+}
+
+
+#if !defined(DBUG_OFF)
+
+/**
+ Create a string that describes an action.
+
+ @param[out] result buffer for the resulting string
+ @param[in] size size of result buffer
+ @param[in] action action to describe
+*/
+
+static void debug_sync_action_string(char *result, uint size,
+ st_debug_sync_action *action)
+{
+ char *wtxt= result;
+ char *wend= wtxt + size - 1; /* Allow emergency '\0'. */
+ DBUG_ASSERT(result);
+ DBUG_ASSERT(action);
+
+ /* If an execute count is present, signal or wait_for are needed too. */
+ DBUG_ASSERT(!action->execute ||
+ action->signal.length() || action->wait_for.length());
+
+ if (action->execute)
+ {
+ if (action->signal.length())
+ {
+ wtxt= debug_sync_bmove_len(wtxt, wend, STRING_WITH_LEN("SIGNAL "));
+ wtxt= debug_sync_bmove_len(wtxt, wend, action->signal.ptr(),
+ action->signal.length());
+ }
+ if (action->wait_for.length())
+ {
+ if ((wtxt == result) && (wtxt < wend))
+ *(wtxt++)= ' ';
+ wtxt= debug_sync_bmove_len(wtxt, wend, STRING_WITH_LEN(" WAIT_FOR "));
+ wtxt= debug_sync_bmove_len(wtxt, wend, action->wait_for.ptr(),
+ action->wait_for.length());
+
+ if (action->timeout != opt_debug_sync_timeout)
+ {
+ wtxt+= my_snprintf(wtxt, wend - wtxt, " TIMEOUT %lu", action->timeout);
+ }
+ }
+ if (action->execute != 1)
+ {
+ wtxt+= my_snprintf(wtxt, wend - wtxt, " EXECUTE %lu", action->execute);
+ }
+ }
+ if (action->hit_limit)
+ {
+ wtxt+= my_snprintf(wtxt, wend - wtxt, "%sHIT_LIMIT %lu",
+ (wtxt == result) ? "" : " ", action->hit_limit);
+ }
+
+ /*
+ If (wtxt == wend) string may not be terminated.
+ There is one byte left for an emergency termination.
+ */
+ *wtxt= '\0';
+}
+
+
+/**
+ Print actions.
+
+ @param[in] thd thread handle
+*/
+
+static void debug_sync_print_actions(THD *thd)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ uint idx;
+ DBUG_ENTER("debug_sync_print_actions");
+ DBUG_ASSERT(thd);
+
+ if (!ds_control)
+ DBUG_VOID_RETURN;
+
+ for (idx= 0; idx < ds_control->ds_active; idx++)
+ {
+ const char *dsp_name= ds_control->ds_action[idx].sync_point.c_ptr();
+ char action_string[256];
+
+ debug_sync_action_string(action_string, sizeof(action_string),
+ ds_control->ds_action + idx);
+ DBUG_PRINT("debug_sync_list", ("%s %s", dsp_name, action_string));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+#endif /* !defined(DBUG_OFF) */
+
+
+/**
+ Compare two actions by sync point name length, string.
+
+ @param[in] arg1 reference to action1
+ @param[in] arg2 reference to action2
+
+ @return difference
+ @retval == 0 length1/string1 is same as length2/string2
+ @retval < 0 length1/string1 is smaller
+ @retval > 0 length1/string1 is bigger
+*/
+
+static int debug_sync_qsort_cmp(const void* arg1, const void* arg2)
+{
+ st_debug_sync_action *action1= (st_debug_sync_action*) arg1;
+ st_debug_sync_action *action2= (st_debug_sync_action*) arg2;
+ int diff;
+ DBUG_ASSERT(action1);
+ DBUG_ASSERT(action2);
+
+ if (!(diff= action1->sync_point.length() - action2->sync_point.length()))
+ diff= memcmp(action1->sync_point.ptr(), action2->sync_point.ptr(),
+ action1->sync_point.length());
+
+ return diff;
+}
+
+
+/**
+ Find a debug sync action.
+
+ @param[in] actionarr array of debug sync actions
+ @param[in] quantity number of actions in array
+ @param[in] dsp_name name of debug sync point to find
+ @param[in] name_len length of name of debug sync point
+
+ @return action
+ @retval != NULL found sync point in array
+ @retval NULL not found
+
+ @description
+ Binary search. Array needs to be sorted by length, sync point name.
+*/
+
+static st_debug_sync_action *debug_sync_find(st_debug_sync_action *actionarr,
+ int quantity,
+ const char *dsp_name,
+ uint name_len)
+{
+ st_debug_sync_action *action;
+ int low ;
+ int high ;
+ int mid ;
+ int diff ;
+ DBUG_ASSERT(actionarr);
+ DBUG_ASSERT(dsp_name);
+ DBUG_ASSERT(name_len);
+
+ low= 0;
+ high= quantity;
+
+ while (low < high)
+ {
+ mid= (low + high) / 2;
+ action= actionarr + mid;
+ if (!(diff= name_len - action->sync_point.length()) &&
+ !(diff= memcmp(dsp_name, action->sync_point.ptr(), name_len)))
+ return action;
+ if (diff > 0)
+ low= mid + 1;
+ else
+ high= mid - 1;
+ }
+
+ if (low < quantity)
+ {
+ action= actionarr + low;
+ if ((name_len == action->sync_point.length()) &&
+ !memcmp(dsp_name, action->sync_point.ptr(), name_len))
+ return action;
+ }
+
+ return NULL;
+}
+
+
+/**
+ Reset the debug sync facility.
+
+ @param[in] thd thread handle
+
+ @description
+ Remove all actions of this thread.
+ Clear the global signal.
+*/
+
+static void debug_sync_reset(THD *thd)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ DBUG_ENTER("debug_sync_reset");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(ds_control);
+
+ /* Remove all actions of this thread. */
+ ds_control->ds_active= 0;
+
+ /* Clear the global signal. */
+ mysql_mutex_lock(&debug_sync_global.ds_mutex);
+ debug_sync_global.ds_signal.length(0);
+ mysql_mutex_unlock(&debug_sync_global.ds_mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Remove a debug sync action.
+
+ @param[in] ds_control control object
+ @param[in] action action to be removed
+
+ @description
+ Removing an action mainly means to decrement the ds_active counter.
+ But if the action is between other active action in the array, then
+ the array needs to be shrinked. The active actions above the one to
+ be removed have to be moved down by one slot.
+*/
+
+static void debug_sync_remove_action(st_debug_sync_control *ds_control,
+ st_debug_sync_action *action)
+{
+ uint dsp_idx= action - ds_control->ds_action;
+ DBUG_ENTER("debug_sync_remove_action");
+ DBUG_ASSERT(ds_control);
+ DBUG_ASSERT(ds_control == current_thd->debug_sync_control);
+ DBUG_ASSERT(action);
+ DBUG_ASSERT(dsp_idx < ds_control->ds_active);
+
+ /* Decrement the number of currently active actions. */
+ ds_control->ds_active--;
+
+ /*
+ If this was not the last active action in the array, we need to
+ shift remaining active actions down to keep the array gap-free.
+ Otherwise binary search might fail or take longer than necessary at
+ least. Also new actions are always put to the end of the array.
+ */
+ if (ds_control->ds_active > dsp_idx)
+ {
+ /*
+ Do not make save_action an object of class st_debug_sync_action.
+ Its destructor would tamper with the String pointers.
+ */
+ uchar save_action[sizeof(st_debug_sync_action)];
+
+ /*
+ Copy the to-be-removed action object to temporary storage before
+ the shift copies the string pointers over. Do not use assignment
+ because it would use assignment operator methods for the Strings.
+ This would copy the strings. The shift below overwrite the string
+ pointers without freeing them first. By using memmove() we save
+ the pointers, which are overwritten by the shift.
+ */
+ memmove(save_action, action, sizeof(st_debug_sync_action));
+
+ /* Move actions down. */
+ memmove(ds_control->ds_action + dsp_idx,
+ ds_control->ds_action + dsp_idx + 1,
+ (ds_control->ds_active - dsp_idx) *
+ sizeof(st_debug_sync_action));
+
+ /*
+ Copy back the saved action object to the now free array slot. This
+ replaces the double references of String pointers that have been
+ produced by the shift. Again do not use an assignment operator to
+ avoid string allocation/copy.
+ */
+ memmove(ds_control->ds_action + ds_control->ds_active, save_action,
+ sizeof(st_debug_sync_action));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get a debug sync action.
+
+ @param[in] thd thread handle
+ @param[in] dsp_name debug sync point name
+ @param[in] name_len length of sync point name
+
+ @return action
+ @retval != NULL ok
+ @retval NULL error
+
+ @description
+ Find the debug sync action for a debug sync point or make a new one.
+*/
+
+static st_debug_sync_action *debug_sync_get_action(THD *thd,
+ const char *dsp_name,
+ uint name_len)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ st_debug_sync_action *action;
+ DBUG_ENTER("debug_sync_get_action");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(dsp_name);
+ DBUG_ASSERT(name_len);
+ DBUG_ASSERT(ds_control);
+ DBUG_PRINT("debug_sync", ("sync_point: '%.*s'", (int) name_len, dsp_name));
+ DBUG_PRINT("debug_sync", ("active: %u allocated: %u",
+ ds_control->ds_active, ds_control->ds_allocated));
+
+ /* There cannot be more active actions than allocated. */
+ DBUG_ASSERT(ds_control->ds_active <= ds_control->ds_allocated);
+ /* If there are active actions, the action array must be present. */
+ DBUG_ASSERT(!ds_control->ds_active || ds_control->ds_action);
+
+ /* Try to reuse existing action if there is one for this sync point. */
+ if (ds_control->ds_active &&
+ (action= debug_sync_find(ds_control->ds_action, ds_control->ds_active,
+ dsp_name, name_len)))
+ {
+ /* Reuse an already active sync point action. */
+ DBUG_ASSERT((uint)(action - ds_control->ds_action) < ds_control->ds_active);
+ DBUG_PRINT("debug_sync", ("reuse action idx: %ld",
+ (long) (action - ds_control->ds_action)));
+ }
+ else
+ {
+ /* Create a new action. */
+ int dsp_idx= ds_control->ds_active++;
+ set_if_bigger(ds_control->dsp_max_active, ds_control->ds_active);
+ if (ds_control->ds_active > ds_control->ds_allocated)
+ {
+ uint new_alloc= ds_control->ds_active + 3;
+ void *new_action= my_realloc(ds_control->ds_action,
+ new_alloc * sizeof(st_debug_sync_action),
+ MYF(MY_WME | MY_ALLOW_ZERO_PTR));
+ if (!new_action)
+ {
+ /* Error is reported by my_malloc(). */
+ goto err; /* purecov: tested */
+ }
+ ds_control->ds_action= (st_debug_sync_action*) new_action;
+ ds_control->ds_allocated= new_alloc;
+ /* Clear memory as we do not run string constructors here. */
+ bzero((uchar*) (ds_control->ds_action + dsp_idx),
+ (new_alloc - dsp_idx) * sizeof(st_debug_sync_action));
+ }
+ DBUG_PRINT("debug_sync", ("added action idx: %u", dsp_idx));
+ action= ds_control->ds_action + dsp_idx;
+ if (action->sync_point.copy(dsp_name, name_len, system_charset_info))
+ {
+ /* Error is reported by my_malloc(). */
+ goto err; /* purecov: tested */
+ }
+ action->need_sort= TRUE;
+ }
+ DBUG_ASSERT(action >= ds_control->ds_action);
+ DBUG_ASSERT(action < ds_control->ds_action + ds_control->ds_active);
+ DBUG_PRINT("debug_sync", ("action: 0x%lx array: 0x%lx count: %u",
+ (long) action, (long) ds_control->ds_action,
+ ds_control->ds_active));
+
+ DBUG_RETURN(action);
+
+ /* purecov: begin tested */
+ err:
+ DBUG_RETURN(NULL);
+ /* purecov: end */
+}
+
+
+/**
+ Set a debug sync action.
+
+ @param[in] thd thread handle
+ @param[in] action synchronization action
+
+ @return status
+ @retval FALSE ok
+ @retval TRUE error
+
+ @description
+ This is called from the debug sync parser. It arms the action for
+ the requested sync point. If the action parsed into an empty action,
+ it is removed instead.
+
+ Setting an action for a sync point means to make the sync point
+ active. When it is hit it will execute this action.
+
+ Before parsing, we "get" an action object. This is placed at the
+ end of the thread's action array unless the requested sync point
+ has an action already.
+
+ Then the parser fills the action object from the request string.
+
+ Finally the action is "set" for the sync point. If it was parsed
+ to be empty, it is removed from the array. If it did belong to a
+ sync point before, the sync point becomes inactive. If the action
+ became non-empty and it did not belong to a sync point before (it
+ was added at the end of the action array), the action array needs
+ to be sorted by sync point.
+
+ If the sync point name is "now", it is executed immediately.
+*/
+
+static bool debug_sync_set_action(THD *thd, st_debug_sync_action *action)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ bool is_dsp_now= FALSE;
+ DBUG_ENTER("debug_sync_set_action");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(action);
+ DBUG_ASSERT(ds_control);
+
+ action->activation_count= MY_MAX(action->hit_limit, action->execute);
+ if (!action->activation_count)
+ {
+ debug_sync_remove_action(ds_control, action);
+ DBUG_PRINT("debug_sync", ("action cleared"));
+ }
+ else
+ {
+ const char *dsp_name= action->sync_point.c_ptr();
+ DBUG_EXECUTE("debug_sync", {
+ /* Functions as DBUG_PRINT args can change keyword and line nr. */
+ const char *sig_emit= action->signal.c_ptr();
+ const char *sig_wait= action->wait_for.c_ptr();
+ DBUG_PRINT("debug_sync",
+ ("sync_point: '%s' activation_count: %lu hit_limit: %lu "
+ "execute: %lu timeout: %lu signal: '%s' wait_for: '%s'",
+ dsp_name, action->activation_count,
+ action->hit_limit, action->execute, action->timeout,
+ sig_emit, sig_wait));});
+
+ /* Check this before sorting the array. action may move. */
+ is_dsp_now= !my_strcasecmp(system_charset_info, dsp_name, "now");
+
+ if (action->need_sort)
+ {
+ action->need_sort= FALSE;
+ /* Sort actions by (name_len, name). */
+ my_qsort(ds_control->ds_action, ds_control->ds_active,
+ sizeof(st_debug_sync_action), debug_sync_qsort_cmp);
+ }
+ }
+ DBUG_EXECUTE("debug_sync_list", debug_sync_print_actions(thd););
+
+ /* Execute the special sync point 'now' if activated above. */
+ if (is_dsp_now)
+ {
+ DEBUG_SYNC(thd, "now");
+ /*
+ If HIT_LIMIT for sync point "now" was 1, the execution of the sync
+ point decremented it to 0. In this case the following happened:
+
+ - an error message was reported with my_error() and
+ - the statement was killed with thd->killed= THD::KILL_QUERY.
+
+ If a statement reports an error, it must not call send_ok().
+ The calling functions will not call send_ok(), if we return TRUE
+ from this function.
+
+ thd->killed is also set if the wait is interrupted from a
+ KILL or KILL QUERY statement. In this case, no error is reported
+ and shall not be reported as a result of SET DEBUG_SYNC.
+ Hence, we check for the first condition above.
+ */
+ if (thd->is_error())
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Extract a token from a string.
+
+ @param[out] token_p returns start of token
+ @param[out] token_length_p returns length of token
+ @param[in,out] ptr current string pointer, adds '\0' terminators
+
+ @return string pointer or NULL
+ @retval != NULL ptr behind token terminator or at string end
+ @retval NULL no token found in remainder of string
+
+ @note
+ This function assumes that the string is in system_charset_info,
+ that this charset is single byte for ASCII NUL ('\0'), that no
+ character except of ASCII NUL ('\0') contains a byte with value 0,
+ and that ASCII NUL ('\0') is used as the string terminator.
+
+ This function needs to return tokens that are terminated with ASCII
+ NUL ('\0'). The tokens are used in my_strcasecmp(). Unfortunately
+ there is no my_strncasecmp().
+
+ To return the last token without copying it, we require the input
+ string to be nul terminated.
+
+ @description
+ This function skips space characters at string begin.
+
+ It returns a pointer to the first non-space character in *token_p.
+
+ If no non-space character is found before the string terminator
+ ASCII NUL ('\0'), the function returns NULL. *token_p and
+ *token_length_p remain unchanged in this case (they are not set).
+
+ The function takes a space character or an ASCII NUL ('\0') as a
+ terminator of the token. The space character could be multi-byte.
+
+ It returns the length of the token in bytes, excluding the
+ terminator, in *token_length_p.
+
+ If the terminator of the token is ASCII NUL ('\0'), it returns a
+ pointer to the terminator (string end).
+
+ If the terminator is a space character, it replaces the the first
+ byte of the terminator character by ASCII NUL ('\0'), skips the (now
+ corrupted) terminator character, and skips all following space
+ characters. It returns a pointer to the next non-space character or
+ to the string terminator ASCII NUL ('\0').
+*/
+
+static char *debug_sync_token(char **token_p, uint *token_length_p, char *ptr)
+{
+ DBUG_ASSERT(token_p);
+ DBUG_ASSERT(token_length_p);
+ DBUG_ASSERT(ptr);
+
+ /* Skip leading space */
+ while (my_isspace(system_charset_info, *ptr))
+ ptr+= my_mbcharlen(system_charset_info, (uchar) *ptr);
+
+ if (!*ptr)
+ {
+ ptr= NULL;
+ goto end;
+ }
+
+ /* Get token start. */
+ *token_p= ptr;
+
+ /* Find token end. */
+ while (*ptr && !my_isspace(system_charset_info, *ptr))
+ ptr+= my_mbcharlen(system_charset_info, (uchar) *ptr);
+
+ /* Get token length. */
+ *token_length_p= ptr - *token_p;
+
+ /* If necessary, terminate token. */
+ if (*ptr)
+ {
+ /* Get terminator character length. */
+ uint mbspacelen= my_mbcharlen(system_charset_info, (uchar) *ptr);
+
+ /* Terminate token. */
+ *ptr= '\0';
+
+ /* Skip the terminator. */
+ ptr+= mbspacelen;
+
+ /* Skip trailing space */
+ while (my_isspace(system_charset_info, *ptr))
+ ptr+= my_mbcharlen(system_charset_info, (uchar) *ptr);
+ }
+
+ end:
+ return ptr;
+}
+
+
+/**
+ Extract a number from a string.
+
+ @param[out] number_p returns number
+ @param[in] actstrptr current pointer in action string
+
+ @return string pointer or NULL
+ @retval != NULL ptr behind token terminator or at string end
+ @retval NULL no token found or token is not valid number
+
+ @note
+ The same assumptions about charset apply as for debug_sync_token().
+
+ @description
+ This function fetches a token from the string and converts it
+ into a number.
+
+ If there is no token left in the string, or the token is not a valid
+ decimal number, NULL is returned. The result in *number_p is
+ undefined in this case.
+*/
+
+static char *debug_sync_number(ulong *number_p, char *actstrptr)
+{
+ char *ptr;
+ char *ept;
+ char *token;
+ uint token_length;
+ DBUG_ASSERT(number_p);
+ DBUG_ASSERT(actstrptr);
+
+ /* Get token from string. */
+ if (!(ptr= debug_sync_token(&token, &token_length, actstrptr)))
+ goto end;
+
+ *number_p= strtoul(token, &ept, 10);
+ if (*ept)
+ ptr= NULL;
+
+ end:
+ return ptr;
+}
+
+
+/**
+ Evaluate a debug sync action string.
+
+ @param[in] thd thread handle
+ @param[in,out] action_str action string to receive '\0' terminators
+
+ @return status
+ @retval FALSE ok
+ @retval TRUE error
+
+ @description
+ This is called when the DEBUG_SYNC system variable is set.
+ Parse action string, build a debug sync action, activate it.
+
+ Before parsing, we "get" an action object. This is placed at the
+ end of the thread's action array unless the requested sync point
+ has an action already.
+
+ Then the parser fills the action object from the request string.
+
+ Finally the action is "set" for the sync point. This means that the
+ sync point becomes active or inactive, depending on the action
+ values.
+
+ @note
+ The input string needs to be ASCII NUL ('\0') terminated. We split
+ nul-terminated tokens in it without copy.
+
+ @see the function comment of debug_sync_token() for more constraints
+ for the string.
+*/
+
+static bool debug_sync_eval_action(THD *thd, char *action_str)
+{
+ st_debug_sync_action *action= NULL;
+ const char *errmsg;
+ char *ptr;
+ char *token;
+ uint token_length= 0;
+ DBUG_ENTER("debug_sync_eval_action");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(action_str);
+ DBUG_PRINT("debug_sync", ("action_str: '%s'", action_str));
+
+ /*
+ Get debug sync point name. Or a special command.
+ */
+ if (!(ptr= debug_sync_token(&token, &token_length, action_str)))
+ {
+ errmsg= "Missing synchronization point name";
+ goto err;
+ }
+
+ /*
+ If there is a second token, the first one is the sync point name.
+ */
+ if (*ptr)
+ {
+ /* Get an action object to collect the requested action parameters. */
+ action= debug_sync_get_action(thd, token, token_length);
+ if (!action)
+ {
+ /* Error message is sent. */
+ DBUG_RETURN(TRUE); /* purecov: tested */
+ }
+ }
+
+ /*
+ Get kind of action to be taken at sync point.
+ */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ {
+ /* No action present. Try special commands. Token unchanged. */
+
+ /*
+ Try RESET.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "RESET"))
+ {
+ /* It is RESET. Reset all actions and global signal. */
+ debug_sync_reset(thd);
+ goto end;
+ }
+
+ /* Token unchanged. It still contains sync point name. */
+ errmsg= "Missing action after synchronization point name '%.*s'";
+ goto err;
+ }
+
+ /*
+ Check for pseudo actions first. Start with actions that work on
+ an existing action.
+ */
+ DBUG_ASSERT(action);
+
+ /*
+ Try TEST.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "TEST"))
+ {
+ /* It is TEST. Nothing must follow it. */
+ if (*ptr)
+ {
+ errmsg= "Nothing must follow action TEST";
+ goto err;
+ }
+
+ /* Execute sync point. */
+ debug_sync(thd, action->sync_point.ptr(), action->sync_point.length());
+ /* Fix statistics. This was not a real hit of the sync point. */
+ thd->debug_sync_control->dsp_hits--;
+ goto end;
+ }
+
+ /*
+ Now check for actions that define a new action.
+ Initialize action. Do not use bzero(). Strings may have malloced.
+ */
+ action->activation_count= 0;
+ action->hit_limit= 0;
+ action->execute= 0;
+ action->timeout= 0;
+ action->signal.length(0);
+ action->wait_for.length(0);
+
+ /*
+ Try CLEAR.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "CLEAR"))
+ {
+ /* It is CLEAR. Nothing must follow it. */
+ if (*ptr)
+ {
+ errmsg= "Nothing must follow action CLEAR";
+ goto err;
+ }
+
+ /* Set (clear/remove) action. */
+ goto set_action;
+ }
+
+ /*
+ Now check for real sync point actions.
+ */
+
+ /*
+ Try SIGNAL.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "SIGNAL"))
+ {
+ /* It is SIGNAL. Signal name must follow. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ {
+ errmsg= "Missing signal name after action SIGNAL";
+ goto err;
+ }
+ if (action->signal.copy(token, token_length, system_charset_info))
+ {
+ /* Error is reported by my_malloc(). */
+ /* purecov: begin tested */
+ errmsg= NULL;
+ goto err;
+ /* purecov: end */
+ }
+
+ /* Set default for EXECUTE option. */
+ action->execute= 1;
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+
+ /*
+ Try WAIT_FOR.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "WAIT_FOR"))
+ {
+ /* It is WAIT_FOR. Wait_for signal name must follow. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ {
+ errmsg= "Missing signal name after action WAIT_FOR";
+ goto err;
+ }
+ if (action->wait_for.copy(token, token_length, system_charset_info))
+ {
+ /* Error is reported by my_malloc(). */
+ /* purecov: begin tested */
+ errmsg= NULL;
+ goto err;
+ /* purecov: end */
+ }
+
+ /* Set default for EXECUTE and TIMEOUT options. */
+ action->execute= 1;
+ action->timeout= opt_debug_sync_timeout;
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+
+ /*
+ Try TIMEOUT.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "TIMEOUT"))
+ {
+ /* It is TIMEOUT. Number must follow. */
+ if (!(ptr= debug_sync_number(&action->timeout, ptr)))
+ {
+ errmsg= "Missing valid number after TIMEOUT";
+ goto err;
+ }
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+ }
+
+ /*
+ Try EXECUTE.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "EXECUTE"))
+ {
+ /*
+ EXECUTE requires either SIGNAL and/or WAIT_FOR to be present.
+ In this case action->execute has been preset to 1.
+ */
+ if (!action->execute)
+ {
+ errmsg= "Missing action before EXECUTE";
+ goto err;
+ }
+
+ /* Number must follow. */
+ if (!(ptr= debug_sync_number(&action->execute, ptr)))
+ {
+ errmsg= "Missing valid number after EXECUTE";
+ goto err;
+ }
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+
+ /*
+ Try HIT_LIMIT.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "HIT_LIMIT"))
+ {
+ /* Number must follow. */
+ if (!(ptr= debug_sync_number(&action->hit_limit, ptr)))
+ {
+ errmsg= "Missing valid number after HIT_LIMIT";
+ goto err;
+ }
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+
+ errmsg= "Illegal or out of order stuff: '%.*s'";
+
+ err:
+ if (errmsg)
+ {
+ /*
+ NOTE: errmsg must either have %.*s or none % at all.
+ It can be NULL if an error message is already reported
+ (e.g. by my_malloc()).
+ */
+ set_if_smaller(token_length, 64); /* Limit error message length. */
+ my_printf_error(ER_PARSE_ERROR, errmsg, MYF(0), token_length, token);
+ }
+ if (action)
+ debug_sync_remove_action(thd->debug_sync_control, action);
+ DBUG_RETURN(TRUE);
+
+ set_action:
+ DBUG_RETURN(debug_sync_set_action(thd, action));
+
+ end:
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Set the system variable 'debug_sync'.
+
+ @param[in] thd thread handle
+ @param[in] var set variable request
+
+ @return status
+ @retval FALSE ok, variable is set
+ @retval TRUE error, variable could not be set
+
+ @note
+ "Setting" of the system variable 'debug_sync' does not mean to
+ assign a value to it as usual. Instead a debug sync action is parsed
+ from the input string and stored apart from the variable value.
+
+ @note
+ For efficiency reasons, the action string parser places '\0'
+ terminators in the string. So we need to take a copy here.
+*/
+
+bool debug_sync_update(THD *thd, char *val_str)
+{
+ DBUG_ENTER("debug_sync_update");
+ DBUG_PRINT("debug_sync", ("set action: '%s'", val_str));
+
+ /*
+ debug_sync_eval_action() places '\0' in the string, which itself
+ must be '\0' terminated.
+ */
+ DBUG_RETURN(opt_debug_sync_timeout ?
+ debug_sync_eval_action(thd, val_str) :
+ FALSE);
+}
+
+
+/**
+ Retrieve the value of the system variable 'debug_sync'.
+
+ @param[in] thd thread handle
+
+ @return string
+ @retval != NULL ok, string pointer
+ @retval NULL memory allocation error
+
+ @note
+ The value of the system variable 'debug_sync' reflects if
+ the facility is enabled ("ON") or disabled (default, "OFF").
+
+ When "ON", the current signal is added.
+*/
+
+uchar *debug_sync_value_ptr(THD *thd)
+{
+ char *value;
+ DBUG_ENTER("debug_sync_value_ptr");
+
+ if (opt_debug_sync_timeout)
+ {
+ static char on[]= "ON - current signal: '";
+
+ // Ensure exclusive access to debug_sync_global.ds_signal
+ mysql_mutex_lock(&debug_sync_global.ds_mutex);
+
+ size_t lgt= (sizeof(on) /* includes '\0' */ +
+ debug_sync_global.ds_signal.length() + 1 /* for '\'' */);
+ char *vend;
+ char *vptr;
+
+ if ((value= (char*) alloc_root(thd->mem_root, lgt)))
+ {
+ vend= value + lgt - 1; /* reserve space for '\0'. */
+ vptr= debug_sync_bmove_len(value, vend, STRING_WITH_LEN(on));
+ vptr= debug_sync_bmove_len(vptr, vend, debug_sync_global.ds_signal.ptr(),
+ debug_sync_global.ds_signal.length());
+ if (vptr < vend)
+ *(vptr++)= '\'';
+ *vptr= '\0'; /* We have one byte reserved for the worst case. */
+ }
+ mysql_mutex_unlock(&debug_sync_global.ds_mutex);
+ }
+ else
+ {
+ /* purecov: begin tested */
+ value= const_cast<char*>("OFF");
+ /* purecov: end */
+ }
+
+ DBUG_RETURN((uchar*) value);
+}
+
+
+/**
+ Execute requested action at a synchronization point.
+
+ @param[in] thd thread handle
+ @param[in] action action to be executed
+
+ @note
+ This is to be called only if activation count > 0.
+*/
+
+static void debug_sync_execute(THD *thd, st_debug_sync_action *action)
+{
+#ifndef DBUG_OFF
+ const char *dsp_name= action->sync_point.c_ptr();
+ const char *sig_emit= action->signal.c_ptr();
+ const char *sig_wait= action->wait_for.c_ptr();
+#endif
+ DBUG_ENTER("debug_sync_execute");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(action);
+ DBUG_PRINT("debug_sync",
+ ("sync_point: '%s' activation_count: %lu hit_limit: %lu "
+ "execute: %lu timeout: %lu signal: '%s' wait_for: '%s'",
+ dsp_name, action->activation_count, action->hit_limit,
+ action->execute, action->timeout, sig_emit, sig_wait));
+
+ DBUG_ASSERT(action->activation_count);
+ action->activation_count--;
+
+ if (action->execute)
+ {
+ const char *old_proc_info;
+ LINT_INIT(old_proc_info);
+
+ action->execute--;
+
+ /*
+ If we will be going to wait, set proc_info for the PROCESSLIST table.
+ Do this before emitting the signal, so other threads can see it
+ if they awake before we enter_cond() below.
+ */
+ if (action->wait_for.length())
+ {
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ strxnmov(ds_control->ds_proc_info, sizeof(ds_control->ds_proc_info)-1,
+ "debug sync point: ", action->sync_point.c_ptr(), NullS);
+ old_proc_info= thd->proc_info;
+ thd_proc_info(thd, ds_control->ds_proc_info);
+ }
+
+ /*
+ Take mutex to ensure that only one thread access
+ debug_sync_global.ds_signal at a time. Need to take mutex for
+ read access too, to create a memory barrier in order to avoid that
+ threads just reads an old cached version of the signal.
+ */
+ mysql_mutex_lock(&debug_sync_global.ds_mutex);
+
+ if (action->signal.length())
+ {
+ /* Copy the signal to the global variable. */
+ if (debug_sync_global.ds_signal.copy(action->signal))
+ {
+ /*
+ Error is reported by my_malloc().
+ We must disable the facility. We have no way to return an error.
+ */
+ debug_sync_emergency_disable(); /* purecov: tested */
+ }
+ /* Wake threads waiting in a sync point. */
+ mysql_cond_broadcast(&debug_sync_global.ds_cond);
+ DBUG_PRINT("debug_sync_exec", ("signal '%s' at: '%s'",
+ sig_emit, dsp_name));
+ } /* end if (action->signal.length()) */
+
+ if (action->wait_for.length())
+ {
+ mysql_mutex_t *old_mutex= NULL;
+ mysql_cond_t *old_cond= NULL;
+ bool restore_current_mutex;
+ int error= 0;
+ struct timespec abstime;
+
+ /*
+ We don't use enter_cond()/exit_cond(). They do not save old
+ mutex and cond. This would prohibit the use of DEBUG_SYNC
+ between other places of enter_cond() and exit_cond().
+
+ We need to check for existence of thd->mysys_var to also make
+ it possible to use DEBUG_SYNC framework in scheduler when this
+ variable has been set to NULL.
+ */
+ if (thd->mysys_var)
+ {
+ old_mutex= thd->mysys_var->current_mutex;
+ old_cond= thd->mysys_var->current_cond;
+ restore_current_mutex = true;
+ thd->mysys_var->current_mutex= &debug_sync_global.ds_mutex;
+ thd->mysys_var->current_cond= &debug_sync_global.ds_cond;
+ }
+ else
+ restore_current_mutex = false;
+
+ set_timespec(abstime, action->timeout);
+ DBUG_EXECUTE("debug_sync_exec", {
+ /* Functions as DBUG_PRINT args can change keyword and line nr. */
+ const char *sig_glob= debug_sync_global.ds_signal.c_ptr();
+ DBUG_PRINT("debug_sync_exec",
+ ("wait for '%s' at: '%s' curr: '%s'",
+ sig_wait, dsp_name, sig_glob));});
+
+ /*
+ Wait until global signal string matches the wait_for string.
+ Interrupt when thread or query is killed or facility disabled.
+ The facility can become disabled when some thread cannot get
+ the required dynamic memory allocated.
+ */
+ while (stringcmp(&debug_sync_global.ds_signal, &action->wait_for) &&
+ !thd->killed && opt_debug_sync_timeout)
+ {
+ error= mysql_cond_timedwait(&debug_sync_global.ds_cond,
+ &debug_sync_global.ds_mutex,
+ &abstime);
+ DBUG_EXECUTE("debug_sync", {
+ /* Functions as DBUG_PRINT args can change keyword and line nr. */
+ const char *sig_glob= debug_sync_global.ds_signal.c_ptr();
+ DBUG_PRINT("debug_sync",
+ ("awoke from %s global: %s error: %d",
+ sig_wait, sig_glob, error));});
+ if (error == ETIMEDOUT || error == ETIME)
+ {
+ // We should not make the statement fail, even if in strict mode.
+ const bool save_abort_on_warning= thd->abort_on_warning;
+ thd->abort_on_warning= false;
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_DEBUG_SYNC_TIMEOUT, ER(ER_DEBUG_SYNC_TIMEOUT));
+ thd->abort_on_warning= save_abort_on_warning;
+ DBUG_EXECUTE_IF("debug_sync_abort_on_timeout", DBUG_ABORT(););
+ break;
+ }
+ error= 0;
+ }
+ DBUG_EXECUTE("debug_sync_exec",
+ if (thd->killed)
+ DBUG_PRINT("debug_sync_exec",
+ ("killed %d from '%s' at: '%s'",
+ thd->killed, sig_wait, dsp_name));
+ else
+ DBUG_PRINT("debug_sync_exec",
+ ("%s from '%s' at: '%s'",
+ error ? "timeout" : "resume",
+ sig_wait, dsp_name)););
+
+ /*
+ We don't use enter_cond()/exit_cond(). They do not save old
+ mutex and cond. This would prohibit the use of DEBUG_SYNC
+ between other places of enter_cond() and exit_cond(). The
+ protected mutex must always unlocked _before_ mysys_var->mutex
+ is locked. (See comment in THD::exit_cond().)
+ */
+ mysql_mutex_unlock(&debug_sync_global.ds_mutex);
+ if (restore_current_mutex)
+ {
+ mysql_mutex_lock(&thd->mysys_var->mutex);
+ thd->mysys_var->current_mutex= old_mutex;
+ thd->mysys_var->current_cond= old_cond;
+ thd_proc_info(thd, old_proc_info);
+ mysql_mutex_unlock(&thd->mysys_var->mutex);
+ }
+ else
+ thd_proc_info(thd, old_proc_info);
+ }
+ else
+ {
+ /* In case we don't wait, we just release the mutex. */
+ mysql_mutex_unlock(&debug_sync_global.ds_mutex);
+ } /* end if (action->wait_for.length()) */
+
+ } /* end if (action->execute) */
+
+ /* hit_limit is zero for infinite. Don't decrement unconditionally. */
+ if (action->hit_limit)
+ {
+ if (!--action->hit_limit)
+ {
+ thd->killed= KILL_QUERY;
+ my_error(ER_DEBUG_SYNC_HIT_LIMIT, MYF(0));
+ }
+ DBUG_PRINT("debug_sync_exec", ("hit_limit: %lu at: '%s'",
+ action->hit_limit, dsp_name));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Execute requested action at a synchronization point.
+
+ @param[in] thd thread handle
+ @param[in] sync_point_name name of synchronization point
+ @param[in] name_len length of sync point name
+*/
+
+static void debug_sync(THD *thd, const char *sync_point_name, size_t name_len)
+{
+ if (!thd)
+ {
+ if (!(thd= current_thd))
+ return;
+ }
+
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ st_debug_sync_action *action;
+ DBUG_ENTER("debug_sync");
+ DBUG_ASSERT(sync_point_name);
+ DBUG_ASSERT(name_len);
+ DBUG_ASSERT(ds_control);
+ DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name));
+
+ /* Statistics. */
+ ds_control->dsp_hits++;
+
+ if (ds_control->ds_active &&
+ (action= debug_sync_find(ds_control->ds_action, ds_control->ds_active,
+ sync_point_name, name_len)) &&
+ action->activation_count)
+ {
+ /* Sync point is active (action exists). */
+ debug_sync_execute(thd, action);
+
+ /* Statistics. */
+ ds_control->dsp_executed++;
+
+ /* If action became inactive, remove it to shrink the search array. */
+ if (!action->activation_count)
+ debug_sync_remove_action(ds_control, action);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Define debug sync action.
+
+ @param[in] thd thread handle
+ @param[in] action_str action string
+
+ @return status
+ @retval FALSE ok
+ @retval TRUE error
+
+ @description
+ The function is similar to @c debug_sync_eval_action but is
+ to be called immediately from the server code rather than
+ to be triggered by setting a value to DEBUG_SYNC system variable.
+
+ @note
+ The input string is copied prior to be fed to
+ @c debug_sync_eval_action to let the latter modify it.
+
+ Caution.
+ The function allocates in THD::mem_root and therefore
+ is not recommended to be deployed inside big loops.
+*/
+
+bool debug_sync_set_action(THD *thd, const char *action_str, size_t len)
+{
+ bool rc;
+ char *value;
+ DBUG_ENTER("debug_sync_set_action");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(action_str);
+
+ value= strmake_root(thd->mem_root, action_str, len);
+ rc= debug_sync_eval_action(thd, value);
+ DBUG_RETURN(rc);
+}
+
+
+#else /* defined(ENABLED_DEBUG_SYNC) */
+/* prevent linker/lib warning about file without public symbols */
+int debug_sync_dummy;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
diff --git a/sql/debug_sync.h b/sql/debug_sync.h
new file mode 100644
index 00000000000..bf1b3167dbc
--- /dev/null
+++ b/sql/debug_sync.h
@@ -0,0 +1,50 @@
+#ifndef DEBUG_SYNC_INCLUDED
+#define DEBUG_SYNC_INCLUDED
+
+/* Copyright (c) 2009, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+/**
+ @file
+
+ Declarations for the Debug Sync Facility. See debug_sync.cc for details.
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include <my_global.h>
+
+class THD;
+
+#if defined(ENABLED_DEBUG_SYNC)
+
+/* Command line option --debug-sync-timeout. See mysqld.cc. */
+extern MYSQL_PLUGIN_IMPORT uint opt_debug_sync_timeout;
+
+/* Default WAIT_FOR timeout if command line option is given without argument. */
+#define DEBUG_SYNC_DEFAULT_WAIT_TIMEOUT 300
+
+/* Debug Sync prototypes. See debug_sync.cc. */
+extern int debug_sync_init(void);
+extern void debug_sync_end(void);
+extern void debug_sync_init_thread(THD *thd);
+extern void debug_sync_end_thread(THD *thd);
+extern bool debug_sync_set_action(THD *thd, const char *action_str, size_t len);
+
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
+#endif /* DEBUG_SYNC_INCLUDED */
diff --git a/sql/derror.cc b/sql/derror.cc
new file mode 100644
index 00000000000..f19f73238fb
--- /dev/null
+++ b/sql/derror.cc
@@ -0,0 +1,265 @@
+/* Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+ Copyright (C) 2011 Monty Program Ab
+
+ 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 */
+
+/**
+ @file
+
+ @brief
+ Read language depeneded messagefile
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "derror.h"
+#include "mysys_err.h"
+#include "mysqld.h" // lc_messages_dir
+#include "derror.h" // read_texts
+#include "sql_class.h" // THD
+
+static bool check_error_mesg(const char *file_name, const char **errmsg);
+static void init_myfunc_errs(void);
+
+
+C_MODE_START
+static const char **get_server_errmsgs()
+{
+ if (!current_thd)
+ return DEFAULT_ERRMSGS;
+ return CURRENT_THD_ERRMSGS;
+}
+C_MODE_END
+
+/**
+ Read messages from errorfile.
+
+ This function can be called multiple times to reload the messages.
+
+ If it fails to load the messages:
+ - If we already have error messages loaded, keep the old ones and
+ return FALSE(ok)
+ - Initializing the errmesg pointer to an array of empty strings
+ and return TRUE (error)
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+bool init_errmessage(void)
+{
+ const char **errmsgs, **ptr, **org_errmsgs;
+ bool error= FALSE;
+ DBUG_ENTER("init_errmessage");
+
+ /*
+ Get a pointer to the old error messages pointer array.
+ read_texts() tries to free it.
+ */
+ org_errmsgs= my_error_unregister(ER_ERROR_FIRST, ER_ERROR_LAST);
+
+ /* Read messages from file. */
+ if (read_texts(ERRMSG_FILE, my_default_lc_messages->errmsgs->language,
+ &errmsgs, ER_ERROR_LAST - ER_ERROR_FIRST + 1) &&
+ !errmsgs)
+ {
+ my_free(errmsgs);
+
+ if (org_errmsgs)
+ {
+ /* Use old error messages */
+ errmsgs= org_errmsgs;
+ }
+ else
+ {
+ /*
+ No error messages. Create a temporary empty error message so
+ that we don't get a crash if some code wrongly tries to access
+ a non existing error message.
+ */
+ if (!(errmsgs= (const char**) my_malloc((ER_ERROR_LAST-ER_ERROR_FIRST+1)*
+ sizeof(char*), MYF(0))))
+ DBUG_RETURN(TRUE);
+ for (ptr= errmsgs; ptr < errmsgs + ER_ERROR_LAST - ER_ERROR_FIRST; ptr++)
+ *ptr= "";
+ error= TRUE;
+ }
+ }
+ else
+ my_free(org_errmsgs); // Free old language
+
+ /* Register messages for use with my_error(). */
+ if (my_error_register(get_server_errmsgs, ER_ERROR_FIRST, ER_ERROR_LAST))
+ {
+ my_free(errmsgs);
+ DBUG_RETURN(TRUE);
+ }
+
+ DEFAULT_ERRMSGS= errmsgs; /* Init global variable */
+ init_myfunc_errs(); /* Init myfunc messages */
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Check the error messages array contains all relevant error messages
+*/
+
+static bool check_error_mesg(const char *file_name, const char **errmsg)
+{
+ /*
+ The last MySQL error message can't be an empty string; If it is,
+ it means that the error file doesn't contain all MySQL messages
+ and is probably from an older version of MySQL / MariaDB.
+ */
+ if (errmsg[ER_LAST_MYSQL_ERROR_MESSAGE -1 - ER_ERROR_FIRST][0] == 0)
+ {
+ sql_print_error("Error message file '%s' is probably from and older "
+ "version of MariaDB / MYSQL as it doesn't contain all "
+ "error messages", file_name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Read text from packed textfile in language-directory.
+
+ If we can't read messagefile then it's panic- we can't continue.
+*/
+
+bool read_texts(const char *file_name, const char *language,
+ const char ***point, uint error_messages)
+{
+ register uint i;
+ uint count,funktpos;
+ size_t offset, length;
+ File file;
+ char name[FN_REFLEN];
+ char lang_path[FN_REFLEN];
+ uchar *buff;
+ uchar head[32],*pos;
+ DBUG_ENTER("read_texts");
+
+ *point= 0;
+
+ LINT_INIT(buff);
+ funktpos=0;
+ convert_dirname(lang_path, language, NullS);
+ (void) my_load_path(lang_path, lang_path, lc_messages_dir);
+ if ((file= mysql_file_open(key_file_ERRMSG,
+ fn_format(name, file_name, lang_path, "", 4),
+ O_RDONLY | O_SHARE | O_BINARY,
+ MYF(0))) < 0)
+ {
+ /*
+ Trying pre-5.4 sematics of the --language parameter.
+ It included the language-specific part, e.g.:
+
+ --language=/path/to/english/
+ */
+ if ((file= mysql_file_open(key_file_ERRMSG,
+ fn_format(name, file_name, lc_messages_dir, "", 4),
+ O_RDONLY | O_SHARE | O_BINARY,
+ MYF(0))) < 0)
+ goto err;
+ sql_print_warning("An old style --language or -lc-message-dir value with language specific part detected: %s", lc_messages_dir);
+ sql_print_warning("Use --lc-messages-dir without language specific part instead.");
+ }
+
+ funktpos=1;
+ if (mysql_file_read(file, (uchar*) head, 32, MYF(MY_NABP)))
+ goto err;
+ funktpos=2;
+ if (head[0] != (uchar) 254 || head[1] != (uchar) 254 ||
+ head[2] != 2 || head[3] != 3)
+ goto err; /* purecov: inspected */
+
+ error_message_charset_info= system_charset_info;
+ length=uint4korr(head+6); count=uint2korr(head+10);
+
+ if (count < error_messages)
+ {
+ sql_print_error("\
+Error message file '%s' had only %d error messages, but it should contain at least %d error messages.\nCheck that the above file is the right version for this program!",
+ name,count,error_messages);
+ (void) mysql_file_close(file, MYF(MY_WME));
+ DBUG_RETURN(1);
+ }
+
+ if (!(*point= (const char**)
+ my_malloc((size_t) (MY_MAX(length,count*2)+count*sizeof(char*)),MYF(0))))
+ {
+ funktpos=3; /* purecov: inspected */
+ goto err; /* purecov: inspected */
+ }
+ buff= (uchar*) (*point + count);
+
+ if (mysql_file_read(file, buff, (size_t) count*2, MYF(MY_NABP)))
+ goto err;
+ for (i=0, offset=0, pos= buff ; i< count ; i++)
+ {
+ (*point)[i]= (char*) buff+offset;
+ offset+= uint2korr(pos);
+ pos+=2;
+ }
+ if (mysql_file_read(file, buff, length, MYF(MY_NABP)))
+ goto err;
+
+ (void) mysql_file_close(file, MYF(0));
+
+ i= check_error_mesg(file_name, *point);
+ DBUG_RETURN(i);
+
+err:
+ sql_print_error((funktpos == 3) ? "Not enough memory for messagefile '%s'" :
+ (funktpos == 2) ? "Incompatible header in messagefile '%s'. Probably from another version of MariaDB" :
+ ((funktpos == 1) ? "Can't read from messagefile '%s'" :
+ "Can't find messagefile '%s'"), name);
+ if (file != FERR)
+ (void) mysql_file_close(file, MYF(MY_WME));
+ DBUG_RETURN(1);
+} /* read_texts */
+
+
+/**
+ Initiates error-messages used by my_func-library.
+*/
+
+static void init_myfunc_errs()
+{
+ init_glob_errs(); /* Initiate english errors */
+ if (!(specialflag & SPECIAL_ENGLISH))
+ {
+ EE(EE_FILENOTFOUND) = ER(ER_FILE_NOT_FOUND);
+ EE(EE_CANTCREATEFILE) = ER(ER_CANT_CREATE_FILE);
+ EE(EE_READ) = ER(ER_ERROR_ON_READ);
+ EE(EE_WRITE) = ER(ER_ERROR_ON_WRITE);
+ EE(EE_BADCLOSE) = ER(ER_ERROR_ON_CLOSE);
+ EE(EE_OUTOFMEMORY) = ER(ER_OUTOFMEMORY);
+ EE(EE_DELETE) = ER(ER_CANT_DELETE_FILE);
+ EE(EE_LINK) = ER(ER_ERROR_ON_RENAME);
+ EE(EE_EOFERR) = ER(ER_UNEXPECTED_EOF);
+ EE(EE_CANTLOCK) = ER(ER_CANT_LOCK);
+ EE(EE_DIR) = ER(ER_CANT_READ_DIR);
+ EE(EE_STAT) = ER(ER_CANT_GET_STAT);
+ EE(EE_GETWD) = ER(ER_CANT_GET_WD);
+ EE(EE_SETWD) = ER(ER_CANT_SET_WD);
+ EE(EE_DISK_FULL) = ER(ER_DISK_FULL);
+ }
+}
diff --git a/sql/derror.h b/sql/derror.h
new file mode 100644
index 00000000000..b2f6331e048
--- /dev/null
+++ b/sql/derror.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef DERROR_INCLUDED
+#define DERROR_INCLUDED
+
+#include "my_global.h" /* uint */
+
+bool init_errmessage(void);
+bool read_texts(const char *file_name, const char *language,
+ const char ***point, uint error_messages);
+
+#endif /* DERROR_INCLUDED */
diff --git a/sql/des_key_file.cc b/sql/des_key_file.cc
new file mode 100644
index 00000000000..ede2e9fa9d4
--- /dev/null
+++ b/sql/des_key_file.cc
@@ -0,0 +1,106 @@
+/* Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h> // HAVE_*
+#include "sql_priv.h"
+#include "des_key_file.h" // st_des_keyschedule, st_des_keyblock
+#include "log.h" // sql_print_error
+#include <m_ctype.h>
+
+#ifdef HAVE_OPENSSL
+
+struct st_des_keyschedule des_keyschedule[10];
+uint des_default_key;
+
+#define des_cs &my_charset_latin1
+
+/**
+ Load DES keys from plaintext file into
+ memory on MySQL server startup and on command FLUSH DES_KEY_FILE.
+
+ @retval
+ 0 ok
+ @retval
+ 1 Error
+*/
+
+
+bool
+load_des_key_file(const char *file_name)
+{
+ bool result=1;
+ File file;
+ IO_CACHE io;
+ DBUG_ENTER("load_des_key_file");
+ DBUG_PRINT("enter",("name: %s",file_name));
+
+ mysql_mutex_lock(&LOCK_des_key_file);
+ if ((file= mysql_file_open(key_file_des_key_file, file_name,
+ O_RDONLY | O_BINARY, MYF(MY_WME))) < 0 ||
+ init_io_cache(&io, file, IO_SIZE*2, READ_CACHE, 0, 0, MYF(MY_WME)))
+ goto error;
+
+ bzero((char*) des_keyschedule,sizeof(struct st_des_keyschedule) * 10);
+ des_default_key=15; // Impossible key
+ for (;;)
+ {
+ char *start, *end;
+ char buf[1024], offset;
+ st_des_keyblock keyblock;
+ uint length;
+
+ if (!(length=my_b_gets(&io,buf,sizeof(buf)-1)))
+ break; // End of file
+ offset=buf[0];
+ if (offset >= '0' && offset <= '9') // If ok key
+ {
+ offset=(char) (offset - '0');
+ // Remove newline and possible other control characters
+ for (start=buf+1 ; my_isspace(des_cs, *start) ; start++) ;
+ end=buf+length;
+ for (end=strend(buf) ;
+ end > start && !my_isgraph(des_cs, end[-1]) ; end--) ;
+
+ if (start != end)
+ {
+ DES_cblock ivec;
+ bzero((char*) &ivec,sizeof(ivec));
+ // We make good 24-byte (168 bit) key from given plaintext key with MD5
+ EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL,
+ (uchar *) start, (int) (end-start),1,
+ (uchar *) &keyblock,
+ ivec);
+ DES_set_key_unchecked(&keyblock.key1,&(des_keyschedule[(int)offset].ks1));
+ DES_set_key_unchecked(&keyblock.key2,&(des_keyschedule[(int)offset].ks2));
+ DES_set_key_unchecked(&keyblock.key3,&(des_keyschedule[(int)offset].ks3));
+ if (des_default_key == 15)
+ des_default_key= (uint) offset; // use first as def.
+ }
+ }
+ else if (offset != '#')
+ sql_print_error("load_des_file: Found wrong key_number: %c",offset);
+ }
+ result=0;
+
+error:
+ if (file >= 0)
+ {
+ mysql_file_close(file, MYF(0));
+ end_io_cache(&io);
+ }
+ mysql_mutex_unlock(&LOCK_des_key_file);
+ DBUG_RETURN(result);
+}
+#endif /* HAVE_OPENSSL */
diff --git a/sql/des_key_file.h b/sql/des_key_file.h
new file mode 100644
index 00000000000..024a1715d47
--- /dev/null
+++ b/sql/des_key_file.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef DES_KEY_FILE_INCLUDED
+#define DES_KEY_FILE_INCLUDED
+
+#ifdef HAVE_OPENSSL
+#include <openssl/des.h>
+
+#include "violite.h" /* DES_cblock, DES_key_schedule */
+
+struct st_des_keyblock
+{
+ DES_cblock key1, key2, key3;
+};
+
+struct st_des_keyschedule
+{
+ DES_key_schedule ks1, ks2, ks3;
+};
+
+extern struct st_des_keyschedule des_keyschedule[10];
+extern uint des_default_key;
+
+bool load_des_key_file(const char *file_name);
+#endif /* HAVE_OPENSSL */
+
+#endif /* DES_KEY_FILE_INCLUDED */
diff --git a/sql/discover.cc b/sql/discover.cc
new file mode 100644
index 00000000000..82648e94bc5
--- /dev/null
+++ b/sql/discover.cc
@@ -0,0 +1,270 @@
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/**
+ @file
+
+ @brief
+ Functions for discover of frm file from handler
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "discover.h"
+#include <my_dir.h>
+
+/**
+ Read the contents of a .frm file.
+
+ frmdata and len are set to 0 on error.
+
+ @param name path to table-file "db/name"
+ @param frmdata frm data
+ @param len length of the read frmdata
+
+ @retval
+ 0 ok
+ @retval
+ 1 Could not open file
+ @retval
+ 2 Could not stat file
+ @retval
+ 3 Could not allocate data for read. Could not read file
+*/
+
+int readfrm(const char *name, const uchar **frmdata, size_t *len)
+{
+ int error;
+ char index_file[FN_REFLEN];
+ File file;
+ size_t read_len;
+ uchar *read_data;
+ MY_STAT state;
+ DBUG_ENTER("readfrm");
+ DBUG_PRINT("enter",("name: '%s'",name));
+
+ *frmdata= NULL; // In case of errors
+ *len= 0;
+ error= 1;
+ if ((file= mysql_file_open(key_file_frm,
+ fn_format(index_file, name, "", reg_ext,
+ MY_UNPACK_FILENAME|MY_APPEND_EXT),
+ O_RDONLY | O_SHARE,
+ MYF(0))) < 0)
+ goto err_end;
+
+ // Get length of file
+ error= 2;
+ if (mysql_file_fstat(file, &state, MYF(0)))
+ goto err;
+ read_len= (size_t)MY_MIN(FRM_MAX_SIZE, state.st_size); // safety
+
+ // Read whole frm file
+ error= 3;
+ if (!(read_data= (uchar*)my_malloc(read_len, MYF(MY_WME))))
+ goto err;
+ if (mysql_file_read(file, read_data, read_len, MYF(MY_NABP)))
+ {
+ my_free(read_data);
+ goto err;
+ }
+
+ // Setup return data
+ *frmdata= (uchar*) read_data;
+ *len= read_len;
+ error= 0;
+
+ err:
+ if (file > 0)
+ (void) mysql_file_close(file, MYF(MY_WME));
+
+ err_end: /* Here when no file */
+ DBUG_RETURN (error);
+} /* readfrm */
+
+
+/*
+ Write the content of a frm data pointer
+ to a frm file.
+
+ @param path path to table-file "db/name"
+ @param frmdata frm data
+ @param len length of the frmdata
+
+ @retval
+ 0 ok
+ @retval
+ 2 Could not write file
+*/
+
+int writefrm(const char *path, const char *db, const char *table,
+ bool tmp_table, const uchar *frmdata, size_t len)
+{
+ char file_name[FN_REFLEN+1];
+ int error;
+ int create_flags= O_RDWR | O_TRUNC;
+ DBUG_ENTER("writefrm");
+ DBUG_PRINT("enter",("name: '%s' len: %lu ",path, (ulong) len));
+
+ if (tmp_table)
+ create_flags|= O_EXCL | O_NOFOLLOW;
+
+ strxnmov(file_name, sizeof(file_name)-1, path, reg_ext, NullS);
+
+ File file= mysql_file_create(key_file_frm, file_name,
+ CREATE_MODE, create_flags, MYF(0));
+
+ if ((error= file < 0))
+ {
+ if (my_errno == ENOENT)
+ my_error(ER_BAD_DB_ERROR, MYF(0), db);
+ else
+ my_error(ER_CANT_CREATE_TABLE, MYF(0), db, table, my_errno);
+ }
+ else
+ {
+ error= mysql_file_write(file, frmdata, len, MYF(MY_WME | MY_NABP));
+
+ if (!error && !tmp_table && opt_sync_frm)
+ error= mysql_file_sync(file, MYF(MY_WME)) ||
+ my_sync_dir_by_file(file_name, MYF(MY_WME));
+
+ error|= mysql_file_close(file, MYF(MY_WME));
+ }
+ DBUG_RETURN(error);
+} /* writefrm */
+
+static inline void advance(FILEINFO* &from, FILEINFO* &to,
+ FILEINFO* cur, bool &skip)
+{
+ if (skip) // if not copying
+ from= cur; // just advance the start pointer
+ else // if copying
+ if (to == from) // but to the same place (not shifting the data)
+ from= to= cur; // advance both pointers
+ else // otherwise
+ while (from < cur) // have to copy [from...cur) to [to...)
+ *to++ = *from++;
+ skip= false;
+}
+
+/**
+ Go through the directory listing looking for files with a specified
+ extension and add them to the result list
+
+ @details
+ This function may be called many times on the same directory listing
+ but with different extensions. To avoid discovering the same table twice,
+ whenever a table file is discovered, all files with the same name
+ (independently from the extensions) are removed from the list.
+
+ Example: the list contained
+ { "db.opt", "t1.MYD", "t1.MYI", "t1.frm", "t2.ARZ", "t3.ARZ", "t3.frm" }
+ on discovering all ".frm" files, tables "t1" and "t3" will be found,
+ and list will become
+ { "db.opt", "t2.ARZ" }
+ and now ".ARZ" discovery can discover the table "t2"
+
+ @note
+ This function assumes that the directory listing is sorted alphabetically.
+
+ @note Partitioning makes this more complicated. A partitioned table t1 might
+ have files, like t1.frm, t1#P#part1.ibd, t1#P#foo.ibd, etc.
+ That means we need to compare file names only up to the first '#' or '.'
+ whichever comes first.
+*/
+int extension_based_table_discovery(MY_DIR *dirp, const char *ext_meta,
+ handlerton::discovered_list *result)
+{
+ CHARSET_INFO *cs= character_set_filesystem;
+ size_t ext_meta_len= strlen(ext_meta);
+ FILEINFO *from, *to, *cur, *end;
+ bool skip= false;
+
+ from= to= cur= dirp->dir_entry;
+ end= cur + dirp->number_of_files;
+ while (cur < end)
+ {
+ char *octothorp= strrchr(cur->name + 1, '#');
+ char *ext= strchr(octothorp ? octothorp : cur->name, FN_EXTCHAR);
+
+ if (ext)
+ {
+ size_t len= (octothorp ? octothorp : ext) - cur->name;
+ if (from != cur &&
+ (my_strnncoll(cs, (uchar*)from->name, len, (uchar*)cur->name, len) ||
+ (from->name[len] != FN_EXTCHAR && from->name[len] != '#')))
+ advance(from, to, cur, skip);
+
+ if (my_strnncoll(cs, (uchar*)ext, strlen(ext),
+ (uchar*)ext_meta, ext_meta_len) == 0)
+ {
+ *ext = 0;
+ if (result->add_file(cur->name))
+ return 1;
+ *ext = FN_EXTCHAR;
+ skip= true; // table discovered, skip all files with the same name
+ }
+ }
+ else
+ {
+ advance(from, to, cur, skip);
+ from++;
+ }
+
+ cur++;
+ }
+ advance(from, to, cur, skip);
+ dirp->number_of_files= to - dirp->dir_entry;
+ return 0;
+}
+
+/**
+ Simple, not reusable file-based table discovery
+
+ @details
+ simplified version of extension_based_table_discovery(), that does not
+ modify the list of files. It cannot be called many times for the same
+ directory listing, otherwise it'll produce duplicate results.
+*/
+int ext_table_discovery_simple(MY_DIR *dirp,
+ handlerton::discovered_list *result)
+{
+ CHARSET_INFO *cs= character_set_filesystem;
+ FILEINFO *cur, *end;
+
+ cur= dirp->dir_entry;
+ end= cur + dirp->number_of_files;
+ while (cur < end)
+ {
+ char *ext= strrchr(cur->name, FN_EXTCHAR);
+
+ if (ext)
+ {
+ if (my_strnncoll(cs, (uchar*)ext, strlen(ext),
+ (uchar*)reg_ext, reg_ext_length) == 0)
+ {
+ *ext = 0;
+ if (result->add_file(cur->name))
+ return 1;
+ }
+ }
+ cur++;
+ }
+ return 0;
+}
+
diff --git a/sql/discover.h b/sql/discover.h
new file mode 100644
index 00000000000..e1508107235
--- /dev/null
+++ b/sql/discover.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef DISCOVER_INCLUDED
+#define DISCOVER_INCLUDED
+
+#include "my_global.h" /* uchar */
+
+int extension_based_table_discovery(MY_DIR *dirp, const char *ext,
+ handlerton::discovered_list *tl);
+
+#ifdef MYSQL_SERVER
+int readfrm(const char *name, const uchar **data, size_t *length);
+int writefrm(const char *path, const char *db, const char *table,
+ bool tmp_table, const uchar *frmdata, size_t len);
+
+/* a helper to delete an frm file, given a path w/o .frm extension */
+inline void deletefrm(const char *path)
+{
+ char frm_name[FN_REFLEN];
+ strxmov(frm_name, path, reg_ext, NullS);
+ mysql_file_delete(key_file_frm, frm_name, MYF(0));
+}
+
+int ext_table_discovery_simple(MY_DIR *dirp,
+ handlerton::discovered_list *result);
+#endif
+
+#endif /* DISCOVER_INCLUDED */
diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc
new file mode 100644
index 00000000000..09256a34853
--- /dev/null
+++ b/sql/event_data_objects.cc
@@ -0,0 +1,1540 @@
+/*
+ Copyright (c) 2005, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+#define MYSQL_LEX 1
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_parse.h" // parse_sql
+#include "strfunc.h" // find_string_in_array
+#include "sql_db.h" // get_default_db_collation
+#include "sql_time.h" // interval_type_to_name,
+ // date_add_interval,
+ // calc_time_diff
+#include "tztime.h" // my_tz_find, my_tz_OFFSET0, struct Time_zone
+#include "sql_acl.h" // EVENT_ACL, SUPER_ACL
+#include "sp.h" // load_charset, load_collation
+#include "events.h"
+#include "event_data_objects.h"
+#include "event_db_repository.h"
+#include "sp_head.h"
+#include "sql_show.h" // append_definer, append_identifier
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+*/
+
+/*************************************************************************/
+
+/**
+ Event_creation_ctx -- creation context of events.
+*/
+
+class Event_creation_ctx :public Stored_program_creation_ctx,
+ public Sql_alloc
+{
+public:
+ static bool load_from_db(THD *thd,
+ MEM_ROOT *event_mem_root,
+ const char *db_name,
+ const char *event_name,
+ TABLE *event_tbl,
+ Stored_program_creation_ctx **ctx);
+
+public:
+ virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root)
+ {
+ return new (mem_root)
+ Event_creation_ctx(m_client_cs, m_connection_cl, m_db_cl);
+ }
+
+protected:
+ virtual Object_creation_ctx *create_backup_ctx(THD *thd) const
+ {
+ /*
+ We can avoid usual backup/restore employed in stored programs since we
+ know that this is a top level statement and the worker thread is
+ allocated exclusively to execute this event.
+ */
+
+ return NULL;
+ }
+
+private:
+ Event_creation_ctx(CHARSET_INFO *client_cs,
+ CHARSET_INFO *connection_cl,
+ CHARSET_INFO *db_cl)
+ : Stored_program_creation_ctx(client_cs, connection_cl, db_cl)
+ { }
+};
+
+/**************************************************************************
+ Event_creation_ctx implementation.
+**************************************************************************/
+
+bool
+Event_creation_ctx::load_from_db(THD *thd,
+ MEM_ROOT *event_mem_root,
+ const char *db_name,
+ const char *event_name,
+ TABLE *event_tbl,
+ Stored_program_creation_ctx **ctx)
+{
+ /* Load character set/collation attributes. */
+
+ CHARSET_INFO *client_cs;
+ CHARSET_INFO *connection_cl;
+ CHARSET_INFO *db_cl;
+
+ bool invalid_creation_ctx= FALSE;
+
+ if (load_charset(event_mem_root,
+ event_tbl->field[ET_FIELD_CHARACTER_SET_CLIENT],
+ thd->variables.character_set_client,
+ &client_cs))
+ {
+ sql_print_warning("Event '%s'.'%s': invalid value "
+ "in column mysql.event.character_set_client.",
+ (const char *) db_name,
+ (const char *) event_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (load_collation(event_mem_root,
+ event_tbl->field[ET_FIELD_COLLATION_CONNECTION],
+ thd->variables.collation_connection,
+ &connection_cl))
+ {
+ sql_print_warning("Event '%s'.'%s': invalid value "
+ "in column mysql.event.collation_connection.",
+ (const char *) db_name,
+ (const char *) event_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (load_collation(event_mem_root,
+ event_tbl->field[ET_FIELD_DB_COLLATION],
+ NULL,
+ &db_cl))
+ {
+ sql_print_warning("Event '%s'.'%s': invalid value "
+ "in column mysql.event.db_collation.",
+ (const char *) db_name,
+ (const char *) event_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ /*
+ If we failed to resolve the database collation, load the default one
+ from the disk.
+ */
+
+ if (!db_cl)
+ db_cl= get_default_db_collation(thd, db_name);
+
+ /* Create the context. */
+
+ *ctx= new Event_creation_ctx(client_cs, connection_cl, db_cl);
+
+ return invalid_creation_ctx;
+}
+
+/*************************************************************************/
+
+/*
+ Initiliazes dbname and name of an Event_queue_element_for_exec
+ object
+
+ SYNOPSIS
+ Event_queue_element_for_exec::init()
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error (OOM)
+*/
+
+bool
+Event_queue_element_for_exec::init(LEX_STRING db, LEX_STRING n)
+{
+ if (!(dbname.str= my_strndup(db.str, dbname.length= db.length, MYF(MY_WME))))
+ return TRUE;
+ if (!(name.str= my_strndup(n.str, name.length= n.length, MYF(MY_WME))))
+ {
+ my_free(dbname.str);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_queue_element_for_exec::~Event_queue_element_for_exec()
+*/
+
+Event_queue_element_for_exec::~Event_queue_element_for_exec()
+{
+ my_free(dbname.str);
+ my_free(name.str);
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_basic::Event_basic()
+*/
+
+Event_basic::Event_basic()
+{
+ DBUG_ENTER("Event_basic::Event_basic");
+ /* init memory root */
+ init_sql_alloc(&mem_root, 256, 512, MYF(0));
+ dbname.str= name.str= NULL;
+ dbname.length= name.length= 0;
+ time_zone= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_basic::Event_basic()
+*/
+
+Event_basic::~Event_basic()
+{
+ DBUG_ENTER("Event_basic::~Event_basic");
+ free_root(&mem_root, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Short function to load a char column into a LEX_STRING
+
+ SYNOPSIS
+ Event_basic::load_string_field()
+ field_name The field( enum_events_table_field is not actually used
+ because it's unknown in event_data_objects.h)
+ fields The Field array
+ field_value The value
+*/
+
+bool
+Event_basic::load_string_fields(Field **fields, ...)
+{
+ bool ret= FALSE;
+ va_list args;
+ enum enum_events_table_field field_name;
+ LEX_STRING *field_value;
+
+ DBUG_ENTER("Event_basic::load_string_fields");
+
+ va_start(args, fields);
+ field_name= (enum enum_events_table_field) va_arg(args, int);
+ while (field_name < ET_FIELD_COUNT)
+ {
+ field_value= va_arg(args, LEX_STRING *);
+ if ((field_value->str= get_field(&mem_root, fields[field_name])) == NullS)
+ {
+ ret= TRUE;
+ break;
+ }
+ field_value->length= strlen(field_value->str);
+
+ field_name= (enum enum_events_table_field) va_arg(args, int);
+ }
+ va_end(args);
+
+ DBUG_RETURN(ret);
+}
+
+
+bool
+Event_basic::load_time_zone(THD *thd, const LEX_STRING tz_name)
+{
+ String str(tz_name.str, &my_charset_latin1);
+ time_zone= my_tz_find(thd, &str);
+
+ return (time_zone == NULL);
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_queue_element::Event_queue_element()
+*/
+
+Event_queue_element::Event_queue_element():
+ on_completion(Event_parse_data::ON_COMPLETION_DROP),
+ status(Event_parse_data::ENABLED), expression(0), dropped(FALSE),
+ execution_count(0)
+{
+ DBUG_ENTER("Event_queue_element::Event_queue_element");
+
+ starts= ends= execute_at= last_executed= 0;
+ starts_null= ends_null= execute_at_null= TRUE;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_queue_element::Event_queue_element()
+*/
+Event_queue_element::~Event_queue_element()
+{
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_timed::Event_timed()
+*/
+
+Event_timed::Event_timed():
+ created(0), modified(0), sql_mode(0)
+{
+ DBUG_ENTER("Event_timed::Event_timed");
+ init();
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_timed::~Event_timed()
+*/
+
+Event_timed::~Event_timed()
+{
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_job_data::Event_job_data()
+*/
+
+Event_job_data::Event_job_data()
+ :sql_mode(0)
+{
+}
+
+/*
+ Init all member variables
+
+ SYNOPSIS
+ Event_timed::init()
+*/
+
+void
+Event_timed::init()
+{
+ DBUG_ENTER("Event_timed::init");
+
+ definer_user.str= definer_host.str= body.str= comment.str= NULL;
+ definer_user.length= definer_host.length= body.length= comment.length= 0;
+
+ sql_mode= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Load an event's body from a row from mysql.event.
+
+ @details This method is silent on errors and should behave like that.
+ Callers should handle throwing of error messages. The reason is that the
+ class should not know about how to deal with communication.
+
+ @return Operation status
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool
+Event_job_data::load_from_row(THD *thd, TABLE *table)
+{
+ char *ptr;
+ size_t len;
+ LEX_STRING tz_name;
+
+ DBUG_ENTER("Event_job_data::load_from_row");
+
+ if (!table)
+ DBUG_RETURN(TRUE);
+
+ if (table->s->fields < ET_FIELD_COUNT)
+ DBUG_RETURN(TRUE);
+
+ if (load_string_fields(table->field,
+ ET_FIELD_DB, &dbname,
+ ET_FIELD_NAME, &name,
+ ET_FIELD_BODY, &body,
+ ET_FIELD_DEFINER, &definer,
+ ET_FIELD_TIME_ZONE, &tz_name,
+ ET_FIELD_COUNT))
+ DBUG_RETURN(TRUE);
+
+ if (load_time_zone(thd, tz_name))
+ DBUG_RETURN(TRUE);
+
+ Event_creation_ctx::load_from_db(thd, &mem_root, dbname.str, name.str, table,
+ &creation_ctx);
+
+ ptr= strchr(definer.str, '@');
+
+ if (! ptr)
+ ptr= definer.str;
+
+ len= ptr - definer.str;
+ definer_user.str= strmake_root(&mem_root, definer.str, len);
+ definer_user.length= len;
+ len= definer.length - len - 1;
+ /* 1:because of @ */
+ definer_host.str= strmake_root(&mem_root, ptr + 1, len);
+ definer_host.length= len;
+
+ sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int();
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Load an event's body from a row from mysql.event.
+
+ @details This method is silent on errors and should behave like that.
+ Callers should handle throwing of error messages. The reason is that the
+ class should not know about how to deal with communication.
+
+ @return Operation status
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool
+Event_queue_element::load_from_row(THD *thd, TABLE *table)
+{
+ char *ptr;
+ MYSQL_TIME time;
+ LEX_STRING tz_name;
+
+ DBUG_ENTER("Event_queue_element::load_from_row");
+
+ if (!table)
+ DBUG_RETURN(TRUE);
+
+ if (table->s->fields < ET_FIELD_COUNT)
+ DBUG_RETURN(TRUE);
+
+ if (load_string_fields(table->field,
+ ET_FIELD_DB, &dbname,
+ ET_FIELD_NAME, &name,
+ ET_FIELD_DEFINER, &definer,
+ ET_FIELD_TIME_ZONE, &tz_name,
+ ET_FIELD_COUNT))
+ DBUG_RETURN(TRUE);
+
+ if (load_time_zone(thd, tz_name))
+ DBUG_RETURN(TRUE);
+
+ starts_null= table->field[ET_FIELD_STARTS]->is_null();
+ uint not_used;
+ if (!starts_null)
+ {
+ table->field[ET_FIELD_STARTS]->get_date(&time, TIME_NO_ZERO_DATE);
+ starts= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+
+ ends_null= table->field[ET_FIELD_ENDS]->is_null();
+ if (!ends_null)
+ {
+ table->field[ET_FIELD_ENDS]->get_date(&time, TIME_NO_ZERO_DATE);
+ ends= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+
+ if (!table->field[ET_FIELD_INTERVAL_EXPR]->is_null())
+ expression= table->field[ET_FIELD_INTERVAL_EXPR]->val_int();
+ else
+ expression= 0;
+ /*
+ If neigher STARTS and ENDS is set, then both fields are empty.
+ Hence, if ET_FIELD_EXECUTE_AT is empty there is an error.
+ */
+ execute_at_null= table->field[ET_FIELD_EXECUTE_AT]->is_null();
+ DBUG_ASSERT(!(starts_null && ends_null && !expression && execute_at_null));
+ if (!expression && !execute_at_null)
+ {
+ if (table->field[ET_FIELD_EXECUTE_AT]->get_date(&time,
+ TIME_NO_ZERO_DATE))
+ DBUG_RETURN(TRUE);
+ execute_at= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+
+ /*
+ We load the interval type from disk as string and then map it to
+ an integer. This decouples the values of enum interval_type
+ and values actually stored on disk. Therefore the type can be
+ reordered without risking incompatibilities of data between versions.
+ */
+ if (!table->field[ET_FIELD_TRANSIENT_INTERVAL]->is_null())
+ {
+ int i;
+ char buff[MAX_FIELD_WIDTH];
+ String str(buff, sizeof(buff), &my_charset_bin);
+ LEX_STRING tmp;
+
+ table->field[ET_FIELD_TRANSIENT_INTERVAL]->val_str(&str);
+ if (!(tmp.length= str.length()))
+ DBUG_RETURN(TRUE);
+
+ tmp.str= str.c_ptr_safe();
+
+ i= find_string_in_array(interval_type_to_name, &tmp, system_charset_info);
+ if (i < 0)
+ DBUG_RETURN(TRUE);
+ interval= (interval_type) i;
+ }
+
+ if (!table->field[ET_FIELD_LAST_EXECUTED]->is_null())
+ {
+ table->field[ET_FIELD_LAST_EXECUTED]->get_date(&time,
+ TIME_NO_ZERO_DATE);
+ last_executed= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+
+ if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS)
+ DBUG_RETURN(TRUE);
+
+ DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", name.str, ptr));
+
+ /* Set event status (ENABLED | SLAVESIDE_DISABLED | DISABLED) */
+ switch (ptr[0])
+ {
+ case 'E' :
+ status = Event_parse_data::ENABLED;
+ break;
+ case 'S' :
+ status = Event_parse_data::SLAVESIDE_DISABLED;
+ break;
+ case 'D' :
+ default:
+ status = Event_parse_data::DISABLED;
+ break;
+ }
+ if ((ptr= get_field(&mem_root, table->field[ET_FIELD_ORIGINATOR])) == NullS)
+ DBUG_RETURN(TRUE);
+ originator = table->field[ET_FIELD_ORIGINATOR]->val_int();
+
+ /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */
+ if ((ptr= get_field(&mem_root,
+ table->field[ET_FIELD_ON_COMPLETION])) == NullS)
+ DBUG_RETURN(TRUE);
+
+ on_completion= (ptr[0]=='D'? Event_parse_data::ON_COMPLETION_DROP:
+ Event_parse_data::ON_COMPLETION_PRESERVE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Load an event's body from a row from mysql.event.
+
+ @details This method is silent on errors and should behave like that.
+ Callers should handle throwing of error messages. The reason is that the
+ class should not know about how to deal with communication.
+
+ @return Operation status
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool
+Event_timed::load_from_row(THD *thd, TABLE *table)
+{
+ char *ptr;
+ size_t len;
+
+ DBUG_ENTER("Event_timed::load_from_row");
+
+ if (Event_queue_element::load_from_row(thd, table))
+ DBUG_RETURN(TRUE);
+
+ if (load_string_fields(table->field,
+ ET_FIELD_BODY, &body,
+ ET_FIELD_BODY_UTF8, &body_utf8,
+ ET_FIELD_COUNT))
+ DBUG_RETURN(TRUE);
+
+ if (Event_creation_ctx::load_from_db(thd, &mem_root, dbname.str, name.str,
+ table, &creation_ctx))
+ {
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_EVENT_INVALID_CREATION_CTX,
+ ER(ER_EVENT_INVALID_CREATION_CTX),
+ (const char *) dbname.str,
+ (const char *) name.str);
+ }
+
+ ptr= strchr(definer.str, '@');
+
+ if (! ptr)
+ ptr= definer.str;
+
+ len= ptr - definer.str;
+ definer_user.str= strmake_root(&mem_root, definer.str, len);
+ definer_user.length= len;
+ len= definer.length - len - 1;
+ /* 1:because of @ */
+ definer_host.str= strmake_root(&mem_root, ptr + 1, len);
+ definer_host.length= len;
+
+ created= table->field[ET_FIELD_CREATED]->val_int();
+ modified= table->field[ET_FIELD_MODIFIED]->val_int();
+
+ comment.str= get_field(&mem_root, table->field[ET_FIELD_COMMENT]);
+ if (comment.str != NullS)
+ comment.length= strlen(comment.str);
+ else
+ comment.length= 0;
+
+ sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int();
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ add_interval() adds a specified interval to time 'ltime' in time
+ zone 'time_zone', and returns the result converted to the number of
+ seconds since epoch (aka Unix time; in UTC time zone). Zero result
+ means an error.
+*/
+static
+my_time_t
+add_interval(MYSQL_TIME *ltime, const Time_zone *time_zone,
+ interval_type scale, INTERVAL interval)
+{
+ if (date_add_interval(ltime, scale, interval))
+ return 0;
+
+ uint not_used;
+ return time_zone->TIME_to_gmt_sec(ltime, &not_used);
+}
+
+
+/*
+ Computes the sum of a timestamp plus interval.
+
+ SYNOPSIS
+ get_next_time()
+ time_zone event time zone
+ next the sum
+ start add interval_value to this time
+ time_now current time
+ i_value quantity of time type interval to add
+ i_type type of interval to add (SECOND, MINUTE, HOUR, WEEK ...)
+
+ RETURN VALUE
+ 0 OK
+ 1 Error
+
+ NOTES
+ 1) If the interval is conversible to SECOND, like MINUTE, HOUR, DAY, WEEK.
+ Then we use TIMEDIFF()'s implementation as underlying and number of
+ seconds as resolution for computation.
+ 2) In all other cases - MONTH, QUARTER, YEAR we use MONTH as resolution
+ and PERIOD_DIFF()'s implementation
+*/
+
+static
+bool get_next_time(const Time_zone *time_zone, my_time_t *next,
+ my_time_t start, my_time_t time_now,
+ int i_value, interval_type i_type)
+{
+ DBUG_ENTER("get_next_time");
+ DBUG_PRINT("enter", ("start: %lu now: %lu", (long) start, (long) time_now));
+
+ DBUG_ASSERT(start <= time_now);
+
+ longlong months=0, seconds=0;
+
+ switch (i_type) {
+ case INTERVAL_YEAR:
+ months= i_value*12;
+ break;
+ case INTERVAL_QUARTER:
+ /* Has already been converted to months */
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ months= i_value;
+ break;
+ case INTERVAL_WEEK:
+ /* WEEK has already been converted to days */
+ case INTERVAL_DAY:
+ seconds= i_value*24*3600;
+ break;
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_HOUR:
+ seconds= i_value*3600;
+ break;
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_MINUTE:
+ seconds= i_value*60;
+ break;
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ seconds= i_value;
+ break;
+ case INTERVAL_DAY_MICROSECOND:
+ case INTERVAL_HOUR_MICROSECOND:
+ case INTERVAL_MINUTE_MICROSECOND:
+ case INTERVAL_SECOND_MICROSECOND:
+ case INTERVAL_MICROSECOND:
+ /*
+ We should return an error here so SHOW EVENTS/ SELECT FROM I_S.EVENTS
+ would give an error then.
+ */
+ DBUG_RETURN(1);
+ case INTERVAL_LAST:
+ DBUG_ASSERT(0);
+ }
+ DBUG_PRINT("info", ("seconds: %ld months: %ld", (long) seconds, (long) months));
+
+ MYSQL_TIME local_start;
+ MYSQL_TIME local_now;
+
+ /* Convert times from UTC to local. */
+ {
+ time_zone->gmt_sec_to_TIME(&local_start, start);
+ time_zone->gmt_sec_to_TIME(&local_now, time_now);
+ }
+
+ INTERVAL interval;
+ bzero(&interval, sizeof(interval));
+ my_time_t next_time= 0;
+
+ if (seconds)
+ {
+ longlong seconds_diff;
+ long microsec_diff;
+ bool negative= calc_time_diff(&local_now, &local_start, 1,
+ &seconds_diff, &microsec_diff);
+ if (!negative)
+ {
+ /*
+ The formula below returns the interval that, when added to
+ local_start, will always give the time in the future.
+ */
+ interval.second= seconds_diff - seconds_diff % seconds + seconds;
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_SECOND, interval);
+ if (next_time == 0)
+ goto done;
+ }
+
+ if (next_time <= time_now)
+ {
+ /*
+ If 'negative' is true above, then 'next_time == 0', and
+ 'next_time <= time_now' is also true. If negative is false,
+ then next_time was set, but perhaps to the value that is less
+ then time_now. See below for elaboration.
+ */
+ DBUG_ASSERT(negative || next_time > 0);
+
+ /*
+ If local_now < local_start, i.e. STARTS time is in the future
+ according to the local time (it always in the past according
+ to UTC---this is a prerequisite of this function), then
+ STARTS is almost always in the past according to the local
+ time too. However, in the time zone that has backward
+ Daylight Saving Time shift, the following may happen: suppose
+ we have a backward DST shift at certain date after 2:59:59,
+ i.e. local time goes 1:59:59, 2:00:00, ... , 2:59:59, (shift
+ here) 2:00:00 (again), ... , 2:59:59 (again), 3:00:00, ... .
+ Now suppose the time has passed the first 2:59:59, has been
+ shifted backward, and now is (the second) 2:20:00. The user
+ does CREATE EVENT with STARTS 'current-date 2:40:00'. Local
+ time 2:40:00 from create statement is treated by time
+ functions as the first such time, so according to UTC it comes
+ before the second 2:20:00. But according to local time it is
+ obviously in the future, so we end up in this branch.
+
+ Since we are in the second pass through 2:00:00--2:59:59, and
+ any local time form this interval is treated by system
+ functions as the time from the first pass, we have to find the
+ time for the next execution that is past the DST-affected
+ interval (past the second 2:59:59 for our example,
+ i.e. starting from 3:00:00). We do this in the loop until the
+ local time is mapped onto future UTC time. 'start' time is in
+ the past, so we may use 'do { } while' here, and add the first
+ interval right away.
+
+ Alternatively, it could be that local_now >= local_start. Now
+ for the example above imagine we do CREATE EVENT with STARTS
+ 'current-date 2:10:00'. Local start 2:10 is in the past (now
+ is local 2:20), so we add an interval, and get next execution
+ time, say, 2:40. It is in the future according to local time,
+ but, again, since we are in the second pass through
+ 2:00:00--2:59:59, 2:40 will be converted into UTC time in the
+ past. So we will end up in this branch again, and may add
+ intervals in a 'do { } while' loop.
+
+ Note that for any given event we may end up here only if event
+ next execution time will map to the time interval that is
+ passed twice, and only if the server was started during the
+ second pass, or the event is being created during the second
+ pass. After that, we never will get here (unless we again
+ start the server during the second pass). In other words,
+ such a condition is extremely rare.
+ */
+ interval.second= seconds;
+ do
+ {
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_SECOND, interval);
+ if (next_time == 0)
+ goto done;
+ }
+ while (next_time <= time_now);
+ }
+ }
+ else
+ {
+ long diff_months= ((long) local_now.year - (long) local_start.year)*12 +
+ ((long) local_now.month - (long) local_start.month);
+
+ /*
+ Unlike for seconds above, the formula below returns the interval
+ that, when added to the local_start, will give the time in the
+ past, or somewhere in the current month. We are interested in
+ the latter case, to see if this time has already passed, or is
+ yet to come this month.
+
+ Note that the time is guaranteed to be in the past unless
+ (diff_months % months == 0), but no good optimization is
+ possible here, because (diff_months % months == 0) is what will
+ happen most of the time, as get_next_time() will be called right
+ after the execution of the event. We could pass last_executed
+ time to this function, and see if the execution has already
+ happened this month, but for that we will have to convert
+ last_executed from seconds since epoch to local broken-down
+ time, and this will greatly reduce the effect of the
+ optimization. So instead we keep the code simple and clean.
+ */
+ interval.month= (ulong) (diff_months - diff_months % months);
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_MONTH, interval);
+ if (next_time == 0)
+ goto done;
+
+ if (next_time <= time_now)
+ {
+ interval.month= (ulong) months;
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_MONTH, interval);
+ if (next_time == 0)
+ goto done;
+ }
+ }
+
+ DBUG_ASSERT(time_now < next_time);
+
+ *next= next_time;
+
+done:
+ DBUG_PRINT("info", ("next_time: %ld", (long) next_time));
+ DBUG_RETURN(next_time == 0);
+}
+
+
+/*
+ Computes next execution time.
+
+ SYNOPSIS
+ Event_queue_element::compute_next_execution_time()
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error
+
+ NOTES
+ The time is set in execute_at, if no more executions the latter is
+ set to 0.
+*/
+
+bool
+Event_queue_element::compute_next_execution_time()
+{
+ my_time_t time_now;
+ DBUG_ENTER("Event_queue_element::compute_next_execution_time");
+ DBUG_PRINT("enter", ("starts: %lu ends: %lu last_executed: %lu this: 0x%lx",
+ (long) starts, (long) ends, (long) last_executed,
+ (long) this));
+
+ if (status != Event_parse_data::ENABLED)
+ {
+ DBUG_PRINT("compute_next_execution_time",
+ ("Event %s is DISABLED", name.str));
+ goto ret;
+ }
+ /* If one-time, no need to do computation */
+ if (!expression)
+ {
+ /* Let's check whether it was executed */
+ if (last_executed)
+ {
+ DBUG_PRINT("info",("One-time event %s.%s of was already executed",
+ dbname.str, name.str));
+ dropped= (on_completion == Event_parse_data::ON_COMPLETION_DROP);
+ DBUG_PRINT("info",("One-time event will be dropped: %d.", dropped));
+
+ status= Event_parse_data::DISABLED;
+ }
+ goto ret;
+ }
+
+ time_now= current_thd->query_start();
+
+ DBUG_PRINT("info",("NOW: [%lu]", (ulong) time_now));
+
+ /* if time_now is after ends don't execute anymore */
+ if (!ends_null && ends < time_now)
+ {
+ DBUG_PRINT("info", ("NOW after ENDS, don't execute anymore"));
+ /* time_now is after ends. don't execute anymore */
+ execute_at= 0;
+ execute_at_null= TRUE;
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ dropped= TRUE;
+ DBUG_PRINT("info", ("Dropped: %d", dropped));
+ status= Event_parse_data::DISABLED;
+
+ goto ret;
+ }
+
+ /*
+ Here time_now is before or equals ends if the latter is set.
+ Let's check whether time_now is before starts.
+ If so schedule for starts.
+ */
+ if (!starts_null && time_now <= starts)
+ {
+ if (time_now == starts && starts == last_executed)
+ {
+ /*
+ do nothing or we will schedule for second time execution at starts.
+ */
+ }
+ else
+ {
+ DBUG_PRINT("info", ("STARTS is future, NOW <= STARTS,sched for STARTS"));
+ /*
+ starts is in the future
+ time_now before starts. Scheduling for starts
+ */
+ execute_at= starts;
+ execute_at_null= FALSE;
+ goto ret;
+ }
+ }
+
+ if (!starts_null && !ends_null)
+ {
+ /*
+ Both starts and m_ends are set and time_now is between them (incl.)
+ If last_executed is set then increase with m_expression. The new MYSQL_TIME is
+ after m_ends set execute_at to 0. And check for on_completion
+ If not set then schedule for now.
+ */
+ DBUG_PRINT("info", ("Both STARTS & ENDS are set"));
+ if (!last_executed)
+ {
+ DBUG_PRINT("info", ("Not executed so far."));
+ }
+
+ {
+ my_time_t next_exec;
+
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+
+ /* There was previous execution */
+ if (ends < next_exec)
+ {
+ DBUG_PRINT("info", ("Next execution of %s after ENDS. Stop executing.",
+ name.str));
+ /* Next execution after ends. No more executions */
+ execute_at= 0;
+ execute_at_null= TRUE;
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ dropped= TRUE;
+ status= Event_parse_data::DISABLED;
+ }
+ else
+ {
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
+ execute_at= next_exec;
+ execute_at_null= FALSE;
+ }
+ }
+ goto ret;
+ }
+ else if (starts_null && ends_null)
+ {
+ /* starts is always set, so this is a dead branch !! */
+ DBUG_PRINT("info", ("Neither STARTS nor ENDS are set"));
+ /*
+ Both starts and m_ends are not set, so we schedule for the next
+ based on last_executed.
+ */
+ if (last_executed)
+ {
+ my_time_t next_exec;
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+ execute_at= next_exec;
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
+ }
+ else
+ {
+ /* last_executed not set. Schedule the event for now */
+ DBUG_PRINT("info", ("Execute NOW"));
+ execute_at= time_now;
+ }
+ execute_at_null= FALSE;
+ }
+ else
+ {
+ /* either starts or m_ends is set */
+ if (!starts_null)
+ {
+ DBUG_PRINT("info", ("STARTS is set"));
+ /*
+ - starts is set.
+ - starts is not in the future according to check made before
+ Hence schedule for starts + m_expression in case last_executed
+ is not set, otherwise to last_executed + m_expression
+ */
+ if (!last_executed)
+ {
+ DBUG_PRINT("info", ("Not executed so far."));
+ }
+
+ {
+ my_time_t next_exec;
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+ execute_at= next_exec;
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
+ }
+ execute_at_null= FALSE;
+ }
+ else
+ {
+ /* this is a dead branch, because starts is always set !!! */
+ DBUG_PRINT("info", ("STARTS is not set. ENDS is set"));
+ /*
+ - m_ends is set
+ - m_ends is after time_now or is equal
+ Hence check for m_last_execute and increment with m_expression.
+ If last_executed is not set then schedule for now
+ */
+
+ if (!last_executed)
+ execute_at= time_now;
+ else
+ {
+ my_time_t next_exec;
+
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+
+ if (ends < next_exec)
+ {
+ DBUG_PRINT("info", ("Next execution after ENDS. Stop executing."));
+ execute_at= 0;
+ execute_at_null= TRUE;
+ status= Event_parse_data::DISABLED;
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ dropped= TRUE;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Next[%lu]", (ulong) next_exec));
+ execute_at= next_exec;
+ execute_at_null= FALSE;
+ }
+ }
+ }
+ goto ret;
+ }
+ret:
+ DBUG_PRINT("info", ("ret: 0 execute_at: %lu", (long) execute_at));
+ DBUG_RETURN(FALSE);
+err:
+ DBUG_PRINT("info", ("ret=1"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Set the internal last_executed MYSQL_TIME struct to now. NOW is the
+ time according to thd->query_start(), so the THD's clock.
+
+ SYNOPSIS
+ Event_queue_element::mark_last_executed()
+ thd thread context
+*/
+
+void
+Event_queue_element::mark_last_executed(THD *thd)
+{
+ last_executed= thd->query_start();
+
+ execution_count++;
+}
+
+
+static
+void
+append_datetime(String *buf, Time_zone *time_zone, my_time_t secs,
+ const char *name, uint len)
+{
+ char dtime_buff[20*2+32];/* +32 to make my_snprintf_{8bit|ucs2} happy */
+ buf->append(STRING_WITH_LEN(" "));
+ buf->append(name, len);
+ buf->append(STRING_WITH_LEN(" '"));
+ /*
+ Pass the buffer and the second param tells fills the buffer and
+ returns the number of chars to copy.
+ */
+ MYSQL_TIME time;
+ time_zone->gmt_sec_to_TIME(&time, secs);
+ buf->append(dtime_buff, my_datetime_to_str(&time, dtime_buff, 0));
+ buf->append(STRING_WITH_LEN("'"));
+}
+
+
+/*
+ Get SHOW CREATE EVENT as string
+
+ SYNOPSIS
+ Event_timed::get_create_event(THD *thd, String *buf)
+ thd Thread
+ buf String*, should be already allocated. CREATE EVENT goes inside.
+
+ RETURN VALUE
+ 0 OK
+ EVEX_MICROSECOND_UNSUP Error (for now if mysql.event has been
+ tampered and MICROSECONDS interval or
+ derivative has been put there.
+*/
+
+int
+Event_timed::get_create_event(THD *thd, String *buf)
+{
+ char tmp_buf[2 * STRING_BUFFER_USUAL_SIZE];
+ String expr_buf(tmp_buf, sizeof(tmp_buf), system_charset_info);
+ expr_buf.length(0);
+
+ DBUG_ENTER("get_create_event");
+ DBUG_PRINT("ret_info",("body_len=[%d]body=[%s]",
+ (int) body.length, body.str));
+
+ if (expression && Events::reconstruct_interval_expression(&expr_buf, interval,
+ expression))
+ DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
+
+ buf->append(STRING_WITH_LEN("CREATE "));
+ append_definer(thd, buf, &definer_user, &definer_host);
+ buf->append(STRING_WITH_LEN("EVENT "));
+ append_identifier(thd, buf, name.str, name.length);
+
+ if (expression)
+ {
+ buf->append(STRING_WITH_LEN(" ON SCHEDULE EVERY "));
+ buf->append(expr_buf);
+ buf->append(' ');
+ LEX_STRING *ival= &interval_type_to_name[interval];
+ buf->append(ival->str, ival->length);
+
+ if (!starts_null)
+ append_datetime(buf, time_zone, starts, STRING_WITH_LEN("STARTS"));
+
+ if (!ends_null)
+ append_datetime(buf, time_zone, ends, STRING_WITH_LEN("ENDS"));
+ }
+ else
+ {
+ append_datetime(buf, time_zone, execute_at,
+ STRING_WITH_LEN("ON SCHEDULE AT"));
+ }
+
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ buf->append(STRING_WITH_LEN(" ON COMPLETION NOT PRESERVE "));
+ else
+ buf->append(STRING_WITH_LEN(" ON COMPLETION PRESERVE "));
+
+ if (status == Event_parse_data::ENABLED)
+ buf->append(STRING_WITH_LEN("ENABLE"));
+ else if (status == Event_parse_data::SLAVESIDE_DISABLED)
+ buf->append(STRING_WITH_LEN("DISABLE ON SLAVE"));
+ else
+ buf->append(STRING_WITH_LEN("DISABLE"));
+
+ if (comment.length)
+ {
+ buf->append(STRING_WITH_LEN(" COMMENT "));
+ append_unescaped(buf, comment.str, comment.length);
+ }
+ buf->append(STRING_WITH_LEN(" DO "));
+ buf->append(body.str, body.length);
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Get an artificial stored procedure to parse as an event definition.
+*/
+
+bool
+Event_job_data::construct_sp_sql(THD *thd, String *sp_sql)
+{
+ LEX_STRING buffer;
+ const uint STATIC_SQL_LENGTH= 44;
+
+ DBUG_ENTER("Event_job_data::construct_sp_sql");
+
+ /*
+ Allocate a large enough buffer on the thread execution memory
+ root to avoid multiple [re]allocations on system heap
+ */
+ buffer.length= STATIC_SQL_LENGTH + name.length + body.length;
+ if (! (buffer.str= (char*) thd->alloc(buffer.length)))
+ DBUG_RETURN(TRUE);
+
+ sp_sql->set(buffer.str, buffer.length, system_charset_info);
+ sp_sql->length(0);
+
+
+ sp_sql->append(C_STRING_WITH_LEN("CREATE "));
+ sp_sql->append(C_STRING_WITH_LEN("PROCEDURE "));
+ /*
+ Let's use the same name as the event name to perhaps produce a
+ better error message in case it is a part of some parse error.
+ We're using append_identifier here to successfully parse
+ events with reserved names.
+ */
+ append_identifier(thd, sp_sql, name.str, name.length);
+
+ /*
+ The default SQL security of a stored procedure is DEFINER. We
+ have already activated the security context of the event, so
+ let's execute the procedure with the invoker rights to save on
+ resets of security contexts.
+ */
+ sp_sql->append(C_STRING_WITH_LEN("() SQL SECURITY INVOKER "));
+
+ sp_sql->append(body.str, body.length);
+
+ DBUG_RETURN(thd->is_fatal_error);
+}
+
+
+/**
+ Get DROP EVENT statement to binlog the drop of ON COMPLETION NOT
+ PRESERVE event.
+*/
+
+bool
+Event_job_data::construct_drop_event_sql(THD *thd, String *sp_sql)
+{
+ LEX_STRING buffer;
+ const uint STATIC_SQL_LENGTH= 14;
+
+ DBUG_ENTER("Event_job_data::construct_drop_event_sql");
+
+ buffer.length= STATIC_SQL_LENGTH + name.length*2 + dbname.length*2;
+ if (! (buffer.str= (char*) thd->alloc(buffer.length)))
+ DBUG_RETURN(TRUE);
+
+ sp_sql->set(buffer.str, buffer.length, system_charset_info);
+ sp_sql->length(0);
+
+ sp_sql->append(C_STRING_WITH_LEN("DROP EVENT "));
+ append_identifier(thd, sp_sql, dbname.str, dbname.length);
+ sp_sql->append('.');
+ append_identifier(thd, sp_sql, name.str, name.length);
+
+ DBUG_RETURN(thd->is_fatal_error);
+}
+
+/**
+ Compiles and executes the event (the underlying sp_head object)
+
+ @retval TRUE error (reported to the error log)
+ @retval FALSE success
+*/
+
+bool
+Event_job_data::execute(THD *thd, bool drop)
+{
+ String sp_sql;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context event_sctx, *save_sctx= NULL;
+#endif
+ List<Item> empty_item_list;
+ bool ret= TRUE;
+
+ DBUG_ENTER("Event_job_data::execute");
+
+ mysql_reset_thd_for_next_command(thd);
+
+ /*
+ MySQL parser currently assumes that current database is either
+ present in THD or all names in all statements are fully specified.
+ And yet not fully specified names inside stored programs must be
+ be supported, even if the current database is not set:
+ CREATE PROCEDURE db1.p1() BEGIN CREATE TABLE t1; END//
+ -- in this example t1 should be always created in db1 and the statement
+ must parse even if there is no current database.
+
+ To support this feature and still address the parser limitation,
+ we need to set the current database here.
+ We don't have to call mysql_change_db, since the checks performed
+ in it are unnecessary for the purpose of parsing, and
+ mysql_change_db will be invoked anyway later, to activate the
+ procedure database before it's executed.
+ */
+ thd->set_db(dbname.str, dbname.length);
+
+ lex_start(thd);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (event_sctx.change_security_context(thd,
+ &definer_user, &definer_host,
+ &dbname, &save_sctx))
+ {
+ sql_print_error("Event Scheduler: "
+ "[%s].[%s.%s] execution failed, "
+ "failed to authenticate the user.",
+ definer.str, dbname.str, name.str);
+ goto end;
+ }
+#endif
+
+ if (check_access(thd, EVENT_ACL, dbname.str, NULL, NULL, 0, 0))
+ {
+ /*
+ This aspect of behavior is defined in the worklog,
+ and this is how triggers work too: if TRIGGER
+ privilege is revoked from trigger definer,
+ triggers are not executed.
+ */
+ sql_print_error("Event Scheduler: "
+ "[%s].[%s.%s] execution failed, "
+ "user no longer has EVENT privilege.",
+ definer.str, dbname.str, name.str);
+ goto end;
+ }
+
+ if (construct_sp_sql(thd, &sp_sql))
+ goto end;
+
+ /*
+ Set up global thread attributes to reflect the properties of
+ this Event. We can simply reset these instead of usual
+ backup/restore employed in stored programs since we know that
+ this is a top level statement and the worker thread is
+ allocated exclusively to execute this event.
+ */
+
+ thd->variables.sql_mode= sql_mode;
+ thd->variables.time_zone= time_zone;
+
+ thd->set_query(sp_sql.c_ptr_safe(), sp_sql.length());
+
+ {
+ Parser_state parser_state;
+ if (parser_state.init(thd, thd->query(), thd->query_length()))
+ goto end;
+
+ if (parse_sql(thd, & parser_state, creation_ctx))
+ {
+ sql_print_error("Event Scheduler: "
+ "%serror during compilation of %s.%s",
+ thd->is_fatal_error ? "fatal " : "",
+ (const char *) dbname.str, (const char *) name.str);
+ goto end;
+ }
+ }
+
+ {
+ sp_head *sphead= thd->lex->sphead;
+
+ DBUG_ASSERT(sphead);
+
+ sphead->m_flags|= sp_head::LOG_SLOW_STATEMENTS;
+ sphead->m_flags|= sp_head::LOG_GENERAL_LOG;
+
+ sphead->set_info(0, 0, &thd->lex->sp_chistics, sql_mode);
+ sphead->set_creation_ctx(creation_ctx);
+ sphead->optimize();
+
+ ret= sphead->execute_procedure(thd, &empty_item_list);
+ /*
+ There is no pre-locking and therefore there should be no
+ tables open and locked left after execute_procedure.
+ */
+ }
+
+end:
+ if (drop && !thd->is_fatal_error)
+ {
+ /*
+ We must do it here since here we're under the right authentication
+ ID of the event definer.
+ */
+ sql_print_information("Event Scheduler: Dropping %s.%s",
+ (const char *) dbname.str, (const char *) name.str);
+ /*
+ Construct a query for the binary log, to ensure the event is dropped
+ on the slave
+ */
+ if (construct_drop_event_sql(thd, &sp_sql))
+ ret= 1;
+ else
+ {
+ ulong saved_master_access;
+
+ thd->set_query(sp_sql.c_ptr_safe(), sp_sql.length());
+
+ /*
+ NOTE: even if we run in read-only mode, we should be able to lock
+ the mysql.event table for writing. In order to achieve this, we
+ should call mysql_lock_tables() under the super-user.
+
+ Same goes for transaction access mode.
+ Temporarily reset it to read-write.
+ */
+
+ saved_master_access= thd->security_ctx->master_access;
+ thd->security_ctx->master_access |= SUPER_ACL;
+ bool save_tx_read_only= thd->tx_read_only;
+ thd->tx_read_only= false;
+
+ ret= Events::drop_event(thd, dbname, name, FALSE);
+
+ thd->tx_read_only= save_tx_read_only;
+ thd->security_ctx->master_access= saved_master_access;
+ }
+ }
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (save_sctx)
+ event_sctx.restore_security_context(thd, save_sctx);
+#endif
+ thd->lex->unit.cleanup();
+ thd->end_statement();
+ thd->cleanup_after_query();
+ /* Avoid races with SHOW PROCESSLIST */
+ thd->reset_query();
+
+ DBUG_PRINT("info", ("EXECUTED %s.%s ret: %d", dbname.str, name.str, ret));
+
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Checks whether two events are in the same schema
+
+ SYNOPSIS
+ event_basic_db_equal()
+ db Schema
+ et Compare et->dbname to `db`
+
+ RETURN VALUE
+ TRUE Equal
+ FALSE Not equal
+*/
+
+bool
+event_basic_db_equal(LEX_STRING db, Event_basic *et)
+{
+ return !sortcmp_lex_string(et->dbname, db, system_charset_info);
+}
+
+
+/*
+ Checks whether an event has equal `db` and `name`
+
+ SYNOPSIS
+ event_basic_identifier_equal()
+ db Schema
+ name Name
+ et The event object
+
+ RETURN VALUE
+ TRUE Equal
+ FALSE Not equal
+*/
+
+bool
+event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b)
+{
+ return !sortcmp_lex_string(name, b->name, system_charset_info) &&
+ !sortcmp_lex_string(db, b->dbname, system_charset_info);
+}
+
+/**
+ @} (End of group Event_Scheduler)
+*/
diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h
new file mode 100644
index 00000000000..2483c564dff
--- /dev/null
+++ b/sql/event_data_objects.h
@@ -0,0 +1,195 @@
+#ifndef _EVENT_DATA_OBJECTS_H_
+#define _EVENT_DATA_OBJECTS_H_
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+
+ @file event_data_objects.h
+*/
+
+#include "event_parse_data.h"
+#include "thr_lock.h" /* thr_lock_type */
+
+class Field;
+class THD;
+class Time_zone;
+struct TABLE;
+
+class Event_queue_element_for_exec
+{
+public:
+ Event_queue_element_for_exec(){};
+ ~Event_queue_element_for_exec();
+
+ bool
+ init(LEX_STRING dbname, LEX_STRING name);
+
+ LEX_STRING dbname;
+ LEX_STRING name;
+ bool dropped;
+ THD *thd;
+
+private:
+ /* Prevent use of these */
+ Event_queue_element_for_exec(const Event_queue_element_for_exec &);
+ void operator=(Event_queue_element_for_exec &);
+};
+
+
+class Event_basic
+{
+protected:
+ MEM_ROOT mem_root;
+
+public:
+
+ LEX_STRING dbname;
+ LEX_STRING name;
+ LEX_STRING definer;// combination of user and host
+
+ Time_zone *time_zone;
+
+ Event_basic();
+ virtual ~Event_basic();
+
+ virtual bool
+ load_from_row(THD *thd, TABLE *table) = 0;
+
+protected:
+ bool
+ load_string_fields(Field **fields, ...);
+
+ bool
+ load_time_zone(THD *thd, const LEX_STRING tz_name);
+};
+
+
+
+class Event_queue_element : public Event_basic
+{
+public:
+ int on_completion;
+ int status;
+ longlong originator;
+
+ my_time_t last_executed;
+ my_time_t execute_at;
+ my_time_t starts;
+ my_time_t ends;
+ bool starts_null;
+ bool ends_null;
+ bool execute_at_null;
+
+ longlong expression;
+ interval_type interval;
+
+ bool dropped;
+
+ uint execution_count;
+
+ Event_queue_element();
+ virtual ~Event_queue_element();
+
+ virtual bool
+ load_from_row(THD *thd, TABLE *table);
+
+ bool
+ compute_next_execution_time();
+
+ void
+ mark_last_executed(THD *thd);
+};
+
+
+class Event_timed : public Event_queue_element
+{
+ Event_timed(const Event_timed &); /* Prevent use of these */
+ void operator=(Event_timed &);
+
+public:
+ LEX_STRING body;
+
+ LEX_STRING definer_user;
+ LEX_STRING definer_host;
+
+ LEX_STRING comment;
+
+ ulonglong created;
+ ulonglong modified;
+
+ ulong sql_mode;
+
+ class Stored_program_creation_ctx *creation_ctx;
+ LEX_STRING body_utf8;
+
+ Event_timed();
+ virtual ~Event_timed();
+
+ void
+ init();
+
+ virtual bool
+ load_from_row(THD *thd, TABLE *table);
+
+ int
+ get_create_event(THD *thd, String *buf);
+};
+
+
+class Event_job_data : public Event_basic
+{
+public:
+ LEX_STRING body;
+ LEX_STRING definer_user;
+ LEX_STRING definer_host;
+
+ ulong sql_mode;
+
+ class Stored_program_creation_ctx *creation_ctx;
+
+ Event_job_data();
+
+ virtual bool
+ load_from_row(THD *thd, TABLE *table);
+
+ bool
+ execute(THD *thd, bool drop);
+private:
+ bool
+ construct_sp_sql(THD *thd, String *sp_sql);
+ bool
+ construct_drop_event_sql(THD *thd, String *sp_sql);
+
+ Event_job_data(const Event_job_data &); /* Prevent use of these */
+ void operator=(Event_job_data &);
+};
+
+
+/* Compares only the schema part of the identifier */
+bool
+event_basic_db_equal(LEX_STRING db, Event_basic *et);
+
+/* Compares the whole identifier*/
+bool
+event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b);
+
+/**
+ @} (End of group Event_Scheduler)
+*/
+
+#endif /* _EVENT_DATA_OBJECTS_H_ */
diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc
new file mode 100644
index 00000000000..30dffc30edd
--- /dev/null
+++ b/sql/event_db_repository.cc
@@ -0,0 +1,1246 @@
+/*
+ Copyright (c) 2006, 2011, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_base.h" // close_thread_tables
+#include "event_db_repository.h"
+#include "key.h" // key_copy
+#include "sql_db.h" // get_default_db_collation
+#include "sql_time.h" // interval_type_to_name
+#include "tztime.h" // struct Time_zone
+#include "sql_acl.h" // SUPER_ACL, MYSQL_DB_FIELD_COUNT, mysql_db_table_fields
+#include "records.h" // init_read_record, end_read_record
+#include "sp_head.h"
+#include "event_data_objects.h"
+#include "events.h"
+#include "sql_show.h"
+#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+*/
+
+static
+const TABLE_FIELD_TYPE event_table_fields[ET_FIELD_COUNT] =
+{
+ {
+ { C_STRING_WITH_LEN("db") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("name") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("body") },
+ { C_STRING_WITH_LEN("longblob") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("definer") },
+ { C_STRING_WITH_LEN("char(") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("execute_at") },
+ { C_STRING_WITH_LEN("datetime") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("interval_value") },
+ { C_STRING_WITH_LEN("int(11)") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("interval_field") },
+ { C_STRING_WITH_LEN("enum('YEAR','QUARTER','MONTH','DAY',"
+ "'HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR',"
+ "'DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND',"
+ "'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND',"
+ "'SECOND_MICROSECOND')") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("created") },
+ { C_STRING_WITH_LEN("timestamp") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("modified") },
+ { C_STRING_WITH_LEN("timestamp") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("last_executed") },
+ { C_STRING_WITH_LEN("datetime") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("starts") },
+ { C_STRING_WITH_LEN("datetime") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("ends") },
+ { C_STRING_WITH_LEN("datetime") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("status") },
+ { C_STRING_WITH_LEN("enum('ENABLED','DISABLED','SLAVESIDE_DISABLED')") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("on_completion") },
+ { C_STRING_WITH_LEN("enum('DROP','PRESERVE')") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("sql_mode") },
+ { C_STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES',"
+ "'IGNORE_SPACE','IGNORE_BAD_TABLE_OPTIONS','ONLY_FULL_GROUP_BY',"
+ "'NO_UNSIGNED_SUBTRACTION',"
+ "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB',"
+ "'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40',"
+ "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES',"
+ "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES',"
+ "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER',"
+ "'HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH')") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("comment") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("originator") },
+ { C_STRING_WITH_LEN("int(10)") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("time_zone") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("latin1") }
+ },
+ {
+ { C_STRING_WITH_LEN("character_set_client") },
+ { C_STRING_WITH_LEN("char(32)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("collation_connection") },
+ { C_STRING_WITH_LEN("char(32)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("db_collation") },
+ { C_STRING_WITH_LEN("char(32)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("body_utf8") },
+ { C_STRING_WITH_LEN("longblob") },
+ { NULL, 0 }
+ }
+};
+
+static const TABLE_FIELD_DEF
+event_table_def= {ET_FIELD_COUNT, event_table_fields, 0, (uint*) 0};
+
+class Event_db_intact : public Table_check_intact
+{
+protected:
+ void report_error(uint, const char *fmt, ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+ error_log_print(ERROR_LEVEL, fmt, args);
+ va_end(args);
+ }
+};
+
+/** In case of an error, a message is printed to the error log. */
+static Event_db_intact table_intact;
+
+
+/**
+ Puts some data common to CREATE and ALTER EVENT into a row.
+
+ Used both when an event is created and when it is altered.
+
+ @param thd THD
+ @param table The row to fill out
+ @param et Event's data
+ @param sp Event stored routine
+ @param is_update CREATE EVENT or ALTER EVENT
+
+ @retval FALSE success
+ @retval TRUE error
+*/
+
+static bool
+mysql_event_fill_row(THD *thd,
+ TABLE *table,
+ Event_parse_data *et,
+ sp_head *sp,
+ ulonglong sql_mode,
+ my_bool is_update)
+{
+ CHARSET_INFO *scs= system_charset_info;
+ enum enum_events_table_field f_num;
+ Field **fields= table->field;
+ int rs= FALSE;
+
+ DBUG_ENTER("mysql_event_fill_row");
+
+ DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str));
+ DBUG_PRINT("info", ("name =[%s]", et->name.str));
+
+ DBUG_ASSERT(et->on_completion != Event_parse_data::ON_COMPLETION_DEFAULT);
+
+ if (table->s->fields < ET_FIELD_COUNT)
+ {
+ /*
+ Safety: this can only happen if someone started the server
+ and then altered mysql.event.
+ */
+ my_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2, MYF(0),
+ table->s->db.str, table->alias.c_ptr(),
+ (int) ET_FIELD_COUNT, table->s->fields);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (fields[f_num= ET_FIELD_DEFINER]->
+ store(et->definer.str, et->definer.length, scs))
+ goto err_truncate;
+
+ if (fields[f_num= ET_FIELD_DB]->store(et->dbname.str, et->dbname.length, scs))
+ goto err_truncate;
+
+ if (fields[f_num= ET_FIELD_NAME]->store(et->name.str, et->name.length, scs))
+ goto err_truncate;
+
+ /* ON_COMPLETION field is NOT NULL thus not calling set_notnull()*/
+ rs|= fields[ET_FIELD_ON_COMPLETION]->store((longlong)et->on_completion, TRUE);
+
+ /*
+ Set STATUS value unconditionally in case of CREATE EVENT.
+ For ALTER EVENT set it only if value of this field was changed.
+ Since STATUS field is NOT NULL call to set_notnull() is not needed.
+ */
+ if (!is_update || et->status_changed)
+ rs|= fields[ET_FIELD_STATUS]->store((longlong)et->status, TRUE);
+ rs|= fields[ET_FIELD_ORIGINATOR]->store((longlong)et->originator, TRUE);
+
+ if (!is_update)
+ rs|= fields[ET_FIELD_CREATED]->set_time();
+
+ /*
+ Change the SQL_MODE only if body was present in an ALTER EVENT and of course
+ always during CREATE EVENT.
+ */
+ if (et->body_changed)
+ {
+ DBUG_ASSERT(sp->m_body.str);
+
+ rs|= fields[ET_FIELD_SQL_MODE]->store((longlong)sql_mode, TRUE);
+
+ if (fields[f_num= ET_FIELD_BODY]->store(sp->m_body.str,
+ sp->m_body.length,
+ scs))
+ {
+ goto err_truncate;
+ }
+ }
+
+ if (et->expression)
+ {
+ const String *tz_name= thd->variables.time_zone->get_name();
+ if (!is_update || !et->starts_null)
+ {
+ fields[ET_FIELD_TIME_ZONE]->set_notnull();
+ rs|= fields[ET_FIELD_TIME_ZONE]->store(tz_name->ptr(), tz_name->length(),
+ tz_name->charset());
+ }
+
+ fields[ET_FIELD_INTERVAL_EXPR]->set_notnull();
+ rs|= fields[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, TRUE);
+
+ fields[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull();
+
+ rs|= fields[ET_FIELD_TRANSIENT_INTERVAL]->
+ store(interval_type_to_name[et->interval].str,
+ interval_type_to_name[et->interval].length,
+ scs);
+
+ fields[ET_FIELD_EXECUTE_AT]->set_null();
+
+ if (!et->starts_null)
+ {
+ MYSQL_TIME time;
+ my_tz_OFFSET0->gmt_sec_to_TIME(&time, et->starts);
+
+ fields[ET_FIELD_STARTS]->set_notnull();
+ fields[ET_FIELD_STARTS]->store_time(&time);
+ }
+
+ if (!et->ends_null)
+ {
+ MYSQL_TIME time;
+ my_tz_OFFSET0->gmt_sec_to_TIME(&time, et->ends);
+
+ fields[ET_FIELD_ENDS]->set_notnull();
+ fields[ET_FIELD_ENDS]->store_time(&time);
+ }
+ }
+ else if (et->execute_at)
+ {
+ const String *tz_name= thd->variables.time_zone->get_name();
+ fields[ET_FIELD_TIME_ZONE]->set_notnull();
+ rs|= fields[ET_FIELD_TIME_ZONE]->store(tz_name->ptr(), tz_name->length(),
+ tz_name->charset());
+
+ fields[ET_FIELD_INTERVAL_EXPR]->set_null();
+ fields[ET_FIELD_TRANSIENT_INTERVAL]->set_null();
+ fields[ET_FIELD_STARTS]->set_null();
+ fields[ET_FIELD_ENDS]->set_null();
+
+ MYSQL_TIME time;
+ my_tz_OFFSET0->gmt_sec_to_TIME(&time, et->execute_at);
+
+ fields[ET_FIELD_EXECUTE_AT]->set_notnull();
+ fields[ET_FIELD_EXECUTE_AT]->store_time(&time);
+ }
+ else
+ {
+ DBUG_ASSERT(is_update);
+ /*
+ it is normal to be here when the action is update
+ this is an error if the action is create. something is borked
+ */
+ }
+
+ rs|= fields[ET_FIELD_MODIFIED]->set_time();
+
+ if (et->comment.str)
+ {
+ if (fields[f_num= ET_FIELD_COMMENT]->
+ store(et->comment.str, et->comment.length, scs))
+ goto err_truncate;
+ }
+
+ fields[ET_FIELD_CHARACTER_SET_CLIENT]->set_notnull();
+ rs|= fields[ET_FIELD_CHARACTER_SET_CLIENT]->store(
+ thd->variables.character_set_client->csname,
+ strlen(thd->variables.character_set_client->csname),
+ system_charset_info);
+
+ fields[ET_FIELD_COLLATION_CONNECTION]->set_notnull();
+ rs|= fields[ET_FIELD_COLLATION_CONNECTION]->store(
+ thd->variables.collation_connection->name,
+ strlen(thd->variables.collation_connection->name),
+ system_charset_info);
+
+ {
+ CHARSET_INFO *db_cl= get_default_db_collation(thd, et->dbname.str);
+
+ fields[ET_FIELD_DB_COLLATION]->set_notnull();
+ rs|= fields[ET_FIELD_DB_COLLATION]->store(db_cl->name,
+ strlen(db_cl->name),
+ system_charset_info);
+ }
+
+ if (et->body_changed)
+ {
+ fields[ET_FIELD_BODY_UTF8]->set_notnull();
+ rs|= fields[ET_FIELD_BODY_UTF8]->store(sp->m_body_utf8.str,
+ sp->m_body_utf8.length,
+ system_charset_info);
+ }
+
+ if (rs)
+ {
+ my_error(ER_EVENT_STORE_FAILED, MYF(0), fields[f_num]->field_name, rs);
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+
+err_truncate:
+ my_error(ER_EVENT_DATA_TOO_LONG, MYF(0), fields[f_num]->field_name);
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Performs an index scan of event_table (mysql.event) and fills schema_table.
+
+ SYNOPSIS
+ Event_db_repository::index_read_for_db_for_i_s()
+ thd Thread
+ schema_table The I_S.EVENTS table
+ event_table The event table to use for loading (mysql.event)
+ db For which schema to do an index scan.
+
+ RETURN VALUE
+ 0 OK
+ 1 Error
+*/
+
+bool
+Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table,
+ TABLE *event_table,
+ const char *db)
+{
+ CHARSET_INFO *scs= system_charset_info;
+ KEY *key_info;
+ uint key_len;
+ uchar *key_buf= NULL;
+ LINT_INIT(key_buf);
+
+ DBUG_ENTER("Event_db_repository::index_read_for_db_for_i_s");
+
+ DBUG_PRINT("info", ("Using prefix scanning on PK"));
+
+ int ret= event_table->file->ha_index_init(0, 1);
+ if (ret)
+ {
+ event_table->file->print_error(ret, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ key_info= event_table->key_info;
+
+ if (key_info->user_defined_key_parts == 0 ||
+ key_info->key_part[0].field != event_table->field[ET_FIELD_DB])
+ {
+ /* Corrupted table: no index or index on a wrong column */
+ my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "event");
+ ret= 1;
+ goto end;
+ }
+
+ event_table->field[ET_FIELD_DB]->store(db, strlen(db), scs);
+ key_len= key_info->key_part[0].store_length;
+
+ if (!(key_buf= (uchar *)alloc_root(thd->mem_root, key_len)))
+ {
+ /* Don't send error, it would be done by sql_alloc_error_handler() */
+ ret= 1;
+ goto end;
+ }
+
+ key_copy(key_buf, event_table->record[0], key_info, key_len);
+ if (!(ret= event_table->file->ha_index_read_map(event_table->record[0],
+ key_buf,
+ (key_part_map)1,
+ HA_READ_KEY_EXACT)))
+ {
+ DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret));
+ do
+ {
+ ret= copy_event_to_schema_table(thd, schema_table, event_table);
+ if (ret == 0)
+ ret= event_table->file->ha_index_next_same(event_table->record[0],
+ key_buf, key_len);
+ } while (ret == 0);
+ }
+ DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
+
+ /* ret is guaranteed to be != 0 */
+ if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND)
+ ret= 0;
+ else
+ event_table->file->print_error(ret, MYF(0));
+
+end:
+ event_table->file->ha_index_end();
+
+ DBUG_RETURN(MY_TEST(ret));
+}
+
+
+/*
+ Performs a table scan of event_table (mysql.event) and fills schema_table.
+
+ SYNOPSIS
+ Events_db_repository::table_scan_all_for_i_s()
+ thd Thread
+ schema_table The I_S.EVENTS in memory table
+ event_table The event table to use for loading.
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error
+*/
+
+bool
+Event_db_repository::table_scan_all_for_i_s(THD *thd, TABLE *schema_table,
+ TABLE *event_table)
+{
+ int ret;
+ READ_RECORD read_record_info;
+ DBUG_ENTER("Event_db_repository::table_scan_all_for_i_s");
+
+ if (init_read_record(&read_record_info, thd, event_table, NULL, 1, 0, FALSE))
+ DBUG_RETURN(TRUE);
+
+ /*
+ rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE,
+ but rr_handle_error returns -1 for that reason. Thus, read_record()
+ returns -1 eventually.
+ */
+ do
+ {
+ ret= read_record_info.read_record(&read_record_info);
+ if (ret == 0)
+ ret= copy_event_to_schema_table(thd, schema_table, event_table);
+ } while (ret == 0);
+
+ DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
+ end_read_record(&read_record_info);
+
+ /* ret is guaranteed to be != 0 */
+ DBUG_RETURN(ret == -1? FALSE:TRUE);
+}
+
+
+/**
+ Fills I_S.EVENTS with data loaded from mysql.event. Also used by
+ SHOW EVENTS
+
+ The reason we reset and backup open tables here is that this
+ function may be called from any query that accesses
+ INFORMATION_SCHEMA - including a query that is issued from
+ a pre-locked statement, one that already has open and locked
+ tables.
+
+ @retval FALSE success
+ @retval TRUE error
+*/
+
+bool
+Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *i_s_table,
+ const char *db)
+{
+ TABLE *schema_table= i_s_table->table;
+ Open_tables_backup open_tables_backup;
+ TABLE_LIST event_table;
+ int ret= 0;
+
+ DBUG_ENTER("Event_db_repository::fill_schema_events");
+ DBUG_PRINT("info",("db=%s", db? db:"(null)"));
+
+ event_table.init_one_table("mysql", 5, "event", 5, "event", TL_READ);
+
+ if (open_system_tables_for_read(thd, &event_table, &open_tables_backup))
+ DBUG_RETURN(TRUE);
+
+ if (table_intact.check(event_table.table, &event_table_def))
+ {
+ close_system_tables(thd, &open_tables_backup);
+ my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ 1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order
+ thus we won't order it. OTOH, SHOW EVENTS will be
+ ordered.
+ 2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db)
+ Reasoning: Events are per schema, therefore a scan over an index
+ will save use from doing a table scan and comparing
+ every single row's `db` with the schema which we show.
+ */
+ if (db)
+ ret= index_read_for_db_for_i_s(thd, schema_table, event_table.table, db);
+ else
+ ret= table_scan_all_for_i_s(thd, schema_table, event_table.table);
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_PRINT("info", ("Return code=%d", ret));
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Open mysql.event table for read.
+
+ It's assumed that the caller knows what they are doing:
+ - whether it was necessary to reset-and-backup the open tables state
+ - whether the requested lock does not lead to a deadlock
+ - whether this open mode would work under LOCK TABLES, or inside a
+ stored function or trigger.
+
+ Note that if the table can't be locked successfully this operation will
+ close it. Therefore it provides guarantee that it either opens and locks
+ table or fails without leaving any tables open.
+
+ @param[in] thd Thread context
+ @param[in] lock_type How to lock the table
+ @param[out] table We will store the open table here
+
+ @retval TRUE open and lock failed - an error message is pushed into the
+ stack
+ @retval FALSE success
+*/
+
+bool
+Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type,
+ TABLE **table)
+{
+ TABLE_LIST tables;
+ DBUG_ENTER("Event_db_repository::open_event_table");
+
+ tables.init_one_table("mysql", 5, "event", 5, "event", lock_type);
+
+ if (open_and_lock_tables(thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ DBUG_RETURN(TRUE);
+
+ *table= tables.table;
+ tables.table->use_all_columns();
+
+ if (table_intact.check(*table, &event_table_def))
+ {
+ close_thread_tables(thd);
+ my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Creates an event record in mysql.event table.
+
+ Creates an event. Relies on mysql_event_fill_row which is shared with
+ ::update_event.
+
+ @pre All semantic checks must be performed outside. This function
+ only creates a record on disk.
+ @pre The thread handle has no open tables.
+
+ @param[in,out] thd THD
+ @param[in] parse_data Parsed event definition
+ @param[in] create_if_not TRUE if IF NOT EXISTS clause was provided
+ to CREATE EVENT statement
+ @param[out] event_already_exists When method is completed successfully
+ set to true if event already exists else
+ set to false
+ @retval FALSE success
+ @retval TRUE error
+*/
+
+bool
+Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data,
+ bool create_if_not,
+ bool *event_already_exists)
+{
+ int ret= 1;
+ TABLE *table= NULL;
+ sp_head *sp= thd->lex->sphead;
+ ulonglong saved_mode= thd->variables.sql_mode;
+ /*
+ Take a savepoint to release only the lock on mysql.event
+ table at the end but keep the global read lock and
+ possible other locks taken by the caller.
+ */
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+
+ DBUG_ENTER("Event_db_repository::create_event");
+
+ DBUG_PRINT("info", ("open mysql.event for update"));
+ DBUG_ASSERT(sp);
+
+ /* Reset sql_mode during data dictionary operations. */
+ thd->variables.sql_mode= 0;
+
+ if (open_event_table(thd, TL_WRITE, &table))
+ goto end;
+
+ DBUG_PRINT("info", ("name: %.*s", (int) parse_data->name.length,
+ parse_data->name.str));
+
+ DBUG_PRINT("info", ("check existance of an event with the same name"));
+ if (!find_named_event(parse_data->dbname, parse_data->name, table))
+ {
+ if (create_if_not)
+ {
+ *event_already_exists= true;
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS),
+ parse_data->name.str);
+ ret= 0;
+ }
+ else
+ my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), parse_data->name.str);
+
+ goto end;
+ } else
+ *event_already_exists= false;
+
+ DBUG_PRINT("info", ("non-existent, go forward"));
+
+ restore_record(table, s->default_values); // Get default values for fields
+
+ if (system_charset_info->cset->
+ numchars(system_charset_info, parse_data->dbname.str,
+ parse_data->dbname.str + parse_data->dbname.length) >
+ table->field[ET_FIELD_DB]->char_length())
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->dbname.str);
+ goto end;
+ }
+
+ if (system_charset_info->cset->
+ numchars(system_charset_info, parse_data->name.str,
+ parse_data->name.str + parse_data->name.length) >
+ table->field[ET_FIELD_NAME]->char_length())
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->name.str);
+ goto end;
+ }
+
+ if (sp->m_body.length > table->field[ET_FIELD_BODY]->field_length)
+ {
+ my_error(ER_TOO_LONG_BODY, MYF(0), parse_data->name.str);
+ goto end;
+ }
+
+ /*
+ mysql_event_fill_row() calls my_error() in case of error so no need to
+ handle it here
+ */
+ if (mysql_event_fill_row(thd, table, parse_data, sp, saved_mode, FALSE))
+ goto end;
+
+ if ((ret= table->file->ha_write_row(table->record[0])))
+ {
+ table->file->print_error(ret, MYF(0));
+ goto end;
+ }
+ ret= 0;
+
+end:
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ thd->variables.sql_mode= saved_mode;
+ DBUG_RETURN(MY_TEST(ret));
+}
+
+
+/**
+ Used to execute ALTER EVENT. Pendant to Events::update_event().
+
+ @param[in,out] thd thread handle
+ @param[in] parse_data parsed event definition
+ @param[in] new_dbname not NULL if ALTER EVENT RENAME
+ points at a new database name
+ @param[in] new_name not NULL if ALTER EVENT RENAME
+ points at a new event name
+
+ @pre All semantic checks are performed outside this function,
+ it only updates the event definition on disk.
+ @pre We don't have any tables open in the given thread.
+
+ @retval FALSE success
+ @retval TRUE error (reported)
+*/
+
+bool
+Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data,
+ LEX_STRING *new_dbname,
+ LEX_STRING *new_name)
+{
+ CHARSET_INFO *scs= system_charset_info;
+ TABLE *table= NULL;
+ sp_head *sp= thd->lex->sphead;
+ ulonglong saved_mode= thd->variables.sql_mode;
+ /*
+ Take a savepoint to release only the lock on mysql.event
+ table at the end but keep the global read lock and
+ possible other locks taken by the caller.
+ */
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ int ret= 1;
+
+ DBUG_ENTER("Event_db_repository::update_event");
+
+ /* None or both must be set */
+ DBUG_ASSERT((new_dbname && new_name) || new_dbname == new_name);
+
+ /* Reset sql_mode during data dictionary operations. */
+ thd->variables.sql_mode= 0;
+
+ if (open_event_table(thd, TL_WRITE, &table))
+ goto end;
+
+ DBUG_PRINT("info", ("dbname: %s", parse_data->dbname.str));
+ DBUG_PRINT("info", ("name: %s", parse_data->name.str));
+ DBUG_PRINT("info", ("user: %s", parse_data->definer.str));
+
+ /* first look whether we overwrite */
+ if (new_name)
+ {
+ DBUG_PRINT("info", ("rename to: %s@%s", new_dbname->str, new_name->str));
+ if (!find_named_event(*new_dbname, *new_name, table))
+ {
+ my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_name->str);
+ goto end;
+ }
+ }
+ /*
+ ...and then if there is such an event. Don't exchange the blocks
+ because you will get error 120 from table handler because new_name will
+ overwrite the key and SE will tell us that it cannot find the already found
+ row (copied into record[1] later
+ */
+ if (find_named_event(parse_data->dbname, parse_data->name, table))
+ {
+ my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), parse_data->name.str);
+ goto end;
+ }
+
+ store_record(table,record[1]);
+
+ /*
+ We check whether ALTER EVENT was given dates that are in the past.
+ However to know how to react, we need the ON COMPLETION type. The
+ check is deferred to this point because by now we have the previous
+ setting (from the event-table) to fall back on if nothing was specified
+ in the ALTER EVENT-statement.
+ */
+
+ if (parse_data->check_dates(thd,
+ (int) table->field[ET_FIELD_ON_COMPLETION]->val_int()))
+ goto end;
+
+ /*
+ mysql_event_fill_row() calls my_error() in case of error so no need to
+ handle it here
+ */
+ if (mysql_event_fill_row(thd, table, parse_data, sp, saved_mode, TRUE))
+ goto end;
+
+ if (new_dbname)
+ {
+ table->field[ET_FIELD_DB]->store(new_dbname->str, new_dbname->length, scs);
+ table->field[ET_FIELD_NAME]->store(new_name->str, new_name->length, scs);
+ }
+
+ if ((ret= table->file->ha_update_row(table->record[1], table->record[0])))
+ {
+ table->file->print_error(ret, MYF(0));
+ goto end;
+ }
+ ret= 0;
+
+end:
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ thd->variables.sql_mode= saved_mode;
+ DBUG_RETURN(MY_TEST(ret));
+}
+
+
+/**
+ Delete event record from mysql.event table.
+
+ @param[in,out] thd thread handle
+ @param[in] db Database name
+ @param[in] name Event name
+ @param[in] drop_if_exists DROP IF EXISTS clause was specified.
+ If set, and the event does not exist,
+ the error is downgraded to a warning.
+
+ @retval FALSE success
+ @retval TRUE error (reported)
+*/
+
+bool
+Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name,
+ bool drop_if_exists)
+{
+ TABLE *table= NULL;
+ /*
+ Take a savepoint to release only the lock on mysql.event
+ table at the end but keep the global read lock and
+ possible other locks taken by the caller.
+ */
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ int ret= 1;
+
+ DBUG_ENTER("Event_db_repository::drop_event");
+ DBUG_PRINT("enter", ("%s@%s", db.str, name.str));
+
+ if (open_event_table(thd, TL_WRITE, &table))
+ goto end;
+
+ if (!find_named_event(db, name, table))
+ {
+ if ((ret= table->file->ha_delete_row(table->record[0])))
+ table->file->print_error(ret, MYF(0));
+ goto end;
+ }
+
+ /* Event not found */
+ if (!drop_if_exists)
+ {
+ my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str);
+ goto end;
+ }
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST),
+ "Event", name.str);
+ ret= 0;
+
+end:
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ DBUG_RETURN(MY_TEST(ret));
+}
+
+
+/**
+ Positions the internal pointer of `table` to the place where (db, name)
+ is stored.
+
+ In case search succeeded, the table cursor points at the found row.
+
+ @param[in] db database name
+ @param[in] name event name
+ @param[in,out] table mysql.event table
+
+
+ @retval FALSE an event with such db/name key exists
+ @retval TRUE no record found or an error occured.
+*/
+
+bool
+Event_db_repository::find_named_event(LEX_STRING db, LEX_STRING name,
+ TABLE *table)
+{
+ uchar key[MAX_KEY_LENGTH];
+ DBUG_ENTER("Event_db_repository::find_named_event");
+ DBUG_PRINT("enter", ("name: %.*s", (int) name.length, name.str));
+
+ /*
+ Create key to find row. We have to use field->store() to be able to
+ handle VARCHAR and CHAR fields.
+ Assumption here is that the two first fields in the table are
+ 'db' and 'name' and the first key is the primary key over the
+ same fields.
+ */
+ if (db.length > table->field[ET_FIELD_DB]->field_length ||
+ name.length > table->field[ET_FIELD_NAME]->field_length ||
+ table->s->keys == 0 ||
+ table->key_info[0].user_defined_key_parts != 2 ||
+ table->key_info[0].key_part[0].fieldnr != ET_FIELD_DB+1 ||
+ table->key_info[0].key_part[1].fieldnr != ET_FIELD_NAME+1)
+ DBUG_RETURN(TRUE);
+
+ table->field[ET_FIELD_DB]->store(db.str, db.length, &my_charset_bin);
+ table->field[ET_FIELD_NAME]->store(name.str, name.length, &my_charset_bin);
+
+ key_copy(key, table->record[0], table->key_info, table->key_info->key_length);
+
+ if (table->file->ha_index_read_idx_map(table->record[0], 0, key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ DBUG_PRINT("info", ("Row not found"));
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_PRINT("info", ("Row found!"));
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Drops all events in the selected database, from mysql.event.
+
+ SYNOPSIS
+ Event_db_repository::drop_schema_events()
+ thd Thread
+ schema The database to clean from events
+*/
+
+void
+Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema)
+{
+ int ret= 0;
+ TABLE *table= NULL;
+ READ_RECORD read_record_info;
+ enum enum_events_table_field field= ET_FIELD_DB;
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ DBUG_ENTER("Event_db_repository::drop_schema_events");
+ DBUG_PRINT("enter", ("field=%d schema=%s", field, schema.str));
+
+ if (open_event_table(thd, TL_WRITE, &table))
+ DBUG_VOID_RETURN;
+
+ /* only enabled events are in memory, so we go now and delete the rest */
+ if (init_read_record(&read_record_info, thd, table, NULL, 1, 0, FALSE))
+ goto end;
+
+ while (!ret && !(read_record_info.read_record(&read_record_info)) )
+ {
+ char *et_field= get_field(thd->mem_root, table->field[field]);
+
+ /* et_field may be NULL if the table is corrupted or out of memory */
+ if (et_field)
+ {
+ LEX_STRING et_field_lex= { et_field, strlen(et_field) };
+ DBUG_PRINT("info", ("Current event %s name=%s", et_field,
+ get_field(thd->mem_root,
+ table->field[ET_FIELD_NAME])));
+
+ if (!sortcmp_lex_string(et_field_lex, schema, system_charset_info))
+ {
+ DBUG_PRINT("info", ("Dropping"));
+ if ((ret= table->file->ha_delete_row(table->record[0])))
+ table->file->print_error(ret, MYF(0));
+ }
+ }
+ }
+ end_read_record(&read_record_info);
+
+end:
+ close_thread_tables(thd);
+ /*
+ Make sure to only release the MDL lock on mysql.event, not other
+ metadata locks DROP DATABASE might have acquired.
+ */
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Looks for a named event in mysql.event and then loads it from
+ the table.
+
+ @pre The given thread does not have open tables.
+
+ @retval FALSE success
+ @retval TRUE error
+*/
+
+bool
+Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname,
+ LEX_STRING name, Event_basic *etn)
+{
+ bool ret;
+ ulonglong saved_mode= thd->variables.sql_mode;
+ Open_tables_backup open_tables_backup;
+ TABLE_LIST event_table;
+
+ DBUG_ENTER("Event_db_repository::load_named_event");
+ DBUG_PRINT("enter",("thd: 0x%lx name: %*s", (long) thd,
+ (int) name.length, name.str));
+
+ event_table.init_one_table("mysql", 5, "event", 5, "event", TL_READ);
+
+ /* Reset sql_mode during data dictionary operations. */
+ thd->variables.sql_mode= 0;
+
+ /*
+ We don't use open_event_table() here to make sure that SHOW
+ CREATE EVENT works properly in transactional context, and
+ does not release transactional metadata locks when the
+ event table is closed.
+ */
+ if (!(ret= open_system_tables_for_read(thd, &event_table, &open_tables_backup)))
+ {
+ if (table_intact.check(event_table.table, &event_table_def))
+ {
+ close_system_tables(thd, &open_tables_backup);
+ my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if ((ret= find_named_event(dbname, name, event_table.table)))
+ my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str);
+ else if ((ret= etn->load_from_row(thd, event_table.table)))
+ my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "event");
+
+ close_system_tables(thd, &open_tables_backup);
+ }
+
+ thd->variables.sql_mode= saved_mode;
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Update the event record in mysql.event table with a changed status
+ and/or last execution time.
+
+ @pre The thread handle does not have open tables.
+*/
+
+bool
+Event_db_repository::
+update_timing_fields_for_event(THD *thd,
+ LEX_STRING event_db_name,
+ LEX_STRING event_name,
+ my_time_t last_executed,
+ ulonglong status)
+{
+ TABLE *table= NULL;
+ Field **fields;
+ int ret= 1;
+ enum_binlog_format save_binlog_format;
+ MYSQL_TIME time;
+ DBUG_ENTER("Event_db_repository::update_timing_fields_for_event");
+
+ /*
+ Turn off row binlogging of event timing updates. These are not used
+ for RBR of events replicated to the slave.
+ */
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ DBUG_ASSERT(thd->security_ctx->master_access & SUPER_ACL);
+
+ if (open_event_table(thd, TL_WRITE, &table))
+ goto end;
+
+ fields= table->field;
+
+ if (find_named_event(event_db_name, event_name, table))
+ goto end;
+
+ store_record(table, record[1]);
+
+ my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed);
+ fields[ET_FIELD_LAST_EXECUTED]->set_notnull();
+ fields[ET_FIELD_LAST_EXECUTED]->store_time(&time);
+
+ fields[ET_FIELD_STATUS]->set_notnull();
+ fields[ET_FIELD_STATUS]->store(status, TRUE);
+
+ if ((ret= table->file->ha_update_row(table->record[1], table->record[0])))
+ {
+ table->file->print_error(ret, MYF(0));
+ goto end;
+ }
+
+ ret= 0;
+
+end:
+ if (table)
+ close_mysql_tables(thd);
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ DBUG_RETURN(MY_TEST(ret));
+}
+
+
+/**
+ Open mysql.db, mysql.user and mysql.event and check whether:
+ - mysql.db exists and is up to date (or from a newer version of MySQL),
+ - mysql.user has column Event_priv at an expected position,
+ - mysql.event exists and is up to date (or from a newer version of
+ MySQL)
+
+ This function is called only when the server is started.
+ @pre The passed in thread handle has no open tables.
+
+ @retval FALSE OK
+ @retval TRUE Error, an error message is output to the error log.
+*/
+
+bool
+Event_db_repository::check_system_tables(THD *thd)
+{
+ TABLE_LIST tables;
+ int ret= FALSE;
+ const unsigned int event_priv_column_position= 29;
+
+ DBUG_ENTER("Event_db_repository::check_system_tables");
+ DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd));
+
+ /* Check mysql.db */
+ tables.init_one_table("mysql", 5, "db", 2, "db", TL_READ);
+
+ if (open_and_lock_tables(thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ ret= 1;
+ sql_print_error("Cannot open mysql.db");
+ }
+ else
+ {
+ if (table_intact.check(tables.table, &mysql_db_table_def))
+ ret= 1;
+
+ close_mysql_tables(thd);
+ }
+ /* Check mysql.user */
+ tables.init_one_table("mysql", 5, "user", 4, "user", TL_READ);
+
+ if (open_and_lock_tables(thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ ret= 1;
+ sql_print_error("Cannot open mysql.user");
+ }
+ else
+ {
+ if (tables.table->s->fields < event_priv_column_position ||
+ strncmp(tables.table->field[event_priv_column_position]->field_name,
+ STRING_WITH_LEN("Event_priv")))
+ {
+ sql_print_error("mysql.user has no `Event_priv` column at position %d",
+ event_priv_column_position);
+ ret= 1;
+ }
+ close_mysql_tables(thd);
+ }
+ /* Check mysql.event */
+ tables.init_one_table("mysql", 5, "event", 5, "event", TL_READ);
+
+ if (open_and_lock_tables(thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ ret= 1;
+ sql_print_error("Cannot open mysql.event");
+ }
+ else
+ {
+ if (table_intact.check(tables.table, &event_table_def))
+ ret= 1;
+ close_mysql_tables(thd);
+ }
+
+ DBUG_RETURN(MY_TEST(ret));
+}
+
+/**
+ @} (End of group Event_Scheduler)
+*/
diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h
new file mode 100644
index 00000000000..a2862790be1
--- /dev/null
+++ b/sql/event_db_repository.h
@@ -0,0 +1,127 @@
+#ifndef _EVENT_DB_REPOSITORY_H_
+#define _EVENT_DB_REPOSITORY_H_
+/* Copyright (c) 2006, 2011, Oracle and/or its affiliates.
+
+ 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
+*/
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+
+ @file event_db_repository.h
+
+ Data Dictionary related operations of Event Scheduler.
+
+ This is a private header file of Events module. Please do not include it
+ directly. All public declarations of Events module should be stored in
+ events.h and event_data_objects.h.
+*/
+
+enum enum_events_table_field
+{
+ ET_FIELD_DB = 0,
+ ET_FIELD_NAME,
+ ET_FIELD_BODY,
+ ET_FIELD_DEFINER,
+ ET_FIELD_EXECUTE_AT,
+ ET_FIELD_INTERVAL_EXPR,
+ ET_FIELD_TRANSIENT_INTERVAL,
+ ET_FIELD_CREATED,
+ ET_FIELD_MODIFIED,
+ ET_FIELD_LAST_EXECUTED,
+ ET_FIELD_STARTS,
+ ET_FIELD_ENDS,
+ ET_FIELD_STATUS,
+ ET_FIELD_ON_COMPLETION,
+ ET_FIELD_SQL_MODE,
+ ET_FIELD_COMMENT,
+ ET_FIELD_ORIGINATOR,
+ ET_FIELD_TIME_ZONE,
+ ET_FIELD_CHARACTER_SET_CLIENT,
+ ET_FIELD_COLLATION_CONNECTION,
+ ET_FIELD_DB_COLLATION,
+ ET_FIELD_BODY_UTF8,
+ ET_FIELD_COUNT /* a cool trick to count the number of fields :) */
+};
+
+
+int
+events_table_index_read_for_db(THD *thd, TABLE *schema_table,
+ TABLE *event_table);
+
+int
+events_table_scan_all(THD *thd, TABLE *schema_table, TABLE *event_table);
+
+
+class Event_basic;
+class Event_parse_data;
+
+class Event_db_repository
+{
+public:
+ Event_db_repository(){}
+
+ bool
+ create_event(THD *thd, Event_parse_data *parse_data, bool create_if_not,
+ bool *event_already_exists);
+ bool
+ update_event(THD *thd, Event_parse_data *parse_data, LEX_STRING *new_dbname,
+ LEX_STRING *new_name);
+
+ bool
+ drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists);
+
+ void
+ drop_schema_events(THD *thd, LEX_STRING schema);
+
+ bool
+ find_named_event(LEX_STRING db, LEX_STRING name, TABLE *table);
+
+ bool
+ load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_basic *et);
+
+ static bool
+ open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table);
+
+ bool
+ fill_schema_events(THD *thd, TABLE_LIST *tables, const char *db);
+
+ bool
+ update_timing_fields_for_event(THD *thd,
+ LEX_STRING event_db_name,
+ LEX_STRING event_name,
+ my_time_t last_executed,
+ ulonglong status);
+public:
+ static bool
+ check_system_tables(THD *thd);
+private:
+ bool
+ index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table,
+ const char *db);
+
+ bool
+ table_scan_all_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table);
+
+private:
+ /* Prevent use of these */
+ Event_db_repository(const Event_db_repository &);
+ void operator=(Event_db_repository &);
+};
+
+/**
+ @} (End of group Event_Scheduler)
+*/
+#endif /* _EVENT_DB_REPOSITORY_H_ */
diff --git a/sql/event_parse_data.cc b/sql/event_parse_data.cc
new file mode 100644
index 00000000000..56c6c3cc13c
--- /dev/null
+++ b/sql/event_parse_data.cc
@@ -0,0 +1,582 @@
+/*
+ Copyright (c) 2008, 2011, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sp_head.h"
+#include "event_parse_data.h"
+#include "sql_time.h" // TIME_to_timestamp
+
+/*
+ Returns a new instance
+
+ SYNOPSIS
+ Event_parse_data::new_instance()
+
+ RETURN VALUE
+ Address or NULL in case of error
+
+ NOTE
+ Created on THD's mem_root
+*/
+
+Event_parse_data *
+Event_parse_data::new_instance(THD *thd)
+{
+ return new (thd->mem_root) Event_parse_data;
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_parse_data::Event_parse_data()
+*/
+
+Event_parse_data::Event_parse_data()
+ :on_completion(Event_parse_data::ON_COMPLETION_DEFAULT),
+ status(Event_parse_data::ENABLED), status_changed(false),
+ do_not_create(FALSE), body_changed(FALSE),
+ item_starts(NULL), item_ends(NULL), item_execute_at(NULL),
+ starts_null(TRUE), ends_null(TRUE), execute_at_null(TRUE),
+ item_expression(NULL), expression(0)
+{
+ DBUG_ENTER("Event_parse_data::Event_parse_data");
+
+ /* Actually in the parser STARTS is always set */
+ starts= ends= execute_at= 0;
+
+ comment.str= NULL;
+ comment.length= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Set a name of the event
+
+ SYNOPSIS
+ Event_parse_data::init_name()
+ thd THD
+ spn the name extracted in the parser
+*/
+
+void
+Event_parse_data::init_name(THD *thd, sp_name *spn)
+{
+ DBUG_ENTER("Event_parse_data::init_name");
+
+ /* We have to copy strings to get them into the right memroot */
+ dbname.length= spn->m_db.length;
+ dbname.str= thd->strmake(spn->m_db.str, spn->m_db.length);
+ name.length= spn->m_name.length;
+ name.str= thd->strmake(spn->m_name.str, spn->m_name.length);
+
+ if (spn->m_qname.length == 0)
+ spn->init_qname(thd);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ This function is called on CREATE EVENT or ALTER EVENT. When either
+ ENDS or AT is in the past, we are trying to create an event that
+ will never be executed. If it has ON COMPLETION NOT PRESERVE
+ (default), then it would normally be dropped already, so on CREATE
+ EVENT we give a warning, and do not create anyting. On ALTER EVENT
+ we give a error, and do not change the event.
+
+ If the event has ON COMPLETION PRESERVE, then we see if the event is
+ created or altered to the ENABLED (default) state. If so, then we
+ give a warning, and change the state to DISABLED.
+
+ Otherwise it is a valid event in ON COMPLETION PRESERVE DISABLE
+ state.
+*/
+
+void
+Event_parse_data::check_if_in_the_past(THD *thd, my_time_t ltime_utc)
+{
+ if (ltime_utc >= thd->query_start())
+ return;
+
+ /*
+ We'll come back later when we have the real on_completion value
+ */
+ if (on_completion == Event_parse_data::ON_COMPLETION_DEFAULT)
+ return;
+
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ {
+ switch (thd->lex->sql_command) {
+ case SQLCOM_CREATE_EVENT:
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_EVENT_CANNOT_CREATE_IN_THE_PAST,
+ ER(ER_EVENT_CANNOT_CREATE_IN_THE_PAST));
+ break;
+ case SQLCOM_ALTER_EVENT:
+ my_error(ER_EVENT_CANNOT_ALTER_IN_THE_PAST, MYF(0));
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+
+ do_not_create= TRUE;
+ }
+ else if (status == Event_parse_data::ENABLED)
+ {
+ status= Event_parse_data::DISABLED;
+ status_changed= true;
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_EVENT_EXEC_TIME_IN_THE_PAST,
+ ER(ER_EVENT_EXEC_TIME_IN_THE_PAST));
+ }
+}
+
+
+/*
+ Check time/dates in ALTER EVENT
+
+ We check whether ALTER EVENT was given dates that are in the past.
+ However to know how to react, we need the ON COMPLETION type. Hence,
+ the check is deferred until we have the previous ON COMPLETION type
+ from the event-db to fall back on if nothing was specified in the
+ ALTER EVENT-statement.
+
+ SYNOPSIS
+ Event_parse_data::check_dates()
+ thd Thread
+ on_completion ON COMPLETION value currently in event-db.
+ Will be overridden by value in ALTER EVENT if given.
+
+ RETURN VALUE
+ TRUE an error occurred, do not ALTER
+ FALSE OK
+*/
+
+bool
+Event_parse_data::check_dates(THD *thd, int previous_on_completion)
+{
+ if (on_completion == Event_parse_data::ON_COMPLETION_DEFAULT)
+ {
+ on_completion= previous_on_completion;
+ if (!ends_null)
+ check_if_in_the_past(thd, ends);
+ if (!execute_at_null)
+ check_if_in_the_past(thd, execute_at);
+ }
+ return do_not_create;
+}
+
+
+
+/*
+ Sets time for execution for one-time event.
+
+ SYNOPSIS
+ Event_parse_data::init_execute_at()
+ thd Thread
+
+ RETURN VALUE
+ 0 OK
+ ER_WRONG_VALUE Wrong value for execute at (reported)
+*/
+
+int
+Event_parse_data::init_execute_at(THD *thd)
+{
+ uint not_used;
+ MYSQL_TIME ltime;
+ my_time_t ltime_utc;
+
+ DBUG_ENTER("Event_parse_data::init_execute_at");
+
+ if (!item_execute_at)
+ DBUG_RETURN(0);
+
+ if (item_execute_at->fix_fields(thd, &item_execute_at))
+ goto wrong_value;
+
+ /* no starts and/or ends in case of execute_at */
+ DBUG_PRINT("info", ("starts_null && ends_null should be 1 is %d",
+ (starts_null && ends_null)));
+ DBUG_ASSERT(starts_null && ends_null);
+
+ if (item_execute_at->get_date(&ltime, TIME_NO_ZERO_DATE))
+ goto wrong_value;
+
+ ltime_utc= TIME_to_timestamp(thd,&ltime,&not_used);
+ if (!ltime_utc)
+ {
+ DBUG_PRINT("error", ("Execute AT after year 2037"));
+ goto wrong_value;
+ }
+
+ check_if_in_the_past(thd, ltime_utc);
+
+ execute_at_null= FALSE;
+ execute_at= ltime_utc;
+ DBUG_RETURN(0);
+
+wrong_value:
+ report_bad_value("AT", item_execute_at);
+ DBUG_RETURN(ER_WRONG_VALUE);
+}
+
+
+/*
+ Sets time for execution of multi-time event.s
+
+ SYNOPSIS
+ Event_parse_data::init_interval()
+ thd Thread
+
+ RETURN VALUE
+ 0 OK
+ EVEX_BAD_PARAMS Interval is not positive or MICROSECOND (reported)
+ ER_WRONG_VALUE Wrong value for interval (reported)
+*/
+
+int
+Event_parse_data::init_interval(THD *thd)
+{
+ INTERVAL interval_tmp;
+
+ DBUG_ENTER("Event_parse_data::init_interval");
+ if (!item_expression)
+ DBUG_RETURN(0);
+
+ switch (interval) {
+ case INTERVAL_MINUTE_MICROSECOND:
+ case INTERVAL_HOUR_MICROSECOND:
+ case INTERVAL_DAY_MICROSECOND:
+ case INTERVAL_SECOND_MICROSECOND:
+ case INTERVAL_MICROSECOND:
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND");
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+ default:
+ break;
+ }
+
+ if (item_expression->fix_fields(thd, &item_expression))
+ goto wrong_value;
+
+ if (get_interval_value(item_expression, interval, &interval_tmp))
+ goto wrong_value;
+
+ expression= 0;
+
+ switch (interval) {
+ case INTERVAL_YEAR:
+ expression= interval_tmp.year;
+ break;
+ case INTERVAL_QUARTER:
+ case INTERVAL_MONTH:
+ expression= interval_tmp.month;
+ break;
+ case INTERVAL_WEEK:
+ case INTERVAL_DAY:
+ expression= interval_tmp.day;
+ break;
+ case INTERVAL_HOUR:
+ expression= interval_tmp.hour;
+ break;
+ case INTERVAL_MINUTE:
+ expression= interval_tmp.minute;
+ break;
+ case INTERVAL_SECOND:
+ expression= interval_tmp.second;
+ break;
+ case INTERVAL_YEAR_MONTH: // Allow YEAR-MONTH YYYYYMM
+ expression= interval_tmp.year* 12 + interval_tmp.month;
+ break;
+ case INTERVAL_DAY_HOUR:
+ expression= interval_tmp.day* 24 + interval_tmp.hour;
+ break;
+ case INTERVAL_DAY_MINUTE:
+ expression= (interval_tmp.day* 24 + interval_tmp.hour) * 60 +
+ interval_tmp.minute;
+ break;
+ case INTERVAL_HOUR_SECOND: /* day is anyway 0 */
+ case INTERVAL_DAY_SECOND:
+ /* DAY_SECOND having problems because of leap seconds? */
+ expression= ((interval_tmp.day* 24 + interval_tmp.hour) * 60 +
+ interval_tmp.minute)*60
+ + interval_tmp.second;
+ break;
+ case INTERVAL_HOUR_MINUTE:
+ expression= interval_tmp.hour * 60 + interval_tmp.minute;
+ break;
+ case INTERVAL_MINUTE_SECOND:
+ expression= interval_tmp.minute * 60 + interval_tmp.second;
+ break;
+ case INTERVAL_LAST:
+ DBUG_ASSERT(0);
+ default:
+ ;/* these are the microsec stuff */
+ }
+ if (interval_tmp.neg || expression == 0 ||
+ expression > EVEX_MAX_INTERVAL_VALUE)
+ {
+ my_error(ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG, MYF(0));
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+ }
+
+ DBUG_RETURN(0);
+
+wrong_value:
+ report_bad_value("INTERVAL", item_expression);
+ DBUG_RETURN(ER_WRONG_VALUE);
+}
+
+
+/*
+ Sets STARTS.
+
+ SYNOPSIS
+ Event_parse_data::init_starts()
+ expr how much?
+
+ NOTES
+ Note that activation time is not execution time.
+ EVERY 5 MINUTE STARTS "2004-12-12 10:00:00" means that
+ the event will be executed every 5 minutes but this will
+ start at the date shown above. Expressions are possible :
+ DATE_ADD(NOW(), INTERVAL 1 DAY) -- start tommorow at
+ same time.
+
+ RETURN VALUE
+ 0 OK
+ ER_WRONG_VALUE Starts before now
+*/
+
+int
+Event_parse_data::init_starts(THD *thd)
+{
+ uint not_used;
+ MYSQL_TIME ltime;
+ my_time_t ltime_utc;
+
+ DBUG_ENTER("Event_parse_data::init_starts");
+ if (!item_starts)
+ DBUG_RETURN(0);
+
+ if (item_starts->fix_fields(thd, &item_starts))
+ goto wrong_value;
+
+ if (item_starts->get_date(&ltime, TIME_NO_ZERO_DATE))
+ goto wrong_value;
+
+ ltime_utc= TIME_to_timestamp(thd, &ltime, &not_used);
+ if (!ltime_utc)
+ goto wrong_value;
+
+ DBUG_PRINT("info",("now: %ld starts: %ld",
+ (long) thd->query_start(), (long) ltime_utc));
+
+ starts_null= FALSE;
+ starts= ltime_utc;
+ DBUG_RETURN(0);
+
+wrong_value:
+ report_bad_value("STARTS", item_starts);
+ DBUG_RETURN(ER_WRONG_VALUE);
+}
+
+
+/*
+ Sets ENDS (deactivation time).
+
+ SYNOPSIS
+ Event_parse_data::init_ends()
+ thd THD
+
+ NOTES
+ Note that activation time is not execution time.
+ EVERY 5 MINUTE ENDS "2004-12-12 10:00:00" means that
+ the event will be executed every 5 minutes but this will
+ end at the date shown above. Expressions are possible :
+ DATE_ADD(NOW(), INTERVAL 1 DAY) -- end tommorow at
+ same time.
+
+ RETURN VALUE
+ 0 OK
+ EVEX_BAD_PARAMS Error (reported)
+*/
+
+int
+Event_parse_data::init_ends(THD *thd)
+{
+ uint not_used;
+ MYSQL_TIME ltime;
+ my_time_t ltime_utc;
+
+ DBUG_ENTER("Event_parse_data::init_ends");
+ if (!item_ends)
+ DBUG_RETURN(0);
+
+ if (item_ends->fix_fields(thd, &item_ends))
+ goto error_bad_params;
+
+ DBUG_PRINT("info", ("convert to TIME"));
+ if (item_ends->get_date(&ltime, TIME_NO_ZERO_DATE))
+ goto error_bad_params;
+
+ ltime_utc= TIME_to_timestamp(thd, &ltime, &not_used);
+ if (!ltime_utc)
+ goto error_bad_params;
+
+ /* Check whether ends is after starts */
+ DBUG_PRINT("info", ("ENDS after STARTS?"));
+ if (!starts_null && starts >= ltime_utc)
+ goto error_bad_params;
+
+ check_if_in_the_past(thd, ltime_utc);
+
+ ends_null= FALSE;
+ ends= ltime_utc;
+ DBUG_RETURN(0);
+
+error_bad_params:
+ my_error(ER_EVENT_ENDS_BEFORE_STARTS, MYF(0));
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+}
+
+
+/*
+ Prints an error message about invalid value. Internally used
+ during input data verification
+
+ SYNOPSIS
+ Event_parse_data::report_bad_value()
+ item_name The name of the parameter
+ bad_item The parameter
+*/
+
+void
+Event_parse_data::report_bad_value(const char *item_name, Item *bad_item)
+{
+ char buff[120];
+ String str(buff,(uint32) sizeof(buff), system_charset_info);
+ String *str2= bad_item->fixed? bad_item->val_str(&str):NULL;
+ my_error(ER_WRONG_VALUE, MYF(0), item_name, str2? str2->c_ptr_safe():"NULL");
+}
+
+
+/*
+ Checks for validity the data gathered during the parsing phase.
+
+ SYNOPSIS
+ Event_parse_data::check_parse_data()
+ thd Thread
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error (reported)
+*/
+
+bool
+Event_parse_data::check_parse_data(THD *thd)
+{
+ bool ret;
+ DBUG_ENTER("Event_parse_data::check_parse_data");
+ DBUG_PRINT("info", ("execute_at: 0x%lx expr=0x%lx starts=0x%lx ends=0x%lx",
+ (long) item_execute_at, (long) item_expression,
+ (long) item_starts, (long) item_ends));
+
+ init_name(thd, identifier);
+
+ init_definer(thd);
+
+ ret= init_execute_at(thd) || init_interval(thd) || init_starts(thd) ||
+ init_ends(thd);
+ check_originator_id(thd);
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Inits definer (definer_user and definer_host) during parsing.
+
+ SYNOPSIS
+ Event_parse_data::init_definer()
+ thd Thread
+*/
+
+void
+Event_parse_data::init_definer(THD *thd)
+{
+ DBUG_ENTER("Event_parse_data::init_definer");
+
+ DBUG_ASSERT(thd->lex->definer);
+
+ const char *definer_user= thd->lex->definer->user.str;
+ const char *definer_host= thd->lex->definer->host.str;
+ size_t definer_user_len= thd->lex->definer->user.length;
+ size_t definer_host_len= thd->lex->definer->host.length;
+
+ DBUG_PRINT("info",("init definer_user thd->mem_root: 0x%lx "
+ "definer_user: 0x%lx", (long) thd->mem_root,
+ (long) definer_user));
+
+ /* + 1 for @ */
+ DBUG_PRINT("info",("init definer as whole"));
+ definer.length= definer_user_len + definer_host_len + 1;
+ definer.str= (char*) thd->alloc(definer.length + 1);
+
+ DBUG_PRINT("info",("copy the user"));
+ memcpy(definer.str, definer_user, definer_user_len);
+ definer.str[definer_user_len]= '@';
+
+ DBUG_PRINT("info",("copy the host"));
+ memcpy(definer.str + definer_user_len + 1, definer_host, definer_host_len);
+ definer.str[definer.length]= '\0';
+ DBUG_PRINT("info",("definer [%s] initted", definer.str));
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set the originator id of the event to the server_id if executing on
+ the master or set to the server_id of the master if executing on
+ the slave. If executing on slave, also set status to SLAVESIDE_DISABLED.
+
+ SYNOPSIS
+ Event_parse_data::check_originator_id()
+*/
+void Event_parse_data::check_originator_id(THD *thd)
+{
+ /* Disable replicated events on slave. */
+ if ((thd->system_thread == SYSTEM_THREAD_SLAVE_SQL) ||
+ (thd->system_thread == SYSTEM_THREAD_SLAVE_IO))
+ {
+ DBUG_PRINT("info", ("Invoked object status set to SLAVESIDE_DISABLED."));
+ if ((status == Event_parse_data::ENABLED) ||
+ (status == Event_parse_data::DISABLED))
+ {
+ status= Event_parse_data::SLAVESIDE_DISABLED;
+ status_changed= true;
+ }
+ originator = thd->variables.server_id;
+ }
+ else
+ originator = global_system_variables.server_id;
+}
diff --git a/sql/event_parse_data.h b/sql/event_parse_data.h
new file mode 100644
index 00000000000..faf42db623a
--- /dev/null
+++ b/sql/event_parse_data.h
@@ -0,0 +1,132 @@
+/*
+ Copyright (c) 2008, 2011, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifndef _EVENT_PARSE_DATA_H_
+#define _EVENT_PARSE_DATA_H_
+
+#include "sql_list.h" /* Sql_alloc */
+
+class Item;
+class THD;
+class sp_name;
+
+#define EVEX_GET_FIELD_FAILED -2
+#define EVEX_BAD_PARAMS -5
+#define EVEX_MICROSECOND_UNSUP -6
+#define EVEX_MAX_INTERVAL_VALUE 1000000000L
+
+class Event_parse_data : public Sql_alloc
+{
+public:
+ /*
+ ENABLED = feature can function normally (is turned on)
+ SLAVESIDE_DISABLED = feature is turned off on slave
+ DISABLED = feature is turned off
+ */
+ enum enum_status
+ {
+ ENABLED = 1,
+ DISABLED,
+ SLAVESIDE_DISABLED
+ };
+
+ enum enum_on_completion
+ {
+ /*
+ On CREATE EVENT, DROP is the DEFAULT as per the docs.
+ On ALTER EVENT, "no change" is the DEFAULT.
+ */
+ ON_COMPLETION_DEFAULT = 0,
+ ON_COMPLETION_DROP,
+ ON_COMPLETION_PRESERVE
+ };
+
+ int on_completion;
+ int status;
+ bool status_changed;
+ longlong originator;
+ /*
+ do_not_create will be set if STARTS time is in the past and
+ on_completion == ON_COMPLETION_DROP.
+ */
+ bool do_not_create;
+
+ bool body_changed;
+
+ LEX_STRING dbname;
+ LEX_STRING name;
+ LEX_STRING definer;// combination of user and host
+ LEX_STRING comment;
+
+ Item* item_starts;
+ Item* item_ends;
+ Item* item_execute_at;
+
+ my_time_t starts;
+ my_time_t ends;
+ my_time_t execute_at;
+ bool starts_null;
+ bool ends_null;
+ bool execute_at_null;
+
+ sp_name *identifier;
+ Item* item_expression;
+ longlong expression;
+ interval_type interval;
+
+ static Event_parse_data *
+ new_instance(THD *thd);
+
+ bool
+ check_parse_data(THD *thd);
+
+ bool
+ check_dates(THD *thd, int previous_on_completion);
+
+private:
+
+ void
+ init_definer(THD *thd);
+
+ void
+ init_name(THD *thd, sp_name *spn);
+
+ int
+ init_execute_at(THD *thd);
+
+ int
+ init_interval(THD *thd);
+
+ int
+ init_starts(THD *thd);
+
+ int
+ init_ends(THD *thd);
+
+ Event_parse_data();
+ ~Event_parse_data();
+
+ void
+ report_bad_value(const char *item_name, Item *bad_item);
+
+ void
+ check_if_in_the_past(THD *thd, my_time_t ltime_utc);
+
+ Event_parse_data(const Event_parse_data &); /* Prevent use of these */
+ void check_originator_id(THD *thd);
+ void operator=(Event_parse_data &);
+};
+#endif
diff --git a/sql/event_queue.cc b/sql/event_queue.cc
new file mode 100644
index 00000000000..35187af23ac
--- /dev/null
+++ b/sql/event_queue.cc
@@ -0,0 +1,837 @@
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "event_queue.h"
+#include "event_data_objects.h"
+#include "event_db_repository.h"
+#include "events.h"
+#include "sql_audit.h"
+#include "tztime.h" // my_tz_find, my_tz_OFFSET0, struct Time_zone
+#include "log.h" // sql_print_error
+#include "sql_class.h" // struct THD
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+*/
+
+#define EVENT_QUEUE_INITIAL_SIZE 30
+#define EVENT_QUEUE_EXTENT 30
+
+#ifdef __GNUC__
+#if __GNUC__ >= 2
+#define SCHED_FUNC __FUNCTION__
+#endif
+#else
+#define SCHED_FUNC "<unknown>"
+#endif
+
+#define LOCK_QUEUE_DATA() lock_data(SCHED_FUNC, __LINE__)
+#define UNLOCK_QUEUE_DATA() unlock_data(SCHED_FUNC, __LINE__)
+
+/*
+ Compares the execute_at members of two Event_queue_element instances.
+ Used as callback for the prioritized queue when shifting
+ elements inside.
+
+ SYNOPSIS
+ event_queue_element_data_compare_q()
+ vptr Not used (set it to NULL)
+ a First Event_queue_element object
+ b Second Event_queue_element object
+
+ RETURN VALUE
+ -1 a->execute_at < b->execute_at
+ 0 a->execute_at == b->execute_at
+ 1 a->execute_at > b->execute_at
+
+ NOTES
+ execute_at.second_part is not considered during comparison
+*/
+
+extern "C" int event_queue_element_compare_q(void *, uchar *, uchar *);
+
+int event_queue_element_compare_q(void *vptr, uchar* a, uchar *b)
+{
+ Event_queue_element *left = (Event_queue_element *)a;
+ Event_queue_element *right = (Event_queue_element *)b;
+ my_time_t lhs = left->execute_at;
+ my_time_t rhs = right->execute_at;
+
+ if (left->status == Event_parse_data::DISABLED)
+ return right->status != Event_parse_data::DISABLED;
+
+ if (right->status == Event_parse_data::DISABLED)
+ return 1;
+
+ return (lhs < rhs ? -1 : (lhs > rhs ? 1 : 0));
+}
+
+
+/*
+ Constructor of class Event_queue.
+
+ SYNOPSIS
+ Event_queue::Event_queue()
+*/
+
+Event_queue::Event_queue()
+ :next_activation_at(0),
+ mutex_last_locked_at_line(0),
+ mutex_last_unlocked_at_line(0),
+ mutex_last_attempted_lock_at_line(0),
+ mutex_last_locked_in_func("n/a"),
+ mutex_last_unlocked_in_func("n/a"),
+ mutex_last_attempted_lock_in_func("n/a"),
+ mutex_queue_data_locked(FALSE),
+ mutex_queue_data_attempting_lock(FALSE),
+ waiting_on_cond(FALSE)
+{
+ mysql_mutex_init(key_LOCK_event_queue, &LOCK_event_queue, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_queue_state, &COND_queue_state, NULL);
+}
+
+
+Event_queue::~Event_queue()
+{
+ deinit_queue();
+ mysql_mutex_destroy(&LOCK_event_queue);
+ mysql_cond_destroy(&COND_queue_state);
+}
+
+
+/*
+ This is a queue's constructor. Until this method is called, the
+ queue is unusable. We don't use a C++ constructor instead in
+ order to be able to check the return value. The queue is
+ initialized once at server startup. Initialization can fail in
+ case of a failure reading events from the database or out of
+ memory.
+
+ SYNOPSIS
+ Event_queue::init()
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error
+*/
+
+bool
+Event_queue::init_queue(THD *thd)
+{
+ DBUG_ENTER("Event_queue::init_queue");
+ DBUG_PRINT("enter", ("this: 0x%lx", (long) this));
+
+ LOCK_QUEUE_DATA();
+
+ if (::init_queue(&queue, EVENT_QUEUE_INITIAL_SIZE , 0 /*offset*/,
+ 0 /*max_on_top*/, event_queue_element_compare_q,
+ NullS, 0, EVENT_QUEUE_EXTENT))
+ {
+ sql_print_error("Event Scheduler: Can't initialize the execution queue");
+ goto err;
+ }
+
+ UNLOCK_QUEUE_DATA();
+ DBUG_RETURN(FALSE);
+
+err:
+ UNLOCK_QUEUE_DATA();
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Deinits the queue. Remove all elements from it and destroys them
+ too.
+
+ SYNOPSIS
+ Event_queue::deinit_queue()
+*/
+
+void
+Event_queue::deinit_queue()
+{
+ DBUG_ENTER("Event_queue::deinit_queue");
+
+ LOCK_QUEUE_DATA();
+ empty_queue();
+ delete_queue(&queue);
+ UNLOCK_QUEUE_DATA();
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Adds an event to the queue.
+
+ Compute the next execution time for an event, and if it is still
+ active, add it to the queue. Otherwise delete it.
+ The object is left intact in case of an error. Otherwise
+ the queue container assumes ownership of it.
+
+ @param[in] thd thread handle
+ @param[in] new_element a new element to add to the queue
+ @param[out] created set to TRUE if no error and the element is
+ added to the queue, FALSE otherwise
+
+ @retval TRUE an error occured. The value of created is undefined,
+ the element was not deleted.
+ @retval FALSE success
+*/
+
+bool
+Event_queue::create_event(THD *thd, Event_queue_element *new_element,
+ bool *created)
+{
+ DBUG_ENTER("Event_queue::create_event");
+ DBUG_PRINT("enter", ("thd: 0x%lx et=%s.%s", (long) thd,
+ new_element->dbname.str, new_element->name.str));
+
+ /* Will do nothing if the event is disabled */
+ new_element->compute_next_execution_time();
+ if (new_element->status != Event_parse_data::ENABLED)
+ {
+ delete new_element;
+ *created= FALSE;
+ DBUG_RETURN(FALSE);
+ }
+
+ DBUG_PRINT("info", ("new event in the queue: 0x%lx", (long) new_element));
+
+ LOCK_QUEUE_DATA();
+ *created= (queue_insert_safe(&queue, (uchar *) new_element) == FALSE);
+ dbug_dump_queue(thd->query_start());
+ mysql_cond_broadcast(&COND_queue_state);
+ UNLOCK_QUEUE_DATA();
+
+ DBUG_RETURN(!*created);
+}
+
+
+/*
+ Updates an event from the scheduler queue
+
+ SYNOPSIS
+ Event_queue::update_event()
+ thd Thread
+ dbname Schema of the event
+ name Name of the event
+ new_schema New schema, in case of RENAME TO, otherwise NULL
+ new_name New name, in case of RENAME TO, otherwise NULL
+*/
+
+void
+Event_queue::update_event(THD *thd, LEX_STRING dbname, LEX_STRING name,
+ Event_queue_element *new_element)
+{
+ DBUG_ENTER("Event_queue::update_event");
+ DBUG_PRINT("enter", ("thd: 0x%lx et=[%s.%s]", (long) thd, dbname.str, name.str));
+
+ if ((new_element->status == Event_parse_data::DISABLED) ||
+ (new_element->status == Event_parse_data::SLAVESIDE_DISABLED))
+ {
+ DBUG_PRINT("info", ("The event is disabled."));
+ /*
+ Destroy the object but don't skip to end: because we may have to remove
+ object from the cache.
+ */
+ delete new_element;
+ new_element= NULL;
+ }
+ else
+ new_element->compute_next_execution_time();
+
+ LOCK_QUEUE_DATA();
+ find_n_remove_event(dbname, name);
+
+ /* If not disabled event */
+ if (new_element)
+ {
+ DBUG_PRINT("info", ("new event in the queue: 0x%lx", (long) new_element));
+ queue_insert_safe(&queue, (uchar *) new_element);
+ mysql_cond_broadcast(&COND_queue_state);
+ }
+
+ dbug_dump_queue(thd->query_start());
+ UNLOCK_QUEUE_DATA();
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Drops an event from the queue
+
+ SYNOPSIS
+ Event_queue::drop_event()
+ thd Thread
+ dbname Schema of the event to drop
+ name Name of the event to drop
+*/
+
+void
+Event_queue::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name)
+{
+ DBUG_ENTER("Event_queue::drop_event");
+ DBUG_PRINT("enter", ("thd: 0x%lx db :%s name: %s", (long) thd,
+ dbname.str, name.str));
+
+ LOCK_QUEUE_DATA();
+ find_n_remove_event(dbname, name);
+ dbug_dump_queue(thd->query_start());
+ UNLOCK_QUEUE_DATA();
+
+ /*
+ We don't signal here because the scheduler will catch the change
+ next time it wakes up.
+ */
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Drops all events from the in-memory queue and disk that match
+ certain pattern evaluated by a comparator function
+
+ SYNOPSIS
+ Event_queue::drop_matching_events()
+ thd THD
+ pattern A pattern string
+ comparator The function to use for comparing
+
+ RETURN VALUE
+ >=0 Number of dropped events
+
+ NOTE
+ Expected is the caller to acquire lock on LOCK_event_queue
+*/
+
+void
+Event_queue::drop_matching_events(THD *thd, LEX_STRING pattern,
+ bool (*comparator)(LEX_STRING, Event_basic *))
+{
+ uint i;
+ DBUG_ENTER("Event_queue::drop_matching_events");
+ DBUG_PRINT("enter", ("pattern=%s", pattern.str));
+
+ for (i= queue_first_element(&queue) ;
+ i <= queue_last_element(&queue) ;
+ )
+ {
+ Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i);
+ DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str));
+ if (comparator(pattern, et))
+ {
+ /*
+ The queue is ordered. If we remove an element, then all elements
+ after it will shift one position to the left, if we imagine it as
+ an array from left to the right. In this case we should not
+ increment the counter and the (i <= queue_last_element() condition
+ is ok.
+ */
+ queue_remove(&queue, i);
+ delete et;
+ }
+ else
+ i++;
+ }
+ /*
+ We don't call mysql_cond_broadcast(&COND_queue_state);
+ If we remove the top event:
+ 1. The queue is empty. The scheduler will wake up at some time and
+ realize that the queue is empty. If create_event() comes inbetween
+ it will signal the scheduler
+ 2. The queue is not empty, but the next event after the previous top,
+ won't be executed any time sooner than the element we removed. Hence,
+ we may not notify the scheduler and it will realize the change when it
+ wakes up from timedwait.
+ */
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Drops all events from the in-memory queue and disk that are from
+ certain schema.
+
+ SYNOPSIS
+ Event_queue::drop_schema_events()
+ thd HD
+ schema The schema name
+*/
+
+void
+Event_queue::drop_schema_events(THD *thd, LEX_STRING schema)
+{
+ DBUG_ENTER("Event_queue::drop_schema_events");
+ LOCK_QUEUE_DATA();
+ drop_matching_events(thd, schema, event_basic_db_equal);
+ UNLOCK_QUEUE_DATA();
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Searches for an event in the queue
+
+ SYNOPSIS
+ Event_queue::find_n_remove_event()
+ db The schema of the event to find
+ name The event to find
+
+ NOTE
+ The caller should do the locking also the caller is responsible for
+ actual signalling in case an event is removed from the queue.
+*/
+
+void
+Event_queue::find_n_remove_event(LEX_STRING db, LEX_STRING name)
+{
+ uint i;
+ DBUG_ENTER("Event_queue::find_n_remove_event");
+
+ for (i= queue_first_element(&queue);
+ i <= queue_last_element(&queue);
+ i++)
+ {
+ Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i);
+ DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", db.str, name.str,
+ et->dbname.str, et->name.str));
+ if (event_basic_identifier_equal(db, name, et))
+ {
+ queue_remove(&queue, i);
+ delete et;
+ break;
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Recalculates activation times in the queue. There is one reason for
+ that. Because the values (execute_at) by which the queue is ordered are
+ changed by calls to compute_next_execution_time() on a request from the
+ scheduler thread, if it is not running then the values won't be updated.
+ Once the scheduler is started again the values has to be recalculated
+ so they are right for the current time.
+
+ SYNOPSIS
+ Event_queue::recalculate_activation_times()
+ thd Thread
+*/
+
+void
+Event_queue::recalculate_activation_times(THD *thd)
+{
+ uint i;
+ DBUG_ENTER("Event_queue::recalculate_activation_times");
+
+ LOCK_QUEUE_DATA();
+ DBUG_PRINT("info", ("%u loaded events to be recalculated", queue.elements));
+ for (i= queue_first_element(&queue);
+ i <= queue_last_element(&queue);
+ i++)
+ {
+ ((Event_queue_element*)queue_element(&queue, i))->compute_next_execution_time();
+ }
+ queue_fix(&queue);
+ /*
+ The disabled elements are moved to the end during the `fix`.
+ Start from the end and remove all of the elements which are
+ disabled. When we find the first non-disabled one we break, as we
+ have removed all. The queue has been ordered in a way the disabled
+ events are at the end.
+ */
+ for (i= queue_last_element(&queue);
+ (int) i >= (int) queue_first_element(&queue);
+ i--)
+ {
+ Event_queue_element *element=
+ (Event_queue_element*)queue_element(&queue, i);
+ if (element->status != Event_parse_data::DISABLED)
+ break;
+ /*
+ This won't cause queue re-order, because we remove
+ always the last element.
+ */
+ queue_remove(&queue, i);
+ delete element;
+ }
+ UNLOCK_QUEUE_DATA();
+
+ /*
+ XXX: The events are dropped only from memory and not from disk
+ even if `drop_list[j]->dropped` is TRUE. There will be still on the
+ disk till next server restart.
+ Please add code here to do it.
+ */
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Empties the queue and destroys the Event_queue_element objects in the
+ queue.
+
+ SYNOPSIS
+ Event_queue::empty_queue()
+
+ NOTE
+ Should be called with LOCK_event_queue locked
+*/
+
+void
+Event_queue::empty_queue()
+{
+ uint i;
+ DBUG_ENTER("Event_queue::empty_queue");
+ DBUG_PRINT("enter", ("Purging the queue. %u element(s)", queue.elements));
+ sql_print_information("Event Scheduler: Purging the queue. %u events",
+ queue.elements);
+ /* empty the queue */
+ for (i= queue_first_element(&queue);
+ i <= queue_last_element(&queue);
+ i++)
+ {
+ Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i);
+ delete et;
+ }
+ resize_queue(&queue, 0);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Dumps the queue to the trace log.
+
+ SYNOPSIS
+ Event_queue::dbug_dump_queue()
+ now Current timestamp
+*/
+
+void
+Event_queue::dbug_dump_queue(my_time_t when)
+{
+#ifndef DBUG_OFF
+ my_time_t now= when;
+ Event_queue_element *et;
+ uint i;
+ DBUG_ENTER("Event_queue::dbug_dump_queue");
+ DBUG_PRINT("info", ("Dumping queue . Elements=%u", queue.elements));
+ for (i= queue_first_element(&queue);
+ i <= queue_last_element(&queue);
+ i++)
+ {
+ et= ((Event_queue_element*)queue_element(&queue, i));
+ DBUG_PRINT("info", ("et: 0x%lx name: %s.%s", (long) et,
+ et->dbname.str, et->name.str));
+ DBUG_PRINT("info", ("exec_at: %lu starts: %lu ends: %lu execs_so_far: %u "
+ "expr: %ld et.exec_at: %ld now: %ld "
+ "(et.exec_at - now): %d if: %d",
+ (long) et->execute_at, (long) et->starts,
+ (long) et->ends, et->execution_count,
+ (long) et->expression, (long) et->execute_at,
+ (long) now, (int) (et->execute_at - now),
+ et->execute_at <= now));
+ }
+ DBUG_VOID_RETURN;
+#endif
+}
+
+/*
+ Checks whether the top of the queue is elligible for execution and
+ returns an Event_job_data instance in case it should be executed.
+ `now` is compared against `execute_at` of the top element in the queue.
+
+ SYNOPSIS
+ Event_queue::get_top_for_execution_if_time()
+ thd [in] Thread
+ event_name [out] The object to execute
+
+ RETURN VALUE
+ FALSE No error. event_name != NULL
+ TRUE Serious error
+*/
+
+bool
+Event_queue::get_top_for_execution_if_time(THD *thd,
+ Event_queue_element_for_exec **event_name)
+{
+ bool ret= FALSE;
+ *event_name= NULL;
+ my_time_t UNINIT_VAR(last_executed);
+ int UNINIT_VAR(status);
+ DBUG_ENTER("Event_queue::get_top_for_execution_if_time");
+
+ LOCK_QUEUE_DATA();
+ for (;;)
+ {
+ Event_queue_element *top= NULL;
+
+ /* Break loop if thd has been killed */
+ if (thd->killed)
+ {
+ DBUG_PRINT("info", ("thd->killed=%d", thd->killed));
+ goto end;
+ }
+
+ if (!queue.elements)
+ {
+ /* There are no events in the queue */
+ next_activation_at= 0;
+
+ /* Release any held audit resources before waiting */
+ mysql_audit_release(thd);
+
+ /* Wait on condition until signaled. Release LOCK_queue while waiting. */
+ cond_wait(thd, NULL, & stage_waiting_on_empty_queue, SCHED_FUNC, __FILE__, __LINE__);
+
+ continue;
+ }
+
+ top= (Event_queue_element*) queue_top(&queue);
+
+ thd->set_current_time(); /* Get current time */
+
+ next_activation_at= top->execute_at;
+ if (next_activation_at > thd->query_start())
+ {
+ /*
+ Not yet time for top event, wait on condition with
+ time or until signaled. Release LOCK_queue while waiting.
+ */
+ struct timespec top_time= { next_activation_at, 0 };
+
+ /* Release any held audit resources before waiting */
+ mysql_audit_release(thd);
+
+ cond_wait(thd, &top_time, &stage_waiting_for_next_activation, SCHED_FUNC, __FILE__, __LINE__);
+
+ continue;
+ }
+
+ if (!(*event_name= new Event_queue_element_for_exec()) ||
+ (*event_name)->init(top->dbname, top->name))
+ {
+ ret= TRUE;
+ break;
+ }
+
+ DBUG_PRINT("info", ("Ready for execution"));
+ top->mark_last_executed(thd);
+ if (top->compute_next_execution_time())
+ top->status= Event_parse_data::DISABLED;
+ DBUG_PRINT("info", ("event %s status is %d", top->name.str, top->status));
+
+ top->execution_count++;
+ (*event_name)->dropped= top->dropped;
+ /*
+ Save new values of last_executed timestamp and event status on stack
+ in order to be able to update event description in system table once
+ QUEUE_DATA lock is released.
+ */
+ last_executed= top->last_executed;
+ status= top->status;
+
+ if (top->status == Event_parse_data::DISABLED)
+ {
+ DBUG_PRINT("info", ("removing from the queue"));
+ sql_print_information("Event Scheduler: Last execution of %s.%s. %s",
+ top->dbname.str, top->name.str,
+ top->dropped? "Dropping.":"");
+ delete top;
+ queue_remove_top(&queue);
+ }
+ else
+ queue_replace_top(&queue);
+
+ dbug_dump_queue(thd->query_start());
+ break;
+ }
+end:
+ UNLOCK_QUEUE_DATA();
+
+ DBUG_PRINT("info", ("returning %d et_new: 0x%lx ",
+ ret, (long) *event_name));
+
+ if (*event_name)
+ {
+ DBUG_PRINT("info", ("db: %s name: %s",
+ (*event_name)->dbname.str, (*event_name)->name.str));
+
+ Event_db_repository *db_repository= Events::get_db_repository();
+ (void) db_repository->update_timing_fields_for_event(thd,
+ (*event_name)->dbname, (*event_name)->name,
+ last_executed, (ulonglong) status);
+ }
+
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Auxiliary function for locking LOCK_event_queue. Used by the
+ LOCK_QUEUE_DATA macro
+
+ SYNOPSIS
+ Event_queue::lock_data()
+ func Which function is requesting mutex lock
+ line On which line mutex lock is requested
+*/
+
+void
+Event_queue::lock_data(const char *func, uint line)
+{
+ DBUG_ENTER("Event_queue::lock_data");
+ DBUG_PRINT("enter", ("func=%s line=%u", func, line));
+ mutex_last_attempted_lock_in_func= func;
+ mutex_last_attempted_lock_at_line= line;
+ mutex_queue_data_attempting_lock= TRUE;
+ mysql_mutex_lock(&LOCK_event_queue);
+ mutex_last_attempted_lock_in_func= "";
+ mutex_last_attempted_lock_at_line= 0;
+ mutex_queue_data_attempting_lock= FALSE;
+
+ mutex_last_locked_in_func= func;
+ mutex_last_locked_at_line= line;
+ mutex_queue_data_locked= TRUE;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Auxiliary function for unlocking LOCK_event_queue. Used by the
+ UNLOCK_QUEUE_DATA macro
+
+ SYNOPSIS
+ Event_queue::unlock_data()
+ func Which function is requesting mutex unlock
+ line On which line mutex unlock is requested
+*/
+
+void
+Event_queue::unlock_data(const char *func, uint line)
+{
+ DBUG_ENTER("Event_queue::unlock_data");
+ DBUG_PRINT("enter", ("func=%s line=%u", func, line));
+ mutex_last_unlocked_at_line= line;
+ mutex_queue_data_locked= FALSE;
+ mutex_last_unlocked_in_func= func;
+ mysql_mutex_unlock(&LOCK_event_queue);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Wrapper for mysql_cond_wait/timedwait
+
+ SYNOPSIS
+ Event_queue::cond_wait()
+ thd Thread (Could be NULL during shutdown procedure)
+ msg Message for thd->proc_info
+ abstime If not null then call mysql_cond_timedwait()
+ func Which function is requesting cond_wait
+ line On which line cond_wait is requested
+*/
+
+void
+Event_queue::cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage,
+ const char *src_func, const char *src_file, uint src_line)
+{
+ DBUG_ENTER("Event_queue::cond_wait");
+ waiting_on_cond= TRUE;
+ mutex_last_unlocked_at_line= src_line;
+ mutex_queue_data_locked= FALSE;
+ mutex_last_unlocked_in_func= src_func;
+
+ thd->enter_cond(&COND_queue_state, &LOCK_event_queue, stage, NULL, src_func, src_file, src_line);
+
+ if (!thd->killed)
+ {
+ DBUG_PRINT("info", ("pthread_cond_%swait", abstime ? "timed" : ""));
+ if (!abstime)
+ mysql_cond_wait(&COND_queue_state, &LOCK_event_queue);
+ else
+ mysql_cond_timedwait(&COND_queue_state, &LOCK_event_queue, abstime);
+ }
+
+ mutex_last_locked_in_func= src_func;
+ mutex_last_locked_at_line= src_line;
+ mutex_queue_data_locked= TRUE;
+ waiting_on_cond= FALSE;
+
+ /*
+ This will free the lock so we need to relock. Not the best thing to
+ do but we need to obey cond_wait()
+ */
+ thd->exit_cond(NULL, src_func, src_file, src_line);
+ lock_data(src_func, src_line);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Dumps the internal status of the queue
+
+ SYNOPSIS
+ Event_queue::dump_internal_status()
+*/
+
+void
+Event_queue::dump_internal_status()
+{
+ DBUG_ENTER("Event_queue::dump_internal_status");
+
+ /* element count */
+ puts("");
+ puts("Event queue status:");
+ printf("Element count : %u\n", queue.elements);
+ printf("Data locked : %s\n", mutex_queue_data_locked? "YES":"NO");
+ printf("Attempting lock : %s\n", mutex_queue_data_attempting_lock? "YES":"NO");
+ printf("LLA : %s:%u\n", mutex_last_locked_in_func,
+ mutex_last_locked_at_line);
+ printf("LUA : %s:%u\n", mutex_last_unlocked_in_func,
+ mutex_last_unlocked_at_line);
+ if (mutex_last_attempted_lock_at_line)
+ printf("Last lock attempt at: %s:%u\n", mutex_last_attempted_lock_in_func,
+ mutex_last_attempted_lock_at_line);
+ printf("WOC : %s\n", waiting_on_cond? "YES":"NO");
+
+ MYSQL_TIME time;
+ my_tz_OFFSET0->gmt_sec_to_TIME(&time, next_activation_at);
+ if (time.year != 1970)
+ printf("Next activation : %04d-%02d-%02d %02d:%02d:%02d\n",
+ time.year, time.month, time.day, time.hour, time.minute, time.second);
+ else
+ printf("Next activation : never");
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ @} (End of group Event_Scheduler)
+*/
diff --git a/sql/event_queue.h b/sql/event_queue.h
new file mode 100644
index 00000000000..fdd5937ee17
--- /dev/null
+++ b/sql/event_queue.h
@@ -0,0 +1,135 @@
+#ifndef _EVENT_QUEUE_H_
+#define _EVENT_QUEUE_H_
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+/**
+
+ @addtogroup Event_Scheduler
+ @{
+
+ @file event_queue.h
+
+ Queue of events awaiting execution.
+*/
+
+#ifdef HAVE_PSI_INTERFACE
+extern PSI_mutex_key key_LOCK_event_queue;
+extern PSI_cond_key key_COND_queue_state;
+#endif /* HAVE_PSI_INTERFACE */
+
+#include "queues.h" // QUEUE
+#include "sql_string.h" /* LEX_STRING */
+#include "my_time.h" /* my_time_t, interval_type */
+
+class Event_basic;
+class Event_queue_element;
+class Event_queue_element_for_exec;
+
+class THD;
+
+/**
+ Queue of active events awaiting execution.
+*/
+
+class Event_queue
+{
+public:
+ Event_queue();
+ ~Event_queue();
+
+ bool
+ init_queue(THD *thd);
+
+ /* Methods for queue management follow */
+
+ bool
+ create_event(THD *thd, Event_queue_element *new_element,
+ bool *created);
+
+ void
+ update_event(THD *thd, LEX_STRING dbname, LEX_STRING name,
+ Event_queue_element *new_element);
+
+ void
+ drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name);
+
+ void
+ drop_schema_events(THD *thd, LEX_STRING schema);
+
+ void
+ recalculate_activation_times(THD *thd);
+
+ bool
+ get_top_for_execution_if_time(THD *thd,
+ Event_queue_element_for_exec **event_name);
+
+
+ void
+ dump_internal_status();
+
+private:
+ void
+ empty_queue();
+
+ void
+ deinit_queue();
+ /* helper functions for working with mutexes & conditionals */
+ void
+ lock_data(const char *func, uint line);
+
+ void
+ unlock_data(const char *func, uint line);
+
+ void
+ cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage,
+ const char *src_func, const char *src_file, uint src_line);
+
+ void
+ find_n_remove_event(LEX_STRING db, LEX_STRING name);
+
+
+ void
+ drop_matching_events(THD *thd, LEX_STRING pattern,
+ bool (*)(LEX_STRING, Event_basic *));
+
+
+ void
+ dbug_dump_queue(my_time_t now);
+
+ /* LOCK_event_queue is the mutex which protects the access to the queue. */
+ mysql_mutex_t LOCK_event_queue;
+ mysql_cond_t COND_queue_state;
+
+ /* The sorted queue with the Event_queue_element objects */
+ QUEUE queue;
+
+ my_time_t next_activation_at;
+
+ uint mutex_last_locked_at_line;
+ uint mutex_last_unlocked_at_line;
+ uint mutex_last_attempted_lock_at_line;
+ const char* mutex_last_locked_in_func;
+ const char* mutex_last_unlocked_in_func;
+ const char* mutex_last_attempted_lock_in_func;
+ bool mutex_queue_data_locked;
+ bool mutex_queue_data_attempting_lock;
+ bool waiting_on_cond;
+};
+/**
+ @} (End of group Event_Scheduler)
+*/
+
+#endif /* _EVENT_QUEUE_H_ */
diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc
new file mode 100644
index 00000000000..5c4926c830c
--- /dev/null
+++ b/sql/event_scheduler.cc
@@ -0,0 +1,850 @@
+/* Copyright (c) 2006, 2013, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "event_scheduler.h"
+#include "events.h"
+#include "event_data_objects.h"
+#include "event_queue.h"
+#include "event_db_repository.h"
+#include "sql_connect.h" // init_new_connection_handler_thread
+#include "sql_acl.h" // SUPER_ACL
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+*/
+
+#ifdef __GNUC__
+#if __GNUC__ >= 2
+#define SCHED_FUNC __FUNCTION__
+#endif
+#else
+#define SCHED_FUNC "<unknown>"
+#endif
+
+#define LOCK_DATA() lock_data(SCHED_FUNC, __LINE__)
+#define UNLOCK_DATA() unlock_data(SCHED_FUNC, __LINE__)
+#define COND_STATE_WAIT(mythd, abstime, stage) \
+ cond_wait(mythd, abstime, stage, SCHED_FUNC, __FILE__, __LINE__)
+
+extern pthread_attr_t connection_attrib;
+extern ulong event_executed;
+
+Event_db_repository *Event_worker_thread::db_repository;
+
+
+static
+const LEX_STRING scheduler_states_names[] =
+{
+ { C_STRING_WITH_LEN("INITIALIZED") },
+ { C_STRING_WITH_LEN("RUNNING") },
+ { C_STRING_WITH_LEN("STOPPING") }
+};
+
+struct scheduler_param {
+ THD *thd;
+ Event_scheduler *scheduler;
+};
+
+
+/*
+ Prints the stack of infos, warnings, errors from thd to
+ the console so it can be fetched by the logs-into-tables and
+ checked later.
+
+ SYNOPSIS
+ evex_print_warnings
+ thd Thread used during the execution of the event
+ et The event itself
+*/
+
+void
+Event_worker_thread::print_warnings(THD *thd, Event_job_data *et)
+{
+ const Sql_condition *err;
+ DBUG_ENTER("evex_print_warnings");
+ if (thd->get_stmt_da()->is_warning_info_empty())
+ DBUG_VOID_RETURN;
+
+ char msg_buf[10 * STRING_BUFFER_USUAL_SIZE];
+ char prefix_buf[5 * STRING_BUFFER_USUAL_SIZE];
+ String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info);
+ prefix.length(0);
+ prefix.append("Event Scheduler: [");
+
+ prefix.append(et->definer.str, et->definer.length, system_charset_info);
+ prefix.append("][", 2);
+ prefix.append(et->dbname.str, et->dbname.length, system_charset_info);
+ prefix.append('.');
+ prefix.append(et->name.str, et->name.length, system_charset_info);
+ prefix.append("] ", 2);
+
+ Diagnostics_area::Sql_condition_iterator it=
+ thd->get_stmt_da()->sql_conditions();
+ while ((err= it++))
+ {
+ String err_msg(msg_buf, sizeof(msg_buf), system_charset_info);
+ /* set it to 0 or we start adding at the end. That's the trick ;) */
+ err_msg.length(0);
+ err_msg.append(prefix);
+ err_msg.append(err->get_message_text(),
+ err->get_message_octet_length(), system_charset_info);
+ DBUG_ASSERT(err->get_level() < 3);
+ (sql_print_message_handlers[err->get_level()])("%*s", err_msg.length(),
+ err_msg.c_ptr_safe());
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Performs post initialization of structures in a new thread.
+
+ SYNOPSIS
+ post_init_event_thread()
+ thd Thread
+
+ NOTES
+ Before this is called, one should not do any DBUG_XXX() calls.
+
+*/
+
+bool
+post_init_event_thread(THD *thd)
+{
+ (void) init_new_connection_handler_thread();
+ if (init_thr_lock() || thd->store_globals())
+ {
+ thd->cleanup();
+ return TRUE;
+ }
+
+ thread_safe_increment32(&thread_count, &thread_count_lock);
+ mysql_mutex_lock(&LOCK_thread_count);
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ inc_thread_running();
+ return FALSE;
+}
+
+
+/*
+ Cleans up the THD and the threaded environment of the thread.
+
+ SYNOPSIS
+ deinit_event_thread()
+ thd Thread
+*/
+
+void
+deinit_event_thread(THD *thd)
+{
+ thd->proc_info= "Clearing";
+ DBUG_PRINT("exit", ("Event thread finishing"));
+
+ delete_running_thd(thd);
+}
+
+
+/*
+ Performs pre- mysql_thread_create() initialisation of THD. Do this
+ in the thread that will pass THD to the child thread. In the
+ child thread call post_init_event_thread().
+
+ SYNOPSIS
+ pre_init_event_thread()
+ thd The THD of the thread. Has to be allocated by the caller.
+
+ NOTES
+ 1. The host of the thead is my_localhost
+ 2. thd->net is initted with NULL - no communication.
+*/
+
+void
+pre_init_event_thread(THD* thd)
+{
+ THD *orig_thd= current_thd;
+ DBUG_ENTER("pre_init_event_thread");
+
+ set_current_thd(thd);
+ thd->client_capabilities= 0;
+ thd->security_ctx->master_access= 0;
+ thd->security_ctx->db_access= 0;
+ thd->security_ctx->host_or_ip= (char*)my_localhost;
+ my_net_init(&thd->net, NULL, MYF(MY_THREAD_SPECIFIC));
+ thd->security_ctx->set_user((char*)"event_scheduler");
+ thd->net.read_timeout= slave_net_timeout;
+ thd->variables.option_bits|= OPTION_AUTO_IS_NULL;
+ thd->client_capabilities|= CLIENT_MULTI_RESULTS;
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ /*
+ Guarantees that we will see the thread in SHOW PROCESSLIST though its
+ vio is NULL.
+ */
+
+ thd->proc_info= "Initialized";
+ thd->set_time();
+
+ /* Do not use user-supplied timeout value for system threads. */
+ thd->variables.lock_wait_timeout= LONG_TIMEOUT;
+
+ set_current_thd(orig_thd);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Function that executes the scheduler,
+
+ SYNOPSIS
+ event_scheduler_thread()
+ arg Pointer to `struct scheduler_param`
+
+ RETURN VALUE
+ 0 OK
+*/
+
+pthread_handler_t
+event_scheduler_thread(void *arg)
+{
+ /* needs to be first for thread_stack */
+ THD *thd= (THD *) ((struct scheduler_param *) arg)->thd;
+ Event_scheduler *scheduler= ((struct scheduler_param *) arg)->scheduler;
+ bool res;
+
+ thd->thread_stack= (char *)&thd; // remember where our stack is
+
+ mysql_thread_set_psi_id(thd->thread_id);
+
+ res= post_init_event_thread(thd);
+
+ DBUG_ENTER("event_scheduler_thread");
+ my_free(arg);
+ if (!res)
+ scheduler->run(thd);
+ else
+ {
+ thd->proc_info= "Clearing";
+ net_end(&thd->net);
+ delete thd;
+ }
+
+ DBUG_LEAVE; // Against gcc warnings
+ my_thread_end();
+ return 0;
+}
+
+
+/**
+ Function that executes an event in a child thread. Setups the
+ environment for the event execution and cleans after that.
+
+ SYNOPSIS
+ event_worker_thread()
+ arg The Event_job_data object to be processed
+
+ RETURN VALUE
+ 0 OK
+*/
+
+pthread_handler_t
+event_worker_thread(void *arg)
+{
+ THD *thd;
+ Event_queue_element_for_exec *event= (Event_queue_element_for_exec *)arg;
+
+ thd= event->thd;
+
+ mysql_thread_set_psi_id(thd->thread_id);
+
+ Event_worker_thread worker_thread;
+ worker_thread.run(thd, event);
+
+ my_thread_end();
+ return 0; // Can't return anything here
+}
+
+
+/**
+ Function that executes an event in a child thread. Setups the
+ environment for the event execution and cleans after that.
+
+ SYNOPSIS
+ Event_worker_thread::run()
+ thd Thread context
+ event The Event_queue_element_for_exec object to be processed
+*/
+
+void
+Event_worker_thread::run(THD *thd, Event_queue_element_for_exec *event)
+{
+ /* needs to be first for thread_stack */
+ char my_stack;
+ Event_job_data job_data;
+ bool res;
+
+ DBUG_ASSERT(thd->m_digest == NULL);
+ DBUG_ASSERT(thd->m_statement_psi == NULL);
+
+ thd->thread_stack= &my_stack; // remember where our stack is
+ res= post_init_event_thread(thd);
+
+ DBUG_ENTER("Event_worker_thread::run");
+ DBUG_PRINT("info", ("Time is %ld, THD: 0x%lx", (long) my_time(0), (long) thd));
+
+ if (res)
+ goto end;
+
+ if ((res= db_repository->load_named_event(thd, event->dbname, event->name,
+ &job_data)))
+ {
+ DBUG_PRINT("error", ("Got error from load_named_event"));
+ goto end;
+ }
+
+ thd->enable_slow_log= TRUE;
+
+ res= job_data.execute(thd, event->dropped);
+
+ print_warnings(thd, &job_data);
+
+ if (res)
+ sql_print_information("Event Scheduler: "
+ "[%s].[%s.%s] event execution failed.",
+ job_data.definer.str,
+ job_data.dbname.str, job_data.name.str);
+end:
+ DBUG_ASSERT(thd->m_statement_psi == NULL);
+ DBUG_ASSERT(thd->m_digest == NULL);
+ DBUG_PRINT("info", ("Done with Event %s.%s", event->dbname.str,
+ event->name.str));
+
+ delete event;
+ deinit_event_thread(thd);
+
+ DBUG_VOID_RETURN;
+}
+
+
+Event_scheduler::Event_scheduler(Event_queue *queue_arg)
+ :state(INITIALIZED),
+ scheduler_thd(NULL),
+ queue(queue_arg),
+ mutex_last_locked_at_line(0),
+ mutex_last_unlocked_at_line(0),
+ mutex_last_locked_in_func("n/a"),
+ mutex_last_unlocked_in_func("n/a"),
+ mutex_scheduler_data_locked(FALSE),
+ waiting_on_cond(FALSE),
+ started_events(0)
+{
+ mysql_mutex_init(key_event_scheduler_LOCK_scheduler_state,
+ &LOCK_scheduler_state, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_event_scheduler_COND_state, &COND_state, NULL);
+ mysql_mutex_record_order(&LOCK_scheduler_state, &LOCK_global_system_variables);
+}
+
+
+Event_scheduler::~Event_scheduler()
+{
+ stop(); /* does nothing if not running */
+ mysql_mutex_destroy(&LOCK_scheduler_state);
+ mysql_cond_destroy(&COND_state);
+}
+
+
+/**
+ Starts the scheduler (again). Creates a new THD and passes it to
+ a forked thread. Does not wait for acknowledgement from the new
+ thread that it has started. Asynchronous starting. Most of the
+ needed initializations are done in the current thread to minimize
+ the chance of failure in the spawned thread.
+
+ @param[out] err_no - errno indicating type of error which caused
+ failure to start scheduler thread.
+
+ @return
+ @retval false Success.
+ @retval true Error.
+*/
+
+bool
+Event_scheduler::start(int *err_no)
+{
+ THD *new_thd= NULL;
+ bool ret= false;
+ pthread_t th;
+ struct scheduler_param *scheduler_param_value;
+ DBUG_ENTER("Event_scheduler::start");
+
+ LOCK_DATA();
+ DBUG_PRINT("info", ("state before action %s", scheduler_states_names[state].str));
+ if (state > INITIALIZED)
+ goto end;
+
+ if (!(new_thd= new THD))
+ {
+ sql_print_error("Event Scheduler: Cannot initialize the scheduler thread");
+ ret= true;
+ goto end;
+ }
+
+ pre_init_event_thread(new_thd);
+ new_thd->system_thread= SYSTEM_THREAD_EVENT_SCHEDULER;
+ new_thd->set_command(COM_DAEMON);
+
+ /*
+ We should run the event scheduler thread under the super-user privileges.
+ In particular, this is needed to be able to lock the mysql.event table
+ for writing when the server is running in the read-only mode.
+
+ Same goes for transaction access mode. Set it to read-write for this thd.
+ */
+ new_thd->security_ctx->master_access |= SUPER_ACL;
+ new_thd->variables.tx_read_only= false;
+ new_thd->tx_read_only= false;
+
+ /* This should not be marked with MY_THREAD_SPECIFIC */
+ scheduler_param_value=
+ (struct scheduler_param *)my_malloc(sizeof(struct scheduler_param), MYF(0));
+ scheduler_param_value->thd= new_thd;
+ scheduler_param_value->scheduler= this;
+
+ scheduler_thd= new_thd;
+ DBUG_PRINT("info", ("Setting state go RUNNING"));
+ state= RUNNING;
+ DBUG_PRINT("info", ("Forking new thread for scheduler. THD: 0x%lx", (long) new_thd));
+ if ((*err_no= mysql_thread_create(key_thread_event_scheduler,
+ &th, &connection_attrib,
+ event_scheduler_thread,
+ (void*)scheduler_param_value)))
+ {
+ DBUG_PRINT("error", ("cannot create a new thread"));
+ sql_print_error("Event scheduler: Failed to start scheduler,"
+ " Can not create thread for event scheduler (errno=%d)",
+ *err_no);
+
+ new_thd->proc_info= "Clearing";
+ DBUG_ASSERT(new_thd->net.buff != 0);
+ net_end(&new_thd->net);
+
+ state= INITIALIZED;
+ scheduler_thd= NULL;
+ delete new_thd;
+
+ delete scheduler_param_value;
+ ret= true;
+ }
+
+end:
+ UNLOCK_DATA();
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ The main loop of the scheduler.
+
+ SYNOPSIS
+ Event_scheduler::run()
+ thd Thread
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error (Serious error)
+*/
+
+bool
+Event_scheduler::run(THD *thd)
+{
+ int res= FALSE;
+ DBUG_ENTER("Event_scheduler::run");
+
+ sql_print_information("Event Scheduler: scheduler thread started with id %lu",
+ thd->thread_id);
+ /*
+ Recalculate the values in the queue because there could have been stops
+ in executions of the scheduler and some times could have passed by.
+ */
+ queue->recalculate_activation_times(thd);
+
+ while (is_running())
+ {
+ Event_queue_element_for_exec *event_name;
+
+ /* Gets a minimized version */
+ if (queue->get_top_for_execution_if_time(thd, &event_name))
+ {
+ sql_print_information("Event Scheduler: "
+ "Serious error during getting next "
+ "event to execute. Stopping");
+ break;
+ }
+
+ DBUG_PRINT("info", ("get_top_for_execution_if_time returned "
+ "event_name=0x%lx", (long) event_name));
+ if (event_name)
+ {
+ if ((res= execute_top(event_name)))
+ break;
+ }
+ else
+ {
+ DBUG_ASSERT(thd->killed);
+ DBUG_PRINT("info", ("job_data is NULL, the thread was killed"));
+ }
+ DBUG_PRINT("info", ("state=%s", scheduler_states_names[state].str));
+ }
+
+ LOCK_DATA();
+ deinit_event_thread(thd);
+ scheduler_thd= NULL;
+ state= INITIALIZED;
+ DBUG_PRINT("info", ("Broadcasting COND_state back to the stoppers"));
+ mysql_cond_broadcast(&COND_state);
+ UNLOCK_DATA();
+
+ DBUG_RETURN(res);
+}
+
+
+/*
+ Creates a new THD instance and then forks a new thread, while passing
+ the THD pointer and job_data to it.
+
+ SYNOPSIS
+ Event_scheduler::execute_top()
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error (Serious error)
+*/
+
+bool
+Event_scheduler::execute_top(Event_queue_element_for_exec *event_name)
+{
+ THD *new_thd;
+ pthread_t th;
+ int res= 0;
+ DBUG_ENTER("Event_scheduler::execute_top");
+
+ if (!(new_thd= new THD()))
+ goto error;
+
+ pre_init_event_thread(new_thd);
+ new_thd->system_thread= SYSTEM_THREAD_EVENT_WORKER;
+ event_name->thd= new_thd;
+ DBUG_PRINT("info", ("Event %s@%s ready for start",
+ event_name->dbname.str, event_name->name.str));
+
+ /*
+ TODO: should use thread pool here, preferably with an upper limit
+ on number of threads: if too many events are scheduled for the
+ same time, starting all of them at once won't help them run truly
+ in parallel (because of the great amount of synchronization), so
+ we may as well execute them in sequence, keeping concurrency at a
+ reasonable level.
+ */
+ /* Major failure */
+ if ((res= mysql_thread_create(key_thread_event_worker,
+ &th, &connection_attrib, event_worker_thread,
+ event_name)))
+ {
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ Events::opt_event_scheduler= Events::EVENTS_OFF;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ sql_print_error("Event_scheduler::execute_top: Can not create event worker"
+ " thread (errno=%d). Stopping event scheduler", res);
+
+ new_thd->proc_info= "Clearing";
+ DBUG_ASSERT(new_thd->net.buff != 0);
+ net_end(&new_thd->net);
+
+ goto error;
+ }
+
+ started_events++;
+ executed_events++; // For SHOW STATUS
+
+ DBUG_PRINT("info", ("Event is in THD: 0x%lx", (long) new_thd));
+ DBUG_RETURN(FALSE);
+
+error:
+ DBUG_PRINT("error", ("Event_scheduler::execute_top() res: %d", res));
+ if (new_thd)
+ delete new_thd;
+
+ delete event_name;
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Checks whether the state of the scheduler is RUNNING
+
+ SYNOPSIS
+ Event_scheduler::is_running()
+
+ RETURN VALUE
+ TRUE RUNNING
+ FALSE Not RUNNING
+*/
+
+bool
+Event_scheduler::is_running()
+{
+ LOCK_DATA();
+ bool ret= (state == RUNNING);
+ UNLOCK_DATA();
+ return ret;
+}
+
+
+/**
+ Stops the scheduler (again). Waits for acknowledgement from the
+ scheduler that it has stopped - synchronous stopping.
+
+ Already running events will not be stopped. If the user needs
+ them stopped manual intervention is needed.
+
+ SYNOPSIS
+ Event_scheduler::stop()
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error (not reported)
+*/
+
+bool
+Event_scheduler::stop()
+{
+ THD *thd= current_thd;
+ DBUG_ENTER("Event_scheduler::stop");
+ DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd));
+
+ LOCK_DATA();
+ DBUG_PRINT("info", ("state before action %s", scheduler_states_names[state].str));
+ if (state != RUNNING)
+ {
+ /* Synchronously wait until the scheduler stops. */
+ while (state != INITIALIZED)
+ COND_STATE_WAIT(thd, NULL, &stage_waiting_for_scheduler_to_stop);
+ goto end;
+ }
+
+ /* Guarantee we don't catch spurious signals */
+ do {
+ DBUG_PRINT("info", ("Waiting for COND_started_or_stopped from "
+ "the scheduler thread. Current value of state is %s . "
+ "workers count=%d", scheduler_states_names[state].str,
+ workers_count()));
+ /*
+ NOTE: We don't use kill_one_thread() because it can't kill COM_DEAMON
+ threads. In addition, kill_one_thread() requires THD but during shutdown
+ current_thd is NULL. Hence, if kill_one_thread should be used it has to
+ be modified to kill also daemons, by adding a flag, and also we have to
+ create artificial THD here. To save all this work, we just do what
+ kill_one_thread() does to kill a thread. See also sql_repl.cc for similar
+ usage.
+ */
+
+ state= STOPPING;
+ DBUG_PRINT("info", ("Scheduler thread has id %lu",
+ scheduler_thd->thread_id));
+ /* Lock from delete */
+ mysql_mutex_lock(&scheduler_thd->LOCK_thd_data);
+ /* This will wake up the thread if it waits on Queue's conditional */
+ sql_print_information("Event Scheduler: Killing the scheduler thread, "
+ "thread id %lu",
+ scheduler_thd->thread_id);
+ scheduler_thd->awake(KILL_CONNECTION);
+ mysql_mutex_unlock(&scheduler_thd->LOCK_thd_data);
+
+ /* thd could be 0x0, when shutting down */
+ sql_print_information("Event Scheduler: "
+ "Waiting for the scheduler thread to reply");
+
+ /*
+ Wait only 2 seconds, as there is a small chance the thread missed the
+ above awake() call and we may have to do it again
+ */
+ struct timespec top_time;
+ set_timespec(top_time, 2);
+ COND_STATE_WAIT(thd, &top_time, &stage_waiting_for_scheduler_to_stop);
+ } while (state == STOPPING);
+ DBUG_PRINT("info", ("Scheduler thread has cleaned up. Set state to INIT"));
+ sql_print_information("Event Scheduler: Stopped");
+end:
+ UNLOCK_DATA();
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Returns the number of living event worker threads.
+
+ SYNOPSIS
+ Event_scheduler::workers_count()
+*/
+
+uint
+Event_scheduler::workers_count()
+{
+ THD *tmp;
+ uint count= 0;
+
+ DBUG_ENTER("Event_scheduler::workers_count");
+ mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
+ I_List_iterator<THD> it(threads);
+ while ((tmp=it++))
+ if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER)
+ ++count;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_PRINT("exit", ("%d", count));
+ DBUG_RETURN(count);
+}
+
+
+/*
+ Auxiliary function for locking LOCK_scheduler_state. Used
+ by the LOCK_DATA macro.
+
+ SYNOPSIS
+ Event_scheduler::lock_data()
+ func Which function is requesting mutex lock
+ line On which line mutex lock is requested
+*/
+
+void
+Event_scheduler::lock_data(const char *func, uint line)
+{
+ DBUG_ENTER("Event_scheduler::lock_data");
+ DBUG_PRINT("enter", ("func=%s line=%u", func, line));
+ mysql_mutex_lock(&LOCK_scheduler_state);
+ mutex_last_locked_in_func= func;
+ mutex_last_locked_at_line= line;
+ mutex_scheduler_data_locked= TRUE;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Auxiliary function for unlocking LOCK_scheduler_state. Used
+ by the UNLOCK_DATA macro.
+
+ SYNOPSIS
+ Event_scheduler::unlock_data()
+ func Which function is requesting mutex unlock
+ line On which line mutex unlock is requested
+*/
+
+void
+Event_scheduler::unlock_data(const char *func, uint line)
+{
+ DBUG_ENTER("Event_scheduler::unlock_data");
+ DBUG_PRINT("enter", ("func=%s line=%u", func, line));
+ mutex_last_unlocked_at_line= line;
+ mutex_scheduler_data_locked= FALSE;
+ mutex_last_unlocked_in_func= func;
+ mysql_mutex_unlock(&LOCK_scheduler_state);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Wrapper for mysql_cond_wait/timedwait
+
+ SYNOPSIS
+ Event_scheduler::cond_wait()
+ thd Thread (Could be NULL during shutdown procedure)
+ abstime If not null then call mysql_cond_timedwait()
+ msg Message for thd->proc_info
+ func Which function is requesting cond_wait
+ line On which line cond_wait is requested
+*/
+
+void
+Event_scheduler::cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage,
+ const char *src_func, const char *src_file, uint src_line)
+{
+ DBUG_ENTER("Event_scheduler::cond_wait");
+ waiting_on_cond= TRUE;
+ mutex_last_unlocked_at_line= src_line;
+ mutex_scheduler_data_locked= FALSE;
+ mutex_last_unlocked_in_func= src_func;
+ if (thd)
+ thd->enter_cond(&COND_state, &LOCK_scheduler_state, stage,
+ NULL, src_func, src_file, src_line);
+
+ DBUG_PRINT("info", ("mysql_cond_%swait", abstime? "timed":""));
+ if (!abstime)
+ mysql_cond_wait(&COND_state, &LOCK_scheduler_state);
+ else
+ mysql_cond_timedwait(&COND_state, &LOCK_scheduler_state, abstime);
+ if (thd)
+ {
+ /*
+ This will free the lock so we need to relock. Not the best thing to
+ do but we need to obey cond_wait()
+ */
+ thd->exit_cond(NULL, src_func, src_file, src_line);
+ LOCK_DATA();
+ }
+ mutex_last_locked_in_func= src_func;
+ mutex_last_locked_at_line= src_line;
+ mutex_scheduler_data_locked= TRUE;
+ waiting_on_cond= FALSE;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Dumps the internal status of the scheduler
+
+ SYNOPSIS
+ Event_scheduler::dump_internal_status()
+*/
+
+void
+Event_scheduler::dump_internal_status()
+{
+ DBUG_ENTER("Event_scheduler::dump_internal_status");
+
+ puts("");
+ puts("Event scheduler status:");
+ printf("State : %s\n", scheduler_states_names[state].str);
+ printf("Thread id : %lu\n", scheduler_thd? scheduler_thd->thread_id : 0);
+ printf("LLA : %s:%u\n", mutex_last_locked_in_func,
+ mutex_last_locked_at_line);
+ printf("LUA : %s:%u\n", mutex_last_unlocked_in_func,
+ mutex_last_unlocked_at_line);
+ printf("WOC : %s\n", waiting_on_cond? "YES":"NO");
+ printf("Workers : %u\n", workers_count());
+ printf("Executed : %lu\n", (ulong) started_events);
+ printf("Data locked: %s\n", mutex_scheduler_data_locked ? "YES":"NO");
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ @} (End of group Event_Scheduler)
+*/
diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h
new file mode 100644
index 00000000000..6ec7dccefb9
--- /dev/null
+++ b/sql/event_scheduler.h
@@ -0,0 +1,157 @@
+#ifndef _EVENT_SCHEDULER_H_
+#define _EVENT_SCHEDULER_H_
+/* Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+*/
+/**
+ @file
+
+ Declarations of the scheduler thread class
+ and related functionality.
+
+ This file is internal to Event_Scheduler module. Please do not
+ include it directly. All public declarations of Event_Scheduler
+ module are in events.h and event_data_objects.h.
+*/
+
+
+class Event_queue;
+class Event_job_data;
+class Event_db_repository;
+class Event_queue_element_for_exec;
+class Events;
+class THD;
+
+void
+pre_init_event_thread(THD* thd);
+
+bool
+post_init_event_thread(THD* thd);
+
+void
+deinit_event_thread(THD *thd);
+
+
+class Event_worker_thread
+{
+public:
+ static void
+ init(Event_db_repository *db_repository_arg)
+ {
+ db_repository= db_repository_arg;
+ }
+
+ void
+ run(THD *thd, Event_queue_element_for_exec *event);
+
+private:
+ void
+ print_warnings(THD *thd, Event_job_data *et);
+
+ static Event_db_repository *db_repository;
+};
+
+
+class Event_scheduler
+{
+public:
+ Event_scheduler(Event_queue *event_queue_arg);
+ ~Event_scheduler();
+
+
+ /* State changing methods follow */
+
+ bool
+ start(int *err_no);
+
+ bool
+ stop();
+
+ /*
+ Need to be public because has to be called from the function
+ passed to pthread_create.
+ */
+ bool
+ run(THD *thd);
+
+
+ /* Information retrieving methods follow */
+ bool
+ is_running();
+
+ void
+ dump_internal_status();
+
+private:
+ uint
+ workers_count();
+
+ /* helper functions */
+ bool
+ execute_top(Event_queue_element_for_exec *event_name);
+
+ /* helper functions for working with mutexes & conditionals */
+ void
+ lock_data(const char *func, uint line);
+
+ void
+ unlock_data(const char *func, uint line);
+
+ void
+ cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage,
+ const char *src_func, const char *src_file, uint src_line);
+
+ mysql_mutex_t LOCK_scheduler_state;
+
+ enum enum_state
+ {
+ INITIALIZED = 0,
+ RUNNING,
+ STOPPING
+ };
+
+ /* This is the current status of the life-cycle of the scheduler. */
+ enum enum_state state;
+
+ THD *scheduler_thd;
+
+ mysql_cond_t COND_state;
+
+ Event_queue *queue;
+
+ uint mutex_last_locked_at_line;
+ uint mutex_last_unlocked_at_line;
+ const char* mutex_last_locked_in_func;
+ const char* mutex_last_unlocked_in_func;
+ bool mutex_scheduler_data_locked;
+ bool waiting_on_cond;
+
+ ulonglong started_events;
+
+private:
+ /* Prevent use of these */
+ Event_scheduler(const Event_scheduler &);
+ void operator=(Event_scheduler &);
+};
+
+/**
+ @} (End of group Event_Scheduler)
+*/
+
+#endif /* _EVENT_SCHEDULER_H_ */
diff --git a/sql/events.cc b/sql/events.cc
new file mode 100644
index 00000000000..cf4c4a8fe75
--- /dev/null
+++ b/sql/events.cc
@@ -0,0 +1,1180 @@
+/*
+ Copyright (c) 2005, 2013, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_parse.h" // check_access
+#include "sql_base.h" // close_mysql_tables
+#include "sql_show.h" // append_definer
+#include "events.h"
+#include "sql_db.h" // check_db_dir_existence
+#include "sql_table.h" // write_bin_log
+#include "tztime.h" // struct Time_zone
+#include "sql_acl.h" // EVENT_ACL
+#include "records.h" // init_read_record, end_read_record
+#include "event_data_objects.h"
+#include "event_db_repository.h"
+#include "event_queue.h"
+#include "event_scheduler.h"
+#include "sp_head.h" // for Stored_program_creation_ctx
+#include "set_var.h"
+#include "lock.h" // lock_object_name
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+*/
+
+/*
+ TODO list :
+ - CREATE EVENT should not go into binary log! Does it now? The SQL statements
+ issued by the EVENT are replicated.
+ I have an idea how to solve the problem at failover. So the status field
+ will be ENUM('DISABLED', 'ENABLED', 'SLAVESIDE_DISABLED').
+ In this case when CREATE EVENT is replicated it should go into the binary
+ as SLAVESIDE_DISABLED if it is ENABLED, when it's created as DISABLEd it
+ should be replicated as disabled. If an event is ALTERed as DISABLED the
+ query should go untouched into the binary log, when ALTERed as enable then
+ it should go as SLAVESIDE_DISABLED. This is regarding the SQL interface.
+ TT routines however modify mysql.event internally and this does not go the
+ log so in this case queries has to be injected into the log...somehow... or
+ maybe a solution is RBR for this case, because the event may go only from
+ ENABLED to DISABLED status change and this is safe for replicating. As well
+ an event may be deleted which is also safe for RBR.
+
+ - Add logging to file
+
+*/
+
+
+/*
+ If the user (un)intentionally removes an event directly from mysql.event
+ the following sequence has to be used to be able to remove the in-memory
+ counterpart.
+ 1. CREATE EVENT the_name ON SCHEDULE EVERY 1 SECOND DISABLE DO SELECT 1;
+ 2. DROP EVENT the_name
+
+ In other words, the first one will create a row in mysql.event . In the
+ second step because there will be a line, disk based drop will pass and
+ the scheduler will remove the memory counterpart. The reason is that
+ in-memory queue does not check whether the event we try to drop from memory
+ is disabled. Disabled events are not kept in-memory because they are not
+ eligible for execution.
+*/
+
+Event_queue *Events::event_queue;
+Event_scheduler *Events::scheduler;
+Event_db_repository *Events::db_repository;
+ulong Events::opt_event_scheduler= Events::EVENTS_OFF;
+bool Events::check_system_tables_error= FALSE;
+
+
+/*
+ Compares 2 LEX strings regarding case.
+
+ SYNOPSIS
+ sortcmp_lex_string()
+ s First LEX_STRING
+ t Second LEX_STRING
+ cs Charset
+
+ RETURN VALUE
+ -1 s < t
+ 0 s == t
+ 1 s > t
+*/
+
+int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs)
+{
+ return cs->coll->strnncollsp(cs, (uchar *) s.str,s.length,
+ (uchar *) t.str,t.length, 0);
+}
+
+
+/**
+ Push an error into the error stack if the system tables are
+ not up to date.
+*/
+
+bool Events::check_if_system_tables_error()
+{
+ DBUG_ENTER("Events::check_if_system_tables_error");
+
+ if (check_system_tables_error)
+ {
+ my_error(ER_EVENTS_DB_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Reconstructs interval expression from interval type and expression
+ value that is in form of a value of the smalles entity:
+ For
+ YEAR_MONTH - expression is in months
+ DAY_MINUTE - expression is in minutes
+
+ SYNOPSIS
+ Events::reconstruct_interval_expression()
+ buf Preallocated String buffer to add the value to
+ interval The interval type (for instance YEAR_MONTH)
+ expression The value in the lowest entity
+
+ RETURN VALUE
+ 0 OK
+ 1 Error
+*/
+
+int
+Events::reconstruct_interval_expression(String *buf, interval_type interval,
+ longlong expression)
+{
+ ulonglong expr= expression;
+ char tmp_buff[128], *end;
+ bool close_quote= TRUE;
+ int multipl= 0;
+ char separator=':';
+
+ switch (interval) {
+ case INTERVAL_YEAR_MONTH:
+ multipl= 12;
+ separator= '-';
+ goto common_1_lev_code;
+ case INTERVAL_DAY_HOUR:
+ multipl= 24;
+ separator= ' ';
+ goto common_1_lev_code;
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ multipl= 60;
+common_1_lev_code:
+ buf->append('\'');
+ end= longlong10_to_str(expression/multipl, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));
+ expr= expr - (expr/multipl)*multipl;
+ break;
+ case INTERVAL_DAY_MINUTE:
+ {
+ ulonglong tmp_expr= expr;
+
+ tmp_expr/=(24*60);
+ buf->append('\'');
+ end= longlong10_to_str(tmp_expr, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));// days
+ buf->append(' ');
+
+ tmp_expr= expr - tmp_expr*(24*60);//minutes left
+ end= longlong10_to_str(tmp_expr/60, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));// hours
+
+ expr= tmp_expr - (tmp_expr/60)*60;
+ /* the code after the switch will finish */
+ }
+ break;
+ case INTERVAL_HOUR_SECOND:
+ {
+ ulonglong tmp_expr= expr;
+
+ buf->append('\'');
+ end= longlong10_to_str(tmp_expr/3600, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));// hours
+ buf->append(':');
+
+ tmp_expr= tmp_expr - (tmp_expr/3600)*3600;
+ end= longlong10_to_str(tmp_expr/60, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));// minutes
+
+ expr= tmp_expr - (tmp_expr/60)*60;
+ /* the code after the switch will finish */
+ }
+ break;
+ case INTERVAL_DAY_SECOND:
+ {
+ ulonglong tmp_expr= expr;
+
+ tmp_expr/=(24*3600);
+ buf->append('\'');
+ end= longlong10_to_str(tmp_expr, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));// days
+ buf->append(' ');
+
+ tmp_expr= expr - tmp_expr*(24*3600);//seconds left
+ end= longlong10_to_str(tmp_expr/3600, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));// hours
+ buf->append(':');
+
+ tmp_expr= tmp_expr - (tmp_expr/3600)*3600;
+ end= longlong10_to_str(tmp_expr/60, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));// minutes
+
+ expr= tmp_expr - (tmp_expr/60)*60;
+ /* the code after the switch will finish */
+ }
+ break;
+ case INTERVAL_DAY_MICROSECOND:
+ case INTERVAL_HOUR_MICROSECOND:
+ case INTERVAL_MINUTE_MICROSECOND:
+ case INTERVAL_SECOND_MICROSECOND:
+ case INTERVAL_MICROSECOND:
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND");
+ return 1;
+ case INTERVAL_QUARTER:
+ expr/= 3;
+ close_quote= FALSE;
+ break;
+ case INTERVAL_WEEK:
+ expr/= 7;
+ default:
+ close_quote= FALSE;
+ break;
+ }
+ if (close_quote)
+ buf->append(separator);
+ end= longlong10_to_str(expr, tmp_buff, 10);
+ buf->append(tmp_buff, (uint) (end- tmp_buff));
+ if (close_quote)
+ buf->append('\'');
+
+ return 0;
+}
+
+
+/**
+ Create a new query string for removing executable comments
+ for avoiding leak and keeping consistency of the execution
+ on master and slave.
+
+ @param[in] thd Thread handler
+ @param[in] buf Query string
+
+ @return
+ 0 ok
+ 1 error
+*/
+static int
+create_query_string(THD *thd, String *buf)
+{
+ /* Append the "CREATE" part of the query */
+ if (buf->append(STRING_WITH_LEN("CREATE ")))
+ return 1;
+ /* Append definer */
+ append_definer(thd, buf, &(thd->lex->definer->user), &(thd->lex->definer->host));
+ /* Append the left part of thd->query after "DEFINER" part */
+ if (buf->append(thd->lex->stmt_definition_begin,
+ thd->lex->stmt_definition_end -
+ thd->lex->stmt_definition_begin))
+ return 1;
+
+ return 0;
+}
+
+
+/**
+ Create a new event.
+
+ @param[in,out] thd THD
+ @param[in] parse_data Event's data from parsing stage
+ @param[in] if_not_exists Whether IF NOT EXISTS was
+ specified
+ In case there is an event with the same name (db) and
+ IF NOT EXISTS is specified, an warning is put into the stack.
+ @sa Events::drop_event for the notes about locking, pre-locking
+ and Events DDL.
+
+ @retval FALSE OK
+ @retval TRUE Error (reported)
+*/
+
+bool
+Events::create_event(THD *thd, Event_parse_data *parse_data,
+ bool if_not_exists)
+{
+ bool ret;
+ bool event_already_exists;
+ enum_binlog_format save_binlog_format;
+ DBUG_ENTER("Events::create_event");
+
+ if (check_if_system_tables_error())
+ DBUG_RETURN(TRUE);
+
+ /*
+ Perform semantic checks outside of Event_db_repository:
+ once CREATE EVENT is supported in prepared statements, the
+ checks will be moved to PREPARE phase.
+ */
+ if (parse_data->check_parse_data(thd))
+ DBUG_RETURN(TRUE);
+
+ /* At create, one of them must be set */
+ DBUG_ASSERT(parse_data->expression || parse_data->execute_at);
+
+ if (check_access(thd, EVENT_ACL, parse_data->dbname.str, NULL, NULL, 0, 0))
+ DBUG_RETURN(TRUE);
+
+ if (check_db_dir_existence(parse_data->dbname.str))
+ {
+ my_error(ER_BAD_DB_ERROR, MYF(0), parse_data->dbname.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (parse_data->do_not_create)
+ DBUG_RETURN(FALSE);
+ /*
+ Turn off row binlogging of this statement and use statement-based
+ so that all supporting tables are updated for CREATE EVENT command.
+ */
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ if (lock_object_name(thd, MDL_key::EVENT,
+ parse_data->dbname.str, parse_data->name.str))
+ DBUG_RETURN(TRUE);
+
+ /* On error conditions my_error() is called so no need to handle here */
+ if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists,
+ &event_already_exists)))
+ {
+ Event_queue_element *new_element;
+ bool dropped= 0;
+
+ if (!event_already_exists)
+ {
+ if (!(new_element= new Event_queue_element()))
+ ret= TRUE; // OOM
+ else if ((ret= db_repository->load_named_event(thd, parse_data->dbname,
+ parse_data->name,
+ new_element)))
+ {
+ if (!db_repository->drop_event(thd, parse_data->dbname,
+ parse_data->name, TRUE))
+ dropped= 1;
+ delete new_element;
+ }
+ else
+ {
+ /* TODO: do not ignore the out parameter and a possible OOM error! */
+ bool created;
+ if (event_queue)
+ event_queue->create_event(thd, new_element, &created);
+ }
+ }
+ /*
+ binlog the create event unless it's been successfully dropped
+ */
+ if (!dropped)
+ {
+ /* Binlog the create event. */
+ DBUG_ASSERT(thd->query() && thd->query_length());
+ String log_query;
+ if (create_query_string(thd, &log_query))
+ {
+ sql_print_error("Event Error: An error occurred while creating query "
+ "string, before writing it into binary log.");
+ ret= true;
+ }
+ else
+ {
+ /*
+ If the definer is not set or set to CURRENT_USER, the value
+ of CURRENT_USER will be written into the binary log as the
+ definer for the SQL thread.
+ */
+ ret= write_bin_log(thd, TRUE, log_query.ptr(), log_query.length());
+ }
+ }
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Alter an event.
+
+ @param[in,out] thd THD
+ @param[in] parse_data Event's data from parsing stage
+ @param[in] new_dbname A new schema name for the event. Set in the case of
+ ALTER EVENT RENAME, otherwise is NULL.
+ @param[in] new_name A new name for the event. Set in the case of
+ ALTER EVENT RENAME
+
+ Parameter 'et' contains data about dbname and event name.
+ Parameter 'new_name' is the new name of the event, if not null
+ this means that RENAME TO was specified in the query
+ @sa Events::drop_event for the locking notes.
+
+ @retval FALSE OK
+ @retval TRUE error (reported)
+*/
+
+bool
+Events::update_event(THD *thd, Event_parse_data *parse_data,
+ LEX_STRING *new_dbname, LEX_STRING *new_name)
+{
+ int ret;
+ enum_binlog_format save_binlog_format;
+ Event_queue_element *new_element;
+
+ DBUG_ENTER("Events::update_event");
+
+ if (check_if_system_tables_error())
+ DBUG_RETURN(TRUE);
+
+ if (parse_data->check_parse_data(thd) || parse_data->do_not_create)
+ DBUG_RETURN(TRUE);
+
+ if (check_access(thd, EVENT_ACL, parse_data->dbname.str, NULL, NULL, 0, 0))
+ DBUG_RETURN(TRUE);
+
+ if (new_dbname) /* It's a rename */
+ {
+ /* Check that the new and the old names differ. */
+ if ( !sortcmp_lex_string(parse_data->dbname, *new_dbname,
+ system_charset_info) &&
+ !sortcmp_lex_string(parse_data->name, *new_name,
+ system_charset_info))
+ {
+ my_error(ER_EVENT_SAME_NAME, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ And the user has sufficient privileges to use the target database.
+ Do it before checking whether the database exists: we don't want
+ to tell the user that a database doesn't exist if they can not
+ access it.
+ */
+ if (check_access(thd, EVENT_ACL, new_dbname->str, NULL, NULL, 0, 0))
+ DBUG_RETURN(TRUE);
+
+ /* Check that the target database exists */
+ if (check_db_dir_existence(new_dbname->str))
+ {
+ my_error(ER_BAD_DB_ERROR, MYF(0), new_dbname->str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ /*
+ Turn off row binlogging of this statement and use statement-based
+ so that all supporting tables are updated for UPDATE EVENT command.
+ */
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ if (lock_object_name(thd, MDL_key::EVENT,
+ parse_data->dbname.str, parse_data->name.str))
+ DBUG_RETURN(TRUE);
+
+ /* On error conditions my_error() is called so no need to handle here */
+ if (!(ret= db_repository->update_event(thd, parse_data,
+ new_dbname, new_name)))
+ {
+ LEX_STRING dbname= new_dbname ? *new_dbname : parse_data->dbname;
+ LEX_STRING name= new_name ? *new_name : parse_data->name;
+
+ if (!(new_element= new Event_queue_element()))
+ ret= TRUE; // OOM
+ else if ((ret= db_repository->load_named_event(thd, dbname, name,
+ new_element)))
+ delete new_element;
+ else
+ {
+ /*
+ TODO: check if an update actually has inserted an entry
+ into the queue.
+ If not, and the element is ON COMPLETION NOT PRESERVE, delete
+ it right away.
+ */
+ if (event_queue)
+ event_queue->update_event(thd, parse_data->dbname, parse_data->name,
+ new_element);
+ /* Binlog the alter event. */
+ DBUG_ASSERT(thd->query() && thd->query_length());
+ ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ }
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Drops an event
+
+ @param[in,out] thd THD
+ @param[in] dbname Event's schema
+ @param[in] name Event's name
+ @param[in] if_exists When this is set and the event does not exist
+ a warning is pushed into the warning stack.
+ Otherwise the operation produces an error.
+
+ @note Similarly to DROP PROCEDURE, we do not allow DROP EVENT
+ under LOCK TABLES mode, unless table mysql.event is locked. To
+ ensure that, we do not reset & backup the open tables state in
+ this function - if in LOCK TABLES or pre-locking mode, this will
+ lead to an error 'Table mysql.event is not locked with LOCK
+ TABLES' unless it _is_ locked. In pre-locked mode there is
+ another barrier - DROP EVENT commits the current transaction,
+ and COMMIT/ROLLBACK is not allowed in stored functions and
+ triggers.
+
+ @retval FALSE OK
+ @retval TRUE Error (reported)
+*/
+
+bool
+Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists)
+{
+ int ret;
+ enum_binlog_format save_binlog_format;
+ DBUG_ENTER("Events::drop_event");
+
+ if (check_if_system_tables_error())
+ DBUG_RETURN(TRUE);
+
+ if (check_access(thd, EVENT_ACL, dbname.str, NULL, NULL, 0, 0))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Turn off row binlogging of this statement and use statement-based so
+ that all supporting tables are updated for DROP EVENT command.
+ */
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ if (lock_object_name(thd, MDL_key::EVENT,
+ dbname.str, name.str))
+ DBUG_RETURN(TRUE);
+ /* On error conditions my_error() is called so no need to handle here */
+ if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists)))
+ {
+ if (event_queue)
+ event_queue->drop_event(thd, dbname, name);
+ /* Binlog the drop event. */
+ DBUG_ASSERT(thd->query() && thd->query_length());
+ ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Drops all events from a schema
+
+ @note We allow to drop all events in a schema even if the
+ scheduler is disabled. This is to not produce any warnings
+ in case of DROP DATABASE and a disabled scheduler.
+
+ @param[in,out] thd Thread
+ @param[in] db ASCIIZ schema name
+*/
+
+void
+Events::drop_schema_events(THD *thd, char *db)
+{
+ LEX_STRING const db_lex= { db, strlen(db) };
+
+ DBUG_ENTER("Events::drop_schema_events");
+ DBUG_PRINT("enter", ("dropping events from %s", db));
+
+ DBUG_ASSERT(ok_for_lower_case_names(db));
+
+ /*
+ Sic: no check if the scheduler is disabled or system tables
+ are damaged, as intended.
+ */
+ if (event_queue)
+ event_queue->drop_schema_events(thd, db_lex);
+ db_repository->drop_schema_events(thd, db_lex);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ A helper function to generate SHOW CREATE EVENT output from
+ a named event
+*/
+
+static bool
+send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol)
+{
+ char show_str_buf[10 * STRING_BUFFER_USUAL_SIZE];
+ String show_str(show_str_buf, sizeof(show_str_buf), system_charset_info);
+ List<Item> field_list;
+ LEX_STRING sql_mode;
+ const String *tz_name;
+
+ DBUG_ENTER("send_show_create_event");
+
+ show_str.length(0);
+ if (et->get_create_event(thd, &show_str))
+ DBUG_RETURN(TRUE);
+
+ field_list.push_back(new Item_empty_string("Event", NAME_CHAR_LEN));
+
+ if (sql_mode_string_representation(thd, et->sql_mode, &sql_mode))
+ DBUG_RETURN(TRUE);
+
+ field_list.push_back(new Item_empty_string("sql_mode", (uint) sql_mode.length));
+
+ tz_name= et->time_zone->get_name();
+
+ field_list.push_back(new Item_empty_string("time_zone",
+ tz_name->length()));
+
+ field_list.push_back(new Item_empty_string("Create Event",
+ show_str.length()));
+
+ field_list.push_back(
+ new Item_empty_string("character_set_client", MY_CS_NAME_SIZE));
+
+ field_list.push_back(
+ new Item_empty_string("collation_connection", MY_CS_NAME_SIZE));
+
+ field_list.push_back(
+ new Item_empty_string("Database Collation", MY_CS_NAME_SIZE));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ protocol->prepare_for_resend();
+
+ protocol->store(et->name.str, et->name.length, system_charset_info);
+ protocol->store(sql_mode.str, sql_mode.length, system_charset_info);
+ protocol->store(tz_name->ptr(), tz_name->length(), system_charset_info);
+ protocol->store(show_str.ptr(), show_str.length(),
+ et->creation_ctx->get_client_cs());
+ protocol->store(et->creation_ctx->get_client_cs()->csname,
+ strlen(et->creation_ctx->get_client_cs()->csname),
+ system_charset_info);
+ protocol->store(et->creation_ctx->get_connection_cl()->name,
+ strlen(et->creation_ctx->get_connection_cl()->name),
+ system_charset_info);
+ protocol->store(et->creation_ctx->get_db_cl()->name,
+ strlen(et->creation_ctx->get_db_cl()->name),
+ system_charset_info);
+
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+
+ my_eof(thd);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Implement SHOW CREATE EVENT statement
+
+ thd Thread context
+ spn The name of the event (db, name)
+
+ @retval FALSE OK
+ @retval TRUE error (reported)
+*/
+
+bool
+Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name)
+{
+ Event_timed et;
+ bool ret;
+
+ DBUG_ENTER("Events::show_create_event");
+ DBUG_PRINT("enter", ("name: %s@%s", dbname.str, name.str));
+
+ if (check_if_system_tables_error())
+ DBUG_RETURN(TRUE);
+
+ if (check_access(thd, EVENT_ACL, dbname.str, NULL, NULL, 0, 0))
+ DBUG_RETURN(TRUE);
+
+ /*
+ We would like to allow SHOW CREATE EVENT under LOCK TABLES and
+ in pre-locked mode. mysql.event table is marked as a system table.
+ This flag reduces the set of its participation scenarios in LOCK TABLES
+ operation, and therefore an out-of-bound open of this table
+ for reading like the one below (sic, only for reading) is
+ more or less deadlock-free. For additional information about when a
+ deadlock can occur please refer to the description of 'system table'
+ flag.
+ */
+ ret= db_repository->load_named_event(thd, dbname, name, &et);
+
+ if (!ret)
+ ret= send_show_create_event(thd, &et, thd->protocol);
+
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Check access rights and fill INFORMATION_SCHEMA.events table.
+
+ @param[in,out] thd Thread context
+ @param[in] tables The temporary table to fill.
+
+ In MySQL INFORMATION_SCHEMA tables are temporary tables that are
+ created and filled on demand. In this function, we fill
+ INFORMATION_SCHEMA.events. It is a callback for I_S module, invoked from
+ sql_show.cc
+
+ @return Has to be integer, as such is the requirement of the I_S API
+ @retval 0 success
+ @retval 1 an error, pushed into the error stack
+*/
+
+int
+Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */)
+{
+ char *db= NULL;
+ int ret;
+ DBUG_ENTER("Events::fill_schema_events");
+
+ if (check_if_system_tables_error())
+ DBUG_RETURN(1);
+
+ /*
+ If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to
+ be NULL. Let's do an assert anyway.
+ */
+ if (thd->lex->sql_command == SQLCOM_SHOW_EVENTS)
+ {
+ DBUG_ASSERT(thd->lex->select_lex.db);
+ if (!is_infoschema_db(thd->lex->select_lex.db) && // There is no events in I_S
+ check_access(thd, EVENT_ACL, thd->lex->select_lex.db,
+ NULL, NULL, 0, 0))
+ DBUG_RETURN(1);
+ db= thd->lex->select_lex.db;
+
+ if (lower_case_table_names)
+ my_casedn_str(system_charset_info, db);
+ }
+ ret= db_repository->fill_schema_events(thd, tables, db);
+
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Initializes the scheduler's structures.
+
+ @param opt_noacl_or_bootstrap
+ TRUE if there is --skip-grant-tables or --bootstrap
+ option. In that case we disable the event scheduler.
+
+ @note This function is not synchronized.
+
+ @retval FALSE Perhaps there was an error, and the event scheduler
+ is disabled. But the error is not fatal and the
+ server start up can continue.
+ @retval TRUE Fatal error. Startup must terminate (call unireg_abort()).
+*/
+
+bool
+Events::init(bool opt_noacl_or_bootstrap)
+{
+
+ THD *thd;
+ int err_no;
+ bool res= FALSE;
+
+ DBUG_ENTER("Events::init");
+
+ /* We need a temporary THD during boot */
+ if (!(thd= new THD()))
+ {
+ res= TRUE;
+ goto end;
+ }
+ /*
+ The thread stack does not start from this function but we cannot
+ guess the real value. So better some value that doesn't assert than
+ no value.
+ */
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+ /*
+ Set current time for the thread that handles events.
+ Current time is stored in data member start_time of THD class.
+ Subsequently, this value is used to check whether event was expired
+ when make loading events from storage. Check for event expiration time
+ is done at Event_queue_element::compute_next_execution_time() where
+ event's status set to Event_parse_data::DISABLED and dropped flag set
+ to true if event was expired.
+ */
+ thd->set_time();
+ /*
+ We will need Event_db_repository anyway, even if the scheduler is
+ disabled - to perform events DDL.
+ */
+ if (!(db_repository= new Event_db_repository))
+ {
+ res= TRUE; /* fatal error: request unireg_abort */
+ goto end;
+ }
+
+ /*
+ Since we allow event DDL even if the scheduler is disabled,
+ check the system tables, as we might need them.
+
+ If run with --skip-grant-tables or --bootstrap, don't try to do the
+ check of system tables and don't complain: in these modes the tables
+ are most likely not there and we're going to disable the event
+ scheduler anyway.
+ */
+ if (opt_noacl_or_bootstrap || Event_db_repository::check_system_tables(thd))
+ {
+ if (! opt_noacl_or_bootstrap)
+ {
+ sql_print_error("Event Scheduler: An error occurred when initializing "
+ "system tables. Disabling the Event Scheduler.");
+ check_system_tables_error= TRUE;
+ }
+
+ /* Disable the scheduler since the system tables are not up to date */
+ opt_event_scheduler= EVENTS_DISABLED;
+ goto end;
+ }
+
+ /*
+ Was disabled explicitly from the command line, or because we're running
+ with --skip-grant-tables, or --bootstrap, or because we have no system
+ tables.
+ */
+ if (opt_event_scheduler == Events::EVENTS_DISABLED)
+ goto end;
+
+
+ DBUG_ASSERT(opt_event_scheduler == Events::EVENTS_ON ||
+ opt_event_scheduler == Events::EVENTS_OFF);
+
+ if (!(event_queue= new Event_queue) ||
+ !(scheduler= new Event_scheduler(event_queue)))
+ {
+ res= TRUE; /* fatal error: request unireg_abort */
+ goto end;
+ }
+
+ if (event_queue->init_queue(thd) || load_events_from_db(thd) ||
+ (opt_event_scheduler == EVENTS_ON && scheduler->start(&err_no)))
+ {
+ sql_print_error("Event Scheduler: Error while loading from disk.");
+ res= TRUE; /* fatal error: request unireg_abort */
+ goto end;
+ }
+ Event_worker_thread::init(db_repository);
+
+end:
+ if (res)
+ {
+ delete db_repository;
+ delete event_queue;
+ delete scheduler;
+ }
+ delete thd;
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+
+ DBUG_RETURN(res);
+}
+
+/*
+ Cleans up scheduler's resources. Called at server shutdown.
+
+ SYNOPSIS
+ Events::deinit()
+
+ NOTES
+ This function is not synchronized.
+*/
+
+void
+Events::deinit()
+{
+ DBUG_ENTER("Events::deinit");
+
+ if (opt_event_scheduler != EVENTS_DISABLED)
+ {
+ delete scheduler;
+ scheduler= NULL; /* safety */
+ delete event_queue;
+ event_queue= NULL; /* safety */
+ }
+
+ delete db_repository;
+ db_repository= NULL; /* safety */
+
+ DBUG_VOID_RETURN;
+}
+
+#ifdef HAVE_PSI_INTERFACE
+PSI_mutex_key key_LOCK_event_queue,
+ key_event_scheduler_LOCK_scheduler_state;
+
+static PSI_mutex_info all_events_mutexes[]=
+{
+ { &key_LOCK_event_queue, "LOCK_event_queue", PSI_FLAG_GLOBAL},
+ { &key_event_scheduler_LOCK_scheduler_state, "Event_scheduler::LOCK_scheduler_state", PSI_FLAG_GLOBAL}
+};
+
+PSI_cond_key key_event_scheduler_COND_state, key_COND_queue_state;
+
+static PSI_cond_info all_events_conds[]=
+{
+ { &key_event_scheduler_COND_state, "Event_scheduler::COND_state", PSI_FLAG_GLOBAL},
+ { &key_COND_queue_state, "COND_queue_state", PSI_FLAG_GLOBAL},
+};
+
+PSI_thread_key key_thread_event_scheduler, key_thread_event_worker;
+
+static PSI_thread_info all_events_threads[]=
+{
+ { &key_thread_event_scheduler, "event_scheduler", PSI_FLAG_GLOBAL},
+ { &key_thread_event_worker, "event_worker", 0}
+};
+#endif /* HAVE_PSI_INTERFACE */
+
+PSI_stage_info stage_waiting_on_empty_queue= { 0, "Waiting on empty queue", 0};
+PSI_stage_info stage_waiting_for_next_activation= { 0, "Waiting for next activation", 0};
+PSI_stage_info stage_waiting_for_scheduler_to_stop= { 0, "Waiting for the scheduler to stop", 0};
+
+#ifdef HAVE_PSI_INTERFACE
+PSI_stage_info *all_events_stages[]=
+{
+ & stage_waiting_on_empty_queue,
+ & stage_waiting_for_next_activation,
+ & stage_waiting_for_scheduler_to_stop
+};
+
+static void init_events_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ count= array_elements(all_events_mutexes);
+ mysql_mutex_register(category, all_events_mutexes, count);
+
+ count= array_elements(all_events_conds);
+ mysql_cond_register(category, all_events_conds, count);
+
+ count= array_elements(all_events_threads);
+ mysql_thread_register(category, all_events_threads, count);
+
+ count= array_elements(all_events_stages);
+ mysql_stage_register(category, all_events_stages, count);
+
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+/**
+ Inits Events mutexes
+
+ SYNOPSIS
+ Events::init_mutexes()
+ thd Thread
+*/
+
+void
+Events::init_mutexes()
+{
+#ifdef HAVE_PSI_INTERFACE
+ init_events_psi_keys();
+#endif
+}
+
+
+/*
+ Dumps the internal status of the scheduler and the memory cache
+ into a table with two columns - Name & Value. Different properties
+ which could be useful for debugging for instance deadlocks are
+ returned.
+
+ SYNOPSIS
+ Events::dump_internal_status()
+*/
+
+void
+Events::dump_internal_status()
+{
+ DBUG_ENTER("Events::dump_internal_status");
+ puts("\n\n\nEvents status:");
+ puts("LLA = Last Locked At LUA = Last Unlocked At");
+ puts("WOC = Waiting On Condition DL = Data Locked");
+
+ /*
+ opt_event_scheduler should only be accessed while
+ holding LOCK_global_system_variables.
+ */
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ if (opt_event_scheduler == EVENTS_DISABLED)
+ puts("The Event Scheduler is disabled");
+ else
+ {
+ scheduler->dump_internal_status();
+ event_queue->dump_internal_status();
+ }
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ DBUG_VOID_RETURN;
+}
+
+bool Events::start(int *err_no)
+{
+ return scheduler->start(err_no);
+}
+
+bool Events::stop()
+{
+ return scheduler->stop();
+}
+
+/**
+ Loads all ENABLED events from mysql.event into a prioritized
+ queue.
+
+ This function is called during the server start up. It reads
+ every event, computes the next execution time, and if the event
+ needs execution, adds it to a prioritized queue. Otherwise, if
+ ON COMPLETION DROP is specified, the event is automatically
+ removed from the table.
+
+ @param[in,out] thd Thread context. Used for memory allocation in some cases.
+
+ @retval FALSE success
+ @retval TRUE error, the load is aborted
+
+ @note Reports the error to the console
+*/
+
+bool
+Events::load_events_from_db(THD *thd)
+{
+ TABLE *table;
+ READ_RECORD read_record_info;
+ bool ret= TRUE;
+ uint count= 0;
+ ulong saved_master_access;
+
+ DBUG_ENTER("Events::load_events_from_db");
+ DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd));
+
+ /*
+ NOTE: even if we run in read-only mode, we should be able to lock the
+ mysql.event table for writing. In order to achieve this, we should call
+ mysql_lock_tables() under the super user.
+
+ Same goes for transaction access mode.
+ Temporarily reset it to read-write.
+ */
+
+ saved_master_access= thd->security_ctx->master_access;
+ thd->security_ctx->master_access |= SUPER_ACL;
+ bool save_tx_read_only= thd->tx_read_only;
+ thd->tx_read_only= false;
+
+ ret= db_repository->open_event_table(thd, TL_WRITE, &table);
+
+ thd->tx_read_only= save_tx_read_only;
+ thd->security_ctx->master_access= saved_master_access;
+
+ if (ret)
+ {
+ sql_print_error("Event Scheduler: Failed to open table mysql.event");
+ DBUG_RETURN(TRUE);
+ }
+
+ if (init_read_record(&read_record_info, thd, table, NULL, 0, 1, FALSE))
+ {
+ close_thread_tables(thd);
+ DBUG_RETURN(TRUE);
+ }
+
+ while (!(read_record_info.read_record(&read_record_info)))
+ {
+ Event_queue_element *et;
+ bool created, dropped;
+
+ if (!(et= new Event_queue_element))
+ goto end;
+
+ DBUG_PRINT("info", ("Loading event from row."));
+
+ if (et->load_from_row(thd, table))
+ {
+ sql_print_error("Event Scheduler: "
+ "Error while loading events from mysql.event. "
+ "The table probably contains bad data or is corrupted");
+ delete et;
+ goto end;
+ }
+
+ /**
+ Since the Event_queue_element object could be deleted inside
+ Event_queue::create_event we should save the value of dropped flag
+ into the temporary variable.
+ */
+ dropped= et->dropped;
+ if (event_queue->create_event(thd, et, &created))
+ {
+ /* Out of memory */
+ delete et;
+ goto end;
+ }
+ if (created)
+ count++;
+ else if (dropped)
+ {
+ /*
+ If not created, a stale event - drop if immediately if
+ ON COMPLETION NOT PRESERVE.
+ XXX: This won't be replicated, thus the drop won't appear in
+ in the slave. When the slave is restarted it will drop events.
+ However, as the slave will be "out of sync", it might happen that
+ an event created on the master, after master restart, won't be
+ replicated to the slave correctly, as the create will fail there.
+ */
+ int rc= table->file->ha_delete_row(table->record[0]);
+ if (rc)
+ {
+ table->file->print_error(rc, MYF(0));
+ goto end;
+ }
+ }
+ }
+ if (global_system_variables.log_warnings)
+ sql_print_information("Event Scheduler: Loaded %d event%s",
+ count, (count == 1) ? "" : "s");
+ ret= FALSE;
+
+end:
+ end_read_record(&read_record_info);
+
+ close_mysql_tables(thd);
+ DBUG_RETURN(ret);
+}
+
+/**
+ @} (End of group Event_Scheduler)
+*/
diff --git a/sql/events.h b/sql/events.h
new file mode 100644
index 00000000000..646fd257d52
--- /dev/null
+++ b/sql/events.h
@@ -0,0 +1,155 @@
+#ifndef _EVENT_H_
+#define _EVENT_H_
+/* Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/**
+ @defgroup Event_Scheduler Event Scheduler
+ @ingroup Runtime_Environment
+ @{
+
+ @file events.h
+
+ A public interface of Events_Scheduler module.
+*/
+
+#ifdef HAVE_PSI_INTERFACE
+extern PSI_mutex_key key_event_scheduler_LOCK_scheduler_state;
+extern PSI_cond_key key_event_scheduler_COND_state;
+extern PSI_thread_key key_thread_event_scheduler, key_thread_event_worker;
+#endif /* HAVE_PSI_INTERFACE */
+
+/* Always defined, for SHOW PROCESSLIST. */
+extern PSI_stage_info stage_waiting_on_empty_queue;
+extern PSI_stage_info stage_waiting_for_next_activation;
+extern PSI_stage_info stage_waiting_for_scheduler_to_stop;
+
+#include "sql_string.h" /* LEX_STRING */
+#include "my_time.h" /* interval_type */
+
+class Event_db_repository;
+class Event_parse_data;
+class Event_queue;
+class Event_scheduler;
+struct TABLE_LIST;
+class THD;
+typedef class Item COND;
+
+int
+sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs);
+
+/**
+ @brief A facade to the functionality of the Event Scheduler.
+
+ Every public operation against the scheduler has to be executed via the
+ interface provided by a static method of this class. No instance of this
+ class is ever created and it has no non-static data members.
+
+ The life cycle of the Events module is the following:
+
+ At server start up:
+ init_mutexes() -> init()
+ When the server is running:
+ create_event(), drop_event(), start_or_stop_event_scheduler(), etc
+ At shutdown:
+ deinit(), destroy_mutexes().
+
+ The peculiar initialization and shutdown cycle is an adaptation to the
+ outside server startup/shutdown framework and mimics the rest of MySQL
+ subsystems (ACL, time zone tables, etc).
+*/
+
+class Events
+{
+public:
+ /*
+ the following block is to support --event-scheduler command line option
+ and the @@global.event_scheduler SQL variable.
+ See sys_var.cc
+ */
+ enum enum_opt_event_scheduler { EVENTS_OFF, EVENTS_ON, EVENTS_DISABLED };
+ /* Protected using LOCK_global_system_variables only. */
+ static ulong opt_event_scheduler;
+ static bool check_if_system_tables_error();
+ static bool start(int *err_no);
+ static bool stop();
+
+public:
+ /* A hack needed for Event_queue_element */
+ static Event_db_repository *
+ get_db_repository() { return db_repository; }
+
+ static bool
+ init(bool opt_noacl);
+
+ static void
+ deinit();
+
+ static void
+ init_mutexes();
+
+ static void
+ destroy_mutexes();
+
+ static bool
+ create_event(THD *thd, Event_parse_data *parse_data, bool if_exists);
+
+ static bool
+ update_event(THD *thd, Event_parse_data *parse_data,
+ LEX_STRING *new_dbname, LEX_STRING *new_name);
+
+ static bool
+ drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists);
+
+ static void
+ drop_schema_events(THD *thd, char *db);
+
+ static bool
+ show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name);
+
+ /* Needed for both SHOW CREATE EVENT and INFORMATION_SCHEMA */
+ static int
+ reconstruct_interval_expression(String *buf, interval_type interval,
+ longlong expression);
+
+ static int
+ fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */);
+
+ static void
+ dump_internal_status();
+
+private:
+
+ static bool
+ load_events_from_db(THD *thd);
+
+private:
+ static Event_queue *event_queue;
+ static Event_scheduler *scheduler;
+ static Event_db_repository *db_repository;
+ /* Set to TRUE if an error at start up */
+ static bool check_system_tables_error;
+
+private:
+ /* Prevent use of these */
+ Events(const Events &);
+ void operator=(Events &);
+};
+
+/**
+ @} (end of group Event Scheduler)
+*/
+
+#endif /* _EVENT_H_ */
diff --git a/sql/examples/CMakeLists.txt b/sql/examples/CMakeLists.txt
new file mode 100644
index 00000000000..c4ea4c25679
--- /dev/null
+++ b/sql/examples/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# 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_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/sql
+ ${CMAKE_SOURCE_DIR}/extra/yassl/include
+ ${CMAKE_SOURCE_DIR}/regex)
+
+IF(WITH_EXAMPLE_STORAGE_ENGINE)
+ADD_LIBRARY(example ha_example.cc)
+ADD_DEPENDENCIES(example GenError)
+ENDIF(WITH_EXAMPLE_STORAGE_ENGINE)
diff --git a/sql/field.cc b/sql/field.cc
new file mode 100644
index 00000000000..e7e046a8458
--- /dev/null
+++ b/sql/field.cc
@@ -0,0 +1,10152 @@
+/*
+ Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2013, Monty Program Ab.
+
+ 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
+*/
+
+/**
+ @file
+
+ @brief
+ This file implements classes defined in field.h
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_select.h"
+#include "rpl_rli.h" // Pull in Relay_log_info
+#include "slave.h" // Pull in rpl_master_has_bug()
+#include "strfunc.h" // find_type2, find_set
+#include "sql_time.h" // str_to_datetime_with_warn,
+ // str_to_time_with_warn,
+ // TIME_to_timestamp,
+ // make_time, make_date,
+ // make_truncated_value_warning
+#include "tztime.h" // struct Time_zone
+#include "filesort.h" // change_double_for_sort
+#include "log_event.h" // class Table_map_log_event
+#include <m_ctype.h>
+
+// Maximum allowed exponent value for converting string to decimal
+#define MAX_EXPONENT 1024
+
+/*****************************************************************************
+ Instansiate templates and static variables
+*****************************************************************************/
+
+static const char *zero_timestamp="0000-00-00 00:00:00.000000";
+
+/* number of bytes to store second_part part of the TIMESTAMP(N) */
+static uint sec_part_bytes[MAX_DATETIME_PRECISION+1]= { 0, 1, 1, 2, 2, 3, 3 };
+
+/* number of bytes to store DATETIME(N) */
+static uint datetime_hires_bytes[MAX_DATETIME_PRECISION+1]= { 5, 6, 6, 7, 7, 7, 8 };
+
+/* number of bytes to store TIME(N) */
+static uint time_hires_bytes[MAX_DATETIME_PRECISION+1]= { 3, 4, 4, 5, 5, 5, 6 };
+
+uchar Field_null::null[1]={1};
+const char field_separator=',';
+
+#define DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE FLOATING_POINT_BUFFER
+#define LONGLONG_TO_STRING_CONVERSION_BUFFER_SIZE 128
+#define DECIMAL_TO_STRING_CONVERSION_BUFFER_SIZE 128
+#define BLOB_PACK_LENGTH_TO_MAX_LENGH(arg) \
+ ((ulong) ((1LL << MY_MIN(arg, 4) * 8) - 1))
+
+#define ASSERT_COLUMN_MARKED_FOR_READ DBUG_ASSERT(!table || (!table->read_set || bitmap_is_set(table->read_set, field_index)))
+#define ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED DBUG_ASSERT(is_stat_field || !table || (!table->write_set || bitmap_is_set(table->write_set, field_index) || bitmap_is_set(table->vcol_set, field_index)))
+
+#define FLAGSTR(S,F) ((S) & (F) ? #F " " : "")
+
+/*
+ Rules for merging different types of fields in UNION
+
+ NOTE: to avoid 256*256 table, gap in table types numeration is skiped
+ following #defines describe that gap and how to canculate number of fields
+ and index of field in thia array.
+*/
+#define FIELDTYPE_TEAR_FROM (MYSQL_TYPE_BIT + 1)
+#define FIELDTYPE_TEAR_TO (MYSQL_TYPE_NEWDECIMAL - 1)
+#define FIELDTYPE_NUM (FIELDTYPE_TEAR_FROM + (255 - FIELDTYPE_TEAR_TO))
+static inline int field_type2index (enum_field_types field_type)
+{
+ field_type= real_type_to_type(field_type);
+ return (field_type < FIELDTYPE_TEAR_FROM ?
+ field_type :
+ ((int)FIELDTYPE_TEAR_FROM) + (field_type - FIELDTYPE_TEAR_TO) - 1);
+}
+
+
+static enum_field_types field_types_merge_rules [FIELDTYPE_NUM][FIELDTYPE_NUM]=
+{
+ /* MYSQL_TYPE_DECIMAL -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_NEWDECIMAL,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_NEWDECIMAL,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_DECIMAL, MYSQL_TYPE_DECIMAL,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_TINY -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_TINY,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_TINY, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_INT24,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_SHORT -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_SHORT,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_SHORT, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_INT24,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_SHORT,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_LONG -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_LONG, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_LONG, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_FLOAT -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_FLOAT,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_FLOAT,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_FLOAT,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_DOUBLE -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_NULL -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_TINY,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_NULL, MYSQL_TYPE_TIMESTAMP,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_LONGLONG,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_TIME,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_BIT,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_ENUM,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_SET, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY
+ },
+ /* MYSQL_TYPE_TIMESTAMP -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_TIMESTAMP, MYSQL_TYPE_TIMESTAMP,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_LONGLONG -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_LONGLONG,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_LONGLONG,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_LONGLONG,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_INT24 -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_INT24,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_INT24, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_INT24, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_INT24,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_INT24,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_DATE -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_TIME -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_TIME,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_DATETIME -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_YEAR -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_YEAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONGLONG, MYSQL_TYPE_INT24,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_YEAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_NEWDATE -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_DATETIME,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_DATETIME, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_VARCHAR -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_BIT -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_BIT, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_BIT,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_NEWDECIMAL -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_NEWDECIMAL,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_NEWDECIMAL,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_DOUBLE, MYSQL_TYPE_DOUBLE,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_NEWDECIMAL,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_NEWDECIMAL,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_ENUM -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_ENUM, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_SET -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_SET, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_TINY_BLOB -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_TINY_BLOB
+ },
+ /* MYSQL_TYPE_MEDIUM_BLOB -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_MEDIUM_BLOB
+ },
+ /* MYSQL_TYPE_LONG_BLOB -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_LONG_BLOB
+ },
+ /* MYSQL_TYPE_BLOB -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_BLOB
+ },
+ /* MYSQL_TYPE_VAR_STRING -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR
+ },
+ /* MYSQL_TYPE_STRING -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_STRING, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_STRING, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_STRING
+ },
+ /* MYSQL_TYPE_GEOMETRY -> */
+ {
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ MYSQL_TYPE_GEOMETRY, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_BIT <16>-<245>
+ MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ MYSQL_TYPE_VARCHAR, MYSQL_TYPE_TINY_BLOB,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ MYSQL_TYPE_BLOB, MYSQL_TYPE_VARCHAR,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY
+ }
+};
+
+/**
+ Return type of which can carry value of both given types in UNION result.
+
+ @param a type for merging
+ @param b type for merging
+
+ @return
+ type of field
+*/
+
+enum_field_types Field::field_type_merge(enum_field_types a,
+ enum_field_types b)
+{
+ DBUG_ASSERT(real_type_to_type(a) < FIELDTYPE_TEAR_FROM ||
+ real_type_to_type(a) > FIELDTYPE_TEAR_TO);
+ DBUG_ASSERT(real_type_to_type(b) < FIELDTYPE_TEAR_FROM ||
+ real_type_to_type(b) > FIELDTYPE_TEAR_TO);
+ return field_types_merge_rules[field_type2index(a)]
+ [field_type2index(b)];
+}
+
+
+static Item_result field_types_result_type [FIELDTYPE_NUM]=
+{
+ //MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY
+ DECIMAL_RESULT, INT_RESULT,
+ //MYSQL_TYPE_SHORT MYSQL_TYPE_LONG
+ INT_RESULT, INT_RESULT,
+ //MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE
+ REAL_RESULT, REAL_RESULT,
+ //MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP
+ STRING_RESULT, STRING_RESULT,
+ //MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24
+ INT_RESULT, INT_RESULT,
+ //MYSQL_TYPE_DATE MYSQL_TYPE_TIME
+ STRING_RESULT, STRING_RESULT,
+ //MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR
+ STRING_RESULT, INT_RESULT,
+ //MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR
+ STRING_RESULT, STRING_RESULT,
+ //MYSQL_TYPE_BIT <16>-<245>
+ STRING_RESULT,
+ //MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM
+ DECIMAL_RESULT, STRING_RESULT,
+ //MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB
+ STRING_RESULT, STRING_RESULT,
+ //MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB
+ STRING_RESULT, STRING_RESULT,
+ //MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING
+ STRING_RESULT, STRING_RESULT,
+ //MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY
+ STRING_RESULT, STRING_RESULT
+};
+
+
+/*
+ Test if the given string contains important data:
+ not spaces for character string,
+ or any data for binary string.
+
+ SYNOPSIS
+ test_if_important_data()
+ cs Character set
+ str String to test
+ strend String end
+
+ RETURN
+ FALSE - If string does not have important data
+ TRUE - If string has some important data
+*/
+
+static bool
+test_if_important_data(CHARSET_INFO *cs, const char *str, const char *strend)
+{
+ if (cs != &my_charset_bin)
+ str+= cs->cset->scan(cs, str, strend, MY_SEQ_SPACES);
+ return (str < strend);
+}
+
+
+/**
+ Function to compare two unsigned integers for their relative order.
+ Used below. In an anonymous namespace to not clash with definitions
+ in other files.
+ */
+
+CPP_UNNAMED_NS_START
+
+int compare(unsigned int a, unsigned int b)
+{
+ if (a < b)
+ return -1;
+ if (b < a)
+ return 1;
+ return 0;
+}
+
+CPP_UNNAMED_NS_END
+
+/**
+ Detect Item_result by given field type of UNION merge result.
+
+ @param field_type given field type
+
+ @return
+ Item_result (type of internal MySQL expression result)
+*/
+
+Item_result Field::result_merge_type(enum_field_types field_type)
+{
+ DBUG_ASSERT(real_type_to_type(field_type) < FIELDTYPE_TEAR_FROM ||
+ real_type_to_type(field_type) > FIELDTYPE_TEAR_TO);
+ return field_types_result_type[field_type2index(field_type)];
+}
+
+/*****************************************************************************
+ Static help functions
+*****************************************************************************/
+
+/**
+ Output a warning for erroneous conversion of strings to numerical
+ values. For use with ER_TRUNCATED_WRONG_VALUE[_FOR_FIELD]
+
+ @param thd THD object
+ @param str pointer to string that failed to be converted
+ @param length length of string
+ @param cs charset for string
+ @param typestr string describing type converted to
+ @param error error value to output
+ @param field_name (for *_FOR_FIELD) name of field
+ @param row_num (for *_FOR_FIELD) row number
+ */
+static void push_numerical_conversion_warning(THD* thd, const char* str,
+ uint length, CHARSET_INFO* cs,
+ const char* typestr, int error,
+ const char* field_name="UNKNOWN",
+ ulong row_num=0)
+{
+ char buf[MY_MAX(MY_MAX(DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE,
+ LONGLONG_TO_STRING_CONVERSION_BUFFER_SIZE),
+ DECIMAL_TO_STRING_CONVERSION_BUFFER_SIZE)];
+
+ String tmp(buf, sizeof(buf), cs);
+ tmp.copy(str, length, cs);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ error, ER(error), typestr, tmp.c_ptr(),
+ field_name, row_num);
+}
+
+
+/**
+ Check whether a field type can be partially indexed by a key.
+
+ This is a static method, rather than a virtual function, because we need
+ to check the type of a non-Field in mysql_alter_table().
+
+ @param type field type
+
+ @retval
+ TRUE Type can have a prefixed key
+ @retval
+ FALSE Type can not have a prefixed key
+*/
+
+bool Field::type_can_have_key_part(enum enum_field_types type)
+{
+ switch (type) {
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_GEOMETRY:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+
+void Field::make_sort_key(uchar *buff,uint length)
+{
+ if (maybe_null())
+ {
+ if (is_null())
+ {
+ bzero(buff, length + 1);
+ return;
+ }
+ *buff++= 1;
+ }
+ sort_string(buff, length);
+}
+
+
+/**
+ @brief
+ Determine the relative position of the field value in a numeric interval
+
+ @details
+ The function returns a double number between 0.0 and 1.0 as the relative
+ position of the value of the this field in the numeric interval of [min,max].
+ If the value is not in the interval the the function returns 0.0 when
+ the value is less than min, and, 1.0 when the value is greater than max.
+
+ @param min value of the left end of the interval
+ @param max value of the right end of the interval
+
+ @return
+ relative position of the field value in the numeric interval [min,max]
+*/
+
+double Field::pos_in_interval_val_real(Field *min, Field *max)
+{
+ double n, d;
+ n= val_real() - min->val_real();
+ if (n < 0)
+ return 0.0;
+ d= max->val_real() - min->val_real();
+ if (d <= 0)
+ return 1.0;
+ return MY_MIN(n/d, 1.0);
+}
+
+
+static
+inline ulonglong char_prefix_to_ulonglong(uchar *src)
+{
+ uint sz= sizeof(ulonglong);
+ for (uint i= 0; i < sz/2; i++)
+ {
+ uchar tmp= src[i];
+ src[i]= src[sz-1-i];
+ src[sz-1-i]= tmp;
+ }
+ return uint8korr(src);
+}
+
+/*
+ Compute res = a - b, without losing precision and taking care that these are
+ unsigned numbers.
+*/
+static inline double safe_substract(ulonglong a, ulonglong b)
+{
+ return (a > b)? double(a - b) : -double(b - a);
+}
+
+
+/**
+ @brief
+ Determine the relative position of the field value in a string interval
+
+ @details
+ The function returns a double number between 0.0 and 1.0 as the relative
+ position of the value of the this field in the string interval of [min,max].
+ If the value is not in the interval the the function returns 0.0 when
+ the value is less than min, and, 1.0 when the value is greater than max.
+
+ @note
+ To calculate the relative position of the string value v in the interval
+ [min, max] the function first converts the beginning of these three
+ strings v, min, max into the strings that are used for byte comparison.
+ For each string not more sizeof(ulonglong) first bytes are taken
+ from the result of conversion. Then these bytes are interpreted as the
+ big-endian representation of an ulonglong integer. The values of these
+ integer numbers obtained for the strings v, min, max are used to calculate
+ the position of v in [min,max] in the same way is it's done for numeric
+ fields (see Field::pos_in_interval_val_real).
+
+ @todo
+ Improve the procedure for the case when min and max have the same
+ beginning
+
+ @param min value of the left end of the interval
+ @param max value of the right end of the interval
+
+ @return
+ relative position of the field value in the string interval [min,max]
+*/
+
+double Field::pos_in_interval_val_str(Field *min, Field *max, uint data_offset)
+{
+ uchar mp_prefix[sizeof(ulonglong)];
+ uchar minp_prefix[sizeof(ulonglong)];
+ uchar maxp_prefix[sizeof(ulonglong)];
+ ulonglong mp, minp, maxp;
+ my_strnxfrm(charset(), mp_prefix, sizeof(mp),
+ ptr + data_offset,
+ data_length());
+ my_strnxfrm(charset(), minp_prefix, sizeof(minp),
+ min->ptr + data_offset,
+ min->data_length());
+ my_strnxfrm(charset(), maxp_prefix, sizeof(maxp),
+ max->ptr + data_offset,
+ max->data_length());
+ mp= char_prefix_to_ulonglong(mp_prefix);
+ minp= char_prefix_to_ulonglong(minp_prefix);
+ maxp= char_prefix_to_ulonglong(maxp_prefix);
+ double n, d;
+ n= safe_substract(mp, minp);
+ if (n < 0)
+ return 0.0;
+ d= safe_substract(maxp, minp);
+ if (d <= 0)
+ return 1.0;
+ return MY_MIN(n/d, 1.0);
+}
+
+
+/**
+ Numeric fields base class constructor.
+*/
+Field_num::Field_num(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg,
+ uint8 dec_arg, bool zero_arg, bool unsigned_arg)
+ :Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg),
+ dec(dec_arg),zerofill(zero_arg),unsigned_flag(unsigned_arg)
+{
+ if (zerofill)
+ flags|=ZEROFILL_FLAG;
+ if (unsigned_flag)
+ flags|=UNSIGNED_FLAG;
+}
+
+
+void Field_num::prepend_zeros(String *value)
+{
+ int diff;
+ if ((diff= (int) (field_length - value->length())) > 0)
+ {
+ bmove_upp((uchar*) value->ptr()+field_length,
+ (uchar*) value->ptr()+value->length(),
+ value->length());
+ bfill((uchar*) value->ptr(),diff,'0');
+ value->length(field_length);
+ (void) value->c_ptr_quick(); // Avoid warnings in purify
+ }
+}
+
+/**
+ Test if given number is a int.
+
+ @todo
+ Make this multi-byte-character safe
+
+ @param str String to test
+ @param length Length of 'str'
+ @param int_end Pointer to char after last used digit
+ @param cs Character set
+
+ @note
+ This is called after one has called strntoull10rnd() function.
+
+ @retval
+ 0 OK
+ @retval
+ 1 error: empty string or wrong integer.
+ @retval
+ 2 error: garbage at the end of string.
+*/
+
+int Field_num::check_int(CHARSET_INFO *cs, const char *str, int length,
+ const char *int_end, int error)
+{
+ /* Test if we get an empty string or wrong integer */
+ if (str == int_end || error == MY_ERRNO_EDOM)
+ {
+ ErrConvString err(str, length, cs);
+ push_warning_printf(get_thd(), Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
+ ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
+ "integer", err.ptr(), field_name,
+ (ulong) table->in_use->get_stmt_da()->
+ current_row_for_warning());
+ return 1;
+ }
+ /* Test if we have garbage at the end of the given string. */
+ if (test_if_important_data(cs, int_end, str + length))
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ return 2;
+ }
+ return 0;
+}
+
+
+/*
+ Conver a string to an integer then check bounds.
+
+ SYNOPSIS
+ Field_num::get_int
+ cs Character set
+ from String to convert
+ len Length of the string
+ rnd OUT longlong value
+ unsigned_max max unsigned value
+ signed_min min signed value
+ signed_max max signed value
+
+ DESCRIPTION
+ The function calls strntoull10rnd() to get an integer value then
+ check bounds and errors returned. In case of any error a warning
+ is raised.
+
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+bool Field_num::get_int(CHARSET_INFO *cs, const char *from, uint len,
+ longlong *rnd, ulonglong unsigned_max,
+ longlong signed_min, longlong signed_max)
+{
+ char *end;
+ int error;
+
+ *rnd= (longlong) cs->cset->strntoull10rnd(cs, from, len,
+ unsigned_flag, &end,
+ &error);
+ if (unsigned_flag)
+ {
+
+ if ((((ulonglong) *rnd > unsigned_max) &&
+ (*rnd= (longlong) unsigned_max)) ||
+ error == MY_ERRNO_ERANGE)
+ {
+ goto out_of_range;
+ }
+ }
+ else
+ {
+ if (*rnd < signed_min)
+ {
+ *rnd= signed_min;
+ goto out_of_range;
+ }
+ else if (*rnd > signed_max)
+ {
+ *rnd= signed_max;
+ goto out_of_range;
+ }
+ }
+ if (get_thd()->count_cuted_fields &&
+ check_int(cs, from, len, end, error))
+ return 1;
+ return 0;
+
+out_of_range:
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ return 1;
+}
+
+
+/**
+ Process decimal library return codes and issue warnings for overflow and
+ truncation.
+
+ @param op_result decimal library return code (E_DEC_* see include/decimal.h)
+
+ @retval
+ 1 there was overflow
+ @retval
+ 0 no error or some other errors except overflow
+*/
+
+int Field::warn_if_overflow(int op_result)
+{
+ if (op_result == E_DEC_OVERFLOW)
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ return 1;
+ }
+ if (op_result == E_DEC_TRUNCATED)
+ {
+ set_warning(Sql_condition::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1);
+ /* We return 0 here as this is not a critical issue */
+ }
+ return 0;
+}
+
+
+/**
+ Interpret field value as an integer but return the result as a string.
+
+ This is used for printing bit_fields as numbers while debugging.
+*/
+
+String *Field::val_int_as_str(String *val_buffer, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ CHARSET_INFO *cs= &my_charset_bin;
+ uint length;
+ longlong value= val_int();
+
+ if (val_buffer->alloc(MY_INT64_NUM_DECIMAL_DIGITS))
+ return 0;
+ length= (uint) (*cs->cset->longlong10_to_str)(cs, (char*) val_buffer->ptr(),
+ MY_INT64_NUM_DECIMAL_DIGITS,
+ unsigned_val ? 10 : -10,
+ value);
+ val_buffer->length(length);
+ return val_buffer;
+}
+
+
+/// This is used as a table name when the table structure is not set up
+Field::Field(uchar *ptr_arg,uint32 length_arg,uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ utype unireg_check_arg, const char *field_name_arg)
+ :ptr(ptr_arg), null_ptr(null_ptr_arg), table(0), orig_table(0),
+ table_name(0), field_name(field_name_arg), option_list(0),
+ option_struct(0), key_start(0), part_of_key(0),
+ part_of_key_not_clustered(0), part_of_sortkey(0),
+ unireg_check(unireg_check_arg), field_length(length_arg),
+ null_bit(null_bit_arg), is_created_from_null_item(FALSE),
+ read_stats(NULL), collected_stats(0),
+ vcol_info(0),
+ stored_in_db(TRUE)
+{
+ flags=null_ptr ? 0: NOT_NULL_FLAG;
+ comment.str= (char*) "";
+ comment.length=0;
+ field_index= 0;
+ is_stat_field= FALSE;
+ cond_selectivity= 1.0;
+ next_equal_field= NULL;
+}
+
+
+void Field::hash(ulong *nr, ulong *nr2)
+{
+ if (is_null())
+ {
+ *nr^= (*nr << 1) | 1;
+ }
+ else
+ {
+ uint len= pack_length();
+ CHARSET_INFO *cs= sort_charset();
+ cs->coll->hash_sort(cs, ptr, len, nr, nr2);
+ }
+}
+
+size_t
+Field::do_last_null_byte() const
+{
+ DBUG_ASSERT(null_ptr == NULL || null_ptr >= table->record[0]);
+ if (null_ptr)
+ return (size_t) (null_ptr - table->record[0]) + 1;
+ return LAST_NULL_BYTE_UNDEF;
+}
+
+
+void Field::copy_from_tmp(int row_offset)
+{
+ memcpy(ptr,ptr+row_offset,pack_length());
+ if (null_ptr)
+ {
+ *null_ptr= (uchar) ((null_ptr[0] & (uchar) ~(uint) null_bit) |
+ (null_ptr[row_offset] & (uchar) null_bit));
+ }
+}
+
+
+bool Field::send_binary(Protocol *protocol)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff),charset());
+ val_str(&tmp);
+ return protocol->store(tmp.ptr(), tmp.length(), tmp.charset());
+}
+
+
+/**
+ Check to see if field size is compatible with destination.
+
+ This method is used in row-based replication to verify that the
+ slave's field size is less than or equal to the master's field
+ size. The encoded field metadata (from the master or source) is
+ decoded and compared to the size of this field (the slave or
+ destination).
+
+ @note
+
+ The comparison is made so that if the source data (from the master)
+ is less than the target data (on the slave), -1 is returned in @c
+ <code>*order_var</code>. This implies that a conversion is
+ necessary, but that it is lossy and can result in truncation of the
+ value.
+
+ If the source data is strictly greater than the target data, 1 is
+ returned in <code>*order_var</code>. This implies that the source
+ type can is contained in the target type and that a conversion is
+ necessary but is non-lossy.
+
+ If no conversion is required to fit the source type in the target
+ type, 0 is returned in <code>*order_var</code>.
+
+ @param field_metadata Encoded size in field metadata
+ @param mflags Flags from the table map event for the table.
+ @param order_var Pointer to variable where the order
+ between the source field and this field
+ will be returned.
+
+ @return @c true if this field's size is compatible with the
+ master's field size, @c false otherwise.
+*/
+bool Field::compatible_field_size(uint field_metadata,
+ Relay_log_info *rli_arg __attribute__((unused)),
+ uint16 mflags __attribute__((unused)),
+ int *order_var)
+{
+ uint const source_size= pack_length_from_metadata(field_metadata);
+ uint const destination_size= row_pack_length();
+ DBUG_PRINT("debug", ("real_type: %d, source_size: %u, destination_size: %u",
+ real_type(), source_size, destination_size));
+ *order_var = compare(source_size, destination_size);
+ return true;
+}
+
+
+int Field::store(const char *to, uint length, CHARSET_INFO *cs,
+ enum_check_fields check_level)
+{
+ int res;
+ THD *thd= get_thd();
+ enum_check_fields old_check_level= thd->count_cuted_fields;
+ thd->count_cuted_fields= check_level;
+ res= store(to, length, cs);
+ thd->count_cuted_fields= old_check_level;
+ return res;
+}
+
+
+/**
+ Pack the field into a format suitable for storage and transfer.
+
+ To implement packing functionality, only the virtual function
+ should be overridden. The other functions are just convenience
+ functions and hence should not be overridden.
+
+ @note The default method for packing fields just copy the raw bytes
+ of the record into the destination, but never more than
+ <code>max_length</code> characters.
+
+ @param to
+ Pointer to memory area where representation of field should be put.
+
+ @param from
+ Pointer to memory area where record representation of field is
+ stored.
+
+ @param max_length
+ Maximum length of the field, as given in the column definition. For
+ example, for <code>CHAR(1000)</code>, the <code>max_length</code>
+ is 1000. This information is sometimes needed to decide how to pack
+ the data.
+
+*/
+uchar *
+Field::pack(uchar *to, const uchar *from, uint max_length)
+{
+ uint32 length= pack_length();
+ set_if_smaller(length, max_length);
+ memcpy(to, from, length);
+ return to+length;
+}
+
+/**
+ Unpack a field from row data.
+
+ This method is used to unpack a field from a master whose size of
+ the field is less than that of the slave.
+
+ The <code>param_data</code> parameter is a two-byte integer (stored
+ in the least significant 16 bits of the unsigned integer) usually
+ consisting of two parts: the real type in the most significant byte
+ and a original pack length in the least significant byte.
+
+ The exact layout of the <code>param_data</code> field is given by
+ the <code>Table_map_log_event::save_field_metadata()</code>.
+
+ This is the default method for unpacking a field. It just copies
+ the memory block in byte order (of original pack length bytes or
+ length of field, whichever is smaller).
+
+ @param to Destination of the data
+ @param from Source of the data
+ @param param_data Real type and original pack length of the field
+ data
+
+ @return New pointer into memory based on from + length of the data
+ @return 0 if wrong data
+*/
+const uchar *
+Field::unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data)
+{
+ uint length=pack_length(), len;
+ int from_type= 0;
+ /*
+ If from length is > 255, it has encoded data in the upper bits. Need
+ to mask it out.
+ */
+ if (param_data > 255)
+ {
+ from_type= (param_data & 0xff00) >> 8U; // real_type.
+ param_data= param_data & 0x00ff; // length.
+ }
+
+ if ((param_data == 0) ||
+ (length == param_data) ||
+ (from_type != real_type()))
+ {
+ if (from + length > from_end)
+ return 0; // Error in data
+
+ memcpy(to, from, length);
+ return from+length;
+ }
+
+ len= (param_data && (param_data < length)) ? param_data : length;
+
+ if (from + len > from_end)
+ return 0; // Error in data
+
+ memcpy(to, from, len);
+ return from+len;
+}
+
+
+my_decimal *Field::val_decimal(my_decimal *decimal)
+{
+ /* This never have to be called */
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+
+void Field_num::add_zerofill_and_unsigned(String &res) const
+{
+ if (unsigned_flag)
+ res.append(STRING_WITH_LEN(" unsigned"));
+ if (zerofill)
+ res.append(STRING_WITH_LEN(" zerofill"));
+}
+
+
+void Field::make_field(Send_field *field)
+{
+ if (orig_table && orig_table->s->db.str && *orig_table->s->db.str)
+ {
+ field->db_name= orig_table->s->db.str;
+ if (orig_table->pos_in_table_list &&
+ orig_table->pos_in_table_list->schema_table)
+ field->org_table_name= (orig_table->pos_in_table_list->
+ schema_table->table_name);
+ else
+ field->org_table_name= orig_table->s->table_name.str;
+ }
+ else
+ field->org_table_name= field->db_name= "";
+ if (orig_table && orig_table->alias.ptr())
+ {
+ field->table_name= orig_table->alias.ptr();
+ field->org_col_name= field_name;
+ }
+ else
+ {
+ field->table_name= "";
+ field->org_col_name= "";
+ }
+ field->col_name= field_name;
+ field->charsetnr= charset()->number;
+ field->length=field_length;
+ field->type=type();
+ field->flags=table->maybe_null ? (flags & ~NOT_NULL_FLAG) : flags;
+ field->decimals= 0;
+}
+
+
+/**
+ Conversion from decimal to longlong with checking overflow and
+ setting correct value (min/max) in case of overflow.
+
+ @param val value which have to be converted
+ @param unsigned_flag type of integer in which we convert val
+ @param err variable to pass error code
+
+ @return
+ value converted from val
+*/
+longlong Field::convert_decimal2longlong(const my_decimal *val,
+ bool unsigned_flag, int *err)
+{
+ longlong i;
+ if (unsigned_flag)
+ {
+ if (val->sign())
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ i= 0;
+ *err= 1;
+ }
+ else if (warn_if_overflow(my_decimal2int((E_DEC_ERROR &
+ ~E_DEC_OVERFLOW &
+ ~E_DEC_TRUNCATED),
+ val, TRUE, &i)))
+ {
+ i= ~(longlong) 0;
+ *err= 1;
+ }
+ }
+ else if (warn_if_overflow(my_decimal2int((E_DEC_ERROR &
+ ~E_DEC_OVERFLOW &
+ ~E_DEC_TRUNCATED),
+ val, FALSE, &i)))
+ {
+ i= (val->sign() ? LONGLONG_MIN : LONGLONG_MAX);
+ *err= 1;
+ }
+ return i;
+}
+
+
+/**
+ Storing decimal in integer fields.
+
+ @param val value for storing
+
+ @note
+ This method is used by all integer fields, real/decimal redefine it
+
+ @retval
+ 0 OK
+ @retval
+ !=0 error
+*/
+
+int Field_num::store_decimal(const my_decimal *val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int err= 0;
+ longlong i= convert_decimal2longlong(val, unsigned_flag, &err);
+ return MY_TEST(err | store(i, unsigned_flag));
+}
+
+
+/**
+ Return decimal value of integer field.
+
+ @param decimal_value buffer for storing decimal value
+
+ @note
+ This method is used by all integer fields, real/decimal redefine it.
+ All longlong values fit in our decimal buffer which cal store 8*9=72
+ digits of integer number
+
+ @return
+ pointer to decimal buffer with value of field
+*/
+
+my_decimal* Field_num::val_decimal(my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ DBUG_ASSERT(result_type() == INT_RESULT);
+ longlong nr= val_int();
+ int2my_decimal(E_DEC_FATAL_ERROR, nr, unsigned_flag, decimal_value);
+ return decimal_value;
+}
+
+
+Field_str::Field_str(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg, CHARSET_INFO *charset_arg)
+ :Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg)
+{
+ field_charset= charset_arg;
+ if (charset_arg->state & MY_CS_BINSORT)
+ flags|=BINARY_FLAG;
+ field_derivation= DERIVATION_IMPLICIT;
+}
+
+
+void Field_num::make_field(Send_field *field)
+{
+ Field::make_field(field);
+ field->decimals= dec;
+}
+
+/**
+ Decimal representation of Field_str.
+
+ @param d value for storing
+
+ @note
+ Field_str is the base class for fields like Field_enum,
+ Field_date and some similar. Some dates use fraction and also
+ string value should be converted to floating point value according
+ our rules, so we use double to store value of decimal in string.
+
+ @todo
+ use decimal2string?
+
+ @retval
+ 0 OK
+ @retval
+ !=0 error
+*/
+
+int Field_str::store_decimal(const my_decimal *d)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ double val;
+ /* TODO: use decimal2string? */
+ int err= warn_if_overflow(my_decimal2double(E_DEC_FATAL_ERROR &
+ ~E_DEC_OVERFLOW, d, &val));
+ return err | store(val);
+}
+
+
+my_decimal *Field_str::val_decimal(my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ longlong nr= val_int();
+ int2my_decimal(E_DEC_FATAL_ERROR, nr, 0, decimal_value);
+ return decimal_value;
+}
+
+
+uint Field::fill_cache_field(CACHE_FIELD *copy)
+{
+ uint store_length;
+ copy->str= ptr;
+ copy->length= pack_length();
+ copy->field= this;
+ if (flags & BLOB_FLAG)
+ {
+ copy->type= CACHE_BLOB;
+ copy->length-= portable_sizeof_char_ptr;
+ return copy->length;
+ }
+ else if (!zero_pack() &&
+ (type() == MYSQL_TYPE_STRING && copy->length >= 4 &&
+ copy->length < 256))
+ {
+ copy->type= CACHE_STRIPPED; /* Remove end space */
+ store_length= 2;
+ }
+ else if (type() == MYSQL_TYPE_VARCHAR)
+ {
+ copy->type= pack_length()-row_pack_length() == 1 ? CACHE_VARSTR1:
+ CACHE_VARSTR2;
+ store_length= 0;
+ }
+ else
+ {
+ copy->type= 0;
+ store_length= 0;
+ }
+ return copy->length + store_length;
+}
+
+
+bool Field::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ char buff[40];
+ String tmp(buff,sizeof(buff),&my_charset_bin),*res;
+ if (!(res=val_str(&tmp)) ||
+ str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(),
+ ltime, fuzzydate))
+ return 1;
+ return 0;
+}
+
+/**
+ This is called when storing a date in a string.
+
+ @note
+ Needs to be changed if/when we want to support different time formats.
+*/
+
+int Field::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ char buff[MAX_DATE_STRING_REP_LENGTH];
+ uint length= (uint) my_TIME_to_str(ltime, buff, dec);
+ /* Avoid conversion when field character set is ASCII compatible */
+ return store(buff, length, (charset()->state & MY_CS_NONASCII) ?
+ &my_charset_latin1 : charset());
+}
+
+
+bool Field::optimize_range(uint idx, uint part)
+{
+ return MY_TEST(table->file->index_flags(idx, part, 1) & HA_READ_RANGE);
+}
+
+
+Field *Field::new_field(MEM_ROOT *root, TABLE *new_table,
+ bool keep_type __attribute__((unused)))
+{
+ Field *tmp;
+ if (!(tmp= (Field*) memdup_root(root,(char*) this,size_of())))
+ return 0;
+
+ if (tmp->table->maybe_null)
+ tmp->flags&= ~NOT_NULL_FLAG;
+ tmp->table= new_table;
+ tmp->key_start.init(0);
+ tmp->part_of_key.init(0);
+ tmp->part_of_sortkey.init(0);
+ /*
+ TODO: it is not clear why this method needs to reset unireg_check.
+ Try not to reset it, or explain why it needs to be reset.
+ */
+ tmp->unireg_check= Field::NONE;
+ tmp->flags&= (NOT_NULL_FLAG | BLOB_FLAG | UNSIGNED_FLAG |
+ ZEROFILL_FLAG | BINARY_FLAG | ENUM_FLAG | SET_FLAG);
+ tmp->reset_fields();
+ return tmp;
+}
+
+
+Field *Field::new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit)
+{
+ Field *tmp;
+ if ((tmp= new_field(root, new_table, table == new_table)))
+ {
+ tmp->ptr= new_ptr;
+ tmp->null_ptr= new_null_ptr;
+ tmp->null_bit= new_null_bit;
+ }
+ return tmp;
+}
+
+
+/* This is used to generate a field in TABLE from TABLE_SHARE */
+
+Field *Field::clone(MEM_ROOT *root, TABLE *new_table)
+{
+ Field *tmp;
+ if ((tmp= (Field*) memdup_root(root,(char*) this,size_of())))
+ {
+ tmp->init(new_table);
+ tmp->move_field_offset((my_ptrdiff_t) (new_table->record[0] -
+ new_table->s->default_values));
+ }
+ return tmp;
+}
+
+
+
+Field *Field::clone(MEM_ROOT *root, TABLE *new_table, my_ptrdiff_t diff,
+ bool stat_flag)
+{
+ Field *tmp;
+ if ((tmp= (Field*) memdup_root(root,(char*) this,size_of())))
+ {
+ tmp->init(new_table);
+ tmp->move_field_offset(diff);
+ }
+ tmp->is_stat_field= stat_flag;
+ return tmp;
+}
+
+
+Field *Field::clone(MEM_ROOT *root, my_ptrdiff_t diff)
+{
+ Field *tmp;
+ if ((tmp= (Field*) memdup_root(root,(char*) this,size_of())))
+ {
+ tmp->move_field_offset(diff);
+ }
+ return tmp;
+}
+
+
+/****************************************************************************
+ Field_null, a field that always return NULL
+****************************************************************************/
+
+void Field_null::sql_type(String &res) const
+{
+ res.set_ascii(STRING_WITH_LEN("null"));
+}
+
+
+/****************************************************************************
+ Functions for the Field_decimal class
+ This is an number stored as a pre-space (or pre-zero) string
+****************************************************************************/
+
+int
+Field_decimal::reset(void)
+{
+ Field_decimal::store(STRING_WITH_LEN("0"),&my_charset_bin);
+ return 0;
+}
+
+void Field_decimal::overflow(bool negative)
+{
+ uint len=field_length;
+ uchar *to=ptr, filler= '9';
+
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ if (negative)
+ {
+ if (!unsigned_flag)
+ {
+ /* Put - sign as a first digit so we'll have -999..999 or 999..999 */
+ *to++ = '-';
+ len--;
+ }
+ else
+ {
+ filler= '0'; // Fill up with 0
+ if (!zerofill)
+ {
+ /*
+ Handle unsigned integer without zerofill, in which case
+ the number should be of format ' 0' or ' 0.000'
+ */
+ uint whole_part=field_length- (dec ? dec+2 : 1);
+ // Fill with spaces up to the first digit
+ bfill(to, whole_part, ' ');
+ to+= whole_part;
+ len-= whole_part;
+ // The main code will also handle the 0 before the decimal point
+ }
+ }
+ }
+ bfill(to, len, filler);
+ if (dec)
+ ptr[field_length-dec-1]='.';
+ return;
+}
+
+
+int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff,sizeof(buff), &my_charset_bin);
+ const uchar *from= (uchar*) from_arg;
+
+ /* Convert character set if the old one is multi uchar */
+ if (cs->mbmaxlen > 1)
+ {
+ uint dummy_errors;
+ tmp.copy((char*) from, len, cs, &my_charset_bin, &dummy_errors);
+ from= (uchar*) tmp.ptr();
+ len= tmp.length();
+ }
+
+ const uchar *end= from+len;
+ /* The pointer where the field value starts (i.e., "where to write") */
+ uchar *to= ptr;
+ uint tmp_dec, tmp_uint;
+ /*
+ The sign of the number : will be 0 (means positive but sign not
+ specified), '+' or '-'
+ */
+ uchar sign_char=0;
+ /* The pointers where prezeros start and stop */
+ const uchar *pre_zeros_from, *pre_zeros_end;
+ /* The pointers where digits at the left of '.' start and stop */
+ const uchar *int_digits_from, *int_digits_end;
+ /* The pointers where digits at the right of '.' start and stop */
+ const uchar *frac_digits_from, *frac_digits_end;
+ /* The sign of the exponent : will be 0 (means no exponent), '+' or '-' */
+ char expo_sign_char=0;
+ uint exponent=0; // value of the exponent
+ /*
+ Pointers used when digits move from the left of the '.' to the
+ right of the '.' (explained below)
+ */
+ const uchar *UNINIT_VAR(int_digits_tail_from);
+ /* Number of 0 that need to be added at the left of the '.' (1E3: 3 zeros) */
+ uint UNINIT_VAR(int_digits_added_zeros);
+ /*
+ Pointer used when digits move from the right of the '.' to the left
+ of the '.'
+ */
+ const uchar *UNINIT_VAR(frac_digits_head_end);
+ /* Number of 0 that need to be added at the right of the '.' (for 1E-3) */
+ uint UNINIT_VAR(frac_digits_added_zeros);
+ uchar *pos,*tmp_left_pos,*tmp_right_pos;
+ /* Pointers that are used as limits (begin and end of the field buffer) */
+ uchar *left_wall,*right_wall;
+ uchar tmp_char;
+ /*
+ To remember if get_thd()->cuted_fields has already been incremented,
+ to do that only once
+ */
+ bool is_cuted_fields_incr=0;
+
+ /*
+ There are three steps in this function :
+ - parse the input string
+ - modify the position of digits around the decimal dot '.'
+ according to the exponent value (if specified)
+ - write the formatted number
+ */
+
+ if ((tmp_dec=dec))
+ tmp_dec++;
+
+ /* skip pre-space */
+ while (from != end && my_isspace(&my_charset_bin,*from))
+ from++;
+ if (from == end)
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ is_cuted_fields_incr=1;
+ }
+ else if (*from == '+' || *from == '-') // Found some sign ?
+ {
+ sign_char= *from++;
+ /*
+ We allow "+" for unsigned decimal unless defined different
+ Both options allowed as one may wish not to have "+" for unsigned numbers
+ because of data processing issues
+ */
+ if (unsigned_flag)
+ {
+ if (sign_char=='-')
+ {
+ Field_decimal::overflow(1);
+ return 1;
+ }
+ /*
+ Defining this will not store "+" for unsigned decimal type even if
+ it is passed in numeric string. This will make some tests to fail
+ */
+#ifdef DONT_ALLOW_UNSIGNED_PLUS
+ else
+ sign_char=0;
+#endif
+ }
+ }
+
+ pre_zeros_from= from;
+ for (; from!=end && *from == '0'; from++) ; // Read prezeros
+ pre_zeros_end=int_digits_from=from;
+ /* Read non zero digits at the left of '.'*/
+ for (; from != end && my_isdigit(&my_charset_bin, *from) ; from++) ;
+ int_digits_end=from;
+ if (from!=end && *from == '.') // Some '.' ?
+ from++;
+ frac_digits_from= from;
+ /* Read digits at the right of '.' */
+ for (;from!=end && my_isdigit(&my_charset_bin, *from); from++) ;
+ frac_digits_end=from;
+ // Some exponentiation symbol ?
+ if (from != end && (*from == 'e' || *from == 'E'))
+ {
+ from++;
+ if (from != end && (*from == '+' || *from == '-')) // Some exponent sign ?
+ expo_sign_char= *from++;
+ else
+ expo_sign_char= '+';
+ /*
+ Read digits of the exponent and compute its value. We must care about
+ 'exponent' overflow, because as unsigned arithmetic is "modulo", big
+ exponents will become small (e.g. 1e4294967296 will become 1e0, and the
+ field will finally contain 1 instead of its max possible value).
+ */
+ for (;from!=end && my_isdigit(&my_charset_bin, *from); from++)
+ {
+ exponent=10*exponent+(*from-'0');
+ if (exponent>MAX_EXPONENT)
+ break;
+ }
+ }
+
+ /*
+ We only have to generate warnings if count_cuted_fields is set.
+ This is to avoid extra checks of the number when they are not needed.
+ Even if this flag is not set, it's OK to increment warnings, if
+ it makes the code easer to read.
+ */
+
+ if (get_thd()->count_cuted_fields)
+ {
+ // Skip end spaces
+ for (;from != end && my_isspace(&my_charset_bin, *from); from++) ;
+ if (from != end) // If still something left, warn
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ is_cuted_fields_incr=1;
+ }
+ }
+
+ /*
+ Now "move" digits around the decimal dot according to the exponent value,
+ and add necessary zeros.
+ Examples :
+ - 1E+3 : needs 3 more zeros at the left of '.' (int_digits_added_zeros=3)
+ - 1E-3 : '1' moves at the right of '.', and 2 more zeros are needed
+ between '.' and '1'
+ - 1234.5E-3 : '234' moves at the right of '.'
+ These moves are implemented with pointers which point at the begin
+ and end of each moved segment. Examples :
+ - 1234.5E-3 : before the code below is executed, the int_digits part is
+ from '1' to '4' and the frac_digits part from '5' to '5'. After the code
+ below, the int_digits part is from '1' to '1', the frac_digits_head
+ part is from '2' to '4', and the frac_digits part from '5' to '5'.
+ - 1234.5E3 : before the code below is executed, the int_digits part is
+ from '1' to '4' and the frac_digits part from '5' to '5'. After the code
+ below, the int_digits part is from '1' to '4', the int_digits_tail
+ part is from '5' to '5', the frac_digits part is empty, and
+ int_digits_added_zeros=2 (to make 1234500).
+ */
+
+ /*
+ Below tmp_uint cannot overflow with small enough MAX_EXPONENT setting,
+ as int_digits_added_zeros<=exponent<4G and
+ (int_digits_end-int_digits_from)<=max_allowed_packet<=2G and
+ (frac_digits_from-int_digits_tail_from)<=max_allowed_packet<=2G
+ */
+
+ if (!expo_sign_char)
+ tmp_uint=tmp_dec+(uint)(int_digits_end-int_digits_from);
+ else if (expo_sign_char == '-')
+ {
+ tmp_uint=MY_MIN(exponent,(uint)(int_digits_end-int_digits_from));
+ frac_digits_added_zeros=exponent-tmp_uint;
+ int_digits_end -= tmp_uint;
+ frac_digits_head_end=int_digits_end+tmp_uint;
+ tmp_uint=tmp_dec+(uint)(int_digits_end-int_digits_from);
+ }
+ else // (expo_sign_char=='+')
+ {
+ tmp_uint=MY_MIN(exponent,(uint)(frac_digits_end-frac_digits_from));
+ int_digits_added_zeros=exponent-tmp_uint;
+ int_digits_tail_from=frac_digits_from;
+ frac_digits_from=frac_digits_from+tmp_uint;
+ /*
+ We "eat" the heading zeros of the
+ int_digits.int_digits_tail.int_digits_added_zeros concatenation
+ (for example 0.003e3 must become 3 and not 0003)
+ */
+ if (int_digits_from == int_digits_end)
+ {
+ /*
+ There was nothing in the int_digits part, so continue
+ eating int_digits_tail zeros
+ */
+ for (; int_digits_tail_from != frac_digits_from &&
+ *int_digits_tail_from == '0'; int_digits_tail_from++) ;
+ if (int_digits_tail_from == frac_digits_from)
+ {
+ // there were only zeros in int_digits_tail too
+ int_digits_added_zeros=0;
+ }
+ }
+ tmp_uint= (uint) (tmp_dec+(int_digits_end-int_digits_from)+
+ (uint)(frac_digits_from-int_digits_tail_from)+
+ int_digits_added_zeros);
+ }
+
+ /*
+ Now write the formated number
+
+ First the digits of the int_% parts.
+ Do we have enough room to write these digits ?
+ If the sign is defined and '-', we need one position for it
+ */
+
+ if (field_length < tmp_uint + (int) (sign_char == '-'))
+ {
+ // too big number, change to max or min number
+ Field_decimal::overflow(sign_char == '-');
+ return 1;
+ }
+
+ /*
+ Tmp_left_pos is the position where the leftmost digit of
+ the int_% parts will be written
+ */
+ tmp_left_pos=pos=to+(uint)(field_length-tmp_uint);
+
+ // Write all digits of the int_% parts
+ while (int_digits_from != int_digits_end)
+ *pos++ = *int_digits_from++ ;
+
+ if (expo_sign_char == '+')
+ {
+ while (int_digits_tail_from != frac_digits_from)
+ *pos++= *int_digits_tail_from++;
+ while (int_digits_added_zeros-- >0)
+ *pos++= '0';
+ }
+ /*
+ Note the position where the rightmost digit of the int_% parts has been
+ written (this is to later check if the int_% parts contained nothing,
+ meaning an extra 0 is needed).
+ */
+ tmp_right_pos=pos;
+
+ /*
+ Step back to the position of the leftmost digit of the int_% parts,
+ to write sign and fill with zeros or blanks or prezeros.
+ */
+ pos=tmp_left_pos-1;
+ if (zerofill)
+ {
+ left_wall=to-1;
+ while (pos > left_wall) // Fill with zeros
+ *pos--='0';
+ }
+ else
+ {
+ left_wall=to+(sign_char != 0)-1;
+ if (!expo_sign_char) // If exponent was specified, ignore prezeros
+ {
+ for (;pos > left_wall && pre_zeros_from !=pre_zeros_end;
+ pre_zeros_from++)
+ *pos--= '0';
+ }
+ if (pos == tmp_right_pos-1)
+ *pos--= '0'; // no 0 has ever been written, so write one
+ left_wall= to-1;
+ if (sign_char && pos != left_wall)
+ {
+ /* Write sign if possible (it is if sign is '-') */
+ *pos--= sign_char;
+ }
+ while (pos != left_wall)
+ *pos--=' '; //fill with blanks
+ }
+
+ /*
+ Write digits of the frac_% parts ;
+ Depending on get_thd()->count_cutted_fields, we may also want
+ to know if some non-zero tail of these parts will
+ be truncated (for example, 0.002->0.00 will generate a warning,
+ while 0.000->0.00 will not)
+ (and 0E1000000000 will not, while 1E-1000000000 will)
+ */
+
+ pos=to+(uint)(field_length-tmp_dec); // Calculate post to '.'
+ right_wall=to+field_length;
+ if (pos != right_wall)
+ *pos++='.';
+
+ if (expo_sign_char == '-')
+ {
+ while (frac_digits_added_zeros-- > 0)
+ {
+ if (pos == right_wall)
+ {
+ if (get_thd()->count_cuted_fields && !is_cuted_fields_incr)
+ break; // Go on below to see if we lose non zero digits
+ return 0;
+ }
+ *pos++='0';
+ }
+ while (int_digits_end != frac_digits_head_end)
+ {
+ tmp_char= *int_digits_end++;
+ if (pos == right_wall)
+ {
+ if (tmp_char != '0') // Losing a non zero digit ?
+ {
+ if (!is_cuted_fields_incr)
+ set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ return 0;
+ }
+ continue;
+ }
+ *pos++= tmp_char;
+ }
+ }
+
+ for (;frac_digits_from!=frac_digits_end;)
+ {
+ tmp_char= *frac_digits_from++;
+ if (pos == right_wall)
+ {
+ if (tmp_char != '0') // Losing a non zero digit ?
+ {
+ if (!is_cuted_fields_incr)
+ {
+ /*
+ This is a note, not a warning, as we don't want to abort
+ when we cut decimals in strict mode
+ */
+ set_warning(Sql_condition::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1);
+ }
+ return 0;
+ }
+ continue;
+ }
+ *pos++= tmp_char;
+ }
+
+ while (pos != right_wall)
+ *pos++='0'; // Fill with zeros at right of '.'
+ return 0;
+}
+
+
+int Field_decimal::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ if (unsigned_flag && nr < 0)
+ {
+ overflow(1);
+ return 1;
+ }
+
+ if (!isfinite(nr)) // Handle infinity as special case
+ {
+ overflow(nr < 0.0);
+ return 1;
+ }
+
+ reg4 uint i;
+ size_t length;
+ uchar fyllchar,*to;
+ char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE];
+
+ fyllchar = zerofill ? (char) '0' : (char) ' ';
+ length= my_fcvt(nr, dec, buff, NULL);
+
+ if (length > field_length)
+ {
+ overflow(nr < 0.0);
+ return 1;
+ }
+ else
+ {
+ to=ptr;
+ for (i=field_length-length ; i-- > 0 ;)
+ *to++ = fyllchar;
+ memcpy(to,buff,length);
+ return 0;
+ }
+}
+
+
+int Field_decimal::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ char buff[22];
+ uint length, int_part;
+ char fyllchar;
+ uchar *to;
+
+ if (nr < 0 && unsigned_flag && !unsigned_val)
+ {
+ overflow(1);
+ return 1;
+ }
+ length= (uint) (longlong10_to_str(nr,buff,unsigned_val ? 10 : -10) - buff);
+ int_part= field_length- (dec ? dec+1 : 0);
+
+ if (length > int_part)
+ {
+ overflow(!unsigned_val && nr < 0L); /* purecov: inspected */
+ return 1;
+ }
+
+ fyllchar = zerofill ? (char) '0' : (char) ' ';
+ to= ptr;
+ for (uint i=int_part-length ; i-- > 0 ;)
+ *to++ = fyllchar;
+ memcpy(to,buff,length);
+ if (dec)
+ {
+ to[length]='.';
+ bfill(to+length+1,dec,'0');
+ }
+ return 0;
+}
+
+
+double Field_decimal::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int not_used;
+ char *end_not_used;
+ return my_strntod(&my_charset_bin, (char*) ptr, field_length, &end_not_used,
+ &not_used);
+}
+
+longlong Field_decimal::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int not_used;
+ if (unsigned_flag)
+ return my_strntoull(&my_charset_bin, (char*) ptr, field_length, 10, NULL,
+ &not_used);
+ return my_strntoll(&my_charset_bin, (char*) ptr, field_length, 10, NULL,
+ &not_used);
+}
+
+
+String *Field_decimal::val_str(String *val_buffer __attribute__((unused)),
+ String *val_ptr)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ uchar *str;
+ size_t tmp_length;
+
+ for (str=ptr ; *str == ' ' ; str++) ;
+ val_ptr->set_charset(&my_charset_numeric);
+ tmp_length= (size_t) (str-ptr);
+ if (field_length < tmp_length) // Error in data
+ val_ptr->length(0);
+ else
+ val_ptr->set_ascii((const char*) str, field_length-tmp_length);
+ return val_ptr;
+}
+
+/**
+ Should be able to handle at least the following fixed decimal formats:
+ 5.00 , -1.0, 05, -05, +5 with optional pre/end space
+*/
+
+int Field_decimal::cmp(const uchar *a_ptr,const uchar *b_ptr)
+{
+ const uchar *end;
+ int swap=0;
+ /* First remove prefixes '0', ' ', and '-' */
+ for (end=a_ptr+field_length;
+ a_ptr != end &&
+ (*a_ptr == *b_ptr ||
+ ((my_isspace(&my_charset_bin,*a_ptr) || *a_ptr == '+' ||
+ *a_ptr == '0') &&
+ (my_isspace(&my_charset_bin,*b_ptr) || *b_ptr == '+' ||
+ *b_ptr == '0')));
+ a_ptr++,b_ptr++)
+ {
+ if (*a_ptr == '-') // If both numbers are negative
+ swap= -1 ^ 1; // Swap result
+ }
+ if (a_ptr == end)
+ return 0;
+ if (*a_ptr == '-')
+ return -1;
+ if (*b_ptr == '-')
+ return 1;
+
+ while (a_ptr != end)
+ {
+ if (*a_ptr++ != *b_ptr++)
+ return swap ^ (a_ptr[-1] < b_ptr[-1] ? -1 : 1); // compare digits
+ }
+ return 0;
+}
+
+
+void Field_decimal::sort_string(uchar *to,uint length)
+{
+ uchar *str,*end;
+ for (str=ptr,end=ptr+length;
+ str != end &&
+ ((my_isspace(&my_charset_bin,*str) || *str == '+' ||
+ *str == '0')) ;
+ str++)
+ *to++=' ';
+ if (str == end)
+ return; /* purecov: inspected */
+
+ if (*str == '-')
+ {
+ *to++=1; // Smaller than any number
+ str++;
+ while (str != end)
+ if (my_isdigit(&my_charset_bin,*str))
+ *to++= (char) ('9' - *str++);
+ else
+ *to++= *str++;
+ }
+ else memcpy(to,str,(uint) (end-str));
+}
+
+
+void Field_decimal::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ uint tmp=field_length;
+ if (!unsigned_flag)
+ tmp--;
+ if (dec)
+ tmp--;
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "decimal(%d,%d)",tmp,dec));
+ add_zerofill_and_unsigned(res);
+}
+
+
+/****************************************************************************
+** Field_new_decimal
+****************************************************************************/
+
+Field_new_decimal::Field_new_decimal(uchar *ptr_arg,
+ uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg,
+ const char *field_name_arg,
+ uint8 dec_arg,bool zero_arg,
+ bool unsigned_arg)
+ :Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, dec_arg, zero_arg, unsigned_arg)
+{
+ precision= my_decimal_length_to_precision(len_arg, dec_arg, unsigned_arg);
+ set_if_smaller(precision, DECIMAL_MAX_PRECISION);
+ DBUG_ASSERT((precision <= DECIMAL_MAX_PRECISION) &&
+ (dec <= DECIMAL_MAX_SCALE));
+ bin_size= my_decimal_get_binary_size(precision, dec);
+}
+
+
+Field_new_decimal::Field_new_decimal(uint32 len_arg,
+ bool maybe_null_arg,
+ const char *name,
+ uint8 dec_arg,
+ bool unsigned_arg)
+ :Field_num((uchar*) 0, len_arg,
+ maybe_null_arg ? (uchar*) "": 0, 0,
+ NONE, name, dec_arg, 0, unsigned_arg)
+{
+ precision= my_decimal_length_to_precision(len_arg, dec_arg, unsigned_arg);
+ set_if_smaller(precision, DECIMAL_MAX_PRECISION);
+ DBUG_ASSERT((precision <= DECIMAL_MAX_PRECISION) &&
+ (dec <= DECIMAL_MAX_SCALE));
+ bin_size= my_decimal_get_binary_size(precision, dec);
+}
+
+
+Field *Field_new_decimal::create_from_item (Item *item)
+{
+ uint8 dec= item->decimals;
+ uint8 intg= item->decimal_precision() - dec;
+ uint32 len= item->max_char_length();
+
+ DBUG_ASSERT (item->result_type() == DECIMAL_RESULT);
+
+ /*
+ Trying to put too many digits overall in a DECIMAL(prec,dec)
+ will always throw a warning. We must limit dec to
+ DECIMAL_MAX_SCALE however to prevent an assert() later.
+ */
+
+ if (dec > 0)
+ {
+ signed int overflow;
+
+ dec= MY_MIN(dec, DECIMAL_MAX_SCALE);
+
+ /*
+ If the value still overflows the field with the corrected dec,
+ we'll throw out decimals rather than integers. This is still
+ bad and of course throws a truncation warning.
+ +1: for decimal point
+ */
+
+ const int required_length=
+ my_decimal_precision_to_length(intg + dec, dec,
+ item->unsigned_flag);
+
+ overflow= required_length - len;
+
+ if (overflow > 0)
+ dec= MY_MAX(0, dec - overflow); // too long, discard fract
+ else
+ /* Corrected value fits. */
+ len= required_length;
+ }
+ return new Field_new_decimal(len, item->maybe_null, item->name,
+ dec, item->unsigned_flag);
+}
+
+
+int Field_new_decimal::reset(void)
+{
+ store_value(&decimal_zero);
+ return 0;
+}
+
+
+/**
+ Generate max/min decimal value in case of overflow.
+
+ @param decimal_value buffer for value
+ @param sign sign of value which caused overflow
+*/
+
+void Field_new_decimal::set_value_on_overflow(my_decimal *decimal_value,
+ bool sign)
+{
+ DBUG_ENTER("Field_new_decimal::set_value_on_overflow");
+ max_my_decimal(decimal_value, precision, decimals());
+ if (sign)
+ {
+ if (unsigned_flag)
+ my_decimal_set_zero(decimal_value);
+ else
+ decimal_value->sign(TRUE);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Store decimal value in the binary buffer.
+
+ Checks if decimal_value fits into field size.
+ If it does, stores the decimal in the buffer using binary format.
+ Otherwise sets maximal number that can be stored in the field.
+
+ @param decimal_value my_decimal
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool Field_new_decimal::store_value(const my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ DBUG_ENTER("Field_new_decimal::store_value");
+#ifndef DBUG_OFF
+ {
+ char dbug_buff[DECIMAL_MAX_STR_LENGTH+2];
+ DBUG_PRINT("enter", ("value: %s", dbug_decimal_as_string(dbug_buff, decimal_value)));
+ }
+#endif
+
+ /* check that we do not try to write negative value in unsigned field */
+ if (unsigned_flag && decimal_value->sign())
+ {
+ DBUG_PRINT("info", ("unsigned overflow"));
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ decimal_value= &decimal_zero;
+ }
+#ifndef DBUG_OFF
+ {
+ char dbug_buff[DECIMAL_MAX_STR_LENGTH+2];
+ DBUG_PRINT("info", ("saving with precision %d scale: %d value %s",
+ (int)precision, (int)dec,
+ dbug_decimal_as_string(dbug_buff, decimal_value)));
+ }
+#endif
+
+ if (warn_if_overflow(my_decimal2binary(E_DEC_FATAL_ERROR & ~E_DEC_OVERFLOW,
+ decimal_value, ptr, precision, dec)))
+ {
+ my_decimal buff;
+ DBUG_PRINT("info", ("overflow"));
+ set_value_on_overflow(&buff, decimal_value->sign());
+ my_decimal2binary(E_DEC_FATAL_ERROR, &buff, ptr, precision, dec);
+ error= 1;
+ }
+ DBUG_EXECUTE("info", print_decimal_buff(decimal_value, (uchar *) ptr,
+ bin_size););
+ DBUG_RETURN(error);
+}
+
+
+int Field_new_decimal::store(const char *from, uint length,
+ CHARSET_INFO *charset_arg)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int err;
+ my_decimal decimal_value;
+ THD *thd= get_thd();
+ DBUG_ENTER("Field_new_decimal::store(char*)");
+
+ if ((err= str2my_decimal(E_DEC_FATAL_ERROR &
+ ~(E_DEC_OVERFLOW | E_DEC_BAD_NUM),
+ from, length, charset_arg,
+ &decimal_value)) &&
+ thd->abort_on_warning)
+ {
+ ErrConvString errmsg(from, length, charset_arg);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
+ ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
+ "decimal", errmsg.ptr(), field_name,
+ static_cast<ulong>(thd->get_stmt_da()->
+ current_row_for_warning()));
+ DBUG_RETURN(err);
+ }
+
+ switch (err) {
+ case E_DEC_TRUNCATED:
+ set_warning(Sql_condition::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1);
+ break;
+ case E_DEC_OVERFLOW:
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ set_value_on_overflow(&decimal_value, decimal_value.sign());
+ break;
+ case E_DEC_BAD_NUM:
+ {
+ ErrConvString errmsg(from, length, charset_arg);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
+ ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
+ "decimal", errmsg.ptr(), field_name,
+ static_cast<ulong>(thd->get_stmt_da()->
+ current_row_for_warning()));
+ my_decimal_set_zero(&decimal_value);
+ break;
+ }
+ }
+
+#ifndef DBUG_OFF
+ char dbug_buff[DECIMAL_MAX_STR_LENGTH+2];
+ DBUG_PRINT("enter", ("value: %s",
+ dbug_decimal_as_string(dbug_buff, &decimal_value)));
+#endif
+ store_value(&decimal_value);
+ DBUG_RETURN(err);
+}
+
+
+/**
+ @todo
+ Fix following when double2my_decimal when double2decimal
+ will return E_DEC_TRUNCATED always correctly
+*/
+
+int Field_new_decimal::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ my_decimal decimal_value;
+ int err;
+ THD *thd= get_thd();
+ DBUG_ENTER("Field_new_decimal::store(double)");
+
+ err= double2my_decimal(E_DEC_FATAL_ERROR & ~E_DEC_OVERFLOW, nr,
+ &decimal_value);
+ if (err)
+ {
+ if (check_overflow(err))
+ set_value_on_overflow(&decimal_value, decimal_value.sign());
+ /* Only issue a warning if store_value doesn't issue an warning */
+ thd->got_warning= 0;
+ }
+ if (store_value(&decimal_value))
+ err= 1;
+ else if (err && !thd->got_warning)
+ err= warn_if_overflow(err);
+ DBUG_RETURN(err);
+}
+
+
+int Field_new_decimal::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ my_decimal decimal_value;
+ int err;
+
+ if ((err= int2my_decimal(E_DEC_FATAL_ERROR & ~E_DEC_OVERFLOW,
+ nr, unsigned_val, &decimal_value)))
+ {
+ if (check_overflow(err))
+ set_value_on_overflow(&decimal_value, decimal_value.sign());
+ /* Only issue a warning if store_value doesn't issue an warning */
+ get_thd()->got_warning= 0;
+ }
+ if (store_value(&decimal_value))
+ err= 1;
+ else if (err && !get_thd()->got_warning)
+ err= warn_if_overflow(err);
+ return err;
+}
+
+
+int Field_new_decimal::store_decimal(const my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ return store_value(decimal_value);
+}
+
+
+int Field_new_decimal::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ my_decimal decimal_value;
+ return store_value(date2my_decimal(ltime, &decimal_value));
+}
+
+
+double Field_new_decimal::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ double dbl;
+ my_decimal decimal_value;
+ my_decimal2double(E_DEC_FATAL_ERROR, val_decimal(&decimal_value), &dbl);
+ return dbl;
+}
+
+
+longlong Field_new_decimal::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ longlong i;
+ my_decimal decimal_value;
+ my_decimal2int(E_DEC_FATAL_ERROR, val_decimal(&decimal_value),
+ unsigned_flag, &i);
+ return i;
+}
+
+
+my_decimal* Field_new_decimal::val_decimal(my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ DBUG_ENTER("Field_new_decimal::val_decimal");
+ binary2my_decimal(E_DEC_FATAL_ERROR, ptr, decimal_value,
+ precision, dec);
+ DBUG_EXECUTE("info", print_decimal_buff(decimal_value, (uchar *) ptr,
+ bin_size););
+ DBUG_RETURN(decimal_value);
+}
+
+
+String *Field_new_decimal::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ my_decimal decimal_value;
+ uint fixed_precision= zerofill ? precision : 0;
+ my_decimal2string(E_DEC_FATAL_ERROR, val_decimal(&decimal_value),
+ fixed_precision, dec, '0', val_buffer);
+ val_buffer->set_charset(&my_charset_numeric);
+ return val_buffer;
+}
+
+
+int Field_new_decimal::cmp(const uchar *a,const uchar*b)
+{
+ return memcmp(a, b, bin_size);
+}
+
+
+void Field_new_decimal::sort_string(uchar *buff,
+ uint length __attribute__((unused)))
+{
+ memcpy(buff, ptr, bin_size);
+}
+
+
+void Field_new_decimal::sql_type(String &str) const
+{
+ CHARSET_INFO *cs= str.charset();
+ str.length(cs->cset->snprintf(cs, (char*) str.ptr(), str.alloced_length(),
+ "decimal(%d,%d)", precision, (int)dec));
+ add_zerofill_and_unsigned(str);
+}
+
+
+/**
+ Save the field metadata for new decimal fields.
+
+ Saves the precision in the first byte and decimals() in the second
+ byte of the field metadata array at index of *metadata_ptr and
+ *(metadata_ptr + 1).
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_new_decimal::do_save_field_metadata(uchar *metadata_ptr)
+{
+ *metadata_ptr= precision;
+ *(metadata_ptr + 1)= decimals();
+ return 2;
+}
+
+
+/**
+ Returns the number of bytes field uses in row-based replication
+ row packed size.
+
+ This method is used in row-based replication to determine the number
+ of bytes that the field consumes in the row record format. This is
+ used to skip fields in the master that do not exist on the slave.
+
+ @param field_metadata Encoded size in field metadata
+
+ @returns The size of the field based on the field metadata.
+*/
+uint Field_new_decimal::pack_length_from_metadata(uint field_metadata)
+{
+ uint const source_precision= (field_metadata >> 8U) & 0x00ff;
+ uint const source_decimal= field_metadata & 0x00ff;
+ uint const source_size= my_decimal_get_binary_size(source_precision,
+ source_decimal);
+ return (source_size);
+}
+
+
+bool Field_new_decimal::compatible_field_size(uint field_metadata,
+ Relay_log_info * __attribute__((unused)),
+ uint16 mflags __attribute__((unused)),
+ int *order_var)
+{
+ uint const source_precision= (field_metadata >> 8U) & 0x00ff;
+ uint const source_decimal= field_metadata & 0x00ff;
+ int order= compare(source_precision, precision);
+ *order_var= order != 0 ? order : compare(source_decimal, dec);
+ return true;
+}
+
+
+uint Field_new_decimal::is_equal(Create_field *new_field)
+{
+ return ((new_field->sql_type == real_type()) &&
+ ((new_field->flags & UNSIGNED_FLAG) ==
+ (uint) (flags & UNSIGNED_FLAG)) &&
+ ((new_field->flags & AUTO_INCREMENT_FLAG) ==
+ (uint) (flags & AUTO_INCREMENT_FLAG)) &&
+ (new_field->length == max_display_length()) &&
+ (new_field->decimals == dec));
+}
+
+
+/**
+ Unpack a decimal field from row data.
+
+ This method is used to unpack a decimal or numeric field from a master
+ whose size of the field is less than that of the slave.
+
+ @param to Destination of the data
+ @param from Source of the data
+ @param param_data Precision (upper) and decimal (lower) values
+
+ @return New pointer into memory based on from + length of the data
+*/
+const uchar *
+Field_new_decimal::unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data)
+{
+ if (param_data == 0)
+ return Field::unpack(to, from, from_end, param_data);
+
+ uint from_precision= (param_data & 0xff00) >> 8U;
+ uint from_decimal= param_data & 0x00ff;
+ uint length=pack_length();
+ uint from_pack_len= my_decimal_get_binary_size(from_precision, from_decimal);
+ uint len= (param_data && (from_pack_len < length)) ?
+ from_pack_len : length;
+ if ((from_pack_len && (from_pack_len < length)) ||
+ (from_precision < precision) ||
+ (from_decimal < decimals()))
+ {
+ /*
+ If the master's data is smaller than the slave, we need to convert
+ the binary to decimal then resize the decimal converting it back to
+ a decimal and write that to the raw data buffer.
+ */
+ decimal_digit_t dec_buf[DECIMAL_MAX_PRECISION];
+ decimal_t dec_val;
+ dec_val.len= from_precision;
+ dec_val.buf= dec_buf;
+ /*
+ Note: bin2decimal does not change the length of the field. So it is
+ just the first step the resizing operation. The second step does the
+ resizing using the precision and decimals from the slave.
+ */
+ bin2decimal((uchar *)from, &dec_val, from_precision, from_decimal);
+ decimal2bin(&dec_val, to, precision, decimals());
+ }
+ else
+ {
+ if (from + len > from_end)
+ return 0; // Wrong data
+ memcpy(to, from, len); // Sizes are the same, just copy the data.
+ }
+ return from+len;
+}
+
+int Field_num::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ longlong v= TIME_to_ulonglong(ltime);
+ if (ltime->neg == 0)
+ return store(v, true);
+ return store(-v, false);
+}
+
+
+/****************************************************************************
+** tiny int
+****************************************************************************/
+
+int Field_tiny::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error;
+ longlong rnd;
+
+ error= get_int(cs, from, len, &rnd, 255, -128, 127);
+ ptr[0]= unsigned_flag ? (char) (ulonglong) rnd : (char) rnd;
+ return error;
+}
+
+
+int Field_tiny::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ nr=rint(nr);
+ if (unsigned_flag)
+ {
+ if (nr < 0.0)
+ {
+ *ptr=0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > 255.0)
+ {
+ *ptr= (uchar) 255;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ *ptr= (uchar) nr;
+ }
+ else
+ {
+ if (nr < -128.0)
+ {
+ *ptr= (uchar) -128;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > 127.0)
+ {
+ *ptr=127;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ *ptr=(uchar) (int) nr;
+ }
+ return error;
+}
+
+
+int Field_tiny::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+
+ if (unsigned_flag)
+ {
+ if (nr < 0 && !unsigned_val)
+ {
+ *ptr= 0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if ((ulonglong) nr > (ulonglong) 255)
+ {
+ *ptr= (char) 255;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ *ptr=(char) nr;
+ }
+ else
+ {
+ if (nr < 0 && unsigned_val)
+ nr= 256; // Generate overflow
+ if (nr < -128)
+ {
+ *ptr= (char) -128;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > 127)
+ {
+ *ptr=127;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ *ptr=(char) nr;
+ }
+ return error;
+}
+
+
+double Field_tiny::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int tmp= unsigned_flag ? (int) ptr[0] :
+ (int) ((signed char*) ptr)[0];
+ return (double) tmp;
+}
+
+
+longlong Field_tiny::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int tmp= unsigned_flag ? (int) ptr[0] :
+ (int) ((signed char*) ptr)[0];
+ return (longlong) tmp;
+}
+
+
+String *Field_tiny::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ CHARSET_INFO *cs= &my_charset_numeric;
+ uint length;
+ uint mlength=MY_MAX(field_length+1,5*cs->mbmaxlen);
+ val_buffer->alloc(mlength);
+ char *to=(char*) val_buffer->ptr();
+
+ if (unsigned_flag)
+ length= (uint) cs->cset->long10_to_str(cs,to,mlength, 10,
+ (long) *ptr);
+ else
+ length= (uint) cs->cset->long10_to_str(cs,to,mlength,-10,
+ (long) *((signed char*) ptr));
+
+ val_buffer->length(length);
+ if (zerofill)
+ prepend_zeros(val_buffer);
+ val_buffer->set_charset(cs);
+ return val_buffer;
+}
+
+bool Field_tiny::send_binary(Protocol *protocol)
+{
+ return protocol->store_tiny((longlong) (int8) ptr[0]);
+}
+
+int Field_tiny::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ signed char a,b;
+ a=(signed char) a_ptr[0]; b= (signed char) b_ptr[0];
+ if (unsigned_flag)
+ return ((uchar) a < (uchar) b) ? -1 : ((uchar) a > (uchar) b) ? 1 : 0;
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_tiny::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ if (unsigned_flag)
+ *to= *ptr;
+ else
+ to[0] = (char) (ptr[0] ^ (uchar) 128); /* Revers signbit */
+}
+
+void Field_tiny::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "tinyint(%d)",(int) field_length));
+ add_zerofill_and_unsigned(res);
+}
+
+/****************************************************************************
+ Field type short int (2 byte)
+****************************************************************************/
+
+int Field_short::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int store_tmp;
+ int error;
+ longlong rnd;
+
+ error= get_int(cs, from, len, &rnd, UINT_MAX16, INT_MIN16, INT_MAX16);
+ store_tmp= unsigned_flag ? (int) (ulonglong) rnd : (int) rnd;
+ int2store(ptr, store_tmp);
+ return error;
+}
+
+
+int Field_short::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ int16 res;
+ nr=rint(nr);
+ if (unsigned_flag)
+ {
+ if (nr < 0)
+ {
+ res=0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > (double) UINT_MAX16)
+ {
+ res=(int16) UINT_MAX16;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ res=(int16) (uint16) nr;
+ }
+ else
+ {
+ if (nr < (double) INT_MIN16)
+ {
+ res=INT_MIN16;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > (double) INT_MAX16)
+ {
+ res=INT_MAX16;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ res=(int16) (int) nr;
+ }
+ int2store(ptr,res);
+ return error;
+}
+
+
+int Field_short::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ int16 res;
+
+ if (unsigned_flag)
+ {
+ if (nr < 0L && !unsigned_val)
+ {
+ res=0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if ((ulonglong) nr > (ulonglong) UINT_MAX16)
+ {
+ res=(int16) UINT_MAX16;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ res=(int16) (uint16) nr;
+ }
+ else
+ {
+ if (nr < 0 && unsigned_val)
+ nr= UINT_MAX16+1; // Generate overflow
+
+ if (nr < INT_MIN16)
+ {
+ res=INT_MIN16;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > (longlong) INT_MAX16)
+ {
+ res=INT_MAX16;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ res=(int16) nr;
+ }
+ int2store(ptr,res);
+ return error;
+}
+
+
+double Field_short::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ short j;
+ j=sint2korr(ptr);
+ return unsigned_flag ? (double) (unsigned short) j : (double) j;
+}
+
+longlong Field_short::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ short j;
+ j=sint2korr(ptr);
+ return unsigned_flag ? (longlong) (unsigned short) j : (longlong) j;
+}
+
+
+String *Field_short::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ CHARSET_INFO *cs= &my_charset_numeric;
+ uint length;
+ uint mlength=MY_MAX(field_length+1,7*cs->mbmaxlen);
+ val_buffer->alloc(mlength);
+ char *to=(char*) val_buffer->ptr();
+ short j;
+ j=sint2korr(ptr);
+
+ if (unsigned_flag)
+ length=(uint) cs->cset->long10_to_str(cs, to, mlength, 10,
+ (long) (uint16) j);
+ else
+ length=(uint) cs->cset->long10_to_str(cs, to, mlength,-10, (long) j);
+ val_buffer->length(length);
+ if (zerofill)
+ prepend_zeros(val_buffer);
+ val_buffer->set_charset(cs);
+ return val_buffer;
+}
+
+
+bool Field_short::send_binary(Protocol *protocol)
+{
+ return protocol->store_short(Field_short::val_int());
+}
+
+
+int Field_short::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ short a,b;
+ a=sint2korr(a_ptr);
+ b=sint2korr(b_ptr);
+
+ if (unsigned_flag)
+ return ((unsigned short) a < (unsigned short) b) ? -1 :
+ ((unsigned short) a > (unsigned short) b) ? 1 : 0;
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_short::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ if (unsigned_flag)
+ to[0] = ptr[1];
+ else
+ to[0] = (char) (ptr[1] ^ 128); /* Revers signbit */
+ to[1] = ptr[0];
+}
+
+void Field_short::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "smallint(%d)",(int) field_length));
+ add_zerofill_and_unsigned(res);
+}
+
+
+/****************************************************************************
+ Field type medium int (3 byte)
+****************************************************************************/
+
+int Field_medium::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int store_tmp;
+ int error;
+ longlong rnd;
+
+ error= get_int(cs, from, len, &rnd, UINT_MAX24, INT_MIN24, INT_MAX24);
+ store_tmp= unsigned_flag ? (int) (ulonglong) rnd : (int) rnd;
+ int3store(ptr, store_tmp);
+ return error;
+}
+
+
+int Field_medium::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ nr=rint(nr);
+ if (unsigned_flag)
+ {
+ if (nr < 0)
+ {
+ int3store(ptr,0);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr >= (double) (long) (1L << 24))
+ {
+ uint32 tmp=(uint32) (1L << 24)-1L;
+ int3store(ptr,tmp);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ int3store(ptr,(uint32) nr);
+ }
+ else
+ {
+ if (nr < (double) INT_MIN24)
+ {
+ long tmp=(long) INT_MIN24;
+ int3store(ptr,tmp);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > (double) INT_MAX24)
+ {
+ long tmp=(long) INT_MAX24;
+ int3store(ptr,tmp);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ int3store(ptr,(long) nr);
+ }
+ return error;
+}
+
+
+int Field_medium::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+
+ if (unsigned_flag)
+ {
+ if (nr < 0 && !unsigned_val)
+ {
+ int3store(ptr,0);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if ((ulonglong) nr >= (ulonglong) (long) (1L << 24))
+ {
+ long tmp= (long) (1L << 24)-1L;
+ int3store(ptr,tmp);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ int3store(ptr,(uint32) nr);
+ }
+ else
+ {
+ if (nr < 0 && unsigned_val)
+ nr= (ulonglong) (long) (1L << 24); // Generate overflow
+
+ if (nr < (longlong) INT_MIN24)
+ {
+ long tmp= (long) INT_MIN24;
+ int3store(ptr,tmp);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (nr > (longlong) INT_MAX24)
+ {
+ long tmp=(long) INT_MAX24;
+ int3store(ptr,tmp);
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ int3store(ptr,(long) nr);
+ }
+ return error;
+}
+
+
+double Field_medium::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ long j= unsigned_flag ? (long) uint3korr(ptr) : sint3korr(ptr);
+ return (double) j;
+}
+
+
+longlong Field_medium::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ long j= unsigned_flag ? (long) uint3korr(ptr) : sint3korr(ptr);
+ return (longlong) j;
+}
+
+
+String *Field_medium::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ CHARSET_INFO *cs= &my_charset_numeric;
+ uint length;
+ uint mlength=MY_MAX(field_length+1,10*cs->mbmaxlen);
+ val_buffer->alloc(mlength);
+ char *to=(char*) val_buffer->ptr();
+ long j= unsigned_flag ? (long) uint3korr(ptr) : sint3korr(ptr);
+
+ length=(uint) cs->cset->long10_to_str(cs,to,mlength,-10,j);
+ val_buffer->length(length);
+ if (zerofill)
+ prepend_zeros(val_buffer); /* purecov: inspected */
+ val_buffer->set_charset(cs);
+ return val_buffer;
+}
+
+
+bool Field_medium::send_binary(Protocol *protocol)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ return protocol->store_long(Field_medium::val_int());
+}
+
+
+int Field_medium::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ long a,b;
+ if (unsigned_flag)
+ {
+ a=uint3korr(a_ptr);
+ b=uint3korr(b_ptr);
+ }
+ else
+ {
+ a=sint3korr(a_ptr);
+ b=sint3korr(b_ptr);
+ }
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_medium::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ if (unsigned_flag)
+ to[0] = ptr[2];
+ else
+ to[0] = (uchar) (ptr[2] ^ 128); /* Revers signbit */
+ to[1] = ptr[1];
+ to[2] = ptr[0];
+}
+
+
+void Field_medium::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "mediumint(%d)",(int) field_length));
+ add_zerofill_and_unsigned(res);
+}
+
+/****************************************************************************
+** long int
+****************************************************************************/
+
+int Field_long::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ long store_tmp;
+ int error;
+ longlong rnd;
+
+ error= get_int(cs, from, len, &rnd, UINT_MAX32, INT_MIN32, INT_MAX32);
+ store_tmp= unsigned_flag ? (long) (ulonglong) rnd : (long) rnd;
+ int4store(ptr, store_tmp);
+ return error;
+}
+
+
+int Field_long::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ int32 res;
+ nr=rint(nr);
+ if (unsigned_flag)
+ {
+ if (nr < 0)
+ {
+ res=0;
+ error= 1;
+ }
+ else if (nr > (double) UINT_MAX32)
+ {
+ res= UINT_MAX32;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else
+ res=(int32) (ulong) nr;
+ }
+ else
+ {
+ if (nr < (double) INT_MIN32)
+ {
+ res=(int32) INT_MIN32;
+ error= 1;
+ }
+ else if (nr > (double) INT_MAX32)
+ {
+ res=(int32) INT_MAX32;
+ error= 1;
+ }
+ else
+ res=(int32) (longlong) nr;
+ }
+ if (error)
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+
+ int4store(ptr,res);
+ return error;
+}
+
+
+int Field_long::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ int32 res;
+
+ if (unsigned_flag)
+ {
+ if (nr < 0 && !unsigned_val)
+ {
+ res=0;
+ error= 1;
+ }
+ else if ((ulonglong) nr >= (1LL << 32))
+ {
+ res=(int32) (uint32) ~0L;
+ error= 1;
+ }
+ else
+ res=(int32) (uint32) nr;
+ }
+ else
+ {
+ if (nr < 0 && unsigned_val)
+ nr= ((longlong) INT_MAX32) + 1; // Generate overflow
+ if (nr < (longlong) INT_MIN32)
+ {
+ res=(int32) INT_MIN32;
+ error= 1;
+ }
+ else if (nr > (longlong) INT_MAX32)
+ {
+ res=(int32) INT_MAX32;
+ error= 1;
+ }
+ else
+ res=(int32) nr;
+ }
+ if (error)
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+
+ int4store(ptr,res);
+ return error;
+}
+
+
+double Field_long::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int32 j;
+ j=sint4korr(ptr);
+ return unsigned_flag ? (double) (uint32) j : (double) j;
+}
+
+longlong Field_long::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int32 j;
+ /* See the comment in Field_long::store(long long) */
+ DBUG_ASSERT(!table || table->in_use == current_thd);
+ j=sint4korr(ptr);
+ return unsigned_flag ? (longlong) (uint32) j : (longlong) j;
+}
+
+String *Field_long::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ CHARSET_INFO *cs= &my_charset_numeric;
+ uint length;
+ uint mlength=MY_MAX(field_length+1,12*cs->mbmaxlen);
+ val_buffer->alloc(mlength);
+ char *to=(char*) val_buffer->ptr();
+ int32 j;
+ j=sint4korr(ptr);
+
+ if (unsigned_flag)
+ length=cs->cset->long10_to_str(cs,to,mlength, 10,(long) (uint32)j);
+ else
+ length=cs->cset->long10_to_str(cs,to,mlength,-10,(long) j);
+ val_buffer->length(length);
+ if (zerofill)
+ prepend_zeros(val_buffer);
+ val_buffer->set_charset(cs);
+ return val_buffer;
+}
+
+
+bool Field_long::send_binary(Protocol *protocol)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ return protocol->store_long(Field_long::val_int());
+}
+
+int Field_long::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ int32 a,b;
+ a=sint4korr(a_ptr);
+ b=sint4korr(b_ptr);
+ if (unsigned_flag)
+ return ((uint32) a < (uint32) b) ? -1 : ((uint32) a > (uint32) b) ? 1 : 0;
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_long::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ if (unsigned_flag)
+ to[0] = ptr[3];
+ else
+ to[0] = (char) (ptr[3] ^ 128); /* Revers signbit */
+ to[1] = ptr[2];
+ to[2] = ptr[1];
+ to[3] = ptr[0];
+}
+
+
+void Field_long::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "int(%d)",(int) field_length));
+ add_zerofill_and_unsigned(res);
+}
+
+/****************************************************************************
+ Field type longlong int (8 bytes)
+****************************************************************************/
+
+int Field_longlong::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ char *end;
+ ulonglong tmp;
+
+ tmp= cs->cset->strntoull10rnd(cs,from,len,unsigned_flag,&end,&error);
+ if (error == MY_ERRNO_ERANGE)
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ else if (get_thd()->count_cuted_fields &&
+ check_int(cs, from, len, end, error))
+ error= 1;
+ else
+ error= 0;
+ int8store(ptr,tmp);
+ return error;
+}
+
+
+int Field_longlong::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ bool error;
+ longlong res;
+
+ res= double_to_longlong(nr, unsigned_flag, &error);
+
+ if (error)
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+
+ int8store(ptr,res);
+ return error;
+}
+
+
+int Field_longlong::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+
+ if (nr < 0) // Only possible error
+ {
+ /*
+ if field is unsigned and value is signed (< 0) or
+ if field is signed and value is unsigned we have an overflow
+ */
+ if (unsigned_flag != unsigned_val)
+ {
+ nr= unsigned_flag ? (ulonglong) 0 : (ulonglong) LONGLONG_MAX;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ error= 1;
+ }
+ }
+
+ int8store(ptr,nr);
+ return error;
+}
+
+
+double Field_longlong::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ longlong j;
+ j=sint8korr(ptr);
+ /* The following is open coded to avoid a bug in gcc 3.3 */
+ if (unsigned_flag)
+ {
+ ulonglong tmp= (ulonglong) j;
+ return ulonglong2double(tmp);
+ }
+ return (double) j;
+}
+
+
+longlong Field_longlong::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ longlong j;
+ j=sint8korr(ptr);
+ return j;
+}
+
+
+String *Field_longlong::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ CHARSET_INFO *cs= &my_charset_numeric;
+ uint length;
+ uint mlength=MY_MAX(field_length+1,22*cs->mbmaxlen);
+ val_buffer->alloc(mlength);
+ char *to=(char*) val_buffer->ptr();
+ longlong j;
+ j=sint8korr(ptr);
+
+ length=(uint) (cs->cset->longlong10_to_str)(cs,to,mlength,
+ unsigned_flag ? 10 : -10, j);
+ val_buffer->length(length);
+ if (zerofill)
+ prepend_zeros(val_buffer);
+ val_buffer->set_charset(cs);
+ return val_buffer;
+}
+
+
+bool Field_longlong::send_binary(Protocol *protocol)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ return protocol->store_longlong(Field_longlong::val_int(), unsigned_flag);
+}
+
+
+int Field_longlong::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ longlong a,b;
+ a=sint8korr(a_ptr);
+ b=sint8korr(b_ptr);
+ if (unsigned_flag)
+ return ((ulonglong) a < (ulonglong) b) ? -1 :
+ ((ulonglong) a > (ulonglong) b) ? 1 : 0;
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_longlong::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ if (unsigned_flag)
+ to[0] = ptr[7];
+ else
+ to[0] = (char) (ptr[7] ^ 128); /* Revers signbit */
+ to[1] = ptr[6];
+ to[2] = ptr[5];
+ to[3] = ptr[4];
+ to[4] = ptr[3];
+ to[5] = ptr[2];
+ to[6] = ptr[1];
+ to[7] = ptr[0];
+}
+
+
+void Field_longlong::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "bigint(%d)",(int) field_length));
+ add_zerofill_and_unsigned(res);
+}
+
+
+/*
+ Floating-point numbers
+ */
+
+/****************************************************************************
+ single precision float
+****************************************************************************/
+
+int Field_float::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ int error;
+ char *end;
+ double nr= my_strntod(cs,(char*) from,len,&end,&error);
+ if (error || (!len || ((uint) (end-from) != len &&
+ get_thd()->count_cuted_fields)))
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN,
+ (error ? ER_WARN_DATA_OUT_OF_RANGE : WARN_DATA_TRUNCATED), 1);
+ error= error ? 1 : 2;
+ }
+ Field_float::store(nr);
+ return error;
+}
+
+
+int Field_float::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= truncate_double(&nr, field_length,
+ not_fixed ? NOT_FIXED_DEC : dec,
+ unsigned_flag, FLT_MAX);
+ if (error)
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ if (error < 0) // Wrong double value
+ {
+ error= 1;
+ set_null();
+ }
+ }
+ float j= (float)nr;
+
+ float4store(ptr,j);
+ return error;
+}
+
+
+int Field_float::store(longlong nr, bool unsigned_val)
+{
+ return Field_float::store(unsigned_val ? ulonglong2double((ulonglong) nr) :
+ (double) nr);
+}
+
+
+double Field_float::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ float j;
+ float4get(j,ptr);
+ return ((double) j);
+}
+
+longlong Field_float::val_int(void)
+{
+ float j;
+ float4get(j,ptr);
+ return (longlong) rint(j);
+}
+
+
+String *Field_float::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ DBUG_ASSERT(!zerofill || field_length <= MAX_FIELD_CHARLENGTH);
+ float nr;
+ float4get(nr,ptr);
+
+ uint to_length= 70;
+ if (val_buffer->alloc(to_length))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return val_buffer;
+ }
+
+ char *to=(char*) val_buffer->ptr();
+ size_t len;
+
+ if (dec >= NOT_FIXED_DEC)
+ len= my_gcvt(nr, MY_GCVT_ARG_FLOAT, to_length - 1, to, NULL);
+ else
+ {
+ /*
+ We are safe here because the buffer length is 70, and
+ fabs(float) < 10^39, dec < NOT_FIXED_DEC. So the resulting string
+ will be not longer than 69 chars + terminating '\0'.
+ */
+ len= my_fcvt(nr, dec, to, NULL);
+ }
+ val_buffer->length((uint) len);
+ if (zerofill)
+ prepend_zeros(val_buffer);
+ val_buffer->set_charset(&my_charset_numeric);
+ return val_buffer;
+}
+
+
+int Field_float::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ float a,b;
+ float4get(a,a_ptr);
+ float4get(b,b_ptr);
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+#define FLT_EXP_DIG (sizeof(float)*8-FLT_MANT_DIG)
+
+void Field_float::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ float nr;
+ float4get(nr,ptr);
+
+ uchar *tmp= to;
+ if (nr == (float) 0.0)
+ { /* Change to zero string */
+ tmp[0]=(uchar) 128;
+ bzero((char*) tmp+1,sizeof(nr)-1);
+ }
+ else
+ {
+#ifdef WORDS_BIGENDIAN
+ memcpy(tmp, &nr, sizeof(nr));
+#else
+ tmp[0]= ptr[3]; tmp[1]=ptr[2]; tmp[2]= ptr[1]; tmp[3]=ptr[0];
+#endif
+ if (tmp[0] & 128) /* Negative */
+ { /* make complement */
+ uint i;
+ for (i=0 ; i < sizeof(nr); i++)
+ tmp[i]= (uchar) (tmp[i] ^ (uchar) 255);
+ }
+ else
+ {
+ ushort exp_part=(((ushort) tmp[0] << 8) | (ushort) tmp[1] |
+ (ushort) 32768);
+ exp_part+= (ushort) 1 << (16-1-FLT_EXP_DIG);
+ tmp[0]= (uchar) (exp_part >> 8);
+ tmp[1]= (uchar) exp_part;
+ }
+ }
+}
+
+
+bool Field_float::send_binary(Protocol *protocol)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ return protocol->store((float) Field_float::val_real(), dec, (String*) 0);
+}
+
+
+/**
+ Save the field metadata for float fields.
+
+ Saves the pack length in the first byte.
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_float::do_save_field_metadata(uchar *metadata_ptr)
+{
+ *metadata_ptr= pack_length();
+ return 1;
+}
+
+
+void Field_float::sql_type(String &res) const
+{
+ if (dec == NOT_FIXED_DEC)
+ {
+ res.set_ascii(STRING_WITH_LEN("float"));
+ }
+ else
+ {
+ CHARSET_INFO *cs= res.charset();
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "float(%d,%d)",(int) field_length,dec));
+ }
+ add_zerofill_and_unsigned(res);
+}
+
+
+/****************************************************************************
+ double precision floating point numbers
+****************************************************************************/
+
+int Field_double::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ int error;
+ char *end;
+ double nr= my_strntod(cs,(char*) from, len, &end, &error);
+ if (error || (!len || ((uint) (end-from) != len &&
+ get_thd()->count_cuted_fields)))
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN,
+ (error ? ER_WARN_DATA_OUT_OF_RANGE : WARN_DATA_TRUNCATED), 1);
+ error= error ? 1 : 2;
+ }
+ Field_double::store(nr);
+ return error;
+}
+
+
+int Field_double::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= truncate_double(&nr, field_length,
+ not_fixed ? NOT_FIXED_DEC : dec,
+ unsigned_flag, DBL_MAX);
+ if (error)
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ if (error < 0) // Wrong double value
+ {
+ error= 1;
+ set_null();
+ }
+ }
+
+ float8store(ptr,nr);
+ return error;
+}
+
+
+int Field_double::store(longlong nr, bool unsigned_val)
+{
+ return Field_double::store(unsigned_val ? ulonglong2double((ulonglong) nr) :
+ (double) nr);
+}
+
+/*
+ If a field has fixed length, truncate the double argument pointed to by 'nr'
+ appropriately.
+ Also ensure that the argument is within [-max_value; max_value] range.
+
+ return
+ 0 ok
+ -1 Illegal double value
+ 1 Value was truncated
+*/
+
+int truncate_double(double *nr, uint field_length, uint dec,
+ bool unsigned_flag, double max_value)
+{
+ int error= 0;
+ double res= *nr;
+
+ if (isnan(res))
+ {
+ *nr= 0;
+ return -1;
+ }
+ else if (unsigned_flag && res < 0)
+ {
+ *nr= 0;
+ return 1;
+ }
+
+ if (dec < NOT_FIXED_DEC)
+ {
+ uint order= field_length - dec;
+ uint step= array_elements(log_10) - 1;
+ max_value= 1.0;
+ for (; order > step; order-= step)
+ max_value*= log_10[step];
+ max_value*= log_10[order];
+ max_value-= 1.0 / log_10[dec];
+
+ /* Check for infinity so we don't get NaN in calculations */
+ if (!my_isinf(res))
+ {
+ double tmp= rint((res - floor(res)) * log_10[dec]) / log_10[dec];
+ res= floor(res) + tmp;
+ }
+ }
+
+ if (res < -max_value)
+ {
+ res= -max_value;
+ error= 1;
+ }
+ else if (res > max_value)
+ {
+ res= max_value;
+ error= 1;
+ }
+
+ *nr= res;
+ return error;
+}
+
+/*
+ Convert double to longlong / ulonglong.
+ If double is outside of range, adjust return value and set error.
+
+ SYNOPSIS
+ double_to_longlong()
+ nr Number to convert
+ unsigned_flag 1 if result is unsigned
+ error Will be set to 1 in case of overflow.
+*/
+
+longlong double_to_longlong(double nr, bool unsigned_flag, bool *error)
+{
+ longlong res;
+
+ *error= 0;
+
+ nr= rint(nr);
+ if (unsigned_flag)
+ {
+ if (nr < 0)
+ {
+ res= 0;
+ *error= 1;
+ }
+ else if (nr >= (double) ULONGLONG_MAX)
+ {
+ res= ~(longlong) 0;
+ *error= 1;
+ }
+ else
+ res= (longlong) double2ulonglong(nr);
+ }
+ else
+ {
+ if (nr <= (double) LONGLONG_MIN)
+ {
+ res= LONGLONG_MIN;
+ *error= (nr < (double) LONGLONG_MIN);
+ }
+ else if (nr >= (double) (ulonglong) LONGLONG_MAX)
+ {
+ res= LONGLONG_MAX;
+ *error= (nr > (double) LONGLONG_MAX);
+ }
+ else
+ res= (longlong) nr;
+ }
+ return res;
+}
+
+
+int Field_real::store_decimal(const my_decimal *dm)
+{
+ double dbl;
+ my_decimal2double(E_DEC_FATAL_ERROR, dm, &dbl);
+ return store(dbl);
+}
+
+int Field_real::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ return store(TIME_to_double(ltime));
+}
+
+
+double Field_double::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ double j;
+ float8get(j,ptr);
+ return j;
+}
+
+longlong Field_double::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ double j;
+ longlong res;
+ bool error;
+ float8get(j,ptr);
+
+ res= double_to_longlong(j, 0, &error);
+ if (error)
+ {
+ ErrConvDouble err(j);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER",
+ err.ptr());
+ }
+ return res;
+}
+
+
+my_decimal *Field_real::val_decimal(my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ double2my_decimal(E_DEC_FATAL_ERROR, val_real(), decimal_value);
+ return decimal_value;
+}
+
+
+bool Field_real::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ double nr= val_real();
+ return double_to_datetime_with_warn(nr, ltime, fuzzydate, field_name);
+}
+
+
+String *Field_double::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ DBUG_ASSERT(!zerofill || field_length <= MAX_FIELD_CHARLENGTH);
+ double nr;
+ float8get(nr,ptr);
+
+ uint to_length= DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE;
+ if (val_buffer->alloc(to_length))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return val_buffer;
+ }
+
+ char *to=(char*) val_buffer->ptr();
+ size_t len;
+
+ if (dec >= NOT_FIXED_DEC)
+ len= my_gcvt(nr, MY_GCVT_ARG_DOUBLE, to_length - 1, to, NULL);
+ else
+ len= my_fcvt(nr, dec, to, NULL);
+
+ val_buffer->length((uint) len);
+ if (zerofill)
+ prepend_zeros(val_buffer);
+ val_buffer->set_charset(&my_charset_numeric);
+ return val_buffer;
+}
+
+bool Field_double::send_binary(Protocol *protocol)
+{
+ return protocol->store((double) Field_double::val_real(), dec, (String*) 0);
+}
+
+
+int Field_double::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ double a,b;
+ float8get(a,a_ptr);
+ float8get(b,b_ptr);
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+
+#define DBL_EXP_DIG (sizeof(double)*8-DBL_MANT_DIG)
+
+/* The following should work for IEEE */
+
+void Field_double::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ double nr;
+ float8get(nr,ptr);
+ change_double_for_sort(nr, to);
+}
+
+
+/**
+ Save the field metadata for double fields.
+
+ Saves the pack length in the first byte of the field metadata array
+ at index of *metadata_ptr.
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_double::do_save_field_metadata(uchar *metadata_ptr)
+{
+ *metadata_ptr= pack_length();
+ return 1;
+}
+
+
+void Field_double::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ if (dec == NOT_FIXED_DEC)
+ {
+ res.set_ascii(STRING_WITH_LEN("double"));
+ }
+ else
+ {
+ res.length(cs->cset->snprintf(cs,(char*) res.ptr(),res.alloced_length(),
+ "double(%d,%d)",(int) field_length,dec));
+ }
+ add_zerofill_and_unsigned(res);
+}
+
+
+/**
+ TIMESTAMP type holds datetime values in range from 1970-01-01 00:00:01 UTC to
+ 2038-01-01 00:00:00 UTC stored as number of seconds since Unix
+ Epoch in UTC.
+
+ Actually SQL-99 says that we should allow niladic functions (like NOW())
+ as defaults for any field. The current limitation (only NOW() and only
+ for TIMESTAMP and DATETIME fields) are because of restricted binary .frm
+ format and should go away in the future.
+
+ Also because of this limitation of binary .frm format we use 5 different
+ unireg_check values with TIMESTAMP field to distinguish various cases of
+ DEFAULT or ON UPDATE values. These values are:
+
+ TIMESTAMP_OLD_FIELD - old timestamp, if there was not any fields with
+ auto-set-on-update (or now() as default) in this table before, then this
+ field has NOW() as default and is updated when row changes, else it is
+ field which has 0 as default value and is not automatically updated.
+ TIMESTAMP_DN_FIELD - field with NOW() as default but not set on update
+ automatically (TIMESTAMP DEFAULT NOW())
+ TIMESTAMP_UN_FIELD - field which is set on update automatically but has not
+ NOW() as default (but it may has 0 or some other const timestamp as
+ default) (TIMESTAMP ON UPDATE NOW()).
+ TIMESTAMP_DNUN_FIELD - field which has now() as default and is auto-set on
+ update. (TIMESTAMP DEFAULT NOW() ON UPDATE NOW())
+ NONE - field which is not auto-set on update with some other than NOW()
+ default value (TIMESTAMP DEFAULT 0).
+
+ Note that TIMESTAMP_OLD_FIELDs are never created explicitly now, they are
+ left only for preserving ability to read old tables. Such fields replaced
+ with their newer analogs in CREATE TABLE and in SHOW CREATE TABLE. This is
+ because we want to prefer NONE unireg_check before TIMESTAMP_OLD_FIELD for
+ "TIMESTAMP DEFAULT 'Const'" field. (Old timestamps allowed such
+ specification too but ignored default value for first timestamp, which of
+ course is non-standard.) In most cases user won't notice any change, only
+ exception is different behavior of old/new timestamps during ALTER TABLE.
+ */
+
+Field_timestamp::Field_timestamp(uchar *ptr_arg, uint32 len_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg,
+ const char *field_name_arg,
+ TABLE_SHARE *share)
+ :Field_temporal(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg)
+{
+ /* For 4.0 MYD and 4.0 InnoDB compatibility */
+ flags|= UNSIGNED_FLAG;
+ if (unireg_check != NONE)
+ {
+ /*
+ We mark the flag with TIMESTAMP_FLAG to indicate to the client that
+ this field will be automaticly updated on insert.
+ */
+ flags|= TIMESTAMP_FLAG;
+ if (unireg_check != TIMESTAMP_DN_FIELD)
+ flags|= ON_UPDATE_NOW_FLAG;
+ }
+}
+
+
+my_time_t Field_timestamp::get_timestamp(ulong *sec_part) const
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ *sec_part= 0;
+ return sint4korr(ptr);
+}
+
+
+int Field_timestamp::store_TIME_with_warning(THD *thd, MYSQL_TIME *l_time,
+ const ErrConv *str,
+ int was_cut,
+ bool have_smth_to_conv)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ uint error = 0;
+ my_time_t timestamp;
+
+ if (MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) || !have_smth_to_conv)
+ {
+ error= 1;
+ set_datetime_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED,
+ str, MYSQL_TIMESTAMP_DATETIME, 1);
+ }
+ else if (MYSQL_TIME_WARN_HAVE_NOTES(was_cut))
+ {
+ error= 3;
+ set_datetime_warning(Sql_condition::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED,
+ str, MYSQL_TIMESTAMP_DATETIME, 1);
+ }
+ /* Only convert a correct date (not a zero date) */
+ if (have_smth_to_conv && l_time->month)
+ {
+ uint conversion_error;
+ timestamp= TIME_to_timestamp(thd, l_time, &conversion_error);
+ if (timestamp == 0 && l_time->second_part == 0)
+ conversion_error= ER_WARN_DATA_OUT_OF_RANGE;
+ if (conversion_error)
+ {
+ set_datetime_warning(Sql_condition::WARN_LEVEL_WARN, conversion_error,
+ str, MYSQL_TIMESTAMP_DATETIME, !error);
+ error= 1;
+ }
+ }
+ else
+ {
+ timestamp= 0;
+ l_time->second_part= 0;
+ }
+ store_TIME(timestamp, l_time->second_part);
+ return error;
+}
+
+
+static bool
+copy_or_convert_to_datetime(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to)
+{
+ if (from->time_type == MYSQL_TIMESTAMP_TIME)
+ return time_to_datetime(thd, from, to);
+ *to= *from;
+ return false;
+}
+
+
+int Field_timestamp::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ int unused;
+ ErrConvTime str(ltime);
+ THD *thd= get_thd();
+ MYSQL_TIME l_time;
+ bool valid= !copy_or_convert_to_datetime(thd, ltime, &l_time) &&
+ !check_date(&l_time, pack_time(&l_time) != 0,
+ (thd->variables.sql_mode & MODE_NO_ZERO_DATE) |
+ MODE_NO_ZERO_IN_DATE, &unused);
+
+ return store_TIME_with_warning(thd, &l_time, &str, false, valid);
+}
+
+
+int Field_timestamp::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ MYSQL_TIME l_time;
+ MYSQL_TIME_STATUS status;
+ bool have_smth_to_conv;
+ ErrConvString str(from, len, cs);
+ THD *thd= get_thd();
+
+ /* We don't want to store invalid or fuzzy datetime values in TIMESTAMP */
+ have_smth_to_conv= !str_to_datetime(cs, from, len, &l_time,
+ (thd->variables.sql_mode &
+ MODE_NO_ZERO_DATE) |
+ MODE_NO_ZERO_IN_DATE, &status);
+ return store_TIME_with_warning(thd, &l_time, &str,
+ status.warnings, have_smth_to_conv);
+}
+
+
+int Field_timestamp::store(double nr)
+{
+ MYSQL_TIME l_time;
+ int error;
+ ErrConvDouble str(nr);
+ THD *thd= get_thd();
+
+ longlong tmp= double_to_datetime(nr, &l_time, (thd->variables.sql_mode &
+ MODE_NO_ZERO_DATE) |
+ MODE_NO_ZERO_IN_DATE, &error);
+ return store_TIME_with_warning(thd, &l_time, &str, error, tmp != -1);
+}
+
+
+int Field_timestamp::store(longlong nr, bool unsigned_val)
+{
+ MYSQL_TIME l_time;
+ int error;
+ ErrConvInteger str(nr, unsigned_val);
+ THD *thd= get_thd();
+
+ /* We don't want to store invalid or fuzzy datetime values in TIMESTAMP */
+ longlong tmp= number_to_datetime(nr, 0, &l_time, (thd->variables.sql_mode &
+ MODE_NO_ZERO_DATE) |
+ MODE_NO_ZERO_IN_DATE, &error);
+ return store_TIME_with_warning(thd, &l_time, &str, error, tmp != -1);
+}
+
+
+double Field_timestamp::val_real(void)
+{
+ return (double) Field_timestamp::val_int();
+}
+
+
+longlong Field_timestamp::val_int(void)
+{
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, TIME_NO_ZERO_DATE))
+ return 0;
+
+ return ltime.year * 10000000000LL + ltime.month * 100000000LL +
+ ltime.day * 1000000L + ltime.hour * 10000L +
+ ltime.minute * 100 + ltime.second;
+}
+
+
+String *Field_timestamp::val_str(String *val_buffer, String *val_ptr)
+{
+ MYSQL_TIME ltime;
+ uint32 temp, temp2;
+ uint dec;
+ char *to;
+
+ val_buffer->alloc(field_length+1);
+ to= (char*) val_buffer->ptr();
+ val_buffer->length(field_length);
+
+ if (get_date(&ltime, TIME_NO_ZERO_DATE))
+ { /* Zero time is "000000" */
+ val_ptr->set(zero_timestamp, field_length, &my_charset_numeric);
+ return val_ptr;
+ }
+ val_buffer->set_charset(&my_charset_numeric); // Safety
+
+ temp= ltime.year % 100;
+ if (temp < YY_PART_YEAR - 1)
+ {
+ *to++= '2';
+ *to++= '0';
+ }
+ else
+ {
+ *to++= '1';
+ *to++= '9';
+ }
+ temp2=temp/10; temp=temp-temp2*10;
+ *to++= (char) ('0'+(char) (temp2));
+ *to++= (char) ('0'+(char) (temp));
+ *to++= '-';
+ temp=ltime.month;
+ temp2=temp/10; temp=temp-temp2*10;
+ *to++= (char) ('0'+(char) (temp2));
+ *to++= (char) ('0'+(char) (temp));
+ *to++= '-';
+ temp=ltime.day;
+ temp2=temp/10; temp=temp-temp2*10;
+ *to++= (char) ('0'+(char) (temp2));
+ *to++= (char) ('0'+(char) (temp));
+ *to++= ' ';
+ temp=ltime.hour;
+ temp2=temp/10; temp=temp-temp2*10;
+ *to++= (char) ('0'+(char) (temp2));
+ *to++= (char) ('0'+(char) (temp));
+ *to++= ':';
+ temp=ltime.minute;
+ temp2=temp/10; temp=temp-temp2*10;
+ *to++= (char) ('0'+(char) (temp2));
+ *to++= (char) ('0'+(char) (temp));
+ *to++= ':';
+ temp=ltime.second;
+ temp2=temp/10; temp=temp-temp2*10;
+ *to++= (char) ('0'+(char) (temp2));
+ *to++= (char) ('0'+(char) (temp));
+ *to= 0;
+ val_buffer->set_charset(&my_charset_numeric);
+
+ if ((dec= decimals()))
+ {
+ ulong sec_part= (ulong) sec_part_shift(ltime.second_part, dec);
+ char *buf= const_cast<char*>(val_buffer->ptr() + MAX_DATETIME_WIDTH);
+ for (int i= dec; i > 0; i--, sec_part/= 10)
+ buf[i]= (char)(sec_part % 10) + '0';
+ buf[0]= '.';
+ buf[dec + 1]= 0;
+ }
+ return val_buffer;
+}
+
+
+bool Field_timestamp::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ THD *thd= get_thd();
+ thd->time_zone_used= 1;
+ ulong sec_part;
+ my_time_t temp= get_timestamp(&sec_part);
+ if (temp == 0 && sec_part == 0)
+ { /* Zero time is "000000" */
+ if (fuzzydate & TIME_NO_ZERO_DATE)
+ return 1;
+ bzero((char*) ltime,sizeof(*ltime));
+ }
+ else
+ {
+ thd->variables.time_zone->gmt_sec_to_TIME(ltime, (my_time_t)temp);
+ ltime->second_part= sec_part;
+ }
+ return 0;
+}
+
+
+bool Field_timestamp::send_binary(Protocol *protocol)
+{
+ MYSQL_TIME ltime;
+ Field_timestamp::get_date(&ltime, 0);
+ return protocol->store(&ltime, 0);
+}
+
+
+int Field_timestamp::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ int32 a,b;
+ a=sint4korr(a_ptr);
+ b=sint4korr(b_ptr);
+ return ((uint32) a < (uint32) b) ? -1 : ((uint32) a > (uint32) b) ? 1 : 0;
+}
+
+
+void Field_timestamp::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ to[0] = ptr[3];
+ to[1] = ptr[2];
+ to[2] = ptr[1];
+ to[3] = ptr[0];
+}
+
+
+void Field_timestamp::sql_type(String &res) const
+{
+ if (!decimals())
+ {
+ res.set_ascii(STRING_WITH_LEN("timestamp"));
+ return;
+ }
+ CHARSET_INFO *cs=res.charset();
+ res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(),
+ "timestamp(%u)", decimals()));
+}
+
+
+int Field_timestamp::set_time()
+{
+ THD *thd= get_thd();
+ set_notnull();
+ store_TIME(thd->query_start(), 0);
+ return 0;
+}
+
+/**
+ Mark the field as having an explicit default value.
+
+ @param value if available, the value that the field is being set to
+
+ @note
+ Fields that have an explicit default value should not be updated
+ automatically via the DEFAULT or ON UPDATE functions. The functions
+ that deal with data change functionality (INSERT/UPDATE/LOAD),
+ determine if there is an explicit value for each field before performing
+ the data change, and call this method to mark the field.
+
+ For timestamp columns, the only case where a column is not marked
+ as been given a value are:
+ - It's explicitly assigned with DEFAULT
+ - We assign NULL to a timestamp field that is defined as NOT NULL.
+ This is how MySQL has worked since it's start.
+*/
+
+void Field_timestamp::set_explicit_default(Item *value)
+{
+ if (((value->type() == Item::DEFAULT_VALUE_ITEM &&
+ !((Item_default_value*)value)->arg) ||
+ (!maybe_null() && value->null_value)))
+ return;
+ set_has_explicit_value();
+}
+
+#ifdef NOT_USED
+static void store_native(ulonglong num, uchar *to, uint bytes)
+{
+ switch(bytes) {
+ case 1: *to= (uchar)num; break;
+ case 2: shortstore(to, (ushort)num); break;
+ case 3: int3store(to, num); /* Sic!*/ break;
+ case 4: longstore(to, (ulong)num); break;
+ case 8: longlongstore(to, num); break;
+ default: DBUG_ASSERT(0);
+ }
+}
+
+static longlong read_native(const uchar *from, uint bytes)
+{
+ switch(bytes) {
+ case 1: return from[0];
+ case 2: { uint16 tmp; shortget(tmp, from); return tmp; }
+ case 3: return uint3korr(from);
+ case 4: { uint32 tmp; longget(tmp, from); return tmp; }
+ case 8: { longlong tmp; longlongget(tmp, from); return tmp; }
+ default: DBUG_ASSERT(0); return 0;
+ }
+}
+#endif
+
+static void store_lowendian(ulonglong num, uchar *to, uint bytes)
+{
+ switch(bytes) {
+ case 1: *to= (uchar)num; break;
+ case 2: int2store(to, num); break;
+ case 3: int3store(to, num); break;
+ case 4: int4store(to, num); break;
+ case 8: int8store(to, num); break;
+ default: DBUG_ASSERT(0);
+ }
+}
+
+static longlong read_lowendian(const uchar *from, uint bytes)
+{
+ switch(bytes) {
+ case 1: return from[0];
+ case 2: return uint2korr(from);
+ case 3: return uint3korr(from);
+ case 4: return uint4korr(from);
+ case 8: return sint8korr(from);
+ default: DBUG_ASSERT(0); return 0;
+ }
+}
+
+static void store_bigendian(ulonglong num, uchar *to, uint bytes)
+{
+ switch(bytes) {
+ case 1: mi_int1store(to, num); break;
+ case 2: mi_int2store(to, num); break;
+ case 3: mi_int3store(to, num); break;
+ case 4: mi_int4store(to, num); break;
+ case 5: mi_int5store(to, num); break;
+ case 6: mi_int6store(to, num); break;
+ case 7: mi_int7store(to, num); break;
+ case 8: mi_int8store(to, num); break;
+ default: DBUG_ASSERT(0);
+ }
+}
+
+static longlong read_bigendian(const uchar *from, uint bytes)
+{
+ switch(bytes) {
+ case 1: return mi_uint1korr(from);
+ case 2: return mi_uint2korr(from);
+ case 3: return mi_uint3korr(from);
+ case 4: return mi_uint4korr(from);
+ case 5: return mi_uint5korr(from);
+ case 6: return mi_uint6korr(from);
+ case 7: return mi_uint7korr(from);
+ case 8: return mi_sint8korr(from);
+ default: DBUG_ASSERT(0); return 0;
+ }
+}
+
+void Field_timestamp_hires::store_TIME(my_time_t timestamp, ulong sec_part)
+{
+ mi_int4store(ptr, timestamp);
+ store_bigendian(sec_part_shift(sec_part, dec), ptr+4, sec_part_bytes[dec]);
+}
+
+my_time_t Field_timestamp_hires::get_timestamp(ulong *sec_part) const
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ *sec_part= (long)sec_part_unshift(read_bigendian(ptr+4, sec_part_bytes[dec]), dec);
+ return mi_uint4korr(ptr);
+}
+
+double Field_timestamp_with_dec::val_real(void)
+{
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, TIME_NO_ZERO_DATE))
+ return 0;
+
+ return ltime.year * 1e10 + ltime.month * 1e8 +
+ ltime.day * 1e6 + ltime.hour * 1e4 +
+ ltime.minute * 1e2 + ltime.second + ltime.second_part*1e-6;
+}
+
+my_decimal *Field_timestamp_with_dec::val_decimal(my_decimal *d)
+{
+ MYSQL_TIME ltime;
+ get_date(&ltime, 0);
+ return TIME_to_my_decimal(&ltime, d);
+}
+
+int Field_timestamp::store_decimal(const my_decimal *d)
+{
+ ulonglong nr;
+ ulong sec_part;
+ int error;
+ MYSQL_TIME ltime;
+ longlong tmp;
+ THD *thd= get_thd();
+ ErrConvDecimal str(d);
+
+ if (my_decimal2seconds(d, &nr, &sec_part))
+ {
+ tmp= -1;
+ error= 2;
+ }
+ else
+ tmp= number_to_datetime(nr, sec_part, &ltime, TIME_NO_ZERO_IN_DATE |
+ (thd->variables.sql_mode &
+ MODE_NO_ZERO_DATE), &error);
+
+ return store_TIME_with_warning(thd, &ltime, &str, error, tmp != -1);
+}
+
+int Field_timestamp_with_dec::set_time()
+{
+ THD *thd= get_thd();
+ set_notnull();
+ store_TIME(thd->query_start(), thd->query_start_sec_part());
+ return 0;
+}
+
+bool Field_timestamp_with_dec::send_binary(Protocol *protocol)
+{
+ MYSQL_TIME ltime;
+ Field_timestamp::get_date(&ltime, 0);
+ return protocol->store(&ltime, dec);
+}
+
+
+int Field_timestamp_hires::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ int32 a,b;
+ ulong a_sec_part, b_sec_part;
+ a= mi_uint4korr(a_ptr);
+ a_sec_part= (ulong)read_bigendian(a_ptr+4, sec_part_bytes[dec]);
+ b= mi_uint4korr(b_ptr);
+ b_sec_part= (ulong)read_bigendian(b_ptr+4, sec_part_bytes[dec]);
+ return ((uint32) a < (uint32) b) ? -1 : ((uint32) a > (uint32) b) ? 1 :
+ a_sec_part < b_sec_part ? -1 : a_sec_part > b_sec_part ? 1 : 0;
+}
+
+
+uint32 Field_timestamp_hires::pack_length() const
+{
+ return 4 + sec_part_bytes[dec];
+}
+
+void Field_timestamp_with_dec::make_field(Send_field *field)
+{
+ Field::make_field(field);
+ field->decimals= dec;
+}
+
+
+/*************************************************************
+** MySQL-5.6 compatible TIMESTAMP(N)
+**************************************************************/
+
+void Field_timestampf::store_TIME(my_time_t timestamp, ulong sec_part)
+{
+ struct timeval tm;
+ tm.tv_sec= timestamp;
+ tm.tv_usec= sec_part;
+ my_timeval_trunc(&tm, dec);
+ my_timestamp_to_binary(&tm, ptr, dec);
+}
+
+
+my_time_t Field_timestampf::get_timestamp(ulong *sec_part) const
+{
+ struct timeval tm;
+ my_timestamp_from_binary(&tm, ptr, dec);
+ *sec_part= tm.tv_usec;
+ return tm.tv_sec;
+}
+
+
+/*************************************************************/
+uint Field_temporal::is_equal(Create_field *new_field)
+{
+ return new_field->sql_type == real_type() &&
+ new_field->length == max_display_length();
+}
+
+
+void Field_temporal::set_warnings(Sql_condition::enum_warning_level trunc_level,
+ const ErrConv *str, int was_cut,
+ timestamp_type ts_type)
+{
+ /*
+ error code logic:
+ MYSQL_TIME_WARN_TRUNCATED means that the value was not a date/time at all.
+ it will be stored as zero date/time.
+ MYSQL_TIME_WARN_OUT_OF_RANGE means that the value was a date/time,
+ that is, it was parsed as such, but the value was invalid.
+
+ Also, MYSQL_TIME_WARN_TRUNCATED is used when storing a DATETIME in
+ a DATE field and non-zero time part is thrown away.
+ */
+ if (was_cut & MYSQL_TIME_WARN_TRUNCATED)
+ set_datetime_warning(trunc_level, WARN_DATA_TRUNCATED,
+ str, mysql_type_to_time_type(type()), 1);
+ if (was_cut & MYSQL_TIME_WARN_OUT_OF_RANGE)
+ set_datetime_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE,
+ str, mysql_type_to_time_type(type()), 1);
+}
+
+
+/*
+ Store string into a date/time field
+
+ RETURN
+ 0 ok
+ 1 Value was cut during conversion
+ 2 value was out of range
+ 3 Datetime value that was cut (warning level NOTE)
+ This is used by opt_range.cc:get_mm_leaf().
+*/
+int Field_temporal_with_date::store_TIME_with_warning(MYSQL_TIME *ltime,
+ const ErrConv *str,
+ int was_cut,
+ int have_smth_to_conv)
+{
+ Sql_condition::enum_warning_level trunc_level= Sql_condition::WARN_LEVEL_WARN;
+ int ret= 2;
+
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+
+ if (was_cut == 0 && have_smth_to_conv == 0) // special case: zero date
+ {
+ was_cut= MYSQL_TIME_WARN_OUT_OF_RANGE;
+ }
+ else if (!have_smth_to_conv)
+ {
+ bzero(ltime, sizeof(*ltime));
+ was_cut= MYSQL_TIME_WARN_TRUNCATED;
+ ret= 1;
+ }
+ else if (!MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) &&
+ (MYSQL_TIME_WARN_HAVE_NOTES(was_cut) ||
+ (mysql_type_to_time_type(type()) == MYSQL_TIMESTAMP_DATE &&
+ (ltime->hour || ltime->minute || ltime->second || ltime->second_part))))
+ {
+ trunc_level= Sql_condition::WARN_LEVEL_NOTE;
+ was_cut|= MYSQL_TIME_WARN_TRUNCATED;
+ ret= 3;
+ }
+ set_warnings(trunc_level, str, was_cut, mysql_type_to_time_type(type()));
+ store_TIME(ltime);
+ return was_cut ? ret : 0;
+}
+
+
+int Field_temporal_with_date::store(const char *from, uint len, CHARSET_INFO *cs)
+{
+ MYSQL_TIME ltime;
+ MYSQL_TIME_STATUS status;
+ THD *thd= get_thd();
+ ErrConvString str(from, len, cs);
+ bool func_res= !str_to_datetime(cs, from, len, &ltime,
+ sql_mode_for_dates(thd),
+ &status);
+ return store_TIME_with_warning(&ltime, &str, status.warnings, func_res);
+}
+
+
+int Field_temporal_with_date::store(double nr)
+{
+ int error= 0;
+ MYSQL_TIME ltime;
+ THD *thd= get_thd();
+ ErrConvDouble str(nr);
+
+ longlong tmp= double_to_datetime(nr, &ltime,
+ sql_mode_for_dates(thd), &error);
+ return store_TIME_with_warning(&ltime, &str, error, tmp != -1);
+}
+
+
+int Field_temporal_with_date::store(longlong nr, bool unsigned_val)
+{
+ int error;
+ MYSQL_TIME ltime;
+ longlong tmp;
+ THD *thd= get_thd();
+ ErrConvInteger str(nr, unsigned_val);
+
+ tmp= number_to_datetime(nr, 0, &ltime, sql_mode_for_dates(thd), &error);
+
+ return store_TIME_with_warning(&ltime, &str, error, tmp != -1);
+}
+
+
+int Field_temporal_with_date::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ int error= 0, have_smth_to_conv= 1;
+ ErrConvTime str(ltime);
+ MYSQL_TIME l_time;
+
+ if (copy_or_convert_to_datetime(get_thd(), ltime, &l_time))
+ {
+ /*
+ Set have_smth_to_conv and error in a way to have
+ store_TIME_with_warning do bzero().
+ */
+ have_smth_to_conv= false;
+ error= MYSQL_TIME_WARN_OUT_OF_RANGE;
+ goto store;
+ }
+
+ /*
+ We don't perform range checking here since values stored in TIME
+ structure always fit into DATETIME range.
+ */
+ have_smth_to_conv= !check_date(&l_time, pack_time(&l_time) != 0,
+ sql_mode_for_dates(current_thd), &error);
+store:
+ return store_TIME_with_warning(&l_time, &str, error, have_smth_to_conv);
+}
+
+my_decimal *Field_temporal::val_decimal(my_decimal *d)
+{
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, 0))
+ {
+ bzero(&ltime, sizeof(ltime));
+ ltime.time_type= mysql_type_to_time_type(type());
+ }
+ return TIME_to_my_decimal(&ltime, d);
+}
+
+/****************************************************************************
+** time type
+** In string context: HH:MM:SS
+** In number context: HHMMSS
+** Stored as a 3 byte unsigned int
+****************************************************************************/
+int Field_time::store_TIME_with_warning(MYSQL_TIME *ltime,
+ const ErrConv *str,
+ int was_cut,
+ int have_smth_to_conv)
+{
+ Sql_condition::enum_warning_level trunc_level= Sql_condition::WARN_LEVEL_WARN;
+ int ret= 2;
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+
+ if (!have_smth_to_conv)
+ {
+ bzero(ltime, sizeof(*ltime));
+ was_cut= MYSQL_TIME_WARN_TRUNCATED;
+ ret= 1;
+ }
+ else if (!MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) &&
+ ((ltime->year || ltime->month) ||
+ MYSQL_TIME_WARN_HAVE_NOTES(was_cut)))
+ {
+ if (ltime->year || ltime->month)
+ ltime->year= ltime->month= ltime->day= 0;
+ trunc_level= Sql_condition::WARN_LEVEL_NOTE;
+ was_cut|= MYSQL_TIME_WARN_TRUNCATED;
+ ret= 3;
+ }
+ set_warnings(trunc_level, str, was_cut, MYSQL_TIMESTAMP_TIME);
+ store_TIME(ltime);
+ return was_cut ? ret : 0;
+}
+
+
+void Field_time::store_TIME(MYSQL_TIME *ltime)
+{
+ long tmp= (ltime->day*24L+ltime->hour)*10000L +
+ (ltime->minute*100+ltime->second);
+ if (ltime->neg)
+ tmp= -tmp;
+ int3store(ptr,tmp);
+}
+
+int Field_time::store(const char *from,uint len,CHARSET_INFO *cs)
+{
+ MYSQL_TIME ltime;
+ MYSQL_TIME_STATUS status;
+ ErrConvString str(from, len, cs);
+ bool have_smth_to_conv=
+ !str_to_time(cs, from, len, &ltime, sql_mode_for_dates(get_thd()),
+ &status);
+
+ return store_TIME_with_warning(&ltime, &str,
+ status.warnings, have_smth_to_conv);
+}
+
+
+/**
+ subtract a given number of days from DATETIME, return TIME
+
+ optimized version of calc_time_diff()
+
+ @note it might generate TIME values outside of the valid TIME range!
+*/
+static void calc_datetime_days_diff(MYSQL_TIME *ltime, long days)
+{
+ long daydiff= calc_daynr(ltime->year, ltime->month, ltime->day) - days;
+ ltime->year= ltime->month= 0;
+ if (daydiff >=0 )
+ ltime->day= daydiff;
+ else
+ {
+ longlong timediff= ((((daydiff * 24LL +
+ ltime->hour) * 60LL +
+ ltime->minute) * 60LL +
+ ltime->second) * 1000000LL +
+ ltime->second_part);
+ unpack_time(timediff, ltime);
+ }
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+}
+
+
+int Field_time::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ MYSQL_TIME l_time= *ltime;
+ ErrConvTime str(ltime);
+ int was_cut= 0;
+
+ if (curdays && l_time.time_type != MYSQL_TIMESTAMP_TIME)
+ calc_datetime_days_diff(&l_time, curdays);
+
+ int have_smth_to_conv= !check_time_range(&l_time, decimals(), &was_cut);
+ return store_TIME_with_warning(&l_time, &str, was_cut, have_smth_to_conv);
+}
+
+
+int Field_time::store(double nr)
+{
+ MYSQL_TIME ltime;
+ ErrConvDouble str(nr);
+ int was_cut;
+ bool neg= nr < 0;
+ if (neg)
+ nr= -nr;
+ int have_smth_to_conv= !number_to_time(neg, (ulonglong) nr,
+ (ulong)((nr - floor(nr)) * TIME_SECOND_PART_FACTOR),
+ &ltime, &was_cut);
+
+ return store_TIME_with_warning(&ltime, &str, was_cut, have_smth_to_conv);
+}
+
+
+int Field_time::store(longlong nr, bool unsigned_val)
+{
+ MYSQL_TIME ltime;
+ ErrConvInteger str(nr, unsigned_val);
+ int was_cut;
+ if (nr < 0 && unsigned_val)
+ nr= 99991231235959LL + 1;
+ int have_smth_to_conv= !number_to_time(nr < 0,
+ (ulonglong) (nr < 0 ? -nr : nr),
+ 0, &ltime, &was_cut);
+
+ return store_TIME_with_warning(&ltime, &str, was_cut, have_smth_to_conv);
+}
+
+
+void Field_time::set_curdays(THD *thd)
+{
+ MYSQL_TIME ltime;
+ set_current_date(thd, &ltime);
+ curdays= calc_daynr(ltime.year, ltime.month, ltime.day);
+}
+
+
+Field *Field_time::new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit)
+{
+ THD *thd= get_thd();
+ Field_time *res=
+ (Field_time*) Field::new_key_field(root, new_table, new_ptr, length,
+ new_null_ptr, new_null_bit);
+ if (!(thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) && res)
+ res->set_curdays(thd);
+ return res;
+}
+
+
+double Field_time::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ uint32 j= (uint32) uint3korr(ptr);
+ return (double) j;
+}
+
+longlong Field_time::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ return (longlong) sint3korr(ptr);
+}
+
+
+/**
+ @note
+ This function is multi-byte safe as the result string is always of type
+ my_charset_bin
+*/
+
+String *Field_time::val_str(String *str,
+ String *unused __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ MYSQL_TIME ltime;
+ get_date(&ltime, TIME_TIME_ONLY);
+ str->alloc(field_length + 1);
+ str->length(my_time_to_str(&ltime, const_cast<char*>(str->ptr()), decimals()));
+ str->set_charset(&my_charset_numeric);
+ return str;
+}
+
+
+bool Field_time::check_zero_in_date_with_warn(ulonglong fuzzydate)
+{
+ if (!(fuzzydate & TIME_TIME_ONLY) && (fuzzydate & TIME_NO_ZERO_IN_DATE))
+ {
+ THD *thd= get_thd();
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_DATA_OUT_OF_RANGE,
+ ER(ER_WARN_DATA_OUT_OF_RANGE), field_name,
+ thd->get_stmt_da()->current_row_for_warning());
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ @note
+ Normally we would not consider 'time' as a valid date, but we allow
+ get_date() here to be able to do things like
+ DATE_FORMAT(time, "%l.%i %p")
+*/
+
+bool Field_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ if (check_zero_in_date_with_warn(fuzzydate))
+ return true;
+ long tmp=(long) sint3korr(ptr);
+ ltime->neg=0;
+ if (tmp < 0)
+ {
+ ltime->neg= 1;
+ tmp=-tmp;
+ }
+ ltime->year= ltime->month= ltime->day= 0;
+ ltime->hour= (int) (tmp/10000);
+ tmp-=ltime->hour*10000;
+ ltime->minute= (int) tmp/100;
+ ltime->second= (int) tmp % 100;
+ ltime->second_part=0;
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+ return 0;
+}
+
+
+bool Field_time::send_binary(Protocol *protocol)
+{
+ MYSQL_TIME ltime;
+ get_date(&ltime, TIME_TIME_ONLY);
+ return protocol->store_time(&ltime, decimals());
+}
+
+
+int Field_time::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ int32 a,b;
+ a=(int32) sint3korr(a_ptr);
+ b=(int32) sint3korr(b_ptr);
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_time::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ to[0] = (uchar) (ptr[2] ^ 128);
+ to[1] = ptr[1];
+ to[2] = ptr[0];
+}
+
+void Field_time::sql_type(String &res) const
+{
+ if (decimals() == 0)
+ {
+ res.set_ascii(STRING_WITH_LEN("time"));
+ return;
+ }
+ const CHARSET_INFO *cs= res.charset();
+ res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(),
+ "time(%d)", decimals()));
+}
+
+int Field_time_hires::reset()
+{
+ store_bigendian(zero_point, ptr, Field_time_hires::pack_length());
+ return 0;
+}
+
+
+void Field_time_hires::store_TIME(MYSQL_TIME *ltime)
+{
+ ulonglong packed= sec_part_shift(pack_time(ltime), dec) + zero_point;
+ store_bigendian(packed, ptr, Field_time_hires::pack_length());
+}
+
+int Field_time::store_decimal(const my_decimal *d)
+{
+ ulonglong nr;
+ ulong sec_part;
+ ErrConvDecimal str(d);
+ MYSQL_TIME ltime;
+ int was_cut;
+ bool neg= my_decimal2seconds(d, &nr, &sec_part);
+
+ int have_smth_to_conv= !number_to_time(neg, nr, sec_part, &ltime, &was_cut);
+
+ return store_TIME_with_warning(&ltime, &str, was_cut, have_smth_to_conv);
+}
+
+uint32 Field_time_hires::pack_length() const
+{
+ return time_hires_bytes[dec];
+}
+
+longlong Field_time_with_dec::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ MYSQL_TIME ltime;
+ get_date(&ltime, TIME_TIME_ONLY);
+ longlong val= TIME_to_ulonglong_time(&ltime);
+ return ltime.neg ? -val : val;
+}
+
+double Field_time_with_dec::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ MYSQL_TIME ltime;
+ get_date(&ltime, TIME_TIME_ONLY);
+ return TIME_to_double(&ltime);
+}
+
+bool Field_time_hires::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ if (check_zero_in_date_with_warn(fuzzydate))
+ return true;
+ uint32 len= pack_length();
+ longlong packed= read_bigendian(ptr, len);
+
+ packed= sec_part_unshift(packed - zero_point, dec);
+
+ unpack_time(packed, ltime);
+ /*
+ unpack_time() returns MYSQL_TIMESTAMP_DATETIME.
+ To get MYSQL_TIMESTAMP_TIME we need few adjustments
+ */
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+ ltime->hour+= (ltime->month*32+ltime->day)*24;
+ ltime->month= ltime->day= 0;
+ return false;
+}
+
+
+int Field_time_hires::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ ulonglong a=read_bigendian(a_ptr, Field_time_hires::pack_length());
+ ulonglong b=read_bigendian(b_ptr, Field_time_hires::pack_length());
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_time_hires::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ DBUG_ASSERT(length == Field_time_hires::pack_length());
+ memcpy(to, ptr, length);
+ to[0]^= 128;
+}
+
+void Field_time_with_dec::make_field(Send_field *field)
+{
+ Field::make_field(field);
+ field->decimals= dec;
+}
+
+/****************************************************************************
+** time type with fsp (MySQL-5.6 version)
+** In string context: HH:MM:SS.FFFFFF
+** In number context: HHMMSS.FFFFFF
+****************************************************************************/
+
+int Field_timef::reset()
+{
+ my_time_packed_to_binary(0, ptr, dec);
+ return 0;
+}
+
+void Field_timef::store_TIME(MYSQL_TIME *ltime)
+{
+ my_time_trunc(ltime, decimals());
+ longlong tmp= TIME_to_longlong_time_packed(ltime);
+ my_time_packed_to_binary(tmp, ptr, dec);
+}
+
+bool Field_timef::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ if (check_zero_in_date_with_warn(fuzzydate))
+ return true;
+ longlong tmp= my_time_packed_from_binary(ptr, dec);
+ TIME_from_longlong_time_packed(ltime, tmp);
+ return false;
+}
+
+/****************************************************************************
+** year type
+** Save in a byte the year 0, 1901->2155
+** Can handle 2 byte or 4 byte years!
+****************************************************************************/
+
+int Field_year::store(const char *from, uint len,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ char *end;
+ int error;
+ longlong nr= cs->cset->strntoull10rnd(cs, from, len, 0, &end, &error);
+
+ if (nr < 0 || (nr >= 100 && nr <= 1900) || nr > 2155 ||
+ error == MY_ERRNO_ERANGE)
+ {
+ *ptr=0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ return 1;
+ }
+ if (get_thd()->count_cuted_fields &&
+ (error= check_int(cs, from, len, end, error)))
+ {
+ if (error == 1) /* empty or incorrect string */
+ {
+ *ptr= 0;
+ return 1;
+ }
+ error= 1;
+ }
+
+ if (nr != 0 || len != 4)
+ {
+ if (nr < YY_PART_YEAR)
+ nr+=100; // 2000 - 2069
+ else if (nr > 1900)
+ nr-= 1900;
+ }
+ *ptr= (char) (uchar) nr;
+ return error;
+}
+
+
+int Field_year::store(double nr)
+{
+ if (nr < 0.0 || nr > 2155.0)
+ {
+ (void) Field_year::store((longlong) -1, FALSE);
+ return 1;
+ }
+ return Field_year::store((longlong) nr, FALSE);
+}
+
+
+int Field_year::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ if (nr < 0 || (nr >= 100 && nr <= 1900) || nr > 2155)
+ {
+ *ptr= 0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ return 1;
+ }
+ if (nr != 0 || field_length != 4) // 0000 -> 0; 00 -> 2000
+ {
+ if (nr < YY_PART_YEAR)
+ nr+=100; // 2000 - 2069
+ else if (nr > 1900)
+ nr-= 1900;
+ }
+ *ptr= (char) (uchar) nr;
+ return 0;
+}
+
+
+int Field_year::store_time_dec(MYSQL_TIME *ltime, uint dec)
+{
+ ErrConvTime str(ltime);
+ if (Field_year::store(ltime->year, 0))
+ return 1;
+
+ set_datetime_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED,
+ &str, ltime->time_type, 1);
+ return 0;
+}
+
+bool Field_year::send_binary(Protocol *protocol)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ ulonglong tmp= Field_year::val_int();
+ return protocol->store_short(tmp);
+}
+
+
+double Field_year::val_real(void)
+{
+ return (double) Field_year::val_int();
+}
+
+
+longlong Field_year::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ DBUG_ASSERT(field_length == 2 || field_length == 4);
+ int tmp= (int) ptr[0];
+ if (field_length != 4)
+ tmp%=100; // Return last 2 char
+ else if (tmp)
+ tmp+=1900;
+ return (longlong) tmp;
+}
+
+
+String *Field_year::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ DBUG_ASSERT(field_length < 5);
+ val_buffer->alloc(5);
+ val_buffer->length(field_length);
+ char *to=(char*) val_buffer->ptr();
+ sprintf(to,field_length == 2 ? "%02d" : "%04d",(int) Field_year::val_int());
+ val_buffer->set_charset(&my_charset_numeric);
+ return val_buffer;
+}
+
+
+bool Field_year::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ int tmp= (int) ptr[0];
+ if (tmp || field_length != 4)
+ tmp+= 1900;
+ return int_to_datetime_with_warn(false, tmp * 10000,
+ ltime, fuzzydate, field_name);
+}
+
+
+void Field_year::sql_type(String &res) const
+{
+ CHARSET_INFO *cs=res.charset();
+ res.length(cs->cset->snprintf(cs,(char*)res.ptr(),res.alloced_length(),
+ "year(%d)",(int) field_length));
+}
+
+
+/****************************************************************************
+** date type
+** In string context: YYYY-MM-DD
+** In number context: YYYYMMDD
+** Stored as a 4 byte unsigned int
+****************************************************************************/
+
+void Field_date::store_TIME(MYSQL_TIME *ltime)
+{
+ uint tmp= ltime->year*10000L + ltime->month*100+ltime->day;
+ int4store(ptr,tmp);
+}
+
+bool Field_date::send_binary(Protocol *protocol)
+{
+ longlong tmp= Field_date::val_int();
+ MYSQL_TIME tm;
+ tm.year= (uint32) tmp/10000L % 10000;
+ tm.month= (uint32) tmp/100 % 100;
+ tm.day= (uint32) tmp % 100;
+ return protocol->store_date(&tm);
+}
+
+
+double Field_date::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int32 j;
+ j=sint4korr(ptr);
+ return (double) (uint32) j;
+}
+
+
+longlong Field_date::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int32 j;
+ j=sint4korr(ptr);
+ return (longlong) (uint32) j;
+}
+
+
+String *Field_date::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ MYSQL_TIME ltime;
+ int32 tmp;
+ tmp=sint4korr(ptr);
+ ltime.neg= 0;
+ ltime.year= (int) ((uint32) tmp/10000L % 10000);
+ ltime.month= (int) ((uint32) tmp/100 % 100);
+ ltime.day= (int) ((uint32) tmp % 100);
+
+ val_buffer->alloc(MAX_DATE_STRING_REP_LENGTH);
+ uint length= (uint) my_date_to_str(&ltime,
+ const_cast<char*>(val_buffer->ptr()));
+ val_buffer->length(length);
+ val_buffer->set_charset(&my_charset_numeric);
+
+ return val_buffer;
+}
+
+
+int Field_date::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ int32 a,b;
+ a=sint4korr(a_ptr);
+ b=sint4korr(b_ptr);
+ return ((uint32) a < (uint32) b) ? -1 : ((uint32) a > (uint32) b) ? 1 : 0;
+}
+
+
+void Field_date::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ to[0] = ptr[3];
+ to[1] = ptr[2];
+ to[2] = ptr[1];
+ to[3] = ptr[0];
+}
+
+void Field_date::sql_type(String &res) const
+{
+ res.set_ascii(STRING_WITH_LEN("date"));
+}
+
+
+/****************************************************************************
+** The new date type
+** This is identical to the old date type, but stored on 3 bytes instead of 4
+** In number context: YYYYMMDD
+****************************************************************************/
+
+void Field_newdate::store_TIME(MYSQL_TIME *ltime)
+{
+ uint tmp= ltime->year*16*32 + ltime->month*32+ltime->day;
+ int3store(ptr,tmp);
+}
+
+
+bool Field_newdate::send_binary(Protocol *protocol)
+{
+ MYSQL_TIME tm;
+ Field_newdate::get_date(&tm,0);
+ return protocol->store_date(&tm);
+}
+
+
+double Field_newdate::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ return (double) Field_newdate::val_int();
+}
+
+
+longlong Field_newdate::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ ulong j= uint3korr(ptr);
+ j= (j % 32L)+(j / 32L % 16L)*100L + (j/(16L*32L))*10000L;
+ return (longlong) j;
+}
+
+
+String *Field_newdate::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ val_buffer->alloc(field_length);
+ val_buffer->length(field_length);
+ uint32 tmp=(uint32) uint3korr(ptr);
+ int part;
+ char *pos=(char*) val_buffer->ptr()+10;
+
+ /* Open coded to get more speed */
+ *pos--=0; // End NULL
+ part=(int) (tmp & 31);
+ *pos--= (char) ('0'+part%10);
+ *pos--= (char) ('0'+part/10);
+ *pos--= '-';
+ part=(int) (tmp >> 5 & 15);
+ *pos--= (char) ('0'+part%10);
+ *pos--= (char) ('0'+part/10);
+ *pos--= '-';
+ part=(int) (tmp >> 9);
+ *pos--= (char) ('0'+part%10); part/=10;
+ *pos--= (char) ('0'+part%10); part/=10;
+ *pos--= (char) ('0'+part%10); part/=10;
+ *pos= (char) ('0'+part);
+ val_buffer->set_charset(&my_charset_numeric);
+ return val_buffer;
+}
+
+
+bool Field_newdate::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ uint32 tmp=(uint32) uint3korr(ptr);
+ ltime->day= tmp & 31;
+ ltime->month= (tmp >> 5) & 15;
+ ltime->year= (tmp >> 9);
+ ltime->time_type= MYSQL_TIMESTAMP_DATE;
+ ltime->hour= ltime->minute= ltime->second= ltime->second_part= ltime->neg= 0;
+ if (!tmp)
+ return fuzzydate & TIME_NO_ZERO_DATE;
+ if (!ltime->month || !ltime->day)
+ return fuzzydate & TIME_NO_ZERO_IN_DATE;
+ return 0;
+}
+
+
+int Field_newdate::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ uint32 a,b;
+ a=(uint32) uint3korr(a_ptr);
+ b=(uint32) uint3korr(b_ptr);
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+
+void Field_newdate::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ to[0] = ptr[2];
+ to[1] = ptr[1];
+ to[2] = ptr[0];
+}
+
+
+void Field_newdate::sql_type(String &res) const
+{
+ res.set_ascii(STRING_WITH_LEN("date"));
+}
+
+
+/****************************************************************************
+** datetime type
+** In string context: YYYY-MM-DD HH:MM:DD
+** In number context: YYYYMMDDHHMMDD
+** Stored as a 8 byte unsigned int. Should sometimes be change to a 6 byte int.
+****************************************************************************/
+
+void Field_datetime::store_TIME(MYSQL_TIME *ltime)
+{
+ ulonglong tmp= TIME_to_ulonglong_datetime(ltime);
+ int8store(ptr,tmp);
+}
+
+bool Field_datetime::send_binary(Protocol *protocol)
+{
+ MYSQL_TIME tm;
+ Field_datetime::get_date(&tm, 0);
+ return protocol->store(&tm, 0);
+}
+
+
+double Field_datetime::val_real(void)
+{
+ return (double) Field_datetime::val_int();
+}
+
+longlong Field_datetime::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ longlong j;
+ j=sint8korr(ptr);
+ return j;
+}
+
+
+String *Field_datetime::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ val_buffer->alloc(field_length);
+ val_buffer->length(field_length);
+
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ ulonglong tmp;
+ long part1,part2;
+ char *pos;
+ int part3;
+
+ tmp= Field_datetime::val_int();
+
+ /*
+ Avoid problem with slow longlong arithmetic and sprintf
+ */
+
+ part1=(long) (tmp/1000000LL);
+ part2=(long) (tmp - (ulonglong) part1*1000000LL);
+
+ pos=(char*) val_buffer->ptr() + MAX_DATETIME_WIDTH;
+ *pos--=0;
+ *pos--= (char) ('0'+(char) (part2%10)); part2/=10;
+ *pos--= (char) ('0'+(char) (part2%10)); part3= (int) (part2 / 10);
+ *pos--= ':';
+ *pos--= (char) ('0'+(char) (part3%10)); part3/=10;
+ *pos--= (char) ('0'+(char) (part3%10)); part3/=10;
+ *pos--= ':';
+ *pos--= (char) ('0'+(char) (part3%10)); part3/=10;
+ *pos--= (char) ('0'+(char) part3);
+ *pos--= ' ';
+ *pos--= (char) ('0'+(char) (part1%10)); part1/=10;
+ *pos--= (char) ('0'+(char) (part1%10)); part1/=10;
+ *pos--= '-';
+ *pos--= (char) ('0'+(char) (part1%10)); part1/=10;
+ *pos--= (char) ('0'+(char) (part1%10)); part3= (int) (part1/10);
+ *pos--= '-';
+ *pos--= (char) ('0'+(char) (part3%10)); part3/=10;
+ *pos--= (char) ('0'+(char) (part3%10)); part3/=10;
+ *pos--= (char) ('0'+(char) (part3%10)); part3/=10;
+ *pos=(char) ('0'+(char) part3);
+ val_buffer->set_charset(&my_charset_numeric);
+ return val_buffer;
+}
+
+bool Field_datetime::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ longlong tmp=Field_datetime::val_int();
+ uint32 part1,part2;
+ part1=(uint32) (tmp/1000000LL);
+ part2=(uint32) (tmp - (ulonglong) part1*1000000LL);
+
+ ltime->time_type= MYSQL_TIMESTAMP_DATETIME;
+ ltime->neg= 0;
+ ltime->second_part= 0;
+ ltime->second= (int) (part2%100);
+ ltime->minute= (int) (part2/100%100);
+ ltime->hour= (int) (part2/10000);
+ ltime->day= (int) (part1%100);
+ ltime->month= (int) (part1/100%100);
+ ltime->year= (int) (part1/10000);
+ if (!tmp)
+ return fuzzydate & TIME_NO_ZERO_DATE;
+ if (!ltime->month || !ltime->day)
+ return fuzzydate & TIME_NO_ZERO_IN_DATE;
+ return 0;
+}
+
+int Field_datetime::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ longlong a,b;
+ a=sint8korr(a_ptr);
+ b=sint8korr(b_ptr);
+ return ((ulonglong) a < (ulonglong) b) ? -1 :
+ ((ulonglong) a > (ulonglong) b) ? 1 : 0;
+}
+
+void Field_datetime::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ to[0] = ptr[7];
+ to[1] = ptr[6];
+ to[2] = ptr[5];
+ to[3] = ptr[4];
+ to[4] = ptr[3];
+ to[5] = ptr[2];
+ to[6] = ptr[1];
+ to[7] = ptr[0];
+}
+
+
+void Field_datetime::sql_type(String &res) const
+{
+ if (decimals() == 0)
+ {
+ res.set_ascii(STRING_WITH_LEN("datetime"));
+ return;
+ }
+ CHARSET_INFO *cs= res.charset();
+ res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(),
+ "datetime(%u)", decimals()));
+}
+
+
+int Field_datetime::set_time()
+{
+ THD *thd= table->in_use;
+ MYSQL_TIME now_time;
+ thd->variables.time_zone->gmt_sec_to_TIME(&now_time, thd->query_start());
+ now_time.second_part= thd->query_start_sec_part();
+ set_notnull();
+ store_TIME(&now_time);
+ thd->time_zone_used= 1;
+ return 0;
+}
+
+
+void Field_datetime_hires::store_TIME(MYSQL_TIME *ltime)
+{
+ ulonglong packed= sec_part_shift(pack_time(ltime), dec);
+ store_bigendian(packed, ptr, Field_datetime_hires::pack_length());
+}
+
+int Field_temporal_with_date::store_decimal(const my_decimal *d)
+{
+ ulonglong nr;
+ ulong sec_part;
+ int error;
+ MYSQL_TIME ltime;
+ longlong tmp;
+ THD *thd= get_thd();
+ ErrConvDecimal str(d);
+
+ if (my_decimal2seconds(d, &nr, &sec_part))
+ {
+ tmp= -1;
+ error= 2;
+ }
+ else
+ tmp= number_to_datetime(nr, sec_part, &ltime, sql_mode_for_dates(thd),
+ &error);
+
+ return store_TIME_with_warning(&ltime, &str, error, tmp != -1);
+}
+
+bool Field_datetime_with_dec::send_binary(Protocol *protocol)
+{
+ MYSQL_TIME ltime;
+ get_date(&ltime, 0);
+ return protocol->store(&ltime, dec);
+}
+
+
+double Field_datetime_with_dec::val_real(void)
+{
+ MYSQL_TIME ltime;
+ get_date(&ltime, 0);
+ return TIME_to_double(&ltime);
+}
+
+longlong Field_datetime_with_dec::val_int(void)
+{
+ MYSQL_TIME ltime;
+ get_date(&ltime, 0);
+ return TIME_to_ulonglong_datetime(&ltime);
+}
+
+
+String *Field_datetime_with_dec::val_str(String *str,
+ String *unused __attribute__((unused)))
+{
+ MYSQL_TIME ltime;
+ get_date(&ltime, 0);
+ str->alloc(field_length+1);
+ str->length(field_length);
+ my_datetime_to_str(&ltime, (char*) str->ptr(), dec);
+ str->set_charset(&my_charset_numeric);
+ return str;
+}
+
+bool Field_datetime_hires::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ ulonglong packed= read_bigendian(ptr, Field_datetime_hires::pack_length());
+ unpack_time(sec_part_unshift(packed, dec), ltime);
+ if (!packed)
+ return fuzzydate & TIME_NO_ZERO_DATE;
+ if (!ltime->month || !ltime->day)
+ return fuzzydate & TIME_NO_ZERO_IN_DATE;
+ return 0;
+}
+
+uint32 Field_datetime_hires::pack_length() const
+{
+ return datetime_hires_bytes[dec];
+}
+
+int Field_datetime_hires::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ ulonglong a=read_bigendian(a_ptr, Field_datetime_hires::pack_length());
+ ulonglong b=read_bigendian(b_ptr, Field_datetime_hires::pack_length());
+ return a < b ? -1 : a > b ? 1 : 0;
+}
+
+void Field_datetime_with_dec::make_field(Send_field *field)
+{
+ Field::make_field(field);
+ field->decimals= dec;
+}
+
+
+/****************************************************************************
+** MySQL-5.6 compatible DATETIME(N)
+**
+****************************************************************************/
+int Field_datetimef::reset()
+{
+ my_datetime_packed_to_binary(0, ptr, dec);
+ return 0;
+}
+
+void Field_datetimef::store_TIME(MYSQL_TIME *ltime)
+{
+ my_time_trunc(ltime, decimals());
+ longlong tmp= TIME_to_longlong_datetime_packed(ltime);
+ my_datetime_packed_to_binary(tmp, ptr, dec);
+}
+
+bool Field_datetimef::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ longlong tmp= my_datetime_packed_from_binary(ptr, dec);
+ TIME_from_longlong_datetime_packed(ltime, tmp);
+ if (!tmp)
+ return fuzzydate & TIME_NO_ZERO_DATE;
+ if (!ltime->month || !ltime->day)
+ return fuzzydate & TIME_NO_ZERO_IN_DATE;
+ return false;
+}
+
+
+/****************************************************************************
+** string type
+** A string may be varchar or binary
+****************************************************************************/
+
+/*
+ Report "not well formed" or "cannot convert" error
+ after storing a character string info a field.
+
+ SYNOPSIS
+ check_string_copy_error()
+ field - Field
+ well_formed_error_pos - where not well formed data was first met
+ cannot_convert_error_pos - where a not-convertable character was first met
+ end - end of the string
+ cs - character set of the string
+
+ NOTES
+ As of version 5.0 both cases return the same error:
+
+ "Invalid string value: 'xxx' for column 't' at row 1"
+
+ Future versions will possibly introduce a new error message:
+
+ "Cannot convert character string: 'xxx' for column 't' at row 1"
+
+ RETURN
+ FALSE - If errors didn't happen
+ TRUE - If an error happened
+*/
+
+static bool
+check_string_copy_error(Field_str *field,
+ const char *well_formed_error_pos,
+ const char *cannot_convert_error_pos,
+ const char *end,
+ CHARSET_INFO *cs)
+{
+ const char *pos;
+ char tmp[32];
+ THD *thd;
+
+ thd= field->get_thd();
+
+ if (!(pos= well_formed_error_pos) &&
+ !(pos= cannot_convert_error_pos))
+ return FALSE;
+
+ convert_to_printable(tmp, sizeof(tmp), pos, (end - pos), cs, 6);
+
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
+ ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
+ "string", tmp, field->field_name,
+ thd->get_stmt_da()->current_row_for_warning());
+ return TRUE;
+}
+
+
+/*
+ Check if we lost any important data and send a truncation error/warning
+
+ SYNOPSIS
+ Field_longstr::report_if_important_data()
+ pstr - Truncated rest of string
+ end - End of truncated string
+ count_spaces - Treat traling spaces as important data
+
+ RETURN VALUES
+ 0 - None was truncated (or we don't count cut fields)
+ 2 - Some bytes was truncated
+
+ NOTE
+ Check if we lost any important data (anything in a binary string,
+ or any non-space in others). If only trailing spaces was lost,
+ send a truncation note, otherwise send a truncation error.
+ Silently ignore traling spaces if the count_space parameter is FALSE.
+*/
+
+int
+Field_longstr::report_if_important_data(const char *pstr, const char *end,
+ bool count_spaces)
+{
+ THD *thd= get_thd();
+ if ((pstr < end) && thd->count_cuted_fields)
+ {
+ if (test_if_important_data(field_charset, pstr, end))
+ {
+ if (thd->abort_on_warning)
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1);
+ else
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ return 2;
+ }
+ else if (count_spaces)
+ { /* If we lost only spaces then produce a NOTE, not a WARNING */
+ set_warning(Sql_condition::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1);
+ return 2;
+ }
+ }
+ return 0;
+}
+
+
+ /* Copy a string and fill with space */
+
+int Field_string::store(const char *from,uint length,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ uint copy_length;
+ const char *well_formed_error_pos;
+ const char *cannot_convert_error_pos;
+ const char *from_end_pos;
+
+ /* See the comment for Field_long::store(long long) */
+ DBUG_ASSERT(!table || table->in_use == current_thd);
+
+ copy_length= well_formed_copy_nchars(field_charset,
+ (char*) ptr, field_length,
+ cs, from, length,
+ field_length / field_charset->mbmaxlen,
+ &well_formed_error_pos,
+ &cannot_convert_error_pos,
+ &from_end_pos);
+
+ /* Append spaces if the string was shorter than the field. */
+ if (copy_length < field_length)
+ field_charset->cset->fill(field_charset,(char*) ptr+copy_length,
+ field_length-copy_length,
+ field_charset->pad_char);
+
+ if (check_string_copy_error(this, well_formed_error_pos,
+ cannot_convert_error_pos, from + length, cs))
+ return 2;
+
+ return report_if_important_data(from_end_pos, from + length, FALSE);
+}
+
+
+/**
+ Store double value in Field_string or Field_varstring.
+
+ Pretty prints double number into field_length characters buffer.
+
+ @param nr number
+*/
+
+int Field_str::store(double nr)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE];
+ uint local_char_length= field_length / charset()->mbmaxlen;
+ size_t length= 0;
+ my_bool error= (local_char_length == 0);
+
+ // my_gcvt() requires width > 0, and we may have a CHAR(0) column.
+ if (!error)
+ length= my_gcvt(nr, MY_GCVT_ARG_DOUBLE, local_char_length, buff, &error);
+
+ if (error)
+ {
+ if (get_thd()->abort_on_warning)
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1);
+ else
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ }
+ return store(buff, length, &my_charset_numeric);
+}
+
+uint Field::is_equal(Create_field *new_field)
+{
+ return (new_field->sql_type == real_type());
+}
+
+
+uint Field_str::is_equal(Create_field *new_field)
+{
+ if (field_flags_are_binary() != new_field->field_flags_are_binary())
+ return 0;
+
+ return ((new_field->sql_type == real_type()) &&
+ new_field->charset == field_charset &&
+ new_field->length == max_display_length());
+}
+
+
+int Field_string::store(longlong nr, bool unsigned_val)
+{
+ char buff[64];
+ int l;
+ CHARSET_INFO *cs=charset();
+ l= (cs->cset->longlong10_to_str)(cs,buff,sizeof(buff),
+ unsigned_val ? 10 : -10, nr);
+ return Field_string::store(buff,(uint)l,cs);
+}
+
+
+int Field_longstr::store_decimal(const my_decimal *d)
+{
+ char buff[DECIMAL_MAX_STR_LENGTH+1];
+ String str(buff, sizeof(buff), &my_charset_numeric);
+ my_decimal2string(E_DEC_FATAL_ERROR, d, 0, 0, 0, &str);
+ return store(str.ptr(), str.length(), str.charset());
+}
+
+uint32 Field_longstr::max_data_length() const
+{
+ return field_length + (field_length > 255 ? 2 : 1);
+}
+
+
+double Field_string::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int error;
+ char *end;
+ CHARSET_INFO *cs= charset();
+ double result;
+
+ result= my_strntod(cs,(char*) ptr,field_length,&end,&error);
+ if (!get_thd()->no_errors &&
+ (error || (field_length != (uint32)(end - (char*) ptr) &&
+ !check_if_only_end_space(cs, end,
+ (char*) ptr + field_length))))
+ {
+ ErrConvString err((char*) ptr, field_length, cs);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), "DOUBLE",
+ err.ptr());
+ }
+ return result;
+}
+
+
+longlong Field_string::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int error;
+ char *end;
+ CHARSET_INFO *cs= charset();
+ longlong result;
+
+ result= my_strntoll(cs, (char*) ptr,field_length,10,&end,&error);
+ if (!get_thd()->no_errors &&
+ (error || (field_length != (uint32)(end - (char*) ptr) &&
+ !check_if_only_end_space(cs, end,
+ (char*) ptr + field_length))))
+ {
+ ErrConvString err((char*) ptr, field_length, cs);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE),
+ "INTEGER", err.ptr());
+ }
+ return result;
+}
+
+
+String *Field_string::val_str(String *val_buffer __attribute__((unused)),
+ String *val_ptr)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ /* See the comment for Field_long::store(long long) */
+ DBUG_ASSERT(!table || table->in_use == current_thd);
+ uint length;
+ if (get_thd()->variables.sql_mode &
+ MODE_PAD_CHAR_TO_FULL_LENGTH)
+ length= my_charpos(field_charset, ptr, ptr + field_length,
+ field_length / field_charset->mbmaxlen);
+ else
+ length= field_charset->cset->lengthsp(field_charset, (const char*) ptr,
+ field_length);
+ val_ptr->set((const char*) ptr, length, field_charset);
+ return val_ptr;
+}
+
+
+my_decimal *Field_string::val_decimal(my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int err= str2my_decimal(E_DEC_FATAL_ERROR, (char*) ptr, field_length,
+ charset(), decimal_value);
+ if (!get_thd()->no_errors && err)
+ {
+ ErrConvString errmsg((char*) ptr, field_length, charset());
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE),
+ "DECIMAL", errmsg.ptr());
+ }
+
+ return decimal_value;
+}
+
+
+struct Check_field_param {
+ Field *field;
+};
+
+#ifdef HAVE_REPLICATION
+static bool
+check_field_for_37426(const void *param_arg)
+{
+ Check_field_param *param= (Check_field_param*) param_arg;
+ DBUG_ASSERT(param->field->real_type() == MYSQL_TYPE_STRING);
+ DBUG_PRINT("debug", ("Field %s - type: %d, size: %d",
+ param->field->field_name,
+ param->field->real_type(),
+ param->field->row_pack_length()));
+ return param->field->row_pack_length() > 255;
+}
+#endif
+
+bool
+Field_string::compatible_field_size(uint field_metadata,
+ Relay_log_info *rli_arg,
+ uint16 mflags __attribute__((unused)),
+ int *order_var)
+{
+#ifdef HAVE_REPLICATION
+ const Check_field_param check_param = { this };
+ if (rpl_master_has_bug(rli_arg, 37426, TRUE,
+ check_field_for_37426, &check_param))
+ return FALSE; // Not compatible field sizes
+#endif
+ return Field::compatible_field_size(field_metadata, rli_arg, mflags, order_var);
+}
+
+
+int Field_string::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ uint a_len, b_len;
+
+ if (field_charset->mbmaxlen != 1)
+ {
+ uint char_len= field_length/field_charset->mbmaxlen;
+ a_len= my_charpos(field_charset, a_ptr, a_ptr + field_length, char_len);
+ b_len= my_charpos(field_charset, b_ptr, b_ptr + field_length, char_len);
+ }
+ else
+ a_len= b_len= field_length;
+ /*
+ We have to remove end space to be able to compare multi-byte-characters
+ like in latin_de 'ae' and 0xe4
+ */
+ return field_charset->coll->strnncollsp(field_charset,
+ a_ptr, a_len,
+ b_ptr, b_len,
+ 0);
+}
+
+
+void Field_string::sort_string(uchar *to,uint length)
+{
+ uint tmp __attribute__((unused))=
+ field_charset->coll->strnxfrm(field_charset,
+ to, length,
+ char_length() *
+ field_charset->strxfrm_multiply,
+ ptr, field_length,
+ MY_STRXFRM_PAD_WITH_SPACE |
+ MY_STRXFRM_PAD_TO_MAXLEN);
+ DBUG_ASSERT(tmp == length);
+}
+
+
+void Field_string::sql_type(String &res) const
+{
+ THD *thd= table->in_use;
+ CHARSET_INFO *cs=res.charset();
+ ulong length;
+
+ length= cs->cset->snprintf(cs,(char*) res.ptr(),
+ res.alloced_length(), "%s(%d)",
+ (type() == MYSQL_TYPE_VAR_STRING ?
+ (has_charset() ? "varchar" : "varbinary") :
+ (has_charset() ? "char" : "binary")),
+ (int) field_length / charset()->mbmaxlen);
+ res.length(length);
+ if ((thd->variables.sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)) &&
+ has_charset() && (charset()->state & MY_CS_BINSORT))
+ res.append(STRING_WITH_LEN(" binary"));
+}
+
+
+uchar *Field_string::pack(uchar *to, const uchar *from, uint max_length)
+{
+ uint length= MY_MIN(field_length,max_length);
+ uint local_char_length= max_length/field_charset->mbmaxlen;
+ DBUG_PRINT("debug", ("Packing field '%s' - length: %u ", field_name, length));
+
+ if (length > local_char_length)
+ local_char_length= my_charpos(field_charset, from, from+length,
+ local_char_length);
+ set_if_smaller(length, local_char_length);
+
+ /*
+ TODO: change charset interface to add a new function that does
+ the following or add a flag to lengthsp to do it itself
+ (this is for not packing padding adding bytes in BINARY
+ fields).
+ */
+ if (field_charset->mbmaxlen == 1)
+ {
+ while (length && from[length-1] == field_charset->pad_char)
+ length --;
+ }
+ else
+ length= field_charset->cset->lengthsp(field_charset, (const char*) from, length);
+
+ // Length always stored little-endian
+ *to++= (uchar) length;
+ if (field_length > 255)
+ *to++= (uchar) (length >> 8);
+
+ // Store the actual bytes of the string
+ memcpy(to, from, length);
+ return to+length;
+}
+
+
+/**
+ Unpack a string field from row data.
+
+ This method is used to unpack a string field from a master whose size
+ of the field is less than that of the slave. Note that there can be a
+ variety of field types represented with this class. Certain types like
+ ENUM or SET are processed differently. Hence, the upper byte of the
+ @c param_data argument contains the result of field->real_type() from
+ the master.
+
+ @note For information about how the length is packed, see @c
+ Field_string::do_save_field_metadata
+
+ @param to Destination of the data
+ @param from Source of the data
+ @param param_data Real type (upper) and length (lower) values
+
+ @return New pointer into memory based on from + length of the data
+*/
+const uchar *
+Field_string::unpack(uchar *to, const uchar *from, const uchar *from_end,
+ uint param_data)
+{
+ uint from_length, length;
+
+ /*
+ Compute the declared length of the field on the master. This is
+ used to decide if one or two bytes should be read as length.
+ */
+ if (param_data)
+ from_length= (((param_data >> 4) & 0x300) ^ 0x300) + (param_data & 0x00ff);
+ else
+ from_length= field_length;
+
+ DBUG_PRINT("debug",
+ ("param_data: 0x%x, field_length: %u, from_length: %u",
+ param_data, field_length, from_length));
+ /*
+ Compute the actual length of the data by reading one or two bits
+ (depending on the declared field length on the master).
+ */
+ if (from_length > 255)
+ {
+ if (from + 2 > from_end)
+ return 0;
+ length= uint2korr(from);
+ from+= 2;
+ }
+ else
+ {
+ if (from + 1 > from_end)
+ return 0;
+ length= (uint) *from++;
+ }
+ if (from + length > from_end || length > field_length)
+ return 0;
+
+ memcpy(to, from, length);
+ // Pad the string with the pad character of the fields charset
+ field_charset->cset->fill(field_charset, (char*) to + length, field_length - length, field_charset->pad_char);
+ return from+length;
+}
+
+
+/**
+ Save the field metadata for string fields.
+
+ Saves the real type in the first byte and the field length in the
+ second byte of the field metadata array at index of *metadata_ptr and
+ *(metadata_ptr + 1).
+
+ @note In order to be able to handle lengths exceeding 255 and be
+ backwards-compatible with pre-5.1.26 servers, an extra two bits of
+ the length has been added to the metadata in such a way that if
+ they are set, a new unrecognized type is generated. This will
+ cause pre-5.1-26 servers to stop due to a field type mismatch,
+ while new servers will be able to extract the extra bits. If the
+ length is <256, there will be no difference and both a new and an
+ old server will be able to handle it.
+
+ @note The extra two bits are added to bits 13 and 14 of the
+ parameter data (with 1 being the least siginficant bit and 16 the
+ most significant bit of the word) by xoring the extra length bits
+ with the real type. Since all allowable types have 0xF as most
+ significant bits of the metadata word, lengths <256 will not affect
+ the real type at all, while all other values will result in a
+ non-existant type in the range 17-244.
+
+ @see Field_string::unpack
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_string::do_save_field_metadata(uchar *metadata_ptr)
+{
+ DBUG_ASSERT(field_length < 1024);
+ DBUG_ASSERT((real_type() & 0xF0) == 0xF0);
+ DBUG_PRINT("debug", ("field_length: %u, real_type: %u",
+ field_length, real_type()));
+ *metadata_ptr= (real_type() ^ ((field_length & 0x300) >> 4));
+ *(metadata_ptr + 1)= field_length & 0xFF;
+ return 2;
+}
+
+
+uint Field_string::packed_col_length(const uchar *data_ptr, uint length)
+{
+ if (length > 255)
+ return uint2korr(data_ptr)+2;
+ return (uint) *data_ptr + 1;
+}
+
+
+uint Field_string::max_packed_col_length(uint max_length)
+{
+ return (max_length > 255 ? 2 : 1)+max_length;
+}
+
+
+uint Field_string::get_key_image(uchar *buff, uint length, imagetype type_arg)
+{
+ uint bytes = my_charpos(field_charset, (char*) ptr,
+ (char*) ptr + field_length,
+ length / field_charset->mbmaxlen);
+ memcpy(buff, ptr, bytes);
+ if (bytes < length)
+ field_charset->cset->fill(field_charset, (char*) buff + bytes,
+ length - bytes, field_charset->pad_char);
+ return bytes;
+}
+
+
+Field *Field_string::new_field(MEM_ROOT *root, TABLE *new_table,
+ bool keep_type)
+{
+ Field *field;
+ if (type() != MYSQL_TYPE_VAR_STRING || keep_type)
+ field= Field::new_field(root, new_table, keep_type);
+ else if ((field= new Field_varstring(field_length, maybe_null(), field_name,
+ new_table->s, charset())))
+ {
+ /*
+ Old VARCHAR field which should be modified to a VARCHAR on copy
+ This is done to ensure that ALTER TABLE will convert old VARCHAR fields
+ to now VARCHAR fields.
+ */
+ field->init(new_table);
+ /*
+ Normally orig_table is different from table only if field was created
+ via ::new_field. Here we alter the type of field, so ::new_field is
+ not applicable. But we still need to preserve the original field
+ metadata for the client-server protocol.
+ */
+ field->orig_table= orig_table;
+ }
+ return field;
+}
+
+
+/****************************************************************************
+ VARCHAR type
+ Data in field->ptr is stored as:
+ 1 or 2 bytes length-prefix-header (from Field_varstring::length_bytes)
+ data
+
+ NOTE:
+ When VARCHAR is stored in a key (for handler::index_read() etc) it's always
+ stored with a 2 byte prefix. (Just like blob keys).
+
+ Normally length_bytes is calculated as (field_length < 256 : 1 ? 2)
+ The exception is if there is a prefix key field that is part of a long
+ VARCHAR, in which case field_length for this may be 1 but the length_bytes
+ is 2.
+****************************************************************************/
+
+const uint Field_varstring::MAX_SIZE= UINT_MAX16;
+
+/**
+ Save the field metadata for varstring fields.
+
+ Saves the field length in the first byte. Note: may consume
+ 2 bytes. Caller must ensure second byte is contiguous with
+ first byte (e.g. array index 0,1).
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_varstring::do_save_field_metadata(uchar *metadata_ptr)
+{
+ DBUG_ASSERT(field_length <= 65535);
+ int2store((char*)metadata_ptr, field_length);
+ return 2;
+}
+
+int Field_varstring::store(const char *from,uint length,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ uint copy_length;
+ const char *well_formed_error_pos;
+ const char *cannot_convert_error_pos;
+ const char *from_end_pos;
+
+ copy_length= well_formed_copy_nchars(field_charset,
+ (char*) ptr + length_bytes,
+ field_length,
+ cs, from, length,
+ field_length / field_charset->mbmaxlen,
+ &well_formed_error_pos,
+ &cannot_convert_error_pos,
+ &from_end_pos);
+
+ if (length_bytes == 1)
+ *ptr= (uchar) copy_length;
+ else
+ int2store(ptr, copy_length);
+
+ if (check_string_copy_error(this, well_formed_error_pos,
+ cannot_convert_error_pos, from + length, cs))
+ return 2;
+
+ return report_if_important_data(from_end_pos, from + length, TRUE);
+}
+
+
+int Field_varstring::store(longlong nr, bool unsigned_val)
+{
+ char buff[64];
+ uint length;
+ length= (uint) (field_charset->cset->longlong10_to_str)(field_charset,
+ buff,
+ sizeof(buff),
+ (unsigned_val ? 10:
+ -10),
+ nr);
+ return Field_varstring::store(buff, length, field_charset);
+}
+
+
+double Field_varstring::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int error;
+ char *end;
+ double result;
+ CHARSET_INFO* cs= charset();
+
+ uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+ result= my_strntod(cs, (char*)ptr+length_bytes, length, &end, &error);
+
+ if (!get_thd()->no_errors &&
+ (error || (length != (uint)(end - (char*)ptr+length_bytes) &&
+ !check_if_only_end_space(cs, end, (char*)ptr+length_bytes+length))))
+ {
+ push_numerical_conversion_warning(current_thd, (char*)ptr+length_bytes,
+ length, cs,"DOUBLE",
+ ER_TRUNCATED_WRONG_VALUE);
+ }
+ return result;
+}
+
+
+longlong Field_varstring::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int error;
+ char *end;
+ CHARSET_INFO *cs= charset();
+
+ uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+ longlong result= my_strntoll(cs, (char*) ptr+length_bytes, length, 10,
+ &end, &error);
+
+ if (!get_thd()->no_errors &&
+ (error || (length != (uint)(end - (char*)ptr+length_bytes) &&
+ !check_if_only_end_space(cs, end, (char*)ptr+length_bytes+length))))
+ {
+ push_numerical_conversion_warning(current_thd, (char*)ptr+length_bytes,
+ length, cs, "INTEGER",
+ ER_TRUNCATED_WRONG_VALUE);
+ }
+ return result;
+}
+
+String *Field_varstring::val_str(String *val_buffer __attribute__((unused)),
+ String *val_ptr)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+ val_ptr->set((const char*) ptr+length_bytes, length, field_charset);
+ return val_ptr;
+}
+
+
+my_decimal *Field_varstring::val_decimal(my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ CHARSET_INFO *cs= charset();
+ uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+ int error= str2my_decimal(E_DEC_FATAL_ERROR, (char*) ptr+length_bytes, length,
+ cs, decimal_value);
+
+ if (!get_thd()->no_errors && error)
+ {
+ push_numerical_conversion_warning(current_thd, (char*)ptr+length_bytes,
+ length, cs, "DECIMAL",
+ ER_TRUNCATED_WRONG_VALUE);
+ }
+ return decimal_value;
+}
+
+
+int Field_varstring::cmp_max(const uchar *a_ptr, const uchar *b_ptr,
+ uint max_len)
+{
+ uint a_length, b_length;
+ int diff;
+
+ if (length_bytes == 1)
+ {
+ a_length= (uint) *a_ptr;
+ b_length= (uint) *b_ptr;
+ }
+ else
+ {
+ a_length= uint2korr(a_ptr);
+ b_length= uint2korr(b_ptr);
+ }
+ set_if_smaller(a_length, max_len);
+ set_if_smaller(b_length, max_len);
+ diff= field_charset->coll->strnncollsp(field_charset,
+ a_ptr+
+ length_bytes,
+ a_length,
+ b_ptr+
+ length_bytes,
+ b_length,0);
+ return diff;
+}
+
+
+/**
+ @note
+ varstring and blob keys are ALWAYS stored with a 2 byte length prefix
+*/
+
+int Field_varstring::key_cmp(const uchar *key_ptr, uint max_key_length)
+{
+ uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+ uint local_char_length= max_key_length / field_charset->mbmaxlen;
+
+ local_char_length= my_charpos(field_charset, ptr + length_bytes,
+ ptr + length_bytes + length, local_char_length);
+ set_if_smaller(length, local_char_length);
+ return field_charset->coll->strnncollsp(field_charset,
+ ptr + length_bytes,
+ length,
+ key_ptr+
+ HA_KEY_BLOB_LENGTH,
+ uint2korr(key_ptr), 0);
+}
+
+
+/**
+ Compare to key segments (always 2 byte length prefix).
+
+ @note
+ This is used only to compare key segments created for index_read().
+ (keys are created and compared in key.cc)
+*/
+
+int Field_varstring::key_cmp(const uchar *a,const uchar *b)
+{
+ return field_charset->coll->strnncollsp(field_charset,
+ a + HA_KEY_BLOB_LENGTH,
+ uint2korr(a),
+ b + HA_KEY_BLOB_LENGTH,
+ uint2korr(b),
+ 0);
+}
+
+
+void Field_varstring::sort_string(uchar *to,uint length)
+{
+ uint tot_length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+
+ if (field_charset == &my_charset_bin)
+ {
+ /* Store length last in high-byte order to sort longer strings first */
+ if (length_bytes == 1)
+ to[length-1]= tot_length;
+ else
+ mi_int2store(to+length-2, tot_length);
+ length-= length_bytes;
+ }
+
+ tot_length= field_charset->coll->strnxfrm(field_charset,
+ to, length,
+ char_length() *
+ field_charset->strxfrm_multiply,
+ ptr + length_bytes, tot_length,
+ MY_STRXFRM_PAD_WITH_SPACE |
+ MY_STRXFRM_PAD_TO_MAXLEN);
+ DBUG_ASSERT(tot_length == length);
+}
+
+
+enum ha_base_keytype Field_varstring::key_type() const
+{
+ enum ha_base_keytype res;
+
+ if (binary())
+ res= length_bytes == 1 ? HA_KEYTYPE_VARBINARY1 : HA_KEYTYPE_VARBINARY2;
+ else
+ res= length_bytes == 1 ? HA_KEYTYPE_VARTEXT1 : HA_KEYTYPE_VARTEXT2;
+ return res;
+}
+
+
+void Field_varstring::sql_type(String &res) const
+{
+ THD *thd= table->in_use;
+ CHARSET_INFO *cs=res.charset();
+ ulong length;
+
+ length= cs->cset->snprintf(cs,(char*) res.ptr(),
+ res.alloced_length(), "%s(%d)",
+ (has_charset() ? "varchar" : "varbinary"),
+ (int) field_length / charset()->mbmaxlen);
+ res.length(length);
+ if ((thd->variables.sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)) &&
+ has_charset() && (charset()->state & MY_CS_BINSORT))
+ res.append(STRING_WITH_LEN(" binary"));
+}
+
+
+uint32 Field_varstring::data_length()
+{
+ return length_bytes == 1 ? (uint32) *ptr : uint2korr(ptr);
+}
+
+/*
+ Functions to create a packed row.
+ Here the number of length bytes are depending on the given max_length
+*/
+
+uchar *Field_varstring::pack(uchar *to, const uchar *from, uint max_length)
+{
+ uint length= length_bytes == 1 ? (uint) *from : uint2korr(from);
+ set_if_smaller(max_length, field_length);
+ if (length > max_length)
+ length=max_length;
+
+ /* Length always stored little-endian */
+ *to++= length & 0xFF;
+ if (max_length > 255)
+ *to++= (length >> 8) & 0xFF;
+
+ /* Store bytes of string */
+ if (length > 0)
+ memcpy(to, from+length_bytes, length);
+ return to+length;
+}
+
+
+/**
+ Unpack a varstring field from row data.
+
+ This method is used to unpack a varstring field from a master
+ whose size of the field is less than that of the slave.
+
+ @note
+ The string length is always packed little-endian.
+
+ @param to Destination of the data
+ @param from Source of the data
+ @param param_data Length bytes from the master's field data
+
+ @return New pointer into memory based on from + length of the data
+*/
+const uchar *
+Field_varstring::unpack(uchar *to, const uchar *from, const uchar *from_end,
+ uint param_data)
+{
+ uint length;
+ uint l_bytes= (param_data && (param_data < field_length)) ?
+ (param_data <= 255) ? 1 : 2 : length_bytes;
+
+ if (from + l_bytes > from_end)
+ return 0; // Error in data
+
+ if (l_bytes == 1)
+ {
+ to[0]= *from++;
+ length= to[0];
+ if (length_bytes == 2)
+ to[1]= 0;
+ }
+ else /* l_bytes == 2 */
+ {
+ length= uint2korr(from);
+ to[0]= *from++;
+ to[1]= *from++;
+ }
+ if (length)
+ {
+ if (from + length > from_end || length > field_length)
+ return 0; // Error in data
+ memcpy(to+ length_bytes, from, length);
+ }
+ return from+length;
+}
+
+
+uint Field_varstring::packed_col_length(const uchar *data_ptr, uint length)
+{
+ if (length > 255)
+ return uint2korr(data_ptr)+2;
+ return (uint) *data_ptr + 1;
+}
+
+
+uint Field_varstring::max_packed_col_length(uint max_length)
+{
+ return (max_length > 255 ? 2 : 1)+max_length;
+}
+
+uint Field_varstring::get_key_image(uchar *buff, uint length, imagetype type)
+{
+ uint f_length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+ uint local_char_length= length / field_charset->mbmaxlen;
+ uchar *pos= ptr+length_bytes;
+ local_char_length= my_charpos(field_charset, pos, pos + f_length,
+ local_char_length);
+ set_if_smaller(f_length, local_char_length);
+ /* Key is always stored with 2 bytes */
+ int2store(buff,f_length);
+ memcpy(buff+HA_KEY_BLOB_LENGTH, pos, f_length);
+ if (f_length < length)
+ {
+ /*
+ Must clear this as we do a memcmp in opt_range.cc to detect
+ identical keys
+ */
+ bzero(buff+HA_KEY_BLOB_LENGTH+f_length, (length-f_length));
+ }
+ return HA_KEY_BLOB_LENGTH+f_length;
+}
+
+
+void Field_varstring::set_key_image(const uchar *buff,uint length)
+{
+ length= uint2korr(buff); // Real length is here
+ (void) Field_varstring::store((const char*) buff+HA_KEY_BLOB_LENGTH, length,
+ field_charset);
+}
+
+
+int Field_varstring::cmp_binary(const uchar *a_ptr, const uchar *b_ptr,
+ uint32 max_length)
+{
+ uint32 a_length,b_length;
+
+ if (length_bytes == 1)
+ {
+ a_length= (uint) *a_ptr;
+ b_length= (uint) *b_ptr;
+ }
+ else
+ {
+ a_length= uint2korr(a_ptr);
+ b_length= uint2korr(b_ptr);
+ }
+ set_if_smaller(a_length, max_length);
+ set_if_smaller(b_length, max_length);
+ if (a_length != b_length)
+ return 1;
+ return memcmp(a_ptr+length_bytes, b_ptr+length_bytes, a_length);
+}
+
+
+Field *Field_varstring::new_field(MEM_ROOT *root, TABLE *new_table,
+ bool keep_type)
+{
+ Field_varstring *res= (Field_varstring*) Field::new_field(root, new_table,
+ keep_type);
+ if (res)
+ res->length_bytes= length_bytes;
+ return res;
+}
+
+
+Field *Field_varstring::new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit)
+{
+ Field_varstring *res;
+ if ((res= (Field_varstring*) Field::new_key_field(root, new_table,
+ new_ptr, length,
+ new_null_ptr, new_null_bit)))
+ {
+ /* Keys length prefixes are always packed with 2 bytes */
+ res->length_bytes= 2;
+ }
+ return res;
+}
+
+uint Field_varstring::is_equal(Create_field *new_field)
+{
+ if (new_field->sql_type == real_type() &&
+ new_field->charset == field_charset)
+ {
+ if (new_field->length == max_display_length())
+ return IS_EQUAL_YES;
+ if (new_field->length > max_display_length() &&
+ ((new_field->length <= 255 && max_display_length() <= 255) ||
+ (new_field->length > 255 && max_display_length() > 255)))
+ return IS_EQUAL_PACK_LENGTH; // VARCHAR, longer variable length
+ }
+ return IS_EQUAL_NO;
+}
+
+
+void Field_varstring::hash(ulong *nr, ulong *nr2)
+{
+ if (is_null())
+ {
+ *nr^= (*nr << 1) | 1;
+ }
+ else
+ {
+ uint len= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr);
+ CHARSET_INFO *cs= charset();
+ cs->coll->hash_sort(cs, ptr + length_bytes, len, nr, nr2);
+ }
+}
+
+
+/****************************************************************************
+** blob type
+** A blob is saved as a length and a pointer. The length is stored in the
+** packlength slot and may be from 1-4.
+****************************************************************************/
+
+Field_blob::Field_blob(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ TABLE_SHARE *share, uint blob_pack_length,
+ CHARSET_INFO *cs)
+ :Field_longstr(ptr_arg, BLOB_PACK_LENGTH_TO_MAX_LENGH(blob_pack_length),
+ null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg,
+ cs),
+ packlength(blob_pack_length)
+{
+ DBUG_ASSERT(blob_pack_length <= 4); // Only pack lengths 1-4 supported currently
+ flags|= BLOB_FLAG;
+ share->blob_fields++;
+ /* TODO: why do not fill table->s->blob_field array here? */
+}
+
+
+void Field_blob::store_length(uchar *i_ptr, uint i_packlength, uint32 i_number)
+{
+ store_lowendian(i_number, i_ptr, i_packlength);
+}
+
+
+uint32 Field_blob::get_length(const uchar *pos, uint packlength_arg)
+{
+ return (uint32)read_lowendian(pos, packlength_arg);
+}
+
+
+int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ uint copy_length, new_length;
+ const char *well_formed_error_pos;
+ const char *cannot_convert_error_pos;
+ const char *from_end_pos, *tmp;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmpstr(buff,sizeof(buff), &my_charset_bin);
+
+ if (!length)
+ {
+ bzero(ptr,Field_blob::pack_length());
+ return 0;
+ }
+
+ /*
+ If the 'from' address is in the range of the temporary 'value'-
+ object we need to copy the content to a different location or it will be
+ invalidated when the 'value'-object is reallocated to make room for
+ the new character set.
+ */
+ if (from >= value.ptr() && from <= value.ptr()+value.length())
+ {
+ /*
+ If content of the 'from'-address is cached in the 'value'-object
+ it is possible that the content needs a character conversion.
+ */
+ if (!String::needs_conversion_on_storage(length, cs, field_charset))
+ {
+ Field_blob::store_length(length);
+ bmove(ptr + packlength, &from, sizeof(char*));
+ return 0;
+ }
+ if (tmpstr.copy(from, length, cs))
+ goto oom_error;
+ from= tmpstr.ptr();
+ }
+
+ new_length= MY_MIN(max_data_length(), field_charset->mbmaxlen * length);
+ if (value.alloc(new_length))
+ goto oom_error;
+
+
+ if (f_is_hex_escape(flags))
+ {
+ copy_length= my_copy_with_hex_escaping(field_charset,
+ (char*) value.ptr(), new_length,
+ from, length);
+ Field_blob::store_length(copy_length);
+ tmp= value.ptr();
+ bmove(ptr + packlength, (uchar*) &tmp, sizeof(char*));
+ return 0;
+ }
+ /*
+ "length" is OK as "nchars" argument to well_formed_copy_nchars as this
+ is never used to limit the length of the data. The cut of long data
+ is done with the new_length value.
+ */
+ copy_length= well_formed_copy_nchars(field_charset,
+ (char*) value.ptr(), new_length,
+ cs, from, length,
+ length,
+ &well_formed_error_pos,
+ &cannot_convert_error_pos,
+ &from_end_pos);
+
+ Field_blob::store_length(copy_length);
+ tmp= value.ptr();
+ bmove(ptr+packlength,(uchar*) &tmp,sizeof(char*));
+
+ if (check_string_copy_error(this, well_formed_error_pos,
+ cannot_convert_error_pos, from + length, cs))
+ return 2;
+
+ return report_if_important_data(from_end_pos, from + length, TRUE);
+
+oom_error:
+ /* Fatal OOM error */
+ bzero(ptr,Field_blob::pack_length());
+ return -1;
+}
+
+
+int Field_blob::store(double nr)
+{
+ CHARSET_INFO *cs=charset();
+ value.set_real(nr, NOT_FIXED_DEC, cs);
+ return Field_blob::store(value.ptr(),(uint) value.length(), cs);
+}
+
+
+int Field_blob::store(longlong nr, bool unsigned_val)
+{
+ CHARSET_INFO *cs=charset();
+ value.set_int(nr, unsigned_val, cs);
+ return Field_blob::store(value.ptr(), (uint) value.length(), cs);
+}
+
+
+double Field_blob::val_real(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int not_used;
+ char *end_not_used, *blob;
+ uint32 length;
+ CHARSET_INFO *cs;
+
+ memcpy(&blob, ptr+packlength, sizeof(char*));
+ if (!blob)
+ return 0.0;
+ length= get_length(ptr);
+ cs= charset();
+ return my_strntod(cs, blob, length, &end_not_used, &not_used);
+}
+
+
+longlong Field_blob::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int not_used;
+ char *blob;
+ memcpy(&blob, ptr+packlength, sizeof(char*));
+ if (!blob)
+ return 0;
+ uint32 length=get_length(ptr);
+ return my_strntoll(charset(),blob,length,10,NULL,&not_used);
+}
+
+String *Field_blob::val_str(String *val_buffer __attribute__((unused)),
+ String *val_ptr)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ char *blob;
+ memcpy(&blob, ptr+packlength, sizeof(char*));
+ if (!blob)
+ val_ptr->set("",0,charset()); // A bit safer than ->length(0)
+ else
+ val_ptr->set((const char*) blob,get_length(ptr),charset());
+ return val_ptr;
+}
+
+
+my_decimal *Field_blob::val_decimal(my_decimal *decimal_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ const char *blob;
+ size_t length;
+ memcpy(&blob, ptr+packlength, sizeof(const uchar*));
+ if (!blob)
+ {
+ blob= "";
+ length= 0;
+ }
+ else
+ length= get_length(ptr);
+
+ str2my_decimal(E_DEC_FATAL_ERROR, blob, length, charset(),
+ decimal_value);
+ return decimal_value;
+}
+
+
+int Field_blob::cmp(const uchar *a,uint32 a_length, const uchar *b,
+ uint32 b_length)
+{
+ return field_charset->coll->strnncollsp(field_charset,
+ a, a_length, b, b_length,
+ 0);
+}
+
+
+int Field_blob::cmp_max(const uchar *a_ptr, const uchar *b_ptr,
+ uint max_length)
+{
+ uchar *blob1,*blob2;
+ memcpy(&blob1, a_ptr+packlength, sizeof(char*));
+ memcpy(&blob2, b_ptr+packlength, sizeof(char*));
+ uint a_len= get_length(a_ptr), b_len= get_length(b_ptr);
+ set_if_smaller(a_len, max_length);
+ set_if_smaller(b_len, max_length);
+ return Field_blob::cmp(blob1,a_len,blob2,b_len);
+}
+
+
+int Field_blob::cmp_binary(const uchar *a_ptr, const uchar *b_ptr,
+ uint32 max_length)
+{
+ char *a,*b;
+ uint diff;
+ uint32 a_length,b_length;
+ memcpy(&a, a_ptr+packlength, sizeof(char*));
+ memcpy(&b, b_ptr+packlength, sizeof(char*));
+ a_length=get_length(a_ptr);
+ if (a_length > max_length)
+ a_length=max_length;
+ b_length=get_length(b_ptr);
+ if (b_length > max_length)
+ b_length=max_length;
+ diff=memcmp(a,b,MY_MIN(a_length,b_length));
+ return diff ? diff : (int) (a_length - b_length);
+}
+
+
+/* The following is used only when comparing a key */
+
+uint Field_blob::get_key_image(uchar *buff,uint length, imagetype type_arg)
+{
+ uint32 blob_length= get_length(ptr);
+ uchar *blob;
+
+#ifdef HAVE_SPATIAL
+ if (type_arg == itMBR)
+ {
+ const char *dummy;
+ MBR mbr;
+ Geometry_buffer buffer;
+ Geometry *gobj;
+ const uint image_length= SIZEOF_STORED_DOUBLE*4;
+
+ if (blob_length < SRID_SIZE)
+ {
+ bzero(buff, image_length);
+ return image_length;
+ }
+ get_ptr(&blob);
+ gobj= Geometry::construct(&buffer, (char*) blob, blob_length);
+ if (!gobj || gobj->get_mbr(&mbr, &dummy))
+ bzero(buff, image_length);
+ else
+ {
+ float8store(buff, mbr.xmin);
+ float8store(buff+8, mbr.xmax);
+ float8store(buff+16, mbr.ymin);
+ float8store(buff+24, mbr.ymax);
+ }
+ return image_length;
+ }
+#endif /*HAVE_SPATIAL*/
+
+ get_ptr(&blob);
+ uint local_char_length= length / field_charset->mbmaxlen;
+ local_char_length= my_charpos(field_charset, blob, blob + blob_length,
+ local_char_length);
+ set_if_smaller(blob_length, local_char_length);
+
+ if ((uint32) length > blob_length)
+ {
+ /*
+ Must clear this as we do a memcmp in opt_range.cc to detect
+ identical keys
+ */
+ bzero(buff+HA_KEY_BLOB_LENGTH+blob_length, (length-blob_length));
+ length=(uint) blob_length;
+ }
+ int2store(buff,length);
+ memcpy(buff+HA_KEY_BLOB_LENGTH, blob, length);
+ return HA_KEY_BLOB_LENGTH+length;
+}
+
+
+void Field_blob::set_key_image(const uchar *buff,uint length)
+{
+ length= uint2korr(buff);
+ (void) Field_blob::store((const char*) buff+HA_KEY_BLOB_LENGTH, length,
+ field_charset);
+}
+
+
+int Field_blob::key_cmp(const uchar *key_ptr, uint max_key_length)
+{
+ uchar *blob1;
+ uint blob_length=get_length(ptr);
+ memcpy(&blob1, ptr+packlength, sizeof(char*));
+ CHARSET_INFO *cs= charset();
+ uint local_char_length= max_key_length / cs->mbmaxlen;
+ local_char_length= my_charpos(cs, blob1, blob1+blob_length,
+ local_char_length);
+ set_if_smaller(blob_length, local_char_length);
+ return Field_blob::cmp(blob1, blob_length,
+ key_ptr+HA_KEY_BLOB_LENGTH,
+ uint2korr(key_ptr));
+}
+
+int Field_blob::key_cmp(const uchar *a,const uchar *b)
+{
+ return Field_blob::cmp(a+HA_KEY_BLOB_LENGTH, uint2korr(a),
+ b+HA_KEY_BLOB_LENGTH, uint2korr(b));
+}
+
+
+Field *Field_blob::new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit)
+{
+ Field_varstring *res= new (root) Field_varstring(new_ptr, length, 2,
+ new_null_ptr, new_null_bit, Field::NONE,
+ field_name, table->s, charset());
+ res->init(new_table);
+ return res;
+}
+
+
+/**
+ Save the field metadata for blob fields.
+
+ Saves the pack length in the first byte of the field metadata array
+ at index of *metadata_ptr.
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_blob::do_save_field_metadata(uchar *metadata_ptr)
+{
+ DBUG_ENTER("Field_blob::do_save_field_metadata");
+ *metadata_ptr= pack_length_no_ptr();
+ DBUG_PRINT("debug", ("metadata: %u (pack_length_no_ptr)", *metadata_ptr));
+ DBUG_RETURN(1);
+}
+
+
+uint32 Field_blob::sort_length() const
+{
+ return (uint32) (current_thd->variables.max_sort_length +
+ (field_charset == &my_charset_bin ? 0 : packlength));
+}
+
+
+void Field_blob::sort_string(uchar *to,uint length)
+{
+ uchar *blob;
+ uint blob_length=get_length();
+
+ if (!blob_length)
+ bzero(to,length);
+ else
+ {
+ if (field_charset == &my_charset_bin)
+ {
+ uchar *pos;
+
+ /*
+ Store length of blob last in blob to shorter blobs before longer blobs
+ */
+ length-= packlength;
+ pos= to+length;
+
+ store_bigendian(blob_length, pos, packlength);
+ }
+ memcpy(&blob, ptr+packlength, sizeof(char*));
+
+ blob_length= field_charset->coll->strnxfrm(field_charset,
+ to, length, length,
+ blob, blob_length,
+ MY_STRXFRM_PAD_WITH_SPACE |
+ MY_STRXFRM_PAD_TO_MAXLEN);
+ DBUG_ASSERT(blob_length == length);
+ }
+}
+
+
+void Field_blob::sql_type(String &res) const
+{
+ const char *str;
+ uint length;
+ switch (packlength) {
+ default: str="tiny"; length=4; break;
+ case 2: str=""; length=0; break;
+ case 3: str="medium"; length= 6; break;
+ case 4: str="long"; length=4; break;
+ }
+ res.set_ascii(str,length);
+ if (charset() == &my_charset_bin)
+ res.append(STRING_WITH_LEN("blob"));
+ else
+ {
+ res.append(STRING_WITH_LEN("text"));
+ }
+}
+
+uchar *Field_blob::pack(uchar *to, const uchar *from, uint max_length)
+{
+ uchar *save= ptr;
+ ptr= (uchar*) from;
+ uint32 length=get_length(); // Length of from string
+
+ /*
+ Store max length, which will occupy packlength bytes. If the max
+ length given is smaller than the actual length of the blob, we
+ just store the initial bytes of the blob.
+ */
+ store_length(to, packlength, MY_MIN(length, max_length));
+
+ /*
+ Store the actual blob data, which will occupy 'length' bytes.
+ */
+ if (length > 0)
+ {
+ get_ptr((uchar**) &from);
+ memcpy(to+packlength, from,length);
+ }
+ ptr=save; // Restore org row pointer
+ return to+packlength+length;
+}
+
+
+/**
+ Unpack a blob field from row data.
+
+ This method is used to unpack a blob field from a master whose size of
+ the field is less than that of the slave. Note: This method is included
+ to satisfy inheritance rules, but is not needed for blob fields. It
+ simply is used as a pass-through to the original unpack() method for
+ blob fields.
+
+ @param to Destination of the data
+ @param from Source of the data
+ @param param_data @c TRUE if base types should be stored in little-
+ endian format, @c FALSE if native format should
+ be used.
+
+ @return New pointer into memory based on from + length of the data
+*/
+const uchar *Field_blob::unpack(uchar *to, const uchar *from,
+ const uchar *from_end, uint param_data)
+{
+ DBUG_ENTER("Field_blob::unpack");
+ DBUG_PRINT("enter", ("to: 0x%lx; from: 0x%lx; param_data: %u",
+ (ulong) to, (ulong) from, param_data));
+ uint const master_packlength=
+ param_data > 0 ? param_data & 0xFF : packlength;
+ if (from + master_packlength > from_end)
+ DBUG_RETURN(0); // Error in data
+ uint32 const length= get_length(from, master_packlength);
+ DBUG_DUMP("packed", from, length + master_packlength);
+ bitmap_set_bit(table->write_set, field_index);
+ if (from + master_packlength + length > from_end)
+ DBUG_RETURN(0);
+ store(reinterpret_cast<const char*>(from) + master_packlength,
+ length, field_charset);
+ DBUG_DUMP("record", to, table->s->reclength);
+ DBUG_RETURN(from + master_packlength + length);
+}
+
+
+uint Field_blob::packed_col_length(const uchar *data_ptr, uint length)
+{
+ if (length > 255)
+ return uint2korr(data_ptr)+2;
+ return (uint) *data_ptr + 1;
+}
+
+
+uint Field_blob::max_packed_col_length(uint max_length)
+{
+ return (max_length > 255 ? 2 : 1)+max_length;
+}
+
+
+uint Field_blob::is_equal(Create_field *new_field)
+{
+ if (field_flags_are_binary() != new_field->field_flags_are_binary())
+ return 0;
+
+ return ((new_field->sql_type == get_blob_type_from_length(max_data_length()))
+ && new_field->charset == field_charset &&
+ new_field->pack_length == pack_length());
+}
+
+
+#ifdef HAVE_SPATIAL
+
+void Field_geom::sql_type(String &res) const
+{
+ CHARSET_INFO *cs= &my_charset_latin1;
+ switch (geom_type)
+ {
+ case GEOM_POINT:
+ res.set(STRING_WITH_LEN("point"), cs);
+ break;
+ case GEOM_LINESTRING:
+ res.set(STRING_WITH_LEN("linestring"), cs);
+ break;
+ case GEOM_POLYGON:
+ res.set(STRING_WITH_LEN("polygon"), cs);
+ break;
+ case GEOM_MULTIPOINT:
+ res.set(STRING_WITH_LEN("multipoint"), cs);
+ break;
+ case GEOM_MULTILINESTRING:
+ res.set(STRING_WITH_LEN("multilinestring"), cs);
+ break;
+ case GEOM_MULTIPOLYGON:
+ res.set(STRING_WITH_LEN("multipolygon"), cs);
+ break;
+ case GEOM_GEOMETRYCOLLECTION:
+ res.set(STRING_WITH_LEN("geometrycollection"), cs);
+ break;
+ default:
+ res.set(STRING_WITH_LEN("geometry"), cs);
+ }
+}
+
+
+int Field_geom::store(double nr)
+{
+ my_message(ER_CANT_CREATE_GEOMETRY_OBJECT,
+ ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0));
+ return -1;
+}
+
+
+int Field_geom::store(longlong nr, bool unsigned_val)
+{
+ my_message(ER_CANT_CREATE_GEOMETRY_OBJECT,
+ ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0));
+ return -1;
+}
+
+
+int Field_geom::store_decimal(const my_decimal *)
+{
+ my_message(ER_CANT_CREATE_GEOMETRY_OBJECT,
+ ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0));
+ return -1;
+}
+
+
+int Field_geom::store(const char *from, uint length, CHARSET_INFO *cs)
+{
+ if (!length)
+ bzero(ptr, Field_blob::pack_length());
+ else
+ {
+ if (from == Geometry::bad_geometry_data.ptr())
+ goto err;
+ // Check given WKB
+ uint32 wkb_type;
+ if (length < SRID_SIZE + WKB_HEADER_SIZE + 4)
+ goto err;
+ wkb_type= uint4korr(from + SRID_SIZE + 1);
+ if (wkb_type < (uint32) Geometry::wkb_point ||
+ wkb_type > (uint32) Geometry::wkb_last)
+ goto err;
+
+ if (geom_type != Field::GEOM_GEOMETRY &&
+ geom_type != Field::GEOM_GEOMETRYCOLLECTION &&
+ (uint32) geom_type != wkb_type)
+ {
+ my_printf_error(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
+ ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), MYF(0),
+ Geometry::ci_collection[geom_type]->m_name.str,
+ Geometry::ci_collection[wkb_type]->m_name.str, field_name,
+ (ulong) table->in_use->get_stmt_da()->current_row_for_warning());
+ goto err_exit;
+ }
+
+ Field_blob::store_length(length);
+ if ((table->copy_blobs || length <= MAX_FIELD_WIDTH) &&
+ from != value.ptr())
+ { // Must make a copy
+ value.copy(from, length, cs);
+ from= value.ptr();
+ }
+ bmove(ptr + packlength, &from, sizeof(char*));
+ }
+ return 0;
+
+err:
+ my_message(ER_CANT_CREATE_GEOMETRY_OBJECT,
+ ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0));
+err_exit:
+ bzero(ptr, Field_blob::pack_length());
+ return -1;
+}
+
+#endif /*HAVE_SPATIAL*/
+
+/****************************************************************************
+** enum type.
+** This is a string which only can have a selection of different values.
+** If one uses this string in a number context one gets the type number.
+****************************************************************************/
+
+enum ha_base_keytype Field_enum::key_type() const
+{
+ switch (packlength) {
+ default: return HA_KEYTYPE_BINARY;
+ case 2: return HA_KEYTYPE_USHORT_INT;
+ case 3: return HA_KEYTYPE_UINT24;
+ case 4: return HA_KEYTYPE_ULONG_INT;
+ case 8: return HA_KEYTYPE_ULONGLONG;
+ }
+}
+
+void Field_enum::store_type(ulonglong value)
+{
+ store_lowendian(value, ptr, packlength);
+}
+
+
+/**
+ @note
+ Storing a empty string in a enum field gives a warning
+ (if there isn't a empty value in the enum)
+*/
+
+int Field_enum::store(const char *from,uint length,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int err= 0;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmpstr(buff,sizeof(buff), &my_charset_bin);
+
+ /* Convert character set if necessary */
+ if (String::needs_conversion_on_storage(length, cs, field_charset))
+ {
+ uint dummy_errors;
+ tmpstr.copy(from, length, cs, field_charset, &dummy_errors);
+ from= tmpstr.ptr();
+ length= tmpstr.length();
+ }
+
+ /* Remove end space */
+ length= field_charset->cset->lengthsp(field_charset, from, length);
+ uint tmp=find_type2(typelib, from, length, field_charset);
+ if (!tmp)
+ {
+ if (length < 6) // Can't be more than 99999 enums
+ {
+ /* This is for reading numbers with LOAD DATA INFILE */
+ char *end;
+ tmp=(uint) my_strntoul(cs,from,length,10,&end,&err);
+ if (err || end != from+length || tmp > typelib->count)
+ {
+ tmp=0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ }
+ if (!get_thd()->count_cuted_fields)
+ err= 0;
+ }
+ else
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ }
+ store_type((ulonglong) tmp);
+ return err;
+}
+
+
+int Field_enum::store(double nr)
+{
+ return Field_enum::store((longlong) nr, FALSE);
+}
+
+
+int Field_enum::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ if ((ulonglong) nr > typelib->count || nr == 0)
+ {
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ if (nr != 0 || get_thd()->count_cuted_fields)
+ {
+ nr= 0;
+ error= 1;
+ }
+ }
+ store_type((ulonglong) (uint) nr);
+ return error;
+}
+
+
+double Field_enum::val_real(void)
+{
+ return (double) Field_enum::val_int();
+}
+
+
+longlong Field_enum::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ return read_lowendian(ptr, packlength);
+}
+
+
+/**
+ Save the field metadata for enum fields.
+
+ Saves the real type in the first byte and the pack length in the
+ second byte of the field metadata array at index of *metadata_ptr and
+ *(metadata_ptr + 1).
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_enum::do_save_field_metadata(uchar *metadata_ptr)
+{
+ *metadata_ptr= real_type();
+ *(metadata_ptr + 1)= pack_length();
+ return 2;
+}
+
+
+String *Field_enum::val_str(String *val_buffer __attribute__((unused)),
+ String *val_ptr)
+{
+ uint tmp=(uint) Field_enum::val_int();
+ if (!tmp || tmp > typelib->count)
+ val_ptr->set("", 0, field_charset);
+ else
+ val_ptr->set((const char*) typelib->type_names[tmp-1],
+ typelib->type_lengths[tmp-1],
+ field_charset);
+ return val_ptr;
+}
+
+int Field_enum::cmp(const uchar *a_ptr, const uchar *b_ptr)
+{
+ uchar *old= ptr;
+ ptr= (uchar*) a_ptr;
+ ulonglong a=Field_enum::val_int();
+ ptr= (uchar*) b_ptr;
+ ulonglong b=Field_enum::val_int();
+ ptr= old;
+ return (a < b) ? -1 : (a > b) ? 1 : 0;
+}
+
+void Field_enum::sort_string(uchar *to,uint length __attribute__((unused)))
+{
+ ulonglong value=Field_enum::val_int();
+ to+=packlength-1;
+ for (uint i=0 ; i < packlength ; i++)
+ {
+ *to-- = (uchar) (value & 255);
+ value>>=8;
+ }
+}
+
+
+void Field_enum::sql_type(String &res) const
+{
+ char buffer[255];
+ String enum_item(buffer, sizeof(buffer), res.charset());
+
+ res.length(0);
+ res.append(STRING_WITH_LEN("enum("));
+
+ bool flag=0;
+ uint *len= typelib->type_lengths;
+ for (const char **pos= typelib->type_names; *pos; pos++, len++)
+ {
+ uint dummy_errors;
+ if (flag)
+ res.append(',');
+ /* convert to res.charset() == utf8, then quote */
+ enum_item.copy(*pos, *len, charset(), res.charset(), &dummy_errors);
+ append_unescaped(&res, enum_item.ptr(), enum_item.length());
+ flag= 1;
+ }
+ res.append(')');
+}
+
+
+Field *Field_enum::new_field(MEM_ROOT *root, TABLE *new_table,
+ bool keep_type)
+{
+ Field_enum *res= (Field_enum*) Field::new_field(root, new_table, keep_type);
+ if (res)
+ res->typelib= copy_typelib(root, typelib);
+ return res;
+}
+
+
+/*
+ set type.
+ This is a string which can have a collection of different values.
+ Each string value is separated with a ','.
+ For example "One,two,five"
+ If one uses this string in a number context one gets the bits as a longlong
+ number.
+*/
+
+
+int Field_set::store(const char *from,uint length,CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ bool got_warning= 0;
+ int err= 0;
+ char *not_used;
+ uint not_used2;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmpstr(buff,sizeof(buff), &my_charset_bin);
+
+ /* Convert character set if necessary */
+ if (String::needs_conversion_on_storage(length, cs, field_charset))
+ {
+ uint dummy_errors;
+ tmpstr.copy(from, length, cs, field_charset, &dummy_errors);
+ from= tmpstr.ptr();
+ length= tmpstr.length();
+ }
+ ulonglong tmp= find_set(typelib, from, length, field_charset,
+ &not_used, &not_used2, &got_warning);
+ if (!tmp && length && length < 22)
+ {
+ /* This is for reading numbers with LOAD DATA INFILE */
+ char *end;
+ tmp=my_strntoull(cs,from,length,10,&end,&err);
+ if (err || end != from+length ||
+ tmp > (ulonglong) (((longlong) 1 << typelib->count) - (longlong) 1))
+ {
+ tmp=0;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ }
+ }
+ else if (got_warning)
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ store_type(tmp);
+ return err;
+}
+
+
+int Field_set::store(longlong nr, bool unsigned_val)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int error= 0;
+ ulonglong max_nr;
+
+ if (sizeof(ulonglong)*8 <= typelib->count)
+ max_nr= ULONGLONG_MAX;
+ else
+ max_nr= (1ULL << typelib->count) - 1;
+
+ if ((ulonglong) nr > max_nr)
+ {
+ nr&= max_nr;
+ set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ error=1;
+ }
+ store_type((ulonglong) nr);
+ return error;
+}
+
+
+String *Field_set::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ulonglong tmp=(ulonglong) Field_enum::val_int();
+ uint bitnr=0;
+
+ if (tmp == 0)
+ {
+ /*
+ Some callers expect *val_buffer to contain the result,
+ so we assign to it, rather than doing 'return &empty_set_string.
+ */
+ *val_buffer= empty_set_string;
+ return val_buffer;
+ }
+
+ val_buffer->set_charset(field_charset);
+ val_buffer->length(0);
+
+ while (tmp && bitnr < (uint) typelib->count)
+ {
+ if (tmp & 1)
+ {
+ if (val_buffer->length())
+ val_buffer->append(&field_separator, 1, &my_charset_latin1);
+ String str(typelib->type_names[bitnr],
+ typelib->type_lengths[bitnr],
+ field_charset);
+ val_buffer->append(str);
+ }
+ tmp>>=1;
+ bitnr++;
+ }
+ return val_buffer;
+}
+
+
+void Field_set::sql_type(String &res) const
+{
+ char buffer[255];
+ String set_item(buffer, sizeof(buffer), res.charset());
+
+ res.length(0);
+ res.append(STRING_WITH_LEN("set("));
+
+ bool flag=0;
+ uint *len= typelib->type_lengths;
+ for (const char **pos= typelib->type_names; *pos; pos++, len++)
+ {
+ uint dummy_errors;
+ if (flag)
+ res.append(',');
+ /* convert to res.charset() == utf8, then quote */
+ set_item.copy(*pos, *len, charset(), res.charset(), &dummy_errors);
+ append_unescaped(&res, set_item.ptr(), set_item.length());
+ flag= 1;
+ }
+ res.append(')');
+}
+
+/**
+ @retval
+ 1 if the fields are equally defined
+ @retval
+ 0 if the fields are unequally defined
+*/
+
+bool Field::eq_def(Field *field)
+{
+ if (real_type() != field->real_type() || charset() != field->charset() ||
+ pack_length() != field->pack_length())
+ return 0;
+ return 1;
+}
+
+
+/**
+ Compare the first t1::count type names.
+
+ @return TRUE if the type names of t1 match those of t2. FALSE otherwise.
+*/
+
+static bool compare_type_names(CHARSET_INFO *charset, TYPELIB *t1, TYPELIB *t2)
+{
+ for (uint i= 0; i < t1->count; i++)
+ if (my_strnncoll(charset,
+ (const uchar*) t1->type_names[i],
+ t1->type_lengths[i],
+ (const uchar*) t2->type_names[i],
+ t2->type_lengths[i]))
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ @return
+ returns 1 if the fields are equally defined
+*/
+
+bool Field_enum::eq_def(Field *field)
+{
+ TYPELIB *values;
+
+ if (!Field::eq_def(field))
+ return FALSE;
+
+ values= ((Field_enum*) field)->typelib;
+
+ /* Definition must be strictly equal. */
+ if (typelib->count != values->count)
+ return FALSE;
+
+ return compare_type_names(field_charset, typelib, values);
+}
+
+
+/**
+ Check whether two fields can be considered 'equal' for table
+ alteration purposes. Fields are equal if they retain the same
+ pack length and if new members are added to the end of the list.
+
+ @return IS_EQUAL_YES if fields are compatible.
+ IS_EQUAL_NO otherwise.
+*/
+
+uint Field_enum::is_equal(Create_field *new_field)
+{
+ TYPELIB *values= new_field->interval;
+
+ /*
+ The fields are compatible if they have the same flags,
+ type, charset and have the same underlying length.
+ */
+ if (new_field->field_flags_are_binary() != field_flags_are_binary() ||
+ new_field->sql_type != real_type() ||
+ new_field->charset != field_charset ||
+ new_field->pack_length != pack_length())
+ return IS_EQUAL_NO;
+
+ /*
+ Changing the definition of an ENUM or SET column by adding a new
+ enumeration or set members to the end of the list of valid member
+ values only alters table metadata and not table data.
+ */
+ if (typelib->count > values->count)
+ return IS_EQUAL_NO;
+
+ /* Check whether there are modification before the end. */
+ if (! compare_type_names(field_charset, typelib, new_field->interval))
+ return IS_EQUAL_NO;
+
+ return IS_EQUAL_YES;
+}
+
+
+uchar *Field_enum::pack(uchar *to, const uchar *from, uint max_length)
+{
+ DBUG_ENTER("Field_enum::pack");
+ DBUG_PRINT("debug", ("packlength: %d", packlength));
+ DBUG_DUMP("from", from, packlength);
+ DBUG_RETURN(pack_int(to, from, packlength));
+}
+
+const uchar *Field_enum::unpack(uchar *to, const uchar *from,
+ const uchar *from_end, uint param_data)
+{
+ DBUG_ENTER("Field_enum::unpack");
+ DBUG_PRINT("debug", ("packlength: %d", packlength));
+ DBUG_DUMP("from", from, packlength);
+ DBUG_RETURN(unpack_int(to, from, from_end, packlength));
+}
+
+
+/**
+ @return
+ returns 1 if the fields are equally defined
+*/
+bool Field_num::eq_def(Field *field)
+{
+ if (!Field::eq_def(field))
+ return 0;
+ Field_num *from_num= (Field_num*) field;
+
+ if (unsigned_flag != from_num->unsigned_flag ||
+ (zerofill && !from_num->zerofill && !zero_pack()) ||
+ dec != from_num->dec)
+ return 0;
+ return 1;
+}
+
+
+/**
+ Check whether two numeric fields can be considered 'equal' for table
+ alteration purposes. Fields are equal if they are of the same type
+ and retain the same pack length.
+*/
+
+uint Field_num::is_equal(Create_field *new_field)
+{
+ return ((new_field->sql_type == real_type()) &&
+ ((new_field->flags & UNSIGNED_FLAG) ==
+ (uint) (flags & UNSIGNED_FLAG)) &&
+ ((new_field->flags & AUTO_INCREMENT_FLAG) ==
+ (uint) (flags & AUTO_INCREMENT_FLAG)) &&
+ (new_field->pack_length == pack_length()));
+}
+
+
+/*
+ Bit field.
+
+ We store the first 0 - 6 uneven bits among the null bits
+ at the start of the record. The rest bytes are stored in
+ the record itself.
+
+ For example:
+
+ CREATE TABLE t1 (a int, b bit(17), c bit(21) not null, d bit(8));
+ We would store data as follows in the record:
+
+ Byte Bit
+ 1 7 - reserve for delete
+ 6 - null bit for 'a'
+ 5 - null bit for 'b'
+ 4 - first (high) bit of 'b'
+ 3 - first (high) bit of 'c'
+ 2 - second bit of 'c'
+ 1 - third bit of 'c'
+ 0 - forth bit of 'c'
+ 2 7 - firth bit of 'c'
+ 6 - null bit for 'd'
+ 3 - 6 four bytes for 'a'
+ 7 - 8 two bytes for 'b'
+ 9 - 10 two bytes for 'c'
+ 11 one byte for 'd'
+*/
+
+Field_bit::Field_bit(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, uchar *bit_ptr_arg, uchar bit_ofs_arg,
+ enum utype unireg_check_arg, const char *field_name_arg)
+ : Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg),
+ bit_ptr(bit_ptr_arg), bit_ofs(bit_ofs_arg), bit_len(len_arg & 7),
+ bytes_in_rec(len_arg / 8)
+{
+ DBUG_ENTER("Field_bit::Field_bit");
+ DBUG_PRINT("enter", ("ptr_arg: %p, null_ptr_arg: %p, len_arg: %u, bit_len: %u, bytes_in_rec: %u",
+ ptr_arg, null_ptr_arg, len_arg, bit_len, bytes_in_rec));
+ flags|= UNSIGNED_FLAG;
+ /*
+ Ensure that Field::eq() can distinguish between two different bit fields.
+ (two bit fields that are not null, may have same ptr and null_ptr)
+ */
+ if (!null_ptr_arg)
+ null_bit= bit_ofs_arg;
+ DBUG_VOID_RETURN;
+}
+
+
+void Field_bit::hash(ulong *nr, ulong *nr2)
+{
+ if (is_null())
+ {
+ *nr^= (*nr << 1) | 1;
+ }
+ else
+ {
+ CHARSET_INFO *cs= &my_charset_bin;
+ longlong value= Field_bit::val_int();
+ uchar tmp[8];
+ mi_int8store(tmp,value);
+ cs->coll->hash_sort(cs, tmp, 8, nr, nr2);
+ }
+}
+
+
+size_t
+Field_bit::do_last_null_byte() const
+{
+ /*
+ Code elsewhere is assuming that bytes are 8 bits, so I'm using
+ that value instead of the correct one: CHAR_BIT.
+
+ REFACTOR SUGGESTION (Matz): Change to use the correct number of
+ bits. On systems with CHAR_BIT > 8 (not very common), the storage
+ will lose the extra bits.
+ */
+ DBUG_PRINT("test", ("bit_ofs: %d, bit_len: %d bit_ptr: 0x%lx",
+ bit_ofs, bit_len, (long) bit_ptr));
+ uchar *result;
+ if (bit_len == 0)
+ result= null_ptr;
+ else if (bit_ofs + bit_len > 8)
+ result= bit_ptr + 1;
+ else
+ result= bit_ptr;
+
+ if (result)
+ return (size_t) (result - table->record[0]) + 1;
+ return LAST_NULL_BYTE_UNDEF;
+}
+
+
+Field *Field_bit::new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit)
+{
+ Field_bit *res;
+ if ((res= (Field_bit*) Field::new_key_field(root, new_table, new_ptr, length,
+ new_null_ptr, new_null_bit)))
+ {
+ /* Move bits normally stored in null_pointer to new_ptr */
+ res->bit_ptr= new_ptr;
+ res->bit_ofs= 0;
+ if (bit_len)
+ res->ptr++; // Store rest of data here
+ }
+ return res;
+}
+
+
+uint Field_bit::is_equal(Create_field *new_field)
+{
+ return (new_field->sql_type == real_type() &&
+ new_field->length == max_display_length());
+}
+
+
+int Field_bit::store(const char *from, uint length, CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int delta;
+
+ for (; length && !*from; from++, length--) // skip left 0's
+ ;
+ delta= bytes_in_rec - length;
+
+ if (delta < -1 ||
+ (delta == -1 && (uchar) *from > ((1 << bit_len) - 1)) ||
+ (!bit_len && delta < 0))
+ {
+ set_rec_bits((1 << bit_len) - 1, bit_ptr, bit_ofs, bit_len);
+ memset(ptr, 0xff, bytes_in_rec);
+ if (get_thd()->really_abort_on_warning())
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1);
+ else
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ return 1;
+ }
+ /* delta is >= -1 here */
+ if (delta > 0)
+ {
+ if (bit_len)
+ clr_rec_bits(bit_ptr, bit_ofs, bit_len);
+ bzero(ptr, delta);
+ memcpy(ptr + delta, from, length);
+ }
+ else if (delta == 0)
+ {
+ if (bit_len)
+ clr_rec_bits(bit_ptr, bit_ofs, bit_len);
+ memcpy(ptr, from, length);
+ }
+ else
+ {
+ if (bit_len)
+ {
+ set_rec_bits((uchar) *from, bit_ptr, bit_ofs, bit_len);
+ from++;
+ }
+ memcpy(ptr, from, bytes_in_rec);
+ }
+ return 0;
+}
+
+
+int Field_bit::store(double nr)
+{
+ return Field_bit::store((longlong) nr, FALSE);
+}
+
+
+int Field_bit::store(longlong nr, bool unsigned_val)
+{
+ char buf[8];
+
+ mi_int8store(buf, nr);
+ return store(buf, 8, NULL);
+}
+
+
+int Field_bit::store_decimal(const my_decimal *val)
+{
+ int err= 0;
+ longlong i= convert_decimal2longlong(val, 1, &err);
+ return MY_TEST(err | store(i, TRUE));
+}
+
+
+double Field_bit::val_real(void)
+{
+ return (double) Field_bit::val_int();
+}
+
+
+longlong Field_bit::val_int(void)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ ulonglong bits= 0;
+ if (bit_len)
+ {
+ bits= get_rec_bits(bit_ptr, bit_ofs, bit_len);
+ bits<<= (bytes_in_rec * 8);
+ }
+
+ switch (bytes_in_rec) {
+ case 0: return bits;
+ case 1: return bits | (ulonglong) ptr[0];
+ case 2: return bits | mi_uint2korr(ptr);
+ case 3: return bits | mi_uint3korr(ptr);
+ case 4: return bits | mi_uint4korr(ptr);
+ case 5: return bits | mi_uint5korr(ptr);
+ case 6: return bits | mi_uint6korr(ptr);
+ case 7: return bits | mi_uint7korr(ptr);
+ default: return mi_uint8korr(ptr + bytes_in_rec - sizeof(longlong));
+ }
+}
+
+
+String *Field_bit::val_str(String *val_buffer,
+ String *val_ptr __attribute__((unused)))
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ char buff[sizeof(longlong)];
+ uint length= MY_MIN(pack_length(), sizeof(longlong));
+ ulonglong bits= val_int();
+ mi_int8store(buff,bits);
+
+ val_buffer->alloc(length);
+ memcpy((char *) val_buffer->ptr(), buff+8-length, length);
+ val_buffer->length(length);
+ val_buffer->set_charset(&my_charset_bin);
+ return val_buffer;
+}
+
+
+my_decimal *Field_bit::val_decimal(my_decimal *deciaml_value)
+{
+ ASSERT_COLUMN_MARKED_FOR_READ;
+ int2my_decimal(E_DEC_FATAL_ERROR, val_int(), 1, deciaml_value);
+ return deciaml_value;
+}
+
+
+/*
+ Compare two bit fields using pointers within the record.
+ SYNOPSIS
+ cmp_max()
+ a Pointer to field->ptr in first record
+ b Pointer to field->ptr in second record
+ max_len Maximum length used in index
+ DESCRIPTION
+ This method is used from key_rec_cmp used by merge sorts used
+ by partitioned index read and later other similar places.
+ The a and b pointer must be pointers to the field in a record
+ (not the table->record[0] necessarily)
+*/
+int Field_bit::cmp_max(const uchar *a, const uchar *b, uint max_len)
+{
+ my_ptrdiff_t a_diff= a - ptr;
+ my_ptrdiff_t b_diff= b - ptr;
+ if (bit_len)
+ {
+ int flag;
+ uchar bits_a= get_rec_bits(bit_ptr+a_diff, bit_ofs, bit_len);
+ uchar bits_b= get_rec_bits(bit_ptr+b_diff, bit_ofs, bit_len);
+ if ((flag= (int) (bits_a - bits_b)))
+ return flag;
+ }
+ if (!bytes_in_rec)
+ return 0;
+ return memcmp(a, b, bytes_in_rec);
+}
+
+
+int Field_bit::key_cmp(const uchar *str, uint length)
+{
+ if (bit_len)
+ {
+ int flag;
+ uchar bits= get_rec_bits(bit_ptr, bit_ofs, bit_len);
+ if ((flag= (int) (bits - *str)))
+ return flag;
+ str++;
+ length--;
+ }
+ return memcmp(ptr, str, length);
+}
+
+
+int Field_bit::cmp_offset(uint row_offset)
+{
+ if (bit_len)
+ {
+ int flag;
+ uchar bits_a= get_rec_bits(bit_ptr, bit_ofs, bit_len);
+ uchar bits_b= get_rec_bits(bit_ptr + row_offset, bit_ofs, bit_len);
+ if ((flag= (int) (bits_a - bits_b)))
+ return flag;
+ }
+ return memcmp(ptr, ptr + row_offset, bytes_in_rec);
+}
+
+
+uint Field_bit::get_key_image(uchar *buff, uint length, imagetype type_arg)
+{
+ if (bit_len)
+ {
+ uchar bits= get_rec_bits(bit_ptr, bit_ofs, bit_len);
+ *buff++= bits;
+ length--;
+ }
+ uint data_length = MY_MIN(length, bytes_in_rec);
+ memcpy(buff, ptr, data_length);
+ return data_length + 1;
+}
+
+
+/**
+ Save the field metadata for bit fields.
+
+ Saves the bit length in the first byte and bytes in record in the
+ second byte of the field metadata array at index of *metadata_ptr and
+ *(metadata_ptr + 1).
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns number of bytes written to metadata_ptr
+*/
+int Field_bit::do_save_field_metadata(uchar *metadata_ptr)
+{
+ DBUG_ENTER("Field_bit::do_save_field_metadata");
+ DBUG_PRINT("debug", ("bit_len: %d, bytes_in_rec: %d",
+ bit_len, bytes_in_rec));
+ /*
+ Since this class and Field_bit_as_char have different ideas of
+ what should be stored here, we compute the values of the metadata
+ explicitly using the field_length.
+ */
+ metadata_ptr[0]= field_length % 8;
+ metadata_ptr[1]= field_length / 8;
+ DBUG_RETURN(2);
+}
+
+
+/**
+ Returns the number of bytes field uses in row-based replication
+ row packed size.
+
+ This method is used in row-based replication to determine the number
+ of bytes that the field consumes in the row record format. This is
+ used to skip fields in the master that do not exist on the slave.
+
+ @param field_metadata Encoded size in field metadata
+
+ @returns The size of the field based on the field metadata.
+*/
+uint Field_bit::pack_length_from_metadata(uint field_metadata)
+{
+ uint const from_len= (field_metadata >> 8U) & 0x00ff;
+ uint const from_bit_len= field_metadata & 0x00ff;
+ uint const source_size= from_len + ((from_bit_len > 0) ? 1 : 0);
+ return (source_size);
+}
+
+
+bool
+Field_bit::compatible_field_size(uint field_metadata,
+ Relay_log_info * __attribute__((unused)),
+ uint16 mflags,
+ int *order_var)
+{
+ DBUG_ENTER("Field_bit::compatible_field_size");
+ DBUG_ASSERT((field_metadata >> 16) == 0);
+ uint from_bit_len=
+ 8 * (field_metadata >> 8) + (field_metadata & 0xff);
+ uint to_bit_len= max_display_length();
+ DBUG_PRINT("debug", ("from_bit_len: %u, to_bit_len: %u",
+ from_bit_len, to_bit_len));
+ /*
+ If the bit length exact flag is clear, we are dealing with an old
+ master, so we allow some less strict behaviour if replicating by
+ moving both bit lengths to an even multiple of 8.
+
+ We do this by computing the number of bytes to store the field
+ instead, and then compare the result.
+ */
+ if (!(mflags & Table_map_log_event::TM_BIT_LEN_EXACT_F)) {
+ from_bit_len= (from_bit_len + 7) / 8;
+ to_bit_len= (to_bit_len + 7) / 8;
+ }
+
+ *order_var= compare(from_bit_len, to_bit_len);
+ DBUG_RETURN(TRUE);
+}
+
+
+
+void Field_bit::sql_type(String &res) const
+{
+ CHARSET_INFO *cs= res.charset();
+ ulong length= cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(),
+ "bit(%d)", (int) field_length);
+ res.length((uint) length);
+}
+
+
+uchar *
+Field_bit::pack(uchar *to, const uchar *from, uint max_length)
+{
+ DBUG_ASSERT(max_length > 0);
+ uint length;
+ if (bit_len > 0)
+ {
+ /*
+ We have the following:
+
+ ptr Points into a field in record R1
+ from Points to a field in a record R2
+ bit_ptr Points to the byte (in the null bytes) that holds the
+ odd bits of R1
+ from_bitp Points to the byte that holds the odd bits of R2
+
+ We have the following:
+
+ ptr - bit_ptr = from - from_bitp
+
+ We want to isolate 'from_bitp', so this gives:
+
+ ptr - bit_ptr - from = - from_bitp
+ - ptr + bit_ptr + from = from_bitp
+ bit_ptr + from - ptr = from_bitp
+ */
+ uchar bits= get_rec_bits(bit_ptr + (from - ptr), bit_ofs, bit_len);
+ *to++= bits;
+ }
+ length= MY_MIN(bytes_in_rec, max_length - (bit_len > 0));
+ memcpy(to, from, length);
+ return to + length;
+}
+
+
+/**
+ Unpack a bit field from row data.
+
+ This method is used to unpack a bit field from a master whose size
+ of the field is less than that of the slave.
+
+ @param to Destination of the data
+ @param from Source of the data
+ @param param_data Bit length (upper) and length (lower) values
+
+ @return New pointer into memory based on from + length of the data
+*/
+const uchar *
+Field_bit::unpack(uchar *to, const uchar *from, const uchar *from_end,
+ uint param_data)
+{
+ DBUG_ENTER("Field_bit::unpack");
+ DBUG_PRINT("enter", ("to: %p, from: %p, param_data: 0x%x",
+ to, from, param_data));
+ DBUG_PRINT("debug", ("bit_ptr: %p, bit_len: %u, bit_ofs: %u",
+ bit_ptr, bit_len, bit_ofs));
+ uint const from_len= (param_data >> 8U) & 0x00ff;
+ uint const from_bit_len= param_data & 0x00ff;
+ DBUG_PRINT("debug", ("from_len: %u, from_bit_len: %u",
+ from_len, from_bit_len));
+ /*
+ If the parameter data is zero (i.e., undefined), or if the master
+ and slave have the same sizes, then use the old unpack() method.
+ */
+ if (param_data == 0 ||
+ ((from_bit_len == bit_len) && (from_len == bytes_in_rec)))
+ {
+ if (from + bytes_in_rec + MY_TEST(bit_len) > from_end)
+ return 0; // Error in data
+
+ if (bit_len > 0)
+ {
+ /*
+ set_rec_bits is a macro, don't put the post-increment in the
+ argument since that might cause strange side-effects.
+
+ For the choice of the second argument, see the explanation for
+ Field_bit::pack().
+ */
+ set_rec_bits(*from, bit_ptr + (to - ptr), bit_ofs, bit_len);
+ from++;
+ }
+ memcpy(to, from, bytes_in_rec);
+ DBUG_RETURN(from + bytes_in_rec);
+ }
+
+ /*
+ We are converting a smaller bit field to a larger one here.
+ To do that, we first need to construct a raw value for the original
+ bit value stored in the from buffer. Then that needs to be converted
+ to the larger field then sent to store() for writing to the field.
+ Lastly the odd bits need to be masked out if the bytes_in_rec > 0.
+ Otherwise stray bits can cause spurious values.
+ */
+
+ uint len= from_len + ((from_bit_len > 0) ? 1 : 0);
+ uint new_len= (field_length + 7) / 8;
+
+ if (from + len > from_end || new_len < len)
+ return 0; // Error in data
+
+ char *value= (char *)my_alloca(new_len);
+ bzero(value, new_len);
+
+ memcpy(value + (new_len - len), from, len);
+ /*
+ Mask out the unused bits in the partial byte.
+ TODO: Add code to the master to always mask these bits and remove
+ the following.
+ */
+ if ((from_bit_len > 0) && (from_len > 0))
+ value[new_len - len]= value[new_len - len] & ((1U << from_bit_len) - 1);
+ bitmap_set_bit(table->write_set,field_index);
+ store(value, new_len, system_charset_info);
+ my_afree(value);
+ DBUG_RETURN(from + len);
+}
+
+
+void Field_bit::set_default()
+{
+ if (bit_len > 0)
+ {
+ my_ptrdiff_t const offset= table->s->default_values - table->record[0];
+ uchar bits= get_rec_bits(bit_ptr + offset, bit_ofs, bit_len);
+ set_rec_bits(bits, bit_ptr, bit_ofs, bit_len);
+ }
+ Field::set_default();
+}
+
+/*
+ Bit field support for non-MyISAM tables.
+*/
+
+Field_bit_as_char::Field_bit_as_char(uchar *ptr_arg, uint32 len_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg,
+ const char *field_name_arg)
+ :Field_bit(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, 0, 0,
+ unireg_check_arg, field_name_arg)
+{
+ flags|= UNSIGNED_FLAG;
+ bit_len= 0;
+ bytes_in_rec= (len_arg + 7) / 8;
+}
+
+
+int Field_bit_as_char::store(const char *from, uint length, CHARSET_INFO *cs)
+{
+ ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED;
+ int delta;
+ uchar bits= (uchar) (field_length & 7);
+
+ for (; length && !*from; from++, length--) // skip left 0's
+ ;
+ delta= bytes_in_rec - length;
+
+ if (delta < 0 ||
+ (delta == 0 && bits && (uint) (uchar) *from >= (uint) (1 << bits)))
+ {
+ memset(ptr, 0xff, bytes_in_rec);
+ if (bits)
+ *ptr&= ((1 << bits) - 1); /* set first uchar */
+ if (get_thd()->really_abort_on_warning())
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1);
+ else
+ set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
+ return 1;
+ }
+ bzero(ptr, delta);
+ memcpy(ptr + delta, from, length);
+ return 0;
+}
+
+
+void Field_bit_as_char::sql_type(String &res) const
+{
+ CHARSET_INFO *cs= res.charset();
+ ulong length= cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(),
+ "bit(%d)", (int) field_length);
+ res.length((uint) length);
+}
+
+
+/*****************************************************************************
+ Handling of field and Create_field
+*****************************************************************************/
+
+/**
+ Convert create_field::length from number of characters to number of bytes.
+*/
+
+void Create_field::create_length_to_internal_length(void)
+{
+ switch (sql_type) {
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ length*= charset->mbmaxlen;
+ key_length= length;
+ pack_length= calc_pack_length(sql_type, length);
+ break;
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ /* Pack_length already calculated in sql_parse.cc */
+ length*= charset->mbmaxlen;
+ key_length= pack_length;
+ break;
+ case MYSQL_TYPE_BIT:
+ if (f_bit_as_char(pack_flag))
+ {
+ key_length= pack_length= ((length + 7) & ~7) / 8;
+ }
+ else
+ {
+ pack_length= length / 8;
+ /* We need one extra byte to store the bits we save among the null bits */
+ key_length= pack_length + MY_TEST(length & 7);
+ }
+ break;
+ case MYSQL_TYPE_NEWDECIMAL:
+ key_length= pack_length=
+ my_decimal_get_binary_size(my_decimal_length_to_precision(length,
+ decimals,
+ flags &
+ UNSIGNED_FLAG),
+ decimals);
+ break;
+ default:
+ key_length= pack_length= calc_pack_length(sql_type, length);
+ break;
+ }
+}
+
+
+/**
+ Init for a tmp table field. To be extended if need be.
+*/
+void Create_field::init_for_tmp_table(enum_field_types sql_type_arg,
+ uint32 length_arg, uint32 decimals_arg,
+ bool maybe_null, bool is_unsigned,
+ uint pack_length_arg)
+{
+ DBUG_ENTER("Create_field::init_for_tmp_table");
+
+ field_name= "";
+ sql_type= sql_type_arg;
+ char_length= length= length_arg;;
+ unireg_check= Field::NONE;
+ interval= 0;
+ charset= &my_charset_bin;
+ geom_type= Field::GEOM_GEOMETRY;
+
+ DBUG_PRINT("enter", ("sql_type: %d, length: %u, pack_length: %u",
+ sql_type_arg, length_arg, pack_length_arg));
+
+ /*
+ These pack flags are crafted to get it correctly through the
+ branches of make_field().
+ */
+ switch (sql_type_arg)
+ {
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_SET:
+ pack_flag= 0;
+ break;
+
+ case MYSQL_TYPE_GEOMETRY:
+ pack_flag= FIELDFLAG_GEOM;
+ break;
+
+ case MYSQL_TYPE_ENUM:
+ pack_flag= FIELDFLAG_INTERVAL;
+ break;
+
+ case MYSQL_TYPE_NEWDECIMAL:
+ DBUG_ASSERT(decimals_arg <= DECIMAL_MAX_SCALE);
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ pack_flag= FIELDFLAG_NUMBER |
+ (decimals_arg & FIELDFLAG_MAX_DEC) << FIELDFLAG_DEC_SHIFT;
+ break;
+
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ pack_flag= FIELDFLAG_BLOB;
+ break;
+
+ case MYSQL_TYPE_BIT:
+ pack_flag= FIELDFLAG_NUMBER | FIELDFLAG_TREAT_BIT_AS_CHAR;
+ break;
+
+ default:
+ pack_flag= FIELDFLAG_NUMBER;
+ break;
+ }
+
+ /*
+ Set the pack flag correctly for the blob-like types. This sets the
+ packtype to something that make_field can use. If the pack type is
+ not set correctly, the packlength will be reeeeally wierd (like
+ 129 or so).
+ */
+ switch (sql_type_arg)
+ {
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ // If you are going to use the above types, you have to pass a
+ // pack_length as parameter. Assert that is really done.
+ DBUG_ASSERT(pack_length_arg != ~0U);
+ pack_flag|= pack_length_to_packflag(pack_length_arg);
+ break;
+ default:
+ /* Nothing */
+ break;
+ }
+
+ pack_flag|=
+ (maybe_null ? FIELDFLAG_MAYBE_NULL : 0) |
+ (is_unsigned ? 0 : FIELDFLAG_DECIMAL);
+
+ DBUG_PRINT("debug", ("pack_flag: %s%s%s%s%s%s, pack_type: %d",
+ FLAGSTR(pack_flag, FIELDFLAG_BINARY),
+ FLAGSTR(pack_flag, FIELDFLAG_NUMBER),
+ FLAGSTR(pack_flag, FIELDFLAG_INTERVAL),
+ FLAGSTR(pack_flag, FIELDFLAG_GEOM),
+ FLAGSTR(pack_flag, FIELDFLAG_BLOB),
+ FLAGSTR(pack_flag, FIELDFLAG_DECIMAL),
+ f_packtype(pack_flag)));
+ vcol_info= 0;
+ create_if_not_exists= FALSE;
+ stored_in_db= TRUE;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Initialize field definition for create.
+
+ @param thd Thread handle
+ @param fld_name Field name
+ @param fld_type Field type
+ @param fld_length Field length
+ @param fld_decimals Decimal (if any)
+ @param fld_type_modifier Additional type information
+ @param fld_default_value Field default value (if any)
+ @param fld_on_update_value The value of ON UPDATE clause
+ @param fld_comment Field comment
+ @param fld_change Field change
+ @param fld_interval_list Interval list (if any)
+ @param fld_charset Field charset
+ @param fld_geom_type Field geometry type (if any)
+ @param fld_vcol_info Virtual column data
+
+ @retval
+ FALSE on success
+ @retval
+ TRUE on error
+*/
+
+bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type,
+ char *fld_length, char *fld_decimals,
+ uint fld_type_modifier, Item *fld_default_value,
+ Item *fld_on_update_value, LEX_STRING *fld_comment,
+ char *fld_change, List<String> *fld_interval_list,
+ CHARSET_INFO *fld_charset, uint fld_geom_type,
+ Virtual_column_info *fld_vcol_info,
+ engine_option_value *create_opt, bool check_exists)
+{
+ uint sign_len, allowed_type_modifier= 0;
+ ulong max_field_charlength= MAX_FIELD_CHARLENGTH;
+ const bool on_update_is_function=
+ (fld_on_update_value != NULL &&
+ fld_on_update_value->type() == Item::FUNC_ITEM);
+
+ DBUG_ENTER("Create_field::init()");
+
+ field= 0;
+ field_name= fld_name;
+ flags= fld_type_modifier;
+ option_list= create_opt;
+
+ if (fld_default_value != NULL && fld_default_value->type() == Item::FUNC_ITEM)
+ {
+ /* There is a function default for insertions. */
+ def= NULL;
+ unireg_check= (on_update_is_function ?
+ Field::TIMESTAMP_DNUN_FIELD : // for insertions and for updates.
+ Field::TIMESTAMP_DN_FIELD); // only for insertions.
+ }
+ else
+ {
+ /* No function default for insertions. Either NULL or a constant. */
+ def= fld_default_value;
+ if (on_update_is_function)
+ unireg_check= Field::TIMESTAMP_UN_FIELD; // function default for updates
+ else
+ unireg_check= ((fld_type_modifier & AUTO_INCREMENT_FLAG) != 0 ?
+ Field::NEXT_NUMBER : // Automatic increment.
+ Field::NONE);
+ }
+
+ decimals= fld_decimals ? (uint)atoi(fld_decimals) : 0;
+ if (decimals >= NOT_FIXED_DEC)
+ {
+ my_error(ER_TOO_BIG_SCALE, MYF(0), decimals, fld_name,
+ static_cast<ulong>(NOT_FIXED_DEC - 1));
+ DBUG_RETURN(TRUE);
+ }
+
+ sql_type= fld_type;
+ length= 0;
+ change= fld_change;
+ interval= 0;
+ pack_length= key_length= 0;
+ charset= fld_charset;
+ geom_type= (Field::geometry_type) fld_geom_type;
+ interval_list.empty();
+
+ comment= *fld_comment;
+ vcol_info= fld_vcol_info;
+ create_if_not_exists= check_exists;
+ stored_in_db= TRUE;
+
+ /* Initialize data for a computed field */
+ if ((uchar)fld_type == (uchar)MYSQL_TYPE_VIRTUAL)
+ {
+ DBUG_ASSERT(vcol_info && vcol_info->expr_item);
+ stored_in_db= vcol_info->is_stored();
+ /*
+ Walk through the Item tree checking if all items are valid
+ to be part of the virtual column
+ */
+ if (vcol_info->expr_item->walk(&Item::check_vcol_func_processor, 0, NULL))
+ {
+ my_error(ER_VIRTUAL_COLUMN_FUNCTION_IS_NOT_ALLOWED, MYF(0), field_name);
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Make a field created for the real type.
+ Note that regular and computed fields differ from each other only by
+ Field::vcol_info. It is is always NULL for a column that is not
+ computed.
+ */
+ sql_type= fld_type= vcol_info->get_real_type();
+ }
+
+ /*
+ Set NO_DEFAULT_VALUE_FLAG if this field doesn't have a default value and
+ it is NOT NULL, not an AUTO_INCREMENT field and not a TIMESTAMP.
+ */
+ if (!fld_default_value && !(fld_type_modifier & AUTO_INCREMENT_FLAG) &&
+ (fld_type_modifier & NOT_NULL_FLAG) && !is_timestamp_type(fld_type))
+ flags|= NO_DEFAULT_VALUE_FLAG;
+
+ if (fld_length != NULL)
+ {
+ errno= 0;
+ length= strtoul(fld_length, NULL, 10);
+ if ((errno != 0) || (length > MAX_FIELD_BLOBLENGTH))
+ {
+ my_error(ER_TOO_BIG_DISPLAYWIDTH, MYF(0), fld_name, MAX_FIELD_BLOBLENGTH);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (length == 0)
+ fld_length= NULL; /* purecov: inspected */
+ }
+
+ sign_len= fld_type_modifier & UNSIGNED_FLAG ? 0 : 1;
+
+ switch (fld_type) {
+ case MYSQL_TYPE_TINY:
+ if (!fld_length)
+ length= MAX_TINYINT_WIDTH+sign_len;
+ allowed_type_modifier= AUTO_INCREMENT_FLAG;
+ break;
+ case MYSQL_TYPE_SHORT:
+ if (!fld_length)
+ length= MAX_SMALLINT_WIDTH+sign_len;
+ allowed_type_modifier= AUTO_INCREMENT_FLAG;
+ break;
+ case MYSQL_TYPE_INT24:
+ if (!fld_length)
+ length= MAX_MEDIUMINT_WIDTH+sign_len;
+ allowed_type_modifier= AUTO_INCREMENT_FLAG;
+ break;
+ case MYSQL_TYPE_LONG:
+ if (!fld_length)
+ length= MAX_INT_WIDTH+sign_len;
+ allowed_type_modifier= AUTO_INCREMENT_FLAG;
+ break;
+ case MYSQL_TYPE_LONGLONG:
+ if (!fld_length)
+ length= MAX_BIGINT_WIDTH;
+ allowed_type_modifier= AUTO_INCREMENT_FLAG;
+ break;
+ case MYSQL_TYPE_NULL:
+ break;
+ case MYSQL_TYPE_NEWDECIMAL:
+ my_decimal_trim(&length, &decimals);
+ if (length > DECIMAL_MAX_PRECISION)
+ {
+ my_error(ER_TOO_BIG_PRECISION, MYF(0), static_cast<int>(length),
+ fld_name, static_cast<ulong>(DECIMAL_MAX_PRECISION));
+ DBUG_RETURN(TRUE);
+ }
+ if (length < decimals)
+ {
+ my_error(ER_M_BIGGER_THAN_D, MYF(0), fld_name);
+ DBUG_RETURN(TRUE);
+ }
+ length=
+ my_decimal_precision_to_length(length, decimals,
+ fld_type_modifier & UNSIGNED_FLAG);
+ pack_length=
+ my_decimal_get_binary_size(length, decimals);
+ break;
+ case MYSQL_TYPE_VARCHAR:
+ /*
+ Long VARCHAR's are automaticly converted to blobs in mysql_prepare_table
+ if they don't have a default value
+ */
+ max_field_charlength= MAX_FIELD_VARCHARLENGTH;
+ break;
+ case MYSQL_TYPE_STRING:
+ break;
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ if (fld_default_value)
+ {
+ /* Allow empty as default value. */
+ String str,*res;
+ res= fld_default_value->val_str(&str);
+ /*
+ A default other than '' is always an error, and any non-NULL
+ specified default is an error in strict mode.
+ */
+ if (res->length() || thd->is_strict_mode())
+ {
+ my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0),
+ fld_name); /* purecov: inspected */
+ DBUG_RETURN(TRUE);
+ }
+ else
+ {
+ /*
+ Otherwise a default of '' is just a warning.
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BLOB_CANT_HAVE_DEFAULT,
+ ER(ER_BLOB_CANT_HAVE_DEFAULT),
+ fld_name);
+ }
+ def= 0;
+ }
+ flags|= BLOB_FLAG;
+ break;
+ case MYSQL_TYPE_YEAR:
+ if (!fld_length || length != 2)
+ length= 4; /* Default length */
+ flags|= ZEROFILL_FLAG | UNSIGNED_FLAG;
+ break;
+ case MYSQL_TYPE_FLOAT:
+ /* change FLOAT(precision) to FLOAT or DOUBLE */
+ allowed_type_modifier= AUTO_INCREMENT_FLAG;
+ if (fld_length && !fld_decimals)
+ {
+ uint tmp_length= length;
+ if (tmp_length > PRECISION_FOR_DOUBLE)
+ {
+ my_error(ER_WRONG_FIELD_SPEC, MYF(0), fld_name);
+ DBUG_RETURN(TRUE);
+ }
+ else if (tmp_length > PRECISION_FOR_FLOAT)
+ {
+ sql_type= MYSQL_TYPE_DOUBLE;
+ length= MAX_DOUBLE_STR_LENGTH;
+ }
+ else
+ length= MAX_FLOAT_STR_LENGTH;
+ decimals= NOT_FIXED_DEC;
+ break;
+ }
+ if (!fld_length && !fld_decimals)
+ {
+ length= MAX_FLOAT_STR_LENGTH;
+ decimals= NOT_FIXED_DEC;
+ }
+ if (length < decimals &&
+ decimals != NOT_FIXED_DEC)
+ {
+ my_error(ER_M_BIGGER_THAN_D, MYF(0), fld_name);
+ DBUG_RETURN(TRUE);
+ }
+ break;
+ case MYSQL_TYPE_DOUBLE:
+ allowed_type_modifier= AUTO_INCREMENT_FLAG;
+ if (!fld_length && !fld_decimals)
+ {
+ length= DBL_DIG+7;
+ decimals= NOT_FIXED_DEC;
+ }
+ if (length < decimals &&
+ decimals != NOT_FIXED_DEC)
+ {
+ my_error(ER_M_BIGGER_THAN_D, MYF(0), fld_name);
+ DBUG_RETURN(TRUE);
+ }
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_TIMESTAMP2:
+ if (length > MAX_DATETIME_PRECISION)
+ {
+ my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name,
+ MAX_DATETIME_PRECISION);
+ DBUG_RETURN(TRUE);
+ }
+ length+= MAX_DATETIME_WIDTH + (length ? 1 : 0);
+ flags|= UNSIGNED_FLAG;
+ break;
+ case MYSQL_TYPE_DATE:
+ /* We don't support creation of MYSQL_TYPE_DATE anymore */
+ sql_type= MYSQL_TYPE_NEWDATE;
+ /* fall trough */
+ case MYSQL_TYPE_NEWDATE:
+ length= MAX_DATE_WIDTH;
+ break;
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_TIME2:
+ if (length > MAX_DATETIME_PRECISION)
+ {
+ my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name,
+ MAX_DATETIME_PRECISION);
+ DBUG_RETURN(TRUE);
+ }
+ length+= MIN_TIME_WIDTH + (length ? 1 : 0);
+ break;
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_DATETIME2:
+ if (length > MAX_DATETIME_PRECISION)
+ {
+ my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name,
+ MAX_DATETIME_PRECISION);
+ DBUG_RETURN(TRUE);
+ }
+ length+= MAX_DATETIME_WIDTH + (length ? 1 : 0);
+ break;
+ case MYSQL_TYPE_SET:
+ {
+ pack_length= get_set_pack_length(fld_interval_list->elements);
+
+ List_iterator<String> it(*fld_interval_list);
+ String *tmp;
+ while ((tmp= it++))
+ interval_list.push_back(tmp);
+ /*
+ Set fake length to 1 to pass the below conditions.
+ Real length will be set in mysql_prepare_table()
+ when we know the character set of the column
+ */
+ length= 1;
+ break;
+ }
+ case MYSQL_TYPE_ENUM:
+ {
+ /* Should be safe. */
+ pack_length= get_enum_pack_length(fld_interval_list->elements);
+
+ List_iterator<String> it(*fld_interval_list);
+ String *tmp;
+ while ((tmp= it++))
+ interval_list.push_back(tmp);
+ length= 1; /* See comment for MYSQL_TYPE_SET above. */
+ break;
+ }
+ case MYSQL_TYPE_VAR_STRING:
+ DBUG_ASSERT(0); /* Impossible. */
+ break;
+ case MYSQL_TYPE_BIT:
+ {
+ if (!fld_length)
+ length= 1;
+ if (length > MAX_BIT_FIELD_LENGTH)
+ {
+ my_error(ER_TOO_BIG_DISPLAYWIDTH, MYF(0), fld_name,
+ static_cast<ulong>(MAX_BIT_FIELD_LENGTH));
+ DBUG_RETURN(TRUE);
+ }
+ pack_length= (length + 7) / 8;
+ break;
+ }
+ case MYSQL_TYPE_DECIMAL:
+ DBUG_ASSERT(0); /* Was obsolete */
+ }
+ /* Remember the value of length */
+ char_length= length;
+
+ if (!(flags & BLOB_FLAG) &&
+ ((length > max_field_charlength && fld_type != MYSQL_TYPE_SET &&
+ fld_type != MYSQL_TYPE_ENUM &&
+ (fld_type != MYSQL_TYPE_VARCHAR || fld_default_value)) ||
+ ((length == 0) &&
+ fld_type != MYSQL_TYPE_STRING &&
+ fld_type != MYSQL_TYPE_VARCHAR && fld_type != MYSQL_TYPE_GEOMETRY)))
+ {
+ my_error((fld_type == MYSQL_TYPE_VAR_STRING ||
+ fld_type == MYSQL_TYPE_VARCHAR ||
+ fld_type == MYSQL_TYPE_STRING) ? ER_TOO_BIG_FIELDLENGTH :
+ ER_TOO_BIG_DISPLAYWIDTH,
+ MYF(0),
+ fld_name, max_field_charlength); /* purecov: inspected */
+ DBUG_RETURN(TRUE);
+ }
+ fld_type_modifier&= AUTO_INCREMENT_FLAG;
+ if ((~allowed_type_modifier) & fld_type_modifier)
+ {
+ my_error(ER_WRONG_FIELD_SPEC, MYF(0), fld_name);
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE); /* success */
+}
+
+
+enum_field_types get_blob_type_from_length(ulong length)
+{
+ enum_field_types type;
+ if (length < 256)
+ type= MYSQL_TYPE_TINY_BLOB;
+ else if (length < 65536)
+ type= MYSQL_TYPE_BLOB;
+ else if (length < 256L*256L*256L)
+ type= MYSQL_TYPE_MEDIUM_BLOB;
+ else
+ type= MYSQL_TYPE_LONG_BLOB;
+ return type;
+}
+
+
+/*
+ Make a field from the .frm file info
+*/
+
+uint32 calc_pack_length(enum_field_types type,uint32 length)
+{
+ switch (type) {
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_DECIMAL: return (length);
+ case MYSQL_TYPE_VARCHAR: return (length + (length < 256 ? 1: 2));
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_TINY : return 1;
+ case MYSQL_TYPE_SHORT : return 2;
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_NEWDATE: return 3;
+ case MYSQL_TYPE_TIME: return length > MIN_TIME_WIDTH
+ ? time_hires_bytes[length - 1 - MIN_TIME_WIDTH]
+ : 3;
+ case MYSQL_TYPE_TIME2:
+ return length > MIN_TIME_WIDTH ?
+ my_time_binary_length(length - MIN_TIME_WIDTH - 1) : 3;
+ case MYSQL_TYPE_TIMESTAMP:
+ return length > MAX_DATETIME_WIDTH
+ ? 4 + sec_part_bytes[length - 1 - MAX_DATETIME_WIDTH]
+ : 4;
+ case MYSQL_TYPE_TIMESTAMP2:
+ return length > MAX_DATETIME_WIDTH ?
+ my_timestamp_binary_length(length - MAX_DATETIME_WIDTH - 1) : 4;
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_LONG : return 4;
+ case MYSQL_TYPE_FLOAT : return sizeof(float);
+ case MYSQL_TYPE_DOUBLE: return sizeof(double);
+ case MYSQL_TYPE_DATETIME:
+ return length > MAX_DATETIME_WIDTH
+ ? datetime_hires_bytes[length - 1 - MAX_DATETIME_WIDTH]
+ : 8;
+ case MYSQL_TYPE_DATETIME2:
+ return length > MAX_DATETIME_WIDTH ?
+ my_datetime_binary_length(length - MAX_DATETIME_WIDTH - 1) : 5;
+ case MYSQL_TYPE_LONGLONG: return 8; /* Don't crash if no longlong */
+ case MYSQL_TYPE_NULL : return 0;
+ case MYSQL_TYPE_TINY_BLOB: return 1+portable_sizeof_char_ptr;
+ case MYSQL_TYPE_BLOB: return 2+portable_sizeof_char_ptr;
+ case MYSQL_TYPE_MEDIUM_BLOB: return 3+portable_sizeof_char_ptr;
+ case MYSQL_TYPE_LONG_BLOB: return 4+portable_sizeof_char_ptr;
+ case MYSQL_TYPE_GEOMETRY: return 4+portable_sizeof_char_ptr;
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_NEWDECIMAL:
+ abort(); return 0; // This shouldn't happen
+ case MYSQL_TYPE_BIT: return length / 8;
+ default:
+ return 0;
+ }
+}
+
+
+uint pack_length_to_packflag(uint type)
+{
+ switch (type) {
+ case 1: return f_settype((uint) MYSQL_TYPE_TINY);
+ case 2: return f_settype((uint) MYSQL_TYPE_SHORT);
+ case 3: return f_settype((uint) MYSQL_TYPE_INT24);
+ case 4: return f_settype((uint) MYSQL_TYPE_LONG);
+ case 8: return f_settype((uint) MYSQL_TYPE_LONGLONG);
+ }
+ return 0; // This shouldn't happen
+}
+
+
+Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length,
+ uchar *null_pos, uchar null_bit,
+ uint pack_flag,
+ enum_field_types field_type,
+ CHARSET_INFO *field_charset,
+ Field::geometry_type geom_type,
+ Field::utype unireg_check,
+ TYPELIB *interval,
+ const char *field_name)
+{
+ uchar *UNINIT_VAR(bit_ptr);
+ uchar UNINIT_VAR(bit_offset);
+ if (field_type == MYSQL_TYPE_BIT && !f_bit_as_char(pack_flag))
+ {
+ bit_ptr= null_pos;
+ bit_offset= null_bit;
+ if (f_maybe_null(pack_flag)) // if null field
+ {
+ bit_ptr+= (null_bit == 7); // shift bit_ptr and bit_offset
+ bit_offset= (bit_offset + 1) & 7;
+ }
+ }
+
+ if (!f_maybe_null(pack_flag))
+ {
+ null_pos=0;
+ null_bit=0;
+ }
+ else
+ {
+ null_bit= ((uchar) 1) << null_bit;
+ }
+
+ DBUG_PRINT("debug", ("field_type: %d, field_length: %u, interval: %p, pack_flag: %s%s%s%s%s",
+ field_type, field_length, interval,
+ FLAGSTR(pack_flag, FIELDFLAG_BINARY),
+ FLAGSTR(pack_flag, FIELDFLAG_INTERVAL),
+ FLAGSTR(pack_flag, FIELDFLAG_NUMBER),
+ FLAGSTR(pack_flag, FIELDFLAG_PACK),
+ FLAGSTR(pack_flag, FIELDFLAG_BLOB)));
+
+ if (f_is_alpha(pack_flag))
+ {
+ if (!f_is_packed(pack_flag))
+ {
+ if (field_type == MYSQL_TYPE_STRING ||
+ field_type == MYSQL_TYPE_DECIMAL || // 3.23 or 4.0 string
+ field_type == MYSQL_TYPE_VAR_STRING)
+ return new Field_string(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ field_charset);
+ if (field_type == MYSQL_TYPE_VARCHAR)
+ return new Field_varstring(ptr,field_length,
+ HA_VARCHAR_PACKLENGTH(field_length),
+ null_pos,null_bit,
+ unireg_check, field_name,
+ share,
+ field_charset);
+ return 0; // Error
+ }
+
+ uint pack_length=calc_pack_length((enum_field_types)
+ f_packtype(pack_flag),
+ field_length);
+
+#ifdef HAVE_SPATIAL
+ if (f_is_geom(pack_flag))
+ {
+ status_var_increment(current_thd->status_var.feature_gis);
+ return new Field_geom(ptr,null_pos,null_bit,
+ unireg_check, field_name, share,
+ pack_length, geom_type);
+ }
+#endif
+ if (f_is_blob(pack_flag))
+ return new Field_blob(ptr,null_pos,null_bit,
+ unireg_check, field_name, share,
+ pack_length, field_charset);
+ if (interval)
+ {
+ if (f_is_enum(pack_flag))
+ return new Field_enum(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ pack_length, interval, field_charset);
+ else
+ return new Field_set(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ pack_length, interval, field_charset);
+ }
+ }
+
+ switch (field_type) {
+ case MYSQL_TYPE_DECIMAL:
+ return new Field_decimal(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_decimals(pack_flag),
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag) == 0);
+ case MYSQL_TYPE_NEWDECIMAL:
+ return new Field_new_decimal(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_decimals(pack_flag),
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag) == 0);
+ case MYSQL_TYPE_FLOAT:
+ return new Field_float(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_decimals(pack_flag),
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag)== 0);
+ case MYSQL_TYPE_DOUBLE:
+ return new Field_double(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_decimals(pack_flag),
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag)== 0);
+ case MYSQL_TYPE_TINY:
+ return new Field_tiny(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag) == 0);
+ case MYSQL_TYPE_SHORT:
+ return new Field_short(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag) == 0);
+ case MYSQL_TYPE_INT24:
+ return new Field_medium(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag) == 0);
+ case MYSQL_TYPE_LONG:
+ return new Field_long(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag) == 0);
+ case MYSQL_TYPE_LONGLONG:
+ return new Field_longlong(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name,
+ f_is_zerofill(pack_flag) != 0,
+ f_is_dec(pack_flag) == 0);
+ case MYSQL_TYPE_TIMESTAMP:
+ {
+ uint dec= field_length > MAX_DATETIME_WIDTH ?
+ field_length - MAX_DATETIME_WIDTH - 1: 0;
+ return new_Field_timestamp(ptr, null_pos, null_bit, unireg_check,
+ field_name, share, dec);
+ }
+ case MYSQL_TYPE_TIMESTAMP2:
+ {
+ uint dec= field_length > MAX_DATETIME_WIDTH ?
+ field_length - MAX_DATETIME_WIDTH - 1: 0;
+ return new Field_timestampf(ptr, null_pos, null_bit, unireg_check,
+ field_name, share, dec);
+ }
+ case MYSQL_TYPE_YEAR:
+ return new Field_year(ptr,field_length,null_pos,null_bit,
+ unireg_check, field_name);
+ case MYSQL_TYPE_DATE:
+ return new Field_date(ptr,null_pos,null_bit,
+ unireg_check, field_name);
+ case MYSQL_TYPE_NEWDATE:
+ return new Field_newdate(ptr,null_pos,null_bit,
+ unireg_check, field_name);
+ case MYSQL_TYPE_TIME:
+ {
+ uint dec= field_length > MIN_TIME_WIDTH ?
+ field_length - MIN_TIME_WIDTH - 1: 0;
+ return new_Field_time(ptr, null_pos, null_bit, unireg_check,
+ field_name, dec);
+ }
+ case MYSQL_TYPE_TIME2:
+ {
+ uint dec= field_length > MIN_TIME_WIDTH ?
+ field_length - MIN_TIME_WIDTH - 1: 0;
+ return new Field_timef(ptr, null_pos, null_bit, unireg_check,
+ field_name, dec);
+ }
+ case MYSQL_TYPE_DATETIME:
+ {
+ uint dec= field_length > MAX_DATETIME_WIDTH ?
+ field_length - MAX_DATETIME_WIDTH - 1: 0;
+ return new_Field_datetime(ptr, null_pos, null_bit, unireg_check,
+ field_name, dec);
+ }
+ case MYSQL_TYPE_DATETIME2:
+ {
+ uint dec= field_length > MAX_DATETIME_WIDTH ?
+ field_length - MAX_DATETIME_WIDTH - 1: 0;
+ return new Field_datetimef(ptr, null_pos, null_bit, unireg_check,
+ field_name, dec);
+ }
+ case MYSQL_TYPE_NULL:
+ return new Field_null(ptr, field_length, unireg_check, field_name,
+ field_charset);
+ case MYSQL_TYPE_BIT:
+ return f_bit_as_char(pack_flag) ?
+ new Field_bit_as_char(ptr, field_length, null_pos, null_bit,
+ unireg_check, field_name) :
+ new Field_bit(ptr, field_length, null_pos, null_bit, bit_ptr,
+ bit_offset, unireg_check, field_name);
+
+ default: // Impossible (Wrong version)
+ break;
+ }
+ return 0;
+}
+
+
+/** Create a field suitable for create of table. */
+
+Create_field::Create_field(Field *old_field,Field *orig_field)
+{
+ field= old_field;
+ field_name=change=old_field->field_name;
+ length= old_field->field_length;
+ flags= old_field->flags;
+ unireg_check=old_field->unireg_check;
+ pack_length=old_field->pack_length();
+ key_length= old_field->key_length();
+ sql_type= old_field->real_type();
+ charset= old_field->charset(); // May be NULL ptr
+ comment= old_field->comment;
+ decimals= old_field->decimals();
+ vcol_info= old_field->vcol_info;
+ create_if_not_exists= FALSE;
+ stored_in_db= old_field->stored_in_db;
+ option_list= old_field->option_list;
+ option_struct= old_field->option_struct;
+
+ switch (sql_type) {
+ case MYSQL_TYPE_BLOB:
+ switch (pack_length - portable_sizeof_char_ptr) {
+ case 1: sql_type= MYSQL_TYPE_TINY_BLOB; break;
+ case 2: sql_type= MYSQL_TYPE_BLOB; break;
+ case 3: sql_type= MYSQL_TYPE_MEDIUM_BLOB; break;
+ default: sql_type= MYSQL_TYPE_LONG_BLOB; break;
+ }
+ length/= charset->mbmaxlen;
+ key_length/= charset->mbmaxlen;
+ break;
+ case MYSQL_TYPE_STRING:
+ /* Change CHAR -> VARCHAR if dynamic record length */
+ if (old_field->type() == MYSQL_TYPE_VAR_STRING)
+ sql_type= MYSQL_TYPE_VARCHAR;
+ /* fall through */
+
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_VAR_STRING:
+ /* This is corrected in create_length_to_internal_length */
+ length= (length+charset->mbmaxlen-1) / charset->mbmaxlen;
+ break;
+#ifdef HAVE_SPATIAL
+ case MYSQL_TYPE_GEOMETRY:
+ geom_type= ((Field_geom*)old_field)->geom_type;
+ break;
+#endif
+ case MYSQL_TYPE_YEAR:
+ if (length != 4)
+ {
+ char buff[sizeof("YEAR()") + MY_INT64_NUM_DECIMAL_DIGITS + 1];
+ my_snprintf(buff, sizeof(buff), "YEAR(%lu)", length);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_WARN_DEPRECATED_SYNTAX,
+ ER(ER_WARN_DEPRECATED_SYNTAX),
+ buff, "YEAR(4)");
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (flags & (ENUM_FLAG | SET_FLAG))
+ interval= ((Field_enum*) old_field)->typelib;
+ else
+ interval=0;
+ def=0;
+ char_length= length;
+
+ /*
+ Copy the default value from the column object orig_field, if:
+ 1) The column has a constant default value.
+ 2) The column type is not a BLOB type.
+ 3) The original column (old_field) was properly initialized with a record
+ buffer pointer.
+ 4) The original column doesn't have a default function to auto-initialize
+ the column on INSERT
+ */
+ if (!(flags & (NO_DEFAULT_VALUE_FLAG | BLOB_FLAG)) && // 1) 2)
+ old_field->ptr && orig_field && // 3)
+ !old_field->has_insert_default_function()) // 4)
+ {
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff), charset);
+ my_ptrdiff_t diff;
+
+ /* Get the value from default_values */
+ diff= (my_ptrdiff_t) (orig_field->table->s->default_values-
+ orig_field->table->record[0]);
+ orig_field->move_field_offset(diff); // Points now at default_values
+ if (!orig_field->is_real_null())
+ {
+ char buff[MAX_FIELD_WIDTH], *pos;
+ String tmp(buff, sizeof(buff), charset), *res;
+ res= orig_field->val_str(&tmp);
+ pos= (char*) sql_strmake(res->ptr(), res->length());
+ def= new Item_string(pos, res->length(), charset);
+ }
+ orig_field->move_field_offset(-diff); // Back to record[0]
+ }
+}
+
+
+/**
+ maximum possible character length for blob.
+
+ This method is used in Item_field::set_field to calculate
+ max_length for Item.
+
+ For example:
+ CREATE TABLE t2 SELECT CONCAT(tinyblob_utf8_column) FROM t1;
+ must create a "VARCHAR(255) CHARACTER SET utf8" column.
+
+ @return
+ length
+*/
+
+uint32 Field_blob::char_length()
+{
+ switch (packlength)
+ {
+ case 1:
+ return 255;
+ case 2:
+ return 65535;
+ case 3:
+ return 16777215;
+ case 4:
+ return (uint32) 4294967295U;
+ default:
+ DBUG_ASSERT(0); // we should never go here
+ return 0;
+ }
+}
+
+
+/**
+ Makes a clone of this object for ALTER/CREATE TABLE
+
+ @param mem_root MEM_ROOT where to clone the field
+*/
+
+Create_field *Create_field::clone(MEM_ROOT *mem_root) const
+{
+ Create_field *res= new (mem_root) Create_field(*this);
+ return res;
+}
+
+
+/**
+ maximum possible display length for blob.
+
+ @return
+ length
+*/
+
+uint32 Field_blob::max_display_length()
+{
+ switch (packlength)
+ {
+ case 1:
+ return 255 * field_charset->mbmaxlen;
+ case 2:
+ return 65535 * field_charset->mbmaxlen;
+ case 3:
+ return 16777215 * field_charset->mbmaxlen;
+ case 4:
+ return (uint32) 4294967295U;
+ default:
+ DBUG_ASSERT(0); // we should never go here
+ return 0;
+ }
+}
+
+
+/*****************************************************************************
+ Warning handling
+*****************************************************************************/
+
+/**
+* Produce warning or note about data saved into field.
+
+ @param level - level of message (Note/Warning/Error)
+ @param code - error code of message to be produced
+ @param cut_increment - whenever we should increase cut fields count
+
+ @note
+ This function won't produce warning and increase cut fields counter
+ if count_cuted_fields == CHECK_FIELD_IGNORE for current thread.
+
+ if count_cuted_fields == CHECK_FIELD_IGNORE then we ignore notes.
+ This allows us to avoid notes in optimisation, like convert_constant_item().
+
+ @retval
+ 1 if count_cuted_fields == CHECK_FIELD_IGNORE and error level is not NOTE
+ @retval
+ 0 otherwise
+*/
+
+bool
+Field::set_warning(Sql_condition::enum_warning_level level, uint code,
+ int cut_increment) const
+{
+ /*
+ If this field was created only for type conversion purposes it
+ will have table == NULL.
+ */
+ THD *thd= table ? table->in_use : current_thd;
+ if (thd->count_cuted_fields)
+ {
+ thd->cuted_fields+= cut_increment;
+ push_warning_printf(thd, level, code, ER(code), field_name,
+ thd->get_stmt_da()->current_row_for_warning());
+ return 0;
+ }
+ return level >= Sql_condition::WARN_LEVEL_WARN;
+}
+
+
+/**
+ Produce warning or note about datetime string data saved into field.
+
+ @param level level of message (Note/Warning/Error)
+ @param code error code of message to be produced
+ @param str string value which we tried to save
+ @param ts_type type of datetime value (datetime/date/time)
+ @param cuted_increment whenever we should increase cut fields count or not
+
+ @note
+ This function will always produce some warning but won't increase cut
+ fields counter if count_cuted_fields ==FIELD_CHECK_IGNORE for current
+ thread.
+
+ See also bug#2336
+
+*/
+
+void Field::set_datetime_warning(Sql_condition::enum_warning_level level,
+ uint code, const ErrConv *str,
+ timestamp_type ts_type, int cuted_increment)
+{
+ THD *thd= get_thd();
+ if (thd->really_abort_on_warning() && level >= Sql_condition::WARN_LEVEL_WARN)
+ make_truncated_value_warning(thd, level, str, ts_type, field_name);
+ else
+ set_warning(level, code, cuted_increment);
+}
+
+
+/*
+ @brief
+ Return possible keys for a field
+
+ @details
+ Return bit map of keys over this field which can be used by the range
+ optimizer. For a field of a generic table such keys are all keys that starts
+ from this field. For a field of a materialized derived table/view such keys
+ are all keys in which this field takes a part. This is less restrictive as
+ keys for a materialized derived table/view are generated on the fly from
+ present fields, thus the case when a field for the beginning of a key is
+ absent is impossible.
+
+ @return map of possible keys
+*/
+
+key_map Field::get_possible_keys()
+{
+ DBUG_ASSERT(table->pos_in_table_list);
+ return (table->pos_in_table_list->is_materialized_derived() ?
+ part_of_key : key_start);
+}
+
+
+/**
+ Mark the field as having an explicit default value.
+
+ @param value if available, the value that the field is being set to
+
+ @note
+ Fields that have an explicit default value should not be updated
+ automatically via the DEFAULT or ON UPDATE functions. The functions
+ that deal with data change functionality (INSERT/UPDATE/LOAD),
+ determine if there is an explicit value for each field before performing
+ the data change, and call this method to mark the field.
+
+ If the 'value' parameter is NULL, then the field is marked unconditionally
+ as having an explicit value. If 'value' is not NULL, then it can be further
+ analyzed to check if it really should count as a value.
+*/
+
+void Field::set_explicit_default(Item *value)
+{
+ if (value->type() == Item::DEFAULT_VALUE_ITEM &&
+ !((Item_default_value*)value)->arg)
+ return;
+ set_has_explicit_value();
+}
diff --git a/sql/field.h b/sql/field.h
new file mode 100644
index 00000000000..8d76f54e216
--- /dev/null
+++ b/sql/field.h
@@ -0,0 +1,3054 @@
+#ifndef FIELD_INCLUDED
+#define FIELD_INCLUDED
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/*
+ Because of the function new_field() all field classes that have static
+ variables must declare the size_of() member function.
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "mysqld.h" /* system_charset_info */
+#include "table.h" /* TABLE */
+#include "sql_string.h" /* String */
+#include "my_decimal.h" /* my_decimal */
+#include "sql_error.h" /* Sql_condition */
+#include "compat56.h"
+
+class Send_field;
+class Protocol;
+class Create_field;
+class Relay_log_info;
+class Field;
+class Column_statistics;
+class Column_statistics_collected;
+
+enum enum_check_fields
+{
+ CHECK_FIELD_IGNORE,
+ CHECK_FIELD_WARN,
+ CHECK_FIELD_ERROR_FOR_NULL
+};
+
+
+enum Derivation
+{
+ DERIVATION_IGNORABLE= 6,
+ DERIVATION_NUMERIC= 5,
+ DERIVATION_COERCIBLE= 4,
+ DERIVATION_SYSCONST= 3,
+ DERIVATION_IMPLICIT= 2,
+ DERIVATION_NONE= 1,
+ DERIVATION_EXPLICIT= 0
+};
+
+#define STORAGE_TYPE_MASK 7
+#define COLUMN_FORMAT_MASK 7
+#define COLUMN_FORMAT_SHIFT 3
+
+#define my_charset_numeric my_charset_latin1
+#define MY_REPERTOIRE_NUMERIC MY_REPERTOIRE_ASCII
+
+/* The length of the header part for each virtual column in the .frm file */
+#define FRM_VCOL_HEADER_SIZE(b) (3 + MY_TEST(b))
+
+class Count_distinct_field;
+
+struct ha_field_option_struct;
+
+struct st_cache_field;
+int field_conv(Field *to,Field *from);
+int field_conv_incompatible(Field *to,Field *from);
+bool memcpy_field_possible(Field *to, Field *from);
+int truncate_double(double *nr, uint field_length, uint dec,
+ bool unsigned_flag, double max_value);
+longlong double_to_longlong(double nr, bool unsigned_flag, bool *error);
+
+inline uint get_enum_pack_length(int elements)
+{
+ return elements < 256 ? 1 : 2;
+}
+
+inline uint get_set_pack_length(int elements)
+{
+ uint len= (elements + 7) / 8;
+ return len > 4 ? 8 : len;
+}
+
+
+/**
+ Tests if field type is temporal and has date part,
+ i.e. represents DATE, DATETIME or TIMESTAMP types in SQL.
+
+ @param type Field type, as returned by field->type().
+ @retval true If field type is temporal type with date part.
+ @retval false If field type is not temporal type with date part.
+*/
+inline bool is_temporal_type_with_date(enum_field_types type)
+{
+ switch (type)
+ {
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIMESTAMP:
+ return true;
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_TIMESTAMP2:
+ DBUG_ASSERT(0); // field->real_type() should not get to here.
+ default:
+ return false;
+ }
+}
+
+
+/**
+ Recognizer for concrete data type (called real_type for some reason),
+ returning true if it is one of the TIMESTAMP types.
+*/
+inline bool is_timestamp_type(enum_field_types type)
+{
+ return type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_TIMESTAMP2;
+}
+
+
+/**
+ Convert temporal real types as retuned by field->real_type()
+ to field type as returned by field->type().
+
+ @param real_type Real type.
+ @retval Field type.
+*/
+inline enum_field_types real_type_to_type(enum_field_types real_type)
+{
+ switch (real_type)
+ {
+ case MYSQL_TYPE_TIME2:
+ return MYSQL_TYPE_TIME;
+ case MYSQL_TYPE_DATETIME2:
+ return MYSQL_TYPE_DATETIME;
+ case MYSQL_TYPE_TIMESTAMP2:
+ return MYSQL_TYPE_TIMESTAMP;
+ case MYSQL_TYPE_NEWDATE:
+ return MYSQL_TYPE_DATE;
+ /* Note: NEWDECIMAL is a type, not only a real_type */
+ default: return real_type;
+ }
+}
+
+
+static inline enum enum_mysql_timestamp_type
+mysql_type_to_time_type(enum enum_field_types mysql_type)
+{
+ switch(mysql_type) {
+ case MYSQL_TYPE_TIME2:
+ case MYSQL_TYPE_TIME: return MYSQL_TIMESTAMP_TIME;
+ case MYSQL_TYPE_TIMESTAMP2:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_DATETIME: return MYSQL_TIMESTAMP_DATETIME;
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_DATE: return MYSQL_TIMESTAMP_DATE;
+ default: return MYSQL_TIMESTAMP_ERROR;
+ }
+}
+
+
+/**
+ Tests if field type is temporal, i.e. represents
+ DATE, TIME, DATETIME or TIMESTAMP types in SQL.
+
+ @param type Field type, as returned by field->type().
+ @retval true If field type is temporal
+ @retval false If field type is not temporal
+*/
+inline bool is_temporal_type(enum_field_types type)
+{
+ return mysql_type_to_time_type(type) != MYSQL_TIMESTAMP_ERROR;
+}
+
+
+/**
+ Tests if field type is temporal and has time part,
+ i.e. represents TIME, DATETIME or TIMESTAMP types in SQL.
+
+ @param type Field type, as returned by field->type().
+ @retval true If field type is temporal type with time part.
+ @retval false If field type is not temporal type with time part.
+*/
+inline bool is_temporal_type_with_time(enum_field_types type)
+{
+ switch (type)
+ {
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIMESTAMP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+/*
+ Virtual_column_info is the class to contain additional
+ characteristics that is specific for a virtual/computed
+ field such as:
+ - the defining expression that is evaluated to compute the value
+ of the field
+ - whether the field is to be stored in the database
+ - whether the field is used in a partitioning expression
+*/
+
+class Virtual_column_info: public Sql_alloc
+{
+private:
+ /*
+ The following data is only updated by the parser and read
+ when a Create_field object is created/initialized.
+ */
+ enum_field_types field_type; /* Real field type*/
+ /* Flag indicating that the field is physically stored in the database */
+ bool stored_in_db;
+ /* Flag indicating that the field used in a partitioning expression */
+ bool in_partitioning_expr;
+
+public:
+ /* The expression to compute the value of the virtual column */
+ Item *expr_item;
+ /* Text representation of the defining expression */
+ LEX_STRING expr_str;
+
+ Virtual_column_info()
+ : field_type((enum enum_field_types)MYSQL_TYPE_VIRTUAL),
+ stored_in_db(FALSE), in_partitioning_expr(FALSE),
+ expr_item(NULL)
+ {
+ expr_str.str= NULL;
+ expr_str.length= 0;
+ };
+ ~Virtual_column_info() {}
+ enum_field_types get_real_type()
+ {
+ return field_type;
+ }
+ void set_field_type(enum_field_types fld_type)
+ {
+ /* Calling this function can only be done once. */
+ field_type= fld_type;
+ }
+ bool is_stored()
+ {
+ return stored_in_db;
+ }
+ void set_stored_in_db_flag(bool stored)
+ {
+ stored_in_db= stored;
+ }
+ bool is_in_partitioning_expr()
+ {
+ return in_partitioning_expr;
+ }
+ void mark_as_in_partitioning_expr()
+ {
+ in_partitioning_expr= TRUE;
+ }
+};
+
+class Field
+{
+ Field(const Item &); /* Prevent use of these */
+ void operator=(Field &);
+public:
+ static void *operator new(size_t size, MEM_ROOT *mem_root) throw ()
+ { return alloc_root(mem_root, size); }
+ static void *operator new(size_t size) throw ()
+ { return sql_alloc(size); }
+ static void operator delete(void *ptr_arg, size_t size) { TRASH(ptr_arg, size); }
+ static void operator delete(void *ptr, MEM_ROOT *mem_root)
+ { DBUG_ASSERT(0); }
+
+ uchar *ptr; // Position to field in record
+ /**
+ Byte where the @c NULL bit is stored inside a record. If this Field is a
+ @c NOT @c NULL field, this member is @c NULL.
+ */
+ uchar *null_ptr;
+ /*
+ Note that you can use table->in_use as replacement for current_thd member
+ only inside of val_*() and store() members (e.g. you can't use it in cons)
+ */
+ TABLE *table; // Pointer for table
+ TABLE *orig_table; // Pointer to original table
+ const char * const *table_name;
+ const char *field_name;
+ /** reference to the list of options or NULL */
+ engine_option_value *option_list;
+ ha_field_option_struct *option_struct; /* structure with parsed options */
+ LEX_STRING comment;
+ /* Field is part of the following keys */
+ key_map key_start, part_of_key, part_of_key_not_clustered;
+ key_map part_of_sortkey;
+ /*
+ We use three additional unireg types for TIMESTAMP to overcome limitation
+ of current binary format of .frm file. We'd like to be able to support
+ NOW() as default and on update value for such fields but unable to hold
+ this info anywhere except unireg_check field. This issue will be resolved
+ in more clean way with transition to new text based .frm format.
+ See also comment for Field_timestamp::Field_timestamp().
+ */
+ enum utype { NONE,DATE,SHIELD,NOEMPTY,CASEUP,PNR,BGNR,PGNR,YES,NO,REL,
+ CHECK,EMPTY,UNKNOWN_FIELD,CASEDN,NEXT_NUMBER,INTERVAL_FIELD,
+ BIT_FIELD, TIMESTAMP_OLD_FIELD, CAPITALIZE, BLOB_FIELD,
+ TIMESTAMP_DN_FIELD, TIMESTAMP_UN_FIELD, TIMESTAMP_DNUN_FIELD};
+ enum geometry_type
+ {
+ GEOM_GEOMETRY = 0, GEOM_POINT = 1, GEOM_LINESTRING = 2, GEOM_POLYGON = 3,
+ GEOM_MULTIPOINT = 4, GEOM_MULTILINESTRING = 5, GEOM_MULTIPOLYGON = 6,
+ GEOM_GEOMETRYCOLLECTION = 7
+ };
+ enum imagetype { itRAW, itMBR};
+
+ utype unireg_check;
+ uint32 field_length; // Length of field
+ uint32 flags;
+ uint16 field_index; // field number in fields array
+ uchar null_bit; // Bit used to test null bit
+ /**
+ If true, this field was created in create_tmp_field_from_item from a NULL
+ value. This means that the type of the field is just a guess, and the type
+ may be freely coerced to another type.
+
+ @see create_tmp_field_from_item
+ @see Item_type_holder::get_real_type
+
+ */
+ bool is_created_from_null_item;
+
+ /* TRUE in Field objects created for column min/max values */
+ bool is_stat_field;
+
+ /*
+ Selectivity of the range condition over this field.
+ When calculating this selectivity a range predicate
+ is taken into account only if:
+ - it is extracted from the WHERE clause
+ - it depends only on the table the field belongs to
+ */
+ double cond_selectivity;
+
+ /*
+ The next field in the class of equal fields at the top AND level
+ of the WHERE clause
+ */
+ Field *next_equal_field;
+
+ /*
+ This structure is used for statistical data on the column
+ that has been read from the statistical table column_stat
+ */
+ Column_statistics *read_stats;
+ /*
+ This structure is used for statistical data on the column that
+ is collected by the function collect_statistics_for_table
+ */
+ Column_statistics_collected *collected_stats;
+
+ /*
+ This is additional data provided for any computed(virtual) field.
+ In particular it includes a pointer to the item by which this field
+ can be computed from other fields.
+ */
+ Virtual_column_info *vcol_info;
+ /*
+ Flag indicating that the field is physically stored in tables
+ rather than just computed from other fields.
+ As of now, FALSE can be set only for computed virtual columns.
+ */
+ bool stored_in_db;
+
+ Field(uchar *ptr_arg,uint32 length_arg,uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg);
+ virtual ~Field() {}
+ /* Store functions returns 1 on overflow and -1 on fatal error */
+ virtual int store(const char *to, uint length,CHARSET_INFO *cs)=0;
+ virtual int store(double nr)=0;
+ virtual int store(longlong nr, bool unsigned_val)=0;
+ virtual int store_decimal(const my_decimal *d)=0;
+ virtual int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ int store_time(MYSQL_TIME *ltime)
+ { return store_time_dec(ltime, TIME_SECOND_PART_DIGITS); }
+ int store(const char *to, uint length, CHARSET_INFO *cs,
+ enum_check_fields check_level);
+ virtual double val_real(void)=0;
+ virtual longlong val_int(void)=0;
+ virtual my_decimal *val_decimal(my_decimal *);
+ inline String *val_str(String *str) { return val_str(str, str); }
+ /*
+ val_str(buf1, buf2) gets two buffers and should use them as follows:
+ if it needs a temp buffer to convert result to string - use buf1
+ example Field_tiny::val_str()
+ if the value exists as a string already - use buf2
+ example Field_string::val_str()
+ consequently, buf2 may be created as 'String buf;' - no memory
+ will be allocated for it. buf1 will be allocated to hold a
+ value if it's too small. Using allocated buffer for buf2 may result in
+ an unnecessary free (and later, may be an alloc).
+ This trickery is used to decrease a number of malloc calls.
+ */
+ virtual String *val_str(String*,String *)=0;
+ String *val_int_as_str(String *val_buffer, bool unsigned_flag);
+ /*
+ str_needs_quotes() returns TRUE if the value returned by val_str() needs
+ to be quoted when used in constructing an SQL query.
+ */
+ virtual bool str_needs_quotes() { return FALSE; }
+ virtual Item_result result_type () const=0;
+ virtual Item_result cmp_type () const { return result_type(); }
+ static bool type_can_have_key_part(enum_field_types);
+ static enum_field_types field_type_merge(enum_field_types, enum_field_types);
+ static Item_result result_merge_type(enum_field_types);
+ virtual bool eq(Field *field)
+ {
+ return (ptr == field->ptr && null_ptr == field->null_ptr &&
+ null_bit == field->null_bit && field->type() == type());
+ }
+ virtual bool eq_def(Field *field);
+
+ /*
+ pack_length() returns size (in bytes) used to store field data in memory
+ (i.e. it returns the maximum size of the field in a row of the table,
+ which is located in RAM).
+ */
+ virtual uint32 pack_length() const { return (uint32) field_length; }
+
+ /*
+ pack_length_in_rec() returns size (in bytes) used to store field data on
+ storage (i.e. it returns the maximal size of the field in a row of the
+ table, which is located on disk).
+ */
+ virtual uint32 pack_length_in_rec() const { return pack_length(); }
+ virtual bool compatible_field_size(uint metadata, Relay_log_info *rli,
+ uint16 mflags, int *order);
+ virtual uint pack_length_from_metadata(uint field_metadata)
+ {
+ DBUG_ENTER("Field::pack_length_from_metadata");
+ DBUG_RETURN(field_metadata);
+ }
+ virtual uint row_pack_length() const { return 0; }
+ virtual int save_field_metadata(uchar *first_byte)
+ { return do_save_field_metadata(first_byte); }
+
+ /*
+ data_length() return the "real size" of the data in memory.
+ */
+ virtual uint32 data_length() { return pack_length(); }
+ virtual uint32 sort_length() const { return pack_length(); }
+
+ /*
+ Get the number bytes occupied by the value in the field.
+ CHAR values are stripped of trailing spaces.
+ Flexible values are stripped of their length.
+ */
+ virtual uint32 value_length()
+ {
+ uint len;
+ if (!zero_pack() &&
+ (type() == MYSQL_TYPE_STRING &&
+ (len= pack_length()) >= 4 && len < 256))
+ {
+ uchar *str, *end;
+ for (str= ptr, end= str+len; end > str && end[-1] == ' '; end--) {}
+ len=(uint) (end-str);
+ return len;
+ }
+ return data_length();
+ }
+
+ /**
+ Get the maximum size of the data in packed format.
+
+ @return Maximum data length of the field when packed using the
+ Field::pack() function.
+ */
+ virtual uint32 max_data_length() const {
+ return pack_length();
+ };
+
+ virtual int reset(void) { bzero(ptr,pack_length()); return 0; }
+ virtual void reset_fields() {}
+ virtual void set_default()
+ {
+ my_ptrdiff_t l_offset= (my_ptrdiff_t) (table->s->default_values -
+ table->record[0]);
+ memcpy(ptr, ptr + l_offset, pack_length());
+ if (null_ptr)
+ *null_ptr= ((*null_ptr & (uchar) ~null_bit) |
+ (null_ptr[l_offset] & null_bit));
+ }
+
+ bool has_insert_default_function() const
+ {
+ return unireg_check == TIMESTAMP_DN_FIELD ||
+ unireg_check == TIMESTAMP_DNUN_FIELD;
+ }
+
+ bool has_update_default_function() const
+ {
+ return unireg_check == TIMESTAMP_UN_FIELD ||
+ unireg_check == TIMESTAMP_DNUN_FIELD;
+ }
+
+ /*
+ Mark the field as having a value supplied by the client, thus it should
+ not be auto-updated.
+ */
+ void set_has_explicit_value()
+ {
+ flags|= HAS_EXPLICIT_VALUE;
+ }
+
+ virtual void set_explicit_default(Item *value);
+
+ /**
+ Evaluates the @c INSERT default function and stores the result in the
+ field. If no such function exists for the column, or the function is not
+ valid for the column's data type, invoking this function has no effect.
+ */
+ virtual int evaluate_insert_default_function() { return 0; }
+
+
+ /**
+ Evaluates the @c UPDATE default function, if one exists, and stores the
+ result in the record buffer. If no such function exists for the column,
+ or the function is not valid for the column's data type, invoking this
+ function has no effect.
+ */
+ virtual int evaluate_update_default_function() { return 0; }
+
+ virtual bool binary() const { return 1; }
+ virtual bool zero_pack() const { return 1; }
+ virtual enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; }
+ virtual uint32 key_length() const { return pack_length(); }
+ virtual enum_field_types type() const =0;
+ virtual enum_field_types real_type() const { return type(); }
+ virtual enum_field_types binlog_type() const
+ {
+ /*
+ Binlog stores field->type() as type code by default. For example,
+ it puts MYSQL_TYPE_STRING in case of CHAR, VARCHAR, SET and ENUM,
+ with extra data type details put into metadata.
+
+ Binlog behaviour slightly differs between various MySQL and MariaDB
+ versions for the temporal data types TIME, DATETIME and TIMESTAMP.
+
+ MySQL prior to 5.6 uses MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME
+ and MYSQL_TYPE_TIMESTAMP type codes in binlog and stores no
+ additional metadata.
+
+ MariaDB-5.3 implements new versions for TIME, DATATIME, TIMESTAMP
+ with fractional second precision, but uses the old format for the
+ types TIME(0), DATETIME(0), TIMESTAMP(0), and it still stores
+ MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME and MYSQL_TYPE_TIMESTAMP in binlog,
+ with no additional metadata.
+ So row-based replication between temporal data types of
+ different precision is not possible in MariaDB.
+
+ MySQL-5.6 also implements a new version of TIME, DATETIME, TIMESTAMP
+ which support fractional second precision 0..6, and use the new
+ format even for the types TIME(0), DATETIME(0), TIMESTAMP(0).
+ For these new data types, MySQL-5.6 stores new type codes
+ MYSQL_TYPE_TIME2, MYSQL_TYPE_DATETIME2, MYSQL_TYPE_TIMESTAMP2 in binlog,
+ with fractional precision 0..6 put into metadata.
+ This makes it in theory possible to do row-based replication between
+ columns of different fractional precision (e.g. from TIME(1) on master
+ to TIME(6) on slave). However, it's not currently fully implemented yet.
+ MySQL-5.6 can only do row-based replication from the old types
+ TIME, DATETIME, TIMESTAMP (represented by MYSQL_TYPE_TIME,
+ MYSQL_TYPE_DATETIME and MYSQL_TYPE_TIMESTAMP type codes in binlog)
+ to the new corresponding types TIME(0), DATETIME(0), TIMESTAMP(0).
+
+ Note: MariaDB starting from the version 10.0 understands the new
+ MySQL-5.6 type codes MYSQL_TYPE_TIME2, MYSQL_TYPE_DATETIME2,
+ MYSQL_TYPE_TIMESTAMP2. When started over MySQL-5.6 tables both on
+ master and on slave, MariaDB-10.0 can also do row-based replication
+ from the old types TIME, DATETIME, TIMESTAMP to the new MySQL-5.6
+ types TIME(0), DATETIME(0), TIMESTAMP(0).
+
+ Note: perhaps binlog should eventually be modified to store
+ real_type() instead of type() for all column types.
+ */
+ return type();
+ }
+ inline int cmp(const uchar *str) { return cmp(ptr,str); }
+ virtual int cmp_max(const uchar *a, const uchar *b, uint max_len)
+ { return cmp(a, b); }
+ virtual int cmp(const uchar *,const uchar *)=0;
+ virtual int cmp_binary(const uchar *a,const uchar *b, uint32 max_length=~0L)
+ { return memcmp(a,b,pack_length()); }
+ virtual int cmp_offset(uint row_offset)
+ { return cmp(ptr,ptr+row_offset); }
+ virtual int cmp_binary_offset(uint row_offset)
+ { return cmp_binary(ptr, ptr+row_offset); };
+ virtual int key_cmp(const uchar *a,const uchar *b)
+ { return cmp(a, b); }
+ virtual int key_cmp(const uchar *str, uint length)
+ { return cmp(ptr,str); }
+ /*
+ Update the value m of the 'min_val' field with the current value v
+ of this field if force_update is set to TRUE or if v < m.
+ Return TRUE if the value has been updated.
+ */
+ virtual bool update_min(Field *min_val, bool force_update)
+ {
+ bool update_fl= force_update || cmp(ptr, min_val->ptr) < 0;
+ if (update_fl)
+ {
+ min_val->set_notnull();
+ memcpy(min_val->ptr, ptr, pack_length());
+ }
+ return update_fl;
+ }
+ /*
+ Update the value m of the 'max_val' field with the current value v
+ of this field if force_update is set to TRUE or if v > m.
+ Return TRUE if the value has been updated.
+ */
+ virtual bool update_max(Field *max_val, bool force_update)
+ {
+ bool update_fl= force_update || cmp(ptr, max_val->ptr) > 0;
+ if (update_fl)
+ {
+ max_val->set_notnull();
+ memcpy(max_val->ptr, ptr, pack_length());
+ }
+ return update_fl;
+ }
+ virtual void store_field_value(uchar *val, uint len)
+ {
+ memcpy(ptr, val, len);
+ }
+ virtual uint decimals() const { return 0; }
+ /*
+ Caller beware: sql_type can change str.Ptr, so check
+ ptr() to see if it changed if you are using your own buffer
+ in str and restore it with set() if needed
+ */
+ virtual void sql_type(String &str) const =0;
+ virtual uint size_of() const =0; // For new field
+ inline bool is_null(my_ptrdiff_t row_offset= 0) const
+ {
+ /*
+ The table may have been marked as containing only NULL values
+ for all fields if it is a NULL-complemented row of an OUTER JOIN
+ or if the query is an implicitly grouped query (has aggregate
+ functions but no GROUP BY clause) with no qualifying rows. If
+ this is the case (in which TABLE::null_row is true), the field
+ is considered to be NULL.
+
+ Note that if a table->null_row is set then also all null_bits are
+ set for the row.
+
+ In the case of the 'result_field' for GROUP BY, table->null_row might
+ refer to the *next* row in the table (when the algorithm is: read the
+ next row, see if any of group column values have changed, send the
+ result - grouped - row to the client if yes). So, table->null_row might
+ be wrong, but such a result_field is always nullable (that's defined by
+ original_field->maybe_null()) and we trust its null bit.
+ */
+ return null_ptr ? null_ptr[row_offset] & null_bit : table->null_row;
+ }
+ inline bool is_real_null(my_ptrdiff_t row_offset= 0) const
+ { return null_ptr && (null_ptr[row_offset] & null_bit); }
+ inline bool is_null_in_record(const uchar *record) const
+ {
+ if (!null_ptr)
+ return 0;
+ return record[(uint) (null_ptr - table->record[0])] & null_bit;
+ }
+ inline void set_null(my_ptrdiff_t row_offset= 0)
+ { if (null_ptr) null_ptr[row_offset]|= null_bit; }
+ inline void set_notnull(my_ptrdiff_t row_offset= 0)
+ { if (null_ptr) null_ptr[row_offset]&= (uchar) ~null_bit; }
+ inline bool maybe_null(void) const
+ { return null_ptr != 0 || table->maybe_null; }
+
+ /* @return true if this field is NULL-able, false otherwise. */
+ inline bool real_maybe_null(void) const { return null_ptr != 0; }
+ uint null_offset(const uchar *record) const
+ { return (uint) (null_ptr - record); }
+
+ uint null_offset() const
+ { return null_offset(table->record[0]); }
+ void set_null_ptr(uchar *p_null_ptr, uint p_null_bit)
+ {
+ null_ptr= p_null_ptr;
+ null_bit= p_null_bit;
+ }
+
+ inline THD *get_thd() { return table ? table->in_use : current_thd; }
+
+ enum {
+ LAST_NULL_BYTE_UNDEF= 0
+ };
+
+ /*
+ Find the position of the last null byte for the field.
+
+ SYNOPSIS
+ last_null_byte()
+
+ DESCRIPTION
+ Return a pointer to the last byte of the null bytes where the
+ field conceptually is placed.
+
+ RETURN VALUE
+ The position of the last null byte relative to the beginning of
+ the record. If the field does not use any bits of the null
+ bytes, the value 0 (LAST_NULL_BYTE_UNDEF) is returned.
+ */
+ size_t last_null_byte() const {
+ size_t bytes= do_last_null_byte();
+ DBUG_PRINT("debug", ("last_null_byte() ==> %ld", (long) bytes));
+ DBUG_ASSERT(bytes <= table->s->null_bytes);
+ return bytes;
+ }
+
+ void make_sort_key(uchar *buff, uint length);
+ virtual void make_field(Send_field *);
+ virtual void sort_string(uchar *buff,uint length)=0;
+ virtual bool optimize_range(uint idx, uint part);
+ virtual void free() {}
+ virtual Field *new_field(MEM_ROOT *root, TABLE *new_table,
+ bool keep_type);
+ virtual Field *new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit);
+ Field *clone(MEM_ROOT *mem_root, TABLE *new_table);
+ Field *clone(MEM_ROOT *mem_root, TABLE *new_table, my_ptrdiff_t diff,
+ bool stat_flag= FALSE);
+ Field *clone(MEM_ROOT *mem_root, my_ptrdiff_t diff);
+ inline void move_field(uchar *ptr_arg,uchar *null_ptr_arg,uchar null_bit_arg)
+ {
+ ptr=ptr_arg; null_ptr=null_ptr_arg; null_bit=null_bit_arg;
+ }
+ inline void move_field(uchar *ptr_arg) { ptr=ptr_arg; }
+ virtual void move_field_offset(my_ptrdiff_t ptr_diff)
+ {
+ ptr=ADD_TO_PTR(ptr,ptr_diff, uchar*);
+ if (null_ptr)
+ null_ptr=ADD_TO_PTR(null_ptr,ptr_diff,uchar*);
+ }
+ virtual void get_image(uchar *buff, uint length, CHARSET_INFO *cs)
+ { memcpy(buff,ptr,length); }
+ virtual void set_image(const uchar *buff,uint length, CHARSET_INFO *cs)
+ { memcpy(ptr,buff,length); }
+
+
+ /*
+ Copy a field part into an output buffer.
+
+ SYNOPSIS
+ Field::get_key_image()
+ buff [out] output buffer
+ length output buffer size
+ type itMBR for geometry blobs, otherwise itRAW
+
+ DESCRIPTION
+ This function makes a copy of field part of size equal to or
+ less than "length" parameter value.
+ For fields of string types (CHAR, VARCHAR, TEXT) the rest of buffer
+ is padded by zero byte.
+
+ NOTES
+ For variable length character fields (i.e. UTF-8) the "length"
+ parameter means a number of output buffer bytes as if all field
+ characters have maximal possible size (mbmaxlen). In the other words,
+ "length" parameter is a number of characters multiplied by
+ field_charset->mbmaxlen.
+
+ RETURN
+ Number of copied bytes (excluding padded zero bytes -- see above).
+ */
+
+ virtual uint get_key_image(uchar *buff, uint length, imagetype type_arg)
+ {
+ get_image(buff, length, &my_charset_bin);
+ return length;
+ }
+ virtual void set_key_image(const uchar *buff,uint length)
+ { set_image(buff,length, &my_charset_bin); }
+ inline longlong val_int_offset(uint row_offset)
+ {
+ ptr+=row_offset;
+ longlong tmp=val_int();
+ ptr-=row_offset;
+ return tmp;
+ }
+ inline longlong val_int(const uchar *new_ptr)
+ {
+ uchar *old_ptr= ptr;
+ longlong return_value;
+ ptr= (uchar*) new_ptr;
+ return_value= val_int();
+ ptr= old_ptr;
+ return return_value;
+ }
+ inline String *val_str(String *str, const uchar *new_ptr)
+ {
+ uchar *old_ptr= ptr;
+ ptr= (uchar*) new_ptr;
+ val_str(str);
+ ptr= old_ptr;
+ return str;
+ }
+ virtual bool send_binary(Protocol *protocol);
+
+ virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
+ /**
+ @overload Field::pack(uchar*, const uchar*, uint, bool)
+ */
+ uchar *pack(uchar *to, const uchar *from)
+ {
+ DBUG_ENTER("Field::pack");
+ uchar *result= this->pack(to, from, UINT_MAX);
+ DBUG_RETURN(result);
+ }
+
+ virtual const uchar *unpack(uchar* to, const uchar *from,
+ const uchar *from_end, uint param_data=0);
+
+ virtual uint packed_col_length(const uchar *to, uint length)
+ { return length;}
+ virtual uint max_packed_col_length(uint max_length)
+ { return max_length;}
+
+ uint offset(uchar *record)
+ {
+ return (uint) (ptr - record);
+ }
+ void copy_from_tmp(int offset);
+ uint fill_cache_field(struct st_cache_field *copy);
+ virtual bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ bool get_time(MYSQL_TIME *ltime) { return get_date(ltime, TIME_TIME_ONLY); }
+ virtual CHARSET_INFO *charset(void) const { return &my_charset_bin; }
+ virtual CHARSET_INFO *charset_for_protocol(void) const
+ { return binary() ? &my_charset_bin : charset(); }
+ virtual CHARSET_INFO *sort_charset(void) const { return charset(); }
+ virtual bool has_charset(void) const { return FALSE; }
+ /*
+ match_collation_to_optimize_range() is to distinguish in
+ range optimizer (see opt_range.cc) between real string types:
+ CHAR, VARCHAR, TEXT
+ and the other string-alike types with result_type() == STRING_RESULT:
+ DATE, TIME, DATETIME, TIMESTAMP
+ We need it to decide whether to test if collation of the operation
+ matches collation of the field (needed only for real string types).
+ */
+ virtual bool match_collation_to_optimize_range() const { return false; }
+ virtual void set_charset(CHARSET_INFO *charset_arg) { }
+ virtual enum Derivation derivation(void) const
+ { return DERIVATION_IMPLICIT; }
+ virtual uint repertoire(void) const { return MY_REPERTOIRE_UNICODE30; }
+ virtual void set_derivation(enum Derivation derivation_arg) { }
+ virtual int set_time() { return 1; }
+ bool set_warning(Sql_condition::enum_warning_level, unsigned int code,
+ int cuted_increment) const;
+ void set_datetime_warning(Sql_condition::enum_warning_level, uint code,
+ const ErrConv *str, timestamp_type ts_type,
+ int cuted_increment);
+ inline bool check_overflow(int op_result)
+ {
+ return (op_result == E_DEC_OVERFLOW);
+ }
+ int warn_if_overflow(int op_result);
+ void set_table_name(String *alias)
+ {
+ table_name= &alias->Ptr;
+ }
+ void init(TABLE *table_arg)
+ {
+ orig_table= table= table_arg;
+ set_table_name(&table_arg->alias);
+ }
+
+ /* maximum possible display length */
+ virtual uint32 max_display_length()= 0;
+
+ /**
+ Whether a field being created is compatible with a existing one.
+
+ Used by the ALTER TABLE code to evaluate whether the new definition
+ of a table is compatible with the old definition so that it can
+ determine if data needs to be copied over (table data change).
+ */
+ virtual uint is_equal(Create_field *new_field);
+ /* convert decimal to longlong with overflow check */
+ longlong convert_decimal2longlong(const my_decimal *val, bool unsigned_flag,
+ int *err);
+ /* The max. number of characters */
+ virtual uint32 char_length()
+ {
+ return field_length / charset()->mbmaxlen;
+ }
+
+ virtual geometry_type get_geometry_type()
+ {
+ /* shouldn't get here. */
+ DBUG_ASSERT(0);
+ return GEOM_GEOMETRY;
+ }
+
+ ha_storage_media field_storage_type() const
+ {
+ return (ha_storage_media)
+ ((flags >> FIELD_FLAGS_STORAGE_MEDIA) & 3);
+ }
+
+ void set_storage_type(ha_storage_media storage_type_arg)
+ {
+ DBUG_ASSERT(field_storage_type() == HA_SM_DEFAULT);
+ flags |= (storage_type_arg << FIELD_FLAGS_STORAGE_MEDIA);
+ }
+
+ column_format_type column_format() const
+ {
+ return (column_format_type)
+ ((flags >> FIELD_FLAGS_COLUMN_FORMAT) & 3);
+ }
+
+ void set_column_format(column_format_type column_format_arg)
+ {
+ DBUG_ASSERT(column_format() == COLUMN_FORMAT_TYPE_DEFAULT);
+ flags |= (column_format_arg << FIELD_FLAGS_COLUMN_FORMAT);
+ }
+
+ key_map get_possible_keys();
+
+ /* Hash value */
+ virtual void hash(ulong *nr, ulong *nr2);
+
+ /* Check whether the field can be used as a join attribute in hash join */
+ virtual bool hash_join_is_possible() { return TRUE; }
+ virtual bool eq_cmp_as_binary() { return TRUE; }
+
+ /* Position of the field value within the interval of [min, max] */
+ virtual double pos_in_interval(Field *min, Field *max)
+ {
+ return (double) 0.5;
+ }
+
+ friend int cre_myisam(char * name, register TABLE *form, uint options,
+ ulonglong auto_increment_value);
+ friend class Copy_field;
+ friend class Item_avg_field;
+ friend class Item_std_field;
+ friend class Item_sum_num;
+ friend class Item_sum_sum;
+ friend class Item_sum_str;
+ friend class Item_sum_count;
+ friend class Item_sum_avg;
+ friend class Item_sum_std;
+ friend class Item_sum_min;
+ friend class Item_sum_max;
+ friend class Item_func_group_concat;
+
+private:
+ /*
+ Primitive for implementing last_null_byte().
+
+ SYNOPSIS
+ do_last_null_byte()
+
+ DESCRIPTION
+ Primitive for the implementation of the last_null_byte()
+ function. This represents the inheritance interface and can be
+ overridden by subclasses.
+ */
+ virtual size_t do_last_null_byte() const;
+
+/**
+ Retrieve the field metadata for fields.
+
+ This default implementation returns 0 and saves 0 in the metadata_ptr
+ value.
+
+ @param metadata_ptr First byte of field metadata
+
+ @returns 0 no bytes written.
+*/
+ virtual int do_save_field_metadata(uchar *metadata_ptr)
+ { return 0; }
+
+protected:
+ uchar *pack_int(uchar *to, const uchar *from, size_t size)
+ {
+ memcpy(to, from, size);
+ return to + size;
+ }
+
+ const uchar *unpack_int(uchar* to, const uchar *from,
+ const uchar *from_end, size_t size)
+ {
+ if (from + size > from_end)
+ return 0;
+ memcpy(to, from, size);
+ return from + size;
+ }
+
+ uchar *pack_int16(uchar *to, const uchar *from)
+ { return pack_int(to, from, 2); }
+ const uchar *unpack_int16(uchar* to, const uchar *from, const uchar *from_end)
+ { return unpack_int(to, from, from_end, 2); }
+ uchar *pack_int24(uchar *to, const uchar *from)
+ { return pack_int(to, from, 3); }
+ const uchar *unpack_int24(uchar* to, const uchar *from, const uchar *from_end)
+ { return unpack_int(to, from, from_end, 3); }
+ uchar *pack_int32(uchar *to, const uchar *from)
+ { return pack_int(to, from, 4); }
+ const uchar *unpack_int32(uchar* to, const uchar *from, const uchar *from_end)
+ { return unpack_int(to, from, from_end, 4); }
+ uchar *pack_int64(uchar* to, const uchar *from)
+ { return pack_int(to, from, 8); }
+ const uchar *unpack_int64(uchar* to, const uchar *from, const uchar *from_end)
+ { return unpack_int(to, from, from_end, 8); }
+
+ bool field_flags_are_binary()
+ {
+ return (flags & (BINCMP_FLAG | BINARY_FLAG)) != 0;
+ }
+ double pos_in_interval_val_real(Field *min, Field *max);
+ double pos_in_interval_val_str(Field *min, Field *max, uint data_offset);
+};
+
+
+class Field_num :public Field {
+public:
+ const uint8 dec;
+ bool zerofill,unsigned_flag; // Purify cannot handle bit fields
+ Field_num(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg,
+ uint8 dec_arg, bool zero_arg, bool unsigned_arg);
+ enum Item_result result_type () const { return INT_RESULT; }
+ enum Derivation derivation(void) const { return DERIVATION_NUMERIC; }
+ uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; }
+ CHARSET_INFO *charset(void) const { return &my_charset_numeric; }
+ void prepend_zeros(String *value);
+ void add_zerofill_and_unsigned(String &res) const;
+ friend class Create_field;
+ void make_field(Send_field *);
+ uint decimals() const { return (uint) dec; }
+ uint size_of() const { return sizeof(*this); }
+ bool eq_def(Field *field);
+ int store_decimal(const my_decimal *);
+ my_decimal *val_decimal(my_decimal *);
+ uint is_equal(Create_field *new_field);
+ uint row_pack_length() const { return pack_length(); }
+ uint32 pack_length_from_metadata(uint field_metadata) {
+ uint32 length= pack_length();
+ DBUG_PRINT("result", ("pack_length_from_metadata(%d): %u",
+ field_metadata, length));
+ return length;
+ }
+ int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ int check_int(CHARSET_INFO *cs, const char *str, int length,
+ const char *int_end, int error);
+ bool get_int(CHARSET_INFO *cs, const char *from, uint len,
+ longlong *rnd, ulonglong unsigned_max,
+ longlong signed_min, longlong signed_max);
+ double pos_in_interval(Field *min, Field *max)
+ {
+ return pos_in_interval_val_real(min, max);
+ }
+};
+
+
+class Field_str :public Field {
+protected:
+ CHARSET_INFO *field_charset;
+ enum Derivation field_derivation;
+public:
+ Field_str(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg, CHARSET_INFO *charset);
+ Item_result result_type () const { return STRING_RESULT; }
+ uint decimals() const { return NOT_FIXED_DEC; }
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val)=0;
+ int store_decimal(const my_decimal *);
+ int store(const char *to,uint length,CHARSET_INFO *cs)=0;
+ uint repertoire(void) const
+ {
+ return my_charset_repertoire(field_charset);
+ }
+ CHARSET_INFO *charset(void) const { return field_charset; }
+ void set_charset(CHARSET_INFO *charset_arg) { field_charset= charset_arg; }
+ enum Derivation derivation(void) const { return field_derivation; }
+ virtual void set_derivation(enum Derivation derivation_arg)
+ { field_derivation= derivation_arg; }
+ bool binary() const { return field_charset == &my_charset_bin; }
+ uint32 max_display_length() { return field_length; }
+ friend class Create_field;
+ my_decimal *val_decimal(my_decimal *);
+ virtual bool str_needs_quotes() { return TRUE; }
+ uint is_equal(Create_field *new_field);
+ bool eq_cmp_as_binary() { return MY_TEST(flags & BINARY_FLAG); }
+ virtual uint length_size() { return 0; }
+ double pos_in_interval(Field *min, Field *max)
+ {
+ return pos_in_interval_val_str(min, max, length_size());
+ }
+};
+
+/* base class for Field_string, Field_varstring and Field_blob */
+
+class Field_longstr :public Field_str
+{
+protected:
+ int report_if_important_data(const char *ptr, const char *end,
+ bool count_spaces);
+public:
+ Field_longstr(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg, CHARSET_INFO *charset_arg)
+ :Field_str(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg,
+ field_name_arg, charset_arg)
+ {}
+
+ int store_decimal(const my_decimal *d);
+ uint32 max_data_length() const;
+ bool match_collation_to_optimize_range() const { return true; }
+};
+
+/* base class for float and double and decimal (old one) */
+class Field_real :public Field_num {
+public:
+ bool not_fixed;
+
+ Field_real(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg,
+ uint8 dec_arg, bool zero_arg, bool unsigned_arg)
+ :Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg,
+ field_name_arg, dec_arg, zero_arg, unsigned_arg),
+ not_fixed(dec_arg >= NOT_FIXED_DEC)
+ {}
+ Item_result result_type () const { return REAL_RESULT; }
+ int store_decimal(const my_decimal *);
+ int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ my_decimal *val_decimal(my_decimal *);
+ uint32 max_display_length() { return field_length; }
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+class Field_decimal :public Field_real {
+public:
+ Field_decimal(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint8 dec_arg,bool zero_arg,bool unsigned_arg)
+ :Field_real(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ dec_arg, zero_arg, unsigned_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_DECIMAL;}
+ enum ha_base_keytype key_type() const
+ { return zerofill ? HA_KEYTYPE_BINARY : HA_KEYTYPE_NUM; }
+ int reset(void);
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ void overflow(bool negative);
+ bool zero_pack() const { return 0; }
+ void sql_type(String &str) const;
+ virtual uchar *pack(uchar* to, const uchar *from, uint max_length)
+ {
+ return Field::pack(to, from, max_length);
+ }
+};
+
+
+/* New decimal/numeric field which use fixed point arithmetic */
+class Field_new_decimal :public Field_num {
+private:
+ int do_save_field_metadata(uchar *first_byte);
+public:
+ /* The maximum number of decimal digits can be stored */
+ uint precision;
+ uint bin_size;
+ /*
+ Constructors take max_length of the field as a parameter - not the
+ precision as the number of decimal digits allowed.
+ So for example we need to count length from precision handling
+ CREATE TABLE ( DECIMAL(x,y))
+ */
+ Field_new_decimal(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint8 dec_arg, bool zero_arg, bool unsigned_arg);
+ Field_new_decimal(uint32 len_arg, bool maybe_null_arg,
+ const char *field_name_arg, uint8 dec_arg,
+ bool unsigned_arg);
+ enum_field_types type() const { return MYSQL_TYPE_NEWDECIMAL;}
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; }
+ Item_result result_type () const { return DECIMAL_RESULT; }
+ int reset(void);
+ bool store_value(const my_decimal *decimal_value);
+ void set_value_on_overflow(my_decimal *decimal_value, bool sign);
+ int store(const char *to, uint length, CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ int store_decimal(const my_decimal *);
+ double val_real(void);
+ longlong val_int(void);
+ my_decimal *val_decimal(my_decimal *);
+ String *val_str(String*, String *);
+ int cmp(const uchar *, const uchar *);
+ void sort_string(uchar *buff, uint length);
+ bool zero_pack() const { return 0; }
+ void sql_type(String &str) const;
+ uint32 max_display_length() { return field_length; }
+ uint size_of() const { return sizeof(*this); }
+ uint32 pack_length() const { return (uint32) bin_size; }
+ uint pack_length_from_metadata(uint field_metadata);
+ uint row_pack_length() const { return pack_length(); }
+ bool compatible_field_size(uint field_metadata, Relay_log_info *rli,
+ uint16 mflags, int *order_var);
+ uint is_equal(Create_field *new_field);
+ virtual const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data);
+ static Field *create_from_item (Item *);
+};
+
+
+class Field_tiny :public Field_num {
+public:
+ Field_tiny(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ bool zero_arg, bool unsigned_arg)
+ :Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ 0, zero_arg,unsigned_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_TINY;}
+ enum ha_base_keytype key_type() const
+ { return unsigned_flag ? HA_KEYTYPE_BINARY : HA_KEYTYPE_INT8; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int reset(void) { ptr[0]=0; return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 1; }
+ void sql_type(String &str) const;
+ uint32 max_display_length() { return 4; }
+
+ virtual uchar *pack(uchar* to, const uchar *from, uint max_length)
+ {
+ *to= *from;
+ return to + 1;
+ }
+
+ virtual const uchar *unpack(uchar* to, const uchar *from,
+ const uchar *from_end, uint param_data)
+ {
+ if (from == from_end)
+ return 0;
+ *to= *from;
+ return from + 1;
+ }
+};
+
+
+class Field_short :public Field_num {
+public:
+ Field_short(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ bool zero_arg, bool unsigned_arg)
+ :Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ 0, zero_arg,unsigned_arg)
+ {}
+ Field_short(uint32 len_arg,bool maybe_null_arg, const char *field_name_arg,
+ bool unsigned_arg)
+ :Field_num((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "": 0,0,
+ NONE, field_name_arg, 0, 0, unsigned_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_SHORT;}
+ enum ha_base_keytype key_type() const
+ { return unsigned_flag ? HA_KEYTYPE_USHORT_INT : HA_KEYTYPE_SHORT_INT;}
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int reset(void) { ptr[0]=ptr[1]=0; return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 2; }
+ void sql_type(String &str) const;
+ uint32 max_display_length() { return 6; }
+
+ virtual uchar *pack(uchar* to, const uchar *from, uint max_length)
+ { return pack_int16(to, from); }
+
+ virtual const uchar *unpack(uchar* to, const uchar *from,
+ const uchar *from_end, uint param_data)
+ { return unpack_int16(to, from, from_end); }
+};
+
+class Field_medium :public Field_num {
+public:
+ Field_medium(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ bool zero_arg, bool unsigned_arg)
+ :Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ 0, zero_arg,unsigned_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_INT24;}
+ enum ha_base_keytype key_type() const
+ { return unsigned_flag ? HA_KEYTYPE_UINT24 : HA_KEYTYPE_INT24; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int reset(void) { ptr[0]=ptr[1]=ptr[2]=0; return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 3; }
+ void sql_type(String &str) const;
+ uint32 max_display_length() { return 8; }
+
+ virtual uchar *pack(uchar* to, const uchar *from, uint max_length)
+ {
+ return Field::pack(to, from, max_length);
+ }
+};
+
+
+class Field_long :public Field_num {
+public:
+ Field_long(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ bool zero_arg, bool unsigned_arg)
+ :Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ 0, zero_arg,unsigned_arg)
+ {}
+ Field_long(uint32 len_arg,bool maybe_null_arg, const char *field_name_arg,
+ bool unsigned_arg)
+ :Field_num((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "": 0,0,
+ NONE, field_name_arg,0,0,unsigned_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_LONG;}
+ enum ha_base_keytype key_type() const
+ { return unsigned_flag ? HA_KEYTYPE_ULONG_INT : HA_KEYTYPE_LONG_INT; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int reset(void) { ptr[0]=ptr[1]=ptr[2]=ptr[3]=0; return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ bool send_binary(Protocol *protocol);
+ String *val_str(String*,String *);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 4; }
+ void sql_type(String &str) const;
+ uint32 max_display_length() { return MY_INT32_NUM_DECIMAL_DIGITS; }
+ virtual uchar *pack(uchar* to, const uchar *from,
+ uint max_length __attribute__((unused)))
+ {
+ return pack_int32(to, from);
+ }
+ virtual const uchar *unpack(uchar* to, const uchar *from,
+ const uchar *from_end,
+ uint param_data __attribute__((unused)))
+ {
+ return unpack_int32(to, from, from_end);
+ }
+};
+
+
+class Field_longlong :public Field_num {
+public:
+ Field_longlong(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ bool zero_arg, bool unsigned_arg)
+ :Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ 0, zero_arg,unsigned_arg)
+ {}
+ Field_longlong(uint32 len_arg,bool maybe_null_arg,
+ const char *field_name_arg,
+ bool unsigned_arg)
+ :Field_num((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "": 0,0,
+ NONE, field_name_arg,0,0,unsigned_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_LONGLONG;}
+ enum ha_base_keytype key_type() const
+ { return unsigned_flag ? HA_KEYTYPE_ULONGLONG : HA_KEYTYPE_LONGLONG; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int reset(void)
+ {
+ ptr[0]=ptr[1]=ptr[2]=ptr[3]=ptr[4]=ptr[5]=ptr[6]=ptr[7]=0;
+ return 0;
+ }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 8; }
+ void sql_type(String &str) const;
+ uint32 max_display_length() { return 20; }
+ virtual uchar *pack(uchar* to, const uchar *from,
+ uint max_length __attribute__((unused)))
+ {
+ return pack_int64(to, from);
+ }
+ const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data __attribute__((unused)))
+ {
+ return unpack_int64(to, from, from_end);
+ }
+};
+
+
+class Field_float :public Field_real {
+public:
+ Field_float(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint8 dec_arg,bool zero_arg,bool unsigned_arg)
+ :Field_real(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ dec_arg, zero_arg, unsigned_arg)
+ {}
+ Field_float(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg,
+ uint8 dec_arg)
+ :Field_real((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "": 0, (uint) 0,
+ NONE, field_name_arg, dec_arg, 0, 0)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_FLOAT;}
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_FLOAT; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int reset(void) { bzero(ptr,sizeof(float)); return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return sizeof(float); }
+ uint row_pack_length() const { return pack_length(); }
+ void sql_type(String &str) const;
+private:
+ int do_save_field_metadata(uchar *first_byte);
+};
+
+
+class Field_double :public Field_real {
+public:
+ Field_double(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint8 dec_arg,bool zero_arg,bool unsigned_arg)
+ :Field_real(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ dec_arg, zero_arg, unsigned_arg)
+ {}
+ Field_double(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg,
+ uint8 dec_arg)
+ :Field_real((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "" : 0, (uint) 0,
+ NONE, field_name_arg, dec_arg, 0, 0)
+ {}
+ Field_double(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg,
+ uint8 dec_arg, bool not_fixed_arg)
+ :Field_real((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "" : 0, (uint) 0,
+ NONE, field_name_arg, dec_arg, 0, 0)
+ {not_fixed= not_fixed_arg; }
+ enum_field_types type() const { return MYSQL_TYPE_DOUBLE;}
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_DOUBLE; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int reset(void) { bzero(ptr,sizeof(double)); return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return sizeof(double); }
+ uint row_pack_length() const { return pack_length(); }
+ void sql_type(String &str) const;
+private:
+ int do_save_field_metadata(uchar *first_byte);
+};
+
+
+/* Everything saved in this will disappear. It will always return NULL */
+
+class Field_null :public Field_str {
+ static uchar null[1];
+public:
+ Field_null(uchar *ptr_arg, uint32 len_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ CHARSET_INFO *cs)
+ :Field_str(ptr_arg, len_arg, null, 1,
+ unireg_check_arg, field_name_arg, cs)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_NULL;}
+ int store(const char *to, uint length, CHARSET_INFO *cs)
+ { null[0]=1; return 0; }
+ int store(double nr) { null[0]=1; return 0; }
+ int store(longlong nr, bool unsigned_val) { null[0]=1; return 0; }
+ int store_decimal(const my_decimal *d) { null[0]=1; return 0; }
+ int reset(void) { return 0; }
+ double val_real(void) { return 0.0;}
+ longlong val_int(void) { return 0;}
+ my_decimal *val_decimal(my_decimal *) { return 0; }
+ String *val_str(String *value,String *value2)
+ { value2->length(0); return value2;}
+ int cmp(const uchar *a, const uchar *b) { return 0;}
+ void sort_string(uchar *buff, uint length) {}
+ uint32 pack_length() const { return 0; }
+ void sql_type(String &str) const;
+ uint size_of() const { return sizeof(*this); }
+ uint32 max_display_length() { return 4; }
+ void move_field_offset(my_ptrdiff_t ptr_diff) {}
+};
+
+
+class Field_temporal: public Field {
+public:
+ Field_temporal(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, utype unireg_check_arg,
+ const char *field_name_arg)
+ :Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg,
+ field_name_arg)
+ { flags|= BINARY_FLAG; }
+ Item_result result_type () const { return STRING_RESULT; }
+ uint32 max_display_length() { return field_length; }
+ bool str_needs_quotes() { return TRUE; }
+ enum Derivation derivation(void) const { return DERIVATION_NUMERIC; }
+ uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; }
+ CHARSET_INFO *charset(void) const { return &my_charset_numeric; }
+ const CHARSET_INFO *sort_charset(void) const { return &my_charset_bin; }
+ bool binary() const { return true; }
+ enum Item_result cmp_type () const { return TIME_RESULT; }
+ uint is_equal(Create_field *new_field);
+ bool eq_def(Field *field)
+ {
+ return (Field::eq_def(field) && decimals() == field->decimals());
+ }
+ my_decimal *val_decimal(my_decimal*);
+ void set_warnings(Sql_condition::enum_warning_level trunc_level,
+ const ErrConv *str, int was_cut, timestamp_type ts_type);
+ double pos_in_interval(Field *min, Field *max)
+ {
+ return pos_in_interval_val_real(min, max);
+ }
+};
+
+
+/**
+ Abstract class for:
+ - DATE
+ - DATETIME
+ - DATETIME(1..6)
+ - DATETIME(0..6) - MySQL56 version
+*/
+class Field_temporal_with_date: public Field_temporal {
+protected:
+ int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str,
+ int was_cut, int have_smth_to_conv);
+ virtual void store_TIME(MYSQL_TIME *ltime) = 0;
+public:
+ Field_temporal_with_date(uchar *ptr_arg, uint32 len_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ utype unireg_check_arg,
+ const char *field_name_arg)
+ :Field_temporal(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg)
+ {}
+ int store(const char *to, uint length, CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ int store_decimal(const my_decimal *);
+};
+
+
+class Field_timestamp :public Field_temporal {
+protected:
+ int store_TIME_with_warning(THD *, MYSQL_TIME *, const ErrConv *,
+ int warnings, bool have_smth_to_conv);
+public:
+ Field_timestamp(uchar *ptr_arg, uint32 len_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ TABLE_SHARE *share);
+ enum_field_types type() const { return MYSQL_TYPE_TIMESTAMP;}
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ int store_decimal(const my_decimal *);
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 4; }
+ void sql_type(String &str) const;
+ bool zero_pack() const { return 0; }
+ virtual int set_time();
+ virtual void set_default()
+ {
+ if (has_insert_default_function())
+ set_time();
+ else
+ Field::set_default();
+ }
+ virtual void set_explicit_default(Item *value);
+ virtual int evaluate_insert_default_function()
+ {
+ int res= 0;
+ if (has_insert_default_function())
+ res= set_time();
+ return res;
+ }
+ virtual int evaluate_update_default_function()
+ {
+ int res= 0;
+ if (has_update_default_function())
+ res= set_time();
+ return res;
+ }
+ /* Get TIMESTAMP field value as seconds since begging of Unix Epoch */
+ virtual my_time_t get_timestamp(ulong *sec_part) const;
+ virtual void store_TIME(my_time_t timestamp, ulong sec_part)
+ {
+ int4store(ptr,timestamp);
+ }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ uchar *pack(uchar *to, const uchar *from,
+ uint max_length __attribute__((unused)))
+ {
+ return pack_int32(to, from);
+ }
+ const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data __attribute__((unused)))
+ {
+ return unpack_int32(to, from, from_end);
+ }
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+/**
+ Abstract class for:
+ - TIMESTAMP(1..6)
+ - TIMESTAMP(0..6) - MySQL56 version
+*/
+class Field_timestamp_with_dec :public Field_timestamp {
+protected:
+ uint dec;
+public:
+ Field_timestamp_with_dec(uchar *ptr_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg,
+ const char *field_name_arg,
+ TABLE_SHARE *share, uint dec_arg) :
+ Field_timestamp(ptr_arg,
+ MAX_DATETIME_WIDTH + dec_arg + MY_TEST(dec_arg), null_ptr_arg,
+ null_bit_arg, unireg_check_arg, field_name_arg, share),
+ dec(dec_arg)
+ {
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ }
+ uint decimals() const { return dec; }
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; }
+ uchar *pack(uchar *to, const uchar *from, uint max_length)
+ { return Field::pack(to, from, max_length); }
+ const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data)
+ { return Field::unpack(to, from, from_end, param_data); }
+ void make_field(Send_field *field);
+ void sort_string(uchar *to, uint length)
+ {
+ DBUG_ASSERT(length == pack_length());
+ memcpy(to, ptr, length);
+ }
+ bool send_binary(Protocol *protocol);
+ double val_real(void);
+ my_decimal* val_decimal(my_decimal*);
+ int set_time();
+};
+
+
+class Field_timestamp_hires :public Field_timestamp_with_dec {
+public:
+ Field_timestamp_hires(uchar *ptr_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg,
+ const char *field_name_arg,
+ TABLE_SHARE *share, uint dec_arg) :
+ Field_timestamp_with_dec(ptr_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, share, dec_arg)
+ {
+ DBUG_ASSERT(dec);
+ }
+ my_time_t get_timestamp(ulong *sec_part) const;
+ void store_TIME(my_time_t timestamp, ulong sec_part);
+ int cmp(const uchar *,const uchar *);
+ uint32 pack_length() const;
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+/**
+ TIMESTAMP(0..6) - MySQL56 version
+*/
+class Field_timestampf :public Field_timestamp_with_dec {
+ int do_save_field_metadata(uchar *metadata_ptr)
+ {
+ *metadata_ptr= decimals();
+ return 1;
+ }
+public:
+ Field_timestampf(uchar *ptr_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg,
+ const char *field_name_arg,
+ TABLE_SHARE *share, uint dec_arg) :
+ Field_timestamp_with_dec(ptr_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, share, dec_arg)
+ {}
+ enum_field_types real_type() const { return MYSQL_TYPE_TIMESTAMP2; }
+ enum_field_types binlog_type() const { return MYSQL_TYPE_TIMESTAMP2; }
+ uint32 pack_length() const
+ {
+ return my_timestamp_binary_length(dec);
+ }
+ uint row_pack_length() const { return pack_length(); }
+ uint pack_length_from_metadata(uint field_metadata)
+ {
+ DBUG_ENTER("Field_timestampf::pack_length_from_metadata");
+ uint tmp= my_timestamp_binary_length(field_metadata);
+ DBUG_RETURN(tmp);
+ }
+ int cmp(const uchar *a_ptr,const uchar *b_ptr)
+ {
+ return memcmp(a_ptr, b_ptr, pack_length());
+ }
+ void store_TIME(my_time_t timestamp, ulong sec_part);
+ my_time_t get_timestamp(ulong *sec_part) const;
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+class Field_year :public Field_tiny {
+public:
+ Field_year(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg)
+ :Field_tiny(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, 1, 1)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_YEAR;}
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ bool send_binary(Protocol *protocol);
+ uint32 max_display_length() { return field_length; }
+ void sql_type(String &str) const;
+};
+
+
+class Field_date :public Field_temporal_with_date {
+ void store_TIME(MYSQL_TIME *ltime);
+public:
+ Field_date(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg)
+ :Field_temporal_with_date(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg) {}
+ enum_field_types type() const { return MYSQL_TYPE_DATE;}
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; }
+ int reset(void) { ptr[0]=ptr[1]=ptr[2]=ptr[3]=0; return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 4; }
+ void sql_type(String &str) const;
+ uchar *pack(uchar* to, const uchar *from,
+ uint max_length __attribute__((unused)))
+ {
+ return pack_int32(to, from);
+ }
+ const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data __attribute__((unused)))
+ {
+ return unpack_int32(to, from, from_end);
+ }
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+class Field_newdate :public Field_temporal_with_date {
+ void store_TIME(MYSQL_TIME *ltime);
+public:
+ Field_newdate(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg)
+ :Field_temporal_with_date(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_DATE;}
+ enum_field_types real_type() const { return MYSQL_TYPE_NEWDATE; }
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_UINT24; }
+ int reset(void) { ptr[0]=ptr[1]=ptr[2]=0; return 0; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 3; }
+ void sql_type(String &str) const;
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+class Field_time :public Field_temporal {
+ /*
+ when this Field_time instance is used for storing values for index lookups
+ (see class store_key, Field::new_key_field(), etc), the following
+ might be set to TO_DAYS(CURDATE()). See also Field_time::store_time_dec()
+ */
+ long curdays;
+protected:
+ virtual void store_TIME(MYSQL_TIME *ltime);
+ int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str,
+ int was_cut, int have_smth_to_conv);
+ bool check_zero_in_date_with_warn(ulonglong fuzzydate);
+public:
+ Field_time(uchar *ptr_arg, uint length_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, enum utype unireg_check_arg,
+ const char *field_name_arg)
+ :Field_temporal(ptr_arg, length_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg), curdays(0)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_TIME;}
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_INT24; }
+ int store_time_dec(MYSQL_TIME *ltime, uint dec);
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int store_decimal(const my_decimal *);
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 3; }
+ void sql_type(String &str) const;
+ uint size_of() const { return sizeof(*this); }
+ void set_curdays(THD *thd);
+ Field *new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit);
+};
+
+
+/**
+ Abstract class for:
+ - TIME(1..6)
+ - TIME(0..6) - MySQL56 version
+*/
+class Field_time_with_dec :public Field_time {
+protected:
+ uint dec;
+public:
+ Field_time_with_dec(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint dec_arg)
+ :Field_time(ptr_arg, MIN_TIME_WIDTH + dec_arg + MY_TEST(dec_arg),
+ null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg),
+ dec(dec_arg)
+ {
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ }
+ uint decimals() const { return dec; }
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; }
+ longlong val_int(void);
+ double val_real(void);
+ void make_field(Send_field *);
+};
+
+
+/**
+ TIME(1..6)
+*/
+class Field_time_hires :public Field_time_with_dec {
+ longlong zero_point;
+ void store_TIME(MYSQL_TIME *ltime);
+public:
+ Field_time_hires(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint dec_arg)
+ :Field_time_with_dec(ptr_arg, null_ptr_arg,
+ null_bit_arg, unireg_check_arg, field_name_arg,
+ dec_arg)
+ {
+ DBUG_ASSERT(dec);
+ zero_point= sec_part_shift(
+ ((TIME_MAX_VALUE_SECONDS+1LL)*TIME_SECOND_PART_FACTOR), dec);
+ }
+ int reset(void);
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const;
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+/**
+ TIME(0..6) - MySQL56 version
+*/
+class Field_timef :public Field_time_with_dec {
+ void store_TIME(MYSQL_TIME *ltime);
+ int do_save_field_metadata(uchar *metadata_ptr)
+ {
+ *metadata_ptr= decimals();
+ return 1;
+ }
+public:
+ Field_timef(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint dec_arg)
+ :Field_time_with_dec(ptr_arg, null_ptr_arg,
+ null_bit_arg, unireg_check_arg, field_name_arg,
+ dec_arg)
+ {
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ }
+ enum_field_types real_type() const { return MYSQL_TYPE_TIME2; }
+ enum_field_types binlog_type() const { return MYSQL_TYPE_TIME2; }
+ uint32 pack_length() const
+ {
+ return my_time_binary_length(dec);
+ }
+ uint row_pack_length() const { return pack_length(); }
+ uint pack_length_from_metadata(uint field_metadata)
+ {
+ DBUG_ENTER("Field_timef::pack_length_from_metadata");
+ uint tmp= my_time_binary_length(field_metadata);
+ DBUG_RETURN(tmp);
+ }
+ void sort_string(uchar *to, uint length)
+ {
+ DBUG_ASSERT(length == Field_timef::pack_length());
+ memcpy(to, ptr, length);
+ }
+ int cmp(const uchar *a_ptr, const uchar *b_ptr)
+ {
+ return memcmp(a_ptr, b_ptr, pack_length());
+ }
+ int reset();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+class Field_datetime :public Field_temporal_with_date {
+ void store_TIME(MYSQL_TIME *ltime);
+public:
+ Field_datetime(uchar *ptr_arg, uint length_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, enum utype unireg_check_arg,
+ const char *field_name_arg)
+ :Field_temporal_with_date(ptr_arg, length_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg)
+ {}
+ enum_field_types type() const { return MYSQL_TYPE_DATETIME;}
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONGLONG; }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ bool send_binary(Protocol *protocol);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return 8; }
+ void sql_type(String &str) const;
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ virtual int set_time();
+ virtual void set_default()
+ {
+ if (has_insert_default_function())
+ set_time();
+ else
+ Field::set_default();
+ }
+ virtual int evaluate_insert_default_function()
+ {
+ int res= 0;
+ if (has_insert_default_function())
+ res= set_time();
+ return res;
+ }
+ virtual int evaluate_update_default_function()
+ {
+ int res= 0;
+ if (has_update_default_function())
+ res= set_time();
+ return res;
+ }
+ uchar *pack(uchar* to, const uchar *from,
+ uint max_length __attribute__((unused)))
+ {
+ return pack_int64(to, from);
+ }
+ const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data __attribute__((unused)))
+ {
+ return unpack_int64(to, from, from_end);
+ }
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+/**
+ Abstract class for:
+ - DATETIME(1..6)
+ - DATETIME(0..6) - MySQL56 version
+*/
+class Field_datetime_with_dec :public Field_datetime {
+protected:
+ uint dec;
+public:
+ Field_datetime_with_dec(uchar *ptr_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, enum utype unireg_check_arg,
+ const char *field_name_arg, uint dec_arg)
+ :Field_datetime(ptr_arg, MAX_DATETIME_WIDTH + dec_arg + MY_TEST(dec_arg),
+ null_ptr_arg, null_bit_arg, unireg_check_arg,
+ field_name_arg), dec(dec_arg)
+ {
+ DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
+ }
+ uint decimals() const { return dec; }
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; }
+ void make_field(Send_field *field);
+ bool send_binary(Protocol *protocol);
+ uchar *pack(uchar *to, const uchar *from, uint max_length)
+ { return Field::pack(to, from, max_length); }
+ const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
+ uint param_data)
+ { return Field::unpack(to, from, from_end, param_data); }
+ void sort_string(uchar *to, uint length)
+ {
+ DBUG_ASSERT(length == pack_length());
+ memcpy(to, ptr, length);
+ }
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+};
+
+
+/**
+ DATETIME(1..6)
+*/
+class Field_datetime_hires :public Field_datetime_with_dec {
+ void store_TIME(MYSQL_TIME *ltime);
+public:
+ Field_datetime_hires(uchar *ptr_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, enum utype unireg_check_arg,
+ const char *field_name_arg, uint dec_arg)
+ :Field_datetime_with_dec(ptr_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, dec_arg)
+ {
+ DBUG_ASSERT(dec);
+ }
+ int cmp(const uchar *,const uchar *);
+ uint32 pack_length() const;
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+/**
+ DATETIME(0..6) - MySQL56 version
+*/
+class Field_datetimef :public Field_datetime_with_dec {
+ void store_TIME(MYSQL_TIME *ltime);
+ int do_save_field_metadata(uchar *metadata_ptr)
+ {
+ *metadata_ptr= decimals();
+ return 1;
+ }
+public:
+ Field_datetimef(uchar *ptr_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, enum utype unireg_check_arg,
+ const char *field_name_arg, uint dec_arg)
+ :Field_datetime_with_dec(ptr_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, dec_arg)
+ {}
+ enum_field_types real_type() const { return MYSQL_TYPE_DATETIME2; }
+ enum_field_types binlog_type() const { return MYSQL_TYPE_DATETIME2; }
+ uint32 pack_length() const
+ {
+ return my_datetime_binary_length(dec);
+ }
+ uint row_pack_length() const { return pack_length(); }
+ uint pack_length_from_metadata(uint field_metadata)
+ {
+ DBUG_ENTER("Field_datetimef::pack_length_from_metadata");
+ uint tmp= my_datetime_binary_length(field_metadata);
+ DBUG_RETURN(tmp);
+ }
+ int cmp(const uchar *a_ptr, const uchar *b_ptr)
+ {
+ return memcmp(a_ptr, b_ptr, pack_length());
+ }
+ int reset();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ uint size_of() const { return sizeof(*this); }
+};
+
+
+static inline Field_timestamp *
+new_Field_timestamp(uchar *ptr, uchar *null_ptr, uchar null_bit,
+ enum Field::utype unireg_check, const char *field_name,
+ TABLE_SHARE *share, uint dec)
+{
+ if (dec==0)
+ return new Field_timestamp(ptr, MAX_DATETIME_WIDTH, null_ptr, null_bit,
+ unireg_check, field_name, share);
+ if (dec == NOT_FIXED_DEC)
+ dec= MAX_DATETIME_PRECISION;
+ return new Field_timestamp_hires(ptr, null_ptr, null_bit, unireg_check,
+ field_name, share, dec);
+}
+
+static inline Field_time *
+new_Field_time(uchar *ptr, uchar *null_ptr, uchar null_bit,
+ enum Field::utype unireg_check, const char *field_name,
+ uint dec)
+{
+ if (dec == 0)
+ return new Field_time(ptr, MIN_TIME_WIDTH, null_ptr, null_bit,
+ unireg_check, field_name);
+ if (dec == NOT_FIXED_DEC)
+ dec= MAX_DATETIME_PRECISION;
+ return new Field_time_hires(ptr, null_ptr, null_bit,
+ unireg_check, field_name, dec);
+}
+
+static inline Field_datetime *
+new_Field_datetime(uchar *ptr, uchar *null_ptr, uchar null_bit,
+ enum Field::utype unireg_check,
+ const char *field_name, uint dec)
+{
+ if (dec == 0)
+ return new Field_datetime(ptr, MAX_DATETIME_WIDTH, null_ptr, null_bit,
+ unireg_check, field_name);
+ if (dec == NOT_FIXED_DEC)
+ dec= MAX_DATETIME_PRECISION;
+ return new Field_datetime_hires(ptr, null_ptr, null_bit,
+ unireg_check, field_name, dec);
+}
+
+class Field_string :public Field_longstr {
+public:
+ bool can_alter_field_type;
+ Field_string(uchar *ptr_arg, uint32 len_arg,uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ CHARSET_INFO *cs)
+ :Field_longstr(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, cs),
+ can_alter_field_type(1) {};
+ Field_string(uint32 len_arg,bool maybe_null_arg, const char *field_name_arg,
+ CHARSET_INFO *cs)
+ :Field_longstr((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "": 0, 0,
+ NONE, field_name_arg, cs),
+ can_alter_field_type(1) {};
+
+ enum_field_types type() const
+ {
+ return ((can_alter_field_type && orig_table &&
+ orig_table->s->db_create_options & HA_OPTION_PACK_RECORD &&
+ field_length >= 4) &&
+ orig_table->s->frm_version < FRM_VER_TRUE_VARCHAR ?
+ MYSQL_TYPE_VAR_STRING : MYSQL_TYPE_STRING);
+ }
+ enum ha_base_keytype key_type() const
+ { return binary() ? HA_KEYTYPE_BINARY : HA_KEYTYPE_TEXT; }
+ bool zero_pack() const { return 0; }
+ int reset(void)
+ {
+ charset()->cset->fill(charset(),(char*) ptr, field_length,
+ (has_charset() ? ' ' : 0));
+ return 0;
+ }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(longlong nr, bool unsigned_val);
+ int store(double nr) { return Field_str::store(nr); } /* QQ: To be deleted */
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ my_decimal *val_decimal(my_decimal *);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ void sql_type(String &str) const;
+ virtual uchar *pack(uchar *to, const uchar *from,
+ uint max_length);
+ virtual const uchar *unpack(uchar* to, const uchar *from,
+ const uchar *from_end,uint param_data);
+ uint pack_length_from_metadata(uint field_metadata)
+ {
+ DBUG_PRINT("debug", ("field_metadata: 0x%04x", field_metadata));
+ if (field_metadata == 0)
+ return row_pack_length();
+ return (((field_metadata >> 4) & 0x300) ^ 0x300) + (field_metadata & 0x00ff);
+ }
+ bool compatible_field_size(uint field_metadata, Relay_log_info *rli,
+ uint16 mflags, int *order_var);
+ uint row_pack_length() const { return field_length; }
+ int pack_cmp(const uchar *a,const uchar *b,uint key_length,
+ bool insert_or_update);
+ int pack_cmp(const uchar *b,uint key_length,bool insert_or_update);
+ uint packed_col_length(const uchar *to, uint length);
+ uint max_packed_col_length(uint max_length);
+ uint size_of() const { return sizeof(*this); }
+ enum_field_types real_type() const { return MYSQL_TYPE_STRING; }
+ bool has_charset(void) const
+ { return charset() == &my_charset_bin ? FALSE : TRUE; }
+ Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type);
+ virtual uint get_key_image(uchar *buff,uint length, imagetype type);
+private:
+ int do_save_field_metadata(uchar *first_byte);
+};
+
+
+class Field_varstring :public Field_longstr {
+public:
+ /*
+ The maximum space available in a Field_varstring, in bytes. See
+ length_bytes.
+ */
+ static const uint MAX_SIZE;
+ /* Store number of bytes used to store length (1 or 2) */
+ uint32 length_bytes;
+ Field_varstring(uchar *ptr_arg,
+ uint32 len_arg, uint length_bytes_arg,
+ uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ TABLE_SHARE *share, CHARSET_INFO *cs)
+ :Field_longstr(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, cs),
+ length_bytes(length_bytes_arg)
+ {
+ share->varchar_fields++;
+ }
+ Field_varstring(uint32 len_arg,bool maybe_null_arg,
+ const char *field_name_arg,
+ TABLE_SHARE *share, CHARSET_INFO *cs)
+ :Field_longstr((uchar*) 0,len_arg, maybe_null_arg ? (uchar*) "": 0, 0,
+ NONE, field_name_arg, cs),
+ length_bytes(len_arg < 256 ? 1 :2)
+ {
+ share->varchar_fields++;
+ }
+
+ enum_field_types type() const { return MYSQL_TYPE_VARCHAR; }
+ enum ha_base_keytype key_type() const;
+ uint row_pack_length() const { return field_length; }
+ bool zero_pack() const { return 0; }
+ int reset(void) { bzero(ptr,field_length+length_bytes); return 0; }
+ uint32 pack_length() const { return (uint32) field_length+length_bytes; }
+ uint32 key_length() const { return (uint32) field_length; }
+ uint32 sort_length() const
+ {
+ return (uint32) field_length + (field_charset == &my_charset_bin ?
+ length_bytes : 0);
+ }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(longlong nr, bool unsigned_val);
+ int store(double nr) { return Field_str::store(nr); } /* QQ: To be deleted */
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ my_decimal *val_decimal(my_decimal *);
+ int cmp_max(const uchar *, const uchar *, uint max_length);
+ int cmp(const uchar *a,const uchar *b)
+ {
+ return cmp_max(a, b, ~0L);
+ }
+ void sort_string(uchar *buff,uint length);
+ uint get_key_image(uchar *buff,uint length, imagetype type);
+ void set_key_image(const uchar *buff,uint length);
+ void sql_type(String &str) const;
+ virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
+ virtual const uchar *unpack(uchar* to, const uchar *from,
+ const uchar *from_end, uint param_data);
+ int cmp_binary(const uchar *a,const uchar *b, uint32 max_length=~0L);
+ int key_cmp(const uchar *,const uchar*);
+ int key_cmp(const uchar *str, uint length);
+ uint packed_col_length(const uchar *to, uint length);
+ uint max_packed_col_length(uint max_length);
+ uint32 data_length();
+ uint size_of() const { return sizeof(*this); }
+ enum_field_types real_type() const { return MYSQL_TYPE_VARCHAR; }
+ bool has_charset(void) const
+ { return charset() == &my_charset_bin ? FALSE : TRUE; }
+ Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type);
+ Field *new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit);
+ uint is_equal(Create_field *new_field);
+ void hash(ulong *nr, ulong *nr2);
+ uint length_size() { return length_bytes; }
+private:
+ int do_save_field_metadata(uchar *first_byte);
+};
+
+
+class Field_blob :public Field_longstr {
+protected:
+ /**
+ The number of bytes used to represent the length of the blob.
+ */
+ uint packlength;
+
+ /**
+ The 'value'-object is a cache fronting the storage engine.
+ */
+ String value;
+
+public:
+ Field_blob(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ TABLE_SHARE *share, uint blob_pack_length, CHARSET_INFO *cs);
+ Field_blob(uint32 len_arg,bool maybe_null_arg, const char *field_name_arg,
+ CHARSET_INFO *cs)
+ :Field_longstr((uchar*) 0, len_arg, maybe_null_arg ? (uchar*) "": 0, 0,
+ NONE, field_name_arg, cs),
+ packlength(4)
+ {
+ flags|= BLOB_FLAG;
+ }
+ Field_blob(uint32 len_arg,bool maybe_null_arg, const char *field_name_arg,
+ CHARSET_INFO *cs, bool set_packlength)
+ :Field_longstr((uchar*) 0,len_arg, maybe_null_arg ? (uchar*) "": 0, 0,
+ NONE, field_name_arg, cs)
+ {
+ flags|= BLOB_FLAG;
+ packlength= 4;
+ if (set_packlength)
+ {
+ uint32 l_char_length= len_arg/cs->mbmaxlen;
+ packlength= l_char_length <= 255 ? 1 :
+ l_char_length <= 65535 ? 2 :
+ l_char_length <= 16777215 ? 3 : 4;
+ }
+ }
+ Field_blob(uint32 packlength_arg)
+ :Field_longstr((uchar*) 0, 0, (uchar*) "", 0, NONE, "temp", system_charset_info),
+ packlength(packlength_arg) {}
+ /* Note that the default copy constructor is used, in clone() */
+ enum_field_types type() const { return MYSQL_TYPE_BLOB;}
+ enum ha_base_keytype key_type() const
+ { return binary() ? HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2; }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ my_decimal *val_decimal(my_decimal *);
+ int cmp_max(const uchar *, const uchar *, uint max_length);
+ int cmp(const uchar *a,const uchar *b)
+ { return cmp_max(a, b, ~0L); }
+ int cmp(const uchar *a, uint32 a_length, const uchar *b, uint32 b_length);
+ int cmp_binary(const uchar *a,const uchar *b, uint32 max_length=~0L);
+ int key_cmp(const uchar *,const uchar*);
+ int key_cmp(const uchar *str, uint length);
+ /* Never update the value of min_val for a blob field */
+ bool update_min(Field *min_val, bool force_update) { return FALSE; }
+ /* Never update the value of max_val for a blob field */
+ bool update_max(Field *max_val, bool force_update) { return FALSE; }
+ uint32 key_length() const { return 0; }
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const
+ { return (uint32) (packlength + portable_sizeof_char_ptr); }
+
+ /**
+ Return the packed length without the pointer size added.
+
+ This is used to determine the size of the actual data in the row
+ buffer.
+
+ @returns The length of the raw data itself without the pointer.
+ */
+ uint32 pack_length_no_ptr() const
+ { return (uint32) (packlength); }
+ uint row_pack_length() const { return pack_length_no_ptr(); }
+ uint32 sort_length() const;
+ uint32 value_length() { return get_length(); }
+ virtual uint32 max_data_length() const
+ {
+ return (uint32) (((ulonglong) 1 << (packlength*8)) -1);
+ }
+ int reset(void) { bzero(ptr, packlength+sizeof(uchar*)); return 0; }
+ void reset_fields() { bzero((uchar*) &value,sizeof(value)); }
+ uint32 get_field_buffer_size(void) { return value.alloced_length(); }
+ void store_length(uchar *i_ptr, uint i_packlength, uint32 i_number);
+ inline void store_length(uint32 number)
+ {
+ store_length(ptr, packlength, number);
+ }
+ inline uint32 get_length(uint row_offset= 0)
+ { return get_length(ptr+row_offset, this->packlength); }
+ uint32 get_length(const uchar *ptr, uint packlength);
+ uint32 get_length(const uchar *ptr_arg)
+ { return get_length(ptr_arg, this->packlength); }
+ inline void get_ptr(uchar **str)
+ {
+ memcpy(str, ptr+packlength, sizeof(uchar*));
+ }
+ inline void get_ptr(uchar **str, uint row_offset)
+ {
+ memcpy(str, ptr+packlength+row_offset, sizeof(char*));
+ }
+ inline void set_ptr(uchar *length, uchar *data)
+ {
+ memcpy(ptr,length,packlength);
+ memcpy(ptr+packlength, &data,sizeof(char*));
+ }
+ void set_ptr_offset(my_ptrdiff_t ptr_diff, uint32 length, uchar *data)
+ {
+ uchar *ptr_ofs= ADD_TO_PTR(ptr,ptr_diff,uchar*);
+ store_length(ptr_ofs, packlength, length);
+ memcpy(ptr_ofs+packlength, &data, sizeof(char*));
+ }
+ inline void set_ptr(uint32 length, uchar *data)
+ {
+ set_ptr_offset(0, length, data);
+ }
+ uint get_key_image(uchar *buff,uint length, imagetype type);
+ void set_key_image(const uchar *buff,uint length);
+ Field *new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit);
+ void sql_type(String &str) const;
+ inline bool copy()
+ {
+ uchar *tmp;
+ get_ptr(&tmp);
+ if (value.copy((char*) tmp, get_length(), charset()))
+ {
+ Field_blob::reset();
+ return 1;
+ }
+ tmp=(uchar*) value.ptr();
+ memcpy(ptr+packlength, &tmp, sizeof(char*));
+ return 0;
+ }
+ virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
+ virtual const uchar *unpack(uchar *to, const uchar *from,
+ const uchar *from_end, uint param_data);
+ uint packed_col_length(const uchar *col_ptr, uint length);
+ uint max_packed_col_length(uint max_length);
+ void free() { value.free(); }
+ inline void clear_temporary() { bzero((uchar*) &value,sizeof(value)); }
+ friend int field_conv_incompatible(Field *to,Field *from);
+ uint size_of() const { return sizeof(*this); }
+ bool has_charset(void) const
+ { return charset() == &my_charset_bin ? FALSE : TRUE; }
+ uint32 max_display_length();
+ uint32 char_length();
+ uint is_equal(Create_field *new_field);
+ inline bool in_read_set() { return bitmap_is_set(table->read_set, field_index); }
+ inline bool in_write_set() { return bitmap_is_set(table->write_set, field_index); }
+private:
+ int do_save_field_metadata(uchar *first_byte);
+};
+
+
+#ifdef HAVE_SPATIAL
+class Field_geom :public Field_blob {
+public:
+ enum geometry_type geom_type;
+
+ Field_geom(uchar *ptr_arg, uchar *null_ptr_arg, uint null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ TABLE_SHARE *share, uint blob_pack_length,
+ enum geometry_type geom_type_arg)
+ :Field_blob(ptr_arg, null_ptr_arg, null_bit_arg, unireg_check_arg,
+ field_name_arg, share, blob_pack_length, &my_charset_bin)
+ { geom_type= geom_type_arg; }
+ Field_geom(uint32 len_arg,bool maybe_null_arg, const char *field_name_arg,
+ TABLE_SHARE *share, enum geometry_type geom_type_arg)
+ :Field_blob(len_arg, maybe_null_arg, field_name_arg, &my_charset_bin)
+ { geom_type= geom_type_arg; }
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_VARBINARY2; }
+ enum_field_types type() const { return MYSQL_TYPE_GEOMETRY; }
+ bool match_collation_to_optimize_range() const { return false; }
+ void sql_type(String &str) const;
+ int store(const char *to, uint length, CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int store_decimal(const my_decimal *);
+ uint size_of() const { return sizeof(*this); }
+ /**
+ Key length is provided only to support hash joins. (compared byte for byte)
+ Ex: SELECT .. FROM t1,t2 WHERE t1.field_geom1=t2.field_geom2.
+
+ The comparison is not very relevant, as identical geometry might be
+ represented differently, but we need to support it either way.
+ */
+ uint32 key_length() const { return packlength; }
+
+ /**
+ Non-nullable GEOMETRY types cannot have defaults,
+ but the underlying blob must still be reset.
+ */
+ int reset(void) { return Field_blob::reset() || !maybe_null(); }
+
+ geometry_type get_geometry_type() { return geom_type; };
+};
+#endif /*HAVE_SPATIAL*/
+
+
+class Field_enum :public Field_str {
+protected:
+ uint packlength;
+public:
+ TYPELIB *typelib;
+ Field_enum(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint packlength_arg,
+ TYPELIB *typelib_arg,
+ CHARSET_INFO *charset_arg)
+ :Field_str(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg, charset_arg),
+ packlength(packlength_arg),typelib(typelib_arg)
+ {
+ flags|=ENUM_FLAG;
+ }
+ Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type);
+ enum_field_types type() const { return MYSQL_TYPE_STRING; }
+ enum Item_result cmp_type () const { return INT_RESULT; }
+ enum ha_base_keytype key_type() const;
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*,String *);
+ int cmp(const uchar *,const uchar *);
+ void sort_string(uchar *buff,uint length);
+ uint32 pack_length() const { return (uint32) packlength; }
+ void store_type(ulonglong value);
+ void sql_type(String &str) const;
+ uint size_of() const { return sizeof(*this); }
+ enum_field_types real_type() const { return MYSQL_TYPE_ENUM; }
+ uint pack_length_from_metadata(uint field_metadata)
+ { return (field_metadata & 0x00ff); }
+ uint row_pack_length() const { return pack_length(); }
+ virtual bool zero_pack() const { return 0; }
+ bool optimize_range(uint idx, uint part) { return 0; }
+ bool eq_def(Field *field);
+ bool has_charset(void) const { return TRUE; }
+ /* enum and set are sorted as integers */
+ CHARSET_INFO *sort_charset(void) const { return &my_charset_bin; }
+ uint decimals() const { return 0; }
+
+ virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
+ virtual const uchar *unpack(uchar *to, const uchar *from,
+ const uchar *from_end, uint param_data);
+
+private:
+ int do_save_field_metadata(uchar *first_byte);
+ uint is_equal(Create_field *new_field);
+};
+
+
+class Field_set :public Field_enum {
+public:
+ Field_set(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg,
+ uint32 packlength_arg,
+ TYPELIB *typelib_arg, CHARSET_INFO *charset_arg)
+ :Field_enum(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
+ unireg_check_arg, field_name_arg,
+ packlength_arg,
+ typelib_arg,charset_arg),
+ empty_set_string("", 0, charset_arg)
+ {
+ flags=(flags & ~ENUM_FLAG) | SET_FLAG;
+ }
+ int store(const char *to,uint length,CHARSET_INFO *charset);
+ int store(double nr) { return Field_set::store((longlong) nr, FALSE); }
+ int store(longlong nr, bool unsigned_val);
+
+ virtual bool zero_pack() const { return 1; }
+ String *val_str(String*,String *);
+ void sql_type(String &str) const;
+ uint size_of() const { return sizeof(*this); }
+ enum_field_types real_type() const { return MYSQL_TYPE_SET; }
+ bool has_charset(void) const { return TRUE; }
+private:
+ const String empty_set_string;
+};
+
+
+/*
+ Note:
+ To use Field_bit::cmp_binary() you need to copy the bits stored in
+ the beginning of the record (the NULL bytes) to each memory you
+ want to compare (where the arguments point).
+
+ This is the reason:
+ - Field_bit::cmp_binary() is only implemented in the base class
+ (Field::cmp_binary()).
+ - Field::cmp_binary() currenly use pack_length() to calculate how
+ long the data is.
+ - pack_length() includes size of the bits stored in the NULL bytes
+ of the record.
+*/
+class Field_bit :public Field {
+public:
+ uchar *bit_ptr; // position in record where 'uneven' bits store
+ uchar bit_ofs; // offset to 'uneven' high bits
+ uint bit_len; // number of 'uneven' high bits
+ uint bytes_in_rec;
+ Field_bit(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg, uchar *bit_ptr_arg, uchar bit_ofs_arg,
+ enum utype unireg_check_arg, const char *field_name_arg);
+ enum_field_types type() const { return MYSQL_TYPE_BIT; }
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_BIT; }
+ uint32 key_length() const { return (uint32) (field_length + 7) / 8; }
+ uint32 max_data_length() const { return (field_length + 7) / 8; }
+ uint32 max_display_length() { return field_length; }
+ uint size_of() const { return sizeof(*this); }
+ Item_result result_type () const { return INT_RESULT; }
+ int reset(void) {
+ bzero(ptr, bytes_in_rec);
+ if (bit_ptr && (bit_len > 0)) // reset odd bits among null bits
+ clr_rec_bits(bit_ptr, bit_ofs, bit_len);
+ return 0;
+ }
+ int store(const char *to, uint length, CHARSET_INFO *charset);
+ int store(double nr);
+ int store(longlong nr, bool unsigned_val);
+ int store_decimal(const my_decimal *);
+ double val_real(void);
+ longlong val_int(void);
+ String *val_str(String*, String *);
+ virtual bool str_needs_quotes() { return TRUE; }
+ my_decimal *val_decimal(my_decimal *);
+ int cmp(const uchar *a, const uchar *b)
+ {
+ DBUG_ASSERT(ptr == a || ptr == b);
+ if (ptr == a)
+ return Field_bit::key_cmp(b, bytes_in_rec + MY_TEST(bit_len));
+ else
+ return Field_bit::key_cmp(a, bytes_in_rec + MY_TEST(bit_len)) * -1;
+ }
+ int cmp_binary_offset(uint row_offset)
+ { return cmp_offset(row_offset); }
+ int cmp_max(const uchar *a, const uchar *b, uint max_length);
+ int key_cmp(const uchar *a, const uchar *b)
+ { return cmp_binary((uchar *) a, (uchar *) b); }
+ int key_cmp(const uchar *str, uint length);
+ int cmp_offset(uint row_offset);
+ bool update_min(Field *min_val, bool force_update)
+ {
+ longlong val= val_int();
+ bool update_fl= force_update || val < min_val->val_int();
+ if (update_fl)
+ {
+ min_val->set_notnull();
+ min_val->store(val, FALSE);
+ }
+ return update_fl;
+ }
+ bool update_max(Field *max_val, bool force_update)
+ {
+ longlong val= val_int();
+ bool update_fl= force_update || val > max_val->val_int();
+ if (update_fl)
+ {
+ max_val->set_notnull();
+ max_val->store(val, FALSE);
+ }
+ return update_fl;
+ }
+ void store_field_value(uchar *val, uint len)
+ {
+ store(*((longlong *)val), TRUE);
+ }
+ double pos_in_interval(Field *min, Field *max)
+ {
+ return pos_in_interval_val_real(min, max);
+ }
+ void get_image(uchar *buff, uint length, CHARSET_INFO *cs)
+ { get_key_image(buff, length, itRAW); }
+ void set_image(const uchar *buff,uint length, CHARSET_INFO *cs)
+ { Field_bit::store((char *) buff, length, cs); }
+ uint get_key_image(uchar *buff, uint length, imagetype type);
+ void set_key_image(const uchar *buff, uint length)
+ { Field_bit::store((char*) buff, length, &my_charset_bin); }
+ void sort_string(uchar *buff, uint length)
+ { get_key_image(buff, length, itRAW); }
+ uint32 pack_length() const { return (uint32) (field_length + 7) / 8; }
+ uint32 pack_length_in_rec() const { return bytes_in_rec; }
+ uint pack_length_from_metadata(uint field_metadata);
+ uint row_pack_length() const
+ { return (bytes_in_rec + ((bit_len > 0) ? 1 : 0)); }
+ bool compatible_field_size(uint metadata, Relay_log_info *rli,
+ uint16 mflags, int *order_var);
+ void sql_type(String &str) const;
+ virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
+ virtual const uchar *unpack(uchar *to, const uchar *from,
+ const uchar *from_end, uint param_data);
+ virtual void set_default();
+
+ Field *new_key_field(MEM_ROOT *root, TABLE *new_table,
+ uchar *new_ptr, uint32 length,
+ uchar *new_null_ptr, uint new_null_bit);
+ void set_bit_ptr(uchar *bit_ptr_arg, uchar bit_ofs_arg)
+ {
+ bit_ptr= bit_ptr_arg;
+ bit_ofs= bit_ofs_arg;
+ }
+ bool eq(Field *field)
+ {
+ return (Field::eq(field) &&
+ bit_ptr == ((Field_bit *)field)->bit_ptr &&
+ bit_ofs == ((Field_bit *)field)->bit_ofs);
+ }
+ uint is_equal(Create_field *new_field);
+ void move_field_offset(my_ptrdiff_t ptr_diff)
+ {
+ Field::move_field_offset(ptr_diff);
+ bit_ptr= ADD_TO_PTR(bit_ptr, ptr_diff, uchar*);
+ }
+ void hash(ulong *nr, ulong *nr2);
+
+private:
+ virtual size_t do_last_null_byte() const;
+ int do_save_field_metadata(uchar *first_byte);
+};
+
+
+/**
+ BIT field represented as chars for non-MyISAM tables.
+
+ @todo The inheritance relationship is backwards since Field_bit is
+ an extended version of Field_bit_as_char and not the other way
+ around. Hence, we should refactor it to fix the hierarchy order.
+ */
+class Field_bit_as_char: public Field_bit {
+public:
+ Field_bit_as_char(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
+ uchar null_bit_arg,
+ enum utype unireg_check_arg, const char *field_name_arg);
+ enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; }
+ uint size_of() const { return sizeof(*this); }
+ int store(const char *to, uint length, CHARSET_INFO *charset);
+ int store(double nr) { return Field_bit::store(nr); }
+ int store(longlong nr, bool unsigned_val)
+ { return Field_bit::store(nr, unsigned_val); }
+ void sql_type(String &str) const;
+};
+
+
+/*
+ Create field class for CREATE TABLE
+*/
+
+class Create_field :public Sql_alloc
+{
+public:
+ const char *field_name;
+ const char *change; // If done with alter table
+ const char *after; // Put column after this one
+ LEX_STRING comment; // Comment for field
+ Item *def; // Default value
+ enum enum_field_types sql_type;
+ /*
+ At various stages in execution this can be length of field in bytes or
+ max number of characters.
+ */
+ ulong length;
+ /*
+ The value of `length' as set by parser: is the number of characters
+ for most of the types, or of bytes for BLOBs or numeric types.
+ */
+ uint32 char_length;
+ uint decimals, flags, pack_length, key_length;
+ Field::utype unireg_check;
+ TYPELIB *interval; // Which interval to use
+ TYPELIB *save_interval; // Temporary copy for the above
+ // Used only for UCS2 intervals
+ List<String> interval_list;
+ CHARSET_INFO *charset;
+ Field::geometry_type geom_type;
+ Field *field; // For alter table
+ engine_option_value *option_list;
+ /** structure with parsed options (for comparing fields in ALTER TABLE) */
+ ha_field_option_struct *option_struct;
+
+ uint8 interval_id; // For rea_create_table
+ uint offset,pack_flag;
+ bool create_if_not_exists; // Used in ALTER TABLE IF NOT EXISTS
+
+ /*
+ This is additinal data provided for any computed(virtual) field.
+ In particular it includes a pointer to the item by which this field
+ can be computed from other fields.
+ */
+ Virtual_column_info *vcol_info;
+ /*
+ Flag indicating that the field is physically stored in tables
+ rather than just computed from other fields.
+ As of now, FALSE can be set only for computed virtual columns.
+ */
+ bool stored_in_db;
+
+ Create_field() :after(0), option_list(NULL), option_struct(NULL),
+ create_if_not_exists(FALSE)
+ {}
+ Create_field(Field *field, Field *orig_field);
+ /* Used to make a clone of this object for ALTER/CREATE TABLE */
+ Create_field *clone(MEM_ROOT *mem_root) const;
+ void create_length_to_internal_length(void);
+
+ /* Init for a tmp table field. To be extended if need be. */
+ void init_for_tmp_table(enum_field_types sql_type_arg,
+ uint32 max_length, uint32 decimals,
+ bool maybe_null, bool is_unsigned,
+ uint pack_length = ~0U);
+
+ bool init(THD *thd, char *field_name, enum_field_types type, char *length,
+ char *decimals, uint type_modifier, Item *default_value,
+ Item *on_update_value, LEX_STRING *comment, char *change,
+ List<String> *interval_list, CHARSET_INFO *cs,
+ uint uint_geom_type, Virtual_column_info *vcol_info,
+ engine_option_value *option_list, bool check_exists);
+
+ bool field_flags_are_binary()
+ {
+ return (flags & (BINCMP_FLAG | BINARY_FLAG)) != 0;
+ }
+
+ ha_storage_media field_storage_type() const
+ {
+ return (ha_storage_media)
+ ((flags >> FIELD_FLAGS_STORAGE_MEDIA) & 3);
+ }
+
+ column_format_type column_format() const
+ {
+ return (column_format_type)
+ ((flags >> FIELD_FLAGS_COLUMN_FORMAT) & 3);
+ }
+
+ uint virtual_col_expr_maxlen()
+ {
+ return 255 - FRM_VCOL_HEADER_SIZE(interval != NULL);
+ }
+};
+
+
+/*
+ A class for sending info to the client
+*/
+
+class Send_field :public Sql_alloc {
+ public:
+ const char *db_name;
+ const char *table_name,*org_table_name;
+ const char *col_name,*org_col_name;
+ ulong length;
+ uint charsetnr, flags, decimals;
+ enum_field_types type;
+ Send_field() {}
+};
+
+
+/*
+ A class for quick copying data to fields
+*/
+
+class Copy_field :public Sql_alloc {
+ /**
+ Convenience definition of a copy function returned by
+ get_copy_func.
+ */
+ typedef void Copy_func(Copy_field*);
+ Copy_func *get_copy_func(Field *to, Field *from);
+public:
+ uchar *from_ptr,*to_ptr;
+ uchar *from_null_ptr,*to_null_ptr;
+ bool *null_row;
+ uint from_bit,to_bit;
+ /**
+ Number of bytes in the fields pointed to by 'from_ptr' and
+ 'to_ptr'. Usually this is the number of bytes that are copied from
+ 'from_ptr' to 'to_ptr'.
+
+ For variable-length fields (VARCHAR), the first byte(s) describe
+ the actual length of the text. For VARCHARs with length
+ < 256 there is 1 length byte
+ >= 256 there is 2 length bytes
+ Thus, if from_field is VARCHAR(10), from_length (and in most cases
+ to_length) is 11. For VARCHAR(1024), the length is 1026. @see
+ Field_varstring::length_bytes
+
+ Note that for VARCHARs, do_copy() will be do_varstring*() which
+ only copies the length-bytes (1 or 2) + the actual length of the
+ text instead of from/to_length bytes. @see get_copy_func()
+ */
+ uint from_length,to_length;
+ Field *from_field,*to_field;
+ String tmp; // For items
+
+ Copy_field() {}
+ ~Copy_field() {}
+ void set(Field *to,Field *from,bool save); // Field to field
+ void set(uchar *to,Field *from); // Field to string
+ void (*do_copy)(Copy_field *);
+ void (*do_copy2)(Copy_field *); // Used to handle null values
+};
+
+
+Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length,
+ uchar *null_pos, uchar null_bit,
+ uint pack_flag, enum_field_types field_type,
+ CHARSET_INFO *cs,
+ Field::geometry_type geom_type,
+ Field::utype unireg_check,
+ TYPELIB *interval, const char *field_name);
+uint pack_length_to_packflag(uint type);
+enum_field_types get_blob_type_from_length(ulong length);
+uint32 calc_pack_length(enum_field_types type,uint32 length);
+int set_field_to_null(Field *field);
+int set_field_to_null_with_conversions(Field *field, bool no_conversions);
+
+/*
+ The following are for the interface with the .frm file
+*/
+
+#define FIELDFLAG_DECIMAL 1
+#define FIELDFLAG_BINARY 1 // Shares same flag
+#define FIELDFLAG_NUMBER 2
+#define FIELDFLAG_ZEROFILL 4
+#define FIELDFLAG_PACK 120 // Bits used for packing
+#define FIELDFLAG_INTERVAL 256 // mangled with decimals!
+#define FIELDFLAG_BITFIELD 512 // mangled with decimals!
+#define FIELDFLAG_BLOB 1024 // mangled with decimals!
+#define FIELDFLAG_GEOM 2048 // mangled with decimals!
+
+#define FIELDFLAG_TREAT_BIT_AS_CHAR 4096 /* use Field_bit_as_char */
+
+#define FIELDFLAG_LEFT_FULLSCREEN 8192
+#define FIELDFLAG_RIGHT_FULLSCREEN 16384
+#define FIELDFLAG_FORMAT_NUMBER 16384 // predit: ###,,## in output
+#define FIELDFLAG_NO_DEFAULT 16384 /* sql */
+#define FIELDFLAG_SUM ((uint) 32768)// predit: +#fieldflag
+#define FIELDFLAG_MAYBE_NULL ((uint) 32768)// sql
+#define FIELDFLAG_HEX_ESCAPE ((uint) 0x10000)
+#define FIELDFLAG_PACK_SHIFT 3
+#define FIELDFLAG_DEC_SHIFT 8
+#define FIELDFLAG_MAX_DEC 31
+#define FIELDFLAG_NUM_SCREEN_TYPE 0x7F01
+#define FIELDFLAG_ALFA_SCREEN_TYPE 0x7800
+
+#define MTYP_TYPENR(type) (type & 127) /* Remove bits from type */
+
+#define f_is_dec(x) ((x) & FIELDFLAG_DECIMAL)
+#define f_is_num(x) ((x) & FIELDFLAG_NUMBER)
+#define f_is_zerofill(x) ((x) & FIELDFLAG_ZEROFILL)
+#define f_is_packed(x) ((x) & FIELDFLAG_PACK)
+#define f_packtype(x) (((x) >> FIELDFLAG_PACK_SHIFT) & 15)
+#define f_decimals(x) ((uint8) (((x) >> FIELDFLAG_DEC_SHIFT) & FIELDFLAG_MAX_DEC))
+#define f_is_alpha(x) (!f_is_num(x))
+#define f_is_binary(x) ((x) & FIELDFLAG_BINARY) // 4.0- compatibility
+#define f_is_enum(x) (((x) & (FIELDFLAG_INTERVAL | FIELDFLAG_NUMBER)) == FIELDFLAG_INTERVAL)
+#define f_is_bitfield(x) (((x) & (FIELDFLAG_BITFIELD | FIELDFLAG_NUMBER)) == FIELDFLAG_BITFIELD)
+#define f_is_blob(x) (((x) & (FIELDFLAG_BLOB | FIELDFLAG_NUMBER)) == FIELDFLAG_BLOB)
+#define f_is_geom(x) (((x) & (FIELDFLAG_GEOM | FIELDFLAG_NUMBER)) == FIELDFLAG_GEOM)
+#define f_is_equ(x) ((x) & (1+2+FIELDFLAG_PACK+31*256))
+#define f_settype(x) (((int) x) << FIELDFLAG_PACK_SHIFT)
+#define f_maybe_null(x) (x & FIELDFLAG_MAYBE_NULL)
+#define f_no_default(x) (x & FIELDFLAG_NO_DEFAULT)
+#define f_bit_as_char(x) ((x) & FIELDFLAG_TREAT_BIT_AS_CHAR)
+#define f_is_hex_escape(x) ((x) & FIELDFLAG_HEX_ESCAPE)
+
+#endif /* FIELD_INCLUDED */
diff --git a/sql/field_conv.cc b/sql/field_conv.cc
new file mode 100644
index 00000000000..e31f7c5f005
--- /dev/null
+++ b/sql/field_conv.cc
@@ -0,0 +1,960 @@
+/*
+ Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2012, Monty Program Ab
+
+
+ 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 */
+
+
+/**
+ @file
+
+ @brief
+ Functions to copy data to or from fields
+
+ This could be done with a single short function but opencoding this
+ gives much more speed.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_class.h" // THD
+#include <m_ctype.h>
+
+static void do_field_eq(Copy_field *copy)
+{
+ memcpy(copy->to_ptr,copy->from_ptr,copy->from_length);
+}
+
+static void do_field_1(Copy_field *copy)
+{
+ copy->to_ptr[0]=copy->from_ptr[0];
+}
+
+static void do_field_2(Copy_field *copy)
+{
+ copy->to_ptr[0]=copy->from_ptr[0];
+ copy->to_ptr[1]=copy->from_ptr[1];
+}
+
+static void do_field_3(Copy_field *copy)
+{
+ copy->to_ptr[0]=copy->from_ptr[0];
+ copy->to_ptr[1]=copy->from_ptr[1];
+ copy->to_ptr[2]=copy->from_ptr[2];
+}
+
+static void do_field_4(Copy_field *copy)
+{
+ copy->to_ptr[0]=copy->from_ptr[0];
+ copy->to_ptr[1]=copy->from_ptr[1];
+ copy->to_ptr[2]=copy->from_ptr[2];
+ copy->to_ptr[3]=copy->from_ptr[3];
+}
+
+static void do_field_6(Copy_field *copy)
+{ // For blob field
+ copy->to_ptr[0]=copy->from_ptr[0];
+ copy->to_ptr[1]=copy->from_ptr[1];
+ copy->to_ptr[2]=copy->from_ptr[2];
+ copy->to_ptr[3]=copy->from_ptr[3];
+ copy->to_ptr[4]=copy->from_ptr[4];
+ copy->to_ptr[5]=copy->from_ptr[5];
+}
+
+static void do_field_8(Copy_field *copy)
+{
+ copy->to_ptr[0]=copy->from_ptr[0];
+ copy->to_ptr[1]=copy->from_ptr[1];
+ copy->to_ptr[2]=copy->from_ptr[2];
+ copy->to_ptr[3]=copy->from_ptr[3];
+ copy->to_ptr[4]=copy->from_ptr[4];
+ copy->to_ptr[5]=copy->from_ptr[5];
+ copy->to_ptr[6]=copy->from_ptr[6];
+ copy->to_ptr[7]=copy->from_ptr[7];
+}
+
+
+static void do_field_to_null_str(Copy_field *copy)
+{
+ if (*copy->from_null_ptr & copy->from_bit)
+ {
+ bzero(copy->to_ptr,copy->from_length);
+ copy->to_null_ptr[0]=1; // Always bit 1
+ }
+ else
+ {
+ copy->to_null_ptr[0]=0;
+ memcpy(copy->to_ptr,copy->from_ptr,copy->from_length);
+ }
+}
+
+
+static void do_outer_field_to_null_str(Copy_field *copy)
+{
+ if (*copy->null_row ||
+ (copy->from_null_ptr && (*copy->from_null_ptr & copy->from_bit)))
+ {
+ bzero(copy->to_ptr,copy->from_length);
+ copy->to_null_ptr[0]=1; // Always bit 1
+ }
+ else
+ {
+ copy->to_null_ptr[0]=0;
+ memcpy(copy->to_ptr,copy->from_ptr,copy->from_length);
+ }
+}
+
+
+int
+set_field_to_null(Field *field)
+{
+ if (field->table->null_catch_flags & CHECK_ROW_FOR_NULLS_TO_REJECT)
+ {
+ field->table->null_catch_flags|= REJECT_ROW_DUE_TO_NULL_FIELDS;
+ return -1;
+ }
+ if (field->real_maybe_null())
+ {
+ field->set_null();
+ field->reset();
+ return 0;
+ }
+ field->reset();
+ switch (field->table->in_use->count_cuted_fields) {
+ case CHECK_FIELD_WARN:
+ field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
+ /* fall through */
+ case CHECK_FIELD_IGNORE:
+ return 0;
+ case CHECK_FIELD_ERROR_FOR_NULL:
+ if (!field->table->in_use->no_errors)
+ my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name);
+ return -1;
+ }
+ DBUG_ASSERT(0); // impossible
+ return -1;
+}
+
+
+/**
+ Set field to NULL or TIMESTAMP or to next auto_increment number.
+
+ @param field Field to update
+ @param no_conversions Set to 1 if we should return 1 if field can't
+ take null values.
+ If set to 0 we will do store the 'default value'
+ if the field is a special field. If not we will
+ give an error.
+
+ @retval
+ 0 Field could take 0 or an automatic conversion was used
+ @retval
+ -1 Field could not take NULL and no conversion was used.
+ If no_conversion was not set, an error message is printed
+*/
+
+int
+set_field_to_null_with_conversions(Field *field, bool no_conversions)
+{
+ if (field->table->null_catch_flags & CHECK_ROW_FOR_NULLS_TO_REJECT)
+ {
+ field->table->null_catch_flags|= REJECT_ROW_DUE_TO_NULL_FIELDS;
+ return -1;
+ }
+ if (field->real_maybe_null())
+ {
+ field->set_null();
+ field->reset();
+ return 0;
+ }
+ if (no_conversions)
+ return -1;
+
+ /*
+ Check if this is a special type, which will get a special walue
+ when set to NULL (TIMESTAMP fields which allow setting to NULL
+ are handled by first check).
+ */
+ if (field->type() == MYSQL_TYPE_TIMESTAMP)
+ {
+ ((Field_timestamp*) field)->set_time();
+ return 0; // Ok to set time to NULL
+ }
+
+ // Note: we ignore any potential failure of reset() here.
+ field->reset();
+
+ if (field == field->table->next_number_field)
+ {
+ field->table->auto_increment_field_not_null= FALSE;
+ return 0; // field is set in fill_record()
+ }
+ switch (field->table->in_use->count_cuted_fields) {
+ case CHECK_FIELD_WARN:
+ field->set_warning(Sql_condition::WARN_LEVEL_WARN, ER_BAD_NULL_ERROR, 1);
+ /* fall through */
+ case CHECK_FIELD_IGNORE:
+ return 0;
+ case CHECK_FIELD_ERROR_FOR_NULL:
+ if (!field->table->in_use->no_errors)
+ my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name);
+ return -1;
+ }
+ DBUG_ASSERT(0); // impossible
+ return -1;
+}
+
+
+static void do_skip(Copy_field *copy __attribute__((unused)))
+{
+}
+
+
+/*
+ Copy: (NULLable field) -> (NULLable field)
+
+ note: if the record we're copying from is NULL-complemetned (i.e.
+ from_field->table->null_row==1), it will also have all NULLable columns to be
+ set to NULLs, so we dont need to check table->null_row here.
+*/
+
+static void do_copy_null(Copy_field *copy)
+{
+ if (*copy->from_null_ptr & copy->from_bit)
+ {
+ *copy->to_null_ptr|=copy->to_bit;
+ copy->to_field->reset();
+ }
+ else
+ {
+ *copy->to_null_ptr&= ~copy->to_bit;
+ (copy->do_copy2)(copy);
+ }
+}
+
+/*
+ Copy: (not-NULL field in table that can be NULL-complemented) -> (NULLable
+ field)
+*/
+
+static void do_outer_field_null(Copy_field *copy)
+{
+ if (*copy->null_row ||
+ (copy->from_null_ptr && (*copy->from_null_ptr & copy->from_bit)))
+ {
+ *copy->to_null_ptr|=copy->to_bit;
+ copy->to_field->reset();
+ }
+ else
+ {
+ *copy->to_null_ptr&= ~copy->to_bit;
+ (copy->do_copy2)(copy);
+ }
+}
+
+/*
+ Copy: (not-NULL field in table that can be NULL-complemented) -> (not-NULL
+ field)
+*/
+static void do_copy_nullable_row_to_notnull(Copy_field *copy)
+{
+ if (*copy->null_row ||
+ (copy->from_null_ptr && (*copy->from_null_ptr & copy->from_bit)))
+ {
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ copy->to_field->reset();
+ }
+ else
+ {
+ (copy->do_copy2)(copy);
+ }
+
+}
+
+/* Copy: (NULL-able field) -> (not NULL-able field) */
+static void do_copy_not_null(Copy_field *copy)
+{
+ if (*copy->from_null_ptr & copy->from_bit)
+ {
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ copy->to_field->reset();
+ }
+ else
+ (copy->do_copy2)(copy);
+}
+
+
+/* Copy: (non-NULLable field) -> (NULLable field) */
+static void do_copy_maybe_null(Copy_field *copy)
+{
+ *copy->to_null_ptr&= ~copy->to_bit;
+ (copy->do_copy2)(copy);
+}
+
+/* timestamp and next_number has special handling in case of NULL values */
+
+static void do_copy_timestamp(Copy_field *copy)
+{
+ if (*copy->from_null_ptr & copy->from_bit)
+ {
+ /* Same as in set_field_to_null_with_conversions() */
+ ((Field_timestamp*) copy->to_field)->set_time();
+ }
+ else
+ (copy->do_copy2)(copy);
+}
+
+
+static void do_copy_next_number(Copy_field *copy)
+{
+ if (*copy->from_null_ptr & copy->from_bit)
+ {
+ /* Same as in set_field_to_null_with_conversions() */
+ copy->to_field->table->auto_increment_field_not_null= FALSE;
+ copy->to_field->reset();
+ }
+ else
+ (copy->do_copy2)(copy);
+}
+
+
+static void do_copy_blob(Copy_field *copy)
+{
+ ulong length=((Field_blob*) copy->from_field)->get_length();
+ ((Field_blob*) copy->to_field)->store_length(length);
+ memcpy(copy->to_ptr, copy->from_ptr, sizeof(char*));
+}
+
+static void do_conv_blob(Copy_field *copy)
+{
+ copy->from_field->val_str(&copy->tmp);
+ ((Field_blob *) copy->to_field)->store(copy->tmp.ptr(),
+ copy->tmp.length(),
+ copy->tmp.charset());
+}
+
+/** Save blob in copy->tmp for GROUP BY. */
+
+static void do_save_blob(Copy_field *copy)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String res(buff,sizeof(buff),copy->tmp.charset());
+ copy->from_field->val_str(&res);
+ copy->tmp.copy(res);
+ ((Field_blob *) copy->to_field)->store(copy->tmp.ptr(),
+ copy->tmp.length(),
+ copy->tmp.charset());
+}
+
+
+static void do_field_string(Copy_field *copy)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String res(buff, sizeof(buff), copy->from_field->charset());
+ res.length(0U);
+
+ copy->from_field->val_str(&res);
+ copy->to_field->store(res.c_ptr_quick(), res.length(), res.charset());
+}
+
+
+static void do_field_enum(Copy_field *copy)
+{
+ if (copy->from_field->val_int() == 0)
+ ((Field_enum *) copy->to_field)->store_type((ulonglong) 0);
+ else
+ do_field_string(copy);
+}
+
+
+static void do_field_varbinary_pre50(Copy_field *copy)
+{
+ char buff[MAX_FIELD_WIDTH];
+ copy->tmp.set_quick(buff,sizeof(buff),copy->tmp.charset());
+ copy->from_field->val_str(&copy->tmp);
+
+ /* Use the same function as in 4.1 to trim trailing spaces */
+ uint length= my_lengthsp_8bit(&my_charset_bin, copy->tmp.c_ptr_quick(),
+ copy->from_field->field_length);
+
+ copy->to_field->store(copy->tmp.c_ptr_quick(), length,
+ copy->tmp.charset());
+}
+
+
+static void do_field_int(Copy_field *copy)
+{
+ longlong value= copy->from_field->val_int();
+ copy->to_field->store(value,
+ MY_TEST(copy->from_field->flags & UNSIGNED_FLAG));
+}
+
+static void do_field_real(Copy_field *copy)
+{
+ double value=copy->from_field->val_real();
+ copy->to_field->store(value);
+}
+
+
+static void do_field_decimal(Copy_field *copy)
+{
+ my_decimal value;
+ copy->to_field->store_decimal(copy->from_field->val_decimal(&value));
+}
+
+
+static void do_field_temporal(Copy_field *copy)
+{
+ MYSQL_TIME ltime;
+ copy->from_field->get_date(&ltime, 0);
+ copy->to_field->store_time_dec(&ltime, copy->from_field->decimals());
+}
+
+
+/**
+ string copy for single byte characters set when to string is shorter than
+ from string.
+*/
+
+static void do_cut_string(Copy_field *copy)
+{
+ CHARSET_INFO *cs= copy->from_field->charset();
+ memcpy(copy->to_ptr,copy->from_ptr,copy->to_length);
+
+ /* Check if we loosed any important characters */
+ if (cs->cset->scan(cs,
+ (char*) copy->from_ptr + copy->to_length,
+ (char*) copy->from_ptr + copy->from_length,
+ MY_SEQ_SPACES) < copy->from_length - copy->to_length)
+ {
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ }
+}
+
+
+/**
+ string copy for multi byte characters set when to string is shorter than
+ from string.
+*/
+
+static void do_cut_string_complex(Copy_field *copy)
+{ // Shorter string field
+ int well_formed_error;
+ CHARSET_INFO *cs= copy->from_field->charset();
+ const uchar *from_end= copy->from_ptr + copy->from_length;
+ uint copy_length= cs->cset->well_formed_len(cs,
+ (char*) copy->from_ptr,
+ (char*) from_end,
+ copy->to_length / cs->mbmaxlen,
+ &well_formed_error);
+ if (copy->to_length < copy_length)
+ copy_length= copy->to_length;
+ memcpy(copy->to_ptr, copy->from_ptr, copy_length);
+
+ /* Check if we lost any important characters */
+ if (well_formed_error ||
+ cs->cset->scan(cs, (char*) copy->from_ptr + copy_length,
+ (char*) from_end,
+ MY_SEQ_SPACES) < (copy->from_length - copy_length))
+ {
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ }
+
+ if (copy_length < copy->to_length)
+ cs->cset->fill(cs, (char*) copy->to_ptr + copy_length,
+ copy->to_length - copy_length, ' ');
+}
+
+
+
+
+static void do_expand_binary(Copy_field *copy)
+{
+ CHARSET_INFO *cs= copy->from_field->charset();
+ memcpy(copy->to_ptr,copy->from_ptr,copy->from_length);
+ cs->cset->fill(cs, (char*) copy->to_ptr+copy->from_length,
+ copy->to_length-copy->from_length, '\0');
+}
+
+
+
+static void do_expand_string(Copy_field *copy)
+{
+ CHARSET_INFO *cs= copy->from_field->charset();
+ memcpy(copy->to_ptr,copy->from_ptr,copy->from_length);
+ cs->cset->fill(cs, (char*) copy->to_ptr+copy->from_length,
+ copy->to_length-copy->from_length, ' ');
+}
+
+
+static void do_varstring1(Copy_field *copy)
+{
+ uint length= (uint) *(uchar*) copy->from_ptr;
+ if (length > copy->to_length- 1)
+ {
+ length=copy->to_length - 1;
+ if (copy->from_field->table->in_use->count_cuted_fields &&
+ copy->to_field)
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ }
+ *(uchar*) copy->to_ptr= (uchar) length;
+ memcpy(copy->to_ptr+1, copy->from_ptr + 1, length);
+}
+
+
+static void do_varstring1_mb(Copy_field *copy)
+{
+ int well_formed_error;
+ CHARSET_INFO *cs= copy->from_field->charset();
+ uint from_length= (uint) *(uchar*) copy->from_ptr;
+ const uchar *from_ptr= copy->from_ptr + 1;
+ uint to_char_length= (copy->to_length - 1) / cs->mbmaxlen;
+ uint length= cs->cset->well_formed_len(cs, (char*) from_ptr,
+ (char*) from_ptr + from_length,
+ to_char_length, &well_formed_error);
+ if (length < from_length)
+ {
+ if (current_thd->count_cuted_fields)
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ }
+ *copy->to_ptr= (uchar) length;
+ memcpy(copy->to_ptr + 1, from_ptr, length);
+}
+
+
+static void do_varstring2(Copy_field *copy)
+{
+ uint length=uint2korr(copy->from_ptr);
+ if (length > copy->to_length- HA_KEY_BLOB_LENGTH)
+ {
+ length=copy->to_length-HA_KEY_BLOB_LENGTH;
+ if (copy->from_field->table->in_use->count_cuted_fields &&
+ copy->to_field)
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ }
+ int2store(copy->to_ptr,length);
+ memcpy(copy->to_ptr+HA_KEY_BLOB_LENGTH, copy->from_ptr + HA_KEY_BLOB_LENGTH,
+ length);
+}
+
+
+static void do_varstring2_mb(Copy_field *copy)
+{
+ int well_formed_error;
+ CHARSET_INFO *cs= copy->from_field->charset();
+ uint char_length= (copy->to_length - HA_KEY_BLOB_LENGTH) / cs->mbmaxlen;
+ uint from_length= uint2korr(copy->from_ptr);
+ const uchar *from_beg= copy->from_ptr + HA_KEY_BLOB_LENGTH;
+ uint length= cs->cset->well_formed_len(cs, (char*) from_beg,
+ (char*) from_beg + from_length,
+ char_length, &well_formed_error);
+ if (length < from_length)
+ {
+ if (current_thd->count_cuted_fields)
+ copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, 1);
+ }
+ int2store(copy->to_ptr, length);
+ memcpy(copy->to_ptr+HA_KEY_BLOB_LENGTH, from_beg, length);
+}
+
+
+/***************************************************************************
+** The different functions that fills in a Copy_field class
+***************************************************************************/
+
+/**
+ copy of field to maybe null string.
+ If field is null then the all bytes are set to 0.
+ if field is not null then the first byte is set to 1 and the rest of the
+ string is the field value.
+ The 'to' buffer should have a size of field->pack_length()+1
+*/
+
+void Copy_field::set(uchar *to,Field *from)
+{
+ from_ptr=from->ptr;
+ to_ptr=to;
+ from_length=from->pack_length();
+ if (from->maybe_null())
+ {
+ from_null_ptr=from->null_ptr;
+ from_bit= from->null_bit;
+ to_ptr[0]= 1; // Null as default value
+ to_null_ptr= (uchar*) to_ptr++;
+ to_bit= 1;
+ if (from->table->maybe_null)
+ {
+ null_row= &from->table->null_row;
+ do_copy= do_outer_field_to_null_str;
+ }
+ else
+ do_copy= do_field_to_null_str;
+ }
+ else
+ {
+ to_null_ptr= 0; // For easy debugging
+ do_copy= do_field_eq;
+ }
+}
+
+
+/*
+ To do:
+
+ If 'save' is set to true and the 'from' is a blob field, do_copy is set to
+ do_save_blob rather than do_conv_blob. The only differences between them
+ appears to be:
+
+ - do_save_blob allocates and uses an intermediate buffer before calling
+ Field_blob::store. Is this in order to trigger the call to
+ well_formed_copy_nchars, by changing the pointer copy->tmp.ptr()?
+ That call will take place anyway in all known cases.
+
+ - The above causes a truncation to MAX_FIELD_WIDTH. Is this the intended
+ effect? Truncation is handled by well_formed_copy_nchars anyway.
+ */
+void Copy_field::set(Field *to,Field *from,bool save)
+{
+ if (to->type() == MYSQL_TYPE_NULL)
+ {
+ to_null_ptr=0; // For easy debugging
+ to_ptr=0;
+ do_copy=do_skip;
+ return;
+ }
+ from_field=from;
+ to_field=to;
+ from_ptr=from->ptr;
+ from_length=from->pack_length();
+ to_ptr= to->ptr;
+ to_length=to_field->pack_length();
+
+ // set up null handling
+ from_null_ptr=to_null_ptr=0;
+ if (from->maybe_null())
+ {
+ from_null_ptr= from->null_ptr;
+ from_bit= from->null_bit;
+ if (to_field->real_maybe_null())
+ {
+ to_null_ptr= to->null_ptr;
+ to_bit= to->null_bit;
+ if (from_null_ptr)
+ do_copy= do_copy_null;
+ else
+ {
+ null_row= &from->table->null_row;
+ do_copy= do_outer_field_null;
+ }
+ }
+ else
+ {
+ if (to_field->type() == MYSQL_TYPE_TIMESTAMP)
+ do_copy= do_copy_timestamp; // Automatic timestamp
+ else if (to_field == to_field->table->next_number_field)
+ do_copy= do_copy_next_number;
+ else
+ {
+ if (!from_null_ptr)
+ {
+ null_row= &from->table->null_row;
+ do_copy= do_copy_nullable_row_to_notnull;
+ }
+ else
+ do_copy= do_copy_not_null;
+ }
+ }
+ }
+ else if (to_field->real_maybe_null())
+ {
+ to_null_ptr= to->null_ptr;
+ to_bit= to->null_bit;
+ do_copy= do_copy_maybe_null;
+ }
+ else
+ do_copy=0;
+
+ if ((to->flags & BLOB_FLAG) && save)
+ do_copy2= do_save_blob;
+ else
+ do_copy2= get_copy_func(to,from);
+ if (!do_copy) // Not null
+ do_copy=do_copy2;
+}
+
+
+Copy_field::Copy_func *
+Copy_field::get_copy_func(Field *to,Field *from)
+{
+ if (to->flags & BLOB_FLAG)
+ {
+ if (!(from->flags & BLOB_FLAG) || from->charset() != to->charset())
+ return do_conv_blob;
+ if (from_length != to_length)
+ {
+ // Correct pointer to point at char pointer
+ to_ptr+= to_length - portable_sizeof_char_ptr;
+ from_ptr+= from_length - portable_sizeof_char_ptr;
+ return do_copy_blob;
+ }
+ }
+ else
+ {
+ if (to->real_type() == MYSQL_TYPE_BIT ||
+ from->real_type() == MYSQL_TYPE_BIT)
+ return do_field_int;
+ if (to->result_type() == DECIMAL_RESULT)
+ return do_field_decimal;
+ if (from->cmp_type() == TIME_RESULT)
+ {
+ /* If types are not 100 % identical then convert trough get_date() */
+ if (!to->eq_def(from) ||
+ ((to->table->in_use->variables.sql_mode &
+ (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE)) &&
+ mysql_type_to_time_type(to->type()) != MYSQL_TIMESTAMP_TIME))
+ return do_field_temporal;
+ /* Do binary copy */
+ }
+ // Check if identical fields
+ if (from->result_type() == STRING_RESULT)
+ {
+ /*
+ Detect copy from pre 5.0 varbinary to varbinary as of 5.0 and
+ use special copy function that removes trailing spaces and thus
+ repairs data.
+ */
+ if (from->type() == MYSQL_TYPE_VAR_STRING && !from->has_charset() &&
+ to->type() == MYSQL_TYPE_VARCHAR && !to->has_charset())
+ return do_field_varbinary_pre50;
+
+ if (to->real_type() != from->real_type())
+ {
+ if (from->real_type() == MYSQL_TYPE_ENUM ||
+ from->real_type() == MYSQL_TYPE_SET)
+ if (to->result_type() != STRING_RESULT)
+ return do_field_int; // Convert SET to number
+ return do_field_string;
+ }
+ if (to->real_type() == MYSQL_TYPE_ENUM ||
+ to->real_type() == MYSQL_TYPE_SET)
+ {
+ if (!to->eq_def(from))
+ {
+ if (from->real_type() == MYSQL_TYPE_ENUM &&
+ to->real_type() == MYSQL_TYPE_ENUM)
+ return do_field_enum;
+ return do_field_string;
+ }
+ }
+ else if (to->charset() != from->charset())
+ return do_field_string;
+ else if (to->real_type() == MYSQL_TYPE_VARCHAR)
+ {
+ if (((Field_varstring*) to)->length_bytes !=
+ ((Field_varstring*) from)->length_bytes)
+ return do_field_string;
+ return (((Field_varstring*) to)->length_bytes == 1 ?
+ (from->charset()->mbmaxlen == 1 ? do_varstring1 :
+ do_varstring1_mb) :
+ (from->charset()->mbmaxlen == 1 ? do_varstring2 :
+ do_varstring2_mb));
+ }
+ else if (to_length < from_length)
+ return (from->charset()->mbmaxlen == 1 ?
+ do_cut_string : do_cut_string_complex);
+ else if (to_length > from_length)
+ {
+ if (to->charset() == &my_charset_bin)
+ return do_expand_binary;
+ return do_expand_string;
+ }
+ }
+ else if (to->real_type() != from->real_type() ||
+ to_length != from_length)
+ {
+ if ((to->real_type() == MYSQL_TYPE_ENUM ||
+ to->real_type() == MYSQL_TYPE_SET) &&
+ from->real_type() == MYSQL_TYPE_NEWDECIMAL)
+ return do_field_decimal;
+ if (to->real_type() == MYSQL_TYPE_DECIMAL ||
+ to->result_type() == STRING_RESULT)
+ return do_field_string;
+ if (to->result_type() == INT_RESULT)
+ return do_field_int;
+ return do_field_real;
+ }
+ else
+ {
+ if (!to->eq_def(from))
+ {
+ if (to->real_type() == MYSQL_TYPE_DECIMAL)
+ return do_field_string;
+ if (to->result_type() == INT_RESULT)
+ return do_field_int;
+ else
+ return do_field_real;
+ }
+ }
+ }
+ /* Identical field types */
+ switch (to_length) {
+ case 1: return do_field_1;
+ case 2: return do_field_2;
+ case 3: return do_field_3;
+ case 4: return do_field_4;
+ case 6: return do_field_6;
+ case 8: return do_field_8;
+ }
+ return do_field_eq;
+}
+
+/**
+ Check if it is possible just copy value of the fields
+
+ @param to The field to copy to
+ @param from The field to copy from
+
+ @retval TRUE - it is possible to just copy value of 'from' to 'to'.
+ @retval FALSE - conversion is needed
+*/
+
+bool memcpy_field_possible(Field *to,Field *from)
+{
+ const enum_field_types to_real_type= to->real_type();
+ const enum_field_types from_real_type= from->real_type();
+ /*
+ Warning: Calling from->type() may be unsafe in some (unclear) circumstances
+ related to SPs. See MDEV-6799.
+ */
+ return (to_real_type == from_real_type &&
+ !(to->flags & BLOB_FLAG && to->table->copy_blobs) &&
+ to->pack_length() == from->pack_length() &&
+ !(to->flags & UNSIGNED_FLAG && !(from->flags & UNSIGNED_FLAG)) &&
+ to->decimals() == from->decimals() &&
+ to_real_type != MYSQL_TYPE_ENUM &&
+ to_real_type != MYSQL_TYPE_SET &&
+ to_real_type != MYSQL_TYPE_BIT &&
+ (to_real_type != MYSQL_TYPE_NEWDECIMAL ||
+ to->field_length == from->field_length) &&
+ from->charset() == to->charset() &&
+ (!sql_mode_for_dates(to->table->in_use) ||
+ (from->type()!= MYSQL_TYPE_DATE &&
+ from->type()!= MYSQL_TYPE_DATETIME)) &&
+ (from_real_type != MYSQL_TYPE_VARCHAR ||
+ ((Field_varstring*)from)->length_bytes ==
+ ((Field_varstring*)to)->length_bytes));
+}
+
+
+/** Simple quick field convert that is called on insert. */
+
+int field_conv(Field *to,Field *from)
+{
+ if (memcpy_field_possible(to, from))
+ { // Identical fields
+ /*
+ This may happen if one does 'UPDATE ... SET x=x'
+ The test is here mostly for valgrind, but can also be relevant
+ if memcpy() is implemented with prefetch-write
+ */
+ if (to->ptr != from->ptr)
+ memcpy(to->ptr, from->ptr, to->pack_length());
+ return 0;
+ }
+ return field_conv_incompatible(to, from);
+}
+
+
+/**
+ Copy value of the field with conversion.
+
+ @note Impossibility of simple copy should be checked before this call.
+
+ @param to The field to copy to
+ @param from The field to copy from
+
+ @retval TRUE ERROR
+ @retval FALSE OK
+*/
+
+int field_conv_incompatible(Field *to, Field *from)
+{
+ const enum_field_types to_real_type= to->real_type();
+ const enum_field_types from_real_type= from->real_type();
+ if (to->flags & BLOB_FLAG)
+ { // Be sure the value is stored
+ Field_blob *blob=(Field_blob*) to;
+ from->val_str(&blob->value);
+ /*
+ Copy value if copy_blobs is set, or source is not a string and
+ we have a pointer to its internal string conversion buffer.
+ */
+ if (to->table->copy_blobs ||
+ (!blob->value.is_alloced() &&
+ from_real_type != MYSQL_TYPE_STRING &&
+ from_real_type != MYSQL_TYPE_VARCHAR))
+ blob->value.copy();
+ return blob->store(blob->value.ptr(),blob->value.length(),from->charset());
+ }
+ if (from_real_type == MYSQL_TYPE_ENUM &&
+ to_real_type == MYSQL_TYPE_ENUM &&
+ from->val_int() == 0)
+ {
+ ((Field_enum *)(to))->store_type(0);
+ return 0;
+ }
+ Item_result from_result_type= from->result_type();
+ if (from_result_type == REAL_RESULT)
+ return to->store(from->val_real());
+ if (from_result_type == DECIMAL_RESULT)
+ {
+ my_decimal buff;
+ return to->store_decimal(from->val_decimal(&buff));
+ }
+ if (from->cmp_type() == TIME_RESULT)
+ {
+ MYSQL_TIME ltime;
+ if (from->get_date(&ltime, 0))
+ return to->reset();
+ else
+ return to->store_time_dec(&ltime, from->decimals());
+ }
+ if ((from_result_type == STRING_RESULT &&
+ (to->result_type() == STRING_RESULT ||
+ (from_real_type != MYSQL_TYPE_ENUM &&
+ from_real_type != MYSQL_TYPE_SET))) ||
+ to->type() == MYSQL_TYPE_DECIMAL)
+ {
+ char buff[MAX_FIELD_WIDTH];
+ String result(buff,sizeof(buff),from->charset());
+ from->val_str(&result);
+ /*
+ We use c_ptr_quick() here to make it easier if to is a float/double
+ as the conversion routines will do a copy of the result doesn't
+ end with \0. Can be replaced with .ptr() when we have our own
+ string->double conversion.
+ */
+ return to->store(result.c_ptr_quick(),result.length(),from->charset());
+ }
+ return to->store(from->val_int(), MY_TEST(from->flags & UNSIGNED_FLAG));
+}
diff --git a/sql/filesort.cc b/sql/filesort.cc
new file mode 100644
index 00000000000..027437fca67
--- /dev/null
+++ b/sql/filesort.cc
@@ -0,0 +1,2118 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, Monty Program Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/**
+ @file
+
+ @brief
+ Sorts a database
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "filesort.h"
+#include "unireg.h" // REQUIRED by other includes
+#ifdef HAVE_STDDEF_H
+#include <stddef.h> /* for macro offsetof */
+#endif
+#include <m_ctype.h>
+#include "sql_sort.h"
+#include "probes_mysql.h"
+#include "sql_base.h" // update_virtual_fields
+#include "sql_test.h" // TEST_filesort
+#include "opt_range.h" // SQL_SELECT
+#include "bounded_queue.h"
+#include "filesort_utils.h"
+#include "sql_select.h"
+#include "log_slow.h"
+#include "debug_sync.h"
+
+/// How to write record_ref.
+#define WRITE_REF(file,from) \
+if (my_b_write((file),(uchar*) (from),param->ref_length)) \
+ DBUG_RETURN(1);
+
+ /* functions defined in this file */
+
+static uchar *read_buffpek_from_file(IO_CACHE *buffer_file, uint count,
+ uchar *buf);
+static ha_rows find_all_keys(Sort_param *param,SQL_SELECT *select,
+ Filesort_info *fs_info,
+ IO_CACHE *buffer_file,
+ IO_CACHE *tempfile,
+ Bounded_queue<uchar, uchar> *pq,
+ ha_rows *found_rows);
+static bool write_keys(Sort_param *param, Filesort_info *fs_info,
+ uint count, IO_CACHE *buffer_file, IO_CACHE *tempfile);
+static void make_sortkey(Sort_param *param, uchar *to, uchar *ref_pos);
+static void register_used_fields(Sort_param *param);
+static bool save_index(Sort_param *param, uint count,
+ Filesort_info *table_sort);
+static uint suffix_length(ulong string_length);
+static uint sortlength(THD *thd, SORT_FIELD *sortorder, uint s_length,
+ bool *multi_byte_charset);
+static SORT_ADDON_FIELD *get_addon_fields(ulong max_length_for_sort_data,
+ Field **ptabfield,
+ uint sortlength, uint *plength);
+static void unpack_addon_fields(struct st_sort_addon_field *addon_field,
+ uchar *buff, uchar *buff_end);
+static bool check_if_pq_applicable(Sort_param *param, Filesort_info *info,
+ TABLE *table,
+ ha_rows records, ulong memory_available);
+
+
+void Sort_param::init_for_filesort(uint sortlen, TABLE *table,
+ ulong max_length_for_sort_data,
+ ha_rows maxrows, bool sort_positions)
+{
+ sort_length= sortlen;
+ ref_length= table->file->ref_length;
+ if (!(table->file->ha_table_flags() & HA_FAST_KEY_READ) &&
+ !table->fulltext_searched && !sort_positions)
+ {
+ /*
+ Get the descriptors of all fields whose values are appended
+ to sorted fields and get its total length in addon_length.
+ */
+ addon_field= get_addon_fields(max_length_for_sort_data,
+ table->field, sort_length, &addon_length);
+ }
+ if (addon_field)
+ res_length= addon_length;
+ else
+ {
+ res_length= ref_length;
+ /*
+ The reference to the record is considered
+ as an additional sorted field
+ */
+ sort_length+= ref_length;
+ }
+ rec_length= sort_length + addon_length;
+ max_rows= maxrows;
+}
+
+
+/**
+ Sort a table.
+ Creates a set of pointers that can be used to read the rows
+ in sorted order. This should be done with the functions
+ in records.cc.
+
+ Before calling filesort, one must have done
+ table->file->info(HA_STATUS_VARIABLE)
+
+ The result set is stored in table->io_cache or
+ table->record_pointers.
+
+ @param thd Current thread
+ @param table Table to sort
+ @param sortorder How to sort the table
+ @param s_length Number of elements in sortorder
+ @param select Condition to apply to the rows
+ @param max_rows Return only this many rows
+ @param sort_positions Set to TRUE if we want to force sorting by position
+ (Needed by UPDATE/INSERT or ALTER TABLE or
+ when rowids are required by executor)
+ @param[out] examined_rows Store number of examined rows here
+ @param[out] found_rows Store the number of found rows here
+
+ @note
+ If we sort by position (like if sort_positions is 1) filesort() will
+ call table->prepare_for_position().
+
+ @retval
+ HA_POS_ERROR Error
+ @retval
+ \# Number of rows
+*/
+
+ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length,
+ SQL_SELECT *select, ha_rows max_rows,
+ bool sort_positions,
+ ha_rows *examined_rows,
+ ha_rows *found_rows)
+{
+ int error;
+ size_t memory_available= thd->variables.sortbuff_size;
+ uint maxbuffer;
+ BUFFPEK *buffpek;
+ ha_rows num_rows= HA_POS_ERROR;
+ IO_CACHE tempfile, buffpek_pointers, *outfile;
+ Sort_param param;
+ bool multi_byte_charset;
+ Bounded_queue<uchar, uchar> pq;
+
+ DBUG_ENTER("filesort");
+ DBUG_EXECUTE("info",TEST_filesort(sortorder,s_length););
+#ifdef SKIP_DBUG_IN_FILESORT
+ DBUG_PUSH(""); /* No DBUG here */
+#endif
+ Filesort_info table_sort= table->sort;
+ TABLE_LIST *tab= table->pos_in_table_list;
+ Item_subselect *subselect= tab ? tab->containing_subselect() : 0;
+
+ MYSQL_FILESORT_START(table->s->db.str, table->s->table_name.str);
+ DEBUG_SYNC(thd, "filesort_start");
+
+ /*
+ Release InnoDB's adaptive hash index latch (if holding) before
+ running a sort.
+ */
+ ha_release_temporary_latches(thd);
+
+ /*
+ Don't use table->sort in filesort as it is also used by
+ QUICK_INDEX_MERGE_SELECT. Work with a copy and put it back at the end
+ when index_merge select has finished with it.
+ */
+ table->sort.io_cache= NULL;
+ DBUG_ASSERT(table_sort.record_pointers == NULL);
+
+ outfile= table_sort.io_cache;
+ my_b_clear(&tempfile);
+ my_b_clear(&buffpek_pointers);
+ buffpek=0;
+ error= 1;
+ *found_rows= HA_POS_ERROR;
+
+ param.init_for_filesort(sortlength(thd, sortorder, s_length,
+ &multi_byte_charset),
+ table,
+ thd->variables.max_length_for_sort_data,
+ max_rows, sort_positions);
+
+ table_sort.addon_buf= 0;
+ table_sort.addon_length= param.addon_length;
+ table_sort.addon_field= param.addon_field;
+ table_sort.unpack= unpack_addon_fields;
+ if (param.addon_field &&
+ !(table_sort.addon_buf=
+ (uchar *) my_malloc(param.addon_length, MYF(MY_WME |
+ MY_THREAD_SPECIFIC))))
+ goto err;
+
+ if (select && select->quick)
+ thd->inc_status_sort_range();
+ else
+ thd->inc_status_sort_scan();
+ thd->query_plan_flags|= QPLAN_FILESORT;
+
+ // If number of rows is not known, use as much of sort buffer as possible.
+ num_rows= table->file->estimate_rows_upper_bound();
+
+ if (multi_byte_charset &&
+ !(param.tmp_buffer= (char*) my_malloc(param.sort_length,
+ MYF(MY_WME | MY_THREAD_SPECIFIC))))
+ goto err;
+
+ if (check_if_pq_applicable(&param, &table_sort,
+ table, num_rows, memory_available))
+ {
+ DBUG_PRINT("info", ("filesort PQ is applicable"));
+ thd->query_plan_flags|= QPLAN_FILESORT_PRIORITY_QUEUE;
+ status_var_increment(thd->status_var.filesort_pq_sorts_);
+ const size_t compare_length= param.sort_length;
+ if (pq.init(param.max_rows,
+ true, // max_at_top
+ NULL, // compare_function
+ compare_length,
+ &make_sortkey, &param, table_sort.get_sort_keys()))
+ {
+ /*
+ If we fail to init pq, we have to give up:
+ out of memory means my_malloc() will call my_error().
+ */
+ DBUG_PRINT("info", ("failed to allocate PQ"));
+ table_sort.free_sort_buffer();
+ DBUG_ASSERT(thd->is_error());
+ goto err;
+ }
+ // For PQ queries (with limit) we initialize all pointers.
+ table_sort.init_record_pointers();
+ }
+ else
+ {
+ DBUG_PRINT("info", ("filesort PQ is not applicable"));
+
+ size_t min_sort_memory= MY_MAX(MIN_SORT_MEMORY, param.sort_length*MERGEBUFF2);
+ set_if_bigger(min_sort_memory, sizeof(BUFFPEK*)*MERGEBUFF2);
+ while (memory_available >= min_sort_memory)
+ {
+ ulonglong keys= memory_available / (param.rec_length + sizeof(char*));
+ param.max_keys_per_buffer= (uint) MY_MIN(num_rows, keys);
+ if (table_sort.get_sort_keys())
+ {
+ // If we have already allocated a buffer, it better have same size!
+ if (!table_sort.check_sort_buffer_properties(param.max_keys_per_buffer,
+ param.rec_length))
+ {
+ /*
+ table->sort will still have a pointer to the same buffer,
+ but that will be overwritten by the assignment below.
+ */
+ table_sort.free_sort_buffer();
+ }
+ }
+ table_sort.alloc_sort_buffer(param.max_keys_per_buffer, param.rec_length);
+ if (table_sort.get_sort_keys())
+ break;
+ size_t old_memory_available= memory_available;
+ memory_available= memory_available/4*3;
+ if (memory_available < min_sort_memory &&
+ old_memory_available > min_sort_memory)
+ memory_available= min_sort_memory;
+ }
+ if (memory_available < min_sort_memory)
+ {
+ my_error(ER_OUT_OF_SORTMEMORY,MYF(ME_ERROR + ME_FATALERROR));
+ goto err;
+ }
+ }
+
+ if (open_cached_file(&buffpek_pointers,mysql_tmpdir,TEMP_PREFIX,
+ DISK_BUFFER_SIZE, MYF(MY_WME)))
+ goto err;
+
+ param.sort_form= table;
+ param.end=(param.local_sortorder=sortorder)+s_length;
+ num_rows= find_all_keys(&param, select,
+ &table_sort,
+ &buffpek_pointers,
+ &tempfile,
+ pq.is_initialized() ? &pq : NULL,
+ found_rows);
+ if (num_rows == HA_POS_ERROR)
+ goto err;
+
+ maxbuffer= (uint) (my_b_tell(&buffpek_pointers)/sizeof(*buffpek));
+
+ if (maxbuffer == 0) // The whole set is in memory
+ {
+ if (save_index(&param, (uint) num_rows, &table_sort))
+ goto err;
+ }
+ else
+ {
+ /* filesort cannot handle zero-length records during merge. */
+ DBUG_ASSERT(param.sort_length != 0);
+
+ if (table_sort.buffpek && table_sort.buffpek_len < maxbuffer)
+ {
+ my_free(table_sort.buffpek);
+ table_sort.buffpek= 0;
+ }
+ if (!(table_sort.buffpek=
+ (uchar *) read_buffpek_from_file(&buffpek_pointers, maxbuffer,
+ table_sort.buffpek)))
+ goto err;
+ buffpek= (BUFFPEK *) table_sort.buffpek;
+ table_sort.buffpek_len= maxbuffer;
+ close_cached_file(&buffpek_pointers);
+ /* Open cached file if it isn't open */
+ if (! my_b_inited(outfile) &&
+ open_cached_file(outfile,mysql_tmpdir,TEMP_PREFIX,READ_RECORD_BUFFER,
+ MYF(MY_WME)))
+ goto err;
+ if (reinit_io_cache(outfile,WRITE_CACHE,0L,0,0))
+ goto err;
+
+ /*
+ Use also the space previously used by string pointers in sort_buffer
+ for temporary key storage.
+ */
+ param.max_keys_per_buffer=((param.max_keys_per_buffer *
+ (param.rec_length + sizeof(char*))) /
+ param.rec_length - 1);
+ maxbuffer--; // Offset from 0
+ if (merge_many_buff(&param,
+ (uchar*) table_sort.get_sort_keys(),
+ buffpek,&maxbuffer,
+ &tempfile))
+ goto err;
+ if (flush_io_cache(&tempfile) ||
+ reinit_io_cache(&tempfile,READ_CACHE,0L,0,0))
+ goto err;
+ if (merge_index(&param,
+ (uchar*) table_sort.get_sort_keys(),
+ buffpek,
+ maxbuffer,
+ &tempfile,
+ outfile))
+ goto err;
+ }
+
+ if (num_rows > param.max_rows)
+ {
+ // If find_all_keys() produced more results than the query LIMIT.
+ num_rows= param.max_rows;
+ }
+ error= 0;
+
+ err:
+ my_free(param.tmp_buffer);
+ if (!subselect || !subselect->is_uncacheable())
+ {
+ table_sort.free_sort_buffer();
+ my_free(buffpek);
+ table_sort.buffpek= 0;
+ table_sort.buffpek_len= 0;
+ }
+ close_cached_file(&tempfile);
+ close_cached_file(&buffpek_pointers);
+ if (my_b_inited(outfile))
+ {
+ if (flush_io_cache(outfile))
+ error=1;
+ {
+ my_off_t save_pos=outfile->pos_in_file;
+ /* For following reads */
+ if (reinit_io_cache(outfile,READ_CACHE,0L,0,0))
+ error=1;
+ outfile->end_of_file=save_pos;
+ }
+ }
+ if (error)
+ {
+ int kill_errno= thd->killed_errno();
+ DBUG_ASSERT(thd->is_error() || kill_errno || thd->killed == ABORT_QUERY);
+
+ /*
+ We replace the table->sort at the end.
+ Hence calling free_io_cache to make sure table->sort.io_cache
+ used for QUICK_INDEX_MERGE_SELECT is free.
+ */
+ free_io_cache(table);
+
+ my_printf_error(ER_FILSORT_ABORT,
+ "%s: %s",
+ MYF(0),
+ ER_THD(thd, ER_FILSORT_ABORT),
+ kill_errno ? ER(kill_errno) :
+ thd->killed == ABORT_QUERY ? "" :
+ thd->get_stmt_da()->message());
+
+ if (global_system_variables.log_warnings > 1)
+ {
+ sql_print_warning("%s, host: %s, user: %s, thread: %lu, query: %-.4096s",
+ ER_THD(thd, ER_FILSORT_ABORT),
+ thd->security_ctx->host_or_ip,
+ &thd->security_ctx->priv_user[0],
+ (ulong) thd->thread_id,
+ thd->query());
+ }
+ }
+ else
+ thd->inc_status_sort_rows(num_rows);
+ *examined_rows= param.examined_rows;
+#ifdef SKIP_DBUG_IN_FILESORT
+ DBUG_POP(); /* Ok to DBUG */
+#endif
+
+ /* table->sort.io_cache should be free by this time */
+ DBUG_ASSERT(NULL == table->sort.io_cache);
+
+ // Assign the copy back!
+ table->sort= table_sort;
+
+ DBUG_PRINT("exit",
+ ("num_rows: %ld examined_rows: %ld found_rows: %ld",
+ (long) num_rows, (long) *examined_rows, (long) *found_rows));
+ MYSQL_FILESORT_DONE(error, num_rows);
+ DBUG_RETURN(error ? HA_POS_ERROR : num_rows);
+} /* filesort */
+
+
+void filesort_free_buffers(TABLE *table, bool full)
+{
+ DBUG_ENTER("filesort_free_buffers");
+ my_free(table->sort.record_pointers);
+ table->sort.record_pointers= NULL;
+
+ if (full)
+ {
+ table->sort.free_sort_buffer();
+ my_free(table->sort.buffpek);
+ table->sort.buffpek= NULL;
+ table->sort.buffpek_len= 0;
+ }
+
+ my_free(table->sort.addon_buf);
+ my_free(table->sort.addon_field);
+ table->sort.addon_buf= NULL;
+ table->sort.addon_field= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/** Read 'count' number of buffer pointers into memory. */
+
+static uchar *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count,
+ uchar *buf)
+{
+ size_t length= sizeof(BUFFPEK)*count;
+ uchar *tmp= buf;
+ DBUG_ENTER("read_buffpek_from_file");
+ if (count > UINT_MAX/sizeof(BUFFPEK))
+ return 0; /* sizeof(BUFFPEK)*count will overflow */
+ if (!tmp)
+ tmp= (uchar *)my_malloc(length, MYF(MY_WME | MY_THREAD_SPECIFIC));
+ if (tmp)
+ {
+ if (reinit_io_cache(buffpek_pointers,READ_CACHE,0L,0,0) ||
+ my_b_read(buffpek_pointers, (uchar*) tmp, length))
+ {
+ my_free(tmp);
+ tmp=0;
+ }
+ }
+ DBUG_RETURN(tmp);
+}
+
+#ifndef DBUG_OFF
+
+/* Buffer where record is returned */
+char dbug_print_row_buff[512];
+
+/* Temporary buffer for printing a column */
+char dbug_print_row_buff_tmp[512];
+
+/*
+ Print table's current row into a buffer and return a pointer to it.
+
+ This is intended to be used from gdb:
+
+ (gdb) p dbug_print_table_row(table)
+ $33 = "SUBQUERY2_t1(col_int_key,col_varchar_nokey)=(7,c)"
+ (gdb)
+
+ Only columns in table->read_set are printed
+*/
+
+const char* dbug_print_table_row(TABLE *table)
+{
+ Field **pfield;
+ String tmp(dbug_print_row_buff_tmp,
+ sizeof(dbug_print_row_buff_tmp),&my_charset_bin);
+
+ String output(dbug_print_row_buff, sizeof(dbug_print_row_buff),
+ &my_charset_bin);
+
+ output.length(0);
+ output.append(table->alias);
+ output.append("(");
+ bool first= true;
+
+ for (pfield= table->field; *pfield ; pfield++)
+ {
+ if (table->read_set && !bitmap_is_set(table->read_set, (*pfield)->field_index))
+ continue;
+
+ if (first)
+ first= false;
+ else
+ output.append(",");
+
+ output.append((*pfield)->field_name? (*pfield)->field_name: "NULL");
+ }
+
+ output.append(")=(");
+
+ first= true;
+ for (pfield= table->field; *pfield ; pfield++)
+ {
+ Field *field= *pfield;
+
+ if (table->read_set && !bitmap_is_set(table->read_set, (*pfield)->field_index))
+ continue;
+
+ if (first)
+ first= false;
+ else
+ output.append(",");
+
+ if (field->is_null())
+ output.append("NULL");
+ else
+ {
+ if (field->type() == MYSQL_TYPE_BIT)
+ (void) field->val_int_as_str(&tmp, 1);
+ else
+ field->val_str(&tmp);
+ output.append(tmp.ptr(), tmp.length());
+ }
+ }
+ output.append(")");
+
+ return output.c_ptr_safe();
+}
+
+
+/*
+ Print a text, SQL-like record representation into dbug trace.
+
+ Note: this function is a work in progress: at the moment
+ - column read bitmap is ignored (can print garbage for unused columns)
+ - there is no quoting
+*/
+static void dbug_print_record(TABLE *table, bool print_rowid)
+{
+ char buff[1024];
+ Field **pfield;
+ String tmp(buff,sizeof(buff),&my_charset_bin);
+ DBUG_LOCK_FILE;
+
+ fprintf(DBUG_FILE, "record (");
+ for (pfield= table->field; *pfield ; pfield++)
+ fprintf(DBUG_FILE, "%s%s", (*pfield)->field_name, (pfield[1])? ", ":"");
+ fprintf(DBUG_FILE, ") = ");
+
+ fprintf(DBUG_FILE, "(");
+ for (pfield= table->field; *pfield ; pfield++)
+ {
+ Field *field= *pfield;
+
+ if (field->is_null())
+ fwrite("NULL", sizeof(char), 4, DBUG_FILE);
+
+ if (field->type() == MYSQL_TYPE_BIT)
+ (void) field->val_int_as_str(&tmp, 1);
+ else
+ field->val_str(&tmp);
+
+ fwrite(tmp.ptr(),sizeof(char),tmp.length(),DBUG_FILE);
+ if (pfield[1])
+ fwrite(", ", sizeof(char), 2, DBUG_FILE);
+ }
+ fprintf(DBUG_FILE, ")");
+ if (print_rowid)
+ {
+ fprintf(DBUG_FILE, " rowid ");
+ for (uint i=0; i < table->file->ref_length; i++)
+ {
+ fprintf(DBUG_FILE, "%x", (uchar)table->file->ref[i]);
+ }
+ }
+ fprintf(DBUG_FILE, "\n");
+ DBUG_UNLOCK_FILE;
+}
+
+#endif
+
+
+/**
+ Search after sort_keys, and write them into tempfile
+ (if we run out of space in the sort_keys buffer).
+ All produced sequences are guaranteed to be non-empty.
+
+ @param param Sorting parameter
+ @param select Use this to get source data
+ @param sort_keys Array of pointers to sort key + addon buffers.
+ @param buffpek_pointers File to write BUFFPEKs describing sorted segments
+ in tempfile.
+ @param tempfile File to write sorted sequences of sortkeys to.
+ @param pq If !NULL, use it for keeping top N elements
+ @param [out] found_rows The number of FOUND_ROWS().
+ For a query with LIMIT, this value will typically
+ be larger than the function return value.
+
+ @note
+ Basic idea:
+ @verbatim
+ while (get_next_sortkey())
+ {
+ if (using priority queue)
+ push sort key into queue
+ else
+ {
+ if (no free space in sort_keys buffers)
+ {
+ sort sort_keys buffer;
+ dump sorted sequence to 'tempfile';
+ dump BUFFPEK describing sequence location into 'buffpek_pointers';
+ }
+ put sort key into 'sort_keys';
+ }
+ }
+ if (sort_keys has some elements && dumped at least once)
+ sort-dump-dump as above;
+ else
+ don't sort, leave sort_keys array to be sorted by caller.
+ @endverbatim
+
+ @retval
+ Number of records written on success.
+ @retval
+ HA_POS_ERROR on error.
+*/
+
+static ha_rows find_all_keys(Sort_param *param, SQL_SELECT *select,
+ Filesort_info *fs_info,
+ IO_CACHE *buffpek_pointers,
+ IO_CACHE *tempfile,
+ Bounded_queue<uchar, uchar> *pq,
+ ha_rows *found_rows)
+{
+ int error,flag,quick_select;
+ uint idx,indexpos,ref_length;
+ uchar *ref_pos,*next_pos,ref_buff[MAX_REFLENGTH];
+ my_off_t record;
+ TABLE *sort_form;
+ THD *thd= current_thd;
+ handler *file;
+ MY_BITMAP *save_read_set, *save_write_set, *save_vcol_set;
+
+ DBUG_ENTER("find_all_keys");
+ DBUG_PRINT("info",("using: %s",
+ (select ? select->quick ? "ranges" : "where":
+ "every row")));
+
+ idx=indexpos=0;
+ error=quick_select=0;
+ sort_form=param->sort_form;
+ file=sort_form->file;
+ ref_length=param->ref_length;
+ ref_pos= ref_buff;
+ quick_select=select && select->quick;
+ record=0;
+ *found_rows= 0;
+ flag= ((file->ha_table_flags() & HA_REC_NOT_IN_SEQ) || quick_select);
+ if (flag)
+ ref_pos= &file->ref[0];
+ next_pos=ref_pos;
+
+ DBUG_EXECUTE_IF("show_explain_in_find_all_keys",
+ dbug_serve_apcs(thd, 1);
+ );
+
+ if (!quick_select)
+ {
+ next_pos=(uchar*) 0; /* Find records in sequence */
+ DBUG_EXECUTE_IF("bug14365043_1",
+ DBUG_SET("+d,ha_rnd_init_fail"););
+ if (file->ha_rnd_init_with_error(1))
+ DBUG_RETURN(HA_POS_ERROR);
+ file->extra_opt(HA_EXTRA_CACHE,
+ current_thd->variables.read_buff_size);
+ }
+
+ /* Remember original bitmaps */
+ save_read_set= sort_form->read_set;
+ save_write_set= sort_form->write_set;
+ save_vcol_set= sort_form->vcol_set;
+ /* Set up temporary column read map for columns used by sort */
+ bitmap_clear_all(&sort_form->tmp_set);
+ /* Temporary set for register_used_fields and register_field_in_read_map */
+ sort_form->read_set= &sort_form->tmp_set;
+ register_used_fields(param);
+ if (quick_select)
+ select->quick->add_used_key_part_to_set(sort_form->read_set);
+
+ Item *sort_cond= !select ?
+ 0 : !select->pre_idx_push_select_cond ?
+ select->cond : select->pre_idx_push_select_cond;
+ if (sort_cond)
+ sort_cond->walk(&Item::register_field_in_read_map, 1, (uchar*) sort_form);
+ sort_form->column_bitmaps_set(&sort_form->tmp_set, &sort_form->tmp_set,
+ &sort_form->tmp_set);
+
+
+ if (quick_select)
+ {
+ if (select->quick->reset())
+ DBUG_RETURN(HA_POS_ERROR);
+ }
+
+ DEBUG_SYNC(thd, "after_index_merge_phase1");
+ for (;;)
+ {
+ if (quick_select)
+ {
+ if ((error= select->quick->get_next()))
+ break;
+ if (!error && sort_form->vfield)
+ update_virtual_fields(thd, sort_form);
+ file->position(sort_form->record[0]);
+ DBUG_EXECUTE_IF("debug_filesort", dbug_print_record(sort_form, TRUE););
+ }
+ else /* Not quick-select */
+ {
+ {
+ error= file->ha_rnd_next(sort_form->record[0]);
+ if (!error && sort_form->vfield)
+ update_virtual_fields(thd, sort_form);
+ if (!flag)
+ {
+ my_store_ptr(ref_pos,ref_length,record); // Position to row
+ record+= sort_form->s->db_record_offset;
+ }
+ else if (!error)
+ file->position(sort_form->record[0]);
+ }
+ if (error && error != HA_ERR_RECORD_DELETED)
+ break;
+ }
+
+ if (thd->check_killed())
+ {
+ DBUG_PRINT("info",("Sort killed by user"));
+ if (!quick_select)
+ {
+ (void) file->extra(HA_EXTRA_NO_CACHE);
+ file->ha_rnd_end();
+ }
+ DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */
+ }
+
+ bool write_record= false;
+ if (error == 0)
+ {
+ param->examined_rows++;
+ if (select && select->cond)
+ {
+ /*
+ If the condition 'select->cond' contains a subquery, restore the
+ original read/write sets of the table 'sort_form' because when
+ SQL_SELECT::skip_record evaluates this condition. it may include a
+ correlated subquery predicate, such that some field in the subquery
+ refers to 'sort_form'.
+
+ PSergey-todo: discuss the above with Timour.
+ */
+ MY_BITMAP *tmp_read_set= sort_form->read_set;
+ MY_BITMAP *tmp_write_set= sort_form->write_set;
+ MY_BITMAP *tmp_vcol_set= sort_form->vcol_set;
+
+ if (select->cond->with_subselect)
+ sort_form->column_bitmaps_set(save_read_set, save_write_set,
+ save_vcol_set);
+ write_record= (select->skip_record(thd) > 0);
+ if (select->cond->with_subselect)
+ sort_form->column_bitmaps_set(tmp_read_set,
+ tmp_write_set,
+ tmp_vcol_set);
+ }
+ else
+ write_record= true;
+ }
+
+ if (write_record)
+ {
+ ++(*found_rows);
+ if (pq)
+ {
+ pq->push(ref_pos);
+ idx= pq->num_elements();
+ }
+ else
+ {
+ if (idx == param->max_keys_per_buffer)
+ {
+ if (write_keys(param, fs_info, idx, buffpek_pointers, tempfile))
+ DBUG_RETURN(HA_POS_ERROR);
+ idx= 0;
+ indexpos++;
+ }
+ make_sortkey(param, fs_info->get_record_buffer(idx++), ref_pos);
+ }
+ }
+
+ /* It does not make sense to read more keys in case of a fatal error */
+ if (thd->is_error())
+ break;
+
+ /*
+ We need to this after checking the error as the transaction may have
+ rolled back in case of a deadlock
+ */
+ if (!write_record)
+ file->unlock_row();
+ }
+ if (!quick_select)
+ {
+ (void) file->extra(HA_EXTRA_NO_CACHE); /* End cacheing of records */
+ if (!next_pos)
+ file->ha_rnd_end();
+ }
+
+ if (thd->is_error())
+ DBUG_RETURN(HA_POS_ERROR);
+
+ /* Signal we should use orignal column read and write maps */
+ sort_form->column_bitmaps_set(save_read_set, save_write_set, save_vcol_set);
+
+ DBUG_PRINT("test",("error: %d indexpos: %d",error,indexpos));
+ if (error != HA_ERR_END_OF_FILE)
+ {
+ file->print_error(error,MYF(ME_ERROR | ME_WAITTANG)); // purecov: inspected
+ DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */
+ }
+ if (indexpos && idx &&
+ write_keys(param, fs_info, idx, buffpek_pointers, tempfile))
+ DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */
+ const ha_rows retval=
+ my_b_inited(tempfile) ?
+ (ha_rows) (my_b_tell(tempfile)/param->rec_length) : idx;
+ DBUG_PRINT("info", ("find_all_keys return %u", (uint) retval));
+ DBUG_RETURN(retval);
+} /* find_all_keys */
+
+
+/**
+ @details
+ Sort the buffer and write:
+ -# the sorted sequence to tempfile
+ -# a BUFFPEK describing the sorted sequence position to buffpek_pointers
+
+ (was: Skriver en buffert med nycklar till filen)
+
+ @param param Sort parameters
+ @param sort_keys Array of pointers to keys to sort
+ @param count Number of elements in sort_keys array
+ @param buffpek_pointers One 'BUFFPEK' struct will be written into this file.
+ The BUFFPEK::{file_pos, count} will indicate where
+ the sorted data was stored.
+ @param tempfile The sorted sequence will be written into this file.
+
+ @retval
+ 0 OK
+ @retval
+ 1 Error
+*/
+
+static bool
+write_keys(Sort_param *param, Filesort_info *fs_info, uint count,
+ IO_CACHE *buffpek_pointers, IO_CACHE *tempfile)
+{
+ size_t rec_length;
+ uchar **end;
+ BUFFPEK buffpek;
+ DBUG_ENTER("write_keys");
+
+ rec_length= param->rec_length;
+ uchar **sort_keys= fs_info->get_sort_keys();
+
+ fs_info->sort_buffer(param, count);
+
+ if (!my_b_inited(tempfile) &&
+ open_cached_file(tempfile, mysql_tmpdir, TEMP_PREFIX, DISK_BUFFER_SIZE,
+ MYF(MY_WME)))
+ goto err; /* purecov: inspected */
+ /* check we won't have more buffpeks than we can possibly keep in memory */
+ if (my_b_tell(buffpek_pointers) + sizeof(BUFFPEK) > (ulonglong)UINT_MAX)
+ goto err;
+ buffpek.file_pos= my_b_tell(tempfile);
+ if ((ha_rows) count > param->max_rows)
+ count=(uint) param->max_rows; /* purecov: inspected */
+ buffpek.count=(ha_rows) count;
+ for (end=sort_keys+count ; sort_keys != end ; sort_keys++)
+ if (my_b_write(tempfile, (uchar*) *sort_keys, (uint) rec_length))
+ goto err;
+ if (my_b_write(buffpek_pointers, (uchar*) &buffpek, sizeof(buffpek)))
+ goto err;
+ DBUG_RETURN(0);
+
+err:
+ DBUG_RETURN(1);
+} /* write_keys */
+
+
+/**
+ Store length as suffix in high-byte-first order.
+*/
+
+static inline void store_length(uchar *to, uint length, uint pack_length)
+{
+ switch (pack_length) {
+ case 1:
+ *to= (uchar) length;
+ break;
+ case 2:
+ mi_int2store(to, length);
+ break;
+ case 3:
+ mi_int3store(to, length);
+ break;
+ default:
+ mi_int4store(to, length);
+ break;
+ }
+}
+
+
+/** Make a sort-key from record. */
+
+static void make_sortkey(register Sort_param *param,
+ register uchar *to, uchar *ref_pos)
+{
+ reg3 Field *field;
+ reg1 SORT_FIELD *sort_field;
+ reg5 uint length;
+
+ for (sort_field=param->local_sortorder ;
+ sort_field != param->end ;
+ sort_field++)
+ {
+ bool maybe_null=0;
+ if ((field=sort_field->field))
+ { // Field
+ field->make_sort_key(to, sort_field->length);
+ if ((maybe_null = field->maybe_null()))
+ to++;
+ }
+ else
+ { // Item
+ Item *item=sort_field->item;
+ maybe_null= item->maybe_null;
+ switch (sort_field->result_type) {
+ case STRING_RESULT:
+ {
+ const CHARSET_INFO *cs=item->collation.collation;
+ char fill_char= ((cs->state & MY_CS_BINSORT) ? (char) 0 : ' ');
+
+ if (maybe_null)
+ *to++=1;
+ char *tmp_buffer= param->tmp_buffer ? param->tmp_buffer : (char*)to;
+ String tmp(tmp_buffer, param->sort_length, cs);
+ String *res= item->str_result(&tmp);
+ if (!res)
+ {
+ if (maybe_null)
+ memset(to-1, 0, sort_field->length+1);
+ else
+ {
+ /* purecov: begin deadcode */
+ /*
+ This should only happen during extreme conditions if we run out
+ of memory or have an item marked not null when it can be null.
+ This code is here mainly to avoid a hard crash in this case.
+ */
+ DBUG_ASSERT(0);
+ DBUG_PRINT("warning",
+ ("Got null on something that shouldn't be null"));
+ memset(to, 0, sort_field->length); // Avoid crash
+ /* purecov: end */
+ }
+ break;
+ }
+ length= res->length();
+ if (sort_field->need_strxnfrm)
+ {
+ uint tmp_length __attribute__((unused));
+ tmp_length= cs->coll->strnxfrm(cs, to, sort_field->length,
+ item->max_char_length() *
+ cs->strxfrm_multiply,
+ (uchar*) res->ptr(), length,
+ MY_STRXFRM_PAD_WITH_SPACE |
+ MY_STRXFRM_PAD_TO_MAXLEN);
+ DBUG_ASSERT(tmp_length == sort_field->length);
+ }
+ else
+ {
+ uint diff;
+ uint sort_field_length= sort_field->length -
+ sort_field->suffix_length;
+ if (sort_field_length < length)
+ {
+ diff= 0;
+ length= sort_field_length;
+ }
+ else
+ diff= sort_field_length - length;
+ if (sort_field->suffix_length)
+ {
+ /* Store length last in result_string */
+ store_length(to + sort_field_length, length,
+ sort_field->suffix_length);
+ }
+ /* apply cs->sort_order for case-insensitive comparison if needed */
+ my_strnxfrm(cs,(uchar*)to,length,(const uchar*)res->ptr(),length);
+ cs->cset->fill(cs, (char *)to+length,diff,fill_char);
+ }
+ break;
+ }
+ case INT_RESULT:
+ case TIME_RESULT:
+ {
+ longlong UNINIT_VAR(value);
+ if (sort_field->result_type == INT_RESULT)
+ value= item->val_int_result();
+ else
+ {
+ MYSQL_TIME buf;
+ if (item->get_date_result(&buf, TIME_INVALID_DATES))
+ {
+ DBUG_ASSERT(maybe_null);
+ DBUG_ASSERT(item->null_value);
+ }
+ else
+ value= pack_time(&buf);
+ }
+ if (maybe_null)
+ {
+ *to++=1; /* purecov: inspected */
+ if (item->null_value)
+ {
+ if (maybe_null)
+ memset(to-1, 0, sort_field->length+1);
+ else
+ {
+ DBUG_PRINT("warning",
+ ("Got null on something that shouldn't be null"));
+ memset(to, 0, sort_field->length);
+ }
+ break;
+ }
+ }
+ to[7]= (uchar) value;
+ to[6]= (uchar) (value >> 8);
+ to[5]= (uchar) (value >> 16);
+ to[4]= (uchar) (value >> 24);
+ to[3]= (uchar) (value >> 32);
+ to[2]= (uchar) (value >> 40);
+ to[1]= (uchar) (value >> 48);
+ if (item->unsigned_flag) /* Fix sign */
+ to[0]= (uchar) (value >> 56);
+ else
+ to[0]= (uchar) (value >> 56) ^ 128; /* Reverse signbit */
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ my_decimal dec_buf, *dec_val= item->val_decimal_result(&dec_buf);
+ if (maybe_null)
+ {
+ if (item->null_value)
+ {
+ memset(to, 0, sort_field->length+1);
+ to++;
+ break;
+ }
+ *to++=1;
+ }
+ my_decimal2binary(E_DEC_FATAL_ERROR, dec_val, to,
+ item->max_length - (item->decimals ? 1:0),
+ item->decimals);
+ break;
+ }
+ case REAL_RESULT:
+ {
+ double value= item->val_result();
+ if (maybe_null)
+ {
+ if (item->null_value)
+ {
+ memset(to, 0, sort_field->length+1);
+ to++;
+ break;
+ }
+ *to++=1;
+ }
+ change_double_for_sort(value,(uchar*) to);
+ break;
+ }
+ case ROW_RESULT:
+ default:
+ // This case should never be choosen
+ DBUG_ASSERT(0);
+ break;
+ }
+ }
+ if (sort_field->reverse)
+ { /* Revers key */
+ if (maybe_null && (to[-1]= !to[-1]))
+ {
+ to+= sort_field->length; // don't waste the time reversing all 0's
+ continue;
+ }
+ length=sort_field->length;
+ while (length--)
+ {
+ *to = (uchar) (~ *to);
+ to++;
+ }
+ }
+ else
+ to+= sort_field->length;
+ }
+
+ if (param->addon_field)
+ {
+ /*
+ Save field values appended to sorted fields.
+ First null bit indicators are appended then field values follow.
+ In this implementation we use fixed layout for field values -
+ the same for all records.
+ */
+ SORT_ADDON_FIELD *addonf= param->addon_field;
+ uchar *nulls= to;
+ DBUG_ASSERT(addonf != 0);
+ memset(nulls, 0, addonf->offset);
+ to+= addonf->offset;
+ for ( ; (field= addonf->field) ; addonf++)
+ {
+ if (addonf->null_bit && field->is_null())
+ {
+ nulls[addonf->null_offset]|= addonf->null_bit;
+#ifdef HAVE_valgrind
+ bzero(to, addonf->length);
+#endif
+ }
+ else
+ {
+#ifdef HAVE_valgrind
+ uchar *end= field->pack(to, field->ptr);
+ uint length= (uint) ((to + addonf->length) - end);
+ DBUG_ASSERT((int) length >= 0);
+ if (length)
+ bzero(end, length);
+#else
+ (void) field->pack(to, field->ptr);
+#endif
+ }
+ to+= addonf->length;
+ }
+ }
+ else
+ {
+ /* Save filepos last */
+ memcpy((uchar*) to, ref_pos, (size_t) param->ref_length);
+ }
+ return;
+}
+
+
+/*
+ Register fields used by sorting in the sorted table's read set
+*/
+
+static void register_used_fields(Sort_param *param)
+{
+ reg1 SORT_FIELD *sort_field;
+ TABLE *table=param->sort_form;
+ MY_BITMAP *bitmap= table->read_set;
+
+ for (sort_field= param->local_sortorder ;
+ sort_field != param->end ;
+ sort_field++)
+ {
+ Field *field;
+ if ((field= sort_field->field))
+ {
+ if (field->table == table)
+ {
+ if (field->vcol_info)
+ {
+ Item *vcol_item= field->vcol_info->expr_item;
+ vcol_item->walk(&Item::register_field_in_read_map, 1, (uchar *) 0);
+ }
+ bitmap_set_bit(bitmap, field->field_index);
+ }
+ }
+ else
+ { // Item
+ sort_field->item->walk(&Item::register_field_in_read_map, 1,
+ (uchar *) table);
+ }
+ }
+
+ if (param->addon_field)
+ {
+ SORT_ADDON_FIELD *addonf= param->addon_field;
+ Field *field;
+ for ( ; (field= addonf->field) ; addonf++)
+ bitmap_set_bit(bitmap, field->field_index);
+ }
+ else
+ {
+ /* Save filepos last */
+ table->prepare_for_position();
+ }
+}
+
+
+static bool save_index(Sort_param *param, uint count, Filesort_info *table_sort)
+{
+ uint offset,res_length;
+ uchar *to;
+ DBUG_ENTER("save_index");
+
+ table_sort->sort_buffer(param, count);
+ res_length= param->res_length;
+ offset= param->rec_length-res_length;
+ if (!(to= table_sort->record_pointers=
+ (uchar*) my_malloc(res_length*count,
+ MYF(MY_WME | MY_THREAD_SPECIFIC))))
+ DBUG_RETURN(1); /* purecov: inspected */
+ uchar **sort_keys= table_sort->get_sort_keys();
+ for (uchar **end= sort_keys+count ; sort_keys != end ; sort_keys++)
+ {
+ memcpy(to, *sort_keys+offset, res_length);
+ to+= res_length;
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Test whether priority queue is worth using to get top elements of an
+ ordered result set. If it is, then allocates buffer for required amount of
+ records
+
+ @param param Sort parameters.
+ @param filesort_info Filesort information.
+ @param table Table to sort.
+ @param num_rows Estimate of number of rows in source record set.
+ @param memory_available Memory available for sorting.
+
+ DESCRIPTION
+ Given a query like this:
+ SELECT ... FROM t ORDER BY a1,...,an LIMIT max_rows;
+ This function tests whether a priority queue should be used to keep
+ the result. Necessary conditions are:
+ - estimate that it is actually cheaper than merge-sort
+ - enough memory to store the <max_rows> records.
+
+ If we don't have space for <max_rows> records, but we *do* have
+ space for <max_rows> keys, we may rewrite 'table' to sort with
+ references to records instead of additional data.
+ (again, based on estimates that it will actually be cheaper).
+
+ @retval
+ true - if it's ok to use PQ
+ false - PQ will be slower than merge-sort, or there is not enough memory.
+*/
+
+bool check_if_pq_applicable(Sort_param *param,
+ Filesort_info *filesort_info,
+ TABLE *table, ha_rows num_rows,
+ ulong memory_available)
+{
+ DBUG_ENTER("check_if_pq_applicable");
+
+ /*
+ How much Priority Queue sort is slower than qsort.
+ Measurements (see unit test) indicate that PQ is roughly 3 times slower.
+ */
+ const double PQ_slowness= 3.0;
+
+ if (param->max_rows == HA_POS_ERROR)
+ {
+ DBUG_PRINT("info", ("No LIMIT"));
+ DBUG_RETURN(false);
+ }
+
+ if (param->max_rows + 2 >= UINT_MAX)
+ {
+ DBUG_PRINT("info", ("Too large LIMIT"));
+ DBUG_RETURN(false);
+ }
+
+ ulong num_available_keys=
+ memory_available / (param->rec_length + sizeof(char*));
+ // We need 1 extra record in the buffer, when using PQ.
+ param->max_keys_per_buffer= (uint) param->max_rows + 1;
+
+ if (num_rows < num_available_keys)
+ {
+ // The whole source set fits into memory.
+ if (param->max_rows < num_rows/PQ_slowness )
+ {
+ filesort_info->alloc_sort_buffer(param->max_keys_per_buffer,
+ param->rec_length);
+ DBUG_RETURN(filesort_info->get_sort_keys() != NULL);
+ }
+ else
+ {
+ // PQ will be slower.
+ DBUG_RETURN(false);
+ }
+ }
+
+ // Do we have space for LIMIT rows in memory?
+ if (param->max_keys_per_buffer < num_available_keys)
+ {
+ filesort_info->alloc_sort_buffer(param->max_keys_per_buffer,
+ param->rec_length);
+ DBUG_RETURN(filesort_info->get_sort_keys() != NULL);
+ }
+
+ // Try to strip off addon fields.
+ if (param->addon_field)
+ {
+ const ulong row_length=
+ param->sort_length + param->ref_length + sizeof(char*);
+ num_available_keys= memory_available / row_length;
+
+ // Can we fit all the keys in memory?
+ if (param->max_keys_per_buffer < num_available_keys)
+ {
+ const double sort_merge_cost=
+ get_merge_many_buffs_cost_fast(num_rows,
+ num_available_keys,
+ row_length);
+ /*
+ PQ has cost:
+ (insert + qsort) * log(queue size) / TIME_FOR_COMPARE_ROWID +
+ cost of file lookup afterwards.
+ The lookup cost is a bit pessimistic: we take scan_time and assume
+ that on average we find the row after scanning half of the file.
+ A better estimate would be lookup cost, but note that we are doing
+ random lookups here, rather than sequential scan.
+ */
+ const double pq_cpu_cost=
+ (PQ_slowness * num_rows + param->max_keys_per_buffer) *
+ log((double) param->max_keys_per_buffer) / TIME_FOR_COMPARE_ROWID;
+ const double pq_io_cost=
+ param->max_rows * table->file->scan_time() / 2.0;
+ const double pq_cost= pq_cpu_cost + pq_io_cost;
+
+ if (sort_merge_cost < pq_cost)
+ DBUG_RETURN(false);
+
+ filesort_info->alloc_sort_buffer(param->max_keys_per_buffer,
+ param->sort_length + param->ref_length);
+ if (filesort_info->get_sort_keys())
+ {
+ // Make attached data to be references instead of fields.
+ my_free(filesort_info->addon_buf);
+ my_free(filesort_info->addon_field);
+ filesort_info->addon_buf= NULL;
+ filesort_info->addon_field= NULL;
+ param->addon_field= NULL;
+ param->addon_length= 0;
+
+ param->res_length= param->ref_length;
+ param->sort_length+= param->ref_length;
+ param->rec_length= param->sort_length;
+
+ DBUG_RETURN(true);
+ }
+ }
+ }
+ DBUG_RETURN(false);
+}
+
+
+/** Merge buffers to make < MERGEBUFF2 buffers. */
+
+int merge_many_buff(Sort_param *param, uchar *sort_buffer,
+ BUFFPEK *buffpek, uint *maxbuffer, IO_CACHE *t_file)
+{
+ register uint i;
+ IO_CACHE t_file2,*from_file,*to_file,*temp;
+ BUFFPEK *lastbuff;
+ DBUG_ENTER("merge_many_buff");
+
+ if (*maxbuffer < MERGEBUFF2)
+ DBUG_RETURN(0); /* purecov: inspected */
+ if (flush_io_cache(t_file) ||
+ open_cached_file(&t_file2,mysql_tmpdir,TEMP_PREFIX,DISK_BUFFER_SIZE,
+ MYF(MY_WME)))
+ DBUG_RETURN(1); /* purecov: inspected */
+
+ from_file= t_file ; to_file= &t_file2;
+ while (*maxbuffer >= MERGEBUFF2)
+ {
+ if (reinit_io_cache(from_file,READ_CACHE,0L,0,0))
+ goto cleanup;
+ if (reinit_io_cache(to_file,WRITE_CACHE,0L,0,0))
+ goto cleanup;
+ lastbuff=buffpek;
+ for (i=0 ; i <= *maxbuffer-MERGEBUFF*3/2 ; i+=MERGEBUFF)
+ {
+ if (merge_buffers(param,from_file,to_file,sort_buffer,lastbuff++,
+ buffpek+i,buffpek+i+MERGEBUFF-1,0))
+ goto cleanup;
+ }
+ if (merge_buffers(param,from_file,to_file,sort_buffer,lastbuff++,
+ buffpek+i,buffpek+ *maxbuffer,0))
+ break; /* purecov: inspected */
+ if (flush_io_cache(to_file))
+ break; /* purecov: inspected */
+ temp=from_file; from_file=to_file; to_file=temp;
+ setup_io_cache(from_file);
+ setup_io_cache(to_file);
+ *maxbuffer= (uint) (lastbuff-buffpek)-1;
+ }
+cleanup:
+ close_cached_file(to_file); // This holds old result
+ if (to_file == t_file)
+ {
+ *t_file=t_file2; // Copy result file
+ setup_io_cache(t_file);
+ }
+
+ DBUG_RETURN(*maxbuffer >= MERGEBUFF2); /* Return 1 if interrupted */
+} /* merge_many_buff */
+
+
+/**
+ Read data to buffer.
+
+ @retval
+ (uint)-1 if something goes wrong
+*/
+
+uint read_to_buffer(IO_CACHE *fromfile, BUFFPEK *buffpek,
+ uint rec_length)
+{
+ register uint count;
+ uint length;
+
+ if ((count=(uint) MY_MIN((ha_rows) buffpek->max_keys,buffpek->count)))
+ {
+ if (mysql_file_pread(fromfile->file, (uchar*) buffpek->base,
+ (length= rec_length*count),
+ buffpek->file_pos, MYF_RW))
+ return((uint) -1); /* purecov: inspected */
+ buffpek->key=buffpek->base;
+ buffpek->file_pos+= length; /* New filepos */
+ buffpek->count-= count;
+ buffpek->mem_count= count;
+ }
+ return (count*rec_length);
+} /* read_to_buffer */
+
+
+/**
+ Put all room used by freed buffer to use in adjacent buffer.
+
+ Note, that we can't simply distribute memory evenly between all buffers,
+ because new areas must not overlap with old ones.
+
+ @param[in] queue list of non-empty buffers, without freed buffer
+ @param[in] reuse empty buffer
+ @param[in] key_length key length
+*/
+
+void reuse_freed_buff(QUEUE *queue, BUFFPEK *reuse, uint key_length)
+{
+ uchar *reuse_end= reuse->base + reuse->max_keys * key_length;
+ for (uint i= queue_first_element(queue);
+ i <= queue_last_element(queue);
+ i++)
+ {
+ BUFFPEK *bp= (BUFFPEK *) queue_element(queue, i);
+ if (bp->base + bp->max_keys * key_length == reuse->base)
+ {
+ bp->max_keys+= reuse->max_keys;
+ return;
+ }
+ else if (bp->base == reuse_end)
+ {
+ bp->base= reuse->base;
+ bp->max_keys+= reuse->max_keys;
+ return;
+ }
+ }
+ DBUG_ASSERT(0);
+}
+
+
+/**
+ Merge buffers to one buffer.
+
+ @param param Sort parameter
+ @param from_file File with source data (BUFFPEKs point to this file)
+ @param to_file File to write the sorted result data.
+ @param sort_buffer Buffer for data to store up to MERGEBUFF2 sort keys.
+ @param lastbuff OUT Store here BUFFPEK describing data written to to_file
+ @param Fb First element in source BUFFPEKs array
+ @param Tb Last element in source BUFFPEKs array
+ @param flag
+
+ @retval
+ 0 OK
+ @retval
+ other error
+*/
+
+int merge_buffers(Sort_param *param, IO_CACHE *from_file,
+ IO_CACHE *to_file, uchar *sort_buffer,
+ BUFFPEK *lastbuff, BUFFPEK *Fb, BUFFPEK *Tb,
+ int flag)
+{
+ int error;
+ uint rec_length,res_length,offset;
+ size_t sort_length;
+ ulong maxcount;
+ ha_rows max_rows,org_max_rows;
+ my_off_t to_start_filepos;
+ uchar *strpos;
+ BUFFPEK *buffpek;
+ QUEUE queue;
+ qsort2_cmp cmp;
+ void *first_cmp_arg;
+ element_count dupl_count= 0;
+ uchar *src;
+ uchar *unique_buff= param->unique_buff;
+ const bool killable= !param->not_killable;
+ THD* const thd=current_thd;
+ DBUG_ENTER("merge_buffers");
+
+ thd->inc_status_sort_merge_passes();
+ thd->query_plan_fsort_passes++;
+
+ error=0;
+ rec_length= param->rec_length;
+ res_length= param->res_length;
+ sort_length= param->sort_length;
+ uint dupl_count_ofs= rec_length-sizeof(element_count);
+ uint min_dupl_count= param->min_dupl_count;
+ bool check_dupl_count= flag && min_dupl_count;
+ offset= (rec_length-
+ (flag && min_dupl_count ? sizeof(dupl_count) : 0)-res_length);
+ uint wr_len= flag ? res_length : rec_length;
+ uint wr_offset= flag ? offset : 0;
+ maxcount= (ulong) (param->max_keys_per_buffer/((uint) (Tb-Fb) +1));
+ to_start_filepos= my_b_tell(to_file);
+ strpos= sort_buffer;
+ org_max_rows=max_rows= param->max_rows;
+
+ set_if_bigger(maxcount, 1);
+
+ if (unique_buff)
+ {
+ cmp= param->compare;
+ first_cmp_arg= (void *) &param->cmp_context;
+ }
+ else
+ {
+ cmp= get_ptr_compare(sort_length);
+ first_cmp_arg= (void*) &sort_length;
+ }
+ if (init_queue(&queue, (uint) (Tb-Fb)+1, offsetof(BUFFPEK,key), 0,
+ (queue_compare) cmp, first_cmp_arg, 0, 0))
+ DBUG_RETURN(1); /* purecov: inspected */
+ for (buffpek= Fb ; buffpek <= Tb ; buffpek++)
+ {
+ buffpek->base= strpos;
+ buffpek->max_keys= maxcount;
+ strpos+=
+ (uint) (error= (int) read_to_buffer(from_file, buffpek, rec_length));
+
+ if (error == -1)
+ goto err; /* purecov: inspected */
+ buffpek->max_keys= buffpek->mem_count; // If less data in buffers than expected
+ queue_insert(&queue, (uchar*) buffpek);
+ }
+
+ if (unique_buff)
+ {
+ /*
+ Called by Unique::get()
+ Copy the first argument to unique_buff for unique removal.
+ Store it also in 'to_file'.
+ */
+ buffpek= (BUFFPEK*) queue_top(&queue);
+ memcpy(unique_buff, buffpek->key, rec_length);
+ if (min_dupl_count)
+ memcpy(&dupl_count, unique_buff+dupl_count_ofs,
+ sizeof(dupl_count));
+ buffpek->key+= rec_length;
+ if (! --buffpek->mem_count)
+ {
+ if (!(error= (int) read_to_buffer(from_file, buffpek,
+ rec_length)))
+ {
+ queue_remove(&queue,0);
+ reuse_freed_buff(&queue, buffpek, rec_length);
+ }
+ else if (error == -1)
+ goto err; /* purecov: inspected */
+ }
+ queue_replace_top(&queue); // Top element has been used
+ }
+ else
+ cmp= 0; // Not unique
+
+ while (queue.elements > 1)
+ {
+ if (killable && thd->check_killed())
+ {
+ error= 1; goto err; /* purecov: inspected */
+ }
+ for (;;)
+ {
+ buffpek= (BUFFPEK*) queue_top(&queue);
+ src= buffpek->key;
+ if (cmp) // Remove duplicates
+ {
+ if (!(*cmp)(first_cmp_arg, &unique_buff,
+ (uchar**) &buffpek->key))
+ {
+ if (min_dupl_count)
+ {
+ element_count cnt;
+ memcpy(&cnt, (uchar *) buffpek->key+dupl_count_ofs, sizeof(cnt));
+ dupl_count+= cnt;
+ }
+ goto skip_duplicate;
+ }
+ if (min_dupl_count)
+ {
+ memcpy(unique_buff+dupl_count_ofs, &dupl_count,
+ sizeof(dupl_count));
+ }
+ src= unique_buff;
+ }
+
+ /*
+ Do not write into the output file if this is the final merge called
+ for a Unique object used for intersection and dupl_count is less
+ than min_dupl_count.
+ If the Unique object is used to intersect N sets of unique elements
+ then for any element:
+ dupl_count >= N <=> the element is occurred in each of these N sets.
+ */
+ if (!check_dupl_count || dupl_count >= min_dupl_count)
+ {
+ if (my_b_write(to_file, src+wr_offset, wr_len))
+ {
+ error=1; goto err; /* purecov: inspected */
+ }
+ }
+ if (cmp)
+ {
+ memcpy(unique_buff, (uchar*) buffpek->key, rec_length);
+ if (min_dupl_count)
+ memcpy(&dupl_count, unique_buff+dupl_count_ofs,
+ sizeof(dupl_count));
+ }
+ if (!--max_rows)
+ {
+ error= 0; /* purecov: inspected */
+ goto end; /* purecov: inspected */
+ }
+
+ skip_duplicate:
+ buffpek->key+= rec_length;
+ if (! --buffpek->mem_count)
+ {
+ if (!(error= (int) read_to_buffer(from_file, buffpek,
+ rec_length)))
+ {
+ (void) queue_remove_top(&queue);
+ reuse_freed_buff(&queue, buffpek, rec_length);
+ break; /* One buffer have been removed */
+ }
+ else if (error == -1)
+ goto err; /* purecov: inspected */
+ }
+ queue_replace_top(&queue); /* Top element has been replaced */
+ }
+ }
+ buffpek= (BUFFPEK*) queue_top(&queue);
+ buffpek->base= (uchar*) sort_buffer;
+ buffpek->max_keys= param->max_keys_per_buffer;
+
+ /*
+ As we know all entries in the buffer are unique, we only have to
+ check if the first one is the same as the last one we wrote
+ */
+ if (cmp)
+ {
+ if (!(*cmp)(first_cmp_arg, &unique_buff, (uchar**) &buffpek->key))
+ {
+ if (min_dupl_count)
+ {
+ element_count cnt;
+ memcpy(&cnt, (uchar *) buffpek->key+dupl_count_ofs, sizeof(cnt));
+ dupl_count+= cnt;
+ }
+ buffpek->key+= rec_length;
+ --buffpek->mem_count;
+ }
+
+ if (min_dupl_count)
+ memcpy(unique_buff+dupl_count_ofs, &dupl_count,
+ sizeof(dupl_count));
+
+ if (!check_dupl_count || dupl_count >= min_dupl_count)
+ {
+ src= unique_buff;
+ if (my_b_write(to_file, src+wr_offset, wr_len))
+ {
+ error=1; goto err; /* purecov: inspected */
+ }
+ if (!--max_rows)
+ {
+ error= 0;
+ goto end;
+ }
+ }
+ }
+
+ do
+ {
+ if ((ha_rows) buffpek->mem_count > max_rows)
+ { /* Don't write too many records */
+ buffpek->mem_count= (uint) max_rows;
+ buffpek->count= 0; /* Don't read more */
+ }
+ max_rows-= buffpek->mem_count;
+ if (flag == 0)
+ {
+ if (my_b_write(to_file, (uchar*) buffpek->key,
+ (rec_length*buffpek->mem_count)))
+ {
+ error= 1; goto err; /* purecov: inspected */
+ }
+ }
+ else
+ {
+ register uchar *end;
+ src= buffpek->key+offset;
+ for (end= src+buffpek->mem_count*rec_length ;
+ src != end ;
+ src+= rec_length)
+ {
+ if (check_dupl_count)
+ {
+ memcpy((uchar *) &dupl_count, src+dupl_count_ofs, sizeof(dupl_count));
+ if (dupl_count < min_dupl_count)
+ continue;
+ }
+ if (my_b_write(to_file, src, wr_len))
+ {
+ error=1; goto err;
+ }
+ }
+ }
+ }
+ while ((error=(int) read_to_buffer(from_file, buffpek, rec_length))
+ != -1 && error != 0);
+
+end:
+ lastbuff->count= MY_MIN(org_max_rows-max_rows, param->max_rows);
+ lastbuff->file_pos= to_start_filepos;
+err:
+ delete_queue(&queue);
+ DBUG_RETURN(error);
+} /* merge_buffers */
+
+
+ /* Do a merge to output-file (save only positions) */
+
+int merge_index(Sort_param *param, uchar *sort_buffer,
+ BUFFPEK *buffpek, uint maxbuffer,
+ IO_CACHE *tempfile, IO_CACHE *outfile)
+{
+ DBUG_ENTER("merge_index");
+ if (merge_buffers(param,tempfile,outfile,sort_buffer,buffpek,buffpek,
+ buffpek+maxbuffer,1))
+ DBUG_RETURN(1); /* purecov: inspected */
+ DBUG_RETURN(0);
+} /* merge_index */
+
+
+static uint suffix_length(ulong string_length)
+{
+ if (string_length < 256)
+ return 1;
+ if (string_length < 256L*256L)
+ return 2;
+ if (string_length < 256L*256L*256L)
+ return 3;
+ return 4; // Can't sort longer than 4G
+}
+
+
+
+/**
+ Calculate length of sort key.
+
+ @param thd Thread handler
+ @param sortorder Order of items to sort
+ @param s_length Number of items to sort
+ @param[out] multi_byte_charset Set to 1 if we are using multi-byte charset
+ (In which case we have to use strxnfrm())
+
+ @note
+ sortorder->length is updated for each sort item.
+ @n
+ sortorder->need_strxnfrm is set 1 if we have to use strxnfrm
+
+ @return
+ Total length of sort buffer in bytes
+*/
+
+static uint
+sortlength(THD *thd, SORT_FIELD *sortorder, uint s_length,
+ bool *multi_byte_charset)
+{
+ reg2 uint length;
+ const CHARSET_INFO *cs;
+ *multi_byte_charset= 0;
+
+ length=0;
+ for (; s_length-- ; sortorder++)
+ {
+ sortorder->need_strxnfrm= 0;
+ sortorder->suffix_length= 0;
+ if (sortorder->field)
+ {
+ cs= sortorder->field->sort_charset();
+ sortorder->length= sortorder->field->sort_length();
+
+ if (use_strnxfrm((cs=sortorder->field->sort_charset())))
+ {
+ sortorder->need_strxnfrm= 1;
+ *multi_byte_charset= 1;
+ sortorder->length= cs->coll->strnxfrmlen(cs, sortorder->length);
+ }
+ if (sortorder->field->maybe_null())
+ length++; // Place for NULL marker
+ }
+ else
+ {
+ sortorder->result_type= sortorder->item->cmp_type();
+ switch (sortorder->result_type) {
+ case STRING_RESULT:
+ sortorder->length=sortorder->item->max_length;
+ set_if_smaller(sortorder->length, thd->variables.max_sort_length);
+ if (use_strnxfrm((cs=sortorder->item->collation.collation)))
+ {
+ sortorder->length= cs->coll->strnxfrmlen(cs, sortorder->length);
+ sortorder->need_strxnfrm= 1;
+ *multi_byte_charset= 1;
+ }
+ else if (cs == &my_charset_bin)
+ {
+ /* Store length last to be able to sort blob/varbinary */
+ sortorder->suffix_length= suffix_length(sortorder->length);
+ sortorder->length+= sortorder->suffix_length;
+ }
+ break;
+ case TIME_RESULT:
+ case INT_RESULT:
+ sortorder->length=8; // Size of intern longlong
+ break;
+ case DECIMAL_RESULT:
+ sortorder->length=
+ my_decimal_get_binary_size(sortorder->item->max_length -
+ (sortorder->item->decimals ? 1 : 0),
+ sortorder->item->decimals);
+ break;
+ case REAL_RESULT:
+ sortorder->length=sizeof(double);
+ break;
+ case ROW_RESULT:
+ default:
+ // This case should never be choosen
+ DBUG_ASSERT(0);
+ break;
+ }
+ if (sortorder->item->maybe_null)
+ length++; // Place for NULL marker
+ }
+ set_if_smaller(sortorder->length, thd->variables.max_sort_length);
+ length+=sortorder->length;
+ }
+ sortorder->field= (Field*) 0; // end marker
+ DBUG_PRINT("info",("sort_length: %d",length));
+ return length;
+}
+
+
+/**
+ Get descriptors of fields appended to sorted fields and
+ calculate its total length.
+
+ The function first finds out what fields are used in the result set.
+ Then it calculates the length of the buffer to store the values of
+ these fields together with the value of sort values.
+ If the calculated length is not greater than max_length_for_sort_data
+ the function allocates memory for an array of descriptors containing
+ layouts for the values of the non-sorted fields in the buffer and
+ fills them.
+
+ @param thd Current thread
+ @param ptabfield Array of references to the table fields
+ @param sortlength Total length of sorted fields
+ @param[out] plength Total length of appended fields
+
+ @note
+ The null bits for the appended values are supposed to be put together
+ and stored the buffer just ahead of the value of the first field.
+
+ @return
+ Pointer to the layout descriptors for the appended fields, if any
+ @retval
+ NULL if we do not store field values with sort data.
+*/
+
+static SORT_ADDON_FIELD *
+get_addon_fields(ulong max_length_for_sort_data,
+ Field **ptabfield, uint sortlength, uint *plength)
+{
+ Field **pfield;
+ Field *field;
+ SORT_ADDON_FIELD *addonf;
+ uint length= 0;
+ uint fields= 0;
+ uint null_fields= 0;
+ MY_BITMAP *read_set= (*ptabfield)->table->read_set;
+
+ /*
+ If there is a reference to a field in the query add it
+ to the the set of appended fields.
+ Note for future refinement:
+ This this a too strong condition.
+ Actually we need only the fields referred in the
+ result set. And for some of them it makes sense to use
+ the values directly from sorted fields.
+ But beware the case when item->cmp_type() != item->result_type()
+ */
+ *plength= 0;
+
+ for (pfield= ptabfield; (field= *pfield) ; pfield++)
+ {
+ if (!bitmap_is_set(read_set, field->field_index))
+ continue;
+ if (field->flags & BLOB_FLAG)
+ return 0;
+ length+= field->max_packed_col_length(field->pack_length());
+ if (field->maybe_null())
+ null_fields++;
+ fields++;
+ }
+ if (!fields)
+ return 0;
+ length+= (null_fields+7)/8;
+
+ if (length+sortlength > max_length_for_sort_data ||
+ !(addonf= (SORT_ADDON_FIELD *) my_malloc(sizeof(SORT_ADDON_FIELD)*
+ (fields+1),
+ MYF(MY_WME |
+ MY_THREAD_SPECIFIC))))
+ return 0;
+
+ *plength= length;
+ length= (null_fields+7)/8;
+ null_fields= 0;
+ for (pfield= ptabfield; (field= *pfield) ; pfield++)
+ {
+ if (!bitmap_is_set(read_set, field->field_index))
+ continue;
+ addonf->field= field;
+ addonf->offset= length;
+ if (field->maybe_null())
+ {
+ addonf->null_offset= null_fields/8;
+ addonf->null_bit= 1<<(null_fields & 7);
+ null_fields++;
+ }
+ else
+ {
+ addonf->null_offset= 0;
+ addonf->null_bit= 0;
+ }
+ addonf->length= field->max_packed_col_length(field->pack_length());
+ length+= addonf->length;
+ addonf++;
+ }
+ addonf->field= 0; // Put end marker
+
+ DBUG_PRINT("info",("addon_length: %d",length));
+ return (addonf-fields);
+}
+
+
+/**
+ Copy (unpack) values appended to sorted fields from a buffer back to
+ their regular positions specified by the Field::ptr pointers.
+
+ @param addon_field Array of descriptors for appended fields
+ @param buff Buffer which to unpack the value from
+
+ @note
+ The function is supposed to be used only as a callback function
+ when getting field values for the sorted result set.
+
+ @return
+ void.
+*/
+
+static void
+unpack_addon_fields(struct st_sort_addon_field *addon_field, uchar *buff,
+ uchar *buff_end)
+{
+ Field *field;
+ SORT_ADDON_FIELD *addonf= addon_field;
+
+ for ( ; (field= addonf->field) ; addonf++)
+ {
+ if (addonf->null_bit && (addonf->null_bit & buff[addonf->null_offset]))
+ {
+ field->set_null();
+ continue;
+ }
+ field->set_notnull();
+ field->unpack(field->ptr, buff + addonf->offset, buff_end, 0);
+ }
+}
+
+/*
+** functions to change a double or float to a sortable string
+** The following should work for IEEE
+*/
+
+#define DBL_EXP_DIG (sizeof(double)*8-DBL_MANT_DIG)
+
+void change_double_for_sort(double nr,uchar *to)
+{
+ uchar *tmp=(uchar*) to;
+ if (nr == 0.0)
+ { /* Change to zero string */
+ tmp[0]=(uchar) 128;
+ memset(tmp+1, 0, sizeof(nr)-1);
+ }
+ else
+ {
+#ifdef WORDS_BIGENDIAN
+ memcpy(tmp, &nr, sizeof(nr));
+#else
+ {
+ uchar *ptr= (uchar*) &nr;
+#if defined(__FLOAT_WORD_ORDER) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN)
+ tmp[0]= ptr[3]; tmp[1]=ptr[2]; tmp[2]= ptr[1]; tmp[3]=ptr[0];
+ tmp[4]= ptr[7]; tmp[5]=ptr[6]; tmp[6]= ptr[5]; tmp[7]=ptr[4];
+#else
+ tmp[0]= ptr[7]; tmp[1]=ptr[6]; tmp[2]= ptr[5]; tmp[3]=ptr[4];
+ tmp[4]= ptr[3]; tmp[5]=ptr[2]; tmp[6]= ptr[1]; tmp[7]=ptr[0];
+#endif
+ }
+#endif
+ if (tmp[0] & 128) /* Negative */
+ { /* make complement */
+ uint i;
+ for (i=0 ; i < sizeof(nr); i++)
+ tmp[i]=tmp[i] ^ (uchar) 255;
+ }
+ else
+ { /* Set high and move exponent one up */
+ ushort exp_part=(((ushort) tmp[0] << 8) | (ushort) tmp[1] |
+ (ushort) 32768);
+ exp_part+= (ushort) 1 << (16-1-DBL_EXP_DIG);
+ tmp[0]= (uchar) (exp_part >> 8);
+ tmp[1]= (uchar) exp_part;
+ }
+ }
+}
+
diff --git a/sql/filesort.h b/sql/filesort.h
new file mode 100644
index 00000000000..8960fa6cb66
--- /dev/null
+++ b/sql/filesort.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef FILESORT_INCLUDED
+#define FILESORT_INCLUDED
+
+class SQL_SELECT;
+
+#include "my_global.h" /* uint, uchar */
+#include "my_base.h" /* ha_rows */
+
+class SQL_SELECT;
+class THD;
+struct TABLE;
+typedef struct st_sort_field SORT_FIELD;
+
+ha_rows filesort(THD *thd, TABLE *table, st_sort_field *sortorder,
+ uint s_length, SQL_SELECT *select,
+ ha_rows max_rows, bool sort_positions,
+ ha_rows *examined_rows, ha_rows *found_rows);
+void filesort_free_buffers(TABLE *table, bool full);
+void change_double_for_sort(double nr,uchar *to);
+
+#endif /* FILESORT_INCLUDED */
diff --git a/sql/filesort_utils.cc b/sql/filesort_utils.cc
new file mode 100644
index 00000000000..1cef30b6a56
--- /dev/null
+++ b/sql/filesort_utils.cc
@@ -0,0 +1,143 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include "filesort_utils.h"
+#include "sql_const.h"
+#include "sql_sort.h"
+#include "table.h"
+#include "my_sys.h"
+
+
+namespace {
+/**
+ A local helper function. See comments for get_merge_buffers_cost().
+ */
+double get_merge_cost(ha_rows num_elements, ha_rows num_buffers, uint elem_size)
+{
+ return
+ 2.0 * ((double) num_elements * elem_size) / IO_SIZE
+ + (double) num_elements * log((double) num_buffers) /
+ (TIME_FOR_COMPARE_ROWID * M_LN2);
+}
+}
+
+/**
+ This is a simplified, and faster version of @see get_merge_many_buffs_cost().
+ We calculate the cost of merging buffers, by simulating the actions
+ of @see merge_many_buff. For explanations of formulas below,
+ see comments for get_merge_buffers_cost().
+ TODO: Use this function for Unique::get_use_cost().
+*/
+double get_merge_many_buffs_cost_fast(ha_rows num_rows,
+ ha_rows num_keys_per_buffer,
+ uint elem_size)
+{
+ ha_rows num_buffers= num_rows / num_keys_per_buffer;
+ ha_rows last_n_elems= num_rows % num_keys_per_buffer;
+ double total_cost;
+
+ // Calculate CPU cost of sorting buffers.
+ total_cost=
+ ( num_buffers * num_keys_per_buffer * log(1.0 + num_keys_per_buffer) +
+ last_n_elems * log(1.0 + last_n_elems) )
+ / TIME_FOR_COMPARE_ROWID;
+
+ // Simulate behavior of merge_many_buff().
+ while (num_buffers >= MERGEBUFF2)
+ {
+ // Calculate # of calls to merge_buffers().
+ const ha_rows loop_limit= num_buffers - MERGEBUFF*3/2;
+ const ha_rows num_merge_calls= 1 + loop_limit/MERGEBUFF;
+ const ha_rows num_remaining_buffs=
+ num_buffers - num_merge_calls * MERGEBUFF;
+
+ // Cost of merge sort 'num_merge_calls'.
+ total_cost+=
+ num_merge_calls *
+ get_merge_cost(num_keys_per_buffer * MERGEBUFF, MERGEBUFF, elem_size);
+
+ // # of records in remaining buffers.
+ last_n_elems+= num_remaining_buffs * num_keys_per_buffer;
+
+ // Cost of merge sort of remaining buffers.
+ total_cost+=
+ get_merge_cost(last_n_elems, 1 + num_remaining_buffs, elem_size);
+
+ num_buffers= num_merge_calls;
+ num_keys_per_buffer*= MERGEBUFF;
+ }
+
+ // Simulate final merge_buff call.
+ last_n_elems+= num_keys_per_buffer * num_buffers;
+ total_cost+= get_merge_cost(last_n_elems, 1 + num_buffers, elem_size);
+ return total_cost;
+}
+
+uchar **Filesort_buffer::alloc_sort_buffer(uint num_records, uint record_length)
+{
+ ulong sort_buff_sz;
+
+ DBUG_ENTER("alloc_sort_buffer");
+
+ DBUG_EXECUTE_IF("alloc_sort_buffer_fail",
+ DBUG_SET("+d,simulate_out_of_memory"););
+
+ if (m_idx_array.is_null())
+ {
+ sort_buff_sz= num_records * (record_length + sizeof(uchar*));
+ set_if_bigger(sort_buff_sz, record_length * MERGEBUFF2);
+ uchar **sort_keys=
+ (uchar**) my_malloc(sort_buff_sz, MYF(MY_THREAD_SPECIFIC));
+ m_idx_array= Idx_array(sort_keys, num_records);
+ m_record_length= record_length;
+ uchar **start_of_data= m_idx_array.array() + m_idx_array.size();
+ m_start_of_data= reinterpret_cast<uchar*>(start_of_data);
+ }
+ else
+ {
+ DBUG_ASSERT(num_records == m_idx_array.size());
+ DBUG_ASSERT(record_length == m_record_length);
+ }
+ DBUG_RETURN(m_idx_array.array());
+}
+
+
+void Filesort_buffer::free_sort_buffer()
+{
+ my_free(m_idx_array.array());
+ m_idx_array= Idx_array();
+ m_record_length= 0;
+ m_start_of_data= NULL;
+}
+
+
+void Filesort_buffer::sort_buffer(const Sort_param *param, uint count)
+{
+ size_t size= param->sort_length;
+ if (count <= 1 || size == 0)
+ return;
+ uchar **keys= get_sort_keys();
+ uchar **buffer= NULL;
+ if (radixsort_is_appliccable(count, param->sort_length) &&
+ (buffer= (uchar**) my_malloc(count*sizeof(char*),
+ MYF(MY_THREAD_SPECIFIC))))
+ {
+ radixsort_for_str_ptr(keys, count, param->sort_length, buffer);
+ my_free(buffer);
+ return;
+ }
+
+ my_qsort2(keys, count, sizeof(uchar*), get_ptr_compare(size), &size);
+}
diff --git a/sql/filesort_utils.h b/sql/filesort_utils.h
new file mode 100644
index 00000000000..00fa6f2566b
--- /dev/null
+++ b/sql/filesort_utils.h
@@ -0,0 +1,129 @@
+/* Copyright (c) 2010, 2012 Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef FILESORT_UTILS_INCLUDED
+#define FILESORT_UTILS_INCLUDED
+
+#include "my_global.h"
+#include "my_base.h"
+#include "sql_array.h"
+
+class Sort_param;
+/*
+ Calculate cost of merge sort
+
+ @param num_rows Total number of rows.
+ @param num_keys_per_buffer Number of keys per buffer.
+ @param elem_size Size of each element.
+
+ Calculates cost of merge sort by simulating call to merge_many_buff().
+
+ @retval
+ Computed cost of merge sort in disk seeks.
+
+ @note
+ Declared here in order to be able to unit test it,
+ since library dependencies have not been sorted out yet.
+
+ See also comments get_merge_many_buffs_cost().
+*/
+
+double get_merge_many_buffs_cost_fast(ha_rows num_rows,
+ ha_rows num_keys_per_buffer,
+ uint elem_size);
+
+
+/**
+ A wrapper class around the buffer used by filesort().
+ The buffer is a contiguous chunk of memory,
+ where the first part is <num_records> pointers to the actual data.
+
+ We wrap the buffer in order to be able to do lazy initialization of the
+ pointers: the buffer is often much larger than what we actually need.
+
+ The buffer must be kept available for multiple executions of the
+ same sort operation, so we have explicit allocate and free functions,
+ rather than doing alloc/free in CTOR/DTOR.
+*/
+class Filesort_buffer
+{
+public:
+ Filesort_buffer() :
+ m_idx_array(), m_record_length(0), m_start_of_data(NULL)
+ {}
+
+ /** Sort me... */
+ void sort_buffer(const Sort_param *param, uint count);
+
+ /// Initializes a record pointer.
+ uchar *get_record_buffer(uint idx)
+ {
+ m_idx_array[idx]= m_start_of_data + (idx * m_record_length);
+ return m_idx_array[idx];
+ }
+
+ /// Initializes all the record pointers.
+ void init_record_pointers()
+ {
+ for (uint ix= 0; ix < m_idx_array.size(); ++ix)
+ (void) get_record_buffer(ix);
+ }
+
+ /// Returns total size: pointer array + record buffers.
+ size_t sort_buffer_size() const
+ {
+ return m_idx_array.size() * (m_record_length + sizeof(uchar*));
+ }
+
+ /// Allocates the buffer, but does *not* initialize pointers.
+ uchar **alloc_sort_buffer(uint num_records, uint record_length);
+
+
+ /// Check <num_records, record_length> for the buffer
+ bool check_sort_buffer_properties(uint num_records, uint record_length)
+ {
+ return (static_cast<uint>(m_idx_array.size()) == num_records &&
+ m_record_length == record_length);
+ }
+
+ /// Frees the buffer.
+ void free_sort_buffer();
+
+ /// Getter, for calling routines which still use the uchar** interface.
+ uchar **get_sort_keys() { return m_idx_array.array(); }
+
+ /**
+ We need an assignment operator, see filesort().
+ This happens to have the same semantics as the one that would be
+ generated by the compiler. We still implement it here, to show shallow
+ assignment explicitly: we have two objects sharing the same array.
+ */
+ Filesort_buffer &operator=(const Filesort_buffer &rhs)
+ {
+ m_idx_array= rhs.m_idx_array;
+ m_record_length= rhs.m_record_length;
+ m_start_of_data= rhs.m_start_of_data;
+ return *this;
+ }
+
+private:
+ typedef Bounds_checked_array<uchar*> Idx_array;
+
+ Idx_array m_idx_array;
+ uint m_record_length;
+ uchar *m_start_of_data;
+};
+
+#endif // FILESORT_UTILS_INCLUDED
diff --git a/sql/gcalc_slicescan.cc b/sql/gcalc_slicescan.cc
new file mode 100644
index 00000000000..251869cad03
--- /dev/null
+++ b/sql/gcalc_slicescan.cc
@@ -0,0 +1,1998 @@
+/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved.
+ Copyright (C) 2011 Monty Program Ab.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <m_string.h>
+
+#ifdef HAVE_SPATIAL
+
+#include "gcalc_slicescan.h"
+
+
+#define PH_DATA_OFFSET 8
+#define coord_to_float(d) ((double) d)
+#define coord_eq(a, b) (a == b)
+
+typedef int (*sc_compare_func)(const void*, const void*);
+
+#define LS_LIST_ITEM Gcalc_dyn_list::Item
+#define LS_COMPARE_FUNC_DECL sc_compare_func compare,
+#define LS_COMPARE_FUNC_CALL(list_el1, list_el2) (*compare)(list_el1, list_el2)
+#define LS_NEXT(A) (A)->next
+#define LS_SET_NEXT(A,val) (A)->next= val
+#define LS_P_NEXT(A) &(A)->next
+#define LS_NAME sort_list
+#define LS_SCOPE static
+#define LS_STRUCT_NAME sort_list_stack_struct
+#include "plistsort.c"
+
+
+#define GCALC_COORD_MINUS 0x80000000
+#define FIRST_DIGIT(d) ((d) & 0x7FFFFFFF)
+#define GCALC_SIGN(d) ((d) & 0x80000000)
+
+static Gcalc_scan_iterator::point *eq_sp(const Gcalc_heap::Info *pi)
+{
+ GCALC_DBUG_ASSERT(pi->type == Gcalc_heap::nt_eq_node);
+ return (Gcalc_scan_iterator::point *) pi->eq_data;
+}
+
+
+static Gcalc_scan_iterator::intersection_info *i_data(const Gcalc_heap::Info *pi)
+{
+ GCALC_DBUG_ASSERT(pi->type == Gcalc_heap::nt_intersection);
+ return (Gcalc_scan_iterator::intersection_info *) pi->intersection_data;
+}
+
+
+#ifndef GCALC_DBUG_OFF
+
+int gcalc_step_counter= 0;
+
+void GCALC_DBUG_CHECK_COUNTER()
+{
+ if (++gcalc_step_counter == 0)
+ GCALC_DBUG_PRINT(("step_counter_0"));
+ else
+ GCALC_DBUG_PRINT(("%d step_counter", gcalc_step_counter));
+}
+
+
+const char *gcalc_ev_name(int ev)
+{
+ switch (ev)
+ {
+ case scev_none:
+ return "n";
+ case scev_thread:
+ return "t";
+ case scev_two_threads:
+ return "tt";
+ case scev_end:
+ return "e";
+ case scev_two_ends:
+ return "ee";
+ case scev_intersection:
+ return "i";
+ case scev_point:
+ return "p";
+ case scev_single_point:
+ return "sp";
+ default:;
+ };
+ GCALC_DBUG_ASSERT(0);
+ return "unk";
+}
+
+
+static int gcalc_pi_str(char *str, const Gcalc_heap::Info *pi, const char *postfix)
+{
+ return sprintf(str, "%s %d %d | %s %d %d%s",
+ GCALC_SIGN(pi->ix[0]) ? "-":"", FIRST_DIGIT(pi->ix[0]),pi->ix[1],
+ GCALC_SIGN(pi->iy[0]) ? "-":"", FIRST_DIGIT(pi->iy[0]),pi->iy[1],
+ postfix);
+
+}
+
+
+static void GCALC_DBUG_PRINT_PI(const Gcalc_heap::Info *pi)
+{
+ char buf[128];
+ int n_buf;
+ if (pi->type == Gcalc_heap::nt_intersection)
+ {
+ const Gcalc_scan_iterator::intersection_info *ic= i_data(pi);
+
+ GCALC_DBUG_PRINT(("intersection point %d %d",
+ ic->edge_a->thread, ic->edge_b->thread));
+ return;
+ }
+ if (pi->type == Gcalc_heap::nt_eq_node)
+ {
+ const Gcalc_scan_iterator::point *e= eq_sp(pi);
+ GCALC_DBUG_PRINT(("eq point %d", e->thread));
+ return;
+ }
+ n_buf= gcalc_pi_str(buf, pi, "");
+ buf[n_buf]= 0;
+ GCALC_DBUG_PRINT(("%s", buf));
+}
+
+
+static void GCALC_DBUG_PRINT_SLICE(const char *header,
+ const Gcalc_scan_iterator::point *slice)
+{
+ int nbuf;
+ char buf[1024];
+ nbuf= strlen(header);
+ strcpy(buf, header);
+ for (; slice; slice= slice->get_next())
+ {
+ int lnbuf= nbuf;
+ lnbuf+= sprintf(buf + lnbuf, "%d\t", slice->thread);
+ lnbuf+= sprintf(buf + lnbuf, "%s\t", gcalc_ev_name(slice->event));
+
+ lnbuf+= gcalc_pi_str(buf + lnbuf, slice->pi, "\t");
+ if (slice->is_bottom())
+ lnbuf+= sprintf(buf+lnbuf, "bt\t");
+ else
+ lnbuf+= gcalc_pi_str(buf+lnbuf, slice->next_pi, "\t");
+ buf[lnbuf]= 0;
+ GCALC_DBUG_PRINT(("%s", buf));
+ }
+}
+
+
+#else
+#define GCALC_DBUG_CHECK_COUNTER() do { } while(0)
+#define GCALC_DBUG_PRINT_PI(pi) do { } while(0)
+#define GCALC_DBUG_PRINT_SLICE(a, b) do { } while(0)
+#define GCALC_DBUG_PRINT_INTERSECTIONS(a) do { } while(0)
+#define GCALC_DBUG_PRINT_STATE(a) do { } while(0)
+#endif /*GCALC_DBUG_OFF*/
+
+
+Gcalc_dyn_list::Gcalc_dyn_list(size_t blk_size, size_t sizeof_item):
+ m_blk_size(blk_size - ALLOC_ROOT_MIN_BLOCK_SIZE),
+ m_sizeof_item(ALIGN_SIZE(sizeof_item)),
+ m_points_per_blk((m_blk_size - PH_DATA_OFFSET) / m_sizeof_item),
+ m_blk_hook(&m_first_blk),
+ m_free(NULL),
+ m_keep(NULL)
+{}
+
+
+void Gcalc_dyn_list::format_blk(void* block)
+{
+ Item *pi_end, *cur_pi, *first_pi;
+ GCALC_DBUG_ASSERT(m_free == NULL);
+ first_pi= cur_pi= (Item *)(((char *)block) + PH_DATA_OFFSET);
+ pi_end= ptr_add(first_pi, m_points_per_blk - 1);
+ do {
+ cur_pi= cur_pi->next= ptr_add(cur_pi, 1);
+ } while (cur_pi<pi_end);
+ cur_pi->next= m_free;
+ m_free= first_pi;
+}
+
+
+Gcalc_dyn_list::Item *Gcalc_dyn_list::alloc_new_blk()
+{
+ void *new_block= my_malloc(m_blk_size, MYF(MY_WME));
+ if (!new_block)
+ return NULL;
+ *m_blk_hook= new_block;
+ m_blk_hook= (void**)new_block;
+ format_blk(new_block);
+ return new_item();
+}
+
+
+static void free_blk_list(void *list)
+{
+ void *next_blk;
+ while (list)
+ {
+ next_blk= *((void **)list);
+ my_free(list);
+ list= next_blk;
+ }
+}
+
+
+void Gcalc_dyn_list::cleanup()
+{
+ *m_blk_hook= NULL;
+ free_blk_list(m_first_blk);
+ m_first_blk= NULL;
+ m_blk_hook= &m_first_blk;
+ m_free= NULL;
+}
+
+
+Gcalc_dyn_list::~Gcalc_dyn_list()
+{
+ cleanup();
+}
+
+
+void Gcalc_dyn_list::reset()
+{
+ *m_blk_hook= NULL;
+ if (m_first_blk)
+ {
+ free_blk_list(*((void **)m_first_blk));
+ m_blk_hook= (void**)m_first_blk;
+ m_free= NULL;
+ format_blk(m_first_blk);
+ }
+}
+
+
+/* Internal coordinate operations implementations */
+
+void gcalc_set_zero(Gcalc_internal_coord *d, int d_len)
+{
+ do
+ {
+ d[--d_len]= 0;
+ } while (d_len);
+}
+
+
+int gcalc_is_zero(const Gcalc_internal_coord *d, int d_len)
+{
+ do
+ {
+ if (d[--d_len] != 0)
+ return 0;
+ } while (d_len);
+ return 1;
+}
+
+
+#ifdef GCALC_CHECK_WITH_FLOAT
+static double *gcalc_coord_extent= NULL;
+
+long double gcalc_get_double(const Gcalc_internal_coord *d, int d_len)
+{
+ int n= 1;
+ long double res= (long double) FIRST_DIGIT(d[0]);
+ do
+ {
+ res*= (long double) GCALC_DIG_BASE;
+ res+= (long double) d[n];
+ } while(++n < d_len);
+
+ n= 0;
+ do
+ {
+ if ((n & 1) && gcalc_coord_extent)
+ res/= *gcalc_coord_extent;
+ } while(++n < d_len);
+
+ if (GCALC_SIGN(d[0]))
+ res*= -1.0;
+ return res;
+}
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+
+
+static void do_add(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b)
+{
+ int n_digit= result_len-1;
+ gcalc_digit_t carry= 0;
+
+ do
+ {
+ if ((result[n_digit]=
+ a[n_digit] + b[n_digit] + carry) >= GCALC_DIG_BASE)
+ {
+ carry= 1;
+ result[n_digit]-= GCALC_DIG_BASE;
+ }
+ else
+ carry= 0;
+ } while (--n_digit);
+
+ result[0]= (a[0] + FIRST_DIGIT(b[0]) + carry);
+
+ GCALC_DBUG_ASSERT(FIRST_DIGIT(result[0]) < GCALC_DIG_BASE);
+}
+
+
+static void do_sub(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b)
+{
+ int n_digit= result_len-1;
+ gcalc_digit_t carry= 0;
+ gcalc_digit_t cur_b, cur_a;
+
+ do
+ {
+ cur_b= b[n_digit] + carry;
+ cur_a= a[n_digit];
+ if (cur_a < cur_b)
+ {
+ carry= 1;
+ result[n_digit]= (GCALC_DIG_BASE - cur_b) + cur_a;
+ }
+ else
+ {
+ carry= 0;
+ result[n_digit]= cur_a - cur_b;
+ }
+ } while (--n_digit);
+
+
+ result[0]= a[0] - FIRST_DIGIT(b[0]) - carry;
+
+ GCALC_DBUG_ASSERT(FIRST_DIGIT(a[0]) >= FIRST_DIGIT(b[0]) + carry);
+ GCALC_DBUG_ASSERT(!gcalc_is_zero(result, result_len));
+}
+/*
+static void do_sub(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b)
+{
+ int n_digit= result_len-1;
+ gcalc_digit_t carry= 0;
+
+ do
+ {
+ if ((result[n_digit]= a[n_digit] - b[n_digit] - carry) < 0)
+ {
+ carry= 1;
+ result[n_digit]+= GCALC_DIG_BASE;
+ }
+ else
+ carry= 0;
+ } while (--n_digit);
+
+
+ result[0]= a[0] - FIRST_DIGIT(b[0]) - carry;
+
+ GCALC_DBUG_ASSERT(FIRST_DIGIT(a[0]) - FIRST_DIGIT(b[0]) - carry >= 0);
+ GCALC_DBUG_ASSERT(!gcalc_is_zero(result, result_len));
+}
+*/
+
+static int do_cmp(const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b, int len)
+{
+ int n_digit= 1;
+
+ if ((FIRST_DIGIT(a[0]) != FIRST_DIGIT(b[0])))
+ return FIRST_DIGIT(a[0]) > FIRST_DIGIT(b[0]) ? 1 : -1;
+
+ do
+ {
+ if ((a[n_digit] != b[n_digit]))
+ return a[n_digit] > b[n_digit] ? 1 : -1;
+ } while (++n_digit < len);
+
+ return 0;
+}
+
+
+#ifdef GCALC_CHECK_WITH_FLOAT
+static int de_weak_check(long double a, long double b, long double ex)
+{
+ long double d= a - b;
+ if (d < ex && d > -ex)
+ return 1;
+
+ d/= fabsl(a) + fabsl(b);
+ if (d < ex && d > -ex)
+ return 1;
+ return 0;
+}
+
+static int de_check(long double a, long double b)
+{
+ return de_weak_check(a, b, (long double) 1e-9);
+}
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+
+
+void gcalc_mul_coord(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a, int a_len,
+ const Gcalc_internal_coord *b, int b_len)
+{
+ GCALC_DBUG_ASSERT(result_len == a_len + b_len);
+ GCALC_DBUG_ASSERT(a_len >= b_len);
+ int n_a, n_b, n_res;
+ gcalc_digit_t carry= 0;
+
+ gcalc_set_zero(result, result_len);
+
+ n_a= a_len - 1;
+ do
+ {
+ gcalc_coord2 cur_a= n_a ? a[n_a] : FIRST_DIGIT(a[0]);
+ n_b= b_len - 1;
+ do
+ {
+ gcalc_coord2 cur_b= n_b ? b[n_b] : FIRST_DIGIT(b[0]);
+ gcalc_coord2 mul= cur_a * cur_b + carry + result[n_a + n_b + 1];
+ result[n_a + n_b + 1]= mul % GCALC_DIG_BASE;
+ carry= (gcalc_digit_t) (mul / (gcalc_coord2) GCALC_DIG_BASE);
+ } while (n_b--);
+ if (carry)
+ {
+ for (n_res= n_a; (result[n_res]+= carry) >= GCALC_DIG_BASE;
+ n_res--)
+ {
+ result[n_res]-= GCALC_DIG_BASE;
+ carry= 1;
+ }
+ carry= 0;
+ }
+ } while (n_a--);
+ if (!gcalc_is_zero(result, result_len))
+ result[0]|= GCALC_SIGN(a[0] ^ b[0]);
+#ifdef GCALC_CHECK_WITH_FLOAT
+ GCALC_DBUG_ASSERT(de_check(gcalc_get_double(a, a_len) *
+ gcalc_get_double(b, b_len),
+ gcalc_get_double(result, result_len)));
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+}
+
+
+inline void gcalc_mul_coord1(Gcalc_coord1 result,
+ const Gcalc_coord1 a, const Gcalc_coord1 b)
+{
+ return gcalc_mul_coord(result, GCALC_COORD_BASE2,
+ a, GCALC_COORD_BASE, b, GCALC_COORD_BASE);
+}
+
+
+void gcalc_add_coord(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b)
+{
+ if (GCALC_SIGN(a[0]) == GCALC_SIGN(b[0]))
+ do_add(result, result_len, a, b);
+ else
+ {
+ int cmp_res= do_cmp(a, b, result_len);
+ if (cmp_res == 0)
+ gcalc_set_zero(result, result_len);
+ else if (cmp_res > 0)
+ do_sub(result, result_len, a, b);
+ else
+ do_sub(result, result_len, b, a);
+ }
+#ifdef GCALC_CHECK_WITH_FLOAT
+ GCALC_DBUG_ASSERT(de_check(gcalc_get_double(a, result_len) +
+ gcalc_get_double(b, result_len),
+ gcalc_get_double(result, result_len)));
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+}
+
+
+void gcalc_sub_coord(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b)
+{
+ if (GCALC_SIGN(a[0] ^ b[0]))
+ do_add(result, result_len, a, b);
+ else
+ {
+ int cmp_res= do_cmp(a, b, result_len);
+ if (cmp_res == 0)
+ gcalc_set_zero(result, result_len);
+ else if (cmp_res > 0)
+ do_sub(result, result_len, a, b);
+ else
+ {
+ do_sub(result, result_len, b, a);
+ result[0]^= GCALC_COORD_MINUS;
+ }
+ }
+#ifdef GCALC_CHECK_WITH_FLOAT
+ GCALC_DBUG_ASSERT(de_check(gcalc_get_double(a, result_len) -
+ gcalc_get_double(b, result_len),
+ gcalc_get_double(result, result_len)));
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+}
+
+
+inline void gcalc_sub_coord1(Gcalc_coord1 result,
+ const Gcalc_coord1 a, const Gcalc_coord1 b)
+{
+ return gcalc_sub_coord(result, GCALC_COORD_BASE, a, b);
+}
+
+
+int gcalc_cmp_coord(const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b, int len)
+{
+ int n_digit= 0;
+ int result= 0;
+
+ do
+ {
+ if (a[n_digit] == b[n_digit])
+ {
+ n_digit++;
+ continue;
+ }
+ if (a[n_digit] > b[n_digit])
+ result= GCALC_SIGN(a[0]) ? -1 : 1;
+ else
+ result= GCALC_SIGN(b[0]) ? 1 : -1;
+ break;
+
+ } while (n_digit < len);
+
+#ifdef GCALC_CHECK_WITH_FLOAT
+ if (result == 0)
+ GCALC_DBUG_ASSERT(de_check(gcalc_get_double(a, len),
+ gcalc_get_double(b, len)));
+ else if (result == 1)
+ GCALC_DBUG_ASSERT(de_check(gcalc_get_double(a, len),
+ gcalc_get_double(b, len)) ||
+ gcalc_get_double(a, len) > gcalc_get_double(b, len));
+ else
+ GCALC_DBUG_ASSERT(de_check(gcalc_get_double(a, len),
+ gcalc_get_double(b, len)) ||
+ gcalc_get_double(a, len) < gcalc_get_double(b, len));
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+ return result;
+}
+
+
+#define gcalc_cmp_coord1(a, b) gcalc_cmp_coord(a, b, GCALC_COORD_BASE)
+
+int gcalc_set_double(Gcalc_internal_coord *c, double d, double ext)
+{
+ int sign;
+ double ds= d * ext;
+ if ((sign= ds < 0))
+ ds= -ds;
+ c[0]= (gcalc_digit_t) (ds / (double) GCALC_DIG_BASE);
+ c[1]= (gcalc_digit_t) (ds - ((double) c[0]) * (double) GCALC_DIG_BASE);
+ if (c[1] >= GCALC_DIG_BASE)
+ {
+ c[1]= 0;
+ c[0]++;
+ }
+ if (sign && (c[0] | c[1]))
+ c[0]|= GCALC_COORD_MINUS;
+#ifdef GCALC_CHECK_WITH_FLOAT
+ GCALC_DBUG_ASSERT(de_check(d, gcalc_get_double(c, 2)));
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+ return 0;
+}
+
+
+typedef gcalc_digit_t Gcalc_coord4[GCALC_COORD_BASE*4];
+typedef gcalc_digit_t Gcalc_coord5[GCALC_COORD_BASE*5];
+
+
+void Gcalc_scan_iterator::intersection_info::do_calc_t()
+{
+ Gcalc_coord1 a2_a1x, a2_a1y;
+ Gcalc_coord2 x1y2, x2y1;
+
+ gcalc_sub_coord1(a2_a1x, edge_b->pi->ix, edge_a->pi->ix);
+ gcalc_sub_coord1(a2_a1y, edge_b->pi->iy, edge_a->pi->iy);
+
+ GCALC_DBUG_ASSERT(!gcalc_is_zero(edge_a->dy, GCALC_COORD_BASE) ||
+ !gcalc_is_zero(edge_b->dy, GCALC_COORD_BASE));
+
+ gcalc_mul_coord1(x1y2, edge_a->dx, edge_b->dy);
+ gcalc_mul_coord1(x2y1, edge_a->dy, edge_b->dx);
+ gcalc_sub_coord(t_b, GCALC_COORD_BASE2, x1y2, x2y1);
+
+
+ gcalc_mul_coord1(x1y2, a2_a1x, edge_b->dy);
+ gcalc_mul_coord1(x2y1, a2_a1y, edge_b->dx);
+ gcalc_sub_coord(t_a, GCALC_COORD_BASE2, x1y2, x2y1);
+ t_calculated= 1;
+}
+
+
+void Gcalc_scan_iterator::intersection_info::do_calc_y()
+{
+ GCALC_DBUG_ASSERT(t_calculated);
+
+ Gcalc_coord3 a_tb, b_ta;
+
+ gcalc_mul_coord(a_tb, GCALC_COORD_BASE3,
+ t_b, GCALC_COORD_BASE2, edge_a->pi->iy, GCALC_COORD_BASE);
+ gcalc_mul_coord(b_ta, GCALC_COORD_BASE3,
+ t_a, GCALC_COORD_BASE2, edge_a->dy, GCALC_COORD_BASE);
+
+ gcalc_add_coord(y_exp, GCALC_COORD_BASE3, a_tb, b_ta);
+ y_calculated= 1;
+}
+
+
+void Gcalc_scan_iterator::intersection_info::do_calc_x()
+{
+ GCALC_DBUG_ASSERT(t_calculated);
+
+ Gcalc_coord3 a_tb, b_ta;
+
+ gcalc_mul_coord(a_tb, GCALC_COORD_BASE3,
+ t_b, GCALC_COORD_BASE2, edge_a->pi->ix, GCALC_COORD_BASE);
+ gcalc_mul_coord(b_ta, GCALC_COORD_BASE3,
+ t_a, GCALC_COORD_BASE2, edge_a->dx, GCALC_COORD_BASE);
+
+ gcalc_add_coord(x_exp, GCALC_COORD_BASE3, a_tb, b_ta);
+ x_calculated= 1;
+}
+
+
+static int cmp_node_isc(const Gcalc_heap::Info *node,
+ const Gcalc_heap::Info *isc)
+{
+ GCALC_DBUG_ASSERT(node->type == Gcalc_heap::nt_shape_node);
+ Gcalc_scan_iterator::intersection_info *inf= i_data(isc);
+ Gcalc_coord3 exp;
+ int result;
+
+ inf->calc_t();
+ inf->calc_y_exp();
+
+ gcalc_mul_coord(exp, GCALC_COORD_BASE3,
+ inf->t_b, GCALC_COORD_BASE2, node->iy, GCALC_COORD_BASE);
+
+ result= gcalc_cmp_coord(exp, inf->y_exp, GCALC_COORD_BASE3);
+#ifdef GCALC_CHECK_WITH_FLOAT
+ long double int_x, int_y;
+ isc->calc_xy_ld(&int_x, &int_y);
+ if (result < 0)
+ {
+ if (!de_check(int_y, node->y) && node->y > int_y)
+ GCALC_DBUG_PRINT(("floatcheck cmp_nod_iscy %g < %LG", node->y, int_y));
+ }
+ else if (result > 0)
+ {
+ if (!de_check(int_y, node->y) && node->y < int_y)
+ GCALC_DBUG_PRINT(("floatcheck cmp_nod_iscy %g > %LG", node->y, int_y));
+ }
+ else
+ {
+ if (!de_check(int_y, node->y))
+ GCALC_DBUG_PRINT(("floatcheck cmp_nod_iscy %g == %LG", node->y, int_y));
+ }
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+ if (result)
+ goto exit;
+
+
+ inf->calc_x_exp();
+ gcalc_mul_coord(exp, GCALC_COORD_BASE3,
+ inf->t_b, GCALC_COORD_BASE2, node->ix, GCALC_COORD_BASE);
+
+ result= gcalc_cmp_coord(exp, inf->x_exp, GCALC_COORD_BASE3);
+#ifdef GCALC_CHECK_WITH_FLOAT
+ if (result < 0)
+ {
+ if (!de_check(int_x, node->x) && node->x > int_x)
+ GCALC_DBUG_PRINT(("floatcheck cmp_nod_iscx failed %g < %LG",
+ node->x, int_x));
+ }
+ else if (result > 0)
+ {
+ if (!de_check(int_x, node->x) && node->x < int_x)
+ GCALC_DBUG_PRINT(("floatcheck cmp_nod_iscx failed %g > %LG",
+ node->x, int_x));
+ }
+ else
+ {
+ if (!de_check(int_x, node->x))
+ GCALC_DBUG_PRINT(("floatcheck cmp_nod_iscx failed %g == %LG",
+ node->x, int_x));
+ }
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+exit:
+ return result;
+}
+
+
+static int cmp_intersections(const Gcalc_heap::Info *i1,
+ const Gcalc_heap::Info *i2)
+{
+ Gcalc_scan_iterator::intersection_info *inf1= i_data(i1);
+ Gcalc_scan_iterator::intersection_info *inf2= i_data(i2);
+ Gcalc_coord5 exp_a, exp_b;
+ int result;
+
+ inf1->calc_t();
+ inf2->calc_t();
+
+ inf1->calc_y_exp();
+ inf2->calc_y_exp();
+
+ gcalc_mul_coord(exp_a, GCALC_COORD_BASE5,
+ inf1->y_exp, GCALC_COORD_BASE3, inf2->t_b, GCALC_COORD_BASE2);
+ gcalc_mul_coord(exp_b, GCALC_COORD_BASE5,
+ inf2->y_exp, GCALC_COORD_BASE3, inf1->t_b, GCALC_COORD_BASE2);
+
+ result= gcalc_cmp_coord(exp_a, exp_b, GCALC_COORD_BASE5);
+#ifdef GCALC_CHECK_WITH_FLOAT
+ long double x1, y1, x2, y2;
+ i1->calc_xy_ld(&x1, &y1);
+ i2->calc_xy_ld(&x2, &y2);
+
+ if (result < 0)
+ {
+ if (!de_check(y1, y2) && y2 > y1)
+ GCALC_DBUG_PRINT(("floatcheck cmp_intersections_y failed %LG < %LG",
+ y2, y1));
+ }
+ else if (result > 0)
+ {
+ if (!de_check(y1, y2) && y2 < y1)
+ GCALC_DBUG_PRINT(("floatcheck cmp_intersections_y failed %LG > %LG",
+ y2, y1));
+ }
+ else
+ {
+ if (!de_check(y1, y2))
+ GCALC_DBUG_PRINT(("floatcheck cmp_intersections_y failed %LG == %LG",
+ y2, y1));
+ }
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+
+ if (result != 0)
+ return result;
+
+
+ inf1->calc_x_exp();
+ inf2->calc_x_exp();
+ gcalc_mul_coord(exp_a, GCALC_COORD_BASE5,
+ inf1->x_exp, GCALC_COORD_BASE3, inf2->t_b, GCALC_COORD_BASE2);
+ gcalc_mul_coord(exp_b, GCALC_COORD_BASE5,
+ inf2->x_exp, GCALC_COORD_BASE3, inf1->t_b, GCALC_COORD_BASE2);
+
+ result= gcalc_cmp_coord(exp_a, exp_b, GCALC_COORD_BASE5);
+#ifdef GCALC_CHECK_WITH_FLOAT
+ if (result < 0)
+ {
+ if (!de_check(x1, x2) && x2 > x1)
+ GCALC_DBUG_PRINT(("floatcheck cmp_intersectionsx failed %LG < %LG",
+ x2, x1));
+ }
+ else if (result > 0)
+ {
+ if (!de_check(x1, x2) && x2 < x1)
+ GCALC_DBUG_PRINT(("floatcheck cmp_intersectionsx failed %LG > %LG",
+ x2, x1));
+ }
+ else
+ {
+ if (!de_check(x1, x2))
+ GCALC_DBUG_PRINT(("floatcheck cmp_intersectionsx failed %LG == %LG",
+ x2, x1));
+ }
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+ return result;
+}
+/* Internal coordinates implementation end */
+
+
+#define GCALC_SCALE_1 1e18
+
+static double find_scale(double extent)
+{
+ double scale= 1e-2;
+ while (scale < extent)
+ scale*= (double ) 10;
+ return GCALC_SCALE_1 / scale / 10;
+}
+
+
+void Gcalc_heap::set_extent(double xmin, double xmax, double ymin, double ymax)
+{
+ xmin= fabs(xmin);
+ xmax= fabs(xmax);
+ ymin= fabs(ymin);
+ ymax= fabs(ymax);
+
+ if (xmax < xmin)
+ xmax= xmin;
+ if (ymax < ymin)
+ ymax= ymin;
+
+ coord_extent= xmax > ymax ? xmax : ymax;
+ coord_extent= find_scale(coord_extent);
+#ifdef GCALC_CHECK_WITH_FLOAT
+ gcalc_coord_extent= &coord_extent;
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+}
+
+
+void Gcalc_heap::free_point_info(Gcalc_heap::Info *i,
+ Gcalc_dyn_list::Item **i_hook)
+{
+ if (m_hook == &i->next)
+ m_hook= i_hook;
+ *i_hook= i->next;
+ free_item(i);
+ m_n_points--;
+}
+
+
+Gcalc_heap::Info *Gcalc_heap::new_point_info(double x, double y,
+ gcalc_shape_info shape)
+{
+ Info *result= (Info *)new_item();
+ if (!result)
+ return NULL;
+ *m_hook= result;
+ m_hook= &result->next;
+ result->x= x;
+ result->y= y;
+ result->shape= shape;
+ result->top_node= 1;
+ result->type= nt_shape_node;
+ gcalc_set_double(result->ix, x, coord_extent);
+ gcalc_set_double(result->iy, y, coord_extent);
+
+ m_n_points++;
+ return result;
+}
+
+
+static Gcalc_heap::Info *new_intersection(
+ Gcalc_heap *heap, Gcalc_scan_iterator::intersection_info *ii)
+{
+ Gcalc_heap::Info *isc= (Gcalc_heap::Info *)heap->new_item();
+ if (!isc)
+ return 0;
+ isc->type= Gcalc_heap::nt_intersection;
+ isc->p1= ii->edge_a->pi;
+ isc->p2= ii->edge_a->next_pi;
+ isc->p3= ii->edge_b->pi;
+ isc->p4= ii->edge_b->next_pi;
+ isc->intersection_data= ii;
+ return isc;
+}
+
+
+static Gcalc_heap::Info *new_eq_point(
+ Gcalc_heap *heap, const Gcalc_heap::Info *p,
+ Gcalc_scan_iterator::point *edge)
+{
+ Gcalc_heap::Info *eqp= (Gcalc_heap::Info *)heap->new_item();
+ if (!eqp)
+ return 0;
+ eqp->type= Gcalc_heap::nt_eq_node;
+ eqp->node= p;
+ eqp->eq_data= edge;
+ return eqp;
+}
+
+
+void Gcalc_heap::Info::calc_xy(double *x, double *y) const
+{
+ double b0_x= p2->x - p1->x;
+ double b0_y= p2->y - p1->y;
+ double b1_x= p4->x - p3->x;
+ double b1_y= p4->y - p3->y;
+ double b0xb1= b0_x * b1_y - b0_y * b1_x;
+ double t= (p3->x - p1->x) * b1_y - (p3->y - p1->y) * b1_x;
+
+ t/= b0xb1;
+
+ *x= p1->x + b0_x * t;
+ *y= p1->y + b0_y * t;
+}
+
+
+#ifdef GCALC_CHECK_WITH_FLOAT
+void Gcalc_heap::Info::calc_xy_ld(long double *x, long double *y) const
+{
+ long double b0_x= ((long double) p2->x) - p1->x;
+ long double b0_y= ((long double) p2->y) - p1->y;
+ long double b1_x= ((long double) p4->x) - p3->x;
+ long double b1_y= ((long double) p4->y) - p3->y;
+ long double b0xb1= b0_x * b1_y - b0_y * b1_x;
+ long double ax= ((long double) p3->x) - p1->x;
+ long double ay= ((long double) p3->y) - p1->y;
+ long double t_a= ax * b1_y - ay * b1_x;
+ long double hx= (b0xb1 * (long double) p1->x + b0_x * t_a);
+ long double hy= (b0xb1 * (long double) p1->y + b0_y * t_a);
+
+ if (fabs(b0xb1) < 1e-15)
+ {
+ *x= p1->x;
+ *y= p1->y;
+ return;
+ }
+
+ *x= hx/b0xb1;
+ *y= hy/b0xb1;
+}
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+
+
+static int cmp_point_info(const Gcalc_heap::Info *i0,
+ const Gcalc_heap::Info *i1)
+{
+ int cmp_y= gcalc_cmp_coord1(i0->iy, i1->iy);
+ if (cmp_y)
+ return cmp_y;
+ return gcalc_cmp_coord1(i0->ix, i1->ix);
+}
+
+
+static inline void trim_node(Gcalc_heap::Info *node, Gcalc_heap::Info *prev_node)
+{
+ if (!node)
+ return;
+ node->top_node= 0;
+ GCALC_DBUG_ASSERT((node->left == prev_node) || (node->right == prev_node));
+ if (node->left == prev_node)
+ node->left= node->right;
+ node->right= NULL;
+ GCALC_DBUG_ASSERT(cmp_point_info(node, prev_node));
+}
+
+
+static int compare_point_info(const void *e0, const void *e1)
+{
+ const Gcalc_heap::Info *i0= (const Gcalc_heap::Info *)e0;
+ const Gcalc_heap::Info *i1= (const Gcalc_heap::Info *)e1;
+ return cmp_point_info(i0, i1) > 0;
+}
+
+
+void Gcalc_heap::prepare_operation()
+{
+ Info *cur;
+ GCALC_DBUG_ASSERT(m_hook);
+ *m_hook= NULL;
+ m_hook= NULL; /* just to check it's not called twice */
+ m_first= sort_list(compare_point_info, m_first, m_n_points);
+
+ /* TODO - move this to the 'normal_scan' loop */
+ for (cur= get_first(); cur; cur= cur->get_next())
+ {
+ trim_node(cur->left, cur);
+ trim_node(cur->right, cur);
+ }
+}
+
+
+void Gcalc_heap::reset()
+{
+ if (m_n_points)
+ {
+ free_list(m_first);
+ m_n_points= 0;
+ }
+ m_hook= &m_first;
+}
+
+
+int Gcalc_shape_transporter::int_single_point(gcalc_shape_info Info,
+ double x, double y)
+{
+ Gcalc_heap::Info *point= m_heap->new_point_info(x, y, Info);
+ if (!point)
+ return 1;
+ point->left= point->right= 0;
+ return 0;
+}
+
+
+int Gcalc_shape_transporter::int_add_point(gcalc_shape_info Info,
+ double x, double y)
+{
+ Gcalc_heap::Info *point;
+ Gcalc_dyn_list::Item **hook;
+
+ hook= m_heap->get_cur_hook();
+
+ if (!(point= m_heap->new_point_info(x, y, Info)))
+ return 1;
+ if (m_first)
+ {
+ if (cmp_point_info(m_prev, point) == 0)
+ {
+ /* Coinciding points, do nothing */
+ m_heap->free_point_info(point, hook);
+ return 0;
+ }
+ GCALC_DBUG_ASSERT(!m_prev || m_prev->x != x || m_prev->y != y);
+ m_prev->left= point;
+ point->right= m_prev;
+ }
+ else
+ m_first= point;
+ m_prev= point;
+ m_prev_hook= hook;
+ return 0;
+}
+
+
+void Gcalc_shape_transporter::int_complete()
+{
+ GCALC_DBUG_ASSERT(m_shape_started == 1 || m_shape_started == 3);
+
+ if (!m_first)
+ return;
+
+ /* simple point */
+ if (m_first == m_prev)
+ {
+ m_first->right= m_first->left= NULL;
+ return;
+ }
+
+ /* line */
+ if (m_shape_started == 1)
+ {
+ m_first->right= NULL;
+ m_prev->left= m_prev->right;
+ m_prev->right= NULL;
+ return;
+ }
+
+ /* polygon */
+ if (cmp_point_info(m_first, m_prev) == 0)
+ {
+ /* Coinciding points, remove the last one from the list */
+ m_prev->right->left= m_first;
+ m_first->right= m_prev->right;
+ m_heap->free_point_info(m_prev, m_prev_hook);
+ }
+ else
+ {
+ GCALC_DBUG_ASSERT(m_prev->x != m_first->x || m_prev->y != m_first->y);
+ m_first->right= m_prev;
+ m_prev->left= m_first;
+ }
+}
+
+
+inline void calc_dx_dy(Gcalc_scan_iterator::point *p)
+{
+ gcalc_sub_coord1(p->dx, p->next_pi->ix, p->pi->ix);
+ gcalc_sub_coord1(p->dy, p->next_pi->iy, p->pi->iy);
+ if (GCALC_SIGN(p->dx[0]))
+ {
+ p->l_border= &p->next_pi->ix;
+ p->r_border= &p->pi->ix;
+ }
+ else
+ {
+ p->r_border= &p->next_pi->ix;
+ p->l_border= &p->pi->ix;
+ }
+}
+
+
+Gcalc_scan_iterator::Gcalc_scan_iterator(size_t blk_size) :
+ Gcalc_dyn_list(blk_size, sizeof(point) > sizeof(intersection_info) ?
+ sizeof(point) :
+ sizeof(intersection_info))
+{
+ state.slice= NULL;
+ m_bottom_points= NULL;
+ m_bottom_hook= &m_bottom_points;
+}
+
+
+void Gcalc_scan_iterator::init(Gcalc_heap *points)
+{
+ GCALC_DBUG_ASSERT(points->ready());
+ GCALC_DBUG_ASSERT(!state.slice);
+
+ if (!(m_cur_pi= points->get_first()))
+ return;
+ m_heap= points;
+ state.event_position_hook= &state.slice;
+ state.event_end= NULL;
+#ifndef GCALC_DBUG_OFF
+ m_cur_thread= 0;
+#endif /*GCALC_DBUG_OFF*/
+ GCALC_SET_TERMINATED(killed, 0);
+}
+
+void Gcalc_scan_iterator::reset()
+{
+ state.slice= NULL;
+ m_bottom_points= NULL;
+ m_bottom_hook= &m_bottom_points;
+ Gcalc_dyn_list::reset();
+}
+
+
+int Gcalc_scan_iterator::point::cmp_dx_dy(const Gcalc_coord1 dx_a,
+ const Gcalc_coord1 dy_a,
+ const Gcalc_coord1 dx_b,
+ const Gcalc_coord1 dy_b)
+{
+ Gcalc_coord2 dx_a_dy_b;
+ Gcalc_coord2 dy_a_dx_b;
+ gcalc_mul_coord1(dx_a_dy_b, dx_a, dy_b);
+ gcalc_mul_coord1(dy_a_dx_b, dy_a, dx_b);
+
+ return gcalc_cmp_coord(dx_a_dy_b, dy_a_dx_b, GCALC_COORD_BASE2);
+}
+
+
+int Gcalc_scan_iterator::point::cmp_dx_dy(const Gcalc_heap::Info *p1,
+ const Gcalc_heap::Info *p2,
+ const Gcalc_heap::Info *p3,
+ const Gcalc_heap::Info *p4)
+{
+ Gcalc_coord1 dx_a, dy_a, dx_b, dy_b;
+ gcalc_sub_coord1(dx_a, p2->ix, p1->ix);
+ gcalc_sub_coord1(dy_a, p2->iy, p1->iy);
+ gcalc_sub_coord1(dx_b, p4->ix, p3->ix);
+ gcalc_sub_coord1(dy_b, p4->iy, p3->iy);
+ return cmp_dx_dy(dx_a, dy_a, dx_b, dy_b);
+}
+
+
+int Gcalc_scan_iterator::point::cmp_dx_dy(const point *p) const
+{
+ GCALC_DBUG_ASSERT(!is_bottom());
+ return cmp_dx_dy(dx, dy, p->dx, p->dy);
+}
+
+
+#ifdef GCALC_CHECK_WITH_FLOAT
+void Gcalc_scan_iterator::point::calc_x(long double *x, long double y,
+ long double ix) const
+{
+ long double ddy= gcalc_get_double(dy, GCALC_COORD_BASE);
+ if (fabsl(ddy) < (long double) 1e-20)
+ {
+ *x= ix;
+ }
+ else
+ *x= (ddy * (long double) pi->x + gcalc_get_double(dx, GCALC_COORD_BASE) *
+ (y - pi->y)) / ddy;
+}
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+
+
+static int compare_events(const void *e0, const void *e1)
+{
+ const Gcalc_scan_iterator::point *p0= (const Gcalc_scan_iterator::point *)e0;
+ const Gcalc_scan_iterator::point *p1= (const Gcalc_scan_iterator::point *)e1;
+ return p0->cmp_dx_dy(p1) > 0;
+}
+
+
+int Gcalc_scan_iterator::arrange_event(int do_sorting, int n_intersections)
+{
+ int ev_counter;
+ point *sp;
+ point **sp_hook;
+
+ ev_counter= 0;
+
+ *m_bottom_hook= NULL;
+ for (sp= m_bottom_points; sp; sp= sp->get_next())
+ sp->ev_next= sp->get_next();
+
+ for (sp= state.slice, sp_hook= &state.slice;
+ sp; sp_hook= sp->next_ptr(), sp= sp->get_next())
+ {
+ if (sp->event)
+ {
+ state.event_position_hook= sp_hook;
+ break;
+ }
+ }
+
+ for (sp= *(sp_hook= state.event_position_hook);
+ sp && sp->event; sp_hook= sp->next_ptr(), sp= sp->get_next())
+ {
+ ev_counter++;
+ if (sp->get_next() && sp->get_next()->event)
+ sp->ev_next= sp->get_next();
+ else
+ sp->ev_next= m_bottom_points;
+ }
+
+#ifndef GCALC_DBUG_OFF
+ {
+ point *cur_p= sp;
+ for (; cur_p; cur_p= cur_p->get_next())
+ GCALC_DBUG_ASSERT(!cur_p->event);
+ }
+#endif /*GCALC_DBUG_OFF*/
+
+ state.event_end= sp;
+
+ if (ev_counter == 2 && n_intersections == 1)
+ {
+ /* If we had only intersection, just swap the two points. */
+ sp= *state.event_position_hook;
+ *state.event_position_hook= sp->get_next();
+ sp->next= (*state.event_position_hook)->next;
+ (*state.event_position_hook)->next= sp;
+
+ /* The list of the events should be restored. */
+ (*state.event_position_hook)->ev_next= sp;
+ sp->ev_next= m_bottom_points;
+ }
+ else if (ev_counter == 2 && get_events()->event == scev_two_threads)
+ {
+ /* Do nothing. */
+ }
+ else if (ev_counter > 1 && do_sorting)
+ {
+ point *cur_p;
+ *sp_hook= NULL;
+ sp= (point *) sort_list(compare_events, *state.event_position_hook,
+ ev_counter);
+ /* Find last item in the list, it's changed after the sorting. */
+ for (cur_p= sp->get_next(); cur_p->get_next();
+ cur_p= cur_p->get_next())
+ {}
+ cur_p->next= state.event_end;
+ *state.event_position_hook= sp;
+ /* The list of the events should be restored. */
+ for (; sp && sp->event; sp= sp->get_next())
+ {
+ if (sp->get_next() && sp->get_next()->event)
+ sp->ev_next= sp->get_next();
+ else
+ sp->ev_next= m_bottom_points;
+ }
+ }
+
+#ifndef GCALC_DBUG_OFF
+ {
+ const event_point *ev= get_events();
+ for (; ev && ev->get_next(); ev= ev->get_next())
+ {
+ if (ev->is_bottom() || ev->get_next()->is_bottom())
+ break;
+ GCALC_DBUG_ASSERT(ev->cmp_dx_dy(ev->get_next()) <= 0);
+ }
+ }
+#endif /*GCALC_DBUG_OFF*/
+ return 0;
+}
+
+
+int Gcalc_heap::Info::equal_pi(const Info *pi) const
+{
+ if (type == nt_intersection)
+ return equal_intersection;
+ if (pi->type == nt_eq_node)
+ return 1;
+ if (type == nt_eq_node || pi->type == nt_intersection)
+ return 0;
+ return cmp_point_info(this, pi) == 0;
+}
+
+int Gcalc_scan_iterator::step()
+{
+ int result= 0;
+ int do_sorting= 0;
+ int n_intersections= 0;
+ point *sp;
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::step");
+ GCALC_DBUG_ASSERT(more_points());
+
+ if (GCALC_TERMINATED(killed))
+ GCALC_DBUG_RETURN(0xFFFF);
+
+ /* Clear the old event marks. */
+ if (m_bottom_points)
+ {
+ free_list((Gcalc_dyn_list::Item **) &m_bottom_points,
+ (Gcalc_dyn_list::Item **) m_bottom_hook);
+ m_bottom_points= NULL;
+ m_bottom_hook= &m_bottom_points;
+ }
+ for (sp= *state.event_position_hook;
+ sp != state.event_end; sp= sp->get_next())
+ sp->event= scev_none;
+
+//#ifndef GCALC_DBUG_OFF
+ state.event_position_hook= NULL;
+ state.pi= NULL;
+//#endif /*GCALC_DBUG_OFF*/
+
+ do
+ {
+#ifndef GCALC_DBUG_OFF
+ if (m_cur_pi->type == Gcalc_heap::nt_intersection &&
+ m_cur_pi->get_next()->type == Gcalc_heap::nt_intersection &&
+ m_cur_pi->equal_intersection)
+ GCALC_DBUG_ASSERT(cmp_intersections(m_cur_pi, m_cur_pi->get_next()) == 0);
+#endif /*GCALC_DBUG_OFF*/
+ GCALC_DBUG_CHECK_COUNTER();
+ GCALC_DBUG_PRINT_SLICE("step:", state.slice);
+ GCALC_DBUG_PRINT_PI(m_cur_pi);
+ if (m_cur_pi->type == Gcalc_heap::nt_shape_node)
+ {
+ if (m_cur_pi->is_top())
+ {
+ result= insert_top_node();
+ if (!m_cur_pi->is_bottom())
+ do_sorting++;
+ }
+ else if (m_cur_pi->is_bottom())
+ remove_bottom_node();
+ else
+ {
+ do_sorting++;
+ result= node_scan();
+ }
+ if (result)
+ GCALC_DBUG_RETURN(result);
+ state.pi= m_cur_pi;
+ }
+ else if (m_cur_pi->type == Gcalc_heap::nt_eq_node)
+ {
+ do_sorting++;
+ eq_scan();
+ }
+ else
+ {
+ /* nt_intersection */
+ do_sorting++;
+ n_intersections++;
+ intersection_scan();
+ if (!state.pi || state.pi->type == Gcalc_heap::nt_intersection)
+ state.pi= m_cur_pi;
+ }
+
+ m_cur_pi= m_cur_pi->get_next();
+ } while (m_cur_pi && state.pi->equal_pi(m_cur_pi));
+
+ GCALC_DBUG_RETURN(arrange_event(do_sorting, n_intersections));
+}
+
+
+static int node_on_right(const Gcalc_heap::Info *node,
+ const Gcalc_heap::Info *edge_a, const Gcalc_heap::Info *edge_b)
+{
+ Gcalc_coord1 a_x, a_y;
+ Gcalc_coord1 b_x, b_y;
+ Gcalc_coord2 ax_by, ay_bx;
+ int result;
+
+ gcalc_sub_coord1(a_x, node->ix, edge_a->ix);
+ gcalc_sub_coord1(a_y, node->iy, edge_a->iy);
+ gcalc_sub_coord1(b_x, edge_b->ix, edge_a->ix);
+ gcalc_sub_coord1(b_y, edge_b->iy, edge_a->iy);
+ gcalc_mul_coord1(ax_by, a_x, b_y);
+ gcalc_mul_coord1(ay_bx, a_y, b_x);
+ result= gcalc_cmp_coord(ax_by, ay_bx, GCALC_COORD_BASE2);
+#ifdef GCALC_CHECK_WITH_FLOAT
+ {
+ long double dx= gcalc_get_double(edge_b->ix, GCALC_COORD_BASE) -
+ gcalc_get_double(edge_a->ix, GCALC_COORD_BASE);
+ long double dy= gcalc_get_double(edge_b->iy, GCALC_COORD_BASE) -
+ gcalc_get_double(edge_a->iy, GCALC_COORD_BASE);
+ long double ax= gcalc_get_double(node->ix, GCALC_COORD_BASE) -
+ gcalc_get_double(edge_a->ix, GCALC_COORD_BASE);
+ long double ay= gcalc_get_double(node->iy, GCALC_COORD_BASE) -
+ gcalc_get_double(edge_a->iy, GCALC_COORD_BASE);
+ long double d= ax * dy - ay * dx;
+ if (result == 0)
+ GCALC_DBUG_ASSERT(de_check(d, 0.0));
+ else if (result < 0)
+ GCALC_DBUG_ASSERT(de_check(d, 0.0) || d < 0);
+ else
+ GCALC_DBUG_ASSERT(de_check(d, 0.0) || d > 0);
+ }
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+ return result;
+}
+
+
+static int cmp_tops(const Gcalc_heap::Info *top_node,
+ const Gcalc_heap::Info *edge_a, const Gcalc_heap::Info *edge_b)
+{
+ int cmp_res_a, cmp_res_b;
+
+ cmp_res_a= gcalc_cmp_coord1(edge_a->ix, top_node->ix);
+ cmp_res_b= gcalc_cmp_coord1(edge_b->ix, top_node->ix);
+
+ if (cmp_res_a <= 0 && cmp_res_b > 0)
+ return -1;
+ if (cmp_res_b <= 0 && cmp_res_a > 0)
+ return 1;
+ if (cmp_res_a == 0 && cmp_res_b == 0)
+ return 0;
+
+ return node_on_right(edge_a, top_node, edge_b);
+}
+
+
+int Gcalc_scan_iterator::insert_top_node()
+{
+ point *sp= state.slice;
+ point **prev_hook= &state.slice;
+ point *sp1= NULL;
+ point *sp0= new_slice_point();
+ int cmp_res;
+
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::insert_top_node");
+ if (!sp0)
+ GCALC_DBUG_RETURN(1);
+ sp0->pi= m_cur_pi;
+ sp0->next_pi= m_cur_pi->left;
+#ifndef GCALC_DBUG_OFF
+ sp0->thread= m_cur_thread++;
+#endif /*GCALC_DBUG_OFF*/
+ if (m_cur_pi->left)
+ {
+ calc_dx_dy(sp0);
+ if (m_cur_pi->right)
+ {
+ if (!(sp1= new_slice_point()))
+ GCALC_DBUG_RETURN(1);
+ sp1->event= sp0->event= scev_two_threads;
+ sp1->pi= m_cur_pi;
+ sp1->next_pi= m_cur_pi->right;
+#ifndef GCALC_DBUG_OFF
+ sp1->thread= m_cur_thread++;
+#endif /*GCALC_DBUG_OFF*/
+ calc_dx_dy(sp1);
+ /* We have two threads so should decide which one will be first */
+ cmp_res= cmp_tops(m_cur_pi, m_cur_pi->left, m_cur_pi->right);
+ if (cmp_res > 0)
+ {
+ point *tmp= sp0;
+ sp0= sp1;
+ sp1= tmp;
+ }
+ else if (cmp_res == 0)
+ {
+ /* Exactly same direction of the edges. */
+ cmp_res= gcalc_cmp_coord1(m_cur_pi->left->iy, m_cur_pi->right->iy);
+ if (cmp_res != 0)
+ {
+ if (cmp_res < 0)
+ {
+ if (add_eq_node(sp0->next_pi, sp1))
+ GCALC_DBUG_RETURN(1);
+ }
+ else
+ {
+ if (add_eq_node(sp1->next_pi, sp0))
+ GCALC_DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ cmp_res= gcalc_cmp_coord1(m_cur_pi->left->ix, m_cur_pi->right->ix);
+ if (cmp_res != 0)
+ {
+ if (cmp_res < 0)
+ {
+ if (add_eq_node(sp0->next_pi, sp1))
+ GCALC_DBUG_RETURN(1);
+ }
+ else
+ {
+ if (add_eq_node(sp1->next_pi, sp0))
+ GCALC_DBUG_RETURN(1);
+ }
+ }
+ }
+ }
+ }
+ else
+ sp0->event= scev_thread;
+ }
+ else
+ sp0->event= scev_single_point;
+
+
+ /* Check if we already have an event - then we'll place the node there */
+ for (; sp && !sp->event; prev_hook= sp->next_ptr(), sp=sp->get_next())
+ {}
+ if (!sp)
+ {
+ sp= state.slice;
+ prev_hook= &state.slice;
+ /* We need to find the place to insert. */
+ for (; sp; prev_hook= sp->next_ptr(), sp=sp->get_next())
+ {
+ if (sp->event || gcalc_cmp_coord1(*sp->r_border, m_cur_pi->ix) < 0)
+ continue;
+ cmp_res= node_on_right(m_cur_pi, sp->pi, sp->next_pi);
+ if (cmp_res == 0)
+ {
+ /* The top node lies on the edge. */
+ /* Nodes of that edge will be handled in other places. */
+ sp->event= scev_intersection;
+ }
+ else if (cmp_res < 0)
+ break;
+ }
+ }
+
+ if (sp0->event == scev_single_point)
+ {
+ /* Add single point to the bottom list. */
+ *m_bottom_hook= sp0;
+ m_bottom_hook= sp0->next_ptr();
+ state.event_position_hook= prev_hook;
+ }
+ else
+ {
+ *prev_hook= sp0;
+ sp0->next= sp;
+ if (add_events_for_node(sp0))
+ GCALC_DBUG_RETURN(1);
+
+ if (sp0->event == scev_two_threads)
+ {
+ *prev_hook= sp1;
+ sp1->next= sp;
+ if (add_events_for_node(sp1))
+ GCALC_DBUG_RETURN(1);
+
+ sp0->next= sp1;
+ *prev_hook= sp0;
+ }
+ }
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+void Gcalc_scan_iterator::remove_bottom_node()
+{
+ point *sp= state.slice;
+ point **sp_hook= &state.slice;
+ point *first_bottom_point= NULL;
+
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::remove_bottom_node");
+ for (; sp; sp= sp->get_next())
+ {
+ if (sp->next_pi == m_cur_pi)
+ {
+ *sp_hook= sp->get_next();
+ sp->pi= m_cur_pi;
+ sp->next_pi= NULL;
+ if (first_bottom_point)
+ {
+ first_bottom_point->event= sp->event= scev_two_ends;
+ break;
+ }
+ first_bottom_point= sp;
+ sp->event= scev_end;
+ state.event_position_hook= sp_hook;
+ }
+ else
+ sp_hook= sp->next_ptr();
+ }
+ GCALC_DBUG_ASSERT(first_bottom_point);
+ *m_bottom_hook= first_bottom_point;
+ m_bottom_hook= first_bottom_point->next_ptr();
+ if (sp)
+ {
+ *m_bottom_hook= sp;
+ m_bottom_hook= sp->next_ptr();
+ }
+
+ GCALC_DBUG_VOID_RETURN;
+}
+
+
+int Gcalc_scan_iterator::add_events_for_node(point *sp_node)
+{
+ point *sp= state.slice;
+ int cur_pi_r, sp_pi_r;
+
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::add_events_for_node");
+
+ /* Scan to the event point. */
+ for (; sp != sp_node; sp= sp->get_next())
+ {
+ GCALC_DBUG_ASSERT(!sp->is_bottom());
+ GCALC_DBUG_PRINT(("left cut_edge %d", sp->thread));
+ if (sp->next_pi == sp_node->next_pi ||
+ gcalc_cmp_coord1(*sp->r_border, *sp_node->l_border) < 0)
+ continue;
+ sp_pi_r= node_on_right(sp->next_pi, sp_node->pi, sp_node->next_pi);
+ if (sp_pi_r < 0)
+ continue;
+ cur_pi_r= node_on_right(sp_node->next_pi, sp->pi, sp->next_pi);
+ if (cur_pi_r > 0)
+ continue;
+ if (cur_pi_r == 0 && sp_pi_r == 0)
+ {
+ int cmp_res= cmp_point_info(sp->next_pi, sp_node->next_pi);
+ if (cmp_res > 0)
+ {
+ if (add_eq_node(sp_node->next_pi, sp))
+ GCALC_DBUG_RETURN(1);
+ }
+ else if (cmp_res < 0)
+ {
+ if (add_eq_node(sp->next_pi, sp_node))
+ GCALC_DBUG_RETURN(1);
+ }
+ continue;
+ }
+
+ if (cur_pi_r == 0)
+ {
+ if (add_eq_node(sp_node->next_pi, sp))
+ GCALC_DBUG_RETURN(1);
+ continue;
+ }
+ else if (sp_pi_r == 0)
+ {
+ if (add_eq_node(sp->next_pi, sp_node))
+ GCALC_DBUG_RETURN(1);
+ continue;
+ }
+
+ if (sp->event)
+ {
+#ifndef GCALC_DBUG_OFF
+ cur_pi_r= node_on_right(sp_node->pi, sp->pi, sp->next_pi);
+ GCALC_DBUG_ASSERT(cur_pi_r == 0);
+#endif /*GCALC_DBUG_OFF*/
+ continue;
+ }
+ cur_pi_r= node_on_right(sp_node->pi, sp->pi, sp->next_pi);
+ GCALC_DBUG_ASSERT(cur_pi_r >= 0);
+ //GCALC_DBUG_ASSERT(cur_pi_r > 0); /* Is it ever violated? */
+ if (cur_pi_r > 0 && add_intersection(sp, sp_node, m_cur_pi))
+ GCALC_DBUG_RETURN(1);
+ }
+
+ /* Scan to the end of the slice */
+ sp= sp->get_next();
+
+ for (; sp; sp= sp->get_next())
+ {
+ GCALC_DBUG_ASSERT(!sp->is_bottom());
+ GCALC_DBUG_PRINT(("right cut_edge %d", sp->thread));
+ if (sp->next_pi == sp_node->next_pi ||
+ gcalc_cmp_coord1(*sp_node->r_border, *sp->l_border) < 0)
+ continue;
+ sp_pi_r= node_on_right(sp->next_pi, sp_node->pi, sp_node->next_pi);
+ if (sp_pi_r > 0)
+ continue;
+ cur_pi_r= node_on_right(sp_node->next_pi, sp->pi, sp->next_pi);
+ if (cur_pi_r < 0)
+ continue;
+ if (cur_pi_r == 0 && sp_pi_r == 0)
+ {
+ int cmp_res= cmp_point_info(sp->next_pi, sp_node->next_pi);
+ if (cmp_res > 0)
+ {
+ if (add_eq_node(sp_node->next_pi, sp))
+ GCALC_DBUG_RETURN(1);
+ }
+ else if (cmp_res < 0)
+ {
+ if (add_eq_node(sp->next_pi, sp_node))
+ GCALC_DBUG_RETURN(1);
+ }
+ continue;
+ }
+ if (cur_pi_r == 0)
+ {
+ if (add_eq_node(sp_node->next_pi, sp))
+ GCALC_DBUG_RETURN(1);
+ continue;
+ }
+ else if (sp_pi_r == 0)
+ {
+ if (add_eq_node(sp->next_pi, sp_node))
+ GCALC_DBUG_RETURN(1);
+ continue;
+ }
+
+ if (sp->event)
+ {
+#ifndef GCALC_DBUG_OFF
+ cur_pi_r= node_on_right(sp_node->pi, sp->pi, sp->next_pi);
+ GCALC_DBUG_ASSERT(cur_pi_r == 0);
+#endif /*GCALC_DBUG_OFF*/
+ continue;
+ }
+ cur_pi_r= node_on_right(sp_node->pi, sp->pi, sp->next_pi);
+ GCALC_DBUG_ASSERT(cur_pi_r <= 0);
+ //GCALC_DBUG_ASSERT(cur_pi_r < 0); /* Is it ever violated? */
+ if (cur_pi_r < 0 && add_intersection(sp_node, sp, m_cur_pi))
+ GCALC_DBUG_RETURN(1);
+ }
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_scan_iterator::node_scan()
+{
+ point *sp= state.slice;
+ Gcalc_heap::Info *cur_pi= m_cur_pi;
+
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::node_scan");
+
+ /* Scan to the event point. */
+ /* Can be avoided if we add link to the sp to the Info. */
+ for (; sp->next_pi != cur_pi; sp= sp->get_next())
+ {}
+
+ GCALC_DBUG_PRINT(("node for %d", sp->thread));
+ /* Handle the point itself. */
+ sp->pi= cur_pi;
+ sp->next_pi= cur_pi->left;
+ sp->event= scev_point;
+ calc_dx_dy(sp);
+
+ GCALC_DBUG_RETURN(add_events_for_node(sp));
+}
+
+
+void Gcalc_scan_iterator::eq_scan()
+{
+ point *sp= eq_sp(m_cur_pi);
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::eq_scan");
+
+#ifndef GCALC_DBUG_OFF
+ {
+ point *cur_p= state.slice;
+ for (; cur_p && cur_p != sp; cur_p= cur_p->get_next())
+ {}
+ GCALC_DBUG_ASSERT(cur_p);
+ }
+#endif /*GCALC_DBUG_OFF*/
+ if (!sp->event)
+ {
+ sp->event= scev_intersection;
+ sp->ev_pi= m_cur_pi;
+ }
+
+ GCALC_DBUG_VOID_RETURN;
+}
+
+
+void Gcalc_scan_iterator::intersection_scan()
+{
+ intersection_info *ii= i_data(m_cur_pi);
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::intersection_scan");
+
+#ifndef GCALC_DBUG_OFF
+ {
+ point *sp= state.slice;
+ for (; sp && sp != ii->edge_a; sp= sp->get_next())
+ {}
+ GCALC_DBUG_ASSERT(sp);
+ for (; sp && sp != ii->edge_b; sp= sp->get_next())
+ {}
+ GCALC_DBUG_ASSERT(sp);
+ }
+#endif /*GCALC_DBUG_OFF*/
+
+ ii->edge_a->event= ii->edge_b->event= scev_intersection;
+ ii->edge_a->ev_pi= ii->edge_b->ev_pi= m_cur_pi;
+ free_item(ii);
+ m_cur_pi->intersection_data= NULL;
+
+ GCALC_DBUG_VOID_RETURN;
+}
+
+
+int Gcalc_scan_iterator::add_intersection(point *sp_a, point *sp_b,
+ Gcalc_heap::Info *pi_from)
+{
+ Gcalc_heap::Info *ii;
+ intersection_info *i_calc;
+ int cmp_res;
+ int skip_next= 0;
+
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::add_intersection");
+ if (!(i_calc= new_intersection_info(sp_a, sp_b)) ||
+ !(ii= new_intersection(m_heap, i_calc)))
+ GCALC_DBUG_RETURN(1);
+
+ ii->equal_intersection= 0;
+
+ for (;
+ pi_from->get_next() != sp_a->next_pi &&
+ pi_from->get_next() != sp_b->next_pi;
+ pi_from= pi_from->get_next())
+ {
+ Gcalc_heap::Info *cur= pi_from->get_next();
+ if (skip_next)
+ {
+ if (cur->type == Gcalc_heap::nt_intersection)
+ skip_next= cur->equal_intersection;
+ else
+ skip_next= 0;
+ continue;
+ }
+ if (cur->type == Gcalc_heap::nt_intersection)
+ {
+ cmp_res= cmp_intersections(cur, ii);
+ skip_next= cur->equal_intersection;
+ }
+ else if (cur->type == Gcalc_heap::nt_eq_node)
+ continue;
+ else
+ cmp_res= cmp_node_isc(cur, ii);
+ if (cmp_res == 0)
+ {
+ ii->equal_intersection= 1;
+ break;
+ }
+ else if (cmp_res > 0)
+ break;
+ }
+
+ /* Intersection inserted before the equal point. */
+ ii->next= pi_from->get_next();
+ pi_from->next= ii;
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_scan_iterator::add_eq_node(Gcalc_heap::Info *node, point *sp)
+{
+ Gcalc_heap::Info *en;
+
+ GCALC_DBUG_ENTER("Gcalc_scan_iterator::add_intersection");
+ en= new_eq_point(m_heap, node, sp);
+ if (!en)
+ GCALC_DBUG_RETURN(1);
+
+ /* eq_node iserted after teh equal point. */
+ en->next= node->get_next();
+ node->next= en;
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+void calc_t(Gcalc_coord2 t_a, Gcalc_coord2 t_b,
+ Gcalc_coord1 dxa, Gcalc_coord1 dxb,
+ const Gcalc_heap::Info *p1, const Gcalc_heap::Info *p2,
+ const Gcalc_heap::Info *p3, const Gcalc_heap::Info *p4)
+{
+ Gcalc_coord1 a2_a1x, a2_a1y;
+ Gcalc_coord2 x1y2, x2y1;
+ Gcalc_coord1 dya, dyb;
+
+ gcalc_sub_coord1(a2_a1x, p3->ix, p1->ix);
+ gcalc_sub_coord1(a2_a1y, p3->iy, p1->iy);
+
+ gcalc_sub_coord1(dxa, p2->ix, p1->ix);
+ gcalc_sub_coord1(dya, p2->iy, p1->iy);
+ gcalc_sub_coord1(dxb, p4->ix, p3->ix);
+ gcalc_sub_coord1(dyb, p4->iy, p3->iy);
+
+ gcalc_mul_coord1(x1y2, dxa, dyb);
+ gcalc_mul_coord1(x2y1, dya, dxb);
+ gcalc_sub_coord(t_b, GCALC_COORD_BASE2, x1y2, x2y1);
+
+
+ gcalc_mul_coord1(x1y2, a2_a1x, dyb);
+ gcalc_mul_coord1(x2y1, a2_a1y, dxb);
+ gcalc_sub_coord(t_a, GCALC_COORD_BASE2, x1y2, x2y1);
+}
+
+
+double Gcalc_scan_iterator::get_y() const
+{
+ if (state.pi->type == Gcalc_heap::nt_intersection)
+ {
+ Gcalc_coord1 dxa, dya;
+ Gcalc_coord2 t_a, t_b;
+ Gcalc_coord3 a_tb, b_ta, y_exp;
+ calc_t(t_a, t_b, dxa, dya,
+ state.pi->p1, state.pi->p2, state.pi->p3, state.pi->p4);
+
+
+ gcalc_mul_coord(a_tb, GCALC_COORD_BASE3,
+ t_b, GCALC_COORD_BASE2, state.pi->p1->iy, GCALC_COORD_BASE);
+ gcalc_mul_coord(b_ta, GCALC_COORD_BASE3,
+ t_a, GCALC_COORD_BASE2, dya, GCALC_COORD_BASE);
+
+ gcalc_add_coord(y_exp, GCALC_COORD_BASE3, a_tb, b_ta);
+
+ return (get_pure_double(y_exp, GCALC_COORD_BASE3) /
+ get_pure_double(t_b, GCALC_COORD_BASE2)) / m_heap->coord_extent;
+ }
+ else
+ return state.pi->y;
+}
+
+
+double Gcalc_scan_iterator::get_event_x() const
+{
+ if (state.pi->type == Gcalc_heap::nt_intersection)
+ {
+ Gcalc_coord1 dxa, dya;
+ Gcalc_coord2 t_a, t_b;
+ Gcalc_coord3 a_tb, b_ta, x_exp;
+ calc_t(t_a, t_b, dxa, dya,
+ state.pi->p1, state.pi->p2, state.pi->p3, state.pi->p4);
+
+
+ gcalc_mul_coord(a_tb, GCALC_COORD_BASE3,
+ t_b, GCALC_COORD_BASE2, state.pi->p1->ix, GCALC_COORD_BASE);
+ gcalc_mul_coord(b_ta, GCALC_COORD_BASE3,
+ t_a, GCALC_COORD_BASE2, dxa, GCALC_COORD_BASE);
+
+ gcalc_add_coord(x_exp, GCALC_COORD_BASE3, a_tb, b_ta);
+
+ return (get_pure_double(x_exp, GCALC_COORD_BASE3) /
+ get_pure_double(t_b, GCALC_COORD_BASE2)) / m_heap->coord_extent;
+ }
+ else
+ return state.pi->x;
+}
+
+double Gcalc_scan_iterator::get_h() const
+{
+ double cur_y= get_y();
+ double next_y;
+ if (state.pi->type == Gcalc_heap::nt_intersection)
+ {
+ double x;
+ state.pi->calc_xy(&x, &next_y);
+ }
+ else
+ next_y= state.pi->y;
+ return next_y - cur_y;
+}
+
+
+double Gcalc_scan_iterator::get_sp_x(const point *sp) const
+{
+ double dy;
+ if (sp->event & (scev_end | scev_two_ends | scev_point))
+ return sp->pi->x;
+ dy= sp->next_pi->y - sp->pi->y;
+ if (fabs(dy) < 1e-12)
+ return sp->pi->x;
+ return (sp->next_pi->x - sp->pi->x) * dy;
+}
+
+
+double Gcalc_scan_iterator::get_pure_double(const Gcalc_internal_coord *d,
+ int d_len)
+{
+ int n= 1;
+ long double res= (long double) FIRST_DIGIT(d[0]);
+ do
+ {
+ res*= (long double) GCALC_DIG_BASE;
+ res+= (long double) d[n];
+ } while(++n < d_len);
+
+ if (GCALC_SIGN(d[0]))
+ res*= -1.0;
+ return res;
+}
+
+
+#endif /* HAVE_SPATIAL */
diff --git a/sql/gcalc_slicescan.h b/sql/gcalc_slicescan.h
new file mode 100644
index 00000000000..55de497f1ee
--- /dev/null
+++ b/sql/gcalc_slicescan.h
@@ -0,0 +1,600 @@
+/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved.
+ Copyright (C) 2011 Monty Program Ab.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#ifndef GCALC_SLICESCAN_INCLUDED
+#define GCALC_SLICESCAN_INCLUDED
+
+#ifndef DBUG_OFF
+// #define GCALC_CHECK_WITH_FLOAT
+#else
+#define GCALC_DBUG_OFF
+#endif /*DBUG_OFF*/
+
+#ifndef GCALC_DBUG_OFF
+#define GCALC_DBUG_PRINT(b) DBUG_PRINT("Gcalc", b)
+#define GCALC_DBUG_ENTER(a) DBUG_ENTER("Gcalc "a)
+#define GCALC_DBUG_RETURN(r) DBUG_RETURN(r)
+#define GCALC_DBUG_VOID_RETURN DBUG_VOID_RETURN
+#define GCALC_DBUG_ASSERT(r) DBUG_ASSERT(r)
+#else
+#define GCALC_DBUG_PRINT(b) do {} while(0)
+#define GCALC_DBUG_ENTER(a) do {} while(0)
+#define GCALC_DBUG_RETURN(r) return (r)
+#define GCALC_DBUG_VOID_RETURN do {} while(0)
+#define GCALC_DBUG_ASSERT(r) do {} while(0)
+#endif /*GCALC_DBUG_OFF*/
+
+#define GCALC_TERMINATED(state_var) (state_var && (*state_var))
+#define GCALC_SET_TERMINATED(state_var, val) state_var= val
+#define GCALC_DECL_TERMINATED_STATE(varname) \
+ volatile int *varname;
+
+/*
+ Gcalc_dyn_list class designed to manage long lists of same-size objects
+ with the possible efficiency.
+ It allocates fixed-size blocks of memory (blk_size specified at the time
+ of creation). When new object is added to the list, it occupies part of
+ this block until it's full. Then the new block is allocated.
+ Freed objects are chained to the m_free list, and if it's not empty, the
+ newly added object is taken from this list instead the block.
+*/
+
+class Gcalc_dyn_list
+{
+public:
+ class Item
+ {
+ public:
+ Item *next;
+ };
+
+ Gcalc_dyn_list(size_t blk_size, size_t sizeof_item);
+ ~Gcalc_dyn_list();
+ Item *new_item()
+ {
+ Item *result;
+ if (m_free)
+ {
+ result= m_free;
+ m_free= m_free->next;
+ }
+ else
+ result= alloc_new_blk();
+
+ return result;
+ }
+ inline void free_item(Item *item)
+ {
+ item->next= m_free;
+ m_free= item;
+ }
+ inline void free_list(Item **list, Item **hook)
+ {
+ *hook= m_free;
+ m_free= *list;
+ }
+
+ void free_list(Item *list)
+ {
+ Item **hook= &list;
+ while (*hook)
+ hook= &(*hook)->next;
+ free_list(&list, hook);
+ }
+
+ void reset();
+ void cleanup();
+
+protected:
+ size_t m_blk_size;
+ size_t m_sizeof_item;
+ unsigned int m_points_per_blk;
+ void *m_first_blk;
+ void **m_blk_hook;
+ Item *m_free;
+ Item *m_keep;
+
+ Item *alloc_new_blk();
+ void format_blk(void* block);
+ inline Item *ptr_add(Item *ptr, int n_items)
+ {
+ return (Item *)(((char*)ptr) + n_items * m_sizeof_item);
+ }
+};
+
+/* Internal Gcalc coordinates to provide the precise calculations */
+
+#define GCALC_DIG_BASE 1000000000
+typedef uint32 gcalc_digit_t;
+typedef unsigned long long gcalc_coord2;
+typedef gcalc_digit_t Gcalc_internal_coord;
+#define GCALC_COORD_BASE 2
+#define GCALC_COORD_BASE2 4
+#define GCALC_COORD_BASE3 6
+#define GCALC_COORD_BASE4 8
+#define GCALC_COORD_BASE5 10
+
+typedef gcalc_digit_t Gcalc_coord1[GCALC_COORD_BASE];
+typedef gcalc_digit_t Gcalc_coord2[GCALC_COORD_BASE*2];
+typedef gcalc_digit_t Gcalc_coord3[GCALC_COORD_BASE*3];
+
+
+void gcalc_mul_coord(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a, int a_len,
+ const Gcalc_internal_coord *b, int b_len);
+
+void gcalc_add_coord(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b);
+
+void gcalc_sub_coord(Gcalc_internal_coord *result, int result_len,
+ const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b);
+
+int gcalc_cmp_coord(const Gcalc_internal_coord *a,
+ const Gcalc_internal_coord *b, int len);
+
+/* Internal coordinates declarations end. */
+
+
+typedef uint gcalc_shape_info;
+
+/*
+ Gcalc_heap represents the 'dynamic list' of Info objects, that
+ contain information about vertexes of all the shapes that take
+ part in some spatial calculation. Can become quite long.
+ After filled, the list is usually sorted and then walked through
+ in the slicescan algorithm.
+ The Gcalc_heap and the algorithm can only operate with two
+ kinds of shapes - polygon and polyline. So all the spatial
+ objects should be represented as sets of these two.
+*/
+
+class Gcalc_heap : public Gcalc_dyn_list
+{
+public:
+ enum node_type
+ {
+ nt_shape_node,
+ nt_intersection,
+ nt_eq_node
+ };
+ class Info : public Gcalc_dyn_list::Item
+ {
+ public:
+ node_type type;
+ union
+ {
+ struct
+ {
+ /* nt_shape_node */
+ gcalc_shape_info shape;
+ Info *left;
+ Info *right;
+ double x,y;
+ Gcalc_coord1 ix, iy;
+ int top_node;
+ };
+ struct
+ {
+ /* nt_intersection */
+ /* Line p1-p2 supposed to intersect line p3-p4 */
+ const Info *p1;
+ const Info *p2;
+ const Info *p3;
+ const Info *p4;
+ void *intersection_data;
+ int equal_intersection;
+ };
+ struct
+ {
+ /* nt_eq_node */
+ const Info *node;
+ void *eq_data;
+ };
+ };
+
+ bool is_bottom() const
+ { GCALC_DBUG_ASSERT(type == nt_shape_node); return !left; }
+ bool is_top() const
+ { GCALC_DBUG_ASSERT(type == nt_shape_node); return top_node; }
+ bool is_single_node() const
+ { return is_bottom() && is_top(); }
+
+ void calc_xy(double *x, double *y) const;
+ int equal_pi(const Info *pi) const;
+#ifdef GCALC_CHECK_WITH_FLOAT
+ void calc_xy_ld(long double *x, long double *y) const;
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+
+ Info *get_next() { return (Info *)next; }
+ const Info *get_next() const { return (const Info *)next; }
+ };
+
+ Gcalc_heap(size_t blk_size=8192) :
+ Gcalc_dyn_list(blk_size, sizeof(Info)),
+ m_hook(&m_first), m_n_points(0)
+ {}
+ void set_extent(double xmin, double xmax, double ymin, double ymax);
+ Info *new_point_info(double x, double y, gcalc_shape_info shape);
+ void free_point_info(Info *i, Gcalc_dyn_list::Item **i_hook);
+ Info *new_intersection(const Info *p1, const Info *p2,
+ const Info *p3, const Info *p4);
+ void prepare_operation();
+ inline bool ready() const { return m_hook == NULL; }
+ Info *get_first() { return (Info *)m_first; }
+ const Info *get_first() const { return (const Info *)m_first; }
+ Gcalc_dyn_list::Item **get_last_hook() { return m_hook; }
+ void reset();
+#ifdef GCALC_CHECK_WITH_FLOAT
+ long double get_double(const Gcalc_internal_coord *c) const;
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+ double coord_extent;
+ Gcalc_dyn_list::Item **get_cur_hook() { return m_hook; }
+
+private:
+ Gcalc_dyn_list::Item *m_first;
+ Gcalc_dyn_list::Item **m_hook;
+ int m_n_points;
+};
+
+
+/*
+ the spatial object has to be represented as a set of
+ simple polygones and polylines to be sent to the slicescan.
+
+ Gcalc_shape_transporter class and his descendants are used to
+ simplify storing the information about the shape into necessary structures.
+ This base class only fills the Gcalc_heap with the information about
+ shapes and vertices.
+
+ Normally the Gcalc_shape_transporter family object is sent as a parameter
+ to the 'get_shapes' method of an 'spatial' object so it can pass
+ the spatial information about itself. The virtual methods are
+ treating this data in a way the caller needs.
+*/
+
+class Gcalc_shape_transporter
+{
+private:
+ Gcalc_heap::Info *m_first;
+ Gcalc_heap::Info *m_prev;
+ Gcalc_dyn_list::Item **m_prev_hook;
+ int m_shape_started;
+ void int_complete();
+protected:
+ Gcalc_heap *m_heap;
+ int int_single_point(gcalc_shape_info Info, double x, double y);
+ int int_add_point(gcalc_shape_info Info, double x, double y);
+ void int_start_line()
+ {
+ DBUG_ASSERT(!m_shape_started);
+ m_shape_started= 1;
+ m_first= m_prev= NULL;
+ }
+ void int_complete_line()
+ {
+ DBUG_ASSERT(m_shape_started== 1);
+ int_complete();
+ m_shape_started= 0;
+ }
+ void int_start_ring()
+ {
+ DBUG_ASSERT(m_shape_started== 2);
+ m_shape_started= 3;
+ m_first= m_prev= NULL;
+ }
+ void int_complete_ring()
+ {
+ DBUG_ASSERT(m_shape_started== 3);
+ int_complete();
+ m_shape_started= 2;
+ }
+ void int_start_poly()
+ {
+ DBUG_ASSERT(!m_shape_started);
+ m_shape_started= 2;
+ }
+ void int_complete_poly()
+ {
+ DBUG_ASSERT(m_shape_started== 2);
+ m_shape_started= 0;
+ }
+ bool line_started() { return m_shape_started == 1; };
+public:
+ Gcalc_shape_transporter(Gcalc_heap *heap) :
+ m_shape_started(0), m_heap(heap) {}
+
+ virtual int single_point(double x, double y)=0;
+ virtual int start_line()=0;
+ virtual int complete_line()=0;
+ virtual int start_poly()=0;
+ virtual int complete_poly()=0;
+ virtual int start_ring()=0;
+ virtual int complete_ring()=0;
+ virtual int add_point(double x, double y)=0;
+ virtual int start_collection(int n_objects) { return 0; }
+ virtual int empty_shape() { return 0; }
+ int start_simple_poly()
+ {
+ return start_poly() || start_ring();
+ }
+ int complete_simple_poly()
+ {
+ return complete_ring() || complete_poly();
+ }
+ virtual ~Gcalc_shape_transporter() {}
+};
+
+
+enum Gcalc_scan_events
+{
+ scev_none= 0,
+ scev_point= 1, /* Just a new point in thread */
+ scev_thread= 2, /* Start of the new thread */
+ scev_two_threads= 4, /* A couple of new threads started */
+ scev_intersection= 8, /* Intersection happened */
+ scev_end= 16, /* Single thread finished */
+ scev_two_ends= 32, /* A couple of threads finished */
+ scev_single_point= 64 /* Got single point */
+};
+
+
+/*
+ Gcalc_scan_iterator incapsulates the slisescan algorithm.
+ It takes filled Gcalc_heap as an datasource. Then can be
+ iterated trought the vertexes and intersection points with
+ the step() method. After the 'step()' one usually observes
+ the current 'slice' to do the necessary calculations, like
+ looking for intersections, calculating the area, whatever.
+*/
+
+class Gcalc_scan_iterator : public Gcalc_dyn_list
+{
+public:
+ class point : public Gcalc_dyn_list::Item
+ {
+ public:
+ Gcalc_coord1 dx;
+ Gcalc_coord1 dy;
+ Gcalc_heap::Info *pi;
+ Gcalc_heap::Info *next_pi;
+ Gcalc_heap::Info *ev_pi;
+ const Gcalc_coord1 *l_border;
+ const Gcalc_coord1 *r_border;
+ point *ev_next;
+
+ Gcalc_scan_events event;
+
+ inline const point *c_get_next() const
+ { return (const point *)next; }
+ inline bool is_bottom() const { return !next_pi; }
+ gcalc_shape_info get_shape() const { return pi->shape; }
+ inline point *get_next() { return (point *)next; }
+ inline const point *get_next() const { return (const point *)next; }
+ /* Compare the dx_dy parameters regarding the horiz_dir */
+ /* returns -1 if less, 0 if equal, 1 if bigger */
+ static int cmp_dx_dy(const Gcalc_coord1 dx_a,
+ const Gcalc_coord1 dy_a,
+ const Gcalc_coord1 dx_b,
+ const Gcalc_coord1 dy_b);
+ static int cmp_dx_dy(const Gcalc_heap::Info *p1,
+ const Gcalc_heap::Info *p2,
+ const Gcalc_heap::Info *p3,
+ const Gcalc_heap::Info *p4);
+ int cmp_dx_dy(const point *p) const;
+ point **next_ptr() { return (point **) &next; }
+#ifndef GCALC_DBUG_OFF
+ unsigned int thread;
+#endif /*GCALC_DBUG_OFF*/
+#ifdef GCALC_CHECK_WITH_FLOAT
+ void calc_x(long double *x, long double y, long double ix) const;
+#endif /*GCALC_CHECK_WITH_FLOAT*/
+ };
+
+ /* That class introduced mostly for the 'typecontrol' reason. */
+ /* only difference from the point classis the get_next() function. */
+ class event_point : public point
+ {
+ public:
+ inline const event_point *get_next() const
+ { return (const event_point*) ev_next; }
+ int simple_event() const
+ {
+ return !ev_next ? (event & (scev_point | scev_end)) :
+ (!ev_next->ev_next && event == scev_two_ends);
+ }
+ };
+
+ class intersection_info : public Gcalc_dyn_list::Item
+ {
+ public:
+ point *edge_a;
+ point *edge_b;
+
+ Gcalc_coord2 t_a;
+ Gcalc_coord2 t_b;
+ int t_calculated;
+ Gcalc_coord3 x_exp;
+ int x_calculated;
+ Gcalc_coord3 y_exp;
+ int y_calculated;
+ void calc_t()
+ {if (!t_calculated) do_calc_t(); }
+ void calc_y_exp()
+ { if (!y_calculated) do_calc_y(); }
+ void calc_x_exp()
+ { if (!x_calculated) do_calc_x(); }
+
+ void do_calc_t();
+ void do_calc_x();
+ void do_calc_y();
+ };
+
+
+ class slice_state
+ {
+ public:
+ point *slice;
+ point **event_position_hook;
+ point *event_end;
+ const Gcalc_heap::Info *pi;
+ };
+
+public:
+ Gcalc_scan_iterator(size_t blk_size= 8192);
+
+ GCALC_DECL_TERMINATED_STATE(killed)
+
+ void init(Gcalc_heap *points); /* Iterator can be reused */
+ void reset();
+ int step();
+
+ Gcalc_heap::Info *more_points() { return m_cur_pi; }
+ bool more_trapezoids()
+ { return m_cur_pi && m_cur_pi->next; }
+
+ const point *get_bottom_points() const
+ { return m_bottom_points; }
+ const point *get_event_position() const
+ { return *state.event_position_hook; }
+ const point *get_event_end() const
+ { return state.event_end; }
+ const event_point *get_events() const
+ { return (const event_point *)
+ (*state.event_position_hook == state.event_end ?
+ m_bottom_points : *state.event_position_hook); }
+ const point *get_b_slice() const { return state.slice; }
+ double get_h() const;
+ double get_y() const;
+ double get_event_x() const;
+ double get_sp_x(const point *sp) const;
+ int intersection_step() const
+ { return state.pi->type == Gcalc_heap::nt_intersection; }
+ const Gcalc_heap::Info *get_cur_pi() const
+ {
+ return state.pi;
+ }
+
+private:
+ Gcalc_heap *m_heap;
+ Gcalc_heap::Info *m_cur_pi;
+ slice_state state;
+
+#ifndef GCALC_DBUG_OFF
+ unsigned int m_cur_thread;
+#endif /*GCALC_DBUG_OFF*/
+
+ point *m_bottom_points;
+ point **m_bottom_hook;
+
+ int node_scan();
+ void eq_scan();
+ void intersection_scan();
+ void remove_bottom_node();
+ int insert_top_node();
+ int add_intersection(point *sp_a, point *sp_b,
+ Gcalc_heap::Info *pi_from);
+ int add_eq_node(Gcalc_heap::Info *node, point *sp);
+ int add_events_for_node(point *sp_node);
+
+ point *new_slice_point()
+ {
+ point *new_point= (point *)new_item();
+ return new_point;
+ }
+ intersection_info *new_intersection_info(point *a, point *b)
+ {
+ intersection_info *ii= (intersection_info *)new_item();
+ ii->edge_a= a;
+ ii->edge_b= b;
+ ii->t_calculated= ii->x_calculated= ii->y_calculated= 0;
+ return ii;
+ }
+ int arrange_event(int do_sorting, int n_intersections);
+ static double get_pure_double(const Gcalc_internal_coord *d, int d_len);
+};
+
+
+/*
+ Gcalc_trapezoid_iterator simplifies the calculations on
+ the current slice of the Gcalc_scan_iterator.
+ One can walk through the trapezoids formed between
+ previous and current slices.
+*/
+
+#ifdef TMP_BLOCK
+class Gcalc_trapezoid_iterator
+{
+protected:
+ const Gcalc_scan_iterator::point *sp0;
+ const Gcalc_scan_iterator::point *sp1;
+public:
+ Gcalc_trapezoid_iterator(const Gcalc_scan_iterator *scan_i) :
+ sp0(scan_i->get_b_slice()),
+ sp1(scan_i->get_t_slice())
+ {}
+
+ inline bool more() const { return sp1 && sp1->next; }
+
+ const Gcalc_scan_iterator::point *lt() const { return sp1; }
+ const Gcalc_scan_iterator::point *lb() const { return sp0; }
+ const Gcalc_scan_iterator::point *rb() const
+ {
+ const Gcalc_scan_iterator::point *result= sp0;
+ while ((result= result->c_get_next())->is_bottom())
+ {}
+ return result;
+ }
+ const Gcalc_scan_iterator::point *rt() const
+ { return sp1->c_get_next(); }
+
+ void operator++()
+ {
+ sp0= rb();
+ sp1= rt();
+ }
+};
+#endif /*TMP_BLOCK*/
+
+
+/*
+ Gcalc_point_iterator simplifies the calculations on
+ the current slice of the Gcalc_scan_iterator.
+ One can walk through the points on the current slice.
+*/
+
+class Gcalc_point_iterator
+{
+protected:
+ const Gcalc_scan_iterator::point *sp;
+public:
+ Gcalc_point_iterator(const Gcalc_scan_iterator *scan_i):
+ sp(scan_i->get_b_slice())
+ {}
+
+ inline bool more() const { return sp != NULL; }
+ inline void operator++() { sp= sp->c_get_next(); }
+ inline const Gcalc_scan_iterator::point *point() const { return sp; }
+ inline const Gcalc_heap::Info *get_pi() const { return sp->pi; }
+ inline gcalc_shape_info get_shape() const { return sp->get_shape(); }
+ inline void restart(const Gcalc_scan_iterator *scan_i)
+ { sp= scan_i->get_b_slice(); }
+};
+
+#endif /*GCALC_SLICESCAN_INCLUDED*/
+
diff --git a/sql/gcalc_tools.cc b/sql/gcalc_tools.cc
new file mode 100644
index 00000000000..864437401b7
--- /dev/null
+++ b/sql/gcalc_tools.cc
@@ -0,0 +1,1425 @@
+/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved.
+ Copyright (C) 2011 Monty Program Ab.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include <my_global.h>
+
+#ifdef HAVE_SPATIAL
+
+#include "gcalc_tools.h"
+#include "spatial.h"
+
+#define float_to_coord(d) ((double) d)
+
+
+/*
+ Adds new shape to the relation.
+ After that it can be used as an argument of an operation.
+*/
+
+gcalc_shape_info Gcalc_function::add_new_shape(uint32 shape_id,
+ shape_type shape_kind)
+{
+ shapes_buffer.q_append((uint32) shape_kind);
+ return n_shapes++;
+}
+
+
+/*
+ Adds new operation to the constructed relation.
+ To construct the complex relation one has to specify operations
+ in prefix style.
+*/
+
+void Gcalc_function::add_operation(uint operation, uint32 n_operands)
+{
+ uint32 op_code= (uint32 ) operation + n_operands;
+ function_buffer.q_append(op_code);
+}
+
+
+/*
+ Sometimes the number of arguments is unknown at the moment the operation
+ is added. That allows to specify it later.
+*/
+
+void Gcalc_function::add_operands_to_op(uint32 operation_pos, uint32 n_operands)
+{
+ uint32 op_code= uint4korr(function_buffer.ptr() + operation_pos) + n_operands;
+ function_buffer.write_at_position(operation_pos, op_code);
+}
+
+
+/*
+ Just like the add_operation() but the result will be the inverted
+ value of an operation.
+*/
+
+void Gcalc_function::add_not_operation(op_type operation, uint32 n_operands)
+{
+ uint32 op_code= ((uint32) op_not | (uint32 ) operation) + n_operands;
+ function_buffer.q_append(op_code);
+}
+
+
+int Gcalc_function::single_shape_op(shape_type shape_kind, gcalc_shape_info *si)
+{
+ if (reserve_shape_buffer(1) || reserve_op_buffer(1))
+ return 1;
+ *si= add_new_shape(0, shape_kind);
+ add_operation(op_shape, *si);
+ return 0;
+}
+
+
+int Gcalc_function::repeat_expression(uint32 exp_pos)
+{
+ if (reserve_op_buffer(1))
+ return 1;
+ add_operation(op_repeat, exp_pos);
+ return 0;
+}
+
+
+/*
+ Specify how many arguments we're going to have.
+*/
+
+int Gcalc_function::reserve_shape_buffer(uint n_shapes)
+{
+ return shapes_buffer.reserve(n_shapes * 4, 512);
+}
+
+
+/*
+ Specify how many operations we're going to have.
+*/
+
+int Gcalc_function::reserve_op_buffer(uint n_ops)
+{
+ return function_buffer.reserve(n_ops * 4, 512);
+}
+
+
+int Gcalc_function::alloc_states()
+{
+ if (function_buffer.reserve((n_shapes+1) * 2 * sizeof(int)))
+ return 1;
+ i_states= (int *) (function_buffer.ptr() + ALIGN_SIZE(function_buffer.length()));
+ b_states= i_states + (n_shapes + 1);
+ return 0;
+}
+
+
+int Gcalc_function::count_internal(const char *cur_func, uint set_type,
+ const char **end)
+{
+ uint c_op= uint4korr(cur_func);
+ op_type next_func= (op_type) (c_op & op_any);
+ int mask= (c_op & op_not) ? 1:0;
+ uint n_ops= c_op & ~(op_any | op_not | v_mask);
+ uint n_shape= c_op & ~(op_any | op_not | v_mask); /* same as n_ops */
+ value v_state= (value) (c_op & v_mask);
+ int result= 0;
+ const char *sav_cur_func= cur_func;
+
+ // GCALC_DBUG_ENTER("Gcalc_function::count_internal");
+
+ cur_func+= 4;
+ if (next_func == op_shape)
+ {
+ if (set_type == 0)
+ result= i_states[n_shape] | b_states[n_shape];
+ else if (set_type == op_border)
+ result= b_states[n_shape];
+ else if (set_type == op_internals)
+ result= i_states[n_shape] && !b_states[n_shape];
+ goto exit;
+ }
+
+ if (next_func == op_false)
+ {
+ result= 0;
+ goto exit;
+ }
+
+ if (next_func == op_border || next_func == op_internals)
+ {
+ result= count_internal(cur_func, next_func, &cur_func);
+ goto exit;
+ }
+
+ if (next_func == op_repeat)
+ {
+ result= count_internal(function_buffer.ptr() + n_ops, set_type, 0);
+ goto exit;
+ }
+
+ if (n_ops == 0)
+ return mask;
+ //GCALC_DBUG_RETURN(mask);
+
+ result= count_internal(cur_func, set_type, &cur_func);
+
+ while (--n_ops)
+ {
+ int next_res= count_internal(cur_func, set_type, &cur_func);
+ switch (next_func)
+ {
+ case op_union:
+ result= result | next_res;
+ break;
+ case op_intersection:
+ result= result & next_res;
+ break;
+ case op_symdifference:
+ result= result ^ next_res;
+ break;
+ case op_difference:
+ result= result & !next_res;
+ break;
+ default:
+ GCALC_DBUG_ASSERT(FALSE);
+ };
+ }
+
+exit:
+ result^= mask;
+ if (v_state != v_empty)
+ {
+ switch (v_state)
+ {
+ case v_find_t:
+ if (result)
+ {
+ c_op= (c_op & ~v_mask) | v_t_found;
+ int4store(sav_cur_func, c_op);
+ };
+ break;
+ case v_find_f:
+ if (!result)
+ {
+ c_op= (c_op & ~v_mask) | v_f_found;
+ int4store(sav_cur_func, c_op);
+ };
+ break;
+ case v_t_found:
+ result= 1;
+ break;
+ case v_f_found:
+ result= 0;
+ break;
+ default:
+ GCALC_DBUG_ASSERT(0);
+ };
+ }
+
+ if (end)
+ *end= cur_func;
+ return result;
+ //GCALC_DBUG_RETURN(result);
+}
+
+
+void Gcalc_function::clear_i_states()
+{
+ for (uint i= 0; i < n_shapes; i++)
+ i_states[i]= 0;
+}
+
+
+void Gcalc_function::clear_b_states()
+{
+ for (uint i= 0; i < n_shapes; i++)
+ b_states[i]= 0;
+}
+
+
+/*
+ Clear the state of the object.
+*/
+
+void Gcalc_function::reset()
+{
+ n_shapes= 0;
+ shapes_buffer.length(0);
+ function_buffer.length(0);
+}
+
+
+int Gcalc_function::check_function(Gcalc_scan_iterator &scan_it)
+{
+ const Gcalc_scan_iterator::point *eq_start, *cur_eq;
+ const Gcalc_scan_iterator::event_point *events;
+ GCALC_DBUG_ENTER("Gcalc_function::check_function");
+
+ while (scan_it.more_points())
+ {
+ if (scan_it.step())
+ GCALC_DBUG_RETURN(-1);
+ events= scan_it.get_events();
+
+ /* these kinds of events don't change the function */
+ Gcalc_point_iterator pit(&scan_it);
+ clear_b_states();
+ clear_i_states();
+ /* Walk to the event, marking polygons we met */
+ for (; pit.point() != scan_it.get_event_position(); ++pit)
+ {
+ gcalc_shape_info si= pit.point()->get_shape();
+ if ((get_shape_kind(si) == Gcalc_function::shape_polygon))
+ invert_i_state(si);
+ }
+ if (events->simple_event())
+ {
+ if (events->event == scev_end)
+ set_b_state(events->get_shape());
+
+ if (count())
+ GCALC_DBUG_RETURN(1);
+ clear_b_states();
+ continue;
+ }
+
+ /* Check the status of the event point */
+ for (; events; events= events->get_next())
+ {
+ gcalc_shape_info si= events->get_shape();
+ if (events->event == scev_thread ||
+ events->event == scev_end ||
+ events->event == scev_single_point ||
+ (get_shape_kind(si) == Gcalc_function::shape_polygon))
+ set_b_state(si);
+ else if (get_shape_kind(si) == Gcalc_function::shape_line)
+ set_i_state(si);
+ }
+
+ if (count())
+ GCALC_DBUG_RETURN(1);
+
+ /* Set back states changed in the loop above. */
+ for (events= scan_it.get_events(); events; events= events->get_next())
+ {
+ gcalc_shape_info si= events->get_shape();
+ if (events->event == scev_thread ||
+ events->event == scev_end ||
+ events->event == scev_single_point ||
+ (get_shape_kind(si) == Gcalc_function::shape_polygon))
+ clear_b_state(si);
+ else if (get_shape_kind(si) == Gcalc_function::shape_line)
+ clear_i_state(si);
+ }
+
+ if (scan_it.get_event_position() == scan_it.get_event_end())
+ continue;
+
+ /* Check the status after the event */
+ eq_start= pit.point();
+ do
+ {
+ ++pit;
+ if (pit.point() != scan_it.get_event_end() &&
+ eq_start->cmp_dx_dy(pit.point()) == 0)
+ continue;
+ for (cur_eq= eq_start; cur_eq != pit.point();
+ cur_eq= cur_eq->get_next())
+ {
+ gcalc_shape_info si= cur_eq->get_shape();
+ if (get_shape_kind(si) == Gcalc_function::shape_polygon)
+ set_b_state(si);
+ else
+ invert_i_state(si);
+ }
+ if (count())
+ GCALC_DBUG_RETURN(1);
+
+ for (cur_eq= eq_start; cur_eq != pit.point(); cur_eq= cur_eq->get_next())
+ {
+ gcalc_shape_info si= cur_eq->get_shape();
+ if ((get_shape_kind(si) == Gcalc_function::shape_polygon))
+ {
+ clear_b_state(si);
+ invert_i_state(si);
+ }
+ else
+ invert_i_state(cur_eq->get_shape());
+ }
+ if (count())
+ GCALC_DBUG_RETURN(1);
+ eq_start= pit.point();
+ } while (pit.point() != scan_it.get_event_end());
+ }
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_transporter::single_point(double x, double y)
+{
+ gcalc_shape_info si;
+ return m_fn->single_shape_op(Gcalc_function::shape_point, &si) ||
+ int_single_point(si, x, y);
+}
+
+
+int Gcalc_operation_transporter::start_line()
+{
+ int_start_line();
+ return m_fn->single_shape_op(Gcalc_function::shape_line, &m_si);
+}
+
+
+int Gcalc_operation_transporter::complete_line()
+{
+ int_complete_line();
+ return 0;
+}
+
+
+int Gcalc_operation_transporter::start_poly()
+{
+ int_start_poly();
+ return m_fn->single_shape_op(Gcalc_function::shape_polygon, &m_si);
+}
+
+
+int Gcalc_operation_transporter::complete_poly()
+{
+ int_complete_poly();
+ return 0;
+}
+
+
+int Gcalc_operation_transporter::start_ring()
+{
+ int_start_ring();
+ return 0;
+}
+
+
+int Gcalc_operation_transporter::complete_ring()
+{
+ int_complete_ring();
+ return 0;
+}
+
+
+int Gcalc_operation_transporter::add_point(double x, double y)
+{
+ return int_add_point(m_si, x, y);
+}
+
+
+int Gcalc_operation_transporter::start_collection(int n_objects)
+{
+ if (m_fn->reserve_shape_buffer(n_objects) || m_fn->reserve_op_buffer(1))
+ return 1;
+ m_fn->add_operation(Gcalc_function::op_union, n_objects);
+ return 0;
+}
+
+
+int Gcalc_operation_transporter::empty_shape()
+{
+ if (m_fn->reserve_op_buffer(1))
+ return 1;
+ m_fn->add_operation(Gcalc_function::op_false, 0);
+ return 0;
+}
+
+
+int Gcalc_result_receiver::start_shape(Gcalc_function::shape_type shape)
+{
+ GCALC_DBUG_ENTER("Gcalc_result_receiver::start_shape");
+ if (buffer.reserve(4*2, 512))
+ GCALC_DBUG_RETURN(1);
+ cur_shape= shape;
+ shape_pos= buffer.length();
+ buffer.length(shape_pos + ((shape == Gcalc_function::shape_point) ? 4:8));
+ n_points= 0;
+ shape_area= 0.0;
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_result_receiver::add_point(double x, double y)
+{
+ GCALC_DBUG_ENTER("Gcalc_result_receiver::add_point");
+ if (n_points && x == prev_x && y == prev_y)
+ GCALC_DBUG_RETURN(0);
+
+ if (!n_points++)
+ {
+ prev_x= first_x= x;
+ prev_y= first_y= y;
+ GCALC_DBUG_RETURN(0);
+ }
+
+ shape_area+= prev_x*y - prev_y*x;
+
+ if (buffer.reserve(8*2, 512))
+ GCALC_DBUG_RETURN(1);
+ buffer.q_append(prev_x);
+ buffer.q_append(prev_y);
+ prev_x= x;
+ prev_y= y;
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_result_receiver::complete_shape()
+{
+ GCALC_DBUG_ENTER("Gcalc_result_receiver::complete_shape");
+ if (n_points == 0)
+ {
+ buffer.length(shape_pos);
+ GCALC_DBUG_RETURN(0);
+ }
+ if (n_points == 1)
+ {
+ if (cur_shape != Gcalc_function::shape_point)
+ {
+ if (cur_shape == Gcalc_function::shape_hole)
+ {
+ buffer.length(shape_pos);
+ GCALC_DBUG_RETURN(0);
+ }
+ cur_shape= Gcalc_function::shape_point;
+ buffer.length(buffer.length()-4);
+ }
+ }
+ else
+ {
+ GCALC_DBUG_ASSERT(cur_shape != Gcalc_function::shape_point);
+ if (cur_shape == Gcalc_function::shape_hole)
+ {
+ shape_area+= prev_x*first_y - prev_y*first_x;
+ if (fabs(shape_area) < 1e-8)
+ {
+ buffer.length(shape_pos);
+ GCALC_DBUG_RETURN(0);
+ }
+ }
+
+ if ((cur_shape == Gcalc_function::shape_polygon ||
+ cur_shape == Gcalc_function::shape_hole) &&
+ prev_x == first_x && prev_y == first_y)
+ {
+ n_points--;
+ buffer.write_at_position(shape_pos+4, n_points);
+ goto do_complete;
+ }
+ buffer.write_at_position(shape_pos+4, n_points);
+ }
+
+ if (buffer.reserve(8*2, 512))
+ GCALC_DBUG_RETURN(1);
+ buffer.q_append(prev_x);
+ buffer.q_append(prev_y);
+
+do_complete:
+ buffer.write_at_position(shape_pos, (uint32) cur_shape);
+
+ if (!n_shapes++)
+ {
+ GCALC_DBUG_ASSERT(cur_shape != Gcalc_function::shape_hole);
+ common_shapetype= cur_shape;
+ }
+ else if (cur_shape == Gcalc_function::shape_hole)
+ {
+ ++n_holes;
+ }
+ else if (!collection_result && (cur_shape != common_shapetype))
+ {
+ collection_result= true;
+ }
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_result_receiver::single_point(double x, double y)
+{
+ return start_shape(Gcalc_function::shape_point) ||
+ add_point(x, y) ||
+ complete_shape();
+}
+
+
+int Gcalc_result_receiver::done()
+{
+ return 0;
+}
+
+
+void Gcalc_result_receiver::reset()
+{
+ buffer.length(0);
+ collection_result= FALSE;
+ n_shapes= n_holes= 0;
+}
+
+
+int Gcalc_result_receiver::get_result_typeid()
+{
+ if (!n_shapes || collection_result)
+ return Geometry::wkb_geometrycollection;
+
+ switch (common_shapetype)
+ {
+ case Gcalc_function::shape_polygon:
+ return (n_shapes - n_holes == 1) ?
+ Geometry::wkb_polygon : Geometry::wkb_multipolygon;
+ case Gcalc_function::shape_point:
+ return (n_shapes == 1) ? Geometry::wkb_point : Geometry::wkb_multipoint;
+ case Gcalc_function::shape_line:
+ return (n_shapes == 1) ? Geometry::wkb_linestring :
+ Geometry::wkb_multilinestring;
+ default:
+ GCALC_DBUG_ASSERT(0);
+ }
+ return 0;
+}
+
+
+int Gcalc_result_receiver::move_hole(uint32 dest_position, uint32 source_position,
+ uint32 *position_shift)
+{
+ char *ptr;
+ int source_len;
+ GCALC_DBUG_ENTER("Gcalc_result_receiver::move_hole");
+ GCALC_DBUG_PRINT(("ps %d %d", dest_position, source_position));
+
+ *position_shift= source_len= buffer.length() - source_position;
+
+ if (dest_position == source_position)
+ GCALC_DBUG_RETURN(0);
+
+ if (buffer.reserve(source_len, MY_ALIGN(source_len, 512)))
+ GCALC_DBUG_RETURN(1);
+
+ ptr= (char *) buffer.ptr();
+ memmove(ptr + dest_position + source_len, ptr + dest_position,
+ buffer.length() - dest_position);
+ memcpy(ptr + dest_position, ptr + buffer.length(), source_len);
+ GCALC_DBUG_RETURN(0);
+}
+
+
+Gcalc_operation_reducer::Gcalc_operation_reducer(size_t blk_size) :
+ Gcalc_dyn_list(blk_size, sizeof(res_point)),
+#ifndef GCALC_DBUG_OFF
+ n_res_points(0),
+#endif /*GCALC_DBUG_OFF*/
+ m_res_hook((Gcalc_dyn_list::Item **)&m_result),
+ m_first_active_thread(NULL)
+{}
+
+
+void Gcalc_operation_reducer::init(Gcalc_function *fn, modes mode)
+{
+ m_fn= fn;
+ m_mode= mode;
+ m_first_active_thread= NULL;
+ m_lines= NULL;
+ m_lines_hook= (Gcalc_dyn_list::Item **) &m_lines;
+ m_poly_borders= NULL;
+ m_poly_borders_hook= (Gcalc_dyn_list::Item **) &m_poly_borders;
+ GCALC_SET_TERMINATED(killed, 0);
+}
+
+
+Gcalc_operation_reducer::
+Gcalc_operation_reducer(Gcalc_function *fn, modes mode, size_t blk_size) :
+ Gcalc_dyn_list(blk_size, sizeof(res_point)),
+ m_res_hook((Gcalc_dyn_list::Item **)&m_result)
+{
+ init(fn, mode);
+}
+
+
+void Gcalc_operation_reducer::res_point::set(const Gcalc_scan_iterator *si)
+{
+ intersection_point= si->intersection_step();
+ pi= si->get_cur_pi();
+}
+
+
+Gcalc_operation_reducer::res_point *
+ Gcalc_operation_reducer::add_res_point(Gcalc_function::shape_type type)
+{
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::add_res_point");
+ res_point *result= (res_point *)new_item();
+ *m_res_hook= result;
+ result->prev_hook= m_res_hook;
+ m_res_hook= &result->next;
+ result->type= type;
+#ifndef GCALC_DBUG_OFF
+ result->point_n= n_res_points++;
+#endif /*GCALC_DBUG_OFF*/
+ GCALC_DBUG_RETURN(result);
+}
+
+int Gcalc_operation_reducer::add_line(int incoming, active_thread *t,
+ const Gcalc_scan_iterator::point *p)
+{
+ line *l= new_line();
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::add_line");
+ if (!l)
+ GCALC_DBUG_RETURN(1);
+ l->incoming= incoming;
+ l->t= t;
+ l->p= p;
+ *m_lines_hook= l;
+ m_lines_hook= &l->next;
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::add_poly_border(int incoming,
+ active_thread *t, int prev_state, const Gcalc_scan_iterator::point *p)
+{
+ poly_border *b= new_poly_border();
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::add_poly_border");
+ if (!b)
+ GCALC_DBUG_RETURN(1);
+ b->incoming= incoming;
+ b->t= t;
+ b->prev_state= prev_state;
+ b->p= p;
+ *m_poly_borders_hook= b;
+ m_poly_borders_hook= &b->next;
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::continue_range(active_thread *t,
+ const Gcalc_heap::Info *p,
+ const Gcalc_heap::Info *p_next)
+{
+ res_point *rp= add_res_point(t->rp->type);
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::continue_range");
+ if (!rp)
+ GCALC_DBUG_RETURN(1);
+ rp->glue= NULL;
+ rp->down= t->rp;
+ t->rp->up= rp;
+ rp->intersection_point= false;
+ rp->pi= p;
+ t->rp= rp;
+ t->p1= p;
+ t->p2= p_next;
+ GCALC_DBUG_RETURN(0);
+}
+
+
+inline int Gcalc_operation_reducer::continue_i_range(active_thread *t,
+ const Gcalc_heap::Info *ii)
+{
+ res_point *rp= add_res_point(t->rp->type);
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::continue_i_range");
+ if (!rp)
+ GCALC_DBUG_RETURN(1);
+ rp->glue= NULL;
+ rp->down= t->rp;
+ t->rp->up= rp;
+ rp->intersection_point= true;
+ rp->pi= ii;
+ t->rp= rp;
+ GCALC_DBUG_RETURN(0);
+}
+
+int Gcalc_operation_reducer::end_couple(active_thread *t0, active_thread *t1,
+ const Gcalc_heap::Info *p)
+{
+ res_point *rp0, *rp1;
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::end_couple");
+ GCALC_DBUG_ASSERT(t0->rp->type == t1->rp->type);
+ if (!(rp0= add_res_point(t0->rp->type)) ||
+ !(rp1= add_res_point(t0->rp->type)))
+ GCALC_DBUG_RETURN(1);
+ rp0->down= t0->rp;
+ rp1->down= t1->rp;
+ rp1->glue= rp0;
+ rp0->glue= rp1;
+ rp0->up= rp1->up= NULL;
+ t0->rp->up= rp0;
+ t1->rp->up= rp1;
+ rp0->intersection_point= rp1->intersection_point= false;
+ rp0->pi= rp1->pi= p;
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::count_slice(Gcalc_scan_iterator *si)
+{
+ Gcalc_point_iterator pi(si);
+ int prev_state= 0;
+ int sav_prev_state;
+ active_thread *prev_range= NULL;
+ const Gcalc_scan_iterator::event_point *events;
+ const Gcalc_scan_iterator::point *eq_start;
+ active_thread **cur_t_hook= &m_first_active_thread;
+ active_thread **starting_t_hook;
+ active_thread *bottom_threads= NULL;
+ active_thread *eq_thread, *point_thread;;
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::count_slice");
+
+ m_fn->clear_i_states();
+ /* Walk to the event, remembering what is needed. */
+ for (; pi.point() != si->get_event_position();
+ ++pi, cur_t_hook= (active_thread **) &(*cur_t_hook)->next)
+ {
+ active_thread *cur_t= *cur_t_hook;
+ if (cur_t->enabled() &&
+ cur_t->rp->type == Gcalc_function::shape_polygon)
+ {
+ prev_state^= 1;
+ prev_range= prev_state ? cur_t : 0;
+ }
+ if (m_fn->get_shape_kind(pi.get_shape()) == Gcalc_function::shape_polygon)
+ m_fn->invert_i_state(pi.get_shape());
+ }
+
+ events= si->get_events();
+ if (events->simple_event())
+ {
+ active_thread *cur_t= *cur_t_hook;
+ switch (events->event)
+ {
+ case scev_point:
+ {
+ if (cur_t->enabled() &&
+ continue_range(cur_t, events->pi, events->next_pi))
+ GCALC_DBUG_RETURN(1);
+ break;
+ }
+ case scev_end:
+ {
+ if (cur_t->enabled() && end_line(cur_t, si))
+ GCALC_DBUG_RETURN(1);
+ *cur_t_hook= cur_t->get_next();
+ free_item(cur_t);
+ break;
+ }
+ case scev_two_ends:
+ {
+ if (cur_t->enabled() && cur_t->get_next()->enabled())
+ {
+ /* When two threads are ended here */
+ if (end_couple(cur_t, cur_t->get_next(), events->pi))
+ GCALC_DBUG_RETURN(1);
+ }
+ else if (cur_t->enabled() || cur_t->get_next()->enabled())
+ {
+ /* Rare case when edges of a polygon coincide */
+ if (end_line(cur_t->enabled() ? cur_t : cur_t->get_next(), si))
+ GCALC_DBUG_RETURN(1);
+ }
+ *cur_t_hook= cur_t->get_next()->get_next();
+ free_item(cur_t->next);
+ free_item(cur_t);
+ break;
+ }
+ default:
+ GCALC_DBUG_ASSERT(0);
+ }
+ GCALC_DBUG_RETURN(0);
+ }
+
+ starting_t_hook= cur_t_hook;
+ sav_prev_state= prev_state;
+
+ /* Walk through the event, collecting all the 'incoming' threads */
+ for (; events; events= events->get_next())
+ {
+ active_thread *cur_t= *cur_t_hook;
+
+ if (events->event == scev_single_point)
+ continue;
+
+ if (events->event == scev_thread ||
+ events->event == scev_two_threads)
+ {
+ active_thread *new_t= new_active_thread();
+ if (!new_t)
+ GCALC_DBUG_RETURN(1);
+ new_t->rp= NULL;
+ /* Insert into the main thread list before the current */
+ new_t->next= cur_t;
+ *cur_t_hook= new_t;
+ cur_t_hook= (active_thread **) &new_t->next;
+ }
+ else
+ {
+ if (events->is_bottom())
+ {
+ /* Move thread from the main list to the bottom_threads. */
+ *cur_t_hook= cur_t->get_next();
+ cur_t->next= bottom_threads;
+ bottom_threads= cur_t;
+ }
+ if (cur_t->enabled())
+ {
+ if (cur_t->rp->type == Gcalc_function::shape_line)
+ {
+ GCALC_DBUG_ASSERT(!prev_state);
+ add_line(1, cur_t, events);
+ }
+ else
+ {
+ add_poly_border(1, cur_t, prev_state, events);
+ prev_state^= 1;
+ }
+ if (!events->is_bottom())
+ {
+ active_thread *new_t= new_active_thread();
+ if (!new_t)
+ GCALC_DBUG_RETURN(1);
+ new_t->rp= NULL;
+ /* Replace the current thread with the new. */
+ new_t->next= cur_t->next;
+ *cur_t_hook= new_t;
+ cur_t_hook= (active_thread **) &new_t->next;
+ /* And move old to the bottom list */
+ cur_t->next= bottom_threads;
+ bottom_threads= cur_t;
+ }
+ }
+ else if (!events->is_bottom())
+ cur_t_hook= (active_thread **) &cur_t->next;
+ }
+ }
+ prev_state= sav_prev_state;
+ cur_t_hook= starting_t_hook;
+
+ eq_start= pi.point();
+ eq_thread= point_thread= *starting_t_hook;
+ m_fn->clear_b_states();
+ while (eq_start != si->get_event_end())
+ {
+ const Gcalc_scan_iterator::point *cur_eq;
+ int in_state, after_state;
+
+ ++pi;
+ point_thread= point_thread->get_next();
+
+ if (pi.point() != si->get_event_end() &&
+ eq_start->cmp_dx_dy(pi.point()) == 0)
+ continue;
+
+ for (cur_eq= eq_start; cur_eq != pi.point(); cur_eq= cur_eq->get_next())
+ m_fn->set_b_state(cur_eq->get_shape());
+ in_state= m_fn->count();
+
+ m_fn->clear_b_states();
+ for (cur_eq= eq_start; cur_eq != pi.point(); cur_eq= cur_eq->get_next())
+ {
+ gcalc_shape_info si= cur_eq->get_shape();
+ if ((m_fn->get_shape_kind(si) == Gcalc_function::shape_polygon))
+ m_fn->invert_i_state(si);
+ }
+ after_state= m_fn->count();
+ if (prev_state != after_state)
+ {
+ if (add_poly_border(0, eq_thread, prev_state, eq_start))
+ GCALC_DBUG_RETURN(1);
+ }
+ else if (!prev_state /* &&!after_state */ && in_state)
+ {
+ if (add_line(0, eq_thread, eq_start))
+ GCALC_DBUG_RETURN(1);
+ }
+
+ prev_state= after_state;
+ eq_start= pi.point();
+ eq_thread= point_thread;
+ }
+
+ if (!sav_prev_state && !m_poly_borders && !m_lines)
+ {
+ /* Check if we need to add the event point itself */
+ m_fn->clear_i_states();
+ /* b_states supposed to be clean already */
+ for (pi.restart(si); pi.point() != si->get_event_position(); ++pi)
+ {
+ if (m_fn->get_shape_kind(pi.get_shape()) == Gcalc_function::shape_polygon)
+ m_fn->invert_i_state(pi.get_shape());
+ }
+ for (events= si->get_events(); events; events= events->get_next())
+ m_fn->set_b_state(events->get_shape());
+
+ GCALC_DBUG_RETURN(m_fn->count() ? add_single_point(si) : 0);
+ }
+
+ if (m_poly_borders)
+ {
+ *m_poly_borders_hook= NULL;
+ while (m_poly_borders)
+ {
+ poly_border *pb1, *pb2;
+ pb1= m_poly_borders;
+ GCALC_DBUG_ASSERT(m_poly_borders->next);
+
+ pb2= get_pair_border(pb1);
+ /* Remove pb1 from the list. The pb2 already removed in get_pair_border. */
+ m_poly_borders= pb1->get_next();
+ if (connect_threads(pb1->incoming, pb2->incoming,
+ pb1->t, pb2->t, pb1->p, pb2->p,
+ prev_range, si, Gcalc_function::shape_polygon))
+ GCALC_DBUG_RETURN(1);
+
+ free_item(pb1);
+ free_item(pb2);
+ }
+ m_poly_borders_hook= (Gcalc_dyn_list::Item **) &m_poly_borders;
+ m_poly_borders= NULL;
+ }
+
+ if (m_lines)
+ {
+ *m_lines_hook= NULL;
+ if (m_lines->get_next() &&
+ !m_lines->get_next()->get_next())
+ {
+ if (connect_threads(m_lines->incoming, m_lines->get_next()->incoming,
+ m_lines->t, m_lines->get_next()->t,
+ m_lines->p, m_lines->get_next()->p,
+ NULL, si, Gcalc_function::shape_line))
+ GCALC_DBUG_RETURN(1);
+ }
+ else
+ {
+ for (line *cur_line= m_lines; cur_line; cur_line= cur_line->get_next())
+ {
+ if (cur_line->incoming)
+ {
+ if (end_line(cur_line->t, si))
+ GCALC_DBUG_RETURN(1);
+ }
+ else
+ start_line(cur_line->t, cur_line->p, si);
+ }
+ }
+ free_list(m_lines);
+ m_lines= NULL;
+ m_lines_hook= (Gcalc_dyn_list::Item **) &m_lines;
+ }
+
+ if (bottom_threads)
+ free_list(bottom_threads);
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::add_single_point(const Gcalc_scan_iterator *si)
+{
+ res_point *rp= add_res_point(Gcalc_function::shape_point);
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::add_single_point");
+ if (!rp)
+ GCALC_DBUG_RETURN(1);
+ rp->glue= rp->up= rp->down= NULL;
+ rp->set(si);
+ GCALC_DBUG_RETURN(0);
+}
+
+
+Gcalc_operation_reducer::poly_border
+ *Gcalc_operation_reducer::get_pair_border(poly_border *b1)
+{
+ poly_border *prev_b= b1;
+ poly_border *result= b1->get_next();
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::get_pair_border");
+ if (b1->prev_state)
+ {
+ if (b1->incoming)
+ {
+ /* Find the first outgoing, otherwise the last one. */
+ while (result->incoming && result->get_next())
+ {
+ prev_b= result;
+ result= result->get_next();
+ }
+ }
+ else
+ {
+ /* Get the last one */
+ while (result->get_next())
+ {
+ prev_b= result;
+ result= result->get_next();
+ }
+ }
+ }
+ else /* !b1->prev_state */
+ {
+ if (b1->incoming)
+ {
+ /* Get the next incoming, otherwise the last one. */
+ while (!result->incoming && result->get_next())
+ {
+ prev_b= result;
+ result= result->get_next();
+ }
+ }
+ else
+ {
+ /* Just pick the next one */
+ }
+ }
+ /* Delete the result from the list. */
+ prev_b->next= result->next;
+ GCALC_DBUG_RETURN(result);
+}
+
+
+int Gcalc_operation_reducer::connect_threads(
+ int incoming_a, int incoming_b,
+ active_thread *ta, active_thread *tb,
+ const Gcalc_scan_iterator::point *pa, const Gcalc_scan_iterator::point *pb,
+ active_thread *prev_range,
+ const Gcalc_scan_iterator *si, Gcalc_function::shape_type s_t)
+{
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::connect_threads");
+ GCALC_DBUG_PRINT(("incoming %d %d", incoming_a, incoming_b));
+ if (incoming_a && incoming_b)
+ {
+ res_point *rpa, *rpb;
+ GCALC_DBUG_ASSERT(ta->rp->type == tb->rp->type);
+ if (!(rpa= add_res_point(ta->rp->type)) ||
+ !(rpb= add_res_point(ta->rp->type)))
+ GCALC_DBUG_RETURN(1);
+ rpa->down= ta->rp;
+ rpb->down= tb->rp;
+ rpb->glue= rpa;
+ rpa->glue= rpb;
+ rpa->up= rpb->up= NULL;
+ ta->rp->up= rpa;
+ tb->rp->up= rpb;
+ rpa->set(si);
+ rpb->set(si);
+ ta->rp= tb->rp= NULL;
+ GCALC_DBUG_RETURN(0);
+ }
+ if (!incoming_a)
+ {
+ GCALC_DBUG_ASSERT(!incoming_b);
+
+ res_point *rp0, *rp1;
+ if (!(rp0= add_res_point(s_t)) || !(rp1= add_res_point(s_t)))
+ GCALC_DBUG_RETURN(1);
+ rp0->glue= rp1;
+ rp1->glue= rp0;
+ rp0->set(si);
+ rp1->set(si);
+ rp0->down= rp1->down= NULL;
+ ta->rp= rp0;
+ tb->rp= rp1;
+ ta->p1= pa->pi;
+ ta->p2= pa->next_pi;
+
+ tb->p1= pb->pi;
+ tb->p2= pb->next_pi;
+
+ if (prev_range)
+ {
+ rp0->outer_poly= prev_range->thread_start;
+ tb->thread_start= prev_range->thread_start;
+ /* Chack if needed */
+ ta->thread_start= prev_range->thread_start;
+ }
+ else
+ {
+ rp0->outer_poly= 0;
+ ta->thread_start= rp0;
+ /* Chack if needed */
+ tb->thread_start= rp0;
+ }
+ GCALC_DBUG_RETURN(0);
+ }
+ /* else, if only ta is incoming */
+
+ GCALC_DBUG_ASSERT(tb != ta);
+ tb->rp= ta->rp;
+ tb->thread_start= ta->thread_start;
+ if (Gcalc_scan_iterator::point::
+ cmp_dx_dy(ta->p1, ta->p2, pb->pi, pb->next_pi) != 0)
+ {
+ if (si->intersection_step() ?
+ continue_i_range(tb, si->get_cur_pi()) :
+ continue_range(tb, si->get_cur_pi(), pb->next_pi))
+ GCALC_DBUG_RETURN(1);
+ }
+ tb->p1= pb->pi;
+ tb->p2= pb->next_pi;
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::start_line(active_thread *t,
+ const Gcalc_scan_iterator::point *p,
+ const Gcalc_scan_iterator *si)
+{
+ res_point *rp= add_res_point(Gcalc_function::shape_line);
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::start_line");
+ if (!rp)
+ GCALC_DBUG_RETURN(1);
+ rp->glue= rp->down= NULL;
+ rp->set(si);
+ t->rp= rp;
+ t->p1= p->pi;
+ t->p2= p->next_pi;
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::end_line(active_thread *t,
+ const Gcalc_scan_iterator *si)
+{
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::end_line");
+ GCALC_DBUG_ASSERT(t->rp->type == Gcalc_function::shape_line);
+ res_point *rp= add_res_point(Gcalc_function::shape_line);
+ if (!rp)
+ GCALC_DBUG_RETURN(1);
+ rp->glue= rp->up= NULL;
+ rp->down= t->rp;
+ rp->set(si);
+ t->rp->up= rp;
+ t->rp= NULL;
+
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::count_all(Gcalc_heap *hp)
+{
+ Gcalc_scan_iterator si;
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::count_all");
+ si.init(hp);
+ GCALC_SET_TERMINATED(si.killed, killed);
+ while (si.more_points())
+ {
+ if (si.step())
+ GCALC_DBUG_RETURN(1);
+ if (count_slice(&si))
+ GCALC_DBUG_RETURN(1);
+ }
+ GCALC_DBUG_RETURN(0);
+}
+
+inline void Gcalc_operation_reducer::free_result(res_point *res)
+{
+ if ((*res->prev_hook= res->next))
+ {
+ res->get_next()->prev_hook= res->prev_hook;
+ }
+ free_item(res);
+}
+
+
+inline int Gcalc_operation_reducer::get_single_result(res_point *res,
+ Gcalc_result_receiver *storage)
+{
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::get_single_result");
+ if (res->intersection_point)
+ {
+ double x, y;
+ res->pi->calc_xy(&x, &y);
+ if (storage->single_point(x,y))
+ GCALC_DBUG_RETURN(1);
+ }
+ else
+ if (storage->single_point(res->pi->x, res->pi->y))
+ GCALC_DBUG_RETURN(1);
+ free_result(res);
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::get_result_thread(res_point *cur,
+ Gcalc_result_receiver *storage,
+ int move_upward,
+ res_point *first_poly_node)
+{
+ res_point *next;
+ bool glue_step= false;
+ double x, y;
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::get_result_thread");
+ while (cur)
+ {
+ if (!glue_step)
+ {
+ if (cur->intersection_point)
+ {
+ cur->pi->calc_xy(&x, &y);
+ }
+ else
+ {
+ x= cur->pi->x;
+ y= cur->pi->y;
+ }
+ if (storage->add_point(x, y))
+ GCALC_DBUG_RETURN(1);
+ }
+
+ next= move_upward ? cur->up : cur->down;
+ if (!next && !glue_step)
+ {
+ next= cur->glue;
+ move_upward^= 1;
+ glue_step= true;
+ if (next)
+ next->glue= NULL;
+ }
+ else
+ glue_step= false;
+
+ cur->first_poly_node= first_poly_node;
+ free_result(cur);
+ cur= next;
+ }
+ GCALC_DBUG_RETURN(0);
+}
+
+
+int Gcalc_operation_reducer::get_polygon_result(res_point *cur,
+ Gcalc_result_receiver *storage,
+ res_point *first_poly_node)
+{
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::get_polygon_result");
+ res_point *glue= cur->glue;
+ glue->up->down= NULL;
+ free_result(glue);
+ GCALC_DBUG_RETURN(get_result_thread(cur, storage, 1, first_poly_node) ||
+ storage->complete_shape());
+}
+
+
+int Gcalc_operation_reducer::get_line_result(res_point *cur,
+ Gcalc_result_receiver *storage)
+{
+ res_point *next;
+ res_point *cur_orig= cur;
+ int move_upward= 1;
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::get_line_result");
+ if (cur->glue)
+ {
+ /* Here we have to find the beginning of the line */
+ next= cur->up;
+ move_upward= 1;
+ while (next)
+ {
+ cur= next;
+ next= move_upward ? next->up : next->down;
+ if (!next)
+ {
+ next= cur->glue;
+ if (next == cur_orig)
+ {
+ /* It's the line loop */
+ cur= cur_orig;
+ cur->glue->glue= NULL;
+ move_upward= 1;
+ break;
+ }
+ move_upward^= 1;
+ }
+ }
+ }
+
+ GCALC_DBUG_RETURN(get_result_thread(cur, storage, move_upward, 0) ||
+ storage->complete_shape());
+}
+
+
+int Gcalc_operation_reducer::get_result(Gcalc_result_receiver *storage)
+{
+ poly_instance *polygons= NULL;
+
+ GCALC_DBUG_ENTER("Gcalc_operation_reducer::get_result");
+ *m_res_hook= NULL;
+
+ /* This is to workaround an old gcc's bug */
+ if (m_res_hook == (Gcalc_dyn_list::Item **) &m_result)
+ goto done;
+
+ while (m_result)
+ {
+ Gcalc_function::shape_type shape= m_result->type;
+ if (shape == Gcalc_function::shape_point)
+ {
+ if (get_single_result(m_result, storage))
+ GCALC_DBUG_RETURN(1);
+ continue;
+ }
+ if (shape == Gcalc_function::shape_polygon)
+ {
+ if (m_result->outer_poly)
+ {
+ uint32 insert_position, hole_position, position_shift;
+ poly_instance *cur_poly;
+ insert_position= m_result->outer_poly->first_poly_node->poly_position;
+ GCALC_DBUG_ASSERT(insert_position);
+ hole_position= storage->position();
+ storage->start_shape(Gcalc_function::shape_hole);
+ if (get_polygon_result(m_result, storage,
+ m_result->outer_poly->first_poly_node) ||
+ storage->move_hole(insert_position, hole_position,
+ &position_shift))
+ GCALC_DBUG_RETURN(1);
+ for (cur_poly= polygons;
+ cur_poly && *cur_poly->after_poly_position >= insert_position;
+ cur_poly= cur_poly->get_next())
+ *cur_poly->after_poly_position+= position_shift;
+ }
+ else
+ {
+ uint32 *poly_position= &m_result->poly_position;
+ poly_instance *p= new_poly();
+ p->after_poly_position= poly_position;
+ p->next= polygons;
+ polygons= p;
+ storage->start_shape(Gcalc_function::shape_polygon);
+ if (get_polygon_result(m_result, storage, m_result))
+ GCALC_DBUG_RETURN(1);
+ *poly_position= storage->position();
+ }
+ }
+ else
+ {
+ storage->start_shape(shape);
+ if (get_line_result(m_result, storage))
+ GCALC_DBUG_RETURN(1);
+ }
+ }
+
+done:
+ m_res_hook= (Gcalc_dyn_list::Item **)&m_result;
+ storage->done();
+ GCALC_DBUG_RETURN(0);
+}
+
+
+void Gcalc_operation_reducer::reset()
+{
+ free_list((Gcalc_heap::Item **) &m_result, m_res_hook);
+ m_res_hook= (Gcalc_dyn_list::Item **)&m_result;
+ free_list(m_first_active_thread);
+}
+
+#endif /*HAVE_SPATIAL*/
+
diff --git a/sql/gcalc_tools.h b/sql/gcalc_tools.h
new file mode 100644
index 00000000000..12ee56732a2
--- /dev/null
+++ b/sql/gcalc_tools.h
@@ -0,0 +1,348 @@
+/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved.
+ Copyright (C) 2011 Monty Program Ab.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#ifndef GCALC_TOOLS_INCLUDED
+#define GCALC_TOOLS_INCLUDED
+
+#include "gcalc_slicescan.h"
+#include "sql_string.h"
+
+
+/*
+ The Gcalc_function class objects are used to check for a binary relation.
+ The relation can be constructed with the prefix notation using predicates as
+ op_not (as !A)
+ op_union ( A || B || C... )
+ op_intersection ( A && B && C ... )
+ op_symdifference ( A+B+C+... == 1 )
+ op_difference ( A && !(B||C||..))
+ with the calls of the add_operation(operation, n_operands) method.
+ The relation is calculated over a set of shapes, that in turn have
+ to be added with the add_new_shape() method. All the 'shapes' can
+ be set to 0 with clear_shapes() method and single value
+ can be changed with the invert_state() method.
+ Then the value of the relation can be calculated with the count() method.
+ Frequently used method is find_function(Gcalc_scan_iterator it) that
+ iterates through the 'it' until the relation becomes TRUE.
+*/
+
+class Gcalc_function
+{
+private:
+ String shapes_buffer;
+ String function_buffer;
+ int *i_states;
+ int *b_states;
+ uint32 cur_object_id;
+ uint n_shapes;
+ int count_internal(const char *cur_func, uint set_type,
+ const char **end);
+public:
+ enum value
+ {
+ v_empty= 0x0000000,
+ v_find_t= 0x1000000,
+ v_find_f= 0x2000000,
+ v_t_found= 0x3000000,
+ v_f_found= 0x4000000,
+ v_mask= 0x7000000
+ };
+ enum op_type
+ {
+ op_not= 0x80000000,
+ op_shape= 0x00000000,
+ op_union= 0x10000000,
+ op_intersection= 0x20000000,
+ op_symdifference= 0x30000000,
+ op_difference= 0x40000000,
+ op_repeat= 0x50000000,
+ op_border= 0x60000000,
+ op_internals= 0x70000000,
+ op_false= 0x08000000,
+ op_any= 0x78000000 /* The mask to get any of the operations */
+ };
+ enum shape_type
+ {
+ shape_point= 0,
+ shape_line= 1,
+ shape_polygon= 2,
+ shape_hole= 3
+ };
+ Gcalc_function() : n_shapes(0) {}
+ gcalc_shape_info add_new_shape(uint32 shape_id, shape_type shape_kind);
+ /*
+ Adds the leaf operation that returns the shape value.
+ Also adds the shape to the list of operands.
+ */
+ int single_shape_op(shape_type shape_kind, gcalc_shape_info *si);
+ void add_operation(uint operation, uint32 n_operands);
+ void add_not_operation(op_type operation, uint32 n_operands);
+ uint32 get_next_expression_pos() { return function_buffer.length(); }
+ void add_operands_to_op(uint32 operation_pos, uint32 n_operands);
+ int repeat_expression(uint32 exp_pos);
+ void set_cur_obj(uint32 cur_obj) { cur_object_id= cur_obj; }
+ int reserve_shape_buffer(uint n_shapes);
+ int reserve_op_buffer(uint n_ops);
+ uint get_nshapes() const { return n_shapes; }
+ shape_type get_shape_kind(gcalc_shape_info si) const
+ {
+ return (shape_type) uint4korr(shapes_buffer.ptr() + (si*4));
+ }
+
+ void set_states(int *shape_states) { i_states= shape_states; }
+ int alloc_states();
+ void invert_i_state(gcalc_shape_info shape) { i_states[shape]^= 1; }
+ void set_i_state(gcalc_shape_info shape) { i_states[shape]= 1; }
+ void clear_i_state(gcalc_shape_info shape) { i_states[shape]= 0; }
+ void set_b_state(gcalc_shape_info shape) { b_states[shape]= 1; }
+ void clear_b_state(gcalc_shape_info shape) { b_states[shape]= 0; }
+ int get_state(gcalc_shape_info shape)
+ { return i_states[shape] | b_states[shape]; }
+ int get_i_state(gcalc_shape_info shape) { return i_states[shape]; }
+ int get_b_state(gcalc_shape_info shape) { return b_states[shape]; }
+ int count()
+ { return count_internal(function_buffer.ptr(), 0, 0); }
+ void clear_i_states();
+ void clear_b_states();
+ void reset();
+
+ int check_function(Gcalc_scan_iterator &scan_it);
+};
+
+
+/*
+ Gcalc_operation_transporter class extends the Gcalc_shape_transporter.
+ In addition to the parent's functionality, it fills the Gcalc_function
+ object so it has the function that determines the proper shape.
+ For example Multipolyline will be represented as an union of polylines.
+*/
+
+class Gcalc_operation_transporter : public Gcalc_shape_transporter
+{
+protected:
+ Gcalc_function *m_fn;
+ gcalc_shape_info m_si;
+public:
+ Gcalc_operation_transporter(Gcalc_function *fn, Gcalc_heap *heap) :
+ Gcalc_shape_transporter(heap), m_fn(fn) {}
+
+ int single_point(double x, double y);
+ int start_line();
+ int complete_line();
+ int start_poly();
+ int complete_poly();
+ int start_ring();
+ int complete_ring();
+ int add_point(double x, double y);
+ int start_collection(int n_objects);
+ int empty_shape();
+};
+
+
+/*
+ When we calculate the result of an spatial operation like
+ Union or Intersection, we receive vertexes of the result
+ one-by-one, and probably need to treat them in variative ways.
+ So, the Gcalc_result_receiver class designed to get these
+ vertexes and construct shapes/objects out of them.
+ and to store the result in an appropriate format
+*/
+
+class Gcalc_result_receiver
+{
+ String buffer;
+ uint32 n_points;
+ Gcalc_function::shape_type common_shapetype;
+ bool collection_result;
+ uint32 n_shapes;
+ uint32 n_holes;
+
+ Gcalc_function::shape_type cur_shape;
+ uint32 shape_pos;
+ double first_x, first_y, prev_x, prev_y;
+ double shape_area;
+public:
+ Gcalc_result_receiver() : collection_result(FALSE), n_shapes(0), n_holes(0)
+ {}
+ int start_shape(Gcalc_function::shape_type shape);
+ int add_point(double x, double y);
+ int complete_shape();
+ int single_point(double x, double y);
+ int done();
+ void reset();
+
+ const char *result() { return buffer.ptr(); }
+ uint length() { return buffer.length(); }
+ int get_nshapes() { return n_shapes; }
+ int get_nholes() { return n_holes; }
+ int get_result_typeid();
+ uint32 position() { return buffer.length(); }
+ int move_hole(uint32 dest_position, uint32 source_position,
+ uint32 *position_shift);
+};
+
+
+/*
+ Gcalc_operation_reducer class incapsulates the spatial
+ operation functionality. It analyses the slices generated by
+ the slicescan and calculates the shape of the result defined
+ by some Gcalc_function.
+*/
+
+class Gcalc_operation_reducer : public Gcalc_dyn_list
+{
+public:
+ enum modes
+ {
+ /* Numeric values important here - careful with changing */
+ default_mode= 0,
+ prefer_big_with_holes= 1,
+ polygon_selfintersections_allowed= 2, /* allowed in the result */
+ line_selfintersections_allowed= 4 /* allowed in the result */
+ };
+
+ Gcalc_operation_reducer(size_t blk_size=8192);
+ void init(Gcalc_function *fn, modes mode= default_mode);
+ Gcalc_operation_reducer(Gcalc_function *fn, modes mode= default_mode,
+ size_t blk_size=8192);
+ GCALC_DECL_TERMINATED_STATE(killed)
+ int count_slice(Gcalc_scan_iterator *si);
+ int count_all(Gcalc_heap *hp);
+ int get_result(Gcalc_result_receiver *storage);
+ void reset();
+
+#ifndef GCALC_DBUG_OFF
+ int n_res_points;
+#endif /*GCALC_DBUG_OFF*/
+ class res_point : public Gcalc_dyn_list::Item
+ {
+ public:
+ int intersection_point;
+ union
+ {
+ const Gcalc_heap::Info *pi;
+ res_point *first_poly_node;
+ };
+ union
+ {
+ res_point *outer_poly;
+ uint32 poly_position;
+ };
+ res_point *up;
+ res_point *down;
+ res_point *glue;
+ Gcalc_function::shape_type type;
+ Gcalc_dyn_list::Item **prev_hook;
+#ifndef GCALC_DBUG_OFF
+ int point_n;
+#endif /*GCALC_DBUG_OFF*/
+ void set(const Gcalc_scan_iterator *si);
+ res_point *get_next() { return (res_point *)next; }
+ };
+
+ class active_thread : public Gcalc_dyn_list::Item
+ {
+ public:
+ res_point *rp;
+ res_point *thread_start;
+
+ const Gcalc_heap::Info *p1, *p2;
+ res_point *enabled() { return rp; }
+ active_thread *get_next() { return (active_thread *)next; }
+ };
+
+ class poly_instance : public Gcalc_dyn_list::Item
+ {
+ public:
+ uint32 *after_poly_position;
+ poly_instance *get_next() { return (poly_instance *)next; }
+ };
+
+ class line : public Gcalc_dyn_list::Item
+ {
+ public:
+ active_thread *t;
+ int incoming;
+ const Gcalc_scan_iterator::point *p;
+ line *get_next() { return (line *)next; }
+ };
+
+ class poly_border : public Gcalc_dyn_list::Item
+ {
+ public:
+ active_thread *t;
+ int incoming;
+ int prev_state;
+ const Gcalc_scan_iterator::point *p;
+ poly_border *get_next() { return (poly_border *)next; }
+ };
+
+ line *m_lines;
+ Gcalc_dyn_list::Item **m_lines_hook;
+ poly_border *m_poly_borders;
+ Gcalc_dyn_list::Item **m_poly_borders_hook;
+ line *new_line() { return (line *) new_item(); }
+ poly_border *new_poly_border() { return (poly_border *) new_item(); }
+ int add_line(int incoming, active_thread *t,
+ const Gcalc_scan_iterator::point *p);
+ int add_poly_border(int incoming, active_thread *t, int prev_state,
+ const Gcalc_scan_iterator::point *p);
+
+protected:
+ Gcalc_function *m_fn;
+ Gcalc_dyn_list::Item **m_res_hook;
+ res_point *m_result;
+ int m_mode;
+
+ res_point *result_heap;
+ active_thread *m_first_active_thread;
+
+ res_point *add_res_point(Gcalc_function::shape_type type);
+ active_thread *new_active_thread() { return (active_thread *)new_item(); }
+
+ poly_instance *new_poly() { return (poly_instance *) new_item(); }
+
+private:
+ int start_line(active_thread *t, const Gcalc_scan_iterator::point *p,
+ const Gcalc_scan_iterator *si);
+ int end_line(active_thread *t, const Gcalc_scan_iterator *si);
+ int connect_threads(int incoming_a, int incoming_b,
+ active_thread *ta, active_thread *tb,
+ const Gcalc_scan_iterator::point *pa,
+ const Gcalc_scan_iterator::point *pb,
+ active_thread *prev_range,
+ const Gcalc_scan_iterator *si,
+ Gcalc_function::shape_type s_t);
+ int add_single_point(const Gcalc_scan_iterator *si);
+ poly_border *get_pair_border(poly_border *b1);
+ int continue_range(active_thread *t, const Gcalc_heap::Info *p,
+ const Gcalc_heap::Info *p_next);
+ int continue_i_range(active_thread *t,
+ const Gcalc_heap::Info *ii);
+ int end_couple(active_thread *t0, active_thread *t1, const Gcalc_heap::Info *p);
+ int get_single_result(res_point *res, Gcalc_result_receiver *storage);
+ int get_result_thread(res_point *cur, Gcalc_result_receiver *storage,
+ int move_upward, res_point *first_poly_node);
+ int get_polygon_result(res_point *cur, Gcalc_result_receiver *storage,
+ res_point *first_poly_node);
+ int get_line_result(res_point *cur, Gcalc_result_receiver *storage);
+
+ void free_result(res_point *res);
+};
+
+#endif /*GCALC_TOOLS_INCLUDED*/
+
diff --git a/sql/gen_lex_hash.cc b/sql/gen_lex_hash.cc
new file mode 100644
index 00000000000..c37f4f145cf
--- /dev/null
+++ b/sql/gen_lex_hash.cc
@@ -0,0 +1,479 @@
+/*
+ Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+
+ 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 */
+
+/**
+ @file
+
+ @details
+@verbatim
+The idea of presented algorithm see in
+"The Art of Computer Programming" by Donald E. Knuth
+Volume 3 "Sorting and searching"
+(chapter 6.3 "Digital searching" - name and number of chapter
+ is back translation from Russian edition :))
+
+as illustration of data structures, imagine next table:
+
+static SYMBOL symbols[] = {
+ { "ADD", SYM(ADD),0,0},
+ { "AND", SYM(AND),0,0},
+ { "DAY", SYM(DAY_SYM),0,0},
+};
+
+for this structure, presented program generate next searching-structure:
+
++-----------+-+-+-+
+| len |1|2|3|
++-----------+-+-+-+
+|first_char |0|0|a|
+|last_char |0|0|d|
+|link |0|0|+|
+ |
+ V
+ +----------+-+-+-+--+
+ | 1 char|a|b|c|d |
+ +----------+-+-+-+--+
+ |first_char|b|0|0|0 |
+ |last_char |n|0|0|-1|
+ |link |+|0|0|+ |
+ | |
+ | V
+ | symbols[2] ( "DAY" )
+ V
++----------+--+-+-+-+-+-+-+-+-+-+--+
+| 2 char|d |e|f|j|h|i|j|k|l|m|n |
++----------+--+-+-+-+-+-+-+-+-+-+--+
+|first_char|0 |0|0|0|0|0|0|0|0|0|0 |
+|last_char |-1|0|0|0|0|0|0|0|0|0|-1|
+|link |+ |0|0|0|0|0|0|0|0|0|+ |
+ | |
+ V V
+ symbols[0] ( "ADD" ) symbols[1] ( "AND" )
+
+for optimization, link is the 16-bit index in 'symbols' or 'sql_functions'
+or search-array..
+
+So, we can read full search-structure as 32-bit word
+@endverbatim
+
+@todo
+ use instead to_upper_lex, special array
+ (substitute chars) without skip codes..
+@todo
+ try use reverse order of comparing..
+
+*/
+
+#define NO_YACC_SYMBOLS
+#include <my_global.h>
+#include "mysql_version.h"
+#include "lex.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */
+
+struct hash_lex_struct
+{
+ int first_char;
+ char last_char;
+ union{
+ hash_lex_struct *char_tails;
+ int iresult;
+ };
+ int ithis;
+};
+
+hash_lex_struct *get_hash_struct_by_len(hash_lex_struct **root_by_len,
+ int len, int *max_len)
+{
+ if (*max_len<len){
+ *root_by_len= (hash_lex_struct *)realloc((char*)*root_by_len,
+ sizeof(hash_lex_struct)*len);
+ hash_lex_struct *cur, *end= *root_by_len + len;
+ for (cur= *root_by_len + *max_len; cur<end; cur++)
+ cur->first_char= 0;
+ *max_len= len;
+ }
+ return (*root_by_len)+(len-1);
+}
+
+void insert_into_hash(hash_lex_struct *root, const char *name,
+ int len_from_begin, int index, int function)
+{
+ hash_lex_struct *end, *cur, *tails;
+
+ if (!root->first_char)
+ {
+ root->first_char= -1;
+ root->iresult= index;
+ return;
+ }
+
+ if (root->first_char == -1)
+ {
+ int index2= root->iresult;
+ const char *name2= (index2 < 0 ? sql_functions[-index2-1] :
+ symbols[index2]).name + len_from_begin;
+ root->first_char= (int) (uchar) name2[0];
+ root->last_char= (char) root->first_char;
+ tails= (hash_lex_struct*)malloc(sizeof(hash_lex_struct));
+ root->char_tails= tails;
+ tails->first_char= -1;
+ tails->iresult= index2;
+ }
+
+ size_t real_size= (root->last_char-root->first_char+1);
+
+ if (root->first_char>(*name))
+ {
+ size_t new_size= root->last_char-(*name)+1;
+ if (new_size<real_size) printf("error!!!!\n");
+ tails= root->char_tails;
+ tails= (hash_lex_struct*)realloc((char*)tails,
+ sizeof(hash_lex_struct)*new_size);
+ root->char_tails= tails;
+ memmove(tails+(new_size-real_size),tails,real_size*sizeof(hash_lex_struct));
+ end= tails + new_size - real_size;
+ for (cur= tails; cur<end; cur++)
+ cur->first_char= 0;
+ root->first_char= (int) (uchar) *name;
+ }
+
+ if (root->last_char<(*name))
+ {
+ size_t new_size= (*name)-root->first_char+1;
+ if (new_size<real_size) printf("error!!!!\n");
+ tails= root->char_tails;
+ tails= (hash_lex_struct*)realloc((char*)tails,
+ sizeof(hash_lex_struct)*new_size);
+ root->char_tails= tails;
+ end= tails + new_size;
+ for (cur= tails+real_size; cur<end; cur++)
+ cur->first_char= 0;
+ root->last_char= (*name);
+ }
+
+ insert_into_hash(root->char_tails+(*name)-root->first_char,
+ name+1,len_from_begin+1,index,function);
+}
+
+
+hash_lex_struct *root_by_len= 0;
+int max_len=0;
+
+hash_lex_struct *root_by_len2= 0;
+int max_len2=0;
+
+void insert_symbols()
+{
+ size_t i= 0;
+ SYMBOL *cur;
+ for (cur= symbols; i<array_elements(symbols); cur++, i++){
+ hash_lex_struct *root=
+ get_hash_struct_by_len(&root_by_len,cur->length,&max_len);
+ insert_into_hash(root,cur->name,0,(uint) i,0);
+ }
+}
+
+void insert_sql_functions()
+{
+ int i= 0;
+ SYMBOL *cur;
+ for (cur= sql_functions; i < (int) array_elements(sql_functions); cur++, i++)
+ {
+ hash_lex_struct *root=
+ get_hash_struct_by_len(&root_by_len,cur->length,&max_len);
+ insert_into_hash(root,cur->name,0,-i-1,1);
+ }
+}
+
+void calc_length()
+{
+ SYMBOL *cur, *end= symbols + array_elements(symbols);
+ for (cur= symbols; cur < end; cur++)
+ cur->length=(uchar) strlen(cur->name);
+ end= sql_functions + array_elements(sql_functions);
+ for (cur= sql_functions; cur<end; cur++)
+ cur->length=(uchar) strlen(cur->name);
+}
+
+void generate_find_structs()
+{
+ root_by_len= 0;
+ max_len=0;
+
+ insert_symbols();
+
+ root_by_len2= root_by_len;
+ max_len2= max_len;
+
+ root_by_len= 0;
+ max_len= 0;
+
+ insert_symbols();
+ insert_sql_functions();
+}
+
+char *hash_map= 0;
+int size_hash_map= 0;
+
+void add_struct_to_map(hash_lex_struct *st)
+{
+ st->ithis= size_hash_map/4;
+ size_hash_map+= 4;
+ hash_map= (char*)realloc((char*)hash_map,size_hash_map);
+ hash_map[size_hash_map-4]= (char) (st->first_char == -1 ? 0 :
+ st->first_char);
+ hash_map[size_hash_map-3]= (char) (st->first_char == -1 ||
+ st->first_char == 0 ? 0 : st->last_char);
+ if (st->first_char == -1)
+ {
+ hash_map[size_hash_map-2]= ((unsigned int)(int16)st->iresult)&255;
+ hash_map[size_hash_map-1]= ((unsigned int)(int16)st->iresult)>>8;
+ }
+ else if (st->first_char == 0)
+ {
+ hash_map[size_hash_map-2]= ((unsigned int)(int16)array_elements(symbols))&255;
+ hash_map[size_hash_map-1]= ((unsigned int)(int16)array_elements(symbols))>>8;
+ }
+}
+
+
+void add_structs_to_map(hash_lex_struct *st, int len)
+{
+ hash_lex_struct *cur, *end= st+len;
+ for (cur= st; cur<end; cur++)
+ add_struct_to_map(cur);
+ for (cur= st; cur<end; cur++)
+ {
+ if (cur->first_char && cur->first_char != -1)
+ add_structs_to_map(cur->char_tails,cur->last_char-cur->first_char+1);
+ }
+}
+
+void set_links(hash_lex_struct *st, int len)
+{
+ hash_lex_struct *cur, *end= st+len;
+ for (cur= st; cur<end; cur++)
+ {
+ if (cur->first_char != 0 && cur->first_char != -1)
+ {
+ int ilink= cur->char_tails->ithis;
+ hash_map[cur->ithis*4+2]= ilink%256;
+ hash_map[cur->ithis*4+3]= ilink/256;
+ set_links(cur->char_tails,cur->last_char-cur->first_char+1);
+ }
+ }
+}
+
+
+void print_hash_map(const char *name)
+{
+ char *cur;
+ int i;
+
+ printf("static uchar %s[%d]= {\n",name,size_hash_map);
+ for (i=0, cur= hash_map; i<size_hash_map; i++, cur++)
+ {
+ switch(i%4){
+ case 0: case 1:
+ if (!*cur)
+ printf("0, ");
+ else
+ printf("\'%c\', ",*cur);
+ break;
+ case 2: printf("%u, ",(uint)(uchar)*cur); break;
+ case 3: printf("%u,\n",(uint)(uchar)*cur); break;
+ }
+ }
+ printf("};\n");
+}
+
+
+void print_find_structs()
+{
+ add_structs_to_map(root_by_len,max_len);
+ set_links(root_by_len,max_len);
+ print_hash_map("sql_functions_map");
+
+ hash_map= 0;
+ size_hash_map= 0;
+
+ printf("\n");
+
+ add_structs_to_map(root_by_len2,max_len2);
+ set_links(root_by_len2,max_len2);
+ print_hash_map("symbols_map");
+}
+
+
+int check_dup_symbols(SYMBOL *s1, SYMBOL *s2)
+{
+ if (s1->length!=s2->length || strncmp(s1->name,s2->name,s1->length))
+ return 0;
+
+ const char *err_tmpl= "\ngen_lex_hash fatal error : \
+Unfortunately gen_lex_hash can not generate a hash,\n since \
+your lex.h has duplicate definition for a symbol \"%s\"\n\n";
+ printf (err_tmpl,s1->name);
+ fprintf (stderr,err_tmpl,s1->name);
+
+ return 1;
+}
+
+
+int check_duplicates()
+{
+ SYMBOL *cur1, *cur2, *s_end, *f_end;
+
+ s_end= symbols + array_elements(symbols);
+ f_end= sql_functions + array_elements(sql_functions);
+
+ for (cur1= symbols; cur1<s_end; cur1++)
+ {
+ for (cur2= cur1+1; cur2<s_end; cur2++)
+ {
+ if (check_dup_symbols(cur1,cur2))
+ return 1;
+ }
+ for (cur2= sql_functions; cur2<f_end; cur2++)
+ {
+ if (check_dup_symbols(cur1,cur2))
+ return 1;
+ }
+ }
+
+ for (cur1= sql_functions; cur1<f_end; cur1++)
+ {
+ for (cur2= cur1+1; cur2< f_end; cur2++)
+ {
+ if (check_dup_symbols(cur1,cur2))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int main(int argc,char **argv)
+{
+
+
+ /* Broken up to indicate that it's not advice to you, gentle reader. */
+ printf("/*\n\n Do " "not " "edit " "this " "file " "directly!\n\n*/\n");
+
+ puts("/*");
+ puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
+ puts("*/");
+
+ /* Broken up to indicate that it's not advice to you, gentle reader. */
+ printf("/* Do " "not " "edit " "this " "file! This is generated by "
+ "gen_lex_hash.cc\nthat seeks for a perfect hash function */\n\n");
+ printf("#include \"lex.h\"\n\n");
+
+ calc_length();
+
+ if (check_duplicates())
+ exit(1);
+
+ generate_find_structs();
+ print_find_structs();
+
+ printf("\nstatic unsigned int sql_functions_max_len=%d;\n", max_len);
+ printf("\nstatic unsigned int symbols_max_len=%d;\n\n", max_len2);
+
+ printf("\
+static SYMBOL *get_hash_symbol(const char *s,\n\
+ unsigned int len,bool function)\n\
+{\n\
+ register uchar *hash_map;\n\
+ register const char *cur_str= s;\n\
+\n\
+ if (len == 0) {\n\
+ DBUG_PRINT(\"warning\", (\"get_hash_symbol() received a request for a zero-length symbol, which is probably a mistake.\"));\
+ return(NULL);\n\
+ }\n"
+);
+
+ printf("\
+ if (function){\n\
+ if (len>sql_functions_max_len) return 0;\n\
+ hash_map= sql_functions_map;\n\
+ register uint32 cur_struct= uint4korr(hash_map+((len-1)*4));\n\
+\n\
+ for (;;){\n\
+ register uchar first_char= (uchar)cur_struct;\n\
+\n\
+ if (first_char == 0)\n\
+ {\n\
+ register int16 ires= (int16)(cur_struct>>16);\n\
+ if (ires==array_elements(symbols)) return 0;\n\
+ register SYMBOL *res;\n\
+ if (ires>=0) \n\
+ res= symbols+ires;\n\
+ else\n\
+ res= sql_functions-ires-1;\n\
+ register uint count= (uint) (cur_str - s);\n\
+ return lex_casecmp(cur_str,res->name+count,len-count) ? 0 : res;\n\
+ }\n\
+\n\
+ register uchar cur_char= (uchar)to_upper_lex[(uchar)*cur_str];\n\
+ if (cur_char<first_char) return 0;\n\
+ cur_struct>>=8;\n\
+ if (cur_char>(uchar)cur_struct) return 0;\n\
+\n\
+ cur_struct>>=8;\n\
+ cur_struct= uint4korr(hash_map+\n\
+ (((uint16)cur_struct + cur_char - first_char)*4));\n\
+ cur_str++;\n\
+ }\n"
+);
+
+ printf("\
+ }else{\n\
+ if (len>symbols_max_len) return 0;\n\
+ hash_map= symbols_map;\n\
+ register uint32 cur_struct= uint4korr(hash_map+((len-1)*4));\n\
+\n\
+ for (;;){\n\
+ register uchar first_char= (uchar)cur_struct;\n\
+\n\
+ if (first_char==0){\n\
+ register int16 ires= (int16)(cur_struct>>16);\n\
+ if (ires==array_elements(symbols)) return 0;\n\
+ register SYMBOL *res= symbols+ires;\n\
+ register uint count= (uint) (cur_str - s);\n\
+ return lex_casecmp(cur_str,res->name+count,len-count)!=0 ? 0 : res;\n\
+ }\n\
+\n\
+ register uchar cur_char= (uchar)to_upper_lex[(uchar)*cur_str];\n\
+ if (cur_char<first_char) return 0;\n\
+ cur_struct>>=8;\n\
+ if (cur_char>(uchar)cur_struct) return 0;\n\
+\n\
+ cur_struct>>=8;\n\
+ cur_struct= uint4korr(hash_map+\n\
+ (((uint16)cur_struct + cur_char - first_char)*4));\n\
+ cur_str++;\n\
+ }\n\
+ }\n\
+}\n"
+);
+ exit(0);
+}
+
diff --git a/sql/gen_lex_token.cc b/sql/gen_lex_token.cc
new file mode 100644
index 00000000000..3584dd60c62
--- /dev/null
+++ b/sql/gen_lex_token.cc
@@ -0,0 +1,353 @@
+/*
+ Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#include <my_global.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+/* We only need the tokens here */
+#define YYSTYPE_IS_DECLARED
+#include <sql_yacc.h>
+#include <lex.h>
+
+#include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */
+
+/*
+ This is a tool used during build only,
+ so MY_MAX_TOKEN does not need to be exact,
+ only big enough to hold:
+ - 256 character terminal tokens
+ - YYNTOKENS named terminal tokens
+ from bison.
+ See also YYMAXUTOK.
+*/
+#define MY_MAX_TOKEN 1000
+/** Generated token. */
+struct gen_lex_token_string
+{
+ const char *m_token_string;
+ int m_token_length;
+ bool m_append_space;
+ bool m_start_expr;
+};
+
+gen_lex_token_string compiled_token_array[MY_MAX_TOKEN];
+int max_token_seen= 0;
+
+char char_tokens[256];
+
+int tok_generic_value= 0;
+int tok_generic_value_list= 0;
+int tok_row_single_value= 0;
+int tok_row_single_value_list= 0;
+int tok_row_multiple_value= 0;
+int tok_row_multiple_value_list= 0;
+int tok_unused= 0;
+
+void set_token(int tok, const char *str)
+{
+ if (tok <= 0)
+ {
+ fprintf(stderr, "Bad token found\n");
+ exit(1);
+ }
+
+ if (tok > max_token_seen)
+ {
+ max_token_seen= tok;
+ }
+
+ if (max_token_seen >= MY_MAX_TOKEN)
+ {
+ fprintf(stderr, "Added that many new keywords ? Increase MY_MAX_TOKEN\n");
+ exit(1);
+ }
+
+ compiled_token_array[tok].m_token_string= str;
+ compiled_token_array[tok].m_token_length= strlen(str);
+ compiled_token_array[tok].m_append_space= true;
+ compiled_token_array[tok].m_start_expr= false;
+}
+
+void set_start_expr_token(int tok)
+{
+ compiled_token_array[tok].m_start_expr= true;
+}
+
+void compute_tokens()
+{
+ int tok;
+ unsigned int i;
+ char *str;
+
+ /*
+ Default value.
+ */
+ for (tok= 0; tok < MY_MAX_TOKEN; tok++)
+ {
+ compiled_token_array[tok].m_token_string= "(unknown)";
+ compiled_token_array[tok].m_token_length= 9;
+ compiled_token_array[tok].m_append_space= true;
+ compiled_token_array[tok].m_start_expr= false;
+ }
+
+ /*
+ Tokens made of just one terminal character
+ */
+ for (tok=0; tok < 256; tok++)
+ {
+ str= & char_tokens[tok];
+ str[0]= (char) tok;
+ compiled_token_array[tok].m_token_string= str;
+ compiled_token_array[tok].m_token_length= 1;
+ compiled_token_array[tok].m_append_space= true;
+ }
+
+ max_token_seen= 255;
+
+ /*
+ String terminal tokens, used in sql_yacc.yy
+ */
+ set_token(NEG, "~");
+ set_token(TABLE_REF_PRIORITY, "TABLE_REF_PRIORITY");
+
+ /*
+ Tokens hard coded in sql_lex.cc
+ */
+
+ set_token(WITH_CUBE_SYM, "WITH CUBE");
+ set_token(WITH_ROLLUP_SYM, "WITH ROLLUP");
+ set_token(NOT2_SYM, "!");
+ set_token(OR2_SYM, "|");
+ set_token(PARAM_MARKER, "?");
+ set_token(SET_VAR, ":=");
+ set_token(UNDERSCORE_CHARSET, "(_charset)");
+ set_token(END_OF_INPUT, "");
+
+ /*
+ Values.
+ These tokens are all normalized later,
+ so this strings will never be displayed.
+ */
+ set_token(BIN_NUM, "(bin)");
+ set_token(DECIMAL_NUM, "(decimal)");
+ set_token(FLOAT_NUM, "(float)");
+ set_token(HEX_NUM, "(hex)");
+ set_token(LEX_HOSTNAME, "(hostname)");
+ set_token(LONG_NUM, "(long)");
+ set_token(NUM, "(num)");
+ set_token(TEXT_STRING, "(text)");
+ set_token(NCHAR_STRING, "(nchar)");
+ set_token(ULONGLONG_NUM, "(ulonglong)");
+
+ /*
+ Identifiers.
+ */
+ set_token(IDENT, "(id)");
+ set_token(IDENT_QUOTED, "(id_quoted)");
+
+ /*
+ Unused tokens
+ */
+ set_token(LOCATOR_SYM, "LOCATOR");
+ set_token(SERVER_OPTIONS, "SERVER_OPTIONS");
+ set_token(UDF_RETURNS_SYM, "UDF_RETURNS");
+
+ /*
+ See symbols[] in sql/lex.h
+ */
+ for (i= 0; i< sizeof(symbols)/sizeof(symbols[0]); i++)
+ {
+ set_token(symbols[i].tok, symbols[i].name);
+ }
+
+ /*
+ See sql_functions[] in sql/lex.h
+ */
+ for (i= 0; i< sizeof(sql_functions)/sizeof(sql_functions[0]); i++)
+ {
+ set_token(sql_functions[i].tok, sql_functions[i].name);
+ }
+
+ /*
+ Additional FAKE tokens,
+ used internally to normalize a digest text.
+ */
+
+ max_token_seen++;
+ tok_generic_value= max_token_seen;
+ set_token(tok_generic_value, "?");
+
+ max_token_seen++;
+ tok_generic_value_list= max_token_seen;
+ set_token(tok_generic_value_list, "?, ...");
+
+ max_token_seen++;
+ tok_row_single_value= max_token_seen;
+ set_token(tok_row_single_value, "(?)");
+
+ max_token_seen++;
+ tok_row_single_value_list= max_token_seen;
+ set_token(tok_row_single_value_list, "(?) /* , ... */");
+
+ max_token_seen++;
+ tok_row_multiple_value= max_token_seen;
+ set_token(tok_row_multiple_value, "(...)");
+
+ max_token_seen++;
+ tok_row_multiple_value_list= max_token_seen;
+ set_token(tok_row_multiple_value_list, "(...) /* , ... */");
+
+ max_token_seen++;
+ tok_unused= max_token_seen;
+ set_token(tok_unused, "UNUSED");
+
+ /*
+ Fix whitespace for some special tokens.
+ */
+
+ /*
+ The lexer parses "@@variable" as '@', '@', 'variable',
+ returning a token for '@' alone.
+
+ This is incorrect, '@' is not really a token,
+ because the syntax "@ @ variable" (with spaces) is not accepted:
+ The lexer keeps some internal state after the '@' fake token.
+
+ To work around this, digest text are printed as "@@variable".
+ */
+ compiled_token_array[(int) '@'].m_append_space= false;
+
+ /*
+ Define additional properties for tokens.
+
+ List all the token that are followed by an expression.
+ This is needed to differentiate unary from binary
+ '+' and '-' operators, because we want to:
+ - reduce <unary +> <NUM> to <?>,
+ - preserve <...> <binary +> <NUM> as is.
+ */
+ set_start_expr_token('(');
+ set_start_expr_token(',');
+ set_start_expr_token(EVERY_SYM);
+ set_start_expr_token(AT_SYM);
+ set_start_expr_token(STARTS_SYM);
+ set_start_expr_token(ENDS_SYM);
+ set_start_expr_token(DEFAULT);
+ set_start_expr_token(RETURN_SYM);
+ set_start_expr_token(IF);
+ set_start_expr_token(ELSEIF_SYM);
+ set_start_expr_token(CASE_SYM);
+ set_start_expr_token(WHEN_SYM);
+ set_start_expr_token(WHILE_SYM);
+ set_start_expr_token(UNTIL_SYM);
+ set_start_expr_token(SELECT_SYM);
+
+ set_start_expr_token(OR_SYM);
+ set_start_expr_token(OR2_SYM);
+ set_start_expr_token(XOR);
+ set_start_expr_token(AND_SYM);
+ set_start_expr_token(AND_AND_SYM);
+ set_start_expr_token(NOT_SYM);
+ set_start_expr_token(BETWEEN_SYM);
+ set_start_expr_token(LIKE);
+ set_start_expr_token(REGEXP);
+
+ set_start_expr_token('|');
+ set_start_expr_token('&');
+ set_start_expr_token(SHIFT_LEFT);
+ set_start_expr_token(SHIFT_RIGHT);
+ set_start_expr_token('+');
+ set_start_expr_token('-');
+ set_start_expr_token(INTERVAL_SYM);
+ set_start_expr_token('*');
+ set_start_expr_token('/');
+ set_start_expr_token('%');
+ set_start_expr_token(DIV_SYM);
+ set_start_expr_token(MOD_SYM);
+ set_start_expr_token('^');
+}
+
+void print_tokens()
+{
+ int tok;
+
+ printf("#ifdef LEX_TOKEN_WITH_DEFINITION\n");
+ printf("lex_token_string lex_token_array[]=\n");
+ printf("{\n");
+ printf("/* PART 1: character tokens. */\n");
+
+ for (tok= 0; tok<256; tok++)
+ {
+ printf("/* %03d */ { \"\\x%02x\", 1, %s, %s},\n",
+ tok,
+ tok,
+ compiled_token_array[tok].m_append_space ? "true" : "false",
+ compiled_token_array[tok].m_start_expr ? "true" : "false");
+ }
+
+ printf("/* PART 2: named tokens. */\n");
+
+ for (tok= 256; tok<= max_token_seen; tok++)
+ {
+ printf("/* %03d */ { \"%s\", %d, %s, %s},\n",
+ tok,
+ compiled_token_array[tok].m_token_string,
+ compiled_token_array[tok].m_token_length,
+ compiled_token_array[tok].m_append_space ? "true" : "false",
+ compiled_token_array[tok].m_start_expr ? "true" : "false");
+ }
+
+ printf("/* DUMMY */ { \"\", 0, false, false}\n");
+ printf("};\n");
+ printf("#endif /* LEX_TOKEN_WITH_DEFINITION */\n");
+
+ printf("/* DIGEST specific tokens. */\n");
+ printf("#define TOK_GENERIC_VALUE %d\n", tok_generic_value);
+ printf("#define TOK_GENERIC_VALUE_LIST %d\n", tok_generic_value_list);
+ printf("#define TOK_ROW_SINGLE_VALUE %d\n", tok_row_single_value);
+ printf("#define TOK_ROW_SINGLE_VALUE_LIST %d\n", tok_row_single_value_list);
+ printf("#define TOK_ROW_MULTIPLE_VALUE %d\n", tok_row_multiple_value);
+ printf("#define TOK_ROW_MULTIPLE_VALUE_LIST %d\n", tok_row_multiple_value_list);
+ printf("#define TOK_UNUSED %d\n", tok_unused);
+}
+
+int main(int argc,char **argv)
+{
+ puts("/*");
+ puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2011"));
+ puts("*/");
+
+ printf("/*\n");
+ printf(" This file is generated, do not edit.\n");
+ printf(" See file sql/gen_lex_token.cc.\n");
+ printf("*/\n");
+ printf("struct lex_token_string\n");
+ printf("{\n");
+ printf(" const char *m_token_string;\n");
+ printf(" int m_token_length;\n");
+ printf(" bool m_append_space;\n");
+ printf(" bool m_start_expr;\n");
+ printf("};\n");
+ printf("typedef struct lex_token_string lex_token_string;\n");
+
+ compute_tokens();
+ print_tokens();
+
+ return 0;
+}
+
diff --git a/sql/gstream.cc b/sql/gstream.cc
new file mode 100644
index 00000000000..adb46083621
--- /dev/null
+++ b/sql/gstream.cc
@@ -0,0 +1,145 @@
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/*
+ Functions to read and parse geometrical data.
+ NOTE: These functions assumes that the string is end \0 terminated!
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "gstream.h"
+#include "m_string.h" // LEX_STRING
+
+enum Gis_read_stream::enum_tok_types Gis_read_stream::get_next_toc_type()
+{
+ skip_space();
+ if (m_cur >= m_limit)
+ return eostream;
+ if (my_isvar_start(&my_charset_bin, *m_cur))
+ return word;
+ if ((*m_cur >= '0' && *m_cur <= '9') || *m_cur == '-' || *m_cur == '+')
+ return numeric;
+ if (*m_cur == '(')
+ return l_bra;
+ if (*m_cur == ')')
+ return r_bra;
+ if (*m_cur == ',')
+ return comma;
+ return unknown;
+}
+
+
+bool Gis_read_stream::lookup_next_word(LEX_STRING *res)
+{
+ const char *cur= m_cur;
+
+ skip_space();
+ res->str= (char*) cur;
+ /* The following will also test for \0 */
+ if ((cur >= m_limit) || !my_isvar_start(&my_charset_bin, *cur))
+ return 1;
+
+ /*
+ We can't combine the following increment with my_isvar() because
+ my_isvar() is a macro that would cause side effects
+ */
+ cur++;
+ while ((cur < m_limit) && my_isvar(&my_charset_bin, *cur))
+ cur++;
+
+ res->length= (uint32) (cur - res->str);
+ return 0;
+}
+
+
+bool Gis_read_stream::get_next_word(LEX_STRING *res)
+{
+ skip_space();
+ res->str= (char*) m_cur;
+ /* The following will also test for \0 */
+ if ((m_cur >= m_limit) || !my_isvar_start(&my_charset_bin, *m_cur))
+ return 1;
+
+ /*
+ We can't combine the following increment with my_isvar() because
+ my_isvar() is a macro that would cause side effects
+ */
+ m_cur++;
+ while ((m_cur < m_limit) && my_isvar(&my_charset_bin, *m_cur))
+ m_cur++;
+
+ res->length= (uint32) (m_cur - res->str);
+ return 0;
+}
+
+
+/*
+ Read a floating point number
+
+ NOTE: Number must start with a digit or sign. It can't start with a decimal
+ point
+*/
+
+bool Gis_read_stream::get_next_number(double *d)
+{
+ char *endptr;
+ int err;
+
+ skip_space();
+
+ if ((m_cur >= m_limit) ||
+ ((*m_cur < '0' || *m_cur > '9') && *m_cur != '-' && *m_cur != '+'))
+ {
+ set_error_msg("Numeric constant expected");
+ return 1;
+ }
+
+ *d = my_strntod(m_charset, (char *)m_cur,
+ (uint) (m_limit-m_cur), &endptr, &err);
+ if (err)
+ return 1;
+ if (endptr)
+ m_cur = endptr;
+ return 0;
+}
+
+
+bool Gis_read_stream::check_next_symbol(char symbol)
+{
+ skip_space();
+ if ((m_cur >= m_limit) || (*m_cur != symbol))
+ {
+ char buff[32];
+ strmov(buff, "'?' expected");
+ buff[2]= symbol;
+ set_error_msg(buff);
+ return 1;
+ }
+ m_cur++;
+ return 0;
+}
+
+
+/*
+ Remember error message.
+*/
+
+void Gis_read_stream::set_error_msg(const char *msg)
+{
+ size_t len= strlen(msg); // ok in this context
+ m_err_msg= (char *) my_realloc(m_err_msg, (uint) len + 1, MYF(MY_ALLOW_ZERO_PTR));
+ memcpy(m_err_msg, msg, len + 1);
+}
diff --git a/sql/gstream.h b/sql/gstream.h
new file mode 100644
index 00000000000..f10b7e9b830
--- /dev/null
+++ b/sql/gstream.h
@@ -0,0 +1,93 @@
+#ifndef GSTREAM_INCLUDED
+#define GSTREAM_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "my_global.h" /* NULL, NullS */
+#include "my_sys.h" /* MY_ALLOW_ZERO_PTR */
+#include "m_ctype.h" /* my_charset_latin1, my_charset_bin */
+
+class Gis_read_stream
+{
+public:
+ enum enum_tok_types
+ {
+ unknown,
+ eostream,
+ word,
+ numeric,
+ l_bra,
+ r_bra,
+ comma
+ };
+
+ Gis_read_stream(CHARSET_INFO *charset, const char *buffer, int size)
+ :m_cur(buffer), m_limit(buffer + size), m_err_msg(NULL), m_charset(charset)
+ {}
+ Gis_read_stream(): m_cur(NullS), m_limit(NullS), m_err_msg(NullS)
+ {}
+ ~Gis_read_stream()
+ {
+ my_free(m_err_msg);
+ }
+
+ enum enum_tok_types get_next_toc_type();
+ bool lookup_next_word(LEX_STRING *res);
+ bool get_next_word(LEX_STRING *);
+ bool get_next_number(double *);
+ bool check_next_symbol(char);
+
+ inline void skip_space()
+ {
+ while ((m_cur < m_limit) && my_isspace(&my_charset_latin1, *m_cur))
+ m_cur++;
+ }
+ /* Skip next character, if match. Return 1 if no match */
+ inline bool skip_char(char skip)
+ {
+ skip_space();
+ if ((m_cur >= m_limit) || *m_cur != skip)
+ return 1; /* Didn't find char */
+ m_cur++;
+ return 0;
+ }
+ /* Returns the next notempty character. */
+ char next_symbol()
+ {
+ skip_space();
+ if (m_cur >= m_limit)
+ return 0; /* EOL meet. */
+ return *m_cur;
+ }
+ void set_error_msg(const char *msg);
+
+ // caller should free this pointer
+ char *get_error_msg()
+ {
+ char *err_msg = m_err_msg;
+ m_err_msg= NullS;
+ return err_msg;
+ }
+
+protected:
+ const char *m_cur;
+ const char *m_limit;
+ char *m_err_msg;
+ CHARSET_INFO *m_charset;
+};
+
+#endif /* GSTREAM_INCLUDED */
diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc
new file mode 100644
index 00000000000..ed05521a473
--- /dev/null
+++ b/sql/ha_ndbcluster.cc
@@ -0,0 +1,11061 @@
+/* Copyright (c) 2004, 2011, Oracle and/or its affiliates.
+
+ 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 */
+
+/**
+ @file
+
+ @brief
+ This file defines the NDB Cluster handler: the interface between
+ MySQL and NDB Cluster
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_table.h" // build_table_filename,
+ // tablename_to_filename,
+ // filename_to_tablename
+#include "sql_partition.h" // HA_CAN_*, partition_info, part_id_range
+#include "sql_base.h" // close_cached_tables
+#include "discover.h" // readfrm
+#include "sql_acl.h" // wild_case_compare
+#include "rpl_mi.h"
+#include "transaction.h"
+
+/*
+ There is an incompatibility between GNU ar and the Solaris linker
+ which makes the Solaris linker return an elf error when compiling
+ without NDB support (which makes libndb.a an empty library).
+ To avoid this we add a dummy declaration of a static variable
+ which makes us avoid this bug.
+*/
+int ha_ndb_dummy;
+#include <my_dir.h>
+#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE
+#include "ha_ndbcluster.h"
+#include <ndbapi/NdbApi.hpp>
+#include "ha_ndbcluster_cond.h"
+#include <../util/Bitmask.hpp>
+#include <ndbapi/NdbIndexStat.hpp>
+
+#include "ha_ndbcluster_binlog.h"
+#include "ha_ndbcluster_tables.h"
+
+#include "sql_plugin.h"
+#include "probes_mysql.h"
+#include "sql_show.h" // init_fill_schema_files_row,
+ // schema_table_store_record
+#include "sql_test.h" // print_where
+
+#ifdef ndb_dynamite
+#undef assert
+#define assert(x) do { if(x) break; ::printf("%s %d: assert failed: %s\n", __FILE__, __LINE__, #x); ::fflush(stdout); ::signal(SIGABRT,SIG_DFL); ::abort(); ::kill(::getpid(),6); ::kill(::getpid(),9); } while (0)
+#endif
+
+// ndb interface initialization/cleanup functions
+extern "C" void ndb_init_internal();
+extern "C" void ndb_end_internal();
+
+static const int DEFAULT_PARALLELISM= 0;
+static const ha_rows DEFAULT_AUTO_PREFETCH= 32;
+static const ulong ONE_YEAR_IN_SECONDS= (ulong) 3600L*24L*365L;
+
+ulong opt_ndb_extra_logging;
+static ulong opt_ndb_cache_check_time;
+static char* opt_ndb_connectstring;
+static char* opt_ndb_mgmd_host;
+static uint opt_ndb_nodeid;
+
+
+static MYSQL_THDVAR_UINT(
+ autoincrement_prefetch_sz, /* name */
+ PLUGIN_VAR_RQCMDARG,
+ "Specify number of autoincrement values that are prefetched.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 1, /* default */
+ 1, /* min */
+ 256, /* max */
+ 0 /* block */
+);
+
+
+static MYSQL_THDVAR_BOOL(
+ force_send, /* name */
+ PLUGIN_VAR_OPCMDARG,
+ "Force send of buffers to ndb immediately without waiting for "
+ "other threads.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 1 /* default */
+);
+
+
+static MYSQL_THDVAR_BOOL(
+ use_exact_count, /* name */
+ PLUGIN_VAR_OPCMDARG,
+ "Use exact records count during query planning and for fast "
+ "select count(*), disable for faster queries.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 1 /* default */
+);
+
+
+static MYSQL_THDVAR_BOOL(
+ use_transactions, /* name */
+ PLUGIN_VAR_OPCMDARG,
+ "Use transactions for large inserts, if enabled then large "
+ "inserts will be split into several smaller transactions",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 1 /* default */
+);
+
+
+static MYSQL_THDVAR_BOOL(
+ use_copying_alter_table, /* name */
+ PLUGIN_VAR_OPCMDARG,
+ "Force ndbcluster to always copy tables at alter table (should "
+ "only be used if on-line alter table fails).",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 0 /* default */
+);
+
+
+static MYSQL_THDVAR_UINT(
+ optimized_node_selection, /* name */
+ PLUGIN_VAR_OPCMDARG,
+ "Select nodes for transactions in a more optimal way.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 3, /* default */
+ 0, /* min */
+ 3, /* max */
+ 0 /* block */
+);
+
+
+static MYSQL_THDVAR_BOOL(
+ index_stat_enable, /* name */
+ PLUGIN_VAR_OPCMDARG,
+ "Use ndb index statistics in query optimization.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ FALSE /* default */
+);
+
+
+static MYSQL_THDVAR_ULONG(
+ index_stat_cache_entries, /* name */
+ PLUGIN_VAR_NOCMDARG,
+ "",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 32, /* default */
+ 0, /* min */
+ ULONG_MAX, /* max */
+ 0 /* block */
+);
+
+
+static MYSQL_THDVAR_ULONG(
+ index_stat_update_freq, /* name */
+ PLUGIN_VAR_NOCMDARG,
+ "",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 20, /* default */
+ 0, /* min */
+ ULONG_MAX, /* max */
+ 0 /* block */
+);
+
+// Default value for parallelism
+static const int parallelism= 0;
+
+// Default value for max number of transactions
+// createable against NDB from this handler
+static const int max_transactions= 3; // should really be 2 but there is a transaction to much allocated when loch table is used
+
+static uint ndbcluster_partition_flags();
+static uint ndbcluster_alter_table_flags(uint flags);
+static int ndbcluster_init(void *);
+static int ndbcluster_end(handlerton *hton, ha_panic_function flag);
+static bool ndbcluster_show_status(handlerton *hton, THD*,
+ stat_print_fn *,
+ enum ha_stat_type);
+static int ndbcluster_alter_tablespace(handlerton *hton,
+ THD* thd,
+ st_alter_tablespace *info);
+static int ndbcluster_fill_is_table(handlerton *hton,
+ THD *thd,
+ TABLE_LIST *tables,
+ COND *cond,
+ enum enum_schema_tables);
+static int ndbcluster_fill_files_table(handlerton *hton,
+ THD *thd,
+ TABLE_LIST *tables,
+ COND *cond);
+
+handlerton *ndbcluster_hton;
+
+static handler *ndbcluster_create_handler(handlerton *hton,
+ TABLE_SHARE *table,
+ MEM_ROOT *mem_root)
+{
+ return new (mem_root) ha_ndbcluster(hton, table);
+}
+
+static uint ndbcluster_partition_flags()
+{
+ return (HA_CAN_PARTITION | HA_CAN_UPDATE_PARTITION_KEY |
+ HA_CAN_PARTITION_UNIQUE | HA_USE_AUTO_PARTITION);
+}
+
+static uint ndbcluster_alter_table_flags(uint flags)
+{
+ if (flags & ALTER_DROP_PARTITION)
+ return 0;
+ else
+ return (HA_ONLINE_ADD_INDEX | HA_ONLINE_DROP_INDEX |
+ HA_ONLINE_ADD_UNIQUE_INDEX | HA_ONLINE_DROP_UNIQUE_INDEX |
+ HA_PARTITION_FUNCTION_SUPPORTED);
+
+}
+
+#define NDB_AUTO_INCREMENT_RETRIES 10
+
+#define ERR_PRINT(err) \
+ DBUG_PRINT("error", ("%d message: %s", err.code, err.message))
+
+#define ERR_RETURN(err) \
+{ \
+ const NdbError& tmp= err; \
+ set_ndb_err(current_thd, tmp); \
+ DBUG_RETURN(ndb_to_mysql_error(&tmp)); \
+}
+
+#define ERR_RETURN_PREPARE(rc, err) \
+{ \
+ const NdbError& tmp= err; \
+ set_ndb_err(current_thd, tmp); \
+ rc= ndb_to_mysql_error(&tmp); \
+}
+
+#define ERR_BREAK(err, code) \
+{ \
+ const NdbError& tmp= err; \
+ set_ndb_err(current_thd, tmp); \
+ code= ndb_to_mysql_error(&tmp); \
+ break; \
+}
+
+static int ndbcluster_inited= 0;
+int ndbcluster_terminating= 0;
+
+static Ndb* g_ndb= NULL;
+Ndb_cluster_connection* g_ndb_cluster_connection= NULL;
+uchar g_node_id_map[max_ndb_nodes];
+
+/// Handler synchronization
+mysql_mutex_t ndbcluster_mutex;
+
+/// Table lock handling
+HASH ndbcluster_open_tables;
+
+static uchar *ndbcluster_get_key(NDB_SHARE *share, size_t *length,
+ my_bool not_used __attribute__((unused)));
+#ifdef HAVE_NDB_BINLOG
+static int rename_share(NDB_SHARE *share, const char *new_key);
+#endif
+static int ndb_get_table_statistics(ha_ndbcluster*, bool, Ndb*, const NDBTAB *,
+ struct Ndb_statistics *);
+
+
+// Util thread variables
+pthread_t ndb_util_thread;
+int ndb_util_thread_running= 0;
+mysql_mutex_t LOCK_ndb_util_thread;
+mysql_cond_t COND_ndb_util_thread;
+mysql_cond_t COND_ndb_util_ready;
+pthread_handler_t ndb_util_thread_func(void *arg);
+
+/**
+ Dummy buffer to read zero pack_length fields
+ which are mapped to 1 char.
+*/
+static uint32 dummy_buf;
+
+/**
+ Stats that can be retrieved from ndb.
+*/
+
+struct Ndb_statistics {
+ Uint64 row_count;
+ Uint64 commit_count;
+ Uint64 row_size;
+ Uint64 fragment_memory;
+};
+
+/* Status variables shown with 'show status like 'Ndb%' */
+
+static long ndb_cluster_node_id= 0;
+static const char * ndb_connected_host= 0;
+static long ndb_connected_port= 0;
+static long ndb_number_of_replicas= 0;
+long ndb_number_of_data_nodes= 0;
+long ndb_number_of_ready_data_nodes= 0;
+long ndb_connect_count= 0;
+
+static int update_status_variables(Ndb_cluster_connection *c)
+{
+ ndb_cluster_node_id= c->node_id();
+ ndb_connected_port= c->get_connected_port();
+ ndb_connected_host= c->get_connected_host();
+ ndb_number_of_replicas= 0;
+ ndb_number_of_ready_data_nodes= c->get_no_ready();
+ ndb_number_of_data_nodes= c->no_db_nodes();
+ ndb_connect_count= c->get_connect_count();
+ return 0;
+}
+
+SHOW_VAR ndb_status_variables[]= {
+ {"cluster_node_id", (char*) &ndb_cluster_node_id, SHOW_LONG},
+ {"config_from_host", (char*) &ndb_connected_host, SHOW_CHAR_PTR},
+ {"config_from_port", (char*) &ndb_connected_port, SHOW_LONG},
+// {"number_of_replicas", (char*) &ndb_number_of_replicas, SHOW_LONG},
+ {"number_of_data_nodes",(char*) &ndb_number_of_data_nodes, SHOW_LONG},
+ {NullS, NullS, SHOW_LONG}
+};
+
+/*
+ Error handling functions
+*/
+
+/* Note for merge: old mapping table, moved to storage/ndb/ndberror.c */
+
+static int ndb_to_mysql_error(const NdbError *ndberr)
+{
+ /* read the mysql mapped error code */
+ int error= ndberr->mysql_code;
+
+ switch (error)
+ {
+ /* errors for which we do not add warnings, just return mapped error code
+ */
+ case HA_ERR_NO_SUCH_TABLE:
+ case HA_ERR_KEY_NOT_FOUND:
+ return error;
+
+ /* Mapping missing, go with the ndb error code*/
+ case -1:
+ error= ndberr->code;
+ break;
+ /* Mapping exists, go with the mapped code */
+ default:
+ break;
+ }
+
+ /*
+ Push the NDB error message as warning
+ - Used to be able to use SHOW WARNINGS toget more info on what the error is
+ - Used by replication to see if the error was temporary
+ */
+ if (ndberr->status == NdbError::TemporaryError)
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_TEMPORARY_ERRMSG, ER(ER_GET_TEMPORARY_ERRMSG),
+ ndberr->code, ndberr->message, "NDB");
+ else
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ ndberr->code, ndberr->message, "NDB");
+ return error;
+}
+
+int execute_no_commit_ignore_no_key(ha_ndbcluster *h, NdbTransaction *trans)
+{
+ if (trans->execute(NdbTransaction::NoCommit,
+ NdbOperation::AO_IgnoreError,
+ h->m_force_send) == -1)
+ return -1;
+
+ const NdbError &err= trans->getNdbError();
+ if (err.classification != NdbError::NoError &&
+ err.classification != NdbError::ConstraintViolation &&
+ err.classification != NdbError::NoDataFound)
+ return -1;
+
+ return 0;
+}
+
+inline
+int execute_no_commit(ha_ndbcluster *h, NdbTransaction *trans,
+ bool force_release)
+{
+ h->release_completed_operations(trans, force_release);
+ return h->m_ignore_no_key ?
+ execute_no_commit_ignore_no_key(h,trans) :
+ trans->execute(NdbTransaction::NoCommit,
+ NdbOperation::AbortOnError,
+ h->m_force_send);
+}
+
+inline
+int execute_commit(ha_ndbcluster *h, NdbTransaction *trans)
+{
+ return trans->execute(NdbTransaction::Commit,
+ NdbOperation::AbortOnError,
+ h->m_force_send);
+}
+
+inline
+int execute_commit(THD *thd, NdbTransaction *trans)
+{
+ return trans->execute(NdbTransaction::Commit,
+ NdbOperation::AbortOnError,
+ THDVAR(thd, force_send));
+}
+
+inline
+int execute_no_commit_ie(ha_ndbcluster *h, NdbTransaction *trans,
+ bool force_release)
+{
+ h->release_completed_operations(trans, force_release);
+ return trans->execute(NdbTransaction::NoCommit,
+ NdbOperation::AO_IgnoreError,
+ h->m_force_send);
+}
+
+/*
+ Place holder for ha_ndbcluster thread specific data
+*/
+typedef struct st_thd_ndb_share {
+ const void *key;
+ struct Ndb_local_table_statistics stat;
+} THD_NDB_SHARE;
+static
+uchar *thd_ndb_share_get_key(THD_NDB_SHARE *thd_ndb_share, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= sizeof(thd_ndb_share->key);
+ return (uchar*) &thd_ndb_share->key;
+}
+
+Thd_ndb::Thd_ndb()
+{
+ ndb= new Ndb(g_ndb_cluster_connection, "");
+ lock_count= 0;
+ start_stmt_count= 0;
+ count= 0;
+ trans= NULL;
+ m_error= FALSE;
+ m_error_code= 0;
+ query_state&= NDB_QUERY_NORMAL;
+ options= 0;
+ (void) my_hash_init(&open_tables, &my_charset_bin, 5, 0, 0,
+ (my_hash_get_key)thd_ndb_share_get_key, 0, 0);
+}
+
+Thd_ndb::~Thd_ndb()
+{
+ if (ndb)
+ {
+#ifndef DBUG_OFF
+ Ndb::Free_list_usage tmp;
+ tmp.m_name= 0;
+ while (ndb->get_free_list_usage(&tmp))
+ {
+ uint leaked= (uint) tmp.m_created - tmp.m_free;
+ if (leaked)
+ fprintf(stderr, "NDB: Found %u %s%s that %s not been released\n",
+ leaked, tmp.m_name,
+ (leaked == 1)?"":"'s",
+ (leaked == 1)?"has":"have");
+ }
+#endif
+ delete ndb;
+ ndb= NULL;
+ }
+ changed_tables.empty();
+ my_hash_free(&open_tables);
+}
+
+void
+Thd_ndb::init_open_tables()
+{
+ count= 0;
+ m_error= FALSE;
+ m_error_code= 0;
+ my_hash_reset(&open_tables);
+}
+
+inline
+Ndb *ha_ndbcluster::get_ndb()
+{
+ return get_thd_ndb(current_thd)->ndb;
+}
+
+/*
+ * manage uncommitted insert/deletes during transactio to get records correct
+ */
+
+void ha_ndbcluster::set_rec_per_key()
+{
+ DBUG_ENTER("ha_ndbcluster::get_status_const");
+ for (uint i=0 ; i < table_share->keys ; i++)
+ {
+ table->key_info[i].rec_per_key[table->key_info[i].key_parts-1]= 1;
+ }
+ DBUG_VOID_RETURN;
+}
+
+ha_rows ha_ndbcluster::records()
+{
+ ha_rows retval;
+ DBUG_ENTER("ha_ndbcluster::records");
+ struct Ndb_local_table_statistics *local_info= m_table_info;
+ DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d",
+ ((const NDBTAB *)m_table)->getTableId(),
+ local_info->no_uncommitted_rows_count));
+
+ Ndb *ndb= get_ndb();
+ ndb->setDatabaseName(m_dbname);
+ struct Ndb_statistics stat;
+ if (ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat) == 0)
+ {
+ retval= stat.row_count;
+ }
+ else
+ {
+ DBUG_RETURN(HA_POS_ERROR);
+ }
+
+ THD *thd= current_thd;
+ if (get_thd_ndb(thd)->m_error)
+ local_info->no_uncommitted_rows_count= 0;
+
+ DBUG_RETURN(retval + local_info->no_uncommitted_rows_count);
+}
+
+int ha_ndbcluster::records_update()
+{
+ if (m_ha_not_exact_count)
+ return 0;
+ DBUG_ENTER("ha_ndbcluster::records_update");
+ int result= 0;
+
+ struct Ndb_local_table_statistics *local_info= m_table_info;
+ DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d",
+ ((const NDBTAB *)m_table)->getTableId(),
+ local_info->no_uncommitted_rows_count));
+ {
+ Ndb *ndb= get_ndb();
+ struct Ndb_statistics stat;
+ if (ndb->setDatabaseName(m_dbname))
+ {
+ return my_errno= HA_ERR_OUT_OF_MEM;
+ }
+ result= ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat);
+ if (result == 0)
+ {
+ stats.mean_rec_length= stat.row_size;
+ stats.data_file_length= stat.fragment_memory;
+ local_info->records= stat.row_count;
+ }
+ }
+ {
+ THD *thd= current_thd;
+ if (get_thd_ndb(thd)->m_error)
+ local_info->no_uncommitted_rows_count= 0;
+ }
+ if (result == 0)
+ stats.records= local_info->records+ local_info->no_uncommitted_rows_count;
+ DBUG_RETURN(result);
+}
+
+void ha_ndbcluster::no_uncommitted_rows_execute_failure()
+{
+ if (m_ha_not_exact_count)
+ return;
+ DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_execute_failure");
+ get_thd_ndb(current_thd)->m_error= TRUE;
+ get_thd_ndb(current_thd)->m_error_code= 0;
+ DBUG_VOID_RETURN;
+}
+
+void ha_ndbcluster::no_uncommitted_rows_update(int c)
+{
+ if (m_ha_not_exact_count)
+ return;
+ DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_update");
+ struct Ndb_local_table_statistics *local_info= m_table_info;
+ local_info->no_uncommitted_rows_count+= c;
+ DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d",
+ ((const NDBTAB *)m_table)->getTableId(),
+ local_info->no_uncommitted_rows_count));
+ DBUG_VOID_RETURN;
+}
+
+void ha_ndbcluster::no_uncommitted_rows_reset(THD *thd)
+{
+ if (m_ha_not_exact_count)
+ return;
+ DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_reset");
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ thd_ndb->count++;
+ thd_ndb->m_error= FALSE;
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Sets the latest ndb error code on the thd_ndb object such that it
+ can be retrieved later to know which ndb error caused the handler
+ error.
+*/
+static void set_ndb_err(THD *thd, const NdbError &err)
+{
+ DBUG_ENTER("set_ndb_err");
+ ERR_PRINT(err);
+
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ if (thd_ndb == NULL)
+ DBUG_VOID_RETURN;
+#ifdef NOT_YET
+ /*
+ Check if error code is overwritten, in this case the original
+ failure cause will be lost. E.g. if 4350 error is given. So
+ push a warning so that it can be detected which is the root
+ error cause.
+ */
+ if (thd_ndb->m_query_id == thd->query_id &&
+ thd_ndb->m_error_code != 0 &&
+ thd_ndb->m_error_code != err.code)
+ {
+ char buf[FN_REFLEN];
+ ndb_error_string(thd_ndb->m_error_code, buf, sizeof(buf));
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ thd_ndb->m_error_code, buf, "NDB");
+ }
+#endif
+ thd_ndb->m_query_id= thd->query_id;
+ thd_ndb->m_error_code= err.code;
+ DBUG_VOID_RETURN;
+}
+
+int ha_ndbcluster::ndb_err(NdbTransaction *trans)
+{
+ THD *thd= current_thd;
+ int res;
+ NdbError err= trans->getNdbError();
+ DBUG_ENTER("ndb_err");
+
+ set_ndb_err(thd, err);
+
+ switch (err.classification) {
+ case NdbError::SchemaError:
+ {
+ // TODO perhaps we need to do more here, invalidate also in the cache
+ m_table->setStatusInvalid();
+ /* Close other open handlers not used by any thread */
+ TABLE_LIST table_list;
+ bzero((char*) &table_list,sizeof(table_list));
+ table_list.db= m_dbname;
+ table_list.alias= table_list.table_name= m_tabname;
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
+ break;
+ }
+ default:
+ break;
+ }
+ res= ndb_to_mysql_error(&err);
+ DBUG_PRINT("info", ("transformed ndbcluster error %d to mysql error %d",
+ err.code, res));
+ if (res == HA_ERR_FOUND_DUPP_KEY)
+ {
+ char *error_data= err.details;
+ uint dupkey= MAX_KEY;
+
+ for (uint i= 0; i < MAX_KEY; i++)
+ {
+ if (m_index[i].type == UNIQUE_INDEX ||
+ m_index[i].type == UNIQUE_ORDERED_INDEX)
+ {
+ const NDBINDEX *unique_index=
+ (const NDBINDEX *) m_index[i].unique_index;
+ if (unique_index &&
+ (char *) unique_index->getObjectId() == error_data)
+ {
+ dupkey= i;
+ break;
+ }
+ }
+ }
+ if (m_rows_to_insert == 1)
+ {
+ /*
+ We can only distinguish between primary and non-primary
+ violations here, so we need to return MAX_KEY for non-primary
+ to signal that key is unknown
+ */
+ m_dupkey= err.code == 630 ? table_share->primary_key : dupkey;
+ }
+ else
+ {
+ /* We are batching inserts, offending key is not available */
+ m_dupkey= (uint) -1;
+ }
+ }
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Override the default get_error_message in order to add the
+ error message of NDB .
+*/
+
+bool ha_ndbcluster::get_error_message(int error,
+ String *buf)
+{
+ DBUG_ENTER("ha_ndbcluster::get_error_message");
+ DBUG_PRINT("enter", ("error: %d", error));
+
+ Ndb *ndb= check_ndb_in_thd(current_thd);
+ if (!ndb)
+ DBUG_RETURN(FALSE);
+
+ const NdbError err= ndb->getNdbError(error);
+ bool temporary= err.status==NdbError::TemporaryError;
+ buf->set(err.message, strlen(err.message), &my_charset_bin);
+ DBUG_PRINT("exit", ("message: %s, temporary: %d", buf->ptr(), temporary));
+ DBUG_RETURN(temporary);
+}
+
+
+#ifndef DBUG_OFF
+/**
+ Check if type is supported by NDB.
+*/
+
+static bool ndb_supported_type(enum_field_types type)
+{
+ switch (type) {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_GEOMETRY:
+ return TRUE;
+ case MYSQL_TYPE_NULL:
+ break;
+ }
+ return FALSE;
+}
+#endif /* !DBUG_OFF */
+
+
+/**
+ Check if MySQL field type forces var part in ndb storage.
+*/
+static bool field_type_forces_var_part(enum_field_types type)
+{
+ switch (type) {
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ return TRUE;
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ return FALSE;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ Instruct NDB to set the value of the hidden primary key.
+*/
+
+bool ha_ndbcluster::set_hidden_key(NdbOperation *ndb_op,
+ uint fieldnr, const uchar *field_ptr)
+{
+ DBUG_ENTER("set_hidden_key");
+ DBUG_RETURN(ndb_op->equal(fieldnr, (char*)field_ptr) != 0);
+}
+
+
+/**
+ Instruct NDB to set the value of one primary key attribute.
+*/
+
+int ha_ndbcluster::set_ndb_key(NdbOperation *ndb_op, Field *field,
+ uint fieldnr, const uchar *field_ptr)
+{
+ uint32 pack_len= field->pack_length();
+ DBUG_ENTER("set_ndb_key");
+ DBUG_PRINT("enter", ("%d: %s, ndb_type: %u, len=%d",
+ fieldnr, field->field_name, field->type(),
+ pack_len));
+ DBUG_DUMP("key", field_ptr, pack_len);
+
+ DBUG_ASSERT(ndb_supported_type(field->type()));
+ DBUG_ASSERT(! (field->flags & BLOB_FLAG));
+ // Common implementation for most field types
+ DBUG_RETURN(ndb_op->equal(fieldnr, (char*) field_ptr, pack_len) != 0);
+}
+
+
+/**
+ Instruct NDB to set the value of one attribute.
+*/
+
+int ha_ndbcluster::set_ndb_value(NdbOperation *ndb_op, Field *field,
+ uint fieldnr, int row_offset,
+ bool *set_blob_value)
+{
+ const uchar* field_ptr= field->ptr + row_offset;
+ uint32 pack_len= field->pack_length();
+ DBUG_ENTER("set_ndb_value");
+ DBUG_PRINT("enter", ("%d: %s type: %u len=%d is_null=%s",
+ fieldnr, field->field_name, field->type(),
+ pack_len, field->is_null(row_offset) ? "Y" : "N"));
+ DBUG_DUMP("value", field_ptr, pack_len);
+
+ DBUG_ASSERT(ndb_supported_type(field->type()));
+ {
+ // ndb currently does not support size 0
+ uint32 empty_field;
+ if (pack_len == 0)
+ {
+ pack_len= sizeof(empty_field);
+ field_ptr= (uchar *)&empty_field;
+ if (field->is_null(row_offset))
+ empty_field= 0;
+ else
+ empty_field= 1;
+ }
+ if (! (field->flags & BLOB_FLAG))
+ {
+ if (field->type() != MYSQL_TYPE_BIT)
+ {
+ if (field->is_null(row_offset))
+ {
+ DBUG_PRINT("info", ("field is NULL"));
+ // Set value to NULL
+ DBUG_RETURN((ndb_op->setValue(fieldnr, (char*)NULL) != 0));
+ }
+ // Common implementation for most field types
+ DBUG_RETURN(ndb_op->setValue(fieldnr, (char*)field_ptr) != 0);
+ }
+ else // if (field->type() == MYSQL_TYPE_BIT)
+ {
+ longlong bits= field->val_int();
+
+ // Round up bit field length to nearest word boundry
+ pack_len= ((pack_len + 3) >> 2) << 2;
+ DBUG_ASSERT(pack_len <= 8);
+ if (field->is_null(row_offset))
+ // Set value to NULL
+ DBUG_RETURN((ndb_op->setValue(fieldnr, (char*)NULL) != 0));
+ DBUG_PRINT("info", ("bit field"));
+ DBUG_DUMP("value", (uchar*)&bits, pack_len);
+#ifdef WORDS_BIGENDIAN
+ /* store lsw first */
+ bits = ((bits >> 32) & 0x00000000FFFFFFFFLL)
+ | ((bits << 32) & 0xFFFFFFFF00000000LL);
+#endif
+ DBUG_RETURN(ndb_op->setValue(fieldnr, (char*)&bits) != 0);
+ }
+ }
+ // Blob type
+ NdbBlob *ndb_blob= ndb_op->getBlobHandle(fieldnr);
+ if (ndb_blob != NULL)
+ {
+ if (field->is_null(row_offset))
+ DBUG_RETURN(ndb_blob->setNull() != 0);
+
+ Field_blob *field_blob= (Field_blob*)field;
+
+ // Get length and pointer to data
+ uint32 blob_len= field_blob->get_length(field_ptr);
+ uchar* blob_ptr= NULL;
+ field_blob->get_ptr(&blob_ptr);
+
+ // Looks like NULL ptr signals length 0 blob
+ if (blob_ptr == NULL) {
+ DBUG_ASSERT(blob_len == 0);
+ blob_ptr= (uchar*)"";
+ }
+
+ DBUG_PRINT("value", ("set blob ptr: 0x%lx len: %u",
+ (long) blob_ptr, blob_len));
+ DBUG_DUMP("value", blob_ptr, MY_MIN(blob_len, 26));
+
+ if (set_blob_value)
+ *set_blob_value= TRUE;
+ // No callback needed to write value
+ DBUG_RETURN(ndb_blob->setValue(blob_ptr, blob_len) != 0);
+ }
+ DBUG_RETURN(1);
+ }
+}
+
+
+NdbBlob::ActiveHook g_get_ndb_blobs_value;
+
+/**
+ Callback to read all blob values.
+ - not done in unpack_record because unpack_record is valid
+ after execute(Commit) but reading blobs is not
+ - may only generate read operations; they have to be executed
+ somewhere before the data is available
+ - due to single buffer for all blobs, we let the last blob
+ process all blobs (last so that all are active)
+ - null bit is still set in unpack_record.
+
+ @todo
+ allocate blob part aligned buffers
+*/
+
+int g_get_ndb_blobs_value(NdbBlob *ndb_blob, void *arg)
+{
+ DBUG_ENTER("g_get_ndb_blobs_value");
+ if (ndb_blob->blobsNextBlob() != NULL)
+ DBUG_RETURN(0);
+ ha_ndbcluster *ha= (ha_ndbcluster *)arg;
+ int ret= get_ndb_blobs_value(ha->table, ha->m_value,
+ ha->m_blobs_buffer, ha->m_blobs_buffer_size,
+ ha->m_blobs_offset);
+ DBUG_RETURN(ret);
+}
+
+/*
+ This routine is shared by injector. There is no common blobs buffer
+ so the buffer and length are passed by reference. Injector also
+ passes a record pointer diff.
+ */
+int get_ndb_blobs_value(TABLE* table, NdbValue* value_array,
+ uchar*& buffer, uint& buffer_size,
+ my_ptrdiff_t ptrdiff)
+{
+ DBUG_ENTER("get_ndb_blobs_value");
+
+ // Field has no field number so cannot use TABLE blob_field
+ // Loop twice, first only counting total buffer size
+ for (int loop= 0; loop <= 1; loop++)
+ {
+ uint32 offset= 0;
+ for (uint i= 0; i < table->s->fields; i++)
+ {
+ Field *field= table->field[i];
+ NdbValue value= value_array[i];
+ if (! (field->flags & BLOB_FLAG))
+ continue;
+ if (value.blob == NULL)
+ {
+ DBUG_PRINT("info",("[%u] skipped", i));
+ continue;
+ }
+ Field_blob *field_blob= (Field_blob *)field;
+ NdbBlob *ndb_blob= value.blob;
+ int isNull;
+ if (ndb_blob->getNull(isNull) != 0)
+ ERR_RETURN(ndb_blob->getNdbError());
+ if (isNull == 0) {
+ Uint64 len64= 0;
+ if (ndb_blob->getLength(len64) != 0)
+ ERR_RETURN(ndb_blob->getNdbError());
+ // Align to Uint64
+ uint32 size= len64;
+ if (size % 8 != 0)
+ size+= 8 - size % 8;
+ if (loop == 1)
+ {
+ uchar *buf= buffer + offset;
+ uint32 len= 0xffffffff; // Max uint32
+ if (ndb_blob->readData(buf, len) != 0)
+ ERR_RETURN(ndb_blob->getNdbError());
+ DBUG_PRINT("info", ("[%u] offset: %u buf: 0x%lx len=%u [ptrdiff=%d]",
+ i, offset, (long) buf, len, (int)ptrdiff));
+ DBUG_ASSERT(len == len64);
+ // Ugly hack assumes only ptr needs to be changed
+ field_blob->set_ptr_offset(ptrdiff, len, buf);
+ }
+ offset+= size;
+ }
+ else if (loop == 1) // undefined or null
+ {
+ // have to set length even in this case
+ uchar *buf= buffer + offset; // or maybe NULL
+ uint32 len= 0;
+ field_blob->set_ptr_offset(ptrdiff, len, buf);
+ DBUG_PRINT("info", ("[%u] isNull=%d", i, isNull));
+ }
+ }
+ if (loop == 0 && offset > buffer_size)
+ {
+ my_free(buffer);
+ buffer_size= 0;
+ DBUG_PRINT("info", ("allocate blobs buffer size %u", offset));
+ buffer= (uchar*) my_malloc(offset, MYF(MY_WME));
+ if (buffer == NULL)
+ {
+ sql_print_error("ha_ndbcluster::get_ndb_blobs_value: "
+ "my_malloc(%u) failed", offset);
+ DBUG_RETURN(-1);
+ }
+ buffer_size= offset;
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Instruct NDB to fetch one field.
+
+ Data is read directly into buffer provided by field
+ if field is NULL, data is read into memory provided by NDBAPI.
+*/
+
+int ha_ndbcluster::get_ndb_value(NdbOperation *ndb_op, Field *field,
+ uint fieldnr, uchar* buf)
+{
+ DBUG_ENTER("get_ndb_value");
+ DBUG_PRINT("enter", ("fieldnr: %d flags: %o", fieldnr,
+ (int)(field != NULL ? field->flags : 0)));
+
+ if (field != NULL)
+ {
+ DBUG_ASSERT(buf);
+ DBUG_ASSERT(ndb_supported_type(field->type()));
+ DBUG_ASSERT(field->ptr != NULL);
+ if (! (field->flags & BLOB_FLAG))
+ {
+ if (field->type() != MYSQL_TYPE_BIT)
+ {
+ uchar *field_buf;
+ if (field->pack_length() != 0)
+ field_buf= buf + (field->ptr - table->record[0]);
+ else
+ field_buf= (uchar *)&dummy_buf;
+ m_value[fieldnr].rec= ndb_op->getValue(fieldnr,
+ (char*) field_buf);
+ }
+ else // if (field->type() == MYSQL_TYPE_BIT)
+ {
+ m_value[fieldnr].rec= ndb_op->getValue(fieldnr);
+ }
+ DBUG_RETURN(m_value[fieldnr].rec == NULL);
+ }
+
+ // Blob type
+ NdbBlob *ndb_blob= ndb_op->getBlobHandle(fieldnr);
+ m_value[fieldnr].blob= ndb_blob;
+ if (ndb_blob != NULL)
+ {
+ // Set callback
+ m_blobs_offset= buf - (uchar*) table->record[0];
+ void *arg= (void *)this;
+ DBUG_RETURN(ndb_blob->setActiveHook(g_get_ndb_blobs_value, arg) != 0);
+ }
+ DBUG_RETURN(1);
+ }
+
+ // Used for hidden key only
+ m_value[fieldnr].rec= ndb_op->getValue(fieldnr, (char*) m_ref);
+ DBUG_RETURN(m_value[fieldnr].rec == NULL);
+}
+
+/*
+ Instruct NDB to fetch the partition id (fragment id)
+*/
+int ha_ndbcluster::get_ndb_partition_id(NdbOperation *ndb_op)
+{
+ DBUG_ENTER("get_ndb_partition_id");
+ DBUG_RETURN(ndb_op->getValue(NdbDictionary::Column::FRAGMENT,
+ (char *)&m_part_id) == NULL);
+}
+
+/**
+ Check if any set or get of blob value in current query.
+*/
+
+bool ha_ndbcluster::uses_blob_value()
+{
+ MY_BITMAP *bitmap;
+ uint *blob_index, *blob_index_end;
+ if (table_share->blob_fields == 0)
+ return FALSE;
+
+ bitmap= m_write_op ? table->write_set : table->read_set;
+ blob_index= table_share->blob_field;
+ blob_index_end= blob_index + table_share->blob_fields;
+ do
+ {
+ if (bitmap_is_set(bitmap, table->field[*blob_index]->field_index))
+ return TRUE;
+ } while (++blob_index != blob_index_end);
+ return FALSE;
+}
+
+
+/**
+ Get metadata for this table from NDB.
+
+ Check that frm-file on disk is equal to frm-file
+ of table accessed in NDB.
+
+ @retval
+ 0 ok
+ @retval
+ -2 Meta data has changed; Re-read data and try again
+*/
+
+int cmp_frm(const NDBTAB *ndbtab, const void *pack_data,
+ uint pack_length)
+{
+ DBUG_ENTER("cmp_frm");
+ /*
+ Compare FrmData in NDB with frm file from disk.
+ */
+ if ((pack_length != ndbtab->getFrmLength()) ||
+ (memcmp(pack_data, ndbtab->getFrmData(), pack_length)))
+ DBUG_RETURN(1);
+ DBUG_RETURN(0);
+}
+
+int ha_ndbcluster::get_metadata(const char *path)
+{
+ Ndb *ndb= get_ndb();
+ NDBDICT *dict= ndb->getDictionary();
+ const NDBTAB *tab;
+ int error;
+ DBUG_ENTER("get_metadata");
+ DBUG_PRINT("enter", ("m_tabname: %s, path: %s", m_tabname, path));
+
+ DBUG_ASSERT(m_table == NULL);
+ DBUG_ASSERT(m_table_info == NULL);
+
+ uchar *data= NULL, *pack_data= NULL;
+ size_t length, pack_length;
+
+ /*
+ Compare FrmData in NDB with frm file from disk.
+ */
+ error= 0;
+ if (readfrm(path, &data, &length) ||
+ packfrm(data, length, &pack_data, &pack_length))
+ {
+ my_free(data);
+ my_free(pack_data);
+ DBUG_RETURN(1);
+ }
+
+ Ndb_table_guard ndbtab_g(dict, m_tabname);
+ if (!(tab= ndbtab_g.get_table()))
+ ERR_RETURN(dict->getNdbError());
+
+ if (get_ndb_share_state(m_share) != NSS_ALTERED
+ && cmp_frm(tab, pack_data, pack_length))
+ {
+ DBUG_PRINT("error",
+ ("metadata, pack_length: %lu getFrmLength: %d memcmp: %d",
+ (ulong) pack_length, tab->getFrmLength(),
+ memcmp(pack_data, tab->getFrmData(), pack_length)));
+ DBUG_DUMP("pack_data", (uchar*) pack_data, pack_length);
+ DBUG_DUMP("frm", (uchar*) tab->getFrmData(), tab->getFrmLength());
+ error= HA_ERR_TABLE_DEF_CHANGED;
+ }
+ my_free(data);
+ my_free(pack_data);
+
+ if (error)
+ goto err;
+
+ DBUG_PRINT("info", ("fetched table %s", tab->getName()));
+ m_table= tab;
+ if ((error= open_indexes(ndb, table, FALSE)) == 0)
+ {
+ ndbtab_g.release();
+ DBUG_RETURN(0);
+ }
+err:
+ ndbtab_g.invalidate();
+ m_table= NULL;
+ DBUG_RETURN(error);
+}
+
+static int fix_unique_index_attr_order(NDB_INDEX_DATA &data,
+ const NDBINDEX *index,
+ KEY *key_info)
+{
+ DBUG_ENTER("fix_unique_index_attr_order");
+ unsigned sz= index->getNoOfIndexColumns();
+
+ if (data.unique_index_attrid_map)
+ my_free(data.unique_index_attrid_map);
+ data.unique_index_attrid_map= (uchar*)my_malloc(sz,MYF(MY_WME));
+ if (data.unique_index_attrid_map == 0)
+ {
+ sql_print_error("fix_unique_index_attr_order: my_malloc(%u) failure",
+ (unsigned int)sz);
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ }
+
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+ DBUG_ASSERT(key_info->user_defined_key_parts == sz);
+ for (unsigned i= 0; key_part != end; key_part++, i++)
+ {
+ const char *field_name= key_part->field->field_name;
+#ifndef DBUG_OFF
+ data.unique_index_attrid_map[i]= 255;
+#endif
+ for (unsigned j= 0; j < sz; j++)
+ {
+ const NDBCOL *c= index->getColumn(j);
+ if (strcmp(field_name, c->getName()) == 0)
+ {
+ data.unique_index_attrid_map[i]= j;
+ break;
+ }
+ }
+ DBUG_ASSERT(data.unique_index_attrid_map[i] != 255);
+ }
+ DBUG_RETURN(0);
+}
+
+/*
+ Create all the indexes for a table.
+ If any index should fail to be created,
+ the error is returned immediately
+*/
+int ha_ndbcluster::create_indexes(Ndb *ndb, TABLE *tab)
+{
+ uint i;
+ int error= 0;
+ const char *index_name;
+ KEY* key_info= tab->key_info;
+ const char **key_name= tab->s->keynames.type_names;
+ DBUG_ENTER("ha_ndbcluster::create_indexes");
+
+ for (i= 0; i < tab->s->keys; i++, key_info++, key_name++)
+ {
+ index_name= *key_name;
+ NDB_INDEX_TYPE idx_type= get_index_type_from_table(i);
+ error= create_index(index_name, key_info, idx_type, i);
+ if (error)
+ {
+ DBUG_PRINT("error", ("Failed to create index %u", i));
+ break;
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+static void ndb_init_index(NDB_INDEX_DATA &data)
+{
+ data.type= UNDEFINED_INDEX;
+ data.status= UNDEFINED;
+ data.unique_index= NULL;
+ data.index= NULL;
+ data.unique_index_attrid_map= NULL;
+ data.index_stat=NULL;
+ data.index_stat_cache_entries=0;
+ data.index_stat_update_freq=0;
+ data.index_stat_query_count=0;
+}
+
+static void ndb_clear_index(NDB_INDEX_DATA &data)
+{
+ if (data.unique_index_attrid_map)
+ {
+ my_free(data.unique_index_attrid_map);
+ }
+ if (data.index_stat)
+ {
+ delete data.index_stat;
+ }
+ ndb_init_index(data);
+}
+
+/*
+ Associate a direct reference to an index handle
+ with an index (for faster access)
+ */
+int ha_ndbcluster::add_index_handle(THD *thd, NDBDICT *dict, KEY *key_info,
+ const char *index_name, uint index_no)
+{
+ int error= 0;
+ NDB_INDEX_TYPE idx_type= get_index_type_from_table(index_no);
+ m_index[index_no].type= idx_type;
+ DBUG_ENTER("ha_ndbcluster::add_index_handle");
+ DBUG_PRINT("enter", ("table %s", m_tabname));
+
+ if (idx_type != PRIMARY_KEY_INDEX && idx_type != UNIQUE_INDEX)
+ {
+ DBUG_PRINT("info", ("Get handle to index %s", index_name));
+ const NDBINDEX *index;
+ do
+ {
+ index= dict->getIndexGlobal(index_name, *m_table);
+ if (!index)
+ ERR_RETURN(dict->getNdbError());
+ DBUG_PRINT("info", ("index: 0x%lx id: %d version: %d.%d status: %d",
+ (long) index,
+ index->getObjectId(),
+ index->getObjectVersion() & 0xFFFFFF,
+ index->getObjectVersion() >> 24,
+ index->getObjectStatus()));
+ DBUG_ASSERT(index->getObjectStatus() ==
+ NdbDictionary::Object::Retrieved);
+ break;
+ } while (1);
+ m_index[index_no].index= index;
+ // ordered index - add stats
+ NDB_INDEX_DATA& d=m_index[index_no];
+ delete d.index_stat;
+ d.index_stat=NULL;
+ if (THDVAR(thd, index_stat_enable))
+ {
+ d.index_stat=new NdbIndexStat(index);
+ d.index_stat_cache_entries=THDVAR(thd, index_stat_cache_entries);
+ d.index_stat_update_freq=THDVAR(thd, index_stat_update_freq);
+ d.index_stat_query_count=0;
+ d.index_stat->alloc_cache(d.index_stat_cache_entries);
+ DBUG_PRINT("info", ("index %s stat=on cache_entries=%u update_freq=%u",
+ index->getName(),
+ d.index_stat_cache_entries,
+ d.index_stat_update_freq));
+ } else
+ {
+ DBUG_PRINT("info", ("index %s stat=off", index->getName()));
+ }
+ }
+ if (idx_type == UNIQUE_ORDERED_INDEX || idx_type == UNIQUE_INDEX)
+ {
+ char unique_index_name[FN_LEN + 1];
+ static const char* unique_suffix= "$unique";
+ m_has_unique_index= TRUE;
+ strxnmov(unique_index_name, FN_LEN, index_name, unique_suffix, NullS);
+ DBUG_PRINT("info", ("Get handle to unique_index %s", unique_index_name));
+ const NDBINDEX *index;
+ do
+ {
+ index= dict->getIndexGlobal(unique_index_name, *m_table);
+ if (!index)
+ ERR_RETURN(dict->getNdbError());
+ DBUG_PRINT("info", ("index: 0x%lx id: %d version: %d.%d status: %d",
+ (long) index,
+ index->getObjectId(),
+ index->getObjectVersion() & 0xFFFFFF,
+ index->getObjectVersion() >> 24,
+ index->getObjectStatus()));
+ DBUG_ASSERT(index->getObjectStatus() ==
+ NdbDictionary::Object::Retrieved);
+ break;
+ } while (1);
+ m_index[index_no].unique_index= index;
+ error= fix_unique_index_attr_order(m_index[index_no], index, key_info);
+ }
+ if (!error)
+ m_index[index_no].status= ACTIVE;
+
+ DBUG_RETURN(error);
+}
+
+/*
+ Associate index handles for each index of a table
+*/
+int ha_ndbcluster::open_indexes(Ndb *ndb, TABLE *tab, bool ignore_error)
+{
+ uint i;
+ int error= 0;
+ THD *thd=current_thd;
+ NDBDICT *dict= ndb->getDictionary();
+ KEY* key_info= tab->key_info;
+ const char **key_name= tab->s->keynames.type_names;
+ DBUG_ENTER("ha_ndbcluster::open_indexes");
+ m_has_unique_index= FALSE;
+ for (i= 0; i < tab->s->keys; i++, key_info++, key_name++)
+ {
+ if ((error= add_index_handle(thd, dict, key_info, *key_name, i)))
+ {
+ if (ignore_error)
+ m_index[i].index= m_index[i].unique_index= NULL;
+ else
+ break;
+ }
+ m_index[i].null_in_unique_index= FALSE;
+ if (check_index_fields_not_null(key_info))
+ m_index[i].null_in_unique_index= TRUE;
+ }
+
+ if (error && !ignore_error)
+ {
+ while (i > 0)
+ {
+ i--;
+ if (m_index[i].index)
+ {
+ dict->removeIndexGlobal(*m_index[i].index, 1);
+ m_index[i].index= NULL;
+ }
+ if (m_index[i].unique_index)
+ {
+ dict->removeIndexGlobal(*m_index[i].unique_index, 1);
+ m_index[i].unique_index= NULL;
+ }
+ }
+ }
+
+ DBUG_ASSERT(error == 0 || error == 4243);
+
+ DBUG_RETURN(error);
+}
+
+/*
+ Renumber indexes in index list by shifting out
+ indexes that are to be dropped
+ */
+void ha_ndbcluster::renumber_indexes(Ndb *ndb, TABLE *tab)
+{
+ uint i;
+ const char *index_name;
+ KEY* key_info= tab->key_info;
+ const char **key_name= tab->s->keynames.type_names;
+ DBUG_ENTER("ha_ndbcluster::renumber_indexes");
+
+ for (i= 0; i < tab->s->keys; i++, key_info++, key_name++)
+ {
+ index_name= *key_name;
+ NDB_INDEX_TYPE idx_type= get_index_type_from_table(i);
+ m_index[i].type= idx_type;
+ if (m_index[i].status == TO_BE_DROPPED)
+ {
+ DBUG_PRINT("info", ("Shifting index %s(%i) out of the list",
+ index_name, i));
+ NDB_INDEX_DATA tmp;
+ uint j= i + 1;
+ // Shift index out of list
+ while(j != MAX_KEY && m_index[j].status != UNDEFINED)
+ {
+ tmp= m_index[j - 1];
+ m_index[j - 1]= m_index[j];
+ m_index[j]= tmp;
+ j++;
+ }
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Drop all indexes that are marked for deletion
+*/
+int ha_ndbcluster::drop_indexes(Ndb *ndb, TABLE *tab)
+{
+ uint i;
+ int error= 0;
+ const char *index_name;
+ KEY* key_info= tab->key_info;
+ NDBDICT *dict= ndb->getDictionary();
+ DBUG_ENTER("ha_ndbcluster::drop_indexes");
+
+ for (i= 0; i < tab->s->keys; i++, key_info++)
+ {
+ NDB_INDEX_TYPE idx_type= get_index_type_from_table(i);
+ m_index[i].type= idx_type;
+ if (m_index[i].status == TO_BE_DROPPED)
+ {
+ const NdbDictionary::Index *index= m_index[i].index;
+ const NdbDictionary::Index *unique_index= m_index[i].unique_index;
+
+ if (index)
+ {
+ index_name= index->getName();
+ DBUG_PRINT("info", ("Dropping index %u: %s", i, index_name));
+ // Drop ordered index from ndb
+ error= dict->dropIndexGlobal(*index);
+ if (!error)
+ {
+ dict->removeIndexGlobal(*index, 1);
+ m_index[i].index= NULL;
+ }
+ }
+ if (!error && unique_index)
+ {
+ index_name= unique_index->getName();
+ DBUG_PRINT("info", ("Dropping unique index %u: %s", i, index_name));
+ // Drop unique index from ndb
+ error= dict->dropIndexGlobal(*unique_index);
+ if (!error)
+ {
+ dict->removeIndexGlobal(*unique_index, 1);
+ m_index[i].unique_index= NULL;
+ }
+ }
+ if (error)
+ DBUG_RETURN(error);
+ ndb_clear_index(m_index[i]);
+ continue;
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+/**
+ Decode the type of an index from information
+ provided in table object.
+*/
+NDB_INDEX_TYPE ha_ndbcluster::get_index_type_from_table(uint inx) const
+{
+ return get_index_type_from_key(inx, table_share->key_info,
+ inx == table_share->primary_key);
+}
+
+NDB_INDEX_TYPE ha_ndbcluster::get_index_type_from_key(uint inx,
+ KEY *key_info,
+ bool primary) const
+{
+ bool is_hash_index= (key_info[inx].algorithm ==
+ HA_KEY_ALG_HASH);
+ if (primary)
+ return is_hash_index ? PRIMARY_KEY_INDEX : PRIMARY_KEY_ORDERED_INDEX;
+
+ return ((key_info[inx].flags & HA_NOSAME) ?
+ (is_hash_index ? UNIQUE_INDEX : UNIQUE_ORDERED_INDEX) :
+ ORDERED_INDEX);
+}
+
+bool ha_ndbcluster::check_index_fields_not_null(KEY* key_info)
+{
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+ DBUG_ENTER("ha_ndbcluster::check_index_fields_not_null");
+
+ for (; key_part != end; key_part++)
+ {
+ Field* field= key_part->field;
+ if (field->maybe_null())
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+void ha_ndbcluster::release_metadata(THD *thd, Ndb *ndb)
+{
+ uint i;
+
+ DBUG_ENTER("release_metadata");
+ DBUG_PRINT("enter", ("m_tabname: %s", m_tabname));
+
+ NDBDICT *dict= ndb->getDictionary();
+ int invalidate_indexes= 0;
+ if (thd && thd->lex && thd->lex->sql_command == SQLCOM_FLUSH)
+ {
+ invalidate_indexes = 1;
+ }
+ if (m_table != NULL)
+ {
+ if (m_table->getObjectStatus() == NdbDictionary::Object::Invalid)
+ invalidate_indexes= 1;
+ dict->removeTableGlobal(*m_table, invalidate_indexes);
+ }
+ // TODO investigate
+ DBUG_ASSERT(m_table_info == NULL);
+ m_table_info= NULL;
+
+ // Release index list
+ for (i= 0; i < MAX_KEY; i++)
+ {
+ if (m_index[i].unique_index)
+ {
+ DBUG_ASSERT(m_table != NULL);
+ dict->removeIndexGlobal(*m_index[i].unique_index, invalidate_indexes);
+ }
+ if (m_index[i].index)
+ {
+ DBUG_ASSERT(m_table != NULL);
+ dict->removeIndexGlobal(*m_index[i].index, invalidate_indexes);
+ }
+ ndb_clear_index(m_index[i]);
+ }
+
+ m_table= NULL;
+ DBUG_VOID_RETURN;
+}
+
+int ha_ndbcluster::get_ndb_lock_type(enum thr_lock_type type)
+{
+ if (type >= TL_WRITE_ALLOW_WRITE)
+ return NdbOperation::LM_Exclusive;
+ if (type == TL_READ_WITH_SHARED_LOCKS ||
+ uses_blob_value())
+ return NdbOperation::LM_Read;
+ return NdbOperation::LM_CommittedRead;
+}
+
+static const ulong index_type_flags[]=
+{
+ /* UNDEFINED_INDEX */
+ 0,
+
+ /* PRIMARY_KEY_INDEX */
+ HA_ONLY_WHOLE_INDEX,
+
+ /* PRIMARY_KEY_ORDERED_INDEX */
+ /*
+ Enable HA_KEYREAD_ONLY when "sorted" indexes are supported,
+ thus ORDERD BY clauses can be optimized by reading directly
+ through the index.
+ */
+ // HA_KEYREAD_ONLY |
+ HA_READ_NEXT |
+ HA_READ_PREV |
+ HA_READ_RANGE |
+ HA_READ_ORDER,
+
+ /* UNIQUE_INDEX */
+ HA_ONLY_WHOLE_INDEX,
+
+ /* UNIQUE_ORDERED_INDEX */
+ HA_READ_NEXT |
+ HA_READ_PREV |
+ HA_READ_RANGE |
+ HA_READ_ORDER,
+
+ /* ORDERED_INDEX */
+ HA_READ_NEXT |
+ HA_READ_PREV |
+ HA_READ_RANGE |
+ HA_READ_ORDER
+};
+
+static const int index_flags_size= sizeof(index_type_flags)/sizeof(ulong);
+
+inline NDB_INDEX_TYPE ha_ndbcluster::get_index_type(uint idx_no) const
+{
+ DBUG_ASSERT(idx_no < MAX_KEY);
+ return m_index[idx_no].type;
+}
+
+inline bool ha_ndbcluster::has_null_in_unique_index(uint idx_no) const
+{
+ DBUG_ASSERT(idx_no < MAX_KEY);
+ return m_index[idx_no].null_in_unique_index;
+}
+
+
+/**
+ Get the flags for an index.
+
+ @return
+ flags depending on the type of the index.
+*/
+
+inline ulong ha_ndbcluster::index_flags(uint idx_no, uint part,
+ bool all_parts) const
+{
+ DBUG_ENTER("ha_ndbcluster::index_flags");
+ DBUG_PRINT("enter", ("idx_no: %u", idx_no));
+ DBUG_ASSERT(get_index_type_from_table(idx_no) < index_flags_size);
+ DBUG_RETURN(index_type_flags[get_index_type_from_table(idx_no)] |
+ HA_KEY_SCAN_NOT_ROR);
+}
+
+static void shrink_varchar(Field* field, const uchar* & ptr, uchar* buf)
+{
+ if (field->type() == MYSQL_TYPE_VARCHAR && ptr != NULL) {
+ Field_varstring* f= (Field_varstring*)field;
+ if (f->length_bytes == 1) {
+ uint pack_len= field->pack_length();
+ DBUG_ASSERT(1 <= pack_len && pack_len <= 256);
+ if (ptr[1] == 0) {
+ buf[0]= ptr[0];
+ } else {
+ DBUG_ASSERT(FALSE);
+ buf[0]= 255;
+ }
+ memmove(buf + 1, ptr + 2, pack_len - 1);
+ ptr= buf;
+ }
+ }
+}
+
+int ha_ndbcluster::set_primary_key(NdbOperation *op, const uchar *key)
+{
+ KEY* key_info= table->key_info + table_share->primary_key;
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+ DBUG_ENTER("set_primary_key");
+
+ for (; key_part != end; key_part++)
+ {
+ Field* field= key_part->field;
+ const uchar* ptr= key;
+ uchar buf[256];
+ shrink_varchar(field, ptr, buf);
+ if (set_ndb_key(op, field,
+ key_part->fieldnr-1, ptr))
+ ERR_RETURN(op->getNdbError());
+ key += key_part->store_length;
+ }
+ DBUG_RETURN(0);
+}
+
+
+int ha_ndbcluster::set_primary_key_from_record(NdbOperation *op, const uchar *record)
+{
+ KEY* key_info= table->key_info + table_share->primary_key;
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+ DBUG_ENTER("set_primary_key_from_record");
+
+ for (; key_part != end; key_part++)
+ {
+ Field* field= key_part->field;
+ if (set_ndb_key(op, field,
+ key_part->fieldnr-1, record+key_part->offset))
+ ERR_RETURN(op->getNdbError());
+ }
+ DBUG_RETURN(0);
+}
+
+bool ha_ndbcluster::check_index_fields_in_write_set(uint keyno)
+{
+ KEY* key_info= table->key_info + keyno;
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+ uint i;
+ DBUG_ENTER("check_index_fields_in_write_set");
+
+ for (i= 0; key_part != end; key_part++, i++)
+ {
+ Field* field= key_part->field;
+ if (!bitmap_is_set(table->write_set, field->field_index))
+ {
+ DBUG_RETURN(false);
+ }
+ }
+
+ DBUG_RETURN(true);
+}
+
+int ha_ndbcluster::set_index_key_from_record(NdbOperation *op,
+ const uchar *record, uint keyno)
+{
+ KEY* key_info= table->key_info + keyno;
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+ uint i;
+ DBUG_ENTER("set_index_key_from_record");
+
+ for (i= 0; key_part != end; key_part++, i++)
+ {
+ Field* field= key_part->field;
+ if (set_ndb_key(op, field, m_index[keyno].unique_index_attrid_map[i],
+ record+key_part->offset))
+ ERR_RETURN(m_active_trans->getNdbError());
+ }
+ DBUG_RETURN(0);
+}
+
+int
+ha_ndbcluster::set_index_key(NdbOperation *op,
+ const KEY *key_info,
+ const uchar * key_ptr)
+{
+ DBUG_ENTER("ha_ndbcluster::set_index_key");
+ uint i;
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+
+ for (i= 0; key_part != end; key_part++, i++)
+ {
+ Field* field= key_part->field;
+ const uchar* ptr= key_part->null_bit ? key_ptr + 1 : key_ptr;
+ uchar buf[256];
+ shrink_varchar(field, ptr, buf);
+ if (set_ndb_key(op, field, m_index[active_index].unique_index_attrid_map[i], ptr))
+ ERR_RETURN(m_active_trans->getNdbError());
+ key_ptr+= key_part->store_length;
+ }
+ DBUG_RETURN(0);
+}
+
+inline
+int ha_ndbcluster::define_read_attrs(uchar* buf, NdbOperation* op)
+{
+ uint i;
+ DBUG_ENTER("define_read_attrs");
+
+ // Define attributes to read
+ for (i= 0; i < table_share->fields; i++)
+ {
+ Field *field= table->field[i];
+ if (bitmap_is_set(table->read_set, i) ||
+ ((field->flags & PRI_KEY_FLAG)))
+ {
+ if (get_ndb_value(op, field, i, buf))
+ ERR_RETURN(op->getNdbError());
+ }
+ else
+ {
+ m_value[i].ptr= NULL;
+ }
+ }
+
+ if (table_share->primary_key == MAX_KEY)
+ {
+ DBUG_PRINT("info", ("Getting hidden key"));
+ // Scanning table with no primary key
+ int hidden_no= table_share->fields;
+#ifndef DBUG_OFF
+ const NDBTAB *tab= (const NDBTAB *) m_table;
+ if (!tab->getColumn(hidden_no))
+ DBUG_RETURN(1);
+#endif
+ if (get_ndb_value(op, NULL, hidden_no, NULL))
+ ERR_RETURN(op->getNdbError());
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Read one record from NDB using primary key.
+*/
+
+int ha_ndbcluster::pk_read(const uchar *key, uint key_len, uchar *buf,
+ uint32 part_id)
+{
+ uint no_fields= table_share->fields;
+ NdbConnection *trans= m_active_trans;
+ NdbOperation *op;
+
+ int res;
+ DBUG_ENTER("pk_read");
+ DBUG_PRINT("enter", ("key_len: %u", key_len));
+ DBUG_DUMP("key", key, key_len);
+ m_write_op= FALSE;
+
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) ||
+ op->readTuple(lm) != 0)
+ ERR_RETURN(trans->getNdbError());
+
+ if (table_share->primary_key == MAX_KEY)
+ {
+ // This table has no primary key, use "hidden" primary key
+ DBUG_PRINT("info", ("Using hidden key"));
+ DBUG_DUMP("key", key, 8);
+ if (set_hidden_key(op, no_fields, key))
+ ERR_RETURN(trans->getNdbError());
+
+ // Read key at the same time, for future reference
+ if (get_ndb_value(op, NULL, no_fields, NULL))
+ ERR_RETURN(trans->getNdbError());
+ }
+ else
+ {
+ if ((res= set_primary_key(op, key)))
+ return res;
+ }
+
+ if ((res= define_read_attrs(buf, op)))
+ DBUG_RETURN(res);
+
+ if (m_use_partition_function)
+ {
+ op->setPartitionId(part_id);
+ // If table has user defined partitioning
+ // and no indexes, we need to read the partition id
+ // to support ORDER BY queries
+ if (table_share->primary_key == MAX_KEY &&
+ get_ndb_partition_id(op))
+ ERR_RETURN(trans->getNdbError());
+ }
+
+ if ((res = execute_no_commit_ie(this,trans,FALSE)) != 0 ||
+ op->getNdbError().code)
+ {
+ table->status= STATUS_NOT_FOUND;
+ DBUG_RETURN(ndb_err(trans));
+ }
+
+ // The value have now been fetched from NDB
+ unpack_record(buf);
+ table->status= 0;
+ DBUG_RETURN(0);
+}
+
+/**
+ Read one complementing record from NDB using primary key from old_data
+ or hidden key.
+*/
+
+int ha_ndbcluster::complemented_read(const uchar *old_data, uchar *new_data,
+ uint32 old_part_id)
+{
+ uint no_fields= table_share->fields, i;
+ NdbTransaction *trans= m_active_trans;
+ NdbOperation *op;
+ DBUG_ENTER("complemented_read");
+ m_write_op= FALSE;
+
+ if (bitmap_is_set_all(table->read_set))
+ {
+ // We have allready retrieved all fields, nothing to complement
+ DBUG_RETURN(0);
+ }
+
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) ||
+ op->readTuple(lm) != 0)
+ ERR_RETURN(trans->getNdbError());
+ if (table_share->primary_key != MAX_KEY)
+ {
+ if (set_primary_key_from_record(op, old_data))
+ ERR_RETURN(trans->getNdbError());
+ }
+ else
+ {
+ // This table has no primary key, use "hidden" primary key
+ if (set_hidden_key(op, table->s->fields, m_ref))
+ ERR_RETURN(op->getNdbError());
+ }
+
+ if (m_use_partition_function)
+ op->setPartitionId(old_part_id);
+
+ // Read all unreferenced non-key field(s)
+ for (i= 0; i < no_fields; i++)
+ {
+ Field *field= table->field[i];
+ if (!((field->flags & PRI_KEY_FLAG) ||
+ bitmap_is_set(table->read_set, i)) &&
+ !bitmap_is_set(table->write_set, i))
+ {
+ if (get_ndb_value(op, field, i, new_data))
+ ERR_RETURN(trans->getNdbError());
+ }
+ }
+
+ if (execute_no_commit(this,trans,FALSE) != 0)
+ {
+ table->status= STATUS_NOT_FOUND;
+ DBUG_RETURN(ndb_err(trans));
+ }
+
+ // The value have now been fetched from NDB
+ unpack_record(new_data);
+ table->status= 0;
+
+ /*
+ * restore m_value
+ */
+ for (i= 0; i < no_fields; i++)
+ {
+ Field *field= table->field[i];
+ if (!((field->flags & PRI_KEY_FLAG) ||
+ bitmap_is_set(table->read_set, i)))
+ {
+ m_value[i].ptr= NULL;
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+/**
+ Check that all operations between first and last all
+ have gotten the errcode
+ If checking for HA_ERR_KEY_NOT_FOUND then update m_dupkey
+ for all succeeding operations
+*/
+bool ha_ndbcluster::check_all_operations_for_error(NdbTransaction *trans,
+ const NdbOperation *first,
+ const NdbOperation *last,
+ uint errcode)
+{
+ const NdbOperation *op= first;
+ DBUG_ENTER("ha_ndbcluster::check_all_operations_for_error");
+
+ while(op)
+ {
+ NdbError err= op->getNdbError();
+ if (err.status != NdbError::Success)
+ {
+ if (ndb_to_mysql_error(&err) != (int) errcode)
+ DBUG_RETURN(FALSE);
+ if (op == last) break;
+ op= trans->getNextCompletedOperation(op);
+ }
+ else
+ {
+ // We found a duplicate
+ if (op->getType() == NdbOperation::UniqueIndexAccess)
+ {
+ if (errcode == HA_ERR_KEY_NOT_FOUND)
+ {
+ NdbIndexOperation *iop= (NdbIndexOperation *) op;
+ const NDBINDEX *index= iop->getIndex();
+ // Find the key_no of the index
+ for(uint i= 0; i<table->s->keys; i++)
+ {
+ if (m_index[i].unique_index == index)
+ {
+ m_dupkey= i;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Must have been primary key access
+ DBUG_ASSERT(op->getType() == NdbOperation::PrimaryKeyAccess);
+ if (errcode == HA_ERR_KEY_NOT_FOUND)
+ m_dupkey= table->s->primary_key;
+ }
+ DBUG_RETURN(FALSE);
+ }
+ }
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ * Check if record contains any null valued columns that are part of a key
+ */
+static
+int
+check_null_in_record(const KEY* key_info, const uchar *record)
+{
+ KEY_PART_INFO *curr_part, *end_part;
+ curr_part= key_info->key_part;
+ end_part= curr_part + key_info->user_defined_key_parts;
+
+ while (curr_part != end_part)
+ {
+ if (curr_part->null_bit &&
+ (record[curr_part->null_offset] & curr_part->null_bit))
+ return 1;
+ curr_part++;
+ }
+ return 0;
+ /*
+ We could instead pre-compute a bitmask in table_share with one bit for
+ every null-bit in the key, and so check this just by OR'ing the bitmask
+ with the null bitmap in the record.
+ But not sure it's worth it.
+ */
+}
+
+/**
+ Peek to check if any rows already exist with conflicting
+ primary key or unique index values
+*/
+
+int ha_ndbcluster::peek_indexed_rows(const uchar *record,
+ NDB_WRITE_OP write_op)
+{
+ NdbTransaction *trans= m_active_trans;
+ NdbOperation *op;
+ const NdbOperation *first, *last;
+ uint i;
+ int res;
+ DBUG_ENTER("peek_indexed_rows");
+
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ first= NULL;
+ if (write_op != NDB_UPDATE && table->s->primary_key != MAX_KEY)
+ {
+ /*
+ * Fetch any row with colliding primary key
+ */
+ if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) ||
+ op->readTuple(lm) != 0)
+ ERR_RETURN(trans->getNdbError());
+
+ first= op;
+ if ((res= set_primary_key_from_record(op, record)))
+ ERR_RETURN(trans->getNdbError());
+
+ if (m_use_partition_function)
+ {
+ uint32 part_id;
+ int error;
+ longlong func_value;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ error= m_part_info->get_partition_id(m_part_info, &part_id, &func_value);
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+ if (error)
+ {
+ m_part_info->err_value= func_value;
+ DBUG_RETURN(error);
+ }
+ op->setPartitionId(part_id);
+ }
+ }
+ /*
+ * Fetch any rows with colliding unique indexes
+ */
+ KEY* key_info;
+ KEY_PART_INFO *key_part, *end;
+ for (i= 0, key_info= table->key_info; i < table->s->keys; i++, key_info++)
+ {
+ if (i != table->s->primary_key &&
+ key_info->flags & HA_NOSAME)
+ {
+ /*
+ A unique index is defined on table.
+ We cannot look up a NULL field value in a unique index. But since
+ keys with NULLs are not indexed, such rows cannot conflict anyway, so
+ we just skip the index in this case.
+ */
+ if (check_null_in_record(key_info, record))
+ {
+ DBUG_PRINT("info", ("skipping check for key with NULL"));
+ continue;
+ }
+ if (write_op != NDB_INSERT && !check_index_fields_in_write_set(i))
+ {
+ DBUG_PRINT("info", ("skipping check for key %u not in write_set", i));
+ continue;
+ }
+ NdbIndexOperation *iop;
+ const NDBINDEX *unique_index = m_index[i].unique_index;
+ key_part= key_info->key_part;
+ end= key_part + key_info->user_defined_key_parts;
+ if (!(iop= trans->getNdbIndexOperation(unique_index, m_table)) ||
+ iop->readTuple(lm) != 0)
+ ERR_RETURN(trans->getNdbError());
+
+ if (!first)
+ first= iop;
+ if ((res= set_index_key_from_record(iop, record, i)))
+ ERR_RETURN(trans->getNdbError());
+ }
+ }
+ last= trans->getLastDefinedOperation();
+ if (first)
+ res= execute_no_commit_ie(this,trans,FALSE);
+ else
+ {
+ // Table has no keys
+ table->status= STATUS_NOT_FOUND;
+ DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
+ }
+ if (check_all_operations_for_error(trans, first, last,
+ HA_ERR_KEY_NOT_FOUND))
+ {
+ table->status= STATUS_NOT_FOUND;
+ DBUG_RETURN(ndb_err(trans));
+ }
+ else
+ {
+ DBUG_PRINT("info", ("m_dupkey %d", m_dupkey));
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Read one record from NDB using unique secondary index.
+*/
+
+int ha_ndbcluster::unique_index_read(const uchar *key,
+ uint key_len, uchar *buf)
+{
+ int res;
+ NdbTransaction *trans= m_active_trans;
+ NdbIndexOperation *op;
+ DBUG_ENTER("ha_ndbcluster::unique_index_read");
+ DBUG_PRINT("enter", ("key_len: %u, index: %u", key_len, active_index));
+ DBUG_DUMP("key", key, key_len);
+
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ if (!(op= trans->getNdbIndexOperation(m_index[active_index].unique_index,
+ m_table)) ||
+ op->readTuple(lm) != 0)
+ ERR_RETURN(trans->getNdbError());
+
+ // Set secondary index key(s)
+ if ((res= set_index_key(op, table->key_info + active_index, key)))
+ DBUG_RETURN(res);
+
+ if ((res= define_read_attrs(buf, op)))
+ DBUG_RETURN(res);
+
+ if (execute_no_commit_ie(this,trans,FALSE) != 0 ||
+ op->getNdbError().code)
+ {
+ int err= ndb_err(trans);
+ if(err==HA_ERR_KEY_NOT_FOUND)
+ table->status= STATUS_NOT_FOUND;
+ else
+ table->status= STATUS_GARBAGE;
+
+ DBUG_RETURN(err);
+ }
+
+ // The value have now been fetched from NDB
+ unpack_record(buf);
+ table->status= 0;
+ DBUG_RETURN(0);
+}
+
+inline int ha_ndbcluster::fetch_next(NdbScanOperation* cursor)
+{
+ DBUG_ENTER("fetch_next");
+ int local_check;
+ NdbTransaction *trans= m_active_trans;
+
+ if (m_lock_tuple)
+ {
+ /*
+ Lock level m_lock.type either TL_WRITE_ALLOW_WRITE
+ (SELECT FOR UPDATE) or TL_READ_WITH_SHARED_LOCKS (SELECT
+ LOCK WITH SHARE MODE) and row was not explictly unlocked
+ with unlock_row() call
+ */
+ NdbConnection *con_trans= m_active_trans;
+ NdbOperation *op;
+ // Lock row
+ DBUG_PRINT("info", ("Keeping lock on scanned row"));
+
+ if (!(op= m_active_cursor->lockCurrentTuple()))
+ {
+ /* purecov: begin inspected */
+ m_lock_tuple= FALSE;
+ ERR_RETURN(con_trans->getNdbError());
+ /* purecov: end */
+ }
+ m_ops_pending++;
+ }
+ m_lock_tuple= FALSE;
+
+ bool contact_ndb= m_lock.type < TL_WRITE_ALLOW_WRITE &&
+ m_lock.type != TL_READ_WITH_SHARED_LOCKS;;
+ do {
+ DBUG_PRINT("info", ("Call nextResult, contact_ndb: %d", contact_ndb));
+ /*
+ We can only handle one tuple with blobs at a time.
+ */
+ if (m_ops_pending && m_blobs_pending)
+ {
+ if (execute_no_commit(this,trans,FALSE) != 0)
+ DBUG_RETURN(ndb_err(trans));
+ m_ops_pending= 0;
+ m_blobs_pending= FALSE;
+ }
+
+ if ((local_check= cursor->nextResult(contact_ndb, m_force_send)) == 0)
+ {
+ /*
+ Explicitly lock tuple if "select for update" or
+ "select lock in share mode"
+ */
+ m_lock_tuple= (m_lock.type == TL_WRITE_ALLOW_WRITE
+ ||
+ m_lock.type == TL_READ_WITH_SHARED_LOCKS);
+ DBUG_RETURN(0);
+ }
+ else if (local_check == 1 || local_check == 2)
+ {
+ // 1: No more records
+ // 2: No more cached records
+
+ /*
+ Before fetching more rows and releasing lock(s),
+ all pending update or delete operations should
+ be sent to NDB
+ */
+ DBUG_PRINT("info", ("ops_pending: %ld", (long) m_ops_pending));
+ if (m_ops_pending)
+ {
+ if (m_transaction_on)
+ {
+ if (execute_no_commit(this,trans,FALSE) != 0)
+ DBUG_RETURN(-1);
+ }
+ else
+ {
+ if (execute_commit(this,trans) != 0)
+ DBUG_RETURN(-1);
+ if (trans->restart() != 0)
+ {
+ DBUG_ASSERT(0);
+ DBUG_RETURN(-1);
+ }
+ }
+ m_ops_pending= 0;
+ }
+ contact_ndb= (local_check == 2);
+ }
+ else
+ {
+ DBUG_RETURN(-1);
+ }
+ } while (local_check == 2);
+
+ DBUG_RETURN(1);
+}
+
+/**
+ Get the next record of a started scan. Try to fetch
+ it locally from NdbApi cached records if possible,
+ otherwise ask NDB for more.
+
+ @note
+ If this is a update/delete make sure to not contact
+ NDB before any pending ops have been sent to NDB.
+*/
+
+inline int ha_ndbcluster::next_result(uchar *buf)
+{
+ int res;
+ DBUG_ENTER("next_result");
+
+ if (!m_active_cursor)
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+
+ if ((res= fetch_next(m_active_cursor)) == 0)
+ {
+ DBUG_PRINT("info", ("One more record found"));
+
+ unpack_record(buf);
+ table->status= 0;
+ DBUG_RETURN(0);
+ }
+ else if (res == 1)
+ {
+ // No more records
+ table->status= STATUS_NOT_FOUND;
+
+ DBUG_PRINT("info", ("No more records"));
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ else
+ {
+ DBUG_RETURN(ndb_err(m_active_trans));
+ }
+}
+
+/**
+ Set bounds for ordered index scan.
+*/
+
+int ha_ndbcluster::set_bounds(NdbIndexScanOperation *op,
+ uint inx,
+ bool rir,
+ const key_range *keys[2],
+ uint range_no)
+{
+ const KEY *const key_info= table->key_info + inx;
+ const uint key_parts= key_info->user_defined_key_parts;
+ uint key_tot_len[2];
+ uint tot_len;
+ uint i, j;
+
+ DBUG_ENTER("set_bounds");
+ DBUG_PRINT("info", ("key_parts=%d", key_parts));
+
+ for (j= 0; j <= 1; j++)
+ {
+ const key_range *key= keys[j];
+ if (key != NULL)
+ {
+ // for key->flag see ha_rkey_function
+ DBUG_PRINT("info", ("key %d length=%d flag=%d",
+ j, key->length, key->flag));
+ key_tot_len[j]= key->length;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("key %d not present", j));
+ key_tot_len[j]= 0;
+ }
+ }
+ tot_len= 0;
+
+ for (i= 0; i < key_parts; i++)
+ {
+ KEY_PART_INFO *key_part= &key_info->key_part[i];
+ Field *field= key_part->field;
+#ifndef DBUG_OFF
+ uint part_len= key_part->length;
+#endif
+ uint part_store_len= key_part->store_length;
+ // Info about each key part
+ struct part_st {
+ bool part_last;
+ const key_range *key;
+ const uchar *part_ptr;
+ bool part_null;
+ int bound_type;
+ const uchar* bound_ptr;
+ };
+ struct part_st part[2];
+
+ for (j= 0; j <= 1; j++)
+ {
+ struct part_st &p= part[j];
+ p.key= NULL;
+ p.bound_type= -1;
+ if (tot_len < key_tot_len[j])
+ {
+ p.part_last= (tot_len + part_store_len >= key_tot_len[j]);
+ p.key= keys[j];
+ p.part_ptr= &p.key->key[tot_len];
+ p.part_null= key_part->null_bit && *p.part_ptr;
+ p.bound_ptr= (const char *)
+ p.part_null ? 0 : key_part->null_bit ? p.part_ptr + 1 : p.part_ptr;
+
+ if (j == 0)
+ {
+ switch (p.key->flag)
+ {
+ case HA_READ_KEY_EXACT:
+ if (! rir)
+ p.bound_type= NdbIndexScanOperation::BoundEQ;
+ else // differs for records_in_range
+ p.bound_type= NdbIndexScanOperation::BoundLE;
+ break;
+ // ascending
+ case HA_READ_KEY_OR_NEXT:
+ p.bound_type= NdbIndexScanOperation::BoundLE;
+ break;
+ case HA_READ_AFTER_KEY:
+ if (! p.part_last)
+ p.bound_type= NdbIndexScanOperation::BoundLE;
+ else
+ p.bound_type= NdbIndexScanOperation::BoundLT;
+ break;
+ // descending
+ case HA_READ_PREFIX_LAST: // weird
+ p.bound_type= NdbIndexScanOperation::BoundEQ;
+ break;
+ case HA_READ_PREFIX_LAST_OR_PREV: // weird
+ p.bound_type= NdbIndexScanOperation::BoundGE;
+ break;
+ case HA_READ_BEFORE_KEY:
+ if (! p.part_last)
+ p.bound_type= NdbIndexScanOperation::BoundGE;
+ else
+ p.bound_type= NdbIndexScanOperation::BoundGT;
+ break;
+ default:
+ break;
+ }
+ }
+ if (j == 1) {
+ switch (p.key->flag)
+ {
+ // ascending
+ case HA_READ_BEFORE_KEY:
+ if (! p.part_last)
+ p.bound_type= NdbIndexScanOperation::BoundGE;
+ else
+ p.bound_type= NdbIndexScanOperation::BoundGT;
+ break;
+ case HA_READ_AFTER_KEY: // weird
+ p.bound_type= NdbIndexScanOperation::BoundGE;
+ break;
+ default:
+ break;
+ // descending strangely sets no end key
+ }
+ }
+
+ if (p.bound_type == -1)
+ {
+ DBUG_PRINT("error", ("key %d unknown flag %d", j, p.key->flag));
+ DBUG_ASSERT(FALSE);
+ // Stop setting bounds but continue with what we have
+ DBUG_RETURN(op->end_of_bound(range_no));
+ }
+ }
+ }
+
+ // Seen with e.g. b = 1 and c > 1
+ if (part[0].bound_type == NdbIndexScanOperation::BoundLE &&
+ part[1].bound_type == NdbIndexScanOperation::BoundGE &&
+ memcmp(part[0].part_ptr, part[1].part_ptr, part_store_len) == 0)
+ {
+ DBUG_PRINT("info", ("replace LE/GE pair by EQ"));
+ part[0].bound_type= NdbIndexScanOperation::BoundEQ;
+ part[1].bound_type= -1;
+ }
+ // Not seen but was in previous version
+ if (part[0].bound_type == NdbIndexScanOperation::BoundEQ &&
+ part[1].bound_type == NdbIndexScanOperation::BoundGE &&
+ memcmp(part[0].part_ptr, part[1].part_ptr, part_store_len) == 0)
+ {
+ DBUG_PRINT("info", ("remove GE from EQ/GE pair"));
+ part[1].bound_type= -1;
+ }
+
+ for (j= 0; j <= 1; j++)
+ {
+ struct part_st &p= part[j];
+ // Set bound if not done with this key
+ if (p.key != NULL)
+ {
+ DBUG_PRINT("info", ("key %d:%d offset: %d length: %d last: %d bound: %d",
+ j, i, tot_len, part_len, p.part_last, p.bound_type));
+ DBUG_DUMP("info", p.part_ptr, part_store_len);
+
+ // Set bound if not cancelled via type -1
+ if (p.bound_type != -1)
+ {
+ const uchar* ptr= p.bound_ptr;
+ uchar buf[256];
+ shrink_varchar(field, ptr, buf);
+ if (op->setBound(i, p.bound_type, ptr))
+ ERR_RETURN(op->getNdbError());
+ }
+ }
+ }
+
+ tot_len+= part_store_len;
+ }
+ DBUG_RETURN(op->end_of_bound(range_no));
+}
+
+/**
+ Start ordered index scan in NDB.
+*/
+
+int ha_ndbcluster::ordered_index_scan(const key_range *start_key,
+ const key_range *end_key,
+ bool sorted, bool descending,
+ uchar* buf, part_id_range *part_spec)
+{
+ int res;
+ bool restart;
+ NdbTransaction *trans= m_active_trans;
+ NdbIndexScanOperation *op;
+
+ DBUG_ENTER("ha_ndbcluster::ordered_index_scan");
+ DBUG_PRINT("enter", ("index: %u, sorted: %d, descending: %d",
+ active_index, sorted, descending));
+ DBUG_PRINT("enter", ("Starting new ordered scan on %s", m_tabname));
+ m_write_op= FALSE;
+
+ // Check that sorted seems to be initialised
+ DBUG_ASSERT(sorted == 0 || sorted == 1);
+
+ if (m_active_cursor == 0)
+ {
+ restart= FALSE;
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ bool need_pk = (lm == NdbOperation::LM_Read);
+ if (!(op= trans->getNdbIndexScanOperation(m_index[active_index].index,
+ m_table)) ||
+ op->readTuples(lm, 0, parallelism, sorted, descending, FALSE, need_pk))
+ ERR_RETURN(trans->getNdbError());
+ if (m_use_partition_function && part_spec != NULL &&
+ part_spec->start_part == part_spec->end_part)
+ op->setPartitionId(part_spec->start_part);
+ m_active_cursor= op;
+ } else {
+ restart= TRUE;
+ op= (NdbIndexScanOperation*)m_active_cursor;
+
+ if (m_use_partition_function && part_spec != NULL &&
+ part_spec->start_part == part_spec->end_part)
+ op->setPartitionId(part_spec->start_part);
+ DBUG_ASSERT(op->getSorted() == sorted);
+ DBUG_ASSERT(op->getLockMode() ==
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type));
+ if (op->reset_bounds(m_force_send))
+ DBUG_RETURN(ndb_err(m_active_trans));
+ }
+
+ {
+ const key_range *keys[2]= { start_key, end_key };
+ res= set_bounds(op, active_index, FALSE, keys);
+ if (res)
+ DBUG_RETURN(res);
+ }
+
+ if (!restart)
+ {
+ if (m_cond && m_cond->generate_scan_filter(op))
+ DBUG_RETURN(ndb_err(trans));
+
+ if ((res= define_read_attrs(buf, op)))
+ {
+ DBUG_RETURN(res);
+ }
+
+ // If table has user defined partitioning
+ // and no primary key, we need to read the partition id
+ // to support ORDER BY queries
+ if (m_use_partition_function &&
+ (table_share->primary_key == MAX_KEY) &&
+ (get_ndb_partition_id(op)))
+ ERR_RETURN(trans->getNdbError());
+ }
+
+ if (execute_no_commit(this,trans,FALSE) != 0)
+ DBUG_RETURN(ndb_err(trans));
+
+ DBUG_RETURN(next_result(buf));
+}
+
+static
+int
+guess_scan_flags(NdbOperation::LockMode lm,
+ const NDBTAB* tab, const MY_BITMAP* readset)
+{
+ int flags= 0;
+ flags|= (lm == NdbOperation::LM_Read) ? NdbScanOperation::SF_KeyInfo : 0;
+ if (tab->checkColumns(0, 0) & 2)
+ {
+ int ret = tab->checkColumns(readset->bitmap, no_bytes_in_map(readset));
+
+ if (ret & 2)
+ { // If disk columns...use disk scan
+ flags |= NdbScanOperation::SF_DiskScan;
+ }
+ else if ((ret & 4) == 0 && (lm == NdbOperation::LM_Exclusive))
+ {
+ // If no mem column is set and exclusive...guess disk scan
+ flags |= NdbScanOperation::SF_DiskScan;
+ }
+ }
+ return flags;
+}
+
+
+/*
+ Unique index scan in NDB (full table scan with scan filter)
+ */
+
+int ha_ndbcluster::unique_index_scan(const KEY* key_info,
+ const uchar *key,
+ uint key_len,
+ uchar *buf)
+{
+ int res;
+ NdbScanOperation *op;
+ NdbTransaction *trans= m_active_trans;
+ part_id_range part_spec;
+
+ DBUG_ENTER("unique_index_scan");
+ DBUG_PRINT("enter", ("Starting new scan on %s", m_tabname));
+
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ int flags= guess_scan_flags(lm, m_table, table->read_set);
+ if (!(op=trans->getNdbScanOperation((const NDBTAB *) m_table)) ||
+ op->readTuples(lm, flags, parallelism))
+ ERR_RETURN(trans->getNdbError());
+ m_active_cursor= op;
+
+ if (m_use_partition_function)
+ {
+ part_spec.start_part= 0;
+ part_spec.end_part= m_part_info->get_tot_partitions() - 1;
+ prune_partition_set(table, &part_spec);
+ DBUG_PRINT("info", ("part_spec.start_part = %u, part_spec.end_part = %u",
+ part_spec.start_part, part_spec.end_part));
+ /*
+ If partition pruning has found no partition in set
+ we can return HA_ERR_END_OF_FILE
+ If partition pruning has found exactly one partition in set
+ we can optimize scan to run towards that partition only.
+ */
+ if (part_spec.start_part > part_spec.end_part)
+ {
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ else if (part_spec.start_part == part_spec.end_part)
+ {
+ /*
+ Only one partition is required to scan, if sorted is required we
+ don't need it any more since output from one ordered partitioned
+ index is always sorted.
+ */
+ m_active_cursor->setPartitionId(part_spec.start_part);
+ }
+ // If table has user defined partitioning
+ // and no primary key, we need to read the partition id
+ // to support ORDER BY queries
+ if ((table_share->primary_key == MAX_KEY) &&
+ (get_ndb_partition_id(op)))
+ ERR_RETURN(trans->getNdbError());
+ }
+ if (!m_cond)
+ m_cond= new ha_ndbcluster_cond;
+ if (!m_cond)
+ {
+ my_errno= HA_ERR_OUT_OF_MEM;
+ DBUG_RETURN(my_errno);
+ }
+ if (m_cond->generate_scan_filter_from_key(op, key_info, key, key_len, buf))
+ DBUG_RETURN(ndb_err(trans));
+ if ((res= define_read_attrs(buf, op)))
+ DBUG_RETURN(res);
+
+ if (execute_no_commit(this,trans,FALSE) != 0)
+ DBUG_RETURN(ndb_err(trans));
+ DBUG_PRINT("exit", ("Scan started successfully"));
+ DBUG_RETURN(next_result(buf));
+}
+
+
+/**
+ Start full table scan in NDB.
+*/
+int ha_ndbcluster::full_table_scan(uchar *buf)
+{
+ int res;
+ NdbScanOperation *op;
+ NdbTransaction *trans= m_active_trans;
+ part_id_range part_spec;
+
+ DBUG_ENTER("full_table_scan");
+ DBUG_PRINT("enter", ("Starting new scan on %s", m_tabname));
+ m_write_op= FALSE;
+
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ int flags= guess_scan_flags(lm, m_table, table->read_set);
+ if (!(op=trans->getNdbScanOperation(m_table)) ||
+ op->readTuples(lm, flags, parallelism))
+ ERR_RETURN(trans->getNdbError());
+ m_active_cursor= op;
+
+ if (m_use_partition_function)
+ {
+ part_spec.start_part= 0;
+ part_spec.end_part= m_part_info->get_tot_partitions() - 1;
+ prune_partition_set(table, &part_spec);
+ DBUG_PRINT("info", ("part_spec.start_part: %u part_spec.end_part: %u",
+ part_spec.start_part, part_spec.end_part));
+ /*
+ If partition pruning has found no partition in set
+ we can return HA_ERR_END_OF_FILE
+ If partition pruning has found exactly one partition in set
+ we can optimize scan to run towards that partition only.
+ */
+ if (part_spec.start_part > part_spec.end_part)
+ {
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ else if (part_spec.start_part == part_spec.end_part)
+ {
+ /*
+ Only one partition is required to scan, if sorted is required we
+ don't need it any more since output from one ordered partitioned
+ index is always sorted.
+ */
+ m_active_cursor->setPartitionId(part_spec.start_part);
+ }
+ // If table has user defined partitioning
+ // and no primary key, we need to read the partition id
+ // to support ORDER BY queries
+ if ((table_share->primary_key == MAX_KEY) &&
+ (get_ndb_partition_id(op)))
+ ERR_RETURN(trans->getNdbError());
+ }
+
+ if (m_cond && m_cond->generate_scan_filter(op))
+ DBUG_RETURN(ndb_err(trans));
+ if ((res= define_read_attrs(buf, op)))
+ DBUG_RETURN(res);
+
+ if (execute_no_commit(this,trans,FALSE) != 0)
+ DBUG_RETURN(ndb_err(trans));
+ DBUG_PRINT("exit", ("Scan started successfully"));
+ DBUG_RETURN(next_result(buf));
+}
+
+int
+ha_ndbcluster::set_auto_inc(Field *field)
+{
+ DBUG_ENTER("ha_ndbcluster::set_auto_inc");
+ Ndb *ndb= get_ndb();
+ bool read_bit= bitmap_is_set(table->read_set, field->field_index);
+ bitmap_set_bit(table->read_set, field->field_index);
+ Uint64 next_val= (Uint64) field->val_int() + 1;
+ if (!read_bit)
+ bitmap_clear_bit(table->read_set, field->field_index);
+#ifndef DBUG_OFF
+ char buff[22];
+ DBUG_PRINT("info",
+ ("Trying to set next auto increment value to %s",
+ llstr(next_val, buff)));
+#endif
+ if (ndb->checkUpdateAutoIncrementValue(m_share->tuple_id_range, next_val))
+ {
+ Ndb_tuple_id_range_guard g(m_share);
+ if (ndb->setAutoIncrementValue(m_table, g.range, next_val, TRUE)
+ == -1)
+ ERR_RETURN(ndb->getNdbError());
+ }
+ DBUG_RETURN(0);
+}
+
+/**
+ Insert one record into NDB.
+*/
+int ha_ndbcluster::write_row(uchar *record)
+{
+ bool has_auto_increment;
+ uint i;
+ NdbTransaction *trans= m_active_trans;
+ NdbOperation *op;
+ int res;
+ THD *thd= table->in_use;
+ longlong func_value= 0;
+ DBUG_ENTER("ha_ndbcluster::write_row");
+
+ m_write_op= TRUE;
+ has_auto_increment= (table->next_number_field && record == table->record[0]);
+ if (table_share->primary_key != MAX_KEY)
+ {
+ /*
+ * Increase any auto_incremented primary key
+ */
+ if (has_auto_increment)
+ {
+ int error;
+
+ m_skip_auto_increment= FALSE;
+ if ((error= update_auto_increment()))
+ DBUG_RETURN(error);
+ m_skip_auto_increment= (insert_id_for_cur_row == 0);
+ }
+ }
+
+ /*
+ * If IGNORE the ignore constraint violations on primary and unique keys
+ */
+ if (!m_use_write && m_ignore_dup_key)
+ {
+ /*
+ compare if expression with that in start_bulk_insert()
+ start_bulk_insert will set parameters to ensure that each
+ write_row is committed individually
+ */
+ int peek_res= peek_indexed_rows(record, NDB_INSERT);
+
+ if (!peek_res)
+ {
+ DBUG_RETURN(HA_ERR_FOUND_DUPP_KEY);
+ }
+ if (peek_res != HA_ERR_KEY_NOT_FOUND)
+ DBUG_RETURN(peek_res);
+ }
+
+ ha_statistic_increment(&SSV::ha_write_count);
+
+ if (!(op= trans->getNdbOperation(m_table)))
+ ERR_RETURN(trans->getNdbError());
+
+ res= (m_use_write) ? op->writeTuple() :op->insertTuple();
+ if (res != 0)
+ ERR_RETURN(trans->getNdbError());
+
+ if (m_use_partition_function)
+ {
+ uint32 part_id;
+ int error;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ error= m_part_info->get_partition_id(m_part_info, &part_id, &func_value);
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+ if (error)
+ {
+ m_part_info->err_value= func_value;
+ DBUG_RETURN(error);
+ }
+ op->setPartitionId(part_id);
+ }
+
+ if (table_share->primary_key == MAX_KEY)
+ {
+ // Table has hidden primary key
+ Ndb *ndb= get_ndb();
+ Uint64 auto_value;
+ uint retries= NDB_AUTO_INCREMENT_RETRIES;
+ int retry_sleep= 30; /* 30 milliseconds, transaction */
+ for (;;)
+ {
+ Ndb_tuple_id_range_guard g(m_share);
+ if (ndb->getAutoIncrementValue(m_table, g.range, auto_value, 1) == -1)
+ {
+ if (--retries &&
+ ndb->getNdbError().status == NdbError::TemporaryError)
+ {
+ my_sleep(retry_sleep);
+ continue;
+ }
+ ERR_RETURN(ndb->getNdbError());
+ }
+ break;
+ }
+ if (set_hidden_key(op, table_share->fields, (const uchar*)&auto_value))
+ ERR_RETURN(op->getNdbError());
+ }
+ else
+ {
+ int error;
+ if ((error= set_primary_key_from_record(op, record)))
+ DBUG_RETURN(error);
+ }
+
+ // Set non-key attribute(s)
+ bool set_blob_value= FALSE;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ for (i= 0; i < table_share->fields; i++)
+ {
+ Field *field= table->field[i];
+ if (!(field->flags & PRI_KEY_FLAG) &&
+ (bitmap_is_set(table->write_set, i) || !m_use_write) &&
+ set_ndb_value(op, field, i, record-table->record[0], &set_blob_value))
+ {
+ m_skip_auto_increment= TRUE;
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+ ERR_RETURN(op->getNdbError());
+ }
+ }
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+
+ if (m_use_partition_function)
+ {
+ /*
+ We need to set the value of the partition function value in
+ NDB since the NDB kernel doesn't have easy access to the function
+ to calculate the value.
+ */
+ if (func_value >= INT_MAX32)
+ func_value= INT_MAX32;
+ uint32 part_func_value= (uint32)func_value;
+ uint no_fields= table_share->fields;
+ if (table_share->primary_key == MAX_KEY)
+ no_fields++;
+ op->setValue(no_fields, part_func_value);
+ }
+
+ if (unlikely(m_slow_path))
+ {
+ /*
+ ignore TNTO_NO_LOGGING for slave thd. It is used to indicate
+ log-slave-updates option. This is instead handled in the
+ injector thread, by looking explicitly at the
+ opt_log_slave_updates flag.
+ */
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ if (thd->slave_thread)
+ op->setAnyValue(thd->server_id);
+ else if (thd_ndb->trans_options & TNTO_NO_LOGGING)
+ op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING);
+ }
+ m_rows_changed++;
+
+ /*
+ Execute write operation
+ NOTE When doing inserts with many values in
+ each INSERT statement it should not be necessary
+ to NoCommit the transaction between each row.
+ Find out how this is detected!
+ */
+ m_rows_inserted++;
+ no_uncommitted_rows_update(1);
+ m_bulk_insert_not_flushed= TRUE;
+ if ((m_rows_to_insert == (ha_rows) 1) ||
+ ((m_rows_inserted % m_bulk_insert_rows) == 0) ||
+ m_primary_key_update ||
+ set_blob_value)
+ {
+ // Send rows to NDB
+ DBUG_PRINT("info", ("Sending inserts to NDB, "\
+ "rows_inserted: %d bulk_insert_rows: %d",
+ (int)m_rows_inserted, (int)m_bulk_insert_rows));
+
+ m_bulk_insert_not_flushed= FALSE;
+ if (m_transaction_on)
+ {
+ if (execute_no_commit(this,trans,FALSE) != 0)
+ {
+ m_skip_auto_increment= TRUE;
+ no_uncommitted_rows_execute_failure();
+ DBUG_RETURN(ndb_err(trans));
+ }
+ }
+ else
+ {
+ if (execute_commit(this,trans) != 0)
+ {
+ m_skip_auto_increment= TRUE;
+ no_uncommitted_rows_execute_failure();
+ DBUG_RETURN(ndb_err(trans));
+ }
+ if (trans->restart() != 0)
+ {
+ DBUG_ASSERT(0);
+ DBUG_RETURN(-1);
+ }
+ }
+ }
+ if ((has_auto_increment) && (m_skip_auto_increment))
+ {
+ int ret_val;
+ if ((ret_val= set_auto_inc(table->next_number_field)))
+ {
+ DBUG_RETURN(ret_val);
+ }
+ }
+ m_skip_auto_increment= TRUE;
+
+ DBUG_PRINT("exit",("ok"));
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Compare if a key in a row has changed.
+*/
+
+int ha_ndbcluster::key_cmp(uint keynr, const uchar * old_row,
+ const uchar * new_row)
+{
+ KEY_PART_INFO *key_part=table->key_info[keynr].key_part;
+ KEY_PART_INFO *end=key_part+table->key_info[keynr].key_parts;
+
+ for (; key_part != end ; key_part++)
+ {
+ if (key_part->null_bit)
+ {
+ if ((old_row[key_part->null_offset] & key_part->null_bit) !=
+ (new_row[key_part->null_offset] & key_part->null_bit))
+ return 1;
+ }
+ if (key_part->key_part_flag & (HA_BLOB_PART | HA_VAR_LENGTH_PART))
+ {
+
+ if (key_part->field->cmp_binary((old_row + key_part->offset),
+ (new_row + key_part->offset),
+ (ulong) key_part->length))
+ return 1;
+ }
+ else
+ {
+ if (memcmp(old_row+key_part->offset, new_row+key_part->offset,
+ key_part->length))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ Update one record in NDB using primary key.
+*/
+
+int ha_ndbcluster::update_row(const uchar *old_data, uchar *new_data)
+{
+ THD *thd= table->in_use;
+ NdbTransaction *trans= m_active_trans;
+ NdbScanOperation* cursor= m_active_cursor;
+ NdbOperation *op;
+ uint i;
+ uint32 old_part_id= 0, new_part_id= 0;
+ int error;
+ longlong func_value;
+ bool pk_update= (table_share->primary_key != MAX_KEY &&
+ key_cmp(table_share->primary_key, old_data, new_data));
+ DBUG_ENTER("update_row");
+ m_write_op= TRUE;
+
+ /*
+ * If IGNORE the ignore constraint violations on primary and unique keys,
+ * but check that it is not part of INSERT ... ON DUPLICATE KEY UPDATE
+ */
+ if (m_ignore_dup_key && (thd->lex->sql_command == SQLCOM_UPDATE ||
+ thd->lex->sql_command == SQLCOM_UPDATE_MULTI))
+ {
+ NDB_WRITE_OP write_op= (pk_update) ? NDB_PK_UPDATE : NDB_UPDATE;
+ int peek_res= peek_indexed_rows(new_data, write_op);
+
+ if (!peek_res)
+ {
+ DBUG_RETURN(HA_ERR_FOUND_DUPP_KEY);
+ }
+ if (peek_res != HA_ERR_KEY_NOT_FOUND)
+ DBUG_RETURN(peek_res);
+ }
+
+ ha_statistic_increment(&SSV::ha_update_count);
+
+ if (m_use_partition_function &&
+ (error= get_parts_for_update(old_data, new_data, table->record[0],
+ m_part_info, &old_part_id, &new_part_id,
+ &func_value)))
+ {
+ m_part_info->err_value= func_value;
+ DBUG_RETURN(error);
+ }
+
+ /*
+ * Check for update of primary key or partition change
+ * for special handling
+ */
+ if (pk_update || old_part_id != new_part_id)
+ {
+ int read_res, insert_res, delete_res, undo_res;
+
+ DBUG_PRINT("info", ("primary key update or partition change, "
+ "doing read+delete+insert"));
+ // Get all old fields, since we optimize away fields not in query
+ read_res= complemented_read(old_data, new_data, old_part_id);
+ if (read_res)
+ {
+ DBUG_PRINT("info", ("read failed"));
+ DBUG_RETURN(read_res);
+ }
+ // Delete old row
+ m_primary_key_update= TRUE;
+ delete_res= delete_row(old_data);
+ m_primary_key_update= FALSE;
+ if (delete_res)
+ {
+ DBUG_PRINT("info", ("delete failed"));
+ DBUG_RETURN(delete_res);
+ }
+ // Insert new row
+ DBUG_PRINT("info", ("delete succeded"));
+ m_primary_key_update= TRUE;
+ /*
+ If we are updating a primary key with auto_increment
+ then we need to update the auto_increment counter
+ */
+ if (table->found_next_number_field &&
+ bitmap_is_set(table->write_set,
+ table->found_next_number_field->field_index) &&
+ (error= set_auto_inc(table->found_next_number_field)))
+ {
+ DBUG_RETURN(error);
+ }
+ insert_res= write_row(new_data);
+ m_primary_key_update= FALSE;
+ if (insert_res)
+ {
+ DBUG_PRINT("info", ("insert failed"));
+ if (trans->commitStatus() == NdbConnection::Started)
+ {
+ // Undo delete_row(old_data)
+ m_primary_key_update= TRUE;
+ undo_res= write_row((uchar *)old_data);
+ if (undo_res)
+ push_warning(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ undo_res,
+ "NDB failed undoing delete at primary key update");
+ m_primary_key_update= FALSE;
+ }
+ DBUG_RETURN(insert_res);
+ }
+ DBUG_PRINT("info", ("delete+insert succeeded"));
+ DBUG_RETURN(0);
+ }
+ /*
+ If we are updating a unique key with auto_increment
+ then we need to update the auto_increment counter
+ */
+ if (table->found_next_number_field &&
+ bitmap_is_set(table->write_set,
+ table->found_next_number_field->field_index) &&
+ (error= set_auto_inc(table->found_next_number_field)))
+ {
+ DBUG_RETURN(error);
+ }
+ if (cursor)
+ {
+ /*
+ We are scanning records and want to update the record
+ that was just found, call updateTuple on the cursor
+ to take over the lock to a new update operation
+ And thus setting the primary key of the record from
+ the active record in cursor
+ */
+ DBUG_PRINT("info", ("Calling updateTuple on cursor"));
+ if (!(op= cursor->updateCurrentTuple()))
+ ERR_RETURN(trans->getNdbError());
+ m_lock_tuple= FALSE;
+ m_ops_pending++;
+ if (uses_blob_value())
+ m_blobs_pending= TRUE;
+ if (m_use_partition_function)
+ cursor->setPartitionId(new_part_id);
+ }
+ else
+ {
+ if (!(op= trans->getNdbOperation(m_table)) ||
+ op->updateTuple() != 0)
+ ERR_RETURN(trans->getNdbError());
+
+ if (m_use_partition_function)
+ op->setPartitionId(new_part_id);
+ if (table_share->primary_key == MAX_KEY)
+ {
+ // This table has no primary key, use "hidden" primary key
+ DBUG_PRINT("info", ("Using hidden key"));
+
+ // Require that the PK for this record has previously been
+ // read into m_ref
+ DBUG_DUMP("key", m_ref, NDB_HIDDEN_PRIMARY_KEY_LENGTH);
+
+ if (set_hidden_key(op, table->s->fields, m_ref))
+ ERR_RETURN(op->getNdbError());
+ }
+ else
+ {
+ int res;
+ if ((res= set_primary_key_from_record(op, old_data)))
+ DBUG_RETURN(res);
+ }
+ }
+
+ m_rows_changed++;
+
+ // Set non-key attribute(s)
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ for (i= 0; i < table_share->fields; i++)
+ {
+ Field *field= table->field[i];
+ if (bitmap_is_set(table->write_set, i) &&
+ (!(field->flags & PRI_KEY_FLAG)) &&
+ set_ndb_value(op, field, i, new_data - table->record[0]))
+ {
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+ ERR_RETURN(op->getNdbError());
+ }
+ }
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+
+ if (m_use_partition_function)
+ {
+ if (func_value >= INT_MAX32)
+ func_value= INT_MAX32;
+ uint32 part_func_value= (uint32)func_value;
+ uint no_fields= table_share->fields;
+ if (table_share->primary_key == MAX_KEY)
+ no_fields++;
+ op->setValue(no_fields, part_func_value);
+ }
+
+ if (unlikely(m_slow_path))
+ {
+ /*
+ ignore TNTO_NO_LOGGING for slave thd. It is used to indicate
+ log-slave-updates option. This is instead handled in the
+ injector thread, by looking explicitly at the
+ opt_log_slave_updates flag.
+ */
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ if (thd->slave_thread)
+ op->setAnyValue(thd->server_id);
+ else if (thd_ndb->trans_options & TNTO_NO_LOGGING)
+ op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING);
+ }
+ /*
+ Execute update operation if we are not doing a scan for update
+ and there exist UPDATE AFTER triggers
+ */
+
+ if ((!cursor || m_update_cannot_batch) &&
+ execute_no_commit(this,trans,false) != 0) {
+ no_uncommitted_rows_execute_failure();
+ DBUG_RETURN(ndb_err(trans));
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Delete one record from NDB, using primary key .
+*/
+
+int ha_ndbcluster::delete_row(const uchar *record)
+{
+ THD *thd= table->in_use;
+ NdbTransaction *trans= m_active_trans;
+ NdbScanOperation* cursor= m_active_cursor;
+ NdbOperation *op;
+ uint32 part_id;
+ int error;
+ DBUG_ENTER("delete_row");
+ m_write_op= TRUE;
+
+ ha_statistic_increment(&SSV::ha_delete_count);
+ m_rows_changed++;
+
+ if (m_use_partition_function &&
+ (error= get_part_for_delete(record, table->record[0], m_part_info,
+ &part_id)))
+ {
+ DBUG_RETURN(error);
+ }
+
+ if (cursor)
+ {
+ /*
+ We are scanning records and want to delete the record
+ that was just found, call deleteTuple on the cursor
+ to take over the lock to a new delete operation
+ And thus setting the primary key of the record from
+ the active record in cursor
+ */
+ DBUG_PRINT("info", ("Calling deleteTuple on cursor"));
+ if (cursor->deleteCurrentTuple() != 0)
+ ERR_RETURN(trans->getNdbError());
+ m_lock_tuple= FALSE;
+ m_ops_pending++;
+
+ if (m_use_partition_function)
+ cursor->setPartitionId(part_id);
+
+ no_uncommitted_rows_update(-1);
+
+ if (unlikely(m_slow_path))
+ {
+ /*
+ ignore TNTO_NO_LOGGING for slave thd. It is used to indicate
+ log-slave-updates option. This is instead handled in the
+ injector thread, by looking explicitly at the
+ opt_log_slave_updates flag.
+ */
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ if (thd->slave_thread)
+ ((NdbOperation *)trans->getLastDefinedOperation())->
+ setAnyValue(thd->server_id);
+ else if (thd_ndb->trans_options & TNTO_NO_LOGGING)
+ ((NdbOperation *)trans->getLastDefinedOperation())->
+ setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING);
+ }
+ if (!(m_primary_key_update || m_delete_cannot_batch))
+ // If deleting from cursor, NoCommit will be handled in next_result
+ DBUG_RETURN(0);
+ }
+ else
+ {
+
+ if (!(op=trans->getNdbOperation(m_table)) ||
+ op->deleteTuple() != 0)
+ ERR_RETURN(trans->getNdbError());
+
+ if (m_use_partition_function)
+ op->setPartitionId(part_id);
+
+ no_uncommitted_rows_update(-1);
+
+ if (table_share->primary_key == MAX_KEY)
+ {
+ // This table has no primary key, use "hidden" primary key
+ DBUG_PRINT("info", ("Using hidden key"));
+
+ if (set_hidden_key(op, table->s->fields, m_ref))
+ ERR_RETURN(op->getNdbError());
+ }
+ else
+ {
+ if ((error= set_primary_key_from_record(op, record)))
+ DBUG_RETURN(error);
+ }
+
+ if (unlikely(m_slow_path))
+ {
+ /*
+ ignore TNTO_NO_LOGGING for slave thd. It is used to indicate
+ log-slave-updates option. This is instead handled in the
+ injector thread, by looking explicitly at the
+ opt_log_slave_updates flag.
+ */
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ if (thd->slave_thread)
+ op->setAnyValue(thd->server_id);
+ else if (thd_ndb->trans_options & TNTO_NO_LOGGING)
+ op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING);
+ }
+ }
+
+ // Execute delete operation
+ if (execute_no_commit(this,trans,FALSE) != 0) {
+ no_uncommitted_rows_execute_failure();
+ DBUG_RETURN(ndb_err(trans));
+ }
+ DBUG_RETURN(0);
+}
+
+/**
+ Unpack a record read from NDB.
+
+ @param buf Buffer to store read row
+
+ @note
+ The data for each row is read directly into the
+ destination buffer. This function is primarily
+ called in order to check if any fields should be
+ set to null.
+*/
+
+void ndb_unpack_record(TABLE *table, NdbValue *value,
+ MY_BITMAP *defined, uchar *buf)
+{
+ Field **p_field= table->field, *field= *p_field;
+ my_ptrdiff_t row_offset= (my_ptrdiff_t) (buf - table->record[0]);
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->write_set);
+ DBUG_ENTER("ndb_unpack_record");
+
+ /*
+ Set the filler bits of the null byte, since they are
+ not touched in the code below.
+
+ The filler bits are the MSBs in the last null byte
+ */
+ if (table->s->null_bytes > 0)
+ buf[table->s->null_bytes - 1]|= 256U - (1U <<
+ table->s->last_null_bit_pos);
+ /*
+ Set null flag(s)
+ */
+ for ( ; field;
+ p_field++, value++, field= *p_field)
+ {
+ field->set_notnull(row_offset);
+ if ((*value).ptr)
+ {
+ if (!(field->flags & BLOB_FLAG))
+ {
+ int is_null= (*value).rec->isNULL();
+ if (is_null)
+ {
+ if (is_null > 0)
+ {
+ DBUG_PRINT("info",("[%u] NULL",
+ (*value).rec->getColumn()->getColumnNo()));
+ field->set_null(row_offset);
+ }
+ else
+ {
+ DBUG_PRINT("info",("[%u] UNDEFINED",
+ (*value).rec->getColumn()->getColumnNo()));
+ bitmap_clear_bit(defined,
+ (*value).rec->getColumn()->getColumnNo());
+ }
+ }
+ else if (field->type() == MYSQL_TYPE_BIT)
+ {
+ Field_bit *field_bit= static_cast<Field_bit*>(field);
+
+ /*
+ Move internal field pointer to point to 'buf'. Calling
+ the correct member function directly since we know the
+ type of the object.
+ */
+ field_bit->Field_bit::move_field_offset(row_offset);
+ if (field->pack_length() < 5)
+ {
+ DBUG_PRINT("info", ("bit field H'%.8X",
+ (*value).rec->u_32_value()));
+ field_bit->Field_bit::store((longlong) (*value).rec->u_32_value(),
+ FALSE);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("bit field H'%.8X%.8X",
+ *(Uint32 *)(*value).rec->aRef(),
+ *((Uint32 *)(*value).rec->aRef()+1)));
+#ifdef WORDS_BIGENDIAN
+ /* lsw is stored first */
+ Uint32 *buf= (Uint32 *)(*value).rec->aRef();
+ field_bit->Field_bit::store((((longlong)*buf)
+ & 0x000000000FFFFFFFFLL)
+ |
+ ((((longlong)*(buf+1)) << 32)
+ & 0xFFFFFFFF00000000LL),
+ TRUE);
+#else
+ field_bit->Field_bit::store((longlong)
+ (*value).rec->u_64_value(), TRUE);
+#endif
+ }
+ /*
+ Move back internal field pointer to point to original
+ value (usually record[0]).
+ */
+ field_bit->Field_bit::move_field_offset(-row_offset);
+ DBUG_PRINT("info",("[%u] SET",
+ (*value).rec->getColumn()->getColumnNo()));
+ DBUG_DUMP("info", field->ptr, field->pack_length());
+ }
+ else
+ {
+ DBUG_PRINT("info",("[%u] SET",
+ (*value).rec->getColumn()->getColumnNo()));
+ DBUG_DUMP("info", field->ptr, field->pack_length());
+ }
+ }
+ else
+ {
+ NdbBlob *ndb_blob= (*value).blob;
+ uint col_no = ndb_blob->getColumn()->getColumnNo();
+ int isNull;
+ ndb_blob->getDefined(isNull);
+ if (isNull == 1)
+ {
+ DBUG_PRINT("info",("[%u] NULL", col_no));
+ field->set_null(row_offset);
+ }
+ else if (isNull == -1)
+ {
+ DBUG_PRINT("info",("[%u] UNDEFINED", col_no));
+ bitmap_clear_bit(defined, col_no);
+ }
+ else
+ {
+#ifndef DBUG_OFF
+ // pointer vas set in get_ndb_blobs_value
+ Field_blob *field_blob= (Field_blob*)field;
+ uchar *ptr;
+ field_blob->get_ptr(&ptr, row_offset);
+ uint32 len= field_blob->get_length(row_offset);
+ DBUG_PRINT("info",("[%u] SET ptr: 0x%lx len: %u",
+ col_no, (long) ptr, len));
+#endif
+ }
+ }
+ }
+ }
+ dbug_tmp_restore_column_map(table->write_set, old_map);
+ DBUG_VOID_RETURN;
+}
+
+void ha_ndbcluster::unpack_record(uchar *buf)
+{
+ ndb_unpack_record(table, m_value, 0, buf);
+#ifndef DBUG_OFF
+ // Read and print all values that was fetched
+ if (table_share->primary_key == MAX_KEY)
+ {
+ // Table with hidden primary key
+ int hidden_no= table_share->fields;
+ const NDBTAB *tab= m_table;
+ char buff[22];
+ const NDBCOL *hidden_col= tab->getColumn(hidden_no);
+ const NdbRecAttr* rec= m_value[hidden_no].rec;
+ DBUG_ASSERT(rec);
+ DBUG_PRINT("hidden", ("%d: %s \"%s\"", hidden_no,
+ hidden_col->getName(),
+ llstr(rec->u_64_value(), buff)));
+ }
+ //DBUG_EXECUTE("value", print_results(););
+#endif
+}
+
+/**
+ Utility function to print/dump the fetched field.
+
+ To avoid unnecessary work, wrap in DBUG_EXECUTE as in:
+ DBUG_EXECUTE("value", print_results(););
+*/
+
+void ha_ndbcluster::print_results()
+{
+ DBUG_ENTER("print_results");
+
+#ifndef DBUG_OFF
+
+ char buf_type[MAX_FIELD_WIDTH], buf_val[MAX_FIELD_WIDTH];
+ String type(buf_type, sizeof(buf_type), &my_charset_bin);
+ String val(buf_val, sizeof(buf_val), &my_charset_bin);
+ for (uint f= 0; f < table_share->fields; f++)
+ {
+ /* Use DBUG_PRINT since DBUG_FILE cannot be filtered out */
+ char buf[2000];
+ Field *field;
+ void* ptr;
+ NdbValue value;
+
+ buf[0]= 0;
+ field= table->field[f];
+ if (!(value= m_value[f]).ptr)
+ {
+ strmov(buf, "not read");
+ goto print_value;
+ }
+
+ ptr= field->ptr;
+
+ if (! (field->flags & BLOB_FLAG))
+ {
+ if (value.rec->isNULL())
+ {
+ strmov(buf, "NULL");
+ goto print_value;
+ }
+ type.length(0);
+ val.length(0);
+ field->sql_type(type);
+ field->val_str(&val);
+ my_snprintf(buf, sizeof(buf), "%s %s", type.c_ptr(), val.c_ptr());
+ }
+ else
+ {
+ NdbBlob *ndb_blob= value.blob;
+ bool isNull= TRUE;
+ ndb_blob->getNull(isNull);
+ if (isNull)
+ strmov(buf, "NULL");
+ }
+
+print_value:
+ DBUG_PRINT("value", ("%u,%s: %s", f, field->field_name, buf));
+ }
+#endif
+ DBUG_VOID_RETURN;
+}
+
+
+int ha_ndbcluster::index_init(uint index, bool sorted)
+{
+ DBUG_ENTER("ha_ndbcluster::index_init");
+ DBUG_PRINT("enter", ("index: %u sorted: %d", index, sorted));
+ active_index= index;
+ m_sorted= sorted;
+ /*
+ Locks are are explicitly released in scan
+ unless m_lock.type == TL_READ_HIGH_PRIORITY
+ and no sub-sequent call to unlock_row()
+ */
+ m_lock_tuple= FALSE;
+ DBUG_RETURN(0);
+}
+
+
+int ha_ndbcluster::index_end()
+{
+ DBUG_ENTER("ha_ndbcluster::index_end");
+ DBUG_RETURN(close_scan());
+}
+
+/**
+ Check if key contains null.
+*/
+static
+int
+check_null_in_key(const KEY* key_info, const uchar *key, uint key_len)
+{
+ KEY_PART_INFO *curr_part, *end_part;
+ const uchar* end_ptr= key + key_len;
+ curr_part= key_info->key_part;
+ end_part= curr_part + key_info->user_defined_key_parts;
+
+ for (; curr_part != end_part && key < end_ptr; curr_part++)
+ {
+ if (curr_part->null_bit && *key)
+ return 1;
+
+ key += curr_part->store_length;
+ }
+ return 0;
+}
+
+int ha_ndbcluster::index_read(uchar *buf,
+ const uchar *key, uint key_len,
+ enum ha_rkey_function find_flag)
+{
+ key_range start_key;
+ bool descending= FALSE;
+ int rc;
+ DBUG_ENTER("ha_ndbcluster::index_read");
+ DBUG_PRINT("enter", ("active_index: %u, key_len: %u, find_flag: %d",
+ active_index, key_len, find_flag));
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+
+ start_key.key= key;
+ start_key.length= key_len;
+ start_key.flag= find_flag;
+ descending= FALSE;
+ switch (find_flag) {
+ case HA_READ_KEY_OR_PREV:
+ case HA_READ_BEFORE_KEY:
+ case HA_READ_PREFIX_LAST:
+ case HA_READ_PREFIX_LAST_OR_PREV:
+ descending= TRUE;
+ break;
+ default:
+ break;
+ }
+ rc= read_range_first_to_buf(&start_key, 0, descending,
+ m_sorted, buf);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+
+int ha_ndbcluster::index_next(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_ndbcluster::index_next");
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ ha_statistic_increment(&SSV::ha_read_next_count);
+ rc= next_result(buf);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+
+int ha_ndbcluster::index_prev(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_ndbcluster::index_prev");
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ ha_statistic_increment(&SSV::ha_read_prev_count);
+ rc= next_result(buf);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+
+int ha_ndbcluster::index_first(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_ndbcluster::index_first");
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ ha_statistic_increment(&SSV::ha_read_first_count);
+ // Start the ordered index scan and fetch the first row
+
+ // Only HA_READ_ORDER indexes get called by index_first
+ rc= ordered_index_scan(0, 0, TRUE, FALSE, buf, NULL);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+
+int ha_ndbcluster::index_last(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_ndbcluster::index_last");
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ ha_statistic_increment(&SSV::ha_read_last_count);
+ rc= ordered_index_scan(0, 0, TRUE, TRUE, buf, NULL);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+int ha_ndbcluster::index_read_last(uchar * buf, const uchar * key, uint key_len)
+{
+ DBUG_ENTER("ha_ndbcluster::index_read_last");
+ DBUG_RETURN(index_read(buf, key, key_len, HA_READ_PREFIX_LAST));
+}
+
+int ha_ndbcluster::read_range_first_to_buf(const key_range *start_key,
+ const key_range *end_key,
+ bool desc, bool sorted,
+ uchar* buf)
+{
+ part_id_range part_spec;
+ ndb_index_type type= get_index_type(active_index);
+ const KEY* key_info= table->key_info+active_index;
+ int error;
+ DBUG_ENTER("ha_ndbcluster::read_range_first_to_buf");
+ DBUG_PRINT("info", ("desc: %d, sorted: %d", desc, sorted));
+
+ if (m_use_partition_function)
+ {
+ get_partition_set(table, buf, active_index, start_key, &part_spec);
+ DBUG_PRINT("info", ("part_spec.start_part: %u part_spec.end_part: %u",
+ part_spec.start_part, part_spec.end_part));
+ /*
+ If partition pruning has found no partition in set
+ we can return HA_ERR_END_OF_FILE
+ If partition pruning has found exactly one partition in set
+ we can optimize scan to run towards that partition only.
+ */
+ if (part_spec.start_part > part_spec.end_part)
+ {
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ else if (part_spec.start_part == part_spec.end_part)
+ {
+ /*
+ Only one partition is required to scan, if sorted is required we
+ don't need it any more since output from one ordered partitioned
+ index is always sorted.
+ */
+ sorted= FALSE;
+ }
+ }
+
+ m_write_op= FALSE;
+ switch (type){
+ case PRIMARY_KEY_ORDERED_INDEX:
+ case PRIMARY_KEY_INDEX:
+ if (start_key &&
+ start_key->length == key_info->key_length &&
+ start_key->flag == HA_READ_KEY_EXACT)
+ {
+ if (m_active_cursor && (error= close_scan()))
+ DBUG_RETURN(error);
+ error= pk_read(start_key->key, start_key->length, buf,
+ part_spec.start_part);
+ DBUG_RETURN(error == HA_ERR_KEY_NOT_FOUND ? HA_ERR_END_OF_FILE : error);
+ }
+ break;
+ case UNIQUE_ORDERED_INDEX:
+ case UNIQUE_INDEX:
+ if (start_key && start_key->length == key_info->key_length &&
+ start_key->flag == HA_READ_KEY_EXACT &&
+ !check_null_in_key(key_info, start_key->key, start_key->length))
+ {
+ if (m_active_cursor && (error= close_scan()))
+ DBUG_RETURN(error);
+
+ error= unique_index_read(start_key->key, start_key->length, buf);
+ DBUG_RETURN(error == HA_ERR_KEY_NOT_FOUND ? HA_ERR_END_OF_FILE : error);
+ }
+ else if (type == UNIQUE_INDEX)
+ DBUG_RETURN(unique_index_scan(key_info,
+ start_key->key,
+ start_key->length,
+ buf));
+ break;
+ default:
+ break;
+ }
+ // Start the ordered index scan and fetch the first row
+ DBUG_RETURN(ordered_index_scan(start_key, end_key, sorted, desc, buf,
+ &part_spec));
+}
+
+int ha_ndbcluster::read_range_first(const key_range *start_key,
+ const key_range *end_key,
+ bool eq_r, bool sorted)
+{
+ int rc;
+ uchar* buf= table->record[0];
+ DBUG_ENTER("ha_ndbcluster::read_range_first");
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ rc= read_range_first_to_buf(start_key, end_key, FALSE,
+ sorted, buf);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+int ha_ndbcluster::read_range_next()
+{
+ int rc;
+ DBUG_ENTER("ha_ndbcluster::read_range_next");
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ rc= next_result(table->record[0]);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+
+int ha_ndbcluster::rnd_init(bool scan)
+{
+ NdbScanOperation *cursor= m_active_cursor;
+ DBUG_ENTER("rnd_init");
+ DBUG_PRINT("enter", ("scan: %d", scan));
+ // Check if scan is to be restarted
+ if (cursor)
+ {
+ if (!scan)
+ DBUG_RETURN(1);
+ if (cursor->restart(m_force_send) != 0)
+ {
+ DBUG_ASSERT(0);
+ DBUG_RETURN(-1);
+ }
+ }
+ index_init(table_share->primary_key, 0);
+ DBUG_RETURN(0);
+}
+
+int ha_ndbcluster::close_scan()
+{
+ NdbTransaction *trans= m_active_trans;
+ DBUG_ENTER("close_scan");
+
+ m_multi_cursor= 0;
+ if (!m_active_cursor && !m_multi_cursor)
+ DBUG_RETURN(0);
+
+ NdbScanOperation *cursor= m_active_cursor ? m_active_cursor : m_multi_cursor;
+
+ if (m_lock_tuple)
+ {
+ /*
+ Lock level m_lock.type either TL_WRITE_ALLOW_WRITE
+ (SELECT FOR UPDATE) or TL_READ_WITH_SHARED_LOCKS (SELECT
+ LOCK WITH SHARE MODE) and row was not explictly unlocked
+ with unlock_row() call
+ */
+ NdbOperation *op;
+ // Lock row
+ DBUG_PRINT("info", ("Keeping lock on scanned row"));
+
+ if (!(op= cursor->lockCurrentTuple()))
+ {
+ m_lock_tuple= FALSE;
+ ERR_RETURN(trans->getNdbError());
+ }
+ m_ops_pending++;
+ }
+ m_lock_tuple= FALSE;
+ if (m_ops_pending)
+ {
+ /*
+ Take over any pending transactions to the
+ deleteing/updating transaction before closing the scan
+ */
+ DBUG_PRINT("info", ("ops_pending: %ld", (long) m_ops_pending));
+ if (execute_no_commit(this,trans,FALSE) != 0) {
+ no_uncommitted_rows_execute_failure();
+ DBUG_RETURN(ndb_err(trans));
+ }
+ m_ops_pending= 0;
+ }
+
+ cursor->close(m_force_send, TRUE);
+ m_active_cursor= m_multi_cursor= NULL;
+ DBUG_RETURN(0);
+}
+
+int ha_ndbcluster::rnd_end()
+{
+ DBUG_ENTER("rnd_end");
+ DBUG_RETURN(close_scan());
+}
+
+
+int ha_ndbcluster::rnd_next(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("rnd_next");
+ MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str,
+ TRUE);
+ ha_statistic_increment(&SSV::ha_read_rnd_next_count);
+
+ if (!m_active_cursor)
+ rc= full_table_scan(buf);
+ else
+ rc= next_result(buf);
+ MYSQL_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ An "interesting" record has been found and it's pk
+ retrieved by calling position. Now it's time to read
+ the record from db once again.
+*/
+
+int ha_ndbcluster::rnd_pos(uchar *buf, uchar *pos)
+{
+ int rc;
+ DBUG_ENTER("rnd_pos");
+ MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str,
+ FALSE);
+ ha_statistic_increment(&SSV::ha_read_rnd_count);
+ // The primary key for the record is stored in pos
+ // Perform a pk_read using primary key "index"
+ {
+ part_id_range part_spec;
+ uint key_length= ref_length;
+ if (m_use_partition_function)
+ {
+ if (table_share->primary_key == MAX_KEY)
+ {
+ /*
+ The partition id has been fetched from ndb
+ and has been stored directly after the hidden key
+ */
+ DBUG_DUMP("key+part", pos, key_length);
+ key_length= ref_length - sizeof(m_part_id);
+ part_spec.start_part= part_spec.end_part= *(uint32 *)(pos + key_length);
+ }
+ else
+ {
+ key_range key_spec;
+ KEY *key_info= table->key_info + table_share->primary_key;
+ key_spec.key= pos;
+ key_spec.length= key_length;
+ key_spec.flag= HA_READ_KEY_EXACT;
+ get_full_part_id_from_key(table, buf, key_info,
+ &key_spec, &part_spec);
+ DBUG_ASSERT(part_spec.start_part == part_spec.end_part);
+ }
+ DBUG_PRINT("info", ("partition id %u", part_spec.start_part));
+ }
+ DBUG_DUMP("key", pos, key_length);
+ rc= pk_read(pos, key_length, buf, part_spec.start_part);
+ MYSQL_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+ }
+}
+
+
+/**
+ Store the primary key of this record in ref
+ variable, so that the row can be retrieved again later
+ using "reference" in rnd_pos.
+*/
+
+void ha_ndbcluster::position(const uchar *record)
+{
+ KEY *key_info;
+ KEY_PART_INFO *key_part;
+ KEY_PART_INFO *end;
+ uchar *buff;
+ uint key_length;
+
+ DBUG_ENTER("position");
+
+ if (table_share->primary_key != MAX_KEY)
+ {
+ key_length= ref_length;
+ key_info= table->key_info + table_share->primary_key;
+ key_part= key_info->key_part;
+ end= key_part + key_info->user_defined_key_parts;
+ buff= ref;
+
+ for (; key_part != end; key_part++)
+ {
+ if (key_part->null_bit) {
+ /* Store 0 if the key part is a NULL part */
+ if (record[key_part->null_offset]
+ & key_part->null_bit) {
+ *buff++= 1;
+ continue;
+ }
+ *buff++= 0;
+ }
+
+ size_t len = key_part->length;
+ const uchar * ptr = record + key_part->offset;
+ Field *field = key_part->field;
+ if (field->type() == MYSQL_TYPE_VARCHAR)
+ {
+ if (((Field_varstring*)field)->length_bytes == 1)
+ {
+ /**
+ * Keys always use 2 bytes length
+ */
+ buff[0] = ptr[0];
+ buff[1] = 0;
+ memcpy(buff+2, ptr + 1, len);
+ }
+ else
+ {
+ memcpy(buff, ptr, len + 2);
+ }
+ len += 2;
+ }
+ else
+ {
+ memcpy(buff, ptr, len);
+ }
+ buff += len;
+ }
+ }
+ else
+ {
+ // No primary key, get hidden key
+ DBUG_PRINT("info", ("Getting hidden key"));
+ // If table has user defined partition save the partition id as well
+ if(m_use_partition_function)
+ {
+ DBUG_PRINT("info", ("Saving partition id %u", m_part_id));
+ key_length= ref_length - sizeof(m_part_id);
+ memcpy(ref+key_length, (void *)&m_part_id, sizeof(m_part_id));
+ }
+ else
+ key_length= ref_length;
+#ifndef DBUG_OFF
+ int hidden_no= table->s->fields;
+ const NDBTAB *tab= m_table;
+ const NDBCOL *hidden_col= tab->getColumn(hidden_no);
+ DBUG_ASSERT(hidden_col->getPrimaryKey() &&
+ hidden_col->getAutoIncrement() &&
+ key_length == NDB_HIDDEN_PRIMARY_KEY_LENGTH);
+#endif
+ memcpy(ref, m_ref, key_length);
+ }
+#ifndef DBUG_OFF
+ if (table_share->primary_key == MAX_KEY && m_use_partition_function)
+ DBUG_DUMP("key+part", ref, key_length+sizeof(m_part_id));
+#endif
+ DBUG_DUMP("ref", ref, key_length);
+ DBUG_VOID_RETURN;
+}
+
+
+int ha_ndbcluster::info(uint flag)
+{
+ int result= 0;
+ DBUG_ENTER("info");
+ DBUG_PRINT("enter", ("flag: %d", flag));
+
+ if (flag & HA_STATUS_POS)
+ DBUG_PRINT("info", ("HA_STATUS_POS"));
+ if (flag & HA_STATUS_NO_LOCK)
+ DBUG_PRINT("info", ("HA_STATUS_NO_LOCK"));
+ if (flag & HA_STATUS_TIME)
+ DBUG_PRINT("info", ("HA_STATUS_TIME"));
+ if (flag & HA_STATUS_VARIABLE)
+ {
+ DBUG_PRINT("info", ("HA_STATUS_VARIABLE"));
+ if (m_table_info)
+ {
+ if (m_ha_not_exact_count)
+ stats.records= 100;
+ else
+ result= records_update();
+ }
+ else
+ {
+ if ((my_errno= check_ndb_connection()))
+ DBUG_RETURN(my_errno);
+ Ndb *ndb= get_ndb();
+ ndb->setDatabaseName(m_dbname);
+ struct Ndb_statistics stat;
+ if (ndb->setDatabaseName(m_dbname))
+ {
+ DBUG_RETURN(my_errno= HA_ERR_OUT_OF_MEM);
+ }
+ if (THDVAR(current_thd, use_exact_count) &&
+ (result= ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat))
+ == 0)
+ {
+ stats.mean_rec_length= stat.row_size;
+ stats.data_file_length= stat.fragment_memory;
+ stats.records= stat.row_count;
+ }
+ else
+ {
+ stats.mean_rec_length= 0;
+ stats.records= 100;
+ }
+ }
+ }
+ if (flag & HA_STATUS_CONST)
+ {
+ DBUG_PRINT("info", ("HA_STATUS_CONST"));
+ set_rec_per_key();
+ }
+ if (flag & HA_STATUS_ERRKEY)
+ {
+ DBUG_PRINT("info", ("HA_STATUS_ERRKEY"));
+ errkey= m_dupkey;
+ }
+ if (flag & HA_STATUS_AUTO)
+ {
+ DBUG_PRINT("info", ("HA_STATUS_AUTO"));
+ if (m_table && table->found_next_number_field)
+ {
+ if ((my_errno= check_ndb_connection()))
+ DBUG_RETURN(my_errno);
+ Ndb *ndb= get_ndb();
+ Ndb_tuple_id_range_guard g(m_share);
+
+ Uint64 auto_increment_value64;
+ if (ndb->readAutoIncrementValue(m_table, g.range,
+ auto_increment_value64) == -1)
+ {
+ const NdbError err= ndb->getNdbError();
+ sql_print_error("Error %lu in readAutoIncrementValue(): %s",
+ (ulong) err.code, err.message);
+ stats.auto_increment_value= ~(ulonglong)0;
+ }
+ else
+ stats.auto_increment_value= (ulonglong)auto_increment_value64;
+ }
+ }
+
+ if(result == -1)
+ result= HA_ERR_NO_CONNECTION;
+
+ DBUG_RETURN(result);
+}
+
+
+void ha_ndbcluster::get_dynamic_partition_info(PARTITION_STATS *stat_info,
+ uint part_id)
+{
+ /*
+ This functions should be fixed. Suggested fix: to
+ implement ndb function which retrives the statistics
+ about ndb partitions.
+ */
+ bzero((char*) stat_info, sizeof(PARTITION_STATS));
+ return;
+}
+
+
+int ha_ndbcluster::extra(enum ha_extra_function operation)
+{
+ DBUG_ENTER("extra");
+ switch (operation) {
+ case HA_EXTRA_IGNORE_DUP_KEY: /* Dup keys don't rollback everything*/
+ DBUG_PRINT("info", ("HA_EXTRA_IGNORE_DUP_KEY"));
+ DBUG_PRINT("info", ("Ignoring duplicate key"));
+ m_ignore_dup_key= TRUE;
+ break;
+ case HA_EXTRA_NO_IGNORE_DUP_KEY:
+ DBUG_PRINT("info", ("HA_EXTRA_NO_IGNORE_DUP_KEY"));
+ m_ignore_dup_key= FALSE;
+ break;
+ case HA_EXTRA_IGNORE_NO_KEY:
+ DBUG_PRINT("info", ("HA_EXTRA_IGNORE_NO_KEY"));
+ DBUG_PRINT("info", ("Turning on AO_IgnoreError at Commit/NoCommit"));
+ m_ignore_no_key= TRUE;
+ break;
+ case HA_EXTRA_NO_IGNORE_NO_KEY:
+ DBUG_PRINT("info", ("HA_EXTRA_NO_IGNORE_NO_KEY"));
+ DBUG_PRINT("info", ("Turning on AO_IgnoreError at Commit/NoCommit"));
+ m_ignore_no_key= FALSE;
+ break;
+ case HA_EXTRA_WRITE_CAN_REPLACE:
+ DBUG_PRINT("info", ("HA_EXTRA_WRITE_CAN_REPLACE"));
+ if (!m_has_unique_index ||
+ current_thd->slave_thread) /* always set if slave, quick fix for bug 27378 */
+ {
+ DBUG_PRINT("info", ("Turning ON use of write instead of insert"));
+ m_use_write= TRUE;
+ }
+ break;
+ case HA_EXTRA_WRITE_CANNOT_REPLACE:
+ DBUG_PRINT("info", ("HA_EXTRA_WRITE_CANNOT_REPLACE"));
+ DBUG_PRINT("info", ("Turning OFF use of write instead of insert"));
+ m_use_write= FALSE;
+ break;
+ case HA_EXTRA_DELETE_CANNOT_BATCH:
+ DBUG_PRINT("info", ("HA_EXTRA_DELETE_CANNOT_BATCH"));
+ m_delete_cannot_batch= TRUE;
+ break;
+ case HA_EXTRA_UPDATE_CANNOT_BATCH:
+ DBUG_PRINT("info", ("HA_EXTRA_UPDATE_CANNOT_BATCH"));
+ m_update_cannot_batch= TRUE;
+ break;
+ default:
+ break;
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+int ha_ndbcluster::reset()
+{
+ DBUG_ENTER("ha_ndbcluster::reset");
+ if (m_cond)
+ {
+ m_cond->cond_clear();
+ }
+
+ /*
+ Regular partition pruning will set the bitmap appropriately.
+ Some queries like ALTER TABLE doesn't use partition pruning and
+ thus the 'used_partitions' bitmap needs to be initialized
+ */
+ if (m_part_info)
+ bitmap_set_all(&m_part_info->used_partitions);
+
+ /* reset flags set by extra calls */
+ m_ignore_dup_key= FALSE;
+ m_use_write= FALSE;
+ m_ignore_no_key= FALSE;
+ m_delete_cannot_batch= FALSE;
+ m_update_cannot_batch= FALSE;
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Start of an insert, remember number of rows to be inserted, it will
+ be used in write_row and get_autoincrement to send an optimal number
+ of rows in each roundtrip to the server.
+
+ @param
+ rows number of rows to insert, 0 if unknown
+*/
+
+void ha_ndbcluster::start_bulk_insert(ha_rows rows)
+{
+ int bytes, batch;
+ const NDBTAB *tab= m_table;
+
+ DBUG_ENTER("start_bulk_insert");
+ DBUG_PRINT("enter", ("rows: %d", (int)rows));
+
+ m_rows_inserted= (ha_rows) 0;
+ if (!m_use_write && m_ignore_dup_key)
+ {
+ /*
+ compare if expression with that in write_row
+ we have a situation where peek_indexed_rows() will be called
+ so we cannot batch
+ */
+ DBUG_PRINT("info", ("Batching turned off as duplicate key is "
+ "ignored by using peek_row"));
+ m_rows_to_insert= 1;
+ m_bulk_insert_rows= 1;
+ DBUG_VOID_RETURN;
+ }
+ if (rows == (ha_rows) 0)
+ {
+ /* We don't know how many will be inserted, guess */
+ m_rows_to_insert= m_autoincrement_prefetch;
+ }
+ else
+ m_rows_to_insert= rows;
+
+ /*
+ Calculate how many rows that should be inserted
+ per roundtrip to NDB. This is done in order to minimize the
+ number of roundtrips as much as possible. However performance will
+ degrade if too many bytes are inserted, thus it's limited by this
+ calculation.
+ */
+ const int bytesperbatch= 8192;
+ bytes= 12 + tab->getRowSizeInBytes() + 4 * tab->getNoOfColumns();
+ batch= bytesperbatch/bytes;
+ batch= batch == 0 ? 1 : batch;
+ DBUG_PRINT("info", ("batch: %d, bytes: %d", batch, bytes));
+ m_bulk_insert_rows= batch;
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ End of an insert.
+*/
+int ha_ndbcluster::end_bulk_insert()
+{
+ int error= 0;
+ DBUG_ENTER("end_bulk_insert");
+
+ // Check if last inserts need to be flushed
+ if (m_bulk_insert_not_flushed)
+ {
+ NdbTransaction *trans= m_active_trans;
+ // Send rows to NDB
+ DBUG_PRINT("info", ("Sending inserts to NDB, "\
+ "rows_inserted: %d bulk_insert_rows: %d",
+ (int) m_rows_inserted, (int) m_bulk_insert_rows));
+ m_bulk_insert_not_flushed= FALSE;
+ if (m_transaction_on)
+ {
+ if (execute_no_commit(this, trans,FALSE) != 0)
+ {
+ no_uncommitted_rows_execute_failure();
+ my_errno= error= ndb_err(trans);
+ }
+ }
+ else
+ {
+ if (execute_commit(this, trans) != 0)
+ {
+ no_uncommitted_rows_execute_failure();
+ my_errno= error= ndb_err(trans);
+ }
+ else
+ {
+ int res __attribute__((unused))= trans->restart();
+ DBUG_ASSERT(res == 0);
+ }
+ }
+ }
+
+ m_rows_inserted= (ha_rows) 0;
+ m_rows_to_insert= (ha_rows) 1;
+ DBUG_RETURN(error);
+}
+
+
+int ha_ndbcluster::extra_opt(enum ha_extra_function operation, ulong cache_size)
+{
+ DBUG_ENTER("extra_opt");
+ DBUG_PRINT("enter", ("cache_size: %lu", cache_size));
+ DBUG_RETURN(extra(operation));
+}
+
+static const char *ha_ndbcluster_exts[] = {
+ ha_ndb_ext,
+ NullS
+};
+
+const char** ha_ndbcluster::bas_ext() const
+{
+ return ha_ndbcluster_exts;
+}
+
+/**
+ How many seeks it will take to read through the table.
+
+ This is to be comparable to the number returned by records_in_range so
+ that we can decide if we should scan the table or use keys.
+*/
+
+double ha_ndbcluster::scan_time()
+{
+ DBUG_ENTER("ha_ndbcluster::scan_time()");
+ double res= rows2double(stats.records*1000);
+ DBUG_PRINT("exit", ("table: %s value: %f",
+ m_tabname, res));
+ DBUG_RETURN(res);
+}
+
+/*
+ Convert MySQL table locks into locks supported by Ndb Cluster.
+ Note that MySQL Cluster does currently not support distributed
+ table locks, so to be safe one should set cluster in Single
+ User Mode, before relying on table locks when updating tables
+ from several MySQL servers
+*/
+
+THR_LOCK_DATA **ha_ndbcluster::store_lock(THD *thd,
+ THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type)
+{
+ DBUG_ENTER("store_lock");
+ if (lock_type != TL_IGNORE && m_lock.type == TL_UNLOCK)
+ {
+
+ /* If we are not doing a LOCK TABLE, then allow multiple
+ writers */
+
+ /* Since NDB does not currently have table locks
+ this is treated as a ordinary lock */
+
+ if ((lock_type >= TL_WRITE_CONCURRENT_INSERT &&
+ lock_type <= TL_WRITE) && !thd->in_lock_tables)
+ lock_type= TL_WRITE_ALLOW_WRITE;
+
+ /* In queries of type INSERT INTO t1 SELECT ... FROM t2 ...
+ MySQL would use the lock TL_READ_NO_INSERT on t2, and that
+ would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts
+ to t2. Convert the lock to a normal read lock to allow
+ concurrent inserts to t2. */
+
+ if (lock_type == TL_READ_NO_INSERT && !thd->in_lock_tables)
+ lock_type= TL_READ;
+
+ m_lock.type=lock_type;
+ }
+ *to++= &m_lock;
+
+ DBUG_PRINT("exit", ("lock_type: %d", lock_type));
+
+ DBUG_RETURN(to);
+}
+
+#ifndef DBUG_OFF
+#define PRINT_OPTION_FLAGS(t) { \
+ if (t->variables.option_bits & OPTION_NOT_AUTOCOMMIT) \
+ DBUG_PRINT("thd->variables.option_bits", ("OPTION_NOT_AUTOCOMMIT")); \
+ if (t->variables.option_bits & OPTION_BEGIN) \
+ DBUG_PRINT("thd->variables.option_bits", ("OPTION_BEGIN")); \
+ if (t->variables.option_bits & OPTION_TABLE_LOCK) \
+ DBUG_PRINT("thd->variables.option_bits", ("OPTION_TABLE_LOCK")); \
+}
+#else
+#define PRINT_OPTION_FLAGS(t)
+#endif
+
+
+/*
+ As MySQL will execute an external lock for every new table it uses
+ we can use this to start the transactions.
+ If we are in auto_commit mode we just need to start a transaction
+ for the statement, this will be stored in thd_ndb.stmt.
+ If not, we have to start a master transaction if there doesn't exist
+ one from before, this will be stored in thd_ndb.all
+
+ When a table lock is held one transaction will be started which holds
+ the table lock and for each statement a hupp transaction will be started
+ If we are locking the table then:
+ - save the NdbDictionary::Table for easy access
+ - save reference to table statistics
+ - refresh list of the indexes for the table if needed (if altered)
+ */
+
+#ifdef HAVE_NDB_BINLOG
+extern Master_info *active_mi;
+static int ndbcluster_update_apply_status(THD *thd, int do_update)
+{
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ Ndb *ndb= thd_ndb->ndb;
+ NDBDICT *dict= ndb->getDictionary();
+ const NDBTAB *ndbtab;
+ NdbTransaction *trans= thd_ndb->trans;
+ ndb->setDatabaseName(NDB_REP_DB);
+ Ndb_table_guard ndbtab_g(dict, NDB_APPLY_TABLE);
+ if (!(ndbtab= ndbtab_g.get_table()))
+ {
+ return -1;
+ }
+ NdbOperation *op= 0;
+ int r= 0;
+ r|= (op= trans->getNdbOperation(ndbtab)) == 0;
+ DBUG_ASSERT(r == 0);
+ if (do_update)
+ r|= op->updateTuple();
+ else
+ r|= op->writeTuple();
+ DBUG_ASSERT(r == 0);
+ // server_id
+ r|= op->equal(0u, (Uint32)thd->server_id);
+ DBUG_ASSERT(r == 0);
+ if (!do_update)
+ {
+ // epoch
+ r|= op->setValue(1u, (Uint64)0);
+ DBUG_ASSERT(r == 0);
+ }
+ // log_name
+ char tmp_buf[FN_REFLEN];
+ ndb_pack_varchar(ndbtab->getColumn(2u), tmp_buf,
+ active_mi->rli.group_master_log_name,
+ strlen(active_mi->rli.group_master_log_name));
+ r|= op->setValue(2u, tmp_buf);
+ DBUG_ASSERT(r == 0);
+ // start_pos
+ r|= op->setValue(3u, (Uint64)active_mi->rli.group_master_log_pos);
+ DBUG_ASSERT(r == 0);
+ // end_pos
+ r|= op->setValue(4u, (Uint64)active_mi->rli.group_master_log_pos +
+ ((Uint64)active_mi->rli.future_event_relay_log_pos -
+ (Uint64)active_mi->rli.group_relay_log_pos));
+ DBUG_ASSERT(r == 0);
+ return 0;
+}
+#endif /* HAVE_NDB_BINLOG */
+
+void ha_ndbcluster::transaction_checks(THD *thd)
+{
+ if (thd->lex->sql_command == SQLCOM_LOAD)
+ {
+ m_transaction_on= FALSE;
+ /* Would be simpler if has_transactions() didn't always say "yes" */
+ thd->transaction.all.modified_non_trans_table=
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ }
+ else if (!thd->transaction.on)
+ m_transaction_on= FALSE;
+ else
+ m_transaction_on= THDVAR(thd, use_transactions);
+}
+
+int ha_ndbcluster::start_statement(THD *thd,
+ Thd_ndb *thd_ndb,
+ Ndb *ndb)
+{
+ DBUG_ENTER("ha_ndbcluster::start_statement");
+ PRINT_OPTION_FLAGS(thd);
+
+ trans_register_ha(thd, FALSE, ndbcluster_hton);
+ if (!thd_ndb->trans)
+ {
+ if (thd->in_multi_stmt_transaction_mode())
+ trans_register_ha(thd, TRUE, ndbcluster_hton);
+ DBUG_PRINT("trans",("Starting transaction"));
+ thd_ndb->trans= ndb->startTransaction();
+ if (thd_ndb->trans == NULL)
+ ERR_RETURN(ndb->getNdbError());
+ thd_ndb->init_open_tables();
+ thd_ndb->query_state&= NDB_QUERY_NORMAL;
+ thd_ndb->trans_options= 0;
+ thd_ndb->m_slow_path= FALSE;
+ if (!(thd->variables.option_bits & OPTION_BIN_LOG) ||
+ thd->variables.binlog_format == BINLOG_FORMAT_STMT)
+ {
+ thd_ndb->trans_options|= TNTO_NO_LOGGING;
+ thd_ndb->m_slow_path= TRUE;
+ }
+ else if (thd->slave_thread)
+ thd_ndb->m_slow_path= TRUE;
+ }
+ /*
+ If this is the start of a LOCK TABLE, a table look
+ should be taken on the table in NDB
+
+ Check if it should be read or write lock
+ */
+ if (thd->variables.option_bits & OPTION_TABLE_LOCK)
+ {
+ //lockThisTable();
+ DBUG_PRINT("info", ("Locking the table..." ));
+ }
+ DBUG_RETURN(0);
+}
+
+int ha_ndbcluster::init_handler_for_statement(THD *thd, Thd_ndb *thd_ndb)
+{
+ /*
+ This is the place to make sure this handler instance
+ has a started transaction.
+
+ The transaction is started by the first handler on which
+ MySQL Server calls external lock
+
+ Other handlers in the same stmt or transaction should use
+ the same NDB transaction. This is done by setting up the m_active_trans
+ pointer to point to the NDB transaction.
+ */
+
+ DBUG_ENTER("ha_ndbcluster::init_handler_for_statement");
+ // store thread specific data first to set the right context
+ m_force_send= THDVAR(thd, force_send);
+ m_ha_not_exact_count= !THDVAR(thd, use_exact_count);
+ m_autoincrement_prefetch=
+ (THDVAR(thd, autoincrement_prefetch_sz) >
+ DEFAULT_AUTO_PREFETCH) ?
+ (ha_rows) THDVAR(thd, autoincrement_prefetch_sz)
+ : (ha_rows) DEFAULT_AUTO_PREFETCH;
+ m_active_trans= thd_ndb->trans;
+ DBUG_ASSERT(m_active_trans);
+ // Start of transaction
+ m_rows_changed= 0;
+ m_ops_pending= 0;
+ m_slow_path= thd_ndb->m_slow_path;
+#ifdef HAVE_NDB_BINLOG
+ if (unlikely(m_slow_path))
+ {
+ if (m_share == ndb_apply_status_share && thd->slave_thread)
+ thd_ndb->trans_options|= TNTO_INJECTED_APPLY_STATUS;
+ }
+#endif
+
+ if (thd->in_multi_stmt_transaction_mode())
+ {
+ const void *key= m_table;
+ HASH_SEARCH_STATE state;
+ THD_NDB_SHARE *thd_ndb_share=
+ (THD_NDB_SHARE*)my_hash_first(&thd_ndb->open_tables, (uchar *)&key, sizeof(key), &state);
+ while (thd_ndb_share && thd_ndb_share->key != key)
+ thd_ndb_share= (THD_NDB_SHARE*)my_hash_next(&thd_ndb->open_tables, (uchar *)&key, sizeof(key), &state);
+ if (thd_ndb_share == 0)
+ {
+ thd_ndb_share= (THD_NDB_SHARE *) alloc_root(&thd->transaction.mem_root,
+ sizeof(THD_NDB_SHARE));
+ if (!thd_ndb_share)
+ {
+ mem_alloc_error(sizeof(THD_NDB_SHARE));
+ DBUG_RETURN(1);
+ }
+ thd_ndb_share->key= key;
+ thd_ndb_share->stat.last_count= thd_ndb->count;
+ thd_ndb_share->stat.no_uncommitted_rows_count= 0;
+ thd_ndb_share->stat.records= ~(ha_rows)0;
+ my_hash_insert(&thd_ndb->open_tables, (uchar *)thd_ndb_share);
+ }
+ else if (thd_ndb_share->stat.last_count != thd_ndb->count)
+ {
+ thd_ndb_share->stat.last_count= thd_ndb->count;
+ thd_ndb_share->stat.no_uncommitted_rows_count= 0;
+ thd_ndb_share->stat.records= ~(ha_rows)0;
+ }
+ DBUG_PRINT("exit", ("thd_ndb_share: 0x%lx key: 0x%lx",
+ (long) thd_ndb_share, (long) key));
+ m_table_info= &thd_ndb_share->stat;
+ }
+ else
+ {
+ struct Ndb_local_table_statistics &stat= m_table_info_instance;
+ stat.last_count= thd_ndb->count;
+ stat.no_uncommitted_rows_count= 0;
+ stat.records= ~(ha_rows)0;
+ m_table_info= &stat;
+ }
+ DBUG_RETURN(0);
+}
+
+int ha_ndbcluster::external_lock(THD *thd, int lock_type)
+{
+ int error=0;
+ DBUG_ENTER("external_lock");
+
+ /*
+ Check that this handler instance has a connection
+ set up to the Ndb object of thd
+ */
+ if (check_ndb_connection(thd))
+ DBUG_RETURN(1);
+
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ Ndb *ndb= thd_ndb->ndb;
+
+ DBUG_PRINT("enter", ("this: 0x%lx thd: 0x%lx thd_ndb: 0x%lx "
+ "thd_ndb->lock_count: %d",
+ (long) this, (long) thd, (long) thd_ndb,
+ thd_ndb->lock_count));
+
+ if (lock_type != F_UNLCK)
+ {
+ DBUG_PRINT("info", ("lock_type != F_UNLCK"));
+ transaction_checks(thd);
+ if (!thd_ndb->lock_count++)
+ {
+ if ((error= start_statement(thd, thd_ndb, ndb)))
+ goto error;
+ }
+ if ((error= init_handler_for_statement(thd, thd_ndb)))
+ goto error;
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("lock_type == F_UNLCK"));
+
+ if (opt_ndb_cache_check_time && m_rows_changed)
+ {
+ DBUG_PRINT("info", ("Rows has changed and util thread is running"));
+ if (thd->in_multi_stmt_transaction_mode())
+ {
+ DBUG_PRINT("info", ("Add share to list of tables to be invalidated"));
+ /* NOTE push_back allocates memory using transactions mem_root! */
+ thd_ndb->changed_tables.push_back(m_share, &thd->transaction.mem_root);
+ }
+
+ mysql_mutex_lock(&m_share->mutex);
+ DBUG_PRINT("info", ("Invalidating commit_count"));
+ m_share->commit_count= 0;
+ m_share->commit_count_lock++;
+ mysql_mutex_unlock(&m_share->mutex);
+ }
+
+ if (!--thd_ndb->lock_count)
+ {
+ DBUG_PRINT("trans", ("Last external_lock"));
+ PRINT_OPTION_FLAGS(thd);
+
+ if (!thd->in_multi_stmt_transaction_mode())
+ {
+ if (thd_ndb->trans)
+ {
+ /*
+ Unlock is done without a transaction commit / rollback.
+ This happens if the thread didn't update any rows
+ We must in this case close the transaction to release resources
+ */
+ DBUG_PRINT("trans",("ending non-updating transaction"));
+ ndb->closeTransaction(thd_ndb->trans);
+ thd_ndb->trans= NULL;
+ }
+ }
+ }
+ m_table_info= NULL;
+
+ /*
+ This is the place to make sure this handler instance
+ no longer are connected to the active transaction.
+
+ And since the handler is no longer part of the transaction
+ it can't have open cursors, ops or blobs pending.
+ */
+ m_active_trans= NULL;
+
+ if (m_active_cursor)
+ DBUG_PRINT("warning", ("m_active_cursor != NULL"));
+ m_active_cursor= NULL;
+
+ if (m_multi_cursor)
+ DBUG_PRINT("warning", ("m_multi_cursor != NULL"));
+ m_multi_cursor= NULL;
+
+ if (m_blobs_pending)
+ DBUG_PRINT("warning", ("blobs_pending != 0"));
+ m_blobs_pending= 0;
+
+ if (m_ops_pending)
+ DBUG_PRINT("warning", ("ops_pending != 0L"));
+ m_ops_pending= 0;
+ DBUG_RETURN(0);
+ }
+error:
+ thd_ndb->lock_count--;
+ DBUG_RETURN(error);
+}
+
+/**
+ Unlock the last row read in an open scan.
+ Rows are unlocked by default in ndb, but
+ for SELECT FOR UPDATE and SELECT LOCK WIT SHARE MODE
+ locks are kept if unlock_row() is not called.
+*/
+
+void ha_ndbcluster::unlock_row()
+{
+ DBUG_ENTER("unlock_row");
+
+ DBUG_PRINT("info", ("Unlocking row"));
+ m_lock_tuple= FALSE;
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Start a transaction for running a statement if one is not
+ already running in a transaction. This will be the case in
+ a BEGIN; COMMIT; block
+ When using LOCK TABLE's external_lock will start a transaction
+ since ndb does not currently does not support table locking.
+*/
+
+int ha_ndbcluster::start_stmt(THD *thd, thr_lock_type lock_type)
+{
+ int error=0;
+ DBUG_ENTER("start_stmt");
+
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ transaction_checks(thd);
+ if (!thd_ndb->start_stmt_count++)
+ {
+ Ndb *ndb= thd_ndb->ndb;
+ if ((error= start_statement(thd, thd_ndb, ndb)))
+ goto error;
+ }
+ if ((error= init_handler_for_statement(thd, thd_ndb)))
+ goto error;
+ DBUG_RETURN(0);
+error:
+ thd_ndb->start_stmt_count--;
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Commit a transaction started in NDB.
+*/
+
+static int ndbcluster_commit(handlerton *hton, THD *thd, bool all)
+{
+ int res= 0;
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ Ndb *ndb= thd_ndb->ndb;
+ NdbTransaction *trans= thd_ndb->trans;
+
+ DBUG_ENTER("ndbcluster_commit");
+ DBUG_ASSERT(ndb);
+ PRINT_OPTION_FLAGS(thd);
+ DBUG_PRINT("enter", ("Commit %s", (all ? "all" : "stmt")));
+ thd_ndb->start_stmt_count= 0;
+ if (trans == NULL || (!all && thd->in_multi_stmt_transaction_mode()))
+ {
+ /*
+ An odditity in the handler interface is that commit on handlerton
+ is called to indicate end of statement only in cases where
+ autocommit isn't used and the all flag isn't set.
+
+ We also leave quickly when a transaction haven't even been started,
+ in this case we are safe that no clean up is needed. In this case
+ the MySQL Server could handle the query without contacting the
+ NDB kernel.
+ */
+ DBUG_PRINT("info", ("Commit before start or end-of-statement only"));
+ DBUG_RETURN(0);
+ }
+
+#ifdef HAVE_NDB_BINLOG
+ if (unlikely(thd_ndb->m_slow_path))
+ {
+ if (thd->slave_thread)
+ ndbcluster_update_apply_status
+ (thd, thd_ndb->trans_options & TNTO_INJECTED_APPLY_STATUS);
+ }
+#endif /* HAVE_NDB_BINLOG */
+
+ if (execute_commit(thd,trans) != 0)
+ {
+ const NdbError err= trans->getNdbError();
+ const NdbOperation *error_op= trans->getNdbErrorOperation();
+ set_ndb_err(thd, err);
+ res= ndb_to_mysql_error(&err);
+ if (res != -1)
+ ndbcluster_print_error(res, error_op);
+ }
+ ndb->closeTransaction(trans);
+ thd_ndb->trans= NULL;
+
+ /* Clear commit_count for tables changed by transaction */
+ NDB_SHARE* share;
+ List_iterator_fast<NDB_SHARE> it(thd_ndb->changed_tables);
+ while ((share= it++))
+ {
+ mysql_mutex_lock(&share->mutex);
+ DBUG_PRINT("info", ("Invalidate commit_count for %s, share->commit_count: %lu",
+ share->table_name, (ulong) share->commit_count));
+ share->commit_count= 0;
+ share->commit_count_lock++;
+ mysql_mutex_unlock(&share->mutex);
+ }
+ thd_ndb->changed_tables.empty();
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Rollback a transaction started in NDB.
+*/
+
+static int ndbcluster_rollback(handlerton *hton, THD *thd, bool all)
+{
+ int res= 0;
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ Ndb *ndb= thd_ndb->ndb;
+ NdbTransaction *trans= thd_ndb->trans;
+
+ DBUG_ENTER("ndbcluster_rollback");
+ DBUG_ASSERT(ndb);
+ thd_ndb->start_stmt_count= 0;
+ if (trans == NULL || (!all &&
+ thd->in_multi_stmt_transaction_mode()))
+ {
+ /* Ignore end-of-statement until real rollback or commit is called */
+ DBUG_PRINT("info", ("Rollback before start or end-of-statement only"));
+ DBUG_RETURN(0);
+ }
+
+ if (trans->execute(NdbTransaction::Rollback) != 0)
+ {
+ const NdbError err= trans->getNdbError();
+ const NdbOperation *error_op= trans->getNdbErrorOperation();
+ set_ndb_err(thd, err);
+ res= ndb_to_mysql_error(&err);
+ if (res != -1)
+ ndbcluster_print_error(res, error_op);
+ }
+ ndb->closeTransaction(trans);
+ thd_ndb->trans= NULL;
+
+ /* Clear list of tables changed by transaction */
+ thd_ndb->changed_tables.empty();
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Define NDB column based on Field.
+
+ Not member of ha_ndbcluster because NDBCOL cannot be declared.
+
+ MySQL text types with character set "binary" are mapped to true
+ NDB binary types without a character set. This may change.
+
+ @return
+ Returns 0 or mysql error code.
+*/
+
+static int create_ndb_column(NDBCOL &col,
+ Field *field,
+ HA_CREATE_INFO *info)
+{
+ // Set name
+ if (col.setName(field->field_name))
+ {
+ return (my_errno= errno);
+ }
+ // Get char set
+ CHARSET_INFO *cs= field->charset();
+ // Set type and sizes
+ const enum enum_field_types mysql_type= field->real_type();
+ switch (mysql_type) {
+ // Numeric types
+ case MYSQL_TYPE_TINY:
+ if (field->flags & UNSIGNED_FLAG)
+ col.setType(NDBCOL::Tinyunsigned);
+ else
+ col.setType(NDBCOL::Tinyint);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_SHORT:
+ if (field->flags & UNSIGNED_FLAG)
+ col.setType(NDBCOL::Smallunsigned);
+ else
+ col.setType(NDBCOL::Smallint);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_LONG:
+ if (field->flags & UNSIGNED_FLAG)
+ col.setType(NDBCOL::Unsigned);
+ else
+ col.setType(NDBCOL::Int);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_INT24:
+ if (field->flags & UNSIGNED_FLAG)
+ col.setType(NDBCOL::Mediumunsigned);
+ else
+ col.setType(NDBCOL::Mediumint);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_LONGLONG:
+ if (field->flags & UNSIGNED_FLAG)
+ col.setType(NDBCOL::Bigunsigned);
+ else
+ col.setType(NDBCOL::Bigint);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_FLOAT:
+ col.setType(NDBCOL::Float);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_DOUBLE:
+ col.setType(NDBCOL::Double);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_DECIMAL:
+ {
+ Field_decimal *f= (Field_decimal*)field;
+ uint precision= f->pack_length();
+ uint scale= f->decimals();
+ if (field->flags & UNSIGNED_FLAG)
+ {
+ col.setType(NDBCOL::Olddecimalunsigned);
+ precision-= (scale > 0);
+ }
+ else
+ {
+ col.setType(NDBCOL::Olddecimal);
+ precision-= 1 + (scale > 0);
+ }
+ col.setPrecision(precision);
+ col.setScale(scale);
+ col.setLength(1);
+ }
+ break;
+ case MYSQL_TYPE_NEWDECIMAL:
+ {
+ Field_new_decimal *f= (Field_new_decimal*)field;
+ uint precision= f->precision;
+ uint scale= f->decimals();
+ if (field->flags & UNSIGNED_FLAG)
+ {
+ col.setType(NDBCOL::Decimalunsigned);
+ }
+ else
+ {
+ col.setType(NDBCOL::Decimal);
+ }
+ col.setPrecision(precision);
+ col.setScale(scale);
+ col.setLength(1);
+ }
+ break;
+ // Date types
+ case MYSQL_TYPE_DATETIME:
+ col.setType(NDBCOL::Datetime);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_DATE: // ?
+ col.setType(NDBCOL::Char);
+ col.setLength(field->pack_length());
+ break;
+ case MYSQL_TYPE_NEWDATE:
+ col.setType(NDBCOL::Date);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_TIME:
+ col.setType(NDBCOL::Time);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_YEAR:
+ col.setType(NDBCOL::Year);
+ col.setLength(1);
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ col.setType(NDBCOL::Timestamp);
+ col.setLength(1);
+ break;
+ // Char types
+ case MYSQL_TYPE_STRING:
+ if (field->pack_length() == 0)
+ {
+ col.setType(NDBCOL::Bit);
+ col.setLength(1);
+ }
+ else if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin)
+ {
+ col.setType(NDBCOL::Binary);
+ col.setLength(field->pack_length());
+ }
+ else
+ {
+ col.setType(NDBCOL::Char);
+ col.setCharset(cs);
+ col.setLength(field->pack_length());
+ }
+ break;
+ case MYSQL_TYPE_VAR_STRING: // ?
+ case MYSQL_TYPE_VARCHAR:
+ {
+ Field_varstring* f= (Field_varstring*)field;
+ if (f->length_bytes == 1)
+ {
+ if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin)
+ col.setType(NDBCOL::Varbinary);
+ else {
+ col.setType(NDBCOL::Varchar);
+ col.setCharset(cs);
+ }
+ }
+ else if (f->length_bytes == 2)
+ {
+ if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin)
+ col.setType(NDBCOL::Longvarbinary);
+ else {
+ col.setType(NDBCOL::Longvarchar);
+ col.setCharset(cs);
+ }
+ }
+ else
+ {
+ return HA_ERR_UNSUPPORTED;
+ }
+ col.setLength(field->field_length);
+ }
+ break;
+ // Blob types (all come in as MYSQL_TYPE_BLOB)
+ mysql_type_tiny_blob:
+ case MYSQL_TYPE_TINY_BLOB:
+ if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin)
+ col.setType(NDBCOL::Blob);
+ else {
+ col.setType(NDBCOL::Text);
+ col.setCharset(cs);
+ }
+ col.setInlineSize(256);
+ // No parts
+ col.setPartSize(0);
+ col.setStripeSize(0);
+ break;
+ //mysql_type_blob:
+ case MYSQL_TYPE_GEOMETRY:
+ case MYSQL_TYPE_BLOB:
+ if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin)
+ col.setType(NDBCOL::Blob);
+ else {
+ col.setType(NDBCOL::Text);
+ col.setCharset(cs);
+ }
+ {
+ Field_blob *field_blob= (Field_blob *)field;
+ /*
+ * max_data_length is 2^8-1, 2^16-1, 2^24-1 for tiny, blob, medium.
+ * Tinyblob gets no blob parts. The other cases are just a crude
+ * way to control part size and striping.
+ *
+ * In mysql blob(256) is promoted to blob(65535) so it does not
+ * in fact fit "inline" in NDB.
+ */
+ if (field_blob->max_data_length() < (1 << 8))
+ goto mysql_type_tiny_blob;
+ else if (field_blob->max_data_length() < (1 << 16))
+ {
+ col.setInlineSize(256);
+ col.setPartSize(2000);
+ col.setStripeSize(16);
+ }
+ else if (field_blob->max_data_length() < (1 << 24))
+ goto mysql_type_medium_blob;
+ else
+ goto mysql_type_long_blob;
+ }
+ break;
+ mysql_type_medium_blob:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin)
+ col.setType(NDBCOL::Blob);
+ else {
+ col.setType(NDBCOL::Text);
+ col.setCharset(cs);
+ }
+ col.setInlineSize(256);
+ col.setPartSize(4000);
+ col.setStripeSize(8);
+ break;
+ mysql_type_long_blob:
+ case MYSQL_TYPE_LONG_BLOB:
+ if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin)
+ col.setType(NDBCOL::Blob);
+ else {
+ col.setType(NDBCOL::Text);
+ col.setCharset(cs);
+ }
+ col.setInlineSize(256);
+ col.setPartSize(8000);
+ col.setStripeSize(4);
+ break;
+ // Other types
+ case MYSQL_TYPE_ENUM:
+ col.setType(NDBCOL::Char);
+ col.setLength(field->pack_length());
+ break;
+ case MYSQL_TYPE_SET:
+ col.setType(NDBCOL::Char);
+ col.setLength(field->pack_length());
+ break;
+ case MYSQL_TYPE_BIT:
+ {
+ int no_of_bits= field->field_length;
+ col.setType(NDBCOL::Bit);
+ if (!no_of_bits)
+ col.setLength(1);
+ else
+ col.setLength(no_of_bits);
+ break;
+ }
+ case MYSQL_TYPE_NULL:
+ goto mysql_type_unsupported;
+ mysql_type_unsupported:
+ default:
+ return HA_ERR_UNSUPPORTED;
+ }
+ // Set nullable and pk
+ col.setNullable(field->maybe_null());
+ col.setPrimaryKey(field->flags & PRI_KEY_FLAG);
+ // Set autoincrement
+ if (field->flags & AUTO_INCREMENT_FLAG)
+ {
+#ifndef DBUG_OFF
+ char buff[22];
+#endif
+ col.setAutoIncrement(TRUE);
+ ulonglong value= info->auto_increment_value ?
+ info->auto_increment_value : (ulonglong) 1;
+ DBUG_PRINT("info", ("Autoincrement key, initial: %s", llstr(value, buff)));
+ col.setAutoIncrementInitialValue(value);
+ }
+ else
+ col.setAutoIncrement(FALSE);
+ return 0;
+}
+
+/**
+ Create a table in NDB Cluster
+*/
+
+int ha_ndbcluster::create(const char *name,
+ TABLE *form,
+ HA_CREATE_INFO *create_info)
+{
+ THD *thd= current_thd;
+ NDBTAB tab;
+ NDBCOL col;
+ size_t pack_length, length;
+ uint i, pk_length= 0;
+ uchar *data= NULL, *pack_data= NULL;
+ bool create_from_engine= (create_info->table_options & HA_OPTION_CREATE_FROM_ENGINE);
+ bool is_truncate= (thd->lex->sql_command == SQLCOM_TRUNCATE);
+ char tablespace[FN_LEN + 1];
+ NdbDictionary::Table::SingleUserMode single_user_mode= NdbDictionary::Table::SingleUserModeLocked;
+
+ DBUG_ENTER("ha_ndbcluster::create");
+ DBUG_PRINT("enter", ("name: %s", name));
+
+ DBUG_ASSERT(*fn_rext((char*)name) == 0);
+ set_dbname(name);
+ set_tabname(name);
+
+ if ((my_errno= check_ndb_connection()))
+ DBUG_RETURN(my_errno);
+
+ Ndb *ndb= get_ndb();
+ NDBDICT *dict= ndb->getDictionary();
+
+ if (is_truncate)
+ {
+ {
+ Ndb_table_guard ndbtab_g(dict, m_tabname);
+ if (!(m_table= ndbtab_g.get_table()))
+ ERR_RETURN(dict->getNdbError());
+ if ((get_tablespace_name(thd, tablespace, FN_LEN)))
+ create_info->tablespace= tablespace;
+ m_table= NULL;
+ }
+ DBUG_PRINT("info", ("Dropping and re-creating table for TRUNCATE"));
+ if ((my_errno= delete_table(name)))
+ DBUG_RETURN(my_errno);
+ }
+ table= form;
+ if (create_from_engine)
+ {
+ /*
+ Table already exists in NDB and frm file has been created by
+ caller.
+ Do Ndb specific stuff, such as create a .ndb file
+ */
+ if ((my_errno= write_ndb_file(name)))
+ DBUG_RETURN(my_errno);
+#ifdef HAVE_NDB_BINLOG
+ ndbcluster_create_binlog_setup(get_ndb(), name, strlen(name),
+ m_dbname, m_tabname, FALSE);
+#endif /* HAVE_NDB_BINLOG */
+ DBUG_RETURN(my_errno);
+ }
+
+#ifdef HAVE_NDB_BINLOG
+ /*
+ Don't allow table creation unless
+ schema distribution table is setup
+ ( unless it is a creation of the schema dist table itself )
+ */
+ if (!ndb_schema_share)
+ {
+ if (!(strcmp(m_dbname, NDB_REP_DB) == 0 &&
+ strcmp(m_tabname, NDB_SCHEMA_TABLE) == 0))
+ {
+ DBUG_PRINT("info", ("Schema distribution table not setup"));
+ DBUG_ASSERT(ndb_schema_share);
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+ }
+ single_user_mode = NdbDictionary::Table::SingleUserModeReadWrite;
+ }
+#endif /* HAVE_NDB_BINLOG */
+
+ DBUG_PRINT("table", ("name: %s", m_tabname));
+ if (tab.setName(m_tabname))
+ {
+ DBUG_RETURN(my_errno= errno);
+ }
+ tab.setLogging(!(create_info->options & HA_LEX_CREATE_TMP_TABLE));
+ tab.setSingleUserMode(single_user_mode);
+
+ // Save frm data for this table
+ if (readfrm(name, &data, &length))
+ DBUG_RETURN(1);
+ if (packfrm(data, length, &pack_data, &pack_length))
+ {
+ my_free(data);
+ DBUG_RETURN(2);
+ }
+ DBUG_PRINT("info",
+ ("setFrm data: 0x%lx len: %lu", (long) pack_data,
+ (ulong) pack_length));
+ tab.setFrm(pack_data, pack_length);
+ my_free(data);
+ my_free(pack_data);
+
+ /*
+ Check for disk options
+ */
+ if (create_info->storage_media == HA_SM_DISK)
+ {
+ if (create_info->tablespace)
+ tab.setTablespaceName(create_info->tablespace);
+ else
+ tab.setTablespaceName("DEFAULT-TS");
+ }
+ else if (create_info->tablespace)
+ {
+ if (create_info->storage_media == HA_SM_MEMORY)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ILLEGAL_HA_CREATE_OPTION,
+ ER(ER_ILLEGAL_HA_CREATE_OPTION),
+ ndbcluster_hton_name,
+ "TABLESPACE currently only supported for "
+ "STORAGE DISK");
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+ tab.setTablespaceName(create_info->tablespace);
+ create_info->storage_media = HA_SM_DISK; //if use tablespace, that also means store on disk
+ }
+
+ /*
+ Handle table row type
+
+ Default is to let table rows have var part reference so that online
+ add column can be performed in the future. Explicitly setting row
+ type to fixed will omit var part reference, which will save data
+ memory in ndb, but at the cost of not being able to online add
+ column to this table
+ */
+ switch (create_info->row_type) {
+ case ROW_TYPE_FIXED:
+ tab.setForceVarPart(FALSE);
+ break;
+ case ROW_TYPE_DYNAMIC:
+ /* fall through, treat as default */
+ default:
+ /* fall through, treat as default */
+ case ROW_TYPE_DEFAULT:
+ tab.setForceVarPart(TRUE);
+ break;
+ }
+
+ /*
+ Setup columns
+ */
+ for (i= 0; i < form->s->fields; i++)
+ {
+ Field *field= form->field[i];
+ DBUG_PRINT("info", ("name: %s type: %u pack_length: %d",
+ field->field_name, field->real_type(),
+ field->pack_length()));
+ if ((my_errno= create_ndb_column(col, field, create_info)))
+ DBUG_RETURN(my_errno);
+
+ if (create_info->storage_media == HA_SM_DISK)
+ col.setStorageType(NdbDictionary::Column::StorageTypeDisk);
+ else
+ col.setStorageType(NdbDictionary::Column::StorageTypeMemory);
+
+ switch (create_info->row_type) {
+ case ROW_TYPE_FIXED:
+ if (field_type_forces_var_part(field->type()))
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ILLEGAL_HA_CREATE_OPTION,
+ ER(ER_ILLEGAL_HA_CREATE_OPTION),
+ ndbcluster_hton_name,
+ "Row format FIXED incompatible with "
+ "variable sized attribute");
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+ break;
+ case ROW_TYPE_DYNAMIC:
+ /*
+ Future: make columns dynamic in this case
+ */
+ break;
+ default:
+ break;
+ }
+ if (tab.addColumn(col))
+ {
+ DBUG_RETURN(my_errno= errno);
+ }
+ if (col.getPrimaryKey())
+ pk_length += (field->pack_length() + 3) / 4;
+ }
+
+ KEY* key_info;
+ for (i= 0, key_info= form->key_info; i < form->s->keys; i++, key_info++)
+ {
+ KEY_PART_INFO *key_part= key_info->key_part;
+ KEY_PART_INFO *end= key_part + key_info->user_defined_key_parts;
+ for (; key_part != end; key_part++)
+ tab.getColumn(key_part->fieldnr-1)->setStorageType(
+ NdbDictionary::Column::StorageTypeMemory);
+ }
+
+ // No primary key, create shadow key as 64 bit, auto increment
+ if (form->s->primary_key == MAX_KEY)
+ {
+ DBUG_PRINT("info", ("Generating shadow key"));
+ if (col.setName("$PK"))
+ {
+ DBUG_RETURN(my_errno= errno);
+ }
+ col.setType(NdbDictionary::Column::Bigunsigned);
+ col.setLength(1);
+ col.setNullable(FALSE);
+ col.setPrimaryKey(TRUE);
+ col.setAutoIncrement(TRUE);
+ if (tab.addColumn(col))
+ {
+ DBUG_RETURN(my_errno= errno);
+ }
+ pk_length += 2;
+ }
+
+ // Make sure that blob tables don't have to big part size
+ for (i= 0; i < form->s->fields; i++)
+ {
+ /**
+ * The extra +7 concists
+ * 2 - words from pk in blob table
+ * 5 - from extra words added by tup/dict??
+ */
+ switch (form->field[i]->real_type()) {
+ case MYSQL_TYPE_GEOMETRY:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ {
+ NdbDictionary::Column * column= tab.getColumn(i);
+ int size= pk_length + (column->getPartSize()+3)/4 + 7;
+ if (size > NDB_MAX_TUPLE_SIZE_IN_WORDS &&
+ (pk_length+7) < NDB_MAX_TUPLE_SIZE_IN_WORDS)
+ {
+ size= NDB_MAX_TUPLE_SIZE_IN_WORDS - pk_length - 7;
+ column->setPartSize(4*size);
+ }
+ /**
+ * If size > NDB_MAX and pk_length+7 >= NDB_MAX
+ * then the table can't be created anyway, so skip
+ * changing part size, and have error later
+ */
+ }
+ default:
+ break;
+ }
+ }
+
+ // Check partition info
+ partition_info *part_info= form->part_info;
+ if ((my_errno= set_up_partition_info(part_info, form, (void*)&tab)))
+ {
+ DBUG_RETURN(my_errno);
+ }
+
+ // Create the table in NDB
+ if (dict->createTable(tab) != 0)
+ {
+ const NdbError err= dict->getNdbError();
+ set_ndb_err(thd, err);
+ my_errno= ndb_to_mysql_error(&err);
+ DBUG_RETURN(my_errno);
+ }
+
+ Ndb_table_guard ndbtab_g(dict, m_tabname);
+ // temporary set m_table during create
+ // reset at return
+ m_table= ndbtab_g.get_table();
+ // TODO check also that we have the same frm...
+ if (!m_table)
+ {
+ /* purecov: begin deadcode */
+ const NdbError err= dict->getNdbError();
+ set_ndb_err(thd, err);
+ my_errno= ndb_to_mysql_error(&err);
+ DBUG_RETURN(my_errno);
+ /* purecov: end */
+ }
+
+ DBUG_PRINT("info", ("Table %s/%s created successfully",
+ m_dbname, m_tabname));
+
+ // Create secondary indexes
+ my_errno= create_indexes(ndb, form);
+
+ if (!my_errno)
+ my_errno= write_ndb_file(name);
+ else
+ {
+ /*
+ Failed to create an index,
+ drop the table (and all it's indexes)
+ */
+ while (dict->dropTableGlobal(*m_table))
+ {
+ switch (dict->getNdbError().status)
+ {
+ case NdbError::TemporaryError:
+ if (!thd->killed)
+ continue; // retry indefinitly
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ m_table = 0;
+ DBUG_RETURN(my_errno);
+ }
+
+#ifdef HAVE_NDB_BINLOG
+ if (!my_errno)
+ {
+ NDB_SHARE *share= 0;
+ mysql_mutex_lock(&ndbcluster_mutex);
+ /*
+ First make sure we get a "fresh" share here, not an old trailing one...
+ */
+ {
+ uint length= (uint) strlen(name);
+ if ((share= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables,
+ (uchar*) name, length)))
+ handle_trailing_share(share);
+ }
+ /*
+ get a new share
+ */
+
+ /* ndb_share reference create */
+ if (!(share= get_share(name, form, TRUE, TRUE)))
+ {
+ sql_print_error("NDB: allocating table share for %s failed", name);
+ /* my_errno is set */
+ }
+ else
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s binlog create use_count: %u",
+ share->key, share->use_count));
+ }
+ mysql_mutex_unlock(&ndbcluster_mutex);
+
+ while (!IS_TMP_PREFIX(m_tabname))
+ {
+ String event_name(INJECTOR_EVENT_LEN);
+ ndb_rep_event_name(&event_name,m_dbname,m_tabname);
+ int do_event_op= ndb_binlog_running;
+
+ if (!ndb_schema_share &&
+ strcmp(share->db, NDB_REP_DB) == 0 &&
+ strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0)
+ do_event_op= 1;
+
+ /*
+ Always create an event for the table, as other mysql servers
+ expect it to be there.
+ */
+ if (!ndbcluster_create_event(ndb, m_table, event_name.c_ptr(), share,
+ share && do_event_op ? 2 : 1/* push warning */))
+ {
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: CREATE TABLE Event: %s",
+ event_name.c_ptr());
+ if (share &&
+ ndbcluster_create_event_ops(share, m_table, event_name.c_ptr()))
+ {
+ sql_print_error("NDB Binlog: FAILED CREATE TABLE event operations."
+ " Event: %s", name);
+ /* a warning has been issued to the client */
+ }
+ }
+ /*
+ warning has been issued if ndbcluster_create_event failed
+ and (share && do_event_op)
+ */
+ if (share && !do_event_op)
+ share->flags|= NSF_NO_BINLOG;
+ ndbcluster_log_schema_op(thd, share,
+ thd->query(), thd->query_length(),
+ share->db, share->table_name,
+ m_table->getObjectId(),
+ m_table->getObjectVersion(),
+ (is_truncate) ?
+ SOT_TRUNCATE_TABLE : SOT_CREATE_TABLE,
+ 0, 0);
+ break;
+ }
+ }
+#endif /* HAVE_NDB_BINLOG */
+
+ m_table= 0;
+ DBUG_RETURN(my_errno);
+}
+
+int ha_ndbcluster::create_handler_files(const char *file,
+ const char *old_name,
+ int action_flag,
+ HA_CREATE_INFO *create_info)
+{
+ Ndb* ndb;
+ const NDBTAB *tab;
+ uchar *data= NULL, *pack_data= NULL;
+ size_t length, pack_length;
+ int error= 0;
+
+ DBUG_ENTER("create_handler_files");
+
+ if (action_flag != CHF_INDEX_FLAG)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ DBUG_PRINT("enter", ("file: %s", file));
+ if (!(ndb= get_ndb()))
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+
+ NDBDICT *dict= ndb->getDictionary();
+ if (!create_info->frm_only)
+ DBUG_RETURN(0); // Must be a create, ignore since frm is saved in create
+
+ // TODO handle this
+ DBUG_ASSERT(m_table != 0);
+
+ set_dbname(file);
+ set_tabname(file);
+ Ndb_table_guard ndbtab_g(dict, m_tabname);
+ DBUG_PRINT("info", ("m_dbname: %s, m_tabname: %s", m_dbname, m_tabname));
+ if (!(tab= ndbtab_g.get_table()))
+ DBUG_RETURN(0); // Unkown table, must be temporary table
+
+ DBUG_ASSERT(get_ndb_share_state(m_share) == NSS_ALTERED);
+ if (readfrm(file, &data, &length) ||
+ packfrm(data, length, &pack_data, &pack_length))
+ {
+ DBUG_PRINT("info", ("Missing frm for %s", m_tabname));
+ my_free(data);
+ my_free(pack_data);
+ error= 1;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Table %s has changed, altering frm in ndb",
+ m_tabname));
+ NdbDictionary::Table new_tab= *tab;
+ new_tab.setFrm(pack_data, pack_length);
+ if (dict->alterTableGlobal(*tab, new_tab))
+ {
+ set_ndb_err(current_thd, dict->getNdbError());
+ error= ndb_to_mysql_error(&dict->getNdbError());
+ }
+ my_free(data);
+ my_free(pack_data);
+ }
+
+ set_ndb_share_state(m_share, NSS_INITIAL);
+ /* ndb_share reference schema(?) free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog schema(?) free use_count: %u",
+ m_share->key, m_share->use_count));
+ free_share(&m_share); // Decrease ref_count
+
+ DBUG_RETURN(error);
+}
+
+int ha_ndbcluster::create_index(const char *name, KEY *key_info,
+ NDB_INDEX_TYPE idx_type, uint idx_no)
+{
+ int error= 0;
+ char unique_name[FN_LEN + 1];
+ static const char* unique_suffix= "$unique";
+ DBUG_ENTER("ha_ndbcluster::create_ordered_index");
+ DBUG_PRINT("info", ("Creating index %u: %s", idx_no, name));
+
+ if (idx_type == UNIQUE_ORDERED_INDEX || idx_type == UNIQUE_INDEX)
+ {
+ strxnmov(unique_name, FN_LEN, name, unique_suffix, NullS);
+ DBUG_PRINT("info", ("Created unique index name \'%s\' for index %d",
+ unique_name, idx_no));
+ }
+
+ switch (idx_type){
+ case PRIMARY_KEY_INDEX:
+ // Do nothing, already created
+ break;
+ case PRIMARY_KEY_ORDERED_INDEX:
+ error= create_ordered_index(name, key_info);
+ break;
+ case UNIQUE_ORDERED_INDEX:
+ if (!(error= create_ordered_index(name, key_info)))
+ error= create_unique_index(unique_name, key_info);
+ break;
+ case UNIQUE_INDEX:
+ if (check_index_fields_not_null(key_info))
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_NULL_COLUMN_IN_INDEX,
+ "Ndb does not support unique index on NULL valued attributes, index access with NULL value will become full table scan");
+ }
+ error= create_unique_index(unique_name, key_info);
+ break;
+ case ORDERED_INDEX:
+ if (key_info->algorithm == HA_KEY_ALG_HASH)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ILLEGAL_HA_CREATE_OPTION,
+ ER(ER_ILLEGAL_HA_CREATE_OPTION),
+ ndbcluster_hton_name,
+ "Ndb does not support non-unique "
+ "hash based indexes");
+ error= HA_ERR_UNSUPPORTED;
+ break;
+ }
+ error= create_ordered_index(name, key_info);
+ break;
+ default:
+ DBUG_ASSERT(FALSE);
+ break;
+ }
+
+ DBUG_RETURN(error);
+}
+
+int ha_ndbcluster::create_ordered_index(const char *name,
+ KEY *key_info)
+{
+ DBUG_ENTER("ha_ndbcluster::create_ordered_index");
+ DBUG_RETURN(create_ndb_index(name, key_info, FALSE));
+}
+
+int ha_ndbcluster::create_unique_index(const char *name,
+ KEY *key_info)
+{
+
+ DBUG_ENTER("ha_ndbcluster::create_unique_index");
+ DBUG_RETURN(create_ndb_index(name, key_info, TRUE));
+}
+
+
+/**
+ Create an index in NDB Cluster.
+
+ @todo
+ Only temporary ordered indexes supported
+*/
+
+int ha_ndbcluster::create_ndb_index(const char *name,
+ KEY *key_info,
+ bool unique)
+{
+ Ndb *ndb= get_ndb();
+ NdbDictionary::Dictionary *dict= ndb->getDictionary();
+ KEY_PART_INFO *key_part= key_info->key_part;
+ KEY_PART_INFO *end= key_part + key_info->user_defined_key_parts;
+
+ DBUG_ENTER("ha_ndbcluster::create_index");
+ DBUG_PRINT("enter", ("name: %s ", name));
+
+ NdbDictionary::Index ndb_index(name);
+ if (unique)
+ ndb_index.setType(NdbDictionary::Index::UniqueHashIndex);
+ else
+ {
+ ndb_index.setType(NdbDictionary::Index::OrderedIndex);
+ // TODO Only temporary ordered indexes supported
+ ndb_index.setLogging(FALSE);
+ }
+ if (ndb_index.setTable(m_tabname))
+ {
+ DBUG_RETURN(my_errno= errno);
+ }
+
+ for (; key_part != end; key_part++)
+ {
+ Field *field= key_part->field;
+ DBUG_PRINT("info", ("attr: %s", field->field_name));
+ if (ndb_index.addColumnName(field->field_name))
+ {
+ DBUG_RETURN(my_errno= errno);
+ }
+ }
+
+ if (dict->createIndex(ndb_index, *m_table))
+ ERR_RETURN(dict->getNdbError());
+
+ // Success
+ DBUG_PRINT("info", ("Created index %s", name));
+ DBUG_RETURN(0);
+}
+
+/*
+ Prepare for an on-line alter table
+*/
+void ha_ndbcluster::prepare_for_alter()
+{
+ /* ndb_share reference schema */
+ ndbcluster_get_share(m_share); // Increase ref_count
+ DBUG_PRINT("NDB_SHARE", ("%s binlog schema use_count: %u",
+ m_share->key, m_share->use_count));
+ set_ndb_share_state(m_share, NSS_ALTERED);
+}
+
+/*
+ Add an index on-line to a table
+*/
+int ha_ndbcluster::add_index(TABLE *table_arg,
+ KEY *key_info, uint num_of_keys)
+{
+ int error= 0;
+ uint idx;
+ DBUG_ENTER("ha_ndbcluster::add_index");
+ DBUG_PRINT("enter", ("table %s", table_arg->s->table_name.str));
+ DBUG_ASSERT(m_share->state == NSS_ALTERED);
+
+ for (idx= 0; idx < num_of_keys; idx++)
+ {
+ KEY *key= key_info + idx;
+ KEY_PART_INFO *key_part= key->key_part;
+ KEY_PART_INFO *end= key_part + key->key_parts;
+ NDB_INDEX_TYPE idx_type= get_index_type_from_key(idx, key_info, false);
+ DBUG_PRINT("info", ("Adding index: '%s'", key_info[idx].name));
+ // Add fields to key_part struct
+ for (; key_part != end; key_part++)
+ key_part->field= table->field[key_part->fieldnr];
+ // Check index type
+ // Create index in ndb
+ if((error= create_index(key_info[idx].name, key, idx_type, idx)))
+ break;
+ }
+ if (error)
+ {
+ set_ndb_share_state(m_share, NSS_INITIAL);
+ /* ndb_share reference schema free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog schema free use_count: %u",
+ m_share->key, m_share->use_count));
+ free_share(&m_share); // Decrease ref_count
+ }
+ DBUG_RETURN(error);
+}
+
+/*
+ Mark one or several indexes for deletion. and
+ renumber the remaining indexes
+*/
+int ha_ndbcluster::prepare_drop_index(TABLE *table_arg,
+ uint *key_num, uint num_of_keys)
+{
+ DBUG_ENTER("ha_ndbcluster::prepare_drop_index");
+ DBUG_ASSERT(m_share->state == NSS_ALTERED);
+ // Mark indexes for deletion
+ uint idx;
+ for (idx= 0; idx < num_of_keys; idx++)
+ {
+ DBUG_PRINT("info", ("ha_ndbcluster::prepare_drop_index %u", *key_num));
+ m_index[*key_num++].status= TO_BE_DROPPED;
+ }
+ // Renumber indexes
+ THD *thd= current_thd;
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ Ndb *ndb= thd_ndb->ndb;
+ renumber_indexes(ndb, table_arg);
+ DBUG_RETURN(0);
+}
+
+/*
+ Really drop all indexes marked for deletion
+*/
+int ha_ndbcluster::final_drop_index(TABLE *table_arg)
+{
+ int error;
+ DBUG_ENTER("ha_ndbcluster::final_drop_index");
+ DBUG_PRINT("info", ("ha_ndbcluster::final_drop_index"));
+ // Really drop indexes
+ THD *thd= current_thd;
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ Ndb *ndb= thd_ndb->ndb;
+ if((error= drop_indexes(ndb, table_arg)))
+ {
+ m_share->state= NSS_INITIAL;
+ /* ndb_share reference schema free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog schema free use_count: %u",
+ m_share->key, m_share->use_count));
+ free_share(&m_share); // Decrease ref_count
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ Rename a table in NDB Cluster.
+*/
+
+int ha_ndbcluster::rename_table(const char *from, const char *to)
+{
+ NDBDICT *dict;
+ char old_dbname[FN_HEADLEN];
+ char new_dbname[FN_HEADLEN];
+ char new_tabname[FN_HEADLEN];
+ const NDBTAB *orig_tab;
+ int result;
+ bool recreate_indexes= FALSE;
+ NDBDICT::List index_list;
+
+ DBUG_ENTER("ha_ndbcluster::rename_table");
+ DBUG_PRINT("info", ("Renaming %s to %s", from, to));
+ set_dbname(from, old_dbname);
+ set_dbname(to, new_dbname);
+ set_tabname(from);
+ set_tabname(to, new_tabname);
+
+ if (check_ndb_connection())
+ DBUG_RETURN(my_errno= HA_ERR_NO_CONNECTION);
+
+ Ndb *ndb= get_ndb();
+ ndb->setDatabaseName(old_dbname);
+ dict= ndb->getDictionary();
+ Ndb_table_guard ndbtab_g(dict, m_tabname);
+ if (!(orig_tab= ndbtab_g.get_table()))
+ ERR_RETURN(dict->getNdbError());
+
+#ifdef HAVE_NDB_BINLOG
+ int ndb_table_id= orig_tab->getObjectId();
+ int ndb_table_version= orig_tab->getObjectVersion();
+
+ /* ndb_share reference temporary */
+ NDB_SHARE *share= get_share(from, 0, FALSE);
+ if (share)
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ int r __attribute__((unused))= rename_share(share, to);
+ DBUG_ASSERT(r == 0);
+ }
+#endif
+ if (my_strcasecmp(system_charset_info, new_dbname, old_dbname))
+ {
+ dict->listIndexes(index_list, *orig_tab);
+ recreate_indexes= TRUE;
+ }
+ // Change current database to that of target table
+ set_dbname(to);
+ if (ndb->setDatabaseName(m_dbname))
+ {
+ ERR_RETURN(ndb->getNdbError());
+ }
+
+ NdbDictionary::Table new_tab= *orig_tab;
+ new_tab.setName(new_tabname);
+ if (dict->alterTableGlobal(*orig_tab, new_tab) != 0)
+ {
+ NdbError ndb_error= dict->getNdbError();
+#ifdef HAVE_NDB_BINLOG
+ if (share)
+ {
+ int ret __attribute__((unused))= rename_share(share, from);
+ DBUG_ASSERT(ret == 0);
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+#endif
+ ERR_RETURN(ndb_error);
+ }
+
+ // Rename .ndb file
+ if ((result= handler::rename_table(from, to)))
+ {
+ // ToDo in 4.1 should rollback alter table...
+#ifdef HAVE_NDB_BINLOG
+ if (share)
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+#endif
+ DBUG_RETURN(result);
+ }
+
+#ifdef HAVE_NDB_BINLOG
+ int is_old_table_tmpfile= 1;
+ if (share && share->op)
+ dict->forceGCPWait();
+
+ /* handle old table */
+ if (!IS_TMP_PREFIX(m_tabname))
+ {
+ is_old_table_tmpfile= 0;
+ String event_name(INJECTOR_EVENT_LEN);
+ ndb_rep_event_name(&event_name, from + sizeof(share_prefix) - 1, 0);
+ ndbcluster_handle_drop_table(ndb, event_name.c_ptr(), share,
+ "rename table");
+ }
+
+ if (!result && !IS_TMP_PREFIX(new_tabname))
+ {
+ /* always create an event for the table */
+ String event_name(INJECTOR_EVENT_LEN);
+ ndb_rep_event_name(&event_name, to + sizeof(share_prefix) - 1, 0);
+ Ndb_table_guard ndbtab_g2(dict, new_tabname);
+ const NDBTAB *ndbtab= ndbtab_g2.get_table();
+
+ if (!ndbcluster_create_event(ndb, ndbtab, event_name.c_ptr(), share,
+ share && ndb_binlog_running ? 2 : 1/* push warning */))
+ {
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: RENAME Event: %s",
+ event_name.c_ptr());
+ if (share &&
+ ndbcluster_create_event_ops(share, ndbtab, event_name.c_ptr()))
+ {
+ sql_print_error("NDB Binlog: FAILED create event operations "
+ "during RENAME. Event %s", event_name.c_ptr());
+ /* a warning has been issued to the client */
+ }
+ }
+ /*
+ warning has been issued if ndbcluster_create_event failed
+ and (share && ndb_binlog_running)
+ */
+ if (!is_old_table_tmpfile)
+ ndbcluster_log_schema_op(current_thd, share,
+ current_thd->query(),
+ current_thd->query_length(),
+ old_dbname, m_tabname,
+ ndb_table_id, ndb_table_version,
+ SOT_RENAME_TABLE,
+ m_dbname, new_tabname);
+ }
+
+ // If we are moving tables between databases, we need to recreate
+ // indexes
+ if (recreate_indexes)
+ {
+ for (unsigned i = 0; i < index_list.count; i++)
+ {
+ NDBDICT::List::Element& index_el = index_list.elements[i];
+ // Recreate any indexes not stored in the system database
+ if (my_strcasecmp(system_charset_info,
+ index_el.database, NDB_SYSTEM_DATABASE))
+ {
+ set_dbname(from);
+ ndb->setDatabaseName(m_dbname);
+ const NDBINDEX * index= dict->getIndexGlobal(index_el.name, new_tab);
+ DBUG_PRINT("info", ("Creating index %s/%s",
+ index_el.database, index->getName()));
+ dict->createIndex(*index, new_tab);
+ DBUG_PRINT("info", ("Dropping index %s/%s",
+ index_el.database, index->getName()));
+ set_dbname(from);
+ ndb->setDatabaseName(m_dbname);
+ dict->dropIndexGlobal(*index);
+ }
+ }
+ }
+ if (share)
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+#endif
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Delete table from NDB Cluster.
+*/
+
+/* static version which does not need a handler */
+
+int
+ha_ndbcluster::delete_table(ha_ndbcluster *h, Ndb *ndb,
+ const char *path,
+ const char *db,
+ const char *table_name)
+{
+ THD *thd= current_thd;
+ DBUG_ENTER("ha_ndbcluster::ndbcluster_delete_table");
+ NDBDICT *dict= ndb->getDictionary();
+ int ndb_table_id= 0;
+ int ndb_table_version= 0;
+#ifdef HAVE_NDB_BINLOG
+ /*
+ Don't allow drop table unless
+ schema distribution table is setup
+ */
+ if (!ndb_schema_share)
+ {
+ DBUG_PRINT("info", ("Schema distribution table not setup"));
+ DBUG_ASSERT(ndb_schema_share);
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+ }
+ /* ndb_share reference temporary */
+ NDB_SHARE *share= get_share(path, 0, FALSE);
+ if (share)
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ }
+#endif
+
+ /* Drop the table from NDB */
+
+ int res= 0;
+ if (h && h->m_table)
+ {
+retry_temporary_error1:
+ if (dict->dropTableGlobal(*h->m_table) == 0)
+ {
+ ndb_table_id= h->m_table->getObjectId();
+ ndb_table_version= h->m_table->getObjectVersion();
+ DBUG_PRINT("info", ("success 1"));
+ }
+ else
+ {
+ switch (dict->getNdbError().status)
+ {
+ case NdbError::TemporaryError:
+ if (!thd->killed)
+ goto retry_temporary_error1; // retry indefinitly
+ break;
+ default:
+ break;
+ }
+ set_ndb_err(thd, dict->getNdbError());
+ res= ndb_to_mysql_error(&dict->getNdbError());
+ DBUG_PRINT("info", ("error(1) %u", res));
+ }
+ h->release_metadata(thd, ndb);
+ }
+ else
+ {
+ ndb->setDatabaseName(db);
+ while (1)
+ {
+ Ndb_table_guard ndbtab_g(dict, table_name);
+ if (ndbtab_g.get_table())
+ {
+ retry_temporary_error2:
+ if (dict->dropTableGlobal(*ndbtab_g.get_table()) == 0)
+ {
+ ndb_table_id= ndbtab_g.get_table()->getObjectId();
+ ndb_table_version= ndbtab_g.get_table()->getObjectVersion();
+ DBUG_PRINT("info", ("success 2"));
+ break;
+ }
+ else
+ {
+ switch (dict->getNdbError().status)
+ {
+ case NdbError::TemporaryError:
+ if (!thd->killed)
+ goto retry_temporary_error2; // retry indefinitly
+ break;
+ default:
+ if (dict->getNdbError().code == NDB_INVALID_SCHEMA_OBJECT)
+ {
+ ndbtab_g.invalidate();
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ set_ndb_err(thd, dict->getNdbError());
+ res= ndb_to_mysql_error(&dict->getNdbError());
+ DBUG_PRINT("info", ("error(2) %u", res));
+ break;
+ }
+ }
+
+ if (res)
+ {
+#ifdef HAVE_NDB_BINLOG
+ /* the drop table failed for some reason, drop the share anyways */
+ if (share)
+ {
+ mysql_mutex_lock(&ndbcluster_mutex);
+ if (share->state != NSS_DROPPED)
+ {
+ /*
+ The share kept by the server has not been freed, free it
+ */
+ share->state= NSS_DROPPED;
+ /* ndb_share reference create free */
+ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share, TRUE);
+ }
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share, TRUE);
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ }
+#endif
+ DBUG_RETURN(res);
+ }
+
+#ifdef HAVE_NDB_BINLOG
+ /* stop the logging of the dropped table, and cleanup */
+
+ /*
+ drop table is successful even if table does not exist in ndb
+ and in case table was actually not dropped, there is no need
+ to force a gcp, and setting the event_name to null will indicate
+ that there is no event to be dropped
+ */
+ int table_dropped= dict->getNdbError().code != 709;
+
+ if (!IS_TMP_PREFIX(table_name) && share &&
+ current_thd->lex->sql_command != SQLCOM_TRUNCATE)
+ {
+ ndbcluster_log_schema_op(thd, share,
+ thd->query(), thd->query_length(),
+ share->db, share->table_name,
+ ndb_table_id, ndb_table_version,
+ SOT_DROP_TABLE, 0, 0);
+ }
+ else if (table_dropped && share && share->op) /* ndbcluster_log_schema_op
+ will do a force GCP */
+ dict->forceGCPWait();
+
+ if (!IS_TMP_PREFIX(table_name))
+ {
+ String event_name(INJECTOR_EVENT_LEN);
+ ndb_rep_event_name(&event_name, path + sizeof(share_prefix) - 1, 0);
+ ndbcluster_handle_drop_table(ndb,
+ table_dropped ? event_name.c_ptr() : 0,
+ share, "delete table");
+ }
+
+ if (share)
+ {
+ mysql_mutex_lock(&ndbcluster_mutex);
+ if (share->state != NSS_DROPPED)
+ {
+ /*
+ The share kept by the server has not been freed, free it
+ */
+ share->state= NSS_DROPPED;
+ /* ndb_share reference create free */
+ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share, TRUE);
+ }
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share, TRUE);
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ }
+#endif
+ DBUG_RETURN(0);
+}
+
+int ha_ndbcluster::delete_table(const char *name)
+{
+ DBUG_ENTER("ha_ndbcluster::delete_table");
+ DBUG_PRINT("enter", ("name: %s", name));
+ set_dbname(name);
+ set_tabname(name);
+
+#ifdef HAVE_NDB_BINLOG
+ /*
+ Don't allow drop table unless
+ schema distribution table is setup
+ */
+ if (!ndb_schema_share)
+ {
+ DBUG_PRINT("info", ("Schema distribution table not setup"));
+ DBUG_ASSERT(ndb_schema_share);
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+ }
+#endif
+
+ if (check_ndb_connection())
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+
+ /* Call ancestor function to delete .ndb file */
+ handler::delete_table(name);
+
+ DBUG_RETURN(delete_table(this, get_ndb(),name, m_dbname, m_tabname));
+}
+
+
+void ha_ndbcluster::get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *first_value,
+ ulonglong *nb_reserved_values)
+{
+ uint cache_size;
+ Uint64 auto_value;
+ THD *thd= current_thd;
+ DBUG_ENTER("get_auto_increment");
+ DBUG_PRINT("enter", ("m_tabname: %s", m_tabname));
+ Ndb *ndb= get_ndb();
+
+ if (m_rows_inserted > m_rows_to_insert)
+ {
+ /* We guessed too low */
+ m_rows_to_insert+= m_autoincrement_prefetch;
+ }
+ uint remaining= m_rows_to_insert - m_rows_inserted;
+ ha_rows prefetch= THDVAR(thd, autoincrement_prefetch_sz);
+ uint min_prefetch=
+ (remaining < prefetch) ? prefetch : remaining;
+ cache_size= ((remaining < m_autoincrement_prefetch) ?
+ min_prefetch
+ : remaining);
+ uint retries= NDB_AUTO_INCREMENT_RETRIES;
+ int retry_sleep= 30; /* 30 milliseconds, transaction */
+ for (;;)
+ {
+ Ndb_tuple_id_range_guard g(m_share);
+ if ((m_skip_auto_increment &&
+ ndb->readAutoIncrementValue(m_table, g.range, auto_value)) ||
+ ndb->getAutoIncrementValue(m_table, g.range, auto_value, cache_size, increment, offset))
+ {
+ if (--retries &&
+ ndb->getNdbError().status == NdbError::TemporaryError)
+ {
+ my_sleep(retry_sleep);
+ continue;
+ }
+ const NdbError err= ndb->getNdbError();
+ sql_print_error("Error %lu in ::get_auto_increment(): %s",
+ (ulong) err.code, err.message);
+ *first_value= ~(ulonglong) 0;
+ DBUG_VOID_RETURN;
+ }
+ break;
+ }
+ *first_value= (longlong)auto_value;
+ /* From the point of view of MySQL, NDB reserves one row at a time */
+ *nb_reserved_values= 1;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Constructor for the NDB Cluster table handler .
+*/
+
+/*
+ Normal flags for binlogging is that ndb has HA_HAS_OWN_BINLOGGING
+ and preferes HA_BINLOG_ROW_CAPABLE
+ Other flags are set under certain circumstaces in table_flags()
+*/
+#define HA_NDBCLUSTER_TABLE_FLAGS \
+ HA_REC_NOT_IN_SEQ | \
+ HA_NULL_IN_KEY | \
+ HA_AUTO_PART_KEY | \
+ HA_NO_PREFIX_CHAR_KEYS | \
+ HA_NEED_READ_RANGE_BUFFER | \
+ HA_CAN_GEOMETRY | \
+ HA_CAN_BIT_FIELD | \
+ HA_PRIMARY_KEY_REQUIRED_FOR_POSITION | \
+ HA_PRIMARY_KEY_REQUIRED_FOR_DELETE | \
+ HA_PARTIAL_COLUMN_READ | \
+ HA_HAS_OWN_BINLOGGING | \
+ HA_BINLOG_ROW_CAPABLE | \
+ HA_HAS_RECORDS
+
+ha_ndbcluster::ha_ndbcluster(handlerton *hton, TABLE_SHARE *table_arg):
+ handler(hton, table_arg),
+ m_active_trans(NULL),
+ m_active_cursor(NULL),
+ m_table(NULL),
+ m_table_info(NULL),
+ m_table_flags(HA_NDBCLUSTER_TABLE_FLAGS),
+ m_share(0),
+ m_part_info(NULL),
+ m_use_partition_function(FALSE),
+ m_sorted(FALSE),
+ m_use_write(FALSE),
+ m_ignore_dup_key(FALSE),
+ m_has_unique_index(FALSE),
+ m_primary_key_update(FALSE),
+ m_ignore_no_key(FALSE),
+ m_rows_to_insert((ha_rows) 1),
+ m_rows_inserted((ha_rows) 0),
+ m_bulk_insert_rows((ha_rows) 1024),
+ m_rows_changed((ha_rows) 0),
+ m_bulk_insert_not_flushed(FALSE),
+ m_delete_cannot_batch(FALSE),
+ m_update_cannot_batch(FALSE),
+ m_ops_pending(0),
+ m_skip_auto_increment(TRUE),
+ m_blobs_pending(0),
+ m_blobs_offset(0),
+ m_blobs_buffer(0),
+ m_blobs_buffer_size(0),
+ m_dupkey((uint) -1),
+ m_ha_not_exact_count(FALSE),
+ m_force_send(TRUE),
+ m_autoincrement_prefetch(DEFAULT_AUTO_PREFETCH),
+ m_transaction_on(TRUE),
+ m_cond(NULL),
+ m_multi_cursor(NULL)
+{
+ int i;
+
+ DBUG_ENTER("ha_ndbcluster");
+
+ m_tabname[0]= '\0';
+ m_dbname[0]= '\0';
+
+ stats.records= ~(ha_rows)0; // uninitialized
+ stats.block_size= 1024;
+
+ for (i= 0; i < MAX_KEY; i++)
+ ndb_init_index(m_index[i]);
+
+ DBUG_VOID_RETURN;
+}
+
+
+int ha_ndbcluster::ha_initialise()
+{
+ DBUG_ENTER("ha_ndbcluster::ha_initialise");
+ if (check_ndb_in_thd(current_thd))
+ {
+ DBUG_RETURN(FALSE);
+ }
+ DBUG_RETURN(TRUE);
+}
+
+/**
+ Destructor for NDB Cluster table handler.
+*/
+
+ha_ndbcluster::~ha_ndbcluster()
+{
+ THD *thd= current_thd;
+ Ndb *ndb= thd ? check_ndb_in_thd(thd) : g_ndb;
+ DBUG_ENTER("~ha_ndbcluster");
+
+ if (m_share)
+ {
+ /* ndb_share reference handler free */
+ DBUG_PRINT("NDB_SHARE", ("%s handler free use_count: %u",
+ m_share->key, m_share->use_count));
+ free_share(&m_share);
+ }
+ release_metadata(thd, ndb);
+ my_free(m_blobs_buffer);
+ m_blobs_buffer= 0;
+
+ // Check for open cursor/transaction
+ if (m_active_cursor) {
+ }
+ DBUG_ASSERT(m_active_cursor == NULL);
+ if (m_active_trans) {
+ }
+ DBUG_ASSERT(m_active_trans == NULL);
+
+ // Discard any generated condition
+ DBUG_PRINT("info", ("Deleting generated condition"));
+ if (m_cond)
+ {
+ delete m_cond;
+ m_cond= NULL;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+
+/**
+ Open a table for further use.
+
+ - fetch metadata for this table from NDB
+ - check that table exists
+
+ @retval
+ 0 ok
+ @retval
+ < 0 Table has changed
+*/
+
+int ha_ndbcluster::open(const char *name, int mode, uint test_if_locked)
+{
+ int res;
+ KEY *key;
+ DBUG_ENTER("ha_ndbcluster::open");
+ DBUG_PRINT("enter", ("name: %s mode: %d test_if_locked: %d",
+ name, mode, test_if_locked));
+
+ /*
+ Setup ref_length to make room for the whole
+ primary key to be written in the ref variable
+ */
+
+ if (table_share->primary_key != MAX_KEY)
+ {
+ key= table->key_info+table_share->primary_key;
+ ref_length= key->key_length;
+ }
+ else // (table_share->primary_key == MAX_KEY)
+ {
+ if (m_use_partition_function)
+ {
+ ref_length+= sizeof(m_part_id);
+ }
+ }
+
+ DBUG_PRINT("info", ("ref_length: %d", ref_length));
+
+ // Init table lock structure
+ /* ndb_share reference handler */
+ if (!(m_share=get_share(name, table)))
+ DBUG_RETURN(1);
+ DBUG_PRINT("NDB_SHARE", ("%s handler use_count: %u",
+ m_share->key, m_share->use_count));
+ thr_lock_data_init(&m_share->lock,&m_lock,(void*) 0);
+
+ set_dbname(name);
+ set_tabname(name);
+
+ if ((res= check_ndb_connection()) ||
+ (res= get_metadata(name)))
+ {
+ /* ndb_share reference handler free */
+ DBUG_PRINT("NDB_SHARE", ("%s handler free use_count: %u",
+ m_share->key, m_share->use_count));
+ free_share(&m_share);
+ m_share= 0;
+ DBUG_RETURN(res);
+ }
+ while (1)
+ {
+ Ndb *ndb= get_ndb();
+ if (ndb->setDatabaseName(m_dbname))
+ {
+ set_ndb_err(current_thd, ndb->getNdbError());
+ res= ndb_to_mysql_error(&ndb->getNdbError());
+ break;
+ }
+ struct Ndb_statistics stat;
+ res= ndb_get_table_statistics(NULL, FALSE, ndb, m_table, &stat);
+ stats.mean_rec_length= stat.row_size;
+ stats.data_file_length= stat.fragment_memory;
+ stats.records= stat.row_count;
+ if(!res)
+ res= info(HA_STATUS_CONST);
+ break;
+ }
+ if (res)
+ {
+ free_share(&m_share);
+ m_share= 0;
+ release_metadata(current_thd, get_ndb());
+ DBUG_RETURN(res);
+ }
+#ifdef HAVE_NDB_BINLOG
+ if (!ndb_binlog_tables_inited)
+ {
+ table->db_stat|= HA_READ_ONLY;
+ sql_print_information("table '%s' opened read only", name);
+ }
+#endif
+ DBUG_RETURN(0);
+}
+
+/*
+ Set partition info
+
+ SYNOPSIS
+ set_part_info()
+ part_info
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ Set up partition info when handler object created
+*/
+
+void ha_ndbcluster::set_part_info(partition_info *part_info)
+{
+ m_part_info= part_info;
+ if (!(m_part_info->part_type == HASH_PARTITION &&
+ m_part_info->list_of_part_fields &&
+ !m_part_info->is_sub_partitioned()))
+ m_use_partition_function= TRUE;
+}
+
+/**
+ Close the table; release resources setup by open().
+*/
+
+int ha_ndbcluster::close(void)
+{
+ DBUG_ENTER("close");
+ THD *thd= table->in_use;
+ Ndb *ndb= thd ? check_ndb_in_thd(thd) : g_ndb;
+ /* ndb_share reference handler free */
+ DBUG_PRINT("NDB_SHARE", ("%s handler free use_count: %u",
+ m_share->key, m_share->use_count));
+ free_share(&m_share);
+ m_share= 0;
+ release_metadata(thd, ndb);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @todo
+ - Alt.1 If init fails because to many allocated Ndb
+ wait on condition for a Ndb object to be released.
+ - Alt.2 Seize/release from pool, wait until next release
+*/
+Thd_ndb* ha_ndbcluster::seize_thd_ndb()
+{
+ Thd_ndb *thd_ndb;
+ DBUG_ENTER("seize_thd_ndb");
+
+ thd_ndb= new Thd_ndb();
+ if (thd_ndb == NULL)
+ {
+ my_errno= HA_ERR_OUT_OF_MEM;
+ return NULL;
+ }
+ if (thd_ndb->ndb->init(max_transactions) != 0)
+ {
+ ERR_PRINT(thd_ndb->ndb->getNdbError());
+ /*
+ TODO
+ Alt.1 If init fails because to many allocated Ndb
+ wait on condition for a Ndb object to be released.
+ Alt.2 Seize/release from pool, wait until next release
+ */
+ delete thd_ndb;
+ thd_ndb= NULL;
+ }
+ DBUG_RETURN(thd_ndb);
+}
+
+
+void ha_ndbcluster::release_thd_ndb(Thd_ndb* thd_ndb)
+{
+ DBUG_ENTER("release_thd_ndb");
+ delete thd_ndb;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ If this thread already has a Thd_ndb object allocated
+ in current THD, reuse it. Otherwise
+ seize a Thd_ndb object, assign it to current THD and use it.
+
+*/
+
+Ndb* check_ndb_in_thd(THD* thd)
+{
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ if (!thd_ndb)
+ {
+ if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb()))
+ return NULL;
+ set_thd_ndb(thd, thd_ndb);
+ }
+ return thd_ndb->ndb;
+}
+
+
+
+int ha_ndbcluster::check_ndb_connection(THD* thd)
+{
+ Ndb *ndb;
+ DBUG_ENTER("check_ndb_connection");
+
+ if (!(ndb= check_ndb_in_thd(thd)))
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+ if (ndb->setDatabaseName(m_dbname))
+ {
+ ERR_RETURN(ndb->getNdbError());
+ }
+ DBUG_RETURN(0);
+}
+
+
+static int ndbcluster_close_connection(handlerton *hton, THD *thd)
+{
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ DBUG_ENTER("ndbcluster_close_connection");
+ if (thd_ndb)
+ {
+ ha_ndbcluster::release_thd_ndb(thd_ndb);
+ set_thd_ndb(thd, NULL); // not strictly required but does not hurt either
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Try to discover one table from NDB.
+*/
+
+int ndbcluster_discover(handlerton *hton, THD* thd, const char *db,
+ const char *name,
+ uchar **frmblob,
+ size_t *frmlen)
+{
+ int error= 0;
+ NdbError ndb_error;
+ size_t len;
+ uchar* data= NULL;
+ Ndb* ndb;
+ char key[FN_REFLEN + 1];
+ DBUG_ENTER("ndbcluster_discover");
+ DBUG_PRINT("enter", ("db: %s, name: %s", db, name));
+
+ if (!(ndb= check_ndb_in_thd(thd)))
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+ if (ndb->setDatabaseName(db))
+ {
+ ERR_RETURN(ndb->getNdbError());
+ }
+ NDBDICT* dict= ndb->getDictionary();
+ build_table_filename(key, sizeof(key) - 1, db, name, "", 0);
+ /* ndb_share reference temporary */
+ NDB_SHARE *share= get_share(key, 0, FALSE);
+ if (share)
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ }
+ if (share && get_ndb_share_state(share) == NSS_ALTERED)
+ {
+ // Frm has been altered on disk, but not yet written to ndb
+ if (readfrm(key, &data, &len))
+ {
+ DBUG_PRINT("error", ("Could not read frm"));
+ error= 1;
+ goto err;
+ }
+ }
+ else
+ {
+ Ndb_table_guard ndbtab_g(dict, name);
+ const NDBTAB *tab= ndbtab_g.get_table();
+ if (!tab)
+ {
+ const NdbError err= dict->getNdbError();
+ if (err.code == 709 || err.code == 723)
+ {
+ error= -1;
+ DBUG_PRINT("info", ("ndb_error.code: %u", ndb_error.code));
+ }
+ else
+ {
+ error= -1;
+ ndb_error= err;
+ DBUG_PRINT("info", ("ndb_error.code: %u", ndb_error.code));
+ }
+ goto err;
+ }
+ DBUG_PRINT("info", ("Found table %s", tab->getName()));
+
+ len= tab->getFrmLength();
+ if (len == 0 || tab->getFrmData() == NULL)
+ {
+ DBUG_PRINT("error", ("No frm data found."));
+ error= 1;
+ goto err;
+ }
+
+ if (unpackfrm(&data, &len, (uchar*) tab->getFrmData()))
+ {
+ DBUG_PRINT("error", ("Could not unpack table"));
+ error= 1;
+ goto err;
+ }
+ }
+
+ *frmlen= len;
+ *frmblob= data;
+
+ if (share)
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+
+ DBUG_RETURN(0);
+err:
+ my_free(data);
+ if (share)
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+ if (ndb_error.code)
+ {
+ ERR_RETURN(ndb_error);
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ Check if a table exists in NDB.
+*/
+
+int ndbcluster_table_exists_in_engine(handlerton *hton, THD* thd,
+ const char *db,
+ const char *name)
+{
+ Ndb* ndb;
+ DBUG_ENTER("ndbcluster_table_exists_in_engine");
+ DBUG_PRINT("enter", ("db: %s name: %s", db, name));
+
+ if (!(ndb= check_ndb_in_thd(thd)))
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+ NDBDICT* dict= ndb->getDictionary();
+ NdbDictionary::Dictionary::List list;
+ if (dict->listObjects(list, NdbDictionary::Object::UserTable) != 0)
+ ERR_RETURN(dict->getNdbError());
+ for (uint i= 0 ; i < list.count ; i++)
+ {
+ NdbDictionary::Dictionary::List::Element& elmt= list.elements[i];
+ if (my_strcasecmp(system_charset_info, elmt.database, db))
+ continue;
+ if (my_strcasecmp(system_charset_info, elmt.name, name))
+ continue;
+ DBUG_PRINT("info", ("Found table"));
+ DBUG_RETURN(HA_ERR_TABLE_EXIST);
+ }
+ DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
+}
+
+
+
+extern "C" uchar* tables_get_key(const char *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= strlen(entry);
+ return (uchar*) entry;
+}
+
+
+/**
+ Drop a database in NDB Cluster
+
+ @note
+ add a dummy void function, since stupid handlerton is returning void instead of int...
+*/
+int ndbcluster_drop_database_impl(const char *path)
+{
+ DBUG_ENTER("ndbcluster_drop_database");
+ THD *thd= current_thd;
+ char dbname[FN_HEADLEN];
+ Ndb* ndb;
+ NdbDictionary::Dictionary::List list;
+ uint i;
+ char *tabname;
+ List<char> drop_list;
+ int ret= 0;
+ ha_ndbcluster::set_dbname(path, (char *)&dbname);
+ DBUG_PRINT("enter", ("db: %s", dbname));
+
+ if (!(ndb= check_ndb_in_thd(thd)))
+ DBUG_RETURN(-1);
+
+ // List tables in NDB
+ NDBDICT *dict= ndb->getDictionary();
+ if (dict->listObjects(list,
+ NdbDictionary::Object::UserTable) != 0)
+ DBUG_RETURN(-1);
+ for (i= 0 ; i < list.count ; i++)
+ {
+ NdbDictionary::Dictionary::List::Element& elmt= list.elements[i];
+ DBUG_PRINT("info", ("Found %s/%s in NDB", elmt.database, elmt.name));
+
+ // Add only tables that belongs to db
+ if (my_strcasecmp(system_charset_info, elmt.database, dbname))
+ continue;
+ DBUG_PRINT("info", ("%s must be dropped", elmt.name));
+ drop_list.push_back(thd->strdup(elmt.name));
+ }
+ // Drop any tables belonging to database
+ char full_path[FN_REFLEN + 1];
+ char *tmp= full_path +
+ build_table_filename(full_path, sizeof(full_path) - 1, dbname, "", "", 0);
+ if (ndb->setDatabaseName(dbname))
+ {
+ ERR_RETURN(ndb->getNdbError());
+ }
+ List_iterator_fast<char> it(drop_list);
+ while ((tabname=it++))
+ {
+ tablename_to_filename(tabname, tmp, FN_REFLEN - (tmp - full_path)-1);
+ if (ha_ndbcluster::delete_table(0, ndb, full_path, dbname, tabname))
+ {
+ const NdbError err= dict->getNdbError();
+ if (err.code != 709 && err.code != 723)
+ {
+ set_ndb_err(thd, err);
+ ret= ndb_to_mysql_error(&err);
+ }
+ }
+ }
+ DBUG_RETURN(ret);
+}
+
+static void ndbcluster_drop_database(handlerton *hton, char *path)
+{
+ DBUG_ENTER("ndbcluster_drop_database");
+#ifdef HAVE_NDB_BINLOG
+ /*
+ Don't allow drop database unless
+ schema distribution table is setup
+ */
+ if (!ndb_schema_share)
+ {
+ DBUG_PRINT("info", ("Schema distribution table not setup"));
+ DBUG_ASSERT(ndb_schema_share);
+ DBUG_VOID_RETURN;
+ }
+#endif
+ ndbcluster_drop_database_impl(path);
+#ifdef HAVE_NDB_BINLOG
+ char db[FN_REFLEN];
+ THD *thd= current_thd;
+ ha_ndbcluster::set_dbname(path, db);
+ ndbcluster_log_schema_op(thd, 0,
+ thd->query(), thd->query_length(),
+ db, "", 0, 0, SOT_DROP_DB, 0, 0);
+#endif
+ DBUG_VOID_RETURN;
+}
+
+int ndb_create_table_from_engine(THD *thd, const char *db,
+ const char *table_name)
+{
+ LEX *old_lex= thd->lex, newlex;
+ thd->lex= &newlex;
+ newlex.current_select= NULL;
+ int res= ha_create_table_from_engine(thd, db, table_name);
+ thd->lex= old_lex;
+ return res;
+}
+
+/*
+ find all tables in ndb and discover those needed
+*/
+int ndbcluster_find_all_files(THD *thd)
+{
+ Ndb* ndb;
+ char key[FN_REFLEN + 1];
+ NDBDICT *dict;
+ int unhandled, retries= 5, skipped;
+ DBUG_ENTER("ndbcluster_find_all_files");
+
+ if (!(ndb= check_ndb_in_thd(thd)))
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+
+ dict= ndb->getDictionary();
+
+ LINT_INIT(unhandled);
+ LINT_INIT(skipped);
+ do
+ {
+ NdbDictionary::Dictionary::List list;
+ if (dict->listObjects(list, NdbDictionary::Object::UserTable) != 0)
+ ERR_RETURN(dict->getNdbError());
+ unhandled= 0;
+ skipped= 0;
+ retries--;
+ for (uint i= 0 ; i < list.count ; i++)
+ {
+ NDBDICT::List::Element& elmt= list.elements[i];
+ if (IS_TMP_PREFIX(elmt.name) || IS_NDB_BLOB_PREFIX(elmt.name))
+ {
+ DBUG_PRINT("info", ("Skipping %s.%s in NDB", elmt.database, elmt.name));
+ continue;
+ }
+ DBUG_PRINT("info", ("Found %s.%s in NDB", elmt.database, elmt.name));
+ if (elmt.state != NDBOBJ::StateOnline &&
+ elmt.state != NDBOBJ::StateBackup &&
+ elmt.state != NDBOBJ::StateBuilding)
+ {
+ sql_print_information("NDB: skipping setup table %s.%s, in state %d",
+ elmt.database, elmt.name, elmt.state);
+ skipped++;
+ continue;
+ }
+
+ ndb->setDatabaseName(elmt.database);
+ Ndb_table_guard ndbtab_g(dict, elmt.name);
+ const NDBTAB *ndbtab= ndbtab_g.get_table();
+ if (!ndbtab)
+ {
+ if (retries == 0)
+ sql_print_error("NDB: failed to setup table %s.%s, error: %d, %s",
+ elmt.database, elmt.name,
+ dict->getNdbError().code,
+ dict->getNdbError().message);
+ unhandled++;
+ continue;
+ }
+
+ if (ndbtab->getFrmLength() == 0)
+ continue;
+
+ /* check if database exists */
+ char *end= key +
+ build_table_filename(key, sizeof(key) - 1, elmt.database, "", "", 0);
+ if (my_access(key, F_OK))
+ {
+ /* no such database defined, skip table */
+ continue;
+ }
+ /* finalize construction of path */
+ end+= tablename_to_filename(elmt.name, end,
+ sizeof(key)-(end-key));
+ uchar *data= 0, *pack_data= 0;
+ size_t length, pack_length;
+ int discover= 0;
+ if (readfrm(key, &data, &length) ||
+ packfrm(data, length, &pack_data, &pack_length))
+ {
+ discover= 1;
+ sql_print_information("NDB: missing frm for %s.%s, discovering...",
+ elmt.database, elmt.name);
+ }
+ else if (cmp_frm(ndbtab, pack_data, pack_length))
+ {
+ /* ndb_share reference temporary */
+ NDB_SHARE *share= get_share(key, 0, FALSE);
+ if (share)
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ }
+ if (!share || get_ndb_share_state(share) != NSS_ALTERED)
+ {
+ discover= 1;
+ sql_print_information("NDB: mismatch in frm for %s.%s, discovering...",
+ elmt.database, elmt.name);
+ }
+ if (share)
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+ }
+ my_free(data);
+ my_free(pack_data);
+
+ if (discover)
+ {
+ /* ToDo 4.1 database needs to be created if missing */
+ if (ndb_create_table_from_engine(thd, elmt.database, elmt.name))
+ {
+ /* ToDo 4.1 handle error */
+ }
+ }
+#ifdef HAVE_NDB_BINLOG
+ else
+ {
+ /* set up replication for this table */
+ ndbcluster_create_binlog_setup(ndb, key, end-key,
+ elmt.database, elmt.name,
+ TRUE);
+ }
+#endif
+ }
+ }
+ while (unhandled && retries);
+
+ DBUG_RETURN(-(skipped + unhandled));
+}
+
+int ndbcluster_find_files(handlerton *hton, THD *thd,
+ const char *db,
+ const char *path,
+ const char *wild, bool dir, List<LEX_STRING> *files)
+{
+ DBUG_ENTER("ndbcluster_find_files");
+ DBUG_PRINT("enter", ("db: %s", db));
+ { // extra bracket to avoid gcc 2.95.3 warning
+ uint i;
+ Ndb* ndb;
+ char name[FN_REFLEN + 1];
+ HASH ndb_tables, ok_tables;
+ NDBDICT::List list;
+
+ if (!(ndb= check_ndb_in_thd(thd)))
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+
+ if (dir)
+ DBUG_RETURN(0); // Discover of databases not yet supported
+
+ // List tables in NDB
+ NDBDICT *dict= ndb->getDictionary();
+ if (dict->listObjects(list,
+ NdbDictionary::Object::UserTable) != 0)
+ ERR_RETURN(dict->getNdbError());
+
+ if (my_hash_init(&ndb_tables, system_charset_info,list.count,0,0,
+ (my_hash_get_key)tables_get_key,0,0))
+ {
+ DBUG_PRINT("error", ("Failed to init HASH ndb_tables"));
+ DBUG_RETURN(-1);
+ }
+
+ if (my_hash_init(&ok_tables, system_charset_info,32,0,0,
+ (my_hash_get_key)tables_get_key,0,0))
+ {
+ DBUG_PRINT("error", ("Failed to init HASH ok_tables"));
+ my_hash_free(&ndb_tables);
+ DBUG_RETURN(-1);
+ }
+
+ for (i= 0 ; i < list.count ; i++)
+ {
+ NDBDICT::List::Element& elmt= list.elements[i];
+ if (IS_TMP_PREFIX(elmt.name) || IS_NDB_BLOB_PREFIX(elmt.name))
+ {
+ DBUG_PRINT("info", ("Skipping %s.%s in NDB", elmt.database, elmt.name));
+ continue;
+ }
+ DBUG_PRINT("info", ("Found %s/%s in NDB", elmt.database, elmt.name));
+
+ // Add only tables that belongs to db
+ if (my_strcasecmp(system_charset_info, elmt.database, db))
+ continue;
+
+ // Apply wildcard to list of tables in NDB
+ if (wild)
+ {
+ if (lower_case_table_names)
+ {
+ if (wild_case_compare(files_charset_info, elmt.name, wild))
+ continue;
+ }
+ else if (wild_compare(elmt.name,wild,0))
+ continue;
+ }
+ DBUG_PRINT("info", ("Inserting %s into ndb_tables hash", elmt.name));
+ my_hash_insert(&ndb_tables, (uchar*)thd->strdup(elmt.name));
+ }
+
+ LEX_STRING *file_name;
+ List_iterator<LEX_STRING> it(*files);
+ List<char> delete_list;
+ char *file_name_str;
+ while ((file_name=it++))
+ {
+ bool file_on_disk= FALSE;
+ DBUG_PRINT("info", ("%s", file_name->str));
+ if (my_hash_search(&ndb_tables, (uchar*) file_name->str,
+ file_name->length))
+ {
+ build_table_filename(name, sizeof(name) - 1, db,
+ file_name->str, reg_ext, 0);
+ if (my_access(name, F_OK))
+ {
+ DBUG_PRINT("info", ("Table %s listed and need discovery",
+ file_name->str));
+ if (ndb_create_table_from_engine(thd, db, file_name->str))
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TABLE_EXISTS_ERROR,
+ "Discover of table %s.%s failed",
+ db, file_name->str);
+ continue;
+ }
+ }
+ DBUG_PRINT("info", ("%s existed in NDB _and_ on disk ", file_name->str));
+ file_on_disk= TRUE;
+ }
+
+ // Check for .ndb file with this name
+ build_table_filename(name, sizeof(name) - 1, db,
+ file_name->str, ha_ndb_ext, 0);
+ DBUG_PRINT("info", ("Check access for %s", name));
+ if (my_access(name, F_OK))
+ {
+ DBUG_PRINT("info", ("%s did not exist on disk", name));
+ // .ndb file did not exist on disk, another table type
+ if (file_on_disk)
+ {
+ // Ignore this ndb table
+ uchar *record= my_hash_search(&ndb_tables, (uchar*) file_name->str,
+ file_name->length);
+ DBUG_ASSERT(record);
+ my_hash_delete(&ndb_tables, record);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TABLE_EXISTS_ERROR,
+ "Local table %s.%s shadows ndb table",
+ db, file_name->str);
+ }
+ continue;
+ }
+ if (file_on_disk)
+ {
+ // File existed in NDB and as frm file, put in ok_tables list
+ my_hash_insert(&ok_tables, (uchar*) file_name->str);
+ continue;
+ }
+ DBUG_PRINT("info", ("%s existed on disk", name));
+ // The .ndb file exists on disk, but it's not in list of tables in ndb
+ // Verify that handler agrees table is gone.
+ if (ndbcluster_table_exists_in_engine(hton, thd, db, file_name->str) ==
+ HA_ERR_NO_SUCH_TABLE)
+ {
+ DBUG_PRINT("info", ("NDB says %s does not exists", file_name->str));
+ it.remove();
+ // Put in list of tables to remove from disk
+ delete_list.push_back(thd->strdup(file_name->str));
+ }
+ }
+
+#ifdef HAVE_NDB_BINLOG
+ /* setup logging to binlog for all discovered tables */
+ {
+ char *end, *end1= name +
+ build_table_filename(name, sizeof(name) - 1, db, "", "", 0);
+ for (i= 0; i < ok_tables.records; i++)
+ {
+ file_name_str= (char*)my_hash_element(&ok_tables, i);
+ end= end1 +
+ tablename_to_filename(file_name_str, end1, sizeof(name) - (end1 - name));
+ ndbcluster_create_binlog_setup(ndb, name, end-name,
+ db, file_name_str, TRUE);
+ }
+ }
+#endif
+
+ // Check for new files to discover
+ DBUG_PRINT("info", ("Checking for new files to discover"));
+ List<char> create_list;
+ for (i= 0 ; i < ndb_tables.records ; i++)
+ {
+ file_name_str= (char*) my_hash_element(&ndb_tables, i);
+ if (!my_hash_search(&ok_tables, (uchar*) file_name_str,
+ strlen(file_name_str)))
+ {
+ build_table_filename(name, sizeof(name) - 1,
+ db, file_name_str, reg_ext, 0);
+ if (my_access(name, F_OK))
+ {
+ DBUG_PRINT("info", ("%s must be discovered", file_name_str));
+ // File is in list of ndb tables and not in ok_tables
+ // This table need to be created
+ create_list.push_back(thd->strdup(file_name_str));
+ }
+ }
+ }
+
+ /*
+ Delete old files.
+
+ ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog
+ thread in situations when some tables are already open. This means that
+ code below will try to obtain exclusive metadata lock on some table
+ while holding shared meta-data lock on other tables. This might lead to a
+ deadlock but such a deadlock should be detected by MDL deadlock detector.
+
+ XXX: the scenario described above is not covered with any test.
+ */
+ List_iterator_fast<char> it3(delete_list);
+ while ((file_name_str= it3++))
+ {
+ DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str));
+ /* Delete the table and all related files. */
+ TABLE_LIST table_list;
+ table_list.init_one_table(db, strlen(db), file_name_str,
+ strlen(file_name_str), file_name_str,
+ TL_WRITE);
+ table_list.mdl_request.set_type(MDL_EXCLUSIVE);
+ (void)mysql_rm_table_part2(thd, &table_list,
+ FALSE, /* if_exists */
+ FALSE, /* drop_temporary */
+ FALSE, /* drop_view */
+ TRUE /* dont_log_query*/);
+ trans_commit_implicit(thd); /* Safety, should be unnecessary. */
+ thd->mdl_context.release_transactional_locks();
+ /* Clear error message that is returned when table is deleted */
+ thd->clear_error();
+ }
+
+ /* Lock mutex before creating .FRM files. */
+ /* Create new files. */
+ List_iterator_fast<char> it2(create_list);
+ while ((file_name_str=it2++))
+ {
+ DBUG_PRINT("info", ("Table %s need discovery", file_name_str));
+ if (ndb_create_table_from_engine(thd, db, file_name_str) == 0)
+ {
+ LEX_STRING *tmp_file_name= 0;
+ tmp_file_name= thd->make_lex_string(tmp_file_name, file_name_str,
+ strlen(file_name_str), TRUE);
+ files->push_back(tmp_file_name);
+ }
+ }
+
+ my_hash_free(&ok_tables);
+ my_hash_free(&ndb_tables);
+
+ // Delete schema file from files
+ if (!strcmp(db, NDB_REP_DB))
+ {
+ uint count = 0;
+ while (count++ < files->elements)
+ {
+ file_name = (LEX_STRING *)files->pop();
+ if (!strcmp(file_name->str, NDB_SCHEMA_TABLE))
+ {
+ DBUG_PRINT("info", ("skip %s.%s table, it should be hidden to user",
+ NDB_REP_DB, NDB_SCHEMA_TABLE));
+ continue;
+ }
+ files->push_back(file_name);
+ }
+ }
+ } // extra bracket to avoid gcc 2.95.3 warning
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Initialise all gloal variables before creating
+ a NDB Cluster table handler
+ */
+
+/* Call back after cluster connect */
+static int connect_callback()
+{
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ update_status_variables(g_ndb_cluster_connection);
+
+ uint node_id, i= 0;
+ Ndb_cluster_connection_node_iter node_iter;
+ memset((void *)g_node_id_map, 0xFFFF, sizeof(g_node_id_map));
+ while ((node_id= g_ndb_cluster_connection->get_next_node(node_iter)))
+ g_node_id_map[node_id]= i++;
+
+ mysql_cond_signal(&COND_ndb_util_thread);
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+ return 0;
+}
+
+extern int ndb_dictionary_is_mysqld;
+
+#ifdef HAVE_PSI_INTERFACE
+
+#ifdef HAVE_NDB_BINLOG
+PSI_mutex_key key_injector_mutex, key_ndb_schema_share_mutex,
+ key_ndb_schema_object_mutex;
+#endif /* HAVE_NDB_BINLOG */
+
+PSI_mutex_key key_NDB_SHARE_mutex, key_ndbcluster_mutex,
+ key_LOCK_ndb_util_thread;
+
+static PSI_mutex_info all_ndbcluster_mutexes[]=
+{
+#ifdef HAVE_NDB_BINLOG
+ {& key_injector_mutex, "injector_mutex", PSI_FLAG_GLOBAL},
+ {& key_ndb_schema_share_mutex, "ndb_schema_share_mutex", PSI_FLAG_GLOBAL},
+ {& key_ndb_schema_object_mutex, "ndb_schema_object_mutex", PSI_FLAG_GLOBAL},
+#endif /* HAVE_NDB_BINLOG */
+ {& key_NDB_SHARE_mutex, "NDB_SHARE::mutex", PSI_FLAG_GLOBAL},
+ {& key_ndbcluster_mutex, "ndbcluster_mutex", PSI_FLAG_GLOBAL},
+ {& key_LOCK_ndb_util_thread, "LOCK_ndb_util_thread", PSI_FLAG_GLOBAL}
+};
+
+#ifdef HAVE_NDB_BINLOG
+PSI_cond_key key_injector_cond;
+#endif /* HAVE_NDB_BINLOG */
+
+PSI_cond_key key_COND_ndb_util_thread, key_COND_ndb_util_ready;
+
+static PSI_cond_info all_ndbcluster_conds[]=
+{
+#ifdef HAVE_NDB_BINLOG
+ {& key_injector_cond, "injector_cond", PSI_FLAG_GLOBAL},
+#endif /* HAVE_NDB_BINLOG */
+ {& key_COND_ndb_util_thread, "COND_ndb_util_thread", PSI_FLAG_GLOBAL},
+ {& key_COND_ndb_util_ready, "COND_ndb_util_ready", PSI_FLAG_GLOBAL}
+};
+
+#ifdef HAVE_NDB_BINLOG
+PSI_thread_key key_thread_ndb_binlog;
+#endif /* HAVE_NDB_BINLOG */
+PSI_thread_key key_thread_ndb_util;
+
+static PSI_thread_info all_ndbcluster_threads[]=
+{
+#ifdef HAVE_NDB_BINLOG
+ { &key_thread_ndb_binlog, "ndb_binlog", PSI_FLAG_GLOBAL},
+#endif /* HAVE_NDB_BINLOG */
+ { &key_thread_ndb_util, "ndb_util", PSI_FLAG_GLOBAL}
+};
+
+PSI_file_key key_file_ndb;
+
+static PSI_file_info all_ndbcluster_files[]=
+{
+ { &key_file_ndb, "ndb", 0}
+};
+
+void init_ndbcluster_psi_keys()
+{
+ const char* category= "ndbcluster";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_ndbcluster_mutexes);
+ PSI_server->register_mutex(category, all_ndbcluster_mutexes, count);
+
+ count= array_elements(all_ndbcluster_conds);
+ PSI_server->register_cond(category, all_ndbcluster_conds, count);
+
+ count= array_elements(all_ndbcluster_threads);
+ PSI_server->register_thread(category, all_ndbcluster_threads, count);
+
+ count= array_elements(all_ndbcluster_files);
+ PSI_server->register_file(category, all_ndbcluster_files, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+static int ndbcluster_init(void *p)
+{
+ int res;
+ DBUG_ENTER("ndbcluster_init");
+
+ if (ndbcluster_inited)
+ DBUG_RETURN(FALSE);
+
+#ifdef HAVE_PSI_INTERFACE
+ init_ndbcluster_psi_keys();
+#endif
+
+ mysql_mutex_init(key_ndbcluster_mutex,
+ &ndbcluster_mutex, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_ndb_util_thread,
+ &LOCK_ndb_util_thread, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_ndb_util_thread, &COND_ndb_util_thread, NULL);
+ mysql_cond_init(key_COND_ndb_util_ready, &COND_ndb_util_ready, NULL);
+ ndb_util_thread_running= -1;
+ ndbcluster_terminating= 0;
+ ndb_dictionary_is_mysqld= 1;
+ ndbcluster_hton= (handlerton *)p;
+
+ {
+ handlerton *h= ndbcluster_hton;
+ h->state= SHOW_OPTION_YES;
+ h->db_type= DB_TYPE_NDBCLUSTER;
+ h->close_connection= ndbcluster_close_connection;
+ h->commit= ndbcluster_commit;
+ h->rollback= ndbcluster_rollback;
+ h->create= ndbcluster_create_handler; /* Create a new handler */
+ h->drop_database= ndbcluster_drop_database; /* Drop a database */
+ h->panic= ndbcluster_end; /* Panic call */
+ h->show_status= ndbcluster_show_status; /* Show status */
+ h->alter_tablespace= ndbcluster_alter_tablespace; /* Show status */
+ h->partition_flags= ndbcluster_partition_flags; /* Partition flags */
+ h->alter_table_flags=ndbcluster_alter_table_flags; /* Alter table flags */
+ h->fill_is_table= ndbcluster_fill_is_table;
+#ifdef HAVE_NDB_BINLOG
+ ndbcluster_binlog_init_handlerton();
+#endif
+ h->flags= HTON_CAN_RECREATE | HTON_TEMPORARY_NOT_SUPPORTED;
+ h->discover= ndbcluster_discover;
+ h->find_files= ndbcluster_find_files;
+ h->table_exists_in_engine= ndbcluster_table_exists_in_engine;
+ }
+
+ // Format the connect string to be used for connecting to the cluster
+ int pos= 0;
+ char connectstring_buf[1024] = {0};
+ if (opt_ndb_nodeid != 0)
+ pos+= my_snprintf(connectstring_buf, sizeof(connectstring_buf),
+ "nodeid=%u", opt_ndb_nodeid);
+ if (opt_ndb_mgmd_host)
+ pos+= my_snprintf(connectstring_buf+pos, sizeof(connectstring_buf)-pos,
+ "%s%s", pos ? "," : "", opt_ndb_mgmd_host);
+ if (opt_ndb_connectstring)
+ pos+= my_snprintf(connectstring_buf+pos, sizeof(connectstring_buf)-pos,
+ "%s%s", pos ? "," : "", opt_ndb_connectstring);
+
+
+ // Initialize ndb interface
+ ndb_init_internal();
+
+ // Set connectstring if specified
+ if (opt_ndb_connectstring != 0)
+ DBUG_PRINT("connectstring", ("%s", opt_ndb_connectstring));
+ if ((g_ndb_cluster_connection=
+ new Ndb_cluster_connection(opt_ndb_connectstring)) == 0)
+ {
+ DBUG_PRINT("error",("Ndb_cluster_connection(%s)",
+ opt_ndb_connectstring));
+ my_errno= HA_ERR_OUT_OF_MEM;
+ goto ndbcluster_init_error;
+ }
+ {
+ char buf[128];
+ my_snprintf(buf, sizeof(buf), "mysqld --server-id=%lu", server_id);
+ g_ndb_cluster_connection->set_name(buf);
+ }
+ g_ndb_cluster_connection->set_optimized_node_selection
+ (THDVAR(0, optimized_node_selection));
+
+ // Create a Ndb object to open the connection to NDB
+ if ( (g_ndb= new Ndb(g_ndb_cluster_connection, "sys")) == 0 )
+ {
+ DBUG_PRINT("error", ("failed to create global ndb object"));
+ my_errno= HA_ERR_OUT_OF_MEM;
+ goto ndbcluster_init_error;
+ }
+ if (g_ndb->init() != 0)
+ {
+ ERR_PRINT (g_ndb->getNdbError());
+ goto ndbcluster_init_error;
+ }
+
+ if ((res= g_ndb_cluster_connection->connect(0,0,0)) == 0)
+ {
+ connect_callback();
+ DBUG_PRINT("info",("NDBCLUSTER storage engine at %s on port %d",
+ g_ndb_cluster_connection->get_connected_host(),
+ g_ndb_cluster_connection->get_connected_port()));
+ g_ndb_cluster_connection->wait_until_ready(10,3);
+ }
+ else if (res == 1)
+ {
+ if (g_ndb_cluster_connection->start_connect_thread(connect_callback))
+ {
+ DBUG_PRINT("error", ("g_ndb_cluster_connection->start_connect_thread()"));
+ goto ndbcluster_init_error;
+ }
+#ifndef DBUG_OFF
+ {
+ char buf[1024];
+ DBUG_PRINT("info",
+ ("NDBCLUSTER storage engine not started, "
+ "will connect using %s",
+ g_ndb_cluster_connection->
+ get_connectstring(buf,sizeof(buf))));
+ }
+#endif
+ }
+ else
+ {
+ DBUG_ASSERT(res == -1);
+ DBUG_PRINT("error", ("permanent error"));
+ goto ndbcluster_init_error;
+ }
+
+ (void) my_hash_init(&ndbcluster_open_tables,system_charset_info,32,0,0,
+ (my_hash_get_key) ndbcluster_get_key,0,0);
+#ifdef HAVE_NDB_BINLOG
+ /* start the ndb injector thread */
+ if (ndbcluster_binlog_start())
+ goto ndbcluster_init_error;
+#endif /* HAVE_NDB_BINLOG */
+
+ // Create utility thread
+ pthread_t tmp;
+ if (mysql_thread_create(key_thread_ndb_util,
+ &tmp, &connection_attrib, ndb_util_thread_func, 0))
+ {
+ DBUG_PRINT("error", ("Could not create ndb utility thread"));
+ my_hash_free(&ndbcluster_open_tables);
+ mysql_mutex_destroy(&ndbcluster_mutex);
+ mysql_mutex_destroy(&LOCK_ndb_util_thread);
+ mysql_cond_destroy(&COND_ndb_util_thread);
+ mysql_cond_destroy(&COND_ndb_util_ready);
+ goto ndbcluster_init_error;
+ }
+
+ /* Wait for the util thread to start */
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ while (ndb_util_thread_running < 0)
+ mysql_cond_wait(&COND_ndb_util_ready, &LOCK_ndb_util_thread);
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+
+ if (!ndb_util_thread_running)
+ {
+ DBUG_PRINT("error", ("ndb utility thread exited prematurely"));
+ my_hash_free(&ndbcluster_open_tables);
+ mysql_mutex_destroy(&ndbcluster_mutex);
+ mysql_mutex_destroy(&LOCK_ndb_util_thread);
+ mysql_cond_destroy(&COND_ndb_util_thread);
+ mysql_cond_destroy(&COND_ndb_util_ready);
+ goto ndbcluster_init_error;
+ }
+
+ ndbcluster_inited= 1;
+ DBUG_RETURN(FALSE);
+
+ndbcluster_init_error:
+ if (g_ndb)
+ delete g_ndb;
+ g_ndb= NULL;
+ if (g_ndb_cluster_connection)
+ delete g_ndb_cluster_connection;
+ g_ndb_cluster_connection= NULL;
+ ndbcluster_hton->state= SHOW_OPTION_DISABLED; // If we couldn't use handler
+
+ DBUG_RETURN(TRUE);
+}
+
+/**
+ Used to fill in INFORMATION_SCHEMA* tables.
+
+ @param hton handle to the handlerton structure
+ @param thd the thread/connection descriptor
+ @param[in,out] tables the information schema table that is filled up
+ @param cond used for conditional pushdown to storage engine
+ @param schema_table_idx the table id that distinguishes the type of table
+
+ @return Operation status
+ */
+static int ndbcluster_fill_is_table(handlerton *hton,
+ THD *thd,
+ TABLE_LIST *tables,
+ COND *cond,
+ enum enum_schema_tables schema_table_idx)
+{
+ int ret= 0;
+
+ if (schema_table_idx == SCH_FILES)
+ {
+ ret= ndbcluster_fill_files_table(hton, thd, tables, cond);
+ }
+
+ return ret;
+}
+
+
+static int ndbcluster_end(handlerton *hton, ha_panic_function type)
+{
+ DBUG_ENTER("ndbcluster_end");
+
+ if (!ndbcluster_inited)
+ DBUG_RETURN(0);
+ ndbcluster_inited= 0;
+
+ /* wait for util thread to finish */
+ sql_print_information("Stopping Cluster Utility thread");
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ ndbcluster_terminating= 1;
+ mysql_cond_signal(&COND_ndb_util_thread);
+ while (ndb_util_thread_running > 0)
+ mysql_cond_wait(&COND_ndb_util_ready, &LOCK_ndb_util_thread);
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+
+
+#ifdef HAVE_NDB_BINLOG
+ {
+ mysql_mutex_lock(&ndbcluster_mutex);
+ while (ndbcluster_open_tables.records)
+ {
+ NDB_SHARE *share=
+ (NDB_SHARE*) my_hash_element(&ndbcluster_open_tables, 0);
+#ifndef DBUG_OFF
+ fprintf(stderr, "NDB: table share %s with use_count %d not freed\n",
+ share->key, share->use_count);
+#endif
+ ndbcluster_real_free_share(&share);
+ }
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ }
+#endif
+ my_hash_free(&ndbcluster_open_tables);
+
+ if (g_ndb)
+ {
+#ifndef DBUG_OFF
+ Ndb::Free_list_usage tmp;
+ tmp.m_name= 0;
+ while (g_ndb->get_free_list_usage(&tmp))
+ {
+ uint leaked= (uint) tmp.m_created - tmp.m_free;
+ if (leaked)
+ fprintf(stderr, "NDB: Found %u %s%s that %s not been released\n",
+ leaked, tmp.m_name,
+ (leaked == 1)?"":"'s",
+ (leaked == 1)?"has":"have");
+ }
+#endif
+ delete g_ndb;
+ g_ndb= NULL;
+ }
+ delete g_ndb_cluster_connection;
+ g_ndb_cluster_connection= NULL;
+
+ // cleanup ndb interface
+ ndb_end_internal();
+
+ mysql_mutex_destroy(&ndbcluster_mutex);
+ mysql_mutex_destroy(&LOCK_ndb_util_thread);
+ mysql_cond_destroy(&COND_ndb_util_thread);
+ mysql_cond_destroy(&COND_ndb_util_ready);
+ DBUG_RETURN(0);
+}
+
+void ha_ndbcluster::print_error(int error, myf errflag)
+{
+ DBUG_ENTER("ha_ndbcluster::print_error");
+ DBUG_PRINT("enter", ("error: %d", error));
+
+ if (error == HA_ERR_NO_PARTITION_FOUND)
+ m_part_info->print_no_partition_found(table);
+ else
+ handler::print_error(error, errflag);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Static error print function called from static handler method
+ ndbcluster_commit and ndbcluster_rollback.
+*/
+
+void ndbcluster_print_error(int error, const NdbOperation *error_op)
+{
+ DBUG_ENTER("ndbcluster_print_error");
+ TABLE_SHARE share;
+ const char *tab_name= (error_op) ? error_op->getTableName() : "";
+ share.db.str= (char*) "";
+ share.db.length= 0;
+ share.table_name.str= (char *) tab_name;
+ share.table_name.length= strlen(tab_name);
+ ha_ndbcluster error_handler(ndbcluster_hton, &share);
+ error_handler.print_error(error, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Set a given location from full pathname to database name.
+*/
+
+void ha_ndbcluster::set_dbname(const char *path_name, char *dbname)
+{
+ char *end, *ptr, *tmp_name;
+ char tmp_buff[FN_REFLEN + 1];
+
+ tmp_name= tmp_buff;
+ /* Scan name from the end */
+ ptr= strend(path_name)-1;
+ while (ptr >= path_name && *ptr != '\\' && *ptr != '/') {
+ ptr--;
+ }
+ ptr--;
+ end= ptr;
+ while (ptr >= path_name && *ptr != '\\' && *ptr != '/') {
+ ptr--;
+ }
+ uint name_len= end - ptr;
+ memcpy(tmp_name, ptr + 1, name_len);
+ tmp_name[name_len]= '\0';
+#ifdef __WIN__
+ /* Put to lower case */
+
+ ptr= tmp_name;
+
+ while (*ptr != '\0') {
+ *ptr= tolower(*ptr);
+ ptr++;
+ }
+#endif
+ filename_to_tablename(tmp_name, dbname, sizeof(tmp_buff) - 1);
+}
+
+/**
+ Set m_dbname from full pathname to table file.
+*/
+
+void ha_ndbcluster::set_dbname(const char *path_name)
+{
+ set_dbname(path_name, m_dbname);
+}
+
+/**
+ Set a given location from full pathname to table file.
+*/
+
+void
+ha_ndbcluster::set_tabname(const char *path_name, char * tabname)
+{
+ char *end, *ptr, *tmp_name;
+ char tmp_buff[FN_REFLEN + 1];
+
+ tmp_name= tmp_buff;
+ /* Scan name from the end */
+ end= strend(path_name)-1;
+ ptr= end;
+ while (ptr >= path_name && *ptr != '\\' && *ptr != '/') {
+ ptr--;
+ }
+ uint name_len= end - ptr;
+ memcpy(tmp_name, ptr + 1, end - ptr);
+ tmp_name[name_len]= '\0';
+#ifdef __WIN__
+ /* Put to lower case */
+ ptr= tmp_name;
+
+ while (*ptr != '\0') {
+ *ptr= tolower(*ptr);
+ ptr++;
+ }
+#endif
+ filename_to_tablename(tmp_name, tabname, sizeof(tmp_buff) - 1);
+}
+
+/**
+ Set m_tabname from full pathname to table file.
+*/
+
+void ha_ndbcluster::set_tabname(const char *path_name)
+{
+ set_tabname(path_name, m_tabname);
+}
+
+
+ha_rows
+ha_ndbcluster::records_in_range(uint inx, key_range *min_key,
+ key_range *max_key)
+{
+ KEY *key_info= table->key_info + inx;
+ uint key_length= key_info->key_length;
+ NDB_INDEX_TYPE idx_type= get_index_type(inx);
+
+ DBUG_ENTER("records_in_range");
+ // Prevent partial read of hash indexes by returning HA_POS_ERROR
+ if ((idx_type == UNIQUE_INDEX || idx_type == PRIMARY_KEY_INDEX) &&
+ ((min_key && min_key->length < key_length) ||
+ (max_key && max_key->length < key_length)))
+ DBUG_RETURN(HA_POS_ERROR);
+
+ // Read from hash index with full key
+ // This is a "const" table which returns only one record!
+ if ((idx_type != ORDERED_INDEX) &&
+ ((min_key && min_key->length == key_length) ||
+ (max_key && max_key->length == key_length)))
+ DBUG_RETURN(1);
+
+ if ((idx_type == PRIMARY_KEY_ORDERED_INDEX ||
+ idx_type == UNIQUE_ORDERED_INDEX ||
+ idx_type == ORDERED_INDEX) &&
+ m_index[inx].index_stat != NULL)
+ {
+ NDB_INDEX_DATA& d=m_index[inx];
+ const NDBINDEX* index= d.index;
+ Ndb* ndb=get_ndb();
+ NdbTransaction* trans=NULL;
+ NdbIndexScanOperation* op=NULL;
+ int res=0;
+ Uint64 rows;
+
+ do
+ {
+ // We must provide approx table rows
+ Uint64 table_rows=0;
+ Ndb_local_table_statistics *ndb_info= m_table_info;
+ if (ndb_info->records != ~(ha_rows)0 && ndb_info->records != 0)
+ {
+ table_rows = ndb_info->records;
+ DBUG_PRINT("info", ("use info->records: %lu", (ulong) table_rows));
+ }
+ else
+ {
+ Ndb_statistics stat;
+ if ((res=ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat)))
+ break;
+ table_rows=stat.row_count;
+ DBUG_PRINT("info", ("use db row_count: %lu", (ulong) table_rows));
+ if (table_rows == 0) {
+ // Problem if autocommit=0
+#ifdef ndb_get_table_statistics_uses_active_trans
+ rows=0;
+ break;
+#endif
+ }
+ }
+
+ // Define scan op for the range
+ if ((trans=m_active_trans) == NULL ||
+ trans->commitStatus() != NdbTransaction::Started)
+ {
+ DBUG_PRINT("info", ("no active trans"));
+ if (! (trans=ndb->startTransaction()))
+ ERR_BREAK(ndb->getNdbError(), res);
+ }
+ if (! (op=trans->getNdbIndexScanOperation(index, (NDBTAB*)m_table)))
+ ERR_BREAK(trans->getNdbError(), res);
+ if ((op->readTuples(NdbOperation::LM_CommittedRead)) == -1)
+ ERR_BREAK(op->getNdbError(), res);
+ const key_range *keys[2]={ min_key, max_key };
+ if ((res=set_bounds(op, inx, TRUE, keys)) != 0)
+ break;
+
+ // Decide if db should be contacted
+ int flags=0;
+ if (d.index_stat_query_count < d.index_stat_cache_entries ||
+ (d.index_stat_update_freq != 0 &&
+ d.index_stat_query_count % d.index_stat_update_freq == 0))
+ {
+ DBUG_PRINT("info", ("force stat from db"));
+ flags|=NdbIndexStat::RR_UseDb;
+ }
+ if (d.index_stat->records_in_range(index, op, table_rows, &rows, flags) == -1)
+ ERR_BREAK(d.index_stat->getNdbError(), res);
+ d.index_stat_query_count++;
+ } while (0);
+
+ if (trans != m_active_trans && rows == 0)
+ rows = 1;
+ if (trans != m_active_trans && trans != NULL)
+ ndb->closeTransaction(trans);
+ if (res != 0)
+ DBUG_RETURN(HA_POS_ERROR);
+ DBUG_RETURN(rows);
+ }
+
+ DBUG_RETURN(10); /* Good guess when you don't know anything */
+}
+
+ulonglong ha_ndbcluster::table_flags(void) const
+{
+ THD *thd= current_thd;
+ ulonglong f= m_table_flags;
+ if (m_ha_not_exact_count)
+ f= f & ~HA_STATS_RECORDS_IS_EXACT;
+ /*
+ To allow for logging of ndb tables during stmt based logging;
+ flag cabablity, but also turn off flag for OWN_BINLOGGING
+ */
+ if (thd->variables.binlog_format == BINLOG_FORMAT_STMT)
+ f= (f | HA_BINLOG_STMT_CAPABLE) & ~HA_HAS_OWN_BINLOGGING;
+ return f;
+}
+const char * ha_ndbcluster::table_type() const
+{
+ return("NDBCLUSTER");
+}
+uint ha_ndbcluster::max_supported_record_length() const
+{
+ return NDB_MAX_TUPLE_SIZE;
+}
+uint ha_ndbcluster::max_supported_keys() const
+{
+ return MAX_KEY;
+}
+uint ha_ndbcluster::max_supported_key_parts() const
+{
+ return NDB_MAX_NO_OF_ATTRIBUTES_IN_KEY;
+}
+uint ha_ndbcluster::max_supported_key_length() const
+{
+ return NDB_MAX_KEY_SIZE;
+}
+uint ha_ndbcluster::max_supported_key_part_length() const
+{
+ return NDB_MAX_KEY_SIZE;
+}
+bool ha_ndbcluster::low_byte_first() const
+{
+#ifdef WORDS_BIGENDIAN
+ return FALSE;
+#else
+ return TRUE;
+#endif
+}
+const char* ha_ndbcluster::index_type(uint key_number)
+{
+ switch (get_index_type(key_number)) {
+ case ORDERED_INDEX:
+ case UNIQUE_ORDERED_INDEX:
+ case PRIMARY_KEY_ORDERED_INDEX:
+ return "BTREE";
+ case UNIQUE_INDEX:
+ case PRIMARY_KEY_INDEX:
+ default:
+ return "HASH";
+ }
+}
+
+uint8 ha_ndbcluster::table_cache_type()
+{
+ DBUG_ENTER("ha_ndbcluster::table_cache_type=HA_CACHE_TBL_ASKTRANSACT");
+ DBUG_RETURN(HA_CACHE_TBL_ASKTRANSACT);
+}
+
+
+/**
+ Retrieve the commit count for the table object.
+
+ @param thd Thread context.
+ @param norm_name Normalized path to the table.
+ @param[out] commit_count Commit count for the table.
+
+ @return 0 on success.
+ @return 1 if an error occured.
+*/
+
+uint ndb_get_commitcount(THD *thd, char *norm_name,
+ Uint64 *commit_count)
+{
+ char dbname[NAME_LEN + 1];
+ NDB_SHARE *share;
+ DBUG_ENTER("ndb_get_commitcount");
+
+ DBUG_PRINT("enter", ("name: %s", norm_name));
+ pthread_mutex_lock(&ndbcluster_mutex);
+ if (!(share=(NDB_SHARE*) my_hash_search(&ndbcluster_open_tables,
+ (const uchar*) norm_name,
+ strlen(norm_name))))
+ {
+ pthread_mutex_unlock(&ndbcluster_mutex);
+ DBUG_PRINT("info", ("Table %s not found in ndbcluster_open_tables",
+ norm_name));
+ DBUG_RETURN(1);
+ }
+ /* ndb_share reference temporary, free below */
+ share->use_count++;
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ mysql_mutex_unlock(&ndbcluster_mutex);
+
+ mysql_mutex_lock(&share->mutex);
+ if (opt_ndb_cache_check_time > 0)
+ {
+ if (share->commit_count != 0)
+ {
+ *commit_count= share->commit_count;
+#ifndef DBUG_OFF
+ char buff[22];
+#endif
+ DBUG_PRINT("info", ("Getting commit_count: %s from share",
+ llstr(share->commit_count, buff)));
+ mysql_mutex_unlock(&share->mutex);
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ DBUG_RETURN(0);
+ }
+ }
+ DBUG_PRINT("info", ("Get commit_count from NDB"));
+ Ndb *ndb;
+ if (!(ndb= check_ndb_in_thd(thd)))
+ DBUG_RETURN(1);
+
+ ha_ndbcluster::set_dbname(norm_name, dbname);
+ if (ndb->setDatabaseName(dbname))
+ {
+ ERR_RETURN(ndb->getNdbError());
+ }
+ uint lock= share->commit_count_lock;
+ mysql_mutex_unlock(&share->mutex);
+
+ struct Ndb_statistics stat;
+ {
+ char tblname[NAME_LEN + 1];
+ ha_ndbcluster::set_tabname(norm_name, tblname);
+ Ndb_table_guard ndbtab_g(ndb->getDictionary(), tblname);
+ if (ndbtab_g.get_table() == 0
+ || ndb_get_table_statistics(NULL, FALSE, ndb, ndbtab_g.get_table(), &stat))
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ DBUG_RETURN(1);
+ }
+ }
+
+ mysql_mutex_lock(&share->mutex);
+ if (share->commit_count_lock == lock)
+ {
+#ifndef DBUG_OFF
+ char buff[22];
+#endif
+ DBUG_PRINT("info", ("Setting commit_count to %s",
+ llstr(stat.commit_count, buff)));
+ share->commit_count= stat.commit_count;
+ *commit_count= stat.commit_count;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Discarding commit_count, comit_count_lock changed"));
+ *commit_count= 0;
+ }
+ mysql_mutex_unlock(&share->mutex);
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Check if a cached query can be used.
+
+ This is done by comparing the supplied engine_data to commit_count of
+ the table.
+
+ The commit_count is either retrieved from the share for the table, where
+ it has been cached by the util thread. If the util thread is not started,
+ NDB has to be contacetd to retrieve the commit_count, this will introduce
+ a small delay while waiting for NDB to answer.
+
+
+ @param thd thread handle
+ @param full_name normalized path to the table in the canonical
+ format.
+ @param full_name_len length of the normalized path to the table.
+ @param engine_data parameter retrieved when query was first inserted into
+ the cache. If the value of engine_data is changed,
+ all queries for this table should be invalidated.
+
+ @retval
+ TRUE Yes, use the query from cache
+ @retval
+ FALSE No, don't use the cached query, and if engine_data
+ has changed, all queries for this table should be invalidated
+
+*/
+
+static my_bool
+ndbcluster_cache_retrieval_allowed(THD *thd,
+ char *full_name, uint full_name_len,
+ ulonglong *engine_data)
+{
+ Uint64 commit_count;
+ char dbname[NAME_LEN + 1];
+ char tabname[NAME_LEN + 1];
+#ifndef DBUG_OFF
+ char buff[22], buff2[22];
+#endif
+
+ ha_ndbcluster::set_dbname(full_name, dbname);
+ ha_ndbcluster::set_tabname(full_name, tabname);
+
+ DBUG_ENTER("ndbcluster_cache_retrieval_allowed");
+ DBUG_PRINT("enter", ("dbname: %s, tabname: %s", dbname, tabname));
+
+ if (thd->in_multi_stmt_transaction_mode())
+ {
+ DBUG_PRINT("exit", ("No, don't use cache in transaction"));
+ DBUG_RETURN(FALSE);
+ }
+
+ if (ndb_get_commitcount(thd, full_name, &commit_count))
+ {
+ *engine_data= 0; /* invalidate */
+ DBUG_PRINT("exit", ("No, could not retrieve commit_count"));
+ DBUG_RETURN(FALSE);
+ }
+ DBUG_PRINT("info", ("*engine_data: %s, commit_count: %s",
+ llstr(*engine_data, buff), llstr(commit_count, buff2)));
+ if (commit_count == 0)
+ {
+ *engine_data= 0; /* invalidate */
+ DBUG_PRINT("exit", ("No, local commit has been performed"));
+ DBUG_RETURN(FALSE);
+ }
+ else if (*engine_data != commit_count)
+ {
+ *engine_data= commit_count; /* invalidate */
+ DBUG_PRINT("exit", ("No, commit_count has changed"));
+ DBUG_RETURN(FALSE);
+ }
+
+ DBUG_PRINT("exit", ("OK to use cache, engine_data: %s",
+ llstr(*engine_data, buff)));
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Register a table for use in the query cache.
+
+ Fetch the commit_count for the table and return it in engine_data,
+ this will later be used to check if the table has changed, before
+ the cached query is reused.
+
+ @param thd thread handle
+ @param full_name normalized path to the table in the
+ canonical format.
+ @param full_name_len length of the normalized path to the table.
+ @param engine_callback function to be called before using cache on
+ this table
+ @param[out] engine_data commit_count for this table
+
+ @retval
+ TRUE Yes, it's ok to cahce this query
+ @retval
+ FALSE No, don't cach the query
+*/
+
+my_bool
+ha_ndbcluster::register_query_cache_table(THD *thd,
+ char *full_name, uint full_name_len,
+ qc_engine_callback *engine_callback,
+ ulonglong *engine_data)
+{
+ Uint64 commit_count;
+#ifndef DBUG_OFF
+ char buff[22];
+#endif
+ DBUG_ENTER("ha_ndbcluster::register_query_cache_table");
+ DBUG_PRINT("enter",("dbname: %s, tabname: %s", m_dbname, m_tabname));
+
+ if (thd->in_multi_stmt_transaction_mode())
+ {
+ DBUG_PRINT("exit", ("Can't register table during transaction"));
+ DBUG_RETURN(FALSE);
+ }
+
+ if (ndb_get_commitcount(thd, full_name, &commit_count))
+ {
+ *engine_data= 0;
+ DBUG_PRINT("exit", ("Error, could not get commitcount"));
+ DBUG_RETURN(FALSE);
+ }
+ *engine_data= commit_count;
+ *engine_callback= ndbcluster_cache_retrieval_allowed;
+ DBUG_PRINT("exit", ("commit_count: %s", llstr(commit_count, buff)));
+ DBUG_RETURN(commit_count > 0);
+}
+
+
+/**
+ Handling the shared NDB_SHARE structure that is needed to
+ provide table locking.
+
+ It's also used for sharing data with other NDB handlers
+ in the same MySQL Server. There is currently not much
+ data we want to or can share.
+*/
+
+static uchar *ndbcluster_get_key(NDB_SHARE *share, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= share->key_length;
+ return (uchar*) share->key;
+}
+
+
+#ifndef DBUG_OFF
+
+static void print_share(const char* where, NDB_SHARE* share)
+{
+ fprintf(DBUG_FILE,
+ "%s %s.%s: use_count: %u, commit_count: %lu\n",
+ where, share->db, share->table_name, share->use_count,
+ (ulong) share->commit_count);
+ fprintf(DBUG_FILE,
+ " - key: %s, key_length: %d\n",
+ share->key, share->key_length);
+
+#ifdef HAVE_NDB_BINLOG
+ if (share->table)
+ fprintf(DBUG_FILE,
+ " - share->table: %p %s.%s\n",
+ share->table, share->table->s->db.str,
+ share->table->s->table_name.str);
+#endif
+}
+
+
+static void print_ndbcluster_open_tables()
+{
+ DBUG_LOCK_FILE;
+ fprintf(DBUG_FILE, ">ndbcluster_open_tables\n");
+ for (uint i= 0; i < ndbcluster_open_tables.records; i++)
+ print_share("",
+ (NDB_SHARE*)my_hash_element(&ndbcluster_open_tables, i));
+ fprintf(DBUG_FILE, "<ndbcluster_open_tables\n");
+ DBUG_UNLOCK_FILE;
+}
+
+#endif
+
+
+#define dbug_print_open_tables() \
+ DBUG_EXECUTE("info", \
+ print_ndbcluster_open_tables(););
+
+#define dbug_print_share(t, s) \
+ DBUG_LOCK_FILE; \
+ DBUG_EXECUTE("info", \
+ print_share((t), (s));); \
+ DBUG_UNLOCK_FILE;
+
+
+#ifdef HAVE_NDB_BINLOG
+/*
+ For some reason a share is still around, try to salvage the situation
+ by closing all cached tables. If the share still exists, there is an
+ error somewhere but only report this to the error log. Keep this
+ "trailing share" but rename it since there are still references to it
+ to avoid segmentation faults. There is a risk that the memory for
+ this trailing share leaks.
+
+ Must be called with previous mysql_mutex_lock(&ndbcluster_mutex)
+*/
+int handle_trailing_share(NDB_SHARE *share)
+{
+ THD *thd= current_thd;
+ static ulong trailing_share_id= 0;
+ DBUG_ENTER("handle_trailing_share");
+
+ /* ndb_share reference temporary, free below */
+ ++share->use_count;
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ mysql_mutex_unlock(&ndbcluster_mutex);
+
+ TABLE_LIST table_list;
+ bzero((char*) &table_list,sizeof(table_list));
+ table_list.db= share->db;
+ table_list.alias= table_list.table_name= share->table_name;
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
+
+ mysql_mutex_lock(&ndbcluster_mutex);
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ if (!--share->use_count)
+ {
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB_SHARE: trailing share "
+ "%s(connect_count: %u) "
+ "released by close_cached_tables at "
+ "connect_count: %u",
+ share->key,
+ share->connect_count,
+ g_ndb_cluster_connection->get_connect_count());
+ ndbcluster_real_free_share(&share);
+ DBUG_RETURN(0);
+ }
+
+ /*
+ share still exists, if share has not been dropped by server
+ release that share
+ */
+ if (share->state != NSS_DROPPED)
+ {
+ share->state= NSS_DROPPED;
+ /* ndb_share reference create free */
+ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
+ share->key, share->use_count));
+ --share->use_count;
+
+ if (share->use_count == 0)
+ {
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB_SHARE: trailing share "
+ "%s(connect_count: %u) "
+ "released after NSS_DROPPED check "
+ "at connect_count: %u",
+ share->key,
+ share->connect_count,
+ g_ndb_cluster_connection->get_connect_count());
+ ndbcluster_real_free_share(&share);
+ DBUG_RETURN(0);
+ }
+ }
+
+ sql_print_warning("NDB_SHARE: %s already exists use_count=%d."
+ " Moving away for safety, but possible memleak.",
+ share->key, share->use_count);
+ dbug_print_open_tables();
+
+ /*
+ Ndb share has not been released as it should
+ */
+#ifdef NOT_YET
+ DBUG_ASSERT(FALSE);
+#endif
+
+ /*
+ This is probably an error. We can however save the situation
+ at the cost of a possible mem leak, by "renaming" the share
+ - First remove from hash
+ */
+ my_hash_delete(&ndbcluster_open_tables, (uchar*) share);
+
+ /*
+ now give it a new name, just a running number
+ if space is not enough allocate some more
+ */
+ {
+ const uint min_key_length= 10;
+ if (share->key_length < min_key_length)
+ {
+ share->key= (char*) alloc_root(&share->mem_root, min_key_length + 1);
+ share->key_length= min_key_length;
+ }
+ share->key_length=
+ my_snprintf(share->key, min_key_length + 1, "#leak%lu",
+ trailing_share_id++);
+ }
+ /* Keep it for possible the future trailing free */
+ my_hash_insert(&ndbcluster_open_tables, (uchar*) share);
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Rename share is used during rename table.
+*/
+static int rename_share(NDB_SHARE *share, const char *new_key)
+{
+ NDB_SHARE *tmp;
+ mysql_mutex_lock(&ndbcluster_mutex);
+ uint new_length= (uint) strlen(new_key);
+ DBUG_PRINT("rename_share", ("old_key: %s old__length: %d",
+ share->key, share->key_length));
+ if ((tmp= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables,
+ (uchar*) new_key, new_length)))
+ handle_trailing_share(tmp);
+
+ /* remove the share from hash */
+ my_hash_delete(&ndbcluster_open_tables, (uchar*) share);
+ dbug_print_open_tables();
+
+ /* save old stuff if insert should fail */
+ uint old_length= share->key_length;
+ char *old_key= share->key;
+
+ /*
+ now allocate and set the new key, db etc
+ enough space for key, db, and table_name
+ */
+ share->key= (char*) alloc_root(&share->mem_root, 2 * (new_length + 1));
+ strmov(share->key, new_key);
+ share->key_length= new_length;
+
+ if (my_hash_insert(&ndbcluster_open_tables, (uchar*) share))
+ {
+ // ToDo free the allocated stuff above?
+ DBUG_PRINT("error", ("rename_share: my_hash_insert %s failed",
+ share->key));
+ share->key= old_key;
+ share->key_length= old_length;
+ if (my_hash_insert(&ndbcluster_open_tables, (uchar*) share))
+ {
+ sql_print_error("rename_share: failed to recover %s", share->key);
+ DBUG_PRINT("error", ("rename_share: my_hash_insert %s failed",
+ share->key));
+ }
+ dbug_print_open_tables();
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ return -1;
+ }
+ dbug_print_open_tables();
+
+ share->db= share->key + new_length + 1;
+ ha_ndbcluster::set_dbname(new_key, share->db);
+ share->table_name= share->db + strlen(share->db) + 1;
+ ha_ndbcluster::set_tabname(new_key, share->table_name);
+
+ dbug_print_share("rename_share:", share);
+ if (share->table)
+ {
+ if (share->op == 0)
+ {
+ share->table->s->db.str= share->db;
+ share->table->s->db.length= strlen(share->db);
+ share->table->s->table_name.str= share->table_name;
+ share->table->s->table_name.length= strlen(share->table_name);
+ }
+ }
+ /* else rename will be handled when the ALTER event comes */
+ share->old_names= old_key;
+ // ToDo free old_names after ALTER EVENT
+
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ return 0;
+}
+#endif
+
+/*
+ Increase refcount on existing share.
+ Always returns share and cannot fail.
+*/
+NDB_SHARE *ndbcluster_get_share(NDB_SHARE *share)
+{
+ mysql_mutex_lock(&ndbcluster_mutex);
+ share->use_count++;
+
+ dbug_print_open_tables();
+ dbug_print_share("ndbcluster_get_share:", share);
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ return share;
+}
+
+
+/*
+ Get a share object for key
+
+ Returns share for key, and increases the refcount on the share.
+
+ create_if_not_exists == TRUE:
+ creates share if it does not alreade exist
+ returns 0 only due to out of memory, and then sets my_error
+
+ create_if_not_exists == FALSE:
+ returns 0 if share does not exist
+
+ have_lock == TRUE, mysql_mutex_lock(&ndbcluster_mutex) already taken
+*/
+
+NDB_SHARE *ndbcluster_get_share(const char *key, TABLE *table,
+ bool create_if_not_exists,
+ bool have_lock)
+{
+ NDB_SHARE *share;
+ uint length= (uint) strlen(key);
+ DBUG_ENTER("ndbcluster_get_share");
+ DBUG_PRINT("enter", ("key: '%s'", key));
+
+ if (!have_lock)
+ mysql_mutex_lock(&ndbcluster_mutex);
+ if (!(share= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables,
+ (uchar*) key,
+ length)))
+ {
+ if (!create_if_not_exists)
+ {
+ DBUG_PRINT("error", ("get_share: %s does not exist", key));
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(0);
+ }
+ if ((share= (NDB_SHARE*) my_malloc(sizeof(*share),
+ MYF(MY_WME | MY_ZEROFILL))))
+ {
+ MEM_ROOT **root_ptr=
+ my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC);
+ MEM_ROOT *old_root= *root_ptr;
+ init_sql_alloc(&share->mem_root, 1024, 0, MYF(0));
+ *root_ptr= &share->mem_root; // remember to reset before return
+ share->state= NSS_INITIAL;
+ /* enough space for key, db, and table_name */
+ share->key= (char*) alloc_root(*root_ptr, 2 * (length + 1));
+ share->key_length= length;
+ strmov(share->key, key);
+ if (my_hash_insert(&ndbcluster_open_tables, (uchar*) share))
+ {
+ free_root(&share->mem_root, MYF(0));
+ my_free(share);
+ *root_ptr= old_root;
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(0);
+ }
+ thr_lock_init(&share->lock);
+ mysql_mutex_init(key_NDB_SHARE_mutex, &share->mutex, MY_MUTEX_INIT_FAST);
+ share->commit_count= 0;
+ share->commit_count_lock= 0;
+ share->db= share->key + length + 1;
+ ha_ndbcluster::set_dbname(key, share->db);
+ share->table_name= share->db + strlen(share->db) + 1;
+ ha_ndbcluster::set_tabname(key, share->table_name);
+#ifdef HAVE_NDB_BINLOG
+ if (ndbcluster_binlog_init_share(share, table))
+ {
+ DBUG_PRINT("error", ("get_share: %s could not init share", key));
+ ndbcluster_real_free_share(&share);
+ *root_ptr= old_root;
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(0);
+ }
+#endif
+ *root_ptr= old_root;
+ }
+ else
+ {
+ DBUG_PRINT("error", ("get_share: failed to alloc share"));
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ my_error(ER_OUTOFMEMORY, MYF(0), static_cast<int>(sizeof(*share)));
+ DBUG_RETURN(0);
+ }
+ }
+ share->use_count++;
+
+ dbug_print_open_tables();
+ dbug_print_share("ndbcluster_get_share:", share);
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(share);
+}
+
+
+void ndbcluster_real_free_share(NDB_SHARE **share)
+{
+ DBUG_ENTER("ndbcluster_real_free_share");
+ dbug_print_share("ndbcluster_real_free_share:", *share);
+
+ my_hash_delete(&ndbcluster_open_tables, (uchar*) *share);
+ thr_lock_delete(&(*share)->lock);
+ mysql_mutex_destroy(&(*share)->mutex);
+
+#ifdef HAVE_NDB_BINLOG
+ if ((*share)->table)
+ {
+ // (*share)->table->mem_root is freed by closefrm
+ closefrm((*share)->table, 0);
+ // (*share)->table_share->mem_root is freed by free_table_share
+ free_table_share((*share)->table_share);
+#ifndef DBUG_OFF
+ bzero((uchar*)(*share)->table_share, sizeof(*(*share)->table_share));
+ bzero((uchar*)(*share)->table, sizeof(*(*share)->table));
+ (*share)->table_share= 0;
+ (*share)->table= 0;
+#endif
+ }
+#endif
+ free_root(&(*share)->mem_root, MYF(0));
+ my_free(*share);
+ *share= 0;
+
+ dbug_print_open_tables();
+ DBUG_VOID_RETURN;
+}
+
+
+void ndbcluster_free_share(NDB_SHARE **share, bool have_lock)
+{
+ if (!have_lock)
+ mysql_mutex_lock(&ndbcluster_mutex);
+ if ((*share)->util_lock == current_thd)
+ (*share)->util_lock= 0;
+ if (!--(*share)->use_count)
+ {
+ ndbcluster_real_free_share(share);
+ }
+ else
+ {
+ dbug_print_open_tables();
+ dbug_print_share("ndbcluster_free_share:", *share);
+ }
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+}
+
+
+static
+int
+ndb_get_table_statistics(ha_ndbcluster* file, bool report_error, Ndb* ndb, const NDBTAB *ndbtab,
+ struct Ndb_statistics * ndbstat)
+{
+ NdbTransaction* pTrans;
+ NdbError error;
+ int retries= 10;
+ int reterr= 0;
+ int retry_sleep= 30; /* 30 milliseconds, transaction */
+#ifndef DBUG_OFF
+ char buff[22], buff2[22], buff3[22], buff4[22];
+#endif
+ DBUG_ENTER("ndb_get_table_statistics");
+ DBUG_PRINT("enter", ("table: %s", ndbtab->getName()));
+
+ DBUG_ASSERT(ndbtab != 0);
+
+ do
+ {
+ Uint64 rows, commits, fixed_mem, var_mem;
+ Uint32 size;
+ Uint32 count= 0;
+ Uint64 sum_rows= 0;
+ Uint64 sum_commits= 0;
+ Uint64 sum_row_size= 0;
+ Uint64 sum_mem= 0;
+ NdbScanOperation*pOp;
+ int check;
+
+ if ((pTrans= ndb->startTransaction()) == NULL)
+ {
+ error= ndb->getNdbError();
+ goto retry;
+ }
+
+ if ((pOp= pTrans->getNdbScanOperation(ndbtab)) == NULL)
+ {
+ error= pTrans->getNdbError();
+ goto retry;
+ }
+
+ if (pOp->readTuples(NdbOperation::LM_CommittedRead))
+ {
+ error= pOp->getNdbError();
+ goto retry;
+ }
+
+ if (pOp->interpret_exit_last_row() == -1)
+ {
+ error= pOp->getNdbError();
+ goto retry;
+ }
+
+ pOp->getValue(NdbDictionary::Column::ROW_COUNT, (char*)&rows);
+ pOp->getValue(NdbDictionary::Column::COMMIT_COUNT, (char*)&commits);
+ pOp->getValue(NdbDictionary::Column::ROW_SIZE, (char*)&size);
+ pOp->getValue(NdbDictionary::Column::FRAGMENT_FIXED_MEMORY,
+ (char*)&fixed_mem);
+ pOp->getValue(NdbDictionary::Column::FRAGMENT_VARSIZED_MEMORY,
+ (char*)&var_mem);
+
+ if (pTrans->execute(NdbTransaction::NoCommit,
+ NdbOperation::AbortOnError,
+ TRUE) == -1)
+ {
+ error= pTrans->getNdbError();
+ goto retry;
+ }
+
+ while ((check= pOp->nextResult(TRUE, TRUE)) == 0)
+ {
+ sum_rows+= rows;
+ sum_commits+= commits;
+ if (sum_row_size < size)
+ sum_row_size= size;
+ sum_mem+= fixed_mem + var_mem;
+ count++;
+ }
+
+ if (check == -1)
+ {
+ error= pOp->getNdbError();
+ goto retry;
+ }
+
+ pOp->close(TRUE);
+
+ ndb->closeTransaction(pTrans);
+
+ ndbstat->row_count= sum_rows;
+ ndbstat->commit_count= sum_commits;
+ ndbstat->row_size= sum_row_size;
+ ndbstat->fragment_memory= sum_mem;
+
+ DBUG_PRINT("exit", ("records: %s commits: %s "
+ "row_size: %s mem: %s count: %u",
+ llstr(sum_rows, buff),
+ llstr(sum_commits, buff2),
+ llstr(sum_row_size, buff3),
+ llstr(sum_mem, buff4),
+ count));
+
+ DBUG_RETURN(0);
+retry:
+ if(report_error)
+ {
+ if (file && pTrans)
+ {
+ reterr= file->ndb_err(pTrans);
+ }
+ else
+ {
+ const NdbError& tmp= error;
+ ERR_PRINT(tmp);
+ reterr= ndb_to_mysql_error(&tmp);
+ }
+ }
+ else
+ reterr= error.code;
+
+ if (pTrans)
+ {
+ ndb->closeTransaction(pTrans);
+ pTrans= NULL;
+ }
+ if (error.status == NdbError::TemporaryError && retries--)
+ {
+ my_sleep(retry_sleep);
+ continue;
+ }
+ set_ndb_err(current_thd, error);
+ break;
+ } while(1);
+ DBUG_PRINT("exit", ("failed, reterr: %u, NdbError %u(%s)", reterr,
+ error.code, error.message));
+ DBUG_RETURN(reterr);
+}
+
+/**
+ Create a .ndb file to serve as a placeholder indicating
+ that the table with this name is a ndb table.
+*/
+
+int ha_ndbcluster::write_ndb_file(const char *name)
+{
+ File file;
+ bool error=1;
+ char path[FN_REFLEN];
+
+ DBUG_ENTER("write_ndb_file");
+ DBUG_PRINT("enter", ("name: %s", name));
+
+ (void)strxnmov(path, FN_REFLEN-1,
+ mysql_data_home,"/",name,ha_ndb_ext,NullS);
+
+ if ((file= mysql_file_create(key_file_ndb, path, CREATE_MODE,
+ O_RDWR | O_TRUNC, MYF(MY_WME))) >= 0)
+ {
+ // It's an empty file
+ error=0;
+ mysql_file_close(file, MYF(0));
+ }
+ DBUG_RETURN(error);
+}
+
+void
+ha_ndbcluster::release_completed_operations(NdbTransaction *trans,
+ bool force_release)
+{
+ if (trans->hasBlobOperation())
+ {
+ /* We are reading/writing BLOB fields,
+ releasing operation records is unsafe
+ */
+ return;
+ }
+ if (!force_release)
+ {
+ if (get_thd_ndb(current_thd)->query_state & NDB_QUERY_MULTI_READ_RANGE)
+ {
+ /* We are batching reads and have not consumed all fetched
+ rows yet, releasing operation records is unsafe
+ */
+ return;
+ }
+ }
+ trans->releaseCompletedOperations();
+}
+
+bool
+ha_ndbcluster::null_value_index_search(KEY_MULTI_RANGE *ranges,
+ KEY_MULTI_RANGE *end_range,
+ HANDLER_BUFFER *buffer)
+{
+ DBUG_ENTER("null_value_index_search");
+ KEY* key_info= table->key_info + active_index;
+ KEY_MULTI_RANGE *range= ranges;
+ ulong reclength= table->s->reclength;
+ uchar *curr= (uchar*)buffer->buffer;
+ uchar *end_of_buffer= (uchar*)buffer->buffer_end;
+
+ for (; range<end_range && curr+reclength <= end_of_buffer;
+ range++)
+ {
+ const uchar *key= range->start_key.key;
+ uint key_len= range->start_key.length;
+ if (check_null_in_key(key_info, key, key_len))
+ DBUG_RETURN(TRUE);
+ curr += reclength;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+#if 0
+/* MRR/NDB is disabled, for details see method declarations in ha_ndbcluster.h */
+int
+ha_ndbcluster::read_multi_range_first(KEY_MULTI_RANGE **found_range_p,
+ KEY_MULTI_RANGE *ranges,
+ uint range_count,
+ bool sorted,
+ HANDLER_BUFFER *buffer)
+{
+ m_write_op= FALSE;
+ int res;
+ KEY* key_info= table->key_info + active_index;
+ NDB_INDEX_TYPE cur_index_type= get_index_type(active_index);
+ ulong reclength= table_share->reclength;
+ NdbOperation* op;
+ Thd_ndb *thd_ndb= get_thd_ndb(current_thd);
+ DBUG_ENTER("ha_ndbcluster::read_multi_range_first");
+
+ /**
+ * blobs and unique hash index with NULL can't be batched currently
+ */
+ if (uses_blob_value() ||
+ (cur_index_type == UNIQUE_INDEX &&
+ has_null_in_unique_index(active_index) &&
+ null_value_index_search(ranges, ranges+range_count, buffer))
+ || m_delete_cannot_batch || m_update_cannot_batch)
+ {
+ m_disable_multi_read= TRUE;
+ DBUG_RETURN(handler::read_multi_range_first(found_range_p,
+ ranges,
+ range_count,
+ sorted,
+ buffer));
+ }
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ thd_ndb->query_state|= NDB_QUERY_MULTI_READ_RANGE;
+ m_disable_multi_read= FALSE;
+
+ /*
+ * Copy arguments into member variables
+ */
+ m_multi_ranges= ranges;
+ multi_range_curr= ranges;
+ multi_range_end= ranges+range_count;
+ multi_range_sorted= sorted;
+ multi_range_buffer= buffer;
+
+ /*
+ * read multi range will read ranges as follows (if not ordered)
+ *
+ * input read order
+ * ====== ==========
+ * pk-op 1 pk-op 1
+ * pk-op 2 pk-op 2
+ * range 3 range (3,5) NOTE result rows will be intermixed
+ * pk-op 4 pk-op 4
+ * range 5
+ * pk-op 6 pk-ok 6
+ */
+
+ /*
+ * Variables for loop
+ */
+ uchar *curr= (uchar*)buffer->buffer;
+ uchar *end_of_buffer= (uchar*)buffer->buffer_end;
+ NdbOperation::LockMode lm=
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type);
+ bool need_pk = (lm == NdbOperation::LM_Read);
+ const NDBTAB *tab= m_table;
+ const NDBINDEX *unique_idx= m_index[active_index].unique_index;
+ const NDBINDEX *idx= m_index[active_index].index;
+ const NdbOperation* lastOp= m_active_trans->getLastDefinedOperation();
+ NdbIndexScanOperation* scanOp= 0;
+ for (; multi_range_curr<multi_range_end && curr+reclength <= end_of_buffer;
+ multi_range_curr++)
+ {
+ part_id_range part_spec;
+ if (m_use_partition_function)
+ {
+ get_partition_set(table, curr, active_index,
+ &multi_range_curr->start_key,
+ &part_spec);
+ DBUG_PRINT("info", ("part_spec.start_part: %u part_spec.end_part: %u",
+ part_spec.start_part, part_spec.end_part));
+ /*
+ If partition pruning has found no partition in set
+ we can skip this scan
+ */
+ if (part_spec.start_part > part_spec.end_part)
+ {
+ /*
+ We can skip this partition since the key won't fit into any
+ partition
+ */
+ curr += reclength;
+ multi_range_curr->range_flag |= SKIP_RANGE;
+ continue;
+ }
+ }
+ switch (cur_index_type) {
+ case PRIMARY_KEY_ORDERED_INDEX:
+ if (!(multi_range_curr->start_key.length == key_info->key_length &&
+ multi_range_curr->start_key.flag == HA_READ_KEY_EXACT))
+ goto range;
+ // else fall through
+ case PRIMARY_KEY_INDEX:
+ {
+ multi_range_curr->range_flag |= UNIQUE_RANGE;
+ if ((op= m_active_trans->getNdbOperation(tab)) &&
+ !op->readTuple(lm) &&
+ !set_primary_key(op, multi_range_curr->start_key.key) &&
+ !define_read_attrs(curr, op) &&
+ (!m_use_partition_function ||
+ (op->setPartitionId(part_spec.start_part), TRUE)))
+ curr += reclength;
+ else
+ {
+ ERR_RETURN_PREPARE(res,
+ op ? op->getNdbError() :
+ m_active_trans->getNdbError())
+ MYSQL_INDEX_READ_ROW_DONE(res);
+ DBUG_RETURN(res);
+ }
+ break;
+ }
+ break;
+ case UNIQUE_ORDERED_INDEX:
+ if (!(multi_range_curr->start_key.length == key_info->key_length &&
+ multi_range_curr->start_key.flag == HA_READ_KEY_EXACT &&
+ !check_null_in_key(key_info, multi_range_curr->start_key.key,
+ multi_range_curr->start_key.length)))
+ goto range;
+ // else fall through
+ case UNIQUE_INDEX:
+ {
+ multi_range_curr->range_flag |= UNIQUE_RANGE;
+ if ((op= m_active_trans->getNdbIndexOperation(unique_idx, tab)) &&
+ !op->readTuple(lm) &&
+ !set_index_key(op, key_info, multi_range_curr->start_key.key) &&
+ !define_read_attrs(curr, op))
+ curr += reclength;
+ else
+ {
+ ERR_RETURN_PREPARE(res,
+ op ? op->getNdbError() :
+ m_active_trans->getNdbError());
+ MYSQL_INDEX_READ_ROW_DONE(res);
+ DBUG_RETURN(res);
+ }
+ break;
+ }
+ case ORDERED_INDEX: {
+ range:
+ multi_range_curr->range_flag &= ~(uint)UNIQUE_RANGE;
+ if (scanOp == 0)
+ {
+ if (m_multi_cursor)
+ {
+ scanOp= m_multi_cursor;
+ DBUG_ASSERT(scanOp->getSorted() == sorted);
+ DBUG_ASSERT(scanOp->getLockMode() ==
+ (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type));
+ if (scanOp->reset_bounds(m_force_send))
+ {
+ res= ndb_err(m_active_trans);
+ MYSQL_INDEX_READ_ROW_DONE(res);
+ DBUG_RETURN(res);
+ }
+
+ end_of_buffer -= reclength;
+ }
+ else if ((scanOp= m_active_trans->getNdbIndexScanOperation(idx, tab))
+ &&!scanOp->readTuples(lm, 0, parallelism, sorted,
+ FALSE, TRUE, need_pk, TRUE)
+ &&!(m_cond && m_cond->generate_scan_filter(scanOp))
+ &&!define_read_attrs(end_of_buffer-reclength, scanOp))
+ {
+ m_multi_cursor= scanOp;
+ m_multi_range_cursor_result_ptr= end_of_buffer-reclength;
+ }
+ else
+ {
+ ERR_RETURN_PREPARE(res,
+ scanOp ? scanOp->getNdbError() :
+ m_active_trans->getNdbError());
+ MYSQL_INDEX_READ_ROW_DONE(res);
+ DBUG_RETURN(res);
+ }
+ }
+
+ const key_range *keys[2]= { &multi_range_curr->start_key,
+ &multi_range_curr->end_key };
+ if ((res= set_bounds(scanOp, active_index, FALSE, keys,
+ multi_range_curr-ranges)))
+ {
+ MYSQL_INDEX_READ_ROW_DONE(res);
+ DBUG_RETURN(res);
+ }
+ break;
+ }
+ case UNDEFINED_INDEX:
+ DBUG_ASSERT(FALSE);
+ MYSQL_INDEX_READ_ROW_DONE(1);
+ DBUG_RETURN(1);
+ break;
+ }
+ }
+
+ if (multi_range_curr != multi_range_end)
+ {
+ /*
+ * Mark that we're using entire buffer (even if might not) as
+ * we haven't read all ranges for some reason
+ * This as we don't want mysqld to reuse the buffer when we read
+ * the remaining ranges
+ */
+ buffer->end_of_used_area= (uchar*)buffer->buffer_end;
+ }
+ else
+ {
+ buffer->end_of_used_area= curr;
+ }
+
+ /*
+ * Set first operation in multi range
+ */
+ m_current_multi_operation=
+ lastOp ? lastOp->next() : m_active_trans->getFirstDefinedOperation();
+ if (!(res= execute_no_commit_ie(this, m_active_trans,true)))
+ {
+ m_multi_range_defined= multi_range_curr;
+ multi_range_curr= ranges;
+ m_multi_range_result_ptr= (uchar*)buffer->buffer;
+ res= loc_read_multi_range_next(found_range_p);
+ MYSQL_INDEX_READ_ROW_DONE(res);
+ DBUG_RETURN(res);
+ }
+ ERR_RETURN_PREPARE(res, m_active_trans->getNdbError());
+ MYSQL_INDEX_READ_ROW_DONE(res);
+ DBUG_RETURN(res);
+}
+
+#if 0
+#define DBUG_MULTI_RANGE(x) DBUG_PRINT("info", ("read_multi_range_next: case %d\n", x));
+#else
+#define DBUG_MULTI_RANGE(x)
+#endif
+
+int
+ha_ndbcluster::read_multi_range_next(KEY_MULTI_RANGE ** multi_range_found_p)
+{
+ int rc;
+ DBUG_ENTER("ha_ndbcluster::read_multi_range_next");
+ if (m_disable_multi_read)
+ {
+ DBUG_MULTI_RANGE(11);
+ DBUG_RETURN(handler::read_multi_range_next(multi_range_found_p));
+ }
+ MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str);
+ rc= loc_read_multi_range_next(multi_range_found_p);
+ MYSQL_INDEX_READ_ROW_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+int ha_ndbcluster::loc_read_multi_range_next(
+ KEY_MULTI_RANGE **multi_range_found_p)
+{
+ int res;
+ int range_no;
+ ulong reclength= table_share->reclength;
+ const NdbOperation* op= m_current_multi_operation;
+ DBUG_ENTER("ha_ndbcluster::loc_read_multi_range_next");
+
+ for (;multi_range_curr < m_multi_range_defined; multi_range_curr++)
+ {
+ DBUG_MULTI_RANGE(12);
+ if (multi_range_curr->range_flag & SKIP_RANGE)
+ continue;
+ if (multi_range_curr->range_flag & UNIQUE_RANGE)
+ {
+ if (op->getNdbError().code == 0)
+ {
+ DBUG_MULTI_RANGE(13);
+ goto found_next;
+ }
+
+ op= m_active_trans->getNextCompletedOperation(op);
+ m_multi_range_result_ptr += reclength;
+ continue;
+ }
+ else if (m_multi_cursor && !multi_range_sorted)
+ {
+ DBUG_MULTI_RANGE(1);
+ if ((res= fetch_next(m_multi_cursor)) == 0)
+ {
+ DBUG_MULTI_RANGE(2);
+ range_no= m_multi_cursor->get_range_no();
+ goto found;
+ }
+ else
+ {
+ DBUG_MULTI_RANGE(14);
+ goto close_scan;
+ }
+ }
+ else if (m_multi_cursor && multi_range_sorted)
+ {
+ if (m_active_cursor && (res= fetch_next(m_multi_cursor)))
+ {
+ DBUG_MULTI_RANGE(3);
+ goto close_scan;
+ }
+
+ range_no= m_multi_cursor->get_range_no();
+ uint current_range_no= multi_range_curr - m_multi_ranges;
+ if ((uint) range_no == current_range_no)
+ {
+ DBUG_MULTI_RANGE(4);
+ // return current row
+ goto found;
+ }
+ else if (range_no > (int)current_range_no)
+ {
+ DBUG_MULTI_RANGE(5);
+ // wait with current row
+ m_active_cursor= 0;
+ continue;
+ }
+ else
+ {
+ DBUG_MULTI_RANGE(6);
+ // First fetch from cursor
+ DBUG_ASSERT(range_no == -1);
+ if ((res= m_multi_cursor->nextResult(TRUE)))
+ {
+ DBUG_MULTI_RANGE(15);
+ goto close_scan;
+ }
+ multi_range_curr--; // Will be increased in for-loop
+ continue;
+ }
+ }
+ else /* m_multi_cursor == 0 */
+ {
+ DBUG_MULTI_RANGE(7);
+ /*
+ * Corresponds to range 5 in example in read_multi_range_first
+ */
+ (void)1;
+ continue;
+ }
+
+ DBUG_ASSERT(FALSE); // Should only get here via goto's
+close_scan:
+ if (res == 1)
+ {
+ m_multi_cursor->close(FALSE, TRUE);
+ m_active_cursor= m_multi_cursor= 0;
+ DBUG_MULTI_RANGE(8);
+ continue;
+ }
+ else
+ {
+ DBUG_MULTI_RANGE(9);
+ DBUG_RETURN(ndb_err(m_active_trans));
+ }
+ }
+
+ if (multi_range_curr == multi_range_end)
+ {
+ DBUG_MULTI_RANGE(16);
+ Thd_ndb *thd_ndb= get_thd_ndb(current_thd);
+ thd_ndb->query_state&= NDB_QUERY_NORMAL;
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+
+ /*
+ * Read remaining ranges
+ */
+ MYSQL_INDEX_READ_ROW_DONE(1);
+ DBUG_RETURN(read_multi_range_first(multi_range_found_p,
+ multi_range_curr,
+ multi_range_end - multi_range_curr,
+ multi_range_sorted,
+ multi_range_buffer));
+
+found:
+ /*
+ * Found a record belonging to a scan
+ */
+ m_active_cursor= m_multi_cursor;
+ * multi_range_found_p= m_multi_ranges + range_no;
+ memcpy(table->record[0], m_multi_range_cursor_result_ptr, reclength);
+ setup_recattr(m_active_cursor->getFirstRecAttr());
+ unpack_record(table->record[0]);
+ table->status= 0;
+ DBUG_RETURN(0);
+
+found_next:
+ /*
+ * Found a record belonging to a pk/index op,
+ * copy result and move to next to prepare for next call
+ */
+ * multi_range_found_p= multi_range_curr;
+ memcpy(table->record[0], m_multi_range_result_ptr, reclength);
+ setup_recattr(op->getFirstRecAttr());
+ unpack_record(table->record[0]);
+ table->status= 0;
+
+ multi_range_curr++;
+ m_current_multi_operation= m_active_trans->getNextCompletedOperation(op);
+ m_multi_range_result_ptr += reclength;
+ DBUG_RETURN(0);
+}
+#endif
+
+int
+ha_ndbcluster::setup_recattr(const NdbRecAttr* curr)
+{
+ DBUG_ENTER("setup_recattr");
+
+ Field **field, **end;
+ NdbValue *value= m_value;
+
+ end= table->field + table_share->fields;
+
+ for (field= table->field; field < end; field++, value++)
+ {
+ if ((* value).ptr)
+ {
+ DBUG_ASSERT(curr != 0);
+ NdbValue* val= m_value + curr->getColumn()->getColumnNo();
+ DBUG_ASSERT(val->ptr);
+ val->rec= curr;
+ curr= curr->next();
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+/**
+ @param[in] comment table comment defined by user
+
+ @return
+ table comment + additional
+*/
+char*
+ha_ndbcluster::update_table_comment(
+ /* out: table comment + additional */
+ const char* comment)/* in: table comment defined by user */
+{
+ uint length= strlen(comment);
+ if (length > 64000 - 3)
+ {
+ return((char*)comment); /* string too long */
+ }
+
+ Ndb* ndb;
+ if (!(ndb= get_ndb()))
+ {
+ return((char*)comment);
+ }
+
+ if (ndb->setDatabaseName(m_dbname))
+ {
+ return((char*)comment);
+ }
+ const NDBTAB* tab= m_table;
+ DBUG_ASSERT(tab != NULL);
+
+ char *str;
+ const char *fmt="%s%snumber_of_replicas: %d";
+ const unsigned fmt_len_plus_extra= length + strlen(fmt);
+ if ((str= (char*) my_malloc(fmt_len_plus_extra, MYF(0))) == NULL)
+ {
+ sql_print_error("ha_ndbcluster::update_table_comment: "
+ "my_malloc(%u) failed", (unsigned int)fmt_len_plus_extra);
+ return (char*)comment;
+ }
+
+ my_snprintf(str,fmt_len_plus_extra,fmt,comment,
+ length > 0 ? " ":"",
+ tab->getReplicaCount());
+ return str;
+}
+
+
+/**
+ Utility thread main loop.
+*/
+pthread_handler_t ndb_util_thread_func(void *arg __attribute__((unused)))
+{
+ THD *thd; /* needs to be first for thread_stack */
+ struct timespec abstime;
+ Thd_ndb *thd_ndb;
+ uint share_list_size= 0;
+ NDB_SHARE **share_list= NULL;
+
+ my_thread_init();
+ DBUG_ENTER("ndb_util_thread");
+ DBUG_PRINT("enter", ("cache_check_time: %lu", opt_ndb_cache_check_time));
+
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+
+ thd= new THD; /* note that contructor of THD uses DBUG_ */
+ if (thd == NULL)
+ {
+ my_errno= HA_ERR_OUT_OF_MEM;
+ DBUG_RETURN(NULL);
+ }
+ THD_CHECK_SENTRY(thd);
+ pthread_detach_this_thread();
+ ndb_util_thread= pthread_self();
+
+ thd->thread_stack= (char*)&thd; /* remember where our stack is */
+ if (thd->store_globals())
+ goto ndb_util_thread_fail;
+ thd->init_for_queries();
+ thd->main_security_ctx.host_or_ip= "";
+ thd->client_capabilities = 0;
+ my_net_init(&thd->net, 0, MYF(MY_THREAD_SPECIFIC));
+ thd->main_security_ctx.master_access= ~0;
+ thd->main_security_ctx.priv_user[0] = 0;
+ /* Do not use user-supplied timeout value for system threads. */
+ thd->variables.lock_wait_timeout= LONG_TIMEOUT;
+
+ CHARSET_INFO *charset_connection;
+ charset_connection= get_charset_by_csname("utf8",
+ MY_CS_PRIMARY, MYF(MY_WME));
+ thd->variables.character_set_client= charset_connection;
+ thd->variables.character_set_results= charset_connection;
+ thd->variables.collation_connection= charset_connection;
+ thd->update_charset();
+
+ /* Signal successful initialization */
+ ndb_util_thread_running= 1;
+ mysql_cond_signal(&COND_ndb_util_ready);
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+
+ /*
+ wait for mysql server to start
+ */
+ mysql_mutex_lock(&LOCK_server_started);
+ while (!mysqld_server_started)
+ {
+ set_timespec(abstime, 1);
+ mysql_cond_timedwait(&COND_server_started, &LOCK_server_started,
+ &abstime);
+ if (ndbcluster_terminating)
+ {
+ mysql_mutex_unlock(&LOCK_server_started);
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ goto ndb_util_thread_end;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_server_started);
+
+ /*
+ Wait for cluster to start
+ */
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ while (!ndb_cluster_node_id && (ndbcluster_hton->slot != ~(uint)0))
+ {
+ /* ndb not connected yet */
+ mysql_cond_wait(&COND_ndb_util_thread, &LOCK_ndb_util_thread);
+ if (ndbcluster_terminating)
+ goto ndb_util_thread_end;
+ }
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+
+ /* Get thd_ndb for this thread */
+ if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb()))
+ {
+ sql_print_error("Could not allocate Thd_ndb object");
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ goto ndb_util_thread_end;
+ }
+ set_thd_ndb(thd, thd_ndb);
+ thd_ndb->options|= TNO_NO_LOG_SCHEMA_OP;
+
+#ifdef HAVE_NDB_BINLOG
+ if (opt_ndb_extra_logging && ndb_binlog_running)
+ sql_print_information("NDB Binlog: Ndb tables initially read only.");
+ /* create tables needed by the replication */
+ ndbcluster_setup_binlog_table_shares(thd);
+#else
+ /*
+ Get all table definitions from the storage node
+ */
+ ndbcluster_find_all_files(thd);
+#endif
+
+ set_timespec(abstime, 0);
+ for (;;)
+ {
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ if (!ndbcluster_terminating)
+ mysql_cond_timedwait(&COND_ndb_util_thread,
+ &LOCK_ndb_util_thread,
+ &abstime);
+ if (ndbcluster_terminating) /* Shutting down server */
+ goto ndb_util_thread_end;
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+#ifdef NDB_EXTRA_DEBUG_UTIL_THREAD
+ DBUG_PRINT("ndb_util_thread", ("Started, opt_ndb_cache_check_time: %lu",
+ opt_ndb_cache_check_time));
+#endif
+
+#ifdef HAVE_NDB_BINLOG
+ /*
+ Check that the ndb_apply_status_share and ndb_schema_share
+ have been created.
+ If not try to create it
+ */
+ if (!ndb_binlog_tables_inited)
+ ndbcluster_setup_binlog_table_shares(thd);
+#endif
+
+ if (opt_ndb_cache_check_time == 0)
+ {
+ /* Wake up in 1 second to check if value has changed */
+ set_timespec(abstime, 1);
+ continue;
+ }
+
+ /* Lock mutex and fill list with pointers to all open tables */
+ NDB_SHARE *share;
+ mysql_mutex_lock(&ndbcluster_mutex);
+ uint i, open_count, record_count= ndbcluster_open_tables.records;
+ if (share_list_size < record_count)
+ {
+ NDB_SHARE ** new_share_list= new NDB_SHARE * [record_count];
+ if (!new_share_list)
+ {
+ sql_print_warning("ndb util thread: malloc failure, "
+ "query cache not maintained properly");
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ goto next; // At least do not crash
+ }
+ delete [] share_list;
+ share_list_size= record_count;
+ share_list= new_share_list;
+ }
+ for (i= 0, open_count= 0; i < record_count; i++)
+ {
+ share= (NDB_SHARE *)my_hash_element(&ndbcluster_open_tables, i);
+#ifdef HAVE_NDB_BINLOG
+ if ((share->use_count - (int) (share->op != 0) - (int) (share->op != 0))
+ <= 0)
+ continue; // injector thread is the only user, skip statistics
+ share->util_lock= current_thd; // Mark that util thread has lock
+#endif /* HAVE_NDB_BINLOG */
+ /* ndb_share reference temporary, free below */
+ share->use_count++; /* Make sure the table can't be closed */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ DBUG_PRINT("ndb_util_thread",
+ ("Found open table[%d]: %s, use_count: %d",
+ i, share->table_name, share->use_count));
+
+ /* Store pointer to table */
+ share_list[open_count++]= share;
+ }
+ mysql_mutex_unlock(&ndbcluster_mutex);
+
+ /* Iterate through the open files list */
+ for (i= 0; i < open_count; i++)
+ {
+ share= share_list[i];
+#ifdef HAVE_NDB_BINLOG
+ if ((share->use_count - (int) (share->op != 0) - (int) (share->op != 0))
+ <= 1)
+ {
+ /*
+ Util thread and injector thread is the only user, skip statistics
+ */
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ continue;
+ }
+#endif /* HAVE_NDB_BINLOG */
+ DBUG_PRINT("ndb_util_thread",
+ ("Fetching commit count for: %s", share->key));
+
+ struct Ndb_statistics stat;
+ uint lock;
+ mysql_mutex_lock(&share->mutex);
+ lock= share->commit_count_lock;
+ mysql_mutex_unlock(&share->mutex);
+ {
+ /* Contact NDB to get commit count for table */
+ Ndb* ndb= thd_ndb->ndb;
+ if (ndb->setDatabaseName(share->db))
+ {
+ goto loop_next;
+ }
+ Ndb_table_guard ndbtab_g(ndb->getDictionary(), share->table_name);
+ if (ndbtab_g.get_table() &&
+ ndb_get_table_statistics(NULL, FALSE, ndb,
+ ndbtab_g.get_table(), &stat) == 0)
+ {
+#ifndef DBUG_OFF
+ char buff[22], buff2[22];
+#endif
+ DBUG_PRINT("info",
+ ("Table: %s commit_count: %s rows: %s",
+ share->key,
+ llstr(stat.commit_count, buff),
+ llstr(stat.row_count, buff2)));
+ }
+ else
+ {
+ DBUG_PRINT("ndb_util_thread",
+ ("Error: Could not get commit count for table %s",
+ share->key));
+ stat.commit_count= 0;
+ }
+ }
+ loop_next:
+ mysql_mutex_lock(&share->mutex);
+ if (share->commit_count_lock == lock)
+ share->commit_count= stat.commit_count;
+ mysql_mutex_unlock(&share->mutex);
+
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+next:
+ /* Calculate new time to wake up */
+ int secs= 0;
+ int msecs= opt_ndb_cache_check_time;
+
+ struct timeval tick_time;
+ gettimeofday(&tick_time, 0);
+ abstime.tv_sec= tick_time.tv_sec;
+ abstime.tv_nsec= tick_time.tv_usec * 1000;
+
+ if (msecs >= 1000){
+ secs= msecs / 1000;
+ msecs= msecs % 1000;
+ }
+
+ abstime.tv_sec+= secs;
+ abstime.tv_nsec+= msecs * 1000000;
+ if (abstime.tv_nsec >= 1000000000) {
+ abstime.tv_sec+= 1;
+ abstime.tv_nsec-= 1000000000;
+ }
+ }
+
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+
+ndb_util_thread_end:
+ndb_util_thread_fail:
+ if (share_list)
+ delete [] share_list;
+ delete thd;
+
+ /* signal termination */
+ ndb_util_thread_running= 0;
+ mysql_cond_signal(&COND_ndb_util_ready);
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+ DBUG_PRINT("exit", ("ndb_util_thread"));
+
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+ my_thread_end();
+ pthread_exit(0);
+ return NULL; // Avoid compiler warnings
+}
+
+/*
+ Condition pushdown
+*/
+/**
+ Push a condition to ndbcluster storage engine for evaluation
+ during table and index scans. The conditions will be stored on a stack
+ for possibly storing several conditions. The stack can be popped
+ by calling cond_pop, handler::extra(HA_EXTRA_RESET) (handler::reset())
+ will clear the stack.
+ The current implementation supports arbitrary AND/OR nested conditions
+ with comparisons between columns and constants (including constant
+ expressions and function calls) and the following comparison operators:
+ =, !=, >, >=, <, <=, "is null", and "is not null".
+
+ @retval
+ NULL The condition was supported and will be evaluated for each
+ row found during the scan
+ @retval
+ cond The condition was not supported and all rows will be returned from
+ the scan for evaluation (and thus not saved on stack)
+*/
+const
+COND*
+ha_ndbcluster::cond_push(const COND *cond)
+{
+ DBUG_ENTER("cond_push");
+ if (!m_cond)
+ m_cond= new ha_ndbcluster_cond;
+ if (!m_cond)
+ {
+ my_errno= HA_ERR_OUT_OF_MEM;
+ DBUG_RETURN(NULL);
+ }
+ DBUG_EXECUTE("where",print_where((COND *)cond, m_tabname, QT_ORDINARY););
+ DBUG_RETURN(m_cond->cond_push(cond, table, (NDBTAB *)m_table));
+}
+
+/**
+ Pop the top condition from the condition stack of the handler instance.
+*/
+void
+ha_ndbcluster::cond_pop()
+{
+ if (m_cond)
+ m_cond->cond_pop();
+}
+
+
+/*
+ get table space info for SHOW CREATE TABLE
+*/
+char* ha_ndbcluster::get_tablespace_name(THD *thd, char* name, uint name_len)
+{
+ Ndb *ndb= check_ndb_in_thd(thd);
+ NDBDICT *ndbdict= ndb->getDictionary();
+ NdbError ndberr;
+ Uint32 id;
+ ndb->setDatabaseName(m_dbname);
+ const NDBTAB *ndbtab= m_table;
+ DBUG_ASSERT(ndbtab != NULL);
+ if (!ndbtab->getTablespace(&id))
+ {
+ return 0;
+ }
+ {
+ NdbDictionary::Tablespace ts= ndbdict->getTablespace(id);
+ ndberr= ndbdict->getNdbError();
+ if(ndberr.classification != NdbError::NoError)
+ goto err;
+ DBUG_PRINT("info", ("Found tablespace '%s'", ts.getName()));
+ if (name)
+ {
+ strxnmov(name, name_len, ts.getName(), NullS);
+ return name;
+ }
+ else
+ return (my_strdup(ts.getName(), MYF(0)));
+ }
+err:
+ if (ndberr.status == NdbError::TemporaryError)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_TEMPORARY_ERRMSG, ER(ER_GET_TEMPORARY_ERRMSG),
+ ndberr.code, ndberr.message, "NDB");
+ else
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ ndberr.code, ndberr.message, "NDB");
+ return 0;
+}
+
+/*
+ Implements the SHOW NDB STATUS command.
+*/
+bool
+ndbcluster_show_status(handlerton *hton, THD* thd, stat_print_fn *stat_print,
+ enum ha_stat_type stat_type)
+{
+ char buf[IO_SIZE];
+ uint buflen;
+ DBUG_ENTER("ndbcluster_show_status");
+
+ if (stat_type != HA_ENGINE_STATUS)
+ {
+ DBUG_RETURN(FALSE);
+ }
+
+ update_status_variables(g_ndb_cluster_connection);
+ buflen=
+ my_snprintf(buf, sizeof(buf),
+ "cluster_node_id=%ld, "
+ "connected_host=%s, "
+ "connected_port=%ld, "
+ "number_of_data_nodes=%ld, "
+ "number_of_ready_data_nodes=%ld, "
+ "connect_count=%ld",
+ ndb_cluster_node_id,
+ ndb_connected_host,
+ ndb_connected_port,
+ ndb_number_of_data_nodes,
+ ndb_number_of_ready_data_nodes,
+ ndb_connect_count);
+ if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length,
+ STRING_WITH_LEN("connection"), buf, buflen))
+ DBUG_RETURN(TRUE);
+
+ if (get_thd_ndb(thd) && get_thd_ndb(thd)->ndb)
+ {
+ Ndb* ndb= (get_thd_ndb(thd))->ndb;
+ Ndb::Free_list_usage tmp;
+ tmp.m_name= 0;
+ while (ndb->get_free_list_usage(&tmp))
+ {
+ buflen=
+ my_snprintf(buf, sizeof(buf),
+ "created=%u, free=%u, sizeof=%u",
+ tmp.m_created, tmp.m_free, tmp.m_sizeof);
+ if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length,
+ tmp.m_name, strlen(tmp.m_name), buf, buflen))
+ DBUG_RETURN(TRUE);
+ }
+ }
+#ifdef HAVE_NDB_BINLOG
+ ndbcluster_show_status_binlog(thd, stat_print, stat_type);
+#endif
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Create a table in NDB Cluster
+ */
+static uint get_no_fragments(ulonglong max_rows)
+{
+#if MYSQL_VERSION_ID >= 50000
+ uint acc_row_size= 25 + /*safety margin*/ 2;
+#else
+ uint acc_row_size= pk_length*4;
+ /* add acc overhead */
+ if (pk_length <= 8) /* main page will set the limit */
+ acc_row_size+= 25 + /*safety margin*/ 2;
+ else /* overflow page will set the limit */
+ acc_row_size+= 4 + /*safety margin*/ 4;
+#endif
+ ulonglong acc_fragment_size= 512*1024*1024;
+#if MYSQL_VERSION_ID >= 50100
+ return (max_rows*acc_row_size)/acc_fragment_size+1;
+#else
+ return ((max_rows*acc_row_size)/acc_fragment_size+1
+ +1/*correct rounding*/)/2;
+#endif
+}
+
+
+/*
+ Routine to adjust default number of partitions to always be a multiple
+ of number of nodes and never more than 4 times the number of nodes.
+
+*/
+static bool adjusted_frag_count(uint no_fragments, uint no_nodes,
+ uint &reported_frags)
+{
+ uint i= 0;
+ reported_frags= no_nodes;
+ while (reported_frags < no_fragments && ++i < 4 &&
+ (reported_frags + no_nodes) < MAX_PARTITIONS)
+ reported_frags+= no_nodes;
+ return (reported_frags < no_fragments);
+}
+
+int ha_ndbcluster::get_default_no_partitions(HA_CREATE_INFO *create_info)
+{
+ ha_rows max_rows, min_rows;
+ if (create_info)
+ {
+ max_rows= create_info->max_rows;
+ min_rows= create_info->min_rows;
+ }
+ else
+ {
+ max_rows= table_share->max_rows;
+ min_rows= table_share->min_rows;
+ }
+ uint reported_frags;
+ uint no_fragments=
+ get_no_fragments(max_rows >= min_rows ? max_rows : min_rows);
+ uint no_nodes= g_ndb_cluster_connection->no_db_nodes();
+ if (adjusted_frag_count(no_fragments, no_nodes, reported_frags))
+ {
+ push_warning(current_thd,
+ Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
+ "Ndb might have problems storing the max amount of rows specified");
+ }
+ return (int)reported_frags;
+}
+
+
+/*
+ Set-up auto-partitioning for NDB Cluster
+
+ SYNOPSIS
+ set_auto_partitions()
+ part_info Partition info struct to set-up
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ Set-up auto partitioning scheme for tables that didn't define any
+ partitioning. We'll use PARTITION BY KEY() in this case which
+ translates into partition by primary key if a primary key exists
+ and partition by hidden key otherwise.
+*/
+
+
+enum ndb_distribution_enum { ND_KEYHASH= 0, ND_LINHASH= 1 };
+static const char* distribution_names[]= { "KEYHASH", "LINHASH", NullS };
+static ulong default_ndb_distribution= ND_KEYHASH;
+static TYPELIB distribution_typelib= {
+ array_elements(distribution_names) - 1,
+ "",
+ distribution_names,
+ NULL
+};
+static MYSQL_SYSVAR_ENUM(
+ distribution, /* name */
+ default_ndb_distribution, /* var */
+ PLUGIN_VAR_RQCMDARG,
+ "Default distribution for new tables in ndb",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ ND_KEYHASH, /* default */
+ &distribution_typelib /* typelib */
+);
+
+void ha_ndbcluster::set_auto_partitions(partition_info *part_info)
+{
+ DBUG_ENTER("ha_ndbcluster::set_auto_partitions");
+ part_info->list_of_part_fields= TRUE;
+ part_info->part_type= HASH_PARTITION;
+ switch (default_ndb_distribution)
+ {
+ case ND_KEYHASH:
+ part_info->linear_hash_ind= FALSE;
+ break;
+ case ND_LINHASH:
+ part_info->linear_hash_ind= TRUE;
+ break;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+int ha_ndbcluster::set_range_data(void *tab_ref, partition_info *part_info)
+{
+ NDBTAB *tab= (NDBTAB*)tab_ref;
+ int32 *range_data= (int32*)my_malloc(part_info->num_parts*sizeof(int32),
+ MYF(0));
+ uint i;
+ int error= 0;
+ bool unsigned_flag= part_info->part_expr->unsigned_flag;
+ DBUG_ENTER("set_range_data");
+
+ if (!range_data)
+ {
+ mem_alloc_error(part_info->num_parts*sizeof(int32));
+ DBUG_RETURN(1);
+ }
+ for (i= 0; i < part_info->num_parts; i++)
+ {
+ longlong range_val= part_info->range_int_array[i];
+ if (unsigned_flag)
+ range_val-= 0x8000000000000000ULL;
+ if (range_val < INT_MIN32 || range_val >= INT_MAX32)
+ {
+ if ((i != part_info->num_parts - 1) ||
+ (range_val != LONGLONG_MAX))
+ {
+ my_error(ER_LIMITED_PART_RANGE, MYF(0), "NDB");
+ error= 1;
+ goto error;
+ }
+ range_val= INT_MAX32;
+ }
+ range_data[i]= (int32)range_val;
+ }
+ tab->setRangeListData(range_data, sizeof(int32)*part_info->num_parts);
+error:
+ my_free(range_data);
+ DBUG_RETURN(error);
+}
+
+int ha_ndbcluster::set_list_data(void *tab_ref, partition_info *part_info)
+{
+ NDBTAB *tab= (NDBTAB*)tab_ref;
+ int32 *list_data= (int32*)my_malloc(part_info->num_list_values * 2
+ * sizeof(int32), MYF(0));
+ uint32 *part_id, i;
+ int error= 0;
+ bool unsigned_flag= part_info->part_expr->unsigned_flag;
+ DBUG_ENTER("set_list_data");
+
+ if (!list_data)
+ {
+ mem_alloc_error(part_info->num_list_values*2*sizeof(int32));
+ DBUG_RETURN(1);
+ }
+ for (i= 0; i < part_info->num_list_values; i++)
+ {
+ LIST_PART_ENTRY *list_entry= &part_info->list_array[i];
+ longlong list_val= list_entry->list_value;
+ if (unsigned_flag)
+ list_val-= 0x8000000000000000ULL;
+ if (list_val < INT_MIN32 || list_val > INT_MAX32)
+ {
+ my_error(ER_LIMITED_PART_RANGE, MYF(0), "NDB");
+ error= 1;
+ goto error;
+ }
+ list_data[2*i]= (int32)list_val;
+ part_id= (uint32*)&list_data[2*i+1];
+ *part_id= list_entry->partition_id;
+ }
+ tab->setRangeListData(list_data, 2*sizeof(int32)*part_info->num_list_values);
+error:
+ my_free(list_data);
+ DBUG_RETURN(error);
+}
+
+/*
+ User defined partitioning set-up. We need to check how many fragments the
+ user wants defined and which node groups to put those into. Later we also
+ want to attach those partitions to a tablespace.
+
+ All the functionality of the partition function, partition limits and so
+ forth are entirely handled by the MySQL Server. There is one exception to
+ this rule for PARTITION BY KEY where NDB handles the hash function and
+ this type can thus be handled transparently also by NDB API program.
+ For RANGE, HASH and LIST and subpartitioning the NDB API programs must
+ implement the function to map to a partition.
+*/
+
+uint ha_ndbcluster::set_up_partition_info(partition_info *part_info,
+ TABLE *table,
+ void *tab_par)
+{
+ uint16 frag_data[MAX_PARTITIONS];
+ char *ts_names[MAX_PARTITIONS];
+ ulong fd_index= 0, i, j;
+ NDBTAB *tab= (NDBTAB*)tab_par;
+ NDBTAB::FragmentType ftype= NDBTAB::UserDefined;
+ partition_element *part_elem;
+ bool first= TRUE;
+ uint tot_ts_name_len;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ int error;
+ DBUG_ENTER("ha_ndbcluster::set_up_partition_info");
+
+ if (part_info->part_type == HASH_PARTITION &&
+ part_info->list_of_part_fields == TRUE)
+ {
+ Field **fields= part_info->part_field_array;
+
+ if (part_info->linear_hash_ind)
+ ftype= NDBTAB::DistrKeyLin;
+ else
+ ftype= NDBTAB::DistrKeyHash;
+
+ for (i= 0; i < part_info->part_field_list.elements; i++)
+ {
+ NDBCOL *col= tab->getColumn(fields[i]->field_index);
+ DBUG_PRINT("info",("setting dist key on %s", col->getName()));
+ col->setPartitionKey(TRUE);
+ }
+ }
+ else
+ {
+ if (!current_thd->variables.new_mode)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ILLEGAL_HA_CREATE_OPTION,
+ ER(ER_ILLEGAL_HA_CREATE_OPTION),
+ ndbcluster_hton_name,
+ "LIST, RANGE and HASH partition disabled by default,"
+ " use --new option to enable");
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+ /*
+ Create a shadow field for those tables that have user defined
+ partitioning. This field stores the value of the partition
+ function such that NDB can handle reorganisations of the data
+ even when the MySQL Server isn't available to assist with
+ calculation of the partition function value.
+ */
+ NDBCOL col;
+ DBUG_PRINT("info", ("Generating partition func value field"));
+ col.setName("$PART_FUNC_VALUE");
+ col.setType(NdbDictionary::Column::Int);
+ col.setLength(1);
+ col.setNullable(FALSE);
+ col.setPrimaryKey(FALSE);
+ col.setAutoIncrement(FALSE);
+ tab->addColumn(col);
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ if ((error= set_range_data((void*)tab, part_info)))
+ {
+ DBUG_RETURN(error);
+ }
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+ if ((error= set_list_data((void*)tab, part_info)))
+ {
+ DBUG_RETURN(error);
+ }
+ }
+ }
+ tab->setFragmentType(ftype);
+ i= 0;
+ tot_ts_name_len= 0;
+ do
+ {
+ uint ng;
+ part_elem= part_it++;
+ if (!part_info->is_sub_partitioned())
+ {
+ ng= part_elem->nodegroup_id;
+ if (first && ng == UNDEF_NODEGROUP)
+ ng= 0;
+ ts_names[fd_index]= part_elem->tablespace_name;
+ frag_data[fd_index++]= ng;
+ }
+ else
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ j= 0;
+ do
+ {
+ part_elem= sub_it++;
+ ng= part_elem->nodegroup_id;
+ if (first && ng == UNDEF_NODEGROUP)
+ ng= 0;
+ ts_names[fd_index]= part_elem->tablespace_name;
+ frag_data[fd_index++]= ng;
+ } while (++j < part_info->num_subparts);
+ }
+ first= FALSE;
+ } while (++i < part_info->num_parts);
+ tab->setDefaultNoPartitionsFlag(part_info->use_default_num_partitions);
+ tab->setLinearFlag(part_info->linear_hash_ind);
+ {
+ ha_rows max_rows= table_share->max_rows;
+ ha_rows min_rows= table_share->min_rows;
+ if (max_rows < min_rows)
+ max_rows= min_rows;
+ if (max_rows != (ha_rows)0) /* default setting, don't set fragmentation */
+ {
+ tab->setMaxRows(max_rows);
+ tab->setMinRows(min_rows);
+ }
+ }
+ tab->setTablespaceNames(ts_names, fd_index*sizeof(char*));
+ tab->setFragmentCount(fd_index);
+ tab->setFragmentData(&frag_data, fd_index*2);
+ DBUG_RETURN(0);
+}
+
+
+bool ha_ndbcluster::check_if_incompatible_data(HA_CREATE_INFO *create_info,
+ uint table_changes)
+{
+ DBUG_ENTER("ha_ndbcluster::check_if_incompatible_data");
+ uint i;
+ const NDBTAB *tab= (const NDBTAB *) m_table;
+
+ if (THDVAR(current_thd, use_copying_alter_table))
+ {
+ DBUG_PRINT("info", ("On-line alter table disabled"));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+
+ int pk= 0;
+ int ai= 0;
+
+ if (create_info->tablespace)
+ create_info->storage_media = HA_SM_DISK;
+ else
+ create_info->storage_media = HA_SM_MEMORY;
+
+ for (i= 0; i < table->s->fields; i++)
+ {
+ Field *field= table->field[i];
+ const NDBCOL *col= tab->getColumn(i);
+ if ((col->getStorageType() == NDB_STORAGETYPE_MEMORY && create_info->storage_media != HA_SM_MEMORY) ||
+ (col->getStorageType() == NDB_STORAGETYPE_DISK && create_info->storage_media != HA_SM_DISK))
+ {
+ DBUG_PRINT("info", ("Column storage media is changed"));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+
+ if (field->flags & FIELD_IS_RENAMED)
+ {
+ DBUG_PRINT("info", ("Field has been renamed, copy table"));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+ if ((field->flags & FIELD_IN_ADD_INDEX) &&
+ col->getStorageType() == NdbDictionary::Column::StorageTypeDisk)
+ {
+ DBUG_PRINT("info", ("add/drop index not supported for disk stored column"));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+
+ if (field->flags & PRI_KEY_FLAG)
+ pk=1;
+ if (field->flags & FIELD_IN_ADD_INDEX)
+ ai=1;
+ }
+
+ char tablespace_name[FN_LEN + 1];
+ if (get_tablespace_name(current_thd, tablespace_name, FN_LEN))
+ {
+ if (create_info->tablespace)
+ {
+ if (strcmp(create_info->tablespace, tablespace_name))
+ {
+ DBUG_PRINT("info", ("storage media is changed, old tablespace=%s, new tablespace=%s",
+ tablespace_name, create_info->tablespace));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info", ("storage media is changed, old is DISK and tablespace=%s, new is MEM",
+ tablespace_name));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+ }
+ else
+ {
+ if (create_info->storage_media != HA_SM_MEMORY)
+ {
+ DBUG_PRINT("info", ("storage media is changed, old is MEM, new is DISK and tablespace=%s",
+ create_info->tablespace));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+ }
+
+ if (table_changes != IS_EQUAL_YES)
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+
+ /* Check that auto_increment value was not changed */
+ if ((create_info->used_fields & HA_CREATE_USED_AUTO) &&
+ create_info->auto_increment_value != 0)
+ {
+ DBUG_PRINT("info", ("auto_increment value changed"));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+
+ /* Check that row format didn't change */
+ if ((create_info->used_fields & HA_CREATE_USED_AUTO) &&
+ get_row_type() != create_info->row_type)
+ {
+ DBUG_PRINT("info", ("row format changed"));
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+ }
+
+ DBUG_PRINT("info", ("new table seems compatible"));
+ DBUG_RETURN(COMPATIBLE_DATA_YES);
+}
+
+bool set_up_tablespace(st_alter_tablespace *alter_info,
+ NdbDictionary::Tablespace *ndb_ts)
+{
+ ndb_ts->setName(alter_info->tablespace_name);
+ ndb_ts->setExtentSize(alter_info->extent_size);
+ ndb_ts->setDefaultLogfileGroup(alter_info->logfile_group_name);
+ return FALSE;
+}
+
+bool set_up_datafile(st_alter_tablespace *alter_info,
+ NdbDictionary::Datafile *ndb_df)
+{
+ if (alter_info->max_size > 0)
+ {
+ my_error(ER_TABLESPACE_AUTO_EXTEND_ERROR, MYF(0));
+ return TRUE;
+ }
+ ndb_df->setPath(alter_info->data_file_name);
+ ndb_df->setSize(alter_info->initial_size);
+ ndb_df->setTablespace(alter_info->tablespace_name);
+ return FALSE;
+}
+
+bool set_up_logfile_group(st_alter_tablespace *alter_info,
+ NdbDictionary::LogfileGroup *ndb_lg)
+{
+ ndb_lg->setName(alter_info->logfile_group_name);
+ ndb_lg->setUndoBufferSize(alter_info->undo_buffer_size);
+ return FALSE;
+}
+
+bool set_up_undofile(st_alter_tablespace *alter_info,
+ NdbDictionary::Undofile *ndb_uf)
+{
+ ndb_uf->setPath(alter_info->undo_file_name);
+ ndb_uf->setSize(alter_info->initial_size);
+ ndb_uf->setLogfileGroup(alter_info->logfile_group_name);
+ return FALSE;
+}
+
+int ndbcluster_alter_tablespace(handlerton *hton,
+ THD* thd, st_alter_tablespace *alter_info)
+{
+ int is_tablespace= 0;
+ NdbError err;
+ NDBDICT *dict;
+ int error;
+ const char *errmsg;
+ Ndb *ndb;
+ DBUG_ENTER("ha_ndbcluster::alter_tablespace");
+ LINT_INIT(errmsg);
+
+ ndb= check_ndb_in_thd(thd);
+ if (ndb == NULL)
+ {
+ DBUG_RETURN(HA_ERR_NO_CONNECTION);
+ }
+ dict= ndb->getDictionary();
+
+ switch (alter_info->ts_cmd_type){
+ case (CREATE_TABLESPACE):
+ {
+ error= ER_CREATE_FILEGROUP_FAILED;
+
+ NdbDictionary::Tablespace ndb_ts;
+ NdbDictionary::Datafile ndb_df;
+ NdbDictionary::ObjectId objid;
+ if (set_up_tablespace(alter_info, &ndb_ts))
+ {
+ DBUG_RETURN(1);
+ }
+ if (set_up_datafile(alter_info, &ndb_df))
+ {
+ DBUG_RETURN(1);
+ }
+ errmsg= "TABLESPACE";
+ if (dict->createTablespace(ndb_ts, &objid))
+ {
+ DBUG_PRINT("error", ("createTablespace returned %d", error));
+ goto ndberror;
+ }
+ DBUG_PRINT("alter_info", ("Successfully created Tablespace"));
+ errmsg= "DATAFILE";
+ if (dict->createDatafile(ndb_df))
+ {
+ err= dict->getNdbError();
+ NdbDictionary::Tablespace tmp= dict->getTablespace(ndb_ts.getName());
+ if (dict->getNdbError().code == 0 &&
+ tmp.getObjectId() == objid.getObjectId() &&
+ tmp.getObjectVersion() == objid.getObjectVersion())
+ {
+ dict->dropTablespace(tmp);
+ }
+
+ DBUG_PRINT("error", ("createDatafile returned %d", error));
+ goto ndberror2;
+ }
+ is_tablespace= 1;
+ break;
+ }
+ case (ALTER_TABLESPACE):
+ {
+ error= ER_ALTER_FILEGROUP_FAILED;
+ if (alter_info->ts_alter_tablespace_type == ALTER_TABLESPACE_ADD_FILE)
+ {
+ NdbDictionary::Datafile ndb_df;
+ if (set_up_datafile(alter_info, &ndb_df))
+ {
+ DBUG_RETURN(1);
+ }
+ errmsg= " CREATE DATAFILE";
+ if (dict->createDatafile(ndb_df))
+ {
+ goto ndberror;
+ }
+ }
+ else if(alter_info->ts_alter_tablespace_type == ALTER_TABLESPACE_DROP_FILE)
+ {
+ NdbDictionary::Tablespace ts= dict->getTablespace(alter_info->tablespace_name);
+ NdbDictionary::Datafile df= dict->getDatafile(0, alter_info->data_file_name);
+ NdbDictionary::ObjectId objid;
+ df.getTablespaceId(&objid);
+ if (ts.getObjectId() == objid.getObjectId() &&
+ strcmp(df.getPath(), alter_info->data_file_name) == 0)
+ {
+ errmsg= " DROP DATAFILE";
+ if (dict->dropDatafile(df))
+ {
+ goto ndberror;
+ }
+ }
+ else
+ {
+ DBUG_PRINT("error", ("No such datafile"));
+ my_error(ER_ALTER_FILEGROUP_FAILED, MYF(0), " NO SUCH FILE");
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ DBUG_PRINT("error", ("Unsupported alter tablespace: %d",
+ alter_info->ts_alter_tablespace_type));
+ DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED);
+ }
+ is_tablespace= 1;
+ break;
+ }
+ case (CREATE_LOGFILE_GROUP):
+ {
+ error= ER_CREATE_FILEGROUP_FAILED;
+ NdbDictionary::LogfileGroup ndb_lg;
+ NdbDictionary::Undofile ndb_uf;
+ NdbDictionary::ObjectId objid;
+ if (alter_info->undo_file_name == NULL)
+ {
+ /*
+ REDO files in LOGFILE GROUP not supported yet
+ */
+ DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED);
+ }
+ if (set_up_logfile_group(alter_info, &ndb_lg))
+ {
+ DBUG_RETURN(1);
+ }
+ errmsg= "LOGFILE GROUP";
+ if (dict->createLogfileGroup(ndb_lg, &objid))
+ {
+ goto ndberror;
+ }
+ DBUG_PRINT("alter_info", ("Successfully created Logfile Group"));
+ if (set_up_undofile(alter_info, &ndb_uf))
+ {
+ DBUG_RETURN(1);
+ }
+ errmsg= "UNDOFILE";
+ if (dict->createUndofile(ndb_uf))
+ {
+ err= dict->getNdbError();
+ NdbDictionary::LogfileGroup tmp= dict->getLogfileGroup(ndb_lg.getName());
+ if (dict->getNdbError().code == 0 &&
+ tmp.getObjectId() == objid.getObjectId() &&
+ tmp.getObjectVersion() == objid.getObjectVersion())
+ {
+ dict->dropLogfileGroup(tmp);
+ }
+ goto ndberror2;
+ }
+ break;
+ }
+ case (ALTER_LOGFILE_GROUP):
+ {
+ error= ER_ALTER_FILEGROUP_FAILED;
+ if (alter_info->undo_file_name == NULL)
+ {
+ /*
+ REDO files in LOGFILE GROUP not supported yet
+ */
+ DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED);
+ }
+ NdbDictionary::Undofile ndb_uf;
+ if (set_up_undofile(alter_info, &ndb_uf))
+ {
+ DBUG_RETURN(1);
+ }
+ errmsg= "CREATE UNDOFILE";
+ if (dict->createUndofile(ndb_uf))
+ {
+ goto ndberror;
+ }
+ break;
+ }
+ case (DROP_TABLESPACE):
+ {
+ error= ER_DROP_FILEGROUP_FAILED;
+ errmsg= "TABLESPACE";
+ if (dict->dropTablespace(dict->getTablespace(alter_info->tablespace_name)))
+ {
+ goto ndberror;
+ }
+ is_tablespace= 1;
+ break;
+ }
+ case (DROP_LOGFILE_GROUP):
+ {
+ error= ER_DROP_FILEGROUP_FAILED;
+ errmsg= "LOGFILE GROUP";
+ if (dict->dropLogfileGroup(dict->getLogfileGroup(alter_info->logfile_group_name)))
+ {
+ goto ndberror;
+ }
+ break;
+ }
+ case (CHANGE_FILE_TABLESPACE):
+ {
+ DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED);
+ }
+ case (ALTER_ACCESS_MODE_TABLESPACE):
+ {
+ DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED);
+ }
+ default:
+ {
+ DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED);
+ }
+ }
+#ifdef HAVE_NDB_BINLOG
+ if (is_tablespace)
+ ndbcluster_log_schema_op(thd, 0,
+ thd->query(), thd->query_length(),
+ "", alter_info->tablespace_name,
+ 0, 0,
+ SOT_TABLESPACE, 0, 0);
+ else
+ ndbcluster_log_schema_op(thd, 0,
+ thd->query(), thd->query_length(),
+ "", alter_info->logfile_group_name,
+ 0, 0,
+ SOT_LOGFILE_GROUP, 0, 0);
+#endif
+ DBUG_RETURN(FALSE);
+
+ndberror:
+ err= dict->getNdbError();
+ndberror2:
+ set_ndb_err(thd, err);
+ ndb_to_mysql_error(&err);
+
+ my_error(error, MYF(0), errmsg);
+ DBUG_RETURN(1);
+}
+
+
+bool ha_ndbcluster::get_no_parts(const char *name, uint *num_parts)
+{
+ Ndb *ndb;
+ NDBDICT *dict;
+ int err;
+ DBUG_ENTER("ha_ndbcluster::get_no_parts");
+ LINT_INIT(err);
+
+ set_dbname(name);
+ set_tabname(name);
+ for (;;)
+ {
+ if (check_ndb_connection())
+ {
+ err= HA_ERR_NO_CONNECTION;
+ break;
+ }
+ ndb= get_ndb();
+ ndb->setDatabaseName(m_dbname);
+ Ndb_table_guard ndbtab_g(dict= ndb->getDictionary(), m_tabname);
+ if (!ndbtab_g.get_table())
+ ERR_BREAK(dict->getNdbError(), err);
+ *num_parts= ndbtab_g.get_table()->getFragmentCount();
+ DBUG_RETURN(FALSE);
+ }
+
+ print_error(err, MYF(0));
+ DBUG_RETURN(TRUE);
+}
+
+static int ndbcluster_fill_files_table(handlerton *hton,
+ THD *thd,
+ TABLE_LIST *tables,
+ COND *cond)
+{
+ TABLE* table= tables->table;
+ Ndb *ndb= check_ndb_in_thd(thd);
+ NdbDictionary::Dictionary* dict= ndb->getDictionary();
+ NdbDictionary::Dictionary::List dflist;
+ NdbError ndberr;
+ uint i;
+ DBUG_ENTER("ndbcluster_fill_files_table");
+
+ dict->listObjects(dflist, NdbDictionary::Object::Datafile);
+ ndberr= dict->getNdbError();
+ if (ndberr.classification != NdbError::NoError)
+ ERR_RETURN(ndberr);
+
+ for (i= 0; i < dflist.count; i++)
+ {
+ NdbDictionary::Dictionary::List::Element& elt = dflist.elements[i];
+ Ndb_cluster_connection_node_iter iter;
+ uint id;
+
+ g_ndb_cluster_connection->init_get_next_node(iter);
+
+ while ((id= g_ndb_cluster_connection->get_next_node(iter)))
+ {
+ init_fill_schema_files_row(table);
+ NdbDictionary::Datafile df= dict->getDatafile(id, elt.name);
+ ndberr= dict->getNdbError();
+ if(ndberr.classification != NdbError::NoError)
+ {
+ if (ndberr.classification == NdbError::SchemaError)
+ continue;
+
+ if (ndberr.classification == NdbError::UnknownResultError)
+ continue;
+
+ ERR_RETURN(ndberr);
+ }
+ NdbDictionary::Tablespace ts= dict->getTablespace(df.getTablespace());
+ ndberr= dict->getNdbError();
+ if (ndberr.classification != NdbError::NoError)
+ {
+ if (ndberr.classification == NdbError::SchemaError)
+ continue;
+ ERR_RETURN(ndberr);
+ }
+ table->field[IS_FILES_TABLE_CATALOG]->store(STRING_WITH_LEN("def"),
+ system_charset_info);
+ table->field[IS_FILES_FILE_NAME]->set_notnull();
+ table->field[IS_FILES_FILE_NAME]->store(elt.name, strlen(elt.name),
+ system_charset_info);
+ table->field[IS_FILES_FILE_TYPE]->set_notnull();
+ table->field[IS_FILES_FILE_TYPE]->store("DATAFILE",8,
+ system_charset_info);
+ table->field[IS_FILES_TABLESPACE_NAME]->set_notnull();
+ table->field[IS_FILES_TABLESPACE_NAME]->store(df.getTablespace(),
+ strlen(df.getTablespace()),
+ system_charset_info);
+ table->field[IS_FILES_LOGFILE_GROUP_NAME]->set_notnull();
+ table->field[IS_FILES_LOGFILE_GROUP_NAME]->
+ store(ts.getDefaultLogfileGroup(),
+ strlen(ts.getDefaultLogfileGroup()),
+ system_charset_info);
+ table->field[IS_FILES_ENGINE]->set_notnull();
+ table->field[IS_FILES_ENGINE]->store(ndbcluster_hton_name,
+ ndbcluster_hton_name_length,
+ system_charset_info);
+
+ table->field[IS_FILES_FREE_EXTENTS]->set_notnull();
+ table->field[IS_FILES_FREE_EXTENTS]->store(df.getFree()
+ / ts.getExtentSize());
+ table->field[IS_FILES_TOTAL_EXTENTS]->set_notnull();
+ table->field[IS_FILES_TOTAL_EXTENTS]->store(df.getSize()
+ / ts.getExtentSize());
+ table->field[IS_FILES_EXTENT_SIZE]->set_notnull();
+ table->field[IS_FILES_EXTENT_SIZE]->store(ts.getExtentSize());
+ table->field[IS_FILES_INITIAL_SIZE]->set_notnull();
+ table->field[IS_FILES_INITIAL_SIZE]->store(df.getSize());
+ table->field[IS_FILES_MAXIMUM_SIZE]->set_notnull();
+ table->field[IS_FILES_MAXIMUM_SIZE]->store(df.getSize());
+ table->field[IS_FILES_VERSION]->set_notnull();
+ table->field[IS_FILES_VERSION]->store(df.getObjectVersion());
+
+ table->field[IS_FILES_ROW_FORMAT]->set_notnull();
+ table->field[IS_FILES_ROW_FORMAT]->store("FIXED", 5, system_charset_info);
+
+ char extra[30];
+ int len= my_snprintf(extra, sizeof(extra), "CLUSTER_NODE=%u", id);
+ table->field[IS_FILES_EXTRA]->set_notnull();
+ table->field[IS_FILES_EXTRA]->store(extra, len, system_charset_info);
+ schema_table_store_record(thd, table);
+ }
+ }
+
+ NdbDictionary::Dictionary::List uflist;
+ dict->listObjects(uflist, NdbDictionary::Object::Undofile);
+ ndberr= dict->getNdbError();
+ if (ndberr.classification != NdbError::NoError)
+ ERR_RETURN(ndberr);
+
+ for (i= 0; i < uflist.count; i++)
+ {
+ NdbDictionary::Dictionary::List::Element& elt= uflist.elements[i];
+ Ndb_cluster_connection_node_iter iter;
+ unsigned id;
+
+ g_ndb_cluster_connection->init_get_next_node(iter);
+
+ while ((id= g_ndb_cluster_connection->get_next_node(iter)))
+ {
+ NdbDictionary::Undofile uf= dict->getUndofile(id, elt.name);
+ ndberr= dict->getNdbError();
+ if (ndberr.classification != NdbError::NoError)
+ {
+ if (ndberr.classification == NdbError::SchemaError)
+ continue;
+ if (ndberr.classification == NdbError::UnknownResultError)
+ continue;
+ ERR_RETURN(ndberr);
+ }
+ NdbDictionary::LogfileGroup lfg=
+ dict->getLogfileGroup(uf.getLogfileGroup());
+ ndberr= dict->getNdbError();
+ if (ndberr.classification != NdbError::NoError)
+ {
+ if (ndberr.classification == NdbError::SchemaError)
+ continue;
+ ERR_RETURN(ndberr);
+ }
+
+ init_fill_schema_files_row(table);
+ table->field[IS_FILES_FILE_NAME]->set_notnull();
+ table->field[IS_FILES_FILE_NAME]->store(elt.name, strlen(elt.name),
+ system_charset_info);
+ table->field[IS_FILES_FILE_TYPE]->set_notnull();
+ table->field[IS_FILES_FILE_TYPE]->store("UNDO LOG", 8,
+ system_charset_info);
+ NdbDictionary::ObjectId objid;
+ uf.getLogfileGroupId(&objid);
+ table->field[IS_FILES_LOGFILE_GROUP_NAME]->set_notnull();
+ table->field[IS_FILES_LOGFILE_GROUP_NAME]->store(uf.getLogfileGroup(),
+ strlen(uf.getLogfileGroup()),
+ system_charset_info);
+ table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->set_notnull();
+ table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->store(objid.getObjectId());
+ table->field[IS_FILES_ENGINE]->set_notnull();
+ table->field[IS_FILES_ENGINE]->store(ndbcluster_hton_name,
+ ndbcluster_hton_name_length,
+ system_charset_info);
+
+ table->field[IS_FILES_TOTAL_EXTENTS]->set_notnull();
+ table->field[IS_FILES_TOTAL_EXTENTS]->store(uf.getSize()/4);
+ table->field[IS_FILES_EXTENT_SIZE]->set_notnull();
+ table->field[IS_FILES_EXTENT_SIZE]->store(4);
+
+ table->field[IS_FILES_INITIAL_SIZE]->set_notnull();
+ table->field[IS_FILES_INITIAL_SIZE]->store(uf.getSize());
+ table->field[IS_FILES_MAXIMUM_SIZE]->set_notnull();
+ table->field[IS_FILES_MAXIMUM_SIZE]->store(uf.getSize());
+
+ table->field[IS_FILES_VERSION]->set_notnull();
+ table->field[IS_FILES_VERSION]->store(uf.getObjectVersion());
+
+ char extra[100];
+ int len= my_snprintf(extra,sizeof(extra),"CLUSTER_NODE=%u;UNDO_BUFFER_SIZE=%lu",
+ id, (ulong) lfg.getUndoBufferSize());
+ table->field[IS_FILES_EXTRA]->set_notnull();
+ table->field[IS_FILES_EXTRA]->store(extra, len, system_charset_info);
+ schema_table_store_record(thd, table);
+ }
+ }
+
+ // now for LFGs
+ NdbDictionary::Dictionary::List lfglist;
+ dict->listObjects(lfglist, NdbDictionary::Object::LogfileGroup);
+ ndberr= dict->getNdbError();
+ if (ndberr.classification != NdbError::NoError)
+ ERR_RETURN(ndberr);
+
+ for (i= 0; i < lfglist.count; i++)
+ {
+ NdbDictionary::Dictionary::List::Element& elt= lfglist.elements[i];
+
+ NdbDictionary::LogfileGroup lfg= dict->getLogfileGroup(elt.name);
+ ndberr= dict->getNdbError();
+ if (ndberr.classification != NdbError::NoError)
+ {
+ if (ndberr.classification == NdbError::SchemaError)
+ continue;
+ ERR_RETURN(ndberr);
+ }
+
+ init_fill_schema_files_row(table);
+ table->field[IS_FILES_FILE_TYPE]->set_notnull();
+ table->field[IS_FILES_FILE_TYPE]->store("UNDO LOG", 8,
+ system_charset_info);
+
+ table->field[IS_FILES_LOGFILE_GROUP_NAME]->set_notnull();
+ table->field[IS_FILES_LOGFILE_GROUP_NAME]->store(elt.name,
+ strlen(elt.name),
+ system_charset_info);
+ table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->set_notnull();
+ table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->store(lfg.getObjectId());
+ table->field[IS_FILES_ENGINE]->set_notnull();
+ table->field[IS_FILES_ENGINE]->store(ndbcluster_hton_name,
+ ndbcluster_hton_name_length,
+ system_charset_info);
+
+ table->field[IS_FILES_FREE_EXTENTS]->set_notnull();
+ table->field[IS_FILES_FREE_EXTENTS]->store(lfg.getUndoFreeWords());
+ table->field[IS_FILES_EXTENT_SIZE]->set_notnull();
+ table->field[IS_FILES_EXTENT_SIZE]->store(4);
+
+ table->field[IS_FILES_VERSION]->set_notnull();
+ table->field[IS_FILES_VERSION]->store(lfg.getObjectVersion());
+
+ char extra[100];
+ int len= my_snprintf(extra,sizeof(extra),
+ "UNDO_BUFFER_SIZE=%lu",
+ (ulong) lfg.getUndoBufferSize());
+ table->field[IS_FILES_EXTRA]->set_notnull();
+ table->field[IS_FILES_EXTRA]->store(extra, len, system_charset_info);
+ schema_table_store_record(thd, table);
+ }
+ DBUG_RETURN(0);
+}
+
+SHOW_VAR ndb_status_variables_export[]= {
+ {"Ndb", (char*) &ndb_status_variables, SHOW_ARRAY},
+ {NullS, NullS, SHOW_LONG}
+};
+
+static MYSQL_SYSVAR_ULONG(
+ cache_check_time, /* name */
+ opt_ndb_cache_check_time, /* var */
+ PLUGIN_VAR_RQCMDARG,
+ "A dedicated thread is created to, at the given "
+ "millisecond interval, invalidate the query cache "
+ "if another MySQL server in the cluster has changed "
+ "the data in the database.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 0, /* default */
+ 0, /* min */
+ ONE_YEAR_IN_SECONDS, /* max */
+ 0 /* block */
+);
+
+
+static MYSQL_SYSVAR_ULONG(
+ extra_logging, /* name */
+ opt_ndb_extra_logging, /* var */
+ PLUGIN_VAR_OPCMDARG,
+ "Turn on more logging in the error log.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 1, /* default */
+ 0, /* min */
+ 0, /* max */
+ 0 /* block */
+);
+
+
+ulong opt_ndb_report_thresh_binlog_epoch_slip;
+static MYSQL_SYSVAR_ULONG(
+ report_thresh_binlog_epoch_slip, /* name */
+ opt_ndb_report_thresh_binlog_epoch_slip,/* var */
+ PLUGIN_VAR_RQCMDARG,
+ "Threshold on number of epochs to be behind before reporting binlog "
+ "status. E.g. 3 means that if the difference between what epoch has "
+ "been received from the storage nodes and what has been applied to "
+ "the binlog is 3 or more, a status message will be sent to the cluster "
+ "log.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 3, /* default */
+ 0, /* min */
+ 256, /* max */
+ 0 /* block */
+);
+
+
+ulong opt_ndb_report_thresh_binlog_mem_usage;
+static MYSQL_SYSVAR_ULONG(
+ report_thresh_binlog_mem_usage, /* name */
+ opt_ndb_report_thresh_binlog_mem_usage,/* var */
+ PLUGIN_VAR_RQCMDARG,
+ "Threshold on percentage of free memory before reporting binlog "
+ "status. E.g. 10 means that if amount of available memory for "
+ "receiving binlog data from the storage nodes goes below 10%, "
+ "a status message will be sent to the cluster log.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 10, /* default */
+ 0, /* min */
+ 100, /* max */
+ 0 /* block */
+);
+
+
+static MYSQL_SYSVAR_STR(
+ connectstring, /* name */
+ opt_ndb_connectstring, /* var */
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Connect string for ndbcluster.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ NULL /* default */
+);
+
+
+static MYSQL_SYSVAR_STR(
+ mgmd_host, /* name */
+ opt_ndb_mgmd_host, /* var */
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Set host and port for ndb_mgmd. Syntax: hostname[:port]",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ NULL /* default */
+);
+
+
+static MYSQL_SYSVAR_UINT(
+ nodeid, /* name */
+ opt_ndb_nodeid, /* var */
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Nodeid for this mysqld in the cluster.",
+ NULL, /* check func. */
+ NULL, /* update func. */
+ 0, /* default */
+ 0, /* min */
+ 255, /* max */
+ 0 /* block */
+);
+
+static struct st_mysql_sys_var* system_variables[]= {
+ MYSQL_SYSVAR(cache_check_time),
+ MYSQL_SYSVAR(extra_logging),
+ MYSQL_SYSVAR(report_thresh_binlog_mem_usage),
+ MYSQL_SYSVAR(report_thresh_binlog_epoch_slip),
+ MYSQL_SYSVAR(distribution),
+ MYSQL_SYSVAR(autoincrement_prefetch_sz),
+ MYSQL_SYSVAR(force_send),
+ MYSQL_SYSVAR(use_exact_count),
+ MYSQL_SYSVAR(use_transactions),
+ MYSQL_SYSVAR(use_copying_alter_table),
+ MYSQL_SYSVAR(optimized_node_selection),
+ MYSQL_SYSVAR(index_stat_enable),
+ MYSQL_SYSVAR(index_stat_cache_entries),
+ MYSQL_SYSVAR(index_stat_update_freq),
+ MYSQL_SYSVAR(connectstring),
+ MYSQL_SYSVAR(mgmd_host),
+ MYSQL_SYSVAR(nodeid),
+
+ NULL
+};
+
+
+struct st_mysql_storage_engine ndbcluster_storage_engine=
+{ MYSQL_HANDLERTON_INTERFACE_VERSION };
+
+mysql_declare_plugin(ndbcluster)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &ndbcluster_storage_engine,
+ ndbcluster_hton_name,
+ "MySQL AB",
+ "Clustered, fault-tolerant tables",
+ PLUGIN_LICENSE_GPL,
+ ndbcluster_init, /* Plugin Init */
+ NULL, /* Plugin Deinit */
+ 0x0100 /* 1.0 */,
+ ndb_status_variables_export,/* status variables */
+ system_variables, /* system variables */
+ NULL, /* config options */
+ 0, /* flags */
+}
+mysql_declare_plugin_end;
+maria_declare_plugin(ndbcluster)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &ndbcluster_storage_engine,
+ ndbcluster_hton_name,
+ "MySQL AB",
+ "Clustered, fault-tolerant tables",
+ PLUGIN_LICENSE_GPL,
+ ndbcluster_init, /* Plugin Init */
+ NULL, /* Plugin Deinit */
+ 0x0100 /* 1.0 */,
+ ndb_status_variables_export,/* status variables */
+ NULL, /* system variables */
+ "1.0", /* string version */
+ MariaDB_PLUGIN_MATURITY_GAMMA /* maturity */
+}
+maria_declare_plugin_end;
+
+#else
+int Sun_ar_require_a_symbol_here= 0;
+#endif
diff --git a/sql/ha_ndbcluster.h b/sql/ha_ndbcluster.h
new file mode 100644
index 00000000000..70e1e9dc7cf
--- /dev/null
+++ b/sql/ha_ndbcluster.h
@@ -0,0 +1,599 @@
+#ifndef HA_NDBCLUSTER_INCLUDED
+#define HA_NDBCLUSTER_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/*
+ This file defines the NDB Cluster handler: the interface between MySQL and
+ NDB Cluster
+*/
+
+/* The class defining a handle to an NDB Cluster table */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+/* Blob tables and events are internal to NDB and must never be accessed */
+#define IS_NDB_BLOB_PREFIX(A) is_prefix(A, "NDB$BLOB")
+
+#include <NdbApi.hpp>
+#include <ndbapi_limits.h>
+
+#define NDB_HIDDEN_PRIMARY_KEY_LENGTH 8
+
+#ifdef HAVE_PSI_INTERFACE
+extern PSI_file_key key_file_ndb;
+#endif /* HAVE_PSI_INTERFACE */
+
+
+class Ndb; // Forward declaration
+class NdbOperation; // Forward declaration
+class NdbTransaction; // Forward declaration
+class NdbRecAttr; // Forward declaration
+class NdbScanOperation;
+class NdbIndexScanOperation;
+class NdbBlob;
+class NdbIndexStat;
+class NdbEventOperation;
+class ha_ndbcluster_cond;
+
+#include "sql_partition.h" /* part_id_range */
+
+// connectstring to cluster if given by mysqld
+extern const char *ndbcluster_connectstring;
+
+typedef enum ndb_index_type {
+ UNDEFINED_INDEX = 0,
+ PRIMARY_KEY_INDEX = 1,
+ PRIMARY_KEY_ORDERED_INDEX = 2,
+ UNIQUE_INDEX = 3,
+ UNIQUE_ORDERED_INDEX = 4,
+ ORDERED_INDEX = 5
+} NDB_INDEX_TYPE;
+
+typedef enum ndb_index_status {
+ UNDEFINED = 0,
+ ACTIVE = 1,
+ TO_BE_DROPPED = 2
+} NDB_INDEX_STATUS;
+
+typedef struct ndb_index_data {
+ NDB_INDEX_TYPE type;
+ NDB_INDEX_STATUS status;
+ const NdbDictionary::Index *index;
+ const NdbDictionary::Index *unique_index;
+ unsigned char *unique_index_attrid_map;
+ bool null_in_unique_index;
+ // In this version stats are not shared between threads
+ NdbIndexStat* index_stat;
+ uint index_stat_cache_entries;
+ // Simple counter mechanism to decide when to connect to db
+ uint index_stat_update_freq;
+ uint index_stat_query_count;
+} NDB_INDEX_DATA;
+
+typedef enum ndb_write_op {
+ NDB_INSERT = 0,
+ NDB_UPDATE = 1,
+ NDB_PK_UPDATE = 2
+} NDB_WRITE_OP;
+
+typedef union { const NdbRecAttr *rec; NdbBlob *blob; void *ptr; } NdbValue;
+
+int get_ndb_blobs_value(TABLE* table, NdbValue* value_array,
+ uchar*& buffer, uint& buffer_size,
+ my_ptrdiff_t ptrdiff);
+
+typedef enum {
+ NSS_INITIAL= 0,
+ NSS_DROPPED,
+ NSS_ALTERED
+} NDB_SHARE_STATE;
+
+typedef struct st_ndbcluster_share {
+ NDB_SHARE_STATE state;
+ MEM_ROOT mem_root;
+ THR_LOCK lock;
+ mysql_mutex_t mutex;
+ char *key;
+ uint key_length;
+ THD *util_lock;
+ uint use_count;
+ uint commit_count_lock;
+ ulonglong commit_count;
+ char *db;
+ char *table_name;
+ Ndb::TupleIdRange tuple_id_range;
+#ifdef HAVE_NDB_BINLOG
+ uint32 connect_count;
+ uint32 flags;
+ NdbEventOperation *op;
+ NdbEventOperation *op_old; // for rename table
+ char *old_names; // for rename table
+ TABLE_SHARE *table_share;
+ TABLE *table;
+ uchar *record[2]; // pointer to allocated records for receiving data
+ NdbValue *ndb_value[2];
+ MY_BITMAP *subscriber_bitmap;
+#endif
+} NDB_SHARE;
+
+inline
+NDB_SHARE_STATE
+get_ndb_share_state(NDB_SHARE *share)
+{
+ NDB_SHARE_STATE state;
+ mysql_mutex_lock(&share->mutex);
+ state= share->state;
+ mysql_mutex_unlock(&share->mutex);
+ return state;
+}
+
+inline
+void
+set_ndb_share_state(NDB_SHARE *share, NDB_SHARE_STATE state)
+{
+ mysql_mutex_lock(&share->mutex);
+ share->state= state;
+ mysql_mutex_unlock(&share->mutex);
+}
+
+struct Ndb_tuple_id_range_guard {
+ Ndb_tuple_id_range_guard(NDB_SHARE* _share) :
+ share(_share),
+ range(share->tuple_id_range) {
+ mysql_mutex_lock(&share->mutex);
+ }
+ ~Ndb_tuple_id_range_guard() {
+ mysql_mutex_unlock(&share->mutex);
+ }
+ NDB_SHARE* share;
+ Ndb::TupleIdRange& range;
+};
+
+#ifdef HAVE_NDB_BINLOG
+/* NDB_SHARE.flags */
+#define NSF_HIDDEN_PK 1 /* table has hidden primary key */
+#define NSF_BLOB_FLAG 2 /* table has blob attributes */
+#define NSF_NO_BINLOG 4 /* table should not be binlogged */
+#endif
+
+typedef enum ndb_query_state_bits {
+ NDB_QUERY_NORMAL = 0,
+ NDB_QUERY_MULTI_READ_RANGE = 1
+} NDB_QUERY_STATE_BITS;
+
+/*
+ Place holder for ha_ndbcluster thread specific data
+*/
+
+enum THD_NDB_OPTIONS
+{
+ TNO_NO_LOG_SCHEMA_OP= 1 << 0
+};
+
+enum THD_NDB_TRANS_OPTIONS
+{
+ TNTO_INJECTED_APPLY_STATUS= 1 << 0
+ ,TNTO_NO_LOGGING= 1 << 1
+};
+
+struct Ndb_local_table_statistics {
+ int no_uncommitted_rows_count;
+ ulong last_count;
+ ha_rows records;
+};
+
+class Thd_ndb
+{
+ public:
+ Thd_ndb();
+ ~Thd_ndb();
+
+ void init_open_tables();
+
+ Ndb *ndb;
+ ulong count;
+ uint lock_count;
+ uint start_stmt_count;
+ NdbTransaction *trans;
+ bool m_error;
+ bool m_slow_path;
+ int m_error_code;
+ uint32 m_query_id; /* query id whn m_error_code was set */
+ uint32 options;
+ uint32 trans_options;
+ List<NDB_SHARE> changed_tables;
+ uint query_state;
+ HASH open_tables;
+};
+
+class ha_ndbcluster: public handler
+{
+ public:
+ ha_ndbcluster(handlerton *hton, TABLE_SHARE *table);
+ ~ha_ndbcluster();
+
+ int ha_initialise();
+ int open(const char *name, int mode, uint test_if_locked);
+ int close(void);
+
+ int write_row(uchar *buf);
+ int update_row(const uchar *old_data, uchar *new_data);
+ int delete_row(const uchar *buf);
+ int index_init(uint index, bool sorted);
+ int index_end();
+ int index_read(uchar *buf, const uchar *key, uint key_len,
+ enum ha_rkey_function find_flag);
+ int index_next(uchar *buf);
+ int index_prev(uchar *buf);
+ int index_first(uchar *buf);
+ int index_last(uchar *buf);
+ int index_read_last(uchar * buf, const uchar * key, uint key_len);
+ int rnd_init(bool scan);
+ int rnd_end();
+ int rnd_next(uchar *buf);
+ int rnd_pos(uchar *buf, uchar *pos);
+ void position(const uchar *record);
+ int read_range_first(const key_range *start_key,
+ const key_range *end_key,
+ bool eq_range, bool sorted);
+ int read_range_first_to_buf(const key_range *start_key,
+ const key_range *end_key,
+ bool eq_range, bool sorted,
+ uchar* buf);
+ int read_range_next();
+ int alter_tablespace(st_alter_tablespace *info);
+
+ /**
+ * Multi range stuff
+ */
+#if 0
+ /*
+ MRR/NDB is disabled in MariaDB. This is because in MariaDB, we've
+ backported
+ - the latest version of MRR interface (BKA needs this)
+ - the latest version of DS-MRR implementation
+ but didn't backport the latest version MRR/NDB implementation.
+
+ */
+ int read_multi_range_first(KEY_MULTI_RANGE **found_range_p,
+ KEY_MULTI_RANGE*ranges, uint range_count,
+ bool sorted, HANDLER_BUFFER *buffer);
+ int read_multi_range_next(KEY_MULTI_RANGE **found_range_p);
+#endif
+ bool null_value_index_search(KEY_MULTI_RANGE *ranges,
+ KEY_MULTI_RANGE *end_range,
+ HANDLER_BUFFER *buffer);
+
+ bool get_error_message(int error, String *buf);
+ ha_rows records();
+ ha_rows estimate_rows_upper_bound()
+ { return HA_POS_ERROR; }
+ int info(uint);
+ void get_dynamic_partition_info(PARTITION_STATS *stat_info, uint part_id);
+ int extra(enum ha_extra_function operation);
+ int extra_opt(enum ha_extra_function operation, ulong cache_size);
+ int reset();
+ int external_lock(THD *thd, int lock_type);
+ void unlock_row();
+ int start_stmt(THD *thd, thr_lock_type lock_type);
+ void print_error(int error, myf errflag);
+ const char * table_type() const;
+ const char ** bas_ext() const;
+ ulonglong table_flags(void) const;
+ void prepare_for_alter();
+ int add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys);
+ int prepare_drop_index(TABLE *table_arg, uint *key_num, uint num_of_keys);
+ int final_drop_index(TABLE *table_arg);
+ void set_part_info(partition_info *part_info);
+ ulong index_flags(uint idx, uint part, bool all_parts) const;
+ uint max_supported_record_length() const;
+ uint max_supported_keys() const;
+ uint max_supported_key_parts() const;
+ uint max_supported_key_length() const;
+ uint max_supported_key_part_length() const;
+
+ int rename_table(const char *from, const char *to);
+ int delete_table(const char *name);
+ int create(const char *name, TABLE *form, HA_CREATE_INFO *info);
+ int create_handler_files(const char *file, const char *old_name,
+ int action_flag, HA_CREATE_INFO *info);
+ int get_default_no_partitions(HA_CREATE_INFO *info);
+ bool get_no_parts(const char *name, uint *no_parts);
+ void set_auto_partitions(partition_info *part_info);
+ virtual bool is_fatal_error(int error, uint flags)
+ {
+ if (!handler::is_fatal_error(error, flags) ||
+ error == HA_ERR_NO_PARTITION_FOUND)
+ return FALSE;
+ return TRUE;
+ }
+
+ THR_LOCK_DATA **store_lock(THD *thd,
+ THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type);
+
+ bool low_byte_first() const;
+
+ const char* index_type(uint key_number);
+
+ double scan_time();
+ ha_rows records_in_range(uint inx, key_range *min_key, key_range *max_key);
+ void start_bulk_insert(ha_rows rows);
+ int end_bulk_insert();
+
+ static Thd_ndb* seize_thd_ndb();
+ static void release_thd_ndb(Thd_ndb* thd_ndb);
+
+static void set_dbname(const char *pathname, char *dbname);
+static void set_tabname(const char *pathname, char *tabname);
+
+ /*
+ Condition pushdown
+ */
+
+ /*
+ Push condition down to the table handler.
+ SYNOPSIS
+ cond_push()
+ cond Condition to be pushed. The condition tree must not be
+ modified by the by the caller.
+ RETURN
+ The 'remainder' condition that caller must use to filter out records.
+ NULL means the handler will not return rows that do not match the
+ passed condition.
+ NOTES
+ The pushed conditions form a stack (from which one can remove the
+ last pushed condition using cond_pop).
+ The table handler filters out rows using (pushed_cond1 AND pushed_cond2
+ AND ... AND pushed_condN)
+ or less restrictive condition, depending on handler's capabilities.
+
+ handler->reset() call empties the condition stack.
+ Calls to rnd_init/rnd_end, index_init/index_end etc do not affect the
+ condition stack.
+ The current implementation supports arbitrary AND/OR nested conditions
+ with comparisons between columns and constants (including constant
+ expressions and function calls) and the following comparison operators:
+ =, !=, >, >=, <, <=, like, "not like", "is null", and "is not null".
+ Negated conditions are supported by NOT which generate NAND/NOR groups.
+ */
+ const COND *cond_push(const COND *cond);
+ /*
+ Pop the top condition from the condition stack of the handler instance.
+ SYNOPSIS
+ cond_pop()
+ Pops the top if condition stack, if stack is not empty
+ */
+ void cond_pop();
+
+ uint8 table_cache_type();
+
+ /*
+ * Internal to ha_ndbcluster, used by C functions
+ */
+ int ndb_err(NdbTransaction*);
+
+ my_bool register_query_cache_table(THD *thd, char *table_key,
+ uint key_length,
+ qc_engine_callback *engine_callback,
+ ulonglong *engine_data);
+
+ bool check_if_incompatible_data(HA_CREATE_INFO *info,
+ uint table_changes);
+
+private:
+ int loc_read_multi_range_next(KEY_MULTI_RANGE **found_range_p);
+ friend int ndbcluster_drop_database_impl(const char *path);
+ friend int ndb_handle_schema_change(THD *thd,
+ Ndb *ndb, NdbEventOperation *pOp,
+ NDB_SHARE *share);
+
+ static int delete_table(ha_ndbcluster *h, Ndb *ndb,
+ const char *path,
+ const char *db,
+ const char *table_name);
+ int create_ndb_index(const char *name, KEY *key_info, bool unique);
+ int create_ordered_index(const char *name, KEY *key_info);
+ int create_unique_index(const char *name, KEY *key_info);
+ int create_index(const char *name, KEY *key_info,
+ NDB_INDEX_TYPE idx_type, uint idx_no);
+// Index list management
+ int create_indexes(Ndb *ndb, TABLE *tab);
+ int open_indexes(Ndb *ndb, TABLE *tab, bool ignore_error);
+ void renumber_indexes(Ndb *ndb, TABLE *tab);
+ int drop_indexes(Ndb *ndb, TABLE *tab);
+ int add_index_handle(THD *thd, NdbDictionary::Dictionary *dict,
+ KEY *key_info, const char *index_name, uint index_no);
+ int get_metadata(const char* path);
+ void release_metadata(THD *thd, Ndb *ndb);
+ NDB_INDEX_TYPE get_index_type(uint idx_no) const;
+ NDB_INDEX_TYPE get_index_type_from_table(uint index_no) const;
+ NDB_INDEX_TYPE get_index_type_from_key(uint index_no, KEY *key_info,
+ bool primary) const;
+ bool has_null_in_unique_index(uint idx_no) const;
+ bool check_index_fields_not_null(KEY *key_info);
+
+ uint set_up_partition_info(partition_info *part_info,
+ TABLE *table,
+ void *tab);
+ char* get_tablespace_name(THD *thd, char *name, uint name_len);
+ int set_range_data(void *tab, partition_info* part_info);
+ int set_list_data(void *tab, partition_info* part_info);
+ int complemented_read(const uchar *old_data, uchar *new_data,
+ uint32 old_part_id);
+ int pk_read(const uchar *key, uint key_len, uchar *buf, uint32 part_id);
+ int ordered_index_scan(const key_range *start_key,
+ const key_range *end_key,
+ bool sorted, bool descending, uchar* buf,
+ part_id_range *part_spec);
+ int unique_index_read(const uchar *key, uint key_len,
+ uchar *buf);
+ int unique_index_scan(const KEY* key_info,
+ const uchar *key,
+ uint key_len,
+ uchar *buf);
+ int full_table_scan(uchar * buf);
+
+ bool check_all_operations_for_error(NdbTransaction *trans,
+ const NdbOperation *first,
+ const NdbOperation *last,
+ uint errcode);
+ int peek_indexed_rows(const uchar *record, NDB_WRITE_OP write_op);
+ int fetch_next(NdbScanOperation* op);
+ int set_auto_inc(Field *field);
+ int next_result(uchar *buf);
+ int define_read_attrs(uchar* buf, NdbOperation* op);
+ int filtered_scan(const uchar *key, uint key_len,
+ uchar *buf,
+ enum ha_rkey_function find_flag);
+ int close_scan();
+ void unpack_record(uchar *buf);
+ int get_ndb_lock_type(enum thr_lock_type type);
+
+ void set_dbname(const char *pathname);
+ void set_tabname(const char *pathname);
+
+ bool set_hidden_key(NdbOperation*,
+ uint fieldnr, const uchar* field_ptr);
+ int set_ndb_key(NdbOperation*, Field *field,
+ uint fieldnr, const uchar* field_ptr);
+ int set_ndb_value(NdbOperation*, Field *field, uint fieldnr,
+ int row_offset= 0, bool *set_blob_value= 0);
+ int get_ndb_value(NdbOperation*, Field *field, uint fieldnr, uchar*);
+ int get_ndb_partition_id(NdbOperation *);
+ friend int g_get_ndb_blobs_value(NdbBlob *ndb_blob, void *arg);
+ int set_primary_key(NdbOperation *op, const uchar *key);
+ int set_primary_key_from_record(NdbOperation *op, const uchar *record);
+ bool check_index_fields_in_write_set(uint keyno);
+ int set_index_key_from_record(NdbOperation *op, const uchar *record,
+ uint keyno);
+ int set_bounds(NdbIndexScanOperation*, uint inx, bool rir,
+ const key_range *keys[2], uint= 0);
+ int key_cmp(uint keynr, const uchar * old_row, const uchar * new_row);
+ int set_index_key(NdbOperation *, const KEY *key_info, const uchar *key_ptr);
+ void print_results();
+
+ virtual void get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *first_value,
+ ulonglong *nb_reserved_values);
+ bool uses_blob_value();
+
+ char *update_table_comment(const char * comment);
+
+ int write_ndb_file(const char *name);
+
+ int check_ndb_connection(THD* thd= current_thd);
+
+ void set_rec_per_key();
+ int records_update();
+ void no_uncommitted_rows_execute_failure();
+ void no_uncommitted_rows_update(int);
+ void no_uncommitted_rows_reset(THD *);
+
+ void release_completed_operations(NdbTransaction*, bool);
+
+ friend int execute_commit(ha_ndbcluster*, NdbTransaction*);
+ friend int execute_no_commit_ignore_no_key(ha_ndbcluster*, NdbTransaction*);
+ friend int execute_no_commit(ha_ndbcluster*, NdbTransaction*, bool);
+ friend int execute_no_commit_ie(ha_ndbcluster*, NdbTransaction*, bool);
+
+ void transaction_checks(THD *thd);
+ int start_statement(THD *thd, Thd_ndb *thd_ndb, Ndb* ndb);
+ int init_handler_for_statement(THD *thd, Thd_ndb *thd_ndb);
+
+ NdbTransaction *m_active_trans;
+ NdbScanOperation *m_active_cursor;
+ const NdbDictionary::Table *m_table;
+ struct Ndb_local_table_statistics *m_table_info;
+ struct Ndb_local_table_statistics m_table_info_instance;
+ char m_dbname[FN_HEADLEN];
+ //char m_schemaname[FN_HEADLEN];
+ char m_tabname[FN_HEADLEN];
+ ulonglong m_table_flags;
+ THR_LOCK_DATA m_lock;
+ bool m_lock_tuple;
+ NDB_SHARE *m_share;
+ NDB_INDEX_DATA m_index[MAX_KEY];
+ // NdbRecAttr has no reference to blob
+ NdbValue m_value[NDB_MAX_ATTRIBUTES_IN_TABLE];
+ uchar m_ref[NDB_HIDDEN_PRIMARY_KEY_LENGTH];
+ partition_info *m_part_info;
+ uint32 m_part_id;
+ uchar *m_rec0;
+ Field **m_part_field_array;
+ bool m_use_partition_function;
+ bool m_sorted;
+ bool m_use_write;
+ bool m_ignore_dup_key;
+ bool m_has_unique_index;
+ bool m_primary_key_update;
+ bool m_write_op;
+ bool m_ignore_no_key;
+ ha_rows m_rows_to_insert; // TODO: merge it with handler::estimation_rows_to_insert?
+ ha_rows m_rows_inserted;
+ ha_rows m_bulk_insert_rows;
+ ha_rows m_rows_changed;
+ bool m_bulk_insert_not_flushed;
+ bool m_delete_cannot_batch;
+ bool m_update_cannot_batch;
+ ha_rows m_ops_pending;
+ bool m_skip_auto_increment;
+ bool m_blobs_pending;
+ bool m_slow_path;
+ my_ptrdiff_t m_blobs_offset;
+ // memory for blobs in one tuple
+ uchar *m_blobs_buffer;
+ uint32 m_blobs_buffer_size;
+ uint m_dupkey;
+ // set from thread variables at external lock
+ bool m_ha_not_exact_count;
+ bool m_force_send;
+ ha_rows m_autoincrement_prefetch;
+ bool m_transaction_on;
+
+ ha_ndbcluster_cond *m_cond;
+ bool m_disable_multi_read;
+ uchar *m_multi_range_result_ptr;
+ KEY_MULTI_RANGE *m_multi_ranges;
+ KEY_MULTI_RANGE *m_multi_range_defined;
+ const NdbOperation *m_current_multi_operation;
+ NdbIndexScanOperation *m_multi_cursor;
+ uchar *m_multi_range_cursor_result_ptr;
+ int setup_recattr(const NdbRecAttr*);
+ Ndb *get_ndb();
+};
+
+extern SHOW_VAR ndb_status_variables[];
+
+int ndbcluster_discover(THD* thd, const char* dbname, const char* name,
+ const void** frmblob, uint* frmlen);
+int ndbcluster_find_files(THD *thd,const char *db,const char *path,
+ const char *wild, bool dir, List<LEX_STRING> *files);
+int ndbcluster_table_exists_in_engine(THD* thd,
+ const char *db, const char *name);
+void ndbcluster_print_error(int error, const NdbOperation *error_op);
+
+static const char ndbcluster_hton_name[]= "ndbcluster";
+static const int ndbcluster_hton_name_length=sizeof(ndbcluster_hton_name)-1;
+extern int ndbcluster_terminating;
+extern int ndb_util_thread_running;
+extern mysql_cond_t COND_ndb_util_ready;
+
+#endif /* HA_NDBCLUSTER_INCLUDED */
diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
new file mode 100644
index 00000000000..73513ac9f40
--- /dev/null
+++ b/sql/ha_ndbcluster_binlog.cc
@@ -0,0 +1,4426 @@
+/* Copyright (c) 2006, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2012, 2013, Monty Proram Ab.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_show.h"
+#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE
+#include "ha_ndbcluster.h"
+
+#ifdef HAVE_NDB_BINLOG
+#include "rpl_injector.h"
+#include "rpl_filter.h"
+#include "slave.h"
+#include "ha_ndbcluster_binlog.h"
+#include "NdbDictionary.hpp"
+#include "ndb_cluster_connection.hpp"
+#include <util/NdbAutoPtr.hpp>
+
+#include "sql_base.h" // close_thread_tables
+#include "sql_table.h" // build_table_filename
+#include "table.h" // open_table_from_share
+#include "discover.h" // readfrm, writefrm
+#include "lock.h" // MYSQL_LOCK_IGNORE_FLUSH,
+ // mysql_unlock_tables
+#include "sql_parse.h" // mysql_parse
+#include "transaction.h"
+
+#ifdef ndb_dynamite
+#undef assert
+#define assert(x) do { if(x) break; ::printf("%s %d: assert failed: %s\n", __FILE__, __LINE__, #x); ::fflush(stdout); ::signal(SIGABRT,SIG_DFL); ::abort(); ::kill(::getpid(),6); ::kill(::getpid(),9); } while (0)
+#endif
+
+extern my_bool opt_ndb_log_binlog_index;
+extern ulong opt_ndb_extra_logging;
+/*
+ defines for cluster replication table names
+*/
+#include "ha_ndbcluster_tables.h"
+#define NDB_APPLY_TABLE_FILE "./" NDB_REP_DB "/" NDB_APPLY_TABLE
+#define NDB_SCHEMA_TABLE_FILE "./" NDB_REP_DB "/" NDB_SCHEMA_TABLE
+
+/*
+ Timeout for syncing schema events between
+ mysql servers, and between mysql server and the binlog
+*/
+static const int DEFAULT_SYNC_TIMEOUT= 120;
+
+
+/*
+ Flag showing if the ndb injector thread is running, if so == 1
+ -1 if it was started but later stopped for some reason
+ 0 if never started
+*/
+static int ndb_binlog_thread_running= 0;
+
+/*
+ Flag showing if the ndb binlog should be created, if so == TRUE
+ FALSE if not
+*/
+my_bool ndb_binlog_running= FALSE;
+my_bool ndb_binlog_tables_inited= FALSE;
+
+/*
+ Global reference to the ndb injector thread THD oject
+
+ Has one sole purpose, for setting the in_use table member variable
+ in get_share(...)
+*/
+THD *injector_thd= 0;
+
+/*
+ Global reference to ndb injector thd object.
+
+ Used mainly by the binlog index thread, but exposed to the client sql
+ thread for one reason; to setup the events operations for a table
+ to enable ndb injector thread receiving events.
+
+ Must therefore always be used with a surrounding
+ mysql_mutex_lock(&injector_mutex), when doing create/dropEventOperation
+*/
+static Ndb *injector_ndb= 0;
+static Ndb *schema_ndb= 0;
+
+static int ndbcluster_binlog_inited= 0;
+/*
+ Flag "ndbcluster_binlog_terminating" set when shutting down mysqld.
+ Server main loop should call handlerton function:
+
+ ndbcluster_hton->binlog_func ==
+ ndbcluster_binlog_func(...,BFN_BINLOG_END,...) ==
+ ndbcluster_binlog_end
+
+ at shutdown, which sets the flag. And then server needs to wait for it
+ to complete. Otherwise binlog will not be complete.
+
+ ndbcluster_hton->panic == ndbcluster_end() will not return until
+ ndb binlog is completed
+*/
+static int ndbcluster_binlog_terminating= 0;
+
+/*
+ Mutex and condition used for interacting between client sql thread
+ and injector thread
+*/
+pthread_t ndb_binlog_thread;
+mysql_mutex_t injector_mutex;
+mysql_cond_t injector_cond;
+
+/* NDB Injector thread (used for binlog creation) */
+static ulonglong ndb_latest_applied_binlog_epoch= 0;
+static ulonglong ndb_latest_handled_binlog_epoch= 0;
+static ulonglong ndb_latest_received_binlog_epoch= 0;
+
+NDB_SHARE *ndb_apply_status_share= 0;
+NDB_SHARE *ndb_schema_share= 0;
+mysql_mutex_t ndb_schema_share_mutex;
+
+extern my_bool opt_log_slave_updates;
+static my_bool g_ndb_log_slave_updates;
+
+/* Schema object distribution handling */
+HASH ndb_schema_objects;
+typedef struct st_ndb_schema_object {
+ mysql_mutex_t mutex;
+ char *key;
+ uint key_length;
+ uint use_count;
+ MY_BITMAP slock_bitmap;
+ uint32 slock[256/32]; // 256 bits for lock status of table
+} NDB_SCHEMA_OBJECT;
+static NDB_SCHEMA_OBJECT *ndb_get_schema_object(const char *key,
+ my_bool create_if_not_exists,
+ my_bool have_lock);
+static void ndb_free_schema_object(NDB_SCHEMA_OBJECT **ndb_schema_object,
+ bool have_lock);
+
+static Uint64 *p_latest_trans_gci= 0;
+
+/*
+ Global variables for holding the ndb_binlog_index table reference
+*/
+static TABLE *ndb_binlog_index= 0;
+static TABLE_LIST binlog_tables;
+
+/*
+ Helper functions
+*/
+
+#ifndef DBUG_OFF
+/* purecov: begin deadcode */
+static void print_records(TABLE *table, const uchar *record)
+{
+ for (uint j= 0; j < table->s->fields; j++)
+ {
+ char buf[40];
+ int pos= 0;
+ Field *field= table->field[j];
+ const uchar* field_ptr= field->ptr - table->record[0] + record;
+ int pack_len= field->pack_length();
+ int n= pack_len < 10 ? pack_len : 10;
+
+ for (int i= 0; i < n && pos < 20; i++)
+ {
+ pos+= sprintf(&buf[pos]," %x", (int) (uchar) field_ptr[i]);
+ }
+ buf[pos]= 0;
+ DBUG_PRINT("info",("[%u]field_ptr[0->%d]: %s", j, n, buf));
+ }
+}
+/* purecov: end */
+#else
+#define print_records(a,b)
+#endif
+
+
+#ifndef DBUG_OFF
+static void dbug_print_table(const char *info, TABLE *table)
+{
+ if (table == 0)
+ {
+ DBUG_PRINT("info",("%s: (null)", info));
+ return;
+ }
+ DBUG_PRINT("info",
+ ("%s: %s.%s s->fields: %d "
+ "reclength: %lu rec_buff_length: %u record[0]: 0x%lx "
+ "record[1]: 0x%lx",
+ info,
+ table->s->db.str,
+ table->s->table_name.str,
+ table->s->fields,
+ table->s->reclength,
+ table->s->rec_buff_length,
+ (long) table->record[0],
+ (long) table->record[1]));
+
+ for (unsigned int i= 0; i < table->s->fields; i++)
+ {
+ Field *f= table->field[i];
+ DBUG_PRINT("info",
+ ("[%d] \"%s\"(0x%lx:%s%s%s%s%s%s) type: %d pack_length: %d "
+ "ptr: 0x%lx[+%d] null_bit: %u null_ptr: 0x%lx[+%d]",
+ i,
+ f->field_name,
+ (long) f->flags,
+ (f->flags & PRI_KEY_FLAG) ? "pri" : "attr",
+ (f->flags & NOT_NULL_FLAG) ? "" : ",nullable",
+ (f->flags & UNSIGNED_FLAG) ? ",unsigned" : ",signed",
+ (f->flags & ZEROFILL_FLAG) ? ",zerofill" : "",
+ (f->flags & BLOB_FLAG) ? ",blob" : "",
+ (f->flags & BINARY_FLAG) ? ",binary" : "",
+ f->real_type(),
+ f->pack_length(),
+ (long) f->ptr, (int) (f->ptr - table->record[0]),
+ f->null_bit,
+ (long) f->null_ptr,
+ (int) ((uchar*) f->null_ptr - table->record[0])));
+ if (f->type() == MYSQL_TYPE_BIT)
+ {
+ Field_bit *g= (Field_bit*) f;
+ DBUG_PRINT("MYSQL_TYPE_BIT",("field_length: %d bit_ptr: 0x%lx[+%d] "
+ "bit_ofs: %d bit_len: %u",
+ g->field_length, (long) g->bit_ptr,
+ (int) ((uchar*) g->bit_ptr -
+ table->record[0]),
+ g->bit_ofs, g->bit_len));
+ }
+ }
+}
+#else
+#define dbug_print_table(a,b)
+#endif
+
+
+/*
+ Run a query through mysql_parse
+
+ Used to:
+ - purging the ndb_binlog_index
+ - creating the ndb_apply_status table
+*/
+static void run_query(THD *thd, char *buf, char *end,
+ const int *no_print_error, my_bool disable_binlog)
+{
+ ulong save_thd_query_length= thd->query_length();
+ char *save_thd_query= thd->query();
+ ulong save_thread_id= thd->variables.pseudo_thread_id;
+ struct system_status_var save_thd_status_var= thd->status_var;
+ THD_TRANS save_thd_transaction_all= thd->transaction.all;
+ THD_TRANS save_thd_transaction_stmt= thd->transaction.stmt;
+ ulonglong save_thd_options= thd->variables.option_bits;
+ DBUG_ASSERT(sizeof(save_thd_options) == sizeof(thd->variables.option_bits));
+ NET save_thd_net= thd->net;
+
+ bzero((char*) &thd->net, sizeof(NET));
+ thd->set_query(buf, (uint) (end - buf));
+ thd->variables.pseudo_thread_id= thread_id;
+ thd->transaction.stmt.modified_non_trans_table= FALSE;
+ if (disable_binlog)
+ thd->variables.option_bits&= ~OPTION_BIN_LOG;
+
+ DBUG_PRINT("query", ("%s", thd->query()));
+
+ DBUG_ASSERT(!thd->in_sub_stmt);
+ DBUG_ASSERT(!thd->locked_tables_mode);
+
+ {
+ Parser_state parser_state;
+ if (!parser_state.init(thd, thd->query(), thd->query_length()))
+ mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);
+ }
+
+ if (no_print_error && thd->is_slave_error)
+ {
+ int i;
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ for (i= 0; no_print_error[i]; i++)
+ if ((thd_ndb->m_error_code == no_print_error[i]) ||
+ (thd->get_stmt_da()->sql_errno() == (unsigned) no_print_error[i]))
+ break;
+ if (!no_print_error[i])
+ sql_print_error("NDB: %s: error %s %d(ndb: %d) %d %d",
+ buf,
+ thd->get_stmt_da()->message(),
+ thd->get_stmt_da()->sql_errno(),
+ thd_ndb->m_error_code,
+ (int) thd->is_error(), thd->is_slave_error);
+ }
+ /*
+ XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command()
+ can not be called from within a statement, and
+ run_query() can be called from anywhere, including from within
+ a sub-statement.
+ This particular reset is a temporary hack to avoid an assert
+ for double assignment of the diagnostics area when run_query()
+ is called from ndbcluster_reset_logs(), which is called from
+ mysql_flush().
+ */
+ thd->get_stmt_da()->reset_diagnostics_area();
+
+ thd->variables.option_bits= save_thd_options;
+ thd->set_query(save_thd_query, save_thd_query_length);
+ thd->variables.pseudo_thread_id= save_thread_id;
+ thd->status_var= save_thd_status_var;
+ thd->transaction.all= save_thd_transaction_all;
+ thd->transaction.stmt= save_thd_transaction_stmt;
+ thd->net= save_thd_net;
+ thd->set_current_stmt_binlog_format_row();
+
+ if (thd == injector_thd)
+ {
+ /*
+ running the query will close all tables, including the ndb_binlog_index
+ used in injector_thd
+ */
+ ndb_binlog_index= 0;
+ }
+}
+
+static void
+ndbcluster_binlog_close_table(THD *thd, NDB_SHARE *share)
+{
+ DBUG_ENTER("ndbcluster_binlog_close_table");
+ if (share->table_share)
+ {
+ closefrm(share->table, 1);
+ share->table_share= 0;
+ share->table= 0;
+ }
+ DBUG_ASSERT(share->table == 0);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Creates a TABLE object for the ndb cluster table
+
+ NOTES
+ This does not open the underlying table
+*/
+
+static int
+ndbcluster_binlog_open_table(THD *thd, NDB_SHARE *share,
+ TABLE_SHARE *table_share, TABLE *table,
+ int reopen)
+{
+ int error;
+ DBUG_ENTER("ndbcluster_binlog_open_table");
+
+ init_tmp_table_share(thd, table_share, share->db, 0, share->table_name,
+ share->key);
+ if ((error= open_table_def(thd, table_share, 0)))
+ {
+ DBUG_PRINT("error", ("open_table_def failed: %d my_errno: %d", error, my_errno));
+ free_table_share(table_share);
+ DBUG_RETURN(error);
+ }
+ if ((error= open_table_from_share(thd, table_share, "", 0 /* fon't allocate buffers */,
+ (uint) READ_ALL, 0, table, FALSE)))
+ {
+ DBUG_PRINT("error", ("open_table_from_share failed %d my_errno: %d", error, my_errno));
+ free_table_share(table_share);
+ DBUG_RETURN(error);
+ }
+ tdc_assign_new_table_id(table_share);
+
+ if (!reopen)
+ {
+ // allocate memory on ndb share so it can be reused after online alter table
+ (void)multi_alloc_root(&share->mem_root,
+ &(share->record[0]), table->s->rec_buff_length,
+ &(share->record[1]), table->s->rec_buff_length,
+ NULL);
+ }
+ {
+ my_ptrdiff_t row_offset= share->record[0] - table->record[0];
+ Field **p_field;
+ for (p_field= table->field; *p_field; p_field++)
+ (*p_field)->move_field_offset(row_offset);
+ table->record[0]= share->record[0];
+ table->record[1]= share->record[1];
+ }
+
+ table->in_use= injector_thd;
+
+ table->s->db.str= share->db;
+ table->s->db.length= strlen(share->db);
+ table->s->table_name.str= share->table_name;
+ table->s->table_name.length= strlen(share->table_name);
+
+ DBUG_ASSERT(share->table_share == 0);
+ share->table_share= table_share;
+ DBUG_ASSERT(share->table == 0);
+ share->table= table;
+ /* We can't use 'use_all_columns()' as the file object is not setup yet */
+ table->column_bitmaps_set_no_signal(&table->s->all_set, &table->s->all_set);
+#ifndef DBUG_OFF
+ dbug_print_table("table", table);
+#endif
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Initialize the binlog part of the NDB_SHARE
+*/
+int ndbcluster_binlog_init_share(NDB_SHARE *share, TABLE *_table)
+{
+ THD *thd= current_thd;
+ MEM_ROOT *mem_root= &share->mem_root;
+ int do_event_op= ndb_binlog_running;
+ int error= 0;
+ DBUG_ENTER("ndbcluster_binlog_init_share");
+
+ share->connect_count= g_ndb_cluster_connection->get_connect_count();
+
+ share->op= 0;
+ share->table= 0;
+
+ if (!ndb_schema_share &&
+ strcmp(share->db, NDB_REP_DB) == 0 &&
+ strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0)
+ do_event_op= 1;
+ else if (!ndb_apply_status_share &&
+ strcmp(share->db, NDB_REP_DB) == 0 &&
+ strcmp(share->table_name, NDB_APPLY_TABLE) == 0)
+ do_event_op= 1;
+
+ {
+ int i, no_nodes= g_ndb_cluster_connection->no_db_nodes();
+ share->subscriber_bitmap= (MY_BITMAP*)
+ alloc_root(mem_root, no_nodes * sizeof(MY_BITMAP));
+ for (i= 0; i < no_nodes; i++)
+ {
+ my_bitmap_init(&share->subscriber_bitmap[i],
+ (Uint32*)alloc_root(mem_root, max_ndb_nodes/8),
+ max_ndb_nodes, FALSE);
+ bitmap_clear_all(&share->subscriber_bitmap[i]);
+ }
+ }
+
+ if (!do_event_op)
+ {
+ if (_table)
+ {
+ if (_table->s->primary_key == MAX_KEY)
+ share->flags|= NSF_HIDDEN_PK;
+ if (_table->s->blob_fields != 0)
+ share->flags|= NSF_BLOB_FLAG;
+ }
+ else
+ {
+ share->flags|= NSF_NO_BINLOG;
+ }
+ DBUG_RETURN(error);
+ }
+ while (1)
+ {
+ int error;
+ TABLE_SHARE *table_share= (TABLE_SHARE *) alloc_root(mem_root, sizeof(*table_share));
+ TABLE *table= (TABLE*) alloc_root(mem_root, sizeof(*table));
+ if ((error= ndbcluster_binlog_open_table(thd, share, table_share, table, 0)))
+ break;
+ /*
+ ! do not touch the contents of the table
+ it may be in use by the injector thread
+ */
+ MEM_ROOT *mem_root= &share->mem_root;
+ share->ndb_value[0]= (NdbValue*)
+ alloc_root(mem_root, sizeof(NdbValue) *
+ (table->s->fields + 2 /*extra for hidden key and part key*/));
+ share->ndb_value[1]= (NdbValue*)
+ alloc_root(mem_root, sizeof(NdbValue) *
+ (table->s->fields + 2 /*extra for hidden key and part key*/));
+
+ if (table->s->primary_key == MAX_KEY)
+ share->flags|= NSF_HIDDEN_PK;
+ if (table->s->blob_fields != 0)
+ share->flags|= NSF_BLOB_FLAG;
+ break;
+ }
+ DBUG_RETURN(error);
+}
+
+/*****************************************************************
+ functions called from master sql client threads
+****************************************************************/
+
+/*
+ called in mysql_show_binlog_events and reset_logs to make sure we wait for
+ all events originating from this mysql server to arrive in the binlog
+
+ Wait for the last epoch in which the last transaction is a part of.
+
+ Wait a maximum of 30 seconds.
+*/
+static void ndbcluster_binlog_wait(THD *thd)
+{
+ if (ndb_binlog_running)
+ {
+ DBUG_ENTER("ndbcluster_binlog_wait");
+ const char *save_info= thd ? thd->proc_info : 0;
+ ulonglong wait_epoch= *p_latest_trans_gci;
+ int count= 30;
+ if (thd)
+ thd->proc_info= "Waiting for ndbcluster binlog update to "
+ "reach current position";
+ while (count && ndb_binlog_running &&
+ ndb_latest_handled_binlog_epoch < wait_epoch)
+ {
+ count--;
+ sleep(1);
+ }
+ if (thd)
+ thd->proc_info= save_info;
+ DBUG_VOID_RETURN;
+ }
+}
+
+/*
+ Called from MYSQL_BIN_LOG::reset_logs in log.cc when binlog is emptied
+*/
+static int ndbcluster_reset_logs(THD *thd)
+{
+ if (!ndb_binlog_running)
+ return 0;
+
+ DBUG_ENTER("ndbcluster_reset_logs");
+
+ /*
+ Wait for all events orifinating from this mysql server has
+ reached the binlog before continuing to reset
+ */
+ ndbcluster_binlog_wait(thd);
+
+ char buf[1024];
+ char *end= strmov(buf, "DELETE FROM " NDB_REP_DB "." NDB_REP_TABLE);
+
+ run_query(thd, buf, end, NULL, TRUE);
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Called from MYSQL_BIN_LOG::purge_logs in log.cc when the binlog "file"
+ is removed
+*/
+
+static int
+ndbcluster_binlog_index_purge_file(THD *thd, const char *file)
+{
+ if (!ndb_binlog_running || thd->slave_thread)
+ return 0;
+
+ DBUG_ENTER("ndbcluster_binlog_index_purge_file");
+ DBUG_PRINT("enter", ("file: %s", file));
+
+ char buf[1024];
+ char *end= strmov(strmov(strmov(buf,
+ "DELETE FROM "
+ NDB_REP_DB "." NDB_REP_TABLE
+ " WHERE File='"), file), "'");
+
+ run_query(thd, buf, end, NULL, TRUE);
+
+ DBUG_RETURN(0);
+}
+
+static void
+ndbcluster_binlog_log_query(handlerton *hton, THD *thd, enum_binlog_command binlog_command,
+ const char *query, uint query_length,
+ const char *db, const char *table_name)
+{
+ DBUG_ENTER("ndbcluster_binlog_log_query");
+ DBUG_PRINT("enter", ("db: %s table_name: %s query: %s",
+ db, table_name, query));
+ enum SCHEMA_OP_TYPE type;
+ int log= 0;
+ switch (binlog_command)
+ {
+ case LOGCOM_CREATE_TABLE:
+ type= SOT_CREATE_TABLE;
+ DBUG_ASSERT(FALSE);
+ break;
+ case LOGCOM_ALTER_TABLE:
+ type= SOT_ALTER_TABLE;
+ log= 1;
+ break;
+ case LOGCOM_RENAME_TABLE:
+ type= SOT_RENAME_TABLE;
+ DBUG_ASSERT(FALSE);
+ break;
+ case LOGCOM_DROP_TABLE:
+ type= SOT_DROP_TABLE;
+ DBUG_ASSERT(FALSE);
+ break;
+ case LOGCOM_CREATE_DB:
+ type= SOT_CREATE_DB;
+ log= 1;
+ break;
+ case LOGCOM_ALTER_DB:
+ type= SOT_ALTER_DB;
+ log= 1;
+ break;
+ case LOGCOM_DROP_DB:
+ type= SOT_DROP_DB;
+ DBUG_ASSERT(FALSE);
+ break;
+ }
+ if (log)
+ {
+ ndbcluster_log_schema_op(thd, 0, query, query_length,
+ db, table_name, 0, 0, type,
+ 0, 0);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ End use of the NDB Cluster binlog
+ - wait for binlog thread to shutdown
+*/
+
+static int ndbcluster_binlog_end(THD *thd)
+{
+ DBUG_ENTER("ndbcluster_binlog_end");
+
+ if (!ndbcluster_binlog_inited)
+ DBUG_RETURN(0);
+ ndbcluster_binlog_inited= 0;
+
+#ifdef HAVE_NDB_BINLOG
+ if (ndb_util_thread_running > 0)
+ {
+ /*
+ Wait for util thread to die (as this uses the injector mutex)
+ There is a very small change that ndb_util_thread dies and the
+ following mutex is freed before it's accessed. This shouldn't
+ however be a likely case as the ndbcluster_binlog_end is supposed to
+ be called before ndb_cluster_end().
+ */
+ mysql_mutex_lock(&LOCK_ndb_util_thread);
+ /* Ensure mutex are not freed if ndb_cluster_end is running at same time */
+ ndb_util_thread_running++;
+ ndbcluster_terminating= 1;
+ mysql_cond_signal(&COND_ndb_util_thread);
+ while (ndb_util_thread_running > 1)
+ mysql_cond_wait(&COND_ndb_util_ready, &LOCK_ndb_util_thread);
+ ndb_util_thread_running--;
+ mysql_mutex_unlock(&LOCK_ndb_util_thread);
+ }
+
+ /* wait for injector thread to finish */
+ ndbcluster_binlog_terminating= 1;
+ mysql_mutex_lock(&injector_mutex);
+ mysql_cond_signal(&injector_cond);
+ while (ndb_binlog_thread_running > 0)
+ mysql_cond_wait(&injector_cond, &injector_mutex);
+ mysql_mutex_unlock(&injector_mutex);
+
+ mysql_mutex_destroy(&injector_mutex);
+ mysql_cond_destroy(&injector_cond);
+ mysql_mutex_destroy(&ndb_schema_share_mutex);
+#endif
+
+ DBUG_RETURN(0);
+}
+
+/*****************************************************************
+ functions called from slave sql client threads
+****************************************************************/
+static void ndbcluster_reset_slave(THD *thd)
+{
+ if (!ndb_binlog_running)
+ return;
+
+ DBUG_ENTER("ndbcluster_reset_slave");
+ char buf[1024];
+ char *end= strmov(buf, "DELETE FROM " NDB_REP_DB "." NDB_APPLY_TABLE);
+ run_query(thd, buf, end, NULL, TRUE);
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Initialize the binlog part of the ndb handlerton
+*/
+
+/**
+ Upon the sql command flush logs, we need to ensure that all outstanding
+ ndb data to be logged has made it to the binary log to get a deterministic
+ behavior on the rotation of the log.
+ */
+static bool ndbcluster_flush_logs(handlerton *hton)
+{
+ ndbcluster_binlog_wait(current_thd);
+ return FALSE;
+}
+
+static int ndbcluster_binlog_func(handlerton *hton, THD *thd,
+ enum_binlog_func fn,
+ void *arg)
+{
+ switch(fn)
+ {
+ case BFN_RESET_LOGS:
+ ndbcluster_reset_logs(thd);
+ break;
+ case BFN_RESET_SLAVE:
+ ndbcluster_reset_slave(thd);
+ break;
+ case BFN_BINLOG_WAIT:
+ ndbcluster_binlog_wait(thd);
+ break;
+ case BFN_BINLOG_END:
+ ndbcluster_binlog_end(thd);
+ break;
+ case BFN_BINLOG_PURGE_FILE:
+ ndbcluster_binlog_index_purge_file(thd, (const char *)arg);
+ break;
+ }
+ return 0;
+}
+
+void ndbcluster_binlog_init_handlerton()
+{
+ handlerton *h= ndbcluster_hton;
+ h->flush_logs= ndbcluster_flush_logs;
+ h->binlog_func= ndbcluster_binlog_func;
+ h->binlog_log_query= ndbcluster_binlog_log_query;
+}
+
+
+
+
+
+/*
+ check the availability af the ndb_apply_status share
+ - return share, but do not increase refcount
+ - return 0 if there is no share
+*/
+static NDB_SHARE *ndbcluster_check_ndb_apply_status_share()
+{
+ mysql_mutex_lock(&ndbcluster_mutex);
+
+ void *share= my_hash_search(&ndbcluster_open_tables,
+ (uchar*) NDB_APPLY_TABLE_FILE,
+ sizeof(NDB_APPLY_TABLE_FILE) - 1);
+ DBUG_PRINT("info",("ndbcluster_check_ndb_apply_status_share %s 0x%lx",
+ NDB_APPLY_TABLE_FILE, (long) share));
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ return (NDB_SHARE*) share;
+}
+
+/*
+ check the availability af the schema share
+ - return share, but do not increase refcount
+ - return 0 if there is no share
+*/
+static NDB_SHARE *ndbcluster_check_ndb_schema_share()
+{
+ mysql_mutex_lock(&ndbcluster_mutex);
+
+ void *share= my_hash_search(&ndbcluster_open_tables,
+ (uchar*) NDB_SCHEMA_TABLE_FILE,
+ sizeof(NDB_SCHEMA_TABLE_FILE) - 1);
+ DBUG_PRINT("info",("ndbcluster_check_ndb_schema_share %s 0x%lx",
+ NDB_SCHEMA_TABLE_FILE, (long) share));
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ return (NDB_SHARE*) share;
+}
+
+/*
+ Create the ndb_apply_status table
+*/
+static int ndbcluster_create_ndb_apply_status_table(THD *thd)
+{
+ DBUG_ENTER("ndbcluster_create_ndb_apply_status_table");
+
+ /*
+ Check if we already have the apply status table.
+ If so it should have been discovered at startup
+ and thus have a share
+ */
+
+ if (ndbcluster_check_ndb_apply_status_share())
+ DBUG_RETURN(0);
+
+ if (g_ndb_cluster_connection->get_no_ready() <= 0)
+ DBUG_RETURN(0);
+
+ char buf[1024 + 1], *end;
+
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB: Creating " NDB_REP_DB "." NDB_APPLY_TABLE);
+
+ /*
+ Check if apply status table exists in MySQL "dictionary"
+ if so, remove it since there is none in Ndb
+ */
+ {
+ build_table_filename(buf, sizeof(buf) - 1,
+ NDB_REP_DB, NDB_APPLY_TABLE, reg_ext, 0);
+ mysql_file_delete(key_file_frm, buf, MYF(0));
+ }
+
+ /*
+ Note, updating this table schema must be reflected in ndb_restore
+ */
+ end= strmov(buf, "CREATE TABLE IF NOT EXISTS "
+ NDB_REP_DB "." NDB_APPLY_TABLE
+ " ( server_id INT UNSIGNED NOT NULL,"
+ " epoch BIGINT UNSIGNED NOT NULL, "
+ " log_name VARCHAR(255) BINARY NOT NULL, "
+ " start_pos BIGINT UNSIGNED NOT NULL, "
+ " end_pos BIGINT UNSIGNED NOT NULL, "
+ " PRIMARY KEY USING HASH (server_id) ) ENGINE=NDB CHARACTER SET latin1");
+
+ const int no_print_error[6]= {ER_TABLE_EXISTS_ERROR,
+ 701,
+ 702,
+ 721, // Table already exist
+ 4009,
+ 0}; // do not print error 701 etc
+ run_query(thd, buf, end, no_print_error, TRUE);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Create the schema table
+*/
+static int ndbcluster_create_schema_table(THD *thd)
+{
+ DBUG_ENTER("ndbcluster_create_schema_table");
+
+ /*
+ Check if we already have the schema table.
+ If so it should have been discovered at startup
+ and thus have a share
+ */
+
+ if (ndbcluster_check_ndb_schema_share())
+ DBUG_RETURN(0);
+
+ if (g_ndb_cluster_connection->get_no_ready() <= 0)
+ DBUG_RETURN(0);
+
+ char buf[1024 + 1], *end;
+
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB: Creating " NDB_REP_DB "." NDB_SCHEMA_TABLE);
+
+ /*
+ Check if schema table exists in MySQL "dictionary"
+ if so, remove it since there is none in Ndb
+ */
+ {
+ build_table_filename(buf, sizeof(buf) - 1,
+ NDB_REP_DB, NDB_SCHEMA_TABLE, reg_ext, 0);
+ mysql_file_delete(key_file_frm, buf, MYF(0));
+ }
+
+ /*
+ Update the defines below to reflect the table schema
+ */
+ end= strmov(buf, "CREATE TABLE IF NOT EXISTS "
+ NDB_REP_DB "." NDB_SCHEMA_TABLE
+ " ( db VARBINARY(63) NOT NULL,"
+ " name VARBINARY(63) NOT NULL,"
+ " slock BINARY(32) NOT NULL,"
+ " query BLOB NOT NULL,"
+ " node_id INT UNSIGNED NOT NULL,"
+ " epoch BIGINT UNSIGNED NOT NULL,"
+ " id INT UNSIGNED NOT NULL,"
+ " version INT UNSIGNED NOT NULL,"
+ " type INT UNSIGNED NOT NULL,"
+ " PRIMARY KEY USING HASH (db,name) ) ENGINE=NDB CHARACTER SET latin1");
+
+ const int no_print_error[6]= {ER_TABLE_EXISTS_ERROR,
+ 701,
+ 702,
+ 721, // Table already exist
+ 4009,
+ 0}; // do not print error 701 etc
+ run_query(thd, buf, end, no_print_error, TRUE);
+
+ DBUG_RETURN(0);
+}
+
+int ndbcluster_setup_binlog_table_shares(THD *thd)
+{
+ if (!ndb_schema_share &&
+ ndbcluster_check_ndb_schema_share() == 0)
+ {
+ ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_SCHEMA_TABLE);
+ if (!ndb_schema_share)
+ {
+ ndbcluster_create_schema_table(thd);
+ // always make sure we create the 'schema' first
+ if (!ndb_schema_share)
+ return 1;
+ }
+ }
+ if (!ndb_apply_status_share &&
+ ndbcluster_check_ndb_apply_status_share() == 0)
+ {
+ ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_APPLY_TABLE);
+ if (!ndb_apply_status_share)
+ {
+ ndbcluster_create_ndb_apply_status_table(thd);
+ if (!ndb_apply_status_share)
+ return 1;
+ }
+ }
+ if (!ndbcluster_find_all_files(thd))
+ {
+ ndb_binlog_tables_inited= TRUE;
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: ndb tables writable");
+ close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
+ /* Signal injector thread that all is setup */
+ mysql_cond_signal(&injector_cond);
+ }
+ return 0;
+}
+
+/*
+ Defines and struct for schema table.
+ Should reflect table definition above.
+*/
+#define SCHEMA_DB_I 0u
+#define SCHEMA_NAME_I 1u
+#define SCHEMA_SLOCK_I 2u
+#define SCHEMA_QUERY_I 3u
+#define SCHEMA_NODE_ID_I 4u
+#define SCHEMA_EPOCH_I 5u
+#define SCHEMA_ID_I 6u
+#define SCHEMA_VERSION_I 7u
+#define SCHEMA_TYPE_I 8u
+#define SCHEMA_SIZE 9u
+#define SCHEMA_SLOCK_SIZE 32u
+
+struct Cluster_schema
+{
+ uchar db_length;
+ char db[64];
+ uchar name_length;
+ char name[64];
+ uchar slock_length;
+ uint32 slock[SCHEMA_SLOCK_SIZE/4];
+ unsigned short query_length;
+ char *query;
+ Uint64 epoch;
+ uint32 node_id;
+ uint32 id;
+ uint32 version;
+ uint32 type;
+ uint32 any_value;
+};
+
+static void print_could_not_discover_error(THD *thd,
+ const Cluster_schema *schema)
+{
+ sql_print_error("NDB Binlog: Could not discover table '%s.%s' from "
+ "binlog schema event '%s' from node %d. "
+ "my_errno: %d",
+ schema->db, schema->name, schema->query,
+ schema->node_id, my_errno);
+ List_iterator_fast<Sql_condition> it(thd->warning_info->warn_list());
+ Sql_condition *err;
+ while ((err= it++))
+ sql_print_warning("NDB Binlog: (%d)%s", err->get_sql_errno(),
+ err->get_message_text());
+}
+
+/*
+ Transfer schema table data into corresponding struct
+*/
+static void ndbcluster_get_schema(NDB_SHARE *share,
+ Cluster_schema *s)
+{
+ TABLE *table= share->table;
+ Field **field;
+ /* unpack blob values */
+ uchar* blobs_buffer= 0;
+ uint blobs_buffer_size= 0;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ {
+ ptrdiff_t ptrdiff= 0;
+ int ret= get_ndb_blobs_value(table, share->ndb_value[0],
+ blobs_buffer, blobs_buffer_size,
+ ptrdiff);
+ if (ret != 0)
+ {
+ my_free(blobs_buffer);
+ DBUG_PRINT("info", ("blob read error"));
+ DBUG_ASSERT(FALSE);
+ }
+ }
+ /* db varchar 1 length uchar */
+ field= table->field;
+ s->db_length= *(uint8*)(*field)->ptr;
+ DBUG_ASSERT(s->db_length <= (*field)->field_length);
+ DBUG_ASSERT((*field)->field_length + 1 == sizeof(s->db));
+ memcpy(s->db, (*field)->ptr + 1, s->db_length);
+ s->db[s->db_length]= 0;
+ /* name varchar 1 length uchar */
+ field++;
+ s->name_length= *(uint8*)(*field)->ptr;
+ DBUG_ASSERT(s->name_length <= (*field)->field_length);
+ DBUG_ASSERT((*field)->field_length + 1 == sizeof(s->name));
+ memcpy(s->name, (*field)->ptr + 1, s->name_length);
+ s->name[s->name_length]= 0;
+ /* slock fixed length */
+ field++;
+ s->slock_length= (*field)->field_length;
+ DBUG_ASSERT((*field)->field_length == sizeof(s->slock));
+ memcpy(s->slock, (*field)->ptr, s->slock_length);
+ /* query blob */
+ field++;
+ {
+ Field_blob *field_blob= (Field_blob*)(*field);
+ uint blob_len= field_blob->get_length((*field)->ptr);
+ uchar *blob_ptr= 0;
+ field_blob->get_ptr(&blob_ptr);
+ DBUG_ASSERT(blob_len == 0 || blob_ptr != 0);
+ s->query_length= blob_len;
+ s->query= sql_strmake((char*) blob_ptr, blob_len);
+ }
+ /* node_id */
+ field++;
+ s->node_id= ((Field_long *)*field)->val_int();
+ /* epoch */
+ field++;
+ s->epoch= ((Field_long *)*field)->val_int();
+ /* id */
+ field++;
+ s->id= ((Field_long *)*field)->val_int();
+ /* version */
+ field++;
+ s->version= ((Field_long *)*field)->val_int();
+ /* type */
+ field++;
+ s->type= ((Field_long *)*field)->val_int();
+ /* free blobs buffer */
+ my_free(blobs_buffer);
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+}
+
+/*
+ helper function to pack a ndb varchar
+*/
+char *ndb_pack_varchar(const NDBCOL *col, char *buf,
+ const char *str, int sz)
+{
+ switch (col->getArrayType())
+ {
+ case NDBCOL::ArrayTypeFixed:
+ memcpy(buf, str, sz);
+ break;
+ case NDBCOL::ArrayTypeShortVar:
+ *(uchar*)buf= (uchar)sz;
+ memcpy(buf + 1, str, sz);
+ break;
+ case NDBCOL::ArrayTypeMediumVar:
+ int2store(buf, sz);
+ memcpy(buf + 2, str, sz);
+ break;
+ }
+ return buf;
+}
+
+/*
+ acknowledge handling of schema operation
+*/
+static int
+ndbcluster_update_slock(THD *thd,
+ const char *db,
+ const char *table_name)
+{
+ DBUG_ENTER("ndbcluster_update_slock");
+ if (!ndb_schema_share)
+ {
+ DBUG_RETURN(0);
+ }
+
+ const NdbError *ndb_error= 0;
+ uint32 node_id= g_ndb_cluster_connection->node_id();
+ Ndb *ndb= check_ndb_in_thd(thd);
+ char save_db[FN_HEADLEN];
+ strcpy(save_db, ndb->getDatabaseName());
+
+ char tmp_buf[FN_REFLEN];
+ NDBDICT *dict= ndb->getDictionary();
+ ndb->setDatabaseName(NDB_REP_DB);
+ Ndb_table_guard ndbtab_g(dict, NDB_SCHEMA_TABLE);
+ const NDBTAB *ndbtab= ndbtab_g.get_table();
+ NdbTransaction *trans= 0;
+ int retries= 100;
+ int retry_sleep= 10; /* 10 milliseconds, transaction */
+ const NDBCOL *col[SCHEMA_SIZE];
+ unsigned sz[SCHEMA_SIZE];
+
+ MY_BITMAP slock;
+ uint32 bitbuf[SCHEMA_SLOCK_SIZE/4];
+ my_bitmap_init(&slock, bitbuf, sizeof(bitbuf)*8, false);
+
+ if (ndbtab == 0)
+ {
+ abort();
+ DBUG_RETURN(0);
+ }
+
+ {
+ uint i;
+ for (i= 0; i < SCHEMA_SIZE; i++)
+ {
+ col[i]= ndbtab->getColumn(i);
+ if (i != SCHEMA_QUERY_I)
+ {
+ sz[i]= col[i]->getLength();
+ DBUG_ASSERT(sz[i] <= sizeof(tmp_buf));
+ }
+ }
+ }
+
+ while (1)
+ {
+ if ((trans= ndb->startTransaction()) == 0)
+ goto err;
+ {
+ NdbOperation *op= 0;
+ int r= 0;
+
+ /* read the bitmap exlusive */
+ r|= (op= trans->getNdbOperation(ndbtab)) == 0;
+ DBUG_ASSERT(r == 0);
+ r|= op->readTupleExclusive();
+ DBUG_ASSERT(r == 0);
+
+ /* db */
+ ndb_pack_varchar(col[SCHEMA_DB_I], tmp_buf, db, strlen(db));
+ r|= op->equal(SCHEMA_DB_I, tmp_buf);
+ DBUG_ASSERT(r == 0);
+ /* name */
+ ndb_pack_varchar(col[SCHEMA_NAME_I], tmp_buf, table_name,
+ strlen(table_name));
+ r|= op->equal(SCHEMA_NAME_I, tmp_buf);
+ DBUG_ASSERT(r == 0);
+ /* slock */
+ r|= op->getValue(SCHEMA_SLOCK_I, (char*)slock.bitmap) == 0;
+ DBUG_ASSERT(r == 0);
+ }
+ if (trans->execute(NdbTransaction::NoCommit))
+ goto err;
+ bitmap_clear_bit(&slock, node_id);
+ {
+ NdbOperation *op= 0;
+ int r= 0;
+
+ /* now update the tuple */
+ r|= (op= trans->getNdbOperation(ndbtab)) == 0;
+ DBUG_ASSERT(r == 0);
+ r|= op->updateTuple();
+ DBUG_ASSERT(r == 0);
+
+ /* db */
+ ndb_pack_varchar(col[SCHEMA_DB_I], tmp_buf, db, strlen(db));
+ r|= op->equal(SCHEMA_DB_I, tmp_buf);
+ DBUG_ASSERT(r == 0);
+ /* name */
+ ndb_pack_varchar(col[SCHEMA_NAME_I], tmp_buf, table_name,
+ strlen(table_name));
+ r|= op->equal(SCHEMA_NAME_I, tmp_buf);
+ DBUG_ASSERT(r == 0);
+ /* slock */
+ r|= op->setValue(SCHEMA_SLOCK_I, (char*)slock.bitmap);
+ DBUG_ASSERT(r == 0);
+ /* node_id */
+ r|= op->setValue(SCHEMA_NODE_ID_I, node_id);
+ DBUG_ASSERT(r == 0);
+ /* type */
+ r|= op->setValue(SCHEMA_TYPE_I, (uint32)SOT_CLEAR_SLOCK);
+ DBUG_ASSERT(r == 0);
+ }
+ if (trans->execute(NdbTransaction::Commit) == 0)
+ {
+ dict->forceGCPWait();
+ DBUG_PRINT("info", ("node %d cleared lock on '%s.%s'",
+ node_id, db, table_name));
+ break;
+ }
+ err:
+ const NdbError *this_error= trans ?
+ &trans->getNdbError() : &ndb->getNdbError();
+ if (this_error->status == NdbError::TemporaryError)
+ {
+ if (retries--)
+ {
+ if (trans)
+ ndb->closeTransaction(trans);
+ my_sleep(retry_sleep);
+ continue; // retry
+ }
+ }
+ ndb_error= this_error;
+ break;
+ }
+
+ if (ndb_error)
+ {
+ char buf[1024];
+ my_snprintf(buf, sizeof(buf), "Could not release lock on '%s.%s'",
+ db, table_name);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ ndb_error->code, ndb_error->message, buf);
+ }
+ if (trans)
+ ndb->closeTransaction(trans);
+ ndb->setDatabaseName(save_db);
+ DBUG_RETURN(0);
+}
+
+/*
+ log query in schema table
+*/
+static void ndb_report_waiting(const char *key,
+ int the_time,
+ const char *op,
+ const char *obj)
+{
+ ulonglong ndb_latest_epoch= 0;
+ const char *proc_info= "<no info>";
+ mysql_mutex_lock(&injector_mutex);
+ if (injector_ndb)
+ ndb_latest_epoch= injector_ndb->getLatestGCI();
+ if (injector_thd)
+ proc_info= injector_thd->proc_info;
+ mysql_mutex_unlock(&injector_mutex);
+ sql_print_information("NDB %s:"
+ " waiting max %u sec for %s %s."
+ " epochs: (%u,%u,%u)"
+ " injector proc_info: %s"
+ ,key, the_time, op, obj
+ ,(uint)ndb_latest_handled_binlog_epoch
+ ,(uint)ndb_latest_received_binlog_epoch
+ ,(uint)ndb_latest_epoch
+ ,proc_info
+ );
+}
+
+int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share,
+ const char *query, int query_length,
+ const char *db, const char *table_name,
+ uint32 ndb_table_id,
+ uint32 ndb_table_version,
+ enum SCHEMA_OP_TYPE type,
+ const char *new_db, const char *new_table_name)
+{
+ DBUG_ENTER("ndbcluster_log_schema_op");
+ Thd_ndb *thd_ndb= get_thd_ndb(thd);
+ if (!thd_ndb)
+ {
+ if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb()))
+ {
+ sql_print_error("Could not allocate Thd_ndb object");
+ DBUG_RETURN(1);
+ }
+ set_thd_ndb(thd, thd_ndb);
+ }
+
+ DBUG_PRINT("enter",
+ ("query: %s db: %s table_name: %s thd_ndb->options: %d",
+ query, db, table_name, thd_ndb->options));
+ if (!ndb_schema_share || thd_ndb->options & TNO_NO_LOG_SCHEMA_OP)
+ {
+ DBUG_RETURN(0);
+ }
+
+ char tmp_buf2_mem[FN_REFLEN];
+ String tmp_buf2(tmp_buf2_mem, sizeof(tmp_buf2_mem), system_charset_info);
+ tmp_buf2.length(0);
+ const char *type_str;
+ switch (type)
+ {
+ case SOT_DROP_TABLE:
+ /* drop database command, do not log at drop table */
+ if (thd->lex->sql_command == SQLCOM_DROP_DB)
+ DBUG_RETURN(0);
+ /* redo the drop table query as is may contain several tables */
+ tmp_buf2.append(STRING_WITH_LEN("drop table "));
+ append_identifier(thd, &tmp_buf2, table_name, strlen(table_name));
+ query= tmp_buf2.c_ptr_safe();
+ query_length= tmp_buf2.length();
+ type_str= "drop table";
+ break;
+ case SOT_RENAME_TABLE:
+ /* redo the rename table query as is may contain several tables */
+ tmp_buf2.append(STRING_WITH_LEN("rename table "));
+ append_identifier(thd, &tmp_buf2, db, strlen(db));
+ tmp_buf2.append(STRING_WITH_LEN("."));
+ append_identifier(thd, &tmp_buf2, table_name, strlen(table_name));
+ tmp_buf2.append(STRING_WITH_LEN(" to "));
+ append_identifier(thd, &tmp_buf2, new_db, strlen(new_db));
+ tmp_buf2.append(STRING_WITH_LEN("."));
+ append_identifier(thd, &tmp_buf2, new_table_name, strlen(new_table_name));
+ query= tmp_buf2.c_ptr_safe();
+ query_length= tmp_buf2.length();
+ type_str= "rename table";
+ break;
+ case SOT_CREATE_TABLE:
+ type_str= "create table";
+ break;
+ case SOT_ALTER_TABLE:
+ type_str= "alter table";
+ break;
+ case SOT_DROP_DB:
+ type_str= "drop db";
+ break;
+ case SOT_CREATE_DB:
+ type_str= "create db";
+ break;
+ case SOT_ALTER_DB:
+ type_str= "alter db";
+ break;
+ case SOT_TABLESPACE:
+ type_str= "tablespace";
+ break;
+ case SOT_LOGFILE_GROUP:
+ type_str= "logfile group";
+ break;
+ case SOT_TRUNCATE_TABLE:
+ type_str= "truncate table";
+ break;
+ default:
+ abort(); /* should not happen, programming error */
+ }
+
+ NDB_SCHEMA_OBJECT *ndb_schema_object;
+ {
+ char key[FN_REFLEN + 1];
+ build_table_filename(key, sizeof(key) - 1, db, table_name, "", 0);
+ ndb_schema_object= ndb_get_schema_object(key, TRUE, FALSE);
+ }
+
+ const NdbError *ndb_error= 0;
+ uint32 node_id= g_ndb_cluster_connection->node_id();
+ Uint64 epoch= 0;
+ MY_BITMAP schema_subscribers;
+ uint32 bitbuf[sizeof(ndb_schema_object->slock)/4];
+ char bitbuf_e[sizeof(bitbuf)];
+ bzero(bitbuf_e, sizeof(bitbuf_e));
+ {
+ int i, updated= 0;
+ int no_storage_nodes= g_ndb_cluster_connection->no_db_nodes();
+ my_bitmap_init(&schema_subscribers, bitbuf, sizeof(bitbuf)*8, FALSE);
+ bitmap_set_all(&schema_subscribers);
+
+ /* begin protect ndb_schema_share */
+ mysql_mutex_lock(&ndb_schema_share_mutex);
+ if (ndb_schema_share == 0)
+ {
+ mysql_mutex_unlock(&ndb_schema_share_mutex);
+ if (ndb_schema_object)
+ ndb_free_schema_object(&ndb_schema_object, FALSE);
+ DBUG_RETURN(0);
+ }
+ mysql_mutex_lock(&ndb_schema_share->mutex);
+ for (i= 0; i < no_storage_nodes; i++)
+ {
+ MY_BITMAP *table_subscribers= &ndb_schema_share->subscriber_bitmap[i];
+ if (!bitmap_is_clear_all(table_subscribers))
+ {
+ bitmap_intersect(&schema_subscribers,
+ table_subscribers);
+ updated= 1;
+ }
+ }
+ mysql_mutex_unlock(&ndb_schema_share->mutex);
+ mysql_mutex_unlock(&ndb_schema_share_mutex);
+ /* end protect ndb_schema_share */
+
+ if (updated)
+ {
+ bitmap_clear_bit(&schema_subscribers, node_id);
+ /*
+ if setting own acknowledge bit it is important that
+ no other mysqld's are registred, as subsequent code
+ will cause the original event to be hidden (by blob
+ merge event code)
+ */
+ if (bitmap_is_clear_all(&schema_subscribers))
+ bitmap_set_bit(&schema_subscribers, node_id);
+ }
+ else
+ bitmap_clear_all(&schema_subscribers);
+
+ if (ndb_schema_object)
+ {
+ mysql_mutex_lock(&ndb_schema_object->mutex);
+ memcpy(ndb_schema_object->slock, schema_subscribers.bitmap,
+ sizeof(ndb_schema_object->slock));
+ mysql_mutex_unlock(&ndb_schema_object->mutex);
+ }
+
+ DBUG_DUMP("schema_subscribers", (uchar*)schema_subscribers.bitmap,
+ no_bytes_in_map(&schema_subscribers));
+ DBUG_PRINT("info", ("bitmap_is_clear_all(&schema_subscribers): %d",
+ bitmap_is_clear_all(&schema_subscribers)));
+ }
+
+ Ndb *ndb= thd_ndb->ndb;
+ char save_db[FN_REFLEN];
+ strcpy(save_db, ndb->getDatabaseName());
+
+ char tmp_buf[FN_REFLEN];
+ NDBDICT *dict= ndb->getDictionary();
+ ndb->setDatabaseName(NDB_REP_DB);
+ Ndb_table_guard ndbtab_g(dict, NDB_SCHEMA_TABLE);
+ const NDBTAB *ndbtab= ndbtab_g.get_table();
+ NdbTransaction *trans= 0;
+ int retries= 100;
+ int retry_sleep= 10; /* 10 milliseconds, transaction */
+ const NDBCOL *col[SCHEMA_SIZE];
+ unsigned sz[SCHEMA_SIZE];
+
+ if (ndbtab == 0)
+ {
+ if (strcmp(NDB_REP_DB, db) != 0 ||
+ strcmp(NDB_SCHEMA_TABLE, table_name))
+ {
+ ndb_error= &dict->getNdbError();
+ }
+ goto end;
+ }
+
+ {
+ uint i;
+ for (i= 0; i < SCHEMA_SIZE; i++)
+ {
+ col[i]= ndbtab->getColumn(i);
+ if (i != SCHEMA_QUERY_I)
+ {
+ sz[i]= col[i]->getLength();
+ DBUG_ASSERT(sz[i] <= sizeof(tmp_buf));
+ }
+ }
+ }
+
+ while (1)
+ {
+ const char *log_db= db;
+ const char *log_tab= table_name;
+ const char *log_subscribers= (char*)schema_subscribers.bitmap;
+ uint32 log_type= (uint32)type;
+ if ((trans= ndb->startTransaction()) == 0)
+ goto err;
+ while (1)
+ {
+ NdbOperation *op= 0;
+ int r= 0;
+ r|= (op= trans->getNdbOperation(ndbtab)) == 0;
+ DBUG_ASSERT(r == 0);
+ r|= op->writeTuple();
+ DBUG_ASSERT(r == 0);
+
+ /* db */
+ ndb_pack_varchar(col[SCHEMA_DB_I], tmp_buf, log_db, strlen(log_db));
+ r|= op->equal(SCHEMA_DB_I, tmp_buf);
+ DBUG_ASSERT(r == 0);
+ /* name */
+ ndb_pack_varchar(col[SCHEMA_NAME_I], tmp_buf, log_tab,
+ strlen(log_tab));
+ r|= op->equal(SCHEMA_NAME_I, tmp_buf);
+ DBUG_ASSERT(r == 0);
+ /* slock */
+ DBUG_ASSERT(sz[SCHEMA_SLOCK_I] == sizeof(bitbuf));
+ r|= op->setValue(SCHEMA_SLOCK_I, log_subscribers);
+ DBUG_ASSERT(r == 0);
+ /* query */
+ {
+ NdbBlob *ndb_blob= op->getBlobHandle(SCHEMA_QUERY_I);
+ DBUG_ASSERT(ndb_blob != 0);
+ uint blob_len= query_length;
+ const char* blob_ptr= query;
+ r|= ndb_blob->setValue(blob_ptr, blob_len);
+ DBUG_ASSERT(r == 0);
+ }
+ /* node_id */
+ r|= op->setValue(SCHEMA_NODE_ID_I, node_id);
+ DBUG_ASSERT(r == 0);
+ /* epoch */
+ r|= op->setValue(SCHEMA_EPOCH_I, epoch);
+ DBUG_ASSERT(r == 0);
+ /* id */
+ r|= op->setValue(SCHEMA_ID_I, ndb_table_id);
+ DBUG_ASSERT(r == 0);
+ /* version */
+ r|= op->setValue(SCHEMA_VERSION_I, ndb_table_version);
+ DBUG_ASSERT(r == 0);
+ /* type */
+ r|= op->setValue(SCHEMA_TYPE_I, log_type);
+ DBUG_ASSERT(r == 0);
+ /* any value */
+ if (!(thd->variables.option_bits & OPTION_BIN_LOG))
+ r|= op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING);
+ else
+ r|= op->setAnyValue(thd->server_id);
+ DBUG_ASSERT(r == 0);
+ if (log_db != new_db && new_db && new_table_name)
+ {
+ log_db= new_db;
+ log_tab= new_table_name;
+ log_subscribers= bitbuf_e; // no ack expected on this
+ log_type= (uint32)SOT_RENAME_TABLE_NEW;
+ continue;
+ }
+ break;
+ }
+ if (trans->execute(NdbTransaction::Commit) == 0)
+ {
+ DBUG_PRINT("info", ("logged: %s", query));
+ break;
+ }
+err:
+ const NdbError *this_error= trans ?
+ &trans->getNdbError() : &ndb->getNdbError();
+ if (this_error->status == NdbError::TemporaryError)
+ {
+ if (retries--)
+ {
+ if (trans)
+ ndb->closeTransaction(trans);
+ my_sleep(retry_sleep);
+ continue; // retry
+ }
+ }
+ ndb_error= this_error;
+ break;
+ }
+end:
+ if (ndb_error)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ ndb_error->code,
+ ndb_error->message,
+ "Could not log query '%s' on other mysqld's");
+
+ if (trans)
+ ndb->closeTransaction(trans);
+ ndb->setDatabaseName(save_db);
+
+ /*
+ Wait for other mysqld's to acknowledge the table operation
+ */
+ if (ndb_error == 0 &&
+ !bitmap_is_clear_all(&schema_subscribers))
+ {
+ /*
+ if own nodeid is set we are a single mysqld registred
+ as an optimization we update the slock directly
+ */
+ if (bitmap_is_set(&schema_subscribers, node_id))
+ ndbcluster_update_slock(thd, db, table_name);
+ else
+ dict->forceGCPWait();
+
+ int max_timeout= DEFAULT_SYNC_TIMEOUT;
+ mysql_mutex_lock(&ndb_schema_object->mutex);
+ while (1)
+ {
+ struct timespec abstime;
+ int i;
+ int no_storage_nodes= g_ndb_cluster_connection->no_db_nodes();
+ set_timespec(abstime, 1);
+ int ret= mysql_cond_timedwait(&injector_cond,
+ &ndb_schema_object->mutex,
+ &abstime);
+ if (thd->killed)
+ break;
+
+ /* begin protect ndb_schema_share */
+ mysql_mutex_lock(&ndb_schema_share_mutex);
+ if (ndb_schema_share == 0)
+ {
+ mysql_mutex_unlock(&ndb_schema_share_mutex);
+ break;
+ }
+ mysql_mutex_lock(&ndb_schema_share->mutex);
+ for (i= 0; i < no_storage_nodes; i++)
+ {
+ /* remove any unsubscribed from schema_subscribers */
+ MY_BITMAP *tmp= &ndb_schema_share->subscriber_bitmap[i];
+ if (!bitmap_is_clear_all(tmp))
+ bitmap_intersect(&schema_subscribers, tmp);
+ }
+ mysql_mutex_unlock(&ndb_schema_share->mutex);
+ mysql_mutex_unlock(&ndb_schema_share_mutex);
+ /* end protect ndb_schema_share */
+
+ /* remove any unsubscribed from ndb_schema_object->slock */
+ bitmap_intersect(&ndb_schema_object->slock_bitmap, &schema_subscribers);
+
+ DBUG_DUMP("ndb_schema_object->slock_bitmap.bitmap",
+ (uchar*)ndb_schema_object->slock_bitmap.bitmap,
+ no_bytes_in_map(&ndb_schema_object->slock_bitmap));
+
+ if (bitmap_is_clear_all(&ndb_schema_object->slock_bitmap))
+ break;
+
+ if (ret)
+ {
+ max_timeout--;
+ if (max_timeout == 0)
+ {
+ sql_print_error("NDB %s: distributing %s timed out. Ignoring...",
+ type_str, ndb_schema_object->key);
+ break;
+ }
+ if (opt_ndb_extra_logging)
+ ndb_report_waiting(type_str, max_timeout,
+ "distributing", ndb_schema_object->key);
+ }
+ }
+ mysql_mutex_unlock(&ndb_schema_object->mutex);
+ }
+
+ if (ndb_schema_object)
+ ndb_free_schema_object(&ndb_schema_object, FALSE);
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Handle _non_ data events from the storage nodes
+*/
+int
+ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
+ NDB_SHARE *share)
+{
+ DBUG_ENTER("ndb_handle_schema_change");
+ TABLE* table= share->table;
+ TABLE_SHARE *table_share= share->table_share;
+ const char *dbname= table_share->db.str;
+ const char *tabname= table_share->table_name.str;
+ bool do_close_cached_tables= FALSE;
+ bool is_online_alter_table= FALSE;
+ bool is_rename_table= FALSE;
+ bool is_remote_change=
+ (uint) pOp->getReqNodeId() != g_ndb_cluster_connection->node_id();
+
+ if (pOp->getEventType() == NDBEVENT::TE_ALTER)
+ {
+ if (pOp->tableFrmChanged())
+ {
+ DBUG_PRINT("info", ("NDBEVENT::TE_ALTER: table frm changed"));
+ is_online_alter_table= TRUE;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("NDBEVENT::TE_ALTER: name changed"));
+ DBUG_ASSERT(pOp->tableNameChanged());
+ is_rename_table= TRUE;
+ }
+ }
+
+ {
+ ndb->setDatabaseName(dbname);
+ Ndb_table_guard ndbtab_g(ndb->getDictionary(), tabname);
+ const NDBTAB *ev_tab= pOp->getTable();
+ const NDBTAB *cache_tab= ndbtab_g.get_table();
+ if (cache_tab &&
+ cache_tab->getObjectId() == ev_tab->getObjectId() &&
+ cache_tab->getObjectVersion() <= ev_tab->getObjectVersion())
+ ndbtab_g.invalidate();
+ }
+
+ /*
+ Refresh local frm file and dictionary cache if
+ remote on-line alter table
+ */
+ if (is_remote_change && is_online_alter_table)
+ {
+ const char *tabname= table_share->table_name.str;
+ char key[FN_REFLEN + 1];
+ uchar *data= 0, *pack_data= 0;
+ size_t length, pack_length;
+ int error;
+ NDBDICT *dict= ndb->getDictionary();
+ const NDBTAB *altered_table= pOp->getTable();
+
+ DBUG_PRINT("info", ("Detected frm change of table %s.%s",
+ dbname, tabname));
+ build_table_filename(key, FN_LEN - 1, dbname, tabname, NullS, 0);
+ /*
+ If the there is no local table shadowing the altered table and
+ it has an frm that is different than the one on disk then
+ overwrite it with the new table definition
+ */
+ if (!ndbcluster_check_if_local_table(dbname, tabname) &&
+ readfrm(key, &data, &length) == 0 &&
+ packfrm(data, length, &pack_data, &pack_length) == 0 &&
+ cmp_frm(altered_table, pack_data, pack_length))
+ {
+ DBUG_DUMP("frm", (uchar*) altered_table->getFrmData(),
+ altered_table->getFrmLength());
+ Ndb_table_guard ndbtab_g(dict, tabname);
+ const NDBTAB *old= ndbtab_g.get_table();
+ if (!old &&
+ old->getObjectVersion() != altered_table->getObjectVersion())
+ dict->putTable(altered_table);
+
+ my_free(data);
+ data= NULL;
+ if ((error= unpackfrm(&data, &length,
+ (const uchar*) altered_table->getFrmData())) ||
+ (error= writefrm(key, data, length)))
+ {
+ sql_print_information("NDB: Failed write frm for %s.%s, error %d",
+ dbname, tabname, error);
+ }
+
+ // copy names as memory will be freed
+ NdbAutoPtr<char> a1((char *)(dbname= strdup(dbname)));
+ NdbAutoPtr<char> a2((char *)(tabname= strdup(tabname)));
+ ndbcluster_binlog_close_table(thd, share);
+
+ TABLE_LIST table_list;
+ bzero((char*) &table_list,sizeof(table_list));
+ table_list.db= (char *)dbname;
+ table_list.alias= table_list.table_name= (char *)tabname;
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
+
+ if ((error= ndbcluster_binlog_open_table(thd, share,
+ table_share, table, 1)))
+ sql_print_information("NDB: Failed to re-open table %s.%s",
+ dbname, tabname);
+
+ table= share->table;
+ table_share= share->table_share;
+ dbname= table_share->db.str;
+ tabname= table_share->table_name.str;
+ }
+ my_free(data);
+ my_free(pack_data);
+ }
+
+ // If only frm was changed continue replicating
+ if (is_online_alter_table)
+ {
+ /* Signal ha_ndbcluster::alter_table that drop is done */
+ mysql_cond_signal(&injector_cond);
+ DBUG_RETURN(0);
+ }
+
+ mysql_mutex_lock(&share->mutex);
+ if (is_rename_table && !is_remote_change)
+ {
+ DBUG_PRINT("info", ("Detected name change of table %s.%s",
+ share->db, share->table_name));
+ /* ToDo: remove printout */
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: rename table %s%s/%s -> %s.",
+ share_prefix, share->table->s->db.str,
+ share->table->s->table_name.str,
+ share->key);
+ {
+ ndb->setDatabaseName(share->table->s->db.str);
+ Ndb_table_guard ndbtab_g(ndb->getDictionary(),
+ share->table->s->table_name.str);
+ const NDBTAB *ev_tab= pOp->getTable();
+ const NDBTAB *cache_tab= ndbtab_g.get_table();
+ if (cache_tab &&
+ cache_tab->getObjectId() == ev_tab->getObjectId() &&
+ cache_tab->getObjectVersion() <= ev_tab->getObjectVersion())
+ ndbtab_g.invalidate();
+ }
+ /* do the rename of the table in the share */
+ share->table->s->db.str= share->db;
+ share->table->s->db.length= strlen(share->db);
+ share->table->s->table_name.str= share->table_name;
+ share->table->s->table_name.length= strlen(share->table_name);
+ }
+ DBUG_ASSERT(share->op == pOp || share->op_old == pOp);
+ if (share->op_old == pOp)
+ share->op_old= 0;
+ else
+ share->op= 0;
+ // either just us or drop table handling as well
+
+ /* Signal ha_ndbcluster::delete/rename_table that drop is done */
+ mysql_mutex_unlock(&share->mutex);
+ mysql_cond_signal(&injector_cond);
+
+ mysql_mutex_lock(&ndbcluster_mutex);
+ /* ndb_share reference binlog free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share, TRUE);
+ if (is_remote_change && share && share->state != NSS_DROPPED)
+ {
+ DBUG_PRINT("info", ("remote change"));
+ share->state= NSS_DROPPED;
+ if (share->use_count != 1)
+ {
+ /* open handler holding reference */
+ /* wait with freeing create ndb_share to below */
+ do_close_cached_tables= TRUE;
+ }
+ else
+ {
+ /* ndb_share reference create free */
+ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share, TRUE);
+ share= 0;
+ }
+ }
+ else
+ share= 0;
+ mysql_mutex_unlock(&ndbcluster_mutex);
+
+ pOp->setCustomData(0);
+
+ mysql_mutex_lock(&injector_mutex);
+ ndb->dropEventOperation(pOp);
+ pOp= 0;
+ mysql_mutex_unlock(&injector_mutex);
+
+ if (do_close_cached_tables)
+ {
+ TABLE_LIST table_list;
+ bzero((char*) &table_list,sizeof(table_list));
+ table_list.db= (char *)dbname;
+ table_list.alias= table_list.table_name= (char *)tabname;
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
+ /* ndb_share reference create free */
+ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+ DBUG_RETURN(0);
+}
+
+static void ndb_binlog_query(THD *thd, Cluster_schema *schema)
+{
+ if (schema->any_value & NDB_ANYVALUE_RESERVED)
+ {
+ if (schema->any_value != NDB_ANYVALUE_FOR_NOLOGGING)
+ sql_print_warning("NDB: unknown value for binlog signalling 0x%X, "
+ "query not logged",
+ schema->any_value);
+ return;
+ }
+ uint32 thd_server_id_save= thd->server_id;
+ DBUG_ASSERT(sizeof(thd_server_id_save) == sizeof(thd->server_id));
+ char *thd_db_save= thd->db;
+ if (schema->any_value == 0)
+ thd->server_id= ::server_id;
+ else
+ thd->server_id= schema->any_value;
+ thd->db= schema->db;
+ int errcode = query_error_code(thd, thd->killed == NOT_KILLED);
+ thd->binlog_query(THD::STMT_QUERY_TYPE, schema->query,
+ schema->query_length, FALSE, TRUE,
+ schema->name[0] == 0 || thd->db[0] == 0,
+ errcode);
+ thd->server_id= thd_server_id_save;
+ thd->db= thd_db_save;
+}
+
+static int
+ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
+ NdbEventOperation *pOp,
+ List<Cluster_schema>
+ *post_epoch_log_list,
+ List<Cluster_schema>
+ *post_epoch_unlock_list,
+ MEM_ROOT *mem_root)
+{
+ DBUG_ENTER("ndb_binlog_thread_handle_schema_event");
+ NDB_SHARE *tmp_share= (NDB_SHARE *)pOp->getCustomData();
+ if (tmp_share && ndb_schema_share == tmp_share)
+ {
+ NDBEVENT::TableEvent ev_type= pOp->getEventType();
+ DBUG_PRINT("enter", ("%s.%s ev_type: %d",
+ tmp_share->db, tmp_share->table_name, ev_type));
+ if (ev_type == NDBEVENT::TE_UPDATE ||
+ ev_type == NDBEVENT::TE_INSERT)
+ {
+ Cluster_schema *schema= (Cluster_schema *)
+ sql_alloc(sizeof(Cluster_schema));
+ MY_BITMAP slock;
+ my_bitmap_init(&slock, schema->slock, 8*SCHEMA_SLOCK_SIZE, FALSE);
+ uint node_id= g_ndb_cluster_connection->node_id();
+ {
+ ndbcluster_get_schema(tmp_share, schema);
+ schema->any_value= pOp->getAnyValue();
+ }
+ enum SCHEMA_OP_TYPE schema_type= (enum SCHEMA_OP_TYPE)schema->type;
+ DBUG_PRINT("info",
+ ("%s.%s: log query_length: %d query: '%s' type: %d",
+ schema->db, schema->name,
+ schema->query_length, schema->query,
+ schema_type));
+ if (schema_type == SOT_CLEAR_SLOCK)
+ {
+ /*
+ handle slock after epoch is completed to ensure that
+ schema events get inserted in the binlog after any data
+ events
+ */
+ post_epoch_log_list->push_back(schema, mem_root);
+ DBUG_RETURN(0);
+ }
+ if (schema->node_id != node_id)
+ {
+ int log_query= 0, post_epoch_unlock= 0;
+ switch (schema_type)
+ {
+ case SOT_DROP_TABLE:
+ // fall through
+ case SOT_RENAME_TABLE:
+ // fall through
+ case SOT_RENAME_TABLE_NEW:
+ // fall through
+ case SOT_ALTER_TABLE:
+ post_epoch_log_list->push_back(schema, mem_root);
+ /* acknowledge this query _after_ epoch completion */
+ post_epoch_unlock= 1;
+ break;
+ case SOT_TRUNCATE_TABLE:
+ {
+ char key[FN_REFLEN + 1];
+ build_table_filename(key, sizeof(key) - 1,
+ schema->db, schema->name, "", 0);
+ /* ndb_share reference temporary, free below */
+ NDB_SHARE *share= get_share(key, 0, FALSE, FALSE);
+ if (share)
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ }
+ // invalidation already handled by binlog thread
+ if (!share || !share->op)
+ {
+ {
+ injector_ndb->setDatabaseName(schema->db);
+ Ndb_table_guard ndbtab_g(injector_ndb->getDictionary(),
+ schema->name);
+ ndbtab_g.invalidate();
+ }
+ TABLE_LIST table_list;
+ bzero((char*) &table_list,sizeof(table_list));
+ table_list.db= schema->db;
+ table_list.alias= table_list.table_name= schema->name;
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
+ }
+ /* ndb_share reference temporary free */
+ if (share)
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ }
+ }
+ // fall through
+ case SOT_CREATE_TABLE:
+ if (ndbcluster_check_if_local_table(schema->db, schema->name))
+ {
+ DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'",
+ schema->db, schema->name));
+ sql_print_error("NDB Binlog: Skipping locally defined table '%s.%s' from "
+ "binlog schema event '%s' from node %d. ",
+ schema->db, schema->name, schema->query,
+ schema->node_id);
+ }
+ else if (ndb_create_table_from_engine(thd, schema->db, schema->name))
+ {
+ print_could_not_discover_error(thd, schema);
+ }
+ log_query= 1;
+ break;
+ case SOT_DROP_DB:
+ /* Drop the database locally if it only contains ndb tables */
+ if (! ndbcluster_check_if_local_tables_in_db(thd, schema->db))
+ {
+ const int no_print_error[1]= {0};
+ run_query(thd, schema->query,
+ schema->query + schema->query_length,
+ no_print_error, /* print error */
+ TRUE); /* don't binlog the query */
+ /* binlog dropping database after any table operations */
+ post_epoch_log_list->push_back(schema, mem_root);
+ /* acknowledge this query _after_ epoch completion */
+ post_epoch_unlock= 1;
+ }
+ else
+ {
+ /* Database contained local tables, leave it */
+ sql_print_error("NDB Binlog: Skipping drop database '%s' since it contained local tables "
+ "binlog schema event '%s' from node %d. ",
+ schema->db, schema->query,
+ schema->node_id);
+ log_query= 1;
+ }
+ break;
+ case SOT_CREATE_DB:
+ /* fall through */
+ case SOT_ALTER_DB:
+ {
+ const int no_print_error[1]= {0};
+ run_query(thd, schema->query,
+ schema->query + schema->query_length,
+ no_print_error, /* print error */
+ TRUE); /* don't binlog the query */
+ log_query= 1;
+ break;
+ }
+ case SOT_TABLESPACE:
+ case SOT_LOGFILE_GROUP:
+ log_query= 1;
+ break;
+ case SOT_CLEAR_SLOCK:
+ abort();
+ }
+ if (log_query && ndb_binlog_running)
+ ndb_binlog_query(thd, schema);
+ /* signal that schema operation has been handled */
+ DBUG_DUMP("slock", (uchar*) schema->slock, schema->slock_length);
+ if (bitmap_is_set(&slock, node_id))
+ {
+ if (post_epoch_unlock)
+ post_epoch_unlock_list->push_back(schema, mem_root);
+ else
+ ndbcluster_update_slock(thd, schema->db, schema->name);
+ }
+ }
+ DBUG_RETURN(0);
+ }
+ /*
+ the normal case of UPDATE/INSERT has already been handled
+ */
+ switch (ev_type)
+ {
+ case NDBEVENT::TE_DELETE:
+ // skip
+ break;
+ case NDBEVENT::TE_CLUSTER_FAILURE:
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: cluster failure for %s at epoch %u.",
+ ndb_schema_share->key, (unsigned) pOp->getGCI());
+ // fall through
+ case NDBEVENT::TE_DROP:
+ if (opt_ndb_extra_logging &&
+ ndb_binlog_tables_inited && ndb_binlog_running)
+ sql_print_information("NDB Binlog: ndb tables initially "
+ "read only on reconnect.");
+
+ /* begin protect ndb_schema_share */
+ mysql_mutex_lock(&ndb_schema_share_mutex);
+ /* ndb_share reference binlog extra free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u",
+ ndb_schema_share->key,
+ ndb_schema_share->use_count));
+ free_share(&ndb_schema_share);
+ ndb_schema_share= 0;
+ ndb_binlog_tables_inited= 0;
+ mysql_mutex_unlock(&ndb_schema_share_mutex);
+ /* end protect ndb_schema_share */
+
+ close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
+ // fall through
+ case NDBEVENT::TE_ALTER:
+ ndb_handle_schema_change(thd, ndb, pOp, tmp_share);
+ break;
+ case NDBEVENT::TE_NODE_FAILURE:
+ {
+ uint8 node_id= g_node_id_map[pOp->getNdbdNodeId()];
+ DBUG_ASSERT(node_id != 0xFF);
+ mysql_mutex_lock(&tmp_share->mutex);
+ bitmap_clear_all(&tmp_share->subscriber_bitmap[node_id]);
+ DBUG_PRINT("info",("NODE_FAILURE UNSUBSCRIBE[%d]", node_id));
+ if (opt_ndb_extra_logging)
+ {
+ sql_print_information("NDB Binlog: Node: %d, down,"
+ " Subscriber bitmask %x%x",
+ pOp->getNdbdNodeId(),
+ tmp_share->subscriber_bitmap[node_id].bitmap[1],
+ tmp_share->subscriber_bitmap[node_id].bitmap[0]);
+ }
+ mysql_mutex_unlock(&tmp_share->mutex);
+ mysql_cond_signal(&injector_cond);
+ break;
+ }
+ case NDBEVENT::TE_SUBSCRIBE:
+ {
+ uint8 node_id= g_node_id_map[pOp->getNdbdNodeId()];
+ uint8 req_id= pOp->getReqNodeId();
+ DBUG_ASSERT(req_id != 0 && node_id != 0xFF);
+ mysql_mutex_lock(&tmp_share->mutex);
+ bitmap_set_bit(&tmp_share->subscriber_bitmap[node_id], req_id);
+ DBUG_PRINT("info",("SUBSCRIBE[%d] %d", node_id, req_id));
+ if (opt_ndb_extra_logging)
+ {
+ sql_print_information("NDB Binlog: Node: %d, subscribe from node %d,"
+ " Subscriber bitmask %x%x",
+ pOp->getNdbdNodeId(),
+ req_id,
+ tmp_share->subscriber_bitmap[node_id].bitmap[1],
+ tmp_share->subscriber_bitmap[node_id].bitmap[0]);
+ }
+ mysql_mutex_unlock(&tmp_share->mutex);
+ mysql_cond_signal(&injector_cond);
+ break;
+ }
+ case NDBEVENT::TE_UNSUBSCRIBE:
+ {
+ uint8 node_id= g_node_id_map[pOp->getNdbdNodeId()];
+ uint8 req_id= pOp->getReqNodeId();
+ DBUG_ASSERT(req_id != 0 && node_id != 0xFF);
+ mysql_mutex_lock(&tmp_share->mutex);
+ bitmap_clear_bit(&tmp_share->subscriber_bitmap[node_id], req_id);
+ DBUG_PRINT("info",("UNSUBSCRIBE[%d] %d", node_id, req_id));
+ if (opt_ndb_extra_logging)
+ {
+ sql_print_information("NDB Binlog: Node: %d, unsubscribe from node %d,"
+ " Subscriber bitmask %x%x",
+ pOp->getNdbdNodeId(),
+ req_id,
+ tmp_share->subscriber_bitmap[node_id].bitmap[1],
+ tmp_share->subscriber_bitmap[node_id].bitmap[0]);
+ }
+ mysql_mutex_unlock(&tmp_share->mutex);
+ mysql_cond_signal(&injector_cond);
+ break;
+ }
+ default:
+ sql_print_error("NDB Binlog: unknown non data event %d for %s. "
+ "Ignoring...", (unsigned) ev_type, tmp_share->key);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+/*
+ process any operations that should be done after
+ the epoch is complete
+*/
+static void
+ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd,
+ List<Cluster_schema>
+ *post_epoch_log_list,
+ List<Cluster_schema>
+ *post_epoch_unlock_list)
+{
+ if (post_epoch_log_list->elements == 0)
+ return;
+ DBUG_ENTER("ndb_binlog_thread_handle_schema_event_post_epoch");
+ Cluster_schema *schema;
+ while ((schema= post_epoch_log_list->pop()))
+ {
+ DBUG_PRINT("info",
+ ("%s.%s: log query_length: %d query: '%s' type: %d",
+ schema->db, schema->name,
+ schema->query_length, schema->query,
+ schema->type));
+ int log_query= 0;
+ {
+ enum SCHEMA_OP_TYPE schema_type= (enum SCHEMA_OP_TYPE)schema->type;
+ char key[FN_REFLEN + 1];
+ build_table_filename(key, sizeof(key) - 1, schema->db, schema->name, "", 0);
+ if (schema_type == SOT_CLEAR_SLOCK)
+ {
+ mysql_mutex_lock(&ndbcluster_mutex);
+ NDB_SCHEMA_OBJECT *ndb_schema_object=
+ (NDB_SCHEMA_OBJECT*) my_hash_search(&ndb_schema_objects,
+ (uchar*) key, strlen(key));
+ if (ndb_schema_object)
+ {
+ mysql_mutex_lock(&ndb_schema_object->mutex);
+ memcpy(ndb_schema_object->slock, schema->slock,
+ sizeof(ndb_schema_object->slock));
+ DBUG_DUMP("ndb_schema_object->slock_bitmap.bitmap",
+ (uchar*)ndb_schema_object->slock_bitmap.bitmap,
+ no_bytes_in_map(&ndb_schema_object->slock_bitmap));
+ mysql_mutex_unlock(&ndb_schema_object->mutex);
+ mysql_cond_signal(&injector_cond);
+ }
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ continue;
+ }
+ /* ndb_share reference temporary, free below */
+ NDB_SHARE *share= get_share(key, 0, FALSE, FALSE);
+ if (share)
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u",
+ share->key, share->use_count));
+ }
+ switch (schema_type)
+ {
+ case SOT_DROP_DB:
+ log_query= 1;
+ break;
+ case SOT_DROP_TABLE:
+ log_query= 1;
+ // invalidation already handled by binlog thread
+ if (share && share->op)
+ {
+ break;
+ }
+ // fall through
+ case SOT_RENAME_TABLE:
+ // fall through
+ case SOT_ALTER_TABLE:
+ // invalidation already handled by binlog thread
+ if (!share || !share->op)
+ {
+ {
+ injector_ndb->setDatabaseName(schema->db);
+ Ndb_table_guard ndbtab_g(injector_ndb->getDictionary(),
+ schema->name);
+ ndbtab_g.invalidate();
+ }
+ TABLE_LIST table_list;
+ bzero((char*) &table_list,sizeof(table_list));
+ table_list.db= schema->db;
+ table_list.alias= table_list.table_name= schema->name;
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
+ }
+ if (schema_type != SOT_ALTER_TABLE)
+ break;
+ // fall through
+ case SOT_RENAME_TABLE_NEW:
+ log_query= 1;
+ if (ndb_binlog_running && (!share || !share->op))
+ {
+ /*
+ we need to free any share here as command below
+ may need to call handle_trailing_share
+ */
+ if (share)
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ share= 0;
+ }
+ if (ndbcluster_check_if_local_table(schema->db, schema->name))
+ {
+ DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'",
+ schema->db, schema->name));
+ sql_print_error("NDB Binlog: Skipping locally defined table '%s.%s' from "
+ "binlog schema event '%s' from node %d. ",
+ schema->db, schema->name, schema->query,
+ schema->node_id);
+ }
+ else if (ndb_create_table_from_engine(thd, schema->db, schema->name))
+ {
+ print_could_not_discover_error(thd, schema);
+ }
+ }
+ break;
+ default:
+ DBUG_ASSERT(FALSE);
+ }
+ if (share)
+ {
+ /* ndb_share reference temporary free */
+ DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ share= 0;
+ }
+ }
+ if (ndb_binlog_running && log_query)
+ ndb_binlog_query(thd, schema);
+ }
+ while ((schema= post_epoch_unlock_list->pop()))
+ {
+ ndbcluster_update_slock(thd, schema->db, schema->name);
+ }
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Timer class for doing performance measurements
+*/
+
+/*********************************************************************
+ Internal helper functions for handeling of the cluster replication tables
+ - ndb_binlog_index
+ - ndb_apply_status
+*********************************************************************/
+
+/*
+ struct to hold the data to be inserted into the
+ ndb_binlog_index table
+*/
+struct ndb_binlog_index_row {
+ ulonglong gci;
+ const char *master_log_file;
+ ulonglong master_log_pos;
+ ulonglong n_inserts;
+ ulonglong n_updates;
+ ulonglong n_deletes;
+ ulonglong n_schemaops;
+};
+
+/*
+ Open the ndb_binlog_index table
+*/
+static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index)
+{
+ static char repdb[]= NDB_REP_DB;
+ static char reptable[]= NDB_REP_TABLE;
+ const char *save_proc_info= thd->proc_info;
+ TABLE_LIST *tables= &binlog_tables;
+
+ tables->init_one_table(repdb, strlen(repdb), reptable, strlen(reptable),
+ reptable, TL_WRITE);
+ thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE;
+
+ tables->required_type= FRMTYPE_TABLE;
+ thd->clear_error();
+ if (open_and_lock_tables(thd, tables, FALSE, 0))
+ {
+ if (thd->killed)
+ sql_print_error("NDB Binlog: Opening ndb_binlog_index: killed");
+ else
+ sql_print_error("NDB Binlog: Opening ndb_binlog_index: %d, '%s'",
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->proc_info= save_proc_info;
+ return -1;
+ }
+ *ndb_binlog_index= tables->table;
+ thd->proc_info= save_proc_info;
+ (*ndb_binlog_index)->use_all_columns();
+ return 0;
+}
+
+
+/*
+ Insert one row in the ndb_binlog_index
+*/
+
+int ndb_add_ndb_binlog_index(THD *thd, void *_row)
+{
+ ndb_binlog_index_row &row= *(ndb_binlog_index_row *) _row;
+ int error= 0;
+ /*
+ Turn of binlogging to prevent the table changes to be written to
+ the binary log.
+ */
+ ulong saved_options= thd->variables.option_bits;
+ thd->variables.option_bits&= ~OPTION_BIN_LOG;
+
+ if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index))
+ {
+ sql_print_error("NDB Binlog: Unable to lock table ndb_binlog_index");
+ error= -1;
+ goto add_ndb_binlog_index_err;
+ }
+
+ /*
+ Intialize ndb_binlog_index->record[0]
+ */
+ empty_record(ndb_binlog_index);
+
+ ndb_binlog_index->field[0]->store(row.master_log_pos);
+ ndb_binlog_index->field[1]->store(row.master_log_file,
+ strlen(row.master_log_file),
+ &my_charset_bin);
+ ndb_binlog_index->field[2]->store(row.gci);
+ ndb_binlog_index->field[3]->store(row.n_inserts);
+ ndb_binlog_index->field[4]->store(row.n_updates);
+ ndb_binlog_index->field[5]->store(row.n_deletes);
+ ndb_binlog_index->field[6]->store(row.n_schemaops);
+
+ if ((error= ndb_binlog_index->file->ha_write_row(ndb_binlog_index->record[0])))
+ {
+ sql_print_error("NDB Binlog: Writing row to ndb_binlog_index: %d", error);
+ error= -1;
+ goto add_ndb_binlog_index_err;
+ }
+
+add_ndb_binlog_index_err:
+ thd->get_stmt_da()->set_overwrite_status(true);
+ thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ close_thread_tables(thd);
+ /*
+ There should be no need for rolling back transaction due to deadlock
+ (since ndb_binlog_index is non transactional).
+ */
+ DBUG_ASSERT(! thd->transaction_rollback_request);
+
+ thd->mdl_context.release_transactional_locks();
+ ndb_binlog_index= 0;
+ thd->variables.option_bits= saved_options;
+ return error;
+}
+
+/*********************************************************************
+ Functions for start, stop, wait for ndbcluster binlog thread
+*********************************************************************/
+
+enum Binlog_thread_state
+{
+ BCCC_running= 0,
+ BCCC_exit= 1,
+ BCCC_restart= 2
+};
+
+static enum Binlog_thread_state do_ndbcluster_binlog_close_connection= BCCC_restart;
+
+int ndbcluster_binlog_start()
+{
+ DBUG_ENTER("ndbcluster_binlog_start");
+
+ if (::server_id == 0)
+ {
+ sql_print_warning("NDB: server id set to zero will cause any other mysqld "
+ "with bin log to log with wrong server id");
+ }
+ else if (::server_id & 0x1 << 31)
+ {
+ sql_print_error("NDB: server id's with high bit set is reserved for internal "
+ "purposes");
+ DBUG_RETURN(-1);
+ }
+
+ mysql_mutex_init(key_injector_mutex, &injector_mutex, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_injector_cond, &injector_cond, NULL);
+ mysql_mutex_init(key_ndb_schema_share_mutex,
+ &ndb_schema_share_mutex, MY_MUTEX_INIT_FAST);
+
+ /* Create injector thread */
+ if (mysql_thread_create(key_thread_ndb_binlog,
+ &ndb_binlog_thread, &connection_attrib,
+ ndb_binlog_thread_func, 0))
+ {
+ DBUG_PRINT("error", ("Could not create ndb injector thread"));
+ mysql_cond_destroy(&injector_cond);
+ mysql_mutex_destroy(&injector_mutex);
+ DBUG_RETURN(-1);
+ }
+
+ ndbcluster_binlog_inited= 1;
+
+ /* Wait for the injector thread to start */
+ mysql_mutex_lock(&injector_mutex);
+ while (!ndb_binlog_thread_running)
+ mysql_cond_wait(&injector_cond, &injector_mutex);
+ mysql_mutex_unlock(&injector_mutex);
+
+ if (ndb_binlog_thread_running < 0)
+ DBUG_RETURN(-1);
+
+ DBUG_RETURN(0);
+}
+
+
+/**************************************************************
+ Internal helper functions for creating/dropping ndb events
+ used by the client sql threads
+**************************************************************/
+void
+ndb_rep_event_name(String *event_name,const char *db, const char *tbl)
+{
+ event_name->set_ascii("REPL$", 5);
+ event_name->append(db);
+ if (tbl)
+ {
+ event_name->append('/');
+ event_name->append(tbl);
+ }
+}
+
+bool
+ndbcluster_check_if_local_table(const char *dbname, const char *tabname)
+{
+ char key[FN_REFLEN + 1];
+ char ndb_file[FN_REFLEN + 1];
+
+ DBUG_ENTER("ndbcluster_check_if_local_table");
+ build_table_filename(key, FN_LEN-1, dbname, tabname, reg_ext, 0);
+ build_table_filename(ndb_file, FN_LEN-1, dbname, tabname, ha_ndb_ext, 0);
+ /* Check that any defined table is an ndb table */
+ DBUG_PRINT("info", ("Looking for file %s and %s", key, ndb_file));
+ if ((! my_access(key, F_OK)) && my_access(ndb_file, F_OK))
+ {
+ DBUG_PRINT("info", ("table file %s not on disk, local table", ndb_file));
+
+
+ DBUG_RETURN(true);
+ }
+
+ DBUG_RETURN(false);
+}
+
+bool
+ndbcluster_check_if_local_tables_in_db(THD *thd, const char *dbname)
+{
+ DBUG_ENTER("ndbcluster_check_if_local_tables_in_db");
+ DBUG_PRINT("info", ("Looking for files in directory %s", dbname));
+ LEX_STRING *tabname;
+ List<LEX_STRING> files;
+ char path[FN_REFLEN + 1];
+
+ build_table_filename(path, sizeof(path) - 1, dbname, "", "", 0);
+ if (find_files(thd, &files, dbname, path, NullS, 0) != FIND_FILES_OK)
+ {
+ DBUG_PRINT("info", ("Failed to find files"));
+ DBUG_RETURN(true);
+ }
+ DBUG_PRINT("info",("found: %d files", files.elements));
+ while ((tabname= files.pop()))
+ {
+ DBUG_PRINT("info", ("Found table %s", tabname->str));
+ if (ndbcluster_check_if_local_table(dbname, tabname->str))
+ DBUG_RETURN(true);
+ }
+
+ DBUG_RETURN(false);
+}
+
+/*
+ Common function for setting up everything for logging a table at
+ create/discover.
+*/
+int ndbcluster_create_binlog_setup(Ndb *ndb, const char *key,
+ uint key_len,
+ const char *db,
+ const char *table_name,
+ my_bool share_may_exist)
+{
+ int do_event_op= ndb_binlog_running;
+ DBUG_ENTER("ndbcluster_create_binlog_setup");
+ DBUG_PRINT("enter",("key: %s key_len: %d %s.%s share_may_exist: %d",
+ key, key_len, db, table_name, share_may_exist));
+ DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(table_name));
+ DBUG_ASSERT(strlen(key) == key_len);
+
+ mysql_mutex_lock(&ndbcluster_mutex);
+
+ /* Handle any trailing share */
+ NDB_SHARE *share= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables,
+ (uchar*) key, key_len);
+
+ if (share && share_may_exist)
+ {
+ if (share->flags & NSF_NO_BINLOG ||
+ share->op != 0 ||
+ share->op_old != 0)
+ {
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(0); // replication already setup, or should not
+ }
+ }
+
+ if (share)
+ {
+ if (share->op || share->op_old)
+ {
+ my_errno= HA_ERR_TABLE_EXIST;
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(1);
+ }
+ if (!share_may_exist || share->connect_count !=
+ g_ndb_cluster_connection->get_connect_count())
+ {
+ handle_trailing_share(share);
+ share= NULL;
+ }
+ }
+
+ /* Create share which is needed to hold replication information */
+ if (share)
+ {
+ /* ndb_share reference create */
+ ++share->use_count;
+ DBUG_PRINT("NDB_SHARE", ("%s create use_count: %u",
+ share->key, share->use_count));
+ }
+ /* ndb_share reference create */
+ else if (!(share= get_share(key, 0, TRUE, TRUE)))
+ {
+ sql_print_error("NDB Binlog: "
+ "allocating table share for %s failed", key);
+ }
+ else
+ {
+ DBUG_PRINT("NDB_SHARE", ("%s create use_count: %u",
+ share->key, share->use_count));
+ }
+
+ if (!ndb_schema_share &&
+ strcmp(share->db, NDB_REP_DB) == 0 &&
+ strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0)
+ do_event_op= 1;
+ else if (!ndb_apply_status_share &&
+ strcmp(share->db, NDB_REP_DB) == 0 &&
+ strcmp(share->table_name, NDB_APPLY_TABLE) == 0)
+ do_event_op= 1;
+
+ if (!do_event_op)
+ {
+ share->flags|= NSF_NO_BINLOG;
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(0);
+ }
+ mysql_mutex_unlock(&ndbcluster_mutex);
+
+ while (share && !IS_TMP_PREFIX(table_name))
+ {
+ /*
+ ToDo make sanity check of share so that the table is actually the same
+ I.e. we need to do open file from frm in this case
+ Currently awaiting this to be fixed in the 4.1 tree in the general
+ case
+ */
+
+ /* Create the event in NDB */
+ ndb->setDatabaseName(db);
+
+ NDBDICT *dict= ndb->getDictionary();
+ Ndb_table_guard ndbtab_g(dict, table_name);
+ const NDBTAB *ndbtab= ndbtab_g.get_table();
+ if (ndbtab == 0)
+ {
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: Failed to get table %s from ndb: "
+ "%s, %d", key, dict->getNdbError().message,
+ dict->getNdbError().code);
+ break; // error
+ }
+ String event_name(INJECTOR_EVENT_LEN);
+ ndb_rep_event_name(&event_name, db, table_name);
+ /*
+ event should have been created by someone else,
+ but let's make sure, and create if it doesn't exist
+ */
+ const NDBEVENT *ev= dict->getEvent(event_name.c_ptr());
+ if (!ev)
+ {
+ if (ndbcluster_create_event(ndb, ndbtab, event_name.c_ptr(), share))
+ {
+ sql_print_error("NDB Binlog: "
+ "FAILED CREATE (DISCOVER) TABLE Event: %s",
+ event_name.c_ptr());
+ break; // error
+ }
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: "
+ "CREATE (DISCOVER) TABLE Event: %s",
+ event_name.c_ptr());
+ }
+ else
+ {
+ delete ev;
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: DISCOVER TABLE Event: %s",
+ event_name.c_ptr());
+ }
+
+ /*
+ create the event operations for receiving logging events
+ */
+ if (ndbcluster_create_event_ops(share, ndbtab, event_name.c_ptr()))
+ {
+ sql_print_error("NDB Binlog:"
+ "FAILED CREATE (DISCOVER) EVENT OPERATIONS Event: %s",
+ event_name.c_ptr());
+ /* a warning has been issued to the client */
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(-1);
+}
+
+int
+ndbcluster_create_event(Ndb *ndb, const NDBTAB *ndbtab,
+ const char *event_name, NDB_SHARE *share,
+ int push_warning)
+{
+ THD *thd= current_thd;
+ DBUG_ENTER("ndbcluster_create_event");
+ DBUG_PRINT("info", ("table=%s version=%d event=%s share=%s",
+ ndbtab->getName(), ndbtab->getObjectVersion(),
+ event_name, share ? share->key : "(nil)"));
+ DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(ndbtab->getName()));
+ if (!share)
+ {
+ DBUG_PRINT("info", ("share == NULL"));
+ DBUG_RETURN(0);
+ }
+ if (share->flags & NSF_NO_BINLOG)
+ {
+ DBUG_PRINT("info", ("share->flags & NSF_NO_BINLOG, flags: %x %d",
+ share->flags, share->flags & NSF_NO_BINLOG));
+ DBUG_RETURN(0);
+ }
+
+ NDBDICT *dict= ndb->getDictionary();
+ NDBEVENT my_event(event_name);
+ my_event.setTable(*ndbtab);
+ my_event.addTableEvent(NDBEVENT::TE_ALL);
+ if (share->flags & NSF_HIDDEN_PK)
+ {
+ if (share->flags & NSF_BLOB_FLAG)
+ {
+ sql_print_error("NDB Binlog: logging of table %s "
+ "with BLOB attribute and no PK is not supported",
+ share->key);
+ if (push_warning)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ILLEGAL_HA_CREATE_OPTION,
+ ER(ER_ILLEGAL_HA_CREATE_OPTION),
+ ndbcluster_hton_name,
+ "Binlog of table with BLOB attribute and no PK");
+
+ share->flags|= NSF_NO_BINLOG;
+ DBUG_RETURN(-1);
+ }
+ /* No primary key, subscribe for all attributes */
+ my_event.setReport(NDBEVENT::ER_ALL);
+ DBUG_PRINT("info", ("subscription all"));
+ }
+ else
+ {
+ if (ndb_schema_share || strcmp(share->db, NDB_REP_DB) ||
+ strcmp(share->table_name, NDB_SCHEMA_TABLE))
+ {
+ my_event.setReport(NDBEVENT::ER_UPDATED);
+ DBUG_PRINT("info", ("subscription only updated"));
+ }
+ else
+ {
+ my_event.setReport((NDBEVENT::EventReport)
+ (NDBEVENT::ER_ALL | NDBEVENT::ER_SUBSCRIBE));
+ DBUG_PRINT("info", ("subscription all and subscribe"));
+ }
+ }
+ if (share->flags & NSF_BLOB_FLAG)
+ my_event.mergeEvents(TRUE);
+
+ /* add all columns to the event */
+ int n_cols= ndbtab->getNoOfColumns();
+ for(int a= 0; a < n_cols; a++)
+ my_event.addEventColumn(a);
+
+ if (dict->createEvent(my_event)) // Add event to database
+ {
+ if (dict->getNdbError().classification != NdbError::SchemaObjectExists)
+ {
+ /*
+ failed, print a warning
+ */
+ if (push_warning > 1)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ dict->getNdbError().code,
+ dict->getNdbError().message, "NDB");
+ sql_print_error("NDB Binlog: Unable to create event in database. "
+ "Event: %s Error Code: %d Message: %s", event_name,
+ dict->getNdbError().code, dict->getNdbError().message);
+ DBUG_RETURN(-1);
+ }
+
+ /*
+ try retrieving the event, if table version/id matches, we will get
+ a valid event. Otherwise we have a trailing event from before
+ */
+ const NDBEVENT *ev;
+ if ((ev= dict->getEvent(event_name)))
+ {
+ delete ev;
+ DBUG_RETURN(0);
+ }
+
+ /*
+ trailing event from before; an error, but try to correct it
+ */
+ if (dict->getNdbError().code == NDB_INVALID_SCHEMA_OBJECT &&
+ dict->dropEvent(my_event.getName()))
+ {
+ if (push_warning > 1)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ dict->getNdbError().code,
+ dict->getNdbError().message, "NDB");
+ sql_print_error("NDB Binlog: Unable to create event in database. "
+ " Attempt to correct with drop failed. "
+ "Event: %s Error Code: %d Message: %s",
+ event_name,
+ dict->getNdbError().code,
+ dict->getNdbError().message);
+ DBUG_RETURN(-1);
+ }
+
+ /*
+ try to add the event again
+ */
+ if (dict->createEvent(my_event))
+ {
+ if (push_warning > 1)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ dict->getNdbError().code,
+ dict->getNdbError().message, "NDB");
+ sql_print_error("NDB Binlog: Unable to create event in database. "
+ " Attempt to correct with drop ok, but create failed. "
+ "Event: %s Error Code: %d Message: %s",
+ event_name,
+ dict->getNdbError().code,
+ dict->getNdbError().message);
+ DBUG_RETURN(-1);
+ }
+#ifdef NDB_BINLOG_EXTRA_WARNINGS
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ 0, "NDB Binlog: Removed trailing event",
+ "NDB");
+#endif
+ }
+
+ DBUG_RETURN(0);
+}
+
+inline int is_ndb_compatible_type(Field *field)
+{
+ return
+ !(field->flags & BLOB_FLAG) &&
+ field->type() != MYSQL_TYPE_BIT &&
+ field->pack_length() != 0;
+}
+
+/*
+ - create eventOperations for receiving log events
+ - setup ndb recattrs for reception of log event data
+ - "start" the event operation
+
+ used at create/discover of tables
+*/
+int
+ndbcluster_create_event_ops(NDB_SHARE *share, const NDBTAB *ndbtab,
+ const char *event_name)
+{
+ THD *thd= current_thd;
+ /*
+ we are in either create table or rename table so table should be
+ locked, hence we can work with the share without locks
+ */
+
+ DBUG_ENTER("ndbcluster_create_event_ops");
+ DBUG_PRINT("enter", ("table: %s event: %s", ndbtab->getName(), event_name));
+ DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(ndbtab->getName()));
+
+ DBUG_ASSERT(share != 0);
+
+ if (share->flags & NSF_NO_BINLOG)
+ {
+ DBUG_PRINT("info", ("share->flags & NSF_NO_BINLOG, flags: %x",
+ share->flags));
+ DBUG_RETURN(0);
+ }
+
+ int do_ndb_schema_share= 0, do_ndb_apply_status_share= 0;
+ if (!ndb_schema_share && strcmp(share->db, NDB_REP_DB) == 0 &&
+ strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0)
+ do_ndb_schema_share= 1;
+ else if (!ndb_apply_status_share && strcmp(share->db, NDB_REP_DB) == 0 &&
+ strcmp(share->table_name, NDB_APPLY_TABLE) == 0)
+ do_ndb_apply_status_share= 1;
+ else if (!binlog_filter->db_ok(share->db) || !ndb_binlog_running)
+ {
+ share->flags|= NSF_NO_BINLOG;
+ DBUG_RETURN(0);
+ }
+
+ if (share->op)
+ {
+ assert(share->op->getCustomData() == (void *) share);
+
+ DBUG_ASSERT(share->use_count > 1);
+ sql_print_error("NDB Binlog: discover reusing old ev op");
+ /* ndb_share reference ToDo free */
+ DBUG_PRINT("NDB_SHARE", ("%s ToDo free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share); // old event op already has reference
+ DBUG_RETURN(0);
+ }
+
+ TABLE *table= share->table;
+
+ int retries= 100;
+ /*
+ 100 milliseconds, temporary error on schema operation can
+ take some time to be resolved
+ */
+ int retry_sleep= 100;
+ while (1)
+ {
+ mysql_mutex_lock(&injector_mutex);
+ Ndb *ndb= injector_ndb;
+ if (do_ndb_schema_share)
+ ndb= schema_ndb;
+
+ if (ndb == 0)
+ {
+ mysql_mutex_unlock(&injector_mutex);
+ DBUG_RETURN(-1);
+ }
+
+ NdbEventOperation* op;
+ if (do_ndb_schema_share)
+ op= ndb->createEventOperation(event_name);
+ else
+ {
+ // set injector_ndb database/schema from table internal name
+ int ret= ndb->setDatabaseAndSchemaName(ndbtab);
+ assert(ret == 0);
+ op= ndb->createEventOperation(event_name);
+ // reset to catch errors
+ ndb->setDatabaseName("");
+ }
+ if (!op)
+ {
+ sql_print_error("NDB Binlog: Creating NdbEventOperation failed for"
+ " %s",event_name);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ ndb->getNdbError().code,
+ ndb->getNdbError().message,
+ "NDB");
+ mysql_mutex_unlock(&injector_mutex);
+ DBUG_RETURN(-1);
+ }
+
+ if (share->flags & NSF_BLOB_FLAG)
+ op->mergeEvents(TRUE); // currently not inherited from event
+
+ DBUG_PRINT("info", ("share->ndb_value[0]: 0x%lx share->ndb_value[1]: 0x%lx",
+ (long) share->ndb_value[0],
+ (long) share->ndb_value[1]));
+ int n_columns= ndbtab->getNoOfColumns();
+ int n_fields= table ? table->s->fields : 0; // XXX ???
+ for (int j= 0; j < n_columns; j++)
+ {
+ const char *col_name= ndbtab->getColumn(j)->getName();
+ NdbValue attr0, attr1;
+ if (j < n_fields)
+ {
+ Field *f= share->table->field[j];
+ if (is_ndb_compatible_type(f))
+ {
+ DBUG_PRINT("info", ("%s compatible", col_name));
+ attr0.rec= op->getValue(col_name, (char*) f->ptr);
+ attr1.rec= op->getPreValue(col_name,
+ (f->ptr - share->table->record[0]) +
+ (char*) share->table->record[1]);
+ }
+ else if (! (f->flags & BLOB_FLAG))
+ {
+ DBUG_PRINT("info", ("%s non compatible", col_name));
+ attr0.rec= op->getValue(col_name);
+ attr1.rec= op->getPreValue(col_name);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("%s blob", col_name));
+ DBUG_ASSERT(share->flags & NSF_BLOB_FLAG);
+ attr0.blob= op->getBlobHandle(col_name);
+ attr1.blob= op->getPreBlobHandle(col_name);
+ if (attr0.blob == NULL || attr1.blob == NULL)
+ {
+ sql_print_error("NDB Binlog: Creating NdbEventOperation"
+ " blob field %u handles failed (code=%d) for %s",
+ j, op->getNdbError().code, event_name);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ op->getNdbError().code,
+ op->getNdbError().message,
+ "NDB");
+ ndb->dropEventOperation(op);
+ mysql_mutex_unlock(&injector_mutex);
+ DBUG_RETURN(-1);
+ }
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info", ("%s hidden key", col_name));
+ attr0.rec= op->getValue(col_name);
+ attr1.rec= op->getPreValue(col_name);
+ }
+ share->ndb_value[0][j].ptr= attr0.ptr;
+ share->ndb_value[1][j].ptr= attr1.ptr;
+ DBUG_PRINT("info", ("&share->ndb_value[0][%d]: 0x%lx "
+ "share->ndb_value[0][%d]: 0x%lx",
+ j, (long) &share->ndb_value[0][j],
+ j, (long) attr0.ptr));
+ DBUG_PRINT("info", ("&share->ndb_value[1][%d]: 0x%lx "
+ "share->ndb_value[1][%d]: 0x%lx",
+ j, (long) &share->ndb_value[0][j],
+ j, (long) attr1.ptr));
+ }
+ op->setCustomData((void *) share); // set before execute
+ share->op= op; // assign op in NDB_SHARE
+ if (op->execute())
+ {
+ share->op= NULL;
+ retries--;
+ if (op->getNdbError().status != NdbError::TemporaryError &&
+ op->getNdbError().code != 1407)
+ retries= 0;
+ if (retries == 0)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ op->getNdbError().code, op->getNdbError().message,
+ "NDB");
+ sql_print_error("NDB Binlog: ndbevent->execute failed for %s; %d %s",
+ event_name,
+ op->getNdbError().code, op->getNdbError().message);
+ }
+ ndb->dropEventOperation(op);
+ mysql_mutex_unlock(&injector_mutex);
+ if (retries)
+ {
+ my_sleep(retry_sleep);
+ continue;
+ }
+ DBUG_RETURN(-1);
+ }
+ mysql_mutex_unlock(&injector_mutex);
+ break;
+ }
+
+ /* ndb_share reference binlog */
+ get_share(share);
+ DBUG_PRINT("NDB_SHARE", ("%s binlog use_count: %u",
+ share->key, share->use_count));
+ if (do_ndb_apply_status_share)
+ {
+ /* ndb_share reference binlog extra */
+ ndb_apply_status_share= get_share(share);
+ DBUG_PRINT("NDB_SHARE", ("%s binlog extra use_count: %u",
+ share->key, share->use_count));
+ mysql_cond_signal(&injector_cond);
+ }
+ else if (do_ndb_schema_share)
+ {
+ /* ndb_share reference binlog extra */
+ ndb_schema_share= get_share(share);
+ DBUG_PRINT("NDB_SHARE", ("%s binlog extra use_count: %u",
+ share->key, share->use_count));
+ mysql_cond_signal(&injector_cond);
+ }
+
+ DBUG_PRINT("info",("%s share->op: 0x%lx share->use_count: %u",
+ share->key, (long) share->op, share->use_count));
+
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: logging %s", share->key);
+ DBUG_RETURN(0);
+}
+
+/*
+ when entering the calling thread should have a share lock id share != 0
+ then the injector thread will have one as well, i.e. share->use_count == 0
+ (unless it has already dropped... then share->op == 0)
+*/
+int
+ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name,
+ NDB_SHARE *share, const char *type_str)
+{
+ DBUG_ENTER("ndbcluster_handle_drop_table");
+ THD *thd= current_thd;
+
+ NDBDICT *dict= ndb->getDictionary();
+ if (event_name && dict->dropEvent(event_name))
+ {
+ if (dict->getNdbError().code != 4710)
+ {
+ /* drop event failed for some reason, issue a warning */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRMSG, ER(ER_GET_ERRMSG),
+ dict->getNdbError().code,
+ dict->getNdbError().message, "NDB");
+ /* error is not that the event did not exist */
+ sql_print_error("NDB Binlog: Unable to drop event in database. "
+ "Event: %s Error Code: %d Message: %s",
+ event_name,
+ dict->getNdbError().code,
+ dict->getNdbError().message);
+ /* ToDo; handle error? */
+ if (share && share->op &&
+ share->op->getState() == NdbEventOperation::EO_EXECUTING &&
+ dict->getNdbError().mysql_code != HA_ERR_NO_CONNECTION)
+ {
+ DBUG_ASSERT(FALSE);
+ DBUG_RETURN(-1);
+ }
+ }
+ }
+
+ if (share == 0 || share->op == 0)
+ {
+ DBUG_RETURN(0);
+ }
+
+/*
+ Syncronized drop between client thread and injector thread is
+ neccessary in order to maintain ordering in the binlog,
+ such that the drop occurs _after_ any inserts/updates/deletes.
+
+ The penalty for this is that the drop table becomes slow.
+
+ This wait is however not strictly neccessary to produce a binlog
+ that is usable. However the slave does not currently handle
+ these out of order, thus we are keeping the SYNC_DROP_ defined
+ for now.
+*/
+ const char *save_proc_info= thd->proc_info;
+#define SYNC_DROP_
+#ifdef SYNC_DROP_
+ thd->proc_info= "Syncing ndb table schema operation and binlog";
+ mysql_mutex_lock(&share->mutex);
+ int max_timeout= DEFAULT_SYNC_TIMEOUT;
+ while (share->op)
+ {
+ struct timespec abstime;
+ set_timespec(abstime, 1);
+ int ret= mysql_cond_timedwait(&injector_cond,
+ &share->mutex,
+ &abstime);
+ if (thd->killed ||
+ share->op == 0)
+ break;
+ if (ret)
+ {
+ max_timeout--;
+ if (max_timeout == 0)
+ {
+ sql_print_error("NDB %s: %s timed out. Ignoring...",
+ type_str, share->key);
+ break;
+ }
+ if (opt_ndb_extra_logging)
+ ndb_report_waiting(type_str, max_timeout,
+ type_str, share->key);
+ }
+ }
+ mysql_mutex_unlock(&share->mutex);
+#else
+ mysql_mutex_lock(&share->mutex);
+ share->op_old= share->op;
+ share->op= 0;
+ mysql_mutex_unlock(&share->mutex);
+#endif
+ thd->proc_info= save_proc_info;
+
+ DBUG_RETURN(0);
+}
+
+
+/********************************************************************
+ Internal helper functions for differentd events from the stoarage nodes
+ used by the ndb injector thread
+********************************************************************/
+
+/*
+ Handle error states on events from the storage nodes
+*/
+static int ndb_binlog_thread_handle_error(Ndb *ndb, NdbEventOperation *pOp,
+ ndb_binlog_index_row &row)
+{
+ NDB_SHARE *share= (NDB_SHARE *)pOp->getCustomData();
+ DBUG_ENTER("ndb_binlog_thread_handle_error");
+
+ int overrun= pOp->isOverrun();
+ if (overrun)
+ {
+ /*
+ ToDo: this error should rather clear the ndb_binlog_index...
+ and continue
+ */
+ sql_print_error("NDB Binlog: Overrun in event buffer, "
+ "this means we have dropped events. Cannot "
+ "continue binlog for %s", share->key);
+ pOp->clearError();
+ DBUG_RETURN(-1);
+ }
+
+ if (!pOp->isConsistent())
+ {
+ /*
+ ToDo: this error should rather clear the ndb_binlog_index...
+ and continue
+ */
+ sql_print_error("NDB Binlog: Not Consistent. Cannot "
+ "continue binlog for %s. Error code: %d"
+ " Message: %s", share->key,
+ pOp->getNdbError().code,
+ pOp->getNdbError().message);
+ pOp->clearError();
+ DBUG_RETURN(-1);
+ }
+ sql_print_error("NDB Binlog: unhandled error %d for table %s",
+ pOp->hasError(), share->key);
+ pOp->clearError();
+ DBUG_RETURN(0);
+}
+
+static int
+ndb_binlog_thread_handle_non_data_event(THD *thd, Ndb *ndb,
+ NdbEventOperation *pOp,
+ ndb_binlog_index_row &row)
+{
+ NDB_SHARE *share= (NDB_SHARE *)pOp->getCustomData();
+ NDBEVENT::TableEvent type= pOp->getEventType();
+
+ switch (type)
+ {
+ case NDBEVENT::TE_CLUSTER_FAILURE:
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: cluster failure for %s at epoch %u.",
+ share->key, (unsigned) pOp->getGCI());
+ if (ndb_apply_status_share == share)
+ {
+ if (opt_ndb_extra_logging &&
+ ndb_binlog_tables_inited && ndb_binlog_running)
+ sql_print_information("NDB Binlog: ndb tables initially "
+ "read only on reconnect.");
+ /* ndb_share reference binlog extra free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u",
+ share->key, share->use_count));
+ free_share(&ndb_apply_status_share);
+ ndb_apply_status_share= 0;
+ ndb_binlog_tables_inited= 0;
+ }
+ DBUG_PRINT("error", ("CLUSTER FAILURE EVENT: "
+ "%s received share: 0x%lx op: 0x%lx share op: 0x%lx "
+ "op_old: 0x%lx",
+ share->key, (long) share, (long) pOp,
+ (long) share->op, (long) share->op_old));
+ break;
+ case NDBEVENT::TE_DROP:
+ if (ndb_apply_status_share == share)
+ {
+ if (opt_ndb_extra_logging &&
+ ndb_binlog_tables_inited && ndb_binlog_running)
+ sql_print_information("NDB Binlog: ndb tables initially "
+ "read only on reconnect.");
+ /* ndb_share reference binlog extra free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u",
+ share->key, share->use_count));
+ free_share(&ndb_apply_status_share);
+ ndb_apply_status_share= 0;
+ ndb_binlog_tables_inited= 0;
+ }
+ /* ToDo: remove printout */
+ if (opt_ndb_extra_logging)
+ sql_print_information("NDB Binlog: drop table %s.", share->key);
+ // fall through
+ case NDBEVENT::TE_ALTER:
+ row.n_schemaops++;
+ DBUG_PRINT("info", ("TABLE %s EVENT: %s received share: 0x%lx op: 0x%lx "
+ "share op: 0x%lx op_old: 0x%lx",
+ type == NDBEVENT::TE_DROP ? "DROP" : "ALTER",
+ share->key, (long) share, (long) pOp,
+ (long) share->op, (long) share->op_old));
+ break;
+ case NDBEVENT::TE_NODE_FAILURE:
+ /* fall through */
+ case NDBEVENT::TE_SUBSCRIBE:
+ /* fall through */
+ case NDBEVENT::TE_UNSUBSCRIBE:
+ /* ignore */
+ return 0;
+ default:
+ sql_print_error("NDB Binlog: unknown non data event %d for %s. "
+ "Ignoring...", (unsigned) type, share->key);
+ return 0;
+ }
+
+ ndb_handle_schema_change(thd, ndb, pOp, share);
+ return 0;
+}
+
+/*
+ Handle data events from the storage nodes
+*/
+static int
+ndb_binlog_thread_handle_data_event(Ndb *ndb, NdbEventOperation *pOp,
+ ndb_binlog_index_row &row,
+ injector::transaction &trans)
+{
+ NDB_SHARE *share= (NDB_SHARE*) pOp->getCustomData();
+ if (share == ndb_apply_status_share)
+ return 0;
+
+ uint32 originating_server_id= pOp->getAnyValue();
+ if (originating_server_id == 0)
+ originating_server_id= ::server_id;
+ else if (originating_server_id & NDB_ANYVALUE_RESERVED)
+ {
+ if (originating_server_id != NDB_ANYVALUE_FOR_NOLOGGING)
+ sql_print_warning("NDB: unknown value for binlog signalling 0x%X, "
+ "event not logged",
+ originating_server_id);
+ return 0;
+ }
+ else if (!g_ndb_log_slave_updates)
+ {
+ /*
+ This event comes from a slave applier since it has an originating
+ server id set. Since option to log slave updates is not set, skip it.
+ */
+ return 0;
+ }
+
+ TABLE *table= share->table;
+ DBUG_ASSERT(trans.good());
+ DBUG_ASSERT(table != 0);
+
+ dbug_print_table("table", table);
+
+ TABLE_SHARE *table_s= table->s;
+ uint n_fields= table_s->fields;
+ MY_BITMAP b;
+ /* Potential buffer for the bitmap */
+ uint32 bitbuf[128 / (sizeof(uint32) * 8)];
+ my_bitmap_init(&b, n_fields <= sizeof(bitbuf) * 8 ? bitbuf : NULL,
+ n_fields, FALSE);
+ bitmap_set_all(&b);
+
+ /*
+ row data is already in table->record[0]
+ As we told the NdbEventOperation to do this
+ (saves moving data about many times)
+ */
+
+ /*
+ for now malloc/free blobs buffer each time
+ TODO if possible share single permanent buffer with handlers
+ */
+ uchar* blobs_buffer[2] = { 0, 0 };
+ uint blobs_buffer_size[2] = { 0, 0 };
+
+ switch(pOp->getEventType())
+ {
+ case NDBEVENT::TE_INSERT:
+ row.n_inserts++;
+ DBUG_PRINT("info", ("INSERT INTO %s.%s",
+ table_s->db.str, table_s->table_name.str));
+ {
+ if (share->flags & NSF_BLOB_FLAG)
+ {
+ my_ptrdiff_t ptrdiff= 0;
+ int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[0],
+ blobs_buffer[0],
+ blobs_buffer_size[0],
+ ptrdiff);
+ DBUG_ASSERT(ret == 0);
+ }
+ ndb_unpack_record(table, share->ndb_value[0], &b, table->record[0]);
+ int ret __attribute__((unused))= trans.write_row(originating_server_id,
+ injector::transaction::table(table,
+ TRUE),
+ &b, n_fields, table->record[0]);
+ DBUG_ASSERT(ret == 0);
+ }
+ break;
+ case NDBEVENT::TE_DELETE:
+ row.n_deletes++;
+ DBUG_PRINT("info",("DELETE FROM %s.%s",
+ table_s->db.str, table_s->table_name.str));
+ {
+ /*
+ table->record[0] contains only the primary key in this case
+ since we do not have an after image
+ */
+ int n;
+ if (table->s->primary_key != MAX_KEY)
+ n= 0; /*
+ use the primary key only as it save time and space and
+ it is the only thing needed to log the delete
+ */
+ else
+ n= 1; /*
+ we use the before values since we don't have a primary key
+ since the mysql server does not handle the hidden primary
+ key
+ */
+
+ if (share->flags & NSF_BLOB_FLAG)
+ {
+ my_ptrdiff_t ptrdiff= table->record[n] - table->record[0];
+ int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[n],
+ blobs_buffer[n],
+ blobs_buffer_size[n],
+ ptrdiff);
+ DBUG_ASSERT(ret == 0);
+ }
+ ndb_unpack_record(table, share->ndb_value[n], &b, table->record[n]);
+ DBUG_EXECUTE("info", print_records(table, table->record[n]););
+ int ret __attribute__((unused))= trans.delete_row(originating_server_id,
+ injector::transaction::table(table,
+ TRUE),
+ &b, n_fields, table->record[n]);
+ DBUG_ASSERT(ret == 0);
+ }
+ break;
+ case NDBEVENT::TE_UPDATE:
+ row.n_updates++;
+ DBUG_PRINT("info", ("UPDATE %s.%s",
+ table_s->db.str, table_s->table_name.str));
+ {
+ if (share->flags & NSF_BLOB_FLAG)
+ {
+ my_ptrdiff_t ptrdiff= 0;
+ int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[0],
+ blobs_buffer[0],
+ blobs_buffer_size[0],
+ ptrdiff);
+ DBUG_ASSERT(ret == 0);
+ }
+ ndb_unpack_record(table, share->ndb_value[0],
+ &b, table->record[0]);
+ DBUG_EXECUTE("info", print_records(table, table->record[0]););
+ if (table->s->primary_key != MAX_KEY)
+ {
+ /*
+ since table has a primary key, we can do a write
+ using only after values
+ */
+ trans.write_row(originating_server_id,
+ injector::transaction::table(table, TRUE),
+ &b, n_fields, table->record[0]);// after values
+ }
+ else
+ {
+ /*
+ mysql server cannot handle the ndb hidden key and
+ therefore needs the before image as well
+ */
+ if (share->flags & NSF_BLOB_FLAG)
+ {
+ my_ptrdiff_t ptrdiff= table->record[1] - table->record[0];
+ int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[1],
+ blobs_buffer[1],
+ blobs_buffer_size[1],
+ ptrdiff);
+ DBUG_ASSERT(ret == 0);
+ }
+ ndb_unpack_record(table, share->ndb_value[1], &b, table->record[1]);
+ DBUG_EXECUTE("info", print_records(table, table->record[1]););
+ int ret __attribute__((unused))= trans.update_row(originating_server_id,
+ injector::transaction::table(table,
+ TRUE),
+ &b, n_fields,
+ table->record[1], // before values
+ table->record[0]);// after values
+ DBUG_ASSERT(ret == 0);
+ }
+ }
+ break;
+ default:
+ /* We should REALLY never get here. */
+ DBUG_PRINT("info", ("default - uh oh, a brain exploded."));
+ break;
+ }
+
+ if (share->flags & NSF_BLOB_FLAG)
+ {
+ my_free(blobs_buffer[0]);
+ my_free(blobs_buffer[1]);
+ }
+
+ return 0;
+}
+
+//#define RUN_NDB_BINLOG_TIMER
+#ifdef RUN_NDB_BINLOG_TIMER
+class Timer
+{
+public:
+ Timer() { start(); }
+ void start() { gettimeofday(&m_start, 0); }
+ void stop() { gettimeofday(&m_stop, 0); }
+ ulong elapsed_ms()
+ {
+ return (ulong)
+ (((longlong) m_stop.tv_sec - (longlong) m_start.tv_sec) * 1000 +
+ ((longlong) m_stop.tv_usec -
+ (longlong) m_start.tv_usec + 999) / 1000);
+ }
+private:
+ struct timeval m_start,m_stop;
+};
+#endif
+
+/****************************************************************
+ Injector thread main loop
+****************************************************************/
+
+static uchar *
+ndb_schema_objects_get_key(NDB_SCHEMA_OBJECT *schema_object,
+ size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= schema_object->key_length;
+ return (uchar*) schema_object->key;
+}
+
+static NDB_SCHEMA_OBJECT *ndb_get_schema_object(const char *key,
+ my_bool create_if_not_exists,
+ my_bool have_lock)
+{
+ NDB_SCHEMA_OBJECT *ndb_schema_object;
+ uint length= (uint) strlen(key);
+ DBUG_ENTER("ndb_get_schema_object");
+ DBUG_PRINT("enter", ("key: '%s'", key));
+
+ if (!have_lock)
+ mysql_mutex_lock(&ndbcluster_mutex);
+ while (!(ndb_schema_object=
+ (NDB_SCHEMA_OBJECT*) my_hash_search(&ndb_schema_objects,
+ (uchar*) key,
+ length)))
+ {
+ if (!create_if_not_exists)
+ {
+ DBUG_PRINT("info", ("does not exist"));
+ break;
+ }
+ if (!(ndb_schema_object=
+ (NDB_SCHEMA_OBJECT*) my_malloc(sizeof(*ndb_schema_object) + length + 1,
+ MYF(MY_WME | MY_ZEROFILL))))
+ {
+ DBUG_PRINT("info", ("malloc error"));
+ break;
+ }
+ ndb_schema_object->key= (char *)(ndb_schema_object+1);
+ memcpy(ndb_schema_object->key, key, length + 1);
+ ndb_schema_object->key_length= length;
+ if (my_hash_insert(&ndb_schema_objects, (uchar*) ndb_schema_object))
+ {
+ my_free(ndb_schema_object);
+ break;
+ }
+ mysql_mutex_init(key_ndb_schema_object_mutex, &ndb_schema_object->mutex, MY_MUTEX_INIT_FAST);
+ my_bitmap_init(&ndb_schema_object->slock_bitmap, ndb_schema_object->slock,
+ sizeof(ndb_schema_object->slock)*8, FALSE);
+ bitmap_clear_all(&ndb_schema_object->slock_bitmap);
+ break;
+ }
+ if (ndb_schema_object)
+ {
+ ndb_schema_object->use_count++;
+ DBUG_PRINT("info", ("use_count: %d", ndb_schema_object->use_count));
+ }
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_RETURN(ndb_schema_object);
+}
+
+
+static void ndb_free_schema_object(NDB_SCHEMA_OBJECT **ndb_schema_object,
+ bool have_lock)
+{
+ DBUG_ENTER("ndb_free_schema_object");
+ DBUG_PRINT("enter", ("key: '%s'", (*ndb_schema_object)->key));
+ if (!have_lock)
+ mysql_mutex_lock(&ndbcluster_mutex);
+ if (!--(*ndb_schema_object)->use_count)
+ {
+ DBUG_PRINT("info", ("use_count: %d", (*ndb_schema_object)->use_count));
+ my_hash_delete(&ndb_schema_objects, (uchar*) *ndb_schema_object);
+ mysql_mutex_destroy(&(*ndb_schema_object)->mutex);
+ my_free(*ndb_schema_object);
+ *ndb_schema_object= 0;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("use_count: %d", (*ndb_schema_object)->use_count));
+ }
+ if (!have_lock)
+ mysql_mutex_unlock(&ndbcluster_mutex);
+ DBUG_VOID_RETURN;
+}
+
+extern ulong opt_ndb_report_thresh_binlog_epoch_slip;
+extern ulong opt_ndb_report_thresh_binlog_mem_usage;
+
+pthread_handler_t ndb_binlog_thread_func(void *arg)
+{
+ THD *thd; /* needs to be first for thread_stack */
+ Ndb *i_ndb= 0;
+ Ndb *s_ndb= 0;
+ Thd_ndb *thd_ndb=0;
+ int ndb_update_ndb_binlog_index= 1;
+ injector *inj= injector::instance();
+ uint incident_id= 0;
+
+#ifdef RUN_NDB_BINLOG_TIMER
+ Timer main_timer;
+#endif
+
+ mysql_mutex_lock(&injector_mutex);
+ /*
+ Set up the Thread
+ */
+ my_thread_init();
+ DBUG_ENTER("ndb_binlog_thread");
+
+ thd= new THD; /* note that contructor of THD uses DBUG_ */
+ THD_CHECK_SENTRY(thd);
+ thd->set_current_stmt_binlog_format_row();
+
+ /* We need to set thd->thread_id before thd->store_globals, or it will
+ set an invalid value for thd->variables.pseudo_thread_id.
+ */
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->thread_id= thread_id++;
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ mysql_thread_set_psi_id(thd->thread_id);
+
+ thd->thread_stack= (char*) &thd; /* remember where our stack is */
+ if (thd->store_globals())
+ {
+ thd->cleanup();
+ delete thd;
+ ndb_binlog_thread_running= -1;
+ mysql_mutex_unlock(&injector_mutex);
+ mysql_cond_signal(&injector_cond);
+
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+ my_thread_end();
+ pthread_exit(0);
+ return NULL; // Avoid compiler warnings
+ }
+
+ thd->init_for_queries();
+ thd->command= COM_DAEMON;
+ thd->system_thread= SYSTEM_THREAD_NDBCLUSTER_BINLOG;
+ thd->main_security_ctx.host_or_ip= "";
+ thd->client_capabilities= 0;
+ my_net_init(&thd->net, 0, MYF(MY_THREAD_SPECIFIC));
+ thd->main_security_ctx.master_access= ~0;
+ thd->main_security_ctx.priv_user[0]= 0;
+ /* Do not use user-supplied timeout value for system threads. */
+ thd->variables.lock_wait_timeout= LONG_TIMEOUT;
+
+ /*
+ Set up ndb binlog
+ */
+ sql_print_information("Starting MySQL Cluster Binlog Thread");
+
+ pthread_detach_this_thread();
+ thd->real_id= pthread_self();
+ mysql_mutex_lock(&LOCK_thread_count);
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ thd->lex->start_transaction_opt= 0;
+
+ if (!(s_ndb= new Ndb(g_ndb_cluster_connection, "")) ||
+ s_ndb->init())
+ {
+ sql_print_error("NDB Binlog: Getting Schema Ndb object failed");
+ ndb_binlog_thread_running= -1;
+ mysql_mutex_unlock(&injector_mutex);
+ mysql_cond_signal(&injector_cond);
+ goto err;
+ }
+
+ // empty database
+ if (!(i_ndb= new Ndb(g_ndb_cluster_connection, "")) ||
+ i_ndb->init())
+ {
+ sql_print_error("NDB Binlog: Getting Ndb object failed");
+ ndb_binlog_thread_running= -1;
+ mysql_mutex_unlock(&injector_mutex);
+ mysql_cond_signal(&injector_cond);
+ goto err;
+ }
+
+ /* init hash for schema object distribution */
+ (void) my_hash_init(&ndb_schema_objects, system_charset_info, 32, 0, 0,
+ (my_hash_get_key)ndb_schema_objects_get_key, 0, 0);
+
+ /*
+ Expose global reference to our ndb object.
+
+ Used by both sql client thread and binlog thread to interact
+ with the storage
+ mysql_mutex_lock(&injector_mutex);
+ */
+ injector_thd= thd;
+ injector_ndb= i_ndb;
+ p_latest_trans_gci=
+ injector_ndb->get_ndb_cluster_connection().get_latest_trans_gci();
+ schema_ndb= s_ndb;
+
+ if (opt_bin_log)
+ {
+ ndb_binlog_running= TRUE;
+ }
+
+ /* Thread start up completed */
+ ndb_binlog_thread_running= 1;
+ mysql_mutex_unlock(&injector_mutex);
+ mysql_cond_signal(&injector_cond);
+
+ /*
+ wait for mysql server to start (so that the binlog is started
+ and thus can receive the first GAP event)
+ */
+ mysql_mutex_lock(&LOCK_server_started);
+ while (!mysqld_server_started)
+ {
+ struct timespec abstime;
+ set_timespec(abstime, 1);
+ mysql_cond_timedwait(&COND_server_started, &LOCK_server_started,
+ &abstime);
+ if (ndbcluster_terminating)
+ {
+ mysql_mutex_unlock(&LOCK_server_started);
+ goto err;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_server_started);
+restart:
+ /*
+ Main NDB Injector loop
+ */
+ while (ndb_binlog_running)
+ {
+ /*
+ check if it is the first log, if so we do not insert a GAP event
+ as there is really no log to have a GAP in
+ */
+ if (incident_id == 0)
+ {
+ LOG_INFO log_info;
+ mysql_bin_log.get_current_log(&log_info);
+ int len= strlen(log_info.log_file_name);
+ uint no= 0;
+ if ((sscanf(log_info.log_file_name + len - 6, "%u", &no) == 1) &&
+ no == 1)
+ {
+ /* this is the fist log, so skip GAP event */
+ break;
+ }
+ }
+
+ /*
+ Always insert a GAP event as we cannot know what has happened
+ in the cluster while not being connected.
+ */
+ LEX_STRING const msg[2]=
+ {
+ { C_STRING_WITH_LEN("mysqld startup") },
+ { C_STRING_WITH_LEN("cluster disconnect")}
+ };
+ int error __attribute__((unused))=
+ inj->record_incident(thd, INCIDENT_LOST_EVENTS, msg[incident_id]);
+ DBUG_ASSERT(!error);
+ break;
+ }
+ incident_id= 1;
+ {
+ thd->proc_info= "Waiting for ndbcluster to start";
+
+ mysql_mutex_lock(&injector_mutex);
+ while (!ndb_schema_share ||
+ (ndb_binlog_running && !ndb_apply_status_share))
+ {
+ /* ndb not connected yet */
+ struct timespec abstime;
+ set_timespec(abstime, 1);
+ mysql_cond_timedwait(&injector_cond, &injector_mutex, &abstime);
+ if (ndbcluster_binlog_terminating)
+ {
+ mysql_mutex_unlock(&injector_mutex);
+ goto err;
+ }
+ }
+ mysql_mutex_unlock(&injector_mutex);
+
+ if (thd_ndb == NULL)
+ {
+ DBUG_ASSERT(ndbcluster_hton->slot != ~(uint)0);
+ if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb()))
+ {
+ sql_print_error("Could not allocate Thd_ndb object");
+ goto err;
+ }
+ set_thd_ndb(thd, thd_ndb);
+ thd_ndb->options|= TNO_NO_LOG_SCHEMA_OP;
+ thd->query_id= 0; // to keep valgrind quiet
+ }
+ }
+
+ {
+ // wait for the first event
+ thd->proc_info= "Waiting for first event from ndbcluster";
+ int schema_res, res;
+ Uint64 schema_gci;
+ do
+ {
+ DBUG_PRINT("info", ("Waiting for the first event"));
+
+ if (ndbcluster_binlog_terminating)
+ goto err;
+
+ schema_res= s_ndb->pollEvents(100, &schema_gci);
+ } while (schema_gci == 0 || ndb_latest_received_binlog_epoch == schema_gci);
+ if (ndb_binlog_running)
+ {
+ Uint64 gci= i_ndb->getLatestGCI();
+ while (gci < schema_gci || gci == ndb_latest_received_binlog_epoch)
+ {
+ if (ndbcluster_binlog_terminating)
+ goto err;
+ res= i_ndb->pollEvents(10, &gci);
+ }
+ if (gci > schema_gci)
+ {
+ schema_gci= gci;
+ }
+ }
+ // now check that we have epochs consistant with what we had before the restart
+ DBUG_PRINT("info", ("schema_res: %d schema_gci: %lu", schema_res,
+ (long) schema_gci));
+ {
+ i_ndb->flushIncompleteEvents(schema_gci);
+ s_ndb->flushIncompleteEvents(schema_gci);
+ if (schema_gci < ndb_latest_handled_binlog_epoch)
+ {
+ sql_print_error("NDB Binlog: cluster has been restarted --initial or with older filesystem. "
+ "ndb_latest_handled_binlog_epoch: %u, while current epoch: %u. "
+ "RESET MASTER should be issued. Resetting ndb_latest_handled_binlog_epoch.",
+ (unsigned) ndb_latest_handled_binlog_epoch, (unsigned) schema_gci);
+ *p_latest_trans_gci= 0;
+ ndb_latest_handled_binlog_epoch= 0;
+ ndb_latest_applied_binlog_epoch= 0;
+ ndb_latest_received_binlog_epoch= 0;
+ }
+ else if (ndb_latest_applied_binlog_epoch > 0)
+ {
+ sql_print_warning("NDB Binlog: cluster has reconnected. "
+ "Changes to the database that occured while "
+ "disconnected will not be in the binlog");
+ }
+ if (opt_ndb_extra_logging)
+ {
+ sql_print_information("NDB Binlog: starting log at epoch %u",
+ (unsigned)schema_gci);
+ }
+ }
+ }
+ {
+ static char db[]= "";
+ thd->db= db;
+ }
+ do_ndbcluster_binlog_close_connection= BCCC_running;
+ for ( ; !((ndbcluster_binlog_terminating ||
+ do_ndbcluster_binlog_close_connection) &&
+ ndb_latest_handled_binlog_epoch >= *p_latest_trans_gci) &&
+ do_ndbcluster_binlog_close_connection != BCCC_restart; )
+ {
+#ifndef DBUG_OFF
+ if (do_ndbcluster_binlog_close_connection)
+ {
+ DBUG_PRINT("info", ("do_ndbcluster_binlog_close_connection: %d, "
+ "ndb_latest_handled_binlog_epoch: %lu, "
+ "*p_latest_trans_gci: %lu",
+ do_ndbcluster_binlog_close_connection,
+ (ulong) ndb_latest_handled_binlog_epoch,
+ (ulong) *p_latest_trans_gci));
+ }
+#endif
+#ifdef RUN_NDB_BINLOG_TIMER
+ main_timer.stop();
+ sql_print_information("main_timer %ld ms", main_timer.elapsed_ms());
+ main_timer.start();
+#endif
+
+ /*
+ now we don't want any events before next gci is complete
+ */
+ thd->proc_info= "Waiting for event from ndbcluster";
+ thd->set_time();
+
+ /* wait for event or 1000 ms */
+ Uint64 gci= 0, schema_gci;
+ int res= 0, tot_poll_wait= 1000;
+ if (ndb_binlog_running)
+ {
+ res= i_ndb->pollEvents(tot_poll_wait, &gci);
+ tot_poll_wait= 0;
+ }
+ else
+ {
+ /*
+ Just consume any events, not used if no binlogging
+ e.g. node failure events
+ */
+ Uint64 tmp_gci;
+ if (i_ndb->pollEvents(0, &tmp_gci))
+ while (i_ndb->nextEvent())
+ ;
+ }
+ int schema_res= s_ndb->pollEvents(tot_poll_wait, &schema_gci);
+ ndb_latest_received_binlog_epoch= gci;
+
+ while (gci > schema_gci && schema_res >= 0)
+ {
+ static char buf[64];
+ thd->proc_info= "Waiting for schema epoch";
+ my_snprintf(buf, sizeof(buf), "%s %u(%u)", thd->proc_info, (unsigned) schema_gci, (unsigned) gci);
+ thd->proc_info= buf;
+ schema_res= s_ndb->pollEvents(10, &schema_gci);
+ }
+
+ if ((ndbcluster_binlog_terminating ||
+ do_ndbcluster_binlog_close_connection) &&
+ (ndb_latest_handled_binlog_epoch >= *p_latest_trans_gci ||
+ !ndb_binlog_running))
+ break; /* Shutting down server */
+
+ if (ndb_binlog_index && ndb_binlog_index->s->has_old_version())
+ {
+ if (ndb_binlog_index->s->has_old_version())
+ {
+ trans_commit_stmt(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ ndb_binlog_index= 0;
+ }
+ }
+
+ MEM_ROOT **root_ptr=
+ my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC);
+ MEM_ROOT *old_root= *root_ptr;
+ MEM_ROOT mem_root;
+ init_sql_alloc(&mem_root, 4096, 0, MYF(0));
+ List<Cluster_schema> post_epoch_log_list;
+ List<Cluster_schema> post_epoch_unlock_list;
+ *root_ptr= &mem_root;
+
+ if (unlikely(schema_res > 0))
+ {
+ thd->proc_info= "Processing events from schema table";
+ s_ndb->
+ setReportThreshEventGCISlip(opt_ndb_report_thresh_binlog_epoch_slip);
+ s_ndb->
+ setReportThreshEventFreeMem(opt_ndb_report_thresh_binlog_mem_usage);
+ NdbEventOperation *pOp= s_ndb->nextEvent();
+ while (pOp != NULL)
+ {
+ if (!pOp->hasError())
+ {
+ ndb_binlog_thread_handle_schema_event(thd, s_ndb, pOp,
+ &post_epoch_log_list,
+ &post_epoch_unlock_list,
+ &mem_root);
+ DBUG_PRINT("info", ("s_ndb first: %s", s_ndb->getEventOperation() ?
+ s_ndb->getEventOperation()->getEvent()->getTable()->getName() :
+ "<empty>"));
+ DBUG_PRINT("info", ("i_ndb first: %s", i_ndb->getEventOperation() ?
+ i_ndb->getEventOperation()->getEvent()->getTable()->getName() :
+ "<empty>"));
+ if (i_ndb->getEventOperation() == NULL &&
+ s_ndb->getEventOperation() == NULL &&
+ do_ndbcluster_binlog_close_connection == BCCC_running)
+ {
+ DBUG_PRINT("info", ("do_ndbcluster_binlog_close_connection= BCCC_restart"));
+ do_ndbcluster_binlog_close_connection= BCCC_restart;
+ if (ndb_latest_received_binlog_epoch < *p_latest_trans_gci && ndb_binlog_running)
+ {
+ sql_print_error("NDB Binlog: latest transaction in epoch %lu not in binlog "
+ "as latest received epoch is %lu",
+ (ulong) *p_latest_trans_gci,
+ (ulong) ndb_latest_received_binlog_epoch);
+ }
+ }
+ }
+ else
+ sql_print_error("NDB: error %lu (%s) on handling "
+ "binlog schema event",
+ (ulong) pOp->getNdbError().code,
+ pOp->getNdbError().message);
+ pOp= s_ndb->nextEvent();
+ }
+ }
+
+ if (res > 0)
+ {
+ DBUG_PRINT("info", ("pollEvents res: %d", res));
+ thd->proc_info= "Processing events";
+ NdbEventOperation *pOp= i_ndb->nextEvent();
+ ndb_binlog_index_row row;
+ while (pOp != NULL)
+ {
+#ifdef RUN_NDB_BINLOG_TIMER
+ Timer gci_timer, write_timer;
+ int event_count= 0;
+ gci_timer.start();
+#endif
+ gci= pOp->getGCI();
+ DBUG_PRINT("info", ("Handling gci: %d", (unsigned)gci));
+ // sometimes get TE_ALTER with invalid table
+ DBUG_ASSERT(pOp->getEventType() == NdbDictionary::Event::TE_ALTER ||
+ ! IS_NDB_BLOB_PREFIX(pOp->getEvent()->getTable()->getName()));
+ DBUG_ASSERT(gci <= ndb_latest_received_binlog_epoch);
+
+ /* initialize some variables for this epoch */
+ g_ndb_log_slave_updates= opt_log_slave_updates;
+ i_ndb->
+ setReportThreshEventGCISlip(opt_ndb_report_thresh_binlog_epoch_slip);
+ i_ndb->setReportThreshEventFreeMem(opt_ndb_report_thresh_binlog_mem_usage);
+
+ bzero((char*) &row, sizeof(row));
+ thd->variables.character_set_client= &my_charset_latin1;
+ injector::transaction trans;
+ // pass table map before epoch
+ {
+ Uint32 iter= 0;
+ const NdbEventOperation *gci_op;
+ Uint32 event_types;
+ while ((gci_op= i_ndb->getGCIEventOperations(&iter, &event_types))
+ != NULL)
+ {
+ NDB_SHARE *share= (NDB_SHARE*)gci_op->getCustomData();
+ DBUG_PRINT("info", ("per gci_op: 0x%lx share: 0x%lx event_types: 0x%x",
+ (long) gci_op, (long) share, event_types));
+ // workaround for interface returning TE_STOP events
+ // which are normally filtered out below in the nextEvent loop
+ if ((event_types & ~NdbDictionary::Event::TE_STOP) == 0)
+ {
+ DBUG_PRINT("info", ("Skipped TE_STOP on table %s",
+ gci_op->getEvent()->getTable()->getName()));
+ continue;
+ }
+ // this should not happen
+ if (share == NULL || share->table == NULL)
+ {
+ DBUG_PRINT("info", ("no share or table %s!",
+ gci_op->getEvent()->getTable()->getName()));
+ continue;
+ }
+ if (share == ndb_apply_status_share)
+ {
+ // skip this table, it is handled specially
+ continue;
+ }
+ TABLE *table= share->table;
+#ifndef DBUG_OFF
+ const LEX_STRING &name= table->s->table_name;
+#endif
+ if ((event_types & (NdbDictionary::Event::TE_INSERT |
+ NdbDictionary::Event::TE_UPDATE |
+ NdbDictionary::Event::TE_DELETE)) == 0)
+ {
+ DBUG_PRINT("info", ("skipping non data event table: %.*s",
+ (int) name.length, name.str));
+ continue;
+ }
+ if (!trans.good())
+ {
+ DBUG_PRINT("info",
+ ("Found new data event, initializing transaction"));
+ inj->new_trans(thd, &trans);
+ }
+ DBUG_PRINT("info", ("use_table: %.*s",
+ (int) name.length, name.str));
+ injector::transaction::table tbl(table, TRUE);
+ int ret __attribute__((unused))= trans.use_table(::server_id, tbl);
+ DBUG_ASSERT(ret == 0);
+ }
+ }
+ if (trans.good())
+ {
+ if (ndb_apply_status_share)
+ {
+ TABLE *table= ndb_apply_status_share->table;
+
+#ifndef DBUG_OFF
+ const LEX_STRING& name= table->s->table_name;
+ DBUG_PRINT("info", ("use_table: %.*s",
+ (int) name.length, name.str));
+#endif
+ injector::transaction::table tbl(table, TRUE);
+ int ret __attribute__((unused))= trans.use_table(::server_id, tbl);
+ DBUG_ASSERT(ret == 0);
+
+ /*
+ Intialize table->record[0]
+ */
+ empty_record(table);
+
+ table->field[0]->store((longlong)::server_id);
+ table->field[1]->store((longlong)gci);
+ table->field[2]->store("", 0, &my_charset_bin);
+ table->field[3]->store((longlong)0);
+ table->field[4]->store((longlong)0);
+ trans.write_row(::server_id,
+ injector::transaction::table(table, TRUE),
+ &table->s->all_set, table->s->fields,
+ table->record[0]);
+ }
+ else
+ {
+ sql_print_error("NDB: Could not get apply status share");
+ }
+ }
+#ifdef RUN_NDB_BINLOG_TIMER
+ write_timer.start();
+#endif
+ do
+ {
+#ifdef RUN_NDB_BINLOG_TIMER
+ event_count++;
+#endif
+ if (pOp->hasError() &&
+ ndb_binlog_thread_handle_error(i_ndb, pOp, row) < 0)
+ goto err;
+
+#ifndef DBUG_OFF
+ {
+ NDB_SHARE *share= (NDB_SHARE*) pOp->getCustomData();
+ DBUG_PRINT("info",
+ ("EVENT TYPE: %d GCI: %ld last applied: %ld "
+ "share: 0x%lx (%s.%s)", pOp->getEventType(),
+ (long) gci,
+ (long) ndb_latest_applied_binlog_epoch,
+ (long) share,
+ share ? share->db : "'NULL'",
+ share ? share->table_name : "'NULL'"));
+ DBUG_ASSERT(share != 0);
+ }
+ // assert that there is consistancy between gci op list
+ // and event list
+ {
+ Uint32 iter= 0;
+ const NdbEventOperation *gci_op;
+ Uint32 event_types;
+ while ((gci_op= i_ndb->getGCIEventOperations(&iter, &event_types))
+ != NULL)
+ {
+ if (gci_op == pOp)
+ break;
+ }
+ DBUG_ASSERT(gci_op == pOp);
+ DBUG_ASSERT((event_types & pOp->getEventType()) != 0);
+ }
+#endif
+ if ((unsigned) pOp->getEventType() <
+ (unsigned) NDBEVENT::TE_FIRST_NON_DATA_EVENT)
+ ndb_binlog_thread_handle_data_event(i_ndb, pOp, row, trans);
+ else
+ {
+ // set injector_ndb database/schema from table internal name
+ int ret __attribute__((unused))=
+ i_ndb->setDatabaseAndSchemaName(pOp->getEvent()->getTable());
+ DBUG_ASSERT(ret == 0);
+ ndb_binlog_thread_handle_non_data_event(thd, i_ndb, pOp, row);
+ // reset to catch errors
+ i_ndb->setDatabaseName("");
+ DBUG_PRINT("info", ("s_ndb first: %s", s_ndb->getEventOperation() ?
+ s_ndb->getEventOperation()->getEvent()->getTable()->getName() :
+ "<empty>"));
+ DBUG_PRINT("info", ("i_ndb first: %s", i_ndb->getEventOperation() ?
+ i_ndb->getEventOperation()->getEvent()->getTable()->getName() :
+ "<empty>"));
+ if (i_ndb->getEventOperation() == NULL &&
+ s_ndb->getEventOperation() == NULL &&
+ do_ndbcluster_binlog_close_connection == BCCC_running)
+ {
+ DBUG_PRINT("info", ("do_ndbcluster_binlog_close_connection= BCCC_restart"));
+ do_ndbcluster_binlog_close_connection= BCCC_restart;
+ if (ndb_latest_received_binlog_epoch < *p_latest_trans_gci && ndb_binlog_running)
+ {
+ sql_print_error("NDB Binlog: latest transaction in epoch %lu not in binlog "
+ "as latest received epoch is %lu",
+ (ulong) *p_latest_trans_gci,
+ (ulong) ndb_latest_received_binlog_epoch);
+ }
+ }
+ }
+
+ pOp= i_ndb->nextEvent();
+ } while (pOp && pOp->getGCI() == gci);
+
+ /*
+ note! pOp is not referring to an event in the next epoch
+ or is == 0
+ */
+#ifdef RUN_NDB_BINLOG_TIMER
+ write_timer.stop();
+#endif
+
+ if (trans.good())
+ {
+ //DBUG_ASSERT(row.n_inserts || row.n_updates || row.n_deletes);
+ thd->proc_info= "Committing events to binlog";
+ injector::transaction::binlog_pos start= trans.start_pos();
+ if (int r= trans.commit())
+ {
+ sql_print_error("NDB Binlog: "
+ "Error during COMMIT of GCI. Error: %d",
+ r);
+ /* TODO: Further handling? */
+ }
+ row.gci= gci;
+ row.master_log_file= start.file_name();
+ row.master_log_pos= start.file_pos();
+
+ DBUG_PRINT("info", ("COMMIT gci: %lu", (ulong) gci));
+ if (ndb_update_ndb_binlog_index)
+ ndb_add_ndb_binlog_index(thd, &row);
+ ndb_latest_applied_binlog_epoch= gci;
+ }
+ ndb_latest_handled_binlog_epoch= gci;
+#ifdef RUN_NDB_BINLOG_TIMER
+ gci_timer.stop();
+ sql_print_information("gci %ld event_count %d write time "
+ "%ld(%d e/s), total time %ld(%d e/s)",
+ (ulong)gci, event_count,
+ write_timer.elapsed_ms(),
+ (1000*event_count) / write_timer.elapsed_ms(),
+ gci_timer.elapsed_ms(),
+ (1000*event_count) / gci_timer.elapsed_ms());
+#endif
+ }
+ }
+
+ ndb_binlog_thread_handle_schema_event_post_epoch(thd,
+ &post_epoch_log_list,
+ &post_epoch_unlock_list);
+ free_root(&mem_root, MYF(0));
+ *root_ptr= old_root;
+ ndb_latest_handled_binlog_epoch= ndb_latest_received_binlog_epoch;
+ }
+ if (do_ndbcluster_binlog_close_connection == BCCC_restart)
+ {
+ ndb_binlog_tables_inited= FALSE;
+ trans_commit_stmt(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ ndb_binlog_index= 0;
+ goto restart;
+ }
+err:
+ sql_print_information("Stopping Cluster Binlog");
+ DBUG_PRINT("info",("Shutting down cluster binlog thread"));
+ thd->proc_info= "Shutting down";
+ thd->get_stmt_da()->set_overwrite_status(true);
+ thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ mysql_mutex_lock(&injector_mutex);
+ /* don't mess with the injector_ndb anymore from other threads */
+ injector_thd= 0;
+ injector_ndb= 0;
+ p_latest_trans_gci= 0;
+ schema_ndb= 0;
+ mysql_mutex_unlock(&injector_mutex);
+ thd->db= 0; // as not to try to free memory
+
+ if (ndb_apply_status_share)
+ {
+ /* ndb_share reference binlog extra free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u",
+ ndb_apply_status_share->key,
+ ndb_apply_status_share->use_count));
+ free_share(&ndb_apply_status_share);
+ ndb_apply_status_share= 0;
+ }
+ if (ndb_schema_share)
+ {
+ /* begin protect ndb_schema_share */
+ mysql_mutex_lock(&ndb_schema_share_mutex);
+ /* ndb_share reference binlog extra free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u",
+ ndb_schema_share->key,
+ ndb_schema_share->use_count));
+ free_share(&ndb_schema_share);
+ ndb_schema_share= 0;
+ ndb_binlog_tables_inited= 0;
+ mysql_mutex_unlock(&ndb_schema_share_mutex);
+ /* end protect ndb_schema_share */
+ }
+
+ /* remove all event operations */
+ if (s_ndb)
+ {
+ NdbEventOperation *op;
+ DBUG_PRINT("info",("removing all event operations"));
+ while ((op= s_ndb->getEventOperation()))
+ {
+ DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(op->getEvent()->getTable()->getName()));
+ DBUG_PRINT("info",("removing event operation on %s",
+ op->getEvent()->getName()));
+ NDB_SHARE *share= (NDB_SHARE*) op->getCustomData();
+ DBUG_ASSERT(share != 0);
+ DBUG_ASSERT(share->op == op ||
+ share->op_old == op);
+ share->op= share->op_old= 0;
+ /* ndb_share reference binlog free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ s_ndb->dropEventOperation(op);
+ }
+ delete s_ndb;
+ s_ndb= 0;
+ }
+ if (i_ndb)
+ {
+ NdbEventOperation *op;
+ DBUG_PRINT("info",("removing all event operations"));
+ while ((op= i_ndb->getEventOperation()))
+ {
+ DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(op->getEvent()->getTable()->getName()));
+ DBUG_PRINT("info",("removing event operation on %s",
+ op->getEvent()->getName()));
+ NDB_SHARE *share= (NDB_SHARE*) op->getCustomData();
+ DBUG_ASSERT(share != 0);
+ DBUG_ASSERT(share->op == op ||
+ share->op_old == op);
+ share->op= share->op_old= 0;
+ /* ndb_share reference binlog free */
+ DBUG_PRINT("NDB_SHARE", ("%s binlog free use_count: %u",
+ share->key, share->use_count));
+ free_share(&share);
+ i_ndb->dropEventOperation(op);
+ }
+ delete i_ndb;
+ i_ndb= 0;
+ }
+
+ my_hash_free(&ndb_schema_objects);
+
+ delete thd;
+
+ ndb_binlog_thread_running= -1;
+ ndb_binlog_running= FALSE;
+ mysql_cond_signal(&injector_cond);
+
+ DBUG_PRINT("exit", ("ndb_binlog_thread"));
+
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+ my_thread_end();
+ pthread_exit(0);
+ return NULL; // Avoid compiler warnings
+}
+
+bool
+ndbcluster_show_status_binlog(THD* thd, stat_print_fn *stat_print,
+ enum ha_stat_type stat_type)
+{
+ char buf[IO_SIZE];
+ uint buflen;
+ ulonglong ndb_latest_epoch= 0;
+ DBUG_ENTER("ndbcluster_show_status_binlog");
+
+ mysql_mutex_lock(&injector_mutex);
+ if (injector_ndb)
+ {
+ char buff1[22],buff2[22],buff3[22],buff4[22],buff5[22];
+ ndb_latest_epoch= injector_ndb->getLatestGCI();
+ mysql_mutex_unlock(&injector_mutex);
+
+ buflen=
+ snprintf(buf, sizeof(buf),
+ "latest_epoch=%s, "
+ "latest_trans_epoch=%s, "
+ "latest_received_binlog_epoch=%s, "
+ "latest_handled_binlog_epoch=%s, "
+ "latest_applied_binlog_epoch=%s",
+ llstr(ndb_latest_epoch, buff1),
+ llstr(*p_latest_trans_gci, buff2),
+ llstr(ndb_latest_received_binlog_epoch, buff3),
+ llstr(ndb_latest_handled_binlog_epoch, buff4),
+ llstr(ndb_latest_applied_binlog_epoch, buff5));
+ if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length,
+ "binlog", strlen("binlog"),
+ buf, buflen))
+ DBUG_RETURN(TRUE);
+ }
+ else
+ mysql_mutex_unlock(&injector_mutex);
+ DBUG_RETURN(FALSE);
+}
+
+#endif /* HAVE_NDB_BINLOG */
+#endif
diff --git a/sql/ha_ndbcluster_binlog.h b/sql/ha_ndbcluster_binlog.h
new file mode 100644
index 00000000000..a02f687d76f
--- /dev/null
+++ b/sql/ha_ndbcluster_binlog.h
@@ -0,0 +1,239 @@
+#ifndef HA_NDBCLUSTER_BINLOG_INCLUDED
+#define HA_NDBCLUSTER_BINLOG_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "sql_class.h" /* THD */
+
+// Typedefs for long names
+typedef NdbDictionary::Object NDBOBJ;
+typedef NdbDictionary::Column NDBCOL;
+typedef NdbDictionary::Table NDBTAB;
+typedef NdbDictionary::Index NDBINDEX;
+typedef NdbDictionary::Dictionary NDBDICT;
+typedef NdbDictionary::Event NDBEVENT;
+
+#define IS_TMP_PREFIX(A) (is_prefix(A, tmp_file_prefix))
+
+#define INJECTOR_EVENT_LEN 200
+
+#define NDB_INVALID_SCHEMA_OBJECT 241
+
+/* server id's with high bit set is reservered */
+#define NDB_ANYVALUE_FOR_NOLOGGING 0xFFFFFFFF
+#define NDB_ANYVALUE_RESERVED 0x80000000
+
+extern handlerton *ndbcluster_hton;
+
+/*
+ The numbers below must not change as they
+ are passed between mysql servers, and if changed
+ would break compatablility. Add new numbers to
+ the end.
+*/
+enum SCHEMA_OP_TYPE
+{
+ SOT_DROP_TABLE= 0,
+ SOT_CREATE_TABLE= 1,
+ SOT_RENAME_TABLE_NEW= 2,
+ SOT_ALTER_TABLE= 3,
+ SOT_DROP_DB= 4,
+ SOT_CREATE_DB= 5,
+ SOT_ALTER_DB= 6,
+ SOT_CLEAR_SLOCK= 7,
+ SOT_TABLESPACE= 8,
+ SOT_LOGFILE_GROUP= 9,
+ SOT_RENAME_TABLE= 10,
+ SOT_TRUNCATE_TABLE= 11
+};
+
+const uint max_ndb_nodes= 64; /* multiple of 32 */
+
+static const char *ha_ndb_ext=".ndb";
+static const char share_prefix[]= "./";
+
+class Ndb_table_guard
+{
+public:
+ Ndb_table_guard(NDBDICT *dict, const char *tabname)
+ : m_dict(dict)
+ {
+ DBUG_ENTER("Ndb_table_guard");
+ m_ndbtab= m_dict->getTableGlobal(tabname);
+ m_invalidate= 0;
+ DBUG_PRINT("info", ("m_ndbtab: %p", m_ndbtab));
+ DBUG_VOID_RETURN;
+ }
+ ~Ndb_table_guard()
+ {
+ DBUG_ENTER("~Ndb_table_guard");
+ if (m_ndbtab)
+ {
+ DBUG_PRINT("info", ("m_ndbtab: %p m_invalidate: %d",
+ m_ndbtab, m_invalidate));
+ m_dict->removeTableGlobal(*m_ndbtab, m_invalidate);
+ }
+ DBUG_VOID_RETURN;
+ }
+ const NDBTAB *get_table() { return m_ndbtab; }
+ void invalidate() { m_invalidate= 1; }
+ const NDBTAB *release()
+ {
+ DBUG_ENTER("Ndb_table_guard::release");
+ const NDBTAB *tmp= m_ndbtab;
+ DBUG_PRINT("info", ("m_ndbtab: %p", m_ndbtab));
+ m_ndbtab = 0;
+ DBUG_RETURN(tmp);
+ }
+private:
+ const NDBTAB *m_ndbtab;
+ NDBDICT *m_dict;
+ int m_invalidate;
+};
+
+#ifdef HAVE_NDB_BINLOG
+
+#ifdef HAVE_PSI_INTERFACE
+extern PSI_mutex_key key_injector_mutex, key_ndb_schema_share_mutex,
+ key_ndb_schema_object_mutex;
+extern PSI_cond_key key_injector_cond;
+extern PSI_thread_key key_thread_ndb_binlog;
+#endif /* HAVE_PSI_INTERFACE */
+
+extern pthread_t ndb_binlog_thread;
+extern mysql_mutex_t injector_mutex;
+extern mysql_cond_t injector_cond;
+
+extern unsigned char g_node_id_map[max_ndb_nodes];
+extern pthread_t ndb_util_thread;
+extern mysql_mutex_t LOCK_ndb_util_thread;
+extern mysql_cond_t COND_ndb_util_thread;
+extern int ndbcluster_util_inited;
+extern mysql_mutex_t ndbcluster_mutex;
+extern HASH ndbcluster_open_tables;
+extern Ndb_cluster_connection* g_ndb_cluster_connection;
+extern long ndb_number_of_storage_nodes;
+
+/*
+ Initialize the binlog part of the ndb handlerton
+*/
+void ndbcluster_binlog_init_handlerton();
+/*
+ Initialize the binlog part of the NDB_SHARE
+*/
+int ndbcluster_binlog_init_share(NDB_SHARE *share, TABLE *table);
+
+bool ndbcluster_check_if_local_table(const char *dbname, const char *tabname);
+bool ndbcluster_check_if_local_tables_in_db(THD *thd, const char *dbname);
+
+int ndbcluster_create_binlog_setup(Ndb *ndb, const char *key,
+ uint key_len,
+ const char *db,
+ const char *table_name,
+ my_bool share_may_exist);
+int ndbcluster_create_event(Ndb *ndb, const NDBTAB *table,
+ const char *event_name, NDB_SHARE *share,
+ int push_warning= 0);
+int ndbcluster_create_event_ops(NDB_SHARE *share,
+ const NDBTAB *ndbtab,
+ const char *event_name);
+int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share,
+ const char *query, int query_length,
+ const char *db, const char *table_name,
+ uint32 ndb_table_id,
+ uint32 ndb_table_version,
+ enum SCHEMA_OP_TYPE type,
+ const char *new_db,
+ const char *new_table_name);
+int ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name,
+ NDB_SHARE *share,
+ const char *type_str);
+void ndb_rep_event_name(String *event_name,
+ const char *db, const char *tbl);
+int ndb_create_table_from_engine(THD *thd, const char *db,
+ const char *table_name);
+int ndbcluster_binlog_start();
+pthread_handler_t ndb_binlog_thread_func(void *arg);
+
+/*
+ table mysql.ndb_apply_status
+*/
+int ndbcluster_setup_binlog_table_shares(THD *thd);
+extern NDB_SHARE *ndb_apply_status_share;
+extern NDB_SHARE *ndb_schema_share;
+
+extern THD *injector_thd;
+extern my_bool ndb_binlog_running;
+extern my_bool ndb_binlog_tables_inited;
+
+bool
+ndbcluster_show_status_binlog(THD* thd, stat_print_fn *stat_print,
+ enum ha_stat_type stat_type);
+
+/*
+ prototypes for ndb handler utility function also needed by
+ the ndb binlog code
+*/
+int cmp_frm(const NDBTAB *ndbtab, const void *pack_data,
+ uint pack_length);
+int ndbcluster_find_all_files(THD *thd);
+#endif /* HAVE_NDB_BINLOG */
+
+void ndb_unpack_record(TABLE *table, NdbValue *value,
+ MY_BITMAP *defined, uchar *buf);
+char *ndb_pack_varchar(const NDBCOL *col, char *buf,
+ const char *str, int sz);
+
+NDB_SHARE *ndbcluster_get_share(const char *key,
+ TABLE *table,
+ bool create_if_not_exists,
+ bool have_lock);
+NDB_SHARE *ndbcluster_get_share(NDB_SHARE *share);
+void ndbcluster_free_share(NDB_SHARE **share, bool have_lock);
+void ndbcluster_real_free_share(NDB_SHARE **share);
+int handle_trailing_share(NDB_SHARE *share);
+inline NDB_SHARE *get_share(const char *key,
+ TABLE *table,
+ bool create_if_not_exists= TRUE,
+ bool have_lock= FALSE)
+{
+ return ndbcluster_get_share(key, table, create_if_not_exists, have_lock);
+}
+
+inline NDB_SHARE *get_share(NDB_SHARE *share)
+{
+ return ndbcluster_get_share(share);
+}
+
+inline void free_share(NDB_SHARE **share, bool have_lock= FALSE)
+{
+ ndbcluster_free_share(share, have_lock);
+}
+
+inline
+Thd_ndb *
+get_thd_ndb(THD *thd)
+{ return (Thd_ndb *) thd_get_ha_data(thd, ndbcluster_hton); }
+
+inline
+void
+set_thd_ndb(THD *thd, Thd_ndb *thd_ndb)
+{ thd_set_ha_data(thd, ndbcluster_hton, thd_ndb); }
+
+Ndb* check_ndb_in_thd(THD* thd);
+
+#endif /* HA_NDBCLUSTER_BINLOG_INCLUDED */
diff --git a/sql/ha_ndbcluster_cond.cc b/sql/ha_ndbcluster_cond.cc
new file mode 100644
index 00000000000..f39e72e1549
--- /dev/null
+++ b/sql/ha_ndbcluster_cond.cc
@@ -0,0 +1,1476 @@
+/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+*/
+
+/*
+ This file defines the NDB Cluster handler engine_condition_pushdown
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_class.h" // set_var.h: THD
+#include "my_global.h" // WITH_*
+#include "log.h" // sql_print_error
+
+#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE
+#include <ndbapi/NdbApi.hpp>
+#include "ha_ndbcluster_cond.h"
+
+// Typedefs for long names
+typedef NdbDictionary::Column NDBCOL;
+typedef NdbDictionary::Table NDBTAB;
+
+
+/**
+ Serialize a constant item into a Ndb_cond node.
+
+ @param const_type item's result type
+ @param item item to be serialized
+ @param curr_cond Ndb_cond node the item to be serialized into
+ @param context Traverse context
+*/
+
+static void ndb_serialize_const(Item_result const_type, const Item *item,
+ Ndb_cond *curr_cond,
+ Ndb_cond_traverse_context *context)
+{
+ DBUG_ASSERT(item->const_item());
+ switch (const_type) {
+ case STRING_RESULT:
+ {
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::STRING_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(STRING_RESULT);
+ context->expect_collation(item->collation.collation);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ // Check that string result have correct collation
+ if (!context->expecting_collation(item->collation.collation))
+ {
+ DBUG_PRINT("info", ("Found non-matching collation %s",
+ item->collation.collation->name));
+ context->supported= FALSE;
+ }
+ }
+ break;
+ }
+ case REAL_RESULT:
+ {
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::REAL_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(REAL_RESULT);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ }
+ break;
+ }
+ case INT_RESULT:
+ {
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::INT_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(INT_RESULT);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ }
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::DECIMAL_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(DECIMAL_RESULT);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+/*
+ Serialize the item tree into a linked list represented by Ndb_cond
+ for fast generation of NbdScanFilter. Adds information such as
+ position of fields that is not directly available in the Item tree.
+ Also checks if condition is supported.
+*/
+void ndb_serialize_cond(const Item *item, void *arg)
+{
+ Ndb_cond_traverse_context *context= (Ndb_cond_traverse_context *) arg;
+ DBUG_ENTER("ndb_serialize_cond");
+
+ // Check if we are skipping arguments to a function to be evaluated
+ if (context->skip)
+ {
+ if (!item)
+ {
+ DBUG_PRINT("info", ("Unexpected mismatch of found and expected number of function arguments %u", context->skip));
+ sql_print_error("ndb_serialize_cond: Unexpected mismatch of found and "
+ "expected number of function arguments %u", context->skip);
+ context->skip= 0;
+ DBUG_VOID_RETURN;
+ }
+ DBUG_PRINT("info", ("Skiping argument %d", context->skip));
+ context->skip--;
+ switch (item->type()) {
+ case Item::FUNC_ITEM:
+ {
+ Item_func *func_item= (Item_func *) item;
+ context->skip+= func_item->argument_count();
+ break;
+ }
+ case Item::INT_ITEM:
+ case Item::REAL_ITEM:
+ case Item::STRING_ITEM:
+ case Item::VARBIN_ITEM:
+ case Item::DECIMAL_ITEM:
+ break;
+ default:
+ context->supported= FALSE;
+ break;
+ }
+
+ DBUG_VOID_RETURN;
+ }
+
+ if (context->supported)
+ {
+ Ndb_rewrite_context *rewrite_context2= context->rewrite_stack;
+ const Item_func *rewrite_func_item;
+ // Check if we are rewriting some unsupported function call
+ if (rewrite_context2 &&
+ (rewrite_func_item= rewrite_context2->func_item) &&
+ rewrite_context2->count++ == 0)
+ {
+ switch (rewrite_func_item->functype()) {
+ case Item_func::BETWEEN:
+ /*
+ Rewrite
+ <field>|<const> BETWEEN <const1>|<field1> AND <const2>|<field2>
+ to <field>|<const> > <const1>|<field1> AND
+ <field>|<const> < <const2>|<field2>
+ or actually in prefix format
+ BEGIN(AND) GT(<field>|<const>, <const1>|<field1>),
+ LT(<field>|<const>, <const2>|<field2>), END()
+ */
+ case Item_func::IN_FUNC:
+ {
+ /*
+ Rewrite <field>|<const> IN(<const1>|<field1>, <const2>|<field2>,..)
+ to <field>|<const> = <const1>|<field1> OR
+ <field> = <const2>|<field2> ...
+ or actually in prefix format
+ BEGIN(OR) EQ(<field>|<const>, <const1><field1>),
+ EQ(<field>|<const>, <const2>|<field2>), ... END()
+ Each part of the disjunction is added for each call
+ to ndb_serialize_cond and end of rewrite statement
+ is wrapped in end of ndb_serialize_cond
+ */
+ if (context->expecting(item->type()) || item->const_item())
+ {
+ // This is the <field>|<const> item, save it in the rewrite context
+ rewrite_context2->left_hand_item= item;
+ if (item->type() == Item::FUNC_ITEM)
+ {
+ Item_func *func_item= (Item_func *) item;
+ if ((func_item->functype() == Item_func::UNKNOWN_FUNC ||
+ func_item->functype() == Item_func::NEG_FUNC) &&
+ func_item->const_item())
+ {
+ // Skip any arguments since we will evaluate function instead
+ DBUG_PRINT("info", ("Skip until end of arguments marker"));
+ context->skip= func_item->argument_count();
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Found unsupported functional expression in BETWEEN|IN"));
+ context->supported= FALSE;
+ DBUG_VOID_RETURN;
+
+ }
+ }
+ }
+ else
+ {
+ // Non-supported BETWEEN|IN expression
+ DBUG_PRINT("info", ("Found unexpected item of type %u in BETWEEN|IN",
+ item->type()));
+ context->supported= FALSE;
+ DBUG_VOID_RETURN;
+ }
+ break;
+ }
+ default:
+ context->supported= FALSE;
+ break;
+ }
+ DBUG_VOID_RETURN;
+ }
+ else
+ {
+ Ndb_cond_stack *ndb_stack= context->stack_ptr;
+ Ndb_cond *prev_cond= context->cond_ptr;
+ Ndb_cond *curr_cond= context->cond_ptr= new Ndb_cond();
+ if (!ndb_stack->ndb_cond)
+ ndb_stack->ndb_cond= curr_cond;
+ curr_cond->prev= prev_cond;
+ if (prev_cond) prev_cond->next= curr_cond;
+ // Check if we are rewriting some unsupported function call
+ if (context->rewrite_stack)
+ {
+ Ndb_rewrite_context *rewrite_context= context->rewrite_stack;
+ const Item_func *func_item= rewrite_context->func_item;
+ switch (func_item->functype()) {
+ case Item_func::BETWEEN:
+ {
+ /*
+ Rewrite
+ <field>|<const> BETWEEN <const1>|<field1> AND <const2>|<field2>
+ to <field>|<const> > <const1>|<field1> AND
+ <field>|<const> < <const2>|<field2>
+ or actually in prefix format
+ BEGIN(AND) GT(<field>|<const>, <const1>|<field1>),
+ LT(<field>|<const>, <const2>|<field2>), END()
+ */
+ if (rewrite_context->count == 2)
+ {
+ // Lower limit of BETWEEN
+ DBUG_PRINT("info", ("GE_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(Item_func::GE_FUNC, 2);
+ }
+ else if (rewrite_context->count == 3)
+ {
+ // Upper limit of BETWEEN
+ DBUG_PRINT("info", ("LE_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(Item_func::LE_FUNC, 2);
+ }
+ else
+ {
+ // Illegal BETWEEN expression
+ DBUG_PRINT("info", ("Illegal BETWEEN expression"));
+ context->supported= FALSE;
+ DBUG_VOID_RETURN;
+ }
+ break;
+ }
+ case Item_func::IN_FUNC:
+ {
+ /*
+ Rewrite <field>|<const> IN(<const1>|<field1>, <const2>|<field2>,..)
+ to <field>|<const> = <const1>|<field1> OR
+ <field> = <const2>|<field2> ...
+ or actually in prefix format
+ BEGIN(OR) EQ(<field>|<const>, <const1><field1>),
+ EQ(<field>|<const>, <const2>|<field2>), ... END()
+ Each part of the disjunction is added for each call
+ to ndb_serialize_cond and end of rewrite statement
+ is wrapped in end of ndb_serialize_cond
+ */
+ DBUG_PRINT("info", ("EQ_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(Item_func::EQ_FUNC, 2);
+ break;
+ }
+ default:
+ context->supported= FALSE;
+ }
+ // Handle left hand <field>|<const>
+ context->rewrite_stack= NULL; // Disable rewrite mode
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FUNC_ITEM);
+ ndb_serialize_cond(rewrite_context->left_hand_item, arg);
+ context->skip= 0; // Any FUNC_ITEM expression has already been parsed
+ context->rewrite_stack= rewrite_context; // Enable rewrite mode
+ if (!context->supported)
+ DBUG_VOID_RETURN;
+
+ prev_cond= context->cond_ptr;
+ curr_cond= context->cond_ptr= new Ndb_cond();
+ prev_cond->next= curr_cond;
+ }
+
+ // Check for end of AND/OR expression
+ if (!item)
+ {
+ // End marker for condition group
+ DBUG_PRINT("info", ("End of condition group"));
+ curr_cond->ndb_item= new Ndb_item(NDB_END_COND);
+ }
+ else
+ {
+ switch (item->type()) {
+ case Item::FIELD_ITEM:
+ {
+ Item_field *field_item= (Item_field *) item;
+ Field *field= field_item->field;
+ enum_field_types type= field->type();
+ /*
+ Check that the field is part of the table of the handler
+ instance and that we expect a field with of this result type.
+ */
+ if (context->table->s == field->table->s)
+ {
+ const NDBTAB *tab= context->ndb_table;
+ DBUG_PRINT("info", ("FIELD_ITEM"));
+ DBUG_PRINT("info", ("table %s", tab->getName()));
+ DBUG_PRINT("info", ("column %s", field->field_name));
+ DBUG_PRINT("info", ("type %d", field->type()));
+ DBUG_PRINT("info", ("result type %d", field->result_type()));
+
+ // Check that we are expecting a field and with the correct
+ // result type
+ if (context->expecting(Item::FIELD_ITEM) &&
+ context->expecting_field_type(field->type()) &&
+ (context->expecting_field_result(field->result_type()) ||
+ // Date and year can be written as string or int
+ ((type == MYSQL_TYPE_TIME ||
+ type == MYSQL_TYPE_DATE ||
+ type == MYSQL_TYPE_YEAR ||
+ type == MYSQL_TYPE_DATETIME)
+ ? (context->expecting_field_result(STRING_RESULT) ||
+ context->expecting_field_result(INT_RESULT))
+ : TRUE)) &&
+ // Bit fields no yet supported in scan filter
+ type != MYSQL_TYPE_BIT &&
+ // No BLOB support in scan filter
+ type != MYSQL_TYPE_TINY_BLOB &&
+ type != MYSQL_TYPE_MEDIUM_BLOB &&
+ type != MYSQL_TYPE_LONG_BLOB &&
+ type != MYSQL_TYPE_BLOB)
+ {
+ const NDBCOL *col= tab->getColumn(field->field_name);
+ DBUG_ASSERT(col);
+ curr_cond->ndb_item= new Ndb_item(field, col->getColumnNo());
+ context->dont_expect(Item::FIELD_ITEM);
+ context->expect_no_field_result();
+ if (! context->expecting_nothing())
+ {
+ // We have not seen second argument yet
+ if (type == MYSQL_TYPE_TIME ||
+ type == MYSQL_TYPE_DATE ||
+ type == MYSQL_TYPE_YEAR ||
+ type == MYSQL_TYPE_DATETIME)
+ {
+ context->expect_only(Item::STRING_ITEM);
+ context->expect(Item::INT_ITEM);
+ }
+ else
+ switch (field->result_type()) {
+ case STRING_RESULT:
+ // Expect char string or binary string
+ context->expect_only(Item::STRING_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect_collation(field_item->collation.collation);
+ break;
+ case REAL_RESULT:
+ context->expect_only(Item::REAL_ITEM);
+ context->expect(Item::DECIMAL_ITEM);
+ context->expect(Item::INT_ITEM);
+ break;
+ case INT_RESULT:
+ context->expect_only(Item::INT_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ break;
+ case DECIMAL_RESULT:
+ context->expect_only(Item::DECIMAL_ITEM);
+ context->expect(Item::REAL_ITEM);
+ context->expect(Item::INT_ITEM);
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ // Check that field and string constant collations are the same
+ if ((field->result_type() == STRING_RESULT) &&
+ !context->expecting_collation(item->collation.collation)
+ && type != MYSQL_TYPE_TIME
+ && type != MYSQL_TYPE_DATE
+ && type != MYSQL_TYPE_YEAR
+ && type != MYSQL_TYPE_DATETIME)
+ {
+ DBUG_PRINT("info", ("Found non-matching collation %s",
+ item->collation.collation->name));
+ context->supported= FALSE;
+ }
+ }
+ break;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Was not expecting field of type %u(%u)",
+ field->result_type(), type));
+ context->supported= FALSE;
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Was not expecting field from table %s (%s)",
+ context->table->s->table_name.str,
+ field->table->s->table_name.str));
+ context->supported= FALSE;
+ }
+ break;
+ }
+ case Item::FUNC_ITEM:
+ {
+ Item_func *func_item= (Item_func *) item;
+ // Check that we expect a function or functional expression here
+ if (context->expecting(Item::FUNC_ITEM) ||
+ func_item->functype() == Item_func::UNKNOWN_FUNC ||
+ func_item->functype() == Item_func::NEG_FUNC)
+ context->expect_nothing();
+ else
+ {
+ // Did not expect function here
+ context->supported= FALSE;
+ break;
+ }
+
+ switch (func_item->functype()) {
+ case Item_func::EQ_FUNC:
+ {
+ DBUG_PRINT("info", ("EQ_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::REAL_ITEM);
+ context->expect(Item::DECIMAL_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::NE_FUNC:
+ {
+ DBUG_PRINT("info", ("NE_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::REAL_ITEM);
+ context->expect(Item::DECIMAL_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::LT_FUNC:
+ {
+ DBUG_PRINT("info", ("LT_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::REAL_ITEM);
+ context->expect(Item::DECIMAL_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::LE_FUNC:
+ {
+ DBUG_PRINT("info", ("LE_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::REAL_ITEM);
+ context->expect(Item::DECIMAL_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::GE_FUNC:
+ {
+ DBUG_PRINT("info", ("GE_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::REAL_ITEM);
+ context->expect(Item::DECIMAL_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::GT_FUNC:
+ {
+ DBUG_PRINT("info", ("GT_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::REAL_ITEM);
+ context->expect(Item::DECIMAL_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::LIKE_FUNC:
+ {
+ DBUG_PRINT("info", ("LIKE_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_only_field_type(MYSQL_TYPE_STRING);
+ context->expect_field_type(MYSQL_TYPE_VAR_STRING);
+ context->expect_field_type(MYSQL_TYPE_VARCHAR);
+ context->expect_field_result(STRING_RESULT);
+ context->expect(Item::FUNC_ITEM);
+ break;
+ }
+ case Item_func::ISNULL_FUNC:
+ {
+ DBUG_PRINT("info", ("ISNULL_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::ISNOTNULL_FUNC:
+ {
+ DBUG_PRINT("info", ("ISNOTNULL_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::FIELD_ITEM);
+ context->expect_field_result(STRING_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(INT_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ break;
+ }
+ case Item_func::NOT_FUNC:
+ {
+ DBUG_PRINT("info", ("NOT_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(func_item->functype(),
+ func_item);
+ context->expect(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ break;
+ }
+ case Item_func::BETWEEN:
+ {
+ DBUG_PRINT("info", ("BETWEEN, rewriting using AND"));
+ Item_func_between *between_func= (Item_func_between *) func_item;
+ Ndb_rewrite_context *rewrite_context=
+ new Ndb_rewrite_context(func_item);
+ rewrite_context->next= context->rewrite_stack;
+ context->rewrite_stack= rewrite_context;
+ if (between_func->negated)
+ {
+ DBUG_PRINT("info", ("NOT_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(Item_func::NOT_FUNC, 1);
+ prev_cond= curr_cond;
+ curr_cond= context->cond_ptr= new Ndb_cond();
+ curr_cond->prev= prev_cond;
+ prev_cond->next= curr_cond;
+ }
+ DBUG_PRINT("info", ("COND_AND_FUNC"));
+ curr_cond->ndb_item=
+ new Ndb_item(Item_func::COND_AND_FUNC,
+ func_item->argument_count() - 1);
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FUNC_ITEM);
+ break;
+ }
+ case Item_func::IN_FUNC:
+ {
+ DBUG_PRINT("info", ("IN_FUNC, rewriting using OR"));
+ Item_func_in *in_func= (Item_func_in *) func_item;
+ Ndb_rewrite_context *rewrite_context=
+ new Ndb_rewrite_context(func_item);
+ rewrite_context->next= context->rewrite_stack;
+ context->rewrite_stack= rewrite_context;
+ if (in_func->negated)
+ {
+ DBUG_PRINT("info", ("NOT_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(Item_func::NOT_FUNC, 1);
+ prev_cond= curr_cond;
+ curr_cond= context->cond_ptr= new Ndb_cond();
+ curr_cond->prev= prev_cond;
+ prev_cond->next= curr_cond;
+ }
+ DBUG_PRINT("info", ("COND_OR_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(Item_func::COND_OR_FUNC,
+ func_item->argument_count() - 1);
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect(Item::INT_ITEM);
+ context->expect(Item::STRING_ITEM);
+ context->expect(Item::VARBIN_ITEM);
+ context->expect(Item::FUNC_ITEM);
+ break;
+ }
+ case Item_func::NEG_FUNC:
+ case Item_func::UNKNOWN_FUNC:
+ {
+ DBUG_PRINT("info", ("UNKNOWN_FUNC %s",
+ func_item->const_item()?"const":""));
+ DBUG_PRINT("info", ("result type %d", func_item->result_type()));
+ if (func_item->const_item())
+ {
+ ndb_serialize_const(func_item->result_type(), item, curr_cond,
+ context);
+
+ // Skip any arguments since we will evaluate function instead
+ DBUG_PRINT("info", ("Skip until end of arguments marker"));
+ context->skip= func_item->argument_count();
+ }
+ else
+ // Function does not return constant expression
+ context->supported= FALSE;
+ break;
+ }
+ default:
+ {
+ DBUG_PRINT("info", ("Found func_item of type %d",
+ func_item->functype()));
+ context->supported= FALSE;
+ }
+ }
+ break;
+ }
+ case Item::STRING_ITEM:
+ DBUG_PRINT("info", ("STRING_ITEM"));
+ if (context->expecting(Item::STRING_ITEM))
+ {
+#ifndef DBUG_OFF
+ char buff[256];
+ String str(buff,(uint32) sizeof(buff), system_charset_info);
+ str.length(0);
+ Item_string *string_item= (Item_string *) item;
+ DBUG_PRINT("info", ("value \"%s\"",
+ string_item->val_str(&str)->ptr()));
+#endif
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::STRING_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(STRING_RESULT);
+ context->expect_collation(item->collation.collation);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ // Check that we are comparing with a field with same collation
+ if (!context->expecting_collation(item->collation.collation))
+ {
+ DBUG_PRINT("info", ("Found non-matching collation %s",
+ item->collation.collation->name));
+ context->supported= FALSE;
+ }
+ }
+ }
+ else
+ context->supported= FALSE;
+ break;
+ case Item::INT_ITEM:
+ DBUG_PRINT("info", ("INT_ITEM"));
+ if (context->expecting(Item::INT_ITEM))
+ {
+ DBUG_PRINT("info", ("value %ld",
+ (long) ((Item_int*) item)->value));
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::INT_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(INT_RESULT);
+ context->expect_field_result(REAL_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ }
+ }
+ else
+ context->supported= FALSE;
+ break;
+ case Item::REAL_ITEM:
+ DBUG_PRINT("info", ("REAL_ITEM"));
+ if (context->expecting(Item::REAL_ITEM))
+ {
+ DBUG_PRINT("info", ("value %f", ((Item_float*) item)->value));
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::REAL_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(REAL_RESULT);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ }
+ }
+ else
+ context->supported= FALSE;
+ break;
+ case Item::VARBIN_ITEM:
+ DBUG_PRINT("info", ("VARBIN_ITEM"));
+ if (context->expecting(Item::VARBIN_ITEM))
+ {
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::VARBIN_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(STRING_RESULT);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ }
+ }
+ else
+ context->supported= FALSE;
+ break;
+ case Item::DECIMAL_ITEM:
+ DBUG_PRINT("info", ("DECIMAL_ITEM"));
+ if (context->expecting(Item::DECIMAL_ITEM))
+ {
+ DBUG_PRINT("info", ("value %f",
+ ((Item_decimal*) item)->val_real()));
+ NDB_ITEM_QUALIFICATION q;
+ q.value_type= Item::DECIMAL_ITEM;
+ curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item);
+ if (! context->expecting_no_field_result())
+ {
+ // We have not seen the field argument yet
+ context->expect_only(Item::FIELD_ITEM);
+ context->expect_only_field_result(REAL_RESULT);
+ context->expect_field_result(DECIMAL_RESULT);
+ }
+ else
+ {
+ // Expect another logical expression
+ context->expect_only(Item::FUNC_ITEM);
+ context->expect(Item::COND_ITEM);
+ }
+ }
+ else
+ context->supported= FALSE;
+ break;
+ case Item::COND_ITEM:
+ {
+ Item_cond *cond_item= (Item_cond *) item;
+
+ if (context->expecting(Item::COND_ITEM))
+ {
+ switch (cond_item->functype()) {
+ case Item_func::COND_AND_FUNC:
+ DBUG_PRINT("info", ("COND_AND_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(cond_item->functype(),
+ cond_item);
+ break;
+ case Item_func::COND_OR_FUNC:
+ DBUG_PRINT("info", ("COND_OR_FUNC"));
+ curr_cond->ndb_item= new Ndb_item(cond_item->functype(),
+ cond_item);
+ break;
+ default:
+ DBUG_PRINT("info", ("COND_ITEM %d", cond_item->functype()));
+ context->supported= FALSE;
+ break;
+ }
+ }
+ else
+ {
+ /* Did not expect condition */
+ context->supported= FALSE;
+ }
+ break;
+ }
+ case Item::CACHE_ITEM:
+ {
+ DBUG_PRINT("info", ("CACHE_ITEM"));
+ if (item->const_item())
+ {
+ ndb_serialize_const(((Item_cache*)item)->result_type(), item,
+ curr_cond, context);
+ }
+ else
+ context->supported= FALSE;
+
+ break;
+ }
+ default:
+ {
+ DBUG_PRINT("info", ("Found item of type %d", item->type()));
+ context->supported= FALSE;
+ }
+ }
+ }
+ if (context->supported && context->rewrite_stack)
+ {
+ Ndb_rewrite_context *rewrite_context= context->rewrite_stack;
+ if (rewrite_context->count ==
+ rewrite_context->func_item->argument_count())
+ {
+ // Rewrite is done, wrap an END() at the en
+ DBUG_PRINT("info", ("End of condition group"));
+ prev_cond= curr_cond;
+ curr_cond= context->cond_ptr= new Ndb_cond();
+ curr_cond->prev= prev_cond;
+ prev_cond->next= curr_cond;
+ curr_cond->ndb_item= new Ndb_item(NDB_END_COND);
+ // Pop rewrite stack
+ context->rewrite_stack= rewrite_context->next;
+ rewrite_context->next= NULL;
+ delete(rewrite_context);
+ }
+ }
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Push a condition
+ */
+const
+COND*
+ha_ndbcluster_cond::cond_push(const COND *cond,
+ TABLE *table, const NDBTAB *ndb_table)
+{
+ DBUG_ENTER("cond_push");
+ Ndb_cond_stack *ndb_cond = new Ndb_cond_stack();
+ if (ndb_cond == NULL)
+ {
+ my_errno= HA_ERR_OUT_OF_MEM;
+ DBUG_RETURN(NULL);
+ }
+ if (m_cond_stack)
+ ndb_cond->next= m_cond_stack;
+ else
+ ndb_cond->next= NULL;
+ m_cond_stack= ndb_cond;
+
+ if (serialize_cond(cond, ndb_cond, table, ndb_table))
+ {
+ DBUG_RETURN(NULL);
+ }
+ else
+ {
+ cond_pop();
+ }
+ DBUG_RETURN(cond);
+}
+
+/*
+ Pop the top condition from the condition stack
+*/
+void
+ha_ndbcluster_cond::cond_pop()
+{
+ Ndb_cond_stack *ndb_cond_stack= m_cond_stack;
+ if (ndb_cond_stack)
+ {
+ m_cond_stack= ndb_cond_stack->next;
+ ndb_cond_stack->next= NULL;
+ delete ndb_cond_stack;
+ }
+}
+
+/*
+ Clear the condition stack
+*/
+void
+ha_ndbcluster_cond::cond_clear()
+{
+ DBUG_ENTER("cond_clear");
+ while (m_cond_stack)
+ cond_pop();
+
+ DBUG_VOID_RETURN;
+}
+
+bool
+ha_ndbcluster_cond::serialize_cond(const COND *cond, Ndb_cond_stack *ndb_cond,
+ TABLE *table, const NDBTAB *ndb_table)
+{
+ DBUG_ENTER("serialize_cond");
+ Item *item= (Item *) cond;
+ Ndb_cond_traverse_context context(table, ndb_table, ndb_cond);
+ // Expect a logical expression
+ context.expect(Item::FUNC_ITEM);
+ context.expect(Item::COND_ITEM);
+ item->traverse_cond(&ndb_serialize_cond, (void *) &context, Item::PREFIX);
+ DBUG_PRINT("info", ("The pushed condition is %ssupported", (context.supported)?"":"not "));
+
+ DBUG_RETURN(context.supported);
+}
+
+int
+ha_ndbcluster_cond::build_scan_filter_predicate(Ndb_cond * &cond,
+ NdbScanFilter *filter,
+ bool negated)
+{
+ DBUG_ENTER("build_scan_filter_predicate");
+ switch (cond->ndb_item->type) {
+ case NDB_FUNCTION:
+ {
+ if (!cond->next)
+ break;
+ Ndb_item *a= cond->next->ndb_item;
+ Ndb_item *b, *field, *value= NULL;
+
+ switch (cond->ndb_item->argument_count()) {
+ case 1:
+ field= (a->type == NDB_FIELD)? a : NULL;
+ break;
+ case 2:
+ if (!cond->next->next)
+ {
+ field= NULL;
+ break;
+ }
+ b= cond->next->next->ndb_item;
+ value= ((a->type == NDB_VALUE) ? a :
+ (b->type == NDB_VALUE) ? b :
+ NULL);
+ field= ((a->type == NDB_FIELD) ? a :
+ (b->type == NDB_FIELD) ? b :
+ NULL);
+ break;
+ default:
+ field= NULL; //Keep compiler happy
+ DBUG_ASSERT(0);
+ break;
+ }
+ switch ((negated) ?
+ Ndb_item::negate(cond->ndb_item->qualification.function_type)
+ : cond->ndb_item->qualification.function_type) {
+ case NDB_EQ_FUNC:
+ {
+ if (!value || !field) break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ DBUG_PRINT("info", ("Generating EQ filter"));
+ if (filter->cmp(NdbScanFilter::COND_EQ,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_NE_FUNC:
+ {
+ if (!value || !field) break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ DBUG_PRINT("info", ("Generating NE filter"));
+ if (filter->cmp(NdbScanFilter::COND_NE,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_LT_FUNC:
+ {
+ if (!value || !field) break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ if (a == field)
+ {
+ DBUG_PRINT("info", ("Generating LT filter"));
+ if (filter->cmp(NdbScanFilter::COND_LT,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Generating GT filter"));
+ if (filter->cmp(NdbScanFilter::COND_GT,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_LE_FUNC:
+ {
+ if (!value || !field) break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ if (a == field)
+ {
+ DBUG_PRINT("info", ("Generating LE filter"));
+ if (filter->cmp(NdbScanFilter::COND_LE,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Generating GE filter"));
+ if (filter->cmp(NdbScanFilter::COND_GE,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_GE_FUNC:
+ {
+ if (!value || !field) break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ if (a == field)
+ {
+ DBUG_PRINT("info", ("Generating GE filter"));
+ if (filter->cmp(NdbScanFilter::COND_GE,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Generating LE filter"));
+ if (filter->cmp(NdbScanFilter::COND_LE,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_GT_FUNC:
+ {
+ if (!value || !field) break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ if (a == field)
+ {
+ DBUG_PRINT("info", ("Generating GT filter"));
+ if (filter->cmp(NdbScanFilter::COND_GT,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Generating LT filter"));
+ if (filter->cmp(NdbScanFilter::COND_LT,
+ field->get_field_no(),
+ field->get_val(),
+ field->pack_length()) == -1)
+ DBUG_RETURN(1);
+ }
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_LIKE_FUNC:
+ {
+ if (!value || !field) break;
+ if ((value->qualification.value_type != Item::STRING_ITEM) &&
+ (value->qualification.value_type != Item::VARBIN_ITEM))
+ break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ DBUG_PRINT("info", ("Generating LIKE filter: like(%d,%s,%d)",
+ field->get_field_no(), value->get_val(),
+ value->pack_length()));
+ if (filter->cmp(NdbScanFilter::COND_LIKE,
+ field->get_field_no(),
+ value->get_val(),
+ value->pack_length()) == -1)
+ DBUG_RETURN(1);
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_NOTLIKE_FUNC:
+ {
+ if (!value || !field) break;
+ if ((value->qualification.value_type != Item::STRING_ITEM) &&
+ (value->qualification.value_type != Item::VARBIN_ITEM))
+ break;
+ // Save value in right format for the field type
+ value->save_in_field(field);
+ DBUG_PRINT("info", ("Generating NOTLIKE filter: notlike(%d,%s,%d)",
+ field->get_field_no(), value->get_val(),
+ value->pack_length()));
+ if (filter->cmp(NdbScanFilter::COND_NOT_LIKE,
+ field->get_field_no(),
+ value->get_val(),
+ value->pack_length()) == -1)
+ DBUG_RETURN(1);
+ cond= cond->next->next->next;
+ DBUG_RETURN(0);
+ }
+ case NDB_ISNULL_FUNC:
+ if (!field)
+ break;
+ DBUG_PRINT("info", ("Generating ISNULL filter"));
+ if (filter->isnull(field->get_field_no()) == -1)
+ DBUG_RETURN(1);
+ cond= cond->next->next;
+ DBUG_RETURN(0);
+ case NDB_ISNOTNULL_FUNC:
+ {
+ if (!field)
+ break;
+ DBUG_PRINT("info", ("Generating ISNOTNULL filter"));
+ if (filter->isnotnull(field->get_field_no()) == -1)
+ DBUG_RETURN(1);
+ cond= cond->next->next;
+ DBUG_RETURN(0);
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ DBUG_PRINT("info", ("Found illegal condition"));
+ DBUG_RETURN(1);
+}
+
+
+int
+ha_ndbcluster_cond::build_scan_filter_group(Ndb_cond* &cond,
+ NdbScanFilter *filter)
+{
+ uint level=0;
+ bool negated= FALSE;
+ DBUG_ENTER("build_scan_filter_group");
+
+ do
+ {
+ if (!cond)
+ DBUG_RETURN(1);
+ switch (cond->ndb_item->type) {
+ case NDB_FUNCTION:
+ {
+ switch (cond->ndb_item->qualification.function_type) {
+ case NDB_COND_AND_FUNC:
+ {
+ level++;
+ DBUG_PRINT("info", ("Generating %s group %u", (negated)?"NAND":"AND",
+ level));
+ if ((negated) ? filter->begin(NdbScanFilter::NAND)
+ : filter->begin(NdbScanFilter::AND) == -1)
+ DBUG_RETURN(1);
+ negated= FALSE;
+ cond= cond->next;
+ break;
+ }
+ case NDB_COND_OR_FUNC:
+ {
+ level++;
+ DBUG_PRINT("info", ("Generating %s group %u", (negated)?"NOR":"OR",
+ level));
+ if ((negated) ? filter->begin(NdbScanFilter::NOR)
+ : filter->begin(NdbScanFilter::OR) == -1)
+ DBUG_RETURN(1);
+ negated= FALSE;
+ cond= cond->next;
+ break;
+ }
+ case NDB_NOT_FUNC:
+ {
+ DBUG_PRINT("info", ("Generating negated query"));
+ cond= cond->next;
+ negated= TRUE;
+ break;
+ }
+ default:
+ if (build_scan_filter_predicate(cond, filter, negated))
+ DBUG_RETURN(1);
+ negated= FALSE;
+ break;
+ }
+ break;
+ }
+ case NDB_END_COND:
+ DBUG_PRINT("info", ("End of group %u", level));
+ level--;
+ if (cond) cond= cond->next;
+ if (filter->end() == -1)
+ DBUG_RETURN(1);
+ if (!negated)
+ break;
+ // else fall through (NOT END is an illegal condition)
+ default:
+ {
+ DBUG_PRINT("info", ("Illegal scan filter"));
+ }
+ }
+ } while (level > 0 || negated);
+
+ DBUG_RETURN(0);
+}
+
+
+int
+ha_ndbcluster_cond::build_scan_filter(Ndb_cond * &cond, NdbScanFilter *filter)
+{
+ bool simple_cond= TRUE;
+ DBUG_ENTER("build_scan_filter");
+
+ switch (cond->ndb_item->type) {
+ case NDB_FUNCTION:
+ switch (cond->ndb_item->qualification.function_type) {
+ case NDB_COND_AND_FUNC:
+ case NDB_COND_OR_FUNC:
+ simple_cond= FALSE;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ if (simple_cond && filter->begin() == -1)
+ DBUG_RETURN(1);
+ if (build_scan_filter_group(cond, filter))
+ DBUG_RETURN(1);
+ if (simple_cond && filter->end() == -1)
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+int
+ha_ndbcluster_cond::generate_scan_filter(NdbScanOperation *op)
+{
+ DBUG_ENTER("generate_scan_filter");
+
+ if (m_cond_stack)
+ {
+ NdbScanFilter filter(op, false); // don't abort on too large
+
+ int ret=generate_scan_filter_from_cond(filter);
+ if (ret != 0)
+ {
+ const NdbError& err=filter.getNdbError();
+ if (err.code == NdbScanFilter::FilterTooLarge)
+ {
+ // err.message has static storage
+ DBUG_PRINT("info", ("%s", err.message));
+ push_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ err.code, err.message);
+ ret=0;
+ }
+ }
+ if (ret != 0)
+ DBUG_RETURN(ret);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Empty stack"));
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+int
+ha_ndbcluster_cond::generate_scan_filter_from_cond(NdbScanFilter& filter)
+{
+ bool multiple_cond= FALSE;
+ DBUG_ENTER("generate_scan_filter_from_cond");
+
+ // Wrap an AND group around multiple conditions
+ if (m_cond_stack->next)
+ {
+ multiple_cond= TRUE;
+ if (filter.begin() == -1)
+ DBUG_RETURN(1);
+ }
+ for (Ndb_cond_stack *stack= m_cond_stack;
+ (stack);
+ stack= stack->next)
+ {
+ Ndb_cond *cond= stack->ndb_cond;
+
+ if (build_scan_filter(cond, &filter))
+ {
+ DBUG_PRINT("info", ("build_scan_filter failed"));
+ DBUG_RETURN(1);
+ }
+ }
+ if (multiple_cond && filter.end() == -1)
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+
+int ha_ndbcluster_cond::generate_scan_filter_from_key(NdbScanOperation *op,
+ const KEY* key_info,
+ const uchar *key,
+ uint key_len,
+ uchar *buf)
+{
+ KEY_PART_INFO* key_part= key_info->key_part;
+ KEY_PART_INFO* end= key_part+key_info->user_defined_key_parts;
+ NdbScanFilter filter(op, true); // abort on too large
+ int res;
+ DBUG_ENTER("generate_scan_filter_from_key");
+
+ filter.begin(NdbScanFilter::AND);
+ for (; key_part != end; key_part++)
+ {
+ Field* field= key_part->field;
+ uint32 pack_len= field->pack_length();
+ const uchar* ptr= key;
+ DBUG_PRINT("info", ("Filtering value for %s", field->field_name));
+ DBUG_DUMP("key", ptr, pack_len);
+ if (key_part->null_bit)
+ {
+ DBUG_PRINT("info", ("Generating ISNULL filter"));
+ if (filter.isnull(key_part->fieldnr-1) == -1)
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Generating EQ filter"));
+ if (filter.cmp(NdbScanFilter::COND_EQ,
+ key_part->fieldnr-1,
+ ptr,
+ pack_len) == -1)
+ DBUG_RETURN(1);
+ }
+ key += key_part->store_length;
+ }
+ // Add any pushed condition
+ if (m_cond_stack &&
+ (res= generate_scan_filter_from_cond(filter)))
+ DBUG_RETURN(res);
+
+ if (filter.end() == -1)
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+#endif
diff --git a/sql/ha_ndbcluster_cond.h b/sql/ha_ndbcluster_cond.h
new file mode 100644
index 00000000000..952b705bfc2
--- /dev/null
+++ b/sql/ha_ndbcluster_cond.h
@@ -0,0 +1,500 @@
+#ifndef HA_NDBCLUSTER_COND_INCLUDED
+#define HA_NDBCLUSTER_COND_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/*
+ This file defines the data structures used by engine condition pushdown in
+ the NDB Cluster handler
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "set_var.h" /* Item, Item_field */
+
+typedef enum ndb_item_type {
+ NDB_VALUE = 0, // Qualified more with Item::Type
+ NDB_FIELD = 1, // Qualified from table definition
+ NDB_FUNCTION = 2,// Qualified from Item_func::Functype
+ NDB_END_COND = 3 // End marker for condition group
+} NDB_ITEM_TYPE;
+
+typedef enum ndb_func_type {
+ NDB_EQ_FUNC = 0,
+ NDB_NE_FUNC = 1,
+ NDB_LT_FUNC = 2,
+ NDB_LE_FUNC = 3,
+ NDB_GT_FUNC = 4,
+ NDB_GE_FUNC = 5,
+ NDB_ISNULL_FUNC = 6,
+ NDB_ISNOTNULL_FUNC = 7,
+ NDB_LIKE_FUNC = 8,
+ NDB_NOTLIKE_FUNC = 9,
+ NDB_NOT_FUNC = 10,
+ NDB_UNKNOWN_FUNC = 11,
+ NDB_COND_AND_FUNC = 12,
+ NDB_COND_OR_FUNC = 13,
+ NDB_UNSUPPORTED_FUNC = 14
+} NDB_FUNC_TYPE;
+
+typedef union ndb_item_qualification {
+ Item::Type value_type;
+ enum_field_types field_type; // Instead of Item::FIELD_ITEM
+ NDB_FUNC_TYPE function_type; // Instead of Item::FUNC_ITEM
+} NDB_ITEM_QUALIFICATION;
+
+typedef struct ndb_item_field_value {
+ Field* field;
+ int column_no;
+} NDB_ITEM_FIELD_VALUE;
+
+typedef union ndb_item_value {
+ const Item *item;
+ NDB_ITEM_FIELD_VALUE *field_value;
+ uint arg_count;
+} NDB_ITEM_VALUE;
+
+struct negated_function_mapping
+{
+ NDB_FUNC_TYPE pos_fun;
+ NDB_FUNC_TYPE neg_fun;
+};
+
+/*
+ Define what functions can be negated in condition pushdown.
+ Note, these HAVE to be in the same order as in definition enum
+*/
+static const negated_function_mapping neg_map[]=
+{
+ {NDB_EQ_FUNC, NDB_NE_FUNC},
+ {NDB_NE_FUNC, NDB_EQ_FUNC},
+ {NDB_LT_FUNC, NDB_GE_FUNC},
+ {NDB_LE_FUNC, NDB_GT_FUNC},
+ {NDB_GT_FUNC, NDB_LE_FUNC},
+ {NDB_GE_FUNC, NDB_LT_FUNC},
+ {NDB_ISNULL_FUNC, NDB_ISNOTNULL_FUNC},
+ {NDB_ISNOTNULL_FUNC, NDB_ISNULL_FUNC},
+ {NDB_LIKE_FUNC, NDB_NOTLIKE_FUNC},
+ {NDB_NOTLIKE_FUNC, NDB_LIKE_FUNC},
+ {NDB_NOT_FUNC, NDB_UNSUPPORTED_FUNC},
+ {NDB_UNKNOWN_FUNC, NDB_UNSUPPORTED_FUNC},
+ {NDB_COND_AND_FUNC, NDB_UNSUPPORTED_FUNC},
+ {NDB_COND_OR_FUNC, NDB_UNSUPPORTED_FUNC},
+ {NDB_UNSUPPORTED_FUNC, NDB_UNSUPPORTED_FUNC}
+};
+
+/*
+ This class is the construction element for serialization of Item tree
+ in condition pushdown.
+ An instance of Ndb_Item represents a constant, table field reference,
+ unary or binary comparison predicate, and start/end of AND/OR.
+ Instances of Ndb_Item are stored in a linked list implemented by Ndb_cond
+ class.
+ The order of elements produced by Ndb_cond::next corresponds to
+ breadth-first traversal of the Item (i.e. expression) tree in prefix order.
+ AND and OR have arbitrary arity, so the end of AND/OR group is marked with
+ Ndb_item with type == NDB_END_COND.
+ NOT items represent negated conditions and generate NAND/NOR groups.
+*/
+class Ndb_item : public Sql_alloc
+{
+public:
+ Ndb_item(NDB_ITEM_TYPE item_type) : type(item_type) {};
+ Ndb_item(NDB_ITEM_TYPE item_type,
+ NDB_ITEM_QUALIFICATION item_qualification,
+ const Item *item_value)
+ : type(item_type), qualification(item_qualification)
+ {
+ switch(item_type) {
+ case(NDB_VALUE):
+ value.item= item_value;
+ break;
+ case(NDB_FIELD): {
+ NDB_ITEM_FIELD_VALUE *field_value= new NDB_ITEM_FIELD_VALUE();
+ Item_field *field_item= (Item_field *) item_value;
+ field_value->field= field_item->field;
+ field_value->column_no= -1; // Will be fetched at scan filter generation
+ value.field_value= field_value;
+ break;
+ }
+ case(NDB_FUNCTION):
+ value.item= item_value;
+ value.arg_count= ((Item_func *) item_value)->argument_count();
+ break;
+ case(NDB_END_COND):
+ break;
+ }
+ };
+ Ndb_item(Field *field, int column_no) : type(NDB_FIELD)
+ {
+ NDB_ITEM_FIELD_VALUE *field_value= new NDB_ITEM_FIELD_VALUE();
+ qualification.field_type= field->type();
+ field_value->field= field;
+ field_value->column_no= column_no;
+ value.field_value= field_value;
+ };
+ Ndb_item(Item_func::Functype func_type, const Item *item_value)
+ : type(NDB_FUNCTION)
+ {
+ qualification.function_type= item_func_to_ndb_func(func_type);
+ value.item= item_value;
+ value.arg_count= ((Item_func *) item_value)->argument_count();
+ };
+ Ndb_item(Item_func::Functype func_type, uint no_args)
+ : type(NDB_FUNCTION)
+ {
+ qualification.function_type= item_func_to_ndb_func(func_type);
+ value.arg_count= no_args;
+ };
+ ~Ndb_item()
+ {
+ if (type == NDB_FIELD)
+ {
+ delete value.field_value;
+ value.field_value= NULL;
+ }
+ };
+
+ uint32 pack_length()
+ {
+ switch(type) {
+ case(NDB_VALUE):
+ if(qualification.value_type == Item::STRING_ITEM)
+ return value.item->str_value.length();
+ break;
+ case(NDB_FIELD):
+ return value.field_value->field->pack_length();
+ default:
+ break;
+ }
+
+ return 0;
+ };
+
+ Field * get_field() { return value.field_value->field; };
+
+ int get_field_no() { return value.field_value->column_no; };
+
+ int argument_count()
+ {
+ return value.arg_count;
+ };
+
+ const char* get_val()
+ {
+ switch(type) {
+ case(NDB_VALUE):
+ if(qualification.value_type == Item::STRING_ITEM)
+ return value.item->str_value.ptr();
+ break;
+ case(NDB_FIELD):
+ return (char*) value.field_value->field->ptr;
+ default:
+ break;
+ }
+
+ return NULL;
+ };
+
+ void save_in_field(Ndb_item *field_item)
+ {
+ Field *field = field_item->value.field_value->field;
+ const Item *item= value.item;
+
+ if (item && field)
+ {
+ my_bitmap_map *old_map=
+ dbug_tmp_use_all_columns(field->table, field->table->write_set);
+ ((Item *)item)->save_in_field(field, FALSE);
+ dbug_tmp_restore_column_map(field->table->write_set, old_map);
+ }
+ };
+
+ static NDB_FUNC_TYPE item_func_to_ndb_func(Item_func::Functype fun)
+ {
+ switch (fun) {
+ case (Item_func::EQ_FUNC): { return NDB_EQ_FUNC; }
+ case (Item_func::NE_FUNC): { return NDB_NE_FUNC; }
+ case (Item_func::LT_FUNC): { return NDB_LT_FUNC; }
+ case (Item_func::LE_FUNC): { return NDB_LE_FUNC; }
+ case (Item_func::GT_FUNC): { return NDB_GT_FUNC; }
+ case (Item_func::GE_FUNC): { return NDB_GE_FUNC; }
+ case (Item_func::ISNULL_FUNC): { return NDB_ISNULL_FUNC; }
+ case (Item_func::ISNOTNULL_FUNC): { return NDB_ISNOTNULL_FUNC; }
+ case (Item_func::LIKE_FUNC): { return NDB_LIKE_FUNC; }
+ case (Item_func::NOT_FUNC): { return NDB_NOT_FUNC; }
+ case (Item_func::NEG_FUNC): { return NDB_UNKNOWN_FUNC; }
+ case (Item_func::UNKNOWN_FUNC): { return NDB_UNKNOWN_FUNC; }
+ case (Item_func::COND_AND_FUNC): { return NDB_COND_AND_FUNC; }
+ case (Item_func::COND_OR_FUNC): { return NDB_COND_OR_FUNC; }
+ default: { return NDB_UNSUPPORTED_FUNC; }
+ }
+ };
+
+ static NDB_FUNC_TYPE negate(NDB_FUNC_TYPE fun)
+ {
+ uint i= (uint) fun;
+ DBUG_ASSERT(fun == neg_map[i].pos_fun);
+ return neg_map[i].neg_fun;
+ };
+
+ NDB_ITEM_TYPE type;
+ NDB_ITEM_QUALIFICATION qualification;
+ private:
+ NDB_ITEM_VALUE value;
+};
+
+/*
+ This class implements a linked list used for storing a
+ serialization of the Item tree for condition pushdown.
+ */
+class Ndb_cond : public Sql_alloc
+{
+ public:
+ Ndb_cond() : ndb_item(NULL), next(NULL), prev(NULL) {};
+ ~Ndb_cond()
+ {
+ if (ndb_item) delete ndb_item;
+ ndb_item= NULL;
+ /*
+ First item in the linked list deletes all in a loop
+ Note - doing it recursively causes stack issues for
+ big IN clauses
+ */
+ Ndb_cond *n= next;
+ while (n)
+ {
+ Ndb_cond *tmp= n;
+ n= n->next;
+ tmp->next= NULL;
+ delete tmp;
+ }
+ next= prev= NULL;
+ };
+ Ndb_item *ndb_item;
+ Ndb_cond *next;
+ Ndb_cond *prev;
+};
+
+/*
+ This class implements a stack for storing several conditions
+ for pushdown (represented as serialized Item trees using Ndb_cond).
+ The current implementation only pushes one condition, but is
+ prepared for handling several (C1 AND C2 ...) if the logic for
+ pushing conditions is extended in sql_select.
+*/
+class Ndb_cond_stack : public Sql_alloc
+{
+ public:
+ Ndb_cond_stack() : ndb_cond(NULL), next(NULL) {};
+ ~Ndb_cond_stack()
+ {
+ if (ndb_cond) delete ndb_cond;
+ ndb_cond= NULL;
+ if (next) delete next;
+ next= NULL;
+ };
+ Ndb_cond *ndb_cond;
+ Ndb_cond_stack *next;
+};
+
+class Ndb_rewrite_context : public Sql_alloc
+{
+public:
+ Ndb_rewrite_context(Item_func *func)
+ : func_item(func), left_hand_item(NULL), count(0) {};
+ ~Ndb_rewrite_context()
+ {
+ if (next) delete next;
+ }
+ const Item_func *func_item;
+ const Item *left_hand_item;
+ uint count;
+ Ndb_rewrite_context *next;
+};
+
+/*
+ This class is used for storing the context when traversing
+ the Item tree. It stores a reference to the table the condition
+ is defined on, the serialized representation being generated,
+ if the condition found is supported, and information what is
+ expected next in the tree inorder for the condition to be supported.
+*/
+class Ndb_cond_traverse_context : public Sql_alloc
+{
+ public:
+ Ndb_cond_traverse_context(TABLE *tab, const NdbDictionary::Table *ndb_tab,
+ Ndb_cond_stack* stack)
+ : table(tab), ndb_table(ndb_tab),
+ supported(TRUE), stack_ptr(stack), cond_ptr(NULL),
+ skip(0), collation(NULL), rewrite_stack(NULL)
+ {
+ // Allocate type checking bitmaps
+ my_bitmap_init(&expect_mask, 0, 512, FALSE);
+ my_bitmap_init(&expect_field_type_mask, 0, 512, FALSE);
+ my_bitmap_init(&expect_field_result_mask, 0, 512, FALSE);
+
+ if (stack)
+ cond_ptr= stack->ndb_cond;
+ };
+ ~Ndb_cond_traverse_context()
+ {
+ my_bitmap_free(&expect_mask);
+ my_bitmap_free(&expect_field_type_mask);
+ my_bitmap_free(&expect_field_result_mask);
+ if (rewrite_stack) delete rewrite_stack;
+ }
+ void expect(Item::Type type)
+ {
+ bitmap_set_bit(&expect_mask, (uint) type);
+ if (type == Item::FIELD_ITEM) expect_all_field_types();
+ };
+ void dont_expect(Item::Type type)
+ {
+ bitmap_clear_bit(&expect_mask, (uint) type);
+ };
+ bool expecting(Item::Type type)
+ {
+ return bitmap_is_set(&expect_mask, (uint) type);
+ };
+ void expect_nothing()
+ {
+ bitmap_clear_all(&expect_mask);
+ };
+ bool expecting_nothing()
+ {
+ return bitmap_is_clear_all(&expect_mask);
+ }
+ void expect_only(Item::Type type)
+ {
+ expect_nothing();
+ expect(type);
+ };
+
+ void expect_field_type(enum_field_types type)
+ {
+ bitmap_set_bit(&expect_field_type_mask, (uint) type);
+ };
+ void expect_all_field_types()
+ {
+ bitmap_set_all(&expect_field_type_mask);
+ };
+ bool expecting_field_type(enum_field_types type)
+ {
+ return bitmap_is_set(&expect_field_type_mask, (uint) type);
+ };
+ void expect_no_field_type()
+ {
+ bitmap_clear_all(&expect_field_type_mask);
+ };
+ bool expecting_no_field_type()
+ {
+ return bitmap_is_clear_all(&expect_field_type_mask);
+ }
+ void expect_only_field_type(enum_field_types result)
+ {
+ expect_no_field_type();
+ expect_field_type(result);
+ };
+
+ void expect_field_result(Item_result result)
+ {
+ bitmap_set_bit(&expect_field_result_mask, (uint) result);
+ };
+ bool expecting_field_result(Item_result result)
+ {
+ return bitmap_is_set(&expect_field_result_mask, (uint) result);
+ };
+ void expect_no_field_result()
+ {
+ bitmap_clear_all(&expect_field_result_mask);
+ };
+ bool expecting_no_field_result()
+ {
+ return bitmap_is_clear_all(&expect_field_result_mask);
+ }
+ void expect_only_field_result(Item_result result)
+ {
+ expect_no_field_result();
+ expect_field_result(result);
+ };
+ void expect_collation(CHARSET_INFO* col)
+ {
+ collation= col;
+ };
+ bool expecting_collation(CHARSET_INFO* col)
+ {
+ bool matching= (!collation) ? true : (collation == col);
+ collation= NULL;
+
+ return matching;
+ };
+
+ TABLE* table;
+ const NdbDictionary::Table *ndb_table;
+ bool supported;
+ Ndb_cond_stack* stack_ptr;
+ Ndb_cond* cond_ptr;
+ MY_BITMAP expect_mask;
+ MY_BITMAP expect_field_type_mask;
+ MY_BITMAP expect_field_result_mask;
+ uint skip;
+ CHARSET_INFO* collation;
+ Ndb_rewrite_context *rewrite_stack;
+};
+
+class ha_ndbcluster;
+
+class ha_ndbcluster_cond
+{
+public:
+ ha_ndbcluster_cond()
+ : m_cond_stack(NULL)
+ {}
+ ~ha_ndbcluster_cond()
+ { if (m_cond_stack) delete m_cond_stack; }
+ const COND *cond_push(const COND *cond,
+ TABLE *table, const NdbDictionary::Table *ndb_table);
+ void cond_pop();
+ void cond_clear();
+ int generate_scan_filter(NdbScanOperation* op);
+ int generate_scan_filter_from_cond(NdbScanFilter& filter);
+ int generate_scan_filter_from_key(NdbScanOperation* op,
+ const KEY* key_info,
+ const uchar *key,
+ uint key_len,
+ uchar *buf);
+private:
+ bool serialize_cond(const COND *cond, Ndb_cond_stack *ndb_cond,
+ TABLE *table, const NdbDictionary::Table *ndb_table);
+ int build_scan_filter_predicate(Ndb_cond* &cond,
+ NdbScanFilter* filter,
+ bool negated= false);
+ int build_scan_filter_group(Ndb_cond* &cond,
+ NdbScanFilter* filter);
+ int build_scan_filter(Ndb_cond* &cond, NdbScanFilter* filter);
+
+ Ndb_cond_stack *m_cond_stack;
+};
+
+#endif /* HA_NDBCLUSTER_COND_INCLUDED */
diff --git a/sql/ha_ndbcluster_tables.h b/sql/ha_ndbcluster_tables.h
new file mode 100644
index 00000000000..4d97ca2c254
--- /dev/null
+++ b/sql/ha_ndbcluster_tables.h
@@ -0,0 +1,29 @@
+#ifndef HA_NDBCLUSTER_TABLES_INCLUDED
+#define HA_NDBCLUSTER_TABLES_INCLUDED
+
+/* Copyright (c) 2000-2003, 2006, 2007 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+*/
+
+#define NDB_REP_DB "mysql"
+#define OLD_NDB_REP_DB "cluster"
+#define NDB_REP_TABLE "ndb_binlog_index"
+#define NDB_APPLY_TABLE "ndb_apply_status"
+#define OLD_NDB_APPLY_TABLE "apply_status"
+#define NDB_SCHEMA_TABLE "ndb_schema"
+#define OLD_NDB_SCHEMA_TABLE "schema"
+
+#endif /* HA_NDBCLUSTER_TABLES_INCLUDED */
diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc
new file mode 100644
index 00000000000..d8fc647d2a2
--- /dev/null
+++ b/sql/ha_partition.cc
@@ -0,0 +1,9122 @@
+/*
+ Copyright (c) 2005, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab & SkySQL Ab
+
+ 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
+*/
+
+/*
+ This handler was developed by Mikael Ronstrom for version 5.1 of MySQL.
+ It is an abstraction layer on top of other handlers such as MyISAM,
+ InnoDB, Federated, Berkeley DB and so forth. Partitioned tables can also
+ be handled by a storage engine. The current example of this is NDB
+ Cluster that has internally handled partitioning. This have benefits in
+ that many loops needed in the partition handler can be avoided.
+
+ Partitioning has an inherent feature which in some cases is positive and
+ in some cases is negative. It splits the data into chunks. This makes
+ the data more manageable, queries can easily be parallelised towards the
+ parts and indexes are split such that there are less levels in the
+ index trees. The inherent disadvantage is that to use a split index
+ one has to scan all index parts which is ok for large queries but for
+ small queries it can be a disadvantage.
+
+ Partitioning lays the foundation for more manageable databases that are
+ extremely large. It does also lay the foundation for more parallelism
+ in the execution of queries. This functionality will grow with later
+ versions of MySQL.
+
+ The partition is setup to use table locks. It implements an partition "SHARE"
+ that is inserted into a hash by table name. You can use this to store
+ information of state that any partition handler object will be able to see
+ if it is using the same table.
+
+ Please read the object definition in ha_partition.h before reading the rest
+ if this file.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_parse.h" // append_file_to_dir
+#include "create_options.h"
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+#include "ha_partition.h"
+#include "sql_table.h" // tablename_to_filename
+#include "key.h"
+#include "sql_plugin.h"
+#include "sql_show.h" // append_identifier
+#include "sql_admin.h" // SQL_ADMIN_MSG_TEXT_SIZE
+
+#include "debug_sync.h"
+
+/* First 4 bytes in the .par file is the number of 32-bit words in the file */
+#define PAR_WORD_SIZE 4
+/* offset to the .par file checksum */
+#define PAR_CHECKSUM_OFFSET 4
+/* offset to the total number of partitions */
+#define PAR_NUM_PARTS_OFFSET 8
+/* offset to the engines array */
+#define PAR_ENGINES_OFFSET 12
+#define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | \
+ HA_REC_NOT_IN_SEQ | \
+ HA_CAN_REPAIR)
+#define PARTITION_DISABLED_TABLE_FLAGS (HA_CAN_GEOMETRY | \
+ HA_CAN_FULLTEXT | \
+ HA_DUPLICATE_POS | \
+ HA_CAN_SQL_HANDLER | \
+ HA_CAN_INSERT_DELAYED | \
+ HA_READ_BEFORE_WRITE_REMOVAL)
+static const char *ha_par_ext= ".par";
+
+/****************************************************************************
+ MODULE create/delete handler object
+****************************************************************************/
+
+static handler *partition_create_handler(handlerton *hton,
+ TABLE_SHARE *share,
+ MEM_ROOT *mem_root);
+static uint partition_flags();
+static uint alter_table_flags(uint flags);
+
+extern "C" int cmp_key_part_id(void *key_p, uchar *ref1, uchar *ref2);
+extern "C" int cmp_key_rowid_part_id(void *ptr, uchar *ref1, uchar *ref2);
+
+/*
+ If frm_error() is called then we will use this to to find out what file
+ extensions exist for the storage engine. This is also used by the default
+ rename_table and delete_table method in handler.cc.
+*/
+static const char *ha_partition_ext[]=
+{
+ ha_par_ext, NullS
+};
+
+
+#ifdef HAVE_PSI_INTERFACE
+PSI_mutex_key key_partition_auto_inc_mutex;
+
+static PSI_mutex_info all_partition_mutexes[]=
+{
+ { &key_partition_auto_inc_mutex, "Partition_share::auto_inc_mutex", 0}
+};
+
+static void init_partition_psi_keys(void)
+{
+ const char* category= "partition";
+ int count;
+
+ count= array_elements(all_partition_mutexes);
+ mysql_mutex_register(category, all_partition_mutexes, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+static int partition_initialize(void *p)
+{
+
+ handlerton *partition_hton;
+ partition_hton= (handlerton *)p;
+
+ partition_hton->state= SHOW_OPTION_YES;
+ partition_hton->db_type= DB_TYPE_PARTITION_DB;
+ partition_hton->create= partition_create_handler;
+ partition_hton->partition_flags= partition_flags;
+ partition_hton->alter_table_flags= alter_table_flags;
+ partition_hton->flags= HTON_NOT_USER_SELECTABLE |
+ HTON_HIDDEN |
+ HTON_TEMPORARY_NOT_SUPPORTED;
+ partition_hton->tablefile_extensions= ha_partition_ext;
+
+#ifdef HAVE_PSI_INTERFACE
+ init_partition_psi_keys();
+#endif
+ return 0;
+}
+
+
+/**
+ Initialize and allocate space for partitions shares.
+
+ @param num_parts Number of partitions to allocate storage for.
+
+ @return Operation status.
+ @retval true Failure (out of memory).
+ @retval false Success.
+*/
+
+bool Partition_share::init(uint num_parts)
+{
+ DBUG_ENTER("Partition_share::init");
+ mysql_mutex_init(key_partition_auto_inc_mutex,
+ &auto_inc_mutex,
+ MY_MUTEX_INIT_FAST);
+ auto_inc_initialized= false;
+ partition_name_hash_initialized= false;
+ next_auto_inc_val= 0;
+ partitions_share_refs= new Parts_share_refs;
+ if (!partitions_share_refs)
+ DBUG_RETURN(true);
+ if (partitions_share_refs->init(num_parts))
+ {
+ delete partitions_share_refs;
+ DBUG_RETURN(true);
+ }
+ DBUG_RETURN(false);
+}
+
+
+/*
+ Create new partition handler
+
+ SYNOPSIS
+ partition_create_handler()
+ table Table object
+
+ RETURN VALUE
+ New partition object
+*/
+
+static handler *partition_create_handler(handlerton *hton,
+ TABLE_SHARE *share,
+ MEM_ROOT *mem_root)
+{
+ ha_partition *file= new (mem_root) ha_partition(hton, share);
+ if (file && file->initialize_partition(mem_root))
+ {
+ delete file;
+ file= 0;
+ }
+ return file;
+}
+
+/*
+ HA_CAN_PARTITION:
+ Used by storage engines that can handle partitioning without this
+ partition handler
+ (Partition, NDB)
+
+ HA_CAN_UPDATE_PARTITION_KEY:
+ Set if the handler can update fields that are part of the partition
+ function.
+
+ HA_CAN_PARTITION_UNIQUE:
+ Set if the handler can handle unique indexes where the fields of the
+ unique key are not part of the fields of the partition function. Thus
+ a unique key can be set on all fields.
+
+ HA_USE_AUTO_PARTITION
+ Set if the handler sets all tables to be partitioned by default.
+*/
+
+static uint partition_flags()
+{
+ return HA_CAN_PARTITION;
+}
+
+static uint alter_table_flags(uint flags __attribute__((unused)))
+{
+ return (HA_PARTITION_FUNCTION_SUPPORTED |
+ HA_FAST_CHANGE_PARTITION);
+}
+
+const uint32 ha_partition::NO_CURRENT_PART_ID= NOT_A_PARTITION_ID;
+
+/*
+ Constructor method
+
+ SYNOPSIS
+ ha_partition()
+ table Table object
+
+ RETURN VALUE
+ NONE
+*/
+
+ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share)
+ :handler(hton, share)
+{
+ DBUG_ENTER("ha_partition::ha_partition(table)");
+ init_alloc_root(&m_mem_root, 512, 512, MYF(0));
+ init_handler_variables();
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Constructor method
+
+ SYNOPSIS
+ ha_partition()
+ part_info Partition info
+
+ RETURN VALUE
+ NONE
+*/
+
+ha_partition::ha_partition(handlerton *hton, partition_info *part_info)
+ :handler(hton, NULL)
+{
+ DBUG_ENTER("ha_partition::ha_partition(part_info)");
+ DBUG_ASSERT(part_info);
+ init_alloc_root(&m_mem_root, 512, 512, MYF(0));
+ init_handler_variables();
+ m_part_info= part_info;
+ m_create_handler= TRUE;
+ m_is_sub_partitioned= m_part_info->is_sub_partitioned();
+ DBUG_VOID_RETURN;
+}
+
+/**
+ ha_partition constructor method used by ha_partition::clone()
+
+ @param hton Handlerton (partition_hton)
+ @param share Table share object
+ @param part_info_arg partition_info to use
+ @param clone_arg ha_partition to clone
+ @param clme_mem_root_arg MEM_ROOT to use
+
+ @return New partition handler
+*/
+
+ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share,
+ partition_info *part_info_arg,
+ ha_partition *clone_arg,
+ MEM_ROOT *clone_mem_root_arg)
+ :handler(hton, share)
+{
+ DBUG_ENTER("ha_partition::ha_partition(clone)");
+ init_alloc_root(&m_mem_root, 512, 512, MYF(0));
+ init_handler_variables();
+ m_part_info= part_info_arg;
+ m_create_handler= TRUE;
+ m_is_sub_partitioned= m_part_info->is_sub_partitioned();
+ m_is_clone_of= clone_arg;
+ m_clone_mem_root= clone_mem_root_arg;
+ part_share= clone_arg->part_share;
+ m_tot_parts= clone_arg->m_tot_parts;
+ m_pkey_is_clustered= clone_arg->primary_key_is_clustered();
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Initialize handler object
+
+ SYNOPSIS
+ init_handler_variables()
+
+ RETURN VALUE
+ NONE
+*/
+
+void ha_partition::init_handler_variables()
+{
+ active_index= MAX_KEY;
+ m_mode= 0;
+ m_open_test_lock= 0;
+ m_file_buffer= NULL;
+ m_name_buffer_ptr= NULL;
+ m_engine_array= NULL;
+ m_connect_string= NULL;
+ m_file= NULL;
+ m_file_tot_parts= 0;
+ m_reorged_file= NULL;
+ m_new_file= NULL;
+ m_reorged_parts= 0;
+ m_added_file= NULL;
+ m_tot_parts= 0;
+ m_pkey_is_clustered= 0;
+ m_part_spec.start_part= NO_CURRENT_PART_ID;
+ m_scan_value= 2;
+ m_ref_length= 0;
+ m_part_spec.end_part= NO_CURRENT_PART_ID;
+ m_index_scan_type= partition_no_index_scan;
+ m_start_key.key= NULL;
+ m_start_key.length= 0;
+ m_myisam= FALSE;
+ m_innodb= FALSE;
+ m_extra_cache= FALSE;
+ m_extra_cache_size= 0;
+ m_extra_prepare_for_update= FALSE;
+ m_extra_cache_part_id= NO_CURRENT_PART_ID;
+ m_handler_status= handler_not_initialized;
+ m_part_field_array= NULL;
+ m_ordered_rec_buffer= NULL;
+ m_top_entry= NO_CURRENT_PART_ID;
+ m_rec_length= 0;
+ m_last_part= 0;
+ m_rec0= 0;
+ m_err_rec= NULL;
+ m_curr_key_info[0]= NULL;
+ m_curr_key_info[1]= NULL;
+ m_part_func_monotonicity_info= NON_MONOTONIC;
+ auto_increment_lock= FALSE;
+ auto_increment_safe_stmt_log_lock= FALSE;
+ /*
+ this allows blackhole to work properly
+ */
+ m_num_locks= 0;
+ m_part_info= NULL;
+ m_create_handler= FALSE;
+ m_is_sub_partitioned= 0;
+ m_is_clone_of= NULL;
+ m_clone_mem_root= NULL;
+ part_share= NULL;
+ m_new_partitions_share_refs.empty();
+ m_part_ids_sorted_by_num_of_records= NULL;
+
+#ifdef DONT_HAVE_TO_BE_INITALIZED
+ m_start_key.flag= 0;
+ m_ordered= TRUE;
+#endif
+}
+
+
+const char *ha_partition::table_type() const
+{
+ // we can do this since we only support a single engine type
+ return m_file[0]->table_type();
+}
+
+
+/*
+ Destructor method
+
+ SYNOPSIS
+ ~ha_partition()
+
+ RETURN VALUE
+ NONE
+*/
+
+ha_partition::~ha_partition()
+{
+ DBUG_ENTER("ha_partition::~ha_partition()");
+ if (m_new_partitions_share_refs.elements)
+ m_new_partitions_share_refs.delete_elements();
+ if (m_file != NULL)
+ {
+ uint i;
+ for (i= 0; i < m_tot_parts; i++)
+ delete m_file[i];
+ }
+ destroy_record_priority_queue();
+ my_free(m_part_ids_sorted_by_num_of_records);
+
+ clear_handler_file();
+
+ free_root(&m_mem_root, MYF(0));
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Initialize partition handler object
+
+ SYNOPSIS
+ initialize_partition()
+ mem_root Allocate memory through this
+
+ RETURN VALUE
+ 1 Error
+ 0 Success
+
+ DESCRIPTION
+
+ The partition handler is only a layer on top of other engines. Thus it
+ can't really perform anything without the underlying handlers. Thus we
+ add this method as part of the allocation of a handler object.
+
+ 1) Allocation of underlying handlers
+ If we have access to the partition info we will allocate one handler
+ instance for each partition.
+ 2) Allocation without partition info
+ The cases where we don't have access to this information is when called
+ in preparation for delete_table and rename_table and in that case we
+ only need to set HA_FILE_BASED. In that case we will use the .par file
+ that contains information about the partitions and their engines and
+ the names of each partition.
+ 3) Table flags initialisation
+ We need also to set table flags for the partition handler. This is not
+ static since it depends on what storage engines are used as underlying
+ handlers.
+ The table flags is set in this routine to simulate the behaviour of a
+ normal storage engine
+ The flag HA_FILE_BASED will be set independent of the underlying handlers
+ 4) Index flags initialisation
+ When knowledge exists on the indexes it is also possible to initialize the
+ index flags. Again the index flags must be initialized by using the under-
+ lying handlers since this is storage engine dependent.
+ The flag HA_READ_ORDER will be reset for the time being to indicate no
+ ordered output is available from partition handler indexes. Later a merge
+ sort will be performed using the underlying handlers.
+ 5) primary_key_is_clustered and has_transactions are
+ calculated here.
+
+*/
+
+bool ha_partition::initialize_partition(MEM_ROOT *mem_root)
+{
+ handler **file_array, *file;
+ ulonglong check_table_flags;
+ DBUG_ENTER("ha_partition::initialize_partition");
+
+ if (m_create_handler)
+ {
+ m_tot_parts= m_part_info->get_tot_partitions();
+ DBUG_ASSERT(m_tot_parts > 0);
+ if (new_handlers_from_part_info(mem_root))
+ DBUG_RETURN(1);
+ }
+ else if (!table_share || !table_share->normalized_path.str)
+ {
+ /*
+ Called with dummy table share (delete, rename and alter table).
+ Don't need to set-up anything.
+ */
+ DBUG_RETURN(0);
+ }
+ else if (get_from_handler_file(table_share->normalized_path.str,
+ mem_root, false))
+ {
+ my_error(ER_FAILED_READ_FROM_PAR_FILE, MYF(0));
+ DBUG_RETURN(1);
+ }
+ /*
+ We create all underlying table handlers here. We do it in this special
+ method to be able to report allocation errors.
+
+ Set up primary_key_is_clustered and
+ has_transactions since they are called often in all kinds of places,
+ other parameters are calculated on demand.
+ Verify that all partitions have the same table_flags.
+ */
+ check_table_flags= m_file[0]->ha_table_flags();
+ m_pkey_is_clustered= TRUE;
+ file_array= m_file;
+ do
+ {
+ file= *file_array;
+ if (!file->primary_key_is_clustered())
+ m_pkey_is_clustered= FALSE;
+ if (check_table_flags != file->ha_table_flags())
+ {
+ my_error(ER_MIX_HANDLER_ERROR, MYF(0));
+ DBUG_RETURN(1);
+ }
+ } while (*(++file_array));
+ m_handler_status= handler_initialized;
+ DBUG_RETURN(0);
+}
+
+/****************************************************************************
+ MODULE meta data changes
+****************************************************************************/
+/*
+ Delete a table
+
+ SYNOPSIS
+ delete_table()
+ name Full path of table name
+
+ RETURN VALUE
+ >0 Error
+ 0 Success
+
+ DESCRIPTION
+ Used to delete a table. By the time delete_table() has been called all
+ opened references to this table will have been closed (and your globally
+ shared references released. The variable name will just be the name of
+ the table. You will need to remove any files you have created at this
+ point.
+
+ If you do not implement this, the default delete_table() is called from
+ handler.cc and it will delete all files with the file extentions returned
+ by bas_ext().
+
+ Called from handler.cc by delete_table and ha_create_table(). Only used
+ during create if the table_flag HA_DROP_BEFORE_CREATE was specified for
+ the storage engine.
+*/
+
+int ha_partition::delete_table(const char *name)
+{
+ DBUG_ENTER("ha_partition::delete_table");
+
+ DBUG_RETURN(del_ren_table(name, NULL));
+}
+
+
+/*
+ Rename a table
+
+ SYNOPSIS
+ rename_table()
+ from Full path of old table name
+ to Full path of new table name
+
+ RETURN VALUE
+ >0 Error
+ 0 Success
+
+ DESCRIPTION
+ Renames a table from one name to another from alter table call.
+
+ If you do not implement this, the default rename_table() is called from
+ handler.cc and it will rename all files with the file extentions returned
+ by bas_ext().
+
+ Called from sql_table.cc by mysql_rename_table().
+*/
+
+int ha_partition::rename_table(const char *from, const char *to)
+{
+ DBUG_ENTER("ha_partition::rename_table");
+
+ DBUG_RETURN(del_ren_table(from, to));
+}
+
+
+/*
+ Create the handler file (.par-file)
+
+ SYNOPSIS
+ create_partitioning_metadata()
+ name Full path of table name
+ create_info Create info generated for CREATE TABLE
+
+ RETURN VALUE
+ >0 Error
+ 0 Success
+
+ DESCRIPTION
+ create_partitioning_metadata is called to create any handler specific files
+ before opening the file with openfrm to later call ::create on the
+ file object.
+ In the partition handler this is used to store the names of partitions
+ and types of engines in the partitions.
+*/
+
+int ha_partition::create_partitioning_metadata(const char *path,
+ const char *old_path,
+ int action_flag)
+{
+ DBUG_ENTER("ha_partition::create_partitioning_metadata()");
+
+ /*
+ We need to update total number of parts since we might write the handler
+ file as part of a partition management command
+ */
+ if (action_flag == CHF_DELETE_FLAG ||
+ action_flag == CHF_RENAME_FLAG)
+ {
+ char name[FN_REFLEN];
+ char old_name[FN_REFLEN];
+
+ strxmov(name, path, ha_par_ext, NullS);
+ strxmov(old_name, old_path, ha_par_ext, NullS);
+ if ((action_flag == CHF_DELETE_FLAG &&
+ mysql_file_delete(key_file_partition, name, MYF(MY_WME))) ||
+ (action_flag == CHF_RENAME_FLAG &&
+ mysql_file_rename(key_file_partition, old_name, name, MYF(MY_WME))))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else if (action_flag == CHF_CREATE_FLAG)
+ {
+ if (create_handler_file(path))
+ {
+ my_error(ER_CANT_CREATE_HANDLER_FILE, MYF(0));
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Create a partitioned table
+
+ SYNOPSIS
+ create()
+ name Full path of table name
+ table_arg Table object
+ create_info Create info generated for CREATE TABLE
+
+ RETURN VALUE
+ >0 Error
+ 0 Success
+
+ DESCRIPTION
+ create() is called to create a table. The variable name will have the name
+ of the table. When create() is called you do not need to worry about
+ opening the table. Also, the FRM file will have already been created so
+ adjusting create_info will not do you any good. You can overwrite the frm
+ file at this point if you wish to change the table definition, but there
+ are no methods currently provided for doing that.
+
+ Called from handler.cc by ha_create_table().
+*/
+
+int ha_partition::create(const char *name, TABLE *table_arg,
+ HA_CREATE_INFO *create_info)
+{
+ int error;
+ char name_buff[FN_REFLEN], name_lc_buff[FN_REFLEN];
+ char *name_buffer_ptr;
+ const char *path;
+ uint i;
+ List_iterator_fast <partition_element> part_it(m_part_info->partitions);
+ partition_element *part_elem;
+ handler **file, **abort_file;
+ DBUG_ENTER("ha_partition::create");
+
+ DBUG_ASSERT(*fn_rext((char*)name) == '\0');
+
+ /* Not allowed to create temporary partitioned tables */
+ if (create_info && create_info->tmp_table())
+ {
+ my_error(ER_PARTITION_NO_TEMPORARY, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (get_from_handler_file(name, ha_thd()->mem_root, false))
+ DBUG_RETURN(TRUE);
+ DBUG_ASSERT(m_file_buffer);
+ DBUG_PRINT("enter", ("name: (%s)", name));
+ name_buffer_ptr= m_name_buffer_ptr;
+ file= m_file;
+ /*
+ Since ha_partition has HA_FILE_BASED, it must alter underlying table names
+ if they do not have HA_FILE_BASED and lower_case_table_names == 2.
+ See Bug#37402, for Mac OS X.
+ The appended #P#<partname>[#SP#<subpartname>] will remain in current case.
+ Using the first partitions handler, since mixing handlers is not allowed.
+ */
+ path= get_canonical_filename(*file, name, name_lc_buff);
+ for (i= 0; i < m_part_info->num_parts; i++)
+ {
+ part_elem= part_it++;
+ if (m_is_sub_partitioned)
+ {
+ uint j;
+ List_iterator_fast <partition_element> sub_it(part_elem->subpartitions);
+ for (j= 0; j < m_part_info->num_subparts; j++)
+ {
+ part_elem= sub_it++;
+ create_partition_name(name_buff, path, name_buffer_ptr,
+ NORMAL_PART_NAME, FALSE);
+ if ((error= set_up_table_before_create(table_arg, name_buff,
+ create_info, part_elem)) ||
+ ((error= (*file)->ha_create(name_buff, table_arg, create_info))))
+ goto create_error;
+
+ name_buffer_ptr= strend(name_buffer_ptr) + 1;
+ file++;
+ }
+ }
+ else
+ {
+ create_partition_name(name_buff, path, name_buffer_ptr,
+ NORMAL_PART_NAME, FALSE);
+ if ((error= set_up_table_before_create(table_arg, name_buff,
+ create_info, part_elem)) ||
+ ((error= (*file)->ha_create(name_buff, table_arg, create_info))))
+ goto create_error;
+
+ name_buffer_ptr= strend(name_buffer_ptr) + 1;
+ file++;
+ }
+ }
+ DBUG_RETURN(0);
+
+create_error:
+ name_buffer_ptr= m_name_buffer_ptr;
+ for (abort_file= file, file= m_file; file < abort_file; file++)
+ {
+ create_partition_name(name_buff, path, name_buffer_ptr, NORMAL_PART_NAME,
+ FALSE);
+ (void) (*file)->ha_delete_table((const char*) name_buff);
+ name_buffer_ptr= strend(name_buffer_ptr) + 1;
+ }
+ handler::delete_table(name);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Drop partitions as part of ALTER TABLE of partitions
+
+ SYNOPSIS
+ drop_partitions()
+ path Complete path of db and table name
+
+ RETURN VALUE
+ >0 Failure
+ 0 Success
+
+ DESCRIPTION
+ Use part_info object on handler object to deduce which partitions to
+ drop (each partition has a state attached to it)
+*/
+
+int ha_partition::drop_partitions(const char *path)
+{
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ char part_name_buff[FN_REFLEN];
+ uint num_parts= m_part_info->partitions.elements;
+ uint num_subparts= m_part_info->num_subparts;
+ uint i= 0;
+ uint name_variant;
+ int ret_error;
+ int error= 0;
+ DBUG_ENTER("ha_partition::drop_partitions");
+
+ /*
+ Assert that it works without HA_FILE_BASED and lower_case_table_name = 2.
+ We use m_file[0] as long as all partitions have the same storage engine.
+ */
+ DBUG_ASSERT(!strcmp(path, get_canonical_filename(m_file[0], path,
+ part_name_buff)));
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_TO_BE_DROPPED)
+ {
+ handler *file;
+ /*
+ This part is to be dropped, meaning the part or all its subparts.
+ */
+ name_variant= NORMAL_PART_NAME;
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ uint j= 0, part;
+ do
+ {
+ partition_element *sub_elem= sub_it++;
+ part= i * num_subparts + j;
+ create_subpartition_name(part_name_buff, path,
+ part_elem->partition_name,
+ sub_elem->partition_name, name_variant);
+ file= m_file[part];
+ DBUG_PRINT("info", ("Drop subpartition %s", part_name_buff));
+ if ((ret_error= file->ha_delete_table(part_name_buff)))
+ error= ret_error;
+ if (deactivate_ddl_log_entry(sub_elem->log_entry->entry_pos))
+ error= 1;
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ create_partition_name(part_name_buff, path,
+ part_elem->partition_name, name_variant,
+ TRUE);
+ file= m_file[i];
+ DBUG_PRINT("info", ("Drop partition %s", part_name_buff));
+ if ((ret_error= file->ha_delete_table(part_name_buff)))
+ error= ret_error;
+ if (deactivate_ddl_log_entry(part_elem->log_entry->entry_pos))
+ error= 1;
+ }
+ if (part_elem->part_state == PART_IS_CHANGED)
+ part_elem->part_state= PART_NORMAL;
+ else
+ part_elem->part_state= PART_IS_DROPPED;
+ }
+ } while (++i < num_parts);
+ (void) sync_ddl_log();
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Rename partitions as part of ALTER TABLE of partitions
+
+ SYNOPSIS
+ rename_partitions()
+ path Complete path of db and table name
+
+ RETURN VALUE
+ TRUE Failure
+ FALSE Success
+
+ DESCRIPTION
+ When reorganising partitions, adding hash partitions and coalescing
+ partitions it can be necessary to rename partitions while holding
+ an exclusive lock on the table.
+ Which partitions to rename is given by state of partitions found by the
+ partition info struct referenced from the handler object
+*/
+
+int ha_partition::rename_partitions(const char *path)
+{
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ List_iterator<partition_element> temp_it(m_part_info->temp_partitions);
+ char part_name_buff[FN_REFLEN];
+ char norm_name_buff[FN_REFLEN];
+ uint num_parts= m_part_info->partitions.elements;
+ uint part_count= 0;
+ uint num_subparts= m_part_info->num_subparts;
+ uint i= 0;
+ uint j= 0;
+ int error= 0;
+ int ret_error;
+ uint temp_partitions= m_part_info->temp_partitions.elements;
+ handler *file;
+ partition_element *part_elem, *sub_elem;
+ DBUG_ENTER("ha_partition::rename_partitions");
+
+ /*
+ Assert that it works without HA_FILE_BASED and lower_case_table_name = 2.
+ We use m_file[0] as long as all partitions have the same storage engine.
+ */
+ DBUG_ASSERT(!strcmp(path, get_canonical_filename(m_file[0], path,
+ norm_name_buff)));
+
+ DEBUG_SYNC(ha_thd(), "before_rename_partitions");
+ if (temp_partitions)
+ {
+ /*
+ These are the reorganised partitions that have already been copied.
+ We delete the partitions and log the delete by inactivating the
+ delete log entry in the table log. We only need to synchronise
+ these writes before moving to the next loop since there is no
+ interaction among reorganised partitions, they cannot have the
+ same name.
+ */
+ do
+ {
+ part_elem= temp_it++;
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ j= 0;
+ do
+ {
+ sub_elem= sub_it++;
+ file= m_reorged_file[part_count++];
+ create_subpartition_name(norm_name_buff, path,
+ part_elem->partition_name,
+ sub_elem->partition_name,
+ NORMAL_PART_NAME);
+ DBUG_PRINT("info", ("Delete subpartition %s", norm_name_buff));
+ if ((ret_error= file->ha_delete_table(norm_name_buff)))
+ error= ret_error;
+ else if (deactivate_ddl_log_entry(sub_elem->log_entry->entry_pos))
+ error= 1;
+ else
+ sub_elem->log_entry= NULL; /* Indicate success */
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ file= m_reorged_file[part_count++];
+ create_partition_name(norm_name_buff, path,
+ part_elem->partition_name, NORMAL_PART_NAME,
+ TRUE);
+ DBUG_PRINT("info", ("Delete partition %s", norm_name_buff));
+ if ((ret_error= file->ha_delete_table(norm_name_buff)))
+ error= ret_error;
+ else if (deactivate_ddl_log_entry(part_elem->log_entry->entry_pos))
+ error= 1;
+ else
+ part_elem->log_entry= NULL; /* Indicate success */
+ }
+ } while (++i < temp_partitions);
+ (void) sync_ddl_log();
+ }
+ i= 0;
+ do
+ {
+ /*
+ When state is PART_IS_CHANGED it means that we have created a new
+ TEMP partition that is to be renamed to normal partition name and
+ we are to delete the old partition with currently the normal name.
+
+ We perform this operation by
+ 1) Delete old partition with normal partition name
+ 2) Signal this in table log entry
+ 3) Synch table log to ensure we have consistency in crashes
+ 4) Rename temporary partition name to normal partition name
+ 5) Signal this to table log entry
+ It is not necessary to synch the last state since a new rename
+ should not corrupt things if there was no temporary partition.
+
+ The only other parts we need to cater for are new parts that
+ replace reorganised parts. The reorganised parts were deleted
+ by the code above that goes through the temp_partitions list.
+ Thus the synch above makes it safe to simply perform step 4 and 5
+ for those entries.
+ */
+ part_elem= part_it++;
+ if (part_elem->part_state == PART_IS_CHANGED ||
+ part_elem->part_state == PART_TO_BE_DROPPED ||
+ (part_elem->part_state == PART_IS_ADDED && temp_partitions))
+ {
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ uint part;
+
+ j= 0;
+ do
+ {
+ sub_elem= sub_it++;
+ part= i * num_subparts + j;
+ create_subpartition_name(norm_name_buff, path,
+ part_elem->partition_name,
+ sub_elem->partition_name,
+ NORMAL_PART_NAME);
+ if (part_elem->part_state == PART_IS_CHANGED)
+ {
+ file= m_reorged_file[part_count++];
+ DBUG_PRINT("info", ("Delete subpartition %s", norm_name_buff));
+ if ((ret_error= file->ha_delete_table(norm_name_buff)))
+ error= ret_error;
+ else if (deactivate_ddl_log_entry(sub_elem->log_entry->entry_pos))
+ error= 1;
+ (void) sync_ddl_log();
+ }
+ file= m_new_file[part];
+ create_subpartition_name(part_name_buff, path,
+ part_elem->partition_name,
+ sub_elem->partition_name,
+ TEMP_PART_NAME);
+ DBUG_PRINT("info", ("Rename subpartition from %s to %s",
+ part_name_buff, norm_name_buff));
+ if ((ret_error= file->ha_rename_table(part_name_buff,
+ norm_name_buff)))
+ error= ret_error;
+ else if (deactivate_ddl_log_entry(sub_elem->log_entry->entry_pos))
+ error= 1;
+ else
+ sub_elem->log_entry= NULL;
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ create_partition_name(norm_name_buff, path,
+ part_elem->partition_name, NORMAL_PART_NAME,
+ TRUE);
+ if (part_elem->part_state == PART_IS_CHANGED)
+ {
+ file= m_reorged_file[part_count++];
+ DBUG_PRINT("info", ("Delete partition %s", norm_name_buff));
+ if ((ret_error= file->ha_delete_table(norm_name_buff)))
+ error= ret_error;
+ else if (deactivate_ddl_log_entry(part_elem->log_entry->entry_pos))
+ error= 1;
+ (void) sync_ddl_log();
+ }
+ file= m_new_file[i];
+ create_partition_name(part_name_buff, path,
+ part_elem->partition_name, TEMP_PART_NAME,
+ TRUE);
+ DBUG_PRINT("info", ("Rename partition from %s to %s",
+ part_name_buff, norm_name_buff));
+ if ((ret_error= file->ha_rename_table(part_name_buff,
+ norm_name_buff)))
+ error= ret_error;
+ else if (deactivate_ddl_log_entry(part_elem->log_entry->entry_pos))
+ error= 1;
+ else
+ part_elem->log_entry= NULL;
+ }
+ }
+ } while (++i < num_parts);
+ (void) sync_ddl_log();
+ DBUG_RETURN(error);
+}
+
+
+#define OPTIMIZE_PARTS 1
+#define ANALYZE_PARTS 2
+#define CHECK_PARTS 3
+#define REPAIR_PARTS 4
+#define ASSIGN_KEYCACHE_PARTS 5
+#define PRELOAD_KEYS_PARTS 6
+
+static const char *opt_op_name[]= {NULL,
+ "optimize", "analyze", "check", "repair",
+ "assign_to_keycache", "preload_keys"};
+
+/*
+ Optimize table
+
+ SYNOPSIS
+ optimize()
+ thd Thread object
+ check_opt Check/analyze/repair/optimize options
+
+ RETURN VALUES
+ >0 Error
+ 0 Success
+*/
+
+int ha_partition::optimize(THD *thd, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("ha_partition::optimize");
+
+ DBUG_RETURN(handle_opt_partitions(thd, check_opt, OPTIMIZE_PARTS));
+}
+
+
+/*
+ Analyze table
+
+ SYNOPSIS
+ analyze()
+ thd Thread object
+ check_opt Check/analyze/repair/optimize options
+
+ RETURN VALUES
+ >0 Error
+ 0 Success
+*/
+
+int ha_partition::analyze(THD *thd, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("ha_partition::analyze");
+
+ DBUG_RETURN(handle_opt_partitions(thd, check_opt, ANALYZE_PARTS));
+}
+
+
+/*
+ Check table
+
+ SYNOPSIS
+ check()
+ thd Thread object
+ check_opt Check/analyze/repair/optimize options
+
+ RETURN VALUES
+ >0 Error
+ 0 Success
+*/
+
+int ha_partition::check(THD *thd, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("ha_partition::check");
+
+ DBUG_RETURN(handle_opt_partitions(thd, check_opt, CHECK_PARTS));
+}
+
+
+/*
+ Repair table
+
+ SYNOPSIS
+ repair()
+ thd Thread object
+ check_opt Check/analyze/repair/optimize options
+
+ RETURN VALUES
+ >0 Error
+ 0 Success
+*/
+
+int ha_partition::repair(THD *thd, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("ha_partition::repair");
+
+ int res= handle_opt_partitions(thd, check_opt, REPAIR_PARTS);
+ DBUG_RETURN(res);
+}
+
+/**
+ Assign to keycache
+
+ @param thd Thread object
+ @param check_opt Check/analyze/repair/optimize options
+
+ @return
+ @retval >0 Error
+ @retval 0 Success
+*/
+
+int ha_partition::assign_to_keycache(THD *thd, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("ha_partition::assign_to_keycache");
+
+ DBUG_RETURN(handle_opt_partitions(thd, check_opt, ASSIGN_KEYCACHE_PARTS));
+}
+
+
+/**
+ Preload to keycache
+
+ @param thd Thread object
+ @param check_opt Check/analyze/repair/optimize options
+
+ @return
+ @retval >0 Error
+ @retval 0 Success
+*/
+
+int ha_partition::preload_keys(THD *thd, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("ha_partition::preload_keys");
+
+ DBUG_RETURN(handle_opt_partitions(thd, check_opt, PRELOAD_KEYS_PARTS));
+}
+
+
+/*
+ Handle optimize/analyze/check/repair of one partition
+
+ SYNOPSIS
+ handle_opt_part()
+ thd Thread object
+ check_opt Options
+ file Handler object of partition
+ flag Optimize/Analyze/Check/Repair flag
+
+ RETURN VALUE
+ >0 Failure
+ 0 Success
+*/
+
+int ha_partition::handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt,
+ uint part_id, uint flag)
+{
+ int error;
+ handler *file= m_file[part_id];
+ DBUG_ENTER("handle_opt_part");
+ DBUG_PRINT("enter", ("flag = %u", flag));
+
+ if (flag == OPTIMIZE_PARTS)
+ error= file->ha_optimize(thd, check_opt);
+ else if (flag == ANALYZE_PARTS)
+ error= file->ha_analyze(thd, check_opt);
+ else if (flag == CHECK_PARTS)
+ {
+ error= file->ha_check(thd, check_opt);
+ if (!error ||
+ error == HA_ADMIN_ALREADY_DONE ||
+ error == HA_ADMIN_NOT_IMPLEMENTED)
+ {
+ if (check_opt->flags & (T_MEDIUM | T_EXTEND))
+ error= check_misplaced_rows(part_id, false);
+ }
+ }
+ else if (flag == REPAIR_PARTS)
+ {
+ error= file->ha_repair(thd, check_opt);
+ if (!error ||
+ error == HA_ADMIN_ALREADY_DONE ||
+ error == HA_ADMIN_NOT_IMPLEMENTED)
+ {
+ if (check_opt->flags & (T_MEDIUM | T_EXTEND))
+ error= check_misplaced_rows(part_id, true);
+ }
+ }
+ else if (flag == ASSIGN_KEYCACHE_PARTS)
+ error= file->assign_to_keycache(thd, check_opt);
+ else if (flag == PRELOAD_KEYS_PARTS)
+ error= file->preload_keys(thd, check_opt);
+ else
+ {
+ DBUG_ASSERT(FALSE);
+ error= 1;
+ }
+ if (error == HA_ADMIN_ALREADY_DONE)
+ error= 0;
+ DBUG_RETURN(error);
+}
+
+
+/*
+ print a message row formatted for ANALYZE/CHECK/OPTIMIZE/REPAIR TABLE
+ (modelled after mi_check_print_msg)
+ TODO: move this into the handler, or rewrite mysql_admin_table.
+*/
+static bool print_admin_msg(THD* thd, uint len,
+ const char* msg_type,
+ const char* db_name, String &table_name,
+ const char* op_name, const char *fmt, ...)
+ ATTRIBUTE_FORMAT(printf, 7, 8);
+static bool print_admin_msg(THD* thd, uint len,
+ const char* msg_type,
+ const char* db_name, String &table_name,
+ const char* op_name, const char *fmt, ...)
+{
+ va_list args;
+ Protocol *protocol= thd->protocol;
+ uint length;
+ uint msg_length;
+ char name[NAME_LEN*2+2];
+ char *msgbuf;
+ bool error= true;
+
+ if (!(msgbuf= (char*) my_malloc(len, MYF(0))))
+ return true;
+ va_start(args, fmt);
+ msg_length= my_vsnprintf(msgbuf, len, fmt, args);
+ va_end(args);
+ if (msg_length >= (len - 1))
+ goto err;
+ msgbuf[len - 1] = 0; // healthy paranoia
+
+
+ if (!thd->vio_ok())
+ {
+ sql_print_error("%s", msgbuf);
+ goto err;
+ }
+
+ length=(uint) (strxmov(name, db_name, ".", table_name.c_ptr_safe(), NullS) - name);
+ /*
+ TODO: switch from protocol to push_warning here. The main reason we didn't
+ it yet is parallel repair. Due to following trace:
+ mi_check_print_msg/push_warning/sql_alloc/my_pthread_getspecific_ptr.
+
+ Also we likely need to lock mutex here (in both cases with protocol and
+ push_warning).
+ */
+ DBUG_PRINT("info",("print_admin_msg: %s, %s, %s, %s", name, op_name,
+ msg_type, msgbuf));
+ protocol->prepare_for_resend();
+ protocol->store(name, length, system_charset_info);
+ protocol->store(op_name, system_charset_info);
+ protocol->store(msg_type, system_charset_info);
+ protocol->store(msgbuf, msg_length, system_charset_info);
+ if (protocol->write())
+ {
+ sql_print_error("Failed on my_net_write, writing to stderr instead: %s\n",
+ msgbuf);
+ goto err;
+ }
+ error= false;
+err:
+ my_free(msgbuf);
+ return error;
+}
+
+
+/*
+ Handle optimize/analyze/check/repair of partitions
+
+ SYNOPSIS
+ handle_opt_partitions()
+ thd Thread object
+ check_opt Options
+ flag Optimize/Analyze/Check/Repair flag
+
+ RETURN VALUE
+ >0 Failure
+ 0 Success
+*/
+
+int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt,
+ uint flag)
+{
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ uint num_parts= m_part_info->num_parts;
+ uint num_subparts= m_part_info->num_subparts;
+ uint i= 0;
+ int error;
+ DBUG_ENTER("ha_partition::handle_opt_partitions");
+ DBUG_PRINT("enter", ("flag= %u", flag));
+
+ do
+ {
+ partition_element *part_elem= part_it++;
+ /*
+ when ALTER TABLE <CMD> PARTITION ...
+ it should only do named partitions, otherwise all partitions
+ */
+ if (!(thd->lex->alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION) ||
+ part_elem->part_state == PART_ADMIN)
+ {
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element> subpart_it(part_elem->subpartitions);
+ partition_element *sub_elem;
+ uint j= 0, part;
+ do
+ {
+ sub_elem= subpart_it++;
+ part= i * num_subparts + j;
+ DBUG_PRINT("info", ("Optimize subpartition %u (%s)",
+ part, sub_elem->partition_name));
+ if ((error= handle_opt_part(thd, check_opt, part, flag)))
+ {
+ /* print a line which partition the error belongs to */
+ if (error != HA_ADMIN_NOT_IMPLEMENTED &&
+ error != HA_ADMIN_ALREADY_DONE &&
+ error != HA_ADMIN_TRY_ALTER)
+ {
+ print_admin_msg(thd, MYSQL_ERRMSG_SIZE, "error",
+ table_share->db.str, table->alias,
+ opt_op_name[flag],
+ "Subpartition %s returned error",
+ sub_elem->partition_name);
+ }
+ /* reset part_state for the remaining partitions */
+ do
+ {
+ if (part_elem->part_state == PART_ADMIN)
+ part_elem->part_state= PART_NORMAL;
+ } while ((part_elem= part_it++));
+ DBUG_RETURN(error);
+ }
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Optimize partition %u (%s)", i,
+ part_elem->partition_name));
+ if ((error= handle_opt_part(thd, check_opt, i, flag)))
+ {
+ /* print a line which partition the error belongs to */
+ if (error != HA_ADMIN_NOT_IMPLEMENTED &&
+ error != HA_ADMIN_ALREADY_DONE &&
+ error != HA_ADMIN_TRY_ALTER)
+ {
+ print_admin_msg(thd, MYSQL_ERRMSG_SIZE, "error",
+ table_share->db.str, table->alias,
+ opt_op_name[flag], "Partition %s returned error",
+ part_elem->partition_name);
+ }
+ /* reset part_state for the remaining partitions */
+ do
+ {
+ if (part_elem->part_state == PART_ADMIN)
+ part_elem->part_state= PART_NORMAL;
+ } while ((part_elem= part_it++));
+ DBUG_RETURN(error);
+ }
+ }
+ part_elem->part_state= PART_NORMAL;
+ }
+ } while (++i < num_parts);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief Check and repair the table if neccesary
+
+ @param thd Thread object
+
+ @retval TRUE Error/Not supported
+ @retval FALSE Success
+
+ @note Called if open_table_from_share fails and ::is_crashed().
+*/
+
+bool ha_partition::check_and_repair(THD *thd)
+{
+ handler **file= m_file;
+ DBUG_ENTER("ha_partition::check_and_repair");
+
+ do
+ {
+ if ((*file)->ha_check_and_repair(thd))
+ DBUG_RETURN(TRUE);
+ } while (*(++file));
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @breif Check if the table can be automatically repaired
+
+ @retval TRUE Can be auto repaired
+ @retval FALSE Cannot be auto repaired
+*/
+
+bool ha_partition::auto_repair(int error) const
+{
+ DBUG_ENTER("ha_partition::auto_repair");
+
+ /*
+ As long as we only support one storage engine per table,
+ we can use the first partition for this function.
+ */
+ DBUG_RETURN(m_file[0]->auto_repair(error));
+}
+
+
+/**
+ @breif Check if the table is crashed
+
+ @retval TRUE Crashed
+ @retval FALSE Not crashed
+*/
+
+bool ha_partition::is_crashed() const
+{
+ handler **file= m_file;
+ DBUG_ENTER("ha_partition::is_crashed");
+
+ do
+ {
+ if ((*file)->is_crashed())
+ DBUG_RETURN(TRUE);
+ } while (*(++file));
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Prepare by creating a new partition
+
+ SYNOPSIS
+ prepare_new_partition()
+ table Table object
+ create_info Create info from CREATE TABLE
+ file Handler object of new partition
+ part_name partition name
+
+ RETURN VALUE
+ >0 Error
+ 0 Success
+*/
+
+int ha_partition::prepare_new_partition(TABLE *tbl,
+ HA_CREATE_INFO *create_info,
+ handler *file, const char *part_name,
+ partition_element *p_elem,
+ uint disable_non_uniq_indexes)
+{
+ int error;
+ DBUG_ENTER("prepare_new_partition");
+
+ /*
+ This call to set_up_table_before_create() is done for an alter table.
+ So this may be the second time around for this partition_element,
+ depending on how many partitions and subpartitions there were before,
+ and how many there are now.
+ The first time, on the CREATE, data_file_name and index_file_name
+ came from the parser. They did not have the file name attached to
+ the end. But if this partition is less than the total number of
+ previous partitions, it's data_file_name has the filename attached.
+ So we need to take the partition filename off if it exists.
+ That file name may be different from part_name, which will be
+ attached in append_file_to_dir().
+ */
+ truncate_partition_filename(p_elem->data_file_name);
+ truncate_partition_filename(p_elem->index_file_name);
+
+ if ((error= set_up_table_before_create(tbl, part_name, create_info, p_elem)))
+ goto error_create;
+
+ tbl->s->connect_string = p_elem->connect_string;
+ if ((error= file->ha_create(part_name, tbl, create_info)))
+ {
+ /*
+ Added for safety, InnoDB reports HA_ERR_FOUND_DUPP_KEY
+ if the table/partition already exists.
+ If we return that error code, then print_error would try to
+ get_dup_key on a non-existing partition.
+ So return a more reasonable error code.
+ */
+ if (error == HA_ERR_FOUND_DUPP_KEY)
+ error= HA_ERR_TABLE_EXIST;
+ goto error_create;
+ }
+ DBUG_PRINT("info", ("partition %s created", part_name));
+ if ((error= file->ha_open(tbl, part_name, m_mode,
+ m_open_test_lock | HA_OPEN_NO_PSI_CALL)))
+ goto error_open;
+ DBUG_PRINT("info", ("partition %s opened", part_name));
+
+ /*
+ Note: if you plan to add another call that may return failure,
+ better to do it before external_lock() as cleanup_new_partition()
+ assumes that external_lock() is last call that may fail here.
+ Otherwise see description for cleanup_new_partition().
+ */
+ if ((error= file->ha_external_lock(ha_thd(), F_WRLCK)))
+ goto error_external_lock;
+ DBUG_PRINT("info", ("partition %s external locked", part_name));
+
+ if (disable_non_uniq_indexes)
+ file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+
+ DBUG_RETURN(0);
+error_external_lock:
+ (void) file->ha_close();
+error_open:
+ (void) file->ha_delete_table(part_name);
+error_create:
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Cleanup by removing all created partitions after error
+
+ SYNOPSIS
+ cleanup_new_partition()
+ part_count Number of partitions to remove
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ This function is called immediately after prepare_new_partition() in
+ case the latter fails.
+
+ In prepare_new_partition() last call that may return failure is
+ external_lock(). That means if prepare_new_partition() fails,
+ partition does not have external lock. Thus no need to call
+ external_lock(F_UNLCK) here.
+
+ TODO:
+ We must ensure that in the case that we get an error during the process
+ that we call external_lock with F_UNLCK, close the table and delete the
+ table in the case where we have been successful with prepare_handler.
+ We solve this by keeping an array of successful calls to prepare_handler
+ which can then be used to undo the call.
+*/
+
+void ha_partition::cleanup_new_partition(uint part_count)
+{
+ DBUG_ENTER("ha_partition::cleanup_new_partition");
+
+ if (m_added_file)
+ {
+ THD *thd= ha_thd();
+ handler **file= m_added_file;
+ while ((part_count > 0) && (*file))
+ {
+ (*file)->ha_external_lock(thd, F_UNLCK);
+ (*file)->ha_close();
+
+ /* Leave the (*file)->ha_delete_table(part_name) to the ddl-log */
+
+ file++;
+ part_count--;
+ }
+ m_added_file= NULL;
+ }
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Implement the partition changes defined by ALTER TABLE of partitions
+
+ SYNOPSIS
+ change_partitions()
+ create_info HA_CREATE_INFO object describing all
+ fields and indexes in table
+ path Complete path of db and table name
+ out: copied Output parameter where number of copied
+ records are added
+ out: deleted Output parameter where number of deleted
+ records are added
+ pack_frm_data Reference to packed frm file
+ pack_frm_len Length of packed frm file
+
+ RETURN VALUE
+ >0 Failure
+ 0 Success
+
+ DESCRIPTION
+ Add and copy if needed a number of partitions, during this operation
+ no other operation is ongoing in the server. This is used by
+ ADD PARTITION all types as well as by REORGANIZE PARTITION. For
+ one-phased implementations it is used also by DROP and COALESCE
+ PARTITIONs.
+ One-phased implementation needs the new frm file, other handlers will
+ get zero length and a NULL reference here.
+*/
+
+int ha_partition::change_partitions(HA_CREATE_INFO *create_info,
+ const char *path,
+ ulonglong * const copied,
+ ulonglong * const deleted,
+ const uchar *pack_frm_data
+ __attribute__((unused)),
+ size_t pack_frm_len
+ __attribute__((unused)))
+{
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ List_iterator <partition_element> t_it(m_part_info->temp_partitions);
+ char part_name_buff[FN_REFLEN];
+ uint num_parts= m_part_info->partitions.elements;
+ uint num_subparts= m_part_info->num_subparts;
+ uint i= 0;
+ uint num_remain_partitions, part_count, orig_count;
+ handler **new_file_array;
+ int error= 1;
+ bool first;
+ uint temp_partitions= m_part_info->temp_partitions.elements;
+ THD *thd= ha_thd();
+ DBUG_ENTER("ha_partition::change_partitions");
+
+ /*
+ Assert that it works without HA_FILE_BASED and lower_case_table_name = 2.
+ We use m_file[0] as long as all partitions have the same storage engine.
+ */
+ DBUG_ASSERT(!strcmp(path, get_canonical_filename(m_file[0], path,
+ part_name_buff)));
+ m_reorged_parts= 0;
+ if (!m_part_info->is_sub_partitioned())
+ num_subparts= 1;
+
+ /*
+ Step 1:
+ Calculate number of reorganised partitions and allocate space for
+ their handler references.
+ */
+ if (temp_partitions)
+ {
+ m_reorged_parts= temp_partitions * num_subparts;
+ }
+ else
+ {
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_CHANGED ||
+ part_elem->part_state == PART_REORGED_DROPPED)
+ {
+ m_reorged_parts+= num_subparts;
+ }
+ } while (++i < num_parts);
+ }
+ if (m_reorged_parts &&
+ !(m_reorged_file= (handler**)sql_calloc(sizeof(handler*)*
+ (m_reorged_parts + 1))))
+ {
+ mem_alloc_error(sizeof(handler*)*(m_reorged_parts+1));
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ }
+
+ /*
+ Step 2:
+ Calculate number of partitions after change and allocate space for
+ their handler references.
+ */
+ num_remain_partitions= 0;
+ if (temp_partitions)
+ {
+ num_remain_partitions= num_parts * num_subparts;
+ }
+ else
+ {
+ part_it.rewind();
+ i= 0;
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_NORMAL ||
+ part_elem->part_state == PART_TO_BE_ADDED ||
+ part_elem->part_state == PART_CHANGED)
+ {
+ num_remain_partitions+= num_subparts;
+ }
+ } while (++i < num_parts);
+ }
+ if (!(new_file_array= (handler**)sql_calloc(sizeof(handler*)*
+ (2*(num_remain_partitions + 1)))))
+ {
+ mem_alloc_error(sizeof(handler*)*2*(num_remain_partitions+1));
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ }
+ m_added_file= &new_file_array[num_remain_partitions + 1];
+
+ /*
+ Step 3:
+ Fill m_reorged_file with handler references and NULL at the end
+ */
+ if (m_reorged_parts)
+ {
+ i= 0;
+ part_count= 0;
+ first= TRUE;
+ part_it.rewind();
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_CHANGED ||
+ part_elem->part_state == PART_REORGED_DROPPED)
+ {
+ memcpy((void*)&m_reorged_file[part_count],
+ (void*)&m_file[i*num_subparts],
+ sizeof(handler*)*num_subparts);
+ part_count+= num_subparts;
+ }
+ else if (first && temp_partitions &&
+ part_elem->part_state == PART_TO_BE_ADDED)
+ {
+ /*
+ When doing an ALTER TABLE REORGANIZE PARTITION a number of
+ partitions is to be reorganised into a set of new partitions.
+ The reorganised partitions are in this case in the temp_partitions
+ list. We copy all of them in one batch and thus we only do this
+ until we find the first partition with state PART_TO_BE_ADDED
+ since this is where the new partitions go in and where the old
+ ones used to be.
+ */
+ first= FALSE;
+ DBUG_ASSERT(((i*num_subparts) + m_reorged_parts) <= m_file_tot_parts);
+ memcpy((void*)m_reorged_file, &m_file[i*num_subparts],
+ sizeof(handler*)*m_reorged_parts);
+ }
+ } while (++i < num_parts);
+ }
+
+ /*
+ Step 4:
+ Fill new_array_file with handler references. Create the handlers if
+ needed.
+ */
+ i= 0;
+ part_count= 0;
+ orig_count= 0;
+ first= TRUE;
+ part_it.rewind();
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_NORMAL)
+ {
+ DBUG_ASSERT(orig_count + num_subparts <= m_file_tot_parts);
+ memcpy((void*)&new_file_array[part_count], (void*)&m_file[orig_count],
+ sizeof(handler*)*num_subparts);
+ part_count+= num_subparts;
+ orig_count+= num_subparts;
+ }
+ else if (part_elem->part_state == PART_CHANGED ||
+ part_elem->part_state == PART_TO_BE_ADDED)
+ {
+ uint j= 0;
+ Parts_share_refs *p_share_refs;
+ /*
+ The Handler_shares for each partition's handler can be allocated
+ within this handler, since there will not be any more instances of the
+ new partitions, until the table is reopened after the ALTER succeeded.
+ */
+ p_share_refs= new Parts_share_refs;
+ if (!p_share_refs)
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ if (p_share_refs->init(num_subparts))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ if (m_new_partitions_share_refs.push_back(p_share_refs))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ do
+ {
+ handler **new_file= &new_file_array[part_count++];
+ if (!(*new_file=
+ get_new_handler(table->s,
+ thd->mem_root,
+ part_elem->engine_type)))
+ {
+ mem_alloc_error(sizeof(handler));
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ }
+ if ((*new_file)->set_ha_share_ref(&p_share_refs->ha_shares[j]))
+ {
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ }
+ } while (++j < num_subparts);
+ if (part_elem->part_state == PART_CHANGED)
+ orig_count+= num_subparts;
+ else if (temp_partitions && first)
+ {
+ orig_count+= (num_subparts * temp_partitions);
+ first= FALSE;
+ }
+ }
+ } while (++i < num_parts);
+ first= FALSE;
+ /*
+ Step 5:
+ Create the new partitions and also open, lock and call external_lock
+ on them to prepare them for copy phase and also for later close
+ calls
+ */
+
+ /*
+ Before creating new partitions check whether indexes are disabled
+ in the partitions.
+ */
+
+ uint disable_non_uniq_indexes = indexes_are_disabled();
+
+ i= 0;
+ part_count= 0;
+ part_it.rewind();
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_TO_BE_ADDED ||
+ part_elem->part_state == PART_CHANGED)
+ {
+ /*
+ A new partition needs to be created PART_TO_BE_ADDED means an
+ entirely new partition and PART_CHANGED means a changed partition
+ that will still exist with either more or less data in it.
+ */
+ uint name_variant= NORMAL_PART_NAME;
+ if (part_elem->part_state == PART_CHANGED ||
+ (part_elem->part_state == PART_TO_BE_ADDED && temp_partitions))
+ name_variant= TEMP_PART_NAME;
+ if (m_part_info->is_sub_partitioned())
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ uint j= 0, part;
+ do
+ {
+ partition_element *sub_elem= sub_it++;
+ create_subpartition_name(part_name_buff, path,
+ part_elem->partition_name,
+ sub_elem->partition_name,
+ name_variant);
+ part= i * num_subparts + j;
+ DBUG_PRINT("info", ("Add subpartition %s", part_name_buff));
+ if ((error= prepare_new_partition(table, create_info,
+ new_file_array[part],
+ (const char *)part_name_buff,
+ sub_elem,
+ disable_non_uniq_indexes)))
+ {
+ cleanup_new_partition(part_count);
+ DBUG_RETURN(error);
+ }
+
+ m_added_file[part_count++]= new_file_array[part];
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ create_partition_name(part_name_buff, path,
+ part_elem->partition_name, name_variant,
+ TRUE);
+ DBUG_PRINT("info", ("Add partition %s", part_name_buff));
+ if ((error= prepare_new_partition(table, create_info,
+ new_file_array[i],
+ (const char *)part_name_buff,
+ part_elem,
+ disable_non_uniq_indexes)))
+ {
+ cleanup_new_partition(part_count);
+ DBUG_RETURN(error);
+ }
+
+ m_added_file[part_count++]= new_file_array[i];
+ }
+ }
+ } while (++i < num_parts);
+
+ /*
+ Step 6:
+ State update to prepare for next write of the frm file.
+ */
+ i= 0;
+ part_it.rewind();
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_TO_BE_ADDED)
+ part_elem->part_state= PART_IS_ADDED;
+ else if (part_elem->part_state == PART_CHANGED)
+ part_elem->part_state= PART_IS_CHANGED;
+ else if (part_elem->part_state == PART_REORGED_DROPPED)
+ part_elem->part_state= PART_TO_BE_DROPPED;
+ } while (++i < num_parts);
+ for (i= 0; i < temp_partitions; i++)
+ {
+ partition_element *part_elem= t_it++;
+ DBUG_ASSERT(part_elem->part_state == PART_TO_BE_REORGED);
+ part_elem->part_state= PART_TO_BE_DROPPED;
+ }
+ m_new_file= new_file_array;
+ if ((error= copy_partitions(copied, deleted)))
+ {
+ /*
+ Close and unlock the new temporary partitions.
+ They will later be deleted through the ddl-log.
+ */
+ cleanup_new_partition(part_count);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Copy partitions as part of ALTER TABLE of partitions
+
+ SYNOPSIS
+ copy_partitions()
+ out:copied Number of records copied
+ out:deleted Number of records deleted
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ change_partitions has done all the preparations, now it is time to
+ actually copy the data from the reorganised partitions to the new
+ partitions.
+*/
+
+int ha_partition::copy_partitions(ulonglong * const copied,
+ ulonglong * const deleted)
+{
+ uint reorg_part= 0;
+ int result= 0;
+ longlong func_value;
+ DBUG_ENTER("ha_partition::copy_partitions");
+
+ if (m_part_info->linear_hash_ind)
+ {
+ if (m_part_info->part_type == HASH_PARTITION)
+ set_linear_hash_mask(m_part_info, m_part_info->num_parts);
+ else
+ set_linear_hash_mask(m_part_info, m_part_info->num_subparts);
+ }
+
+ while (reorg_part < m_reorged_parts)
+ {
+ handler *file= m_reorged_file[reorg_part];
+ uint32 new_part;
+
+ late_extra_cache(reorg_part);
+ if ((result= file->ha_rnd_init_with_error(1)))
+ goto init_error;
+ while (TRUE)
+ {
+ if ((result= file->ha_rnd_next(m_rec0)))
+ {
+ if (result == HA_ERR_RECORD_DELETED)
+ continue; //Probably MyISAM
+ if (result != HA_ERR_END_OF_FILE)
+ goto error;
+ /*
+ End-of-file reached, break out to continue with next partition or
+ end the copy process.
+ */
+ break;
+ }
+ /* Found record to insert into new handler */
+ if (m_part_info->get_partition_id(m_part_info, &new_part,
+ &func_value))
+ {
+ /*
+ This record is in the original table but will not be in the new
+ table since it doesn't fit into any partition any longer due to
+ changed partitioning ranges or list values.
+ */
+ (*deleted)++;
+ }
+ else
+ {
+ THD *thd= ha_thd();
+ /* Copy record to new handler */
+ (*copied)++;
+ tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */
+ result= m_new_file[new_part]->ha_write_row(m_rec0);
+ reenable_binlog(thd);
+ if (result)
+ goto error;
+ }
+ }
+ late_extra_no_cache(reorg_part);
+ file->ha_rnd_end();
+ reorg_part++;
+ }
+ DBUG_RETURN(FALSE);
+error:
+ m_reorged_file[reorg_part]->ha_rnd_end();
+init_error:
+ DBUG_RETURN(result);
+}
+
+/*
+ Update create info as part of ALTER TABLE
+
+ SYNOPSIS
+ update_create_info()
+ create_info Create info from ALTER TABLE
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ Forward this handler call to the storage engine foreach
+ partition handler. The data_file_name for each partition may
+ need to be reset if the tablespace was moved. Use a dummy
+ HA_CREATE_INFO structure and transfer necessary data.
+*/
+
+void ha_partition::update_create_info(HA_CREATE_INFO *create_info)
+{
+ DBUG_ENTER("ha_partition::update_create_info");
+
+ /*
+ Fix for bug#38751, some engines needs info-calls in ALTER.
+ Archive need this since it flushes in ::info.
+ HA_STATUS_AUTO is optimized so it will not always be forwarded
+ to all partitions, but HA_STATUS_VARIABLE will.
+ */
+ info(HA_STATUS_VARIABLE);
+
+ info(HA_STATUS_AUTO);
+
+ if (!(create_info->used_fields & HA_CREATE_USED_AUTO))
+ create_info->auto_increment_value= stats.auto_increment_value;
+
+ /*
+ DATA DIRECTORY and INDEX DIRECTORY are never applied to the whole
+ partitioned table, only its parts.
+ */
+ my_bool from_alter = (create_info->data_file_name == (const char*) -1);
+ create_info->data_file_name= create_info->index_file_name = NULL;
+
+ create_info->connect_string= null_lex_str;
+
+ /*
+ We do not need to update the individual partition DATA DIRECTORY settings
+ since they can be changed by ALTER TABLE ... REORGANIZE PARTITIONS.
+ */
+ if (from_alter)
+ DBUG_VOID_RETURN;
+
+ /*
+ send Handler::update_create_info() to the storage engine for each
+ partition that currently has a handler object. Using a dummy
+ HA_CREATE_INFO structure to collect DATA and INDEX DIRECTORYs.
+ */
+
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ partition_element *part_elem, *sub_elem;
+ uint num_subparts= m_part_info->num_subparts;
+ uint num_parts = num_subparts ? m_file_tot_parts / num_subparts
+ : m_file_tot_parts;
+ HA_CREATE_INFO dummy_info;
+ memset(&dummy_info, 0, sizeof(dummy_info));
+
+ /*
+ Since update_create_info() can be called from mysql_prepare_alter_table()
+ when not all handlers are set up, we look for that condition first.
+ If all handlers are not available, do not call update_create_info for any.
+ */
+ uint i, j, part;
+ for (i= 0; i < num_parts; i++)
+ {
+ part_elem= part_it++;
+ if (!part_elem)
+ DBUG_VOID_RETURN;
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element> subpart_it(part_elem->subpartitions);
+ for (j= 0; j < num_subparts; j++)
+ {
+ sub_elem= subpart_it++;
+ if (!sub_elem)
+ DBUG_VOID_RETURN;
+ part= i * num_subparts + j;
+ if (part >= m_file_tot_parts || !m_file[part])
+ DBUG_VOID_RETURN;
+ }
+ }
+ else
+ {
+ if (!m_file[i])
+ DBUG_VOID_RETURN;
+ }
+ }
+ part_it.rewind();
+
+ for (i= 0; i < num_parts; i++)
+ {
+ part_elem= part_it++;
+ DBUG_ASSERT(part_elem);
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element> subpart_it(part_elem->subpartitions);
+ for (j= 0; j < num_subparts; j++)
+ {
+ sub_elem= subpart_it++;
+ DBUG_ASSERT(sub_elem);
+ part= i * num_subparts + j;
+ DBUG_ASSERT(part < m_file_tot_parts && m_file[part]);
+ if (ha_legacy_type(m_file[part]->ht) == DB_TYPE_INNODB)
+ {
+ dummy_info.data_file_name= dummy_info.index_file_name = NULL;
+ m_file[part]->update_create_info(&dummy_info);
+
+ if (dummy_info.data_file_name || sub_elem->data_file_name)
+ {
+ sub_elem->data_file_name = (char*) dummy_info.data_file_name;
+ }
+ if (dummy_info.index_file_name || sub_elem->index_file_name)
+ {
+ sub_elem->index_file_name = (char*) dummy_info.index_file_name;
+ }
+ }
+ }
+ }
+ else
+ {
+ DBUG_ASSERT(m_file[i]);
+ if (ha_legacy_type(m_file[i]->ht) == DB_TYPE_INNODB)
+ {
+ dummy_info.data_file_name= dummy_info.index_file_name= NULL;
+ m_file[i]->update_create_info(&dummy_info);
+ if (dummy_info.data_file_name || part_elem->data_file_name)
+ {
+ part_elem->data_file_name = (char*) dummy_info.data_file_name;
+ }
+ if (dummy_info.index_file_name || part_elem->index_file_name)
+ {
+ part_elem->index_file_name = (char*) dummy_info.index_file_name;
+ }
+ }
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Change the internal TABLE_SHARE pointer
+
+ @param table_arg TABLE object
+ @param share New share to use
+
+ @note Is used in error handling in ha_delete_table.
+ All handlers should exist (lock_partitions should not be used)
+*/
+
+void ha_partition::change_table_ptr(TABLE *table_arg, TABLE_SHARE *share)
+{
+ handler **file_array;
+ table= table_arg;
+ table_share= share;
+ /*
+ m_file can be NULL when using an old cached table in DROP TABLE, when the
+ table just has REMOVED PARTITIONING, see Bug#42438
+ */
+ if (m_file)
+ {
+ file_array= m_file;
+ DBUG_ASSERT(*file_array);
+ do
+ {
+ (*file_array)->change_table_ptr(table_arg, share);
+ } while (*(++file_array));
+ }
+
+ if (m_added_file && m_added_file[0])
+ {
+ /* if in middle of a drop/rename etc */
+ file_array= m_added_file;
+ do
+ {
+ (*file_array)->change_table_ptr(table_arg, share);
+ } while (*(++file_array));
+ }
+}
+
+/*
+ Change comments specific to handler
+
+ SYNOPSIS
+ update_table_comment()
+ comment Original comment
+
+ RETURN VALUE
+ new comment
+
+ DESCRIPTION
+ No comment changes so far
+*/
+
+char *ha_partition::update_table_comment(const char *comment)
+{
+ return (char*) comment; /* Nothing to change */
+}
+
+
+/**
+ Handle delete and rename table
+
+ @param from Full path of old table
+ @param to Full path of new table
+
+ @return Operation status
+ @retval >0 Error
+ @retval 0 Success
+
+ @note Common routine to handle delete_table and rename_table.
+ The routine uses the partition handler file to get the
+ names of the partition instances. Both these routines
+ are called after creating the handler without table
+ object and thus the file is needed to discover the
+ names of the partitions and the underlying storage engines.
+*/
+
+uint ha_partition::del_ren_table(const char *from, const char *to)
+{
+ int save_error= 0;
+ int error;
+ char from_buff[FN_REFLEN], to_buff[FN_REFLEN], from_lc_buff[FN_REFLEN],
+ to_lc_buff[FN_REFLEN];
+ char *name_buffer_ptr;
+ const char *from_path;
+ const char *to_path= NULL;
+ uint i;
+ handler **file, **abort_file;
+ DBUG_ENTER("ha_partition::del_ren_table");
+
+ if (get_from_handler_file(from, ha_thd()->mem_root, false))
+ DBUG_RETURN(TRUE);
+ DBUG_ASSERT(m_file_buffer);
+ DBUG_PRINT("enter", ("from: (%s) to: (%s)", from, to ? to : "(nil)"));
+ name_buffer_ptr= m_name_buffer_ptr;
+ file= m_file;
+ if (to == NULL)
+ {
+ /*
+ Delete table, start by delete the .par file. If error, break, otherwise
+ delete as much as possible.
+ */
+ if ((error= handler::delete_table(from)))
+ DBUG_RETURN(error);
+ }
+ /*
+ Since ha_partition has HA_FILE_BASED, it must alter underlying table names
+ if they do not have HA_FILE_BASED and lower_case_table_names == 2.
+ See Bug#37402, for Mac OS X.
+ The appended #P#<partname>[#SP#<subpartname>] will remain in current case.
+ Using the first partitions handler, since mixing handlers is not allowed.
+ */
+ from_path= get_canonical_filename(*file, from, from_lc_buff);
+ if (to != NULL)
+ to_path= get_canonical_filename(*file, to, to_lc_buff);
+ i= 0;
+ do
+ {
+ create_partition_name(from_buff, from_path, name_buffer_ptr,
+ NORMAL_PART_NAME, FALSE);
+
+ if (to != NULL)
+ { // Rename branch
+ create_partition_name(to_buff, to_path, name_buffer_ptr,
+ NORMAL_PART_NAME, FALSE);
+ error= (*file)->ha_rename_table(from_buff, to_buff);
+ if (error)
+ goto rename_error;
+ }
+ else // delete branch
+ {
+ error= (*file)->ha_delete_table(from_buff);
+ }
+ name_buffer_ptr= strend(name_buffer_ptr) + 1;
+ if (error)
+ save_error= error;
+ i++;
+ } while (*(++file));
+ if (to != NULL)
+ {
+ if ((error= handler::rename_table(from, to)))
+ {
+ /* Try to revert everything, ignore errors */
+ (void) handler::rename_table(to, from);
+ goto rename_error;
+ }
+ }
+ DBUG_RETURN(save_error);
+rename_error:
+ name_buffer_ptr= m_name_buffer_ptr;
+ for (abort_file= file, file= m_file; file < abort_file; file++)
+ {
+ /* Revert the rename, back from 'to' to the original 'from' */
+ create_partition_name(from_buff, from_path, name_buffer_ptr,
+ NORMAL_PART_NAME, FALSE);
+ create_partition_name(to_buff, to_path, name_buffer_ptr,
+ NORMAL_PART_NAME, FALSE);
+ /* Ignore error here */
+ (void) (*file)->ha_rename_table(to_buff, from_buff);
+ name_buffer_ptr= strend(name_buffer_ptr) + 1;
+ }
+ DBUG_RETURN(error);
+}
+
+uint ha_partition::count_query_cache_dependant_tables(uint8 *tables_type)
+{
+ DBUG_ENTER("ha_partition::count_query_cache_dependant_tables");
+ /* Here we rely on the fact that all tables are of the same type */
+ uint8 type= m_file[0]->table_cache_type();
+ (*tables_type)|= type;
+ DBUG_PRINT("info", ("cnt: %u", (uint)m_tot_parts));
+ /*
+ We need save underlying tables only for HA_CACHE_TBL_ASKTRANSACT:
+ HA_CACHE_TBL_NONTRANSACT - because all changes goes through partition table
+ HA_CACHE_TBL_NOCACHE - because will not be cached
+ HA_CACHE_TBL_TRANSACT - QC need to know that such type present
+ */
+ DBUG_RETURN(type == HA_CACHE_TBL_ASKTRANSACT ? m_tot_parts : 0);
+}
+
+my_bool ha_partition::
+reg_query_cache_dependant_table(THD *thd,
+ char *engine_key, uint engine_key_len,
+ char *cache_key, uint cache_key_len,
+ uint8 type,
+ Query_cache *cache,
+ Query_cache_block_table **block_table,
+ handler *file,
+ uint *n)
+{
+ DBUG_ENTER("ha_partition::reg_query_cache_dependant_table");
+ qc_engine_callback engine_callback;
+ ulonglong engine_data;
+ /* ask undelying engine */
+ if (!file->register_query_cache_table(thd, engine_key,
+ engine_key_len,
+ &engine_callback,
+ &engine_data))
+ {
+ DBUG_PRINT("qcache", ("Handler does not allow caching for %.*s",
+ engine_key_len, engine_key));
+ /*
+ As this can change from call to call, don't reset set
+ thd->lex->safe_to_cache_query
+ */
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ DBUG_RETURN(TRUE);
+ }
+ (++(*block_table))->n= ++(*n);
+ if (!cache->insert_table(cache_key_len,
+ cache_key, (*block_table),
+ table_share->db.length,
+ (uint8) (cache_key_len -
+ table_share->table_cache_key.length),
+ type,
+ engine_callback, engine_data,
+ FALSE))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+
+my_bool ha_partition::
+register_query_cache_dependant_tables(THD *thd,
+ Query_cache *cache,
+ Query_cache_block_table **block_table,
+ uint *n)
+{
+ char *engine_key_end, *query_cache_key_end;
+ uint i;
+ uint num_parts= m_part_info->num_parts;
+ uint num_subparts= m_part_info->num_subparts;
+ int diff_length;
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ char engine_key[FN_REFLEN], query_cache_key[FN_REFLEN];
+ DBUG_ENTER("ha_partition::register_query_cache_dependant_tables");
+
+ /* see ha_partition::count_query_cache_dependant_tables */
+ if (m_file[0]->table_cache_type() != HA_CACHE_TBL_ASKTRANSACT)
+ DBUG_RETURN(FALSE); // nothing to register
+
+ /* prepare static part of the key */
+ memcpy(engine_key, table_share->normalized_path.str,
+ table_share->normalized_path.length);
+ memcpy(query_cache_key, table_share->table_cache_key.str,
+ table_share->table_cache_key.length);
+
+ diff_length= ((int) table_share->table_cache_key.length -
+ (int) table_share->normalized_path.length -1);
+
+ engine_key_end= engine_key + table_share->normalized_path.length;
+ query_cache_key_end= query_cache_key + table_share->table_cache_key.length -1;
+
+ engine_key_end[0]= engine_key_end[2]= query_cache_key_end[0]=
+ query_cache_key_end[2]= '#';
+ query_cache_key_end[1]= engine_key_end[1]= 'P';
+ engine_key_end+= 3;
+ query_cache_key_end+= 3;
+
+ i= 0;
+ do
+ {
+ partition_element *part_elem= part_it++;
+ char *engine_pos= strmov(engine_key_end, part_elem->partition_name);
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element> subpart_it(part_elem->subpartitions);
+ partition_element *sub_elem;
+ uint j= 0, part;
+ engine_pos[0]= engine_pos[3]= '#';
+ engine_pos[1]= 'S';
+ engine_pos[2]= 'P';
+ engine_pos += 4;
+ do
+ {
+ char *end;
+ uint length;
+ sub_elem= subpart_it++;
+ part= i * num_subparts + j;
+ /* we store the end \0 as part of the key */
+ end= strmov(engine_pos, sub_elem->partition_name);
+ length= end - engine_key;
+ /* Copy the suffix also to query cache key */
+ memcpy(query_cache_key_end, engine_key_end, (end - engine_key_end));
+ if (reg_query_cache_dependant_table(thd, engine_key, length,
+ query_cache_key,
+ length + diff_length,
+ m_file[part]->table_cache_type(),
+ cache,
+ block_table, m_file[part],
+ n))
+ DBUG_RETURN(TRUE);
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ char *end= engine_pos+1; // copy end \0
+ uint length= end - engine_key;
+ /* Copy the suffix also to query cache key */
+ memcpy(query_cache_key_end, engine_key_end, (end - engine_key_end));
+ if (reg_query_cache_dependant_table(thd, engine_key, length,
+ query_cache_key,
+ length + diff_length,
+ m_file[i]->table_cache_type(),
+ cache,
+ block_table, m_file[i],
+ n))
+ DBUG_RETURN(TRUE);
+ }
+ } while (++i < num_parts);
+ DBUG_PRINT("info", ("cnt: %u", (uint)m_tot_parts));
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Set up table share object before calling create on underlying handler
+
+ @param table Table object
+ @param info Create info
+ @param part_elem[in,out] Pointer to used partition_element, searched if NULL
+
+ @return status
+ @retval TRUE Error
+ @retval FALSE Success
+
+ @details
+ Set up
+ 1) Comment on partition
+ 2) MAX_ROWS, MIN_ROWS on partition
+ 3) Index file name on partition
+ 4) Data file name on partition
+*/
+
+int ha_partition::set_up_table_before_create(TABLE *tbl,
+ const char *partition_name_with_path,
+ HA_CREATE_INFO *info,
+ partition_element *part_elem)
+{
+ int error= 0;
+ const char *partition_name;
+ THD *thd= ha_thd();
+ DBUG_ENTER("set_up_table_before_create");
+
+ DBUG_ASSERT(part_elem);
+
+ if (!part_elem)
+ DBUG_RETURN(1);
+ tbl->s->max_rows= part_elem->part_max_rows;
+ tbl->s->min_rows= part_elem->part_min_rows;
+ partition_name= strrchr(partition_name_with_path, FN_LIBCHAR);
+ if ((part_elem->index_file_name &&
+ (error= append_file_to_dir(thd,
+ (const char**)&part_elem->index_file_name,
+ partition_name+1))) ||
+ (part_elem->data_file_name &&
+ (error= append_file_to_dir(thd,
+ (const char**)&part_elem->data_file_name,
+ partition_name+1))))
+ {
+ DBUG_RETURN(error);
+ }
+ info->index_file_name= part_elem->index_file_name;
+ info->data_file_name= part_elem->data_file_name;
+ info->connect_string= part_elem->connect_string;
+ if (info->connect_string.length)
+ info->used_fields|= HA_CREATE_USED_CONNECTION;
+ tbl->s->connect_string= part_elem->connect_string;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Add two names together
+
+ SYNOPSIS
+ name_add()
+ out:dest Destination string
+ first_name First name
+ sec_name Second name
+
+ RETURN VALUE
+ >0 Error
+ 0 Success
+
+ DESCRIPTION
+ Routine used to add two names with '_' in between then. Service routine
+ to create_handler_file
+ Include the NULL in the count of characters since it is needed as separator
+ between the partition names.
+*/
+
+static uint name_add(char *dest, const char *first_name, const char *sec_name)
+{
+ return (uint) (strxmov(dest, first_name, "#SP#", sec_name, NullS) -dest) + 1;
+}
+
+
+/**
+ Create the special .par file
+
+ @param name Full path of table name
+
+ @return Operation status
+ @retval FALSE Error code
+ @retval TRUE Success
+
+ @note
+ Method used to create handler file with names of partitions, their
+ engine types and the number of partitions.
+*/
+
+bool ha_partition::create_handler_file(const char *name)
+{
+ partition_element *part_elem, *subpart_elem;
+ uint i, j, part_name_len, subpart_name_len;
+ uint tot_partition_words, tot_name_len, num_parts;
+ uint tot_parts= 0;
+ uint tot_len_words, tot_len_byte, chksum, tot_name_words;
+ char *name_buffer_ptr;
+ uchar *file_buffer, *engine_array;
+ bool result= TRUE;
+ char file_name[FN_REFLEN];
+ char part_name[FN_REFLEN];
+ char subpart_name[FN_REFLEN];
+ File file;
+ List_iterator_fast <partition_element> part_it(m_part_info->partitions);
+ DBUG_ENTER("create_handler_file");
+
+ num_parts= m_part_info->partitions.elements;
+ DBUG_PRINT("info", ("table name = %s, num_parts = %u", name,
+ num_parts));
+ tot_name_len= 0;
+ for (i= 0; i < num_parts; i++)
+ {
+ part_elem= part_it++;
+ if (part_elem->part_state != PART_NORMAL &&
+ part_elem->part_state != PART_TO_BE_ADDED &&
+ part_elem->part_state != PART_CHANGED)
+ continue;
+ tablename_to_filename(part_elem->partition_name, part_name,
+ FN_REFLEN);
+ part_name_len= strlen(part_name);
+ if (!m_is_sub_partitioned)
+ {
+ tot_name_len+= part_name_len + 1;
+ tot_parts++;
+ }
+ else
+ {
+ List_iterator_fast <partition_element> sub_it(part_elem->subpartitions);
+ for (j= 0; j < m_part_info->num_subparts; j++)
+ {
+ subpart_elem= sub_it++;
+ tablename_to_filename(subpart_elem->partition_name,
+ subpart_name,
+ FN_REFLEN);
+ subpart_name_len= strlen(subpart_name);
+ tot_name_len+= part_name_len + subpart_name_len + 5;
+ tot_parts++;
+ }
+ }
+ }
+ /*
+ File format:
+ Length in words 4 byte
+ Checksum 4 byte
+ Total number of partitions 4 byte
+ Array of engine types n * 4 bytes where
+ n = (m_tot_parts + 3)/4
+ Length of name part in bytes 4 bytes
+ (Names in filename format)
+ Name part m * 4 bytes where
+ m = ((length_name_part + 3)/4)*4
+
+ All padding bytes are zeroed
+ */
+ tot_partition_words= (tot_parts + PAR_WORD_SIZE - 1) / PAR_WORD_SIZE;
+ tot_name_words= (tot_name_len + PAR_WORD_SIZE - 1) / PAR_WORD_SIZE;
+ /* 4 static words (tot words, checksum, tot partitions, name length) */
+ tot_len_words= 4 + tot_partition_words + tot_name_words;
+ tot_len_byte= PAR_WORD_SIZE * tot_len_words;
+ if (!(file_buffer= (uchar *) my_malloc(tot_len_byte, MYF(MY_ZEROFILL))))
+ DBUG_RETURN(TRUE);
+ engine_array= (file_buffer + PAR_ENGINES_OFFSET);
+ name_buffer_ptr= (char*) (engine_array + tot_partition_words * PAR_WORD_SIZE
+ + PAR_WORD_SIZE);
+ part_it.rewind();
+ for (i= 0; i < num_parts; i++)
+ {
+ part_elem= part_it++;
+ if (part_elem->part_state != PART_NORMAL &&
+ part_elem->part_state != PART_TO_BE_ADDED &&
+ part_elem->part_state != PART_CHANGED)
+ continue;
+ if (!m_is_sub_partitioned)
+ {
+ tablename_to_filename(part_elem->partition_name, part_name, FN_REFLEN);
+ name_buffer_ptr= strmov(name_buffer_ptr, part_name)+1;
+ *engine_array= (uchar) ha_legacy_type(part_elem->engine_type);
+ DBUG_PRINT("info", ("engine: %u", *engine_array));
+ engine_array++;
+ }
+ else
+ {
+ List_iterator_fast <partition_element> sub_it(part_elem->subpartitions);
+ for (j= 0; j < m_part_info->num_subparts; j++)
+ {
+ subpart_elem= sub_it++;
+ tablename_to_filename(part_elem->partition_name, part_name,
+ FN_REFLEN);
+ tablename_to_filename(subpart_elem->partition_name, subpart_name,
+ FN_REFLEN);
+ name_buffer_ptr+= name_add(name_buffer_ptr,
+ part_name,
+ subpart_name);
+ *engine_array= (uchar) ha_legacy_type(subpart_elem->engine_type);
+ DBUG_PRINT("info", ("engine: %u", *engine_array));
+ engine_array++;
+ }
+ }
+ }
+ chksum= 0;
+ int4store(file_buffer, tot_len_words);
+ int4store(file_buffer + PAR_NUM_PARTS_OFFSET, tot_parts);
+ int4store(file_buffer + PAR_ENGINES_OFFSET +
+ (tot_partition_words * PAR_WORD_SIZE),
+ tot_name_len);
+ for (i= 0; i < tot_len_words; i++)
+ chksum^= uint4korr(file_buffer + PAR_WORD_SIZE * i);
+ int4store(file_buffer + PAR_CHECKSUM_OFFSET, chksum);
+ /*
+ Add .par extension to the file name.
+ Create and write and close file
+ to be used at open, delete_table and rename_table
+ */
+ fn_format(file_name, name, "", ha_par_ext, MY_APPEND_EXT);
+ if ((file= mysql_file_create(key_file_partition,
+ file_name, CREATE_MODE, O_RDWR | O_TRUNC,
+ MYF(MY_WME))) >= 0)
+ {
+ result= mysql_file_write(file, (uchar *) file_buffer, tot_len_byte,
+ MYF(MY_WME | MY_NABP)) != 0;
+
+ /* Write connection information (for federatedx engine) */
+ part_it.rewind();
+ for (i= 0; i < num_parts && !result; i++)
+ {
+ uchar buffer[4];
+ part_elem= part_it++;
+ uint length = part_elem->connect_string.length;
+ int4store(buffer, length);
+ if (my_write(file, buffer, 4, MYF(MY_WME | MY_NABP)) ||
+ my_write(file, (uchar *) part_elem->connect_string.str, length,
+ MYF(MY_WME | MY_NABP)))
+ {
+ result= TRUE;
+ break;
+ }
+ }
+ (void) mysql_file_close(file, MYF(0));
+ }
+ else
+ result= TRUE;
+ my_free(file_buffer);
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Clear handler variables and free some memory
+*/
+
+void ha_partition::clear_handler_file()
+{
+ if (m_engine_array)
+ plugin_unlock_list(NULL, m_engine_array, m_tot_parts);
+ free_root(&m_mem_root, MYF(MY_KEEP_PREALLOC));
+ m_file_buffer= NULL;
+ m_engine_array= NULL;
+ m_connect_string= NULL;
+}
+
+
+/**
+ Create underlying handler objects
+
+ @param mem_root Allocate memory through this
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+bool ha_partition::create_handlers(MEM_ROOT *mem_root)
+{
+ uint i;
+ uint alloc_len= (m_tot_parts + 1) * sizeof(handler*);
+ handlerton *hton0;
+ DBUG_ENTER("create_handlers");
+
+ if (!(m_file= (handler **) alloc_root(mem_root, alloc_len)))
+ DBUG_RETURN(TRUE);
+ m_file_tot_parts= m_tot_parts;
+ bzero((char*) m_file, alloc_len);
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ handlerton *hton= plugin_data(m_engine_array[i], handlerton*);
+ if (!(m_file[i]= get_new_handler(table_share, mem_root, hton)))
+ DBUG_RETURN(TRUE);
+ DBUG_PRINT("info", ("engine_type: %u", hton->db_type));
+ }
+ /* For the moment we only support partition over the same table engine */
+ hton0= plugin_data(m_engine_array[0], handlerton*);
+ if (hton0 == myisam_hton)
+ {
+ DBUG_PRINT("info", ("MyISAM"));
+ m_myisam= TRUE;
+ }
+ /* INNODB may not be compiled in... */
+ else if (ha_legacy_type(hton0) == DB_TYPE_INNODB)
+ {
+ DBUG_PRINT("info", ("InnoDB"));
+ m_innodb= TRUE;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Create underlying handler objects from partition info
+
+ SYNOPSIS
+ new_handlers_from_part_info()
+ mem_root Allocate memory through this
+
+ RETURN VALUE
+ TRUE Error
+ FALSE Success
+*/
+
+bool ha_partition::new_handlers_from_part_info(MEM_ROOT *mem_root)
+{
+ uint i, j, part_count;
+ partition_element *part_elem;
+ uint alloc_len= (m_tot_parts + 1) * sizeof(handler*);
+ List_iterator_fast <partition_element> part_it(m_part_info->partitions);
+ DBUG_ENTER("ha_partition::new_handlers_from_part_info");
+
+ if (!(m_file= (handler **) alloc_root(mem_root, alloc_len)))
+ {
+ mem_alloc_error(alloc_len);
+ goto error_end;
+ }
+ m_file_tot_parts= m_tot_parts;
+ bzero((char*) m_file, alloc_len);
+ DBUG_ASSERT(m_part_info->num_parts > 0);
+
+ i= 0;
+ part_count= 0;
+ /*
+ Don't know the size of the underlying storage engine, invent a number of
+ bytes allocated for error message if allocation fails
+ */
+ do
+ {
+ part_elem= part_it++;
+ if (m_is_sub_partitioned)
+ {
+ for (j= 0; j < m_part_info->num_subparts; j++)
+ {
+ if (!(m_file[part_count++]= get_new_handler(table_share, mem_root,
+ part_elem->engine_type)))
+ goto error;
+ DBUG_PRINT("info", ("engine_type: %u",
+ (uint) ha_legacy_type(part_elem->engine_type)));
+ }
+ }
+ else
+ {
+ if (!(m_file[part_count++]= get_new_handler(table_share, mem_root,
+ part_elem->engine_type)))
+ goto error;
+ DBUG_PRINT("info", ("engine_type: %u",
+ (uint) ha_legacy_type(part_elem->engine_type)));
+ }
+ } while (++i < m_part_info->num_parts);
+ if (part_elem->engine_type == myisam_hton)
+ {
+ DBUG_PRINT("info", ("MyISAM"));
+ m_myisam= TRUE;
+ }
+ DBUG_RETURN(FALSE);
+error:
+ mem_alloc_error(sizeof(handler));
+error_end:
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Read the .par file to get the partitions engines and names
+
+ @param name Name of table file (without extention)
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+
+ @note On success, m_file_buffer is allocated and must be
+ freed by the caller. m_name_buffer_ptr and m_tot_parts is also set.
+*/
+
+bool ha_partition::read_par_file(const char *name)
+{
+ char buff[FN_REFLEN];
+ uchar *tot_name_len_offset;
+ File file;
+ uchar *file_buffer;
+ uint i, len_bytes, len_words, tot_partition_words, tot_name_words, chksum;
+ DBUG_ENTER("ha_partition::read_par_file");
+ DBUG_PRINT("enter", ("table name: '%s'", name));
+
+ if (m_file_buffer)
+ DBUG_RETURN(false);
+ fn_format(buff, name, "", ha_par_ext, MY_APPEND_EXT);
+
+ /* Following could be done with mysql_file_stat to read in whole file */
+ if ((file= mysql_file_open(key_file_partition,
+ buff, O_RDONLY | O_SHARE, MYF(0))) < 0)
+ DBUG_RETURN(TRUE);
+ if (mysql_file_read(file, (uchar *) &buff[0], PAR_WORD_SIZE, MYF(MY_NABP)))
+ goto err1;
+ len_words= uint4korr(buff);
+ len_bytes= PAR_WORD_SIZE * len_words;
+ if (mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR)
+ goto err1;
+ if (!(file_buffer= (uchar*) alloc_root(&m_mem_root, len_bytes)))
+ goto err1;
+ if (mysql_file_read(file, file_buffer, len_bytes, MYF(MY_NABP)))
+ goto err2;
+
+ chksum= 0;
+ for (i= 0; i < len_words; i++)
+ chksum ^= uint4korr((file_buffer) + PAR_WORD_SIZE * i);
+ if (chksum)
+ goto err2;
+ m_tot_parts= uint4korr((file_buffer) + PAR_NUM_PARTS_OFFSET);
+ DBUG_PRINT("info", ("No of parts = %u", m_tot_parts));
+ tot_partition_words= (m_tot_parts + PAR_WORD_SIZE - 1) / PAR_WORD_SIZE;
+
+ tot_name_len_offset= file_buffer + PAR_ENGINES_OFFSET +
+ PAR_WORD_SIZE * tot_partition_words;
+ tot_name_words= (uint4korr(tot_name_len_offset) + PAR_WORD_SIZE - 1) /
+ PAR_WORD_SIZE;
+ /*
+ Verify the total length = tot size word, checksum word, num parts word +
+ engines array + name length word + name array.
+ */
+ if (len_words != (tot_partition_words + tot_name_words + 4))
+ goto err2;
+ m_file_buffer= file_buffer; // Will be freed in clear_handler_file()
+ m_name_buffer_ptr= (char*) (tot_name_len_offset + PAR_WORD_SIZE);
+
+ if (!(m_connect_string= (LEX_STRING*)
+ alloc_root(&m_mem_root, m_tot_parts * sizeof(LEX_STRING))))
+ goto err2;
+ bzero(m_connect_string, m_tot_parts * sizeof(LEX_STRING));
+
+ /* Read connection arguments (for federated X engine) */
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ LEX_STRING connect_string;
+ uchar buffer[4];
+ if (my_read(file, buffer, 4, MYF(MY_NABP)))
+ {
+ /* No extra options; Probably not a federatedx engine */
+ break;
+ }
+ connect_string.length= uint4korr(buffer);
+ connect_string.str= (char*) alloc_root(&m_mem_root, connect_string.length+1);
+ if (my_read(file, (uchar*) connect_string.str, connect_string.length,
+ MYF(MY_NABP)))
+ break;
+ connect_string.str[connect_string.length]= 0;
+ m_connect_string[i]= connect_string;
+ }
+
+ (void) mysql_file_close(file, MYF(0));
+ DBUG_RETURN(false);
+
+err2:
+err1:
+ (void) mysql_file_close(file, MYF(0));
+ DBUG_RETURN(true);
+}
+
+
+/**
+ Setup m_engine_array
+
+ @param mem_root MEM_ROOT to use for allocating new handlers
+
+ @return Operation status
+ @retval false Success
+ @retval true Failure
+*/
+
+bool ha_partition::setup_engine_array(MEM_ROOT *mem_root)
+{
+ uint i;
+ uchar *buff;
+ handlerton **engine_array, *first_engine;
+ enum legacy_db_type db_type, first_db_type;
+
+ DBUG_ASSERT(!m_file);
+ DBUG_ENTER("ha_partition::setup_engine_array");
+ engine_array= (handlerton **) my_alloca(m_tot_parts * sizeof(handlerton*));
+ if (!engine_array)
+ DBUG_RETURN(true);
+
+ buff= (uchar *) (m_file_buffer + PAR_ENGINES_OFFSET);
+ first_db_type= (enum legacy_db_type) buff[0];
+ first_engine= ha_resolve_by_legacy_type(ha_thd(), first_db_type);
+ if (!first_engine)
+ goto err;
+
+ if (!(m_engine_array= (plugin_ref*)
+ alloc_root(&m_mem_root, m_tot_parts * sizeof(plugin_ref))))
+ goto err;
+
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ db_type= (enum legacy_db_type) buff[i];
+ if (db_type != first_db_type)
+ {
+ DBUG_PRINT("error", ("partition %u engine %d is not same as "
+ "first partition %d", i, db_type,
+ (int) first_db_type));
+ DBUG_ASSERT(0);
+ clear_handler_file();
+ goto err;
+ }
+ m_engine_array[i]= ha_lock_engine(NULL, first_engine);
+ if (!m_engine_array[i])
+ {
+ clear_handler_file();
+ goto err;
+ }
+ }
+
+ my_afree(engine_array);
+
+ if (create_handlers(mem_root))
+ {
+ clear_handler_file();
+ DBUG_RETURN(true);
+ }
+
+ DBUG_RETURN(false);
+
+err:
+ my_afree(engine_array);
+ DBUG_RETURN(true);
+}
+
+
+/**
+ Get info about partition engines and their names from the .par file
+
+ @param name Full path of table name
+ @param mem_root Allocate memory through this
+ @param is_clone If it is a clone, don't create new handlers
+
+ @return Operation status
+ @retval true Error
+ @retval false Success
+
+ @note Open handler file to get partition names, engine types and number of
+ partitions.
+*/
+
+bool ha_partition::get_from_handler_file(const char *name, MEM_ROOT *mem_root,
+ bool is_clone)
+{
+ DBUG_ENTER("ha_partition::get_from_handler_file");
+ DBUG_PRINT("enter", ("table name: '%s'", name));
+
+ if (m_file_buffer)
+ DBUG_RETURN(false);
+
+ if (read_par_file(name))
+ DBUG_RETURN(true);
+
+ if (!is_clone && setup_engine_array(mem_root))
+ DBUG_RETURN(true);
+
+ DBUG_RETURN(false);
+}
+
+
+/****************************************************************************
+ MODULE open/close object
+****************************************************************************/
+
+/**
+ Get the partition name.
+
+ @param part Struct containing name and length
+ @param[out] length Length of the name
+
+ @return Partition name
+*/
+
+static uchar *get_part_name(PART_NAME_DEF *part, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= part->length;
+ return part->partition_name;
+}
+
+
+/**
+ Insert a partition name in the partition_name_hash.
+
+ @param name Name of partition
+ @param part_id Partition id (number)
+ @param is_subpart Set if the name belongs to a subpartition
+
+ @return Operation status
+ @retval true Failure
+ @retval false Sucess
+*/
+
+bool ha_partition::insert_partition_name_in_hash(const char *name, uint part_id,
+ bool is_subpart)
+{
+ PART_NAME_DEF *part_def;
+ uchar *part_name;
+ uint part_name_length;
+ DBUG_ENTER("ha_partition::insert_partition_name_in_hash");
+ /*
+ Calculate and store the length here, to avoid doing it when
+ searching the hash.
+ */
+ part_name_length= strlen(name);
+ /*
+ Must use memory that lives as long as table_share.
+ Freed in the Partition_share destructor.
+ Since we use my_multi_malloc, then my_free(part_def) will also free
+ part_name, as a part of my_hash_free.
+ */
+ if (!my_multi_malloc(MY_WME,
+ &part_def, sizeof(PART_NAME_DEF),
+ &part_name, part_name_length + 1,
+ NULL))
+ DBUG_RETURN(true);
+ memcpy(part_name, name, part_name_length + 1);
+ part_def->partition_name= part_name;
+ part_def->length= part_name_length;
+ part_def->part_id= part_id;
+ part_def->is_subpart= is_subpart;
+ if (my_hash_insert(&part_share->partition_name_hash, (uchar *) part_def))
+ {
+ my_free(part_def);
+ DBUG_RETURN(true);
+ }
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Populate the partition_name_hash in part_share.
+*/
+
+bool ha_partition::populate_partition_name_hash()
+{
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ uint num_parts= m_part_info->num_parts;
+ uint num_subparts= m_is_sub_partitioned ? m_part_info->num_subparts : 1;
+ uint tot_names;
+ uint i= 0;
+ DBUG_ASSERT(part_share);
+
+ DBUG_ENTER("ha_partition::populate_partition_name_hash");
+
+ /*
+ partition_name_hash is only set once and never changed
+ -> OK to check without locking.
+ */
+
+ if (part_share->partition_name_hash_initialized)
+ DBUG_RETURN(false);
+ lock_shared_ha_data();
+ if (part_share->partition_name_hash_initialized)
+ {
+ unlock_shared_ha_data();
+ DBUG_RETURN(false);
+ }
+ tot_names= m_is_sub_partitioned ? m_tot_parts + num_parts : num_parts;
+ if (my_hash_init(&part_share->partition_name_hash,
+ system_charset_info, tot_names, 0, 0,
+ (my_hash_get_key) get_part_name,
+ my_free, HASH_UNIQUE))
+ {
+ unlock_shared_ha_data();
+ DBUG_RETURN(TRUE);
+ }
+
+ do
+ {
+ partition_element *part_elem= part_it++;
+ DBUG_ASSERT(part_elem->part_state == PART_NORMAL);
+ if (part_elem->part_state == PART_NORMAL)
+ {
+ if (insert_partition_name_in_hash(part_elem->partition_name,
+ i * num_subparts, false))
+ goto err;
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element>
+ subpart_it(part_elem->subpartitions);
+ partition_element *sub_elem;
+ uint j= 0;
+ do
+ {
+ sub_elem= subpart_it++;
+ if (insert_partition_name_in_hash(sub_elem->partition_name,
+ i * num_subparts + j, true))
+ goto err;
+
+ } while (++j < num_subparts);
+ }
+ }
+ } while (++i < num_parts);
+
+ part_share->partition_name_hash_initialized= true;
+ unlock_shared_ha_data();
+
+ DBUG_RETURN(FALSE);
+err:
+ my_hash_free(&part_share->partition_name_hash);
+ unlock_shared_ha_data();
+
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Set Handler_share pointer and allocate Handler_share pointers
+ for each partition and set those.
+
+ @param ha_share_arg Where to store/retrieve the Partitioning_share pointer
+ to be shared by all instances of the same table.
+
+ @return Operation status
+ @retval true Failure
+ @retval false Sucess
+*/
+
+bool ha_partition::set_ha_share_ref(Handler_share **ha_share_arg)
+{
+ Handler_share **ha_shares;
+ uint i;
+ DBUG_ENTER("ha_partition::set_ha_share_ref");
+
+ DBUG_ASSERT(!part_share);
+ DBUG_ASSERT(table_share);
+ DBUG_ASSERT(!m_is_clone_of);
+ DBUG_ASSERT(m_tot_parts);
+ if (handler::set_ha_share_ref(ha_share_arg))
+ DBUG_RETURN(true);
+ if (!(part_share= get_share()))
+ DBUG_RETURN(true);
+ DBUG_ASSERT(part_share->partitions_share_refs);
+ DBUG_ASSERT(part_share->partitions_share_refs->num_parts >= m_tot_parts);
+ ha_shares= part_share->partitions_share_refs->ha_shares;
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ if (m_file[i]->set_ha_share_ref(&ha_shares[i]))
+ DBUG_RETURN(true);
+ }
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Get the PARTITION_SHARE for the table.
+
+ @return Operation status
+ @retval true Error
+ @retval false Success
+
+ @note Gets or initializes the Partition_share object used by partitioning.
+ The Partition_share is used for handling the auto_increment etc.
+*/
+
+Partition_share *ha_partition::get_share()
+{
+ Partition_share *tmp_share;
+ DBUG_ENTER("ha_partition::get_share");
+ DBUG_ASSERT(table_share);
+
+ lock_shared_ha_data();
+ if (!(tmp_share= static_cast<Partition_share*>(get_ha_share_ptr())))
+ {
+ tmp_share= new Partition_share;
+ if (!tmp_share)
+ goto err;
+ if (tmp_share->init(m_tot_parts))
+ {
+ delete tmp_share;
+ tmp_share= NULL;
+ goto err;
+ }
+ set_ha_share_ptr(static_cast<Handler_share*>(tmp_share));
+ }
+err:
+ unlock_shared_ha_data();
+ DBUG_RETURN(tmp_share);
+}
+
+
+
+/**
+ Helper function for freeing all internal bitmaps.
+*/
+
+void ha_partition::free_partition_bitmaps()
+{
+ /* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */
+ my_bitmap_free(&m_bulk_insert_started);
+ my_bitmap_free(&m_locked_partitions);
+ my_bitmap_free(&m_partitions_to_reset);
+ my_bitmap_free(&m_key_not_found_partitions);
+}
+
+
+/**
+ Helper function for initializing all internal bitmaps.
+*/
+
+bool ha_partition::init_partition_bitmaps()
+{
+ DBUG_ENTER("ha_partition::init_partition_bitmaps");
+ /* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */
+ if (my_bitmap_init(&m_bulk_insert_started, NULL, m_tot_parts + 1, FALSE))
+ DBUG_RETURN(true);
+ bitmap_clear_all(&m_bulk_insert_started);
+
+ /* Initialize the bitmap we use to keep track of locked partitions */
+ if (my_bitmap_init(&m_locked_partitions, NULL, m_tot_parts, FALSE))
+ {
+ my_bitmap_free(&m_bulk_insert_started);
+ DBUG_RETURN(true);
+ }
+ bitmap_clear_all(&m_locked_partitions);
+
+ /*
+ Initialize the bitmap we use to keep track of partitions which may have
+ something to reset in ha_reset().
+ */
+ if (my_bitmap_init(&m_partitions_to_reset, NULL, m_tot_parts, FALSE))
+ {
+ my_bitmap_free(&m_bulk_insert_started);
+ my_bitmap_free(&m_locked_partitions);
+ DBUG_RETURN(true);
+ }
+ bitmap_clear_all(&m_partitions_to_reset);
+
+ /*
+ Initialize the bitmap we use to keep track of partitions which returned
+ HA_ERR_KEY_NOT_FOUND from index_read_map.
+ */
+ if (my_bitmap_init(&m_key_not_found_partitions, NULL, m_tot_parts, FALSE))
+ {
+ my_bitmap_free(&m_bulk_insert_started);
+ my_bitmap_free(&m_locked_partitions);
+ my_bitmap_free(&m_partitions_to_reset);
+ DBUG_RETURN(true);
+ }
+ bitmap_clear_all(&m_key_not_found_partitions);
+ m_key_not_found= false;
+ /* Initialize the bitmap for read/lock_partitions */
+ if (!m_is_clone_of)
+ {
+ DBUG_ASSERT(!m_clone_mem_root);
+ if (m_part_info->set_partition_bitmaps(NULL))
+ {
+ free_partition_bitmaps();
+ DBUG_RETURN(true);
+ }
+ }
+ DBUG_RETURN(false);
+}
+
+
+/*
+ Open handler object
+
+ SYNOPSIS
+ open()
+ name Full path of table name
+ mode Open mode flags
+ test_if_locked ?
+
+ RETURN VALUE
+ >0 Error
+ 0 Success
+
+ DESCRIPTION
+ Used for opening tables. The name will be the name of the file.
+ A table is opened when it needs to be opened. For instance
+ when a request comes in for a select on the table (tables are not
+ open and closed for each request, they are cached).
+
+ Called from handler.cc by handler::ha_open(). The server opens all tables
+ by calling ha_open() which then calls the handler specific open().
+*/
+
+int ha_partition::open(const char *name, int mode, uint test_if_locked)
+{
+ char *name_buffer_ptr;
+ int error= HA_ERR_INITIALIZATION;
+ handler **file;
+ char name_buff[FN_REFLEN];
+ ulonglong check_table_flags;
+ DBUG_ENTER("ha_partition::open");
+
+ DBUG_ASSERT(table->s == table_share);
+ ref_length= 0;
+ m_mode= mode;
+ m_open_test_lock= test_if_locked;
+ m_part_field_array= m_part_info->full_part_field_array;
+ if (get_from_handler_file(name, &table->mem_root, MY_TEST(m_is_clone_of)))
+ DBUG_RETURN(error);
+ name_buffer_ptr= m_name_buffer_ptr;
+ if (populate_partition_name_hash())
+ {
+ DBUG_RETURN(HA_ERR_INITIALIZATION);
+ }
+ m_start_key.length= 0;
+ m_rec0= table->record[0];
+ m_rec_length= table_share->stored_rec_length;
+ if (!m_part_ids_sorted_by_num_of_records)
+ {
+ if (!(m_part_ids_sorted_by_num_of_records=
+ (uint32*) my_malloc(m_tot_parts * sizeof(uint32), MYF(MY_WME))))
+ DBUG_RETURN(error);
+ uint32 i;
+ /* Initialize it with all partition ids. */
+ for (i= 0; i < m_tot_parts; i++)
+ m_part_ids_sorted_by_num_of_records[i]= i;
+ }
+
+ if (init_partition_bitmaps())
+ DBUG_RETURN(error);
+
+ DBUG_ASSERT(m_part_info);
+
+ if (m_is_clone_of)
+ {
+ uint i, alloc_len;
+ DBUG_ASSERT(m_clone_mem_root);
+ /* Allocate an array of handler pointers for the partitions handlers. */
+ alloc_len= (m_tot_parts + 1) * sizeof(handler*);
+ if (!(m_file= (handler **) alloc_root(m_clone_mem_root, alloc_len)))
+ {
+ error= HA_ERR_INITIALIZATION;
+ goto err_alloc;
+ }
+ memset(m_file, 0, alloc_len);
+ /*
+ Populate them by cloning the original partitions. This also opens them.
+ Note that file->ref is allocated too.
+ */
+ file= m_is_clone_of->m_file;
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ create_partition_name(name_buff, name, name_buffer_ptr, NORMAL_PART_NAME,
+ FALSE);
+ /* ::clone() will also set ha_share from the original. */
+ if (!(m_file[i]= file[i]->clone(name_buff, m_clone_mem_root)))
+ {
+ error= HA_ERR_INITIALIZATION;
+ file= &m_file[i];
+ goto err_handler;
+ }
+ name_buffer_ptr+= strlen(name_buffer_ptr) + 1;
+ }
+ }
+ else
+ {
+ file= m_file;
+ do
+ {
+ create_partition_name(name_buff, name, name_buffer_ptr, NORMAL_PART_NAME,
+ FALSE);
+ table->s->connect_string = m_connect_string[(uint)(file-m_file)];
+ if ((error= (*file)->ha_open(table, name_buff, mode,
+ test_if_locked | HA_OPEN_NO_PSI_CALL)))
+ goto err_handler;
+ bzero(&table->s->connect_string, sizeof(LEX_STRING));
+ if (m_file == file)
+ m_num_locks= (*file)->lock_count();
+ DBUG_ASSERT(m_num_locks == (*file)->lock_count());
+ name_buffer_ptr+= strlen(name_buffer_ptr) + 1;
+ } while (*(++file));
+ }
+
+ file= m_file;
+ ref_length= (*file)->ref_length;
+ check_table_flags= (((*file)->ha_table_flags() &
+ ~(PARTITION_DISABLED_TABLE_FLAGS)) |
+ (PARTITION_ENABLED_TABLE_FLAGS));
+ while (*(++file))
+ {
+ /* MyISAM can have smaller ref_length for partitions with MAX_ROWS set */
+ set_if_bigger(ref_length, ((*file)->ref_length));
+ /*
+ Verify that all partitions have the same set of table flags.
+ Mask all flags that partitioning enables/disables.
+ */
+ if (check_table_flags != (((*file)->ha_table_flags() &
+ ~(PARTITION_DISABLED_TABLE_FLAGS)) |
+ (PARTITION_ENABLED_TABLE_FLAGS)))
+ {
+ error= HA_ERR_INITIALIZATION;
+ /* set file to last handler, so all of them are closed */
+ file = &m_file[m_tot_parts - 1];
+ goto err_handler;
+ }
+ }
+ key_used_on_scan= m_file[0]->key_used_on_scan;
+ implicit_emptied= m_file[0]->implicit_emptied;
+ /*
+ Add 2 bytes for partition id in position ref length.
+ ref_length=max_in_all_partitions(ref_length) + PARTITION_BYTES_IN_POS
+ */
+ ref_length+= PARTITION_BYTES_IN_POS;
+ m_ref_length= ref_length;
+
+ /*
+ Release buffer read from .par file. It will not be reused again after
+ being opened once.
+ */
+ clear_handler_file();
+
+ /*
+ Some handlers update statistics as part of the open call. This will in
+ some cases corrupt the statistics of the partition handler and thus
+ to ensure we have correct statistics we call info from open after
+ calling open on all individual handlers.
+ */
+ m_handler_status= handler_opened;
+ if (m_part_info->part_expr)
+ m_part_func_monotonicity_info=
+ m_part_info->part_expr->get_monotonicity_info();
+ else if (m_part_info->list_of_part_fields)
+ m_part_func_monotonicity_info= MONOTONIC_STRICT_INCREASING;
+ info(HA_STATUS_VARIABLE | HA_STATUS_CONST);
+ DBUG_RETURN(0);
+
+err_handler:
+ DEBUG_SYNC(ha_thd(), "partition_open_error");
+ while (file-- != m_file)
+ (*file)->ha_close();
+err_alloc:
+ free_partition_bitmaps();
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Disabled since it is not possible to prune yet.
+ without pruning, it need to rebind/unbind every partition in every
+ statement which uses a table from the table cache. Will also use
+ as many PSI_tables as there are partitions.
+*/
+#ifdef HAVE_M_PSI_PER_PARTITION
+void ha_partition::unbind_psi()
+{
+ uint i;
+
+ DBUG_ENTER("ha_partition::unbind_psi");
+ handler::unbind_psi();
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ DBUG_ASSERT(m_file[i] != NULL);
+ m_file[i]->unbind_psi();
+ }
+ DBUG_VOID_RETURN;
+}
+
+void ha_partition::rebind_psi()
+{
+ uint i;
+
+ DBUG_ENTER("ha_partition::rebind_psi");
+ handler::rebind_psi();
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ DBUG_ASSERT(m_file[i] != NULL);
+ m_file[i]->rebind_psi();
+ }
+ DBUG_VOID_RETURN;
+}
+#endif /* HAVE_M_PSI_PER_PARTITION */
+
+
+/**
+ Clone the open and locked partitioning handler.
+
+ @param mem_root MEM_ROOT to use.
+
+ @return Pointer to the successfully created clone or NULL
+
+ @details
+ This function creates a new ha_partition handler as a clone/copy. The
+ original (this) must already be opened and locked. The clone will use
+ the originals m_part_info.
+ It also allocates memory for ref + ref_dup.
+ In ha_partition::open() it will clone its original handlers partitions
+ which will allocate then on the correct MEM_ROOT and also open them.
+*/
+
+handler *ha_partition::clone(const char *name, MEM_ROOT *mem_root)
+{
+ ha_partition *new_handler;
+
+ DBUG_ENTER("ha_partition::clone");
+ new_handler= new (mem_root) ha_partition(ht, table_share, m_part_info,
+ this, mem_root);
+ if (!new_handler)
+ DBUG_RETURN(NULL);
+
+ /*
+ We will not clone each partition's handler here, it will be done in
+ ha_partition::open() for clones. Also set_ha_share_ref is not needed
+ here, since 1) ha_share is copied in the constructor used above
+ 2) each partition's cloned handler will set it from its original.
+ */
+
+ /*
+ Allocate new_handler->ref here because otherwise ha_open will allocate it
+ on this->table->mem_root and we will not be able to reclaim that memory
+ when the clone handler object is destroyed.
+ */
+ if (!(new_handler->ref= (uchar*) alloc_root(mem_root,
+ ALIGN_SIZE(m_ref_length)*2)))
+ goto err;
+
+ if (new_handler->ha_open(table, name,
+ table->db_stat,
+ HA_OPEN_IGNORE_IF_LOCKED | HA_OPEN_NO_PSI_CALL))
+ goto err;
+
+ DBUG_RETURN((handler*) new_handler);
+
+err:
+ delete new_handler;
+ DBUG_RETURN(NULL);
+}
+
+
+/*
+ Close handler object
+
+ SYNOPSIS
+ close()
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ Called from sql_base.cc, sql_select.cc, and table.cc.
+ In sql_select.cc it is only used to close up temporary tables or during
+ the process where a temporary table is converted over to being a
+ myisam table.
+ For sql_base.cc look at close_data_tables().
+*/
+
+int ha_partition::close(void)
+{
+ bool first= TRUE;
+ handler **file;
+ DBUG_ENTER("ha_partition::close");
+
+ DBUG_ASSERT(table->s == table_share);
+ destroy_record_priority_queue();
+ free_partition_bitmaps();
+ DBUG_ASSERT(m_part_info);
+ file= m_file;
+
+repeat:
+ do
+ {
+ (*file)->ha_close();
+ } while (*(++file));
+
+ if (first && m_added_file && m_added_file[0])
+ {
+ file= m_added_file;
+ first= FALSE;
+ goto repeat;
+ }
+
+ m_handler_status= handler_closed;
+ DBUG_RETURN(0);
+}
+
+/****************************************************************************
+ MODULE start/end statement
+****************************************************************************/
+/*
+ A number of methods to define various constants for the handler. In
+ the case of the partition handler we need to use some max and min
+ of the underlying handlers in most cases.
+*/
+
+/*
+ Set external locks on table
+
+ SYNOPSIS
+ external_lock()
+ thd Thread object
+ lock_type Type of external lock
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ First you should go read the section "locking functions for mysql" in
+ lock.cc to understand this.
+ This create a lock on the table. If you are implementing a storage engine
+ that can handle transactions look at ha_berkeley.cc to see how you will
+ want to go about doing this. Otherwise you should consider calling
+ flock() here.
+ Originally this method was used to set locks on file level to enable
+ several MySQL Servers to work on the same data. For transactional
+ engines it has been "abused" to also mean start and end of statements
+ to enable proper rollback of statements and transactions. When LOCK
+ TABLES has been issued the start_stmt method takes over the role of
+ indicating start of statement but in this case there is no end of
+ statement indicator(?).
+
+ Called from lock.cc by lock_external() and unlock_external(). Also called
+ from sql_table.cc by copy_data_between_tables().
+*/
+
+int ha_partition::external_lock(THD *thd, int lock_type)
+{
+ uint error;
+ uint i, first_used_partition;
+ MY_BITMAP *used_partitions;
+ DBUG_ENTER("ha_partition::external_lock");
+
+ DBUG_ASSERT(!auto_increment_lock && !auto_increment_safe_stmt_log_lock);
+
+ if (lock_type == F_UNLCK)
+ used_partitions= &m_locked_partitions;
+ else
+ used_partitions= &(m_part_info->lock_partitions);
+
+ first_used_partition= bitmap_get_first_set(used_partitions);
+
+ for (i= first_used_partition;
+ i < m_tot_parts;
+ i= bitmap_get_next_set(used_partitions, i))
+ {
+ DBUG_PRINT("info", ("external_lock(thd, %d) part %d", lock_type, i));
+ if ((error= m_file[i]->ha_external_lock(thd, lock_type)))
+ {
+ if (lock_type != F_UNLCK)
+ goto err_handler;
+ }
+ DBUG_PRINT("info", ("external_lock part %u lock %d", i, lock_type));
+ if (lock_type != F_UNLCK)
+ bitmap_set_bit(&m_locked_partitions, i);
+ }
+ if (lock_type == F_UNLCK)
+ {
+ bitmap_clear_all(used_partitions);
+ }
+ else
+ {
+ /* Add touched partitions to be included in reset(). */
+ bitmap_union(&m_partitions_to_reset, used_partitions);
+ }
+
+ if (m_added_file && m_added_file[0])
+ {
+ handler **file= m_added_file;
+ DBUG_ASSERT(lock_type == F_UNLCK);
+ do
+ {
+ (void) (*file)->ha_external_lock(thd, lock_type);
+ } while (*(++file));
+ }
+ DBUG_RETURN(0);
+
+err_handler:
+ uint j;
+ for (j= first_used_partition;
+ j < i;
+ j= bitmap_get_next_set(&m_locked_partitions, j))
+ {
+ (void) m_file[j]->ha_external_lock(thd, F_UNLCK);
+ }
+ bitmap_clear_all(&m_locked_partitions);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Get the lock(s) for the table and perform conversion of locks if needed
+
+ SYNOPSIS
+ store_lock()
+ thd Thread object
+ to Lock object array
+ lock_type Table lock type
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ The idea with handler::store_lock() is the following:
+
+ The statement decided which locks we should need for the table
+ for updates/deletes/inserts we get WRITE locks, for SELECT... we get
+ read locks.
+
+ Before adding the lock into the table lock handler (see thr_lock.c)
+ mysqld calls store lock with the requested locks. Store lock can now
+ modify a write lock to a read lock (or some other lock), ignore the
+ lock (if we don't want to use MySQL table locks at all) or add locks
+ for many tables (like we do when we are using a MERGE handler).
+
+ Berkeley DB for partition changes all WRITE locks to TL_WRITE_ALLOW_WRITE
+ (which signals that we are doing WRITES, but we are still allowing other
+ reader's and writer's.
+
+ When releasing locks, store_lock() is also called. In this case one
+ usually doesn't have to do anything.
+
+ store_lock is called when holding a global mutex to ensure that only
+ one thread at a time changes the locking information of tables.
+
+ In some exceptional cases MySQL may send a request for a TL_IGNORE;
+ This means that we are requesting the same lock as last time and this
+ should also be ignored. (This may happen when someone does a flush
+ table when we have opened a part of the tables, in which case mysqld
+ closes and reopens the tables and tries to get the same locks as last
+ time). In the future we will probably try to remove this.
+
+ Called from lock.cc by get_lock_data().
+*/
+
+THR_LOCK_DATA **ha_partition::store_lock(THD *thd,
+ THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type)
+{
+ uint i;
+ DBUG_ENTER("ha_partition::store_lock");
+ DBUG_ASSERT(thd == current_thd);
+
+ /*
+ This can be called from get_lock_data() in mysql_lock_abort_for_thread(),
+ even when thd != table->in_use. In that case don't use partition pruning,
+ but use all partitions instead to avoid using another threads structures.
+ */
+ if (thd != table->in_use)
+ {
+ for (i= 0; i < m_tot_parts; i++)
+ to= m_file[i]->store_lock(thd, to, lock_type);
+ }
+ else
+ {
+ for (i= bitmap_get_first_set(&(m_part_info->lock_partitions));
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->lock_partitions, i))
+ {
+ DBUG_PRINT("info", ("store lock %d iteration", i));
+ to= m_file[i]->store_lock(thd, to, lock_type);
+ }
+ }
+ DBUG_RETURN(to);
+}
+
+/*
+ Start a statement when table is locked
+
+ SYNOPSIS
+ start_stmt()
+ thd Thread object
+ lock_type Type of external lock
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ This method is called instead of external lock when the table is locked
+ before the statement is executed.
+*/
+
+int ha_partition::start_stmt(THD *thd, thr_lock_type lock_type)
+{
+ int error= 0;
+ uint i;
+ /* Assert that read_partitions is included in lock_partitions */
+ DBUG_ASSERT(bitmap_is_subset(&m_part_info->read_partitions,
+ &m_part_info->lock_partitions));
+ /*
+ m_locked_partitions is set in previous external_lock/LOCK TABLES.
+ Current statement's lock requests must not include any partitions
+ not previously locked.
+ */
+ DBUG_ASSERT(bitmap_is_subset(&m_part_info->lock_partitions,
+ &m_locked_partitions));
+ DBUG_ENTER("ha_partition::start_stmt");
+
+ for (i= bitmap_get_first_set(&(m_part_info->lock_partitions));
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->lock_partitions, i))
+ {
+ if ((error= m_file[i]->start_stmt(thd, lock_type)))
+ break;
+ /* Add partition to be called in reset(). */
+ bitmap_set_bit(&m_partitions_to_reset, i);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Get number of lock objects returned in store_lock
+
+ @returns Number of locks returned in call to store_lock
+
+ @desc
+ Returns the number of store locks needed in call to store lock.
+ We return number of partitions we will lock multiplied with number of
+ locks needed by each partition. Assists the above functions in allocating
+ sufficient space for lock structures.
+*/
+
+uint ha_partition::lock_count() const
+{
+ DBUG_ENTER("ha_partition::lock_count");
+ /*
+ The caller want to know the upper bound, to allocate enough memory.
+ There is no performance lost if we simply return maximum number locks
+ needed, only some minor over allocation of memory in get_lock_data().
+
+ Also notice that this may be called for another thread != table->in_use,
+ when mysql_lock_abort_for_thread() is called. So this is more safe, then
+ using number of partitions after pruning.
+ */
+ DBUG_RETURN(m_tot_parts * m_num_locks);
+}
+
+
+/*
+ Unlock last accessed row
+
+ SYNOPSIS
+ unlock_row()
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ Record currently processed was not in the result set of the statement
+ and is thus unlocked. Used for UPDATE and DELETE queries.
+*/
+
+void ha_partition::unlock_row()
+{
+ DBUG_ENTER("ha_partition::unlock_row");
+ m_file[m_last_part]->unlock_row();
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Check if semi consistent read was used
+
+ SYNOPSIS
+ was_semi_consistent_read()
+
+ RETURN VALUE
+ TRUE Previous read was a semi consistent read
+ FALSE Previous read was not a semi consistent read
+
+ DESCRIPTION
+ See handler.h:
+ In an UPDATE or DELETE, if the row under the cursor was locked by another
+ transaction, and the engine used an optimistic read of the last
+ committed row value under the cursor, then the engine returns 1 from this
+ function. MySQL must NOT try to update this optimistic value. If the
+ optimistic value does not match the WHERE condition, MySQL can decide to
+ skip over this row. Currently only works for InnoDB. This can be used to
+ avoid unnecessary lock waits.
+
+ If this method returns nonzero, it will also signal the storage
+ engine that the next read will be a locking re-read of the row.
+*/
+bool ha_partition::was_semi_consistent_read()
+{
+ DBUG_ENTER("ha_partition::was_semi_consistent_read");
+ DBUG_ASSERT(m_last_part < m_tot_parts &&
+ bitmap_is_set(&(m_part_info->read_partitions), m_last_part));
+ DBUG_RETURN(m_file[m_last_part]->was_semi_consistent_read());
+}
+
+/**
+ Use semi consistent read if possible
+
+ SYNOPSIS
+ try_semi_consistent_read()
+ yes Turn on semi consistent read
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ See handler.h:
+ Tell the engine whether it should avoid unnecessary lock waits.
+ If yes, in an UPDATE or DELETE, if the row under the cursor was locked
+ by another transaction, the engine may try an optimistic read of
+ the last committed row value under the cursor.
+ Note: prune_partitions are already called before this call, so using
+ pruning is OK.
+*/
+void ha_partition::try_semi_consistent_read(bool yes)
+{
+ uint i;
+ DBUG_ENTER("ha_partition::try_semi_consistent_read");
+
+ i= bitmap_get_first_set(&(m_part_info->read_partitions));
+ DBUG_ASSERT(i != MY_BIT_NONE);
+ for (;
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ m_file[i]->try_semi_consistent_read(yes);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/****************************************************************************
+ MODULE change record
+****************************************************************************/
+
+/*
+ Insert a row to the table
+
+ SYNOPSIS
+ write_row()
+ buf The row in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ write_row() inserts a row. buf() is a byte array of data, normally
+ record[0].
+
+ You can use the field information to extract the data from the native byte
+ array type.
+
+ Example of this would be:
+ for (Field **field=table->field ; *field ; field++)
+ {
+ ...
+ }
+
+ See ha_tina.cc for a variant of extracting all of the data as strings.
+ ha_berkeley.cc has a variant of how to store it intact by "packing" it
+ for ha_berkeley's own native storage type.
+
+ Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
+ sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc.
+
+ ADDITIONAL INFO:
+
+ We have to set auto_increment fields, because those may be used in
+ determining which partition the row should be written to.
+*/
+
+int ha_partition::write_row(uchar * buf)
+{
+ uint32 part_id;
+ int error;
+ longlong func_value;
+ bool have_auto_increment= table->next_number_field && buf == table->record[0];
+ my_bitmap_map *old_map;
+ THD *thd= ha_thd();
+ sql_mode_t saved_sql_mode= thd->variables.sql_mode;
+ bool saved_auto_inc_field_not_null= table->auto_increment_field_not_null;
+ DBUG_ENTER("ha_partition::write_row");
+ DBUG_ASSERT(buf == m_rec0);
+
+ /*
+ If we have an auto_increment column and we are writing a changed row
+ or a new row, then update the auto_increment value in the record.
+ */
+ if (have_auto_increment)
+ {
+ if (!part_share->auto_inc_initialized &&
+ !table_share->next_number_keypart)
+ {
+ /*
+ If auto_increment in table_share is not initialized, start by
+ initializing it.
+ */
+ info(HA_STATUS_AUTO);
+ }
+ error= update_auto_increment();
+
+ /*
+ If we have failed to set the auto-increment value for this row,
+ it is highly likely that we will not be able to insert it into
+ the correct partition. We must check and fail if neccessary.
+ */
+ if (error)
+ goto exit;
+
+ /*
+ Don't allow generation of auto_increment value the partitions handler.
+ If a partitions handler would change the value, then it might not
+ match the partition any longer.
+ This can occur if 'SET INSERT_ID = 0; INSERT (NULL)',
+ So allow this by adding 'MODE_NO_AUTO_VALUE_ON_ZERO' to sql_mode.
+ The partitions handler::next_insert_id must always be 0. Otherwise
+ we need to forward release_auto_increment, or reset it for all
+ partitions.
+ */
+ if (table->next_number_field->val_int() == 0)
+ {
+ table->auto_increment_field_not_null= TRUE;
+ thd->variables.sql_mode|= MODE_NO_AUTO_VALUE_ON_ZERO;
+ }
+ }
+
+ old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ error= m_part_info->get_partition_id(m_part_info, &part_id, &func_value);
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+ if (unlikely(error))
+ {
+ m_part_info->err_value= func_value;
+ goto exit;
+ }
+ if (!bitmap_is_set(&(m_part_info->lock_partitions), part_id))
+ {
+ DBUG_PRINT("info", ("Write to non-locked partition %u (func_value: %ld)",
+ part_id, (long) func_value));
+ error= HA_ERR_NOT_IN_LOCK_PARTITIONS;
+ goto exit;
+ }
+ m_last_part= part_id;
+ DBUG_PRINT("info", ("Insert in partition %d", part_id));
+ start_part_bulk_insert(thd, part_id);
+
+ tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */
+ error= m_file[part_id]->ha_write_row(buf);
+ if (have_auto_increment && !table->s->next_number_keypart)
+ set_auto_increment_if_higher(table->next_number_field);
+ reenable_binlog(thd);
+exit:
+ thd->variables.sql_mode= saved_sql_mode;
+ table->auto_increment_field_not_null= saved_auto_inc_field_not_null;
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Update an existing row
+
+ SYNOPSIS
+ update_row()
+ old_data Old record in MySQL Row Format
+ new_data New record in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ Yes, update_row() does what you expect, it updates a row. old_data will
+ have the previous row record in it, while new_data will have the newest
+ data in it.
+ Keep in mind that the server can do updates based on ordering if an
+ ORDER BY clause was used. Consecutive ordering is not guarenteed.
+
+ Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc.
+ new_data is always record[0]
+ old_data is always record[1]
+*/
+
+int ha_partition::update_row(const uchar *old_data, uchar *new_data)
+{
+ THD *thd= ha_thd();
+ uint32 new_part_id, old_part_id;
+ int error= 0;
+ longlong func_value;
+ DBUG_ENTER("ha_partition::update_row");
+ m_err_rec= NULL;
+
+ // Need to read partition-related columns, to locate the row's partition:
+ DBUG_ASSERT(bitmap_is_subset(&m_part_info->full_part_field_set,
+ table->read_set));
+ if ((error= get_parts_for_update(old_data, new_data, table->record[0],
+ m_part_info, &old_part_id, &new_part_id,
+ &func_value)))
+ {
+ m_part_info->err_value= func_value;
+ goto exit;
+ }
+ DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), old_part_id));
+ if (!bitmap_is_set(&(m_part_info->lock_partitions), new_part_id))
+ {
+ error= HA_ERR_NOT_IN_LOCK_PARTITIONS;
+ goto exit;
+ }
+
+ /*
+ The protocol for updating a row is:
+ 1) position the handler (cursor) on the row to be updated,
+ either through the last read row (rnd or index) or by rnd_pos.
+ 2) call update_row with both old and new full records as arguments.
+
+ This means that m_last_part should already be set to actual partition
+ where the row was read from. And if that is not the same as the
+ calculated part_id we found a misplaced row, we return an error to
+ notify the user that something is broken in the row distribution
+ between partitions! Since we don't check all rows on read, we return an
+ error instead of correcting m_last_part, to make the user aware of the
+ problem!
+
+ Notice that HA_READ_BEFORE_WRITE_REMOVAL does not require this protocol,
+ so this is not supported for this engine.
+ */
+ if (old_part_id != m_last_part)
+ {
+ m_err_rec= old_data;
+ DBUG_RETURN(HA_ERR_ROW_IN_WRONG_PARTITION);
+ }
+
+ m_last_part= new_part_id;
+ start_part_bulk_insert(thd, new_part_id);
+ if (new_part_id == old_part_id)
+ {
+ DBUG_PRINT("info", ("Update in partition %d", new_part_id));
+ tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */
+ error= m_file[new_part_id]->ha_update_row(old_data, new_data);
+ reenable_binlog(thd);
+ goto exit;
+ }
+ else
+ {
+ Field *saved_next_number_field= table->next_number_field;
+ /*
+ Don't allow generation of auto_increment value for update.
+ table->next_number_field is never set on UPDATE.
+ But is set for INSERT ... ON DUPLICATE KEY UPDATE,
+ and since update_row() does not generate or update an auto_inc value,
+ we cannot have next_number_field set when moving a row
+ to another partition with write_row(), since that could
+ generate/update the auto_inc value.
+ This gives the same behavior for partitioned vs non partitioned tables.
+ */
+ table->next_number_field= NULL;
+ DBUG_PRINT("info", ("Update from partition %d to partition %d",
+ old_part_id, new_part_id));
+ tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */
+ error= m_file[new_part_id]->ha_write_row(new_data);
+ reenable_binlog(thd);
+ table->next_number_field= saved_next_number_field;
+ if (error)
+ goto exit;
+
+ tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */
+ error= m_file[old_part_id]->ha_delete_row(old_data);
+ reenable_binlog(thd);
+ if (error)
+ {
+#ifdef IN_THE_FUTURE
+ (void) m_file[new_part_id]->delete_last_inserted_row(new_data);
+#endif
+ goto exit;
+ }
+ }
+
+exit:
+ /*
+ if updating an auto_increment column, update
+ part_share->next_auto_inc_val if needed.
+ (not to be used if auto_increment on secondary field in a multi-column
+ index)
+ mysql_update does not set table->next_number_field, so we use
+ table->found_next_number_field instead.
+ Also checking that the field is marked in the write set.
+ */
+ if (table->found_next_number_field &&
+ new_data == table->record[0] &&
+ !table->s->next_number_keypart &&
+ bitmap_is_set(table->write_set,
+ table->found_next_number_field->field_index))
+ {
+ if (!part_share->auto_inc_initialized)
+ info(HA_STATUS_AUTO);
+ set_auto_increment_if_higher(table->found_next_number_field);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Remove an existing row
+
+ SYNOPSIS
+ delete_row
+ buf Deleted row in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error Code
+ 0 Success
+
+ DESCRIPTION
+ This will delete a row. buf will contain a copy of the row to be deleted.
+ The server will call this right after the current row has been read
+ (from either a previous rnd_xxx() or index_xxx() call).
+ If you keep a pointer to the last row or can access a primary key it will
+ make doing the deletion quite a bit easier.
+ Keep in mind that the server does no guarentee consecutive deletions.
+ ORDER BY clauses can be used.
+
+ Called in sql_acl.cc and sql_udf.cc to manage internal table information.
+ Called in sql_delete.cc, sql_insert.cc, and sql_select.cc. In sql_select
+ it is used for removing duplicates while in insert it is used for REPLACE
+ calls.
+
+ buf is either record[0] or record[1]
+*/
+
+int ha_partition::delete_row(const uchar *buf)
+{
+ uint32 part_id;
+ int error;
+ THD *thd= ha_thd();
+ DBUG_ENTER("ha_partition::delete_row");
+ m_err_rec= NULL;
+
+ DBUG_ASSERT(bitmap_is_subset(&m_part_info->full_part_field_set,
+ table->read_set));
+ if ((error= get_part_for_delete(buf, m_rec0, m_part_info, &part_id)))
+ {
+ DBUG_RETURN(error);
+ }
+ /* Should never call delete_row on a partition which is not read */
+ DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), part_id));
+ DBUG_ASSERT(bitmap_is_set(&(m_part_info->lock_partitions), part_id));
+ if (!bitmap_is_set(&(m_part_info->lock_partitions), part_id))
+ DBUG_RETURN(HA_ERR_NOT_IN_LOCK_PARTITIONS);
+
+ /*
+ The protocol for deleting a row is:
+ 1) position the handler (cursor) on the row to be deleted,
+ either through the last read row (rnd or index) or by rnd_pos.
+ 2) call delete_row with the full record as argument.
+
+ This means that m_last_part should already be set to actual partition
+ where the row was read from. And if that is not the same as the
+ calculated part_id we found a misplaced row, we return an error to
+ notify the user that something is broken in the row distribution
+ between partitions! Since we don't check all rows on read, we return an
+ error instead of forwarding the delete to the correct (m_last_part)
+ partition!
+
+ Notice that HA_READ_BEFORE_WRITE_REMOVAL does not require this protocol,
+ so this is not supported for this engine.
+
+ TODO: change the assert in InnoDB into an error instead and make this one
+ an assert instead and remove the get_part_for_delete()!
+ */
+ if (part_id != m_last_part)
+ {
+ m_err_rec= buf;
+ DBUG_RETURN(HA_ERR_ROW_IN_WRONG_PARTITION);
+ }
+
+ m_last_part= part_id;
+ tmp_disable_binlog(thd);
+ error= m_file[part_id]->ha_delete_row(buf);
+ reenable_binlog(thd);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Delete all rows in a table
+
+ SYNOPSIS
+ delete_all_rows()
+
+ RETURN VALUE
+ >0 Error Code
+ 0 Success
+
+ DESCRIPTION
+ Used to delete all rows in a table. Both for cases of truncate and
+ for cases where the optimizer realizes that all rows will be
+ removed as a result of a SQL statement.
+
+ Called from item_sum.cc by Item_func_group_concat::clear(),
+ Item_sum_count_distinct::clear(), and Item_func_group_concat::clear().
+ Called from sql_delete.cc by mysql_delete().
+ Called from sql_select.cc by JOIN::reset().
+ Called from sql_union.cc by st_select_lex_unit::exec().
+*/
+
+int ha_partition::delete_all_rows()
+{
+ int error;
+ uint i;
+ DBUG_ENTER("ha_partition::delete_all_rows");
+
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ /* Can be pruned, like DELETE FROM t PARTITION (pX) */
+ if ((error= m_file[i]->ha_delete_all_rows()))
+ DBUG_RETURN(error);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Manually truncate the table.
+
+ @retval 0 Success.
+ @retval > 0 Error code.
+*/
+
+int ha_partition::truncate()
+{
+ int error;
+ handler **file;
+ DBUG_ENTER("ha_partition::truncate");
+
+ /*
+ TRUNCATE also means resetting auto_increment. Hence, reset
+ it so that it will be initialized again at the next use.
+ */
+ lock_auto_increment();
+ part_share->next_auto_inc_val= 0;
+ part_share->auto_inc_initialized= false;
+ unlock_auto_increment();
+
+ file= m_file;
+ do
+ {
+ if ((error= (*file)->ha_truncate()))
+ DBUG_RETURN(error);
+ } while (*(++file));
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Truncate a set of specific partitions.
+
+ @remark Auto increment value will be truncated in that partition as well!
+
+ ALTER TABLE t TRUNCATE PARTITION ...
+*/
+
+int ha_partition::truncate_partition(Alter_info *alter_info, bool *binlog_stmt)
+{
+ int error= 0;
+ List_iterator<partition_element> part_it(m_part_info->partitions);
+ uint num_parts= m_part_info->num_parts;
+ uint num_subparts= m_part_info->num_subparts;
+ uint i= 0;
+ DBUG_ENTER("ha_partition::truncate_partition");
+
+ /* Only binlog when it starts any call to the partitions handlers */
+ *binlog_stmt= false;
+
+ if (set_part_state(alter_info, m_part_info, PART_ADMIN))
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+
+ /*
+ TRUNCATE also means resetting auto_increment. Hence, reset
+ it so that it will be initialized again at the next use.
+ */
+ lock_auto_increment();
+ part_share->next_auto_inc_val= 0;
+ part_share->auto_inc_initialized= FALSE;
+ unlock_auto_increment();
+
+ *binlog_stmt= true;
+
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_ADMIN)
+ {
+ if (m_is_sub_partitioned)
+ {
+ List_iterator<partition_element>
+ subpart_it(part_elem->subpartitions);
+ partition_element *sub_elem;
+ uint j= 0, part;
+ do
+ {
+ sub_elem= subpart_it++;
+ part= i * num_subparts + j;
+ DBUG_PRINT("info", ("truncate subpartition %u (%s)",
+ part, sub_elem->partition_name));
+ if ((error= m_file[part]->ha_truncate()))
+ break;
+ sub_elem->part_state= PART_NORMAL;
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("truncate partition %u (%s)", i,
+ part_elem->partition_name));
+ error= m_file[i]->ha_truncate();
+ }
+ part_elem->part_state= PART_NORMAL;
+ }
+ } while (!error && (++i < num_parts));
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Start a large batch of insert rows
+
+ SYNOPSIS
+ start_bulk_insert()
+ rows Number of rows to insert
+ flags Flags to control index creation
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ rows == 0 means we will probably insert many rows
+*/
+void ha_partition::start_bulk_insert(ha_rows rows, uint flags)
+{
+ DBUG_ENTER("ha_partition::start_bulk_insert");
+
+ m_bulk_inserted_rows= 0;
+ bitmap_clear_all(&m_bulk_insert_started);
+ /* use the last bit for marking if bulk_insert_started was called */
+ bitmap_set_bit(&m_bulk_insert_started, m_tot_parts);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Check if start_bulk_insert has been called for this partition,
+ if not, call it and mark it called
+*/
+void ha_partition::start_part_bulk_insert(THD *thd, uint part_id)
+{
+ long old_buffer_size;
+ if (!bitmap_is_set(&m_bulk_insert_started, part_id) &&
+ bitmap_is_set(&m_bulk_insert_started, m_tot_parts))
+ {
+ DBUG_ASSERT(bitmap_is_set(&(m_part_info->lock_partitions), part_id));
+ old_buffer_size= thd->variables.read_buff_size;
+ /* Update read_buffer_size for this partition */
+ thd->variables.read_buff_size= estimate_read_buffer_size(old_buffer_size);
+ m_file[part_id]->ha_start_bulk_insert(guess_bulk_insert_rows());
+ bitmap_set_bit(&m_bulk_insert_started, part_id);
+ thd->variables.read_buff_size= old_buffer_size;
+ }
+ m_bulk_inserted_rows++;
+}
+
+/*
+ Estimate the read buffer size for each partition.
+ SYNOPSIS
+ ha_partition::estimate_read_buffer_size()
+ original_size read buffer size originally set for the server
+ RETURN VALUE
+ estimated buffer size.
+ DESCRIPTION
+ If the estimated number of rows to insert is less than 10 (but not 0)
+ the new buffer size is same as original buffer size.
+ In case of first partition of when partition function is monotonic
+ new buffer size is same as the original buffer size.
+ For rest of the partition total buffer of 10*original_size is divided
+ equally if number of partition is more than 10 other wise each partition
+ will be allowed to use original buffer size.
+*/
+long ha_partition::estimate_read_buffer_size(long original_size)
+{
+ /*
+ If number of rows to insert is less than 10, but not 0,
+ return original buffer size.
+ */
+ if (estimation_rows_to_insert && (estimation_rows_to_insert < 10))
+ return (original_size);
+ /*
+ If first insert/partition and monotonic partition function,
+ allow using buffer size originally set.
+ */
+ if (!m_bulk_inserted_rows &&
+ m_part_func_monotonicity_info != NON_MONOTONIC &&
+ m_tot_parts > 1)
+ return original_size;
+ /*
+ Allow total buffer used in all partition to go up to 10*read_buffer_size.
+ 11*read_buffer_size in case of monotonic partition function.
+ */
+
+ if (m_tot_parts < 10)
+ return original_size;
+ return (original_size * 10 / m_tot_parts);
+}
+
+/*
+ Try to predict the number of inserts into this partition.
+
+ If less than 10 rows (including 0 which means Unknown)
+ just give that as a guess
+ If monotonic partitioning function was used
+ guess that 50 % of the inserts goes to the first partition
+ For all other cases, guess on equal distribution between the partitions
+*/
+ha_rows ha_partition::guess_bulk_insert_rows()
+{
+ DBUG_ENTER("guess_bulk_insert_rows");
+
+ if (estimation_rows_to_insert < 10)
+ DBUG_RETURN(estimation_rows_to_insert);
+
+ /* If first insert/partition and monotonic partition function, guess 50%. */
+ if (!m_bulk_inserted_rows &&
+ m_part_func_monotonicity_info != NON_MONOTONIC &&
+ m_tot_parts > 1)
+ DBUG_RETURN(estimation_rows_to_insert / 2);
+
+ /* Else guess on equal distribution (+1 is to avoid returning 0/Unknown) */
+ if (m_bulk_inserted_rows < estimation_rows_to_insert)
+ DBUG_RETURN(((estimation_rows_to_insert - m_bulk_inserted_rows)
+ / m_tot_parts) + 1);
+ /* The estimation was wrong, must say 'Unknown' */
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Finish a large batch of insert rows
+
+ SYNOPSIS
+ end_bulk_insert()
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ Note: end_bulk_insert can be called without start_bulk_insert
+ being called, see bug#44108.
+
+*/
+
+int ha_partition::end_bulk_insert()
+{
+ int error= 0;
+ uint i;
+ DBUG_ENTER("ha_partition::end_bulk_insert");
+
+ if (!bitmap_is_set(&m_bulk_insert_started, m_tot_parts))
+ DBUG_RETURN(error);
+
+ for (i= bitmap_get_first_set(&m_bulk_insert_started);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_bulk_insert_started, i))
+ {
+ int tmp;
+ if ((tmp= m_file[i]->ha_end_bulk_insert()))
+ error= tmp;
+ }
+ bitmap_clear_all(&m_bulk_insert_started);
+ DBUG_RETURN(error);
+}
+
+
+/****************************************************************************
+ MODULE full table scan
+****************************************************************************/
+/*
+ Initialize engine for random reads
+
+ SYNOPSIS
+ ha_partition::rnd_init()
+ scan 0 Initialize for random reads through rnd_pos()
+ 1 Initialize for random scan through rnd_next()
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ rnd_init() is called when the server wants the storage engine to do a
+ table scan or when the server wants to access data through rnd_pos.
+
+ When scan is used we will scan one handler partition at a time.
+ When preparing for rnd_pos we will init all handler partitions.
+ No extra cache handling is needed when scannning is not performed.
+
+ Before initialising we will call rnd_end to ensure that we clean up from
+ any previous incarnation of a table scan.
+ Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc,
+ sql_table.cc, and sql_update.cc.
+*/
+
+int ha_partition::rnd_init(bool scan)
+{
+ int error;
+ uint i= 0;
+ uint32 part_id;
+ DBUG_ENTER("ha_partition::rnd_init");
+
+ /*
+ For operations that may need to change data, we may need to extend
+ read_set.
+ */
+ if (get_lock_type() == F_WRLCK)
+ {
+ /*
+ If write_set contains any of the fields used in partition and
+ subpartition expression, we need to set all bits in read_set because
+ the row may need to be inserted in a different [sub]partition. In
+ other words update_row() can be converted into write_row(), which
+ requires a complete record.
+ */
+ if (bitmap_is_overlapping(&m_part_info->full_part_field_set,
+ table->write_set))
+ bitmap_set_all(table->read_set);
+ else
+ {
+ /*
+ Some handlers only read fields as specified by the bitmap for the
+ read set. For partitioned handlers we always require that the
+ fields of the partition functions are read such that we can
+ calculate the partition id to place updated and deleted records.
+ */
+ bitmap_union(table->read_set, &m_part_info->full_part_field_set);
+ }
+ }
+
+ /* Now we see what the index of our first important partition is */
+ DBUG_PRINT("info", ("m_part_info->read_partitions: 0x%lx",
+ (long) m_part_info->read_partitions.bitmap));
+ part_id= bitmap_get_first_set(&(m_part_info->read_partitions));
+ DBUG_PRINT("info", ("m_part_spec.start_part %d", part_id));
+
+ if (MY_BIT_NONE == part_id)
+ {
+ error= 0;
+ goto err1;
+ }
+
+ /*
+ We have a partition and we are scanning with rnd_next
+ so we bump our cache
+ */
+ DBUG_PRINT("info", ("rnd_init on partition %d", part_id));
+ if (scan)
+ {
+ /*
+ rnd_end() is needed for partitioning to reset internal data if scan
+ is already in use
+ */
+ rnd_end();
+ late_extra_cache(part_id);
+ if ((error= m_file[part_id]->ha_rnd_init(scan)))
+ goto err;
+ }
+ else
+ {
+ for (i= part_id;
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ if ((error= m_file[i]->ha_rnd_init(scan)))
+ goto err;
+ }
+ }
+ m_scan_value= scan;
+ m_part_spec.start_part= part_id;
+ m_part_spec.end_part= m_tot_parts - 1;
+ DBUG_PRINT("info", ("m_scan_value=%d", m_scan_value));
+ DBUG_RETURN(0);
+
+err:
+ /* Call rnd_end for all previously inited partitions. */
+ for (;
+ part_id < i;
+ part_id= bitmap_get_next_set(&m_part_info->read_partitions, part_id))
+ {
+ m_file[part_id]->ha_rnd_end();
+ }
+err1:
+ m_scan_value= 2;
+ m_part_spec.start_part= NO_CURRENT_PART_ID;
+ DBUG_RETURN(error);
+}
+
+
+/*
+ End of a table scan
+
+ SYNOPSIS
+ rnd_end()
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+*/
+
+int ha_partition::rnd_end()
+{
+ DBUG_ENTER("ha_partition::rnd_end");
+ switch (m_scan_value) {
+ case 2: // Error
+ break;
+ case 1:
+ if (NO_CURRENT_PART_ID != m_part_spec.start_part) // Table scan
+ {
+ late_extra_no_cache(m_part_spec.start_part);
+ m_file[m_part_spec.start_part]->ha_rnd_end();
+ }
+ break;
+ case 0:
+ uint i;
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ m_file[i]->ha_rnd_end();
+ }
+ break;
+ }
+ m_scan_value= 2;
+ m_part_spec.start_part= NO_CURRENT_PART_ID;
+ DBUG_RETURN(0);
+}
+
+/*
+ read next row during full table scan (scan in random row order)
+
+ SYNOPSIS
+ rnd_next()
+ buf buffer that should be filled with data
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ This is called for each row of the table scan. When you run out of records
+ you should return HA_ERR_END_OF_FILE.
+ The Field structure for the table is the key to getting data into buf
+ in a manner that will allow the server to understand it.
+
+ Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc,
+ sql_table.cc, and sql_update.cc.
+*/
+
+int ha_partition::rnd_next(uchar *buf)
+{
+ handler *file;
+ int result= HA_ERR_END_OF_FILE;
+ uint part_id= m_part_spec.start_part;
+ DBUG_ENTER("ha_partition::rnd_next");
+
+ /* upper level will increment this once again at end of call */
+ decrement_statistics(&SSV::ha_read_rnd_next_count);
+
+ if (NO_CURRENT_PART_ID == part_id)
+ {
+ /*
+ The original set of partitions to scan was empty and thus we report
+ the result here.
+ */
+ goto end;
+ }
+
+ DBUG_ASSERT(m_scan_value == 1);
+ file= m_file[part_id];
+
+ while (TRUE)
+ {
+ result= file->ha_rnd_next(buf);
+ if (!result)
+ {
+ m_last_part= part_id;
+ m_part_spec.start_part= part_id;
+ table->status= 0;
+ DBUG_RETURN(0);
+ }
+
+ /*
+ if we get here, then the current partition ha_rnd_next returned failure
+ */
+ if (result == HA_ERR_RECORD_DELETED)
+ continue; // Probably MyISAM
+
+ if (result != HA_ERR_END_OF_FILE)
+ goto end_dont_reset_start_part; // Return error
+
+ /* End current partition */
+ late_extra_no_cache(part_id);
+ DBUG_PRINT("info", ("rnd_end on partition %d", part_id));
+ if ((result= file->ha_rnd_end()))
+ break;
+
+ /* Shift to next partition */
+ part_id= bitmap_get_next_set(&m_part_info->read_partitions, part_id);
+ if (part_id >= m_tot_parts)
+ {
+ result= HA_ERR_END_OF_FILE;
+ break;
+ }
+ m_last_part= part_id;
+ m_part_spec.start_part= part_id;
+ file= m_file[part_id];
+ DBUG_PRINT("info", ("rnd_init on partition %d", part_id));
+ if ((result= file->ha_rnd_init(1)))
+ break;
+ late_extra_cache(part_id);
+ }
+
+end:
+ m_part_spec.start_part= NO_CURRENT_PART_ID;
+end_dont_reset_start_part:
+ table->status= STATUS_NOT_FOUND;
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Save position of current row
+
+ SYNOPSIS
+ position()
+ record Current record in MySQL Row Format
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ position() is called after each call to rnd_next() if the data needs
+ to be ordered. You can do something like the following to store
+ the position:
+ ha_store_ptr(ref, ref_length, current_position);
+
+ The server uses ref to store data. ref_length in the above case is
+ the size needed to store current_position. ref is just a byte array
+ that the server will maintain. If you are using offsets to mark rows, then
+ current_position should be the offset. If it is a primary key like in
+ BDB, then it needs to be a primary key.
+
+ Called from filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc.
+*/
+
+void ha_partition::position(const uchar *record)
+{
+ handler *file= m_file[m_last_part];
+ uint pad_length;
+ DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), m_last_part));
+ DBUG_ENTER("ha_partition::position");
+
+ file->position(record);
+ int2store(ref, m_last_part);
+ memcpy((ref + PARTITION_BYTES_IN_POS), file->ref, file->ref_length);
+ pad_length= m_ref_length - PARTITION_BYTES_IN_POS - file->ref_length;
+ if (pad_length)
+ memset((ref + PARTITION_BYTES_IN_POS + file->ref_length), 0, pad_length);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Read row using position
+
+ SYNOPSIS
+ rnd_pos()
+ out:buf Row read in MySQL Row Format
+ position Position of read row
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ This is like rnd_next, but you are given a position to use
+ to determine the row. The position will be of the type that you stored in
+ ref. You can use ha_get_ptr(pos,ref_length) to retrieve whatever key
+ or position you saved when position() was called.
+ Called from filesort.cc records.cc sql_insert.cc sql_select.cc
+ sql_update.cc.
+*/
+
+int ha_partition::rnd_pos(uchar * buf, uchar *pos)
+{
+ uint part_id;
+ handler *file;
+ DBUG_ENTER("ha_partition::rnd_pos");
+ decrement_statistics(&SSV::ha_read_rnd_count);
+
+ part_id= uint2korr((const uchar *) pos);
+ DBUG_ASSERT(part_id < m_tot_parts);
+ file= m_file[part_id];
+ DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), part_id));
+ m_last_part= part_id;
+ DBUG_RETURN(file->ha_rnd_pos(buf, (pos + PARTITION_BYTES_IN_POS)));
+}
+
+
+/*
+ Read row using position using given record to find
+
+ SYNOPSIS
+ rnd_pos_by_record()
+ record Current record in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ this works as position()+rnd_pos() functions, but does some extra work,
+ calculating m_last_part - the partition to where the 'record'
+ should go.
+
+ called from replication (log_event.cc)
+*/
+
+int ha_partition::rnd_pos_by_record(uchar *record)
+{
+ DBUG_ENTER("ha_partition::rnd_pos_by_record");
+
+ if (unlikely(get_part_for_delete(record, m_rec0, m_part_info, &m_last_part)))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(handler::rnd_pos_by_record(record));
+}
+
+
+/****************************************************************************
+ MODULE index scan
+****************************************************************************/
+/*
+ Positions an index cursor to the index specified in the handle. Fetches the
+ row if available. If the key value is null, begin at the first key of the
+ index.
+
+ There are loads of optimisations possible here for the partition handler.
+ The same optimisations can also be checked for full table scan although
+ only through conditions and not from index ranges.
+ Phase one optimisations:
+ Check if the fields of the partition function are bound. If so only use
+ the single partition it becomes bound to.
+ Phase two optimisations:
+ If it can be deducted through range or list partitioning that only a
+ subset of the partitions are used, then only use those partitions.
+*/
+
+
+/**
+ Setup the ordered record buffer and the priority queue.
+*/
+
+bool ha_partition::init_record_priority_queue()
+{
+ DBUG_ENTER("ha_partition::init_record_priority_queue");
+ DBUG_ASSERT(!m_ordered_rec_buffer);
+ /*
+ Initialize the ordered record buffer.
+ */
+ if (!m_ordered_rec_buffer)
+ {
+ uint alloc_len;
+ uint used_parts= bitmap_bits_set(&m_part_info->read_partitions);
+ /* Allocate record buffer for each used partition. */
+ m_priority_queue_rec_len= m_rec_length + PARTITION_BYTES_IN_POS;
+ if (!m_using_extended_keys)
+ m_priority_queue_rec_len += m_file[0]->ref_length;
+ alloc_len= used_parts * m_priority_queue_rec_len;
+ /* Allocate a key for temporary use when setting up the scan. */
+ alloc_len+= table_share->max_key_length;
+
+ if (!(m_ordered_rec_buffer= (uchar*)my_malloc(alloc_len, MYF(MY_WME))))
+ DBUG_RETURN(true);
+
+ /*
+ We set-up one record per partition and each record has 2 bytes in
+ front where the partition id is written. This is used by ordered
+ index_read.
+ We also set-up a reference to the first record for temporary use in
+ setting up the scan.
+ */
+ char *ptr= (char*) m_ordered_rec_buffer;
+ uint i;
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ DBUG_PRINT("info", ("init rec-buf for part %u", i));
+ int2store(ptr, i);
+ ptr+= m_priority_queue_rec_len;
+ }
+ m_start_key.key= (const uchar*)ptr;
+
+ /* Initialize priority queue, initialized to reading forward. */
+ int (*cmp_func)(void *, uchar *, uchar *);
+ void *cmp_arg;
+ if (!m_using_extended_keys)
+ {
+ cmp_func= cmp_key_rowid_part_id;
+ cmp_arg= (void*)this;
+ }
+ else
+ {
+ cmp_func= cmp_key_part_id;
+ cmp_arg= (void*)m_curr_key_info;
+ }
+ if (init_queue(&m_queue, used_parts, 0, 0, cmp_func, cmp_arg, 0, 0))
+ {
+ my_free(m_ordered_rec_buffer);
+ m_ordered_rec_buffer= NULL;
+ DBUG_RETURN(true);
+ }
+ }
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Destroy the ordered record buffer and the priority queue.
+*/
+
+void ha_partition::destroy_record_priority_queue()
+{
+ DBUG_ENTER("ha_partition::destroy_record_priority_queue");
+ if (m_ordered_rec_buffer)
+ {
+ delete_queue(&m_queue);
+ my_free(m_ordered_rec_buffer);
+ m_ordered_rec_buffer= NULL;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Initialize handler before start of index scan
+
+ SYNOPSIS
+ index_init()
+ inx Index number
+ sorted Is rows to be returned in sorted order
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ index_init is always called before starting index scans (except when
+ starting through index_read_idx and using read_range variants).
+*/
+
+int ha_partition::index_init(uint inx, bool sorted)
+{
+ int error= 0;
+ uint i;
+ DBUG_ENTER("ha_partition::index_init");
+
+ DBUG_PRINT("info", ("inx %u sorted %u", inx, sorted));
+ active_index= inx;
+ m_part_spec.start_part= NO_CURRENT_PART_ID;
+ m_start_key.length= 0;
+ m_ordered= sorted;
+ m_ordered_scan_ongoing= FALSE;
+ m_curr_key_info[0]= table->key_info+inx;
+ if (m_pkey_is_clustered && table->s->primary_key != MAX_KEY)
+ {
+ /*
+ if PK is clustered, then the key cmp must use the pk to
+ differentiate between equal key in given index.
+ */
+ DBUG_PRINT("info", ("Clustered pk, using pk as secondary cmp"));
+ m_curr_key_info[1]= table->key_info+table->s->primary_key;
+ m_curr_key_info[2]= NULL;
+ m_using_extended_keys= TRUE;
+ }
+ else
+ {
+ m_curr_key_info[1]= NULL;
+ m_using_extended_keys= FALSE;
+ }
+
+ if (init_record_priority_queue())
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+ /*
+ Some handlers only read fields as specified by the bitmap for the
+ read set. For partitioned handlers we always require that the
+ fields of the partition functions are read such that we can
+ calculate the partition id to place updated and deleted records.
+ But this is required for operations that may need to change data only.
+ */
+ if (get_lock_type() == F_WRLCK)
+ bitmap_union(table->read_set, &m_part_info->full_part_field_set);
+ if (sorted)
+ {
+ /*
+ An ordered scan is requested. We must make sure all fields of the
+ used index are in the read set, as partitioning requires them for
+ sorting (see ha_partition::handle_ordered_index_scan).
+
+ The SQL layer may request an ordered index scan without having index
+ fields in the read set when
+ - it needs to do an ordered scan over an index prefix.
+ - it evaluates ORDER BY with SELECT COUNT(*) FROM t1.
+
+ TODO: handle COUNT(*) queries via unordered scan.
+ */
+ KEY **key_info= m_curr_key_info;
+ do
+ {
+ for (i= 0; i < (*key_info)->user_defined_key_parts; i++)
+ bitmap_set_bit(table->read_set,
+ (*key_info)->key_part[i].field->field_index);
+ } while (*(++key_info));
+ }
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ if ((error= m_file[i]->ha_index_init(inx, sorted)))
+ goto err;
+
+ DBUG_EXECUTE_IF("ha_partition_fail_index_init", {
+ i++;
+ error= HA_ERR_NO_PARTITION_FOUND;
+ goto err;
+ });
+ }
+err:
+ if (error)
+ {
+ /* End the previously initialized indexes. */
+ uint j;
+ for (j= bitmap_get_first_set(&m_part_info->read_partitions);
+ j < i;
+ j= bitmap_get_next_set(&m_part_info->read_partitions, j))
+ {
+ (void) m_file[j]->ha_index_end();
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ End of index scan
+
+ SYNOPSIS
+ index_end()
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ index_end is called at the end of an index scan to clean up any
+ things needed to clean up.
+*/
+
+int ha_partition::index_end()
+{
+ int error= 0;
+ uint i;
+ DBUG_ENTER("ha_partition::index_end");
+
+ active_index= MAX_KEY;
+ m_part_spec.start_part= NO_CURRENT_PART_ID;
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ int tmp;
+ if ((tmp= m_file[i]->ha_index_end()))
+ error= tmp;
+ }
+ destroy_record_priority_queue();
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Read one record in an index scan and start an index scan
+
+ SYNOPSIS
+ index_read_map()
+ buf Read row in MySQL Row Format
+ key Key parts in consecutive order
+ keypart_map Which part of key is used
+ find_flag What type of key condition is used
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ index_read_map starts a new index scan using a start key. The MySQL Server
+ will check the end key on its own. Thus to function properly the
+ partitioned handler need to ensure that it delivers records in the sort
+ order of the MySQL Server.
+ index_read_map can be restarted without calling index_end on the previous
+ index scan and without calling index_init. In this case the index_read_map
+ is on the same index as the previous index_scan. This is particularly
+ used in conjuntion with multi read ranges.
+*/
+
+int ha_partition::index_read_map(uchar *buf, const uchar *key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag)
+{
+ DBUG_ENTER("ha_partition::index_read_map");
+ decrement_statistics(&SSV::ha_read_key_count);
+ end_range= 0;
+ m_index_scan_type= partition_index_read;
+ m_start_key.key= key;
+ m_start_key.keypart_map= keypart_map;
+ m_start_key.flag= find_flag;
+ DBUG_RETURN(common_index_read(buf, TRUE));
+}
+
+
+/* Compare two part_no partition numbers */
+static int cmp_part_ids(uchar *ref1, uchar *ref2)
+{
+ /* The following was taken from ha_partition::cmp_ref */
+ my_ptrdiff_t diff1= ref2[1] - ref1[1];
+ my_ptrdiff_t diff2= ref2[0] - ref1[0];
+ if (!diff1 && !diff2)
+ return 0;
+
+ if (diff1 > 0)
+ return(-1);
+
+ if (diff1 < 0)
+ return(+1);
+
+ if (diff2 > 0)
+ return(-1);
+
+ return(+1);
+}
+
+
+/*
+ @brief
+ Provide ordering by (key_value, part_no).
+*/
+
+extern "C" int cmp_key_part_id(void *key_p, uchar *ref1, uchar *ref2)
+{
+ int res;
+ if ((res= key_rec_cmp(key_p, ref1 + PARTITION_BYTES_IN_POS,
+ ref2 + PARTITION_BYTES_IN_POS)))
+ {
+ return res;
+ }
+ return cmp_part_ids(ref1, ref2);
+}
+
+/*
+ @brief
+ Provide ordering by (key_value, underying_table_rowid, part_no).
+*/
+extern "C" int cmp_key_rowid_part_id(void *ptr, uchar *ref1, uchar *ref2)
+{
+ ha_partition *file= (ha_partition*)ptr;
+ int res;
+
+ if ((res= key_rec_cmp(file->m_curr_key_info, ref1 + PARTITION_BYTES_IN_POS,
+ ref2 + PARTITION_BYTES_IN_POS)))
+ {
+ return res;
+ }
+ if ((res= file->m_file[0]->cmp_ref(ref1 + PARTITION_BYTES_IN_POS + file->m_rec_length,
+ ref2 + PARTITION_BYTES_IN_POS + file->m_rec_length)))
+ {
+ return res;
+ }
+ return cmp_part_ids(ref1, ref2);
+}
+
+
+/**
+ Common routine for a number of index_read variants
+
+ @param buf Buffer where the record should be returned.
+ @param have_start_key TRUE <=> the left endpoint is available, i.e.
+ we're in index_read call or in read_range_first
+ call and the range has left endpoint.
+ FALSE <=> there is no left endpoint (we're in
+ read_range_first() call and the range has no left
+ endpoint).
+
+ @return Operation status
+ @retval 0 OK
+ @retval HA_ERR_END_OF_FILE Whole index scanned, without finding the record.
+ @retval HA_ERR_KEY_NOT_FOUND Record not found, but index cursor positioned.
+ @retval other error code.
+
+ @details
+ Start scanning the range (when invoked from read_range_first()) or doing
+ an index lookup (when invoked from index_read_XXX):
+ - If possible, perform partition selection
+ - Find the set of partitions we're going to use
+ - Depending on whether we need ordering:
+ NO: Get the first record from first used partition (see
+ handle_unordered_scan_next_partition)
+ YES: Fill the priority queue and get the record that is the first in
+ the ordering
+*/
+
+int ha_partition::common_index_read(uchar *buf, bool have_start_key)
+{
+ int error;
+ uint UNINIT_VAR(key_len); /* used if have_start_key==TRUE */
+ bool reverse_order= FALSE;
+ DBUG_ENTER("ha_partition::common_index_read");
+
+ DBUG_PRINT("info", ("m_ordered %u m_ordered_scan_ong %u",
+ m_ordered, m_ordered_scan_ongoing));
+
+ if (have_start_key)
+ {
+ m_start_key.length= key_len= calculate_key_len(table, active_index,
+ m_start_key.key,
+ m_start_key.keypart_map);
+ DBUG_PRINT("info", ("have_start_key map %lu find_flag %u len %u",
+ m_start_key.keypart_map, m_start_key.flag, key_len));
+ DBUG_ASSERT(key_len);
+ }
+ if ((error= partition_scan_set_up(buf, have_start_key)))
+ {
+ DBUG_RETURN(error);
+ }
+
+ if (have_start_key &&
+ (m_start_key.flag == HA_READ_PREFIX_LAST ||
+ m_start_key.flag == HA_READ_PREFIX_LAST_OR_PREV ||
+ m_start_key.flag == HA_READ_BEFORE_KEY))
+ {
+ reverse_order= TRUE;
+ m_ordered_scan_ongoing= TRUE;
+ }
+ DBUG_PRINT("info", ("m_ordered %u m_o_scan_ong %u have_start_key %u",
+ m_ordered, m_ordered_scan_ongoing, have_start_key));
+ if (!m_ordered_scan_ongoing)
+ {
+ /*
+ We use unordered index scan when read_range is used and flag
+ is set to not use ordered.
+ We also use an unordered index scan when the number of partitions to
+ scan is only one.
+ The unordered index scan will use the partition set created.
+ */
+ DBUG_PRINT("info", ("doing unordered scan"));
+ error= handle_unordered_scan_next_partition(buf);
+ }
+ else
+ {
+ /*
+ In all other cases we will use the ordered index scan. This will use
+ the partition set created by the get_partition_set method.
+ */
+ error= handle_ordered_index_scan(buf, reverse_order);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Start an index scan from leftmost record and return first record
+
+ SYNOPSIS
+ index_first()
+ buf Read row in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ index_first() asks for the first key in the index.
+ This is similar to index_read except that there is no start key since
+ the scan starts from the leftmost entry and proceeds forward with
+ index_next.
+
+ Called from opt_range.cc, opt_sum.cc, sql_handler.cc,
+ and sql_select.cc.
+*/
+
+int ha_partition::index_first(uchar * buf)
+{
+ DBUG_ENTER("ha_partition::index_first");
+ decrement_statistics(&SSV::ha_read_first_count);
+
+ end_range= 0;
+ m_index_scan_type= partition_index_first;
+ DBUG_RETURN(common_first_last(buf));
+}
+
+
+/*
+ Start an index scan from rightmost record and return first record
+
+ SYNOPSIS
+ index_last()
+ buf Read row in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ index_last() asks for the last key in the index.
+ This is similar to index_read except that there is no start key since
+ the scan starts from the rightmost entry and proceeds forward with
+ index_prev.
+
+ Called from opt_range.cc, opt_sum.cc, sql_handler.cc,
+ and sql_select.cc.
+*/
+
+int ha_partition::index_last(uchar * buf)
+{
+ DBUG_ENTER("ha_partition::index_last");
+ decrement_statistics(&SSV::ha_read_last_count);
+
+ m_index_scan_type= partition_index_last;
+ DBUG_RETURN(common_first_last(buf));
+}
+
+/*
+ Common routine for index_first/index_last
+
+ SYNOPSIS
+ ha_partition::common_first_last()
+
+ see index_first for rest
+*/
+
+int ha_partition::common_first_last(uchar *buf)
+{
+ int error;
+
+ if ((error= partition_scan_set_up(buf, FALSE)))
+ return error;
+ if (!m_ordered_scan_ongoing &&
+ m_index_scan_type != partition_index_last)
+ return handle_unordered_scan_next_partition(buf);
+ return handle_ordered_index_scan(buf, FALSE);
+}
+
+
+/*
+ Optimization of the default implementation to take advantage of dynamic
+ partition pruning.
+*/
+int ha_partition::index_read_idx_map(uchar *buf, uint index,
+ const uchar *key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag)
+{
+ int error= HA_ERR_KEY_NOT_FOUND;
+ DBUG_ENTER("ha_partition::index_read_idx_map");
+
+ if (find_flag == HA_READ_KEY_EXACT)
+ {
+ uint part;
+ m_start_key.key= key;
+ m_start_key.keypart_map= keypart_map;
+ m_start_key.flag= find_flag;
+ m_start_key.length= calculate_key_len(table, index, m_start_key.key,
+ m_start_key.keypart_map);
+
+ get_partition_set(table, buf, index, &m_start_key, &m_part_spec);
+
+ /*
+ We have either found exactly 1 partition
+ (in which case start_part == end_part)
+ or no matching partitions (start_part > end_part)
+ */
+ DBUG_ASSERT(m_part_spec.start_part >= m_part_spec.end_part);
+ /* The start part is must be marked as used. */
+ DBUG_ASSERT(m_part_spec.start_part > m_part_spec.end_part ||
+ bitmap_is_set(&(m_part_info->read_partitions),
+ m_part_spec.start_part));
+
+ for (part= m_part_spec.start_part;
+ part <= m_part_spec.end_part;
+ part= bitmap_get_next_set(&m_part_info->read_partitions, part))
+ {
+ error= m_file[part]->ha_index_read_idx_map(buf, index, key,
+ keypart_map, find_flag);
+ if (error != HA_ERR_KEY_NOT_FOUND &&
+ error != HA_ERR_END_OF_FILE)
+ break;
+ }
+ if (part <= m_part_spec.end_part)
+ m_last_part= part;
+ }
+ else
+ {
+ /*
+ If not only used with READ_EXACT, we should investigate if possible
+ to optimize for other find_flag's as well.
+ */
+ DBUG_ASSERT(0);
+ /* fall back on the default implementation */
+ error= handler::index_read_idx_map(buf, index, key, keypart_map, find_flag);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Read next record in a forward index scan
+
+ SYNOPSIS
+ index_next()
+ buf Read row in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ Used to read forward through the index.
+*/
+
+int ha_partition::index_next(uchar * buf)
+{
+ DBUG_ENTER("ha_partition::index_next");
+ decrement_statistics(&SSV::ha_read_next_count);
+
+ /*
+ TODO(low priority):
+ If we want partition to work with the HANDLER commands, we
+ must be able to do index_last() -> index_prev() -> index_next()
+ and if direction changes, we must step back those partitions in
+ the record queue so we don't return a value from the wrong direction.
+ */
+ DBUG_ASSERT(m_index_scan_type != partition_index_last);
+ if (!m_ordered_scan_ongoing)
+ {
+ DBUG_RETURN(handle_unordered_next(buf, FALSE));
+ }
+ DBUG_RETURN(handle_ordered_next(buf, FALSE));
+}
+
+
+/*
+ Read next record special
+
+ SYNOPSIS
+ index_next_same()
+ buf Read row in MySQL Row Format
+ key Key
+ keylen Length of key
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ This routine is used to read the next but only if the key is the same
+ as supplied in the call.
+*/
+
+int ha_partition::index_next_same(uchar *buf, const uchar *key, uint keylen)
+{
+ DBUG_ENTER("ha_partition::index_next_same");
+ decrement_statistics(&SSV::ha_read_next_count);
+
+ DBUG_ASSERT(keylen == m_start_key.length);
+ DBUG_ASSERT(m_index_scan_type != partition_index_last);
+ if (!m_ordered_scan_ongoing)
+ DBUG_RETURN(handle_unordered_next(buf, TRUE));
+ DBUG_RETURN(handle_ordered_next(buf, TRUE));
+}
+
+
+/*
+ Read next record when performing index scan backwards
+
+ SYNOPSIS
+ index_prev()
+ buf Read row in MySQL Row Format
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ Used to read backwards through the index.
+*/
+
+int ha_partition::index_prev(uchar * buf)
+{
+ DBUG_ENTER("ha_partition::index_prev");
+ decrement_statistics(&SSV::ha_read_prev_count);
+
+ /* TODO: read comment in index_next */
+ DBUG_ASSERT(m_index_scan_type != partition_index_first);
+ DBUG_RETURN(handle_ordered_prev(buf));
+}
+
+
+/*
+ Start a read of one range with start and end key
+
+ SYNOPSIS
+ read_range_first()
+ start_key Specification of start key
+ end_key Specification of end key
+ eq_range_arg Is it equal range
+ sorted Should records be returned in sorted order
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+
+ DESCRIPTION
+ We reimplement read_range_first since we don't want the compare_key
+ check at the end. This is already performed in the partition handler.
+ read_range_next is very much different due to that we need to scan
+ all underlying handlers.
+*/
+
+int ha_partition::read_range_first(const key_range *start_key,
+ const key_range *end_key,
+ bool eq_range_arg, bool sorted)
+{
+ int error;
+ DBUG_ENTER("ha_partition::read_range_first");
+
+ m_ordered= sorted;
+ eq_range= eq_range_arg;
+ set_end_range(end_key);
+
+ range_key_part= m_curr_key_info[0]->key_part;
+ if (start_key)
+ m_start_key= *start_key;
+ else
+ m_start_key.key= NULL;
+
+ m_index_scan_type= partition_read_range;
+ error= common_index_read(m_rec0, MY_TEST(start_key));
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Read next record in read of a range with start and end key
+
+ SYNOPSIS
+ read_range_next()
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+*/
+
+int ha_partition::read_range_next()
+{
+ DBUG_ENTER("ha_partition::read_range_next");
+
+ if (m_ordered_scan_ongoing)
+ {
+ DBUG_RETURN(handle_ordered_next(table->record[0], eq_range));
+ }
+ DBUG_RETURN(handle_unordered_next(table->record[0], eq_range));
+}
+
+
+/*
+ Common routine to set up index scans
+
+ SYNOPSIS
+ ha_partition::partition_scan_set_up()
+ buf Buffer to later return record in (this function
+ needs it to calculcate partitioning function
+ values)
+
+ idx_read_flag TRUE <=> m_start_key has range start endpoint which
+ probably can be used to determine the set of partitions
+ to scan.
+ FALSE <=> there is no start endpoint.
+
+ DESCRIPTION
+ Find out which partitions we'll need to read when scanning the specified
+ range.
+
+ If we need to scan only one partition, set m_ordered_scan_ongoing=FALSE
+ as we will not need to do merge ordering.
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+*/
+
+int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag)
+{
+ DBUG_ENTER("ha_partition::partition_scan_set_up");
+
+ if (idx_read_flag)
+ get_partition_set(table,buf,active_index,&m_start_key,&m_part_spec);
+ else
+ {
+ m_part_spec.start_part= 0;
+ m_part_spec.end_part= m_tot_parts - 1;
+ }
+ if (m_part_spec.start_part > m_part_spec.end_part)
+ {
+ /*
+ We discovered a partition set but the set was empty so we report
+ key not found.
+ */
+ DBUG_PRINT("info", ("scan with no partition to scan"));
+ table->status= STATUS_NOT_FOUND;
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ if (m_part_spec.start_part == m_part_spec.end_part)
+ {
+ /*
+ We discovered a single partition to scan, this never needs to be
+ performed using the ordered index scan.
+ */
+ DBUG_PRINT("info", ("index scan using the single partition %d",
+ m_part_spec.start_part));
+ m_ordered_scan_ongoing= FALSE;
+ }
+ else
+ {
+ /*
+ Set m_ordered_scan_ongoing according how the scan should be done
+ Only exact partitions are discovered atm by get_partition_set.
+ Verify this, also bitmap must have at least one bit set otherwise
+ the result from this table is the empty set.
+ */
+ uint start_part= bitmap_get_first_set(&(m_part_info->read_partitions));
+ if (start_part == MY_BIT_NONE)
+ {
+ DBUG_PRINT("info", ("scan with no partition to scan"));
+ table->status= STATUS_NOT_FOUND;
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ if (start_part > m_part_spec.start_part)
+ m_part_spec.start_part= start_part;
+ DBUG_ASSERT(m_part_spec.start_part < m_tot_parts);
+ m_ordered_scan_ongoing= m_ordered;
+ }
+ DBUG_ASSERT(m_part_spec.start_part < m_tot_parts &&
+ m_part_spec.end_part < m_tot_parts);
+ DBUG_RETURN(0);
+}
+
+
+/****************************************************************************
+ Unordered Index Scan Routines
+****************************************************************************/
+/*
+ Common routine to handle index_next with unordered results
+
+ SYNOPSIS
+ handle_unordered_next()
+ out:buf Read row in MySQL Row Format
+ next_same Called from index_next_same
+
+ RETURN VALUE
+ HA_ERR_END_OF_FILE End of scan
+ 0 Success
+ other Error code
+
+ DESCRIPTION
+ These routines are used to scan partitions without considering order.
+ This is performed in two situations.
+ 1) In read_multi_range this is the normal case
+ 2) When performing any type of index_read, index_first, index_last where
+ all fields in the partition function is bound. In this case the index
+ scan is performed on only one partition and thus it isn't necessary to
+ perform any sort.
+*/
+
+int ha_partition::handle_unordered_next(uchar *buf, bool is_next_same)
+{
+ handler *file;
+ int error;
+ DBUG_ENTER("ha_partition::handle_unordered_next");
+
+ if (m_part_spec.start_part >= m_tot_parts)
+ {
+ /* Should never happen! */
+ DBUG_ASSERT(0);
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ file= m_file[m_part_spec.start_part];
+
+ /*
+ We should consider if this should be split into three functions as
+ partition_read_range is_next_same are always local constants
+ */
+
+ if (m_index_scan_type == partition_read_range)
+ {
+ if (!(error= file->read_range_next()))
+ {
+ m_last_part= m_part_spec.start_part;
+ DBUG_RETURN(0);
+ }
+ }
+ else if (is_next_same)
+ {
+ if (!(error= file->ha_index_next_same(buf, m_start_key.key,
+ m_start_key.length)))
+ {
+ m_last_part= m_part_spec.start_part;
+ DBUG_RETURN(0);
+ }
+ }
+ else
+ {
+ if (!(error= file->ha_index_next(buf)))
+ {
+ m_last_part= m_part_spec.start_part;
+ DBUG_RETURN(0); // Row was in range
+ }
+ }
+
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ m_part_spec.start_part++; // Start using next part
+ error= handle_unordered_scan_next_partition(buf);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Handle index_next when changing to new partition
+
+ SYNOPSIS
+ handle_unordered_scan_next_partition()
+ buf Read row in MySQL Row Format
+
+ RETURN VALUE
+ HA_ERR_END_OF_FILE End of scan
+ 0 Success
+ other Error code
+
+ DESCRIPTION
+ This routine is used to start the index scan on the next partition.
+ Both initial start and after completing scan on one partition.
+*/
+
+int ha_partition::handle_unordered_scan_next_partition(uchar * buf)
+{
+ uint i= m_part_spec.start_part;
+ int saved_error= HA_ERR_END_OF_FILE;
+ DBUG_ENTER("ha_partition::handle_unordered_scan_next_partition");
+
+ if (i)
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i - 1);
+ else
+ i= bitmap_get_first_set(&m_part_info->read_partitions);
+
+ for (;
+ i <= m_part_spec.end_part;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ int error;
+ handler *file= m_file[i];
+ m_part_spec.start_part= i;
+ switch (m_index_scan_type) {
+ case partition_read_range:
+ DBUG_PRINT("info", ("read_range_first on partition %d", i));
+ error= file->read_range_first(m_start_key.key? &m_start_key: NULL,
+ end_range, eq_range, FALSE);
+ break;
+ case partition_index_read:
+ DBUG_PRINT("info", ("index_read on partition %d", i));
+ error= file->ha_index_read_map(buf, m_start_key.key,
+ m_start_key.keypart_map,
+ m_start_key.flag);
+ break;
+ case partition_index_first:
+ DBUG_PRINT("info", ("index_first on partition %d", i));
+ error= file->ha_index_first(buf);
+ break;
+ case partition_index_first_unordered:
+ /*
+ We perform a scan without sorting and this means that we
+ should not use the index_first since not all handlers
+ support it and it is also unnecessary to restrict sort
+ order.
+ */
+ DBUG_PRINT("info", ("read_range_first on partition %d", i));
+ table->record[0]= buf;
+ error= file->read_range_first(0, end_range, eq_range, 0);
+ table->record[0]= m_rec0;
+ break;
+ default:
+ DBUG_ASSERT(FALSE);
+ DBUG_RETURN(1);
+ }
+ if (!error)
+ {
+ m_last_part= i;
+ DBUG_RETURN(0);
+ }
+ if ((error != HA_ERR_END_OF_FILE) && (error != HA_ERR_KEY_NOT_FOUND))
+ DBUG_RETURN(error);
+
+ /*
+ If HA_ERR_KEY_NOT_FOUND, we must return that error instead of
+ HA_ERR_END_OF_FILE, to be able to continue search.
+ */
+ if (saved_error != HA_ERR_KEY_NOT_FOUND)
+ saved_error= error;
+ DBUG_PRINT("info", ("END_OF_FILE/KEY_NOT_FOUND on partition %d", i));
+ }
+ if (saved_error == HA_ERR_END_OF_FILE)
+ m_part_spec.start_part= NO_CURRENT_PART_ID;
+ DBUG_RETURN(saved_error);
+}
+
+
+/**
+ Common routine to start index scan with ordered results.
+
+ @param[out] buf Read row in MySQL Row Format
+
+ @return Operation status
+ @retval HA_ERR_END_OF_FILE End of scan
+ @retval HA_ERR_KEY_NOT_FOUNE End of scan
+ @retval 0 Success
+ @retval other Error code
+
+ @details
+ This part contains the logic to handle index scans that require ordered
+ output. This includes all except those started by read_range_first with
+ the flag ordered set to FALSE. Thus most direct index_read and all
+ index_first and index_last.
+
+ We implement ordering by keeping one record plus a key buffer for each
+ partition. Every time a new entry is requested we will fetch a new
+ entry from the partition that is currently not filled with an entry.
+ Then the entry is put into its proper sort position.
+
+ Returning a record is done by getting the top record, copying the
+ record to the request buffer and setting the partition as empty on
+ entries.
+*/
+
+int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order)
+{
+ uint i;
+ uint j= queue_first_element(&m_queue);
+ bool found= FALSE;
+ uchar *part_rec_buf_ptr= m_ordered_rec_buffer;
+ int saved_error= HA_ERR_END_OF_FILE;
+ DBUG_ENTER("ha_partition::handle_ordered_index_scan");
+
+ if (m_key_not_found)
+ {
+ m_key_not_found= false;
+ bitmap_clear_all(&m_key_not_found_partitions);
+ }
+ m_top_entry= NO_CURRENT_PART_ID;
+ queue_remove_all(&m_queue);
+ DBUG_ASSERT(bitmap_is_set(&m_part_info->read_partitions,
+ m_part_spec.start_part));
+
+ /*
+ Position part_rec_buf_ptr to point to the first used partition >=
+ start_part. There may be partitions marked by used_partitions,
+ but is before start_part. These partitions has allocated record buffers
+ but is dynamically pruned, so those buffers must be skipped.
+ */
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_part_spec.start_part;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ part_rec_buf_ptr+= m_priority_queue_rec_len;
+ }
+ DBUG_PRINT("info", ("m_part_spec.start_part %u first_used_part %u",
+ m_part_spec.start_part, i));
+ for (/* continue from above */ ;
+ i <= m_part_spec.end_part;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ DBUG_PRINT("info", ("reading from part %u (scan_type: %u)",
+ i, m_index_scan_type));
+ DBUG_ASSERT(i == uint2korr(part_rec_buf_ptr));
+ uchar *rec_buf_ptr= part_rec_buf_ptr + PARTITION_BYTES_IN_POS;
+ int error;
+ handler *file= m_file[i];
+
+ switch (m_index_scan_type) {
+ case partition_index_read:
+ error= file->ha_index_read_map(rec_buf_ptr,
+ m_start_key.key,
+ m_start_key.keypart_map,
+ m_start_key.flag);
+ break;
+ case partition_index_first:
+ error= file->ha_index_first(rec_buf_ptr);
+ reverse_order= FALSE;
+ break;
+ case partition_index_last:
+ error= file->ha_index_last(rec_buf_ptr);
+ reverse_order= TRUE;
+ break;
+ case partition_read_range:
+ {
+ /*
+ This can only read record to table->record[0], as it was set when
+ the table was being opened. We have to memcpy data ourselves.
+ */
+ error= file->read_range_first(m_start_key.key? &m_start_key: NULL,
+ end_range, eq_range, TRUE);
+ memcpy(rec_buf_ptr, table->record[0], m_rec_length);
+ reverse_order= FALSE;
+ break;
+ }
+ default:
+ DBUG_ASSERT(FALSE);
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ if (!error)
+ {
+ found= TRUE;
+ if (!m_using_extended_keys)
+ {
+ file->position(rec_buf_ptr);
+ memcpy(rec_buf_ptr + m_rec_length, file->ref, file->ref_length);
+ }
+ /*
+ Initialize queue without order first, simply insert
+ */
+ queue_element(&m_queue, j++)= part_rec_buf_ptr;
+ }
+ else if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ {
+ DBUG_RETURN(error);
+ }
+ else if (error == HA_ERR_KEY_NOT_FOUND)
+ {
+ DBUG_PRINT("info", ("HA_ERR_KEY_NOT_FOUND from partition %u", i));
+ bitmap_set_bit(&m_key_not_found_partitions, i);
+ m_key_not_found= true;
+ saved_error= error;
+ }
+ part_rec_buf_ptr+= m_priority_queue_rec_len;
+ }
+ if (found)
+ {
+ /*
+ We found at least one partition with data, now sort all entries and
+ after that read the first entry and copy it to the buffer to return in.
+ */
+ queue_set_max_at_top(&m_queue, reverse_order);
+ queue_set_cmp_arg(&m_queue, m_using_extended_keys? m_curr_key_info : (void*)this);
+ m_queue.elements= j - queue_first_element(&m_queue);
+ queue_fix(&m_queue);
+ return_top_record(buf);
+ table->status= 0;
+ DBUG_PRINT("info", ("Record returned from partition %d", m_top_entry));
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(saved_error);
+}
+
+
+/*
+ Return the top record in sort order
+
+ SYNOPSIS
+ return_top_record()
+ out:buf Row returned in MySQL Row Format
+
+ RETURN VALUE
+ NONE
+*/
+
+void ha_partition::return_top_record(uchar *buf)
+{
+ uint part_id;
+ uchar *key_buffer= queue_top(&m_queue);
+ uchar *rec_buffer= key_buffer + PARTITION_BYTES_IN_POS;
+
+ part_id= uint2korr(key_buffer);
+ memcpy(buf, rec_buffer, m_rec_length);
+ m_last_part= part_id;
+ m_top_entry= part_id;
+}
+
+
+/**
+ Add index_next/prev from partitions without exact match.
+
+ If there where any partitions that returned HA_ERR_KEY_NOT_FOUND when
+ ha_index_read_map was done, those partitions must be included in the
+ following index_next/prev call.
+*/
+
+int ha_partition::handle_ordered_index_scan_key_not_found()
+{
+ int error;
+ uint i, old_elements= m_queue.elements;
+ uchar *part_buf= m_ordered_rec_buffer;
+ uchar *curr_rec_buf= NULL;
+ DBUG_ENTER("ha_partition::handle_ordered_index_scan_key_not_found");
+ DBUG_ASSERT(m_key_not_found);
+ /*
+ Loop over all used partitions to get the correct offset
+ into m_ordered_rec_buffer.
+ */
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ if (bitmap_is_set(&m_key_not_found_partitions, i))
+ {
+ /*
+ This partition is used and did return HA_ERR_KEY_NOT_FOUND
+ in index_read_map.
+ */
+ curr_rec_buf= part_buf + PARTITION_BYTES_IN_POS;
+ error= m_file[i]->ha_index_next(curr_rec_buf);
+ /* HA_ERR_KEY_NOT_FOUND is not allowed from index_next! */
+ DBUG_ASSERT(error != HA_ERR_KEY_NOT_FOUND);
+ if (!error)
+ queue_insert(&m_queue, part_buf);
+ else if (error != HA_ERR_END_OF_FILE && error != HA_ERR_KEY_NOT_FOUND)
+ DBUG_RETURN(error);
+ }
+ part_buf += m_priority_queue_rec_len;
+ }
+ DBUG_ASSERT(curr_rec_buf);
+ bitmap_clear_all(&m_key_not_found_partitions);
+ m_key_not_found= false;
+
+ if (m_queue.elements > old_elements)
+ {
+ /* Update m_top_entry, which may have changed. */
+ uchar *key_buffer= queue_top(&m_queue);
+ m_top_entry= uint2korr(key_buffer);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Common routine to handle index_next with ordered results
+
+ SYNOPSIS
+ handle_ordered_next()
+ out:buf Read row in MySQL Row Format
+ next_same Called from index_next_same
+
+ RETURN VALUE
+ HA_ERR_END_OF_FILE End of scan
+ 0 Success
+ other Error code
+*/
+
+int ha_partition::handle_ordered_next(uchar *buf, bool is_next_same)
+{
+ int error;
+ uint part_id= m_top_entry;
+ uchar *rec_buf= queue_top(&m_queue) + PARTITION_BYTES_IN_POS;
+ handler *file;
+ DBUG_ENTER("ha_partition::handle_ordered_next");
+
+ if (m_key_not_found)
+ {
+ if (is_next_same)
+ {
+ /* Only rows which match the key. */
+ m_key_not_found= false;
+ bitmap_clear_all(&m_key_not_found_partitions);
+ }
+ else
+ {
+ /* There are partitions not included in the index record queue. */
+ uint old_elements= m_queue.elements;
+ if ((error= handle_ordered_index_scan_key_not_found()))
+ DBUG_RETURN(error);
+ /*
+ If the queue top changed, i.e. one of the partitions that gave
+ HA_ERR_KEY_NOT_FOUND in index_read_map found the next record,
+ return it.
+ Otherwise replace the old with a call to index_next (fall through).
+ */
+ if (old_elements != m_queue.elements && part_id != m_top_entry)
+ {
+ return_top_record(buf);
+ DBUG_RETURN(0);
+ }
+ }
+ }
+ if (part_id >= m_tot_parts)
+ {
+ /* This should never happen! */
+ DBUG_ASSERT(0);
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+
+ file= m_file[part_id];
+
+ if (m_index_scan_type == partition_read_range)
+ {
+ error= file->read_range_next();
+ memcpy(rec_buf, table->record[0], m_rec_length);
+ }
+ else if (!is_next_same)
+ error= file->ha_index_next(rec_buf);
+ else
+ error= file->ha_index_next_same(rec_buf, m_start_key.key,
+ m_start_key.length);
+
+ if (error)
+ {
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ /* Return next buffered row */
+ queue_remove_top(&m_queue);
+ if (m_queue.elements)
+ {
+ DBUG_PRINT("info", ("Record returned from partition %u (2)",
+ m_top_entry));
+ return_top_record(buf);
+ table->status= 0;
+ error= 0;
+ }
+ }
+ DBUG_RETURN(error);
+ }
+
+ if (!m_using_extended_keys)
+ {
+ file->position(rec_buf);
+ memcpy(rec_buf + m_rec_length, file->ref, file->ref_length);
+ }
+
+ queue_replace_top(&m_queue);
+ return_top_record(buf);
+ DBUG_PRINT("info", ("Record returned from partition %u", m_top_entry));
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Common routine to handle index_prev with ordered results
+
+ SYNOPSIS
+ handle_ordered_prev()
+ out:buf Read row in MySQL Row Format
+
+ RETURN VALUE
+ HA_ERR_END_OF_FILE End of scan
+ 0 Success
+ other Error code
+*/
+
+int ha_partition::handle_ordered_prev(uchar *buf)
+{
+ int error;
+ uint part_id= m_top_entry;
+ uchar *rec_buf= queue_top(&m_queue) + PARTITION_BYTES_IN_POS;
+ handler *file= m_file[part_id];
+ DBUG_ENTER("ha_partition::handle_ordered_prev");
+
+ if ((error= file->ha_index_prev(rec_buf)))
+ {
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ queue_remove_top(&m_queue);
+ if (m_queue.elements)
+ {
+ return_top_record(buf);
+ DBUG_PRINT("info", ("Record returned from partition %d (2)",
+ m_top_entry));
+ error= 0;
+ table->status= 0;
+ }
+ }
+ DBUG_RETURN(error);
+ }
+ queue_replace_top(&m_queue);
+ return_top_record(buf);
+ DBUG_PRINT("info", ("Record returned from partition %d", m_top_entry));
+ DBUG_RETURN(0);
+}
+
+
+/****************************************************************************
+ MODULE information calls
+****************************************************************************/
+
+/*
+ These are all first approximations of the extra, info, scan_time
+ and read_time calls
+*/
+
+/**
+ Helper function for sorting according to number of rows in descending order.
+*/
+
+int ha_partition::compare_number_of_records(ha_partition *me,
+ const uint32 *a,
+ const uint32 *b)
+{
+ handler **file= me->m_file;
+ /* Note: sorting in descending order! */
+ if (file[*a]->stats.records > file[*b]->stats.records)
+ return -1;
+ if (file[*a]->stats.records < file[*b]->stats.records)
+ return 1;
+ return 0;
+}
+
+
+/*
+ General method to gather info from handler
+
+ SYNOPSIS
+ info()
+ flag Specifies what info is requested
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ ::info() is used to return information to the optimizer.
+ Currently this table handler doesn't implement most of the fields
+ really needed. SHOW also makes use of this data
+ Another note, if your handler doesn't proved exact record count,
+ you will probably want to have the following in your code:
+ if (records < 2)
+ records = 2;
+ The reason is that the server will optimize for cases of only a single
+ record. If in a table scan you don't know the number of records
+ it will probably be better to set records to two so you can return
+ as many records as you need.
+
+ Along with records a few more variables you may wish to set are:
+ records
+ deleted
+ data_file_length
+ index_file_length
+ delete_length
+ check_time
+ Take a look at the public variables in handler.h for more information.
+
+ Called in:
+ filesort.cc
+ ha_heap.cc
+ item_sum.cc
+ opt_sum.cc
+ sql_delete.cc
+ sql_delete.cc
+ sql_derived.cc
+ sql_select.cc
+ sql_select.cc
+ sql_select.cc
+ sql_select.cc
+ sql_select.cc
+ sql_show.cc
+ sql_show.cc
+ sql_show.cc
+ sql_show.cc
+ sql_table.cc
+ sql_union.cc
+ sql_update.cc
+
+ Some flags that are not implemented
+ HA_STATUS_POS:
+ This parameter is never used from the MySQL Server. It is checked in a
+ place in MyISAM so could potentially be used by MyISAM specific
+ programs.
+ HA_STATUS_NO_LOCK:
+ This is declared and often used. It's only used by MyISAM.
+ It means that MySQL doesn't need the absolute latest statistics
+ information. This may save the handler from doing internal locks while
+ retrieving statistics data.
+*/
+
+int ha_partition::info(uint flag)
+{
+ uint no_lock_flag= flag & HA_STATUS_NO_LOCK;
+ uint extra_var_flag= flag & HA_STATUS_VARIABLE_EXTRA;
+ DBUG_ENTER("ha_partition::info");
+
+#ifndef DBUG_OFF
+ if (bitmap_is_set_all(&(m_part_info->read_partitions)))
+ DBUG_PRINT("info", ("All partitions are used"));
+#endif /* DBUG_OFF */
+ if (flag & HA_STATUS_AUTO)
+ {
+ bool auto_inc_is_first_in_idx= (table_share->next_number_keypart == 0);
+ DBUG_PRINT("info", ("HA_STATUS_AUTO"));
+ if (!table->found_next_number_field)
+ stats.auto_increment_value= 0;
+ else if (part_share->auto_inc_initialized)
+ {
+ lock_auto_increment();
+ stats.auto_increment_value= part_share->next_auto_inc_val;
+ unlock_auto_increment();
+ }
+ else
+ {
+ lock_auto_increment();
+ /* to avoid two concurrent initializations, check again when locked */
+ if (part_share->auto_inc_initialized)
+ stats.auto_increment_value= part_share->next_auto_inc_val;
+ else
+ {
+ /*
+ The auto-inc mutex in the table_share is locked, so we do not need
+ to have the handlers locked.
+ HA_STATUS_NO_LOCK is not checked, since we cannot skip locking
+ the mutex, because it is initialized.
+ */
+ handler *file, **file_array;
+ ulonglong auto_increment_value= 0;
+ file_array= m_file;
+ DBUG_PRINT("info",
+ ("checking all partitions for auto_increment_value"));
+ do
+ {
+ file= *file_array;
+ file->info(HA_STATUS_AUTO | no_lock_flag);
+ set_if_bigger(auto_increment_value,
+ file->stats.auto_increment_value);
+ } while (*(++file_array));
+
+ DBUG_ASSERT(auto_increment_value);
+ stats.auto_increment_value= auto_increment_value;
+ if (auto_inc_is_first_in_idx)
+ {
+ set_if_bigger(part_share->next_auto_inc_val,
+ auto_increment_value);
+ part_share->auto_inc_initialized= true;
+ DBUG_PRINT("info", ("initializing next_auto_inc_val to %lu",
+ (ulong) part_share->next_auto_inc_val));
+ }
+ }
+ unlock_auto_increment();
+ }
+ }
+ if (flag & HA_STATUS_VARIABLE)
+ {
+ uint i;
+ DBUG_PRINT("info", ("HA_STATUS_VARIABLE"));
+ /*
+ Calculates statistical variables
+ records: Estimate of number records in table
+ We report sum (always at least 2 if not empty)
+ deleted: Estimate of number holes in the table due to
+ deletes
+ We report sum
+ data_file_length: Length of data file, in principle bytes in table
+ We report sum
+ index_file_length: Length of index file, in principle bytes in
+ indexes in the table
+ We report sum
+ delete_length: Length of free space easily used by new records in table
+ We report sum
+ mean_record_length:Mean record length in the table
+ We calculate this
+ check_time: Time of last check (only applicable to MyISAM)
+ We report last time of all underlying handlers
+ */
+ handler *file;
+ stats.records= 0;
+ stats.deleted= 0;
+ stats.data_file_length= 0;
+ stats.index_file_length= 0;
+ stats.check_time= 0;
+ stats.delete_length= 0;
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ file= m_file[i];
+ file->info(HA_STATUS_VARIABLE | no_lock_flag | extra_var_flag);
+ stats.records+= file->stats.records;
+ stats.deleted+= file->stats.deleted;
+ stats.data_file_length+= file->stats.data_file_length;
+ stats.index_file_length+= file->stats.index_file_length;
+ stats.delete_length+= file->stats.delete_length;
+ if (file->stats.check_time > stats.check_time)
+ stats.check_time= file->stats.check_time;
+ }
+ if (stats.records && stats.records < 2 &&
+ !(m_file[0]->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT))
+ stats.records= 2;
+ if (stats.records > 0)
+ stats.mean_rec_length= (ulong) (stats.data_file_length / stats.records);
+ else
+ stats.mean_rec_length= 0;
+ }
+ if (flag & HA_STATUS_CONST)
+ {
+ DBUG_PRINT("info", ("HA_STATUS_CONST"));
+ /*
+ Recalculate loads of constant variables. MyISAM also sets things
+ directly on the table share object.
+
+ Check whether this should be fixed since handlers should not
+ change things directly on the table object.
+
+ Monty comment: This should NOT be changed! It's the handlers
+ responsibility to correct table->s->keys_xxxx information if keys
+ have been disabled.
+
+ The most important parameters set here is records per key on
+ all indexes. block_size and primar key ref_length.
+
+ For each index there is an array of rec_per_key.
+ As an example if we have an index with three attributes a,b and c
+ we will have an array of 3 rec_per_key.
+ rec_per_key[0] is an estimate of number of records divided by
+ number of unique values of the field a.
+ rec_per_key[1] is an estimate of the number of records divided
+ by the number of unique combinations of the fields a and b.
+ rec_per_key[2] is an estimate of the number of records divided
+ by the number of unique combinations of the fields a,b and c.
+
+ Many handlers only set the value of rec_per_key when all fields
+ are bound (rec_per_key[2] in the example above).
+
+ If the handler doesn't support statistics, it should set all of the
+ above to 0.
+
+ We first scans through all partitions to get the one holding most rows.
+ We will then allow the handler with the most rows to set
+ the rec_per_key and use this as an estimate on the total table.
+
+ max_data_file_length: Maximum data file length
+ We ignore it, is only used in
+ SHOW TABLE STATUS
+ max_index_file_length: Maximum index file length
+ We ignore it since it is never used
+ block_size: Block size used
+ We set it to the value of the first handler
+ ref_length: We set this to the value calculated
+ and stored in local object
+ create_time: Creation time of table
+
+ So we calculate these constants by using the variables from the
+ handler with most rows.
+ */
+ handler *file, **file_array;
+ ulonglong max_records= 0;
+ uint32 i= 0;
+ uint32 handler_instance= 0;
+
+ file_array= m_file;
+ do
+ {
+ file= *file_array;
+ /* Get variables if not already done */
+ if (!(flag & HA_STATUS_VARIABLE) ||
+ !bitmap_is_set(&(m_part_info->read_partitions),
+ (file_array - m_file)))
+ file->info(HA_STATUS_VARIABLE | no_lock_flag | extra_var_flag);
+ if (file->stats.records > max_records)
+ {
+ max_records= file->stats.records;
+ handler_instance= i;
+ }
+ i++;
+ } while (*(++file_array));
+ /*
+ Sort the array of part_ids by number of records in
+ in descending order.
+ */
+ my_qsort2((void*) m_part_ids_sorted_by_num_of_records,
+ m_tot_parts,
+ sizeof(uint32),
+ (qsort2_cmp) compare_number_of_records,
+ this);
+
+ file= m_file[handler_instance];
+ file->info(HA_STATUS_CONST | no_lock_flag);
+ stats.block_size= file->stats.block_size;
+ stats.create_time= file->stats.create_time;
+ ref_length= m_ref_length;
+ }
+ if (flag & HA_STATUS_ERRKEY)
+ {
+ handler *file= m_file[m_last_part];
+ DBUG_PRINT("info", ("info: HA_STATUS_ERRKEY"));
+ /*
+ This flag is used to get index number of the unique index that
+ reported duplicate key
+ We will report the errkey on the last handler used and ignore the rest
+ Note: all engines does not support HA_STATUS_ERRKEY, so set errkey.
+ */
+ file->errkey= errkey;
+ file->info(HA_STATUS_ERRKEY | no_lock_flag);
+ errkey= file->errkey;
+ }
+ if (flag & HA_STATUS_TIME)
+ {
+ handler *file, **file_array;
+ DBUG_PRINT("info", ("info: HA_STATUS_TIME"));
+ /*
+ This flag is used to set the latest update time of the table.
+ Used by SHOW commands
+ We will report the maximum of these times
+ */
+ stats.update_time= 0;
+ file_array= m_file;
+ do
+ {
+ file= *file_array;
+ file->info(HA_STATUS_TIME | no_lock_flag);
+ if (file->stats.update_time > stats.update_time)
+ stats.update_time= file->stats.update_time;
+ } while (*(++file_array));
+ }
+ DBUG_RETURN(0);
+}
+
+
+void ha_partition::get_dynamic_partition_info(PARTITION_STATS *stat_info,
+ uint part_id)
+{
+ handler *file= m_file[part_id];
+ DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), part_id));
+ file->info(HA_STATUS_TIME | HA_STATUS_VARIABLE |
+ HA_STATUS_VARIABLE_EXTRA | HA_STATUS_NO_LOCK);
+
+ stat_info->records= file->stats.records;
+ stat_info->mean_rec_length= file->stats.mean_rec_length;
+ stat_info->data_file_length= file->stats.data_file_length;
+ stat_info->max_data_file_length= file->stats.max_data_file_length;
+ stat_info->index_file_length= file->stats.index_file_length;
+ stat_info->delete_length= file->stats.delete_length;
+ stat_info->create_time= file->stats.create_time;
+ stat_info->update_time= file->stats.update_time;
+ stat_info->check_time= file->stats.check_time;
+ stat_info->check_sum= 0;
+ if (file->ha_table_flags() & (HA_HAS_OLD_CHECKSUM | HA_HAS_NEW_CHECKSUM))
+ stat_info->check_sum= file->checksum();
+ return;
+}
+
+
+/**
+ General function to prepare handler for certain behavior.
+
+ @param[in] operation operation to execute
+
+ @return status
+ @retval 0 success
+ @retval >0 error code
+
+ @detail
+
+ extra() is called whenever the server wishes to send a hint to
+ the storage engine. The MyISAM engine implements the most hints.
+
+ We divide the parameters into the following categories:
+ 1) Operations used by most handlers
+ 2) Operations used by some non-MyISAM handlers
+ 3) Operations used only by MyISAM
+ 4) Operations only used by temporary tables for query processing
+ 5) Operations only used by MyISAM internally
+ 6) Operations not used at all
+ 7) Operations only used by federated tables for query processing
+ 8) Operations only used by NDB
+ 9) Operations only used by MERGE
+
+ The partition handler need to handle category 1), 2) and 3).
+
+ 1) Operations used by most handlers
+ -----------------------------------
+ HA_EXTRA_RESET:
+ This option is used by most handlers and it resets the handler state
+ to the same state as after an open call. This includes releasing
+ any READ CACHE or WRITE CACHE or other internal buffer used.
+
+ It is called from the reset method in the handler interface. There are
+ three instances where this is called.
+ 1) After completing a INSERT ... SELECT ... query the handler for the
+ table inserted into is reset
+ 2) It is called from close_thread_table which in turn is called from
+ close_thread_tables except in the case where the tables are locked
+ in which case ha_commit_stmt is called instead.
+ It is only called from here if refresh_version hasn't changed and the
+ table is not an old table when calling close_thread_table.
+ close_thread_tables is called from many places as a general clean up
+ function after completing a query.
+ 3) It is called when deleting the QUICK_RANGE_SELECT object if the
+ QUICK_RANGE_SELECT object had its own handler object. It is called
+ immediatley before close of this local handler object.
+ HA_EXTRA_KEYREAD:
+ HA_EXTRA_NO_KEYREAD:
+ These parameters are used to provide an optimisation hint to the handler.
+ If HA_EXTRA_KEYREAD is set it is enough to read the index fields, for
+ many handlers this means that the index-only scans can be used and it
+ is not necessary to use the real records to satisfy this part of the
+ query. Index-only scans is a very important optimisation for disk-based
+ indexes. For main-memory indexes most indexes contain a reference to the
+ record and thus KEYREAD only says that it is enough to read key fields.
+ HA_EXTRA_NO_KEYREAD disables this for the handler, also HA_EXTRA_RESET
+ will disable this option.
+ The handler will set HA_KEYREAD_ONLY in its table flags to indicate this
+ feature is supported.
+ HA_EXTRA_FLUSH:
+ Indication to flush tables to disk, is supposed to be used to
+ ensure disk based tables are flushed at end of query execution.
+ Currently is never used.
+
+ HA_EXTRA_FORCE_REOPEN:
+ Only used by MyISAM and Archive, called when altering table,
+ closing tables to enforce a reopen of the table files.
+
+ 2) Operations used by some non-MyISAM handlers
+ ----------------------------------------------
+ HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
+ This is a strictly InnoDB feature that is more or less undocumented.
+ When it is activated InnoDB copies field by field from its fetch
+ cache instead of all fields in one memcpy. Have no idea what the
+ purpose of this is.
+ Cut from include/my_base.h:
+ When using HA_EXTRA_KEYREAD, overwrite only key member fields and keep
+ other fields intact. When this is off (by default) InnoDB will use memcpy
+ to overwrite entire row.
+ HA_EXTRA_IGNORE_DUP_KEY:
+ HA_EXTRA_NO_IGNORE_DUP_KEY:
+ Informs the handler to we will not stop the transaction if we get an
+ duplicate key errors during insert/upate.
+ Always called in pair, triggered by INSERT IGNORE and other similar
+ SQL constructs.
+ Not used by MyISAM.
+
+ 3) Operations used only by MyISAM
+ ---------------------------------
+ HA_EXTRA_NORMAL:
+ Only used in MyISAM to reset quick mode, not implemented by any other
+ handler. Quick mode is also reset in MyISAM by HA_EXTRA_RESET.
+
+ It is called after completing a successful DELETE query if the QUICK
+ option is set.
+
+ HA_EXTRA_QUICK:
+ When the user does DELETE QUICK FROM table where-clause; this extra
+ option is called before the delete query is performed and
+ HA_EXTRA_NORMAL is called after the delete query is completed.
+ Temporary tables used internally in MySQL always set this option
+
+ The meaning of quick mode is that when deleting in a B-tree no merging
+ of leafs is performed. This is a common method and many large DBMS's
+ actually only support this quick mode since it is very difficult to
+ merge leaves in a tree used by many threads concurrently.
+
+ HA_EXTRA_CACHE:
+ This flag is usually set with extra_opt along with a cache size.
+ The size of this buffer is set by the user variable
+ record_buffer_size. The value of this cache size is the amount of
+ data read from disk in each fetch when performing a table scan.
+ This means that before scanning a table it is normal to call
+ extra with HA_EXTRA_CACHE and when the scan is completed to call
+ HA_EXTRA_NO_CACHE to release the cache memory.
+
+ Some special care is taken when using this extra parameter since there
+ could be a write ongoing on the table in the same statement. In this
+ one has to take special care since there might be a WRITE CACHE as
+ well. HA_EXTRA_CACHE specifies using a READ CACHE and using
+ READ CACHE and WRITE CACHE at the same time is not possible.
+
+ Only MyISAM currently use this option.
+
+ It is set when doing full table scans using rr_sequential and
+ reset when completing such a scan with end_read_record
+ (resetting means calling extra with HA_EXTRA_NO_CACHE).
+
+ It is set in filesort.cc for MyISAM internal tables and it is set in
+ a multi-update where HA_EXTRA_CACHE is called on a temporary result
+ table and after that ha_rnd_init(0) on table to be updated
+ and immediately after that HA_EXTRA_NO_CACHE on table to be updated.
+
+ Apart from that it is always used from init_read_record but not when
+ used from UPDATE statements. It is not used from DELETE statements
+ with ORDER BY and LIMIT but it is used in normal scan loop in DELETE
+ statements. The reason here is that DELETE's in MyISAM doesn't move
+ existings data rows.
+
+ It is also set in copy_data_between_tables when scanning the old table
+ to copy over to the new table.
+ And it is set in join_init_read_record where quick objects are used
+ to perform a scan on the table. In this case the full table scan can
+ even be performed multiple times as part of the nested loop join.
+
+ For purposes of the partition handler it is obviously necessary to have
+ special treatment of this extra call. If we would simply pass this
+ extra call down to each handler we would allocate
+ cache size * no of partitions amount of memory and this is not
+ necessary since we will only scan one partition at a time when doing
+ full table scans.
+
+ Thus we treat it by first checking whether we have MyISAM handlers in
+ the table, if not we simply ignore the call and if we have we will
+ record the call but will not call any underlying handler yet. Then
+ when performing the sequential scan we will check this recorded value
+ and call extra_opt whenever we start scanning a new partition.
+
+ HA_EXTRA_NO_CACHE:
+ When performing a UNION SELECT HA_EXTRA_NO_CACHE is called from the
+ flush method in the select_union class.
+ It is used to some extent when insert delayed inserts.
+ See HA_EXTRA_RESET_STATE for use in conjunction with delete_all_rows().
+
+ It should be ok to call HA_EXTRA_NO_CACHE on all underlying handlers
+ if they are MyISAM handlers. Other handlers we can ignore the call
+ for. If no cache is in use they will quickly return after finding
+ this out. And we also ensure that all caches are disabled and no one
+ is left by mistake.
+ In the future this call will probably be deleted and we will instead call
+ ::reset();
+
+ HA_EXTRA_WRITE_CACHE:
+ See above, called from various places. It is mostly used when we
+ do INSERT ... SELECT
+ No special handling to save cache space is developed currently.
+
+ HA_EXTRA_PREPARE_FOR_UPDATE:
+ This is called as part of a multi-table update. When the table to be
+ updated is also scanned then this informs MyISAM handler to drop any
+ caches if dynamic records are used (fixed size records do not care
+ about this call). We pass this along to the first partition to scan, and
+ flag that it is to be called after HA_EXTRA_CACHE when moving to the next
+ partition to scan.
+
+ HA_EXTRA_PREPARE_FOR_DROP:
+ Only used by MyISAM, called in preparation for a DROP TABLE.
+ It's used mostly by Windows that cannot handle dropping an open file.
+ On other platforms it has the same effect as HA_EXTRA_FORCE_REOPEN.
+
+ HA_EXTRA_PREPARE_FOR_RENAME:
+ Informs the handler we are about to attempt a rename of the table.
+ For handlers that have share open files (MyISAM key-file and
+ Archive writer) they must close the files before rename is possible
+ on Windows.
+
+ HA_EXTRA_READCHECK:
+ HA_EXTRA_NO_READCHECK:
+ Only one call to HA_EXTRA_NO_READCHECK from ha_open where it says that
+ this is not needed in SQL. The reason for this call is that MyISAM sets
+ the READ_CHECK_USED in the open call so the call is needed for MyISAM
+ to reset this feature.
+ The idea with this parameter was to inform of doing/not doing a read
+ check before applying an update. Since SQL always performs a read before
+ applying the update No Read Check is needed in MyISAM as well.
+
+ This is a cut from Docs/myisam.txt
+ Sometimes you might want to force an update without checking whether
+ another user has changed the record since you last read it. This is
+ somewhat dangerous, so it should ideally not be used. That can be
+ accomplished by wrapping the mi_update() call in two calls to mi_extra(),
+ using these functions:
+ HA_EXTRA_NO_READCHECK=5 No readcheck on update
+ HA_EXTRA_READCHECK=6 Use readcheck (def)
+
+ 4) Operations only used by temporary tables for query processing
+ ----------------------------------------------------------------
+ HA_EXTRA_RESET_STATE:
+ Same as reset() except that buffers are not released. If there is
+ a READ CACHE it is reinit'ed. A cache is reinit'ed to restart reading
+ or to change type of cache between READ CACHE and WRITE CACHE.
+
+ This extra function is always called immediately before calling
+ delete_all_rows on the handler for temporary tables.
+ There are cases however when HA_EXTRA_RESET_STATE isn't called in
+ a similar case for a temporary table in sql_union.cc and in two other
+ cases HA_EXTRA_NO_CACHE is called before and HA_EXTRA_WRITE_CACHE
+ called afterwards.
+ The case with HA_EXTRA_NO_CACHE and HA_EXTRA_WRITE_CACHE means
+ disable caching, delete all rows and enable WRITE CACHE. This is
+ used for temporary tables containing distinct sums and a
+ functional group.
+
+ The only case that delete_all_rows is called on non-temporary tables
+ is in sql_delete.cc when DELETE FROM table; is called by a user.
+ In this case no special extra calls are performed before or after this
+ call.
+
+ The partition handler should not need to bother about this one. It
+ should never be called.
+
+ HA_EXTRA_NO_ROWS:
+ Don't insert rows indication to HEAP and MyISAM, only used by temporary
+ tables used in query processing.
+ Not handled by partition handler.
+
+ 5) Operations only used by MyISAM internally
+ --------------------------------------------
+ HA_EXTRA_REINIT_CACHE:
+ This call reinitializes the READ CACHE described above if there is one
+ and otherwise the call is ignored.
+
+ We can thus safely call it on all underlying handlers if they are
+ MyISAM handlers. It is however never called so we don't handle it at all.
+ HA_EXTRA_FLUSH_CACHE:
+ Flush WRITE CACHE in MyISAM. It is only from one place in the code.
+ This is in sql_insert.cc where it is called if the table_flags doesn't
+ contain HA_DUPLICATE_POS. The only handler having the HA_DUPLICATE_POS
+ set is the MyISAM handler and so the only handler not receiving this
+ call is MyISAM.
+ Thus in effect this call is called but never used. Could be removed
+ from sql_insert.cc
+ HA_EXTRA_NO_USER_CHANGE:
+ Only used by MyISAM, never called.
+ Simulates lock_type as locked.
+ HA_EXTRA_WAIT_LOCK:
+ HA_EXTRA_WAIT_NOLOCK:
+ Only used by MyISAM, called from MyISAM handler but never from server
+ code on top of the handler.
+ Sets lock_wait on/off
+ HA_EXTRA_NO_KEYS:
+ Only used MyISAM, only used internally in MyISAM handler, never called
+ from server level.
+ HA_EXTRA_KEYREAD_CHANGE_POS:
+ HA_EXTRA_REMEMBER_POS:
+ HA_EXTRA_RESTORE_POS:
+ HA_EXTRA_PRELOAD_BUFFER_SIZE:
+ HA_EXTRA_CHANGE_KEY_TO_DUP:
+ HA_EXTRA_CHANGE_KEY_TO_UNIQUE:
+ Only used by MyISAM, never called.
+
+ 6) Operations not used at all
+ -----------------------------
+ HA_EXTRA_KEY_CACHE:
+ HA_EXTRA_NO_KEY_CACHE:
+ This parameters are no longer used and could be removed.
+
+ 7) Operations only used by federated tables for query processing
+ ----------------------------------------------------------------
+ HA_EXTRA_INSERT_WITH_UPDATE:
+ Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be
+ executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY.
+
+ 8) Operations only used by NDB
+ ------------------------------
+ HA_EXTRA_DELETE_CANNOT_BATCH:
+ HA_EXTRA_UPDATE_CANNOT_BATCH:
+ Inform handler that delete_row()/update_row() cannot batch deletes/updates
+ and should perform them immediately. This may be needed when table has
+ AFTER DELETE/UPDATE triggers which access to subject table.
+ These flags are reset by the handler::extra(HA_EXTRA_RESET) call.
+
+ 9) Operations only used by MERGE
+ ------------------------------
+ HA_EXTRA_ADD_CHILDREN_LIST:
+ HA_EXTRA_ATTACH_CHILDREN:
+ HA_EXTRA_IS_ATTACHED_CHILDREN:
+ HA_EXTRA_DETACH_CHILDREN:
+ Special actions for MERGE tables. Ignore.
+*/
+
+int ha_partition::extra(enum ha_extra_function operation)
+{
+ DBUG_ENTER("ha_partition:extra");
+ DBUG_PRINT("info", ("operation: %d", (int) operation));
+
+ switch (operation) {
+ /* Category 1), used by most handlers */
+ case HA_EXTRA_KEYREAD:
+ case HA_EXTRA_NO_KEYREAD:
+ case HA_EXTRA_FLUSH:
+ case HA_EXTRA_PREPARE_FOR_FORCED_CLOSE:
+ DBUG_RETURN(loop_extra(operation));
+ case HA_EXTRA_PREPARE_FOR_RENAME:
+ case HA_EXTRA_FORCE_REOPEN:
+ DBUG_RETURN(loop_extra_alter(operation));
+ break;
+
+ /* Category 2), used by non-MyISAM handlers */
+ case HA_EXTRA_IGNORE_DUP_KEY:
+ case HA_EXTRA_NO_IGNORE_DUP_KEY:
+ case HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
+ {
+ if (!m_myisam)
+ DBUG_RETURN(loop_extra(operation));
+ break;
+ }
+
+ /* Category 3), used by MyISAM handlers */
+ case HA_EXTRA_PREPARE_FOR_UPDATE:
+ /*
+ Needs to be run on the first partition in the range now, and
+ later in late_extra_cache, when switching to a new partition to scan.
+ */
+ m_extra_prepare_for_update= TRUE;
+ if (m_part_spec.start_part != NO_CURRENT_PART_ID)
+ {
+ if (!m_extra_cache)
+ m_extra_cache_part_id= m_part_spec.start_part;
+ DBUG_ASSERT(m_extra_cache_part_id == m_part_spec.start_part);
+ (void) m_file[m_part_spec.start_part]->extra(HA_EXTRA_PREPARE_FOR_UPDATE);
+ }
+ break;
+ case HA_EXTRA_NORMAL:
+ case HA_EXTRA_QUICK:
+ case HA_EXTRA_PREPARE_FOR_DROP:
+ case HA_EXTRA_FLUSH_CACHE:
+ {
+ DBUG_RETURN(loop_extra(operation));
+ }
+ case HA_EXTRA_NO_READCHECK:
+ {
+ /*
+ This is only done as a part of ha_open, which is also used in
+ ha_partition::open, so no need to do anything.
+ */
+ break;
+ }
+ case HA_EXTRA_CACHE:
+ {
+ prepare_extra_cache(0);
+ break;
+ }
+ case HA_EXTRA_NO_CACHE:
+ {
+ int ret= 0;
+ if (m_extra_cache_part_id != NO_CURRENT_PART_ID)
+ ret= m_file[m_extra_cache_part_id]->extra(HA_EXTRA_NO_CACHE);
+ m_extra_cache= FALSE;
+ m_extra_cache_size= 0;
+ m_extra_prepare_for_update= FALSE;
+ m_extra_cache_part_id= NO_CURRENT_PART_ID;
+ DBUG_RETURN(ret);
+ }
+ case HA_EXTRA_WRITE_CACHE:
+ {
+ m_extra_cache= FALSE;
+ m_extra_cache_size= 0;
+ m_extra_prepare_for_update= FALSE;
+ m_extra_cache_part_id= NO_CURRENT_PART_ID;
+ DBUG_RETURN(loop_extra(operation));
+ }
+ case HA_EXTRA_IGNORE_NO_KEY:
+ case HA_EXTRA_NO_IGNORE_NO_KEY:
+ {
+ /*
+ Ignore as these are specific to NDB for handling
+ idempotency
+ */
+ break;
+ }
+ case HA_EXTRA_WRITE_CAN_REPLACE:
+ case HA_EXTRA_WRITE_CANNOT_REPLACE:
+ {
+ /*
+ Informs handler that write_row() can replace rows which conflict
+ with row being inserted by PK/unique key without reporting error
+ to the SQL-layer.
+
+ This optimization is not safe for partitioned table in general case
+ since we may have to put new version of row into partition which is
+ different from partition in which old version resides (for example
+ when we partition by non-PK column or by some column which is not
+ part of unique key which were violated).
+ And since NDB which is the only engine at the moment that supports
+ this optimization handles partitioning on its own we simple disable
+ it here. (BTW for NDB this optimization is safe since it supports
+ only KEY partitioning and won't use this optimization for tables
+ which have additional unique constraints).
+ */
+ break;
+ }
+ /* Category 7), used by federated handlers */
+ case HA_EXTRA_INSERT_WITH_UPDATE:
+ DBUG_RETURN(loop_extra(operation));
+ /* Category 8) Operations only used by NDB */
+ case HA_EXTRA_DELETE_CANNOT_BATCH:
+ case HA_EXTRA_UPDATE_CANNOT_BATCH:
+ {
+ /* Currently only NDB use the *_CANNOT_BATCH */
+ break;
+ }
+ /* Category 9) Operations only used by MERGE */
+ case HA_EXTRA_ADD_CHILDREN_LIST:
+ case HA_EXTRA_ATTACH_CHILDREN:
+ case HA_EXTRA_IS_ATTACHED_CHILDREN:
+ case HA_EXTRA_DETACH_CHILDREN:
+ {
+ /* Special actions for MERGE tables. Ignore. */
+ break;
+ }
+ /*
+ http://dev.mysql.com/doc/refman/5.1/en/partitioning-limitations.html
+ says we no longer support logging to partitioned tables, so we fail
+ here.
+ */
+ case HA_EXTRA_MARK_AS_LOG_TABLE:
+ DBUG_RETURN(ER_UNSUPORTED_LOG_ENGINE);
+ default:
+ {
+ /* Temporary crash to discover what is wrong */
+ DBUG_ASSERT(0);
+ break;
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Special extra call to reset extra parameters
+
+ @return Operation status.
+ @retval >0 Error code
+ @retval 0 Success
+
+ @note Called at end of each statement to reset buffers.
+ To avoid excessive calls, the m_partitions_to_reset bitmap keep records
+ of which partitions that have been used in extra(), external_lock() or
+ start_stmt() and is needed to be called.
+*/
+
+int ha_partition::reset(void)
+{
+ int result= 0;
+ int tmp;
+ uint i;
+ DBUG_ENTER("ha_partition::reset");
+
+ for (i= bitmap_get_first_set(&m_partitions_to_reset);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_partitions_to_reset, i))
+ {
+ if ((tmp= m_file[i]->ha_reset()))
+ result= tmp;
+ }
+ bitmap_clear_all(&m_partitions_to_reset);
+ DBUG_RETURN(result);
+}
+
+/*
+ Special extra method for HA_EXTRA_CACHE with cachesize as extra parameter
+
+ SYNOPSIS
+ extra_opt()
+ operation Must be HA_EXTRA_CACHE
+ cachesize Size of cache in full table scan
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+*/
+
+int ha_partition::extra_opt(enum ha_extra_function operation, ulong cachesize)
+{
+ DBUG_ENTER("ha_partition::extra_opt()");
+
+ DBUG_ASSERT(HA_EXTRA_CACHE == operation);
+ prepare_extra_cache(cachesize);
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Call extra on handler with HA_EXTRA_CACHE and cachesize
+
+ SYNOPSIS
+ prepare_extra_cache()
+ cachesize Size of cache for full table scan
+
+ RETURN VALUE
+ NONE
+*/
+
+void ha_partition::prepare_extra_cache(uint cachesize)
+{
+ DBUG_ENTER("ha_partition::prepare_extra_cache()");
+ DBUG_PRINT("info", ("cachesize %u", cachesize));
+
+ m_extra_cache= TRUE;
+ m_extra_cache_size= cachesize;
+ if (m_part_spec.start_part != NO_CURRENT_PART_ID)
+ {
+ DBUG_ASSERT(bitmap_is_set(&m_partitions_to_reset,
+ m_part_spec.start_part));
+ bitmap_set_bit(&m_partitions_to_reset, m_part_spec.start_part);
+ late_extra_cache(m_part_spec.start_part);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Prepares our new and reorged handlers for rename or delete.
+
+ @param operation Operation to forward
+
+ @return Operation status
+ @retval 0 Success
+ @retval !0 Error
+*/
+
+int ha_partition::loop_extra_alter(enum ha_extra_function operation)
+{
+ int result= 0, tmp;
+ handler **file;
+ DBUG_ENTER("ha_partition::loop_extra_alter()");
+ DBUG_ASSERT(operation == HA_EXTRA_PREPARE_FOR_RENAME ||
+ operation == HA_EXTRA_FORCE_REOPEN);
+
+ if (m_new_file != NULL)
+ {
+ for (file= m_new_file; *file; file++)
+ if ((tmp= (*file)->extra(operation)))
+ result= tmp;
+ }
+ if (m_reorged_file != NULL)
+ {
+ for (file= m_reorged_file; *file; file++)
+ if ((tmp= (*file)->extra(operation)))
+ result= tmp;
+ }
+ if ((tmp= loop_extra(operation)))
+ result= tmp;
+ DBUG_RETURN(result);
+}
+
+/*
+ Call extra on all partitions
+
+ SYNOPSIS
+ loop_extra()
+ operation extra operation type
+
+ RETURN VALUE
+ >0 Error code
+ 0 Success
+*/
+
+int ha_partition::loop_extra(enum ha_extra_function operation)
+{
+ int result= 0, tmp;
+ uint i;
+ DBUG_ENTER("ha_partition::loop_extra()");
+
+ for (i= bitmap_get_first_set(&m_part_info->lock_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->lock_partitions, i))
+ {
+ if ((tmp= m_file[i]->extra(operation)))
+ result= tmp;
+ }
+ /* Add all used partitions to be called in reset(). */
+ bitmap_union(&m_partitions_to_reset, &m_part_info->lock_partitions);
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Call extra(HA_EXTRA_CACHE) on next partition_id
+
+ SYNOPSIS
+ late_extra_cache()
+ partition_id Partition id to call extra on
+
+ RETURN VALUE
+ NONE
+*/
+
+void ha_partition::late_extra_cache(uint partition_id)
+{
+ handler *file;
+ DBUG_ENTER("ha_partition::late_extra_cache");
+ DBUG_PRINT("info", ("extra_cache %u prepare %u partid %u size %u",
+ m_extra_cache, m_extra_prepare_for_update,
+ partition_id, m_extra_cache_size));
+
+ if (!m_extra_cache && !m_extra_prepare_for_update)
+ DBUG_VOID_RETURN;
+ file= m_file[partition_id];
+ if (m_extra_cache)
+ {
+ if (m_extra_cache_size == 0)
+ (void) file->extra(HA_EXTRA_CACHE);
+ else
+ (void) file->extra_opt(HA_EXTRA_CACHE, m_extra_cache_size);
+ }
+ if (m_extra_prepare_for_update)
+ {
+ DBUG_ASSERT(m_extra_cache);
+ (void) file->extra(HA_EXTRA_PREPARE_FOR_UPDATE);
+ }
+ m_extra_cache_part_id= partition_id;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Call extra(HA_EXTRA_NO_CACHE) on next partition_id
+
+ SYNOPSIS
+ late_extra_no_cache()
+ partition_id Partition id to call extra on
+
+ RETURN VALUE
+ NONE
+*/
+
+void ha_partition::late_extra_no_cache(uint partition_id)
+{
+ handler *file;
+ DBUG_ENTER("ha_partition::late_extra_no_cache");
+
+ if (!m_extra_cache && !m_extra_prepare_for_update)
+ DBUG_VOID_RETURN;
+ file= m_file[partition_id];
+ (void) file->extra(HA_EXTRA_NO_CACHE);
+ DBUG_ASSERT(partition_id == m_extra_cache_part_id);
+ m_extra_cache_part_id= NO_CURRENT_PART_ID;
+ DBUG_VOID_RETURN;
+}
+
+
+/****************************************************************************
+ MODULE optimiser support
+****************************************************************************/
+
+/**
+ Get keys to use for scanning.
+
+ @return key_map of keys usable for scanning
+
+ @note No need to use read_partitions here, since it does not depend on
+ which partitions is used, only which storage engine used.
+*/
+
+const key_map *ha_partition::keys_to_use_for_scanning()
+{
+ DBUG_ENTER("ha_partition::keys_to_use_for_scanning");
+ DBUG_RETURN(m_file[0]->keys_to_use_for_scanning());
+}
+
+
+/**
+ Minimum number of rows to base optimizer estimate on.
+*/
+
+ha_rows ha_partition::min_rows_for_estimate()
+{
+ uint i, max_used_partitions, tot_used_partitions;
+ DBUG_ENTER("ha_partition::min_rows_for_estimate");
+
+ tot_used_partitions= bitmap_bits_set(&m_part_info->read_partitions);
+
+ /*
+ All partitions might have been left as unused during partition pruning
+ due to, for example, an impossible WHERE condition. Nonetheless, the
+ optimizer might still attempt to perform (e.g. range) analysis where an
+ estimate of the the number of rows is calculated using records_in_range.
+ Hence, to handle this and other possible cases, use zero as the minimum
+ number of rows to base the estimate on if no partition is being used.
+ */
+ if (!tot_used_partitions)
+ DBUG_RETURN(0);
+
+ /*
+ Allow O(log2(tot_partitions)) increase in number of used partitions.
+ This gives O(tot_rows/log2(tot_partitions)) rows to base the estimate on.
+ I.e when the total number of partitions doubles, allow one more
+ partition to be checked.
+ */
+ i= 2;
+ max_used_partitions= 1;
+ while (i < m_tot_parts)
+ {
+ max_used_partitions++;
+ i= i << 1;
+ }
+ if (max_used_partitions > tot_used_partitions)
+ max_used_partitions= tot_used_partitions;
+
+ /* stats.records is already updated by the info(HA_STATUS_VARIABLE) call. */
+ DBUG_PRINT("info", ("max_used_partitions: %u tot_rows: %lu",
+ max_used_partitions,
+ (ulong) stats.records));
+ DBUG_PRINT("info", ("tot_used_partitions: %u min_rows_to_check: %lu",
+ tot_used_partitions,
+ (ulong) stats.records * max_used_partitions
+ / tot_used_partitions));
+ DBUG_RETURN(stats.records * max_used_partitions / tot_used_partitions);
+}
+
+
+/**
+ Get the biggest used partition.
+
+ Starting at the N:th biggest partition and skips all non used
+ partitions, returning the biggest used partition found
+
+ @param[in,out] part_index Skip the *part_index biggest partitions
+
+ @return The biggest used partition with index not lower than *part_index.
+ @retval NO_CURRENT_PART_ID No more partition used.
+ @retval != NO_CURRENT_PART_ID partition id of biggest used partition with
+ index >= *part_index supplied. Note that
+ *part_index will be updated to the next
+ partition index to use.
+*/
+
+uint ha_partition::get_biggest_used_partition(uint *part_index)
+{
+ uint part_id;
+ while ((*part_index) < m_tot_parts)
+ {
+ part_id= m_part_ids_sorted_by_num_of_records[(*part_index)++];
+ if (bitmap_is_set(&m_part_info->read_partitions, part_id))
+ return part_id;
+ }
+ return NO_CURRENT_PART_ID;
+}
+
+
+/*
+ Return time for a scan of the table
+
+ SYNOPSIS
+ scan_time()
+
+ RETURN VALUE
+ time for scan
+*/
+
+double ha_partition::scan_time()
+{
+ double scan_time= 0;
+ uint i;
+ DBUG_ENTER("ha_partition::scan_time");
+
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ scan_time+= m_file[i]->scan_time();
+ DBUG_RETURN(scan_time);
+}
+
+
+/**
+ Find number of records in a range.
+ @param inx Index number
+ @param min_key Start of range
+ @param max_key End of range
+
+ @return Number of rows in range.
+
+ Given a starting key, and an ending key estimate the number of rows that
+ will exist between the two. max_key may be empty which in case determine
+ if start_key matches any rows.
+*/
+
+ha_rows ha_partition::records_in_range(uint inx, key_range *min_key,
+ key_range *max_key)
+{
+ ha_rows min_rows_to_check, rows, estimated_rows=0, checked_rows= 0;
+ uint partition_index= 0, part_id;
+ DBUG_ENTER("ha_partition::records_in_range");
+
+ min_rows_to_check= min_rows_for_estimate();
+
+ while ((part_id= get_biggest_used_partition(&partition_index))
+ != NO_CURRENT_PART_ID)
+ {
+ rows= m_file[part_id]->records_in_range(inx, min_key, max_key);
+
+ DBUG_PRINT("info", ("part %u match %lu rows of %lu", part_id, (ulong) rows,
+ (ulong) m_file[part_id]->stats.records));
+
+ if (rows == HA_POS_ERROR)
+ DBUG_RETURN(HA_POS_ERROR);
+ estimated_rows+= rows;
+ checked_rows+= m_file[part_id]->stats.records;
+ /*
+ Returning 0 means no rows can be found, so we must continue
+ this loop as long as we have estimated_rows == 0.
+ Also many engines return 1 to indicate that there may exist
+ a matching row, we do not normalize this by dividing by number of
+ used partitions, but leave it to be returned as a sum, which will
+ reflect that we will need to scan each partition's index.
+
+ Note that this statistics may not always be correct, so we must
+ continue even if the current partition has 0 rows, since we might have
+ deleted rows from the current partition, or inserted to the next
+ partition.
+ */
+ if (estimated_rows && checked_rows &&
+ checked_rows >= min_rows_to_check)
+ {
+ DBUG_PRINT("info",
+ ("records_in_range(inx %u): %lu (%lu * %lu / %lu)",
+ inx,
+ (ulong) (estimated_rows * stats.records / checked_rows),
+ (ulong) estimated_rows,
+ (ulong) stats.records,
+ (ulong) checked_rows));
+ DBUG_RETURN(estimated_rows * stats.records / checked_rows);
+ }
+ }
+ DBUG_PRINT("info", ("records_in_range(inx %u): %lu",
+ inx,
+ (ulong) estimated_rows));
+ DBUG_RETURN(estimated_rows);
+}
+
+
+/**
+ Estimate upper bound of number of rows.
+
+ @return Number of rows.
+*/
+
+ha_rows ha_partition::estimate_rows_upper_bound()
+{
+ ha_rows rows, tot_rows= 0;
+ handler **file= m_file;
+ DBUG_ENTER("ha_partition::estimate_rows_upper_bound");
+
+ do
+ {
+ if (bitmap_is_set(&(m_part_info->read_partitions), (file - m_file)))
+ {
+ rows= (*file)->estimate_rows_upper_bound();
+ if (rows == HA_POS_ERROR)
+ DBUG_RETURN(HA_POS_ERROR);
+ tot_rows+= rows;
+ }
+ } while (*(++file));
+ DBUG_RETURN(tot_rows);
+}
+
+
+/*
+ Get time to read
+
+ SYNOPSIS
+ read_time()
+ index Index number used
+ ranges Number of ranges
+ rows Number of rows
+
+ RETURN VALUE
+ time for read
+
+ DESCRIPTION
+ This will be optimised later to include whether or not the index can
+ be used with partitioning. To achieve we need to add another parameter
+ that specifies how many of the index fields that are bound in the ranges.
+ Possibly added as a new call to handlers.
+*/
+
+double ha_partition::read_time(uint index, uint ranges, ha_rows rows)
+{
+ DBUG_ENTER("ha_partition::read_time");
+
+ DBUG_RETURN(m_file[0]->read_time(index, ranges, rows));
+}
+
+
+/**
+ Number of rows in table. see handler.h
+
+ @return Number of records in the table (after pruning!)
+*/
+
+ha_rows ha_partition::records()
+{
+ ha_rows rows, tot_rows= 0;
+ uint i;
+ DBUG_ENTER("ha_partition::records");
+
+ for (i= bitmap_get_first_set(&m_part_info->read_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->read_partitions, i))
+ {
+ rows= m_file[i]->records();
+ if (rows == HA_POS_ERROR)
+ DBUG_RETURN(HA_POS_ERROR);
+ tot_rows+= rows;
+ }
+ DBUG_RETURN(tot_rows);
+}
+
+
+/*
+ Is it ok to switch to a new engine for this table
+
+ SYNOPSIS
+ can_switch_engine()
+
+ RETURN VALUE
+ TRUE Ok
+ FALSE Not ok
+
+ DESCRIPTION
+ Used to ensure that tables with foreign key constraints are not moved
+ to engines without foreign key support.
+*/
+
+bool ha_partition::can_switch_engines()
+{
+ handler **file;
+ DBUG_ENTER("ha_partition::can_switch_engines");
+
+ file= m_file;
+ do
+ {
+ if (!(*file)->can_switch_engines())
+ DBUG_RETURN(FALSE);
+ } while (*(++file));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Is table cache supported
+
+ SYNOPSIS
+ table_cache_type()
+
+*/
+
+uint8 ha_partition::table_cache_type()
+{
+ DBUG_ENTER("ha_partition::table_cache_type");
+
+ DBUG_RETURN(m_file[0]->table_cache_type());
+}
+
+
+/**
+ Calculate hash value for KEY partitioning using an array of fields.
+
+ @param field_array An array of the fields in KEY partitioning
+
+ @return hash_value calculated
+
+ @note Uses the hash function on the character set of the field.
+ Integer and floating point fields use the binary character set by default.
+*/
+
+uint32 ha_partition::calculate_key_hash_value(Field **field_array)
+{
+ ulong nr1= 1;
+ ulong nr2= 4;
+ bool use_51_hash;
+ use_51_hash= MY_TEST((*field_array)->table->part_info->key_algorithm ==
+ partition_info::KEY_ALGORITHM_51);
+
+ do
+ {
+ Field *field= *field_array;
+ if (use_51_hash)
+ {
+ switch (field->real_type()) {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_NEWDATE:
+ {
+ if (field->is_null())
+ {
+ nr1^= (nr1 << 1) | 1;
+ continue;
+ }
+ /* Force this to my_hash_sort_bin, which was used in 5.1! */
+ uint len= field->pack_length();
+ my_charset_bin.coll->hash_sort(&my_charset_bin, field->ptr, len,
+ &nr1, &nr2);
+ /* Done with this field, continue with next one. */
+ continue;
+ }
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_BIT:
+ /* Not affected, same in 5.1 and 5.5 */
+ break;
+ /*
+ ENUM/SET uses my_hash_sort_simple in 5.1 (i.e. my_charset_latin1)
+ and my_hash_sort_bin in 5.5!
+ */
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ {
+ if (field->is_null())
+ {
+ nr1^= (nr1 << 1) | 1;
+ continue;
+ }
+ /* Force this to my_hash_sort_bin, which was used in 5.1! */
+ uint len= field->pack_length();
+ my_charset_latin1.coll->hash_sort(&my_charset_latin1, field->ptr,
+ len, &nr1, &nr2);
+ continue;
+ }
+ /* New types in mysql-5.6. */
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_TIME2:
+ case MYSQL_TYPE_TIMESTAMP2:
+ /* Not affected, 5.6+ only! */
+ break;
+
+ /* These types should not be allowed for partitioning! */
+ case MYSQL_TYPE_NULL:
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_GEOMETRY:
+ /* fall through. */
+ default:
+ DBUG_ASSERT(0); // New type?
+ /* Fall through for default hashing (5.5). */
+ }
+ /* fall through, use collation based hashing. */
+ }
+ field->hash(&nr1, &nr2);
+ } while (*(++field_array));
+ return (uint32) nr1;
+}
+
+
+/****************************************************************************
+ MODULE print messages
+****************************************************************************/
+
+const char *ha_partition::index_type(uint inx)
+{
+ uint first_used_partition;
+ DBUG_ENTER("ha_partition::index_type");
+
+ first_used_partition= bitmap_get_first_set(&(m_part_info->read_partitions));
+
+ if (first_used_partition == MY_BIT_NONE)
+ {
+ DBUG_ASSERT(0); // How can this happen?
+ DBUG_RETURN(handler::index_type(inx));
+ }
+
+ DBUG_RETURN(m_file[first_used_partition]->index_type(inx));
+}
+
+
+enum row_type ha_partition::get_row_type() const
+{
+ uint i;
+ enum row_type type;
+ DBUG_ENTER("ha_partition::get_row_type");
+
+ i= bitmap_get_first_set(&m_part_info->read_partitions);
+ DBUG_ASSERT(i < m_tot_parts);
+ if (i >= m_tot_parts)
+ DBUG_RETURN(ROW_TYPE_NOT_USED);
+
+ type= m_file[i]->get_row_type();
+ DBUG_PRINT("info", ("partition %u, row_type: %d", i, type));
+
+ for (i= bitmap_get_next_set(&m_part_info->lock_partitions, i);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->lock_partitions, i))
+ {
+ enum row_type part_type= m_file[i]->get_row_type();
+ DBUG_PRINT("info", ("partition %u, row_type: %d", i, type));
+ if (part_type != type)
+ DBUG_RETURN(ROW_TYPE_NOT_USED);
+ }
+
+ DBUG_RETURN(type);
+}
+
+
+void ha_partition::append_row_to_str(String &str)
+{
+ const uchar *rec;
+ bool is_rec0= !m_err_rec || m_err_rec == table->record[0];
+ if (is_rec0)
+ rec= table->record[0];
+ else
+ rec= m_err_rec;
+ // If PK, use full PK instead of full part field array!
+ if (table->s->primary_key != MAX_KEY)
+ {
+ KEY *key= table->key_info + table->s->primary_key;
+ KEY_PART_INFO *key_part= key->key_part;
+ KEY_PART_INFO *key_part_end= key_part + key->user_defined_key_parts;
+ if (!is_rec0)
+ set_key_field_ptr(key, rec, table->record[0]);
+ for (; key_part != key_part_end; key_part++)
+ {
+ Field *field= key_part->field;
+ str.append(" ");
+ str.append(field->field_name);
+ str.append(":");
+ field_unpack(&str, field, rec, 0, false);
+ }
+ if (!is_rec0)
+ set_key_field_ptr(key, table->record[0], rec);
+ }
+ else
+ {
+ Field **field_ptr;
+ if (!is_rec0)
+ set_field_ptr(m_part_info->full_part_field_array, rec,
+ table->record[0]);
+ /* No primary key, use full partition field array. */
+ for (field_ptr= m_part_info->full_part_field_array;
+ *field_ptr;
+ field_ptr++)
+ {
+ Field *field= *field_ptr;
+ str.append(" ");
+ str.append(field->field_name);
+ str.append(":");
+ field_unpack(&str, field, rec, 0, false);
+ }
+ if (!is_rec0)
+ set_field_ptr(m_part_info->full_part_field_array, table->record[0],
+ rec);
+ }
+}
+
+
+void ha_partition::print_error(int error, myf errflag)
+{
+ THD *thd= ha_thd();
+ DBUG_ENTER("ha_partition::print_error");
+
+ /* Should probably look for my own errors first */
+ DBUG_PRINT("enter", ("error: %d", error));
+
+ if ((error == HA_ERR_NO_PARTITION_FOUND) &&
+ ! (thd->lex->alter_info.flags & Alter_info::ALTER_TRUNCATE_PARTITION))
+ {
+ m_part_info->print_no_partition_found(table, errflag);
+ DBUG_VOID_RETURN;
+ }
+ else if (error == HA_ERR_ROW_IN_WRONG_PARTITION)
+ {
+ /* Should only happen on DELETE or UPDATE! */
+ DBUG_ASSERT(thd_sql_command(thd) == SQLCOM_DELETE ||
+ thd_sql_command(thd) == SQLCOM_DELETE_MULTI ||
+ thd_sql_command(thd) == SQLCOM_UPDATE ||
+ thd_sql_command(thd) == SQLCOM_UPDATE_MULTI);
+ DBUG_ASSERT(m_err_rec);
+ if (m_err_rec)
+ {
+ uint max_length;
+ char buf[MAX_KEY_LENGTH];
+ String str(buf,sizeof(buf),system_charset_info);
+ uint32 part_id;
+ str.length(0);
+ str.append("(");
+ str.append_ulonglong(m_last_part);
+ str.append(" != ");
+ if (get_part_for_delete(m_err_rec, m_rec0, m_part_info, &part_id))
+ str.append("?");
+ else
+ str.append_ulonglong(part_id);
+ str.append(")");
+ append_row_to_str(str);
+
+ /* Log this error, so the DBA can notice it and fix it! */
+ sql_print_error("Table '%-192s' corrupted: row in wrong partition: %s\n"
+ "Please REPAIR the table!",
+ table->s->table_name.str,
+ str.c_ptr_safe());
+
+ max_length= (MYSQL_ERRMSG_SIZE - (uint) strlen(ER(ER_ROW_IN_WRONG_PARTITION)));
+ if (str.length() >= max_length)
+ {
+ str.length(max_length-4);
+ str.append(STRING_WITH_LEN("..."));
+ }
+ my_error(ER_ROW_IN_WRONG_PARTITION, MYF(0), str.c_ptr_safe());
+ m_err_rec= NULL;
+ DBUG_VOID_RETURN;
+ }
+ /* fall through to generic error handling. */
+ }
+
+ /* In case m_file has not been initialized, like in bug#42438 */
+ if (m_file)
+ {
+ if (m_last_part >= m_tot_parts)
+ {
+ DBUG_ASSERT(0);
+ m_last_part= 0;
+ }
+ m_file[m_last_part]->print_error(error, errflag);
+ }
+ else
+ handler::print_error(error, errflag);
+ DBUG_VOID_RETURN;
+}
+
+
+bool ha_partition::get_error_message(int error, String *buf)
+{
+ DBUG_ENTER("ha_partition::get_error_message");
+
+ /* Should probably look for my own errors first */
+
+ /* In case m_file has not been initialized, like in bug#42438 */
+ if (m_file)
+ DBUG_RETURN(m_file[m_last_part]->get_error_message(error, buf));
+ DBUG_RETURN(handler::get_error_message(error, buf));
+
+}
+
+
+/****************************************************************************
+ MODULE in-place ALTER
+****************************************************************************/
+/**
+ Get table flags.
+*/
+
+handler::Table_flags ha_partition::table_flags() const
+{
+ uint first_used_partition= 0;
+ DBUG_ENTER("ha_partition::table_flags");
+ if (m_handler_status < handler_initialized ||
+ m_handler_status >= handler_closed)
+ DBUG_RETURN(PARTITION_ENABLED_TABLE_FLAGS);
+
+ if (get_lock_type() != F_UNLCK)
+ {
+ /*
+ The flags are cached after external_lock, and may depend on isolation
+ level. So we should use a locked partition to get the correct flags.
+ */
+ first_used_partition= bitmap_get_first_set(&m_part_info->lock_partitions);
+ if (first_used_partition == MY_BIT_NONE)
+ first_used_partition= 0;
+ }
+ DBUG_RETURN((m_file[first_used_partition]->ha_table_flags() &
+ ~(PARTITION_DISABLED_TABLE_FLAGS)) |
+ (PARTITION_ENABLED_TABLE_FLAGS));
+}
+
+
+/**
+ alter_table_flags must be on handler/table level, not on hton level
+ due to the ha_partition hton does not know what the underlying hton is.
+*/
+uint ha_partition::alter_table_flags(uint flags)
+{
+ uint flags_to_return;
+ DBUG_ENTER("ha_partition::alter_table_flags");
+
+ flags_to_return= ht->alter_table_flags(flags);
+ flags_to_return|= m_file[0]->alter_table_flags(flags);
+
+ DBUG_RETURN(flags_to_return);
+}
+
+
+/**
+ check if copy of data is needed in alter table.
+*/
+bool ha_partition::check_if_incompatible_data(HA_CREATE_INFO *create_info,
+ uint table_changes)
+{
+ handler **file;
+ bool ret= COMPATIBLE_DATA_YES;
+
+ /*
+ The check for any partitioning related changes have already been done
+ in mysql_alter_table (by fix_partition_func), so it is only up to
+ the underlying handlers.
+ */
+ for (file= m_file; *file; file++)
+ if ((ret= (*file)->check_if_incompatible_data(create_info,
+ table_changes)) !=
+ COMPATIBLE_DATA_YES)
+ break;
+ return ret;
+}
+
+
+/**
+ Support of in-place alter table.
+*/
+
+/**
+ Helper class for in-place alter, see handler.h
+*/
+
+class ha_partition_inplace_ctx : public inplace_alter_handler_ctx
+{
+public:
+ inplace_alter_handler_ctx **handler_ctx_array;
+private:
+ uint m_tot_parts;
+
+public:
+ ha_partition_inplace_ctx(THD *thd, uint tot_parts)
+ : inplace_alter_handler_ctx(),
+ handler_ctx_array(NULL),
+ m_tot_parts(tot_parts)
+ {}
+
+ ~ha_partition_inplace_ctx()
+ {
+ if (handler_ctx_array)
+ {
+ for (uint index= 0; index < m_tot_parts; index++)
+ delete handler_ctx_array[index];
+ }
+ }
+};
+
+
+enum_alter_inplace_result
+ha_partition::check_if_supported_inplace_alter(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+{
+ uint index= 0;
+ enum_alter_inplace_result result= HA_ALTER_INPLACE_NO_LOCK;
+ ha_partition_inplace_ctx *part_inplace_ctx;
+ bool first_is_set= false;
+ THD *thd= ha_thd();
+
+ DBUG_ENTER("ha_partition::check_if_supported_inplace_alter");
+ /*
+ Support inplace change of KEY () -> KEY ALGORITHM = N ().
+ Any other change would set partition_changed in
+ prep_alter_part_table() in mysql_alter_table().
+ */
+ if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION)
+ DBUG_RETURN(HA_ALTER_INPLACE_NO_LOCK);
+
+ part_inplace_ctx=
+ new (thd->mem_root) ha_partition_inplace_ctx(thd, m_tot_parts);
+ if (!part_inplace_ctx)
+ DBUG_RETURN(HA_ALTER_ERROR);
+
+ part_inplace_ctx->handler_ctx_array= (inplace_alter_handler_ctx **)
+ thd->alloc(sizeof(inplace_alter_handler_ctx *) * (m_tot_parts + 1));
+ if (!part_inplace_ctx->handler_ctx_array)
+ DBUG_RETURN(HA_ALTER_ERROR);
+
+ /* Set all to NULL, including the terminating one. */
+ for (index= 0; index <= m_tot_parts; index++)
+ part_inplace_ctx->handler_ctx_array[index]= NULL;
+
+ ha_alter_info->handler_flags |= Alter_inplace_info::ALTER_PARTITIONED;
+ for (index= 0; index < m_tot_parts; index++)
+ {
+ enum_alter_inplace_result p_result=
+ m_file[index]->check_if_supported_inplace_alter(altered_table,
+ ha_alter_info);
+ part_inplace_ctx->handler_ctx_array[index]= ha_alter_info->handler_ctx;
+
+ if (index == 0)
+ {
+ first_is_set= (ha_alter_info->handler_ctx != NULL);
+ }
+ else if (first_is_set != (ha_alter_info->handler_ctx != NULL))
+ {
+ /* Either none or all partitions must set handler_ctx! */
+ DBUG_ASSERT(0);
+ DBUG_RETURN(HA_ALTER_ERROR);
+ }
+ if (p_result < result)
+ result= p_result;
+ if (result == HA_ALTER_ERROR)
+ break;
+ }
+
+ ha_alter_info->handler_ctx= part_inplace_ctx;
+ /*
+ To indicate for future inplace calls that there are several
+ partitions/handlers that need to be committed together,
+ we set group_commit_ctx to the NULL terminated array of
+ the partitions handlers.
+ */
+ ha_alter_info->group_commit_ctx= part_inplace_ctx->handler_ctx_array;
+
+ DBUG_RETURN(result);
+}
+
+
+bool ha_partition::prepare_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+{
+ uint index= 0;
+ bool error= false;
+ ha_partition_inplace_ctx *part_inplace_ctx;
+
+ DBUG_ENTER("ha_partition::prepare_inplace_alter_table");
+
+ /*
+ Changing to similar partitioning, only update metadata.
+ Non allowed changes would be catched in prep_alter_part_table().
+ */
+ if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION)
+ DBUG_RETURN(false);
+
+ part_inplace_ctx=
+ static_cast<class ha_partition_inplace_ctx*>(ha_alter_info->handler_ctx);
+
+ for (index= 0; index < m_tot_parts && !error; index++)
+ {
+ ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[index];
+ if (m_file[index]->ha_prepare_inplace_alter_table(altered_table,
+ ha_alter_info))
+ error= true;
+ part_inplace_ctx->handler_ctx_array[index]= ha_alter_info->handler_ctx;
+ }
+ ha_alter_info->handler_ctx= part_inplace_ctx;
+
+ DBUG_RETURN(error);
+}
+
+
+bool ha_partition::inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+{
+ uint index= 0;
+ bool error= false;
+ ha_partition_inplace_ctx *part_inplace_ctx;
+
+ DBUG_ENTER("ha_partition::inplace_alter_table");
+
+ /*
+ Changing to similar partitioning, only update metadata.
+ Non allowed changes would be catched in prep_alter_part_table().
+ */
+ if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION)
+ DBUG_RETURN(false);
+
+ part_inplace_ctx=
+ static_cast<class ha_partition_inplace_ctx*>(ha_alter_info->handler_ctx);
+
+ for (index= 0; index < m_tot_parts && !error; index++)
+ {
+ ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[index];
+ if (m_file[index]->ha_inplace_alter_table(altered_table,
+ ha_alter_info))
+ error= true;
+ part_inplace_ctx->handler_ctx_array[index]= ha_alter_info->handler_ctx;
+ }
+ ha_alter_info->handler_ctx= part_inplace_ctx;
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Note that this function will try rollback failed ADD INDEX by
+ executing DROP INDEX for the indexes that were committed (if any)
+ before the error occured. This means that the underlying storage
+ engine must be able to drop index in-place with X-lock held.
+ (As X-lock will be held here if new indexes are to be committed)
+*/
+bool ha_partition::commit_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info,
+ bool commit)
+{
+ ha_partition_inplace_ctx *part_inplace_ctx;
+ bool error= false;
+
+ DBUG_ENTER("ha_partition::commit_inplace_alter_table");
+
+ /*
+ Changing to similar partitioning, only update metadata.
+ Non allowed changes would be catched in prep_alter_part_table().
+ */
+ if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION)
+ DBUG_RETURN(false);
+
+ part_inplace_ctx=
+ static_cast<class ha_partition_inplace_ctx*>(ha_alter_info->handler_ctx);
+
+ if (commit)
+ {
+ DBUG_ASSERT(ha_alter_info->group_commit_ctx ==
+ part_inplace_ctx->handler_ctx_array);
+ ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[0];
+ error= m_file[0]->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info, commit);
+ if (error)
+ goto end;
+ if (ha_alter_info->group_commit_ctx)
+ {
+ /*
+ If ha_alter_info->group_commit_ctx is not set to NULL,
+ then the engine did only commit the first partition!
+ The engine is probably new, since both innodb and the default
+ implementation of handler::commit_inplace_alter_table sets it to NULL
+ and simply return false, since it allows metadata changes only.
+ Loop over all other partitions as to follow the protocol!
+ */
+ uint i;
+ DBUG_ASSERT(0);
+ for (i= 1; i < m_tot_parts; i++)
+ {
+ ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[i];
+ error|= m_file[i]->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info,
+ true);
+ }
+ }
+ }
+ else
+ {
+ uint i;
+ for (i= 0; i < m_tot_parts; i++)
+ {
+ /* Rollback, commit == false, is done for each partition! */
+ ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[i];
+ if (m_file[i]->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info, false))
+ error= true;
+ }
+ }
+end:
+ ha_alter_info->handler_ctx= part_inplace_ctx;
+
+ DBUG_RETURN(error);
+}
+
+
+void ha_partition::notify_table_changed()
+{
+ handler **file;
+
+ DBUG_ENTER("ha_partition::notify_table_changed");
+
+ for (file= m_file; *file; file++)
+ (*file)->ha_notify_table_changed();
+
+ DBUG_VOID_RETURN;
+}
+
+
+uint ha_partition::min_of_the_max_uint(
+ uint (handler::*operator_func)(void) const) const
+{
+ handler **file;
+ uint min_of_the_max= ((*m_file)->*operator_func)();
+
+ for (file= m_file+1; *file; file++)
+ {
+ uint tmp= ((*file)->*operator_func)();
+ set_if_smaller(min_of_the_max, tmp);
+ }
+ return min_of_the_max;
+}
+
+
+uint ha_partition::max_supported_key_parts() const
+{
+ return min_of_the_max_uint(&handler::max_supported_key_parts);
+}
+
+
+uint ha_partition::max_supported_key_length() const
+{
+ return min_of_the_max_uint(&handler::max_supported_key_length);
+}
+
+
+uint ha_partition::max_supported_key_part_length() const
+{
+ return min_of_the_max_uint(&handler::max_supported_key_part_length);
+}
+
+
+uint ha_partition::max_supported_record_length() const
+{
+ return min_of_the_max_uint(&handler::max_supported_record_length);
+}
+
+
+uint ha_partition::max_supported_keys() const
+{
+ return min_of_the_max_uint(&handler::max_supported_keys);
+}
+
+
+uint ha_partition::extra_rec_buf_length() const
+{
+ handler **file;
+ uint max= (*m_file)->extra_rec_buf_length();
+
+ for (file= m_file, file++; *file; file++)
+ if (max < (*file)->extra_rec_buf_length())
+ max= (*file)->extra_rec_buf_length();
+ return max;
+}
+
+
+uint ha_partition::min_record_length(uint options) const
+{
+ handler **file;
+ uint max= (*m_file)->min_record_length(options);
+
+ for (file= m_file, file++; *file; file++)
+ if (max < (*file)->min_record_length(options))
+ max= (*file)->min_record_length(options);
+ return max;
+}
+
+
+/****************************************************************************
+ MODULE compare records
+****************************************************************************/
+/*
+ Compare two positions
+
+ SYNOPSIS
+ cmp_ref()
+ ref1 First position
+ ref2 Second position
+
+ RETURN VALUE
+ <0 ref1 < ref2
+ 0 Equal
+ >0 ref1 > ref2
+
+ DESCRIPTION
+ We get two references and need to check if those records are the same.
+ If they belong to different partitions we decide that they are not
+ the same record. Otherwise we use the particular handler to decide if
+ they are the same. Sort in partition id order if not equal.
+
+ MariaDB note:
+ Please don't merge the code from MySQL that does this:
+
+ We get two references and need to check if those records are the same.
+ If they belong to different partitions we decide that they are not
+ the same record. Otherwise we use the particular handler to decide if
+ they are the same. Sort in partition id order if not equal.
+
+ It is incorrect, MariaDB has an alternative fix.
+*/
+
+int ha_partition::cmp_ref(const uchar *ref1, const uchar *ref2)
+{
+ int cmp;
+ my_ptrdiff_t diff1, diff2;
+ DBUG_ENTER("ha_partition::cmp_ref");
+
+ cmp = m_file[0]->cmp_ref((ref1 + PARTITION_BYTES_IN_POS),
+ (ref2 + PARTITION_BYTES_IN_POS));
+ if (cmp)
+ DBUG_RETURN(cmp);
+
+ if ((ref1[0] == ref2[0]) && (ref1[1] == ref2[1]))
+ {
+ /* This means that the references are same and are in same partition.*/
+ DBUG_RETURN(0);
+ }
+
+ /*
+ In Innodb we compare with either primary key value or global DB_ROW_ID so
+ it is not possible that the two references are equal and are in different
+ partitions, but in myisam it is possible since we are comparing offsets.
+ Remove this assert if DB_ROW_ID is changed to be per partition.
+ */
+ DBUG_ASSERT(!m_innodb);
+
+ diff1= ref2[1] - ref1[1];
+ diff2= ref2[0] - ref1[0];
+ if (diff1 > 0)
+ {
+ DBUG_RETURN(-1);
+ }
+ if (diff1 < 0)
+ {
+ DBUG_RETURN(+1);
+ }
+ if (diff2 > 0)
+ {
+ DBUG_RETURN(-1);
+ }
+ DBUG_RETURN(+1);
+}
+
+
+/****************************************************************************
+ MODULE auto increment
+****************************************************************************/
+
+
+int ha_partition::reset_auto_increment(ulonglong value)
+{
+ handler **file= m_file;
+ int res;
+ DBUG_ENTER("ha_partition::reset_auto_increment");
+ lock_auto_increment();
+ part_share->auto_inc_initialized= false;
+ part_share->next_auto_inc_val= 0;
+ do
+ {
+ if ((res= (*file)->ha_reset_auto_increment(value)) != 0)
+ break;
+ } while (*(++file));
+ unlock_auto_increment();
+ DBUG_RETURN(res);
+}
+
+
+/**
+ This method is called by update_auto_increment which in turn is called
+ by the individual handlers as part of write_row. We use the
+ part_share->next_auto_inc_val, or search all
+ partitions for the highest auto_increment_value if not initialized or
+ if auto_increment field is a secondary part of a key, we must search
+ every partition when holding a mutex to be sure of correctness.
+*/
+
+void ha_partition::get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *first_value,
+ ulonglong *nb_reserved_values)
+{
+ DBUG_ENTER("ha_partition::get_auto_increment");
+ DBUG_PRINT("info", ("offset: %lu inc: %lu desired_values: %lu "
+ "first_value: %lu", (ulong) offset, (ulong) increment,
+ (ulong) nb_desired_values, (ulong) *first_value));
+ DBUG_ASSERT(increment && nb_desired_values);
+ *first_value= 0;
+ if (table->s->next_number_keypart)
+ {
+ /*
+ next_number_keypart is != 0 if the auto_increment column is a secondary
+ column in the index (it is allowed in MyISAM)
+ */
+ DBUG_PRINT("info", ("next_number_keypart != 0"));
+ ulonglong nb_reserved_values_part;
+ ulonglong first_value_part, max_first_value;
+ handler **file= m_file;
+ first_value_part= max_first_value= *first_value;
+ /* Must find highest value among all partitions. */
+ do
+ {
+ /* Only nb_desired_values = 1 makes sense */
+ (*file)->get_auto_increment(offset, increment, 1,
+ &first_value_part, &nb_reserved_values_part);
+ if (first_value_part == ULONGLONG_MAX) // error in one partition
+ {
+ *first_value= first_value_part;
+ /* log that the error was between table/partition handler */
+ sql_print_error("Partition failed to reserve auto_increment value");
+ DBUG_VOID_RETURN;
+ }
+ DBUG_PRINT("info", ("first_value_part: %lu", (ulong) first_value_part));
+ set_if_bigger(max_first_value, first_value_part);
+ } while (*(++file));
+ *first_value= max_first_value;
+ *nb_reserved_values= 1;
+ }
+ else
+ {
+ THD *thd= ha_thd();
+ /*
+ This is initialized in the beginning of the first write_row call.
+ */
+ DBUG_ASSERT(part_share->auto_inc_initialized);
+ /*
+ Get a lock for handling the auto_increment in part_share
+ for avoiding two concurrent statements getting the same number.
+ */
+
+ lock_auto_increment();
+
+ /*
+ In a multi-row insert statement like INSERT SELECT and LOAD DATA
+ where the number of candidate rows to insert is not known in advance
+ we must hold a lock/mutex for the whole statement if we have statement
+ based replication. Because the statement-based binary log contains
+ only the first generated value used by the statement, and slaves assumes
+ all other generated values used by this statement were consecutive to
+ this first one, we must exclusively lock the generator until the statement
+ is done.
+ */
+ if (!auto_increment_safe_stmt_log_lock &&
+ thd->lex->sql_command != SQLCOM_INSERT &&
+ mysql_bin_log.is_open() &&
+ !thd->is_current_stmt_binlog_format_row() &&
+ (thd->variables.option_bits & OPTION_BIN_LOG))
+ {
+ DBUG_PRINT("info", ("locking auto_increment_safe_stmt_log_lock"));
+ auto_increment_safe_stmt_log_lock= TRUE;
+ }
+
+ /* this gets corrected (for offset/increment) in update_auto_increment */
+ *first_value= part_share->next_auto_inc_val;
+ part_share->next_auto_inc_val+= nb_desired_values * increment;
+
+ unlock_auto_increment();
+ DBUG_PRINT("info", ("*first_value: %lu", (ulong) *first_value));
+ *nb_reserved_values= nb_desired_values;
+ }
+ DBUG_VOID_RETURN;
+}
+
+void ha_partition::release_auto_increment()
+{
+ DBUG_ENTER("ha_partition::release_auto_increment");
+
+ if (table->s->next_number_keypart)
+ {
+ uint i;
+ for (i= bitmap_get_first_set(&m_part_info->lock_partitions);
+ i < m_tot_parts;
+ i= bitmap_get_next_set(&m_part_info->lock_partitions, i))
+ {
+ m_file[i]->ha_release_auto_increment();
+ }
+ }
+ else if (next_insert_id)
+ {
+ ulonglong next_auto_inc_val;
+ lock_auto_increment();
+ next_auto_inc_val= part_share->next_auto_inc_val;
+ /*
+ If the current auto_increment values is lower than the reserved
+ value, and the reserved value was reserved by this thread,
+ we can lower the reserved value.
+ */
+ if (next_insert_id < next_auto_inc_val &&
+ auto_inc_interval_for_cur_row.maximum() >= next_auto_inc_val)
+ {
+ THD *thd= ha_thd();
+ /*
+ Check that we do not lower the value because of a failed insert
+ with SET INSERT_ID, i.e. forced/non generated values.
+ */
+ if (thd->auto_inc_intervals_forced.maximum() < next_insert_id)
+ part_share->next_auto_inc_val= next_insert_id;
+ }
+ DBUG_PRINT("info", ("part_share->next_auto_inc_val: %lu",
+ (ulong) part_share->next_auto_inc_val));
+
+ /* Unlock the multi row statement lock taken in get_auto_increment */
+ if (auto_increment_safe_stmt_log_lock)
+ {
+ auto_increment_safe_stmt_log_lock= FALSE;
+ DBUG_PRINT("info", ("unlocking auto_increment_safe_stmt_log_lock"));
+ }
+
+ unlock_auto_increment();
+ }
+ DBUG_VOID_RETURN;
+}
+
+/****************************************************************************
+ MODULE initialize handler for HANDLER call
+****************************************************************************/
+
+void ha_partition::init_table_handle_for_HANDLER()
+{
+ return;
+}
+
+
+/**
+ Return the checksum of the table (all partitions)
+*/
+
+uint ha_partition::checksum() const
+{
+ ha_checksum sum= 0;
+
+ DBUG_ENTER("ha_partition::checksum");
+ if ((table_flags() & (HA_HAS_OLD_CHECKSUM | HA_HAS_NEW_CHECKSUM)))
+ {
+ handler **file= m_file;
+ do
+ {
+ sum+= (*file)->checksum();
+ } while (*(++file));
+ }
+ DBUG_RETURN(sum);
+}
+
+
+/****************************************************************************
+ MODULE enable/disable indexes
+****************************************************************************/
+
+/*
+ Disable indexes for a while
+ SYNOPSIS
+ disable_indexes()
+ mode Mode
+ RETURN VALUES
+ 0 Success
+ != 0 Error
+*/
+
+int ha_partition::disable_indexes(uint mode)
+{
+ handler **file;
+ int error= 0;
+
+ DBUG_ASSERT(bitmap_is_set_all(&(m_part_info->lock_partitions)));
+ for (file= m_file; *file; file++)
+ {
+ if ((error= (*file)->ha_disable_indexes(mode)))
+ break;
+ }
+ return error;
+}
+
+
+/*
+ Enable indexes again
+ SYNOPSIS
+ enable_indexes()
+ mode Mode
+ RETURN VALUES
+ 0 Success
+ != 0 Error
+*/
+
+int ha_partition::enable_indexes(uint mode)
+{
+ handler **file;
+ int error= 0;
+
+ DBUG_ASSERT(bitmap_is_set_all(&(m_part_info->lock_partitions)));
+ for (file= m_file; *file; file++)
+ {
+ if ((error= (*file)->ha_enable_indexes(mode)))
+ break;
+ }
+ return error;
+}
+
+
+/*
+ Check if indexes are disabled
+ SYNOPSIS
+ indexes_are_disabled()
+
+ RETURN VALUES
+ 0 Indexes are enabled
+ != 0 Indexes are disabled
+*/
+
+int ha_partition::indexes_are_disabled(void)
+{
+ handler **file;
+ int error= 0;
+
+ DBUG_ASSERT(bitmap_is_set_all(&(m_part_info->lock_partitions)));
+ for (file= m_file; *file; file++)
+ {
+ if ((error= (*file)->indexes_are_disabled()))
+ break;
+ }
+ return error;
+}
+
+
+/**
+ Check/fix misplaced rows.
+
+ @param read_part_id Partition to check/fix.
+ @param repair If true, move misplaced rows to correct partition.
+
+ @return Operation status.
+ @retval 0 Success
+ @retval != 0 Error
+*/
+
+int ha_partition::check_misplaced_rows(uint read_part_id, bool repair)
+{
+ int result= 0;
+ uint32 correct_part_id;
+ longlong func_value;
+ longlong num_misplaced_rows= 0;
+
+ DBUG_ENTER("ha_partition::check_misplaced_rows");
+
+ DBUG_ASSERT(m_file);
+
+ if (repair)
+ {
+ /* We must read the full row, if we need to move it! */
+ bitmap_set_all(table->read_set);
+ bitmap_set_all(table->write_set);
+ }
+ else
+ {
+ /* Only need to read the partitioning fields. */
+ bitmap_union(table->read_set, &m_part_info->full_part_field_set);
+ }
+
+ if ((result= m_file[read_part_id]->ha_rnd_init(1)))
+ DBUG_RETURN(result);
+
+ while (true)
+ {
+ if ((result= m_file[read_part_id]->ha_rnd_next(m_rec0)))
+ {
+ if (result == HA_ERR_RECORD_DELETED)
+ continue;
+ if (result != HA_ERR_END_OF_FILE)
+ break;
+
+ if (num_misplaced_rows > 0)
+ {
+ print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, "warning",
+ table_share->db.str, table->alias,
+ opt_op_name[REPAIR_PARTS],
+ "Moved %lld misplaced rows",
+ num_misplaced_rows);
+ }
+ /* End-of-file reached, all rows are now OK, reset result and break. */
+ result= 0;
+ break;
+ }
+
+ result= m_part_info->get_partition_id(m_part_info, &correct_part_id,
+ &func_value);
+ if (result)
+ break;
+
+ if (correct_part_id != read_part_id)
+ {
+ num_misplaced_rows++;
+ if (!repair)
+ {
+ /* Check. */
+ print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, "error",
+ table_share->db.str, table->alias,
+ opt_op_name[CHECK_PARTS],
+ "Found a misplaced row");
+ /* Break on first misplaced row! */
+ result= HA_ADMIN_NEEDS_UPGRADE;
+ break;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Moving row from partition %d to %d",
+ read_part_id, correct_part_id));
+
+ /*
+ Insert row into correct partition. Notice that there are no commit
+ for every N row, so the repair will be one large transaction!
+ */
+ if ((result= m_file[correct_part_id]->ha_write_row(m_rec0)))
+ {
+ /*
+ We have failed to insert a row, it might have been a duplicate!
+ */
+ char buf[MAX_KEY_LENGTH];
+ String str(buf,sizeof(buf),system_charset_info);
+ str.length(0);
+ if (result == HA_ERR_FOUND_DUPP_KEY)
+ {
+ str.append("Duplicate key found, "
+ "please update or delete the record:\n");
+ result= HA_ADMIN_CORRUPT;
+ }
+ m_err_rec= NULL;
+ append_row_to_str(str);
+
+ /*
+ If the engine supports transactions, the failure will be
+ rollbacked.
+ */
+ if (!m_file[correct_part_id]->has_transactions())
+ {
+ /* Log this error, so the DBA can notice it and fix it! */
+ sql_print_error("Table '%-192s' failed to move/insert a row"
+ " from part %d into part %d:\n%s",
+ table->s->table_name.str,
+ read_part_id,
+ correct_part_id,
+ str.c_ptr_safe());
+ }
+ print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, "error",
+ table_share->db.str, table->alias,
+ opt_op_name[REPAIR_PARTS],
+ "Failed to move/insert a row"
+ " from part %d into part %d:\n%s",
+ read_part_id,
+ correct_part_id,
+ str.c_ptr_safe());
+ break;
+ }
+
+ /* Delete row from wrong partition. */
+ if ((result= m_file[read_part_id]->ha_delete_row(m_rec0)))
+ {
+ if (m_file[correct_part_id]->has_transactions())
+ break;
+ /*
+ We have introduced a duplicate, since we failed to remove it
+ from the wrong partition.
+ */
+ char buf[MAX_KEY_LENGTH];
+ String str(buf,sizeof(buf),system_charset_info);
+ str.length(0);
+ m_err_rec= NULL;
+ append_row_to_str(str);
+
+ /* Log this error, so the DBA can notice it and fix it! */
+ sql_print_error("Table '%-192s': Delete from part %d failed with"
+ " error %d. But it was already inserted into"
+ " part %d, when moving the misplaced row!"
+ "\nPlease manually fix the duplicate row:\n%s",
+ table->s->table_name.str,
+ read_part_id,
+ result,
+ correct_part_id,
+ str.c_ptr_safe());
+ break;
+ }
+ }
+ }
+ }
+
+ int tmp_result= m_file[read_part_id]->ha_rnd_end();
+ DBUG_RETURN(result ? result : tmp_result);
+}
+
+
+#define KEY_PARTITIONING_CHANGED_STR \
+ "KEY () partitioning changed, please run:\n" \
+ "ALTER TABLE %s.%s ALGORITHM = INPLACE %s"
+
+int ha_partition::check_for_upgrade(HA_CHECK_OPT *check_opt)
+{
+ int error= HA_ADMIN_NEEDS_CHECK;
+ DBUG_ENTER("ha_partition::check_for_upgrade");
+
+ /*
+ This is called even without FOR UPGRADE,
+ if the .frm version is lower than the current version.
+ In that case return that it needs checking!
+ */
+ if (!(check_opt->sql_flags & TT_FOR_UPGRADE))
+ DBUG_RETURN(error);
+
+ /*
+ Partitions will be checked for during their ha_check!
+
+ Check if KEY (sub)partitioning was used and any field's hash calculation
+ differs from 5.1, see bug#14521864.
+ */
+ if (table->s->mysql_version < 50503 && // 5.1 table (<5.5.3)
+ ((m_part_info->part_type == HASH_PARTITION && // KEY partitioned
+ m_part_info->list_of_part_fields) ||
+ (m_is_sub_partitioned && // KEY subpartitioned
+ m_part_info->list_of_subpart_fields)))
+ {
+ Field **field;
+ if (m_is_sub_partitioned)
+ {
+ field= m_part_info->subpart_field_array;
+ }
+ else
+ {
+ field= m_part_info->part_field_array;
+ }
+ for (; *field; field++)
+ {
+ switch ((*field)->real_type()) {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ {
+ THD *thd= ha_thd();
+ char *part_buf;
+ String db_name, table_name;
+ uint part_buf_len;
+ bool skip_generation= false;
+ partition_info::enum_key_algorithm old_algorithm;
+ old_algorithm= m_part_info->key_algorithm;
+ error= HA_ADMIN_FAILED;
+ append_identifier(ha_thd(), &db_name, table_share->db.str,
+ table_share->db.length);
+ append_identifier(ha_thd(), &table_name, table_share->table_name.str,
+ table_share->table_name.length);
+ if (m_part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE)
+ {
+ /*
+ Only possible when someone tampered with .frm files,
+ like during tests :)
+ */
+ skip_generation= true;
+ }
+ m_part_info->key_algorithm= partition_info::KEY_ALGORITHM_51;
+ if (skip_generation ||
+ !(part_buf= generate_partition_syntax(m_part_info,
+ &part_buf_len,
+ true,
+ true,
+ NULL,
+ NULL,
+ NULL)) ||
+ print_admin_msg(thd, SQL_ADMIN_MSG_TEXT_SIZE + 1, "error",
+ table_share->db.str,
+ table->alias,
+ opt_op_name[CHECK_PARTS],
+ KEY_PARTITIONING_CHANGED_STR,
+ db_name.c_ptr_safe(),
+ table_name.c_ptr_safe(),
+ part_buf))
+ {
+ /* Error creating admin message (too long string?). */
+ print_admin_msg(thd, MYSQL_ERRMSG_SIZE, "error",
+ table_share->db.str, table->alias,
+ opt_op_name[CHECK_PARTS],
+ KEY_PARTITIONING_CHANGED_STR,
+ db_name.c_ptr_safe(), table_name.c_ptr_safe(),
+ "<old partition clause>, but add ALGORITHM = 1"
+ " between 'KEY' and '(' to change the metadata"
+ " without the need of a full table rebuild.");
+ }
+ m_part_info->key_algorithm= old_algorithm;
+ DBUG_RETURN(error);
+ }
+ default:
+ /* Not affected! */
+ ;
+ }
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+struct st_mysql_storage_engine partition_storage_engine=
+{ MYSQL_HANDLERTON_INTERFACE_VERSION };
+
+maria_declare_plugin(partition)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &partition_storage_engine,
+ "partition",
+ "Mikael Ronstrom, MySQL AB",
+ "Partition Storage Engine Helper",
+ PLUGIN_LICENSE_GPL,
+ partition_initialize, /* Plugin Init */
+ NULL, /* Plugin Deinit */
+ 0x0100, /* 1.0 */
+ NULL, /* status variables */
+ NULL, /* system variables */
+ "1.0", /* string version */
+ MariaDB_PLUGIN_MATURITY_STABLE /* maturity */
+}
+maria_declare_plugin_end;
+
+#endif
diff --git a/sql/ha_partition.h b/sql/ha_partition.h
new file mode 100644
index 00000000000..71ae84b06a0
--- /dev/null
+++ b/sql/ha_partition.h
@@ -0,0 +1,1290 @@
+#ifndef HA_PARTITION_INCLUDED
+#define HA_PARTITION_INCLUDED
+
+/*
+ Copyright (c) 2005, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab & SkySQL Ab.
+
+ 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 "sql_partition.h" /* part_id_range, partition_element */
+#include "queues.h" /* QUEUE */
+
+enum partition_keywords
+{
+ PKW_HASH= 0, PKW_RANGE, PKW_LIST, PKW_KEY, PKW_MAXVALUE, PKW_LINEAR,
+ PKW_COLUMNS, PKW_ALGORITHM
+};
+
+
+#define PARTITION_BYTES_IN_POS 2
+
+
+/** Struct used for partition_name_hash */
+typedef struct st_part_name_def
+{
+ uchar *partition_name;
+ uint length;
+ uint32 part_id;
+ my_bool is_subpart;
+} PART_NAME_DEF;
+
+/** class where to save partitions Handler_share's */
+class Parts_share_refs
+{
+public:
+ uint num_parts; /**< Size of ha_share array */
+ Handler_share **ha_shares; /**< Storage for each part */
+ Parts_share_refs()
+ {
+ num_parts= 0;
+ ha_shares= NULL;
+ }
+ ~Parts_share_refs()
+ {
+ uint i;
+ for (i= 0; i < num_parts; i++)
+ if (ha_shares[i])
+ delete ha_shares[i];
+ if (ha_shares)
+ delete [] ha_shares;
+ }
+ bool init(uint arg_num_parts)
+ {
+ DBUG_ASSERT(!num_parts && !ha_shares);
+ num_parts= arg_num_parts;
+ /* Allocate an array of Handler_share pointers */
+ ha_shares= new Handler_share *[num_parts];
+ if (!ha_shares)
+ {
+ num_parts= 0;
+ return true;
+ }
+ memset(ha_shares, 0, sizeof(Handler_share*) * num_parts);
+ return false;
+ }
+};
+
+
+/**
+ Partition specific Handler_share.
+*/
+class Partition_share : public Handler_share
+{
+public:
+ bool auto_inc_initialized;
+ mysql_mutex_t auto_inc_mutex; /**< protecting auto_inc val */
+ ulonglong next_auto_inc_val; /**< first non reserved value */
+ /**
+ Hash of partition names. Initialized in the first ha_partition::open()
+ for the table_share. After that it is read-only, i.e. no locking required.
+ */
+ bool partition_name_hash_initialized;
+ HASH partition_name_hash;
+ /** Storage for each partitions Handler_share */
+ Parts_share_refs *partitions_share_refs;
+ Partition_share() {}
+ ~Partition_share()
+ {
+ DBUG_ENTER("Partition_share::~Partition_share");
+ mysql_mutex_destroy(&auto_inc_mutex);
+ if (partition_name_hash_initialized)
+ my_hash_free(&partition_name_hash);
+ if (partitions_share_refs)
+ delete partitions_share_refs;
+ DBUG_VOID_RETURN;
+ }
+ bool init(uint num_parts);
+ void lock_auto_inc()
+ {
+ mysql_mutex_lock(&auto_inc_mutex);
+ }
+ void unlock_auto_inc()
+ {
+ mysql_mutex_unlock(&auto_inc_mutex);
+ }
+};
+
+
+extern "C" int cmp_key_rowid_part_id(void *ptr, uchar *ref1, uchar *ref2);
+
+class ha_partition :public handler
+{
+private:
+ enum partition_index_scan_type
+ {
+ partition_index_read= 0,
+ partition_index_first= 1,
+ partition_index_first_unordered= 2,
+ partition_index_last= 3,
+ partition_index_read_last= 4,
+ partition_read_range = 5,
+ partition_no_index_scan= 6
+ };
+ /* Data for the partition handler */
+ int m_mode; // Open mode
+ uint m_open_test_lock; // Open test_if_locked
+ uchar *m_file_buffer; // Content of the .par file
+ char *m_name_buffer_ptr; // Pointer to first partition name
+ MEM_ROOT m_mem_root;
+ plugin_ref *m_engine_array; // Array of types of the handlers
+ handler **m_file; // Array of references to handler inst.
+ uint m_file_tot_parts; // Debug
+ handler **m_new_file; // Array of references to new handlers
+ handler **m_reorged_file; // Reorganised partitions
+ handler **m_added_file; // Added parts kept for errors
+ LEX_STRING *m_connect_string;
+ partition_info *m_part_info; // local reference to partition
+ Field **m_part_field_array; // Part field array locally to save acc
+ uchar *m_ordered_rec_buffer; // Row and key buffer for ord. idx scan
+ /*
+ Current index.
+ When used in key_rec_cmp: If clustered pk, index compare
+ must compare pk if given index is same for two rows.
+ So normally m_curr_key_info[0]= current index and m_curr_key[1]= NULL,
+ and if clustered pk, [0]= current index, [1]= pk, [2]= NULL
+ */
+ KEY *m_curr_key_info[3]; // Current index
+ uchar *m_rec0; // table->record[0]
+ const uchar *m_err_rec; // record which gave error
+ QUEUE m_queue; // Prio queue used by sorted read
+
+ /*
+ Length of an element in m_ordered_rec_buffer. The elements are composed of
+
+ [part_no] [table->record copy] [underlying_table_rowid]
+
+ underlying_table_rowid is only stored when the table has no extended keys.
+ */
+ uint m_priority_queue_rec_len;
+
+ /*
+ If true, then sorting records by key value also sorts them by their
+ underlying_table_rowid.
+ */
+ bool m_using_extended_keys;
+
+ /*
+ Since the partition handler is a handler on top of other handlers, it
+ is necessary to keep information about what the underlying handler
+ characteristics is. It is not possible to keep any handler instances
+ for this since the MySQL Server sometimes allocating the handler object
+ without freeing them.
+ */
+ enum enum_handler_status
+ {
+ handler_not_initialized= 0,
+ handler_initialized,
+ handler_opened,
+ handler_closed
+ };
+ enum_handler_status m_handler_status;
+
+ uint m_reorged_parts; // Number of reorganised parts
+ uint m_tot_parts; // Total number of partitions;
+ uint m_num_locks; // For engines like ha_blackhole, which needs no locks
+ uint m_last_part; // Last file that we update,write,read
+ part_id_range m_part_spec; // Which parts to scan
+ uint m_scan_value; // Value passed in rnd_init
+ // call
+ uint m_ref_length; // Length of position in this
+ // handler object
+ key_range m_start_key; // index read key range
+ enum partition_index_scan_type m_index_scan_type;// What type of index
+ // scan
+ uint m_top_entry; // Which partition is to
+ // deliver next result
+ uint m_rec_length; // Local copy of record length
+
+ bool m_ordered; // Ordered/Unordered index scan
+ bool m_pkey_is_clustered; // Is primary key clustered
+ bool m_create_handler; // Handler used to create table
+ bool m_is_sub_partitioned; // Is subpartitioned
+ bool m_ordered_scan_ongoing;
+
+ /*
+ If set, this object was created with ha_partition::clone and doesn't
+ "own" the m_part_info structure.
+ */
+ ha_partition *m_is_clone_of;
+ MEM_ROOT *m_clone_mem_root;
+
+ /*
+ We keep track if all underlying handlers are MyISAM since MyISAM has a
+ great number of extra flags not needed by other handlers.
+ */
+ bool m_myisam; // Are all underlying handlers
+ // MyISAM
+ /*
+ We keep track of InnoDB handlers below since it requires proper setting
+ of query_id in fields at index_init and index_read calls.
+ */
+ bool m_innodb; // Are all underlying handlers
+ // InnoDB
+ /*
+ When calling extra(HA_EXTRA_CACHE) we do not pass this to the underlying
+ handlers immediately. Instead we cache it and call the underlying
+ immediately before starting the scan on the partition. This is to
+ prevent allocating a READ CACHE for each partition in parallel when
+ performing a full table scan on MyISAM partitioned table.
+ This state is cleared by extra(HA_EXTRA_NO_CACHE).
+ */
+ bool m_extra_cache;
+ uint m_extra_cache_size;
+ /* The same goes for HA_EXTRA_PREPARE_FOR_UPDATE */
+ bool m_extra_prepare_for_update;
+ /* Which partition has active cache */
+ uint m_extra_cache_part_id;
+
+ void init_handler_variables();
+ /*
+ Variables for lock structures.
+ */
+ THR_LOCK_DATA lock; /* MySQL lock */
+
+ bool auto_increment_lock; /**< lock reading/updating auto_inc */
+ /**
+ Flag to keep the auto_increment lock through out the statement.
+ This to ensure it will work with statement based replication.
+ */
+ bool auto_increment_safe_stmt_log_lock;
+ /** For optimizing ha_start_bulk_insert calls */
+ MY_BITMAP m_bulk_insert_started;
+ ha_rows m_bulk_inserted_rows;
+ /** used for prediction of start_bulk_insert rows */
+ enum_monotonicity_info m_part_func_monotonicity_info;
+ /** keep track of locked partitions */
+ MY_BITMAP m_locked_partitions;
+ /** Stores shared auto_increment etc. */
+ Partition_share *part_share;
+ /** Temporary storage for new partitions Handler_shares during ALTER */
+ List<Parts_share_refs> m_new_partitions_share_refs;
+ /** Sorted array of partition ids in descending order of number of rows. */
+ uint32 *m_part_ids_sorted_by_num_of_records;
+ /* Compare function for my_qsort2, for reversed order. */
+ static int compare_number_of_records(ha_partition *me,
+ const uint32 *a,
+ const uint32 *b);
+ /** keep track of partitions to call ha_reset */
+ MY_BITMAP m_partitions_to_reset;
+ /** partitions that returned HA_ERR_KEY_NOT_FOUND. */
+ MY_BITMAP m_key_not_found_partitions;
+ bool m_key_not_found;
+public:
+ Partition_share *get_part_share() { return part_share; }
+ handler *clone(const char *name, MEM_ROOT *mem_root);
+ virtual void set_part_info(partition_info *part_info)
+ {
+ m_part_info= part_info;
+ m_is_sub_partitioned= part_info->is_sub_partitioned();
+ }
+ /*
+ -------------------------------------------------------------------------
+ MODULE create/delete handler object
+ -------------------------------------------------------------------------
+ Object create/delete methode. The normal called when a table object
+ exists. There is also a method to create the handler object with only
+ partition information. This is used from mysql_create_table when the
+ table is to be created and the engine type is deduced to be the
+ partition handler.
+ -------------------------------------------------------------------------
+ */
+ ha_partition(handlerton *hton, TABLE_SHARE * table);
+ ha_partition(handlerton *hton, partition_info * part_info);
+ ha_partition(handlerton *hton, TABLE_SHARE *share,
+ partition_info *part_info_arg,
+ ha_partition *clone_arg,
+ MEM_ROOT *clone_mem_root_arg);
+ ~ha_partition();
+ /*
+ A partition handler has no characteristics in itself. It only inherits
+ those from the underlying handlers. Here we set-up those constants to
+ enable later calls of the methods to retrieve constants from the under-
+ lying handlers. Returns false if not successful.
+ */
+ bool initialize_partition(MEM_ROOT *mem_root);
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE meta data changes
+ -------------------------------------------------------------------------
+ Meta data routines to CREATE, DROP, RENAME table and often used at
+ ALTER TABLE (update_create_info used from ALTER TABLE and SHOW ..).
+
+ update_table_comment is used in SHOW TABLE commands to provide a
+ chance for the handler to add any interesting comments to the table
+ comments not provided by the users comment.
+
+ create_partitioning_metadata is called before opening a new handler object
+ with openfrm to call create. It is used to create any local handler
+ object needed in opening the object in openfrm
+ -------------------------------------------------------------------------
+ */
+ virtual int delete_table(const char *from);
+ virtual int rename_table(const char *from, const char *to);
+ virtual int create(const char *name, TABLE *form,
+ HA_CREATE_INFO *create_info);
+ virtual int create_partitioning_metadata(const char *name,
+ const char *old_name, int action_flag);
+ virtual void update_create_info(HA_CREATE_INFO *create_info);
+ virtual char *update_table_comment(const char *comment);
+ virtual int change_partitions(HA_CREATE_INFO *create_info,
+ const char *path,
+ ulonglong * const copied,
+ ulonglong * const deleted,
+ const uchar *pack_frm_data,
+ size_t pack_frm_len);
+ virtual int drop_partitions(const char *path);
+ virtual int rename_partitions(const char *path);
+ bool get_no_parts(const char *name, uint *num_parts)
+ {
+ DBUG_ENTER("ha_partition::get_no_parts");
+ *num_parts= m_tot_parts;
+ DBUG_RETURN(0);
+ }
+ virtual void change_table_ptr(TABLE *table_arg, TABLE_SHARE *share);
+ virtual bool check_if_incompatible_data(HA_CREATE_INFO *create_info,
+ uint table_changes);
+private:
+ int copy_partitions(ulonglong * const copied, ulonglong * const deleted);
+ void cleanup_new_partition(uint part_count);
+ int prepare_new_partition(TABLE *table, HA_CREATE_INFO *create_info,
+ handler *file, const char *part_name,
+ partition_element *p_elem,
+ uint disable_non_uniq_indexes);
+ /*
+ delete_table and rename_table uses very similar logic which
+ is packed into this routine.
+ */
+ uint del_ren_table(const char *from, const char *to);
+ /*
+ One method to create the table_name.par file containing the names of the
+ underlying partitions, their engine and the number of partitions.
+ And one method to read it in.
+ */
+ bool create_handler_file(const char *name);
+ bool setup_engine_array(MEM_ROOT *mem_root);
+ bool read_par_file(const char *name);
+ bool get_from_handler_file(const char *name, MEM_ROOT *mem_root,
+ bool is_clone);
+ bool new_handlers_from_part_info(MEM_ROOT *mem_root);
+ bool create_handlers(MEM_ROOT *mem_root);
+ void clear_handler_file();
+ int set_up_table_before_create(TABLE *table_arg,
+ const char *partition_name_with_path,
+ HA_CREATE_INFO *info,
+ partition_element *p_elem);
+ partition_element *find_partition_element(uint part_id);
+ bool insert_partition_name_in_hash(const char *name, uint part_id,
+ bool is_subpart);
+ bool populate_partition_name_hash();
+ Partition_share *get_share();
+ bool set_ha_share_ref(Handler_share **ha_share);
+ void fix_data_dir(char* path);
+ bool init_partition_bitmaps();
+ void free_partition_bitmaps();
+
+public:
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE open/close object
+ -------------------------------------------------------------------------
+ Open and close handler object to ensure all underlying files and
+ objects allocated and deallocated for query handling is handled
+ properly.
+ -------------------------------------------------------------------------
+
+ A handler object is opened as part of its initialisation and before
+ being used for normal queries (not before meta-data changes always.
+ If the object was opened it will also be closed before being deleted.
+ */
+ virtual int open(const char *name, int mode, uint test_if_locked);
+ virtual int close(void);
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE start/end statement
+ -------------------------------------------------------------------------
+ This module contains methods that are used to understand start/end of
+ statements, transaction boundaries, and aid for proper concurrency
+ control.
+ The partition handler need not implement abort and commit since this
+ will be handled by any underlying handlers implementing transactions.
+ There is only one call to each handler type involved per transaction
+ and these go directly to the handlers supporting transactions
+ currently InnoDB, BDB and NDB).
+ -------------------------------------------------------------------------
+ */
+ virtual THR_LOCK_DATA **store_lock(THD * thd, THR_LOCK_DATA ** to,
+ enum thr_lock_type lock_type);
+ virtual int external_lock(THD * thd, int lock_type);
+ /*
+ When table is locked a statement is started by calling start_stmt
+ instead of external_lock
+ */
+ virtual int start_stmt(THD * thd, thr_lock_type lock_type);
+ /*
+ Lock count is number of locked underlying handlers (I assume)
+ */
+ virtual uint lock_count(void) const;
+ /*
+ Call to unlock rows not to be updated in transaction
+ */
+ virtual void unlock_row();
+ /*
+ Check if semi consistent read
+ */
+ virtual bool was_semi_consistent_read();
+ /*
+ Call to hint about semi consistent read
+ */
+ virtual void try_semi_consistent_read(bool);
+
+ /*
+ NOTE: due to performance and resource issues with many partitions,
+ we only use the m_psi on the ha_partition handler, excluding all
+ partitions m_psi.
+ */
+#ifdef HAVE_M_PSI_PER_PARTITION
+ /*
+ Bind the table/handler thread to track table i/o.
+ */
+ virtual void unbind_psi();
+ virtual void rebind_psi();
+#endif
+ /*
+ -------------------------------------------------------------------------
+ MODULE change record
+ -------------------------------------------------------------------------
+ This part of the handler interface is used to change the records
+ after INSERT, DELETE, UPDATE, REPLACE method calls but also other
+ special meta-data operations as ALTER TABLE, LOAD DATA, TRUNCATE.
+ -------------------------------------------------------------------------
+
+ These methods are used for insert (write_row), update (update_row)
+ and delete (delete_row). All methods to change data always work on
+ one row at a time. update_row and delete_row also contains the old
+ row.
+ delete_all_rows will delete all rows in the table in one call as a
+ special optimisation for DELETE from table;
+
+ Bulk inserts are supported if all underlying handlers support it.
+ start_bulk_insert and end_bulk_insert is called before and after a
+ number of calls to write_row.
+ */
+ virtual int write_row(uchar * buf);
+ virtual int update_row(const uchar * old_data, uchar * new_data);
+ virtual int delete_row(const uchar * buf);
+ virtual int delete_all_rows(void);
+ virtual int truncate();
+ virtual void start_bulk_insert(ha_rows rows, uint flags);
+ virtual int end_bulk_insert();
+private:
+ ha_rows guess_bulk_insert_rows();
+ void start_part_bulk_insert(THD *thd, uint part_id);
+ long estimate_read_buffer_size(long original_size);
+public:
+
+ /*
+ Method for truncating a specific partition.
+ (i.e. ALTER TABLE t1 TRUNCATE PARTITION p).
+
+ @remark This method is a partitioning-specific hook
+ and thus not a member of the general SE API.
+ */
+ int truncate_partition(Alter_info *, bool *binlog_stmt);
+
+ virtual bool is_fatal_error(int error, uint flags)
+ {
+ if (!handler::is_fatal_error(error, flags) ||
+ error == HA_ERR_NO_PARTITION_FOUND ||
+ error == HA_ERR_NOT_IN_LOCK_PARTITIONS)
+ return FALSE;
+ return TRUE;
+ }
+
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE full table scan
+ -------------------------------------------------------------------------
+ This module is used for the most basic access method for any table
+ handler. This is to fetch all data through a full table scan. No
+ indexes are needed to implement this part.
+ It contains one method to start the scan (rnd_init) that can also be
+ called multiple times (typical in a nested loop join). Then proceeding
+ to the next record (rnd_next) and closing the scan (rnd_end).
+ To remember a record for later access there is a method (position)
+ and there is a method used to retrieve the record based on the stored
+ position.
+ The position can be a file position, a primary key, a ROWID dependent
+ on the handler below.
+ -------------------------------------------------------------------------
+ */
+ /*
+ unlike index_init(), rnd_init() can be called two times
+ without rnd_end() in between (it only makes sense if scan=1).
+ then the second call should prepare for the new table scan
+ (e.g if rnd_init allocates the cursor, second call should
+ position it to the start of the table, no need to deallocate
+ and allocate it again
+ */
+ virtual int rnd_init(bool scan);
+ virtual int rnd_end();
+ virtual int rnd_next(uchar * buf);
+ virtual int rnd_pos(uchar * buf, uchar * pos);
+ virtual int rnd_pos_by_record(uchar *record);
+ virtual void position(const uchar * record);
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE index scan
+ -------------------------------------------------------------------------
+ This part of the handler interface is used to perform access through
+ indexes. The interface is defined as a scan interface but the handler
+ can also use key lookup if the index is a unique index or a primary
+ key index.
+ Index scans are mostly useful for SELECT queries but are an important
+ part also of UPDATE, DELETE, REPLACE and CREATE TABLE table AS SELECT
+ and so forth.
+ Naturally an index is needed for an index scan and indexes can either
+ be ordered, hash based. Some ordered indexes can return data in order
+ but not necessarily all of them.
+ There are many flags that define the behavior of indexes in the
+ various handlers. These methods are found in the optimizer module.
+ -------------------------------------------------------------------------
+
+ index_read is called to start a scan of an index. The find_flag defines
+ the semantics of the scan. These flags are defined in
+ include/my_base.h
+ index_read_idx is the same but also initializes index before calling doing
+ the same thing as index_read. Thus it is similar to index_init followed
+ by index_read. This is also how we implement it.
+
+ index_read/index_read_idx does also return the first row. Thus for
+ key lookups, the index_read will be the only call to the handler in
+ the index scan.
+
+ index_init initializes an index before using it and index_end does
+ any end processing needed.
+ */
+ virtual int index_read_map(uchar * buf, const uchar * key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag);
+ virtual int index_init(uint idx, bool sorted);
+ virtual int index_end();
+
+ /**
+ @breif
+ Positions an index cursor to the index specified in the hanlde. Fetches the
+ row if available. If the key value is null, begin at first key of the
+ index.
+ */
+ virtual int index_read_idx_map(uchar *buf, uint index, const uchar *key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag);
+ /*
+ These methods are used to jump to next or previous entry in the index
+ scan. There are also methods to jump to first and last entry.
+ */
+ virtual int index_next(uchar * buf);
+ virtual int index_prev(uchar * buf);
+ virtual int index_first(uchar * buf);
+ virtual int index_last(uchar * buf);
+ virtual int index_next_same(uchar * buf, const uchar * key, uint keylen);
+
+ /*
+ read_first_row is virtual method but is only implemented by
+ handler.cc, no storage engine has implemented it so neither
+ will the partition handler.
+
+ virtual int read_first_row(uchar *buf, uint primary_key);
+ */
+
+ /*
+ We don't implement multi read range yet, will do later.
+ virtual int read_multi_range_first(KEY_MULTI_RANGE **found_range_p,
+ KEY_MULTI_RANGE *ranges, uint range_count,
+ bool sorted, HANDLER_BUFFER *buffer);
+ virtual int read_multi_range_next(KEY_MULTI_RANGE **found_range_p);
+ */
+
+
+ virtual int read_range_first(const key_range * start_key,
+ const key_range * end_key,
+ bool eq_range, bool sorted);
+ virtual int read_range_next();
+
+private:
+ bool init_record_priority_queue();
+ void destroy_record_priority_queue();
+ int common_index_read(uchar * buf, bool have_start_key);
+ int common_first_last(uchar * buf);
+ int partition_scan_set_up(uchar * buf, bool idx_read_flag);
+ int handle_unordered_next(uchar * buf, bool next_same);
+ int handle_unordered_scan_next_partition(uchar * buf);
+ int handle_ordered_index_scan(uchar * buf, bool reverse_order);
+ int handle_ordered_index_scan_key_not_found();
+ int handle_ordered_next(uchar * buf, bool next_same);
+ int handle_ordered_prev(uchar * buf);
+ void return_top_record(uchar * buf);
+public:
+ /*
+ -------------------------------------------------------------------------
+ MODULE information calls
+ -------------------------------------------------------------------------
+ This calls are used to inform the handler of specifics of the ongoing
+ scans and other actions. Most of these are used for optimisation
+ purposes.
+ -------------------------------------------------------------------------
+ */
+ virtual int info(uint);
+ void get_dynamic_partition_info(PARTITION_STATS *stat_info,
+ uint part_id);
+ virtual int extra(enum ha_extra_function operation);
+ virtual int extra_opt(enum ha_extra_function operation, ulong cachesize);
+ virtual int reset(void);
+ virtual uint count_query_cache_dependant_tables(uint8 *tables_type);
+ virtual my_bool
+ register_query_cache_dependant_tables(THD *thd,
+ Query_cache *cache,
+ Query_cache_block_table **block,
+ uint *n);
+
+private:
+ my_bool reg_query_cache_dependant_table(THD *thd,
+ char *engine_key,
+ uint engine_key_len,
+ char *query_key, uint query_key_len,
+ uint8 type,
+ Query_cache *cache,
+ Query_cache_block_table
+ **block_table,
+ handler *file, uint *n);
+ static const uint NO_CURRENT_PART_ID;
+ int loop_extra(enum ha_extra_function operation);
+ int loop_extra_alter(enum ha_extra_function operations);
+ void late_extra_cache(uint partition_id);
+ void late_extra_no_cache(uint partition_id);
+ void prepare_extra_cache(uint cachesize);
+public:
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE optimiser support
+ -------------------------------------------------------------------------
+ -------------------------------------------------------------------------
+ */
+
+ /*
+ NOTE !!!!!!
+ -------------------------------------------------------------------------
+ -------------------------------------------------------------------------
+ One important part of the public handler interface that is not depicted in
+ the methods is the attribute records
+
+ which is defined in the base class. This is looked upon directly and is
+ set by calling info(HA_STATUS_INFO) ?
+ -------------------------------------------------------------------------
+ */
+
+private:
+ /* Helper functions for optimizer hints. */
+ ha_rows min_rows_for_estimate();
+ uint get_biggest_used_partition(uint *part_index);
+public:
+
+ /*
+ keys_to_use_for_scanning can probably be implemented as the
+ intersection of all underlying handlers if mixed handlers are used.
+ This method is used to derive whether an index can be used for
+ index-only scanning when performing an ORDER BY query.
+ Only called from one place in sql_select.cc
+ */
+ virtual const key_map *keys_to_use_for_scanning();
+
+ /*
+ Called in test_quick_select to determine if indexes should be used.
+ */
+ virtual double scan_time();
+
+ /*
+ The next method will never be called if you do not implement indexes.
+ */
+ virtual double read_time(uint index, uint ranges, ha_rows rows);
+ /*
+ For the given range how many records are estimated to be in this range.
+ Used by optimiser to calculate cost of using a particular index.
+ */
+ virtual ha_rows records_in_range(uint inx, key_range * min_key,
+ key_range * max_key);
+
+ /*
+ Upper bound of number records returned in scan is sum of all
+ underlying handlers.
+ */
+ virtual ha_rows estimate_rows_upper_bound();
+
+ /*
+ table_cache_type is implemented by the underlying handler but all
+ underlying handlers must have the same implementation for it to work.
+ */
+ virtual uint8 table_cache_type();
+ virtual ha_rows records();
+
+ /* Calculate hash value for PARTITION BY KEY tables. */
+ static uint32 calculate_key_hash_value(Field **field_array);
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE print messages
+ -------------------------------------------------------------------------
+ This module contains various methods that returns text messages for
+ table types, index type and error messages.
+ -------------------------------------------------------------------------
+ */
+ /*
+ The name of the index type that will be used for display
+ Here we must ensure that all handlers use the same index type
+ for each index created.
+ */
+ virtual const char *index_type(uint inx);
+
+ /* The name of the table type that will be used for display purposes */
+ virtual const char *table_type() const;
+
+ /* The name of the row type used for the underlying tables. */
+ virtual enum row_type get_row_type() const;
+
+ /*
+ Handler specific error messages
+ */
+ virtual void print_error(int error, myf errflag);
+ virtual bool get_error_message(int error, String * buf);
+ /*
+ -------------------------------------------------------------------------
+ MODULE handler characteristics
+ -------------------------------------------------------------------------
+ This module contains a number of methods defining limitations and
+ characteristics of the handler. The partition handler will calculate
+ this characteristics based on underlying handler characteristics.
+ -------------------------------------------------------------------------
+
+ This is a list of flags that says what the storage engine
+ implements. The current table flags are documented in handler.h
+ The partition handler will support whatever the underlying handlers
+ support except when specifically mentioned below about exceptions
+ to this rule.
+ NOTE: This cannot be cached since it can depend on TRANSACTION ISOLATION
+ LEVEL which is dynamic, see bug#39084.
+
+ HA_READ_RND_SAME:
+ Not currently used. (Means that the handler supports the rnd_same() call)
+ (MyISAM, HEAP)
+
+ HA_TABLE_SCAN_ON_INDEX:
+ Used to avoid scanning full tables on an index. If this flag is set then
+ the handler always has a primary key (hidden if not defined) and this
+ index is used for scanning rather than a full table scan in all
+ situations.
+ (InnoDB, BDB, Federated)
+
+ HA_REC_NOT_IN_SEQ:
+ This flag is set for handlers that cannot guarantee that the rows are
+ returned accroding to incremental positions (0, 1, 2, 3...).
+ This also means that rnd_next() should return HA_ERR_RECORD_DELETED
+ if it finds a deleted row.
+ (MyISAM (not fixed length row), BDB, HEAP, NDB, InooDB)
+
+ HA_CAN_GEOMETRY:
+ Can the storage engine handle spatial data.
+ Used to check that no spatial attributes are declared unless
+ the storage engine is capable of handling it.
+ (MyISAM)
+
+ HA_FAST_KEY_READ:
+ Setting this flag indicates that the handler is equally fast in
+ finding a row by key as by position.
+ This flag is used in a very special situation in conjunction with
+ filesort's. For further explanation see intro to init_read_record.
+ (BDB, HEAP, InnoDB)
+
+ HA_NULL_IN_KEY:
+ Is NULL values allowed in indexes.
+ If this is not allowed then it is not possible to use an index on a
+ NULLable field.
+ (BDB, HEAP, MyISAM, NDB, InnoDB)
+
+ HA_DUPLICATE_POS:
+ Tells that we can the position for the conflicting duplicate key
+ record is stored in table->file->dupp_ref. (insert uses rnd_pos() on
+ this to find the duplicated row)
+ (MyISAM)
+
+ HA_CAN_INDEX_BLOBS:
+ Is the storage engine capable of defining an index of a prefix on
+ a BLOB attribute.
+ (BDB, Federated, MyISAM, InnoDB)
+
+ HA_AUTO_PART_KEY:
+ Auto increment fields can be part of a multi-part key. For second part
+ auto-increment keys, the auto_incrementing is done in handler.cc
+ (BDB, Federated, MyISAM, NDB)
+
+ HA_REQUIRE_PRIMARY_KEY:
+ Can't define a table without primary key (and cannot handle a table
+ with hidden primary key)
+ (No handler has this limitation currently)
+
+ HA_STATS_RECORDS_IS_EXACT:
+ Does the counter of records after the info call specify an exact
+ value or not. If it does this flag is set.
+ Only MyISAM and HEAP uses exact count.
+
+ HA_CAN_INSERT_DELAYED:
+ Can the storage engine support delayed inserts.
+ To start with the partition handler will not support delayed inserts.
+ Further investigation needed.
+ (HEAP, MyISAM)
+
+ HA_PRIMARY_KEY_IN_READ_INDEX:
+ This parameter is set when the handler will also return the primary key
+ when doing read-only-key on another index.
+
+ HA_NOT_DELETE_WITH_CACHE:
+ Seems to be an old MyISAM feature that is no longer used. No handler
+ has it defined but it is checked in init_read_record.
+ Further investigation needed.
+ (No handler defines it)
+
+ HA_NO_PREFIX_CHAR_KEYS:
+ Indexes on prefixes of character fields is not allowed.
+ (NDB)
+
+ HA_CAN_FULLTEXT:
+ Does the storage engine support fulltext indexes
+ The partition handler will start by not supporting fulltext indexes.
+ (MyISAM)
+
+ HA_CAN_SQL_HANDLER:
+ Can the HANDLER interface in the MySQL API be used towards this
+ storage engine.
+ (MyISAM, InnoDB)
+
+ HA_NO_AUTO_INCREMENT:
+ Set if the storage engine does not support auto increment fields.
+ (Currently not set by any handler)
+
+ HA_HAS_CHECKSUM:
+ Special MyISAM feature. Has special SQL support in CREATE TABLE.
+ No special handling needed by partition handler.
+ (MyISAM)
+
+ HA_FILE_BASED:
+ Should file names always be in lower case (used by engines
+ that map table names to file names.
+ Since partition handler has a local file this flag is set.
+ (BDB, Federated, MyISAM)
+
+ HA_CAN_BIT_FIELD:
+ Is the storage engine capable of handling bit fields?
+ (MyISAM, NDB)
+
+ HA_NEED_READ_RANGE_BUFFER:
+ Is Read Multi-Range supported => need multi read range buffer
+ This parameter specifies whether a buffer for read multi range
+ is needed by the handler. Whether the handler supports this
+ feature or not is dependent of whether the handler implements
+ read_multi_range* calls or not. The only handler currently
+ supporting this feature is NDB so the partition handler need
+ not handle this call. There are methods in handler.cc that will
+ transfer those calls into index_read and other calls in the
+ index scan module.
+ (NDB)
+
+ HA_PRIMARY_KEY_REQUIRED_FOR_POSITION:
+ Does the storage engine need a PK for position?
+ (InnoDB)
+
+ HA_FILE_BASED is always set for partition handler since we use a
+ special file for handling names of partitions, engine types.
+ HA_REC_NOT_IN_SEQ is always set for partition handler since we cannot
+ guarantee that the records will be returned in sequence.
+ HA_CAN_GEOMETRY, HA_CAN_FULLTEXT, HA_CAN_SQL_HANDLER, HA_DUPLICATE_POS,
+ HA_CAN_INSERT_DELAYED, HA_PRIMARY_KEY_REQUIRED_FOR_POSITION is disabled
+ until further investigated.
+ */
+ virtual Table_flags table_flags() const;
+
+ /*
+ This is a bitmap of flags that says how the storage engine
+ implements indexes. The current index flags are documented in
+ handler.h. If you do not implement indexes, just return zero
+ here.
+
+ part is the key part to check. First key part is 0
+ If all_parts it's set, MySQL want to know the flags for the combined
+ index up to and including 'part'.
+
+ HA_READ_NEXT:
+ Does the index support read next, this is assumed in the server
+ code and never checked so all indexes must support this.
+ Note that the handler can be used even if it doesn't have any index.
+ (BDB, HEAP, MyISAM, Federated, NDB, InnoDB)
+
+ HA_READ_PREV:
+ Can the index be used to scan backwards.
+ (BDB, HEAP, MyISAM, NDB, InnoDB)
+
+ HA_READ_ORDER:
+ Can the index deliver its record in index order. Typically true for
+ all ordered indexes and not true for hash indexes.
+ In first step this is not true for partition handler until a merge
+ sort has been implemented in partition handler.
+ Used to set keymap part_of_sortkey
+ This keymap is only used to find indexes usable for resolving an ORDER BY
+ in the query. Thus in most cases index_read will work just fine without
+ order in result production. When this flag is set it is however safe to
+ order all output started by index_read since most engines do this. With
+ read_multi_range calls there is a specific flag setting order or not
+ order so in those cases ordering of index output can be avoided.
+ (BDB, InnoDB, HEAP, MyISAM, NDB)
+
+ HA_READ_RANGE:
+ Specify whether index can handle ranges, typically true for all
+ ordered indexes and not true for hash indexes.
+ Used by optimiser to check if ranges (as key >= 5) can be optimised
+ by index.
+ (BDB, InnoDB, NDB, MyISAM, HEAP)
+
+ HA_ONLY_WHOLE_INDEX:
+ Can't use part key searches. This is typically true for hash indexes
+ and typically not true for ordered indexes.
+ (Federated, NDB, HEAP)
+
+ HA_KEYREAD_ONLY:
+ Does the storage engine support index-only scans on this index.
+ Enables use of HA_EXTRA_KEYREAD and HA_EXTRA_NO_KEYREAD
+ Used to set key_map keys_for_keyread and to check in optimiser for
+ index-only scans. When doing a read under HA_EXTRA_KEYREAD the handler
+ only have to fill in the columns the key covers. If
+ HA_PRIMARY_KEY_IN_READ_INDEX is set then also the PRIMARY KEY columns
+ must be updated in the row.
+ (BDB, InnoDB, MyISAM)
+ */
+ virtual ulong index_flags(uint inx, uint part, bool all_parts) const
+ {
+ /*
+ The following code is not safe if you are using different
+ storage engines or different index types per partition.
+ */
+ return m_file[0]->index_flags(inx, part, all_parts);
+ }
+
+ /**
+ wrapper function for handlerton alter_table_flags, since
+ the ha_partition_hton cannot know all its capabilities
+ */
+ virtual uint alter_table_flags(uint flags);
+ /*
+ unireg.cc will call the following to make sure that the storage engine
+ can handle the data it is about to send.
+
+ The maximum supported values is the minimum of all handlers in the table
+ */
+ uint min_of_the_max_uint(uint (handler::*operator_func)(void) const) const;
+ virtual uint max_supported_record_length() const;
+ virtual uint max_supported_keys() const;
+ virtual uint max_supported_key_parts() const;
+ virtual uint max_supported_key_length() const;
+ virtual uint max_supported_key_part_length() const;
+
+ /*
+ The extra record buffer length is the maximum needed by all handlers.
+ The minimum record length is the maximum of all involved handlers.
+ */
+ virtual uint extra_rec_buf_length() const;
+ virtual uint min_record_length(uint options) const;
+
+ /*
+ Primary key is clustered can only be true if all underlying handlers have
+ this feature.
+ */
+ virtual bool primary_key_is_clustered()
+ { return m_pkey_is_clustered; }
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE compare records
+ -------------------------------------------------------------------------
+ cmp_ref checks if two references are the same. For most handlers this is
+ a simple memcmp of the reference. However some handlers use primary key
+ as reference and this can be the same even if memcmp says they are
+ different. This is due to character sets and end spaces and so forth.
+ For the partition handler the reference is first two bytes providing the
+ partition identity of the referred record and then the reference of the
+ underlying handler.
+ Thus cmp_ref for the partition handler always returns FALSE for records
+ not in the same partition and uses cmp_ref on the underlying handler
+ to check whether the rest of the reference part is also the same.
+ -------------------------------------------------------------------------
+ */
+ virtual int cmp_ref(const uchar * ref1, const uchar * ref2);
+ /*
+ -------------------------------------------------------------------------
+ MODULE auto increment
+ -------------------------------------------------------------------------
+ This module is used to handle the support of auto increments.
+
+ This variable in the handler is used as part of the handler interface
+ It is maintained by the parent handler object and should not be
+ touched by child handler objects (see handler.cc for its use).
+
+ auto_increment_column_changed
+ -------------------------------------------------------------------------
+ */
+ virtual void get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *first_value,
+ ulonglong *nb_reserved_values);
+ virtual void release_auto_increment();
+private:
+ virtual int reset_auto_increment(ulonglong value);
+ virtual void lock_auto_increment()
+ {
+ /* lock already taken */
+ if (auto_increment_safe_stmt_log_lock)
+ return;
+ DBUG_ASSERT(!auto_increment_lock);
+ if(table_share->tmp_table == NO_TMP_TABLE)
+ {
+ auto_increment_lock= TRUE;
+ part_share->lock_auto_inc();
+ }
+ }
+ virtual void unlock_auto_increment()
+ {
+ /*
+ If auto_increment_safe_stmt_log_lock is true, we have to keep the lock.
+ It will be set to false and thus unlocked at the end of the statement by
+ ha_partition::release_auto_increment.
+ */
+ if(auto_increment_lock && !auto_increment_safe_stmt_log_lock)
+ {
+ part_share->unlock_auto_inc();
+ auto_increment_lock= FALSE;
+ }
+ }
+ virtual void set_auto_increment_if_higher(Field *field)
+ {
+ ulonglong nr= (((Field_num*) field)->unsigned_flag ||
+ field->val_int() > 0) ? field->val_int() : 0;
+ lock_auto_increment();
+ DBUG_ASSERT(part_share->auto_inc_initialized);
+ /* must check when the mutex is taken */
+ if (nr >= part_share->next_auto_inc_val)
+ part_share->next_auto_inc_val= nr + 1;
+ unlock_auto_increment();
+ }
+
+public:
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE initialize handler for HANDLER call
+ -------------------------------------------------------------------------
+ This method is a special InnoDB method called before a HANDLER query.
+ -------------------------------------------------------------------------
+ */
+ virtual void init_table_handle_for_HANDLER();
+
+ /*
+ The remainder of this file defines the handler methods not implemented
+ by the partition handler
+ */
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE foreign key support
+ -------------------------------------------------------------------------
+ The following methods are used to implement foreign keys as supported by
+ InnoDB. Implement this ??
+ get_foreign_key_create_info is used by SHOW CREATE TABLE to get a textual
+ description of how the CREATE TABLE part to define FOREIGN KEY's is done.
+ free_foreign_key_create_info is used to free the memory area that provided
+ this description.
+ can_switch_engines checks if it is ok to switch to a new engine based on
+ the foreign key info in the table.
+ -------------------------------------------------------------------------
+
+ virtual char* get_foreign_key_create_info()
+ virtual void free_foreign_key_create_info(char* str)
+
+ virtual int get_foreign_key_list(THD *thd,
+ List<FOREIGN_KEY_INFO> *f_key_list)
+ virtual uint referenced_by_foreign_key()
+ */
+ virtual bool can_switch_engines();
+ /*
+ -------------------------------------------------------------------------
+ MODULE fulltext index
+ -------------------------------------------------------------------------
+ Fulltext stuff not yet.
+ -------------------------------------------------------------------------
+ virtual int ft_init() { return HA_ERR_WRONG_COMMAND; }
+ virtual FT_INFO *ft_init_ext(uint flags,uint inx,const uchar *key,
+ uint keylen)
+ { return NULL; }
+ virtual int ft_read(uchar *buf) { return HA_ERR_WRONG_COMMAND; }
+ */
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE restart full table scan at position (MyISAM)
+ -------------------------------------------------------------------------
+ The following method is only used by MyISAM when used as
+ temporary tables in a join.
+ virtual int restart_rnd_next(uchar *buf, uchar *pos);
+ */
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE in-place ALTER TABLE
+ -------------------------------------------------------------------------
+ These methods are in the handler interface. (used by innodb-plugin)
+ They are used for in-place alter table:
+ -------------------------------------------------------------------------
+ */
+ virtual enum_alter_inplace_result
+ check_if_supported_inplace_alter(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info);
+ virtual bool prepare_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info);
+ virtual bool inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info);
+ virtual bool commit_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info,
+ bool commit);
+ virtual void notify_table_changed();
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE tablespace support
+ -------------------------------------------------------------------------
+ Admin of table spaces is not applicable to the partition handler (InnoDB)
+ This means that the following method is not implemented:
+ -------------------------------------------------------------------------
+ virtual int discard_or_import_tablespace(my_bool discard)
+ */
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE admin MyISAM
+ -------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------
+ OPTIMIZE TABLE, CHECK TABLE, ANALYZE TABLE and REPAIR TABLE are
+ mapped to a routine that handles looping over a given set of
+ partitions and those routines send a flag indicating to execute on
+ all partitions.
+ -------------------------------------------------------------------------
+ */
+ virtual int optimize(THD* thd, HA_CHECK_OPT *check_opt);
+ virtual int analyze(THD* thd, HA_CHECK_OPT *check_opt);
+ virtual int check(THD* thd, HA_CHECK_OPT *check_opt);
+ virtual int repair(THD* thd, HA_CHECK_OPT *check_opt);
+ virtual bool check_and_repair(THD *thd);
+ virtual bool auto_repair(int error) const;
+ virtual bool is_crashed() const;
+ virtual int check_for_upgrade(HA_CHECK_OPT *check_opt);
+
+ private:
+ int handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, uint flags);
+ int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, uint part_id,
+ uint flag);
+ /**
+ Check if the rows are placed in the correct partition. If the given
+ argument is true, then move the rows to the correct partition.
+ */
+ int check_misplaced_rows(uint read_part_id, bool repair);
+ void append_row_to_str(String &str);
+ public:
+
+ /*
+ -------------------------------------------------------------------------
+ Admin commands not supported currently (almost purely MyISAM routines)
+ This means that the following methods are not implemented:
+ -------------------------------------------------------------------------
+
+ virtual int backup(TD* thd, HA_CHECK_OPT *check_opt);
+ virtual int restore(THD* thd, HA_CHECK_OPT *check_opt);
+ virtual int dump(THD* thd, int fd = -1);
+ virtual int net_read_dump(NET* net);
+ */
+ virtual uint checksum() const;
+ /* Enabled keycache for performance reasons, WL#4571 */
+ virtual int assign_to_keycache(THD* thd, HA_CHECK_OPT *check_opt);
+ virtual int preload_keys(THD* thd, HA_CHECK_OPT* check_opt);
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE enable/disable indexes
+ -------------------------------------------------------------------------
+ Enable/Disable Indexes are only supported by HEAP and MyISAM.
+ -------------------------------------------------------------------------
+ */
+ virtual int disable_indexes(uint mode);
+ virtual int enable_indexes(uint mode);
+ virtual int indexes_are_disabled(void);
+
+ /*
+ -------------------------------------------------------------------------
+ MODULE append_create_info
+ -------------------------------------------------------------------------
+ append_create_info is only used by MyISAM MERGE tables and the partition
+ handler will not support this handler as underlying handler.
+ Implement this??
+ -------------------------------------------------------------------------
+ virtual void append_create_info(String *packet)
+ */
+
+ /*
+ the following heavily relies on the fact that all partitions
+ are in the same storage engine.
+
+ When this limitation is lifted, the following hack should go away,
+ and a proper interface for engines needs to be introduced:
+
+ an PARTITION_SHARE structure that has a pointer to the TABLE_SHARE.
+ is given to engines everywhere where TABLE_SHARE is used now
+ has members like option_struct, ha_data
+ perhaps TABLE needs to be split the same way too...
+
+ this can also be done before partition will support a mix of engines,
+ but preferably together with other incompatible API changes.
+ */
+ virtual handlerton *partition_ht() const
+ {
+ handlerton *h= m_file[0]->ht;
+ for (uint i=1; i < m_tot_parts; i++)
+ DBUG_ASSERT(h == m_file[i]->ht);
+ return h;
+ }
+
+
+ friend int cmp_key_rowid_part_id(void *ptr, uchar *ref1, uchar *ref2);
+};
+
+#endif /* HA_PARTITION_INCLUDED */
diff --git a/sql/handler.cc b/sql/handler.cc
new file mode 100644
index 00000000000..70bce6f3963
--- /dev/null
+++ b/sql/handler.cc
@@ -0,0 +1,6323 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab.
+
+ 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 */
+
+/** @file handler.cc
+
+ @brief
+ Handler-calling-functions
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "rpl_handler.h"
+#include "sql_cache.h" // query_cache, query_cache_*
+#include "sql_connect.h" // global_table_stats
+#include "key.h" // key_copy, key_unpack, key_cmp_if_same, key_cmp
+#include "sql_table.h" // build_table_filename
+#include "sql_parse.h" // check_stack_overrun
+#include "sql_acl.h" // SUPER_ACL
+#include "sql_base.h" // free_io_cache
+#include "discover.h" // extension_based_table_discovery, etc
+#include "log_event.h" // *_rows_log_event
+#include "create_options.h"
+#include "rpl_filter.h"
+#include <myisampack.h>
+#include "transaction.h"
+#include "myisam.h"
+#include "probes_mysql.h"
+#include <mysql/psi/mysql_table.h>
+#include "debug_sync.h" // DEBUG_SYNC
+#include "sql_audit.h"
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+#include "ha_partition.h"
+#endif
+
+#ifdef WITH_ARIA_STORAGE_ENGINE
+#include "../storage/maria/ha_maria.h"
+#endif
+
+/*
+ While we have legacy_db_type, we have this array to
+ check for dups and to find handlerton from legacy_db_type.
+ Remove when legacy_db_type is finally gone
+*/
+st_plugin_int *hton2plugin[MAX_HA];
+
+static handlerton *installed_htons[128];
+
+#define BITMAP_STACKBUF_SIZE (128/8)
+
+KEY_CREATE_INFO default_key_create_info=
+{ HA_KEY_ALG_UNDEF, 0, {NullS, 0}, {NullS, 0}, true };
+
+/* number of entries in handlertons[] */
+ulong total_ha= 0;
+/* number of storage engines (from handlertons[]) that support 2pc */
+ulong total_ha_2pc= 0;
+/* size of savepoint storage area (see ha_init) */
+ulong savepoint_alloc_size= 0;
+
+static const LEX_STRING sys_table_aliases[]=
+{
+ { C_STRING_WITH_LEN("INNOBASE") }, { C_STRING_WITH_LEN("INNODB") },
+ { C_STRING_WITH_LEN("NDB") }, { C_STRING_WITH_LEN("NDBCLUSTER") },
+ { C_STRING_WITH_LEN("HEAP") }, { C_STRING_WITH_LEN("MEMORY") },
+ { C_STRING_WITH_LEN("MERGE") }, { C_STRING_WITH_LEN("MRG_MYISAM") },
+ { C_STRING_WITH_LEN("Maria") }, { C_STRING_WITH_LEN("Aria") },
+ {NullS, 0}
+};
+
+const char *ha_row_type[] = {
+ "", "FIXED", "DYNAMIC", "COMPRESSED", "REDUNDANT", "COMPACT", "PAGE"
+};
+
+const char *tx_isolation_names[] =
+{ "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ", "SERIALIZABLE",
+ NullS};
+TYPELIB tx_isolation_typelib= {array_elements(tx_isolation_names)-1,"",
+ tx_isolation_names, NULL};
+
+static TYPELIB known_extensions= {0,"known_exts", NULL, NULL};
+uint known_extensions_id= 0;
+
+static int commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans,
+ bool is_real_trans);
+
+
+static plugin_ref ha_default_plugin(THD *thd)
+{
+ if (thd->variables.table_plugin)
+ return thd->variables.table_plugin;
+ return my_plugin_lock(thd, global_system_variables.table_plugin);
+}
+
+
+/** @brief
+ Return the default storage engine handlerton for thread
+
+ SYNOPSIS
+ ha_default_handlerton(thd)
+ thd current thread
+
+ RETURN
+ pointer to handlerton
+*/
+handlerton *ha_default_handlerton(THD *thd)
+{
+ plugin_ref plugin= ha_default_plugin(thd);
+ DBUG_ASSERT(plugin);
+ handlerton *hton= plugin_hton(plugin);
+ DBUG_ASSERT(hton);
+ return hton;
+}
+
+
+/** @brief
+ Return the storage engine handlerton for the supplied name
+
+ SYNOPSIS
+ ha_resolve_by_name(thd, name)
+ thd current thread
+ name name of storage engine
+
+ RETURN
+ pointer to storage engine plugin handle
+*/
+plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name)
+{
+ const LEX_STRING *table_alias;
+ plugin_ref plugin;
+
+redo:
+ /* my_strnncoll is a macro and gcc doesn't do early expansion of macro */
+ if (thd && !my_charset_latin1.coll->strnncoll(&my_charset_latin1,
+ (const uchar *)name->str, name->length,
+ (const uchar *)STRING_WITH_LEN("DEFAULT"), 0))
+ return ha_default_plugin(thd);
+
+ if ((plugin= my_plugin_lock_by_name(thd, name, MYSQL_STORAGE_ENGINE_PLUGIN)))
+ {
+ handlerton *hton= plugin_hton(plugin);
+ if (hton && !(hton->flags & HTON_NOT_USER_SELECTABLE))
+ return plugin;
+
+ /*
+ unlocking plugin immediately after locking is relatively low cost.
+ */
+ plugin_unlock(thd, plugin);
+ }
+
+ /*
+ We check for the historical aliases.
+ */
+ for (table_alias= sys_table_aliases; table_alias->str; table_alias+= 2)
+ {
+ if (!my_strnncoll(&my_charset_latin1,
+ (const uchar *)name->str, name->length,
+ (const uchar *)table_alias->str, table_alias->length))
+ {
+ name= table_alias + 1;
+ goto redo;
+ }
+ }
+
+ return NULL;
+}
+
+
+plugin_ref ha_lock_engine(THD *thd, const handlerton *hton)
+{
+ if (hton)
+ {
+ st_plugin_int *plugin= hton2plugin[hton->slot];
+ return my_plugin_lock(thd, plugin_int_to_ref(plugin));
+ }
+ return NULL;
+}
+
+
+handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type)
+{
+ plugin_ref plugin;
+ switch (db_type) {
+ case DB_TYPE_DEFAULT:
+ return ha_default_handlerton(thd);
+ default:
+ if (db_type > DB_TYPE_UNKNOWN && db_type < DB_TYPE_DEFAULT &&
+ (plugin= ha_lock_engine(thd, installed_htons[db_type])))
+ return plugin_hton(plugin);
+ /* fall through */
+ case DB_TYPE_UNKNOWN:
+ return NULL;
+ }
+}
+
+
+/**
+ Use other database handler if databasehandler is not compiled in.
+*/
+handlerton *ha_checktype(THD *thd, enum legacy_db_type database_type,
+ bool no_substitute, bool report_error)
+{
+ handlerton *hton= ha_resolve_by_legacy_type(thd, database_type);
+ if (ha_storage_engine_is_enabled(hton))
+ return hton;
+
+ if (no_substitute)
+ {
+ if (report_error)
+ {
+ const char *engine_name= ha_resolve_storage_engine_name(hton);
+ my_error(ER_FEATURE_DISABLED,MYF(0),engine_name,engine_name);
+ }
+ return NULL;
+ }
+
+ (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE));
+
+ return ha_default_handlerton(thd);
+} /* ha_checktype */
+
+
+handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc,
+ handlerton *db_type)
+{
+ handler *file;
+ DBUG_ENTER("get_new_handler");
+ DBUG_PRINT("enter", ("alloc: 0x%lx", (long) alloc));
+
+ if (db_type && db_type->state == SHOW_OPTION_YES && db_type->create)
+ {
+ if ((file= db_type->create(db_type, share, alloc)))
+ file->init();
+ DBUG_RETURN(file);
+ }
+ /*
+ Try the default table type
+ Here the call to current_thd() is ok as we call this function a lot of
+ times but we enter this branch very seldom.
+ */
+ DBUG_RETURN(get_new_handler(share, alloc, ha_default_handlerton(current_thd)));
+}
+
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+handler *get_ha_partition(partition_info *part_info)
+{
+ ha_partition *partition;
+ DBUG_ENTER("get_ha_partition");
+ if ((partition= new ha_partition(partition_hton, part_info)))
+ {
+ if (partition->initialize_partition(current_thd->mem_root))
+ {
+ delete partition;
+ partition= 0;
+ }
+ else
+ partition->init();
+ }
+ else
+ {
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR),
+ static_cast<int>(sizeof(ha_partition)));
+ }
+ DBUG_RETURN(((handler*) partition));
+}
+#endif
+
+
+static const char **handler_errmsgs;
+
+C_MODE_START
+static const char **get_handler_errmsgs()
+{
+ return handler_errmsgs;
+}
+C_MODE_END
+
+
+/**
+ Register handler error messages for use with my_error().
+
+ @retval
+ 0 OK
+ @retval
+ !=0 Error
+*/
+
+int ha_init_errors(void)
+{
+#define SETMSG(nr, msg) handler_errmsgs[(nr) - HA_ERR_FIRST]= (msg)
+
+ /* Allocate a pointer array for the error message strings. */
+ /* Zerofill it to avoid uninitialized gaps. */
+ if (! (handler_errmsgs= (const char**) my_malloc(HA_ERR_ERRORS * sizeof(char*),
+ MYF(MY_WME | MY_ZEROFILL))))
+ return 1;
+
+ /* Set the dedicated error messages. */
+ SETMSG(HA_ERR_KEY_NOT_FOUND, ER_DEFAULT(ER_KEY_NOT_FOUND));
+ SETMSG(HA_ERR_FOUND_DUPP_KEY, ER_DEFAULT(ER_DUP_KEY));
+ SETMSG(HA_ERR_RECORD_CHANGED, "Update wich is recoverable");
+ SETMSG(HA_ERR_WRONG_INDEX, "Wrong index given to function");
+ SETMSG(HA_ERR_CRASHED, ER_DEFAULT(ER_NOT_KEYFILE));
+ SETMSG(HA_ERR_WRONG_IN_RECORD, ER_DEFAULT(ER_CRASHED_ON_USAGE));
+ SETMSG(HA_ERR_OUT_OF_MEM, "Table handler out of memory");
+ SETMSG(HA_ERR_NOT_A_TABLE, "Incorrect file format '%.64s'");
+ SETMSG(HA_ERR_WRONG_COMMAND, "Command not supported");
+ SETMSG(HA_ERR_OLD_FILE, ER_DEFAULT(ER_OLD_KEYFILE));
+ SETMSG(HA_ERR_NO_ACTIVE_RECORD, "No record read in update");
+ SETMSG(HA_ERR_RECORD_DELETED, "Intern record deleted");
+ SETMSG(HA_ERR_RECORD_FILE_FULL, ER_DEFAULT(ER_RECORD_FILE_FULL));
+ SETMSG(HA_ERR_INDEX_FILE_FULL, "No more room in index file '%.64s'");
+ SETMSG(HA_ERR_END_OF_FILE, "End in next/prev/first/last");
+ SETMSG(HA_ERR_UNSUPPORTED, ER_DEFAULT(ER_ILLEGAL_HA));
+ SETMSG(HA_ERR_TO_BIG_ROW, "Too big row");
+ SETMSG(HA_WRONG_CREATE_OPTION, "Wrong create option");
+ SETMSG(HA_ERR_FOUND_DUPP_UNIQUE, ER_DEFAULT(ER_DUP_UNIQUE));
+ SETMSG(HA_ERR_UNKNOWN_CHARSET, "Can't open charset");
+ SETMSG(HA_ERR_WRONG_MRG_TABLE_DEF, ER_DEFAULT(ER_WRONG_MRG_TABLE));
+ SETMSG(HA_ERR_CRASHED_ON_REPAIR, ER_DEFAULT(ER_CRASHED_ON_REPAIR));
+ SETMSG(HA_ERR_CRASHED_ON_USAGE, ER_DEFAULT(ER_CRASHED_ON_USAGE));
+ SETMSG(HA_ERR_LOCK_WAIT_TIMEOUT, ER_DEFAULT(ER_LOCK_WAIT_TIMEOUT));
+ SETMSG(HA_ERR_LOCK_TABLE_FULL, ER_DEFAULT(ER_LOCK_TABLE_FULL));
+ SETMSG(HA_ERR_READ_ONLY_TRANSACTION, ER_DEFAULT(ER_READ_ONLY_TRANSACTION));
+ SETMSG(HA_ERR_LOCK_DEADLOCK, ER_DEFAULT(ER_LOCK_DEADLOCK));
+ SETMSG(HA_ERR_CANNOT_ADD_FOREIGN, ER_DEFAULT(ER_CANNOT_ADD_FOREIGN));
+ SETMSG(HA_ERR_NO_REFERENCED_ROW, ER_DEFAULT(ER_NO_REFERENCED_ROW_2));
+ SETMSG(HA_ERR_ROW_IS_REFERENCED, ER_DEFAULT(ER_ROW_IS_REFERENCED_2));
+ SETMSG(HA_ERR_NO_SAVEPOINT, "No savepoint with that name");
+ SETMSG(HA_ERR_NON_UNIQUE_BLOCK_SIZE, "Non unique key block size");
+ SETMSG(HA_ERR_NO_SUCH_TABLE, "No such table: '%.64s'");
+ SETMSG(HA_ERR_TABLE_EXIST, ER_DEFAULT(ER_TABLE_EXISTS_ERROR));
+ SETMSG(HA_ERR_NO_CONNECTION, "Could not connect to storage engine");
+ SETMSG(HA_ERR_TABLE_DEF_CHANGED, ER_DEFAULT(ER_TABLE_DEF_CHANGED));
+ SETMSG(HA_ERR_FOREIGN_DUPLICATE_KEY, "FK constraint would lead to duplicate key");
+ SETMSG(HA_ERR_TABLE_NEEDS_UPGRADE, ER_DEFAULT(ER_TABLE_NEEDS_UPGRADE));
+ SETMSG(HA_ERR_TABLE_READONLY, ER_DEFAULT(ER_OPEN_AS_READONLY));
+ SETMSG(HA_ERR_AUTOINC_READ_FAILED, ER_DEFAULT(ER_AUTOINC_READ_FAILED));
+ SETMSG(HA_ERR_AUTOINC_ERANGE, ER_DEFAULT(ER_WARN_DATA_OUT_OF_RANGE));
+ SETMSG(HA_ERR_TOO_MANY_CONCURRENT_TRXS, ER_DEFAULT(ER_TOO_MANY_CONCURRENT_TRXS));
+ SETMSG(HA_ERR_INDEX_COL_TOO_LONG, ER_DEFAULT(ER_INDEX_COLUMN_TOO_LONG));
+ SETMSG(HA_ERR_INDEX_CORRUPT, ER_DEFAULT(ER_INDEX_CORRUPT));
+ SETMSG(HA_FTS_INVALID_DOCID, "Invalid InnoDB FTS Doc ID");
+ SETMSG(HA_ERR_TABLE_IN_FK_CHECK, ER_DEFAULT(ER_TABLE_IN_FK_CHECK));
+ SETMSG(HA_ERR_DISK_FULL, ER_DEFAULT(ER_DISK_FULL));
+ SETMSG(HA_ERR_FTS_TOO_MANY_WORDS_IN_PHRASE, "Too many words in a FTS phrase or proximity search");
+
+ /* Register the error messages for use with my_error(). */
+ return my_error_register(get_handler_errmsgs, HA_ERR_FIRST, HA_ERR_LAST);
+}
+
+
+/**
+ Unregister handler error messages.
+
+ @retval
+ 0 OK
+ @retval
+ !=0 Error
+*/
+static int ha_finish_errors(void)
+{
+ const char **errmsgs;
+
+ /* Allocate a pointer array for the error message strings. */
+ if (! (errmsgs= my_error_unregister(HA_ERR_FIRST, HA_ERR_LAST)))
+ return 1;
+ my_free(errmsgs);
+ return 0;
+}
+
+static volatile int32 need_full_discover_for_existence= 0;
+static volatile int32 engines_with_discover_table_names= 0;
+static volatile int32 engines_with_discover= 0;
+
+static int full_discover_for_existence(handlerton *, const char *, const char *)
+{ return 0; }
+
+static int ext_based_existence(handlerton *, const char *, const char *)
+{ return 0; }
+
+static int hton_ext_based_table_discovery(handlerton *hton, LEX_STRING *db,
+ MY_DIR *dir, handlerton::discovered_list *result)
+{
+ /*
+ tablefile_extensions[0] is the metadata file, see
+ the comment above tablefile_extensions declaration
+ */
+ return extension_based_table_discovery(dir, hton->tablefile_extensions[0],
+ result);
+}
+
+static void update_discovery_counters(handlerton *hton, int val)
+{
+ if (hton->discover_table_existence == full_discover_for_existence)
+ my_atomic_add32(&need_full_discover_for_existence, val);
+
+ if (hton->discover_table_names)
+ my_atomic_add32(&engines_with_discover_table_names, val);
+
+ if (hton->discover_table)
+ my_atomic_add32(&engines_with_discover, val);
+}
+
+int ha_finalize_handlerton(st_plugin_int *plugin)
+{
+ handlerton *hton= (handlerton *)plugin->data;
+ DBUG_ENTER("ha_finalize_handlerton");
+
+ /* hton can be NULL here, if ha_initialize_handlerton() failed. */
+ if (!hton)
+ goto end;
+
+ switch (hton->state) {
+ case SHOW_OPTION_NO:
+ case SHOW_OPTION_DISABLED:
+ break;
+ case SHOW_OPTION_YES:
+ if (installed_htons[hton->db_type] == hton)
+ installed_htons[hton->db_type]= NULL;
+ break;
+ };
+
+ if (hton->panic)
+ hton->panic(hton, HA_PANIC_CLOSE);
+
+ if (plugin->plugin->deinit)
+ {
+ /*
+ Today we have no defined/special behavior for uninstalling
+ engine plugins.
+ */
+ DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str));
+ if (plugin->plugin->deinit(NULL))
+ {
+ DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
+ plugin->name.str));
+ }
+ }
+
+ free_sysvar_table_options(hton);
+ update_discovery_counters(hton, -1);
+
+ /*
+ In case a plugin is uninstalled and re-installed later, it should
+ reuse an array slot. Otherwise the number of uninstall/install
+ cycles would be limited.
+ */
+ if (hton->slot != HA_SLOT_UNDEF)
+ {
+ /* Make sure we are not unpluging another plugin */
+ DBUG_ASSERT(hton2plugin[hton->slot] == plugin);
+ DBUG_ASSERT(hton->slot < MAX_HA);
+ hton2plugin[hton->slot]= NULL;
+ }
+
+ my_free(hton);
+
+ end:
+ DBUG_RETURN(0);
+}
+
+
+int ha_initialize_handlerton(st_plugin_int *plugin)
+{
+ handlerton *hton;
+ static const char *no_exts[]= { 0 };
+ DBUG_ENTER("ha_initialize_handlerton");
+ DBUG_PRINT("plugin", ("initialize plugin: '%s'", plugin->name.str));
+
+ hton= (handlerton *)my_malloc(sizeof(handlerton),
+ MYF(MY_WME | MY_ZEROFILL));
+ if (hton == NULL)
+ {
+ sql_print_error("Unable to allocate memory for plugin '%s' handlerton.",
+ plugin->name.str);
+ goto err_no_hton_memory;
+ }
+
+ hton->tablefile_extensions= no_exts;
+ hton->discover_table_names= hton_ext_based_table_discovery;
+
+ hton->slot= HA_SLOT_UNDEF;
+ /* Historical Requirement */
+ plugin->data= hton; // shortcut for the future
+ if (plugin->plugin->init && plugin->plugin->init(hton))
+ {
+ sql_print_error("Plugin '%s' init function returned error.",
+ plugin->name.str);
+ goto err;
+ }
+
+ // hton_ext_based_table_discovery() works only when discovery
+ // is supported and the engine if file-based.
+ if (hton->discover_table_names == hton_ext_based_table_discovery &&
+ (!hton->discover_table || !hton->tablefile_extensions[0]))
+ hton->discover_table_names= NULL;
+
+ // default discover_table_existence implementation
+ if (!hton->discover_table_existence && hton->discover_table)
+ {
+ if (hton->tablefile_extensions[0])
+ hton->discover_table_existence= ext_based_existence;
+ else
+ hton->discover_table_existence= full_discover_for_existence;
+ }
+
+ /*
+ the switch below and hton->state should be removed when
+ command-line options for plugins will be implemented
+ */
+ switch (hton->state) {
+ case SHOW_OPTION_NO:
+ break;
+ case SHOW_OPTION_YES:
+ {
+ uint tmp;
+ ulong fslot;
+
+ DBUG_EXECUTE_IF("unstable_db_type", {
+ static int i= (int) DB_TYPE_FIRST_DYNAMIC;
+ hton->db_type= (enum legacy_db_type)++i;
+ });
+
+ /* now check the db_type for conflict */
+ if (hton->db_type <= DB_TYPE_UNKNOWN ||
+ hton->db_type >= DB_TYPE_DEFAULT ||
+ installed_htons[hton->db_type])
+ {
+ int idx= (int) DB_TYPE_FIRST_DYNAMIC;
+
+ while (idx < (int) DB_TYPE_DEFAULT && installed_htons[idx])
+ idx++;
+
+ if (idx == (int) DB_TYPE_DEFAULT)
+ {
+ sql_print_warning("Too many storage engines!");
+ goto err_deinit;
+ }
+ if (hton->db_type != DB_TYPE_UNKNOWN)
+ sql_print_warning("Storage engine '%s' has conflicting typecode. "
+ "Assigning value %d.", plugin->plugin->name, idx);
+ hton->db_type= (enum legacy_db_type) idx;
+ }
+
+ /*
+ In case a plugin is uninstalled and re-installed later, it should
+ reuse an array slot. Otherwise the number of uninstall/install
+ cycles would be limited. So look for a free slot.
+ */
+ DBUG_PRINT("plugin", ("total_ha: %lu", total_ha));
+ for (fslot= 0; fslot < total_ha; fslot++)
+ {
+ if (!hton2plugin[fslot])
+ break;
+ }
+ if (fslot < total_ha)
+ hton->slot= fslot;
+ else
+ {
+ if (total_ha >= MAX_HA)
+ {
+ sql_print_error("Too many plugins loaded. Limit is %lu. "
+ "Failed on '%s'", (ulong) MAX_HA, plugin->name.str);
+ goto err_deinit;
+ }
+ hton->slot= total_ha++;
+ }
+ installed_htons[hton->db_type]= hton;
+ tmp= hton->savepoint_offset;
+ hton->savepoint_offset= savepoint_alloc_size;
+ savepoint_alloc_size+= tmp;
+ hton2plugin[hton->slot]=plugin;
+ if (hton->prepare)
+ {
+ total_ha_2pc++;
+ if (tc_log && tc_log != get_tc_log_implementation())
+ {
+ total_ha_2pc--;
+ hton->prepare= 0;
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR,
+ "Cannot enable tc-log at run-time. "
+ "XA features of %s are disabled",
+ plugin->name.str);
+ }
+ }
+ break;
+ }
+ /* fall through */
+ default:
+ hton->state= SHOW_OPTION_DISABLED;
+ break;
+ }
+
+ /*
+ This is entirely for legacy. We will create a new "disk based" hton and a
+ "memory" hton which will be configurable longterm. We should be able to
+ remove partition and myisammrg.
+ */
+ switch (hton->db_type) {
+ case DB_TYPE_HEAP:
+ heap_hton= hton;
+ break;
+ case DB_TYPE_MYISAM:
+ myisam_hton= hton;
+ break;
+ case DB_TYPE_PARTITION_DB:
+ partition_hton= hton;
+ break;
+ default:
+ break;
+ };
+
+ resolve_sysvar_table_options(hton);
+ update_discovery_counters(hton, 1);
+
+ DBUG_RETURN(0);
+
+err_deinit:
+ /*
+ Let plugin do its inner deinitialization as plugin->init()
+ was successfully called before.
+ */
+ if (plugin->plugin->deinit)
+ (void) plugin->plugin->deinit(NULL);
+
+err:
+ my_free(hton);
+err_no_hton_memory:
+ plugin->data= NULL;
+ DBUG_RETURN(1);
+}
+
+int ha_init()
+{
+ int error= 0;
+ DBUG_ENTER("ha_init");
+
+ DBUG_ASSERT(total_ha < MAX_HA);
+ /*
+ Check if there is a transaction-capable storage engine besides the
+ binary log (which is considered a transaction-capable storage engine in
+ counting total_ha)
+ */
+ opt_using_transactions= total_ha>(ulong)opt_bin_log;
+ savepoint_alloc_size+= sizeof(SAVEPOINT);
+ DBUG_RETURN(error);
+}
+
+int ha_end()
+{
+ int error= 0;
+ DBUG_ENTER("ha_end");
+
+
+ /*
+ This should be eventualy based on the graceful shutdown flag.
+ So if flag is equal to HA_PANIC_CLOSE, the deallocate
+ the errors.
+ */
+ if (ha_finish_errors())
+ error= 1;
+
+ DBUG_RETURN(error);
+}
+
+static my_bool dropdb_handlerton(THD *unused1, plugin_ref plugin,
+ void *path)
+{
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->drop_database)
+ hton->drop_database(hton, (char *)path);
+ return FALSE;
+}
+
+
+void ha_drop_database(char* path)
+{
+ plugin_foreach(NULL, dropdb_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, path);
+}
+
+
+static my_bool checkpoint_state_handlerton(THD *unused1, plugin_ref plugin,
+ void *disable)
+{
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->checkpoint_state)
+ hton->checkpoint_state(hton, (int) *(bool*) disable);
+ return FALSE;
+}
+
+
+void ha_checkpoint_state(bool disable)
+{
+ plugin_foreach(NULL, checkpoint_state_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, &disable);
+}
+
+
+struct st_commit_checkpoint_request {
+ void *cookie;
+ void (*pre_hook)(void *);
+};
+
+static my_bool commit_checkpoint_request_handlerton(THD *unused1, plugin_ref plugin,
+ void *data)
+{
+ st_commit_checkpoint_request *st= (st_commit_checkpoint_request *)data;
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->commit_checkpoint_request)
+ {
+ void *cookie= st->cookie;
+ if (st->pre_hook)
+ (*st->pre_hook)(cookie);
+ (*hton->commit_checkpoint_request)(hton, cookie);
+ }
+ return FALSE;
+}
+
+
+/*
+ Invoke commit_checkpoint_request() in all storage engines that implement it.
+
+ If pre_hook is non-NULL, the hook will be called prior to each invocation.
+*/
+void
+ha_commit_checkpoint_request(void *cookie, void (*pre_hook)(void *))
+{
+ st_commit_checkpoint_request st;
+ st.cookie= cookie;
+ st.pre_hook= pre_hook;
+ plugin_foreach(NULL, commit_checkpoint_request_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &st);
+}
+
+
+
+static my_bool closecon_handlerton(THD *thd, plugin_ref plugin,
+ void *unused)
+{
+ handlerton *hton= plugin_hton(plugin);
+ /*
+ there's no need to rollback here as all transactions must
+ be rolled back already
+ */
+ if (hton->state == SHOW_OPTION_YES && thd_get_ha_data(thd, hton))
+ {
+ if (hton->close_connection)
+ hton->close_connection(hton, thd);
+ /* make sure ha_data is reset and ha_data_lock is released */
+ thd_set_ha_data(thd, hton, NULL);
+ }
+ return FALSE;
+}
+
+/**
+ @note
+ don't bother to rollback here, it's done already
+*/
+void ha_close_connection(THD* thd)
+{
+ plugin_foreach(thd, closecon_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, 0);
+}
+
+static my_bool kill_handlerton(THD *thd, plugin_ref plugin,
+ void *level)
+{
+ handlerton *hton= plugin_hton(plugin);
+
+ if (hton->state == SHOW_OPTION_YES && hton->kill_query &&
+ thd_get_ha_data(thd, hton))
+ hton->kill_query(hton, thd, *(enum thd_kill_levels *) level);
+ return FALSE;
+}
+
+void ha_kill_query(THD* thd, enum thd_kill_levels level)
+{
+ DBUG_ENTER("ha_kill_query");
+ plugin_foreach(thd, kill_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, &level);
+ DBUG_VOID_RETURN;
+}
+
+
+/* ========================================================================
+ ======================= TRANSACTIONS ===================================*/
+
+/**
+ Transaction handling in the server
+ ==================================
+
+ In each client connection, MySQL maintains two transactional
+ states:
+ - a statement transaction,
+ - a standard, also called normal transaction.
+
+ Historical note
+ ---------------
+ "Statement transaction" is a non-standard term that comes
+ from the times when MySQL supported BerkeleyDB storage engine.
+
+ First of all, it should be said that in BerkeleyDB auto-commit
+ mode auto-commits operations that are atomic to the storage
+ engine itself, such as a write of a record, and are too
+ high-granular to be atomic from the application perspective
+ (MySQL). One SQL statement could involve many BerkeleyDB
+ auto-committed operations and thus BerkeleyDB auto-commit was of
+ little use to MySQL.
+
+ Secondly, instead of SQL standard savepoints, BerkeleyDB
+ provided the concept of "nested transactions". In a nutshell,
+ transactions could be arbitrarily nested, but when the parent
+ transaction was committed or aborted, all its child (nested)
+ transactions were handled committed or aborted as well.
+ Commit of a nested transaction, in turn, made its changes
+ visible, but not durable: it destroyed the nested transaction,
+ all its changes would become available to the parent and
+ currently active nested transactions of this parent.
+
+ So the mechanism of nested transactions was employed to
+ provide "all or nothing" guarantee of SQL statements
+ required by the standard.
+ A nested transaction would be created at start of each SQL
+ statement, and destroyed (committed or aborted) at statement
+ end. Such nested transaction was internally referred to as
+ a "statement transaction" and gave birth to the term.
+
+ (Historical note ends)
+
+ Since then a statement transaction is started for each statement
+ that accesses transactional tables or uses the binary log. If
+ the statement succeeds, the statement transaction is committed.
+ If the statement fails, the transaction is rolled back. Commits
+ of statement transactions are not durable -- each such
+ transaction is nested in the normal transaction, and if the
+ normal transaction is rolled back, the effects of all enclosed
+ statement transactions are undone as well. Technically,
+ a statement transaction can be viewed as a savepoint which is
+ maintained automatically in order to make effects of one
+ statement atomic.
+
+ The normal transaction is started by the user and is ended
+ usually upon a user request as well. The normal transaction
+ encloses transactions of all statements issued between
+ its beginning and its end.
+ In autocommit mode, the normal transaction is equivalent
+ to the statement transaction.
+
+ Since MySQL supports PSEA (pluggable storage engine
+ architecture), more than one transactional engine can be
+ active at a time. Hence transactions, from the server
+ point of view, are always distributed. In particular,
+ transactional state is maintained independently for each
+ engine. In order to commit a transaction the two phase
+ commit protocol is employed.
+
+ Not all statements are executed in context of a transaction.
+ Administrative and status information statements do not modify
+ engine data, and thus do not start a statement transaction and
+ also have no effect on the normal transaction. Examples of such
+ statements are SHOW STATUS and RESET SLAVE.
+
+ Similarly DDL statements are not transactional,
+ and therefore a transaction is [almost] never started for a DDL
+ statement. The difference between a DDL statement and a purely
+ administrative statement though is that a DDL statement always
+ commits the current transaction before proceeding, if there is
+ any.
+
+ At last, SQL statements that work with non-transactional
+ engines also have no effect on the transaction state of the
+ connection. Even though they are written to the binary log,
+ and the binary log is, overall, transactional, the writes
+ are done in "write-through" mode, directly to the binlog
+ file, followed with a OS cache sync, in other words,
+ bypassing the binlog undo log (translog).
+ They do not commit the current normal transaction.
+ A failure of a statement that uses non-transactional tables
+ would cause a rollback of the statement transaction, but
+ in case there no non-transactional tables are used,
+ no statement transaction is started.
+
+ Data layout
+ -----------
+
+ The server stores its transaction-related data in
+ thd->transaction. This structure has two members of type
+ THD_TRANS. These members correspond to the statement and
+ normal transactions respectively:
+
+ - thd->transaction.stmt contains a list of engines
+ that are participating in the given statement
+ - thd->transaction.all contains a list of engines that
+ have participated in any of the statement transactions started
+ within the context of the normal transaction.
+ Each element of the list contains a pointer to the storage
+ engine, engine-specific transactional data, and engine-specific
+ transaction flags.
+
+ In autocommit mode thd->transaction.all is empty.
+ Instead, data of thd->transaction.stmt is
+ used to commit/rollback the normal transaction.
+
+ The list of registered engines has a few important properties:
+ - no engine is registered in the list twice
+ - engines are present in the list a reverse temporal order --
+ new participants are always added to the beginning of the list.
+
+ Transaction life cycle
+ ----------------------
+
+ When a new connection is established, thd->transaction
+ members are initialized to an empty state.
+ If a statement uses any tables, all affected engines
+ are registered in the statement engine list. In
+ non-autocommit mode, the same engines are registered in
+ the normal transaction list.
+ At the end of the statement, the server issues a commit
+ or a roll back for all engines in the statement list.
+ At this point transaction flags of an engine, if any, are
+ propagated from the statement list to the list of the normal
+ transaction.
+ When commit/rollback is finished, the statement list is
+ cleared. It will be filled in again by the next statement,
+ and emptied again at the next statement's end.
+
+ The normal transaction is committed in a similar way
+ (by going over all engines in thd->transaction.all list)
+ but at different times:
+ - upon COMMIT SQL statement is issued by the user
+ - implicitly, by the server, at the beginning of a DDL statement
+ or SET AUTOCOMMIT={0|1} statement.
+
+ The normal transaction can be rolled back as well:
+ - if the user has requested so, by issuing ROLLBACK SQL
+ statement
+ - if one of the storage engines requested a rollback
+ by setting thd->transaction_rollback_request. This may
+ happen in case, e.g., when the transaction in the engine was
+ chosen a victim of the internal deadlock resolution algorithm
+ and rolled back internally. When such a situation happens, there
+ is little the server can do and the only option is to rollback
+ transactions in all other participating engines. In this case
+ the rollback is accompanied by an error sent to the user.
+
+ As follows from the use cases above, the normal transaction
+ is never committed when there is an outstanding statement
+ transaction. In most cases there is no conflict, since
+ commits of the normal transaction are issued by a stand-alone
+ administrative or DDL statement, thus no outstanding statement
+ transaction of the previous statement exists. Besides,
+ all statements that manipulate with the normal transaction
+ are prohibited in stored functions and triggers, therefore
+ no conflicting situation can occur in a sub-statement either.
+ The remaining rare cases when the server explicitly has
+ to commit the statement transaction prior to committing the normal
+ one cover error-handling scenarios (see for example
+ SQLCOM_LOCK_TABLES).
+
+ When committing a statement or a normal transaction, the server
+ either uses the two-phase commit protocol, or issues a commit
+ in each engine independently. The two-phase commit protocol
+ is used only if:
+ - all participating engines support two-phase commit (provide
+ handlerton::prepare PSEA API call) and
+ - transactions in at least two engines modify data (i.e. are
+ not read-only).
+
+ Note that the two phase commit is used for
+ statement transactions, even though they are not durable anyway.
+ This is done to ensure logical consistency of data in a multiple-
+ engine transaction.
+ For example, imagine that some day MySQL supports unique
+ constraint checks deferred till the end of statement. In such
+ case a commit in one of the engines may yield ER_DUP_KEY,
+ and MySQL should be able to gracefully abort statement
+ transactions of other participants.
+
+ After the normal transaction has been committed,
+ thd->transaction.all list is cleared.
+
+ When a connection is closed, the current normal transaction, if
+ any, is rolled back.
+
+ Roles and responsibilities
+ --------------------------
+
+ The server has no way to know that an engine participates in
+ the statement and a transaction has been started
+ in it unless the engine says so. Thus, in order to be
+ a part of a transaction, the engine must "register" itself.
+ This is done by invoking trans_register_ha() server call.
+ Normally the engine registers itself whenever handler::external_lock()
+ is called. trans_register_ha() can be invoked many times: if
+ an engine is already registered, the call does nothing.
+ In case autocommit is not set, the engine must register itself
+ twice -- both in the statement list and in the normal transaction
+ list.
+ In which list to register is a parameter of trans_register_ha().
+
+ Note, that although the registration interface in itself is
+ fairly clear, the current usage practice often leads to undesired
+ effects. E.g. since a call to trans_register_ha() in most engines
+ is embedded into implementation of handler::external_lock(), some
+ DDL statements start a transaction (at least from the server
+ point of view) even though they are not expected to. E.g.
+ CREATE TABLE does not start a transaction, since
+ handler::external_lock() is never called during CREATE TABLE. But
+ CREATE TABLE ... SELECT does, since handler::external_lock() is
+ called for the table that is being selected from. This has no
+ practical effects currently, but must be kept in mind
+ nevertheless.
+
+ Once an engine is registered, the server will do the rest
+ of the work.
+
+ During statement execution, whenever any of data-modifying
+ PSEA API methods is used, e.g. handler::write_row() or
+ handler::update_row(), the read-write flag is raised in the
+ statement transaction for the involved engine.
+ Currently All PSEA calls are "traced", and the data can not be
+ changed in a way other than issuing a PSEA call. Important:
+ unless this invariant is preserved the server will not know that
+ a transaction in a given engine is read-write and will not
+ involve the two-phase commit protocol!
+
+ At the end of a statement, server call trans_commit_stmt is
+ invoked. This call in turn invokes handlerton::prepare()
+ for every involved engine. Prepare is followed by a call
+ to handlerton::commit_one_phase() If a one-phase commit
+ will suffice, handlerton::prepare() is not invoked and
+ the server only calls handlerton::commit_one_phase().
+ At statement commit, the statement-related read-write
+ engine flag is propagated to the corresponding flag in the
+ normal transaction. When the commit is complete, the list
+ of registered engines is cleared.
+
+ Rollback is handled in a similar fashion.
+
+ Additional notes on DDL and the normal transaction.
+ ---------------------------------------------------
+
+ DDLs and operations with non-transactional engines
+ do not "register" in thd->transaction lists, and thus do not
+ modify the transaction state. Besides, each DDL in
+ MySQL is prefixed with an implicit normal transaction commit
+ (a call to trans_commit_implicit()), and thus leaves nothing
+ to modify.
+ However, as it has been pointed out with CREATE TABLE .. SELECT,
+ some DDL statements can start a *new* transaction.
+
+ Behaviour of the server in this case is currently badly
+ defined.
+ DDL statements use a form of "semantic" logging
+ to maintain atomicity: if CREATE TABLE .. SELECT failed,
+ the newly created table is deleted.
+ In addition, some DDL statements issue interim transaction
+ commits: e.g. ALTER TABLE issues a commit after data is copied
+ from the original table to the internal temporary table. Other
+ statements, e.g. CREATE TABLE ... SELECT do not always commit
+ after itself.
+ And finally there is a group of DDL statements such as
+ RENAME/DROP TABLE that doesn't start a new transaction
+ and doesn't commit.
+
+ This diversity makes it hard to say what will happen if
+ by chance a stored function is invoked during a DDL --
+ whether any modifications it makes will be committed or not
+ is not clear. Fortunately, SQL grammar of few DDLs allows
+ invocation of a stored function.
+
+ A consistent behaviour is perhaps to always commit the normal
+ transaction after all DDLs, just like the statement transaction
+ is always committed at the end of all statements.
+*/
+
+/**
+ Register a storage engine for a transaction.
+
+ Every storage engine MUST call this function when it starts
+ a transaction or a statement (that is it must be called both for the
+ "beginning of transaction" and "beginning of statement").
+ Only storage engines registered for the transaction/statement
+ will know when to commit/rollback it.
+
+ @note
+ trans_register_ha is idempotent - storage engine may register many
+ times per transaction.
+
+*/
+void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
+{
+ THD_TRANS *trans;
+ Ha_trx_info *ha_info;
+ DBUG_ENTER("trans_register_ha");
+ DBUG_PRINT("enter",("%s", all ? "all" : "stmt"));
+
+ if (all)
+ {
+ trans= &thd->transaction.all;
+ thd->server_status|= SERVER_STATUS_IN_TRANS;
+ if (thd->tx_read_only)
+ thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY;
+ DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS"));
+ }
+ else
+ trans= &thd->transaction.stmt;
+
+ ha_info= thd->ha_data[ht_arg->slot].ha_info + (all ? 1 : 0);
+
+ if (ha_info->is_started())
+ DBUG_VOID_RETURN; /* already registered, return */
+
+ ha_info->register_ha(trans, ht_arg);
+
+ trans->no_2pc|=(ht_arg->prepare==0);
+ if (thd->transaction.xid_state.xid.is_null())
+ thd->transaction.xid_state.xid.set(thd->query_id);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ @retval
+ 0 ok
+ @retval
+ 1 error, transaction was rolled back
+*/
+int ha_prepare(THD *thd)
+{
+ int error=0, all=1;
+ THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
+ Ha_trx_info *ha_info= trans->ha_list;
+ DBUG_ENTER("ha_prepare");
+
+ if (ha_info)
+ {
+ for (; ha_info; ha_info= ha_info->next())
+ {
+ int err;
+ handlerton *ht= ha_info->ht();
+ status_var_increment(thd->status_var.ha_prepare_count);
+ if (ht->prepare)
+ {
+ if ((err= ht->prepare(ht, thd, all)))
+ {
+ my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
+ ha_rollback_trans(thd, all);
+ error=1;
+ break;
+ }
+ }
+ else
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_GET_ERRNO, ER(ER_GET_ERRNO),
+ HA_ERR_WRONG_COMMAND,
+ ha_resolve_storage_engine_name(ht));
+
+ }
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+/**
+ Check if we can skip the two-phase commit.
+
+ A helper function to evaluate if two-phase commit is mandatory.
+ As a side effect, propagates the read-only/read-write flags
+ of the statement transaction to its enclosing normal transaction.
+
+ If we have at least two engines with read-write changes we must
+ run a two-phase commit. Otherwise we can run several independent
+ commits as the only transactional engine has read-write changes
+ and others are read-only.
+
+ @retval 0 All engines are read-only.
+ @retval 1 We have the only engine with read-write changes.
+ @retval >1 More than one engine have read-write changes.
+ Note: return value might NOT be the exact number of
+ engines with read-write changes.
+*/
+
+static
+uint
+ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list,
+ bool all)
+{
+ /* The number of storage engines that have actual changes. */
+ unsigned rw_ha_count= 0;
+ Ha_trx_info *ha_info;
+
+ for (ha_info= ha_list; ha_info; ha_info= ha_info->next())
+ {
+ if (ha_info->is_trx_read_write())
+ ++rw_ha_count;
+
+ if (! all)
+ {
+ Ha_trx_info *ha_info_all= &thd->ha_data[ha_info->ht()->slot].ha_info[1];
+ DBUG_ASSERT(ha_info != ha_info_all);
+ /*
+ Merge read-only/read-write information about statement
+ transaction to its enclosing normal transaction. Do this
+ only if in a real transaction -- that is, if we know
+ that ha_info_all is registered in thd->transaction.all.
+ Since otherwise we only clutter the normal transaction flags.
+ */
+ if (ha_info_all->is_started()) /* FALSE if autocommit. */
+ ha_info_all->coalesce_trx_with(ha_info);
+ }
+ else if (rw_ha_count > 1)
+ {
+ /*
+ It is a normal transaction, so we don't need to merge read/write
+ information up, and the need for two-phase commit has been
+ already established. Break the loop prematurely.
+ */
+ break;
+ }
+ }
+ return rw_ha_count;
+}
+
+
+/**
+ @retval
+ 0 ok
+ @retval
+ 1 transaction was rolled back
+ @retval
+ 2 error during commit, data may be inconsistent
+
+ @todo
+ Since we don't support nested statement transactions in 5.0,
+ we can't commit or rollback stmt transactions while we are inside
+ stored functions or triggers. So we simply do nothing now.
+ TODO: This should be fixed in later ( >= 5.1) releases.
+*/
+int ha_commit_trans(THD *thd, bool all)
+{
+ int error= 0, cookie;
+ /*
+ 'all' means that this is either an explicit commit issued by
+ user, or an implicit commit issued by a DDL.
+ */
+ THD_TRANS *trans= all ? &thd->transaction.all : &thd->transaction.stmt;
+ /*
+ "real" is a nick name for a transaction for which a commit will
+ make persistent changes. E.g. a 'stmt' transaction inside a 'all'
+ transation is not 'real': even though it's possible to commit it,
+ the changes are not durable as they might be rolled back if the
+ enclosing 'all' transaction is rolled back.
+ */
+ bool is_real_trans= ((all || thd->transaction.all.ha_list == 0) &&
+ !(thd->variables.option_bits & OPTION_GTID_BEGIN));
+ Ha_trx_info *ha_info= trans->ha_list;
+ bool need_prepare_ordered, need_commit_ordered;
+ my_xid xid;
+ DBUG_ENTER("ha_commit_trans");
+ DBUG_PRINT("info",("thd: %p option_bits: %lu all: %d",
+ thd, (ulong) thd->variables.option_bits, all));
+
+ /* Just a random warning to test warnings pushed during autocommit. */
+ DBUG_EXECUTE_IF("warn_during_ha_commit_trans",
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARNING_NOT_COMPLETE_ROLLBACK,
+ ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)););
+
+ DBUG_PRINT("info",
+ ("all: %d thd->in_sub_stmt: %d ha_info: %p is_real_trans: %d",
+ all, thd->in_sub_stmt, ha_info, is_real_trans));
+ /*
+ We must not commit the normal transaction if a statement
+ transaction is pending. Otherwise statement transaction
+ flags will not get propagated to its normal transaction's
+ counterpart.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
+ trans == &thd->transaction.stmt);
+
+ if (thd->in_sub_stmt)
+ {
+ DBUG_ASSERT(0);
+ /*
+ Since we don't support nested statement transactions in 5.0,
+ we can't commit or rollback stmt transactions while we are inside
+ stored functions or triggers. So we simply do nothing now.
+ TODO: This should be fixed in later ( >= 5.1) releases.
+ */
+ if (!all)
+ DBUG_RETURN(0);
+ /*
+ We assume that all statements which commit or rollback main transaction
+ are prohibited inside of stored functions or triggers. So they should
+ bail out with error even before ha_commit_trans() call. To be 100% safe
+ let us throw error in non-debug builds.
+ */
+ my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
+ DBUG_RETURN(2);
+ }
+
+#ifdef WITH_ARIA_STORAGE_ENGINE
+ ha_maria::implicit_commit(thd, TRUE);
+#endif
+
+ if (!ha_info)
+ {
+ /*
+ Free resources and perform other cleanup even for 'empty' transactions.
+ */
+ if (is_real_trans)
+ thd->transaction.cleanup();
+ DBUG_RETURN(0);
+ }
+
+ DBUG_EXECUTE_IF("crash_commit_before", DBUG_SUICIDE(););
+
+ /* Close all cursors that can not survive COMMIT */
+ if (is_real_trans) /* not a statement commit */
+ thd->stmt_map.close_transient_cursors();
+
+ uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all);
+ /* rw_trans is TRUE when we in a transaction changing data */
+ bool rw_trans= is_real_trans && (rw_ha_count > 0);
+ MDL_request mdl_request;
+ DBUG_PRINT("info", ("is_real_trans: %d rw_trans: %d rw_ha_count: %d",
+ is_real_trans, rw_trans, rw_ha_count));
+
+ if (rw_trans)
+ {
+ /*
+ Acquire a metadata lock which will ensure that COMMIT is blocked
+ by an active FLUSH TABLES WITH READ LOCK (and vice versa:
+ COMMIT in progress blocks FTWRL).
+
+ We allow the owner of FTWRL to COMMIT; we assume that it knows
+ what it does.
+ */
+ mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_EXPLICIT);
+
+ if (thd->mdl_context.acquire_lock(&mdl_request,
+ thd->variables.lock_wait_timeout))
+ {
+ ha_rollback_trans(thd, all);
+ DBUG_RETURN(1);
+ }
+
+ DEBUG_SYNC(thd, "ha_commit_trans_after_acquire_commit_lock");
+ }
+
+ if (rw_trans &&
+ opt_readonly &&
+ !(thd->security_ctx->master_access & SUPER_ACL) &&
+ !thd->slave_thread)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
+ goto err;
+ }
+
+ if (trans->no_2pc || (rw_ha_count <= 1))
+ {
+ error= ha_commit_one_phase(thd, all);
+ goto done;
+ }
+
+ need_prepare_ordered= FALSE;
+ need_commit_ordered= FALSE;
+ xid= thd->transaction.xid_state.xid.get_my_xid();
+
+ for (Ha_trx_info *hi= ha_info; hi; hi= hi->next())
+ {
+ int err;
+ handlerton *ht= hi->ht();
+ /*
+ Do not call two-phase commit if this particular
+ transaction is read-only. This allows for simpler
+ implementation in engines that are always read-only.
+ */
+ if (! hi->is_trx_read_write())
+ continue;
+ /*
+ Sic: we know that prepare() is not NULL since otherwise
+ trans->no_2pc would have been set.
+ */
+ err= ht->prepare(ht, thd, all);
+ status_var_increment(thd->status_var.ha_prepare_count);
+ if (err)
+ my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
+
+ if (err)
+ goto err;
+
+ need_prepare_ordered|= (ht->prepare_ordered != NULL);
+ need_commit_ordered|= (ht->commit_ordered != NULL);
+ }
+ DEBUG_SYNC(thd, "ha_commit_trans_after_prepare");
+ DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_SUICIDE(););
+
+ if (!is_real_trans)
+ {
+ error= commit_one_phase_2(thd, all, trans, is_real_trans);
+ goto done;
+ }
+
+ DEBUG_SYNC(thd, "ha_commit_trans_before_log_and_order");
+ cookie= tc_log->log_and_order(thd, xid, all, need_prepare_ordered,
+ need_commit_ordered);
+ if (!cookie)
+ goto err;
+
+ DEBUG_SYNC(thd, "ha_commit_trans_after_log_and_order");
+ DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_SUICIDE(););
+
+ error= commit_one_phase_2(thd, all, trans, is_real_trans) ? 2 : 0;
+
+ DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_SUICIDE(););
+ if (tc_log->unlog(cookie, xid))
+ {
+ error= 2; /* Error during commit */
+ goto end;
+ }
+
+done:
+ DBUG_EXECUTE_IF("crash_commit_after", DBUG_SUICIDE(););
+ RUN_HOOK(transaction, after_commit, (thd, FALSE));
+ goto end;
+
+ /* Come here if error and we need to rollback. */
+err:
+ error= 1; /* Transaction was rolled back */
+ /*
+ In parallel replication, rollback is delayed, as there is extra replication
+ book-keeping to be done before rolling back and allowing a conflicting
+ transaction to continue (MDEV-7458).
+ */
+ if (!(thd->rgi_slave && thd->rgi_slave->is_parallel_exec))
+ ha_rollback_trans(thd, all);
+
+end:
+ if (rw_trans && mdl_request.ticket)
+ {
+ /*
+ We do not always immediately release transactional locks
+ after ha_commit_trans() (see uses of ha_enable_transaction()),
+ thus we release the commit blocker lock as soon as it's
+ not needed.
+ */
+ thd->mdl_context.release_lock(mdl_request.ticket);
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ @note
+ This function does not care about global read lock. A caller should.
+
+ @param[in] all Is set in case of explicit commit
+ (COMMIT statement), or implicit commit
+ issued by DDL. Is not set when called
+ at the end of statement, even if
+ autocommit=1.
+*/
+
+int ha_commit_one_phase(THD *thd, bool all)
+{
+ THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
+ /*
+ "real" is a nick name for a transaction for which a commit will
+ make persistent changes. E.g. a 'stmt' transaction inside a 'all'
+ transaction is not 'real': even though it's possible to commit it,
+ the changes are not durable as they might be rolled back if the
+ enclosing 'all' transaction is rolled back.
+ We establish the value of 'is_real_trans' by checking
+ if it's an explicit COMMIT/BEGIN statement, or implicit
+ commit issued by DDL (all == TRUE), or if we're running
+ in autocommit mode (it's only in the autocommit mode
+ ha_commit_one_phase() can be called with an empty
+ transaction.all.ha_list, see why in trans_register_ha()).
+ */
+ bool is_real_trans= ((all || thd->transaction.all.ha_list == 0) &&
+ !(thd->variables.option_bits & OPTION_GTID_BEGIN));
+ int res;
+ DBUG_ENTER("ha_commit_one_phase");
+ if (is_real_trans)
+ {
+ DEBUG_SYNC(thd, "ha_commit_one_phase");
+ if ((res= thd->wait_for_prior_commit()))
+ DBUG_RETURN(res);
+ }
+ res= commit_one_phase_2(thd, all, trans, is_real_trans);
+ DBUG_RETURN(res);
+}
+
+
+static int
+commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans)
+{
+ int error= 0;
+ Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
+ DBUG_ENTER("commit_one_phase_2");
+ if (is_real_trans)
+ DEBUG_SYNC(thd, "commit_one_phase_2");
+ if (ha_info)
+ {
+ for (; ha_info; ha_info= ha_info_next)
+ {
+ int err;
+ handlerton *ht= ha_info->ht();
+ if ((err= ht->commit(ht, thd, all)))
+ {
+ my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
+ error=1;
+ }
+ /* Should this be done only if is_real_trans is set ? */
+ status_var_increment(thd->status_var.ha_commit_count);
+ ha_info_next= ha_info->next();
+ ha_info->reset(); /* keep it conveniently zero-filled */
+ }
+ trans->ha_list= 0;
+ trans->no_2pc=0;
+ if (all)
+ {
+#ifdef HAVE_QUERY_CACHE
+ if (thd->transaction.changed_tables)
+ query_cache.invalidate(thd, thd->transaction.changed_tables);
+#endif
+ }
+ }
+ /* Free resources and perform other cleanup even for 'empty' transactions. */
+ if (is_real_trans)
+ {
+ thd->has_waiter= false;
+ thd->transaction.cleanup();
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+int ha_rollback_trans(THD *thd, bool all)
+{
+ int error=0;
+ THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
+ Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
+ /*
+ "real" is a nick name for a transaction for which a commit will
+ make persistent changes. E.g. a 'stmt' transaction inside a 'all'
+ transaction is not 'real': even though it's possible to commit it,
+ the changes are not durable as they might be rolled back if the
+ enclosing 'all' transaction is rolled back.
+ We establish the value of 'is_real_trans' by checking
+ if it's an explicit COMMIT or BEGIN statement, or implicit
+ commit issued by DDL (in these cases all == TRUE),
+ or if we're running in autocommit mode (it's only in the autocommit mode
+ ha_commit_one_phase() is called with an empty
+ transaction.all.ha_list, see why in trans_register_ha()).
+ */
+ bool is_real_trans=all || thd->transaction.all.ha_list == 0;
+ DBUG_ENTER("ha_rollback_trans");
+
+ /*
+ We must not rollback the normal transaction if a statement
+ transaction is pending.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
+ trans == &thd->transaction.stmt);
+
+ if (thd->in_sub_stmt)
+ {
+ DBUG_ASSERT(0);
+ /*
+ If we are inside stored function or trigger we should not commit or
+ rollback current statement transaction. See comment in ha_commit_trans()
+ call for more information.
+ */
+ if (!all)
+ DBUG_RETURN(0);
+ my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ if (ha_info)
+ {
+ /* Close all cursors that can not survive ROLLBACK */
+ if (is_real_trans) /* not a statement commit */
+ thd->stmt_map.close_transient_cursors();
+
+ for (; ha_info; ha_info= ha_info_next)
+ {
+ int err;
+ handlerton *ht= ha_info->ht();
+ if ((err= ht->rollback(ht, thd, all)))
+ { // cannot happen
+ my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
+ error=1;
+ }
+ status_var_increment(thd->status_var.ha_rollback_count);
+ ha_info_next= ha_info->next();
+ ha_info->reset(); /* keep it conveniently zero-filled */
+ }
+ trans->ha_list= 0;
+ trans->no_2pc=0;
+ }
+
+ /*
+ Thanks to possibility of MDL deadlock rollback request can come even if
+ transaction hasn't been started in any transactional storage engine.
+ */
+ if (is_real_trans && thd->transaction_rollback_request &&
+ thd->transaction.xid_state.xa_state != XA_NOTR)
+ thd->transaction.xid_state.rm_error= thd->get_stmt_da()->sql_errno();
+
+ /* Always cleanup. Even if nht==0. There may be savepoints. */
+ if (is_real_trans)
+ {
+ thd->has_waiter= false;
+ thd->transaction.cleanup();
+ }
+ if (all)
+ thd->transaction_rollback_request= FALSE;
+
+ /*
+ If a non-transactional table was updated, warn; don't warn if this is a
+ slave thread (because when a slave thread executes a ROLLBACK, it has
+ been read from the binary log, so it's 100% sure and normal to produce
+ error ER_WARNING_NOT_COMPLETE_ROLLBACK. If we sent the warning to the
+ slave SQL thread, it would not stop the thread but just be printed in
+ the error log; but we don't want users to wonder why they have this
+ message in the error log, so we don't send it.
+
+ We don't have to test for thd->killed == KILL_SYSTEM_THREAD as
+ it doesn't matter if a warning is pushed to a system thread or not:
+ No one will see it...
+ */
+ if (is_real_trans && thd->transaction.all.modified_non_trans_table &&
+ !thd->slave_thread && thd->killed < KILL_CONNECTION)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARNING_NOT_COMPLETE_ROLLBACK,
+ ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
+ (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE));
+ DBUG_RETURN(error);
+}
+
+
+struct xahton_st {
+ XID *xid;
+ int result;
+};
+
+static my_bool xacommit_handlerton(THD *unused1, plugin_ref plugin,
+ void *arg)
+{
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->recover)
+ {
+ hton->commit_by_xid(hton, ((struct xahton_st *)arg)->xid);
+ ((struct xahton_st *)arg)->result= 0;
+ }
+ return FALSE;
+}
+
+static my_bool xarollback_handlerton(THD *unused1, plugin_ref plugin,
+ void *arg)
+{
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->recover)
+ {
+ hton->rollback_by_xid(hton, ((struct xahton_st *)arg)->xid);
+ ((struct xahton_st *)arg)->result= 0;
+ }
+ return FALSE;
+}
+
+
+int ha_commit_or_rollback_by_xid(XID *xid, bool commit)
+{
+ struct xahton_st xaop;
+ xaop.xid= xid;
+ xaop.result= 1;
+
+ plugin_foreach(NULL, commit ? xacommit_handlerton : xarollback_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &xaop);
+
+ return xaop.result;
+}
+
+
+#ifndef DBUG_OFF
+/**
+ @note
+ This does not need to be multi-byte safe or anything
+*/
+static char* xid_to_str(char *buf, XID *xid)
+{
+ int i;
+ char *s=buf;
+ *s++='\'';
+ for (i=0; i < xid->gtrid_length+xid->bqual_length; i++)
+ {
+ uchar c=(uchar)xid->data[i];
+ /* is_next_dig is set if next character is a number */
+ bool is_next_dig= FALSE;
+ if (i < XIDDATASIZE)
+ {
+ char ch= xid->data[i+1];
+ is_next_dig= (ch >= '0' && ch <='9');
+ }
+ if (i == xid->gtrid_length)
+ {
+ *s++='\'';
+ if (xid->bqual_length)
+ {
+ *s++='.';
+ *s++='\'';
+ }
+ }
+ if (c < 32 || c > 126)
+ {
+ *s++='\\';
+ /*
+ If next character is a number, write current character with
+ 3 octal numbers to ensure that the next number is not seen
+ as part of the octal number
+ */
+ if (c > 077 || is_next_dig)
+ *s++=_dig_vec_lower[c >> 6];
+ if (c > 007 || is_next_dig)
+ *s++=_dig_vec_lower[(c >> 3) & 7];
+ *s++=_dig_vec_lower[c & 7];
+ }
+ else
+ {
+ if (c == '\'' || c == '\\')
+ *s++='\\';
+ *s++=c;
+ }
+ }
+ *s++='\'';
+ *s=0;
+ return buf;
+}
+#endif
+
+/**
+ recover() step of xa.
+
+ @note
+ there are three modes of operation:
+ - automatic recover after a crash
+ in this case commit_list != 0, tc_heuristic_recover==0
+ all xids from commit_list are committed, others are rolled back
+ - manual (heuristic) recover
+ in this case commit_list==0, tc_heuristic_recover != 0
+ DBA has explicitly specified that all prepared transactions should
+ be committed (or rolled back).
+ - no recovery (MySQL did not detect a crash)
+ in this case commit_list==0, tc_heuristic_recover == 0
+ there should be no prepared transactions in this case.
+*/
+struct xarecover_st
+{
+ int len, found_foreign_xids, found_my_xids;
+ XID *list;
+ HASH *commit_list;
+ bool dry_run;
+};
+
+static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin,
+ void *arg)
+{
+ handlerton *hton= plugin_hton(plugin);
+ struct xarecover_st *info= (struct xarecover_st *) arg;
+ int got;
+
+ if (hton->state == SHOW_OPTION_YES && hton->recover)
+ {
+ while ((got= hton->recover(hton, info->list, info->len)) > 0 )
+ {
+ sql_print_information("Found %d prepared transaction(s) in %s",
+ got, hton_name(hton)->str);
+ for (int i=0; i < got; i ++)
+ {
+ my_xid x=info->list[i].get_my_xid();
+ if (!x) // not "mine" - that is generated by external TM
+ {
+#ifndef DBUG_OFF
+ char buf[XIDDATASIZE*4+6]; // see xid_to_str
+ sql_print_information("ignore xid %s", xid_to_str(buf, info->list+i));
+#endif
+ xid_cache_insert(info->list+i, XA_PREPARED);
+ info->found_foreign_xids++;
+ continue;
+ }
+ if (info->dry_run)
+ {
+ info->found_my_xids++;
+ continue;
+ }
+ // recovery mode
+ if (info->commit_list ?
+ my_hash_search(info->commit_list, (uchar *)&x, sizeof(x)) != 0 :
+ tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT)
+ {
+#ifndef DBUG_OFF
+ char buf[XIDDATASIZE*4+6]; // see xid_to_str
+ sql_print_information("commit xid %s", xid_to_str(buf, info->list+i));
+#endif
+ hton->commit_by_xid(hton, info->list+i);
+ }
+ else
+ {
+#ifndef DBUG_OFF
+ char buf[XIDDATASIZE*4+6]; // see xid_to_str
+ sql_print_information("rollback xid %s",
+ xid_to_str(buf, info->list+i));
+#endif
+ hton->rollback_by_xid(hton, info->list+i);
+ }
+ }
+ if (got < info->len)
+ break;
+ }
+ }
+ return FALSE;
+}
+
+int ha_recover(HASH *commit_list)
+{
+ struct xarecover_st info;
+ DBUG_ENTER("ha_recover");
+ info.found_foreign_xids= info.found_my_xids= 0;
+ info.commit_list= commit_list;
+ info.dry_run= (info.commit_list==0 && tc_heuristic_recover==0);
+ info.list= NULL;
+
+ /* commit_list and tc_heuristic_recover cannot be set both */
+ DBUG_ASSERT(info.commit_list==0 || tc_heuristic_recover==0);
+ /* if either is set, total_ha_2pc must be set too */
+ DBUG_ASSERT(info.dry_run || total_ha_2pc>(ulong)opt_bin_log);
+
+ if (total_ha_2pc <= (ulong)opt_bin_log)
+ DBUG_RETURN(0);
+
+ if (info.commit_list)
+ sql_print_information("Starting crash recovery...");
+
+ for (info.len= MAX_XID_LIST_SIZE ;
+ info.list==0 && info.len > MIN_XID_LIST_SIZE; info.len/=2)
+ {
+ info.list=(XID *)my_malloc(info.len*sizeof(XID), MYF(0));
+ }
+ if (!info.list)
+ {
+ sql_print_error(ER(ER_OUTOFMEMORY),
+ static_cast<int>(info.len*sizeof(XID)));
+ DBUG_RETURN(1);
+ }
+
+ plugin_foreach(NULL, xarecover_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &info);
+
+ my_free(info.list);
+ if (info.found_foreign_xids)
+ sql_print_warning("Found %d prepared XA transactions",
+ info.found_foreign_xids);
+ if (info.dry_run && info.found_my_xids)
+ {
+ sql_print_error("Found %d prepared transactions! It means that mysqld was "
+ "not shut down properly last time and critical recovery "
+ "information (last binlog or %s file) was manually deleted "
+ "after a crash. You have to start mysqld with "
+ "--tc-heuristic-recover switch to commit or rollback "
+ "pending transactions.",
+ info.found_my_xids, opt_tc_log_file);
+ DBUG_RETURN(1);
+ }
+ if (info.commit_list)
+ sql_print_information("Crash recovery finished.");
+ DBUG_RETURN(0);
+}
+
+/**
+ return the list of XID's to a client, the same way SHOW commands do.
+
+ @note
+ I didn't find in XA specs that an RM cannot return the same XID twice,
+ so mysql_xa_recover does not filter XID's to ensure uniqueness.
+ It can be easily fixed later, if necessary.
+*/
+bool mysql_xa_recover(THD *thd)
+{
+ List<Item> field_list;
+ Protocol *protocol= thd->protocol;
+ int i=0;
+ XID_STATE *xs;
+ DBUG_ENTER("mysql_xa_recover");
+
+ field_list.push_back(new Item_int("formatID", 0, MY_INT32_NUM_DECIMAL_DIGITS));
+ field_list.push_back(new Item_int("gtrid_length", 0, MY_INT32_NUM_DECIMAL_DIGITS));
+ field_list.push_back(new Item_int("bqual_length", 0, MY_INT32_NUM_DECIMAL_DIGITS));
+ field_list.push_back(new Item_empty_string("data",XIDDATASIZE));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(1);
+
+ mysql_mutex_lock(&LOCK_xid_cache);
+ while ((xs= (XID_STATE*) my_hash_element(&xid_cache, i++)))
+ {
+ if (xs->xa_state==XA_PREPARED)
+ {
+ protocol->prepare_for_resend();
+ protocol->store_longlong((longlong)xs->xid.formatID, FALSE);
+ protocol->store_longlong((longlong)xs->xid.gtrid_length, FALSE);
+ protocol->store_longlong((longlong)xs->xid.bqual_length, FALSE);
+ protocol->store(xs->xid.data, xs->xid.gtrid_length+xs->xid.bqual_length,
+ &my_charset_bin);
+ if (protocol->write())
+ {
+ mysql_mutex_unlock(&LOCK_xid_cache);
+ DBUG_RETURN(1);
+ }
+ }
+ }
+
+ mysql_mutex_unlock(&LOCK_xid_cache);
+ my_eof(thd);
+ DBUG_RETURN(0);
+}
+
+/*
+ Called by engine to notify TC that a new commit checkpoint has been reached.
+ See comments on handlerton method commit_checkpoint_request() for details.
+*/
+void
+commit_checkpoint_notify_ha(handlerton *hton, void *cookie)
+{
+ tc_log->commit_checkpoint_notify(cookie);
+}
+
+
+/**
+ @details
+ This function should be called when MySQL sends rows of a SELECT result set
+ or the EOF mark to the client. It releases a possible adaptive hash index
+ S-latch held by thd in InnoDB and also releases a possible InnoDB query
+ FIFO ticket to enter InnoDB. To save CPU time, InnoDB allows a thd to
+ keep them over several calls of the InnoDB handler interface when a join
+ is executed. But when we let the control to pass to the client they have
+ to be released because if the application program uses mysql_use_result(),
+ it may deadlock on the S-latch if the application on another connection
+ performs another SQL query. In MySQL-4.1 this is even more important because
+ there a connection can have several SELECT queries open at the same time.
+
+ @param thd the thread handle of the current connection
+
+ @return
+ always 0
+*/
+
+int ha_release_temporary_latches(THD *thd)
+{
+ Ha_trx_info *info;
+
+ /*
+ Note that below we assume that only transactional storage engines
+ may need release_temporary_latches(). If this will ever become false,
+ we could iterate on thd->open_tables instead (and remove duplicates
+ as if (!seen[hton->slot]) { seen[hton->slot]=1; ... }).
+ */
+ for (info= thd->transaction.stmt.ha_list; info; info= info->next())
+ {
+ handlerton *hton= info->ht();
+ if (hton && hton->release_temporary_latches)
+ hton->release_temporary_latches(hton, thd);
+ }
+ return 0;
+}
+
+/**
+ Check if all storage engines used in transaction agree that after
+ rollback to savepoint it is safe to release MDL locks acquired after
+ savepoint creation.
+
+ @param thd The client thread that executes the transaction.
+
+ @return true - It is safe to release MDL locks.
+ false - If it is not.
+*/
+bool ha_rollback_to_savepoint_can_release_mdl(THD *thd)
+{
+ Ha_trx_info *ha_info;
+ THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
+ &thd->transaction.all);
+
+ DBUG_ENTER("ha_rollback_to_savepoint_can_release_mdl");
+
+ /**
+ Checking whether it is safe to release metadata locks after rollback to
+ savepoint in all the storage engines that are part of the transaction.
+ */
+ for (ha_info= trans->ha_list; ha_info; ha_info= ha_info->next())
+ {
+ handlerton *ht= ha_info->ht();
+ DBUG_ASSERT(ht);
+
+ if (ht->savepoint_rollback_can_release_mdl == 0 ||
+ ht->savepoint_rollback_can_release_mdl(ht, thd) == false)
+ DBUG_RETURN(false);
+ }
+
+ DBUG_RETURN(true);
+}
+
+int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
+{
+ int error=0;
+ THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
+ &thd->transaction.all);
+ Ha_trx_info *ha_info, *ha_info_next;
+
+ DBUG_ENTER("ha_rollback_to_savepoint");
+
+ trans->no_2pc=0;
+ /*
+ rolling back to savepoint in all storage engines that were part of the
+ transaction when the savepoint was set
+ */
+ for (ha_info= sv->ha_list; ha_info; ha_info= ha_info->next())
+ {
+ int err;
+ handlerton *ht= ha_info->ht();
+ DBUG_ASSERT(ht);
+ DBUG_ASSERT(ht->savepoint_set != 0);
+ if ((err= ht->savepoint_rollback(ht, thd,
+ (uchar *)(sv+1)+ht->savepoint_offset)))
+ { // cannot happen
+ my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
+ error=1;
+ }
+ status_var_increment(thd->status_var.ha_savepoint_rollback_count);
+ trans->no_2pc|= ht->prepare == 0;
+ }
+ /*
+ rolling back the transaction in all storage engines that were not part of
+ the transaction when the savepoint was set
+ */
+ for (ha_info= trans->ha_list; ha_info != sv->ha_list;
+ ha_info= ha_info_next)
+ {
+ int err;
+ handlerton *ht= ha_info->ht();
+ if ((err= ht->rollback(ht, thd, !thd->in_sub_stmt)))
+ { // cannot happen
+ my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
+ error=1;
+ }
+ status_var_increment(thd->status_var.ha_rollback_count);
+ ha_info_next= ha_info->next();
+ ha_info->reset(); /* keep it conveniently zero-filled */
+ }
+ trans->ha_list= sv->ha_list;
+ DBUG_RETURN(error);
+}
+
+/**
+ @note
+ according to the sql standard (ISO/IEC 9075-2:2003)
+ section "4.33.4 SQL-statements and transaction states",
+ SAVEPOINT is *not* transaction-initiating SQL-statement
+*/
+int ha_savepoint(THD *thd, SAVEPOINT *sv)
+{
+ int error=0;
+ THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
+ &thd->transaction.all);
+ Ha_trx_info *ha_info= trans->ha_list;
+ DBUG_ENTER("ha_savepoint");
+
+ for (; ha_info; ha_info= ha_info->next())
+ {
+ int err;
+ handlerton *ht= ha_info->ht();
+ DBUG_ASSERT(ht);
+ if (! ht->savepoint_set)
+ {
+ my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "SAVEPOINT");
+ error=1;
+ break;
+ }
+ if ((err= ht->savepoint_set(ht, thd, (uchar *)(sv+1)+ht->savepoint_offset)))
+ { // cannot happen
+ my_error(ER_GET_ERRNO, MYF(0), err, hton_name(ht)->str);
+ error=1;
+ }
+ status_var_increment(thd->status_var.ha_savepoint_count);
+ }
+ /*
+ Remember the list of registered storage engines. All new
+ engines are prepended to the beginning of the list.
+ */
+ sv->ha_list= trans->ha_list;
+
+ DBUG_RETURN(error);
+}
+
+int ha_release_savepoint(THD *thd, SAVEPOINT *sv)
+{
+ int error=0;
+ Ha_trx_info *ha_info= sv->ha_list;
+ DBUG_ENTER("ha_release_savepoint");
+
+ for (; ha_info; ha_info= ha_info->next())
+ {
+ int err;
+ handlerton *ht= ha_info->ht();
+ /* Savepoint life time is enclosed into transaction life time. */
+ DBUG_ASSERT(ht);
+ if (!ht->savepoint_release)
+ continue;
+ if ((err= ht->savepoint_release(ht, thd,
+ (uchar *)(sv+1) + ht->savepoint_offset)))
+ { // cannot happen
+ my_error(ER_GET_ERRNO, MYF(0), err, hton_name(ht)->str);
+ error=1;
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+
+static my_bool snapshot_handlerton(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES &&
+ hton->start_consistent_snapshot)
+ {
+ hton->start_consistent_snapshot(hton, thd);
+ *((bool *)arg)= false;
+ }
+ return FALSE;
+}
+
+int ha_start_consistent_snapshot(THD *thd)
+{
+ bool warn= true;
+
+ /*
+ Holding the LOCK_commit_ordered mutex ensures that we get the same
+ snapshot for all engines (including the binary log). This allows us
+ among other things to do backups with
+ START TRANSACTION WITH CONSISTENT SNAPSHOT and
+ have a consistent binlog position.
+ */
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ plugin_foreach(thd, snapshot_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, &warn);
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+
+ /*
+ Same idea as when one wants to CREATE TABLE in one engine which does not
+ exist:
+ */
+ if (warn)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
+ "This MySQL server does not support any "
+ "consistent-read capable storage engine");
+ return 0;
+}
+
+
+static my_bool flush_handlerton(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->flush_logs &&
+ hton->flush_logs(hton))
+ return TRUE;
+ return FALSE;
+}
+
+
+bool ha_flush_logs(handlerton *db_type)
+{
+ if (db_type == NULL)
+ {
+ if (plugin_foreach(NULL, flush_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, 0))
+ return TRUE;
+ }
+ else
+ {
+ if (db_type->state != SHOW_OPTION_YES ||
+ (db_type->flush_logs && db_type->flush_logs(db_type)))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ @brief make canonical filename
+
+ @param[in] file table handler
+ @param[in] path original path
+ @param[out] tmp_path buffer for canonized path
+
+ @details Lower case db name and table name path parts for
+ non file based tables when lower_case_table_names
+ is 2 (store as is, compare in lower case).
+ Filesystem path prefix (mysql_data_home or tmpdir)
+ is left intact.
+
+ @note tmp_path may be left intact if no conversion was
+ performed.
+
+ @retval canonized path
+
+ @todo This may be done more efficiently when table path
+ gets built. Convert this function to something like
+ ASSERT_CANONICAL_FILENAME.
+*/
+const char *get_canonical_filename(handler *file, const char *path,
+ char *tmp_path)
+{
+ uint i;
+ if (lower_case_table_names != 2 || (file->ha_table_flags() & HA_FILE_BASED))
+ return path;
+
+ for (i= 0; i <= mysql_tmpdir_list.max; i++)
+ {
+ if (is_prefix(path, mysql_tmpdir_list.list[i]))
+ return path;
+ }
+
+ /* Ensure that table handler get path in lower case */
+ if (tmp_path != path)
+ strmov(tmp_path, path);
+
+ /*
+ we only should turn into lowercase database/table part
+ so start the process after homedirectory
+ */
+ my_casedn_str(files_charset_info, tmp_path + mysql_data_home_len);
+ return tmp_path;
+}
+
+
+/**
+ An interceptor to hijack the text of the error message without
+ setting an error in the thread. We need the text to present it
+ in the form of a warning to the user.
+*/
+
+struct Ha_delete_table_error_handler: public Internal_error_handler
+{
+public:
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+ char buff[MYSQL_ERRMSG_SIZE];
+};
+
+
+bool
+Ha_delete_table_error_handler::
+handle_condition(THD *,
+ uint,
+ const char*,
+ Sql_condition::enum_warning_level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ /* Grab the error message */
+ strmake_buf(buff, msg);
+ return TRUE;
+}
+
+
+/** @brief
+ This should return ENOENT if the file doesn't exists.
+ The .frm file will be deleted only if we return 0 or ENOENT
+*/
+int ha_delete_table(THD *thd, handlerton *table_type, const char *path,
+ const char *db, const char *alias, bool generate_warning)
+{
+ handler *file;
+ char tmp_path[FN_REFLEN];
+ int error;
+ TABLE dummy_table;
+ TABLE_SHARE dummy_share;
+ DBUG_ENTER("ha_delete_table");
+
+ /* table_type is NULL in ALTER TABLE when renaming only .frm files */
+ if (table_type == NULL || table_type == view_pseudo_hton ||
+ ! (file=get_new_handler((TABLE_SHARE*)0, thd->mem_root, table_type)))
+ DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
+
+ bzero((char*) &dummy_table, sizeof(dummy_table));
+ bzero((char*) &dummy_share, sizeof(dummy_share));
+ dummy_table.s= &dummy_share;
+
+ path= get_canonical_filename(file, path, tmp_path);
+ if ((error= file->ha_delete_table(path)) && generate_warning)
+ {
+ /*
+ Because file->print_error() use my_error() to generate the error message
+ we use an internal error handler to intercept it and store the text
+ in a temporary buffer. Later the message will be presented to user
+ as a warning.
+ */
+ Ha_delete_table_error_handler ha_delete_table_error_handler;
+
+ /* Fill up strucutures that print_error may need */
+ dummy_share.path.str= (char*) path;
+ dummy_share.path.length= strlen(path);
+ dummy_share.normalized_path= dummy_share.path;
+ dummy_share.db.str= (char*) db;
+ dummy_share.db.length= strlen(db);
+ dummy_share.table_name.str= (char*) alias;
+ dummy_share.table_name.length= strlen(alias);
+ dummy_table.alias.set(alias, dummy_share.table_name.length,
+ table_alias_charset);
+
+ file->change_table_ptr(&dummy_table, &dummy_share);
+
+ thd->push_internal_handler(&ha_delete_table_error_handler);
+ file->print_error(error, 0);
+
+ thd->pop_internal_handler();
+
+ /*
+ XXX: should we convert *all* errors to warnings here?
+ What if the error is fatal?
+ */
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, error,
+ ha_delete_table_error_handler.buff);
+ }
+ delete file;
+
+ DBUG_RETURN(error);
+}
+
+/****************************************************************************
+** General handler functions
+****************************************************************************/
+handler *handler::clone(const char *name, MEM_ROOT *mem_root)
+{
+ handler *new_handler= get_new_handler(table->s, mem_root, ht);
+
+ if (!new_handler)
+ return NULL;
+ if (new_handler->set_ha_share_ref(ha_share))
+ goto err;
+
+ /*
+ Allocate handler->ref here because otherwise ha_open will allocate it
+ on this->table->mem_root and we will not be able to reclaim that memory
+ when the clone handler object is destroyed.
+ */
+
+ if (!(new_handler->ref= (uchar*) alloc_root(mem_root,
+ ALIGN_SIZE(ref_length)*2)))
+ goto err;
+
+ /*
+ TODO: Implement a more efficient way to have more than one index open for
+ the same table instance. The ha_open call is not cachable for clone.
+
+ This is not critical as the engines already have the table open
+ and should be able to use the original instance of the table.
+ */
+ if (new_handler->ha_open(table, name, table->db_stat,
+ HA_OPEN_IGNORE_IF_LOCKED))
+ goto err;
+
+ return new_handler;
+
+err:
+ delete new_handler;
+ return NULL;
+}
+
+
+double handler::keyread_time(uint index, uint ranges, ha_rows rows)
+{
+ /*
+ It is assumed that we will read trough the whole key range and that all
+ key blocks are half full (normally things are much better). It is also
+ assumed that each time we read the next key from the index, the handler
+ performs a random seek, thus the cost is proportional to the number of
+ blocks read. This model does not take into account clustered indexes -
+ engines that support that (e.g. InnoDB) may want to overwrite this method.
+ The model counts in the time to read index entries from cache.
+ */
+ ulong len= table->key_info[index].key_length + ref_length;
+ if (index == table->s->primary_key && table->file->primary_key_is_clustered())
+ len= table->s->stored_rec_length;
+ double keys_per_block= (stats.block_size/2.0/len+1);
+ return (rows + keys_per_block-1)/ keys_per_block +
+ len*rows/(stats.block_size+1)/TIME_FOR_COMPARE ;
+}
+
+void **handler::ha_data(THD *thd) const
+{
+ return thd_ha_data(thd, ht);
+}
+
+THD *handler::ha_thd(void) const
+{
+ DBUG_ASSERT(!table || !table->in_use || table->in_use == current_thd);
+ return (table && table->in_use) ? table->in_use : current_thd;
+}
+
+void handler::unbind_psi()
+{
+ /*
+ Notify the instrumentation that this table is not owned
+ by this thread any more.
+ */
+ PSI_CALL_unbind_table(m_psi);
+}
+
+void handler::rebind_psi()
+{
+ /*
+ Notify the instrumentation that this table is now owned
+ by this thread.
+ */
+ m_psi= PSI_CALL_rebind_table(ha_table_share_psi(), this, m_psi);
+}
+
+
+PSI_table_share *handler::ha_table_share_psi() const
+{
+ return table_share->m_psi;
+}
+
+/** @brief
+ Open database-handler.
+
+ IMPLEMENTATION
+ Try O_RDONLY if cannot open as O_RDWR
+ Don't wait for locks if not HA_OPEN_WAIT_IF_LOCKED is set
+*/
+int handler::ha_open(TABLE *table_arg, const char *name, int mode,
+ uint test_if_locked)
+{
+ int error;
+ DBUG_ENTER("handler::ha_open");
+ DBUG_PRINT("enter",
+ ("name: %s db_type: %d db_stat: %d mode: %d lock_test: %d",
+ name, ht->db_type, table_arg->db_stat, mode,
+ test_if_locked));
+
+ table= table_arg;
+ DBUG_ASSERT(table->s == table_share);
+ DBUG_ASSERT(m_lock_type == F_UNLCK);
+ DBUG_PRINT("info", ("old m_lock_type: %d F_UNLCK %d", m_lock_type, F_UNLCK));
+ DBUG_ASSERT(alloc_root_inited(&table->mem_root));
+
+ if ((error=open(name,mode,test_if_locked)))
+ {
+ if ((error == EACCES || error == EROFS) && mode == O_RDWR &&
+ (table->db_stat & HA_TRY_READ_ONLY))
+ {
+ table->db_stat|=HA_READ_ONLY;
+ error=open(name,O_RDONLY,test_if_locked);
+ }
+ }
+ if (error)
+ {
+ my_errno= error; /* Safeguard */
+ DBUG_PRINT("error",("error: %d errno: %d",error,errno));
+ }
+ else
+ {
+ DBUG_ASSERT(m_psi == NULL);
+ DBUG_ASSERT(table_share != NULL);
+ /*
+ Do not call this for partitions handlers, since it may take too much
+ resources.
+ So only use the m_psi on table level, not for individual partitions.
+ */
+ if (!(test_if_locked & HA_OPEN_NO_PSI_CALL))
+ {
+ m_psi= PSI_CALL_open_table(ha_table_share_psi(), this);
+ }
+
+ if (table->s->db_options_in_use & HA_OPTION_READ_ONLY_DATA)
+ table->db_stat|=HA_READ_ONLY;
+ (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL
+
+ /* ref is already allocated for us if we're called from handler::clone() */
+ if (!ref && !(ref= (uchar*) alloc_root(&table->mem_root,
+ ALIGN_SIZE(ref_length)*2)))
+ {
+ ha_close();
+ error=HA_ERR_OUT_OF_MEM;
+ }
+ else
+ dup_ref=ref+ALIGN_SIZE(ref_length);
+ cached_table_flags= table_flags();
+ }
+ reset_statistics();
+ internal_tmp_table= MY_TEST(test_if_locked & HA_OPEN_INTERNAL_TABLE);
+ DBUG_RETURN(error);
+}
+
+int handler::ha_close(void)
+{
+ DBUG_ENTER("ha_close");
+ /*
+ Increment global statistics for temporary tables.
+ In_use is 0 for tables that was closed from the table cache.
+ */
+ if (table->in_use)
+ status_var_add(table->in_use->status_var.rows_tmp_read, rows_tmp_read);
+ PSI_CALL_close_table(m_psi);
+ m_psi= NULL; /* instrumentation handle, invalid after close_table() */
+
+ DBUG_ASSERT(m_lock_type == F_UNLCK);
+ DBUG_ASSERT(inited == NONE);
+ DBUG_RETURN(close());
+}
+
+int handler::ha_rnd_next(uchar *buf)
+{
+ int result;
+ DBUG_ENTER("handler::ha_rnd_next");
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(inited == RND);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, MAX_KEY, 0,
+ { result= rnd_next(buf); })
+ if (!result)
+ {
+ update_rows_read();
+ increment_statistics(&SSV::ha_read_rnd_next_count);
+ }
+ else if (result == HA_ERR_RECORD_DELETED)
+ increment_statistics(&SSV::ha_read_rnd_deleted_count);
+ else
+ increment_statistics(&SSV::ha_read_rnd_next_count);
+
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ DBUG_RETURN(result);
+}
+
+int handler::ha_rnd_pos(uchar *buf, uchar *pos)
+{
+ int result;
+ DBUG_ENTER("handler::ha_rnd_pos");
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ /* TODO: Find out how to solve ha_rnd_pos when finding duplicate update. */
+ /* DBUG_ASSERT(inited == RND); */
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, MAX_KEY, 0,
+ { result= rnd_pos(buf, pos); })
+ increment_statistics(&SSV::ha_read_rnd_count);
+ if (!result)
+ update_rows_read();
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ DBUG_RETURN(result);
+}
+
+int handler::ha_index_read_map(uchar *buf, const uchar *key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag)
+{
+ int result;
+ DBUG_ENTER("handler::ha_index_read_map");
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(inited==INDEX);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0,
+ { result= index_read_map(buf, key, keypart_map, find_flag); })
+ increment_statistics(&SSV::ha_read_key_count);
+ if (!result)
+ update_index_statistics();
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ DBUG_RETURN(result);
+}
+
+/*
+ @note: Other index lookup/navigation functions require prior
+ handler->index_init() call. This function is different, it requires
+ that the scan is not initialized, and accepts "uint index" as an argument.
+*/
+
+int handler::ha_index_read_idx_map(uchar *buf, uint index, const uchar *key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag)
+{
+ int result;
+ DBUG_ASSERT(inited==NONE);
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(end_range == NULL);
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, index, 0,
+ { result= index_read_idx_map(buf, index, key, keypart_map, find_flag); })
+ increment_statistics(&SSV::ha_read_key_count);
+ if (!result)
+ {
+ update_rows_read();
+ index_rows_read[index]++;
+ }
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ return result;
+}
+
+int handler::ha_index_next(uchar * buf)
+{
+ int result;
+ DBUG_ENTER("handler::ha_index_next");
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(inited==INDEX);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0,
+ { result= index_next(buf); })
+ increment_statistics(&SSV::ha_read_next_count);
+ if (!result)
+ update_index_statistics();
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ DBUG_RETURN(result);
+}
+
+int handler::ha_index_prev(uchar * buf)
+{
+ int result;
+ DBUG_ENTER("handler::ha_index_prev");
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(inited==INDEX);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0,
+ { result= index_prev(buf); })
+ increment_statistics(&SSV::ha_read_prev_count);
+ if (!result)
+ update_index_statistics();
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ DBUG_RETURN(result);
+}
+
+int handler::ha_index_first(uchar * buf)
+{
+ int result;
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(inited==INDEX);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0,
+ { result= index_first(buf); })
+ increment_statistics(&SSV::ha_read_first_count);
+ if (!result)
+ update_index_statistics();
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ return result;
+}
+
+int handler::ha_index_last(uchar * buf)
+{
+ int result;
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(inited==INDEX);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0,
+ { result= index_last(buf); })
+ increment_statistics(&SSV::ha_read_last_count);
+ if (!result)
+ update_index_statistics();
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ return result;
+}
+
+int handler::ha_index_next_same(uchar *buf, const uchar *key, uint keylen)
+{
+ int result;
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ASSERT(inited==INDEX);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0,
+ { result= index_next_same(buf, key, keylen); })
+ increment_statistics(&SSV::ha_read_next_count);
+ if (!result)
+ update_index_statistics();
+ table->status=result ? STATUS_NOT_FOUND: 0;
+ return result;
+}
+
+/* Initialize handler for random reading, with error handling */
+
+int handler::ha_rnd_init_with_error(bool scan)
+{
+ int error;
+ if (!(error= ha_rnd_init(scan)))
+ return 0;
+ table->file->print_error(error, MYF(0));
+ return error;
+}
+
+
+/**
+ Read first row (only) from a table.
+
+ This is never called for InnoDB tables, as these table types
+ has the HA_STATS_RECORDS_IS_EXACT set.
+*/
+int handler::read_first_row(uchar * buf, uint primary_key)
+{
+ register int error;
+ DBUG_ENTER("handler::read_first_row");
+
+ /*
+ If there is very few deleted rows in the table, find the first row by
+ scanning the table.
+ TODO remove the test for HA_READ_ORDER
+ */
+ if (stats.deleted < 10 || primary_key >= MAX_KEY ||
+ !(index_flags(primary_key, 0, 0) & HA_READ_ORDER))
+ {
+ if (!(error= ha_rnd_init(1)))
+ {
+ while ((error= ha_rnd_next(buf)) == HA_ERR_RECORD_DELETED)
+ /* skip deleted row */;
+ const int end_error= ha_rnd_end();
+ if (!error)
+ error= end_error;
+ }
+ }
+ else
+ {
+ /* Find the first row through the primary key */
+ if (!(error= ha_index_init(primary_key, 0)))
+ {
+ error= ha_index_first(buf);
+ const int end_error= ha_index_end();
+ if (!error)
+ error= end_error;
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ Generate the next auto-increment number based on increment and offset.
+ computes the lowest number
+ - strictly greater than "nr"
+ - of the form: auto_increment_offset + N * auto_increment_increment
+ If overflow happened then return MAX_ULONGLONG value as an
+ indication of overflow.
+ In most cases increment= offset= 1, in which case we get:
+ @verbatim 1,2,3,4,5,... @endverbatim
+ If increment=10 and offset=5 and previous number is 1, we get:
+ @verbatim 1,5,15,25,35,... @endverbatim
+*/
+inline ulonglong
+compute_next_insert_id(ulonglong nr,struct system_variables *variables)
+{
+ const ulonglong save_nr= nr;
+
+ if (variables->auto_increment_increment == 1)
+ nr= nr + 1; // optimization of the formula below
+ else
+ {
+ nr= (((nr+ variables->auto_increment_increment -
+ variables->auto_increment_offset)) /
+ (ulonglong) variables->auto_increment_increment);
+ nr= (nr* (ulonglong) variables->auto_increment_increment +
+ variables->auto_increment_offset);
+ }
+
+ if (unlikely(nr <= save_nr))
+ return ULONGLONG_MAX;
+
+ return nr;
+}
+
+
+void handler::adjust_next_insert_id_after_explicit_value(ulonglong nr)
+{
+ /*
+ If we have set THD::next_insert_id previously and plan to insert an
+ explicitely-specified value larger than this, we need to increase
+ THD::next_insert_id to be greater than the explicit value.
+ */
+ if ((next_insert_id > 0) && (nr >= next_insert_id))
+ set_next_insert_id(compute_next_insert_id(nr, &table->in_use->variables));
+}
+
+
+/** @brief
+ Computes the largest number X:
+ - smaller than or equal to "nr"
+ - of the form: auto_increment_offset + N * auto_increment_increment
+ where N>=0.
+
+ SYNOPSIS
+ prev_insert_id
+ nr Number to "round down"
+ variables variables struct containing auto_increment_increment and
+ auto_increment_offset
+
+ RETURN
+ The number X if it exists, "nr" otherwise.
+*/
+inline ulonglong
+prev_insert_id(ulonglong nr, struct system_variables *variables)
+{
+ if (unlikely(nr < variables->auto_increment_offset))
+ {
+ /*
+ There's nothing good we can do here. That is a pathological case, where
+ the offset is larger than the column's max possible value, i.e. not even
+ the first sequence value may be inserted. User will receive warning.
+ */
+ DBUG_PRINT("info",("auto_increment: nr: %lu cannot honour "
+ "auto_increment_offset: %lu",
+ (ulong) nr, variables->auto_increment_offset));
+ return nr;
+ }
+ if (variables->auto_increment_increment == 1)
+ return nr; // optimization of the formula below
+ nr= (((nr - variables->auto_increment_offset)) /
+ (ulonglong) variables->auto_increment_increment);
+ return (nr * (ulonglong) variables->auto_increment_increment +
+ variables->auto_increment_offset);
+}
+
+
+/**
+ Update the auto_increment field if necessary.
+
+ Updates columns with type NEXT_NUMBER if:
+
+ - If column value is set to NULL (in which case
+ auto_increment_field_not_null is 0)
+ - If column is set to 0 and (sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO) is not
+ set. In the future we will only set NEXT_NUMBER fields if one sets them
+ to NULL (or they are not included in the insert list).
+
+ In those cases, we check if the currently reserved interval still has
+ values we have not used. If yes, we pick the smallest one and use it.
+ Otherwise:
+
+ - If a list of intervals has been provided to the statement via SET
+ INSERT_ID or via an Intvar_log_event (in a replication slave), we pick the
+ first unused interval from this list, consider it as reserved.
+
+ - Otherwise we set the column for the first row to the value
+ next_insert_id(get_auto_increment(column))) which is usually
+ max-used-column-value+1.
+ We call get_auto_increment() for the first row in a multi-row
+ statement. get_auto_increment() will tell us the interval of values it
+ reserved for us.
+
+ - In both cases, for the following rows we use those reserved values without
+ calling the handler again (we just progress in the interval, computing
+ each new value from the previous one). Until we have exhausted them, then
+ we either take the next provided interval or call get_auto_increment()
+ again to reserve a new interval.
+
+ - In both cases, the reserved intervals are remembered in
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog if statement-based
+ binlogging; the last reserved interval is remembered in
+ auto_inc_interval_for_cur_row. The number of reserved intervals is
+ remembered in auto_inc_intervals_count. It differs from the number of
+ elements in thd->auto_inc_intervals_in_cur_stmt_for_binlog() because the
+ latter list is cumulative over all statements forming one binlog event
+ (when stored functions and triggers are used), and collapses two
+ contiguous intervals in one (see its append() method).
+
+ The idea is that generated auto_increment values are predictable and
+ independent of the column values in the table. This is needed to be
+ able to replicate into a table that already has rows with a higher
+ auto-increment value than the one that is inserted.
+
+ After we have already generated an auto-increment number and the user
+ inserts a column with a higher value than the last used one, we will
+ start counting from the inserted value.
+
+ This function's "outputs" are: the table's auto_increment field is filled
+ with a value, thd->next_insert_id is filled with the value to use for the
+ next row, if a value was autogenerated for the current row it is stored in
+ thd->insert_id_for_cur_row, if get_auto_increment() was called
+ thd->auto_inc_interval_for_cur_row is modified, if that interval is not
+ present in thd->auto_inc_intervals_in_cur_stmt_for_binlog it is added to
+ this list.
+
+ @todo
+ Replace all references to "next number" or NEXT_NUMBER to
+ "auto_increment", everywhere (see below: there is
+ table->auto_increment_field_not_null, and there also exists
+ table->next_number_field, it's not consistent).
+
+ @retval
+ 0 ok
+ @retval
+ HA_ERR_AUTOINC_READ_FAILED get_auto_increment() was called and
+ returned ~(ulonglong) 0
+ @retval
+ HA_ERR_AUTOINC_ERANGE storing value in field caused strict mode
+ failure.
+*/
+
+#define AUTO_INC_DEFAULT_NB_ROWS 1 // Some prefer 1024 here
+#define AUTO_INC_DEFAULT_NB_MAX_BITS 16
+#define AUTO_INC_DEFAULT_NB_MAX ((1 << AUTO_INC_DEFAULT_NB_MAX_BITS) - 1)
+
+int handler::update_auto_increment()
+{
+ ulonglong nr, nb_reserved_values;
+ bool append= FALSE;
+ THD *thd= table->in_use;
+ struct system_variables *variables= &thd->variables;
+ int result=0, tmp;
+ enum enum_check_fields save_count_cuted_fields;
+ DBUG_ENTER("handler::update_auto_increment");
+
+ /*
+ next_insert_id is a "cursor" into the reserved interval, it may go greater
+ than the interval, but not smaller.
+ */
+ DBUG_ASSERT(next_insert_id >= auto_inc_interval_for_cur_row.minimum());
+
+ if ((nr= table->next_number_field->val_int()) != 0 ||
+ (table->auto_increment_field_not_null &&
+ thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO))
+ {
+ /*
+ Update next_insert_id if we had already generated a value in this
+ statement (case of INSERT VALUES(null),(3763),(null):
+ the last NULL needs to insert 3764, not the value of the first NULL plus
+ 1).
+ Ignore negative values.
+ */
+ if ((longlong) nr > 0 || (table->next_number_field->flags & UNSIGNED_FLAG))
+ adjust_next_insert_id_after_explicit_value(nr);
+ insert_id_for_cur_row= 0; // didn't generate anything
+ DBUG_RETURN(0);
+ }
+
+ if ((nr= next_insert_id) >= auto_inc_interval_for_cur_row.maximum())
+ {
+ /* next_insert_id is beyond what is reserved, so we reserve more. */
+ const Discrete_interval *forced=
+ thd->auto_inc_intervals_forced.get_next();
+ if (forced != NULL)
+ {
+ nr= forced->minimum();
+ nb_reserved_values= forced->values();
+ }
+ else
+ {
+ /*
+ handler::estimation_rows_to_insert was set by
+ handler::ha_start_bulk_insert(); if 0 it means "unknown".
+ */
+ ulonglong nb_desired_values;
+ /*
+ If an estimation was given to the engine:
+ - use it.
+ - if we already reserved numbers, it means the estimation was
+ not accurate, then we'll reserve 2*AUTO_INC_DEFAULT_NB_ROWS the 2nd
+ time, twice that the 3rd time etc.
+ If no estimation was given, use those increasing defaults from the
+ start, starting from AUTO_INC_DEFAULT_NB_ROWS.
+ Don't go beyond a max to not reserve "way too much" (because
+ reservation means potentially losing unused values).
+ Note that in prelocked mode no estimation is given.
+ */
+
+ if ((auto_inc_intervals_count == 0) && (estimation_rows_to_insert > 0))
+ nb_desired_values= estimation_rows_to_insert;
+ else if ((auto_inc_intervals_count == 0) &&
+ (thd->lex->many_values.elements > 0))
+ {
+ /*
+ For multi-row inserts, if the bulk inserts cannot be started, the
+ handler::estimation_rows_to_insert will not be set. But we still
+ want to reserve the autoinc values.
+ */
+ nb_desired_values= thd->lex->many_values.elements;
+ }
+ else /* go with the increasing defaults */
+ {
+ /* avoid overflow in formula, with this if() */
+ if (auto_inc_intervals_count <= AUTO_INC_DEFAULT_NB_MAX_BITS)
+ {
+ nb_desired_values= AUTO_INC_DEFAULT_NB_ROWS *
+ (1 << auto_inc_intervals_count);
+ set_if_smaller(nb_desired_values, AUTO_INC_DEFAULT_NB_MAX);
+ }
+ else
+ nb_desired_values= AUTO_INC_DEFAULT_NB_MAX;
+ }
+ get_auto_increment(variables->auto_increment_offset,
+ variables->auto_increment_increment,
+ nb_desired_values, &nr,
+ &nb_reserved_values);
+ if (nr == ULONGLONG_MAX)
+ DBUG_RETURN(HA_ERR_AUTOINC_READ_FAILED); // Mark failure
+
+ /*
+ That rounding below should not be needed when all engines actually
+ respect offset and increment in get_auto_increment(). But they don't
+ so we still do it. Wonder if for the not-first-in-index we should do
+ it. Hope that this rounding didn't push us out of the interval; even
+ if it did we cannot do anything about it (calling the engine again
+ will not help as we inserted no row).
+ */
+ nr= compute_next_insert_id(nr-1, variables);
+ }
+
+ if (table->s->next_number_keypart == 0)
+ {
+ /* We must defer the appending until "nr" has been possibly truncated */
+ append= TRUE;
+ }
+ else
+ {
+ /*
+ For such auto_increment there is no notion of interval, just a
+ singleton. The interval is not even stored in
+ thd->auto_inc_interval_for_cur_row, so we are sure to call the engine
+ for next row.
+ */
+ DBUG_PRINT("info",("auto_increment: special not-first-in-index"));
+ }
+ }
+
+ if (unlikely(nr == ULONGLONG_MAX))
+ DBUG_RETURN(HA_ERR_AUTOINC_ERANGE);
+
+ DBUG_PRINT("info",("auto_increment: %llu nb_reserved_values: %llu",
+ nr, append ? nb_reserved_values : 0));
+
+ /* Store field without warning (Warning will be printed by insert) */
+ save_count_cuted_fields= thd->count_cuted_fields;
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+ tmp= table->next_number_field->store((longlong) nr, TRUE);
+ thd->count_cuted_fields= save_count_cuted_fields;
+
+ if (unlikely(tmp)) // Out of range value in store
+ {
+ /*
+ It's better to return an error here than getting a confusing
+ 'duplicate key error' later.
+ */
+ result= HA_ERR_AUTOINC_ERANGE;
+ }
+ if (append)
+ {
+ auto_inc_interval_for_cur_row.replace(nr, nb_reserved_values,
+ variables->auto_increment_increment);
+ auto_inc_intervals_count++;
+ /* Row-based replication does not need to store intervals in binlog */
+ if (mysql_bin_log.is_open() && !thd->is_current_stmt_binlog_format_row())
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.append(auto_inc_interval_for_cur_row.minimum(),
+ auto_inc_interval_for_cur_row.values(),
+ variables->auto_increment_increment);
+ }
+
+ /*
+ Record this autogenerated value. If the caller then
+ succeeds to insert this value, it will call
+ record_first_successful_insert_id_in_cur_stmt()
+ which will set first_successful_insert_id_in_cur_stmt if it's not
+ already set.
+ */
+ insert_id_for_cur_row= nr;
+
+ if (result) // overflow
+ DBUG_RETURN(result);
+
+ /*
+ Set next insert id to point to next auto-increment value to be able to
+ handle multi-row statements.
+ */
+ set_next_insert_id(compute_next_insert_id(nr, variables));
+
+ DBUG_RETURN(0);
+}
+
+
+/** @brief
+ MySQL signal that it changed the column bitmap
+
+ USAGE
+ This is for handlers that needs to setup their own column bitmaps.
+ Normally the handler should set up their own column bitmaps in
+ index_init() or rnd_init() and in any column_bitmaps_signal() call after
+ this.
+
+ The handler is allowd to do changes to the bitmap after a index_init or
+ rnd_init() call is made as after this, MySQL will not use the bitmap
+ for any program logic checking.
+*/
+void handler::column_bitmaps_signal()
+{
+ DBUG_ENTER("column_bitmaps_signal");
+ if (table)
+ DBUG_PRINT("info", ("read_set: 0x%lx write_set: 0x%lx",
+ (long) table->read_set, (long) table->write_set));
+ DBUG_VOID_RETURN;
+}
+
+
+/** @brief
+ Reserves an interval of auto_increment values from the handler.
+
+ SYNOPSIS
+ get_auto_increment()
+ offset
+ increment
+ nb_desired_values how many values we want
+ first_value (OUT) the first value reserved by the handler
+ nb_reserved_values (OUT) how many values the handler reserved
+
+ offset and increment means that we want values to be of the form
+ offset + N * increment, where N>=0 is integer.
+ If the function sets *first_value to ~(ulonglong)0 it means an error.
+ If the function sets *nb_reserved_values to ULONGLONG_MAX it means it has
+ reserved to "positive infinite".
+*/
+void handler::get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *first_value,
+ ulonglong *nb_reserved_values)
+{
+ ulonglong nr;
+ int error;
+
+ (void) extra(HA_EXTRA_KEYREAD);
+ table->mark_columns_used_by_index_no_reset(table->s->next_number_index,
+ table->read_set);
+ column_bitmaps_signal();
+
+ if (ha_index_init(table->s->next_number_index, 1))
+ {
+ /* This should never happen, assert in debug, and fail in release build */
+ DBUG_ASSERT(0);
+ (void) extra(HA_EXTRA_NO_KEYREAD);
+ *first_value= ULONGLONG_MAX;
+ return;
+ }
+
+ if (table->s->next_number_keypart == 0)
+ { // Autoincrement at key-start
+ error= ha_index_last(table->record[1]);
+ /*
+ MySQL implicitely assumes such method does locking (as MySQL decides to
+ use nr+increment without checking again with the handler, in
+ handler::update_auto_increment()), so reserves to infinite.
+ */
+ *nb_reserved_values= ULONGLONG_MAX;
+ }
+ else
+ {
+ uchar key[MAX_KEY_LENGTH];
+ key_copy(key, table->record[0],
+ table->key_info + table->s->next_number_index,
+ table->s->next_number_key_offset);
+ error= ha_index_read_map(table->record[1], key,
+ make_prev_keypart_map(table->s->
+ next_number_keypart),
+ HA_READ_PREFIX_LAST);
+ /*
+ MySQL needs to call us for next row: assume we are inserting ("a",null)
+ here, we return 3, and next this statement will want to insert
+ ("b",null): there is no reason why ("b",3+1) would be the good row to
+ insert: maybe it already exists, maybe 3+1 is too large...
+ */
+ *nb_reserved_values= 1;
+ }
+
+ if (error)
+ {
+ if (error == HA_ERR_END_OF_FILE || error == HA_ERR_KEY_NOT_FOUND)
+ /* No entry found, that's fine */;
+ else
+ print_error(error, MYF(0));
+ nr= 1;
+ }
+ else
+ nr= ((ulonglong) table->next_number_field->
+ val_int_offset(table->s->rec_buff_length)+1);
+ ha_index_end();
+ (void) extra(HA_EXTRA_NO_KEYREAD);
+ *first_value= nr;
+ return;
+}
+
+
+void handler::ha_release_auto_increment()
+{
+ DBUG_ENTER("ha_release_auto_increment");
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK ||
+ (!next_insert_id && !insert_id_for_cur_row));
+ release_auto_increment();
+ insert_id_for_cur_row= 0;
+ auto_inc_interval_for_cur_row.replace(0, 0, 0);
+ auto_inc_intervals_count= 0;
+ if (next_insert_id > 0)
+ {
+ next_insert_id= 0;
+ /*
+ this statement used forced auto_increment values if there were some,
+ wipe them away for other statements.
+ */
+ table->in_use->auto_inc_intervals_forced.empty();
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Construct and emit duplicate key error message using information
+ from table's record buffer.
+
+ @param table TABLE object which record buffer should be used as
+ source for column values.
+ @param key Key description.
+ @param msg Error message template to which key value should be
+ added.
+ @param errflag Flags for my_error() call.
+*/
+
+void print_keydup_error(TABLE *table, KEY *key, const char *msg, myf errflag)
+{
+ /* Write the duplicated key in the error message */
+ char key_buff[MAX_KEY_LENGTH];
+ String str(key_buff,sizeof(key_buff),system_charset_info);
+
+ if (key == NULL)
+ {
+ /* Key is unknown */
+ str.copy("", 0, system_charset_info);
+ my_printf_error(ER_DUP_ENTRY, msg, errflag, str.c_ptr(), "*UNKNOWN*");
+ }
+ else
+ {
+ /* Table is opened and defined at this point */
+ key_unpack(&str,table, key);
+ uint max_length=MYSQL_ERRMSG_SIZE-(uint) strlen(msg);
+ if (str.length() >= max_length)
+ {
+ str.length(max_length-4);
+ str.append(STRING_WITH_LEN("..."));
+ }
+ my_printf_error(ER_DUP_ENTRY, msg, errflag, str.c_ptr_safe(), key->name);
+ }
+}
+
+/**
+ Construct and emit duplicate key error message using information
+ from table's record buffer.
+
+ @sa print_keydup_error(table, key, msg, errflag).
+*/
+
+void print_keydup_error(TABLE *table, KEY *key, myf errflag)
+{
+ print_keydup_error(table, key, ER(ER_DUP_ENTRY_WITH_KEY_NAME), errflag);
+}
+
+
+/**
+ Print error that we got from handler function.
+
+ @note
+ In case of delete table it's only safe to use the following parts of
+ the 'table' structure:
+ - table->s->path
+ - table->alias
+*/
+
+#define SET_FATAL_ERROR fatal_error=1
+
+void handler::print_error(int error, myf errflag)
+{
+ bool fatal_error= 0;
+ DBUG_ENTER("handler::print_error");
+ DBUG_PRINT("enter",("error: %d",error));
+
+ int textno= -1; // impossible value
+ switch (error) {
+ case EACCES:
+ textno=ER_OPEN_AS_READONLY;
+ break;
+ case EAGAIN:
+ textno=ER_FILE_USED;
+ break;
+ case ENOENT:
+ textno=ER_FILE_NOT_FOUND;
+ break;
+ case ENOSPC:
+ case HA_ERR_DISK_FULL:
+ textno= ER_DISK_FULL;
+ SET_FATAL_ERROR; // Ensure error is logged
+ break;
+ case HA_ERR_KEY_NOT_FOUND:
+ case HA_ERR_NO_ACTIVE_RECORD:
+ case HA_ERR_RECORD_DELETED:
+ case HA_ERR_END_OF_FILE:
+ /*
+ This errors is not not normally fatal (for example for reads). However
+ if you get it during an update or delete, then its fatal.
+ As the user is calling print_error() (which is not done on read), we
+ assume something when wrong with the update or delete.
+ */
+ SET_FATAL_ERROR;
+ textno=ER_KEY_NOT_FOUND;
+ break;
+ case HA_ERR_ABORTED_BY_USER:
+ {
+ DBUG_ASSERT(table->in_use->killed);
+ table->in_use->send_kill_message();
+ DBUG_VOID_RETURN;
+ }
+ case HA_ERR_WRONG_MRG_TABLE_DEF:
+ textno=ER_WRONG_MRG_TABLE;
+ break;
+ case HA_ERR_FOUND_DUPP_KEY:
+ {
+ if (table)
+ {
+ uint key_nr=get_dup_key(error);
+ if ((int) key_nr >= 0)
+ {
+ print_keydup_error(table,
+ key_nr == MAX_KEY ? NULL : &table->key_info[key_nr],
+ errflag);
+ DBUG_VOID_RETURN;
+ }
+ }
+ textno=ER_DUP_KEY;
+ break;
+ }
+ case HA_ERR_FOREIGN_DUPLICATE_KEY:
+ {
+ char rec_buf[MAX_KEY_LENGTH];
+ String rec(rec_buf, sizeof(rec_buf), system_charset_info);
+ /* Table is opened and defined at this point */
+
+ /*
+ Just print the subset of fields that are part of the first index,
+ printing the whole row from there is not easy.
+ */
+ key_unpack(&rec, table, &table->key_info[0]);
+
+ char child_table_name[NAME_LEN + 1];
+ char child_key_name[NAME_LEN + 1];
+ if (get_foreign_dup_key(child_table_name, sizeof(child_table_name),
+ child_key_name, sizeof(child_key_name)))
+ {
+ my_error(ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO, errflag,
+ table_share->table_name.str, rec.c_ptr_safe(),
+ child_table_name, child_key_name);
+ }
+ else
+ {
+ my_error(ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO, errflag,
+ table_share->table_name.str, rec.c_ptr_safe());
+ }
+ DBUG_VOID_RETURN;
+ }
+ case HA_ERR_NULL_IN_SPATIAL:
+ my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, errflag);
+ DBUG_VOID_RETURN;
+ case HA_ERR_FOUND_DUPP_UNIQUE:
+ textno=ER_DUP_UNIQUE;
+ break;
+ case HA_ERR_RECORD_CHANGED:
+ /*
+ This is not fatal error when using HANDLER interface
+ SET_FATAL_ERROR;
+ */
+ textno=ER_CHECKREAD;
+ break;
+ case HA_ERR_CRASHED:
+ SET_FATAL_ERROR;
+ textno=ER_NOT_KEYFILE;
+ break;
+ case HA_ERR_WRONG_IN_RECORD:
+ SET_FATAL_ERROR;
+ textno= ER_CRASHED_ON_USAGE;
+ break;
+ case HA_ERR_CRASHED_ON_USAGE:
+ SET_FATAL_ERROR;
+ textno=ER_CRASHED_ON_USAGE;
+ break;
+ case HA_ERR_NOT_A_TABLE:
+ textno= error;
+ break;
+ case HA_ERR_CRASHED_ON_REPAIR:
+ SET_FATAL_ERROR;
+ textno=ER_CRASHED_ON_REPAIR;
+ break;
+ case HA_ERR_OUT_OF_MEM:
+ textno=ER_OUT_OF_RESOURCES;
+ break;
+ case HA_ERR_WRONG_COMMAND:
+ my_error(ER_ILLEGAL_HA, MYF(0), table_type(), table_share->db.str,
+ table_share->table_name.str);
+ DBUG_VOID_RETURN;
+ break;
+ case HA_ERR_OLD_FILE:
+ textno=ER_OLD_KEYFILE;
+ break;
+ case HA_ERR_UNSUPPORTED:
+ textno=ER_UNSUPPORTED_EXTENSION;
+ break;
+ case HA_ERR_RECORD_FILE_FULL:
+ case HA_ERR_INDEX_FILE_FULL:
+ {
+ textno=ER_RECORD_FILE_FULL;
+ /* Write the error message to error log */
+ errflag|= ME_NOREFRESH;
+ break;
+ }
+ case HA_ERR_LOCK_WAIT_TIMEOUT:
+ textno=ER_LOCK_WAIT_TIMEOUT;
+ break;
+ case HA_ERR_LOCK_TABLE_FULL:
+ textno=ER_LOCK_TABLE_FULL;
+ break;
+ case HA_ERR_LOCK_DEADLOCK:
+ textno=ER_LOCK_DEADLOCK;
+ /* cannot continue. the statement was already aborted in the engine */
+ SET_FATAL_ERROR;
+ break;
+ case HA_ERR_READ_ONLY_TRANSACTION:
+ textno=ER_READ_ONLY_TRANSACTION;
+ break;
+ case HA_ERR_CANNOT_ADD_FOREIGN:
+ textno=ER_CANNOT_ADD_FOREIGN;
+ break;
+ case HA_ERR_ROW_IS_REFERENCED:
+ {
+ String str;
+ get_error_message(error, &str);
+ my_error(ER_ROW_IS_REFERENCED_2, errflag, str.c_ptr_safe());
+ DBUG_VOID_RETURN;
+ }
+ case HA_ERR_NO_REFERENCED_ROW:
+ {
+ String str;
+ get_error_message(error, &str);
+ my_error(ER_NO_REFERENCED_ROW_2, errflag, str.c_ptr_safe());
+ DBUG_VOID_RETURN;
+ }
+ case HA_ERR_TABLE_DEF_CHANGED:
+ textno=ER_TABLE_DEF_CHANGED;
+ break;
+ case HA_ERR_NO_SUCH_TABLE:
+ my_error(ER_NO_SUCH_TABLE_IN_ENGINE, errflag, table_share->db.str,
+ table_share->table_name.str);
+ DBUG_VOID_RETURN;
+ case HA_ERR_RBR_LOGGING_FAILED:
+ textno= ER_BINLOG_ROW_LOGGING_FAILED;
+ break;
+ case HA_ERR_DROP_INDEX_FK:
+ {
+ const char *ptr= "???";
+ uint key_nr= get_dup_key(error);
+ if ((int) key_nr >= 0)
+ ptr= table->key_info[key_nr].name;
+ my_error(ER_DROP_INDEX_FK, errflag, ptr);
+ DBUG_VOID_RETURN;
+ }
+ case HA_ERR_TABLE_NEEDS_UPGRADE:
+ textno=ER_TABLE_NEEDS_UPGRADE;
+ break;
+ case HA_ERR_NO_PARTITION_FOUND:
+ textno=ER_WRONG_PARTITION_NAME;
+ break;
+ case HA_ERR_TABLE_READONLY:
+ textno= ER_OPEN_AS_READONLY;
+ break;
+ case HA_ERR_AUTOINC_READ_FAILED:
+ textno= ER_AUTOINC_READ_FAILED;
+ break;
+ case HA_ERR_AUTOINC_ERANGE:
+ textno= error;
+ my_error(textno, errflag, table->next_number_field->field_name,
+ table->in_use->get_stmt_da()->current_row_for_warning());
+ DBUG_VOID_RETURN;
+ break;
+ case HA_ERR_TOO_MANY_CONCURRENT_TRXS:
+ textno= ER_TOO_MANY_CONCURRENT_TRXS;
+ break;
+ case HA_ERR_INDEX_COL_TOO_LONG:
+ textno= ER_INDEX_COLUMN_TOO_LONG;
+ break;
+ case HA_ERR_NOT_IN_LOCK_PARTITIONS:
+ textno=ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET;
+ break;
+ case HA_ERR_INDEX_CORRUPT:
+ textno= ER_INDEX_CORRUPT;
+ break;
+ case HA_ERR_UNDO_REC_TOO_BIG:
+ textno= ER_UNDO_RECORD_TOO_BIG;
+ break;
+ case HA_ERR_TABLE_IN_FK_CHECK:
+ textno= ER_TABLE_IN_FK_CHECK;
+ break;
+ default:
+ {
+ /* The error was "unknown" to this function.
+ Ask handler if it has got a message for this error */
+ bool temporary= FALSE;
+ String str;
+ temporary= get_error_message(error, &str);
+ if (!str.is_empty())
+ {
+ const char* engine= table_type();
+ if (temporary)
+ my_error(ER_GET_TEMPORARY_ERRMSG, errflag, error, str.c_ptr(),
+ engine);
+ else
+ {
+ SET_FATAL_ERROR;
+ my_error(ER_GET_ERRMSG, errflag, error, str.c_ptr(), engine);
+ }
+ }
+ else
+ my_error(ER_GET_ERRNO, errflag, error, table_type());
+ DBUG_VOID_RETURN;
+ }
+ }
+ DBUG_ASSERT(textno > 0);
+ if (fatal_error)
+ {
+ /* Ensure this becomes a true error */
+ errflag&= ~(ME_JUST_WARNING | ME_JUST_INFO);
+ if ((debug_assert_if_crashed_table ||
+ global_system_variables.log_warnings > 1))
+ {
+ /*
+ Log error to log before we crash or if extended warnings are requested
+ */
+ errflag|= ME_NOREFRESH;
+ }
+ }
+
+ /* if we got an OS error from a file-based engine, specify a path of error */
+ if (error < HA_ERR_FIRST && bas_ext()[0])
+ {
+ char buff[FN_REFLEN];
+ strxnmov(buff, sizeof(buff),
+ table_share->normalized_path.str, bas_ext()[0], NULL);
+ my_error(textno, errflag, buff, error);
+ }
+ else
+ my_error(textno, errflag, table_share->table_name.str, error);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Return an error message specific to this handler.
+
+ @param error error code previously returned by handler
+ @param buf pointer to String where to add error message
+
+ @return
+ Returns true if this is a temporary error
+*/
+bool handler::get_error_message(int error, String* buf)
+{
+ return FALSE;
+}
+
+/**
+ Check for incompatible collation changes.
+
+ @retval
+ HA_ADMIN_NEEDS_UPGRADE Table may have data requiring upgrade.
+ @retval
+ 0 No upgrade required.
+*/
+
+int handler::check_collation_compatibility()
+{
+ ulong mysql_version= table->s->mysql_version;
+
+ if (mysql_version < 50124)
+ {
+ KEY *key= table->key_info;
+ KEY *key_end= key + table->s->keys;
+ for (; key < key_end; key++)
+ {
+ KEY_PART_INFO *key_part= key->key_part;
+ KEY_PART_INFO *key_part_end= key_part + key->user_defined_key_parts;
+ for (; key_part < key_part_end; key_part++)
+ {
+ if (!key_part->fieldnr)
+ continue;
+ Field *field= table->field[key_part->fieldnr - 1];
+ uint cs_number= field->charset()->number;
+ if ((mysql_version < 50048 &&
+ (cs_number == 11 || /* ascii_general_ci - bug #29499, bug #27562 */
+ cs_number == 41 || /* latin7_general_ci - bug #29461 */
+ cs_number == 42 || /* latin7_general_cs - bug #29461 */
+ cs_number == 20 || /* latin7_estonian_cs - bug #29461 */
+ cs_number == 21 || /* latin2_hungarian_ci - bug #29461 */
+ cs_number == 22 || /* koi8u_general_ci - bug #29461 */
+ cs_number == 23 || /* cp1251_ukrainian_ci - bug #29461 */
+ cs_number == 26)) || /* cp1250_general_ci - bug #29461 */
+ (mysql_version < 50124 &&
+ (cs_number == 33 || /* utf8_general_ci - bug #27877 */
+ cs_number == 35))) /* ucs2_general_ci - bug #27877 */
+ return HA_ADMIN_NEEDS_UPGRADE;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+int handler::ha_check_for_upgrade(HA_CHECK_OPT *check_opt)
+{
+ int error;
+ KEY *keyinfo, *keyend;
+ KEY_PART_INFO *keypart, *keypartend;
+
+ if (table->s->incompatible_version)
+ return HA_ADMIN_NEEDS_ALTER;
+
+ if (!table->s->mysql_version)
+ {
+ /* check for blob-in-key error */
+ keyinfo= table->key_info;
+ keyend= table->key_info + table->s->keys;
+ for (; keyinfo < keyend; keyinfo++)
+ {
+ keypart= keyinfo->key_part;
+ keypartend= keypart + keyinfo->user_defined_key_parts;
+ for (; keypart < keypartend; keypart++)
+ {
+ if (!keypart->fieldnr)
+ continue;
+ Field *field= table->field[keypart->fieldnr-1];
+ if (field->type() == MYSQL_TYPE_BLOB)
+ {
+ if (check_opt->sql_flags & TT_FOR_UPGRADE)
+ check_opt->flags= T_MEDIUM;
+ return HA_ADMIN_NEEDS_CHECK;
+ }
+ }
+ }
+ }
+ if (table->s->frm_version != FRM_VER_TRUE_VARCHAR)
+ return HA_ADMIN_NEEDS_ALTER;
+
+ if ((error= check_collation_compatibility()))
+ return error;
+
+ return check_for_upgrade(check_opt);
+}
+
+
+int handler::check_old_types()
+{
+ Field** field;
+
+ if (!table->s->mysql_version)
+ {
+ /* check for bad DECIMAL field */
+ for (field= table->field; (*field); field++)
+ {
+ if ((*field)->type() == MYSQL_TYPE_NEWDECIMAL)
+ {
+ return HA_ADMIN_NEEDS_ALTER;
+ }
+ if ((*field)->type() == MYSQL_TYPE_VAR_STRING)
+ {
+ return HA_ADMIN_NEEDS_ALTER;
+ }
+ }
+ }
+ return 0;
+}
+
+
+static bool update_frm_version(TABLE *table)
+{
+ char path[FN_REFLEN];
+ File file;
+ int result= 1;
+ DBUG_ENTER("update_frm_version");
+
+ /*
+ No need to update frm version in case table was created or checked
+ by server with the same version. This also ensures that we do not
+ update frm version for temporary tables as this code doesn't support
+ temporary tables.
+ */
+ if (table->s->mysql_version == MYSQL_VERSION_ID)
+ DBUG_RETURN(0);
+
+ strxmov(path, table->s->normalized_path.str, reg_ext, NullS);
+
+ if ((file= mysql_file_open(key_file_frm,
+ path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0)
+ {
+ uchar version[4];
+
+ int4store(version, MYSQL_VERSION_ID);
+
+ if ((result= mysql_file_pwrite(file, (uchar*) version, 4, 51L, MYF_RW)))
+ goto err;
+
+ table->s->mysql_version= MYSQL_VERSION_ID;
+ }
+err:
+ if (file >= 0)
+ (void) mysql_file_close(file, MYF(MY_WME));
+ DBUG_RETURN(result);
+}
+
+
+
+/**
+ @return
+ key if error because of duplicated keys
+*/
+uint handler::get_dup_key(int error)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ DBUG_ENTER("handler::get_dup_key");
+ table->file->errkey = (uint) -1;
+ if (error == HA_ERR_FOUND_DUPP_KEY || error == HA_ERR_FOREIGN_DUPLICATE_KEY ||
+ error == HA_ERR_FOUND_DUPP_UNIQUE || error == HA_ERR_NULL_IN_SPATIAL ||
+ error == HA_ERR_DROP_INDEX_FK)
+ table->file->info(HA_STATUS_ERRKEY | HA_STATUS_NO_LOCK);
+ DBUG_RETURN(table->file->errkey);
+}
+
+
+/**
+ Delete all files with extension from bas_ext().
+
+ @param name Base name of table
+
+ @note
+ We assume that the handler may return more extensions than
+ was actually used for the file.
+
+ @retval
+ 0 If we successfully deleted at least one file from base_ext and
+ didn't get any other errors than ENOENT
+ @retval
+ !0 Error
+*/
+int handler::delete_table(const char *name)
+{
+ int saved_error= 0;
+ int error= 0;
+ int enoent_or_zero;
+ char buff[FN_REFLEN];
+
+ if (ht->discover_table)
+ enoent_or_zero= 0; // the table may not exist in the engine, it's ok
+ else
+ enoent_or_zero= ENOENT; // the first file of bas_ext() *must* exist
+
+ for (const char **ext=bas_ext(); *ext ; ext++)
+ {
+ fn_format(buff, name, "", *ext, MY_UNPACK_FILENAME|MY_APPEND_EXT);
+ if (mysql_file_delete_with_symlink(key_file_misc, buff, MYF(0)))
+ {
+ if (my_errno != ENOENT)
+ {
+ /*
+ If error on the first existing file, return the error.
+ Otherwise delete as much as possible.
+ */
+ if (enoent_or_zero)
+ return my_errno;
+ saved_error= my_errno;
+ }
+ }
+ else
+ enoent_or_zero= 0; // No error for ENOENT
+ error= enoent_or_zero;
+ }
+ return saved_error ? saved_error : error;
+}
+
+
+int handler::rename_table(const char * from, const char * to)
+{
+ int error= 0;
+ const char **ext, **start_ext;
+ start_ext= bas_ext();
+ for (ext= start_ext; *ext ; ext++)
+ {
+ if (rename_file_ext(from, to, *ext))
+ {
+ if ((error=my_errno) != ENOENT)
+ break;
+ error= 0;
+ }
+ }
+ if (error)
+ {
+ /* Try to revert the rename. Ignore errors. */
+ for (; ext >= start_ext; ext--)
+ rename_file_ext(to, from, *ext);
+ }
+ return error;
+}
+
+
+void handler::drop_table(const char *name)
+{
+ ha_close();
+ delete_table(name);
+}
+
+
+/**
+ Performs checks upon the table.
+
+ @param thd thread doing CHECK TABLE operation
+ @param check_opt options from the parser
+
+ @retval
+ HA_ADMIN_OK Successful upgrade
+ @retval
+ HA_ADMIN_NEEDS_UPGRADE Table has structures requiring upgrade
+ @retval
+ HA_ADMIN_NEEDS_ALTER Table has structures requiring ALTER TABLE
+ @retval
+ HA_ADMIN_NOT_IMPLEMENTED
+*/
+int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
+{
+ int error;
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+
+ if ((table->s->mysql_version >= MYSQL_VERSION_ID) &&
+ (check_opt->sql_flags & TT_FOR_UPGRADE))
+ return 0;
+
+ if (table->s->mysql_version < MYSQL_VERSION_ID)
+ {
+ if ((error= check_old_types()))
+ return error;
+ error= ha_check_for_upgrade(check_opt);
+ if (error && (error != HA_ADMIN_NEEDS_CHECK))
+ return error;
+ if (!error && (check_opt->sql_flags & TT_FOR_UPGRADE))
+ return 0;
+ }
+ if ((error= check(thd, check_opt)))
+ return error;
+ /* Skip updating frm version if not main handler. */
+ if (table->file != this)
+ return error;
+ return update_frm_version(table);
+}
+
+/**
+ A helper function to mark a transaction read-write,
+ if it is started.
+*/
+
+inline
+void
+handler::mark_trx_read_write()
+{
+ Ha_trx_info *ha_info= &ha_thd()->ha_data[ht->slot].ha_info[0];
+ /*
+ When a storage engine method is called, the transaction must
+ have been started, unless it's a DDL call, for which the
+ storage engine starts the transaction internally, and commits
+ it internally, without registering in the ha_list.
+ Unfortunately here we can't know know for sure if the engine
+ has registered the transaction or not, so we must check.
+ */
+ if (ha_info->is_started())
+ {
+ DBUG_ASSERT(has_transactions());
+ /*
+ table_share can be NULL in ha_delete_table(). See implementation
+ of standalone function ha_delete_table() in sql_base.cc.
+ */
+ if (table_share == NULL || table_share->tmp_table == NO_TMP_TABLE)
+ ha_info->set_trx_read_write();
+ }
+}
+
+
+/**
+ Repair table: public interface.
+
+ @sa handler::repair()
+*/
+
+int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt)
+{
+ int result;
+
+ mark_trx_read_write();
+
+ result= repair(thd, check_opt);
+ DBUG_ASSERT(result == HA_ADMIN_NOT_IMPLEMENTED ||
+ ha_table_flags() & HA_CAN_REPAIR);
+
+ if (result == HA_ADMIN_OK)
+ result= update_frm_version(table);
+ return result;
+}
+
+
+/**
+ Bulk update row: public interface.
+
+ @sa handler::bulk_update_row()
+*/
+
+int
+handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
+ uint *dup_key_found)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ mark_trx_read_write();
+
+ return bulk_update_row(old_data, new_data, dup_key_found);
+}
+
+
+/**
+ Delete all rows: public interface.
+
+ @sa handler::delete_all_rows()
+*/
+
+int
+handler::ha_delete_all_rows()
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ mark_trx_read_write();
+
+ return delete_all_rows();
+}
+
+
+/**
+ Truncate table: public interface.
+
+ @sa handler::truncate()
+*/
+
+int
+handler::ha_truncate()
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ mark_trx_read_write();
+
+ return truncate();
+}
+
+
+/**
+ Reset auto increment: public interface.
+
+ @sa handler::reset_auto_increment()
+*/
+
+int
+handler::ha_reset_auto_increment(ulonglong value)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ mark_trx_read_write();
+
+ return reset_auto_increment(value);
+}
+
+
+/**
+ Optimize table: public interface.
+
+ @sa handler::optimize()
+*/
+
+int
+handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ mark_trx_read_write();
+
+ return optimize(thd, check_opt);
+}
+
+
+/**
+ Analyze table: public interface.
+
+ @sa handler::analyze()
+*/
+
+int
+handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ mark_trx_read_write();
+
+ return analyze(thd, check_opt);
+}
+
+
+/**
+ Check and repair table: public interface.
+
+ @sa handler::check_and_repair()
+*/
+
+bool
+handler::ha_check_and_repair(THD *thd)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_UNLCK);
+ mark_trx_read_write();
+
+ return check_and_repair(thd);
+}
+
+
+/**
+ Disable indexes: public interface.
+
+ @sa handler::disable_indexes()
+*/
+
+int
+handler::ha_disable_indexes(uint mode)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ mark_trx_read_write();
+
+ return disable_indexes(mode);
+}
+
+
+/**
+ Enable indexes: public interface.
+
+ @sa handler::enable_indexes()
+*/
+
+int
+handler::ha_enable_indexes(uint mode)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ mark_trx_read_write();
+
+ return enable_indexes(mode);
+}
+
+
+/**
+ Discard or import tablespace: public interface.
+
+ @sa handler::discard_or_import_tablespace()
+*/
+
+int
+handler::ha_discard_or_import_tablespace(my_bool discard)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ mark_trx_read_write();
+
+ return discard_or_import_tablespace(discard);
+}
+
+
+bool handler::ha_prepare_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+{
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+ mark_trx_read_write();
+
+ return prepare_inplace_alter_table(altered_table, ha_alter_info);
+}
+
+
+bool handler::ha_commit_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info,
+ bool commit)
+{
+ /*
+ At this point we should have an exclusive metadata lock on the table.
+ The exception is if we're about to roll back changes (commit= false).
+ In this case, we might be rolling back after a failed lock upgrade,
+ so we could be holding the same lock level as for inplace_alter_table().
+ */
+ DBUG_ASSERT(ha_thd()->mdl_context.is_lock_owner(MDL_key::TABLE,
+ table->s->db.str,
+ table->s->table_name.str,
+ MDL_EXCLUSIVE) ||
+ !commit);
+
+ return commit_inplace_alter_table(altered_table, ha_alter_info, commit);
+}
+
+
+/*
+ Default implementation to support in-place alter table
+ and old online add/drop index API
+*/
+
+enum_alter_inplace_result
+handler::check_if_supported_inplace_alter(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+{
+ DBUG_ENTER("check_if_supported_alter");
+
+ HA_CREATE_INFO *create_info= ha_alter_info->create_info;
+
+ Alter_inplace_info::HA_ALTER_FLAGS inplace_offline_operations=
+ Alter_inplace_info::ALTER_COLUMN_EQUAL_PACK_LENGTH |
+ Alter_inplace_info::ALTER_COLUMN_NAME |
+ Alter_inplace_info::ALTER_COLUMN_DEFAULT |
+ Alter_inplace_info::ALTER_COLUMN_OPTION |
+ Alter_inplace_info::CHANGE_CREATE_OPTION |
+ Alter_inplace_info::ALTER_RENAME;
+
+ /* Is there at least one operation that requires copy algorithm? */
+ if (ha_alter_info->handler_flags & ~inplace_offline_operations)
+ DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
+
+ /*
+ ALTER TABLE tbl_name CONVERT TO CHARACTER SET .. and
+ ALTER TABLE table_name DEFAULT CHARSET = .. most likely
+ change column charsets and so not supported in-place through
+ old API.
+
+ Changing of PACK_KEYS, MAX_ROWS and ROW_FORMAT options were
+ not supported as in-place operations in old API either.
+ */
+ if (create_info->used_fields & (HA_CREATE_USED_CHARSET |
+ HA_CREATE_USED_DEFAULT_CHARSET |
+ HA_CREATE_USED_PACK_KEYS |
+ HA_CREATE_USED_MAX_ROWS) ||
+ (table->s->row_type != create_info->row_type))
+ DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
+
+ uint table_changes= (ha_alter_info->handler_flags &
+ Alter_inplace_info::ALTER_COLUMN_EQUAL_PACK_LENGTH) ?
+ IS_EQUAL_PACK_LENGTH : IS_EQUAL_YES;
+ if (table->file->check_if_incompatible_data(create_info, table_changes)
+ == COMPATIBLE_DATA_YES)
+ DBUG_RETURN(HA_ALTER_INPLACE_EXCLUSIVE_LOCK);
+
+ DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
+}
+
+
+/*
+ Default implementation to support in-place alter table
+ and old online add/drop index API
+*/
+
+void handler::notify_table_changed()
+{
+ ha_create_partitioning_metadata(table->s->path.str, NULL, CHF_INDEX_FLAG);
+}
+
+
+void Alter_inplace_info::report_unsupported_error(const char *not_supported,
+ const char *try_instead)
+{
+ if (unsupported_reason == NULL)
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
+ not_supported, try_instead);
+ else
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ not_supported, unsupported_reason, try_instead);
+}
+
+
+/**
+ Rename table: public interface.
+
+ @sa handler::rename_table()
+*/
+
+int
+handler::ha_rename_table(const char *from, const char *to)
+{
+ DBUG_ASSERT(m_lock_type == F_UNLCK);
+ mark_trx_read_write();
+
+ return rename_table(from, to);
+}
+
+
+/**
+ Delete table: public interface.
+
+ @sa handler::delete_table()
+*/
+
+int
+handler::ha_delete_table(const char *name)
+{
+ mark_trx_read_write();
+ return delete_table(name);
+}
+
+
+/**
+ Drop table in the engine: public interface.
+
+ @sa handler::drop_table()
+
+ The difference between this and delete_table() is that the table is open in
+ drop_table().
+*/
+
+void
+handler::ha_drop_table(const char *name)
+{
+ DBUG_ASSERT(m_lock_type == F_UNLCK);
+ mark_trx_read_write();
+
+ return drop_table(name);
+}
+
+
+/**
+ Create a table in the engine: public interface.
+
+ @sa handler::create()
+*/
+
+int
+handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info)
+{
+ DBUG_ASSERT(m_lock_type == F_UNLCK);
+ mark_trx_read_write();
+ int error= create(name, form, info);
+ if (!error &&
+ !(info->options & (HA_LEX_CREATE_TMP_TABLE | HA_CREATE_TMP_ALTER)))
+ mysql_audit_create_table(form);
+ return error;
+}
+
+
+/**
+ Create handler files for CREATE TABLE: public interface.
+
+ @sa handler::create_partitioning_metadata()
+*/
+
+int
+handler::ha_create_partitioning_metadata(const char *name, const char *old_name,
+ int action_flag)
+{
+ /*
+ Normally this is done when unlocked, but in fast_alter_partition_table,
+ it is done on an already locked handler when preparing to alter/rename
+ partitions.
+ */
+ DBUG_ASSERT(m_lock_type == F_UNLCK ||
+ (!old_name && strcmp(name, table_share->path.str)));
+ mark_trx_read_write();
+
+ return create_partitioning_metadata(name, old_name, action_flag);
+}
+
+
+/**
+ Change partitions: public interface.
+
+ @sa handler::change_partitions()
+*/
+
+int
+handler::ha_change_partitions(HA_CREATE_INFO *create_info,
+ const char *path,
+ ulonglong * const copied,
+ ulonglong * const deleted,
+ const uchar *pack_frm_data,
+ size_t pack_frm_len)
+{ /*
+ Must have at least RDLCK or be a TMP table. Read lock is needed to read
+ from current partitions and write lock will be taken on new partitions.
+ */
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type != F_UNLCK);
+
+ mark_trx_read_write();
+
+ return change_partitions(create_info, path, copied, deleted,
+ pack_frm_data, pack_frm_len);
+}
+
+
+/**
+ Drop partitions: public interface.
+
+ @sa handler::drop_partitions()
+*/
+
+int
+handler::ha_drop_partitions(const char *path)
+{
+ DBUG_ASSERT(!table->db_stat);
+
+ mark_trx_read_write();
+
+ return drop_partitions(path);
+}
+
+
+/**
+ Rename partitions: public interface.
+
+ @sa handler::rename_partitions()
+*/
+
+int
+handler::ha_rename_partitions(const char *path)
+{
+ DBUG_ASSERT(!table->db_stat);
+
+ mark_trx_read_write();
+
+ return rename_partitions(path);
+}
+
+
+/**
+ Tell the storage engine that it is allowed to "disable transaction" in the
+ handler. It is a hint that ACID is not required - it is used in NDB for
+ ALTER TABLE, for example, when data are copied to temporary table.
+ A storage engine may treat this hint any way it likes. NDB for example
+ starts to commit every now and then automatically.
+ This hint can be safely ignored.
+*/
+int ha_enable_transaction(THD *thd, bool on)
+{
+ int error=0;
+ DBUG_ENTER("ha_enable_transaction");
+ DBUG_PRINT("enter", ("on: %d", (int) on));
+
+ if ((thd->transaction.on= on))
+ {
+ /*
+ Now all storage engines should have transaction handling enabled.
+ But some may have it enabled all the time - "disabling" transactions
+ is an optimization hint that storage engine is free to ignore.
+ So, let's commit an open transaction (if any) now.
+ */
+ if (!(error= ha_commit_trans(thd, 0)))
+ error= trans_commit_implicit(thd);
+ }
+ DBUG_RETURN(error);
+}
+
+int handler::index_next_same(uchar *buf, const uchar *key, uint keylen)
+{
+ int error;
+ DBUG_ENTER("handler::index_next_same");
+ if (!(error=index_next(buf)))
+ {
+ my_ptrdiff_t ptrdiff= buf - table->record[0];
+ uchar *UNINIT_VAR(save_record_0);
+ KEY *UNINIT_VAR(key_info);
+ KEY_PART_INFO *UNINIT_VAR(key_part);
+ KEY_PART_INFO *UNINIT_VAR(key_part_end);
+
+ /*
+ key_cmp_if_same() compares table->record[0] against 'key'.
+ In parts it uses table->record[0] directly, in parts it uses
+ field objects with their local pointers into table->record[0].
+ If 'buf' is distinct from table->record[0], we need to move
+ all record references. This is table->record[0] itself and
+ the field pointers of the fields used in this key.
+ */
+ if (ptrdiff)
+ {
+ save_record_0= table->record[0];
+ table->record[0]= buf;
+ key_info= table->key_info + active_index;
+ key_part= key_info->key_part;
+ key_part_end= key_part + key_info->user_defined_key_parts;
+ for (; key_part < key_part_end; key_part++)
+ {
+ DBUG_ASSERT(key_part->field);
+ key_part->field->move_field_offset(ptrdiff);
+ }
+ }
+
+ if (key_cmp_if_same(table, key, active_index, keylen))
+ {
+ table->status=STATUS_NOT_FOUND;
+ error=HA_ERR_END_OF_FILE;
+ }
+
+ /* Move back if necessary. */
+ if (ptrdiff)
+ {
+ table->record[0]= save_record_0;
+ for (key_part= key_info->key_part; key_part < key_part_end; key_part++)
+ key_part->field->move_field_offset(-ptrdiff);
+ }
+ }
+ DBUG_PRINT("return",("%i", error));
+ DBUG_RETURN(error);
+}
+
+
+void handler::get_dynamic_partition_info(PARTITION_STATS *stat_info,
+ uint part_id)
+{
+ info(HA_STATUS_CONST | HA_STATUS_TIME | HA_STATUS_VARIABLE |
+ HA_STATUS_NO_LOCK);
+ stat_info->records= stats.records;
+ stat_info->mean_rec_length= stats.mean_rec_length;
+ stat_info->data_file_length= stats.data_file_length;
+ stat_info->max_data_file_length= stats.max_data_file_length;
+ stat_info->index_file_length= stats.index_file_length;
+ stat_info->delete_length= stats.delete_length;
+ stat_info->create_time= stats.create_time;
+ stat_info->update_time= stats.update_time;
+ stat_info->check_time= stats.check_time;
+ stat_info->check_sum= 0;
+ if (table_flags() & (HA_HAS_OLD_CHECKSUM | HA_HAS_OLD_CHECKSUM))
+ stat_info->check_sum= checksum();
+ return;
+}
+
+
+/*
+ Updates the global table stats with the TABLE this handler represents
+*/
+
+void handler::update_global_table_stats()
+{
+ TABLE_STATS * table_stats;
+
+ status_var_add(table->in_use->status_var.rows_read, rows_read);
+ DBUG_ASSERT(rows_tmp_read == 0);
+
+ if (!table->in_use->userstat_running)
+ {
+ rows_read= rows_changed= 0;
+ return;
+ }
+
+ if (rows_read + rows_changed == 0)
+ return; // Nothing to update.
+
+ DBUG_ASSERT(table->s && table->s->table_cache_key.str);
+
+ mysql_mutex_lock(&LOCK_global_table_stats);
+ /* Gets the global table stats, creating one if necessary. */
+ if (!(table_stats= (TABLE_STATS*)
+ my_hash_search(&global_table_stats,
+ (uchar*) table->s->table_cache_key.str,
+ table->s->table_cache_key.length)))
+ {
+ if (!(table_stats = ((TABLE_STATS*)
+ my_malloc(sizeof(TABLE_STATS),
+ MYF(MY_WME | MY_ZEROFILL)))))
+ {
+ /* Out of memory error already given */
+ goto end;
+ }
+ memcpy(table_stats->table, table->s->table_cache_key.str,
+ table->s->table_cache_key.length);
+ table_stats->table_name_length= table->s->table_cache_key.length;
+ table_stats->engine_type= ht->db_type;
+ /* No need to set variables to 0, as we use MY_ZEROFILL above */
+
+ if (my_hash_insert(&global_table_stats, (uchar*) table_stats))
+ {
+ /* Out of memory error is already given */
+ my_free(table_stats);
+ goto end;
+ }
+ }
+ // Updates the global table stats.
+ table_stats->rows_read+= rows_read;
+ table_stats->rows_changed+= rows_changed;
+ table_stats->rows_changed_x_indexes+= (rows_changed *
+ (table->s->keys ? table->s->keys :
+ 1));
+ rows_read= rows_changed= 0;
+end:
+ mysql_mutex_unlock(&LOCK_global_table_stats);
+}
+
+
+/*
+ Updates the global index stats with this handler's accumulated index reads.
+*/
+
+void handler::update_global_index_stats()
+{
+ DBUG_ASSERT(table->s);
+
+ if (!table->in_use->userstat_running)
+ {
+ /* Reset all index read values */
+ bzero(index_rows_read, sizeof(index_rows_read[0]) * table->s->keys);
+ return;
+ }
+
+ for (uint index = 0; index < table->s->keys; index++)
+ {
+ if (index_rows_read[index])
+ {
+ INDEX_STATS* index_stats;
+ uint key_length;
+ KEY *key_info = &table->key_info[index]; // Rows were read using this
+
+ DBUG_ASSERT(key_info->cache_name);
+ if (!key_info->cache_name)
+ continue;
+ key_length= table->s->table_cache_key.length + key_info->name_length + 1;
+ mysql_mutex_lock(&LOCK_global_index_stats);
+ // Gets the global index stats, creating one if necessary.
+ if (!(index_stats= (INDEX_STATS*) my_hash_search(&global_index_stats,
+ key_info->cache_name,
+ key_length)))
+ {
+ if (!(index_stats = ((INDEX_STATS*)
+ my_malloc(sizeof(INDEX_STATS),
+ MYF(MY_WME | MY_ZEROFILL)))))
+ goto end; // Error is already given
+
+ memcpy(index_stats->index, key_info->cache_name, key_length);
+ index_stats->index_name_length= key_length;
+ if (my_hash_insert(&global_index_stats, (uchar*) index_stats))
+ {
+ my_free(index_stats);
+ goto end;
+ }
+ }
+ /* Updates the global index stats. */
+ index_stats->rows_read+= index_rows_read[index];
+ index_rows_read[index]= 0;
+end:
+ mysql_mutex_unlock(&LOCK_global_index_stats);
+ }
+ }
+}
+
+
+/****************************************************************************
+** Some general functions that isn't in the handler class
+****************************************************************************/
+
+/**
+ Initiates table-file and calls appropriate database-creator.
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+int ha_create_table(THD *thd, const char *path,
+ const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info, LEX_CUSTRING *frm)
+{
+ int error= 1;
+ TABLE table;
+ char name_buff[FN_REFLEN];
+ const char *name;
+ TABLE_SHARE share;
+ bool temp_table __attribute__((unused)) =
+ create_info->options & (HA_LEX_CREATE_TMP_TABLE | HA_CREATE_TMP_ALTER);
+
+ DBUG_ENTER("ha_create_table");
+
+ init_tmp_table_share(thd, &share, db, 0, table_name, path);
+
+ if (frm)
+ {
+ bool write_frm_now= !create_info->db_type->discover_table &&
+ !create_info->tmp_table();
+
+ share.frm_image= frm;
+
+ // open an frm image
+ if (share.init_from_binary_frm_image(thd, write_frm_now,
+ frm->str, frm->length))
+ goto err;
+ }
+ else
+ {
+ // open an frm file
+ share.db_plugin= ha_lock_engine(thd, create_info->db_type);
+
+ if (open_table_def(thd, &share))
+ goto err;
+ }
+
+ share.m_psi= PSI_CALL_get_table_share(temp_table, &share);
+
+ if (open_table_from_share(thd, &share, "", 0, READ_ALL, 0, &table, true))
+ goto err;
+
+ update_create_info_from_table(create_info, &table);
+
+ name= get_canonical_filename(table.file, share.path.str, name_buff);
+
+ error= table.file->ha_create(name, &table, create_info);
+
+ if (error)
+ {
+ if (!thd->is_error())
+ my_error(ER_CANT_CREATE_TABLE, MYF(0), db, table_name, error);
+ table.file->print_error(error, MYF(ME_JUST_WARNING));
+ PSI_CALL_drop_table_share(temp_table, share.db.str, share.db.length,
+ share.table_name.str, share.table_name.length);
+ }
+
+ (void) closefrm(&table, 0);
+
+err:
+ free_table_share(&share);
+ DBUG_RETURN(error != 0);
+}
+
+void st_ha_check_opt::init()
+{
+ flags= sql_flags= 0;
+ start_time= my_time(0);
+}
+
+
+/*****************************************************************************
+ Key cache handling.
+
+ This code is only relevant for ISAM/MyISAM tables
+
+ key_cache->cache may be 0 only in the case where a key cache is not
+ initialized or when we where not able to init the key cache in a previous
+ call to ha_init_key_cache() (probably out of memory)
+*****************************************************************************/
+
+/**
+ Init a key cache if it has not been initied before.
+*/
+int ha_init_key_cache(const char *name, KEY_CACHE *key_cache, void *unused
+ __attribute__((unused)))
+{
+ DBUG_ENTER("ha_init_key_cache");
+
+ if (!key_cache->key_cache_inited)
+ {
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ size_t tmp_buff_size= (size_t) key_cache->param_buff_size;
+ uint tmp_block_size= (uint) key_cache->param_block_size;
+ uint division_limit= (uint)key_cache->param_division_limit;
+ uint age_threshold= (uint)key_cache->param_age_threshold;
+ uint partitions= (uint)key_cache->param_partitions;
+ uint changed_blocks_hash_size= (uint)key_cache->changed_blocks_hash_size;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ DBUG_RETURN(!init_key_cache(key_cache,
+ tmp_block_size,
+ tmp_buff_size,
+ division_limit, age_threshold,
+ changed_blocks_hash_size,
+ partitions));
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Resize key cache.
+*/
+int ha_resize_key_cache(KEY_CACHE *key_cache)
+{
+ DBUG_ENTER("ha_resize_key_cache");
+
+ if (key_cache->key_cache_inited)
+ {
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ size_t tmp_buff_size= (size_t) key_cache->param_buff_size;
+ long tmp_block_size= (long) key_cache->param_block_size;
+ uint division_limit= (uint)key_cache->param_division_limit;
+ uint age_threshold= (uint)key_cache->param_age_threshold;
+ uint changed_blocks_hash_size= (uint)key_cache->changed_blocks_hash_size;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ DBUG_RETURN(!resize_key_cache(key_cache, tmp_block_size,
+ tmp_buff_size,
+ division_limit, age_threshold,
+ changed_blocks_hash_size));
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Change parameters for key cache (like division_limit)
+*/
+int ha_change_key_cache_param(KEY_CACHE *key_cache)
+{
+ DBUG_ENTER("ha_change_key_cache_param");
+
+ if (key_cache->key_cache_inited)
+ {
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ uint division_limit= (uint)key_cache->param_division_limit;
+ uint age_threshold= (uint)key_cache->param_age_threshold;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ change_key_cache_param(key_cache, division_limit, age_threshold);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Repartition key cache
+*/
+int ha_repartition_key_cache(KEY_CACHE *key_cache)
+{
+ DBUG_ENTER("ha_repartition_key_cache");
+
+ if (key_cache->key_cache_inited)
+ {
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ size_t tmp_buff_size= (size_t) key_cache->param_buff_size;
+ long tmp_block_size= (long) key_cache->param_block_size;
+ uint division_limit= (uint)key_cache->param_division_limit;
+ uint age_threshold= (uint)key_cache->param_age_threshold;
+ uint partitions= (uint)key_cache->param_partitions;
+ uint changed_blocks_hash_size= (uint)key_cache->changed_blocks_hash_size;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ DBUG_RETURN(!repartition_key_cache(key_cache, tmp_block_size,
+ tmp_buff_size,
+ division_limit, age_threshold,
+ changed_blocks_hash_size,
+ partitions));
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Move all tables from one key cache to another one.
+*/
+int ha_change_key_cache(KEY_CACHE *old_key_cache,
+ KEY_CACHE *new_key_cache)
+{
+ mi_change_key_cache(old_key_cache, new_key_cache);
+ return 0;
+}
+
+
+static my_bool discover_handlerton(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ TABLE_SHARE *share= (TABLE_SHARE *)arg;
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->discover_table)
+ {
+ share->db_plugin= plugin;
+ int error= hton->discover_table(hton, thd, share);
+ if (error != HA_ERR_NO_SUCH_TABLE)
+ {
+ if (error)
+ {
+ DBUG_ASSERT(share->error); // tdc_lock_share needs that
+ /*
+ report an error, unless it is "generic" and a more
+ specific one was already reported
+ */
+ if (error != HA_ERR_GENERIC || !thd->is_error())
+ my_error(ER_GET_ERRNO, MYF(0), error, plugin_name(plugin)->str);
+ share->db_plugin= 0;
+ }
+ else
+ share->error= OPEN_FRM_OK;
+
+ status_var_increment(thd->status_var.ha_discover_count);
+ return TRUE; // abort the search
+ }
+ share->db_plugin= 0;
+ }
+
+ DBUG_ASSERT(share->error == OPEN_FRM_OPEN_ERROR);
+ return FALSE; // continue with the next engine
+}
+
+int ha_discover_table(THD *thd, TABLE_SHARE *share)
+{
+ DBUG_ENTER("ha_discover_table");
+ int found;
+
+ DBUG_ASSERT(share->error == OPEN_FRM_OPEN_ERROR); // share is not OK yet
+
+ if (!engines_with_discover)
+ found= FALSE;
+ else if (share->db_plugin)
+ found= discover_handlerton(thd, share->db_plugin, share);
+ else
+ found= plugin_foreach(thd, discover_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, share);
+
+ if (!found)
+ open_table_error(share, OPEN_FRM_OPEN_ERROR, ENOENT); // not found
+
+ DBUG_RETURN(share->error != OPEN_FRM_OK);
+}
+
+static my_bool file_ext_exists(char *path, size_t path_len, const char *ext)
+{
+ strmake(path + path_len, ext, FN_REFLEN - path_len);
+ return !access(path, F_OK);
+}
+
+struct st_discover_existence_args
+{
+ char *path;
+ size_t path_len;
+ const char *db, *table_name;
+ handlerton *hton;
+ bool frm_exists;
+};
+
+static my_bool discover_existence(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ st_discover_existence_args *args= (st_discover_existence_args*)arg;
+ handlerton *ht= plugin_hton(plugin);
+ if (ht->state != SHOW_OPTION_YES || !ht->discover_table_existence)
+ return args->frm_exists;
+
+ args->hton= ht;
+
+ if (ht->discover_table_existence == ext_based_existence)
+ return file_ext_exists(args->path, args->path_len,
+ ht->tablefile_extensions[0]);
+
+ return ht->discover_table_existence(ht, args->db, args->table_name);
+}
+
+class Table_exists_error_handler : public Internal_error_handler
+{
+public:
+ Table_exists_error_handler()
+ : m_handled_errors(0), m_unhandled_errors(0)
+ {}
+
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+ {
+ *cond_hdl= NULL;
+ if (sql_errno == ER_NO_SUCH_TABLE ||
+ sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE ||
+ sql_errno == ER_WRONG_OBJECT)
+ {
+ m_handled_errors++;
+ return TRUE;
+ }
+
+ if (level == Sql_condition::WARN_LEVEL_ERROR)
+ m_unhandled_errors++;
+ return FALSE;
+ }
+
+ bool safely_trapped_errors()
+ {
+ return ((m_handled_errors > 0) && (m_unhandled_errors == 0));
+ }
+
+private:
+ int m_handled_errors;
+ int m_unhandled_errors;
+};
+
+/**
+ Check if a given table exists, without doing a full discover, if possible
+
+ If the 'hton' is not NULL, it's set to the handlerton of the storage engine
+ of this table, or to view_pseudo_hton if the frm belongs to a view.
+
+ This function takes discovery correctly into account. If frm is found,
+ it discovers the table to make sure it really exists in the engine.
+ If no frm is found it discovers the table, in case it still exists in
+ the engine.
+
+ While it tries to cut corners (don't open .frm if no discovering engine is
+ enabled, no full discovery if all discovering engines support
+ discover_table_existence, etc), it still *may* be quite expensive
+ and must be used sparingly.
+
+ @retval true Table exists (even if the error occurred, like bad frm)
+ @retval false Table does not exist (one can do CREATE TABLE table_name)
+
+ @note if frm exists and the table in engine doesn't, *hton will be set,
+ but the return value will be false.
+
+ @note if frm file exists, but the table cannot be opened (engine not
+ loaded, frm is invalid), the return value will be true, but
+ *hton will be NULL.
+*/
+bool ha_table_exists(THD *thd, const char *db, const char *table_name,
+ handlerton **hton)
+{
+ handlerton *dummy;
+ DBUG_ENTER("ha_table_exists");
+
+ if (hton)
+ *hton= 0;
+ else if (engines_with_discover)
+ hton= &dummy;
+
+ TABLE_SHARE *share= tdc_lock_share(db, table_name);
+ if (share)
+ {
+ if (hton)
+ *hton= share->db_type();
+ tdc_unlock_share(share);
+ DBUG_RETURN(TRUE);
+ }
+
+ char path[FN_REFLEN + 1];
+ size_t path_len = build_table_filename(path, sizeof(path) - 1,
+ db, table_name, "", 0);
+ st_discover_existence_args args= {path, path_len, db, table_name, 0, true};
+
+ if (file_ext_exists(path, path_len, reg_ext))
+ {
+ bool exists= true;
+ if (hton)
+ {
+ enum legacy_db_type db_type;
+ if (dd_frm_type(thd, path, &db_type) != FRMTYPE_VIEW)
+ {
+ handlerton *ht= ha_resolve_by_legacy_type(thd, db_type);
+ if ((*hton= ht))
+ // verify that the table really exists
+ exists= discover_existence(thd,
+ plugin_int_to_ref(hton2plugin[ht->slot]), &args);
+ }
+ else
+ *hton= view_pseudo_hton;
+ }
+ DBUG_RETURN(exists);
+ }
+
+ args.frm_exists= false;
+ if (plugin_foreach(thd, discover_existence, MYSQL_STORAGE_ENGINE_PLUGIN,
+ &args))
+ {
+ if (hton)
+ *hton= args.hton;
+ DBUG_RETURN(TRUE);
+ }
+
+
+ if (need_full_discover_for_existence)
+ {
+ TABLE_LIST table;
+ uint flags = GTS_TABLE | GTS_VIEW;
+
+ if (!hton)
+ flags|= GTS_NOLOCK;
+
+ Table_exists_error_handler no_such_table_handler;
+ thd->push_internal_handler(&no_such_table_handler);
+ TABLE_SHARE *share= tdc_acquire_share(thd, db, table_name, flags);
+ thd->pop_internal_handler();
+
+ if (hton && share)
+ {
+ *hton= share->db_type();
+ tdc_release_share(share);
+ }
+
+ // the table doesn't exist if we've caught ER_NO_SUCH_TABLE and nothing else
+ DBUG_RETURN(!no_such_table_handler.safely_trapped_errors());
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Discover all table names in a given database
+*/
+extern "C" {
+
+static int cmp_file_names(const void *a, const void *b)
+{
+ CHARSET_INFO *cs= character_set_filesystem;
+ char *aa= ((FILEINFO *)a)->name;
+ char *bb= ((FILEINFO *)b)->name;
+ return my_strnncoll(cs, (uchar*)aa, strlen(aa), (uchar*)bb, strlen(bb));
+}
+
+static int cmp_table_names(LEX_STRING * const *a, LEX_STRING * const *b)
+{
+ return my_strnncoll(&my_charset_bin, (uchar*)((*a)->str), (*a)->length,
+ (uchar*)((*b)->str), (*b)->length);
+}
+
+}
+
+Discovered_table_list::Discovered_table_list(THD *thd_arg,
+ Dynamic_array<LEX_STRING*> *tables_arg,
+ const LEX_STRING *wild_arg) :
+ thd(thd_arg), with_temps(false), tables(tables_arg)
+{
+ if (wild_arg->str && wild_arg->str[0])
+ {
+ wild= wild_arg->str;
+ wend= wild + wild_arg->length;
+ }
+ else
+ wild= 0;
+}
+
+bool Discovered_table_list::add_table(const char *tname, size_t tlen)
+{
+ /*
+ TODO Check with_temps and filter out temp tables.
+ Implement the check, when we'll have at least one affected engine (with
+ custom discover_table_names() method, that calls add_table() directly).
+ Note: avoid comparing the same name twice (here and in add_file).
+ */
+ if (wild && my_wildcmp(files_charset_info, tname, tname + tlen, wild, wend,
+ wild_prefix, wild_one, wild_many))
+ return 0;
+
+ LEX_STRING *name= thd->make_lex_string(tname, tlen);
+ if (!name || tables->append(name))
+ return 1;
+ return 0;
+}
+
+bool Discovered_table_list::add_file(const char *fname)
+{
+ bool is_temp= strncmp(fname, STRING_WITH_LEN(tmp_file_prefix)) == 0;
+
+ if (is_temp && !with_temps)
+ return 0;
+
+ char tname[SAFE_NAME_LEN + 1];
+ size_t tlen= filename_to_tablename(fname, tname, sizeof(tname), is_temp);
+ return add_table(tname, tlen);
+}
+
+
+void Discovered_table_list::sort()
+{
+ tables->sort(cmp_table_names);
+}
+
+void Discovered_table_list::remove_duplicates()
+{
+ LEX_STRING **src= tables->front();
+ LEX_STRING **dst= src;
+ while (++dst <= tables->back())
+ {
+ LEX_STRING *s= *src, *d= *dst;
+ DBUG_ASSERT(strncmp(s->str, d->str, MY_MIN(s->length, d->length)) <= 0);
+ if ((s->length != d->length || strncmp(s->str, d->str, d->length)))
+ {
+ src++;
+ if (src != dst)
+ *src= *dst;
+ }
+ }
+ tables->elements(src - tables->front() + 1);
+}
+
+struct st_discover_names_args
+{
+ LEX_STRING *db;
+ MY_DIR *dirp;
+ Discovered_table_list *result;
+ uint possible_duplicates;
+};
+
+static my_bool discover_names(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ st_discover_names_args *args= (st_discover_names_args *)arg;
+ handlerton *ht= plugin_hton(plugin);
+
+ if (ht->state == SHOW_OPTION_YES && ht->discover_table_names)
+ {
+ uint old_elements= args->result->tables->elements();
+ if (ht->discover_table_names(ht, args->db, args->dirp, args->result))
+ return 1;
+
+ /*
+ hton_ext_based_table_discovery never discovers a table that has
+ a corresponding .frm file; but custom engine discover methods might
+ */
+ if (ht->discover_table_names != hton_ext_based_table_discovery)
+ args->possible_duplicates+= args->result->tables->elements() - old_elements;
+ }
+
+ return 0;
+}
+
+/**
+ Return the list of tables
+
+ @param thd
+ @param db database to look into
+ @param dirp list of files in this database (as returned by my_dir())
+ @param result the object to return the list of files in
+ @param reusable if true, on return, 'dirp' will be a valid list of all
+ non-table files. If false, discovery will work much faster,
+ but it will leave 'dirp' corrupted and completely unusable,
+ only good for my_dirend().
+
+ Normally, reusable=false for SHOW and INFORMATION_SCHEMA, and reusable=true
+ for DROP DATABASE (as it needs to know and delete non-table files).
+*/
+
+int ha_discover_table_names(THD *thd, LEX_STRING *db, MY_DIR *dirp,
+ Discovered_table_list *result, bool reusable)
+{
+ int error;
+ DBUG_ENTER("ha_discover_table_names");
+
+ if (engines_with_discover_table_names == 0 && !reusable)
+ {
+ error= ext_table_discovery_simple(dirp, result);
+ result->sort();
+ }
+ else
+ {
+ st_discover_names_args args= {db, dirp, result, 0};
+
+ /* extension_based_table_discovery relies on dirp being sorted */
+ my_qsort(dirp->dir_entry, dirp->number_of_files,
+ sizeof(FILEINFO), cmp_file_names);
+
+ error= extension_based_table_discovery(dirp, reg_ext, result) ||
+ plugin_foreach(thd, discover_names,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &args);
+ result->sort();
+
+ if (args.possible_duplicates > 0)
+ result->remove_duplicates();
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+#ifdef HAVE_NDB_BINLOG
+/*
+ TODO: change this into a dynamic struct
+ List<handlerton> does not work as
+ 1. binlog_end is called when MEM_ROOT is gone
+ 2. cannot work with thd MEM_ROOT as memory should be freed
+*/
+#define MAX_HTON_LIST_ST 63
+struct hton_list_st
+{
+ handlerton *hton[MAX_HTON_LIST_ST];
+ uint sz;
+};
+
+struct binlog_func_st
+{
+ enum_binlog_func fn;
+ void *arg;
+};
+
+/** @brief
+ Listing handlertons first to avoid recursive calls and deadlock
+*/
+static my_bool binlog_func_list(THD *thd, plugin_ref plugin, void *arg)
+{
+ hton_list_st *hton_list= (hton_list_st *)arg;
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->binlog_func)
+ {
+ uint sz= hton_list->sz;
+ if (sz == MAX_HTON_LIST_ST-1)
+ {
+ /* list full */
+ return FALSE;
+ }
+ hton_list->hton[sz]= hton;
+ hton_list->sz= sz+1;
+ }
+ return FALSE;
+}
+
+static my_bool binlog_func_foreach(THD *thd, binlog_func_st *bfn)
+{
+ hton_list_st hton_list;
+ uint i, sz;
+
+ hton_list.sz= 0;
+ plugin_foreach(thd, binlog_func_list,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &hton_list);
+
+ for (i= 0, sz= hton_list.sz; i < sz ; i++)
+ hton_list.hton[i]->binlog_func(hton_list.hton[i], thd, bfn->fn, bfn->arg);
+ return FALSE;
+}
+
+int ha_reset_logs(THD *thd)
+{
+ binlog_func_st bfn= {BFN_RESET_LOGS, 0};
+ binlog_func_foreach(thd, &bfn);
+ return 0;
+}
+
+void ha_reset_slave(THD* thd)
+{
+ binlog_func_st bfn= {BFN_RESET_SLAVE, 0};
+ binlog_func_foreach(thd, &bfn);
+}
+
+void ha_binlog_wait(THD* thd)
+{
+ binlog_func_st bfn= {BFN_BINLOG_WAIT, 0};
+ binlog_func_foreach(thd, &bfn);
+}
+
+int ha_binlog_end(THD* thd)
+{
+ binlog_func_st bfn= {BFN_BINLOG_END, 0};
+ binlog_func_foreach(thd, &bfn);
+ return 0;
+}
+
+int ha_binlog_index_purge_file(THD *thd, const char *file)
+{
+ binlog_func_st bfn= {BFN_BINLOG_PURGE_FILE, (void *)file};
+ binlog_func_foreach(thd, &bfn);
+ return 0;
+}
+
+struct binlog_log_query_st
+{
+ enum_binlog_command binlog_command;
+ const char *query;
+ uint query_length;
+ const char *db;
+ const char *table_name;
+};
+
+static my_bool binlog_log_query_handlerton2(THD *thd,
+ handlerton *hton,
+ void *args)
+{
+ struct binlog_log_query_st *b= (struct binlog_log_query_st*)args;
+ if (hton->state == SHOW_OPTION_YES && hton->binlog_log_query)
+ hton->binlog_log_query(hton, thd,
+ b->binlog_command,
+ b->query,
+ b->query_length,
+ b->db,
+ b->table_name);
+ return FALSE;
+}
+
+static my_bool binlog_log_query_handlerton(THD *thd,
+ plugin_ref plugin,
+ void *args)
+{
+ return binlog_log_query_handlerton2(thd, plugin_hton(plugin), args);
+}
+
+void ha_binlog_log_query(THD *thd, handlerton *hton,
+ enum_binlog_command binlog_command,
+ const char *query, uint query_length,
+ const char *db, const char *table_name)
+{
+ struct binlog_log_query_st b;
+ b.binlog_command= binlog_command;
+ b.query= query;
+ b.query_length= query_length;
+ b.db= db;
+ b.table_name= table_name;
+ if (hton == 0)
+ plugin_foreach(thd, binlog_log_query_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &b);
+ else
+ binlog_log_query_handlerton2(thd, hton, &b);
+}
+#endif
+
+
+/**
+ Read first row between two ranges.
+ Store ranges for future calls to read_range_next.
+
+ @param start_key Start key. Is 0 if no min range
+ @param end_key End key. Is 0 if no max range
+ @param eq_range_arg Set to 1 if start_key == end_key
+ @param sorted Set to 1 if result should be sorted per key
+
+ @note
+ Record is read into table->record[0]
+
+ @retval
+ 0 Found row
+ @retval
+ HA_ERR_END_OF_FILE No rows in range
+ @retval
+ \# Error code
+*/
+int handler::read_range_first(const key_range *start_key,
+ const key_range *end_key,
+ bool eq_range_arg, bool sorted)
+{
+ int result;
+ DBUG_ENTER("handler::read_range_first");
+
+ eq_range= eq_range_arg;
+ set_end_range(end_key);
+ range_key_part= table->key_info[active_index].key_part;
+
+ if (!start_key) // Read first record
+ result= ha_index_first(table->record[0]);
+ else
+ result= ha_index_read_map(table->record[0],
+ start_key->key,
+ start_key->keypart_map,
+ start_key->flag);
+ if (result)
+ DBUG_RETURN((result == HA_ERR_KEY_NOT_FOUND)
+ ? HA_ERR_END_OF_FILE
+ : result);
+
+ if (compare_key(end_range) <= 0)
+ {
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ /*
+ The last read row does not fall in the range. So request
+ storage engine to release row lock if possible.
+ */
+ unlock_row();
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+}
+
+
+/**
+ Read next row between two ranges.
+
+ @note
+ Record is read into table->record[0]
+
+ @retval
+ 0 Found row
+ @retval
+ HA_ERR_END_OF_FILE No rows in range
+ @retval
+ \# Error code
+*/
+int handler::read_range_next()
+{
+ int result;
+ DBUG_ENTER("handler::read_range_next");
+
+ if (eq_range)
+ {
+ /* We trust that index_next_same always gives a row in range */
+ DBUG_RETURN(ha_index_next_same(table->record[0],
+ end_range->key,
+ end_range->length));
+ }
+ result= ha_index_next(table->record[0]);
+ if (result)
+ DBUG_RETURN(result);
+
+ if (compare_key(end_range) <= 0)
+ {
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ /*
+ The last read row does not fall in the range. So request
+ storage engine to release row lock if possible.
+ */
+ unlock_row();
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+}
+
+
+void handler::set_end_range(const key_range *end_key)
+{
+ end_range= 0;
+ if (end_key)
+ {
+ end_range= &save_end_range;
+ save_end_range= *end_key;
+ key_compare_result_on_equal=
+ ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 :
+ (end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0);
+ }
+}
+
+
+/**
+ Compare if found key (in row) is over max-value.
+
+ @param range range to compare to row. May be 0 for no range
+
+ @see also
+ key.cc::key_cmp()
+
+ @return
+ The return value is SIGN(key_in_row - range_key):
+
+ - 0 : Key is equal to range or 'range' == 0 (no range)
+ - -1 : Key is less than range
+ - 1 : Key is larger than range
+*/
+int handler::compare_key(key_range *range)
+{
+ int cmp;
+ if (!range || in_range_check_pushed_down)
+ return 0; // No max range
+ cmp= key_cmp(range_key_part, range->key, range->length);
+ if (!cmp)
+ cmp= key_compare_result_on_equal;
+ return cmp;
+}
+
+
+/*
+ Same as compare_key() but doesn't check have in_range_check_pushed_down.
+ This is used by index condition pushdown implementation.
+*/
+
+int handler::compare_key2(key_range *range)
+{
+ int cmp;
+ if (!range)
+ return 0; // no max range
+ cmp= key_cmp(range_key_part, range->key, range->length);
+ if (!cmp)
+ cmp= key_compare_result_on_equal;
+ return cmp;
+}
+
+
+/**
+ ICP callback - to be called by an engine to check the pushed condition
+*/
+extern "C" enum icp_result handler_index_cond_check(void* h_arg)
+{
+ handler *h= (handler*)h_arg;
+ THD *thd= h->table->in_use;
+ enum icp_result res;
+
+ enum thd_kill_levels abort_at= h->has_transactions() ?
+ THD_ABORT_SOFTLY : THD_ABORT_ASAP;
+ if (thd_kill_level(thd) > abort_at)
+ return ICP_ABORTED_BY_USER;
+
+ if (h->end_range && h->compare_key2(h->end_range) > 0)
+ return ICP_OUT_OF_RANGE;
+ h->increment_statistics(&SSV::ha_icp_attempts);
+ if ((res= h->pushed_idx_cond->val_int()? ICP_MATCH : ICP_NO_MATCH) ==
+ ICP_MATCH)
+ h->increment_statistics(&SSV::ha_icp_match);
+ return res;
+}
+
+int handler::index_read_idx_map(uchar * buf, uint index, const uchar * key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag)
+{
+ int error, error1;
+ LINT_INIT(error1);
+
+ error= ha_index_init(index, 0);
+ if (!error)
+ {
+ error= index_read_map(buf, key, keypart_map, find_flag);
+ error1= ha_index_end();
+ }
+ return error ? error : error1;
+}
+
+
+/**
+ Returns a list of all known extensions.
+
+ No mutexes, worst case race is a minor surplus memory allocation
+ We have to recreate the extension map if mysqld is restarted (for example
+ within libmysqld)
+
+ @retval
+ pointer pointer to TYPELIB structure
+*/
+static my_bool exts_handlerton(THD *unused, plugin_ref plugin,
+ void *arg)
+{
+ List<char> *found_exts= (List<char> *) arg;
+ handlerton *hton= plugin_hton(plugin);
+ List_iterator_fast<char> it(*found_exts);
+ const char **ext, *old_ext;
+
+ for (ext= hton->tablefile_extensions; *ext; ext++)
+ {
+ while ((old_ext= it++))
+ {
+ if (!strcmp(old_ext, *ext))
+ break;
+ }
+ if (!old_ext)
+ found_exts->push_back((char *) *ext);
+
+ it.rewind();
+ }
+ return FALSE;
+}
+
+TYPELIB *ha_known_exts(void)
+{
+ if (!known_extensions.type_names || mysys_usage_id != known_extensions_id)
+ {
+ List<char> found_exts;
+ const char **ext, *old_ext;
+
+ known_extensions_id= mysys_usage_id;
+ found_exts.push_back((char*) TRG_EXT);
+ found_exts.push_back((char*) TRN_EXT);
+
+ plugin_foreach(NULL, exts_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &found_exts);
+
+ ext= (const char **) my_once_alloc(sizeof(char *)*
+ (found_exts.elements+1),
+ MYF(MY_WME | MY_FAE));
+
+ DBUG_ASSERT(ext != 0);
+ known_extensions.count= found_exts.elements;
+ known_extensions.type_names= ext;
+
+ List_iterator_fast<char> it(found_exts);
+ while ((old_ext= it++))
+ *ext++= old_ext;
+ *ext= 0;
+ }
+ return &known_extensions;
+}
+
+
+static bool stat_print(THD *thd, const char *type, uint type_len,
+ const char *file, uint file_len,
+ const char *status, uint status_len)
+{
+ Protocol *protocol= thd->protocol;
+ protocol->prepare_for_resend();
+ protocol->store(type, type_len, system_charset_info);
+ protocol->store(file, file_len, system_charset_info);
+ protocol->store(status, status_len, system_charset_info);
+ if (protocol->write())
+ return TRUE;
+ return FALSE;
+}
+
+
+static my_bool showstat_handlerton(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ enum ha_stat_type stat= *(enum ha_stat_type *) arg;
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->state == SHOW_OPTION_YES && hton->show_status &&
+ hton->show_status(hton, thd, stat_print, stat))
+ return TRUE;
+ return FALSE;
+}
+
+bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat)
+{
+ List<Item> field_list;
+ Protocol *protocol= thd->protocol;
+ bool result;
+
+ field_list.push_back(new Item_empty_string("Type",10));
+ field_list.push_back(new Item_empty_string("Name",FN_REFLEN));
+ field_list.push_back(new Item_empty_string("Status",10));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ return TRUE;
+
+ if (db_type == NULL)
+ {
+ result= plugin_foreach(thd, showstat_handlerton,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &stat);
+ }
+ else
+ {
+ if (db_type->state != SHOW_OPTION_YES)
+ {
+ const LEX_STRING *name= hton_name(db_type);
+ result= stat_print(thd, name->str, name->length,
+ "", 0, "DISABLED", 8) ? 1 : 0;
+ }
+ else
+ {
+ result= db_type->show_status &&
+ db_type->show_status(db_type, thd, stat_print, stat) ? 1 : 0;
+ }
+ }
+
+ /*
+ We also check thd->is_error() as Innodb may return 0 even if
+ there was an error.
+ */
+ if (!result && !thd->is_error())
+ my_eof(thd);
+ else if (!thd->is_error())
+ my_error(ER_GET_ERRNO, MYF(0), errno, hton_name(db_type)->str);
+ return result;
+}
+
+/*
+ Function to check if the conditions for row-based binlogging is
+ correct for the table.
+
+ A row in the given table should be replicated if:
+ - Row-based replication is enabled in the current thread
+ - The binlog is enabled
+ - It is not a temporary table
+ - The binary log is open
+ - The database the table resides in shall be binlogged (binlog_*_db rules)
+ - table is not mysql.event
+*/
+
+static bool check_table_binlog_row_based(THD *thd, TABLE *table)
+{
+ if (table->s->cached_row_logging_check == -1)
+ {
+ int const check(table->s->tmp_table == NO_TMP_TABLE &&
+ ! table->no_replicate &&
+ binlog_filter->db_ok(table->s->db.str));
+ table->s->cached_row_logging_check= check;
+ }
+
+ DBUG_ASSERT(table->s->cached_row_logging_check == 0 ||
+ table->s->cached_row_logging_check == 1);
+
+ return (thd->is_current_stmt_binlog_format_row() &&
+ table->s->cached_row_logging_check &&
+ (thd->variables.option_bits & OPTION_BIN_LOG) &&
+ mysql_bin_log.is_open());
+}
+
+
+/** @brief
+ Write table maps for all (manually or automatically) locked tables
+ to the binary log. Also, if binlog_annotate_row_events is ON,
+ write Annotate_rows event before the first table map.
+
+ SYNOPSIS
+ write_locked_table_maps()
+ thd Pointer to THD structure
+
+ DESCRIPTION
+ This function will generate and write table maps for all tables
+ that are locked by the thread 'thd'.
+
+ RETURN VALUE
+ 0 All OK
+ 1 Failed to write all table maps
+
+ SEE ALSO
+ THD::lock
+*/
+
+static int write_locked_table_maps(THD *thd)
+{
+ DBUG_ENTER("write_locked_table_maps");
+ DBUG_PRINT("enter", ("thd: 0x%lx thd->lock: 0x%lx "
+ "thd->extra_lock: 0x%lx",
+ (long) thd, (long) thd->lock, (long) thd->extra_lock));
+
+ DBUG_PRINT("debug", ("get_binlog_table_maps(): %d", thd->get_binlog_table_maps()));
+
+ if (thd->get_binlog_table_maps() == 0)
+ {
+ MYSQL_LOCK *locks[2];
+ locks[0]= thd->extra_lock;
+ locks[1]= thd->lock;
+ my_bool with_annotate= thd->variables.binlog_annotate_row_events &&
+ thd->query() && thd->query_length();
+
+ for (uint i= 0 ; i < sizeof(locks)/sizeof(*locks) ; ++i )
+ {
+ MYSQL_LOCK const *const lock= locks[i];
+ if (lock == NULL)
+ continue;
+
+ TABLE **const end_ptr= lock->table + lock->table_count;
+ for (TABLE **table_ptr= lock->table ;
+ table_ptr != end_ptr ;
+ ++table_ptr)
+ {
+ TABLE *const table= *table_ptr;
+ DBUG_PRINT("info", ("Checking table %s", table->s->table_name.str));
+ if (table->current_lock == F_WRLCK &&
+ check_table_binlog_row_based(thd, table))
+ {
+ /*
+ We need to have a transactional behavior for SQLCOM_CREATE_TABLE
+ (e.g. CREATE TABLE... SELECT * FROM TABLE) in order to keep a
+ compatible behavior with the STMT based replication even when
+ the table is not transactional. In other words, if the operation
+ fails while executing the insert phase nothing is written to the
+ binlog.
+
+ Note that at this point, we check the type of a set of tables to
+ create the table map events. In the function binlog_log_row(),
+ which calls the current function, we check the type of the table
+ of the current row.
+ */
+ bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
+ table->file->has_transactions();
+ int const error= thd->binlog_write_table_map(table, has_trans,
+ &with_annotate);
+ /*
+ If an error occurs, it is the responsibility of the caller to
+ roll back the transaction.
+ */
+ if (unlikely(error))
+ DBUG_RETURN(1);
+ }
+ }
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+typedef bool Log_func(THD*, TABLE*, bool, MY_BITMAP*,
+ uint, const uchar*, const uchar*);
+
+static int binlog_log_row(TABLE* table,
+ const uchar *before_record,
+ const uchar *after_record,
+ Log_func *log_func)
+{
+ bool error= 0;
+ THD *const thd= table->in_use;
+
+ if (check_table_binlog_row_based(thd, table))
+ {
+ MY_BITMAP cols;
+ /* Potential buffer on the stack for the bitmap */
+ uint32 bitbuf[BITMAP_STACKBUF_SIZE/sizeof(uint32)];
+ uint n_fields= table->s->fields;
+ my_bool use_bitbuf= n_fields <= sizeof(bitbuf)*8;
+
+ /*
+ If there are no table maps written to the binary log, this is
+ the first row handled in this statement. In that case, we need
+ to write table maps for all locked tables to the binary log.
+ */
+ if (likely(!(error= my_bitmap_init(&cols,
+ use_bitbuf ? bitbuf : NULL,
+ (n_fields + 7) & ~7UL,
+ FALSE))))
+ {
+ bitmap_set_all(&cols);
+ if (likely(!(error= write_locked_table_maps(thd))))
+ {
+ /*
+ We need to have a transactional behavior for SQLCOM_CREATE_TABLE
+ (i.e. CREATE TABLE... SELECT * FROM TABLE) in order to keep a
+ compatible behavior with the STMT based replication even when
+ the table is not transactional. In other words, if the operation
+ fails while executing the insert phase nothing is written to the
+ binlog.
+ */
+ bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
+ table->file->has_transactions();
+ error= (*log_func)(thd, table, has_trans, &cols, table->s->fields,
+ before_record, after_record);
+ }
+ if (!use_bitbuf)
+ my_bitmap_free(&cols);
+ }
+ }
+ return error ? HA_ERR_RBR_LOGGING_FAILED : 0;
+}
+
+int handler::ha_external_lock(THD *thd, int lock_type)
+{
+ int error;
+ DBUG_ENTER("handler::ha_external_lock");
+ /*
+ Whether this is lock or unlock, this should be true, and is to verify that
+ if get_auto_increment() was called (thus may have reserved intervals or
+ taken a table lock), ha_release_auto_increment() was too.
+ */
+ DBUG_ASSERT(next_insert_id == 0);
+ /* Consecutive calls for lock without unlocking in between is not allowed */
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ ((lock_type != F_UNLCK && m_lock_type == F_UNLCK) ||
+ lock_type == F_UNLCK));
+ /* SQL HANDLER call locks/unlock while scanning (RND/INDEX). */
+ DBUG_ASSERT(inited == NONE || table->open_by_handler);
+
+ if (MYSQL_HANDLER_RDLOCK_START_ENABLED() ||
+ MYSQL_HANDLER_WRLOCK_START_ENABLED() ||
+ MYSQL_HANDLER_UNLOCK_START_ENABLED())
+ {
+ if (lock_type == F_RDLCK)
+ {
+ MYSQL_HANDLER_RDLOCK_START(table_share->db.str,
+ table_share->table_name.str);
+ }
+ else if (lock_type == F_WRLCK)
+ {
+ MYSQL_HANDLER_WRLOCK_START(table_share->db.str,
+ table_share->table_name.str);
+ }
+ else if (lock_type == F_UNLCK)
+ {
+ MYSQL_HANDLER_UNLOCK_START(table_share->db.str,
+ table_share->table_name.str);
+ }
+ }
+
+ ha_statistic_increment(&SSV::ha_external_lock_count);
+
+ /*
+ We cache the table flags if the locking succeeded. Otherwise, we
+ keep them as they were when they were fetched in ha_open().
+ */
+ MYSQL_TABLE_LOCK_WAIT(m_psi, PSI_TABLE_EXTERNAL_LOCK, lock_type,
+ { error= external_lock(thd, lock_type); })
+
+ if (error == 0)
+ {
+ m_lock_type= lock_type;
+ cached_table_flags= table_flags();
+ if (table_share->tmp_table == NO_TMP_TABLE)
+ mysql_audit_external_lock(thd, table_share, lock_type);
+ }
+
+ if (MYSQL_HANDLER_RDLOCK_DONE_ENABLED() ||
+ MYSQL_HANDLER_WRLOCK_DONE_ENABLED() ||
+ MYSQL_HANDLER_UNLOCK_DONE_ENABLED())
+ {
+ if (lock_type == F_RDLCK)
+ {
+ MYSQL_HANDLER_RDLOCK_DONE(error);
+ }
+ else if (lock_type == F_WRLCK)
+ {
+ MYSQL_HANDLER_WRLOCK_DONE(error);
+ }
+ else if (lock_type == F_UNLCK)
+ {
+ MYSQL_HANDLER_UNLOCK_DONE(error);
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+
+/** @brief
+ Check handler usage and reset state of file to after 'open'
+*/
+int handler::ha_reset()
+{
+ DBUG_ENTER("ha_reset");
+ /* Check that we have called all proper deallocation functions */
+ DBUG_ASSERT((uchar*) table->def_read_set.bitmap +
+ table->s->column_bitmap_size ==
+ (uchar*) table->def_write_set.bitmap);
+ DBUG_ASSERT(bitmap_is_set_all(&table->s->all_set));
+ DBUG_ASSERT(table->key_read == 0);
+ /* ensure that ha_index_end / ha_rnd_end has been called */
+ DBUG_ASSERT(inited == NONE);
+ /* Free cache used by filesort */
+ free_io_cache(table);
+ /* reset the bitmaps to point to defaults */
+ table->default_column_bitmaps();
+ pushed_cond= NULL;
+ /* Reset information about pushed engine conditions */
+ cancel_pushed_idx_cond();
+ /* Reset information about pushed index conditions */
+ DBUG_RETURN(reset());
+}
+
+
+int handler::ha_write_row(uchar *buf)
+{
+ int error;
+ Log_func *log_func= Write_rows_log_event::binlog_row_logging_function;
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ DBUG_ENTER("handler::ha_write_row");
+ DEBUG_SYNC_C("ha_write_row_start");
+
+ MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str);
+ mark_trx_read_write();
+ increment_statistics(&SSV::ha_write_count);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,
+ { error= write_row(buf); })
+
+ MYSQL_INSERT_ROW_DONE(error);
+ if (unlikely(error))
+ DBUG_RETURN(error);
+ rows_changed++;
+ if (unlikely(error= binlog_log_row(table, 0, buf, log_func)))
+ DBUG_RETURN(error); /* purecov: inspected */
+
+ DEBUG_SYNC_C("ha_write_row_end");
+ DBUG_RETURN(0);
+}
+
+
+int handler::ha_update_row(const uchar *old_data, uchar *new_data)
+{
+ int error;
+ Log_func *log_func= Update_rows_log_event::binlog_row_logging_function;
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+
+ /*
+ Some storage engines require that the new record is in record[0]
+ (and the old record is in record[1]).
+ */
+ DBUG_ASSERT(new_data == table->record[0]);
+ DBUG_ASSERT(old_data == table->record[1]);
+
+ MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str);
+ mark_trx_read_write();
+ increment_statistics(&SSV::ha_update_count);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_UPDATE_ROW, active_index, 0,
+ { error= update_row(old_data, new_data);})
+
+ MYSQL_UPDATE_ROW_DONE(error);
+ if (unlikely(error))
+ return error;
+ rows_changed++;
+ if (unlikely(error= binlog_log_row(table, old_data, new_data, log_func)))
+ return error;
+ return 0;
+}
+
+int handler::ha_delete_row(const uchar *buf)
+{
+ int error;
+ Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function;
+ DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
+ m_lock_type == F_WRLCK);
+ /*
+ Normally table->record[0] is used, but sometimes table->record[1] is used.
+ */
+ DBUG_ASSERT(buf == table->record[0] ||
+ buf == table->record[1]);
+
+ MYSQL_DELETE_ROW_START(table_share->db.str, table_share->table_name.str);
+ mark_trx_read_write();
+ increment_statistics(&SSV::ha_delete_count);
+
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_DELETE_ROW, active_index, 0,
+ { error= delete_row(buf);})
+ MYSQL_DELETE_ROW_DONE(error);
+ if (unlikely(error))
+ return error;
+ rows_changed++;
+ if (unlikely(error= binlog_log_row(table, buf, 0, log_func)))
+ return error;
+ return 0;
+}
+
+
+
+/** @brief
+ use_hidden_primary_key() is called in case of an update/delete when
+ (table_flags() and HA_PRIMARY_KEY_REQUIRED_FOR_DELETE) is defined
+ but we don't have a primary key
+*/
+void handler::use_hidden_primary_key()
+{
+ /* fallback to use all columns in the table to identify row */
+ table->column_bitmaps_set(&table->s->all_set, table->write_set);
+}
+
+
+/**
+ Get an initialized ha_share.
+
+ @return Initialized ha_share
+ @retval NULL ha_share is not yet initialized.
+ @retval != NULL previous initialized ha_share.
+
+ @note
+ If not a temp table, then LOCK_ha_data must be held.
+*/
+
+Handler_share *handler::get_ha_share_ptr()
+{
+ DBUG_ENTER("handler::get_ha_share_ptr");
+ DBUG_ASSERT(ha_share && table_share);
+
+#ifndef DBUG_OFF
+ if (table_share->tmp_table == NO_TMP_TABLE)
+ mysql_mutex_assert_owner(&table_share->LOCK_ha_data);
+#endif
+
+ DBUG_RETURN(*ha_share);
+}
+
+
+/**
+ Set ha_share to be used by all instances of the same table/partition.
+
+ @param ha_share Handler_share to be shared.
+
+ @note
+ If not a temp table, then LOCK_ha_data must be held.
+*/
+
+void handler::set_ha_share_ptr(Handler_share *arg_ha_share)
+{
+ DBUG_ENTER("handler::set_ha_share_ptr");
+ DBUG_ASSERT(ha_share);
+#ifndef DBUG_OFF
+ if (table_share->tmp_table == NO_TMP_TABLE)
+ mysql_mutex_assert_owner(&table_share->LOCK_ha_data);
+#endif
+
+ *ha_share= arg_ha_share;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Take a lock for protecting shared handler data.
+*/
+
+void handler::lock_shared_ha_data()
+{
+ DBUG_ASSERT(table_share);
+ if (table_share->tmp_table == NO_TMP_TABLE)
+ mysql_mutex_lock(&table_share->LOCK_ha_data);
+}
+
+
+/**
+ Release lock for protecting ha_share.
+*/
+
+void handler::unlock_shared_ha_data()
+{
+ DBUG_ASSERT(table_share);
+ if (table_share->tmp_table == NO_TMP_TABLE)
+ mysql_mutex_unlock(&table_share->LOCK_ha_data);
+}
+
+/** @brief
+ Dummy function which accept information about log files which is not need
+ by handlers
+*/
+void signal_log_not_needed(struct handlerton, char *log_file)
+{
+ DBUG_ENTER("signal_log_not_needed");
+ DBUG_PRINT("enter", ("logfile '%s'", log_file));
+ DBUG_VOID_RETURN;
+}
+
+void handler::set_lock_type(enum thr_lock_type lock)
+{
+ table->reginfo.lock_type= lock;
+}
+
+#ifdef TRANS_LOG_MGM_EXAMPLE_CODE
+/*
+ Example of transaction log management functions based on assumption that logs
+ placed into a directory
+*/
+#include <my_dir.h>
+#include <my_sys.h>
+int example_of_iterator_using_for_logs_cleanup(handlerton *hton)
+{
+ void *buffer;
+ int res= 1;
+ struct handler_iterator iterator;
+ struct handler_log_file_data data;
+
+ if (!hton->create_iterator)
+ return 1; /* iterator creator is not supported */
+
+ if ((*hton->create_iterator)(hton, HA_TRANSACTLOG_ITERATOR, &iterator) !=
+ HA_ITERATOR_OK)
+ {
+ /* error during creation of log iterator or iterator is not supported */
+ return 1;
+ }
+ while((*iterator.next)(&iterator, (void*)&data) == 0)
+ {
+ printf("%s\n", data.filename.str);
+ if (data.status == HA_LOG_STATUS_FREE &&
+ mysql_file_delete(INSTRUMENT_ME,
+ data.filename.str, MYF(MY_WME)))
+ goto err;
+ }
+ res= 0;
+err:
+ (*iterator.destroy)(&iterator);
+ return res;
+}
+
+
+/*
+ Here we should get info from handler where it save logs but here is
+ just example, so we use constant.
+ IMHO FN_ROOTDIR ("/") is safe enough for example, because nobody has
+ rights on it except root and it consist of directories only at lest for
+ *nix (sorry, can't find windows-safe solution here, but it is only example).
+*/
+#define fl_dir FN_ROOTDIR
+
+
+/** @brief
+ Dummy function to return log status should be replaced by function which
+ really detect the log status and check that the file is a log of this
+ handler.
+*/
+enum log_status fl_get_log_status(char *log)
+{
+ MY_STAT stat_buff;
+ if (mysql_file_stat(INSTRUMENT_ME, log, &stat_buff, MYF(0)))
+ return HA_LOG_STATUS_INUSE;
+ return HA_LOG_STATUS_NOSUCHLOG;
+}
+
+
+struct fl_buff
+{
+ LEX_STRING *names;
+ enum log_status *statuses;
+ uint32 entries;
+ uint32 current;
+};
+
+
+int fl_log_iterator_next(struct handler_iterator *iterator,
+ void *iterator_object)
+{
+ struct fl_buff *buff= (struct fl_buff *)iterator->buffer;
+ struct handler_log_file_data *data=
+ (struct handler_log_file_data *) iterator_object;
+ if (buff->current >= buff->entries)
+ return 1;
+ data->filename= buff->names[buff->current];
+ data->status= buff->statuses[buff->current];
+ buff->current++;
+ return 0;
+}
+
+
+void fl_log_iterator_destroy(struct handler_iterator *iterator)
+{
+ my_free(iterator->buffer);
+}
+
+
+/** @brief
+ returns buffer, to be assigned in handler_iterator struct
+*/
+enum handler_create_iterator_result
+fl_log_iterator_buffer_init(struct handler_iterator *iterator)
+{
+ MY_DIR *dirp;
+ struct fl_buff *buff;
+ char *name_ptr;
+ uchar *ptr;
+ FILEINFO *file;
+ uint32 i;
+
+ /* to be able to make my_free without crash in case of error */
+ iterator->buffer= 0;
+
+ if (!(dirp = my_dir(fl_dir, MYF(MY_THREAD_SPECIFIC))))
+ {
+ return HA_ITERATOR_ERROR;
+ }
+ if ((ptr= (uchar*)my_malloc(ALIGN_SIZE(sizeof(fl_buff)) +
+ ((ALIGN_SIZE(sizeof(LEX_STRING)) +
+ sizeof(enum log_status) +
+ + FN_REFLEN + 1) *
+ (uint) dirp->number_off_files),
+ MYF(MY_THREAD_SPECIFIC))) == 0)
+ {
+ return HA_ITERATOR_ERROR;
+ }
+ buff= (struct fl_buff *)ptr;
+ buff->entries= buff->current= 0;
+ ptr= ptr + (ALIGN_SIZE(sizeof(fl_buff)));
+ buff->names= (LEX_STRING*) (ptr);
+ ptr= ptr + ((ALIGN_SIZE(sizeof(LEX_STRING)) *
+ (uint) dirp->number_off_files));
+ buff->statuses= (enum log_status *)(ptr);
+ name_ptr= (char *)(ptr + (sizeof(enum log_status) *
+ (uint) dirp->number_off_files));
+ for (i=0 ; i < (uint) dirp->number_off_files ; i++)
+ {
+ enum log_status st;
+ file= dirp->dir_entry + i;
+ if ((file->name[0] == '.' &&
+ ((file->name[1] == '.' && file->name[2] == '\0') ||
+ file->name[1] == '\0')))
+ continue;
+ if ((st= fl_get_log_status(file->name)) == HA_LOG_STATUS_NOSUCHLOG)
+ continue;
+ name_ptr= strxnmov(buff->names[buff->entries].str= name_ptr,
+ FN_REFLEN, fl_dir, file->name, NullS);
+ buff->names[buff->entries].length= (name_ptr -
+ buff->names[buff->entries].str);
+ buff->statuses[buff->entries]= st;
+ buff->entries++;
+ }
+
+ iterator->buffer= buff;
+ iterator->next= &fl_log_iterator_next;
+ iterator->destroy= &fl_log_iterator_destroy;
+ my_dirend(dirp);
+ return HA_ITERATOR_OK;
+}
+
+
+/* An example of a iterator creator */
+enum handler_create_iterator_result
+fl_create_iterator(enum handler_iterator_type type,
+ struct handler_iterator *iterator)
+{
+ switch(type) {
+ case HA_TRANSACTLOG_ITERATOR:
+ return fl_log_iterator_buffer_init(iterator);
+ default:
+ return HA_ITERATOR_UNSUPPORTED;
+ }
+}
+#endif /*TRANS_LOG_MGM_EXAMPLE_CODE*/
+
+
+bool HA_CREATE_INFO::check_conflicting_charset_declarations(CHARSET_INFO *cs)
+{
+ if ((used_fields & HA_CREATE_USED_DEFAULT_CHARSET) &&
+ /* DEFAULT vs explicit, or explicit vs DEFAULT */
+ (((default_table_charset == NULL) != (cs == NULL)) ||
+ /* Two different explicit character sets */
+ (default_table_charset && cs &&
+ !my_charset_same(default_table_charset, cs))))
+ {
+ my_error(ER_CONFLICTING_DECLARATIONS, MYF(0),
+ "CHARACTER SET ", default_table_charset ?
+ default_table_charset->csname : "DEFAULT",
+ "CHARACTER SET ", cs ? cs->csname : "DEFAULT");
+ return true;
+ }
+ return false;
+}
diff --git a/sql/handler.h b/sql/handler.h
new file mode 100644
index 00000000000..2e219d5f3cc
--- /dev/null
+++ b/sql/handler.h
@@ -0,0 +1,4153 @@
+#ifndef HANDLER_INCLUDED
+#define HANDLER_INCLUDED
+/*
+ Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, Monty Program Ab.
+
+ 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
+*/
+
+/* Definitions for parameters to do with handler-routines */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_const.h"
+#include "mysqld.h" /* server_id */
+#include "sql_plugin.h" /* plugin_ref, st_plugin_int, plugin */
+#include "thr_lock.h" /* thr_lock_type, THR_LOCK_DATA */
+#include "sql_cache.h"
+#include "structs.h" /* SHOW_COMP_OPTION */
+#include "sql_array.h" /* Dynamic_array<> */
+#include "mdl.h"
+
+#include <my_compare.h>
+#include <ft_global.h>
+#include <keycache.h>
+#include <mysql/psi/mysql_table.h>
+
+#if MAX_KEY > 128
+#error MAX_KEY is too large. Values up to 128 are supported.
+#endif
+
+class Alter_info;
+
+// the following is for checking tables
+
+#define HA_ADMIN_ALREADY_DONE 1
+#define HA_ADMIN_OK 0
+#define HA_ADMIN_NOT_IMPLEMENTED -1
+#define HA_ADMIN_FAILED -2
+#define HA_ADMIN_CORRUPT -3
+#define HA_ADMIN_INTERNAL_ERROR -4
+#define HA_ADMIN_INVALID -5
+#define HA_ADMIN_REJECT -6
+#define HA_ADMIN_TRY_ALTER -7
+#define HA_ADMIN_WRONG_CHECKSUM -8
+#define HA_ADMIN_NOT_BASE_TABLE -9
+#define HA_ADMIN_NEEDS_UPGRADE -10
+#define HA_ADMIN_NEEDS_ALTER -11
+#define HA_ADMIN_NEEDS_CHECK -12
+
+/**
+ Return values for check_if_supported_inplace_alter().
+
+ @see check_if_supported_inplace_alter() for description of
+ the individual values.
+*/
+enum enum_alter_inplace_result {
+ HA_ALTER_ERROR,
+ HA_ALTER_INPLACE_NOT_SUPPORTED,
+ HA_ALTER_INPLACE_EXCLUSIVE_LOCK,
+ HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE,
+ HA_ALTER_INPLACE_SHARED_LOCK,
+ HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE,
+ HA_ALTER_INPLACE_NO_LOCK
+};
+
+/* Bits in table_flags() to show what database can do */
+
+#define HA_NO_TRANSACTIONS (1ULL << 0) /* Doesn't support transactions */
+#define HA_PARTIAL_COLUMN_READ (1ULL << 1) /* read may not return all columns */
+#define HA_TABLE_SCAN_ON_INDEX (1ULL << 2) /* No separate data/index file */
+/*
+ The following should be set if the following is not true when scanning
+ a table with rnd_next()
+ - We will see all rows (including deleted ones)
+ - Row positions are 'table->s->db_record_offset' apart
+ If this flag is not set, filesort will do a position() call for each matched
+ row to be able to find the row later.
+*/
+#define HA_REC_NOT_IN_SEQ (1ULL << 3)
+#define HA_CAN_GEOMETRY (1ULL << 4)
+/*
+ Reading keys in random order is as fast as reading keys in sort order
+ (Used in records.cc to decide if we should use a record cache and by
+ filesort to decide if we should sort key + data or key + pointer-to-row
+*/
+#define HA_FAST_KEY_READ (1ULL << 5)
+/*
+ Set the following flag if we on delete should force all key to be read
+ and on update read all keys that changes
+*/
+#define HA_REQUIRES_KEY_COLUMNS_FOR_DELETE (1ULL << 6)
+#define HA_NULL_IN_KEY (1ULL << 7) /* One can have keys with NULL */
+#define HA_DUPLICATE_POS (1ULL << 8) /* ha_position() gives dup row */
+#define HA_NO_BLOBS (1ULL << 9) /* Doesn't support blobs */
+#define HA_CAN_INDEX_BLOBS (1ULL << 10)
+#define HA_AUTO_PART_KEY (1ULL << 11) /* auto-increment in multi-part key */
+#define HA_REQUIRE_PRIMARY_KEY (1ULL << 12) /* .. and can't create a hidden one */
+#define HA_STATS_RECORDS_IS_EXACT (1ULL << 13) /* stats.records is exact */
+/*
+ INSERT_DELAYED only works with handlers that uses MySQL internal table
+ level locks
+*/
+#define HA_CAN_INSERT_DELAYED (1ULL << 14)
+/*
+ If we get the primary key columns for free when we do an index read
+ (usually, it also implies that HA_PRIMARY_KEY_REQUIRED_FOR_POSITION
+ flag is set).
+*/
+#define HA_PRIMARY_KEY_IN_READ_INDEX (1ULL << 15)
+/*
+ If HA_PRIMARY_KEY_REQUIRED_FOR_POSITION is set, it means that to position()
+ uses a primary key given by the record argument.
+ Without primary key, we can't call position().
+ If not set, the position is returned as the current rows position
+ regardless of what argument is given.
+*/
+#define HA_PRIMARY_KEY_REQUIRED_FOR_POSITION (1ULL << 16)
+#define HA_CAN_RTREEKEYS (1ULL << 17)
+#define HA_NOT_DELETE_WITH_CACHE (1ULL << 18)
+/*
+ The following is we need to a primary key to delete (and update) a row.
+ If there is no primary key, all columns needs to be read on update and delete
+*/
+#define HA_PRIMARY_KEY_REQUIRED_FOR_DELETE (1ULL << 19)
+#define HA_NO_PREFIX_CHAR_KEYS (1ULL << 20)
+#define HA_CAN_FULLTEXT (1ULL << 21)
+#define HA_CAN_SQL_HANDLER (1ULL << 22)
+#define HA_NO_AUTO_INCREMENT (1ULL << 23)
+/* Has automatic checksums and uses the old checksum format */
+#define HA_HAS_OLD_CHECKSUM (1ULL << 24)
+/* Table data are stored in separate files (for lower_case_table_names) */
+#define HA_FILE_BASED (1ULL << 26)
+#define HA_NO_VARCHAR (1ULL << 27)
+#define HA_CAN_BIT_FIELD (1ULL << 28) /* supports bit fields */
+#define HA_NEED_READ_RANGE_BUFFER (1ULL << 29) /* for read_multi_range */
+#define HA_ANY_INDEX_MAY_BE_UNIQUE (1ULL << 30)
+#define HA_NO_COPY_ON_ALTER (1ULL << 31)
+#define HA_HAS_RECORDS (1ULL << 32) /* records() gives exact count*/
+/* Has it's own method of binlog logging */
+#define HA_HAS_OWN_BINLOGGING (1ULL << 33)
+/*
+ Engine is capable of row-format and statement-format logging,
+ respectively
+*/
+#define HA_BINLOG_ROW_CAPABLE (1ULL << 34)
+#define HA_BINLOG_STMT_CAPABLE (1ULL << 35)
+/*
+ When a multiple key conflict happens in a REPLACE command mysql
+ expects the conflicts to be reported in the ascending order of
+ key names.
+
+ For e.g.
+
+ CREATE TABLE t1 (a INT, UNIQUE (a), b INT NOT NULL, UNIQUE (b), c INT NOT
+ NULL, INDEX(c));
+
+ REPLACE INTO t1 VALUES (1,1,1),(2,2,2),(2,1,3);
+
+ MySQL expects the conflict with 'a' to be reported before the conflict with
+ 'b'.
+
+ If the underlying storage engine does not report the conflicting keys in
+ ascending order, it causes unexpected errors when the REPLACE command is
+ executed.
+
+ This flag helps the underlying SE to inform the server that the keys are not
+ ordered.
+*/
+#define HA_DUPLICATE_KEY_NOT_IN_ORDER (1ULL << 36)
+
+/*
+ Engine supports REPAIR TABLE. Used by CHECK TABLE FOR UPGRADE if an
+ incompatible table is detected. If this flag is set, CHECK TABLE FOR UPGRADE
+ will report ER_TABLE_NEEDS_UPGRADE, otherwise ER_TABLE_NEED_REBUILD.
+*/
+#define HA_CAN_REPAIR (1ULL << 37)
+
+/* Has automatic checksums and uses the new checksum format */
+#define HA_HAS_NEW_CHECKSUM (1ULL << 38)
+#define HA_CAN_VIRTUAL_COLUMNS (1ULL << 39)
+#define HA_MRR_CANT_SORT (1ULL << 40)
+#define HA_RECORD_MUST_BE_CLEAN_ON_WRITE (1ULL << 41)
+
+/*
+ Table condition pushdown must be performed regardless of
+ 'engine_condition_pushdown' setting.
+
+ This flag is aimed at storage engines that come with "special" predicates
+ that can only be evaluated inside the storage engine.
+ For example, when one does
+ select * from sphinx_table where query='{fulltext_query}'
+ then the "query=..." condition must be always pushed down into storage
+ engine.
+*/
+#define HA_MUST_USE_TABLE_CONDITION_PUSHDOWN (1ULL << 42)
+
+/**
+ The handler supports read before write removal optimization
+
+ Read before write removal may be used for storage engines which support
+ write without previous read of the row to be updated. Handler returning
+ this flag must implement start_read_removal() and end_read_removal().
+ The handler may return "fake" rows constructed from the key of the row
+ asked for. This is used to optimize UPDATE and DELETE by reducing the
+ numer of roundtrips between handler and storage engine.
+
+ Example:
+ UPDATE a=1 WHERE pk IN (<keys>)
+
+ mysql_update()
+ {
+ if (<conditions for starting read removal>)
+ start_read_removal()
+ -> handler returns true if read removal supported for this table/query
+
+ while(read_record("pk=<key>"))
+ -> handler returns fake row with column "pk" set to <key>
+
+ ha_update_row()
+ -> handler sends write "a=1" for row with "pk=<key>"
+
+ end_read_removal()
+ -> handler returns the number of rows actually written
+ }
+
+ @note This optimization in combination with batching may be used to
+ remove even more roundtrips.
+*/
+#define HA_READ_BEFORE_WRITE_REMOVAL (1LL << 43)
+
+/*
+ Engine supports extended fulltext API
+ */
+#define HA_CAN_FULLTEXT_EXT (1LL << 44)
+
+/*
+ Storage engine supports table export using the
+ FLUSH TABLE <table_list> FOR EXPORT statement
+ (meaning, after this statement one can copy table files out of the
+ datadir and later "import" (somehow) in another MariaDB instance)
+ */
+#define HA_CAN_EXPORT (1LL << 45)
+
+
+/*
+ Set of all binlog flags. Currently only contain the capabilities
+ flags.
+ */
+#define HA_BINLOG_FLAGS (HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE)
+
+/* bits in index_flags(index_number) for what you can do with index */
+#define HA_READ_NEXT 1 /* TODO really use this flag */
+#define HA_READ_PREV 2 /* supports ::index_prev */
+#define HA_READ_ORDER 4 /* index_next/prev follow sort order */
+#define HA_READ_RANGE 8 /* can find all records in a range */
+#define HA_ONLY_WHOLE_INDEX 16 /* Can't use part key searches */
+#define HA_KEYREAD_ONLY 64 /* Support HA_EXTRA_KEYREAD */
+
+/*
+ Index scan will not return records in rowid order. Not guaranteed to be
+ set for unordered (e.g. HASH) indexes.
+*/
+#define HA_KEY_SCAN_NOT_ROR 128
+#define HA_DO_INDEX_COND_PUSHDOWN 256 /* Supports Index Condition Pushdown */
+/*
+ Data is clustered on this key. This means that when you read the key
+ you also get the row data without any additional disk reads.
+*/
+#define HA_CLUSTERED_INDEX 512
+
+/*
+ bits in alter_table_flags:
+*/
+/*
+ These bits are set if different kinds of indexes can be created or dropped
+ in-place without re-creating the table using a temporary table.
+ NO_READ_WRITE indicates that the handler needs concurrent reads and writes
+ of table data to be blocked.
+ Partitioning needs both ADD and DROP to be supported by its underlying
+ handlers, due to error handling, see bug#57778.
+*/
+#define HA_INPLACE_ADD_INDEX_NO_READ_WRITE (1L << 0)
+#define HA_INPLACE_DROP_INDEX_NO_READ_WRITE (1L << 1)
+#define HA_INPLACE_ADD_UNIQUE_INDEX_NO_READ_WRITE (1L << 2)
+#define HA_INPLACE_DROP_UNIQUE_INDEX_NO_READ_WRITE (1L << 3)
+#define HA_INPLACE_ADD_PK_INDEX_NO_READ_WRITE (1L << 4)
+#define HA_INPLACE_DROP_PK_INDEX_NO_READ_WRITE (1L << 5)
+/*
+ These are set if different kinds of indexes can be created or dropped
+ in-place while still allowing concurrent reads (but not writes) of table
+ data. If a handler is capable of one or more of these, it should also set
+ the corresponding *_NO_READ_WRITE bit(s).
+*/
+#define HA_INPLACE_ADD_INDEX_NO_WRITE (1L << 6)
+#define HA_INPLACE_DROP_INDEX_NO_WRITE (1L << 7)
+#define HA_INPLACE_ADD_UNIQUE_INDEX_NO_WRITE (1L << 8)
+#define HA_INPLACE_DROP_UNIQUE_INDEX_NO_WRITE (1L << 9)
+#define HA_INPLACE_ADD_PK_INDEX_NO_WRITE (1L << 10)
+#define HA_INPLACE_DROP_PK_INDEX_NO_WRITE (1L << 11)
+/*
+ HA_PARTITION_FUNCTION_SUPPORTED indicates that the function is
+ supported at all.
+ HA_FAST_CHANGE_PARTITION means that optimised variants of the changes
+ exists but they are not necessarily done online.
+
+ HA_ONLINE_DOUBLE_WRITE means that the handler supports writing to both
+ the new partition and to the old partitions when updating through the
+ old partitioning schema while performing a change of the partitioning.
+ This means that we can support updating of the table while performing
+ the copy phase of the change. For no lock at all also a double write
+ from new to old must exist and this is not required when this flag is
+ set.
+ This is actually removed even before it was introduced the first time.
+ The new idea is that handlers will handle the lock level already in
+ store_lock for ALTER TABLE partitions.
+
+ HA_PARTITION_ONE_PHASE is a flag that can be set by handlers that take
+ care of changing the partitions online and in one phase. Thus all phases
+ needed to handle the change are implemented inside the storage engine.
+ The storage engine must also support auto-discovery since the frm file
+ is changed as part of the change and this change must be controlled by
+ the storage engine. A typical engine to support this is NDB (through
+ WL #2498).
+*/
+#define HA_PARTITION_FUNCTION_SUPPORTED (1L << 12)
+#define HA_FAST_CHANGE_PARTITION (1L << 13)
+#define HA_PARTITION_ONE_PHASE (1L << 14)
+
+/* operations for disable/enable indexes */
+#define HA_KEY_SWITCH_NONUNIQ 0
+#define HA_KEY_SWITCH_ALL 1
+#define HA_KEY_SWITCH_NONUNIQ_SAVE 2
+#define HA_KEY_SWITCH_ALL_SAVE 3
+
+/*
+ Note: the following includes binlog and closing 0.
+ so: innodb + bdb + ndb + binlog + myisam + myisammrg + archive +
+ example + csv + heap + blackhole + federated + 0
+ (yes, the sum is deliberately inaccurate)
+ TODO remove the limit, use dynarrays
+*/
+#define MAX_HA 64
+
+/*
+ Use this instead of 0 as the initial value for the slot number of
+ handlerton, so that we can distinguish uninitialized slot number
+ from slot 0.
+*/
+#define HA_SLOT_UNDEF ((uint)-1)
+
+/*
+ Parameters for open() (in register form->filestat)
+ HA_GET_INFO does an implicit HA_ABORT_IF_LOCKED
+*/
+
+#define HA_OPEN_KEYFILE 1
+#define HA_OPEN_RNDFILE 2
+#define HA_GET_INDEX 4
+#define HA_GET_INFO 8 /* do a ha_info() after open */
+#define HA_READ_ONLY 16 /* File opened as readonly */
+/* Try readonly if can't open with read and write */
+#define HA_TRY_READ_ONLY 32
+#define HA_WAIT_IF_LOCKED 64 /* Wait if locked on open */
+#define HA_ABORT_IF_LOCKED 128 /* skip if locked on open.*/
+#define HA_BLOCK_LOCK 256 /* unlock when reading some records */
+#define HA_OPEN_TEMPORARY 512
+
+ /* Some key definitions */
+#define HA_KEY_NULL_LENGTH 1
+#define HA_KEY_BLOB_LENGTH 2
+
+#define HA_LEX_CREATE_TMP_TABLE 1
+#define HA_LEX_CREATE_IF_NOT_EXISTS 2
+#define HA_LEX_CREATE_TABLE_LIKE 4
+#define HA_CREATE_TMP_ALTER 8
+#define HA_LEX_CREATE_REPLACE 16
+#define HA_MAX_REC_LENGTH 65535
+
+/* Table caching type */
+#define HA_CACHE_TBL_NONTRANSACT 0
+#define HA_CACHE_TBL_NOCACHE 1
+#define HA_CACHE_TBL_ASKTRANSACT 2
+#define HA_CACHE_TBL_TRANSACT 4
+
+/**
+ Options for the START TRANSACTION statement.
+
+ Note that READ ONLY and READ WRITE are logically mutually exclusive.
+ This is enforced by the parser and depended upon by trans_begin().
+
+ We need two flags instead of one in order to differentiate between
+ situation when no READ WRITE/ONLY clause were given and thus transaction
+ is implicitly READ WRITE and the case when READ WRITE clause was used
+ explicitly.
+*/
+
+// WITH CONSISTENT SNAPSHOT option
+static const uint MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT = 1;
+// READ ONLY option
+static const uint MYSQL_START_TRANS_OPT_READ_ONLY = 2;
+// READ WRITE option
+static const uint MYSQL_START_TRANS_OPT_READ_WRITE = 4;
+
+/* Flags for method is_fatal_error */
+#define HA_CHECK_DUP_KEY 1
+#define HA_CHECK_DUP_UNIQUE 2
+#define HA_CHECK_DUP (HA_CHECK_DUP_KEY + HA_CHECK_DUP_UNIQUE)
+
+enum legacy_db_type
+{
+ /* note these numerical values are fixed and can *not* be changed */
+ DB_TYPE_UNKNOWN=0,
+ DB_TYPE_HEAP=6,
+ DB_TYPE_MYISAM=9,
+ DB_TYPE_MRG_MYISAM=10,
+ DB_TYPE_INNODB=12,
+ DB_TYPE_NDBCLUSTER=14,
+ DB_TYPE_EXAMPLE_DB=15,
+ DB_TYPE_ARCHIVE_DB=16,
+ DB_TYPE_CSV_DB=17,
+ DB_TYPE_FEDERATED_DB=18,
+ DB_TYPE_BLACKHOLE_DB=19,
+ DB_TYPE_PARTITION_DB=20,
+ DB_TYPE_BINLOG=21,
+ DB_TYPE_PBXT=23,
+ DB_TYPE_PERFORMANCE_SCHEMA=28,
+ DB_TYPE_ARIA=42,
+ DB_TYPE_TOKUDB=43,
+ DB_TYPE_FIRST_DYNAMIC=44,
+ DB_TYPE_DEFAULT=127 // Must be last
+};
+/*
+ Better name for DB_TYPE_UNKNOWN. Should be used for engines that do not have
+ a hard-coded type value here.
+ */
+#define DB_TYPE_AUTOASSIGN DB_TYPE_UNKNOWN
+
+enum row_type { ROW_TYPE_NOT_USED=-1, ROW_TYPE_DEFAULT, ROW_TYPE_FIXED,
+ ROW_TYPE_DYNAMIC, ROW_TYPE_COMPRESSED,
+ ROW_TYPE_REDUNDANT, ROW_TYPE_COMPACT, ROW_TYPE_PAGE };
+
+/* not part of the enum, so that it shouldn't be in switch(row_type) */
+#define ROW_TYPE_MAX ((uint)ROW_TYPE_PAGE + 1)
+
+/* Specifies data storage format for individual columns */
+enum column_format_type {
+ COLUMN_FORMAT_TYPE_DEFAULT= 0, /* Not specified (use engine default) */
+ COLUMN_FORMAT_TYPE_FIXED= 1, /* FIXED format */
+ COLUMN_FORMAT_TYPE_DYNAMIC= 2 /* DYNAMIC format */
+};
+
+enum enum_binlog_func {
+ BFN_RESET_LOGS= 1,
+ BFN_RESET_SLAVE= 2,
+ BFN_BINLOG_WAIT= 3,
+ BFN_BINLOG_END= 4,
+ BFN_BINLOG_PURGE_FILE= 5
+};
+
+enum enum_binlog_command {
+ LOGCOM_CREATE_TABLE,
+ LOGCOM_ALTER_TABLE,
+ LOGCOM_RENAME_TABLE,
+ LOGCOM_DROP_TABLE,
+ LOGCOM_CREATE_DB,
+ LOGCOM_ALTER_DB,
+ LOGCOM_DROP_DB
+};
+
+/* struct to hold information about the table that should be created */
+
+/* Bits in used_fields */
+#define HA_CREATE_USED_AUTO (1L << 0)
+#define HA_CREATE_USED_RAID (1L << 1) //RAID is no longer availble
+#define HA_CREATE_USED_UNION (1L << 2)
+#define HA_CREATE_USED_INSERT_METHOD (1L << 3)
+#define HA_CREATE_USED_MIN_ROWS (1L << 4)
+#define HA_CREATE_USED_MAX_ROWS (1L << 5)
+#define HA_CREATE_USED_AVG_ROW_LENGTH (1L << 6)
+#define HA_CREATE_USED_PACK_KEYS (1L << 7)
+#define HA_CREATE_USED_CHARSET (1L << 8)
+#define HA_CREATE_USED_DEFAULT_CHARSET (1L << 9)
+#define HA_CREATE_USED_DATADIR (1L << 10)
+#define HA_CREATE_USED_INDEXDIR (1L << 11)
+#define HA_CREATE_USED_ENGINE (1L << 12)
+#define HA_CREATE_USED_CHECKSUM (1L << 13)
+#define HA_CREATE_USED_DELAY_KEY_WRITE (1L << 14)
+#define HA_CREATE_USED_ROW_FORMAT (1L << 15)
+#define HA_CREATE_USED_COMMENT (1L << 16)
+#define HA_CREATE_USED_PASSWORD (1L << 17)
+#define HA_CREATE_USED_CONNECTION (1L << 18)
+#define HA_CREATE_USED_KEY_BLOCK_SIZE (1L << 19)
+/* The following two are used by Maria engine: */
+#define HA_CREATE_USED_TRANSACTIONAL (1L << 20)
+#define HA_CREATE_USED_PAGE_CHECKSUM (1L << 21)
+/** This is set whenever STATS_PERSISTENT=0|1|default has been
+specified in CREATE/ALTER TABLE. See also HA_OPTION_STATS_PERSISTENT in
+include/my_base.h. It is possible to distinguish whether
+STATS_PERSISTENT=default has been specified or no STATS_PERSISTENT= is
+given at all. */
+#define HA_CREATE_USED_STATS_PERSISTENT (1L << 22)
+/**
+ This is set whenever STATS_AUTO_RECALC=0|1|default has been
+ specified in CREATE/ALTER TABLE. See enum_stats_auto_recalc.
+ It is possible to distinguish whether STATS_AUTO_RECALC=default
+ has been specified or no STATS_AUTO_RECALC= is given at all.
+*/
+#define HA_CREATE_USED_STATS_AUTO_RECALC (1L << 23)
+/**
+ This is set whenever STATS_SAMPLE_PAGES=N|default has been
+ specified in CREATE/ALTER TABLE. It is possible to distinguish whether
+ STATS_SAMPLE_PAGES=default has been specified or no STATS_SAMPLE_PAGES= is
+ given at all.
+*/
+#define HA_CREATE_USED_STATS_SAMPLE_PAGES (1L << 24)
+
+
+/*
+ This is master database for most of system tables. However there
+ can be other databases which can hold system tables. Respective
+ storage engines define their own system database names.
+*/
+extern const char *mysqld_system_database;
+
+/*
+ Structure to hold list of system_database.system_table.
+ This is used at both mysqld and storage engine layer.
+*/
+struct st_system_tablename
+{
+ const char *db;
+ const char *tablename;
+};
+
+
+typedef ulonglong my_xid; // this line is the same as in log_event.h
+#define MYSQL_XID_PREFIX "MySQLXid"
+#define MYSQL_XID_PREFIX_LEN 8 // must be a multiple of 8
+#define MYSQL_XID_OFFSET (MYSQL_XID_PREFIX_LEN+sizeof(server_id))
+#define MYSQL_XID_GTRID_LEN (MYSQL_XID_OFFSET+sizeof(my_xid))
+
+#define XIDDATASIZE MYSQL_XIDDATASIZE
+#define MAXGTRIDSIZE 64
+#define MAXBQUALSIZE 64
+
+#define COMPATIBLE_DATA_YES 0
+#define COMPATIBLE_DATA_NO 1
+
+/**
+ struct xid_t is binary compatible with the XID structure as
+ in the X/Open CAE Specification, Distributed Transaction Processing:
+ The XA Specification, X/Open Company Ltd., 1991.
+ http://www.opengroup.org/bookstore/catalog/c193.htm
+
+ @see MYSQL_XID in mysql/plugin.h
+*/
+struct xid_t {
+ long formatID;
+ long gtrid_length;
+ long bqual_length;
+ char data[XIDDATASIZE]; // not \0-terminated !
+
+ xid_t() {} /* Remove gcc warning */
+ bool eq(struct xid_t *xid)
+ { return eq(xid->gtrid_length, xid->bqual_length, xid->data); }
+ bool eq(long g, long b, const char *d)
+ { return g == gtrid_length && b == bqual_length && !memcmp(d, data, g+b); }
+ void set(struct xid_t *xid)
+ { memcpy(this, xid, xid->length()); }
+ void set(long f, const char *g, long gl, const char *b, long bl)
+ {
+ formatID= f;
+ memcpy(data, g, gtrid_length= gl);
+ memcpy(data+gl, b, bqual_length= bl);
+ }
+ void set(ulonglong xid)
+ {
+ my_xid tmp;
+ formatID= 1;
+ set(MYSQL_XID_PREFIX_LEN, 0, MYSQL_XID_PREFIX);
+ memcpy(data+MYSQL_XID_PREFIX_LEN, &server_id, sizeof(server_id));
+ tmp= xid;
+ memcpy(data+MYSQL_XID_OFFSET, &tmp, sizeof(tmp));
+ gtrid_length=MYSQL_XID_GTRID_LEN;
+ }
+ void set(long g, long b, const char *d)
+ {
+ formatID= 1;
+ gtrid_length= g;
+ bqual_length= b;
+ memcpy(data, d, g+b);
+ }
+ bool is_null() { return formatID == -1; }
+ void null() { formatID= -1; }
+ my_xid quick_get_my_xid()
+ {
+ my_xid tmp;
+ memcpy(&tmp, data+MYSQL_XID_OFFSET, sizeof(tmp));
+ return tmp;
+ }
+ my_xid get_my_xid()
+ {
+ return gtrid_length == MYSQL_XID_GTRID_LEN && bqual_length == 0 &&
+ !memcmp(data, MYSQL_XID_PREFIX, MYSQL_XID_PREFIX_LEN) ?
+ quick_get_my_xid() : 0;
+ }
+ uint length()
+ {
+ return sizeof(formatID)+sizeof(gtrid_length)+sizeof(bqual_length)+
+ gtrid_length+bqual_length;
+ }
+ uchar *key()
+ {
+ return (uchar *)&gtrid_length;
+ }
+ uint key_length()
+ {
+ return sizeof(gtrid_length)+sizeof(bqual_length)+gtrid_length+bqual_length;
+ }
+};
+typedef struct xid_t XID;
+
+/* for recover() handlerton call */
+#define MIN_XID_LIST_SIZE 128
+#define MAX_XID_LIST_SIZE (1024*128)
+
+/*
+ These structures are used to pass information from a set of SQL commands
+ on add/drop/change tablespace definitions to the proper hton.
+*/
+#define UNDEF_NODEGROUP 65535
+enum ts_command_type
+{
+ TS_CMD_NOT_DEFINED = -1,
+ CREATE_TABLESPACE = 0,
+ ALTER_TABLESPACE = 1,
+ CREATE_LOGFILE_GROUP = 2,
+ ALTER_LOGFILE_GROUP = 3,
+ DROP_TABLESPACE = 4,
+ DROP_LOGFILE_GROUP = 5,
+ CHANGE_FILE_TABLESPACE = 6,
+ ALTER_ACCESS_MODE_TABLESPACE = 7
+};
+
+enum ts_alter_tablespace_type
+{
+ TS_ALTER_TABLESPACE_TYPE_NOT_DEFINED = -1,
+ ALTER_TABLESPACE_ADD_FILE = 1,
+ ALTER_TABLESPACE_DROP_FILE = 2
+};
+
+enum tablespace_access_mode
+{
+ TS_NOT_DEFINED= -1,
+ TS_READ_ONLY = 0,
+ TS_READ_WRITE = 1,
+ TS_NOT_ACCESSIBLE = 2
+};
+
+struct handlerton;
+class st_alter_tablespace : public Sql_alloc
+{
+ public:
+ const char *tablespace_name;
+ const char *logfile_group_name;
+ enum ts_command_type ts_cmd_type;
+ enum ts_alter_tablespace_type ts_alter_tablespace_type;
+ const char *data_file_name;
+ const char *undo_file_name;
+ const char *redo_file_name;
+ ulonglong extent_size;
+ ulonglong undo_buffer_size;
+ ulonglong redo_buffer_size;
+ ulonglong initial_size;
+ ulonglong autoextend_size;
+ ulonglong max_size;
+ uint nodegroup_id;
+ handlerton *storage_engine;
+ bool wait_until_completed;
+ const char *ts_comment;
+ enum tablespace_access_mode ts_access_mode;
+ st_alter_tablespace()
+ {
+ tablespace_name= NULL;
+ logfile_group_name= "DEFAULT_LG"; //Default log file group
+ ts_cmd_type= TS_CMD_NOT_DEFINED;
+ data_file_name= NULL;
+ undo_file_name= NULL;
+ redo_file_name= NULL;
+ extent_size= 1024*1024; //Default 1 MByte
+ undo_buffer_size= 8*1024*1024; //Default 8 MByte
+ redo_buffer_size= 8*1024*1024; //Default 8 MByte
+ initial_size= 128*1024*1024; //Default 128 MByte
+ autoextend_size= 0; //No autoextension as default
+ max_size= 0; //Max size == initial size => no extension
+ storage_engine= NULL;
+ nodegroup_id= UNDEF_NODEGROUP;
+ wait_until_completed= TRUE;
+ ts_comment= NULL;
+ ts_access_mode= TS_NOT_DEFINED;
+ }
+};
+
+/* The handler for a table type. Will be included in the TABLE structure */
+
+struct TABLE;
+
+/*
+ Make sure that the order of schema_tables and enum_schema_tables are the same.
+*/
+enum enum_schema_tables
+{
+ SCH_ALL_PLUGINS,
+ SCH_APPLICABLE_ROLES,
+ SCH_CHARSETS,
+ SCH_CLIENT_STATS,
+ SCH_COLLATIONS,
+ SCH_COLLATION_CHARACTER_SET_APPLICABILITY,
+ SCH_COLUMNS,
+ SCH_COLUMN_PRIVILEGES,
+ SCH_ENABLED_ROLES,
+ SCH_ENGINES,
+ SCH_EVENTS,
+ SCH_EXPLAIN,
+ SCH_FILES,
+ SCH_GLOBAL_STATUS,
+ SCH_GLOBAL_VARIABLES,
+ SCH_INDEX_STATS,
+ SCH_KEY_CACHES,
+ SCH_KEY_COLUMN_USAGE,
+ SCH_OPEN_TABLES,
+ SCH_PARAMETERS,
+ SCH_PARTITIONS,
+ SCH_PLUGINS,
+ SCH_PROCESSLIST,
+ SCH_PROFILES,
+ SCH_REFERENTIAL_CONSTRAINTS,
+ SCH_PROCEDURES,
+ SCH_SCHEMATA,
+ SCH_SCHEMA_PRIVILEGES,
+ SCH_SESSION_STATUS,
+ SCH_SESSION_VARIABLES,
+ SCH_STATISTICS,
+ SCH_STATUS,
+ SCH_TABLES,
+ SCH_TABLESPACES,
+ SCH_TABLE_CONSTRAINTS,
+ SCH_TABLE_NAMES,
+ SCH_TABLE_PRIVILEGES,
+ SCH_TABLE_STATS,
+ SCH_TRIGGERS,
+ SCH_USER_PRIVILEGES,
+ SCH_USER_STATS,
+ SCH_VARIABLES,
+ SCH_VIEWS
+};
+
+struct TABLE_SHARE;
+struct HA_CREATE_INFO;
+struct st_foreign_key_info;
+typedef struct st_foreign_key_info FOREIGN_KEY_INFO;
+typedef bool (stat_print_fn)(THD *thd, const char *type, uint type_len,
+ const char *file, uint file_len,
+ const char *status, uint status_len);
+enum ha_stat_type { HA_ENGINE_STATUS, HA_ENGINE_LOGS, HA_ENGINE_MUTEX };
+extern st_plugin_int *hton2plugin[MAX_HA];
+
+/* Transaction log maintains type definitions */
+enum log_status
+{
+ HA_LOG_STATUS_FREE= 0, /* log is free and can be deleted */
+ HA_LOG_STATUS_INUSE= 1, /* log can't be deleted because it is in use */
+ HA_LOG_STATUS_NOSUCHLOG= 2 /* no such log (can't be returned by
+ the log iterator status) */
+};
+/*
+ Function for signaling that the log file changed its state from
+ LOG_STATUS_INUSE to LOG_STATUS_FREE
+
+ Now it do nothing, will be implemented as part of new transaction
+ log management for engines.
+ TODO: implement the function.
+*/
+void signal_log_not_needed(struct handlerton, char *log_file);
+/*
+ Data of transaction log iterator.
+*/
+struct handler_log_file_data {
+ LEX_STRING filename;
+ enum log_status status;
+};
+
+/*
+ Definitions for engine-specific table/field/index options in the CREATE TABLE.
+
+ Options are declared with HA_*OPTION_* macros (HA_TOPTION_NUMBER,
+ HA_FOPTION_ENUM, HA_IOPTION_STRING, etc).
+
+ Every macros takes the option name, and the name of the underlying field of
+ the appropriate C structure. The "appropriate C structure" is
+ ha_table_option_struct for table level options,
+ ha_field_option_struct for field level options,
+ ha_index_option_struct for key level options. The engine either
+ defines a structure of this name, or uses #define's to map
+ these "appropriate" names to the actual structure type name.
+
+ ULL options use a ulonglong as the backing store.
+ HA_*OPTION_NUMBER() takes the option name, the structure field name,
+ the default value for the option, min, max, and blk_siz values.
+
+ STRING options use a char* as a backing store.
+ HA_*OPTION_STRING takes the option name and the structure field name.
+ The default value will be 0.
+
+ ENUM options use a uint as a backing store (not enum!!!).
+ HA_*OPTION_ENUM takes the option name, the structure field name,
+ the default value for the option as a number, and a string with the
+ permitted values for this enum - one string with comma separated values,
+ for example: "gzip,bzip2,lzma"
+
+ BOOL options use a bool as a backing store.
+ HA_*OPTION_BOOL takes the option name, the structure field name,
+ and the default value for the option.
+ From the SQL, BOOL options accept YES/NO, ON/OFF, and 1/0.
+
+ The name of the option is limited to 255 bytes,
+ the value (for string options) - to the 32767 bytes.
+
+ See ha_example.cc for an example.
+*/
+
+struct ha_table_option_struct;
+struct ha_field_option_struct;
+struct ha_index_option_struct;
+
+enum ha_option_type { HA_OPTION_TYPE_ULL, /* unsigned long long */
+ HA_OPTION_TYPE_STRING, /* char * */
+ HA_OPTION_TYPE_ENUM, /* uint */
+ HA_OPTION_TYPE_BOOL, /* bool */
+ HA_OPTION_TYPE_SYSVAR};/* type of the sysval */
+
+#define HA_xOPTION_NUMBER(name, struc, field, def, min, max, blk_siz) \
+ { HA_OPTION_TYPE_ULL, name, sizeof(name)-1, \
+ offsetof(struc, field), def, min, max, blk_siz, 0, 0 }
+#define HA_xOPTION_STRING(name, struc, field) \
+ { HA_OPTION_TYPE_STRING, name, sizeof(name)-1, \
+ offsetof(struc, field), 0, 0, 0, 0, 0, 0}
+#define HA_xOPTION_ENUM(name, struc, field, values, def) \
+ { HA_OPTION_TYPE_ENUM, name, sizeof(name)-1, \
+ offsetof(struc, field), def, 0, \
+ sizeof(values)-1, 0, values, 0 }
+#define HA_xOPTION_BOOL(name, struc, field, def) \
+ { HA_OPTION_TYPE_BOOL, name, sizeof(name)-1, \
+ offsetof(struc, field), def, 0, 1, 0, 0, 0 }
+#define HA_xOPTION_SYSVAR(name, struc, field, sysvar) \
+ { HA_OPTION_TYPE_SYSVAR, name, sizeof(name)-1, \
+ offsetof(struc, field), 0, 0, 0, 0, 0, MYSQL_SYSVAR(sysvar) }
+#define HA_xOPTION_END { HA_OPTION_TYPE_ULL, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
+
+#define HA_TOPTION_NUMBER(name, field, def, min, max, blk_siz) \
+ HA_xOPTION_NUMBER(name, ha_table_option_struct, field, def, min, max, blk_siz)
+#define HA_TOPTION_STRING(name, field) \
+ HA_xOPTION_STRING(name, ha_table_option_struct, field)
+#define HA_TOPTION_ENUM(name, field, values, def) \
+ HA_xOPTION_ENUM(name, ha_table_option_struct, field, values, def)
+#define HA_TOPTION_BOOL(name, field, def) \
+ HA_xOPTION_BOOL(name, ha_table_option_struct, field, def)
+#define HA_TOPTION_SYSVAR(name, field, sysvar) \
+ HA_xOPTION_SYSVAR(name, ha_table_option_struct, field, sysvar)
+#define HA_TOPTION_END HA_xOPTION_END
+
+#define HA_FOPTION_NUMBER(name, field, def, min, max, blk_siz) \
+ HA_xOPTION_NUMBER(name, ha_field_option_struct, field, def, min, max, blk_siz)
+#define HA_FOPTION_STRING(name, field) \
+ HA_xOPTION_STRING(name, ha_field_option_struct, field)
+#define HA_FOPTION_ENUM(name, field, values, def) \
+ HA_xOPTION_ENUM(name, ha_field_option_struct, field, values, def)
+#define HA_FOPTION_BOOL(name, field, def) \
+ HA_xOPTION_BOOL(name, ha_field_option_struct, field, def)
+#define HA_FOPTION_SYSVAR(name, field, sysvar) \
+ HA_xOPTION_SYSVAR(name, ha_field_option_struct, field, sysvar)
+#define HA_FOPTION_END HA_xOPTION_END
+
+#define HA_IOPTION_NUMBER(name, field, def, min, max, blk_siz) \
+ HA_xOPTION_NUMBER(name, ha_index_option_struct, field, def, min, max, blk_siz)
+#define HA_IOPTION_STRING(name, field) \
+ HA_xOPTION_STRING(name, ha_index_option_struct, field)
+#define HA_IOPTION_ENUM(name, field, values, def) \
+ HA_xOPTION_ENUM(name, ha_index_option_struct, field, values, def)
+#define HA_IOPTION_BOOL(name, field, def) \
+ HA_xOPTION_BOOL(name, ha_index_option_struct, field, def)
+#define HA_IOPTION_SYSVAR(name, field, sysvar) \
+ HA_xOPTION_SYSVAR(name, ha_index_option_struct, field, sysvar)
+#define HA_IOPTION_END HA_xOPTION_END
+
+typedef struct st_ha_create_table_option {
+ enum ha_option_type type;
+ const char *name;
+ size_t name_length;
+ ptrdiff_t offset;
+ ulonglong def_value;
+ ulonglong min_value, max_value, block_size;
+ const char *values;
+ struct st_mysql_sys_var *var;
+} ha_create_table_option;
+
+enum handler_iterator_type
+{
+ /* request of transaction log iterator */
+ HA_TRANSACTLOG_ITERATOR= 1
+};
+enum handler_create_iterator_result
+{
+ HA_ITERATOR_OK, /* iterator created */
+ HA_ITERATOR_UNSUPPORTED, /* such type of iterator is not supported */
+ HA_ITERATOR_ERROR /* error during iterator creation */
+};
+
+/*
+ Iterator structure. Can be used by handler/handlerton for different purposes.
+
+ Iterator should be created in the way to point "before" the first object
+ it iterate, so next() call move it to the first object or return !=0 if
+ there is nothing to iterate through.
+*/
+struct handler_iterator {
+ /*
+ Moves iterator to next record and return 0 or return !=0
+ if there is no records.
+ iterator_object will be filled by this function if next() returns 0.
+ Content of the iterator_object depend on iterator type.
+ */
+ int (*next)(struct handler_iterator *, void *iterator_object);
+ /*
+ Free resources allocated by iterator, after this call iterator
+ is not usable.
+ */
+ void (*destroy)(struct handler_iterator *);
+ /*
+ Pointer to buffer for the iterator to use.
+ Should be allocated by function which created the iterator and
+ destroied by freed by above "destroy" call
+ */
+ void *buffer;
+};
+
+class handler;
+/*
+ handlerton is a singleton structure - one instance per storage engine -
+ to provide access to storage engine functionality that works on the
+ "global" level (unlike handler class that works on a per-table basis)
+
+ usually handlerton instance is defined statically in ha_xxx.cc as
+
+ static handlerton { ... } xxx_hton;
+
+ savepoint_*, prepare, recover, and *_by_xid pointers can be 0.
+*/
+struct handlerton
+{
+ /*
+ Historical marker for if the engine is available of not
+ */
+ SHOW_COMP_OPTION state;
+
+ /*
+ Historical number used for frm file to determine the correct
+ storage engine. This is going away and new engines will just use
+ "name" for this.
+ */
+ enum legacy_db_type db_type;
+ /*
+ each storage engine has it's own memory area (actually a pointer)
+ in the thd, for storing per-connection information.
+ It is accessed as
+
+ thd->ha_data[xxx_hton.slot]
+
+ slot number is initialized by MySQL after xxx_init() is called.
+ */
+ uint slot;
+ /*
+ to store per-savepoint data storage engine is provided with an area
+ of a requested size (0 is ok here).
+ savepoint_offset must be initialized statically to the size of
+ the needed memory to store per-savepoint information.
+ After xxx_init it is changed to be an offset to savepoint storage
+ area and need not be used by storage engine.
+ see binlog_hton and binlog_savepoint_set/rollback for an example.
+ */
+ uint savepoint_offset;
+ /*
+ handlerton methods:
+
+ close_connection is only called if
+ thd->ha_data[xxx_hton.slot] is non-zero, so even if you don't need
+ this storage area - set it to something, so that MySQL would know
+ this storage engine was accessed in this connection
+ */
+ int (*close_connection)(handlerton *hton, THD *thd);
+ /*
+ Tell handler that query has been killed.
+ */
+ void (*kill_query)(handlerton *hton, THD *thd, enum thd_kill_levels level);
+ /*
+ sv points to an uninitialized storage area of requested size
+ (see savepoint_offset description)
+ */
+ int (*savepoint_set)(handlerton *hton, THD *thd, void *sv);
+ /*
+ sv points to a storage area, that was earlier passed
+ to the savepoint_set call
+ */
+ int (*savepoint_rollback)(handlerton *hton, THD *thd, void *sv);
+ /**
+ Check if storage engine allows to release metadata locks which were
+ acquired after the savepoint if rollback to savepoint is done.
+ @return true - If it is safe to release MDL locks.
+ false - If it is not.
+ */
+ bool (*savepoint_rollback_can_release_mdl)(handlerton *hton, THD *thd);
+ int (*savepoint_release)(handlerton *hton, THD *thd, void *sv);
+ /*
+ 'all' is true if it's a real commit, that makes persistent changes
+ 'all' is false if it's not in fact a commit but an end of the
+ statement that is part of the transaction.
+ NOTE 'all' is also false in auto-commit mode where 'end of statement'
+ and 'real commit' mean the same event.
+ */
+ int (*commit)(handlerton *hton, THD *thd, bool all);
+ /*
+ The commit_ordered() method is called prior to the commit() method, after
+ the transaction manager has decided to commit (not rollback) the
+ transaction. Unlike commit(), commit_ordered() is called only when the
+ full transaction is committed, not for each commit of statement
+ transaction in a multi-statement transaction.
+
+ Not that like prepare(), commit_ordered() is only called when 2-phase
+ commit takes place. Ie. when no binary log and only a single engine
+ participates in a transaction, one commit() is called, no
+ commit_ordered(). So engines must be prepared for this.
+
+ The calls to commit_ordered() in multiple parallel transactions is
+ guaranteed to happen in the same order in every participating
+ handler. This can be used to ensure the same commit order among multiple
+ handlers (eg. in table handler and binlog). So if transaction T1 calls
+ into commit_ordered() of handler A before T2, then T1 will also call
+ commit_ordered() of handler B before T2.
+
+ Engines that implement this method should during this call make the
+ transaction visible to other transactions, thereby making the order of
+ transaction commits be defined by the order of commit_ordered() calls.
+
+ The intention is that commit_ordered() should do the minimal amount of
+ work that needs to happen in consistent commit order among handlers. To
+ preserve ordering, calls need to be serialised on a global mutex, so
+ doing any time-consuming or blocking operations in commit_ordered() will
+ limit scalability.
+
+ Handlers can rely on commit_ordered() calls to be serialised (no two
+ calls can run in parallel, so no extra locking on the handler part is
+ required to ensure this).
+
+ Note that commit_ordered() can be called from a different thread than the
+ one handling the transaction! So it can not do anything that depends on
+ thread local storage, in particular it can not call my_error() and
+ friends (instead it can store the error code and delay the call of
+ my_error() to the commit() method).
+
+ Similarly, since commit_ordered() returns void, any return error code
+ must be saved and returned from the commit() method instead.
+
+ The commit_ordered method is optional, and can be left unset if not
+ needed in a particular handler (then there will be no ordering guarantees
+ wrt. other engines and binary log).
+ */
+ void (*commit_ordered)(handlerton *hton, THD *thd, bool all);
+ int (*rollback)(handlerton *hton, THD *thd, bool all);
+ int (*prepare)(handlerton *hton, THD *thd, bool all);
+ /*
+ The prepare_ordered method is optional. If set, it will be called after
+ successful prepare() in all handlers participating in 2-phase
+ commit. Like commit_ordered(), it is called only when the full
+ transaction is committed, not for each commit of statement transaction.
+
+ The calls to prepare_ordered() among multiple parallel transactions are
+ ordered consistently with calls to commit_ordered(). This means that
+ calls to prepare_ordered() effectively define the commit order, and that
+ each handler will see the same sequence of transactions calling into
+ prepare_ordered() and commit_ordered().
+
+ Thus, prepare_ordered() can be used to define commit order for handlers
+ that need to do this in the prepare step (like binlog). It can also be
+ used to release transaction's locks early in an order consistent with the
+ order transactions will be eventually committed.
+
+ Like commit_ordered(), prepare_ordered() calls are serialised to maintain
+ ordering, so the intention is that they should execute fast, with only
+ the minimal amount of work needed to define commit order. Handlers can
+ rely on this serialisation, and do not need to do any extra locking to
+ avoid two prepare_ordered() calls running in parallel.
+
+ Like commit_ordered(), prepare_ordered() is not guaranteed to be called
+ in the context of the thread handling the rest of the transaction. So it
+ cannot invoke code that relies on thread local storage, in particular it
+ cannot call my_error().
+
+ prepare_ordered() cannot cause a rollback by returning an error, all
+ possible errors must be handled in prepare() (the prepare_ordered()
+ method returns void). In case of some fatal error, a record of the error
+ must be made internally by the engine and returned from commit() later.
+
+ Note that for user-level XA SQL commands, no consistent ordering among
+ prepare_ordered() and commit_ordered() is guaranteed (as that would
+ require blocking all other commits for an indefinite time).
+
+ When 2-phase commit is not used (eg. only one engine (and no binlog) in
+ transaction), neither prepare() nor prepare_ordered() is called.
+ */
+ void (*prepare_ordered)(handlerton *hton, THD *thd, bool all);
+ int (*recover)(handlerton *hton, XID *xid_list, uint len);
+ int (*commit_by_xid)(handlerton *hton, XID *xid);
+ int (*rollback_by_xid)(handlerton *hton, XID *xid);
+ /*
+ The commit_checkpoint_request() handlerton method is used to checkpoint
+ the XA recovery process for storage engines that support two-phase
+ commit.
+
+ The method is optional - an engine that does not implemented is expected
+ to work the traditional way, where every commit() durably flushes the
+ transaction to disk in the engine before completion, so XA recovery will
+ no longer be needed for that transaction.
+
+ An engine that does implement commit_checkpoint_request() is also
+ expected to implement commit_ordered(), so that ordering of commits is
+ consistent between 2pc participants. Such engine is no longer required to
+ durably flush to disk transactions in commit(), provided that the
+ transaction has been successfully prepare()d and commit_ordered(); thus
+ potentionally saving one fsync() call. (Engine must still durably flush
+ to disk in commit() when no prepare()/commit_ordered() steps took place,
+ at least if durable commits are wanted; this happens eg. if binlog is
+ disabled).
+
+ The TC will periodically (eg. once per binlog rotation) call
+ commit_checkpoint_request(). When this happens, the engine must arrange
+ for all transaction that have completed commit_ordered() to be durably
+ flushed to disk (this does not include transactions that might be in the
+ middle of executing commit_ordered()). When such flush has completed, the
+ engine must call commit_checkpoint_notify_ha(), passing back the opaque
+ "cookie".
+
+ The flush and call of commit_checkpoint_notify_ha() need not happen
+ immediately - it can be scheduled and performed asynchroneously (ie. as
+ part of next prepare(), or sync every second, or whatever), but should
+ not be postponed indefinitely. It is however also permissible to do it
+ immediately, before returning from commit_checkpoint_request().
+
+ When commit_checkpoint_notify_ha() is called, the TC will know that the
+ transactions are durably committed, and thus no longer require XA
+ recovery. It uses that to reduce the work needed for any subsequent XA
+ recovery process.
+ */
+ void (*commit_checkpoint_request)(handlerton *hton, void *cookie);
+ /*
+ "Disable or enable checkpointing internal to the storage engine. This is
+ used for FLUSH TABLES WITH READ LOCK AND DISABLE CHECKPOINT to ensure that
+ the engine will never start any recovery from a time between
+ FLUSH TABLES ... ; UNLOCK TABLES.
+
+ While checkpointing is disabled, the engine should pause any background
+ write activity (such as tablespace checkpointing) that require consistency
+ between different files (such as transaction log and tablespace files) for
+ crash recovery to succeed. The idea is to use this to make safe
+ multi-volume LVM snapshot backups.
+ */
+ int (*checkpoint_state)(handlerton *hton, bool disabled);
+ void *(*create_cursor_read_view)(handlerton *hton, THD *thd);
+ void (*set_cursor_read_view)(handlerton *hton, THD *thd, void *read_view);
+ void (*close_cursor_read_view)(handlerton *hton, THD *thd, void *read_view);
+ handler *(*create)(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root);
+ void (*drop_database)(handlerton *hton, char* path);
+ int (*panic)(handlerton *hton, enum ha_panic_function flag);
+ int (*start_consistent_snapshot)(handlerton *hton, THD *thd);
+ bool (*flush_logs)(handlerton *hton);
+ bool (*show_status)(handlerton *hton, THD *thd, stat_print_fn *print, enum ha_stat_type stat);
+ uint (*partition_flags)();
+ uint (*alter_table_flags)(uint flags);
+ int (*alter_tablespace)(handlerton *hton, THD *thd, st_alter_tablespace *ts_info);
+ int (*fill_is_table)(handlerton *hton, THD *thd, TABLE_LIST *tables,
+ class Item *cond,
+ enum enum_schema_tables);
+ uint32 flags; /* global handler flags */
+ /*
+ Those handlerton functions below are properly initialized at handler
+ init.
+ */
+ int (*binlog_func)(handlerton *hton, THD *thd, enum_binlog_func fn, void *arg);
+ void (*binlog_log_query)(handlerton *hton, THD *thd,
+ enum_binlog_command binlog_command,
+ const char *query, uint query_length,
+ const char *db, const char *table_name);
+ int (*release_temporary_latches)(handlerton *hton, THD *thd);
+
+ /*
+ Get log status.
+ If log_status is null then the handler do not support transaction
+ log information (i.e. log iterator can't be created).
+ (see example of implementation in handler.cc, TRANS_LOG_MGM_EXAMPLE_CODE)
+
+ */
+ enum log_status (*get_log_status)(handlerton *hton, char *log);
+
+ /*
+ Iterators creator.
+ Presence of the pointer should be checked before using
+ */
+ enum handler_create_iterator_result
+ (*create_iterator)(handlerton *hton, enum handler_iterator_type type,
+ struct handler_iterator *fill_this_in);
+ /*
+ Optional clauses in the CREATE/ALTER TABLE
+ */
+ ha_create_table_option *table_options; // table level options
+ ha_create_table_option *field_options; // these are specified per field
+ ha_create_table_option *index_options; // these are specified per index
+
+ /**
+ The list of extensions of files created for a single table in the
+ database directory (datadir/db_name/).
+
+ Used by open_table_error(), by the default rename_table and delete_table
+ handler methods, and by the default discovery implementation.
+
+ For engines that have more than one file name extentions (separate
+ metadata, index, and/or data files), the order of elements is relevant.
+ First element of engine file name extentions array should be metadata
+ file extention. This is implied by the open_table_error()
+ and the default discovery implementation.
+
+ Second element - data file extention. This is implied
+ assumed by REPAIR TABLE ... USE_FRM implementation.
+ */
+ const char **tablefile_extensions; // by default - empty list
+
+ /*********************************************************************
+ Table discovery API.
+ It allows the server to "discover" tables that exist in the storage
+ engine, without user issuing an explicit CREATE TABLE statement.
+ **********************************************************************/
+
+ /*
+ This method is required for any engine that supports automatic table
+ discovery, there is no default implementation.
+
+ Given a TABLE_SHARE discover_table() fills it in with a correct table
+ structure using one of the TABLE_SHARE::init_from_* methods.
+
+ Returns HA_ERR_NO_SUCH_TABLE if the table did not exist in the engine,
+ zero if the table was discovered successfully, or any other
+ HA_ERR_* error code as appropriate if the table existed, but the
+ discovery failed.
+ */
+ int (*discover_table)(handlerton *hton, THD* thd, TABLE_SHARE *share);
+
+ /*
+ The discover_table_names method tells the server
+ about all tables in the specified database that the engine
+ knows about. Tables (or file names of tables) are added to
+ the provided discovered_list collector object using
+ add_table() or add_file() methods.
+ */
+ class discovered_list
+ {
+ public:
+ virtual bool add_table(const char *tname, size_t tlen) = 0;
+ virtual bool add_file(const char *fname) = 0;
+ protected: virtual ~discovered_list() {}
+ };
+
+ /*
+ By default (if not implemented by the engine, but the discovery_table() is
+ implemented) it will perform a file-based discovery:
+
+ - if tablefile_extensions[0] is not null, this will discovers all tables
+ with the tablefile_extensions[0] extension.
+
+ Returns 0 on success and 1 on error.
+ */
+ int (*discover_table_names)(handlerton *hton, LEX_STRING *db, MY_DIR *dir,
+ discovered_list *result);
+
+ /*
+ This is a method that allows to server to check if a table exists without
+ an overhead of the complete discovery.
+
+ By default (if not implemented by the engine, but the discovery_table() is
+ implemented) it will try to perform a file-based discovery:
+
+ - if tablefile_extensions[0] is not null this will look for a file name
+ with the tablefile_extensions[0] extension.
+
+ - if tablefile_extensions[0] is null, this will resort to discover_table().
+
+ Note that resorting to discover_table() is slow and the engine
+ should probably implement its own discover_table_existence() method,
+ if its tablefile_extensions[0] is null.
+
+ Returns 1 if the table exists and 0 if it does not.
+ */
+ int (*discover_table_existence)(handlerton *hton, const char *db,
+ const char *table_name);
+
+ /*
+ This is the assisted table discovery method. Unlike the fully
+ automatic discovery as above, here a user is expected to issue an
+ explicit CREATE TABLE with the appropriate table attributes to
+ "assist" the discovery of a table. But this "discovering" CREATE TABLE
+ statement will not specify the table structure - the engine discovers
+ it using this method. For example, FederatedX uses it in
+
+ CREATE TABLE t1 ENGINE=FEDERATED CONNECTION="mysql://foo/bar/t1";
+
+ Given a TABLE_SHARE discover_table_structure() fills it in with a correct
+ table structure using one of the TABLE_SHARE::init_from_* methods.
+
+ Assisted discovery works independently from the automatic discover.
+ An engine is allowed to support only assisted discovery and not
+ support automatic one. Or vice versa.
+ */
+ int (*discover_table_structure)(handlerton *hton, THD* thd,
+ TABLE_SHARE *share, HA_CREATE_INFO *info);
+};
+
+
+static inline LEX_STRING *hton_name(const handlerton *hton)
+{
+ return &(hton2plugin[hton->slot]->name);
+}
+
+static inline handlerton *plugin_hton(plugin_ref plugin)
+{
+ return plugin_data(plugin, handlerton *);
+}
+
+static inline sys_var *find_hton_sysvar(handlerton *hton, st_mysql_sys_var *var)
+{
+ return find_plugin_sysvar(hton2plugin[hton->slot], var);
+}
+
+
+/* Possible flags of a handlerton (there can be 32 of them) */
+#define HTON_NO_FLAGS 0
+#define HTON_CLOSE_CURSORS_AT_COMMIT (1 << 0)
+#define HTON_ALTER_NOT_SUPPORTED (1 << 1) //Engine does not support alter
+#define HTON_CAN_RECREATE (1 << 2) //Delete all is used for truncate
+#define HTON_HIDDEN (1 << 3) //Engine does not appear in lists
+#define HTON_FLUSH_AFTER_RENAME (1 << 4)
+#define HTON_NOT_USER_SELECTABLE (1 << 5)
+#define HTON_TEMPORARY_NOT_SUPPORTED (1 << 6) //Having temporary tables not supported
+#define HTON_SUPPORT_LOG_TABLES (1 << 7) //Engine supports log tables
+#define HTON_NO_PARTITION (1 << 8) //Not partition of these tables
+
+/*
+ This flag should be set when deciding that the engine does not allow
+ row based binary logging (RBL) optimizations.
+
+ Currently, setting this flag, means that table's read/write_set will
+ be left untouched when logging changes to tables in this engine. In
+ practice this means that the server will not mess around with
+ table->write_set and/or table->read_set when using RBL and deciding
+ whether to log full or minimal rows.
+
+ It's valuable for instance for virtual tables, eg: Performance
+ Schema which have no meaning for replication.
+*/
+#define HTON_NO_BINLOG_ROW_OPT (1 << 9)
+#define HTON_SUPPORTS_EXTENDED_KEYS (1 <<10) //supports extended keys
+
+// MySQL compatibility. Unused.
+#define HTON_SUPPORTS_FOREIGN_KEYS (1 << 0) //Foreign key constraint supported.
+
+class Ha_trx_info;
+
+struct THD_TRANS
+{
+ /* true is not all entries in the ht[] support 2pc */
+ bool no_2pc;
+ /* storage engines that registered in this transaction */
+ Ha_trx_info *ha_list;
+ /*
+ The purpose of this flag is to keep track of non-transactional
+ tables that were modified in scope of:
+ - transaction, when the variable is a member of
+ THD::transaction.all
+ - top-level statement or sub-statement, when the variable is a
+ member of THD::transaction.stmt
+ This member has the following life cycle:
+ * stmt.modified_non_trans_table is used to keep track of
+ modified non-transactional tables of top-level statements. At
+ the end of the previous statement and at the beginning of the session,
+ it is reset to FALSE. If such functions
+ as mysql_insert, mysql_update, mysql_delete etc modify a
+ non-transactional table, they set this flag to TRUE. At the
+ end of the statement, the value of stmt.modified_non_trans_table
+ is merged with all.modified_non_trans_table and gets reset.
+ * all.modified_non_trans_table is reset at the end of transaction
+
+ * Since we do not have a dedicated context for execution of a
+ sub-statement, to keep track of non-transactional changes in a
+ sub-statement, we re-use stmt.modified_non_trans_table.
+ At entrance into a sub-statement, a copy of the value of
+ stmt.modified_non_trans_table (containing the changes of the
+ outer statement) is saved on stack. Then
+ stmt.modified_non_trans_table is reset to FALSE and the
+ substatement is executed. Then the new value is merged with the
+ saved value.
+ */
+ bool modified_non_trans_table;
+
+ void reset() { no_2pc= FALSE; modified_non_trans_table= FALSE; }
+ bool is_empty() const { return ha_list == NULL; }
+ THD_TRANS() {} /* Remove gcc warning */
+
+ unsigned int m_unsafe_rollback_flags;
+ /*
+ Define the type of statemens which cannot be rolled back safely.
+ Each type occupies one bit in m_unsafe_rollback_flags.
+ */
+ static unsigned int const MODIFIED_NON_TRANS_TABLE= 0x01;
+ static unsigned int const CREATED_TEMP_TABLE= 0x02;
+ static unsigned int const DROPPED_TEMP_TABLE= 0x04;
+
+ void mark_created_temp_table()
+ {
+ DBUG_PRINT("debug", ("mark_created_temp_table"));
+ m_unsafe_rollback_flags|= CREATED_TEMP_TABLE;
+ }
+
+};
+
+
+/**
+ Either statement transaction or normal transaction - related
+ thread-specific storage engine data.
+
+ If a storage engine participates in a statement/transaction,
+ an instance of this class is present in
+ thd->transaction.{stmt|all}.ha_list. The addition to
+ {stmt|all}.ha_list is made by trans_register_ha().
+
+ When it's time to commit or rollback, each element of ha_list
+ is used to access storage engine's prepare()/commit()/rollback()
+ methods, and also to evaluate if a full two phase commit is
+ necessary.
+
+ @sa General description of transaction handling in handler.cc.
+*/
+
+class Ha_trx_info
+{
+public:
+ /** Register this storage engine in the given transaction context. */
+ void register_ha(THD_TRANS *trans, handlerton *ht_arg)
+ {
+ DBUG_ASSERT(m_flags == 0);
+ DBUG_ASSERT(m_ht == NULL);
+ DBUG_ASSERT(m_next == NULL);
+
+ m_ht= ht_arg;
+ m_flags= (int) TRX_READ_ONLY; /* Assume read-only at start. */
+
+ m_next= trans->ha_list;
+ trans->ha_list= this;
+ }
+
+ /** Clear, prepare for reuse. */
+ void reset()
+ {
+ m_next= NULL;
+ m_ht= NULL;
+ m_flags= 0;
+ }
+
+ Ha_trx_info() { reset(); }
+
+ void set_trx_read_write()
+ {
+ DBUG_ASSERT(is_started());
+ m_flags|= (int) TRX_READ_WRITE;
+ }
+ bool is_trx_read_write() const
+ {
+ DBUG_ASSERT(is_started());
+ return m_flags & (int) TRX_READ_WRITE;
+ }
+ bool is_started() const { return m_ht != NULL; }
+ /** Mark this transaction read-write if the argument is read-write. */
+ void coalesce_trx_with(const Ha_trx_info *stmt_trx)
+ {
+ /*
+ Must be called only after the transaction has been started.
+ Can be called many times, e.g. when we have many
+ read-write statements in a transaction.
+ */
+ DBUG_ASSERT(is_started());
+ if (stmt_trx->is_trx_read_write())
+ set_trx_read_write();
+ }
+ Ha_trx_info *next() const
+ {
+ DBUG_ASSERT(is_started());
+ return m_next;
+ }
+ handlerton *ht() const
+ {
+ DBUG_ASSERT(is_started());
+ return m_ht;
+ }
+private:
+ enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1 };
+ /** Auxiliary, used for ha_list management */
+ Ha_trx_info *m_next;
+ /**
+ Although a given Ha_trx_info instance is currently always used
+ for the same storage engine, 'ht' is not-NULL only when the
+ corresponding storage is a part of a transaction.
+ */
+ handlerton *m_ht;
+ /**
+ Transaction flags related to this engine.
+ Not-null only if this instance is a part of transaction.
+ May assume a combination of enum values above.
+ */
+ uchar m_flags;
+};
+
+
+enum enum_tx_isolation { ISO_READ_UNCOMMITTED, ISO_READ_COMMITTED,
+ ISO_REPEATABLE_READ, ISO_SERIALIZABLE};
+
+
+typedef struct {
+ ulonglong data_file_length;
+ ulonglong max_data_file_length;
+ ulonglong index_file_length;
+ ulonglong delete_length;
+ ha_rows records;
+ ulong mean_rec_length;
+ time_t create_time;
+ time_t check_time;
+ time_t update_time;
+ ulonglong check_sum;
+} PARTITION_STATS;
+
+#define UNDEF_NODEGROUP 65535
+class Item;
+struct st_table_log_memory_entry;
+
+class partition_info;
+
+struct st_partition_iter;
+
+enum ha_choice { HA_CHOICE_UNDEF, HA_CHOICE_NO, HA_CHOICE_YES, HA_CHOICE_MAX };
+
+enum enum_stats_auto_recalc { HA_STATS_AUTO_RECALC_DEFAULT= 0,
+ HA_STATS_AUTO_RECALC_ON,
+ HA_STATS_AUTO_RECALC_OFF };
+
+struct HA_CREATE_INFO
+{
+ CHARSET_INFO *table_charset, *default_table_charset;
+ LEX_CUSTRING tabledef_version;
+ LEX_STRING connect_string;
+ const char *password, *tablespace;
+ LEX_STRING comment;
+ const char *data_file_name, *index_file_name;
+ const char *alias;
+ ulonglong max_rows,min_rows;
+ ulonglong auto_increment_value;
+ ulong table_options; ///< HA_OPTION_ values
+ ulong avg_row_length;
+ ulong used_fields;
+ ulong key_block_size;
+ /*
+ number of pages to sample during
+ stats estimation, if used, otherwise 0.
+ */
+ uint stats_sample_pages;
+ uint null_bits; /* NULL bits at start of record */
+ uint options; /* OR of HA_CREATE_ options */
+ uint org_options; /* original options from query */
+ uint merge_insert_method;
+ uint extra_size; /* length of extra data segment */
+ SQL_I_List<TABLE_LIST> merge_list;
+ handlerton *db_type;
+ /**
+ Row type of the table definition.
+
+ Defaults to ROW_TYPE_DEFAULT for all non-ALTER statements.
+ For ALTER TABLE defaults to ROW_TYPE_NOT_USED (means "keep the current").
+
+ Can be changed either explicitly by the parser.
+ If nothing specified inherits the value of the original table (if present).
+ */
+ enum row_type row_type;
+ enum ha_choice transactional;
+ enum ha_storage_media storage_media; ///< DEFAULT, DISK or MEMORY
+ enum ha_choice page_checksum; ///< If we have page_checksums
+ engine_option_value *option_list; ///< list of table create options
+ enum_stats_auto_recalc stats_auto_recalc;
+ bool varchar; ///< 1 if table has a VARCHAR
+
+ /* the following three are only for ALTER TABLE, check_if_incompatible_data() */
+ ha_table_option_struct *option_struct; ///< structure with parsed table options
+ ha_field_option_struct **fields_option_struct; ///< array of field option structures
+ ha_index_option_struct **indexes_option_struct; ///< array of index option structures
+
+ /* The following is used to remember the old state for CREATE OR REPLACE */
+ TABLE *table;
+ TABLE_LIST *pos_in_locked_tables;
+ MDL_ticket *mdl_ticket;
+ bool table_was_deleted;
+
+ bool tmp_table() { return options & HA_LEX_CREATE_TMP_TABLE; }
+ bool check_conflicting_charset_declarations(CHARSET_INFO *cs);
+ bool add_table_option_default_charset(CHARSET_INFO *cs)
+ {
+ // cs can be NULL, e.g.: CREATE TABLE t1 (..) CHARACTER SET DEFAULT;
+ if (check_conflicting_charset_declarations(cs))
+ return true;
+ default_table_charset= cs;
+ used_fields|= HA_CREATE_USED_DEFAULT_CHARSET;
+ return false;
+ }
+ bool add_alter_list_item_convert_to_charset(CHARSET_INFO *cs)
+ {
+ /*
+ cs cannot be NULL, as sql_yacc.yy translates
+ CONVERT TO CHARACTER SET DEFAULT
+ to
+ CONVERT TO CHARACTER SET <character-set-of-the-current-database>
+ TODO: Should't we postpone resolution of DEFAULT until the
+ character set of the table owner database is loaded from its db.opt?
+ */
+ DBUG_ASSERT(cs);
+ if (check_conflicting_charset_declarations(cs))
+ return true;
+ table_charset= default_table_charset= cs;
+ used_fields|= (HA_CREATE_USED_CHARSET | HA_CREATE_USED_DEFAULT_CHARSET);
+ return false;
+ }
+};
+
+
+/**
+ In-place alter handler context.
+
+ This is a superclass intended to be subclassed by individual handlers
+ in order to store handler unique context between in-place alter API calls.
+
+ The handler is responsible for creating the object. This can be done
+ as early as during check_if_supported_inplace_alter().
+
+ The SQL layer is responsible for destroying the object.
+ The class extends Sql_alloc so the memory will be mem root allocated.
+
+ @see Alter_inplace_info
+*/
+
+class inplace_alter_handler_ctx : public Sql_alloc
+{
+public:
+ inplace_alter_handler_ctx() {}
+
+ virtual ~inplace_alter_handler_ctx() {}
+};
+
+
+/**
+ Class describing changes to be done by ALTER TABLE.
+ Instance of this class is passed to storage engine in order
+ to determine if this ALTER TABLE can be done using in-place
+ algorithm. It is also used for executing the ALTER TABLE
+ using in-place algorithm.
+*/
+
+class Alter_inplace_info
+{
+public:
+ /**
+ Bits to show in detail what operations the storage engine is
+ to execute.
+
+ All these operations are supported as in-place operations by the
+ SQL layer. This means that operations that by their nature must
+ be performed by copying the table to a temporary table, will not
+ have their own flags here.
+
+ We generally try to specify handler flags only if there are real
+ changes. But in cases when it is cumbersome to determine if some
+ attribute has really changed we might choose to set flag
+ pessimistically, for example, relying on parser output only.
+ */
+ typedef ulong HA_ALTER_FLAGS;
+
+ // Add non-unique, non-primary index
+ static const HA_ALTER_FLAGS ADD_INDEX = 1L << 0;
+
+ // Drop non-unique, non-primary index
+ static const HA_ALTER_FLAGS DROP_INDEX = 1L << 1;
+
+ // Add unique, non-primary index
+ static const HA_ALTER_FLAGS ADD_UNIQUE_INDEX = 1L << 2;
+
+ // Drop unique, non-primary index
+ static const HA_ALTER_FLAGS DROP_UNIQUE_INDEX = 1L << 3;
+
+ // Add primary index
+ static const HA_ALTER_FLAGS ADD_PK_INDEX = 1L << 4;
+
+ // Drop primary index
+ static const HA_ALTER_FLAGS DROP_PK_INDEX = 1L << 5;
+
+ // Add column
+ static const HA_ALTER_FLAGS ADD_COLUMN = 1L << 6;
+
+ // Drop column
+ static const HA_ALTER_FLAGS DROP_COLUMN = 1L << 7;
+
+ // Rename column
+ static const HA_ALTER_FLAGS ALTER_COLUMN_NAME = 1L << 8;
+
+ // Change column datatype
+ static const HA_ALTER_FLAGS ALTER_COLUMN_TYPE = 1L << 9;
+
+ /**
+ Change column datatype in such way that new type has compatible
+ packed representation with old type, so it is theoretically
+ possible to perform change by only updating data dictionary
+ without changing table rows.
+ */
+ static const HA_ALTER_FLAGS ALTER_COLUMN_EQUAL_PACK_LENGTH = 1L << 10;
+
+ // Reorder column
+ static const HA_ALTER_FLAGS ALTER_COLUMN_ORDER = 1L << 11;
+
+ // Change column from NOT NULL to NULL
+ static const HA_ALTER_FLAGS ALTER_COLUMN_NULLABLE = 1L << 12;
+
+ // Change column from NULL to NOT NULL
+ static const HA_ALTER_FLAGS ALTER_COLUMN_NOT_NULLABLE = 1L << 13;
+
+ // Set or remove default column value
+ static const HA_ALTER_FLAGS ALTER_COLUMN_DEFAULT = 1L << 14;
+
+ // Add foreign key
+ static const HA_ALTER_FLAGS ADD_FOREIGN_KEY = 1L << 15;
+
+ // Drop foreign key
+ static const HA_ALTER_FLAGS DROP_FOREIGN_KEY = 1L << 16;
+
+ // table_options changed, see HA_CREATE_INFO::used_fields for details.
+ static const HA_ALTER_FLAGS CHANGE_CREATE_OPTION = 1L << 17;
+
+ // Table is renamed
+ static const HA_ALTER_FLAGS ALTER_RENAME = 1L << 18;
+
+ // column's engine options changed, something in field->option_struct
+ static const HA_ALTER_FLAGS ALTER_COLUMN_OPTION = 1L << 19;
+
+ // MySQL alias for the same thing:
+ static const HA_ALTER_FLAGS ALTER_COLUMN_STORAGE_TYPE = 1L << 19;
+
+ // Change the column format of column
+ static const HA_ALTER_FLAGS ALTER_COLUMN_COLUMN_FORMAT = 1L << 20;
+
+ // Add partition
+ static const HA_ALTER_FLAGS ADD_PARTITION = 1L << 21;
+
+ // Drop partition
+ static const HA_ALTER_FLAGS DROP_PARTITION = 1L << 22;
+
+ // Changing partition options
+ static const HA_ALTER_FLAGS ALTER_PARTITION = 1L << 23;
+
+ // Coalesce partition
+ static const HA_ALTER_FLAGS COALESCE_PARTITION = 1L << 24;
+
+ // Reorganize partition ... into
+ static const HA_ALTER_FLAGS REORGANIZE_PARTITION = 1L << 25;
+
+ // Reorganize partition
+ static const HA_ALTER_FLAGS ALTER_TABLE_REORG = 1L << 26;
+
+ // Remove partitioning
+ static const HA_ALTER_FLAGS ALTER_REMOVE_PARTITIONING = 1L << 27;
+
+ // Partition operation with ALL keyword
+ static const HA_ALTER_FLAGS ALTER_ALL_PARTITION = 1L << 28;
+
+ /**
+ Recreate the table for ALTER TABLE FORCE, ALTER TABLE ENGINE
+ and OPTIMIZE TABLE operations.
+ */
+ static const HA_ALTER_FLAGS RECREATE_TABLE = 1L << 29;
+
+ // Virtual columns changed
+ static const HA_ALTER_FLAGS ALTER_COLUMN_VCOL = 1L << 30;
+
+ // ALTER TABLE for a partitioned table
+ static const HA_ALTER_FLAGS ALTER_PARTITIONED = 1L << 31;
+
+ /**
+ Create options (like MAX_ROWS) for the new version of table.
+
+ @note The referenced instance of HA_CREATE_INFO object was already
+ used to create new .FRM file for table being altered. So it
+ has been processed by mysql_prepare_create_table() already.
+ For example, this means that it has HA_OPTION_PACK_RECORD
+ flag in HA_CREATE_INFO::table_options member correctly set.
+ */
+ HA_CREATE_INFO *create_info;
+
+ /**
+ Alter options, fields and keys for the new version of table.
+
+ @note The referenced instance of Alter_info object was already
+ used to create new .FRM file for table being altered. So it
+ has been processed by mysql_prepare_create_table() already.
+ In particular, this means that in Create_field objects for
+ fields which were present in some form in the old version
+ of table, Create_field::field member points to corresponding
+ Field instance for old version of table.
+ */
+ Alter_info *alter_info;
+
+ /**
+ Array of KEYs for new version of table - including KEYs to be added.
+
+ @note Currently this array is produced as result of
+ mysql_prepare_create_table() call.
+ This means that it follows different convention for
+ KEY_PART_INFO::fieldnr values than objects in TABLE::key_info
+ array.
+
+ @todo This is mainly due to the fact that we need to keep compatibility
+ with removed handler::add_index() call. We plan to switch to
+ TABLE::key_info numbering later.
+
+ KEYs are sorted - see sort_keys().
+ */
+ KEY *key_info_buffer;
+
+ /** Size of key_info_buffer array. */
+ uint key_count;
+
+ /** Size of index_drop_buffer array. */
+ uint index_drop_count;
+
+ /**
+ Array of pointers to KEYs to be dropped belonging to the TABLE instance
+ for the old version of the table.
+ */
+ KEY **index_drop_buffer;
+
+ /** Size of index_add_buffer array. */
+ uint index_add_count;
+
+ /**
+ Array of indexes into key_info_buffer for KEYs to be added,
+ sorted in increasing order.
+ */
+ uint *index_add_buffer;
+
+ /**
+ Context information to allow handlers to keep context between in-place
+ alter API calls.
+
+ @see inplace_alter_handler_ctx for information about object lifecycle.
+ */
+ inplace_alter_handler_ctx *handler_ctx;
+
+ /**
+ If the table uses several handlers, like ha_partition uses one handler
+ per partition, this contains a Null terminated array of ctx pointers
+ that should all be committed together.
+ Or NULL if only handler_ctx should be committed.
+ Set to NULL if the low level handler::commit_inplace_alter_table uses it,
+ to signal to the main handler that everything was committed as atomically.
+
+ @see inplace_alter_handler_ctx for information about object lifecycle.
+ */
+ inplace_alter_handler_ctx **group_commit_ctx;
+
+ /**
+ Flags describing in detail which operations the storage engine is to execute.
+ */
+ HA_ALTER_FLAGS handler_flags;
+
+ /**
+ Partition_info taking into account the partition changes to be performed.
+ Contains all partitions which are present in the old version of the table
+ with partitions to be dropped or changed marked as such + all partitions
+ to be added in the new version of table marked as such.
+ */
+ partition_info *modified_part_info;
+
+ /** true for ALTER IGNORE TABLE ... */
+ const bool ignore;
+
+ /** true for online operation (LOCK=NONE) */
+ bool online;
+
+ /**
+ Can be set by handler to describe why a given operation cannot be done
+ in-place (HA_ALTER_INPLACE_NOT_SUPPORTED) or why it cannot be done
+ online (HA_ALTER_INPLACE_NO_LOCK or
+ HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE)
+ If set, it will be used with ER_ALTER_OPERATION_NOT_SUPPORTED_REASON if
+ results from handler::check_if_supported_inplace_alter() doesn't match
+ requirements set by user. If not set, the more generic
+ ER_ALTER_OPERATION_NOT_SUPPORTED will be used.
+
+ Please set to a properly localized string, for example using
+ my_get_err_msg(), so that the error message as a whole is localized.
+ */
+ const char *unsupported_reason;
+
+ Alter_inplace_info(HA_CREATE_INFO *create_info_arg,
+ Alter_info *alter_info_arg,
+ KEY *key_info_arg, uint key_count_arg,
+ partition_info *modified_part_info_arg,
+ bool ignore_arg)
+ : create_info(create_info_arg),
+ alter_info(alter_info_arg),
+ key_info_buffer(key_info_arg),
+ key_count(key_count_arg),
+ index_drop_count(0),
+ index_drop_buffer(NULL),
+ index_add_count(0),
+ index_add_buffer(NULL),
+ handler_ctx(NULL),
+ group_commit_ctx(NULL),
+ handler_flags(0),
+ modified_part_info(modified_part_info_arg),
+ ignore(ignore_arg),
+ online(false),
+ unsupported_reason(NULL)
+ {}
+
+ ~Alter_inplace_info()
+ {
+ delete handler_ctx;
+ }
+
+ /**
+ Used after check_if_supported_inplace_alter() to report
+ error if the result does not match the LOCK/ALGORITHM
+ requirements set by the user.
+
+ @param not_supported Part of statement that was not supported.
+ @param try_instead Suggestion as to what the user should
+ replace not_supported with.
+ */
+ void report_unsupported_error(const char *not_supported,
+ const char *try_instead);
+};
+
+
+typedef struct st_key_create_information
+{
+ enum ha_key_alg algorithm;
+ ulong block_size;
+ LEX_STRING parser_name;
+ LEX_STRING comment;
+ /**
+ A flag to determine if we will check for duplicate indexes.
+ This typically means that the key information was specified
+ directly by the user (set by the parser).
+ */
+ bool check_for_duplicate_indexes;
+} KEY_CREATE_INFO;
+
+
+/*
+ Class for maintaining hooks used inside operations on tables such
+ as: create table functions, delete table functions, and alter table
+ functions.
+
+ Class is using the Template Method pattern to separate the public
+ usage interface from the private inheritance interface. This
+ imposes no overhead, since the public non-virtual function is small
+ enough to be inlined.
+
+ The hooks are usually used for functions that does several things,
+ e.g., create_table_from_items(), which both create a table and lock
+ it.
+ */
+class TABLEOP_HOOKS
+{
+public:
+ TABLEOP_HOOKS() {}
+ virtual ~TABLEOP_HOOKS() {}
+
+ inline void prelock(TABLE **tables, uint count)
+ {
+ do_prelock(tables, count);
+ }
+
+ inline int postlock(TABLE **tables, uint count)
+ {
+ return do_postlock(tables, count);
+ }
+private:
+ /* Function primitive that is called prior to locking tables */
+ virtual void do_prelock(TABLE **tables, uint count)
+ {
+ /* Default is to do nothing */
+ }
+
+ /**
+ Primitive called after tables are locked.
+
+ If an error is returned, the tables will be unlocked and error
+ handling start.
+
+ @return Error code or zero.
+ */
+ virtual int do_postlock(TABLE **tables, uint count)
+ {
+ return 0; /* Default is to do nothing */
+ }
+};
+
+typedef struct st_savepoint SAVEPOINT;
+extern ulong savepoint_alloc_size;
+extern KEY_CREATE_INFO default_key_create_info;
+
+/* Forward declaration for condition pushdown to storage engine */
+typedef class Item COND;
+
+typedef struct st_ha_check_opt
+{
+ st_ha_check_opt() {} /* Remove gcc warning */
+ uint flags; /* isam layer flags (e.g. for myisamchk) */
+ uint sql_flags; /* sql layer flags - for something myisamchk cannot do */
+ time_t start_time; /* When check/repair starts */
+ KEY_CACHE *key_cache; /* new key cache when changing key cache */
+ void init();
+} HA_CHECK_OPT;
+
+
+/********************************************************************************
+ * MRR
+ ********************************************************************************/
+
+typedef void *range_seq_t;
+
+typedef struct st_range_seq_if
+{
+ /*
+ Get key information
+
+ SYNOPSIS
+ get_key_info()
+ init_params The seq_init_param parameter
+ length OUT length of the keys in this range sequence
+ map OUT key_part_map of the keys in this range sequence
+
+ DESCRIPTION
+ This function is set only when using HA_MRR_FIXED_KEY mode. In that mode,
+ all ranges are single-point equality ranges that use the same set of key
+ parts. This function allows the MRR implementation to get the length of
+ a key, and which keyparts it uses.
+ */
+ void (*get_key_info)(void *init_params, uint *length, key_part_map *map);
+
+ /*
+ Initialize the traversal of range sequence
+
+ SYNOPSIS
+ init()
+ init_params The seq_init_param parameter
+ n_ranges The number of ranges obtained
+ flags A combination of HA_MRR_SINGLE_POINT, HA_MRR_FIXED_KEY
+
+ RETURN
+ An opaque value to be used as RANGE_SEQ_IF::next() parameter
+ */
+ range_seq_t (*init)(void *init_params, uint n_ranges, uint flags);
+
+
+ /*
+ Get the next range in the range sequence
+
+ SYNOPSIS
+ next()
+ seq The value returned by RANGE_SEQ_IF::init()
+ range OUT Information about the next range
+
+ RETURN
+ FALSE - Ok, the range structure filled with info about the next range
+ TRUE - No more ranges
+ */
+ bool (*next) (range_seq_t seq, KEY_MULTI_RANGE *range);
+
+ /*
+ Check whether range_info orders to skip the next record
+
+ SYNOPSIS
+ skip_record()
+ seq The value returned by RANGE_SEQ_IF::init()
+ range_info Information about the next range
+ (Ignored if MRR_NO_ASSOCIATION is set)
+ rowid Rowid of the record to be checked (ignored if set to 0)
+
+ RETURN
+ 1 - Record with this range_info and/or this rowid shall be filtered
+ out from the stream of records returned by multi_range_read_next()
+ 0 - The record shall be left in the stream
+ */
+ bool (*skip_record) (range_seq_t seq, range_id_t range_info, uchar *rowid);
+
+ /*
+ Check if the record combination matches the index condition
+ SYNOPSIS
+ skip_index_tuple()
+ seq The value returned by RANGE_SEQ_IF::init()
+ range_info Information about the next range
+
+ RETURN
+ 0 - The record combination satisfies the index condition
+ 1 - Otherwise
+ */
+ bool (*skip_index_tuple) (range_seq_t seq, range_id_t range_info);
+} RANGE_SEQ_IF;
+
+typedef bool (*SKIP_INDEX_TUPLE_FUNC) (range_seq_t seq, range_id_t range_info);
+
+class Cost_estimate
+{
+public:
+ double io_count; /* number of I/O */
+ double avg_io_cost; /* cost of an average I/O oper. */
+ double cpu_cost; /* cost of operations in CPU */
+ double import_cost; /* cost of remote operations */
+ double mem_cost; /* cost of used memory */
+
+ enum { IO_COEFF=1 };
+ enum { CPU_COEFF=1 };
+ enum { MEM_COEFF=1 };
+ enum { IMPORT_COEFF=1 };
+
+ Cost_estimate()
+ {
+ reset();
+ }
+
+ double total_cost()
+ {
+ return IO_COEFF*io_count*avg_io_cost + CPU_COEFF * cpu_cost +
+ MEM_COEFF*mem_cost + IMPORT_COEFF*import_cost;
+ }
+
+ /**
+ Whether or not all costs in the object are zero
+
+ @return true if all costs are zero, false otherwise
+ */
+ bool is_zero() const
+ {
+ return !(io_count || cpu_cost || import_cost || mem_cost);
+ }
+
+ void reset()
+ {
+ avg_io_cost= 1.0;
+ io_count= cpu_cost= mem_cost= import_cost= 0.0;
+ }
+
+ void multiply(double m)
+ {
+ io_count *= m;
+ cpu_cost *= m;
+ import_cost *= m;
+ /* Don't multiply mem_cost */
+ }
+
+ void add(const Cost_estimate* cost)
+ {
+ double io_count_sum= io_count + cost->io_count;
+ add_io(cost->io_count, cost->avg_io_cost);
+ io_count= io_count_sum;
+ cpu_cost += cost->cpu_cost;
+ }
+
+ void add_io(double add_io_cnt, double add_avg_cost)
+ {
+ /* In edge cases add_io_cnt may be zero */
+ if (add_io_cnt > 0)
+ {
+ double io_count_sum= io_count + add_io_cnt;
+ avg_io_cost= (io_count * avg_io_cost +
+ add_io_cnt * add_avg_cost) / io_count_sum;
+ io_count= io_count_sum;
+ }
+ }
+
+ /// Add to CPU cost
+ void add_cpu(double add_cpu_cost) { cpu_cost+= add_cpu_cost; }
+
+ /// Add to import cost
+ void add_import(double add_import_cost) { import_cost+= add_import_cost; }
+
+ /// Add to memory cost
+ void add_mem(double add_mem_cost) { mem_cost+= add_mem_cost; }
+
+ /*
+ To be used when we go from old single value-based cost calculations to
+ the new Cost_estimate-based.
+ */
+ void convert_from_cost(double cost)
+ {
+ reset();
+ io_count= cost;
+ }
+};
+
+void get_sweep_read_cost(TABLE *table, ha_rows nrows, bool interrupted,
+ Cost_estimate *cost);
+
+/*
+ Indicates that all scanned ranges will be singlepoint (aka equality) ranges.
+ The ranges may not use the full key but all of them will use the same number
+ of key parts.
+*/
+#define HA_MRR_SINGLE_POINT 1
+#define HA_MRR_FIXED_KEY 2
+
+/*
+ Indicates that RANGE_SEQ_IF::next(&range) doesn't need to fill in the
+ 'range' parameter.
+*/
+#define HA_MRR_NO_ASSOCIATION 4
+
+/*
+ The MRR user will provide ranges in key order, and MRR implementation
+ must return rows in key order.
+*/
+#define HA_MRR_SORTED 8
+
+/* MRR implementation doesn't have to retrieve full records */
+#define HA_MRR_INDEX_ONLY 16
+
+/*
+ The passed memory buffer is of maximum possible size, the caller can't
+ assume larger buffer.
+*/
+#define HA_MRR_LIMITS 32
+
+
+/*
+ Flag set <=> default MRR implementation is used
+ (The choice is made by **_info[_const]() function which may set this
+ flag. SQL layer remembers the flag value and then passes it to
+ multi_read_range_init().
+*/
+#define HA_MRR_USE_DEFAULT_IMPL 64
+
+/*
+ Used only as parameter to multi_range_read_info():
+ Flag set <=> the caller guarantees that the bounds of the scanned ranges
+ will not have NULL values.
+*/
+#define HA_MRR_NO_NULL_ENDPOINTS 128
+
+/*
+ The MRR user has materialized range keys somewhere in the user's buffer.
+ This can be used for optimization of the procedure that sorts these keys
+ since in this case key values don't have to be copied into the MRR buffer.
+
+ In other words, it is guaranteed that after RANGE_SEQ_IF::next() call the
+ pointer in range->start_key.key will point to a key value that will remain
+ there until the end of the MRR scan.
+*/
+#define HA_MRR_MATERIALIZED_KEYS 256
+
+/*
+ The following bits are reserved for use by MRR implementation. The intended
+ use scenario:
+
+ * sql layer calls handler->multi_range_read_info[_const]()
+ - MRR implementation figures out what kind of scan it will perform, saves
+ the result in *mrr_mode parameter.
+ * sql layer remembers what was returned in *mrr_mode
+
+ * the optimizer picks the query plan (which may or may not include the MRR
+ scan that was estimated by the multi_range_read_info[_const] call)
+
+ * if the query is an EXPLAIN statement, sql layer will call
+ handler->multi_range_read_explain_info(mrr_mode) to get a text description
+ of the picked MRR scan; the description will be a part of EXPLAIN output.
+*/
+#define HA_MRR_IMPLEMENTATION_FLAG1 512
+#define HA_MRR_IMPLEMENTATION_FLAG2 1024
+#define HA_MRR_IMPLEMENTATION_FLAG3 2048
+#define HA_MRR_IMPLEMENTATION_FLAG4 4096
+#define HA_MRR_IMPLEMENTATION_FLAG5 8192
+#define HA_MRR_IMPLEMENTATION_FLAG6 16384
+
+#define HA_MRR_IMPLEMENTATION_FLAGS \
+ (512 | 1024 | 2048 | 4096 | 8192 | 16384)
+
+/*
+ This is a buffer area that the handler can use to store rows.
+ 'end_of_used_area' should be kept updated after calls to
+ read-functions so that other parts of the code can use the
+ remaining area (until next read calls is issued).
+*/
+
+typedef struct st_handler_buffer
+{
+ /* const? */uchar *buffer; /* Buffer one can start using */
+ /* const? */uchar *buffer_end; /* End of buffer */
+ uchar *end_of_used_area; /* End of area that was used by handler */
+} HANDLER_BUFFER;
+
+typedef struct system_status_var SSV;
+
+class ha_statistics
+{
+public:
+ ulonglong data_file_length; /* Length off data file */
+ ulonglong max_data_file_length; /* Length off data file */
+ ulonglong index_file_length;
+ ulonglong max_index_file_length;
+ ulonglong delete_length; /* Free bytes */
+ ulonglong auto_increment_value;
+ /*
+ The number of records in the table.
+ 0 - means the table has exactly 0 rows
+ other - if (table_flags() & HA_STATS_RECORDS_IS_EXACT)
+ the value is the exact number of records in the table
+ else
+ it is an estimate
+ */
+ ha_rows records;
+ ha_rows deleted; /* Deleted records */
+ ulong mean_rec_length; /* physical reclength */
+ time_t create_time; /* When table was created */
+ time_t check_time;
+ time_t update_time;
+ uint block_size; /* index block size */
+
+ /*
+ number of buffer bytes that native mrr implementation needs,
+ */
+ uint mrr_length_per_rec;
+
+ ha_statistics():
+ data_file_length(0), max_data_file_length(0),
+ index_file_length(0), delete_length(0), auto_increment_value(0),
+ records(0), deleted(0), mean_rec_length(0), create_time(0),
+ check_time(0), update_time(0), block_size(0), mrr_length_per_rec(0)
+ {}
+};
+
+extern "C" enum icp_result handler_index_cond_check(void* h_arg);
+
+uint calculate_key_len(TABLE *, uint, const uchar *, key_part_map);
+/*
+ bitmap with first N+1 bits set
+ (keypart_map for a key prefix of [0..N] keyparts)
+*/
+#define make_keypart_map(N) (((key_part_map)2 << (N)) - 1)
+/*
+ bitmap with first N bits set
+ (keypart_map for a key prefix of [0..N-1] keyparts)
+*/
+#define make_prev_keypart_map(N) (((key_part_map)1 << (N)) - 1)
+
+
+/** Base class to be used by handlers different shares */
+class Handler_share
+{
+public:
+ Handler_share() {}
+ virtual ~Handler_share() {}
+};
+
+
+/**
+ The handler class is the interface for dynamically loadable
+ storage engines. Do not add ifdefs and take care when adding or
+ changing virtual functions to avoid vtable confusion
+
+ Functions in this class accept and return table columns data. Two data
+ representation formats are used:
+ 1. TableRecordFormat - Used to pass [partial] table records to/from
+ storage engine
+
+ 2. KeyTupleFormat - used to pass index search tuples (aka "keys") to
+ storage engine. See opt_range.cc for description of this format.
+
+ TableRecordFormat
+ =================
+ [Warning: this description is work in progress and may be incomplete]
+ The table record is stored in a fixed-size buffer:
+
+ record: null_bytes, column1_data, column2_data, ...
+
+ The offsets of the parts of the buffer are also fixed: every column has
+ an offset to its column{i}_data, and if it is nullable it also has its own
+ bit in null_bytes.
+
+ The record buffer only includes data about columns that are marked in the
+ relevant column set (table->read_set and/or table->write_set, depending on
+ the situation).
+ <not-sure>It could be that it is required that null bits of non-present
+ columns are set to 1</not-sure>
+
+ VARIOUS EXCEPTIONS AND SPECIAL CASES
+
+ If the table has no nullable columns, then null_bytes is still
+ present, its length is one byte <not-sure> which must be set to 0xFF
+ at all times. </not-sure>
+
+ If the table has columns of type BIT, then certain bits from those columns
+ may be stored in null_bytes as well. Grep around for Field_bit for
+ details.
+
+ For blob columns (see Field_blob), the record buffer stores length of the
+ data, following by memory pointer to the blob data. The pointer is owned
+ by the storage engine and is valid until the next operation.
+
+ If a blob column has NULL value, then its length and blob data pointer
+ must be set to 0.
+*/
+
+class handler :public Sql_alloc
+{
+public:
+ typedef ulonglong Table_flags;
+protected:
+ TABLE_SHARE *table_share; /* The table definition */
+ TABLE *table; /* The current open table */
+ Table_flags cached_table_flags; /* Set on init() and open() */
+
+ ha_rows estimation_rows_to_insert;
+public:
+ handlerton *ht; /* storage engine of this handler */
+ uchar *ref; /* Pointer to current row */
+ uchar *dup_ref; /* Pointer to duplicate row */
+
+ ha_statistics stats;
+
+ /** MultiRangeRead-related members: */
+ range_seq_t mrr_iter; /* Interator to traverse the range sequence */
+ RANGE_SEQ_IF mrr_funcs; /* Range sequence traversal functions */
+ HANDLER_BUFFER *multi_range_buffer; /* MRR buffer info */
+ uint ranges_in_seq; /* Total number of ranges in the traversed sequence */
+ /* TRUE <=> source MRR ranges and the output are ordered */
+ bool mrr_is_output_sorted;
+
+ /** TRUE <=> we're currently traversing a range in mrr_cur_range. */
+ bool mrr_have_range;
+ /** Current range (the one we're now returning rows from) */
+ KEY_MULTI_RANGE mrr_cur_range;
+
+ /** The following are for read_range() */
+ key_range save_end_range, *end_range;
+ KEY_PART_INFO *range_key_part;
+ int key_compare_result_on_equal;
+ bool eq_range;
+ bool internal_tmp_table; /* If internal tmp table */
+
+ uint errkey; /* Last dup key */
+ uint key_used_on_scan;
+ uint active_index;
+ /*
+ TRUE <=> the engine guarantees that returned records are within the range
+ being scanned.
+ */
+ bool in_range_check_pushed_down;
+
+ /** Length of ref (1-8 or the clustered key length) */
+ uint ref_length;
+ FT_INFO *ft_handler;
+ enum {NONE=0, INDEX, RND} inited;
+ bool implicit_emptied; /* Can be !=0 only if HEAP */
+ const COND *pushed_cond;
+ /**
+ next_insert_id is the next value which should be inserted into the
+ auto_increment column: in a inserting-multi-row statement (like INSERT
+ SELECT), for the first row where the autoinc value is not specified by the
+ statement, get_auto_increment() called and asked to generate a value,
+ next_insert_id is set to the next value, then for all other rows
+ next_insert_id is used (and increased each time) without calling
+ get_auto_increment().
+ */
+ ulonglong next_insert_id;
+ /**
+ insert id for the current row (*autogenerated*; if not
+ autogenerated, it's 0).
+ At first successful insertion, this variable is stored into
+ THD::first_successful_insert_id_in_cur_stmt.
+ */
+ ulonglong insert_id_for_cur_row;
+ /**
+ Interval returned by get_auto_increment() and being consumed by the
+ inserter.
+ */
+ /* Statistics variables */
+ ulonglong rows_read;
+ ulonglong rows_tmp_read;
+ ulonglong rows_changed;
+ /* One bigger than needed to avoid to test if key == MAX_KEY */
+ ulonglong index_rows_read[MAX_KEY+1];
+
+ Item *pushed_idx_cond;
+ uint pushed_idx_cond_keyno; /* The index which the above condition is for */
+
+ Discrete_interval auto_inc_interval_for_cur_row;
+ /**
+ Number of reserved auto-increment intervals. Serves as a heuristic
+ when we have no estimation of how many records the statement will insert:
+ the more intervals we have reserved, the bigger the next one. Reset in
+ handler::ha_release_auto_increment().
+ */
+ uint auto_inc_intervals_count;
+
+ /**
+ Instrumented table associated with this handler.
+ This member should be set to NULL when no instrumentation is in place,
+ so that linking an instrumented/non instrumented server/plugin works.
+ For example:
+ - the server is compiled with the instrumentation.
+ The server expects either NULL or valid pointers in m_psi.
+ - an engine plugin is compiled without instrumentation.
+ The plugin can not leave this pointer uninitialized,
+ or can not leave a trash value on purpose in this pointer,
+ as this would crash the server.
+ */
+ PSI_table *m_psi;
+
+ virtual void unbind_psi();
+ virtual void rebind_psi();
+
+private:
+ /**
+ The lock type set by when calling::ha_external_lock(). This is
+ propagated down to the storage engine. The reason for also storing
+ it here, is that when doing MRR we need to create/clone a second handler
+ object. This cloned handler object needs to know about the lock_type used.
+ */
+ int m_lock_type;
+ /**
+ Pointer where to store/retrieve the Handler_share pointer.
+ For non partitioned handlers this is &TABLE_SHARE::ha_share.
+ */
+ Handler_share **ha_share;
+
+public:
+ handler(handlerton *ht_arg, TABLE_SHARE *share_arg)
+ :table_share(share_arg), table(0),
+ estimation_rows_to_insert(0), ht(ht_arg),
+ ref(0), end_range(NULL), key_used_on_scan(MAX_KEY), active_index(MAX_KEY),
+ in_range_check_pushed_down(FALSE),
+ ref_length(sizeof(my_off_t)),
+ ft_handler(0), inited(NONE),
+ implicit_emptied(0),
+ pushed_cond(0), next_insert_id(0), insert_id_for_cur_row(0),
+ pushed_idx_cond(NULL),
+ pushed_idx_cond_keyno(MAX_KEY),
+ auto_inc_intervals_count(0),
+ m_psi(NULL), m_lock_type(F_UNLCK), ha_share(NULL)
+ {
+ DBUG_PRINT("info",
+ ("handler created F_UNLCK %d F_RDLCK %d F_WRLCK %d",
+ F_UNLCK, F_RDLCK, F_WRLCK));
+ reset_statistics();
+ }
+ virtual ~handler(void)
+ {
+ DBUG_ASSERT(m_lock_type == F_UNLCK);
+ DBUG_ASSERT(inited == NONE);
+ }
+ virtual handler *clone(const char *name, MEM_ROOT *mem_root);
+ /** This is called after create to allow us to set up cached variables */
+ void init()
+ {
+ cached_table_flags= table_flags();
+ }
+ /* ha_ methods: pubilc wrappers for private virtual API */
+
+ int ha_open(TABLE *table, const char *name, int mode, uint test_if_locked);
+ int ha_index_init(uint idx, bool sorted)
+ {
+ DBUG_EXECUTE_IF("ha_index_init_fail", return HA_ERR_TABLE_DEF_CHANGED;);
+ int result;
+ DBUG_ENTER("ha_index_init");
+ DBUG_ASSERT(inited==NONE);
+ if (!(result= index_init(idx, sorted)))
+ {
+ inited= INDEX;
+ active_index= idx;
+ end_range= NULL;
+ }
+ DBUG_RETURN(result);
+ }
+ int ha_index_end()
+ {
+ DBUG_ENTER("ha_index_end");
+ DBUG_ASSERT(inited==INDEX);
+ inited= NONE;
+ active_index= MAX_KEY;
+ end_range= NULL;
+ DBUG_RETURN(index_end());
+ }
+ /* This is called after index_init() if we need to do a index scan */
+ virtual int prepare_index_scan() { return 0; }
+ virtual int prepare_index_key_scan_map(const uchar * key, key_part_map keypart_map)
+ {
+ uint key_len= calculate_key_len(table, active_index, key, keypart_map);
+ return prepare_index_key_scan(key, key_len);
+ }
+ virtual int prepare_index_key_scan( const uchar * key, uint key_len )
+ { return 0; }
+ virtual int prepare_range_scan(const key_range *start_key, const key_range *end_key)
+ { return 0; }
+
+ int ha_rnd_init(bool scan) __attribute__ ((warn_unused_result))
+ {
+ DBUG_EXECUTE_IF("ha_rnd_init_fail", return HA_ERR_TABLE_DEF_CHANGED;);
+ int result;
+ DBUG_ENTER("ha_rnd_init");
+ DBUG_ASSERT(inited==NONE || (inited==RND && scan));
+ inited= (result= rnd_init(scan)) ? NONE: RND;
+ end_range= NULL;
+ DBUG_RETURN(result);
+ }
+ int ha_rnd_end()
+ {
+ DBUG_ENTER("ha_rnd_end");
+ DBUG_ASSERT(inited==RND);
+ inited=NONE;
+ end_range= NULL;
+ DBUG_RETURN(rnd_end());
+ }
+ int ha_rnd_init_with_error(bool scan) __attribute__ ((warn_unused_result));
+ int ha_reset();
+ /* this is necessary in many places, e.g. in HANDLER command */
+ int ha_index_or_rnd_end()
+ {
+ return inited == INDEX ? ha_index_end() : inited == RND ? ha_rnd_end() : 0;
+ }
+ /**
+ The cached_table_flags is set at ha_open and ha_external_lock
+ */
+ Table_flags ha_table_flags() const { return cached_table_flags; }
+ /**
+ These functions represent the public interface to *users* of the
+ handler class, hence they are *not* virtual. For the inheritance
+ interface, see the (private) functions write_row(), update_row(),
+ and delete_row() below.
+ */
+ int ha_external_lock(THD *thd, int lock_type);
+ int ha_write_row(uchar * buf);
+ int ha_update_row(const uchar * old_data, uchar * new_data);
+ int ha_delete_row(const uchar * buf);
+ void ha_release_auto_increment();
+
+ int check_collation_compatibility();
+ int ha_check_for_upgrade(HA_CHECK_OPT *check_opt);
+ /** to be actually called to get 'check()' functionality*/
+ int ha_check(THD *thd, HA_CHECK_OPT *check_opt);
+ int ha_repair(THD* thd, HA_CHECK_OPT* check_opt);
+ void ha_start_bulk_insert(ha_rows rows, uint flags= 0)
+ {
+ DBUG_ENTER("handler::ha_start_bulk_insert");
+ estimation_rows_to_insert= rows;
+ start_bulk_insert(rows, flags);
+ DBUG_VOID_RETURN;
+ }
+ int ha_end_bulk_insert()
+ {
+ DBUG_ENTER("handler::ha_end_bulk_insert");
+ estimation_rows_to_insert= 0;
+ int ret= end_bulk_insert();
+ DBUG_RETURN(ret);
+ }
+ int ha_bulk_update_row(const uchar *old_data, uchar *new_data,
+ uint *dup_key_found);
+ int ha_delete_all_rows();
+ int ha_truncate();
+ int ha_reset_auto_increment(ulonglong value);
+ int ha_optimize(THD* thd, HA_CHECK_OPT* check_opt);
+ int ha_analyze(THD* thd, HA_CHECK_OPT* check_opt);
+ bool ha_check_and_repair(THD *thd);
+ int ha_disable_indexes(uint mode);
+ int ha_enable_indexes(uint mode);
+ int ha_discard_or_import_tablespace(my_bool discard);
+ int ha_rename_table(const char *from, const char *to);
+ int ha_delete_table(const char *name);
+ void ha_drop_table(const char *name);
+
+ int ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info);
+
+ int ha_create_partitioning_metadata(const char *name, const char *old_name,
+ int action_flag);
+
+ int ha_change_partitions(HA_CREATE_INFO *create_info,
+ const char *path,
+ ulonglong * const copied,
+ ulonglong * const deleted,
+ const uchar *pack_frm_data,
+ size_t pack_frm_len);
+ int ha_drop_partitions(const char *path);
+ int ha_rename_partitions(const char *path);
+
+ void adjust_next_insert_id_after_explicit_value(ulonglong nr);
+ int update_auto_increment();
+ virtual void print_error(int error, myf errflag);
+ virtual bool get_error_message(int error, String *buf);
+ uint get_dup_key(int error);
+ /**
+ Retrieves the names of the table and the key for which there was a
+ duplicate entry in the case of HA_ERR_FOREIGN_DUPLICATE_KEY.
+
+ If any of the table or key name is not available this method will return
+ false and will not change any of child_table_name or child_key_name.
+
+ @param child_table_name[out] Table name
+ @param child_table_name_len[in] Table name buffer size
+ @param child_key_name[out] Key name
+ @param child_key_name_len[in] Key name buffer size
+
+ @retval true table and key names were available
+ and were written into the corresponding
+ out parameters.
+ @retval false table and key names were not available,
+ the out parameters were not touched.
+ */
+ virtual bool get_foreign_dup_key(char *child_table_name,
+ uint child_table_name_len,
+ char *child_key_name,
+ uint child_key_name_len)
+ { DBUG_ASSERT(false); return(false); }
+ void reset_statistics()
+ {
+ rows_read= rows_changed= rows_tmp_read= 0;
+ bzero(index_rows_read, sizeof(index_rows_read));
+ }
+ virtual void change_table_ptr(TABLE *table_arg, TABLE_SHARE *share)
+ {
+ table= table_arg;
+ table_share= share;
+ reset_statistics();
+ }
+ virtual double scan_time()
+ { return ulonglong2double(stats.data_file_length) / IO_SIZE + 2; }
+
+ /**
+ The cost of reading a set of ranges from the table using an index
+ to access it.
+
+ @param index The index number.
+ @param ranges The number of ranges to be read.
+ @param rows Total number of rows to be read.
+
+ This method can be used to calculate the total cost of scanning a table
+ using an index by calling it using read_time(index, 1, table_size).
+ */
+ virtual double read_time(uint index, uint ranges, ha_rows rows)
+ { return rows2double(ranges+rows); }
+
+ /**
+ Calculate cost of 'keyread' scan for given index and number of records.
+
+ @param index index to read
+ @param ranges #of ranges to read
+ @param rows #of records to read
+ */
+ virtual double keyread_time(uint index, uint ranges, ha_rows rows);
+
+ virtual const key_map *keys_to_use_for_scanning() { return &key_map_empty; }
+ bool has_transactions()
+ { return (ha_table_flags() & HA_NO_TRANSACTIONS) == 0; }
+ virtual uint extra_rec_buf_length() const { return 0; }
+
+ /**
+ This method is used to analyse the error to see whether the error
+ is ignorable or not, certain handlers can have more error that are
+ ignorable than others. E.g. the partition handler can get inserts
+ into a range where there is no partition and this is an ignorable
+ error.
+ HA_ERR_FOUND_DUP_UNIQUE is a special case in MyISAM that means the
+ same thing as HA_ERR_FOUND_DUP_KEY but can in some cases lead to
+ a slightly different error message.
+ */
+ virtual bool is_fatal_error(int error, uint flags)
+ {
+ if (!error ||
+ ((flags & HA_CHECK_DUP_KEY) &&
+ (error == HA_ERR_FOUND_DUPP_KEY ||
+ error == HA_ERR_FOUND_DUPP_UNIQUE)) ||
+ error == HA_ERR_AUTOINC_ERANGE)
+ return FALSE;
+ return TRUE;
+ }
+
+ /**
+ Number of rows in table. It will only be called if
+ (table_flags() & (HA_HAS_RECORDS | HA_STATS_RECORDS_IS_EXACT)) != 0
+ */
+ virtual ha_rows records() { return stats.records; }
+ /**
+ Return upper bound of current number of records in the table
+ (max. of how many records one will retrieve when doing a full table scan)
+ If upper bound is not known, HA_POS_ERROR should be returned as a max
+ possible upper bound.
+ */
+ virtual ha_rows estimate_rows_upper_bound()
+ { return stats.records+EXTRA_RECORDS; }
+
+ /**
+ Get the row type from the storage engine. If this method returns
+ ROW_TYPE_NOT_USED, the information in HA_CREATE_INFO should be used.
+ */
+ virtual enum row_type get_row_type() const { return ROW_TYPE_NOT_USED; }
+
+ virtual const char *index_type(uint key_number) { DBUG_ASSERT(0); return "";}
+
+
+ /**
+ Signal that the table->read_set and table->write_set table maps changed
+ The handler is allowed to set additional bits in the above map in this
+ call. Normally the handler should ignore all calls until we have done
+ a ha_rnd_init() or ha_index_init(), write_row(), update_row or delete_row()
+ as there may be several calls to this routine.
+ */
+ virtual void column_bitmaps_signal();
+ /*
+ We have to check for inited as some engines, like innodb, sets
+ active_index during table scan.
+ */
+ uint get_index(void) const
+ { return inited == INDEX ? active_index : MAX_KEY; }
+ int ha_close(void);
+
+ /**
+ @retval 0 Bulk update used by handler
+ @retval 1 Bulk update not used, normal operation used
+ */
+ virtual bool start_bulk_update() { return 1; }
+ /**
+ @retval 0 Bulk delete used by handler
+ @retval 1 Bulk delete not used, normal operation used
+ */
+ virtual bool start_bulk_delete() { return 1; }
+ /**
+ After this call all outstanding updates must be performed. The number
+ of duplicate key errors are reported in the duplicate key parameter.
+ It is allowed to continue to the batched update after this call, the
+ handler has to wait until end_bulk_update with changing state.
+
+ @param dup_key_found Number of duplicate keys found
+
+ @retval 0 Success
+ @retval >0 Error code
+ */
+ virtual int exec_bulk_update(uint *dup_key_found)
+ {
+ DBUG_ASSERT(FALSE);
+ return HA_ERR_WRONG_COMMAND;
+ }
+ /**
+ Perform any needed clean-up, no outstanding updates are there at the
+ moment.
+ */
+ virtual void end_bulk_update() { return; }
+ /**
+ Execute all outstanding deletes and close down the bulk delete.
+
+ @retval 0 Success
+ @retval >0 Error code
+ */
+ virtual int end_bulk_delete()
+ {
+ DBUG_ASSERT(FALSE);
+ return HA_ERR_WRONG_COMMAND;
+ }
+ /**
+ @brief
+ Positions an index cursor to the index specified in the
+ handle. Fetches the row if available. If the key value is null,
+ begin at the first key of the index.
+ */
+protected:
+ virtual int index_read_map(uchar * buf, const uchar * key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag)
+ {
+ uint key_len= calculate_key_len(table, active_index, key, keypart_map);
+ return index_read(buf, key, key_len, find_flag);
+ }
+ /**
+ @brief
+ Positions an index cursor to the index specified in the
+ handle. Fetches the row if available. If the key value is null,
+ begin at the first key of the index.
+ */
+ virtual int index_read_idx_map(uchar * buf, uint index, const uchar * key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag);
+ virtual int index_next(uchar * buf)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int index_prev(uchar * buf)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int index_first(uchar * buf)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int index_last(uchar * buf)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int index_next_same(uchar *buf, const uchar *key, uint keylen);
+ virtual int close(void)=0;
+ inline void update_rows_read()
+ {
+ if (likely(!internal_tmp_table))
+ rows_read++;
+ else
+ rows_tmp_read++;
+ }
+ inline void update_index_statistics()
+ {
+ index_rows_read[active_index]++;
+ update_rows_read();
+ }
+public:
+
+ int ha_index_read_map(uchar * buf, const uchar * key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag);
+ int ha_index_read_idx_map(uchar * buf, uint index, const uchar * key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag);
+ int ha_index_next(uchar * buf);
+ int ha_index_prev(uchar * buf);
+ int ha_index_first(uchar * buf);
+ int ha_index_last(uchar * buf);
+ int ha_index_next_same(uchar *buf, const uchar *key, uint keylen);
+ /*
+ TODO: should we make for those functions non-virtual ha_func_name wrappers,
+ too?
+ */
+ virtual ha_rows multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq,
+ void *seq_init_param,
+ uint n_ranges, uint *bufsz,
+ uint *mrr_mode,
+ Cost_estimate *cost);
+ virtual ha_rows multi_range_read_info(uint keyno, uint n_ranges, uint keys,
+ uint key_parts, uint *bufsz,
+ uint *mrr_mode, Cost_estimate *cost);
+ virtual int multi_range_read_init(RANGE_SEQ_IF *seq, void *seq_init_param,
+ uint n_ranges, uint mrr_mode,
+ HANDLER_BUFFER *buf);
+ virtual int multi_range_read_next(range_id_t *range_info);
+ /*
+ Return string representation of the MRR plan.
+
+ This is intended to be used for EXPLAIN, via the following scenario:
+ 1. SQL layer calls handler->multi_range_read_info().
+ 1.1. Storage engine figures out whether it will use some non-default
+ MRR strategy, sets appropritate bits in *mrr_mode, and returns
+ control to SQL layer
+ 2. SQL layer remembers the returned mrr_mode
+ 3. SQL layer compares various options and choses the final query plan. As
+ a part of that, it makes a choice of whether to use the MRR strategy
+ picked in 1.1
+ 4. EXPLAIN code converts the query plan to its text representation. If MRR
+ strategy is part of the plan, it calls
+ multi_range_read_explain_info(mrr_mode) to get a text representation of
+ the picked MRR strategy.
+
+ @param mrr_mode Mode which was returned by multi_range_read_info[_const]
+ @param str INOUT string to be printed for EXPLAIN
+ @param str_end End of the string buffer. The function is free to put the
+ string into [str..str_end] memory range.
+ */
+ virtual int multi_range_read_explain_info(uint mrr_mode, char *str,
+ size_t size)
+ { return 0; }
+
+ virtual int read_range_first(const key_range *start_key,
+ const key_range *end_key,
+ bool eq_range, bool sorted);
+ virtual int read_range_next();
+ void set_end_range(const key_range *end_key);
+ int compare_key(key_range *range);
+ int compare_key2(key_range *range);
+ virtual int ft_init() { return HA_ERR_WRONG_COMMAND; }
+ void ft_end() { ft_handler=NULL; }
+ virtual FT_INFO *ft_init_ext(uint flags, uint inx,String *key)
+ { return NULL; }
+private:
+ virtual int ft_read(uchar *buf) { return HA_ERR_WRONG_COMMAND; }
+ virtual int rnd_next(uchar *buf)=0;
+ virtual int rnd_pos(uchar * buf, uchar *pos)=0;
+ /**
+ This function only works for handlers having
+ HA_PRIMARY_KEY_REQUIRED_FOR_POSITION set.
+ It will return the row with the PK given in the record argument.
+ */
+ virtual int rnd_pos_by_record(uchar *record)
+ {
+ DBUG_ASSERT(table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION);
+ position(record);
+ return rnd_pos(record, ref);
+ }
+ virtual int read_first_row(uchar *buf, uint primary_key);
+public:
+
+ /* Same as above, but with statistics */
+ inline int ha_ft_read(uchar *buf);
+ int ha_rnd_next(uchar *buf);
+ int ha_rnd_pos(uchar *buf, uchar *pos);
+ inline int ha_rnd_pos_by_record(uchar *buf);
+ inline int ha_read_first_row(uchar *buf, uint primary_key);
+
+ /**
+ The following 3 function is only needed for tables that may be
+ internal temporary tables during joins.
+ */
+ virtual int remember_rnd_pos()
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int restart_rnd_next(uchar *buf)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int rnd_same(uchar *buf, uint inx)
+ { return HA_ERR_WRONG_COMMAND; }
+
+ virtual ha_rows records_in_range(uint inx, key_range *min_key,
+ key_range *max_key)
+ { return (ha_rows) 10; }
+ /*
+ If HA_PRIMARY_KEY_REQUIRED_FOR_POSITION is set, then it sets ref
+ (reference to the row, aka position, with the primary key given in
+ the record).
+ Otherwise it set ref to the current row.
+ */
+ virtual void position(const uchar *record)=0;
+ virtual int info(uint)=0; // see my_base.h for full description
+ virtual void get_dynamic_partition_info(PARTITION_STATS *stat_info,
+ uint part_id);
+ virtual int extra(enum ha_extra_function operation)
+ { return 0; }
+ virtual int extra_opt(enum ha_extra_function operation, ulong cache_size)
+ { return extra(operation); }
+
+ /**
+ In an UPDATE or DELETE, if the row under the cursor was locked by another
+ transaction, and the engine used an optimistic read of the last
+ committed row value under the cursor, then the engine returns 1 from this
+ function. MySQL must NOT try to update this optimistic value. If the
+ optimistic value does not match the WHERE condition, MySQL can decide to
+ skip over this row. Currently only works for InnoDB. This can be used to
+ avoid unnecessary lock waits.
+
+ If this method returns nonzero, it will also signal the storage
+ engine that the next read will be a locking re-read of the row.
+ */
+ virtual bool was_semi_consistent_read() { return 0; }
+ /**
+ Tell the engine whether it should avoid unnecessary lock waits.
+ If yes, in an UPDATE or DELETE, if the row under the cursor was locked
+ by another transaction, the engine may try an optimistic read of
+ the last committed row value under the cursor.
+ */
+ virtual void try_semi_consistent_read(bool) {}
+ virtual void unlock_row() {}
+ virtual int start_stmt(THD *thd, thr_lock_type lock_type) {return 0;}
+ virtual void get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *first_value,
+ ulonglong *nb_reserved_values);
+ void set_next_insert_id(ulonglong id)
+ {
+ DBUG_PRINT("info",("auto_increment: next value %lu", (ulong)id));
+ next_insert_id= id;
+ }
+ void restore_auto_increment(ulonglong prev_insert_id)
+ {
+ /*
+ Insertion of a row failed, re-use the lastly generated auto_increment
+ id, for the next row. This is achieved by resetting next_insert_id to
+ what it was before the failed insertion (that old value is provided by
+ the caller). If that value was 0, it was the first row of the INSERT;
+ then if insert_id_for_cur_row contains 0 it means no id was generated
+ for this first row, so no id was generated since the INSERT started, so
+ we should set next_insert_id to 0; if insert_id_for_cur_row is not 0, it
+ is the generated id of the first and failed row, so we use it.
+ */
+ next_insert_id= (prev_insert_id > 0) ? prev_insert_id :
+ insert_id_for_cur_row;
+ }
+
+ virtual void update_create_info(HA_CREATE_INFO *create_info) {}
+ int check_old_types();
+ virtual int assign_to_keycache(THD* thd, HA_CHECK_OPT* check_opt)
+ { return HA_ADMIN_NOT_IMPLEMENTED; }
+ virtual int preload_keys(THD* thd, HA_CHECK_OPT* check_opt)
+ { return HA_ADMIN_NOT_IMPLEMENTED; }
+ /* end of the list of admin commands */
+
+ virtual int indexes_are_disabled(void) {return 0;}
+ virtual char *update_table_comment(const char * comment)
+ { return (char*) comment;}
+ virtual void append_create_info(String *packet) {}
+ /**
+ If index == MAX_KEY then a check for table is made and if index <
+ MAX_KEY then a check is made if the table has foreign keys and if
+ a foreign key uses this index (and thus the index cannot be dropped).
+
+ @param index Index to check if foreign key uses it
+
+ @retval TRUE Foreign key defined on table or index
+ @retval FALSE No foreign key defined
+ */
+ virtual bool is_fk_defined_on_table_or_index(uint index)
+ { return FALSE; }
+ virtual char* get_foreign_key_create_info()
+ { return(NULL);} /* gets foreign key create string from InnoDB */
+ /**
+ Used in ALTER TABLE to check if changing storage engine is allowed.
+
+ @note Called without holding thr_lock.c lock.
+
+ @retval true Changing storage engine is allowed.
+ @retval false Changing storage engine not allowed.
+ */
+ virtual bool can_switch_engines() { return true; }
+ virtual int can_continue_handler_scan() { return 0; }
+ /**
+ Get the list of foreign keys in this table.
+
+ @remark Returns the set of foreign keys where this table is the
+ dependent or child table.
+
+ @param thd The thread handle.
+ @param f_key_list[out] The list of foreign keys.
+
+ @return The handler error code or zero for success.
+ */
+ virtual int
+ get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list)
+ { return 0; }
+ /**
+ Get the list of foreign keys referencing this table.
+
+ @remark Returns the set of foreign keys where this table is the
+ referenced or parent table.
+
+ @param thd The thread handle.
+ @param f_key_list[out] The list of foreign keys.
+
+ @return The handler error code or zero for success.
+ */
+ virtual int
+ get_parent_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list)
+ { return 0; }
+ virtual uint referenced_by_foreign_key() { return 0;}
+ virtual void init_table_handle_for_HANDLER()
+ { return; } /* prepare InnoDB for HANDLER */
+ virtual void free_foreign_key_create_info(char* str) {}
+ /** The following can be called without an open handler */
+ const char *table_type() const { return hton_name(ht)->str; }
+ const char **bas_ext() const { return ht->tablefile_extensions; }
+
+ virtual int get_default_no_partitions(HA_CREATE_INFO *create_info)
+ { return 1;}
+ virtual void set_auto_partitions(partition_info *part_info) { return; }
+ virtual bool get_no_parts(const char *name,
+ uint *no_parts)
+ {
+ *no_parts= 0;
+ return 0;
+ }
+ virtual void set_part_info(partition_info *part_info) {return;}
+
+ virtual ulong index_flags(uint idx, uint part, bool all_parts) const =0;
+
+ uint max_record_length() const
+ { return MY_MIN(HA_MAX_REC_LENGTH, max_supported_record_length()); }
+ uint max_keys() const
+ { return MY_MIN(MAX_KEY, max_supported_keys()); }
+ uint max_key_parts() const
+ { return MY_MIN(MAX_REF_PARTS, max_supported_key_parts()); }
+ uint max_key_length() const
+ { return MY_MIN(MAX_KEY_LENGTH, max_supported_key_length()); }
+ uint max_key_part_length() const
+ { return MY_MIN(MAX_KEY_LENGTH, max_supported_key_part_length()); }
+
+ virtual uint max_supported_record_length() const { return HA_MAX_REC_LENGTH; }
+ virtual uint max_supported_keys() const { return 0; }
+ virtual uint max_supported_key_parts() const { return MAX_REF_PARTS; }
+ virtual uint max_supported_key_length() const { return MAX_KEY_LENGTH; }
+ virtual uint max_supported_key_part_length() const { return 255; }
+ virtual uint min_record_length(uint options) const { return 1; }
+
+ virtual uint checksum() const { return 0; }
+ virtual bool is_crashed() const { return 0; }
+ virtual bool auto_repair(int error) const { return 0; }
+
+ void update_global_table_stats();
+ void update_global_index_stats();
+
+#define CHF_CREATE_FLAG 0
+#define CHF_DELETE_FLAG 1
+#define CHF_RENAME_FLAG 2
+#define CHF_INDEX_FLAG 3
+
+ /**
+ @note lock_count() can return > 1 if the table is MERGE or partitioned.
+ */
+ virtual uint lock_count(void) const { return 1; }
+ /**
+ Is not invoked for non-transactional temporary tables.
+
+ @note store_lock() can return more than one lock if the table is MERGE
+ or partitioned.
+
+ @note that one can NOT rely on table->in_use in store_lock(). It may
+ refer to a different thread if called from mysql_lock_abort_for_thread().
+
+ @note If the table is MERGE, store_lock() can return less locks
+ than lock_count() claimed. This can happen when the MERGE children
+ are not attached when this is called from another thread.
+ */
+ virtual THR_LOCK_DATA **store_lock(THD *thd,
+ THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type)=0;
+
+ /** Type of table for caching query */
+ virtual uint8 table_cache_type() { return HA_CACHE_TBL_NONTRANSACT; }
+
+
+ /**
+ @brief Register a named table with a call back function to the query cache.
+
+ @param thd The thread handle
+ @param table_key A pointer to the table name in the table cache
+ @param key_length The length of the table name
+ @param[out] engine_callback The pointer to the storage engine call back
+ function
+ @param[out] engine_data Storage engine specific data which could be
+ anything
+
+ This method offers the storage engine, the possibility to store a reference
+ to a table name which is going to be used with query cache.
+ The method is called each time a statement is written to the cache and can
+ be used to verify if a specific statement is cachable. It also offers
+ the possibility to register a generic (but static) call back function which
+ is called each time a statement is matched against the query cache.
+
+ @note If engine_data supplied with this function is different from
+ engine_data supplied with the callback function, and the callback returns
+ FALSE, a table invalidation on the current table will occur.
+
+ @return Upon success the engine_callback will point to the storage engine
+ call back function, if any, and engine_data will point to any storage
+ engine data used in the specific implementation.
+ @retval TRUE Success
+ @retval FALSE The specified table or current statement should not be
+ cached
+ */
+
+ virtual my_bool register_query_cache_table(THD *thd, char *table_key,
+ uint key_length,
+ qc_engine_callback
+ *engine_callback,
+ ulonglong *engine_data)
+ {
+ *engine_callback= 0;
+ return TRUE;
+ }
+
+ /*
+ Count tables invisible from all tables list on which current one built
+ (like myisammrg and partitioned tables)
+
+ tables_type mask for the tables should be added herdde
+
+ returns number of such tables
+ */
+
+ virtual uint count_query_cache_dependant_tables(uint8 *tables_type
+ __attribute__((unused)))
+ {
+ return 0;
+ }
+
+ /*
+ register tables invisible from all tables list on which current one built
+ (like myisammrg and partitioned tables).
+
+ @note they should be counted by method above
+
+ cache Query cache pointer
+ block Query cache block to write the table
+ n Number of the table
+
+ @retval FALSE - OK
+ @retval TRUE - Error
+ */
+
+ virtual my_bool
+ register_query_cache_dependant_tables(THD *thd
+ __attribute__((unused)),
+ Query_cache *cache
+ __attribute__((unused)),
+ Query_cache_block_table **block
+ __attribute__((unused)),
+ uint *n __attribute__((unused)))
+ {
+ return FALSE;
+ }
+
+ /*
+ Check if the primary key (if there is one) is a clustered and a
+ reference key. This means:
+
+ - Data is stored together with the primary key (no secondary lookup
+ needed to find the row data). The optimizer uses this to find out
+ the cost of fetching data.
+ - The primary key is part of each secondary key and is used
+ to find the row data in the primary index when reading trough
+ secondary indexes.
+ - When doing a HA_KEYREAD_ONLY we get also all the primary key parts
+ into the row. This is critical property used by index_merge.
+
+ All the above is usually true for engines that store the row
+ data in the primary key index (e.g. in a b-tree), and use the primary
+ key value as a position(). InnoDB is an example of such an engine.
+
+ For such a clustered primary key, the following should also hold:
+ index_flags() should contain HA_CLUSTERED_INDEX
+ table_flags() should contain HA_TABLE_SCAN_ON_INDEX
+
+ @retval TRUE yes
+ @retval FALSE No.
+ */
+ virtual bool primary_key_is_clustered() { return FALSE; }
+ virtual int cmp_ref(const uchar *ref1, const uchar *ref2)
+ {
+ return memcmp(ref1, ref2, ref_length);
+ }
+
+ /*
+ Condition pushdown to storage engines
+ */
+
+ /**
+ Push condition down to the table handler.
+
+ @param cond Condition to be pushed. The condition tree must not be
+ modified by the by the caller.
+
+ @return
+ The 'remainder' condition that caller must use to filter out records.
+ NULL means the handler will not return rows that do not match the
+ passed condition.
+
+ @note
+ The pushed conditions form a stack (from which one can remove the
+ last pushed condition using cond_pop).
+ The table handler filters out rows using (pushed_cond1 AND pushed_cond2
+ AND ... AND pushed_condN)
+ or less restrictive condition, depending on handler's capabilities.
+
+ handler->ha_reset() call empties the condition stack.
+ Calls to rnd_init/rnd_end, index_init/index_end etc do not affect the
+ condition stack.
+ */
+ virtual const COND *cond_push(const COND *cond) { return cond; };
+ /**
+ Pop the top condition from the condition stack of the handler instance.
+
+ Pops the top if condition stack, if stack is not empty.
+ */
+ virtual void cond_pop() { return; };
+
+ /**
+ Push down an index condition to the handler.
+
+ The server will use this method to push down a condition it wants
+ the handler to evaluate when retrieving records using a specified
+ index. The pushed index condition will only refer to fields from
+ this handler that is contained in the index (but it may also refer
+ to fields in other handlers). Before the handler evaluates the
+ condition it must read the content of the index entry into the
+ record buffer.
+
+ The handler is free to decide if and how much of the condition it
+ will take responsibility for evaluating. Based on this evaluation
+ it should return the part of the condition it will not evaluate.
+ If it decides to evaluate the entire condition it should return
+ NULL. If it decides not to evaluate any part of the condition it
+ should return a pointer to the same condition as given as argument.
+
+ @param keyno the index number to evaluate the condition on
+ @param idx_cond the condition to be evaluated by the handler
+
+ @return The part of the pushed condition that the handler decides
+ not to evaluate
+ */
+ virtual Item *idx_cond_push(uint keyno, Item* idx_cond) { return idx_cond; }
+
+ /** Reset information about pushed index conditions */
+ virtual void cancel_pushed_idx_cond()
+ {
+ pushed_idx_cond= NULL;
+ pushed_idx_cond_keyno= MAX_KEY;
+ in_range_check_pushed_down= false;
+ }
+ /**
+ Part of old, deprecated in-place ALTER API.
+ */
+ virtual bool check_if_incompatible_data(HA_CREATE_INFO *create_info,
+ uint table_changes)
+ { return COMPATIBLE_DATA_NO; }
+
+ /* On-line/in-place ALTER TABLE interface. */
+
+ /*
+ Here is an outline of on-line/in-place ALTER TABLE execution through
+ this interface.
+
+ Phase 1 : Initialization
+ ========================
+ During this phase we determine which algorithm should be used
+ for execution of ALTER TABLE and what level concurrency it will
+ require.
+
+ *) This phase starts by opening the table and preparing description
+ of the new version of the table.
+ *) Then we check if it is impossible even in theory to carry out
+ this ALTER TABLE using the in-place algorithm. For example, because
+ we need to change storage engine or the user has explicitly requested
+ usage of the "copy" algorithm.
+ *) If in-place ALTER TABLE is theoretically possible, we continue
+ by compiling differences between old and new versions of the table
+ in the form of HA_ALTER_FLAGS bitmap. We also build a few
+ auxiliary structures describing requested changes and store
+ all these data in the Alter_inplace_info object.
+ *) Then the handler::check_if_supported_inplace_alter() method is called
+ in order to find if the storage engine can carry out changes requested
+ by this ALTER TABLE using the in-place algorithm. To determine this,
+ the engine can rely on data in HA_ALTER_FLAGS/Alter_inplace_info
+ passed to it as well as on its own checks. If the in-place algorithm
+ can be used for this ALTER TABLE, the level of required concurrency for
+ its execution is also returned.
+ If any errors occur during the handler call, ALTER TABLE is aborted
+ and no further handler functions are called.
+ *) Locking requirements of the in-place algorithm are compared to any
+ concurrency requirements specified by user. If there is a conflict
+ between them, we either switch to the copy algorithm or emit an error.
+
+ Phase 2 : Execution
+ ===================
+
+ In this phase the operations are executed.
+
+ *) As the first step, we acquire a lock corresponding to the concurrency
+ level which was returned by handler::check_if_supported_inplace_alter()
+ and requested by the user. This lock is held for most of the
+ duration of in-place ALTER (if HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE
+ or HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE were returned we acquire an
+ exclusive lock for duration of the next step only).
+ *) After that we call handler::ha_prepare_inplace_alter_table() to give the
+ storage engine a chance to update its internal structures with a higher
+ lock level than the one that will be used for the main step of algorithm.
+ After that we downgrade the lock if it is necessary.
+ *) After that, the main step of this phase and algorithm is executed.
+ We call the handler::ha_inplace_alter_table() method, which carries out the
+ changes requested by ALTER TABLE but does not makes them visible to other
+ connections yet.
+ *) We ensure that no other connection uses the table by upgrading our
+ lock on it to exclusive.
+ *) a) If the previous step succeeds, handler::ha_commit_inplace_alter_table() is
+ called to allow the storage engine to do any final updates to its structures,
+ to make all earlier changes durable and visible to other connections.
+ b) If we have failed to upgrade lock or any errors have occured during the
+ handler functions calls (including commit), we call
+ handler::ha_commit_inplace_alter_table()
+ to rollback all changes which were done during previous steps.
+
+ Phase 3 : Final
+ ===============
+
+ In this phase we:
+
+ *) Update SQL-layer data-dictionary by installing .FRM file for the new version
+ of the table.
+ *) Inform the storage engine about this change by calling the
+ handler::ha_notify_table_changed() method.
+ *) Destroy the Alter_inplace_info and handler_ctx objects.
+
+ */
+
+ /**
+ Check if a storage engine supports a particular alter table in-place
+
+ @param altered_table TABLE object for new version of table.
+ @param ha_alter_info Structure describing changes to be done
+ by ALTER TABLE and holding data used
+ during in-place alter.
+
+ @retval HA_ALTER_ERROR Unexpected error.
+ @retval HA_ALTER_INPLACE_NOT_SUPPORTED Not supported, must use copy.
+ @retval HA_ALTER_INPLACE_EXCLUSIVE_LOCK Supported, but requires X lock.
+ @retval HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE
+ Supported, but requires SNW lock
+ during main phase. Prepare phase
+ requires X lock.
+ @retval HA_ALTER_INPLACE_SHARED_LOCK Supported, but requires SNW lock.
+ @retval HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE
+ Supported, concurrent reads/writes
+ allowed. However, prepare phase
+ requires X lock.
+ @retval HA_ALTER_INPLACE_NO_LOCK Supported, concurrent
+ reads/writes allowed.
+
+ @note The default implementation uses the old in-place ALTER API
+ to determine if the storage engine supports in-place ALTER or not.
+
+ @note Called without holding thr_lock.c lock.
+ */
+ virtual enum_alter_inplace_result
+ check_if_supported_inplace_alter(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info);
+
+
+ /**
+ Public functions wrapping the actual handler call.
+ @see prepare_inplace_alter_table()
+ */
+ bool ha_prepare_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info);
+
+
+ /**
+ Public function wrapping the actual handler call.
+ @see inplace_alter_table()
+ */
+ bool ha_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+ {
+ return inplace_alter_table(altered_table, ha_alter_info);
+ }
+
+
+ /**
+ Public function wrapping the actual handler call.
+ Allows us to enforce asserts regardless of handler implementation.
+ @see commit_inplace_alter_table()
+ */
+ bool ha_commit_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info,
+ bool commit);
+
+
+ /**
+ Public function wrapping the actual handler call.
+ @see notify_table_changed()
+ */
+ void ha_notify_table_changed()
+ {
+ notify_table_changed();
+ }
+
+
+protected:
+ /**
+ Allows the storage engine to update internal structures with concurrent
+ writes blocked. If check_if_supported_inplace_alter() returns
+ HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE or
+ HA_ALTER_INPLACE_SHARED_AFTER_PREPARE, this function is called with
+ exclusive lock otherwise the same level of locking as for
+ inplace_alter_table() will be used.
+
+ @note Storage engines are responsible for reporting any errors by
+ calling my_error()/print_error()
+
+ @note If this function reports error, commit_inplace_alter_table()
+ will be called with commit= false.
+
+ @note For partitioning, failing to prepare one partition, means that
+ commit_inplace_alter_table() will be called to roll back changes for
+ all partitions. This means that commit_inplace_alter_table() might be
+ called without prepare_inplace_alter_table() having been called first
+ for a given partition.
+
+ @param altered_table TABLE object for new version of table.
+ @param ha_alter_info Structure describing changes to be done
+ by ALTER TABLE and holding data used
+ during in-place alter.
+
+ @retval true Error
+ @retval false Success
+ */
+ virtual bool prepare_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+ { return false; }
+
+
+ /**
+ Alter the table structure in-place with operations specified using HA_ALTER_FLAGS
+ and Alter_inplace_info. The level of concurrency allowed during this
+ operation depends on the return value from check_if_supported_inplace_alter().
+
+ @note Storage engines are responsible for reporting any errors by
+ calling my_error()/print_error()
+
+ @note If this function reports error, commit_inplace_alter_table()
+ will be called with commit= false.
+
+ @param altered_table TABLE object for new version of table.
+ @param ha_alter_info Structure describing changes to be done
+ by ALTER TABLE and holding data used
+ during in-place alter.
+
+ @retval true Error
+ @retval false Success
+ */
+ virtual bool inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info)
+ { return false; }
+
+
+ /**
+ Commit or rollback the changes made during prepare_inplace_alter_table()
+ and inplace_alter_table() inside the storage engine.
+ Note that in case of rollback the allowed level of concurrency during
+ this operation will be the same as for inplace_alter_table() and thus
+ might be higher than during prepare_inplace_alter_table(). (For example,
+ concurrent writes were blocked during prepare, but might not be during
+ rollback).
+
+ @note Storage engines are responsible for reporting any errors by
+ calling my_error()/print_error()
+
+ @note If this function with commit= true reports error, it will be called
+ again with commit= false.
+
+ @note In case of partitioning, this function might be called for rollback
+ without prepare_inplace_alter_table() having been called first.
+ Also partitioned tables sets ha_alter_info->group_commit_ctx to a NULL
+ terminated array of the partitions handlers and if all of them are
+ committed as one, then group_commit_ctx should be set to NULL to indicate
+ to the partitioning handler that all partitions handlers are committed.
+ @see prepare_inplace_alter_table().
+
+ @param altered_table TABLE object for new version of table.
+ @param ha_alter_info Structure describing changes to be done
+ by ALTER TABLE and holding data used
+ during in-place alter.
+ @param commit True => Commit, False => Rollback.
+
+ @retval true Error
+ @retval false Success
+ */
+ virtual bool commit_inplace_alter_table(TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info,
+ bool commit)
+{
+ /* Nothing to commit/rollback, mark all handlers committed! */
+ ha_alter_info->group_commit_ctx= NULL;
+ return false;
+}
+
+
+ /**
+ Notify the storage engine that the table structure (.FRM) has been updated.
+
+ @note No errors are allowed during notify_table_changed().
+ */
+ virtual void notify_table_changed();
+
+public:
+ /* End of On-line/in-place ALTER TABLE interface. */
+
+
+ /**
+ use_hidden_primary_key() is called in case of an update/delete when
+ (table_flags() and HA_PRIMARY_KEY_REQUIRED_FOR_DELETE) is defined
+ but we don't have a primary key
+ */
+ virtual void use_hidden_primary_key();
+ virtual uint alter_table_flags(uint flags)
+ {
+ if (ht->alter_table_flags)
+ return ht->alter_table_flags(flags);
+ return 0;
+ }
+
+ LEX_STRING *engine_name() { return hton_name(ht); }
+
+ /*
+ @brief
+ Check whether the engine supports virtual columns
+
+ @retval
+ FALSE if the engine does not support virtual columns
+ @retval
+ TRUE if the engine supports virtual columns
+ */
+
+ virtual bool check_if_supported_virtual_columns(void) { return FALSE;}
+
+ TABLE* get_table() { return table; }
+ TABLE_SHARE* get_table_share() { return table_share; }
+protected:
+ /* deprecated, don't use in new engines */
+ inline void ha_statistic_increment(ulong SSV::*offset) const { }
+
+ /* Service methods for use by storage engines. */
+ void **ha_data(THD *) const;
+ THD *ha_thd(void) const;
+
+ /**
+ Acquire the instrumented table information from a table share.
+ @return an instrumented table share, or NULL.
+ */
+ PSI_table_share *ha_table_share_psi() const;
+
+ /**
+ Default rename_table() and delete_table() rename/delete files with a
+ given name and extensions from bas_ext().
+
+ These methods can be overridden, but their default implementation
+ provide useful functionality.
+ */
+ virtual int rename_table(const char *from, const char *to);
+ /**
+ Delete a table in the engine. Called for base as well as temporary
+ tables.
+ */
+ virtual int delete_table(const char *name);
+
+private:
+ /* Private helpers */
+ inline void mark_trx_read_write();
+private:
+ inline void increment_statistics(ulong SSV::*offset) const;
+ inline void decrement_statistics(ulong SSV::*offset) const;
+
+ /*
+ Low-level primitives for storage engines. These should be
+ overridden by the storage engine class. To call these methods, use
+ the corresponding 'ha_*' method above.
+ */
+
+ virtual int open(const char *name, int mode, uint test_if_locked)=0;
+ /* Note: ha_index_read_idx_map() may bypass index_init() */
+ virtual int index_init(uint idx, bool sorted) { return 0; }
+ virtual int index_end() { return 0; }
+ /**
+ rnd_init() can be called two times without rnd_end() in between
+ (it only makes sense if scan=1).
+ then the second call should prepare for the new table scan (e.g
+ if rnd_init allocates the cursor, second call should position it
+ to the start of the table, no need to deallocate and allocate it again
+ */
+ virtual int rnd_init(bool scan)= 0;
+ virtual int rnd_end() { return 0; }
+ virtual int write_row(uchar *buf __attribute__((unused)))
+ {
+ return HA_ERR_WRONG_COMMAND;
+ }
+
+ /**
+ Update a single row.
+
+ Note: If HA_ERR_FOUND_DUPP_KEY is returned, the handler must read
+ all columns of the row so MySQL can create an error message. If
+ the columns required for the error message are not read, the error
+ message will contain garbage.
+ */
+ virtual int update_row(const uchar *old_data __attribute__((unused)),
+ uchar *new_data __attribute__((unused)))
+ {
+ return HA_ERR_WRONG_COMMAND;
+ }
+
+ virtual int delete_row(const uchar *buf __attribute__((unused)))
+ {
+ return HA_ERR_WRONG_COMMAND;
+ }
+ /**
+ Reset state of file to after 'open'.
+ This function is called after every statement for all tables used
+ by that statement.
+ */
+ virtual int reset() { return 0; }
+ virtual Table_flags table_flags(void) const= 0;
+ /**
+ Is not invoked for non-transactional temporary tables.
+
+ Tells the storage engine that we intend to read or write data
+ from the table. This call is prefixed with a call to handler::store_lock()
+ and is invoked only for those handler instances that stored the lock.
+
+ Calls to rnd_init/index_init are prefixed with this call. When table
+ IO is complete, we call external_lock(F_UNLCK).
+ A storage engine writer should expect that each call to
+ ::external_lock(F_[RD|WR]LOCK is followed by a call to
+ ::external_lock(F_UNLCK). If it is not, it is a bug in MySQL.
+
+ The name and signature originate from the first implementation
+ in MyISAM, which would call fcntl to set/clear an advisory
+ lock on the data file in this method.
+
+ @param lock_type F_RDLCK, F_WRLCK, F_UNLCK
+
+ @return non-0 in case of failure, 0 in case of success.
+ When lock_type is F_UNLCK, the return value is ignored.
+ */
+ virtual int external_lock(THD *thd __attribute__((unused)),
+ int lock_type __attribute__((unused)))
+ {
+ return 0;
+ }
+ virtual void release_auto_increment() { return; };
+ /** admin commands - called from mysql_admin_table */
+ virtual int check_for_upgrade(HA_CHECK_OPT *check_opt)
+ { return 0; }
+ virtual int check(THD* thd, HA_CHECK_OPT* check_opt)
+ { return HA_ADMIN_NOT_IMPLEMENTED; }
+
+ /**
+ In this method check_opt can be modified
+ to specify CHECK option to use to call check()
+ upon the table.
+ */
+ virtual int repair(THD* thd, HA_CHECK_OPT* check_opt)
+ {
+ DBUG_ASSERT(!(ha_table_flags() & HA_CAN_REPAIR));
+ return HA_ADMIN_NOT_IMPLEMENTED;
+ }
+ virtual void start_bulk_insert(ha_rows rows, uint flags) {}
+ virtual int end_bulk_insert() { return 0; }
+protected:
+ virtual int index_read(uchar * buf, const uchar * key, uint key_len,
+ enum ha_rkey_function find_flag)
+ { return HA_ERR_WRONG_COMMAND; }
+ friend class ha_partition;
+public:
+ /**
+ This method is similar to update_row, however the handler doesn't need
+ to execute the updates at this point in time. The handler can be certain
+ that another call to bulk_update_row will occur OR a call to
+ exec_bulk_update before the set of updates in this query is concluded.
+
+ @param old_data Old record
+ @param new_data New record
+ @param dup_key_found Number of duplicate keys found
+
+ @retval 0 Bulk delete used by handler
+ @retval 1 Bulk delete not used, normal operation used
+ */
+ virtual int bulk_update_row(const uchar *old_data, uchar *new_data,
+ uint *dup_key_found)
+ {
+ DBUG_ASSERT(FALSE);
+ return HA_ERR_WRONG_COMMAND;
+ }
+ /**
+ This is called to delete all rows in a table
+ If the handler don't support this, then this function will
+ return HA_ERR_WRONG_COMMAND and MySQL will delete the rows one
+ by one.
+ */
+ virtual int delete_all_rows()
+ { return (my_errno=HA_ERR_WRONG_COMMAND); }
+ /**
+ Quickly remove all rows from a table.
+
+ @remark This method is responsible for implementing MySQL's TRUNCATE
+ TABLE statement, which is a DDL operation. As such, a engine
+ can bypass certain integrity checks and in some cases avoid
+ fine-grained locking (e.g. row locks) which would normally be
+ required for a DELETE statement.
+
+ @remark Typically, truncate is not used if it can result in integrity
+ violation. For example, truncate is not used when a foreign
+ key references the table, but it might be used if foreign key
+ checks are disabled.
+
+ @remark Engine is responsible for resetting the auto-increment counter.
+
+ @remark The table is locked in exclusive mode.
+ */
+ virtual int truncate()
+ {
+ int error= delete_all_rows();
+ return error ? error : reset_auto_increment(0);
+ }
+ /**
+ Reset the auto-increment counter to the given value, i.e. the next row
+ inserted will get the given value.
+ */
+ virtual int reset_auto_increment(ulonglong value)
+ { return 0; }
+ virtual int optimize(THD* thd, HA_CHECK_OPT* check_opt)
+ { return HA_ADMIN_NOT_IMPLEMENTED; }
+ virtual int analyze(THD* thd, HA_CHECK_OPT* check_opt)
+ { return HA_ADMIN_NOT_IMPLEMENTED; }
+ virtual bool check_and_repair(THD *thd) { return TRUE; }
+ virtual int disable_indexes(uint mode) { return HA_ERR_WRONG_COMMAND; }
+ virtual int enable_indexes(uint mode) { return HA_ERR_WRONG_COMMAND; }
+ virtual int discard_or_import_tablespace(my_bool discard)
+ { return (my_errno=HA_ERR_WRONG_COMMAND); }
+ virtual void prepare_for_alter() { return; }
+ virtual void drop_table(const char *name);
+ virtual int create(const char *name, TABLE *form, HA_CREATE_INFO *info)=0;
+
+ virtual int create_partitioning_metadata(const char *name, const char *old_name,
+ int action_flag)
+ { return FALSE; }
+
+ virtual int change_partitions(HA_CREATE_INFO *create_info,
+ const char *path,
+ ulonglong * const copied,
+ ulonglong * const deleted,
+ const uchar *pack_frm_data,
+ size_t pack_frm_len)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int drop_partitions(const char *path)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual int rename_partitions(const char *path)
+ { return HA_ERR_WRONG_COMMAND; }
+ virtual bool set_ha_share_ref(Handler_share **arg_ha_share)
+ {
+ DBUG_ASSERT(!ha_share);
+ DBUG_ASSERT(arg_ha_share);
+ if (ha_share || !arg_ha_share)
+ return true;
+ ha_share= arg_ha_share;
+ return false;
+ }
+ int get_lock_type() const { return m_lock_type; }
+public:
+ /* XXX to be removed, see ha_partition::partition_ht() */
+ virtual handlerton *partition_ht() const
+ { return ht; }
+ inline int ha_write_tmp_row(uchar *buf);
+ inline int ha_update_tmp_row(const uchar * old_data, uchar * new_data);
+
+ virtual void set_lock_type(enum thr_lock_type lock);
+
+ friend enum icp_result handler_index_cond_check(void* h_arg);
+protected:
+ Handler_share *get_ha_share_ptr();
+ void set_ha_share_ptr(Handler_share *arg_ha_share);
+ void lock_shared_ha_data();
+ void unlock_shared_ha_data();
+};
+
+#include "multi_range_read.h"
+
+bool key_uses_partial_cols(TABLE_SHARE *table, uint keyno);
+
+ /* Some extern variables used with handlers */
+
+extern const char *ha_row_type[];
+extern MYSQL_PLUGIN_IMPORT const char *tx_isolation_names[];
+extern MYSQL_PLUGIN_IMPORT const char *binlog_format_names[];
+extern TYPELIB tx_isolation_typelib;
+extern const char *myisam_stats_method_names[];
+extern ulong total_ha, total_ha_2pc;
+
+/* lookups */
+handlerton *ha_default_handlerton(THD *thd);
+plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name);
+plugin_ref ha_lock_engine(THD *thd, const handlerton *hton);
+handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type);
+handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc,
+ handlerton *db_type);
+handlerton *ha_checktype(THD *thd, enum legacy_db_type database_type,
+ bool no_substitute, bool report_error);
+
+
+static inline enum legacy_db_type ha_legacy_type(const handlerton *db_type)
+{
+ return (db_type == NULL) ? DB_TYPE_UNKNOWN : db_type->db_type;
+}
+
+static inline const char *ha_resolve_storage_engine_name(const handlerton *db_type)
+{
+ return db_type == NULL ? "UNKNOWN" : hton_name(db_type)->str;
+}
+
+static inline bool ha_check_storage_engine_flag(const handlerton *db_type, uint32 flag)
+{
+ return db_type == NULL ? FALSE : MY_TEST(db_type->flags & flag);
+}
+
+static inline bool ha_storage_engine_is_enabled(const handlerton *db_type)
+{
+ return (db_type && db_type->create) ?
+ (db_type->state == SHOW_OPTION_YES) : FALSE;
+}
+
+#define view_pseudo_hton ((handlerton *)1)
+
+/* basic stuff */
+int ha_init_errors(void);
+int ha_init(void);
+int ha_end(void);
+int ha_initialize_handlerton(st_plugin_int *plugin);
+int ha_finalize_handlerton(st_plugin_int *plugin);
+
+TYPELIB *ha_known_exts(void);
+int ha_panic(enum ha_panic_function flag);
+void ha_close_connection(THD* thd);
+void ha_kill_query(THD* thd, enum thd_kill_levels level);
+bool ha_flush_logs(handlerton *db_type);
+void ha_drop_database(char* path);
+void ha_checkpoint_state(bool disable);
+void ha_commit_checkpoint_request(void *cookie, void (*pre_hook)(void *));
+int ha_create_table(THD *thd, const char *path,
+ const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info, LEX_CUSTRING *frm);
+int ha_delete_table(THD *thd, handlerton *db_type, const char *path,
+ const char *db, const char *alias, bool generate_warning);
+
+/* statistics and info */
+bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat);
+
+/* discovery */
+#ifdef MYSQL_SERVER
+class Discovered_table_list: public handlerton::discovered_list
+{
+ THD *thd;
+ const char *wild, *wend;
+ bool with_temps; // whether to include temp tables in the result
+public:
+ Dynamic_array<LEX_STRING*> *tables;
+
+ Discovered_table_list(THD *thd_arg, Dynamic_array<LEX_STRING*> *tables_arg,
+ const LEX_STRING *wild_arg);
+ Discovered_table_list(THD *thd_arg, Dynamic_array<LEX_STRING*> *tables_arg)
+ : thd(thd_arg), wild(NULL), with_temps(true), tables(tables_arg) {}
+ ~Discovered_table_list() {}
+
+ bool add_table(const char *tname, size_t tlen);
+ bool add_file(const char *fname);
+
+ void sort();
+ void remove_duplicates(); // assumes that the list is sorted
+};
+
+int ha_discover_table(THD *thd, TABLE_SHARE *share);
+int ha_discover_table_names(THD *thd, LEX_STRING *db, MY_DIR *dirp,
+ Discovered_table_list *result, bool reusable);
+bool ha_table_exists(THD *thd, const char *db, const char *table_name,
+ handlerton **hton= 0);
+#endif
+
+/* key cache */
+extern "C" int ha_init_key_cache(const char *name, KEY_CACHE *key_cache, void *);
+int ha_resize_key_cache(KEY_CACHE *key_cache);
+int ha_change_key_cache_param(KEY_CACHE *key_cache);
+int ha_repartition_key_cache(KEY_CACHE *key_cache);
+int ha_change_key_cache(KEY_CACHE *old_key_cache, KEY_CACHE *new_key_cache);
+
+/* report to InnoDB that control passes to the client */
+int ha_release_temporary_latches(THD *thd);
+
+/* transactions: interface to handlerton functions */
+int ha_start_consistent_snapshot(THD *thd);
+int ha_commit_or_rollback_by_xid(XID *xid, bool commit);
+int ha_commit_one_phase(THD *thd, bool all);
+int ha_commit_trans(THD *thd, bool all);
+int ha_rollback_trans(THD *thd, bool all);
+int ha_prepare(THD *thd);
+int ha_recover(HASH *commit_list);
+
+/* transactions: these functions never call handlerton functions directly */
+int ha_enable_transaction(THD *thd, bool on);
+
+/* savepoints */
+int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv);
+bool ha_rollback_to_savepoint_can_release_mdl(THD *thd);
+int ha_savepoint(THD *thd, SAVEPOINT *sv);
+int ha_release_savepoint(THD *thd, SAVEPOINT *sv);
+
+/* these are called by storage engines */
+void trans_register_ha(THD *thd, bool all, handlerton *ht);
+
+/*
+ Storage engine has to assume the transaction will end up with 2pc if
+ - there is more than one 2pc-capable storage engine available
+ - in the current transaction 2pc was not disabled yet
+*/
+#define trans_need_2pc(thd, all) ((total_ha_2pc > 1) && \
+ !((all ? &thd->transaction.all : &thd->transaction.stmt)->no_2pc))
+
+#ifdef HAVE_NDB_BINLOG
+int ha_reset_logs(THD *thd);
+int ha_binlog_index_purge_file(THD *thd, const char *file);
+void ha_reset_slave(THD *thd);
+void ha_binlog_log_query(THD *thd, handlerton *db_type,
+ enum_binlog_command binlog_command,
+ const char *query, uint query_length,
+ const char *db, const char *table_name);
+void ha_binlog_wait(THD *thd);
+int ha_binlog_end(THD *thd);
+#else
+#define ha_reset_logs(a) do {} while (0)
+#define ha_binlog_index_purge_file(a,b) do {} while (0)
+#define ha_reset_slave(a) do {} while (0)
+#define ha_binlog_log_query(a,b,c,d,e,f,g) do {} while (0)
+#define ha_binlog_wait(a) do {} while (0)
+#define ha_binlog_end(a) do {} while (0)
+#endif
+
+const char *get_canonical_filename(handler *file, const char *path,
+ char *tmp_path);
+bool mysql_xa_recover(THD *thd);
+void commit_checkpoint_notify_ha(handlerton *hton, void *cookie);
+
+inline const char *table_case_name(HA_CREATE_INFO *info, const char *name)
+{
+ return ((lower_case_table_names == 2 && info->alias) ? info->alias : name);
+}
+
+void print_keydup_error(TABLE *table, KEY *key, const char *msg, myf errflag);
+void print_keydup_error(TABLE *table, KEY *key, myf errflag);
+#endif
diff --git a/sql/hash_filo.cc b/sql/hash_filo.cc
new file mode 100644
index 00000000000..fc89bb83a9d
--- /dev/null
+++ b/sql/hash_filo.cc
@@ -0,0 +1,33 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/*
+** A class for static sized hash tables where old entries are deleted according
+** to usage.
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "hash_filo.h"
+
+#ifdef __WIN__
+// Remove linker warning 4221 about empty file
+namespace { char dummy; };
+#endif // __WIN__
diff --git a/sql/hash_filo.h b/sql/hash_filo.h
new file mode 100644
index 00000000000..4c8c7575efc
--- /dev/null
+++ b/sql/hash_filo.h
@@ -0,0 +1,215 @@
+/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/*
+** A class for static sized hash tables where old entries are deleted in
+** first-in-last-out to usage.
+*/
+
+#ifndef HASH_FILO_H
+#define HASH_FILO_H
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class interface */
+#endif
+
+#include "hash.h" /* my_hash_get_key, my_hash_free_key, HASH */
+#include "m_string.h" /* bzero */
+#include "mysqld.h" /* key_hash_filo_lock */
+
+class hash_filo_element
+{
+private:
+ hash_filo_element *next_used,*prev_used;
+ public:
+ hash_filo_element() {}
+ hash_filo_element *next()
+ { return next_used; }
+ hash_filo_element *prev()
+ { return prev_used; }
+
+ friend class hash_filo;
+};
+
+
+class hash_filo
+{
+private:
+ const uint key_offset, key_length;
+ const my_hash_get_key get_key;
+ /** Size of this hash table. */
+ uint m_size;
+ my_hash_free_key free_element;
+ bool init;
+ CHARSET_INFO *hash_charset;
+
+ hash_filo_element *first_link,*last_link;
+public:
+ mysql_mutex_t lock;
+ HASH cache;
+
+ hash_filo(uint size_arg, uint key_offset_arg , uint key_length_arg,
+ my_hash_get_key get_key_arg, my_hash_free_key free_element_arg,
+ CHARSET_INFO *hash_charset_arg)
+ :key_offset(key_offset_arg), key_length(key_length_arg),
+ get_key(get_key_arg), m_size(size_arg),
+ free_element(free_element_arg),init(0),
+ hash_charset(hash_charset_arg),
+ first_link(NULL),
+ last_link(NULL)
+ {
+ bzero((char*) &cache,sizeof(cache));
+ }
+
+ ~hash_filo()
+ {
+ if (init)
+ {
+ if (cache.array.buffer) /* Avoid problems with thread library */
+ (void) my_hash_free(&cache);
+ mysql_mutex_destroy(&lock);
+ }
+ }
+ void clear(bool locked=0)
+ {
+ if (!init)
+ {
+ init=1;
+ mysql_mutex_init(key_hash_filo_lock, &lock, MY_MUTEX_INIT_FAST);
+ }
+ if (!locked)
+ mysql_mutex_lock(&lock);
+ first_link= NULL;
+ last_link= NULL;
+ (void) my_hash_free(&cache);
+ (void) my_hash_init(&cache,hash_charset,m_size,key_offset,
+ key_length, get_key, free_element,0);
+ if (!locked)
+ mysql_mutex_unlock(&lock);
+ }
+
+ hash_filo_element *first()
+ {
+ mysql_mutex_assert_owner(&lock);
+ return first_link;
+ }
+
+ hash_filo_element *last()
+ {
+ mysql_mutex_assert_owner(&lock);
+ return last_link;
+ }
+
+ hash_filo_element *search(uchar* key, size_t length)
+ {
+ mysql_mutex_assert_owner(&lock);
+
+ hash_filo_element *entry=(hash_filo_element*)
+ my_hash_search(&cache,(uchar*) key,length);
+ if (entry)
+ { // Found; link it first
+ DBUG_ASSERT(first_link != NULL);
+ DBUG_ASSERT(last_link != NULL);
+ if (entry != first_link)
+ { // Relink used-chain
+ if (entry == last_link)
+ {
+ last_link= last_link->prev_used;
+ /*
+ The list must have at least 2 elements,
+ otherwise entry would be equal to first_link.
+ */
+ DBUG_ASSERT(last_link != NULL);
+ last_link->next_used= NULL;
+ }
+ else
+ {
+ DBUG_ASSERT(entry->next_used != NULL);
+ DBUG_ASSERT(entry->prev_used != NULL);
+ entry->next_used->prev_used = entry->prev_used;
+ entry->prev_used->next_used = entry->next_used;
+ }
+ entry->prev_used= NULL;
+ entry->next_used= first_link;
+
+ first_link->prev_used= entry;
+ first_link=entry;
+ }
+ }
+ return entry;
+ }
+
+ bool add(hash_filo_element *entry)
+ {
+ if (!m_size) return 1;
+ if (cache.records == m_size)
+ {
+ hash_filo_element *tmp=last_link;
+ last_link= last_link->prev_used;
+ if (last_link != NULL)
+ {
+ last_link->next_used= NULL;
+ }
+ else
+ {
+ /* Pathological case, m_size == 1 */
+ first_link= NULL;
+ }
+ my_hash_delete(&cache,(uchar*) tmp);
+ }
+ if (my_hash_insert(&cache,(uchar*) entry))
+ {
+ if (free_element)
+ (*free_element)(entry); // This should never happen
+ return 1;
+ }
+ entry->prev_used= NULL;
+ entry->next_used= first_link;
+ if (first_link != NULL)
+ first_link->prev_used= entry;
+ else
+ last_link= entry;
+ first_link= entry;
+
+ return 0;
+ }
+
+ uint size()
+ { return m_size; }
+
+ void resize(uint new_size)
+ {
+ mysql_mutex_lock(&lock);
+ m_size= new_size;
+ clear(true);
+ mysql_mutex_unlock(&lock);
+ }
+};
+
+template <class T> class Hash_filo: public hash_filo
+{
+public:
+ Hash_filo(uint size_arg, uint key_offset_arg, uint key_length_arg,
+ my_hash_get_key get_key_arg, my_hash_free_key free_element_arg,
+ CHARSET_INFO *hash_charset_arg) :
+ hash_filo(size_arg, key_offset_arg, key_length_arg,
+ get_key_arg, free_element_arg, hash_charset_arg) {}
+ T* first() { return (T*)hash_filo::first(); }
+ T* last() { return (T*)hash_filo::last(); }
+ T* search(uchar* key, size_t len) { return (T*)hash_filo::search(key, len); }
+};
+
+#endif
diff --git a/sql/hostname.cc b/sql/hostname.cc
new file mode 100644
index 00000000000..1879d056623
--- /dev/null
+++ b/sql/hostname.cc
@@ -0,0 +1,1009 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2014, SkySQL Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/**
+ @file
+
+ @brief
+ Get hostname for an IP address.
+
+ Hostnames are checked with reverse name lookup and checked that they
+ doesn't resemble an IP address.
+*/
+#include <my_global.h>
+#include "sql_priv.h"
+#include "hostname.h"
+#include "my_global.h"
+#ifndef __WIN__
+#include <netdb.h> // getservbyname, servent
+#endif
+#include "hash_filo.h"
+#include <m_ctype.h>
+#include "log.h" // sql_print_warning,
+ // sql_print_information
+#include "violite.h" // vio_getnameinfo,
+ // vio_get_normalized_ip_string
+#ifdef __cplusplus
+extern "C" { // Because of SCO 3.2V4.2
+#endif
+#if !defined( __WIN__)
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#include <sys/utsname.h>
+#endif // __WIN__
+#ifdef __cplusplus
+}
+#endif
+
+Host_errors::Host_errors()
+: m_connect(0),
+ m_host_blocked(0),
+ m_nameinfo_transient(0),
+ m_nameinfo_permanent(0),
+ m_format(0),
+ m_addrinfo_transient(0),
+ m_addrinfo_permanent(0),
+ m_FCrDNS(0),
+ m_host_acl(0),
+ m_no_auth_plugin(0),
+ m_auth_plugin(0),
+ m_handshake(0),
+ m_proxy_user(0),
+ m_proxy_user_acl(0),
+ m_authentication(0),
+ m_ssl(0),
+ m_max_user_connection(0),
+ m_max_user_connection_per_hour(0),
+ m_default_database(0),
+ m_init_connect(0),
+ m_local(0)
+{}
+
+Host_errors::~Host_errors()
+{}
+
+void Host_errors::reset()
+{
+ m_connect= 0;
+ m_host_blocked= 0;
+ m_nameinfo_transient= 0;
+ m_nameinfo_permanent= 0;
+ m_format= 0;
+ m_addrinfo_transient= 0;
+ m_addrinfo_permanent= 0;
+ m_FCrDNS= 0;
+ m_host_acl= 0;
+ m_no_auth_plugin= 0;
+ m_auth_plugin= 0;
+ m_handshake= 0;
+ m_proxy_user= 0;
+ m_proxy_user_acl= 0;
+ m_authentication= 0;
+ m_ssl= 0;
+ m_max_user_connection= 0;
+ m_max_user_connection_per_hour= 0;
+ m_default_database= 0;
+ m_init_connect= 0;
+ m_local= 0;
+}
+
+void Host_errors::aggregate(const Host_errors *errors)
+{
+ m_connect+= errors->m_connect;
+ m_host_blocked+= errors->m_host_blocked;
+ m_nameinfo_transient+= errors->m_nameinfo_transient;
+ m_nameinfo_permanent+= errors->m_nameinfo_permanent;
+ m_format+= errors->m_format;
+ m_addrinfo_transient+= errors->m_addrinfo_transient;
+ m_addrinfo_permanent+= errors->m_addrinfo_permanent;
+ m_FCrDNS+= errors->m_FCrDNS;
+ m_host_acl+= errors->m_host_acl;
+ m_no_auth_plugin+= errors->m_no_auth_plugin;
+ m_auth_plugin+= errors->m_auth_plugin;
+ m_handshake+= errors->m_handshake;
+ m_proxy_user+= errors->m_proxy_user;
+ m_proxy_user_acl+= errors->m_proxy_user_acl;
+ m_authentication+= errors->m_authentication;
+ m_ssl+= errors->m_ssl;
+ m_max_user_connection+= errors->m_max_user_connection;
+ m_max_user_connection_per_hour+= errors->m_max_user_connection_per_hour;
+ m_default_database+= errors->m_default_database;
+ m_init_connect+= errors->m_init_connect;
+ m_local+= errors->m_local;
+}
+
+static Hash_filo<Host_entry> *hostname_cache;
+ulong host_cache_size;
+
+void hostname_cache_refresh()
+{
+ hostname_cache->clear();
+}
+
+uint hostname_cache_size()
+{
+ return hostname_cache->size();
+}
+
+void hostname_cache_resize(uint size)
+{
+ hostname_cache->resize(size);
+}
+
+bool hostname_cache_init()
+{
+ Host_entry tmp;
+ uint key_offset= (uint) ((char*) (&tmp.ip_key) - (char*) &tmp);
+
+ if (!(hostname_cache= new Hash_filo<Host_entry>(host_cache_size,
+ key_offset, HOST_ENTRY_KEY_SIZE,
+ NULL, (my_hash_free_key) free,
+ &my_charset_bin)))
+ return 1;
+
+ hostname_cache->clear();
+
+ return 0;
+}
+
+void hostname_cache_free()
+{
+ delete hostname_cache;
+ hostname_cache= NULL;
+}
+
+void hostname_cache_lock()
+{
+ mysql_mutex_lock(&hostname_cache->lock);
+}
+
+void hostname_cache_unlock()
+{
+ mysql_mutex_unlock(&hostname_cache->lock);
+}
+
+static void prepare_hostname_cache_key(const char *ip_string,
+ char *ip_key)
+{
+ int ip_string_length= strlen(ip_string);
+ DBUG_ASSERT(ip_string_length < HOST_ENTRY_KEY_SIZE);
+
+ memset(ip_key, 0, HOST_ENTRY_KEY_SIZE);
+ memcpy(ip_key, ip_string, ip_string_length);
+}
+
+Host_entry *hostname_cache_first()
+{ return hostname_cache->first(); }
+
+static inline Host_entry *hostname_cache_search(const char *ip_key)
+{
+ return hostname_cache->search((uchar *) ip_key, 0);
+}
+
+static void add_hostname_impl(const char *ip_key, const char *hostname,
+ bool validated, Host_errors *errors,
+ ulonglong now)
+{
+ Host_entry *entry;
+ bool need_add= false;
+
+ entry= hostname_cache_search(ip_key);
+
+ if (likely(entry == NULL))
+ {
+ entry= (Host_entry *) malloc(sizeof (Host_entry));
+ if (entry == NULL)
+ return;
+
+ need_add= true;
+ memcpy(&entry->ip_key, ip_key, HOST_ENTRY_KEY_SIZE);
+ entry->m_errors.reset();
+ entry->m_hostname_length= 0;
+ entry->m_host_validated= false;
+ entry->m_first_seen= now;
+ entry->m_last_seen= now;
+ entry->m_first_error_seen= 0;
+ entry->m_last_error_seen= 0;
+ }
+ else
+ {
+ entry->m_last_seen= now;
+ }
+
+ if (validated)
+ {
+ if (hostname != NULL)
+ {
+ uint len= strlen(hostname);
+ if (len > sizeof(entry->m_hostname) - 1)
+ len= sizeof(entry->m_hostname) - 1;
+ memcpy(entry->m_hostname, hostname, len);
+ entry->m_hostname[len]= '\0';
+ entry->m_hostname_length= len;
+
+ DBUG_PRINT("info",
+ ("Adding/Updating '%s' -> '%s' (validated) to the hostname cache...'",
+ (const char *) ip_key,
+ (const char *) entry->m_hostname));
+ }
+ else
+ {
+ entry->m_hostname_length= 0;
+ DBUG_PRINT("info",
+ ("Adding/Updating '%s' -> NULL (validated) to the hostname cache...'",
+ (const char *) ip_key));
+ }
+ entry->m_host_validated= true;
+ /*
+ New errors that are considered 'blocking',
+ that will eventually cause the IP to be black listed and blocked.
+ */
+ errors->sum_connect_errors();
+ }
+ else
+ {
+ entry->m_hostname_length= 0;
+ entry->m_host_validated= false;
+ /* Do not count new blocking errors during DNS failures. */
+ errors->clear_connect_errors();
+ DBUG_PRINT("info",
+ ("Adding/Updating '%s' -> NULL (not validated) to the hostname cache...'",
+ (const char *) ip_key));
+ }
+
+ if (errors->has_error())
+ entry->set_error_timestamps(now);
+
+ entry->m_errors.aggregate(errors);
+
+ if (need_add)
+ hostname_cache->add(entry);
+
+ return;
+}
+
+static void add_hostname(const char *ip_key, const char *hostname,
+ bool validated, Host_errors *errors)
+{
+ if (specialflag & SPECIAL_NO_HOST_CACHE)
+ return;
+
+ ulonglong now= my_hrtime().val;
+
+ mysql_mutex_lock(&hostname_cache->lock);
+
+ add_hostname_impl(ip_key, hostname, validated, errors, now);
+
+ mysql_mutex_unlock(&hostname_cache->lock);
+
+ return;
+}
+
+void inc_host_errors(const char *ip_string, Host_errors *errors)
+{
+ if (!ip_string)
+ return;
+
+ ulonglong now= my_hrtime().val;
+ char ip_key[HOST_ENTRY_KEY_SIZE];
+ prepare_hostname_cache_key(ip_string, ip_key);
+
+ mysql_mutex_lock(&hostname_cache->lock);
+
+ Host_entry *entry= hostname_cache_search(ip_key);
+
+ if (entry)
+ {
+ if (entry->m_host_validated)
+ errors->sum_connect_errors();
+ else
+ errors->clear_connect_errors();
+
+ entry->m_errors.aggregate(errors);
+ entry->set_error_timestamps(now);
+ }
+
+ mysql_mutex_unlock(&hostname_cache->lock);
+}
+
+void reset_host_connect_errors(const char *ip_string)
+{
+ if (!ip_string)
+ return;
+
+ char ip_key[HOST_ENTRY_KEY_SIZE];
+ prepare_hostname_cache_key(ip_string, ip_key);
+
+ mysql_mutex_lock(&hostname_cache->lock);
+
+ Host_entry *entry= hostname_cache_search(ip_key);
+
+ if (entry)
+ entry->m_errors.clear_connect_errors();
+
+ mysql_mutex_unlock(&hostname_cache->lock);
+}
+
+static inline bool is_ip_loopback(const struct sockaddr *ip)
+{
+ switch (ip->sa_family) {
+ case AF_INET:
+ {
+ /* Check for IPv4 127.0.0.1. */
+ struct in_addr *ip4= &((struct sockaddr_in *) ip)->sin_addr;
+ return ntohl(ip4->s_addr) == INADDR_LOOPBACK;
+ }
+
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ {
+ /* Check for IPv6 ::1. */
+ struct in6_addr *ip6= &((struct sockaddr_in6 *) ip)->sin6_addr;
+ return IN6_IS_ADDR_LOOPBACK(ip6);
+ }
+#endif /* HAVE_IPV6 */
+
+ default:
+ return FALSE;
+ }
+}
+
+static inline bool is_hostname_valid(const char *hostname)
+{
+ /*
+ A hostname is invalid if it starts with a number followed by a dot
+ (IPv4 address).
+ */
+
+ if (!my_isdigit(&my_charset_latin1, hostname[0]))
+ return TRUE;
+
+ const char *p= hostname + 1;
+
+ while (my_isdigit(&my_charset_latin1, *p))
+ ++p;
+
+ return *p != '.';
+}
+
+/**
+ Resolve IP-address to host name.
+
+ This function does the following things:
+ - resolves IP-address;
+ - employs Forward Confirmed Reverse DNS technique to validate IP-address;
+ - returns host name if IP-address is validated;
+ - set value to out-variable connect_errors -- this variable represents the
+ number of connection errors from the specified IP-address.
+ - update the host_cache statistics
+
+ NOTE: connect_errors are counted (are supported) only for the clients
+ where IP-address can be resolved and FCrDNS check is passed.
+
+ @param [in] ip_storage IP address (sockaddr). Must be set.
+ @param [in] ip_string IP address (string). Must be set.
+ @param [out] hostname
+ @param [out] connect_errors
+
+ @return Error status
+ @retval 0 Success
+ @retval RC_BLOCKED_HOST The host is blocked.
+
+ The function does not set/report MySQL server error in case of failure.
+ It's caller's responsibility to handle failures of this function
+ properly.
+*/
+
+int ip_to_hostname(struct sockaddr_storage *ip_storage,
+ const char *ip_string,
+ char **hostname,
+ uint *connect_errors)
+{
+ const struct sockaddr *ip= (const sockaddr *) ip_storage;
+ int err_code;
+ bool err_status __attribute__((unused));
+ Host_errors errors;
+
+ DBUG_ENTER("ip_to_hostname");
+ DBUG_PRINT("info", ("IP address: '%s'; family: %d.",
+ (const char *) ip_string,
+ (int) ip->sa_family));
+
+ /* Default output values, for most cases. */
+ *hostname= NULL;
+ *connect_errors= 0;
+
+ /* Check if we have loopback address (127.0.0.1 or ::1). */
+
+ if (is_ip_loopback(ip))
+ {
+ DBUG_PRINT("info", ("Loopback address detected."));
+
+ /* Do not count connect errors from localhost. */
+ *hostname= (char *) my_localhost;
+
+ DBUG_RETURN(0);
+ }
+
+ /* Prepare host name cache key. */
+
+ char ip_key[HOST_ENTRY_KEY_SIZE];
+ prepare_hostname_cache_key(ip_string, ip_key);
+
+ /* Check first if we have host name in the cache. */
+
+ if (!(specialflag & SPECIAL_NO_HOST_CACHE))
+ {
+ ulonglong now= my_hrtime().val;
+
+ mysql_mutex_lock(&hostname_cache->lock);
+
+ Host_entry *entry= hostname_cache_search(ip_key);
+
+ if (entry)
+ {
+ entry->m_last_seen= now;
+ *connect_errors= entry->m_errors.m_connect;
+
+ if (entry->m_errors.m_connect >= max_connect_errors)
+ {
+ entry->m_errors.m_host_blocked++;
+ entry->set_error_timestamps(now);
+ mysql_mutex_unlock(&hostname_cache->lock);
+ DBUG_RETURN(RC_BLOCKED_HOST);
+ }
+
+ /*
+ If there is an IP -> HOSTNAME association in the cache,
+ but for a hostname that was not validated,
+ do not return that hostname: perform the network validation again.
+ */
+ if (entry->m_host_validated)
+ {
+ if (entry->m_hostname_length)
+ *hostname= my_strdup(entry->m_hostname, MYF(0));
+
+ DBUG_PRINT("info",("IP (%s) has been found in the cache. "
+ "Hostname: '%s'",
+ (const char *) ip_key,
+ (const char *) (*hostname? *hostname : "null")
+ ));
+
+ mysql_mutex_unlock(&hostname_cache->lock);
+
+ DBUG_RETURN(0);
+ }
+ }
+
+ mysql_mutex_unlock(&hostname_cache->lock);
+ }
+
+ /*
+ Resolve host name. Return an error if a host name can not be resolved
+ (instead of returning the numeric form of the host name).
+ */
+
+ char hostname_buffer[NI_MAXHOST];
+
+ DBUG_PRINT("info", ("Resolving '%s'...", (const char *) ip_key));
+
+ err_code= vio_getnameinfo(ip, hostname_buffer, NI_MAXHOST, NULL, 0,
+ NI_NAMEREQD);
+
+ /*
+ ===========================================================================
+ DEBUG code only (begin)
+ Simulate various output from vio_getnameinfo().
+ ===========================================================================
+ */
+
+ DBUG_EXECUTE_IF("getnameinfo_error_noname",
+ {
+ strcpy(hostname_buffer, "<garbage>");
+ err_code= EAI_NONAME;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getnameinfo_error_again",
+ {
+ strcpy(hostname_buffer, "<garbage>");
+ err_code= EAI_AGAIN;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getnameinfo_fake_ipv4",
+ {
+ strcpy(hostname_buffer, "santa.claus.ipv4.example.com");
+ err_code= 0;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getnameinfo_fake_ipv6",
+ {
+ strcpy(hostname_buffer, "santa.claus.ipv6.example.com");
+ err_code= 0;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getnameinfo_format_ipv4",
+ {
+ strcpy(hostname_buffer, "12.12.12.12");
+ err_code= 0;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getnameinfo_format_ipv6",
+ {
+ strcpy(hostname_buffer, "12:DEAD:BEEF:0");
+ err_code= 0;
+ }
+ );
+
+ /*
+ ===========================================================================
+ DEBUG code only (end)
+ ===========================================================================
+ */
+
+ if (err_code)
+ {
+ // NOTE: gai_strerror() returns a string ending by a dot.
+
+ DBUG_PRINT("error", ("IP address '%s' could not be resolved: %s",
+ (const char *) ip_key,
+ (const char *) gai_strerror(err_code)));
+
+ sql_print_warning("IP address '%s' could not be resolved: %s",
+ (const char *) ip_key,
+ (const char *) gai_strerror(err_code));
+
+ bool validated;
+ if (vio_is_no_name_error(err_code))
+ {
+ /*
+ The no-name error means that there is no reverse address mapping
+ for the IP address. A host name can not be resolved.
+ */
+ errors.m_nameinfo_permanent= 1;
+ validated= true;
+ }
+ else
+ {
+ /*
+ If it is not the no-name error, we should not cache the hostname
+ (or rather its absence), because the failure might be transient.
+ Only the ip error statistics are cached.
+ */
+ errors.m_nameinfo_transient= 1;
+ validated= false;
+ }
+ add_hostname(ip_key, NULL, validated, &errors);
+
+ DBUG_RETURN(0);
+ }
+
+ DBUG_PRINT("info", ("IP '%s' resolved to '%s'.",
+ (const char *) ip_key,
+ (const char *) hostname_buffer));
+
+ /*
+ Validate hostname: the server does not accept host names, which
+ resemble IP addresses.
+
+ The thing is that theoretically, a host name can be in a form of IPv4
+ address (123.example.org, or 1.2 or even 1.2.3.4). We have to deny such
+ host names because ACL-systems is not designed to work with them.
+
+ For example, it is possible to specify a host name mask (like
+ 192.168.1.%) for an ACL rule. Then, if IPv4-like hostnames are allowed,
+ there is a security hole: instead of allowing access for
+ 192.168.1.0/255 network (which was assumed by the user), the access
+ will be allowed for host names like 192.168.1.example.org.
+ */
+
+ if (!is_hostname_valid(hostname_buffer))
+ {
+ DBUG_PRINT("error", ("IP address '%s' has been resolved "
+ "to the host name '%s', which resembles "
+ "IPv4-address itself.",
+ (const char *) ip_key,
+ (const char *) hostname_buffer));
+
+ sql_print_warning("IP address '%s' has been resolved "
+ "to the host name '%s', which resembles "
+ "IPv4-address itself.",
+ (const char *) ip_key,
+ (const char *) hostname_buffer);
+
+ errors.m_format= 1;
+ add_hostname(ip_key, hostname_buffer, false, &errors);
+
+ DBUG_RETURN(false);
+ }
+
+ /* Get IP-addresses for the resolved host name (FCrDNS technique). */
+
+ struct addrinfo hints;
+ struct addrinfo *addr_info_list;
+ /*
+ Makes fault injection with DBUG_EXECUTE_IF easier.
+ Invoking free_addr_info(NULL) crashes on some platforms.
+ */
+ bool free_addr_info_list= false;
+
+ memset(&hints, 0, sizeof (struct addrinfo));
+ hints.ai_flags= AI_PASSIVE;
+ hints.ai_socktype= SOCK_STREAM;
+ hints.ai_family= AF_UNSPEC;
+
+ DBUG_PRINT("info", ("Getting IP addresses for hostname '%s'...",
+ (const char *) hostname_buffer));
+
+ err_code= getaddrinfo(hostname_buffer, NULL, &hints, &addr_info_list);
+ if (err_code == 0)
+ free_addr_info_list= true;
+
+ /*
+ ===========================================================================
+ DEBUG code only (begin)
+ Simulate various output from getaddrinfo().
+ ===========================================================================
+ */
+ DBUG_EXECUTE_IF("getaddrinfo_error_noname",
+ {
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+
+ addr_info_list= NULL;
+ err_code= EAI_NONAME;
+ free_addr_info_list= false;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getaddrinfo_error_again",
+ {
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+
+ addr_info_list= NULL;
+ err_code= EAI_AGAIN;
+ free_addr_info_list= false;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getaddrinfo_fake_bad_ipv4",
+ {
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+
+ struct sockaddr_in *debug_addr;
+ /*
+ Not thread safe, which is ok.
+ Only one connection at a time is tested with
+ fault injection.
+ */
+ static struct sockaddr_in debug_sock_addr[2];
+ static struct addrinfo debug_addr_info[2];
+ /* Simulating ipv4 192.0.2.126 */
+ debug_addr= & debug_sock_addr[0];
+ debug_addr->sin_family= AF_INET;
+ debug_addr->sin_addr.s_addr= inet_addr("192.0.2.126");
+
+ /* Simulating ipv4 192.0.2.127 */
+ debug_addr= & debug_sock_addr[1];
+ debug_addr->sin_family= AF_INET;
+ debug_addr->sin_addr.s_addr= inet_addr("192.0.2.127");
+
+ debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
+ debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in);
+ debug_addr_info[0].ai_next= & debug_addr_info[1];
+
+ debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
+ debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in);
+ debug_addr_info[1].ai_next= NULL;
+
+ addr_info_list= & debug_addr_info[0];
+ err_code= 0;
+ free_addr_info_list= false;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getaddrinfo_fake_good_ipv4",
+ {
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+
+ struct sockaddr_in *debug_addr;
+ static struct sockaddr_in debug_sock_addr[2];
+ static struct addrinfo debug_addr_info[2];
+ /* Simulating ipv4 192.0.2.5 */
+ debug_addr= & debug_sock_addr[0];
+ debug_addr->sin_family= AF_INET;
+ debug_addr->sin_addr.s_addr= inet_addr("192.0.2.5");
+
+ /* Simulating ipv4 192.0.2.4 */
+ debug_addr= & debug_sock_addr[1];
+ debug_addr->sin_family= AF_INET;
+ debug_addr->sin_addr.s_addr= inet_addr("192.0.2.4");
+
+ debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
+ debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in);
+ debug_addr_info[0].ai_next= & debug_addr_info[1];
+
+ debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
+ debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in);
+ debug_addr_info[1].ai_next= NULL;
+
+ addr_info_list= & debug_addr_info[0];
+ err_code= 0;
+ free_addr_info_list= false;
+ }
+ );
+
+#ifdef HAVE_IPV6
+ DBUG_EXECUTE_IF("getaddrinfo_fake_bad_ipv6",
+ {
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+
+ struct sockaddr_in6 *debug_addr;
+ struct in6_addr *ip6;
+ /*
+ Not thread safe, which is ok.
+ Only one connection at a time is tested with
+ fault injection.
+ */
+ static struct sockaddr_in6 debug_sock_addr[2];
+ static struct addrinfo debug_addr_info[2];
+ /* Simulating ipv6 2001:DB8::6:7E */
+ debug_addr= & debug_sock_addr[0];
+ debug_addr->sin6_family= AF_INET6;
+ ip6= & debug_addr->sin6_addr;
+ /* inet_pton not available on Windows XP. */
+ ip6->s6_addr[ 0] = 0x20;
+ ip6->s6_addr[ 1] = 0x01;
+ ip6->s6_addr[ 2] = 0x0d;
+ ip6->s6_addr[ 3] = 0xb8;
+ ip6->s6_addr[ 4] = 0x00;
+ ip6->s6_addr[ 5] = 0x00;
+ ip6->s6_addr[ 6] = 0x00;
+ ip6->s6_addr[ 7] = 0x00;
+ ip6->s6_addr[ 8] = 0x00;
+ ip6->s6_addr[ 9] = 0x00;
+ ip6->s6_addr[10] = 0x00;
+ ip6->s6_addr[11] = 0x00;
+ ip6->s6_addr[12] = 0x00;
+ ip6->s6_addr[13] = 0x06;
+ ip6->s6_addr[14] = 0x00;
+ ip6->s6_addr[15] = 0x7e;
+
+ /* Simulating ipv6 2001:DB8::6:7F */
+ debug_addr= & debug_sock_addr[1];
+ debug_addr->sin6_family= AF_INET6;
+ ip6= & debug_addr->sin6_addr;
+ ip6->s6_addr[ 0] = 0x20;
+ ip6->s6_addr[ 1] = 0x01;
+ ip6->s6_addr[ 2] = 0x0d;
+ ip6->s6_addr[ 3] = 0xb8;
+ ip6->s6_addr[ 4] = 0x00;
+ ip6->s6_addr[ 5] = 0x00;
+ ip6->s6_addr[ 6] = 0x00;
+ ip6->s6_addr[ 7] = 0x00;
+ ip6->s6_addr[ 8] = 0x00;
+ ip6->s6_addr[ 9] = 0x00;
+ ip6->s6_addr[10] = 0x00;
+ ip6->s6_addr[11] = 0x00;
+ ip6->s6_addr[12] = 0x00;
+ ip6->s6_addr[13] = 0x06;
+ ip6->s6_addr[14] = 0x00;
+ ip6->s6_addr[15] = 0x7f;
+
+ debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
+ debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in6);
+ debug_addr_info[0].ai_next= & debug_addr_info[1];
+
+ debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
+ debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in6);
+ debug_addr_info[1].ai_next= NULL;
+
+ addr_info_list= & debug_addr_info[0];
+ err_code= 0;
+ free_addr_info_list= false;
+ }
+ );
+
+ DBUG_EXECUTE_IF("getaddrinfo_fake_good_ipv6",
+ {
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+
+ struct sockaddr_in6 *debug_addr;
+ struct in6_addr *ip6;
+ /*
+ Not thread safe, which is ok.
+ Only one connection at a time is tested with
+ fault injection.
+ */
+ static struct sockaddr_in6 debug_sock_addr[2];
+ static struct addrinfo debug_addr_info[2];
+ /* Simulating ipv6 2001:DB8::6:7 */
+ debug_addr= & debug_sock_addr[0];
+ debug_addr->sin6_family= AF_INET6;
+ ip6= & debug_addr->sin6_addr;
+ ip6->s6_addr[ 0] = 0x20;
+ ip6->s6_addr[ 1] = 0x01;
+ ip6->s6_addr[ 2] = 0x0d;
+ ip6->s6_addr[ 3] = 0xb8;
+ ip6->s6_addr[ 4] = 0x00;
+ ip6->s6_addr[ 5] = 0x00;
+ ip6->s6_addr[ 6] = 0x00;
+ ip6->s6_addr[ 7] = 0x00;
+ ip6->s6_addr[ 8] = 0x00;
+ ip6->s6_addr[ 9] = 0x00;
+ ip6->s6_addr[10] = 0x00;
+ ip6->s6_addr[11] = 0x00;
+ ip6->s6_addr[12] = 0x00;
+ ip6->s6_addr[13] = 0x06;
+ ip6->s6_addr[14] = 0x00;
+ ip6->s6_addr[15] = 0x07;
+
+ /* Simulating ipv6 2001:DB8::6:6 */
+ debug_addr= & debug_sock_addr[1];
+ debug_addr->sin6_family= AF_INET6;
+ ip6= & debug_addr->sin6_addr;
+ ip6->s6_addr[ 0] = 0x20;
+ ip6->s6_addr[ 1] = 0x01;
+ ip6->s6_addr[ 2] = 0x0d;
+ ip6->s6_addr[ 3] = 0xb8;
+ ip6->s6_addr[ 4] = 0x00;
+ ip6->s6_addr[ 5] = 0x00;
+ ip6->s6_addr[ 6] = 0x00;
+ ip6->s6_addr[ 7] = 0x00;
+ ip6->s6_addr[ 8] = 0x00;
+ ip6->s6_addr[ 9] = 0x00;
+ ip6->s6_addr[10] = 0x00;
+ ip6->s6_addr[11] = 0x00;
+ ip6->s6_addr[12] = 0x00;
+ ip6->s6_addr[13] = 0x06;
+ ip6->s6_addr[14] = 0x00;
+ ip6->s6_addr[15] = 0x06;
+
+ debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
+ debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in6);
+ debug_addr_info[0].ai_next= & debug_addr_info[1];
+
+ debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
+ debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in6);
+ debug_addr_info[1].ai_next= NULL;
+
+ addr_info_list= & debug_addr_info[0];
+ err_code= 0;
+ free_addr_info_list= false;
+ }
+ );
+#endif /* HAVE_IPV6 */
+
+ /*
+ ===========================================================================
+ DEBUG code only (end)
+ ===========================================================================
+ */
+
+ if (err_code != 0)
+ {
+ sql_print_warning("Host name '%s' could not be resolved: %s",
+ (const char *) hostname_buffer,
+ (const char *) gai_strerror(err_code));
+
+ bool validated;
+
+ if (err_code == EAI_NONAME)
+ {
+ errors.m_addrinfo_permanent= 1;
+ validated= true;
+ }
+ else
+ {
+ /*
+ Don't cache responses when the DNS server is down, as otherwise
+ transient DNS failure may leave any number of clients (those
+ that attempted to connect during the outage) unable to connect
+ indefinitely.
+ Only cache error statistics.
+ */
+ errors.m_addrinfo_transient= 1;
+ validated= false;
+ }
+ add_hostname(ip_key, NULL, validated, &errors);
+
+ DBUG_RETURN(false);
+ }
+
+ /* Check that getaddrinfo() returned the used IP (FCrDNS technique). */
+
+ DBUG_PRINT("info", ("The following IP addresses found for '%s':",
+ (const char *) hostname_buffer));
+
+ for (struct addrinfo *addr_info= addr_info_list;
+ addr_info; addr_info= addr_info->ai_next)
+ {
+ char ip_buffer[HOST_ENTRY_KEY_SIZE];
+
+ {
+ err_status=
+ vio_get_normalized_ip_string(addr_info->ai_addr, addr_info->ai_addrlen,
+ ip_buffer, sizeof (ip_buffer));
+ DBUG_ASSERT(!err_status);
+ }
+
+ DBUG_PRINT("info", (" - '%s'", (const char *) ip_buffer));
+
+ if (strcasecmp(ip_key, ip_buffer) == 0)
+ {
+ /* Copy host name string to be stored in the cache. */
+
+ *hostname= my_strdup(hostname_buffer, MYF(0));
+
+ if (!*hostname)
+ {
+ DBUG_PRINT("error", ("Out of memory."));
+
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+ DBUG_RETURN(true);
+ }
+
+ break;
+ }
+ }
+
+ /* Log resolved IP-addresses if no match was found. */
+
+ if (!*hostname)
+ {
+ errors.m_FCrDNS= 1;
+
+ sql_print_warning("Hostname '%s' does not resolve to '%s'.",
+ (const char *) hostname_buffer,
+ (const char *) ip_key);
+ sql_print_information("Hostname '%s' has the following IP addresses:",
+ (const char *) hostname_buffer);
+
+ for (struct addrinfo *addr_info= addr_info_list;
+ addr_info; addr_info= addr_info->ai_next)
+ {
+ char ip_buffer[HOST_ENTRY_KEY_SIZE];
+
+ err_status=
+ vio_get_normalized_ip_string(addr_info->ai_addr, addr_info->ai_addrlen,
+ ip_buffer, sizeof (ip_buffer));
+ DBUG_ASSERT(!err_status);
+
+ sql_print_information(" - %s", (const char *) ip_buffer);
+ }
+ }
+
+ /* Add an entry for the IP to the cache. */
+ add_hostname(ip_key, *hostname, true, &errors);
+
+ /* Free the result of getaddrinfo(). */
+ if (free_addr_info_list)
+ freeaddrinfo(addr_info_list);
+
+ DBUG_RETURN(false);
+}
diff --git a/sql/hostname.h b/sql/hostname.h
new file mode 100644
index 00000000000..81a1d0de88d
--- /dev/null
+++ b/sql/hostname.h
@@ -0,0 +1,184 @@
+/* Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef HOSTNAME_INCLUDED
+#define HOSTNAME_INCLUDED
+
+#include "my_global.h" /* uint */
+#include "my_net.h"
+#include "hash_filo.h"
+
+struct Host_errors
+{
+public:
+ Host_errors();
+ ~Host_errors();
+
+ void reset();
+ void aggregate(const Host_errors *errors);
+
+ /** Number of connect errors. */
+ ulong m_connect;
+
+ /** Number of host blocked errors. */
+ ulong m_host_blocked;
+ /** Number of transient errors from getnameinfo(). */
+ ulong m_nameinfo_transient;
+ /** Number of permanent errors from getnameinfo(). */
+ ulong m_nameinfo_permanent;
+ /** Number of errors from is_hostname_valid(). */
+ ulong m_format;
+ /** Number of transient errors from getaddrinfo(). */
+ ulong m_addrinfo_transient;
+ /** Number of permanent errors from getaddrinfo(). */
+ ulong m_addrinfo_permanent;
+ /** Number of errors from Forward-Confirmed reverse DNS checks. */
+ ulong m_FCrDNS;
+ /** Number of errors from host grants. */
+ ulong m_host_acl;
+ /** Number of errors from missing auth plugin. */
+ ulong m_no_auth_plugin;
+ /** Number of errors from auth plugin. */
+ ulong m_auth_plugin;
+ /** Number of errors from authentication plugins. */
+ ulong m_handshake;
+ /** Number of errors from proxy user. */
+ ulong m_proxy_user;
+ /** Number of errors from proxy user acl. */
+ ulong m_proxy_user_acl;
+ /** Number of errors from authentication. */
+ ulong m_authentication;
+ /** Number of errors from ssl. */
+ ulong m_ssl;
+ /** Number of errors from max user connection. */
+ ulong m_max_user_connection;
+ /** Number of errors from max user connection per hour. */
+ ulong m_max_user_connection_per_hour;
+ /** Number of errors from the default database. */
+ ulong m_default_database;
+ /** Number of errors from init_connect. */
+ ulong m_init_connect;
+ /** Number of errors from the server itself. */
+ ulong m_local;
+
+ bool has_error() const
+ {
+ return ((m_host_blocked != 0)
+ || (m_nameinfo_transient != 0)
+ || (m_nameinfo_permanent != 0)
+ || (m_format != 0)
+ || (m_addrinfo_transient != 0)
+ || (m_addrinfo_permanent != 0)
+ || (m_FCrDNS != 0)
+ || (m_host_acl != 0)
+ || (m_no_auth_plugin != 0)
+ || (m_auth_plugin != 0)
+ || (m_handshake != 0)
+ || (m_proxy_user != 0)
+ || (m_proxy_user_acl != 0)
+ || (m_authentication != 0)
+ || (m_ssl != 0)
+ || (m_max_user_connection != 0)
+ || (m_max_user_connection_per_hour != 0)
+ || (m_default_database != 0)
+ || (m_init_connect != 0)
+ || (m_local != 0));
+ }
+
+ void sum_connect_errors()
+ {
+ /* Current (historical) behavior: */
+ m_connect= m_handshake;
+ }
+
+ void clear_connect_errors()
+ {
+ m_connect= 0;
+ }
+};
+
+/** Size of IP address string in the hash cache. */
+#define HOST_ENTRY_KEY_SIZE INET6_ADDRSTRLEN
+
+/**
+ An entry in the hostname hash table cache.
+
+ Host name cache does two things:
+ - caches host names to save DNS look ups;
+ - counts errors from IP.
+
+ Host name can be empty (that means DNS look up failed),
+ but errors still are counted.
+*/
+class Host_entry : public hash_filo_element
+{
+public:
+ Host_entry *next()
+ { return (Host_entry*) hash_filo_element::next(); }
+
+ /**
+ Client IP address. This is the key used with the hash table.
+
+ The client IP address is always expressed in IPv6, even when the
+ network IPv6 stack is not present.
+
+ This IP address is never used to connect to a socket.
+ */
+ char ip_key[HOST_ENTRY_KEY_SIZE];
+
+ /**
+ One of the host names for the IP address. May be a zero length string.
+ */
+ char m_hostname[HOSTNAME_LENGTH + 1];
+ /** Length in bytes of @c m_hostname. */
+ uint m_hostname_length;
+ /** The hostname is validated and used for authorization. */
+ bool m_host_validated;
+ ulonglong m_first_seen;
+ ulonglong m_last_seen;
+ ulonglong m_first_error_seen;
+ ulonglong m_last_error_seen;
+ /** Error statistics. */
+ Host_errors m_errors;
+
+ void set_error_timestamps(ulonglong now)
+ {
+ if (m_first_error_seen == 0)
+ m_first_error_seen= now;
+ m_last_error_seen= now;
+ }
+};
+
+/** The size of the host_cache. */
+extern ulong host_cache_size;
+
+#define RC_OK 0
+#define RC_BLOCKED_HOST 1
+int ip_to_hostname(struct sockaddr_storage *ip_storage,
+ const char *ip_string,
+ char **hostname, uint *connect_errors);
+
+void inc_host_errors(const char *ip_string, Host_errors *errors);
+void reset_host_connect_errors(const char *ip_string);
+bool hostname_cache_init();
+void hostname_cache_free();
+void hostname_cache_refresh(void);
+uint hostname_cache_size();
+void hostname_cache_resize(uint size);
+void hostname_cache_lock();
+void hostname_cache_unlock();
+Host_entry *hostname_cache_first();
+
+#endif /* HOSTNAME_INCLUDED */
diff --git a/sql/init.cc b/sql/init.cc
new file mode 100644
index 00000000000..91b4b220bf3
--- /dev/null
+++ b/sql/init.cc
@@ -0,0 +1,53 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/**
+ @file
+
+ @brief
+ Init and dummy functions for interface with unireg
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "init.h"
+#include "my_sys.h"
+#include "mysqld.h" // abort_loop, ...
+#include "my_time.h" // my_init_time
+#include "unireg.h" // SPECIAL_SAME_DB_NAME
+#include <m_ctype.h>
+
+void unireg_init(ulong options)
+{
+ DBUG_ENTER("unireg_init");
+
+ error_handler_hook = my_message_stderr;
+ abort_loop=0;
+
+ my_disable_async_io=1; /* aioread is only in shared library */
+ wild_many='%'; wild_one='_'; wild_prefix='\\'; /* Change to sql syntax */
+
+ current_pid=(ulong) getpid(); /* Save for later ref */
+ my_init_time(); /* Init time-functions (read zone) */
+#ifndef EMBEDDED_LIBRARY
+ my_abort_hook=unireg_abort; /* Abort with close of databases */
+#endif
+
+ (void) strmov(reg_ext,".frm");
+ reg_ext_length= 4;
+ specialflag=SPECIAL_SAME_DB_NAME | options; /* Set options from argv */
+ DBUG_VOID_RETURN;
+}
diff --git a/sql/init.h b/sql/init.h
new file mode 100644
index 00000000000..88cd8e6e178
--- /dev/null
+++ b/sql/init.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef INIT_INCLUDED
+#define INIT_INCLUDED
+
+#include "my_global.h" /* ulong */
+
+void unireg_init(ulong options);
+void unireg_end(void) __attribute__((noreturn));
+
+#endif /* INIT_INCLUDED */
diff --git a/sql/innodb_priv.h b/sql/innodb_priv.h
new file mode 100644
index 00000000000..b9e471b3b13
--- /dev/null
+++ b/sql/innodb_priv.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#ifndef INNODB_PRIV_INCLUDED
+#define INNODB_PRIV_INCLUDED
+
+/** @file Declaring server-internal functions that are used by InnoDB. */
+
+#include <sql_priv.h>
+
+class THD;
+
+int get_quote_char_for_identifier(THD *thd, const char *name, uint length);
+bool schema_table_store_record(THD *thd, TABLE *table);
+void localtime_to_TIME(MYSQL_TIME *to, struct tm *from);
+bool check_global_access(THD *thd, ulong want_access, bool no_errors=false);
+uint strconvert(CHARSET_INFO *from_cs, const char *from, uint from_length,
+ CHARSET_INFO *to_cs, char *to, uint to_length,
+ uint *errors);
+void sql_print_error(const char *format, ...);
+
+#define thd_binlog_pos(X, Y, Z) mysql_bin_log_commit_pos(X, Z, Y)
+
+#endif /* INNODB_PRIV_INCLUDED */
diff --git a/sql/item.cc b/sql/item.cc
new file mode 100644
index 00000000000..a465c2d4e36
--- /dev/null
+++ b/sql/item.cc
@@ -0,0 +1,9845 @@
+/*
+ Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include <mysql.h>
+#include <m_ctype.h>
+#include "my_dir.h"
+#include "sp_rcontext.h"
+#include "sp_head.h"
+#include "sql_trigger.h"
+#include "sql_select.h"
+#include "sql_show.h" // append_identifier
+#include "sql_view.h" // VIEW_ANY_SQL
+#include "sql_time.h" // str_to_datetime_with_warn,
+ // make_truncated_value_warning
+#include "sql_acl.h" // get_column_grant,
+ // SELECT_ACL, UPDATE_ACL,
+ // INSERT_ACL,
+ // check_grant_column
+#include "sql_base.h" // enum_resolution_type,
+ // REPORT_EXCEPT_NOT_FOUND,
+ // find_item_in_list,
+ // RESOLVED_AGAINST_ALIAS, ...
+#include "sql_expression_cache.h"
+
+const String my_null_string("NULL", 4, default_charset_info);
+
+static int save_field_in_field(Field *from, bool *null_value,
+ Field *to, bool no_conversions);
+
+
+/**
+ Compare two Items for List<Item>::add_unique()
+*/
+
+bool cmp_items(Item *a, Item *b)
+{
+ return a->eq(b, FALSE);
+}
+
+/****************************************************************************/
+
+/* Hybrid_type_traits {_real} */
+
+void Hybrid_type_traits::fix_length_and_dec(Item *item, Item *arg) const
+{
+ item->decimals= NOT_FIXED_DEC;
+ item->max_length= item->float_length(arg->decimals);
+}
+
+static const Hybrid_type_traits real_traits_instance;
+
+const Hybrid_type_traits *Hybrid_type_traits::instance()
+{
+ return &real_traits_instance;
+}
+
+
+my_decimal *
+Hybrid_type_traits::val_decimal(Hybrid_type *val, my_decimal *to) const
+{
+ double2my_decimal(E_DEC_FATAL_ERROR, val->real, val->dec_buf);
+ return val->dec_buf;
+}
+
+
+String *
+Hybrid_type_traits::val_str(Hybrid_type *val, String *to, uint8 decimals) const
+{
+ to->set_real(val->real, decimals, &my_charset_bin);
+ return to;
+}
+
+/* Hybrid_type_traits_decimal */
+static const Hybrid_type_traits_decimal decimal_traits_instance;
+
+const Hybrid_type_traits_decimal *Hybrid_type_traits_decimal::instance()
+{
+ return &decimal_traits_instance;
+}
+
+
+void
+Hybrid_type_traits_decimal::fix_length_and_dec(Item *item, Item *arg) const
+{
+ item->decimals= arg->decimals;
+ item->max_length= MY_MIN(arg->max_length + DECIMAL_LONGLONG_DIGITS,
+ DECIMAL_MAX_STR_LENGTH);
+}
+
+
+void Hybrid_type_traits_decimal::set_zero(Hybrid_type *val) const
+{
+ my_decimal_set_zero(&val->dec_buf[0]);
+ val->used_dec_buf_no= 0;
+}
+
+
+void Hybrid_type_traits_decimal::add(Hybrid_type *val, Field *f) const
+{
+ my_decimal_add(E_DEC_FATAL_ERROR,
+ &val->dec_buf[val->used_dec_buf_no ^ 1],
+ &val->dec_buf[val->used_dec_buf_no],
+ f->val_decimal(&val->dec_buf[2]));
+ val->used_dec_buf_no^= 1;
+}
+
+
+/**
+ @todo
+ what is '4' for scale?
+*/
+void Hybrid_type_traits_decimal::div(Hybrid_type *val, ulonglong u) const
+{
+ int2my_decimal(E_DEC_FATAL_ERROR, u, TRUE, &val->dec_buf[2]);
+ /* XXX: what is '4' for scale? */
+ my_decimal_div(E_DEC_FATAL_ERROR,
+ &val->dec_buf[val->used_dec_buf_no ^ 1],
+ &val->dec_buf[val->used_dec_buf_no],
+ &val->dec_buf[2], 4);
+ val->used_dec_buf_no^= 1;
+}
+
+
+longlong
+Hybrid_type_traits_decimal::val_int(Hybrid_type *val, bool unsigned_flag) const
+{
+ longlong result;
+ my_decimal2int(E_DEC_FATAL_ERROR, &val->dec_buf[val->used_dec_buf_no],
+ unsigned_flag, &result);
+ return result;
+}
+
+
+double
+Hybrid_type_traits_decimal::val_real(Hybrid_type *val) const
+{
+ my_decimal2double(E_DEC_FATAL_ERROR, &val->dec_buf[val->used_dec_buf_no],
+ &val->real);
+ return val->real;
+}
+
+
+String *
+Hybrid_type_traits_decimal::val_str(Hybrid_type *val, String *to,
+ uint8 decimals) const
+{
+ my_decimal_round(E_DEC_FATAL_ERROR, &val->dec_buf[val->used_dec_buf_no],
+ decimals, FALSE, &val->dec_buf[2]);
+ my_decimal2string(E_DEC_FATAL_ERROR, &val->dec_buf[2], 0, 0, 0, to);
+ return to;
+}
+
+/* Hybrid_type_traits_integer */
+static const Hybrid_type_traits_integer integer_traits_instance;
+
+const Hybrid_type_traits_integer *Hybrid_type_traits_integer::instance()
+{
+ return &integer_traits_instance;
+}
+
+void
+Hybrid_type_traits_integer::fix_length_and_dec(Item *item, Item *arg) const
+{
+ item->decimals= 0;
+ item->max_length= MY_INT64_NUM_DECIMAL_DIGITS;
+ item->unsigned_flag= 0;
+}
+
+/*****************************************************************************
+** Item functions
+*****************************************************************************/
+
+/**
+ Init all special items.
+*/
+
+void item_init(void)
+{
+ item_func_sleep_init();
+ uuid_short_init();
+}
+
+
+/**
+ @todo
+ Make this functions class dependent
+*/
+
+bool Item::val_bool()
+{
+ switch(result_type()) {
+ case INT_RESULT:
+ return val_int() != 0;
+ case DECIMAL_RESULT:
+ {
+ my_decimal decimal_value;
+ my_decimal *val= val_decimal(&decimal_value);
+ if (val)
+ return !my_decimal_is_zero(val);
+ return 0;
+ }
+ case REAL_RESULT:
+ case STRING_RESULT:
+ return val_real() != 0.0;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ return 0; // Wrong (but safe)
+ }
+ return 0; // Wrong (but safe)
+}
+
+
+/**
+ Get date/time/datetime.
+ Optionally extend TIME result to DATETIME.
+*/
+bool Item::get_date_with_conversion(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ /*
+ Some TIME type items return error when trying to do get_date()
+ without TIME_TIME_ONLY set (e.g. Item_field for Field_time).
+ In the SQL standard time->datetime conversion mode we add TIME_TIME_ONLY.
+ In the legacy time->datetime conversion mode we do not add TIME_TIME_ONLY
+ and leave it to get_date() to check date.
+ */
+ ulonglong time_flag= (field_type() == MYSQL_TYPE_TIME &&
+ !(current_thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST)) ?
+ TIME_TIME_ONLY : 0;
+ if (get_date(ltime, fuzzydate | time_flag))
+ return true;
+ if (ltime->time_type == MYSQL_TIMESTAMP_TIME &&
+ !(fuzzydate & TIME_TIME_ONLY))
+ {
+ MYSQL_TIME tmp;
+ if (time_to_datetime_with_warn(current_thd, ltime, &tmp, fuzzydate))
+ return null_value= true;
+ *ltime= tmp;
+ }
+ return false;
+}
+
+
+/*
+ For the items which don't have its own fast val_str_ascii()
+ implementation we provide a generic slower version,
+ which converts from the Item character set to ASCII.
+ For better performance conversion happens only in
+ case of a "tricky" Item character set (e.g. UCS2).
+ Normally conversion does not happen.
+*/
+String *Item::val_str_ascii(String *str)
+{
+ if (!(collation.collation->state & MY_CS_NONASCII))
+ return val_str(str);
+
+ DBUG_ASSERT(str != &str_value);
+
+ uint errors;
+ String *res= val_str(&str_value);
+ if (!res)
+ return 0;
+
+ if ((null_value= str->copy(res->ptr(), res->length(),
+ collation.collation, &my_charset_latin1,
+ &errors)))
+ return 0;
+
+ return str;
+}
+
+
+String *Item::val_str(String *str, String *converter, CHARSET_INFO *cs)
+{
+ String *res= val_str(str);
+ if (null_value)
+ return (String *) 0;
+
+ if (!cs)
+ return res;
+
+ uint errors;
+ if ((null_value= converter->copy(res->ptr(), res->length(),
+ collation.collation, cs, &errors)))
+ return (String *) 0;
+
+ return converter;
+}
+
+
+String *Item::val_string_from_real(String *str)
+{
+ double nr= val_real();
+ if (null_value)
+ return 0; /* purecov: inspected */
+ str->set_real(nr,decimals, &my_charset_numeric);
+ return str;
+}
+
+
+String *Item::val_string_from_int(String *str)
+{
+ longlong nr= val_int();
+ if (null_value)
+ return 0;
+ str->set_int(nr, unsigned_flag, &my_charset_numeric);
+ return str;
+}
+
+
+String *Item::val_string_from_decimal(String *str)
+{
+ my_decimal dec_buf, *dec= val_decimal(&dec_buf);
+ if (null_value)
+ return 0;
+ my_decimal_round(E_DEC_FATAL_ERROR, dec, decimals, FALSE, &dec_buf);
+ my_decimal2string(E_DEC_FATAL_ERROR, &dec_buf, 0, 0, 0, str);
+ return str;
+}
+
+
+/*
+ All val_xxx_from_date() must call this method, to expose consistent behaviour
+ regarding SQL_MODE when converting DATE/DATETIME to other data types.
+*/
+bool Item::get_temporal_with_sql_mode(MYSQL_TIME *ltime)
+{
+ return get_date(ltime, field_type() == MYSQL_TYPE_TIME
+ ? TIME_TIME_ONLY
+ : sql_mode_for_dates(current_thd));
+}
+
+
+bool Item::is_null_from_temporal()
+{
+ MYSQL_TIME ltime;
+ return get_temporal_with_sql_mode(&ltime);
+}
+
+
+String *Item::val_string_from_date(String *str)
+{
+ MYSQL_TIME ltime;
+ if (get_temporal_with_sql_mode(&ltime) ||
+ str->alloc(MAX_DATE_STRING_REP_LENGTH))
+ {
+ null_value= 1;
+ return (String *) 0;
+ }
+ str->length(my_TIME_to_str(&ltime, const_cast<char*>(str->ptr()), decimals));
+ str->set_charset(&my_charset_numeric);
+ return str;
+}
+
+
+my_decimal *Item::val_decimal_from_real(my_decimal *decimal_value)
+{
+ double nr= val_real();
+ if (null_value)
+ return 0;
+ double2my_decimal(E_DEC_FATAL_ERROR, nr, decimal_value);
+ return (decimal_value);
+}
+
+
+my_decimal *Item::val_decimal_from_int(my_decimal *decimal_value)
+{
+ longlong nr= val_int();
+ if (null_value)
+ return 0;
+ int2my_decimal(E_DEC_FATAL_ERROR, nr, unsigned_flag, decimal_value);
+ return decimal_value;
+}
+
+
+my_decimal *Item::val_decimal_from_string(my_decimal *decimal_value)
+{
+ String *res;
+
+ if (!(res= val_str(&str_value)))
+ return 0;
+
+ if (str2my_decimal(E_DEC_FATAL_ERROR & ~E_DEC_BAD_NUM,
+ res->ptr(), res->length(), res->charset(),
+ decimal_value) & E_DEC_BAD_NUM)
+ {
+ ErrConvString err(res);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), "DECIMAL",
+ err.ptr());
+ }
+ return decimal_value;
+}
+
+
+my_decimal *Item::val_decimal_from_date(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_temporal_with_sql_mode(&ltime))
+ {
+ my_decimal_set_zero(decimal_value);
+ null_value= 1; // set NULL, stop processing
+ return 0;
+ }
+ return date2my_decimal(&ltime, decimal_value);
+}
+
+
+my_decimal *Item::val_decimal_from_time(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_time(&ltime))
+ {
+ my_decimal_set_zero(decimal_value);
+ return 0;
+ }
+ return date2my_decimal(&ltime, decimal_value);
+}
+
+
+longlong Item::val_int_from_date()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_temporal_with_sql_mode(&ltime))
+ return 0;
+ longlong v= TIME_to_ulonglong(&ltime);
+ return ltime.neg ? -v : v;
+}
+
+
+double Item::val_real_from_date()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_temporal_with_sql_mode(&ltime))
+ return 0;
+ return TIME_to_double(&ltime);
+}
+
+
+double Item::val_real_from_decimal()
+{
+ /* Note that fix_fields may not be called for Item_avg_field items */
+ double result;
+ my_decimal value_buff, *dec_val= val_decimal(&value_buff);
+ if (null_value)
+ return 0.0;
+ my_decimal2double(E_DEC_FATAL_ERROR, dec_val, &result);
+ return result;
+}
+
+
+longlong Item::val_int_from_decimal()
+{
+ /* Note that fix_fields may not be called for Item_avg_field items */
+ longlong result;
+ my_decimal value, *dec_val= val_decimal(&value);
+ if (null_value)
+ return 0;
+ my_decimal2int(E_DEC_FATAL_ERROR, dec_val, unsigned_flag, &result);
+ return result;
+}
+
+int Item::save_time_in_field(Field *field)
+{
+ MYSQL_TIME ltime;
+ if (get_time(&ltime))
+ return set_field_to_null_with_conversions(field, 0);
+ field->set_notnull();
+ return field->store_time_dec(&ltime, decimals);
+}
+
+
+int Item::save_date_in_field(Field *field)
+{
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, sql_mode_for_dates(current_thd)))
+ return set_field_to_null_with_conversions(field, 0);
+ field->set_notnull();
+ return field->store_time_dec(&ltime, decimals);
+}
+
+
+/*
+ Store the string value in field directly
+
+ SYNOPSIS
+ Item::save_str_value_in_field()
+ field a pointer to field where to store
+ result the pointer to the string value to be stored
+
+ DESCRIPTION
+ The method is used by Item_*::save_in_field implementations
+ when we don't need to calculate the value to store
+ See Item_string::save_in_field() implementation for example
+
+ IMPLEMENTATION
+ Check if the Item is null and stores the NULL or the
+ result value in the field accordingly.
+
+ RETURN
+ Nonzero value if error
+*/
+
+int Item::save_str_value_in_field(Field *field, String *result)
+{
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ return field->store(result->ptr(), result->length(),
+ collation.collation);
+}
+
+
+Item::Item():
+ is_expensive_cache(-1), rsize(0), name(0), orig_name(0), name_length(0),
+ fixed(0), is_autogenerated_name(TRUE),
+ collation(&my_charset_bin, DERIVATION_COERCIBLE)
+{
+ marker= 0;
+ maybe_null=null_value=with_sum_func=with_field=unsigned_flag=0;
+ in_rollup= 0;
+ decimals= 0; max_length= 0;
+ with_subselect= 0;
+ cmp_context= IMPOSSIBLE_RESULT;
+ /* Initially this item is not attached to any JOIN_TAB. */
+ join_tab_idx= MAX_TABLES;
+
+ /* Put item in free list so that we can free all items at end */
+ THD *thd= current_thd;
+ next= thd->free_list;
+ thd->free_list= this;
+ /*
+ Item constructor can be called during execution other then SQL_COM
+ command => we should check thd->lex->current_select on zero (thd->lex
+ can be uninitialised)
+ */
+ if (thd->lex->current_select)
+ {
+ enum_parsing_place place=
+ thd->lex->current_select->parsing_place;
+ if (place == SELECT_LIST ||
+ place == IN_HAVING)
+ thd->lex->current_select->select_n_having_items++;
+ }
+}
+
+/**
+ Constructor used by Item_field, Item_ref & aggregate (sum)
+ functions.
+
+ Used for duplicating lists in processing queries with temporary
+ tables.
+*/
+Item::Item(THD *thd, Item *item):
+ join_tab_idx(item->join_tab_idx),
+ is_expensive_cache(-1),
+ rsize(0),
+ str_value(item->str_value),
+ name(item->name),
+ orig_name(item->orig_name),
+ max_length(item->max_length),
+ name_length(item->name_length),
+ decimals(item->decimals),
+ marker(item->marker),
+ maybe_null(item->maybe_null),
+ in_rollup(item->in_rollup),
+ null_value(item->null_value),
+ unsigned_flag(item->unsigned_flag),
+ with_sum_func(item->with_sum_func),
+ with_field(item->with_field),
+ fixed(item->fixed),
+ is_autogenerated_name(item->is_autogenerated_name),
+ with_subselect(item->has_subquery()),
+ collation(item->collation),
+ cmp_context(item->cmp_context)
+{
+ next= thd->free_list; // Put in free list
+ thd->free_list= this;
+}
+
+
+uint Item::decimal_precision() const
+{
+ Item_result restype= result_type();
+
+ if ((restype == DECIMAL_RESULT) || (restype == INT_RESULT))
+ {
+ uint prec=
+ my_decimal_length_to_precision(max_char_length(), decimals,
+ unsigned_flag);
+ return MY_MIN(prec, DECIMAL_MAX_PRECISION);
+ }
+ return MY_MIN(max_char_length(), DECIMAL_MAX_PRECISION);
+}
+
+
+uint Item::temporal_precision(enum_field_types type)
+{
+ if (const_item() && result_type() == STRING_RESULT &&
+ !is_temporal_type(field_type()))
+ {
+ MYSQL_TIME ltime;
+ String buf, *tmp;
+ MYSQL_TIME_STATUS status;
+ DBUG_ASSERT(fixed);
+ if ((tmp= val_str(&buf)) &&
+ !(type == MYSQL_TYPE_TIME ?
+ str_to_time(tmp->charset(), tmp->ptr(), tmp->length(),
+ &ltime, TIME_TIME_ONLY, &status) :
+ str_to_datetime(tmp->charset(), tmp->ptr(), tmp->length(),
+ &ltime, TIME_FUZZY_DATES, &status)))
+ return MY_MIN(status.precision, TIME_SECOND_PART_DIGITS);
+ }
+ return MY_MIN(decimals, TIME_SECOND_PART_DIGITS);
+}
+
+
+void Item::print_item_w_name(String *str, enum_query_type query_type)
+{
+ print(str, query_type);
+
+ if (name)
+ {
+ THD *thd= current_thd;
+ str->append(STRING_WITH_LEN(" AS "));
+ append_identifier(thd, str, name, (uint) strlen(name));
+ }
+}
+
+
+void Item::print_value(String *str)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String *ptr, tmp(buff,sizeof(buff),str->charset());
+ ptr= val_str(&tmp);
+ if (!ptr)
+ str->append("NULL");
+ else
+ {
+ switch (result_type()) {
+ case STRING_RESULT:
+ append_unescaped(str, ptr->ptr(), ptr->length());
+ break;
+ case DECIMAL_RESULT:
+ case REAL_RESULT:
+ case INT_RESULT:
+ str->append(*ptr);
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ }
+}
+
+
+void Item::cleanup()
+{
+ DBUG_ENTER("Item::cleanup");
+ DBUG_PRINT("enter", ("this: %p", this));
+ fixed= 0;
+ marker= 0;
+ join_tab_idx= MAX_TABLES;
+ if (orig_name)
+ name= orig_name;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ cleanup() item if it is 'fixed'.
+
+ @param arg a dummy parameter, is not used here
+*/
+
+bool Item::cleanup_processor(uchar *arg)
+{
+ if (fixed)
+ cleanup();
+ return FALSE;
+}
+
+
+/**
+ rename item (used for views, cleanup() return original name).
+
+ @param new_name new name of item;
+*/
+
+void Item::rename(char *new_name)
+{
+ /*
+ we can compare pointers to names here, because if name was not changed,
+ pointer will be same
+ */
+ if (!orig_name && new_name != name)
+ orig_name= name;
+ name= new_name;
+}
+
+Item_result Item::cmp_type() const
+{
+ switch (field_type()) {
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ return DECIMAL_RESULT;
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_BIT:
+ return INT_RESULT;
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ return REAL_RESULT;
+ case MYSQL_TYPE_NULL:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_GEOMETRY:
+ return STRING_RESULT;
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_TIMESTAMP2:
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_TIME2:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_NEWDATE:
+ return TIME_RESULT;
+ };
+ DBUG_ASSERT(0);
+ return IMPOSSIBLE_RESULT;
+}
+
+/**
+ Traverse item tree possibly transforming it (replacing items).
+
+ This function is designed to ease transformation of Item trees.
+ Re-execution note: every such transformation is registered for
+ rollback by THD::change_item_tree() and is rolled back at the end
+ of execution by THD::rollback_item_tree_changes().
+
+ Therefore:
+ - this function can not be used at prepared statement prepare
+ (in particular, in fix_fields!), as only permanent
+ transformation of Item trees are allowed at prepare.
+ - the transformer function shall allocate new Items in execution
+ memory root (thd->mem_root) and not anywhere else: allocated
+ items will be gone in the end of execution.
+
+ If you don't need to transform an item tree, but only traverse
+ it, please use Item::walk() instead.
+
+
+ @param transformer functor that performs transformation of a subtree
+ @param arg opaque argument passed to the functor
+
+ @return
+ Returns pointer to the new subtree root. THD::change_item_tree()
+ should be called for it if transformation took place, i.e. if a
+ pointer to newly allocated item is returned.
+*/
+
+Item* Item::transform(Item_transformer transformer, uchar *arg)
+{
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+
+ return (this->*transformer)(arg);
+}
+
+
+/**
+ Create and set up an expression cache for this item
+
+ @param thd Thread handle
+ @param depends_on List of the expression parameters
+
+ @details
+ The function creates an expression cache for an item and its parameters
+ specified by the 'depends_on' list. Then the expression cache is placed
+ into a cache wrapper that is returned as the result of the function.
+
+ @returns
+ A pointer to created wrapper item if successful, NULL - otherwise
+*/
+
+Item* Item::set_expr_cache(THD *thd)
+{
+ DBUG_ENTER("Item::set_expr_cache");
+ Item_cache_wrapper *wrapper;
+ if ((wrapper= new Item_cache_wrapper(this)) &&
+ !wrapper->fix_fields(thd, (Item**)&wrapper))
+ {
+ if (wrapper->set_cache(thd))
+ DBUG_RETURN(NULL);
+ DBUG_RETURN(wrapper);
+ }
+ DBUG_RETURN(NULL);
+}
+
+
+Item_ident::Item_ident(Name_resolution_context *context_arg,
+ const char *db_name_arg,const char *table_name_arg,
+ const char *field_name_arg)
+ :orig_db_name(db_name_arg), orig_table_name(table_name_arg),
+ orig_field_name(field_name_arg), context(context_arg),
+ db_name(db_name_arg), table_name(table_name_arg),
+ field_name(field_name_arg),
+ alias_name_used(FALSE), cached_field_index(NO_CACHED_FIELD_INDEX),
+ cached_table(0), depended_from(0), can_be_depended(TRUE)
+{
+ name = (char*) field_name_arg;
+}
+
+
+Item_ident::Item_ident(TABLE_LIST *view_arg, const char *field_name_arg)
+ :orig_db_name(NullS), orig_table_name(view_arg->table_name),
+ orig_field_name(field_name_arg), context(&view_arg->view->select_lex.context),
+ db_name(NullS), table_name(view_arg->alias),
+ field_name(field_name_arg),
+ alias_name_used(FALSE), cached_field_index(NO_CACHED_FIELD_INDEX),
+ cached_table(NULL), depended_from(NULL), can_be_depended(TRUE)
+{
+ name = (char*) field_name_arg;
+}
+
+
+/**
+ Constructor used by Item_field & Item_*_ref (see Item comment)
+*/
+
+Item_ident::Item_ident(THD *thd, Item_ident *item)
+ :Item(thd, item),
+ orig_db_name(item->orig_db_name),
+ orig_table_name(item->orig_table_name),
+ orig_field_name(item->orig_field_name),
+ context(item->context),
+ db_name(item->db_name),
+ table_name(item->table_name),
+ field_name(item->field_name),
+ alias_name_used(item->alias_name_used),
+ cached_field_index(item->cached_field_index),
+ cached_table(item->cached_table),
+ depended_from(item->depended_from),
+ can_be_depended(item->can_be_depended)
+{}
+
+void Item_ident::cleanup()
+{
+ DBUG_ENTER("Item_ident::cleanup");
+ bool was_fixed= fixed;
+ Item::cleanup();
+ db_name= orig_db_name;
+ table_name= orig_table_name;
+ field_name= orig_field_name;
+ /* Store if this Item was depended */
+ if (was_fixed)
+ {
+ /*
+ We can trust that depended_from set correctly only if this item
+ was fixed
+ */
+ can_be_depended= MY_TEST(depended_from);
+ }
+ DBUG_VOID_RETURN;
+}
+
+bool Item_ident::remove_dependence_processor(uchar * arg)
+{
+ DBUG_ENTER("Item_ident::remove_dependence_processor");
+ if (get_depended_from() == (st_select_lex *) arg)
+ depended_from= 0;
+ context= &((st_select_lex *) arg)->context;
+ DBUG_RETURN(0);
+}
+
+
+bool Item_ident::collect_outer_ref_processor(uchar *param)
+{
+ Collect_deps_prm *prm= (Collect_deps_prm *)param;
+ if (depended_from &&
+ depended_from->nest_level_base == prm->nest_level_base &&
+ depended_from->nest_level < prm->nest_level)
+ {
+ if (prm->collect)
+ prm->parameters->add_unique(this, &cmp_items);
+ else
+ prm->count++;
+ }
+ return FALSE;
+}
+
+
+/**
+ Store the pointer to this item field into a list if not already there.
+
+ The method is used by Item::walk to collect all unique Item_field objects
+ from a tree of Items into a set of items represented as a list.
+
+ Item_cond::walk() and Item_func::walk() stop the evaluation of the
+ processor function for its arguments once the processor returns
+ true.Therefore in order to force this method being called for all item
+ arguments in a condition the method must return false.
+
+ @param arg pointer to a List<Item_field>
+
+ @return
+ FALSE to force the evaluation of collect_item_field_processor
+ for the subsequent items.
+*/
+
+bool Item_field::collect_item_field_processor(uchar *arg)
+{
+ DBUG_ENTER("Item_field::collect_item_field_processor");
+ DBUG_PRINT("info", ("%s", field->field_name ? field->field_name : "noname"));
+ List<Item_field> *item_list= (List<Item_field>*) arg;
+ List_iterator<Item_field> item_list_it(*item_list);
+ Item_field *curr_item;
+ while ((curr_item= item_list_it++))
+ {
+ if (curr_item->eq(this, 1))
+ DBUG_RETURN(FALSE); /* Already in the set. */
+ }
+ item_list->push_back(this);
+ DBUG_RETURN(FALSE);
+}
+
+
+bool Item_field::add_field_to_set_processor(uchar *arg)
+{
+ DBUG_ENTER("Item_field::add_field_to_set_processor");
+ DBUG_PRINT("info", ("%s", field->field_name ? field->field_name : "noname"));
+ TABLE *table= (TABLE *) arg;
+ if (field->table == table)
+ bitmap_set_bit(&table->tmp_set, field->field_index);
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Check if an Item_field references some field from a list of fields.
+
+ Check whether the Item_field represented by 'this' references any
+ of the fields in the keyparts passed via 'arg'. Used with the
+ method Item::walk() to test whether any keypart in a sequence of
+ keyparts is referenced in an expression.
+
+ @param arg Field being compared, arg must be of type Field
+
+ @retval
+ TRUE if 'this' references the field 'arg'
+ @retval
+ FALSE otherwise
+*/
+
+bool Item_field::find_item_in_field_list_processor(uchar *arg)
+{
+ KEY_PART_INFO *first_non_group_part= *((KEY_PART_INFO **) arg);
+ KEY_PART_INFO *last_part= *(((KEY_PART_INFO **) arg) + 1);
+ KEY_PART_INFO *cur_part;
+
+ for (cur_part= first_non_group_part; cur_part != last_part; cur_part++)
+ {
+ if (field->eq(cur_part->field))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Mark field in read_map
+
+ NOTES
+ This is used by filesort to register used fields in a a temporary
+ column read set or to register used fields in a view
+*/
+
+bool Item_field::register_field_in_read_map(uchar *arg)
+{
+ TABLE *table= (TABLE *) arg;
+ if (field->table == table || !table)
+ bitmap_set_bit(field->table->read_set, field->field_index);
+ if (field->vcol_info && field->vcol_info->expr_item)
+ return field->vcol_info->expr_item->walk(&Item::register_field_in_read_map,
+ 1, arg);
+ return 0;
+}
+
+/*
+ @brief
+ Mark field in bitmap supplied as *arg
+*/
+
+bool Item_field::register_field_in_bitmap(uchar *arg)
+{
+ MY_BITMAP *bitmap= (MY_BITMAP *) arg;
+ DBUG_ASSERT(bitmap);
+ bitmap_set_bit(bitmap, field->field_index);
+ return 0;
+}
+
+
+/*
+ Mark field in write_map
+
+ NOTES
+ This is used by UPDATE to register underlying fields of used view fields.
+*/
+
+bool Item_field::register_field_in_write_map(uchar *arg)
+{
+ TABLE *table= (TABLE *) arg;
+ if (field->table == table || !table)
+ bitmap_set_bit(field->table->write_set, field->field_index);
+ return 0;
+}
+
+
+bool Item::check_cols(uint c)
+{
+ if (c != 1)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), c);
+ return 1;
+ }
+ return 0;
+}
+
+
+void Item::set_name(const char *str, uint length, CHARSET_INFO *cs)
+{
+ if (!length)
+ {
+ /* Empty string, used by AS or internal function like last_insert_id() */
+ name= (char*) str;
+ name_length= 0;
+ return;
+ }
+
+ const char *str_start= str;
+ if (!cs->ctype || cs->mbminlen > 1)
+ {
+ str+= cs->cset->scan(cs, str, str + length, MY_SEQ_SPACES);
+ length-= str - str_start;
+ }
+ else
+ {
+ /*
+ This will probably need a better implementation in the future:
+ a function in CHARSET_INFO structure.
+ */
+ while (length && !my_isgraph(cs,*str))
+ { // Fix problem with yacc
+ length--;
+ str++;
+ }
+ }
+ if (str != str_start && !is_autogenerated_name)
+ {
+ char buff[SAFE_NAME_LEN];
+ strmake(buff, str_start,
+ MY_MIN(sizeof(buff)-1, length + (int) (str-str_start)));
+
+ if (length == 0)
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_NAME_BECOMES_EMPTY, ER(ER_NAME_BECOMES_EMPTY),
+ buff);
+ else
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_REMOVED_SPACES, ER(ER_REMOVED_SPACES),
+ buff);
+ }
+ if (!my_charset_same(cs, system_charset_info))
+ {
+ size_t res_length;
+ name= sql_strmake_with_convert(str, length, cs,
+ MAX_ALIAS_NAME, system_charset_info,
+ &res_length);
+ name_length= res_length;
+ }
+ else
+ name= sql_strmake(str, (name_length= MY_MIN(length,MAX_ALIAS_NAME)));
+}
+
+
+void Item::set_name_no_truncate(const char *str, uint length, CHARSET_INFO *cs)
+{
+ if (!my_charset_same(cs, system_charset_info))
+ {
+ size_t res_length;
+ name= sql_strmake_with_convert(str, length, cs,
+ UINT_MAX, system_charset_info,
+ &res_length);
+ name_length= res_length;
+ }
+ else
+ name= sql_strmake(str, (name_length= length));
+}
+
+
+void Item::set_name_for_rollback(THD *thd, const char *str, uint length,
+ CHARSET_INFO *cs)
+{
+ char *old_name, *new_name;
+ old_name= name;
+ set_name(str, length, cs);
+ new_name= name;
+ if (old_name != new_name)
+ {
+ name= old_name;
+ thd->change_item_tree((Item **) &name, (Item *) new_name);
+ }
+}
+
+
+/**
+ @details
+ This function is called when:
+ - Comparing items in the WHERE clause (when doing where optimization)
+ - When trying to find an ORDER BY/GROUP BY item in the SELECT part
+*/
+
+bool Item::eq(const Item *item, bool binary_cmp) const
+{
+ /*
+ Note, that this is never TRUE if item is a Item_param:
+ for all basic constants we have special checks, and Item_param's
+ type() can be only among basic constant types.
+ */
+ return type() == item->type() && name && item->name &&
+ !my_strcasecmp(system_charset_info,name,item->name);
+}
+
+
+Item *Item::safe_charset_converter(CHARSET_INFO *tocs)
+{
+ if (!needs_charset_converter(tocs))
+ return this;
+ Item_func_conv_charset *conv= new Item_func_conv_charset(this, tocs, 1);
+ return conv->safe ? conv : NULL;
+}
+
+
+/**
+ @details
+ Created mostly for mysql_prepare_table(). Important
+ when a string ENUM/SET column is described with a numeric default value:
+
+ CREATE TABLE t1(a SET('a') DEFAULT 1);
+
+ We cannot use generic Item::safe_charset_converter(), because
+ the latter returns a non-fixed Item, so val_str() crashes afterwards.
+ Override Item_num method, to return a fixed item.
+*/
+Item *Item_num::safe_charset_converter(CHARSET_INFO *tocs)
+{
+ /*
+ Item_num returns pure ASCII result,
+ so conversion is needed only in case of "tricky" character
+ sets like UCS2. If tocs is not "tricky", return the item itself.
+ */
+ if (!(tocs->state & MY_CS_NONASCII))
+ return this;
+
+ Item *conv;
+ if ((conv= const_charset_converter(tocs, true)))
+ conv->fix_char_length(max_char_length());
+ return conv;
+}
+
+
+/**
+ Create character set converter for constant items
+ using Item_null, Item_string or Item_static_string_func.
+
+ @param tocs Character set to to convert the string to.
+ @param lossless Whether data loss is acceptable.
+ @param func_name Function name, or NULL.
+
+ @return this, if conversion is not needed,
+ NULL, if safe conversion is not possible, or
+ a new item representing the converted constant.
+*/
+Item *Item::const_charset_converter(CHARSET_INFO *tocs,
+ bool lossless,
+ const char *func_name)
+{
+ DBUG_ASSERT(const_item());
+ DBUG_ASSERT(fixed);
+ StringBuffer<64>tmp;
+ String *s= val_str(&tmp);
+ if (!s)
+ return new Item_null((char *) func_name, tocs);
+
+ if (!needs_charset_converter(s->length(), tocs))
+ {
+ if (collation.collation == &my_charset_bin && tocs != &my_charset_bin &&
+ !this->check_well_formed_result(s, true))
+ return NULL;
+ return this;
+ }
+
+ uint conv_errors;
+ Item_string *conv= func_name ?
+ new Item_static_string_func(func_name,
+ s, tocs, &conv_errors,
+ collation.derivation,
+ collation.repertoire) :
+ new Item_string(s, tocs, &conv_errors,
+ collation.derivation,
+ collation.repertoire);
+
+ if (!conv || (conv_errors && lossless))
+ {
+ /*
+ Safe conversion is not possible (or EOM).
+ We could not convert a string into the requested character set
+ without data loss. The target charset does not cover all the
+ characters from the string. Operation cannot be done correctly.
+ */
+ return NULL;
+ }
+ if (s->charset() == &my_charset_bin && tocs != &my_charset_bin &&
+ !conv->check_well_formed_result(true))
+ return NULL;
+ return conv;
+}
+
+
+Item *Item_param::safe_charset_converter(CHARSET_INFO *tocs)
+{
+ /*
+ Return "this" if in prepare. result_type may change at execition time,
+ to it's possible that the converter will not be needed at all:
+
+ PREPARE stmt FROM 'SELECT * FROM t1 WHERE field = ?';
+ SET @@arg= 1;
+ EXECUTE stms USING @arg;
+
+ In the above example result_type is STRING_RESULT at prepare time,
+ and INT_RESULT at execution time.
+ */
+ return !const_item() || state == NULL_VALUE ?
+ this : const_charset_converter(tocs, true);
+}
+
+
+/**
+ Get the value of the function as a MYSQL_TIME structure.
+ As a extra convenience the time structure is reset on error or NULL values!
+*/
+
+bool Item::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ if (field_type() == MYSQL_TYPE_TIME)
+ fuzzydate|= TIME_TIME_ONLY;
+
+ switch (result_type()) {
+ case INT_RESULT:
+ {
+ longlong value= val_int();
+ bool neg= !unsigned_flag && value < 0;
+ if (field_type() == MYSQL_TYPE_YEAR)
+ {
+ if (max_length == 2)
+ {
+ if (value < 70)
+ value+= 2000;
+ else if (value <= 1900)
+ value+= 1900;
+ }
+ value*= 10000; /* make it YYYYMMHH */
+ }
+ if (null_value || int_to_datetime_with_warn(neg, neg ? -value : value,
+ ltime, fuzzydate,
+ field_name_or_null()))
+ goto err;
+ break;
+ }
+ case REAL_RESULT:
+ {
+ double value= val_real();
+ if (null_value || double_to_datetime_with_warn(value, ltime, fuzzydate,
+ field_name_or_null()))
+ goto err;
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ my_decimal value, *res;
+ if (!(res= val_decimal(&value)) ||
+ decimal_to_datetime_with_warn(res, ltime, fuzzydate,
+ field_name_or_null()))
+ goto err;
+ break;
+ }
+ case STRING_RESULT:
+ {
+ char buff[40];
+ String tmp(buff,sizeof(buff), &my_charset_bin),*res;
+ if (!(res=val_str(&tmp)) ||
+ str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(),
+ ltime, fuzzydate))
+ goto err;
+ break;
+ }
+ default:
+ DBUG_ASSERT(0);
+ }
+
+ return null_value= 0;
+
+err:
+ /*
+ if the item was not null and convertion failed, we return a zero date
+ if allowed, otherwise - null.
+ */
+ bzero((char*) ltime,sizeof(*ltime));
+ return null_value|= !(fuzzydate & TIME_FUZZY_DATES);
+}
+
+bool Item::get_seconds(ulonglong *sec, ulong *sec_part)
+{
+ if (decimals == 0)
+ { // optimize for an important special case
+ longlong val= val_int();
+ bool neg= val < 0 && !unsigned_flag;
+ *sec= neg ? -val : val;
+ *sec_part= 0;
+ return neg;
+ }
+ my_decimal tmp, *dec= val_decimal(&tmp);
+ if (!dec)
+ return 0;
+ return my_decimal2seconds(dec, sec, sec_part);
+}
+
+CHARSET_INFO *Item::default_charset()
+{
+ return current_thd->variables.collation_connection;
+}
+
+
+/*
+ Save value in field, but don't give any warnings
+
+ NOTES
+ This is used to temporary store and retrieve a value in a column,
+ for example in opt_range to adjust the key value to fit the column.
+*/
+
+int Item::save_in_field_no_warnings(Field *field, bool no_conversions)
+{
+ int res;
+ TABLE *table= field->table;
+ THD *thd= table->in_use;
+ enum_check_fields tmp= thd->count_cuted_fields;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->write_set);
+ ulonglong sql_mode= thd->variables.sql_mode;
+ thd->variables.sql_mode&= ~(MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE);
+ thd->variables.sql_mode|= MODE_INVALID_DATES;
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+
+ res= save_in_field(field, no_conversions);
+
+ thd->count_cuted_fields= tmp;
+ dbug_tmp_restore_column_map(table->write_set, old_map);
+ thd->variables.sql_mode= sql_mode;
+ return res;
+}
+
+
+/*****************************************************************************
+ Item_sp_variable methods
+*****************************************************************************/
+
+Item_sp_variable::Item_sp_variable(char *sp_var_name_str,
+ uint sp_var_name_length)
+ :m_thd(0)
+#ifndef DBUG_OFF
+ , m_sp(0)
+#endif
+{
+ m_name.str= sp_var_name_str;
+ m_name.length= sp_var_name_length;
+}
+
+
+bool Item_sp_variable::fix_fields(THD *thd, Item **)
+{
+ Item *it;
+
+ m_thd= thd; /* NOTE: this must be set before any this_xxx() */
+ it= this_item();
+
+ DBUG_ASSERT(it->fixed);
+
+ max_length= it->max_length;
+ decimals= it->decimals;
+ unsigned_flag= it->unsigned_flag;
+ fixed= 1;
+ collation.set(it->collation.collation, it->collation.derivation);
+
+ return FALSE;
+}
+
+
+double Item_sp_variable::val_real()
+{
+ DBUG_ASSERT(fixed);
+ Item *it= this_item();
+ double ret= it->val_real();
+ null_value= it->null_value;
+ return ret;
+}
+
+
+longlong Item_sp_variable::val_int()
+{
+ DBUG_ASSERT(fixed);
+ Item *it= this_item();
+ longlong ret= it->val_int();
+ null_value= it->null_value;
+ return ret;
+}
+
+
+String *Item_sp_variable::val_str(String *sp)
+{
+ DBUG_ASSERT(fixed);
+ Item *it= this_item();
+ String *res= it->val_str(sp);
+
+ null_value= it->null_value;
+
+ if (!res)
+ return NULL;
+
+ /*
+ This way we mark returned value of val_str as const,
+ so that various functions (e.g. CONCAT) won't try to
+ modify the value of the Item. Analogous mechanism is
+ implemented for Item_param.
+ Without this trick Item_splocal could be changed as a
+ side-effect of expression computation. Here is an example
+ of what happens without it: suppose x is varchar local
+ variable in a SP with initial value 'ab' Then
+ select concat(x,'c');
+ would change x's value to 'abc', as Item_func_concat::val_str()
+ would use x's internal buffer to compute the result.
+ This is intended behaviour of Item_func_concat. Comments to
+ Item_param class contain some more details on the topic.
+ */
+
+ if (res != &str_value)
+ str_value.set(res->ptr(), res->length(), res->charset());
+ else
+ res->mark_as_const();
+
+ return &str_value;
+}
+
+
+my_decimal *Item_sp_variable::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed);
+ Item *it= this_item();
+ my_decimal *val= it->val_decimal(decimal_value);
+ null_value= it->null_value;
+ return val;
+}
+
+
+bool Item_sp_variable::is_null()
+{
+ return this_item()->is_null();
+}
+
+
+/*****************************************************************************
+ Item_splocal methods
+*****************************************************************************/
+
+Item_splocal::Item_splocal(const LEX_STRING &sp_var_name,
+ uint sp_var_idx,
+ enum_field_types sp_var_type,
+ uint pos_in_q, uint len_in_q)
+ :Item_sp_variable(sp_var_name.str, sp_var_name.length),
+ m_var_idx(sp_var_idx),
+ limit_clause_param(FALSE),
+ pos_in_query(pos_in_q), len_in_query(len_in_q)
+{
+ maybe_null= TRUE;
+
+ m_type= sp_map_item_type(sp_var_type);
+ m_field_type= sp_var_type;
+ m_result_type= sp_map_result_type(sp_var_type);
+}
+
+
+Item *
+Item_splocal::this_item()
+{
+ DBUG_ASSERT(m_sp == m_thd->spcont->sp);
+
+ return m_thd->spcont->get_item(m_var_idx);
+}
+
+const Item *
+Item_splocal::this_item() const
+{
+ DBUG_ASSERT(m_sp == m_thd->spcont->sp);
+
+ return m_thd->spcont->get_item(m_var_idx);
+}
+
+
+Item **
+Item_splocal::this_item_addr(THD *thd, Item **)
+{
+ DBUG_ASSERT(m_sp == thd->spcont->sp);
+
+ return thd->spcont->get_item_addr(m_var_idx);
+}
+
+
+void Item_splocal::print(String *str, enum_query_type)
+{
+ str->reserve(m_name.length+8);
+ str->append(m_name.str, m_name.length);
+ str->append('@');
+ str->qs_append(m_var_idx);
+}
+
+
+bool Item_splocal::set_value(THD *thd, sp_rcontext *ctx, Item **it)
+{
+ return ctx->set_variable(thd, get_var_idx(), it);
+}
+
+
+/*****************************************************************************
+ Item_case_expr methods
+*****************************************************************************/
+
+Item_case_expr::Item_case_expr(uint case_expr_id)
+ :Item_sp_variable( C_STRING_WITH_LEN("case_expr")),
+ m_case_expr_id(case_expr_id)
+{
+}
+
+
+Item *
+Item_case_expr::this_item()
+{
+ DBUG_ASSERT(m_sp == m_thd->spcont->sp);
+
+ return m_thd->spcont->get_case_expr(m_case_expr_id);
+}
+
+
+
+const Item *
+Item_case_expr::this_item() const
+{
+ DBUG_ASSERT(m_sp == m_thd->spcont->sp);
+
+ return m_thd->spcont->get_case_expr(m_case_expr_id);
+}
+
+
+Item **
+Item_case_expr::this_item_addr(THD *thd, Item **)
+{
+ DBUG_ASSERT(m_sp == thd->spcont->sp);
+
+ return thd->spcont->get_case_expr_addr(m_case_expr_id);
+}
+
+
+void Item_case_expr::print(String *str, enum_query_type)
+{
+ if (str->reserve(MAX_INT_WIDTH + sizeof("case_expr@")))
+ return; /* purecov: inspected */
+ (void) str->append(STRING_WITH_LEN("case_expr@"));
+ str->qs_append(m_case_expr_id);
+}
+
+
+/*****************************************************************************
+ Item_name_const methods
+*****************************************************************************/
+
+double Item_name_const::val_real()
+{
+ DBUG_ASSERT(fixed);
+ double ret= value_item->val_real();
+ null_value= value_item->null_value;
+ return ret;
+}
+
+
+longlong Item_name_const::val_int()
+{
+ DBUG_ASSERT(fixed);
+ longlong ret= value_item->val_int();
+ null_value= value_item->null_value;
+ return ret;
+}
+
+
+String *Item_name_const::val_str(String *sp)
+{
+ DBUG_ASSERT(fixed);
+ String *ret= value_item->val_str(sp);
+ null_value= value_item->null_value;
+ return ret;
+}
+
+
+my_decimal *Item_name_const::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed);
+ my_decimal *val= value_item->val_decimal(decimal_value);
+ null_value= value_item->null_value;
+ return val;
+}
+
+
+bool Item_name_const::is_null()
+{
+ return value_item->is_null();
+}
+
+
+Item_name_const::Item_name_const(Item *name_arg, Item *val):
+ value_item(val), name_item(name_arg)
+{
+ Item::maybe_null= TRUE;
+ valid_args= true;
+ if (!name_item->basic_const_item())
+ goto err;
+
+ if (value_item->basic_const_item())
+ return; // ok
+
+ if (value_item->type() == FUNC_ITEM)
+ {
+ Item_func *value_func= (Item_func *) value_item;
+ if (value_func->functype() != Item_func::COLLATE_FUNC &&
+ value_func->functype() != Item_func::NEG_FUNC)
+ goto err;
+
+ if (value_func->key_item()->basic_const_item())
+ return; // ok
+ }
+
+err:
+ valid_args= false;
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "NAME_CONST");
+}
+
+
+Item::Type Item_name_const::type() const
+{
+ /*
+ As
+ 1. one can try to create the Item_name_const passing non-constant
+ arguments, although it's incorrect and
+ 2. the type() method can be called before the fix_fields() to get
+ type information for a further type cast, e.g.
+ if (item->type() == FIELD_ITEM)
+ ((Item_field *) item)->...
+ we return NULL_ITEM in the case to avoid wrong casting.
+
+ valid_args guarantees value_item->basic_const_item(); if type is
+ FUNC_ITEM, then we have a fudged item_func_neg() on our hands
+ and return the underlying type.
+ For Item_func_set_collation()
+ e.g. NAME_CONST('name', 'value' COLLATE collation) we return its
+ 'value' argument type.
+ */
+ if (!valid_args)
+ return NULL_ITEM;
+ Item::Type value_type= value_item->type();
+ if (value_type == FUNC_ITEM)
+ {
+ /*
+ The second argument of NAME_CONST('name', 'value') must be
+ a simple constant item or a NEG_FUNC/COLLATE_FUNC.
+ */
+ DBUG_ASSERT(((Item_func *) value_item)->functype() ==
+ Item_func::NEG_FUNC ||
+ ((Item_func *) value_item)->functype() ==
+ Item_func::COLLATE_FUNC);
+ return ((Item_func *) value_item)->key_item()->type();
+ }
+ return value_type;
+}
+
+
+bool Item_name_const::fix_fields(THD *thd, Item **ref)
+{
+ char buf[128];
+ String *item_name;
+ String s(buf, sizeof(buf), &my_charset_bin);
+ s.length(0);
+
+ if (value_item->fix_fields(thd, &value_item) ||
+ name_item->fix_fields(thd, &name_item) ||
+ !value_item->const_item() ||
+ !name_item->const_item() ||
+ !(item_name= name_item->val_str(&s))) // Can't have a NULL name
+ {
+ my_error(ER_RESERVED_SYNTAX, MYF(0), "NAME_CONST");
+ return TRUE;
+ }
+ if (is_autogenerated_name)
+ {
+ set_name(item_name->ptr(), (uint) item_name->length(), system_charset_info);
+ }
+ collation.set(value_item->collation.collation, DERIVATION_IMPLICIT);
+ max_length= value_item->max_length;
+ decimals= value_item->decimals;
+ fixed= 1;
+ return FALSE;
+}
+
+
+void Item_name_const::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("NAME_CONST("));
+ name_item->print(str, query_type);
+ str->append(',');
+ value_item->print(str, query_type);
+ str->append(')');
+}
+
+
+/*
+ need a special class to adjust printing : references to aggregate functions
+ must not be printed as refs because the aggregate functions that are added to
+ the front of select list are not printed as well.
+*/
+class Item_aggregate_ref : public Item_ref
+{
+public:
+ Item_aggregate_ref(Name_resolution_context *context_arg, Item **item,
+ const char *table_name_arg, const char *field_name_arg)
+ :Item_ref(context_arg, item, table_name_arg, field_name_arg) {}
+
+ virtual inline void print (String *str, enum_query_type query_type)
+ {
+ if (ref)
+ (*ref)->print(str, query_type);
+ else
+ Item_ident::print(str, query_type);
+ }
+ virtual Ref_Type ref_type() { return AGGREGATE_REF; }
+};
+
+
+/**
+ Move SUM items out from item tree and replace with reference.
+
+ @param thd Thread handler
+ @param ref_pointer_array Pointer to array of reference fields
+ @param fields All fields in select
+ @param ref Pointer to item
+ @param skip_registered <=> function be must skipped for registered
+ SUM items
+
+ @note
+ This is from split_sum_func2() for items that should be split
+
+ All found SUM items are added FIRST in the fields list and
+ we replace the item with a reference.
+
+ thd->fatal_error() may be called if we are out of memory
+*/
+
+void Item::split_sum_func2(THD *thd, Item **ref_pointer_array,
+ List<Item> &fields, Item **ref,
+ bool skip_registered)
+{
+ /* An item of type Item_sum is registered <=> ref_by != 0 */
+ if (type() == SUM_FUNC_ITEM && skip_registered &&
+ ((Item_sum *) this)->ref_by)
+ return;
+ if ((type() != SUM_FUNC_ITEM && with_sum_func) ||
+ (type() == FUNC_ITEM &&
+ (((Item_func *) this)->functype() == Item_func::ISNOTNULLTEST_FUNC ||
+ ((Item_func *) this)->functype() == Item_func::TRIG_COND_FUNC)))
+ {
+ /* Will split complicated items and ignore simple ones */
+ split_sum_func(thd, ref_pointer_array, fields);
+ }
+ else if ((type() == SUM_FUNC_ITEM || (used_tables() & ~PARAM_TABLE_BIT)) &&
+ type() != SUBSELECT_ITEM &&
+ (type() != REF_ITEM ||
+ ((Item_ref*)this)->ref_type() == Item_ref::VIEW_REF))
+ {
+ /*
+ Replace item with a reference so that we can easily calculate
+ it (in case of sum functions) or copy it (in case of fields)
+
+ The test above is to ensure we don't do a reference for things
+ that are constants (PARAM_TABLE_BIT is in effect a constant)
+ or already referenced (for example an item in HAVING)
+ Exception is Item_direct_view_ref which we need to convert to
+ Item_ref to allow fields from view being stored in tmp table.
+ */
+ Item_aggregate_ref *item_ref;
+ uint el= fields.elements;
+ /*
+ If this is an item_ref, get the original item
+ This is a safety measure if this is called for things that is
+ already a reference.
+ */
+ Item *real_itm= real_item();
+
+ ref_pointer_array[el]= real_itm;
+ if (!(item_ref= new Item_aggregate_ref(&thd->lex->current_select->context,
+ ref_pointer_array + el, 0, name)))
+ return; // fatal_error is set
+ if (type() == SUM_FUNC_ITEM)
+ item_ref->depended_from= ((Item_sum *) this)->depended_from();
+ fields.push_front(real_itm);
+ thd->change_item_tree(ref, item_ref);
+ }
+}
+
+
+static bool
+left_is_superset(DTCollation *left, DTCollation *right)
+{
+ /* Allow convert to Unicode */
+ if (left->collation->state & MY_CS_UNICODE &&
+ (left->derivation < right->derivation ||
+ (left->derivation == right->derivation &&
+ (!(right->collation->state & MY_CS_UNICODE) ||
+ /* The code below makes 4-byte utf8 a superset over 3-byte utf8 */
+ (left->collation->state & MY_CS_UNICODE_SUPPLEMENT &&
+ !(right->collation->state & MY_CS_UNICODE_SUPPLEMENT) &&
+ left->collation->mbmaxlen > right->collation->mbmaxlen &&
+ left->collation->mbminlen == right->collation->mbminlen)))))
+ return TRUE;
+ /* Allow convert from ASCII */
+ if (right->repertoire == MY_REPERTOIRE_ASCII &&
+ (left->derivation < right->derivation ||
+ (left->derivation == right->derivation &&
+ !(left->repertoire == MY_REPERTOIRE_ASCII))))
+ return TRUE;
+ /* Disallow conversion otherwise */
+ return FALSE;
+}
+
+/**
+ Aggregate two collations together taking
+ into account their coercibility (aka derivation):.
+
+ 0 == DERIVATION_EXPLICIT - an explicitly written COLLATE clause @n
+ 1 == DERIVATION_NONE - a mix of two different collations @n
+ 2 == DERIVATION_IMPLICIT - a column @n
+ 3 == DERIVATION_COERCIBLE - a string constant.
+
+ The most important rules are:
+ -# If collations are the same:
+ chose this collation, and the strongest derivation.
+ -# If collations are different:
+ - Character sets may differ, but only if conversion without
+ data loss is possible. The caller provides flags whether
+ character set conversion attempts should be done. If no
+ flags are substituted, then the character sets must be the same.
+ Currently processed flags are:
+ MY_COLL_ALLOW_SUPERSET_CONV - allow conversion to a superset
+ MY_COLL_ALLOW_COERCIBLE_CONV - allow conversion of a coercible value
+ - two EXPLICIT collations produce an error, e.g. this is wrong:
+ CONCAT(expr1 collate latin1_swedish_ci, expr2 collate latin1_german_ci)
+ - the side with smaller derivation value wins,
+ i.e. a column is stronger than a string constant,
+ an explicit COLLATE clause is stronger than a column.
+ - if derivations are the same, we have DERIVATION_NONE,
+ we'll wait for an explicit COLLATE clause which possibly can
+ come from another argument later: for example, this is valid,
+ but we don't know yet when collecting the first two arguments:
+ @code
+ CONCAT(latin1_swedish_ci_column,
+ latin1_german1_ci_column,
+ expr COLLATE latin1_german2_ci)
+ @endcode
+*/
+
+bool DTCollation::aggregate(DTCollation &dt, uint flags)
+{
+ if (!my_charset_same(collation, dt.collation))
+ {
+ /*
+ We do allow to use binary strings (like BLOBS)
+ together with character strings.
+ Binaries have more precedence than a character
+ string of the same derivation.
+ */
+ if (collation == &my_charset_bin)
+ {
+ if (derivation <= dt.derivation)
+ {
+ /* Do nothing */
+ }
+ else
+ {
+ set(dt);
+ }
+ }
+ else if (dt.collation == &my_charset_bin)
+ {
+ if (dt.derivation <= derivation)
+ {
+ set(dt);
+ }
+ }
+ else if ((flags & MY_COLL_ALLOW_SUPERSET_CONV) &&
+ left_is_superset(this, &dt))
+ {
+ /* Do nothing */
+ }
+ else if ((flags & MY_COLL_ALLOW_SUPERSET_CONV) &&
+ left_is_superset(&dt, this))
+ {
+ set(dt);
+ }
+ else if ((flags & MY_COLL_ALLOW_COERCIBLE_CONV) &&
+ derivation < dt.derivation &&
+ dt.derivation >= DERIVATION_SYSCONST)
+ {
+ /* Do nothing */
+ }
+ else if ((flags & MY_COLL_ALLOW_COERCIBLE_CONV) &&
+ dt.derivation < derivation &&
+ derivation >= DERIVATION_SYSCONST)
+ {
+ set(dt);
+ }
+ else
+ {
+ // Cannot apply conversion
+ set(&my_charset_bin, DERIVATION_NONE,
+ (dt.repertoire|repertoire));
+ return 1;
+ }
+ }
+ else if (derivation < dt.derivation)
+ {
+ /* Do nothing */
+ }
+ else if (dt.derivation < derivation)
+ {
+ set(dt);
+ }
+ else
+ {
+ if (collation == dt.collation)
+ {
+ /* Do nothing */
+ }
+ else
+ {
+ if (derivation == DERIVATION_EXPLICIT)
+ {
+ set(0, DERIVATION_NONE, 0);
+ return 1;
+ }
+ if (collation->state & MY_CS_BINSORT)
+ return 0;
+ if (dt.collation->state & MY_CS_BINSORT)
+ {
+ set(dt);
+ return 0;
+ }
+ CHARSET_INFO *bin= get_charset_by_csname(collation->csname,
+ MY_CS_BINSORT,MYF(0));
+ set(bin, DERIVATION_NONE);
+ }
+ }
+ repertoire|= dt.repertoire;
+ return 0;
+}
+
+/******************************/
+static
+void my_coll_agg_error(DTCollation &c1, DTCollation &c2, const char *fname)
+{
+ my_error(ER_CANT_AGGREGATE_2COLLATIONS,MYF(0),
+ c1.collation->name,c1.derivation_name(),
+ c2.collation->name,c2.derivation_name(),
+ fname);
+}
+
+
+static
+void my_coll_agg_error(DTCollation &c1, DTCollation &c2, DTCollation &c3,
+ const char *fname)
+{
+ my_error(ER_CANT_AGGREGATE_3COLLATIONS,MYF(0),
+ c1.collation->name,c1.derivation_name(),
+ c2.collation->name,c2.derivation_name(),
+ c3.collation->name,c3.derivation_name(),
+ fname);
+}
+
+
+static
+void my_coll_agg_error(Item** args, uint count, const char *fname,
+ int item_sep)
+{
+ if (count == 2)
+ my_coll_agg_error(args[0]->collation, args[item_sep]->collation, fname);
+ else if (count == 3)
+ my_coll_agg_error(args[0]->collation, args[item_sep]->collation,
+ args[2*item_sep]->collation, fname);
+ else
+ my_error(ER_CANT_AGGREGATE_NCOLLATIONS,MYF(0),fname);
+}
+
+
+bool agg_item_collations(DTCollation &c, const char *fname,
+ Item **av, uint count, uint flags, int item_sep)
+{
+ uint i;
+ Item **arg;
+ bool unknown_cs= 0;
+
+ c.set(av[0]->collation);
+ for (i= 1, arg= &av[item_sep]; i < count; i++, arg+= item_sep)
+ {
+ if (c.aggregate((*arg)->collation, flags))
+ {
+ if (c.derivation == DERIVATION_NONE &&
+ c.collation == &my_charset_bin)
+ {
+ unknown_cs= 1;
+ continue;
+ }
+ my_coll_agg_error(av, count, fname, item_sep);
+ return TRUE;
+ }
+ }
+
+ if (unknown_cs &&
+ c.derivation != DERIVATION_EXPLICIT)
+ {
+ my_coll_agg_error(av, count, fname, item_sep);
+ return TRUE;
+ }
+
+ if ((flags & MY_COLL_DISALLOW_NONE) &&
+ c.derivation == DERIVATION_NONE)
+ {
+ my_coll_agg_error(av, count, fname, item_sep);
+ return TRUE;
+ }
+
+ /* If all arguments where numbers, reset to @@collation_connection */
+ if (flags & MY_COLL_ALLOW_NUMERIC_CONV &&
+ c.derivation == DERIVATION_NUMERIC)
+ c.set(Item::default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_NUMERIC);
+
+ return FALSE;
+}
+
+
+bool agg_item_collations_for_comparison(DTCollation &c, const char *fname,
+ Item **av, uint count, uint flags)
+{
+ return (agg_item_collations(c, fname, av, count,
+ flags | MY_COLL_DISALLOW_NONE, 1));
+}
+
+
+bool agg_item_set_converter(DTCollation &coll, const char *fname,
+ Item **args, uint nargs, uint flags, int item_sep)
+{
+ Item **arg, *safe_args[2]= {NULL, NULL};
+
+ /*
+ For better error reporting: save the first and the second argument.
+ We need this only if the the number of args is 3 or 2:
+ - for a longer argument list, "Illegal mix of collations"
+ doesn't display each argument's characteristics.
+ - if nargs is 1, then this error cannot happen.
+ */
+ if (nargs >=2 && nargs <= 3)
+ {
+ safe_args[0]= args[0];
+ safe_args[1]= args[item_sep];
+ }
+
+ THD *thd= current_thd;
+ bool res= FALSE;
+ uint i;
+
+ /*
+ In case we're in statement prepare, create conversion item
+ in its memory: it will be reused on each execute.
+ */
+ Query_arena backup;
+ Query_arena *arena= thd->stmt_arena->is_stmt_prepare() ?
+ thd->activate_stmt_arena_if_needed(&backup) :
+ NULL;
+
+ for (i= 0, arg= args; i < nargs; i++, arg+= item_sep)
+ {
+ Item* conv= (*arg)->safe_charset_converter(coll.collation);
+ if (conv == *arg)
+ continue;
+ if (!conv && ((*arg)->collation.repertoire == MY_REPERTOIRE_ASCII))
+ conv= new Item_func_conv_charset(*arg, coll.collation, 1);
+
+ if (!conv)
+ {
+ if (nargs >=2 && nargs <= 3)
+ {
+ /* restore the original arguments for better error message */
+ args[0]= safe_args[0];
+ args[item_sep]= safe_args[1];
+ }
+ my_coll_agg_error(args, nargs, fname, item_sep);
+ res= TRUE;
+ break; // we cannot return here, we need to restore "arena".
+ }
+ if ((*arg)->type() == Item::FIELD_ITEM)
+ ((Item_field *)(*arg))->no_const_subst= 1;
+ /*
+ If in statement prepare, then we create a converter for two
+ constant items, do it once and then reuse it.
+ If we're in execution of a prepared statement, arena is NULL,
+ and the conv was created in runtime memory. This can be
+ the case only if the argument is a parameter marker ('?'),
+ because for all true constants the charset converter has already
+ been created in prepare. In this case register the change for
+ rollback.
+ */
+ if (thd->stmt_arena->is_stmt_prepare())
+ *arg= conv;
+ else
+ thd->change_item_tree(arg, conv);
+
+ if (conv->fix_fields(thd, arg))
+ {
+ res= TRUE;
+ break; // we cannot return here, we need to restore "arena".
+ }
+ }
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ return res;
+}
+
+
+/*
+ Collect arguments' character sets together.
+ We allow to apply automatic character set conversion in some cases.
+ The conditions when conversion is possible are:
+ - arguments A and B have different charsets
+ - A wins according to coercibility rules
+ (i.e. a column is stronger than a string constant,
+ an explicit COLLATE clause is stronger than a column)
+ - character set of A is either superset for character set of B,
+ or B is a string constant which can be converted into the
+ character set of A without data loss.
+
+ If all of the above is true, then it's possible to convert
+ B into the character set of A, and then compare according
+ to the collation of A.
+
+ For functions with more than two arguments:
+
+ collect(A,B,C) ::= collect(collect(A,B),C)
+
+ Since this function calls THD::change_item_tree() on the passed Item **
+ pointers, it is necessary to pass the original Item **'s, not copies.
+ Otherwise their values will not be properly restored (see BUG#20769).
+ If the items are not consecutive (eg. args[2] and args[5]), use the
+ item_sep argument, ie.
+
+ agg_item_charsets(coll, fname, &args[2], 2, flags, 3)
+
+*/
+
+bool agg_item_charsets(DTCollation &coll, const char *fname,
+ Item **args, uint nargs, uint flags, int item_sep)
+{
+ if (agg_item_collations(coll, fname, args, nargs, flags, item_sep))
+ return TRUE;
+
+ return agg_item_set_converter(coll, fname, args, nargs, flags, item_sep);
+}
+
+
+void Item_ident_for_show::make_field(Send_field *tmp_field)
+{
+ tmp_field->table_name= tmp_field->org_table_name= table_name;
+ tmp_field->db_name= db_name;
+ tmp_field->col_name= tmp_field->org_col_name= field->field_name;
+ tmp_field->charsetnr= field->charset()->number;
+ tmp_field->length=field->field_length;
+ tmp_field->type=field->type();
+ tmp_field->flags= field->table->maybe_null ?
+ (field->flags & ~NOT_NULL_FLAG) : field->flags;
+ tmp_field->decimals= field->decimals();
+}
+
+/**********************************************/
+
+Item_field::Item_field(Field *f)
+ :Item_ident(0, NullS, *f->table_name, f->field_name),
+ item_equal(0), no_const_subst(0),
+ have_privileges(0), any_privileges(0)
+{
+ set_field(f);
+ /*
+ field_name and table_name should not point to garbage
+ if this item is to be reused
+ */
+ orig_table_name= orig_field_name= "";
+ with_field= 1;
+}
+
+
+/**
+ Constructor used inside setup_wild().
+
+ Ensures that field, table, and database names will live as long as
+ Item_field (this is important in prepared statements).
+*/
+
+Item_field::Item_field(THD *thd, Name_resolution_context *context_arg,
+ Field *f)
+ :Item_ident(context_arg, f->table->s->db.str, *f->table_name, f->field_name),
+ item_equal(0), no_const_subst(0),
+ have_privileges(0), any_privileges(0)
+{
+ /*
+ We always need to provide Item_field with a fully qualified field
+ name to avoid ambiguity when executing prepared statements like
+ SELECT * from d1.t1, d2.t1; (assuming d1.t1 and d2.t1 have columns
+ with same names).
+ This is because prepared statements never deal with wildcards in
+ select list ('*') and always fix fields using fully specified path
+ (i.e. db.table.column).
+ No check for OOM: if db_name is NULL, we'll just get
+ "Field not found" error.
+ We need to copy db_name, table_name and field_name because they must
+ be allocated in the statement memory, not in table memory (the table
+ structure can go away and pop up again between subsequent executions
+ of a prepared statement or after the close_tables_for_reopen() call
+ in mysql_multi_update_prepare() or due to wildcard expansion in stored
+ procedures).
+ */
+ {
+ if (db_name)
+ orig_db_name= thd->strdup(db_name);
+ if (table_name)
+ orig_table_name= thd->strdup(table_name);
+ if (field_name)
+ orig_field_name= thd->strdup(field_name);
+ /*
+ We don't restore 'name' in cleanup because it's not changed
+ during execution. Still we need it to point to persistent
+ memory if this item is to be reused.
+ */
+ name= (char*) orig_field_name;
+ }
+ set_field(f);
+ with_field= 1;
+}
+
+
+Item_field::Item_field(Name_resolution_context *context_arg,
+ const char *db_arg,const char *table_name_arg,
+ const char *field_name_arg)
+ :Item_ident(context_arg, db_arg,table_name_arg,field_name_arg),
+ field(0), result_field(0), item_equal(0), no_const_subst(0),
+ have_privileges(0), any_privileges(0)
+{
+ SELECT_LEX *select= current_thd->lex->current_select;
+ collation.set(DERIVATION_IMPLICIT);
+ if (select && select->parsing_place != IN_HAVING)
+ select->select_n_where_fields++;
+ with_field= 1;
+}
+
+/**
+ Constructor need to process subselect with temporary tables (see Item)
+*/
+
+Item_field::Item_field(THD *thd, Item_field *item)
+ :Item_ident(thd, item),
+ field(item->field),
+ result_field(item->result_field),
+ item_equal(item->item_equal),
+ no_const_subst(item->no_const_subst),
+ have_privileges(item->have_privileges),
+ any_privileges(item->any_privileges)
+{
+ collation.set(DERIVATION_IMPLICIT);
+ with_field= 1;
+}
+
+
+/**
+ Calculate the max column length not taking into account the
+ limitations over integer types.
+
+ When storing data into fields the server currently just ignores the
+ limits specified on integer types, e.g. 1234 can safely be stored in
+ an int(2) and will not cause an error.
+ Thus when creating temporary tables and doing transformations
+ we must adjust the maximum field length to reflect this fact.
+ We take the un-restricted maximum length and adjust it similarly to
+ how the declared length is adjusted wrt unsignedness etc.
+ TODO: this all needs to go when we disable storing 1234 in int(2).
+
+ @param field_par Original field the use to calculate the lengths
+ @param max_length Item's calculated explicit max length
+ @return The adjusted max length
+*/
+
+inline static uint32
+adjust_max_effective_column_length(Field *field_par, uint32 max_length)
+{
+ uint32 new_max_length= field_par->max_display_length();
+ uint32 sign_length= (field_par->flags & UNSIGNED_FLAG) ? 0 : 1;
+
+ switch (field_par->type())
+ {
+ case MYSQL_TYPE_INT24:
+ /*
+ Compensate for MAX_MEDIUMINT_WIDTH being 1 too long (8)
+ compared to the actual number of digits that can fit into
+ the column.
+ */
+ new_max_length+= 1;
+ /* fall through */
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+
+ /* Take out the sign and add a conditional sign */
+ new_max_length= new_max_length - 1 + sign_length;
+ break;
+
+ /* BINGINT is always 20 no matter the sign */
+ case MYSQL_TYPE_LONGLONG:
+ /* make gcc happy */
+ default:
+ break;
+ }
+
+ /* Adjust only if the actual precision based one is bigger than specified */
+ return new_max_length > max_length ? new_max_length : max_length;
+}
+
+
+void Item_field::set_field(Field *field_par)
+{
+ field=result_field=field_par; // for easy coding with fields
+ maybe_null=field->maybe_null();
+ decimals= field->decimals();
+ table_name= *field_par->table_name;
+ field_name= field_par->field_name;
+ db_name= field_par->table->s->db.str;
+ alias_name_used= field_par->table->alias_name_used;
+ unsigned_flag= MY_TEST(field_par->flags & UNSIGNED_FLAG);
+ collation.set(field_par->charset(), field_par->derivation(),
+ field_par->repertoire());
+ fix_char_length(field_par->char_length());
+
+ max_length= adjust_max_effective_column_length(field_par, max_length);
+
+ fixed= 1;
+ if (field->table->s->tmp_table == SYSTEM_TMP_TABLE)
+ any_privileges= 0;
+}
+
+
+/**
+ Reset this item to point to a field from the new temporary table.
+ This is used when we create a new temporary table for each execution
+ of prepared statement.
+*/
+
+void Item_field::reset_field(Field *f)
+{
+ set_field(f);
+ /* 'name' is pointing at field->field_name of old field */
+ name= (char*) f->field_name;
+}
+
+
+bool Item_field::enumerate_field_refs_processor(uchar *arg)
+{
+ Field_enumerator *fe= (Field_enumerator*)arg;
+ fe->visit_field(this);
+ return FALSE;
+}
+
+bool Item_field::update_table_bitmaps_processor(uchar *arg)
+{
+ update_table_bitmaps();
+ return FALSE;
+}
+
+const char *Item_ident::full_name() const
+{
+ char *tmp;
+ if (!table_name || !field_name)
+ return field_name ? field_name : name ? name : "tmp_field";
+ if (db_name && db_name[0])
+ {
+ tmp=(char*) sql_alloc((uint) strlen(db_name)+(uint) strlen(table_name)+
+ (uint) strlen(field_name)+3);
+ strxmov(tmp,db_name,".",table_name,".",field_name,NullS);
+ }
+ else
+ {
+ if (table_name[0])
+ {
+ tmp= (char*) sql_alloc((uint) strlen(table_name) +
+ (uint) strlen(field_name) + 2);
+ strxmov(tmp, table_name, ".", field_name, NullS);
+ }
+ else
+ tmp= (char*) field_name;
+ }
+ return tmp;
+}
+
+void Item_ident::print(String *str, enum_query_type query_type)
+{
+ THD *thd= current_thd;
+ char d_name_buff[MAX_ALIAS_NAME], t_name_buff[MAX_ALIAS_NAME];
+ const char *d_name= db_name, *t_name= table_name;
+ if (lower_case_table_names== 1 ||
+ (lower_case_table_names == 2 && !alias_name_used))
+ {
+ if (table_name && table_name[0])
+ {
+ strmov(t_name_buff, table_name);
+ my_casedn_str(files_charset_info, t_name_buff);
+ t_name= t_name_buff;
+ }
+ if (db_name && db_name[0])
+ {
+ strmov(d_name_buff, db_name);
+ my_casedn_str(files_charset_info, d_name_buff);
+ d_name= d_name_buff;
+ }
+ }
+
+ if (!table_name || !field_name || !field_name[0])
+ {
+ const char *nm= (field_name && field_name[0]) ?
+ field_name : name ? name : "tmp_field";
+ append_identifier(thd, str, nm, (uint) strlen(nm));
+ return;
+ }
+ if (db_name && db_name[0] && !alias_name_used)
+ {
+ if (!(cached_table && cached_table->belong_to_view &&
+ cached_table->belong_to_view->compact_view_format))
+ {
+ append_identifier(thd, str, d_name, (uint)strlen(d_name));
+ str->append('.');
+ }
+ append_identifier(thd, str, t_name, (uint)strlen(t_name));
+ str->append('.');
+ append_identifier(thd, str, field_name, (uint)strlen(field_name));
+ }
+ else
+ {
+ if (table_name[0])
+ {
+ append_identifier(thd, str, t_name, (uint) strlen(t_name));
+ str->append('.');
+ append_identifier(thd, str, field_name, (uint) strlen(field_name));
+ }
+ else
+ append_identifier(thd, str, field_name, (uint) strlen(field_name));
+ }
+}
+
+/* ARGSUSED */
+String *Item_field::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((null_value=field->is_null()))
+ return 0;
+ str->set_charset(str_value.charset());
+ return field->val_str(str,&str_value);
+}
+
+
+double Item_field::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((null_value=field->is_null()))
+ return 0.0;
+ return field->val_real();
+}
+
+
+longlong Item_field::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((null_value=field->is_null()))
+ return 0;
+ return field->val_int();
+}
+
+
+my_decimal *Item_field::val_decimal(my_decimal *decimal_value)
+{
+ if ((null_value= field->is_null()))
+ return 0;
+ return field->val_decimal(decimal_value);
+}
+
+
+String *Item_field::str_result(String *str)
+{
+ if ((null_value=result_field->is_null()))
+ return 0;
+ str->set_charset(str_value.charset());
+ return result_field->val_str(str,&str_value);
+}
+
+bool Item_field::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ if ((null_value=field->is_null()) || field->get_date(ltime,fuzzydate))
+ {
+ bzero((char*) ltime,sizeof(*ltime));
+ return 1;
+ }
+ return 0;
+}
+
+bool Item_field::get_date_result(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ if (result_field->is_null() || result_field->get_date(ltime,fuzzydate))
+ {
+ bzero((char*) ltime,sizeof(*ltime));
+ return (null_value= 1);
+ }
+ return (null_value= 0);
+}
+
+
+void Item_field::save_result(Field *to)
+{
+ save_field_in_field(result_field, &null_value, to, TRUE);
+}
+
+
+double Item_field::val_result()
+{
+ if ((null_value=result_field->is_null()))
+ return 0.0;
+ return result_field->val_real();
+}
+
+longlong Item_field::val_int_result()
+{
+ if ((null_value=result_field->is_null()))
+ return 0;
+ return result_field->val_int();
+}
+
+
+my_decimal *Item_field::val_decimal_result(my_decimal *decimal_value)
+{
+ if ((null_value= result_field->is_null()))
+ return 0;
+ return result_field->val_decimal(decimal_value);
+}
+
+
+bool Item_field::val_bool_result()
+{
+ if ((null_value= result_field->is_null()))
+ return FALSE;
+ switch (result_field->result_type()) {
+ case INT_RESULT:
+ return result_field->val_int() != 0;
+ case DECIMAL_RESULT:
+ {
+ my_decimal decimal_value;
+ my_decimal *val= result_field->val_decimal(&decimal_value);
+ if (val)
+ return !my_decimal_is_zero(val);
+ return 0;
+ }
+ case REAL_RESULT:
+ case STRING_RESULT:
+ return result_field->val_real() != 0.0;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ return 0; // Shut up compiler
+ }
+ return 0;
+}
+
+
+bool Item_field::is_null_result()
+{
+ return (null_value=result_field->is_null());
+}
+
+
+bool Item_field::eq(const Item *item, bool binary_cmp) const
+{
+ Item *real_item= ((Item *) item)->real_item();
+ if (real_item->type() != FIELD_ITEM)
+ return 0;
+
+ Item_field *item_field= (Item_field*) real_item;
+ if (item_field->field && field)
+ return item_field->field == field;
+ /*
+ We may come here when we are trying to find a function in a GROUP BY
+ clause from the select list.
+ In this case the '100 % correct' way to do this would be to first
+ run fix_fields() on the GROUP BY item and then retry this function, but
+ I think it's better to relax the checking a bit as we will in
+ most cases do the correct thing by just checking the field name.
+ (In cases where we would choose wrong we would have to generate a
+ ER_NON_UNIQ_ERROR).
+ */
+ return (!my_strcasecmp(system_charset_info, item_field->name,
+ field_name) &&
+ (!item_field->table_name || !table_name ||
+ (!my_strcasecmp(table_alias_charset, item_field->table_name,
+ table_name) &&
+ (!item_field->db_name || !db_name ||
+ (item_field->db_name && !strcmp(item_field->db_name,
+ db_name))))));
+}
+
+
+table_map Item_field::used_tables() const
+{
+ if (field->table->const_table)
+ return 0; // const item
+ return (get_depended_from() ? OUTER_REF_TABLE_BIT : field->table->map);
+}
+
+table_map Item_field::all_used_tables() const
+{
+ return (get_depended_from() ? OUTER_REF_TABLE_BIT : field->table->map);
+}
+
+void Item_field::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ if (new_parent == get_depended_from())
+ depended_from= NULL;
+ if (context)
+ {
+ Name_resolution_context *ctx= new Name_resolution_context();
+ ctx->outer_context= NULL; // We don't build a complete name resolver
+ ctx->table_list= NULL; // We rely on first_name_resolution_table instead
+ ctx->select_lex= new_parent;
+ ctx->first_name_resolution_table= context->first_name_resolution_table;
+ ctx->last_name_resolution_table= context->last_name_resolution_table;
+ ctx->error_processor= context->error_processor;
+ ctx->error_processor_data= context->error_processor_data;
+ ctx->resolve_in_select_list= context->resolve_in_select_list;
+ ctx->security_ctx= context->security_ctx;
+ this->context=ctx;
+ }
+}
+
+
+Item *Item_field::get_tmp_table_item(THD *thd)
+{
+ Item_field *new_item= new Item_field(thd, this);
+ if (new_item)
+ new_item->field= new_item->result_field;
+ return new_item;
+}
+
+longlong Item_field::val_int_endpoint(bool left_endp, bool *incl_endp)
+{
+ longlong res= val_int();
+ return null_value? LONGLONG_MIN : res;
+}
+
+/**
+ Create an item from a string we KNOW points to a valid longlong
+ end \\0 terminated number string.
+ This is always 'signed'. Unsigned values are created with Item_uint()
+*/
+
+Item_int::Item_int(const char *str_arg, uint length)
+{
+ char *end_ptr= (char*) str_arg + length;
+ int error;
+ value= my_strtoll10(str_arg, &end_ptr, &error);
+ max_length= (uint) (end_ptr - str_arg);
+ name= (char*) str_arg;
+ fixed= 1;
+}
+
+
+my_decimal *Item_int::val_decimal(my_decimal *decimal_value)
+{
+ int2my_decimal(E_DEC_FATAL_ERROR, value, unsigned_flag, decimal_value);
+ return decimal_value;
+}
+
+String *Item_int::val_str(String *str)
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ str->set_int(value, unsigned_flag, collation.collation);
+ return str;
+}
+
+void Item_int::print(String *str, enum_query_type query_type)
+{
+ // my_charset_bin is good enough for numbers
+ str_value.set_int(value, unsigned_flag, &my_charset_bin);
+ str->append(str_value);
+}
+
+
+Item_uint::Item_uint(const char *str_arg, uint length):
+ Item_int(str_arg, length)
+{
+ unsigned_flag= 1;
+}
+
+
+Item_uint::Item_uint(const char *str_arg, longlong i, uint length):
+ Item_int(str_arg, i, length)
+{
+ unsigned_flag= 1;
+}
+
+
+String *Item_uint::val_str(String *str)
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ str->set((ulonglong) value, collation.collation);
+ return str;
+}
+
+
+void Item_uint::print(String *str, enum_query_type query_type)
+{
+ // latin1 is good enough for numbers
+ str_value.set((ulonglong) value, default_charset());
+ str->append(str_value);
+}
+
+
+Item_decimal::Item_decimal(const char *str_arg, uint length,
+ CHARSET_INFO *charset)
+{
+ str2my_decimal(E_DEC_FATAL_ERROR, str_arg, length, charset, &decimal_value);
+ name= (char*) str_arg;
+ decimals= (uint8) decimal_value.frac;
+ fixed= 1;
+ max_length= my_decimal_precision_to_length_no_truncation(decimal_value.intg +
+ decimals,
+ decimals,
+ unsigned_flag);
+}
+
+Item_decimal::Item_decimal(longlong val, bool unsig)
+{
+ int2my_decimal(E_DEC_FATAL_ERROR, val, unsig, &decimal_value);
+ decimals= (uint8) decimal_value.frac;
+ fixed= 1;
+ max_length= my_decimal_precision_to_length_no_truncation(decimal_value.intg +
+ decimals,
+ decimals,
+ unsigned_flag);
+}
+
+
+Item_decimal::Item_decimal(double val, int precision, int scale)
+{
+ double2my_decimal(E_DEC_FATAL_ERROR, val, &decimal_value);
+ decimals= (uint8) decimal_value.frac;
+ fixed= 1;
+ max_length= my_decimal_precision_to_length_no_truncation(decimal_value.intg +
+ decimals,
+ decimals,
+ unsigned_flag);
+}
+
+
+Item_decimal::Item_decimal(const char *str, const my_decimal *val_arg,
+ uint decimal_par, uint length)
+{
+ my_decimal2decimal(val_arg, &decimal_value);
+ name= (char*) str;
+ decimals= (uint8) decimal_par;
+ max_length= length;
+ fixed= 1;
+}
+
+
+Item_decimal::Item_decimal(my_decimal *value_par)
+{
+ my_decimal2decimal(value_par, &decimal_value);
+ decimals= (uint8) decimal_value.frac;
+ fixed= 1;
+ max_length= my_decimal_precision_to_length_no_truncation(decimal_value.intg +
+ decimals,
+ decimals,
+ unsigned_flag);
+}
+
+
+Item_decimal::Item_decimal(const uchar *bin, int precision, int scale)
+{
+ binary2my_decimal(E_DEC_FATAL_ERROR, bin,
+ &decimal_value, precision, scale);
+ decimals= (uint8) decimal_value.frac;
+ fixed= 1;
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
+}
+
+
+longlong Item_decimal::val_int()
+{
+ longlong result;
+ my_decimal2int(E_DEC_FATAL_ERROR, &decimal_value, unsigned_flag, &result);
+ return result;
+}
+
+double Item_decimal::val_real()
+{
+ double result;
+ my_decimal2double(E_DEC_FATAL_ERROR, &decimal_value, &result);
+ return result;
+}
+
+String *Item_decimal::val_str(String *result)
+{
+ result->set_charset(&my_charset_numeric);
+ my_decimal2string(E_DEC_FATAL_ERROR, &decimal_value, 0, 0, 0, result);
+ return result;
+}
+
+void Item_decimal::print(String *str, enum_query_type query_type)
+{
+ my_decimal2string(E_DEC_FATAL_ERROR, &decimal_value, 0, 0, 0, &str_value);
+ str->append(str_value);
+}
+
+
+bool Item_decimal::eq(const Item *item, bool binary_cmp) const
+{
+ if (type() == item->type() && item->basic_const_item())
+ {
+ /*
+ We need to cast off const to call val_decimal(). This should
+ be OK for a basic constant. Additionally, we can pass 0 as
+ a true decimal constant will return its internal decimal
+ storage and ignore the argument.
+ */
+ Item *arg= (Item*) item;
+ my_decimal *value= arg->val_decimal(0);
+ return !my_decimal_cmp(&decimal_value, value);
+ }
+ return 0;
+}
+
+
+void Item_decimal::set_decimal_value(my_decimal *value_par)
+{
+ my_decimal2decimal(value_par, &decimal_value);
+ decimals= (uint8) decimal_value.frac;
+ unsigned_flag= !decimal_value.sign();
+ max_length= my_decimal_precision_to_length_no_truncation(decimal_value.intg +
+ decimals,
+ decimals,
+ unsigned_flag);
+}
+
+
+String *Item_float::val_str(String *str)
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ str->set_real(value, decimals, &my_charset_numeric);
+ return str;
+}
+
+
+my_decimal *Item_float::val_decimal(my_decimal *decimal_value)
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ double2my_decimal(E_DEC_FATAL_ERROR, value, decimal_value);
+ return (decimal_value);
+}
+
+
+void Item_string::print(String *str, enum_query_type query_type)
+{
+ const bool print_introducer=
+ !(query_type & QT_WITHOUT_INTRODUCERS) && is_cs_specified();
+ if (print_introducer)
+ {
+ str->append('_');
+ str->append(collation.collation->csname);
+ }
+
+ str->append('\'');
+
+ if (query_type & QT_TO_SYSTEM_CHARSET)
+ {
+ if (print_introducer)
+ {
+ /*
+ Because we wrote an introducer, we must print str_value in its
+ charset, and the resulting bytes must not be changed until they
+ reach the end client.
+ But the caller is asking for system_charset_info, and may later
+ convert into character_set_results. That means two conversions: we
+ must ensure that they don't change our printed bytes.
+ So we print str_value in the least common denominator of the three
+ charsets involved: ASCII. Non-ASCII characters are printed as \xFF
+ sequences (which is ASCII too). This way, our bytes will not be
+ changed.
+ */
+ ErrConvString tmp(str_value.ptr(), str_value.length(), &my_charset_bin);
+ str->append(tmp.ptr());
+ }
+ else
+ {
+ if (my_charset_same(str_value.charset(), system_charset_info))
+ str_value.print(str); // already in system_charset_info
+ else // need to convert
+ {
+ THD *thd= current_thd;
+ LEX_STRING utf8_lex_str;
+
+ thd->convert_string(&utf8_lex_str,
+ system_charset_info,
+ str_value.c_ptr_safe(),
+ str_value.length(),
+ str_value.charset());
+
+ String utf8_str(utf8_lex_str.str,
+ utf8_lex_str.length,
+ system_charset_info);
+
+ utf8_str.print(str);
+ }
+ }
+ }
+ else
+ {
+ // Caller wants a result in the charset of str_value.
+ str_value.print(str);
+ }
+
+ str->append('\'');
+}
+
+
+double
+double_from_string_with_check(CHARSET_INFO *cs, const char *cptr,
+ const char *end)
+{
+ int error;
+ char *end_of_num= (char*) end;
+ double tmp;
+
+ tmp= my_strntod(cs, (char*) cptr, end - cptr, &end_of_num, &error);
+ if (error || (end != end_of_num &&
+ !check_if_only_end_space(cs, end_of_num, end)))
+ {
+ ErrConvString err(cptr, end - cptr, cs);
+ /*
+ We can use err.ptr() here as ErrConvString is guranteed to put an
+ end \0 here.
+ */
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), "DOUBLE",
+ err.ptr());
+ }
+ return tmp;
+}
+
+
+double Item_string::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ return double_from_string_with_check(str_value.charset(),
+ str_value.ptr(),
+ str_value.ptr() +
+ str_value.length());
+}
+
+
+longlong
+longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr,
+ const char *end)
+{
+ int err;
+ longlong tmp;
+ char *end_of_num= (char*) end;
+
+ tmp= (*(cs->cset->strtoll10))(cs, cptr, &end_of_num, &err);
+ /*
+ TODO: Give error if we wanted a signed integer and we got an unsigned
+ one
+ */
+ if (!current_thd->no_errors &&
+ (err > 0 ||
+ (end != end_of_num && !check_if_only_end_space(cs, end_of_num, end))))
+ {
+ ErrConvString err(cptr, end - cptr, cs);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER",
+ err.ptr());
+ }
+ return tmp;
+}
+
+
+/**
+ @todo
+ Give error if we wanted a signed integer and we got an unsigned one
+*/
+longlong Item_string::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ return longlong_from_string_with_check(str_value.charset(), str_value.ptr(),
+ str_value.ptr()+ str_value.length());
+}
+
+
+my_decimal *Item_string::val_decimal(my_decimal *decimal_value)
+{
+ return val_decimal_from_string(decimal_value);
+}
+
+
+double Item_null::val_real()
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ null_value=1;
+ return 0.0;
+}
+longlong Item_null::val_int()
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ null_value=1;
+ return 0;
+}
+/* ARGSUSED */
+String *Item_null::val_str(String *str)
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ null_value=1;
+ return 0;
+}
+
+my_decimal *Item_null::val_decimal(my_decimal *decimal_value)
+{
+ return 0;
+}
+
+
+Item *Item_null::safe_charset_converter(CHARSET_INFO *tocs)
+{
+ collation.set(tocs);
+ return this;
+}
+
+/*********************** Item_param related ******************************/
+
+/**
+ Default function of Item_param::set_param_func, so in case
+ of malformed packet the server won't SIGSEGV.
+*/
+
+static void
+default_set_param_func(Item_param *param,
+ uchar **pos __attribute__((unused)),
+ ulong len __attribute__((unused)))
+{
+ param->set_null();
+}
+
+
+Item_param::Item_param(uint pos_in_query_arg) :
+ state(NO_VALUE),
+ item_result_type(STRING_RESULT),
+ /* Don't pretend to be a literal unless value for this item is set. */
+ item_type(PARAM_ITEM),
+ param_type(MYSQL_TYPE_VARCHAR),
+ pos_in_query(pos_in_query_arg),
+ set_param_func(default_set_param_func),
+ limit_clause_param(FALSE),
+ m_out_param_info(NULL)
+{
+ name= (char*) "?";
+ /*
+ Since we can't say whenever this item can be NULL or cannot be NULL
+ before mysql_stmt_execute(), so we assuming that it can be NULL until
+ value is set.
+ */
+ maybe_null= 1;
+}
+
+
+void Item_param::set_null()
+{
+ DBUG_ENTER("Item_param::set_null");
+ /* These are cleared after each execution by reset() method */
+ null_value= 1;
+ /*
+ Because of NULL and string values we need to set max_length for each new
+ placeholder value: user can submit NULL for any placeholder type, and
+ string length can be different in each execution.
+ */
+ max_length= 0;
+ decimals= 0;
+ state= NULL_VALUE;
+ item_type= Item::NULL_ITEM;
+ DBUG_VOID_RETURN;
+}
+
+void Item_param::set_int(longlong i, uint32 max_length_arg)
+{
+ DBUG_ENTER("Item_param::set_int");
+ value.integer= (longlong) i;
+ state= INT_VALUE;
+ max_length= max_length_arg;
+ decimals= 0;
+ maybe_null= 0;
+ DBUG_VOID_RETURN;
+}
+
+void Item_param::set_double(double d)
+{
+ DBUG_ENTER("Item_param::set_double");
+ value.real= d;
+ state= REAL_VALUE;
+ max_length= DBL_DIG + 8;
+ decimals= NOT_FIXED_DEC;
+ maybe_null= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set decimal parameter value from string.
+
+ @param str character string
+ @param length string length
+
+ @note
+ As we use character strings to send decimal values in
+ binary protocol, we use str2my_decimal to convert it to
+ internal decimal value.
+*/
+
+void Item_param::set_decimal(const char *str, ulong length)
+{
+ char *end;
+ DBUG_ENTER("Item_param::set_decimal");
+
+ end= (char*) str+length;
+ str2my_decimal(E_DEC_FATAL_ERROR, str, &decimal_value, &end);
+ state= DECIMAL_VALUE;
+ decimals= decimal_value.frac;
+ max_length=
+ my_decimal_precision_to_length_no_truncation(decimal_value.precision(),
+ decimals, unsigned_flag);
+ maybe_null= 0;
+ DBUG_VOID_RETURN;
+}
+
+void Item_param::set_decimal(const my_decimal *dv)
+{
+ state= DECIMAL_VALUE;
+
+ my_decimal2decimal(dv, &decimal_value);
+
+ decimals= (uint8) decimal_value.frac;
+ unsigned_flag= !decimal_value.sign();
+ max_length= my_decimal_precision_to_length(decimal_value.intg + decimals,
+ decimals, unsigned_flag);
+}
+
+/**
+ Set parameter value from MYSQL_TIME value.
+
+ @param tm datetime value to set (time_type is ignored)
+ @param type type of datetime value
+ @param max_length_arg max length of datetime value as string
+
+ @note
+ If we value to be stored is not normalized, zero value will be stored
+ instead and proper warning will be produced. This function relies on
+ the fact that even wrong value sent over binary protocol fits into
+ MAX_DATE_STRING_REP_LENGTH buffer.
+*/
+void Item_param::set_time(MYSQL_TIME *tm, timestamp_type time_type,
+ uint32 max_length_arg)
+{
+ DBUG_ENTER("Item_param::set_time");
+
+ value.time= *tm;
+ value.time.time_type= time_type;
+
+ if (check_datetime_range(&value.time))
+ {
+ ErrConvTime str(&value.time);
+ make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ &str, time_type, 0);
+ set_zero_time(&value.time, MYSQL_TIMESTAMP_ERROR);
+ }
+
+ state= TIME_VALUE;
+ maybe_null= 0;
+ max_length= max_length_arg;
+ decimals= tm->second_part > 0 ? TIME_SECOND_PART_DIGITS : 0;
+ DBUG_VOID_RETURN;
+}
+
+
+bool Item_param::set_str(const char *str, ulong length)
+{
+ DBUG_ENTER("Item_param::set_str");
+ /*
+ Assign string with no conversion: data is converted only after it's
+ been written to the binary log.
+ */
+ uint dummy_errors;
+ if (str_value.copy(str, length, &my_charset_bin, &my_charset_bin,
+ &dummy_errors))
+ DBUG_RETURN(TRUE);
+ state= STRING_VALUE;
+ max_length= length;
+ maybe_null= 0;
+ /* max_length and decimals are set after charset conversion */
+ /* sic: str may be not null-terminated, don't add DBUG_PRINT here */
+ DBUG_RETURN(FALSE);
+}
+
+
+bool Item_param::set_longdata(const char *str, ulong length)
+{
+ DBUG_ENTER("Item_param::set_longdata");
+
+ /*
+ If client character set is multibyte, end of long data packet
+ may hit at the middle of a multibyte character. Additionally,
+ if binary log is open we must write long data value to the
+ binary log in character set of client. This is why we can't
+ convert long data to connection character set as it comes
+ (here), and first have to concatenate all pieces together,
+ write query to the binary log and only then perform conversion.
+ */
+ if (str_value.length() + length > max_long_data_size)
+ {
+ my_message(ER_UNKNOWN_ERROR,
+ "Parameter of prepared statement which is set through "
+ "mysql_send_long_data() is longer than "
+ "'max_long_data_size' bytes",
+ MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ if (str_value.append(str, length, &my_charset_bin))
+ DBUG_RETURN(TRUE);
+ state= LONG_DATA_VALUE;
+ maybe_null= 0;
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Set parameter value from user variable value.
+
+ @param thd Current thread
+ @param entry User variable structure (NULL means use NULL value)
+
+ @retval
+ 0 OK
+ @retval
+ 1 Out of memory
+*/
+
+bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry)
+{
+ DBUG_ENTER("Item_param::set_from_user_var");
+ if (entry && entry->value)
+ {
+ item_result_type= entry->type;
+ unsigned_flag= entry->unsigned_flag;
+ if (limit_clause_param)
+ {
+ bool unused;
+ set_int(entry->val_int(&unused), MY_INT64_NUM_DECIMAL_DIGITS);
+ item_type= Item::INT_ITEM;
+ DBUG_RETURN(!unsigned_flag && value.integer < 0 ? 1 : 0);
+ }
+ switch (item_result_type) {
+ case REAL_RESULT:
+ set_double(*(double*)entry->value);
+ item_type= Item::REAL_ITEM;
+ param_type= MYSQL_TYPE_DOUBLE;
+ break;
+ case INT_RESULT:
+ set_int(*(longlong*)entry->value, MY_INT64_NUM_DECIMAL_DIGITS);
+ item_type= Item::INT_ITEM;
+ param_type= MYSQL_TYPE_LONGLONG;
+ break;
+ case STRING_RESULT:
+ {
+ CHARSET_INFO *fromcs= entry->collation.collation;
+ CHARSET_INFO *tocs= thd->variables.collation_connection;
+ uint32 dummy_offset;
+
+ value.cs_info.character_set_of_placeholder= fromcs;
+ value.cs_info.character_set_client= thd->variables.character_set_client;
+ /*
+ Setup source and destination character sets so that they
+ are different only if conversion is necessary: this will
+ make later checks easier.
+ */
+ value.cs_info.final_character_set_of_str_value=
+ String::needs_conversion(0, fromcs, tocs, &dummy_offset) ?
+ tocs : fromcs;
+ /*
+ Exact value of max_length is not known unless data is converted to
+ charset of connection, so we have to set it later.
+ */
+ item_type= Item::STRING_ITEM;
+ param_type= MYSQL_TYPE_VARCHAR;
+
+ if (set_str((const char *)entry->value, entry->length))
+ DBUG_RETURN(1);
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ const my_decimal *ent_value= (const my_decimal *)entry->value;
+ my_decimal2decimal(ent_value, &decimal_value);
+ state= DECIMAL_VALUE;
+ decimals= ent_value->frac;
+ max_length=
+ my_decimal_precision_to_length_no_truncation(ent_value->precision(),
+ decimals, unsigned_flag);
+ item_type= Item::DECIMAL_ITEM;
+ param_type= MYSQL_TYPE_NEWDECIMAL;
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ set_null();
+ }
+ }
+ else
+ set_null();
+
+ DBUG_RETURN(0);
+}
+
+/**
+ Resets parameter after execution.
+
+ @note
+ We clear null_value here instead of setting it in set_* methods,
+ because we want more easily handle case for long data.
+*/
+
+void Item_param::reset()
+{
+ DBUG_ENTER("Item_param::reset");
+ /* Shrink string buffer if it's bigger than max possible CHAR column */
+ if (str_value.alloced_length() > MAX_CHAR_WIDTH)
+ str_value.free();
+ else
+ str_value.length(0);
+ str_value_ptr.length(0);
+ /*
+ We must prevent all charset conversions until data has been written
+ to the binary log.
+ */
+ str_value.set_charset(&my_charset_bin);
+ collation.set(&my_charset_bin, DERIVATION_COERCIBLE);
+ state= NO_VALUE;
+ maybe_null= 1;
+ null_value= 0;
+ /*
+ Don't reset item_type to PARAM_ITEM: it's only needed to guard
+ us from item optimizations at prepare stage, when item doesn't yet
+ contain a literal of some kind.
+ In all other cases when this object is accessed its value is
+ set (this assumption is guarded by 'state' and
+ DBUG_ASSERTS(state != NO_VALUE) in all Item_param::get_*
+ methods).
+ */
+ DBUG_VOID_RETURN;
+}
+
+
+int Item_param::save_in_field(Field *field, bool no_conversions)
+{
+ field->set_notnull();
+
+ switch (state) {
+ case INT_VALUE:
+ return field->store(value.integer, unsigned_flag);
+ case REAL_VALUE:
+ return field->store(value.real);
+ case DECIMAL_VALUE:
+ return field->store_decimal(&decimal_value);
+ case TIME_VALUE:
+ field->store_time_dec(&value.time, decimals);
+ return 0;
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ return field->store(str_value.ptr(), str_value.length(),
+ str_value.charset());
+ case NULL_VALUE:
+ return set_field_to_null_with_conversions(field, no_conversions);
+ case NO_VALUE:
+ default:
+ DBUG_ASSERT(0);
+ }
+ return 1;
+}
+
+
+bool Item_param::get_date(MYSQL_TIME *res, ulonglong fuzzydate)
+{
+ if (state == TIME_VALUE)
+ {
+ *res= value.time;
+ return 0;
+ }
+ return Item::get_date(res, fuzzydate);
+}
+
+
+double Item_param::val_real()
+{
+ switch (state) {
+ case REAL_VALUE:
+ return value.real;
+ case INT_VALUE:
+ return (double) value.integer;
+ case DECIMAL_VALUE:
+ {
+ double result;
+ my_decimal2double(E_DEC_FATAL_ERROR, &decimal_value, &result);
+ return result;
+ }
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ {
+ int dummy_err;
+ char *end_not_used;
+ return my_strntod(str_value.charset(), (char*) str_value.ptr(),
+ str_value.length(), &end_not_used, &dummy_err);
+ }
+ case TIME_VALUE:
+ /*
+ This works for example when user says SELECT ?+0.0 and supplies
+ time value for the placeholder.
+ */
+ return TIME_to_double(&value.time);
+ case NULL_VALUE:
+ return 0.0;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return 0.0;
+}
+
+
+longlong Item_param::val_int()
+{
+ switch (state) {
+ case REAL_VALUE:
+ return (longlong) rint(value.real);
+ case INT_VALUE:
+ return value.integer;
+ case DECIMAL_VALUE:
+ {
+ longlong i;
+ my_decimal2int(E_DEC_FATAL_ERROR, &decimal_value, unsigned_flag, &i);
+ return i;
+ }
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ {
+ int dummy_err;
+ return my_strntoll(str_value.charset(), str_value.ptr(),
+ str_value.length(), 10, (char**) 0, &dummy_err);
+ }
+ case TIME_VALUE:
+ return (longlong) TIME_to_ulonglong(&value.time);
+ case NULL_VALUE:
+ return 0;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return 0;
+}
+
+
+my_decimal *Item_param::val_decimal(my_decimal *dec)
+{
+ switch (state) {
+ case DECIMAL_VALUE:
+ return &decimal_value;
+ case REAL_VALUE:
+ double2my_decimal(E_DEC_FATAL_ERROR, value.real, dec);
+ return dec;
+ case INT_VALUE:
+ int2my_decimal(E_DEC_FATAL_ERROR, value.integer, unsigned_flag, dec);
+ return dec;
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ string2my_decimal(E_DEC_FATAL_ERROR, &str_value, dec);
+ return dec;
+ case TIME_VALUE:
+ {
+ return TIME_to_my_decimal(&value.time, dec);
+ }
+ case NULL_VALUE:
+ return 0;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return 0;
+}
+
+
+String *Item_param::val_str(String* str)
+{
+ switch (state) {
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ return &str_value_ptr;
+ case REAL_VALUE:
+ str->set_real(value.real, NOT_FIXED_DEC, &my_charset_bin);
+ return str;
+ case INT_VALUE:
+ str->set(value.integer, &my_charset_bin);
+ return str;
+ case DECIMAL_VALUE:
+ if (my_decimal2string(E_DEC_FATAL_ERROR, &decimal_value,
+ 0, 0, 0, str) <= 1)
+ return str;
+ return NULL;
+ case TIME_VALUE:
+ {
+ if (str->reserve(MAX_DATE_STRING_REP_LENGTH))
+ break;
+ str->length((uint) my_TIME_to_str(&value.time, (char*) str->ptr(),
+ decimals));
+ str->set_charset(&my_charset_bin);
+ return str;
+ }
+ case NULL_VALUE:
+ return NULL;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return str;
+}
+
+/**
+ Return Param item values in string format, for generating the dynamic
+ query used in update/binary logs.
+
+ @todo
+ - Change interface and implementation to fill log data in place
+ and avoid one more memcpy/alloc between str and log string.
+ - In case of error we need to notify replication
+ that binary log contains wrong statement
+*/
+
+const String *Item_param::query_val_str(THD *thd, String* str) const
+{
+ switch (state) {
+ case INT_VALUE:
+ str->set_int(value.integer, unsigned_flag, &my_charset_bin);
+ break;
+ case REAL_VALUE:
+ str->set_real(value.real, NOT_FIXED_DEC, &my_charset_bin);
+ break;
+ case DECIMAL_VALUE:
+ if (my_decimal2string(E_DEC_FATAL_ERROR, &decimal_value,
+ 0, 0, 0, str) > 1)
+ return &my_null_string;
+ break;
+ case TIME_VALUE:
+ {
+ char *buf, *ptr;
+ str->length(0);
+ /*
+ TODO: in case of error we need to notify replication
+ that binary log contains wrong statement
+ */
+ if (str->reserve(MAX_DATE_STRING_REP_LENGTH+3))
+ break;
+
+ /* Create date string inplace */
+ buf= str->c_ptr_quick();
+ ptr= buf;
+ *ptr++= '\'';
+ ptr+= (uint) my_TIME_to_str(&value.time, ptr, decimals);
+ *ptr++= '\'';
+ str->length((uint32) (ptr - buf));
+ break;
+ }
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ {
+ str->length(0);
+ append_query_string(value.cs_info.character_set_client, str,
+ str_value.ptr(), str_value.length(),
+ thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
+ break;
+ }
+ case NULL_VALUE:
+ return &my_null_string;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return str;
+}
+
+
+/**
+ Convert string from client character set to the character set of
+ connection.
+*/
+
+bool Item_param::convert_str_value(THD *thd)
+{
+ bool rc= FALSE;
+ if (state == STRING_VALUE || state == LONG_DATA_VALUE)
+ {
+ /*
+ Check is so simple because all charsets were set up properly
+ in setup_one_conversion_function, where typecode of
+ placeholder was also taken into account: the variables are different
+ here only if conversion is really necessary.
+ */
+ if (value.cs_info.final_character_set_of_str_value !=
+ value.cs_info.character_set_of_placeholder)
+ {
+ rc= thd->convert_string(&str_value,
+ value.cs_info.character_set_of_placeholder,
+ value.cs_info.final_character_set_of_str_value);
+ }
+ else
+ str_value.set_charset(value.cs_info.final_character_set_of_str_value);
+ /* Here str_value is guaranteed to be in final_character_set_of_str_value */
+
+ /*
+ str_value_ptr is returned from val_str(). It must be not alloced
+ to prevent it's modification by val_str() invoker.
+ */
+ str_value_ptr.set(str_value.ptr(), str_value.length(),
+ str_value.charset());
+ /* Synchronize item charset and length with value charset */
+ fix_charset_and_length_from_str_value(DERIVATION_COERCIBLE);
+ }
+ return rc;
+}
+
+
+bool Item_param::basic_const_item() const
+{
+ if (state == NO_VALUE || state == TIME_VALUE)
+ return FALSE;
+ return TRUE;
+}
+
+
+Item *
+Item_param::clone_item()
+{
+ /* see comments in the header file */
+ switch (state) {
+ case NULL_VALUE:
+ return new Item_null(name);
+ case INT_VALUE:
+ return (unsigned_flag ?
+ new Item_uint(name, value.integer, max_length) :
+ new Item_int(name, value.integer, max_length));
+ case REAL_VALUE:
+ return new Item_float(name, value.real, decimals, max_length);
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ return new Item_string(name, str_value.c_ptr_quick(), str_value.length(),
+ str_value.charset(),
+ collation.derivation, collation.repertoire);
+ case TIME_VALUE:
+ break;
+ case NO_VALUE:
+ default:
+ DBUG_ASSERT(0);
+ };
+ return 0;
+}
+
+
+bool
+Item_param::eq(const Item *item, bool binary_cmp) const
+{
+ if (!basic_const_item())
+ return FALSE;
+
+ switch (state) {
+ case NULL_VALUE:
+ return null_eq(item);
+ case INT_VALUE:
+ return int_eq(value.integer, item);
+ case REAL_VALUE:
+ return real_eq(value.real, item);
+ case STRING_VALUE:
+ case LONG_DATA_VALUE:
+ return str_eq(&str_value, item, binary_cmp);
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+/* End of Item_param related */
+
+void Item_param::print(String *str, enum_query_type query_type)
+{
+ if (state == NO_VALUE)
+ {
+ str->append('?');
+ }
+ else
+ {
+ char buffer[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buffer, sizeof(buffer), &my_charset_bin);
+ const String *res;
+ res= query_val_str(current_thd, &tmp);
+ str->append(*res);
+ }
+}
+
+
+/**
+ Preserve the original parameter types and values
+ when re-preparing a prepared statement.
+
+ @details Copy parameter type information and conversion
+ function pointers from a parameter of the old statement
+ to the corresponding parameter of the new one.
+
+ Move parameter values from the old parameters to the new
+ one. We simply "exchange" the values, which allows
+ to save on allocation and character set conversion in
+ case a parameter is a string or a blob/clob.
+
+ The old parameter gets the value of this one, which
+ ensures that all memory of this parameter is freed
+ correctly.
+
+ @param[in] src parameter item of the original
+ prepared statement
+*/
+
+void
+Item_param::set_param_type_and_swap_value(Item_param *src)
+{
+ unsigned_flag= src->unsigned_flag;
+ param_type= src->param_type;
+ set_param_func= src->set_param_func;
+ item_type= src->item_type;
+ item_result_type= src->item_result_type;
+
+ collation.set(src->collation);
+ maybe_null= src->maybe_null;
+ null_value= src->null_value;
+ max_length= src->max_length;
+ decimals= src->decimals;
+ state= src->state;
+ value= src->value;
+
+ decimal_value.swap(src->decimal_value);
+ str_value.swap(src->str_value);
+ str_value_ptr.swap(src->str_value_ptr);
+}
+
+
+/**
+ This operation is intended to store some item value in Item_param to be
+ used later.
+
+ @param thd thread context
+ @param ctx stored procedure runtime context
+ @param it a pointer to an item in the tree
+
+ @return Error status
+ @retval TRUE on error
+ @retval FALSE on success
+*/
+
+bool
+Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it)
+{
+ Item *arg= *it;
+
+ if (arg->is_null())
+ {
+ set_null();
+ return FALSE;
+ }
+
+ null_value= FALSE;
+
+ switch (arg->result_type()) {
+ case STRING_RESULT:
+ {
+ char str_buffer[STRING_BUFFER_USUAL_SIZE];
+ String sv_buffer(str_buffer, sizeof(str_buffer), &my_charset_bin);
+ String *sv= arg->val_str(&sv_buffer);
+
+ if (!sv)
+ return TRUE;
+
+ set_str(sv->c_ptr_safe(), sv->length());
+ str_value_ptr.set(str_value.ptr(),
+ str_value.length(),
+ str_value.charset());
+ collation.set(str_value.charset(), DERIVATION_COERCIBLE);
+ decimals= 0;
+
+ break;
+ }
+
+ case REAL_RESULT:
+ set_double(arg->val_real());
+ break;
+
+ case INT_RESULT:
+ set_int(arg->val_int(), arg->max_length);
+ break;
+
+ case DECIMAL_RESULT:
+ {
+ my_decimal dv_buf;
+ my_decimal *dv= arg->val_decimal(&dv_buf);
+
+ if (!dv)
+ return TRUE;
+
+ set_decimal(dv);
+ break;
+ }
+
+ default:
+ /* That can not happen. */
+
+ DBUG_ASSERT(TRUE); // Abort in debug mode.
+
+ set_null(); // Set to NULL in release mode.
+ return FALSE;
+ }
+
+ item_result_type= arg->result_type();
+ item_type= arg->type();
+ return FALSE;
+}
+
+
+/**
+ Setter of Item_param::m_out_param_info.
+
+ m_out_param_info is used to store information about store routine
+ OUT-parameters, such as stored routine name, database, stored routine
+ variable name. It is supposed to be set in sp_head::execute() after
+ Item_param::set_value() is called.
+*/
+
+void
+Item_param::set_out_param_info(Send_field *info)
+{
+ m_out_param_info= info;
+ param_type= m_out_param_info->type;
+}
+
+
+/**
+ Getter of Item_param::m_out_param_info.
+
+ m_out_param_info is used to store information about store routine
+ OUT-parameters, such as stored routine name, database, stored routine
+ variable name. It is supposed to be retrieved in
+ Protocol_binary::send_out_parameters() during creation of OUT-parameter
+ result set.
+*/
+
+const Send_field *
+Item_param::get_out_param_info() const
+{
+ return m_out_param_info;
+}
+
+
+/**
+ Fill meta-data information for the corresponding column in a result set.
+ If this is an OUT-parameter of a stored procedure, preserve meta-data of
+ stored-routine variable.
+
+ @param field container for meta-data to be filled
+*/
+
+void Item_param::make_field(Send_field *field)
+{
+ Item::make_field(field);
+
+ if (!m_out_param_info)
+ return;
+
+ /*
+ This is an OUT-parameter of stored procedure. We should use
+ OUT-parameter info to fill out the names.
+ */
+
+ field->db_name= m_out_param_info->db_name;
+ field->table_name= m_out_param_info->table_name;
+ field->org_table_name= m_out_param_info->org_table_name;
+ field->col_name= m_out_param_info->col_name;
+ field->org_col_name= m_out_param_info->org_col_name;
+
+ field->length= m_out_param_info->length;
+ field->charsetnr= m_out_param_info->charsetnr;
+ field->flags= m_out_param_info->flags;
+ field->decimals= m_out_param_info->decimals;
+ field->type= m_out_param_info->type;
+}
+
+/****************************************************************************
+ Item_copy
+****************************************************************************/
+
+Item_copy *Item_copy::create (Item *item)
+{
+ switch (item->result_type())
+ {
+ case STRING_RESULT:
+ return new Item_copy_string (item);
+ case REAL_RESULT:
+ return new Item_copy_float (item);
+ case INT_RESULT:
+ return item->unsigned_flag ?
+ new Item_copy_uint (item) : new Item_copy_int (item);
+ case DECIMAL_RESULT:
+ return new Item_copy_decimal (item);
+ case TIME_RESULT:
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT (0);
+ }
+ /* should not happen */
+ return NULL;
+}
+
+/****************************************************************************
+ Item_copy_string
+****************************************************************************/
+
+double Item_copy_string::val_real()
+{
+ int err_not_used;
+ char *end_not_used;
+ return (null_value ? 0.0 :
+ my_strntod(str_value.charset(), (char*) str_value.ptr(),
+ str_value.length(), &end_not_used, &err_not_used));
+}
+
+longlong Item_copy_string::val_int()
+{
+ int err;
+ return null_value ? 0 : my_strntoll(str_value.charset(),str_value.ptr(),
+ str_value.length(), 10, (char**) 0,
+ &err);
+}
+
+
+int Item_copy_string::save_in_field(Field *field, bool no_conversions)
+{
+ return save_str_value_in_field(field, &str_value);
+}
+
+
+void Item_copy_string::copy()
+{
+ String *res=item->val_str(&str_value);
+ if (res && res != &str_value)
+ str_value.copy(*res);
+ null_value=item->null_value;
+}
+
+/* ARGSUSED */
+String *Item_copy_string::val_str(String *str)
+{
+ // Item_copy_string is used without fix_fields call
+ if (null_value)
+ return (String*) 0;
+ return &str_value;
+}
+
+
+my_decimal *Item_copy_string::val_decimal(my_decimal *decimal_value)
+{
+ // Item_copy_string is used without fix_fields call
+ if (null_value)
+ return (my_decimal *) 0;
+ string2my_decimal(E_DEC_FATAL_ERROR, &str_value, decimal_value);
+ return (decimal_value);
+}
+
+
+/****************************************************************************
+ Item_copy_int
+****************************************************************************/
+
+void Item_copy_int::copy()
+{
+ cached_value= item->val_int();
+ null_value=item->null_value;
+}
+
+static int save_int_value_in_field (Field *, longlong, bool, bool);
+
+int Item_copy_int::save_in_field(Field *field, bool no_conversions)
+{
+ return save_int_value_in_field(field, cached_value,
+ null_value, unsigned_flag);
+}
+
+
+String *Item_copy_int::val_str(String *str)
+{
+ if (null_value)
+ return (String *) 0;
+
+ str->set(cached_value, &my_charset_bin);
+ return str;
+}
+
+
+my_decimal *Item_copy_int::val_decimal(my_decimal *decimal_value)
+{
+ if (null_value)
+ return (my_decimal *) 0;
+
+ int2my_decimal(E_DEC_FATAL_ERROR, cached_value, unsigned_flag, decimal_value);
+ return decimal_value;
+}
+
+
+/****************************************************************************
+ Item_copy_uint
+****************************************************************************/
+
+String *Item_copy_uint::val_str(String *str)
+{
+ if (null_value)
+ return (String *) 0;
+
+ str->set((ulonglong) cached_value, &my_charset_bin);
+ return str;
+}
+
+
+/****************************************************************************
+ Item_copy_float
+****************************************************************************/
+
+String *Item_copy_float::val_str(String *str)
+{
+ if (null_value)
+ return (String *) 0;
+ else
+ {
+ double nr= val_real();
+ str->set_real(nr,decimals, &my_charset_bin);
+ return str;
+ }
+}
+
+
+my_decimal *Item_copy_float::val_decimal(my_decimal *decimal_value)
+{
+ if (null_value)
+ return (my_decimal *) 0;
+ else
+ {
+ double nr= val_real();
+ double2my_decimal(E_DEC_FATAL_ERROR, nr, decimal_value);
+ return decimal_value;
+ }
+}
+
+
+int Item_copy_float::save_in_field(Field *field, bool no_conversions)
+{
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ return field->store(cached_value);
+}
+
+
+/****************************************************************************
+ Item_copy_decimal
+****************************************************************************/
+
+int Item_copy_decimal::save_in_field(Field *field, bool no_conversions)
+{
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ return field->store_decimal(&cached_value);
+}
+
+
+String *Item_copy_decimal::val_str(String *result)
+{
+ if (null_value)
+ return (String *) 0;
+ result->set_charset(&my_charset_bin);
+ my_decimal2string(E_DEC_FATAL_ERROR, &cached_value, 0, 0, 0, result);
+ return result;
+}
+
+
+double Item_copy_decimal::val_real()
+{
+ if (null_value)
+ return 0.0;
+ else
+ {
+ double result;
+ my_decimal2double(E_DEC_FATAL_ERROR, &cached_value, &result);
+ return result;
+ }
+}
+
+
+longlong Item_copy_decimal::val_int()
+{
+ if (null_value)
+ return 0;
+ else
+ {
+ longlong result;
+ my_decimal2int(E_DEC_FATAL_ERROR, &cached_value, unsigned_flag, &result);
+ return result;
+ }
+}
+
+
+void Item_copy_decimal::copy()
+{
+ my_decimal *nr= item->val_decimal(&cached_value);
+ if (nr && nr != &cached_value)
+ my_decimal2decimal (nr, &cached_value);
+ null_value= item->null_value;
+}
+
+
+/*
+ Functions to convert item to field (for send_result_set_metadata)
+*/
+
+/* ARGSUSED */
+bool Item::fix_fields(THD *thd, Item **ref)
+{
+
+ // We do not check fields which are fixed during construction
+ DBUG_ASSERT(fixed == 0 || basic_const_item());
+ fixed= 1;
+ return FALSE;
+}
+
+
+void Item_ref_null_helper::save_val(Field *to)
+{
+ DBUG_ASSERT(fixed == 1);
+ (*ref)->save_val(to);
+ owner->was_null|= null_value= (*ref)->null_value;
+}
+
+
+double Item_ref_null_helper::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double tmp= (*ref)->val_result();
+ owner->was_null|= null_value= (*ref)->null_value;
+ return tmp;
+}
+
+
+longlong Item_ref_null_helper::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong tmp= (*ref)->val_int_result();
+ owner->was_null|= null_value= (*ref)->null_value;
+ return tmp;
+}
+
+
+my_decimal *Item_ref_null_helper::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ my_decimal *val= (*ref)->val_decimal_result(decimal_value);
+ owner->was_null|= null_value= (*ref)->null_value;
+ return val;
+}
+
+
+bool Item_ref_null_helper::val_bool()
+{
+ DBUG_ASSERT(fixed == 1);
+ bool val= (*ref)->val_bool_result();
+ owner->was_null|= null_value= (*ref)->null_value;
+ return val;
+}
+
+
+String* Item_ref_null_helper::val_str(String* s)
+{
+ DBUG_ASSERT(fixed == 1);
+ String* tmp= (*ref)->str_result(s);
+ owner->was_null|= null_value= (*ref)->null_value;
+ return tmp;
+}
+
+
+bool Item_ref_null_helper::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ return (owner->was_null|= null_value= (*ref)->get_date_result(ltime, fuzzydate));
+}
+
+
+/**
+ Mark item and SELECT_LEXs as dependent if item was resolved in
+ outer SELECT.
+
+ @param thd thread handler
+ @param last select from which current item depend
+ @param current current select
+ @param resolved_item item which was resolved in outer SELECT(for warning)
+ @param mark_item item which should be marked (can be differ in case of
+ substitution)
+*/
+
+static bool mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current,
+ Item_ident *resolved_item,
+ Item_ident *mark_item)
+{
+ const char *db_name= (resolved_item->db_name ?
+ resolved_item->db_name : "");
+ const char *table_name= (resolved_item->table_name ?
+ resolved_item->table_name : "");
+ /* store pointer on SELECT_LEX from which item is dependent */
+ if (mark_item && mark_item->can_be_depended)
+ mark_item->depended_from= last;
+ if (current->mark_as_dependent(thd, last, /** resolved_item psergey-thu
+ **/mark_item))
+ return TRUE;
+ if (thd->lex->describe & DESCRIBE_EXTENDED)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_WARN_FIELD_RESOLVED, ER(ER_WARN_FIELD_RESOLVED),
+ db_name, (db_name[0] ? "." : ""),
+ table_name, (table_name [0] ? "." : ""),
+ resolved_item->field_name,
+ current->select_number, last->select_number);
+ }
+ return FALSE;
+}
+
+
+/**
+ Mark range of selects and resolved identifier (field/reference)
+ item as dependent.
+
+ @param thd thread handler
+ @param last_select select where resolved_item was resolved
+ @param current_sel current select (select where resolved_item was placed)
+ @param found_field field which was found during resolving
+ @param found_item Item which was found during resolving (if resolved
+ identifier belongs to VIEW)
+ @param resolved_item Identifier which was resolved
+
+ @note
+ We have to mark all items between current_sel (including) and
+ last_select (excluding) as dependend (select before last_select should
+ be marked with actual table mask used by resolved item, all other with
+ OUTER_REF_TABLE_BIT) and also write dependence information to Item of
+ resolved identifier.
+*/
+
+void mark_select_range_as_dependent(THD *thd,
+ SELECT_LEX *last_select,
+ SELECT_LEX *current_sel,
+ Field *found_field, Item *found_item,
+ Item_ident *resolved_item)
+{
+ /*
+ Go from current SELECT to SELECT where field was resolved (it
+ have to be reachable from current SELECT, because it was already
+ done once when we resolved this field and cached result of
+ resolving)
+ */
+ SELECT_LEX *previous_select= current_sel;
+ for (; previous_select->outer_select() != last_select;
+ previous_select= previous_select->outer_select())
+ {
+ Item_subselect *prev_subselect_item=
+ previous_select->master_unit()->item;
+ prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT;
+ prev_subselect_item->const_item_cache= 0;
+ }
+ {
+ Item_subselect *prev_subselect_item=
+ previous_select->master_unit()->item;
+ Item_ident *dependent= resolved_item;
+ if (found_field == view_ref_found)
+ {
+ Item::Type type= found_item->type();
+ prev_subselect_item->used_tables_cache|=
+ found_item->used_tables();
+ dependent= ((type == Item::REF_ITEM || type == Item::FIELD_ITEM) ?
+ (Item_ident*) found_item :
+ 0);
+ }
+ else
+ prev_subselect_item->used_tables_cache|=
+ found_field->table->map;
+ prev_subselect_item->const_item_cache= 0;
+ mark_as_dependent(thd, last_select, current_sel, resolved_item,
+ dependent);
+ }
+}
+
+
+/**
+ Search a GROUP BY clause for a field with a certain name.
+
+ Search the GROUP BY list for a column named as find_item. When searching
+ preference is given to columns that are qualified with the same table (and
+ database) name as the one being searched for.
+
+ @param find_item the item being searched for
+ @param group_list GROUP BY clause
+
+ @return
+ - the found item on success
+ - NULL if find_item is not in group_list
+*/
+
+static Item** find_field_in_group_list(Item *find_item, ORDER *group_list)
+{
+ const char *db_name;
+ const char *table_name;
+ const char *field_name;
+ ORDER *found_group= NULL;
+ int found_match_degree= 0;
+ Item_ident *cur_field;
+ int cur_match_degree= 0;
+ char name_buff[SAFE_NAME_LEN+1];
+
+ if (find_item->type() == Item::FIELD_ITEM ||
+ find_item->type() == Item::REF_ITEM)
+ {
+ db_name= ((Item_ident*) find_item)->db_name;
+ table_name= ((Item_ident*) find_item)->table_name;
+ field_name= ((Item_ident*) find_item)->field_name;
+ }
+ else
+ return NULL;
+
+ if (db_name && lower_case_table_names)
+ {
+ /* Convert database to lower case for comparison */
+ strmake_buf(name_buff, db_name);
+ my_casedn_str(files_charset_info, name_buff);
+ db_name= name_buff;
+ }
+
+ DBUG_ASSERT(field_name != 0);
+
+ for (ORDER *cur_group= group_list ; cur_group ; cur_group= cur_group->next)
+ {
+ if ((*(cur_group->item))->real_item()->type() == Item::FIELD_ITEM)
+ {
+ cur_field= (Item_ident*) *cur_group->item;
+ cur_match_degree= 0;
+
+ DBUG_ASSERT(cur_field->field_name != 0);
+
+ if (!my_strcasecmp(system_charset_info,
+ cur_field->field_name, field_name))
+ ++cur_match_degree;
+ else
+ continue;
+
+ if (cur_field->table_name && table_name)
+ {
+ /* If field_name is qualified by a table name. */
+ if (my_strcasecmp(table_alias_charset, cur_field->table_name, table_name))
+ /* Same field names, different tables. */
+ return NULL;
+
+ ++cur_match_degree;
+ if (cur_field->db_name && db_name)
+ {
+ /* If field_name is also qualified by a database name. */
+ if (strcmp(cur_field->db_name, db_name))
+ /* Same field names, different databases. */
+ return NULL;
+ ++cur_match_degree;
+ }
+ }
+
+ if (cur_match_degree > found_match_degree)
+ {
+ found_match_degree= cur_match_degree;
+ found_group= cur_group;
+ }
+ else if (found_group && (cur_match_degree == found_match_degree) &&
+ ! (*(found_group->item))->eq(cur_field, 0))
+ {
+ /*
+ If the current resolve candidate matches equally well as the current
+ best match, they must reference the same column, otherwise the field
+ is ambiguous.
+ */
+ my_error(ER_NON_UNIQ_ERROR, MYF(0),
+ find_item->full_name(), current_thd->where);
+ return NULL;
+ }
+ }
+ }
+
+ if (found_group)
+ return found_group->item;
+ else
+ return NULL;
+}
+
+
+/**
+ Resolve a column reference in a sub-select.
+
+ Resolve a column reference (usually inside a HAVING clause) against the
+ SELECT and GROUP BY clauses of the query described by 'select'. The name
+ resolution algorithm searches both the SELECT and GROUP BY clauses, and in
+ case of a name conflict prefers GROUP BY column names over SELECT names. If
+ both clauses contain different fields with the same names, a warning is
+ issued that name of 'ref' is ambiguous. We extend ANSI SQL in that when no
+ GROUP BY column is found, then a HAVING name is resolved as a possibly
+ derived SELECT column. This extension is allowed only if the
+ MODE_ONLY_FULL_GROUP_BY sql mode isn't enabled.
+
+ @param thd current thread
+ @param ref column reference being resolved
+ @param select the select that ref is resolved against
+
+ @note
+ The resolution procedure is:
+ - Search for a column or derived column named col_ref_i [in table T_j]
+ in the SELECT clause of Q.
+ - Search for a column named col_ref_i [in table T_j]
+ in the GROUP BY clause of Q.
+ - If found different columns with the same name in GROUP BY and SELECT
+ - issue a warning and return the GROUP BY column,
+ - otherwise
+ - if the MODE_ONLY_FULL_GROUP_BY mode is enabled return error
+ - else return the found SELECT column.
+
+
+ @return
+ - NULL - there was an error, and the error was already reported
+ - not_found_item - the item was not resolved, no error was reported
+ - resolved item - if the item was resolved
+*/
+
+static Item**
+resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select)
+{
+ Item **group_by_ref= NULL;
+ Item **select_ref= NULL;
+ ORDER *group_list= select->group_list.first;
+ bool ambiguous_fields= FALSE;
+ uint counter;
+ enum_resolution_type resolution;
+
+ /*
+ Search for a column or derived column named as 'ref' in the SELECT
+ clause of the current select.
+ */
+ if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()),
+ &counter, REPORT_EXCEPT_NOT_FOUND,
+ &resolution)))
+ return NULL; /* Some error occurred. */
+ if (resolution == RESOLVED_AGAINST_ALIAS)
+ ref->alias_name_used= TRUE;
+
+ /* If this is a non-aggregated field inside HAVING, search in GROUP BY. */
+ if (select->having_fix_field && !ref->with_sum_func && group_list)
+ {
+ group_by_ref= find_field_in_group_list(ref, group_list);
+
+ /* Check if the fields found in SELECT and GROUP BY are the same field. */
+ if (group_by_ref && (select_ref != not_found_item) &&
+ !((*group_by_ref)->eq(*select_ref, 0)))
+ {
+ ambiguous_fields= TRUE;
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NON_UNIQ_ERROR,
+ ER(ER_NON_UNIQ_ERROR), ref->full_name(),
+ current_thd->where);
+
+ }
+ }
+
+ if (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY &&
+ select->having_fix_field &&
+ select_ref != not_found_item && !group_by_ref)
+ {
+ /*
+ Report the error if fields was found only in the SELECT item list and
+ the strict mode is enabled.
+ */
+ my_error(ER_NON_GROUPING_FIELD_USED, MYF(0),
+ ref->name, "HAVING");
+ return NULL;
+ }
+ if (select_ref != not_found_item || group_by_ref)
+ {
+ if (select_ref != not_found_item && !ambiguous_fields)
+ {
+ DBUG_ASSERT(*select_ref != 0);
+ if (!select->ref_pointer_array[counter])
+ {
+ my_error(ER_ILLEGAL_REFERENCE, MYF(0),
+ ref->name, "forward reference in item list");
+ return NULL;
+ }
+ DBUG_ASSERT((*select_ref)->fixed);
+ return (select->ref_pointer_array + counter);
+ }
+ if (group_by_ref)
+ return group_by_ref;
+ DBUG_ASSERT(FALSE);
+ return NULL; /* So there is no compiler warning. */
+ }
+
+ return (Item**) not_found_item;
+}
+
+
+/*
+ @brief
+ Whether a table belongs to an outer select.
+
+ @param table table to check
+ @param select current select
+
+ @details
+ Try to find select the table belongs to by ascending the derived tables chain.
+*/
+
+static
+bool is_outer_table(TABLE_LIST *table, SELECT_LEX *select)
+{
+ DBUG_ASSERT(table->select_lex != select);
+ TABLE_LIST *tl;
+
+ if (table->belong_to_view &&
+ table->belong_to_view->select_lex == select)
+ return FALSE;
+
+ for (tl= select->master_unit()->derived;
+ tl && tl->is_merged_derived();
+ select= tl->select_lex, tl= select->master_unit()->derived)
+ {
+ if (tl->select_lex == table->select_lex)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/**
+ Resolve the name of an outer select column reference.
+
+ @param[in] thd current thread
+ @param[in,out] from_field found field reference or (Field*)not_found_field
+ @param[in,out] reference view column if this item was resolved to a
+ view column
+
+ @description
+ The method resolves the column reference represented by 'this' as a column
+ present in outer selects that contain current select.
+
+ In prepared statements, because of cache, find_field_in_tables()
+ can resolve fields even if they don't belong to current context.
+ In this case this method only finds appropriate context and marks
+ current select as dependent. The found reference of field should be
+ provided in 'from_field'.
+
+ The cache is critical for prepared statements of type:
+
+ SELECT a FROM (SELECT a FROM test.t1) AS s1 NATURAL JOIN t2 AS s2;
+
+ This is internally converted to a join similar to
+
+ SELECT a FROM t1 AS s1,t2 AS s2 WHERE t2.a=t1.a;
+
+ Without the cache, we would on re-prepare not know if 'a' did match
+ s1.a or s2.a.
+
+ @note
+ This is the inner loop of Item_field::fix_fields:
+ @code
+ for each outer query Q_k beginning from the inner-most one
+ {
+ search for a column or derived column named col_ref_i
+ [in table T_j] in the FROM clause of Q_k;
+
+ if such a column is not found
+ Search for a column or derived column named col_ref_i
+ [in table T_j] in the SELECT and GROUP clauses of Q_k.
+ }
+ @endcode
+
+ @retval
+ 1 column succefully resolved and fix_fields() should continue.
+ @retval
+ 0 column fully fixed and fix_fields() should return FALSE
+ @retval
+ -1 error occured
+*/
+
+int
+Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference)
+{
+ enum_parsing_place place= NO_MATTER;
+ bool field_found= (*from_field != not_found_field);
+ bool upward_lookup= FALSE;
+ TABLE_LIST *table_list;
+
+ /* Calulate the TABLE_LIST for the table */
+ table_list= (cached_table ? cached_table :
+ field_found && (*from_field) != view_ref_found ?
+ (*from_field)->table->pos_in_table_list : 0);
+ /*
+ If there are outer contexts (outer selects, but current select is
+ not derived table or view) try to resolve this reference in the
+ outer contexts.
+
+ We treat each subselect as a separate namespace, so that different
+ subselects may contain columns with the same names. The subselects
+ are searched starting from the innermost.
+ */
+ Name_resolution_context *last_checked_context= context;
+ Item **ref= (Item **) not_found_item;
+ SELECT_LEX *current_sel= (SELECT_LEX *) thd->lex->current_select;
+ Name_resolution_context *outer_context= 0;
+ SELECT_LEX *select= 0;
+ /* Currently derived tables cannot be correlated */
+ if (current_sel->master_unit()->first_select()->linkage !=
+ DERIVED_TABLE_TYPE)
+ outer_context= context->outer_context;
+
+ /*
+ This assert is to ensure we have an outer contex when *from_field
+ is set.
+ If this would not be the case, we would assert in mark_as_dependent
+ as last_checked_countex == context
+ */
+ DBUG_ASSERT(outer_context || !*from_field ||
+ *from_field == not_found_field);
+ for (;
+ outer_context;
+ outer_context= outer_context->outer_context)
+ {
+ select= outer_context->select_lex;
+ Item_subselect *prev_subselect_item=
+ last_checked_context->select_lex->master_unit()->item;
+ last_checked_context= outer_context;
+ upward_lookup= TRUE;
+
+ place= prev_subselect_item->parsing_place;
+ /*
+ If outer_field is set, field was already found by first call
+ to find_field_in_tables(). Only need to find appropriate context.
+ */
+ if (field_found && outer_context->select_lex !=
+ table_list->select_lex)
+ continue;
+ /*
+ In case of a view, find_field_in_tables() writes the pointer to
+ the found view field into '*reference', in other words, it
+ substitutes this Item_field with the found expression.
+ */
+ if (field_found || (*from_field= find_field_in_tables(thd, this,
+ outer_context->
+ first_name_resolution_table,
+ outer_context->
+ last_name_resolution_table,
+ reference,
+ IGNORE_EXCEPT_NON_UNIQUE,
+ TRUE, TRUE)) !=
+ not_found_field)
+ {
+ if (*from_field)
+ {
+ if (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY &&
+ select->cur_pos_in_select_list != UNDEF_POS)
+ {
+ /*
+ As this is an outer field it should be added to the list of
+ non aggregated fields of the outer select.
+ */
+ marker= select->cur_pos_in_select_list;
+ select->non_agg_fields.push_back(this);
+ }
+ if (*from_field != view_ref_found)
+ {
+ prev_subselect_item->used_tables_cache|= (*from_field)->table->map;
+ prev_subselect_item->const_item_cache= 0;
+ set_field(*from_field);
+ if (!last_checked_context->select_lex->having_fix_field &&
+ select->group_list.elements &&
+ (place == SELECT_LIST || place == IN_HAVING))
+ {
+ Item_outer_ref *rf;
+ /*
+ If an outer field is resolved in a grouping select then it
+ is replaced for an Item_outer_ref object. Otherwise an
+ Item_field object is used.
+ The new Item_outer_ref object is saved in the inner_refs_list of
+ the outer select. Here it is only created. It can be fixed only
+ after the original field has been fixed and this is done in the
+ fix_inner_refs() function.
+ */
+ ;
+ if (!(rf= new Item_outer_ref(context, this)))
+ return -1;
+ thd->change_item_tree(reference, rf);
+ select->inner_refs_list.push_back(rf);
+ rf->in_sum_func= thd->lex->in_sum_func;
+ }
+ /*
+ A reference is resolved to a nest level that's outer or the same as
+ the nest level of the enclosing set function : adjust the value of
+ max_arg_level for the function if it's needed.
+ */
+ if (thd->lex->in_sum_func &&
+ thd->lex->in_sum_func->nest_level >= select->nest_level)
+ {
+ Item::Type ref_type= (*reference)->type();
+ set_if_bigger(thd->lex->in_sum_func->max_arg_level,
+ select->nest_level);
+ set_field(*from_field);
+ fixed= 1;
+ mark_as_dependent(thd, last_checked_context->select_lex,
+ context->select_lex, this,
+ ((ref_type == REF_ITEM ||
+ ref_type == FIELD_ITEM) ?
+ (Item_ident*) (*reference) : 0));
+ return 0;
+ }
+ }
+ else
+ {
+ Item::Type ref_type= (*reference)->type();
+ prev_subselect_item->used_tables_cache|=
+ (*reference)->used_tables();
+ prev_subselect_item->const_item_cache&=
+ (*reference)->const_item();
+ mark_as_dependent(thd, last_checked_context->select_lex,
+ context->select_lex, this,
+ ((ref_type == REF_ITEM || ref_type == FIELD_ITEM) ?
+ (Item_ident*) (*reference) :
+ 0));
+ /*
+ A reference to a view field had been found and we
+ substituted it instead of this Item (find_field_in_tables
+ does it by assigning the new value to *reference), so now
+ we can return from this function.
+ */
+ return 0;
+ }
+ }
+ break;
+ }
+
+ /* Search in SELECT and GROUP lists of the outer select. */
+ if (place != IN_WHERE && place != IN_ON)
+ {
+ if (!(ref= resolve_ref_in_select_and_group(thd, this, select)))
+ return -1; /* Some error occurred (e.g. ambiguous names). */
+ if (ref != not_found_item)
+ {
+ DBUG_ASSERT(*ref && (*ref)->fixed);
+ prev_subselect_item->used_tables_cache|= (*ref)->used_tables();
+ prev_subselect_item->const_item_cache&= (*ref)->const_item();
+ break;
+ }
+ }
+
+ /*
+ Reference is not found in this select => this subquery depend on
+ outer select (or we just trying to find wrong identifier, in this
+ case it does not matter which used tables bits we set)
+ */
+ prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT;
+ prev_subselect_item->const_item_cache= 0;
+ }
+
+ DBUG_ASSERT(ref != 0);
+ if (!*from_field)
+ return -1;
+ if (ref == not_found_item && *from_field == not_found_field)
+ {
+ if (upward_lookup)
+ {
+ // We can't say exactly what absent table or field
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), full_name(), thd->where);
+ }
+ else
+ {
+ /* Call find_field_in_tables only to report the error */
+ find_field_in_tables(thd, this,
+ context->first_name_resolution_table,
+ context->last_name_resolution_table,
+ reference, REPORT_ALL_ERRORS,
+ !any_privileges, TRUE);
+ }
+ return -1;
+ }
+ else if (ref != not_found_item)
+ {
+ Item *save;
+ Item_ref *rf;
+
+ /* Should have been checked in resolve_ref_in_select_and_group(). */
+ DBUG_ASSERT(*ref && (*ref)->fixed);
+ /*
+ Here, a subset of actions performed by Item_ref::set_properties
+ is not enough. So we pass ptr to NULL into Item_[direct]_ref
+ constructor, so no initialization is performed, and call
+ fix_fields() below.
+ */
+ save= *ref;
+ *ref= NULL; // Don't call set_properties()
+ rf= (place == IN_HAVING ?
+ new Item_ref(context, ref, (char*) table_name,
+ (char*) field_name, alias_name_used) :
+ (!select->group_list.elements ?
+ new Item_direct_ref(context, ref, (char*) table_name,
+ (char*) field_name, alias_name_used) :
+ new Item_outer_ref(context, ref, (char*) table_name,
+ (char*) field_name, alias_name_used)));
+ *ref= save;
+ if (!rf)
+ return -1;
+
+ if (place != IN_HAVING && select->group_list.elements)
+ {
+ outer_context->select_lex->inner_refs_list.push_back((Item_outer_ref*)rf);
+ ((Item_outer_ref*)rf)->in_sum_func= thd->lex->in_sum_func;
+ }
+ thd->change_item_tree(reference, rf);
+ /*
+ rf is Item_ref => never substitute other items (in this case)
+ during fix_fields() => we can use rf after fix_fields()
+ */
+ DBUG_ASSERT(!rf->fixed); // Assured by Item_ref()
+ if (rf->fix_fields(thd, reference) || rf->check_cols(1))
+ return -1;
+
+ mark_as_dependent(thd, last_checked_context->select_lex,
+ context->select_lex, rf,
+ rf);
+
+ return 0;
+ }
+ else
+ {
+ mark_as_dependent(thd, last_checked_context->select_lex,
+ context->select_lex,
+ this, (Item_ident*)*reference);
+ if (last_checked_context->select_lex->having_fix_field)
+ {
+ Item_ref *rf;
+ rf= new Item_ref(context, (*from_field)->table->s->db.str,
+ (*from_field)->table->alias.c_ptr(),
+ (char*) field_name);
+ if (!rf)
+ return -1;
+ thd->change_item_tree(reference, rf);
+ /*
+ rf is Item_ref => never substitute other items (in this case)
+ during fix_fields() => we can use rf after fix_fields()
+ */
+ DBUG_ASSERT(!rf->fixed); // Assured by Item_ref()
+ if (rf->fix_fields(thd, reference) || rf->check_cols(1))
+ return -1;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/**
+ Resolve the name of a column reference.
+
+ The method resolves the column reference represented by 'this' as a column
+ present in one of: FROM clause, SELECT clause, GROUP BY clause of a query
+ Q, or in outer queries that contain Q.
+
+ The name resolution algorithm used is (where [T_j] is an optional table
+ name that qualifies the column name):
+
+ @code
+ resolve_column_reference([T_j].col_ref_i)
+ {
+ search for a column or derived column named col_ref_i
+ [in table T_j] in the FROM clause of Q;
+
+ if such a column is NOT found AND // Lookup in outer queries.
+ there are outer queries
+ {
+ for each outer query Q_k beginning from the inner-most one
+ {
+ search for a column or derived column named col_ref_i
+ [in table T_j] in the FROM clause of Q_k;
+
+ if such a column is not found
+ Search for a column or derived column named col_ref_i
+ [in table T_j] in the SELECT and GROUP clauses of Q_k.
+ }
+ }
+ }
+ @endcode
+
+ Notice that compared to Item_ref::fix_fields, here we first search the FROM
+ clause, and then we search the SELECT and GROUP BY clauses.
+
+ @param[in] thd current thread
+ @param[in,out] reference view column if this item was resolved to a
+ view column
+
+ @retval
+ TRUE if error
+ @retval
+ FALSE on success
+*/
+
+bool Item_field::fix_fields(THD *thd, Item **reference)
+{
+ DBUG_ASSERT(fixed == 0);
+ Field *from_field= (Field *)not_found_field;
+ bool outer_fixed= false;
+
+ if (!field) // If field is not checked
+ {
+ TABLE_LIST *table_list;
+ /*
+ In case of view, find_field_in_tables() write pointer to view field
+ expression to 'reference', i.e. it substitute that expression instead
+ of this Item_field
+ */
+ if ((from_field= find_field_in_tables(thd, this,
+ context->first_name_resolution_table,
+ context->last_name_resolution_table,
+ reference,
+ thd->lex->use_only_table_context ?
+ REPORT_ALL_ERRORS :
+ IGNORE_EXCEPT_NON_UNIQUE,
+ !any_privileges,
+ TRUE)) ==
+ not_found_field)
+ {
+ int ret;
+ /* Look up in current select's item_list to find aliased fields */
+ if (thd->lex->current_select->is_item_list_lookup)
+ {
+ uint counter;
+ enum_resolution_type resolution;
+ Item** res= find_item_in_list(this, thd->lex->current_select->item_list,
+ &counter, REPORT_EXCEPT_NOT_FOUND,
+ &resolution);
+ if (!res)
+ return 1;
+ if (resolution == RESOLVED_AGAINST_ALIAS)
+ alias_name_used= TRUE;
+ if (res != (Item **)not_found_item)
+ {
+ if ((*res)->type() == Item::FIELD_ITEM)
+ {
+ /*
+ It's an Item_field referencing another Item_field in the select
+ list.
+ Use the field from the Item_field in the select list and leave
+ the Item_field instance in place.
+ */
+
+ Field *new_field= (*((Item_field**)res))->field;
+
+ if (new_field == NULL)
+ {
+ /* The column to which we link isn't valid. */
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), (*res)->name,
+ current_thd->where);
+ return(1);
+ }
+
+ set_field(new_field);
+ return 0;
+ }
+ else
+ {
+ /*
+ It's not an Item_field in the select list so we must make a new
+ Item_ref to point to the Item in the select list and replace the
+ Item_field created by the parser with the new Item_ref.
+ */
+ Item_ref *rf= new Item_ref(context, db_name,table_name,field_name);
+ if (!rf)
+ return 1;
+ bool ret= rf->fix_fields(thd, (Item **) &rf) || rf->check_cols(1);
+ if (ret)
+ return TRUE;
+
+ SELECT_LEX *select= thd->lex->current_select;
+ thd->change_item_tree(reference,
+ select->parsing_place == IN_GROUP_BY &&
+ alias_name_used ? *rf->ref : rf);
+
+ return FALSE;
+ }
+ }
+ }
+ if ((ret= fix_outer_field(thd, &from_field, reference)) < 0)
+ goto error;
+ outer_fixed= TRUE;
+ if (!ret)
+ goto mark_non_agg_field;
+ }
+ else if (!from_field)
+ goto error;
+
+ table_list= (cached_table ? cached_table :
+ from_field != view_ref_found ?
+ from_field->table->pos_in_table_list : 0);
+ if (!outer_fixed && table_list && table_list->select_lex &&
+ context->select_lex &&
+ table_list->select_lex != context->select_lex &&
+ !context->select_lex->is_merged_child_of(table_list->select_lex) &&
+ is_outer_table(table_list, context->select_lex))
+ {
+ int ret;
+ if ((ret= fix_outer_field(thd, &from_field, reference)) < 0)
+ goto error;
+ outer_fixed= 1;
+ if (!ret)
+ goto mark_non_agg_field;
+ }
+
+ if (thd->lex->in_sum_func &&
+ thd->lex->in_sum_func->nest_level ==
+ thd->lex->current_select->nest_level)
+ set_if_bigger(thd->lex->in_sum_func->max_arg_level,
+ thd->lex->current_select->nest_level);
+ /*
+ if it is not expression from merged VIEW we will set this field.
+
+ We can leave expression substituted from view for next PS/SP rexecution
+ (i.e. do not register this substitution for reverting on cleanup()
+ (register_item_tree_changing())), because this subtree will be
+ fix_field'ed during setup_tables()->setup_underlying() (i.e. before
+ all other expressions of query, and references on tables which do
+ not present in query will not make problems.
+
+ Also we suppose that view can't be changed during PS/SP life.
+ */
+ if (from_field == view_ref_found)
+ return FALSE;
+
+ set_field(from_field);
+ }
+ else if (thd->mark_used_columns != MARK_COLUMNS_NONE)
+ {
+ TABLE *table= field->table;
+ MY_BITMAP *current_bitmap, *other_bitmap;
+ if (thd->mark_used_columns == MARK_COLUMNS_READ)
+ {
+ current_bitmap= table->read_set;
+ other_bitmap= table->write_set;
+ }
+ else
+ {
+ current_bitmap= table->write_set;
+ other_bitmap= table->read_set;
+ }
+ if (!bitmap_fast_test_and_set(current_bitmap, field->field_index))
+ {
+ if (!bitmap_is_set(other_bitmap, field->field_index))
+ {
+ /* First usage of column */
+ table->used_fields++; // Used to optimize loops
+ /* purecov: begin inspected */
+ table->covering_keys.intersect(field->part_of_key);
+ /* purecov: end */
+ }
+ }
+ }
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (any_privileges)
+ {
+ char *db, *tab;
+ db= field->table->s->db.str;
+ tab= field->table->s->table_name.str;
+ if (!(have_privileges= (get_column_grant(thd, &field->table->grant,
+ db, tab, field_name) &
+ VIEW_ANY_ACL)))
+ {
+ my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
+ "ANY", thd->security_ctx->priv_user,
+ thd->security_ctx->host_or_ip, field_name, tab);
+ goto error;
+ }
+ }
+#endif
+ fixed= 1;
+ if (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY &&
+ !outer_fixed && !thd->lex->in_sum_func &&
+ thd->lex->current_select->cur_pos_in_select_list != UNDEF_POS)
+ {
+ thd->lex->current_select->non_agg_fields.push_back(this);
+ marker= thd->lex->current_select->cur_pos_in_select_list;
+ }
+mark_non_agg_field:
+ /*
+ table->pos_in_table_list can be 0 when fixing partition functions
+ or virtual fields.
+ */
+ if (fixed && (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY) &&
+ field->table->pos_in_table_list)
+ {
+ /*
+ Mark selects according to presence of non aggregated fields.
+ Fields from outer selects added to the aggregate function
+ outer_fields list as it's unknown at the moment whether it's
+ aggregated or not.
+ We're using the select lex of the cached table (if present).
+ */
+ SELECT_LEX *select_lex;
+ if (cached_table)
+ select_lex= cached_table->select_lex;
+ else if (!(select_lex= field->table->pos_in_table_list->select_lex))
+ {
+ /*
+ This can only happen when there is no real table in the query.
+ We are using the field's resolution context. context->select_lex is eee
+ safe for use because it's either the SELECT we want to use
+ (the current level) or a stub added by non-SELECT queries.
+ */
+ select_lex= context->select_lex;
+ }
+ if (!thd->lex->in_sum_func)
+ select_lex->set_non_agg_field_used(true);
+ else
+ {
+ if (outer_fixed)
+ thd->lex->in_sum_func->outer_fields.push_back(this);
+ else if (thd->lex->in_sum_func->nest_level !=
+ thd->lex->current_select->nest_level)
+ select_lex->set_non_agg_field_used(true);
+ }
+ }
+ return FALSE;
+
+error:
+ context->process_error(thd);
+ return TRUE;
+}
+
+/*
+ @brief
+ Mark virtual columns as used in a partitioning expression
+*/
+
+bool Item_field::vcol_in_partition_func_processor(uchar *int_arg)
+{
+ DBUG_ASSERT(fixed);
+ if (field->vcol_info)
+ {
+ field->vcol_info->mark_as_in_partitioning_expr();
+ }
+ return FALSE;
+}
+
+
+void Item_field::cleanup()
+{
+ DBUG_ENTER("Item_field::cleanup");
+ Item_ident::cleanup();
+ depended_from= NULL;
+ /*
+ Even if this object was created by direct link to field in setup_wild()
+ it will be linked correctly next time by name of field and table alias.
+ I.e. we can drop 'field'.
+ */
+ field= result_field= 0;
+ item_equal= NULL;
+ null_value= FALSE;
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Find a field among specified multiple equalities.
+
+ The function first searches the field among multiple equalities
+ of the current level (in the cond_equal->current_level list).
+ If it fails, it continues searching in upper levels accessed
+ through a pointer cond_equal->upper_levels.
+ The search terminates as soon as a multiple equality containing
+ the field is found.
+
+ @param cond_equal reference to list of multiple equalities where
+ the field (this object) is to be looked for
+
+ @return
+ - First Item_equal containing the field, if success
+ - 0, otherwise
+*/
+
+Item_equal *Item_field::find_item_equal(COND_EQUAL *cond_equal)
+{
+ Item_equal *item= 0;
+ while (cond_equal)
+ {
+ List_iterator_fast<Item_equal> li(cond_equal->current_level);
+ while ((item= li++))
+ {
+ if (item->contains(field))
+ return item;
+ }
+ /*
+ The field is not found in any of the multiple equalities
+ of the current level. Look for it in upper levels
+ */
+ cond_equal= cond_equal->upper_levels;
+ }
+ return 0;
+}
+
+
+/**
+ Check whether a field item can be substituted for an equal item
+
+ @details
+ The function checks whether a substitution of a field item for
+ an equal item is valid.
+
+ @param arg *arg != NULL <-> the field is in the context
+ where substitution for an equal item is valid
+
+ @note
+ The following statement is not always true:
+ @n
+ x=y => F(x)=F(x/y).
+ @n
+ This means substitution of an item for an equal item not always
+ yields an equavalent condition. Here's an example:
+ @code
+ 'a'='a '
+ (LENGTH('a')=1) != (LENGTH('a ')=2)
+ @endcode
+ Such a substitution is surely valid if either the substituted
+ field is not of a STRING type or if it is an argument of
+ a comparison predicate.
+
+ @retval
+ TRUE substitution is valid
+ @retval
+ FALSE otherwise
+*/
+
+bool Item_field::subst_argument_checker(uchar **arg)
+{
+ return *arg &&
+ (*arg == (uchar *) Item::ANY_SUBST ||
+ result_type() != STRING_RESULT ||
+ (field->flags & BINARY_FLAG));
+}
+
+
+/**
+ Convert a numeric value to a zero-filled string
+
+ @param[in,out] item the item to operate on
+ @param field The field that this value is equated to
+
+ This function converts a numeric value to a string. In this conversion
+ the zero-fill flag of the field is taken into account.
+ This is required so the resulting string value can be used instead of
+ the field reference when propagating equalities.
+*/
+
+static void convert_zerofill_number_to_string(Item **item, Field_num *field)
+{
+ char buff[MAX_FIELD_WIDTH],*pos;
+ String tmp(buff,sizeof(buff), field->charset()), *res;
+
+ res= (*item)->val_str(&tmp);
+ if ((*item)->is_null())
+ *item= new Item_null();
+ else
+ {
+ field->prepend_zeros(res);
+ pos= (char *) sql_strmake (res->ptr(), res->length());
+ *item= new Item_string(pos, res->length(), field->charset());
+ }
+}
+
+
+/**
+ Set a pointer to the multiple equality the field reference belongs to
+ (if any).
+
+ The function looks for a multiple equality containing the field item
+ among those referenced by arg.
+ In the case such equality exists the function does the following.
+ If the found multiple equality contains a constant, then the field
+ reference is substituted for this constant, otherwise it sets a pointer
+ to the multiple equality in the field item.
+
+
+ @param arg reference to list of multiple equalities where
+ the field (this object) is to be looked for
+
+ @note
+ This function is supposed to be called as a callback parameter in calls
+ of the compile method.
+
+ @return
+ - pointer to the replacing constant item, if the field item was substituted
+ - pointer to the field item, otherwise.
+*/
+
+Item *Item_field::equal_fields_propagator(uchar *arg)
+{
+ if (no_const_subst)
+ return this;
+ item_equal= find_item_equal((COND_EQUAL *) arg);
+ Item *item= 0;
+ if (item_equal)
+ item= item_equal->get_const();
+ /*
+ Disable const propagation for items used in different comparison contexts.
+ This must be done because, for example, Item_hex_string->val_int() is not
+ the same as (Item_hex_string->val_str() in BINARY column)->val_int().
+ We cannot simply disable the replacement in a particular context (
+ e.g. <bin_col> = <int_col> AND <bin_col> = <hex_string>) since
+ Items don't know the context they are in and there are functions like
+ IF (<hex_string>, 'yes', 'no').
+ */
+ if (!item || !has_compatible_context(item))
+ item= this;
+ else if (field && (field->flags & ZEROFILL_FLAG) && IS_NUM(field->type()))
+ {
+ if (item && (cmp_context == STRING_RESULT || cmp_context == IMPOSSIBLE_RESULT))
+ convert_zerofill_number_to_string(&item, (Field_num *)field);
+ else
+ item= this;
+ }
+ return item;
+}
+
+
+/**
+ Mark the item to not be part of substitution if it's not a binary item.
+
+ See comments in Arg_comparator::set_compare_func() for details.
+*/
+
+bool Item_field::set_no_const_sub(uchar *arg)
+{
+ if (field->charset() != &my_charset_bin)
+ no_const_subst=1;
+ return FALSE;
+}
+
+
+/**
+ Replace an Item_field for an equal Item_field that evaluated earlier
+ (if any).
+
+ If this->item_equal points to some item and coincides with arg then
+ the function returns a pointer to an item that is taken from
+ the very beginning of the item_equal list which the Item_field
+ object refers to (belongs to) unless item_equal contains a constant
+ item. In this case the function returns this constant item,
+ (if the substitution does not require conversion).
+ If the Item_field object does not refer any Item_equal object
+ 'this' is returned .
+
+ @param arg NULL or points to so some item of the Item_equal type
+
+
+ @note
+ This function is supposed to be called as a callback parameter in calls
+ of the transformer method.
+
+ @return
+ - pointer to a replacement Item_field if there is a better equal item or
+ a pointer to a constant equal item;
+ - this - otherwise.
+*/
+
+Item *Item_field::replace_equal_field(uchar *arg)
+{
+ REPLACE_EQUAL_FIELD_ARG* param= (REPLACE_EQUAL_FIELD_ARG*)arg;
+ if (item_equal && item_equal == param->item_equal)
+ {
+ Item *const_item= item_equal->get_const();
+ if (const_item)
+ {
+ if (!has_compatible_context(const_item))
+ return this;
+ return const_item;
+ }
+ Item_field *subst=
+ (Item_field *)(item_equal->get_first(param->context_tab, this));
+ if (subst)
+ subst= (Item_field *) (subst->real_item());
+ if (subst && !field->eq(subst->field))
+ return subst;
+ }
+ return this;
+}
+
+
+void Item::init_make_field(Send_field *tmp_field,
+ enum enum_field_types field_type_arg)
+{
+ char *empty_name= (char*) "";
+ tmp_field->db_name= empty_name;
+ tmp_field->org_table_name= empty_name;
+ tmp_field->org_col_name= empty_name;
+ tmp_field->table_name= empty_name;
+ tmp_field->col_name= name;
+ tmp_field->charsetnr= collation.collation->number;
+ tmp_field->flags= (maybe_null ? 0 : NOT_NULL_FLAG) |
+ (my_binary_compare(charset_for_protocol()) ?
+ BINARY_FLAG : 0);
+ tmp_field->type= field_type_arg;
+ tmp_field->length=max_length;
+ tmp_field->decimals=decimals;
+ if (unsigned_flag)
+ tmp_field->flags |= UNSIGNED_FLAG;
+}
+
+void Item::make_field(Send_field *tmp_field)
+{
+ init_make_field(tmp_field, field_type());
+}
+
+
+enum_field_types Item::string_field_type() const
+{
+ enum_field_types f_type= MYSQL_TYPE_VAR_STRING;
+ if (max_length >= 16777216)
+ f_type= MYSQL_TYPE_LONG_BLOB;
+ else if (max_length >= 65536)
+ f_type= MYSQL_TYPE_MEDIUM_BLOB;
+ return f_type;
+}
+
+
+void Item_empty_string::make_field(Send_field *tmp_field)
+{
+ init_make_field(tmp_field, string_field_type());
+}
+
+
+enum_field_types Item::field_type() const
+{
+ switch (result_type()) {
+ case STRING_RESULT: return string_field_type();
+ case INT_RESULT: return MYSQL_TYPE_LONGLONG;
+ case DECIMAL_RESULT: return MYSQL_TYPE_NEWDECIMAL;
+ case REAL_RESULT: return MYSQL_TYPE_DOUBLE;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ return MYSQL_TYPE_VARCHAR;
+ }
+ return MYSQL_TYPE_VARCHAR;
+}
+
+
+/**
+ Verifies that the input string is well-formed according to its character set.
+ @param send_error If true, call my_error if string is not well-formed.
+
+ Will truncate input string if it is not well-formed.
+
+ @return
+ If well-formed: input string.
+ If not well-formed:
+ if strict mode: NULL pointer and we set this Item's value to NULL
+ if not strict mode: input string truncated up to last good character
+ */
+String *Item::check_well_formed_result(String *str, bool send_error)
+{
+ /* Check whether we got a well-formed string */
+ CHARSET_INFO *cs= str->charset();
+ uint wlen= str->well_formed_length();
+ if (wlen < str->length())
+ {
+ THD *thd= current_thd;
+ char hexbuf[7];
+ uint diff= str->length() - wlen;
+ set_if_smaller(diff, 3);
+ octet2hex(hexbuf, str->ptr() + wlen, diff);
+ if (send_error)
+ {
+ my_error(ER_INVALID_CHARACTER_STRING, MYF(0),
+ cs->csname, hexbuf);
+ return 0;
+ }
+ if (thd->is_strict_mode())
+ {
+ null_value= 1;
+ str= 0;
+ }
+ else
+ {
+ str->length(wlen);
+ }
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_INVALID_CHARACTER_STRING,
+ ER(ER_INVALID_CHARACTER_STRING), cs->csname, hexbuf);
+ }
+ return str;
+}
+
+/*
+ Compare two items using a given collation
+
+ SYNOPSIS
+ eq_by_collation()
+ item item to compare with
+ binary_cmp TRUE <-> compare as binaries
+ cs collation to use when comparing strings
+
+ DESCRIPTION
+ This method works exactly as Item::eq if the collation cs coincides with
+ the collation of the compared objects. Otherwise, first the collations that
+ differ from cs are replaced for cs and then the items are compared by
+ Item::eq. After the comparison the original collations of items are
+ restored.
+
+ RETURN
+ 1 compared items has been detected as equal
+ 0 otherwise
+*/
+
+bool Item::eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs)
+{
+ CHARSET_INFO *save_cs= 0;
+ CHARSET_INFO *save_item_cs= 0;
+ if (collation.collation != cs)
+ {
+ save_cs= collation.collation;
+ collation.collation= cs;
+ }
+ if (item->collation.collation != cs)
+ {
+ save_item_cs= item->collation.collation;
+ item->collation.collation= cs;
+ }
+ bool res= eq(item, binary_cmp);
+ if (save_cs)
+ collation.collation= save_cs;
+ if (save_item_cs)
+ item->collation.collation= save_item_cs;
+ return res;
+}
+
+
+/**
+ Create a field to hold a string value from an item.
+
+ If too_big_for_varchar() create a blob @n
+ If max_length > 0 create a varchar @n
+ If max_length == 0 create a CHAR(0)
+
+ @param table Table for which the field is created
+*/
+
+Field *Item::make_string_field(TABLE *table)
+{
+ Field *field;
+ DBUG_ASSERT(collation.collation);
+ /*
+ Note: the following check is repeated in
+ subquery_types_allow_materialization():
+ */
+ if (too_big_for_varchar())
+ field= new Field_blob(max_length, maybe_null, name,
+ collation.collation, TRUE);
+ /* Item_type_holder holds the exact type, do not change it */
+ else if (max_length > 0 &&
+ (type() != Item::TYPE_HOLDER || field_type() != MYSQL_TYPE_STRING))
+ field= new Field_varstring(max_length, maybe_null, name, table->s,
+ collation.collation);
+ else
+ field= new Field_string(max_length, maybe_null, name,
+ collation.collation);
+ if (field)
+ field->init(table);
+ return field;
+}
+
+
+/**
+ Create a field based on field_type of argument.
+
+ For now, this is only used to create a field for
+ IFNULL(x,something) and time functions
+
+ @retval
+ NULL error
+ @retval
+ \# Created field
+*/
+
+Field *Item::tmp_table_field_from_field_type(TABLE *table, bool fixed_length)
+{
+ /*
+ The field functions defines a field to be not null if null_ptr is not 0
+ */
+ uchar *null_ptr= maybe_null ? (uchar*) "" : 0;
+ Field *field;
+
+ switch (field_type()) {
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ field= Field_new_decimal::create_from_item(this);
+ break;
+ case MYSQL_TYPE_TINY:
+ field= new Field_tiny((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name, 0, unsigned_flag);
+ break;
+ case MYSQL_TYPE_SHORT:
+ field= new Field_short((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name, 0, unsigned_flag);
+ break;
+ case MYSQL_TYPE_LONG:
+ field= new Field_long((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name, 0, unsigned_flag);
+ break;
+#ifdef HAVE_LONG_LONG
+ case MYSQL_TYPE_LONGLONG:
+ field= new Field_longlong((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name, 0, unsigned_flag);
+ break;
+#endif
+ case MYSQL_TYPE_FLOAT:
+ field= new Field_float((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name, decimals, 0, unsigned_flag);
+ break;
+ case MYSQL_TYPE_DOUBLE:
+ field= new Field_double((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name, decimals, 0, unsigned_flag);
+ break;
+ case MYSQL_TYPE_INT24:
+ field= new Field_medium((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name, 0, unsigned_flag);
+ break;
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_DATE:
+ field= new Field_newdate(0, null_ptr, 0, Field::NONE, name);
+ break;
+ case MYSQL_TYPE_TIME:
+ field= new_Field_time(0, null_ptr, 0, Field::NONE, name, decimals);
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ field= new_Field_timestamp(0, null_ptr, 0,
+ Field::NONE, name, 0, decimals);
+ break;
+ case MYSQL_TYPE_DATETIME:
+ field= new_Field_datetime(0, null_ptr, 0, Field::NONE, name, decimals);
+ break;
+ case MYSQL_TYPE_YEAR:
+ field= new Field_year((uchar*) 0, max_length, null_ptr, 0, Field::NONE,
+ name);
+ break;
+ case MYSQL_TYPE_BIT:
+ field= new Field_bit_as_char(NULL, max_length, null_ptr, 0,
+ Field::NONE, name);
+ break;
+ default:
+ /* This case should never be chosen */
+ DBUG_ASSERT(0);
+ /* If something goes awfully wrong, it's better to get a string than die */
+ case MYSQL_TYPE_NULL:
+ case MYSQL_TYPE_STRING:
+ if (fixed_length && !too_big_for_varchar())
+ {
+ field= new Field_string(max_length, maybe_null, name,
+ collation.collation);
+ break;
+ }
+ /* Fall through to make_string_field() */
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ return make_string_field(table);
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ if (this->type() == Item::TYPE_HOLDER)
+ field= new Field_blob(max_length, maybe_null, name, collation.collation,
+ 1);
+ else
+ field= new Field_blob(max_length, maybe_null, name, collation.collation);
+ break; // Blob handled outside of case
+#ifdef HAVE_SPATIAL
+ case MYSQL_TYPE_GEOMETRY:
+ field= new Field_geom(max_length, maybe_null,
+ name, table->s, get_geometry_type());
+#endif /* HAVE_SPATIAL */
+ }
+ if (field)
+ field->init(table);
+ return field;
+}
+
+
+/* ARGSUSED */
+void Item_field::make_field(Send_field *tmp_field)
+{
+ field->make_field(tmp_field);
+ DBUG_ASSERT(tmp_field->table_name != 0);
+ if (name)
+ tmp_field->col_name=name; // Use user supplied name
+ if (table_name)
+ tmp_field->table_name= table_name;
+ if (db_name)
+ tmp_field->db_name= db_name;
+}
+
+
+/**
+ Save a field value in another field
+
+ @param from Field to take the value from
+ @param [out] null_value Pointer to the null_value flag to set
+ @param to Field to save the value in
+ @param no_conversions How to deal with NULL value
+
+ @details
+ The function takes the value of the field 'from' and, if this value
+ is not null, it saves in the field 'to' setting off the flag referenced
+ by 'null_value'. Otherwise this flag is set on and field 'to' is
+ also set to null possibly with conversion.
+
+ @note
+ This function is used by the functions Item_field::save_in_field,
+ Item_field::save_org_in_field and Item_ref::save_in_field
+
+ @retval FALSE OK
+ @retval TRUE Error
+
+*/
+
+static int save_field_in_field(Field *from, bool *null_value,
+ Field *to, bool no_conversions)
+{
+ int res;
+ DBUG_ENTER("save_field_in_field");
+ if (from->is_null())
+ {
+ (*null_value)= 1;
+ DBUG_RETURN(set_field_to_null_with_conversions(to, no_conversions));
+ }
+ to->set_notnull();
+
+ /*
+ If we're setting the same field as the one we're reading from there's
+ nothing to do. This can happen in 'SET x = x' type of scenarios.
+ */
+ if (to == from)
+ {
+ (*null_value)= 0;
+ DBUG_RETURN(0);
+ }
+
+ res= field_conv(to, from);
+ (*null_value)= 0;
+ DBUG_RETURN(res);
+}
+
+
+static int memcpy_field_value(Field *to, Field *from)
+{
+ if (to->ptr != from->ptr)
+ memcpy(to->ptr,from->ptr, to->pack_length());
+ return 0;
+}
+
+fast_field_copier Item_field::setup_fast_field_copier(Field *to)
+{
+ DBUG_ENTER("Item_field::setup_fast_field_copier");
+ DBUG_RETURN(memcpy_field_possible(to, field) ?
+ &memcpy_field_value :
+ &field_conv_incompatible);
+}
+
+
+/**
+ Set a field's value from a item.
+*/
+
+void Item_field::save_org_in_field(Field *to,
+ fast_field_copier fast_field_copier_func)
+{
+ DBUG_ENTER("Item_field::save_org_in_field");
+ DBUG_PRINT("enter", ("setup: 0x%lx data: 0x%lx",
+ (ulong) to, (ulong) fast_field_copier_func));
+ if (fast_field_copier_func)
+ {
+ if (field->is_null())
+ {
+ null_value= TRUE;
+ set_field_to_null_with_conversions(to, TRUE);
+ DBUG_VOID_RETURN;
+ }
+ to->set_notnull();
+ if (to == field)
+ {
+ null_value= 0;
+ DBUG_VOID_RETURN;
+ }
+ (*fast_field_copier_func)(to, field);
+ }
+ else
+ save_field_in_field(field, &null_value, to, TRUE);
+ DBUG_VOID_RETURN;
+}
+
+
+int Item_field::save_in_field(Field *to, bool no_conversions)
+{
+ return save_field_in_field(result_field, &null_value, to, no_conversions);
+}
+
+
+/**
+ Store null in field.
+
+ This is used on INSERT.
+ Allow NULL to be inserted in timestamp and auto_increment values.
+
+ @param field Field where we want to store NULL
+
+ @retval
+ 0 ok
+ @retval
+ 1 Field doesn't support NULL values and can't handle 'field = NULL'
+*/
+
+int Item_null::save_in_field(Field *field, bool no_conversions)
+{
+ return set_field_to_null_with_conversions(field, no_conversions);
+}
+
+
+/**
+ Store null in field.
+
+ @param field Field where we want to store NULL
+
+ @retval
+ 0 OK
+ @retval
+ 1 Field doesn't support NULL values
+*/
+
+int Item_null::save_safe_in_field(Field *field)
+{
+ return set_field_to_null(field);
+}
+
+
+/*
+ This implementation can lose str_value content, so if the
+ Item uses str_value to store something, it should
+ reimplement it's ::save_in_field() as Item_string, for example, does.
+
+ Note: all Item_XXX::val_str(str) methods must NOT assume that
+ str != str_value. For example, see fix for bug #44743.
+*/
+
+int Item::save_in_field(Field *field, bool no_conversions)
+{
+ int error;
+ if (result_type() == STRING_RESULT)
+ {
+ String *result;
+ CHARSET_INFO *cs= collation.collation;
+ char buff[MAX_FIELD_WIDTH]; // Alloc buffer for small columns
+ str_value.set_quick(buff, sizeof(buff), cs);
+ result=val_str(&str_value);
+ if (null_value)
+ {
+ str_value.set_quick(0, 0, cs);
+ return set_field_to_null_with_conversions(field, no_conversions);
+ }
+
+ /* NOTE: If null_value == FALSE, "result" must be not NULL. */
+
+ field->set_notnull();
+ error=field->store(result->ptr(),result->length(),cs);
+ str_value.set_quick(0, 0, cs);
+ }
+ else if (result_type() == REAL_RESULT)
+ {
+ double nr= val_real();
+ if (null_value)
+ return set_field_to_null_with_conversions(field, no_conversions);
+ field->set_notnull();
+ error=field->store(nr);
+ }
+ else if (result_type() == DECIMAL_RESULT)
+ {
+ my_decimal decimal_value;
+ my_decimal *value= val_decimal(&decimal_value);
+ if (null_value)
+ return set_field_to_null_with_conversions(field, no_conversions);
+ field->set_notnull();
+ error=field->store_decimal(value);
+ }
+ else
+ {
+ longlong nr=val_int();
+ if (null_value)
+ return set_field_to_null_with_conversions(field, no_conversions);
+ field->set_notnull();
+ error=field->store(nr, unsigned_flag);
+ }
+ return error ? error : (field->table->in_use->is_error() ? 1 : 0);
+}
+
+
+int Item_string::save_in_field(Field *field, bool no_conversions)
+{
+ String *result;
+ result=val_str(&str_value);
+ return save_str_value_in_field(field, result);
+}
+
+
+static int save_int_value_in_field (Field *field, longlong nr,
+ bool null_value, bool unsigned_flag)
+{
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ return field->store(nr, unsigned_flag);
+}
+
+
+int Item_int::save_in_field(Field *field, bool no_conversions)
+{
+ return save_int_value_in_field (field, val_int(), null_value, unsigned_flag);
+}
+
+
+void Item_datetime::set(longlong packed)
+{
+ unpack_time(packed, &ltime);
+}
+
+int Item_datetime::save_in_field(Field *field, bool no_conversions)
+{
+ field->set_notnull();
+ return field->store_time_dec(&ltime, decimals);
+}
+
+longlong Item_datetime::val_int()
+{
+ return TIME_to_ulonglong(&ltime);
+}
+
+int Item_decimal::save_in_field(Field *field, bool no_conversions)
+{
+ field->set_notnull();
+ return field->store_decimal(&decimal_value);
+}
+
+
+Item *Item_int_with_ref::clone_item()
+{
+ DBUG_ASSERT(ref->const_item());
+ /*
+ We need to evaluate the constant to make sure it works with
+ parameter markers.
+ */
+ return (ref->unsigned_flag ?
+ new Item_uint(ref->name, ref->val_int(), ref->max_length) :
+ new Item_int(ref->name, ref->val_int(), ref->max_length));
+}
+
+
+Item_num *Item_uint::neg()
+{
+ Item_decimal *item= new Item_decimal(value, 1);
+ return item->neg();
+}
+
+
+static uint nr_of_decimals(const char *str, const char *end)
+{
+ const char *decimal_point;
+
+ /* Find position for '.' */
+ for (;;)
+ {
+ if (str == end)
+ return 0;
+ if (*str == 'e' || *str == 'E')
+ return NOT_FIXED_DEC;
+ if (*str++ == '.')
+ break;
+ }
+ decimal_point= str;
+ for ( ; str < end && my_isdigit(system_charset_info, *str) ; str++)
+ ;
+ if (str < end && (*str == 'e' || *str == 'E'))
+ return NOT_FIXED_DEC;
+ /*
+ QQ:
+ The number of decimal digist in fact should be (str - decimal_point - 1).
+ But it seems the result of nr_of_decimals() is never used!
+
+ In case of 'e' and 'E' nr_of_decimals returns NOT_FIXED_DEC.
+ In case if there is no 'e' or 'E' parser code in sql_yacc.yy
+ never calls Item_float::Item_float() - it creates Item_decimal instead.
+
+ The only piece of code where we call Item_float::Item_float(str, len)
+ without having 'e' or 'E' is item_xmlfunc.cc, but this Item_float
+ never appears in metadata itself. Changing the code to return
+ (str - decimal_point - 1) does not make any changes in the test results.
+
+ This should be addressed somehow.
+ Looks like a reminder from before real DECIMAL times.
+ */
+ return (uint) (str - decimal_point);
+}
+
+
+/**
+ This function is only called during parsing:
+ - when parsing SQL query from sql_yacc.yy
+ - when parsing XPath query from item_xmlfunc.cc
+ We will signal an error if value is not a true double value (overflow):
+ eng: Illegal %s '%-.192s' value found during parsing
+
+ Note: the string is NOT null terminated when called from item_xmlfunc.cc,
+ so this->name will contain some SQL query tail behind the "length" bytes.
+ This is Ok for now, as this Item is never seen in SHOW,
+ or EXPLAIN, or anywhere else in metadata.
+ Item->name should be fixed to use LEX_STRING eventually.
+*/
+
+Item_float::Item_float(const char *str_arg, uint length)
+{
+ int error;
+ char *end_not_used;
+ value= my_strntod(&my_charset_bin, (char*) str_arg, length, &end_not_used,
+ &error);
+ if (error)
+ {
+ char tmp[NAME_LEN + 1];
+ my_snprintf(tmp, sizeof(tmp), "%.*s", length, str_arg);
+ my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "double", tmp);
+ }
+ presentation= name=(char*) str_arg;
+ decimals=(uint8) nr_of_decimals(str_arg, str_arg+length);
+ max_length=length;
+ fixed= 1;
+}
+
+
+int Item_float::save_in_field(Field *field, bool no_conversions)
+{
+ double nr= val_real();
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ return field->store(nr);
+}
+
+
+void Item_float::print(String *str, enum_query_type query_type)
+{
+ if (presentation)
+ {
+ str->append(presentation);
+ return;
+ }
+ char buffer[20];
+ String num(buffer, sizeof(buffer), &my_charset_bin);
+ num.set_real(value, decimals, &my_charset_bin);
+ str->append(num);
+}
+
+
+inline uint char_val(char X)
+{
+ return (uint) (X >= '0' && X <= '9' ? X-'0' :
+ X >= 'A' && X <= 'Z' ? X-'A'+10 :
+ X-'a'+10);
+}
+
+
+void Item_hex_constant::hex_string_init(const char *str, uint str_length)
+{
+ max_length=(str_length+1)/2;
+ char *ptr=(char*) sql_alloc(max_length+1);
+ if (!ptr)
+ {
+ str_value.set("", 0, &my_charset_bin);
+ return;
+ }
+ str_value.set(ptr,max_length,&my_charset_bin);
+ char *end=ptr+max_length;
+ if (max_length*2 != str_length)
+ *ptr++=char_val(*str++); // Not even, assume 0 prefix
+ while (ptr != end)
+ {
+ *ptr++= (char) (char_val(str[0])*16+char_val(str[1]));
+ str+=2;
+ }
+ *ptr=0; // Keep purify happy
+ collation.set(&my_charset_bin, DERIVATION_COERCIBLE);
+ fixed= 1;
+ unsigned_flag= 1;
+}
+
+longlong Item_hex_hybrid::val_int()
+{
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ char *end=(char*) str_value.ptr()+str_value.length(),
+ *ptr=end-MY_MIN(str_value.length(),sizeof(longlong));
+
+ ulonglong value=0;
+ for (; ptr != end ; ptr++)
+ value=(value << 8)+ (ulonglong) (uchar) *ptr;
+ return (longlong) value;
+}
+
+
+int Item_hex_hybrid::save_in_field(Field *field, bool no_conversions)
+{
+ field->set_notnull();
+ if (field->result_type() == STRING_RESULT)
+ return field->store(str_value.ptr(), str_value.length(),
+ collation.collation);
+
+ ulonglong nr;
+ uint32 length= str_value.length();
+
+ if (length > 8)
+ {
+ nr= field->flags & UNSIGNED_FLAG ? ULONGLONG_MAX : LONGLONG_MAX;
+ goto warn;
+ }
+ nr= (ulonglong) val_int();
+ if ((length == 8) && !(field->flags & UNSIGNED_FLAG) && (nr > LONGLONG_MAX))
+ {
+ nr= LONGLONG_MAX;
+ goto warn;
+ }
+ return field->store((longlong) nr, TRUE); // Assume hex numbers are unsigned
+
+warn:
+ if (!field->store((longlong) nr, TRUE))
+ field->set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE,
+ 1);
+ return 1;
+}
+
+
+void Item_hex_hybrid::print(String *str, enum_query_type query_type)
+{
+ uint32 len= MY_MIN(str_value.length(), sizeof(longlong));
+ const char *ptr= str_value.ptr() + str_value.length() - len;
+ str->append("0x");
+ str->append_hex(ptr, len);
+}
+
+
+void Item_hex_string::print(String *str, enum_query_type query_type)
+{
+ str->append("X'");
+ str->append_hex(str_value.ptr(), str_value.length());
+ str->append("'");
+}
+
+
+/*
+ bin item.
+ In string context this is a binary string.
+ In number context this is a longlong value.
+*/
+
+Item_bin_string::Item_bin_string(const char *str, uint str_length)
+{
+ const char *end= str + str_length - 1;
+ uchar bits= 0;
+ uint power= 1;
+
+ max_length= (str_length + 7) >> 3;
+ char *ptr= (char*) sql_alloc(max_length + 1);
+ if (!ptr)
+ return;
+ str_value.set(ptr, max_length, &my_charset_bin);
+
+ if (max_length > 0)
+ {
+ ptr+= max_length - 1;
+ ptr[1]= 0; // Set end null for string
+ for (; end >= str; end--)
+ {
+ if (power == 256)
+ {
+ power= 1;
+ *ptr--= bits;
+ bits= 0;
+ }
+ if (*end == '1')
+ bits|= power;
+ power<<= 1;
+ }
+ *ptr= (char) bits;
+ }
+ else
+ ptr[0]= 0;
+
+ collation.set(&my_charset_bin, DERIVATION_COERCIBLE);
+ fixed= 1;
+}
+
+
+bool Item_temporal_literal::eq(const Item *item, bool binary_cmp) const
+{
+ return
+ item->basic_const_item() && type() == item->type() &&
+ field_type() == ((Item_temporal_literal *) item)->field_type() &&
+ !my_time_compare(&cached_time,
+ &((Item_temporal_literal *) item)->cached_time);
+}
+
+
+void Item_date_literal::print(String *str, enum_query_type query_type)
+{
+ str->append("DATE'");
+ char buf[MAX_DATE_STRING_REP_LENGTH];
+ my_date_to_str(&cached_time, buf);
+ str->append(buf);
+ str->append('\'');
+}
+
+
+bool Item_date_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed);
+ fuzzy_date |= sql_mode_for_dates(current_thd);
+ *ltime= cached_time;
+ return (null_value= check_date_with_warn(ltime, fuzzy_date,
+ MYSQL_TIMESTAMP_ERROR));
+}
+
+
+void Item_datetime_literal::print(String *str, enum_query_type query_type)
+{
+ str->append("TIMESTAMP'");
+ char buf[MAX_DATE_STRING_REP_LENGTH];
+ my_datetime_to_str(&cached_time, buf, decimals);
+ str->append(buf);
+ str->append('\'');
+}
+
+
+bool Item_datetime_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed);
+ fuzzy_date |= sql_mode_for_dates(current_thd);
+ *ltime= cached_time;
+ return (null_value= check_date_with_warn(ltime, fuzzy_date,
+ MYSQL_TIMESTAMP_ERROR));
+}
+
+
+void Item_time_literal::print(String *str, enum_query_type query_type)
+{
+ str->append("TIME'");
+ char buf[MAX_DATE_STRING_REP_LENGTH];
+ my_time_to_str(&cached_time, buf, decimals);
+ str->append(buf);
+ str->append('\'');
+}
+
+
+bool Item_time_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed);
+ *ltime= cached_time;
+ if (fuzzy_date & TIME_TIME_ONLY)
+ return (null_value= false);
+ return (null_value= check_date_with_warn(ltime, fuzzy_date,
+ MYSQL_TIMESTAMP_ERROR));
+}
+
+
+
+/**
+ Pack data in buffer for sending.
+*/
+
+bool Item_null::send(Protocol *protocol, String *packet)
+{
+ return protocol->store_null();
+}
+
+/**
+ This is only called from items that is not of type item_field.
+*/
+
+bool Item::send(Protocol *protocol, String *buffer)
+{
+ bool UNINIT_VAR(result); // Will be set if null_value == 0
+ enum_field_types f_type;
+
+ switch ((f_type=field_type())) {
+ default:
+ case MYSQL_TYPE_NULL:
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_NEWDECIMAL:
+ {
+ String *res;
+ if ((res=val_str(buffer)))
+ {
+ DBUG_ASSERT(!null_value);
+ result= protocol->store(res->ptr(),res->length(),res->charset());
+ }
+ else
+ {
+ DBUG_ASSERT(null_value);
+ }
+ break;
+ }
+ case MYSQL_TYPE_TINY:
+ {
+ longlong nr;
+ nr= val_int();
+ if (!null_value)
+ result= protocol->store_tiny(nr);
+ break;
+ }
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_YEAR:
+ {
+ longlong nr;
+ nr= val_int();
+ if (!null_value)
+ result= protocol->store_short(nr);
+ break;
+ }
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_LONG:
+ {
+ longlong nr;
+ nr= val_int();
+ if (!null_value)
+ result= protocol->store_long(nr);
+ break;
+ }
+ case MYSQL_TYPE_LONGLONG:
+ {
+ longlong nr;
+ nr= val_int();
+ if (!null_value)
+ result= protocol->store_longlong(nr, unsigned_flag);
+ break;
+ }
+ case MYSQL_TYPE_FLOAT:
+ {
+ float nr;
+ nr= (float) val_real();
+ if (!null_value)
+ result= protocol->store(nr, decimals, buffer);
+ break;
+ }
+ case MYSQL_TYPE_DOUBLE:
+ {
+ double nr= val_real();
+ if (!null_value)
+ result= protocol->store(nr, decimals, buffer);
+ break;
+ }
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TIMESTAMP:
+ {
+ MYSQL_TIME tm;
+ get_date(&tm, sql_mode_for_dates(current_thd));
+ if (!null_value)
+ {
+ if (f_type == MYSQL_TYPE_DATE)
+ return protocol->store_date(&tm);
+ else
+ result= protocol->store(&tm, decimals);
+ }
+ break;
+ }
+ case MYSQL_TYPE_TIME:
+ {
+ MYSQL_TIME tm;
+ get_time(&tm);
+ if (!null_value)
+ result= protocol->store_time(&tm, decimals);
+ break;
+ }
+ }
+ if (null_value)
+ result= protocol->store_null();
+ return result;
+}
+
+
+/**
+ Check if an item is a constant one and can be cached.
+
+ @param arg [out] TRUE <=> Cache this item.
+
+ @return TRUE Go deeper in item tree.
+ @return FALSE Don't go deeper in item tree.
+*/
+
+bool Item::cache_const_expr_analyzer(uchar **arg)
+{
+ bool *cache_flag= (bool*)*arg;
+ if (!*cache_flag)
+ {
+ Item *item= real_item();
+ /*
+ Cache constant items unless it's a basic constant, constant field or
+ a subselect (they use their own cache).
+ */
+ if (const_item() &&
+ !(basic_const_item() || item->basic_const_item() ||
+ item->type() == Item::FIELD_ITEM ||
+ item->type() == SUBSELECT_ITEM ||
+ /*
+ Do not cache GET_USER_VAR() function as its const_item() may
+ return TRUE for the current thread but it still may change
+ during the execution.
+ */
+ (item->type() == Item::FUNC_ITEM &&
+ ((Item_func*)item)->functype() == Item_func::GUSERVAR_FUNC)))
+ *cache_flag= TRUE;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Cache item if needed.
+
+ @param arg TRUE <=> Cache this item.
+
+ @return cache if cache needed.
+ @return this otherwise.
+*/
+
+Item* Item::cache_const_expr_transformer(uchar *arg)
+{
+ if (*(bool*)arg)
+ {
+ *((bool*)arg)= FALSE;
+ Item_cache *cache= Item_cache::get_cache(this);
+ if (!cache)
+ return NULL;
+ cache->setup(this);
+ cache->store(this);
+ return cache;
+ }
+ return this;
+}
+
+/**
+ Find Item by reference in the expression
+*/
+bool Item::find_item_processor(uchar *arg)
+{
+ return (this == ((Item *) arg));
+}
+
+bool Item_field::send(Protocol *protocol, String *buffer)
+{
+ return protocol->store(result_field);
+}
+
+
+void Item_field::update_null_value()
+{
+ /*
+ need to set no_errors to prevent warnings about type conversion
+ popping up.
+ */
+ THD *thd= field->table->in_use;
+ int no_errors;
+
+ no_errors= thd->no_errors;
+ thd->no_errors= 1;
+ Item::update_null_value();
+ thd->no_errors= no_errors;
+}
+
+
+/*
+ Add the field to the select list and substitute it for the reference to
+ the field.
+
+ SYNOPSIS
+ Item_field::update_value_transformer()
+ select_arg current select
+
+ DESCRIPTION
+ If the field doesn't belong to the table being inserted into then it is
+ added to the select list, pointer to it is stored in the ref_pointer_array
+ of the select and the field itself is substituted for the Item_ref object.
+ This is done in order to get correct values from update fields that
+ belongs to the SELECT part in the INSERT .. SELECT .. ON DUPLICATE KEY
+ UPDATE statement.
+
+ RETURN
+ 0 if error occured
+ ref if all conditions are met
+ this field otherwise
+*/
+
+Item *Item_field::update_value_transformer(uchar *select_arg)
+{
+ SELECT_LEX *select= (SELECT_LEX*)select_arg;
+ DBUG_ASSERT(fixed);
+
+ if (field->table != select->context.table_list->table &&
+ type() != Item::TRIGGER_FIELD_ITEM)
+ {
+ List<Item> *all_fields= &select->join->all_fields;
+ Item **ref_pointer_array= select->ref_pointer_array;
+ int el= all_fields->elements;
+ Item_ref *ref;
+
+ ref_pointer_array[el]= (Item*)this;
+ all_fields->push_front((Item*)this);
+ ref= new Item_ref(&select->context, ref_pointer_array + el,
+ table_name, field_name);
+ return ref;
+ }
+ return this;
+}
+
+
+void Item_field::print(String *str, enum_query_type query_type)
+{
+ if (field && field->table->const_table)
+ {
+ print_value(str);
+ return;
+ }
+ Item_ident::print(str, query_type);
+}
+
+
+Item_ref::Item_ref(Name_resolution_context *context_arg,
+ Item **item, const char *table_name_arg,
+ const char *field_name_arg,
+ bool alias_name_used_arg)
+ :Item_ident(context_arg, NullS, table_name_arg, field_name_arg),
+ result_field(0), ref(item), reference_trough_name(0)
+{
+ alias_name_used= alias_name_used_arg;
+ /*
+ This constructor used to create some internals references over fixed items
+ */
+ if (ref && *ref && (*ref)->fixed)
+ set_properties();
+}
+
+/*
+ A Field_enumerator-compatible class that invokes mark_as_dependent() for
+ each field that is a reference to some ancestor of current_select.
+*/
+class Dependency_marker: public Field_enumerator
+{
+public:
+ THD *thd;
+ st_select_lex *current_select;
+ virtual void visit_field(Item_field *item)
+ {
+ // Find which select the field is in. This is achieved by walking up
+ // the select tree and looking for the table of interest.
+ st_select_lex *sel;
+ for (sel= current_select; sel; sel= sel->outer_select())
+ {
+ List_iterator<TABLE_LIST> li(sel->leaf_tables);
+ TABLE_LIST *tbl;
+ while ((tbl= li++))
+ {
+ if (tbl->table == item->field->table)
+ {
+ if (sel != current_select)
+ mark_as_dependent(thd, sel, current_select, item, item);
+ return;
+ }
+ }
+ }
+ }
+};
+
+Item_ref::Item_ref(TABLE_LIST *view_arg, Item **item,
+ const char *field_name_arg, bool alias_name_used_arg)
+ :Item_ident(view_arg, field_name_arg),
+ result_field(NULL), ref(item), reference_trough_name(0)
+{
+ alias_name_used= alias_name_used_arg;
+ /*
+ This constructor is used to create some internal references over fixed items
+ */
+ if (ref && *ref && (*ref)->fixed)
+ set_properties();
+}
+
+
+/**
+ Resolve the name of a reference to a column reference.
+
+ The method resolves the column reference represented by 'this' as a column
+ present in one of: GROUP BY clause, SELECT clause, outer queries. It is
+ used typically for columns in the HAVING clause which are not under
+ aggregate functions.
+
+ POSTCONDITION @n
+ Item_ref::ref is 0 or points to a valid item.
+
+ @note
+ The name resolution algorithm used is (where [T_j] is an optional table
+ name that qualifies the column name):
+
+ @code
+ resolve_extended([T_j].col_ref_i)
+ {
+ Search for a column or derived column named col_ref_i [in table T_j]
+ in the SELECT and GROUP clauses of Q.
+
+ if such a column is NOT found AND // Lookup in outer queries.
+ there are outer queries
+ {
+ for each outer query Q_k beginning from the inner-most one
+ {
+ Search for a column or derived column named col_ref_i
+ [in table T_j] in the SELECT and GROUP clauses of Q_k.
+
+ if such a column is not found AND
+ - Q_k is not a group query AND
+ - Q_k is not inside an aggregate function
+ OR
+ - Q_(k-1) is not in a HAVING or SELECT clause of Q_k
+ {
+ search for a column or derived column named col_ref_i
+ [in table T_j] in the FROM clause of Q_k;
+ }
+ }
+ }
+ }
+ @endcode
+ @n
+ This procedure treats GROUP BY and SELECT clauses as one namespace for
+ column references in HAVING. Notice that compared to
+ Item_field::fix_fields, here we first search the SELECT and GROUP BY
+ clauses, and then we search the FROM clause.
+
+ @param[in] thd current thread
+ @param[in,out] reference view column if this item was resolved to a
+ view column
+
+ @todo
+ Here we could first find the field anyway, and then test this
+ condition, so that we can give a better error message -
+ ER_WRONG_FIELD_WITH_GROUP, instead of the less informative
+ ER_BAD_FIELD_ERROR which we produce now.
+
+ @retval
+ TRUE if error
+ @retval
+ FALSE on success
+*/
+
+bool Item_ref::fix_fields(THD *thd, Item **reference)
+{
+ enum_parsing_place place= NO_MATTER;
+ DBUG_ASSERT(fixed == 0);
+ SELECT_LEX *current_sel= thd->lex->current_select;
+
+ if (!ref || ref == not_found_item)
+ {
+ DBUG_ASSERT(reference_trough_name != 0);
+ if (!(ref= resolve_ref_in_select_and_group(thd, this,
+ context->select_lex)))
+ goto error; /* Some error occurred (e.g. ambiguous names). */
+
+ if (ref == not_found_item) /* This reference was not resolved. */
+ {
+ Name_resolution_context *last_checked_context= context;
+ Name_resolution_context *outer_context= context->outer_context;
+ Field *from_field;
+ ref= 0;
+
+ if (!outer_context)
+ {
+ /* The current reference cannot be resolved in this query. */
+ my_error(ER_BAD_FIELD_ERROR,MYF(0),
+ this->full_name(), current_thd->where);
+ goto error;
+ }
+
+ /*
+ If there is an outer context (select), and it is not a derived table
+ (which do not support the use of outer fields for now), try to
+ resolve this reference in the outer select(s).
+
+ We treat each subselect as a separate namespace, so that different
+ subselects may contain columns with the same names. The subselects are
+ searched starting from the innermost.
+ */
+ from_field= (Field*) not_found_field;
+
+ do
+ {
+ SELECT_LEX *select= outer_context->select_lex;
+ Item_subselect *prev_subselect_item=
+ last_checked_context->select_lex->master_unit()->item;
+ last_checked_context= outer_context;
+
+ /* Search in the SELECT and GROUP lists of the outer select. */
+ if (outer_context->resolve_in_select_list)
+ {
+ if (!(ref= resolve_ref_in_select_and_group(thd, this, select)))
+ goto error; /* Some error occurred (e.g. ambiguous names). */
+ if (ref != not_found_item)
+ {
+ DBUG_ASSERT(*ref && (*ref)->fixed);
+ prev_subselect_item->used_tables_cache|= (*ref)->used_tables();
+ prev_subselect_item->const_item_cache&= (*ref)->const_item();
+ break;
+ }
+ /*
+ Set ref to 0 to ensure that we get an error in case we replaced
+ this item with another item and still use this item in some
+ other place of the parse tree.
+ */
+ ref= 0;
+ }
+
+ place= prev_subselect_item->parsing_place;
+ /*
+ Check table fields only if the subquery is used somewhere out of
+ HAVING or the outer SELECT does not use grouping (i.e. tables are
+ accessible).
+ TODO:
+ Here we could first find the field anyway, and then test this
+ condition, so that we can give a better error message -
+ ER_WRONG_FIELD_WITH_GROUP, instead of the less informative
+ ER_BAD_FIELD_ERROR which we produce now.
+ */
+ if ((place != IN_HAVING ||
+ (!select->with_sum_func &&
+ select->group_list.elements == 0)))
+ {
+ /*
+ In case of view, find_field_in_tables() write pointer to view
+ field expression to 'reference', i.e. it substitute that
+ expression instead of this Item_ref
+ */
+ from_field= find_field_in_tables(thd, this,
+ outer_context->
+ first_name_resolution_table,
+ outer_context->
+ last_name_resolution_table,
+ reference,
+ IGNORE_EXCEPT_NON_UNIQUE,
+ TRUE, TRUE);
+ if (! from_field)
+ goto error;
+ if (from_field == view_ref_found)
+ {
+ Item::Type refer_type= (*reference)->type();
+ prev_subselect_item->used_tables_cache|=
+ (*reference)->used_tables();
+ prev_subselect_item->const_item_cache&=
+ (*reference)->const_item();
+ DBUG_ASSERT((*reference)->type() == REF_ITEM);
+ mark_as_dependent(thd, last_checked_context->select_lex,
+ context->select_lex, this,
+ ((refer_type == REF_ITEM ||
+ refer_type == FIELD_ITEM) ?
+ (Item_ident*) (*reference) :
+ 0));
+ /*
+ view reference found, we substituted it instead of this
+ Item, so can quit
+ */
+ return FALSE;
+ }
+ if (from_field != not_found_field)
+ {
+ if (cached_table && cached_table->select_lex &&
+ outer_context->select_lex &&
+ cached_table->select_lex != outer_context->select_lex)
+ {
+ /*
+ Due to cache, find_field_in_tables() can return field which
+ doesn't belong to provided outer_context. In this case we have
+ to find proper field context in order to fix field correcly.
+ */
+ do
+ {
+ outer_context= outer_context->outer_context;
+ select= outer_context->select_lex;
+ prev_subselect_item=
+ last_checked_context->select_lex->master_unit()->item;
+ last_checked_context= outer_context;
+ } while (outer_context && outer_context->select_lex &&
+ cached_table->select_lex != outer_context->select_lex);
+ }
+ prev_subselect_item->used_tables_cache|= from_field->table->map;
+ prev_subselect_item->const_item_cache= 0;
+ break;
+ }
+ }
+ DBUG_ASSERT(from_field == not_found_field);
+
+ /* Reference is not found => depend on outer (or just error). */
+ prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT;
+ prev_subselect_item->const_item_cache= 0;
+
+ outer_context= outer_context->outer_context;
+ } while (outer_context);
+
+ DBUG_ASSERT(from_field != 0 && from_field != view_ref_found);
+ if (from_field != not_found_field)
+ {
+ Item_field* fld;
+ if (!(fld= new Item_field(from_field)))
+ goto error;
+ thd->change_item_tree(reference, fld);
+ mark_as_dependent(thd, last_checked_context->select_lex,
+ thd->lex->current_select, fld, fld);
+ /*
+ A reference is resolved to a nest level that's outer or the same as
+ the nest level of the enclosing set function : adjust the value of
+ max_arg_level for the function if it's needed.
+ */
+ if (thd->lex->in_sum_func &&
+ thd->lex->in_sum_func->nest_level >=
+ last_checked_context->select_lex->nest_level)
+ set_if_bigger(thd->lex->in_sum_func->max_arg_level,
+ last_checked_context->select_lex->nest_level);
+ return FALSE;
+ }
+ if (ref == 0)
+ {
+ /* The item was not a table field and not a reference */
+ my_error(ER_BAD_FIELD_ERROR, MYF(0),
+ this->full_name(), current_thd->where);
+ goto error;
+ }
+ /* Should be checked in resolve_ref_in_select_and_group(). */
+ DBUG_ASSERT(*ref && (*ref)->fixed);
+ mark_as_dependent(thd, last_checked_context->select_lex,
+ context->select_lex, this, this);
+ /*
+ A reference is resolved to a nest level that's outer or the same as
+ the nest level of the enclosing set function : adjust the value of
+ max_arg_level for the function if it's needed.
+ */
+ if (thd->lex->in_sum_func &&
+ thd->lex->in_sum_func->nest_level >=
+ last_checked_context->select_lex->nest_level)
+ set_if_bigger(thd->lex->in_sum_func->max_arg_level,
+ last_checked_context->select_lex->nest_level);
+ }
+ }
+ else if (ref_type() != VIEW_REF)
+ {
+ /*
+ It could be that we're referring to something that's in ancestor selects.
+ We must make an appropriate mark_as_dependent() call for each such
+ outside reference.
+ */
+ Dependency_marker dep_marker;
+ dep_marker.current_select= current_sel;
+ dep_marker.thd= thd;
+ (*ref)->walk(&Item::enumerate_field_refs_processor, FALSE,
+ (uchar*)&dep_marker);
+ }
+
+ DBUG_ASSERT(*ref);
+ /*
+ Check if this is an incorrect reference in a group function or forward
+ reference. Do not issue an error if this is:
+ 1. outer reference (will be fixed later by the fix_inner_refs function);
+ 2. an unnamed reference inside an aggregate function.
+ */
+ if (!((*ref)->type() == REF_ITEM &&
+ ((Item_ref *)(*ref))->ref_type() == OUTER_REF) &&
+ (((*ref)->with_sum_func && name &&
+ !(current_sel->linkage != GLOBAL_OPTIONS_TYPE &&
+ current_sel->having_fix_field)) ||
+ !(*ref)->fixed))
+ {
+ my_error(ER_ILLEGAL_REFERENCE, MYF(0),
+ name, ((*ref)->with_sum_func?
+ "reference to group function":
+ "forward reference in item list"));
+ goto error;
+ }
+
+ set_properties();
+
+ if ((*ref)->check_cols(1))
+ goto error;
+ return FALSE;
+
+error:
+ context->process_error(thd);
+ return TRUE;
+}
+
+
+void Item_ref::set_properties()
+{
+ max_length= (*ref)->max_length;
+ maybe_null= (*ref)->maybe_null;
+ decimals= (*ref)->decimals;
+ collation.set((*ref)->collation);
+ /*
+ We have to remember if we refer to a sum function, to ensure that
+ split_sum_func() doesn't try to change the reference.
+ */
+ with_sum_func= (*ref)->with_sum_func;
+ with_field= (*ref)->with_field;
+ unsigned_flag= (*ref)->unsigned_flag;
+ fixed= 1;
+ if (alias_name_used)
+ return;
+ if ((*ref)->type() == FIELD_ITEM)
+ alias_name_used= ((Item_ident *) (*ref))->alias_name_used;
+ else
+ alias_name_used= TRUE; // it is not field, so it is was resolved by alias
+}
+
+
+void Item_ref::cleanup()
+{
+ DBUG_ENTER("Item_ref::cleanup");
+ Item_ident::cleanup();
+ result_field= 0;
+ if (reference_trough_name)
+ {
+ /* We have to reset the reference as it may been freed */
+ ref= 0;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Transform an Item_ref object with a transformer callback function.
+
+ The function first applies the transform method to the item
+ referenced by this Item_reg object. If this returns a new item the
+ old item is substituted for a new one. After this the transformer
+ is applied to the Item_ref object.
+
+ @param transformer the transformer callback function to be applied to
+ the nodes of the tree of the object
+ @param argument parameter to be passed to the transformer
+
+ @return Item returned as the result of transformation of the Item_ref object
+ @retval !NULL The transformation was successful
+ @retval NULL Out of memory error
+*/
+
+Item* Item_ref::transform(Item_transformer transformer, uchar *arg)
+{
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+ DBUG_ASSERT((*ref) != NULL);
+
+ /* Transform the object we are referencing. */
+ Item *new_item= (*ref)->transform(transformer, arg);
+ if (!new_item)
+ return NULL;
+
+ /*
+ THD::change_item_tree() should be called only if the tree was
+ really transformed, i.e. when a new item has been created.
+ Otherwise we'll be allocating a lot of unnecessary memory for
+ change records at each execution.
+ */
+ if (*ref != new_item)
+ current_thd->change_item_tree(ref, new_item);
+
+ /* Transform the item ref object. */
+ return (this->*transformer)(arg);
+}
+
+
+/**
+ Compile an Item_ref object with a processor and a transformer
+ callback functions.
+
+ First the function applies the analyzer to the Item_ref object. Then
+ if the analizer succeeeds we first applies the compile method to the
+ object the Item_ref object is referencing. If this returns a new
+ item the old item is substituted for a new one. After this the
+ transformer is applied to the Item_ref object itself.
+ The compile function is not called if the analyzer returns NULL
+ in the parameter arg_p.
+
+ @param analyzer the analyzer callback function to be applied to the
+ nodes of the tree of the object
+ @param[in,out] arg_p parameter to be passed to the processor
+ @param transformer the transformer callback function to be applied to the
+ nodes of the tree of the object
+ @param arg_t parameter to be passed to the transformer
+
+ @return Item returned as the result of transformation of the Item_ref object
+*/
+
+Item* Item_ref::compile(Item_analyzer analyzer, uchar **arg_p,
+ Item_transformer transformer, uchar *arg_t)
+{
+ /* Analyze this Item object. */
+ if (!(this->*analyzer)(arg_p))
+ return NULL;
+
+ /* Compile the Item we are referencing. */
+ DBUG_ASSERT((*ref) != NULL);
+ if (*arg_p)
+ {
+ uchar *arg_v= *arg_p;
+ Item *new_item= (*ref)->compile(analyzer, &arg_v, transformer, arg_t);
+ if (new_item && *ref != new_item)
+ current_thd->change_item_tree(ref, new_item);
+ }
+
+ /* Transform this Item object. */
+ return (this->*transformer)(arg_t);
+}
+
+
+void Item_ref::print(String *str, enum_query_type query_type)
+{
+ if (ref)
+ {
+ if ((*ref)->type() != Item::CACHE_ITEM && ref_type() != VIEW_REF &&
+ !table_name && name && alias_name_used)
+ {
+ THD *thd= current_thd;
+ append_identifier(thd, str, (*ref)->real_item()->name,
+ strlen((*ref)->real_item()->name));
+ }
+ else
+ (*ref)->print(str, query_type);
+ }
+ else
+ Item_ident::print(str, query_type);
+}
+
+
+bool Item_ref::send(Protocol *prot, String *tmp)
+{
+ if (result_field)
+ return prot->store(result_field);
+ return (*ref)->send(prot, tmp);
+}
+
+
+double Item_ref::val_result()
+{
+ if (result_field)
+ {
+ if ((null_value= result_field->is_null()))
+ return 0.0;
+ return result_field->val_real();
+ }
+ return val_real();
+}
+
+
+bool Item_ref::is_null_result()
+{
+ if (result_field)
+ return (null_value=result_field->is_null());
+
+ return is_null();
+}
+
+
+longlong Item_ref::val_int_result()
+{
+ if (result_field)
+ {
+ if ((null_value= result_field->is_null()))
+ return 0;
+ return result_field->val_int();
+ }
+ return val_int();
+}
+
+
+String *Item_ref::str_result(String* str)
+{
+ if (result_field)
+ {
+ if ((null_value= result_field->is_null()))
+ return 0;
+ str->set_charset(str_value.charset());
+ return result_field->val_str(str, &str_value);
+ }
+ return val_str(str);
+}
+
+
+my_decimal *Item_ref::val_decimal_result(my_decimal *decimal_value)
+{
+ if (result_field)
+ {
+ if ((null_value= result_field->is_null()))
+ return 0;
+ return result_field->val_decimal(decimal_value);
+ }
+ return val_decimal(decimal_value);
+}
+
+
+bool Item_ref::val_bool_result()
+{
+ if (result_field)
+ {
+ if ((null_value= result_field->is_null()))
+ return 0;
+ switch (result_field->result_type()) {
+ case INT_RESULT:
+ return result_field->val_int() != 0;
+ case DECIMAL_RESULT:
+ {
+ my_decimal decimal_value;
+ my_decimal *val= result_field->val_decimal(&decimal_value);
+ if (val)
+ return !my_decimal_is_zero(val);
+ return 0;
+ }
+ case REAL_RESULT:
+ case STRING_RESULT:
+ return result_field->val_real() != 0.0;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ }
+ return val_bool();
+}
+
+
+void Item_ref::save_result(Field *to)
+{
+ if (result_field)
+ {
+ save_field_in_field(result_field, &null_value, to, TRUE);
+ return;
+ }
+ (*ref)->save_result(to);
+ null_value= (*ref)->null_value;
+}
+
+
+void Item_ref::save_val(Field *to)
+{
+ (*ref)->save_result(to);
+ null_value= (*ref)->null_value;
+}
+
+
+double Item_ref::val_real()
+{
+ DBUG_ASSERT(fixed);
+ double tmp=(*ref)->val_result();
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+longlong Item_ref::val_int()
+{
+ DBUG_ASSERT(fixed);
+ longlong tmp=(*ref)->val_int_result();
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+bool Item_ref::val_bool()
+{
+ DBUG_ASSERT(fixed);
+ bool tmp= (*ref)->val_bool_result();
+ null_value= (*ref)->null_value;
+ return tmp;
+}
+
+
+String *Item_ref::val_str(String* tmp)
+{
+ DBUG_ASSERT(fixed);
+ tmp=(*ref)->str_result(tmp);
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+bool Item_ref::is_null()
+{
+ DBUG_ASSERT(fixed);
+ bool tmp=(*ref)->is_null_result();
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+bool Item_ref::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ return (null_value=(*ref)->get_date_result(ltime,fuzzydate));
+}
+
+
+my_decimal *Item_ref::val_decimal(my_decimal *decimal_value)
+{
+ my_decimal *val= (*ref)->val_decimal_result(decimal_value);
+ null_value= (*ref)->null_value;
+ return val;
+}
+
+int Item_ref::save_in_field(Field *to, bool no_conversions)
+{
+ int res;
+ if (result_field)
+ {
+ if (result_field->is_null())
+ {
+ null_value= 1;
+ res= set_field_to_null_with_conversions(to, no_conversions);
+ return res;
+ }
+ to->set_notnull();
+ res= field_conv(to, result_field);
+ null_value= 0;
+ return res;
+ }
+ res= (*ref)->save_in_field(to, no_conversions);
+ null_value= (*ref)->null_value;
+ return res;
+}
+
+
+void Item_ref::save_org_in_field(Field *field, fast_field_copier optimizer_data)
+{
+ (*ref)->save_org_in_field(field, optimizer_data);
+}
+
+
+void Item_ref::make_field(Send_field *field)
+{
+ (*ref)->make_field(field);
+ /* Non-zero in case of a view */
+ if (name)
+ field->col_name= name;
+ if (table_name)
+ field->table_name= table_name;
+ if (db_name)
+ field->db_name= db_name;
+ if (orig_field_name)
+ field->org_col_name= orig_field_name;
+ if (orig_table_name)
+ field->org_table_name= orig_table_name;
+}
+
+
+Item *Item_ref::get_tmp_table_item(THD *thd)
+{
+ if (!result_field)
+ return (*ref)->get_tmp_table_item(thd);
+
+ Item_field *item= new Item_field(result_field);
+ if (item)
+ {
+ item->table_name= table_name;
+ item->db_name= db_name;
+ }
+ return item;
+}
+
+
+void Item_ref_null_helper::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("<ref_null_helper>("));
+ if (ref)
+ (*ref)->print(str, query_type);
+ else
+ str->append('?');
+ str->append(')');
+}
+
+
+void Item_direct_ref::save_val(Field *to)
+{
+ (*ref)->save_val(to);
+ null_value=(*ref)->null_value;
+}
+
+
+double Item_direct_ref::val_real()
+{
+ double tmp=(*ref)->val_real();
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+longlong Item_direct_ref::val_int()
+{
+ longlong tmp=(*ref)->val_int();
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+String *Item_direct_ref::val_str(String* tmp)
+{
+ tmp=(*ref)->val_str(tmp);
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+my_decimal *Item_direct_ref::val_decimal(my_decimal *decimal_value)
+{
+ my_decimal *tmp= (*ref)->val_decimal(decimal_value);
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+bool Item_direct_ref::val_bool()
+{
+ bool tmp= (*ref)->val_bool();
+ null_value=(*ref)->null_value;
+ return tmp;
+}
+
+
+bool Item_direct_ref::is_null()
+{
+ return (*ref)->is_null();
+}
+
+
+bool Item_direct_ref::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ return (null_value=(*ref)->get_date(ltime,fuzzydate));
+}
+
+
+Item_cache_wrapper::~Item_cache_wrapper()
+{
+ DBUG_ASSERT(expr_cache == 0);
+}
+
+Item_cache_wrapper::Item_cache_wrapper(Item *item_arg)
+:orig_item(item_arg), expr_cache(NULL), expr_value(NULL)
+{
+ DBUG_ASSERT(orig_item->fixed);
+ max_length= orig_item->max_length;
+ maybe_null= orig_item->maybe_null;
+ decimals= orig_item->decimals;
+ collation.set(orig_item->collation);
+ with_sum_func= orig_item->with_sum_func;
+ with_field= orig_item->with_field;
+ unsigned_flag= orig_item->unsigned_flag;
+ name= item_arg->name;
+ name_length= item_arg->name_length;
+ with_subselect= orig_item->with_subselect;
+
+ if ((expr_value= Item_cache::get_cache(orig_item)))
+ expr_value->setup(orig_item);
+
+ fixed= 1;
+}
+
+
+/**
+ Initialize the cache if it is needed
+*/
+
+void Item_cache_wrapper::init_on_demand()
+{
+ if (!expr_cache->is_inited())
+ {
+ orig_item->get_cache_parameters(parameters);
+ expr_cache->init();
+ }
+}
+
+
+void Item_cache_wrapper::print(String *str, enum_query_type query_type)
+{
+ str->append(func_name());
+ if (expr_cache)
+ {
+ init_on_demand();
+ expr_cache->print(str, query_type);
+ }
+ else
+ str->append(STRING_WITH_LEN("<<DISABLED>>"));
+ str->append('(');
+ orig_item->print(str, query_type);
+ str->append(')');
+}
+
+
+/**
+ Prepare the expression cache wrapper (do nothing)
+
+ @retval FALSE OK
+*/
+
+bool Item_cache_wrapper::fix_fields(THD *thd __attribute__((unused)),
+ Item **it __attribute__((unused)))
+{
+ DBUG_ASSERT(orig_item->fixed);
+ DBUG_ASSERT(fixed);
+ return FALSE;
+}
+
+bool Item_cache_wrapper::send(Protocol *protocol, String *buffer)
+{
+ if (result_field)
+ return protocol->store(result_field);
+ return Item::send(protocol, buffer);
+}
+
+/**
+ Clean the expression cache wrapper up before reusing it.
+*/
+
+void Item_cache_wrapper::cleanup()
+{
+ DBUG_ENTER("Item_cache_wrapper::cleanup");
+ Item_result_field::cleanup();
+ delete expr_cache;
+ expr_cache= 0;
+ /* expr_value is Item so it will be destroyed from list of Items */
+ expr_value= 0;
+ parameters.empty();
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Create an expression cache that uses a temporary table
+
+ @param thd Thread handle
+ @param depends_on Parameters of the expression to create cache for
+
+ @details
+ The function takes 'depends_on' as the list of all parameters for
+ the expression wrapped into this object and creates an expression
+ cache in a temporary table containing the field for the parameters
+ and the result of the expression.
+
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool Item_cache_wrapper::set_cache(THD *thd)
+{
+ DBUG_ENTER("Item_cache_wrapper::set_cache");
+ DBUG_ASSERT(expr_cache == 0);
+ expr_cache= new Expression_cache_tmptable(thd, parameters, expr_value);
+ DBUG_RETURN(expr_cache == NULL);
+}
+
+
+/**
+ Check if the current values of the parameters are in the expression cache
+
+ @details
+ The function checks whether the current set of the parameters of the
+ referenced item can be found in the expression cache. If so the function
+ returns the item by which the result of the expression can be easily
+ extracted from the cache with the corresponding val_* method.
+
+ @retval NULL - parameters are not in the cache
+ @retval <item*> - item providing the result of the expression found in cache
+*/
+
+Item *Item_cache_wrapper::check_cache()
+{
+ DBUG_ENTER("Item_cache_wrapper::check_cache");
+ if (expr_cache)
+ {
+ Expression_cache_tmptable::result res;
+ Item *cached_value;
+ init_on_demand();
+ res= expr_cache->check_value(&cached_value);
+ if (res == Expression_cache_tmptable::HIT)
+ DBUG_RETURN(cached_value);
+ }
+ DBUG_RETURN(NULL);
+}
+
+
+/**
+ Get the value of the cached expression and put it in the cache
+*/
+
+inline void Item_cache_wrapper::cache()
+{
+ expr_value->store(orig_item);
+ expr_value->cache_value();
+ expr_cache->put_value(expr_value); // put in expr_cache
+}
+
+
+/**
+ Get the value of the possibly cached item into the field.
+*/
+
+void Item_cache_wrapper::save_val(Field *to)
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::val_int");
+ if (!expr_cache)
+ {
+ orig_item->save_val(to);
+ null_value= orig_item->null_value;
+ DBUG_VOID_RETURN;
+ }
+
+ if ((cached_value= check_cache()))
+ {
+ cached_value->save_val(to);
+ null_value= cached_value->null_value;
+ DBUG_VOID_RETURN;
+ }
+ cache();
+ null_value= expr_value->null_value;
+ expr_value->save_val(to);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get the integer value of the possibly cached item.
+*/
+
+longlong Item_cache_wrapper::val_int()
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::val_int");
+ if (!expr_cache)
+ {
+ longlong tmp= orig_item->val_int();
+ null_value= orig_item->null_value;
+ DBUG_RETURN(tmp);
+ }
+
+ if ((cached_value= check_cache()))
+ {
+ longlong tmp= cached_value->val_int();
+ null_value= cached_value->null_value;
+ DBUG_RETURN(tmp);
+ }
+ cache();
+ null_value= expr_value->null_value;
+ DBUG_RETURN(expr_value->val_int());
+}
+
+
+/**
+ Get the real value of the possibly cached item
+*/
+
+double Item_cache_wrapper::val_real()
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::val_real");
+ if (!expr_cache)
+ {
+ double tmp= orig_item->val_real();
+ null_value= orig_item->null_value;
+ DBUG_RETURN(tmp);
+ }
+
+ if ((cached_value= check_cache()))
+ {
+ double tmp= cached_value->val_real();
+ null_value= cached_value->null_value;
+ DBUG_RETURN(tmp);
+ }
+ cache();
+ null_value= expr_value->null_value;
+ DBUG_RETURN(expr_value->val_real());
+}
+
+
+/**
+ Get the string value of the possibly cached item
+*/
+
+String *Item_cache_wrapper::val_str(String* str)
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::val_str");
+ if (!expr_cache)
+ {
+ String *tmp= orig_item->val_str(str);
+ null_value= orig_item->null_value;
+ DBUG_RETURN(tmp);
+ }
+
+ if ((cached_value= check_cache()))
+ {
+ String *tmp= cached_value->val_str(str);
+ null_value= cached_value->null_value;
+ DBUG_RETURN(tmp);
+ }
+ cache();
+ if ((null_value= expr_value->null_value))
+ DBUG_RETURN(NULL);
+ DBUG_RETURN(expr_value->val_str(str));
+}
+
+
+/**
+ Get the decimal value of the possibly cached item
+*/
+
+my_decimal *Item_cache_wrapper::val_decimal(my_decimal* decimal_value)
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::val_decimal");
+ if (!expr_cache)
+ {
+ my_decimal *tmp= orig_item->val_decimal(decimal_value);
+ null_value= orig_item->null_value;
+ DBUG_RETURN(tmp);
+ }
+
+ if ((cached_value= check_cache()))
+ {
+ my_decimal *tmp= cached_value->val_decimal(decimal_value);
+ null_value= cached_value->null_value;
+ DBUG_RETURN(tmp);
+ }
+ cache();
+ if ((null_value= expr_value->null_value))
+ DBUG_RETURN(NULL);
+ DBUG_RETURN(expr_value->val_decimal(decimal_value));
+}
+
+
+/**
+ Get the boolean value of the possibly cached item
+*/
+
+bool Item_cache_wrapper::val_bool()
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::val_bool");
+ if (!expr_cache)
+ {
+ bool tmp= orig_item->val_bool();
+ null_value= orig_item->null_value;
+ DBUG_RETURN(tmp);
+ }
+
+ if ((cached_value= check_cache()))
+ {
+ bool tmp= cached_value->val_bool();
+ null_value= cached_value->null_value;
+ DBUG_RETURN(tmp);
+ }
+ cache();
+ null_value= expr_value->null_value;
+ DBUG_RETURN(expr_value->val_bool());
+}
+
+
+/**
+ Check for NULL the value of the possibly cached item
+*/
+
+bool Item_cache_wrapper::is_null()
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::is_null");
+ if (!expr_cache)
+ {
+ bool tmp= orig_item->is_null();
+ null_value= orig_item->null_value;
+ DBUG_RETURN(tmp);
+ }
+
+ if ((cached_value= check_cache()))
+ {
+ bool tmp= cached_value->is_null();
+ null_value= cached_value->null_value;
+ DBUG_RETURN(tmp);
+ }
+ cache();
+ DBUG_RETURN((null_value= expr_value->null_value));
+}
+
+
+/**
+ Get the date value of the possibly cached item
+*/
+
+bool Item_cache_wrapper::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ Item *cached_value;
+ DBUG_ENTER("Item_cache_wrapper::get_date");
+ if (!expr_cache)
+ DBUG_RETURN((null_value= orig_item->get_date(ltime, fuzzydate)));
+
+ if ((cached_value= check_cache()))
+ DBUG_RETURN((null_value= cached_value->get_date(ltime, fuzzydate)));
+
+ cache();
+ DBUG_RETURN((null_value= expr_value->get_date(ltime, fuzzydate)));
+}
+
+
+int Item_cache_wrapper::save_in_field(Field *to, bool no_conversions)
+{
+ int res;
+ DBUG_ASSERT(!result_field);
+ res= orig_item->save_in_field(to, no_conversions);
+ null_value= orig_item->null_value;
+ return res;
+}
+
+
+Item* Item_cache_wrapper::get_tmp_table_item(THD *thd_arg)
+{
+ if (!orig_item->with_sum_func && !orig_item->const_item())
+ return new Item_field(result_field);
+ return copy_or_same(thd_arg);
+}
+
+
+bool Item_direct_view_ref::send(Protocol *protocol, String *buffer)
+{
+ if (check_null_ref())
+ return protocol->store_null();
+ return Item_direct_ref::send(protocol, buffer);
+}
+
+/**
+ Prepare referenced field then call usual Item_direct_ref::fix_fields .
+
+ @param thd thread handler
+ @param reference reference on reference where this item stored
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+bool Item_direct_view_ref::fix_fields(THD *thd, Item **reference)
+{
+ DBUG_ASSERT(1);
+ /* view fild reference must be defined */
+ DBUG_ASSERT(*ref);
+ /* (*ref)->check_cols() will be made in Item_direct_ref::fix_fields */
+ if ((*ref)->fixed)
+ {
+ Item *ref_item= (*ref)->real_item();
+ if (ref_item->type() == Item::FIELD_ITEM)
+ {
+ /*
+ In some cases we need to update table read set(see bug#47150).
+ If ref item is FIELD_ITEM and fixed then field and table
+ have proper values. So we can use them for update.
+ */
+ Field *fld= ((Item_field*) ref_item)->field;
+ DBUG_ASSERT(fld && fld->table);
+ if (thd->mark_used_columns == MARK_COLUMNS_READ)
+ bitmap_set_bit(fld->table->read_set, fld->field_index);
+ }
+ }
+ else if (!(*ref)->fixed &&
+ ((*ref)->fix_fields(thd, ref)))
+ return TRUE;
+
+ if (Item_direct_ref::fix_fields(thd, reference))
+ return TRUE;
+ if (view->table && view->table->maybe_null)
+ maybe_null= TRUE;
+ set_null_ref_table();
+ return FALSE;
+}
+
+/*
+ Prepare referenced outer field then call usual Item_direct_ref::fix_fields
+
+ SYNOPSIS
+ Item_outer_ref::fix_fields()
+ thd thread handler
+ reference reference on reference where this item stored
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+bool Item_outer_ref::fix_fields(THD *thd, Item **reference)
+{
+ bool err;
+ /* outer_ref->check_cols() will be made in Item_direct_ref::fix_fields */
+ if ((*ref) && !(*ref)->fixed && ((*ref)->fix_fields(thd, reference)))
+ return TRUE;
+ err= Item_direct_ref::fix_fields(thd, reference);
+ if (!outer_ref)
+ outer_ref= *ref;
+ if ((*ref)->type() == Item::FIELD_ITEM)
+ table_name= ((Item_field*)outer_ref)->table_name;
+ return err;
+}
+
+
+void Item_outer_ref::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ if (get_depended_from() == new_parent)
+ {
+ *ref= outer_ref;
+ (*ref)->fix_after_pullout(new_parent, ref);
+ }
+}
+
+void Item_ref::fix_after_pullout(st_select_lex *new_parent, Item **refptr)
+{
+ (*ref)->fix_after_pullout(new_parent, ref);
+ if (get_depended_from() == new_parent)
+ depended_from= NULL;
+}
+
+
+/**
+ Mark references from inner selects used in group by clause
+
+ The method is used by the walk method when called for the expressions
+ from the group by clause. The callsare occurred in the function
+ fix_inner_refs invoked by JOIN::prepare.
+ The parameter passed to Item_outer_ref::check_inner_refs_processor
+ is the iterator over the list of inner references from the subselects
+ of the select to be prepared. The function marks those references
+ from this list whose occurrences are encountered in the group by
+ expressions passed to the walk method.
+
+ @param arg pointer to the iterator over a list of inner references
+
+ @return
+ FALSE always
+*/
+
+bool Item_outer_ref::check_inner_refs_processor(uchar *arg)
+{
+ List_iterator_fast<Item_outer_ref> *it=
+ ((List_iterator_fast<Item_outer_ref> *) arg);
+ Item_outer_ref *ref;
+ while ((ref= (*it)++))
+ {
+ if (ref == this)
+ {
+ ref->found_in_group_by= 1;
+ break;
+ }
+ }
+ (*it).rewind();
+ return FALSE;
+}
+
+
+/**
+ Compare two view column references for equality.
+
+ A view column reference is considered equal to another column
+ reference if the second one is a view column and if both column
+ references resolve to the same item. It is assumed that both
+ items are of the same type.
+
+ @param item item to compare with
+ @param binary_cmp make binary comparison
+
+ @retval
+ TRUE Referenced item is equal to given item
+ @retval
+ FALSE otherwise
+*/
+
+bool Item_direct_view_ref::eq(const Item *item, bool binary_cmp) const
+{
+ if (item->type() == REF_ITEM)
+ {
+ Item_ref *item_ref= (Item_ref*) item;
+ if (item_ref->ref_type() == VIEW_REF)
+ {
+ Item *item_ref_ref= *(item_ref->ref);
+ return ((*ref)->real_item() == item_ref_ref->real_item());
+ }
+ }
+ return FALSE;
+}
+
+
+Item_equal *Item_direct_view_ref::find_item_equal(COND_EQUAL *cond_equal)
+{
+ Item* field_item= real_item();
+ if (field_item->type() != FIELD_ITEM)
+ return NULL;
+ return ((Item_field *) field_item)->find_item_equal(cond_equal);
+}
+
+
+/**
+ Check whether a reference to field item can be substituted for an equal item
+
+ @details
+ The function checks whether a substitution of a reference to field item for
+ an equal item is valid.
+
+ @param arg *arg != NULL <-> the reference is in the context
+ where substitution for an equal item is valid
+
+ @note
+ See also the note for Item_field::subst_argument_checker
+
+ @retval
+ TRUE substitution is valid
+ @retval
+ FALSE otherwise
+*/
+bool Item_direct_view_ref::subst_argument_checker(uchar **arg)
+{
+ bool res= FALSE;
+ if (*arg)
+ {
+ Item *item= real_item();
+ if (item->type() == FIELD_ITEM &&
+ (*arg == (uchar *) Item::ANY_SUBST ||
+ result_type() != STRING_RESULT ||
+ (((Item_field *) item)->field->flags & BINARY_FLAG)))
+ res= TRUE;
+ }
+ /* Block any substitution into the wrapped object */
+ if (*arg)
+ *arg= NULL;
+ return res;
+}
+
+
+/**
+ Set a pointer to the multiple equality the view field reference belongs to
+ (if any).
+
+ @details
+ The function looks for a multiple equality containing this item of the type
+ Item_direct_view_ref among those referenced by arg.
+ In the case such equality exists the function does the following.
+ If the found multiple equality contains a constant, then the item
+ is substituted for this constant, otherwise the function sets a pointer
+ to the multiple equality in the item.
+
+ @param arg reference to list of multiple equalities where
+ the item (this object) is to be looked for
+
+ @note
+ This function is supposed to be called as a callback parameter in calls
+ of the compile method.
+
+ @note
+ The function calls Item_field::equal_fields_propagator for the field item
+ this->real_item() to do the job. Then it takes the pointer to equal_item
+ from this field item and assigns it to this->item_equal.
+
+ @return
+ - pointer to the replacing constant item, if the field item was substituted
+ - pointer to the field item, otherwise.
+*/
+
+Item *Item_direct_view_ref::equal_fields_propagator(uchar *arg)
+{
+ Item *field_item= real_item();
+ if (field_item->type() != FIELD_ITEM)
+ return this;
+ Item *item= field_item->equal_fields_propagator(arg);
+ set_item_equal(field_item->get_item_equal());
+ field_item->set_item_equal(NULL);
+ if (item != field_item)
+ return item;
+ return this;
+}
+
+
+/**
+ Replace an Item_direct_view_ref for an equal Item_field evaluated earlier
+ (if any).
+
+ @details
+ If this->item_equal points to some item and coincides with arg then
+ the function returns a pointer to a field item that is referred to by the
+ first element of the item_equal list which the Item_direct_view_ref
+ object belongs to unless item_equal contains a constant item. In this
+ case the function returns this constant item (if the substitution does
+ not require conversion).
+ If the Item_direct_view_item object does not refer any Item_equal object
+ 'this' is returned .
+
+ @param arg NULL or points to so some item of the Item_equal type
+
+ @note
+ This function is supposed to be called as a callback parameter in calls
+ of the transformer method.
+
+ @note
+ The function calls Item_field::replace_equal_field for the field item
+ this->real_item() to do the job.
+
+ @return
+ - pointer to a replacement Item_field if there is a better equal item or
+ a pointer to a constant equal item;
+ - this - otherwise.
+*/
+
+Item *Item_direct_view_ref::replace_equal_field(uchar *arg)
+{
+ Item *field_item= real_item();
+ if (field_item->type() != FIELD_ITEM)
+ return this;
+ field_item->set_item_equal(item_equal);
+ Item *item= field_item->replace_equal_field(arg);
+ field_item->set_item_equal(0);
+ return item != field_item ? item : this;
+}
+
+
+bool Item_default_value::eq(const Item *item, bool binary_cmp) const
+{
+ return item->type() == DEFAULT_VALUE_ITEM &&
+ ((Item_default_value *)item)->arg->eq(arg, binary_cmp);
+}
+
+
+bool Item_default_value::fix_fields(THD *thd, Item **items)
+{
+ Item *real_arg;
+ Item_field *field_arg;
+ Field *def_field;
+ DBUG_ASSERT(fixed == 0);
+
+ if (!arg)
+ {
+ fixed= 1;
+ return FALSE;
+ }
+ if (!arg->fixed && arg->fix_fields(thd, &arg))
+ goto error;
+
+
+ real_arg= arg->real_item();
+ if (real_arg->type() != FIELD_ITEM)
+ {
+ my_error(ER_NO_DEFAULT_FOR_FIELD, MYF(0), arg->name);
+ goto error;
+ }
+
+ field_arg= (Item_field *)real_arg;
+ if (field_arg->field->flags & NO_DEFAULT_VALUE_FLAG)
+ {
+ my_error(ER_NO_DEFAULT_FOR_FIELD, MYF(0), field_arg->field->field_name);
+ goto error;
+ }
+ if (!(def_field= (Field*) sql_alloc(field_arg->field->size_of())))
+ goto error;
+ memcpy((void *)def_field, (void *)field_arg->field, field_arg->field->size_of());
+ def_field->move_field_offset((my_ptrdiff_t)
+ (def_field->table->s->default_values -
+ def_field->table->record[0]));
+ set_field(def_field);
+ return FALSE;
+
+error:
+ context->process_error(thd);
+ return TRUE;
+}
+
+
+void Item_default_value::print(String *str, enum_query_type query_type)
+{
+ if (!arg)
+ {
+ str->append(STRING_WITH_LEN("default"));
+ return;
+ }
+ str->append(STRING_WITH_LEN("default("));
+ arg->print(str, query_type);
+ str->append(')');
+}
+
+
+int Item_default_value::save_in_field(Field *field_arg, bool no_conversions)
+{
+ if (!arg)
+ {
+ if (field_arg->flags & NO_DEFAULT_VALUE_FLAG &&
+ field_arg->real_type() != MYSQL_TYPE_ENUM)
+ {
+ if (field_arg->reset())
+ {
+ my_message(ER_CANT_CREATE_GEOMETRY_OBJECT,
+ ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0));
+ return -1;
+ }
+
+ if (context->error_processor == &view_error_processor)
+ {
+ TABLE_LIST *view= field_arg->table->pos_in_table_list->top_table();
+ push_warning_printf(field_arg->table->in_use,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_NO_DEFAULT_FOR_VIEW_FIELD,
+ ER(ER_NO_DEFAULT_FOR_VIEW_FIELD),
+ view->view_db.str,
+ view->view_name.str);
+ }
+ else
+ {
+ push_warning_printf(field_arg->table->in_use,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_NO_DEFAULT_FOR_FIELD,
+ ER(ER_NO_DEFAULT_FOR_FIELD),
+ field_arg->field_name);
+ }
+ return 1;
+ }
+ field_arg->set_default();
+ return 0;
+ }
+ return Item_field::save_in_field(field_arg, no_conversions);
+}
+
+
+/**
+ This method like the walk method traverses the item tree, but at the
+ same time it can replace some nodes in the tree.
+*/
+
+Item *Item_default_value::transform(Item_transformer transformer, uchar *args)
+{
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+
+ /*
+ If the value of arg is NULL, then this object represents a constant,
+ so further transformation is unnecessary (and impossible).
+ */
+ if (!arg)
+ return 0;
+
+ Item *new_item= arg->transform(transformer, args);
+ if (!new_item)
+ return 0;
+
+ /*
+ THD::change_item_tree() should be called only if the tree was
+ really transformed, i.e. when a new item has been created.
+ Otherwise we'll be allocating a lot of unnecessary memory for
+ change records at each execution.
+ */
+ if (arg != new_item)
+ current_thd->change_item_tree(&arg, new_item);
+ return (this->*transformer)(args);
+}
+
+
+bool Item_insert_value::eq(const Item *item, bool binary_cmp) const
+{
+ return item->type() == INSERT_VALUE_ITEM &&
+ ((Item_default_value *)item)->arg->eq(arg, binary_cmp);
+}
+
+
+bool Item_insert_value::fix_fields(THD *thd, Item **items)
+{
+ DBUG_ASSERT(fixed == 0);
+ /* We should only check that arg is in first table */
+ if (!arg->fixed)
+ {
+ bool res;
+ TABLE_LIST *orig_next_table= context->last_name_resolution_table;
+ context->last_name_resolution_table= context->first_name_resolution_table;
+ res= arg->fix_fields(thd, &arg);
+ context->last_name_resolution_table= orig_next_table;
+ if (res)
+ return TRUE;
+ }
+
+ if (arg->type() == REF_ITEM)
+ arg= static_cast<Item_ref *>(arg)->ref[0];
+ if (arg->type() != FIELD_ITEM)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), "", "VALUES() function");
+ return TRUE;
+ }
+
+ Item_field *field_arg= (Item_field *)arg;
+
+ if (field_arg->field->table->insert_values)
+ {
+ Field *def_field= (Field*) sql_alloc(field_arg->field->size_of());
+ if (!def_field)
+ return TRUE;
+ memcpy((void *)def_field, (void *)field_arg->field, field_arg->field->size_of());
+ def_field->move_field_offset((my_ptrdiff_t)
+ (def_field->table->insert_values -
+ def_field->table->record[0]));
+ set_field(def_field);
+ }
+ else
+ {
+ Field *tmp_field= field_arg->field;
+ /* charset doesn't matter here, it's to avoid sigsegv only */
+ tmp_field= new Field_null(0, 0, Field::NONE, field_arg->field->field_name,
+ &my_charset_bin);
+ if (tmp_field)
+ {
+ tmp_field->init(field_arg->field->table);
+ set_field(tmp_field);
+ // the index is important when read bits set
+ tmp_field->field_index= field_arg->field->field_index;
+ }
+ }
+ return FALSE;
+}
+
+void Item_insert_value::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("values("));
+ arg->print(str, query_type);
+ str->append(')');
+}
+
+
+/**
+ Find index of Field object which will be appropriate for item
+ representing field of row being changed in trigger.
+
+ @param thd current thread context
+ @param table table of trigger (and where we looking for fields)
+ @param table_grant_info GRANT_INFO of the subject table
+
+ @note
+ This function does almost the same as fix_fields() for Item_field
+ but is invoked right after trigger definition parsing. Since at
+ this stage we can't say exactly what Field object (corresponding
+ to TABLE::record[0] or TABLE::record[1]) should be bound to this
+ Item, we only find out index of the Field and then select concrete
+ Field object in fix_fields() (by that time Table_trigger_list::old_field/
+ new_field should point to proper array of Fields).
+ It also binds Item_trigger_field to Table_triggers_list object for
+ table of trigger which uses this item.
+*/
+
+void Item_trigger_field::setup_field(THD *thd, TABLE *table,
+ GRANT_INFO *table_grant_info)
+{
+ /*
+ It is too early to mark fields used here, because before execution
+ of statement that will invoke trigger other statements may use same
+ TABLE object, so all such mark-up will be wiped out.
+ So instead we do it in Table_triggers_list::mark_fields_used()
+ method which is called during execution of these statements.
+ */
+ enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
+ thd->mark_used_columns= MARK_COLUMNS_NONE;
+ /*
+ Try to find field by its name and if it will be found
+ set field_idx properly.
+ */
+ (void)find_field_in_table(thd, table, field_name, (uint) strlen(field_name),
+ 0, &field_idx);
+ thd->mark_used_columns= save_mark_used_columns;
+ triggers= table->triggers;
+ table_grants= table_grant_info;
+}
+
+
+bool Item_trigger_field::eq(const Item *item, bool binary_cmp) const
+{
+ return item->type() == TRIGGER_FIELD_ITEM &&
+ row_version == ((Item_trigger_field *)item)->row_version &&
+ !my_strcasecmp(system_charset_info, field_name,
+ ((Item_trigger_field *)item)->field_name);
+}
+
+
+void Item_trigger_field::set_required_privilege(bool rw)
+{
+ /*
+ Require SELECT and UPDATE privilege if this field will be read and
+ set, and only UPDATE privilege for setting the field.
+ */
+ want_privilege= (rw ? SELECT_ACL | UPDATE_ACL : UPDATE_ACL);
+}
+
+
+bool Item_trigger_field::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it)
+{
+ Item *item= sp_prepare_func_item(thd, it);
+
+ if (!item)
+ return true;
+
+ if (!fixed)
+ {
+ if (fix_fields(thd, NULL))
+ return true;
+ }
+
+ // NOTE: field->table->copy_blobs should be false here, but let's
+ // remember the value at runtime to avoid subtle bugs.
+ bool copy_blobs_saved= field->table->copy_blobs;
+
+ field->table->copy_blobs= true;
+
+ int err_code= item->save_in_field(field, 0);
+
+ field->table->copy_blobs= copy_blobs_saved;
+
+ return err_code < 0;
+}
+
+
+bool Item_trigger_field::fix_fields(THD *thd, Item **items)
+{
+ /*
+ Since trigger is object tightly associated with TABLE object most
+ of its set up can be performed during trigger loading i.e. trigger
+ parsing! So we have little to do in fix_fields. :)
+ */
+
+ DBUG_ASSERT(fixed == 0);
+
+ /* Set field. */
+
+ if (field_idx != (uint)-1)
+ {
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ Check access privileges for the subject table. We check privileges only
+ in runtime.
+ */
+
+ if (table_grants)
+ {
+ table_grants->want_privilege= want_privilege;
+
+ if (check_grant_column(thd, table_grants, triggers->trigger_table->s->db.str,
+ triggers->trigger_table->s->table_name.str, field_name,
+ strlen(field_name), thd->security_ctx))
+ return TRUE;
+ }
+#endif // NO_EMBEDDED_ACCESS_CHECKS
+
+ field= (row_version == OLD_ROW) ? triggers->old_field[field_idx] :
+ triggers->new_field[field_idx];
+ set_field(field);
+ fixed= 1;
+ return FALSE;
+ }
+
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), field_name,
+ (row_version == NEW_ROW) ? "NEW" : "OLD");
+ return TRUE;
+}
+
+
+void Item_trigger_field::print(String *str, enum_query_type query_type)
+{
+ str->append((row_version == NEW_ROW) ? "NEW" : "OLD", 3);
+ str->append('.');
+ str->append(field_name);
+}
+
+
+void Item_trigger_field::cleanup()
+{
+ want_privilege= original_privilege;
+ /*
+ Since special nature of Item_trigger_field we should not do most of
+ things from Item_field::cleanup() or Item_ident::cleanup() here.
+ */
+ Item::cleanup();
+}
+
+
+Item_result item_cmp_type(Item_result a,Item_result b)
+{
+ if (a == STRING_RESULT && b == STRING_RESULT)
+ return STRING_RESULT;
+ if (a == INT_RESULT && b == INT_RESULT)
+ return INT_RESULT;
+ else if (a == ROW_RESULT || b == ROW_RESULT)
+ return ROW_RESULT;
+ else if (a == TIME_RESULT || b == TIME_RESULT)
+ return TIME_RESULT;
+ if ((a == INT_RESULT || a == DECIMAL_RESULT) &&
+ (b == INT_RESULT || b == DECIMAL_RESULT))
+ return DECIMAL_RESULT;
+ return REAL_RESULT;
+}
+
+
+void resolve_const_item(THD *thd, Item **ref, Item *comp_item)
+{
+ Item *item= *ref;
+ Item *new_item= NULL;
+ if (item->basic_const_item())
+ return; // Can't be better
+ Item_result res_type=item_cmp_type(comp_item->cmp_type(), item->cmp_type());
+ char *name=item->name; // Alloced by sql_alloc
+
+ switch (res_type) {
+ case TIME_RESULT:
+ {
+ bool is_null;
+ Item **ref_copy= ref;
+ /* the following call creates a constant and puts it in new_item */
+ get_datetime_value(thd, &ref_copy, &new_item, comp_item, &is_null);
+ if (is_null)
+ new_item= new Item_null(name);
+ break;
+ }
+ case STRING_RESULT:
+ {
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff),&my_charset_bin),*result;
+ result=item->val_str(&tmp);
+ if (item->null_value)
+ new_item= new Item_null(name);
+ else
+ {
+ uint length= result->length();
+ char *tmp_str= sql_strmake(result->ptr(), length);
+ new_item= new Item_string(name, tmp_str, length, result->charset());
+ }
+ break;
+ }
+ case INT_RESULT:
+ {
+ longlong result=item->val_int();
+ uint length=item->max_length;
+ bool null_value=item->null_value;
+ new_item= (null_value ? (Item*) new Item_null(name) :
+ (Item*) new Item_int(name, result, length));
+ break;
+ }
+ case ROW_RESULT:
+ if (item->type() == Item::ROW_ITEM && comp_item->type() == Item::ROW_ITEM)
+ {
+ /*
+ Substitute constants only in Item_rows. Don't affect other Items
+ with ROW_RESULT (eg Item_singlerow_subselect).
+
+ For such Items more optimal is to detect if it is constant and replace
+ it with Item_row. This would optimize queries like this:
+ SELECT * FROM t1 WHERE (a,b) = (SELECT a,b FROM t2 LIMIT 1);
+ */
+ Item_row *item_row= (Item_row*) item;
+ Item_row *comp_item_row= (Item_row*) comp_item;
+ uint col;
+ new_item= 0;
+ /*
+ If item and comp_item are both Item_rows and have same number of cols
+ then process items in Item_row one by one.
+ We can't ignore NULL values here as this item may be used with <=>, in
+ which case NULL's are significant.
+ */
+ DBUG_ASSERT(item->result_type() == comp_item->result_type());
+ DBUG_ASSERT(item_row->cols() == comp_item_row->cols());
+ col= item_row->cols();
+ while (col-- > 0)
+ resolve_const_item(thd, item_row->addr(col),
+ comp_item_row->element_index(col));
+ break;
+ }
+ /* Fallthrough */
+ case REAL_RESULT:
+ { // It must REAL_RESULT
+ double result= item->val_real();
+ uint length=item->max_length,decimals=item->decimals;
+ bool null_value=item->null_value;
+ new_item= (null_value ? (Item*) new Item_null(name) : (Item*)
+ new Item_float(name, result, decimals, length));
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ my_decimal decimal_value;
+ my_decimal *result= item->val_decimal(&decimal_value);
+ uint length= item->max_length, decimals= item->decimals;
+ bool null_value= item->null_value;
+ new_item= (null_value ?
+ (Item*) new Item_null(name) :
+ (Item*) new Item_decimal(name, result, length, decimals));
+ break;
+ }
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ break;
+ }
+ if (new_item)
+ thd->change_item_tree(ref, new_item);
+}
+
+/**
+ Compare the value stored in field with the expression from the query.
+
+ @param field Field which the Item is stored in after conversion
+ @param item Original expression from query
+
+ @return Returns an integer greater than, equal to, or less than 0 if
+ the value stored in the field is greater than, equal to,
+ or less than the original Item. A 0 may also be returned if
+ out of memory.
+
+ @note We use this in the range optimizer/partition pruning,
+ because in some cases we can't store the value in the field
+ without some precision/character loss.
+
+ We similarly use it to verify that expressions like
+ BIGINT_FIELD <cmp> <literal value>
+ is done correctly (as int/decimal/float according to literal type).
+
+ @todo rewrite it to use Arg_comparator (currently it's a simplified and
+ incomplete version of it)
+*/
+
+int stored_field_cmp_to_item(THD *thd, Field *field, Item *item)
+{
+ Item_result res_type=item_cmp_type(field->result_type(),
+ item->result_type());
+ /*
+ We have to check field->cmp_type() instead of res_type,
+ as result_type() - and thus res_type - can never be TIME_RESULT (yet).
+ */
+ if (field->cmp_type() == TIME_RESULT)
+ {
+ MYSQL_TIME field_time, item_time, item_time2, *item_time_cmp= &item_time;
+ if (field->type() == MYSQL_TYPE_TIME)
+ {
+ field->get_time(&field_time);
+ item->get_time(&item_time);
+ }
+ else
+ {
+ field->get_date(&field_time, TIME_INVALID_DATES);
+ item->get_date(&item_time, TIME_INVALID_DATES);
+ if (item_time.time_type == MYSQL_TIMESTAMP_TIME)
+ if (time_to_datetime(thd, &item_time, item_time_cmp= &item_time2))
+ return 1;
+ }
+ return my_time_compare(&field_time, item_time_cmp);
+ }
+ if (res_type == STRING_RESULT)
+ {
+ char item_buff[MAX_FIELD_WIDTH];
+ char field_buff[MAX_FIELD_WIDTH];
+
+ String item_tmp(item_buff,sizeof(item_buff),&my_charset_bin);
+ String field_tmp(field_buff,sizeof(field_buff),&my_charset_bin);
+ String *item_result= item->val_str(&item_tmp);
+ /*
+ Some implementations of Item::val_str(String*) actually modify
+ the field Item::null_value, hence we can't check it earlier.
+ */
+ if (item->null_value)
+ return 0;
+ String *field_result= field->val_str(&field_tmp);
+
+ enum_field_types field_type= field->type();
+
+ if (field_type == MYSQL_TYPE_DATE || field_type == MYSQL_TYPE_DATETIME ||
+ field_type == MYSQL_TYPE_TIMESTAMP)
+ {
+ enum_mysql_timestamp_type type= MYSQL_TIMESTAMP_ERROR;
+
+ if (field_type == MYSQL_TYPE_DATE)
+ type= MYSQL_TIMESTAMP_DATE;
+ else
+ type= MYSQL_TIMESTAMP_DATETIME;
+
+ const char *field_name= field->field_name;
+ MYSQL_TIME field_time, item_time;
+ get_mysql_time_from_str(thd, field_result, type, field_name, &field_time);
+ get_mysql_time_from_str(thd, item_result, type, field_name, &item_time);
+
+ return my_time_compare(&field_time, &item_time);
+ }
+ return sortcmp(field_result, item_result, field->charset());
+ }
+ if (res_type == INT_RESULT)
+ return 0; // Both are of type int
+ if (res_type == DECIMAL_RESULT)
+ {
+ my_decimal item_buf, *item_val,
+ field_buf, *field_val;
+ item_val= item->val_decimal(&item_buf);
+ if (item->null_value)
+ return 0;
+ field_val= field->val_decimal(&field_buf);
+ return my_decimal_cmp(field_val, item_val);
+ }
+ /*
+ The patch for Bug#13463415 started using this function for comparing
+ BIGINTs. That uncovered a bug in Visual Studio 32bit optimized mode.
+ Prefixing the auto variables with volatile fixes the problem....
+ */
+ volatile double result= item->val_real();
+ if (item->null_value)
+ return 0;
+ volatile double field_result= field->val_real();
+ if (field_result < result)
+ return -1;
+ else if (field_result > result)
+ return 1;
+ return 0;
+}
+
+Item_cache* Item_cache::get_cache(const Item *item)
+{
+ return get_cache(item, item->cmp_type());
+}
+
+
+/**
+ Get a cache item of given type.
+
+ @param item value to be cached
+ @param type required type of cache
+
+ @return cache item
+*/
+
+Item_cache* Item_cache::get_cache(const Item *item, const Item_result type)
+{
+ switch (type) {
+ case INT_RESULT:
+ return new Item_cache_int(item->field_type());
+ case REAL_RESULT:
+ return new Item_cache_real();
+ case DECIMAL_RESULT:
+ return new Item_cache_decimal();
+ case STRING_RESULT:
+ return new Item_cache_str(item);
+ case ROW_RESULT:
+ return new Item_cache_row();
+ case TIME_RESULT:
+ return new Item_cache_temporal(item->field_type());
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ break;
+ }
+ return 0; // Impossible
+}
+
+void Item_cache::store(Item *item)
+{
+ example= item;
+ if (!item)
+ null_value= TRUE;
+ value_cached= FALSE;
+}
+
+void Item_cache::print(String *str, enum_query_type query_type)
+{
+ if (value_cached)
+ {
+ print_value(str);
+ return;
+ }
+ str->append(STRING_WITH_LEN("<cache>("));
+ if (example)
+ example->print(str, query_type);
+ else
+ Item::print(str, query_type);
+ str->append(')');
+}
+
+/**
+ Assign to this cache NULL value if it is possible
+*/
+
+void Item_cache::set_null()
+{
+ if (maybe_null)
+ {
+ null_value= TRUE;
+ value_cached= TRUE;
+ }
+}
+
+
+bool Item_cache_int::cache_value()
+{
+ if (!example)
+ return FALSE;
+ value_cached= TRUE;
+ value= example->val_int_result();
+ null_value= example->null_value;
+ unsigned_flag= example->unsigned_flag;
+ return TRUE;
+}
+
+
+String *Item_cache_int::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return NULL;
+ str->set_int(value, unsigned_flag, default_charset());
+ return str;
+}
+
+
+my_decimal *Item_cache_int::val_decimal(my_decimal *decimal_val)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return NULL;
+ int2my_decimal(E_DEC_FATAL_ERROR, value, unsigned_flag, decimal_val);
+ return decimal_val;
+}
+
+double Item_cache_int::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return 0.0;
+ return (double) value;
+}
+
+longlong Item_cache_int::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return 0;
+ return value;
+}
+
+int Item_cache_int::save_in_field(Field *field, bool no_conversions)
+{
+ int error;
+ if (!has_value())
+ return set_field_to_null_with_conversions(field, no_conversions);
+
+ field->set_notnull();
+ error= field->store(value, unsigned_flag);
+
+ return error ? error : field->table->in_use->is_error() ? 1 : 0;
+}
+
+
+Item_cache_temporal::Item_cache_temporal(enum_field_types field_type_arg):
+ Item_cache_int(field_type_arg)
+{
+ if (mysql_type_to_time_type(cached_field_type) == MYSQL_TIMESTAMP_ERROR)
+ cached_field_type= MYSQL_TYPE_DATETIME;
+}
+
+
+longlong Item_cache_temporal::val_temporal_packed()
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((!value_cached && !cache_value()) || null_value)
+ {
+ null_value= TRUE;
+ return 0;
+ }
+ return value;
+}
+
+
+String *Item_cache_temporal::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ {
+ null_value= true;
+ return NULL;
+ }
+ return val_string_from_date(str);
+}
+
+
+my_decimal *Item_cache_temporal::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((!value_cached && !cache_value()) || null_value)
+ {
+ null_value= true;
+ return NULL;
+ }
+ return val_decimal_from_date(decimal_value);
+}
+
+
+longlong Item_cache_temporal::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((!value_cached && !cache_value()) || null_value)
+ {
+ null_value= true;
+ return 0;
+ }
+ return val_int_from_date();
+}
+
+
+double Item_cache_temporal::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((!value_cached && !cache_value()) || null_value)
+ {
+ null_value= true;
+ return 0;
+ }
+ return val_real_from_date();
+}
+
+
+bool Item_cache_temporal::cache_value()
+{
+ if (!example)
+ return false;
+
+ value_cached= true;
+
+ MYSQL_TIME ltime;
+ if (example->get_date_result(&ltime, 0))
+ value=0;
+ else
+ value= pack_time(&ltime);
+ null_value= example->null_value;
+ return true;
+}
+
+
+bool Item_cache_temporal::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+{
+ ErrConvInteger str(value);
+
+ if (!has_value())
+ {
+ bzero((char*) ltime,sizeof(*ltime));
+ return 1;
+ }
+
+ unpack_time(value, ltime);
+ ltime->time_type= mysql_type_to_time_type(field_type());
+ if (ltime->time_type == MYSQL_TIMESTAMP_TIME)
+ {
+ ltime->hour+= (ltime->month*32+ltime->day)*24;
+ ltime->month= ltime->day= 0;
+ }
+ return 0;
+
+}
+
+
+int Item_cache_temporal::save_in_field(Field *field, bool no_conversions)
+{
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, 0))
+ return set_field_to_null_with_conversions(field, no_conversions);
+ field->set_notnull();
+ int error= field->store_time_dec(&ltime, decimals);
+ return error ? error : field->table->in_use->is_error() ? 1 : 0;
+}
+
+
+void Item_cache_temporal::store_packed(longlong val_arg, Item *example)
+{
+ /* An explicit values is given, save it. */
+ store(example);
+ value_cached= true;
+ value= val_arg;
+ null_value= false;
+}
+
+
+bool Item_cache_real::cache_value()
+{
+ if (!example)
+ return FALSE;
+ value_cached= TRUE;
+ value= example->val_result();
+ null_value= example->null_value;
+ return TRUE;
+}
+
+
+double Item_cache_real::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return 0.0;
+ return value;
+}
+
+longlong Item_cache_real::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return 0;
+ return (longlong) rint(value);
+}
+
+
+String* Item_cache_real::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return NULL;
+ str->set_real(value, decimals, default_charset());
+ return str;
+}
+
+
+my_decimal *Item_cache_real::val_decimal(my_decimal *decimal_val)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return NULL;
+ double2my_decimal(E_DEC_FATAL_ERROR, value, decimal_val);
+ return decimal_val;
+}
+
+
+bool Item_cache_decimal::cache_value()
+{
+ if (!example)
+ return FALSE;
+ value_cached= TRUE;
+ my_decimal *val= example->val_decimal_result(&decimal_value);
+ if (!(null_value= example->null_value) && val != &decimal_value)
+ my_decimal2decimal(val, &decimal_value);
+ return TRUE;
+}
+
+double Item_cache_decimal::val_real()
+{
+ DBUG_ASSERT(fixed);
+ double res;
+ if (!has_value())
+ return 0.0;
+ my_decimal2double(E_DEC_FATAL_ERROR, &decimal_value, &res);
+ return res;
+}
+
+longlong Item_cache_decimal::val_int()
+{
+ DBUG_ASSERT(fixed);
+ longlong res;
+ if (!has_value())
+ return 0;
+ my_decimal2int(E_DEC_FATAL_ERROR, &decimal_value, unsigned_flag, &res);
+ return res;
+}
+
+String* Item_cache_decimal::val_str(String *str)
+{
+ DBUG_ASSERT(fixed);
+ if (!has_value())
+ return NULL;
+ my_decimal_round(E_DEC_FATAL_ERROR, &decimal_value, decimals, FALSE,
+ &decimal_value);
+ my_decimal2string(E_DEC_FATAL_ERROR, &decimal_value, 0, 0, 0, str);
+ return str;
+}
+
+my_decimal *Item_cache_decimal::val_decimal(my_decimal *val)
+{
+ DBUG_ASSERT(fixed);
+ if (!has_value())
+ return NULL;
+ return &decimal_value;
+}
+
+
+bool Item_cache_str::cache_value()
+{
+ if (!example)
+ return FALSE;
+ value_cached= TRUE;
+ value_buff.set(buffer, sizeof(buffer), example->collation.collation);
+ value= example->str_result(&value_buff);
+ if ((null_value= example->null_value))
+ value= 0;
+ else if (value != &value_buff)
+ {
+ /*
+ We copy string value to avoid changing value if 'item' is table field
+ in queries like following (where t1.c is varchar):
+ select a,
+ (select a,b,c from t1 where t1.a=t2.a) = ROW(a,2,'a'),
+ (select c from t1 where a=t2.a)
+ from t2;
+ */
+ value_buff.copy(*value);
+ value= &value_buff;
+ }
+ return TRUE;
+}
+
+double Item_cache_str::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ int err_not_used;
+ char *end_not_used;
+ if (!has_value())
+ return 0.0;
+ if (value)
+ return my_strntod(value->charset(), (char*) value->ptr(),
+ value->length(), &end_not_used, &err_not_used);
+ return (double) 0;
+}
+
+
+longlong Item_cache_str::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int err;
+ if (!has_value())
+ return 0;
+ if (value)
+ return my_strntoll(value->charset(), value->ptr(),
+ value->length(), 10, (char**) 0, &err);
+ else
+ return (longlong)0;
+}
+
+
+String* Item_cache_str::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return 0;
+ return value;
+}
+
+
+my_decimal *Item_cache_str::val_decimal(my_decimal *decimal_val)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!has_value())
+ return NULL;
+ if (value)
+ string2my_decimal(E_DEC_FATAL_ERROR, value, decimal_val);
+ else
+ decimal_val= 0;
+ return decimal_val;
+}
+
+
+int Item_cache_str::save_in_field(Field *field, bool no_conversions)
+{
+ if (!has_value())
+ return set_field_to_null_with_conversions(field, no_conversions);
+ int res= Item_cache::save_in_field(field, no_conversions);
+ return (is_varbinary && field->type() == MYSQL_TYPE_STRING &&
+ value->length() < field->field_length) ? 1 : res;
+}
+
+
+bool Item_cache_row::allocate(uint num)
+{
+ item_count= num;
+ THD *thd= current_thd;
+ return (!(values=
+ (Item_cache **) thd->calloc(sizeof(Item_cache *)*item_count)));
+}
+
+
+bool Item_cache_row::setup(Item * item)
+{
+ example= item;
+ if (!values && allocate(item->cols()))
+ return 1;
+ for (uint i= 0; i < item_count; i++)
+ {
+ Item *el= item->element_index(i);
+ Item_cache *tmp;
+ if (!(tmp= values[i]= Item_cache::get_cache(el)))
+ return 1;
+ tmp->setup(el);
+ }
+ return 0;
+}
+
+
+void Item_cache_row::store(Item * item)
+{
+ example= item;
+ if (!item)
+ {
+ null_value= TRUE;
+ return;
+ }
+ for (uint i= 0; i < item_count; i++)
+ values[i]->store(item->element_index(i));
+}
+
+
+bool Item_cache_row::cache_value()
+{
+ if (!example)
+ return FALSE;
+ value_cached= TRUE;
+ null_value= 0;
+ example->bring_value();
+ for (uint i= 0; i < item_count; i++)
+ {
+ values[i]->cache_value();
+ null_value|= values[i]->null_value;
+ }
+ return TRUE;
+}
+
+
+void Item_cache_row::illegal_method_call(const char *method)
+{
+ DBUG_ENTER("Item_cache_row::illegal_method_call");
+ DBUG_PRINT("error", ("!!! %s method was called for row item", method));
+ DBUG_ASSERT(0);
+ my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
+ DBUG_VOID_RETURN;
+}
+
+
+bool Item_cache_row::check_cols(uint c)
+{
+ if (c != item_count)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), c);
+ return 1;
+ }
+ return 0;
+}
+
+
+bool Item_cache_row::null_inside()
+{
+ for (uint i= 0; i < item_count; i++)
+ {
+ if (values[i]->cols() > 1)
+ {
+ if (values[i]->null_inside())
+ return 1;
+ }
+ else
+ {
+ values[i]->update_null_value();
+ if (values[i]->null_value)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+void Item_cache_row::bring_value()
+{
+ if (!example)
+ return;
+ example->bring_value();
+ null_value= example->null_value;
+ for (uint i= 0; i < item_count; i++)
+ values[i]->bring_value();
+}
+
+
+/**
+ Assign to this cache NULL value if it is possible
+*/
+
+void Item_cache_row::set_null()
+{
+ Item_cache::set_null();
+ if (!values)
+ return;
+ for (uint i= 0; i < item_count; i++)
+ values[i]->set_null();
+};
+
+
+Item_type_holder::Item_type_holder(THD *thd, Item *item)
+ :Item(thd, item), enum_set_typelib(0), fld_type(get_real_type(item))
+{
+ DBUG_ASSERT(item->fixed);
+ maybe_null= item->maybe_null;
+ collation.set(item->collation);
+ get_full_info(item);
+ /* fix variable decimals which always is NOT_FIXED_DEC */
+ if (Field::result_merge_type(fld_type) == INT_RESULT)
+ decimals= 0;
+ prev_decimal_int_part= item->decimal_int_part();
+#ifdef HAVE_SPATIAL
+ if (item->field_type() == MYSQL_TYPE_GEOMETRY)
+ geometry_type= item->get_geometry_type();
+#endif /* HAVE_SPATIAL */
+}
+
+
+/**
+ Return expression type of Item_type_holder.
+
+ @return
+ Item_result (type of internal MySQL expression result)
+*/
+
+Item_result Item_type_holder::result_type() const
+{
+ return Field::result_merge_type(fld_type);
+}
+
+
+/**
+ Find real field type of item.
+
+ @return
+ type of field which should be created to store item value
+*/
+
+enum_field_types Item_type_holder::get_real_type(Item *item)
+{
+ if (item->type() == REF_ITEM)
+ item= item->real_item();
+ switch(item->type())
+ {
+ case FIELD_ITEM:
+ {
+ /*
+ Item_field::field_type ask Field_type() but sometimes field return
+ a different type, like for enum/set, so we need to ask real type.
+ */
+ Field *field= ((Item_field *) item)->field;
+ enum_field_types type= field->real_type();
+ if (field->is_created_from_null_item)
+ return MYSQL_TYPE_NULL;
+ /* work around about varchar type field detection */
+ if (type == MYSQL_TYPE_STRING && field->type() == MYSQL_TYPE_VAR_STRING)
+ return MYSQL_TYPE_VAR_STRING;
+ return type;
+ }
+ case SUM_FUNC_ITEM:
+ {
+ /*
+ Argument of aggregate function sometimes should be asked about field
+ type
+ */
+ Item_sum *item_sum= (Item_sum *) item;
+ if (item_sum->keep_field_type())
+ return get_real_type(item_sum->get_arg(0));
+ break;
+ }
+ case FUNC_ITEM:
+ if (((Item_func *) item)->functype() == Item_func::GUSERVAR_FUNC)
+ {
+ /*
+ There are work around of problem with changing variable type on the
+ fly and variable always report "string" as field type to get
+ acceptable information for client in send_field, so we make field
+ type from expression type.
+ */
+ switch (item->result_type()) {
+ case STRING_RESULT:
+ return MYSQL_TYPE_VAR_STRING;
+ case INT_RESULT:
+ return MYSQL_TYPE_LONGLONG;
+ case REAL_RESULT:
+ return MYSQL_TYPE_DOUBLE;
+ case DECIMAL_RESULT:
+ return MYSQL_TYPE_NEWDECIMAL;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ return MYSQL_TYPE_VAR_STRING;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return item->field_type();
+}
+
+/**
+ Find field type which can carry current Item_type_holder type and
+ type of given Item.
+
+ @param thd thread handler
+ @param item given item to join its parameters with this item ones
+
+ @retval
+ TRUE error - types are incompatible
+ @retval
+ FALSE OK
+*/
+
+bool Item_type_holder::join_types(THD *thd, Item *item)
+{
+ uint max_length_orig= max_length;
+ uint decimals_orig= decimals;
+ DBUG_ENTER("Item_type_holder::join_types");
+ DBUG_PRINT("info:", ("was type %d len %d, dec %d name %s",
+ fld_type, max_length, decimals,
+ (name ? name : "<NULL>")));
+ DBUG_PRINT("info:", ("in type %d len %d, dec %d",
+ get_real_type(item),
+ item->max_length, item->decimals));
+ fld_type= Field::field_type_merge(fld_type, get_real_type(item));
+ {
+ uint item_decimals= item->decimals;
+ /* fix variable decimals which always is NOT_FIXED_DEC */
+ if (Field::result_merge_type(fld_type) == INT_RESULT)
+ item_decimals= 0;
+ decimals= MY_MAX(decimals, item_decimals);
+ }
+ if (Field::result_merge_type(fld_type) == DECIMAL_RESULT)
+ {
+ decimals= MY_MIN(MY_MAX(decimals, item->decimals), DECIMAL_MAX_SCALE);
+ int item_int_part= item->decimal_int_part();
+ int item_prec = MY_MAX(prev_decimal_int_part, item_int_part) + decimals;
+ int precision= MY_MIN(item_prec, DECIMAL_MAX_PRECISION);
+ unsigned_flag&= item->unsigned_flag;
+ max_length= my_decimal_precision_to_length_no_truncation(precision,
+ decimals,
+ unsigned_flag);
+ }
+
+ switch (Field::result_merge_type(fld_type))
+ {
+ case STRING_RESULT:
+ {
+ const char *old_cs, *old_derivation;
+ uint32 old_max_chars= max_length / collation.collation->mbmaxlen;
+ old_cs= collation.collation->name;
+ old_derivation= collation.derivation_name();
+ if (collation.aggregate(item->collation, MY_COLL_ALLOW_CONV))
+ {
+ my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0),
+ old_cs, old_derivation,
+ item->collation.collation->name,
+ item->collation.derivation_name(),
+ "UNION");
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ To figure out max_length, we have to take into account possible
+ expansion of the size of the values because of character set
+ conversions.
+ */
+ if (collation.collation != &my_charset_bin)
+ {
+ max_length= MY_MAX(old_max_chars * collation.collation->mbmaxlen,
+ display_length(item) /
+ item->collation.collation->mbmaxlen *
+ collation.collation->mbmaxlen);
+ }
+ else
+ set_if_bigger(max_length, display_length(item));
+ break;
+ }
+ case REAL_RESULT:
+ {
+ if (decimals != NOT_FIXED_DEC)
+ {
+ /*
+ For FLOAT(M,D)/DOUBLE(M,D) do not change precision
+ if both fields have the same M and D
+ */
+ if (item->max_length != max_length_orig ||
+ item->decimals != decimals_orig)
+ {
+ int delta1= max_length_orig - decimals_orig;
+ int delta2= item->max_length - item->decimals;
+ max_length= MY_MAX(delta1, delta2) + decimals;
+ if (fld_type == MYSQL_TYPE_FLOAT && max_length > FLT_DIG + 2)
+ {
+ max_length= MAX_FLOAT_STR_LENGTH;
+ decimals= NOT_FIXED_DEC;
+ }
+ else if (fld_type == MYSQL_TYPE_DOUBLE && max_length > DBL_DIG + 2)
+ {
+ max_length= MAX_DOUBLE_STR_LENGTH;
+ decimals= NOT_FIXED_DEC;
+ }
+ }
+ }
+ else
+ max_length= (fld_type == MYSQL_TYPE_FLOAT) ? FLT_DIG+6 : DBL_DIG+7;
+ break;
+ }
+ default:
+ max_length= MY_MAX(max_length, display_length(item));
+ };
+ maybe_null|= item->maybe_null;
+ get_full_info(item);
+
+ /* Remember decimal integer part to be used in DECIMAL_RESULT handleng */
+ prev_decimal_int_part= decimal_int_part();
+ DBUG_PRINT("info", ("become type: %d len: %u dec: %u",
+ (int) fld_type, max_length, (uint) decimals));
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Calculate lenth for merging result for given Item type.
+
+ @param item Item for length detection
+
+ @return
+ length
+*/
+
+uint32 Item_type_holder::display_length(Item *item)
+{
+ if (item->type() == Item::FIELD_ITEM)
+ return ((Item_field *)item)->max_disp_length();
+
+ switch (item->field_type())
+ {
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_GEOMETRY:
+ return item->max_length;
+ case MYSQL_TYPE_TINY:
+ return 4;
+ case MYSQL_TYPE_SHORT:
+ return 6;
+ case MYSQL_TYPE_LONG:
+ return MY_INT32_NUM_DECIMAL_DIGITS;
+ case MYSQL_TYPE_FLOAT:
+ return 25;
+ case MYSQL_TYPE_DOUBLE:
+ return 53;
+ case MYSQL_TYPE_NULL:
+ return 0;
+ case MYSQL_TYPE_LONGLONG:
+ return 20;
+ case MYSQL_TYPE_INT24:
+ return 8;
+ default:
+ DBUG_ASSERT(0); // we should never go there
+ return 0;
+ }
+}
+
+
+/**
+ Make temporary table field according collected information about type
+ of UNION result.
+
+ @param table temporary table for which we create fields
+
+ @return
+ created field
+*/
+
+Field *Item_type_holder::make_field_by_type(TABLE *table)
+{
+ /*
+ The field functions defines a field to be not null if null_ptr is not 0
+ */
+ uchar *null_ptr= maybe_null ? (uchar*) "" : 0;
+ Field *field;
+
+ switch (fld_type) {
+ case MYSQL_TYPE_ENUM:
+ DBUG_ASSERT(enum_set_typelib);
+ field= new Field_enum((uchar *) 0, max_length, null_ptr, 0,
+ Field::NONE, name,
+ get_enum_pack_length(enum_set_typelib->count),
+ enum_set_typelib, collation.collation);
+ if (field)
+ field->init(table);
+ return field;
+ case MYSQL_TYPE_SET:
+ DBUG_ASSERT(enum_set_typelib);
+ field= new Field_set((uchar *) 0, max_length, null_ptr, 0,
+ Field::NONE, name,
+ get_set_pack_length(enum_set_typelib->count),
+ enum_set_typelib, collation.collation);
+ if (field)
+ field->init(table);
+ return field;
+ case MYSQL_TYPE_NULL:
+ return make_string_field(table);
+ default:
+ break;
+ }
+ return tmp_table_field_from_field_type(table, 0);
+}
+
+
+/**
+ Get full information from Item about enum/set fields to be able to create
+ them later.
+
+ @param item Item for information collection
+*/
+void Item_type_holder::get_full_info(Item *item)
+{
+ if (fld_type == MYSQL_TYPE_ENUM ||
+ fld_type == MYSQL_TYPE_SET)
+ {
+ if (item->type() == Item::SUM_FUNC_ITEM &&
+ (((Item_sum*)item)->sum_func() == Item_sum::MAX_FUNC ||
+ ((Item_sum*)item)->sum_func() == Item_sum::MIN_FUNC))
+ item = ((Item_sum*)item)->get_arg(0);
+ /*
+ We can have enum/set type after merging only if we have one enum|set
+ field (or MIN|MAX(enum|set field)) and number of NULL fields
+ */
+ DBUG_ASSERT((enum_set_typelib &&
+ get_real_type(item) == MYSQL_TYPE_NULL) ||
+ (!enum_set_typelib &&
+ item->real_item()->type() == Item::FIELD_ITEM &&
+ (get_real_type(item->real_item()) == MYSQL_TYPE_ENUM ||
+ get_real_type(item->real_item()) == MYSQL_TYPE_SET) &&
+ ((Field_enum*)((Item_field *) item->real_item())->field)->typelib));
+ if (!enum_set_typelib)
+ {
+ enum_set_typelib= ((Field_enum*)((Item_field *) item->real_item())->field)->typelib;
+ }
+ }
+}
+
+
+double Item_type_holder::val_real()
+{
+ DBUG_ASSERT(0); // should never be called
+ return 0.0;
+}
+
+
+longlong Item_type_holder::val_int()
+{
+ DBUG_ASSERT(0); // should never be called
+ return 0;
+}
+
+my_decimal *Item_type_holder::val_decimal(my_decimal *)
+{
+ DBUG_ASSERT(0); // should never be called
+ return 0;
+}
+
+String *Item_type_holder::val_str(String*)
+{
+ DBUG_ASSERT(0); // should never be called
+ return 0;
+}
+
+void Item_result_field::cleanup()
+{
+ DBUG_ENTER("Item_result_field::cleanup()");
+ Item::cleanup();
+ result_field= 0;
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Dummy error processor used by default by Name_resolution_context.
+
+ @note
+ do nothing
+*/
+
+void dummy_error_processor(THD *thd, void *data)
+{}
+
+/**
+ Wrapper of hide_view_error call for Name_resolution_context error
+ processor.
+
+ @note
+ hide view underlying tables details in error messages
+*/
+
+void view_error_processor(THD *thd, void *data)
+{
+ ((TABLE_LIST *)data)->hide_view_error(thd);
+}
+
+
+st_select_lex *Item_ident::get_depended_from() const
+{
+ st_select_lex *dep;
+ if ((dep= depended_from))
+ for ( ; dep->merged_into; dep= dep->merged_into) ;
+ return dep;
+}
+
+
+table_map Item_ref::used_tables() const
+{
+ return get_depended_from() ? OUTER_REF_TABLE_BIT : (*ref)->used_tables();
+}
+
+
+void Item_ref::update_used_tables()
+{
+ if (!get_depended_from())
+ (*ref)->update_used_tables();
+}
+
+void Item_direct_view_ref::update_used_tables()
+{
+ set_null_ref_table();
+ Item_direct_ref::update_used_tables();
+}
+
+
+table_map Item_direct_view_ref::used_tables() const
+{
+ DBUG_ASSERT(null_ref_table);
+
+ if (get_depended_from())
+ return OUTER_REF_TABLE_BIT;
+
+ if (view->is_merged_derived() || view->merged || !view->table)
+ {
+ table_map used= (*ref)->used_tables();
+ return (used ?
+ used :
+ ((null_ref_table != NO_NULL_TABLE) ?
+ null_ref_table->map :
+ (table_map)0 ));
+ }
+ return view->table->map;
+}
+
+table_map Item_direct_view_ref::not_null_tables() const
+{
+ return get_depended_from() ?
+ 0 :
+ ((view->is_merged_derived() || view->merged || !view->table) ?
+ (*ref)->not_null_tables() :
+ view->table->map);
+}
+
+/*
+ we add RAND_TABLE_BIT to prevent moving this item from HAVING to WHERE
+*/
+table_map Item_ref_null_helper::used_tables() const
+{
+ return (get_depended_from() ?
+ OUTER_REF_TABLE_BIT :
+ (*ref)->used_tables() | RAND_TABLE_BIT);
+}
+
+
+#ifndef DBUG_OFF
+
+/* Debugger help function */
+static char dbug_item_print_buf[256];
+
+const char *dbug_print_item(Item *item)
+{
+ char *buf= dbug_item_print_buf;
+ String str(buf, sizeof(dbug_item_print_buf), &my_charset_bin);
+ str.length(0);
+ if (!item)
+ return "(Item*)NULL";
+ item->print(&str ,QT_ORDINARY);
+ if (str.c_ptr() == buf)
+ return buf;
+ else
+ return "Couldn't fit into buffer";
+}
+
+#endif /*DBUG_OFF*/
+
diff --git a/sql/item.h b/sql/item.h
new file mode 100644
index 00000000000..ce757749217
--- /dev/null
+++ b/sql/item.h
@@ -0,0 +1,4899 @@
+#ifndef SQL_ITEM_INCLUDED
+#define SQL_ITEM_INCLUDED
+
+/* Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_priv.h" /* STRING_BUFFER_USUAL_SIZE */
+#include "unireg.h"
+#include "sql_const.h" /* RAND_TABLE_BIT, MAX_FIELD_NAME */
+#include "unireg.h" // REQUIRED: for other includes
+#include "thr_malloc.h" /* sql_calloc */
+#include "field.h" /* Derivation */
+
+C_MODE_START
+#include <ma_dyncol.h>
+C_MODE_END
+
+static inline
+bool trace_unsupported_func(const char *where, const char *processor_name)
+{
+ char buff[64];
+ sprintf(buff, "%s::%s", where, processor_name);
+ DBUG_ENTER(buff);
+ sprintf(buff, "%s returns TRUE: unsupported function", processor_name);
+ DBUG_PRINT("info", ("%s", buff));
+ DBUG_RETURN(TRUE);
+}
+
+static inline
+bool trace_unsupported_by_check_vcol_func_processor(const char *where)
+{
+ return trace_unsupported_func(where, "check_vcol_func_processor");
+}
+
+class Protocol;
+struct TABLE_LIST;
+void item_init(void); /* Init item functions */
+class Item_field;
+class user_var_entry;
+
+
+static inline uint32
+char_to_byte_length_safe(uint32 char_length_arg, uint32 mbmaxlen_arg)
+{
+ ulonglong tmp= ((ulonglong) char_length_arg) * mbmaxlen_arg;
+ return (tmp > UINT_MAX32) ? (uint32) UINT_MAX32 : (uint32) tmp;
+}
+
+
+/*
+ "Declared Type Collation"
+ A combination of collation and its derivation.
+
+ Flags for collation aggregation modes:
+ MY_COLL_ALLOW_SUPERSET_CONV - allow conversion to a superset
+ MY_COLL_ALLOW_COERCIBLE_CONV - allow conversion of a coercible value
+ (i.e. constant).
+ MY_COLL_ALLOW_CONV - allow any kind of conversion
+ (combination of the above two)
+ MY_COLL_ALLOW_NUMERIC_CONV - if all items were numbers, convert to
+ @@character_set_connection
+ MY_COLL_DISALLOW_NONE - don't allow return DERIVATION_NONE
+ (e.g. when aggregating for comparison)
+ MY_COLL_CMP_CONV - combination of MY_COLL_ALLOW_CONV
+ and MY_COLL_DISALLOW_NONE
+*/
+
+#define MY_COLL_ALLOW_SUPERSET_CONV 1
+#define MY_COLL_ALLOW_COERCIBLE_CONV 2
+#define MY_COLL_DISALLOW_NONE 4
+#define MY_COLL_ALLOW_NUMERIC_CONV 8
+
+#define MY_COLL_ALLOW_CONV (MY_COLL_ALLOW_SUPERSET_CONV | MY_COLL_ALLOW_COERCIBLE_CONV)
+#define MY_COLL_CMP_CONV (MY_COLL_ALLOW_CONV | MY_COLL_DISALLOW_NONE)
+
+class DTCollation {
+public:
+ CHARSET_INFO *collation;
+ enum Derivation derivation;
+ uint repertoire;
+
+ void set_repertoire_from_charset(CHARSET_INFO *cs)
+ {
+ repertoire= cs->state & MY_CS_PUREASCII ?
+ MY_REPERTOIRE_ASCII : MY_REPERTOIRE_UNICODE30;
+ }
+ DTCollation()
+ {
+ collation= &my_charset_bin;
+ derivation= DERIVATION_NONE;
+ repertoire= MY_REPERTOIRE_UNICODE30;
+ }
+ DTCollation(CHARSET_INFO *collation_arg, Derivation derivation_arg)
+ {
+ collation= collation_arg;
+ derivation= derivation_arg;
+ set_repertoire_from_charset(collation_arg);
+ }
+ void set(DTCollation &dt)
+ {
+ collation= dt.collation;
+ derivation= dt.derivation;
+ repertoire= dt.repertoire;
+ }
+ void set(CHARSET_INFO *collation_arg, Derivation derivation_arg)
+ {
+ collation= collation_arg;
+ derivation= derivation_arg;
+ set_repertoire_from_charset(collation_arg);
+ }
+ void set(CHARSET_INFO *collation_arg,
+ Derivation derivation_arg,
+ uint repertoire_arg)
+ {
+ collation= collation_arg;
+ derivation= derivation_arg;
+ repertoire= repertoire_arg;
+ }
+ void set_numeric()
+ {
+ collation= &my_charset_numeric;
+ derivation= DERIVATION_NUMERIC;
+ repertoire= MY_REPERTOIRE_NUMERIC;
+ }
+ void set(CHARSET_INFO *collation_arg)
+ {
+ collation= collation_arg;
+ set_repertoire_from_charset(collation_arg);
+ }
+ void set(Derivation derivation_arg)
+ { derivation= derivation_arg; }
+ bool aggregate(DTCollation &dt, uint flags= 0);
+ bool set(DTCollation &dt1, DTCollation &dt2, uint flags= 0)
+ { set(dt1); return aggregate(dt2, flags); }
+ const char *derivation_name() const
+ {
+ switch(derivation)
+ {
+ case DERIVATION_NUMERIC: return "NUMERIC";
+ case DERIVATION_IGNORABLE: return "IGNORABLE";
+ case DERIVATION_COERCIBLE: return "COERCIBLE";
+ case DERIVATION_IMPLICIT: return "IMPLICIT";
+ case DERIVATION_SYSCONST: return "SYSCONST";
+ case DERIVATION_EXPLICIT: return "EXPLICIT";
+ case DERIVATION_NONE: return "NONE";
+ default: return "UNKNOWN";
+ }
+ }
+ int sortcmp(const String *s, const String *t) const
+ {
+ return collation->coll->strnncollsp(collation,
+ (uchar *) s->ptr(), s->length(),
+ (uchar *) t->ptr(), t->length(), 0);
+ }
+};
+
+/*************************************************************************/
+/*
+ A framework to easily handle different return types for hybrid items
+ (hybrid item is an item whose operand can be of any type, e.g. integer,
+ real, decimal).
+*/
+
+struct Hybrid_type_traits;
+
+struct Hybrid_type
+{
+ longlong integer;
+
+ double real;
+ /*
+ Use two decimal buffers interchangeably to speed up += operation
+ which has no native support in decimal library.
+ Hybrid_type+= arg is implemented as dec_buf[1]= dec_buf[0] + arg.
+ The third decimal is used as a handy temporary storage.
+ */
+ my_decimal dec_buf[3];
+ int used_dec_buf_no;
+
+ /*
+ Traits moved to a separate class to
+ a) be able to easily change object traits in runtime
+ b) they work as a differentiator for the union above
+ */
+ const Hybrid_type_traits *traits;
+
+ Hybrid_type() {}
+ /* XXX: add traits->copy() when needed */
+ Hybrid_type(const Hybrid_type &rhs) :traits(rhs.traits) {}
+};
+
+
+/* Hybryd_type_traits interface + default implementation for REAL_RESULT */
+
+struct Hybrid_type_traits
+{
+ virtual Item_result type() const { return REAL_RESULT; }
+
+ virtual void
+ fix_length_and_dec(Item *item, Item *arg) const;
+
+ /* Hybrid_type operations. */
+ virtual void set_zero(Hybrid_type *val) const { val->real= 0.0; }
+ virtual void add(Hybrid_type *val, Field *f) const
+ { val->real+= f->val_real(); }
+ virtual void div(Hybrid_type *val, ulonglong u) const
+ { val->real/= ulonglong2double(u); }
+
+ virtual longlong val_int(Hybrid_type *val, bool unsigned_flag) const
+ { return (longlong) rint(val->real); }
+ virtual double val_real(Hybrid_type *val) const { return val->real; }
+ virtual my_decimal *val_decimal(Hybrid_type *val, my_decimal *buf) const;
+ virtual String *val_str(Hybrid_type *val, String *buf, uint8 decimals) const;
+ static const Hybrid_type_traits *instance();
+ Hybrid_type_traits() {}
+ virtual ~Hybrid_type_traits() {}
+};
+
+
+struct Hybrid_type_traits_decimal: public Hybrid_type_traits
+{
+ virtual Item_result type() const { return DECIMAL_RESULT; }
+
+ virtual void
+ fix_length_and_dec(Item *arg, Item *item) const;
+
+ /* Hybrid_type operations. */
+ virtual void set_zero(Hybrid_type *val) const;
+ virtual void add(Hybrid_type *val, Field *f) const;
+ virtual void div(Hybrid_type *val, ulonglong u) const;
+
+ virtual longlong val_int(Hybrid_type *val, bool unsigned_flag) const;
+ virtual double val_real(Hybrid_type *val) const;
+ virtual my_decimal *val_decimal(Hybrid_type *val, my_decimal *buf) const
+ { return &val->dec_buf[val->used_dec_buf_no]; }
+ virtual String *val_str(Hybrid_type *val, String *buf, uint8 decimals) const;
+ static const Hybrid_type_traits_decimal *instance();
+ Hybrid_type_traits_decimal() {};
+};
+
+
+struct Hybrid_type_traits_integer: public Hybrid_type_traits
+{
+ virtual Item_result type() const { return INT_RESULT; }
+
+ virtual void
+ fix_length_and_dec(Item *arg, Item *item) const;
+
+ /* Hybrid_type operations. */
+ virtual void set_zero(Hybrid_type *val) const
+ { val->integer= 0; }
+ virtual void add(Hybrid_type *val, Field *f) const
+ { val->integer+= f->val_int(); }
+ virtual void div(Hybrid_type *val, ulonglong u) const
+ { val->integer/= (longlong) u; }
+
+ virtual longlong val_int(Hybrid_type *val, bool unsigned_flag) const
+ { return val->integer; }
+ virtual double val_real(Hybrid_type *val) const
+ { return (double) val->integer; }
+ virtual my_decimal *val_decimal(Hybrid_type *val, my_decimal *buf) const
+ {
+ int2my_decimal(E_DEC_FATAL_ERROR, val->integer, 0, &val->dec_buf[2]);
+ return &val->dec_buf[2];
+ }
+ virtual String *val_str(Hybrid_type *val, String *buf, uint8 decimals) const
+ { buf->set(val->integer, &my_charset_bin); return buf;}
+ static const Hybrid_type_traits_integer *instance();
+ Hybrid_type_traits_integer() {};
+};
+
+
+void dummy_error_processor(THD *thd, void *data);
+
+void view_error_processor(THD *thd, void *data);
+
+/*
+ Instances of Name_resolution_context store the information necesary for
+ name resolution of Items and other context analysis of a query made in
+ fix_fields().
+
+ This structure is a part of SELECT_LEX, a pointer to this structure is
+ assigned when an item is created (which happens mostly during parsing
+ (sql_yacc.yy)), but the structure itself will be initialized after parsing
+ is complete
+
+ TODO: move subquery of INSERT ... SELECT and CREATE ... SELECT to
+ separate SELECT_LEX which allow to remove tricks of changing this
+ structure before and after INSERT/CREATE and its SELECT to make correct
+ field name resolution.
+*/
+struct Name_resolution_context: Sql_alloc
+{
+ /*
+ The name resolution context to search in when an Item cannot be
+ resolved in this context (the context of an outer select)
+ */
+ Name_resolution_context *outer_context;
+
+ /*
+ List of tables used to resolve the items of this context. Usually these
+ are tables from the FROM clause of SELECT statement. The exceptions are
+ INSERT ... SELECT and CREATE ... SELECT statements, where SELECT
+ subquery is not moved to a separate SELECT_LEX. For these types of
+ statements we have to change this member dynamically to ensure correct
+ name resolution of different parts of the statement.
+ */
+ TABLE_LIST *table_list;
+ /*
+ In most cases the two table references below replace 'table_list' above
+ for the purpose of name resolution. The first and last name resolution
+ table references allow us to search only in a sub-tree of the nested
+ join tree in a FROM clause. This is needed for NATURAL JOIN, JOIN ... USING
+ and JOIN ... ON.
+ */
+ TABLE_LIST *first_name_resolution_table;
+ /*
+ Last table to search in the list of leaf table references that begins
+ with first_name_resolution_table.
+ */
+ TABLE_LIST *last_name_resolution_table;
+
+ /* Cache first_name_resolution_table in setup_natural_join_row_types */
+ TABLE_LIST *natural_join_first_table;
+ /*
+ SELECT_LEX item belong to, in case of merged VIEW it can differ from
+ SELECT_LEX where item was created, so we can't use table_list/field_list
+ from there
+ */
+ st_select_lex *select_lex;
+
+ /*
+ Processor of errors caused during Item name resolving, now used only to
+ hide underlying tables in errors about views (i.e. it substitute some
+ errors for views)
+ */
+ void (*error_processor)(THD *, void *);
+ void *error_processor_data;
+
+ /*
+ When TRUE items are resolved in this context both against the
+ SELECT list and this->table_list. If FALSE, items are resolved
+ only against this->table_list.
+ */
+ bool resolve_in_select_list;
+
+ /*
+ Security context of this name resolution context. It's used for views
+ and is non-zero only if the view is defined with SQL SECURITY DEFINER.
+ */
+ Security_context *security_ctx;
+
+ Name_resolution_context()
+ :outer_context(0), table_list(0), select_lex(0),
+ error_processor_data(0),
+ security_ctx(0)
+ {}
+
+ void init()
+ {
+ resolve_in_select_list= FALSE;
+ error_processor= &dummy_error_processor;
+ first_name_resolution_table= NULL;
+ last_name_resolution_table= NULL;
+ }
+
+ void resolve_in_table_list_only(TABLE_LIST *tables)
+ {
+ table_list= first_name_resolution_table= tables;
+ resolve_in_select_list= FALSE;
+ }
+
+ void process_error(THD *thd)
+ {
+ (*error_processor)(thd, error_processor_data);
+ }
+ st_select_lex *outer_select()
+ {
+ return (outer_context ?
+ outer_context->select_lex :
+ NULL);
+ }
+};
+
+
+/*
+ Store and restore the current state of a name resolution context.
+*/
+
+class Name_resolution_context_state
+{
+private:
+ TABLE_LIST *save_table_list;
+ TABLE_LIST *save_first_name_resolution_table;
+ TABLE_LIST *save_next_name_resolution_table;
+ bool save_resolve_in_select_list;
+ TABLE_LIST *save_next_local;
+
+public:
+ Name_resolution_context_state() {} /* Remove gcc warning */
+
+public:
+ /* Save the state of a name resolution context. */
+ void save_state(Name_resolution_context *context, TABLE_LIST *table_list)
+ {
+ save_table_list= context->table_list;
+ save_first_name_resolution_table= context->first_name_resolution_table;
+ save_resolve_in_select_list= context->resolve_in_select_list;
+ save_next_local= table_list->next_local;
+ save_next_name_resolution_table= table_list->next_name_resolution_table;
+ }
+
+ /* Restore a name resolution context from saved state. */
+ void restore_state(Name_resolution_context *context, TABLE_LIST *table_list)
+ {
+ table_list->next_local= save_next_local;
+ table_list->next_name_resolution_table= save_next_name_resolution_table;
+ context->table_list= save_table_list;
+ context->first_name_resolution_table= save_first_name_resolution_table;
+ context->resolve_in_select_list= save_resolve_in_select_list;
+ }
+
+ TABLE_LIST *get_first_name_resolution_table()
+ {
+ return save_first_name_resolution_table;
+ }
+};
+
+
+/*
+ This enum is used to report information about monotonicity of function
+ represented by Item* tree.
+ Monotonicity is defined only for Item* trees that represent table
+ partitioning expressions (i.e. have no subselects/user vars/PS parameters
+ etc etc). An Item* tree is assumed to have the same monotonicity properties
+ as its correspoinding function F:
+
+ [signed] longlong F(field1, field2, ...) {
+ put values of field_i into table record buffer;
+ return item->val_int();
+ }
+
+ NOTE
+ At the moment function monotonicity is not well defined (and so may be
+ incorrect) for Item trees with parameters/return types that are different
+ from INT_RESULT, may be NULL, or are unsigned.
+ It will be possible to address this issue once the related partitioning bugs
+ (BUG#16002, BUG#15447, BUG#13436) are fixed.
+
+ The NOT_NULL enums are used in TO_DAYS, since TO_DAYS('2001-00-00') returns
+ NULL which puts those rows into the NULL partition, but
+ '2000-12-31' < '2001-00-00' < '2001-01-01'. So special handling is needed
+ for this (see Bug#20577).
+*/
+
+typedef enum monotonicity_info
+{
+ NON_MONOTONIC, /* none of the below holds */
+ MONOTONIC_INCREASING, /* F() is unary and (x < y) => (F(x) <= F(y)) */
+ MONOTONIC_INCREASING_NOT_NULL, /* But only for valid/real x and y */
+ MONOTONIC_STRICT_INCREASING,/* F() is unary and (x < y) => (F(x) < F(y)) */
+ MONOTONIC_STRICT_INCREASING_NOT_NULL /* But only for valid/real x and y */
+} enum_monotonicity_info;
+
+/*************************************************************************/
+
+class sp_rcontext;
+
+
+class Item_equal;
+
+struct st_join_table* const NO_PARTICULAR_TAB= (struct st_join_table*)0x1;
+
+typedef struct replace_equal_field_arg
+{
+ Item_equal *item_equal;
+ struct st_join_table *context_tab;
+} REPLACE_EQUAL_FIELD_ARG;
+
+class Settable_routine_parameter
+{
+public:
+ /*
+ Set required privileges for accessing the parameter.
+
+ SYNOPSIS
+ set_required_privilege()
+ rw if 'rw' is true then we are going to read and set the
+ parameter, so SELECT and UPDATE privileges might be
+ required, otherwise we only reading it and SELECT
+ privilege might be required.
+ */
+ Settable_routine_parameter() {}
+ virtual ~Settable_routine_parameter() {}
+ virtual void set_required_privilege(bool rw) {};
+
+ /*
+ Set parameter value.
+
+ SYNOPSIS
+ set_value()
+ thd thread handle
+ ctx context to which parameter belongs (if it is local
+ variable).
+ it item which represents new value
+
+ RETURN
+ FALSE if parameter value has been set,
+ TRUE if error has occured.
+ */
+ virtual bool set_value(THD *thd, sp_rcontext *ctx, Item **it)= 0;
+
+ virtual void set_out_param_info(Send_field *info) {}
+
+ virtual const Send_field *get_out_param_info() const
+ { return NULL; }
+};
+
+
+struct st_dyncall_create_def
+{
+ Item *key, *value;
+ CHARSET_INFO *cs;
+ uint len, frac;
+ DYNAMIC_COLUMN_TYPE type;
+};
+
+typedef struct st_dyncall_create_def DYNCALL_CREATE_DEF;
+
+
+typedef bool (Item::*Item_processor) (uchar *arg);
+/*
+ Analyzer function
+ SYNOPSIS
+ argp in/out IN: Analysis parameter
+ OUT: Parameter to be passed to the transformer
+
+ RETURN
+ TRUE Invoke the transformer
+ FALSE Don't do it
+
+*/
+typedef bool (Item::*Item_analyzer) (uchar **argp);
+typedef Item* (Item::*Item_transformer) (uchar *arg);
+typedef void (*Cond_traverser) (const Item *item, void *arg);
+
+struct st_cond_statistic;
+
+struct find_selective_predicates_list_processor_data
+{
+ TABLE *table;
+ List<st_cond_statistic> list;
+};
+
+class Item_equal;
+class COND_EQUAL;
+
+class st_select_lex_unit;
+
+class Item_func_not;
+
+class Item {
+ Item(const Item &); /* Prevent use of these */
+ void operator=(Item &);
+ /**
+ The index in the JOIN::join_tab array of the JOIN_TAB this Item is attached
+ to. Items are attached (or 'pushed') to JOIN_TABs during optimization by the
+ make_cond_for_table procedure. During query execution, this item is
+ evaluated when the join loop reaches the corresponding JOIN_TAB.
+
+ If the value of join_tab_idx >= MAX_TABLES, this means that there is no
+ corresponding JOIN_TAB.
+ */
+ uint join_tab_idx;
+
+public:
+ static void *operator new(size_t size) throw ()
+ { return sql_alloc(size); }
+ static void *operator new(size_t size, MEM_ROOT *mem_root) throw ()
+ { return alloc_root(mem_root, size); }
+ static void operator delete(void *ptr,size_t size) { TRASH(ptr, size); }
+ static void operator delete(void *ptr, MEM_ROOT *mem_root) {}
+
+ enum Type {FIELD_ITEM= 0, FUNC_ITEM, SUM_FUNC_ITEM, STRING_ITEM,
+ INT_ITEM, REAL_ITEM, NULL_ITEM, VARBIN_ITEM,
+ COPY_STR_ITEM, FIELD_AVG_ITEM, DEFAULT_VALUE_ITEM,
+ PROC_ITEM,COND_ITEM, REF_ITEM, FIELD_STD_ITEM,
+ FIELD_VARIANCE_ITEM, INSERT_VALUE_ITEM,
+ SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER,
+ PARAM_ITEM, TRIGGER_FIELD_ITEM, DECIMAL_ITEM,
+ XPATH_NODESET, XPATH_NODESET_CMP,
+ VIEW_FIXER_ITEM, EXPR_CACHE_ITEM,
+ DATE_ITEM};
+
+ enum cond_result { COND_UNDEF,COND_OK,COND_TRUE,COND_FALSE };
+
+ enum traverse_order { POSTFIX, PREFIX };
+
+ /* Cache of the result of is_expensive(). */
+ int8 is_expensive_cache;
+
+ /* Reuse size, only used by SP local variable assignment, otherwize 0 */
+ uint rsize;
+
+protected:
+ /*
+ str_values's main purpose is to be used to cache the value in
+ save_in_field
+ */
+ String str_value;
+
+public:
+ /*
+ Cache val_str() into the own buffer, e.g. to evaluate constant
+ expressions with subqueries in the ORDER/GROUP clauses.
+ */
+ String *val_str() { return val_str(&str_value); }
+
+ char * name; /* Name from select */
+ /* Original item name (if it was renamed)*/
+ char * orig_name;
+ /**
+ Intrusive list pointer for free list. If not null, points to the next
+ Item on some Query_arena's free list. For instance, stored procedures
+ have their own Query_arena's.
+
+ @see Query_arena::free_list
+ */
+ Item *next;
+ /*
+ The maximum value length in characters multiplied by collation->mbmaxlen.
+ Almost always it's the maximum value length in bytes.
+ */
+ uint32 max_length;
+ /*
+ TODO: convert name and name_length fields into LEX_STRING to keep them in
+ sync (see bug #11829681/60295 etc). Then also remove some strlen(name)
+ calls.
+ */
+ uint name_length; /* Length of name */
+ uint decimals;
+ int8 marker;
+ bool maybe_null; /* If item may be null */
+ bool in_rollup; /* If used in GROUP BY list
+ of a query with ROLLUP */
+ bool null_value; /* if item is null */
+ bool unsigned_flag;
+ bool with_sum_func; /* True if item contains a sum func */
+ /**
+ True if any item except Item_sum_func contains a field. Set during parsing.
+ */
+ bool with_field;
+ bool fixed; /* If item fixed with fix_fields */
+ bool is_autogenerated_name; /* indicate was name of this Item
+ autogenerated or set by user */
+ bool with_subselect; /* If this item is a subselect or some
+ of its arguments is or contains a
+ subselect */
+ DTCollation collation;
+ Item_result cmp_context; /* Comparison context */
+ // alloc & destruct is done as start of select using sql_alloc
+ Item();
+ /*
+ Constructor used by Item_field, Item_ref & aggregate (sum) functions.
+ Used for duplicating lists in processing queries with temporary
+ tables
+ Also it used for Item_cond_and/Item_cond_or for creating
+ top AND/OR structure of WHERE clause to protect it of
+ optimisation changes in prepared statements
+ */
+ Item(THD *thd, Item *item);
+ virtual ~Item()
+ {
+#ifdef EXTRA_DEBUG
+ name=0;
+#endif
+ } /*lint -e1509 */
+ void set_name(const char *str, uint length, CHARSET_INFO *cs);
+ void set_name_no_truncate(const char *str, uint length, CHARSET_INFO *cs);
+ void set_name_for_rollback(THD *thd, const char *str, uint length,
+ CHARSET_INFO *cs);
+ void rename(char *new_name);
+ void init_make_field(Send_field *tmp_field,enum enum_field_types type);
+ virtual void cleanup();
+ virtual void make_field(Send_field *field);
+ virtual Field *make_string_field(TABLE *table);
+ virtual bool fix_fields(THD *, Item **);
+ /*
+ Fix after some tables has been pulled out. Basically re-calculate all
+ attributes that are dependent on the tables.
+ */
+ virtual void fix_after_pullout(st_select_lex *new_parent, Item **ref) {};
+
+ /*
+ This method should be used in case where we are sure that we do not need
+ complete fix_fields() procedure.
+ Usually this method is used by the optimizer when it has to create a new
+ item out of other already fixed items. For example, if the optimizer has
+ to create a new Item_func for an inferred equality whose left and right
+ parts are already fixed items. In some cases the optimizer cannot use
+ directly fixed items as the arguments of the created functional item,
+ but rather uses intermediate type conversion items. Then the method is
+ supposed to be applied recursively.
+ */
+ virtual inline void quick_fix_field() { fixed= 1; }
+ /* Function returns 1 on overflow and -1 on fatal errors */
+ int save_in_field_no_warnings(Field *field, bool no_conversions);
+ virtual int save_in_field(Field *field, bool no_conversions);
+ virtual void save_org_in_field(Field *field,
+ fast_field_copier data
+ __attribute__ ((__unused__)))
+ { (void) save_in_field(field, 1); }
+ virtual fast_field_copier setup_fast_field_copier(Field *field)
+ { return NULL; }
+ virtual int save_safe_in_field(Field *field)
+ { return save_in_field(field, 1); }
+ virtual bool send(Protocol *protocol, String *str);
+ virtual bool eq(const Item *, bool binary_cmp) const;
+ /* result_type() of an item specifies how the value should be returned */
+ virtual Item_result result_type() const { return REAL_RESULT; }
+ /* ... while cmp_type() specifies how it should be compared */
+ virtual Item_result cmp_type() const;
+ virtual Item_result cast_to_int_type() const { return cmp_type(); }
+ virtual enum_field_types string_field_type() const;
+ virtual enum_field_types field_type() const;
+ virtual enum Type type() const =0;
+ /*
+ real_type() is the type of base item. This is same as type() for
+ most items, except Item_ref() and Item_cache_wrapper() where it
+ shows the type for the underlaying item.
+ */
+ virtual enum Type real_type() const { return type(); }
+
+ /*
+ Return information about function monotonicity. See comment for
+ enum_monotonicity_info for details. This function can only be called
+ after fix_fields() call.
+ */
+ virtual enum_monotonicity_info get_monotonicity_info() const
+ { return NON_MONOTONIC; }
+
+ /*
+ Convert "func_arg $CMP$ const" half-interval into "FUNC(func_arg) $CMP2$ const2"
+
+ SYNOPSIS
+ val_int_endpoint()
+ left_endp FALSE <=> The interval is "x < const" or "x <= const"
+ TRUE <=> The interval is "x > const" or "x >= const"
+
+ incl_endp IN FALSE <=> the comparison is '<' or '>'
+ TRUE <=> the comparison is '<=' or '>='
+ OUT The same but for the "F(x) $CMP$ F(const)" comparison
+
+ DESCRIPTION
+ This function is defined only for unary monotonic functions. The caller
+ supplies the source half-interval
+
+ x $CMP$ const
+
+ The value of const is supplied implicitly as the value this item's
+ argument, the form of $CMP$ comparison is specified through the
+ function's arguments. The calle returns the result interval
+
+ F(x) $CMP2$ F(const)
+
+ passing back F(const) as the return value, and the form of $CMP2$
+ through the out parameter. NULL values are assumed to be comparable and
+ be less than any non-NULL values.
+
+ RETURN
+ The output range bound, which equal to the value of val_int()
+ - If the value of the function is NULL then the bound is the
+ smallest possible value of LONGLONG_MIN
+ */
+ virtual longlong val_int_endpoint(bool left_endp, bool *incl_endp)
+ { DBUG_ASSERT(0); return 0; }
+
+
+ /* valXXX methods must return NULL or 0 or 0.0 if null_value is set. */
+ /*
+ Return double precision floating point representation of item.
+
+ SYNOPSIS
+ val_real()
+
+ RETURN
+ In case of NULL value return 0.0 and set null_value flag to TRUE.
+ If value is not null null_value flag will be reset to FALSE.
+ */
+ virtual double val_real()=0;
+ /*
+ Return integer representation of item.
+
+ SYNOPSIS
+ val_int()
+
+ RETURN
+ In case of NULL value return 0 and set null_value flag to TRUE.
+ If value is not null null_value flag will be reset to FALSE.
+ */
+ virtual longlong val_int()=0;
+ /*
+ This is just a shortcut to avoid the cast. You should still use
+ unsigned_flag to check the sign of the item.
+ */
+ inline ulonglong val_uint() { return (ulonglong) val_int(); }
+ /*
+ Return string representation of this item object.
+
+ SYNOPSIS
+ val_str()
+ str an allocated buffer this or any nested Item object can use to
+ store return value of this method.
+
+ NOTE
+ Buffer passed via argument should only be used if the item itself
+ doesn't have an own String buffer. In case when the item maintains
+ it's own string buffer, it's preferable to return it instead to
+ minimize number of mallocs/memcpys.
+ The caller of this method can modify returned string, but only in case
+ when it was allocated on heap, (is_alloced() is true). This allows
+ the caller to efficiently use a buffer allocated by a child without
+ having to allocate a buffer of it's own. The buffer, given to
+ val_str() as argument, belongs to the caller and is later used by the
+ caller at it's own choosing.
+ A few implications from the above:
+ - unless you return a string object which only points to your buffer
+ but doesn't manages it you should be ready that it will be
+ modified.
+ - even for not allocated strings (is_alloced() == false) the caller
+ can change charset (see Item_func_{typecast/binary}. XXX: is this
+ a bug?
+ - still you should try to minimize data copying and return internal
+ object whenever possible.
+
+ RETURN
+ In case of NULL value return 0 (NULL pointer) and set null_value flag
+ to TRUE.
+ If value is not null null_value flag will be reset to FALSE.
+ */
+ virtual String *val_str(String *str)=0;
+
+ /*
+ Returns string representation of this item in ASCII format.
+
+ SYNOPSIS
+ val_str_ascii()
+ str - similar to val_str();
+
+ NOTE
+ This method is introduced for performance optimization purposes.
+
+ 1. val_str() result of some Items in string context
+ depends on @@character_set_results.
+ @@character_set_results can be set to a "real multibyte" character
+ set like UCS2, UTF16, UTF32. (We'll use only UTF32 in the examples
+ below for convenience.)
+
+ So the default string result of such functions
+ in these circumstances is real multi-byte character set, like UTF32.
+
+ For example, all numbers in string context
+ return result in @@character_set_results:
+
+ SELECT CONCAT(20010101); -> UTF32
+
+ We do sprintf() first (to get ASCII representation)
+ and then convert to UTF32;
+
+ So these kind "data sources" can use ASCII representation
+ internally, but return multi-byte data only because
+ @@character_set_results wants so.
+ Therefore, conversion from ASCII to UTF32 is applied internally.
+
+
+ 2. Some other functions need in fact ASCII input.
+
+ For example,
+ inet_aton(), GeometryFromText(), Convert_TZ(), GET_FORMAT().
+
+ Similar, fields of certain type, like DATE, TIME,
+ when you insert string data into them, expect in fact ASCII input.
+ If they get non-ASCII input, for example UTF32, they
+ convert input from UTF32 to ASCII, and then use ASCII
+ representation to do further processing.
+
+
+ 3. Now imagine we pass result of a data source of the first type
+ to a data destination of the second type.
+
+ What happens:
+ a. data source converts data from ASCII to UTF32, because
+ @@character_set_results wants so and passes the result to
+ data destination.
+ b. data destination gets UTF32 string.
+ c. data destination converts UTF32 string to ASCII,
+ because it needs ASCII representation to be able to handle data
+ correctly.
+
+ As a result we get two steps of unnecessary conversion:
+ From ASCII to UTF32, then from UTF32 to ASCII.
+
+ A better way to handle these situations is to pass ASCII
+ representation directly from the source to the destination.
+
+ This is why val_str_ascii() introduced.
+
+ RETURN
+ Similar to val_str()
+ */
+ virtual String *val_str_ascii(String *str);
+
+ /*
+ Returns the val_str() value converted to the given character set.
+ */
+ String *val_str(String *str, String *converter, CHARSET_INFO *to);
+ /*
+ Return decimal representation of item with fixed point.
+
+ SYNOPSIS
+ val_decimal()
+ decimal_buffer buffer which can be used by Item for returning value
+ (but can be not)
+
+ NOTE
+ Returned value should not be changed if it is not the same which was
+ passed via argument.
+
+ RETURN
+ Return pointer on my_decimal (it can be other then passed via argument)
+ if value is not NULL (null_value flag will be reset to FALSE).
+ In case of NULL value it return 0 pointer and set null_value flag
+ to TRUE.
+ */
+ virtual my_decimal *val_decimal(my_decimal *decimal_buffer)= 0;
+ /*
+ Return boolean value of item.
+
+ RETURN
+ FALSE value is false or NULL
+ TRUE value is true (not equal to 0)
+ */
+ virtual bool val_bool();
+ virtual String *val_nodeset(String*) { return 0; }
+
+ /*
+ save_val() is method of val_* family which stores value in the given
+ field.
+ */
+ virtual void save_val(Field *to) { save_org_in_field(to, NULL); }
+ /*
+ save_result() is method of val*result() family which stores value in
+ the given field.
+ */
+ virtual void save_result(Field *to) { save_val(to); }
+ /* Helper functions, see item_sum.cc */
+ String *val_string_from_real(String *str);
+ String *val_string_from_int(String *str);
+ String *val_string_from_decimal(String *str);
+ String *val_string_from_date(String *str);
+ my_decimal *val_decimal_from_real(my_decimal *decimal_value);
+ my_decimal *val_decimal_from_int(my_decimal *decimal_value);
+ my_decimal *val_decimal_from_string(my_decimal *decimal_value);
+ my_decimal *val_decimal_from_date(my_decimal *decimal_value);
+ my_decimal *val_decimal_from_time(my_decimal *decimal_value);
+ longlong val_int_from_decimal();
+ longlong val_int_from_date();
+ double val_real_from_decimal();
+ double val_real_from_date();
+
+ // Get TIME, DATE or DATETIME using proper sql_mode flags for the field type
+ bool get_temporal_with_sql_mode(MYSQL_TIME *ltime);
+ // Check NULL value for a TIME, DATE or DATETIME expression
+ bool is_null_from_temporal();
+
+ int save_time_in_field(Field *field);
+ int save_date_in_field(Field *field);
+ int save_str_value_in_field(Field *field, String *result);
+
+ virtual Field *get_tmp_table_field() { return 0; }
+ /* This is also used to create fields in CREATE ... SELECT: */
+ virtual Field *tmp_table_field(TABLE *t_arg) { return 0; }
+ virtual const char *full_name() const { return name ? name : "???"; }
+ const char *field_name_or_null()
+ { return real_item()->type() == Item::FIELD_ITEM ? name : NULL; }
+
+ /*
+ *result* family of methods is analog of *val* family (see above) but
+ return value of result_field of item if it is present. If Item have not
+ result field, it return val(). This methods set null_value flag in same
+ way as *val* methods do it.
+ */
+ virtual double val_result() { return val_real(); }
+ virtual longlong val_int_result() { return val_int(); }
+ virtual String *str_result(String* tmp) { return val_str(tmp); }
+ virtual my_decimal *val_decimal_result(my_decimal *val)
+ { return val_decimal(val); }
+ virtual bool val_bool_result() { return val_bool(); }
+ virtual bool is_null_result() { return is_null(); }
+ /*
+ Returns 1 if result type and collation for val_str() can change between
+ calls
+ */
+ virtual bool dynamic_result() { return 0; }
+ /*
+ Bitmap of tables used by item
+ (note: if you need to check dependencies on individual columns, check out
+ class Field_enumerator)
+ */
+ virtual table_map used_tables() const { return (table_map) 0L; }
+ virtual table_map all_used_tables() const { return used_tables(); }
+ /*
+ Return table map of tables that can't be NULL tables (tables that are
+ used in a context where if they would contain a NULL row generated
+ by a LEFT or RIGHT join, the item would not be true).
+ This expression is used on WHERE item to determinate if a LEFT JOIN can be
+ converted to a normal join.
+ Generally this function should return used_tables() if the function
+ would return null if any of the arguments are null
+ As this is only used in the beginning of optimization, the value don't
+ have to be updated in update_used_tables()
+ */
+ virtual table_map not_null_tables() const { return used_tables(); }
+ /*
+ Returns true if this is a simple constant item like an integer, not
+ a constant expression. Used in the optimizer to propagate basic constants.
+ */
+ virtual bool basic_const_item() const { return 0; }
+ /* cloning of constant items (0 if it is not const) */
+ virtual Item *clone_item() { return 0; }
+ virtual cond_result eq_cmp_result() const { return COND_OK; }
+ inline uint float_length(uint decimals_par) const
+ { return decimals != NOT_FIXED_DEC ? (DBL_DIG+2+decimals_par) : DBL_DIG+8;}
+ /* Returns total number of decimal digits */
+ virtual uint decimal_precision() const;
+ /* Returns the number of integer part digits only */
+ inline int decimal_int_part() const
+ { return my_decimal_int_part(decimal_precision(), decimals); }
+ /*
+ Returns the number of fractional digits only.
+ NOT_FIXED_DEC is replaced to the maximum possible number
+ of fractional digits, taking into account the data type.
+ */
+ uint decimal_scale() const
+ {
+ return decimals < NOT_FIXED_DEC ? decimals :
+ is_temporal_type_with_time(field_type()) ?
+ TIME_SECOND_PART_DIGITS :
+ MY_MIN(max_length, DECIMAL_MAX_SCALE);
+ }
+ /*
+ Returns how many digits a divisor adds into a division result.
+ This is important when the integer part of the divisor can be 0.
+ In this example:
+ SELECT 1 / 0.000001; -> 1000000.0000
+ the divisor adds 5 digits into the result precision.
+
+ Currently this method only replaces NOT_FIXED_DEC to
+ TIME_SECOND_PART_DIGITS for temporal data types.
+ This method can be made virtual, to create more efficient (smaller)
+ data types for division results.
+ For example, in
+ SELECT 1/1.000001;
+ the divisor could provide no additional precision into the result,
+ so could any other items that are know to return a result
+ with non-zero integer part.
+ */
+ uint divisor_precision_increment() const
+ {
+ return decimals < NOT_FIXED_DEC ? decimals :
+ is_temporal_type_with_time(field_type()) ?
+ TIME_SECOND_PART_DIGITS :
+ decimals;
+ }
+ /**
+ TIME or DATETIME precision of the item: 0..6
+ */
+ uint temporal_precision(enum_field_types type);
+ /*
+ Returns true if this is constant (during query execution, i.e. its value
+ will not change until next fix_fields) and its value is known.
+ */
+ virtual bool const_item() const { return used_tables() == 0; }
+ /*
+ Returns true if this is constant but its value may be not known yet.
+ (Can be used for parameters of prep. stmts or of stored procedures.)
+ */
+ virtual bool const_during_execution() const
+ { return (used_tables() & ~PARAM_TABLE_BIT) == 0; }
+
+ /**
+ This method is used for to:
+ - to generate a view definition query (SELECT-statement);
+ - to generate a SQL-query for EXPLAIN EXTENDED;
+ - to generate a SQL-query to be shown in INFORMATION_SCHEMA;
+ - debug.
+
+ For more information about view definition query, INFORMATION_SCHEMA
+ query and why they should be generated from the Item-tree, @see
+ mysql_register_view().
+ */
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ str->append(full_name());
+ }
+
+ void print_item_w_name(String *, enum_query_type query_type);
+ void print_value(String *);
+ virtual void update_used_tables() {}
+ virtual void split_sum_func(THD *thd, Item **ref_pointer_array,
+ List<Item> &fields) {}
+ /* Called for items that really have to be split */
+ void split_sum_func2(THD *thd, Item **ref_pointer_array, List<Item> &fields,
+ Item **ref, bool skip_registered);
+ virtual bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ bool get_time(MYSQL_TIME *ltime)
+ { return get_date(ltime, TIME_TIME_ONLY | TIME_INVALID_DATES); }
+ // Get date with automatic TIME->DATETIME conversion
+ bool get_date_with_conversion(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ bool get_seconds(ulonglong *sec, ulong *sec_part);
+ virtual bool get_date_result(MYSQL_TIME *ltime, ulonglong fuzzydate)
+ { return get_date(ltime,fuzzydate); }
+ /*
+ The method allows to determine nullness of a complex expression
+ without fully evaluating it, instead of calling val/result*() then
+ checking null_value. Used in Item_func_isnull/Item_func_isnotnull
+ and Item_sum_count/Item_sum_count_distinct.
+ Any new item which can be NULL must implement this method.
+ */
+ virtual bool is_null() { return 0; }
+
+ /*
+ Make sure the null_value member has a correct value.
+ */
+ virtual void update_null_value () { (void) val_int(); }
+
+ /*
+ Inform the item that there will be no distinction between its result
+ being FALSE or NULL.
+
+ NOTE
+ This function will be called for eg. Items that are top-level AND-parts
+ of the WHERE clause. Items implementing this function (currently
+ Item_cond_and and subquery-related item) enable special optimizations
+ when they are "top level".
+ */
+ virtual void top_level_item() {}
+ /*
+ set field of temporary table for Item which can be switched on temporary
+ table during query processing (grouping and so on)
+ */
+ virtual void set_result_field(Field *field) {}
+ virtual bool is_result_field() { return 0; }
+ virtual bool is_bool_func() { return 0; }
+ virtual void save_in_result_field(bool no_conversions) {}
+ /*
+ set value of aggregate function in case of no rows for grouping were found
+ */
+ virtual void no_rows_in_result() {}
+ virtual void restore_to_before_no_rows_in_result() {}
+ virtual Item *copy_or_same(THD *thd) { return this; }
+ virtual Item *copy_andor_structure(THD *thd) { return this; }
+ virtual Item *real_item() { return this; }
+ virtual Item *get_tmp_table_item(THD *thd) { return copy_or_same(thd); }
+
+ static CHARSET_INFO *default_charset();
+ virtual CHARSET_INFO *compare_collation() { return NULL; }
+
+ /*
+ For backward compatibility, to make numeric
+ data types return "binary" charset in client-side metadata.
+ */
+ virtual CHARSET_INFO *charset_for_protocol(void) const
+ {
+ return cmp_type() == STRING_RESULT ? collation.collation :
+ &my_charset_bin;
+ };
+
+ virtual bool walk(Item_processor processor, bool walk_subquery, uchar *arg)
+ {
+ return (this->*processor)(arg);
+ }
+
+ virtual bool walk_top_and(Item_processor processor, uchar *arg)
+ {
+ return (this->*processor)(arg);
+ }
+
+ virtual Item* transform(Item_transformer transformer, uchar *arg);
+
+ /*
+ This function performs a generic "compilation" of the Item tree.
+ The process of compilation is assumed to go as follows:
+
+ compile()
+ {
+ if (this->*some_analyzer(...))
+ {
+ compile children if any;
+ this->*some_transformer(...);
+ }
+ }
+
+ i.e. analysis is performed top-down while transformation is done
+ bottom-up.
+ */
+ virtual Item* compile(Item_analyzer analyzer, uchar **arg_p,
+ Item_transformer transformer, uchar *arg_t)
+ {
+ if ((this->*analyzer) (arg_p))
+ return ((this->*transformer) (arg_t));
+ return 0;
+ }
+
+ virtual void traverse_cond(Cond_traverser traverser,
+ void *arg, traverse_order order)
+ {
+ (*traverser)(this, arg);
+ }
+
+ /*
+ This is used to get the most recent version of any function in
+ an item tree. The version is the version where a MySQL function
+ was introduced in. So any function which is added should use
+ this function and set the int_arg to maximum of the input data
+ and their own version info.
+ */
+ virtual bool intro_version(uchar *int_arg) { return 0; }
+
+ virtual bool remove_dependence_processor(uchar * arg) { return 0; }
+ virtual bool cleanup_processor(uchar *arg);
+ virtual bool collect_item_field_processor(uchar * arg) { return 0; }
+ virtual bool add_field_to_set_processor(uchar * arg) { return 0; }
+ virtual bool find_item_in_field_list_processor(uchar *arg) { return 0; }
+ virtual bool find_item_processor(uchar *arg);
+ virtual bool change_context_processor(uchar *context) { return 0; }
+ virtual bool reset_query_id_processor(uchar *query_id_arg) { return 0; }
+ virtual bool is_expensive_processor(uchar *arg) { return 0; }
+ virtual bool register_field_in_read_map(uchar *arg) { return 0; }
+ virtual bool register_field_in_write_map(uchar *arg) { return 0; }
+ virtual bool enumerate_field_refs_processor(uchar *arg) { return 0; }
+ virtual bool mark_as_eliminated_processor(uchar *arg) { return 0; }
+ virtual bool eliminate_subselect_processor(uchar *arg) { return 0; }
+ virtual bool set_fake_select_as_master_processor(uchar *arg) { return 0; }
+ virtual bool update_table_bitmaps_processor(uchar *arg) { return 0; }
+ virtual bool view_used_tables_processor(uchar *arg) { return 0; }
+ virtual bool eval_not_null_tables(uchar *opt_arg) { return 0; }
+ virtual bool is_subquery_processor (uchar *opt_arg) { return 0; }
+ virtual bool count_sargable_conds(uchar *arg) { return 0; }
+ virtual bool limit_index_condition_pushdown_processor(uchar *opt_arg)
+ {
+ return FALSE;
+ }
+ virtual bool exists2in_processor(uchar *opt_arg) { return 0; }
+ virtual bool find_selective_predicates_list_processor(uchar *opt_arg)
+ { return 0; }
+
+ /* To call bool function for all arguments */
+ struct bool_func_call_args
+ {
+ Item *original_func_item;
+ void (Item::*bool_function)();
+ };
+ bool call_bool_func_processor(uchar *org_item)
+ {
+ bool_func_call_args *info= (bool_func_call_args*) org_item;
+ /* Avoid recursion, as walk also calls for original item */
+ if (info->original_func_item != this)
+ (this->*(info->bool_function))();
+ return FALSE;
+ }
+
+
+ /*
+ The next function differs from the previous one that a bitmap to be updated
+ is passed as uchar *arg.
+ */
+ virtual bool register_field_in_bitmap(uchar *arg) { return 0; }
+
+ bool cache_const_expr_analyzer(uchar **arg);
+ Item* cache_const_expr_transformer(uchar *arg);
+
+ /*
+ Check if a partition function is allowed
+ SYNOPSIS
+ check_partition_func_processor()
+ int_arg Ignored
+ RETURN VALUE
+ TRUE Partition function not accepted
+ FALSE Partition function accepted
+
+ DESCRIPTION
+ check_partition_func_processor is used to check if a partition function
+ uses an allowed function. An allowed function will always ensure that
+ X=Y guarantees that also part_function(X)=part_function(Y) where X is
+ a set of partition fields and so is Y. The problems comes mainly from
+ character sets where two equal strings can be quite unequal. E.g. the
+ german character for double s is equal to 2 s.
+
+ The default is that an item is not allowed
+ in a partition function. Allowed functions
+ can never depend on server version, they cannot depend on anything
+ related to the environment. They can also only depend on a set of
+ fields in the table itself. They cannot depend on other tables and
+ cannot contain any queries and cannot contain udf's or similar.
+ If a new Item class is defined and it inherits from a class that is
+ allowed in a partition function then it is very important to consider
+ whether this should be inherited to the new class. If not the function
+ below should be defined in the new Item class.
+
+ The general behaviour is that most integer functions are allowed.
+ If the partition function contains any multi-byte collations then
+ the function check_part_func_fields will report an error on the
+ partition function independent of what functions are used. So the
+ only character sets allowed are single character collation and
+ even for those only a limited set of functions are allowed. The
+ problem with multi-byte collations is that almost every string
+ function has the ability to change things such that two strings
+ that are equal will not be equal after manipulated by a string
+ function. E.g. two strings one contains a double s, there is a
+ special german character that is equal to two s. Now assume a
+ string function removes one character at this place, then in
+ one the double s will be removed and in the other there will
+ still be one s remaining and the strings are no longer equal
+ and thus the partition function will not sort equal strings into
+ the same partitions.
+
+ So the check if a partition function is valid is two steps. First
+ check that the field types are valid, next check that the partition
+ function is valid. The current set of partition functions valid
+ assumes that there are no multi-byte collations amongst the partition
+ fields.
+ */
+ virtual bool check_partition_func_processor(uchar *bool_arg) { return TRUE;}
+ /*
+ @brief
+ Processor used to mark virtual columns used in partitioning expression
+
+ @param
+ arg always ignored
+
+ @retval
+ FALSE always
+ */
+ virtual bool vcol_in_partition_func_processor(uchar *arg)
+ {
+ return FALSE;
+ }
+
+ /*
+ The enumeration Subst_constraint is currently used only in implementations
+ of the virtual function subst_argument_checker.
+ */
+ enum Subst_constraint
+ {
+ NO_SUBST= 0, /* No substitution for a field is allowed */
+ ANY_SUBST, /* Any substitution for a field is allowed */
+ IDENTITY_SUBST /* Substitution for a field is allowed if any two
+ different values of the field type are not equal */
+ };
+
+ virtual bool subst_argument_checker(uchar **arg)
+ {
+ return (*arg != NULL);
+ }
+
+ /*
+ @brief
+ Processor used to check acceptability of an item in the defining
+ expression for a virtual column
+
+ @param
+ arg always ignored
+
+ @retval
+ FALSE the item is accepted in the definition of a virtual column
+ @retval
+ TRUE otherwise
+ */
+ virtual bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(full_name());
+ }
+
+ virtual Item *equal_fields_propagator(uchar * arg) { return this; }
+ virtual bool set_no_const_sub(uchar *arg) { return FALSE; }
+ /* arg points to REPLACE_EQUAL_FIELD_ARG object */
+ virtual Item *replace_equal_field(uchar * arg) { return this; }
+ /*
+ Check if an expression value has allowed arguments, like DATE/DATETIME
+ for date functions. Also used by partitioning code to reject
+ timezone-dependent expressions in a (sub)partitioning function.
+ */
+ virtual bool check_valid_arguments_processor(uchar *bool_arg)
+ {
+ return FALSE;
+ }
+ struct Collect_deps_prm
+ {
+ List<Item> *parameters;
+ /* unit from which we count nest_level */
+ st_select_lex_unit *nest_level_base;
+ uint count;
+ int nest_level;
+ bool collect;
+ };
+ /**
+ Collect outer references
+ */
+ virtual bool collect_outer_ref_processor(uchar *arg) {return FALSE; }
+
+ /**
+ Find a function of a given type
+
+ @param arg the function type to search (enum Item_func::Functype)
+ @return
+ @retval TRUE the function type we're searching for is found
+ @retval FALSE the function type wasn't found
+
+ @description
+ This function can be used (together with Item::walk()) to find functions
+ in an item tree fragment.
+ */
+ virtual bool find_function_processor (uchar *arg)
+ {
+ return FALSE;
+ }
+
+ virtual bool check_inner_refs_processor(uchar *arg) { return FALSE; }
+
+ /*
+ For SP local variable returns pointer to Item representing its
+ current value and pointer to current Item otherwise.
+ */
+ virtual Item *this_item() { return this; }
+ virtual const Item *this_item() const { return this; }
+
+ /*
+ For SP local variable returns address of pointer to Item representing its
+ current value and pointer passed via parameter otherwise.
+ */
+ virtual Item **this_item_addr(THD *thd, Item **addr_arg) { return addr_arg; }
+
+ // Row emulation
+ virtual uint cols() { return 1; }
+ virtual Item* element_index(uint i) { return this; }
+ virtual Item** addr(uint i) { return 0; }
+ virtual bool check_cols(uint c);
+ // It is not row => null inside is impossible
+ virtual bool null_inside() { return 0; }
+ // used in row subselects to get value of elements
+ virtual void bring_value() {}
+
+ Field *tmp_table_field_from_field_type(TABLE *table, bool fixed_length);
+ virtual Item_field *field_for_view_update() { return 0; }
+
+ virtual Item *neg_transformer(THD *thd) { return NULL; }
+ virtual Item *update_value_transformer(uchar *select_arg) { return this; }
+ virtual Item *expr_cache_insert_transformer(uchar *thd_arg) { return this; }
+ virtual bool expr_cache_is_needed(THD *) { return FALSE; }
+ virtual Item *safe_charset_converter(CHARSET_INFO *tocs);
+ bool needs_charset_converter(uint32 length, CHARSET_INFO *tocs)
+ {
+ /*
+ This will return "true" if conversion happens:
+ - between two non-binary different character sets
+ - from "binary" to "unsafe" character set
+ (those that can have non-well-formed string)
+ - from "binary" to UCS2-alike character set with mbminlen>1,
+ when prefix left-padding is needed for an incomplete character:
+ binary 0xFF -> ucs2 0x00FF)
+ */
+ if (!String::needs_conversion_on_storage(length,
+ collation.collation, tocs))
+ return false;
+ /*
+ No needs to add converter if an "arg" is NUMERIC or DATETIME
+ value (which is pure ASCII) and at the same time target DTCollation
+ is ASCII-compatible. For example, no needs to rewrite:
+ SELECT * FROM t1 WHERE datetime_field = '2010-01-01';
+ to
+ SELECT * FROM t1 WHERE CONVERT(datetime_field USING cs) = '2010-01-01';
+
+ TODO: avoid conversion of any values with
+ repertoire ASCII and 7bit-ASCII-compatible,
+ not only numeric/datetime origin.
+ */
+ if (collation.derivation == DERIVATION_NUMERIC &&
+ collation.repertoire == MY_REPERTOIRE_ASCII &&
+ !(collation.collation->state & MY_CS_NONASCII) &&
+ !(tocs->state & MY_CS_NONASCII))
+ return false;
+ return true;
+ }
+ bool needs_charset_converter(CHARSET_INFO *tocs)
+ {
+ // Pass 1 as length to force conversion if tocs->mbminlen>1.
+ return needs_charset_converter(1, tocs);
+ }
+ Item *const_charset_converter(CHARSET_INFO *tocs, bool lossless,
+ const char *func_name);
+ Item *const_charset_converter(CHARSET_INFO *tocs, bool lossless)
+ { return const_charset_converter(tocs, lossless, NULL); }
+ void delete_self()
+ {
+ cleanup();
+ delete this;
+ }
+
+ virtual bool is_splocal() { return 0; } /* Needed for error checking */
+
+ /*
+ Return Settable_routine_parameter interface of the Item. Return 0
+ if this Item is not Settable_routine_parameter.
+ */
+ virtual Settable_routine_parameter *get_settable_routine_parameter()
+ {
+ return 0;
+ }
+ /**
+ Check whether this and the given item has compatible comparison context.
+ Used by the equality propagation. See Item_field::equal_fields_propagator.
+
+ @return
+ TRUE if the context is the same
+ FALSE otherwise.
+ */
+ inline bool has_compatible_context(Item *item) const
+ {
+ return cmp_context == IMPOSSIBLE_RESULT || item->cmp_context == cmp_context;
+ }
+ /**
+ Test whether an expression is expensive to compute. Used during
+ optimization to avoid computing expensive expressions during this
+ phase. Also used to force temp tables when sorting on expensive
+ functions.
+ @todo
+ Normally we should have a method:
+ cost Item::execution_cost(),
+ where 'cost' is either 'double' or some structure of various cost
+ parameters.
+
+ @note
+ This function is now used to prevent evaluation of expensive subquery
+ predicates during the optimization phase. It also prevents evaluation
+ of predicates that are not computable at this moment.
+ */
+ virtual bool is_expensive()
+ {
+ if (is_expensive_cache < 0)
+ is_expensive_cache= walk(&Item::is_expensive_processor, 0, (uchar*)0);
+ return MY_TEST(is_expensive_cache);
+ }
+ virtual Field::geometry_type get_geometry_type() const
+ { return Field::GEOM_GEOMETRY; };
+ String *check_well_formed_result(String *str, bool send_error= 0);
+ bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs);
+ uint32 max_char_length() const
+ { return max_length / collation.collation->mbmaxlen; }
+ bool too_big_for_varchar() const
+ { return max_char_length() > CONVERT_IF_BIGGER_TO_BLOB; }
+ void fix_length_and_charset(uint32 max_char_length_arg, CHARSET_INFO *cs)
+ {
+ max_length= char_to_byte_length_safe(max_char_length_arg, cs->mbmaxlen);
+ collation.collation= cs;
+ }
+ void fix_char_length(uint32 max_char_length_arg)
+ {
+ max_length= char_to_byte_length_safe(max_char_length_arg,
+ collation.collation->mbmaxlen);
+ }
+ /*
+ Return TRUE if the item points to a column of an outer-joined table.
+ */
+ virtual bool is_outer_field() const { DBUG_ASSERT(fixed); return FALSE; }
+
+ /**
+ Checks if this item or any of its decendents contains a subquery.
+ */
+ virtual bool has_subquery() const { return with_subselect; }
+
+ Item* set_expr_cache(THD *thd);
+
+ virtual Item_equal *get_item_equal() { return NULL; }
+ virtual void set_item_equal(Item_equal *item_eq) {};
+ virtual Item_equal *find_item_equal(COND_EQUAL *cond_equal) { return NULL; }
+ /**
+ Set the join tab index to the minimal (left-most) JOIN_TAB to which this
+ Item is attached. The number is an index is depth_first_tab() traversal
+ order.
+ */
+ virtual void set_join_tab_idx(uint join_tab_idx_arg)
+ {
+ if (join_tab_idx_arg < join_tab_idx)
+ join_tab_idx= join_tab_idx_arg;
+ }
+ virtual uint get_join_tab_idx() { return join_tab_idx; }
+
+ table_map view_used_tables(TABLE_LIST *view)
+ {
+ view->view_used_tables= 0;
+ walk(&Item::view_used_tables_processor, 0, (uchar *) view);
+ return view->view_used_tables;
+ }
+
+ /**
+ Collect and add to the list cache parameters for this Item.
+
+ @note Now implemented only for subqueries and in_optimizer,
+ if we need it for general function then this method should
+ be defined for Item_func.
+ */
+ virtual void get_cache_parameters(List<Item> &parameters) { };
+
+ virtual void mark_as_condition_AND_part(TABLE_LIST *embedding) {};
+
+ /* how much position should be reserved for Exists2In transformation */
+ virtual uint exists2in_reserved_items() { return 0; };
+
+ /**
+ Inform the item that it is located under a NOT, which is a top-level item.
+ */
+ virtual void under_not(Item_func_not * upper
+ __attribute__((unused))) {};
+};
+
+
+/**
+ Compare two Items for List<Item>::add_unique()
+*/
+
+bool cmp_items(Item *a, Item *b);
+
+
+/*
+ Class to be used to enumerate all field references in an item tree. This
+ includes references to outside but not fields of the tables within a
+ subquery.
+ Suggested usage:
+
+ class My_enumerator : public Field_enumerator
+ {
+ virtual void visit_field() { ... your actions ...}
+ }
+
+ My_enumerator enumerator;
+ item->walk(Item::enumerate_field_refs_processor, ...,(uchar*)&enumerator);
+
+ This is similar to Visitor pattern.
+*/
+
+class Field_enumerator
+{
+public:
+ virtual void visit_field(Item_field *field)= 0;
+ virtual ~Field_enumerator() {}; /* purecov: inspected */
+ Field_enumerator() {} /* Remove gcc warning */
+};
+
+class sp_head;
+class Item_string;
+
+
+/**
+ A common class for Item_basic_constant and Item_param
+*/
+class Item_basic_value :public Item
+{
+ bool is_basic_value(const Item *item, Type type_arg) const
+ {
+ return item->basic_const_item() && item->type() == type_arg;
+ }
+ bool is_basic_value(Type type_arg) const
+ {
+ return basic_const_item() && type() == type_arg;
+ }
+ bool str_eq(const String *value,
+ const String *other, CHARSET_INFO *cs, bool binary_cmp) const
+ {
+ return binary_cmp ?
+ value->bin_eq(other) :
+ collation.collation == cs && value->eq(other, collation.collation);
+ }
+
+protected:
+ // Value metadata, e.g. to make string processing easier
+ class Metadata: private MY_STRING_METADATA
+ {
+ public:
+ Metadata(const String *str)
+ {
+ my_string_metadata_get(this, str->charset(), str->ptr(), str->length());
+ }
+ Metadata(const String *str, uint repertoire)
+ {
+ MY_STRING_METADATA::repertoire= repertoire;
+ MY_STRING_METADATA::char_length= str->numchars();
+ }
+ uint repertoire() const { return MY_STRING_METADATA::repertoire; }
+ size_t char_length() const { return MY_STRING_METADATA::char_length; }
+ };
+ void fix_charset_and_length_from_str_value(Derivation dv, Metadata metadata)
+ {
+ /*
+ We have to have a different max_length than 'length' here to
+ ensure that we get the right length if we do use the item
+ to create a new table. In this case max_length must be the maximum
+ number of chars for a string of this type because we in Create_field::
+ divide the max_length with mbmaxlen).
+ */
+ collation.set(str_value.charset(), dv, metadata.repertoire());
+ fix_char_length(metadata.char_length());
+ decimals= NOT_FIXED_DEC;
+ }
+ void fix_charset_and_length_from_str_value(Derivation dv)
+ {
+ fix_charset_and_length_from_str_value(dv, Metadata(&str_value));
+ }
+ Item_basic_value(): Item() {}
+ /*
+ In the xxx_eq() methods below we need to cast off "const" to
+ call val_xxx(). This is OK for Item_basic_constant and Item_param.
+ */
+ bool null_eq(const Item *item) const
+ {
+ DBUG_ASSERT(is_basic_value(NULL_ITEM));
+ return item->type() == NULL_ITEM;
+ }
+ bool str_eq(const String *value, const Item *item, bool binary_cmp) const
+ {
+ DBUG_ASSERT(is_basic_value(STRING_ITEM));
+ return is_basic_value(item, STRING_ITEM) &&
+ str_eq(value, ((Item_basic_value*)item)->val_str(NULL),
+ item->collation.collation, binary_cmp);
+ }
+ bool real_eq(double value, const Item *item) const
+ {
+ DBUG_ASSERT(is_basic_value(REAL_ITEM));
+ return is_basic_value(item, REAL_ITEM) &&
+ value == ((Item_basic_value*)item)->val_real();
+ }
+ bool int_eq(longlong value, const Item *item) const
+ {
+ DBUG_ASSERT(is_basic_value(INT_ITEM));
+ return is_basic_value(item, INT_ITEM) &&
+ value == ((Item_basic_value*)item)->val_int() &&
+ (value >= 0 || item->unsigned_flag == unsigned_flag);
+ }
+};
+
+
+class Item_basic_constant :public Item_basic_value
+{
+ table_map used_table_map;
+public:
+ Item_basic_constant(): Item_basic_value(), used_table_map(0) {};
+ void set_used_tables(table_map map) { used_table_map= map; }
+ table_map used_tables() const { return used_table_map; }
+ /* to prevent drop fixed flag (no need parent cleanup call) */
+ void cleanup()
+ {
+ /*
+ Restore the original field name as it might not have been allocated
+ in the statement memory. If the name is auto generated, it must be
+ done again between subsequent executions of a prepared statement.
+ */
+ if (orig_name)
+ name= orig_name;
+ }
+};
+
+
+/*****************************************************************************
+ The class is a base class for representation of stored routine variables in
+ the Item-hierarchy. There are the following kinds of SP-vars:
+ - local variables (Item_splocal);
+ - CASE expression (Item_case_expr);
+*****************************************************************************/
+
+class Item_sp_variable :public Item
+{
+protected:
+ /*
+ THD, which is stored in fix_fields() and is used in this_item() to avoid
+ current_thd use.
+ */
+ THD *m_thd;
+
+public:
+ LEX_STRING m_name;
+
+public:
+#ifndef DBUG_OFF
+ /*
+ Routine to which this Item_splocal belongs. Used for checking if correct
+ runtime context is used for variable handling.
+ */
+ sp_head *m_sp;
+#endif
+
+public:
+ Item_sp_variable(char *sp_var_name_str, uint sp_var_name_length);
+
+public:
+ bool fix_fields(THD *thd, Item **);
+
+ double val_real();
+ longlong val_int();
+ String *val_str(String *sp);
+ my_decimal *val_decimal(my_decimal *decimal_value);
+ bool is_null();
+
+public:
+ inline void make_field(Send_field *field);
+
+ inline bool const_item() const;
+
+ inline int save_in_field(Field *field, bool no_conversions);
+ inline bool send(Protocol *protocol, String *str);
+};
+
+/*****************************************************************************
+ Item_sp_variable inline implementation.
+*****************************************************************************/
+
+inline void Item_sp_variable::make_field(Send_field *field)
+{
+ Item *it= this_item();
+
+ if (name)
+ it->set_name(name, (uint) strlen(name), system_charset_info);
+ else
+ it->set_name(m_name.str, (uint) m_name.length, system_charset_info);
+ it->make_field(field);
+}
+
+inline bool Item_sp_variable::const_item() const
+{
+ return TRUE;
+}
+
+inline int Item_sp_variable::save_in_field(Field *field, bool no_conversions)
+{
+ return this_item()->save_in_field(field, no_conversions);
+}
+
+inline bool Item_sp_variable::send(Protocol *protocol, String *str)
+{
+ return this_item()->send(protocol, str);
+}
+
+
+/*****************************************************************************
+ A reference to local SP variable (incl. reference to SP parameter), used in
+ runtime.
+*****************************************************************************/
+
+class Item_splocal :public Item_sp_variable,
+ private Settable_routine_parameter
+{
+ uint m_var_idx;
+
+ Type m_type;
+ Item_result m_result_type;
+ enum_field_types m_field_type;
+public:
+ /*
+ If this variable is a parameter in LIMIT clause.
+ Used only during NAME_CONST substitution, to not append
+ NAME_CONST to the resulting query and thus not break
+ the slave.
+ */
+ bool limit_clause_param;
+ /*
+ Position of this reference to SP variable in the statement (the
+ statement itself is in sp_instr_stmt::m_query).
+ This is valid only for references to SP variables in statements,
+ excluding DECLARE CURSOR statement. It is used to replace references to SP
+ variables with NAME_CONST calls when putting statements into the binary
+ log.
+ Value of 0 means that this object doesn't corresponding to reference to
+ SP variable in query text.
+ */
+ uint pos_in_query;
+ /*
+ Byte length of SP variable name in the statement (see pos_in_query).
+ The value of this field may differ from the name_length value because
+ name_length contains byte length of UTF8-encoded item name, but
+ the query string (see sp_instr_stmt::m_query) is currently stored with
+ a charset from the SET NAMES statement.
+ */
+ uint len_in_query;
+
+ Item_splocal(const LEX_STRING &sp_var_name, uint sp_var_idx,
+ enum_field_types sp_var_type,
+ uint pos_in_q= 0, uint len_in_q= 0);
+
+ bool is_splocal() { return 1; } /* Needed for error checking */
+
+ Item *this_item();
+ const Item *this_item() const;
+ Item **this_item_addr(THD *thd, Item **);
+
+ virtual void print(String *str, enum_query_type query_type);
+
+public:
+ inline const LEX_STRING *my_name() const;
+
+ inline uint get_var_idx() const;
+
+ inline enum Type type() const;
+ inline Item_result result_type() const;
+ inline enum_field_types field_type() const { return m_field_type; }
+
+private:
+ bool set_value(THD *thd, sp_rcontext *ctx, Item **it);
+
+public:
+ Settable_routine_parameter *get_settable_routine_parameter()
+ {
+ return this;
+ }
+};
+
+/*****************************************************************************
+ Item_splocal inline implementation.
+*****************************************************************************/
+
+inline const LEX_STRING *Item_splocal::my_name() const
+{
+ return &m_name;
+}
+
+inline uint Item_splocal::get_var_idx() const
+{
+ return m_var_idx;
+}
+
+inline enum Item::Type Item_splocal::type() const
+{
+ return m_type;
+}
+
+inline Item_result Item_splocal::result_type() const
+{
+ return m_result_type;
+}
+
+
+/*****************************************************************************
+ A reference to case expression in SP, used in runtime.
+*****************************************************************************/
+
+class Item_case_expr :public Item_sp_variable
+{
+public:
+ Item_case_expr(uint case_expr_id);
+
+public:
+ Item *this_item();
+ const Item *this_item() const;
+ Item **this_item_addr(THD *thd, Item **);
+
+ inline enum Type type() const;
+ inline Item_result result_type() const;
+
+public:
+ /*
+ NOTE: print() is intended to be used from views and for debug.
+ Item_case_expr can not occur in views, so here it is only for debug
+ purposes.
+ */
+ virtual void print(String *str, enum_query_type query_type);
+
+private:
+ uint m_case_expr_id;
+};
+
+/*****************************************************************************
+ Item_case_expr inline implementation.
+*****************************************************************************/
+
+inline enum Item::Type Item_case_expr::type() const
+{
+ return this_item()->type();
+}
+
+inline Item_result Item_case_expr::result_type() const
+{
+ return this_item()->result_type();
+}
+
+
+/*
+ NAME_CONST(given_name, const_value).
+ This 'function' has all properties of the supplied const_value (which is
+ assumed to be a literal constant), and the name given_name.
+
+ This is used to replace references to SP variables when we write PROCEDURE
+ statements into the binary log.
+
+ TODO
+ Together with Item_splocal and Item::this_item() we can actually extract
+ common a base of this class and Item_splocal. Maybe it is possible to
+ extract a common base with class Item_ref, too.
+*/
+
+class Item_name_const : public Item
+{
+ Item *value_item;
+ Item *name_item;
+ bool valid_args;
+public:
+ Item_name_const(Item *name_arg, Item *val);
+
+ bool fix_fields(THD *, Item **);
+
+ enum Type type() const;
+ double val_real();
+ longlong val_int();
+ String *val_str(String *sp);
+ my_decimal *val_decimal(my_decimal *);
+ bool is_null();
+ virtual void print(String *str, enum_query_type query_type);
+
+ Item_result result_type() const
+ {
+ return value_item->result_type();
+ }
+
+ bool const_item() const
+ {
+ return TRUE;
+ }
+
+ int save_in_field(Field *field, bool no_conversions)
+ {
+ return value_item->save_in_field(field, no_conversions);
+ }
+
+ bool send(Protocol *protocol, String *str)
+ {
+ return value_item->send(protocol, str);
+ }
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("name_const");
+ }
+};
+
+bool agg_item_collations(DTCollation &c, const char *name,
+ Item **items, uint nitems, uint flags, int item_sep);
+bool agg_item_collations_for_comparison(DTCollation &c, const char *name,
+ Item **items, uint nitems, uint flags);
+bool agg_item_set_converter(DTCollation &coll, const char *fname,
+ Item **args, uint nargs, uint flags, int item_sep);
+bool agg_item_charsets(DTCollation &c, const char *name,
+ Item **items, uint nitems, uint flags, int item_sep);
+inline bool
+agg_item_charsets_for_string_result(DTCollation &c, const char *name,
+ Item **items, uint nitems,
+ int item_sep= 1)
+{
+ uint flags= MY_COLL_ALLOW_SUPERSET_CONV |
+ MY_COLL_ALLOW_COERCIBLE_CONV |
+ MY_COLL_ALLOW_NUMERIC_CONV;
+ return agg_item_charsets(c, name, items, nitems, flags, item_sep);
+}
+inline bool
+agg_item_charsets_for_comparison(DTCollation &c, const char *name,
+ Item **items, uint nitems,
+ int item_sep= 1)
+{
+ uint flags= MY_COLL_ALLOW_SUPERSET_CONV |
+ MY_COLL_ALLOW_COERCIBLE_CONV |
+ MY_COLL_DISALLOW_NONE;
+ return agg_item_charsets(c, name, items, nitems, flags, item_sep);
+}
+inline bool
+agg_item_charsets_for_string_result_with_comparison(DTCollation &c,
+ const char *name,
+ Item **items, uint nitems,
+ int item_sep= 1)
+{
+ uint flags= MY_COLL_ALLOW_SUPERSET_CONV |
+ MY_COLL_ALLOW_COERCIBLE_CONV |
+ MY_COLL_ALLOW_NUMERIC_CONV |
+ MY_COLL_DISALLOW_NONE;
+ return agg_item_charsets(c, name, items, nitems, flags, item_sep);
+}
+
+
+class Item_num: public Item_basic_constant
+{
+public:
+ Item_num() { collation.set_numeric(); } /* Remove gcc warning */
+ virtual Item_num *neg()= 0;
+ Item *safe_charset_converter(CHARSET_INFO *tocs);
+ bool check_partition_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+};
+
+#define NO_CACHED_FIELD_INDEX ((uint)(-1))
+
+class st_select_lex;
+class Item_ident :public Item
+{
+protected:
+ /*
+ We have to store initial values of db_name, table_name and field_name
+ to be able to restore them during cleanup() because they can be
+ updated during fix_fields() to values from Field object and life-time
+ of those is shorter than life-time of Item_field.
+ */
+ const char *orig_db_name;
+ const char *orig_table_name;
+ const char *orig_field_name;
+
+public:
+ Name_resolution_context *context;
+ const char *db_name;
+ const char *table_name;
+ const char *field_name;
+ bool alias_name_used; /* true if item was resolved against alias */
+ /*
+ Cached value of index for this field in table->field array, used by prep.
+ stmts for speeding up their re-execution. Holds NO_CACHED_FIELD_INDEX
+ if index value is not known.
+ */
+ uint cached_field_index;
+ /*
+ Cached pointer to table which contains this field, used for the same reason
+ by prep. stmt. too in case then we have not-fully qualified field.
+ 0 - means no cached value.
+ */
+ TABLE_LIST *cached_table;
+ st_select_lex *depended_from;
+ /*
+ Some Items resolved in another select should not be marked as dependency
+ of the subquery where they are. During normal name resolution, we check
+ this. Stored procedures and prepared statements first try to resolve an
+ ident item using a cached table reference and field position from the
+ previous query execution (cached_table/cached_field_index). If the
+ tables were not changed, the ident matches the table/field, and we have
+ faster resolution of the ident without looking through all tables and
+ fields in the query. But in this case, we can not check all conditions
+ about this ident item dependency, so we should cache the condition in
+ this variable.
+ */
+ bool can_be_depended;
+ Item_ident(Name_resolution_context *context_arg,
+ const char *db_name_arg, const char *table_name_arg,
+ const char *field_name_arg);
+ Item_ident(THD *thd, Item_ident *item);
+ Item_ident(TABLE_LIST *view_arg, const char *field_name_arg);
+ const char *full_name() const;
+ void cleanup();
+ st_select_lex *get_depended_from() const;
+ bool remove_dependence_processor(uchar * arg);
+ virtual void print(String *str, enum_query_type query_type);
+ virtual bool change_context_processor(uchar *cntx)
+ { context= (Name_resolution_context *)cntx; return FALSE; }
+ /**
+ Collect outer references
+ */
+ virtual bool collect_outer_ref_processor(uchar *arg);
+ friend bool insert_fields(THD *thd, Name_resolution_context *context,
+ const char *db_name,
+ const char *table_name, List_iterator<Item> *it,
+ bool any_privileges);
+};
+
+
+class Item_ident_for_show :public Item
+{
+public:
+ Field *field;
+ const char *db_name;
+ const char *table_name;
+
+ Item_ident_for_show(Field *par_field, const char *db_arg,
+ const char *table_name_arg)
+ :field(par_field), db_name(db_arg), table_name(table_name_arg)
+ {}
+
+ enum Type type() const { return FIELD_ITEM; }
+ double val_real() { return field->val_real(); }
+ longlong val_int() { return field->val_int(); }
+ String *val_str(String *str) { return field->val_str(str); }
+ my_decimal *val_decimal(my_decimal *dec) { return field->val_decimal(dec); }
+ void make_field(Send_field *tmp_field);
+ CHARSET_INFO *charset_for_protocol(void) const
+ { return field->charset_for_protocol(); }
+};
+
+
+class Item_field :public Item_ident
+{
+protected:
+ void set_field(Field *field);
+public:
+ Field *field,*result_field;
+ Item_equal *item_equal;
+ bool no_const_subst;
+ /*
+ if any_privileges set to TRUE then here real effective privileges will
+ be stored
+ */
+ uint have_privileges;
+ /* field need any privileges (for VIEW creation) */
+ bool any_privileges;
+ Item_field(Name_resolution_context *context_arg,
+ const char *db_arg,const char *table_name_arg,
+ const char *field_name_arg);
+ /*
+ Constructor needed to process subselect with temporary tables (see Item)
+ */
+ Item_field(THD *thd, Item_field *item);
+ /*
+ Constructor used inside setup_wild(), ensures that field, table,
+ and database names will live as long as Item_field (this is important
+ in prepared statements).
+ */
+ Item_field(THD *thd, Name_resolution_context *context_arg, Field *field);
+ /*
+ If this constructor is used, fix_fields() won't work, because
+ db_name, table_name and column_name are unknown. It's necessary to call
+ reset_field() before fix_fields() for all fields created this way.
+ */
+ Item_field(Field *field);
+ enum Type type() const { return FIELD_ITEM; }
+ bool eq(const Item *item, bool binary_cmp) const;
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *);
+ String *val_str(String*);
+ void save_result(Field *to);
+ double val_result();
+ longlong val_int_result();
+ String *str_result(String* tmp);
+ my_decimal *val_decimal_result(my_decimal *);
+ bool val_bool_result();
+ bool is_null_result();
+ bool send(Protocol *protocol, String *str_arg);
+ void reset_field(Field *f);
+ bool fix_fields(THD *, Item **);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ void make_field(Send_field *tmp_field);
+ int save_in_field(Field *field,bool no_conversions);
+ void save_org_in_field(Field *field, fast_field_copier optimizer_data);
+ fast_field_copier setup_fast_field_copier(Field *field);
+ table_map used_tables() const;
+ table_map all_used_tables() const;
+ enum Item_result result_type () const
+ {
+ return field->result_type();
+ }
+ Item_result cast_to_int_type() const
+ {
+ return field->cmp_type();
+ }
+ enum_field_types field_type() const
+ {
+ return field->type();
+ }
+ enum_monotonicity_info get_monotonicity_info() const
+ {
+ return MONOTONIC_STRICT_INCREASING;
+ }
+ longlong val_int_endpoint(bool left_endp, bool *incl_endp);
+ Field *get_tmp_table_field() { return result_field; }
+ Field *tmp_table_field(TABLE *t_arg) { return result_field; }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ bool get_date_result(MYSQL_TIME *ltime,ulonglong fuzzydate);
+ bool is_null() { return field->is_null(); }
+ void update_null_value();
+ void update_table_bitmaps()
+ {
+ if (field && field->table)
+ {
+ TABLE *tab= field->table;
+ tab->covering_keys.intersect(field->part_of_key);
+ tab->merge_keys.merge(field->part_of_key);
+ if (tab->read_set)
+ bitmap_fast_test_and_set(tab->read_set, field->field_index);
+ /*
+ Do not mark a self-referecing virtual column.
+ Such virtual columns are reported as invalid.
+ */
+ if (field->vcol_info && tab->vcol_set)
+ tab->mark_virtual_col(field);
+ }
+ }
+ void update_used_tables()
+ {
+ update_table_bitmaps();
+ }
+ Item *get_tmp_table_item(THD *thd);
+ bool collect_item_field_processor(uchar * arg);
+ bool add_field_to_set_processor(uchar * arg);
+ bool find_item_in_field_list_processor(uchar *arg);
+ bool register_field_in_read_map(uchar *arg);
+ bool register_field_in_write_map(uchar *arg);
+ bool register_field_in_bitmap(uchar *arg);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool vcol_in_partition_func_processor(uchar *bool_arg);
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+ bool enumerate_field_refs_processor(uchar *arg);
+ bool update_table_bitmaps_processor(uchar *arg);
+ void cleanup();
+ Item_equal *get_item_equal() { return item_equal; }
+ void set_item_equal(Item_equal *item_eq) { item_equal= item_eq; }
+ Item_equal *find_item_equal(COND_EQUAL *cond_equal);
+ bool subst_argument_checker(uchar **arg);
+ Item *equal_fields_propagator(uchar *arg);
+ bool set_no_const_sub(uchar *arg);
+ Item *replace_equal_field(uchar *arg);
+ inline uint32 max_disp_length() { return field->max_display_length(); }
+ Item_field *field_for_view_update() { return this; }
+ int fix_outer_field(THD *thd, Field **field, Item **reference);
+ virtual Item *update_value_transformer(uchar *select_arg);
+ virtual void print(String *str, enum_query_type query_type);
+ bool is_outer_field() const
+ {
+ DBUG_ASSERT(fixed);
+ return field->table->pos_in_table_list->outer_join;
+ }
+ Field::geometry_type get_geometry_type() const
+ {
+ DBUG_ASSERT(field_type() == MYSQL_TYPE_GEOMETRY);
+ return field->get_geometry_type();
+ }
+ CHARSET_INFO *charset_for_protocol(void) const
+ { return field->charset_for_protocol(); }
+ friend class Item_default_value;
+ friend class Item_insert_value;
+ friend class st_select_lex_unit;
+};
+
+class Item_null :public Item_basic_constant
+{
+public:
+ Item_null(char *name_par=0, CHARSET_INFO *cs= &my_charset_bin)
+ {
+ maybe_null= null_value= TRUE;
+ max_length= 0;
+ name= name_par ? name_par : (char*) "NULL";
+ fixed= 1;
+ collation.set(cs, DERIVATION_IGNORABLE);
+ }
+ enum Type type() const { return NULL_ITEM; }
+ bool eq(const Item *item, bool binary_cmp) const { return null_eq(item); }
+ double val_real();
+ longlong val_int();
+ String *val_str(String *str);
+ my_decimal *val_decimal(my_decimal *);
+ int save_in_field(Field *field, bool no_conversions);
+ int save_safe_in_field(Field *field);
+ bool send(Protocol *protocol, String *str);
+ enum Item_result result_type () const { return STRING_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_NULL; }
+ bool basic_const_item() const { return 1; }
+ Item *clone_item() { return new Item_null(name); }
+ bool is_null() { return 1; }
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ str->append(STRING_WITH_LEN("NULL"));
+ }
+
+ Item *safe_charset_converter(CHARSET_INFO *tocs);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+};
+
+class Item_null_result :public Item_null
+{
+public:
+ Field *result_field;
+ Item_null_result() : Item_null(), result_field(0) {}
+ bool is_result_field() { return result_field != 0; }
+ void save_in_result_field(bool no_conversions)
+ {
+ save_in_field(result_field, no_conversions);
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return TRUE;}
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(full_name());
+ }
+};
+
+/* Item represents one placeholder ('?') of prepared statement */
+
+class Item_param :public Item_basic_value,
+ private Settable_routine_parameter
+{
+public:
+ enum enum_item_param_state
+ {
+ NO_VALUE, NULL_VALUE, INT_VALUE, REAL_VALUE,
+ STRING_VALUE, TIME_VALUE, LONG_DATA_VALUE,
+ DECIMAL_VALUE
+ } state;
+
+ /*
+ A buffer for string and long data values. Historically all allocated
+ values returned from val_str() were treated as eligible to
+ modification. I. e. in some cases Item_func_concat can append it's
+ second argument to return value of the first one. Because of that we
+ can't return the original buffer holding string data from val_str(),
+ and have to have one buffer for data and another just pointing to
+ the data. This is the latter one and it's returned from val_str().
+ Can not be declared inside the union as it's not a POD type.
+ */
+ String str_value_ptr;
+ my_decimal decimal_value;
+ union
+ {
+ longlong integer;
+ double real;
+ /*
+ Character sets conversion info for string values.
+ Character sets of client and connection defined at bind time are used
+ for all conversions, even if one of them is later changed (i.e.
+ between subsequent calls to mysql_stmt_execute).
+ */
+ struct CONVERSION_INFO
+ {
+ CHARSET_INFO *character_set_client;
+ CHARSET_INFO *character_set_of_placeholder;
+ /*
+ This points at character set of connection if conversion
+ to it is required (i. e. if placeholder typecode is not BLOB).
+ Otherwise it's equal to character_set_client (to simplify
+ check in convert_str_value()).
+ */
+ CHARSET_INFO *final_character_set_of_str_value;
+ } cs_info;
+ MYSQL_TIME time;
+ } value;
+
+ /* Cached values for virtual methods to save us one switch. */
+ enum Item_result item_result_type;
+ enum Type item_type;
+
+ /*
+ Used when this item is used in a temporary table.
+ This is NOT placeholder metadata sent to client, as this value
+ is assigned after sending metadata (in setup_one_conversion_function).
+ For example in case of 'SELECT ?' you'll get MYSQL_TYPE_STRING both
+ in result set and placeholders metadata, no matter what type you will
+ supply for this placeholder in mysql_stmt_execute.
+ */
+ enum enum_field_types param_type;
+ /*
+ Offset of placeholder inside statement text. Used to create
+ no-placeholders version of this statement for the binary log.
+ */
+ uint pos_in_query;
+
+ Item_param(uint pos_in_query_arg);
+
+ enum Item_result result_type () const { return item_result_type; }
+ enum Type type() const { return item_type; }
+ enum_field_types field_type() const { return param_type; }
+
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal*);
+ String *val_str(String*);
+ bool get_date(MYSQL_TIME *tm, ulonglong fuzzydate);
+ int save_in_field(Field *field, bool no_conversions);
+
+ void set_null();
+ void set_int(longlong i, uint32 max_length_arg);
+ void set_double(double i);
+ void set_decimal(const char *str, ulong length);
+ void set_decimal(const my_decimal *dv);
+ bool set_str(const char *str, ulong length);
+ bool set_longdata(const char *str, ulong length);
+ void set_time(MYSQL_TIME *tm, timestamp_type type, uint32 max_length_arg);
+ bool set_from_user_var(THD *thd, const user_var_entry *entry);
+ void reset();
+ /*
+ Assign placeholder value from bind data.
+ Note, that 'len' has different semantics in embedded library (as we
+ don't need to check that packet is not broken there). See
+ sql_prepare.cc for details.
+ */
+ void (*set_param_func)(Item_param *param, uchar **pos, ulong len);
+
+ const String *query_val_str(THD *thd, String *str) const;
+
+ bool convert_str_value(THD *thd);
+
+ /*
+ If value for parameter was not set we treat it as non-const
+ so noone will use parameters value in fix_fields still
+ parameter is constant during execution.
+ */
+ virtual table_map used_tables() const
+ { return state != NO_VALUE ? (table_map)0 : PARAM_TABLE_BIT; }
+ virtual void print(String *str, enum_query_type query_type);
+ bool is_null()
+ { DBUG_ASSERT(state != NO_VALUE); return state == NULL_VALUE; }
+ bool basic_const_item() const;
+ /*
+ This method is used to make a copy of a basic constant item when
+ propagating constants in the optimizer. The reason to create a new
+ item and not use the existing one is not precisely known (2005/04/16).
+ Probably we are trying to preserve tree structure of items, in other
+ words, avoid pointing at one item from two different nodes of the tree.
+ Return a new basic constant item if parameter value is a basic
+ constant, assert otherwise. This method is called only if
+ basic_const_item returned TRUE.
+ */
+ Item *safe_charset_converter(CHARSET_INFO *tocs);
+ Item *clone_item();
+ /*
+ Implement by-value equality evaluation if parameter value
+ is set and is a basic constant (integer, real or string).
+ Otherwise return FALSE.
+ */
+ bool eq(const Item *item, bool binary_cmp) const;
+ /** Item is a argument to a limit clause. */
+ bool limit_clause_param;
+ void set_param_type_and_swap_value(Item_param *from);
+
+private:
+ virtual inline Settable_routine_parameter *
+ get_settable_routine_parameter()
+ {
+ return this;
+ }
+
+ virtual bool set_value(THD *thd, sp_rcontext *ctx, Item **it);
+
+ virtual void set_out_param_info(Send_field *info);
+
+public:
+ virtual const Send_field *get_out_param_info() const;
+
+ virtual void make_field(Send_field *field);
+
+private:
+ Send_field *m_out_param_info;
+};
+
+
+class Item_int :public Item_num
+{
+public:
+ longlong value;
+ Item_int(int32 i,uint length= MY_INT32_NUM_DECIMAL_DIGITS)
+ :value((longlong) i)
+ { max_length=length; fixed= 1; }
+ Item_int(longlong i,uint length= MY_INT64_NUM_DECIMAL_DIGITS)
+ :value(i)
+ { max_length=length; fixed= 1; }
+ Item_int(ulonglong i, uint length= MY_INT64_NUM_DECIMAL_DIGITS)
+ :value((longlong)i)
+ { max_length=length; fixed= 1; unsigned_flag= 1; }
+ Item_int(const char *str_arg,longlong i,uint length) :value(i)
+ { max_length=length; name=(char*) str_arg; fixed= 1; }
+ Item_int(const char *str_arg, uint length=64);
+ enum Type type() const { return INT_ITEM; }
+ enum Item_result result_type () const { return INT_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; }
+ longlong val_int() { DBUG_ASSERT(fixed == 1); return value; }
+ double val_real() { DBUG_ASSERT(fixed == 1); return (double) value; }
+ my_decimal *val_decimal(my_decimal *);
+ String *val_str(String*);
+ int save_in_field(Field *field, bool no_conversions);
+ bool basic_const_item() const { return 1; }
+ Item *clone_item() { return new Item_int(name,value,max_length); }
+ virtual void print(String *str, enum_query_type query_type);
+ Item_num *neg() { value= -value; return this; }
+ uint decimal_precision() const
+ { return (uint) (max_length - MY_TEST(value < 0)); }
+ bool eq(const Item *item, bool binary_cmp) const
+ { return int_eq(value, item); }
+ bool check_partition_func_processor(uchar *bool_arg) { return FALSE;}
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+};
+
+
+class Item_uint :public Item_int
+{
+public:
+ Item_uint(const char *str_arg, uint length);
+ Item_uint(ulonglong i) :Item_int(i, 10) {}
+ Item_uint(const char *str_arg, longlong i, uint length);
+ double val_real()
+ { DBUG_ASSERT(fixed == 1); return ulonglong2double((ulonglong)value); }
+ String *val_str(String*);
+ Item *clone_item() { return new Item_uint(name, value, max_length); }
+ virtual void print(String *str, enum_query_type query_type);
+ Item_num *neg ();
+ uint decimal_precision() const { return max_length; }
+};
+
+
+class Item_datetime :public Item_int
+{
+protected:
+ MYSQL_TIME ltime;
+public:
+ Item_datetime() :Item_int(0) { unsigned_flag=0; }
+ int save_in_field(Field *field, bool no_conversions);
+ longlong val_int();
+ double val_real() { return (double)val_int(); }
+ void set(longlong packed);
+};
+
+
+/* decimal (fixed point) constant */
+class Item_decimal :public Item_num
+{
+protected:
+ my_decimal decimal_value;
+public:
+ Item_decimal(const char *str_arg, uint length, CHARSET_INFO *charset);
+ Item_decimal(const char *str, const my_decimal *val_arg,
+ uint decimal_par, uint length);
+ Item_decimal(my_decimal *value_par);
+ Item_decimal(longlong val, bool unsig);
+ Item_decimal(double val, int precision, int scale);
+ Item_decimal(const uchar *bin, int precision, int scale);
+
+ enum Type type() const { return DECIMAL_ITEM; }
+ enum Item_result result_type () const { return DECIMAL_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_NEWDECIMAL; }
+ longlong val_int();
+ double val_real();
+ String *val_str(String*);
+ my_decimal *val_decimal(my_decimal *val) { return &decimal_value; }
+ int save_in_field(Field *field, bool no_conversions);
+ bool basic_const_item() const { return 1; }
+ Item *clone_item()
+ {
+ return new Item_decimal(name, &decimal_value, decimals, max_length);
+ }
+ virtual void print(String *str, enum_query_type query_type);
+ Item_num *neg()
+ {
+ my_decimal_neg(&decimal_value);
+ unsigned_flag= !decimal_value.sign();
+ return this;
+ }
+ uint decimal_precision() const { return decimal_value.precision(); }
+ bool eq(const Item *, bool binary_cmp) const;
+ void set_decimal_value(my_decimal *value_par);
+ bool check_partition_func_processor(uchar *bool_arg) { return FALSE;}
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+};
+
+
+class Item_float :public Item_num
+{
+ char *presentation;
+public:
+ double value;
+ // Item_real() :value(0) {}
+ Item_float(const char *str_arg, uint length);
+ Item_float(const char *str,double val_arg,uint decimal_par,uint length)
+ :value(val_arg)
+ {
+ presentation= name=(char*) str;
+ decimals=(uint8) decimal_par;
+ max_length=length;
+ fixed= 1;
+ }
+ Item_float(double value_par, uint decimal_par) :presentation(0), value(value_par)
+ {
+ decimals= (uint8) decimal_par;
+ fixed= 1;
+ }
+ int save_in_field(Field *field, bool no_conversions);
+ enum Type type() const { return REAL_ITEM; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; }
+ double val_real() { DBUG_ASSERT(fixed == 1); return value; }
+ longlong val_int()
+ {
+ DBUG_ASSERT(fixed == 1);
+ if (value <= (double) LONGLONG_MIN)
+ {
+ return LONGLONG_MIN;
+ }
+ else if (value >= (double) (ulonglong) LONGLONG_MAX)
+ {
+ return LONGLONG_MAX;
+ }
+ return (longlong) rint(value);
+ }
+ String *val_str(String*);
+ my_decimal *val_decimal(my_decimal *);
+ bool basic_const_item() const { return 1; }
+ Item *clone_item()
+ { return new Item_float(name, value, decimals, max_length); }
+ Item_num *neg() { value= -value; return this; }
+ virtual void print(String *str, enum_query_type query_type);
+ bool eq(const Item *item, bool binary_cmp) const
+ { return real_eq(value, item); }
+};
+
+
+class Item_static_float_func :public Item_float
+{
+ const char *func_name;
+public:
+ Item_static_float_func(const char *str, double val_arg, uint decimal_par,
+ uint length)
+ :Item_float(NullS, val_arg, decimal_par, length), func_name(str)
+ {}
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ str->append(func_name);
+ }
+
+ Item *safe_charset_converter(CHARSET_INFO *tocs)
+ {
+ return const_charset_converter(tocs, true, func_name);
+ }
+};
+
+
+class Item_string :public Item_basic_constant
+{
+ bool m_cs_specified;
+protected:
+ /**
+ Set the value of m_cs_specified attribute.
+
+ m_cs_specified attribute shows whether character-set-introducer was
+ explicitly specified in the original query for this text literal or
+ not. The attribute makes sense (is used) only for views.
+
+ This operation is to be called from the parser during parsing an input
+ query.
+ */
+ inline void set_cs_specified(bool cs_specified)
+ {
+ m_cs_specified= cs_specified;
+ }
+ void fix_from_value(Derivation dv, const Metadata metadata)
+ {
+ fix_charset_and_length_from_str_value(dv, metadata);
+ // it is constant => can be used without fix_fields (and frequently used)
+ fixed= 1;
+ }
+ void fix_and_set_name_from_value(Derivation dv, const Metadata metadata)
+ {
+ fix_from_value(dv, metadata);
+ set_name(str_value.ptr(), str_value.length(), str_value.charset());
+ }
+protected:
+ /* Just create an item and do not fill string representation */
+ Item_string(CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE)
+ : m_cs_specified(FALSE)
+ {
+ collation.set(cs, dv);
+ max_length= 0;
+ set_name(NULL, 0, system_charset_info);
+ decimals= NOT_FIXED_DEC;
+ fixed= 1;
+ }
+public:
+ // Constructors with the item name set from its value
+ Item_string(const char *str, uint length, CHARSET_INFO *cs,
+ Derivation dv, uint repertoire)
+ : m_cs_specified(FALSE)
+ {
+ str_value.set_or_copy_aligned(str, length, cs);
+ fix_and_set_name_from_value(dv, Metadata(&str_value, repertoire));
+ }
+ Item_string(const char *str, uint length,
+ CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE)
+ : m_cs_specified(FALSE)
+ {
+ str_value.set_or_copy_aligned(str, length, cs);
+ fix_and_set_name_from_value(dv, Metadata(&str_value));
+ }
+ Item_string(const String *str, CHARSET_INFO *tocs, uint *conv_errors,
+ Derivation dv, uint repertoire)
+ :m_cs_specified(false)
+ {
+ if (str_value.copy(str, tocs, conv_errors))
+ str_value.set("", 0, tocs); // EOM ?
+ str_value.mark_as_const();
+ fix_and_set_name_from_value(dv, Metadata(&str_value, repertoire));
+ }
+ // Constructors with an externally provided item name
+ Item_string(const char *name_par, const char *str, uint length,
+ CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE)
+ :m_cs_specified(false)
+ {
+ str_value.set_or_copy_aligned(str, length, cs);
+ fix_from_value(dv, Metadata(&str_value));
+ set_name(name_par, 0, system_charset_info);
+ }
+ Item_string(const char *name_par, const char *str, uint length,
+ CHARSET_INFO *cs, Derivation dv, uint repertoire)
+ :m_cs_specified(false)
+ {
+ str_value.set_or_copy_aligned(str, length, cs);
+ fix_from_value(dv, Metadata(&str_value, repertoire));
+ set_name(name_par, 0, system_charset_info);
+ }
+ void print_value(String *to) const
+ {
+ str_value.print(to);
+ }
+ enum Type type() const { return STRING_ITEM; }
+ double val_real();
+ longlong val_int();
+ String *val_str(String*)
+ {
+ DBUG_ASSERT(fixed == 1);
+ return (String*) &str_value;
+ }
+ my_decimal *val_decimal(my_decimal *);
+ int save_in_field(Field *field, bool no_conversions);
+ enum Item_result result_type () const { return STRING_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; }
+ bool basic_const_item() const { return 1; }
+ bool eq(const Item *item, bool binary_cmp) const
+ {
+ return str_eq(&str_value, item, binary_cmp);
+ }
+ Item *clone_item()
+ {
+ return new Item_string(name, str_value.ptr(),
+ str_value.length(), collation.collation);
+ }
+ Item *safe_charset_converter(CHARSET_INFO *tocs)
+ {
+ return const_charset_converter(tocs, true);
+ }
+ inline void append(char *str, uint length)
+ {
+ str_value.append(str, length);
+ max_length= str_value.numchars() * collation.collation->mbmaxlen;
+ }
+ virtual void print(String *str, enum_query_type query_type);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+
+ /**
+ Return TRUE if character-set-introducer was explicitly specified in the
+ original query for this item (text literal).
+
+ This operation is to be called from Item_string::print(). The idea is
+ that when a query is generated (re-constructed) from the Item-tree,
+ character-set-introducers should appear only for those literals, where
+ they were explicitly specified by the user. Otherwise, that may lead to
+ loss collation information (character set introducers implies default
+ collation for the literal).
+
+ Basically, that makes sense only for views and hopefully will be gone
+ one day when we start using original query as a view definition.
+
+ @return This operation returns the value of m_cs_specified attribute.
+ @retval TRUE if character set introducer was explicitly specified in
+ the original query.
+ @retval FALSE otherwise.
+ */
+ inline bool is_cs_specified() const
+ {
+ return m_cs_specified;
+ }
+
+ String *check_well_formed_result(bool send_error)
+ { return Item::check_well_formed_result(&str_value, send_error); }
+
+ enum_field_types odbc_temporal_literal_type(const LEX_STRING *type_str) const
+ {
+ /*
+ If string is a reasonably short pure ASCII string literal,
+ try to parse known ODBC style date, time or timestamp literals,
+ e.g:
+ SELECT {d'2001-01-01'};
+ SELECT {t'10:20:30'};
+ SELECT {ts'2001-01-01 10:20:30'};
+ */
+ if (collation.repertoire == MY_REPERTOIRE_ASCII &&
+ str_value.length() < MAX_DATE_STRING_REP_LENGTH * 4)
+ {
+ if (type_str->length == 1)
+ {
+ if (type_str->str[0] == 'd') /* {d'2001-01-01'} */
+ return MYSQL_TYPE_DATE;
+ else if (type_str->str[0] == 't') /* {t'10:20:30'} */
+ return MYSQL_TYPE_TIME;
+ }
+ else if (type_str->length == 2) /* {ts'2001-01-01 10:20:30'} */
+ {
+ if (type_str->str[0] == 't' && type_str->str[1] == 's')
+ return MYSQL_TYPE_DATETIME;
+ }
+ }
+ return MYSQL_TYPE_STRING; // Not a temporal literal
+ }
+};
+
+
+class Item_string_with_introducer :public Item_string
+{
+public:
+ Item_string_with_introducer(const char *str, uint length, CHARSET_INFO *cs)
+ :Item_string(str, length, cs)
+ {
+ set_cs_specified(true);
+ }
+ Item_string_with_introducer(const char *name,
+ const char *str, uint length, CHARSET_INFO *tocs)
+ :Item_string(name, str, length, tocs)
+ {
+ set_cs_specified(true);
+ }
+};
+
+
+class Item_string_sys :public Item_string
+{
+public:
+ Item_string_sys(const char *str, uint length)
+ :Item_string(str, length, system_charset_info)
+ { }
+ Item_string_sys(const char *str)
+ :Item_string(str, strlen(str), system_charset_info)
+ { }
+};
+
+
+class Item_string_ascii :public Item_string
+{
+public:
+ Item_string_ascii(const char *str, uint length)
+ :Item_string(str, length, &my_charset_latin1,
+ DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII)
+ { }
+ Item_string_ascii(const char *str)
+ :Item_string(str, strlen(str), &my_charset_latin1,
+ DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII)
+ { }
+};
+
+
+longlong
+longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr,
+ const char *end);
+double
+double_from_string_with_check(CHARSET_INFO *cs, const char *cptr,
+ const char *end);
+
+class Item_static_string_func :public Item_string
+{
+ const char *func_name;
+public:
+ Item_static_string_func(const char *name_par, const char *str, uint length,
+ CHARSET_INFO *cs,
+ Derivation dv= DERIVATION_COERCIBLE)
+ :Item_string(NullS, str, length, cs, dv), func_name(name_par)
+ {}
+ Item_static_string_func(const char *name_par,
+ const String *str,
+ CHARSET_INFO *tocs, uint *conv_errors,
+ Derivation dv, uint repertoire)
+ :Item_string(str, tocs, conv_errors, dv, repertoire),
+ func_name(name_par)
+ {}
+ Item *safe_charset_converter(CHARSET_INFO *tocs)
+ {
+ return const_charset_converter(tocs, true, func_name);
+ }
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ str->append(func_name);
+ }
+
+ bool check_partition_func_processor(uchar *int_arg) {return TRUE;}
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name);
+ }
+};
+
+
+/* for show tables */
+class Item_partition_func_safe_string: public Item_string
+{
+public:
+ Item_partition_func_safe_string(const char *name_arg, uint length,
+ CHARSET_INFO *cs= NULL):
+ Item_string(name_arg, length, cs)
+ {}
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("safe_string");
+ }
+};
+
+
+class Item_return_date_time :public Item_partition_func_safe_string
+{
+ enum_field_types date_time_field_type;
+public:
+ Item_return_date_time(const char *name_arg, uint length_arg,
+ enum_field_types field_type_arg)
+ :Item_partition_func_safe_string(name_arg, length_arg, &my_charset_bin),
+ date_time_field_type(field_type_arg)
+ { decimals= 0; }
+ enum_field_types field_type() const { return date_time_field_type; }
+};
+
+
+class Item_blob :public Item_partition_func_safe_string
+{
+public:
+ Item_blob(const char *name_arg, uint length) :
+ Item_partition_func_safe_string(name_arg, length, &my_charset_bin)
+ { max_length= length; }
+ enum Type type() const { return TYPE_HOLDER; }
+ enum_field_types field_type() const { return MYSQL_TYPE_BLOB; }
+};
+
+
+/**
+ Item_empty_string -- is a utility class to put an item into List<Item>
+ which is then used in protocol.send_result_set_metadata() when sending SHOW output to
+ the client.
+*/
+
+class Item_empty_string :public Item_partition_func_safe_string
+{
+public:
+ Item_empty_string(const char *header,uint length, CHARSET_INFO *cs= NULL) :
+ Item_partition_func_safe_string("",0, cs ? cs : &my_charset_utf8_general_ci)
+ { name=(char*) header; max_length= length * collation.collation->mbmaxlen; }
+ void make_field(Send_field *field);
+};
+
+
+class Item_return_int :public Item_int
+{
+ enum_field_types int_field_type;
+public:
+ Item_return_int(const char *name_arg, uint length,
+ enum_field_types field_type_arg, longlong value_arg= 0)
+ :Item_int(name_arg, value_arg, length), int_field_type(field_type_arg)
+ {
+ unsigned_flag=1;
+ }
+ enum_field_types field_type() const { return int_field_type; }
+};
+
+
+/**
+ Item_hex_constant -- a common class for hex literals: X'HHHH' and 0xHHHH
+*/
+class Item_hex_constant: public Item_basic_constant
+{
+private:
+ void hex_string_init(const char *str, uint str_length);
+public:
+ Item_hex_constant()
+ {
+ hex_string_init("", 0);
+ }
+ Item_hex_constant(const char *str, uint str_length)
+ {
+ hex_string_init(str, str_length);
+ }
+ enum Type type() const { return VARBIN_ITEM; }
+ enum Item_result result_type () const { return STRING_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; }
+ virtual Item *safe_charset_converter(CHARSET_INFO *tocs)
+ {
+ return const_charset_converter(tocs, true);
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+ bool basic_const_item() const { return 1; }
+ bool eq(const Item *item, bool binary_cmp) const
+ {
+ return item->basic_const_item() && item->type() == type() &&
+ item->cast_to_int_type() == cast_to_int_type() &&
+ str_value.bin_eq(&((Item_hex_constant*)item)->str_value);
+ }
+ String *val_str(String*) { DBUG_ASSERT(fixed == 1); return &str_value; }
+};
+
+
+/**
+ Item_hex_hybrid -- is a class implementing 0xHHHH literals, e.g.:
+ SELECT 0x3132;
+ They can behave as numbers and as strings depending on context.
+*/
+class Item_hex_hybrid: public Item_hex_constant
+{
+public:
+ Item_hex_hybrid(): Item_hex_constant() {}
+ Item_hex_hybrid(const char *str, uint str_length):
+ Item_hex_constant(str, str_length) {}
+ double val_real()
+ {
+ DBUG_ASSERT(fixed == 1);
+ return (double) (ulonglong) Item_hex_hybrid::val_int();
+ }
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *decimal_value)
+ {
+ // following assert is redundant, because fixed=1 assigned in constructor
+ DBUG_ASSERT(fixed == 1);
+ ulonglong value= (ulonglong) Item_hex_hybrid::val_int();
+ int2my_decimal(E_DEC_FATAL_ERROR, value, TRUE, decimal_value);
+ return decimal_value;
+ }
+ int save_in_field(Field *field, bool no_conversions);
+ enum Item_result cast_to_int_type() const { return INT_RESULT; }
+ void print(String *str, enum_query_type query_type);
+};
+
+
+/**
+ Item_hex_string -- is a class implementing X'HHHH' literals, e.g.:
+ SELECT X'3132';
+ Unlike Item_hex_hybrid, X'HHHH' literals behave as strings in all contexts.
+ X'HHHH' are also used in replication of string constants in case of
+ "dangerous" charsets (sjis, cp932, big5, gbk) who can have backslash (0x5C)
+ as the second byte of a multi-byte character, so using '\' escaping for
+ these charsets is not desirable.
+*/
+class Item_hex_string: public Item_hex_constant
+{
+public:
+ Item_hex_string(): Item_hex_constant() {}
+ Item_hex_string(const char *str, uint str_length):
+ Item_hex_constant(str, str_length) {}
+ longlong val_int()
+ {
+ DBUG_ASSERT(fixed == 1);
+ return longlong_from_string_with_check(str_value.charset(),
+ str_value.ptr(),
+ str_value.ptr()+
+ str_value.length());
+ }
+ double val_real()
+ {
+ DBUG_ASSERT(fixed == 1);
+ return double_from_string_with_check(str_value.charset(),
+ str_value.ptr(),
+ str_value.ptr() +
+ str_value.length());
+ }
+ my_decimal *val_decimal(my_decimal *decimal_value)
+ {
+ return val_decimal_from_string(decimal_value);
+ }
+ int save_in_field(Field *field, bool no_conversions)
+ {
+ field->set_notnull();
+ return field->store(str_value.ptr(), str_value.length(),
+ collation.collation);
+ }
+ enum Item_result cast_to_int_type() const { return STRING_RESULT; }
+ void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_bin_string: public Item_hex_hybrid
+{
+public:
+ Item_bin_string(const char *str,uint str_length);
+};
+
+
+class Item_temporal_literal :public Item_basic_constant
+{
+protected:
+ MYSQL_TIME cached_time;
+public:
+ /**
+ Constructor for Item_date_literal.
+ @param ltime DATE value.
+ */
+ Item_temporal_literal(MYSQL_TIME *ltime) :Item_basic_constant()
+ {
+ collation.set(&my_charset_numeric, DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII);
+ decimals= 0;
+ cached_time= *ltime;
+ }
+ Item_temporal_literal(MYSQL_TIME *ltime, uint dec_arg) :Item_basic_constant()
+ {
+ collation.set(&my_charset_numeric, DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII);
+ decimals= dec_arg;
+ cached_time= *ltime;
+ }
+ bool basic_const_item() const { return true; }
+ bool const_item() const { return true; }
+ enum Type type() const { return DATE_ITEM; }
+ bool eq(const Item *item, bool binary_cmp) const;
+ enum Item_result result_type () const { return STRING_RESULT; }
+ Item_result cmp_type() const { return TIME_RESULT; }
+
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+
+ bool is_null()
+ { return is_null_from_temporal(); }
+ bool get_date_with_sql_mode(MYSQL_TIME *to);
+ String *val_str(String *str)
+ { return val_string_from_date(str); }
+ longlong val_int()
+ { return val_int_from_date(); }
+ double val_real()
+ { return val_real_from_date(); }
+ my_decimal *val_decimal(my_decimal *decimal_value)
+ { return val_decimal_from_date(decimal_value); }
+ Field *tmp_table_field(TABLE *table)
+ { return tmp_table_field_from_field_type(table, 0); }
+ int save_in_field(Field *field, bool no_conversions)
+ { return save_date_in_field(field); }
+};
+
+
+/**
+ DATE'2010-01-01'
+*/
+class Item_date_literal: public Item_temporal_literal
+{
+public:
+ Item_date_literal(MYSQL_TIME *ltime)
+ :Item_temporal_literal(ltime)
+ {
+ max_length= MAX_DATE_WIDTH;
+ fixed= 1;
+ /*
+ If date has zero month or day, it can return NULL in case of
+ NO_ZERO_DATE or NO_ZERO_IN_DATE.
+ We can't just check the current sql_mode here in constructor,
+ because sql_mode can change in case of prepared statements
+ between PREPARE and EXECUTE.
+ */
+ maybe_null= !ltime->month || !ltime->day;
+ }
+ enum_field_types field_type() const { return MYSQL_TYPE_DATE; }
+ void print(String *str, enum_query_type query_type);
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+};
+
+
+/**
+ TIME'10:10:10'
+*/
+class Item_time_literal: public Item_temporal_literal
+{
+public:
+ Item_time_literal(MYSQL_TIME *ltime, uint dec_arg)
+ :Item_temporal_literal(ltime, dec_arg)
+ {
+ max_length= MIN_TIME_WIDTH + (decimals ? decimals + 1 : 0);
+ fixed= 1;
+ }
+ enum_field_types field_type() const { return MYSQL_TYPE_TIME; }
+ void print(String *str, enum_query_type query_type);
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+};
+
+
+/**
+ TIMESTAMP'2001-01-01 10:20:30'
+*/
+class Item_datetime_literal: public Item_temporal_literal
+{
+public:
+ Item_datetime_literal(MYSQL_TIME *ltime, uint dec_arg)
+ :Item_temporal_literal(ltime, dec_arg)
+ {
+ max_length= MAX_DATETIME_WIDTH + (decimals ? decimals + 1 : 0);
+ fixed= 1;
+ // See the comment on maybe_null in Item_date_literal
+ maybe_null= !ltime->month || !ltime->day;
+ }
+ enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; }
+ void print(String *str, enum_query_type query_type);
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+};
+
+
+
+class Item_result_field :public Item /* Item with result field */
+{
+public:
+ Field *result_field; /* Save result here */
+ Item_result_field() :result_field(0) {}
+ // Constructor used for Item_sum/Item_cond_and/or (see Item comment)
+ Item_result_field(THD *thd, Item_result_field *item):
+ Item(thd, item), result_field(item->result_field)
+ {}
+ ~Item_result_field() {} /* Required with gcc 2.95 */
+ Field *get_tmp_table_field() { return result_field; }
+ Field *tmp_table_field(TABLE *t_arg) { return result_field; }
+ table_map used_tables() const { return 1; }
+ virtual void fix_length_and_dec()=0;
+ void set_result_field(Field *field) { result_field= field; }
+ bool is_result_field() { return 1; }
+ void save_in_result_field(bool no_conversions)
+ {
+ save_in_field(result_field, no_conversions);
+ }
+ void cleanup();
+ bool check_vcol_func_processor(uchar *arg) { return FALSE;}
+ /*
+ This method is used for debug purposes to print the name of an
+ item to the debug log. The second use of this method is as
+ a helper function of print() and error messages, where it is
+ applicable. To suit both goals it should return a meaningful,
+ distinguishable and sintactically correct string. This method
+ should not be used for runtime type identification, use enum
+ {Sum}Functype and Item_func::functype()/Item_sum::sum_func()
+ instead.
+ Added here, to the parent class of both Item_func and Item_sum_func.
+
+ NOTE: for Items inherited from Item_sum, func_name() return part of
+ function name till first argument (including '(') to make difference in
+ names for functions with 'distinct' clause and without 'distinct' and
+ also to make printing of items inherited from Item_sum uniform.
+ */
+ virtual const char *func_name() const= 0;
+};
+
+
+class Item_ref :public Item_ident
+{
+protected:
+ void set_properties();
+public:
+ enum Ref_Type { REF, DIRECT_REF, VIEW_REF, OUTER_REF, AGGREGATE_REF };
+ Field *result_field; /* Save result here */
+ Item **ref;
+ bool reference_trough_name;
+ Item_ref(Name_resolution_context *context_arg,
+ const char *db_arg, const char *table_name_arg,
+ const char *field_name_arg)
+ :Item_ident(context_arg, db_arg, table_name_arg, field_name_arg),
+ result_field(0), ref(0), reference_trough_name(1) {}
+ /*
+ This constructor is used in two scenarios:
+ A) *item = NULL
+ No initialization is performed, fix_fields() call will be necessary.
+
+ B) *item points to an Item this Item_ref will refer to. This is
+ used for GROUP BY. fix_fields() will not be called in this case,
+ so we call set_properties to make this item "fixed". set_properties
+ performs a subset of action Item_ref::fix_fields does, and this subset
+ is enough for Item_ref's used in GROUP BY.
+
+ TODO we probably fix a superset of problems like in BUG#6658. Check this
+ with Bar, and if we have a more broader set of problems like this.
+ */
+ Item_ref(Name_resolution_context *context_arg, Item **item,
+ const char *table_name_arg, const char *field_name_arg,
+ bool alias_name_used_arg= FALSE);
+ Item_ref(TABLE_LIST *view_arg, Item **item,
+ const char *field_name_arg, bool alias_name_used_arg= FALSE);
+
+ /* Constructor need to process subselect with temporary tables (see Item) */
+ Item_ref(THD *thd, Item_ref *item)
+ :Item_ident(thd, item), result_field(item->result_field), ref(item->ref) {}
+ enum Type type() const { return REF_ITEM; }
+ enum Type real_type() const { return ref ? (*ref)->type() :
+ REF_ITEM; }
+ bool eq(const Item *item, bool binary_cmp) const
+ {
+ Item *it= ((Item *) item)->real_item();
+ return ref && (*ref)->eq(it, binary_cmp);
+ }
+ void save_val(Field *to);
+ void save_result(Field *to);
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *);
+ bool val_bool();
+ String *val_str(String* tmp);
+ bool is_null();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ double val_result();
+ longlong val_int_result();
+ String *str_result(String* tmp);
+ my_decimal *val_decimal_result(my_decimal *);
+ bool val_bool_result();
+ bool is_null_result();
+ bool send(Protocol *prot, String *tmp);
+ void make_field(Send_field *field);
+ bool fix_fields(THD *, Item **);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ int save_in_field(Field *field, bool no_conversions);
+ void save_org_in_field(Field *field, fast_field_copier optimizer_data);
+ fast_field_copier setup_fast_field_copier(Field *field)
+ { return (*ref)->setup_fast_field_copier(field); }
+ enum Item_result result_type () const { return (*ref)->result_type(); }
+ enum_field_types field_type() const { return (*ref)->field_type(); }
+ Field *get_tmp_table_field()
+ { return result_field ? result_field : (*ref)->get_tmp_table_field(); }
+ Item *get_tmp_table_item(THD *thd);
+ table_map used_tables() const;
+ void update_used_tables();
+ bool const_item() const
+ {
+ return (*ref)->const_item();
+ }
+ table_map not_null_tables() const
+ {
+ return depended_from ? 0 : (*ref)->not_null_tables();
+ }
+ void set_result_field(Field *field) { result_field= field; }
+ bool is_result_field() { return 1; }
+ void save_in_result_field(bool no_conversions)
+ {
+ (*ref)->save_in_field(result_field, no_conversions);
+ }
+ Item *real_item()
+ {
+ return ref ? (*ref)->real_item() : this;
+ }
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg)
+ {
+ if (ref && *ref)
+ return (*ref)->walk(processor, walk_subquery, arg) ||
+ (this->*processor)(arg);
+ else
+ return FALSE;
+ }
+ Item* transform(Item_transformer, uchar *arg);
+ Item* compile(Item_analyzer analyzer, uchar **arg_p,
+ Item_transformer transformer, uchar *arg_t);
+ bool enumerate_field_refs_processor(uchar *arg)
+ { return (*ref)->enumerate_field_refs_processor(arg); }
+ void no_rows_in_result()
+ {
+ (*ref)->no_rows_in_result();
+ }
+ void restore_to_before_no_rows_in_result()
+ {
+ (*ref)->restore_to_before_no_rows_in_result();
+ }
+ virtual void print(String *str, enum_query_type query_type);
+ void cleanup();
+ Item_field *field_for_view_update()
+ { return (*ref)->field_for_view_update(); }
+ virtual Ref_Type ref_type() { return REF; }
+
+ // Row emulation: forwarding of ROW-related calls to ref
+ uint cols()
+ {
+ return ref && result_type() == ROW_RESULT ? (*ref)->cols() : 1;
+ }
+ Item* element_index(uint i)
+ {
+ return ref && result_type() == ROW_RESULT ? (*ref)->element_index(i) : this;
+ }
+ Item** addr(uint i)
+ {
+ return ref && result_type() == ROW_RESULT ? (*ref)->addr(i) : 0;
+ }
+ bool check_cols(uint c)
+ {
+ return ref && result_type() == ROW_RESULT ? (*ref)->check_cols(c)
+ : Item::check_cols(c);
+ }
+ bool null_inside()
+ {
+ return ref && result_type() == ROW_RESULT ? (*ref)->null_inside() : 0;
+ }
+ void bring_value()
+ {
+ if (ref && result_type() == ROW_RESULT)
+ (*ref)->bring_value();
+ }
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("ref");
+ }
+ bool basic_const_item() const { return ref && (*ref)->basic_const_item(); }
+ bool is_outer_field() const
+ {
+ DBUG_ASSERT(fixed);
+ DBUG_ASSERT(ref);
+ return (*ref)->is_outer_field();
+ }
+
+ /**
+ Checks if the item tree that ref points to contains a subquery.
+ */
+ virtual bool has_subquery() const
+ {
+ return (*ref)->has_subquery();
+ }
+};
+
+
+/*
+ The same as Item_ref, but get value from val_* family of method to get
+ value of item on which it referred instead of result* family.
+*/
+class Item_direct_ref :public Item_ref
+{
+public:
+ Item_direct_ref(Name_resolution_context *context_arg, Item **item,
+ const char *table_name_arg,
+ const char *field_name_arg,
+ bool alias_name_used_arg= FALSE)
+ :Item_ref(context_arg, item, table_name_arg,
+ field_name_arg, alias_name_used_arg)
+ {}
+ /* Constructor need to process subselect with temporary tables (see Item) */
+ Item_direct_ref(THD *thd, Item_direct_ref *item) : Item_ref(thd, item) {}
+ Item_direct_ref(TABLE_LIST *view_arg, Item **item,
+ const char *field_name_arg,
+ bool alias_name_used_arg= FALSE)
+ :Item_ref(view_arg, item, field_name_arg,
+ alias_name_used_arg)
+ {}
+
+ bool fix_fields(THD *thd, Item **it)
+ {
+ if ((!(*ref)->fixed && (*ref)->fix_fields(thd, ref)) ||
+ (*ref)->check_cols(1))
+ return TRUE;
+ return Item_ref::fix_fields(thd, it);
+ }
+ void save_val(Field *to);
+ double val_real();
+ longlong val_int();
+ String *val_str(String* tmp);
+ my_decimal *val_decimal(my_decimal *);
+ bool val_bool();
+ bool is_null();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ virtual Ref_Type ref_type() { return DIRECT_REF; }
+};
+
+
+/**
+ This class is the same as Item_direct_ref but created to wrap Item_ident
+ before fix_fields() call
+*/
+
+class Item_direct_ref_to_ident :public Item_direct_ref
+{
+ Item_ident *ident;
+public:
+ Item_direct_ref_to_ident(Item_ident *item)
+ :Item_direct_ref(item->context, (Item**)&item, item->table_name, item->field_name,
+ FALSE)
+ {
+ ident= item;
+ ref= (Item**)&ident;
+ }
+
+ bool fix_fields(THD *thd, Item **it)
+ {
+ DBUG_ASSERT(ident->type() == FIELD_ITEM || ident->type() == REF_ITEM);
+ if ((!ident->fixed && ident->fix_fields(thd, ref)) ||
+ ident->check_cols(1))
+ return TRUE;
+ set_properties();
+ return FALSE;
+ }
+
+ virtual void print(String *str, enum_query_type query_type)
+ { ident->print(str, query_type); }
+
+};
+
+
+class Item_cache;
+class Expression_cache;
+
+/**
+ The objects of this class can store its values in an expression cache.
+*/
+
+class Item_cache_wrapper :public Item_result_field
+{
+private:
+ /* Pointer on the cached expression */
+ Item *orig_item;
+ Expression_cache *expr_cache;
+ /*
+ In order to put the expression into the expression cache and return
+ value of val_*() method, we will need to get the expression value twice
+ (probably in different types). In order to avoid making two
+ (potentially costly) orig_item->val_*() calls, we store expression value
+ in this Item_cache object.
+ */
+ Item_cache *expr_value;
+
+ List<Item> parameters;
+
+ Item *check_cache();
+ void cache();
+ void init_on_demand();
+
+public:
+ Item_cache_wrapper(Item *item_arg);
+ ~Item_cache_wrapper();
+
+ const char *func_name() const { return "<expr_cache>"; }
+ enum Type type() const { return EXPR_CACHE_ITEM; }
+ enum Type real_type() const { return orig_item->type(); }
+
+ bool set_cache(THD *thd);
+
+ bool fix_fields(THD *thd, Item **it);
+ void fix_length_and_dec() {}
+ void cleanup();
+
+ /* Methods of getting value which should be cached in the cache */
+ void save_val(Field *to);
+ double val_real();
+ longlong val_int();
+ String *val_str(String* tmp);
+ my_decimal *val_decimal(my_decimal *);
+ bool val_bool();
+ bool is_null();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ bool send(Protocol *protocol, String *buffer);
+ void save_org_in_field(Field *field,
+ fast_field_copier data __attribute__ ((__unused__)))
+ {
+ save_val(field);
+ }
+ void save_in_result_field(bool no_conversions)
+ {
+ save_val(result_field);
+ }
+ Item* get_tmp_table_item(THD *thd_arg);
+
+ /* Following methods make this item transparent as much as possible */
+
+ virtual void print(String *str, enum_query_type query_type);
+ virtual const char *full_name() const { return orig_item->full_name(); }
+ virtual void make_field(Send_field *field) { orig_item->make_field(field); }
+ bool eq(const Item *item, bool binary_cmp) const
+ {
+ Item *it= ((Item *) item)->real_item();
+ return orig_item->eq(it, binary_cmp);
+ }
+ void fix_after_pullout(st_select_lex *new_parent, Item **refptr)
+ {
+ orig_item->fix_after_pullout(new_parent, &orig_item);
+ }
+ int save_in_field(Field *to, bool no_conversions);
+ enum Item_result result_type () const { return orig_item->result_type(); }
+ enum_field_types field_type() const { return orig_item->field_type(); }
+ table_map used_tables() const { return orig_item->used_tables(); }
+ void update_used_tables()
+ {
+ orig_item->update_used_tables();
+ }
+ bool const_item() const { return orig_item->const_item(); }
+ table_map not_null_tables() const { return orig_item->not_null_tables(); }
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg)
+ {
+ return orig_item->walk(processor, walk_subquery, arg) ||
+ (this->*processor)(arg);
+ }
+ bool enumerate_field_refs_processor(uchar *arg)
+ { return orig_item->enumerate_field_refs_processor(arg); }
+ Item_field *field_for_view_update()
+ { return orig_item->field_for_view_update(); }
+
+ /* Row emulation: forwarding of ROW-related calls to orig_item */
+ uint cols()
+ { return result_type() == ROW_RESULT ? orig_item->cols() : 1; }
+ Item* element_index(uint i)
+ { return result_type() == ROW_RESULT ? orig_item->element_index(i) : this; }
+ Item** addr(uint i)
+ { return result_type() == ROW_RESULT ? orig_item->addr(i) : 0; }
+ bool check_cols(uint c)
+ {
+ return (result_type() == ROW_RESULT ?
+ orig_item->check_cols(c) :
+ Item::check_cols(c));
+ }
+ bool null_inside()
+ { return result_type() == ROW_RESULT ? orig_item->null_inside() : 0; }
+ void bring_value()
+ {
+ if (result_type() == ROW_RESULT)
+ orig_item->bring_value();
+ }
+ virtual bool is_expensive() { return orig_item->is_expensive(); }
+ bool is_expensive_processor(uchar *arg)
+ { return orig_item->is_expensive_processor(arg); }
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("cache");
+ }
+};
+
+
+/*
+ Class for view fields, the same as Item_direct_ref, but call fix_fields
+ of reference if it is not called yet
+*/
+class Item_direct_view_ref :public Item_direct_ref
+{
+ Item_equal *item_equal;
+ TABLE_LIST *view;
+ TABLE *null_ref_table;
+
+#define NO_NULL_TABLE (reinterpret_cast<TABLE *>(0x1))
+
+ void set_null_ref_table()
+ {
+ if (!view->is_inner_table_of_outer_join() ||
+ !(null_ref_table= view->get_real_join_table()))
+ null_ref_table= NO_NULL_TABLE;
+ }
+
+ bool check_null_ref()
+ {
+ DBUG_ASSERT(null_ref_table);
+ if (null_ref_table != NO_NULL_TABLE && null_ref_table->null_row)
+ {
+ null_value= 1;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+public:
+ Item_direct_view_ref(Name_resolution_context *context_arg, Item **item,
+ const char *table_name_arg,
+ const char *field_name_arg,
+ TABLE_LIST *view_arg)
+ :Item_direct_ref(context_arg, item, table_name_arg, field_name_arg),
+ item_equal(0), view(view_arg),
+ null_ref_table(NULL)
+ {
+ if (fixed)
+ set_null_ref_table();
+ }
+
+ bool fix_fields(THD *, Item **);
+ bool eq(const Item *item, bool binary_cmp) const;
+ Item *get_tmp_table_item(THD *thd)
+ {
+ Item *item= Item_ref::get_tmp_table_item(thd);
+ item->name= name;
+ return item;
+ }
+ virtual Ref_Type ref_type() { return VIEW_REF; }
+ Item_equal *get_item_equal() { return item_equal; }
+ void set_item_equal(Item_equal *item_eq) { item_equal= item_eq; }
+ Item_equal *find_item_equal(COND_EQUAL *cond_equal);
+ bool subst_argument_checker(uchar **arg);
+ Item *equal_fields_propagator(uchar *arg);
+ Item *replace_equal_field(uchar *arg);
+ table_map used_tables() const;
+ void update_used_tables();
+ table_map not_null_tables() const;
+ bool const_item() const { return used_tables() == 0; }
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg)
+ {
+ return (*ref)->walk(processor, walk_subquery, arg) ||
+ (this->*processor)(arg);
+ }
+ bool view_used_tables_processor(uchar *arg)
+ {
+ TABLE_LIST *view_arg= (TABLE_LIST *) arg;
+ if (view_arg == view)
+ view_arg->view_used_tables|= (*ref)->used_tables();
+ return 0;
+ }
+ void save_val(Field *to)
+ {
+ if (check_null_ref())
+ to->set_null();
+ else
+ Item_direct_ref::save_val(to);
+ }
+ double val_real()
+ {
+ if (check_null_ref())
+ return 0;
+ else
+ return Item_direct_ref::val_real();
+ }
+ longlong val_int()
+ {
+ if (check_null_ref())
+ return 0;
+ else
+ return Item_direct_ref::val_int();
+ }
+ String *val_str(String* tmp)
+ {
+ if (check_null_ref())
+ return NULL;
+ else
+ return Item_direct_ref::val_str(tmp);
+ }
+ my_decimal *val_decimal(my_decimal *tmp)
+ {
+ if (check_null_ref())
+ return NULL;
+ else
+ return Item_direct_ref::val_decimal(tmp);
+ }
+ bool val_bool()
+ {
+ if (check_null_ref())
+ return 0;
+ else
+ return Item_direct_ref::val_bool();
+ }
+ bool is_null()
+ {
+ if (check_null_ref())
+ return 1;
+ else
+ return Item_direct_ref::is_null();
+ }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+ {
+ if (check_null_ref())
+ {
+ bzero((char*) ltime,sizeof(*ltime));
+ return 1;
+ }
+ return Item_direct_ref::get_date(ltime, fuzzydate);
+ }
+ bool send(Protocol *protocol, String *buffer);
+ void save_org_in_field(Field *field,
+ fast_field_copier data __attribute__ ((__unused__)))
+ {
+ if (check_null_ref())
+ field->set_null();
+ else
+ Item_direct_ref::save_val(field);
+ }
+ void save_in_result_field(bool no_conversions)
+ {
+ if (check_null_ref())
+ result_field->set_null();
+ else
+ Item_direct_ref::save_in_result_field(no_conversions);
+ }
+
+ void cleanup()
+ {
+ null_ref_table= NULL;
+ item_equal= NULL;
+ Item_direct_ref::cleanup();
+ }
+};
+
+
+/*
+ Class for outer fields.
+ An object of this class is created when the select where the outer field was
+ resolved is a grouping one. After it has been fixed the ref field will point
+ to either an Item_ref or an Item_direct_ref object which will be used to
+ access the field.
+ See also comments for the fix_inner_refs() and the
+ Item_field::fix_outer_field() functions.
+*/
+
+class Item_sum;
+class Item_outer_ref :public Item_direct_ref
+{
+public:
+ Item *outer_ref;
+ /* The aggregate function under which this outer ref is used, if any. */
+ Item_sum *in_sum_func;
+ /*
+ TRUE <=> that the outer_ref is already present in the select list
+ of the outer select.
+ */
+ bool found_in_select_list;
+ bool found_in_group_by;
+ Item_outer_ref(Name_resolution_context *context_arg,
+ Item_field *outer_field_arg)
+ :Item_direct_ref(context_arg, 0, outer_field_arg->table_name,
+ outer_field_arg->field_name),
+ outer_ref(outer_field_arg), in_sum_func(0),
+ found_in_select_list(0), found_in_group_by(0)
+ {
+ ref= &outer_ref;
+ set_properties();
+ fixed= 0; /* reset flag set in set_properties() */
+ }
+ Item_outer_ref(Name_resolution_context *context_arg, Item **item,
+ const char *table_name_arg, const char *field_name_arg,
+ bool alias_name_used_arg)
+ :Item_direct_ref(context_arg, item, table_name_arg, field_name_arg,
+ alias_name_used_arg),
+ outer_ref(0), in_sum_func(0), found_in_select_list(1), found_in_group_by(0)
+ {}
+ void save_in_result_field(bool no_conversions)
+ {
+ outer_ref->save_org_in_field(result_field, NULL);
+ }
+ bool fix_fields(THD *, Item **);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ table_map used_tables() const
+ {
+ return (*ref)->const_item() ? 0 : OUTER_REF_TABLE_BIT;
+ }
+ table_map not_null_tables() const { return 0; }
+ virtual Ref_Type ref_type() { return OUTER_REF; }
+ bool check_inner_refs_processor(uchar * arg);
+};
+
+
+class Item_in_subselect;
+
+
+/*
+ An object of this class:
+ - Converts val_XXX() calls to ref->val_XXX_result() calls, like Item_ref.
+ - Sets owner->was_null=TRUE if it has returned a NULL value from any
+ val_XXX() function. This allows to inject an Item_ref_null_helper
+ object into subquery and then check if the subquery has produced a row
+ with NULL value.
+*/
+
+class Item_ref_null_helper: public Item_ref
+{
+protected:
+ Item_in_subselect* owner;
+public:
+ Item_ref_null_helper(Name_resolution_context *context_arg,
+ Item_in_subselect* master, Item **item,
+ const char *table_name_arg, const char *field_name_arg)
+ :Item_ref(context_arg, item, table_name_arg, field_name_arg),
+ owner(master) {}
+ void save_val(Field *to);
+ double val_real();
+ longlong val_int();
+ String* val_str(String* s);
+ my_decimal *val_decimal(my_decimal *);
+ bool val_bool();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ virtual void print(String *str, enum_query_type query_type);
+ table_map used_tables() const;
+};
+
+/*
+ The following class is used to optimize comparing of date and bigint columns
+ We need to save the original item ('ref') to be able to call
+ ref->save_in_field(). This is used to create index search keys.
+
+ An instance of Item_int_with_ref may have signed or unsigned integer value.
+
+*/
+
+class Item_int_with_ref :public Item_int
+{
+ Item *ref;
+public:
+ Item_int_with_ref(longlong i, Item *ref_arg, bool unsigned_arg) :
+ Item_int(i), ref(ref_arg)
+ {
+ unsigned_flag= unsigned_arg;
+ }
+ int save_in_field(Field *field, bool no_conversions)
+ {
+ return ref->save_in_field(field, no_conversions);
+ }
+ Item *clone_item();
+ virtual Item *real_item() { return ref; }
+};
+
+#ifdef MYSQL_SERVER
+#include "gstream.h"
+#include "spatial.h"
+#include "item_sum.h"
+#include "item_func.h"
+#include "item_row.h"
+#include "item_cmpfunc.h"
+#include "item_strfunc.h"
+#include "item_geofunc.h"
+#include "item_timefunc.h"
+#include "item_subselect.h"
+#include "item_xmlfunc.h"
+#include "item_create.h"
+#endif
+
+/**
+ Base class to implement typed value caching Item classes
+
+ Item_copy_ classes are very similar to the corresponding Item_
+ classes (e.g. Item_copy_int is similar to Item_int) but they add
+ the following additional functionality to Item_ :
+ 1. Nullability
+ 2. Possibility to store the value not only on instantiation time,
+ but also later.
+ Item_copy_ classes are a functionality subset of Item_cache_
+ classes, as e.g. they don't support comparisons with the original Item
+ as Item_cache_ classes do.
+ Item_copy_ classes are used in GROUP BY calculation.
+ TODO: Item_copy should be made an abstract interface and Item_copy_
+ classes should inherit both the respective Item_ class and the interface.
+ Ideally we should drop Item_copy_ classes altogether and merge
+ their functionality to Item_cache_ (and these should be made to inherit
+ from Item_).
+*/
+
+class Item_copy :public Item
+{
+protected:
+
+ /**
+ Stores the type of the resulting field that would be used to store the data
+ in the cache. This is to avoid calls to the original item.
+ */
+ enum enum_field_types cached_field_type;
+
+ /** The original item that is copied */
+ Item *item;
+
+ /**
+ Stores the result type of the original item, so it can be returned
+ without calling the original item's method
+ */
+ Item_result cached_result_type;
+
+ /**
+ Constructor of the Item_copy class
+
+ stores metadata information about the original class as well as a
+ pointer to it.
+ */
+ Item_copy(Item *i)
+ {
+ item= i;
+ null_value=maybe_null=item->maybe_null;
+ decimals=item->decimals;
+ max_length=item->max_length;
+ name=item->name;
+ cached_field_type= item->field_type();
+ cached_result_type= item->result_type();
+ unsigned_flag= item->unsigned_flag;
+ fixed= item->fixed;
+ collation.set(item->collation);
+ }
+
+public:
+ /**
+ Factory method to create the appropriate subclass dependent on the type of
+ the original item.
+
+ @param item the original item.
+ */
+ static Item_copy *create (Item *item);
+
+ /**
+ Update the cache with the value of the original item
+
+ This is the method that updates the cached value.
+ It must be explicitly called by the user of this class to store the value
+ of the orginal item in the cache.
+ */
+ virtual void copy() = 0;
+
+ Item *get_item() { return item; }
+ /** All of the subclasses should have the same type tag */
+ enum Type type() const { return COPY_STR_ITEM; }
+ enum_field_types field_type() const { return cached_field_type; }
+ enum Item_result result_type () const { return cached_result_type; }
+
+ void make_field(Send_field *field) { item->make_field(field); }
+ table_map used_tables() const { return (table_map) 1L; }
+ bool const_item() const { return 0; }
+ bool is_null() { return null_value; }
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("copy");
+ }
+
+ /*
+ Override the methods below as pure virtual to make sure all the
+ sub-classes implement them.
+ */
+
+ virtual String *val_str(String*) = 0;
+ virtual my_decimal *val_decimal(my_decimal *) = 0;
+ virtual double val_real() = 0;
+ virtual longlong val_int() = 0;
+ virtual int save_in_field(Field *field, bool no_conversions) = 0;
+};
+
+/**
+ Implementation of a string cache.
+
+ Uses Item::str_value for storage
+*/
+class Item_copy_string : public Item_copy
+{
+public:
+ Item_copy_string (Item *item) : Item_copy(item) {}
+
+ String *val_str(String*);
+ my_decimal *val_decimal(my_decimal *);
+ double val_real();
+ longlong val_int();
+ void copy();
+ int save_in_field(Field *field, bool no_conversions);
+};
+
+
+class Item_copy_int : public Item_copy
+{
+protected:
+ longlong cached_value;
+public:
+ Item_copy_int (Item *i) : Item_copy(i) {}
+ int save_in_field(Field *field, bool no_conversions);
+
+ virtual String *val_str(String*);
+ virtual my_decimal *val_decimal(my_decimal *);
+ virtual double val_real()
+ {
+ return null_value ? 0.0 : (double) cached_value;
+ }
+ virtual longlong val_int()
+ {
+ return null_value ? 0 : cached_value;
+ }
+ virtual void copy();
+};
+
+
+class Item_copy_uint : public Item_copy_int
+{
+public:
+ Item_copy_uint (Item *item) : Item_copy_int(item)
+ {
+ unsigned_flag= 1;
+ }
+
+ String *val_str(String*);
+ double val_real()
+ {
+ return null_value ? 0.0 : (double) (ulonglong) cached_value;
+ }
+};
+
+
+class Item_copy_float : public Item_copy
+{
+protected:
+ double cached_value;
+public:
+ Item_copy_float (Item *i) : Item_copy(i) {}
+ int save_in_field(Field *field, bool no_conversions);
+
+ String *val_str(String*);
+ my_decimal *val_decimal(my_decimal *);
+ double val_real()
+ {
+ return null_value ? 0.0 : cached_value;
+ }
+ longlong val_int()
+ {
+ return (longlong) rint(val_real());
+ }
+ void copy()
+ {
+ cached_value= item->val_real();
+ null_value= item->null_value;
+ }
+};
+
+
+class Item_copy_decimal : public Item_copy
+{
+protected:
+ my_decimal cached_value;
+public:
+ Item_copy_decimal (Item *i) : Item_copy(i) {}
+ int save_in_field(Field *field, bool no_conversions);
+
+ String *val_str(String*);
+ my_decimal *val_decimal(my_decimal *)
+ {
+ return null_value ? NULL: &cached_value;
+ }
+ double val_real();
+ longlong val_int();
+ void copy();
+};
+
+
+/*
+ Cached_item_XXX objects are not exactly caches. They do the following:
+
+ Each Cached_item_XXX object has
+ - its source item
+ - saved value of the source item
+ - cmp() method that compares the saved value with the current value of the
+ source item, and if they were not equal saves item's value into the saved
+ value.
+*/
+
+/*
+ Cached_item_XXX objects are not exactly caches. They do the following:
+
+ Each Cached_item_XXX object has
+ - its source item
+ - saved value of the source item
+ - cmp() method that compares the saved value with the current value of the
+ source item, and if they were not equal saves item's value into the saved
+ value.
+*/
+
+class Cached_item :public Sql_alloc
+{
+public:
+ bool null_value;
+ Cached_item() :null_value(0) {}
+ virtual bool cmp(void)=0;
+ virtual ~Cached_item(); /*line -e1509 */
+};
+
+class Cached_item_str :public Cached_item
+{
+ Item *item;
+ uint32 value_max_length;
+ String value,tmp_value;
+public:
+ Cached_item_str(THD *thd, Item *arg);
+ bool cmp(void);
+ ~Cached_item_str(); // Deallocate String:s
+};
+
+
+class Cached_item_real :public Cached_item
+{
+ Item *item;
+ double value;
+public:
+ Cached_item_real(Item *item_par) :item(item_par),value(0.0) {}
+ bool cmp(void);
+};
+
+class Cached_item_int :public Cached_item
+{
+ Item *item;
+ longlong value;
+public:
+ Cached_item_int(Item *item_par) :item(item_par),value(0) {}
+ bool cmp(void);
+};
+
+
+class Cached_item_decimal :public Cached_item
+{
+ Item *item;
+ my_decimal value;
+public:
+ Cached_item_decimal(Item *item_par);
+ bool cmp(void);
+};
+
+class Cached_item_field :public Cached_item
+{
+ uchar *buff;
+ Field *field;
+ uint length;
+
+public:
+ Cached_item_field(Field *arg_field) : field(arg_field)
+ {
+ field= arg_field;
+ /* TODO: take the memory allocation below out of the constructor. */
+ buff= (uchar*) sql_calloc(length=field->pack_length());
+ }
+ bool cmp(void);
+};
+
+class Item_default_value : public Item_field
+{
+public:
+ Item *arg;
+ Item_default_value(Name_resolution_context *context_arg)
+ :Item_field(context_arg, (const char *)NULL, (const char *)NULL,
+ (const char *)NULL),
+ arg(NULL) {}
+ Item_default_value(Name_resolution_context *context_arg, Item *a)
+ :Item_field(context_arg, (const char *)NULL, (const char *)NULL,
+ (const char *)NULL),
+ arg(a) {}
+ enum Type type() const { return DEFAULT_VALUE_ITEM; }
+ bool eq(const Item *item, bool binary_cmp) const;
+ bool fix_fields(THD *, Item **);
+ virtual void print(String *str, enum_query_type query_type);
+ int save_in_field(Field *field_arg, bool no_conversions);
+ table_map used_tables() const { return (table_map)0L; }
+
+ bool walk(Item_processor processor, bool walk_subquery, uchar *args)
+ {
+ return (arg && arg->walk(processor, walk_subquery, args)) ||
+ (this->*processor)(args);
+ }
+
+ Item *transform(Item_transformer transformer, uchar *args);
+};
+
+/*
+ Item_insert_value -- an implementation of VALUES() function.
+ You can use the VALUES(col_name) function in the UPDATE clause
+ to refer to column values from the INSERT portion of the INSERT
+ ... UPDATE statement. In other words, VALUES(col_name) in the
+ UPDATE clause refers to the value of col_name that would be
+ inserted, had no duplicate-key conflict occurred.
+ In all other places this function returns NULL.
+*/
+
+class Item_insert_value : public Item_field
+{
+public:
+ Item *arg;
+ Item_insert_value(Name_resolution_context *context_arg, Item *a)
+ :Item_field(context_arg, (const char *)NULL, (const char *)NULL,
+ (const char *)NULL),
+ arg(a) {}
+ bool eq(const Item *item, bool binary_cmp) const;
+ bool fix_fields(THD *, Item **);
+ virtual void print(String *str, enum_query_type query_type);
+ int save_in_field(Field *field_arg, bool no_conversions)
+ {
+ return Item_field::save_in_field(field_arg, no_conversions);
+ }
+ enum Type type() const { return INSERT_VALUE_ITEM; }
+ /*
+ We use RAND_TABLE_BIT to prevent Item_insert_value from
+ being treated as a constant and precalculated before execution
+ */
+ table_map used_tables() const { return RAND_TABLE_BIT; }
+
+ bool walk(Item_processor processor, bool walk_subquery, uchar *args)
+ {
+ return arg->walk(processor, walk_subquery, args) ||
+ (this->*processor)(args);
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return TRUE;}
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("values");
+ }
+};
+
+
+class Table_triggers_list;
+
+/*
+ Represents NEW/OLD version of field of row which is
+ changed/read in trigger.
+
+ Note: For this item main part of actual binding to Field object happens
+ not during fix_fields() call (like for Item_field) but right after
+ parsing of trigger definition, when table is opened, with special
+ setup_field() call. On fix_fields() stage we simply choose one of
+ two Field instances representing either OLD or NEW version of this
+ field.
+*/
+class Item_trigger_field : public Item_field,
+ private Settable_routine_parameter
+{
+public:
+ /* Is this item represents row from NEW or OLD row ? */
+ enum row_version_type {OLD_ROW, NEW_ROW};
+ row_version_type row_version;
+ /* Next in list of all Item_trigger_field's in trigger */
+ Item_trigger_field *next_trg_field;
+ /* Index of the field in the TABLE::field array */
+ uint field_idx;
+ /* Pointer to Table_trigger_list object for table of this trigger */
+ Table_triggers_list *triggers;
+
+ Item_trigger_field(Name_resolution_context *context_arg,
+ row_version_type row_ver_arg,
+ const char *field_name_arg,
+ ulong priv, const bool ro)
+ :Item_field(context_arg,
+ (const char *)NULL, (const char *)NULL, field_name_arg),
+ row_version(row_ver_arg), field_idx((uint)-1), original_privilege(priv),
+ want_privilege(priv), table_grants(NULL), read_only (ro)
+ {}
+ void setup_field(THD *thd, TABLE *table, GRANT_INFO *table_grant_info);
+ enum Type type() const { return TRIGGER_FIELD_ITEM; }
+ bool eq(const Item *item, bool binary_cmp) const;
+ bool fix_fields(THD *, Item **);
+ virtual void print(String *str, enum_query_type query_type);
+ table_map used_tables() const { return (table_map)0L; }
+ Field *get_tmp_table_field() { return 0; }
+ Item *copy_or_same(THD *thd) { return this; }
+ Item *get_tmp_table_item(THD *thd) { return copy_or_same(thd); }
+ void cleanup();
+
+private:
+ void set_required_privilege(bool rw);
+ bool set_value(THD *thd, sp_rcontext *ctx, Item **it);
+
+public:
+ Settable_routine_parameter *get_settable_routine_parameter()
+ {
+ return (read_only ? 0 : this);
+ }
+
+ bool set_value(THD *thd, Item **it)
+ {
+ return set_value(thd, NULL, it);
+ }
+
+private:
+ /*
+ 'want_privilege' holds privileges required to perform operation on
+ this trigger field (SELECT_ACL if we are going to read it and
+ UPDATE_ACL if we are going to update it). It is initialized at
+ parse time but can be updated later if this trigger field is used
+ as OUT or INOUT parameter of stored routine (in this case
+ set_required_privilege() is called to appropriately update
+ want_privilege and cleanup() is responsible for restoring of
+ original want_privilege once parameter's value is updated).
+ */
+ ulong original_privilege;
+ ulong want_privilege;
+ GRANT_INFO *table_grants;
+ /*
+ Trigger field is read-only unless it belongs to the NEW row in a
+ BEFORE INSERT of BEFORE UPDATE trigger.
+ */
+ bool read_only;
+ virtual bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("trigger");
+ }
+};
+
+
+/**
+ @todo
+ Implement the is_null() method for this class. Currently calling is_null()
+ on any Item_cache object resolves to Item::is_null(), which returns FALSE
+ for any value.
+*/
+
+class Item_cache: public Item_basic_constant
+{
+protected:
+ Item *example;
+ table_map used_table_map;
+ /**
+ Field that this object will get value from. This is used by
+ index-based subquery engines to detect and remove the equality injected
+ by IN->EXISTS transformation.
+ */
+ Field *cached_field;
+ enum enum_field_types cached_field_type;
+ /*
+ TRUE <=> cache holds value of the last stored item (i.e actual value).
+ store() stores item to be cached and sets this flag to FALSE.
+ On the first call of val_xxx function if this flag is set to FALSE the
+ cache_value() will be called to actually cache value of saved item.
+ cache_value() will set this flag to TRUE.
+ */
+ bool value_cached;
+public:
+ Item_cache():
+ example(0), used_table_map(0), cached_field(0),
+ cached_field_type(MYSQL_TYPE_STRING),
+ value_cached(0)
+ {
+ fixed= 1;
+ maybe_null= 1;
+ null_value= 1;
+ }
+ Item_cache(enum_field_types field_type_arg):
+ example(0), used_table_map(0), cached_field(0),
+ cached_field_type(field_type_arg),
+ value_cached(0)
+ {
+ fixed= 1;
+ maybe_null= 1;
+ null_value= 1;
+ }
+
+ void set_used_tables(table_map map) { used_table_map= map; }
+
+ virtual bool allocate(uint i) { return 0; }
+ virtual bool setup(Item *item)
+ {
+ example= item;
+ max_length= item->max_length;
+ decimals= item->decimals;
+ collation.set(item->collation);
+ unsigned_flag= item->unsigned_flag;
+ if (item->type() == FIELD_ITEM)
+ cached_field= ((Item_field *)item)->field;
+ return 0;
+ };
+ enum Type type() const { return CACHE_ITEM; }
+ enum_field_types field_type() const { return cached_field_type; }
+ static Item_cache* get_cache(const Item *item);
+ static Item_cache* get_cache(const Item* item, const Item_result type);
+ table_map used_tables() const { return used_table_map; }
+ virtual void keep_array() {}
+ virtual void print(String *str, enum_query_type query_type);
+ bool eq_def(Field *field)
+ {
+ return cached_field ? cached_field->eq_def (field) : FALSE;
+ }
+ bool eq(const Item *item, bool binary_cmp) const
+ {
+ return this == item;
+ }
+ bool check_vcol_func_processor(uchar *arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("cache");
+ }
+ /**
+ Check if saved item has a non-NULL value.
+ Will cache value of saved item if not already done.
+ @return TRUE if cached value is non-NULL.
+ */
+ bool has_value()
+ {
+ return (value_cached || cache_value()) && !null_value;
+ }
+
+ virtual void store(Item *item);
+ virtual bool cache_value()= 0;
+ bool basic_const_item() const
+ { return MY_TEST(example && example->basic_const_item()); }
+ virtual void clear() { null_value= TRUE; value_cached= FALSE; }
+ bool is_null() { return null_value; }
+ virtual bool is_expensive()
+ {
+ if (value_cached)
+ return false;
+ return example->is_expensive();
+ }
+ bool is_expensive_processor(uchar *arg)
+ {
+ DBUG_ASSERT(example);
+ if (value_cached)
+ return false;
+ return example->is_expensive_processor(arg);
+ }
+ virtual void set_null();
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg)
+ {
+ if (example && example->walk(processor, walk_subquery, arg))
+ return TRUE;
+ return (this->*processor)(arg);
+ }
+};
+
+
+class Item_cache_int: public Item_cache
+{
+protected:
+ longlong value;
+public:
+ Item_cache_int(): Item_cache(MYSQL_TYPE_LONGLONG),
+ value(0) {}
+ Item_cache_int(enum_field_types field_type_arg):
+ Item_cache(field_type_arg), value(0) {}
+
+ double val_real();
+ longlong val_int();
+ String* val_str(String *str);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type() const { return INT_RESULT; }
+ bool cache_value();
+ int save_in_field(Field *field, bool no_conversions);
+};
+
+
+class Item_cache_temporal: public Item_cache_int
+{
+public:
+ Item_cache_temporal(enum_field_types field_type_arg);
+ String* val_str(String *str);
+ my_decimal *val_decimal(my_decimal *);
+ longlong val_int();
+ longlong val_temporal_packed();
+ double val_real();
+ bool cache_value();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ int save_in_field(Field *field, bool no_conversions);
+ Item_result cmp_type() const { return TIME_RESULT; }
+ void store_packed(longlong val_arg, Item *example);
+ /*
+ Having a clone_item method tells optimizer that this object
+ is a constant and need not be optimized further.
+ Important when storing packed datetime values.
+ */
+ Item *clone_item()
+ {
+ Item_cache_temporal *item= new Item_cache_temporal(cached_field_type);
+ item->store_packed(value, example);
+ return item;
+ }
+};
+
+
+class Item_cache_real: public Item_cache
+{
+ double value;
+public:
+ Item_cache_real(): Item_cache(MYSQL_TYPE_DOUBLE),
+ value(0) {}
+
+ double val_real();
+ longlong val_int();
+ String* val_str(String *str);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type() const { return REAL_RESULT; }
+ bool cache_value();
+};
+
+
+class Item_cache_decimal: public Item_cache
+{
+protected:
+ my_decimal decimal_value;
+public:
+ Item_cache_decimal(): Item_cache(MYSQL_TYPE_NEWDECIMAL) {}
+
+ double val_real();
+ longlong val_int();
+ String* val_str(String *str);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type() const { return DECIMAL_RESULT; }
+ bool cache_value();
+};
+
+
+class Item_cache_str: public Item_cache
+{
+ char buffer[STRING_BUFFER_USUAL_SIZE];
+ String *value, value_buff;
+ bool is_varbinary;
+
+public:
+ Item_cache_str(const Item *item) :
+ Item_cache(item->field_type()), value(0),
+ is_varbinary(item->type() == FIELD_ITEM &&
+ cached_field_type == MYSQL_TYPE_VARCHAR &&
+ !((const Item_field *) item)->field->has_charset())
+ {
+ collation.set(const_cast<DTCollation&>(item->collation));
+ }
+ double val_real();
+ longlong val_int();
+ String* val_str(String *);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type() const { return STRING_RESULT; }
+ CHARSET_INFO *charset() const { return value->charset(); };
+ int save_in_field(Field *field, bool no_conversions);
+ bool cache_value();
+};
+
+class Item_cache_row: public Item_cache
+{
+ Item_cache **values;
+ uint item_count;
+ bool save_array;
+public:
+ Item_cache_row()
+ :Item_cache(), values(0), item_count(2),
+ save_array(0) {}
+
+ /*
+ 'allocate' used only in row transformer, to preallocate space for row
+ cache.
+ */
+ bool allocate(uint num);
+ /*
+ 'setup' is needed only by row => it not called by simple row subselect
+ (only by IN subselect (in subselect optimizer))
+ */
+ bool setup(Item *item);
+ void store(Item *item);
+ void illegal_method_call(const char *);
+ void make_field(Send_field *)
+ {
+ illegal_method_call((const char*)"make_field");
+ };
+ double val_real()
+ {
+ illegal_method_call((const char*)"val");
+ return 0;
+ };
+ longlong val_int()
+ {
+ illegal_method_call((const char*)"val_int");
+ return 0;
+ };
+ String *val_str(String *)
+ {
+ illegal_method_call((const char*)"val_str");
+ return 0;
+ };
+ my_decimal *val_decimal(my_decimal *val)
+ {
+ illegal_method_call((const char*)"val_decimal");
+ return 0;
+ };
+
+ enum Item_result result_type() const { return ROW_RESULT; }
+
+ uint cols() { return item_count; }
+ Item *element_index(uint i) { return values[i]; }
+ Item **addr(uint i) { return (Item **) (values + i); }
+ bool check_cols(uint c);
+ bool null_inside();
+ void bring_value();
+ void keep_array() { save_array= 1; }
+ void cleanup()
+ {
+ DBUG_ENTER("Item_cache_row::cleanup");
+ Item_cache::cleanup();
+ if (save_array)
+ bzero(values, item_count*sizeof(Item**));
+ else
+ values= 0;
+ DBUG_VOID_RETURN;
+ }
+ bool cache_value();
+ virtual void set_null();
+};
+
+
+/*
+ Item_type_holder used to store type. name, length of Item for UNIONS &
+ derived tables.
+
+ Item_type_holder do not need cleanup() because its time of live limited by
+ single SP/PS execution.
+*/
+class Item_type_holder: public Item
+{
+protected:
+ TYPELIB *enum_set_typelib;
+ enum_field_types fld_type;
+ Field::geometry_type geometry_type;
+
+ void get_full_info(Item *item);
+
+ /* It is used to count decimal precision in join_types */
+ int prev_decimal_int_part;
+public:
+ Item_type_holder(THD*, Item*);
+
+ Item_result result_type() const;
+ enum_field_types field_type() const { return fld_type; };
+ enum Type type() const { return TYPE_HOLDER; }
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *);
+ String *val_str(String*);
+ bool join_types(THD *thd, Item *);
+ Field *make_field_by_type(TABLE *table);
+ static uint32 display_length(Item *item);
+ static enum_field_types get_real_type(Item *);
+ Field::geometry_type get_geometry_type() const { return geometry_type; };
+};
+
+
+class st_select_lex;
+void mark_select_range_as_dependent(THD *thd,
+ st_select_lex *last_select,
+ st_select_lex *current_sel,
+ Field *found_field, Item *found_item,
+ Item_ident *resolved_item);
+
+extern Cached_item *new_Cached_item(THD *thd, Item *item,
+ bool pass_through_ref);
+extern Item_result item_cmp_type(Item_result a,Item_result b);
+extern void resolve_const_item(THD *thd, Item **ref, Item *cmp_item);
+extern int stored_field_cmp_to_item(THD *thd, Field *field, Item *item);
+
+extern const String my_null_string;
+
+/**
+ Interface for Item iterator
+*/
+
+class Item_iterator
+{
+public:
+ /**
+ Shall set this iterator to the position before the first item
+
+ @note
+ This method also may perform some other initialization actions like
+ allocation of certain resources.
+ */
+ virtual void open()= 0;
+ /**
+ Shall return the next Item (or NULL if there is no next item) and
+ move pointer to position after it.
+ */
+ virtual Item *next()= 0;
+ /**
+ Shall force iterator to free resources (if it holds them)
+
+ @note
+ One should not use the iterator without open() call after close()
+ */
+ virtual void close()= 0;
+
+ virtual ~Item_iterator() {}
+};
+
+
+/**
+ Item iterator over List_iterator_fast for Item references
+*/
+
+class Item_iterator_ref_list: public Item_iterator
+{
+ List_iterator<Item*> list;
+public:
+ Item_iterator_ref_list(List_iterator<Item*> &arg_list):
+ list(arg_list) {}
+ void open() { list.rewind(); }
+ Item *next() { return *(list++); }
+ void close() {}
+};
+
+
+/**
+ Item iterator over List_iterator_fast for Items
+*/
+
+class Item_iterator_list: public Item_iterator
+{
+ List_iterator<Item> list;
+public:
+ Item_iterator_list(List_iterator<Item> &arg_list):
+ list(arg_list) {}
+ void open() { list.rewind(); }
+ Item *next() { return (list++); }
+ void close() {}
+};
+
+
+/**
+ Item iterator over Item interface for rows
+*/
+
+class Item_iterator_row: public Item_iterator
+{
+ Item *base_item;
+ uint current;
+public:
+ Item_iterator_row(Item *base) : base_item(base), current(0) {}
+ void open() { current= 0; }
+ Item *next()
+ {
+ if (current >= base_item->cols())
+ return NULL;
+ return base_item->element_index(current++);
+ }
+ void close() {}
+};
+
+#endif /* SQL_ITEM_INCLUDED */
diff --git a/sql/item_buff.cc b/sql/item_buff.cc
new file mode 100644
index 00000000000..d1134525f7b
--- /dev/null
+++ b/sql/item_buff.cc
@@ -0,0 +1,176 @@
+/*
+ Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+
+/**
+ @file
+
+ @brief
+ Buffers to save and compare item values
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // THD
+#include "set_var.h" // Cached_item, Cached_item_field, ...
+
+/**
+ Create right type of Cached_item for an item.
+*/
+
+Cached_item *new_Cached_item(THD *thd, Item *item, bool pass_through_ref)
+{
+ if (pass_through_ref && item->real_item()->type() == Item::FIELD_ITEM &&
+ !(((Item_field *) (item->real_item()))->field->flags & BLOB_FLAG))
+ {
+ Item_field *real_item= (Item_field *) item->real_item();
+ Field *cached_field= real_item->field;
+ return new Cached_item_field(cached_field);
+ }
+ switch (item->result_type()) {
+ case STRING_RESULT:
+ return new Cached_item_str(thd, (Item_field *) item);
+ case INT_RESULT:
+ return new Cached_item_int((Item_field *) item);
+ case REAL_RESULT:
+ return new Cached_item_real(item);
+ case DECIMAL_RESULT:
+ return new Cached_item_decimal(item);
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0);
+ return 0;
+ }
+}
+
+Cached_item::~Cached_item() {}
+
+/**
+ Compare with old value and replace value with new value.
+
+ @return
+ Return true if values have changed
+*/
+
+Cached_item_str::Cached_item_str(THD *thd, Item *arg)
+ :item(arg),
+ value_max_length(MY_MIN(arg->max_length, thd->variables.max_sort_length)),
+ value(value_max_length)
+{}
+
+bool Cached_item_str::cmp(void)
+{
+ String *res;
+ bool tmp;
+
+ if ((res=item->val_str(&tmp_value)))
+ res->length(MY_MIN(res->length(), value_max_length));
+ if (null_value != item->null_value)
+ {
+ if ((null_value= item->null_value))
+ return TRUE; // New value was null
+ tmp=TRUE;
+ }
+ else if (null_value)
+ return 0; // new and old value was null
+ else
+ tmp= sortcmp(&value,res,item->collation.collation) != 0;
+ if (tmp)
+ value.copy(*res); // Remember for next cmp
+ return tmp;
+}
+
+Cached_item_str::~Cached_item_str()
+{
+ item=0; // Safety
+}
+
+bool Cached_item_real::cmp(void)
+{
+ double nr= item->val_real();
+ if (null_value != item->null_value || nr != value)
+ {
+ null_value= item->null_value;
+ value=nr;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+bool Cached_item_int::cmp(void)
+{
+ longlong nr=item->val_int();
+ if (null_value != item->null_value || nr != value)
+ {
+ null_value= item->null_value;
+ value=nr;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+bool Cached_item_field::cmp(void)
+{
+ bool tmp= FALSE; // Value is identical
+ /* Note that field can't be a blob here ! */
+ if (null_value != field->is_null())
+ {
+ null_value= !null_value;
+ tmp= TRUE; // Value has changed
+ }
+
+ /*
+ If value is not null and value changed (from null to not null or
+ becasue of value change), then copy the new value to buffer.
+ */
+ if (! null_value && (tmp || (tmp= (field->cmp(buff) != 0))))
+ field->get_image(buff,length,field->charset());
+ return tmp;
+}
+
+
+Cached_item_decimal::Cached_item_decimal(Item *it)
+ :item(it)
+{
+ my_decimal_set_zero(&value);
+}
+
+
+bool Cached_item_decimal::cmp()
+{
+ my_decimal tmp;
+ my_decimal *ptmp= item->val_decimal(&tmp);
+ if (null_value != item->null_value ||
+ (!item->null_value && my_decimal_cmp(&value, ptmp)))
+ {
+ null_value= item->null_value;
+ /* Save only not null values */
+ if (!null_value)
+ {
+ my_decimal2decimal(ptmp, &value);
+ return TRUE;
+ }
+ return FALSE;
+ }
+ return FALSE;
+}
+
diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
new file mode 100644
index 00000000000..1f1982ffb80
--- /dev/null
+++ b/sql/item_cmpfunc.cc
@@ -0,0 +1,6522 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/**
+ @file
+
+ @brief
+ This file defines all compare functions
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include <m_ctype.h>
+#include "sql_select.h"
+#include "sql_parse.h" // check_stack_overrun
+#include "sql_time.h" // make_truncated_value_warning
+#include "sql_base.h" // dynamic_column_error_message
+
+static Item_result item_store_type(Item_result a, Item *item,
+ my_bool unsigned_flag)
+{
+ Item_result b= item->result_type();
+
+ if (a == STRING_RESULT || b == STRING_RESULT)
+ return STRING_RESULT;
+ else if (a == REAL_RESULT || b == REAL_RESULT)
+ return REAL_RESULT;
+ else if (a == DECIMAL_RESULT || b == DECIMAL_RESULT ||
+ unsigned_flag != item->unsigned_flag)
+ return DECIMAL_RESULT;
+ else
+ return INT_RESULT;
+}
+
+static void agg_result_type(Item_result *type, Item **items, uint nitems)
+{
+ Item **item, **item_end;
+ my_bool unsigned_flag= 0;
+
+ *type= STRING_RESULT;
+ /* Skip beginning NULL items */
+ for (item= items, item_end= item + nitems; item < item_end; item++)
+ {
+ if ((*item)->type() != Item::NULL_ITEM)
+ {
+ *type= (*item)->result_type();
+ unsigned_flag= (*item)->unsigned_flag;
+ item++;
+ break;
+ }
+ }
+ /* Combine result types. Note: NULL items don't affect the result */
+ for (; item < item_end; item++)
+ {
+ if ((*item)->type() != Item::NULL_ITEM)
+ *type= item_store_type(*type, *item, unsigned_flag);
+ }
+}
+
+
+/**
+ find an temporal type (item) that others will be converted to
+ for the purpose of comparison.
+
+ this is the type that will be used in warnings like
+ "Incorrect <<TYPE>> value".
+*/
+Item *find_date_time_item(Item **args, uint nargs, uint col)
+{
+ Item *date_arg= 0, **arg, **arg_end;
+ for (arg= args, arg_end= args + nargs; arg != arg_end ; arg++)
+ {
+ Item *item= arg[0]->element_index(col);
+ if (item->cmp_type() != TIME_RESULT)
+ continue;
+ if (item->field_type() == MYSQL_TYPE_DATETIME)
+ return item;
+ if (!date_arg)
+ date_arg= item;
+ }
+ return date_arg;
+}
+
+
+/*
+ Compare row signature of two expressions
+
+ SYNOPSIS:
+ cmp_row_type()
+ item1 the first expression
+ item2 the second expression
+
+ DESCRIPTION
+ The function checks that two expressions have compatible row signatures
+ i.e. that the number of columns they return are the same and that if they
+ are both row expressions then each component from the first expression has
+ a row signature compatible with the signature of the corresponding component
+ of the second expression.
+
+ RETURN VALUES
+ 1 type incompatibility has been detected
+ 0 otherwise
+*/
+
+static int cmp_row_type(Item* item1, Item* item2)
+{
+ uint n= item1->cols();
+ if (item2->check_cols(n))
+ return 1;
+ for (uint i=0; i<n; i++)
+ {
+ if (item2->element_index(i)->check_cols(item1->element_index(i)->cols()) ||
+ (item1->element_index(i)->result_type() == ROW_RESULT &&
+ cmp_row_type(item1->element_index(i), item2->element_index(i))))
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Aggregates result types from the array of items.
+
+ SYNOPSIS:
+ agg_cmp_type()
+ type [out] the aggregated type
+ items array of items to aggregate the type from
+ nitems number of items in the array
+
+ DESCRIPTION
+ This function aggregates result types from the array of items. Found type
+ supposed to be used later for comparison of values of these items.
+ Aggregation itself is performed by the item_cmp_type() function.
+ @param[out] type the aggregated type
+ @param items array of items to aggregate the type from
+ @param nitems number of items in the array
+
+ @retval
+ 1 type incompatibility has been detected
+ @retval
+ 0 otherwise
+*/
+
+static int agg_cmp_type(Item_result *type, Item **items, uint nitems)
+{
+ uint i;
+ type[0]= items[0]->cmp_type();
+ for (i= 1 ; i < nitems ; i++)
+ {
+ type[0]= item_cmp_type(type[0], items[i]->cmp_type());
+ /*
+ When aggregating types of two row expressions we have to check
+ that they have the same cardinality and that each component
+ of the first row expression has a compatible row signature with
+ the signature of the corresponding component of the second row
+ expression.
+ */
+ if (type[0] == ROW_RESULT && cmp_row_type(items[0], items[i]))
+ return 1; // error found: invalid usage of rows
+ }
+ return 0;
+}
+
+
+/**
+ @brief Aggregates field types from the array of items.
+
+ @param[in] items array of items to aggregate the type from
+ @paran[in] nitems number of items in the array
+
+ @details This function aggregates field types from the array of items.
+ Found type is supposed to be used later as the result field type
+ of a multi-argument function.
+ Aggregation itself is performed by the Field::field_type_merge()
+ function.
+
+ @note The term "aggregation" is used here in the sense of inferring the
+ result type of a function from its argument types.
+
+ @return aggregated field type.
+*/
+
+enum_field_types agg_field_type(Item **items, uint nitems)
+{
+ uint i;
+ if (!nitems || items[0]->result_type() == ROW_RESULT )
+ return (enum_field_types)-1;
+ enum_field_types res= items[0]->field_type();
+ for (i= 1 ; i < nitems ; i++)
+ res= Field::field_type_merge(res, items[i]->field_type());
+ return res;
+}
+
+/*
+ Collects different types for comparison of first item with each other items
+
+ SYNOPSIS
+ collect_cmp_types()
+ items Array of items to collect types from
+ nitems Number of items in the array
+ skip_nulls Don't collect types of NULL items if TRUE
+
+ DESCRIPTION
+ This function collects different result types for comparison of the first
+ item in the list with each of the remaining items in the 'items' array.
+
+ RETURN
+ 0 - if row type incompatibility has been detected (see cmp_row_type)
+ Bitmap of collected types - otherwise
+*/
+
+static uint collect_cmp_types(Item **items, uint nitems, bool skip_nulls= FALSE)
+{
+ uint i;
+ uint found_types;
+ Item_result left_result= items[0]->cmp_type();
+ DBUG_ASSERT(nitems > 1);
+ found_types= 0;
+ for (i= 1; i < nitems ; i++)
+ {
+ if (skip_nulls && items[i]->type() == Item::NULL_ITEM)
+ continue; // Skip NULL constant items
+ if ((left_result == ROW_RESULT ||
+ items[i]->cmp_type() == ROW_RESULT) &&
+ cmp_row_type(items[0], items[i]))
+ return 0;
+ found_types|= 1U << (uint)item_cmp_type(left_result,
+ items[i]->cmp_type());
+ }
+ /*
+ Even if all right-hand items are NULLs and we are skipping them all, we need
+ at least one type bit in the found_type bitmask.
+ */
+ if (skip_nulls && !found_types)
+ found_types= 1U << (uint)left_result;
+ return found_types;
+}
+
+static void my_coll_agg_error(DTCollation &c1, DTCollation &c2,
+ const char *fname)
+{
+ my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0),
+ c1.collation->name,c1.derivation_name(),
+ c2.collation->name,c2.derivation_name(),
+ fname);
+}
+
+
+Item_bool_func2* Eq_creator::create(Item *a, Item *b) const
+{
+ return new Item_func_eq(a, b);
+}
+
+Item_bool_func2* Eq_creator::create_swap(Item *a, Item *b) const
+{
+ return new Item_func_eq(b, a);
+}
+
+Item_bool_func2* Ne_creator::create(Item *a, Item *b) const
+{
+ return new Item_func_ne(a, b);
+}
+
+Item_bool_func2* Ne_creator::create_swap(Item *a, Item *b) const
+{
+ return new Item_func_ne(b, a);
+}
+
+Item_bool_func2* Gt_creator::create(Item *a, Item *b) const
+{
+ return new Item_func_gt(a, b);
+}
+
+Item_bool_func2* Gt_creator::create_swap(Item *a, Item *b) const
+{
+ return new Item_func_lt(b, a);
+}
+
+Item_bool_func2* Lt_creator::create(Item *a, Item *b) const
+{
+ return new Item_func_lt(a, b);
+}
+
+Item_bool_func2* Lt_creator::create_swap(Item *a, Item *b) const
+{
+ return new Item_func_gt(b, a);
+}
+
+Item_bool_func2* Ge_creator::create(Item *a, Item *b) const
+{
+ return new Item_func_ge(a, b);
+}
+
+Item_bool_func2* Ge_creator::create_swap(Item *a, Item *b) const
+{
+ return new Item_func_le(b, a);
+}
+
+Item_bool_func2* Le_creator::create(Item *a, Item *b) const
+{
+ return new Item_func_le(a, b);
+}
+
+Item_bool_func2* Le_creator::create_swap(Item *a, Item *b) const
+{
+ return new Item_func_ge(b, a);
+}
+
+/*
+ Test functions
+ Most of these returns 0LL if false and 1LL if true and
+ NULL if some arg is NULL.
+*/
+
+longlong Item_func_not::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ bool value= args[0]->val_bool();
+ null_value=args[0]->null_value;
+ return ((!null_value && value == 0) ? 1 : 0);
+}
+
+/*
+ We put any NOT expression into parenthesis to avoid
+ possible problems with internal view representations where
+ any '!' is converted to NOT. It may cause a problem if
+ '!' is used in an expression together with other operators
+ whose precedence is lower than the precedence of '!' yet
+ higher than the precedence of NOT.
+*/
+
+void Item_func_not::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ Item_func::print(str, query_type);
+ str->append(')');
+}
+
+/**
+ special NOT for ALL subquery.
+*/
+
+
+longlong Item_func_not_all::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ bool value= args[0]->val_bool();
+
+ /*
+ return TRUE if there was records in underlying select in max/min
+ optimization (ALL subquery)
+ */
+ if (empty_underlying_subquery())
+ return 1;
+
+ null_value= args[0]->null_value;
+ return ((!null_value && value == 0) ? 1 : 0);
+}
+
+
+bool Item_func_not_all::empty_underlying_subquery()
+{
+ return ((test_sum_item && !test_sum_item->any_value()) ||
+ (test_sub_item && !test_sub_item->any_value()));
+}
+
+void Item_func_not_all::print(String *str, enum_query_type query_type)
+{
+ if (show)
+ Item_func::print(str, query_type);
+ else
+ args[0]->print(str, query_type);
+}
+
+
+/**
+ Special NOP (No OPeration) for ALL subquery. It is like
+ Item_func_not_all.
+
+ @return
+ (return TRUE if underlying subquery do not return rows) but if subquery
+ returns some rows it return same value as argument (TRUE/FALSE).
+*/
+
+longlong Item_func_nop_all::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong value= args[0]->val_int();
+
+ /*
+ return FALSE if there was records in underlying select in max/min
+ optimization (SAME/ANY subquery)
+ */
+ if (empty_underlying_subquery())
+ return 0;
+
+ null_value= args[0]->null_value;
+ return (null_value || value == 0) ? 0 : 1;
+}
+
+
+/**
+ Convert a constant item to an int and replace the original item.
+
+ The function converts a constant expression or string to an integer.
+ On successful conversion the original item is substituted for the
+ result of the item evaluation.
+ This is done when comparing DATE/TIME of different formats and
+ also when comparing bigint to strings (in which case strings
+ are converted to bigints).
+
+ @param thd thread handle
+ @param field item will be converted using the type of this field
+ @param[in,out] item reference to the item to convert
+
+ @note
+ This function is called only at prepare stage.
+ As all derived tables are filled only after all derived tables
+ are prepared we do not evaluate items with subselects here because
+ they can contain derived tables and thus we may attempt to use a
+ table that has not been populated yet.
+
+ @retval
+ 0 Can't convert item
+ @retval
+ 1 Item was replaced with an integer version of the item
+*/
+
+static bool convert_const_to_int(THD *thd, Item_field *field_item,
+ Item **item)
+{
+ Field *field= field_item->field;
+ int result= 0;
+
+ /*
+ We don't need to convert an integer to an integer,
+ pretend it's already converted.
+
+ But we still convert it if it is compared with a Field_year,
+ as YEAR(2) may change the value of an integer when converting it
+ to an integer (say, 0 to 70).
+ */
+ if ((*item)->cmp_type() == INT_RESULT &&
+ field_item->field_type() != MYSQL_TYPE_YEAR)
+ return 1;
+
+ if ((*item)->const_item() && !(*item)->is_expensive())
+ {
+ TABLE *table= field->table;
+ ulonglong orig_sql_mode= thd->variables.sql_mode;
+ enum_check_fields orig_count_cuted_fields= thd->count_cuted_fields;
+ my_bitmap_map *old_maps[2];
+ ulonglong UNINIT_VAR(orig_field_val); /* original field value if valid */
+
+ LINT_INIT(old_maps[0]);
+ LINT_INIT(old_maps[1]);
+
+ /* table->read_set may not be set if we come here from a CREATE TABLE */
+ if (table && table->read_set)
+ dbug_tmp_use_all_columns(table, old_maps,
+ table->read_set, table->write_set);
+ /* For comparison purposes allow invalid dates like 2000-01-32 */
+ thd->variables.sql_mode= (orig_sql_mode & ~MODE_NO_ZERO_DATE) |
+ MODE_INVALID_DATES;
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+
+ /*
+ Store the value of the field/constant because the call to save_in_field
+ below overrides that value. Don't save field value if no data has been
+ read yet.
+ */
+ bool save_field_value= (field_item->const_item() ||
+ !(field->table->status & STATUS_NO_RECORD));
+ if (save_field_value)
+ orig_field_val= field->val_int();
+ if (!(*item)->save_in_field(field, 1) && !field->is_null())
+ {
+ int field_cmp= 0;
+ // If item is a decimal value, we must reject it if it was truncated.
+ if (field->type() == MYSQL_TYPE_LONGLONG)
+ {
+ field_cmp= stored_field_cmp_to_item(thd, field, *item);
+ DBUG_PRINT("info", ("convert_const_to_int %d", field_cmp));
+ }
+
+ if (0 == field_cmp)
+ {
+ Item *tmp= new Item_int_with_ref(field->val_int(), *item,
+ MY_TEST(field->flags & UNSIGNED_FLAG));
+ if (tmp)
+ thd->change_item_tree(item, tmp);
+ result= 1; // Item was replaced
+ }
+ }
+ /* Restore the original field value. */
+ if (save_field_value)
+ {
+ result= field->store(orig_field_val, TRUE);
+ /* orig_field_val must be a valid value that can be restored back. */
+ DBUG_ASSERT(!result);
+ }
+ thd->variables.sql_mode= orig_sql_mode;
+ thd->count_cuted_fields= orig_count_cuted_fields;
+ if (table && table->read_set)
+ dbug_tmp_restore_column_maps(table->read_set, table->write_set, old_maps);
+ }
+ return result;
+}
+
+
+void Item_bool_func2::fix_length_and_dec()
+{
+ max_length= 1; // Function returns 0 or 1
+
+ /*
+ As some compare functions are generated after sql_yacc,
+ we have to check for out of memory conditions here
+ */
+ if (!args[0] || !args[1])
+ return;
+
+ /*
+ We allow to convert to Unicode character sets in some cases.
+ The conditions when conversion is possible are:
+ - arguments A and B have different charsets
+ - A wins according to coercibility rules
+ - character set of A is superset for character set of B
+
+ If all of the above is true, then it's possible to convert
+ B into the character set of A, and then compare according
+ to the collation of A.
+ */
+
+ DTCollation coll;
+ if (args[0]->cmp_type() == STRING_RESULT &&
+ args[1]->cmp_type() == STRING_RESULT &&
+ agg_arg_charsets_for_comparison(coll, args, 2))
+ return;
+
+ args[0]->cmp_context= args[1]->cmp_context=
+ item_cmp_type(args[0]->result_type(), args[1]->result_type());
+
+ /*
+ Make a special case of compare with fields to get nicer comparisons
+ of bigint numbers with constant string.
+ This directly contradicts the manual (number and a string should
+ be compared as doubles), but seems to provide more
+ "intuitive" behavior in some cases (but less intuitive in others).
+
+ But disable conversion in case of LIKE function.
+ */
+ THD *thd= current_thd;
+ if (functype() != LIKE_FUNC && !thd->lex->is_ps_or_view_context_analysis())
+ {
+ int field;
+ if (args[field= 0]->real_item()->type() == FIELD_ITEM ||
+ args[field= 1]->real_item()->type() == FIELD_ITEM)
+ {
+ Item_field *field_item= (Item_field*) (args[field]->real_item());
+ if ((field_item->field_type() == MYSQL_TYPE_LONGLONG ||
+ field_item->field_type() == MYSQL_TYPE_YEAR) &&
+ convert_const_to_int(thd, field_item, &args[!field]))
+ args[0]->cmp_context= args[1]->cmp_context= INT_RESULT;
+ }
+ }
+ set_cmp_func();
+}
+
+
+int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type)
+{
+ owner= item;
+ func= comparator_matrix[type]
+ [is_owner_equal_func()];
+
+ switch (type) {
+ case TIME_RESULT:
+ cmp_collation.collation= &my_charset_numeric;
+ break;
+ case ROW_RESULT:
+ {
+ uint n= (*a)->cols();
+ if (n != (*b)->cols())
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), n);
+ comparators= 0;
+ return 1;
+ }
+ if (!(comparators= new Arg_comparator[n]))
+ return 1;
+ for (uint i=0; i < n; i++)
+ {
+ if ((*a)->element_index(i)->cols() != (*b)->element_index(i)->cols())
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), (*a)->element_index(i)->cols());
+ return 1;
+ }
+ if (comparators[i].set_cmp_func(owner, (*a)->addr(i), (*b)->addr(i),
+ set_null))
+ return 1;
+ }
+ break;
+ }
+ case STRING_RESULT:
+ {
+ if (cmp_collation.collation == &my_charset_bin)
+ {
+ /*
+ We are using BLOB/BINARY/VARBINARY, change to compare byte by byte,
+ without removing end space
+ */
+ if (func == &Arg_comparator::compare_string)
+ func= &Arg_comparator::compare_binary_string;
+ else if (func == &Arg_comparator::compare_e_string)
+ func= &Arg_comparator::compare_e_binary_string;
+
+ /*
+ As this is binary compassion, mark all fields that they can't be
+ transformed. Otherwise we would get into trouble with comparisons
+ like:
+ WHERE col= 'j' AND col LIKE BINARY 'j'
+ which would be transformed to:
+ WHERE col= 'j'
+ */
+ (*a)->walk(&Item::set_no_const_sub, FALSE, (uchar*) 0);
+ (*b)->walk(&Item::set_no_const_sub, FALSE, (uchar*) 0);
+ }
+ break;
+ }
+ case INT_RESULT:
+ {
+ if (func == &Arg_comparator::compare_int_signed)
+ {
+ if ((*a)->unsigned_flag)
+ func= (((*b)->unsigned_flag)?
+ &Arg_comparator::compare_int_unsigned :
+ &Arg_comparator::compare_int_unsigned_signed);
+ else if ((*b)->unsigned_flag)
+ func= &Arg_comparator::compare_int_signed_unsigned;
+ }
+ else if (func== &Arg_comparator::compare_e_int)
+ {
+ if ((*a)->unsigned_flag ^ (*b)->unsigned_flag)
+ func= &Arg_comparator::compare_e_int_diff_signedness;
+ }
+ break;
+ }
+ case DECIMAL_RESULT:
+ break;
+ case REAL_RESULT:
+ {
+ if ((*a)->decimals < NOT_FIXED_DEC && (*b)->decimals < NOT_FIXED_DEC)
+ {
+ precision= 5 / log_10[MY_MAX((*a)->decimals, (*b)->decimals) + 1];
+ if (func == &Arg_comparator::compare_real)
+ func= &Arg_comparator::compare_real_fixed;
+ else if (func == &Arg_comparator::compare_e_real)
+ func= &Arg_comparator::compare_e_real_fixed;
+ }
+ break;
+ }
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ break;
+ }
+ return 0;
+}
+
+/**
+ Parse date provided in a string to a MYSQL_TIME.
+
+ @param[in] thd Thread handle
+ @param[in] str A string to convert
+ @param[in] warn_type Type of the timestamp for issuing the warning
+ @param[in] warn_name Field name for issuing the warning
+ @param[out] l_time The MYSQL_TIME objects is initialized.
+
+ Parses a date provided in the string str into a MYSQL_TIME object.
+ The date is used for comparison, that is fuzzy dates are allowed
+ independently of sql_mode.
+ If the string contains an incorrect date or doesn't correspond to a date at
+ all then a warning is issued. The warn_type and the warn_name arguments are
+ used as the name and the type of the field when issuing the warning. If any
+ input was discarded (trailing or non-timestamp-y characters), return value
+ will be TRUE.
+
+ @return Status flag
+ @retval FALSE Success.
+ @retval True Indicates failure.
+*/
+
+bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type,
+ const char *warn_name, MYSQL_TIME *l_time)
+{
+ bool value;
+ MYSQL_TIME_STATUS status;
+ int flags= TIME_FUZZY_DATES | MODE_INVALID_DATES;
+ ErrConvString err(str);
+
+ DBUG_ASSERT(warn_type != MYSQL_TIMESTAMP_TIME);
+
+ if (!str_to_datetime(str->charset(), str->ptr(), str->length(),
+ l_time, flags, &status))
+ {
+ DBUG_ASSERT(l_time->time_type == MYSQL_TIMESTAMP_DATETIME ||
+ l_time->time_type == MYSQL_TIMESTAMP_DATE);
+ /*
+ Do not return yet, we may still want to throw a "trailing garbage"
+ warning.
+ */
+ value= FALSE;
+ }
+ else
+ {
+ DBUG_ASSERT(l_time->time_type != MYSQL_TIMESTAMP_TIME);
+ DBUG_ASSERT(status.warnings != 0); // Must be set by set_to_datetime()
+ value= TRUE;
+ }
+
+ if (status.warnings > 0)
+ make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ &err, warn_type, warn_name);
+
+ return value;
+}
+
+
+/**
+ Aggregate comparator argument charsets for comparison.
+ One of the arguments ("a" or "b") can be replaced,
+ typically by Item_string or Item_func_conv_charset.
+
+ @return Aggregation result
+ @retval false - if no conversion is needed,
+ or if one of the arguments was converted
+ @retval true - on error, if arguments are not comparable.
+
+ TODO: get rid of this method eventually and refactor the calling code.
+ Argument conversion should happen on the Item_func level.
+ Arg_comparator should get comparable arguments.
+*/
+bool Arg_comparator::agg_arg_charsets_for_comparison()
+{
+ if (cmp_collation.set((*a)->collation, (*b)->collation, MY_COLL_CMP_CONV) ||
+ cmp_collation.derivation == DERIVATION_NONE)
+ {
+ my_coll_agg_error((*a)->collation, (*b)->collation, owner->func_name());
+ return true;
+ }
+ if (agg_item_set_converter(cmp_collation, owner->func_name(),
+ a, 1, MY_COLL_CMP_CONV, 1) ||
+ agg_item_set_converter(cmp_collation, owner->func_name(),
+ b, 1, MY_COLL_CMP_CONV, 1))
+ return true;
+ return false;
+}
+
+
+/**
+ Prepare the comparator (set the comparison function) for comparing
+ items *a1 and *a2 in the context of 'type'.
+
+ @param[in] owner_arg Item, peforming the comparison (e.g. Item_func_eq)
+ @param[in,out] a1 first argument to compare
+ @param[in,out] a2 second argument to compare
+ @param[in] type type context to compare in
+
+ Both *a1 and *a2 can be replaced by this method - typically by constant
+ items, holding the cached converted value of the original (constant) item.
+*/
+
+int Arg_comparator::set_cmp_func(Item_result_field *owner_arg,
+ Item **a1, Item **a2,
+ Item_result type)
+{
+ thd= current_thd;
+ owner= owner_arg;
+ set_null= set_null && owner_arg;
+ a= a1;
+ b= a2;
+
+ if (type == STRING_RESULT &&
+ (*a)->result_type() == STRING_RESULT &&
+ (*b)->result_type() == STRING_RESULT)
+ {
+ /*
+ We must set cmp_collation here as we may be called from for an automatic
+ generated item, like in natural join
+ */
+ if (agg_arg_charsets_for_comparison())
+ return 1;
+ }
+ if (type == INT_RESULT &&
+ (*a)->field_type() == MYSQL_TYPE_YEAR &&
+ (*b)->field_type() == MYSQL_TYPE_YEAR)
+ type= TIME_RESULT;
+
+ a= cache_converted_constant(thd, a, &a_cache, type);
+ b= cache_converted_constant(thd, b, &b_cache, type);
+ return set_compare_func(owner_arg, type);
+}
+
+
+/**
+ Convert and cache a constant.
+
+ @param value [in] An item to cache
+ @param cache_item [out] Placeholder for the cache item
+ @param type [in] Comparison type
+
+ @details
+ When given item is a constant and its type differs from comparison type
+ then cache its value to avoid type conversion of this constant on each
+ evaluation. In this case the value is cached and the reference to the cache
+ is returned.
+ Original value is returned otherwise.
+
+ @return cache item or original value.
+*/
+
+Item** Arg_comparator::cache_converted_constant(THD *thd_arg, Item **value,
+ Item **cache_item,
+ Item_result type)
+{
+ /*
+ Don't need cache if doing context analysis only.
+ Also, get_datetime_value creates Item_cache internally.
+ Unless fixed, we should not do it here.
+ */
+ if (!thd_arg->lex->is_ps_or_view_context_analysis() &&
+ (*value)->const_item() && type != (*value)->result_type() &&
+ type != TIME_RESULT)
+ {
+ Item_cache *cache= Item_cache::get_cache(*value, type);
+ cache->setup(*value);
+ *cache_item= cache;
+ return cache_item;
+ }
+ return value;
+}
+
+
+void Arg_comparator::set_datetime_cmp_func(Item_result_field *owner_arg,
+ Item **a1, Item **b1)
+{
+ thd= current_thd;
+ owner= owner_arg;
+ a= a1;
+ b= b1;
+ a_cache= 0;
+ b_cache= 0;
+ func= comparator_matrix[TIME_RESULT][is_owner_equal_func()];
+}
+
+/**
+ Retrieves correct DATETIME value from given item.
+
+ @param[in] thd thread handle
+ @param[in,out] item_arg item to retrieve DATETIME value from
+ @param[in,out] cache_arg pointer to place to store the caching item to
+ @param[in] warn_item item for issuing the conversion warning
+ @param[out] is_null TRUE <=> the item_arg is null
+
+ @details
+ Retrieves the correct DATETIME value from given item for comparison by the
+ compare_datetime() function.
+
+ If the value should be compared as time (TIME_RESULT), it's retrieved as
+ MYSQL_TIME. Otherwise it's read as a number/string and converted to time.
+ Constant items are cached, so the convertion is only done once for them.
+
+ Note the f_type behavior: if the item can be compared as time, then
+ f_type is this item's field_type(). Otherwise it's field_type() of
+ warn_item (which is the other operand of the comparison operator).
+ This logic provides correct string/number to date/time conversion
+ depending on the other operand (when comparing a string with a date, it's
+ parsed as a date, when comparing a string with a time it's parsed as a time)
+
+ If the item is a constant it is replaced by the Item_cache_int, that
+ holds the packed datetime value.
+
+ @return
+ MYSQL_TIME value, packed in a longlong, suitable for comparison.
+*/
+
+longlong
+get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg,
+ Item *warn_item, bool *is_null)
+{
+ longlong UNINIT_VAR(value);
+ Item *item= **item_arg;
+ enum_field_types f_type= item->cmp_type() == TIME_RESULT ?
+ item->field_type() : warn_item->field_type();
+
+ if (item->result_type() == INT_RESULT &&
+ item->cmp_type() == TIME_RESULT &&
+ item->type() == Item::CACHE_ITEM)
+ {
+ /* it's our Item_cache_temporal, as created below */
+ DBUG_ASSERT(is_temporal_type(((Item_cache *) item)->field_type()));
+ value= ((Item_cache_temporal*) item)->val_temporal_packed();
+ }
+ else
+ {
+ MYSQL_TIME ltime;
+ uint fuzzydate= TIME_FUZZY_DATES | TIME_INVALID_DATES;
+ if ((item->field_type() == MYSQL_TYPE_TIME &&
+ is_temporal_type_with_date(warn_item->field_type())) ?
+ item->get_date_with_conversion(&ltime, fuzzydate) :
+ item->get_date(&ltime, fuzzydate |
+ (f_type == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0)))
+ value= 0; /* invalid date */
+ else
+ value= pack_time(&ltime);
+ }
+ if ((*is_null= item->null_value))
+ return ~(ulonglong) 0;
+ if (cache_arg && item->const_item() &&
+ !(item->type() == Item::CACHE_ITEM && item->cmp_type() == TIME_RESULT))
+ {
+ Query_arena backup;
+ Query_arena *save_arena= thd->switch_to_arena_for_cached_items(&backup);
+ Item_cache_temporal *cache= new Item_cache_temporal(f_type);
+ if (save_arena)
+ thd->set_query_arena(save_arena);
+
+ cache->store_packed(value, item);
+ *cache_arg= cache;
+ *item_arg= cache_arg;
+ }
+ return value;
+}
+
+
+/*
+ Compare items values as dates.
+
+ SYNOPSIS
+ Arg_comparator::compare_datetime()
+
+ DESCRIPTION
+ Compare items values as DATE/DATETIME for both EQUAL_FUNC and from other
+ comparison functions. The correct DATETIME values are obtained
+ with help of the get_datetime_value() function.
+
+ RETURN
+ -1 a < b or at least one item is null
+ 0 a == b
+ 1 a > b
+*/
+
+int Arg_comparator::compare_datetime()
+{
+ bool a_is_null, b_is_null;
+ longlong a_value, b_value;
+
+ if (set_null)
+ owner->null_value= 1;
+
+ /* Get DATE/DATETIME/TIME value of the 'a' item. */
+ a_value= get_datetime_value(thd, &a, &a_cache, *b, &a_is_null);
+ if (a_is_null)
+ return -1;
+
+ /* Get DATE/DATETIME/TIME value of the 'b' item. */
+ b_value= get_datetime_value(thd, &b, &b_cache, *a, &b_is_null);
+ if (b_is_null)
+ return -1;
+
+ /* Here we have two not-NULL values. */
+ if (set_null)
+ owner->null_value= 0;
+
+ /* Compare values. */
+ return a_value < b_value ? -1 : a_value > b_value ? 1 : 0;
+}
+
+int Arg_comparator::compare_e_datetime()
+{
+ bool a_is_null, b_is_null;
+ longlong a_value, b_value;
+
+ /* Get DATE/DATETIME/TIME value of the 'a' item. */
+ a_value= get_datetime_value(thd, &a, &a_cache, *b, &a_is_null);
+
+ /* Get DATE/DATETIME/TIME value of the 'b' item. */
+ b_value= get_datetime_value(thd, &b, &b_cache, *a, &b_is_null);
+ return a_is_null || b_is_null ? a_is_null == b_is_null
+ : a_value == b_value;
+}
+
+int Arg_comparator::compare_string()
+{
+ String *res1,*res2;
+ if ((res1= (*a)->val_str(&value1)))
+ {
+ if ((res2= (*b)->val_str(&value2)))
+ {
+ if (set_null)
+ owner->null_value= 0;
+ return sortcmp(res1,res2,cmp_collation.collation);
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+
+/**
+ Compare strings byte by byte. End spaces are also compared.
+
+ @retval
+ <0 *a < *b
+ @retval
+ 0 *b == *b
+ @retval
+ >0 *a > *b
+*/
+
+int Arg_comparator::compare_binary_string()
+{
+ String *res1,*res2;
+ if ((res1= (*a)->val_str(&value1)))
+ {
+ if ((res2= (*b)->val_str(&value2)))
+ {
+ if (set_null)
+ owner->null_value= 0;
+ uint res1_length= res1->length();
+ uint res2_length= res2->length();
+ int cmp= memcmp(res1->ptr(), res2->ptr(), MY_MIN(res1_length,res2_length));
+ return cmp ? cmp : (int) (res1_length - res2_length);
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+
+/**
+ Compare strings, but take into account that NULL == NULL.
+*/
+
+
+int Arg_comparator::compare_e_string()
+{
+ String *res1,*res2;
+ res1= (*a)->val_str(&value1);
+ res2= (*b)->val_str(&value2);
+ if (!res1 || !res2)
+ return MY_TEST(res1 == res2);
+ return MY_TEST(sortcmp(res1, res2, cmp_collation.collation) == 0);
+}
+
+
+int Arg_comparator::compare_e_binary_string()
+{
+ String *res1,*res2;
+ res1= (*a)->val_str(&value1);
+ res2= (*b)->val_str(&value2);
+ if (!res1 || !res2)
+ return MY_TEST(res1 == res2);
+ return MY_TEST(stringcmp(res1, res2) == 0);
+}
+
+
+int Arg_comparator::compare_real()
+{
+ /*
+ Fix yet another manifestation of Bug#2338. 'Volatile' will instruct
+ gcc to flush double values out of 80-bit Intel FPU registers before
+ performing the comparison.
+ */
+ volatile double val1, val2;
+ val1= (*a)->val_real();
+ if (!(*a)->null_value)
+ {
+ val2= (*b)->val_real();
+ if (!(*b)->null_value)
+ {
+ if (set_null)
+ owner->null_value= 0;
+ if (val1 < val2) return -1;
+ if (val1 == val2) return 0;
+ return 1;
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+int Arg_comparator::compare_decimal()
+{
+ my_decimal decimal1;
+ my_decimal *val1= (*a)->val_decimal(&decimal1);
+ if (!(*a)->null_value)
+ {
+ my_decimal decimal2;
+ my_decimal *val2= (*b)->val_decimal(&decimal2);
+ if (!(*b)->null_value)
+ {
+ if (set_null)
+ owner->null_value= 0;
+ return my_decimal_cmp(val1, val2);
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+int Arg_comparator::compare_e_real()
+{
+ double val1= (*a)->val_real();
+ double val2= (*b)->val_real();
+ if ((*a)->null_value || (*b)->null_value)
+ return MY_TEST((*a)->null_value && (*b)->null_value);
+ return MY_TEST(val1 == val2);
+}
+
+int Arg_comparator::compare_e_decimal()
+{
+ my_decimal decimal1, decimal2;
+ my_decimal *val1= (*a)->val_decimal(&decimal1);
+ my_decimal *val2= (*b)->val_decimal(&decimal2);
+ if ((*a)->null_value || (*b)->null_value)
+ return MY_TEST((*a)->null_value && (*b)->null_value);
+ return MY_TEST(my_decimal_cmp(val1, val2) == 0);
+}
+
+
+int Arg_comparator::compare_real_fixed()
+{
+ /*
+ Fix yet another manifestation of Bug#2338. 'Volatile' will instruct
+ gcc to flush double values out of 80-bit Intel FPU registers before
+ performing the comparison.
+ */
+ volatile double val1, val2;
+ val1= (*a)->val_real();
+ if (!(*a)->null_value)
+ {
+ val2= (*b)->val_real();
+ if (!(*b)->null_value)
+ {
+ if (set_null)
+ owner->null_value= 0;
+ if (val1 == val2 || fabs(val1 - val2) < precision)
+ return 0;
+ if (val1 < val2)
+ return -1;
+ return 1;
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+
+int Arg_comparator::compare_e_real_fixed()
+{
+ double val1= (*a)->val_real();
+ double val2= (*b)->val_real();
+ if ((*a)->null_value || (*b)->null_value)
+ return MY_TEST((*a)->null_value && (*b)->null_value);
+ return MY_TEST(val1 == val2 || fabs(val1 - val2) < precision);
+}
+
+
+int Arg_comparator::compare_int_signed()
+{
+ longlong val1= (*a)->val_int();
+ if (!(*a)->null_value)
+ {
+ longlong val2= (*b)->val_int();
+ if (!(*b)->null_value)
+ {
+ if (set_null)
+ owner->null_value= 0;
+ if (val1 < val2) return -1;
+ if (val1 == val2) return 0;
+ return 1;
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+
+/**
+ Compare values as BIGINT UNSIGNED.
+*/
+
+int Arg_comparator::compare_int_unsigned()
+{
+ ulonglong val1= (*a)->val_int();
+ if (!(*a)->null_value)
+ {
+ ulonglong val2= (*b)->val_int();
+ if (!(*b)->null_value)
+ {
+ if (set_null)
+ owner->null_value= 0;
+ if (val1 < val2) return -1;
+ if (val1 == val2) return 0;
+ return 1;
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+
+/**
+ Compare signed (*a) with unsigned (*B)
+*/
+
+int Arg_comparator::compare_int_signed_unsigned()
+{
+ longlong sval1= (*a)->val_int();
+ if (!(*a)->null_value)
+ {
+ ulonglong uval2= (ulonglong)(*b)->val_int();
+ if (!(*b)->null_value)
+ {
+ if (set_null)
+ owner->null_value= 0;
+ if (sval1 < 0 || (ulonglong)sval1 < uval2)
+ return -1;
+ if ((ulonglong)sval1 == uval2)
+ return 0;
+ return 1;
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+
+/**
+ Compare unsigned (*a) with signed (*B)
+*/
+
+int Arg_comparator::compare_int_unsigned_signed()
+{
+ ulonglong uval1= (ulonglong)(*a)->val_int();
+ if (!(*a)->null_value)
+ {
+ longlong sval2= (*b)->val_int();
+ if (!(*b)->null_value)
+ {
+ if (set_null)
+ owner->null_value= 0;
+ if (sval2 < 0)
+ return 1;
+ if (uval1 < (ulonglong)sval2)
+ return -1;
+ if (uval1 == (ulonglong)sval2)
+ return 0;
+ return 1;
+ }
+ }
+ if (set_null)
+ owner->null_value= 1;
+ return -1;
+}
+
+
+int Arg_comparator::compare_e_int()
+{
+ longlong val1= (*a)->val_int();
+ longlong val2= (*b)->val_int();
+ if ((*a)->null_value || (*b)->null_value)
+ return MY_TEST((*a)->null_value && (*b)->null_value);
+ return MY_TEST(val1 == val2);
+}
+
+/**
+ Compare unsigned *a with signed *b or signed *a with unsigned *b.
+*/
+int Arg_comparator::compare_e_int_diff_signedness()
+{
+ longlong val1= (*a)->val_int();
+ longlong val2= (*b)->val_int();
+ if ((*a)->null_value || (*b)->null_value)
+ return MY_TEST((*a)->null_value && (*b)->null_value);
+ return (val1 >= 0) && MY_TEST(val1 == val2);
+}
+
+int Arg_comparator::compare_row()
+{
+ int res= 0;
+ bool was_null= 0;
+ (*a)->bring_value();
+ (*b)->bring_value();
+
+ if ((*a)->null_value || (*b)->null_value)
+ {
+ owner->null_value= 1;
+ return -1;
+ }
+
+ uint n= (*a)->cols();
+ for (uint i= 0; i<n; i++)
+ {
+ res= comparators[i].compare();
+ /* Aggregate functions don't need special null handling. */
+ if (owner->null_value && owner->type() == Item::FUNC_ITEM)
+ {
+ // NULL was compared
+ switch (((Item_func*)owner)->functype()) {
+ case Item_func::NE_FUNC:
+ break; // NE never aborts on NULL even if abort_on_null is set
+ case Item_func::LT_FUNC:
+ case Item_func::LE_FUNC:
+ case Item_func::GT_FUNC:
+ case Item_func::GE_FUNC:
+ return -1; // <, <=, > and >= always fail on NULL
+ default: // EQ_FUNC
+ if (((Item_bool_func2*)owner)->abort_on_null)
+ return -1; // We do not need correct NULL returning
+ }
+ was_null= 1;
+ owner->null_value= 0;
+ res= 0; // continue comparison (maybe we will meet explicit difference)
+ }
+ else if (res)
+ return res;
+ }
+ if (was_null)
+ {
+ /*
+ There was NULL(s) in comparison in some parts, but there was no
+ explicit difference in other parts, so we have to return NULL.
+ */
+ owner->null_value= 1;
+ return -1;
+ }
+ return 0;
+}
+
+
+int Arg_comparator::compare_e_row()
+{
+ (*a)->bring_value();
+ (*b)->bring_value();
+ uint n= (*a)->cols();
+ for (uint i= 0; i<n; i++)
+ {
+ if (!comparators[i].compare())
+ return 0;
+ }
+ return 1;
+}
+
+
+void Item_func_truth::fix_length_and_dec()
+{
+ maybe_null= 0;
+ null_value= 0;
+ decimals= 0;
+ max_length= 1;
+}
+
+
+void Item_func_truth::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" is "));
+ if (! affirmative)
+ str->append(STRING_WITH_LEN("not "));
+ if (value)
+ str->append(STRING_WITH_LEN("true"));
+ else
+ str->append(STRING_WITH_LEN("false"));
+ str->append(')');
+}
+
+
+bool Item_func_truth::val_bool()
+{
+ bool val= args[0]->val_bool();
+ if (args[0]->null_value)
+ {
+ /*
+ NULL val IS {TRUE, FALSE} --> FALSE
+ NULL val IS NOT {TRUE, FALSE} --> TRUE
+ */
+ return (! affirmative);
+ }
+
+ if (affirmative)
+ {
+ /* {TRUE, FALSE} val IS {TRUE, FALSE} value */
+ return (val == value);
+ }
+
+ /* {TRUE, FALSE} val IS NOT {TRUE, FALSE} value */
+ return (val != value);
+}
+
+
+longlong Item_func_truth::val_int()
+{
+ return (val_bool() ? 1 : 0);
+}
+
+
+bool Item_in_optimizer::is_top_level_item()
+{
+ return ((Item_in_subselect *)args[1])->is_top_level_item();
+}
+
+
+void Item_in_optimizer::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ /* This will re-calculate attributes of our Item_in_subselect: */
+ Item_bool_func::fix_after_pullout(new_parent, ref);
+
+ /* Then, re-calculate not_null_tables_cache: */
+ eval_not_null_tables(NULL);
+}
+
+
+bool Item_in_optimizer::eval_not_null_tables(uchar *opt_arg)
+{
+ not_null_tables_cache= 0;
+ if (is_top_level_item())
+ {
+ /*
+ It is possible to determine NULL-rejectedness of the left arguments
+ of IN only if it is a top-level predicate.
+ */
+ not_null_tables_cache= args[0]->not_null_tables();
+ }
+ return FALSE;
+}
+
+
+bool Item_in_optimizer::fix_left(THD *thd)
+{
+ DBUG_ENTER("Item_in_optimizer::fix_left");
+ if ((!args[0]->fixed && args[0]->fix_fields(thd, args)) ||
+ (!cache && !(cache= Item_cache::get_cache(args[0]))))
+ DBUG_RETURN(1);
+ DBUG_PRINT("info", ("actual fix fields"));
+
+ cache->setup(args[0]);
+ if (cache->cols() == 1)
+ {
+ DBUG_ASSERT(args[0]->type() != ROW_ITEM);
+ /*
+ Note: there can be cases when used_tables()==0 && !const_item(). See
+ Item_sum::update_used_tables for details.
+ */
+ if ((used_tables_cache= args[0]->used_tables()) || !args[0]->const_item())
+ cache->set_used_tables(OUTER_REF_TABLE_BIT);
+ else
+ cache->set_used_tables(0);
+ }
+ else
+ {
+ uint n= cache->cols();
+ for (uint i= 0; i < n; i++)
+ {
+ /* Check that the expression (part of row) do not contain a subquery */
+ if (args[0]->element_index(i)->walk(&Item::is_subquery_processor,
+ FALSE, NULL))
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "SUBQUERY in ROW in left expression of IN/ALL/ANY");
+ DBUG_RETURN(1);
+ }
+ Item *element=args[0]->element_index(i);
+ if (element->used_tables() || !element->const_item())
+ {
+ ((Item_cache *)cache->element_index(i))->
+ set_used_tables(OUTER_REF_TABLE_BIT);
+ cache->set_used_tables(OUTER_REF_TABLE_BIT);
+ }
+ else
+ ((Item_cache *)cache->element_index(i))->set_used_tables(0);
+ }
+ used_tables_cache= args[0]->used_tables();
+ }
+ eval_not_null_tables(NULL);
+ with_sum_func= args[0]->with_sum_func;
+ with_field= args[0]->with_field;
+ if ((const_item_cache= args[0]->const_item()))
+ {
+ cache->store(args[0]);
+ cache->cache_value();
+ }
+ if (args[1]->fixed)
+ {
+ /* to avoid overriding is called to update left expression */
+ used_tables_cache|= args[1]->used_tables();
+ with_sum_func= with_sum_func || args[1]->with_sum_func;
+ const_item_cache= const_item_cache && args[1]->const_item();
+ }
+ DBUG_RETURN(0);
+}
+
+
+bool Item_in_optimizer::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ if (fix_left(thd))
+ return TRUE;
+ if (args[0]->maybe_null)
+ maybe_null=1;
+
+ if (!args[1]->fixed && args[1]->fix_fields(thd, args+1))
+ return TRUE;
+
+ Item_in_subselect * sub= (Item_in_subselect *)args[1];
+ if (!invisible_mode() &&
+ args[0]->cols() != sub->engine->cols())
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), args[0]->cols());
+ return TRUE;
+ }
+ if (args[1]->maybe_null)
+ maybe_null=1;
+ with_subselect= 1;
+ with_sum_func= with_sum_func || args[1]->with_sum_func;
+ with_field= with_field || args[1]->with_field;
+ used_tables_cache|= args[1]->used_tables();
+ const_item_cache&= args[1]->const_item();
+ fixed= 1;
+ return FALSE;
+}
+
+/**
+ Check if Item_in_optimizer should work as a pass-through item for its
+ arguments.
+
+ @note
+ Item_in_optimizer should work as pass-through for
+ - subqueries that were processed by ALL/ANY->MIN/MAX rewrite
+ - subqueries taht were originally EXISTS subqueries (and were coverted by
+ the EXISTS->IN rewrite)
+
+ When Item_in_optimizer is not not working as a pass-through, it
+ - caches its "left argument", args[0].
+ - makes adjustments to subquery item's return value for proper NULL
+ value handling
+*/
+
+bool Item_in_optimizer::invisible_mode()
+{
+ /* MAX/MIN transformed or EXISTS->IN prepared => do nothing */
+ return (args[1]->type() != Item::SUBSELECT_ITEM ||
+ ((Item_subselect *)args[1])->substype() ==
+ Item_subselect::EXISTS_SUBS);
+}
+
+
+/**
+ Add an expression cache for this subquery if it is needed
+
+ @param thd_arg Thread handle
+
+ @details
+ The function checks whether an expression cache is needed for this item
+ and if if so wraps the item into an item of the class
+ Item_exp_cache_wrapper with an appropriate expression cache set up there.
+
+ @note
+ used from Item::transform()
+
+ @return
+ new wrapper item if an expression cache is needed,
+ this item - otherwise
+*/
+
+Item *Item_in_optimizer::expr_cache_insert_transformer(uchar *thd_arg)
+{
+ THD *thd= (THD*) thd_arg;
+ DBUG_ENTER("Item_in_optimizer::expr_cache_insert_transformer");
+
+ if (invisible_mode())
+ DBUG_RETURN(this);
+
+ if (expr_cache)
+ DBUG_RETURN(expr_cache);
+
+ if (args[1]->expr_cache_is_needed(thd) &&
+ (expr_cache= set_expr_cache(thd)))
+ DBUG_RETURN(expr_cache);
+
+ DBUG_RETURN(this);
+}
+
+
+
+/**
+ Collect and add to the list cache parameters for this Item.
+
+ @param parameters The list where to add parameters
+*/
+
+void Item_in_optimizer::get_cache_parameters(List<Item> &parameters)
+{
+ /* Add left expression to the list of the parameters of the subquery */
+ if (!invisible_mode())
+ {
+ if (args[0]->cols() == 1)
+ parameters.add_unique(args[0], &cmp_items);
+ else
+ {
+ for (uint i= 0; i < args[0]->cols(); i++)
+ {
+ parameters.add_unique(args[0]->element_index(i), &cmp_items);
+ }
+ }
+ }
+ args[1]->get_cache_parameters(parameters);
+}
+
+/**
+ The implementation of optimized \<outer expression\> [NOT] IN \<subquery\>
+ predicates. The implementation works as follows.
+
+ For the current value of the outer expression
+
+ - If it contains only NULL values, the original (before rewrite by the
+ Item_in_subselect rewrite methods) inner subquery is non-correlated and
+ was previously executed, there is no need to re-execute it, and the
+ previous return value is returned.
+
+ - If it contains NULL values, check if there is a partial match for the
+ inner query block by evaluating it. For clarity we repeat here the
+ transformation previously performed on the sub-query. The expression
+
+ <tt>
+ ( oc_1, ..., oc_n )
+ \<in predicate\>
+ ( SELECT ic_1, ..., ic_n
+ FROM \<table\>
+ WHERE \<inner where\>
+ )
+ </tt>
+
+ was transformed into
+
+ <tt>
+ ( oc_1, ..., oc_n )
+ \<in predicate\>
+ ( SELECT ic_1, ..., ic_n
+ FROM \<table\>
+ WHERE \<inner where\> AND ... ( ic_k = oc_k OR ic_k IS NULL )
+ HAVING ... NOT ic_k IS NULL
+ )
+ </tt>
+
+ The evaluation will now proceed according to special rules set up
+ elsewhere. These rules include:
+
+ - The HAVING NOT \<inner column\> IS NULL conditions added by the
+ aforementioned rewrite methods will detect whether they evaluated (and
+ rejected) a NULL value and if so, will cause the subquery to evaluate
+ to NULL.
+
+ - The added WHERE and HAVING conditions are present only for those inner
+ columns that correspond to outer column that are not NULL at the moment.
+
+ - If there is an eligible index for executing the subquery, the special
+ access method "Full scan on NULL key" is employed which ensures that
+ the inner query will detect if there are NULL values resulting from the
+ inner query. This access method will quietly resort to table scan if it
+ needs to find NULL values as well.
+
+ - Under these conditions, the sub-query need only be evaluated in order to
+ find out whether it produced any rows.
+
+ - If it did, we know that there was a partial match since there are
+ NULL values in the outer row expression.
+
+ - If it did not, the result is FALSE or UNKNOWN. If at least one of the
+ HAVING sub-predicates rejected a NULL value corresponding to an outer
+ non-NULL, and hence the inner query block returns UNKNOWN upon
+ evaluation, there was a partial match and the result is UNKNOWN.
+
+ - If it contains no NULL values, the call is forwarded to the inner query
+ block.
+
+ @see Item_in_subselect::val_bool()
+ @see Item_is_not_null_test::val_int()
+*/
+
+longlong Item_in_optimizer::val_int()
+{
+ bool tmp;
+ DBUG_ASSERT(fixed == 1);
+ cache->store(args[0]);
+ cache->cache_value();
+ DBUG_ENTER(" Item_in_optimizer::val_int");
+
+ if (invisible_mode())
+ {
+ longlong res= args[1]->val_int();
+ null_value= args[1]->null_value;
+ DBUG_PRINT("info", ("pass trough"));
+ DBUG_RETURN(res);
+ }
+
+ if (cache->null_value)
+ {
+ DBUG_PRINT("info", ("Left NULL..."));
+ /*
+ We're evaluating
+ "<outer_value_list> [NOT] IN (SELECT <inner_value_list>...)"
+ where one or more of the outer values is NULL.
+ */
+ if (((Item_in_subselect*)args[1])->is_top_level_item())
+ {
+ /*
+ We're evaluating a top level item, e.g.
+ "<outer_value_list> IN (SELECT <inner_value_list>...)",
+ and in this case a NULL value in the outer_value_list means
+ that the result shall be NULL/FALSE (makes no difference for
+ top level items). The cached value is NULL, so just return
+ NULL.
+ */
+ null_value= 1;
+ }
+ else
+ {
+ /*
+ We're evaluating an item where a NULL value in either the
+ outer or inner value list does not automatically mean that we
+ can return NULL/FALSE. An example of such a query is
+ "<outer_value_list> NOT IN (SELECT <inner_value_list>...)"
+ The result when there is at least one NULL value is: NULL if the
+ SELECT evaluated over the non-NULL values produces at least
+ one row, FALSE otherwise
+ */
+ Item_in_subselect *item_subs=(Item_in_subselect*)args[1];
+ bool all_left_cols_null= true;
+ const uint ncols= cache->cols();
+
+ /*
+ Turn off the predicates that are based on column compares for
+ which the left part is currently NULL
+ */
+ for (uint i= 0; i < ncols; i++)
+ {
+ if (cache->element_index(i)->null_value)
+ item_subs->set_cond_guard_var(i, FALSE);
+ else
+ all_left_cols_null= false;
+ }
+
+ if (!item_subs->is_correlated &&
+ all_left_cols_null && result_for_null_param != UNKNOWN)
+ {
+ /*
+ This is a non-correlated subquery, all values in the outer
+ value list are NULL, and we have already evaluated the
+ subquery for all NULL values: Return the same result we
+ did last time without evaluating the subquery.
+ */
+ null_value= result_for_null_param;
+ }
+ else
+ {
+ /* The subquery has to be evaluated */
+ (void) item_subs->val_bool_result();
+ if (item_subs->engine->no_rows())
+ null_value= item_subs->null_value;
+ else
+ null_value= TRUE;
+ if (all_left_cols_null)
+ result_for_null_param= null_value;
+ }
+
+ /* Turn all predicates back on */
+ for (uint i= 0; i < ncols; i++)
+ item_subs->set_cond_guard_var(i, TRUE);
+ }
+ DBUG_RETURN(0);
+ }
+ tmp= args[1]->val_bool_result();
+ null_value= args[1]->null_value;
+ DBUG_RETURN(tmp);
+}
+
+
+void Item_in_optimizer::keep_top_level_cache()
+{
+ cache->keep_array();
+ save_cache= 1;
+}
+
+
+void Item_in_optimizer::cleanup()
+{
+ DBUG_ENTER("Item_in_optimizer::cleanup");
+ Item_bool_func::cleanup();
+ if (!save_cache)
+ cache= 0;
+ expr_cache= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+bool Item_in_optimizer::is_null()
+{
+ val_int();
+ return null_value;
+}
+
+
+/**
+ Transform an Item_in_optimizer and its arguments with a callback function.
+
+ @param transformer the transformer callback function to be applied to the
+ nodes of the tree of the object
+ @param parameter to be passed to the transformer
+
+ @detail
+ Recursively transform the left and the right operand of this Item. The
+ Right operand is an Item_in_subselect or its subclass. To avoid the
+ creation of new Items, we use the fact the the left operand of the
+ Item_in_subselect is the same as the one of 'this', so instead of
+ transforming its operand, we just assign the left operand of the
+ Item_in_subselect to be equal to the left operand of 'this'.
+ The transformation is not applied further to the subquery operand
+ if the IN predicate.
+
+ @returns
+ @retval pointer to the transformed item
+ @retval NULL if an error occurred
+*/
+
+Item *Item_in_optimizer::transform(Item_transformer transformer,
+ uchar *argument)
+{
+ Item *new_item;
+
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+ DBUG_ASSERT(arg_count == 2);
+
+ /* Transform the left IN operand. */
+ new_item= (*args)->transform(transformer, argument);
+ if (!new_item)
+ return 0;
+ /*
+ THD::change_item_tree() should be called only if the tree was
+ really transformed, i.e. when a new item has been created.
+ Otherwise we'll be allocating a lot of unnecessary memory for
+ change records at each execution.
+ */
+ if ((*args) != new_item)
+ current_thd->change_item_tree(args, new_item);
+
+ if (invisible_mode())
+ {
+ /* MAX/MIN transformed => pass through */
+ new_item= args[1]->transform(transformer, argument);
+ if (!new_item)
+ return 0;
+ if (args[1] != new_item)
+ current_thd->change_item_tree(args + 1, new_item);
+ }
+ else
+ {
+ /*
+ Transform the right IN operand which should be an Item_in_subselect or a
+ subclass of it. The left operand of the IN must be the same as the left
+ operand of this Item_in_optimizer, so in this case there is no further
+ transformation, we only make both operands the same.
+ TODO: is it the way it should be?
+ */
+ DBUG_ASSERT((args[1])->type() == Item::SUBSELECT_ITEM &&
+ (((Item_subselect*)(args[1]))->substype() ==
+ Item_subselect::IN_SUBS ||
+ ((Item_subselect*)(args[1]))->substype() ==
+ Item_subselect::ALL_SUBS ||
+ ((Item_subselect*)(args[1]))->substype() ==
+ Item_subselect::ANY_SUBS));
+
+ Item_in_subselect *in_arg= (Item_in_subselect*)args[1];
+ current_thd->change_item_tree(&in_arg->left_expr, args[0]);
+ }
+ return (this->*transformer)(argument);
+}
+
+
+bool Item_in_optimizer::is_expensive_processor(uchar *arg)
+{
+ return args[0]->is_expensive_processor(arg) ||
+ args[1]->is_expensive_processor(arg);
+}
+
+
+bool Item_in_optimizer::is_expensive()
+{
+ return args[0]->is_expensive() || args[1]->is_expensive();
+}
+
+
+longlong Item_func_eq::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int value= cmp.compare();
+ return value == 0 ? 1 : 0;
+}
+
+
+/** Same as Item_func_eq, but NULL = NULL. */
+
+void Item_func_equal::fix_length_and_dec()
+{
+ Item_bool_func2::fix_length_and_dec();
+ maybe_null=null_value=0;
+}
+
+longlong Item_func_equal::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ return cmp.compare();
+}
+
+longlong Item_func_ne::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int value= cmp.compare();
+ return value != 0 && !null_value ? 1 : 0;
+}
+
+
+longlong Item_func_ge::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int value= cmp.compare();
+ return value >= 0 ? 1 : 0;
+}
+
+
+longlong Item_func_gt::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int value= cmp.compare();
+ return value > 0 ? 1 : 0;
+}
+
+longlong Item_func_le::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int value= cmp.compare();
+ return value <= 0 && !null_value ? 1 : 0;
+}
+
+
+longlong Item_func_lt::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int value= cmp.compare();
+ return value < 0 && !null_value ? 1 : 0;
+}
+
+
+longlong Item_func_strcmp::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *a= args[0]->val_str(&value1);
+ String *b= args[1]->val_str(&value2);
+ if (!a || !b)
+ {
+ null_value=1;
+ return 0;
+ }
+ int value= cmp_collation.sortcmp(a, b);
+ null_value=0;
+ return !value ? 0 : (value < 0 ? (longlong) -1 : (longlong) 1);
+}
+
+
+bool Item_func_opt_neg::eq(const Item *item, bool binary_cmp) const
+{
+ /* Assume we don't have rtti */
+ if (this == item)
+ return 1;
+ if (item->type() != FUNC_ITEM)
+ return 0;
+ Item_func *item_func=(Item_func*) item;
+ if (arg_count != item_func->arg_count ||
+ functype() != item_func->functype())
+ return 0;
+ if (negated != ((Item_func_opt_neg *) item_func)->negated)
+ return 0;
+ for (uint i=0; i < arg_count ; i++)
+ if (!args[i]->eq(item_func->arguments()[i], binary_cmp))
+ return 0;
+ return 1;
+}
+
+
+void Item_func_interval::fix_length_and_dec()
+{
+ uint rows= row->cols();
+
+ use_decimal_comparison= ((row->element_index(0)->result_type() ==
+ DECIMAL_RESULT) ||
+ (row->element_index(0)->result_type() ==
+ INT_RESULT));
+ if (rows > 8)
+ {
+ bool not_null_consts= TRUE;
+
+ for (uint i= 1; not_null_consts && i < rows; i++)
+ {
+ Item *el= row->element_index(i);
+ not_null_consts&= el->const_item() && !el->is_null();
+ }
+
+ if (not_null_consts &&
+ (intervals=
+ (interval_range*) sql_alloc(sizeof(interval_range) * (rows - 1))))
+ {
+ if (use_decimal_comparison)
+ {
+ for (uint i= 1; i < rows; i++)
+ {
+ Item *el= row->element_index(i);
+ interval_range *range= intervals + (i-1);
+ if ((el->result_type() == DECIMAL_RESULT) ||
+ (el->result_type() == INT_RESULT))
+ {
+ range->type= DECIMAL_RESULT;
+ range->dec.init();
+ my_decimal *dec= el->val_decimal(&range->dec);
+ if (dec != &range->dec)
+ {
+ range->dec= *dec;
+ }
+ }
+ else
+ {
+ range->type= REAL_RESULT;
+ range->dbl= el->val_real();
+ }
+ }
+ }
+ else
+ {
+ for (uint i= 1; i < rows; i++)
+ {
+ intervals[i-1].dbl= row->element_index(i)->val_real();
+ }
+ }
+ }
+ }
+ maybe_null= 0;
+ max_length= 2;
+ used_tables_cache|= row->used_tables();
+ not_null_tables_cache= row->not_null_tables();
+ with_sum_func= with_sum_func || row->with_sum_func;
+ with_field= with_field || row->with_field;
+ const_item_cache&= row->const_item();
+}
+
+
+/**
+ Execute Item_func_interval().
+
+ @note
+ If we are doing a decimal comparison, we are evaluating the first
+ item twice.
+
+ @return
+ - -1 if null value,
+ - 0 if lower than lowest
+ - 1 - arg_count-1 if between args[n] and args[n+1]
+ - arg_count if higher than biggest argument
+*/
+
+longlong Item_func_interval::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value;
+ my_decimal dec_buf, *dec= NULL;
+ uint i;
+
+ if (use_decimal_comparison)
+ {
+ dec= row->element_index(0)->val_decimal(&dec_buf);
+ if (row->element_index(0)->null_value)
+ return -1;
+ my_decimal2double(E_DEC_FATAL_ERROR, dec, &value);
+ }
+ else
+ {
+ value= row->element_index(0)->val_real();
+ if (row->element_index(0)->null_value)
+ return -1;
+ }
+
+ if (intervals)
+ { // Use binary search to find interval
+ uint start,end;
+ start= 0;
+ end= row->cols()-2;
+ while (start != end)
+ {
+ uint mid= (start + end + 1) / 2;
+ interval_range *range= intervals + mid;
+ my_bool cmp_result;
+ /*
+ The values in the range intervall may have different types,
+ Only do a decimal comparision of the first argument is a decimal
+ and we are comparing against a decimal
+ */
+ if (dec && range->type == DECIMAL_RESULT)
+ cmp_result= my_decimal_cmp(&range->dec, dec) <= 0;
+ else
+ cmp_result= (range->dbl <= value);
+ if (cmp_result)
+ start= mid;
+ else
+ end= mid - 1;
+ }
+ interval_range *range= intervals+start;
+ return ((dec && range->type == DECIMAL_RESULT) ?
+ my_decimal_cmp(dec, &range->dec) < 0 :
+ value < range->dbl) ? 0 : start + 1;
+ }
+
+ for (i=1 ; i < row->cols() ; i++)
+ {
+ Item *el= row->element_index(i);
+ if (use_decimal_comparison &&
+ ((el->result_type() == DECIMAL_RESULT) ||
+ (el->result_type() == INT_RESULT)))
+ {
+ my_decimal e_dec_buf, *e_dec= el->val_decimal(&e_dec_buf);
+ /* Skip NULL ranges. */
+ if (el->null_value)
+ continue;
+ if (my_decimal_cmp(e_dec, dec) > 0)
+ return i - 1;
+ }
+ else
+ {
+ double val= el->val_real();
+ /* Skip NULL ranges. */
+ if (el->null_value)
+ continue;
+ if (val > value)
+ return i - 1;
+ }
+ }
+ return i-1;
+}
+
+
+/**
+ Perform context analysis of a BETWEEN item tree.
+
+ This function performs context analysis (name resolution) and calculates
+ various attributes of the item tree with Item_func_between as its root.
+ The function saves in ref the pointer to the item or to a newly created
+ item that is considered as a replacement for the original one.
+
+ @param thd reference to the global context of the query thread
+ @param ref pointer to Item* variable where pointer to resulting "fixed"
+ item is to be assigned
+
+ @note
+ Let T0(e)/T1(e) be the value of not_null_tables(e) when e is used on
+ a predicate/function level. Then it's easy to show that:
+ @verbatim
+ T0(e BETWEEN e1 AND e2) = union(T1(e),T1(e1),T1(e2))
+ T1(e BETWEEN e1 AND e2) = union(T1(e),intersection(T1(e1),T1(e2)))
+ T0(e NOT BETWEEN e1 AND e2) = union(T1(e),intersection(T1(e1),T1(e2)))
+ T1(e NOT BETWEEN e1 AND e2) = union(T1(e),intersection(T1(e1),T1(e2)))
+ @endverbatim
+
+ @retval
+ 0 ok
+ @retval
+ 1 got error
+*/
+
+bool Item_func_between::fix_fields(THD *thd, Item **ref)
+{
+ if (Item_func_opt_neg::fix_fields(thd, ref))
+ return 1;
+
+ thd->lex->current_select->between_count++;
+
+
+ return 0;
+}
+
+
+bool Item_func_between::eval_not_null_tables(uchar *opt_arg)
+{
+ if (Item_func_opt_neg::eval_not_null_tables(NULL))
+ return 1;
+
+ /* not_null_tables_cache == union(T1(e),T1(e1),T1(e2)) */
+ if (pred_level && !negated)
+ return 0;
+
+ /* not_null_tables_cache == union(T1(e), intersection(T1(e1),T1(e2))) */
+ not_null_tables_cache= (args[0]->not_null_tables() |
+ (args[1]->not_null_tables() &
+ args[2]->not_null_tables()));
+ return 0;
+}
+
+
+bool Item_func_between::count_sargable_conds(uchar *arg)
+{
+ SELECT_LEX *sel= (SELECT_LEX *) arg;
+ sel->cond_count++;
+ sel->between_count++;
+ return 0;
+}
+
+
+void Item_func_between::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ /* This will re-calculate attributes of the arguments */
+ Item_func_opt_neg::fix_after_pullout(new_parent, ref);
+ /* Then, re-calculate not_null_tables_cache according to our special rules */
+ eval_not_null_tables(NULL);
+}
+
+void Item_func_between::fix_length_and_dec()
+{
+ THD *thd= current_thd;
+ max_length= 1;
+ compare_as_dates= 0;
+
+ /*
+ As some compare functions are generated after sql_yacc,
+ we have to check for out of memory conditions here
+ */
+ if (!args[0] || !args[1] || !args[2])
+ return;
+ if ( agg_cmp_type(&cmp_type, args, 3))
+ return;
+ if (cmp_type == STRING_RESULT &&
+ agg_arg_charsets_for_comparison(cmp_collation, args, 3))
+ return;
+
+ /*
+ When comparing as date/time, we need to convert non-temporal values
+ (e.g. strings) to MYSQL_TIME. get_datetime_value() does it
+ automatically when one of the operands is a date/time. But here we
+ may need to compare two strings as dates (str1 BETWEEN str2 AND date).
+ For this to work, we need to know what date/time type we compare
+ strings as.
+ */
+ if (cmp_type == TIME_RESULT)
+ compare_as_dates= find_date_time_item(args, 3, 0);
+
+ /* See the comment about the similar block in Item_bool_func2 */
+ if (args[0]->real_item()->type() == FIELD_ITEM &&
+ !thd->lex->is_ps_or_view_context_analysis())
+ {
+ Item_field *field_item= (Item_field*) (args[0]->real_item());
+ if (field_item->field_type() == MYSQL_TYPE_LONGLONG ||
+ field_item->field_type() == MYSQL_TYPE_YEAR)
+ {
+ const bool cvt_arg1= convert_const_to_int(thd, field_item, &args[1]);
+ const bool cvt_arg2= convert_const_to_int(thd, field_item, &args[2]);
+ if (cvt_arg1 && cvt_arg2)
+ cmp_type=INT_RESULT; // Works for all types.
+ }
+ }
+}
+
+
+longlong Item_func_between::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+
+ switch (cmp_type) {
+ case TIME_RESULT:
+ {
+ THD *thd= current_thd;
+ longlong value, a, b;
+ Item *cache, **ptr;
+ bool value_is_null, a_is_null, b_is_null;
+
+ ptr= &args[0];
+ value= get_datetime_value(thd, &ptr, &cache, compare_as_dates,
+ &value_is_null);
+ if (ptr != &args[0])
+ thd->change_item_tree(&args[0], *ptr);
+
+ if ((null_value= value_is_null))
+ return 0;
+
+ ptr= &args[1];
+ a= get_datetime_value(thd, &ptr, &cache, compare_as_dates, &a_is_null);
+ if (ptr != &args[1])
+ thd->change_item_tree(&args[1], *ptr);
+
+ ptr= &args[2];
+ b= get_datetime_value(thd, &ptr, &cache, compare_as_dates, &b_is_null);
+ if (ptr != &args[2])
+ thd->change_item_tree(&args[2], *ptr);
+
+ if (!a_is_null && !b_is_null)
+ return (longlong) ((value >= a && value <= b) != negated);
+ if (a_is_null && b_is_null)
+ null_value=1;
+ else if (a_is_null)
+ null_value= value <= b; // not null if false range.
+ else
+ null_value= value >= a;
+ break;
+ }
+
+ case STRING_RESULT:
+ {
+ String *value,*a,*b;
+ value=args[0]->val_str(&value0);
+ if ((null_value=args[0]->null_value))
+ return 0;
+ a=args[1]->val_str(&value1);
+ b=args[2]->val_str(&value2);
+ if (!args[1]->null_value && !args[2]->null_value)
+ return (longlong) ((sortcmp(value,a,cmp_collation.collation) >= 0 &&
+ sortcmp(value,b,cmp_collation.collation) <= 0) !=
+ negated);
+ if (args[1]->null_value && args[2]->null_value)
+ null_value=1;
+ else if (args[1]->null_value)
+ {
+ // Set to not null if false range.
+ null_value= sortcmp(value,b,cmp_collation.collation) <= 0;
+ }
+ else
+ {
+ // Set to not null if false range.
+ null_value= sortcmp(value,a,cmp_collation.collation) >= 0;
+ }
+ break;
+ }
+ case INT_RESULT:
+ {
+ longlong value=args[0]->val_int(), a, b;
+ if ((null_value=args[0]->null_value))
+ return 0; /* purecov: inspected */
+ a=args[1]->val_int();
+ b=args[2]->val_int();
+ if (!args[1]->null_value && !args[2]->null_value)
+ return (longlong) ((value >= a && value <= b) != negated);
+ if (args[1]->null_value && args[2]->null_value)
+ null_value=1;
+ else if (args[1]->null_value)
+ {
+ null_value= value <= b; // not null if false range.
+ }
+ else
+ {
+ null_value= value >= a;
+ }
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ my_decimal dec_buf, *dec= args[0]->val_decimal(&dec_buf),
+ a_buf, *a_dec, b_buf, *b_dec;
+ if ((null_value=args[0]->null_value))
+ return 0; /* purecov: inspected */
+ a_dec= args[1]->val_decimal(&a_buf);
+ b_dec= args[2]->val_decimal(&b_buf);
+ if (!args[1]->null_value && !args[2]->null_value)
+ return (longlong) ((my_decimal_cmp(dec, a_dec) >= 0 &&
+ my_decimal_cmp(dec, b_dec) <= 0) != negated);
+ if (args[1]->null_value && args[2]->null_value)
+ null_value=1;
+ else if (args[1]->null_value)
+ null_value= (my_decimal_cmp(dec, b_dec) <= 0);
+ else
+ null_value= (my_decimal_cmp(dec, a_dec) >= 0);
+ break;
+ }
+ case REAL_RESULT:
+ {
+ double value= args[0]->val_real(),a,b;
+ if ((null_value=args[0]->null_value))
+ return 0; /* purecov: inspected */
+ a= args[1]->val_real();
+ b= args[2]->val_real();
+ if (!args[1]->null_value && !args[2]->null_value)
+ return (longlong) ((value >= a && value <= b) != negated);
+ if (args[1]->null_value && args[2]->null_value)
+ null_value=1;
+ else if (args[1]->null_value)
+ {
+ null_value= value <= b; // not null if false range.
+ }
+ else
+ {
+ null_value= value >= a;
+ }
+ break;
+ }
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ null_value= 1;
+ return 0;
+ }
+ return (longlong) (!null_value && negated);
+}
+
+
+void Item_func_between::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ args[0]->print(str, query_type);
+ if (negated)
+ str->append(STRING_WITH_LEN(" not"));
+ str->append(STRING_WITH_LEN(" between "));
+ args[1]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" and "));
+ args[2]->print(str, query_type);
+ str->append(')');
+}
+
+void
+Item_func_ifnull::fix_length_and_dec()
+{
+ uint32 char_length;
+ agg_result_type(&cached_result_type, args, 2);
+ cached_field_type= agg_field_type(args, 2);
+ maybe_null=args[1]->maybe_null;
+ decimals= MY_MAX(args[0]->decimals, args[1]->decimals);
+ unsigned_flag= args[0]->unsigned_flag && args[1]->unsigned_flag;
+
+ if (cached_result_type == DECIMAL_RESULT || cached_result_type == INT_RESULT)
+ {
+ int len0= args[0]->max_char_length() - args[0]->decimals
+ - (args[0]->unsigned_flag ? 0 : 1);
+
+ int len1= args[1]->max_char_length() - args[1]->decimals
+ - (args[1]->unsigned_flag ? 0 : 1);
+
+ char_length= MY_MAX(len0, len1) + decimals + (unsigned_flag ? 0 : 1);
+ }
+ else
+ char_length= MY_MAX(args[0]->max_char_length(), args[1]->max_char_length());
+
+ switch (cached_result_type) {
+ case STRING_RESULT:
+ if (count_string_result_length(cached_field_type, args, arg_count))
+ return;
+ break;
+ case DECIMAL_RESULT:
+ case REAL_RESULT:
+ break;
+ case INT_RESULT:
+ decimals= 0;
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ fix_char_length(char_length);
+}
+
+
+uint Item_func_ifnull::decimal_precision() const
+{
+ int arg0_int_part= args[0]->decimal_int_part();
+ int arg1_int_part= args[1]->decimal_int_part();
+ int max_int_part= MY_MAX(arg0_int_part, arg1_int_part);
+ int precision= max_int_part + decimals;
+ return MY_MIN(precision, DECIMAL_MAX_PRECISION);
+}
+
+
+Field *Item_func_ifnull::tmp_table_field(TABLE *table)
+{
+ return tmp_table_field_from_field_type(table, 0);
+}
+
+double
+Item_func_ifnull::real_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if (!args[0]->null_value)
+ {
+ null_value=0;
+ return value;
+ }
+ value= args[1]->val_real();
+ if ((null_value=args[1]->null_value))
+ return 0.0;
+ return value;
+}
+
+longlong
+Item_func_ifnull::int_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong value=args[0]->val_int();
+ if (!args[0]->null_value)
+ {
+ null_value=0;
+ return value;
+ }
+ value=args[1]->val_int();
+ if ((null_value=args[1]->null_value))
+ return 0;
+ return value;
+}
+
+
+my_decimal *Item_func_ifnull::decimal_op(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ my_decimal *value= args[0]->val_decimal(decimal_value);
+ if (!args[0]->null_value)
+ {
+ null_value= 0;
+ return value;
+ }
+ value= args[1]->val_decimal(decimal_value);
+ if ((null_value= args[1]->null_value))
+ return 0;
+ return value;
+}
+
+
+String *
+Item_func_ifnull::str_op(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res =args[0]->val_str(str);
+ if (!args[0]->null_value)
+ {
+ null_value=0;
+ res->set_charset(collation.collation);
+ return res;
+ }
+ res=args[1]->val_str(str);
+ if ((null_value=args[1]->null_value))
+ return 0;
+ res->set_charset(collation.collation);
+ return res;
+}
+
+
+bool Item_func_ifnull::date_op(MYSQL_TIME *ltime, uint fuzzydate)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!args[0]->get_date_with_conversion(ltime, fuzzydate & ~TIME_FUZZY_DATES))
+ return (null_value= false);
+ if (!args[1]->get_date_with_conversion(ltime, fuzzydate & ~TIME_FUZZY_DATES))
+ return (null_value= false);
+ bzero((char*) ltime,sizeof(*ltime));
+ return null_value= !(fuzzydate & TIME_FUZZY_DATES);
+}
+
+
+/**
+ Perform context analysis of an IF item tree.
+
+ This function performs context analysis (name resolution) and calculates
+ various attributes of the item tree with Item_func_if as its root.
+ The function saves in ref the pointer to the item or to a newly created
+ item that is considered as a replacement for the original one.
+
+ @param thd reference to the global context of the query thread
+ @param ref pointer to Item* variable where pointer to resulting "fixed"
+ item is to be assigned
+
+ @note
+ Let T0(e)/T1(e) be the value of not_null_tables(e) when e is used on
+ a predicate/function level. Then it's easy to show that:
+ @verbatim
+ T0(IF(e,e1,e2) = T1(IF(e,e1,e2))
+ T1(IF(e,e1,e2)) = intersection(T1(e1),T1(e2))
+ @endverbatim
+
+ @retval
+ 0 ok
+ @retval
+ 1 got error
+*/
+
+bool
+Item_func_if::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ args[0]->top_level_item();
+
+ if (Item_func::fix_fields(thd, ref))
+ return 1;
+
+ return 0;
+}
+
+
+bool
+Item_func_if::eval_not_null_tables(uchar *opt_arg)
+{
+ if (Item_func::eval_not_null_tables(NULL))
+ return 1;
+
+ not_null_tables_cache= (args[1]->not_null_tables() &
+ args[2]->not_null_tables());
+
+ return 0;
+}
+
+
+void Item_func_if::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ /* This will re-calculate attributes of the arguments */
+ Item_func::fix_after_pullout(new_parent, ref);
+ /* Then, re-calculate not_null_tables_cache according to our special rules */
+ eval_not_null_tables(NULL);
+}
+
+
+void Item_func_if::cache_type_info(Item *source)
+{
+ collation.set(source->collation);
+ cached_field_type= source->field_type();
+ cached_result_type= source->result_type();
+ decimals= source->decimals;
+ max_length= source->max_length;
+ maybe_null= source->maybe_null;
+ unsigned_flag= source->unsigned_flag;
+}
+
+
+void
+Item_func_if::fix_length_and_dec()
+{
+ // Let IF(cond, expr, NULL) and IF(cond, NULL, expr) inherit type from expr.
+ if (args[1]->type() == NULL_ITEM)
+ {
+ cache_type_info(args[2]);
+ maybe_null= true;
+ // If both arguments are NULL, make resulting type BINARY(0).
+ if (args[2]->type() == NULL_ITEM)
+ cached_field_type= MYSQL_TYPE_STRING;
+ return;
+ }
+ if (args[2]->type() == NULL_ITEM)
+ {
+ cache_type_info(args[1]);
+ maybe_null= true;
+ return;
+ }
+
+ agg_result_type(&cached_result_type, args + 1, 2);
+ cached_field_type= agg_field_type(args + 1, 2);
+ maybe_null= args[1]->maybe_null || args[2]->maybe_null;
+ decimals= MY_MAX(args[1]->decimals, args[2]->decimals);
+ unsigned_flag=args[1]->unsigned_flag && args[2]->unsigned_flag;
+
+ if (cached_result_type == STRING_RESULT)
+ {
+ count_string_result_length(cached_field_type, args + 1, 2);
+ return;
+ }
+ else
+ {
+ collation.set_numeric(); // Number
+ }
+
+ uint32 char_length;
+ if ((cached_result_type == DECIMAL_RESULT )
+ || (cached_result_type == INT_RESULT))
+ {
+ int len1= args[1]->max_length - args[1]->decimals
+ - (args[1]->unsigned_flag ? 0 : 1);
+
+ int len2= args[2]->max_length - args[2]->decimals
+ - (args[2]->unsigned_flag ? 0 : 1);
+
+ char_length= MY_MAX(len1, len2) + decimals + (unsigned_flag ? 0 : 1);
+ }
+ else
+ char_length= MY_MAX(args[1]->max_char_length(), args[2]->max_char_length());
+ fix_char_length(char_length);
+}
+
+
+uint Item_func_if::decimal_precision() const
+{
+ int arg1_prec= args[1]->decimal_int_part();
+ int arg2_prec= args[2]->decimal_int_part();
+ int precision=MY_MAX(arg1_prec,arg2_prec) + decimals;
+ return MY_MIN(precision, DECIMAL_MAX_PRECISION);
+}
+
+
+double
+Item_func_if::real_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ Item *arg= args[0]->val_bool() ? args[1] : args[2];
+ double value= arg->val_real();
+ null_value=arg->null_value;
+ return value;
+}
+
+longlong
+Item_func_if::int_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ Item *arg= args[0]->val_bool() ? args[1] : args[2];
+ longlong value=arg->val_int();
+ null_value=arg->null_value;
+ return value;
+}
+
+String *
+Item_func_if::str_op(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ Item *arg= args[0]->val_bool() ? args[1] : args[2];
+ String *res=arg->val_str(str);
+ if (res)
+ res->set_charset(collation.collation);
+ null_value=arg->null_value;
+ return res;
+}
+
+
+my_decimal *
+Item_func_if::decimal_op(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ Item *arg= args[0]->val_bool() ? args[1] : args[2];
+ my_decimal *value= arg->val_decimal(decimal_value);
+ null_value= arg->null_value;
+ return value;
+}
+
+
+bool Item_func_if::date_op(MYSQL_TIME *ltime, uint fuzzydate)
+{
+ DBUG_ASSERT(fixed == 1);
+ Item *arg= args[0]->val_bool() ? args[1] : args[2];
+ return (null_value= arg->get_date_with_conversion(ltime, fuzzydate));
+}
+
+
+void
+Item_func_nullif::fix_length_and_dec()
+{
+ Item_bool_func2::fix_length_and_dec();
+ maybe_null=1;
+ if (args[0]) // Only false if EOM
+ {
+ decimals=args[0]->decimals;
+ unsigned_flag= args[0]->unsigned_flag;
+ cached_result_type= args[0]->result_type();
+ if (cached_result_type == STRING_RESULT &&
+ agg_arg_charsets_for_comparison(collation, args, arg_count))
+ return;
+ fix_char_length(args[0]->max_char_length());
+ }
+}
+
+
+/**
+ @note
+ Note that we have to evaluate the first argument twice as the compare
+ may have been done with a different type than return value
+ @return
+ NULL if arguments are equal
+ @return
+ the first argument if not equal
+*/
+
+double
+Item_func_nullif::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value;
+ if (!cmp.compare())
+ {
+ null_value=1;
+ return 0.0;
+ }
+ value= args[0]->val_real();
+ null_value=args[0]->null_value;
+ return value;
+}
+
+longlong
+Item_func_nullif::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong value;
+ if (!cmp.compare())
+ {
+ null_value=1;
+ return 0;
+ }
+ value=args[0]->val_int();
+ null_value=args[0]->null_value;
+ return value;
+}
+
+String *
+Item_func_nullif::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res;
+ if (!cmp.compare())
+ {
+ null_value=1;
+ return 0;
+ }
+ res=args[0]->val_str(str);
+ null_value=args[0]->null_value;
+ return res;
+}
+
+
+my_decimal *
+Item_func_nullif::val_decimal(my_decimal * decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ my_decimal *res;
+ if (!cmp.compare())
+ {
+ null_value=1;
+ return 0;
+ }
+ res= args[0]->val_decimal(decimal_value);
+ null_value= args[0]->null_value;
+ return res;
+}
+
+
+bool
+Item_func_nullif::is_null()
+{
+ return (null_value= (!cmp.compare() ? 1 : args[0]->null_value));
+}
+
+
+/**
+ Find and return matching items for CASE or ELSE item if all compares
+ are failed or NULL if ELSE item isn't defined.
+
+ IMPLEMENTATION
+ In order to do correct comparisons of the CASE expression (the expression
+ between CASE and the first WHEN) with each WHEN expression several
+ comparators are used. One for each result type. CASE expression can be
+ evaluated up to # of different result types are used. To check whether
+ the CASE expression already was evaluated for a particular result type
+ a bit mapped variable value_added_map is used. Result types are mapped
+ to it according to their int values i.e. STRING_RESULT is mapped to bit
+ 0, REAL_RESULT to bit 1, so on.
+
+ @retval
+ NULL Nothing found and there is no ELSE expression defined
+ @retval
+ item Found item or ELSE item if defined and all comparisons are
+ failed
+*/
+
+Item *Item_func_case::find_item(String *str)
+{
+ uint value_added_map= 0;
+
+ if (first_expr_num == -1)
+ {
+ for (uint i=0 ; i < ncases ; i+=2)
+ {
+ // No expression between CASE and the first WHEN
+ if (args[i]->val_bool())
+ return args[i+1];
+ continue;
+ }
+ }
+ else
+ {
+ /* Compare every WHEN argument with it and return the first match */
+ for (uint i=0 ; i < ncases ; i+=2)
+ {
+ if (args[i]->real_item()->type() == NULL_ITEM)
+ continue;
+ cmp_type= item_cmp_type(left_result_type, args[i]->cmp_type());
+ DBUG_ASSERT(cmp_type != ROW_RESULT);
+ DBUG_ASSERT(cmp_items[(uint)cmp_type]);
+ if (!(value_added_map & (1U << (uint)cmp_type)))
+ {
+ cmp_items[(uint)cmp_type]->store_value(args[first_expr_num]);
+ if ((null_value=args[first_expr_num]->null_value))
+ return else_expr_num != -1 ? args[else_expr_num] : 0;
+ value_added_map|= 1U << (uint)cmp_type;
+ }
+ if (!cmp_items[(uint)cmp_type]->cmp(args[i]) && !args[i]->null_value)
+ return args[i + 1];
+ }
+ }
+ // No, WHEN clauses all missed, return ELSE expression
+ return else_expr_num != -1 ? args[else_expr_num] : 0;
+}
+
+
+String *Item_func_case::str_op(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res;
+ Item *item=find_item(str);
+
+ if (!item)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value= 0;
+ if (!(res=item->val_str(str)))
+ null_value= 1;
+ return res;
+}
+
+
+longlong Item_func_case::int_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH];
+ String dummy_str(buff,sizeof(buff),default_charset());
+ Item *item=find_item(&dummy_str);
+ longlong res;
+
+ if (!item)
+ {
+ null_value=1;
+ return 0;
+ }
+ res=item->val_int();
+ null_value=item->null_value;
+ return res;
+}
+
+double Item_func_case::real_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH];
+ String dummy_str(buff,sizeof(buff),default_charset());
+ Item *item=find_item(&dummy_str);
+ double res;
+
+ if (!item)
+ {
+ null_value=1;
+ return 0;
+ }
+ res= item->val_real();
+ null_value=item->null_value;
+ return res;
+}
+
+
+my_decimal *Item_func_case::decimal_op(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH];
+ String dummy_str(buff, sizeof(buff), default_charset());
+ Item *item= find_item(&dummy_str);
+ my_decimal *res;
+
+ if (!item)
+ {
+ null_value=1;
+ return 0;
+ }
+
+ res= item->val_decimal(decimal_value);
+ null_value= item->null_value;
+ return res;
+}
+
+
+bool Item_func_case::date_op(MYSQL_TIME *ltime, uint fuzzydate)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH];
+ String dummy_str(buff, sizeof(buff), default_charset());
+ Item *item= find_item(&dummy_str);
+ if (!item)
+ return (null_value= true);
+ return (null_value= item->get_date_with_conversion(ltime, fuzzydate));
+}
+
+
+bool Item_func_case::fix_fields(THD *thd, Item **ref)
+{
+ /*
+ buff should match stack usage from
+ Item_func_case::val_int() -> Item_func_case::find_item()
+ */
+ uchar buff[MAX_FIELD_WIDTH*2+sizeof(String)*2+sizeof(String*)*2+sizeof(double)*2+sizeof(longlong)*2];
+ bool res= Item_func::fix_fields(thd, ref);
+ /*
+ Call check_stack_overrun after fix_fields to be sure that stack variable
+ is not optimized away
+ */
+ if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
+ return TRUE; // Fatal error flag is set!
+ return res;
+}
+
+
+void Item_func_case::agg_str_lengths(Item* arg)
+{
+ fix_char_length(MY_MAX(max_char_length(), arg->max_char_length()));
+ set_if_bigger(decimals, arg->decimals);
+ unsigned_flag= unsigned_flag && arg->unsigned_flag;
+}
+
+
+void Item_func_case::agg_num_lengths(Item *arg)
+{
+ uint len= my_decimal_length_to_precision(arg->max_length, arg->decimals,
+ arg->unsigned_flag) - arg->decimals;
+ set_if_bigger(max_length, len);
+ set_if_bigger(decimals, arg->decimals);
+ unsigned_flag= unsigned_flag && arg->unsigned_flag;
+}
+
+
+/**
+ Check if (*place) and new_value points to different Items and call
+ THD::change_item_tree() if needed.
+
+ This function is a workaround for implementation deficiency in
+ Item_func_case. The problem there is that the 'args' attribute contains
+ Items from different expressions.
+
+ The function must not be used elsewhere and will be remove eventually.
+*/
+
+static void change_item_tree_if_needed(THD *thd,
+ Item **place,
+ Item *new_value)
+{
+ if (*place == new_value)
+ return;
+
+ thd->change_item_tree(place, new_value);
+}
+
+
+void Item_func_case::fix_length_and_dec()
+{
+ Item **agg;
+ uint nagg;
+ uint found_types= 0;
+ THD *thd= current_thd;
+
+ if (!(agg= (Item**) sql_alloc(sizeof(Item*)*(ncases+1))))
+ return;
+
+ if (else_expr_num == -1 || args[else_expr_num]->maybe_null)
+ maybe_null= 1;
+
+ /*
+ Aggregate all THEN and ELSE expression types
+ and collations when string result
+ */
+
+ for (nagg= 0 ; nagg < ncases/2 ; nagg++)
+ agg[nagg]= args[nagg*2+1];
+
+ if (else_expr_num != -1)
+ agg[nagg++]= args[else_expr_num];
+
+ agg_result_type(&cached_result_type, agg, nagg);
+ cached_field_type= agg_field_type(agg, nagg);
+
+ if (cached_result_type == STRING_RESULT)
+ {
+ if (count_string_result_length(cached_field_type, agg, nagg))
+ return;
+ /*
+ Copy all THEN and ELSE items back to args[] array.
+ Some of the items might have been changed to Item_func_conv_charset.
+ */
+ for (nagg= 0 ; nagg < ncases / 2 ; nagg++)
+ change_item_tree_if_needed(thd, &args[nagg * 2 + 1], agg[nagg]);
+
+ if (else_expr_num != -1)
+ change_item_tree_if_needed(thd, &args[else_expr_num], agg[nagg++]);
+ }
+ else
+ {
+ collation.set_numeric();
+ max_length=0;
+ decimals=0;
+ unsigned_flag= TRUE;
+ for (uint i= 0; i < ncases; i+= 2)
+ agg_num_lengths(args[i + 1]);
+ if (else_expr_num != -1)
+ agg_num_lengths(args[else_expr_num]);
+ max_length= my_decimal_precision_to_length_no_truncation(max_length +
+ decimals, decimals,
+ unsigned_flag);
+ }
+
+ /*
+ Aggregate first expression and all WHEN expression types
+ and collations when string comparison
+ */
+ if (first_expr_num != -1)
+ {
+ uint i;
+ agg[0]= args[first_expr_num];
+ left_result_type= agg[0]->cmp_type();
+
+ /*
+ As the first expression and WHEN expressions
+ are intermixed in args[] array THEN and ELSE items,
+ extract the first expression and all WHEN expressions into
+ a temporary array, to process them easier.
+ */
+ for (nagg= 0; nagg < ncases/2 ; nagg++)
+ agg[nagg+1]= args[nagg*2];
+ nagg++;
+ if (!(found_types= collect_cmp_types(agg, nagg)))
+ return;
+
+ Item *date_arg= 0;
+ if (found_types & (1U << TIME_RESULT))
+ date_arg= find_date_time_item(args, arg_count, 0);
+
+ if (found_types & (1U << STRING_RESULT))
+ {
+ /*
+ If we'll do string comparison, we also need to aggregate
+ character set and collation for first/WHEN items and
+ install converters for some of them to cmp_collation when necessary.
+ This is done because cmp_item compatators cannot compare
+ strings in two different character sets.
+ Some examples when we install converters:
+
+ 1. Converter installed for the first expression:
+
+ CASE latin1_item WHEN utf16_item THEN ... END
+
+ is replaced to:
+
+ CASE CONVERT(latin1_item USING utf16) WHEN utf16_item THEN ... END
+
+ 2. Converter installed for the left WHEN item:
+
+ CASE utf16_item WHEN latin1_item THEN ... END
+
+ is replaced to:
+
+ CASE utf16_item WHEN CONVERT(latin1_item USING utf16) THEN ... END
+ */
+ if (agg_arg_charsets_for_comparison(cmp_collation, agg, nagg))
+ return;
+ /*
+ Now copy first expression and all WHEN expressions back to args[]
+ arrray, because some of the items might have been changed to converters
+ (e.g. Item_func_conv_charset, or Item_string for constants).
+ */
+ change_item_tree_if_needed(thd, &args[first_expr_num], agg[0]);
+
+ for (nagg= 0; nagg < ncases / 2; nagg++)
+ change_item_tree_if_needed(thd, &args[nagg * 2], agg[nagg + 1]);
+ }
+
+ for (i= 0; i <= (uint)TIME_RESULT; i++)
+ {
+ if (found_types & (1U << i) && !cmp_items[i])
+ {
+ DBUG_ASSERT((Item_result)i != ROW_RESULT);
+
+ if (!(cmp_items[i]=
+ cmp_item::get_comparator((Item_result)i, date_arg,
+ cmp_collation.collation)))
+ return;
+ }
+ }
+ /*
+ Set cmp_context of all WHEN arguments. This prevents
+ Item_field::equal_fields_propagator() from transforming a
+ zerofill argument into a string constant. Such a change would
+ require rebuilding cmp_items.
+ */
+ for (i= 0; i < ncases; i+= 2)
+ args[i]->cmp_context= item_cmp_type(left_result_type,
+ args[i]->result_type());
+ }
+}
+
+
+uint Item_func_case::decimal_precision() const
+{
+ int max_int_part=0;
+ for (uint i=0 ; i < ncases ; i+=2)
+ set_if_bigger(max_int_part, args[i+1]->decimal_int_part());
+
+ if (else_expr_num != -1)
+ set_if_bigger(max_int_part, args[else_expr_num]->decimal_int_part());
+ return MY_MIN(max_int_part + decimals, DECIMAL_MAX_PRECISION);
+}
+
+
+/**
+ @todo
+ Fix this so that it prints the whole CASE expression
+*/
+
+void Item_func_case::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("(case "));
+ if (first_expr_num != -1)
+ {
+ args[first_expr_num]->print(str, query_type);
+ str->append(' ');
+ }
+ for (uint i=0 ; i < ncases ; i+=2)
+ {
+ str->append(STRING_WITH_LEN("when "));
+ args[i]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" then "));
+ args[i+1]->print(str, query_type);
+ str->append(' ');
+ }
+ if (else_expr_num != -1)
+ {
+ str->append(STRING_WITH_LEN("else "));
+ args[else_expr_num]->print(str, query_type);
+ str->append(' ');
+ }
+ str->append(STRING_WITH_LEN("end)"));
+}
+
+
+void Item_func_case::cleanup()
+{
+ uint i;
+ DBUG_ENTER("Item_func_case::cleanup");
+ Item_func::cleanup();
+ for (i= 0; i <= (uint)TIME_RESULT; i++)
+ {
+ delete cmp_items[i];
+ cmp_items[i]= 0;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Coalesce - return first not NULL argument.
+*/
+
+String *Item_func_coalesce::str_op(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ null_value=0;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ String *res;
+ if ((res=args[i]->val_str(str)))
+ return res;
+ }
+ null_value=1;
+ return 0;
+}
+
+longlong Item_func_coalesce::int_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ null_value=0;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ longlong res=args[i]->val_int();
+ if (!args[i]->null_value)
+ return res;
+ }
+ null_value=1;
+ return 0;
+}
+
+double Item_func_coalesce::real_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ null_value=0;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ double res= args[i]->val_real();
+ if (!args[i]->null_value)
+ return res;
+ }
+ null_value=1;
+ return 0;
+}
+
+
+bool Item_func_coalesce::date_op(MYSQL_TIME *ltime,uint fuzzydate)
+{
+ DBUG_ASSERT(fixed == 1);
+ null_value= 0;
+ for (uint i= 0; i < arg_count; i++)
+ {
+ bool res= args[i]->get_date_with_conversion(ltime,
+ fuzzydate & ~TIME_FUZZY_DATES);
+ if (!args[i]->null_value)
+ return res;
+ }
+ bzero((char*) ltime,sizeof(*ltime));
+ return null_value|= !(fuzzydate & TIME_FUZZY_DATES);
+}
+
+
+my_decimal *Item_func_coalesce::decimal_op(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ null_value= 0;
+ for (uint i= 0; i < arg_count; i++)
+ {
+ my_decimal *res= args[i]->val_decimal(decimal_value);
+ if (!args[i]->null_value)
+ return res;
+ }
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_coalesce::fix_length_and_dec()
+{
+ cached_field_type= agg_field_type(args, arg_count);
+ agg_result_type(&cached_result_type, args, arg_count);
+ switch (cached_result_type) {
+ case STRING_RESULT:
+ if (count_string_result_length(cached_field_type, args, arg_count))
+ return;
+ break;
+ case DECIMAL_RESULT:
+ count_decimal_length();
+ break;
+ case REAL_RESULT:
+ count_real_length();
+ break;
+ case INT_RESULT:
+ count_only_length(args, arg_count);
+ decimals= 0;
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+}
+
+/****************************************************************************
+ Classes and function for the IN operator
+****************************************************************************/
+
+/*
+ Determine which of the signed longlong arguments is bigger
+
+ SYNOPSIS
+ cmp_longs()
+ a_val left argument
+ b_val right argument
+
+ DESCRIPTION
+ This function will compare two signed longlong arguments
+ and will return -1, 0, or 1 if left argument is smaller than,
+ equal to or greater than the right argument.
+
+ RETURN VALUE
+ -1 left argument is smaller than the right argument.
+ 0 left argument is equal to the right argument.
+ 1 left argument is greater than the right argument.
+*/
+static inline int cmp_longs (longlong a_val, longlong b_val)
+{
+ return a_val < b_val ? -1 : a_val == b_val ? 0 : 1;
+}
+
+
+/*
+ Determine which of the unsigned longlong arguments is bigger
+
+ SYNOPSIS
+ cmp_ulongs()
+ a_val left argument
+ b_val right argument
+
+ DESCRIPTION
+ This function will compare two unsigned longlong arguments
+ and will return -1, 0, or 1 if left argument is smaller than,
+ equal to or greater than the right argument.
+
+ RETURN VALUE
+ -1 left argument is smaller than the right argument.
+ 0 left argument is equal to the right argument.
+ 1 left argument is greater than the right argument.
+*/
+static inline int cmp_ulongs (ulonglong a_val, ulonglong b_val)
+{
+ return a_val < b_val ? -1 : a_val == b_val ? 0 : 1;
+}
+
+
+/*
+ Compare two integers in IN value list format (packed_longlong)
+
+ SYNOPSIS
+ cmp_longlong()
+ cmp_arg an argument passed to the calling function (my_qsort2)
+ a left argument
+ b right argument
+
+ DESCRIPTION
+ This function will compare two integer arguments in the IN value list
+ format and will return -1, 0, or 1 if left argument is smaller than,
+ equal to or greater than the right argument.
+ It's used in sorting the IN values list and finding an element in it.
+ Depending on the signedness of the arguments cmp_longlong() will
+ compare them as either signed (using cmp_longs()) or unsigned (using
+ cmp_ulongs()).
+
+ RETURN VALUE
+ -1 left argument is smaller than the right argument.
+ 0 left argument is equal to the right argument.
+ 1 left argument is greater than the right argument.
+*/
+int cmp_longlong(void *cmp_arg,
+ in_longlong::packed_longlong *a,
+ in_longlong::packed_longlong *b)
+{
+ if (a->unsigned_flag != b->unsigned_flag)
+ {
+ /*
+ One of the args is unsigned and is too big to fit into the
+ positive signed range. Report no match.
+ */
+ if ((a->unsigned_flag && ((ulonglong) a->val) > (ulonglong) LONGLONG_MAX)
+ ||
+ (b->unsigned_flag && ((ulonglong) b->val) > (ulonglong) LONGLONG_MAX))
+ return a->unsigned_flag ? 1 : -1;
+ /*
+ Although the signedness differs both args can fit into the signed
+ positive range. Make them signed and compare as usual.
+ */
+ return cmp_longs(a->val, b->val);
+ }
+ if (a->unsigned_flag)
+ return cmp_ulongs((ulonglong) a->val, (ulonglong) b->val);
+ return cmp_longs(a->val, b->val);
+}
+
+static int cmp_double(void *cmp_arg, double *a,double *b)
+{
+ return *a < *b ? -1 : *a == *b ? 0 : 1;
+}
+
+static int cmp_row(void *cmp_arg, cmp_item_row *a, cmp_item_row *b)
+{
+ return a->compare(b);
+}
+
+
+static int cmp_decimal(void *cmp_arg, my_decimal *a, my_decimal *b)
+{
+ /*
+ We need call of fixing buffer pointer, because fast sort just copy
+ decimal buffers in memory and pointers left pointing on old buffer place
+ */
+ a->fix_buffer_pointer();
+ b->fix_buffer_pointer();
+ return my_decimal_cmp(a, b);
+}
+
+
+int in_vector::find(Item *item)
+{
+ uchar *result=get_value(item);
+ if (!result || !used_count)
+ return 0; // Null value
+
+ uint start,end;
+ start=0; end=used_count-1;
+ while (start != end)
+ {
+ uint mid=(start+end+1)/2;
+ int res;
+ if ((res=(*compare)(collation, base+mid*size, result)) == 0)
+ return 1;
+ if (res < 0)
+ start=mid;
+ else
+ end=mid-1;
+ }
+ return (int) ((*compare)(collation, base+start*size, result) == 0);
+}
+
+in_string::in_string(uint elements,qsort2_cmp cmp_func, CHARSET_INFO *cs)
+ :in_vector(elements, sizeof(String), cmp_func, cs),
+ tmp(buff, sizeof(buff), &my_charset_bin)
+{}
+
+in_string::~in_string()
+{
+ if (base)
+ {
+ // base was allocated with help of sql_alloc => following is OK
+ for (uint i=0 ; i < count ; i++)
+ ((String*) base)[i].free();
+ }
+}
+
+void in_string::set(uint pos,Item *item)
+{
+ String *str=((String*) base)+pos;
+ String *res=item->val_str(str);
+ if (res && res != str)
+ {
+ if (res->uses_buffer_owned_by(str))
+ res->copy();
+ if (item->type() == Item::FUNC_ITEM)
+ str->copy(*res);
+ else
+ *str= *res;
+ }
+ if (!str->charset())
+ {
+ CHARSET_INFO *cs;
+ if (!(cs= item->collation.collation))
+ cs= &my_charset_bin; // Should never happen for STR items
+ str->set_charset(cs);
+ }
+}
+
+
+uchar *in_string::get_value(Item *item)
+{
+ return (uchar*) item->val_str(&tmp);
+}
+
+in_row::in_row(uint elements, Item * item)
+{
+ base= (char*) new cmp_item_row[count= elements];
+ size= sizeof(cmp_item_row);
+ compare= (qsort2_cmp) cmp_row;
+ /*
+ We need to reset these as otherwise we will call sort() with
+ uninitialized (even if not used) elements
+ */
+ used_count= elements;
+ collation= 0;
+}
+
+in_row::~in_row()
+{
+ if (base)
+ delete [] (cmp_item_row*) base;
+}
+
+uchar *in_row::get_value(Item *item)
+{
+ tmp.store_value(item);
+ if (item->is_null())
+ return 0;
+ return (uchar *)&tmp;
+}
+
+void in_row::set(uint pos, Item *item)
+{
+ DBUG_ENTER("in_row::set");
+ DBUG_PRINT("enter", ("pos: %u item: 0x%lx", pos, (ulong) item));
+ ((cmp_item_row*) base)[pos].store_value_by_template(&tmp, item);
+ DBUG_VOID_RETURN;
+}
+
+in_longlong::in_longlong(uint elements)
+ :in_vector(elements,sizeof(packed_longlong),(qsort2_cmp) cmp_longlong, 0)
+{}
+
+void in_longlong::set(uint pos,Item *item)
+{
+ struct packed_longlong *buff= &((packed_longlong*) base)[pos];
+
+ buff->val= item->val_int();
+ buff->unsigned_flag= item->unsigned_flag;
+}
+
+uchar *in_longlong::get_value(Item *item)
+{
+ tmp.val= item->val_int();
+ if (item->null_value)
+ return 0;
+ tmp.unsigned_flag= item->unsigned_flag;
+ return (uchar*) &tmp;
+}
+
+void in_datetime::set(uint pos,Item *item)
+{
+ Item **tmp_item= &item;
+ bool is_null;
+ struct packed_longlong *buff= &((packed_longlong*) base)[pos];
+
+ buff->val= get_datetime_value(thd, &tmp_item, 0, warn_item, &is_null);
+ buff->unsigned_flag= 1L;
+}
+
+uchar *in_datetime::get_value(Item *item)
+{
+ bool is_null;
+ Item **tmp_item= lval_cache ? &lval_cache : &item;
+ tmp.val= get_datetime_value(thd, &tmp_item, &lval_cache, warn_item, &is_null);
+ if (item->null_value)
+ return 0;
+ tmp.unsigned_flag= 1L;
+ return (uchar*) &tmp;
+}
+
+in_double::in_double(uint elements)
+ :in_vector(elements,sizeof(double),(qsort2_cmp) cmp_double, 0)
+{}
+
+void in_double::set(uint pos,Item *item)
+{
+ ((double*) base)[pos]= item->val_real();
+}
+
+uchar *in_double::get_value(Item *item)
+{
+ tmp= item->val_real();
+ if (item->null_value)
+ return 0; /* purecov: inspected */
+ return (uchar*) &tmp;
+}
+
+
+in_decimal::in_decimal(uint elements)
+ :in_vector(elements, sizeof(my_decimal),(qsort2_cmp) cmp_decimal, 0)
+{}
+
+
+void in_decimal::set(uint pos, Item *item)
+{
+ /* as far as 'item' is constant, we can store reference on my_decimal */
+ my_decimal *dec= ((my_decimal *)base) + pos;
+ dec->len= DECIMAL_BUFF_LENGTH;
+ dec->fix_buffer_pointer();
+ my_decimal *res= item->val_decimal(dec);
+ /* if item->val_decimal() is evaluated to NULL then res == 0 */
+ if (!item->null_value && res != dec)
+ my_decimal2decimal(res, dec);
+}
+
+
+uchar *in_decimal::get_value(Item *item)
+{
+ my_decimal *result= item->val_decimal(&val);
+ if (item->null_value)
+ return 0;
+ return (uchar *)result;
+}
+
+
+cmp_item* cmp_item::get_comparator(Item_result type, Item *warn_item,
+ CHARSET_INFO *cs)
+{
+ switch (type) {
+ case STRING_RESULT:
+ return new cmp_item_sort_string(cs);
+ case INT_RESULT:
+ return new cmp_item_int;
+ case REAL_RESULT:
+ return new cmp_item_real;
+ case ROW_RESULT:
+ return new cmp_item_row;
+ case DECIMAL_RESULT:
+ return new cmp_item_decimal;
+ case TIME_RESULT:
+ DBUG_ASSERT(warn_item);
+ return new cmp_item_datetime(warn_item);
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ break;
+ }
+ return 0; // to satisfy compiler :)
+}
+
+
+cmp_item* cmp_item_sort_string::make_same()
+{
+ return new cmp_item_sort_string_in_static(cmp_charset);
+}
+
+cmp_item* cmp_item_int::make_same()
+{
+ return new cmp_item_int();
+}
+
+cmp_item* cmp_item_real::make_same()
+{
+ return new cmp_item_real();
+}
+
+cmp_item* cmp_item_row::make_same()
+{
+ return new cmp_item_row();
+}
+
+
+cmp_item_row::~cmp_item_row()
+{
+ DBUG_ENTER("~cmp_item_row");
+ DBUG_PRINT("enter",("this: 0x%lx", (long) this));
+ if (comparators)
+ {
+ for (uint i= 0; i < n; i++)
+ {
+ if (comparators[i])
+ delete comparators[i];
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void cmp_item_row::alloc_comparators()
+{
+ if (!comparators)
+ comparators= (cmp_item **) current_thd->calloc(sizeof(cmp_item *)*n);
+}
+
+
+void cmp_item_row::store_value(Item *item)
+{
+ DBUG_ENTER("cmp_item_row::store_value");
+ n= item->cols();
+ alloc_comparators();
+ if (comparators)
+ {
+ item->bring_value();
+ item->null_value= 0;
+ for (uint i=0; i < n; i++)
+ {
+ if (!comparators[i])
+ {
+ DBUG_ASSERT(item->element_index(i)->cmp_type() != TIME_RESULT);
+ if (!(comparators[i]=
+ cmp_item::get_comparator(item->element_index(i)->result_type(), 0,
+ item->element_index(i)->collation.collation)))
+ break; // new failed
+ }
+ comparators[i]->store_value(item->element_index(i));
+ item->null_value|= item->element_index(i)->null_value;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void cmp_item_row::store_value_by_template(cmp_item *t, Item *item)
+{
+ cmp_item_row *tmpl= (cmp_item_row*) t;
+ if (tmpl->n != item->cols())
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), tmpl->n);
+ return;
+ }
+ n= tmpl->n;
+ if ((comparators= (cmp_item **) sql_alloc(sizeof(cmp_item *)*n)))
+ {
+ item->bring_value();
+ item->null_value= 0;
+ for (uint i=0; i < n; i++)
+ {
+ if (!(comparators[i]= tmpl->comparators[i]->make_same()))
+ break; // new failed
+ comparators[i]->store_value_by_template(tmpl->comparators[i],
+ item->element_index(i));
+ item->null_value|= item->element_index(i)->null_value;
+ }
+ }
+}
+
+
+int cmp_item_row::cmp(Item *arg)
+{
+ arg->null_value= 0;
+ if (arg->cols() != n)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), n);
+ return 1;
+ }
+ bool was_null= 0;
+ arg->bring_value();
+ for (uint i=0; i < n; i++)
+ {
+ if (comparators[i]->cmp(arg->element_index(i)))
+ {
+ if (!arg->element_index(i)->null_value)
+ return 1;
+ was_null= 1;
+ }
+ }
+ return (arg->null_value= was_null);
+}
+
+
+int cmp_item_row::compare(cmp_item *c)
+{
+ cmp_item_row *l_cmp= (cmp_item_row *) c;
+ for (uint i=0; i < n; i++)
+ {
+ int res;
+ if ((res= comparators[i]->compare(l_cmp->comparators[i])))
+ return res;
+ }
+ return 0;
+}
+
+
+void cmp_item_decimal::store_value(Item *item)
+{
+ my_decimal *val= item->val_decimal(&value);
+ /* val may be zero if item is nnull */
+ if (val && val != &value)
+ my_decimal2decimal(val, &value);
+}
+
+
+int cmp_item_decimal::cmp(Item *arg)
+{
+ my_decimal tmp_buf, *tmp= arg->val_decimal(&tmp_buf);
+ if (arg->null_value)
+ return 1;
+ return my_decimal_cmp(&value, tmp);
+}
+
+
+int cmp_item_decimal::compare(cmp_item *arg)
+{
+ cmp_item_decimal *l_cmp= (cmp_item_decimal*) arg;
+ return my_decimal_cmp(&value, &l_cmp->value);
+}
+
+
+cmp_item* cmp_item_decimal::make_same()
+{
+ return new cmp_item_decimal();
+}
+
+
+void cmp_item_datetime::store_value(Item *item)
+{
+ bool is_null;
+ Item **tmp_item= lval_cache ? &lval_cache : &item;
+ value= get_datetime_value(thd, &tmp_item, &lval_cache, warn_item, &is_null);
+}
+
+
+int cmp_item_datetime::cmp(Item *arg)
+{
+ bool is_null;
+ Item **tmp_item= &arg;
+ return value !=
+ get_datetime_value(thd, &tmp_item, 0, warn_item, &is_null);
+}
+
+
+int cmp_item_datetime::compare(cmp_item *ci)
+{
+ cmp_item_datetime *l_cmp= (cmp_item_datetime *)ci;
+ return (value < l_cmp->value) ? -1 : ((value == l_cmp->value) ? 0 : 1);
+}
+
+
+cmp_item *cmp_item_datetime::make_same()
+{
+ return new cmp_item_datetime(warn_item);
+}
+
+
+bool Item_func_in::nulls_in_row()
+{
+ Item **arg,**arg_end;
+ for (arg= args+1, arg_end= args+arg_count; arg != arg_end ; arg++)
+ {
+ if ((*arg)->null_inside())
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Perform context analysis of an IN item tree.
+
+ This function performs context analysis (name resolution) and calculates
+ various attributes of the item tree with Item_func_in as its root.
+ The function saves in ref the pointer to the item or to a newly created
+ item that is considered as a replacement for the original one.
+
+ @param thd reference to the global context of the query thread
+ @param ref pointer to Item* variable where pointer to resulting "fixed"
+ item is to be assigned
+
+ @note
+ Let T0(e)/T1(e) be the value of not_null_tables(e) when e is used on
+ a predicate/function level. Then it's easy to show that:
+ @verbatim
+ T0(e IN(e1,...,en)) = union(T1(e),intersection(T1(ei)))
+ T1(e IN(e1,...,en)) = union(T1(e),intersection(T1(ei)))
+ T0(e NOT IN(e1,...,en)) = union(T1(e),union(T1(ei)))
+ T1(e NOT IN(e1,...,en)) = union(T1(e),intersection(T1(ei)))
+ @endverbatim
+
+ @retval
+ 0 ok
+ @retval
+ 1 got error
+*/
+
+bool
+Item_func_in::fix_fields(THD *thd, Item **ref)
+{
+
+ if (Item_func_opt_neg::fix_fields(thd, ref))
+ return 1;
+
+ return 0;
+}
+
+
+bool
+Item_func_in::eval_not_null_tables(uchar *opt_arg)
+{
+ Item **arg, **arg_end;
+
+ if (Item_func_opt_neg::eval_not_null_tables(NULL))
+ return 1;
+
+ /* not_null_tables_cache == union(T1(e),union(T1(ei))) */
+ if (pred_level && negated)
+ return 0;
+
+ /* not_null_tables_cache = union(T1(e),intersection(T1(ei))) */
+ not_null_tables_cache= ~(table_map) 0;
+ for (arg= args + 1, arg_end= args + arg_count; arg != arg_end; arg++)
+ not_null_tables_cache&= (*arg)->not_null_tables();
+ not_null_tables_cache|= (*args)->not_null_tables();
+ return 0;
+}
+
+
+void Item_func_in::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ /* This will re-calculate attributes of the arguments */
+ Item_func_opt_neg::fix_after_pullout(new_parent, ref);
+ /* Then, re-calculate not_null_tables_cache according to our special rules */
+ eval_not_null_tables(NULL);
+}
+
+static int srtcmp_in(CHARSET_INFO *cs, const String *x,const String *y)
+{
+ return cs->coll->strnncollsp(cs,
+ (uchar *) x->ptr(),x->length(),
+ (uchar *) y->ptr(),y->length(), 0);
+}
+
+void Item_func_in::fix_length_and_dec()
+{
+ Item **arg, **arg_end;
+ bool const_itm= 1;
+ THD *thd= current_thd;
+ /* TRUE <=> arguments values will be compared as DATETIMEs. */
+ Item *date_arg= 0;
+ uint found_types= 0;
+ uint type_cnt= 0, i;
+ Item_result cmp_type= STRING_RESULT;
+ left_result_type= args[0]->cmp_type();
+ if (!(found_types= collect_cmp_types(args, arg_count, true)))
+ return;
+
+ for (arg= args + 1, arg_end= args + arg_count; arg != arg_end ; arg++)
+ {
+ if (!arg[0]->const_item())
+ {
+ const_itm= 0;
+ break;
+ }
+ }
+ for (i= 0; i <= (uint)TIME_RESULT; i++)
+ {
+ if (found_types & (1U << i))
+ {
+ (type_cnt)++;
+ cmp_type= (Item_result) i;
+ }
+ }
+
+ if (type_cnt == 1)
+ {
+ if (cmp_type == STRING_RESULT &&
+ agg_arg_charsets_for_comparison(cmp_collation, args, arg_count))
+ return;
+ arg_types_compatible= TRUE;
+
+ if (cmp_type == ROW_RESULT)
+ {
+ uint cols= args[0]->cols();
+ cmp_item_row *cmp= 0;
+
+ if (const_itm && !nulls_in_row())
+ {
+ array= new in_row(arg_count-1, 0);
+ cmp= &((in_row*)array)->tmp;
+ }
+ else
+ {
+ if (!(cmp= new cmp_item_row))
+ return;
+ cmp_items[ROW_RESULT]= cmp;
+ }
+ cmp->n= cols;
+ cmp->alloc_comparators();
+
+ for (uint col= 0; col < cols; col++)
+ {
+ date_arg= find_date_time_item(args, arg_count, col);
+ if (date_arg)
+ {
+ cmp_item **cmp= 0;
+ if (array)
+ cmp= ((in_row*)array)->tmp.comparators + col;
+ else
+ cmp= ((cmp_item_row*)cmp_items[ROW_RESULT])->comparators + col;
+ *cmp= new cmp_item_datetime(date_arg);
+ }
+ }
+ }
+ }
+ /*
+ Row item with NULLs inside can return NULL or FALSE =>
+ they can't be processed as static
+ */
+ if (type_cnt == 1 && const_itm && !nulls_in_row())
+ {
+ /*
+ IN must compare INT columns and constants as int values (the same
+ way as equality does).
+ So we must check here if the column on the left and all the constant
+ values on the right can be compared as integers and adjust the
+ comparison type accordingly.
+
+ See the comment about the similar block in Item_bool_func2
+ */
+ if (args[0]->real_item()->type() == FIELD_ITEM &&
+ !thd->lex->is_view_context_analysis() && cmp_type != INT_RESULT)
+ {
+ Item_field *field_item= (Item_field*) (args[0]->real_item());
+ if (field_item->field_type() == MYSQL_TYPE_LONGLONG ||
+ field_item->field_type() == MYSQL_TYPE_YEAR)
+ {
+ bool all_converted= TRUE;
+ for (arg=args+1, arg_end=args+arg_count; arg != arg_end ; arg++)
+ {
+ if (!convert_const_to_int(thd, field_item, &arg[0]))
+ all_converted= FALSE;
+ }
+ if (all_converted)
+ cmp_type= INT_RESULT;
+ }
+ }
+ switch (cmp_type) {
+ case STRING_RESULT:
+ array=new in_string(arg_count-1,(qsort2_cmp) srtcmp_in,
+ cmp_collation.collation);
+ break;
+ case INT_RESULT:
+ array= new in_longlong(arg_count-1);
+ break;
+ case REAL_RESULT:
+ array= new in_double(arg_count-1);
+ break;
+ case ROW_RESULT:
+ /*
+ The row comparator was created at the beginning but only DATETIME
+ items comparators were initialized. Call store_value() to setup
+ others.
+ */
+ ((in_row*)array)->tmp.store_value(args[0]);
+ break;
+ case DECIMAL_RESULT:
+ array= new in_decimal(arg_count - 1);
+ break;
+ case TIME_RESULT:
+ date_arg= find_date_time_item(args, arg_count, 0);
+ array= new in_datetime(date_arg, arg_count - 1);
+ break;
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ break;
+ }
+ if (array && !(thd->is_fatal_error)) // If not EOM
+ {
+ uint j=0;
+ for (uint i=1 ; i < arg_count ; i++)
+ {
+ array->set(j,args[i]);
+ if (!args[i]->null_value) // Skip NULL values
+ j++;
+ else
+ have_null= 1;
+ }
+ if ((array->used_count= j))
+ array->sort();
+ }
+ }
+ else
+ {
+ if (found_types & (1U << TIME_RESULT))
+ date_arg= find_date_time_item(args, arg_count, 0);
+ if (found_types & (1U << STRING_RESULT) &&
+ agg_arg_charsets_for_comparison(cmp_collation, args, arg_count))
+ return;
+ for (i= 0; i <= (uint) TIME_RESULT; i++)
+ {
+ if (found_types & (1U << i) && !cmp_items[i])
+ {
+ if (!cmp_items[i] && !(cmp_items[i]=
+ cmp_item::get_comparator((Item_result)i, date_arg,
+ cmp_collation.collation)))
+ return;
+ }
+ }
+ }
+ /*
+ Set cmp_context of all arguments. This prevents
+ Item_field::equal_fields_propagator() from transforming a zerofill integer
+ argument into a string constant. Such a change would require rebuilding
+ cmp_itmes.
+ */
+ for (arg= args + 1, arg_end= args + arg_count; arg != arg_end ; arg++)
+ {
+ arg[0]->cmp_context= item_cmp_type(left_result_type, arg[0]->result_type());
+ }
+ max_length= 1;
+}
+
+
+void Item_func_in::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ args[0]->print(str, query_type);
+ if (negated)
+ str->append(STRING_WITH_LEN(" not"));
+ str->append(STRING_WITH_LEN(" in ("));
+ print_args(str, 1, query_type);
+ str->append(STRING_WITH_LEN("))"));
+}
+
+
+/*
+ Evaluate the function and return its value.
+
+ SYNOPSIS
+ val_int()
+
+ DESCRIPTION
+ Evaluate the function and return its value.
+
+ IMPLEMENTATION
+ If the array object is defined then the value of the function is
+ calculated by means of this array.
+ Otherwise several cmp_item objects are used in order to do correct
+ comparison of left expression and an expression from the values list.
+ One cmp_item object correspond to one used comparison type. Left
+ expression can be evaluated up to number of different used comparison
+ types. A bit mapped variable value_added_map is used to check whether
+ the left expression already was evaluated for a particular result type.
+ Result types are mapped to it according to their integer values i.e.
+ STRING_RESULT is mapped to bit 0, REAL_RESULT to bit 1, so on.
+
+ RETURN
+ Value of the function
+*/
+
+longlong Item_func_in::val_int()
+{
+ cmp_item *in_item;
+ DBUG_ASSERT(fixed == 1);
+ uint value_added_map= 0;
+ if (array)
+ {
+ int tmp=array->find(args[0]);
+ null_value=args[0]->null_value || (!tmp && have_null);
+ return (longlong) (!null_value && tmp != negated);
+ }
+
+ if ((null_value= args[0]->real_item()->type() == NULL_ITEM))
+ return 0;
+
+ have_null= 0;
+ for (uint i= 1 ; i < arg_count ; i++)
+ {
+ if (args[i]->real_item()->type() == NULL_ITEM)
+ {
+ have_null= TRUE;
+ continue;
+ }
+ Item_result cmp_type= item_cmp_type(left_result_type, args[i]->cmp_type());
+ in_item= cmp_items[(uint)cmp_type];
+ DBUG_ASSERT(in_item);
+ if (!(value_added_map & (1U << (uint)cmp_type)))
+ {
+ in_item->store_value(args[0]);
+ if ((null_value= args[0]->null_value))
+ return 0;
+ value_added_map|= 1U << (uint)cmp_type;
+ }
+ if (!in_item->cmp(args[i]) && !args[i]->null_value)
+ return (longlong) (!negated);
+ have_null|= args[i]->null_value;
+ }
+
+ null_value= have_null;
+ return (longlong) (!null_value && negated);
+}
+
+
+longlong Item_func_bit_or::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ ulonglong arg1= (ulonglong) args[0]->val_int();
+ if (args[0]->null_value)
+ {
+ null_value=1; /* purecov: inspected */
+ return 0; /* purecov: inspected */
+ }
+ ulonglong arg2= (ulonglong) args[1]->val_int();
+ if (args[1]->null_value)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ return (longlong) (arg1 | arg2);
+}
+
+
+longlong Item_func_bit_and::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ ulonglong arg1= (ulonglong) args[0]->val_int();
+ if (args[0]->null_value)
+ {
+ null_value=1; /* purecov: inspected */
+ return 0; /* purecov: inspected */
+ }
+ ulonglong arg2= (ulonglong) args[1]->val_int();
+ if (args[1]->null_value)
+ {
+ null_value=1; /* purecov: inspected */
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+ return (longlong) (arg1 & arg2);
+}
+
+Item_cond::Item_cond(THD *thd, Item_cond *item)
+ :Item_bool_func(thd, item),
+ abort_on_null(item->abort_on_null),
+ and_tables_cache(item->and_tables_cache)
+{
+ /*
+ item->list will be copied by copy_andor_arguments() call
+ */
+}
+
+
+void Item_cond::copy_andor_arguments(THD *thd, Item_cond *item)
+{
+ List_iterator_fast<Item> li(item->list);
+ while (Item *it= li++)
+ list.push_back(it->copy_andor_structure(thd));
+}
+
+
+bool
+Item_cond::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ List_iterator<Item> li(list);
+ Item *item;
+ uchar buff[sizeof(char*)]; // Max local vars in function
+ not_null_tables_cache= used_tables_cache= 0;
+ const_item_cache= 1;
+
+ /*
+ and_table_cache is the value that Item_cond_or() returns for
+ not_null_tables()
+ */
+ and_tables_cache= ~(table_map) 0;
+
+ if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
+ return TRUE; // Fatal error flag is set!
+ /*
+ The following optimization reduces the depth of an AND-OR tree.
+ E.g. a WHERE clause like
+ F1 AND (F2 AND (F2 AND F4))
+ is parsed into a tree with the same nested structure as defined
+ by braces. This optimization will transform such tree into
+ AND (F1, F2, F3, F4).
+ Trees of OR items are flattened as well:
+ ((F1 OR F2) OR (F3 OR F4)) => OR (F1, F2, F3, F4)
+ Items for removed AND/OR levels will dangle until the death of the
+ entire statement.
+ The optimization is currently prepared statements and stored procedures
+ friendly as it doesn't allocate any memory and its effects are durable
+ (i.e. do not depend on PS/SP arguments).
+ */
+ while ((item=li++))
+ {
+ while (item->type() == Item::COND_ITEM &&
+ ((Item_cond*) item)->functype() == functype() &&
+ !((Item_cond*) item)->list.is_empty())
+ { // Identical function
+ li.replace(((Item_cond*) item)->list);
+ ((Item_cond*) item)->list.empty();
+ item= *li.ref(); // new current item
+ }
+ if (abort_on_null)
+ item->top_level_item();
+
+ /*
+ replace degraded condition:
+ was: <field>
+ become: <field> = 1
+ */
+ if (item->type() == FIELD_ITEM)
+ {
+ Query_arena backup, *arena;
+ Item *new_item;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ if ((new_item= new Item_func_ne(item, new Item_int(0, 1))))
+ li.replace(item= new_item);
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ }
+
+ // item can be substituted in fix_fields
+ if ((!item->fixed &&
+ item->fix_fields(thd, li.ref())) ||
+ (item= *li.ref())->check_cols(1))
+ return TRUE; /* purecov: inspected */
+ used_tables_cache|= item->used_tables();
+ if (item->const_item())
+ {
+ if (!item->is_expensive() && !cond_has_datetime_is_null(item) &&
+ item->val_int() == 0)
+ {
+ /*
+ This is "... OR false_cond OR ..."
+ In this case, false_cond has no effect on cond_or->not_null_tables()
+ */
+ }
+ else
+ {
+ /*
+ This is "... OR const_cond OR ..."
+ In this case, cond_or->not_null_tables()=0, because the condition
+ const_cond might evaluate to true (regardless of whether some tables
+ were NULL-complemented).
+ */
+ and_tables_cache= (table_map) 0;
+ }
+ }
+ else
+ {
+ table_map tmp_table_map= item->not_null_tables();
+ not_null_tables_cache|= tmp_table_map;
+ and_tables_cache&= tmp_table_map;
+
+ const_item_cache= FALSE;
+ }
+
+ with_sum_func= with_sum_func || item->with_sum_func;
+ with_field= with_field || item->with_field;
+ with_subselect|= item->has_subquery();
+ if (item->maybe_null)
+ maybe_null=1;
+ }
+ thd->lex->current_select->cond_count+= list.elements;
+ fix_length_and_dec();
+ fixed= 1;
+ return FALSE;
+}
+
+
+bool
+Item_cond::eval_not_null_tables(uchar *opt_arg)
+{
+ Item *item;
+ List_iterator<Item> li(list);
+ not_null_tables_cache= (table_map) 0;
+ and_tables_cache= ~(table_map) 0;
+ while ((item=li++))
+ {
+ table_map tmp_table_map;
+ if (item->const_item())
+ {
+ if (!item->is_expensive() && !cond_has_datetime_is_null(item) &&
+ item->val_int() == 0)
+ {
+ /*
+ This is "... OR false_cond OR ..."
+ In this case, false_cond has no effect on cond_or->not_null_tables()
+ */
+ }
+ else
+ {
+ /*
+ This is "... OR const_cond OR ..."
+ In this case, cond_or->not_null_tables()=0, because the condition
+ some_cond_or might be true regardless of what tables are
+ NULL-complemented.
+ */
+ and_tables_cache= (table_map) 0;
+ }
+ }
+ else
+ {
+ tmp_table_map= item->not_null_tables();
+ not_null_tables_cache|= tmp_table_map;
+ and_tables_cache&= tmp_table_map;
+ }
+ }
+ return 0;
+}
+
+
+void Item_cond::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ List_iterator<Item> li(list);
+ Item *item;
+
+ used_tables_cache=0;
+ const_item_cache=1;
+
+ and_tables_cache= ~(table_map) 0; // Here and below we do as fix_fields does
+ not_null_tables_cache= 0;
+
+ while ((item=li++))
+ {
+ table_map tmp_table_map;
+ item->fix_after_pullout(new_parent, li.ref());
+ item= *li.ref();
+ used_tables_cache|= item->used_tables();
+ const_item_cache&= item->const_item();
+
+ if (item->const_item())
+ and_tables_cache= (table_map) 0;
+ else
+ {
+ tmp_table_map= item->not_null_tables();
+ not_null_tables_cache|= tmp_table_map;
+ and_tables_cache&= tmp_table_map;
+ const_item_cache= FALSE;
+ }
+ }
+}
+
+
+bool Item_cond::walk(Item_processor processor, bool walk_subquery, uchar *arg)
+{
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ while ((item= li++))
+ if (item->walk(processor, walk_subquery, arg))
+ return 1;
+ return Item_func::walk(processor, walk_subquery, arg);
+}
+
+bool Item_cond_and::walk_top_and(Item_processor processor, uchar *arg)
+{
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ while ((item= li++))
+ if (item->walk_top_and(processor, arg))
+ return 1;
+ return Item_cond::walk_top_and(processor, arg);
+}
+
+
+/**
+ Transform an Item_cond object with a transformer callback function.
+
+ The function recursively applies the transform method to each
+ member item of the condition list.
+ If the call of the method for a member item returns a new item
+ the old item is substituted for a new one.
+ After this the transformer is applied to the root node
+ of the Item_cond object.
+
+ @param transformer the transformer callback function to be applied to
+ the nodes of the tree of the object
+ @param arg parameter to be passed to the transformer
+
+ @return
+ Item returned as the result of transformation of the root node
+*/
+
+Item *Item_cond::transform(Item_transformer transformer, uchar *arg)
+{
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+
+ List_iterator<Item> li(list);
+ Item *item;
+ while ((item= li++))
+ {
+ Item *new_item= item->transform(transformer, arg);
+ if (!new_item)
+ return 0;
+
+ /*
+ THD::change_item_tree() should be called only if the tree was
+ really transformed, i.e. when a new item has been created.
+ Otherwise we'll be allocating a lot of unnecessary memory for
+ change records at each execution.
+ */
+ if (new_item != item)
+ current_thd->change_item_tree(li.ref(), new_item);
+ }
+ return Item_func::transform(transformer, arg);
+}
+
+
+/**
+ Compile Item_cond object with a processor and a transformer
+ callback functions.
+
+ First the function applies the analyzer to the root node of
+ the Item_func object. Then if the analyzer succeeeds (returns TRUE)
+ the function recursively applies the compile method to member
+ item of the condition list.
+ If the call of the method for a member item returns a new item
+ the old item is substituted for a new one.
+ After this the transformer is applied to the root node
+ of the Item_cond object.
+
+ @param analyzer the analyzer callback function to be applied to the
+ nodes of the tree of the object
+ @param[in,out] arg_p parameter to be passed to the analyzer
+ @param transformer the transformer callback function to be applied to the
+ nodes of the tree of the object
+ @param arg_t parameter to be passed to the transformer
+
+ @return
+ Item returned as the result of transformation of the root node
+*/
+
+Item *Item_cond::compile(Item_analyzer analyzer, uchar **arg_p,
+ Item_transformer transformer, uchar *arg_t)
+{
+ if (!(this->*analyzer)(arg_p))
+ return 0;
+
+ List_iterator<Item> li(list);
+ Item *item;
+ while ((item= li++))
+ {
+ /*
+ The same parameter value of arg_p must be passed
+ to analyze any argument of the condition formula.
+ */
+ uchar *arg_v= *arg_p;
+ Item *new_item= item->compile(analyzer, &arg_v, transformer, arg_t);
+ if (new_item && new_item != item)
+ current_thd->change_item_tree(li.ref(), new_item);
+ }
+ return Item_func::transform(transformer, arg_t);
+}
+
+void Item_cond::traverse_cond(Cond_traverser traverser,
+ void *arg, traverse_order order)
+{
+ List_iterator<Item> li(list);
+ Item *item;
+
+ switch(order) {
+ case(PREFIX):
+ (*traverser)(this, arg);
+ while ((item= li++))
+ {
+ item->traverse_cond(traverser, arg, order);
+ }
+ (*traverser)(NULL, arg);
+ break;
+ case(POSTFIX):
+ while ((item= li++))
+ {
+ item->traverse_cond(traverser, arg, order);
+ }
+ (*traverser)(this, arg);
+ }
+}
+
+/**
+ Move SUM items out from item tree and replace with reference.
+
+ The split is done to get an unique item for each SUM function
+ so that we can easily find and calculate them.
+ (Calculation done by update_sum_func() and copy_sum_funcs() in
+ sql_select.cc)
+
+ @param thd Thread handler
+ @param ref_pointer_array Pointer to array of reference fields
+ @param fields All fields in select
+
+ @note
+ This function is run on all expression (SELECT list, WHERE, HAVING etc)
+ that have or refer (HAVING) to a SUM expression.
+*/
+
+void Item_cond::split_sum_func(THD *thd, Item **ref_pointer_array,
+ List<Item> &fields)
+{
+ List_iterator<Item> li(list);
+ Item *item;
+ while ((item= li++))
+ item->split_sum_func2(thd, ref_pointer_array, fields, li.ref(), TRUE);
+}
+
+
+table_map
+Item_cond::used_tables() const
+{ // This caches used_tables
+ return used_tables_cache;
+}
+
+
+void Item_cond::update_used_tables()
+{
+ List_iterator_fast<Item> li(list);
+ Item *item;
+
+ used_tables_cache=0;
+ const_item_cache=1;
+ while ((item=li++))
+ {
+ item->update_used_tables();
+ used_tables_cache|= item->used_tables();
+ const_item_cache&= item->const_item();
+ }
+}
+
+
+void Item_cond::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ if ((item=li++))
+ item->print(str, query_type);
+ while ((item=li++))
+ {
+ str->append(' ');
+ str->append(func_name());
+ str->append(' ');
+ item->print(str, query_type);
+ }
+ str->append(')');
+}
+
+
+void Item_cond::neg_arguments(THD *thd)
+{
+ List_iterator<Item> li(list);
+ Item *item;
+ while ((item= li++)) /* Apply not transformation to the arguments */
+ {
+ Item *new_item= item->neg_transformer(thd);
+ if (!new_item)
+ {
+ if (!(new_item= new Item_func_not(item)))
+ return; // Fatal OEM error
+ }
+ (void) li.replace(new_item);
+ }
+}
+
+
+void Item_cond_and::mark_as_condition_AND_part(TABLE_LIST *embedding)
+{
+ List_iterator<Item> li(list);
+ Item *item;
+ while ((item=li++))
+ {
+ item->mark_as_condition_AND_part(embedding);
+ }
+}
+
+
+/**
+ Evaluation of AND(expr, expr, expr ...).
+
+ @note
+ abort_if_null is set for AND expressions for which we don't care if the
+ result is NULL or 0. This is set for:
+ - WHERE clause
+ - HAVING clause
+ - IF(expression)
+
+ @retval
+ 1 If all expressions are true
+ @retval
+ 0 If all expressions are false or if we find a NULL expression and
+ 'abort_on_null' is set.
+ @retval
+ NULL if all expression are either 1 or NULL
+*/
+
+
+longlong Item_cond_and::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ null_value= 0;
+ while ((item=li++))
+ {
+ if (!item->val_bool())
+ {
+ if (abort_on_null || !(null_value= item->null_value))
+ return 0; // return FALSE
+ }
+ }
+ return null_value ? 0 : 1;
+}
+
+
+longlong Item_cond_or::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ null_value=0;
+ while ((item=li++))
+ {
+ if (item->val_bool())
+ {
+ null_value=0;
+ return 1;
+ }
+ if (item->null_value)
+ null_value=1;
+ }
+ return 0;
+}
+
+/**
+ Create an AND expression from two expressions.
+
+ @param a expression or NULL
+ @param b expression.
+ @param org_item Don't modify a if a == *org_item.
+ If a == NULL, org_item is set to point at b,
+ to ensure that future calls will not modify b.
+
+ @note
+ This will not modify item pointed to by org_item or b
+ The idea is that one can call this in a loop and create and
+ 'and' over all items without modifying any of the original items.
+
+ @retval
+ NULL Error
+ @retval
+ Item
+*/
+
+Item *and_expressions(Item *a, Item *b, Item **org_item)
+{
+ if (!a)
+ return (*org_item= (Item*) b);
+ if (a == *org_item)
+ {
+ Item_cond *res;
+ if ((res= new Item_cond_and(a, (Item*) b)))
+ {
+ res->used_tables_cache= a->used_tables() | b->used_tables();
+ res->not_null_tables_cache= a->not_null_tables() | b->not_null_tables();
+ }
+ return res;
+ }
+ if (((Item_cond_and*) a)->add((Item*) b))
+ return 0;
+ ((Item_cond_and*) a)->used_tables_cache|= b->used_tables();
+ ((Item_cond_and*) a)->not_null_tables_cache|= b->not_null_tables();
+ return a;
+}
+
+
+longlong Item_func_isnull::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (const_item() && !args[0]->maybe_null)
+ return 0;
+ return args[0]->is_null() ? 1: 0;
+}
+
+
+longlong Item_is_not_null_test::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_is_not_null_test::val_int");
+ if (const_item() && !args[0]->maybe_null)
+ DBUG_RETURN(1);
+ if (args[0]->is_null())
+ {
+ DBUG_PRINT("info", ("null"));
+ owner->was_null|= 1;
+ DBUG_RETURN(0);
+ }
+ else
+ DBUG_RETURN(1);
+}
+
+/**
+ Optimize case of not_null_column IS NULL.
+*/
+void Item_is_not_null_test::update_used_tables()
+{
+ if (!args[0]->maybe_null)
+ used_tables_cache= 0; /* is always true */
+ else
+ args[0]->update_used_tables();
+}
+
+
+longlong Item_func_isnotnull::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ return args[0]->is_null() ? 0 : 1;
+}
+
+
+void Item_func_isnotnull::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" is not null)"));
+}
+
+
+longlong Item_func_like::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String* res = args[0]->val_str(&cmp.value1);
+ if (args[0]->null_value)
+ {
+ null_value=1;
+ return 0;
+ }
+ String* res2 = args[1]->val_str(&cmp.value2);
+ if (args[1]->null_value)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ if (canDoTurboBM)
+ return turboBM_matches(res->ptr(), res->length()) ? 1 : 0;
+ return my_wildcmp(cmp.cmp_collation.collation,
+ res->ptr(),res->ptr()+res->length(),
+ res2->ptr(),res2->ptr()+res2->length(),
+ escape,wild_one,wild_many) ? 0 : 1;
+}
+
+
+/**
+ We can optimize a where if first character isn't a wildcard
+*/
+
+Item_func::optimize_type Item_func_like::select_optimize() const
+{
+ if (!args[1]->const_item() || args[1]->is_expensive())
+ return OPTIMIZE_NONE;
+
+ String* res2= args[1]->val_str((String *)&cmp.value2);
+ if (!res2)
+ return OPTIMIZE_NONE;
+
+ if (!res2->length()) // Can optimize empty wildcard: column LIKE ''
+ return OPTIMIZE_OP;
+
+ DBUG_ASSERT(res2->ptr());
+ char first= res2->ptr()[0];
+ return (first == wild_many || first == wild_one) ?
+ OPTIMIZE_NONE : OPTIMIZE_OP;
+}
+
+
+bool Item_func_like::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ if (Item_bool_func2::fix_fields(thd, ref) ||
+ escape_item->fix_fields(thd, &escape_item))
+ return TRUE;
+
+ if (!escape_item->const_during_execution())
+ {
+ my_error(ER_WRONG_ARGUMENTS,MYF(0),"ESCAPE");
+ return TRUE;
+ }
+
+ if (escape_item->const_item())
+ {
+ /* If we are on execution stage */
+ String *escape_str= escape_item->val_str(&cmp.value1);
+ if (escape_str)
+ {
+ const char *escape_str_ptr= escape_str->ptr();
+ if (escape_used_in_parsing && (
+ (((thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) &&
+ escape_str->numchars() != 1) ||
+ escape_str->numchars() > 1)))
+ {
+ my_error(ER_WRONG_ARGUMENTS,MYF(0),"ESCAPE");
+ return TRUE;
+ }
+
+ if (use_mb(cmp.cmp_collation.collation))
+ {
+ CHARSET_INFO *cs= escape_str->charset();
+ my_wc_t wc;
+ int rc= cs->cset->mb_wc(cs, &wc,
+ (const uchar*) escape_str_ptr,
+ (const uchar*) escape_str_ptr +
+ escape_str->length());
+ escape= (int) (rc > 0 ? wc : '\\');
+ }
+ else
+ {
+ /*
+ In the case of 8bit character set, we pass native
+ code instead of Unicode code as "escape" argument.
+ Convert to "cs" if charset of escape differs.
+ */
+ CHARSET_INFO *cs= cmp.cmp_collation.collation;
+ uint32 unused;
+ if (escape_str->needs_conversion(escape_str->length(),
+ escape_str->charset(), cs, &unused))
+ {
+ char ch;
+ uint errors;
+ uint32 cnvlen= copy_and_convert(&ch, 1, cs, escape_str_ptr,
+ escape_str->length(),
+ escape_str->charset(), &errors);
+ escape= cnvlen ? ch : '\\';
+ }
+ else
+ escape= escape_str_ptr ? *escape_str_ptr : '\\';
+ }
+ }
+ else
+ escape= '\\';
+
+ /*
+ We could also do boyer-more for non-const items, but as we would have to
+ recompute the tables for each row it's not worth it.
+ */
+ if (args[1]->const_item() && !use_strnxfrm(collation.collation) &&
+ !args[1]->is_expensive())
+ {
+ String* res2 = args[1]->val_str(&cmp.value2);
+ if (!res2)
+ return FALSE; // Null argument
+
+ const size_t len = res2->length();
+ const char* first = res2->ptr();
+ const char* last = first + len - 1;
+ /*
+ len must be > 2 ('%pattern%')
+ heuristic: only do TurboBM for pattern_len > 2
+ */
+
+ if (len > MIN_TURBOBM_PATTERN_LEN + 2 &&
+ *first == wild_many &&
+ *last == wild_many)
+ {
+ const char* tmp = first + 1;
+ for (; *tmp != wild_many && *tmp != wild_one && *tmp != escape; tmp++) ;
+ canDoTurboBM = (tmp == last) && !use_mb(args[0]->collation.collation);
+ }
+ if (canDoTurboBM)
+ {
+ pattern_len = (int) len - 2;
+ pattern = thd->strmake(first + 1, pattern_len);
+ DBUG_PRINT("info", ("Initializing pattern: '%s'", first));
+ int *suff = (int*) thd->alloc((int) (sizeof(int)*
+ ((pattern_len + 1)*2+
+ alphabet_size)));
+ bmGs = suff + pattern_len + 1;
+ bmBc = bmGs + pattern_len + 1;
+ turboBM_compute_good_suffix_shifts(suff);
+ turboBM_compute_bad_character_shifts();
+ DBUG_PRINT("info",("done"));
+ }
+ use_sampling= (len > 2 && (*first == wild_many || *first == wild_one));
+ }
+ }
+ return FALSE;
+}
+
+
+void Item_func_like::cleanup()
+{
+ canDoTurboBM= FALSE;
+ Item_bool_func2::cleanup();
+}
+
+
+bool Item_func_like::find_selective_predicates_list_processor(uchar *arg)
+{
+ find_selective_predicates_list_processor_data *data=
+ (find_selective_predicates_list_processor_data *) arg;
+ if (use_sampling && used_tables() == data->table->map)
+ {
+ COND_STATISTIC *stat= (COND_STATISTIC *)sql_alloc(sizeof(COND_STATISTIC));
+ if (!stat)
+ return TRUE;
+ stat->cond= this;
+ Item *arg0= args[0]->real_item();
+ if (args[1]->const_item() && arg0->type() == FIELD_ITEM)
+ stat->field_arg= ((Item_field *)arg0)->field;
+ else
+ stat->field_arg= NULL;
+ data->list.push_back(stat);
+ }
+ return FALSE;
+}
+
+
+int Regexp_processor_pcre::default_regex_flags()
+{
+ return default_regex_flags_pcre(current_thd);
+}
+
+
+/**
+ Convert string to lib_charset, if needed.
+*/
+String *Regexp_processor_pcre::convert_if_needed(String *str, String *converter)
+{
+ if (m_conversion_is_needed)
+ {
+ uint dummy_errors;
+ if (converter->copy(str->ptr(), str->length(), str->charset(),
+ m_library_charset, &dummy_errors))
+ return NULL;
+ str= converter;
+ }
+ return str;
+}
+
+
+/**
+ @brief Compile regular expression.
+
+ @param[in] pattern the pattern to compile from.
+ @param[in] send_error send error message if any.
+
+ @details Make necessary character set conversion then
+ compile regular expression passed in the args[1].
+
+ @retval false success.
+ @retval true error occurred.
+ */
+
+bool Regexp_processor_pcre::compile(String *pattern, bool send_error)
+{
+ const char *pcreErrorStr;
+ int pcreErrorOffset;
+
+ if (is_compiled())
+ {
+ if (!stringcmp(pattern, &m_prev_pattern))
+ return false;
+ m_prev_pattern.copy(*pattern);
+ pcre_free(m_pcre);
+ m_pcre= NULL;
+ }
+
+ if (!(pattern= convert_if_needed(pattern, &pattern_converter)))
+ return true;
+
+ m_pcre= pcre_compile(pattern->c_ptr_safe(), m_library_flags,
+ &pcreErrorStr, &pcreErrorOffset, NULL);
+
+ if (m_pcre == NULL)
+ {
+ if (send_error)
+ {
+ char buff[MAX_FIELD_WIDTH];
+ my_snprintf(buff, sizeof(buff), "%s at offset %d", pcreErrorStr, pcreErrorOffset);
+ my_error(ER_REGEXP_ERROR, MYF(0), buff);
+ }
+ return true;
+ }
+ return false;
+}
+
+
+bool Regexp_processor_pcre::compile(Item *item, bool send_error)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+ String *pattern= item->val_str(&tmp);
+ if (item->null_value || compile(pattern, send_error))
+ return true;
+ return false;
+}
+
+
+bool Regexp_processor_pcre::exec(const char *str, int length, int offset)
+{
+ m_pcre_exec_rc= pcre_exec(m_pcre, NULL, str, length,
+ offset, 0, m_SubStrVec, m_subpatterns_needed * 3);
+ return false;
+}
+
+
+bool Regexp_processor_pcre::exec(String *str, int offset,
+ uint n_result_offsets_to_convert)
+{
+ if (!(str= convert_if_needed(str, &subject_converter)))
+ return true;
+ m_pcre_exec_rc= pcre_exec(m_pcre, NULL, str->c_ptr_safe(), str->length(),
+ offset, 0, m_SubStrVec, m_subpatterns_needed * 3);
+ if (m_pcre_exec_rc > 0)
+ {
+ uint i;
+ for (i= 0; i < n_result_offsets_to_convert; i++)
+ {
+ /*
+ Convert byte offset into character offset.
+ */
+ m_SubStrVec[i]= (int) str->charset()->cset->numchars(str->charset(),
+ str->ptr(),
+ str->ptr() +
+ m_SubStrVec[i]);
+ }
+ }
+ return false;
+}
+
+
+bool Regexp_processor_pcre::exec(Item *item, int offset,
+ uint n_result_offsets_to_convert)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff),&my_charset_bin);
+ String *res= item->val_str(&tmp);
+ if (item->null_value)
+ return true;
+ return exec(res, offset, n_result_offsets_to_convert);
+}
+
+
+void Regexp_processor_pcre::fix_owner(Item_func *owner,
+ Item *subject_arg,
+ Item *pattern_arg)
+{
+ if (!is_compiled() && pattern_arg->const_item())
+ {
+ if (compile(pattern_arg, true))
+ {
+ owner->maybe_null= 1; // Will always return NULL
+ return;
+ }
+ set_const(true);
+ owner->maybe_null= subject_arg->maybe_null;
+ }
+ else
+ owner->maybe_null= 1;
+}
+
+
+void
+Item_func_regex::fix_length_and_dec()
+{
+ Item_bool_func::fix_length_and_dec();
+
+ if (agg_arg_charsets_for_comparison(cmp_collation, args, 2))
+ return;
+
+ re.init(cmp_collation.collation, 0, 0);
+ re.fix_owner(this, args[0], args[1]);
+}
+
+
+longlong Item_func_regex::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((null_value= re.recompile(args[1])))
+ return 0;
+
+ if ((null_value= re.exec(args[0], 0, 0)))
+ return 0;
+
+ return re.match();
+}
+
+
+void
+Item_func_regexp_instr::fix_length_and_dec()
+{
+ if (agg_arg_charsets_for_comparison(cmp_collation, args, 2))
+ return;
+
+ re.init(cmp_collation.collation, 0, 1);
+ re.fix_owner(this, args[0], args[1]);
+}
+
+
+longlong Item_func_regexp_instr::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if ((null_value= re.recompile(args[1])))
+ return 0;
+
+ if ((null_value= re.exec(args[0], 0, 1)))
+ return 0;
+
+ return re.match() ? re.subpattern_start(0) + 1 : 0;
+}
+
+
+#ifdef LIKE_CMP_TOUPPER
+#define likeconv(cs,A) (uchar) (cs)->toupper(A)
+#else
+#define likeconv(cs,A) (uchar) (cs)->sort_order[(uchar) (A)]
+#endif
+
+
+/**
+ Precomputation dependent only on pattern_len.
+*/
+
+void Item_func_like::turboBM_compute_suffixes(int *suff)
+{
+ const int plm1 = pattern_len - 1;
+ int f = 0;
+ int g = plm1;
+ int *const splm1 = suff + plm1;
+ CHARSET_INFO *cs= cmp.cmp_collation.collation;
+
+ *splm1 = pattern_len;
+
+ if (!cs->sort_order)
+ {
+ int i;
+ for (i = pattern_len - 2; i >= 0; i--)
+ {
+ int tmp = *(splm1 + i - f);
+ if (g < i && tmp < i - g)
+ suff[i] = tmp;
+ else
+ {
+ if (i < g)
+ g = i; // g = MY_MIN(i, g)
+ f = i;
+ while (g >= 0 && pattern[g] == pattern[g + plm1 - f])
+ g--;
+ suff[i] = f - g;
+ }
+ }
+ }
+ else
+ {
+ int i;
+ for (i = pattern_len - 2; 0 <= i; --i)
+ {
+ int tmp = *(splm1 + i - f);
+ if (g < i && tmp < i - g)
+ suff[i] = tmp;
+ else
+ {
+ if (i < g)
+ g = i; // g = MY_MIN(i, g)
+ f = i;
+ while (g >= 0 &&
+ likeconv(cs, pattern[g]) == likeconv(cs, pattern[g + plm1 - f]))
+ g--;
+ suff[i] = f - g;
+ }
+ }
+ }
+}
+
+
+/**
+ Precomputation dependent only on pattern_len.
+*/
+
+void Item_func_like::turboBM_compute_good_suffix_shifts(int *suff)
+{
+ turboBM_compute_suffixes(suff);
+
+ int *end = bmGs + pattern_len;
+ int *k;
+ for (k = bmGs; k < end; k++)
+ *k = pattern_len;
+
+ int tmp;
+ int i;
+ int j = 0;
+ const int plm1 = pattern_len - 1;
+ for (i = plm1; i > -1; i--)
+ {
+ if (suff[i] == i + 1)
+ {
+ for (tmp = plm1 - i; j < tmp; j++)
+ {
+ int *tmp2 = bmGs + j;
+ if (*tmp2 == pattern_len)
+ *tmp2 = tmp;
+ }
+ }
+ }
+
+ int *tmp2;
+ for (tmp = plm1 - i; j < tmp; j++)
+ {
+ tmp2 = bmGs + j;
+ if (*tmp2 == pattern_len)
+ *tmp2 = tmp;
+ }
+
+ tmp2 = bmGs + plm1;
+ for (i = 0; i <= pattern_len - 2; i++)
+ *(tmp2 - suff[i]) = plm1 - i;
+}
+
+
+/**
+ Precomputation dependent on pattern_len.
+*/
+
+void Item_func_like::turboBM_compute_bad_character_shifts()
+{
+ int *i;
+ int *end = bmBc + alphabet_size;
+ int j;
+ const int plm1 = pattern_len - 1;
+ CHARSET_INFO *cs= cmp.cmp_collation.collation;
+
+ for (i = bmBc; i < end; i++)
+ *i = pattern_len;
+
+ if (!cs->sort_order)
+ {
+ for (j = 0; j < plm1; j++)
+ bmBc[(uint) (uchar) pattern[j]] = plm1 - j;
+ }
+ else
+ {
+ for (j = 0; j < plm1; j++)
+ bmBc[(uint) likeconv(cs,pattern[j])] = plm1 - j;
+ }
+}
+
+
+/**
+ Search for pattern in text.
+
+ @return
+ returns true/false for match/no match
+*/
+
+bool Item_func_like::turboBM_matches(const char* text, int text_len) const
+{
+ register int bcShift;
+ register int turboShift;
+ int shift = pattern_len;
+ int j = 0;
+ int u = 0;
+ CHARSET_INFO *cs= cmp.cmp_collation.collation;
+
+ const int plm1= pattern_len - 1;
+ const int tlmpl= text_len - pattern_len;
+
+ /* Searching */
+ if (!cs->sort_order)
+ {
+ while (j <= tlmpl)
+ {
+ register int i= plm1;
+ while (i >= 0 && pattern[i] == text[i + j])
+ {
+ i--;
+ if (i == plm1 - shift)
+ i-= u;
+ }
+ if (i < 0)
+ return 1;
+
+ register const int v = plm1 - i;
+ turboShift = u - v;
+ bcShift = bmBc[(uint) (uchar) text[i + j]] - plm1 + i;
+ shift = MY_MAX(turboShift, bcShift);
+ shift = MY_MAX(shift, bmGs[i]);
+ if (shift == bmGs[i])
+ u = MY_MIN(pattern_len - shift, v);
+ else
+ {
+ if (turboShift < bcShift)
+ shift = MY_MAX(shift, u + 1);
+ u = 0;
+ }
+ j+= shift;
+ }
+ return 0;
+ }
+ else
+ {
+ while (j <= tlmpl)
+ {
+ register int i = plm1;
+ while (i >= 0 && likeconv(cs,pattern[i]) == likeconv(cs,text[i + j]))
+ {
+ i--;
+ if (i == plm1 - shift)
+ i-= u;
+ }
+ if (i < 0)
+ return 1;
+
+ register const int v = plm1 - i;
+ turboShift = u - v;
+ bcShift = bmBc[(uint) likeconv(cs, text[i + j])] - plm1 + i;
+ shift = MY_MAX(turboShift, bcShift);
+ shift = MY_MAX(shift, bmGs[i]);
+ if (shift == bmGs[i])
+ u = MY_MIN(pattern_len - shift, v);
+ else
+ {
+ if (turboShift < bcShift)
+ shift = MY_MAX(shift, u + 1);
+ u = 0;
+ }
+ j+= shift;
+ }
+ return 0;
+ }
+}
+
+
+/**
+ Make a logical XOR of the arguments.
+
+ If either operator is NULL, return NULL.
+
+ @todo
+ (low priority) Change this to be optimized as: @n
+ A XOR B -> (A) == 1 AND (B) <> 1) OR (A <> 1 AND (B) == 1) @n
+ To be able to do this, we would however first have to extend the MySQL
+ range optimizer to handle OR better.
+
+ @note
+ As we don't do any index optimization on XOR this is not going to be
+ very fast to use.
+*/
+
+longlong Item_func_xor::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int result= 0;
+ null_value= false;
+ for (uint i= 0; i < arg_count; i++)
+ {
+ result^= (args[i]->val_int() != 0);
+ if (args[i]->null_value)
+ {
+ null_value= true;
+ return 0;
+ }
+ }
+ return result;
+}
+
+/**
+ Apply NOT transformation to the item and return a new one.
+
+
+ Transform the item using next rules:
+ @verbatim
+ a AND b AND ... -> NOT(a) OR NOT(b) OR ...
+ a OR b OR ... -> NOT(a) AND NOT(b) AND ...
+ NOT(a) -> a
+ a = b -> a != b
+ a != b -> a = b
+ a < b -> a >= b
+ a >= b -> a < b
+ a > b -> a <= b
+ a <= b -> a > b
+ IS NULL(a) -> IS NOT NULL(a)
+ IS NOT NULL(a) -> IS NULL(a)
+ @endverbatim
+
+ @param thd thread handler
+
+ @return
+ New item or
+ NULL if we cannot apply NOT transformation (see Item::neg_transformer()).
+*/
+
+Item *Item_func_not::neg_transformer(THD *thd) /* NOT(x) -> x */
+{
+ return args[0];
+}
+
+
+bool Item_func_not::fix_fields(THD *thd, Item **ref)
+{
+ args[0]->under_not(this);
+ if (args[0]->type() == FIELD_ITEM)
+ {
+ /* replace "NOT <field>" with "<filed> == 0" */
+ Query_arena backup, *arena;
+ Item *new_item;
+ bool rc= TRUE;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ if ((new_item= new Item_func_eq(args[0], new Item_int(0, 1))))
+ {
+ new_item->name= name;
+ rc= (*ref= new_item)->fix_fields(thd, ref);
+ }
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ return rc;
+ }
+ return Item_func::fix_fields(thd, ref);
+}
+
+
+Item *Item_bool_rowready_func2::neg_transformer(THD *thd)
+{
+ Item *item= negated_item();
+ return item;
+}
+
+/**
+ XOR can be negated by negating one of the operands:
+
+ NOT (a XOR b) => (NOT a) XOR b
+ => a XOR (NOT b)
+
+ @param thd Thread handle
+ @return New negated item
+*/
+Item *Item_func_xor::neg_transformer(THD *thd)
+{
+ Item *neg_operand;
+ Item_func_xor *new_item;
+ if ((neg_operand= args[0]->neg_transformer(thd)))
+ // args[0] has neg_tranformer
+ new_item= new(thd->mem_root) Item_func_xor(neg_operand, args[1]);
+ else if ((neg_operand= args[1]->neg_transformer(thd)))
+ // args[1] has neg_tranformer
+ new_item= new(thd->mem_root) Item_func_xor(args[0], neg_operand);
+ else
+ {
+ neg_operand= new(thd->mem_root) Item_func_not(args[0]);
+ new_item= new(thd->mem_root) Item_func_xor(neg_operand, args[1]);
+ }
+ return new_item;
+}
+
+
+/**
+ a IS NULL -> a IS NOT NULL.
+*/
+Item *Item_func_isnull::neg_transformer(THD *thd)
+{
+ Item *item= new Item_func_isnotnull(args[0]);
+ return item;
+}
+
+
+/**
+ a IS NOT NULL -> a IS NULL.
+*/
+Item *Item_func_isnotnull::neg_transformer(THD *thd)
+{
+ Item *item= new Item_func_isnull(args[0]);
+ return item;
+}
+
+
+Item *Item_cond_and::neg_transformer(THD *thd) /* NOT(a AND b AND ...) -> */
+ /* NOT a OR NOT b OR ... */
+{
+ neg_arguments(thd);
+ Item *item= new Item_cond_or(list);
+ return item;
+}
+
+
+Item *Item_cond_or::neg_transformer(THD *thd) /* NOT(a OR b OR ...) -> */
+ /* NOT a AND NOT b AND ... */
+{
+ neg_arguments(thd);
+ Item *item= new Item_cond_and(list);
+ return item;
+}
+
+
+Item *Item_func_nop_all::neg_transformer(THD *thd)
+{
+ /* "NOT (e $cmp$ ANY (SELECT ...)) -> e $rev_cmp$" ALL (SELECT ...) */
+ Item_func_not_all *new_item= new Item_func_not_all(args[0]);
+ Item_allany_subselect *allany= (Item_allany_subselect*)args[0];
+ allany->create_comp_func(FALSE);
+ allany->all= !allany->all;
+ allany->upper_item= new_item;
+ return new_item;
+}
+
+Item *Item_func_not_all::neg_transformer(THD *thd)
+{
+ /* "NOT (e $cmp$ ALL (SELECT ...)) -> e $rev_cmp$" ANY (SELECT ...) */
+ Item_func_nop_all *new_item= new Item_func_nop_all(args[0]);
+ Item_allany_subselect *allany= (Item_allany_subselect*)args[0];
+ allany->all= !allany->all;
+ allany->create_comp_func(TRUE);
+ allany->upper_item= new_item;
+ return new_item;
+}
+
+Item *Item_func_eq::negated_item() /* a = b -> a != b */
+{
+ return new Item_func_ne(args[0], args[1]);
+}
+
+
+Item *Item_func_ne::negated_item() /* a != b -> a = b */
+{
+ return new Item_func_eq(args[0], args[1]);
+}
+
+
+Item *Item_func_lt::negated_item() /* a < b -> a >= b */
+{
+ return new Item_func_ge(args[0], args[1]);
+}
+
+
+Item *Item_func_ge::negated_item() /* a >= b -> a < b */
+{
+ return new Item_func_lt(args[0], args[1]);
+}
+
+
+Item *Item_func_gt::negated_item() /* a > b -> a <= b */
+{
+ return new Item_func_le(args[0], args[1]);
+}
+
+
+Item *Item_func_le::negated_item() /* a <= b -> a > b */
+{
+ return new Item_func_gt(args[0], args[1]);
+}
+
+/**
+ just fake method, should never be called.
+*/
+Item *Item_bool_rowready_func2::negated_item()
+{
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+
+/**
+ Construct a minimal multiple equality item
+
+ @param f1 the first equal item
+ @param f2 the second equal item
+ @param with_const_item TRUE if the first item is constant
+
+ @details
+ The constructor builds a new item equal object for the equality f1=f2.
+ One of the equal items can be constant. If this is the case it is passed
+ always as the first parameter and the parameter with_const_item serves
+ as an indicator of this case.
+ Currently any non-constant parameter items must point to an item of the
+ of the type Item_field or Item_direct_view_ref(Item_field).
+*/
+
+Item_equal::Item_equal(Item *f1, Item *f2, bool with_const_item)
+ : Item_bool_func(), eval_item(0), cond_false(0), cond_true(0),
+ context_field(NULL), link_equal_fields(FALSE)
+{
+ const_item_cache= 0;
+ with_const= with_const_item;
+ equal_items.push_back(f1);
+ equal_items.push_back(f2);
+ compare_as_dates= with_const_item && f2->cmp_type() == TIME_RESULT;
+ upper_levels= NULL;
+ sargable= TRUE;
+}
+
+
+/**
+ Copy constructor for a multiple equality
+
+ @param item_equal source item for the constructor
+
+ @details
+ The function creates a copy of an Item_equal object.
+ This constructor is used when an item belongs to a multiple equality
+ of an upper level (an upper AND/OR level or an upper level of a nested
+ outer join).
+*/
+
+Item_equal::Item_equal(Item_equal *item_equal)
+ : Item_bool_func(), eval_item(0), cond_false(0), cond_true(0),
+ context_field(NULL), link_equal_fields(FALSE)
+{
+ const_item_cache= 0;
+ List_iterator_fast<Item> li(item_equal->equal_items);
+ Item *item;
+ while ((item= li++))
+ {
+ equal_items.push_back(item);
+ }
+ with_const= item_equal->with_const;
+ compare_as_dates= item_equal->compare_as_dates;
+ cond_false= item_equal->cond_false;
+ upper_levels= item_equal->upper_levels;
+ sargable= TRUE;
+}
+
+
+/**
+ @brief
+ Add a constant item to the Item_equal object
+
+ @param[in] c the constant to add
+ @param[in] f item from the list equal_items the item c is equal to
+ (this parameter is optional)
+
+ @details
+ The method adds the constant item c to the equal_items list. If the list
+ doesn't have any constant item yet the item c is just put in the front
+ the list. Otherwise the value of c is compared with the value of the
+ constant item from equal_items. If they are not equal cond_false is set
+ to TRUE. This serves as an indicator that this Item_equal is always FALSE.
+ The optional parameter f is used to adjust the flag compare_as_dates.
+*/
+
+void Item_equal::add_const(Item *c, Item *f)
+{
+ if (cond_false)
+ return;
+ if (!with_const)
+ {
+ with_const= TRUE;
+ if (f)
+ compare_as_dates= f->cmp_type() == TIME_RESULT;
+ equal_items.push_front(c);
+ return;
+ }
+ Item *const_item= get_const();
+ if (compare_as_dates)
+ {
+ cmp.set_datetime_cmp_func(this, &c, &const_item);
+ cond_false= cmp.compare();
+ }
+ else
+ {
+ Item_func_eq *func= new Item_func_eq(c, const_item);
+ if (func->set_cmp_func())
+ {
+ /*
+ Setting a comparison function fails when trying to compare
+ incompatible charsets. Charset compatibility is checked earlier,
+ except for constant subqueries where we may do it here.
+ */
+ return;
+ }
+ func->quick_fix_field();
+ cond_false= !func->val_int();
+ }
+ if (with_const && equal_items.elements == 1)
+ cond_true= TRUE;
+ if (cond_false || cond_true)
+ const_item_cache= 1;
+}
+
+
+/**
+ @brief
+ Check whether a field is referred to in the multiple equality
+
+ @param field field whose occurrence is to be checked
+
+ @details
+ The function checks whether field is referred to by one of the
+ items from the equal_items list.
+
+ @retval
+ 1 if multiple equality contains a reference to field
+ @retval
+ 0 otherwise
+*/
+
+bool Item_equal::contains(Field *field)
+{
+ Item_equal_fields_iterator it(*this);
+ while (it++)
+ {
+ if (field->eq(it.get_curr_field()))
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ @brief
+ Join members of another Item_equal object
+
+ @param item multiple equality whose members are to be joined
+
+ @details
+ The function actually merges two multiple equalities. After this operation
+ the Item_equal object additionally contains the field items of another item of
+ the type Item_equal.
+ If the optional constant items are not equal the cond_false flag is set to TRUE.
+
+ @notes
+ The function is called for any equality f1=f2 such that f1 and f2 are items
+ of the type Item_field or Item_direct_view_ref(Item_field), and, f1->field is
+ referred to in the list this->equal_items, while the list item->equal_items
+ contains a reference to f2->field.
+*/
+
+void Item_equal::merge(Item_equal *item)
+{
+ Item *c= item->get_const();
+ if (c)
+ item->equal_items.pop();
+ equal_items.concat(&item->equal_items);
+ if (c)
+ {
+ /*
+ The flag cond_false will be set to TRUE after this if
+ the multiple equality already contains a constant and its
+ value is not equal to the value of c.
+ */
+ add_const(c);
+ }
+ cond_false|= item->cond_false;
+}
+
+
+/**
+ @brief
+ Merge members of another Item_equal object into this one
+
+ @param item multiple equality whose members are to be merged
+ @param save_merged keep the list of equalities in 'item' intact
+ (e.g. for other merges)
+
+ @details
+ If the Item_equal 'item' happens to have some elements of the list
+ of equal items belonging to 'this' object then the function merges
+ the equal items from 'item' into this list.
+ If both lists contains constants and they are different then
+ the value of the cond_false flag is set to TRUE.
+
+ @retval
+ 1 the lists of equal items in 'item' and 'this' contain common elements
+ @retval
+ 0 otherwise
+
+ @notes
+ The method 'merge' just joins the list of equal items belonging to 'item'
+ to the list of equal items belonging to this object assuming that the lists
+ are disjoint. It would be more correct to call the method 'join'.
+ The method 'merge_into_with_check' really merges two lists of equal items if
+ they have common members.
+*/
+
+bool Item_equal::merge_with_check(Item_equal *item, bool save_merged)
+{
+ bool intersected= FALSE;
+ Item_equal_fields_iterator_slow fi(*item);
+
+ while (fi++)
+ {
+ if (contains(fi.get_curr_field()))
+ {
+ intersected= TRUE;
+ if (!save_merged)
+ fi.remove();
+ }
+ }
+ if (intersected)
+ {
+ if (!save_merged)
+ merge(item);
+ else
+ {
+ Item *c= item->get_const();
+ if (c)
+ add_const(c);
+ if (!cond_false)
+ {
+ Item *item;
+ fi.rewind();
+ while ((item= fi++))
+ {
+ if (!contains(fi.get_curr_field()))
+ add(item);
+ }
+ }
+ }
+ }
+ return intersected;
+}
+
+
+/**
+ @brief
+ Merge this object into a list of Item_equal objects
+
+ @param list the list of Item_equal objects to merge into
+ @param save_merged keep the list of equalities in 'this' intact
+ (e.g. for other merges)
+ @param only_intersected do not merge if there are no common members
+ in any of Item_equal objects from the list
+ and this Item_equal
+
+ @details
+ If the list of equal items from 'this' object contains common members
+ with the lists of equal items belonging to Item_equal objects from 'list'
+ then all involved Item_equal objects e1,...,ek are merged into one
+ Item equal that replaces e1,...,ek in the 'list'. Otherwise, in the case
+ when the value of the parameter only_if_intersected is false, this
+ Item_equal is joined to the 'list'.
+*/
+
+void Item_equal::merge_into_list(List<Item_equal> *list,
+ bool save_merged,
+ bool only_intersected)
+{
+ Item_equal *item;
+ List_iterator<Item_equal> it(*list);
+ Item_equal *merge_into= NULL;
+ while((item= it++))
+ {
+ if (!merge_into)
+ {
+ if (item->merge_with_check(this, save_merged))
+ merge_into= item;
+ }
+ else
+ {
+ if (merge_into->merge_with_check(item, false))
+ it.remove();
+ }
+ }
+ if (!only_intersected && !merge_into)
+ list->push_back(this);
+}
+
+
+/**
+ @brief
+ Order equal items of the multiple equality according to a sorting criteria
+
+ @param compare function to compare items from the equal_items list
+ @param arg context extra parameter for the cmp function
+
+ @details
+ The function performs ordering of the items from the equal_items list
+ according to the criteria determined by the cmp callback parameter.
+ If cmp(item1,item2,arg)<0 than item1 must be placed after item2.
+
+ @notes
+ The function sorts equal items by the bubble sort algorithm.
+ The list of field items is looked through and whenever two neighboring
+ members follow in a wrong order they are swapped. This is performed
+ again and again until we get all members in a right order.
+*/
+
+void Item_equal::sort(Item_field_cmpfunc compare, void *arg)
+{
+ bubble_sort<Item>(&equal_items, compare, arg);
+}
+
+
+/**
+ @brief
+ Check appearance of new constant items in the multiple equality object
+
+ @details
+ The function checks appearance of new constant items among the members
+ of the equal_items list. Each new constant item is compared with
+ the constant item from the list if there is any. If there is none the first
+ new constant item is placed at the very beginning of the list and
+ with_const is set to TRUE. If it happens that the compared constant items
+ are unequal then the flag cond_false is set to TRUE.
+
+ @notes
+ Currently this function is called only after substitution of constant tables.
+*/
+
+void Item_equal::update_const()
+{
+ List_iterator<Item> it(equal_items);
+ if (with_const)
+ it++;
+ Item *item;
+ while ((item= it++))
+ {
+ if (item->const_item() && !item->is_expensive() &&
+ /*
+ Don't propagate constant status of outer-joined column.
+ Such a constant status here is a result of:
+ a) empty outer-joined table: in this case such a column has a
+ value of NULL; but at the same time other arguments of
+ Item_equal don't have to be NULLs and the value of the whole
+ multiple equivalence expression doesn't have to be NULL or FALSE
+ because of the outer join nature;
+ or
+ b) outer-joined table contains only 1 row: the result of
+ this column is equal to a row field value *or* NULL.
+ Both values are inacceptable as Item_equal constants.
+ */
+ !item->is_outer_field())
+ {
+ if (item == equal_items.head())
+ with_const= TRUE;
+ else
+ {
+ it.remove();
+ add_const(item);
+ }
+ }
+ }
+}
+
+
+/**
+ @brief
+ Fix fields in a completely built multiple equality
+
+ @param thd currently not used thread handle
+ @param ref not used
+
+ @details
+ This function is called once the multiple equality has been built out of
+ the WHERE/ON condition and no new members are expected to be added to the
+ equal_items list anymore.
+ As any implementation of the virtual fix_fields method the function
+ calculates the cached values of not_null_tables_cache, used_tables_cache,
+ const_item_cache and calls fix_length_and_dec().
+ Additionally the function sets a reference to the Item_equal object in
+ the non-constant items of the equal_items list unless such a reference has
+ been already set.
+
+ @notes
+ Currently this function is called only in the function
+ build_equal_items_for_cond.
+
+ @retval
+ FALSE always
+*/
+
+bool Item_equal::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ Item_equal_fields_iterator it(*this);
+ Item *item;
+ Field *first_equal_field= NULL;
+ Field *last_equal_field= NULL;
+ Field *prev_equal_field= NULL;
+ not_null_tables_cache= used_tables_cache= 0;
+ const_item_cache= 0;
+ while ((item= it++))
+ {
+ table_map tmp_table_map;
+ used_tables_cache|= item->used_tables();
+ tmp_table_map= item->not_null_tables();
+ not_null_tables_cache|= tmp_table_map;
+ DBUG_ASSERT(!item->with_sum_func && !item->with_subselect);
+ if (item->maybe_null)
+ maybe_null= 1;
+ if (!item->get_item_equal())
+ item->set_item_equal(this);
+ if (link_equal_fields && item->real_item()->type() == FIELD_ITEM)
+ {
+ last_equal_field= ((Item_field *) (item->real_item()))->field;
+ if (!prev_equal_field)
+ first_equal_field= last_equal_field;
+ else
+ prev_equal_field->next_equal_field= last_equal_field;
+ prev_equal_field= last_equal_field;
+ }
+ }
+ if (prev_equal_field && last_equal_field != first_equal_field)
+ last_equal_field->next_equal_field= first_equal_field;
+ fix_length_and_dec();
+ fixed= 1;
+ return FALSE;
+}
+
+
+/**
+ Update the value of the used table attribute and other attributes
+ */
+
+void Item_equal::update_used_tables()
+{
+ not_null_tables_cache= used_tables_cache= 0;
+ if ((const_item_cache= cond_false || cond_true))
+ return;
+ Item_equal_fields_iterator it(*this);
+ Item *item;
+ const_item_cache= 1;
+ while ((item= it++))
+ {
+ item->update_used_tables();
+ used_tables_cache|= item->used_tables();
+ /* see commentary at Item_equal::update_const() */
+ const_item_cache&= item->const_item() && !item->is_outer_field();
+ }
+}
+
+
+bool Item_equal::count_sargable_conds(uchar *arg)
+{
+ SELECT_LEX *sel= (SELECT_LEX *) arg;
+ uint m= equal_items.elements;
+ sel->cond_count+= m*(m-1);
+ return 0;
+}
+
+
+/**
+ @brief
+ Evaluate multiple equality
+
+ @details
+ The function evaluate multiple equality to a boolean value.
+ The function ignores non-constant items from the equal_items list.
+ The function returns 1 if all constant items from the list are equal.
+ It returns 0 if there are unequal constant items in the list or
+ one of the constant items is evaluated to NULL.
+
+ @notes
+ Currently this function can be called only at the optimization
+ stage after the constant table substitution, since all Item_equals
+ are eliminated before the execution stage.
+
+ @retval
+ 0 multiple equality is always FALSE or NULL
+ 1 otherwise
+*/
+
+longlong Item_equal::val_int()
+{
+ if (cond_false)
+ return 0;
+ if (cond_true)
+ return 1;
+ Item *item= get_const();
+ Item_equal_fields_iterator it(*this);
+ if (!item)
+ item= it++;
+ eval_item->store_value(item);
+ if ((null_value= item->null_value))
+ return 0;
+ while ((item= it++))
+ {
+ Field *field= it.get_curr_field();
+ /* Skip fields of non-const tables. They haven't been read yet */
+ if (field->table->const_table)
+ {
+ if (eval_item->cmp(item) || (null_value= item->null_value))
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+void Item_equal::fix_length_and_dec()
+{
+ Item *item= get_first(NO_PARTICULAR_TAB, NULL);
+ eval_item= cmp_item::get_comparator(item->cmp_type(), item,
+ item->collation.collation);
+}
+
+
+bool Item_equal::walk(Item_processor processor, bool walk_subquery, uchar *arg)
+{
+ Item *item;
+ Item_equal_fields_iterator it(*this);
+ while ((item= it++))
+ {
+ if (item->walk(processor, walk_subquery, arg))
+ return 1;
+ }
+ return Item_func::walk(processor, walk_subquery, arg);
+}
+
+
+Item *Item_equal::transform(Item_transformer transformer, uchar *arg)
+{
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+
+ Item *item;
+ Item_equal_fields_iterator it(*this);
+ while ((item= it++))
+ {
+ Item *new_item= item->transform(transformer, arg);
+ if (!new_item)
+ return 0;
+
+ /*
+ THD::change_item_tree() should be called only if the tree was
+ really transformed, i.e. when a new item has been created.
+ Otherwise we'll be allocating a lot of unnecessary memory for
+ change records at each execution.
+ */
+ if (new_item != item)
+ current_thd->change_item_tree((Item **) it.ref(), new_item);
+ }
+ return Item_func::transform(transformer, arg);
+}
+
+
+void Item_equal::print(String *str, enum_query_type query_type)
+{
+ if (cond_false)
+ {
+ str->append('0');
+ return;
+ }
+ str->append(func_name());
+ str->append('(');
+ List_iterator_fast<Item> it(equal_items);
+ Item *item;
+ item= it++;
+ item->print(str, query_type);
+ while ((item= it++))
+ {
+ str->append(',');
+ str->append(' ');
+ item->print(str, query_type);
+ }
+ str->append(')');
+}
+
+
+CHARSET_INFO *Item_equal::compare_collation()
+{
+ Item_equal_fields_iterator it(*this);
+ Item *item= it++;
+ return item->collation.collation;
+}
+
+
+/*
+ @brief Get the first equal field of multiple equality.
+ @param[in] field the field to get equal field to
+
+ @details Get the first field of multiple equality that is equal to the
+ given field. In order to make semi-join materialization strategy work
+ correctly we can't propagate equal fields from upper select to a
+ materialized semi-join.
+ Thus the fields is returned according to following rules:
+
+ 1) If the given field belongs to a semi-join then the first field in
+ multiple equality which belong to the same semi-join is returned.
+ Otherwise NULL is returned.
+ 2) If the given field doesn't belong to a semi-join then
+ the first field in the multiple equality that doesn't belong to any
+ semi-join is returned.
+ If all fields in the equality are belong to semi-join(s) then NULL
+ is returned.
+ 3) If no field is given then the first field in the multiple equality
+ is returned without regarding whether it belongs to a semi-join or not.
+
+ @retval Found first field in the multiple equality.
+ @retval 0 if no field found.
+*/
+
+Item* Item_equal::get_first(JOIN_TAB *context, Item *field_item)
+{
+ Item_equal_fields_iterator it(*this);
+ Item *item;
+ if (!field_item)
+ return (it++);
+ Field *field= ((Item_field *) (field_item->real_item()))->field;
+
+ /*
+ Of all equal fields, return the first one we can use. Normally, this is the
+ field which belongs to the table that is the first in the join order.
+
+ There is one exception to this: When semi-join materialization strategy is
+ used, and the given field belongs to a table within the semi-join nest, we
+ must pick the first field in the semi-join nest.
+
+ Example: suppose we have a join order:
+
+ ot1 ot2 SJ-Mat(it1 it2 it3) ot3
+
+ and equality ot2.col = it1.col = it2.col
+ If we're looking for best substitute for 'it2.col', we should pick it1.col
+ and not ot2.col.
+
+ eliminate_item_equal() also has code that deals with equality substitution
+ in presense of SJM nests.
+ */
+
+ TABLE_LIST *emb_nest;
+ if (context != NO_PARTICULAR_TAB)
+ emb_nest= context->emb_sj_nest;
+ else
+ emb_nest= field->table->pos_in_table_list->embedding;
+
+ if (emb_nest && emb_nest->sj_mat_info && emb_nest->sj_mat_info->is_used)
+ {
+ /*
+ It's a field from an materialized semi-join. We can substitute it for
+ - a constant item
+ - a field from the same semi-join
+ Find the first of such items:
+ */
+ while ((item= it++))
+ {
+ if (item->const_item() ||
+ it.get_curr_field()->table->pos_in_table_list->embedding == emb_nest)
+ {
+ /*
+ If we found given field then return NULL to avoid unnecessary
+ substitution.
+ */
+ return (item != field_item) ? item : NULL;
+ }
+ }
+ }
+ else
+ {
+ /*
+ The field is not in SJ-Materialization nest. We must return the first
+ field in the join order. The field may be inside a semi-join nest, i.e
+ a join order may look like this:
+
+ SJ-Mat(it1 it2) ot1 ot2
+
+ where we're looking what to substitute ot2.col for. In this case we must
+ still return it1.col, here's a proof why:
+
+ First let's note that either it1.col or it2.col participates in
+ subquery's IN-equality. It can't be otherwise, because materialization is
+ only applicable to uncorrelated subqueries, so the only way we could
+ infer "it1.col=ot1.col" is from the IN-equality. Ok, so IN-eqality has
+ it1.col or it2.col on its inner side. it1.col is first such item in the
+ join order, so it's not possible for SJ-Mat to be
+ SJ-Materialization-lookup, it is SJ-Materialization-Scan. The scan part
+ of this strategy will unpack value of it1.col=it2.col into it1.col
+ (that's the first equal item inside the subquery), and we'll be able to
+ get it from there. qed.
+ */
+
+ return equal_items.head();
+ }
+ // Shouldn't get here.
+ DBUG_ASSERT(0);
+ return NULL;
+}
+
+
+longlong Item_func_dyncol_check::val_int()
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+ DYNAMIC_COLUMN col;
+ String *str;
+ enum enum_dyncol_func_result rc;
+
+ str= args[0]->val_str(&tmp);
+ if (args[0]->null_value)
+ goto null;
+ col.length= str->length();
+ /* We do not change the string, so could do this trick */
+ col.str= (char *)str->ptr();
+ rc= mariadb_dyncol_check(&col);
+ if (rc < 0 && rc != ER_DYNCOL_FORMAT)
+ {
+ dynamic_column_error_message(rc);
+ goto null;
+ }
+ null_value= FALSE;
+ return rc == ER_DYNCOL_OK;
+
+null:
+ null_value= TRUE;
+ return 0;
+}
+
+longlong Item_func_dyncol_exists::val_int()
+{
+ char buff[STRING_BUFFER_USUAL_SIZE], nmstrbuf[11];
+ String tmp(buff, sizeof(buff), &my_charset_bin),
+ nmbuf(nmstrbuf, sizeof(nmstrbuf), system_charset_info);
+ DYNAMIC_COLUMN col;
+ String *str;
+ LEX_STRING buf, *name= NULL;
+ ulonglong num= 0;
+ enum enum_dyncol_func_result rc;
+
+ if (args[1]->result_type() == INT_RESULT)
+ num= args[1]->val_int();
+ else
+ {
+ String *nm= args[1]->val_str(&nmbuf);
+ if (!nm || args[1]->null_value)
+ {
+ null_value= 1;
+ return 1;
+ }
+ if (my_charset_same(nm->charset(), &my_charset_utf8_general_ci))
+ {
+ buf.str= (char *) nm->ptr();
+ buf.length= nm->length();
+ }
+ else
+ {
+ uint strlen;
+ uint dummy_errors;
+ buf.str= (char *)sql_alloc((strlen= nm->length() *
+ my_charset_utf8_general_ci.mbmaxlen + 1));
+ if (buf.str)
+ {
+ buf.length=
+ copy_and_convert(buf.str, strlen, &my_charset_utf8_general_ci,
+ nm->ptr(), nm->length(), nm->charset(),
+ &dummy_errors);
+ }
+ else
+ buf.length= 0;
+ }
+ name= &buf;
+ }
+ str= args[0]->val_str(&tmp);
+ if (args[0]->null_value || args[1]->null_value || num > UINT_MAX16)
+ goto null;
+ col.length= str->length();
+ /* We do not change the string, so could do this trick */
+ col.str= (char *)str->ptr();
+ rc= ((name == NULL) ?
+ mariadb_dyncol_exists_num(&col, (uint) num) :
+ mariadb_dyncol_exists_named(&col, name));
+ if (rc < 0)
+ {
+ dynamic_column_error_message(rc);
+ goto null;
+ }
+ null_value= FALSE;
+ return rc == ER_DYNCOL_YES;
+
+null:
+ null_value= TRUE;
+ return 0;
+}
diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h
new file mode 100644
index 00000000000..8611182f32d
--- /dev/null
+++ b/sql/item_cmpfunc.h
@@ -0,0 +1,2139 @@
+#ifndef ITEM_CMPFUNC_INCLUDED
+#define ITEM_CMPFUNC_INCLUDED
+/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2011, Monty Program Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* compare and test functions */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "thr_malloc.h" /* sql_calloc */
+#include "item_func.h" /* Item_int_func, Item_bool_func */
+#define PCRE_STATIC 1 /* Important on Windows */
+#include "pcre.h" /* pcre header file */
+
+extern Item_result item_cmp_type(Item_result a,Item_result b);
+class Item_bool_func2;
+class Arg_comparator;
+
+typedef int (Arg_comparator::*arg_cmp_func)();
+
+typedef int (*Item_field_cmpfunc)(Item *f1, Item *f2, void *arg);
+
+class Arg_comparator: public Sql_alloc
+{
+ Item **a, **b;
+ arg_cmp_func func;
+ Item_result_field *owner;
+ bool set_null; // TRUE <=> set owner->null_value
+ Arg_comparator *comparators; // used only for compare_row()
+ double precision;
+ /* Fields used in DATE/DATETIME comparison. */
+ THD *thd;
+ Item *a_cache, *b_cache; // Cached values of a and b items
+ // when one of arguments is NULL.
+ int set_compare_func(Item_result_field *owner, Item_result type);
+ inline int set_compare_func(Item_result_field *owner_arg)
+ {
+ return set_compare_func(owner_arg, item_cmp_type((*a)->result_type(),
+ (*b)->result_type()));
+ }
+ bool agg_arg_charsets_for_comparison();
+
+public:
+ DTCollation cmp_collation;
+ /* Allow owner function to use string buffers. */
+ String value1, value2;
+
+ Arg_comparator(): set_null(TRUE), comparators(0), thd(0),
+ a_cache(0), b_cache(0) {};
+ Arg_comparator(Item **a1, Item **a2): a(a1), b(a2), set_null(TRUE),
+ comparators(0), thd(0), a_cache(0), b_cache(0) {};
+
+ int set_cmp_func(Item_result_field *owner_arg,
+ Item **a1, Item **a2,
+ Item_result type);
+
+ inline int set_cmp_func(Item_result_field *owner_arg,
+ Item **a1, Item **a2, bool set_null_arg)
+ {
+ set_null= set_null_arg;
+ return set_cmp_func(owner_arg, a1, a2,
+ item_cmp_type((*a1)->cmp_type(),
+ (*a2)->cmp_type()));
+ }
+ inline int compare() { return (this->*func)(); }
+
+ int compare_string(); // compare args[0] & args[1]
+ int compare_binary_string(); // compare args[0] & args[1]
+ int compare_real(); // compare args[0] & args[1]
+ int compare_decimal(); // compare args[0] & args[1]
+ int compare_int_signed(); // compare args[0] & args[1]
+ int compare_int_signed_unsigned();
+ int compare_int_unsigned_signed();
+ int compare_int_unsigned();
+ int compare_row(); // compare args[0] & args[1]
+ int compare_e_string(); // compare args[0] & args[1]
+ int compare_e_binary_string(); // compare args[0] & args[1]
+ int compare_e_real(); // compare args[0] & args[1]
+ int compare_e_decimal(); // compare args[0] & args[1]
+ int compare_e_int(); // compare args[0] & args[1]
+ int compare_e_int_diff_signedness();
+ int compare_e_row(); // compare args[0] & args[1]
+ int compare_real_fixed();
+ int compare_e_real_fixed();
+ int compare_datetime(); // compare args[0] & args[1] as DATETIMEs
+ int compare_e_datetime();
+
+ Item** cache_converted_constant(THD *thd, Item **value, Item **cache,
+ Item_result type);
+ void set_datetime_cmp_func(Item_result_field *owner_arg, Item **a1, Item **b1);
+ static arg_cmp_func comparator_matrix [6][2];
+ inline bool is_owner_equal_func()
+ {
+ return (owner->type() == Item::FUNC_ITEM &&
+ ((Item_func*)owner)->functype() == Item_func::EQUAL_FUNC);
+ }
+ void cleanup()
+ {
+ delete [] comparators;
+ comparators= 0;
+ }
+ friend class Item_func;
+};
+
+class Item_bool_func :public Item_int_func
+{
+public:
+ Item_bool_func() :Item_int_func() {}
+ Item_bool_func(Item *a) :Item_int_func(a) {}
+ Item_bool_func(Item *a,Item *b) :Item_int_func(a,b) {}
+ Item_bool_func(Item *a, Item *b, Item *c) :Item_int_func(a, b, c) {}
+ Item_bool_func(List<Item> &list) :Item_int_func(list) { }
+ Item_bool_func(THD *thd, Item_bool_func *item) :Item_int_func(thd, item) {}
+ bool is_bool_func() { return 1; }
+ void fix_length_and_dec() { decimals=0; max_length=1; }
+ uint decimal_precision() const { return 1; }
+};
+
+
+/**
+ Abstract Item class, to represent <code>X IS [NOT] (TRUE | FALSE)</code>
+ boolean predicates.
+*/
+
+class Item_func_truth : public Item_bool_func
+{
+public:
+ virtual bool val_bool();
+ virtual longlong val_int();
+ virtual void fix_length_and_dec();
+ virtual void print(String *str, enum_query_type query_type);
+
+protected:
+ Item_func_truth(Item *a, bool a_value, bool a_affirmative)
+ : Item_bool_func(a), value(a_value), affirmative(a_affirmative)
+ {}
+
+ ~Item_func_truth()
+ {}
+private:
+ /**
+ True for <code>X IS [NOT] TRUE</code>,
+ false for <code>X IS [NOT] FALSE</code> predicates.
+ */
+ const bool value;
+ /**
+ True for <code>X IS Y</code>, false for <code>X IS NOT Y</code> predicates.
+ */
+ const bool affirmative;
+};
+
+
+/**
+ This Item represents a <code>X IS TRUE</code> boolean predicate.
+*/
+
+class Item_func_istrue : public Item_func_truth
+{
+public:
+ Item_func_istrue(Item *a) : Item_func_truth(a, true, true) {}
+ ~Item_func_istrue() {}
+ virtual const char* func_name() const { return "istrue"; }
+};
+
+
+/**
+ This Item represents a <code>X IS NOT TRUE</code> boolean predicate.
+*/
+
+class Item_func_isnottrue : public Item_func_truth
+{
+public:
+ Item_func_isnottrue(Item *a) : Item_func_truth(a, true, false) {}
+ ~Item_func_isnottrue() {}
+ virtual const char* func_name() const { return "isnottrue"; }
+};
+
+
+/**
+ This Item represents a <code>X IS FALSE</code> boolean predicate.
+*/
+
+class Item_func_isfalse : public Item_func_truth
+{
+public:
+ Item_func_isfalse(Item *a) : Item_func_truth(a, false, true) {}
+ ~Item_func_isfalse() {}
+ virtual const char* func_name() const { return "isfalse"; }
+};
+
+
+/**
+ This Item represents a <code>X IS NOT FALSE</code> boolean predicate.
+*/
+
+class Item_func_isnotfalse : public Item_func_truth
+{
+public:
+ Item_func_isnotfalse(Item *a) : Item_func_truth(a, false, false) {}
+ ~Item_func_isnotfalse() {}
+ virtual const char* func_name() const { return "isnotfalse"; }
+};
+
+
+class Item_cache;
+#define UNKNOWN (-1)
+
+
+/*
+ Item_in_optimizer(left_expr, Item_in_subselect(...))
+
+ Item_in_optimizer is used to wrap an instance of Item_in_subselect. This
+ class does the following:
+ - Evaluate the left expression and store it in Item_cache_* object (to
+ avoid re-evaluating it many times during subquery execution)
+ - Shortcut the evaluation of "NULL IN (...)" to NULL in the cases where we
+ don't care if the result is NULL or FALSE.
+
+ NOTE
+ It is not quite clear why the above listed functionality should be
+ placed into a separate class called 'Item_in_optimizer'.
+*/
+
+class Item_in_optimizer: public Item_bool_func
+{
+protected:
+ Item_cache *cache;
+ Item *expr_cache;
+ bool save_cache;
+ /*
+ Stores the value of "NULL IN (SELECT ...)" for uncorrelated subqueries:
+ UNKNOWN - "NULL in (SELECT ...)" has not yet been evaluated
+ FALSE - result is FALSE
+ TRUE - result is NULL
+ */
+ int result_for_null_param;
+public:
+ Item_in_optimizer(Item *a, Item *b):
+ Item_bool_func(a, b), cache(0), expr_cache(0),
+ save_cache(0), result_for_null_param(UNKNOWN)
+ { with_subselect= true; }
+ bool fix_fields(THD *, Item **);
+ bool fix_left(THD *thd);
+ table_map not_null_tables() const { return 0; }
+ bool is_null();
+ longlong val_int();
+ void cleanup();
+ const char *func_name() const { return "<in_optimizer>"; }
+ Item_cache **get_cache() { return &cache; }
+ void keep_top_level_cache();
+ Item *transform(Item_transformer transformer, uchar *arg);
+ virtual Item *expr_cache_insert_transformer(uchar *thd_arg);
+ bool is_expensive_processor(uchar *arg);
+ bool is_expensive();
+ void set_join_tab_idx(uint join_tab_idx_arg)
+ { args[1]->set_join_tab_idx(join_tab_idx_arg); }
+ virtual void get_cache_parameters(List<Item> &parameters);
+ bool is_top_level_item();
+ bool eval_not_null_tables(uchar *opt_arg);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ bool invisible_mode();
+ void reset_cache() { cache= NULL; }
+};
+
+class Comp_creator
+{
+public:
+ Comp_creator() {} /* Remove gcc warning */
+ virtual ~Comp_creator() {} /* Remove gcc warning */
+ /**
+ Create operation with given arguments.
+ */
+ virtual Item_bool_func2* create(Item *a, Item *b) const = 0;
+ /**
+ Create operation with given arguments in swap order.
+ */
+ virtual Item_bool_func2* create_swap(Item *a, Item *b) const = 0;
+ virtual const char* symbol(bool invert) const = 0;
+ virtual bool eqne_op() const = 0;
+ virtual bool l_op() const = 0;
+};
+
+class Eq_creator :public Comp_creator
+{
+public:
+ Eq_creator() {} /* Remove gcc warning */
+ virtual ~Eq_creator() {} /* Remove gcc warning */
+ virtual Item_bool_func2* create(Item *a, Item *b) const;
+ virtual Item_bool_func2* create_swap(Item *a, Item *b) const;
+ virtual const char* symbol(bool invert) const { return invert? "<>" : "="; }
+ virtual bool eqne_op() const { return 1; }
+ virtual bool l_op() const { return 0; }
+};
+
+class Ne_creator :public Comp_creator
+{
+public:
+ Ne_creator() {} /* Remove gcc warning */
+ virtual ~Ne_creator() {} /* Remove gcc warning */
+ virtual Item_bool_func2* create(Item *a, Item *b) const;
+ virtual Item_bool_func2* create_swap(Item *a, Item *b) const;
+ virtual const char* symbol(bool invert) const { return invert? "=" : "<>"; }
+ virtual bool eqne_op() const { return 1; }
+ virtual bool l_op() const { return 0; }
+};
+
+class Gt_creator :public Comp_creator
+{
+public:
+ Gt_creator() {} /* Remove gcc warning */
+ virtual ~Gt_creator() {} /* Remove gcc warning */
+ virtual Item_bool_func2* create(Item *a, Item *b) const;
+ virtual Item_bool_func2* create_swap(Item *a, Item *b) const;
+ virtual const char* symbol(bool invert) const { return invert? "<=" : ">"; }
+ virtual bool eqne_op() const { return 0; }
+ virtual bool l_op() const { return 0; }
+};
+
+class Lt_creator :public Comp_creator
+{
+public:
+ Lt_creator() {} /* Remove gcc warning */
+ virtual ~Lt_creator() {} /* Remove gcc warning */
+ virtual Item_bool_func2* create(Item *a, Item *b) const;
+ virtual Item_bool_func2* create_swap(Item *a, Item *b) const;
+ virtual const char* symbol(bool invert) const { return invert? ">=" : "<"; }
+ virtual bool eqne_op() const { return 0; }
+ virtual bool l_op() const { return 1; }
+};
+
+class Ge_creator :public Comp_creator
+{
+public:
+ Ge_creator() {} /* Remove gcc warning */
+ virtual ~Ge_creator() {} /* Remove gcc warning */
+ virtual Item_bool_func2* create(Item *a, Item *b) const;
+ virtual Item_bool_func2* create_swap(Item *a, Item *b) const;
+ virtual const char* symbol(bool invert) const { return invert? "<" : ">="; }
+ virtual bool eqne_op() const { return 0; }
+ virtual bool l_op() const { return 0; }
+};
+
+class Le_creator :public Comp_creator
+{
+public:
+ Le_creator() {} /* Remove gcc warning */
+ virtual ~Le_creator() {} /* Remove gcc warning */
+ virtual Item_bool_func2* create(Item *a, Item *b) const;
+ virtual Item_bool_func2* create_swap(Item *a, Item *b) const;
+ virtual const char* symbol(bool invert) const { return invert? ">" : "<="; }
+ virtual bool eqne_op() const { return 0; }
+ virtual bool l_op() const { return 1; }
+};
+
+class Item_bool_func2 :public Item_bool_func
+{ /* Bool with 2 string args */
+protected:
+ Arg_comparator cmp;
+ bool abort_on_null;
+
+public:
+ Item_bool_func2(Item *a,Item *b)
+ :Item_bool_func(a,b), cmp(tmp_arg, tmp_arg+1),
+ abort_on_null(FALSE) { sargable= TRUE; }
+ void fix_length_and_dec();
+ int set_cmp_func()
+ {
+ return cmp.set_cmp_func(this, tmp_arg, tmp_arg+1, TRUE);
+ }
+ optimize_type select_optimize() const { return OPTIMIZE_OP; }
+ virtual enum Functype rev_functype() const { return UNKNOWN_FUNC; }
+ bool have_rev_func() const { return rev_functype() != UNKNOWN_FUNC; }
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ Item_func::print_op(str, query_type);
+ }
+
+ bool is_null() { return MY_TEST(args[0]->is_null() || args[1]->is_null()); }
+ CHARSET_INFO *compare_collation() { return cmp.cmp_collation.collation; }
+ void top_level_item() { abort_on_null= TRUE; }
+ Arg_comparator *get_comparator() { return &cmp; }
+ void cleanup()
+ {
+ Item_bool_func::cleanup();
+ cmp.cleanup();
+ }
+
+ friend class Arg_comparator;
+};
+
+class Item_bool_rowready_func2 :public Item_bool_func2
+{
+public:
+ Item_bool_rowready_func2(Item *a, Item *b) :Item_bool_func2(a, b)
+ {
+ allowed_arg_cols= 0; // Fetch this value from first argument
+ }
+ Item *neg_transformer(THD *thd);
+ virtual Item *negated_item();
+ bool subst_argument_checker(uchar **arg)
+ {
+ return (*arg != NULL);
+ }
+};
+
+/**
+ XOR inherits from Item_bool_func2 because it is not optimized yet.
+ Later, when XOR is optimized, it needs to inherit from
+ Item_cond instead. See WL#5800.
+*/
+class Item_func_xor :public Item_bool_func2
+{
+public:
+ Item_func_xor(Item *i1, Item *i2) :Item_bool_func2(i1, i2) {}
+ enum Functype functype() const { return XOR_FUNC; }
+ const char *func_name() const { return "xor"; }
+ longlong val_int();
+ void top_level_item() {}
+ Item *neg_transformer(THD *thd);
+ bool subst_argument_checker(uchar **arg)
+ {
+ return (*arg != NULL);
+ }
+};
+
+class Item_func_not :public Item_bool_func
+{
+ bool abort_on_null;
+public:
+ Item_func_not(Item *a) :Item_bool_func(a), abort_on_null(FALSE) {}
+ virtual void top_level_item() { abort_on_null= 1; }
+ bool is_top_level_item() { return abort_on_null; }
+ longlong val_int();
+ enum Functype functype() const { return NOT_FUNC; }
+ const char *func_name() const { return "not"; }
+ Item *neg_transformer(THD *thd);
+ bool fix_fields(THD *, Item **);
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+class Item_maxmin_subselect;
+
+/*
+ trigcond<param>(arg) ::= param? arg : TRUE
+
+ The class Item_func_trig_cond is used for guarded predicates
+ which are employed only for internal purposes.
+ A guarded predicate is an object consisting of an a regular or
+ a guarded predicate P and a pointer to a boolean guard variable g.
+ A guarded predicate P/g is evaluated to true if the value of the
+ guard g is false, otherwise it is evaluated to the same value that
+ the predicate P: val(P/g)= g ? val(P):true.
+ Guarded predicates allow us to include predicates into a conjunction
+ conditionally. Currently they are utilized for pushed down predicates
+ in queries with outer join operations.
+
+ In the future, probably, it makes sense to extend this class to
+ the objects consisting of three elements: a predicate P, a pointer
+ to a variable g and a firing value s with following evaluation
+ rule: val(P/g,s)= g==s? val(P) : true. It will allow us to build only
+ one item for the objects of the form P/g1/g2...
+
+ Objects of this class are built only for query execution after
+ the execution plan has been already selected. That's why this
+ class needs only val_int out of generic methods.
+
+ Current uses of Item_func_trig_cond objects:
+ - To wrap selection conditions when executing outer joins
+ - To wrap condition that is pushed down into subquery
+*/
+
+class Item_func_trig_cond: public Item_bool_func
+{
+ bool *trig_var;
+public:
+ Item_func_trig_cond(Item *a, bool *f) : Item_bool_func(a) { trig_var= f; }
+ longlong val_int() { return *trig_var ? args[0]->val_int() : 1; }
+ enum Functype functype() const { return TRIG_COND_FUNC; };
+ const char *func_name() const { return "trigcond"; };
+ bool const_item() const { return FALSE; }
+ bool *get_trig_var() { return trig_var; }
+};
+
+class Item_func_not_all :public Item_func_not
+{
+ /* allow to check presence of values in max/min optimization */
+ Item_sum_hybrid *test_sum_item;
+ Item_maxmin_subselect *test_sub_item;
+
+public:
+ bool show;
+
+ Item_func_not_all(Item *a)
+ :Item_func_not(a), test_sum_item(0), test_sub_item(0),
+ show(0)
+ {}
+ table_map not_null_tables() const { return 0; }
+ longlong val_int();
+ enum Functype functype() const { return NOT_ALL_FUNC; }
+ const char *func_name() const { return "<not>"; }
+ bool fix_fields(THD *thd, Item **ref)
+ {return Item_func::fix_fields(thd, ref);}
+ virtual void print(String *str, enum_query_type query_type);
+ void set_sum_test(Item_sum_hybrid *item) { test_sum_item= item; test_sub_item= 0; };
+ void set_sub_test(Item_maxmin_subselect *item) { test_sub_item= item; test_sum_item= 0;};
+ bool empty_underlying_subquery();
+ Item *neg_transformer(THD *thd);
+};
+
+
+class Item_func_nop_all :public Item_func_not_all
+{
+public:
+
+ Item_func_nop_all(Item *a) :Item_func_not_all(a) {}
+ longlong val_int();
+ const char *func_name() const { return "<nop>"; }
+ Item *neg_transformer(THD *thd);
+};
+
+
+class Item_func_eq :public Item_bool_rowready_func2
+{
+public:
+ Item_func_eq(Item *a,Item *b) :
+ Item_bool_rowready_func2(a,b), in_equality_no(UINT_MAX)
+ {}
+ longlong val_int();
+ enum Functype functype() const { return EQ_FUNC; }
+ enum Functype rev_functype() const { return EQ_FUNC; }
+ cond_result eq_cmp_result() const { return COND_TRUE; }
+ const char *func_name() const { return "="; }
+ Item *negated_item();
+ /*
+ - If this equality is created from the subquery's IN-equality:
+ number of the item it was created from, e.g. for
+ (a,b) IN (SELECT c,d ...) a=c will have in_equality_no=0,
+ and b=d will have in_equality_no=1.
+ - Otherwise, UINT_MAX
+ */
+ uint in_equality_no;
+ virtual uint exists2in_reserved_items() { return 1; };
+};
+
+class Item_func_equal :public Item_bool_rowready_func2
+{
+public:
+ Item_func_equal(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {};
+ longlong val_int();
+ void fix_length_and_dec();
+ table_map not_null_tables() const { return 0; }
+ enum Functype functype() const { return EQUAL_FUNC; }
+ enum Functype rev_functype() const { return EQUAL_FUNC; }
+ cond_result eq_cmp_result() const { return COND_TRUE; }
+ const char *func_name() const { return "<=>"; }
+ Item *neg_transformer(THD *thd) { return 0; }
+};
+
+
+class Item_func_ge :public Item_bool_rowready_func2
+{
+public:
+ Item_func_ge(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {};
+ longlong val_int();
+ enum Functype functype() const { return GE_FUNC; }
+ enum Functype rev_functype() const { return LE_FUNC; }
+ cond_result eq_cmp_result() const { return COND_TRUE; }
+ const char *func_name() const { return ">="; }
+ Item *negated_item();
+};
+
+
+class Item_func_gt :public Item_bool_rowready_func2
+{
+public:
+ Item_func_gt(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {};
+ longlong val_int();
+ enum Functype functype() const { return GT_FUNC; }
+ enum Functype rev_functype() const { return LT_FUNC; }
+ cond_result eq_cmp_result() const { return COND_FALSE; }
+ const char *func_name() const { return ">"; }
+ Item *negated_item();
+};
+
+
+class Item_func_le :public Item_bool_rowready_func2
+{
+public:
+ Item_func_le(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {};
+ longlong val_int();
+ enum Functype functype() const { return LE_FUNC; }
+ enum Functype rev_functype() const { return GE_FUNC; }
+ cond_result eq_cmp_result() const { return COND_TRUE; }
+ const char *func_name() const { return "<="; }
+ Item *negated_item();
+};
+
+
+class Item_func_lt :public Item_bool_rowready_func2
+{
+public:
+ Item_func_lt(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {}
+ longlong val_int();
+ enum Functype functype() const { return LT_FUNC; }
+ enum Functype rev_functype() const { return GT_FUNC; }
+ cond_result eq_cmp_result() const { return COND_FALSE; }
+ const char *func_name() const { return "<"; }
+ Item *negated_item();
+};
+
+
+class Item_func_ne :public Item_bool_rowready_func2
+{
+public:
+ Item_func_ne(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {}
+ longlong val_int();
+ enum Functype functype() const { return NE_FUNC; }
+ cond_result eq_cmp_result() const { return COND_FALSE; }
+ optimize_type select_optimize() const { return OPTIMIZE_KEY; }
+ const char *func_name() const { return "<>"; }
+ Item *negated_item();
+};
+
+
+/*
+ The class Item_func_opt_neg is defined to factor out the functionality
+ common for the classes Item_func_between and Item_func_in. The objects
+ of these classes can express predicates or there negations.
+ The alternative approach would be to create pairs Item_func_between,
+ Item_func_notbetween and Item_func_in, Item_func_notin.
+
+*/
+
+class Item_func_opt_neg :public Item_bool_func
+{
+public:
+ bool negated; /* <=> the item represents NOT <func> */
+ bool pred_level; /* <=> [NOT] <func> is used on a predicate level */
+public:
+ Item_func_opt_neg(Item *a, Item *b, Item *c)
+ :Item_bool_func(a, b, c), negated(0), pred_level(0) {}
+ Item_func_opt_neg(List<Item> &list)
+ :Item_bool_func(list), negated(0), pred_level(0) {}
+public:
+ inline void negate() { negated= !negated; }
+ inline void top_level_item() { pred_level= 1; }
+ Item *neg_transformer(THD *thd)
+ {
+ negated= !negated;
+ return this;
+ }
+ bool eq(const Item *item, bool binary_cmp) const;
+ bool subst_argument_checker(uchar **arg) { return TRUE; }
+};
+
+
+class Item_func_between :public Item_func_opt_neg
+{
+ DTCollation cmp_collation;
+public:
+ Item_result cmp_type;
+ String value0,value1,value2;
+ /* TRUE <=> arguments will be compared as dates. */
+ Item *compare_as_dates;
+ Item_func_between(Item *a, Item *b, Item *c)
+ :Item_func_opt_neg(a, b, c), compare_as_dates(FALSE) { sargable= TRUE; }
+ longlong val_int();
+ optimize_type select_optimize() const { return OPTIMIZE_KEY; }
+ enum Functype functype() const { return BETWEEN; }
+ const char *func_name() const { return "between"; }
+ bool fix_fields(THD *, Item **);
+ void fix_length_and_dec();
+ virtual void print(String *str, enum_query_type query_type);
+ CHARSET_INFO *compare_collation() { return cmp_collation.collation; }
+ bool eval_not_null_tables(uchar *opt_arg);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ bool count_sargable_conds(uchar *arg);
+};
+
+
+class Item_func_strcmp :public Item_int_func
+{
+ String value1, value2;
+ DTCollation cmp_collation;
+public:
+ Item_func_strcmp(Item *a,Item *b) :Item_int_func(a,b) {}
+ longlong val_int();
+ uint decimal_precision() const { return 1; }
+ const char *func_name() const { return "strcmp"; }
+ void fix_length_and_dec()
+ {
+ agg_arg_charsets_for_comparison(cmp_collation, args, 2);
+ fix_char_length(2); // returns "1" or "0" or "-1"
+ }
+};
+
+
+struct interval_range
+{
+ Item_result type;
+ double dbl;
+ my_decimal dec;
+};
+
+class Item_func_interval :public Item_int_func
+{
+ Item_row *row;
+ bool use_decimal_comparison;
+ interval_range *intervals;
+public:
+ Item_func_interval(Item_row *a)
+ :Item_int_func(a),row(a),intervals(0)
+ {
+ allowed_arg_cols= 0; // Fetch this value from first argument
+ }
+ longlong val_int();
+ void fix_length_and_dec();
+ const char *func_name() const { return "interval"; }
+ uint decimal_precision() const { return 2; }
+ void print(String *str, enum_query_type query_type)
+ {
+ str->append(func_name());
+ print_args(str, 0, query_type);
+ }
+};
+
+
+class Item_func_coalesce :public Item_func_hybrid_field_type
+{
+public:
+ Item_func_coalesce(Item *a, Item *b) :Item_func_hybrid_field_type(a, b) {}
+ Item_func_coalesce(List<Item> &list) :Item_func_hybrid_field_type(list) {}
+ double real_op();
+ longlong int_op();
+ String *str_op(String *);
+ my_decimal *decimal_op(my_decimal *);
+ bool date_op(MYSQL_TIME *ltime,uint fuzzydate);
+ void fix_length_and_dec();
+ const char *func_name() const { return "coalesce"; }
+ table_map not_null_tables() const { return 0; }
+};
+
+
+class Item_func_ifnull :public Item_func_coalesce
+{
+protected:
+ bool field_type_defined;
+public:
+ Item_func_ifnull(Item *a, Item *b) :Item_func_coalesce(a,b) {}
+ double real_op();
+ longlong int_op();
+ String *str_op(String *str);
+ my_decimal *decimal_op(my_decimal *);
+ bool date_op(MYSQL_TIME *ltime,uint fuzzydate);
+ void fix_length_and_dec();
+ const char *func_name() const { return "ifnull"; }
+ Field *tmp_table_field(TABLE *table);
+ uint decimal_precision() const;
+};
+
+
+class Item_func_if :public Item_func_hybrid_field_type
+{
+public:
+ Item_func_if(Item *a,Item *b,Item *c)
+ :Item_func_hybrid_field_type(a,b,c)
+ {}
+ bool date_op(MYSQL_TIME *ltime, uint fuzzydate);
+ longlong int_op();
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+ String *str_op(String *);
+ bool fix_fields(THD *, Item **);
+ void fix_length_and_dec();
+ uint decimal_precision() const;
+ const char *func_name() const { return "if"; }
+ bool eval_not_null_tables(uchar *opt_arg);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+private:
+ void cache_type_info(Item *source);
+};
+
+
+class Item_func_nullif :public Item_bool_func2
+{
+ enum Item_result cached_result_type;
+public:
+ Item_func_nullif(Item *a,Item *b)
+ :Item_bool_func2(a,b), cached_result_type(INT_RESULT)
+ {}
+ bool is_bool_func() { return false; }
+ double val_real();
+ longlong val_int();
+ String *val_str(String *str);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type () const { return cached_result_type; }
+ void fix_length_and_dec();
+ uint decimal_precision() const { return args[0]->decimal_precision(); }
+ const char *func_name() const { return "nullif"; }
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ Item_func::print(str, query_type);
+ }
+
+ table_map not_null_tables() const { return 0; }
+ bool is_null();
+};
+
+
+/* Functions to handle the optimized IN */
+
+
+/* A vector of values of some type */
+
+class in_vector :public Sql_alloc
+{
+public:
+ char *base;
+ uint size;
+ qsort2_cmp compare;
+ CHARSET_INFO *collation;
+ uint count;
+ uint used_count;
+ in_vector() {}
+ in_vector(uint elements,uint element_length,qsort2_cmp cmp_func,
+ CHARSET_INFO *cmp_coll)
+ :base((char*) sql_calloc(elements*element_length)),
+ size(element_length), compare(cmp_func), collation(cmp_coll),
+ count(elements), used_count(elements) {}
+ virtual ~in_vector() {}
+ virtual void set(uint pos,Item *item)=0;
+ virtual uchar *get_value(Item *item)=0;
+ void sort()
+ {
+ my_qsort2(base,used_count,size,compare,(void*)collation);
+ }
+ int find(Item *item);
+
+ /*
+ Create an instance of Item_{type} (e.g. Item_decimal) constant object
+ which type allows it to hold an element of this vector without any
+ conversions.
+ The purpose of this function is to be able to get elements of this
+ vector in form of Item_xxx constants without creating Item_xxx object
+ for every array element you get (i.e. this implements "FlyWeight" pattern)
+ */
+ virtual Item* create_item() { return NULL; }
+
+ /*
+ Store the value at position #pos into provided item object
+ SYNOPSIS
+ value_to_item()
+ pos Index of value to store
+ item Constant item to store value into. The item must be of the same
+ type that create_item() returns.
+ */
+ virtual void value_to_item(uint pos, Item *item) { }
+
+ /* Compare values number pos1 and pos2 for equality */
+ bool compare_elems(uint pos1, uint pos2)
+ {
+ return MY_TEST(compare(collation, base + pos1 * size, base + pos2 * size));
+ }
+ virtual Item_result result_type()= 0;
+};
+
+class in_string :public in_vector
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp;
+ class Item_string_for_in_vector: public Item_string
+ {
+ public:
+ Item_string_for_in_vector(CHARSET_INFO *cs):
+ Item_string(cs)
+ { }
+ void set_value(const String *str)
+ {
+ str_value= *str;
+ collation.set(str->charset());
+ }
+ };
+public:
+ in_string(uint elements,qsort2_cmp cmp_func, CHARSET_INFO *cs);
+ ~in_string();
+ void set(uint pos,Item *item);
+ uchar *get_value(Item *item);
+ Item* create_item()
+ {
+ return new Item_string_for_in_vector(collation);
+ }
+ void value_to_item(uint pos, Item *item)
+ {
+ String *str=((String*) base)+pos;
+ Item_string_for_in_vector *to= (Item_string_for_in_vector*) item;
+ to->set_value(str);
+ }
+ Item_result result_type() { return STRING_RESULT; }
+};
+
+class in_longlong :public in_vector
+{
+protected:
+ /*
+ Here we declare a temporary variable (tmp) of the same type as the
+ elements of this vector. tmp is used in finding if a given value is in
+ the list.
+ */
+ struct packed_longlong
+ {
+ longlong val;
+ longlong unsigned_flag; // Use longlong, not bool, to preserve alignment
+ } tmp;
+public:
+ in_longlong(uint elements);
+ void set(uint pos,Item *item);
+ uchar *get_value(Item *item);
+
+ Item* create_item()
+ {
+ /*
+ We're created a signed INT, this may not be correct in
+ general case (see BUG#19342).
+ */
+ return new Item_int((longlong)0);
+ }
+ void value_to_item(uint pos, Item *item)
+ {
+ ((Item_int*) item)->value= ((packed_longlong*) base)[pos].val;
+ ((Item_int*) item)->unsigned_flag= (bool)
+ ((packed_longlong*) base)[pos].unsigned_flag;
+ }
+ Item_result result_type() { return INT_RESULT; }
+
+ friend int cmp_longlong(void *cmp_arg, packed_longlong *a,packed_longlong *b);
+};
+
+
+/*
+ Class to represent a vector of constant DATE/DATETIME values.
+ Values are obtained with help of the get_datetime_value() function.
+ If the left item is a constant one then its value is cached in the
+ lval_cache variable.
+*/
+class in_datetime :public in_longlong
+{
+public:
+ THD *thd;
+ /* An item used to issue warnings. */
+ Item *warn_item;
+ /* Cache for the left item. */
+ Item *lval_cache;
+
+ in_datetime(Item *warn_item_arg, uint elements)
+ :in_longlong(elements), thd(current_thd), warn_item(warn_item_arg),
+ lval_cache(0) {};
+ void set(uint pos,Item *item);
+ uchar *get_value(Item *item);
+ Item* create_item()
+ {
+ return new Item_datetime();
+ }
+ void value_to_item(uint pos, Item *item)
+ {
+ packed_longlong *val= reinterpret_cast<packed_longlong*>(base)+pos;
+ Item_datetime *dt= reinterpret_cast<Item_datetime*>(item);
+ dt->set(val->val);
+ }
+ friend int cmp_longlong(void *cmp_arg, packed_longlong *a,packed_longlong *b);
+};
+
+
+class in_double :public in_vector
+{
+ double tmp;
+public:
+ in_double(uint elements);
+ void set(uint pos,Item *item);
+ uchar *get_value(Item *item);
+ Item *create_item()
+ {
+ return new Item_float(0.0, 0);
+ }
+ void value_to_item(uint pos, Item *item)
+ {
+ ((Item_float*)item)->value= ((double*) base)[pos];
+ }
+ Item_result result_type() { return REAL_RESULT; }
+};
+
+
+class in_decimal :public in_vector
+{
+ my_decimal val;
+public:
+ in_decimal(uint elements);
+ void set(uint pos, Item *item);
+ uchar *get_value(Item *item);
+ Item *create_item()
+ {
+ return new Item_decimal(0, FALSE);
+ }
+ void value_to_item(uint pos, Item *item)
+ {
+ my_decimal *dec= ((my_decimal *)base) + pos;
+ Item_decimal *item_dec= (Item_decimal*)item;
+ item_dec->set_decimal_value(dec);
+ }
+ Item_result result_type() { return DECIMAL_RESULT; }
+
+};
+
+
+/*
+** Classes for easy comparing of non const items
+*/
+
+class cmp_item :public Sql_alloc
+{
+public:
+ CHARSET_INFO *cmp_charset;
+ cmp_item() { cmp_charset= &my_charset_bin; }
+ virtual ~cmp_item() {}
+ virtual void store_value(Item *item)= 0;
+ virtual int cmp(Item *item)= 0;
+ // for optimized IN with row
+ virtual int compare(cmp_item *item)= 0;
+ static cmp_item* get_comparator(Item_result type, Item * warn_item,
+ CHARSET_INFO *cs);
+ virtual cmp_item *make_same()= 0;
+ virtual void store_value_by_template(cmp_item *tmpl, Item *item)
+ {
+ store_value(item);
+ }
+};
+
+class cmp_item_string :public cmp_item
+{
+protected:
+ String *value_res;
+public:
+ cmp_item_string () {}
+ cmp_item_string (CHARSET_INFO *cs) { cmp_charset= cs; }
+ void set_charset(CHARSET_INFO *cs) { cmp_charset= cs; }
+ friend class cmp_item_sort_string;
+ friend class cmp_item_sort_string_in_static;
+};
+
+class cmp_item_sort_string :public cmp_item_string
+{
+protected:
+ char value_buff[STRING_BUFFER_USUAL_SIZE];
+ String value;
+public:
+ cmp_item_sort_string():
+ cmp_item_string() {}
+ cmp_item_sort_string(CHARSET_INFO *cs):
+ cmp_item_string(cs),
+ value(value_buff, sizeof(value_buff), cs) {}
+ void store_value(Item *item)
+ {
+ value_res= item->val_str(&value);
+ }
+ int cmp(Item *arg)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), cmp_charset), *res;
+ res= arg->val_str(&tmp);
+ return (value_res ? (res ? sortcmp(value_res, res, cmp_charset) : 1) :
+ (res ? -1 : 0));
+ }
+ int compare(cmp_item *ci)
+ {
+ cmp_item_string *l_cmp= (cmp_item_string *) ci;
+ return sortcmp(value_res, l_cmp->value_res, cmp_charset);
+ }
+ cmp_item *make_same();
+ void set_charset(CHARSET_INFO *cs)
+ {
+ cmp_charset= cs;
+ value.set_quick(value_buff, sizeof(value_buff), cs);
+ }
+};
+
+class cmp_item_int :public cmp_item
+{
+ longlong value;
+public:
+ cmp_item_int() {} /* Remove gcc warning */
+ void store_value(Item *item)
+ {
+ value= item->val_int();
+ }
+ int cmp(Item *arg)
+ {
+ return value != arg->val_int();
+ }
+ int compare(cmp_item *ci)
+ {
+ cmp_item_int *l_cmp= (cmp_item_int *)ci;
+ return (value < l_cmp->value) ? -1 : ((value == l_cmp->value) ? 0 : 1);
+ }
+ cmp_item *make_same();
+};
+
+/*
+ Compare items in the DATETIME context.
+ Values are obtained with help of the get_datetime_value() function.
+ If the left item is a constant one then its value is cached in the
+ lval_cache variable.
+*/
+class cmp_item_datetime :public cmp_item
+{
+ longlong value;
+public:
+ THD *thd;
+ /* Item used for issuing warnings. */
+ Item *warn_item;
+ /* Cache for the left item. */
+ Item *lval_cache;
+
+ cmp_item_datetime(Item *warn_item_arg)
+ :thd(current_thd), warn_item(warn_item_arg), lval_cache(0) {}
+ void store_value(Item *item);
+ int cmp(Item *arg);
+ int compare(cmp_item *ci);
+ cmp_item *make_same();
+};
+
+class cmp_item_real :public cmp_item
+{
+ double value;
+public:
+ cmp_item_real() {} /* Remove gcc warning */
+ void store_value(Item *item)
+ {
+ value= item->val_real();
+ }
+ int cmp(Item *arg)
+ {
+ return value != arg->val_real();
+ }
+ int compare(cmp_item *ci)
+ {
+ cmp_item_real *l_cmp= (cmp_item_real *) ci;
+ return (value < l_cmp->value)? -1 : ((value == l_cmp->value) ? 0 : 1);
+ }
+ cmp_item *make_same();
+};
+
+
+class cmp_item_decimal :public cmp_item
+{
+ my_decimal value;
+public:
+ cmp_item_decimal() {} /* Remove gcc warning */
+ void store_value(Item *item);
+ int cmp(Item *arg);
+ int compare(cmp_item *c);
+ cmp_item *make_same();
+};
+
+
+/*
+ cmp_item for optimized IN with row (right part string, which never
+ be changed)
+*/
+
+class cmp_item_sort_string_in_static :public cmp_item_string
+{
+ protected:
+ String value;
+public:
+ cmp_item_sort_string_in_static(CHARSET_INFO *cs):
+ cmp_item_string(cs) {}
+ void store_value(Item *item)
+ {
+ value_res= item->val_str(&value);
+ }
+ int cmp(Item *item)
+ {
+ // Should never be called
+ DBUG_ASSERT(0);
+ return 1;
+ }
+ int compare(cmp_item *ci)
+ {
+ cmp_item_string *l_cmp= (cmp_item_string *) ci;
+ return sortcmp(value_res, l_cmp->value_res, cmp_charset);
+ }
+ cmp_item *make_same()
+ {
+ return new cmp_item_sort_string_in_static(cmp_charset);
+ }
+};
+
+
+/*
+ The class Item_func_case is the CASE ... WHEN ... THEN ... END function
+ implementation.
+
+ When there is no expression between CASE and the first WHEN
+ (the CASE expression) then this function simple checks all WHEN expressions
+ one after another. When some WHEN expression evaluated to TRUE then the
+ value of the corresponding THEN expression is returned.
+
+ When the CASE expression is specified then it is compared to each WHEN
+ expression individually. When an equal WHEN expression is found
+ corresponding THEN expression is returned.
+ In order to do correct comparisons several comparators are used. One for
+ each result type. Different result types that are used in particular
+ CASE ... END expression are collected in the fix_length_and_dec() member
+ function and only comparators for there result types are used.
+*/
+
+class Item_func_case :public Item_func_hybrid_field_type
+{
+ int first_expr_num, else_expr_num;
+ enum Item_result left_result_type;
+ String tmp_value;
+ uint ncases;
+ Item_result cmp_type;
+ DTCollation cmp_collation;
+ cmp_item *cmp_items[6]; /* For all result types */
+ cmp_item *case_item;
+public:
+ Item_func_case(List<Item> &list, Item *first_expr_arg, Item *else_expr_arg)
+ :Item_func_hybrid_field_type(), first_expr_num(-1), else_expr_num(-1),
+ left_result_type(INT_RESULT), case_item(0)
+ {
+ ncases= list.elements;
+ if (first_expr_arg)
+ {
+ first_expr_num= list.elements;
+ list.push_back(first_expr_arg);
+ }
+ if (else_expr_arg)
+ {
+ else_expr_num= list.elements;
+ list.push_back(else_expr_arg);
+ }
+ set_arguments(list);
+ bzero(&cmp_items, sizeof(cmp_items));
+ }
+ double real_op();
+ longlong int_op();
+ String *str_op(String *);
+ my_decimal *decimal_op(my_decimal *);
+ bool date_op(MYSQL_TIME *ltime, uint fuzzydate);
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec();
+ uint decimal_precision() const;
+ table_map not_null_tables() const { return 0; }
+ const char *func_name() const { return "case"; }
+ virtual void print(String *str, enum_query_type query_type);
+ Item *find_item(String *str);
+ CHARSET_INFO *compare_collation() { return cmp_collation.collation; }
+ void cleanup();
+ void agg_str_lengths(Item *arg);
+ void agg_num_lengths(Item *arg);
+};
+
+/*
+ The Item_func_in class implements the in_expr IN(values_list) function.
+
+ The current implementation distinguishes 2 cases:
+ 1) all items in the value_list are constants and have the same
+ result type. This case is handled by in_vector class.
+ 2) items in the value_list have different result types or there is some
+ non-constant items.
+ In this case Item_func_in employs several cmp_item objects to performs
+ comparisons of in_expr and an item from the values_list. One cmp_item
+ object for each result type. Different result types are collected in the
+ fix_length_and_dec() member function by means of collect_cmp_types()
+ function.
+*/
+class Item_func_in :public Item_func_opt_neg
+{
+public:
+ /*
+ an array of values when the right hand arguments of IN
+ are all SQL constant and there are no nulls
+ */
+ in_vector *array;
+ bool have_null;
+ /*
+ true when all arguments of the IN clause are of compatible types
+ and can be used safely as comparisons for key conditions
+ */
+ bool arg_types_compatible;
+ Item_result left_result_type;
+ cmp_item *cmp_items[6]; /* One cmp_item for each result type */
+ DTCollation cmp_collation;
+
+ Item_func_in(List<Item> &list)
+ :Item_func_opt_neg(list), array(0), have_null(0),
+ arg_types_compatible(FALSE)
+ {
+ bzero(&cmp_items, sizeof(cmp_items));
+ allowed_arg_cols= 0; // Fetch this value from first argument
+ sargable= TRUE;
+ }
+ longlong val_int();
+ bool fix_fields(THD *, Item **);
+ void fix_length_and_dec();
+ void cleanup()
+ {
+ uint i;
+ DBUG_ENTER("Item_func_in::cleanup");
+ Item_int_func::cleanup();
+ delete array;
+ array= 0;
+ for (i= 0; i <= (uint)TIME_RESULT; i++)
+ {
+ delete cmp_items[i];
+ cmp_items[i]= 0;
+ }
+ DBUG_VOID_RETURN;
+ }
+ optimize_type select_optimize() const
+ { return OPTIMIZE_KEY; }
+ virtual void print(String *str, enum_query_type query_type);
+ enum Functype functype() const { return IN_FUNC; }
+ const char *func_name() const { return " IN "; }
+ bool nulls_in_row();
+ CHARSET_INFO *compare_collation() { return cmp_collation.collation; }
+ bool eval_not_null_tables(uchar *opt_arg);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+};
+
+class cmp_item_row :public cmp_item
+{
+ cmp_item **comparators;
+ uint n;
+public:
+ cmp_item_row(): comparators(0), n(0) {}
+ ~cmp_item_row();
+ void store_value(Item *item);
+ inline void alloc_comparators();
+ int cmp(Item *arg);
+ int compare(cmp_item *arg);
+ cmp_item *make_same();
+ void store_value_by_template(cmp_item *tmpl, Item *);
+ friend void Item_func_in::fix_length_and_dec();
+};
+
+
+class in_row :public in_vector
+{
+ cmp_item_row tmp;
+public:
+ in_row(uint elements, Item *);
+ ~in_row();
+ void set(uint pos,Item *item);
+ uchar *get_value(Item *item);
+ friend void Item_func_in::fix_length_and_dec();
+ Item_result result_type() { return ROW_RESULT; }
+};
+
+/* Functions used by where clause */
+
+class Item_func_isnull :public Item_bool_func
+{
+public:
+ Item_func_isnull(Item *a) :Item_bool_func(a) { sargable= TRUE; }
+ longlong val_int();
+ enum Functype functype() const { return ISNULL_FUNC; }
+ void fix_length_and_dec()
+ {
+ decimals=0; max_length=1; maybe_null=0;
+ update_used_tables();
+ }
+ const char *func_name() const { return "isnull"; }
+ /* Optimize case of not_null_column IS NULL */
+ virtual void update_used_tables()
+ {
+ if (!args[0]->maybe_null)
+ {
+ used_tables_cache= 0; /* is always false */
+ const_item_cache= 1;
+ }
+ else
+ {
+ args[0]->update_used_tables();
+ used_tables_cache= args[0]->used_tables();
+ const_item_cache= args[0]->const_item();
+ }
+ }
+ table_map not_null_tables() const { return 0; }
+ optimize_type select_optimize() const { return OPTIMIZE_NULL; }
+ Item *neg_transformer(THD *thd);
+ CHARSET_INFO *compare_collation() { return args[0]->collation.collation; }
+};
+
+/* Functions used by HAVING for rewriting IN subquery */
+
+class Item_in_subselect;
+
+/*
+ This is like IS NOT NULL but it also remembers if it ever has
+ encountered a NULL.
+*/
+class Item_is_not_null_test :public Item_func_isnull
+{
+ Item_in_subselect* owner;
+public:
+ Item_is_not_null_test(Item_in_subselect* ow, Item *a)
+ :Item_func_isnull(a), owner(ow)
+ {}
+ enum Functype functype() const { return ISNOTNULLTEST_FUNC; }
+ longlong val_int();
+ const char *func_name() const { return "<is_not_null_test>"; }
+ void update_used_tables();
+ /*
+ we add RAND_TABLE_BIT to prevent moving this item from HAVING to WHERE
+ */
+ table_map used_tables() const
+ { return used_tables_cache | RAND_TABLE_BIT; }
+ bool const_item() const { return FALSE; }
+};
+
+
+class Item_func_isnotnull :public Item_bool_func
+{
+ bool abort_on_null;
+public:
+ Item_func_isnotnull(Item *a) :Item_bool_func(a), abort_on_null(0)
+ { sargable= TRUE; }
+ longlong val_int();
+ enum Functype functype() const { return ISNOTNULL_FUNC; }
+ void fix_length_and_dec()
+ {
+ decimals=0; max_length=1; maybe_null=0;
+ }
+ const char *func_name() const { return "isnotnull"; }
+ optimize_type select_optimize() const { return OPTIMIZE_NULL; }
+ table_map not_null_tables() const
+ { return abort_on_null ? not_null_tables_cache : 0; }
+ Item *neg_transformer(THD *thd);
+ virtual void print(String *str, enum_query_type query_type);
+ CHARSET_INFO *compare_collation() { return args[0]->collation.collation; }
+ void top_level_item() { abort_on_null=1; }
+};
+
+
+class Item_func_like :public Item_bool_func2
+{
+ // Turbo Boyer-Moore data
+ bool canDoTurboBM; // pattern is '%abcd%' case
+ const char* pattern;
+ int pattern_len;
+
+ // TurboBM buffers, *this is owner
+ int* bmGs; // good suffix shift table, size is pattern_len + 1
+ int* bmBc; // bad character shift table, size is alphabet_size
+
+ void turboBM_compute_suffixes(int* suff);
+ void turboBM_compute_good_suffix_shifts(int* suff);
+ void turboBM_compute_bad_character_shifts();
+ bool turboBM_matches(const char* text, int text_len) const;
+ enum { alphabet_size = 256 };
+
+ Item *escape_item;
+
+ bool escape_used_in_parsing;
+ bool use_sampling;
+
+public:
+ int escape;
+
+ Item_func_like(Item *a,Item *b, Item *escape_arg, bool escape_used)
+ :Item_bool_func2(a,b), canDoTurboBM(FALSE), pattern(0), pattern_len(0),
+ bmGs(0), bmBc(0), escape_item(escape_arg),
+ escape_used_in_parsing(escape_used), use_sampling(0) {}
+ longlong val_int();
+ enum Functype functype() const { return LIKE_FUNC; }
+ optimize_type select_optimize() const;
+ cond_result eq_cmp_result() const
+ {
+ /**
+ We cannot always rewrite conditions as follows:
+ from: WHERE expr1=const AND expr1 LIKE expr2
+ to: WHERE expr1=const AND const LIKE expr2
+ or
+ from: WHERE expr1=const AND expr2 LIKE expr1
+ to: WHERE expr1=const AND expr2 LIKE const
+
+ because LIKE works differently comparing to the regular "=" operator:
+
+ 1. LIKE performs a stricter one-character-to-one-character comparison
+ and does not recognize contractions and expansions.
+ Replacing "expr1" to "const in LIKE would make the condition
+ stricter in case of a complex collation.
+
+ 2. LIKE does not ignore trailing spaces and thus works differently
+ from the "=" operator in case of "PAD SPACE" collations
+ (which are the majority in MariaDB). So, for "PAD SPACE" collations:
+
+ - expr1=const - ignores trailing spaces
+ - const LIKE expr2 - does not ignore trailing spaces
+ - expr2 LIKE const - does not ignore trailing spaces
+
+ Allow only "binary" for now.
+ It neither ignores trailing spaces nor has contractions/expansions.
+
+ TODO:
+ We could still replace "expr1" to "const" in "expr1 LIKE expr2"
+ in case of a "PAD SPACE" collation, but only if "expr2" has '%'
+ at the end.
+ */
+ return ((Item_func_like *)this)->compare_collation() == &my_charset_bin ?
+ COND_TRUE : COND_OK;
+ }
+ const char *func_name() const { return "like"; }
+ bool fix_fields(THD *thd, Item **ref);
+ void cleanup();
+
+ bool find_selective_predicates_list_processor(uchar *arg);
+};
+
+
+class Regexp_processor_pcre
+{
+ pcre *m_pcre;
+ bool m_conversion_is_needed;
+ bool m_is_const;
+ int m_library_flags;
+ CHARSET_INFO *m_data_charset;
+ CHARSET_INFO *m_library_charset;
+ String m_prev_pattern;
+ int m_pcre_exec_rc;
+ int m_SubStrVec[30];
+ uint m_subpatterns_needed;
+public:
+ String *convert_if_needed(String *src, String *converter);
+ String subject_converter;
+ String pattern_converter;
+ String replace_converter;
+ Regexp_processor_pcre() :
+ m_pcre(NULL), m_conversion_is_needed(true), m_is_const(0),
+ m_library_flags(0),
+ m_data_charset(&my_charset_utf8_general_ci),
+ m_library_charset(&my_charset_utf8_general_ci),
+ m_subpatterns_needed(0)
+ {}
+ int default_regex_flags();
+ void init(CHARSET_INFO *data_charset, int extra_flags, uint nsubpatterns)
+ {
+ m_library_flags= default_regex_flags() | extra_flags |
+ (data_charset != &my_charset_bin ?
+ (PCRE_UTF8 | PCRE_UCP) : 0) |
+ ((data_charset->state &
+ (MY_CS_BINSORT | MY_CS_CSSORT)) ? 0 : PCRE_CASELESS);
+
+ // Convert text data to utf-8.
+ m_library_charset= data_charset == &my_charset_bin ?
+ &my_charset_bin : &my_charset_utf8_general_ci;
+
+ m_conversion_is_needed= (data_charset != &my_charset_bin) &&
+ !my_charset_same(data_charset, m_library_charset);
+ m_subpatterns_needed= nsubpatterns;
+ }
+ void fix_owner(Item_func *owner, Item *subject_arg, Item *pattern_arg);
+ bool compile(String *pattern, bool send_error);
+ bool compile(Item *item, bool send_error);
+ bool recompile(Item *item)
+ {
+ return !m_is_const && compile(item, false);
+ }
+ bool exec(const char *str, int length, int offset);
+ bool exec(String *str, int offset, uint n_result_offsets_to_convert);
+ bool exec(Item *item, int offset, uint n_result_offsets_to_convert);
+ bool match() const { return m_pcre_exec_rc < 0 ? 0 : 1; }
+ int nsubpatterns() const { return m_pcre_exec_rc <= 0 ? 0 : m_pcre_exec_rc; }
+ int subpattern_start(int n) const
+ {
+ return m_pcre_exec_rc <= 0 ? 0 : m_SubStrVec[n * 2];
+ }
+ int subpattern_end(int n) const
+ {
+ return m_pcre_exec_rc <= 0 ? 0 : m_SubStrVec[n * 2 + 1];
+ }
+ int subpattern_length(int n) const
+ {
+ return subpattern_end(n) - subpattern_start(n);
+ }
+ void cleanup()
+ {
+ if (m_pcre)
+ {
+ pcre_free(m_pcre);
+ m_pcre= NULL;
+ }
+ m_prev_pattern.length(0);
+ }
+ bool is_compiled() const { return m_pcre != NULL; }
+ bool is_const() const { return m_is_const; }
+ void set_const(bool arg) { m_is_const= arg; }
+ CHARSET_INFO * library_charset() const { return m_library_charset; }
+};
+
+
+class Item_func_regex :public Item_bool_func
+{
+ Regexp_processor_pcre re;
+ DTCollation cmp_collation;
+public:
+ Item_func_regex(Item *a,Item *b) :Item_bool_func(a,b)
+ {}
+ void cleanup()
+ {
+ DBUG_ENTER("Item_func_regex::cleanup");
+ Item_bool_func::cleanup();
+ re.cleanup();
+ DBUG_VOID_RETURN;
+ }
+ longlong val_int();
+ void fix_length_and_dec();
+ const char *func_name() const { return "regexp"; }
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ print_op(str, query_type);
+ }
+
+ CHARSET_INFO *compare_collation() { return cmp_collation.collation; }
+};
+
+
+class Item_func_regexp_instr :public Item_int_func
+{
+ Regexp_processor_pcre re;
+ DTCollation cmp_collation;
+public:
+ Item_func_regexp_instr(Item *a, Item *b) :Item_int_func(a, b)
+ {}
+ void cleanup()
+ {
+ DBUG_ENTER("Item_func_regexp_instr::cleanup");
+ Item_int_func::cleanup();
+ re.cleanup();
+ DBUG_VOID_RETURN;
+ }
+ longlong val_int();
+ void fix_length_and_dec();
+ const char *func_name() const { return "regexp_instr"; }
+};
+
+
+typedef class Item COND;
+
+class Item_cond :public Item_bool_func
+{
+protected:
+ List<Item> list;
+ bool abort_on_null;
+ table_map and_tables_cache;
+
+public:
+ /* Item_cond() is only used to create top level items */
+ Item_cond(): Item_bool_func(), abort_on_null(1)
+ { const_item_cache=0; }
+ Item_cond(Item *i1,Item *i2)
+ :Item_bool_func(), abort_on_null(0)
+ {
+ list.push_back(i1);
+ list.push_back(i2);
+ }
+ Item_cond(THD *thd, Item_cond *item);
+ Item_cond(List<Item> &nlist)
+ :Item_bool_func(), list(nlist), abort_on_null(0) {}
+ bool add(Item *item)
+ {
+ DBUG_ASSERT(item);
+ return list.push_back(item);
+ }
+ bool add_at_head(Item *item)
+ {
+ DBUG_ASSERT(item);
+ return list.push_front(item);
+ }
+ void add_at_head(List<Item> *nlist)
+ {
+ DBUG_ASSERT(nlist->elements);
+ list.prepand(nlist);
+ }
+ void add_at_end(List<Item> *nlist)
+ {
+ DBUG_ASSERT(nlist->elements);
+ list.concat(nlist);
+ }
+ bool fix_fields(THD *, Item **ref);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+
+ enum Type type() const { return COND_ITEM; }
+ List<Item>* argument_list() { return &list; }
+ table_map used_tables() const;
+ void update_used_tables();
+ virtual void print(String *str, enum_query_type query_type);
+ void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields);
+ friend int setup_conds(THD *thd, TABLE_LIST *tables, TABLE_LIST *leaves,
+ COND **conds);
+ void top_level_item() { abort_on_null=1; }
+ bool top_level() { return abort_on_null; }
+ void copy_andor_arguments(THD *thd, Item_cond *item);
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg);
+ Item *transform(Item_transformer transformer, uchar *arg);
+ void traverse_cond(Cond_traverser, void *arg, traverse_order order);
+ void neg_arguments(THD *thd);
+ enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; }
+ bool subst_argument_checker(uchar **arg) { return TRUE; }
+ Item *compile(Item_analyzer analyzer, uchar **arg_p,
+ Item_transformer transformer, uchar *arg_t);
+ bool eval_not_null_tables(uchar *opt_arg);
+};
+
+template <template<class> class LI, class T> class Item_equal_iterator;
+
+/*
+ The class Item_equal is used to represent conjunctions of equality
+ predicates of the form field1 = field2, and field=const in where
+ conditions and on expressions.
+
+ All equality predicates of the form field1=field2 contained in a
+ conjunction are substituted for a sequence of items of this class.
+ An item of this class Item_equal(f1,f2,...fk) represents a
+ multiple equality f1=f2=...=fk.
+
+ If a conjunction contains predicates f1=f2 and f2=f3, a new item of
+ this class is created Item_equal(f1,f2,f3) representing the multiple
+ equality f1=f2=f3 that substitutes the above equality predicates in
+ the conjunction.
+ A conjunction of the predicates f2=f1 and f3=f1 and f3=f2 will be
+ substituted for the item representing the same multiple equality
+ f1=f2=f3.
+ An item Item_equal(f1,f2) can appear instead of a conjunction of
+ f2=f1 and f1=f2, or instead of just the predicate f1=f2.
+
+ An item of the class Item_equal inherits equalities from outer
+ conjunctive levels.
+
+ Suppose we have a where condition of the following form:
+ WHERE f1=f2 AND f3=f4 AND f3=f5 AND ... AND (...OR (f1=f3 AND ...)).
+ In this case:
+ f1=f2 will be substituted for Item_equal(f1,f2);
+ f3=f4 and f3=f5 will be substituted for Item_equal(f3,f4,f5);
+ f1=f3 will be substituted for Item_equal(f1,f2,f3,f4,f5);
+
+ An object of the class Item_equal can contain an optional constant
+ item c. Then it represents a multiple equality of the form
+ c=f1=...=fk.
+
+ Objects of the class Item_equal are used for the following:
+
+ 1. An object Item_equal(t1.f1,...,tk.fk) allows us to consider any
+ pair of tables ti and tj as joined by an equi-condition.
+ Thus it provide us with additional access paths from table to table.
+
+ 2. An object Item_equal(t1.f1,...,tk.fk) is applied to deduce new
+ SARGable predicates:
+ f1=...=fk AND P(fi) => f1=...=fk AND P(fi) AND P(fj).
+ It also can give us additional index scans and can allow us to
+ improve selectivity estimates.
+
+ 3. An object Item_equal(t1.f1,...,tk.fk) is used to optimize the
+ selected execution plan for the query: if table ti is accessed
+ before the table tj then in any predicate P in the where condition
+ the occurrence of tj.fj is substituted for ti.fi. This can allow
+ an evaluation of the predicate at an earlier step.
+
+ When feature 1 is supported they say that join transitive closure
+ is employed.
+ When feature 2 is supported they say that search argument transitive
+ closure is employed.
+ Both features are usually supported by preprocessing original query and
+ adding additional predicates.
+ We do not just add predicates, we rather dynamically replace some
+ predicates that can not be used to access tables in the investigated
+ plan for those, obtained by substitution of some fields for equal fields,
+ that can be used.
+
+ Prepared Statements/Stored Procedures note: instances of class
+ Item_equal are created only at the time a PS/SP is executed and
+ are deleted in the end of execution. All changes made to these
+ objects need not be registered in the list of changes of the parse
+ tree and do not harm PS/SP re-execution.
+
+ Item equal objects are employed only at the optimize phase. Usually they are
+ not supposed to be evaluated. Yet in some cases we call the method val_int()
+ for them. We have to take care of restricting the predicate such an
+ object represents f1=f2= ...=fn to the projection of known fields fi1=...=fik.
+*/
+
+class Item_equal: public Item_bool_func
+{
+ /*
+ The list of equal items. Currently the list can contain:
+ - Item_fields items for references to table columns
+ - Item_direct_view_ref items for references to view columns
+ - one const item
+
+ If the list contains a constant item this item is always first in the list.
+ The list contains at least two elements.
+ Currently all Item_fields/Item_direct_view_ref items in the list should
+ refer to table columns with equavalent type definitions. In particular
+ if these are string columns they should have the same charset/collation.
+
+ Use objects of the companion class Item_equal_fields_iterator to iterate
+ over all items from the list of the Item_field/Item_direct_view_ref classes.
+ */
+ List<Item> equal_items;
+ /*
+ TRUE <-> one of the items is a const item.
+ Such item is always first in in the equal_items list
+ */
+ bool with_const;
+ /*
+ The field eval_item is used when this item is evaluated
+ with the method val_int()
+ */
+ cmp_item *eval_item;
+ /*
+ This initially is set to FALSE. It becomes TRUE when this item is evaluated
+ as being always false. If the flag is TRUE the contents of the list
+ the equal_items should be ignored.
+ */
+ bool cond_false;
+ /*
+ This initially is set to FALSE. It becomes TRUE when this item is evaluated
+ as being always true. If the flag is TRUE the contents of the list
+ the equal_items should be ignored.
+ */
+ bool cond_true;
+ /*
+ compare_as_dates=TRUE <-> constants equal to fields from equal_items
+ must be compared as datetimes and not as strings.
+ compare_as_dates can be TRUE only if with_const=TRUE
+ */
+ bool compare_as_dates;
+ /*
+ The comparator used to compare constants equal to fields from equal_items
+ as datetimes. The comparator is used only if compare_as_dates=TRUE
+ */
+ Arg_comparator cmp;
+
+ /*
+ For Item_equal objects inside an OR clause: one of the fields that were
+ used in the original equality.
+ */
+ Item_field *context_field;
+
+ bool link_equal_fields;
+
+public:
+
+ COND_EQUAL *upper_levels; /* multiple equalities of upper and levels */
+
+ inline Item_equal()
+ : Item_bool_func(), with_const(FALSE), eval_item(0), cond_false(0),
+ context_field(NULL)
+ { const_item_cache=0; sargable= TRUE; }
+ Item_equal(Item *f1, Item *f2, bool with_const_item);
+ Item_equal(Item_equal *item_equal);
+ /* Currently the const item is always the first in the list of equal items */
+ inline Item* get_const() { return with_const ? equal_items.head() : NULL; }
+ void add_const(Item *c, Item *f = NULL);
+ /** Add a non-constant item to the multiple equality */
+ void add(Item *f) { equal_items.push_back(f); }
+ bool contains(Field *field);
+ Item* get_first(struct st_join_table *context, Item *field);
+ /** Get number of field items / references to field items in this object */
+ uint n_field_items() { return equal_items.elements - MY_TEST(with_const); }
+ void merge(Item_equal *item);
+ bool merge_with_check(Item_equal *equal_item, bool save_merged);
+ void merge_into_list(List<Item_equal> *list, bool save_merged,
+ bool only_intersected);
+ void update_const();
+ enum Functype functype() const { return MULT_EQUAL_FUNC; }
+ longlong val_int();
+ const char *func_name() const { return "multiple equal"; }
+ optimize_type select_optimize() const { return OPTIMIZE_EQUAL; }
+ void sort(Item_field_cmpfunc compare, void *arg);
+ void fix_length_and_dec();
+ bool fix_fields(THD *thd, Item **ref);
+ void update_used_tables();
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg);
+ Item *transform(Item_transformer transformer, uchar *arg);
+ virtual void print(String *str, enum_query_type query_type);
+ CHARSET_INFO *compare_collation();
+
+ void set_context_field(Item_field *ctx_field) { context_field= ctx_field; }
+ void set_link_equal_fields(bool flag) { link_equal_fields= flag; }
+ friend class Item_equal_fields_iterator;
+ bool count_sargable_conds(uchar *arg);
+ friend class Item_equal_iterator<List_iterator_fast,Item>;
+ friend class Item_equal_iterator<List_iterator,Item>;
+ friend Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels,
+ Item_equal *item_equal);
+ friend bool setup_sj_materialization_part1(struct st_join_table *tab);
+ friend bool setup_sj_materialization_part2(struct st_join_table *tab);
+};
+
+class COND_EQUAL: public Sql_alloc
+{
+public:
+ uint max_members; /* max number of members the current level
+ list and all lower level lists */
+ COND_EQUAL *upper_levels; /* multiple equalities of upper and levels */
+ List<Item_equal> current_level; /* list of multiple equalities of
+ the current and level */
+ COND_EQUAL()
+ {
+ upper_levels= 0;
+ }
+ void copy(COND_EQUAL &cond_equal)
+ {
+ max_members= cond_equal.max_members;
+ upper_levels= cond_equal.upper_levels;
+ if (cond_equal.current_level.is_empty())
+ current_level.empty();
+ else
+ current_level= cond_equal.current_level;
+ }
+};
+
+
+/*
+ The template Item_equal_iterator is used to define classes
+ Item_equal_fields_iterator and Item_equal_fields_iterator_slow.
+ These are helper classes for the class Item equal
+ Both classes are used to iterate over references to table/view columns
+ from the list of equal items that included in an Item_equal object.
+ The second class supports the operation of removal of the current member
+ from the list when performing an iteration.
+*/
+
+template <template<class> class LI, typename T> class Item_equal_iterator
+ : public LI<T>
+{
+protected:
+ Item_equal *item_equal;
+ Item *curr_item;
+public:
+ Item_equal_iterator<LI,T>(Item_equal &item_eq)
+ :LI<T> (item_eq.equal_items)
+ {
+ curr_item= NULL;
+ item_equal= &item_eq;
+ if (item_eq.with_const)
+ {
+ LI<T> *list_it= this;
+ curr_item= (*list_it)++;
+ }
+ }
+ Item* operator++(int)
+ {
+ LI<T> *list_it= this;
+ curr_item= (*list_it)++;
+ return curr_item;
+ }
+ void rewind(void)
+ {
+ LI<T> *list_it= this;
+ list_it->rewind();
+ if (item_equal->with_const)
+ curr_item= (*list_it)++;
+ }
+ Field *get_curr_field()
+ {
+ Item_field *item= (Item_field *) (curr_item->real_item());
+ return item->field;
+ }
+};
+
+typedef Item_equal_iterator<List_iterator_fast,Item > Item_equal_iterator_fast;
+
+class Item_equal_fields_iterator
+ :public Item_equal_iterator_fast
+{
+public:
+ Item_equal_fields_iterator(Item_equal &item_eq)
+ :Item_equal_iterator_fast(item_eq)
+ { }
+ Item ** ref()
+ {
+ return List_iterator_fast<Item>::ref();
+ }
+};
+
+typedef Item_equal_iterator<List_iterator,Item > Item_equal_iterator_iterator_slow;
+
+class Item_equal_fields_iterator_slow
+ :public Item_equal_iterator_iterator_slow
+{
+public:
+ Item_equal_fields_iterator_slow(Item_equal &item_eq)
+ :Item_equal_iterator_iterator_slow(item_eq)
+ { }
+ void remove()
+ {
+ List_iterator<Item>::remove();
+ }
+};
+
+
+class Item_cond_and :public Item_cond
+{
+public:
+ COND_EQUAL cond_equal; /* contains list of Item_equal objects for
+ the current and level and reference
+ to multiple equalities of upper and levels */
+ Item_cond_and() :Item_cond() {}
+ Item_cond_and(Item *i1,Item *i2) :Item_cond(i1,i2) {}
+ Item_cond_and(THD *thd, Item_cond_and *item) :Item_cond(thd, item) {}
+ Item_cond_and(List<Item> &list_arg): Item_cond(list_arg) {}
+ enum Functype functype() const { return COND_AND_FUNC; }
+ longlong val_int();
+ const char *func_name() const { return "and"; }
+ table_map not_null_tables() const
+ { return abort_on_null ? not_null_tables_cache: and_tables_cache; }
+ Item* copy_andor_structure(THD *thd)
+ {
+ Item_cond_and *item;
+ if ((item= new Item_cond_and(thd, this)))
+ item->copy_andor_arguments(thd, this);
+ return item;
+ }
+ Item *neg_transformer(THD *thd);
+ void mark_as_condition_AND_part(TABLE_LIST *embedding);
+ virtual uint exists2in_reserved_items() { return list.elements; };
+ bool walk_top_and(Item_processor processor, uchar *arg);
+};
+
+inline bool is_cond_and(Item *item)
+{
+ if (item->type() != Item::COND_ITEM)
+ return FALSE;
+
+ Item_cond *cond_item= (Item_cond*) item;
+ return (cond_item->functype() == Item_func::COND_AND_FUNC);
+}
+
+class Item_cond_or :public Item_cond
+{
+public:
+ Item_cond_or() :Item_cond() {}
+ Item_cond_or(Item *i1,Item *i2) :Item_cond(i1,i2) {}
+ Item_cond_or(THD *thd, Item_cond_or *item) :Item_cond(thd, item) {}
+ Item_cond_or(List<Item> &list_arg): Item_cond(list_arg) {}
+ enum Functype functype() const { return COND_OR_FUNC; }
+ longlong val_int();
+ const char *func_name() const { return "or"; }
+ table_map not_null_tables() const { return and_tables_cache; }
+ Item* copy_andor_structure(THD *thd)
+ {
+ Item_cond_or *item;
+ if ((item= new Item_cond_or(thd, this)))
+ item->copy_andor_arguments(thd, this);
+ return item;
+ }
+ Item *neg_transformer(THD *thd);
+};
+
+class Item_func_dyncol_check :public Item_bool_func
+{
+public:
+ Item_func_dyncol_check(Item *str) :Item_bool_func(str) {}
+ longlong val_int();
+ const char *func_name() const { return "column_check"; }
+};
+
+class Item_func_dyncol_exists :public Item_bool_func
+{
+public:
+ Item_func_dyncol_exists(Item *str, Item *num) :Item_bool_func(str, num) {}
+ longlong val_int();
+ const char *func_name() const { return "column_exists"; }
+};
+
+inline bool is_cond_or(Item *item)
+{
+ if (item->type() != Item::COND_ITEM)
+ return FALSE;
+
+ Item_cond *cond_item= (Item_cond*) item;
+ return (cond_item->functype() == Item_func::COND_OR_FUNC);
+}
+
+/* Some useful inline functions */
+
+inline Item *and_conds(Item *a, Item *b)
+{
+ if (!b) return a;
+ if (!a) return b;
+ return new Item_cond_and(a, b);
+}
+
+
+Item *and_expressions(Item *a, Item *b, Item **org_item);
+
+longlong get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg,
+ Item *warn_item, bool *is_null);
+
+
+bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type,
+ const char *warn_name, MYSQL_TIME *l_time);
+
+/*
+ These need definitions from this file but the variables are defined
+ in mysqld.h. The variables really belong in this component, but for
+ the time being we leave them in mysqld.cc to avoid merge problems.
+*/
+extern Eq_creator eq_creator;
+extern Ne_creator ne_creator;
+extern Gt_creator gt_creator;
+extern Lt_creator lt_creator;
+extern Ge_creator ge_creator;
+extern Le_creator le_creator;
+
+#endif /* ITEM_CMPFUNC_INCLUDED */
+
diff --git a/sql/item_create.cc b/sql/item_create.cc
new file mode 100644
index 00000000000..852891f7743
--- /dev/null
+++ b/sql/item_create.cc
@@ -0,0 +1,6293 @@
+/*
+ Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2008-2011 Monty Program Ab
+
+ 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 */
+
+/**
+ @file
+
+ @brief
+ Functions to create an item. Used by sql_yac.yy
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h"
+#include "sp_head.h"
+#include "sp.h"
+#include "item_inetfunc.h"
+#include "sql_time.h"
+
+/*
+=============================================================================
+ HELPER FUNCTIONS
+=============================================================================
+*/
+
+static const char* item_name(Item *a, String *str)
+{
+ if (a->name)
+ return a->name;
+ str->length(0);
+ a->print(str, QT_ORDINARY);
+ return str->c_ptr_safe();
+}
+
+
+static void wrong_precision_error(uint errcode, Item *a,
+ ulonglong number, ulong maximum)
+{
+ char buff[1024];
+ String buf(buff, sizeof(buff), system_charset_info);
+
+ my_error(errcode, MYF(0), (uint) MY_MIN(number, UINT_MAX32),
+ item_name(a, &buf), maximum);
+}
+
+
+/**
+ Get precision and scale for a declaration
+
+ return
+ 0 ok
+ 1 error
+*/
+
+bool get_length_and_scale(ulonglong length, ulonglong decimals,
+ ulong *out_length, uint *out_decimals,
+ uint max_precision, uint max_scale,
+ Item *a)
+{
+ if (length > (ulonglong) max_precision)
+ {
+ wrong_precision_error(ER_TOO_BIG_PRECISION, a, length, max_precision);
+ return 1;
+ }
+ if (decimals > (ulonglong) max_scale)
+ {
+ wrong_precision_error(ER_TOO_BIG_SCALE, a, decimals, max_scale);
+ return 1;
+ }
+
+ *out_length= (ulong) length;
+ *out_decimals= (uint) decimals;
+ my_decimal_trim(out_length, out_decimals);
+
+ if (*out_length < *out_decimals)
+ {
+ my_error(ER_M_BIGGER_THAN_D, MYF(0), "");
+ return 1;
+ }
+ return 0;
+}
+
+/*
+=============================================================================
+ LOCAL DECLARATIONS
+=============================================================================
+*/
+
+/**
+ Adapter for native functions with a variable number of arguments.
+ The main use of this class is to discard the following calls:
+ <code>foo(expr1 AS name1, expr2 AS name2, ...)</code>
+ which are syntactically correct (the syntax can refer to a UDF),
+ but semantically invalid for native functions.
+*/
+
+class Create_native_func : public Create_func
+{
+public:
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /**
+ Builder method, with no arguments.
+ @param thd The current thread
+ @param name The native function name
+ @param item_list The function parameters, none of which are named
+ @return An item representing the function call
+ */
+ virtual Item *create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list) = 0;
+
+protected:
+ /** Constructor. */
+ Create_native_func() {}
+ /** Destructor. */
+ virtual ~Create_native_func() {}
+};
+
+
+/**
+ Adapter for functions that takes exactly zero arguments.
+*/
+
+class Create_func_arg0 : public Create_func
+{
+public:
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /**
+ Builder method, with no arguments.
+ @param thd The current thread
+ @return An item representing the function call
+ */
+ virtual Item *create_builder(THD *thd) = 0;
+
+protected:
+ /** Constructor. */
+ Create_func_arg0() {}
+ /** Destructor. */
+ virtual ~Create_func_arg0() {}
+};
+
+
+/**
+ Adapter for functions that takes exactly one argument.
+*/
+
+class Create_func_arg1 : public Create_func
+{
+public:
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /**
+ Builder method, with one argument.
+ @param thd The current thread
+ @param arg1 The first argument of the function
+ @return An item representing the function call
+ */
+ virtual Item *create_1_arg(THD *thd, Item *arg1) = 0;
+
+protected:
+ /** Constructor. */
+ Create_func_arg1() {}
+ /** Destructor. */
+ virtual ~Create_func_arg1() {}
+};
+
+
+/**
+ Adapter for functions that takes exactly two arguments.
+*/
+
+class Create_func_arg2 : public Create_func
+{
+public:
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /**
+ Builder method, with two arguments.
+ @param thd The current thread
+ @param arg1 The first argument of the function
+ @param arg2 The second argument of the function
+ @return An item representing the function call
+ */
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2) = 0;
+
+protected:
+ /** Constructor. */
+ Create_func_arg2() {}
+ /** Destructor. */
+ virtual ~Create_func_arg2() {}
+};
+
+
+/**
+ Adapter for functions that takes exactly three arguments.
+*/
+
+class Create_func_arg3 : public Create_func
+{
+public:
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /**
+ Builder method, with three arguments.
+ @param thd The current thread
+ @param arg1 The first argument of the function
+ @param arg2 The second argument of the function
+ @param arg3 The third argument of the function
+ @return An item representing the function call
+ */
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) = 0;
+
+protected:
+ /** Constructor. */
+ Create_func_arg3() {}
+ /** Destructor. */
+ virtual ~Create_func_arg3() {}
+};
+
+
+/**
+ Function builder for Stored Functions.
+*/
+
+class Create_sp_func : public Create_qfunc
+{
+public:
+ virtual Item *create_with_db(THD *thd, LEX_STRING db, LEX_STRING name,
+ bool use_explicit_name, List<Item> *item_list);
+
+ static Create_sp_func s_singleton;
+
+protected:
+ /** Constructor. */
+ Create_sp_func() {}
+ /** Destructor. */
+ virtual ~Create_sp_func() {}
+};
+
+
+#ifndef HAVE_SPATIAL
+/**
+ Common (non) builder for geometry functions.
+ This builder is used in <code>--without-geometry</code> builds only,
+ to report an error.
+*/
+
+class Create_func_no_geom : public Create_func
+{
+public:
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /** Singleton. */
+ static Create_func_no_geom s_singleton;
+
+protected:
+ /** Constructor. */
+ Create_func_no_geom() {}
+ /** Destructor. */
+ virtual ~Create_func_no_geom() {}
+};
+#endif
+
+
+/*
+ Concrete functions builders (native functions).
+ Please keep this list sorted in alphabetical order,
+ it helps to compare code between versions, and helps with merges conflicts.
+*/
+
+class Create_func_abs : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_abs s_singleton;
+
+protected:
+ Create_func_abs() {}
+ virtual ~Create_func_abs() {}
+};
+
+
+class Create_func_acos : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_acos s_singleton;
+
+protected:
+ Create_func_acos() {}
+ virtual ~Create_func_acos() {}
+};
+
+
+class Create_func_addtime : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_addtime s_singleton;
+
+protected:
+ Create_func_addtime() {}
+ virtual ~Create_func_addtime() {}
+};
+
+
+class Create_func_aes_encrypt : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_aes_encrypt s_singleton;
+
+protected:
+ Create_func_aes_encrypt() {}
+ virtual ~Create_func_aes_encrypt() {}
+};
+
+
+class Create_func_aes_decrypt : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_aes_decrypt s_singleton;
+
+protected:
+ Create_func_aes_decrypt() {}
+ virtual ~Create_func_aes_decrypt() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_area : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_area s_singleton;
+
+protected:
+ Create_func_area() {}
+ virtual ~Create_func_area() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_as_wkb : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_as_wkb s_singleton;
+
+protected:
+ Create_func_as_wkb() {}
+ virtual ~Create_func_as_wkb() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_as_wkt : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_as_wkt s_singleton;
+
+protected:
+ Create_func_as_wkt() {}
+ virtual ~Create_func_as_wkt() {}
+};
+#endif
+
+
+class Create_func_asin : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_asin s_singleton;
+
+protected:
+ Create_func_asin() {}
+ virtual ~Create_func_asin() {}
+};
+
+
+class Create_func_atan : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_atan s_singleton;
+
+protected:
+ Create_func_atan() {}
+ virtual ~Create_func_atan() {}
+};
+
+
+class Create_func_benchmark : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_benchmark s_singleton;
+
+protected:
+ Create_func_benchmark() {}
+ virtual ~Create_func_benchmark() {}
+};
+
+
+class Create_func_bin : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_bin s_singleton;
+
+protected:
+ Create_func_bin() {}
+ virtual ~Create_func_bin() {}
+};
+
+
+class Create_func_binlog_gtid_pos : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_binlog_gtid_pos s_singleton;
+
+protected:
+ Create_func_binlog_gtid_pos() {}
+ virtual ~Create_func_binlog_gtid_pos() {}
+};
+
+
+class Create_func_bit_count : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_bit_count s_singleton;
+
+protected:
+ Create_func_bit_count() {}
+ virtual ~Create_func_bit_count() {}
+};
+
+
+class Create_func_bit_length : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_bit_length s_singleton;
+
+protected:
+ Create_func_bit_length() {}
+ virtual ~Create_func_bit_length() {}
+};
+
+
+class Create_func_ceiling : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_ceiling s_singleton;
+
+protected:
+ Create_func_ceiling() {}
+ virtual ~Create_func_ceiling() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_centroid : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_centroid s_singleton;
+
+protected:
+ Create_func_centroid() {}
+ virtual ~Create_func_centroid() {}
+};
+#endif
+
+
+class Create_func_char_length : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_char_length s_singleton;
+
+protected:
+ Create_func_char_length() {}
+ virtual ~Create_func_char_length() {}
+};
+
+
+class Create_func_coercibility : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_coercibility s_singleton;
+
+protected:
+ Create_func_coercibility() {}
+ virtual ~Create_func_coercibility() {}
+};
+
+class Create_func_dyncol_check : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dyncol_check s_singleton;
+
+protected:
+ Create_func_dyncol_check() {}
+ virtual ~Create_func_dyncol_check() {}
+};
+
+class Create_func_dyncol_exists : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_dyncol_exists s_singleton;
+
+protected:
+ Create_func_dyncol_exists() {}
+ virtual ~Create_func_dyncol_exists() {}
+};
+
+class Create_func_dyncol_list : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dyncol_list s_singleton;
+
+protected:
+ Create_func_dyncol_list() {}
+ virtual ~Create_func_dyncol_list() {}
+};
+
+class Create_func_dyncol_json : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dyncol_json s_singleton;
+
+protected:
+ Create_func_dyncol_json() {}
+ virtual ~Create_func_dyncol_json() {}
+};
+
+
+class Create_func_compress : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_compress s_singleton;
+
+protected:
+ Create_func_compress() {}
+ virtual ~Create_func_compress() {}
+};
+
+
+class Create_func_concat : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_concat s_singleton;
+
+protected:
+ Create_func_concat() {}
+ virtual ~Create_func_concat() {}
+};
+
+
+class Create_func_decode_histogram : public Create_func_arg2
+{
+public:
+ Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_decode_histogram s_singleton;
+
+protected:
+ Create_func_decode_histogram() {}
+ virtual ~Create_func_decode_histogram() {}
+};
+
+
+class Create_func_concat_ws : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_concat_ws s_singleton;
+
+protected:
+ Create_func_concat_ws() {}
+ virtual ~Create_func_concat_ws() {}
+};
+
+
+class Create_func_connection_id : public Create_func_arg0
+{
+public:
+ virtual Item *create_builder(THD *thd);
+
+ static Create_func_connection_id s_singleton;
+
+protected:
+ Create_func_connection_id() {}
+ virtual ~Create_func_connection_id() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_mbr_contains : public Create_func_arg2
+{
+ public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_mbr_contains s_singleton;
+
+ protected:
+ Create_func_mbr_contains() {}
+ virtual ~Create_func_mbr_contains() {}
+};
+
+
+class Create_func_contains : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_contains s_singleton;
+
+protected:
+ Create_func_contains() {}
+ virtual ~Create_func_contains() {}
+};
+#endif
+
+
+class Create_func_conv : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_conv s_singleton;
+
+protected:
+ Create_func_conv() {}
+ virtual ~Create_func_conv() {}
+};
+
+
+class Create_func_convert_tz : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_convert_tz s_singleton;
+
+protected:
+ Create_func_convert_tz() {}
+ virtual ~Create_func_convert_tz() {}
+};
+
+
+class Create_func_cos : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_cos s_singleton;
+
+protected:
+ Create_func_cos() {}
+ virtual ~Create_func_cos() {}
+};
+
+
+class Create_func_cot : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_cot s_singleton;
+
+protected:
+ Create_func_cot() {}
+ virtual ~Create_func_cot() {}
+};
+
+
+class Create_func_crc32 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_crc32 s_singleton;
+
+protected:
+ Create_func_crc32() {}
+ virtual ~Create_func_crc32() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_crosses : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_crosses s_singleton;
+
+protected:
+ Create_func_crosses() {}
+ virtual ~Create_func_crosses() {}
+};
+#endif
+
+
+class Create_func_date_format : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_date_format s_singleton;
+
+protected:
+ Create_func_date_format() {}
+ virtual ~Create_func_date_format() {}
+};
+
+
+class Create_func_datediff : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_datediff s_singleton;
+
+protected:
+ Create_func_datediff() {}
+ virtual ~Create_func_datediff() {}
+};
+
+
+class Create_func_dayname : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dayname s_singleton;
+
+protected:
+ Create_func_dayname() {}
+ virtual ~Create_func_dayname() {}
+};
+
+
+class Create_func_dayofmonth : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dayofmonth s_singleton;
+
+protected:
+ Create_func_dayofmonth() {}
+ virtual ~Create_func_dayofmonth() {}
+};
+
+
+class Create_func_dayofweek : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dayofweek s_singleton;
+
+protected:
+ Create_func_dayofweek() {}
+ virtual ~Create_func_dayofweek() {}
+};
+
+
+class Create_func_dayofyear : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dayofyear s_singleton;
+
+protected:
+ Create_func_dayofyear() {}
+ virtual ~Create_func_dayofyear() {}
+};
+
+
+class Create_func_decode : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_decode s_singleton;
+
+protected:
+ Create_func_decode() {}
+ virtual ~Create_func_decode() {}
+};
+
+
+class Create_func_degrees : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_degrees s_singleton;
+
+protected:
+ Create_func_degrees() {}
+ virtual ~Create_func_degrees() {}
+};
+
+
+class Create_func_des_decrypt : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_des_decrypt s_singleton;
+
+protected:
+ Create_func_des_decrypt() {}
+ virtual ~Create_func_des_decrypt() {}
+};
+
+
+class Create_func_des_encrypt : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_des_encrypt s_singleton;
+
+protected:
+ Create_func_des_encrypt() {}
+ virtual ~Create_func_des_encrypt() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_dimension : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_dimension s_singleton;
+
+protected:
+ Create_func_dimension() {}
+ virtual ~Create_func_dimension() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_mbr_disjoint : public Create_func_arg2
+{
+ public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_mbr_disjoint s_singleton;
+
+ protected:
+ Create_func_mbr_disjoint() {}
+ virtual ~Create_func_mbr_disjoint() {}
+};
+
+
+class Create_func_disjoint : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_disjoint s_singleton;
+
+protected:
+ Create_func_disjoint() {}
+ virtual ~Create_func_disjoint() {}
+};
+
+
+class Create_func_distance : public Create_func_arg2
+{
+ public:
+ virtual Item* create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_distance s_singleton;
+
+ protected:
+ Create_func_distance() {}
+ virtual ~Create_func_distance() {}
+};
+#endif
+
+
+class Create_func_elt : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_elt s_singleton;
+
+protected:
+ Create_func_elt() {}
+ virtual ~Create_func_elt() {}
+};
+
+
+class Create_func_encode : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_encode s_singleton;
+
+protected:
+ Create_func_encode() {}
+ virtual ~Create_func_encode() {}
+};
+
+
+class Create_func_encrypt : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_encrypt s_singleton;
+
+protected:
+ Create_func_encrypt() {}
+ virtual ~Create_func_encrypt() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_endpoint : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_endpoint s_singleton;
+
+protected:
+ Create_func_endpoint() {}
+ virtual ~Create_func_endpoint() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_envelope : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_envelope s_singleton;
+
+protected:
+ Create_func_envelope() {}
+ virtual ~Create_func_envelope() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_mbr_equals : public Create_func_arg2
+{
+ public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_mbr_equals s_singleton;
+
+ protected:
+ Create_func_mbr_equals() {}
+ virtual ~Create_func_mbr_equals() {}
+};
+
+
+class Create_func_equals : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_equals s_singleton;
+
+protected:
+ Create_func_equals() {}
+ virtual ~Create_func_equals() {}
+};
+#endif
+
+
+class Create_func_exp : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_exp s_singleton;
+
+protected:
+ Create_func_exp() {}
+ virtual ~Create_func_exp() {}
+};
+
+
+class Create_func_export_set : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_export_set s_singleton;
+
+protected:
+ Create_func_export_set() {}
+ virtual ~Create_func_export_set() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_exteriorring : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_exteriorring s_singleton;
+
+protected:
+ Create_func_exteriorring() {}
+ virtual ~Create_func_exteriorring() {}
+};
+#endif
+
+
+class Create_func_field : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_field s_singleton;
+
+protected:
+ Create_func_field() {}
+ virtual ~Create_func_field() {}
+};
+
+
+class Create_func_find_in_set : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_find_in_set s_singleton;
+
+protected:
+ Create_func_find_in_set() {}
+ virtual ~Create_func_find_in_set() {}
+};
+
+
+class Create_func_floor : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_floor s_singleton;
+
+protected:
+ Create_func_floor() {}
+ virtual ~Create_func_floor() {}
+};
+
+
+class Create_func_format : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_format s_singleton;
+
+protected:
+ Create_func_format() {}
+ virtual ~Create_func_format() {}
+};
+
+
+class Create_func_found_rows : public Create_func_arg0
+{
+public:
+ virtual Item *create_builder(THD *thd);
+
+ static Create_func_found_rows s_singleton;
+
+protected:
+ Create_func_found_rows() {}
+ virtual ~Create_func_found_rows() {}
+};
+
+
+class Create_func_from_base64 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_from_base64 s_singleton;
+
+protected:
+ Create_func_from_base64() {}
+ virtual ~Create_func_from_base64() {}
+};
+
+
+class Create_func_from_days : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_from_days s_singleton;
+
+protected:
+ Create_func_from_days() {}
+ virtual ~Create_func_from_days() {}
+};
+
+
+class Create_func_from_unixtime : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_from_unixtime s_singleton;
+
+protected:
+ Create_func_from_unixtime() {}
+ virtual ~Create_func_from_unixtime() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_geometry_from_text : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_geometry_from_text s_singleton;
+
+protected:
+ Create_func_geometry_from_text() {}
+ virtual ~Create_func_geometry_from_text() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_geometry_from_wkb : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_geometry_from_wkb s_singleton;
+
+protected:
+ Create_func_geometry_from_wkb() {}
+ virtual ~Create_func_geometry_from_wkb() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_geometry_type : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_geometry_type s_singleton;
+
+protected:
+ Create_func_geometry_type() {}
+ virtual ~Create_func_geometry_type() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_geometryn : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_geometryn s_singleton;
+
+protected:
+ Create_func_geometryn() {}
+ virtual ~Create_func_geometryn() {}
+};
+#endif
+
+
+class Create_func_get_lock : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_get_lock s_singleton;
+
+protected:
+ Create_func_get_lock() {}
+ virtual ~Create_func_get_lock() {}
+};
+
+
+#if defined(HAVE_SPATIAL) && !defined(DBUG_OFF)
+class Create_func_gis_debug : public Create_func_arg1
+{
+ public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_gis_debug s_singleton;
+
+ protected:
+ Create_func_gis_debug() {}
+ virtual ~Create_func_gis_debug() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_glength : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_glength s_singleton;
+
+protected:
+ Create_func_glength() {}
+ virtual ~Create_func_glength() {}
+};
+#endif
+
+
+class Create_func_greatest : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_greatest s_singleton;
+
+protected:
+ Create_func_greatest() {}
+ virtual ~Create_func_greatest() {}
+};
+
+
+class Create_func_hex : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_hex s_singleton;
+
+protected:
+ Create_func_hex() {}
+ virtual ~Create_func_hex() {}
+};
+
+
+class Create_func_ifnull : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_ifnull s_singleton;
+
+protected:
+ Create_func_ifnull() {}
+ virtual ~Create_func_ifnull() {}
+};
+
+
+class Create_func_inet_ntoa : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_inet_ntoa s_singleton;
+
+protected:
+ Create_func_inet_ntoa() {}
+ virtual ~Create_func_inet_ntoa() {}
+};
+
+
+class Create_func_inet_aton : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_inet_aton s_singleton;
+
+protected:
+ Create_func_inet_aton() {}
+ virtual ~Create_func_inet_aton() {}
+};
+
+
+class Create_func_inet6_aton : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_inet6_aton s_singleton;
+
+protected:
+ Create_func_inet6_aton() {}
+ virtual ~Create_func_inet6_aton() {}
+};
+
+
+class Create_func_inet6_ntoa : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_inet6_ntoa s_singleton;
+
+protected:
+ Create_func_inet6_ntoa() {}
+ virtual ~Create_func_inet6_ntoa() {}
+};
+
+
+class Create_func_is_ipv4 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_is_ipv4 s_singleton;
+
+protected:
+ Create_func_is_ipv4() {}
+ virtual ~Create_func_is_ipv4() {}
+};
+
+
+class Create_func_is_ipv6 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_is_ipv6 s_singleton;
+
+protected:
+ Create_func_is_ipv6() {}
+ virtual ~Create_func_is_ipv6() {}
+};
+
+
+class Create_func_is_ipv4_compat : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_is_ipv4_compat s_singleton;
+
+protected:
+ Create_func_is_ipv4_compat() {}
+ virtual ~Create_func_is_ipv4_compat() {}
+};
+
+
+class Create_func_is_ipv4_mapped : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_is_ipv4_mapped s_singleton;
+
+protected:
+ Create_func_is_ipv4_mapped() {}
+ virtual ~Create_func_is_ipv4_mapped() {}
+};
+
+
+class Create_func_instr : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_instr s_singleton;
+
+protected:
+ Create_func_instr() {}
+ virtual ~Create_func_instr() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_interiorringn : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_interiorringn s_singleton;
+
+protected:
+ Create_func_interiorringn() {}
+ virtual ~Create_func_interiorringn() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_mbr_intersects : public Create_func_arg2
+{
+ public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_mbr_intersects s_singleton;
+
+ protected:
+ Create_func_mbr_intersects() {}
+ virtual ~Create_func_mbr_intersects() {}
+};
+
+
+class Create_func_intersects : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_intersects s_singleton;
+
+protected:
+ Create_func_intersects() {}
+ virtual ~Create_func_intersects() {}
+};
+
+
+class Create_func_intersection : public Create_func_arg2
+{
+public:
+ virtual Item* create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_intersection s_singleton;
+
+protected:
+ Create_func_intersection() {}
+ virtual ~Create_func_intersection() {}
+};
+
+
+class Create_func_difference : public Create_func_arg2
+{
+public:
+ virtual Item* create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_difference s_singleton;
+
+protected:
+ Create_func_difference() {}
+ virtual ~Create_func_difference() {}
+};
+
+
+class Create_func_union : public Create_func_arg2
+{
+public:
+ virtual Item* create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_union s_singleton;
+
+protected:
+ Create_func_union() {}
+ virtual ~Create_func_union() {}
+};
+
+
+class Create_func_symdifference : public Create_func_arg2
+{
+public:
+ virtual Item* create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_symdifference s_singleton;
+
+protected:
+ Create_func_symdifference() {}
+ virtual ~Create_func_symdifference() {}
+};
+
+
+class Create_func_buffer : public Create_func_arg2
+{
+public:
+ virtual Item* create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_buffer s_singleton;
+
+protected:
+ Create_func_buffer() {}
+ virtual ~Create_func_buffer() {}
+};
+#endif /*HAVE_SPATIAL*/
+
+
+class Create_func_is_free_lock : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_is_free_lock s_singleton;
+
+protected:
+ Create_func_is_free_lock() {}
+ virtual ~Create_func_is_free_lock() {}
+};
+
+
+class Create_func_is_used_lock : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_is_used_lock s_singleton;
+
+protected:
+ Create_func_is_used_lock() {}
+ virtual ~Create_func_is_used_lock() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_isclosed : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_isclosed s_singleton;
+
+protected:
+ Create_func_isclosed() {}
+ virtual ~Create_func_isclosed() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_isempty : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_isempty s_singleton;
+
+protected:
+ Create_func_isempty() {}
+ virtual ~Create_func_isempty() {}
+};
+#endif
+
+
+class Create_func_isnull : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_isnull s_singleton;
+
+protected:
+ Create_func_isnull() {}
+ virtual ~Create_func_isnull() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_issimple : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_issimple s_singleton;
+
+protected:
+ Create_func_issimple() {}
+ virtual ~Create_func_issimple() {}
+};
+#endif
+
+
+class Create_func_last_day : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_last_day s_singleton;
+
+protected:
+ Create_func_last_day() {}
+ virtual ~Create_func_last_day() {}
+};
+
+
+class Create_func_last_insert_id : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_last_insert_id s_singleton;
+
+protected:
+ Create_func_last_insert_id() {}
+ virtual ~Create_func_last_insert_id() {}
+};
+
+
+class Create_func_lcase : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_lcase s_singleton;
+
+protected:
+ Create_func_lcase() {}
+ virtual ~Create_func_lcase() {}
+};
+
+
+class Create_func_least : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_least s_singleton;
+
+protected:
+ Create_func_least() {}
+ virtual ~Create_func_least() {}
+};
+
+
+class Create_func_length : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_length s_singleton;
+
+protected:
+ Create_func_length() {}
+ virtual ~Create_func_length() {}
+};
+
+
+#ifndef DBUG_OFF
+class Create_func_like_range_min : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_like_range_min s_singleton;
+
+protected:
+ Create_func_like_range_min() {}
+ virtual ~Create_func_like_range_min() {}
+};
+
+
+class Create_func_like_range_max : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_like_range_max s_singleton;
+
+protected:
+ Create_func_like_range_max() {}
+ virtual ~Create_func_like_range_max() {}
+};
+#endif
+
+
+class Create_func_ln : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_ln s_singleton;
+
+protected:
+ Create_func_ln() {}
+ virtual ~Create_func_ln() {}
+};
+
+
+class Create_func_load_file : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_load_file s_singleton;
+
+protected:
+ Create_func_load_file() {}
+ virtual ~Create_func_load_file() {}
+};
+
+
+class Create_func_locate : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_locate s_singleton;
+
+protected:
+ Create_func_locate() {}
+ virtual ~Create_func_locate() {}
+};
+
+
+class Create_func_log : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_log s_singleton;
+
+protected:
+ Create_func_log() {}
+ virtual ~Create_func_log() {}
+};
+
+
+class Create_func_log10 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_log10 s_singleton;
+
+protected:
+ Create_func_log10() {}
+ virtual ~Create_func_log10() {}
+};
+
+
+class Create_func_log2 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_log2 s_singleton;
+
+protected:
+ Create_func_log2() {}
+ virtual ~Create_func_log2() {}
+};
+
+
+class Create_func_lpad : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_lpad s_singleton;
+
+protected:
+ Create_func_lpad() {}
+ virtual ~Create_func_lpad() {}
+};
+
+
+class Create_func_ltrim : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_ltrim s_singleton;
+
+protected:
+ Create_func_ltrim() {}
+ virtual ~Create_func_ltrim() {}
+};
+
+
+class Create_func_makedate : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_makedate s_singleton;
+
+protected:
+ Create_func_makedate() {}
+ virtual ~Create_func_makedate() {}
+};
+
+
+class Create_func_maketime : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_maketime s_singleton;
+
+protected:
+ Create_func_maketime() {}
+ virtual ~Create_func_maketime() {}
+};
+
+
+class Create_func_make_set : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_make_set s_singleton;
+
+protected:
+ Create_func_make_set() {}
+ virtual ~Create_func_make_set() {}
+};
+
+
+class Create_func_master_pos_wait : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_master_pos_wait s_singleton;
+
+protected:
+ Create_func_master_pos_wait() {}
+ virtual ~Create_func_master_pos_wait() {}
+};
+
+
+class Create_func_master_gtid_wait : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_master_gtid_wait s_singleton;
+
+protected:
+ Create_func_master_gtid_wait() {}
+ virtual ~Create_func_master_gtid_wait() {}
+};
+
+
+class Create_func_md5 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_md5 s_singleton;
+
+protected:
+ Create_func_md5() {}
+ virtual ~Create_func_md5() {}
+};
+
+
+class Create_func_monthname : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_monthname s_singleton;
+
+protected:
+ Create_func_monthname() {}
+ virtual ~Create_func_monthname() {}
+};
+
+
+class Create_func_name_const : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_name_const s_singleton;
+
+protected:
+ Create_func_name_const() {}
+ virtual ~Create_func_name_const() {}
+};
+
+
+class Create_func_nullif : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_nullif s_singleton;
+
+protected:
+ Create_func_nullif() {}
+ virtual ~Create_func_nullif() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_numgeometries : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_numgeometries s_singleton;
+
+protected:
+ Create_func_numgeometries() {}
+ virtual ~Create_func_numgeometries() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_numinteriorring : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_numinteriorring s_singleton;
+
+protected:
+ Create_func_numinteriorring() {}
+ virtual ~Create_func_numinteriorring() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_numpoints : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_numpoints s_singleton;
+
+protected:
+ Create_func_numpoints() {}
+ virtual ~Create_func_numpoints() {}
+};
+#endif
+
+
+class Create_func_oct : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_oct s_singleton;
+
+protected:
+ Create_func_oct() {}
+ virtual ~Create_func_oct() {}
+};
+
+
+class Create_func_ord : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_ord s_singleton;
+
+protected:
+ Create_func_ord() {}
+ virtual ~Create_func_ord() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_mbr_overlaps : public Create_func_arg2
+{
+ public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_mbr_overlaps s_singleton;
+
+ protected:
+ Create_func_mbr_overlaps() {}
+ virtual ~Create_func_mbr_overlaps() {}
+};
+
+
+class Create_func_overlaps : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_overlaps s_singleton;
+
+protected:
+ Create_func_overlaps() {}
+ virtual ~Create_func_overlaps() {}
+};
+#endif
+
+
+class Create_func_period_add : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_period_add s_singleton;
+
+protected:
+ Create_func_period_add() {}
+ virtual ~Create_func_period_add() {}
+};
+
+
+class Create_func_period_diff : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_period_diff s_singleton;
+
+protected:
+ Create_func_period_diff() {}
+ virtual ~Create_func_period_diff() {}
+};
+
+
+class Create_func_pi : public Create_func_arg0
+{
+public:
+ virtual Item *create_builder(THD *thd);
+
+ static Create_func_pi s_singleton;
+
+protected:
+ Create_func_pi() {}
+ virtual ~Create_func_pi() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_pointn : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_pointn s_singleton;
+
+protected:
+ Create_func_pointn() {}
+ virtual ~Create_func_pointn() {}
+};
+#endif
+
+
+class Create_func_pow : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_pow s_singleton;
+
+protected:
+ Create_func_pow() {}
+ virtual ~Create_func_pow() {}
+};
+
+
+class Create_func_quote : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_quote s_singleton;
+
+protected:
+ Create_func_quote() {}
+ virtual ~Create_func_quote() {}
+};
+
+
+class Create_func_regexp_instr : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_regexp_instr s_singleton;
+
+protected:
+ Create_func_regexp_instr() {}
+ virtual ~Create_func_regexp_instr() {}
+};
+
+
+class Create_func_regexp_replace : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_regexp_replace s_singleton;
+
+protected:
+ Create_func_regexp_replace() {}
+ virtual ~Create_func_regexp_replace() {}
+};
+
+
+class Create_func_regexp_substr : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_regexp_substr s_singleton;
+
+protected:
+ Create_func_regexp_substr() {}
+ virtual ~Create_func_regexp_substr() {}
+};
+
+
+class Create_func_radians : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_radians s_singleton;
+
+protected:
+ Create_func_radians() {}
+ virtual ~Create_func_radians() {}
+};
+
+
+class Create_func_rand : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_rand s_singleton;
+
+protected:
+ Create_func_rand() {}
+ virtual ~Create_func_rand() {}
+};
+
+
+class Create_func_release_lock : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_release_lock s_singleton;
+
+protected:
+ Create_func_release_lock() {}
+ virtual ~Create_func_release_lock() {}
+};
+
+
+class Create_func_reverse : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_reverse s_singleton;
+
+protected:
+ Create_func_reverse() {}
+ virtual ~Create_func_reverse() {}
+};
+
+
+class Create_func_round : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_round s_singleton;
+
+protected:
+ Create_func_round() {}
+ virtual ~Create_func_round() {}
+};
+
+
+class Create_func_rpad : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_rpad s_singleton;
+
+protected:
+ Create_func_rpad() {}
+ virtual ~Create_func_rpad() {}
+};
+
+
+class Create_func_rtrim : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_rtrim s_singleton;
+
+protected:
+ Create_func_rtrim() {}
+ virtual ~Create_func_rtrim() {}
+};
+
+
+class Create_func_sec_to_time : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_sec_to_time s_singleton;
+
+protected:
+ Create_func_sec_to_time() {}
+ virtual ~Create_func_sec_to_time() {}
+};
+
+
+class Create_func_sha : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_sha s_singleton;
+
+protected:
+ Create_func_sha() {}
+ virtual ~Create_func_sha() {}
+};
+
+
+class Create_func_sha2 : public Create_func_arg2
+{
+public:
+ virtual Item* create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_sha2 s_singleton;
+
+protected:
+ Create_func_sha2() {}
+ virtual ~Create_func_sha2() {}
+};
+
+
+class Create_func_sign : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_sign s_singleton;
+
+protected:
+ Create_func_sign() {}
+ virtual ~Create_func_sign() {}
+};
+
+
+class Create_func_sin : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_sin s_singleton;
+
+protected:
+ Create_func_sin() {}
+ virtual ~Create_func_sin() {}
+};
+
+
+class Create_func_sleep : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_sleep s_singleton;
+
+protected:
+ Create_func_sleep() {}
+ virtual ~Create_func_sleep() {}
+};
+
+
+class Create_func_soundex : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_soundex s_singleton;
+
+protected:
+ Create_func_soundex() {}
+ virtual ~Create_func_soundex() {}
+};
+
+
+class Create_func_space : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_space s_singleton;
+
+protected:
+ Create_func_space() {}
+ virtual ~Create_func_space() {}
+};
+
+
+class Create_func_sqrt : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_sqrt s_singleton;
+
+protected:
+ Create_func_sqrt() {}
+ virtual ~Create_func_sqrt() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_srid : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_srid s_singleton;
+
+protected:
+ Create_func_srid() {}
+ virtual ~Create_func_srid() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_startpoint : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_startpoint s_singleton;
+
+protected:
+ Create_func_startpoint() {}
+ virtual ~Create_func_startpoint() {}
+};
+#endif
+
+
+class Create_func_str_to_date : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_str_to_date s_singleton;
+
+protected:
+ Create_func_str_to_date() {}
+ virtual ~Create_func_str_to_date() {}
+};
+
+
+class Create_func_strcmp : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_strcmp s_singleton;
+
+protected:
+ Create_func_strcmp() {}
+ virtual ~Create_func_strcmp() {}
+};
+
+
+class Create_func_substr_index : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_substr_index s_singleton;
+
+protected:
+ Create_func_substr_index() {}
+ virtual ~Create_func_substr_index() {}
+};
+
+
+class Create_func_subtime : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_subtime s_singleton;
+
+protected:
+ Create_func_subtime() {}
+ virtual ~Create_func_subtime() {}
+};
+
+
+class Create_func_tan : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_tan s_singleton;
+
+protected:
+ Create_func_tan() {}
+ virtual ~Create_func_tan() {}
+};
+
+
+class Create_func_time_format : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_time_format s_singleton;
+
+protected:
+ Create_func_time_format() {}
+ virtual ~Create_func_time_format() {}
+};
+
+
+class Create_func_time_to_sec : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_time_to_sec s_singleton;
+
+protected:
+ Create_func_time_to_sec() {}
+ virtual ~Create_func_time_to_sec() {}
+};
+
+
+class Create_func_timediff : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_timediff s_singleton;
+
+protected:
+ Create_func_timediff() {}
+ virtual ~Create_func_timediff() {}
+};
+
+
+class Create_func_to_base64 : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_to_base64 s_singleton;
+
+protected:
+ Create_func_to_base64() {}
+ virtual ~Create_func_to_base64() {}
+};
+
+
+class Create_func_to_days : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_to_days s_singleton;
+
+protected:
+ Create_func_to_days() {}
+ virtual ~Create_func_to_days() {}
+};
+
+class Create_func_to_seconds : public Create_func_arg1
+{
+public:
+ virtual Item* create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_to_seconds s_singleton;
+
+protected:
+ Create_func_to_seconds() {}
+ virtual ~Create_func_to_seconds() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_touches : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_touches s_singleton;
+
+protected:
+ Create_func_touches() {}
+ virtual ~Create_func_touches() {}
+};
+#endif
+
+
+class Create_func_ucase : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_ucase s_singleton;
+
+protected:
+ Create_func_ucase() {}
+ virtual ~Create_func_ucase() {}
+};
+
+
+class Create_func_uncompress : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_uncompress s_singleton;
+
+protected:
+ Create_func_uncompress() {}
+ virtual ~Create_func_uncompress() {}
+};
+
+
+class Create_func_uncompressed_length : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_uncompressed_length s_singleton;
+
+protected:
+ Create_func_uncompressed_length() {}
+ virtual ~Create_func_uncompressed_length() {}
+};
+
+
+class Create_func_unhex : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_unhex s_singleton;
+
+protected:
+ Create_func_unhex() {}
+ virtual ~Create_func_unhex() {}
+};
+
+
+class Create_func_unix_timestamp : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_unix_timestamp s_singleton;
+
+protected:
+ Create_func_unix_timestamp() {}
+ virtual ~Create_func_unix_timestamp() {}
+};
+
+
+class Create_func_uuid : public Create_func_arg0
+{
+public:
+ virtual Item *create_builder(THD *thd);
+
+ static Create_func_uuid s_singleton;
+
+protected:
+ Create_func_uuid() {}
+ virtual ~Create_func_uuid() {}
+};
+
+
+class Create_func_uuid_short : public Create_func_arg0
+{
+public:
+ virtual Item *create_builder(THD *thd);
+
+ static Create_func_uuid_short s_singleton;
+
+protected:
+ Create_func_uuid_short() {}
+ virtual ~Create_func_uuid_short() {}
+};
+
+
+class Create_func_version : public Create_func_arg0
+{
+public:
+ virtual Item *create_builder(THD *thd);
+
+ static Create_func_version s_singleton;
+
+protected:
+ Create_func_version() {}
+ virtual ~Create_func_version() {}
+};
+
+
+class Create_func_weekday : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_weekday s_singleton;
+
+protected:
+ Create_func_weekday() {}
+ virtual ~Create_func_weekday() {}
+};
+
+
+class Create_func_weekofyear : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_weekofyear s_singleton;
+
+protected:
+ Create_func_weekofyear() {}
+ virtual ~Create_func_weekofyear() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_mbr_within : public Create_func_arg2
+{
+ public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_mbr_within s_singleton;
+
+ protected:
+ Create_func_mbr_within() {}
+ virtual ~Create_func_mbr_within() {}
+};
+
+
+class Create_func_within : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_within s_singleton;
+
+protected:
+ Create_func_within() {}
+ virtual ~Create_func_within() {}
+};
+#endif
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_x : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_x s_singleton;
+
+protected:
+ Create_func_x() {}
+ virtual ~Create_func_x() {}
+};
+#endif
+
+
+class Create_func_xml_extractvalue : public Create_func_arg2
+{
+public:
+ virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
+
+ static Create_func_xml_extractvalue s_singleton;
+
+protected:
+ Create_func_xml_extractvalue() {}
+ virtual ~Create_func_xml_extractvalue() {}
+};
+
+
+class Create_func_xml_update : public Create_func_arg3
+{
+public:
+ virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
+
+ static Create_func_xml_update s_singleton;
+
+protected:
+ Create_func_xml_update() {}
+ virtual ~Create_func_xml_update() {}
+};
+
+
+#ifdef HAVE_SPATIAL
+class Create_func_y : public Create_func_arg1
+{
+public:
+ virtual Item *create_1_arg(THD *thd, Item *arg1);
+
+ static Create_func_y s_singleton;
+
+protected:
+ Create_func_y() {}
+ virtual ~Create_func_y() {}
+};
+#endif
+
+
+class Create_func_year_week : public Create_native_func
+{
+public:
+ virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ static Create_func_year_week s_singleton;
+
+protected:
+ Create_func_year_week() {}
+ virtual ~Create_func_year_week() {}
+};
+
+
+/*
+=============================================================================
+ IMPLEMENTATION
+=============================================================================
+*/
+
+/**
+ Checks if there are named parameters in a parameter list.
+ The syntax to name parameters in a function call is as follow:
+ <code>foo(expr AS named, expr named, expr AS "named", expr "named")</code>
+ @param params The parameter list, can be null
+ @return true if one or more parameter is named
+*/
+static bool has_named_parameters(List<Item> *params)
+{
+ if (params)
+ {
+ Item *param;
+ List_iterator<Item> it(*params);
+ while ((param= it++))
+ {
+ if (! param->is_autogenerated_name)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifndef HAVE_SPATIAL
+Create_func_no_geom Create_func_no_geom::s_singleton;
+
+Item*
+Create_func_no_geom::create_func(THD * /* unused */,
+ LEX_STRING /* unused */,
+ List<Item> * /* unused */)
+{
+ /* FIXME: error message can't be translated. */
+ my_error(ER_FEATURE_DISABLED, MYF(0),
+ sym_group_geom.name, sym_group_geom.needed_define);
+ return NULL;
+}
+#endif
+
+
+Item*
+Create_qfunc::create_func(THD *thd, LEX_STRING name, List<Item> *item_list)
+{
+ LEX_STRING db;
+
+ if (! thd->db && ! thd->lex->sphead)
+ {
+ /*
+ The proper error message should be in the lines of:
+ Can't resolve <name>() to a function call,
+ because this function:
+ - is not a native function,
+ - is not a user defined function,
+ - can not match a qualified (read: stored) function
+ since no database is selected.
+ Reusing ER_SP_DOES_NOT_EXIST have a message consistent with
+ the case when a default database exist, see Create_sp_func::create().
+ */
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
+ "FUNCTION", name.str);
+ return NULL;
+ }
+
+ if (thd->lex->copy_db_to(&db.str, &db.length))
+ return NULL;
+
+ return create_with_db(thd, db, name, false, item_list);
+}
+
+
+#ifdef HAVE_DLOPEN
+Create_udf_func Create_udf_func::s_singleton;
+
+Item*
+Create_udf_func::create_func(THD *thd, LEX_STRING name, List<Item> *item_list)
+{
+ udf_func *udf= find_udf(name.str, name.length);
+ DBUG_ASSERT(udf);
+ return create(thd, udf, item_list);
+}
+
+
+Item*
+Create_udf_func::create(THD *thd, udf_func *udf, List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ DBUG_ENTER("Create_udf_func::create");
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_UDF);
+
+ DBUG_ASSERT( (udf->type == UDFTYPE_FUNCTION)
+ || (udf->type == UDFTYPE_AGGREGATE));
+
+ switch(udf->returns) {
+ case STRING_RESULT:
+ {
+ if (udf->type == UDFTYPE_FUNCTION)
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_func_udf_str(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_func_udf_str(udf);
+ }
+ else
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_sum_udf_str(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_sum_udf_str(udf);
+ }
+ break;
+ }
+ case REAL_RESULT:
+ {
+ if (udf->type == UDFTYPE_FUNCTION)
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_func_udf_float(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_func_udf_float(udf);
+ }
+ else
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_sum_udf_float(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_sum_udf_float(udf);
+ }
+ break;
+ }
+ case INT_RESULT:
+ {
+ if (udf->type == UDFTYPE_FUNCTION)
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_func_udf_int(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_func_udf_int(udf);
+ }
+ else
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_sum_udf_int(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_sum_udf_int(udf);
+ }
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ if (udf->type == UDFTYPE_FUNCTION)
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_func_udf_decimal(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_func_udf_decimal(udf);
+ }
+ else
+ {
+ if (arg_count)
+ func= new (thd->mem_root) Item_sum_udf_decimal(udf, *item_list);
+ else
+ func= new (thd->mem_root) Item_sum_udf_decimal(udf);
+ }
+ break;
+ }
+ default:
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "UDF return type");
+ }
+ }
+ thd->lex->safe_to_cache_query= 0;
+ DBUG_RETURN(func);
+}
+#endif
+
+
+Create_sp_func Create_sp_func::s_singleton;
+
+Item*
+Create_sp_func::create_with_db(THD *thd, LEX_STRING db, LEX_STRING name,
+ bool use_explicit_name, List<Item> *item_list)
+{
+ int arg_count= 0;
+ Item *func= NULL;
+ LEX *lex= thd->lex;
+ sp_name *qname;
+
+ if (has_named_parameters(item_list))
+ {
+ /*
+ The syntax "db.foo(expr AS p1, expr AS p2, ...) is invalid,
+ and has been rejected during syntactic parsing already,
+ because a stored function call may not have named parameters.
+
+ The syntax "foo(expr AS p1, expr AS p2, ...)" is correct,
+ because it can refer to a User Defined Function call.
+ For a Stored Function however, this has no semantic.
+ */
+ my_error(ER_WRONG_PARAMETERS_TO_STORED_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ qname= new (thd->mem_root) sp_name(db, name, use_explicit_name);
+ qname->init_qname(thd);
+ sp_add_used_routine(lex, thd, qname, TYPE_ENUM_FUNCTION);
+
+ if (arg_count > 0)
+ func= new (thd->mem_root) Item_func_sp(lex->current_context(), qname,
+ *item_list);
+ else
+ func= new (thd->mem_root) Item_func_sp(lex->current_context(), qname);
+
+ lex->safe_to_cache_query= 0;
+ return func;
+}
+
+
+Item*
+Create_native_func::create_func(THD *thd, LEX_STRING name, List<Item> *item_list)
+{
+ if (has_named_parameters(item_list))
+ {
+ my_error(ER_WRONG_PARAMETERS_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return create_native(thd, name, item_list);
+}
+
+
+Item*
+Create_func_arg0::create_func(THD *thd, LEX_STRING name, List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count != 0)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return create_builder(thd);
+}
+
+
+Item*
+Create_func_arg1::create_func(THD *thd, LEX_STRING name, List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list)
+ arg_count= item_list->elements;
+
+ if (arg_count != 1)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ Item *param_1= item_list->pop();
+
+ if (! param_1->is_autogenerated_name)
+ {
+ my_error(ER_WRONG_PARAMETERS_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return create_1_arg(thd, param_1);
+}
+
+
+Item*
+Create_func_arg2::create_func(THD *thd, LEX_STRING name, List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list)
+ arg_count= item_list->elements;
+
+ if (arg_count != 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+
+ if ( (! param_1->is_autogenerated_name)
+ || (! param_2->is_autogenerated_name))
+ {
+ my_error(ER_WRONG_PARAMETERS_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return create_2_arg(thd, param_1, param_2);
+}
+
+
+Item*
+Create_func_arg3::create_func(THD *thd, LEX_STRING name, List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list)
+ arg_count= item_list->elements;
+
+ if (arg_count != 3)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ Item *param_3= item_list->pop();
+
+ if ( (! param_1->is_autogenerated_name)
+ || (! param_2->is_autogenerated_name)
+ || (! param_3->is_autogenerated_name))
+ {
+ my_error(ER_WRONG_PARAMETERS_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return create_3_arg(thd, param_1, param_2, param_3);
+}
+
+
+Create_func_abs Create_func_abs::s_singleton;
+
+Item*
+Create_func_abs::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_abs(arg1);
+}
+
+
+Create_func_acos Create_func_acos::s_singleton;
+
+Item*
+Create_func_acos::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_acos(arg1);
+}
+
+
+Create_func_addtime Create_func_addtime::s_singleton;
+
+Item*
+Create_func_addtime::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_add_time(arg1, arg2, 0, 0);
+}
+
+
+Create_func_aes_encrypt Create_func_aes_encrypt::s_singleton;
+
+Item*
+Create_func_aes_encrypt::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_aes_encrypt(arg1, arg2);
+}
+
+
+Create_func_aes_decrypt Create_func_aes_decrypt::s_singleton;
+
+Item*
+Create_func_aes_decrypt::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_aes_decrypt(arg1, arg2);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_area Create_func_area::s_singleton;
+
+Item*
+Create_func_area::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_area(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_as_wkb Create_func_as_wkb::s_singleton;
+
+Item*
+Create_func_as_wkb::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_as_wkb(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_as_wkt Create_func_as_wkt::s_singleton;
+
+Item*
+Create_func_as_wkt::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_as_wkt(arg1);
+}
+#endif
+
+
+Create_func_asin Create_func_asin::s_singleton;
+
+Item*
+Create_func_asin::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_asin(arg1);
+}
+
+
+Create_func_atan Create_func_atan::s_singleton;
+
+Item*
+Create_func_atan::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item* func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_atan(param_1);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_atan(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_benchmark Create_func_benchmark::s_singleton;
+
+Item*
+Create_func_benchmark::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ return new (thd->mem_root) Item_func_benchmark(arg1, arg2);
+}
+
+
+Create_func_bin Create_func_bin::s_singleton;
+
+Item*
+Create_func_bin::create_1_arg(THD *thd, Item *arg1)
+{
+ Item *i10= new (thd->mem_root) Item_int((int32) 10,2);
+ Item *i2= new (thd->mem_root) Item_int((int32) 2,1);
+ return new (thd->mem_root) Item_func_conv(arg1, i10, i2);
+}
+
+
+Create_func_binlog_gtid_pos Create_func_binlog_gtid_pos::s_singleton;
+
+Item*
+Create_func_binlog_gtid_pos::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+#ifdef HAVE_REPLICATION
+ if (!mysql_bin_log.is_open())
+#endif
+ {
+ my_error(ER_NO_BINARY_LOGGING, MYF(0));
+ return NULL;
+ }
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ return new (thd->mem_root) Item_func_binlog_gtid_pos(arg1, arg2);
+}
+
+
+Create_func_bit_count Create_func_bit_count::s_singleton;
+
+Item*
+Create_func_bit_count::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_bit_count(arg1);
+}
+
+
+Create_func_bit_length Create_func_bit_length::s_singleton;
+
+Item*
+Create_func_bit_length::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_bit_length(arg1);
+}
+
+
+Create_func_ceiling Create_func_ceiling::s_singleton;
+
+Item*
+Create_func_ceiling::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_ceiling(arg1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_centroid Create_func_centroid::s_singleton;
+
+Item*
+Create_func_centroid::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_centroid(arg1);
+}
+#endif
+
+
+Create_func_char_length Create_func_char_length::s_singleton;
+
+Item*
+Create_func_char_length::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_char_length(arg1);
+}
+
+
+Create_func_coercibility Create_func_coercibility::s_singleton;
+
+Item*
+Create_func_coercibility::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_coercibility(arg1);
+}
+
+
+Create_func_dyncol_check Create_func_dyncol_check::s_singleton;
+
+Item*
+Create_func_dyncol_check::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_dyncol_check(arg1);
+}
+
+Create_func_dyncol_exists Create_func_dyncol_exists::s_singleton;
+
+Item*
+Create_func_dyncol_exists::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_dyncol_exists(arg1, arg2);
+}
+
+Create_func_dyncol_list Create_func_dyncol_list::s_singleton;
+
+Item*
+Create_func_dyncol_list::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_dyncol_list(arg1);
+}
+
+Create_func_dyncol_json Create_func_dyncol_json::s_singleton;
+
+Item*
+Create_func_dyncol_json::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_dyncol_json(arg1);
+}
+
+Create_func_concat Create_func_concat::s_singleton;
+
+Item*
+Create_func_concat::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 1)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return new (thd->mem_root) Item_func_concat(*item_list);
+}
+
+Create_func_decode_histogram Create_func_decode_histogram::s_singleton;
+
+Item *
+Create_func_decode_histogram::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_decode_histogram(arg1, arg2);
+}
+
+Create_func_concat_ws Create_func_concat_ws::s_singleton;
+
+Item*
+Create_func_concat_ws::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ /* "WS" stands for "With Separator": this function takes 2+ arguments */
+ if (arg_count < 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return new (thd->mem_root) Item_func_concat_ws(*item_list);
+}
+
+
+Create_func_compress Create_func_compress::s_singleton;
+
+Item*
+Create_func_compress::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_compress(arg1);
+}
+
+
+Create_func_connection_id Create_func_connection_id::s_singleton;
+
+Item*
+Create_func_connection_id::create_builder(THD *thd)
+{
+ thd->lex->safe_to_cache_query= 0;
+ return new (thd->mem_root) Item_func_connection_id();
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_mbr_contains Create_func_mbr_contains::s_singleton;
+
+Item*
+Create_func_mbr_contains::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2,
+ Item_func::SP_CONTAINS_FUNC);
+}
+
+
+Create_func_contains Create_func_contains::s_singleton;
+
+Item*
+Create_func_contains::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_CONTAINS_FUNC);
+}
+#endif
+
+
+Create_func_conv Create_func_conv::s_singleton;
+
+Item*
+Create_func_conv::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_conv(arg1, arg2, arg3);
+}
+
+
+Create_func_convert_tz Create_func_convert_tz::s_singleton;
+
+Item*
+Create_func_convert_tz::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_convert_tz(arg1, arg2, arg3);
+}
+
+
+Create_func_cos Create_func_cos::s_singleton;
+
+Item*
+Create_func_cos::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_cos(arg1);
+}
+
+
+Create_func_cot Create_func_cot::s_singleton;
+
+Item*
+Create_func_cot::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_cot(arg1);
+}
+
+
+Create_func_crc32 Create_func_crc32::s_singleton;
+
+Item*
+Create_func_crc32::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_crc32(arg1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_crosses Create_func_crosses::s_singleton;
+
+Item*
+Create_func_crosses::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_CROSSES_FUNC);
+}
+#endif
+
+
+Create_func_date_format Create_func_date_format::s_singleton;
+
+Item*
+Create_func_date_format::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_date_format(arg1, arg2, 0);
+}
+
+
+Create_func_datediff Create_func_datediff::s_singleton;
+
+Item*
+Create_func_datediff::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ Item *i1= new (thd->mem_root) Item_func_to_days(arg1);
+ Item *i2= new (thd->mem_root) Item_func_to_days(arg2);
+
+ return new (thd->mem_root) Item_func_minus(i1, i2);
+}
+
+
+Create_func_dayname Create_func_dayname::s_singleton;
+
+Item*
+Create_func_dayname::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_dayname(arg1);
+}
+
+
+Create_func_dayofmonth Create_func_dayofmonth::s_singleton;
+
+Item*
+Create_func_dayofmonth::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_dayofmonth(arg1);
+}
+
+
+Create_func_dayofweek Create_func_dayofweek::s_singleton;
+
+Item*
+Create_func_dayofweek::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_weekday(arg1, 1);
+}
+
+
+Create_func_dayofyear Create_func_dayofyear::s_singleton;
+
+Item*
+Create_func_dayofyear::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_dayofyear(arg1);
+}
+
+
+Create_func_decode Create_func_decode::s_singleton;
+
+Item*
+Create_func_decode::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_decode(arg1, arg2);
+}
+
+
+Create_func_degrees Create_func_degrees::s_singleton;
+
+Item*
+Create_func_degrees::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_units((char*) "degrees", arg1,
+ 180/M_PI, 0.0);
+}
+
+
+Create_func_des_decrypt Create_func_des_decrypt::s_singleton;
+
+Item*
+Create_func_des_decrypt::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_des_decrypt(param_1);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_des_decrypt(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_des_encrypt Create_func_des_encrypt::s_singleton;
+
+Item*
+Create_func_des_encrypt::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_des_encrypt(param_1);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_des_encrypt(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_dimension Create_func_dimension::s_singleton;
+
+Item*
+Create_func_dimension::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_dimension(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_mbr_disjoint Create_func_mbr_disjoint::s_singleton;
+
+Item*
+Create_func_mbr_disjoint::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2,
+ Item_func::SP_DISJOINT_FUNC);
+}
+
+
+Create_func_disjoint Create_func_disjoint::s_singleton;
+
+Item*
+Create_func_disjoint::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_DISJOINT_FUNC);
+}
+
+
+Create_func_distance Create_func_distance::s_singleton;
+
+Item*
+Create_func_distance::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_distance(arg1, arg2);
+}
+#endif
+
+
+Create_func_elt Create_func_elt::s_singleton;
+
+Item*
+Create_func_elt::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return new (thd->mem_root) Item_func_elt(*item_list);
+}
+
+
+Create_func_encode Create_func_encode::s_singleton;
+
+Item*
+Create_func_encode::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_encode(arg1, arg2);
+}
+
+
+Create_func_encrypt Create_func_encrypt::s_singleton;
+
+Item*
+Create_func_encrypt::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_encrypt(param_1);
+ thd->lex->uncacheable(UNCACHEABLE_RAND);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_encrypt(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_endpoint Create_func_endpoint::s_singleton;
+
+Item*
+Create_func_endpoint::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_spatial_decomp(arg1,
+ Item_func::SP_ENDPOINT);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_envelope Create_func_envelope::s_singleton;
+
+Item*
+Create_func_envelope::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_envelope(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_mbr_equals Create_func_mbr_equals::s_singleton;
+
+Item*
+Create_func_mbr_equals::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2,
+ Item_func::SP_EQUALS_FUNC);
+}
+
+
+Create_func_equals Create_func_equals::s_singleton;
+
+Item*
+Create_func_equals::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_EQUALS_FUNC);
+}
+#endif
+
+
+Create_func_exp Create_func_exp::s_singleton;
+
+Item*
+Create_func_exp::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_exp(arg1);
+}
+
+
+Create_func_export_set Create_func_export_set::s_singleton;
+
+Item*
+Create_func_export_set::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 3:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ Item *param_3= item_list->pop();
+ func= new (thd->mem_root) Item_func_export_set(param_1, param_2, param_3);
+ break;
+ }
+ case 4:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ Item *param_3= item_list->pop();
+ Item *param_4= item_list->pop();
+ func= new (thd->mem_root) Item_func_export_set(param_1, param_2, param_3,
+ param_4);
+ break;
+ }
+ case 5:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ Item *param_3= item_list->pop();
+ Item *param_4= item_list->pop();
+ Item *param_5= item_list->pop();
+ func= new (thd->mem_root) Item_func_export_set(param_1, param_2, param_3,
+ param_4, param_5);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_exteriorring Create_func_exteriorring::s_singleton;
+
+Item*
+Create_func_exteriorring::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_spatial_decomp(arg1,
+ Item_func::SP_EXTERIORRING);
+}
+#endif
+
+
+Create_func_field Create_func_field::s_singleton;
+
+Item*
+Create_func_field::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return new (thd->mem_root) Item_func_field(*item_list);
+}
+
+
+Create_func_find_in_set Create_func_find_in_set::s_singleton;
+
+Item*
+Create_func_find_in_set::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_find_in_set(arg1, arg2);
+}
+
+
+Create_func_floor Create_func_floor::s_singleton;
+
+Item*
+Create_func_floor::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_floor(arg1);
+}
+
+
+Create_func_format Create_func_format::s_singleton;
+
+Item*
+Create_func_format::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= item_list ? item_list->elements : 0;
+
+ switch (arg_count) {
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_format(param_1, param_2);
+ break;
+ }
+ case 3:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ Item *param_3= item_list->pop();
+ func= new (thd->mem_root) Item_func_format(param_1, param_2, param_3);
+ break;
+ }
+ default:
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+
+ return func;
+}
+
+
+Create_func_from_base64 Create_func_from_base64::s_singleton;
+
+
+Item *
+Create_func_from_base64::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_from_base64(arg1);
+}
+
+
+Create_func_found_rows Create_func_found_rows::s_singleton;
+
+Item*
+Create_func_found_rows::create_builder(THD *thd)
+{
+ DBUG_ENTER("Create_func_found_rows::create");
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->safe_to_cache_query= 0;
+ DBUG_RETURN(new (thd->mem_root) Item_func_found_rows());
+}
+
+
+Create_func_from_days Create_func_from_days::s_singleton;
+
+Item*
+Create_func_from_days::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_from_days(arg1);
+}
+
+
+Create_func_from_unixtime Create_func_from_unixtime::s_singleton;
+
+Item*
+Create_func_from_unixtime::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_from_unixtime(param_1);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ Item *ut= new (thd->mem_root) Item_func_from_unixtime(param_1);
+ func= new (thd->mem_root) Item_func_date_format(ut, param_2, 0);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_geometry_from_text Create_func_geometry_from_text::s_singleton;
+
+Item*
+Create_func_geometry_from_text::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_geometry_from_text(param_1);
+ thd->lex->uncacheable(UNCACHEABLE_RAND);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_geometry_from_text(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_geometry_from_wkb Create_func_geometry_from_wkb::s_singleton;
+
+Item*
+Create_func_geometry_from_wkb::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_geometry_from_wkb(param_1);
+ thd->lex->uncacheable(UNCACHEABLE_RAND);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_geometry_from_wkb(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_geometry_type Create_func_geometry_type::s_singleton;
+
+Item*
+Create_func_geometry_type::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_geometry_type(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_geometryn Create_func_geometryn::s_singleton;
+
+Item*
+Create_func_geometryn::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_decomp_n(arg1, arg2,
+ Item_func::SP_GEOMETRYN);
+}
+#endif
+
+
+Create_func_get_lock Create_func_get_lock::s_singleton;
+
+Item*
+Create_func_get_lock::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ return new (thd->mem_root) Item_func_get_lock(arg1, arg2);
+}
+
+
+#if defined(HAVE_SPATIAL) && !defined(DBUG_OFF)
+Create_func_gis_debug Create_func_gis_debug::s_singleton;
+
+Item*
+Create_func_gis_debug::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_gis_debug(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_glength Create_func_glength::s_singleton;
+
+Item*
+Create_func_glength::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_glength(arg1);
+}
+#endif
+
+
+Create_func_greatest Create_func_greatest::s_singleton;
+
+Item*
+Create_func_greatest::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return new (thd->mem_root) Item_func_max(*item_list);
+}
+
+
+Create_func_hex Create_func_hex::s_singleton;
+
+Item*
+Create_func_hex::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_hex(arg1);
+}
+
+
+Create_func_ifnull Create_func_ifnull::s_singleton;
+
+Item*
+Create_func_ifnull::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_ifnull(arg1, arg2);
+}
+
+
+Create_func_inet_ntoa Create_func_inet_ntoa::s_singleton;
+
+Item*
+Create_func_inet_ntoa::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_inet_ntoa(arg1);
+}
+
+
+Create_func_inet6_aton Create_func_inet6_aton::s_singleton;
+
+Item*
+Create_func_inet6_aton::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_inet6_aton(arg1);
+}
+
+
+Create_func_inet6_ntoa Create_func_inet6_ntoa::s_singleton;
+
+Item*
+Create_func_inet6_ntoa::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_inet6_ntoa(arg1);
+}
+
+
+Create_func_inet_aton Create_func_inet_aton::s_singleton;
+
+Item*
+Create_func_inet_aton::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_inet_aton(arg1);
+}
+
+
+Create_func_is_ipv4 Create_func_is_ipv4::s_singleton;
+
+Item*
+Create_func_is_ipv4::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_is_ipv4(arg1);
+}
+
+
+Create_func_is_ipv6 Create_func_is_ipv6::s_singleton;
+
+Item*
+Create_func_is_ipv6::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_is_ipv6(arg1);
+}
+
+
+Create_func_is_ipv4_compat Create_func_is_ipv4_compat::s_singleton;
+
+Item*
+Create_func_is_ipv4_compat::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_is_ipv4_compat(arg1);
+}
+
+
+Create_func_is_ipv4_mapped Create_func_is_ipv4_mapped::s_singleton;
+
+Item*
+Create_func_is_ipv4_mapped::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_is_ipv4_mapped(arg1);
+}
+
+
+Create_func_instr Create_func_instr::s_singleton;
+
+Item*
+Create_func_instr::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_locate(arg1, arg2);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_interiorringn Create_func_interiorringn::s_singleton;
+
+Item*
+Create_func_interiorringn::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_decomp_n(arg1, arg2,
+ Item_func::SP_INTERIORRINGN);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_mbr_intersects Create_func_mbr_intersects::s_singleton;
+
+Item*
+Create_func_mbr_intersects::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2,
+ Item_func::SP_INTERSECTS_FUNC);
+}
+
+
+Create_func_intersects Create_func_intersects::s_singleton;
+
+Item*
+Create_func_intersects::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_INTERSECTS_FUNC);
+}
+
+
+Create_func_intersection Create_func_intersection::s_singleton;
+
+Item*
+Create_func_intersection::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2,
+ Gcalc_function::op_intersection);
+}
+
+
+Create_func_difference Create_func_difference::s_singleton;
+
+Item*
+Create_func_difference::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2,
+ Gcalc_function::op_difference);
+}
+
+
+Create_func_union Create_func_union::s_singleton;
+
+Item*
+Create_func_union::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2,
+ Gcalc_function::op_union);
+}
+
+
+Create_func_symdifference Create_func_symdifference::s_singleton;
+
+Item*
+Create_func_symdifference::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2,
+ Gcalc_function::op_symdifference);
+}
+
+
+Create_func_buffer Create_func_buffer::s_singleton;
+
+Item*
+Create_func_buffer::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_buffer(arg1, arg2);
+}
+#endif /*HAVE_SPATAI*/
+
+
+Create_func_is_free_lock Create_func_is_free_lock::s_singleton;
+
+Item*
+Create_func_is_free_lock::create_1_arg(THD *thd, Item *arg1)
+{
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ return new (thd->mem_root) Item_func_is_free_lock(arg1);
+}
+
+
+Create_func_is_used_lock Create_func_is_used_lock::s_singleton;
+
+Item*
+Create_func_is_used_lock::create_1_arg(THD *thd, Item *arg1)
+{
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ return new (thd->mem_root) Item_func_is_used_lock(arg1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_isclosed Create_func_isclosed::s_singleton;
+
+Item*
+Create_func_isclosed::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_isclosed(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_isempty Create_func_isempty::s_singleton;
+
+Item*
+Create_func_isempty::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_isempty(arg1);
+}
+#endif
+
+
+Create_func_isnull Create_func_isnull::s_singleton;
+
+Item*
+Create_func_isnull::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_isnull(arg1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_issimple Create_func_issimple::s_singleton;
+
+Item*
+Create_func_issimple::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_issimple(arg1);
+}
+#endif
+
+
+Create_func_last_day Create_func_last_day::s_singleton;
+
+Item*
+Create_func_last_day::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_last_day(arg1);
+}
+
+
+Create_func_last_insert_id Create_func_last_insert_id::s_singleton;
+
+Item*
+Create_func_last_insert_id::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 0:
+ {
+ func= new (thd->mem_root) Item_func_last_insert_id();
+ thd->lex->safe_to_cache_query= 0;
+ break;
+ }
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_last_insert_id(param_1);
+ thd->lex->safe_to_cache_query= 0;
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_lcase Create_func_lcase::s_singleton;
+
+Item*
+Create_func_lcase::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_lcase(arg1);
+}
+
+
+Create_func_least Create_func_least::s_singleton;
+
+Item*
+Create_func_least::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return new (thd->mem_root) Item_func_min(*item_list);
+}
+
+
+Create_func_length Create_func_length::s_singleton;
+
+Item*
+Create_func_length::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_length(arg1);
+}
+
+
+#ifndef DBUG_OFF
+Create_func_like_range_min Create_func_like_range_min::s_singleton;
+
+Item*
+Create_func_like_range_min::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_like_range_min(arg1, arg2);
+}
+
+
+Create_func_like_range_max Create_func_like_range_max::s_singleton;
+
+Item*
+Create_func_like_range_max::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_like_range_max(arg1, arg2);
+}
+#endif
+
+
+Create_func_ln Create_func_ln::s_singleton;
+
+Item*
+Create_func_ln::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_ln(arg1);
+}
+
+
+Create_func_load_file Create_func_load_file::s_singleton;
+
+Item*
+Create_func_load_file::create_1_arg(THD *thd, Item *arg1)
+{
+ DBUG_ENTER("Create_func_load_file::create");
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ DBUG_RETURN(new (thd->mem_root) Item_load_file(arg1));
+}
+
+
+Create_func_locate Create_func_locate::s_singleton;
+
+Item*
+Create_func_locate::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ /* Yes, parameters in that order : 2, 1 */
+ func= new (thd->mem_root) Item_func_locate(param_2, param_1);
+ break;
+ }
+ case 3:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ Item *param_3= item_list->pop();
+ /* Yes, parameters in that order : 2, 1, 3 */
+ func= new (thd->mem_root) Item_func_locate(param_2, param_1, param_3);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_log Create_func_log::s_singleton;
+
+Item*
+Create_func_log::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_log(param_1);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_log(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_log10 Create_func_log10::s_singleton;
+
+Item*
+Create_func_log10::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_log10(arg1);
+}
+
+
+Create_func_log2 Create_func_log2::s_singleton;
+
+Item*
+Create_func_log2::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_log2(arg1);
+}
+
+
+Create_func_lpad Create_func_lpad::s_singleton;
+
+Item*
+Create_func_lpad::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_lpad(arg1, arg2, arg3);
+}
+
+
+Create_func_ltrim Create_func_ltrim::s_singleton;
+
+Item*
+Create_func_ltrim::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_ltrim(arg1);
+}
+
+
+Create_func_makedate Create_func_makedate::s_singleton;
+
+Item*
+Create_func_makedate::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_makedate(arg1, arg2);
+}
+
+
+Create_func_maketime Create_func_maketime::s_singleton;
+
+Item*
+Create_func_maketime::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_maketime(arg1, arg2, arg3);
+}
+
+
+Create_func_make_set Create_func_make_set::s_singleton;
+
+Item*
+Create_func_make_set::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return NULL;
+ }
+
+ return new (thd->mem_root) Item_func_make_set(*item_list);
+}
+
+
+Create_func_master_pos_wait Create_func_master_pos_wait::s_singleton;
+
+Item*
+Create_func_master_pos_wait::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 2 || arg_count > 4)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return func;
+ }
+
+ thd->lex->safe_to_cache_query= 0;
+
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ switch (arg_count) {
+ case 2:
+ {
+ func= new (thd->mem_root) Item_master_pos_wait(param_1, param_2);
+ break;
+ }
+ case 3:
+ {
+ Item *param_3= item_list->pop();
+ func= new (thd->mem_root) Item_master_pos_wait(param_1, param_2, param_3);
+ break;
+ }
+ case 4:
+ {
+ Item *param_3= item_list->pop();
+ Item *param_4= item_list->pop();
+ func= new (thd->mem_root) Item_master_pos_wait(param_1, param_2, param_3,
+ param_4);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_master_gtid_wait Create_func_master_gtid_wait::s_singleton;
+
+Item*
+Create_func_master_gtid_wait::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ if (arg_count < 1 || arg_count > 2)
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ return func;
+ }
+
+ thd->lex->safe_to_cache_query= 0;
+
+ Item *param_1= item_list->pop();
+ switch (arg_count) {
+ case 1:
+ {
+ func= new (thd->mem_root) Item_master_gtid_wait(param_1);
+ break;
+ }
+ case 2:
+ {
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_master_gtid_wait(param_1, param_2);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_md5 Create_func_md5::s_singleton;
+
+Item*
+Create_func_md5::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_md5(arg1);
+}
+
+
+Create_func_monthname Create_func_monthname::s_singleton;
+
+Item*
+Create_func_monthname::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_monthname(arg1);
+}
+
+
+Create_func_name_const Create_func_name_const::s_singleton;
+
+Item*
+Create_func_name_const::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_name_const(arg1, arg2);
+}
+
+
+Create_func_nullif Create_func_nullif::s_singleton;
+
+Item*
+Create_func_nullif::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_nullif(arg1, arg2);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_numgeometries Create_func_numgeometries::s_singleton;
+
+Item*
+Create_func_numgeometries::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_numgeometries(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_numinteriorring Create_func_numinteriorring::s_singleton;
+
+Item*
+Create_func_numinteriorring::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_numinteriorring(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_numpoints Create_func_numpoints::s_singleton;
+
+Item*
+Create_func_numpoints::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_numpoints(arg1);
+}
+#endif
+
+
+Create_func_oct Create_func_oct::s_singleton;
+
+Item*
+Create_func_oct::create_1_arg(THD *thd, Item *arg1)
+{
+ Item *i10= new (thd->mem_root) Item_int((int32) 10,2);
+ Item *i8= new (thd->mem_root) Item_int((int32) 8,1);
+ return new (thd->mem_root) Item_func_conv(arg1, i10, i8);
+}
+
+
+Create_func_ord Create_func_ord::s_singleton;
+
+Item*
+Create_func_ord::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_ord(arg1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_mbr_overlaps Create_func_mbr_overlaps::s_singleton;
+
+Item*
+Create_func_mbr_overlaps::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2,
+ Item_func::SP_OVERLAPS_FUNC);
+}
+
+
+Create_func_overlaps Create_func_overlaps::s_singleton;
+
+Item*
+Create_func_overlaps::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_OVERLAPS_FUNC);
+}
+#endif
+
+
+Create_func_period_add Create_func_period_add::s_singleton;
+
+Item*
+Create_func_period_add::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_period_add(arg1, arg2);
+}
+
+
+Create_func_period_diff Create_func_period_diff::s_singleton;
+
+Item*
+Create_func_period_diff::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_period_diff(arg1, arg2);
+}
+
+
+Create_func_pi Create_func_pi::s_singleton;
+
+Item*
+Create_func_pi::create_builder(THD *thd)
+{
+ return new (thd->mem_root) Item_static_float_func("pi()", M_PI, 6, 8);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_pointn Create_func_pointn::s_singleton;
+
+Item*
+Create_func_pointn::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_decomp_n(arg1, arg2,
+ Item_func::SP_POINTN);
+}
+#endif
+
+
+Create_func_pow Create_func_pow::s_singleton;
+
+Item*
+Create_func_pow::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_pow(arg1, arg2);
+}
+
+
+Create_func_quote Create_func_quote::s_singleton;
+
+Item*
+Create_func_quote::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_quote(arg1);
+}
+
+
+Create_func_regexp_instr Create_func_regexp_instr::s_singleton;
+
+Item*
+Create_func_regexp_instr::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_regexp_instr(arg1, arg2);
+}
+
+
+Create_func_regexp_replace Create_func_regexp_replace::s_singleton;
+
+Item*
+Create_func_regexp_replace::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_regexp_replace(arg1, arg2, arg3);
+}
+
+
+Create_func_regexp_substr Create_func_regexp_substr::s_singleton;
+
+Item*
+Create_func_regexp_substr::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_regexp_substr(arg1, arg2);
+}
+
+
+Create_func_radians Create_func_radians::s_singleton;
+
+Item*
+Create_func_radians::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_units((char*) "radians", arg1,
+ M_PI/180, 0.0);
+}
+
+
+Create_func_rand Create_func_rand::s_singleton;
+
+Item*
+Create_func_rand::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ /*
+ When RAND() is binlogged, the seed is binlogged too. So the
+ sequence of random numbers is the same on a replication slave as
+ on the master. However, if several RAND() values are inserted
+ into a table, the order in which the rows are modified may differ
+ between master and slave, because the order is undefined. Hence,
+ the statement is unsafe to log in statement format.
+
+ For normal INSERT's this is howevever safe
+ */
+ if (thd->lex->sql_command != SQLCOM_INSERT)
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+
+ switch (arg_count) {
+ case 0:
+ {
+ func= new (thd->mem_root) Item_func_rand();
+ thd->lex->uncacheable(UNCACHEABLE_RAND);
+ break;
+ }
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_rand(param_1);
+ thd->lex->uncacheable(UNCACHEABLE_RAND);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_release_lock Create_func_release_lock::s_singleton;
+
+Item*
+Create_func_release_lock::create_1_arg(THD *thd, Item *arg1)
+{
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ return new (thd->mem_root) Item_func_release_lock(arg1);
+}
+
+
+Create_func_reverse Create_func_reverse::s_singleton;
+
+Item*
+Create_func_reverse::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_reverse(arg1);
+}
+
+
+Create_func_round Create_func_round::s_singleton;
+
+Item*
+Create_func_round::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ Item *i0 = new (thd->mem_root) Item_int((char*)"0", 0, 1);
+ func= new (thd->mem_root) Item_func_round(param_1, i0, 0);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_round(param_1, param_2, 0);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_rpad Create_func_rpad::s_singleton;
+
+Item*
+Create_func_rpad::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_rpad(arg1, arg2, arg3);
+}
+
+
+Create_func_rtrim Create_func_rtrim::s_singleton;
+
+Item*
+Create_func_rtrim::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_rtrim(arg1);
+}
+
+
+Create_func_sec_to_time Create_func_sec_to_time::s_singleton;
+
+Item*
+Create_func_sec_to_time::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_sec_to_time(arg1);
+}
+
+
+Create_func_sha Create_func_sha::s_singleton;
+
+Item*
+Create_func_sha::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_sha(arg1);
+}
+
+
+Create_func_sha2 Create_func_sha2::s_singleton;
+
+Item*
+Create_func_sha2::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_sha2(arg1, arg2);
+}
+
+
+Create_func_sign Create_func_sign::s_singleton;
+
+Item*
+Create_func_sign::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_sign(arg1);
+}
+
+
+Create_func_sin Create_func_sin::s_singleton;
+
+Item*
+Create_func_sin::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_sin(arg1);
+}
+
+
+Create_func_sleep Create_func_sleep::s_singleton;
+
+Item*
+Create_func_sleep::create_1_arg(THD *thd, Item *arg1)
+{
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ return new (thd->mem_root) Item_func_sleep(arg1);
+}
+
+
+Create_func_soundex Create_func_soundex::s_singleton;
+
+Item*
+Create_func_soundex::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_soundex(arg1);
+}
+
+
+Create_func_space Create_func_space::s_singleton;
+
+Item*
+Create_func_space::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_space(arg1);
+}
+
+
+Create_func_sqrt Create_func_sqrt::s_singleton;
+
+Item*
+Create_func_sqrt::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_sqrt(arg1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_srid Create_func_srid::s_singleton;
+
+Item*
+Create_func_srid::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_srid(arg1);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_startpoint Create_func_startpoint::s_singleton;
+
+Item*
+Create_func_startpoint::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_spatial_decomp(arg1,
+ Item_func::SP_STARTPOINT);
+}
+#endif
+
+
+Create_func_str_to_date Create_func_str_to_date::s_singleton;
+
+Item*
+Create_func_str_to_date::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_str_to_date(arg1, arg2);
+}
+
+
+Create_func_strcmp Create_func_strcmp::s_singleton;
+
+Item*
+Create_func_strcmp::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_strcmp(arg1, arg2);
+}
+
+
+Create_func_substr_index Create_func_substr_index::s_singleton;
+
+Item*
+Create_func_substr_index::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_substr_index(arg1, arg2, arg3);
+}
+
+
+Create_func_subtime Create_func_subtime::s_singleton;
+
+Item*
+Create_func_subtime::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_add_time(arg1, arg2, 0, 1);
+}
+
+
+Create_func_tan Create_func_tan::s_singleton;
+
+Item*
+Create_func_tan::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_tan(arg1);
+}
+
+
+Create_func_time_format Create_func_time_format::s_singleton;
+
+Item*
+Create_func_time_format::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_date_format(arg1, arg2, 1);
+}
+
+
+Create_func_time_to_sec Create_func_time_to_sec::s_singleton;
+
+Item*
+Create_func_time_to_sec::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_time_to_sec(arg1);
+}
+
+
+Create_func_timediff Create_func_timediff::s_singleton;
+
+Item*
+Create_func_timediff::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_timediff(arg1, arg2);
+}
+
+
+Create_func_to_base64 Create_func_to_base64::s_singleton;
+
+Item*
+Create_func_to_base64::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_to_base64(arg1);
+}
+
+
+Create_func_to_days Create_func_to_days::s_singleton;
+
+Item*
+Create_func_to_days::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_to_days(arg1);
+}
+
+
+Create_func_to_seconds Create_func_to_seconds::s_singleton;
+
+Item*
+Create_func_to_seconds::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_to_seconds(arg1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_touches Create_func_touches::s_singleton;
+
+Item*
+Create_func_touches::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_TOUCHES_FUNC);
+}
+#endif
+
+
+Create_func_ucase Create_func_ucase::s_singleton;
+
+Item*
+Create_func_ucase::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_ucase(arg1);
+}
+
+
+Create_func_uncompress Create_func_uncompress::s_singleton;
+
+Item*
+Create_func_uncompress::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_uncompress(arg1);
+}
+
+
+Create_func_uncompressed_length Create_func_uncompressed_length::s_singleton;
+
+Item*
+Create_func_uncompressed_length::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_uncompressed_length(arg1);
+}
+
+
+Create_func_unhex Create_func_unhex::s_singleton;
+
+Item*
+Create_func_unhex::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_unhex(arg1);
+}
+
+
+Create_func_unix_timestamp Create_func_unix_timestamp::s_singleton;
+
+Item*
+Create_func_unix_timestamp::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 0:
+ {
+ func= new (thd->mem_root) Item_func_unix_timestamp();
+ thd->lex->safe_to_cache_query= 0;
+ break;
+ }
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ func= new (thd->mem_root) Item_func_unix_timestamp(param_1);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+Create_func_uuid Create_func_uuid::s_singleton;
+
+Item*
+Create_func_uuid::create_builder(THD *thd)
+{
+ DBUG_ENTER("Create_func_uuid::create");
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->safe_to_cache_query= 0;
+ DBUG_RETURN(new (thd->mem_root) Item_func_uuid());
+}
+
+
+Create_func_uuid_short Create_func_uuid_short::s_singleton;
+
+Item*
+Create_func_uuid_short::create_builder(THD *thd)
+{
+ DBUG_ENTER("Create_func_uuid_short::create");
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ thd->lex->safe_to_cache_query= 0;
+ DBUG_RETURN(new (thd->mem_root) Item_func_uuid_short());
+}
+
+
+Create_func_version Create_func_version::s_singleton;
+
+Item*
+Create_func_version::create_builder(THD *thd)
+{
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ return new (thd->mem_root) Item_static_string_func("version()",
+ server_version,
+ (uint) strlen(server_version),
+ system_charset_info,
+ DERIVATION_SYSCONST);
+}
+
+
+Create_func_weekday Create_func_weekday::s_singleton;
+
+Item*
+Create_func_weekday::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_weekday(arg1, 0);
+}
+
+
+Create_func_weekofyear Create_func_weekofyear::s_singleton;
+
+Item*
+Create_func_weekofyear::create_1_arg(THD *thd, Item *arg1)
+{
+ Item *i1= new (thd->mem_root) Item_int((char*) "0", 3, 1);
+ return new (thd->mem_root) Item_func_week(arg1, i1);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_mbr_within Create_func_mbr_within::s_singleton;
+
+Item*
+Create_func_mbr_within::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2,
+ Item_func::SP_WITHIN_FUNC);
+}
+
+
+Create_func_within Create_func_within::s_singleton;
+
+Item*
+Create_func_within::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2,
+ Item_func::SP_WITHIN_FUNC);
+}
+#endif
+
+
+#ifdef HAVE_SPATIAL
+Create_func_x Create_func_x::s_singleton;
+
+Item*
+Create_func_x::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_x(arg1);
+}
+#endif
+
+
+Create_func_xml_extractvalue Create_func_xml_extractvalue::s_singleton;
+
+Item*
+Create_func_xml_extractvalue::create_2_arg(THD *thd, Item *arg1, Item *arg2)
+{
+ return new (thd->mem_root) Item_func_xml_extractvalue(arg1, arg2);
+}
+
+
+Create_func_xml_update Create_func_xml_update::s_singleton;
+
+Item*
+Create_func_xml_update::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
+{
+ return new (thd->mem_root) Item_func_xml_update(arg1, arg2, arg3);
+}
+
+
+#ifdef HAVE_SPATIAL
+Create_func_y Create_func_y::s_singleton;
+
+Item*
+Create_func_y::create_1_arg(THD *thd, Item *arg1)
+{
+ return new (thd->mem_root) Item_func_y(arg1);
+}
+#endif
+
+
+Create_func_year_week Create_func_year_week::s_singleton;
+
+Item*
+Create_func_year_week::create_native(THD *thd, LEX_STRING name,
+ List<Item> *item_list)
+{
+ Item *func= NULL;
+ int arg_count= 0;
+
+ if (item_list != NULL)
+ arg_count= item_list->elements;
+
+ switch (arg_count) {
+ case 1:
+ {
+ Item *param_1= item_list->pop();
+ Item *i0= new (thd->mem_root) Item_int((char*) "0", 0, 1);
+ func= new (thd->mem_root) Item_func_yearweek(param_1, i0);
+ break;
+ }
+ case 2:
+ {
+ Item *param_1= item_list->pop();
+ Item *param_2= item_list->pop();
+ func= new (thd->mem_root) Item_func_yearweek(param_1, param_2);
+ break;
+ }
+ default:
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
+ break;
+ }
+ }
+
+ return func;
+}
+
+
+struct Native_func_registry
+{
+ LEX_STRING name;
+ Create_func *builder;
+};
+
+#define BUILDER(F) & F::s_singleton
+
+#ifdef HAVE_SPATIAL
+ #define GEOM_BUILDER(F) & F::s_singleton
+#else
+ #define GEOM_BUILDER(F) & Create_func_no_geom::s_singleton
+#endif
+
+/*
+ MySQL native functions.
+ MAINTAINER:
+ - Keep sorted for human lookup. At runtime, a hash table is used.
+ - do **NOT** conditionally (#ifdef, #ifndef) define a function *NAME*:
+ doing so will cause user code that works against a --without-XYZ binary
+ to fail with name collisions against a --with-XYZ binary.
+ Use something similar to GEOM_BUILDER instead.
+ - keep 1 line per entry, it makes grep | sort easier
+*/
+
+static Native_func_registry func_array[] =
+{
+ { { C_STRING_WITH_LEN("ABS") }, BUILDER(Create_func_abs)},
+ { { C_STRING_WITH_LEN("ACOS") }, BUILDER(Create_func_acos)},
+ { { C_STRING_WITH_LEN("ADDTIME") }, BUILDER(Create_func_addtime)},
+ { { C_STRING_WITH_LEN("AES_DECRYPT") }, BUILDER(Create_func_aes_decrypt)},
+ { { C_STRING_WITH_LEN("AES_ENCRYPT") }, BUILDER(Create_func_aes_encrypt)},
+ { { C_STRING_WITH_LEN("AREA") }, GEOM_BUILDER(Create_func_area)},
+ { { C_STRING_WITH_LEN("ASBINARY") }, GEOM_BUILDER(Create_func_as_wkb)},
+ { { C_STRING_WITH_LEN("ASIN") }, BUILDER(Create_func_asin)},
+ { { C_STRING_WITH_LEN("ASTEXT") }, GEOM_BUILDER(Create_func_as_wkt)},
+ { { C_STRING_WITH_LEN("ASWKB") }, GEOM_BUILDER(Create_func_as_wkb)},
+ { { C_STRING_WITH_LEN("ASWKT") }, GEOM_BUILDER(Create_func_as_wkt)},
+ { { C_STRING_WITH_LEN("ATAN") }, BUILDER(Create_func_atan)},
+ { { C_STRING_WITH_LEN("ATAN2") }, BUILDER(Create_func_atan)},
+ { { C_STRING_WITH_LEN("BENCHMARK") }, BUILDER(Create_func_benchmark)},
+ { { C_STRING_WITH_LEN("BIN") }, BUILDER(Create_func_bin)},
+ { { C_STRING_WITH_LEN("BINLOG_GTID_POS") }, BUILDER(Create_func_binlog_gtid_pos)},
+ { { C_STRING_WITH_LEN("BIT_COUNT") }, BUILDER(Create_func_bit_count)},
+ { { C_STRING_WITH_LEN("BIT_LENGTH") }, BUILDER(Create_func_bit_length)},
+ { { C_STRING_WITH_LEN("BUFFER") }, GEOM_BUILDER(Create_func_buffer)},
+ { { C_STRING_WITH_LEN("CEIL") }, BUILDER(Create_func_ceiling)},
+ { { C_STRING_WITH_LEN("CEILING") }, BUILDER(Create_func_ceiling)},
+ { { C_STRING_WITH_LEN("CENTROID") }, GEOM_BUILDER(Create_func_centroid)},
+ { { C_STRING_WITH_LEN("CHARACTER_LENGTH") }, BUILDER(Create_func_char_length)},
+ { { C_STRING_WITH_LEN("CHAR_LENGTH") }, BUILDER(Create_func_char_length)},
+ { { C_STRING_WITH_LEN("COERCIBILITY") }, BUILDER(Create_func_coercibility)},
+ { { C_STRING_WITH_LEN("COLUMN_CHECK") }, BUILDER(Create_func_dyncol_check)},
+ { { C_STRING_WITH_LEN("COLUMN_EXISTS") }, BUILDER(Create_func_dyncol_exists)},
+ { { C_STRING_WITH_LEN("COLUMN_LIST") }, BUILDER(Create_func_dyncol_list)},
+ { { C_STRING_WITH_LEN("COLUMN_JSON") }, BUILDER(Create_func_dyncol_json)},
+ { { C_STRING_WITH_LEN("COMPRESS") }, BUILDER(Create_func_compress)},
+ { { C_STRING_WITH_LEN("CONCAT") }, BUILDER(Create_func_concat)},
+ { { C_STRING_WITH_LEN("CONCAT_WS") }, BUILDER(Create_func_concat_ws)},
+ { { C_STRING_WITH_LEN("CONNECTION_ID") }, BUILDER(Create_func_connection_id)},
+ { { C_STRING_WITH_LEN("CONV") }, BUILDER(Create_func_conv)},
+ { { C_STRING_WITH_LEN("CONVERT_TZ") }, BUILDER(Create_func_convert_tz)},
+ { { C_STRING_WITH_LEN("COS") }, BUILDER(Create_func_cos)},
+ { { C_STRING_WITH_LEN("COT") }, BUILDER(Create_func_cot)},
+ { { C_STRING_WITH_LEN("CRC32") }, BUILDER(Create_func_crc32)},
+ { { C_STRING_WITH_LEN("CROSSES") }, GEOM_BUILDER(Create_func_crosses)},
+ { { C_STRING_WITH_LEN("DATEDIFF") }, BUILDER(Create_func_datediff)},
+ { { C_STRING_WITH_LEN("DATE_FORMAT") }, BUILDER(Create_func_date_format)},
+ { { C_STRING_WITH_LEN("DAYNAME") }, BUILDER(Create_func_dayname)},
+ { { C_STRING_WITH_LEN("DAYOFMONTH") }, BUILDER(Create_func_dayofmonth)},
+ { { C_STRING_WITH_LEN("DAYOFWEEK") }, BUILDER(Create_func_dayofweek)},
+ { { C_STRING_WITH_LEN("DAYOFYEAR") }, BUILDER(Create_func_dayofyear)},
+ { { C_STRING_WITH_LEN("DECODE") }, BUILDER(Create_func_decode)},
+ { { C_STRING_WITH_LEN("DEGREES") }, BUILDER(Create_func_degrees)},
+ { { C_STRING_WITH_LEN("DECODE_HISTOGRAM") }, BUILDER(Create_func_decode_histogram)},
+ { { C_STRING_WITH_LEN("DES_DECRYPT") }, BUILDER(Create_func_des_decrypt)},
+ { { C_STRING_WITH_LEN("DES_ENCRYPT") }, BUILDER(Create_func_des_encrypt)},
+ { { C_STRING_WITH_LEN("DIMENSION") }, GEOM_BUILDER(Create_func_dimension)},
+ { { C_STRING_WITH_LEN("DISJOINT") }, GEOM_BUILDER(Create_func_mbr_disjoint)},
+ { { C_STRING_WITH_LEN("ELT") }, BUILDER(Create_func_elt)},
+ { { C_STRING_WITH_LEN("ENCODE") }, BUILDER(Create_func_encode)},
+ { { C_STRING_WITH_LEN("ENCRYPT") }, BUILDER(Create_func_encrypt)},
+ { { C_STRING_WITH_LEN("ENDPOINT") }, GEOM_BUILDER(Create_func_endpoint)},
+ { { C_STRING_WITH_LEN("ENVELOPE") }, GEOM_BUILDER(Create_func_envelope)},
+ { { C_STRING_WITH_LEN("EQUALS") }, GEOM_BUILDER(Create_func_equals)},
+ { { C_STRING_WITH_LEN("EXP") }, BUILDER(Create_func_exp)},
+ { { C_STRING_WITH_LEN("EXPORT_SET") }, BUILDER(Create_func_export_set)},
+ { { C_STRING_WITH_LEN("EXTERIORRING") }, GEOM_BUILDER(Create_func_exteriorring)},
+ { { C_STRING_WITH_LEN("EXTRACTVALUE") }, BUILDER(Create_func_xml_extractvalue)},
+ { { C_STRING_WITH_LEN("FIELD") }, BUILDER(Create_func_field)},
+ { { C_STRING_WITH_LEN("FIND_IN_SET") }, BUILDER(Create_func_find_in_set)},
+ { { C_STRING_WITH_LEN("FLOOR") }, BUILDER(Create_func_floor)},
+ { { C_STRING_WITH_LEN("FORMAT") }, BUILDER(Create_func_format)},
+ { { C_STRING_WITH_LEN("FOUND_ROWS") }, BUILDER(Create_func_found_rows)},
+ { { C_STRING_WITH_LEN("FROM_BASE64") }, BUILDER(Create_func_from_base64)},
+ { { C_STRING_WITH_LEN("FROM_DAYS") }, BUILDER(Create_func_from_days)},
+ { { C_STRING_WITH_LEN("FROM_UNIXTIME") }, BUILDER(Create_func_from_unixtime)},
+ { { C_STRING_WITH_LEN("GEOMCOLLFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("GEOMCOLLFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("GEOMETRYCOLLECTIONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("GEOMETRYCOLLECTIONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("GEOMETRYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("GEOMETRYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("GEOMETRYN") }, GEOM_BUILDER(Create_func_geometryn)},
+ { { C_STRING_WITH_LEN("GEOMETRYTYPE") }, GEOM_BUILDER(Create_func_geometry_type)},
+ { { C_STRING_WITH_LEN("GEOMFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("GEOMFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("GET_LOCK") }, BUILDER(Create_func_get_lock)},
+ { { C_STRING_WITH_LEN("GLENGTH") }, GEOM_BUILDER(Create_func_glength)},
+ { { C_STRING_WITH_LEN("GREATEST") }, BUILDER(Create_func_greatest)},
+ { { C_STRING_WITH_LEN("HEX") }, BUILDER(Create_func_hex)},
+ { { C_STRING_WITH_LEN("IFNULL") }, BUILDER(Create_func_ifnull)},
+ { { C_STRING_WITH_LEN("INET_ATON") }, BUILDER(Create_func_inet_aton)},
+ { { C_STRING_WITH_LEN("INET_NTOA") }, BUILDER(Create_func_inet_ntoa)},
+ { { C_STRING_WITH_LEN("INET6_ATON") }, BUILDER(Create_func_inet6_aton)},
+ { { C_STRING_WITH_LEN("INET6_NTOA") }, BUILDER(Create_func_inet6_ntoa)},
+ { { C_STRING_WITH_LEN("IS_IPV4") }, BUILDER(Create_func_is_ipv4)},
+ { { C_STRING_WITH_LEN("IS_IPV6") }, BUILDER(Create_func_is_ipv6)},
+ { { C_STRING_WITH_LEN("IS_IPV4_COMPAT") }, BUILDER(Create_func_is_ipv4_compat)},
+ { { C_STRING_WITH_LEN("IS_IPV4_MAPPED") }, BUILDER(Create_func_is_ipv4_mapped)},
+ { { C_STRING_WITH_LEN("INSTR") }, BUILDER(Create_func_instr)},
+ { { C_STRING_WITH_LEN("INTERIORRINGN") }, GEOM_BUILDER(Create_func_interiorringn)},
+ { { C_STRING_WITH_LEN("INTERSECTS") }, GEOM_BUILDER(Create_func_mbr_intersects)},
+ { { C_STRING_WITH_LEN("ISCLOSED") }, GEOM_BUILDER(Create_func_isclosed)},
+ { { C_STRING_WITH_LEN("ISEMPTY") }, GEOM_BUILDER(Create_func_isempty)},
+ { { C_STRING_WITH_LEN("ISNULL") }, BUILDER(Create_func_isnull)},
+ { { C_STRING_WITH_LEN("ISSIMPLE") }, GEOM_BUILDER(Create_func_issimple)},
+ { { C_STRING_WITH_LEN("IS_FREE_LOCK") }, BUILDER(Create_func_is_free_lock)},
+ { { C_STRING_WITH_LEN("IS_USED_LOCK") }, BUILDER(Create_func_is_used_lock)},
+ { { C_STRING_WITH_LEN("LAST_DAY") }, BUILDER(Create_func_last_day)},
+ { { C_STRING_WITH_LEN("LAST_INSERT_ID") }, BUILDER(Create_func_last_insert_id)},
+ { { C_STRING_WITH_LEN("LCASE") }, BUILDER(Create_func_lcase)},
+ { { C_STRING_WITH_LEN("LEAST") }, BUILDER(Create_func_least)},
+ { { C_STRING_WITH_LEN("LENGTH") }, BUILDER(Create_func_length)},
+#ifndef DBUG_OFF
+ { { C_STRING_WITH_LEN("LIKE_RANGE_MIN") }, BUILDER(Create_func_like_range_min)},
+ { { C_STRING_WITH_LEN("LIKE_RANGE_MAX") }, BUILDER(Create_func_like_range_max)},
+#endif
+ { { C_STRING_WITH_LEN("LINEFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("LINEFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("LINESTRINGFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("LINESTRINGFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("LN") }, BUILDER(Create_func_ln)},
+ { { C_STRING_WITH_LEN("LOAD_FILE") }, BUILDER(Create_func_load_file)},
+ { { C_STRING_WITH_LEN("LOCATE") }, BUILDER(Create_func_locate)},
+ { { C_STRING_WITH_LEN("LOG") }, BUILDER(Create_func_log)},
+ { { C_STRING_WITH_LEN("LOG10") }, BUILDER(Create_func_log10)},
+ { { C_STRING_WITH_LEN("LOG2") }, BUILDER(Create_func_log2)},
+ { { C_STRING_WITH_LEN("LOWER") }, BUILDER(Create_func_lcase)},
+ { { C_STRING_WITH_LEN("LPAD") }, BUILDER(Create_func_lpad)},
+ { { C_STRING_WITH_LEN("LTRIM") }, BUILDER(Create_func_ltrim)},
+ { { C_STRING_WITH_LEN("MAKEDATE") }, BUILDER(Create_func_makedate)},
+ { { C_STRING_WITH_LEN("MAKETIME") }, BUILDER(Create_func_maketime)},
+ { { C_STRING_WITH_LEN("MAKE_SET") }, BUILDER(Create_func_make_set)},
+ { { C_STRING_WITH_LEN("MASTER_GTID_WAIT") }, BUILDER(Create_func_master_gtid_wait)},
+ { { C_STRING_WITH_LEN("MASTER_POS_WAIT") }, BUILDER(Create_func_master_pos_wait)},
+ { { C_STRING_WITH_LEN("MBRCONTAINS") }, GEOM_BUILDER(Create_func_mbr_contains)},
+ { { C_STRING_WITH_LEN("MBRDISJOINT") }, GEOM_BUILDER(Create_func_mbr_disjoint)},
+ { { C_STRING_WITH_LEN("MBREQUAL") }, GEOM_BUILDER(Create_func_mbr_equals)},
+ { { C_STRING_WITH_LEN("MBRINTERSECTS") }, GEOM_BUILDER(Create_func_mbr_intersects)},
+ { { C_STRING_WITH_LEN("MBROVERLAPS") }, GEOM_BUILDER(Create_func_mbr_overlaps)},
+ { { C_STRING_WITH_LEN("MBRTOUCHES") }, GEOM_BUILDER(Create_func_touches)},
+ { { C_STRING_WITH_LEN("MBRWITHIN") }, GEOM_BUILDER(Create_func_mbr_within)},
+ { { C_STRING_WITH_LEN("MD5") }, BUILDER(Create_func_md5)},
+ { { C_STRING_WITH_LEN("MLINEFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("MLINEFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("MONTHNAME") }, BUILDER(Create_func_monthname)},
+ { { C_STRING_WITH_LEN("MPOINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("MPOINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("MPOLYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("MPOLYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("MULTILINESTRINGFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("MULTILINESTRINGFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("MULTIPOINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("MULTIPOINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("MULTIPOLYGONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("MULTIPOLYGONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("NAME_CONST") }, BUILDER(Create_func_name_const)},
+ { { C_STRING_WITH_LEN("NULLIF") }, BUILDER(Create_func_nullif)},
+ { { C_STRING_WITH_LEN("NUMGEOMETRIES") }, GEOM_BUILDER(Create_func_numgeometries)},
+ { { C_STRING_WITH_LEN("NUMINTERIORRINGS") }, GEOM_BUILDER(Create_func_numinteriorring)},
+ { { C_STRING_WITH_LEN("NUMPOINTS") }, GEOM_BUILDER(Create_func_numpoints)},
+ { { C_STRING_WITH_LEN("OCT") }, BUILDER(Create_func_oct)},
+ { { C_STRING_WITH_LEN("OCTET_LENGTH") }, BUILDER(Create_func_length)},
+ { { C_STRING_WITH_LEN("ORD") }, BUILDER(Create_func_ord)},
+ { { C_STRING_WITH_LEN("OVERLAPS") }, GEOM_BUILDER(Create_func_mbr_overlaps)},
+ { { C_STRING_WITH_LEN("PERIOD_ADD") }, BUILDER(Create_func_period_add)},
+ { { C_STRING_WITH_LEN("PERIOD_DIFF") }, BUILDER(Create_func_period_diff)},
+ { { C_STRING_WITH_LEN("PI") }, BUILDER(Create_func_pi)},
+ { { C_STRING_WITH_LEN("POINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("POINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("POINTN") }, GEOM_BUILDER(Create_func_pointn)},
+ { { C_STRING_WITH_LEN("POLYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("POLYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("POLYGONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("POLYGONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("POW") }, BUILDER(Create_func_pow)},
+ { { C_STRING_WITH_LEN("POWER") }, BUILDER(Create_func_pow)},
+ { { C_STRING_WITH_LEN("QUOTE") }, BUILDER(Create_func_quote)},
+ { { C_STRING_WITH_LEN("REGEXP_INSTR") }, BUILDER(Create_func_regexp_instr)},
+ { { C_STRING_WITH_LEN("REGEXP_REPLACE") }, BUILDER(Create_func_regexp_replace)},
+ { { C_STRING_WITH_LEN("REGEXP_SUBSTR") }, BUILDER(Create_func_regexp_substr)},
+ { { C_STRING_WITH_LEN("RADIANS") }, BUILDER(Create_func_radians)},
+ { { C_STRING_WITH_LEN("RAND") }, BUILDER(Create_func_rand)},
+ { { C_STRING_WITH_LEN("RELEASE_LOCK") }, BUILDER(Create_func_release_lock)},
+ { { C_STRING_WITH_LEN("REVERSE") }, BUILDER(Create_func_reverse)},
+ { { C_STRING_WITH_LEN("ROUND") }, BUILDER(Create_func_round)},
+ { { C_STRING_WITH_LEN("RPAD") }, BUILDER(Create_func_rpad)},
+ { { C_STRING_WITH_LEN("RTRIM") }, BUILDER(Create_func_rtrim)},
+ { { C_STRING_WITH_LEN("SEC_TO_TIME") }, BUILDER(Create_func_sec_to_time)},
+ { { C_STRING_WITH_LEN("SHA") }, BUILDER(Create_func_sha)},
+ { { C_STRING_WITH_LEN("SHA1") }, BUILDER(Create_func_sha)},
+ { { C_STRING_WITH_LEN("SHA2") }, BUILDER(Create_func_sha2)},
+ { { C_STRING_WITH_LEN("SIGN") }, BUILDER(Create_func_sign)},
+ { { C_STRING_WITH_LEN("SIN") }, BUILDER(Create_func_sin)},
+ { { C_STRING_WITH_LEN("SLEEP") }, BUILDER(Create_func_sleep)},
+ { { C_STRING_WITH_LEN("SOUNDEX") }, BUILDER(Create_func_soundex)},
+ { { C_STRING_WITH_LEN("SPACE") }, BUILDER(Create_func_space)},
+ { { C_STRING_WITH_LEN("SQRT") }, BUILDER(Create_func_sqrt)},
+ { { C_STRING_WITH_LEN("SRID") }, GEOM_BUILDER(Create_func_srid)},
+ { { C_STRING_WITH_LEN("STARTPOINT") }, GEOM_BUILDER(Create_func_startpoint)},
+ { { C_STRING_WITH_LEN("STRCMP") }, BUILDER(Create_func_strcmp)},
+ { { C_STRING_WITH_LEN("STR_TO_DATE") }, BUILDER(Create_func_str_to_date)},
+ { { C_STRING_WITH_LEN("ST_AREA") }, GEOM_BUILDER(Create_func_area)},
+ { { C_STRING_WITH_LEN("ST_ASBINARY") }, GEOM_BUILDER(Create_func_as_wkb)},
+ { { C_STRING_WITH_LEN("ST_ASTEXT") }, GEOM_BUILDER(Create_func_as_wkt)},
+ { { C_STRING_WITH_LEN("ST_ASWKB") }, GEOM_BUILDER(Create_func_as_wkb)},
+ { { C_STRING_WITH_LEN("ST_ASWKT") }, GEOM_BUILDER(Create_func_as_wkt)},
+ { { C_STRING_WITH_LEN("ST_BUFFER") }, GEOM_BUILDER(Create_func_buffer)},
+ { { C_STRING_WITH_LEN("ST_CENTROID") }, GEOM_BUILDER(Create_func_centroid)},
+ { { C_STRING_WITH_LEN("ST_CONTAINS") }, GEOM_BUILDER(Create_func_contains)},
+ { { C_STRING_WITH_LEN("ST_CROSSES") }, GEOM_BUILDER(Create_func_crosses)},
+ { { C_STRING_WITH_LEN("ST_DIFFERENCE") }, GEOM_BUILDER(Create_func_difference)},
+ { { C_STRING_WITH_LEN("ST_DIMENSION") }, GEOM_BUILDER(Create_func_dimension)},
+ { { C_STRING_WITH_LEN("ST_DISJOINT") }, GEOM_BUILDER(Create_func_disjoint)},
+ { { C_STRING_WITH_LEN("ST_DISTANCE") }, GEOM_BUILDER(Create_func_distance)},
+ { { C_STRING_WITH_LEN("ST_ENDPOINT") }, GEOM_BUILDER(Create_func_endpoint)},
+ { { C_STRING_WITH_LEN("ST_ENVELOPE") }, GEOM_BUILDER(Create_func_envelope)},
+ { { C_STRING_WITH_LEN("ST_EQUALS") }, GEOM_BUILDER(Create_func_equals)},
+ { { C_STRING_WITH_LEN("ST_EXTERIORRING") }, GEOM_BUILDER(Create_func_exteriorring)},
+ { { C_STRING_WITH_LEN("ST_GEOMCOLLFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_GEOMCOLLFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_GEOMETRYCOLLECTIONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_GEOMETRYCOLLECTIONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_GEOMETRYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_GEOMETRYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_GEOMETRYN") }, GEOM_BUILDER(Create_func_geometryn)},
+ { { C_STRING_WITH_LEN("ST_GEOMETRYTYPE") }, GEOM_BUILDER(Create_func_geometry_type)},
+ { { C_STRING_WITH_LEN("ST_GEOMFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_GEOMFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+#ifndef DBUG_OFF
+ { { C_STRING_WITH_LEN("ST_GIS_DEBUG") }, GEOM_BUILDER(Create_func_gis_debug)},
+#endif
+ { { C_STRING_WITH_LEN("ST_EQUALS") }, GEOM_BUILDER(Create_func_equals)},
+ { { C_STRING_WITH_LEN("ST_INTERIORRINGN") }, GEOM_BUILDER(Create_func_interiorringn)},
+ { { C_STRING_WITH_LEN("ST_INTERSECTS") }, GEOM_BUILDER(Create_func_intersects)},
+ { { C_STRING_WITH_LEN("ST_INTERSECTION") }, GEOM_BUILDER(Create_func_intersection)},
+ { { C_STRING_WITH_LEN("ST_ISCLOSED") }, GEOM_BUILDER(Create_func_isclosed)},
+ { { C_STRING_WITH_LEN("ST_ISEMPTY") }, GEOM_BUILDER(Create_func_isempty)},
+ { { C_STRING_WITH_LEN("ST_ISSIMPLE") }, GEOM_BUILDER(Create_func_issimple)},
+ { { C_STRING_WITH_LEN("ST_LENGTH") }, GEOM_BUILDER(Create_func_glength)},
+ { { C_STRING_WITH_LEN("ST_LINEFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_LINEFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_LINESTRINGFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_LINESTRINGFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_NUMGEOMETRIES") }, GEOM_BUILDER(Create_func_numgeometries)},
+ { { C_STRING_WITH_LEN("ST_NUMINTERIORRINGS") }, GEOM_BUILDER(Create_func_numinteriorring)},
+ { { C_STRING_WITH_LEN("ST_NUMPOINTS") }, GEOM_BUILDER(Create_func_numpoints)},
+ { { C_STRING_WITH_LEN("ST_OVERLAPS") }, GEOM_BUILDER(Create_func_overlaps)},
+ { { C_STRING_WITH_LEN("ST_POINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_POINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_POINTN") }, GEOM_BUILDER(Create_func_pointn)},
+ { { C_STRING_WITH_LEN("ST_POLYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_POLYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_POLYGONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
+ { { C_STRING_WITH_LEN("ST_POLYGONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
+ { { C_STRING_WITH_LEN("ST_SRID") }, GEOM_BUILDER(Create_func_srid)},
+ { { C_STRING_WITH_LEN("ST_STARTPOINT") }, GEOM_BUILDER(Create_func_startpoint)},
+ { { C_STRING_WITH_LEN("ST_SYMDIFFERENCE") }, GEOM_BUILDER(Create_func_symdifference)},
+ { { C_STRING_WITH_LEN("ST_TOUCHES") }, GEOM_BUILDER(Create_func_touches)},
+ { { C_STRING_WITH_LEN("ST_UNION") }, GEOM_BUILDER(Create_func_union)},
+ { { C_STRING_WITH_LEN("ST_WITHIN") }, GEOM_BUILDER(Create_func_within)},
+ { { C_STRING_WITH_LEN("ST_X") }, GEOM_BUILDER(Create_func_x)},
+ { { C_STRING_WITH_LEN("ST_Y") }, GEOM_BUILDER(Create_func_y)},
+ { { C_STRING_WITH_LEN("SUBSTRING_INDEX") }, BUILDER(Create_func_substr_index)},
+ { { C_STRING_WITH_LEN("SUBTIME") }, BUILDER(Create_func_subtime)},
+ { { C_STRING_WITH_LEN("TAN") }, BUILDER(Create_func_tan)},
+ { { C_STRING_WITH_LEN("TIMEDIFF") }, BUILDER(Create_func_timediff)},
+ { { C_STRING_WITH_LEN("TIME_FORMAT") }, BUILDER(Create_func_time_format)},
+ { { C_STRING_WITH_LEN("TIME_TO_SEC") }, BUILDER(Create_func_time_to_sec)},
+ { { C_STRING_WITH_LEN("TOUCHES") }, GEOM_BUILDER(Create_func_touches)},
+ { { C_STRING_WITH_LEN("TO_BASE64") }, BUILDER(Create_func_to_base64)},
+ { { C_STRING_WITH_LEN("TO_DAYS") }, BUILDER(Create_func_to_days)},
+ { { C_STRING_WITH_LEN("TO_SECONDS") }, BUILDER(Create_func_to_seconds)},
+ { { C_STRING_WITH_LEN("UCASE") }, BUILDER(Create_func_ucase)},
+ { { C_STRING_WITH_LEN("UNCOMPRESS") }, BUILDER(Create_func_uncompress)},
+ { { C_STRING_WITH_LEN("UNCOMPRESSED_LENGTH") }, BUILDER(Create_func_uncompressed_length)},
+ { { C_STRING_WITH_LEN("UNHEX") }, BUILDER(Create_func_unhex)},
+ { { C_STRING_WITH_LEN("UNIX_TIMESTAMP") }, BUILDER(Create_func_unix_timestamp)},
+ { { C_STRING_WITH_LEN("UPDATEXML") }, BUILDER(Create_func_xml_update)},
+ { { C_STRING_WITH_LEN("UPPER") }, BUILDER(Create_func_ucase)},
+ { { C_STRING_WITH_LEN("UUID") }, BUILDER(Create_func_uuid)},
+ { { C_STRING_WITH_LEN("UUID_SHORT") }, BUILDER(Create_func_uuid_short)},
+ { { C_STRING_WITH_LEN("VERSION") }, BUILDER(Create_func_version)},
+ { { C_STRING_WITH_LEN("WEEKDAY") }, BUILDER(Create_func_weekday)},
+ { { C_STRING_WITH_LEN("WEEKOFYEAR") }, BUILDER(Create_func_weekofyear)},
+ { { C_STRING_WITH_LEN("WITHIN") }, GEOM_BUILDER(Create_func_within)},
+ { { C_STRING_WITH_LEN("X") }, GEOM_BUILDER(Create_func_x)},
+ { { C_STRING_WITH_LEN("Y") }, GEOM_BUILDER(Create_func_y)},
+ { { C_STRING_WITH_LEN("YEARWEEK") }, BUILDER(Create_func_year_week)},
+
+ { {0, 0}, NULL}
+};
+
+static HASH native_functions_hash;
+
+extern "C" uchar*
+get_native_fct_hash_key(const uchar *buff, size_t *length,
+ my_bool /* unused */)
+{
+ Native_func_registry *func= (Native_func_registry*) buff;
+ *length= func->name.length;
+ return (uchar*) func->name.str;
+}
+
+/*
+ Load the hash table for native functions.
+ Note: this code is not thread safe, and is intended to be used at server
+ startup only (before going multi-threaded)
+*/
+
+int item_create_init()
+{
+ Native_func_registry *func;
+
+ DBUG_ENTER("item_create_init");
+
+ if (my_hash_init(& native_functions_hash,
+ system_charset_info,
+ array_elements(func_array),
+ 0,
+ 0,
+ (my_hash_get_key) get_native_fct_hash_key,
+ NULL, /* Nothing to free */
+ MYF(0)))
+ DBUG_RETURN(1);
+
+ for (func= func_array; func->builder != NULL; func++)
+ {
+ if (my_hash_insert(& native_functions_hash, (uchar*) func))
+ DBUG_RETURN(1);
+ }
+
+#ifndef DBUG_OFF
+ for (uint i=0 ; i < native_functions_hash.records ; i++)
+ {
+ func= (Native_func_registry*) my_hash_element(& native_functions_hash, i);
+ DBUG_PRINT("info", ("native function: %s length: %u",
+ func->name.str, (uint) func->name.length));
+ }
+#endif
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Empty the hash table for native functions.
+ Note: this code is not thread safe, and is intended to be used at server
+ shutdown only (after thread requests have been executed).
+*/
+
+void item_create_cleanup()
+{
+ DBUG_ENTER("item_create_cleanup");
+ my_hash_free(& native_functions_hash);
+ DBUG_VOID_RETURN;
+}
+
+Create_func *
+find_native_function_builder(THD *thd, LEX_STRING name)
+{
+ Native_func_registry *func;
+ Create_func *builder= NULL;
+
+ /* Thread safe */
+ func= (Native_func_registry*) my_hash_search(& native_functions_hash,
+ (uchar*) name.str,
+ name.length);
+
+ if (func)
+ {
+ builder= func->builder;
+ }
+
+ return builder;
+}
+
+Create_qfunc *
+find_qualified_function_builder(THD *thd)
+{
+ return & Create_sp_func::s_singleton;
+}
+
+
+Item *
+create_func_cast(THD *thd, Item *a, Cast_target cast_type,
+ const char *c_len, const char *c_dec,
+ CHARSET_INFO *cs)
+{
+ Item *UNINIT_VAR(res);
+ ulonglong length= 0, decimals= 0;
+ int error;
+
+ /*
+ We don't have to check for error here as sql_yacc.yy has guaranteed
+ that the values are in range of ulonglong
+ */
+ if (c_len)
+ length= (ulonglong) my_strtoll10(c_len, NULL, &error);
+ if (c_dec)
+ decimals= (ulonglong) my_strtoll10(c_dec, NULL, &error);
+
+ switch (cast_type) {
+ case ITEM_CAST_BINARY:
+ res= new (thd->mem_root) Item_func_binary(a);
+ break;
+ case ITEM_CAST_SIGNED_INT:
+ res= new (thd->mem_root) Item_func_signed(a);
+ break;
+ case ITEM_CAST_UNSIGNED_INT:
+ res= new (thd->mem_root) Item_func_unsigned(a);
+ break;
+ case ITEM_CAST_DATE:
+ res= new (thd->mem_root) Item_date_typecast(a);
+ break;
+ case ITEM_CAST_TIME:
+ if (decimals > MAX_DATETIME_PRECISION)
+ {
+ wrong_precision_error(ER_TOO_BIG_PRECISION, a, decimals,
+ MAX_DATETIME_PRECISION);
+ return 0;
+ }
+ res= new (thd->mem_root) Item_time_typecast(a, (uint) decimals);
+ break;
+ case ITEM_CAST_DATETIME:
+ if (decimals > MAX_DATETIME_PRECISION)
+ {
+ wrong_precision_error(ER_TOO_BIG_PRECISION, a, decimals,
+ MAX_DATETIME_PRECISION);
+ return 0;
+ }
+ res= new (thd->mem_root) Item_datetime_typecast(a, (uint) decimals);
+ break;
+ case ITEM_CAST_DECIMAL:
+ {
+ ulong len;
+ uint dec;
+ if (get_length_and_scale(length, decimals, &len, &dec,
+ DECIMAL_MAX_PRECISION, DECIMAL_MAX_SCALE,
+ a))
+ return NULL;
+ res= new (thd->mem_root) Item_decimal_typecast(a, len, dec);
+ break;
+ }
+ case ITEM_CAST_DOUBLE:
+ {
+ ulong len;
+ uint dec;
+
+ if (!c_len)
+ {
+ length= DBL_DIG+7;
+ decimals= NOT_FIXED_DEC;
+ }
+ else if (get_length_and_scale(length, decimals, &len, &dec,
+ DECIMAL_MAX_PRECISION, NOT_FIXED_DEC-1,
+ a))
+ return NULL;
+ res= new (thd->mem_root) Item_double_typecast(a, (uint) length,
+ (uint) decimals);
+ break;
+ }
+ case ITEM_CAST_CHAR:
+ {
+ int len= -1;
+ CHARSET_INFO *real_cs= (cs ? cs : thd->variables.collation_connection);
+ if (c_len)
+ {
+ if (length > MAX_FIELD_BLOBLENGTH)
+ {
+ char buff[1024];
+ String buf(buff, sizeof(buff), system_charset_info);
+ my_error(ER_TOO_BIG_DISPLAYWIDTH, MYF(0), item_name(a, &buf),
+ MAX_FIELD_BLOBLENGTH);
+ return NULL;
+ }
+ len= (int) length;
+ }
+ res= new (thd->mem_root) Item_char_typecast(a, len, real_cs);
+ break;
+ }
+ default:
+ {
+ DBUG_ASSERT(0);
+ res= 0;
+ break;
+ }
+ }
+ return res;
+}
+
+
+static bool
+have_important_literal_warnings(const MYSQL_TIME_STATUS *status)
+{
+ return (status->warnings & ~MYSQL_TIME_NOTE_TRUNCATED) != 0;
+}
+
+
+/**
+ Builder for datetime literals:
+ TIME'00:00:00', DATE'2001-01-01', TIMESTAMP'2001-01-01 00:00:00'.
+ @param thd The current thread
+ @param str Character literal
+ @param length Length of str
+ @param type Type of literal (TIME, DATE or DATETIME)
+ @param send_error Whether to generate an error on failure
+*/
+
+Item *create_temporal_literal(THD *thd,
+ const char *str, uint length,
+ CHARSET_INFO *cs,
+ enum_field_types type,
+ bool send_error)
+{
+ MYSQL_TIME_STATUS status;
+ MYSQL_TIME ltime;
+ Item *item= NULL;
+ ulonglong flags= sql_mode_for_dates(thd);
+
+ switch(type)
+ {
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_NEWDATE:
+ if (!str_to_datetime(cs, str, length, &ltime, flags, &status) &&
+ ltime.time_type == MYSQL_TIMESTAMP_DATE && !status.warnings)
+ item= new (thd->mem_root) Item_date_literal(&ltime);
+ break;
+ case MYSQL_TYPE_DATETIME:
+ if (!str_to_datetime(cs, str, length, &ltime, flags, &status) &&
+ ltime.time_type == MYSQL_TIMESTAMP_DATETIME &&
+ !have_important_literal_warnings(&status))
+ item= new (thd->mem_root) Item_datetime_literal(&ltime,
+ status.precision);
+ break;
+ case MYSQL_TYPE_TIME:
+ if (!str_to_time(cs, str, length, &ltime, 0, &status) &&
+ ltime.time_type == MYSQL_TIMESTAMP_TIME &&
+ !have_important_literal_warnings(&status))
+ item= new (thd->mem_root) Item_time_literal(&ltime,
+ status.precision);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+
+ if (item)
+ {
+ if (status.warnings) // e.g. a note on nanosecond truncation
+ {
+ ErrConvString err(str, length, cs);
+ make_truncated_value_warning(current_thd,
+ Sql_condition::time_warn_level(status.warnings),
+ &err, ltime.time_type, 0);
+ }
+ return item;
+ }
+
+ if (send_error)
+ {
+ const char *typestr=
+ (type == MYSQL_TYPE_DATE) ? "DATE" :
+ (type == MYSQL_TYPE_TIME) ? "TIME" : "DATETIME";
+ ErrConvString err(str, length, thd->variables.character_set_client);
+ my_error(ER_WRONG_VALUE, MYF(0), typestr, err.ptr());
+ }
+ return NULL;
+}
+
+
+static List<Item> *create_func_dyncol_prepare(THD *thd,
+ DYNCALL_CREATE_DEF **dfs,
+ List<DYNCALL_CREATE_DEF> &list)
+{
+ DYNCALL_CREATE_DEF *def;
+ List_iterator_fast<DYNCALL_CREATE_DEF> li(list);
+ List<Item> *args= new (thd->mem_root) List<Item>;
+
+ *dfs= (DYNCALL_CREATE_DEF *)alloc_root(thd->mem_root,
+ sizeof(DYNCALL_CREATE_DEF) *
+ list.elements);
+
+ if (!args || !*dfs)
+ return NULL;
+
+ for (uint i= 0; (def= li++) ;)
+ {
+ dfs[0][i++]= *def;
+ args->push_back(def->key);
+ args->push_back(def->value);
+ }
+ return args;
+}
+
+Item *create_func_dyncol_create(THD *thd, List<DYNCALL_CREATE_DEF> &list)
+{
+ List<Item> *args;
+ DYNCALL_CREATE_DEF *dfs;
+ if (!(args= create_func_dyncol_prepare(thd, &dfs, list)))
+ return NULL;
+
+ return new (thd->mem_root) Item_func_dyncol_create(*args, dfs);
+}
+
+Item *create_func_dyncol_add(THD *thd, Item *str,
+ List<DYNCALL_CREATE_DEF> &list)
+{
+ List<Item> *args;
+ DYNCALL_CREATE_DEF *dfs;
+
+ if (!(args= create_func_dyncol_prepare(thd, &dfs, list)))
+ return NULL;
+
+ args->push_back(str);
+
+ return new (thd->mem_root) Item_func_dyncol_add(*args, dfs);
+}
+
+
+
+Item *create_func_dyncol_delete(THD *thd, Item *str, List<Item> &nums)
+{
+ DYNCALL_CREATE_DEF *dfs;
+ Item *key;
+ List_iterator_fast<Item> it(nums);
+ List<Item> *args= new (thd->mem_root) List<Item>;
+
+ dfs= (DYNCALL_CREATE_DEF *)alloc_root(thd->mem_root,
+ sizeof(DYNCALL_CREATE_DEF) *
+ nums.elements);
+ if (!args || !dfs)
+ return NULL;
+
+ for (uint i= 0; (key= it++); i++)
+ {
+ dfs[i].key= key;
+ dfs[i].value= new Item_null();
+ dfs[i].type= DYN_COL_INT;
+ args->push_back(dfs[i].key);
+ args->push_back(dfs[i].value);
+ }
+
+ args->push_back(str);
+
+ return new (thd->mem_root) Item_func_dyncol_add(*args, dfs);
+}
+
+
+Item *create_func_dyncol_get(THD *thd, Item *str, Item *num,
+ Cast_target cast_type,
+ const char *c_len, const char *c_dec,
+ CHARSET_INFO *cs)
+{
+ Item *res;
+
+ if (!(res= new (thd->mem_root) Item_dyncol_get(str, num)))
+ return res; // Return NULL
+ return create_func_cast(thd, res, cast_type, c_len, c_dec, cs);
+}
diff --git a/sql/item_create.h b/sql/item_create.h
new file mode 100644
index 00000000000..05fe48f656a
--- /dev/null
+++ b/sql/item_create.h
@@ -0,0 +1,199 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+ Copyright (c) 2008-2011 Monty Program Ab
+
+ 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 */
+
+/* Functions to create an item. Used by sql/sql_yacc.yy */
+
+#ifndef ITEM_CREATE_H
+#define ITEM_CREATE_H
+
+typedef struct st_udf_func udf_func;
+
+/**
+ Public function builder interface.
+ The parser (sql/sql_yacc.yy) uses a factory / builder pattern to
+ construct an <code>Item</code> object for each function call.
+ All the concrete function builders implements this interface,
+ either directly or indirectly with some adapter helpers.
+ Keeping the function creation separated from the bison grammar allows
+ to simplify the parser, and avoid the need to introduce a new token
+ for each function, which has undesirable side effects in the grammar.
+*/
+
+class Create_func
+{
+public:
+ /**
+ The builder create method.
+ Given the function name and list or arguments, this method creates
+ an <code>Item</code> that represents the function call.
+ In case or errors, a NULL item is returned, and an error is reported.
+ Note that the <code>thd</code> object may be modified by the builder.
+ In particular, the following members/methods can be set/called,
+ depending on the function called and the function possible side effects.
+ <ul>
+ <li><code>thd->lex->binlog_row_based_if_mixed</code></li>
+ <li><code>thd->lex->current_context()</code></li>
+ <li><code>thd->lex->safe_to_cache_query</code></li>
+ <li><code>thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT)</code></li>
+ <li><code>thd->lex->uncacheable(UNCACHEABLE_RAND)</code></li>
+ <li><code>thd->lex->add_time_zone_tables_to_query_tables(thd)</code></li>
+ </ul>
+ @param thd The current thread
+ @param name The function name
+ @param item_list The list of arguments to the function, can be NULL
+ @return An item representing the parsed function call, or NULL
+ */
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list) = 0;
+
+protected:
+ /** Constructor */
+ Create_func() {}
+ /** Destructor */
+ virtual ~Create_func() {}
+};
+
+
+/**
+ Function builder for qualified functions.
+ This builder is used with functions call using a qualified function name
+ syntax, as in <code>db.func(expr, expr, ...)</code>.
+*/
+
+class Create_qfunc : public Create_func
+{
+public:
+ /**
+ The builder create method, for unqualified functions.
+ This builder will use the current database for the database name.
+ @param thd The current thread
+ @param name The function name
+ @param item_list The list of arguments to the function, can be NULL
+ @return An item representing the parsed function call
+ */
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /**
+ The builder create method, for qualified functions.
+ @param thd The current thread
+ @param db The database name
+ @param name The function name
+ @param use_explicit_name Should the function be represented as 'db.name'?
+ @param item_list The list of arguments to the function, can be NULL
+ @return An item representing the parsed function call
+ */
+ virtual Item *create_with_db(THD *thd, LEX_STRING db, LEX_STRING name,
+ bool use_explicit_name,
+ List<Item> *item_list) = 0;
+
+protected:
+ /** Constructor. */
+ Create_qfunc() {}
+ /** Destructor. */
+ virtual ~Create_qfunc() {}
+};
+
+
+/**
+ Find the native function builder associated with a given function name.
+ @param thd The current thread
+ @param name The native function name
+ @return The native function builder associated with the name, or NULL
+*/
+extern Create_func * find_native_function_builder(THD *thd, LEX_STRING name);
+
+
+/**
+ Find the function builder for qualified functions.
+ @param thd The current thread
+ @return A function builder for qualified functions
+*/
+extern Create_qfunc * find_qualified_function_builder(THD *thd);
+
+
+#ifdef HAVE_DLOPEN
+/**
+ Function builder for User Defined Functions.
+*/
+
+class Create_udf_func : public Create_func
+{
+public:
+ virtual Item *create_func(THD *thd, LEX_STRING name, List<Item> *item_list);
+
+ /**
+ The builder create method, for User Defined Functions.
+ @param thd The current thread
+ @param fct The User Defined Function metadata
+ @param item_list The list of arguments to the function, can be NULL
+ @return An item representing the parsed function call
+ */
+ Item *create(THD *thd, udf_func *fct, List<Item> *item_list);
+
+ /** Singleton. */
+ static Create_udf_func s_singleton;
+
+protected:
+ /** Constructor. */
+ Create_udf_func() {}
+ /** Destructor. */
+ virtual ~Create_udf_func() {}
+};
+#endif
+
+
+/**
+ Builder for cast expressions.
+ @param thd The current thread
+ @param a The item to cast
+ @param cast_type the type casted into
+ @param len TODO
+ @param dec TODO
+ @param cs The character set
+*/
+Item *
+create_func_cast(THD *thd, Item *a, Cast_target cast_type,
+ const char *len, const char *dec,
+ CHARSET_INFO *cs);
+
+Item *create_temporal_literal(THD *thd,
+ const char *str, uint length,
+ CHARSET_INFO *cs,
+ enum_field_types type,
+ bool send_error);
+inline
+Item *create_temporal_literal(THD *thd, const String *str,
+ enum_field_types type,
+ bool send_error)
+{
+ return create_temporal_literal(thd,
+ str->ptr(), str->length(), str->charset(),
+ type, send_error);
+}
+
+int item_create_init();
+void item_create_cleanup();
+
+Item *create_func_dyncol_create(THD *thd, List<DYNCALL_CREATE_DEF> &list);
+Item *create_func_dyncol_add(THD *thd, Item *str,
+ List<DYNCALL_CREATE_DEF> &list);
+Item *create_func_dyncol_delete(THD *thd, Item *str, List<Item> &nums);
+Item *create_func_dyncol_get(THD *thd, Item *num, Item *str,
+ Cast_target cast_type,
+ const char *c_len, const char *c_dec,
+ CHARSET_INFO *cs);
+Item *create_func_dyncol_json(THD *thd, Item *str);
+#endif
+
diff --git a/sql/item_func.cc b/sql/item_func.cc
new file mode 100644
index 00000000000..0dabd06d423
--- /dev/null
+++ b/sql/item_func.cc
@@ -0,0 +1,7018 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/**
+ @file
+
+ @brief
+ This file defines all numerical functions
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include "sql_plugin.h"
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h"
+#include "slave.h" // for wait_for_master_pos
+#include "sql_show.h" // append_identifier
+#include "strfunc.h" // find_type
+#include "sql_parse.h" // is_update_query
+#include "sql_acl.h" // EXECUTE_ACL
+#include "mysqld.h" // LOCK_short_uuid_generator
+#include "rpl_mi.h"
+#include "sql_time.h"
+#include <m_ctype.h>
+#include <hash.h>
+#include <time.h>
+#include <ft_global.h>
+#include <my_bit.h>
+
+#include "sp_head.h"
+#include "sp_rcontext.h"
+#include "sp.h"
+#include "set_var.h"
+#include "debug_sync.h"
+
+#ifdef NO_EMBEDDED_ACCESS_CHECKS
+#define sp_restore_security_context(A,B) while (0) {}
+#endif
+
+bool check_reserved_words(LEX_STRING *name)
+{
+ if (!my_strcasecmp(system_charset_info, name->str, "GLOBAL") ||
+ !my_strcasecmp(system_charset_info, name->str, "LOCAL") ||
+ !my_strcasecmp(system_charset_info, name->str, "SESSION"))
+ return TRUE;
+ return FALSE;
+}
+
+
+/**
+ @return
+ TRUE if item is a constant
+*/
+
+bool
+eval_const_cond(COND *cond)
+{
+ return ((Item_func*) cond)->val_int() ? TRUE : FALSE;
+}
+
+
+/**
+ Test if the sum of arguments overflows the ulonglong range.
+*/
+static inline bool test_if_sum_overflows_ull(ulonglong arg1, ulonglong arg2)
+{
+ return ULONGLONG_MAX - arg1 < arg2;
+}
+
+void Item_func::set_arguments(List<Item> &list)
+{
+ allowed_arg_cols= 1;
+ arg_count=list.elements;
+ args= tmp_arg; // If 2 arguments
+ if (arg_count <= 2 || (args=(Item**) sql_alloc(sizeof(Item*)*arg_count)))
+ {
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ Item **save_args= args;
+
+ while ((item=li++))
+ {
+ *(save_args++)= item;
+ with_sum_func|=item->with_sum_func;
+ with_field|= item->with_field;
+ }
+ }
+ list.empty(); // Fields are used
+}
+
+Item_func::Item_func(List<Item> &list)
+ :allowed_arg_cols(1)
+{
+ set_arguments(list);
+}
+
+Item_func::Item_func(THD *thd, Item_func *item)
+ :Item_result_field(thd, item),
+ allowed_arg_cols(item->allowed_arg_cols),
+ arg_count(item->arg_count),
+ used_tables_cache(item->used_tables_cache),
+ not_null_tables_cache(item->not_null_tables_cache),
+ const_item_cache(item->const_item_cache)
+{
+ if (arg_count)
+ {
+ if (arg_count <=2)
+ args= tmp_arg;
+ else
+ {
+ if (!(args=(Item**) thd->alloc(sizeof(Item*)*arg_count)))
+ return;
+ }
+ memcpy((char*) args, (char*) item->args, sizeof(Item*)*arg_count);
+ }
+}
+
+
+/*
+ Resolve references to table column for a function and its argument
+
+ SYNOPSIS:
+ fix_fields()
+ thd Thread object
+ ref Pointer to where this object is used. This reference
+ is used if we want to replace this object with another
+ one (for example in the summary functions).
+
+ DESCRIPTION
+ Call fix_fields() for all arguments to the function. The main intention
+ is to allow all Item_field() objects to setup pointers to the table fields.
+
+ Sets as a side effect the following class variables:
+ maybe_null Set if any argument may return NULL
+ with_sum_func Set if any of the arguments contains a sum function
+ with_field Set if any of the arguments contains or is a field
+ used_tables_cache Set to union of the tables used by arguments
+
+ str_value.charset If this is a string function, set this to the
+ character set for the first argument.
+ If any argument is binary, this is set to binary
+
+ If for any item any of the defaults are wrong, then this can
+ be fixed in the fix_length_and_dec() function that is called
+ after this one or by writing a specialized fix_fields() for the
+ item.
+
+ RETURN VALUES
+ FALSE ok
+ TRUE Got error. Stored with my_error().
+*/
+
+bool
+Item_func::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ Item **arg,**arg_end;
+ uchar buff[STACK_BUFF_ALLOC]; // Max argument in function
+
+ used_tables_cache= not_null_tables_cache= 0;
+ const_item_cache=1;
+
+ /*
+ Use stack limit of STACK_MIN_SIZE * 2 since
+ on some platforms a recursive call to fix_fields
+ requires more than STACK_MIN_SIZE bytes (e.g. for
+ MIPS, it takes about 22kB to make one recursive
+ call to Item_func::fix_fields())
+ */
+ if (check_stack_overrun(thd, STACK_MIN_SIZE * 2, buff))
+ return TRUE; // Fatal error if flag is set!
+ if (arg_count)
+ { // Print purify happy
+ for (arg=args, arg_end=args+arg_count; arg != arg_end ; arg++)
+ {
+ Item *item;
+ /*
+ We can't yet set item to *arg as fix_fields may change *arg
+ We shouldn't call fix_fields() twice, so check 'fixed' field first
+ */
+ if ((!(*arg)->fixed && (*arg)->fix_fields(thd, arg)))
+ return TRUE; /* purecov: inspected */
+ item= *arg;
+
+ if (allowed_arg_cols)
+ {
+ if (item->check_cols(allowed_arg_cols))
+ return 1;
+ }
+ else
+ {
+ /* we have to fetch allowed_arg_cols from first argument */
+ DBUG_ASSERT(arg == args); // it is first argument
+ allowed_arg_cols= item->cols();
+ DBUG_ASSERT(allowed_arg_cols); // Can't be 0 any more
+ }
+
+ if (item->maybe_null)
+ maybe_null=1;
+
+ with_sum_func= with_sum_func || item->with_sum_func;
+ with_field= with_field || item->with_field;
+ used_tables_cache|= item->used_tables();
+ const_item_cache&= item->const_item();
+ with_subselect|= item->has_subquery();
+ }
+ }
+ fix_length_and_dec();
+ if (thd->is_error()) // An error inside fix_length_and_dec occured
+ return TRUE;
+ fixed= 1;
+ return FALSE;
+}
+
+void
+Item_func::quick_fix_field()
+{
+ Item **arg,**arg_end;
+ if (arg_count)
+ {
+ for (arg=args, arg_end=args+arg_count; arg != arg_end ; arg++)
+ {
+ if (!(*arg)->fixed)
+ (*arg)->quick_fix_field();
+ }
+ }
+ fixed= 1;
+}
+
+
+bool
+Item_func::eval_not_null_tables(uchar *opt_arg)
+{
+ Item **arg,**arg_end;
+ not_null_tables_cache= 0;
+ if (arg_count)
+ {
+ for (arg=args, arg_end=args+arg_count; arg != arg_end ; arg++)
+ {
+ not_null_tables_cache|= (*arg)->not_null_tables();
+ }
+ }
+ return FALSE;
+}
+
+
+void Item_func::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ Item **arg,**arg_end;
+
+ used_tables_cache= not_null_tables_cache= 0;
+ const_item_cache=1;
+
+ if (arg_count)
+ {
+ for (arg=args, arg_end=args+arg_count; arg != arg_end ; arg++)
+ {
+ (*arg)->fix_after_pullout(new_parent, arg);
+ Item *item= *arg;
+
+ used_tables_cache|= item->used_tables();
+ not_null_tables_cache|= item->not_null_tables();
+ const_item_cache&= item->const_item();
+ }
+ }
+}
+
+
+bool Item_func::walk(Item_processor processor, bool walk_subquery,
+ uchar *argument)
+{
+ if (arg_count)
+ {
+ Item **arg,**arg_end;
+ for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++)
+ {
+ if ((*arg)->walk(processor, walk_subquery, argument))
+ return 1;
+ }
+ }
+ return (this->*processor)(argument);
+}
+
+void Item_func::traverse_cond(Cond_traverser traverser,
+ void *argument, traverse_order order)
+{
+ if (arg_count)
+ {
+ Item **arg,**arg_end;
+
+ switch (order) {
+ case(PREFIX):
+ (*traverser)(this, argument);
+ for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++)
+ {
+ (*arg)->traverse_cond(traverser, argument, order);
+ }
+ break;
+ case (POSTFIX):
+ for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++)
+ {
+ (*arg)->traverse_cond(traverser, argument, order);
+ }
+ (*traverser)(this, argument);
+ }
+ }
+ else
+ (*traverser)(this, argument);
+}
+
+
+/**
+ Transform an Item_func object with a transformer callback function.
+
+ The function recursively applies the transform method to each
+ argument of the Item_func node.
+ If the call of the method for an argument item returns a new item
+ the old item is substituted for a new one.
+ After this the transformer is applied to the root node
+ of the Item_func object.
+ @param transformer the transformer callback function to be applied to
+ the nodes of the tree of the object
+ @param argument parameter to be passed to the transformer
+
+ @return
+ Item returned as the result of transformation of the root node
+*/
+
+Item *Item_func::transform(Item_transformer transformer, uchar *argument)
+{
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+
+ if (arg_count)
+ {
+ Item **arg,**arg_end;
+ for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++)
+ {
+ Item *new_item= (*arg)->transform(transformer, argument);
+ if (!new_item)
+ return 0;
+
+ /*
+ THD::change_item_tree() should be called only if the tree was
+ really transformed, i.e. when a new item has been created.
+ Otherwise we'll be allocating a lot of unnecessary memory for
+ change records at each execution.
+ */
+ if (*arg != new_item)
+ current_thd->change_item_tree(arg, new_item);
+ }
+ }
+ return (this->*transformer)(argument);
+}
+
+
+/**
+ Compile Item_func object with a processor and a transformer
+ callback functions.
+
+ First the function applies the analyzer to the root node of
+ the Item_func object. Then if the analizer succeeeds (returns TRUE)
+ the function recursively applies the compile method to each argument
+ of the Item_func node.
+ If the call of the method for an argument item returns a new item
+ the old item is substituted for a new one.
+ After this the transformer is applied to the root node
+ of the Item_func object.
+ The compile function is not called if the analyzer returns NULL
+ in the parameter arg_p.
+
+ @param analyzer the analyzer callback function to be applied to the
+ nodes of the tree of the object
+ @param[in,out] arg_p parameter to be passed to the processor
+ @param transformer the transformer callback function to be applied to the
+ nodes of the tree of the object
+ @param arg_t parameter to be passed to the transformer
+
+ @return
+ Item returned as the result of transformation of the root node
+*/
+
+Item *Item_func::compile(Item_analyzer analyzer, uchar **arg_p,
+ Item_transformer transformer, uchar *arg_t)
+{
+ if (!(this->*analyzer)(arg_p))
+ return 0;
+ if (*arg_p && arg_count)
+ {
+ Item **arg,**arg_end;
+ for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++)
+ {
+ /*
+ The same parameter value of arg_p must be passed
+ to analyze any argument of the condition formula.
+ */
+ uchar *arg_v= *arg_p;
+ Item *new_item= (*arg)->compile(analyzer, &arg_v, transformer, arg_t);
+ if (new_item && *arg != new_item)
+ current_thd->change_item_tree(arg, new_item);
+ }
+ }
+ return (this->*transformer)(arg_t);
+}
+
+/**
+ See comments in Item_cmp_func::split_sum_func()
+*/
+
+void Item_func::split_sum_func(THD *thd, Item **ref_pointer_array,
+ List<Item> &fields)
+{
+ Item **arg, **arg_end;
+ for (arg= args, arg_end= args+arg_count; arg != arg_end ; arg++)
+ (*arg)->split_sum_func2(thd, ref_pointer_array, fields, arg, TRUE);
+}
+
+
+void Item_func::update_used_tables()
+{
+ used_tables_cache=0;
+ const_item_cache=1;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ args[i]->update_used_tables();
+ used_tables_cache|=args[i]->used_tables();
+ const_item_cache&=args[i]->const_item();
+ }
+}
+
+
+table_map Item_func::used_tables() const
+{
+ return used_tables_cache;
+}
+
+
+table_map Item_func::not_null_tables() const
+{
+ return not_null_tables_cache;
+}
+
+
+void Item_func::print(String *str, enum_query_type query_type)
+{
+ str->append(func_name());
+ str->append('(');
+ print_args(str, 0, query_type);
+ str->append(')');
+}
+
+
+void Item_func::print_args(String *str, uint from, enum_query_type query_type)
+{
+ for (uint i=from ; i < arg_count ; i++)
+ {
+ if (i != from)
+ str->append(',');
+ args[i]->print(str, query_type);
+ }
+}
+
+
+void Item_func::print_op(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ for (uint i=0 ; i < arg_count-1 ; i++)
+ {
+ args[i]->print(str, query_type);
+ str->append(' ');
+ str->append(func_name());
+ str->append(' ');
+ }
+ args[arg_count-1]->print(str, query_type);
+ str->append(')');
+}
+
+
+bool Item_func::eq(const Item *item, bool binary_cmp) const
+{
+ /* Assume we don't have rtti */
+ if (this == item)
+ return 1;
+ if (item->type() != FUNC_ITEM)
+ return 0;
+ Item_func *item_func=(Item_func*) item;
+ Item_func::Functype func_type;
+ if ((func_type= functype()) != item_func->functype() ||
+ arg_count != item_func->arg_count ||
+ (func_type != Item_func::FUNC_SP &&
+ func_name() != item_func->func_name()) ||
+ (func_type == Item_func::FUNC_SP &&
+ my_strcasecmp(system_charset_info, func_name(), item_func->func_name())))
+ return 0;
+ for (uint i=0; i < arg_count ; i++)
+ if (!args[i]->eq(item_func->args[i], binary_cmp))
+ return 0;
+ return 1;
+}
+
+
+Field *Item_func::tmp_table_field(TABLE *table)
+{
+ Field *field= NULL;
+
+ switch (result_type()) {
+ case INT_RESULT:
+ if (max_char_length() > MY_INT32_NUM_DECIMAL_DIGITS)
+ field= new Field_longlong(max_char_length(), maybe_null, name,
+ unsigned_flag);
+ else
+ field= new Field_long(max_char_length(), maybe_null, name,
+ unsigned_flag);
+ break;
+ case REAL_RESULT:
+ field= new Field_double(max_char_length(), maybe_null, name, decimals);
+ break;
+ case STRING_RESULT:
+ return make_string_field(table);
+ case DECIMAL_RESULT:
+ field= Field_new_decimal::create_from_item(this);
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ // This case should never be chosen
+ DBUG_ASSERT(0);
+ field= 0;
+ break;
+ }
+ if (field)
+ field->init(table);
+ return field;
+}
+
+/*
+bool Item_func::is_expensive_processor(uchar *arg)
+{
+ return is_expensive();
+}
+*/
+
+my_decimal *Item_func::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed);
+ longlong nr= val_int();
+ if (null_value)
+ return 0; /* purecov: inspected */
+ int2my_decimal(E_DEC_FATAL_ERROR, nr, unsigned_flag, decimal_value);
+ return decimal_value;
+}
+
+
+String *Item_real_func::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ double nr= val_real();
+ if (null_value)
+ return 0; /* purecov: inspected */
+ str->set_real(nr, decimals, collation.collation);
+ return str;
+}
+
+
+my_decimal *Item_real_func::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed);
+ double nr= val_real();
+ if (null_value)
+ return 0; /* purecov: inspected */
+ double2my_decimal(E_DEC_FATAL_ERROR, nr, decimal_value);
+ return decimal_value;
+}
+
+
+void Item_udf_func::fix_num_length_and_dec()
+{
+ uint fl_length= 0;
+ decimals=0;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ set_if_bigger(decimals,args[i]->decimals);
+ set_if_bigger(fl_length, args[i]->max_length);
+ }
+ max_length=float_length(decimals);
+ if (fl_length > max_length)
+ {
+ decimals= NOT_FIXED_DEC;
+ max_length= float_length(NOT_FIXED_DEC);
+ }
+}
+
+
+/**
+ Count max_length and decimals for temporal functions.
+
+ @param item Argument array
+ @param nitems Number of arguments in the array.
+
+ @retval False on success, true on error.
+*/
+void Item_func::count_datetime_length(Item **item, uint nitems)
+{
+ unsigned_flag= 0;
+ decimals= 0;
+ if (field_type() != MYSQL_TYPE_DATE)
+ {
+ for (uint i= 0; i < nitems; i++)
+ set_if_bigger(decimals, item[i]->decimals);
+ }
+ set_if_smaller(decimals, TIME_SECOND_PART_DIGITS);
+ uint len= decimals ? (decimals + 1) : 0;
+ len+= mysql_temporal_int_part_length(field_type());
+ fix_char_length(len);
+}
+
+/**
+ Set max_length/decimals of function if function is fixed point and
+ result length/precision depends on argument ones.
+*/
+
+void Item_func::count_decimal_length()
+{
+ int max_int_part= 0;
+ decimals= 0;
+ unsigned_flag= 1;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ set_if_bigger(decimals, args[i]->decimals);
+ set_if_bigger(max_int_part, args[i]->decimal_int_part());
+ set_if_smaller(unsigned_flag, args[i]->unsigned_flag);
+ }
+ int precision= MY_MIN(max_int_part + decimals, DECIMAL_MAX_PRECISION);
+ fix_char_length(my_decimal_precision_to_length_no_truncation(precision,
+ decimals,
+ unsigned_flag));
+}
+
+
+/**
+ Set max_length of if it is maximum length of its arguments.
+*/
+
+void Item_func::count_only_length(Item **item, uint nitems)
+{
+ uint32 char_length= 0;
+ unsigned_flag= 0;
+ for (uint i= 0; i < nitems ; i++)
+ {
+ set_if_bigger(char_length, item[i]->max_char_length());
+ set_if_bigger(unsigned_flag, item[i]->unsigned_flag);
+ }
+ fix_char_length(char_length);
+}
+
+
+/**
+ Set max_length/decimals of function if function is floating point and
+ result length/precision depends on argument ones.
+*/
+
+void Item_func::count_real_length()
+{
+ uint32 length= 0;
+ decimals= 0;
+ max_length= 0;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ if (decimals != NOT_FIXED_DEC)
+ {
+ set_if_bigger(decimals, args[i]->decimals);
+ set_if_bigger(length, (args[i]->max_length - args[i]->decimals));
+ }
+ set_if_bigger(max_length, args[i]->max_length);
+ }
+ if (decimals != NOT_FIXED_DEC)
+ {
+ max_length= length;
+ length+= decimals;
+ if (length < max_length) // If previous operation gave overflow
+ max_length= UINT_MAX32;
+ else
+ max_length= length;
+ }
+}
+
+
+/**
+ Calculate max_length and decimals for STRING_RESULT functions.
+
+ @param field_type Field type.
+ @param items Argument array.
+ @param nitems Number of arguments.
+
+ @retval False on success, true on error.
+*/
+bool Item_func::count_string_result_length(enum_field_types field_type,
+ Item **items, uint nitems)
+{
+ if (agg_arg_charsets(collation, items, nitems, MY_COLL_ALLOW_CONV, 1))
+ return true;
+ if (is_temporal_type(field_type))
+ count_datetime_length(items, nitems);
+ else
+ {
+ decimals= NOT_FIXED_DEC;
+ count_only_length(items, nitems);
+ }
+ return false;
+}
+
+
+void Item_func::signal_divide_by_null()
+{
+ THD *thd= current_thd;
+ if (thd->variables.sql_mode & MODE_ERROR_FOR_DIVISION_BY_ZERO)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_DIVISION_BY_ZERO,
+ ER(ER_DIVISION_BY_ZERO));
+ null_value= 1;
+}
+
+
+Item *Item_func::get_tmp_table_item(THD *thd)
+{
+ if (!with_sum_func && !const_item())
+ return new Item_field(result_field);
+ return copy_or_same(thd);
+}
+
+double Item_int_func::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+
+ return unsigned_flag ? (double) ((ulonglong) val_int()) : (double) val_int();
+}
+
+bool Item_int_func::count_sargable_conds(uchar *arg)
+{
+ if (sargable)
+ {
+ SELECT_LEX *sel= (SELECT_LEX *) arg;
+ sel->cond_count++;
+ }
+ return 0;
+}
+
+
+String *Item_int_func::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong nr=val_int();
+ if (null_value)
+ return 0;
+ str->set_int(nr, unsigned_flag, collation.collation);
+ return str;
+}
+
+
+void Item_func_connection_id::fix_length_and_dec()
+{
+ Item_int_func::fix_length_and_dec();
+ max_length= 10;
+}
+
+
+bool Item_func_connection_id::fix_fields(THD *thd, Item **ref)
+{
+ if (Item_int_func::fix_fields(thd, ref))
+ return TRUE;
+ thd->thread_specific_used= TRUE;
+ value= thd->variables.pseudo_thread_id;
+ return FALSE;
+}
+
+
+/**
+ Check arguments here to determine result's type for a numeric
+ function of two arguments.
+*/
+
+void Item_num_op::fix_length_and_dec(void)
+{
+ DBUG_ENTER("Item_num_op::fix_length_and_dec");
+ DBUG_PRINT("info", ("name %s", func_name()));
+ DBUG_ASSERT(arg_count == 2);
+ Item_result r0= args[0]->cast_to_int_type();
+ Item_result r1= args[1]->cast_to_int_type();
+
+ if (r0 == REAL_RESULT || r1 == REAL_RESULT ||
+ r0 == STRING_RESULT || r1 ==STRING_RESULT)
+ {
+ count_real_length();
+ max_length= float_length(decimals);
+ cached_result_type= REAL_RESULT;
+ }
+ else if (r0 == DECIMAL_RESULT || r1 == DECIMAL_RESULT ||
+ r0 == TIME_RESULT || r1 == TIME_RESULT)
+ {
+ cached_result_type= DECIMAL_RESULT;
+ result_precision();
+ fix_decimals();
+ if ((r0 == TIME_RESULT || r1 == TIME_RESULT) && decimals == 0)
+ cached_result_type= INT_RESULT;
+ }
+ else
+ {
+ DBUG_ASSERT(r0 == INT_RESULT && r1 == INT_RESULT);
+ cached_result_type=INT_RESULT;
+ result_precision();
+ decimals= 0;
+ }
+ DBUG_PRINT("info", ("Type: %s",
+ (cached_result_type == REAL_RESULT ? "REAL_RESULT" :
+ cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" :
+ cached_result_type == INT_RESULT ? "INT_RESULT" :
+ "--ILLEGAL!!!--")));
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set result type for a numeric function of one argument
+ (can be also used by a numeric function of many arguments, if the result
+ type depends only on the first argument)
+*/
+
+void Item_func_num1::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_func_num1::fix_length_and_dec");
+ DBUG_PRINT("info", ("name %s", func_name()));
+ switch (cached_result_type= args[0]->cast_to_int_type()) {
+ case INT_RESULT:
+ max_length= args[0]->max_length;
+ unsigned_flag= args[0]->unsigned_flag;
+ break;
+ case STRING_RESULT:
+ case REAL_RESULT:
+ cached_result_type= REAL_RESULT;
+ decimals= args[0]->decimals; // Preserve NOT_FIXED_DEC
+ max_length= float_length(decimals);
+ break;
+ case TIME_RESULT:
+ cached_result_type= DECIMAL_RESULT;
+ case DECIMAL_RESULT:
+ decimals= args[0]->decimal_scale(); // Do not preserve NOT_FIXED_DEC
+ max_length= args[0]->max_length;
+ break;
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ DBUG_PRINT("info", ("Type: %s",
+ (cached_result_type == REAL_RESULT ? "REAL_RESULT" :
+ cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" :
+ cached_result_type == INT_RESULT ? "INT_RESULT" :
+ "--ILLEGAL!!!--")));
+ DBUG_VOID_RETURN;
+}
+
+
+String *Item_func_hybrid_result_type::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ switch (cached_result_type) {
+ case DECIMAL_RESULT:
+ {
+ my_decimal decimal_value, *val;
+ if (!(val= decimal_op(&decimal_value)))
+ return 0; // null is set
+ my_decimal_round(E_DEC_FATAL_ERROR, val, decimals, FALSE, val);
+ str->set_charset(collation.collation);
+ my_decimal2string(E_DEC_FATAL_ERROR, val, 0, 0, 0, str);
+ break;
+ }
+ case INT_RESULT:
+ {
+ longlong nr= int_op();
+ if (null_value)
+ return 0; /* purecov: inspected */
+ str->set_int(nr, unsigned_flag, collation.collation);
+ break;
+ }
+ case REAL_RESULT:
+ {
+ double nr= real_op();
+ if (null_value)
+ return 0; /* purecov: inspected */
+ str->set_real(nr, decimals, collation.collation);
+ break;
+ }
+ case STRING_RESULT:
+ if (is_temporal_type(field_type()))
+ {
+ MYSQL_TIME ltime;
+ if (date_op(&ltime,
+ field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0) ||
+ str->alloc(MAX_DATE_STRING_REP_LENGTH))
+ {
+ null_value= 1;
+ return (String *) 0;
+ }
+ ltime.time_type= mysql_type_to_time_type(field_type());
+ str->length(my_TIME_to_str(&ltime, const_cast<char*>(str->ptr()), decimals));
+ str->set_charset(&my_charset_bin);
+ return str;
+ }
+ return str_op(&str_value);
+ case TIME_RESULT:
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ return str;
+}
+
+
+double Item_func_hybrid_result_type::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ switch (cached_result_type) {
+ case DECIMAL_RESULT:
+ {
+ my_decimal decimal_value, *val;
+ double result;
+ if (!(val= decimal_op(&decimal_value)))
+ return 0.0; // null is set
+ my_decimal2double(E_DEC_FATAL_ERROR, val, &result);
+ return result;
+ }
+ case INT_RESULT:
+ {
+ longlong result= int_op();
+ return unsigned_flag ? (double) ((ulonglong) result) : (double) result;
+ }
+ case REAL_RESULT:
+ return real_op();
+ case STRING_RESULT:
+ {
+ if (is_temporal_type(field_type()))
+ {
+ MYSQL_TIME ltime;
+ if (date_op(&ltime,
+ field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0 ))
+ {
+ null_value= 1;
+ return 0;
+ }
+ ltime.time_type= mysql_type_to_time_type(field_type());
+ return TIME_to_double(&ltime);
+ }
+ char *end_not_used;
+ int err_not_used;
+ String *res= str_op(&str_value);
+ return (res ? my_strntod(res->charset(), (char*) res->ptr(), res->length(),
+ &end_not_used, &err_not_used) : 0.0);
+ }
+ case TIME_RESULT:
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ return 0.0;
+}
+
+
+longlong Item_func_hybrid_result_type::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ switch (cached_result_type) {
+ case DECIMAL_RESULT:
+ {
+ my_decimal decimal_value, *val;
+ if (!(val= decimal_op(&decimal_value)))
+ return 0; // null is set
+ longlong result;
+ my_decimal2int(E_DEC_FATAL_ERROR, val, unsigned_flag, &result);
+ return result;
+ }
+ case INT_RESULT:
+ return int_op();
+ case REAL_RESULT:
+ return (longlong) rint(real_op());
+ case STRING_RESULT:
+ {
+ if (is_temporal_type(field_type()))
+ {
+ MYSQL_TIME ltime;
+ if (date_op(&ltime,
+ field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0))
+ {
+ null_value= 1;
+ return 0;
+ }
+ ltime.time_type= mysql_type_to_time_type(field_type());
+ return TIME_to_ulonglong(&ltime);
+ }
+ int err_not_used;
+ String *res;
+ if (!(res= str_op(&str_value)))
+ return 0;
+
+ char *end= (char*) res->ptr() + res->length();
+ CHARSET_INFO *cs= res->charset();
+ return (*(cs->cset->strtoll10))(cs, res->ptr(), &end, &err_not_used);
+ }
+ case TIME_RESULT:
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ return 0;
+}
+
+
+my_decimal *Item_func_hybrid_result_type::val_decimal(my_decimal *decimal_value)
+{
+ my_decimal *val= decimal_value;
+ DBUG_ASSERT(fixed == 1);
+ switch (cached_result_type) {
+ case DECIMAL_RESULT:
+ val= decimal_op(decimal_value);
+ break;
+ case INT_RESULT:
+ {
+ longlong result= int_op();
+ int2my_decimal(E_DEC_FATAL_ERROR, result, unsigned_flag, decimal_value);
+ break;
+ }
+ case REAL_RESULT:
+ {
+ double result= (double)real_op();
+ double2my_decimal(E_DEC_FATAL_ERROR, result, decimal_value);
+ break;
+ }
+ case STRING_RESULT:
+ {
+ if (is_temporal_type(field_type()))
+ {
+ MYSQL_TIME ltime;
+ if (date_op(&ltime,
+ field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0))
+ {
+ my_decimal_set_zero(decimal_value);
+ null_value= 1;
+ return 0;
+ }
+ ltime.time_type= mysql_type_to_time_type(field_type());
+ return date2my_decimal(&ltime, decimal_value);
+ }
+ String *res;
+ if (!(res= str_op(&str_value)))
+ return NULL;
+
+ str2my_decimal(E_DEC_FATAL_ERROR, (char*) res->ptr(),
+ res->length(), res->charset(), decimal_value);
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ return val;
+}
+
+
+bool Item_func_hybrid_result_type::get_date(MYSQL_TIME *ltime,
+ ulonglong fuzzydate)
+{
+ DBUG_ASSERT(fixed == 1);
+ switch (cached_result_type) {
+ case DECIMAL_RESULT:
+ {
+ my_decimal value, *res;
+ if (!(res= decimal_op(&value)) ||
+ decimal_to_datetime_with_warn(res, ltime, fuzzydate,
+ field_name_or_null()))
+ goto err;
+ break;
+ }
+ case INT_RESULT:
+ {
+ longlong value= int_op();
+ bool neg= !unsigned_flag && value < 0;
+ if (null_value || int_to_datetime_with_warn(neg, neg ? -value : value,
+ ltime, fuzzydate,
+ field_name_or_null()))
+ goto err;
+ break;
+ }
+ case REAL_RESULT:
+ {
+ double value= real_op();
+ if (null_value || double_to_datetime_with_warn(value, ltime, fuzzydate,
+ field_name_or_null()))
+ goto err;
+ break;
+ }
+ case STRING_RESULT:
+ {
+ if (is_temporal_type(field_type()))
+ return date_op(ltime, fuzzydate);
+ char buff[40];
+ String tmp(buff,sizeof(buff), &my_charset_bin),*res;
+ if (!(res= str_op(&tmp)) ||
+ str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(),
+ ltime, fuzzydate))
+ goto err;
+ break;
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+
+ return (null_value= 0);
+
+err:
+ bzero(ltime, sizeof(*ltime));
+ return null_value|= !(fuzzydate & TIME_FUZZY_DATES);
+}
+
+
+void Item_func_signed::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("cast("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" as signed)"));
+
+}
+
+
+longlong Item_func_signed::val_int_from_str(int *error)
+{
+ char buff[MAX_FIELD_WIDTH], *end, *start;
+ uint32 length;
+ String tmp(buff,sizeof(buff), &my_charset_bin), *res;
+ longlong value;
+ CHARSET_INFO *cs;
+
+ /*
+ For a string result, we must first get the string and then convert it
+ to a longlong
+ */
+
+ if (!(res= args[0]->val_str(&tmp)))
+ {
+ null_value= 1;
+ *error= 0;
+ return 0;
+ }
+ null_value= 0;
+ start= (char *)res->ptr();
+ length= res->length();
+ cs= res->charset();
+
+ end= start + length;
+ value= cs->cset->strtoll10(cs, start, &end, error);
+ if (*error > 0 || end != start+ length)
+ {
+ ErrConvString err(res);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER",
+ err.ptr());
+ }
+ return value;
+}
+
+
+longlong Item_func_signed::val_int()
+{
+ longlong value;
+ int error;
+
+ if (args[0]->cast_to_int_type() != STRING_RESULT)
+ {
+ value= args[0]->val_int();
+ null_value= args[0]->null_value;
+ return value;
+ }
+ else if (args[0]->dynamic_result())
+ {
+ /* We come here when argument has an unknown type */
+ args[0]->unsigned_flag= 0; // Mark that we want to have a signed value
+ value= args[0]->val_int();
+ null_value= args[0]->null_value;
+ if (!null_value && args[0]->unsigned_flag && value < 0)
+ goto err; // Warn about overflow
+ return value;
+ }
+
+ value= val_int_from_str(&error);
+ if (value < 0 && error == 0)
+ goto err;
+ return value;
+
+err:
+ push_warning(current_thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR,
+ "Cast to signed converted positive out-of-range integer to "
+ "it's negative complement");
+ return value;
+}
+
+
+void Item_func_unsigned::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("cast("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" as unsigned)"));
+
+}
+
+
+longlong Item_func_unsigned::val_int()
+{
+ longlong value;
+ int error;
+
+ if (args[0]->cast_to_int_type() == DECIMAL_RESULT)
+ {
+ my_decimal tmp, *dec= args[0]->val_decimal(&tmp);
+ if (!(null_value= args[0]->null_value))
+ my_decimal2int(E_DEC_FATAL_ERROR, dec, 1, &value);
+ else
+ value= 0;
+ return value;
+ }
+ else if (args[0]->dynamic_result())
+ {
+ /* We come here when argument has an unknown type */
+ args[0]->unsigned_flag= 1; // Mark that we want to have an unsigned value
+ value= args[0]->val_int();
+ null_value= args[0]->null_value;
+ if (!null_value && args[0]->unsigned_flag == 0 && value < 0)
+ goto err; // Warn about overflow
+ return value;
+ }
+ else if (args[0]->cast_to_int_type() != STRING_RESULT)
+ {
+ value= args[0]->val_int();
+ null_value= args[0]->null_value;
+ if (!null_value && args[0]->unsigned_flag == 0 && value < 0)
+ goto err; // Warn about overflow
+ return value;
+ }
+
+ value= val_int_from_str(&error);
+ if (error < 0)
+ goto err;
+
+ return value;
+
+err:
+ push_warning(current_thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR,
+ "Cast to unsigned converted negative integer to it's "
+ "positive complement");
+ return value;
+}
+
+
+String *Item_decimal_typecast::val_str(String *str)
+{
+ my_decimal tmp_buf, *tmp= val_decimal(&tmp_buf);
+ if (null_value)
+ return NULL;
+ my_decimal2string(E_DEC_FATAL_ERROR, tmp, 0, 0, 0, str);
+ return str;
+}
+
+
+double Item_decimal_typecast::val_real()
+{
+ my_decimal tmp_buf, *tmp= val_decimal(&tmp_buf);
+ double res;
+ if (null_value)
+ return 0.0;
+ my_decimal2double(E_DEC_FATAL_ERROR, tmp, &res);
+ return res;
+}
+
+
+longlong Item_decimal_typecast::val_int()
+{
+ my_decimal tmp_buf, *tmp= val_decimal(&tmp_buf);
+ longlong res;
+ if (null_value)
+ return 0;
+ my_decimal2int(E_DEC_FATAL_ERROR, tmp, unsigned_flag, &res);
+ return res;
+}
+
+
+my_decimal *Item_decimal_typecast::val_decimal(my_decimal *dec)
+{
+ my_decimal tmp_buf, *tmp= args[0]->val_decimal(&tmp_buf);
+ bool sign;
+ uint precision;
+
+ if ((null_value= args[0]->null_value))
+ return NULL;
+ my_decimal_round(E_DEC_FATAL_ERROR, tmp, decimals, FALSE, dec);
+ sign= dec->sign();
+ if (unsigned_flag)
+ {
+ if (sign)
+ {
+ my_decimal_set_zero(dec);
+ goto err;
+ }
+ }
+ precision= my_decimal_length_to_precision(max_length,
+ decimals, unsigned_flag);
+ if (precision - decimals < (uint) my_decimal_intg(dec))
+ {
+ max_my_decimal(dec, precision, decimals);
+ dec->sign(sign);
+ goto err;
+ }
+ return dec;
+
+err:
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_DATA_OUT_OF_RANGE,
+ ER(ER_WARN_DATA_OUT_OF_RANGE),
+ name, 1L);
+ return dec;
+}
+
+
+void Item_decimal_typecast::print(String *str, enum_query_type query_type)
+{
+ char len_buf[20*3 + 1];
+ char *end;
+
+ uint precision= my_decimal_length_to_precision(max_length, decimals,
+ unsigned_flag);
+ str->append(STRING_WITH_LEN("cast("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" as decimal("));
+
+ end=int10_to_str(precision, len_buf,10);
+ str->append(len_buf, (uint32) (end - len_buf));
+
+ str->append(',');
+
+ end=int10_to_str(decimals, len_buf,10);
+ str->append(len_buf, (uint32) (end - len_buf));
+
+ str->append(')');
+ str->append(')');
+}
+
+
+double Item_double_typecast::val_real()
+{
+ int error;
+ double tmp= args[0]->val_real();
+ if ((null_value= args[0]->null_value))
+ return 0.0;
+
+ if ((error= truncate_double(&tmp, max_length, decimals, 0, DBL_MAX)))
+ {
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_DATA_OUT_OF_RANGE,
+ ER(ER_WARN_DATA_OUT_OF_RANGE),
+ name, 1);
+ if (error < 0)
+ {
+ null_value= 1; // Illegal value
+ tmp= 0.0;
+ }
+ }
+ return tmp;
+}
+
+
+void Item_double_typecast::print(String *str, enum_query_type query_type)
+{
+ char len_buf[20*3 + 1];
+ char *end;
+
+ str->append(STRING_WITH_LEN("cast("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" as double"));
+ if (decimals != NOT_FIXED_DEC)
+ {
+ str->append('(');
+ end= int10_to_str(max_length, len_buf,10);
+ str->append(len_buf, (uint32) (end - len_buf));
+ str->append(',');
+ end= int10_to_str(decimals, len_buf,10);
+ str->append(len_buf, (uint32) (end - len_buf));
+ str->append(')');
+ }
+ str->append(')');
+}
+
+double Item_func_plus::real_op()
+{
+ double value= args[0]->val_real() + args[1]->val_real();
+ if ((null_value=args[0]->null_value || args[1]->null_value))
+ return 0.0;
+ return check_float_overflow(value);
+}
+
+
+longlong Item_func_plus::int_op()
+{
+ longlong val0= args[0]->val_int();
+ longlong val1= args[1]->val_int();
+ longlong res= val0 + val1;
+ bool res_unsigned= FALSE;
+
+ if ((null_value= args[0]->null_value || args[1]->null_value))
+ return 0;
+
+ /*
+ First check whether the result can be represented as a
+ (bool unsigned_flag, longlong value) pair, then check if it is compatible
+ with this Item's unsigned_flag by calling check_integer_overflow().
+ */
+ if (args[0]->unsigned_flag)
+ {
+ if (args[1]->unsigned_flag || val1 >= 0)
+ {
+ if (test_if_sum_overflows_ull((ulonglong) val0, (ulonglong) val1))
+ goto err;
+ res_unsigned= TRUE;
+ }
+ else
+ {
+ /* val1 is negative */
+ if ((ulonglong) val0 > (ulonglong) LONGLONG_MAX)
+ res_unsigned= TRUE;
+ }
+ }
+ else
+ {
+ if (args[1]->unsigned_flag)
+ {
+ if (val0 >= 0)
+ {
+ if (test_if_sum_overflows_ull((ulonglong) val0, (ulonglong) val1))
+ goto err;
+ res_unsigned= TRUE;
+ }
+ else
+ {
+ if ((ulonglong) val1 > (ulonglong) LONGLONG_MAX)
+ res_unsigned= TRUE;
+ }
+ }
+ else
+ {
+ if (val0 >=0 && val1 >= 0)
+ res_unsigned= TRUE;
+ else if (val0 < 0 && val1 < 0 && res >= 0)
+ goto err;
+ }
+ }
+ return check_integer_overflow(res, res_unsigned);
+
+err:
+ return raise_integer_overflow();
+}
+
+
+/**
+ Calculate plus of two decimals.
+
+ @param decimal_value Buffer that can be used to store result
+
+ @retval
+ 0 Value was NULL; In this case null_value is set
+ @retval
+ \# Value of operation as a decimal
+*/
+
+my_decimal *Item_func_plus::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal value1, *val1;
+ my_decimal value2, *val2;
+ val1= args[0]->val_decimal(&value1);
+ if ((null_value= args[0]->null_value))
+ return 0;
+ val2= args[1]->val_decimal(&value2);
+ if (!(null_value= (args[1]->null_value ||
+ check_decimal_overflow(my_decimal_add(E_DEC_FATAL_ERROR &
+ ~E_DEC_OVERFLOW,
+ decimal_value,
+ val1, val2)) > 3)))
+ return decimal_value;
+ return 0;
+}
+
+/**
+ Set precision of results for additive operations (+ and -)
+*/
+void Item_func_additive_op::result_precision()
+{
+ decimals= MY_MAX(args[0]->decimal_scale(), args[1]->decimal_scale());
+ int arg1_int= args[0]->decimal_precision() - args[0]->decimal_scale();
+ int arg2_int= args[1]->decimal_precision() - args[1]->decimal_scale();
+ int precision= MY_MAX(arg1_int, arg2_int) + 1 + decimals;
+
+ DBUG_ASSERT(arg1_int >= 0);
+ DBUG_ASSERT(arg2_int >= 0);
+
+ /* Integer operations keep unsigned_flag if one of arguments is unsigned */
+ if (result_type() == INT_RESULT)
+ unsigned_flag= args[0]->unsigned_flag | args[1]->unsigned_flag;
+ else
+ unsigned_flag= args[0]->unsigned_flag & args[1]->unsigned_flag;
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
+}
+
+
+/**
+ The following function is here to allow the user to force
+ subtraction of UNSIGNED BIGINT to return negative values.
+*/
+
+void Item_func_minus::fix_length_and_dec()
+{
+ Item_num_op::fix_length_and_dec();
+ if (unsigned_flag &&
+ (current_thd->variables.sql_mode & MODE_NO_UNSIGNED_SUBTRACTION))
+ unsigned_flag=0;
+}
+
+
+double Item_func_minus::real_op()
+{
+ double value= args[0]->val_real() - args[1]->val_real();
+ if ((null_value=args[0]->null_value || args[1]->null_value))
+ return 0.0;
+ return check_float_overflow(value);
+}
+
+
+longlong Item_func_minus::int_op()
+{
+ longlong val0= args[0]->val_int();
+ longlong val1= args[1]->val_int();
+ longlong res= val0 - val1;
+ bool res_unsigned= FALSE;
+
+ if ((null_value= args[0]->null_value || args[1]->null_value))
+ return 0;
+
+ /*
+ First check whether the result can be represented as a
+ (bool unsigned_flag, longlong value) pair, then check if it is compatible
+ with this Item's unsigned_flag by calling check_integer_overflow().
+ */
+ if (args[0]->unsigned_flag)
+ {
+ if (args[1]->unsigned_flag)
+ {
+ if ((ulonglong) val0 < (ulonglong) val1)
+ {
+ if (res >= 0)
+ goto err;
+ }
+ else
+ res_unsigned= TRUE;
+ }
+ else
+ {
+ if (val1 >= 0)
+ {
+ if ((ulonglong) val0 > (ulonglong) val1)
+ res_unsigned= TRUE;
+ }
+ else
+ {
+ if (test_if_sum_overflows_ull((ulonglong) val0, (ulonglong) -val1))
+ goto err;
+ res_unsigned= TRUE;
+ }
+ }
+ }
+ else
+ {
+ if (args[1]->unsigned_flag)
+ {
+ if ((ulonglong) (val0 - LONGLONG_MIN) < (ulonglong) val1)
+ goto err;
+ }
+ else
+ {
+ if (val0 > 0 && val1 < 0)
+ res_unsigned= TRUE;
+ else if (val0 < 0 && val1 > 0 && res >= 0)
+ goto err;
+ }
+ }
+ return check_integer_overflow(res, res_unsigned);
+
+err:
+ return raise_integer_overflow();
+}
+
+
+/**
+ See Item_func_plus::decimal_op for comments.
+*/
+
+my_decimal *Item_func_minus::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal value1, *val1;
+ my_decimal value2, *val2=
+
+ val1= args[0]->val_decimal(&value1);
+ if ((null_value= args[0]->null_value))
+ return 0;
+ val2= args[1]->val_decimal(&value2);
+ if (!(null_value= (args[1]->null_value ||
+ (check_decimal_overflow(my_decimal_sub(E_DEC_FATAL_ERROR &
+ ~E_DEC_OVERFLOW,
+ decimal_value, val1,
+ val2)) > 3))))
+ return decimal_value;
+ return 0;
+}
+
+
+double Item_func_mul::real_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real() * args[1]->val_real();
+ if ((null_value=args[0]->null_value || args[1]->null_value))
+ return 0.0;
+ return check_float_overflow(value);
+}
+
+
+longlong Item_func_mul::int_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong a= args[0]->val_int();
+ longlong b= args[1]->val_int();
+ longlong res;
+ ulonglong res0, res1;
+ ulong a0, a1, b0, b1;
+ bool res_unsigned= FALSE;
+ bool a_negative= FALSE, b_negative= FALSE;
+
+ if ((null_value= args[0]->null_value || args[1]->null_value))
+ return 0;
+
+ /*
+ First check whether the result can be represented as a
+ (bool unsigned_flag, longlong value) pair, then check if it is compatible
+ with this Item's unsigned_flag by calling check_integer_overflow().
+
+ Let a = a1 * 2^32 + a0 and b = b1 * 2^32 + b0. Then
+ a * b = (a1 * 2^32 + a0) * (b1 * 2^32 + b0) = a1 * b1 * 2^64 +
+ + (a1 * b0 + a0 * b1) * 2^32 + a0 * b0;
+ We can determine if the above sum overflows the ulonglong range by
+ sequentially checking the following conditions:
+ 1. If both a1 and b1 are non-zero.
+ 2. Otherwise, if (a1 * b0 + a0 * b1) is greater than ULONG_MAX.
+ 3. Otherwise, if (a1 * b0 + a0 * b1) * 2^32 + a0 * b0 is greater than
+ ULONGLONG_MAX.
+
+ Since we also have to take the unsigned_flag for a and b into account,
+ it is easier to first work with absolute values and set the
+ correct sign later.
+ */
+ if (!args[0]->unsigned_flag && a < 0)
+ {
+ a_negative= TRUE;
+ a= -a;
+ }
+ if (!args[1]->unsigned_flag && b < 0)
+ {
+ b_negative= TRUE;
+ b= -b;
+ }
+
+ a0= 0xFFFFFFFFUL & a;
+ a1= ((ulonglong) a) >> 32;
+ b0= 0xFFFFFFFFUL & b;
+ b1= ((ulonglong) b) >> 32;
+
+ if (a1 && b1)
+ goto err;
+
+ res1= (ulonglong) a1 * b0 + (ulonglong) a0 * b1;
+ if (res1 > 0xFFFFFFFFUL)
+ goto err;
+
+ res1= res1 << 32;
+ res0= (ulonglong) a0 * b0;
+
+ if (test_if_sum_overflows_ull(res1, res0))
+ goto err;
+ res= res1 + res0;
+
+ if (a_negative != b_negative)
+ {
+ if ((ulonglong) res > (ulonglong) LONGLONG_MIN + 1)
+ goto err;
+ res= -res;
+ }
+ else
+ res_unsigned= TRUE;
+
+ return check_integer_overflow(res, res_unsigned);
+
+err:
+ return raise_integer_overflow();
+}
+
+
+/** See Item_func_plus::decimal_op for comments. */
+
+my_decimal *Item_func_mul::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal value1, *val1;
+ my_decimal value2, *val2;
+ val1= args[0]->val_decimal(&value1);
+ if ((null_value= args[0]->null_value))
+ return 0;
+ val2= args[1]->val_decimal(&value2);
+ if (!(null_value= (args[1]->null_value ||
+ (check_decimal_overflow(my_decimal_mul(E_DEC_FATAL_ERROR &
+ ~E_DEC_OVERFLOW,
+ decimal_value, val1,
+ val2)) > 3))))
+ return decimal_value;
+ return 0;
+}
+
+
+void Item_func_mul::result_precision()
+{
+ /* Integer operations keep unsigned_flag if one of arguments is unsigned */
+ if (result_type() == INT_RESULT)
+ unsigned_flag= args[0]->unsigned_flag | args[1]->unsigned_flag;
+ else
+ unsigned_flag= args[0]->unsigned_flag & args[1]->unsigned_flag;
+ decimals= MY_MIN(args[0]->decimal_scale() + args[1]->decimal_scale(),
+ DECIMAL_MAX_SCALE);
+ uint est_prec = args[0]->decimal_precision() + args[1]->decimal_precision();
+ uint precision= MY_MIN(est_prec, DECIMAL_MAX_PRECISION);
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
+}
+
+
+double Item_func_div::real_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ double val2= args[1]->val_real();
+ if ((null_value= args[0]->null_value || args[1]->null_value))
+ return 0.0;
+ if (val2 == 0.0)
+ {
+ signal_divide_by_null();
+ return 0.0;
+ }
+ return check_float_overflow(value/val2);
+}
+
+
+my_decimal *Item_func_div::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal value1, *val1;
+ my_decimal value2, *val2;
+ int err;
+
+ val1= args[0]->val_decimal(&value1);
+ if ((null_value= args[0]->null_value))
+ return 0;
+ val2= args[1]->val_decimal(&value2);
+ if ((null_value= args[1]->null_value))
+ return 0;
+ if ((err= check_decimal_overflow(my_decimal_div(E_DEC_FATAL_ERROR &
+ ~E_DEC_OVERFLOW &
+ ~E_DEC_DIV_ZERO,
+ decimal_value,
+ val1, val2,
+ prec_increment))) > 3)
+ {
+ if (err == E_DEC_DIV_ZERO)
+ signal_divide_by_null();
+ null_value= 1;
+ return 0;
+ }
+ return decimal_value;
+}
+
+
+void Item_func_div::result_precision()
+{
+ /*
+ We need to add args[1]->divisor_precision_increment(),
+ to properly handle the cases like this:
+ SELECT 5.05 / 0.014; -> 360.714286
+ i.e. when the divisor has a zero integer part
+ and non-zero digits appear only after the decimal point.
+ Precision in this example is calculated as
+ args[0]->decimal_precision() + // 3
+ args[1]->divisor_precision_increment() + // 3
+ prec_increment // 4
+ which gives 10 decimals digits.
+ */
+ uint precision=MY_MIN(args[0]->decimal_precision() +
+ args[1]->divisor_precision_increment() + prec_increment,
+ DECIMAL_MAX_PRECISION);
+
+ /* Integer operations keep unsigned_flag if one of arguments is unsigned */
+ if (result_type() == INT_RESULT)
+ unsigned_flag= args[0]->unsigned_flag | args[1]->unsigned_flag;
+ else
+ unsigned_flag= args[0]->unsigned_flag & args[1]->unsigned_flag;
+ decimals= MY_MIN(args[0]->decimal_scale() + prec_increment, DECIMAL_MAX_SCALE);
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
+}
+
+
+void Item_func_div::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_func_div::fix_length_and_dec");
+ prec_increment= current_thd->variables.div_precincrement;
+ Item_num_op::fix_length_and_dec();
+ switch (cached_result_type) {
+ case REAL_RESULT:
+ {
+ decimals=MY_MAX(args[0]->decimals,args[1]->decimals)+prec_increment;
+ set_if_smaller(decimals, NOT_FIXED_DEC);
+ uint tmp=float_length(decimals);
+ if (decimals == NOT_FIXED_DEC)
+ max_length= tmp;
+ else
+ {
+ max_length=args[0]->max_length - args[0]->decimals + decimals;
+ set_if_smaller(max_length,tmp);
+ }
+ break;
+ }
+ case INT_RESULT:
+ cached_result_type= DECIMAL_RESULT;
+ DBUG_PRINT("info", ("Type changed: DECIMAL_RESULT"));
+ result_precision();
+ break;
+ case DECIMAL_RESULT:
+ result_precision();
+ fix_decimals();
+ break;
+ case STRING_RESULT:
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ maybe_null= 1; // devision by zero
+ DBUG_VOID_RETURN;
+}
+
+
+/* Integer division */
+longlong Item_func_int_div::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+
+ /*
+ Perform division using DECIMAL math if either of the operands has a
+ non-integer type
+ */
+ if (args[0]->result_type() != INT_RESULT ||
+ args[1]->result_type() != INT_RESULT)
+ {
+ my_decimal tmp;
+ my_decimal *val0p= args[0]->val_decimal(&tmp);
+ if ((null_value= args[0]->null_value))
+ return 0;
+ my_decimal val0= *val0p;
+
+ my_decimal *val1p= args[1]->val_decimal(&tmp);
+ if ((null_value= args[1]->null_value))
+ return 0;
+ my_decimal val1= *val1p;
+
+ int err;
+ if ((err= my_decimal_div(E_DEC_FATAL_ERROR & ~E_DEC_DIV_ZERO, &tmp,
+ &val0, &val1, 0)) > 3)
+ {
+ if (err == E_DEC_DIV_ZERO)
+ signal_divide_by_null();
+ return 0;
+ }
+
+ my_decimal truncated;
+ const bool do_truncate= true;
+ if (my_decimal_round(E_DEC_FATAL_ERROR, &tmp, 0, do_truncate, &truncated))
+ DBUG_ASSERT(false);
+
+ longlong res;
+ if (my_decimal2int(E_DEC_FATAL_ERROR, &truncated, unsigned_flag, &res) &
+ E_DEC_OVERFLOW)
+ raise_integer_overflow();
+ return res;
+ }
+
+ longlong val0=args[0]->val_int();
+ longlong val1=args[1]->val_int();
+ bool val0_negative, val1_negative, res_negative;
+ ulonglong uval0, uval1, res;
+ if ((null_value= (args[0]->null_value || args[1]->null_value)))
+ return 0;
+ if (val1 == 0)
+ {
+ signal_divide_by_null();
+ return 0;
+ }
+
+ val0_negative= !args[0]->unsigned_flag && val0 < 0;
+ val1_negative= !args[1]->unsigned_flag && val1 < 0;
+ res_negative= val0_negative != val1_negative;
+ uval0= (ulonglong) (val0_negative ? -val0 : val0);
+ uval1= (ulonglong) (val1_negative ? -val1 : val1);
+ res= uval0 / uval1;
+ if (res_negative)
+ {
+ if (res > (ulonglong) LONGLONG_MAX)
+ return raise_integer_overflow();
+ res= (ulonglong) (-(longlong) res);
+ }
+ return check_integer_overflow(res, !res_negative);
+}
+
+
+void Item_func_int_div::fix_length_and_dec()
+{
+ Item_result argtype= args[0]->result_type();
+ /* use precision ony for the data type it is applicable for and valid */
+ uint32 char_length= args[0]->max_char_length() -
+ (argtype == DECIMAL_RESULT || argtype == INT_RESULT ?
+ args[0]->decimals : 0);
+ fix_char_length(char_length > MY_INT64_NUM_DECIMAL_DIGITS ?
+ MY_INT64_NUM_DECIMAL_DIGITS : char_length);
+ maybe_null=1;
+ unsigned_flag=args[0]->unsigned_flag | args[1]->unsigned_flag;
+}
+
+
+longlong Item_func_mod::int_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong val0= args[0]->val_int();
+ longlong val1= args[1]->val_int();
+ bool val0_negative, val1_negative;
+ ulonglong uval0, uval1;
+ ulonglong res;
+
+ if ((null_value= args[0]->null_value || args[1]->null_value))
+ return 0; /* purecov: inspected */
+ if (val1 == 0)
+ {
+ signal_divide_by_null();
+ return 0;
+ }
+
+ /*
+ '%' is calculated by integer division internally. Since dividing
+ LONGLONG_MIN by -1 generates SIGFPE, we calculate using unsigned values and
+ then adjust the sign appropriately.
+ */
+ val0_negative= !args[0]->unsigned_flag && val0 < 0;
+ val1_negative= !args[1]->unsigned_flag && val1 < 0;
+ uval0= (ulonglong) (val0_negative ? -val0 : val0);
+ uval1= (ulonglong) (val1_negative ? -val1 : val1);
+ res= uval0 % uval1;
+ return check_integer_overflow(val0_negative ? -(longlong) res : res,
+ !val0_negative);
+}
+
+double Item_func_mod::real_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ double val2= args[1]->val_real();
+ if ((null_value= args[0]->null_value || args[1]->null_value))
+ return 0.0; /* purecov: inspected */
+ if (val2 == 0.0)
+ {
+ signal_divide_by_null();
+ return 0.0;
+ }
+ return fmod(value,val2);
+}
+
+
+my_decimal *Item_func_mod::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal value1, *val1;
+ my_decimal value2, *val2;
+
+ val1= args[0]->val_decimal(&value1);
+ if ((null_value= args[0]->null_value))
+ return 0;
+ val2= args[1]->val_decimal(&value2);
+ if ((null_value= args[1]->null_value))
+ return 0;
+ switch (my_decimal_mod(E_DEC_FATAL_ERROR & ~E_DEC_DIV_ZERO, decimal_value,
+ val1, val2)) {
+ case E_DEC_TRUNCATED:
+ case E_DEC_OK:
+ return decimal_value;
+ case E_DEC_DIV_ZERO:
+ signal_divide_by_null();
+ default:
+ null_value= 1;
+ return 0;
+ }
+}
+
+
+void Item_func_mod::result_precision()
+{
+ decimals= MY_MAX(args[0]->decimal_scale(), args[1]->decimal_scale());
+ max_length= MY_MAX(args[0]->max_length, args[1]->max_length);
+}
+
+
+void Item_func_mod::fix_length_and_dec()
+{
+ Item_num_op::fix_length_and_dec();
+ maybe_null= 1;
+ unsigned_flag= args[0]->unsigned_flag;
+}
+
+
+double Item_func_neg::real_op()
+{
+ double value= args[0]->val_real();
+ null_value= args[0]->null_value;
+ return -value;
+}
+
+
+longlong Item_func_neg::int_op()
+{
+ longlong value= args[0]->val_int();
+ if ((null_value= args[0]->null_value))
+ return 0;
+ if (args[0]->unsigned_flag &&
+ (ulonglong) value > (ulonglong) LONGLONG_MAX + 1)
+ return raise_integer_overflow();
+
+ if (value == LONGLONG_MIN)
+ {
+ if (args[0]->unsigned_flag != unsigned_flag)
+ /* negation of LONGLONG_MIN is LONGLONG_MIN. */
+ return LONGLONG_MIN;
+ else
+ return raise_integer_overflow();
+ }
+
+ return check_integer_overflow(-value, !args[0]->unsigned_flag && value < 0);
+}
+
+
+my_decimal *Item_func_neg::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal val, *value= args[0]->val_decimal(&val);
+ if (!(null_value= args[0]->null_value))
+ {
+ my_decimal2decimal(value, decimal_value);
+ my_decimal_neg(decimal_value);
+ return decimal_value;
+ }
+ return 0;
+}
+
+
+void Item_func_neg::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_func_neg::fix_length_and_dec");
+ Item_func_num1::fix_length_and_dec();
+ /* 1 add because sign can appear */
+ max_length= args[0]->max_length + 1;
+
+ /*
+ If this is in integer context keep the context as integer if possible
+ (This is how multiplication and other integer functions works)
+ Use val() to get value as arg_type doesn't mean that item is
+ Item_int or Item_real due to existence of Item_param.
+ */
+ if (cached_result_type == INT_RESULT && args[0]->const_item())
+ {
+ longlong val= args[0]->val_int();
+ if ((ulonglong) val >= (ulonglong) LONGLONG_MIN &&
+ ((ulonglong) val != (ulonglong) LONGLONG_MIN ||
+ args[0]->type() != INT_ITEM))
+ {
+ /*
+ Ensure that result is converted to DECIMAL, as longlong can't hold
+ the negated number
+ */
+ cached_result_type= DECIMAL_RESULT;
+ DBUG_PRINT("info", ("Type changed: DECIMAL_RESULT"));
+ }
+ }
+ unsigned_flag= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+double Item_func_abs::real_op()
+{
+ double value= args[0]->val_real();
+ null_value= args[0]->null_value;
+ return fabs(value);
+}
+
+
+longlong Item_func_abs::int_op()
+{
+ longlong value= args[0]->val_int();
+ if ((null_value= args[0]->null_value))
+ return 0;
+ if (unsigned_flag)
+ return value;
+ /* -LONGLONG_MIN = LONGLONG_MAX + 1 => outside of signed longlong range */
+ if (value == LONGLONG_MIN)
+ return raise_integer_overflow();
+ return (value >= 0) ? value : -value;
+}
+
+
+my_decimal *Item_func_abs::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal val, *value= args[0]->val_decimal(&val);
+ if (!(null_value= args[0]->null_value))
+ {
+ my_decimal2decimal(value, decimal_value);
+ if (decimal_value->sign())
+ my_decimal_neg(decimal_value);
+ return decimal_value;
+ }
+ return 0;
+}
+
+
+void Item_func_abs::fix_length_and_dec()
+{
+ Item_func_num1::fix_length_and_dec();
+ unsigned_flag= args[0]->unsigned_flag;
+}
+
+
+/** Gateway to natural LOG function. */
+double Item_func_ln::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value= args[0]->null_value))
+ return 0.0;
+ if (value <= 0.0)
+ {
+ signal_divide_by_null();
+ return 0.0;
+ }
+ return log(value);
+}
+
+/**
+ Extended but so slower LOG function.
+
+ We have to check if all values are > zero and first one is not one
+ as these are the cases then result is not a number.
+*/
+double Item_func_log::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value= args[0]->null_value))
+ return 0.0;
+ if (value <= 0.0)
+ {
+ signal_divide_by_null();
+ return 0.0;
+ }
+ if (arg_count == 2)
+ {
+ double value2= args[1]->val_real();
+ if ((null_value= args[1]->null_value))
+ return 0.0;
+ if (value2 <= 0.0 || value == 1.0)
+ {
+ signal_divide_by_null();
+ return 0.0;
+ }
+ return log(value2) / log(value);
+ }
+ return log(value);
+}
+
+double Item_func_log2::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+
+ if ((null_value=args[0]->null_value))
+ return 0.0;
+ if (value <= 0.0)
+ {
+ signal_divide_by_null();
+ return 0.0;
+ }
+ return log(value) / M_LN2;
+}
+
+double Item_func_log10::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value= args[0]->null_value))
+ return 0.0;
+ if (value <= 0.0)
+ {
+ signal_divide_by_null();
+ return 0.0;
+ }
+ return log10(value);
+}
+
+double Item_func_exp::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0.0; /* purecov: inspected */
+ return check_float_overflow(exp(value));
+}
+
+double Item_func_sqrt::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=(args[0]->null_value || value < 0)))
+ return 0.0; /* purecov: inspected */
+ return sqrt(value);
+}
+
+double Item_func_pow::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ double val2= args[1]->val_real();
+ if ((null_value=(args[0]->null_value || args[1]->null_value)))
+ return 0.0; /* purecov: inspected */
+ return check_float_overflow(pow(value,val2));
+}
+
+// Trigonometric functions
+
+double Item_func_acos::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ /* One can use this to defer SELECT processing. */
+ DEBUG_SYNC(current_thd, "before_acos_function");
+ // the volatile's for BUG #2338 to calm optimizer down (because of gcc's bug)
+ volatile double value= args[0]->val_real();
+ if ((null_value=(args[0]->null_value || (value < -1.0 || value > 1.0))))
+ return 0.0;
+ return acos(value);
+}
+
+double Item_func_asin::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ // the volatile's for BUG #2338 to calm optimizer down (because of gcc's bug)
+ volatile double value= args[0]->val_real();
+ if ((null_value=(args[0]->null_value || (value < -1.0 || value > 1.0))))
+ return 0.0;
+ return asin(value);
+}
+
+double Item_func_atan::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0.0;
+ if (arg_count == 2)
+ {
+ double val2= args[1]->val_real();
+ if ((null_value=args[1]->null_value))
+ return 0.0;
+ return check_float_overflow(atan2(value,val2));
+ }
+ return atan(value);
+}
+
+double Item_func_cos::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0.0;
+ return cos(value);
+}
+
+double Item_func_sin::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0.0;
+ return sin(value);
+}
+
+double Item_func_tan::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0.0;
+ return check_float_overflow(tan(value));
+}
+
+
+double Item_func_cot::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0.0;
+ return check_float_overflow(1.0 / tan(value));
+}
+
+
+// Shift-functions, same as << and >> in C/C++
+
+
+longlong Item_func_shift_left::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint shift;
+ ulonglong res= ((ulonglong) args[0]->val_int() <<
+ (shift=(uint) args[1]->val_int()));
+ if (args[0]->null_value || args[1]->null_value)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ return (shift < sizeof(longlong)*8 ? (longlong) res : 0);
+}
+
+longlong Item_func_shift_right::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint shift;
+ ulonglong res= (ulonglong) args[0]->val_int() >>
+ (shift=(uint) args[1]->val_int());
+ if (args[0]->null_value || args[1]->null_value)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ return (shift < sizeof(longlong)*8 ? (longlong) res : 0);
+}
+
+
+longlong Item_func_bit_neg::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ ulonglong res= (ulonglong) args[0]->val_int();
+ if ((null_value=args[0]->null_value))
+ return 0;
+ return ~res;
+}
+
+
+// Conversion functions
+
+void Item_func_integer::fix_length_and_dec()
+{
+ max_length=args[0]->max_length - args[0]->decimals+1;
+ uint tmp=float_length(decimals);
+ set_if_smaller(max_length,tmp);
+ decimals=0;
+}
+
+
+void Item_func_int_val::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_func_int_val::fix_length_and_dec");
+ DBUG_PRINT("info", ("name %s", func_name()));
+
+ ulonglong tmp_max_length= (ulonglong ) args[0]->max_length -
+ (args[0]->decimals ? args[0]->decimals + 1 : 0) + 2;
+ max_length= tmp_max_length > (ulonglong) 4294967295U ?
+ (uint32) 4294967295U : (uint32) tmp_max_length;
+ uint tmp= float_length(decimals);
+ set_if_smaller(max_length,tmp);
+ decimals= 0;
+
+ switch (cached_result_type= args[0]->cast_to_int_type())
+ {
+ case STRING_RESULT:
+ case REAL_RESULT:
+ cached_result_type= REAL_RESULT;
+ max_length= float_length(decimals);
+ break;
+ case INT_RESULT:
+ case TIME_RESULT:
+ case DECIMAL_RESULT:
+ /*
+ -2 because in most high position can't be used any digit for longlong
+ and one position for increasing value during operation
+ */
+ if ((args[0]->max_length - args[0]->decimals) >=
+ (DECIMAL_LONGLONG_DIGITS - 2))
+ {
+ cached_result_type= DECIMAL_RESULT;
+ }
+ else
+ {
+ unsigned_flag= args[0]->unsigned_flag;
+ cached_result_type= INT_RESULT;
+ }
+ break;
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ DBUG_PRINT("info", ("Type: %s",
+ (cached_result_type == REAL_RESULT ? "REAL_RESULT" :
+ cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" :
+ cached_result_type == INT_RESULT ? "INT_RESULT" :
+ "--ILLEGAL!!!--")));
+
+ DBUG_VOID_RETURN;
+}
+
+
+longlong Item_func_ceiling::int_op()
+{
+ longlong result;
+ switch (args[0]->result_type()) {
+ case INT_RESULT:
+ result= args[0]->val_int();
+ null_value= args[0]->null_value;
+ break;
+ case DECIMAL_RESULT:
+ {
+ my_decimal dec_buf, *dec;
+ if ((dec= Item_func_ceiling::decimal_op(&dec_buf)))
+ my_decimal2int(E_DEC_FATAL_ERROR, dec, unsigned_flag, &result);
+ else
+ result= 0;
+ break;
+ }
+ default:
+ result= (longlong)Item_func_ceiling::real_op();
+ };
+ return result;
+}
+
+
+double Item_func_ceiling::real_op()
+{
+ /*
+ the volatile's for BUG #3051 to calm optimizer down (because of gcc's
+ bug)
+ */
+ volatile double value= args[0]->val_real();
+ null_value= args[0]->null_value;
+ return ceil(value);
+}
+
+
+my_decimal *Item_func_ceiling::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal val, *value= args[0]->val_decimal(&val);
+ if (!(null_value= (args[0]->null_value ||
+ my_decimal_ceiling(E_DEC_FATAL_ERROR, value,
+ decimal_value) > 1)))
+ return decimal_value;
+ return 0;
+}
+
+
+longlong Item_func_floor::int_op()
+{
+ longlong result;
+ switch (args[0]->result_type()) {
+ case INT_RESULT:
+ result= args[0]->val_int();
+ null_value= args[0]->null_value;
+ break;
+ case DECIMAL_RESULT:
+ {
+ my_decimal dec_buf, *dec;
+ if ((dec= Item_func_floor::decimal_op(&dec_buf)))
+ my_decimal2int(E_DEC_FATAL_ERROR, dec, unsigned_flag, &result);
+ else
+ result= 0;
+ break;
+ }
+ default:
+ result= (longlong)Item_func_floor::real_op();
+ };
+ return result;
+}
+
+
+double Item_func_floor::real_op()
+{
+ /*
+ the volatile's for BUG #3051 to calm optimizer down (because of gcc's
+ bug)
+ */
+ volatile double value= args[0]->val_real();
+ null_value= args[0]->null_value;
+ return floor(value);
+}
+
+
+my_decimal *Item_func_floor::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal val, *value= args[0]->val_decimal(&val);
+ if (!(null_value= (args[0]->null_value ||
+ my_decimal_floor(E_DEC_FATAL_ERROR, value,
+ decimal_value) > 1)))
+ return decimal_value;
+ return 0;
+}
+
+
+void Item_func_round::fix_length_and_dec()
+{
+ int decimals_to_set;
+ longlong val1;
+ bool val1_unsigned;
+
+ unsigned_flag= args[0]->unsigned_flag;
+ if (!args[1]->const_item())
+ {
+ decimals= args[0]->decimals;
+ max_length= float_length(decimals);
+ if (args[0]->result_type() == DECIMAL_RESULT)
+ {
+ max_length++;
+ cached_result_type= DECIMAL_RESULT;
+ }
+ else
+ cached_result_type= REAL_RESULT;
+ return;
+ }
+
+ val1= args[1]->val_int();
+ if ((null_value= args[1]->null_value))
+ return;
+
+ val1_unsigned= args[1]->unsigned_flag;
+ if (val1 < 0)
+ decimals_to_set= val1_unsigned ? INT_MAX : 0;
+ else
+ decimals_to_set= (val1 > INT_MAX) ? INT_MAX : (int) val1;
+
+ if (args[0]->decimals == NOT_FIXED_DEC)
+ {
+ decimals= MY_MIN(decimals_to_set, NOT_FIXED_DEC);
+ max_length= float_length(decimals);
+ cached_result_type= REAL_RESULT;
+ return;
+ }
+
+ switch (args[0]->result_type()) {
+ case REAL_RESULT:
+ case STRING_RESULT:
+ cached_result_type= REAL_RESULT;
+ decimals= MY_MIN(decimals_to_set, NOT_FIXED_DEC);
+ max_length= float_length(decimals);
+ break;
+ case INT_RESULT:
+ if ((!decimals_to_set && truncate) || (args[0]->decimal_precision() < DECIMAL_LONGLONG_DIGITS))
+ {
+ int length_can_increase= MY_TEST(!truncate && (val1 < 0) &&
+ !val1_unsigned);
+ max_length= args[0]->max_length + length_can_increase;
+ /* Here we can keep INT_RESULT */
+ cached_result_type= INT_RESULT;
+ decimals= 0;
+ break;
+ }
+ /* fall through */
+ case DECIMAL_RESULT:
+ {
+ cached_result_type= DECIMAL_RESULT;
+ decimals_to_set= MY_MIN(DECIMAL_MAX_SCALE, decimals_to_set);
+ int decimals_delta= args[0]->decimals - decimals_to_set;
+ int precision= args[0]->decimal_precision();
+ int length_increase= ((decimals_delta <= 0) || truncate) ? 0:1;
+
+ precision-= decimals_delta - length_increase;
+ decimals= MY_MIN(decimals_to_set, DECIMAL_MAX_SCALE);
+ max_length= my_decimal_precision_to_length_no_truncation(precision,
+ decimals,
+ unsigned_flag);
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); /* This result type isn't handled */
+ }
+}
+
+double my_double_round(double value, longlong dec, bool dec_unsigned,
+ bool truncate)
+{
+ double tmp;
+ bool dec_negative= (dec < 0) && !dec_unsigned;
+ ulonglong abs_dec= dec_negative ? -dec : dec;
+ /*
+ tmp2 is here to avoid return the value with 80 bit precision
+ This will fix that the test round(0.1,1) = round(0.1,1) is true
+ Tagging with volatile is no guarantee, it may still be optimized away...
+ */
+ volatile double tmp2;
+
+ tmp=(abs_dec < array_elements(log_10) ?
+ log_10[abs_dec] : pow(10.0,(double) abs_dec));
+
+ // Pre-compute these, to avoid optimizing away e.g. 'floor(v/tmp) * tmp'.
+ volatile double value_div_tmp= value / tmp;
+ volatile double value_mul_tmp= value * tmp;
+
+ if (!dec_negative && my_isinf(tmp)) // "dec" is too large positive number
+ return value;
+
+ if (dec_negative && my_isinf(tmp))
+ tmp2= 0.0;
+ else if (!dec_negative && my_isinf(value_mul_tmp))
+ tmp2= value;
+ else if (truncate)
+ {
+ if (value >= 0.0)
+ tmp2= dec < 0 ? floor(value_div_tmp) * tmp : floor(value_mul_tmp) / tmp;
+ else
+ tmp2= dec < 0 ? ceil(value_div_tmp) * tmp : ceil(value_mul_tmp) / tmp;
+ }
+ else
+ tmp2=dec < 0 ? rint(value_div_tmp) * tmp : rint(value_mul_tmp) / tmp;
+
+ return tmp2;
+}
+
+
+double Item_func_round::real_op()
+{
+ double value= args[0]->val_real();
+
+ if (!(null_value= args[0]->null_value))
+ {
+ longlong dec= args[1]->val_int();
+ if (!(null_value= args[1]->null_value))
+ return my_double_round(value, dec, args[1]->unsigned_flag, truncate);
+ }
+ return 0.0;
+}
+
+/*
+ Rounds a given value to a power of 10 specified as the 'to' argument,
+ avoiding overflows when the value is close to the ulonglong range boundary.
+*/
+
+static inline ulonglong my_unsigned_round(ulonglong value, ulonglong to)
+{
+ ulonglong tmp= value / to * to;
+ return (value - tmp < (to >> 1)) ? tmp : tmp + to;
+}
+
+
+longlong Item_func_round::int_op()
+{
+ longlong value= args[0]->val_int();
+ longlong dec= args[1]->val_int();
+ decimals= 0;
+ ulonglong abs_dec;
+ if ((null_value= args[0]->null_value || args[1]->null_value))
+ return 0;
+ if ((dec >= 0) || args[1]->unsigned_flag)
+ return value; // integer have not digits after point
+
+ abs_dec= -dec;
+ longlong tmp;
+
+ if(abs_dec >= array_elements(log_10_int))
+ return 0;
+
+ tmp= log_10_int[abs_dec];
+
+ if (truncate)
+ value= (unsigned_flag) ?
+ ((ulonglong) value / tmp) * tmp : (value / tmp) * tmp;
+ else
+ value= (unsigned_flag || value >= 0) ?
+ my_unsigned_round((ulonglong) value, tmp) :
+ -(longlong) my_unsigned_round((ulonglong) -value, tmp);
+ return value;
+}
+
+
+my_decimal *Item_func_round::decimal_op(my_decimal *decimal_value)
+{
+ my_decimal val, *value= args[0]->val_decimal(&val);
+ longlong dec= args[1]->val_int();
+ if (dec >= 0 || args[1]->unsigned_flag)
+ dec= MY_MIN((ulonglong) dec, decimals);
+ else if (dec < INT_MIN)
+ dec= INT_MIN;
+
+ if (!(null_value= (args[0]->null_value || args[1]->null_value ||
+ my_decimal_round(E_DEC_FATAL_ERROR, value, (int) dec,
+ truncate, decimal_value) > 1)))
+ return decimal_value;
+ return 0;
+}
+
+
+void Item_func_rand::seed_random(Item *arg)
+{
+ /*
+ TODO: do not do reinit 'rand' for every execute of PS/SP if
+ args[0] is a constant.
+ */
+ uint32 tmp= (uint32) arg->val_int();
+ my_rnd_init(rand, (uint32) (tmp*0x10001L+55555555L),
+ (uint32) (tmp*0x10000001L));
+}
+
+
+bool Item_func_rand::fix_fields(THD *thd,Item **ref)
+{
+ if (Item_real_func::fix_fields(thd, ref))
+ return TRUE;
+ used_tables_cache|= RAND_TABLE_BIT;
+ if (arg_count)
+ { // Only use argument once in query
+ /*
+ Allocate rand structure once: we must use thd->stmt_arena
+ to create rand in proper mem_root if it's a prepared statement or
+ stored procedure.
+
+ No need to send a Rand log event if seed was given eg: RAND(seed),
+ as it will be replicated in the query as such.
+ */
+ if (!rand && !(rand= (struct my_rnd_struct*)
+ thd->stmt_arena->alloc(sizeof(*rand))))
+ return TRUE;
+ }
+ else
+ {
+ /*
+ Save the seed only the first time RAND() is used in the query
+ Once events are forwarded rather than recreated,
+ the following can be skipped if inside the slave thread
+ */
+ if (!thd->rand_used)
+ {
+ thd->rand_used= 1;
+ thd->rand_saved_seed1= thd->rand.seed1;
+ thd->rand_saved_seed2= thd->rand.seed2;
+ }
+ rand= &thd->rand;
+ }
+ return FALSE;
+}
+
+void Item_func_rand::update_used_tables()
+{
+ Item_real_func::update_used_tables();
+ used_tables_cache|= RAND_TABLE_BIT;
+}
+
+
+double Item_func_rand::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (arg_count)
+ {
+ if (!args[0]->const_item())
+ seed_random(args[0]);
+ else if (first_eval)
+ {
+ /*
+ Constantness of args[0] may be set during JOIN::optimize(), if arg[0]
+ is a field item of "constant" table. Thus, we have to evaluate
+ seed_random() for constant arg there but not at the fix_fields method.
+ */
+ first_eval= FALSE;
+ seed_random(args[0]);
+ }
+ }
+ return my_rnd(rand);
+}
+
+longlong Item_func_sign::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ null_value=args[0]->null_value;
+ return value < 0.0 ? -1 : (value > 0 ? 1 : 0);
+}
+
+
+double Item_func_units::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0;
+ return check_float_overflow(value * mul + add);
+}
+
+
+void Item_func_min_max::fix_length_and_dec()
+{
+ int max_int_part=0;
+ decimals=0;
+ max_length=0;
+ maybe_null=0;
+ thd= current_thd;
+ cmp_type=args[0]->result_type();
+
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ set_if_bigger(max_length, args[i]->max_length);
+ set_if_bigger(decimals, args[i]->decimals);
+ set_if_bigger(max_int_part, args[i]->decimal_int_part());
+ if (args[i]->maybe_null)
+ maybe_null= 1;
+ cmp_type= item_cmp_type(cmp_type,args[i]->result_type());
+ }
+ if (cmp_type == STRING_RESULT)
+ agg_arg_charsets_for_string_result_with_comparison(collation,
+ args, arg_count);
+ else if ((cmp_type == DECIMAL_RESULT) || (cmp_type == INT_RESULT))
+ {
+ collation.set_numeric();
+ fix_char_length(my_decimal_precision_to_length_no_truncation(max_int_part +
+ decimals,
+ decimals,
+ unsigned_flag));
+ }
+ else if (cmp_type == REAL_RESULT)
+ fix_char_length(float_length(decimals));
+
+ compare_as_dates= find_date_time_item(args, arg_count, 0);
+ if (compare_as_dates)
+ {
+ cached_field_type= compare_as_dates->field_type();
+ if (mysql_type_to_time_type(cached_field_type) == MYSQL_TIMESTAMP_DATE)
+ decimals= 0;
+ else
+ set_if_smaller(decimals, TIME_SECOND_PART_DIGITS);
+ }
+ else
+ cached_field_type= agg_field_type(args, arg_count);
+}
+
+
+/*
+ Compare item arguments in the DATETIME context.
+
+ DESCRIPTION
+ Compare item arguments as DATETIME values and return the index of the
+ least/greatest argument in the arguments array.
+ The correct DATE/DATETIME value of the found argument is
+ stored to the value pointer, if latter is provided.
+
+ RETURN
+ 1 If one of arguments is NULL or there was a execution error
+ 0 Otherwise
+*/
+
+bool Item_func_min_max::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ longlong UNINIT_VAR(min_max);
+ DBUG_ASSERT(fixed == 1);
+
+ /*
+ just like ::val_int() method of a string item can be called,
+ for example, SELECT CONCAT("10", "12") + 1,
+ ::get_date() can be called for non-temporal values,
+ for example, SELECT MONTH(GREATEST("2011-11-21", "2010-10-09"))
+
+ */
+ if (!compare_as_dates)
+ return Item_func::get_date(ltime, fuzzy_date);
+
+ for (uint i=0; i < arg_count ; i++)
+ {
+ Item **arg= args + i;
+ bool is_null;
+ longlong res= get_datetime_value(thd, &arg, 0, compare_as_dates, &is_null);
+
+ /* Check if we need to stop (because of error or KILL) and stop the loop */
+ if (thd->is_error() || args[i]->null_value)
+ {
+ return (null_value= 1);
+ }
+
+ if (i == 0 || (res < min_max ? cmp_sign : -cmp_sign) > 0)
+ min_max= res;
+ }
+ unpack_time(min_max, ltime);
+
+ if (compare_as_dates->field_type() == MYSQL_TYPE_DATE)
+ {
+ ltime->time_type= MYSQL_TIMESTAMP_DATE;
+ ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0;
+ }
+ else if (compare_as_dates->field_type() == MYSQL_TYPE_TIME)
+ {
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+ ltime->hour+= (ltime->month * 32 + ltime->day) * 24;
+ ltime->year= ltime->month= ltime->day= 0;
+ if (adjust_time_range_with_warn(ltime,
+ std::min<uint>(decimals, TIME_SECOND_PART_DIGITS)))
+ return (null_value= true);
+ }
+
+ if (!(fuzzy_date & TIME_TIME_ONLY) &&
+ ((null_value= check_date_with_warn(ltime, fuzzy_date,
+ MYSQL_TIMESTAMP_ERROR))))
+ return true;
+
+ return (null_value= 0);
+}
+
+
+String *Item_func_min_max::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (compare_as_dates)
+ return val_string_from_date(str);
+ switch (cmp_type) {
+ case INT_RESULT:
+ return val_string_from_int(str);
+ case DECIMAL_RESULT:
+ return val_string_from_decimal(str);
+ case REAL_RESULT:
+ return val_string_from_real(str);
+ case STRING_RESULT:
+ {
+ String *UNINIT_VAR(res);
+ for (uint i=0; i < arg_count ; i++)
+ {
+ if (i == 0)
+ res=args[i]->val_str(str);
+ else
+ {
+ String *res2;
+ res2= args[i]->val_str(res == str ? &tmp_value : str);
+ if (res2)
+ {
+ int cmp= sortcmp(res,res2,collation.collation);
+ if ((cmp_sign < 0 ? cmp : -cmp) < 0)
+ res=res2;
+ }
+ }
+ if ((null_value= args[i]->null_value))
+ return 0;
+ }
+ res->set_charset(collation.collation);
+ return res;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ return 0;
+ }
+ return 0; // Keep compiler happy
+}
+
+
+double Item_func_min_max::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double value=0.0;
+ if (compare_as_dates)
+ {
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, 0))
+ return 0;
+
+ return TIME_to_double(&ltime);
+ }
+ for (uint i=0; i < arg_count ; i++)
+ {
+ if (i == 0)
+ value= args[i]->val_real();
+ else
+ {
+ double tmp= args[i]->val_real();
+ if (!args[i]->null_value && (tmp < value ? cmp_sign : -cmp_sign) > 0)
+ value=tmp;
+ }
+ if ((null_value= args[i]->null_value))
+ break;
+ }
+ return value;
+}
+
+
+longlong Item_func_min_max::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong value=0;
+ if (compare_as_dates)
+ {
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, 0))
+ return 0;
+
+ return TIME_to_ulonglong(&ltime);
+ }
+ for (uint i=0; i < arg_count ; i++)
+ {
+ if (i == 0)
+ value=args[i]->val_int();
+ else
+ {
+ longlong tmp=args[i]->val_int();
+ if (!args[i]->null_value && (tmp < value ? cmp_sign : -cmp_sign) > 0)
+ value=tmp;
+ }
+ if ((null_value= args[i]->null_value))
+ break;
+ }
+ return value;
+}
+
+
+my_decimal *Item_func_min_max::val_decimal(my_decimal *dec)
+{
+ DBUG_ASSERT(fixed == 1);
+ my_decimal tmp_buf, *tmp, *UNINIT_VAR(res);
+
+ if (compare_as_dates)
+ {
+ MYSQL_TIME ltime;
+ if (get_date(&ltime, 0))
+ return 0;
+
+ return date2my_decimal(&ltime, dec);
+ }
+ for (uint i=0; i < arg_count ; i++)
+ {
+ if (i == 0)
+ res= args[i]->val_decimal(dec);
+ else
+ {
+ tmp= args[i]->val_decimal(&tmp_buf); // Zero if NULL
+ if (tmp && (my_decimal_cmp(tmp, res) * cmp_sign) < 0)
+ {
+ if (tmp == &tmp_buf)
+ {
+ /* Move value out of tmp_buf as this will be reused on next loop */
+ my_decimal2decimal(tmp, dec);
+ res= dec;
+ }
+ else
+ res= tmp;
+ }
+ }
+ if ((null_value= args[i]->null_value))
+ {
+ res= 0;
+ break;
+ }
+ }
+ return res;
+}
+
+
+longlong Item_func_length::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res=args[0]->val_str(&value);
+ if (!res)
+ {
+ null_value=1;
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+ return (longlong) res->length();
+}
+
+
+longlong Item_func_char_length::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res=args[0]->val_str(&value);
+ if (!res)
+ {
+ null_value=1;
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+ return (longlong) res->numchars();
+}
+
+
+longlong Item_func_coercibility::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ null_value= 0;
+ return (longlong) args[0]->collation.derivation;
+}
+
+
+void Item_func_locate::fix_length_and_dec()
+{
+ max_length= MY_INT32_NUM_DECIMAL_DIGITS;
+ agg_arg_charsets_for_comparison(cmp_collation, args, 2);
+}
+
+
+longlong Item_func_locate::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *a=args[0]->val_str(&value1);
+ String *b=args[1]->val_str(&value2);
+ if (!a || !b)
+ {
+ null_value=1;
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+ /* must be longlong to avoid truncation */
+ longlong start= 0;
+ longlong start0= 0;
+ my_match_t match;
+
+ if (arg_count == 3)
+ {
+ start0= start= args[2]->val_int() - 1;
+
+ if ((start < 0) || (start > a->length()))
+ return 0;
+
+ /* start is now sufficiently valid to pass to charpos function */
+ start= a->charpos((int) start);
+
+ if (start + b->length() > a->length())
+ return 0;
+ }
+
+ if (!b->length()) // Found empty string at start
+ return start + 1;
+
+ if (!cmp_collation.collation->coll->instr(cmp_collation.collation,
+ a->ptr()+start,
+ (uint) (a->length()-start),
+ b->ptr(), b->length(),
+ &match, 1))
+ return 0;
+ return (longlong) match.mb_len + start0 + 1;
+}
+
+
+void Item_func_locate::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("locate("));
+ args[1]->print(str, query_type);
+ str->append(',');
+ args[0]->print(str, query_type);
+ if (arg_count == 3)
+ {
+ str->append(',');
+ args[2]->print(str, query_type);
+ }
+ str->append(')');
+}
+
+
+longlong Item_func_field::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+
+ if (cmp_type == STRING_RESULT)
+ {
+ String *field;
+ if (!(field= args[0]->val_str(&value)))
+ return 0;
+ for (uint i=1 ; i < arg_count ; i++)
+ {
+ String *tmp_value=args[i]->val_str(&tmp);
+ if (tmp_value && !sortcmp(field,tmp_value,cmp_collation.collation))
+ return (longlong) (i);
+ }
+ }
+ else if (cmp_type == INT_RESULT)
+ {
+ longlong val= args[0]->val_int();
+ if (args[0]->null_value)
+ return 0;
+ for (uint i=1; i < arg_count ; i++)
+ {
+ if (val == args[i]->val_int() && !args[i]->null_value)
+ return (longlong) (i);
+ }
+ }
+ else if (cmp_type == DECIMAL_RESULT)
+ {
+ my_decimal dec_arg_buf, *dec_arg,
+ dec_buf, *dec= args[0]->val_decimal(&dec_buf);
+ if (args[0]->null_value)
+ return 0;
+ for (uint i=1; i < arg_count; i++)
+ {
+ dec_arg= args[i]->val_decimal(&dec_arg_buf);
+ if (!args[i]->null_value && !my_decimal_cmp(dec_arg, dec))
+ return (longlong) (i);
+ }
+ }
+ else
+ {
+ double val= args[0]->val_real();
+ if (args[0]->null_value)
+ return 0;
+ for (uint i=1; i < arg_count ; i++)
+ {
+ if (val == args[i]->val_real() && !args[i]->null_value)
+ return (longlong) (i);
+ }
+ }
+ return 0;
+}
+
+
+void Item_func_field::fix_length_and_dec()
+{
+ maybe_null=0; max_length=3;
+ cmp_type= args[0]->result_type();
+ for (uint i=1; i < arg_count ; i++)
+ cmp_type= item_cmp_type(cmp_type, args[i]->result_type());
+ if (cmp_type == STRING_RESULT)
+ agg_arg_charsets_for_comparison(cmp_collation, args, arg_count);
+}
+
+
+longlong Item_func_ascii::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res=args[0]->val_str(&value);
+ if (!res)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ return (longlong) (res->length() ? (uchar) (*res)[0] : (uchar) 0);
+}
+
+longlong Item_func_ord::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res=args[0]->val_str(&value);
+ if (!res)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ if (!res->length()) return 0;
+#ifdef USE_MB
+ if (use_mb(res->charset()))
+ {
+ register const char *str=res->ptr();
+ register uint32 n=0, l=my_ismbchar(res->charset(),str,str+res->length());
+ if (!l)
+ return (longlong)((uchar) *str);
+ while (l--)
+ n=(n<<8)|(uint32)((uchar) *str++);
+ return (longlong) n;
+ }
+#endif
+ return (longlong) ((uchar) (*res)[0]);
+}
+
+ /* Search after a string in a string of strings separated by ',' */
+ /* Returns number of found type >= 1 or 0 if not found */
+ /* This optimizes searching in enums to bit testing! */
+
+void Item_func_find_in_set::fix_length_and_dec()
+{
+ decimals=0;
+ max_length=3; // 1-999
+ if (args[0]->const_item() && args[1]->type() == FIELD_ITEM)
+ {
+ Field *field= ((Item_field*) args[1])->field;
+ if (field->real_type() == MYSQL_TYPE_SET)
+ {
+ String *find=args[0]->val_str(&value);
+ if (find)
+ {
+ // find is not NULL pointer so args[0] is not a null-value
+ DBUG_ASSERT(!args[0]->null_value);
+ enum_value= find_type(((Field_enum*) field)->typelib,find->ptr(),
+ find->length(), 0);
+ enum_bit=0;
+ if (enum_value)
+ enum_bit=1LL << (enum_value-1);
+ }
+ }
+ }
+ agg_arg_charsets_for_comparison(cmp_collation, args, 2);
+}
+
+static const char separator=',';
+
+longlong Item_func_find_in_set::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (enum_value)
+ {
+ // enum_value is set iff args[0]->const_item() in fix_length_and_dec().
+ DBUG_ASSERT(args[0]->const_item());
+
+ ulonglong tmp= (ulonglong) args[1]->val_int();
+ null_value= args[1]->null_value;
+ /*
+ No need to check args[0]->null_value since enum_value is set iff
+ args[0] is a non-null const item. Note: no DBUG_ASSERT on
+ args[0]->null_value here because args[0] may have been replaced
+ by an Item_cache on which val_int() has not been called. See
+ BUG#11766317
+ */
+ if (!null_value)
+ {
+ if (tmp & enum_bit)
+ return enum_value;
+ }
+ return 0L;
+ }
+
+ String *find=args[0]->val_str(&value);
+ String *buffer=args[1]->val_str(&value2);
+ if (!find || !buffer)
+ {
+ null_value=1;
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+
+ if ((int) (buffer->length() - find->length()) >= 0)
+ {
+ my_wc_t wc= 0;
+ CHARSET_INFO *cs= cmp_collation.collation;
+ const char *str_begin= buffer->ptr();
+ const char *str_end= buffer->ptr();
+ const char *real_end= str_end+buffer->length();
+ const uchar *find_str= (const uchar *) find->ptr();
+ uint find_str_len= find->length();
+ int position= 0;
+ while (1)
+ {
+ int symbol_len;
+ if ((symbol_len= cs->cset->mb_wc(cs, &wc, (uchar*) str_end,
+ (uchar*) real_end)) > 0)
+ {
+ const char *substr_end= str_end + symbol_len;
+ bool is_last_item= (substr_end == real_end);
+ bool is_separator= (wc == (my_wc_t) separator);
+ if (is_separator || is_last_item)
+ {
+ position++;
+ if (is_last_item && !is_separator)
+ str_end= substr_end;
+ if (!my_strnncoll(cs, (const uchar *) str_begin,
+ (uint) (str_end - str_begin),
+ find_str, find_str_len))
+ return (longlong) position;
+ else
+ str_begin= substr_end;
+ }
+ str_end= substr_end;
+ }
+ else if (str_end - str_begin == 0 &&
+ find_str_len == 0 &&
+ wc == (my_wc_t) separator)
+ return (longlong) ++position;
+ else
+ return 0;
+ }
+ }
+ return 0;
+}
+
+longlong Item_func_bit_count::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ ulonglong value= (ulonglong) args[0]->val_int();
+ if ((null_value= args[0]->null_value))
+ return 0; /* purecov: inspected */
+ return (longlong) my_count_bits(value);
+}
+
+
+/****************************************************************************
+** Functions to handle dynamic loadable functions
+** Original source by: Alexis Mikhailov <root@medinf.chuvashia.su>
+** Rewritten by monty.
+****************************************************************************/
+
+#ifdef HAVE_DLOPEN
+
+void udf_handler::cleanup()
+{
+ if (!not_original)
+ {
+ if (initialized)
+ {
+ if (u_d->func_deinit != NULL)
+ {
+ Udf_func_deinit deinit= u_d->func_deinit;
+ (*deinit)(&initid);
+ }
+ free_udf(u_d);
+ initialized= FALSE;
+ }
+ if (buffers) // Because of bug in ecc
+ delete [] buffers;
+ buffers= 0;
+ }
+}
+
+
+bool
+udf_handler::fix_fields(THD *thd, Item_result_field *func,
+ uint arg_count, Item **arguments)
+{
+ uchar buff[STACK_BUFF_ALLOC]; // Max argument in function
+ DBUG_ENTER("Item_udf_func::fix_fields");
+
+ if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
+ DBUG_RETURN(TRUE); // Fatal error flag is set!
+
+ udf_func *tmp_udf=find_udf(u_d->name.str,(uint) u_d->name.length,1);
+
+ if (!tmp_udf)
+ {
+ my_error(ER_CANT_FIND_UDF, MYF(0), u_d->name.str);
+ DBUG_RETURN(TRUE);
+ }
+ u_d=tmp_udf;
+ args=arguments;
+
+ /* Fix all arguments */
+ func->maybe_null=0;
+ used_tables_cache=0;
+ const_item_cache=1;
+
+ if ((f_args.arg_count=arg_count))
+ {
+ if (!(f_args.arg_type= (Item_result*)
+ sql_alloc(f_args.arg_count*sizeof(Item_result))))
+
+ {
+ free_udf(u_d);
+ DBUG_RETURN(TRUE);
+ }
+ uint i;
+ Item **arg,**arg_end;
+ for (i=0, arg=arguments, arg_end=arguments+arg_count;
+ arg != arg_end ;
+ arg++,i++)
+ {
+ if (!(*arg)->fixed &&
+ (*arg)->fix_fields(thd, arg))
+ DBUG_RETURN(1);
+ // we can't assign 'item' before, because fix_fields() can change arg
+ Item *item= *arg;
+ if (item->check_cols(1))
+ DBUG_RETURN(TRUE);
+ /*
+ TODO: We should think about this. It is not always
+ right way just to set an UDF result to return my_charset_bin
+ if one argument has binary sorting order.
+ The result collation should be calculated according to arguments
+ derivations in some cases and should not in other cases.
+ Moreover, some arguments can represent a numeric input
+ which doesn't effect the result character set and collation.
+ There is no a general rule for UDF. Everything depends on
+ the particular user defined function.
+ */
+ if (item->collation.collation->state & MY_CS_BINSORT)
+ func->collation.set(&my_charset_bin);
+ if (item->maybe_null)
+ func->maybe_null=1;
+ func->with_sum_func= func->with_sum_func || item->with_sum_func;
+ func->with_field= func->with_field || item->with_field;
+ func->with_subselect|= item->with_subselect;
+ used_tables_cache|=item->used_tables();
+ const_item_cache&=item->const_item();
+ f_args.arg_type[i]=item->result_type();
+ }
+ //TODO: why all following memory is not allocated with 1 call of sql_alloc?
+ if (!(buffers=new String[arg_count]) ||
+ !(f_args.args= (char**) sql_alloc(arg_count * sizeof(char *))) ||
+ !(f_args.lengths= (ulong*) sql_alloc(arg_count * sizeof(long))) ||
+ !(f_args.maybe_null= (char*) sql_alloc(arg_count * sizeof(char))) ||
+ !(num_buffer= (char*) sql_alloc(arg_count *
+ ALIGN_SIZE(sizeof(double)))) ||
+ !(f_args.attributes= (char**) sql_alloc(arg_count * sizeof(char *))) ||
+ !(f_args.attribute_lengths= (ulong*) sql_alloc(arg_count *
+ sizeof(long))))
+ {
+ free_udf(u_d);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ func->fix_length_and_dec();
+ initid.max_length=func->max_length;
+ initid.maybe_null=func->maybe_null;
+ initid.const_item=const_item_cache;
+ initid.decimals=func->decimals;
+ initid.ptr=0;
+
+ if (u_d->func_init)
+ {
+ char init_msg_buff[MYSQL_ERRMSG_SIZE];
+ char *to=num_buffer;
+ for (uint i=0; i < arg_count; i++)
+ {
+ /*
+ For a constant argument i, args->args[i] points to the argument value.
+ For non-constant, args->args[i] is NULL.
+ */
+ f_args.args[i]= NULL; /* Non-const unless updated below. */
+
+ f_args.lengths[i]= arguments[i]->max_length;
+ f_args.maybe_null[i]= (char) arguments[i]->maybe_null;
+ f_args.attributes[i]= arguments[i]->name;
+ f_args.attribute_lengths[i]= arguments[i]->name_length;
+
+ if (arguments[i]->const_item())
+ {
+ switch (arguments[i]->result_type()) {
+ case STRING_RESULT:
+ case DECIMAL_RESULT:
+ {
+ String *res= arguments[i]->val_str(&buffers[i]);
+ if (arguments[i]->null_value)
+ continue;
+ f_args.args[i]= (char*) res->c_ptr_safe();
+ f_args.lengths[i]= res->length();
+ break;
+ }
+ case INT_RESULT:
+ *((longlong*) to)= arguments[i]->val_int();
+ if (arguments[i]->null_value)
+ continue;
+ f_args.args[i]= to;
+ to+= ALIGN_SIZE(sizeof(longlong));
+ break;
+ case REAL_RESULT:
+ *((double*) to)= arguments[i]->val_real();
+ if (arguments[i]->null_value)
+ continue;
+ f_args.args[i]= to;
+ to+= ALIGN_SIZE(sizeof(double));
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ break;
+ }
+ }
+ }
+ Udf_func_init init= u_d->func_init;
+ if ((error=(uchar) init(&initid, &f_args, init_msg_buff)))
+ {
+ my_error(ER_CANT_INITIALIZE_UDF, MYF(0),
+ u_d->name.str, init_msg_buff);
+ free_udf(u_d);
+ DBUG_RETURN(TRUE);
+ }
+ func->max_length=MY_MIN(initid.max_length,MAX_BLOB_WIDTH);
+ func->maybe_null=initid.maybe_null;
+ const_item_cache=initid.const_item;
+ /*
+ Keep used_tables_cache in sync with const_item_cache.
+ See the comment in Item_udf_func::update_used tables.
+ */
+ if (!const_item_cache && !used_tables_cache)
+ used_tables_cache= RAND_TABLE_BIT;
+ func->decimals=MY_MIN(initid.decimals,NOT_FIXED_DEC);
+ }
+ initialized=1;
+ if (error)
+ {
+ my_error(ER_CANT_INITIALIZE_UDF, MYF(0),
+ u_d->name.str, ER(ER_UNKNOWN_ERROR));
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+bool udf_handler::get_arguments()
+{
+ if (error)
+ return 1; // Got an error earlier
+ char *to= num_buffer;
+ uint str_count=0;
+ for (uint i=0; i < f_args.arg_count; i++)
+ {
+ f_args.args[i]=0;
+ switch (f_args.arg_type[i]) {
+ case STRING_RESULT:
+ case DECIMAL_RESULT:
+ {
+ String *res=args[i]->val_str(&buffers[str_count++]);
+ if (!(args[i]->null_value))
+ {
+ f_args.args[i]= (char*) res->ptr();
+ f_args.lengths[i]= res->length();
+ }
+ else
+ {
+ f_args.lengths[i]= 0;
+ }
+ break;
+ }
+ case INT_RESULT:
+ *((longlong*) to) = args[i]->val_int();
+ if (!args[i]->null_value)
+ {
+ f_args.args[i]=to;
+ to+= ALIGN_SIZE(sizeof(longlong));
+ }
+ break;
+ case REAL_RESULT:
+ *((double*) to)= args[i]->val_real();
+ if (!args[i]->null_value)
+ {
+ f_args.args[i]=to;
+ to+= ALIGN_SIZE(sizeof(double));
+ }
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ @return
+ (String*)NULL in case of NULL values
+*/
+String *udf_handler::val_str(String *str,String *save_str)
+{
+ uchar is_null_tmp=0;
+ ulong res_length;
+ DBUG_ENTER("udf_handler::val_str");
+
+ if (get_arguments())
+ DBUG_RETURN(0);
+ char * (*func)(UDF_INIT *, UDF_ARGS *, char *, ulong *, uchar *, uchar *)=
+ (char* (*)(UDF_INIT *, UDF_ARGS *, char *, ulong *, uchar *, uchar *))
+ u_d->func;
+
+ if ((res_length=str->alloced_length()) < MAX_FIELD_WIDTH)
+ { // This happens VERY seldom
+ if (str->alloc(MAX_FIELD_WIDTH))
+ {
+ error=1;
+ DBUG_RETURN(0);
+ }
+ }
+ char *res=func(&initid, &f_args, (char*) str->ptr(), &res_length,
+ &is_null_tmp, &error);
+ DBUG_PRINT("info", ("udf func returned, res_length: %lu", res_length));
+ if (is_null_tmp || !res || error) // The !res is for safety
+ {
+ DBUG_PRINT("info", ("Null or error"));
+ DBUG_RETURN(0);
+ }
+ if (res == str->ptr())
+ {
+ str->length(res_length);
+ DBUG_PRINT("exit", ("str: %*.s", (int) str->length(), str->ptr()));
+ DBUG_RETURN(str);
+ }
+ save_str->set(res, res_length, str->charset());
+ DBUG_PRINT("exit", ("save_str: %s", save_str->ptr()));
+ DBUG_RETURN(save_str);
+}
+
+
+/*
+ For the moment, UDF functions are returning DECIMAL values as strings
+*/
+
+my_decimal *udf_handler::val_decimal(my_bool *null_value, my_decimal *dec_buf)
+{
+ char buf[DECIMAL_MAX_STR_LENGTH+1], *end;
+ ulong res_length= DECIMAL_MAX_STR_LENGTH;
+
+ if (get_arguments())
+ {
+ *null_value=1;
+ return 0;
+ }
+ char *(*func)(UDF_INIT *, UDF_ARGS *, char *, ulong *, uchar *, uchar *)=
+ (char* (*)(UDF_INIT *, UDF_ARGS *, char *, ulong *, uchar *, uchar *))
+ u_d->func;
+
+ char *res= func(&initid, &f_args, buf, &res_length, &is_null, &error);
+ if (is_null || error)
+ {
+ *null_value= 1;
+ return 0;
+ }
+ end= res+ res_length;
+ str2my_decimal(E_DEC_FATAL_ERROR, res, dec_buf, &end);
+ return dec_buf;
+}
+
+
+void Item_udf_func::cleanup()
+{
+ udf.cleanup();
+ Item_func::cleanup();
+}
+
+
+void Item_udf_func::print(String *str, enum_query_type query_type)
+{
+ str->append(func_name());
+ str->append('(');
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ if (i != 0)
+ str->append(',');
+ args[i]->print_item_w_name(str, query_type);
+ }
+ str->append(')');
+}
+
+
+double Item_func_udf_float::val_real()
+{
+ double res;
+ my_bool tmp_null_value;
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_func_udf_float::val");
+ DBUG_PRINT("info",("result_type: %d arg_count: %d",
+ args[0]->result_type(), arg_count));
+ res= udf.val(&tmp_null_value);
+ null_value= tmp_null_value;
+ DBUG_RETURN(res);
+}
+
+
+String *Item_func_udf_float::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ double nr= val_real();
+ if (null_value)
+ return 0; /* purecov: inspected */
+ str->set_real(nr,decimals,&my_charset_bin);
+ return str;
+}
+
+
+longlong Item_func_udf_int::val_int()
+{
+ longlong res;
+ my_bool tmp_null_value;
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_func_udf_int::val_int");
+ res= udf.val_int(&tmp_null_value);
+ null_value= tmp_null_value;
+ DBUG_RETURN(res);
+}
+
+
+String *Item_func_udf_int::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong nr=val_int();
+ if (null_value)
+ return 0;
+ str->set_int(nr, unsigned_flag, &my_charset_bin);
+ return str;
+}
+
+
+longlong Item_func_udf_decimal::val_int()
+{
+ my_bool tmp_null_value;
+ longlong result;
+ my_decimal dec_buf, *dec= udf.val_decimal(&tmp_null_value, &dec_buf);
+ null_value= tmp_null_value;
+ if (null_value)
+ return 0;
+ my_decimal2int(E_DEC_FATAL_ERROR, dec, unsigned_flag, &result);
+ return result;
+}
+
+
+double Item_func_udf_decimal::val_real()
+{
+ my_bool tmp_null_value;
+ double result;
+ my_decimal dec_buf, *dec= udf.val_decimal(&tmp_null_value, &dec_buf);
+ null_value= tmp_null_value;
+ if (null_value)
+ return 0.0;
+ my_decimal2double(E_DEC_FATAL_ERROR, dec, &result);
+ return result;
+}
+
+
+my_decimal *Item_func_udf_decimal::val_decimal(my_decimal *dec_buf)
+{
+ my_decimal *res;
+ my_bool tmp_null_value;
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_func_udf_decimal::val_decimal");
+ DBUG_PRINT("info",("result_type: %d arg_count: %d",
+ args[0]->result_type(), arg_count));
+
+ res= udf.val_decimal(&tmp_null_value, dec_buf);
+ null_value= tmp_null_value;
+ DBUG_RETURN(res);
+}
+
+
+String *Item_func_udf_decimal::val_str(String *str)
+{
+ my_bool tmp_null_value;
+ my_decimal dec_buf, *dec= udf.val_decimal(&tmp_null_value, &dec_buf);
+ null_value= tmp_null_value;
+ if (null_value)
+ return 0;
+ if (str->length() < DECIMAL_MAX_STR_LENGTH)
+ str->length(DECIMAL_MAX_STR_LENGTH);
+ my_decimal_round(E_DEC_FATAL_ERROR, dec, decimals, FALSE, &dec_buf);
+ my_decimal2string(E_DEC_FATAL_ERROR, &dec_buf, 0, 0, '0', str);
+ return str;
+}
+
+
+/* Default max_length is max argument length */
+
+void Item_func_udf_str::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_func_udf_str::fix_length_and_dec");
+ max_length=0;
+ for (uint i = 0; i < arg_count; i++)
+ set_if_bigger(max_length,args[i]->max_length);
+ DBUG_VOID_RETURN;
+}
+
+String *Item_func_udf_str::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res=udf.val_str(str,&str_value);
+ null_value = !res;
+ return res;
+}
+
+
+/**
+ @note
+ This has to come last in the udf_handler methods, or C for AIX
+ version 6.0.0.0 fails to compile with debugging enabled. (Yes, really.)
+*/
+
+udf_handler::~udf_handler()
+{
+ /* Everything should be properly cleaned up by this moment. */
+ DBUG_ASSERT(not_original || !(initialized || buffers));
+}
+
+#else
+bool udf_handler::get_arguments() { return 0; }
+#endif /* HAVE_DLOPEN */
+
+
+longlong Item_master_pos_wait::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ THD* thd = current_thd;
+ String *log_name = args[0]->val_str(&value);
+ int event_count= 0;
+
+ null_value=0;
+ if (thd->slave_thread || !log_name || !log_name->length())
+ {
+ null_value = 1;
+ return 0;
+ }
+#ifdef HAVE_REPLICATION
+ longlong pos = (ulong)args[1]->val_int();
+ longlong timeout = (arg_count>=3) ? args[2]->val_int() : 0 ;
+ String connection_name_buff;
+ LEX_STRING connection_name;
+ Master_info *mi;
+ if (arg_count >= 4)
+ {
+ String *con;
+ if (!(con= args[3]->val_str(&connection_name_buff)))
+ goto err;
+
+ connection_name.str= (char*) con->ptr();
+ connection_name.length= con->length();
+ if (check_master_connection_name(&connection_name))
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(ME_JUST_WARNING),
+ "MASTER_CONNECTION_NAME");
+ goto err;
+ }
+ }
+ else
+ connection_name= thd->variables.default_master_connection;
+
+ mysql_mutex_lock(&LOCK_active_mi);
+ mi= master_info_index->get_master_info(&connection_name,
+ Sql_condition::WARN_LEVEL_WARN);
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (!mi)
+ goto err;
+
+ if ((event_count = mi->rli.wait_for_pos(thd, log_name, pos, timeout)) == -2)
+ {
+ null_value = 1;
+ event_count=0;
+ }
+#endif
+ return event_count;
+
+#ifdef HAVE_REPLICATION
+err:
+ {
+ null_value = 1;
+ return 0;
+ }
+#endif
+}
+
+
+longlong Item_master_gtid_wait::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong result= 0;
+
+ if (args[0]->null_value)
+ {
+ null_value= 1;
+ return 0;
+ }
+
+ null_value=0;
+#ifdef HAVE_REPLICATION
+ THD* thd= current_thd;
+ longlong timeout_us;
+ String *gtid_pos = args[0]->val_str(&value);
+
+ if (arg_count==2 && !args[1]->null_value)
+ timeout_us= (longlong)(1e6*args[1]->val_real());
+ else
+ timeout_us= (longlong)-1;
+
+ result= rpl_global_gtid_waiting.wait_for_pos(thd, gtid_pos, timeout_us);
+#endif
+ return result;
+}
+
+
+/**
+ Enables a session to wait on a condition until a timeout or a network
+ disconnect occurs.
+
+ @remark The connection is polled every m_interrupt_interval nanoseconds.
+*/
+
+class Interruptible_wait
+{
+ THD *m_thd;
+ struct timespec m_abs_timeout;
+ static const ulonglong m_interrupt_interval;
+
+ public:
+ Interruptible_wait(THD *thd)
+ : m_thd(thd) {}
+
+ ~Interruptible_wait() {}
+
+ public:
+ /**
+ Set the absolute timeout.
+
+ @param timeout The amount of time in nanoseconds to wait
+ */
+ void set_timeout(ulonglong timeout)
+ {
+ /*
+ Calculate the absolute system time at the start so it can
+ be controlled in slices. It relies on the fact that once
+ the absolute time passes, the timed wait call will fail
+ automatically with a timeout error.
+ */
+ set_timespec_nsec(m_abs_timeout, timeout);
+ }
+
+ /** The timed wait. */
+ int wait(mysql_cond_t *, mysql_mutex_t *);
+};
+
+
+/** Time to wait before polling the connection status. */
+const ulonglong Interruptible_wait::m_interrupt_interval= 5 * 1000000000ULL;
+
+
+/**
+ Wait for a given condition to be signaled.
+
+ @param cond The condition variable to wait on.
+ @param mutex The associated mutex.
+
+ @remark The absolute timeout is preserved across calls.
+
+ @retval return value from mysql_cond_timedwait
+*/
+
+int Interruptible_wait::wait(mysql_cond_t *cond, mysql_mutex_t *mutex)
+{
+ int error;
+ struct timespec timeout;
+
+ while (1)
+ {
+ /* Wait for a fixed interval. */
+ set_timespec_nsec(timeout, m_interrupt_interval);
+
+ /* But only if not past the absolute timeout. */
+ if (cmp_timespec(timeout, m_abs_timeout) > 0)
+ timeout= m_abs_timeout;
+
+ error= mysql_cond_timedwait(cond, mutex, &timeout);
+ if (error == ETIMEDOUT || error == ETIME)
+ {
+ /* Return error if timed out or connection is broken. */
+ if (!cmp_timespec(timeout, m_abs_timeout) || !m_thd->is_connected())
+ break;
+ }
+ /* Otherwise, propagate status to the caller. */
+ else
+ break;
+ }
+
+ return error;
+}
+
+
+/**
+ For locks with EXPLICIT duration, MDL returns a new ticket
+ every time a lock is granted. This allows to implement recursive
+ locks without extra allocation or additional data structures, such
+ as below. However, if there are too many tickets in the same
+ MDL_context, MDL_context::find_ticket() is getting too slow,
+ since it's using a linear search.
+ This is why a separate structure is allocated for a user
+ level lock, and before requesting a new lock from MDL,
+ GET_LOCK() checks thd->ull_hash if such lock is already granted,
+ and if so, simply increments a reference counter.
+*/
+
+class User_level_lock
+{
+public:
+ MDL_ticket *lock;
+ int refs;
+};
+
+
+/** Extract a hash key from User_level_lock. */
+
+uchar *ull_get_key(const uchar *ptr, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ User_level_lock *ull = (User_level_lock*) ptr;
+ MDL_key *key = ull->lock->get_key();
+ *length= key->length();
+ return (uchar*) key->ptr();
+}
+
+
+/**
+ Release all user level locks for this THD.
+*/
+
+void mysql_ull_cleanup(THD *thd)
+{
+ User_level_lock *ull;
+ DBUG_ENTER("mysql_ull_cleanup");
+
+ for (uint i= 0; i < thd->ull_hash.records; i++)
+ {
+ ull = (User_level_lock*) my_hash_element(&thd->ull_hash, i);
+ thd->mdl_context.release_lock(ull->lock);
+ my_free(ull);
+ }
+
+ my_hash_free(&thd->ull_hash);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set explicit duration for metadata locks corresponding to
+ user level locks to protect them from being released at the end
+ of transaction.
+*/
+
+void mysql_ull_set_explicit_lock_duration(THD *thd)
+{
+ User_level_lock *ull;
+ DBUG_ENTER("mysql_ull_set_explicit_lock_duration");
+
+ for (uint i= 0; i < thd->ull_hash.records; i++)
+ {
+ ull= (User_level_lock*) my_hash_element(&thd->ull_hash, i);
+ thd->mdl_context.set_lock_duration(ull->lock, MDL_EXPLICIT);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ When MDL detects a lock wait timeout, it pushes
+ an error into the statement diagnostics area.
+ For GET_LOCK(), lock wait timeout is not an error,
+ but a special return value (0).
+ Similarly, killing get_lock wait is not an error either,
+ but a return value NULL.
+ Capture and suppress lock wait timeouts and kills.
+*/
+
+class Lock_wait_timeout_handler: public Internal_error_handler
+{
+public:
+ Lock_wait_timeout_handler() :m_lock_wait_timeout(false) {}
+
+ bool m_lock_wait_timeout;
+
+ bool handle_condition(THD * /* thd */, uint sql_errno,
+ const char * /* sqlstate */,
+ Sql_condition::enum_warning_level /* level */,
+ const char *message,
+ Sql_condition ** /* cond_hdl */);
+};
+
+bool
+Lock_wait_timeout_handler::
+handle_condition(THD *thd, uint sql_errno,
+ const char * /* sqlstate */,
+ Sql_condition::enum_warning_level /* level */,
+ const char *message,
+ Sql_condition ** /* cond_hdl */)
+{
+ if (sql_errno == ER_LOCK_WAIT_TIMEOUT)
+ {
+ m_lock_wait_timeout= true;
+ return true; /* condition handled */
+ }
+ if (thd->is_killed())
+ return true;
+
+ return false;
+}
+
+
+static int ull_name_ok(String *name)
+{
+ if (!name || !name->length())
+ return 0;
+
+ if (name->length() > NAME_LEN)
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), name->c_ptr_safe());
+ return 0;
+ }
+ return 1;
+}
+
+
+/**
+ Get a user level lock.
+
+ @retval
+ 1 : Got lock
+ @retval
+ 0 : Timeout
+ @retval
+ NULL : Error
+*/
+
+longlong Item_func_get_lock::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(&value);
+ ulonglong timeout= args[1]->val_int();
+ THD *thd= current_thd;
+ User_level_lock *ull;
+ DBUG_ENTER("Item_func_get_lock::val_int");
+
+ null_value= 1;
+ /*
+ In slave thread no need to get locks, everything is serialized. Anyway
+ there is no way to make GET_LOCK() work on slave like it did on master
+ (i.e. make it return exactly the same value) because we don't have the
+ same other concurrent threads environment. No matter what we return here,
+ it's not guaranteed to be same as on master.
+ */
+ if (thd->slave_thread)
+ {
+ null_value= 0;
+ DBUG_RETURN(1);
+ }
+
+ if (!ull_name_ok(res))
+ DBUG_RETURN(0);
+ DBUG_PRINT("enter", ("lock: %.*s", res->length(), res->ptr()));
+ /* HASH entries are of type User_level_lock. */
+ if (! my_hash_inited(&thd->ull_hash) &&
+ my_hash_init(&thd->ull_hash, &my_charset_bin,
+ 16 /* small hash */, 0, 0, ull_get_key, NULL, 0))
+ {
+ DBUG_RETURN(0);
+ }
+
+ MDL_request ull_request;
+ ull_request.init(MDL_key::USER_LOCK, res->c_ptr_safe(), "",
+ MDL_SHARED_NO_WRITE, MDL_EXPLICIT);
+ MDL_key *ull_key = &ull_request.key;
+
+
+ if ((ull= (User_level_lock*)
+ my_hash_search(&thd->ull_hash, ull_key->ptr(), ull_key->length())))
+ {
+ /* Recursive lock */
+ ull->refs++;
+ null_value = 0;
+ DBUG_PRINT("info", ("recursive lock, ref-count: %d", (int) ull->refs));
+ DBUG_RETURN(1);
+ }
+
+ Lock_wait_timeout_handler lock_wait_timeout_handler;
+ thd->push_internal_handler(&lock_wait_timeout_handler);
+ bool error= thd->mdl_context.acquire_lock(&ull_request, timeout);
+ (void) thd->pop_internal_handler();
+ if (error)
+ {
+ if (lock_wait_timeout_handler.m_lock_wait_timeout)
+ null_value= 0;
+ DBUG_RETURN(0);
+ }
+
+ ull= (User_level_lock*) my_malloc(sizeof(User_level_lock),
+ MYF(MY_WME|MY_THREAD_SPECIFIC));
+ if (ull == NULL)
+ {
+ thd->mdl_context.release_lock(ull_request.ticket);
+ DBUG_RETURN(0);
+ }
+
+ ull->lock= ull_request.ticket;
+ ull->refs= 1;
+
+ if (my_hash_insert(&thd->ull_hash, (uchar*) ull))
+ {
+ thd->mdl_context.release_lock(ull->lock);
+ my_free(ull);
+ DBUG_RETURN(0);
+ }
+ null_value= 0;
+
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Release a user level lock.
+ @return
+ - 1 if lock released
+ - 0 if lock wasn't held
+ - (SQL) NULL if no such lock
+*/
+
+longlong Item_func_release_lock::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(&value);
+ THD *thd= current_thd;
+ DBUG_ENTER("Item_func_release_lock::val_int");
+ null_value= 1;
+
+ if (!ull_name_ok(res))
+ DBUG_RETURN(0);
+
+ DBUG_PRINT("enter", ("lock: %.*s", res->length(), res->ptr()));
+
+ MDL_key ull_key;
+ ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), "");
+
+ User_level_lock *ull;
+
+ if (!(ull=
+ (User_level_lock*) my_hash_search(&thd->ull_hash,
+ ull_key.ptr(), ull_key.length())))
+ {
+ null_value= thd->mdl_context.get_lock_owner(&ull_key) == 0;
+ DBUG_RETURN(0);
+ }
+ DBUG_PRINT("info", ("ref count: %d", (int) ull->refs));
+ null_value= 0;
+ if (--ull->refs == 0)
+ {
+ my_hash_delete(&thd->ull_hash, (uchar*) ull);
+ thd->mdl_context.release_lock(ull->lock);
+ my_free(ull);
+ }
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Check a user level lock.
+
+ Sets null_value=TRUE on error.
+
+ @retval
+ 1 Available
+ @retval
+ 0 Already taken, or error
+*/
+
+longlong Item_func_is_free_lock::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(&value);
+ THD *thd= current_thd;
+ null_value= 1;
+
+ if (!ull_name_ok(res))
+ return 0;
+
+ MDL_key ull_key;
+ ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), "");
+
+ null_value= 0;
+ return thd->mdl_context.get_lock_owner(&ull_key) == 0;
+}
+
+
+longlong Item_func_is_used_lock::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(&value);
+ THD *thd= current_thd;
+ null_value= 1;
+
+ if (!ull_name_ok(res))
+ return 0;
+
+ MDL_key ull_key;
+ ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), "");
+ ulong thread_id = thd->mdl_context.get_lock_owner(&ull_key);
+ if (thread_id == 0)
+ return 0;
+
+ null_value= 0;
+ return thread_id;
+}
+
+
+longlong Item_func_last_insert_id::val_int()
+{
+ THD *thd= current_thd;
+ DBUG_ASSERT(fixed == 1);
+ if (arg_count)
+ {
+ longlong value= args[0]->val_int();
+ null_value= args[0]->null_value;
+ /*
+ LAST_INSERT_ID(X) must affect the client's mysql_insert_id() as
+ documented in the manual. We don't want to touch
+ first_successful_insert_id_in_cur_stmt because it would make
+ LAST_INSERT_ID(X) take precedence over an generated auto_increment
+ value for this row.
+ */
+ thd->arg_of_last_insert_id_function= TRUE;
+ thd->first_successful_insert_id_in_prev_stmt= value;
+ return value;
+ }
+ return
+ static_cast<longlong>(thd->read_first_successful_insert_id_in_prev_stmt());
+}
+
+
+bool Item_func_last_insert_id::fix_fields(THD *thd, Item **ref)
+{
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ return Item_int_func::fix_fields(thd, ref);
+}
+
+
+/* This function is just used to test speed of different functions */
+
+longlong Item_func_benchmark::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff), &my_charset_bin);
+ my_decimal tmp_decimal;
+ THD *thd=current_thd;
+ ulonglong loop_count;
+
+ loop_count= (ulonglong) args[0]->val_int();
+
+ if (args[0]->null_value ||
+ (!args[0]->unsigned_flag && (((longlong) loop_count) < 0)))
+ {
+ if (!args[0]->null_value)
+ {
+ char buff[22];
+ llstr(((longlong) loop_count), buff);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_VALUE_FOR_TYPE, ER(ER_WRONG_VALUE_FOR_TYPE),
+ "count", buff, "benchmark");
+ }
+
+ null_value= 1;
+ return 0;
+ }
+
+ null_value=0;
+ for (ulonglong loop=0 ; loop < loop_count && !thd->killed; loop++)
+ {
+ switch (args[1]->result_type()) {
+ case REAL_RESULT:
+ (void) args[1]->val_real();
+ break;
+ case INT_RESULT:
+ (void) args[1]->val_int();
+ break;
+ case STRING_RESULT:
+ (void) args[1]->val_str(&tmp);
+ break;
+ case DECIMAL_RESULT:
+ (void) args[1]->val_decimal(&tmp_decimal);
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ return 0;
+ }
+ }
+ return 0;
+}
+
+
+void Item_func_benchmark::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("benchmark("));
+ args[0]->print(str, query_type);
+ str->append(',');
+ args[1]->print(str, query_type);
+ str->append(')');
+}
+
+
+mysql_mutex_t LOCK_item_func_sleep;
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_LOCK_item_func_sleep;
+
+static PSI_mutex_info item_func_sleep_mutexes[]=
+{
+ { &key_LOCK_item_func_sleep, "LOCK_user_locks", PSI_FLAG_GLOBAL}
+};
+
+
+static void init_item_func_sleep_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(item_func_sleep_mutexes);
+ PSI_server->register_mutex(category, item_func_sleep_mutexes, count);
+}
+#endif
+
+static bool item_func_sleep_inited= 0;
+
+
+void item_func_sleep_init(void)
+{
+#ifdef HAVE_PSI_INTERFACE
+ init_item_func_sleep_psi_keys();
+#endif
+
+ mysql_mutex_init(key_LOCK_item_func_sleep, &LOCK_item_func_sleep, MY_MUTEX_INIT_SLOW);
+ item_func_sleep_inited= 1;
+}
+
+
+void item_func_sleep_free(void)
+{
+ if (item_func_sleep_inited)
+ {
+ item_func_sleep_inited= 0;
+ mysql_mutex_destroy(&LOCK_item_func_sleep);
+ }
+}
+
+
+/** This function is just used to create tests with time gaps. */
+
+longlong Item_func_sleep::val_int()
+{
+ THD *thd= current_thd;
+ Interruptible_wait timed_cond(thd);
+ mysql_cond_t cond;
+ double timeout;
+ int error;
+
+ DBUG_ASSERT(fixed == 1);
+
+ timeout= args[0]->val_real();
+ /*
+ On 64-bit OSX mysql_cond_timedwait() waits forever
+ if passed abstime time has already been exceeded by
+ the system time.
+ When given a very short timeout (< 10 mcs) just return
+ immediately.
+ We assume that the lines between this test and the call
+ to mysql_cond_timedwait() will be executed in less than 0.00001 sec.
+ */
+ if (timeout < 0.00001)
+ return 0;
+
+ timed_cond.set_timeout((ulonglong) (timeout * 1000000000.0));
+
+ mysql_cond_init(key_item_func_sleep_cond, &cond, NULL);
+ mysql_mutex_lock(&LOCK_item_func_sleep);
+
+ THD_STAGE_INFO(thd, stage_user_sleep);
+ thd->mysys_var->current_mutex= &LOCK_item_func_sleep;
+ thd->mysys_var->current_cond= &cond;
+
+ error= 0;
+ thd_wait_begin(thd, THD_WAIT_SLEEP);
+ while (!thd->killed)
+ {
+ error= timed_cond.wait(&cond, &LOCK_item_func_sleep);
+ if (error == ETIMEDOUT || error == ETIME)
+ break;
+ error= 0;
+ }
+ thd_wait_end(thd);
+ mysql_mutex_unlock(&LOCK_item_func_sleep);
+ mysql_mutex_lock(&thd->mysys_var->mutex);
+ thd->mysys_var->current_mutex= 0;
+ thd->mysys_var->current_cond= 0;
+ mysql_mutex_unlock(&thd->mysys_var->mutex);
+
+ mysql_cond_destroy(&cond);
+
+ DBUG_EXECUTE_IF("sleep_inject_query_done_debug_sync", {
+ debug_sync_set_action
+ (thd, STRING_WITH_LEN("dispatch_command_end SIGNAL query_done"));
+ };);
+
+ return MY_TEST(!error); // Return 1 killed
+}
+
+
+#define extra_size sizeof(double)
+
+user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
+ bool create_if_not_exists)
+{
+ user_var_entry *entry;
+
+ if (!(entry = (user_var_entry*) my_hash_search(hash, (uchar*) name.str,
+ name.length)) &&
+ create_if_not_exists)
+ {
+ uint size=ALIGN_SIZE(sizeof(user_var_entry))+name.length+1+extra_size;
+ if (!my_hash_inited(hash))
+ return 0;
+ if (!(entry = (user_var_entry*) my_malloc(size,
+ MYF(MY_WME | ME_FATALERROR |
+ MY_THREAD_SPECIFIC))))
+ return 0;
+ entry->name.str=(char*) entry+ ALIGN_SIZE(sizeof(user_var_entry))+
+ extra_size;
+ entry->name.length=name.length;
+ entry->value=0;
+ entry->length=0;
+ entry->update_query_id=0;
+ entry->collation.set(NULL, DERIVATION_IMPLICIT, 0);
+ entry->unsigned_flag= 0;
+ /*
+ If we are here, we were called from a SET or a query which sets a
+ variable. Imagine it is this:
+ INSERT INTO t SELECT @a:=10, @a:=@a+1.
+ Then when we have a Item_func_get_user_var (because of the @a+1) so we
+ think we have to write the value of @a to the binlog. But before that,
+ we have a Item_func_set_user_var to create @a (@a:=10), in this we mark
+ the variable as "already logged" (line below) so that it won't be logged
+ by Item_func_get_user_var (because that's not necessary).
+ */
+ entry->used_query_id=current_thd->query_id;
+ entry->type=STRING_RESULT;
+ memcpy(entry->name.str, name.str, name.length+1);
+ if (my_hash_insert(hash,(uchar*) entry))
+ {
+ my_free(entry);
+ return 0;
+ }
+ }
+ return entry;
+}
+
+
+void Item_func_set_user_var::cleanup()
+{
+ Item_func::cleanup();
+ entry= NULL;
+}
+
+
+bool Item_func_set_user_var::set_entry(THD *thd, bool create_if_not_exists)
+{
+ if (entry && thd->thread_id == entry_thread_id)
+ goto end; // update entry->update_query_id for PS
+ if (!(entry= get_variable(&thd->user_vars, name, create_if_not_exists)))
+ {
+ entry_thread_id= 0;
+ return TRUE;
+ }
+ entry_thread_id= thd->thread_id;
+ /*
+ Remember the last query which updated it, this way a query can later know
+ if this variable is a constant item in the query (it is if update_query_id
+ is different from query_id).
+ */
+end:
+ entry->update_query_id= thd->query_id;
+ return FALSE;
+}
+
+
+/*
+ When a user variable is updated (in a SET command or a query like
+ SELECT @a:= ).
+*/
+
+bool Item_func_set_user_var::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ /* fix_fields will call Item_func_set_user_var::fix_length_and_dec */
+ if (Item_func::fix_fields(thd, ref) || set_entry(thd, TRUE))
+ return TRUE;
+ /*
+ As it is wrong and confusing to associate any
+ character set with NULL, @a should be latin2
+ after this query sequence:
+
+ SET @a=_latin2'string';
+ SET @a=NULL;
+
+ I.e. the second query should not change the charset
+ to the current default value, but should keep the
+ original value assigned during the first query.
+ In order to do it, we don't copy charset
+ from the argument if the argument is NULL
+ and the variable has previously been initialized.
+ */
+ null_item= (args[0]->type() == NULL_ITEM);
+ if (!entry->collation.collation || !null_item)
+ entry->collation.set(args[0]->collation.derivation == DERIVATION_NUMERIC ?
+ default_charset() : args[0]->collation.collation,
+ DERIVATION_IMPLICIT);
+ collation.set(entry->collation.collation, DERIVATION_IMPLICIT);
+ cached_result_type= args[0]->result_type();
+ if (thd->lex->current_select)
+ {
+ /*
+ When this function is used in a derived table/view force the derived
+ table to be materialized to preserve possible side-effect of setting a
+ user variable.
+ */
+ SELECT_LEX_UNIT *unit= thd->lex->current_select->master_unit();
+ TABLE_LIST *derived;
+ for (derived= unit->derived;
+ derived;
+ derived= derived->select_lex->master_unit()->derived)
+ derived->set_materialized_derived();
+ }
+
+ return FALSE;
+}
+
+
+void
+Item_func_set_user_var::fix_length_and_dec()
+{
+ maybe_null=args[0]->maybe_null;
+ decimals=args[0]->decimals;
+ collation.set(DERIVATION_IMPLICIT);
+ if (args[0]->collation.derivation == DERIVATION_NUMERIC)
+ fix_length_and_charset(args[0]->max_char_length(), default_charset());
+ else
+ {
+ fix_length_and_charset(args[0]->max_char_length(),
+ args[0]->collation.collation);
+ }
+ unsigned_flag= args[0]->unsigned_flag;
+}
+
+
+/*
+ Mark field in read_map
+
+ NOTES
+ This is used by filesort to register used fields in a a temporary
+ column read set or to register used fields in a view
+*/
+
+bool Item_func_set_user_var::register_field_in_read_map(uchar *arg)
+{
+ if (result_field)
+ {
+ TABLE *table= (TABLE *) arg;
+ if (result_field->table == table || !table)
+ bitmap_set_bit(result_field->table->read_set, result_field->field_index);
+ if (result_field->vcol_info)
+ return result_field->vcol_info->
+ expr_item->walk(&Item::register_field_in_read_map, 1, arg);
+ }
+ return 0;
+}
+
+/*
+ Mark field in bitmap supplied as *arg
+
+*/
+
+bool Item_func_set_user_var::register_field_in_bitmap(uchar *arg)
+{
+ MY_BITMAP *bitmap = (MY_BITMAP *) arg;
+ DBUG_ASSERT(bitmap);
+ if (result_field)
+ {
+ if (!bitmap)
+ return 1;
+ bitmap_set_bit(bitmap, result_field->field_index);
+ }
+ return 0;
+}
+
+/**
+ Set value to user variable.
+
+ @param entry pointer to structure representing variable
+ @param set_null should we set NULL value ?
+ @param ptr pointer to buffer with new value
+ @param length length of new value
+ @param type type of new value
+ @param cs charset info for new value
+ @param dv derivation for new value
+ @param unsigned_arg indiates if a value of type INT_RESULT is unsigned
+
+ @note Sets error and fatal error if allocation fails.
+
+ @retval
+ false success
+ @retval
+ true failure
+*/
+
+static bool
+update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length,
+ Item_result type, CHARSET_INFO *cs, Derivation dv,
+ bool unsigned_arg)
+{
+ if (set_null)
+ {
+ char *pos= (char*) entry+ ALIGN_SIZE(sizeof(user_var_entry));
+ if (entry->value && entry->value != pos)
+ my_free(entry->value);
+ entry->value= 0;
+ entry->length= 0;
+ }
+ else
+ {
+ if (type == STRING_RESULT)
+ length++; // Store strings with end \0
+ if (length <= extra_size)
+ {
+ /* Save value in value struct */
+ char *pos= (char*) entry+ ALIGN_SIZE(sizeof(user_var_entry));
+ if (entry->value != pos)
+ {
+ if (entry->value)
+ my_free(entry->value);
+ entry->value=pos;
+ }
+ }
+ else
+ {
+ /* Allocate variable */
+ if (entry->length != length)
+ {
+ char *pos= (char*) entry+ ALIGN_SIZE(sizeof(user_var_entry));
+ if (entry->value == pos)
+ entry->value=0;
+ entry->value= (char*) my_realloc(entry->value, length,
+ MYF(MY_ALLOW_ZERO_PTR | MY_WME |
+ ME_FATALERROR |
+ MY_THREAD_SPECIFIC));
+ if (!entry->value)
+ return 1;
+ }
+ }
+ if (type == STRING_RESULT)
+ {
+ length--; // Fix length change above
+ entry->value[length]= 0; // Store end \0
+ }
+ memmove(entry->value, ptr, length);
+ if (type == DECIMAL_RESULT)
+ ((my_decimal*)entry->value)->fix_buffer_pointer();
+ entry->length= length;
+ entry->collation.set(cs, dv);
+ entry->unsigned_flag= unsigned_arg;
+ }
+ entry->type=type;
+ return 0;
+}
+
+
+bool
+Item_func_set_user_var::update_hash(void *ptr, uint length,
+ Item_result res_type,
+ CHARSET_INFO *cs, Derivation dv,
+ bool unsigned_arg)
+{
+ /*
+ If we set a variable explicitely to NULL then keep the old
+ result type of the variable
+ */
+ if ((null_value= args[0]->null_value) && null_item)
+ res_type= entry->type; // Don't change type of item
+ if (::update_hash(entry, (null_value= args[0]->null_value),
+ ptr, length, res_type, cs, dv, unsigned_arg))
+ {
+ null_value= 1;
+ return 1;
+ }
+ return 0;
+}
+
+
+/** Get the value of a variable as a double. */
+
+double user_var_entry::val_real(bool *null_value)
+{
+ if ((*null_value= (value == 0)))
+ return 0.0;
+
+ switch (type) {
+ case REAL_RESULT:
+ return *(double*) value;
+ case INT_RESULT:
+ return (double) *(longlong*) value;
+ case DECIMAL_RESULT:
+ {
+ double result;
+ my_decimal2double(E_DEC_FATAL_ERROR, (my_decimal *)value, &result);
+ return result;
+ }
+ case STRING_RESULT:
+ return my_atof(value); // This is null terminated
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // Impossible
+ break;
+ }
+ return 0.0; // Impossible
+}
+
+
+/** Get the value of a variable as an integer. */
+
+longlong user_var_entry::val_int(bool *null_value) const
+{
+ if ((*null_value= (value == 0)))
+ return 0;
+
+ switch (type) {
+ case REAL_RESULT:
+ return (longlong) *(double*) value;
+ case INT_RESULT:
+ return *(longlong*) value;
+ case DECIMAL_RESULT:
+ {
+ longlong result;
+ my_decimal2int(E_DEC_FATAL_ERROR, (my_decimal *)value, 0, &result);
+ return result;
+ }
+ case STRING_RESULT:
+ {
+ int error;
+ return my_strtoll10(value, (char**) 0, &error);// String is null terminated
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // Impossible
+ break;
+ }
+ return 0; // Impossible
+}
+
+
+/** Get the value of a variable as a string. */
+
+String *user_var_entry::val_str(bool *null_value, String *str,
+ uint decimals)
+{
+ if ((*null_value= (value == 0)))
+ return (String*) 0;
+
+ switch (type) {
+ case REAL_RESULT:
+ str->set_real(*(double*) value, decimals, collation.collation);
+ break;
+ case INT_RESULT:
+ if (!unsigned_flag)
+ str->set(*(longlong*) value, collation.collation);
+ else
+ str->set(*(ulonglong*) value, collation.collation);
+ break;
+ case DECIMAL_RESULT:
+ str_set_decimal((my_decimal *) value, str, collation.collation);
+ break;
+ case STRING_RESULT:
+ if (str->copy(value, length, collation.collation))
+ str= 0; // EOM error
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // Impossible
+ break;
+ }
+ return(str);
+}
+
+/** Get the value of a variable as a decimal. */
+
+my_decimal *user_var_entry::val_decimal(bool *null_value, my_decimal *val)
+{
+ if ((*null_value= (value == 0)))
+ return 0;
+
+ switch (type) {
+ case REAL_RESULT:
+ double2my_decimal(E_DEC_FATAL_ERROR, *(double*) value, val);
+ break;
+ case INT_RESULT:
+ int2my_decimal(E_DEC_FATAL_ERROR, *(longlong*) value, 0, val);
+ break;
+ case DECIMAL_RESULT:
+ my_decimal2decimal((my_decimal *) value, val);
+ break;
+ case STRING_RESULT:
+ str2my_decimal(E_DEC_FATAL_ERROR, value, length, collation.collation, val);
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // Impossible
+ break;
+ }
+ return(val);
+}
+
+/**
+ This functions is invoked on SET \@variable or
+ \@variable:= expression.
+
+ Evaluate (and check expression), store results.
+
+ @note
+ For now it always return OK. All problem with value evaluating
+ will be caught by thd->is_error() check in sql_set_variables().
+
+ @retval
+ FALSE OK.
+*/
+
+bool
+Item_func_set_user_var::check(bool use_result_field)
+{
+ DBUG_ENTER("Item_func_set_user_var::check");
+ if (use_result_field && !result_field)
+ use_result_field= FALSE;
+
+ switch (cached_result_type) {
+ case REAL_RESULT:
+ {
+ save_result.vreal= use_result_field ? result_field->val_real() :
+ args[0]->val_real();
+ break;
+ }
+ case INT_RESULT:
+ {
+ save_result.vint= use_result_field ? result_field->val_int() :
+ args[0]->val_int();
+ unsigned_flag= (use_result_field ?
+ ((Field_num*)result_field)->unsigned_flag:
+ args[0]->unsigned_flag);
+ break;
+ }
+ case STRING_RESULT:
+ {
+ save_result.vstr= use_result_field ? result_field->val_str(&value) :
+ args[0]->val_str(&value);
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ save_result.vdec= use_result_field ?
+ result_field->val_decimal(&decimal_buff) :
+ args[0]->val_decimal(&decimal_buff);
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ break;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief Evaluate and store item's result.
+ This function is invoked on "SELECT ... INTO @var ...".
+
+ @param item An item to get value from.
+*/
+
+void Item_func_set_user_var::save_item_result(Item *item)
+{
+ DBUG_ENTER("Item_func_set_user_var::save_item_result");
+
+ switch (args[0]->result_type()) {
+ case REAL_RESULT:
+ save_result.vreal= item->val_result();
+ break;
+ case INT_RESULT:
+ save_result.vint= item->val_int_result();
+ unsigned_flag= item->unsigned_flag;
+ break;
+ case STRING_RESULT:
+ save_result.vstr= item->str_result(&value);
+ break;
+ case DECIMAL_RESULT:
+ save_result.vdec= item->val_decimal_result(&decimal_buff);
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ break;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ This functions is invoked on
+ SET \@variable or \@variable:= expression.
+
+ @note
+ We have to store the expression as such in the variable, independent of
+ the value method used by the user
+
+ @retval
+ 0 OK
+ @retval
+ 1 EOM Error
+
+*/
+
+bool
+Item_func_set_user_var::update()
+{
+ bool res= 0;
+ DBUG_ENTER("Item_func_set_user_var::update");
+
+ switch (cached_result_type) {
+ case REAL_RESULT:
+ {
+ res= update_hash((void*) &save_result.vreal,sizeof(save_result.vreal),
+ REAL_RESULT, default_charset(), DERIVATION_IMPLICIT, 0);
+ break;
+ }
+ case INT_RESULT:
+ {
+ res= update_hash((void*) &save_result.vint, sizeof(save_result.vint),
+ INT_RESULT, default_charset(), DERIVATION_IMPLICIT,
+ unsigned_flag);
+ break;
+ }
+ case STRING_RESULT:
+ {
+ if (!save_result.vstr) // Null value
+ res= update_hash((void*) 0, 0, STRING_RESULT, &my_charset_bin,
+ DERIVATION_IMPLICIT, 0);
+ else
+ res= update_hash((void*) save_result.vstr->ptr(),
+ save_result.vstr->length(), STRING_RESULT,
+ save_result.vstr->charset(),
+ DERIVATION_IMPLICIT, 0);
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ if (!save_result.vdec) // Null value
+ res= update_hash((void*) 0, 0, DECIMAL_RESULT, &my_charset_bin,
+ DERIVATION_IMPLICIT, 0);
+ else
+ res= update_hash((void*) save_result.vdec,
+ sizeof(my_decimal), DECIMAL_RESULT,
+ default_charset(), DERIVATION_IMPLICIT, 0);
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ break;
+ }
+ DBUG_RETURN(res);
+}
+
+
+double Item_func_set_user_var::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ check(0);
+ update(); // Store expression
+ return entry->val_real(&null_value);
+}
+
+longlong Item_func_set_user_var::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ check(0);
+ update(); // Store expression
+ return entry->val_int(&null_value);
+}
+
+String *Item_func_set_user_var::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ check(0);
+ update(); // Store expression
+ return entry->val_str(&null_value, str, decimals);
+}
+
+
+my_decimal *Item_func_set_user_var::val_decimal(my_decimal *val)
+{
+ DBUG_ASSERT(fixed == 1);
+ check(0);
+ update(); // Store expression
+ return entry->val_decimal(&null_value, val);
+}
+
+
+double Item_func_set_user_var::val_result()
+{
+ DBUG_ASSERT(fixed == 1);
+ check(TRUE);
+ update(); // Store expression
+ return entry->val_real(&null_value);
+}
+
+longlong Item_func_set_user_var::val_int_result()
+{
+ DBUG_ASSERT(fixed == 1);
+ check(TRUE);
+ update(); // Store expression
+ return entry->val_int(&null_value);
+}
+
+bool Item_func_set_user_var::val_bool_result()
+{
+ DBUG_ASSERT(fixed == 1);
+ check(TRUE);
+ update(); // Store expression
+ return entry->val_int(&null_value) != 0;
+}
+
+String *Item_func_set_user_var::str_result(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ check(TRUE);
+ update(); // Store expression
+ return entry->val_str(&null_value, str, decimals);
+}
+
+
+my_decimal *Item_func_set_user_var::val_decimal_result(my_decimal *val)
+{
+ DBUG_ASSERT(fixed == 1);
+ check(TRUE);
+ update(); // Store expression
+ return entry->val_decimal(&null_value, val);
+}
+
+
+bool Item_func_set_user_var::is_null_result()
+{
+ DBUG_ASSERT(fixed == 1);
+ check(TRUE);
+ update(); // Store expression
+ return is_null();
+}
+
+
+void Item_func_set_user_var::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("(@"));
+ str->append(name.str, name.length);
+ str->append(STRING_WITH_LEN(":="));
+ args[0]->print(str, query_type);
+ str->append(')');
+}
+
+
+void Item_func_set_user_var::print_as_stmt(String *str,
+ enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("set @"));
+ str->append(name.str, name.length);
+ str->append(STRING_WITH_LEN(":="));
+ args[0]->print(str, query_type);
+ str->append(')');
+}
+
+bool Item_func_set_user_var::send(Protocol *protocol, String *str_arg)
+{
+ if (result_field)
+ {
+ check(1);
+ update();
+ return protocol->store(result_field);
+ }
+ return Item::send(protocol, str_arg);
+}
+
+void Item_func_set_user_var::make_field(Send_field *tmp_field)
+{
+ if (result_field)
+ {
+ result_field->make_field(tmp_field);
+ DBUG_ASSERT(tmp_field->table_name != 0);
+ if (Item::name)
+ tmp_field->col_name=Item::name; // Use user supplied name
+ }
+ else
+ Item::make_field(tmp_field);
+}
+
+
+/*
+ Save the value of a user variable into a field
+
+ SYNOPSIS
+ save_in_field()
+ field target field to save the value to
+ no_conversion flag indicating whether conversions are allowed
+
+ DESCRIPTION
+ Save the function value into a field and update the user variable
+ accordingly. If a result field is defined and the target field doesn't
+ coincide with it then the value from the result field will be used as
+ the new value of the user variable.
+
+ The reason to have this method rather than simply using the result
+ field in the val_xxx() methods is that the value from the result field
+ not always can be used when the result field is defined.
+ Let's consider the following cases:
+ 1) when filling a tmp table the result field is defined but the value of it
+ is undefined because it has to be produced yet. Thus we can't use it.
+ 2) on execution of an INSERT ... SELECT statement the save_in_field()
+ function will be called to fill the data in the new record. If the SELECT
+ part uses a tmp table then the result field is defined and should be
+ used in order to get the correct result.
+
+ The difference between the SET_USER_VAR function and regular functions
+ like CONCAT is that the Item_func objects for the regular functions are
+ replaced by Item_field objects after the values of these functions have
+ been stored in a tmp table. Yet an object of the Item_field class cannot
+ be used to update a user variable.
+ Due to this we have to handle the result field in a special way here and
+ in the Item_func_set_user_var::send() function.
+
+ RETURN VALUES
+ FALSE Ok
+ TRUE Error
+*/
+
+int Item_func_set_user_var::save_in_field(Field *field, bool no_conversions,
+ bool can_use_result_field)
+{
+ bool use_result_field= (!can_use_result_field ? 0 :
+ (result_field && result_field != field));
+ int error;
+
+ /* Update the value of the user variable */
+ check(use_result_field);
+ update();
+
+ if (result_type() == STRING_RESULT ||
+ (result_type() == REAL_RESULT &&
+ field->result_type() == STRING_RESULT))
+ {
+ String *result;
+ CHARSET_INFO *cs= collation.collation;
+ char buff[MAX_FIELD_WIDTH]; // Alloc buffer for small columns
+ str_value.set_quick(buff, sizeof(buff), cs);
+ result= entry->val_str(&null_value, &str_value, decimals);
+
+ if (null_value)
+ {
+ str_value.set_quick(0, 0, cs);
+ return set_field_to_null_with_conversions(field, no_conversions);
+ }
+
+ /* NOTE: If null_value == FALSE, "result" must be not NULL. */
+
+ field->set_notnull();
+ error=field->store(result->ptr(),result->length(),cs);
+ str_value.set_quick(0, 0, cs);
+ }
+ else if (result_type() == REAL_RESULT)
+ {
+ double nr= entry->val_real(&null_value);
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ error=field->store(nr);
+ }
+ else if (result_type() == DECIMAL_RESULT)
+ {
+ my_decimal decimal_value;
+ my_decimal *val= entry->val_decimal(&null_value, &decimal_value);
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ error=field->store_decimal(val);
+ }
+ else
+ {
+ longlong nr= entry->val_int(&null_value);
+ if (null_value)
+ return set_field_to_null_with_conversions(field, no_conversions);
+ field->set_notnull();
+ error=field->store(nr, unsigned_flag);
+ }
+ return error;
+}
+
+
+String *
+Item_func_get_user_var::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_func_get_user_var::val_str");
+ if (!var_entry)
+ DBUG_RETURN((String*) 0); // No such variable
+ DBUG_RETURN(var_entry->val_str(&null_value, str, decimals));
+}
+
+
+double Item_func_get_user_var::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!var_entry)
+ return 0.0; // No such variable
+ return (var_entry->val_real(&null_value));
+}
+
+
+my_decimal *Item_func_get_user_var::val_decimal(my_decimal *dec)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!var_entry)
+ return 0;
+ return var_entry->val_decimal(&null_value, dec);
+}
+
+
+longlong Item_func_get_user_var::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!var_entry)
+ return 0; // No such variable
+ return (var_entry->val_int(&null_value));
+}
+
+
+/**
+ Get variable by name and, if necessary, put the record of variable
+ use into the binary log.
+
+ When a user variable is invoked from an update query (INSERT, UPDATE etc),
+ stores this variable and its value in thd->user_var_events, so that it can be
+ written to the binlog (will be written just before the query is written, see
+ log.cc).
+
+ @param thd Current thread
+ @param name Variable name
+ @param[out] out_entry variable structure or NULL. The pointer is set
+ regardless of whether function succeeded or not.
+
+ @retval
+ 0 OK
+ @retval
+ 1 Failed to put appropriate record into binary log
+
+*/
+
+static int
+get_var_with_binlog(THD *thd, enum_sql_command sql_command,
+ LEX_STRING &name, user_var_entry **out_entry)
+{
+ BINLOG_USER_VAR_EVENT *user_var_event;
+ user_var_entry *var_entry;
+ var_entry= get_variable(&thd->user_vars, name, 0);
+
+ /*
+ Any reference to user-defined variable which is done from stored
+ function or trigger affects their execution and the execution of the
+ calling statement. We must log all such variables even if they are
+ not involved in table-updating statements.
+ */
+ if (!(opt_bin_log &&
+ (is_update_query(sql_command) || thd->in_sub_stmt)))
+ {
+ *out_entry= var_entry;
+ return 0;
+ }
+
+ if (!var_entry)
+ {
+ /*
+ If the variable does not exist, it's NULL, but we want to create it so
+ that it gets into the binlog (if it didn't, the slave could be
+ influenced by a variable of the same name previously set by another
+ thread).
+ We create it like if it had been explicitly set with SET before.
+ The 'new' mimics what sql_yacc.yy does when 'SET @a=10;'.
+ sql_set_variables() is what is called from 'case SQLCOM_SET_OPTION'
+ in dispatch_command()). Instead of building a one-element list to pass to
+ sql_set_variables(), we could instead manually call check() and update();
+ this would save memory and time; but calling sql_set_variables() makes
+ one unique place to maintain (sql_set_variables()).
+
+ Manipulation with lex is necessary since free_underlaid_joins
+ is going to release memory belonging to the main query.
+ */
+
+ List<set_var_base> tmp_var_list;
+ LEX *sav_lex= thd->lex, lex_tmp;
+ thd->lex= &lex_tmp;
+ lex_start(thd);
+ tmp_var_list.push_back(new set_var_user(new Item_func_set_user_var(name,
+ new Item_null())));
+ /* Create the variable */
+ if (sql_set_variables(thd, &tmp_var_list))
+ {
+ thd->lex= sav_lex;
+ goto err;
+ }
+ thd->lex= sav_lex;
+ if (!(var_entry= get_variable(&thd->user_vars, name, 0)))
+ goto err;
+ }
+ else if (var_entry->used_query_id == thd->query_id ||
+ mysql_bin_log.is_query_in_union(thd, var_entry->used_query_id))
+ {
+ /*
+ If this variable was already stored in user_var_events by this query
+ (because it's used in more than one place in the query), don't store
+ it.
+ */
+ *out_entry= var_entry;
+ return 0;
+ }
+
+ uint size;
+ /*
+ First we need to store value of var_entry, when the next situation
+ appears:
+ > set @a:=1;
+ > insert into t1 values (@a), (@a:=@a+1), (@a:=@a+1);
+ We have to write to binlog value @a= 1.
+
+ We allocate the user_var_event on user_var_events_alloc pool, not on
+ the this-statement-execution pool because in SPs user_var_event objects
+ may need to be valid after current [SP] statement execution pool is
+ destroyed.
+ */
+ size= ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)) + var_entry->length;
+ if (!(user_var_event= (BINLOG_USER_VAR_EVENT *)
+ alloc_root(thd->user_var_events_alloc, size)))
+ goto err;
+
+ user_var_event->value= (char*) user_var_event +
+ ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT));
+ user_var_event->user_var_event= var_entry;
+ user_var_event->type= var_entry->type;
+ user_var_event->charset_number= var_entry->collation.collation->number;
+ user_var_event->unsigned_flag= var_entry->unsigned_flag;
+ if (!var_entry->value)
+ {
+ /* NULL value*/
+ user_var_event->length= 0;
+ user_var_event->value= 0;
+ }
+ else
+ {
+ user_var_event->length= var_entry->length;
+ memcpy(user_var_event->value, var_entry->value,
+ var_entry->length);
+ }
+ /* Mark that this variable has been used by this query */
+ var_entry->used_query_id= thd->query_id;
+ if (insert_dynamic(&thd->user_var_events, (uchar*) &user_var_event))
+ goto err;
+
+ *out_entry= var_entry;
+ return 0;
+
+err:
+ *out_entry= var_entry;
+ return 1;
+}
+
+void Item_func_get_user_var::fix_length_and_dec()
+{
+ THD *thd=current_thd;
+ int error;
+ maybe_null=1;
+ decimals=NOT_FIXED_DEC;
+ max_length=MAX_BLOB_WIDTH;
+
+ error= get_var_with_binlog(thd, thd->lex->sql_command, name, &var_entry);
+
+ /*
+ If the variable didn't exist it has been created as a STRING-type.
+ 'var_entry' is NULL only if there occured an error during the call to
+ get_var_with_binlog.
+ */
+ if (!error && var_entry)
+ {
+ m_cached_result_type= var_entry->type;
+ unsigned_flag= var_entry->unsigned_flag;
+ max_length= var_entry->length;
+
+ collation.set(var_entry->collation);
+ switch (m_cached_result_type) {
+ case REAL_RESULT:
+ fix_char_length(DBL_DIG + 8);
+ break;
+ case INT_RESULT:
+ fix_char_length(MAX_BIGINT_WIDTH);
+ decimals=0;
+ break;
+ case STRING_RESULT:
+ max_length= MAX_BLOB_WIDTH - 1;
+ break;
+ case DECIMAL_RESULT:
+ fix_char_length(DECIMAL_MAX_STR_LENGTH);
+ decimals= DECIMAL_MAX_SCALE;
+ break;
+ case ROW_RESULT: // Keep compiler happy
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0); // This case should never be chosen
+ break;
+ }
+ }
+ else
+ {
+ collation.set(&my_charset_bin, DERIVATION_IMPLICIT);
+ null_value= 1;
+ m_cached_result_type= STRING_RESULT;
+ max_length= MAX_BLOB_WIDTH;
+ }
+}
+
+
+bool Item_func_get_user_var::const_item() const
+{
+ return (!var_entry || current_thd->query_id != var_entry->update_query_id);
+}
+
+
+enum Item_result Item_func_get_user_var::result_type() const
+{
+ return m_cached_result_type;
+}
+
+
+void Item_func_get_user_var::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("(@"));
+ append_identifier(current_thd, str, name.str, name.length);
+ str->append(')');
+}
+
+
+bool Item_func_get_user_var::eq(const Item *item, bool binary_cmp) const
+{
+ /* Assume we don't have rtti */
+ if (this == item)
+ return 1; // Same item is same.
+ /* Check if other type is also a get_user_var() object */
+ if (item->type() != FUNC_ITEM ||
+ ((Item_func*) item)->functype() != functype())
+ return 0;
+ Item_func_get_user_var *other=(Item_func_get_user_var*) item;
+ return (name.length == other->name.length &&
+ !memcmp(name.str, other->name.str, name.length));
+}
+
+
+bool Item_func_get_user_var::set_value(THD *thd,
+ sp_rcontext * /*ctx*/, Item **it)
+{
+ Item_func_set_user_var *suv= new Item_func_set_user_var(get_name(), *it);
+ /*
+ Item_func_set_user_var is not fixed after construction, call
+ fix_fields().
+ */
+ return (!suv || suv->fix_fields(thd, it) || suv->check(0) || suv->update());
+}
+
+
+bool Item_user_var_as_out_param::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ DBUG_ASSERT(thd->lex->exchange);
+ if (Item::fix_fields(thd, ref) ||
+ !(entry= get_variable(&thd->user_vars, name, 1)))
+ return TRUE;
+ entry->type= STRING_RESULT;
+ /*
+ Let us set the same collation which is used for loading
+ of fields in LOAD DATA INFILE.
+ (Since Item_user_var_as_out_param is used only there).
+ */
+ entry->collation.set(thd->lex->exchange->cs ?
+ thd->lex->exchange->cs :
+ thd->variables.collation_database);
+ entry->update_query_id= thd->query_id;
+ return FALSE;
+}
+
+
+void Item_user_var_as_out_param::set_null_value(CHARSET_INFO* cs)
+{
+ ::update_hash(entry, TRUE, 0, 0, STRING_RESULT, cs,
+ DERIVATION_IMPLICIT, 0 /* unsigned_arg */);
+}
+
+
+void Item_user_var_as_out_param::set_value(const char *str, uint length,
+ CHARSET_INFO* cs)
+{
+ ::update_hash(entry, FALSE, (void*)str, length, STRING_RESULT, cs,
+ DERIVATION_IMPLICIT, 0 /* unsigned_arg */);
+}
+
+
+double Item_user_var_as_out_param::val_real()
+{
+ DBUG_ASSERT(0);
+ return 0.0;
+}
+
+
+longlong Item_user_var_as_out_param::val_int()
+{
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+
+String* Item_user_var_as_out_param::val_str(String *str)
+{
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+
+my_decimal* Item_user_var_as_out_param::val_decimal(my_decimal *decimal_buffer)
+{
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+
+void Item_user_var_as_out_param::print_for_load(THD *thd, String *str)
+{
+ str->append('@');
+ append_identifier(thd, str, name.str, name.length);
+}
+
+
+Item_func_get_system_var::
+Item_func_get_system_var(sys_var *var_arg, enum_var_type var_type_arg,
+ LEX_STRING *component_arg, const char *name_arg,
+ size_t name_len_arg)
+ :var(var_arg), var_type(var_type_arg), orig_var_type(var_type_arg),
+ component(*component_arg), cache_present(0)
+{
+ /* set_name() will allocate the name */
+ set_name(name_arg, (uint) name_len_arg, system_charset_info);
+}
+
+
+bool Item_func_get_system_var::is_written_to_binlog()
+{
+ return var->is_written_to_binlog(var_type);
+}
+
+
+void Item_func_get_system_var::update_null_value()
+{
+ THD *thd= current_thd;
+ int save_no_errors= thd->no_errors;
+ thd->no_errors= TRUE;
+ Item::update_null_value();
+ thd->no_errors= save_no_errors;
+}
+
+
+void Item_func_get_system_var::fix_length_and_dec()
+{
+ char *cptr;
+ maybe_null= TRUE;
+ max_length= 0;
+
+ if (var->check_type(var_type))
+ {
+ if (var_type != OPT_DEFAULT)
+ {
+ my_error(ER_INCORRECT_GLOBAL_LOCAL_VAR, MYF(0),
+ var->name.str, var_type == OPT_GLOBAL ? "SESSION" : "GLOBAL");
+ return;
+ }
+ /* As there was no local variable, return the global value */
+ var_type= OPT_GLOBAL;
+ }
+
+ switch (var->show_type())
+ {
+ case SHOW_HA_ROWS:
+ case SHOW_UINT:
+ case SHOW_ULONG:
+ case SHOW_ULONGLONG:
+ unsigned_flag= TRUE;
+ /* fall through */
+ case SHOW_SINT:
+ case SHOW_SLONG:
+ case SHOW_SLONGLONG:
+ collation.set_numeric();
+ fix_char_length(MY_INT64_NUM_DECIMAL_DIGITS);
+ decimals=0;
+ break;
+ case SHOW_CHAR:
+ case SHOW_CHAR_PTR:
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ cptr= var->show_type() == SHOW_CHAR ?
+ (char*) var->value_ptr(current_thd, var_type, &component) :
+ *(char**) var->value_ptr(current_thd, var_type, &component);
+ if (cptr)
+ max_length= system_charset_info->cset->numchars(system_charset_info,
+ cptr,
+ cptr + strlen(cptr));
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ collation.set(system_charset_info, DERIVATION_SYSCONST);
+ max_length*= system_charset_info->mbmaxlen;
+ decimals=NOT_FIXED_DEC;
+ break;
+ case SHOW_LEX_STRING:
+ {
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ LEX_STRING *ls= ((LEX_STRING*)var->value_ptr(current_thd, var_type, &component));
+ max_length= system_charset_info->cset->numchars(system_charset_info,
+ ls->str,
+ ls->str + ls->length);
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ collation.set(system_charset_info, DERIVATION_SYSCONST);
+ max_length*= system_charset_info->mbmaxlen;
+ decimals=NOT_FIXED_DEC;
+ }
+ break;
+ case SHOW_BOOL:
+ case SHOW_MY_BOOL:
+ collation.set_numeric();
+ fix_char_length(1);
+ decimals=0;
+ break;
+ case SHOW_DOUBLE:
+ decimals= 6;
+ collation.set_numeric();
+ fix_char_length(DBL_DIG + 6);
+ break;
+ default:
+ my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str);
+ break;
+ }
+}
+
+
+void Item_func_get_system_var::print(String *str, enum_query_type query_type)
+{
+ str->append(name, name_length);
+}
+
+
+enum Item_result Item_func_get_system_var::result_type() const
+{
+ switch (var->show_type())
+ {
+ case SHOW_BOOL:
+ case SHOW_MY_BOOL:
+ case SHOW_SINT:
+ case SHOW_SLONG:
+ case SHOW_SLONGLONG:
+ case SHOW_UINT:
+ case SHOW_ULONG:
+ case SHOW_ULONGLONG:
+ case SHOW_HA_ROWS:
+ return INT_RESULT;
+ case SHOW_CHAR:
+ case SHOW_CHAR_PTR:
+ case SHOW_LEX_STRING:
+ return STRING_RESULT;
+ case SHOW_DOUBLE:
+ return REAL_RESULT;
+ default:
+ my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str);
+ return STRING_RESULT; // keep the compiler happy
+ }
+}
+
+
+enum_field_types Item_func_get_system_var::field_type() const
+{
+ switch (var->show_type())
+ {
+ case SHOW_BOOL:
+ case SHOW_MY_BOOL:
+ case SHOW_SINT:
+ case SHOW_SLONG:
+ case SHOW_SLONGLONG:
+ case SHOW_UINT:
+ case SHOW_ULONG:
+ case SHOW_ULONGLONG:
+ case SHOW_HA_ROWS:
+ return MYSQL_TYPE_LONGLONG;
+ case SHOW_CHAR:
+ case SHOW_CHAR_PTR:
+ case SHOW_LEX_STRING:
+ return MYSQL_TYPE_VARCHAR;
+ case SHOW_DOUBLE:
+ return MYSQL_TYPE_DOUBLE;
+ default:
+ my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str);
+ return MYSQL_TYPE_VARCHAR; // keep the compiler happy
+ }
+}
+
+
+longlong Item_func_get_system_var::val_int()
+{
+ THD *thd= current_thd;
+
+ DBUG_EXECUTE_IF("simulate_non_gtid_aware_master",
+ {
+ if (0 == strcmp("gtid_domain_id", var->name.str))
+ {
+ my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str);
+ return 0;
+ }
+ });
+ if (cache_present && thd->query_id == used_query_id)
+ {
+ if (cache_present & GET_SYS_VAR_CACHE_LONG)
+ {
+ null_value= cached_null_value;
+ return cached_llval;
+ }
+ else if (cache_present & GET_SYS_VAR_CACHE_DOUBLE)
+ {
+ null_value= cached_null_value;
+ cached_llval= (longlong) cached_dval;
+ cache_present|= GET_SYS_VAR_CACHE_LONG;
+ return cached_llval;
+ }
+ else if (cache_present & GET_SYS_VAR_CACHE_STRING)
+ {
+ null_value= cached_null_value;
+ if (!null_value)
+ cached_llval= longlong_from_string_with_check (cached_strval.charset(),
+ cached_strval.c_ptr(),
+ cached_strval.c_ptr() +
+ cached_strval.length());
+ else
+ cached_llval= 0;
+ cache_present|= GET_SYS_VAR_CACHE_LONG;
+ return cached_llval;
+ }
+ }
+
+ cached_llval= var->val_int(&null_value, thd, var_type, &component);
+ cache_present |= GET_SYS_VAR_CACHE_LONG;
+ used_query_id= thd->query_id;
+ cached_null_value= null_value;
+ return cached_llval;
+}
+
+
+String* Item_func_get_system_var::val_str(String* str)
+{
+ THD *thd= current_thd;
+
+ if (cache_present && thd->query_id == used_query_id)
+ {
+ if (cache_present & GET_SYS_VAR_CACHE_STRING)
+ {
+ null_value= cached_null_value;
+ return null_value ? NULL : &cached_strval;
+ }
+ else if (cache_present & GET_SYS_VAR_CACHE_LONG)
+ {
+ null_value= cached_null_value;
+ if (!null_value)
+ cached_strval.set (cached_llval, collation.collation);
+ cache_present|= GET_SYS_VAR_CACHE_STRING;
+ return null_value ? NULL : &cached_strval;
+ }
+ else if (cache_present & GET_SYS_VAR_CACHE_DOUBLE)
+ {
+ null_value= cached_null_value;
+ if (!null_value)
+ cached_strval.set_real (cached_dval, decimals, collation.collation);
+ cache_present|= GET_SYS_VAR_CACHE_STRING;
+ return null_value ? NULL : &cached_strval;
+ }
+ }
+
+ str= var->val_str(&cached_strval, thd, var_type, &component);
+ cache_present|= GET_SYS_VAR_CACHE_STRING;
+ used_query_id= thd->query_id;
+ cached_null_value= null_value= !str;
+ return str;
+}
+
+
+double Item_func_get_system_var::val_real()
+{
+ THD *thd= current_thd;
+
+ if (cache_present && thd->query_id == used_query_id)
+ {
+ if (cache_present & GET_SYS_VAR_CACHE_DOUBLE)
+ {
+ null_value= cached_null_value;
+ return cached_dval;
+ }
+ else if (cache_present & GET_SYS_VAR_CACHE_LONG)
+ {
+ null_value= cached_null_value;
+ cached_dval= (double)cached_llval;
+ cache_present|= GET_SYS_VAR_CACHE_DOUBLE;
+ return cached_dval;
+ }
+ else if (cache_present & GET_SYS_VAR_CACHE_STRING)
+ {
+ null_value= cached_null_value;
+ if (!null_value)
+ cached_dval= double_from_string_with_check (cached_strval.charset(),
+ cached_strval.c_ptr(),
+ cached_strval.c_ptr() +
+ cached_strval.length());
+ else
+ cached_dval= 0;
+ cache_present|= GET_SYS_VAR_CACHE_DOUBLE;
+ return cached_dval;
+ }
+ }
+
+ cached_dval= var->val_real(&null_value, thd, var_type, &component);
+ cache_present |= GET_SYS_VAR_CACHE_DOUBLE;
+ used_query_id= thd->query_id;
+ cached_null_value= null_value;
+ return cached_dval;
+}
+
+
+bool Item_func_get_system_var::eq(const Item *item, bool binary_cmp) const
+{
+ /* Assume we don't have rtti */
+ if (this == item)
+ return 1; // Same item is same.
+ /* Check if other type is also a get_user_var() object */
+ if (item->type() != FUNC_ITEM ||
+ ((Item_func*) item)->functype() != functype())
+ return 0;
+ Item_func_get_system_var *other=(Item_func_get_system_var*) item;
+ return (var == other->var && var_type == other->var_type);
+}
+
+
+void Item_func_get_system_var::cleanup()
+{
+ Item_func::cleanup();
+ cache_present= 0;
+ var_type= orig_var_type;
+ cached_strval.free();
+}
+
+
+void Item_func_match::init_search(bool no_order)
+{
+ DBUG_ENTER("Item_func_match::init_search");
+
+ if (!table->file->get_table()) // the handler isn't opened yet
+ DBUG_VOID_RETURN;
+
+ /* Check if init_search() has been called before */
+ if (ft_handler)
+ {
+ if (join_key)
+ table->file->ft_handler= ft_handler;
+ DBUG_VOID_RETURN;
+ }
+
+ if (key == NO_SUCH_KEY)
+ {
+ List<Item> fields;
+ fields.push_back(new Item_string(" ", 1, cmp_collation.collation));
+ for (uint i= 1; i < arg_count; i++)
+ fields.push_back(args[i]);
+ concat_ws= new Item_func_concat_ws(fields);
+ /*
+ Above function used only to get value and do not need fix_fields for it:
+ Item_string - basic constant
+ fields - fix_fields() was already called for this arguments
+ Item_func_concat_ws - do not need fix_fields() to produce value
+ */
+ concat_ws->quick_fix_field();
+ }
+
+ if (master)
+ {
+ join_key= master->join_key= join_key | master->join_key;
+ master->init_search(no_order);
+ ft_handler= master->ft_handler;
+ join_key= master->join_key;
+ DBUG_VOID_RETURN;
+ }
+
+ String *ft_tmp= 0;
+
+ // MATCH ... AGAINST (NULL) is meaningless, but possible
+ if (!(ft_tmp=key_item()->val_str(&value)))
+ {
+ ft_tmp= &value;
+ value.set("", 0, cmp_collation.collation);
+ }
+
+ if (ft_tmp->charset() != cmp_collation.collation)
+ {
+ uint dummy_errors;
+ search_value.copy(ft_tmp->ptr(), ft_tmp->length(), ft_tmp->charset(),
+ cmp_collation.collation, &dummy_errors);
+ ft_tmp= &search_value;
+ }
+
+ if (join_key && !no_order)
+ flags|=FT_SORTED;
+
+ if (key != NO_SUCH_KEY)
+ THD_STAGE_INFO(table->in_use, stage_fulltext_initialization);
+
+ ft_handler= table->file->ft_init_ext(flags, key, ft_tmp);
+
+ if (join_key)
+ table->file->ft_handler=ft_handler;
+
+ DBUG_VOID_RETURN;
+}
+
+
+bool Item_func_match::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ Item *UNINIT_VAR(item); // Safe as arg_count is > 1
+
+ status_var_increment(thd->status_var.feature_fulltext);
+
+ maybe_null=1;
+ join_key=0;
+
+ /*
+ const_item is assumed in quite a bit of places, so it would be difficult
+ to remove; If it would ever to be removed, this should include
+ modifications to find_best and auto_close as complement to auto_init code
+ above.
+ */
+ if (Item_func::fix_fields(thd, ref) ||
+ !args[0]->const_during_execution())
+ {
+ my_error(ER_WRONG_ARGUMENTS,MYF(0),"AGAINST");
+ return TRUE;
+ }
+
+ bool allows_multi_table_search= true;
+ const_item_cache=0;
+ table= 0;
+ for (uint i=1 ; i < arg_count ; i++)
+ {
+ item=args[i];
+ if (item->type() == Item::REF_ITEM)
+ args[i]= item= *((Item_ref *)item)->ref;
+ /*
+ When running in PS mode, some Item_field's can already be replaced
+ to Item_func_conv_charset during PREPARE time. This is possible
+ in case of "MATCH (f1,..,fN) AGAINST (... IN BOOLEAN MODE)"
+ when running without any fulltext indexes and when fields f1..fN
+ have different character sets.
+ So we check for FIELD_ITEM only during prepare time and in non-PS mode,
+ and do not check in PS execute time.
+ */
+ if (!thd->stmt_arena->is_stmt_execute() &&
+ item->type() != Item::FIELD_ITEM)
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "AGAINST");
+ return TRUE;
+ }
+ /*
+ During the prepare-time execution of fix_fields() of a PS query some
+ Item_fields's could have been already replaced to Item_func_conv_charset
+ (by the call for agg_arg_charsets_for_comparison below()).
+ But agg_arg_charsets_for_comparison() is written in a way that
+ at least *one* of the Item_field's is not replaced.
+ This makes sure that "table" gets initialized during PS execution time.
+ */
+ if (item->type() == Item::FIELD_ITEM)
+ table= ((Item_field *)item)->field->table;
+
+ allows_multi_table_search &= allows_search_on_non_indexed_columns(table);
+ }
+
+ /*
+ Check that all columns come from the same table.
+ We've already checked that columns in MATCH are fields so
+ PARAM_TABLE_BIT can only appear from AGAINST argument.
+ */
+ if ((used_tables_cache & ~PARAM_TABLE_BIT) != item->used_tables())
+ key=NO_SUCH_KEY;
+
+ if (key == NO_SUCH_KEY && !allows_multi_table_search)
+ {
+ my_error(ER_WRONG_ARGUMENTS,MYF(0),"MATCH");
+ return TRUE;
+ }
+ if (!(table->file->ha_table_flags() & HA_CAN_FULLTEXT))
+ {
+ my_error(ER_TABLE_CANT_HANDLE_FT, MYF(0), table->file->table_type());
+ return 1;
+ }
+ table->fulltext_searched=1;
+ return agg_arg_charsets_for_comparison(cmp_collation, args+1, arg_count-1);
+}
+
+bool Item_func_match::fix_index()
+{
+ Item_field *item;
+ uint ft_to_key[MAX_KEY], ft_cnt[MAX_KEY], fts=0, keynr;
+ uint max_cnt=0, mkeys=0, i;
+
+ /*
+ We will skip execution if the item is not fixed
+ with fix_field
+ */
+ if (!fixed)
+ return false;
+
+ if (key == NO_SUCH_KEY)
+ return 0;
+
+ if (!table)
+ goto err;
+
+ for (keynr=0 ; keynr < table->s->keys ; keynr++)
+ {
+ if ((table->key_info[keynr].flags & HA_FULLTEXT) &&
+ (flags & FT_BOOL ? table->keys_in_use_for_query.is_set(keynr) :
+ table->s->keys_in_use.is_set(keynr)))
+
+ {
+ ft_to_key[fts]=keynr;
+ ft_cnt[fts]=0;
+ fts++;
+ }
+ }
+
+ if (!fts)
+ goto err;
+
+ for (i=1; i < arg_count; i++)
+ {
+ item=(Item_field*)args[i];
+ for (keynr=0 ; keynr < fts ; keynr++)
+ {
+ KEY *ft_key=&table->key_info[ft_to_key[keynr]];
+ uint key_parts=ft_key->user_defined_key_parts;
+
+ for (uint part=0 ; part < key_parts ; part++)
+ {
+ if (item->field->eq(ft_key->key_part[part].field))
+ ft_cnt[keynr]++;
+ }
+ }
+ }
+
+ for (keynr=0 ; keynr < fts ; keynr++)
+ {
+ if (ft_cnt[keynr] > max_cnt)
+ {
+ mkeys=0;
+ max_cnt=ft_cnt[mkeys]=ft_cnt[keynr];
+ ft_to_key[mkeys]=ft_to_key[keynr];
+ continue;
+ }
+ if (max_cnt && ft_cnt[keynr] == max_cnt)
+ {
+ mkeys++;
+ ft_cnt[mkeys]=ft_cnt[keynr];
+ ft_to_key[mkeys]=ft_to_key[keynr];
+ continue;
+ }
+ }
+
+ for (keynr=0 ; keynr <= mkeys ; keynr++)
+ {
+ // partial keys doesn't work
+ if (max_cnt < arg_count-1 ||
+ max_cnt < table->key_info[ft_to_key[keynr]].user_defined_key_parts)
+ continue;
+
+ key=ft_to_key[keynr];
+
+ return 0;
+ }
+
+err:
+ if (allows_search_on_non_indexed_columns(table))
+ {
+ key=NO_SUCH_KEY;
+ return 0;
+ }
+ my_message(ER_FT_MATCHING_KEY_NOT_FOUND,
+ ER(ER_FT_MATCHING_KEY_NOT_FOUND), MYF(0));
+ return 1;
+}
+
+
+bool Item_func_match::eq(const Item *item, bool binary_cmp) const
+{
+ if (item->type() != FUNC_ITEM ||
+ ((Item_func*)item)->functype() != FT_FUNC ||
+ flags != ((Item_func_match*)item)->flags)
+ return 0;
+
+ Item_func_match *ifm=(Item_func_match*) item;
+
+ if (key == ifm->key && table == ifm->table &&
+ key_item()->eq(ifm->key_item(), binary_cmp))
+ return 1;
+
+ return 0;
+}
+
+
+double Item_func_match::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_func_match::val");
+ if (ft_handler == NULL)
+ DBUG_RETURN(-1.0);
+
+ if (key != NO_SUCH_KEY && table->null_row) /* NULL row from an outer join */
+ DBUG_RETURN(0.0);
+
+ if (join_key)
+ {
+ if (table->file->ft_handler)
+ DBUG_RETURN(ft_handler->please->get_relevance(ft_handler));
+ join_key=0;
+ }
+
+ if (key == NO_SUCH_KEY)
+ {
+ String *a= concat_ws->val_str(&value);
+ if ((null_value= (a == 0)) || !a->length())
+ DBUG_RETURN(0);
+ DBUG_RETURN(ft_handler->please->find_relevance(ft_handler,
+ (uchar *)a->ptr(), a->length()));
+ }
+ DBUG_RETURN(ft_handler->please->find_relevance(ft_handler,
+ table->record[0], 0));
+}
+
+void Item_func_match::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("(match "));
+ print_args(str, 1, query_type);
+ str->append(STRING_WITH_LEN(" against ("));
+ args[0]->print(str, query_type);
+ if (flags & FT_BOOL)
+ str->append(STRING_WITH_LEN(" in boolean mode"));
+ else if (flags & FT_EXPAND)
+ str->append(STRING_WITH_LEN(" with query expansion"));
+ str->append(STRING_WITH_LEN("))"));
+}
+
+longlong Item_func_bit_xor::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ ulonglong arg1= (ulonglong) args[0]->val_int();
+ ulonglong arg2= (ulonglong) args[1]->val_int();
+ if ((null_value= (args[0]->null_value || args[1]->null_value)))
+ return 0;
+ return (longlong) (arg1 ^ arg2);
+}
+
+
+/***************************************************************************
+ System variables
+****************************************************************************/
+
+/**
+ Return value of an system variable base[.name] as a constant item.
+
+ @param thd Thread handler
+ @param var_type global / session
+ @param name Name of base or system variable
+ @param component Component.
+
+ @note
+ If component.str = 0 then the variable name is in 'name'
+
+ @return
+ - 0 : error
+ - # : constant item
+*/
+
+
+Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name,
+ LEX_STRING component)
+{
+ sys_var *var;
+ LEX_STRING *base_name, *component_name;
+
+ if (component.str)
+ {
+ base_name= &component;
+ component_name= &name;
+ }
+ else
+ {
+ base_name= &name;
+ component_name= &component; // Empty string
+ }
+
+ if (!(var= find_sys_var(thd, base_name->str, base_name->length)))
+ return 0;
+ if (component.str)
+ {
+ if (!var->is_struct())
+ {
+ my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), base_name->str);
+ return 0;
+ }
+ }
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+
+ set_if_smaller(component_name->length, MAX_SYS_VAR_LENGTH);
+
+ return new Item_func_get_system_var(var, var_type, component_name,
+ NULL, 0);
+}
+
+
+longlong Item_func_row_count::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ THD *thd= current_thd;
+
+ return thd->get_row_count_func();
+}
+
+
+
+
+Item_func_sp::Item_func_sp(Name_resolution_context *context_arg, sp_name *name)
+ :Item_func(), context(context_arg), m_name(name), m_sp(NULL), sp_result_field(NULL)
+{
+ maybe_null= 1;
+ m_name->init_qname(current_thd);
+ dummy_table= (TABLE*) sql_calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE));
+ dummy_table->s= (TABLE_SHARE*) (dummy_table+1);
+}
+
+
+Item_func_sp::Item_func_sp(Name_resolution_context *context_arg,
+ sp_name *name, List<Item> &list)
+ :Item_func(list), context(context_arg), m_name(name), m_sp(NULL),sp_result_field(NULL)
+{
+ maybe_null= 1;
+ m_name->init_qname(current_thd);
+ dummy_table= (TABLE*) sql_calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE));
+ dummy_table->s= (TABLE_SHARE*) (dummy_table+1);
+}
+
+
+void
+Item_func_sp::cleanup()
+{
+ if (sp_result_field)
+ {
+ delete sp_result_field;
+ sp_result_field= NULL;
+ }
+ m_sp= NULL;
+ dummy_table->alias.free();
+ Item_func::cleanup();
+}
+
+const char *
+Item_func_sp::func_name() const
+{
+ THD *thd= current_thd;
+ /* Calculate length to avoid reallocation of string for sure */
+ uint len= (((m_name->m_explicit_name ? m_name->m_db.length : 0) +
+ m_name->m_name.length)*2 + //characters*quoting
+ 2 + // ` and `
+ (m_name->m_explicit_name ?
+ 3 : 0) + // '`', '`' and '.' for the db
+ 1 + // end of string
+ ALIGN_SIZE(1)); // to avoid String reallocation
+ String qname((char *)alloc_root(thd->mem_root, len), len,
+ system_charset_info);
+
+ qname.length(0);
+ if (m_name->m_explicit_name)
+ {
+ append_identifier(thd, &qname, m_name->m_db.str, m_name->m_db.length);
+ qname.append('.');
+ }
+ append_identifier(thd, &qname, m_name->m_name.str, m_name->m_name.length);
+ return qname.c_ptr_safe();
+}
+
+
+void my_missing_function_error(const LEX_STRING &token, const char *func_name)
+{
+ if (token.length && is_lex_native_function (&token))
+ my_error(ER_FUNC_INEXISTENT_NAME_COLLISION, MYF(0), func_name);
+ else
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "FUNCTION", func_name);
+}
+
+
+/**
+ @brief Initialize the result field by creating a temporary dummy table
+ and assign it to a newly created field object. Meta data used to
+ create the field is fetched from the sp_head belonging to the stored
+ proceedure found in the stored procedure functon cache.
+
+ @note This function should be called from fix_fields to init the result
+ field. It is some what related to Item_field.
+
+ @see Item_field
+
+ @param thd A pointer to the session and thread context.
+
+ @return Function return error status.
+ @retval TRUE is returned on an error
+ @retval FALSE is returned on success.
+*/
+
+bool
+Item_func_sp::init_result_field(THD *thd)
+{
+ LEX_STRING empty_name= { C_STRING_WITH_LEN("") };
+ TABLE_SHARE *share;
+ DBUG_ENTER("Item_func_sp::init_result_field");
+
+ DBUG_ASSERT(m_sp == NULL);
+ DBUG_ASSERT(sp_result_field == NULL);
+
+ if (!(m_sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, m_name,
+ &thd->sp_func_cache, TRUE)))
+ {
+ my_missing_function_error (m_name->m_name, m_name->m_qname.str);
+ context->process_error(thd);
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ A Field need to be attached to a Table.
+ Below we "create" a dummy table by initializing
+ the needed pointers.
+ */
+
+ share= dummy_table->s;
+ dummy_table->alias.set("", 0, table_alias_charset);
+ dummy_table->maybe_null = maybe_null;
+ dummy_table->in_use= thd;
+ dummy_table->copy_blobs= TRUE;
+ share->table_cache_key = empty_name;
+ share->table_name = empty_name;
+
+ if (!(sp_result_field= m_sp->create_result_field(max_length, name,
+ dummy_table)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ if (sp_result_field->pack_length() > sizeof(result_buf))
+ {
+ void *tmp;
+ if (!(tmp= sql_alloc(sp_result_field->pack_length())))
+ DBUG_RETURN(TRUE);
+ sp_result_field->move_field((uchar*) tmp);
+ }
+ else
+ sp_result_field->move_field(result_buf);
+
+ sp_result_field->null_ptr= (uchar *) &null_value;
+ sp_result_field->null_bit= 1;
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @note
+ Deterministic stored procedures are considered inexpensive.
+ Consequently such procedures may be evaluated during optimization,
+ if they are constant (checked by the optimizer).
+*/
+
+bool Item_func_sp::is_expensive()
+{
+ return !(m_sp->m_chistics->detistic);
+}
+
+
+/**
+ @brief Initialize local members with values from the Field interface.
+
+ @note called from Item::fix_fields.
+*/
+
+void Item_func_sp::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_func_sp::fix_length_and_dec");
+
+ DBUG_ASSERT(sp_result_field);
+ decimals= sp_result_field->decimals();
+ max_length= sp_result_field->field_length;
+ collation.set(sp_result_field->charset());
+ maybe_null= 1;
+ unsigned_flag= MY_TEST(sp_result_field->flags & UNSIGNED_FLAG);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @brief Execute function & store value in field.
+
+ @return Function returns error status.
+ @retval FALSE on success.
+ @retval TRUE if an error occurred.
+*/
+
+bool
+Item_func_sp::execute()
+{
+ THD *thd= current_thd;
+
+ /* Execute function and store the return value in the field. */
+
+ if (execute_impl(thd))
+ {
+ null_value= 1;
+ context->process_error(thd);
+ if (thd->killed)
+ thd->send_kill_message();
+ return TRUE;
+ }
+
+ /* Check that the field (the value) is not NULL. */
+
+ null_value= sp_result_field->is_null();
+
+ return null_value;
+}
+
+
+/**
+ @brief Execute function and store the return value in the field.
+
+ @note This function was intended to be the concrete implementation of
+ the interface function execute. This was never realized.
+
+ @return The error state.
+ @retval FALSE on success
+ @retval TRUE if an error occurred.
+*/
+bool
+Item_func_sp::execute_impl(THD *thd)
+{
+ bool err_status= TRUE;
+ Sub_statement_state statement_state;
+ Security_context *save_security_ctx= thd->security_ctx;
+ enum enum_sp_data_access access=
+ (m_sp->m_chistics->daccess == SP_DEFAULT_ACCESS) ?
+ SP_DEFAULT_ACCESS_MAPPING : m_sp->m_chistics->daccess;
+
+ DBUG_ENTER("Item_func_sp::execute_impl");
+
+ if (context->security_ctx)
+ {
+ /* Set view definer security context */
+ thd->security_ctx= context->security_ctx;
+ }
+ if (sp_check_access(thd))
+ goto error;
+
+ /*
+ Throw an error if a non-deterministic function is called while
+ statement-based replication (SBR) is active.
+ */
+
+ if (!m_sp->m_chistics->detistic && !trust_function_creators &&
+ (access == SP_CONTAINS_SQL || access == SP_MODIFIES_SQL_DATA) &&
+ (mysql_bin_log.is_open() &&
+ thd->variables.binlog_format == BINLOG_FORMAT_STMT))
+ {
+ my_error(ER_BINLOG_UNSAFE_ROUTINE, MYF(0));
+ goto error;
+ }
+
+ /*
+ Disable the binlogging if this is not a SELECT statement. If this is a
+ SELECT, leave binlogging on, so execute_function() code writes the
+ function call into binlog.
+ */
+ thd->reset_sub_statement_state(&statement_state, SUB_STMT_FUNCTION);
+ err_status= m_sp->execute_function(thd, args, arg_count, sp_result_field);
+ thd->restore_sub_statement_state(&statement_state);
+
+error:
+ thd->security_ctx= save_security_ctx;
+
+ DBUG_RETURN(err_status);
+}
+
+
+void
+Item_func_sp::make_field(Send_field *tmp_field)
+{
+ DBUG_ENTER("Item_func_sp::make_field");
+ DBUG_ASSERT(sp_result_field);
+ sp_result_field->make_field(tmp_field);
+ if (name)
+ tmp_field->col_name= name;
+ DBUG_VOID_RETURN;
+}
+
+
+enum enum_field_types
+Item_func_sp::field_type() const
+{
+ DBUG_ENTER("Item_func_sp::field_type");
+ DBUG_ASSERT(sp_result_field);
+ DBUG_RETURN(sp_result_field->type());
+}
+
+Item_result
+Item_func_sp::result_type() const
+{
+ DBUG_ENTER("Item_func_sp::result_type");
+ DBUG_PRINT("info", ("m_sp = %p", (void *) m_sp));
+ DBUG_ASSERT(sp_result_field);
+ DBUG_RETURN(sp_result_field->result_type());
+}
+
+longlong Item_func_found_rows::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ return current_thd->found_rows();
+}
+
+
+Field *
+Item_func_sp::tmp_table_field(TABLE *t_arg)
+{
+ DBUG_ENTER("Item_func_sp::tmp_table_field");
+
+ DBUG_ASSERT(sp_result_field);
+ DBUG_RETURN(sp_result_field);
+}
+
+
+/**
+ @brief Checks if requested access to function can be granted to user.
+ If function isn't found yet, it searches function first.
+ If function can't be found or user don't have requested access
+ error is raised.
+
+ @param thd thread handler
+
+ @return Indication if the access was granted or not.
+ @retval FALSE Access is granted.
+ @retval TRUE Requested access can't be granted or function doesn't exists.
+
+*/
+
+bool
+Item_func_sp::sp_check_access(THD *thd)
+{
+ DBUG_ENTER("Item_func_sp::sp_check_access");
+ DBUG_ASSERT(m_sp);
+ if (check_routine_access(thd, EXECUTE_ACL,
+ m_sp->m_db.str, m_sp->m_name.str, 0, FALSE))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+bool
+Item_func_sp::fix_fields(THD *thd, Item **ref)
+{
+ bool res;
+ DBUG_ENTER("Item_func_sp::fix_fields");
+ DBUG_ASSERT(fixed == 0);
+
+ /*
+ Checking privileges to execute the function while creating view and
+ executing the function of select.
+ */
+ if (!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW) ||
+ (thd->lex->sql_command == SQLCOM_CREATE_VIEW))
+ {
+ Security_context *save_security_ctx= thd->security_ctx;
+ if (context->security_ctx)
+ thd->security_ctx= context->security_ctx;
+
+ res= check_routine_access(thd, EXECUTE_ACL, m_name->m_db.str,
+ m_name->m_name.str, 0, FALSE);
+ thd->security_ctx= save_security_ctx;
+
+ if (res)
+ {
+ context->process_error(thd);
+ DBUG_RETURN(res);
+ }
+ }
+
+ /*
+ We must call init_result_field before Item_func::fix_fields()
+ to make m_sp and result_field members available to fix_length_and_dec(),
+ which is called from Item_func::fix_fields().
+ */
+ res= init_result_field(thd);
+
+ if (res)
+ DBUG_RETURN(res);
+
+ res= Item_func::fix_fields(thd, ref);
+
+ if (res)
+ DBUG_RETURN(res);
+
+ if (thd->lex->is_view_context_analysis())
+ {
+ /*
+ Here we check privileges of the stored routine only during view
+ creation, in order to validate the view. A runtime check is
+ perfomed in Item_func_sp::execute(), and this method is not
+ called during context analysis. Notice, that during view
+ creation we do not infer into stored routine bodies and do not
+ check privileges of its statements, which would probably be a
+ good idea especially if the view has SQL SECURITY DEFINER and
+ the used stored procedure has SQL SECURITY DEFINER.
+ */
+ res= sp_check_access(thd);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ Try to set and restore the security context to see whether it's valid
+ */
+ Security_context *save_secutiry_ctx;
+ res= set_routine_security_ctx(thd, m_sp, false, &save_secutiry_ctx);
+ if (!res)
+ m_sp->m_security_ctx.restore_security_context(thd, save_secutiry_ctx);
+
+#endif /* ! NO_EMBEDDED_ACCESS_CHECKS */
+ }
+
+ if (!m_sp->m_chistics->detistic)
+ {
+ used_tables_cache |= RAND_TABLE_BIT;
+ const_item_cache= FALSE;
+ }
+
+ DBUG_RETURN(res);
+}
+
+
+void Item_func_sp::update_used_tables()
+{
+ Item_func::update_used_tables();
+
+ if (!m_sp->m_chistics->detistic)
+ {
+ used_tables_cache |= RAND_TABLE_BIT;
+ const_item_cache= FALSE;
+ }
+}
+
+
+/*
+ uuid_short handling.
+
+ The short uuid is defined as a longlong that contains the following bytes:
+
+ Bytes Comment
+ 1 Server_id & 255
+ 4 Startup time of server in seconds
+ 3 Incrementor
+
+ This means that an uuid is guaranteed to be unique
+ even in a replication environment if the following holds:
+
+ - The last byte of the server id is unique
+ - If you between two shutdown of the server don't get more than
+ an average of 2^24 = 16M calls to uuid_short() per second.
+*/
+
+ulonglong uuid_value;
+
+void uuid_short_init()
+{
+ uuid_value= ((((ulonglong) global_system_variables.server_id) << 56) +
+ (((ulonglong) server_start_time) << 24));
+}
+
+
+longlong Item_func_uuid_short::val_int()
+{
+ ulonglong val;
+ mysql_mutex_lock(&LOCK_short_uuid_generator);
+ val= uuid_value++;
+ mysql_mutex_unlock(&LOCK_short_uuid_generator);
+ return (longlong) val;
+}
+
+
+/**
+ Last_value - return last argument.
+*/
+
+void Item_func_last_value::evaluate_sideeffects()
+{
+ DBUG_ASSERT(fixed == 1 && arg_count > 0);
+ for (uint i= 0; i < arg_count-1 ; i++)
+ args[i]->val_int();
+}
+
+String *Item_func_last_value::val_str(String *str)
+{
+ String *tmp;
+ evaluate_sideeffects();
+ tmp= last_value->val_str(str);
+ null_value= last_value->null_value;
+ return tmp;
+}
+
+longlong Item_func_last_value::val_int()
+{
+ longlong tmp;
+ evaluate_sideeffects();
+ tmp= last_value->val_int();
+ null_value= last_value->null_value;
+ return tmp;
+}
+
+double Item_func_last_value::val_real()
+{
+ double tmp;
+ evaluate_sideeffects();
+ tmp= last_value->val_real();
+ null_value= last_value->null_value;
+ return tmp;
+}
+
+my_decimal *Item_func_last_value::val_decimal(my_decimal *decimal_value)
+{
+ my_decimal *tmp;
+ evaluate_sideeffects();
+ tmp= last_value->val_decimal(decimal_value);
+ null_value= last_value->null_value;
+ return tmp;
+}
+
+
+void Item_func_last_value::fix_length_and_dec()
+{
+ last_value= args[arg_count -1];
+ decimals= last_value->decimals;
+ max_length= last_value->max_length;
+ collation.set(last_value->collation.collation);
+ maybe_null= last_value->maybe_null;
+ unsigned_flag= last_value->unsigned_flag;
+}
diff --git a/sql/item_func.h b/sql/item_func.h
new file mode 100644
index 00000000000..ce1f2fdd676
--- /dev/null
+++ b/sql/item_func.h
@@ -0,0 +1,2209 @@
+#ifndef ITEM_FUNC_INCLUDED
+#define ITEM_FUNC_INCLUDED
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* Function items used by mysql */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#ifdef HAVE_IEEEFP_H
+extern "C" /* Bug in BSDI include file */
+{
+#include <ieeefp.h>
+}
+#endif
+
+class Item_func :public Item_result_field
+{
+protected:
+ Item **args, *tmp_arg[2];
+ /*
+ Allowed numbers of columns in result (usually 1, which means scalar value)
+ 0 means get this number from first argument
+ */
+ uint allowed_arg_cols;
+ String *val_str_from_val_str_ascii(String *str, String *str2);
+public:
+ uint arg_count;
+ /*
+ In some cases used_tables_cache is not what used_tables() return
+ so the method should be used where one need used tables bit map
+ (even internally in Item_func_* code).
+ */
+ table_map used_tables_cache;
+ table_map not_null_tables_cache;
+
+ bool const_item_cache;
+ enum Functype { UNKNOWN_FUNC,EQ_FUNC,EQUAL_FUNC,NE_FUNC,LT_FUNC,LE_FUNC,
+ GE_FUNC,GT_FUNC,FT_FUNC,
+ LIKE_FUNC,ISNULL_FUNC,ISNOTNULL_FUNC,
+ COND_AND_FUNC, COND_OR_FUNC, XOR_FUNC,
+ BETWEEN, IN_FUNC, MULT_EQUAL_FUNC,
+ INTERVAL_FUNC, ISNOTNULLTEST_FUNC,
+ SP_EQUALS_FUNC, SP_DISJOINT_FUNC,SP_INTERSECTS_FUNC,
+ SP_TOUCHES_FUNC,SP_CROSSES_FUNC,SP_WITHIN_FUNC,
+ SP_CONTAINS_FUNC,SP_OVERLAPS_FUNC,
+ SP_STARTPOINT,SP_ENDPOINT,SP_EXTERIORRING,
+ SP_POINTN,SP_GEOMETRYN,SP_INTERIORRINGN,
+ NOT_FUNC, NOT_ALL_FUNC,
+ NOW_FUNC, TRIG_COND_FUNC,
+ SUSERVAR_FUNC, GUSERVAR_FUNC, COLLATE_FUNC,
+ EXTRACT_FUNC, CHAR_TYPECAST_FUNC, FUNC_SP, UDF_FUNC,
+ NEG_FUNC, GSYSVAR_FUNC, DYNCOL_FUNC };
+ enum optimize_type { OPTIMIZE_NONE,OPTIMIZE_KEY,OPTIMIZE_OP, OPTIMIZE_NULL,
+ OPTIMIZE_EQUAL };
+ enum Type type() const { return FUNC_ITEM; }
+ virtual enum Functype functype() const { return UNKNOWN_FUNC; }
+ Item_func(void):
+ allowed_arg_cols(1), arg_count(0)
+ {
+ with_sum_func= 0;
+ with_field= 0;
+ }
+ Item_func(Item *a):
+ allowed_arg_cols(1), arg_count(1)
+ {
+ args= tmp_arg;
+ args[0]= a;
+ with_sum_func= a->with_sum_func;
+ with_field= a->with_field;
+ }
+ Item_func(Item *a,Item *b):
+ allowed_arg_cols(1), arg_count(2)
+ {
+ args= tmp_arg;
+ args[0]= a; args[1]= b;
+ with_sum_func= a->with_sum_func || b->with_sum_func;
+ with_field= a->with_field || b->with_field;
+ }
+ Item_func(Item *a,Item *b,Item *c):
+ allowed_arg_cols(1)
+ {
+ arg_count= 0;
+ if ((args= (Item**) sql_alloc(sizeof(Item*)*3)))
+ {
+ arg_count= 3;
+ args[0]= a; args[1]= b; args[2]= c;
+ with_sum_func= a->with_sum_func || b->with_sum_func || c->with_sum_func;
+ with_field= a->with_field || b->with_field || c->with_field;
+ }
+ }
+ Item_func(Item *a,Item *b,Item *c,Item *d):
+ allowed_arg_cols(1)
+ {
+ arg_count= 0;
+ if ((args= (Item**) sql_alloc(sizeof(Item*)*4)))
+ {
+ arg_count= 4;
+ args[0]= a; args[1]= b; args[2]= c; args[3]= d;
+ with_sum_func= a->with_sum_func || b->with_sum_func ||
+ c->with_sum_func || d->with_sum_func;
+ with_field= a->with_field || b->with_field ||
+ c->with_field || d->with_field;
+ }
+ }
+ Item_func(Item *a,Item *b,Item *c,Item *d,Item* e):
+ allowed_arg_cols(1)
+ {
+ arg_count= 5;
+ if ((args= (Item**) sql_alloc(sizeof(Item*)*5)))
+ {
+ args[0]= a; args[1]= b; args[2]= c; args[3]= d; args[4]= e;
+ with_sum_func= a->with_sum_func || b->with_sum_func ||
+ c->with_sum_func || d->with_sum_func || e->with_sum_func ;
+ with_field= a->with_field || b->with_field ||
+ c->with_field || d->with_field || e->with_field;
+ }
+ }
+ Item_func(List<Item> &list);
+ // Constructor used for Item_cond_and/or (see Item comment)
+ Item_func(THD *thd, Item_func *item);
+ bool fix_fields(THD *, Item **ref);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ void quick_fix_field();
+ table_map used_tables() const;
+ table_map not_null_tables() const;
+ void update_used_tables();
+ bool eq(const Item *item, bool binary_cmp) const;
+ virtual optimize_type select_optimize() const { return OPTIMIZE_NONE; }
+ virtual bool have_rev_func() const { return 0; }
+ virtual Item *key_item() const { return args[0]; }
+ virtual bool const_item() const { return const_item_cache; }
+ inline Item **arguments() const { return args; }
+ void set_arguments(List<Item> &list);
+ inline uint argument_count() const { return arg_count; }
+ inline void remove_arguments() { arg_count=0; }
+ void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields);
+ virtual void print(String *str, enum_query_type query_type);
+ void print_op(String *str, enum_query_type query_type);
+ void print_args(String *str, uint from, enum_query_type query_type);
+ void count_only_length(Item **item, uint nitems);
+ void count_real_length();
+ void count_decimal_length();
+ inline bool get_arg0_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+ {
+ return (null_value=args[0]->get_date_with_conversion(ltime, fuzzy_date));
+ }
+ void count_datetime_length(Item **item, uint nitems);
+ bool count_string_result_length(enum_field_types field_type,
+ Item **item, uint nitems);
+ inline bool get_arg0_time(MYSQL_TIME *ltime)
+ {
+ null_value= args[0]->get_time(ltime);
+ DBUG_ASSERT(null_value ||
+ ltime->time_type != MYSQL_TIMESTAMP_TIME || ltime->day == 0);
+ return null_value;
+ }
+ bool is_null() {
+ update_null_value();
+ return null_value;
+ }
+ void signal_divide_by_null();
+ friend class udf_handler;
+ Field *tmp_table_field() { return result_field; }
+ Field *tmp_table_field(TABLE *t_arg);
+ Item *get_tmp_table_item(THD *thd);
+
+ my_decimal *val_decimal(my_decimal *);
+
+ void fix_char_length_ulonglong(ulonglong max_char_length_arg)
+ {
+ ulonglong max_result_length= max_char_length_arg *
+ collation.collation->mbmaxlen;
+ if (max_result_length >= MAX_BLOB_WIDTH)
+ {
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+ }
+ else
+ max_length= (uint32) max_result_length;
+ }
+ bool agg_arg_charsets(DTCollation &c, Item **items, uint nitems,
+ uint flags, int item_sep)
+ {
+ return agg_item_charsets(c, func_name(), items, nitems, flags, item_sep);
+ }
+ /*
+ Aggregate arguments for string result, e.g: CONCAT(a,b)
+ - convert to @@character_set_connection if all arguments are numbers
+ - allow DERIVATION_NONE
+ */
+ bool agg_arg_charsets_for_string_result(DTCollation &c,
+ Item **items, uint nitems,
+ int item_sep= 1)
+ {
+ return agg_item_charsets_for_string_result(c, func_name(),
+ items, nitems, item_sep);
+ }
+ /*
+ Aggregate arguments for comparison, e.g: a=b, a LIKE b, a RLIKE b
+ - don't convert to @@character_set_connection if all arguments are numbers
+ - don't allow DERIVATION_NONE
+ */
+ bool agg_arg_charsets_for_comparison(DTCollation &c,
+ Item **items, uint nitems,
+ int item_sep= 1)
+ {
+ return agg_item_charsets_for_comparison(c, func_name(),
+ items, nitems, item_sep);
+ }
+ /*
+ Aggregate arguments for string result, when some comparison
+ is involved internally, e.g: REPLACE(a,b,c)
+ - convert to @@character_set_connection if all arguments are numbers
+ - disallow DERIVATION_NONE
+ */
+ bool agg_arg_charsets_for_string_result_with_comparison(DTCollation &c,
+ Item **items,
+ uint nitems,
+ int item_sep= 1)
+ {
+ return agg_item_charsets_for_string_result_with_comparison(c, func_name(),
+ items, nitems,
+ item_sep);
+ }
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg);
+ Item *transform(Item_transformer transformer, uchar *arg);
+ Item* compile(Item_analyzer analyzer, uchar **arg_p,
+ Item_transformer transformer, uchar *arg_t);
+ void traverse_cond(Cond_traverser traverser,
+ void * arg, traverse_order order);
+ bool eval_not_null_tables(uchar *opt_arg);
+ // bool is_expensive_processor(uchar *arg);
+ // virtual bool is_expensive() { return 0; }
+ inline void raise_numeric_overflow(const char *type_name)
+ {
+ char buf[256];
+ String str(buf, sizeof(buf), system_charset_info);
+ str.length(0);
+ print(&str, QT_ORDINARY);
+ my_error(ER_DATA_OUT_OF_RANGE, MYF(0), type_name, str.c_ptr_safe());
+ }
+ inline double raise_float_overflow()
+ {
+ raise_numeric_overflow("DOUBLE");
+ return 0.0;
+ }
+ inline longlong raise_integer_overflow()
+ {
+ raise_numeric_overflow(unsigned_flag ? "BIGINT UNSIGNED": "BIGINT");
+ return 0;
+ }
+ inline int raise_decimal_overflow()
+ {
+ raise_numeric_overflow("DECIMAL");
+ return E_DEC_OVERFLOW;
+ }
+ /**
+ Throw an error if the input double number is not finite, i.e. is either
+ +/-INF or NAN.
+ */
+ inline double check_float_overflow(double value)
+ {
+ return isfinite(value) ? value : raise_float_overflow();
+ }
+ /**
+ Throw an error if the input BIGINT value represented by the
+ (longlong value, bool unsigned flag) pair cannot be returned by the
+ function, i.e. is not compatible with this Item's unsigned_flag.
+ */
+ inline longlong check_integer_overflow(longlong value, bool val_unsigned)
+ {
+ if ((unsigned_flag && !val_unsigned && value < 0) ||
+ (!unsigned_flag && val_unsigned &&
+ (ulonglong) value > (ulonglong) LONGLONG_MAX))
+ return raise_integer_overflow();
+ return value;
+ }
+ /**
+ Throw an error if the error code of a DECIMAL operation is E_DEC_OVERFLOW.
+ */
+ inline int check_decimal_overflow(int error)
+ {
+ return (error == E_DEC_OVERFLOW) ? raise_decimal_overflow() : error;
+ }
+
+ bool has_timestamp_args()
+ {
+ DBUG_ASSERT(fixed == TRUE);
+ for (uint i= 0; i < arg_count; i++)
+ {
+ if (args[i]->type() == Item::FIELD_ITEM &&
+ args[i]->field_type() == MYSQL_TYPE_TIMESTAMP)
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ bool has_date_args()
+ {
+ DBUG_ASSERT(fixed == TRUE);
+ for (uint i= 0; i < arg_count; i++)
+ {
+ if (args[i]->type() == Item::FIELD_ITEM &&
+ (args[i]->field_type() == MYSQL_TYPE_DATE ||
+ args[i]->field_type() == MYSQL_TYPE_DATETIME))
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ bool has_time_args()
+ {
+ DBUG_ASSERT(fixed == TRUE);
+ for (uint i= 0; i < arg_count; i++)
+ {
+ if (args[i]->type() == Item::FIELD_ITEM &&
+ (args[i]->field_type() == MYSQL_TYPE_TIME ||
+ args[i]->field_type() == MYSQL_TYPE_DATETIME))
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ bool has_datetime_args()
+ {
+ DBUG_ASSERT(fixed == TRUE);
+ for (uint i= 0; i < arg_count; i++)
+ {
+ if (args[i]->type() == Item::FIELD_ITEM &&
+ args[i]->field_type() == MYSQL_TYPE_DATETIME)
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /*
+ By default only substitution for a field whose two different values
+ are never equal is allowed in the arguments of a function.
+ This is overruled for the direct arguments of comparison functions.
+ */
+ bool subst_argument_checker(uchar **arg)
+ {
+ if (*arg)
+ {
+ *arg= (uchar *) Item::IDENTITY_SUBST;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /*
+ We assume the result of any function that has a TIMESTAMP argument to be
+ timezone-dependent, since a TIMESTAMP value in both numeric and string
+ contexts is interpreted according to the current timezone.
+ The only exception is UNIX_TIMESTAMP() which returns the internal
+ representation of a TIMESTAMP argument verbatim, and thus does not depend on
+ the timezone.
+ */
+ virtual bool check_valid_arguments_processor(uchar *bool_arg)
+ {
+ return has_timestamp_args();
+ }
+
+ virtual bool find_function_processor (uchar *arg)
+ {
+ return functype() == *(Functype *) arg;
+ }
+
+ void no_rows_in_result()
+ {
+ bool_func_call_args info;
+ info.original_func_item= this;
+ info.bool_function= &Item::no_rows_in_result;
+ walk(&Item::call_bool_func_processor, FALSE, (uchar*) &info);
+ }
+ void restore_to_before_no_rows_in_result()
+ {
+ bool_func_call_args info;
+ info.original_func_item= this;
+ info.bool_function= &Item::restore_to_before_no_rows_in_result;
+ walk(&Item::call_bool_func_processor, FALSE, (uchar*) &info);
+ }
+};
+
+
+class Item_real_func :public Item_func
+{
+public:
+ Item_real_func() :Item_func() { collation.set_numeric(); }
+ Item_real_func(Item *a) :Item_func(a) { collation.set_numeric(); }
+ Item_real_func(Item *a,Item *b) :Item_func(a,b) { collation.set_numeric(); }
+ Item_real_func(List<Item> &list) :Item_func(list) { collation.set_numeric(); }
+ String *val_str(String*str);
+ my_decimal *val_decimal(my_decimal *decimal_value);
+ longlong val_int()
+ { DBUG_ASSERT(fixed == 1); return (longlong) rint(val_real()); }
+ enum Item_result result_type () const { return REAL_RESULT; }
+ void fix_length_and_dec()
+ { decimals= NOT_FIXED_DEC; max_length= float_length(decimals); }
+};
+
+
+class Item_func_hybrid_result_type: public Item_func
+{
+protected:
+ Item_result cached_result_type;
+
+public:
+ Item_func_hybrid_result_type() :Item_func(), cached_result_type(REAL_RESULT)
+ { collation.set_numeric(); }
+ Item_func_hybrid_result_type(Item *a) :Item_func(a), cached_result_type(REAL_RESULT)
+ { collation.set_numeric(); }
+ Item_func_hybrid_result_type(Item *a,Item *b)
+ :Item_func(a,b), cached_result_type(REAL_RESULT)
+ { collation.set_numeric(); }
+ Item_func_hybrid_result_type(Item *a,Item *b,Item *c)
+ :Item_func(a,b,c), cached_result_type(REAL_RESULT)
+ { collation.set_numeric(); }
+ Item_func_hybrid_result_type(List<Item> &list)
+ :Item_func(list), cached_result_type(REAL_RESULT)
+ { collation.set_numeric(); }
+
+ enum Item_result result_type () const { return cached_result_type; }
+
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *);
+ String *val_str(String*str);
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+
+ /**
+ @brief Performs the operation that this functions implements when the
+ result type is INT.
+
+ @return The result of the operation.
+ */
+ virtual longlong int_op()= 0;
+
+ /**
+ @brief Performs the operation that this functions implements when the
+ result type is REAL.
+
+ @return The result of the operation.
+ */
+ virtual double real_op()= 0;
+
+ /**
+ @brief Performs the operation that this functions implements when the
+ result type is DECIMAL.
+
+ @param A pointer where the DECIMAL value will be allocated.
+ @return
+ - 0 If the result is NULL
+ - The same pointer it was given, with the area initialized to the
+ result of the operation.
+ */
+ virtual my_decimal *decimal_op(my_decimal *)= 0;
+
+ /**
+ @brief Performs the operation that this functions implements when the
+ result type is a string type.
+
+ @return The result of the operation.
+ */
+ virtual String *str_op(String *)= 0;
+
+ /**
+ @brief Performs the operation that this functions implements when
+ field type is a temporal type.
+ @return The result of the operation.
+ */
+ virtual bool date_op(MYSQL_TIME *res, uint fuzzy_date)= 0;
+
+};
+
+
+
+class Item_func_hybrid_field_type :public Item_func_hybrid_result_type
+{
+protected:
+ enum_field_types cached_field_type;
+public:
+ Item_func_hybrid_field_type()
+ :Item_func_hybrid_result_type(), cached_field_type(MYSQL_TYPE_DOUBLE)
+ {}
+ Item_func_hybrid_field_type(Item *a, Item *b)
+ :Item_func_hybrid_result_type(a, b), cached_field_type(MYSQL_TYPE_DOUBLE)
+ {}
+ Item_func_hybrid_field_type(Item *a, Item *b, Item *c)
+ :Item_func_hybrid_result_type(a, b, c),
+ cached_field_type(MYSQL_TYPE_DOUBLE)
+ {}
+ Item_func_hybrid_field_type(List<Item> &list)
+ :Item_func_hybrid_result_type(list),
+ cached_field_type(MYSQL_TYPE_DOUBLE)
+ {}
+ enum_field_types field_type() const { return cached_field_type; }
+};
+
+
+
+class Item_func_numhybrid: public Item_func_hybrid_result_type
+{
+protected:
+
+ inline void fix_decimals()
+ {
+ DBUG_ASSERT(result_type() == DECIMAL_RESULT);
+ if (decimals == NOT_FIXED_DEC)
+ set_if_smaller(decimals, max_length - 1);
+ }
+
+public:
+ Item_func_numhybrid() :Item_func_hybrid_result_type()
+ { }
+ Item_func_numhybrid(Item *a) :Item_func_hybrid_result_type(a)
+ { }
+ Item_func_numhybrid(Item *a,Item *b)
+ :Item_func_hybrid_result_type(a,b)
+ { }
+ Item_func_numhybrid(Item *a,Item *b,Item *c)
+ :Item_func_hybrid_result_type(a,b,c)
+ { }
+ Item_func_numhybrid(List<Item> &list)
+ :Item_func_hybrid_result_type(list)
+ { }
+ String *str_op(String *str) { DBUG_ASSERT(0); return 0; }
+ bool date_op(MYSQL_TIME *ltime, uint fuzzydate) { DBUG_ASSERT(0); return true; }
+};
+
+
+/* function where type of result detected by first argument */
+class Item_func_num1: public Item_func_numhybrid
+{
+public:
+ Item_func_num1(Item *a) :Item_func_numhybrid(a) {}
+ Item_func_num1(Item *a, Item *b) :Item_func_numhybrid(a, b) {}
+ void fix_length_and_dec();
+};
+
+
+/* Base class for operations like '+', '-', '*' */
+class Item_num_op :public Item_func_numhybrid
+{
+ public:
+ Item_num_op(Item *a,Item *b) :Item_func_numhybrid(a, b) {}
+ virtual void result_precision()= 0;
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ print_op(str, query_type);
+ }
+
+ void fix_length_and_dec();
+};
+
+
+class Item_int_func :public Item_func
+{
+protected:
+ bool sargable;
+public:
+ Item_int_func() :Item_func()
+ { collation.set_numeric(); fix_char_length(21); sargable= false; }
+ Item_int_func(Item *a) :Item_func(a)
+ { collation.set_numeric(); fix_char_length(21); sargable= false; }
+ Item_int_func(Item *a,Item *b) :Item_func(a,b)
+ { collation.set_numeric(); fix_char_length(21); sargable= false; }
+ Item_int_func(Item *a,Item *b,Item *c) :Item_func(a,b,c)
+ { collation.set_numeric(); fix_char_length(21); sargable= false; }
+ Item_int_func(Item *a,Item *b,Item *c, Item *d) :Item_func(a,b,c,d)
+ { collation.set_numeric(); fix_char_length(21); sargable= false; }
+ Item_int_func(List<Item> &list) :Item_func(list)
+ { collation.set_numeric(); fix_char_length(21); sargable= false; }
+ Item_int_func(THD *thd, Item_int_func *item) :Item_func(thd, item)
+ { collation.set_numeric(); sargable= false; }
+ double val_real();
+ String *val_str(String*str);
+ enum Item_result result_type () const { return INT_RESULT; }
+ void fix_length_and_dec() {}
+ bool count_sargable_conds(uchar *arg);
+};
+
+
+class Item_func_connection_id :public Item_int_func
+{
+ longlong value;
+
+public:
+ Item_func_connection_id() {}
+ const char *func_name() const { return "connection_id"; }
+ void fix_length_and_dec();
+ bool fix_fields(THD *thd, Item **ref);
+ longlong val_int() { DBUG_ASSERT(fixed == 1); return value; }
+ bool check_vcol_func_processor(uchar *int_arg) { return TRUE;}
+};
+
+
+class Item_func_signed :public Item_int_func
+{
+public:
+ Item_func_signed(Item *a) :Item_int_func(a)
+ {
+ unsigned_flag= 0;
+ }
+ const char *func_name() const { return "cast_as_signed"; }
+ longlong val_int();
+ longlong val_int_from_str(int *error);
+ void fix_length_and_dec()
+ {
+ fix_char_length(MY_MIN(args[0]->max_char_length(),
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ }
+ virtual void print(String *str, enum_query_type query_type);
+ uint decimal_precision() const { return args[0]->decimal_precision(); }
+};
+
+
+class Item_func_unsigned :public Item_func_signed
+{
+public:
+ Item_func_unsigned(Item *a) :Item_func_signed(a)
+ {
+ unsigned_flag= 1;
+ }
+ const char *func_name() const { return "cast_as_unsigned"; }
+ longlong val_int();
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_decimal_typecast :public Item_func
+{
+ my_decimal decimal_value;
+public:
+ Item_decimal_typecast(Item *a, int len, int dec) :Item_func(a)
+ {
+ decimals= (uint8) dec;
+ collation.set_numeric();
+ fix_char_length(my_decimal_precision_to_length_no_truncation(len, dec,
+ unsigned_flag));
+ }
+ String *val_str(String *str);
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal*);
+ enum Item_result result_type () const { return DECIMAL_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_NEWDECIMAL; }
+ void fix_length_and_dec() {}
+ const char *func_name() const { return "decimal_typecast"; }
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_double_typecast :public Item_real_func
+{
+public:
+ Item_double_typecast(Item *a, int len, int dec) :Item_real_func(a)
+ {
+ decimals= (uint8) dec;
+ max_length= (uint32) len;
+ }
+ double val_real();
+ enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; }
+ void fix_length_and_dec() { maybe_null= 1; }
+ const char *func_name() const { return "double_typecast"; }
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+
+class Item_func_additive_op :public Item_num_op
+{
+public:
+ Item_func_additive_op(Item *a,Item *b) :Item_num_op(a,b) {}
+ void result_precision();
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+
+class Item_func_plus :public Item_func_additive_op
+{
+public:
+ Item_func_plus(Item *a,Item *b) :Item_func_additive_op(a,b) {}
+ const char *func_name() const { return "+"; }
+ longlong int_op();
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+};
+
+class Item_func_minus :public Item_func_additive_op
+{
+public:
+ Item_func_minus(Item *a,Item *b) :Item_func_additive_op(a,b) {}
+ const char *func_name() const { return "-"; }
+ longlong int_op();
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+ void fix_length_and_dec();
+};
+
+
+class Item_func_mul :public Item_num_op
+{
+public:
+ Item_func_mul(Item *a,Item *b) :Item_num_op(a,b) {}
+ const char *func_name() const { return "*"; }
+ longlong int_op();
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+ void result_precision();
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+
+class Item_func_div :public Item_num_op
+{
+public:
+ uint prec_increment;
+ Item_func_div(Item *a,Item *b) :Item_num_op(a,b) {}
+ longlong int_op() { DBUG_ASSERT(0); return 0; }
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+ const char *func_name() const { return "/"; }
+ void fix_length_and_dec();
+ void result_precision();
+};
+
+
+class Item_func_int_div :public Item_int_func
+{
+public:
+ Item_func_int_div(Item *a,Item *b) :Item_int_func(a,b)
+ {}
+ longlong val_int();
+ const char *func_name() const { return "DIV"; }
+ void fix_length_and_dec();
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ print_op(str, query_type);
+ }
+
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+
+class Item_func_mod :public Item_num_op
+{
+public:
+ Item_func_mod(Item *a,Item *b) :Item_num_op(a,b) {}
+ longlong int_op();
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+ const char *func_name() const { return "%"; }
+ void result_precision();
+ void fix_length_and_dec();
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+
+class Item_func_neg :public Item_func_num1
+{
+public:
+ Item_func_neg(Item *a) :Item_func_num1(a) {}
+ double real_op();
+ longlong int_op();
+ my_decimal *decimal_op(my_decimal *);
+ const char *func_name() const { return "-"; }
+ enum Functype functype() const { return NEG_FUNC; }
+ void fix_length_and_dec();
+ uint decimal_precision() const { return args[0]->decimal_precision(); }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+
+class Item_func_abs :public Item_func_num1
+{
+public:
+ Item_func_abs(Item *a) :Item_func_num1(a) {}
+ double real_op();
+ longlong int_op();
+ my_decimal *decimal_op(my_decimal *);
+ const char *func_name() const { return "abs"; }
+ void fix_length_and_dec();
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+// A class to handle logarithmic and trigonometric functions
+
+class Item_dec_func :public Item_real_func
+{
+ public:
+ Item_dec_func(Item *a) :Item_real_func(a) {}
+ Item_dec_func(Item *a,Item *b) :Item_real_func(a,b) {}
+ void fix_length_and_dec()
+ {
+ decimals=NOT_FIXED_DEC; max_length=float_length(decimals);
+ maybe_null=1;
+ }
+};
+
+class Item_func_exp :public Item_dec_func
+{
+public:
+ Item_func_exp(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "exp"; }
+};
+
+
+class Item_func_ln :public Item_dec_func
+{
+public:
+ Item_func_ln(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "ln"; }
+};
+
+
+class Item_func_log :public Item_dec_func
+{
+public:
+ Item_func_log(Item *a) :Item_dec_func(a) {}
+ Item_func_log(Item *a,Item *b) :Item_dec_func(a,b) {}
+ double val_real();
+ const char *func_name() const { return "log"; }
+};
+
+
+class Item_func_log2 :public Item_dec_func
+{
+public:
+ Item_func_log2(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "log2"; }
+};
+
+
+class Item_func_log10 :public Item_dec_func
+{
+public:
+ Item_func_log10(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "log10"; }
+};
+
+
+class Item_func_sqrt :public Item_dec_func
+{
+public:
+ Item_func_sqrt(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "sqrt"; }
+};
+
+
+class Item_func_pow :public Item_dec_func
+{
+public:
+ Item_func_pow(Item *a,Item *b) :Item_dec_func(a,b) {}
+ double val_real();
+ const char *func_name() const { return "pow"; }
+};
+
+
+class Item_func_acos :public Item_dec_func
+{
+public:
+ Item_func_acos(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "acos"; }
+};
+
+class Item_func_asin :public Item_dec_func
+{
+public:
+ Item_func_asin(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "asin"; }
+};
+
+class Item_func_atan :public Item_dec_func
+{
+public:
+ Item_func_atan(Item *a) :Item_dec_func(a) {}
+ Item_func_atan(Item *a,Item *b) :Item_dec_func(a,b) {}
+ double val_real();
+ const char *func_name() const { return "atan"; }
+};
+
+class Item_func_cos :public Item_dec_func
+{
+public:
+ Item_func_cos(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "cos"; }
+};
+
+class Item_func_sin :public Item_dec_func
+{
+public:
+ Item_func_sin(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "sin"; }
+};
+
+class Item_func_tan :public Item_dec_func
+{
+public:
+ Item_func_tan(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "tan"; }
+};
+
+class Item_func_cot :public Item_dec_func
+{
+public:
+ Item_func_cot(Item *a) :Item_dec_func(a) {}
+ double val_real();
+ const char *func_name() const { return "cot"; }
+};
+
+class Item_func_integer :public Item_int_func
+{
+public:
+ inline Item_func_integer(Item *a) :Item_int_func(a) {}
+ void fix_length_and_dec();
+};
+
+
+class Item_func_int_val :public Item_func_num1
+{
+public:
+ Item_func_int_val(Item *a) :Item_func_num1(a) {}
+ void fix_length_and_dec();
+};
+
+
+class Item_func_ceiling :public Item_func_int_val
+{
+public:
+ Item_func_ceiling(Item *a) :Item_func_int_val(a) {}
+ const char *func_name() const { return "ceiling"; }
+ longlong int_op();
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+
+class Item_func_floor :public Item_func_int_val
+{
+public:
+ Item_func_floor(Item *a) :Item_func_int_val(a) {}
+ const char *func_name() const { return "floor"; }
+ longlong int_op();
+ double real_op();
+ my_decimal *decimal_op(my_decimal *);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+/* This handles round and truncate */
+
+class Item_func_round :public Item_func_num1
+{
+ bool truncate;
+public:
+ Item_func_round(Item *a, Item *b, bool trunc_arg)
+ :Item_func_num1(a,b), truncate(trunc_arg) {}
+ const char *func_name() const { return truncate ? "truncate" : "round"; }
+ double real_op();
+ longlong int_op();
+ my_decimal *decimal_op(my_decimal *);
+ void fix_length_and_dec();
+};
+
+
+class Item_func_rand :public Item_real_func
+{
+ struct my_rnd_struct *rand;
+ bool first_eval; // TRUE if val_real() is called 1st time
+public:
+ Item_func_rand(Item *a) :Item_real_func(a), rand(0), first_eval(TRUE) {}
+ Item_func_rand() :Item_real_func() {}
+ double val_real();
+ const char *func_name() const { return "rand"; }
+ bool const_item() const { return 0; }
+ void update_used_tables();
+ bool fix_fields(THD *thd, Item **ref);
+ void cleanup() { first_eval= TRUE; Item_real_func::cleanup(); }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+private:
+ void seed_random (Item * val);
+};
+
+
+class Item_func_sign :public Item_int_func
+{
+public:
+ Item_func_sign(Item *a) :Item_int_func(a) {}
+ const char *func_name() const { return "sign"; }
+ longlong val_int();
+};
+
+
+class Item_func_units :public Item_real_func
+{
+ char *name;
+ double mul,add;
+public:
+ Item_func_units(char *name_arg,Item *a,double mul_arg,double add_arg)
+ :Item_real_func(a),name(name_arg),mul(mul_arg),add(add_arg) {}
+ double val_real();
+ const char *func_name() const { return name; }
+ void fix_length_and_dec()
+ { decimals= NOT_FIXED_DEC; max_length= float_length(decimals); }
+};
+
+
+class Item_func_min_max :public Item_func
+{
+ Item_result cmp_type;
+ String tmp_value;
+ int cmp_sign;
+ /* An item used for issuing warnings while string to DATETIME conversion. */
+ Item *compare_as_dates;
+ THD *thd;
+protected:
+ enum_field_types cached_field_type;
+public:
+ Item_func_min_max(List<Item> &list,int cmp_sign_arg) :Item_func(list),
+ cmp_type(INT_RESULT), cmp_sign(cmp_sign_arg), compare_as_dates(0) {}
+ double val_real();
+ longlong val_int();
+ String *val_str(String *);
+ my_decimal *val_decimal(my_decimal *);
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ void fix_length_and_dec();
+ enum Item_result result_type () const { return cmp_type; }
+ enum_field_types field_type() const { return cached_field_type; }
+};
+
+class Item_func_min :public Item_func_min_max
+{
+public:
+ Item_func_min(List<Item> &list) :Item_func_min_max(list,1) {}
+ const char *func_name() const { return "least"; }
+};
+
+class Item_func_max :public Item_func_min_max
+{
+public:
+ Item_func_max(List<Item> &list) :Item_func_min_max(list,-1) {}
+ const char *func_name() const { return "greatest"; }
+};
+
+
+/*
+ Objects of this class are used for ROLLUP queries to wrap up
+ each constant item referred to in GROUP BY list.
+*/
+
+class Item_func_rollup_const :public Item_func
+{
+public:
+ Item_func_rollup_const(Item *a) :Item_func(a)
+ {
+ name= a->name;
+ name_length= a->name_length;
+ }
+ double val_real() { return args[0]->val_real(); }
+ longlong val_int() { return args[0]->val_int(); }
+ String *val_str(String *str) { return args[0]->val_str(str); }
+ my_decimal *val_decimal(my_decimal *dec) { return args[0]->val_decimal(dec); }
+ const char *func_name() const { return "rollup_const"; }
+ bool const_item() const { return 0; }
+ Item_result result_type() const { return args[0]->result_type(); }
+ void fix_length_and_dec()
+ {
+ collation= args[0]->collation;
+ max_length= args[0]->max_length;
+ decimals=args[0]->decimals;
+ /* The item could be a NULL constant. */
+ null_value= args[0]->is_null();
+ }
+};
+
+
+class Item_func_length :public Item_int_func
+{
+ String value;
+public:
+ Item_func_length(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "length"; }
+ void fix_length_and_dec() { max_length=10; }
+};
+
+class Item_func_bit_length :public Item_func_length
+{
+public:
+ Item_func_bit_length(Item *a) :Item_func_length(a) {}
+ longlong val_int()
+ { DBUG_ASSERT(fixed == 1); return Item_func_length::val_int()*8; }
+ const char *func_name() const { return "bit_length"; }
+};
+
+class Item_func_char_length :public Item_int_func
+{
+ String value;
+public:
+ Item_func_char_length(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "char_length"; }
+ void fix_length_and_dec() { max_length=10; }
+};
+
+class Item_func_coercibility :public Item_int_func
+{
+public:
+ Item_func_coercibility(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "coercibility"; }
+ void fix_length_and_dec() { max_length=10; maybe_null= 0; }
+ table_map not_null_tables() const { return 0; }
+};
+
+class Item_func_locate :public Item_int_func
+{
+ String value1,value2;
+ DTCollation cmp_collation;
+public:
+ Item_func_locate(Item *a,Item *b) :Item_int_func(a,b) {}
+ Item_func_locate(Item *a,Item *b,Item *c) :Item_int_func(a,b,c) {}
+ const char *func_name() const { return "locate"; }
+ longlong val_int();
+ void fix_length_and_dec();
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_func_field :public Item_int_func
+{
+ String value,tmp;
+ Item_result cmp_type;
+ DTCollation cmp_collation;
+public:
+ Item_func_field(List<Item> &list) :Item_int_func(list) {}
+ longlong val_int();
+ const char *func_name() const { return "field"; }
+ void fix_length_and_dec();
+};
+
+
+class Item_func_ascii :public Item_int_func
+{
+ String value;
+public:
+ Item_func_ascii(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "ascii"; }
+ void fix_length_and_dec() { max_length=3; }
+};
+
+class Item_func_ord :public Item_int_func
+{
+ String value;
+public:
+ Item_func_ord(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "ord"; }
+};
+
+class Item_func_find_in_set :public Item_int_func
+{
+ String value,value2;
+ uint enum_value;
+ ulonglong enum_bit;
+ DTCollation cmp_collation;
+public:
+ Item_func_find_in_set(Item *a,Item *b) :Item_int_func(a,b),enum_value(0) {}
+ longlong val_int();
+ const char *func_name() const { return "find_in_set"; }
+ void fix_length_and_dec();
+};
+
+/* Base class for all bit functions: '~', '|', '^', '&', '>>', '<<' */
+
+class Item_func_bit: public Item_int_func
+{
+public:
+ Item_func_bit(Item *a, Item *b) :Item_int_func(a, b) {}
+ Item_func_bit(Item *a) :Item_int_func(a) {}
+ void fix_length_and_dec() { unsigned_flag= 1; }
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ print_op(str, query_type);
+ }
+};
+
+class Item_func_bit_or :public Item_func_bit
+{
+public:
+ Item_func_bit_or(Item *a, Item *b) :Item_func_bit(a, b) {}
+ longlong val_int();
+ const char *func_name() const { return "|"; }
+};
+
+class Item_func_bit_and :public Item_func_bit
+{
+public:
+ Item_func_bit_and(Item *a, Item *b) :Item_func_bit(a, b) {}
+ longlong val_int();
+ const char *func_name() const { return "&"; }
+};
+
+class Item_func_bit_count :public Item_int_func
+{
+public:
+ Item_func_bit_count(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "bit_count"; }
+ void fix_length_and_dec() { max_length=2; }
+};
+
+class Item_func_shift_left :public Item_func_bit
+{
+public:
+ Item_func_shift_left(Item *a, Item *b) :Item_func_bit(a, b) {}
+ longlong val_int();
+ const char *func_name() const { return "<<"; }
+};
+
+class Item_func_shift_right :public Item_func_bit
+{
+public:
+ Item_func_shift_right(Item *a, Item *b) :Item_func_bit(a, b) {}
+ longlong val_int();
+ const char *func_name() const { return ">>"; }
+};
+
+class Item_func_bit_neg :public Item_func_bit
+{
+public:
+ Item_func_bit_neg(Item *a) :Item_func_bit(a) {}
+ longlong val_int();
+ const char *func_name() const { return "~"; }
+
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ Item_func::print(str, query_type);
+ }
+};
+
+
+class Item_func_last_insert_id :public Item_int_func
+{
+public:
+ Item_func_last_insert_id() :Item_int_func() {}
+ Item_func_last_insert_id(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "last_insert_id"; }
+ void fix_length_and_dec()
+ {
+ unsigned_flag= TRUE;
+ if (arg_count)
+ max_length= args[0]->max_length;
+ unsigned_flag=1;
+ }
+ bool fix_fields(THD *thd, Item **ref);
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_benchmark :public Item_int_func
+{
+public:
+ Item_func_benchmark(Item *count_expr, Item *expr)
+ :Item_int_func(count_expr, expr)
+ {}
+ longlong val_int();
+ const char *func_name() const { return "benchmark"; }
+ void fix_length_and_dec() { max_length=1; maybe_null=0; }
+ virtual void print(String *str, enum_query_type query_type);
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+void item_func_sleep_init(void);
+void item_func_sleep_free(void);
+
+class Item_func_sleep :public Item_int_func
+{
+public:
+ Item_func_sleep(Item *a) :Item_int_func(a) {}
+ bool const_item() const { return 0; }
+ const char *func_name() const { return "sleep"; }
+ table_map used_tables() const
+ {
+ return Item_int_func::used_tables() | RAND_TABLE_BIT;
+ }
+ bool is_expensive() { return 1; }
+ longlong val_int();
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+
+#ifdef HAVE_DLOPEN
+
+class Item_udf_func :public Item_func
+{
+protected:
+ udf_handler udf;
+ bool is_expensive_processor(uchar *arg) { return TRUE; }
+
+public:
+ Item_udf_func(udf_func *udf_arg)
+ :Item_func(), udf(udf_arg) {}
+ Item_udf_func(udf_func *udf_arg, List<Item> &list)
+ :Item_func(list), udf(udf_arg) {}
+ const char *func_name() const { return udf.name(); }
+ enum Functype functype() const { return UDF_FUNC; }
+ bool fix_fields(THD *thd, Item **ref)
+ {
+ DBUG_ASSERT(fixed == 0);
+ bool res= udf.fix_fields(thd, this, arg_count, args);
+ used_tables_cache= udf.used_tables_cache;
+ const_item_cache= udf.const_item_cache;
+ fixed= 1;
+ return res;
+ }
+ void fix_num_length_and_dec();
+ void update_used_tables()
+ {
+ /*
+ TODO: Make a member in UDF_INIT and return if a UDF is deterministic or
+ not.
+ Currently UDF_INIT has a member (const_item) that is an in/out
+ parameter to the init() call.
+ The code in udf_handler::fix_fields also duplicates the arguments
+ handling code in Item_func::fix_fields().
+
+ The lack of information if a UDF is deterministic makes writing
+ a correct update_used_tables() for UDFs impossible.
+ One solution to this would be :
+ - Add a is_deterministic member of UDF_INIT
+ - (optionally) deprecate the const_item member of UDF_INIT
+ - Take away the duplicate code from udf_handler::fix_fields() and
+ make Item_udf_func call Item_func::fix_fields() to process its
+ arguments as for any other function.
+ - Store the deterministic flag returned by <udf>_init into the
+ udf_handler.
+ - Don't implement Item_udf_func::fix_fields, implement
+ Item_udf_func::fix_length_and_dec() instead (similar to non-UDF
+ functions).
+ - Override Item_func::update_used_tables to call
+ Item_func::update_used_tables() and add a RAND_TABLE_BIT to the
+ result of Item_func::update_used_tables() if the UDF is
+ non-deterministic.
+ - (optionally) rename RAND_TABLE_BIT to NONDETERMINISTIC_BIT to
+ better describe its usage.
+
+ The above would require a change of the UDF API.
+ Until that change is done here's how the current code works:
+ We call Item_func::update_used_tables() only when we know that
+ the function depends on real non-const tables and is deterministic.
+ This can be done only because we know that the optimizer will
+ call update_used_tables() only when there's possibly a new const
+ table. So update_used_tables() can only make a Item_func more
+ constant than it is currently.
+ That's why we don't need to do anything if a function is guaranteed
+ to return non-constant (it's non-deterministic) or is already a
+ const.
+ */
+ if ((used_tables_cache & ~PSEUDO_TABLE_BITS) &&
+ !(used_tables_cache & RAND_TABLE_BIT))
+ {
+ Item_func::update_used_tables();
+ if (!const_item_cache && !used_tables_cache)
+ used_tables_cache= RAND_TABLE_BIT;
+ }
+ }
+ void cleanup();
+ Item_result result_type () const { return udf.result_type(); }
+ table_map not_null_tables() const { return 0; }
+ bool is_expensive() { return 1; }
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_func_udf_float :public Item_udf_func
+{
+ public:
+ Item_func_udf_float(udf_func *udf_arg)
+ :Item_udf_func(udf_arg) {}
+ Item_func_udf_float(udf_func *udf_arg,
+ List<Item> &list)
+ :Item_udf_func(udf_arg, list) {}
+ longlong val_int()
+ {
+ DBUG_ASSERT(fixed == 1);
+ return (longlong) rint(Item_func_udf_float::val_real());
+ }
+ my_decimal *val_decimal(my_decimal *dec_buf)
+ {
+ double res=val_real();
+ if (null_value)
+ return NULL;
+ double2my_decimal(E_DEC_FATAL_ERROR, res, dec_buf);
+ return dec_buf;
+ }
+ double val_real();
+ String *val_str(String *str);
+ void fix_length_and_dec() { fix_num_length_and_dec(); }
+};
+
+
+class Item_func_udf_int :public Item_udf_func
+{
+public:
+ Item_func_udf_int(udf_func *udf_arg)
+ :Item_udf_func(udf_arg) {}
+ Item_func_udf_int(udf_func *udf_arg,
+ List<Item> &list)
+ :Item_udf_func(udf_arg, list) {}
+ longlong val_int();
+ double val_real() { return (double) Item_func_udf_int::val_int(); }
+ String *val_str(String *str);
+ enum Item_result result_type () const { return INT_RESULT; }
+ void fix_length_and_dec() { decimals= 0; max_length= 21; }
+};
+
+
+class Item_func_udf_decimal :public Item_udf_func
+{
+public:
+ Item_func_udf_decimal(udf_func *udf_arg)
+ :Item_udf_func(udf_arg) {}
+ Item_func_udf_decimal(udf_func *udf_arg, List<Item> &list)
+ :Item_udf_func(udf_arg, list) {}
+ longlong val_int();
+ double val_real();
+ my_decimal *val_decimal(my_decimal *);
+ String *val_str(String *str);
+ enum Item_result result_type () const { return DECIMAL_RESULT; }
+ void fix_length_and_dec() { fix_num_length_and_dec(); }
+};
+
+
+class Item_func_udf_str :public Item_udf_func
+{
+public:
+ Item_func_udf_str(udf_func *udf_arg)
+ :Item_udf_func(udf_arg) {}
+ Item_func_udf_str(udf_func *udf_arg, List<Item> &list)
+ :Item_udf_func(udf_arg, list) {}
+ String *val_str(String *);
+ double val_real()
+ {
+ int err_not_used;
+ char *end_not_used;
+ String *res;
+ res= val_str(&str_value);
+ return res ? my_strntod(res->charset(),(char*) res->ptr(),
+ res->length(), &end_not_used, &err_not_used) : 0.0;
+ }
+ longlong val_int()
+ {
+ int err_not_used;
+ String *res; res=val_str(&str_value);
+ return res ? my_strntoll(res->charset(),res->ptr(),res->length(),10,
+ (char**) 0, &err_not_used) : (longlong) 0;
+ }
+ my_decimal *val_decimal(my_decimal *dec_buf)
+ {
+ String *res=val_str(&str_value);
+ if (!res)
+ return NULL;
+ string2my_decimal(E_DEC_FATAL_ERROR, res, dec_buf);
+ return dec_buf;
+ }
+ enum Item_result result_type () const { return STRING_RESULT; }
+ void fix_length_and_dec();
+};
+
+#else /* Dummy functions to get sql_yacc.cc compiled */
+
+class Item_func_udf_float :public Item_real_func
+{
+ public:
+ Item_func_udf_float(udf_func *udf_arg)
+ :Item_real_func() {}
+ Item_func_udf_float(udf_func *udf_arg, List<Item> &list)
+ :Item_real_func(list) {}
+ double val_real() { DBUG_ASSERT(fixed == 1); return 0.0; }
+};
+
+
+class Item_func_udf_int :public Item_int_func
+{
+public:
+ Item_func_udf_int(udf_func *udf_arg)
+ :Item_int_func() {}
+ Item_func_udf_int(udf_func *udf_arg, List<Item> &list)
+ :Item_int_func(list) {}
+ longlong val_int() { DBUG_ASSERT(fixed == 1); return 0; }
+};
+
+
+class Item_func_udf_decimal :public Item_int_func
+{
+public:
+ Item_func_udf_decimal(udf_func *udf_arg)
+ :Item_int_func() {}
+ Item_func_udf_decimal(udf_func *udf_arg, List<Item> &list)
+ :Item_int_func(list) {}
+ my_decimal *val_decimal(my_decimal *) { DBUG_ASSERT(fixed == 1); return 0; }
+};
+
+
+class Item_func_udf_str :public Item_func
+{
+public:
+ Item_func_udf_str(udf_func *udf_arg)
+ :Item_func() {}
+ Item_func_udf_str(udf_func *udf_arg, List<Item> &list)
+ :Item_func(list) {}
+ String *val_str(String *)
+ { DBUG_ASSERT(fixed == 1); null_value=1; return 0; }
+ double val_real() { DBUG_ASSERT(fixed == 1); null_value= 1; return 0.0; }
+ longlong val_int() { DBUG_ASSERT(fixed == 1); null_value=1; return 0; }
+ enum Item_result result_type () const { return STRING_RESULT; }
+ void fix_length_and_dec() { maybe_null=1; max_length=0; }
+};
+
+#endif /* HAVE_DLOPEN */
+
+void mysql_ull_cleanup(THD *thd);
+void mysql_ull_set_explicit_lock_duration(THD *thd);
+
+class Item_func_get_lock :public Item_int_func
+{
+ String value;
+ public:
+ Item_func_get_lock(Item *a,Item *b) :Item_int_func(a,b) {}
+ longlong val_int();
+ const char *func_name() const { return "get_lock"; }
+ void fix_length_and_dec() { max_length=1; maybe_null=1;}
+ table_map used_tables() const
+ {
+ return Item_int_func::used_tables() | RAND_TABLE_BIT;
+ }
+ bool const_item() const { return 0; }
+ bool is_expensive() { return 1; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+class Item_func_release_lock :public Item_int_func
+{
+ String value;
+public:
+ Item_func_release_lock(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "release_lock"; }
+ void fix_length_and_dec() { max_length= 1; maybe_null= 1;}
+ table_map used_tables() const
+ {
+ return Item_int_func::used_tables() | RAND_TABLE_BIT;
+ }
+ bool const_item() const { return 0; }
+ bool is_expensive() { return 1; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+/* replication functions */
+
+class Item_master_pos_wait :public Item_int_func
+{
+ String value;
+public:
+ Item_master_pos_wait(Item *a,Item *b) :Item_int_func(a,b) {}
+ Item_master_pos_wait(Item *a,Item *b,Item *c) :Item_int_func(a,b,c) {}
+ Item_master_pos_wait(Item *a,Item *b, Item *c, Item *d) :Item_int_func(a,b,c,d) {}
+ longlong val_int();
+ const char *func_name() const { return "master_pos_wait"; }
+ void fix_length_and_dec() { max_length=21; maybe_null=1;}
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_master_gtid_wait :public Item_int_func
+{
+ String value;
+public:
+ Item_master_gtid_wait(Item *a) :Item_int_func(a) {}
+ Item_master_gtid_wait(Item *a,Item *b) :Item_int_func(a,b) {}
+ longlong val_int();
+ const char *func_name() const { return "master_gtid_wait"; }
+ void fix_length_and_dec() { max_length=10+1+10+1+20+1; maybe_null=0;}
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+/* Handling of user definable variables */
+
+class user_var_entry;
+
+class Item_func_set_user_var :public Item_func
+{
+ enum Item_result cached_result_type;
+ user_var_entry *entry;
+ /*
+ The entry_thread_id variable is used:
+ 1) to skip unnecessary updates of the entry field (see above);
+ 2) to reset the entry field that was initialized in the other thread
+ (for example, an item tree of a trigger that updates user variables
+ may be shared between several connections, and the entry_thread_id field
+ prevents updates of one connection user variables from a concurrent
+ connection calling the same trigger that initially updated some
+ user variable it the first connection context).
+ */
+ my_thread_id entry_thread_id;
+ char buffer[MAX_FIELD_WIDTH];
+ String value;
+ my_decimal decimal_buff;
+ bool null_item;
+ union
+ {
+ longlong vint;
+ double vreal;
+ String *vstr;
+ my_decimal *vdec;
+ } save_result;
+
+public:
+ LEX_STRING name; // keep it public
+ Item_func_set_user_var(LEX_STRING a,Item *b)
+ :Item_func(b), cached_result_type(INT_RESULT),
+ entry(NULL), entry_thread_id(0), name(a)
+ {}
+ Item_func_set_user_var(THD *thd, Item_func_set_user_var *item)
+ :Item_func(thd, item), cached_result_type(item->cached_result_type),
+ entry(item->entry), entry_thread_id(item->entry_thread_id),
+ value(item->value), decimal_buff(item->decimal_buff),
+ null_item(item->null_item), save_result(item->save_result),
+ name(item->name)
+ {}
+
+ enum Functype functype() const { return SUSERVAR_FUNC; }
+ double val_real();
+ longlong val_int();
+ String *val_str(String *str);
+ my_decimal *val_decimal(my_decimal *);
+ double val_result();
+ longlong val_int_result();
+ bool val_bool_result();
+ String *str_result(String *str);
+ my_decimal *val_decimal_result(my_decimal *);
+ bool is_null_result();
+ bool update_hash(void *ptr, uint length, enum Item_result type,
+ CHARSET_INFO *cs, Derivation dv, bool unsigned_arg);
+ bool send(Protocol *protocol, String *str_arg);
+ void make_field(Send_field *tmp_field);
+ bool check(bool use_result_field);
+ void save_item_result(Item *item);
+ bool update();
+ enum Item_result result_type () const { return cached_result_type; }
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec();
+ table_map used_tables() const
+ {
+ return Item_func::used_tables() | RAND_TABLE_BIT;
+ }
+ bool const_item() const { return 0; }
+ bool is_expensive() { return 1; }
+ virtual void print(String *str, enum_query_type query_type);
+ void print_as_stmt(String *str, enum_query_type query_type);
+ const char *func_name() const { return "set_user_var"; }
+ int save_in_field(Field *field, bool no_conversions,
+ bool can_use_result_field);
+ int save_in_field(Field *field, bool no_conversions)
+ {
+ return save_in_field(field, no_conversions, 1);
+ }
+ void save_org_in_field(Field *field,
+ fast_field_copier data __attribute__ ((__unused__)))
+ { (void)save_in_field(field, 1, 0); }
+ bool register_field_in_read_map(uchar *arg);
+ bool register_field_in_bitmap(uchar *arg);
+ bool set_entry(THD *thd, bool create_if_not_exists);
+ void cleanup();
+ bool check_vcol_func_processor(uchar *int_arg) {return TRUE;}
+};
+
+
+class Item_func_get_user_var :public Item_func,
+ private Settable_routine_parameter
+{
+ user_var_entry *var_entry;
+ Item_result m_cached_result_type;
+
+public:
+ LEX_STRING name; // keep it public
+ Item_func_get_user_var(LEX_STRING a):
+ Item_func(), m_cached_result_type(STRING_RESULT), name(a) {}
+ enum Functype functype() const { return GUSERVAR_FUNC; }
+ LEX_STRING get_name() { return name; }
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal*);
+ String *val_str(String* str);
+ void fix_length_and_dec();
+ virtual void print(String *str, enum_query_type query_type);
+ enum Item_result result_type() const;
+ /*
+ We must always return variables as strings to guard against selects of type
+ select @t1:=1,@t1,@t:="hello",@t from foo where (@t1:= t2.b)
+ */
+ const char *func_name() const { return "get_user_var"; }
+ bool const_item() const;
+ table_map used_tables() const
+ { return const_item() ? 0 : RAND_TABLE_BIT; }
+ bool eq(const Item *item, bool binary_cmp) const;
+private:
+ bool set_value(THD *thd, sp_rcontext *ctx, Item **it);
+
+public:
+ Settable_routine_parameter *get_settable_routine_parameter()
+ {
+ return this;
+ }
+ bool check_vcol_func_processor(uchar *int_arg) { return TRUE;}
+};
+
+
+/*
+ This item represents user variable used as out parameter (e.g in LOAD DATA),
+ and it is supposed to be used only for this purprose. So it is simplified
+ a lot. Actually you should never obtain its value.
+
+ The only two reasons for this thing being an Item is possibility to store it
+ in List<Item> and desire to place this code somewhere near other functions
+ working with user variables.
+*/
+class Item_user_var_as_out_param :public Item
+{
+ LEX_STRING name;
+ user_var_entry *entry;
+public:
+ Item_user_var_as_out_param(LEX_STRING a) : name(a)
+ { set_name(a.str, 0, system_charset_info); }
+ /* We should return something different from FIELD_ITEM here */
+ enum Type type() const { return STRING_ITEM;}
+ double val_real();
+ longlong val_int();
+ String *val_str(String *str);
+ my_decimal *val_decimal(my_decimal *decimal_buffer);
+ /* fix_fields() binds variable name with its entry structure */
+ bool fix_fields(THD *thd, Item **ref);
+ void print_for_load(THD *thd, String *str);
+ void set_null_value(CHARSET_INFO* cs);
+ void set_value(const char *str, uint length, CHARSET_INFO* cs);
+};
+
+
+/* A system variable */
+
+#define GET_SYS_VAR_CACHE_LONG 1
+#define GET_SYS_VAR_CACHE_DOUBLE 2
+#define GET_SYS_VAR_CACHE_STRING 4
+
+class Item_func_get_system_var :public Item_func
+{
+ sys_var *var;
+ enum_var_type var_type, orig_var_type;
+ LEX_STRING component;
+ longlong cached_llval;
+ double cached_dval;
+ String cached_strval;
+ bool cached_null_value;
+ query_id_t used_query_id;
+ uchar cache_present;
+
+public:
+ Item_func_get_system_var(sys_var *var_arg, enum_var_type var_type_arg,
+ LEX_STRING *component_arg, const char *name_arg,
+ size_t name_len_arg);
+ enum Functype functype() const { return GSYSVAR_FUNC; }
+ void update_null_value();
+ void fix_length_and_dec();
+ void print(String *str, enum_query_type query_type);
+ bool const_item() const { return true; }
+ table_map used_tables() const { return 0; }
+ enum Item_result result_type() const;
+ enum_field_types field_type() const;
+ double val_real();
+ longlong val_int();
+ String* val_str(String*);
+ my_decimal *val_decimal(my_decimal *dec_buf)
+ { return val_decimal_from_real(dec_buf); }
+ /* TODO: fix to support views */
+ const char *func_name() const { return "get_system_var"; }
+ /**
+ Indicates whether this system variable is written to the binlog or not.
+
+ Variables are written to the binlog as part of "status_vars" in
+ Query_log_event, as an Intvar_log_event, or a Rand_log_event.
+
+ @return true if the variable is written to the binlog, false otherwise.
+ */
+ bool is_written_to_binlog();
+ bool eq(const Item *item, bool binary_cmp) const;
+
+ void cleanup();
+ bool check_vcol_func_processor(uchar *int_arg) { return TRUE;}
+};
+
+
+/* for fulltext search */
+
+class Item_func_match :public Item_real_func
+{
+public:
+ uint key, flags;
+ bool join_key;
+ DTCollation cmp_collation;
+ FT_INFO *ft_handler;
+ TABLE *table;
+ Item_func_match *master; // for master-slave optimization
+ Item *concat_ws; // Item_func_concat_ws
+ String value; // value of concat_ws
+ String search_value; // key_item()'s value converted to cmp_collation
+
+ Item_func_match(List<Item> &a, uint b): Item_real_func(a), key(0), flags(b),
+ join_key(0), ft_handler(0), table(0), master(0), concat_ws(0) { }
+ void cleanup()
+ {
+ DBUG_ENTER("Item_func_match::cleanup");
+ Item_real_func::cleanup();
+ if (!master && ft_handler)
+ ft_handler->please->close_search(ft_handler);
+ ft_handler= 0;
+ concat_ws= 0;
+ table= 0; // required by Item_func_match::eq()
+ DBUG_VOID_RETURN;
+ }
+ bool is_expensive_processor(uchar *arg) { return TRUE; }
+ enum Functype functype() const { return FT_FUNC; }
+ const char *func_name() const { return "match"; }
+ table_map not_null_tables() const { return 0; }
+ bool fix_fields(THD *thd, Item **ref);
+ bool eq(const Item *, bool binary_cmp) const;
+ /* The following should be safe, even if we compare doubles */
+ longlong val_int() { DBUG_ASSERT(fixed == 1); return val_real() != 0.0; }
+ double val_real();
+ virtual void print(String *str, enum_query_type query_type);
+
+ bool fix_index();
+ void init_search(bool no_order);
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ /* TODO: consider adding in support for the MATCH-based virtual columns */
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+private:
+ /**
+ Check whether storage engine for given table,
+ allows FTS Boolean search on non-indexed columns.
+
+ @todo A flag should be added to the extended fulltext API so that
+ it may be checked whether search on non-indexed columns are
+ supported. Currently, it is not possible to check for such a
+ flag since @c this->ft_handler is not yet set when this function is
+ called. The current hack is to assume that search on non-indexed
+ columns are supported for engines that does not support the extended
+ fulltext API (e.g., MyISAM), while it is not supported for other
+ engines (e.g., InnoDB)
+
+ @param table_arg Table for which storage engine to check
+
+ @retval true if BOOLEAN search on non-indexed columns is supported
+ @retval false otherwise
+ */
+ bool allows_search_on_non_indexed_columns(TABLE* table_arg)
+ {
+ // Only Boolean search may support non_indexed columns
+ if (!(flags & FT_BOOL))
+ return false;
+
+ DBUG_ASSERT(table_arg && table_arg->file);
+
+ // Assume that if extended fulltext API is not supported,
+ // non-indexed columns are allowed. This will be true for MyISAM.
+ if ((table_arg->file->ha_table_flags() & HA_CAN_FULLTEXT_EXT) == 0)
+ return true;
+
+ return false;
+ }
+
+};
+
+
+class Item_func_bit_xor : public Item_func_bit
+{
+public:
+ Item_func_bit_xor(Item *a, Item *b) :Item_func_bit(a, b) {}
+ longlong val_int();
+ const char *func_name() const { return "^"; }
+};
+
+class Item_func_is_free_lock :public Item_int_func
+{
+ String value;
+public:
+ Item_func_is_free_lock(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "is_free_lock"; }
+ void fix_length_and_dec() { decimals=0; max_length=1; maybe_null=1;}
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+class Item_func_is_used_lock :public Item_int_func
+{
+ String value;
+public:
+ Item_func_is_used_lock(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "is_used_lock"; }
+ void fix_length_and_dec() { decimals=0; max_length=10; maybe_null=1;}
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+/* For type casts */
+
+enum Cast_target
+{
+ ITEM_CAST_BINARY, ITEM_CAST_SIGNED_INT, ITEM_CAST_UNSIGNED_INT,
+ ITEM_CAST_DATE, ITEM_CAST_TIME, ITEM_CAST_DATETIME, ITEM_CAST_CHAR,
+ ITEM_CAST_DECIMAL, ITEM_CAST_DOUBLE
+};
+
+
+class Item_func_row_count :public Item_int_func
+{
+public:
+ Item_func_row_count() :Item_int_func() {}
+ longlong val_int();
+ const char *func_name() const { return "row_count"; }
+ void fix_length_and_dec() { decimals= 0; maybe_null=0; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+/*
+ *
+ * Stored FUNCTIONs
+ *
+ */
+
+class sp_head;
+class sp_name;
+struct st_sp_security_context;
+
+class Item_func_sp :public Item_func
+{
+private:
+ Name_resolution_context *context;
+ sp_name *m_name;
+ mutable sp_head *m_sp;
+ TABLE *dummy_table;
+ uchar result_buf[64];
+ /*
+ The result field of the concrete stored function.
+ */
+ Field *sp_result_field;
+
+ bool execute();
+ bool execute_impl(THD *thd);
+ bool init_result_field(THD *thd);
+
+protected:
+ bool is_expensive_processor(uchar *arg)
+ { return is_expensive(); }
+
+public:
+
+ Item_func_sp(Name_resolution_context *context_arg, sp_name *name);
+
+ Item_func_sp(Name_resolution_context *context_arg,
+ sp_name *name, List<Item> &list);
+
+ virtual ~Item_func_sp()
+ {}
+
+ void update_used_tables();
+
+ void cleanup();
+
+ const char *func_name() const;
+
+ enum enum_field_types field_type() const;
+
+ Field *tmp_table_field(TABLE *t_arg);
+
+ void make_field(Send_field *tmp_field);
+
+ Item_result result_type() const;
+
+ longlong val_int()
+ {
+ if (execute())
+ return (longlong) 0;
+ return sp_result_field->val_int();
+ }
+
+ double val_real()
+ {
+ if (execute())
+ return 0.0;
+ return sp_result_field->val_real();
+ }
+
+ my_decimal *val_decimal(my_decimal *dec_buf)
+ {
+ if (execute())
+ return NULL;
+ return sp_result_field->val_decimal(dec_buf);
+ }
+
+ String *val_str(String *str)
+ {
+ String buf;
+ char buff[20];
+ buf.set(buff, 20, str->charset());
+ buf.length(0);
+ if (execute())
+ return NULL;
+ /*
+ result_field will set buf pointing to internal buffer
+ of the resul_field. Due to this it will change any time
+ when SP is executed. In order to prevent occasional
+ corruption of returned value, we make here a copy.
+ */
+ sp_result_field->val_str(&buf);
+ str->copy(buf);
+ return str;
+ }
+
+ void update_null_value()
+ {
+ execute();
+ }
+
+ virtual bool change_context_processor(uchar *cntx)
+ { context= (Name_resolution_context *)cntx; return FALSE; }
+
+ bool sp_check_access(THD * thd);
+ virtual enum Functype functype() const { return FUNC_SP; }
+
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec(void);
+ bool is_expensive();
+
+ inline Field *get_sp_result_field()
+ {
+ return sp_result_field;
+ }
+
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+ bool limit_index_condition_pushdown_processor(uchar *opt_arg)
+ {
+ return TRUE;
+ }
+};
+
+
+class Item_func_found_rows :public Item_int_func
+{
+public:
+ Item_func_found_rows() :Item_int_func() {}
+ longlong val_int();
+ const char *func_name() const { return "found_rows"; }
+ void fix_length_and_dec() { decimals= 0; maybe_null=0; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+void uuid_short_init();
+
+class Item_func_uuid_short :public Item_int_func
+{
+public:
+ Item_func_uuid_short() :Item_int_func() {}
+ const char *func_name() const { return "uuid_short"; }
+ longlong val_int();
+ void fix_length_and_dec()
+ { max_length= 21; unsigned_flag=1; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_last_value :public Item_func
+{
+protected:
+ Item *last_value;
+public:
+ Item_func_last_value(List<Item> &list) :Item_func(list) {}
+ double val_real();
+ longlong val_int();
+ String *val_str(String *);
+ my_decimal *val_decimal(my_decimal *);
+ void fix_length_and_dec();
+ enum Item_result result_type () const { return last_value->result_type(); }
+ const char *func_name() const { return "last_value"; }
+ table_map not_null_tables() const { return 0; }
+ enum_field_types field_type() const { return last_value->field_type(); }
+ bool const_item() const { return 0; }
+ void evaluate_sideeffects();
+ void update_used_tables()
+ {
+ Item_func::update_used_tables();
+ maybe_null= last_value->maybe_null;
+ }
+};
+
+
+Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name,
+ LEX_STRING component);
+extern bool check_reserved_words(LEX_STRING *name);
+extern enum_field_types agg_field_type(Item **items, uint nitems);
+Item *find_date_time_item(Item **args, uint nargs, uint col);
+double my_double_round(double value, longlong dec, bool dec_unsigned,
+ bool truncate);
+bool eval_const_cond(COND *cond);
+
+extern bool volatile mqh_used;
+
+#endif /* ITEM_FUNC_INCLUDED */
diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc
new file mode 100644
index 00000000000..124c9ce6b72
--- /dev/null
+++ b/sql/item_geofunc.cc
@@ -0,0 +1,1759 @@
+/*
+ Copyright (c) 2003-2007 MySQL AB, 2009, 2010 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+
+/**
+ @file
+
+ @brief
+ This file defines all spatial functions
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // THD, set_var.h: THD
+#include "set_var.h"
+#ifdef HAVE_SPATIAL
+#include <m_ctype.h>
+
+
+Field *Item_geometry_func::tmp_table_field(TABLE *t_arg)
+{
+ Field *result;
+ if ((result= new Field_geom(max_length, maybe_null, name, t_arg->s,
+ get_geometry_type())))
+ result->init(t_arg);
+ return result;
+}
+
+void Item_geometry_func::fix_length_and_dec()
+{
+ collation.set(&my_charset_bin);
+ decimals=0;
+ max_length= (uint32) 4294967295U;
+ maybe_null= 1;
+}
+
+
+String *Item_func_geometry_from_text::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ Geometry_buffer buffer;
+ String arg_val;
+ String *wkt= args[0]->val_str_ascii(&arg_val);
+
+ if ((null_value= args[0]->null_value))
+ return 0;
+
+ Gis_read_stream trs(wkt->charset(), wkt->ptr(), wkt->length());
+ uint32 srid= 0;
+
+ if ((arg_count == 2) && !args[1]->null_value)
+ srid= (uint32)args[1]->val_int();
+
+ str->set_charset(&my_charset_bin);
+ if (str->reserve(SRID_SIZE, 512))
+ return 0;
+ str->length(0);
+ str->q_append(srid);
+ if ((null_value= !Geometry::create_from_wkt(&buffer, &trs, str, 0)))
+ return 0;
+ return str;
+}
+
+
+String *Item_func_geometry_from_wkb::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_val;
+ String *wkb;
+ Geometry_buffer buffer;
+ uint32 srid= 0;
+
+ if (args[0]->field_type() == MYSQL_TYPE_GEOMETRY)
+ {
+ String *str_ret= args[0]->val_str(str);
+ null_value= args[0]->null_value;
+ return str_ret;
+ }
+
+ wkb= args[0]->val_str(&arg_val);
+
+ if ((arg_count == 2) && !args[1]->null_value)
+ srid= (uint32)args[1]->val_int();
+
+ str->set_charset(&my_charset_bin);
+ if (str->reserve(SRID_SIZE, 512))
+ {
+ null_value= TRUE; /* purecov: inspected */
+ return 0; /* purecov: inspected */
+ }
+ str->length(0);
+ str->q_append(srid);
+ if ((null_value=
+ (args[0]->null_value ||
+ !Geometry::create_from_wkb(&buffer, wkb->ptr(), wkb->length(), str))))
+ return 0;
+ return str;
+}
+
+
+String *Item_func_as_wkt::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_val;
+ String *swkb= args[0]->val_str(&arg_val);
+ Geometry_buffer buffer;
+ Geometry *geom= NULL;
+ const char *dummy;
+
+ if ((null_value=
+ (args[0]->null_value ||
+ !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())))))
+ return 0;
+
+ str->length(0);
+ str->set_charset(&my_charset_latin1);
+ if ((null_value= geom->as_wkt(str, &dummy)))
+ return 0;
+
+ return str;
+}
+
+
+void Item_func_as_wkt::fix_length_and_dec()
+{
+ collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII);
+ max_length=MAX_BLOB_WIDTH;
+ maybe_null= 1;
+}
+
+
+String *Item_func_as_wkb::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_val;
+ String *swkb= args[0]->val_str(&arg_val);
+ Geometry_buffer buffer;
+
+ if ((null_value=
+ (args[0]->null_value ||
+ !(Geometry::construct(&buffer, swkb->ptr(), swkb->length())))))
+ return 0;
+
+ str->copy(swkb->ptr() + SRID_SIZE, swkb->length() - SRID_SIZE,
+ &my_charset_bin);
+ return str;
+}
+
+
+String *Item_func_geometry_type::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *swkb= args[0]->val_str(str);
+ Geometry_buffer buffer;
+ Geometry *geom= NULL;
+
+ if ((null_value=
+ (args[0]->null_value ||
+ !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())))))
+ return 0;
+ /* String will not move */
+ str->copy(geom->get_class_info()->m_name.str,
+ geom->get_class_info()->m_name.length,
+ &my_charset_latin1);
+ return str;
+}
+
+
+Field::geometry_type Item_func_envelope::get_geometry_type() const
+{
+ return Field::GEOM_POLYGON;
+}
+
+
+String *Item_func_envelope::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_val;
+ String *swkb= args[0]->val_str(&arg_val);
+ Geometry_buffer buffer;
+ Geometry *geom= NULL;
+ uint32 srid;
+
+ if ((null_value=
+ args[0]->null_value ||
+ !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length()))))
+ return 0;
+
+ srid= uint4korr(swkb->ptr());
+ str->set_charset(&my_charset_bin);
+ str->length(0);
+ if (str->reserve(SRID_SIZE, 512))
+ return 0;
+ str->q_append(srid);
+ return (null_value= geom->envelope(str)) ? 0 : str;
+}
+
+
+Field::geometry_type Item_func_centroid::get_geometry_type() const
+{
+ return Field::GEOM_POINT;
+}
+
+
+String *Item_func_centroid::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_val;
+ String *swkb= args[0]->val_str(&arg_val);
+ Geometry_buffer buffer;
+ Geometry *geom= NULL;
+ uint32 srid;
+
+ if ((null_value= args[0]->null_value ||
+ !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length()))))
+ return 0;
+
+ str->set_charset(&my_charset_bin);
+ if (str->reserve(SRID_SIZE, 512))
+ return 0;
+ str->length(0);
+ srid= uint4korr(swkb->ptr());
+ str->q_append(srid);
+
+ return (null_value= MY_TEST(geom->centroid(str))) ? 0 : str;
+}
+
+
+/*
+ Spatial decomposition functions
+*/
+
+String *Item_func_spatial_decomp::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_val;
+ String *swkb= args[0]->val_str(&arg_val);
+ Geometry_buffer buffer;
+ Geometry *geom= NULL;
+ uint32 srid;
+
+ if ((null_value=
+ (args[0]->null_value ||
+ !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())))))
+ return 0;
+
+ srid= uint4korr(swkb->ptr());
+ str->set_charset(&my_charset_bin);
+ if (str->reserve(SRID_SIZE, 512))
+ goto err;
+ str->length(0);
+ str->q_append(srid);
+ switch (decomp_func) {
+ case SP_STARTPOINT:
+ if (geom->start_point(str))
+ goto err;
+ break;
+
+ case SP_ENDPOINT:
+ if (geom->end_point(str))
+ goto err;
+ break;
+
+ case SP_EXTERIORRING:
+ if (geom->exterior_ring(str))
+ goto err;
+ break;
+
+ default:
+ goto err;
+ }
+ return str;
+
+err:
+ null_value= 1;
+ return 0;
+}
+
+
+String *Item_func_spatial_decomp_n::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_val;
+ String *swkb= args[0]->val_str(&arg_val);
+ long n= (long) args[1]->val_int();
+ Geometry_buffer buffer;
+ Geometry *geom= NULL;
+ uint32 srid;
+
+ if ((null_value=
+ (args[0]->null_value || args[1]->null_value ||
+ !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())))))
+ return 0;
+
+ str->set_charset(&my_charset_bin);
+ if (str->reserve(SRID_SIZE, 512))
+ goto err;
+ srid= uint4korr(swkb->ptr());
+ str->length(0);
+ str->q_append(srid);
+ switch (decomp_func_n)
+ {
+ case SP_POINTN:
+ if (geom->point_n(n,str))
+ goto err;
+ break;
+
+ case SP_GEOMETRYN:
+ if (geom->geometry_n(n,str))
+ goto err;
+ break;
+
+ case SP_INTERIORRINGN:
+ if (geom->interior_ring_n(n,str))
+ goto err;
+ break;
+
+ default:
+ goto err;
+ }
+ return str;
+
+err:
+ null_value=1;
+ return 0;
+}
+
+
+/*
+ Functions to concatenate various spatial objects
+*/
+
+
+/*
+* Concatenate doubles into Point
+*/
+
+
+Field::geometry_type Item_func_point::get_geometry_type() const
+{
+ return Field::GEOM_POINT;
+}
+
+
+String *Item_func_point::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ double x= args[0]->val_real();
+ double y= args[1]->val_real();
+ uint32 srid= 0;
+
+ if ((null_value= (args[0]->null_value ||
+ args[1]->null_value ||
+ str->realloc(4/*SRID*/ + 1 + 4 + SIZEOF_STORED_DOUBLE * 2))))
+ return 0;
+
+ str->set_charset(&my_charset_bin);
+ str->length(0);
+ str->q_append(srid);
+ str->q_append((char)Geometry::wkb_ndr);
+ str->q_append((uint32)Geometry::wkb_point);
+ str->q_append(x);
+ str->q_append(y);
+ return str;
+}
+
+
+/**
+ Concatenates various items into various collections
+ with checkings for valid wkb type of items.
+ For example, MultiPoint can be a collection of Points only.
+ coll_type contains wkb type of target collection.
+ item_type contains a valid wkb type of items.
+ In the case when coll_type is wkbGeometryCollection,
+ we do not check wkb type of items, any is valid.
+*/
+
+String *Item_func_spatial_collection::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String arg_value;
+ uint i;
+ uint32 srid= 0;
+
+ str->set_charset(&my_charset_bin);
+ str->length(0);
+ if (str->reserve(4/*SRID*/ + 1 + 4 + 4, 512))
+ goto err;
+
+ str->q_append(srid);
+ str->q_append((char) Geometry::wkb_ndr);
+ str->q_append((uint32) coll_type);
+ str->q_append((uint32) arg_count);
+
+ for (i= 0; i < arg_count; ++i)
+ {
+ String *res= args[i]->val_str(&arg_value);
+ uint32 len;
+ if (args[i]->null_value || ((len= res->length()) < WKB_HEADER_SIZE))
+ goto err;
+
+ if (coll_type == Geometry::wkb_geometrycollection)
+ {
+ /*
+ In the case of GeometryCollection we don't need any checkings
+ for item types, so just copy them into target collection
+ */
+ if (str->append(res->ptr() + 4/*SRID*/, len - 4/*SRID*/, (uint32) 512))
+ goto err;
+ }
+ else
+ {
+ enum Geometry::wkbType wkb_type;
+ const uint data_offset= 4/*SRID*/ + 1;
+ if (res->length() < data_offset + sizeof(uint32))
+ goto err;
+ const char *data= res->ptr() + data_offset;
+
+ /*
+ In the case of named collection we must check that items
+ are of specific type, let's do this checking now
+ */
+
+ wkb_type= (Geometry::wkbType) uint4korr(data);
+ data+= 4;
+ len-= 5 + 4/*SRID*/;
+ if (wkb_type != item_type)
+ goto err;
+
+ switch (coll_type) {
+ case Geometry::wkb_multipoint:
+ case Geometry::wkb_multilinestring:
+ case Geometry::wkb_multipolygon:
+ if (len < WKB_HEADER_SIZE ||
+ str->append(data-WKB_HEADER_SIZE, len+WKB_HEADER_SIZE, 512))
+ goto err;
+ break;
+
+ case Geometry::wkb_linestring:
+ if (len < POINT_DATA_SIZE || str->append(data, POINT_DATA_SIZE, 512))
+ goto err;
+ break;
+ case Geometry::wkb_polygon:
+ {
+ uint32 n_points;
+ double x1, y1, x2, y2;
+ const char *org_data= data;
+
+ if (len < 4)
+ goto err;
+
+ n_points= uint4korr(data);
+ data+= 4;
+
+ if (n_points < 2 || len < 4 + n_points * POINT_DATA_SIZE)
+ goto err;
+
+ float8get(x1, data);
+ data+= SIZEOF_STORED_DOUBLE;
+ float8get(y1, data);
+ data+= SIZEOF_STORED_DOUBLE;
+
+ data+= (n_points - 2) * POINT_DATA_SIZE;
+
+ float8get(x2, data);
+ float8get(y2, data + SIZEOF_STORED_DOUBLE);
+
+ if ((x1 != x2) || (y1 != y2) ||
+ str->append(org_data, len, 512))
+ goto err;
+ }
+ break;
+
+ default:
+ goto err;
+ }
+ }
+ }
+ if (str->length() > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(), current_thd->variables.max_allowed_packet);
+ goto err;
+ }
+
+ null_value = 0;
+ return str;
+
+err:
+ null_value= 1;
+ return 0;
+}
+
+
+/*
+ Functions for spatial relations
+*/
+
+const char *Item_func_spatial_mbr_rel::func_name() const
+{
+ switch (spatial_rel) {
+ case SP_CONTAINS_FUNC:
+ return "mbrcontains";
+ case SP_WITHIN_FUNC:
+ return "mbrwithin";
+ case SP_EQUALS_FUNC:
+ return "mbrequals";
+ case SP_DISJOINT_FUNC:
+ return "mbrdisjoint";
+ case SP_INTERSECTS_FUNC:
+ return "mbrintersects";
+ case SP_TOUCHES_FUNC:
+ return "mbrtouches";
+ case SP_CROSSES_FUNC:
+ return "mbrcrosses";
+ case SP_OVERLAPS_FUNC:
+ return "mbroverlaps";
+ default:
+ DBUG_ASSERT(0); // Should never happened
+ return "mbrsp_unknown";
+ }
+}
+
+
+longlong Item_func_spatial_mbr_rel::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res1= args[0]->val_str(&cmp.value1);
+ String *res2= args[1]->val_str(&cmp.value2);
+ Geometry_buffer buffer1, buffer2;
+ Geometry *g1, *g2;
+ MBR mbr1, mbr2;
+ const char *dummy;
+
+ if ((null_value=
+ (args[0]->null_value ||
+ args[1]->null_value ||
+ !(g1= Geometry::construct(&buffer1, res1->ptr(), res1->length())) ||
+ !(g2= Geometry::construct(&buffer2, res2->ptr(), res2->length())) ||
+ g1->get_mbr(&mbr1, &dummy) || !mbr1.valid() ||
+ g2->get_mbr(&mbr2, &dummy) || !mbr2.valid())))
+ return 0;
+
+ switch (spatial_rel) {
+ case SP_CONTAINS_FUNC:
+ return mbr1.contains(&mbr2);
+ case SP_WITHIN_FUNC:
+ return mbr1.within(&mbr2);
+ case SP_EQUALS_FUNC:
+ return mbr1.equals(&mbr2);
+ case SP_DISJOINT_FUNC:
+ return mbr1.disjoint(&mbr2);
+ case SP_INTERSECTS_FUNC:
+ return mbr1.intersects(&mbr2);
+ case SP_TOUCHES_FUNC:
+ return mbr1.touches(&mbr2);
+ case SP_OVERLAPS_FUNC:
+ return mbr1.overlaps(&mbr2);
+ case SP_CROSSES_FUNC:
+ return 0;
+ default:
+ break;
+ }
+
+ null_value=1;
+ return 0;
+}
+
+
+Item_func_spatial_rel::Item_func_spatial_rel(Item *a,Item *b,
+ enum Functype sp_rel) :
+ Item_bool_func2(a,b), collector()
+{
+ spatial_rel = sp_rel;
+}
+
+
+Item_func_spatial_rel::~Item_func_spatial_rel()
+{
+}
+
+
+const char *Item_func_spatial_rel::func_name() const
+{
+ switch (spatial_rel) {
+ case SP_CONTAINS_FUNC:
+ return "st_contains";
+ case SP_WITHIN_FUNC:
+ return "st_within";
+ case SP_EQUALS_FUNC:
+ return "st_equals";
+ case SP_DISJOINT_FUNC:
+ return "st_disjoint";
+ case SP_INTERSECTS_FUNC:
+ return "st_intersects";
+ case SP_TOUCHES_FUNC:
+ return "st_touches";
+ case SP_CROSSES_FUNC:
+ return "st_crosses";
+ case SP_OVERLAPS_FUNC:
+ return "st_overlaps";
+ default:
+ DBUG_ASSERT(0); // Should never happened
+ return "sp_unknown";
+ }
+}
+
+
+static double count_edge_t(const Gcalc_heap::Info *ea,
+ const Gcalc_heap::Info *eb,
+ const Gcalc_heap::Info *v,
+ double &ex, double &ey, double &vx, double &vy,
+ double &e_sqrlen)
+{
+ ex= eb->x - ea->x;
+ ey= eb->y - ea->y;
+ vx= v->x - ea->x;
+ vy= v->y - ea->y;
+ e_sqrlen= ex * ex + ey * ey;
+ return (ex * vx + ey * vy) / e_sqrlen;
+}
+
+
+static double distance_to_line(double ex, double ey, double vx, double vy,
+ double e_sqrlen)
+{
+ return fabs(vx * ey - vy * ex) / sqrt(e_sqrlen);
+}
+
+
+static double distance_points(const Gcalc_heap::Info *a,
+ const Gcalc_heap::Info *b)
+{
+ double x= a->x - b->x;
+ double y= a->y - b->y;
+ return sqrt(x * x + y * y);
+}
+
+
+#define GIS_ZERO 0.00000000001
+
+longlong Item_func_spatial_rel::val_int()
+{
+ DBUG_ENTER("Item_func_spatial_rel::val_int");
+ DBUG_ASSERT(fixed == 1);
+ String *res1;
+ String *res2;
+ Geometry_buffer buffer1, buffer2;
+ Geometry *g1, *g2;
+ int result= 0;
+ int mask= 0;
+ uint shape_a, shape_b;
+ MBR umbr, mbr1, mbr2;
+ const char *c_end;
+
+ res1= args[0]->val_str(&tmp_value1);
+ res2= args[1]->val_str(&tmp_value2);
+ Gcalc_operation_transporter trn(&func, &collector);
+
+ if (func.reserve_op_buffer(1))
+ DBUG_RETURN(0);
+
+ if ((null_value=
+ (args[0]->null_value || args[1]->null_value ||
+ !(g1= Geometry::construct(&buffer1, res1->ptr(), res1->length())) ||
+ !(g2= Geometry::construct(&buffer2, res2->ptr(), res2->length())) ||
+ g1->get_mbr(&mbr1, &c_end) || !mbr1.valid() ||
+ g2->get_mbr(&mbr2, &c_end) || !mbr2.valid())))
+ goto exit;
+
+ umbr= mbr1;
+ umbr.add_mbr(&mbr2);
+ collector.set_extent(umbr.xmin, umbr.xmax, umbr.ymin, umbr.ymax);
+
+ mbr1.buffer(1e-5);
+
+ switch (spatial_rel) {
+ case SP_CONTAINS_FUNC:
+ if (!mbr1.contains(&mbr2))
+ goto exit;
+ mask= 1;
+ func.add_operation(Gcalc_function::op_difference, 2);
+ /* Mind the g2 goes first. */
+ null_value= g2->store_shapes(&trn) || g1->store_shapes(&trn);
+ break;
+ case SP_WITHIN_FUNC:
+ mbr2.buffer(2e-5);
+ if (!mbr1.within(&mbr2))
+ goto exit;
+ mask= 1;
+ func.add_operation(Gcalc_function::op_difference, 2);
+ null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn);
+ break;
+ case SP_EQUALS_FUNC:
+ if (!mbr1.contains(&mbr2))
+ goto exit;
+ mask= 1;
+ func.add_operation(Gcalc_function::op_symdifference, 2);
+ null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn);
+ break;
+ case SP_DISJOINT_FUNC:
+ mask= 1;
+ func.add_operation(Gcalc_function::op_intersection, 2);
+ null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn);
+ break;
+ case SP_INTERSECTS_FUNC:
+ if (!mbr1.intersects(&mbr2))
+ goto exit;
+ func.add_operation(Gcalc_function::op_intersection, 2);
+ null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn);
+ break;
+ case SP_OVERLAPS_FUNC:
+ case SP_CROSSES_FUNC:
+ func.add_operation(Gcalc_function::op_intersection, 2);
+ func.add_operation(Gcalc_function::v_find_t |
+ Gcalc_function::op_intersection, 2);
+ shape_a= func.get_next_expression_pos();
+ if ((null_value= g1->store_shapes(&trn)))
+ break;
+ shape_b= func.get_next_expression_pos();
+ if ((null_value= g2->store_shapes(&trn)))
+ break;
+ func.add_operation(Gcalc_function::v_find_t |
+ Gcalc_function::op_intersection, 2);
+ func.add_operation(Gcalc_function::v_find_t |
+ Gcalc_function::op_difference, 2);
+ func.repeat_expression(shape_a);
+ func.repeat_expression(shape_b);
+ func.add_operation(Gcalc_function::v_find_t |
+ Gcalc_function::op_difference, 2);
+ func.repeat_expression(shape_b);
+ func.repeat_expression(shape_a);
+ break;
+ case SP_TOUCHES_FUNC:
+ func.add_operation(Gcalc_function::op_intersection, 2);
+ func.add_operation(Gcalc_function::v_find_f |
+ Gcalc_function::op_not |
+ Gcalc_function::op_intersection, 2);
+ func.add_operation(Gcalc_function::op_internals, 1);
+ shape_a= func.get_next_expression_pos();
+ if ((null_value= g1->store_shapes(&trn)))
+ break;
+ func.add_operation(Gcalc_function::op_internals, 1);
+ shape_b= func.get_next_expression_pos();
+ if ((null_value= g2->store_shapes(&trn)))
+ break;
+ func.add_operation(Gcalc_function::v_find_t |
+ Gcalc_function::op_intersection, 2);
+ func.add_operation(Gcalc_function::op_border, 1);
+ func.repeat_expression(shape_a);
+ func.add_operation(Gcalc_function::op_border, 1);
+ func.repeat_expression(shape_b);
+ break;
+ default:
+ DBUG_ASSERT(FALSE);
+ break;
+ }
+
+ if (null_value)
+ goto exit;
+
+ collector.prepare_operation();
+ scan_it.init(&collector);
+ scan_it.killed= (int *) &(current_thd->killed);
+
+ if (func.alloc_states())
+ goto exit;
+
+ result= func.check_function(scan_it) ^ mask;
+
+exit:
+ collector.reset();
+ func.reset();
+ scan_it.reset();
+ DBUG_RETURN(result);
+}
+
+
+Item_func_spatial_operation::~Item_func_spatial_operation()
+{
+}
+
+
+String *Item_func_spatial_operation::val_str(String *str_value)
+{
+ DBUG_ENTER("Item_func_spatial_operation::val_str");
+ DBUG_ASSERT(fixed == 1);
+ String *res1= args[0]->val_str(&tmp_value1);
+ String *res2= args[1]->val_str(&tmp_value2);
+ Geometry_buffer buffer1, buffer2;
+ Geometry *g1, *g2;
+ uint32 srid= 0;
+ Gcalc_operation_transporter trn(&func, &collector);
+ MBR mbr1, mbr2;
+ const char *c_end;
+
+ if (func.reserve_op_buffer(1))
+ DBUG_RETURN(0);
+ func.add_operation(spatial_op, 2);
+
+ if ((null_value=
+ (args[0]->null_value || args[1]->null_value ||
+ !(g1= Geometry::construct(&buffer1, res1->ptr(), res1->length())) ||
+ !(g2= Geometry::construct(&buffer2, res2->ptr(), res2->length())) ||
+ g1->get_mbr(&mbr1, &c_end) || !mbr1.valid() ||
+ g2->get_mbr(&mbr2, &c_end) || !mbr2.valid())))
+ {
+ str_value= 0;
+ goto exit;
+ }
+
+ mbr1.add_mbr(&mbr2);
+ collector.set_extent(mbr1.xmin, mbr1.xmax, mbr1.ymin, mbr1.ymax);
+
+ if ((null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn)))
+ {
+ str_value= 0;
+ goto exit;
+ }
+
+ collector.prepare_operation();
+ if (func.alloc_states())
+ goto exit;
+
+ operation.init(&func);
+
+ if (operation.count_all(&collector) ||
+ operation.get_result(&res_receiver))
+ goto exit;
+
+
+ str_value->set_charset(&my_charset_bin);
+ if (str_value->reserve(SRID_SIZE, 512))
+ goto exit;
+ str_value->length(0);
+ str_value->q_append(srid);
+
+ if (Geometry::create_from_opresult(&buffer1, str_value, res_receiver))
+ goto exit;
+
+exit:
+ collector.reset();
+ func.reset();
+ res_receiver.reset();
+ DBUG_RETURN(str_value);
+}
+
+
+const char *Item_func_spatial_operation::func_name() const
+{
+ switch (spatial_op) {
+ case Gcalc_function::op_intersection:
+ return "st_intersection";
+ case Gcalc_function::op_difference:
+ return "st_difference";
+ case Gcalc_function::op_union:
+ return "st_union";
+ case Gcalc_function::op_symdifference:
+ return "st_symdifference";
+ default:
+ DBUG_ASSERT(0); // Should never happen
+ return "sp_unknown";
+ }
+}
+
+
+static const int SINUSES_CALCULATED= 32;
+static double n_sinus[SINUSES_CALCULATED+1]=
+{
+ 0,
+ 0.04906767432741802,
+ 0.0980171403295606,
+ 0.1467304744553618,
+ 0.1950903220161283,
+ 0.2429801799032639,
+ 0.2902846772544623,
+ 0.3368898533922201,
+ 0.3826834323650898,
+ 0.4275550934302821,
+ 0.4713967368259976,
+ 0.5141027441932217,
+ 0.5555702330196022,
+ 0.5956993044924334,
+ 0.6343932841636455,
+ 0.6715589548470183,
+ 0.7071067811865475,
+ 0.7409511253549591,
+ 0.773010453362737,
+ 0.8032075314806448,
+ 0.8314696123025452,
+ 0.8577286100002721,
+ 0.8819212643483549,
+ 0.9039892931234433,
+ 0.9238795325112867,
+ 0.9415440651830208,
+ 0.9569403357322089,
+ 0.970031253194544,
+ 0.9807852804032304,
+ 0.989176509964781,
+ 0.9951847266721968,
+ 0.9987954562051724,
+ 1
+};
+
+
+static void get_n_sincos(int n, double *sinus, double *cosinus)
+{
+ DBUG_ASSERT(n > 0 && n < SINUSES_CALCULATED*2+1);
+ if (n < (SINUSES_CALCULATED + 1))
+ {
+ *sinus= n_sinus[n];
+ *cosinus= n_sinus[SINUSES_CALCULATED - n];
+ }
+ else
+ {
+ n-= SINUSES_CALCULATED;
+ *sinus= n_sinus[SINUSES_CALCULATED - n];
+ *cosinus= -n_sinus[n];
+ }
+}
+
+
+static int fill_half_circle(Gcalc_shape_transporter *trn, double x, double y,
+ double ax, double ay)
+{
+ double n_sin, n_cos;
+ double x_n, y_n;
+ for (int n = 1; n < (SINUSES_CALCULATED * 2 - 1); n++)
+ {
+ get_n_sincos(n, &n_sin, &n_cos);
+ x_n= ax * n_cos - ay * n_sin;
+ y_n= ax * n_sin + ay * n_cos;
+ if (trn->add_point(x_n + x, y_n + y))
+ return 1;
+ }
+ return 0;
+}
+
+
+static int fill_gap(Gcalc_shape_transporter *trn,
+ double x, double y,
+ double ax, double ay, double bx, double by, double d,
+ bool *empty_gap)
+{
+ double ab= ax * bx + ay * by;
+ double cosab= ab / (d * d) + GIS_ZERO;
+ double n_sin, n_cos;
+ double x_n, y_n;
+ int n=1;
+
+ *empty_gap= true;
+ for (;;)
+ {
+ get_n_sincos(n++, &n_sin, &n_cos);
+ if (n_cos <= cosab)
+ break;
+ *empty_gap= false;
+ x_n= ax * n_cos - ay * n_sin;
+ y_n= ax * n_sin + ay * n_cos;
+ if (trn->add_point(x_n + x, y_n + y))
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ Calculates the vector (p2,p1) and
+ negatively orthogonal to it with the length of d.
+ The result is (ex,ey) - the vector, (px,py) - the orthogonal.
+*/
+
+static void calculate_perpendicular(
+ double x1, double y1, double x2, double y2, double d,
+ double *ex, double *ey,
+ double *px, double *py)
+{
+ double q;
+ *ex= x1 - x2;
+ *ey= y1 - y2;
+ q= d / sqrt((*ex) * (*ex) + (*ey) * (*ey));
+ *px= (*ey) * q;
+ *py= -(*ex) * q;
+}
+
+
+int Item_func_buffer::Transporter::single_point(double x, double y)
+{
+ if (buffer_op == Gcalc_function::op_difference)
+ {
+ m_fn->add_operation(Gcalc_function::op_false, 0);
+ return 0;
+ }
+
+ m_nshapes= 0;
+ return add_point_buffer(x, y);
+}
+
+
+int Item_func_buffer::Transporter::add_edge_buffer(
+ double x3, double y3, bool round_p1, bool round_p2)
+{
+ Gcalc_operation_transporter trn(m_fn, m_heap);
+ double e1_x, e1_y, e2_x, e2_y, p1_x, p1_y, p2_x, p2_y;
+ double e1e2;
+ double sin1, cos1;
+ double x_n, y_n;
+ bool empty_gap1, empty_gap2;
+
+ ++m_nshapes;
+ if (trn.start_simple_poly())
+ return 1;
+
+ calculate_perpendicular(x1, y1, x2, y2, m_d, &e1_x, &e1_y, &p1_x, &p1_y);
+ calculate_perpendicular(x3, y3, x2, y2, m_d, &e2_x, &e2_y, &p2_x, &p2_y);
+
+ e1e2= e1_x * e2_y - e2_x * e1_y;
+ sin1= n_sinus[1];
+ cos1= n_sinus[31];
+ if (e1e2 < 0)
+ {
+ empty_gap2= false;
+ x_n= x2 + p2_x * cos1 - p2_y * sin1;
+ y_n= y2 + p2_y * cos1 + p2_x * sin1;
+ if (fill_gap(&trn, x2, y2, -p1_x,-p1_y, p2_x,p2_y, m_d, &empty_gap1) ||
+ trn.add_point(x2 + p2_x, y2 + p2_y) ||
+ trn.add_point(x_n, y_n))
+ return 1;
+ }
+ else
+ {
+ x_n= x2 - p2_x * cos1 - p2_y * sin1;
+ y_n= y2 - p2_y * cos1 + p2_x * sin1;
+ if (trn.add_point(x_n, y_n) ||
+ trn.add_point(x2 - p2_x, y2 - p2_y) ||
+ fill_gap(&trn, x2, y2, -p2_x, -p2_y, p1_x, p1_y, m_d, &empty_gap2))
+ return 1;
+ empty_gap1= false;
+ }
+ if ((!empty_gap2 && trn.add_point(x2 + p1_x, y2 + p1_y)) ||
+ trn.add_point(x1 + p1_x, y1 + p1_y))
+ return 1;
+
+ if (round_p1 && fill_half_circle(&trn, x1, y1, p1_x, p1_y))
+ return 1;
+
+ if (trn.add_point(x1 - p1_x, y1 - p1_y) ||
+ (!empty_gap1 && trn.add_point(x2 - p1_x, y2 - p1_y)))
+ return 1;
+ return trn.complete_simple_poly();
+}
+
+
+int Item_func_buffer::Transporter::add_last_edge_buffer()
+{
+ Gcalc_operation_transporter trn(m_fn, m_heap);
+ double e1_x, e1_y, p1_x, p1_y;
+
+ ++m_nshapes;
+ if (trn.start_simple_poly())
+ return 1;
+
+ calculate_perpendicular(x1, y1, x2, y2, m_d, &e1_x, &e1_y, &p1_x, &p1_y);
+
+ if (trn.add_point(x1 + p1_x, y1 + p1_y) ||
+ trn.add_point(x1 - p1_x, y1 - p1_y) ||
+ trn.add_point(x2 - p1_x, y2 - p1_y) ||
+ fill_half_circle(&trn, x2, y2, -p1_x, -p1_y) ||
+ trn.add_point(x2 + p1_x, y2 + p1_y))
+ return 1;
+ return trn.complete_simple_poly();
+}
+
+
+int Item_func_buffer::Transporter::add_point_buffer(double x, double y)
+{
+ Gcalc_operation_transporter trn(m_fn, m_heap);
+
+ m_nshapes++;
+ if (trn.start_simple_poly())
+ return 1;
+ if (trn.add_point(x - m_d, y) ||
+ fill_half_circle(&trn, x, y, -m_d, 0.0) ||
+ trn.add_point(x + m_d, y) ||
+ fill_half_circle(&trn, x, y, m_d, 0.0))
+ return 1;
+ return trn.complete_simple_poly();
+}
+
+
+int Item_func_buffer::Transporter::start_line()
+{
+ if (buffer_op == Gcalc_function::op_difference)
+ {
+ if (m_fn->reserve_op_buffer(1))
+ return 1;
+ m_fn->add_operation(Gcalc_function::op_false, 0);
+ skip_line= TRUE;
+ return 0;
+ }
+
+ m_nshapes= 0;
+
+ if (m_fn->reserve_op_buffer(2))
+ return 1;
+ last_shape_pos= m_fn->get_next_expression_pos();
+ m_fn->add_operation(buffer_op, 0);
+ m_npoints= 0;
+ int_start_line();
+ return 0;
+}
+
+
+int Item_func_buffer::Transporter::start_poly()
+{
+ m_nshapes= 1;
+
+ if (m_fn->reserve_op_buffer(2))
+ return 1;
+ last_shape_pos= m_fn->get_next_expression_pos();
+ m_fn->add_operation(buffer_op, 0);
+ return Gcalc_operation_transporter::start_poly();
+}
+
+
+int Item_func_buffer::Transporter::complete_poly()
+{
+ if (Gcalc_operation_transporter::complete_poly())
+ return 1;
+ m_fn->add_operands_to_op(last_shape_pos, m_nshapes);
+ return 0;
+}
+
+
+int Item_func_buffer::Transporter::start_ring()
+{
+ m_npoints= 0;
+ return Gcalc_operation_transporter::start_ring();
+}
+
+
+int Item_func_buffer::Transporter::start_collection(int n_objects)
+{
+ if (m_fn->reserve_op_buffer(1))
+ return 1;
+ m_fn->add_operation(Gcalc_function::op_union, n_objects);
+ return 0;
+}
+
+
+int Item_func_buffer::Transporter::add_point(double x, double y)
+{
+ if (skip_line)
+ return 0;
+
+ if (m_npoints && x == x2 && y == y2)
+ return 0;
+
+ ++m_npoints;
+
+ if (m_npoints == 1)
+ {
+ x00= x;
+ y00= y;
+ }
+ else if (m_npoints == 2)
+ {
+ x01= x;
+ y01= y;
+ }
+ else if (add_edge_buffer(x, y, (m_npoints == 3) && line_started(), false))
+ return 1;
+
+ x1= x2;
+ y1= y2;
+ x2= x;
+ y2= y;
+
+ return line_started() ? 0 : Gcalc_operation_transporter::add_point(x, y);
+}
+
+
+int Item_func_buffer::Transporter::complete()
+{
+ if (m_npoints)
+ {
+ if (m_npoints == 1)
+ {
+ if (add_point_buffer(x2, y2))
+ return 1;
+ }
+ else if (m_npoints == 2)
+ {
+ if (add_edge_buffer(x1, y1, true, true))
+ return 1;
+ }
+ else if (line_started())
+ {
+ if (add_last_edge_buffer())
+ return 1;
+ }
+ else
+ {
+ if (x2 != x00 || y2 != y00)
+ {
+ if (add_edge_buffer(x00, y00, false, false))
+ return 1;
+ x1= x2;
+ y1= y2;
+ x2= x00;
+ y2= y00;
+ }
+ if (add_edge_buffer(x01, y01, false, false))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+int Item_func_buffer::Transporter::complete_line()
+{
+ if (!skip_line)
+ {
+ if (complete())
+ return 1;
+ int_complete_line();
+ m_fn->add_operands_to_op(last_shape_pos, m_nshapes);
+ }
+ skip_line= FALSE;
+ return 0;
+}
+
+
+int Item_func_buffer::Transporter::complete_ring()
+{
+ return complete() ||
+ Gcalc_operation_transporter::complete_ring();
+}
+
+
+String *Item_func_buffer::val_str(String *str_value)
+{
+ DBUG_ENTER("Item_func_buffer::val_str");
+ DBUG_ASSERT(fixed == 1);
+ String *obj= args[0]->val_str(&tmp_value);
+ double dist= args[1]->val_real();
+ Geometry_buffer buffer;
+ Geometry *g;
+ uint32 srid= 0;
+ String *str_result= NULL;
+ Transporter trn(&func, &collector, dist);
+ MBR mbr;
+ const char *c_end;
+
+ null_value= 1;
+ if (args[0]->null_value || args[1]->null_value ||
+ !(g= Geometry::construct(&buffer, obj->ptr(), obj->length())) ||
+ g->get_mbr(&mbr, &c_end))
+ goto mem_error;
+
+ if (dist > 0.0)
+ mbr.buffer(dist);
+ else
+ {
+ /* This happens when dist is too far negative. */
+ if (mbr.xmax + dist < mbr.xmin || mbr.ymax + dist < mbr.ymin)
+ goto return_empty_result;
+ }
+
+ collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax);
+ /*
+ If the distance given is 0, the Buffer function is in fact NOOP,
+ so it's natural just to return the argument1.
+ Besides, internal calculations here can't handle zero distance anyway.
+ */
+ if (fabs(dist) < GIS_ZERO)
+ {
+ null_value= 0;
+ str_result= obj;
+ goto mem_error;
+ }
+
+ if (g->store_shapes(&trn))
+ goto mem_error;
+
+ collector.prepare_operation();
+ if (func.alloc_states())
+ goto mem_error;
+ operation.init(&func);
+ operation.killed= (int *) &(current_thd->killed);
+
+ if (operation.count_all(&collector) ||
+ operation.get_result(&res_receiver))
+ goto mem_error;
+
+
+return_empty_result:
+ str_value->set_charset(&my_charset_bin);
+ if (str_value->reserve(SRID_SIZE, 512))
+ goto mem_error;
+ str_value->length(0);
+ str_value->q_append(srid);
+
+ if (Geometry::create_from_opresult(&buffer, str_value, res_receiver))
+ goto mem_error;
+
+ null_value= 0;
+ str_result= str_value;
+mem_error:
+ collector.reset();
+ func.reset();
+ res_receiver.reset();
+ DBUG_RETURN(str_result);
+}
+
+
+longlong Item_func_isempty::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String tmp;
+ String *swkb= args[0]->val_str(&tmp);
+ Geometry_buffer buffer;
+
+ null_value= args[0]->null_value ||
+ !(Geometry::construct(&buffer, swkb->ptr(), swkb->length()));
+ return null_value ? 1 : 0;
+}
+
+
+longlong Item_func_issimple::val_int()
+{
+ String *swkb= args[0]->val_str(&tmp);
+ Geometry_buffer buffer;
+ Gcalc_operation_transporter trn(&func, &collector);
+ Geometry *g;
+ int result= 1;
+ const Gcalc_scan_iterator::event_point *ev;
+ MBR mbr;
+ const char *c_end;
+
+ DBUG_ENTER("Item_func_issimple::val_int");
+ DBUG_ASSERT(fixed == 1);
+
+ if ((null_value= (args[0]->null_value ||
+ !(g= Geometry::construct(&buffer, swkb->ptr(), swkb->length())) ||
+ g->get_mbr(&mbr, &c_end))))
+ DBUG_RETURN(0);
+
+ collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax);
+
+ if (g->get_class_info()->m_type_id == Geometry::wkb_point)
+ DBUG_RETURN(1);
+
+ if (g->store_shapes(&trn))
+ goto mem_error;
+
+ collector.prepare_operation();
+ scan_it.init(&collector);
+
+ while (scan_it.more_points())
+ {
+ if (scan_it.step())
+ goto mem_error;
+
+ ev= scan_it.get_events();
+ if (ev->simple_event())
+ continue;
+
+ if ((ev->event == scev_thread || ev->event == scev_single_point) &&
+ !ev->get_next())
+ continue;
+
+ if (ev->event == scev_two_threads && !ev->get_next()->get_next())
+ continue;
+
+ result= 0;
+ break;
+ }
+
+ collector.reset();
+ func.reset();
+ scan_it.reset();
+ DBUG_RETURN(result);
+mem_error:
+ null_value= 1;
+ DBUG_RETURN(0);
+}
+
+
+longlong Item_func_isclosed::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String tmp;
+ String *swkb= args[0]->val_str(&tmp);
+ Geometry_buffer buffer;
+ Geometry *geom;
+ int isclosed= 0; // In case of error
+
+ null_value= (!swkb ||
+ args[0]->null_value ||
+ !(geom=
+ Geometry::construct(&buffer, swkb->ptr(), swkb->length())) ||
+ geom->is_closed(&isclosed));
+
+ return (longlong) isclosed;
+}
+
+/*
+ Numerical functions
+*/
+
+
+longlong Item_func_dimension::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint32 dim= 0; // In case of error
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+ const char *dummy;
+
+ null_value= (!swkb ||
+ args[0]->null_value ||
+ !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())) ||
+ geom->dimension(&dim, &dummy));
+ return (longlong) dim;
+}
+
+
+longlong Item_func_numinteriorring::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint32 num= 0; // In case of error
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ null_value= (!swkb ||
+ !(geom= Geometry::construct(&buffer,
+ swkb->ptr(), swkb->length())) ||
+ geom->num_interior_ring(&num));
+ return (longlong) num;
+}
+
+
+longlong Item_func_numgeometries::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint32 num= 0; // In case of errors
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ null_value= (!swkb ||
+ !(geom= Geometry::construct(&buffer,
+ swkb->ptr(), swkb->length())) ||
+ geom->num_geometries(&num));
+ return (longlong) num;
+}
+
+
+longlong Item_func_numpoints::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint32 num= 0; // In case of errors
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ null_value= (!swkb ||
+ args[0]->null_value ||
+ !(geom= Geometry::construct(&buffer,
+ swkb->ptr(), swkb->length())) ||
+ geom->num_points(&num));
+ return (longlong) num;
+}
+
+
+double Item_func_x::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double res= 0.0; // In case of errors
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ null_value= (!swkb ||
+ !(geom= Geometry::construct(&buffer,
+ swkb->ptr(), swkb->length())) ||
+ geom->get_x(&res));
+ return res;
+}
+
+
+double Item_func_y::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double res= 0; // In case of errors
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ null_value= (!swkb ||
+ !(geom= Geometry::construct(&buffer,
+ swkb->ptr(), swkb->length())) ||
+ geom->get_y(&res));
+ return res;
+}
+
+
+double Item_func_area::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double res= 0; // In case of errors
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+ const char *dummy;
+
+ null_value= (!swkb ||
+ !(geom= Geometry::construct(&buffer,
+ swkb->ptr(), swkb->length())) ||
+ geom->area(&res, &dummy));
+ return res;
+}
+
+double Item_func_glength::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double res= 0; // In case of errors
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+ Geometry *geom;
+ const char *end;
+
+ null_value= (!swkb ||
+ !(geom= Geometry::construct(&buffer,
+ swkb->ptr(),
+ swkb->length())) ||
+ geom->geom_length(&res, &end));
+ return res;
+}
+
+longlong Item_func_srid::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *swkb= args[0]->val_str(&value);
+ Geometry_buffer buffer;
+
+ null_value= (!swkb ||
+ !Geometry::construct(&buffer,
+ swkb->ptr(), swkb->length()));
+ if (null_value)
+ return 0;
+
+ return (longlong) (uint4korr(swkb->ptr()));
+}
+
+
+double Item_func_distance::val_real()
+{
+ bool cur_point_edge;
+ const Gcalc_scan_iterator::point *evpos;
+ const Gcalc_heap::Info *cur_point, *dist_point;
+ const Gcalc_scan_iterator::event_point *ev;
+ double t, distance, cur_distance;
+ double x1, x2, y1, y2;
+ double ex, ey, vx, vy, e_sqrlen;
+ uint obj2_si;
+ Gcalc_operation_transporter trn(&func, &collector);
+
+ DBUG_ENTER("Item_func_distance::val_real");
+ DBUG_ASSERT(fixed == 1);
+ String *res1= args[0]->val_str(&tmp_value1);
+ String *res2= args[1]->val_str(&tmp_value2);
+ Geometry_buffer buffer1, buffer2;
+ Geometry *g1, *g2;
+ MBR mbr1, mbr2;
+ const char *c_end;
+
+
+ if ((null_value= (args[0]->null_value || args[1]->null_value ||
+ !(g1= Geometry::construct(&buffer1, res1->ptr(), res1->length())) ||
+ !(g2= Geometry::construct(&buffer2, res2->ptr(), res2->length())) ||
+ g1->get_mbr(&mbr1, &c_end) ||
+ g2->get_mbr(&mbr2, &c_end))))
+ goto mem_error;
+
+ mbr1.add_mbr(&mbr2);
+ collector.set_extent(mbr1.xmin, mbr1.xmax, mbr1.ymin, mbr1.ymax);
+
+ if ((g1->get_class_info()->m_type_id == Geometry::wkb_point) &&
+ (g2->get_class_info()->m_type_id == Geometry::wkb_point))
+ {
+ if (((Gis_point *) g1)->get_xy(&x1, &y1) ||
+ ((Gis_point *) g2)->get_xy(&x2, &y2))
+ goto mem_error;
+ ex= x2 - x1;
+ ey= y2 - y1;
+ DBUG_RETURN(sqrt(ex * ex + ey * ey));
+ }
+
+ if (func.reserve_op_buffer(1))
+ goto mem_error;
+ func.add_operation(Gcalc_function::op_intersection, 2);
+
+ if (g1->store_shapes(&trn))
+ goto mem_error;
+ obj2_si= func.get_nshapes();
+ if (g2->store_shapes(&trn) || func.alloc_states())
+ goto mem_error;
+
+ if (obj2_si == 0 || func.get_nshapes() == obj2_si)
+ {
+ distance= 0.0;
+ null_value= 1;
+ goto exit;
+ }
+
+
+ collector.prepare_operation();
+ scan_it.init(&collector);
+
+ distance= DBL_MAX;
+ while (scan_it.more_points())
+ {
+ if (scan_it.step())
+ goto mem_error;
+ evpos= scan_it.get_event_position();
+ ev= scan_it.get_events();
+
+ if (ev->simple_event())
+ {
+ cur_point= ev->pi;
+ goto count_distance;
+ }
+ /*
+ handling intersection we only need to check if it's the intersecion
+ of objects 1 and 2. In this case distance is 0
+ */
+ cur_point= NULL;
+
+ /*
+ having these events we need to check for possible intersection
+ of objects
+ scev_thread | scev_two_threads | scev_single_point
+ */
+ func.clear_i_states();
+ for (Gcalc_point_iterator pit(&scan_it); pit.point() != evpos; ++pit)
+ {
+ gcalc_shape_info si= pit.point()->get_shape();
+ if ((func.get_shape_kind(si) == Gcalc_function::shape_polygon))
+ func.invert_i_state(si);
+ }
+
+ func.clear_b_states();
+ for (; ev; ev= ev->get_next())
+ {
+ if (ev->event != scev_intersection)
+ cur_point= ev->pi;
+ func.set_b_state(ev->get_shape());
+ if (func.count())
+ {
+ /* Point of one object is inside the other - intersection found */
+ distance= 0;
+ goto exit;
+ }
+ }
+
+ if (!cur_point)
+ continue;
+
+count_distance:
+ if (cur_point->shape >= obj2_si)
+ continue;
+ cur_point_edge= !cur_point->is_bottom();
+
+ for (dist_point= collector.get_first(); dist_point; dist_point= dist_point->get_next())
+ {
+ /* We only check vertices of object 2 */
+ if (dist_point->type != Gcalc_heap::nt_shape_node ||
+ dist_point->shape < obj2_si)
+ continue;
+
+ /* if we have an edge to check */
+ if (dist_point->left)
+ {
+ t= count_edge_t(dist_point, dist_point->left, cur_point,
+ ex, ey, vx, vy, e_sqrlen);
+ if ((t>0.0) && (t<1.0))
+ {
+ cur_distance= distance_to_line(ex, ey, vx, vy, e_sqrlen);
+ if (distance > cur_distance)
+ distance= cur_distance;
+ }
+ }
+ if (cur_point_edge)
+ {
+ t= count_edge_t(cur_point, cur_point->left, dist_point,
+ ex, ey, vx, vy, e_sqrlen);
+ if ((t>0.0) && (t<1.0))
+ {
+ cur_distance= distance_to_line(ex, ey, vx, vy, e_sqrlen);
+ if (distance > cur_distance)
+ distance= cur_distance;
+ }
+ }
+ cur_distance= distance_points(cur_point, dist_point);
+ if (distance > cur_distance)
+ distance= cur_distance;
+ }
+ }
+exit:
+ collector.reset();
+ func.reset();
+ scan_it.reset();
+ DBUG_RETURN(distance);
+mem_error:
+ null_value= 1;
+ DBUG_RETURN(0);
+}
+
+
+#ifndef DBUG_OFF
+longlong Item_func_gis_debug::val_int()
+{
+ /* For now this is just a stub. TODO: implement the internal GIS debuggign */
+ return 0;
+}
+#endif
+
+#endif /*HAVE_SPATIAL*/
diff --git a/sql/item_geofunc.h b/sql/item_geofunc.h
new file mode 100644
index 00000000000..94be38e26ee
--- /dev/null
+++ b/sql/item_geofunc.h
@@ -0,0 +1,518 @@
+#ifndef ITEM_GEOFUNC_INCLUDED
+#define ITEM_GEOFUNC_INCLUDED
+
+/* Copyright (c) 2000, 2010 Oracle and/or its affiliates.
+ Copyright (C) 2011, 2015 MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* This file defines all spatial functions */
+
+#ifdef HAVE_SPATIAL
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "gcalc_slicescan.h"
+#include "gcalc_tools.h"
+
+class Item_geometry_func: public Item_str_func
+{
+public:
+ Item_geometry_func() :Item_str_func() {}
+ Item_geometry_func(Item *a) :Item_str_func(a) {}
+ Item_geometry_func(Item *a,Item *b) :Item_str_func(a,b) {}
+ Item_geometry_func(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {}
+ Item_geometry_func(List<Item> &list) :Item_str_func(list) {}
+ void fix_length_and_dec();
+ enum_field_types field_type() const { return MYSQL_TYPE_GEOMETRY; }
+ Field *tmp_table_field(TABLE *t_arg);
+ bool is_null() { (void) val_int(); return null_value; }
+};
+
+class Item_func_geometry_from_text: public Item_geometry_func
+{
+public:
+ Item_func_geometry_from_text(Item *a) :Item_geometry_func(a) {}
+ Item_func_geometry_from_text(Item *a, Item *srid) :Item_geometry_func(a, srid) {}
+ const char *func_name() const { return "st_geometryfromtext"; }
+ String *val_str(String *);
+};
+
+class Item_func_geometry_from_wkb: public Item_geometry_func
+{
+public:
+ Item_func_geometry_from_wkb(Item *a): Item_geometry_func(a) {}
+ Item_func_geometry_from_wkb(Item *a, Item *srid): Item_geometry_func(a, srid) {}
+ const char *func_name() const { return "st_geometryfromwkb"; }
+ String *val_str(String *);
+};
+
+class Item_func_as_wkt: public Item_str_ascii_func
+{
+public:
+ Item_func_as_wkt(Item *a): Item_str_ascii_func(a) {}
+ const char *func_name() const { return "st_astext"; }
+ String *val_str_ascii(String *);
+ void fix_length_and_dec();
+};
+
+class Item_func_as_wkb: public Item_geometry_func
+{
+public:
+ Item_func_as_wkb(Item *a): Item_geometry_func(a) {}
+ const char *func_name() const { return "st_aswkb"; }
+ String *val_str(String *);
+ enum_field_types field_type() const { return MYSQL_TYPE_BLOB; }
+};
+
+class Item_func_geometry_type: public Item_str_ascii_func
+{
+public:
+ Item_func_geometry_type(Item *a): Item_str_ascii_func(a) {}
+ String *val_str_ascii(String *);
+ const char *func_name() const { return "st_geometrytype"; }
+ void fix_length_and_dec()
+ {
+ // "GeometryCollection" is the longest
+ fix_length_and_charset(20, default_charset());
+ maybe_null= 1;
+ };
+};
+
+class Item_func_centroid: public Item_geometry_func
+{
+public:
+ Item_func_centroid(Item *a): Item_geometry_func(a) {}
+ const char *func_name() const { return "st_centroid"; }
+ String *val_str(String *);
+ Field::geometry_type get_geometry_type() const;
+};
+
+class Item_func_envelope: public Item_geometry_func
+{
+public:
+ Item_func_envelope(Item *a): Item_geometry_func(a) {}
+ const char *func_name() const { return "st_envelope"; }
+ String *val_str(String *);
+ Field::geometry_type get_geometry_type() const;
+};
+
+class Item_func_point: public Item_geometry_func
+{
+public:
+ Item_func_point(Item *a, Item *b): Item_geometry_func(a, b) {}
+ Item_func_point(Item *a, Item *b, Item *srid): Item_geometry_func(a, b, srid) {}
+ const char *func_name() const { return "point"; }
+ String *val_str(String *);
+ Field::geometry_type get_geometry_type() const;
+};
+
+class Item_func_spatial_decomp: public Item_geometry_func
+{
+ enum Functype decomp_func;
+public:
+ Item_func_spatial_decomp(Item *a, Item_func::Functype ft) :
+ Item_geometry_func(a) { decomp_func = ft; }
+ const char *func_name() const
+ {
+ switch (decomp_func)
+ {
+ case SP_STARTPOINT:
+ return "st_startpoint";
+ case SP_ENDPOINT:
+ return "st_endpoint";
+ case SP_EXTERIORRING:
+ return "st_exteriorring";
+ default:
+ DBUG_ASSERT(0); // Should never happened
+ return "spatial_decomp_unknown";
+ }
+ }
+ String *val_str(String *);
+};
+
+class Item_func_spatial_decomp_n: public Item_geometry_func
+{
+ enum Functype decomp_func_n;
+public:
+ Item_func_spatial_decomp_n(Item *a, Item *b, Item_func::Functype ft):
+ Item_geometry_func(a, b) { decomp_func_n = ft; }
+ const char *func_name() const
+ {
+ switch (decomp_func_n)
+ {
+ case SP_POINTN:
+ return "st_pointn";
+ case SP_GEOMETRYN:
+ return "st_geometryn";
+ case SP_INTERIORRINGN:
+ return "st_interiorringn";
+ default:
+ DBUG_ASSERT(0); // Should never happened
+ return "spatial_decomp_n_unknown";
+ }
+ }
+ String *val_str(String *);
+};
+
+class Item_func_spatial_collection: public Item_geometry_func
+{
+ String tmp_value;
+ enum Geometry::wkbType coll_type;
+ enum Geometry::wkbType item_type;
+public:
+ Item_func_spatial_collection(
+ List<Item> &list, enum Geometry::wkbType ct, enum Geometry::wkbType it):
+ Item_geometry_func(list)
+ {
+ coll_type=ct;
+ item_type=it;
+ }
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ Item_geometry_func::fix_length_and_dec();
+ for (unsigned int i= 0; i < arg_count; ++i)
+ {
+ if (args[i]->fixed && args[i]->field_type() != MYSQL_TYPE_GEOMETRY)
+ {
+ String str;
+ args[i]->print(&str, QT_ORDINARY);
+ str.append('\0');
+ my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "non geometric",
+ str.ptr());
+ }
+ }
+ }
+
+ const char *func_name() const { return "st_multipoint"; }
+};
+
+
+/*
+ Spatial relations
+*/
+
+class Item_func_spatial_mbr_rel: public Item_bool_func2
+{
+ enum Functype spatial_rel;
+public:
+ Item_func_spatial_mbr_rel(Item *a,Item *b, enum Functype sp_rel) :
+ Item_bool_func2(a,b) { spatial_rel = sp_rel; }
+ longlong val_int();
+ enum Functype functype() const
+ {
+ return spatial_rel;
+ }
+ enum Functype rev_functype() const { return spatial_rel; }
+ const char *func_name() const;
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ Item_func::print(str, query_type);
+ }
+ void fix_length_and_dec() { maybe_null= 1; }
+ bool is_null() { (void) val_int(); return null_value; }
+};
+
+
+class Item_func_spatial_rel: public Item_bool_func2
+{
+ enum Functype spatial_rel;
+ Gcalc_heap collector;
+ Gcalc_scan_iterator scan_it;
+ Gcalc_function func;
+ String tmp_value1,tmp_value2;
+public:
+ Item_func_spatial_rel(Item *a,Item *b, enum Functype sp_rel);
+ virtual ~Item_func_spatial_rel();
+ longlong val_int();
+ enum Functype functype() const
+ {
+ return spatial_rel;
+ }
+ enum Functype rev_functype() const { return spatial_rel; }
+ const char *func_name() const;
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ Item_func::print(str, query_type);
+ }
+
+ void fix_length_and_dec() { maybe_null= 1; }
+ bool is_null() { (void) val_int(); return null_value; }
+};
+
+
+/*
+ Spatial operations
+*/
+
+class Item_func_spatial_operation: public Item_geometry_func
+{
+public:
+ Gcalc_function::op_type spatial_op;
+ Gcalc_heap collector;
+ Gcalc_function func;
+
+ Gcalc_result_receiver res_receiver;
+ Gcalc_operation_reducer operation;
+ String tmp_value1,tmp_value2;
+public:
+ Item_func_spatial_operation(Item *a,Item *b, Gcalc_function::op_type sp_op) :
+ Item_geometry_func(a, b), spatial_op(sp_op)
+ {}
+ virtual ~Item_func_spatial_operation();
+ String *val_str(String *);
+ const char *func_name() const;
+ virtual inline void print(String *str, enum_query_type query_type)
+ {
+ Item_func::print(str, query_type);
+ }
+};
+
+
+class Item_func_buffer: public Item_geometry_func
+{
+protected:
+ class Transporter : public Gcalc_operation_transporter
+ {
+ int m_npoints;
+ double m_d;
+ double x1,y1,x2,y2;
+ double x00,y00,x01,y01;
+ int add_edge_buffer(double x3, double y3, bool round_p1, bool round_p2);
+ int add_last_edge_buffer();
+ int add_point_buffer(double x, double y);
+ int complete();
+ int m_nshapes;
+ Gcalc_function::op_type buffer_op;
+ int last_shape_pos;
+ bool skip_line;
+
+ public:
+ Transporter(Gcalc_function *fn, Gcalc_heap *heap, double d) :
+ Gcalc_operation_transporter(fn, heap), m_npoints(0), m_d(d),
+ m_nshapes(0), buffer_op((d > 0.0) ? Gcalc_function::op_union :
+ Gcalc_function::op_difference),
+ skip_line(FALSE)
+ {}
+ int single_point(double x, double y);
+ int start_line();
+ int complete_line();
+ int start_poly();
+ int complete_poly();
+ int start_ring();
+ int complete_ring();
+ int add_point(double x, double y);
+
+ int start_collection(int n_objects);
+ };
+ Gcalc_heap collector;
+ Gcalc_function func;
+
+ Gcalc_result_receiver res_receiver;
+ Gcalc_operation_reducer operation;
+ String tmp_value;
+
+public:
+ Item_func_buffer(Item *obj, Item *distance):
+ Item_geometry_func(obj, distance) {}
+ const char *func_name() const { return "st_buffer"; }
+ String *val_str(String *);
+};
+
+
+class Item_func_isempty: public Item_bool_func
+{
+public:
+ Item_func_isempty(Item *a): Item_bool_func(a) {}
+ longlong val_int();
+ optimize_type select_optimize() const { return OPTIMIZE_NONE; }
+ const char *func_name() const { return "st_isempty"; }
+ void fix_length_and_dec() { maybe_null= 1; }
+};
+
+class Item_func_issimple: public Item_bool_func
+{
+ Gcalc_heap collector;
+ Gcalc_function func;
+ Gcalc_scan_iterator scan_it;
+ String tmp;
+public:
+ Item_func_issimple(Item *a): Item_bool_func(a) {}
+ longlong val_int();
+ optimize_type select_optimize() const { return OPTIMIZE_NONE; }
+ const char *func_name() const { return "st_issimple"; }
+ void fix_length_and_dec() { maybe_null= 1; }
+};
+
+class Item_func_isclosed: public Item_bool_func
+{
+public:
+ Item_func_isclosed(Item *a): Item_bool_func(a) {}
+ longlong val_int();
+ optimize_type select_optimize() const { return OPTIMIZE_NONE; }
+ const char *func_name() const { return "st_isclosed"; }
+ void fix_length_and_dec() { maybe_null= 1; }
+};
+
+class Item_func_dimension: public Item_int_func
+{
+ String value;
+public:
+ Item_func_dimension(Item *a): Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "st_dimension"; }
+ void fix_length_and_dec() { max_length= 10; maybe_null= 1; }
+};
+
+class Item_func_x: public Item_real_func
+{
+ String value;
+public:
+ Item_func_x(Item *a): Item_real_func(a) {}
+ double val_real();
+ const char *func_name() const { return "st_x"; }
+ void fix_length_and_dec()
+ {
+ Item_real_func::fix_length_and_dec();
+ maybe_null= 1;
+ }
+};
+
+
+class Item_func_y: public Item_real_func
+{
+ String value;
+public:
+ Item_func_y(Item *a): Item_real_func(a) {}
+ double val_real();
+ const char *func_name() const { return "st_y"; }
+ void fix_length_and_dec()
+ {
+ Item_real_func::fix_length_and_dec();
+ maybe_null= 1;
+ }
+};
+
+
+class Item_func_numgeometries: public Item_int_func
+{
+ String value;
+public:
+ Item_func_numgeometries(Item *a): Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "st_numgeometries"; }
+ void fix_length_and_dec() { max_length= 10; maybe_null= 1; }
+};
+
+
+class Item_func_numinteriorring: public Item_int_func
+{
+ String value;
+public:
+ Item_func_numinteriorring(Item *a): Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "st_numinteriorrings"; }
+ void fix_length_and_dec() { max_length= 10; maybe_null= 1; }
+};
+
+
+class Item_func_numpoints: public Item_int_func
+{
+ String value;
+public:
+ Item_func_numpoints(Item *a): Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "st_numpoints"; }
+ void fix_length_and_dec() { max_length= 10; maybe_null= 1; }
+};
+
+
+class Item_func_area: public Item_real_func
+{
+ String value;
+public:
+ Item_func_area(Item *a): Item_real_func(a) {}
+ double val_real();
+ const char *func_name() const { return "st_area"; }
+ void fix_length_and_dec()
+ {
+ Item_real_func::fix_length_and_dec();
+ maybe_null= 1;
+ }
+};
+
+
+class Item_func_glength: public Item_real_func
+{
+ String value;
+public:
+ Item_func_glength(Item *a): Item_real_func(a) {}
+ double val_real();
+ const char *func_name() const { return "st_length"; }
+ void fix_length_and_dec()
+ {
+ Item_real_func::fix_length_and_dec();
+ maybe_null= 1;
+ }
+};
+
+
+class Item_func_srid: public Item_int_func
+{
+ String value;
+public:
+ Item_func_srid(Item *a): Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "srid"; }
+ void fix_length_and_dec() { max_length= 10; maybe_null= 1; }
+};
+
+
+class Item_func_distance: public Item_real_func
+{
+ String tmp_value1;
+ String tmp_value2;
+ Gcalc_heap collector;
+ Gcalc_function func;
+ Gcalc_scan_iterator scan_it;
+public:
+ Item_func_distance(Item *a, Item *b): Item_real_func(a, b) {}
+ double val_real();
+ const char *func_name() const { return "st_distance"; }
+};
+
+
+#ifndef DBUG_OFF
+class Item_func_gis_debug: public Item_int_func
+{
+ public:
+ Item_func_gis_debug(Item *a) :Item_int_func(a) { null_value= false; }
+ const char *func_name() const { return "st_gis_debug"; }
+ longlong val_int();
+};
+#endif
+
+
+#define GEOM_NEW(thd, obj_constructor) new (thd->mem_root) obj_constructor
+
+#else /*HAVE_SPATIAL*/
+
+#define GEOM_NEW(thd, obj_constructor) NULL
+
+#endif /*HAVE_SPATIAL*/
+#endif /* ITEM_GEOFUNC_INCLUDED */
diff --git a/sql/item_inetfunc.cc b/sql/item_inetfunc.cc
new file mode 100644
index 00000000000..6a09747fa1a
--- /dev/null
+++ b/sql/item_inetfunc.cc
@@ -0,0 +1,831 @@
+/* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2014 MariaDB Foundation
+
+ 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 <my_global.h>
+#include "item_inetfunc.h"
+
+#include "my_net.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+static const int IN_ADDR_SIZE= sizeof (in_addr);
+static const int IN6_ADDR_SIZE= sizeof (in6_addr);
+static const int IN6_ADDR_NUM_WORDS= IN6_ADDR_SIZE / 2;
+
+static const char HEX_DIGITS[]= "0123456789abcdef";
+
+///////////////////////////////////////////////////////////////////////////
+
+longlong Item_func_inet_aton::val_int()
+{
+ DBUG_ASSERT(fixed);
+
+ uint byte_result= 0;
+ ulonglong result= 0; // We are ready for 64 bit addresses
+ const char *p,* end;
+ char c= '.'; // we mark c to indicate invalid IP in case length is 0
+ int dot_count= 0;
+
+ StringBuffer<36> tmp;
+ String *s= args[0]->val_str_ascii(&tmp);
+
+ if (!s) // If null value
+ goto err;
+
+ null_value= 0;
+
+ end= (p = s->ptr()) + s->length();
+ while (p < end)
+ {
+ c= *p++;
+ int digit= (int) (c - '0');
+ if (digit >= 0 && digit <= 9)
+ {
+ if ((byte_result= byte_result * 10 + digit) > 255)
+ goto err; // Wrong address
+ }
+ else if (c == '.')
+ {
+ dot_count++;
+ result= (result << 8) + (ulonglong) byte_result;
+ byte_result= 0;
+ }
+ else
+ goto err; // Invalid character
+ }
+ if (c != '.') // IP number can't end on '.'
+ {
+ /*
+ Attempt to support short forms of IP-addresses. It's however pretty
+ basic one comparing to the BSD support.
+ Examples:
+ 127 -> 0.0.0.127
+ 127.255 -> 127.0.0.255
+ 127.256 -> NULL (should have been 127.0.1.0)
+ 127.2.1 -> 127.2.0.1
+ */
+ switch (dot_count) {
+ case 1: result<<= 8; /* Fall through */
+ case 2: result<<= 8; /* Fall through */
+ }
+ return (result << 8) + (ulonglong) byte_result;
+ }
+
+err:
+ null_value=1;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+String* Item_func_inet_ntoa::val_str(String* str)
+{
+ DBUG_ASSERT(fixed);
+
+ ulonglong n= (ulonglong) args[0]->val_int();
+
+ /*
+ We do not know if args[0] is NULL until we have called
+ some val function on it if args[0] is not a constant!
+
+ Also return null if n > 255.255.255.255
+ */
+ if ((null_value= (args[0]->null_value || n > 0xffffffff)))
+ return 0; // Null value
+
+ str->set_charset(collation.collation);
+ str->length(0);
+
+ uchar buf[8];
+ int4store(buf, n);
+
+ /* Now we can assume little endian. */
+
+ char num[4];
+ num[3]= '.';
+
+ for (uchar *p= buf + 4; p-- > buf;)
+ {
+ uint c= *p;
+ uint n1, n2; // Try to avoid divisions
+ n1= c / 100; // 100 digits
+ c-= n1 * 100;
+ n2= c / 10; // 10 digits
+ c-= n2 * 10; // last digit
+ num[0]= (char) n1 + '0';
+ num[1]= (char) n2 + '0';
+ num[2]= (char) c + '0';
+ uint length= (n1 ? 4 : n2 ? 3 : 2); // Remove pre-zero
+ uint dot_length= (p <= buf) ? 1 : 0;
+ (void) str->append(num + 4 - length, length - dot_length,
+ &my_charset_latin1);
+ }
+
+ return str;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Check the function argument, handle errors properly.
+
+ @return The function value.
+*/
+
+longlong Item_func_inet_bool_base::val_int()
+{
+ DBUG_ASSERT(fixed);
+
+ if (args[0]->result_type() != STRING_RESULT) // String argument expected
+ return 0;
+
+ String buffer;
+ String *arg_str= args[0]->val_str(&buffer);
+
+ if (!arg_str) // Out-of memory happened. The error has been reported.
+ return 0; // Or: the underlying field is NULL
+
+ return calc_value(arg_str) ? 1 : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Check the function argument, handle errors properly.
+
+ @param [out] buffer Buffer for string operations.
+
+ @return The function value.
+*/
+
+String *Item_func_inet_str_base::val_str_ascii(String *buffer)
+{
+ DBUG_ASSERT(fixed);
+
+ if (args[0]->result_type() != STRING_RESULT) // String argument expected
+ {
+ null_value= true;
+ return NULL;
+ }
+
+ String *arg_str= args[0]->val_str(buffer);
+ if (!arg_str) // Out-of memory happened. The error has been reported.
+ { // Or: the underlying field is NULL
+ null_value= true;
+ return NULL;
+ }
+
+ null_value= !calc_value(arg_str, buffer);
+
+ return null_value ? NULL : buffer;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Tries to convert given string to binary IPv4-address representation.
+ This is a portable alternative to inet_pton(AF_INET).
+
+ @param str String to convert.
+ @param str_len String length.
+ @param[out] ipv4_address Buffer to store IPv4-address.
+
+ @return Completion status.
+ @retval false Given string does not represent an IPv4-address.
+ @retval true The string has been converted sucessfully.
+
+ @note The problem with inet_pton() is that it treats leading zeros in
+ IPv4-part differently on different platforms.
+*/
+
+static bool str_to_ipv4(const char *str, int str_length, in_addr *ipv4_address)
+{
+ if (str_length < 7)
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): "
+ "invalid IPv4 address: too short.",
+ str_length, str));
+ return false;
+ }
+
+ if (str_length > 15)
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): "
+ "invalid IPv4 address: too long.",
+ str_length, str));
+ return false;
+ }
+
+ unsigned char *ipv4_bytes= (unsigned char *) ipv4_address;
+ const char *p= str;
+ int byte_value= 0;
+ int chars_in_group= 0;
+ int dot_count= 0;
+ char c= 0;
+
+ while (((p - str) < str_length) && *p)
+ {
+ c= *p++;
+
+ if (my_isdigit(&my_charset_latin1, c))
+ {
+ ++chars_in_group;
+
+ if (chars_in_group > 3)
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: "
+ "too many characters in a group.",
+ str_length, str));
+ return false;
+ }
+
+ byte_value= byte_value * 10 + (c - '0');
+
+ if (byte_value > 255)
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: "
+ "invalid byte value.",
+ str_length, str));
+ return false;
+ }
+ }
+ else if (c == '.')
+ {
+ if (chars_in_group == 0)
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: "
+ "too few characters in a group.",
+ str_length, str));
+ return false;
+ }
+
+ ipv4_bytes[dot_count]= (unsigned char) byte_value;
+
+ ++dot_count;
+ byte_value= 0;
+ chars_in_group= 0;
+
+ if (dot_count > 3)
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: "
+ "too many dots.", str_length, str));
+ return false;
+ }
+ }
+ else
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: "
+ "invalid character at pos %d.",
+ str_length, str, (int) (p - str)));
+ return false;
+ }
+ }
+
+ if (c == '.')
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: "
+ "ending at '.'.", str_length, str));
+ return false;
+ }
+
+ if (dot_count != 3)
+ {
+ DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: "
+ "too few groups.",
+ str_length, str));
+ return false;
+ }
+
+ ipv4_bytes[3]= (unsigned char) byte_value;
+
+ DBUG_PRINT("info", ("str_to_ipv4(%.*s): valid IPv4 address: %d.%d.%d.%d",
+ str_length, str,
+ ipv4_bytes[0], ipv4_bytes[1],
+ ipv4_bytes[2], ipv4_bytes[3]));
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Tries to convert given string to binary IPv6-address representation.
+ This is a portable alternative to inet_pton(AF_INET6).
+
+ @param str String to convert.
+ @param str_len String length.
+ @param[out] ipv6_address Buffer to store IPv6-address.
+
+ @return Completion status.
+ @retval false Given string does not represent an IPv6-address.
+ @retval true The string has been converted sucessfully.
+
+ @note The problem with inet_pton() is that it treats leading zeros in
+ IPv4-part differently on different platforms.
+*/
+
+static bool str_to_ipv6(const char *str, int str_length, in6_addr *ipv6_address)
+{
+ if (str_length < 2)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: too short.",
+ str_length, str));
+ return false;
+ }
+
+ if (str_length > 8 * 4 + 7)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: too long.",
+ str_length, str));
+ return false;
+ }
+
+ memset(ipv6_address, 0, IN6_ADDR_SIZE);
+
+ const char *p= str;
+
+ if (*p == ':')
+ {
+ ++p;
+
+ if (*p != ':')
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "can not start with ':x'.", str_length, str));
+ return false;
+ }
+ }
+
+ char *ipv6_bytes= (char *) ipv6_address;
+ char *ipv6_bytes_end= ipv6_bytes + IN6_ADDR_SIZE;
+ char *dst= ipv6_bytes;
+ char *gap_ptr= NULL;
+ const char *group_start_ptr= p;
+ int chars_in_group= 0;
+ int group_value= 0;
+
+ while (((p - str) < str_length) && *p)
+ {
+ char c= *p++;
+
+ if (c == ':')
+ {
+ group_start_ptr= p;
+
+ if (!chars_in_group)
+ {
+ if (gap_ptr)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "too many gaps(::).", str_length, str));
+ return false;
+ }
+
+ gap_ptr= dst;
+ continue;
+ }
+
+ if (!*p || ((p - str) >= str_length))
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "ending at ':'.", str_length, str));
+ return false;
+ }
+
+ if (dst + 2 > ipv6_bytes_end)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "too many groups (1).", str_length, str));
+ return false;
+ }
+
+ dst[0]= (unsigned char) (group_value >> 8) & 0xff;
+ dst[1]= (unsigned char) group_value & 0xff;
+ dst += 2;
+
+ chars_in_group= 0;
+ group_value= 0;
+ }
+ else if (c == '.')
+ {
+ if (dst + IN_ADDR_SIZE > ipv6_bytes_end)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "unexpected IPv4-part.", str_length, str));
+ return false;
+ }
+
+ if (!str_to_ipv4(group_start_ptr,
+ str + str_length - group_start_ptr,
+ (in_addr *) dst))
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "invalid IPv4-part.", str_length, str));
+ return false;
+ }
+
+ dst += IN_ADDR_SIZE;
+ chars_in_group= 0;
+
+ break;
+ }
+ else
+ {
+ const char *hdp= strchr(HEX_DIGITS, my_tolower(&my_charset_latin1, c));
+
+ if (!hdp)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "invalid character at pos %d.",
+ str_length, str, (int) (p - str)));
+ return false;
+ }
+
+ if (chars_in_group >= 4)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "too many digits in group.",
+ str_length, str));
+ return false;
+ }
+
+ group_value <<= 4;
+ group_value |= hdp - HEX_DIGITS;
+
+ DBUG_ASSERT(group_value <= 0xffff);
+
+ ++chars_in_group;
+ }
+ }
+
+ if (chars_in_group > 0)
+ {
+ if (dst + 2 > ipv6_bytes_end)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "too many groups (2).", str_length, str));
+ return false;
+ }
+
+ dst[0]= (unsigned char) (group_value >> 8) & 0xff;
+ dst[1]= (unsigned char) group_value & 0xff;
+ dst += 2;
+ }
+
+ if (gap_ptr)
+ {
+ if (dst == ipv6_bytes_end)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "no room for a gap (::).", str_length, str));
+ return false;
+ }
+
+ int bytes_to_move= dst - gap_ptr;
+
+ for (int i= 1; i <= bytes_to_move; ++i)
+ {
+ ipv6_bytes_end[-i]= gap_ptr[bytes_to_move - i];
+ gap_ptr[bytes_to_move - i]= 0;
+ }
+
+ dst= ipv6_bytes_end;
+ }
+
+ if (dst < ipv6_bytes_end)
+ {
+ DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: "
+ "too few groups.", str_length, str));
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Converts IPv4-binary-address to a string. This function is a portable
+ alternative to inet_ntop(AF_INET).
+
+ @param[in] ipv4 IPv4-address data (byte array)
+ @param[out] str A buffer to store string representation of IPv4-address.
+ It must be at least of INET_ADDRSTRLEN.
+
+ @note The problem with inet_ntop() is that it is available starting from
+ Windows Vista, but the minimum supported version is Windows 2000.
+*/
+
+static void ipv4_to_str(const in_addr *ipv4, char *str)
+{
+ const unsigned char *ipv4_bytes= (const unsigned char *) ipv4;
+
+ sprintf(str, "%d.%d.%d.%d",
+ ipv4_bytes[0], ipv4_bytes[1], ipv4_bytes[2], ipv4_bytes[3]);
+}
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Converts IPv6-binary-address to a string. This function is a portable
+ alternative to inet_ntop(AF_INET6).
+
+ @param[in] ipv6 IPv6-address data (byte array)
+ @param[out] str A buffer to store string representation of IPv6-address.
+ It must be at least of INET6_ADDRSTRLEN.
+
+ @note The problem with inet_ntop() is that it is available starting from
+ Windows Vista, but out the minimum supported version is Windows 2000.
+*/
+
+static void ipv6_to_str(const in6_addr *ipv6, char *str)
+{
+ struct Region
+ {
+ int pos;
+ int length;
+ };
+
+ const unsigned char *ipv6_bytes= (const unsigned char *) ipv6;
+
+ // 1. Translate IPv6-address bytes to words.
+ // We can't just cast to short, because it's not guaranteed
+ // that sizeof (short) == 2. So, we have to make a copy.
+
+ uint16 ipv6_words[IN6_ADDR_NUM_WORDS];
+
+ for (int i= 0; i < IN6_ADDR_NUM_WORDS; ++i)
+ ipv6_words[i]= (ipv6_bytes[2 * i] << 8) + ipv6_bytes[2 * i + 1];
+
+ // 2. Find "the gap" -- longest sequence of zeros in IPv6-address.
+
+ Region gap= { -1, -1 };
+
+ {
+ Region rg= { -1, -1 };
+
+ for (int i = 0; i < IN6_ADDR_NUM_WORDS; ++i)
+ {
+ if (ipv6_words[i] != 0)
+ {
+ if (rg.pos >= 0)
+ {
+ if (rg.length > gap.length)
+ gap= rg;
+
+ rg.pos= -1;
+ rg.length= -1;
+ }
+ }
+ else
+ {
+ if (rg.pos >= 0)
+ {
+ ++rg.length;
+ }
+ else
+ {
+ rg.pos= i;
+ rg.length= 1;
+ }
+ }
+ }
+
+ if (rg.pos >= 0)
+ {
+ if (rg.length > gap.length)
+ gap= rg;
+ }
+ }
+
+ // 3. Convert binary data to string.
+
+ char *p= str;
+
+ for (int i = 0; i < IN6_ADDR_NUM_WORDS; ++i)
+ {
+ if (i == gap.pos)
+ {
+ // We're at the gap position. We should put trailing ':' and jump to
+ // the end of the gap.
+
+ if (i == 0)
+ {
+ // The gap starts from the beginning of the data -- leading ':'
+ // should be put additionally.
+
+ *p= ':';
+ ++p;
+ }
+
+ *p= ':';
+ ++p;
+
+ i += gap.length - 1;
+ }
+ else if (i == 6 && gap.pos == 0 &&
+ (gap.length == 6 || // IPv4-compatible
+ (gap.length == 5 && ipv6_words[5] == 0xffff) // IPv4-mapped
+ ))
+ {
+ // The data represents either IPv4-compatible or IPv4-mapped address.
+ // The IPv6-part (zeros or zeros + ffff) has been already put into
+ // the string (str). Now it's time to dump IPv4-part.
+
+ ipv4_to_str((const in_addr *) (ipv6_bytes + 12), p);
+ return;
+ }
+ else
+ {
+ // Usual IPv6-address-field. Print it out using lower-case
+ // hex-letters without leading zeros (recommended IPv6-format).
+ //
+ // If it is not the last field, append closing ':'.
+
+ p += sprintf(p, "%x", ipv6_words[i]);
+
+ if (i != IN6_ADDR_NUM_WORDS - 1)
+ {
+ *p= ':';
+ ++p;
+ }
+ }
+ }
+
+ *p= 0;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Converts IP-address-string to IP-address-data.
+
+ @param arg IP-address-string.
+ @param [out] buffer Buffer to store IP-address-data.
+
+ @return Completion status.
+ @retval false Given string does not represent an IP-address.
+ @retval true The string has been converted sucessfully.
+*/
+
+bool Item_func_inet6_aton::calc_value(String *arg, String *buffer)
+{
+ // ipv4-string -> varbinary(4)
+ // ipv6-string -> varbinary(16)
+
+ in_addr ipv4_address;
+ in6_addr ipv6_address;
+
+ if (str_to_ipv4(arg->ptr(), arg->length(), &ipv4_address))
+ {
+ buffer->length(0);
+ buffer->append((char *) &ipv4_address, sizeof (in_addr), &my_charset_bin);
+
+ return true;
+ }
+
+ if (str_to_ipv6(arg->ptr(), arg->length(), &ipv6_address))
+ {
+ buffer->length(0);
+ buffer->append((char *) &ipv6_address, sizeof (in6_addr), &my_charset_bin);
+
+ return true;
+ }
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Converts IP-address-data to IP-address-string.
+
+ @param arg IP-address-data.
+ @param [out] buffer Buffer to store IP-address-string.
+
+ @return Completion status.
+ @retval false The argument does not correspond to IP-address.
+ @retval true The string has been converted sucessfully.
+*/
+
+bool Item_func_inet6_ntoa::calc_value(String *arg, String *buffer)
+{
+ if (arg->charset() != &my_charset_bin)
+ return false;
+
+ if ((int) arg->length() == IN_ADDR_SIZE)
+ {
+ char str[INET_ADDRSTRLEN];
+
+ ipv4_to_str((const in_addr *) arg->ptr(), str);
+
+ buffer->length(0);
+ buffer->append(str, (uint32) strlen(str), &my_charset_latin1);
+
+ return true;
+ }
+ else if ((int) arg->length() == IN6_ADDR_SIZE)
+ {
+ char str[INET6_ADDRSTRLEN];
+
+ ipv6_to_str((const in6_addr *) arg->ptr(), str);
+
+ buffer->length(0);
+ buffer->append(str, (uint32) strlen(str), &my_charset_latin1);
+
+ return true;
+ }
+
+ DBUG_PRINT("info",
+ ("INET6_NTOA(): varbinary(4) or varbinary(16) expected."));
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Checks if the passed string represents an IPv4-address.
+
+ @param arg The string to check.
+
+ @return Check status.
+ @retval false The passed string does not represent an IPv4-address.
+ @retval true The passed string represents an IPv4-address.
+*/
+
+bool Item_func_is_ipv4::calc_value(const String *arg)
+{
+ in_addr ipv4_address;
+
+ return str_to_ipv4(arg->ptr(), arg->length(), &ipv4_address);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Checks if the passed string represents an IPv6-address.
+
+ @param arg The string to check.
+
+ @return Check status.
+ @retval false The passed string does not represent an IPv6-address.
+ @retval true The passed string represents an IPv6-address.
+*/
+
+bool Item_func_is_ipv6::calc_value(const String *arg)
+{
+ in6_addr ipv6_address;
+
+ return str_to_ipv6(arg->ptr(), arg->length(), &ipv6_address);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Checks if the passed IPv6-address is an IPv4-compat IPv6-address.
+
+ @param arg The IPv6-address to check.
+
+ @return Check status.
+ @retval false The passed IPv6-address is not an IPv4-compatible IPv6-address.
+ @retval true The passed IPv6-address is an IPv4-compatible IPv6-address.
+*/
+
+bool Item_func_is_ipv4_compat::calc_value(const String *arg)
+{
+ if ((int) arg->length() != IN6_ADDR_SIZE || arg->charset() != &my_charset_bin)
+ return false;
+
+ return IN6_IS_ADDR_V4COMPAT((struct in6_addr *) arg->ptr());
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Checks if the passed IPv6-address is an IPv4-mapped IPv6-address.
+
+ @param arg The IPv6-address to check.
+
+ @return Check status.
+ @retval false The passed IPv6-address is not an IPv4-mapped IPv6-address.
+ @retval true The passed IPv6-address is an IPv4-mapped IPv6-address.
+*/
+
+bool Item_func_is_ipv4_mapped::calc_value(const String *arg)
+{
+ if ((int) arg->length() != IN6_ADDR_SIZE || arg->charset() != &my_charset_bin)
+ return false;
+
+ return IN6_IS_ADDR_V4MAPPED((struct in6_addr *) arg->ptr());
+}
diff --git a/sql/item_inetfunc.h b/sql/item_inetfunc.h
new file mode 100644
index 00000000000..3a85d367ff1
--- /dev/null
+++ b/sql/item_inetfunc.h
@@ -0,0 +1,244 @@
+#ifndef ITEM_INETFUNC_INCLUDED
+#define ITEM_INETFUNC_INCLUDED
+
+/* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2014 MariaDB Foundation
+
+ 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 "item.h"
+
+/*************************************************************************
+ Item_func_inet_aton implements INET_ATON() SQL-function.
+*************************************************************************/
+
+class Item_func_inet_aton : public Item_int_func
+{
+public:
+ Item_func_inet_aton(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "inet_aton"; }
+ void fix_length_and_dec()
+ {
+ decimals= 0;
+ max_length= 21;
+ maybe_null= 1;
+ unsigned_flag= 1;
+ }
+};
+
+
+/*************************************************************************
+ Item_func_inet_ntoa implements INET_NTOA() SQL-function.
+*************************************************************************/
+
+class Item_func_inet_ntoa : public Item_str_func
+{
+public:
+ Item_func_inet_ntoa(Item *a)
+ : Item_str_func(a)
+ { }
+ String* val_str(String* str);
+ const char *func_name() const { return "inet_ntoa"; }
+ void fix_length_and_dec()
+ {
+ decimals= 0;
+ fix_length_and_charset(3 * 8 + 7, default_charset());
+ maybe_null= 1;
+ }
+};
+
+
+/*************************************************************************
+ Item_func_inet_bool_base implements common code for INET6/IP-related
+ functions returning boolean value.
+*************************************************************************/
+
+class Item_func_inet_bool_base : public Item_bool_func
+{
+public:
+ inline Item_func_inet_bool_base(Item *ip_addr)
+ : Item_bool_func(ip_addr)
+ {
+ null_value= false;
+ }
+
+public:
+ virtual longlong val_int();
+
+protected:
+ virtual bool calc_value(const String *arg) = 0;
+};
+
+
+/*************************************************************************
+ Item_func_inet_str_base implements common code for INET6/IP-related
+ functions returning string value.
+*************************************************************************/
+
+class Item_func_inet_str_base : public Item_str_ascii_func
+{
+public:
+ inline Item_func_inet_str_base(Item *arg)
+ : Item_str_ascii_func(arg)
+ { }
+
+public:
+ virtual String *val_str_ascii(String *buffer);
+
+protected:
+ virtual bool calc_value(String *arg, String *buffer) = 0;
+};
+
+
+/*************************************************************************
+ Item_func_inet6_aton implements INET6_ATON() SQL-function.
+*************************************************************************/
+
+class Item_func_inet6_aton : public Item_func_inet_str_base
+{
+public:
+ inline Item_func_inet6_aton(Item *ip_addr)
+ : Item_func_inet_str_base(ip_addr)
+ { }
+
+public:
+ virtual const char *func_name() const
+ { return "inet6_aton"; }
+
+ virtual void fix_length_and_dec()
+ {
+ decimals= 0;
+ fix_length_and_charset(16, &my_charset_bin);
+ maybe_null= 1;
+ }
+
+protected:
+ virtual bool calc_value(String *arg, String *buffer);
+};
+
+
+/*************************************************************************
+ Item_func_inet6_ntoa implements INET6_NTOA() SQL-function.
+*************************************************************************/
+
+class Item_func_inet6_ntoa : public Item_func_inet_str_base
+{
+public:
+ inline Item_func_inet6_ntoa(Item *ip_addr)
+ : Item_func_inet_str_base(ip_addr)
+ { }
+
+public:
+ virtual const char *func_name() const
+ { return "inet6_ntoa"; }
+
+ virtual void fix_length_and_dec()
+ {
+ decimals= 0;
+
+ // max length: IPv6-address -- 16 bytes
+ // 16 bytes / 2 bytes per group == 8 groups => 7 delimiter
+ // 4 symbols per group
+ fix_length_and_charset(8 * 4 + 7, default_charset());
+
+ maybe_null= 1;
+ }
+
+protected:
+ virtual bool calc_value(String *arg, String *buffer);
+};
+
+
+/*************************************************************************
+ Item_func_is_ipv4 implements IS_IPV4() SQL-function.
+*************************************************************************/
+
+class Item_func_is_ipv4 : public Item_func_inet_bool_base
+{
+public:
+ inline Item_func_is_ipv4(Item *ip_addr)
+ : Item_func_inet_bool_base(ip_addr)
+ { }
+
+public:
+ virtual const char *func_name() const
+ { return "is_ipv4"; }
+
+protected:
+ virtual bool calc_value(const String *arg);
+};
+
+
+/*************************************************************************
+ Item_func_is_ipv6 implements IS_IPV6() SQL-function.
+*************************************************************************/
+
+class Item_func_is_ipv6 : public Item_func_inet_bool_base
+{
+public:
+ inline Item_func_is_ipv6(Item *ip_addr)
+ : Item_func_inet_bool_base(ip_addr)
+ { }
+
+public:
+ virtual const char *func_name() const
+ { return "is_ipv6"; }
+
+protected:
+ virtual bool calc_value(const String *arg);
+};
+
+
+/*************************************************************************
+ Item_func_is_ipv4_compat implements IS_IPV4_COMPAT() SQL-function.
+*************************************************************************/
+
+class Item_func_is_ipv4_compat : public Item_func_inet_bool_base
+{
+public:
+ inline Item_func_is_ipv4_compat(Item *ip_addr)
+ : Item_func_inet_bool_base(ip_addr)
+ { }
+
+public:
+ virtual const char *func_name() const
+ { return "is_ipv4_compat"; }
+
+protected:
+ virtual bool calc_value(const String *arg);
+};
+
+
+/*************************************************************************
+ Item_func_is_ipv4_mapped implements IS_IPV4_MAPPED() SQL-function.
+*************************************************************************/
+
+class Item_func_is_ipv4_mapped : public Item_func_inet_bool_base
+{
+public:
+ inline Item_func_is_ipv4_mapped(Item *ip_addr)
+ : Item_func_inet_bool_base(ip_addr)
+ { }
+
+public:
+ virtual const char *func_name() const
+ { return "is_ipv4_mapped"; }
+
+protected:
+ virtual bool calc_value(const String *arg);
+};
+
+#endif // ITEM_INETFUNC_INCLUDED
diff --git a/sql/item_row.cc b/sql/item_row.cc
new file mode 100644
index 00000000000..3548a6b9b75
--- /dev/null
+++ b/sql/item_row.cc
@@ -0,0 +1,234 @@
+/*
+ Copyright (c) 2002, 2011, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // THD, set_var.h: THD
+#include "set_var.h"
+
+/**
+ Row items used for comparing rows and IN operations on rows:
+
+ @verbatim
+ (a, b, c) > (10, 10, 30)
+ (a, b, c) = (select c, d, e, from t1 where x=12)
+ (a, b, c) IN ((1,2,2), (3,4,5), (6,7,8)
+ (a, b, c) IN (select c, d, e, from t1)
+ @endverbatim
+
+ @todo
+ think placing 2-3 component items in item (as it done for function
+*/
+
+Item_row::Item_row(List<Item> &arg):
+ Item(), used_tables_cache(0), not_null_tables_cache(0),
+ const_item_cache(1), with_null(0)
+{
+
+ //TODO: think placing 2-3 component items in item (as it done for function)
+ if ((arg_count= arg.elements))
+ items= (Item**) sql_alloc(sizeof(Item*)*arg_count);
+ else
+ items= 0;
+ List_iterator_fast<Item> li(arg);
+ uint i= 0;
+ Item *item;
+ while ((item= li++))
+ {
+ items[i]= item;
+ i++;
+ }
+}
+
+void Item_row::illegal_method_call(const char *method)
+{
+ DBUG_ENTER("Item_row::illegal_method_call");
+ DBUG_PRINT("error", ("!!! %s method was called for row item", method));
+ DBUG_ASSERT(0);
+ my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
+ DBUG_VOID_RETURN;
+}
+
+bool Item_row::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+ null_value= 0;
+ maybe_null= 0;
+ Item **arg, **arg_end;
+ for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++)
+ {
+ if (!(*arg)->fixed &&
+ (*arg)->fix_fields(thd, arg))
+ return TRUE;
+ // we can't assign 'item' before, because fix_fields() can change arg
+ Item *item= *arg;
+ used_tables_cache |= item->used_tables();
+ const_item_cache&= item->const_item() && !with_null;
+ not_null_tables_cache|= item->not_null_tables();
+
+ if (const_item_cache)
+ {
+ if (item->cols() > 1)
+ with_null|= item->null_inside();
+ else
+ {
+ if (item->is_null())
+ with_null|= 1;
+ }
+ }
+ maybe_null|= item->maybe_null;
+ with_sum_func= with_sum_func || item->with_sum_func;
+ with_field= with_field || item->with_field;
+ with_subselect|= item->with_subselect;
+ }
+ fixed= 1;
+ return FALSE;
+}
+
+
+bool
+Item_row::eval_not_null_tables(uchar *opt_arg)
+{
+ Item **arg,**arg_end;
+ not_null_tables_cache= 0;
+ if (arg_count)
+ {
+ for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++)
+ {
+ not_null_tables_cache|= (*arg)->not_null_tables();
+ }
+ }
+ return FALSE;
+}
+
+
+void Item_row::cleanup()
+{
+ DBUG_ENTER("Item_row::cleanup");
+
+ Item::cleanup();
+ /* Reset to the original values */
+ used_tables_cache= 0;
+ const_item_cache= 1;
+ with_null= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_row::split_sum_func(THD *thd, Item **ref_pointer_array,
+ List<Item> &fields)
+{
+ Item **arg, **arg_end;
+ for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++)
+ (*arg)->split_sum_func2(thd, ref_pointer_array, fields, arg, TRUE);
+}
+
+
+void Item_row::update_used_tables()
+{
+ used_tables_cache= 0;
+ const_item_cache= 1;
+ for (uint i= 0; i < arg_count; i++)
+ {
+ items[i]->update_used_tables();
+ used_tables_cache|= items[i]->used_tables();
+ const_item_cache&= items[i]->const_item();
+ }
+}
+
+
+void Item_row::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ used_tables_cache= 0;
+ const_item_cache= 1;
+ not_null_tables_cache= 0;
+ for (uint i= 0; i < arg_count; i++)
+ {
+ items[i]->fix_after_pullout(new_parent, &items[i]);
+ used_tables_cache|= items[i]->used_tables();
+ const_item_cache&= items[i]->const_item();
+ not_null_tables_cache|= items[i]->not_null_tables();
+ }
+}
+
+
+bool Item_row::check_cols(uint c)
+{
+ if (c != arg_count)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), c);
+ return 1;
+ }
+ return 0;
+}
+
+void Item_row::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ for (uint i= 0; i < arg_count; i++)
+ {
+ if (i)
+ str->append(',');
+ items[i]->print(str, query_type);
+ }
+ str->append(')');
+}
+
+
+bool Item_row::walk(Item_processor processor, bool walk_subquery, uchar *arg)
+{
+ for (uint i= 0; i < arg_count; i++)
+ {
+ if (items[i]->walk(processor, walk_subquery, arg))
+ return 1;
+ }
+ return (this->*processor)(arg);
+}
+
+
+Item *Item_row::transform(Item_transformer transformer, uchar *arg)
+{
+ DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
+
+ for (uint i= 0; i < arg_count; i++)
+ {
+ Item *new_item= items[i]->transform(transformer, arg);
+ if (!new_item)
+ return 0;
+
+ /*
+ THD::change_item_tree() should be called only if the tree was
+ really transformed, i.e. when a new item has been created.
+ Otherwise we'll be allocating a lot of unnecessary memory for
+ change records at each execution.
+ */
+ if (items[i] != new_item)
+ current_thd->change_item_tree(&items[i], new_item);
+ }
+ return (this->*transformer)(arg);
+}
+
+void Item_row::bring_value()
+{
+ for (uint i= 0; i < arg_count; i++)
+ items[i]->bring_value();
+}
diff --git a/sql/item_row.h b/sql/item_row.h
new file mode 100644
index 00000000000..aa56068f8ba
--- /dev/null
+++ b/sql/item_row.h
@@ -0,0 +1,91 @@
+#ifndef ITEM_ROW_INCLUDED
+#define ITEM_ROW_INCLUDED
+
+/*
+ Copyright (c) 2002, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+class Item_row: public Item
+{
+ Item **items;
+ table_map used_tables_cache, not_null_tables_cache;
+ uint arg_count;
+ bool const_item_cache;
+ bool with_null;
+public:
+ Item_row(List<Item> &);
+ Item_row(Item_row *item):
+ Item(),
+ items(item->items),
+ used_tables_cache(item->used_tables_cache),
+ not_null_tables_cache(0),
+ arg_count(item->arg_count),
+ const_item_cache(item->const_item_cache),
+ with_null(0)
+ {}
+
+ enum Type type() const { return ROW_ITEM; };
+ void illegal_method_call(const char *);
+ bool is_null() { return null_value; }
+ void make_field(Send_field *)
+ {
+ illegal_method_call((const char*)"make_field");
+ };
+ double val_real()
+ {
+ illegal_method_call((const char*)"val");
+ return 0;
+ };
+ longlong val_int()
+ {
+ illegal_method_call((const char*)"val_int");
+ return 0;
+ };
+ String *val_str(String *)
+ {
+ illegal_method_call((const char*)"val_str");
+ return 0;
+ };
+ my_decimal *val_decimal(my_decimal *)
+ {
+ illegal_method_call((const char*)"val_decimal");
+ return 0;
+ };
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ void cleanup();
+ void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields);
+ table_map used_tables() const { return used_tables_cache; };
+ bool const_item() const { return const_item_cache; };
+ enum Item_result result_type() const { return ROW_RESULT; }
+ Item_result cmp_type() const { return ROW_RESULT; }
+ void update_used_tables();
+ table_map not_null_tables() const { return not_null_tables_cache; }
+ virtual void print(String *str, enum_query_type query_type);
+
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg);
+ Item *transform(Item_transformer transformer, uchar *arg);
+ bool eval_not_null_tables(uchar *opt_arg);
+
+ uint cols() { return arg_count; }
+ Item* element_index(uint i) { return items[i]; }
+ Item** addr(uint i) { return items + i; }
+ bool check_cols(uint c);
+ bool null_inside() { return with_null; };
+ void bring_value();
+ bool check_vcol_func_processor(uchar *int_arg) {return FALSE; }
+};
+
+#endif /* ITEM_ROW_INCLUDED */
diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc
new file mode 100644
index 00000000000..32a19341895
--- /dev/null
+++ b/sql/item_strfunc.cc
@@ -0,0 +1,5185 @@
+/*
+ Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab.
+
+ 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
+*/
+
+/**
+ @file
+
+ @brief
+ This file defines all string functions
+
+ @warning
+ Some string functions don't always put and end-null on a String.
+ (This shouldn't be needed)
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h> // HAVE_*
+
+/* May include caustic 3rd-party defs. Use early, so it can override nothing */
+#include "sha2.h"
+
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h"
+#include "sql_base.h"
+#include "sql_time.h"
+#include "sql_acl.h" // SUPER_ACL
+#include "des_key_file.h" // st_des_keyschedule, st_des_keyblock
+#include "password.h" // my_make_scrambled_password,
+ // my_make_scrambled_password_323
+#include <m_ctype.h>
+#include <base64.h>
+#include <my_md5.h>
+#include "sha1.h"
+#include "my_aes.h"
+#include <zlib.h>
+C_MODE_START
+#include "../mysys/my_static.h" // For soundex_map
+C_MODE_END
+#include "sql_show.h" // append_identifier
+#include <sql_repl.h>
+#include "sql_statistics.h"
+
+size_t username_char_length= 80;
+
+/*
+ For the Items which have only val_str_ascii() method
+ and don't have their own "native" val_str(),
+ we provide a "wrapper" method to convert from ASCII
+ to Item character set when it's necessary.
+ Conversion happens only in case of "tricky" Item character set (e.g. UCS2).
+ Normally conversion does not happen, and val_str_ascii() is immediately
+ returned instead.
+*/
+String *Item_func::val_str_from_val_str_ascii(String *str, String *str2)
+{
+ DBUG_ASSERT(fixed == 1);
+
+ if (!(collation.collation->state & MY_CS_NONASCII))
+ {
+ String *res= val_str_ascii(str);
+ if (res)
+ res->set_charset(collation.collation);
+ return res;
+ }
+
+ DBUG_ASSERT(str != str2);
+
+ uint errors;
+ String *res= val_str_ascii(str);
+ if (!res)
+ return 0;
+
+ if ((null_value= str2->copy(res->ptr(), res->length(),
+ &my_charset_latin1, collation.collation,
+ &errors)))
+ return 0;
+
+ return str2;
+}
+
+
+bool Item_str_func::fix_fields(THD *thd, Item **ref)
+{
+ bool res= Item_func::fix_fields(thd, ref);
+ /*
+ In Item_str_func::check_well_formed_result() we may set null_value
+ flag on the same condition as in test() below.
+ */
+ maybe_null= maybe_null || thd->is_strict_mode();
+ return res;
+}
+
+
+my_decimal *Item_str_func::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[64];
+ String *res, tmp(buff,sizeof(buff), &my_charset_bin);
+ res= val_str(&tmp);
+ if (!res)
+ return 0;
+ (void)str2my_decimal(E_DEC_FATAL_ERROR, (char*) res->ptr(),
+ res->length(), res->charset(), decimal_value);
+ return decimal_value;
+}
+
+
+double Item_str_func::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ int err_not_used;
+ char *end_not_used, buff[64];
+ String *res, tmp(buff,sizeof(buff), &my_charset_bin);
+ res= val_str(&tmp);
+ return res ? my_strntod(res->charset(), (char*) res->ptr(), res->length(),
+ &end_not_used, &err_not_used) : 0.0;
+}
+
+
+longlong Item_str_func::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ int err;
+ char buff[22];
+ String *res, tmp(buff,sizeof(buff), &my_charset_bin);
+ res= val_str(&tmp);
+ return (res ?
+ my_strntoll(res->charset(), res->ptr(), res->length(), 10, NULL,
+ &err) :
+ (longlong) 0);
+}
+
+
+String *Item_func_md5::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String * sptr= args[0]->val_str(str);
+ if (sptr)
+ {
+ uchar digest[16];
+
+ null_value=0;
+ compute_md5_hash((char *) digest, (const char *) sptr->ptr(),
+ sptr->length());
+ if (str->alloc(32)) // Ensure that memory is free
+ {
+ null_value=1;
+ return 0;
+ }
+ array_to_hex((char *) str->ptr(), digest, 16);
+ str->set_charset(&my_charset_numeric);
+ str->length((uint) 32);
+ return str;
+ }
+ null_value=1;
+ return 0;
+}
+
+
+/*
+ The MD5()/SHA() functions treat their parameter as being a case sensitive.
+ Thus we set binary collation on it so different instances of MD5() will be
+ compared properly.
+*/
+static CHARSET_INFO *get_checksum_charset(const char *csname)
+{
+ CHARSET_INFO *cs= get_charset_by_csname(csname, MY_CS_BINSORT, MYF(0));
+ if (!cs)
+ {
+ // Charset has no binary collation: use my_charset_bin.
+ cs= &my_charset_bin;
+ }
+ return cs;
+}
+
+
+void Item_func_md5::fix_length_and_dec()
+{
+ CHARSET_INFO *cs= get_checksum_charset(args[0]->collation.collation->csname);
+ args[0]->collation.set(cs, DERIVATION_COERCIBLE);
+ fix_length_and_charset(32, default_charset());
+}
+
+
+String *Item_func_sha::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String * sptr= args[0]->val_str(str);
+ if (sptr) /* If we got value different from NULL */
+ {
+ /* Temporary buffer to store 160bit digest */
+ uint8 digest[SHA1_HASH_SIZE];
+ compute_sha1_hash(digest, (const char *) sptr->ptr(), sptr->length());
+ /* Ensure that memory is free and we got result */
+ if (!str->alloc(SHA1_HASH_SIZE*2))
+ {
+ array_to_hex((char *) str->ptr(), digest, SHA1_HASH_SIZE);
+ str->set_charset(&my_charset_numeric);
+ str->length((uint) SHA1_HASH_SIZE*2);
+ null_value=0;
+ return str;
+ }
+ }
+ null_value=1;
+ return 0;
+}
+
+void Item_func_sha::fix_length_and_dec()
+{
+ CHARSET_INFO *cs= get_checksum_charset(args[0]->collation.collation->csname);
+ args[0]->collation.set(cs, DERIVATION_COERCIBLE);
+ // size of hex representation of hash
+ fix_length_and_charset(SHA1_HASH_SIZE * 2, default_charset());
+}
+
+String *Item_func_sha2::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+ unsigned char digest_buf[SHA512_DIGEST_LENGTH];
+ String *input_string;
+ unsigned char *input_ptr;
+ size_t input_len;
+ uint digest_length= 0;
+
+ str->set_charset(&my_charset_bin);
+
+ input_string= args[0]->val_str(str);
+ if (input_string == NULL)
+ {
+ null_value= TRUE;
+ return (String *) NULL;
+ }
+
+ null_value= args[0]->null_value;
+ if (null_value)
+ return (String *) NULL;
+
+ input_ptr= (unsigned char *) input_string->ptr();
+ input_len= input_string->length();
+
+ switch ((uint) args[1]->val_int()) {
+#ifndef OPENSSL_NO_SHA512
+ case 512:
+ digest_length= SHA512_DIGEST_LENGTH;
+ (void) SHA512(input_ptr, input_len, digest_buf);
+ break;
+ case 384:
+ digest_length= SHA384_DIGEST_LENGTH;
+ (void) SHA384(input_ptr, input_len, digest_buf);
+ break;
+#endif
+#ifndef OPENSSL_NO_SHA256
+ case 224:
+ digest_length= SHA224_DIGEST_LENGTH;
+ (void) SHA224(input_ptr, input_len, digest_buf);
+ break;
+ case 256:
+ case 0: // SHA-256 is the default
+ digest_length= SHA256_DIGEST_LENGTH;
+ (void) SHA256(input_ptr, input_len, digest_buf);
+ break;
+#endif
+ default:
+ if (!args[1]->const_item())
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_PARAMETERS_TO_NATIVE_FCT,
+ ER(ER_WRONG_PARAMETERS_TO_NATIVE_FCT), "sha2");
+ null_value= TRUE;
+ return NULL;
+ }
+
+ /*
+ Since we're subverting the usual String methods, we must make sure that
+ the destination has space for the bytes we're about to write.
+ */
+ str->realloc((uint) digest_length*2 + 1); /* Each byte as two nybbles */
+
+ /* Convert the large number to a string-hex representation. */
+ array_to_hex((char *) str->ptr(), digest_buf, digest_length);
+
+ /* We poked raw bytes in. We must inform the the String of its length. */
+ str->length((uint) digest_length*2); /* Each byte as two nybbles */
+
+ null_value= FALSE;
+ return str;
+
+#else
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_FEATURE_DISABLED,
+ ER(ER_FEATURE_DISABLED),
+ "sha2", "--with-ssl");
+ null_value= TRUE;
+ return (String *) NULL;
+#endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */
+}
+
+
+void Item_func_sha2::fix_length_and_dec()
+{
+ maybe_null= 1;
+ max_length = 0;
+
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+ int sha_variant= args[1]->const_item() ? args[1]->val_int() : 512;
+
+ switch (sha_variant) {
+#ifndef OPENSSL_NO_SHA512
+ case 512:
+ fix_length_and_charset(SHA512_DIGEST_LENGTH * 2, default_charset());
+ break;
+ case 384:
+ fix_length_and_charset(SHA384_DIGEST_LENGTH * 2, default_charset());
+ break;
+#endif
+#ifndef OPENSSL_NO_SHA256
+ case 256:
+ case 0: // SHA-256 is the default
+ fix_length_and_charset(SHA256_DIGEST_LENGTH * 2, default_charset());
+ break;
+ case 224:
+ fix_length_and_charset(SHA224_DIGEST_LENGTH * 2, default_charset());
+ break;
+#endif
+ default:
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_PARAMETERS_TO_NATIVE_FCT,
+ ER(ER_WRONG_PARAMETERS_TO_NATIVE_FCT), "sha2");
+ }
+
+ CHARSET_INFO *cs= get_checksum_charset(args[0]->collation.collation->csname);
+ args[0]->collation.set(cs, DERIVATION_COERCIBLE);
+
+#else
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_FEATURE_DISABLED,
+ ER(ER_FEATURE_DISABLED),
+ "sha2", "--with-ssl");
+#endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */
+}
+
+/* Implementation of AES encryption routines */
+
+String *Item_func_aes_encrypt::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char key_buff[80];
+ String tmp_key_value(key_buff, sizeof(key_buff), system_charset_info);
+ String *sptr= args[0]->val_str(str); // String to encrypt
+ String *key= args[1]->val_str(&tmp_key_value); // key
+ int aes_length;
+ if (sptr && key) // we need both arguments to be not NULL
+ {
+ null_value=0;
+ aes_length=my_aes_get_size(sptr->length()); // Calculate result length
+
+ if (!str_value.alloc(aes_length)) // Ensure that memory is free
+ {
+ // finally encrypt directly to allocated buffer.
+ if (my_aes_encrypt(sptr->ptr(),sptr->length(), (char*) str_value.ptr(),
+ key->ptr(), key->length()) == aes_length)
+ {
+ // We got the expected result length
+ str_value.length((uint) aes_length);
+ return &str_value;
+ }
+ }
+ }
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_aes_encrypt::fix_length_and_dec()
+{
+ max_length=my_aes_get_size(args[0]->max_length);
+}
+
+
+String *Item_func_aes_decrypt::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char key_buff[80];
+ String tmp_key_value(key_buff, sizeof(key_buff), system_charset_info);
+ String *sptr, *key;
+ DBUG_ENTER("Item_func_aes_decrypt::val_str");
+
+ sptr= args[0]->val_str(str); // String to decrypt
+ key= args[1]->val_str(&tmp_key_value); // Key
+ if (sptr && key) // Need to have both arguments not NULL
+ {
+ null_value=0;
+ if (!str_value.alloc(sptr->length())) // Ensure that memory is free
+ {
+ // finally decrypt directly to allocated buffer.
+ int length;
+ length=my_aes_decrypt(sptr->ptr(), sptr->length(),
+ (char*) str_value.ptr(),
+ key->ptr(), key->length());
+ if (length >= 0) // if we got correct data data
+ {
+ str_value.length((uint) length);
+ DBUG_RETURN(&str_value);
+ }
+ }
+ }
+ // Bad parameters. No memory or bad data will all go here
+ null_value=1;
+ DBUG_RETURN(0);
+}
+
+
+void Item_func_aes_decrypt::fix_length_and_dec()
+{
+ max_length=args[0]->max_length;
+ maybe_null= 1;
+}
+
+
+void Item_func_to_base64::fix_length_and_dec()
+{
+ maybe_null= args[0]->maybe_null;
+ collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII);
+ if (args[0]->max_length > (uint) base64_encode_max_arg_length())
+ {
+ maybe_null= 1;
+ fix_char_length_ulonglong((ulonglong) base64_encode_max_arg_length());
+ }
+ else
+ {
+ int length= base64_needed_encoded_length((int) args[0]->max_length);
+ DBUG_ASSERT(length > 0);
+ fix_char_length_ulonglong((ulonglong) length - 1);
+ }
+}
+
+
+String *Item_func_to_base64::val_str_ascii(String *str)
+{
+ String *res= args[0]->val_str(str);
+ bool too_long= false;
+ int length;
+ if (!res ||
+ res->length() > (uint) base64_encode_max_arg_length() ||
+ (too_long=
+ ((uint) (length= base64_needed_encoded_length((int) res->length())) >
+ current_thd->variables.max_allowed_packet)) ||
+ tmp_value.alloc((uint) length))
+ {
+ null_value= 1; // NULL input, too long input, or OOM.
+ if (too_long)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
+ current_thd->variables.max_allowed_packet);
+ }
+ return 0;
+ }
+ base64_encode(res->ptr(), (int) res->length(), (char*) tmp_value.ptr());
+ DBUG_ASSERT(length > 0);
+ tmp_value.length((uint) length - 1); // Without trailing '\0'
+ null_value= 0;
+ return &tmp_value;
+}
+
+
+void Item_func_from_base64::fix_length_and_dec()
+{
+ if (args[0]->max_length > (uint) base64_decode_max_arg_length())
+ {
+ fix_char_length_ulonglong((ulonglong) base64_decode_max_arg_length());
+ }
+ else
+ {
+ int length= base64_needed_decoded_length((int) args[0]->max_length);
+ fix_char_length_ulonglong((ulonglong) length);
+ }
+ maybe_null= 1; // Can be NULL, e.g. in case of badly formed input string
+}
+
+
+String *Item_func_from_base64::val_str(String *str)
+{
+ String *res= args[0]->val_str_ascii(str);
+ int length;
+ const char *end_ptr;
+
+ if (!res)
+ goto err;
+
+ if (res->length() > (uint) base64_decode_max_arg_length() ||
+ ((uint) (length= base64_needed_decoded_length((int) res->length())) >
+ current_thd->variables.max_allowed_packet))
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
+ current_thd->variables.max_allowed_packet);
+ goto err;
+ }
+
+ if (tmp_value.alloc((uint) length))
+ goto err;
+
+ if ((length= base64_decode(res->ptr(), (int) res->length(),
+ (char *) tmp_value.ptr(), &end_ptr, 0)) < 0 ||
+ end_ptr < res->ptr() + res->length())
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BAD_BASE64_DATA, ER(ER_BAD_BASE64_DATA),
+ end_ptr - res->ptr());
+ goto err;
+ }
+
+ tmp_value.length((uint) length);
+ null_value= 0;
+ return &tmp_value;
+err:
+ null_value= 1; // NULL input, too long input, OOM, or badly formed input
+ return 0;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+
+const char *histogram_types[] =
+ {"SINGLE_PREC_HB", "DOUBLE_PREC_HB", 0};
+static TYPELIB hystorgam_types_typelib=
+ { array_elements(histogram_types),
+ "histogram_types",
+ histogram_types, NULL};
+const char *representation_by_type[]= {"%.3f", "%.5f"};
+
+String *Item_func_decode_histogram::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String *res, tmp(buff, sizeof(buff), &my_charset_bin);
+ int type;
+
+ tmp.length(0);
+ if (!(res= args[0]->val_str(&tmp)) ||
+ (type= find_type(res->c_ptr_safe(),
+ &hystorgam_types_typelib, MYF(0))) <= 0)
+ {
+ null_value= 1;
+ return 0;
+ }
+ type--;
+
+ tmp.length(0);
+ if (!(res= args[1]->val_str(&tmp)))
+ {
+ null_value= 1;
+ return 0;
+ }
+ if (type == DOUBLE_PREC_HB && res->length() % 2 != 0)
+ res->length(res->length() - 1); // one byte is unused
+
+ double prev= 0.0;
+ uint i;
+ str->length(0);
+ char numbuf[32];
+ const uchar *p= (uchar*)res->c_ptr();
+ for (i= 0; i < res->length(); i++)
+ {
+ double val;
+ switch (type)
+ {
+ case SINGLE_PREC_HB:
+ val= p[i] / ((double)((1 << 8) - 1));
+ break;
+ case DOUBLE_PREC_HB:
+ val= uint2korr(p + i) / ((double)((1 << 16) - 1));
+ i++;
+ break;
+ default:
+ val= 0;
+ DBUG_ASSERT(0);
+ }
+ /* show delta with previous value */
+ int size= my_snprintf(numbuf, sizeof(numbuf),
+ representation_by_type[type], val - prev);
+ str->append(numbuf, size);
+ str->append(",");
+ prev= val;
+ }
+ /* show delta with max */
+ int size= my_snprintf(numbuf, sizeof(numbuf),
+ representation_by_type[type], 1.0 - prev);
+ str->append(numbuf, size);
+
+ null_value=0;
+ return str;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ Concatenate args with the following premises:
+ If only one arg (which is ok), return value of arg;
+ Don't reallocate val_str() if not absolute necessary.
+*/
+
+String *Item_func_concat::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res,*res2,*use_as_buff;
+ uint i;
+ bool is_const= 0;
+
+ null_value=0;
+ if (!(res=args[0]->val_str(str)))
+ goto null;
+ use_as_buff= &tmp_value;
+ /* Item_subselect in --ps-protocol mode will state it as a non-const */
+ is_const= args[0]->const_item() || !args[0]->used_tables();
+ for (i=1 ; i < arg_count ; i++)
+ {
+ if (res->length() == 0)
+ {
+ if (!(res=args[i]->val_str(str)))
+ goto null;
+ /*
+ CONCAT accumulates its result in the result of its the first
+ non-empty argument. Because of this we need is_const to be
+ evaluated only for it.
+ */
+ is_const= args[i]->const_item() || !args[i]->used_tables();
+ }
+ else
+ {
+ if (!(res2=args[i]->val_str(use_as_buff)))
+ goto null;
+ if (res2->length() == 0)
+ continue;
+ if (res->length()+res2->length() >
+ current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
+ current_thd->variables.max_allowed_packet);
+ goto null;
+ }
+ if (!is_const && res->alloced_length() >= res->length()+res2->length())
+ { // Use old buffer
+ res->append(*res2);
+ }
+ else if (str->alloced_length() >= res->length()+res2->length())
+ {
+ if (str->ptr() == res2->ptr())
+ str->replace(0,0,*res);
+ else
+ {
+ str->copy(*res);
+ str->append(*res2);
+ }
+ res= str;
+ use_as_buff= &tmp_value;
+ }
+ else if (res == &tmp_value)
+ {
+ if (res->append(*res2)) // Must be a blob
+ goto null;
+ }
+ else if (res2 == &tmp_value)
+ { // This can happend only 1 time
+ if (tmp_value.replace(0,0,*res))
+ goto null;
+ res= &tmp_value;
+ use_as_buff=str; // Put next arg here
+ }
+ else if (tmp_value.is_alloced() && res2->ptr() >= tmp_value.ptr() &&
+ res2->ptr() <= tmp_value.ptr() + tmp_value.alloced_length())
+ {
+ /*
+ This happens really seldom:
+ In this case res2 is sub string of tmp_value. We will
+ now work in place in tmp_value to set it to res | res2
+ */
+ /* Chop the last characters in tmp_value that isn't in res2 */
+ tmp_value.length((uint32) (res2->ptr() - tmp_value.ptr()) +
+ res2->length());
+ /* Place res2 at start of tmp_value, remove chars before res2 */
+ if (tmp_value.replace(0,(uint32) (res2->ptr() - tmp_value.ptr()),
+ *res))
+ goto null;
+ res= &tmp_value;
+ use_as_buff=str; // Put next arg here
+ }
+ else
+ { // Two big const strings
+ /*
+ NOTE: We should be prudent in the initial allocation unit -- the
+ size of the arguments is a function of data distribution, which
+ can be any. Instead of overcommitting at the first row, we grow
+ the allocated amount by the factor of 2. This ensures that no
+ more than 25% of memory will be overcommitted on average.
+ */
+
+ uint concat_len= res->length() + res2->length();
+
+ if (tmp_value.alloced_length() < concat_len)
+ {
+ if (tmp_value.alloced_length() == 0)
+ {
+ if (tmp_value.alloc(concat_len))
+ goto null;
+ }
+ else
+ {
+ uint new_len = MY_MAX(tmp_value.alloced_length() * 2, concat_len);
+
+ if (tmp_value.realloc(new_len))
+ goto null;
+ }
+ }
+
+ if (tmp_value.copy(*res) || tmp_value.append(*res2))
+ goto null;
+
+ res= &tmp_value;
+ use_as_buff=str;
+ }
+ is_const= 0;
+ }
+ }
+ res->set_charset(collation.collation);
+ return res;
+
+null:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_concat::fix_length_and_dec()
+{
+ ulonglong char_length= 0;
+
+ if (agg_arg_charsets_for_string_result(collation, args, arg_count))
+ return;
+
+ for (uint i=0 ; i < arg_count ; i++)
+ char_length+= args[i]->max_char_length();
+
+ fix_char_length_ulonglong(char_length);
+}
+
+/**
+ @details
+ Function des_encrypt() by tonu@spam.ee & monty
+ Works only if compiled with OpenSSL library support.
+ @return
+ A binary string where first character is CHAR(128 | key-number).
+ If one uses a string key key_number is 127.
+ Encryption result is longer than original by formula:
+ @code new_length= org_length + (8-(org_length % 8))+1 @endcode
+*/
+
+String *Item_func_des_encrypt::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+ uint code= ER_WRONG_PARAMETERS_TO_PROCEDURE;
+ DES_cblock ivec;
+ struct st_des_keyblock keyblock;
+ struct st_des_keyschedule keyschedule;
+ const char *append_str="********";
+ uint key_number, res_length, tail;
+ String *res= args[0]->val_str(str);
+
+ if ((null_value= args[0]->null_value))
+ return 0; // ENCRYPT(NULL) == NULL
+ if ((res_length=res->length()) == 0)
+ return make_empty_result();
+ if (arg_count == 1)
+ {
+ /* Protect against someone doing FLUSH DES_KEY_FILE */
+ mysql_mutex_lock(&LOCK_des_key_file);
+ keyschedule= des_keyschedule[key_number=des_default_key];
+ mysql_mutex_unlock(&LOCK_des_key_file);
+ }
+ else if (args[1]->result_type() == INT_RESULT)
+ {
+ key_number= (uint) args[1]->val_int();
+ if (key_number > 9)
+ goto error;
+ mysql_mutex_lock(&LOCK_des_key_file);
+ keyschedule= des_keyschedule[key_number];
+ mysql_mutex_unlock(&LOCK_des_key_file);
+ }
+ else
+ {
+ String *keystr=args[1]->val_str(&tmp_value);
+ if (!keystr)
+ goto error;
+ key_number=127; // User key string
+
+ /* We make good 24-byte (168 bit) key from given plaintext key with MD5 */
+ bzero((char*) &ivec,sizeof(ivec));
+ EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL,
+ (uchar*) keystr->ptr(), (int) keystr->length(),
+ 1, (uchar*) &keyblock,ivec);
+ DES_set_key_unchecked(&keyblock.key1,&keyschedule.ks1);
+ DES_set_key_unchecked(&keyblock.key2,&keyschedule.ks2);
+ DES_set_key_unchecked(&keyblock.key3,&keyschedule.ks3);
+ }
+
+ /*
+ The problem: DES algorithm requires original data to be in 8-bytes
+ chunks. Missing bytes get filled with '*'s and result of encryption
+ can be up to 8 bytes longer than original string. When decrypted,
+ we do not know the size of original string :(
+ We add one byte with value 0x1..0x8 as the last byte of the padded
+ string marking change of string length.
+ */
+
+ tail= 8 - (res_length % 8); // 1..8 marking extra length
+ res_length+=tail;
+ if (tmp_arg.realloc(res_length))
+ goto error;
+ tmp_arg.length(0);
+ tmp_arg.append(res->ptr(), res->length());
+ code= ER_OUT_OF_RESOURCES;
+ if (tmp_arg.append(append_str, tail) || tmp_value.alloc(res_length+1))
+ goto error;
+ tmp_arg[res_length-1]=tail; // save extra length
+ tmp_value.realloc(res_length+1);
+ tmp_value.length(res_length+1);
+ tmp_value.set_charset(&my_charset_bin);
+ tmp_value[0]=(char) (128 | key_number);
+ // Real encryption
+ bzero((char*) &ivec,sizeof(ivec));
+ DES_ede3_cbc_encrypt((const uchar*) (tmp_arg.ptr()),
+ (uchar*) (tmp_value.ptr()+1),
+ res_length,
+ &keyschedule.ks1,
+ &keyschedule.ks2,
+ &keyschedule.ks3,
+ &ivec, TRUE);
+ return &tmp_value;
+
+error:
+ push_warning_printf(current_thd,Sql_condition::WARN_LEVEL_WARN,
+ code, ER(code),
+ "des_encrypt");
+#else
+ push_warning_printf(current_thd,Sql_condition::WARN_LEVEL_WARN,
+ ER_FEATURE_DISABLED, ER(ER_FEATURE_DISABLED),
+ "des_encrypt", "--with-ssl");
+#endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */
+ null_value=1;
+ return 0;
+}
+
+
+String *Item_func_des_decrypt::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+ uint code= ER_WRONG_PARAMETERS_TO_PROCEDURE;
+ DES_cblock ivec;
+ struct st_des_keyblock keyblock;
+ struct st_des_keyschedule keyschedule;
+ String *res= args[0]->val_str(str);
+ uint length,tail;
+
+ if ((null_value= args[0]->null_value))
+ return 0;
+ length= res->length();
+ if (length < 9 || (length % 8) != 1 || !((*res)[0] & 128))
+ return res; // Skip decryption if not encrypted
+
+ if (arg_count == 1) // If automatic uncompression
+ {
+ uint key_number=(uint) (*res)[0] & 127;
+ // Check if automatic key and that we have privilege to uncompress using it
+ if (!(current_thd->security_ctx->master_access & SUPER_ACL) ||
+ key_number > 9)
+ goto error;
+
+ mysql_mutex_lock(&LOCK_des_key_file);
+ keyschedule= des_keyschedule[key_number];
+ mysql_mutex_unlock(&LOCK_des_key_file);
+ }
+ else
+ {
+ // We make good 24-byte (168 bit) key from given plaintext key with MD5
+ String *keystr=args[1]->val_str(&tmp_value);
+ if (!keystr)
+ goto error;
+
+ bzero((char*) &ivec,sizeof(ivec));
+ EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL,
+ (uchar*) keystr->ptr(),(int) keystr->length(),
+ 1,(uchar*) &keyblock,ivec);
+ // Here we set all 64-bit keys (56 effective) one by one
+ DES_set_key_unchecked(&keyblock.key1,&keyschedule.ks1);
+ DES_set_key_unchecked(&keyblock.key2,&keyschedule.ks2);
+ DES_set_key_unchecked(&keyblock.key3,&keyschedule.ks3);
+ }
+ code= ER_OUT_OF_RESOURCES;
+ if (tmp_value.alloc(length-1))
+ goto error;
+
+ bzero((char*) &ivec,sizeof(ivec));
+ DES_ede3_cbc_encrypt((const uchar*) res->ptr()+1,
+ (uchar*) (tmp_value.ptr()),
+ length-1,
+ &keyschedule.ks1,
+ &keyschedule.ks2,
+ &keyschedule.ks3,
+ &ivec, FALSE);
+ /* Restore old length of key */
+ if ((tail=(uint) (uchar) tmp_value[length-2]) > 8)
+ goto wrong_key; // Wrong key
+ tmp_value.length(length-1-tail);
+ tmp_value.set_charset(&my_charset_bin);
+ return &tmp_value;
+
+error:
+ push_warning_printf(current_thd,Sql_condition::WARN_LEVEL_WARN,
+ code, ER(code),
+ "des_decrypt");
+wrong_key:
+#else
+ push_warning_printf(current_thd,Sql_condition::WARN_LEVEL_WARN,
+ ER_FEATURE_DISABLED, ER(ER_FEATURE_DISABLED),
+ "des_decrypt", "--with-ssl");
+#endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */
+ null_value=1;
+ return 0;
+}
+
+
+/**
+ concat with separator. First arg is the separator
+ concat_ws takes at least two arguments.
+*/
+
+String *Item_func_concat_ws::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char tmp_str_buff[10];
+ String tmp_sep_str(tmp_str_buff, sizeof(tmp_str_buff),default_charset_info),
+ *sep_str, *res, *res2,*use_as_buff;
+ uint i;
+ bool is_const= 0;
+
+ null_value=0;
+ if (!(sep_str= args[0]->val_str(&tmp_sep_str)))
+ goto null;
+
+ use_as_buff= &tmp_value;
+ str->length(0); // QQ; Should be removed
+ res=str; // If 0 arg_count
+
+ // Skip until non-null argument is found.
+ // If not, return the empty string
+ for (i=1; i < arg_count; i++)
+ if ((res= args[i]->val_str(str)))
+ {
+ is_const= args[i]->const_item() || !args[i]->used_tables();
+ break;
+ }
+
+ if (i == arg_count)
+ return make_empty_result();
+
+ for (i++; i < arg_count ; i++)
+ {
+ if (!(res2= args[i]->val_str(use_as_buff)))
+ continue; // Skip NULL
+
+ if (res->length() + sep_str->length() + res2->length() >
+ current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
+ current_thd->variables.max_allowed_packet);
+ goto null;
+ }
+ if (!is_const && res->alloced_length() >=
+ res->length() + sep_str->length() + res2->length())
+ { // Use old buffer
+ res->append(*sep_str); // res->length() > 0 always
+ res->append(*res2);
+ }
+ else if (str->alloced_length() >=
+ res->length() + sep_str->length() + res2->length())
+ {
+ /* We have room in str; We can't get any errors here */
+ if (str->ptr() == res2->ptr())
+ { // This is quite uncommon!
+ str->replace(0,0,*sep_str);
+ str->replace(0,0,*res);
+ }
+ else
+ {
+ str->copy(*res);
+ str->append(*sep_str);
+ str->append(*res2);
+ }
+ res=str;
+ use_as_buff= &tmp_value;
+ }
+ else if (res == &tmp_value)
+ {
+ if (res->append(*sep_str) || res->append(*res2))
+ goto null; // Must be a blob
+ }
+ else if (res2 == &tmp_value)
+ { // This can happend only 1 time
+ if (tmp_value.replace(0,0,*sep_str) || tmp_value.replace(0,0,*res))
+ goto null;
+ res= &tmp_value;
+ use_as_buff=str; // Put next arg here
+ }
+ else if (tmp_value.is_alloced() && res2->ptr() >= tmp_value.ptr() &&
+ res2->ptr() < tmp_value.ptr() + tmp_value.alloced_length())
+ {
+ /*
+ This happens really seldom:
+ In this case res2 is sub string of tmp_value. We will
+ now work in place in tmp_value to set it to res | sep_str | res2
+ */
+ /* Chop the last characters in tmp_value that isn't in res2 */
+ tmp_value.length((uint32) (res2->ptr() - tmp_value.ptr()) +
+ res2->length());
+ /* Place res2 at start of tmp_value, remove chars before res2 */
+ if (tmp_value.replace(0,(uint32) (res2->ptr() - tmp_value.ptr()),
+ *res) ||
+ tmp_value.replace(res->length(),0, *sep_str))
+ goto null;
+ res= &tmp_value;
+ use_as_buff=str; // Put next arg here
+ }
+ else
+ { // Two big const strings
+ /*
+ NOTE: We should be prudent in the initial allocation unit -- the
+ size of the arguments is a function of data distribution, which can
+ be any. Instead of overcommitting at the first row, we grow the
+ allocated amount by the factor of 2. This ensures that no more than
+ 25% of memory will be overcommitted on average.
+ */
+
+ uint concat_len= res->length() + sep_str->length() + res2->length();
+
+ if (tmp_value.alloced_length() < concat_len)
+ {
+ if (tmp_value.alloced_length() == 0)
+ {
+ if (tmp_value.alloc(concat_len))
+ goto null;
+ }
+ else
+ {
+ uint new_len = MY_MAX(tmp_value.alloced_length() * 2, concat_len);
+
+ if (tmp_value.realloc(new_len))
+ goto null;
+ }
+ }
+
+ if (tmp_value.copy(*res) ||
+ tmp_value.append(*sep_str) ||
+ tmp_value.append(*res2))
+ goto null;
+ res= &tmp_value;
+ use_as_buff=str;
+ }
+ }
+ res->set_charset(collation.collation);
+ return res;
+
+null:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_concat_ws::fix_length_and_dec()
+{
+ ulonglong char_length;
+
+ if (agg_arg_charsets_for_string_result(collation, args, arg_count))
+ return;
+
+ /*
+ arg_count cannot be less than 2,
+ it is done on parser level in sql_yacc.yy
+ so, (arg_count - 2) is safe here.
+ */
+ char_length= (ulonglong) args[0]->max_char_length() * (arg_count - 2);
+ for (uint i=1 ; i < arg_count ; i++)
+ char_length+= args[i]->max_char_length();
+
+ fix_char_length_ulonglong(char_length);
+}
+
+
+String *Item_func_reverse::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res = args[0]->val_str(str);
+ char *ptr, *end, *tmp;
+
+ if ((null_value=args[0]->null_value))
+ return 0;
+ /* An empty string is a special case as the string pointer may be null */
+ if (!res->length())
+ return make_empty_result();
+ if (tmp_value.alloced_length() < res->length() &&
+ tmp_value.realloc(res->length()))
+ {
+ null_value= 1;
+ return 0;
+ }
+ tmp_value.length(res->length());
+ tmp_value.set_charset(res->charset());
+ ptr= (char *) res->ptr();
+ end= ptr + res->length();
+ tmp= (char *) tmp_value.ptr() + tmp_value.length();
+#ifdef USE_MB
+ if (use_mb(res->charset()))
+ {
+ register uint32 l;
+ while (ptr < end)
+ {
+ if ((l= my_ismbchar(res->charset(),ptr,end)))
+ {
+ tmp-= l;
+ DBUG_ASSERT(tmp >= tmp_value.ptr());
+ memcpy(tmp,ptr,l);
+ ptr+= l;
+ }
+ else
+ *--tmp= *ptr++;
+ }
+ }
+ else
+#endif /* USE_MB */
+ {
+ while (ptr < end)
+ *--tmp= *ptr++;
+ }
+ return &tmp_value;
+}
+
+
+void Item_func_reverse::fix_length_and_dec()
+{
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ fix_char_length(args[0]->max_char_length());
+}
+
+/**
+ Replace all occurences of string2 in string1 with string3.
+
+ Don't reallocate val_str() if not needed.
+
+ @todo
+ Fix that this works with binary strings when using USE_MB
+*/
+
+String *Item_func_replace::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res,*res2,*res3;
+ int offset;
+ uint from_length,to_length;
+ bool alloced=0;
+#ifdef USE_MB
+ const char *ptr,*end,*strend,*search,*search_end;
+ register uint32 l;
+ bool binary_cmp;
+#endif
+
+ null_value=0;
+ res=args[0]->val_str(str);
+ if (args[0]->null_value)
+ goto null;
+ res2=args[1]->val_str(&tmp_value);
+ if (args[1]->null_value)
+ goto null;
+
+ res->set_charset(collation.collation);
+
+#ifdef USE_MB
+ binary_cmp = ((res->charset()->state & MY_CS_BINSORT) || !use_mb(res->charset()));
+#endif
+
+ if (res2->length() == 0)
+ return res;
+#ifndef USE_MB
+ if ((offset=res->strstr(*res2)) < 0)
+ return res;
+#else
+ offset=0;
+ if (binary_cmp && (offset=res->strstr(*res2)) < 0)
+ return res;
+#endif
+ if (!(res3=args[2]->val_str(&tmp_value2)))
+ goto null;
+ from_length= res2->length();
+ to_length= res3->length();
+
+#ifdef USE_MB
+ if (!binary_cmp)
+ {
+ search=res2->ptr();
+ search_end=search+from_length;
+redo:
+ DBUG_ASSERT(res->ptr() || !offset);
+ ptr=res->ptr()+offset;
+ strend=res->ptr()+res->length();
+ /*
+ In some cases val_str() can return empty string
+ with ptr() == NULL and length() == 0.
+ Let's check strend to avoid overflow.
+ */
+ end= strend ? strend - from_length + 1 : NULL;
+ while (ptr < end)
+ {
+ if (*ptr == *search)
+ {
+ register char *i,*j;
+ i=(char*) ptr+1; j=(char*) search+1;
+ while (j != search_end)
+ if (*i++ != *j++) goto skip;
+ offset= (int) (ptr-res->ptr());
+ if (res->length()-from_length + to_length >
+ current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(),
+ current_thd->variables.max_allowed_packet);
+
+ goto null;
+ }
+ if (!alloced)
+ {
+ alloced=1;
+ res=copy_if_not_alloced(str,res,res->length()+to_length);
+ }
+ res->replace((uint) offset,from_length,*res3);
+ offset+=(int) to_length;
+ goto redo;
+ }
+skip:
+ if ((l=my_ismbchar(res->charset(), ptr,strend))) ptr+=l;
+ else ++ptr;
+ }
+ }
+ else
+#endif /* USE_MB */
+ do
+ {
+ if (res->length()-from_length + to_length >
+ current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
+ current_thd->variables.max_allowed_packet);
+ goto null;
+ }
+ if (!alloced)
+ {
+ alloced=1;
+ res=copy_if_not_alloced(str,res,res->length()+to_length);
+ }
+ res->replace((uint) offset,from_length,*res3);
+ offset+=(int) to_length;
+ }
+ while ((offset=res->strstr(*res2,(uint) offset)) >= 0);
+ return res;
+
+null:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_replace::fix_length_and_dec()
+{
+ ulonglong char_length= (ulonglong) args[0]->max_char_length();
+ int diff=(int) (args[2]->max_char_length() - args[1]->max_char_length());
+ if (diff > 0 && args[1]->max_char_length())
+ { // Calculate of maxreplaces
+ ulonglong max_substrs= char_length / args[1]->max_char_length();
+ char_length+= max_substrs * (uint) diff;
+ }
+
+ if (agg_arg_charsets_for_string_result_with_comparison(collation, args, 3))
+ return;
+ fix_char_length_ulonglong(char_length);
+}
+
+
+/*********************************************************************/
+void Item_func_regexp_replace::fix_length_and_dec()
+{
+ if (agg_arg_charsets_for_string_result_with_comparison(collation, args, 3))
+ return;
+ max_length= MAX_BLOB_WIDTH;
+ re.init(collation.collation, 0, 10);
+ re.fix_owner(this, args[0], args[1]);
+}
+
+
+/*
+ Traverse through the replacement string and append to "str".
+ Sub-pattern references \0 .. \9 are recognized, which are replaced
+ to the chunks of the source string.
+*/
+bool Item_func_regexp_replace::append_replacement(String *str,
+ const LEX_CSTRING *source,
+ const LEX_CSTRING *replace)
+{
+ const char *beg= replace->str;
+ const char *end= beg + replace->length;
+ CHARSET_INFO *cs= re.library_charset();
+
+ for ( ; ; )
+ {
+ my_wc_t wc;
+ int cnv, n;
+
+ if ((cnv= cs->cset->mb_wc(cs, &wc, (const uchar *) beg,
+ (const uchar *) end)) < 1)
+ break; /* End of line */
+ beg+= cnv;
+
+ if (wc != '\\')
+ {
+ if (str->append(beg - cnv, cnv, cs))
+ return true;
+ continue;
+ }
+
+ if ((cnv= cs->cset->mb_wc(cs, &wc, (const uchar *) beg,
+ (const uchar *) end)) < 1)
+ break; /* End of line */
+ beg+= cnv;
+
+ if ((n= ((int) wc) - '0') >= 0 && n <= 9)
+ {
+ if (n < re.nsubpatterns())
+ {
+ /* A valid sub-pattern reference found */
+ int pbeg= re.subpattern_start(n), plength= re.subpattern_end(n) - pbeg;
+ if (str->append(source->str + pbeg, plength, cs))
+ return true;
+ }
+ }
+ else
+ {
+ /*
+ A non-digit character following after '\'.
+ Just add the character itself.
+ */
+ if (str->append(beg - cnv, cnv, cs))
+ return false;
+ }
+ }
+ return false;
+}
+
+
+String *Item_func_regexp_replace::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff0[MAX_FIELD_WIDTH];
+ char buff2[MAX_FIELD_WIDTH];
+ String tmp0(buff0,sizeof(buff0),&my_charset_bin);
+ String tmp2(buff2,sizeof(buff2),&my_charset_bin);
+ String *source= args[0]->val_str(&tmp0);
+ String *replace= args[2]->val_str(&tmp2);
+ LEX_CSTRING src, rpl;
+ int startoffset= 0;
+
+ if ((null_value= (args[0]->null_value || args[2]->null_value ||
+ re.recompile(args[1]))))
+ return (String *) 0;
+
+ if (!(source= re.convert_if_needed(source, &re.subject_converter)) ||
+ !(replace= re.convert_if_needed(replace, &re.replace_converter)))
+ goto err;
+
+ src= source->lex_cstring();
+ rpl= replace->lex_cstring();
+
+ str->length(0);
+ str->set_charset(collation.collation);
+
+ for ( ; ; ) // Iterate through all matches
+ {
+
+ if (re.exec(src.str, src.length, startoffset))
+ goto err;
+
+ if (!re.match() || re.subpattern_length(0) == 0)
+ {
+ /*
+ No match or an empty match.
+ Append the rest of the source string
+ starting from startoffset until the end of the source.
+ */
+ if (str->append(src.str + startoffset, src.length - startoffset, re.library_charset()))
+ goto err;
+ return str;
+ }
+
+ /*
+ Append prefix, the part before the matching pattern.
+ starting from startoffset until the next match
+ */
+ if (str->append(src.str + startoffset, re.subpattern_start(0) - startoffset, re.library_charset()))
+ goto err;
+
+ // Append replacement
+ if (append_replacement(str, &src, &rpl))
+ goto err;
+
+ // Set the new start point as the end of previous match
+ startoffset= re.subpattern_end(0);
+ }
+ return str;
+
+err:
+ null_value= true;
+ return (String *) 0;
+}
+
+
+void Item_func_regexp_substr::fix_length_and_dec()
+{
+ if (agg_arg_charsets_for_string_result_with_comparison(collation, args, 2))
+ return;
+ fix_char_length(args[0]->max_char_length());
+ re.init(collation.collation, 0, 10);
+ re.fix_owner(this, args[0], args[1]);
+}
+
+
+String *Item_func_regexp_substr::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff0[MAX_FIELD_WIDTH];
+ String tmp0(buff0,sizeof(buff0),&my_charset_bin);
+ String *source= args[0]->val_str(&tmp0);
+
+ if ((null_value= (args[0]->null_value || re.recompile(args[1]))))
+ return (String *) 0;
+
+ if (!(source= re.convert_if_needed(source, &re.subject_converter)))
+ goto err;
+
+ str->length(0);
+ str->set_charset(collation.collation);
+
+ if (re.exec(source->ptr(), source->length(), 0))
+ goto err;
+
+ if (!re.match())
+ return str;
+
+ if (str->append(source->ptr() + re.subpattern_start(0),
+ re.subpattern_end(0) - re.subpattern_start(0),
+ re.library_charset()))
+ goto err;
+
+ return str;
+
+err:
+ null_value= true;
+ return (String *) 0;
+}
+
+
+/************************************************************************/
+
+
+String *Item_func_insert::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res,*res2;
+ longlong start, length; /* must be longlong to avoid truncation */
+
+ null_value=0;
+ res=args[0]->val_str(str);
+ res2=args[3]->val_str(&tmp_value);
+ start= args[1]->val_int() - 1;
+ length= args[2]->val_int();
+
+ if (args[0]->null_value || args[1]->null_value || args[2]->null_value ||
+ args[3]->null_value)
+ goto null; /* purecov: inspected */
+
+ if ((start < 0) || (start > res->length()))
+ return res; // Wrong param; skip insert
+ if ((length < 0) || (length > res->length()))
+ length= res->length();
+
+ /*
+ There is one exception not handled (intentionaly) by the character set
+ aggregation code. If one string is strong side and is binary, and
+ another one is weak side and is a multi-byte character string,
+ then we need to operate on the second string in terms on bytes when
+ calling ::numchars() and ::charpos(), rather than in terms of characters.
+ Lets substitute its character set to binary.
+ */
+ if (collation.collation == &my_charset_bin)
+ {
+ res->set_charset(&my_charset_bin);
+ res2->set_charset(&my_charset_bin);
+ }
+
+ /* start and length are now sufficiently valid to pass to charpos function */
+ start= res->charpos((int) start);
+ length= res->charpos((int) length, (uint32) start);
+
+ /* Re-testing with corrected params */
+ if (start > res->length())
+ return res; /* purecov: inspected */ // Wrong param; skip insert
+ if (length > res->length() - start)
+ length= res->length() - start;
+
+ if ((ulonglong) (res->length() - length + res2->length()) >
+ (ulonglong) current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(), current_thd->variables.max_allowed_packet);
+ goto null;
+ }
+ res=copy_if_not_alloced(str,res,res->length());
+ res->replace((uint32) start,(uint32) length,*res2);
+ return res;
+null:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_insert::fix_length_and_dec()
+{
+ ulonglong char_length;
+
+ // Handle character set for args[0] and args[3].
+ if (agg_arg_charsets_for_string_result(collation, args, 2, 3))
+ return;
+ char_length= ((ulonglong) args[0]->max_char_length() +
+ (ulonglong) args[3]->max_char_length());
+ fix_char_length_ulonglong(char_length);
+}
+
+
+String *Item_str_conv::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res;
+ if (!(res=args[0]->val_str(str)))
+ {
+ null_value=1; /* purecov: inspected */
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+ if (multiply == 1)
+ {
+ uint len;
+ res= copy_if_not_alloced(&tmp_value, res, res->length());
+ len= converter(collation.collation, (char*) res->ptr(), res->length(),
+ (char*) res->ptr(), res->length());
+ DBUG_ASSERT(len <= res->length());
+ res->length(len);
+ }
+ else
+ {
+ uint len= res->length() * multiply;
+ tmp_value.alloc(len);
+ tmp_value.set_charset(collation.collation);
+ len= converter(collation.collation, (char*) res->ptr(), res->length(),
+ (char*) tmp_value.ptr(), len);
+ tmp_value.length(len);
+ res= &tmp_value;
+ }
+ return res;
+}
+
+
+void Item_func_lcase::fix_length_and_dec()
+{
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ multiply= collation.collation->casedn_multiply;
+ converter= collation.collation->cset->casedn;
+ fix_char_length_ulonglong((ulonglong) args[0]->max_char_length() * multiply);
+}
+
+void Item_func_ucase::fix_length_and_dec()
+{
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ multiply= collation.collation->caseup_multiply;
+ converter= collation.collation->cset->caseup;
+ fix_char_length_ulonglong((ulonglong) args[0]->max_char_length() * multiply);
+}
+
+
+String *Item_func_left::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(str);
+
+ /* must be longlong to avoid truncation */
+ longlong length= args[1]->val_int();
+ uint char_pos;
+
+ if ((null_value=(args[0]->null_value || args[1]->null_value)))
+ return 0;
+
+ /* if "unsigned_flag" is set, we have a *huge* positive number. */
+ if ((length <= 0) && (!args[1]->unsigned_flag))
+ return make_empty_result();
+ if ((res->length() <= (ulonglong) length) ||
+ (res->length() <= (char_pos= res->charpos((int) length))))
+ return res;
+
+ tmp_value.set(*res, 0, char_pos);
+ return &tmp_value;
+}
+
+
+void Item_str_func::left_right_max_length()
+{
+ uint32 char_length= args[0]->max_char_length();
+ if (args[1]->const_item())
+ {
+ int length= (int) args[1]->val_int();
+ if (args[1]->null_value || length <= 0)
+ char_length=0;
+ else
+ set_if_smaller(char_length, (uint) length);
+ }
+ fix_char_length(char_length);
+}
+
+
+void Item_func_left::fix_length_and_dec()
+{
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ left_right_max_length();
+}
+
+
+String *Item_func_right::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(str);
+ /* must be longlong to avoid truncation */
+ longlong length= args[1]->val_int();
+
+ if ((null_value=(args[0]->null_value || args[1]->null_value)))
+ return 0; /* purecov: inspected */
+
+ /* if "unsigned_flag" is set, we have a *huge* positive number. */
+ if ((length <= 0) && (!args[1]->unsigned_flag))
+ return make_empty_result(); /* purecov: inspected */
+
+ if (res->length() <= (ulonglong) length)
+ return res; /* purecov: inspected */
+
+ uint start=res->numchars();
+ if (start <= (uint) length)
+ return res;
+ start=res->charpos(start - (uint) length);
+ tmp_value.set(*res,start,res->length()-start);
+ return &tmp_value;
+}
+
+
+void Item_func_right::fix_length_and_dec()
+{
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ left_right_max_length();
+}
+
+
+String *Item_func_substr::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res = args[0]->val_str(str);
+ /* must be longlong to avoid truncation */
+ longlong start= args[1]->val_int();
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Limit so that code sees out-of-bound value properly. */
+ longlong length= arg_count == 3 ? args[2]->val_int() : INT_MAX32;
+ longlong tmp_length;
+
+ if ((null_value=(args[0]->null_value || args[1]->null_value ||
+ (arg_count == 3 && args[2]->null_value))))
+ return 0; /* purecov: inspected */
+
+ /* Negative or zero length, will return empty string. */
+ if ((arg_count == 3) && (length <= 0) &&
+ (length == 0 || !args[2]->unsigned_flag))
+ return make_empty_result();
+
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Set here so that rest of code sees out-of-bound value as such. */
+ if ((length <= 0) || (length > INT_MAX32))
+ length= INT_MAX32;
+
+ /* if "unsigned_flag" is set, we have a *huge* positive number. */
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ if ((!args[1]->unsigned_flag && (start < INT_MIN32 || start > INT_MAX32)) ||
+ (args[1]->unsigned_flag && ((ulonglong) start > INT_MAX32)))
+ return make_empty_result();
+
+ start= ((start < 0) ? res->numchars() + start : start - 1);
+ start= res->charpos((int) start);
+ if ((start < 0) || ((uint) start + 1 > res->length()))
+ return make_empty_result();
+
+ length= res->charpos((int) length, (uint32) start);
+ tmp_length= res->length() - start;
+ length= MY_MIN(length, tmp_length);
+
+ if (!start && (longlong) res->length() == length)
+ return res;
+ tmp_value.set(*res, (uint32) start, (uint32) length);
+ return &tmp_value;
+}
+
+
+void Item_func_substr::fix_length_and_dec()
+{
+ max_length=args[0]->max_length;
+
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ if (args[1]->const_item())
+ {
+ int32 start= (int32) args[1]->val_int();
+ if (args[1]->null_value)
+ max_length= 0;
+ else if (start < 0)
+ max_length= ((uint)(-start) > max_length) ? 0 : (uint)(-start);
+ else
+ max_length-= MY_MIN((uint)(start - 1), max_length);
+ }
+ if (arg_count == 3 && args[2]->const_item())
+ {
+ int32 length= (int32) args[2]->val_int();
+ if (args[2]->null_value || length <= 0)
+ max_length=0; /* purecov: inspected */
+ else
+ set_if_smaller(max_length,(uint) length);
+ }
+ max_length*= collation.collation->mbmaxlen;
+}
+
+
+void Item_func_substr_index::fix_length_and_dec()
+{
+ if (agg_arg_charsets_for_string_result_with_comparison(collation, args, 2))
+ return;
+ fix_char_length(args[0]->max_char_length());
+}
+
+
+String *Item_func_substr_index::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff),system_charset_info);
+ String *res= args[0]->val_str(str);
+ String *delimiter= args[1]->val_str(&tmp);
+ int32 count= (int32) args[2]->val_int();
+ uint offset;
+
+ if (args[0]->null_value || args[1]->null_value || args[2]->null_value)
+ { // string and/or delim are null
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ uint delimiter_length= delimiter->length();
+ if (!res->length() || !delimiter_length || !count)
+ return make_empty_result(); // Wrong parameters
+
+ res->set_charset(collation.collation);
+
+#ifdef USE_MB
+ if (use_mb(res->charset()))
+ {
+ const char *ptr= res->ptr();
+ const char *strend= ptr+res->length();
+ const char *end= strend-delimiter_length+1;
+ const char *search= delimiter->ptr();
+ const char *search_end= search+delimiter_length;
+ int32 n=0,c=count,pass;
+ register uint32 l;
+ for (pass=(count>0);pass<2;++pass)
+ {
+ while (ptr < end)
+ {
+ if (*ptr == *search)
+ {
+ register char *i,*j;
+ i=(char*) ptr+1; j=(char*) search+1;
+ while (j != search_end)
+ if (*i++ != *j++) goto skip;
+ if (pass==0) ++n;
+ else if (!--c) break;
+ ptr+= delimiter_length;
+ continue;
+ }
+ skip:
+ if ((l=my_ismbchar(res->charset(), ptr,strend))) ptr+=l;
+ else ++ptr;
+ } /* either not found or got total number when count<0 */
+ if (pass == 0) /* count<0 */
+ {
+ c+=n+1;
+ if (c<=0) return res; /* not found, return original string */
+ ptr=res->ptr();
+ }
+ else
+ {
+ if (c) return res; /* Not found, return original string */
+ if (count>0) /* return left part */
+ {
+ tmp_value.set(*res,0,(ulong) (ptr-res->ptr()));
+ }
+ else /* return right part */
+ {
+ ptr+= delimiter_length;
+ tmp_value.set(*res,(ulong) (ptr-res->ptr()), (ulong) (strend-ptr));
+ }
+ }
+ }
+ }
+ else
+#endif /* USE_MB */
+ {
+ if (count > 0)
+ { // start counting from the beginning
+ for (offset=0; ; offset+= delimiter_length)
+ {
+ if ((int) (offset= res->strstr(*delimiter, offset)) < 0)
+ return res; // Didn't find, return org string
+ if (!--count)
+ {
+ tmp_value.set(*res,0,offset);
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ Negative index, start counting at the end
+ */
+ for (offset=res->length(); offset ;)
+ {
+ /*
+ this call will result in finding the position pointing to one
+ address space less than where the found substring is located
+ in res
+ */
+ if ((int) (offset= res->strrstr(*delimiter, offset)) < 0)
+ return res; // Didn't find, return org string
+ /*
+ At this point, we've searched for the substring
+ the number of times as supplied by the index value
+ */
+ if (!++count)
+ {
+ offset+= delimiter_length;
+ tmp_value.set(*res,offset,res->length()- offset);
+ break;
+ }
+ }
+ if (count)
+ return res; // Didn't find, return org string
+ }
+ }
+ /*
+ We always mark tmp_value as const so that if val_str() is called again
+ on this object, we don't disrupt the contents of tmp_value when it was
+ derived from another String.
+ */
+ tmp_value.mark_as_const();
+ return (&tmp_value);
+}
+
+/*
+** The trim functions are extension to ANSI SQL because they trim substrings
+** They ltrim() and rtrim() functions are optimized for 1 byte strings
+** They also return the original string if possible, else they return
+** a substring that points at the original string.
+*/
+
+
+String *Item_func_ltrim::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH], *ptr, *end;
+ String tmp(buff,sizeof(buff),system_charset_info);
+ String *res, *remove_str;
+ uint remove_length;
+ LINT_INIT(remove_length);
+
+ res= args[0]->val_str(str);
+ if ((null_value=args[0]->null_value))
+ return 0;
+ remove_str= &remove; /* Default value. */
+ if (arg_count == 2)
+ {
+ remove_str= args[1]->val_str(&tmp);
+ if ((null_value= args[1]->null_value))
+ return 0;
+ }
+
+ if ((remove_length= remove_str->length()) == 0 ||
+ remove_length > res->length())
+ return non_trimmed_value(res);
+
+ ptr= (char*) res->ptr();
+ end= ptr+res->length();
+ if (remove_length == 1)
+ {
+ char chr=(*remove_str)[0];
+ while (ptr != end && *ptr == chr)
+ ptr++;
+ }
+ else
+ {
+ const char *r_ptr=remove_str->ptr();
+ end-=remove_length;
+ while (ptr <= end && !memcmp(ptr, r_ptr, remove_length))
+ ptr+=remove_length;
+ end+=remove_length;
+ }
+ if (ptr == res->ptr())
+ return non_trimmed_value(res);
+ return trimmed_value(res, (uint32) (ptr - res->ptr()), (uint32) (end - ptr));
+}
+
+
+String *Item_func_rtrim::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH], *ptr, *end;
+ String tmp(buff, sizeof(buff), system_charset_info);
+ String *res, *remove_str;
+ uint remove_length;
+ LINT_INIT(remove_length);
+
+ res= args[0]->val_str(str);
+ if ((null_value=args[0]->null_value))
+ return 0;
+ remove_str= &remove; /* Default value. */
+ if (arg_count == 2)
+ {
+ remove_str= args[1]->val_str(&tmp);
+ if ((null_value= args[1]->null_value))
+ return 0;
+ }
+
+ if ((remove_length= remove_str->length()) == 0 ||
+ remove_length > res->length())
+ return non_trimmed_value(res);
+
+ ptr= (char*) res->ptr();
+ end= ptr+res->length();
+#ifdef USE_MB
+ char *p=ptr;
+ register uint32 l;
+#endif
+ if (remove_length == 1)
+ {
+ char chr=(*remove_str)[0];
+#ifdef USE_MB
+ if (use_mb(collation.collation))
+ {
+ while (ptr < end)
+ {
+ if ((l= my_ismbchar(collation.collation, ptr, end))) ptr+= l, p=ptr;
+ else ++ptr;
+ }
+ ptr=p;
+ }
+#endif
+ while (ptr != end && end[-1] == chr)
+ end--;
+ }
+ else
+ {
+ const char *r_ptr=remove_str->ptr();
+#ifdef USE_MB
+ if (use_mb(collation.collation))
+ {
+ loop:
+ while (ptr + remove_length < end)
+ {
+ if ((l= my_ismbchar(collation.collation, ptr, end))) ptr+= l;
+ else ++ptr;
+ }
+ if (ptr + remove_length == end && !memcmp(ptr,r_ptr,remove_length))
+ {
+ end-=remove_length;
+ ptr=p;
+ goto loop;
+ }
+ }
+ else
+#endif /* USE_MB */
+ {
+ while (ptr + remove_length <= end &&
+ !memcmp(end-remove_length, r_ptr, remove_length))
+ end-=remove_length;
+ }
+ }
+ if (end == res->ptr()+res->length())
+ return non_trimmed_value(res);
+ return trimmed_value(res, 0, (uint32) (end - res->ptr()));
+}
+
+
+String *Item_func_trim::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ char buff[MAX_FIELD_WIDTH], *ptr, *end;
+ const char *r_ptr;
+ String tmp(buff, sizeof(buff), system_charset_info);
+ String *res, *remove_str;
+ uint remove_length;
+ LINT_INIT(remove_length);
+
+ res= args[0]->val_str(str);
+ if ((null_value=args[0]->null_value))
+ return 0;
+ remove_str= &remove; /* Default value. */
+ if (arg_count == 2)
+ {
+ remove_str= args[1]->val_str(&tmp);
+ if ((null_value= args[1]->null_value))
+ return 0;
+ }
+
+ if ((remove_length= remove_str->length()) == 0 ||
+ remove_length > res->length())
+ return non_trimmed_value(res);
+
+ ptr= (char*) res->ptr();
+ end= ptr+res->length();
+ r_ptr= remove_str->ptr();
+ while (ptr+remove_length <= end && !memcmp(ptr,r_ptr,remove_length))
+ ptr+=remove_length;
+#ifdef USE_MB
+ if (use_mb(collation.collation))
+ {
+ char *p=ptr;
+ register uint32 l;
+ loop:
+ while (ptr + remove_length < end)
+ {
+ if ((l= my_ismbchar(collation.collation, ptr, end)))
+ ptr+= l;
+ else
+ ++ptr;
+ }
+ if (ptr + remove_length == end && !memcmp(ptr,r_ptr,remove_length))
+ {
+ end-=remove_length;
+ ptr=p;
+ goto loop;
+ }
+ ptr=p;
+ }
+ else
+#endif /* USE_MB */
+ {
+ while (ptr + remove_length <= end &&
+ !memcmp(end-remove_length,r_ptr,remove_length))
+ end-=remove_length;
+ }
+ if (ptr == res->ptr() && end == ptr+res->length())
+ return non_trimmed_value(res);
+ return trimmed_value(res, (uint32) (ptr - res->ptr()), (uint32) (end - ptr));
+}
+
+void Item_func_trim::fix_length_and_dec()
+{
+ if (arg_count == 1)
+ {
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ remove.set_charset(collation.collation);
+ remove.set_ascii(" ",1);
+ }
+ else
+ {
+ // Handle character set for args[1] and args[0].
+ // Note that we pass args[1] as the first item, and args[0] as the second.
+ if (agg_arg_charsets_for_string_result_with_comparison(collation,
+ &args[1], 2, -1))
+ return;
+ }
+ fix_char_length(args[0]->max_char_length());
+}
+
+void Item_func_trim::print(String *str, enum_query_type query_type)
+{
+ if (arg_count == 1)
+ {
+ Item_func::print(str, query_type);
+ return;
+ }
+ str->append(Item_func_trim::func_name());
+ str->append('(');
+ str->append(mode_name());
+ str->append(' ');
+ args[1]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" from "));
+ args[0]->print(str, query_type);
+ str->append(')');
+}
+
+
+/* Item_func_password */
+
+String *Item_func_password::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(str);
+ check_password_policy(res);
+ if (args[0]->null_value || res->length() == 0)
+ return make_empty_result();
+ my_make_scrambled_password(tmp_value, res->ptr(), res->length());
+ str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH, &my_charset_latin1);
+ return str;
+}
+
+char *Item_func_password::alloc(THD *thd, const char *password, size_t pass_len)
+{
+ char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1);
+ if (buff)
+ {
+ String *password_str= new (thd->mem_root)String(password, thd->variables.
+ character_set_client);
+ check_password_policy(password_str);
+ my_make_scrambled_password(buff, password, pass_len);
+ }
+ return buff;
+}
+
+
+/* Item_func_old_password */
+
+String *Item_func_old_password::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(str);
+ if ((null_value=args[0]->null_value))
+ return 0;
+ if (res->length() == 0)
+ return make_empty_result();
+ my_make_scrambled_password_323(tmp_value, res->ptr(), res->length());
+ str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH_323, &my_charset_latin1);
+ return str;
+}
+
+char *Item_func_old_password::alloc(THD *thd, const char *password,
+ size_t pass_len)
+{
+ char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1);
+ if (buff)
+ my_make_scrambled_password_323(buff, password, pass_len);
+ return buff;
+}
+
+
+#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
+
+String *Item_func_encrypt::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res =args[0]->val_str(str);
+
+#ifdef HAVE_CRYPT
+ char salt[3],*salt_ptr;
+ if ((null_value=args[0]->null_value))
+ return 0;
+ if (res->length() == 0)
+ return make_empty_result();
+ if (arg_count == 1)
+ { // generate random salt
+ time_t timestamp=current_thd->query_start();
+ salt[0] = bin_to_ascii( (ulong) timestamp & 0x3f);
+ salt[1] = bin_to_ascii(( (ulong) timestamp >> 5) & 0x3f);
+ salt[2] = 0;
+ salt_ptr=salt;
+ }
+ else
+ { // obtain salt from the first two bytes
+ String *salt_str=args[1]->val_str(&tmp_value);
+ if ((null_value= (args[1]->null_value || salt_str->length() < 2)))
+ return 0;
+ salt_ptr= salt_str->c_ptr_safe();
+ }
+ mysql_mutex_lock(&LOCK_crypt);
+ char *tmp= crypt(res->c_ptr_safe(),salt_ptr);
+ if (!tmp)
+ {
+ mysql_mutex_unlock(&LOCK_crypt);
+ null_value= 1;
+ return 0;
+ }
+ str->set(tmp, (uint) strlen(tmp), &my_charset_bin);
+ str->copy();
+ mysql_mutex_unlock(&LOCK_crypt);
+ return str;
+#else
+ null_value=1;
+ return 0;
+#endif /* HAVE_CRYPT */
+}
+
+bool Item_func_encode::seed()
+{
+ char buf[80];
+ ulong rand_nr[2];
+ String *key, tmp(buf, sizeof(buf), system_charset_info);
+
+ if (!(key= args[1]->val_str(&tmp)))
+ return TRUE;
+
+ hash_password(rand_nr, key->ptr(), key->length());
+ sql_crypt.init(rand_nr);
+
+ return FALSE;
+}
+
+void Item_func_encode::fix_length_and_dec()
+{
+ max_length=args[0]->max_length;
+ maybe_null=args[0]->maybe_null || args[1]->maybe_null;
+ collation.set(&my_charset_bin);
+ /* Precompute the seed state if the item is constant. */
+ seeded= args[1]->const_item() &&
+ (args[1]->result_type() == STRING_RESULT) && !seed();
+}
+
+String *Item_func_encode::val_str(String *str)
+{
+ String *res;
+ DBUG_ASSERT(fixed == 1);
+
+ if (!(res=args[0]->val_str(str)))
+ {
+ null_value= 1;
+ return NULL;
+ }
+
+ if (!seeded && seed())
+ {
+ null_value= 1;
+ return NULL;
+ }
+
+ null_value= 0;
+ res= copy_if_not_alloced(str, res, res->length());
+ crypto_transform(res);
+ sql_crypt.reinit();
+
+ return res;
+}
+
+void Item_func_encode::crypto_transform(String *res)
+{
+ sql_crypt.encode((char*) res->ptr(),res->length());
+ res->set_charset(&my_charset_bin);
+}
+
+void Item_func_decode::crypto_transform(String *res)
+{
+ sql_crypt.decode((char*) res->ptr(),res->length());
+}
+
+
+String *Item_func_database::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ THD *thd= current_thd;
+ if (thd->db == NULL)
+ {
+ null_value= 1;
+ return 0;
+ }
+ else
+ str->copy(thd->db, thd->db_length, system_charset_info);
+ return str;
+}
+
+
+/**
+ @note USER() is replicated correctly if binlog_format=ROW or (as of
+ BUG#28086) binlog_format=MIXED, but is incorrectly replicated to ''
+ if binlog_format=STATEMENT.
+*/
+bool Item_func_user::init(const char *user, const char *host)
+{
+ DBUG_ASSERT(fixed == 1);
+
+ // For system threads (e.g. replication SQL thread) user may be empty
+ if (user)
+ {
+ CHARSET_INFO *cs= str_value.charset();
+ size_t res_length= (strlen(user)+strlen(host)+2) * cs->mbmaxlen;
+
+ if (str_value.alloc((uint) res_length))
+ {
+ null_value=1;
+ return TRUE;
+ }
+
+ res_length=cs->cset->snprintf(cs, (char*)str_value.ptr(), (uint) res_length,
+ "%s@%s", user, host);
+ str_value.length((uint) res_length);
+ str_value.mark_as_const();
+ }
+ return FALSE;
+}
+
+
+bool Item_func_user::fix_fields(THD *thd, Item **ref)
+{
+ return (Item_func_sysconst::fix_fields(thd, ref) ||
+ init(thd->main_security_ctx.user,
+ thd->main_security_ctx.host_or_ip));
+}
+
+
+bool Item_func_current_user::fix_fields(THD *thd, Item **ref)
+{
+ if (Item_func_sysconst::fix_fields(thd, ref))
+ return TRUE;
+
+ Security_context *ctx= context->security_ctx
+ ? context->security_ctx : thd->security_ctx;
+ return init(ctx->priv_user, ctx->priv_host);
+}
+
+bool Item_func_current_role::fix_fields(THD *thd, Item **ref)
+{
+ if (Item_func_sysconst::fix_fields(thd, ref))
+ return 1;
+
+ Security_context *ctx= context->security_ctx
+ ? context->security_ctx : thd->security_ctx;
+
+ if (ctx->priv_role[0])
+ {
+ if (str_value.copy(ctx->priv_role, strlen(ctx->priv_role),
+ system_charset_info))
+ return 1;
+
+ str_value.mark_as_const();
+ return 0;
+ }
+ null_value= maybe_null= 1;
+ return 0;
+}
+
+
+void Item_func_soundex::fix_length_and_dec()
+{
+ uint32 char_length= args[0]->max_char_length();
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ set_if_bigger(char_length, 4);
+ fix_char_length(char_length);
+ tmp_value.set_charset(collation.collation);
+}
+
+
+/**
+ If alpha, map input letter to soundex code.
+ If not alpha and remove_garbage is set then skip to next char
+ else return 0
+*/
+
+static int soundex_toupper(int ch)
+{
+ return (ch >= 'a' && ch <= 'z') ? ch - 'a' + 'A' : ch;
+}
+
+
+static char get_scode(int wc)
+{
+ int ch= soundex_toupper(wc);
+ if (ch < 'A' || ch > 'Z')
+ {
+ // Thread extended alfa (country spec)
+ return '0'; // as vokal
+ }
+ return(soundex_map[ch-'A']);
+}
+
+
+static bool my_uni_isalpha(int wc)
+{
+ /*
+ Return true for all Basic Latin letters: a..z A..Z.
+ Return true for all Unicode characters with code higher than U+00C0:
+ - characters between 'z' and U+00C0 are controls and punctuations.
+ - "U+00C0 LATIN CAPITAL LETTER A WITH GRAVE" is the first letter after 'z'.
+ */
+ return (wc >= 'a' && wc <= 'z') ||
+ (wc >= 'A' && wc <= 'Z') ||
+ (wc >= 0xC0);
+}
+
+
+String *Item_func_soundex::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res =args[0]->val_str(str);
+ char last_ch,ch;
+ CHARSET_INFO *cs= collation.collation;
+ my_wc_t wc;
+ uint nchars;
+ int rc;
+
+ if ((null_value= args[0]->null_value))
+ return 0; /* purecov: inspected */
+
+ if (tmp_value.alloc(MY_MAX(res->length(), 4 * cs->mbminlen)))
+ return str; /* purecov: inspected */
+ char *to= (char *) tmp_value.ptr();
+ char *to_end= to + tmp_value.alloced_length();
+ char *from= (char *) res->ptr(), *end= from + res->length();
+
+ for ( ; ; ) /* Skip pre-space */
+ {
+ if ((rc= cs->cset->mb_wc(cs, &wc, (uchar*) from, (uchar*) end)) <= 0)
+ return make_empty_result(); /* EOL or invalid byte sequence */
+
+ if (rc == 1 && cs->ctype)
+ {
+ /* Single byte letter found */
+ if (my_isalpha(cs, *from))
+ {
+ last_ch= get_scode(*from); // Code of the first letter
+ *to++= soundex_toupper(*from++); // Copy first letter
+ break;
+ }
+ from++;
+ }
+ else
+ {
+ from+= rc;
+ if (my_uni_isalpha(wc))
+ {
+ /* Multibyte letter found */
+ wc= soundex_toupper(wc);
+ last_ch= get_scode(wc); // Code of the first letter
+ if ((rc= cs->cset->wc_mb(cs, wc, (uchar*) to, (uchar*) to_end)) <= 0)
+ {
+ /* Extra safety - should not really happen */
+ DBUG_ASSERT(false);
+ return make_empty_result();
+ }
+ to+= rc;
+ break;
+ }
+ }
+ }
+
+ /*
+ last_ch is now set to the first 'double-letter' check.
+ loop on input letters until end of input
+ */
+ for (nchars= 1 ; ; )
+ {
+ if ((rc= cs->cset->mb_wc(cs, &wc, (uchar*) from, (uchar*) end)) <= 0)
+ break; /* EOL or invalid byte sequence */
+
+ if (rc == 1 && cs->ctype)
+ {
+ if (!my_isalpha(cs, *from++))
+ continue;
+ }
+ else
+ {
+ from+= rc;
+ if (!my_uni_isalpha(wc))
+ continue;
+ }
+
+ ch= get_scode(wc);
+ if ((ch != '0') && (ch != last_ch)) // if not skipped or double
+ {
+ // letter, copy to output
+ if ((rc= cs->cset->wc_mb(cs, (my_wc_t) ch,
+ (uchar*) to, (uchar*) to_end)) <= 0)
+ {
+ // Extra safety - should not really happen
+ DBUG_ASSERT(false);
+ break;
+ }
+ to+= rc;
+ nchars++;
+ last_ch= ch; // save code of last input letter
+ } // for next double-letter check
+ }
+
+ /* Pad up to 4 characters with DIGIT ZERO, if the string is shorter */
+ if (nchars < 4)
+ {
+ uint nbytes= (4 - nchars) * cs->mbminlen;
+ cs->cset->fill(cs, to, nbytes, '0');
+ to+= nbytes;
+ }
+
+ tmp_value.length((uint) (to-tmp_value.ptr()));
+ return &tmp_value;
+}
+
+
+/**
+ Change a number to format '3,333,333,333.000'.
+
+ This should be 'internationalized' sometimes.
+*/
+
+const int FORMAT_MAX_DECIMALS= 30;
+
+
+MY_LOCALE *Item_func_format::get_locale(Item *item)
+{
+ DBUG_ASSERT(arg_count == 3);
+ String tmp, *locale_name= args[2]->val_str_ascii(&tmp);
+ MY_LOCALE *lc;
+ if (!locale_name ||
+ !(lc= my_locale_by_name(locale_name->c_ptr_safe())))
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_LOCALE,
+ ER(ER_UNKNOWN_LOCALE),
+ locale_name ? locale_name->c_ptr_safe() : "NULL");
+ lc= &my_locale_en_US;
+ }
+ return lc;
+}
+
+void Item_func_format::fix_length_and_dec()
+{
+ uint32 char_length= args[0]->max_char_length();
+ uint32 max_sep_count= (char_length / 3) + (decimals ? 1 : 0) + /*sign*/1;
+ collation.set(default_charset());
+ fix_char_length(char_length + max_sep_count + decimals);
+ if (arg_count == 3)
+ locale= args[2]->basic_const_item() ? get_locale(args[2]) : NULL;
+ else
+ locale= &my_locale_en_US; /* Two arguments */
+}
+
+
+/**
+ @todo
+ This needs to be fixed for multi-byte character set where numbers
+ are stored in more than one byte
+*/
+
+String *Item_func_format::val_str_ascii(String *str)
+{
+ uint32 str_length;
+ /* Number of decimal digits */
+ int dec;
+ /* Number of characters used to represent the decimals, including '.' */
+ uint32 dec_length;
+ MY_LOCALE *lc;
+ DBUG_ASSERT(fixed == 1);
+
+ dec= (int) args[1]->val_int();
+ if (args[1]->null_value)
+ {
+ null_value=1;
+ return NULL;
+ }
+
+ lc= locale ? locale : get_locale(args[2]);
+
+ dec= set_zone(dec, 0, FORMAT_MAX_DECIMALS);
+ dec_length= dec ? dec+1 : 0;
+ null_value=0;
+
+ if (args[0]->result_type() == DECIMAL_RESULT ||
+ args[0]->result_type() == INT_RESULT)
+ {
+ my_decimal dec_val, rnd_dec, *res;
+ res= args[0]->val_decimal(&dec_val);
+ if ((null_value=args[0]->null_value))
+ return 0; /* purecov: inspected */
+ my_decimal_round(E_DEC_FATAL_ERROR, res, dec, false, &rnd_dec);
+ my_decimal2string(E_DEC_FATAL_ERROR, &rnd_dec, 0, 0, 0, str);
+ str_length= str->length();
+ }
+ else
+ {
+ double nr= args[0]->val_real();
+ if ((null_value=args[0]->null_value))
+ return 0; /* purecov: inspected */
+ nr= my_double_round(nr, (longlong) dec, FALSE, FALSE);
+ str->set_real(nr, dec, &my_charset_numeric);
+ if (isnan(nr) || my_isinf(nr))
+ return str;
+ str_length=str->length();
+ }
+ /* We need this test to handle 'nan' and short values */
+ if (lc->grouping[0] > 0 &&
+ str_length >= dec_length + 1 + lc->grouping[0])
+ {
+ /* We need space for ',' between each group of digits as well. */
+ char buf[2 * FLOATING_POINT_BUFFER];
+ int count;
+ const char *grouping= lc->grouping;
+ char sign_length= *str->ptr() == '-' ? 1 : 0;
+ const char *src= str->ptr() + str_length - dec_length - 1;
+ const char *src_begin= str->ptr() + sign_length;
+ char *dst= buf + sizeof(buf);
+
+ /* Put the fractional part */
+ if (dec)
+ {
+ dst-= (dec + 1);
+ *dst= lc->decimal_point;
+ memcpy(dst + 1, src + 2, dec);
+ }
+
+ /* Put the integer part with grouping */
+ for (count= *grouping; src >= src_begin; count--)
+ {
+ /*
+ When *grouping==0x80 (which means "end of grouping")
+ count will be initialized to -1 and
+ we'll never get into this "if" anymore.
+ */
+ if (count == 0)
+ {
+ *--dst= lc->thousand_sep;
+ if (grouping[1])
+ grouping++;
+ count= *grouping;
+ }
+ DBUG_ASSERT(dst > buf);
+ *--dst= *src--;
+ }
+
+ if (sign_length) /* Put '-' */
+ *--dst= *str->ptr();
+
+ /* Put the rest of the integer part without grouping */
+ str->copy(dst, buf + sizeof(buf) - dst, &my_charset_latin1);
+ }
+ else if (dec_length && lc->decimal_point != '.')
+ {
+ /*
+ For short values without thousands (<1000)
+ replace decimal point to localized value.
+ */
+ DBUG_ASSERT(dec_length <= str_length);
+ ((char*) str->ptr())[str_length - dec_length]= lc->decimal_point;
+ }
+ return str;
+}
+
+
+void Item_func_format::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("format("));
+ args[0]->print(str, query_type);
+ str->append(',');
+ args[1]->print(str, query_type);
+ if(arg_count > 2)
+ {
+ str->append(',');
+ args[2]->print(str,query_type);
+ }
+ str->append(')');
+}
+
+void Item_func_elt::fix_length_and_dec()
+{
+ uint32 char_length= 0;
+ decimals=0;
+
+ if (agg_arg_charsets_for_string_result(collation, args + 1, arg_count - 1))
+ return;
+
+ for (uint i= 1 ; i < arg_count ; i++)
+ {
+ set_if_bigger(char_length, args[i]->max_char_length());
+ set_if_bigger(decimals,args[i]->decimals);
+ }
+ fix_char_length(char_length);
+ maybe_null=1; // NULL if wrong first arg
+}
+
+
+double Item_func_elt::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint tmp;
+ null_value=1;
+ if ((tmp=(uint) args[0]->val_int()) == 0 || tmp >= arg_count)
+ return 0.0;
+ double result= args[tmp]->val_real();
+ null_value= args[tmp]->null_value;
+ return result;
+}
+
+
+longlong Item_func_elt::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint tmp;
+ null_value=1;
+ if ((tmp=(uint) args[0]->val_int()) == 0 || tmp >= arg_count)
+ return 0;
+
+ longlong result= args[tmp]->val_int();
+ null_value= args[tmp]->null_value;
+ return result;
+}
+
+
+String *Item_func_elt::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uint tmp;
+ null_value=1;
+ if ((tmp=(uint) args[0]->val_int()) == 0 || tmp >= arg_count)
+ return NULL;
+
+ String *result= args[tmp]->val_str(str);
+ if (result)
+ result->set_charset(collation.collation);
+ null_value= args[tmp]->null_value;
+ return result;
+}
+
+
+void Item_func_make_set::fix_length_and_dec()
+{
+ uint32 char_length= arg_count - 2; /* Separators */
+
+ if (agg_arg_charsets_for_string_result(collation, args + 1, arg_count - 1))
+ return;
+
+ for (uint i=1 ; i < arg_count ; i++)
+ char_length+= args[i]->max_char_length();
+ fix_char_length(char_length);
+}
+
+
+String *Item_func_make_set::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ ulonglong bits;
+ bool first_found=0;
+ Item **ptr=args+1;
+ String *result= make_empty_result();
+
+ bits=args[0]->val_int();
+ if ((null_value=args[0]->null_value))
+ return NULL;
+
+ if (arg_count < 65)
+ bits &= ((ulonglong) 1 << (arg_count-1))-1;
+
+ for (; bits; bits >>= 1, ptr++)
+ {
+ if (bits & 1)
+ {
+ String *res= (*ptr)->val_str(str);
+ if (res) // Skip nulls
+ {
+ if (!first_found)
+ { // First argument
+ first_found=1;
+ if (res != str)
+ result=res; // Use original string
+ else
+ {
+ if (tmp_str.copy(*res)) // Don't use 'str'
+ return make_empty_result();
+ result= &tmp_str;
+ }
+ }
+ else
+ {
+ if (result != &tmp_str)
+ { // Copy data to tmp_str
+ if (tmp_str.alloc(result->length()+res->length()+1) ||
+ tmp_str.copy(*result))
+ return make_empty_result();
+ result= &tmp_str;
+ }
+ if (tmp_str.append(STRING_WITH_LEN(","), &my_charset_bin) || tmp_str.append(*res))
+ return make_empty_result();
+ }
+ }
+ }
+ }
+ return result;
+}
+
+
+String *Item_func_char::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ str->length(0);
+ str->set_charset(collation.collation);
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ int32 num=(int32) args[i]->val_int();
+ if (!args[i]->null_value)
+ {
+ char tmp[4];
+ if (num & 0xFF000000L)
+ {
+ mi_int4store(tmp, num);
+ str->append(tmp, 4, &my_charset_bin);
+ }
+ else if (num & 0xFF0000L)
+ {
+ mi_int3store(tmp, num);
+ str->append(tmp, 3, &my_charset_bin);
+ }
+ else if (num & 0xFF00L)
+ {
+ mi_int2store(tmp, num);
+ str->append(tmp, 2, &my_charset_bin);
+ }
+ else
+ {
+ tmp[0]= (char) num;
+ str->append(tmp, 1, &my_charset_bin);
+ }
+ }
+ }
+ str->realloc(str->length()); // Add end 0 (for Purify)
+ return check_well_formed_result(str);
+}
+
+
+inline String* alloc_buffer(String *res,String *str,String *tmp_value,
+ ulong length)
+{
+ if (res->alloced_length() < length)
+ {
+ if (str->alloced_length() >= length)
+ {
+ (void) str->copy(*res);
+ str->length(length);
+ return str;
+ }
+ if (tmp_value->alloc(length))
+ return 0;
+ (void) tmp_value->copy(*res);
+ tmp_value->length(length);
+ return tmp_value;
+ }
+ res->length(length);
+ return res;
+}
+
+
+void Item_func_repeat::fix_length_and_dec()
+{
+ agg_arg_charsets_for_string_result(collation, args, 1);
+ DBUG_ASSERT(collation.collation != NULL);
+ if (args[1]->const_item())
+ {
+ /* must be longlong to avoid truncation */
+ longlong count= args[1]->val_int();
+
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Set here so that rest of code sees out-of-bound value as such. */
+ if (args[1]->null_value)
+ count= 0;
+ else if (count > INT_MAX32)
+ count= INT_MAX32;
+
+ ulonglong char_length= (ulonglong) args[0]->max_char_length() * count;
+ fix_char_length_ulonglong(char_length);
+ }
+ else
+ {
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+ }
+}
+
+/**
+ Item_func_repeat::str is carefully written to avoid reallocs
+ as much as possible at the cost of a local buffer
+*/
+
+String *Item_func_repeat::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uint length,tot_length;
+ char *to;
+ /* must be longlong to avoid truncation */
+ longlong count= args[1]->val_int();
+ String *res= args[0]->val_str(str);
+
+ if (args[0]->null_value || args[1]->null_value)
+ goto err; // string and/or delim are null
+ null_value= 0;
+
+ if (count <= 0 && (count == 0 || !args[1]->unsigned_flag))
+ return make_empty_result();
+
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Bounds check on count: If this is triggered, we will error. */
+ if ((ulonglong) count > INT_MAX32)
+ count= INT_MAX32;
+ if (count == 1) // To avoid reallocs
+ return res;
+ length=res->length();
+ // Safe length check
+ if (length > current_thd->variables.max_allowed_packet / (uint) count)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(), current_thd->variables.max_allowed_packet);
+ goto err;
+ }
+ tot_length= length*(uint) count;
+ if (!(res= alloc_buffer(res,str,&tmp_value,tot_length)))
+ goto err;
+
+ to=(char*) res->ptr()+length;
+ while (--count)
+ {
+ memcpy(to,res->ptr(),length);
+ to+=length;
+ }
+ return (res);
+
+err:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_space::fix_length_and_dec()
+{
+ collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII);
+ if (args[0]->const_item())
+ {
+ /* must be longlong to avoid truncation */
+ longlong count= args[0]->val_int();
+ if (args[0]->null_value)
+ goto end;
+ /*
+ Assumes that the maximum length of a String is < INT_MAX32.
+ Set here so that rest of code sees out-of-bound value as such.
+ */
+ if (count > INT_MAX32)
+ count= INT_MAX32;
+ fix_char_length_ulonglong(count);
+ return;
+ }
+
+end:
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+}
+
+
+String *Item_func_space::val_str(String *str)
+{
+ uint tot_length;
+ longlong count= args[0]->val_int();
+ const CHARSET_INFO *cs= collation.collation;
+
+ if (args[0]->null_value)
+ goto err; // string and/or delim are null
+ null_value= 0;
+
+ if (count <= 0 && (count == 0 || !args[0]->unsigned_flag))
+ return make_empty_result();
+ /*
+ Assumes that the maximum length of a String is < INT_MAX32.
+ Bounds check on count: If this is triggered, we will error.
+ */
+ if ((ulonglong) count > INT_MAX32)
+ count= INT_MAX32;
+
+ // Safe length check
+ tot_length= (uint) count * cs->mbminlen;
+ if (tot_length > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(),
+ current_thd->variables.max_allowed_packet);
+ goto err;
+ }
+
+ if (str->alloc(tot_length))
+ goto err;
+ str->length(tot_length);
+ str->set_charset(cs);
+ cs->cset->fill(cs, (char*) str->ptr(), tot_length, ' ');
+ return str;
+
+err:
+ null_value= 1;
+ return 0;
+}
+
+
+void Item_func_binlog_gtid_pos::fix_length_and_dec()
+{
+ collation.set(system_charset_info);
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+}
+
+
+String *Item_func_binlog_gtid_pos::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+#ifndef HAVE_REPLICATION
+ null_value= 0;
+ str->copy("", 0, system_charset_info);
+ return str;
+#else
+ String name_str, *name;
+ longlong pos;
+
+ if (args[0]->null_value || args[1]->null_value)
+ goto err;
+
+ name= args[0]->val_str(&name_str);
+ pos= args[1]->val_int();
+
+ if (pos < 0 || pos > UINT_MAX32)
+ goto err;
+
+ if (gtid_state_from_binlog_pos(name->c_ptr_safe(), (uint32)pos, str))
+ goto err;
+ null_value= 0;
+ return str;
+
+err:
+ null_value= 1;
+ return NULL;
+#endif /* !HAVE_REPLICATION */
+}
+
+
+void Item_func_rpad::fix_length_and_dec()
+{
+ // Handle character set for args[0] and args[2].
+ if (agg_arg_charsets_for_string_result(collation, &args[0], 2, 2))
+ return;
+ if (args[1]->const_item())
+ {
+ ulonglong char_length= (ulonglong) args[1]->val_int();
+ DBUG_ASSERT(collation.collation->mbmaxlen > 0);
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Set here so that rest of code sees out-of-bound value as such. */
+ if (args[1]->null_value)
+ char_length= 0;
+ else if (char_length > INT_MAX32)
+ char_length= INT_MAX32;
+ fix_char_length_ulonglong(char_length);
+ }
+ else
+ {
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+ }
+}
+
+
+String *Item_func_rpad::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uint32 res_byte_length,res_char_length,pad_char_length,pad_byte_length;
+ char *to;
+ const char *ptr_pad;
+ /* must be longlong to avoid truncation */
+ longlong count= args[1]->val_int();
+ longlong byte_count;
+ String *res= args[0]->val_str(str);
+ String *rpad= args[2]->val_str(&rpad_str);
+
+ if (!res || args[1]->null_value || !rpad ||
+ ((count < 0) && !args[1]->unsigned_flag))
+ goto err;
+ null_value=0;
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Set here so that rest of code sees out-of-bound value as such. */
+ if ((ulonglong) count > INT_MAX32)
+ count= INT_MAX32;
+ /*
+ There is one exception not handled (intentionaly) by the character set
+ aggregation code. If one string is strong side and is binary, and
+ another one is weak side and is a multi-byte character string,
+ then we need to operate on the second string in terms on bytes when
+ calling ::numchars() and ::charpos(), rather than in terms of characters.
+ Lets substitute its character set to binary.
+ */
+ if (collation.collation == &my_charset_bin)
+ {
+ res->set_charset(&my_charset_bin);
+ rpad->set_charset(&my_charset_bin);
+ }
+
+ if (count <= (res_char_length= res->numchars()))
+ { // String to pad is big enough
+ res->length(res->charpos((int) count)); // Shorten result if longer
+ return (res);
+ }
+ pad_char_length= rpad->numchars();
+
+ byte_count= count * collation.collation->mbmaxlen;
+ if ((ulonglong) byte_count > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(), current_thd->variables.max_allowed_packet);
+ goto err;
+ }
+ if (args[2]->null_value || !pad_char_length)
+ goto err;
+ res_byte_length= res->length(); /* Must be done before alloc_buffer */
+ if (!(res= alloc_buffer(res,str,&tmp_value, (ulong) byte_count)))
+ goto err;
+
+ to= (char*) res->ptr()+res_byte_length;
+ ptr_pad=rpad->ptr();
+ pad_byte_length= rpad->length();
+ count-= res_char_length;
+ for ( ; (uint32) count > pad_char_length; count-= pad_char_length)
+ {
+ memcpy(to,ptr_pad,pad_byte_length);
+ to+= pad_byte_length;
+ }
+ if (count)
+ {
+ pad_byte_length= rpad->charpos((int) count);
+ memcpy(to,ptr_pad,(size_t) pad_byte_length);
+ to+= pad_byte_length;
+ }
+ res->length((uint) (to- (char*) res->ptr()));
+ return (res);
+
+ err:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_lpad::fix_length_and_dec()
+{
+ // Handle character set for args[0] and args[2].
+ if (agg_arg_charsets_for_string_result(collation, &args[0], 2, 2))
+ return;
+
+ if (args[1]->const_item())
+ {
+ ulonglong char_length= (ulonglong) args[1]->val_int();
+ DBUG_ASSERT(collation.collation->mbmaxlen > 0);
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Set here so that rest of code sees out-of-bound value as such. */
+ if (args[1]->null_value)
+ char_length= 0;
+ else if (char_length > INT_MAX32)
+ char_length= INT_MAX32;
+ fix_char_length_ulonglong(char_length);
+ }
+ else
+ {
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+ }
+}
+
+
+String *Item_func_lpad::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uint32 res_char_length,pad_char_length;
+ /* must be longlong to avoid truncation */
+ longlong count= args[1]->val_int();
+ longlong byte_count;
+ String *res= args[0]->val_str(&tmp_value);
+ String *pad= args[2]->val_str(&lpad_str);
+
+ if (!res || args[1]->null_value || !pad ||
+ ((count < 0) && !args[1]->unsigned_flag))
+ goto err;
+ null_value=0;
+ /* Assumes that the maximum length of a String is < INT_MAX32. */
+ /* Set here so that rest of code sees out-of-bound value as such. */
+ if ((ulonglong) count > INT_MAX32)
+ count= INT_MAX32;
+
+ /*
+ There is one exception not handled (intentionaly) by the character set
+ aggregation code. If one string is strong side and is binary, and
+ another one is weak side and is a multi-byte character string,
+ then we need to operate on the second string in terms on bytes when
+ calling ::numchars() and ::charpos(), rather than in terms of characters.
+ Lets substitute its character set to binary.
+ */
+ if (collation.collation == &my_charset_bin)
+ {
+ res->set_charset(&my_charset_bin);
+ pad->set_charset(&my_charset_bin);
+ }
+
+ res_char_length= res->numchars();
+
+ if (count <= res_char_length)
+ {
+ res->length(res->charpos((int) count));
+ return res;
+ }
+
+ pad_char_length= pad->numchars();
+ byte_count= count * collation.collation->mbmaxlen;
+
+ if ((ulonglong) byte_count > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(), current_thd->variables.max_allowed_packet);
+ goto err;
+ }
+
+ if (args[2]->null_value || !pad_char_length ||
+ str->alloc((uint32) byte_count))
+ goto err;
+
+ str->length(0);
+ str->set_charset(collation.collation);
+ count-= res_char_length;
+ while (count >= pad_char_length)
+ {
+ str->append(*pad);
+ count-= pad_char_length;
+ }
+ if (count > 0)
+ str->append(pad->ptr(), pad->charpos((int) count), collation.collation);
+
+ str->append(*res);
+ null_value= 0;
+ return str;
+
+err:
+ null_value= 1;
+ return 0;
+}
+
+
+String *Item_func_conv::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(str);
+ char *endptr,ans[65],*ptr;
+ longlong dec;
+ int from_base= (int) args[1]->val_int();
+ int to_base= (int) args[2]->val_int();
+ int err;
+
+ // Note that abs(INT_MIN) is undefined.
+ if (args[0]->null_value || args[1]->null_value || args[2]->null_value ||
+ from_base == INT_MIN || to_base == INT_MIN ||
+ abs(to_base) > 36 || abs(to_base) < 2 ||
+ abs(from_base) > 36 || abs(from_base) < 2 || !(res->length()))
+ {
+ null_value= 1;
+ return NULL;
+ }
+ null_value= 0;
+ unsigned_flag= !(from_base < 0);
+
+ if (args[0]->field_type() == MYSQL_TYPE_BIT)
+ {
+ /*
+ Special case: The string representation of BIT doesn't resemble the
+ decimal representation, so we shouldn't change it to string and then to
+ decimal.
+ */
+ dec= args[0]->val_int();
+ }
+ else
+ {
+ if (from_base < 0)
+ dec= my_strntoll(res->charset(), res->ptr(), res->length(),
+ -from_base, &endptr, &err);
+ else
+ dec= (longlong) my_strntoull(res->charset(), res->ptr(), res->length(),
+ from_base, &endptr, &err);
+ }
+
+ if (!(ptr= longlong2str(dec, ans, to_base)) ||
+ str->copy(ans, (uint32) (ptr - ans), default_charset()))
+ {
+ null_value= 1;
+ return NULL;
+ }
+ return str;
+}
+
+
+String *Item_func_conv_charset::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (use_cached_value)
+ return null_value ? 0 : &str_value;
+ String *arg= args[0]->val_str(str);
+ uint dummy_errors;
+ if (args[0]->null_value)
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value= tmp_value.copy(arg->ptr(), arg->length(), arg->charset(),
+ conv_charset, &dummy_errors);
+ return null_value ? 0 : check_well_formed_result(&tmp_value);
+}
+
+void Item_func_conv_charset::fix_length_and_dec()
+{
+ collation.set(conv_charset, DERIVATION_IMPLICIT);
+ fix_char_length(args[0]->max_char_length());
+}
+
+void Item_func_conv_charset::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("convert("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" using "));
+ str->append(conv_charset->csname);
+ str->append(')');
+}
+
+String *Item_func_set_collation::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ str=args[0]->val_str(str);
+ if ((null_value=args[0]->null_value))
+ return 0;
+ str->set_charset(collation.collation);
+ return str;
+}
+
+void Item_func_set_collation::fix_length_and_dec()
+{
+ CHARSET_INFO *set_collation;
+ const char *colname;
+ String tmp, *str= args[1]->val_str(&tmp);
+ colname= str->c_ptr();
+ if (colname == binary_keyword)
+ set_collation= get_charset_by_csname(args[0]->collation.collation->csname,
+ MY_CS_BINSORT,MYF(0));
+ else
+ {
+ if (!(set_collation= mysqld_collation_get_by_name(colname)))
+ return;
+ }
+
+ if (!set_collation ||
+ !my_charset_same(args[0]->collation.collation,set_collation))
+ {
+ my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0),
+ colname, args[0]->collation.collation->csname);
+ return;
+ }
+ collation.set(set_collation, DERIVATION_EXPLICIT,
+ args[0]->collation.repertoire);
+ max_length= args[0]->max_length;
+}
+
+
+bool Item_func_set_collation::eq(const Item *item, bool binary_cmp) const
+{
+ /* Assume we don't have rtti */
+ if (this == item)
+ return 1;
+ if (item->type() != FUNC_ITEM)
+ return 0;
+ Item_func *item_func=(Item_func*) item;
+ if (arg_count != item_func->arg_count ||
+ functype() != item_func->functype())
+ return 0;
+ Item_func_set_collation *item_func_sc=(Item_func_set_collation*) item;
+ if (collation.collation != item_func_sc->collation.collation)
+ return 0;
+ for (uint i=0; i < arg_count ; i++)
+ if (!args[i]->eq(item_func_sc->args[i], binary_cmp))
+ return 0;
+ return 1;
+}
+
+
+void Item_func_set_collation::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" collate "));
+ DBUG_ASSERT(args[1]->basic_const_item() &&
+ args[1]->type() == Item::STRING_ITEM);
+ ((Item_string *)args[1])->print_value(str);
+ str->append(')');
+}
+
+String *Item_func_charset::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uint dummy_errors;
+
+ CHARSET_INFO *cs= args[0]->charset_for_protocol();
+ null_value= 0;
+ str->copy(cs->csname, (uint) strlen(cs->csname),
+ &my_charset_latin1, collation.collation, &dummy_errors);
+ return str;
+}
+
+String *Item_func_collation::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uint dummy_errors;
+ CHARSET_INFO *cs= args[0]->charset_for_protocol();
+
+ null_value= 0;
+ str->copy(cs->name, (uint) strlen(cs->name),
+ &my_charset_latin1, collation.collation, &dummy_errors);
+ return str;
+}
+
+
+void Item_func_weight_string::fix_length_and_dec()
+{
+ CHARSET_INFO *cs= args[0]->collation.collation;
+ collation.set(&my_charset_bin, args[0]->collation.derivation);
+ flags= my_strxfrm_flag_normalize(flags, cs->levels_for_order);
+ /*
+ Use result_length if it was given explicitly in constructor,
+ otherwise calculate max_length using argument's max_length
+ and "nweights".
+ */
+ if (!(max_length= result_length))
+ {
+ uint char_length;
+ char_length= ((cs->state & MY_CS_STRNXFRM_BAD_NWEIGHTS) || !nweights) ?
+ args[0]->max_char_length() : nweights;
+ max_length= cs->coll->strnxfrmlen(cs, char_length * cs->mbmaxlen);
+ }
+ maybe_null= 1;
+}
+
+
+/* Return a weight_string according to collation */
+String *Item_func_weight_string::val_str(String *str)
+{
+ String *res;
+ CHARSET_INFO *cs= args[0]->collation.collation;
+ uint tmp_length, frm_length;
+ DBUG_ASSERT(fixed == 1);
+
+ if (args[0]->result_type() != STRING_RESULT ||
+ !(res= args[0]->val_str(str)))
+ goto nl;
+
+ /*
+ Use result_length if it was given in constructor
+ explicitly, otherwise calculate result length
+ from argument and "nweights".
+ */
+ if (!(tmp_length= result_length))
+ {
+ uint char_length;
+ if (cs->state & MY_CS_STRNXFRM_BAD_NWEIGHTS)
+ {
+ /*
+ latin2_czech_cs and cp1250_czech_cs do not support
+ the "nweights" limit in strnxfrm(). Use the full length.
+ */
+ char_length= res->length();
+ }
+ else
+ {
+ /*
+ If we don't need to pad the result with spaces, then it should be
+ OK to calculate character length of the argument approximately:
+ "res->length() / cs->mbminlen" can return a number that is
+ bigger than the real number of characters in the string, so
+ we'll allocate a little bit more memory but avoid calling
+ the slow res->numchars().
+ In case if we do need to pad with spaces, we call res->numchars()
+ to know the true number of characters.
+ */
+ if (!(char_length= nweights))
+ char_length= (flags & MY_STRXFRM_PAD_WITH_SPACE) ?
+ res->numchars() : (res->length() / cs->mbminlen);
+ }
+ tmp_length= cs->coll->strnxfrmlen(cs, char_length * cs->mbmaxlen);
+ }
+
+ if(tmp_length > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
+ current_thd->variables.max_allowed_packet);
+ goto nl;
+ }
+
+ if (tmp_value.alloc(tmp_length))
+ goto nl;
+
+ frm_length= cs->coll->strnxfrm(cs,
+ (uchar *) tmp_value.ptr(), tmp_length,
+ nweights ? nweights : tmp_length,
+ (const uchar *) res->ptr(), res->length(),
+ flags);
+ DBUG_ASSERT(frm_length <= tmp_length);
+
+ tmp_value.length(frm_length);
+ null_value= 0;
+ return &tmp_value;
+
+nl:
+ null_value= 1;
+ return 0;
+}
+
+
+String *Item_func_hex::val_str_ascii(String *str)
+{
+ String *res;
+ DBUG_ASSERT(fixed == 1);
+ if (args[0]->result_type() != STRING_RESULT)
+ {
+ ulonglong dec;
+ char ans[65],*ptr;
+ /* Return hex of unsigned longlong value */
+ if (args[0]->result_type() == REAL_RESULT ||
+ args[0]->result_type() == DECIMAL_RESULT)
+ {
+ double val= args[0]->val_real();
+ if ((val <= (double) LONGLONG_MIN) ||
+ (val >= (double) (ulonglong) ULONGLONG_MAX))
+ dec= ~(longlong) 0;
+ else
+ dec= (ulonglong) (val + (val > 0 ? 0.5 : -0.5));
+ }
+ else
+ dec= (ulonglong) args[0]->val_int();
+
+ if ((null_value= args[0]->null_value))
+ return 0;
+
+ if (!(ptr= longlong2str(dec, ans, 16)) ||
+ str->copy(ans,(uint32) (ptr - ans),
+ &my_charset_numeric))
+ return make_empty_result(); // End of memory
+ return str;
+ }
+
+ /* Convert given string to a hex string, character by character */
+ res= args[0]->val_str(str);
+ if (!res || tmp_value.alloc(res->length()*2+1))
+ {
+ null_value=1;
+ return 0;
+ }
+ null_value=0;
+ tmp_value.length(res->length()*2);
+ tmp_value.set_charset(&my_charset_latin1);
+
+ octet2hex((char*) tmp_value.ptr(), res->ptr(), res->length());
+ return &tmp_value;
+}
+
+ /** Convert given hex string to a binary string. */
+
+String *Item_func_unhex::val_str(String *str)
+{
+ const char *from, *end;
+ char *to;
+ String *res;
+ uint length;
+ DBUG_ASSERT(fixed == 1);
+
+ res= args[0]->val_str(str);
+ if (!res || tmp_value.alloc(length= (1+res->length())/2))
+ {
+ null_value=1;
+ return 0;
+ }
+
+ from= res->ptr();
+ null_value= 0;
+ tmp_value.length(length);
+ to= (char*) tmp_value.ptr();
+ if (res->length() % 2)
+ {
+ int hex_char;
+ *to++= hex_char= hexchar_to_int(*from++);
+ if ((null_value= (hex_char == -1)))
+ return 0;
+ }
+ for (end=res->ptr()+res->length(); from < end ; from+=2, to++)
+ {
+ int hex_char;
+ *to= (hex_char= hexchar_to_int(from[0])) << 4;
+ if ((null_value= (hex_char == -1)))
+ return 0;
+ *to|= hex_char= hexchar_to_int(from[1]);
+ if ((null_value= (hex_char == -1)))
+ return 0;
+ }
+ return &tmp_value;
+}
+
+
+#ifndef DBUG_OFF
+String *Item_func_like_range::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong nbytes= args[1]->val_int();
+ String *res= args[0]->val_str(str);
+ size_t min_len, max_len;
+ CHARSET_INFO *cs= collation.collation;
+
+ if (!res || args[0]->null_value || args[1]->null_value ||
+ nbytes < 0 || nbytes > MAX_BLOB_WIDTH ||
+ min_str.alloc(nbytes) || max_str.alloc(nbytes))
+ goto err;
+ null_value=0;
+
+ if (cs->coll->like_range(cs, res->ptr(), res->length(),
+ '\\', '_', '%', nbytes,
+ (char*) min_str.ptr(), (char*) max_str.ptr(),
+ &min_len, &max_len))
+ goto err;
+
+ min_str.set_charset(collation.collation);
+ max_str.set_charset(collation.collation);
+ min_str.length(min_len);
+ max_str.length(max_len);
+
+ return is_min ? &min_str : &max_str;
+
+err:
+ null_value= 1;
+ return 0;
+}
+#endif
+
+
+void Item_func_binary::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("cast("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" as binary)"));
+}
+
+
+#include <my_dir.h> // For my_stat
+
+String *Item_load_file::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *file_name;
+ File file;
+ MY_STAT stat_info;
+ char path[FN_REFLEN];
+ DBUG_ENTER("load_file");
+
+ if (!(file_name= args[0]->val_str(str))
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ || !(current_thd->security_ctx->master_access & FILE_ACL)
+#endif
+ )
+ goto err;
+
+ (void) fn_format(path, file_name->c_ptr_safe(), mysql_real_data_home, "",
+ MY_RELATIVE_PATH | MY_UNPACK_FILENAME);
+
+ /* Read only allowed from within dir specified by secure_file_priv */
+ if (!is_secure_file_path(path))
+ goto err;
+
+ if (!mysql_file_stat(key_file_loadfile, path, &stat_info, MYF(0)))
+ goto err;
+
+ if (!(stat_info.st_mode & S_IROTH))
+ {
+ /* my_error(ER_TEXTFILE_NOT_READABLE, MYF(0), file_name->c_ptr()); */
+ goto err;
+ }
+ if (stat_info.st_size > (long) current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(), current_thd->variables.max_allowed_packet);
+ goto err;
+ }
+ if (tmp_value.alloc((size_t)stat_info.st_size))
+ goto err;
+ if ((file= mysql_file_open(key_file_loadfile,
+ file_name->ptr(), O_RDONLY, MYF(0))) < 0)
+ goto err;
+ if (mysql_file_read(file, (uchar*) tmp_value.ptr(), stat_info.st_size,
+ MYF(MY_NABP)))
+ {
+ mysql_file_close(file, MYF(0));
+ goto err;
+ }
+ tmp_value.length((uint32)stat_info.st_size);
+ mysql_file_close(file, MYF(0));
+ null_value = 0;
+ DBUG_RETURN(&tmp_value);
+
+err:
+ null_value = 1;
+ DBUG_RETURN(0);
+}
+
+
+String* Item_func_export_set::val_str(String* str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String yes_buf, no_buf, sep_buf;
+ const ulonglong the_set = (ulonglong) args[0]->val_int();
+ const String *yes= args[1]->val_str(&yes_buf);
+ const String *no= args[2]->val_str(&no_buf);
+ const String *sep= NULL;
+
+ uint num_set_values = 64;
+ str->length(0);
+ str->set_charset(collation.collation);
+
+ /* Check if some argument is a NULL value */
+ if (args[0]->null_value || args[1]->null_value || args[2]->null_value)
+ {
+ null_value= true;
+ return NULL;
+ }
+ /*
+ Arg count can only be 3, 4 or 5 here. This is guaranteed from the
+ grammar for EXPORT_SET()
+ */
+ switch(arg_count) {
+ case 5:
+ num_set_values = (uint) args[4]->val_int();
+ if (num_set_values > 64)
+ num_set_values=64;
+ if (args[4]->null_value)
+ {
+ null_value= true;
+ return NULL;
+ }
+ /* Fall through */
+ case 4:
+ if (!(sep = args[3]->val_str(&sep_buf))) // Only true if NULL
+ {
+ null_value= true;
+ return NULL;
+ }
+ break;
+ case 3:
+ {
+ /* errors is not checked - assume "," can always be converted */
+ uint errors;
+ sep_buf.copy(STRING_WITH_LEN(","), &my_charset_bin,
+ collation.collation, &errors);
+ sep = &sep_buf;
+ }
+ break;
+ default:
+ DBUG_ASSERT(0); // cannot happen
+ }
+ null_value= false;
+
+ const ulong max_allowed_packet= current_thd->variables.max_allowed_packet;
+ const uint num_separators= num_set_values > 0 ? num_set_values - 1 : 0;
+ const ulonglong max_total_length=
+ num_set_values * MY_MAX(yes->length(), no->length()) +
+ num_separators * sep->length();
+
+ if (unlikely(max_total_length > max_allowed_packet))
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ func_name(), max_allowed_packet);
+ null_value= true;
+ return NULL;
+ }
+
+ uint ix;
+ ulonglong mask;
+ for (ix= 0, mask=0x1; ix < num_set_values; ++ix, mask = (mask << 1))
+ {
+ if (the_set & mask)
+ str->append(*yes);
+ else
+ str->append(*no);
+ if (ix != num_separators)
+ str->append(*sep);
+ }
+ return str;
+}
+
+void Item_func_export_set::fix_length_and_dec()
+{
+ uint32 length= MY_MAX(args[1]->max_char_length(), args[2]->max_char_length());
+ uint32 sep_length= (arg_count > 3 ? args[3]->max_char_length() : 1);
+
+ if (agg_arg_charsets_for_string_result(collation,
+ args + 1, MY_MIN(4, arg_count) - 1))
+ return;
+ fix_char_length(length * 64 + sep_length * 63);
+}
+
+
+#define get_esc_bit(mask, num) (1 & (*((mask) + ((num) >> 3))) >> ((num) & 7))
+
+/**
+ QUOTE() function returns argument string in single quotes suitable for
+ using in a SQL statement.
+
+ Adds a \\ before all characters that needs to be escaped in a SQL string.
+ We also escape '^Z' (END-OF-FILE in windows) to avoid probelms when
+ running commands from a file in windows.
+
+ This function is very useful when you want to generate SQL statements.
+
+ @note
+ QUOTE(NULL) returns the string 'NULL' (4 letters, without quotes).
+
+ @retval
+ str Quoted string
+ @retval
+ NULL Out of memory.
+*/
+
+String *Item_func_quote::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ /*
+ Bit mask that has 1 for set for the position of the following characters:
+ 0, \, ' and ^Z
+ */
+
+ static uchar escmask[32]=
+ {
+ 0x01, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ char *from, *to, *end, *start;
+ String *arg= args[0]->val_str(str);
+ uint arg_length, new_length;
+ if (!arg) // Null argument
+ {
+ /* Return the string 'NULL' */
+ str->copy(STRING_WITH_LEN("NULL"), collation.collation);
+ null_value= 0;
+ return str;
+ }
+
+ arg_length= arg->length();
+
+ if (collation.collation->mbmaxlen == 1)
+ {
+ new_length= arg_length + 2; /* for beginning and ending ' signs */
+ for (from= (char*) arg->ptr(), end= from + arg_length; from < end; from++)
+ new_length+= get_esc_bit(escmask, (uchar) *from);
+ }
+ else
+ {
+ new_length= (arg_length * 2) + /* For string characters */
+ (2 * collation.collation->mbmaxlen); /* For quotes */
+ }
+
+ if (tmp_value.alloc(new_length))
+ goto null;
+
+ if (collation.collation->mbmaxlen > 1)
+ {
+ CHARSET_INFO *cs= collation.collation;
+ int mblen;
+ uchar *to_end;
+ to= (char*) tmp_value.ptr();
+ to_end= (uchar*) to + new_length;
+
+ /* Put leading quote */
+ if ((mblen= cs->cset->wc_mb(cs, '\'', (uchar *) to, to_end)) <= 0)
+ goto null;
+ to+= mblen;
+
+ for (start= (char*) arg->ptr(), end= start + arg_length; start < end; )
+ {
+ my_wc_t wc;
+ bool escape;
+ if ((mblen= cs->cset->mb_wc(cs, &wc, (uchar*) start, (uchar*) end)) <= 0)
+ goto null;
+ start+= mblen;
+ switch (wc) {
+ case 0: escape= 1; wc= '0'; break;
+ case '\032': escape= 1; wc= 'Z'; break;
+ case '\'': escape= 1; break;
+ case '\\': escape= 1; break;
+ default: escape= 0; break;
+ }
+ if (escape)
+ {
+ if ((mblen= cs->cset->wc_mb(cs, '\\', (uchar*) to, to_end)) <= 0)
+ goto null;
+ to+= mblen;
+ }
+ if ((mblen= cs->cset->wc_mb(cs, wc, (uchar*) to, to_end)) <= 0)
+ goto null;
+ to+= mblen;
+ }
+
+ /* Put trailing quote */
+ if ((mblen= cs->cset->wc_mb(cs, '\'', (uchar *) to, to_end)) <= 0)
+ goto null;
+ to+= mblen;
+ new_length= to - tmp_value.ptr();
+ goto ret;
+ }
+
+ /*
+ We replace characters from the end to the beginning
+ */
+ to= (char*) tmp_value.ptr() + new_length - 1;
+ *to--= '\'';
+ for (start= (char*) arg->ptr(),end= start + arg_length; end-- != start; to--)
+ {
+ /*
+ We can't use the bitmask here as we want to replace \O and ^Z with 0
+ and Z
+ */
+ switch (*end) {
+ case 0:
+ *to--= '0';
+ *to= '\\';
+ break;
+ case '\032':
+ *to--= 'Z';
+ *to= '\\';
+ break;
+ case '\'':
+ case '\\':
+ *to--= *end;
+ *to= '\\';
+ break;
+ default:
+ *to= *end;
+ break;
+ }
+ }
+ *to= '\'';
+
+ret:
+ tmp_value.length(new_length);
+ tmp_value.set_charset(collation.collation);
+ null_value= 0;
+ return &tmp_value;
+
+null:
+ null_value= 1;
+ return 0;
+}
+
+longlong Item_func_uncompressed_length::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(&value);
+ if (!res)
+ {
+ null_value=1;
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+ if (res->is_empty()) return 0;
+
+ /*
+ If length is <= 4 bytes, data is corrupt. This is the best we can do
+ to detect garbage input without decompressing it.
+ */
+ if (res->length() <= 4)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ZLIB_Z_DATA_ERROR,
+ ER(ER_ZLIB_Z_DATA_ERROR));
+ null_value= 1;
+ return 0;
+ }
+
+ /*
+ res->ptr() using is safe because we have tested that string is at least
+ 5 bytes long.
+ res->c_ptr() is not used because:
+ - we do not need \0 terminated string to get first 4 bytes
+ - c_ptr() tests simbol after string end (uninitialiozed memory) which
+ confuse valgrind
+ */
+ return uint4korr(res->ptr()) & 0x3FFFFFFF;
+}
+
+longlong Item_func_crc32::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res=args[0]->val_str(&value);
+ if (!res)
+ {
+ null_value=1;
+ return 0; /* purecov: inspected */
+ }
+ null_value=0;
+ return (longlong) crc32(0L, (uchar*)res->ptr(), res->length());
+}
+
+#ifdef HAVE_COMPRESS
+#include "zlib.h"
+
+String *Item_func_compress::val_str(String *str)
+{
+ int err= Z_OK, code;
+ size_t new_size;
+ String *res;
+ Byte *body;
+ char *tmp, *last_char;
+ DBUG_ASSERT(fixed == 1);
+
+ if (!(res= args[0]->val_str(str)))
+ {
+ null_value= 1;
+ return 0;
+ }
+ null_value= 0;
+ if (res->is_empty()) return res;
+
+ /*
+ Citation from zlib.h (comment for compress function):
+
+ Compresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total
+ size of the destination buffer, which must be at least 0.1% larger than
+ sourceLen plus 12 bytes.
+ We assume here that the buffer can't grow more than .25 %.
+ */
+ new_size= res->length() + res->length() / 5 + 12;
+
+ // Check new_size overflow: new_size <= res->length()
+ if (((uint32) (new_size+5) <= res->length()) ||
+ buffer.realloc((uint32) new_size + 4 + 1))
+ {
+ null_value= 1;
+ return 0;
+ }
+
+ body= ((Byte*)buffer.ptr()) + 4;
+
+ // As far as we have checked res->is_empty() we can use ptr()
+ if ((err= my_compress_buffer(body, &new_size, (const uchar *)res->ptr(),
+ res->length())) != Z_OK)
+ {
+ code= err==Z_MEM_ERROR ? ER_ZLIB_Z_MEM_ERROR : ER_ZLIB_Z_BUF_ERROR;
+ push_warning(current_thd,Sql_condition::WARN_LEVEL_WARN,code,ER(code));
+ null_value= 1;
+ return 0;
+ }
+
+ tmp= (char*)buffer.ptr(); // int4store is a macro; avoid side effects
+ int4store(tmp, res->length() & 0x3FFFFFFF);
+
+ /* This is to ensure that things works for CHAR fields, which trim ' ': */
+ last_char= ((char*)body)+new_size-1;
+ if (*last_char == ' ')
+ {
+ *++last_char= '.';
+ new_size++;
+ }
+
+ buffer.length((uint32)new_size + 4);
+ return &buffer;
+}
+
+
+String *Item_func_uncompress::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res= args[0]->val_str(str);
+ ulong new_size;
+ int err;
+ uint code;
+
+ if (!res)
+ goto err;
+ null_value= 0;
+ if (res->is_empty())
+ return res;
+
+ /* If length is less than 4 bytes, data is corrupt */
+ if (res->length() <= 4)
+ {
+ push_warning_printf(current_thd,Sql_condition::WARN_LEVEL_WARN,
+ ER_ZLIB_Z_DATA_ERROR,
+ ER(ER_ZLIB_Z_DATA_ERROR));
+ goto err;
+ }
+
+ /* Size of uncompressed data is stored as first 4 bytes of field */
+ new_size= uint4korr(res->ptr()) & 0x3FFFFFFF;
+ if (new_size > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd,Sql_condition::WARN_LEVEL_WARN,
+ ER_TOO_BIG_FOR_UNCOMPRESS,
+ ER(ER_TOO_BIG_FOR_UNCOMPRESS),
+ static_cast<int>(current_thd->variables.
+ max_allowed_packet));
+ goto err;
+ }
+ if (buffer.realloc((uint32)new_size))
+ goto err;
+
+ if ((err= uncompress((Byte*)buffer.ptr(), &new_size,
+ ((const Bytef*)res->ptr())+4,res->length()-4)) == Z_OK)
+ {
+ buffer.length((uint32) new_size);
+ return &buffer;
+ }
+
+ code= ((err == Z_BUF_ERROR) ? ER_ZLIB_Z_BUF_ERROR :
+ ((err == Z_MEM_ERROR) ? ER_ZLIB_Z_MEM_ERROR : ER_ZLIB_Z_DATA_ERROR));
+ push_warning(current_thd,Sql_condition::WARN_LEVEL_WARN,code,ER(code));
+
+err:
+ null_value= 1;
+ return 0;
+}
+#endif
+
+
+String *Item_func_uuid::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uchar guid[MY_UUID_SIZE];
+
+ str->realloc(MY_UUID_STRING_LENGTH+1);
+ str->length(MY_UUID_STRING_LENGTH);
+ str->set_charset(system_charset_info);
+ my_uuid(guid);
+ my_uuid2str(guid, (char *)str->ptr());
+
+ return str;
+}
+
+
+Item_func_dyncol_create::Item_func_dyncol_create(List<Item> &args,
+ DYNCALL_CREATE_DEF *dfs)
+ : Item_str_func(args), defs(dfs), vals(0), keys_num(NULL), keys_str(NULL),
+ names(FALSE), force_names(FALSE)
+{
+ DBUG_ASSERT((args.elements & 0x1) == 0); // even number of arguments
+}
+
+
+bool Item_func_dyncol_create::fix_fields(THD *thd, Item **ref)
+{
+ uint i;
+ bool res= Item_func::fix_fields(thd, ref); // no need Item_str_func here
+ if (!res)
+ {
+ vals= (DYNAMIC_COLUMN_VALUE *) alloc_root(thd->mem_root,
+ sizeof(DYNAMIC_COLUMN_VALUE) *
+ (arg_count / 2));
+ for (i= 0;
+ i + 1 < arg_count && args[i]->result_type() == INT_RESULT;
+ i+= 2)
+ ;
+ if (i + 1 < arg_count)
+ {
+ names= TRUE;
+ }
+
+ keys_num= (uint *) alloc_root(thd->mem_root,
+ (sizeof(LEX_STRING) > sizeof(uint) ?
+ sizeof(LEX_STRING) :
+ sizeof(uint)) *
+ (arg_count / 2));
+ keys_str= (LEX_STRING *) keys_num;
+ status_var_increment(thd->status_var.feature_dynamic_columns);
+ }
+ return res || vals == 0 || keys_num == 0;
+}
+
+
+void Item_func_dyncol_create::fix_length_and_dec()
+{
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= TRUE;
+ collation.set(&my_charset_bin);
+ decimals= 0;
+}
+
+bool Item_func_dyncol_create::prepare_arguments(bool force_names_arg)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String *res, tmp(buff, sizeof(buff), &my_charset_bin);
+ uint column_count= (arg_count / 2);
+ uint i;
+ my_decimal dtmp, *dres;
+ force_names= force_names_arg;
+
+ if (!(names || force_names))
+ {
+ for (i= 0; i < column_count; i++)
+ {
+ uint valpos= i * 2 + 1;
+ DYNAMIC_COLUMN_TYPE type= defs[i].type;
+ if (type == DYN_COL_NULL)
+ switch (args[valpos]->field_type())
+ {
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_GEOMETRY:
+ type= DYN_COL_STRING;
+ break;
+ default:
+ break;
+ }
+
+ if (type == DYN_COL_STRING &&
+ args[valpos]->type() == Item::FUNC_ITEM &&
+ ((Item_func *)args[valpos])->functype() == DYNCOL_FUNC)
+ {
+ force_names= 1;
+ break;
+ }
+ }
+ }
+
+ /* get values */
+ for (i= 0; i < column_count; i++)
+ {
+ uint valpos= i * 2 + 1;
+ DYNAMIC_COLUMN_TYPE type= defs[i].type;
+ if (type == DYN_COL_NULL) // auto detect
+ {
+ /*
+ We don't have a default here to ensure we get a warning if
+ one adds a new not handled MYSQL_TYPE_...
+ */
+ switch (args[valpos]->field_type()) {
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ type= DYN_COL_DECIMAL;
+ break;
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_BIT:
+ type= args[valpos]->unsigned_flag ? DYN_COL_UINT : DYN_COL_INT;
+ break;
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ type= DYN_COL_DOUBLE;
+ break;
+ case MYSQL_TYPE_NULL:
+ type= DYN_COL_NULL;
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_TIMESTAMP2:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_DATETIME2:
+ type= DYN_COL_DATETIME;
+ break;
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_NEWDATE:
+ type= DYN_COL_DATE;
+ break;
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_TIME2:
+ type= DYN_COL_TIME;
+ break;
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_GEOMETRY:
+ type= DYN_COL_STRING;
+ break;
+ }
+ }
+ if (type == DYN_COL_STRING &&
+ args[valpos]->type() == Item::FUNC_ITEM &&
+ ((Item_func *)args[valpos])->functype() == DYNCOL_FUNC)
+ {
+ DBUG_ASSERT(names || force_names);
+ type= DYN_COL_DYNCOL;
+ }
+ if (names || force_names)
+ {
+ res= args[i * 2]->val_str(&tmp);
+ if (res)
+ {
+ // guaranty UTF-8 string for names
+ if (my_charset_same(res->charset(), &my_charset_utf8_general_ci))
+ {
+ keys_str[i].length= res->length();
+ keys_str[i].str= sql_strmake(res->ptr(), res->length());
+ }
+ else
+ {
+ uint strlen;
+ uint dummy_errors;
+ char *str=
+ (char *)sql_alloc((strlen= res->length() *
+ my_charset_utf8_general_ci.mbmaxlen + 1));
+ if (str)
+ {
+ keys_str[i].length=
+ copy_and_convert(str, strlen, &my_charset_utf8_general_ci,
+ res->ptr(), res->length(), res->charset(),
+ &dummy_errors);
+ keys_str[i].str= str;
+ }
+ else
+ keys_str[i].length= 0;
+
+ }
+ }
+ else
+ {
+ keys_str[i].length= 0;
+ keys_str[i].str= NULL;
+ }
+ }
+ else
+ keys_num[i]= (uint) args[i * 2]->val_int();
+ if (args[i * 2]->null_value)
+ {
+ /* to make cleanup possible */
+ for (; i < column_count; i++)
+ vals[i].type= DYN_COL_NULL;
+ return 1;
+ }
+ vals[i].type= type;
+ switch (type) {
+ case DYN_COL_NULL:
+ DBUG_ASSERT(args[valpos]->field_type() == MYSQL_TYPE_NULL);
+ break;
+ case DYN_COL_INT:
+ vals[i].x.long_value= args[valpos]->val_int();
+ break;
+ case DYN_COL_UINT:
+ vals[i].x.ulong_value= args[valpos]->val_int();
+ break;
+ case DYN_COL_DOUBLE:
+ vals[i].x.double_value= args[valpos]->val_real();
+ break;
+ case DYN_COL_DYNCOL:
+ case DYN_COL_STRING:
+ res= args[valpos]->val_str(&tmp);
+ if (res &&
+ (vals[i].x.string.value.str= sql_strmake(res->ptr(), res->length())))
+ {
+ vals[i].x.string.value.length= res->length();
+ vals[i].x.string.charset= res->charset();
+ }
+ else
+ {
+ args[valpos]->null_value= 1; // In case of out of memory
+ vals[i].x.string.value.str= NULL;
+ vals[i].x.string.value.length= 0; // just to be safe
+ }
+ break;
+ case DYN_COL_DECIMAL:
+ if ((dres= args[valpos]->val_decimal(&dtmp)))
+ {
+ mariadb_dyncol_prepare_decimal(&vals[i]);
+ DBUG_ASSERT(vals[i].x.decimal.value.len == dres->len);
+ vals[i].x.decimal.value.intg= dres->intg;
+ vals[i].x.decimal.value.frac= dres->frac;
+ vals[i].x.decimal.value.sign= dres->sign();
+ memcpy(vals[i].x.decimal.buffer, dres->buf,
+ sizeof(vals[i].x.decimal.buffer));
+ }
+ else
+ {
+ mariadb_dyncol_prepare_decimal(&vals[i]); // just to be safe
+ DBUG_ASSERT(args[valpos]->null_value);
+ }
+ break;
+ case DYN_COL_DATETIME:
+ case DYN_COL_DATE:
+ args[valpos]->get_date(&vals[i].x.time_value,
+ sql_mode_for_dates(current_thd));
+ break;
+ case DYN_COL_TIME:
+ args[valpos]->get_time(&vals[i].x.time_value);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ vals[i].type= DYN_COL_NULL;
+ }
+ if (vals[i].type != DYN_COL_NULL && args[valpos]->null_value)
+ {
+ vals[i].type= DYN_COL_NULL;
+ }
+ }
+ return FALSE;
+}
+
+
+String *Item_func_dyncol_create::val_str(String *str)
+{
+ DYNAMIC_COLUMN col;
+ String *res;
+ uint column_count= (arg_count / 2);
+ enum enum_dyncol_func_result rc;
+ DBUG_ASSERT((arg_count & 0x1) == 0); // even number of arguments
+
+ if (prepare_arguments(FALSE))
+ {
+ res= NULL;
+ null_value= 1;
+ }
+ else
+ {
+ if ((rc= ((names || force_names) ?
+ mariadb_dyncol_create_many_named(&col, column_count, keys_str,
+ vals, TRUE) :
+ mariadb_dyncol_create_many_num(&col, column_count, keys_num,
+ vals, TRUE))))
+ {
+ dynamic_column_error_message(rc);
+ mariadb_dyncol_free(&col);
+ res= NULL;
+ null_value= TRUE;
+ }
+ else
+ {
+ /* Move result from DYNAMIC_COLUMN to str_value */
+ char *ptr;
+ size_t length, alloc_length;
+ dynstr_reassociate(&col, &ptr, &length, &alloc_length);
+ str_value.reassociate(ptr, (uint32) length, (uint32) alloc_length,
+ &my_charset_bin);
+ res= &str_value;
+ null_value= FALSE;
+ }
+ }
+
+ return res;
+}
+
+void Item_func_dyncol_create::print_arguments(String *str,
+ enum_query_type query_type)
+{
+ uint i;
+ uint column_count= (arg_count / 2);
+ for (i= 0; i < column_count; i++)
+ {
+ args[i*2]->print(str, query_type);
+ str->append(',');
+ args[i*2 + 1]->print(str, query_type);
+ switch (defs[i].type) {
+ case DYN_COL_NULL: // automatic type => write nothing
+ break;
+ case DYN_COL_INT:
+ str->append(STRING_WITH_LEN(" AS int"));
+ break;
+ case DYN_COL_UINT:
+ str->append(STRING_WITH_LEN(" AS unsigned int"));
+ break;
+ case DYN_COL_DOUBLE:
+ str->append(STRING_WITH_LEN(" AS double"));
+ break;
+ case DYN_COL_DYNCOL:
+ case DYN_COL_STRING:
+ str->append(STRING_WITH_LEN(" AS char"));
+ if (defs[i].cs)
+ {
+ str->append(STRING_WITH_LEN(" charset "));
+ str->append(defs[i].cs->csname);
+ str->append(' ');
+ }
+ break;
+ case DYN_COL_DECIMAL:
+ str->append(STRING_WITH_LEN(" AS decimal"));
+ break;
+ case DYN_COL_DATETIME:
+ str->append(STRING_WITH_LEN(" AS datetime"));
+ break;
+ case DYN_COL_DATE:
+ str->append(STRING_WITH_LEN(" AS date"));
+ break;
+ case DYN_COL_TIME:
+ str->append(STRING_WITH_LEN(" AS time"));
+ break;
+ }
+ if (i < column_count - 1)
+ str->append(',');
+ }
+}
+
+
+void Item_func_dyncol_create::print(String *str,
+ enum_query_type query_type)
+{
+ DBUG_ASSERT((arg_count & 0x1) == 0); // even number of arguments
+ str->append(STRING_WITH_LEN("column_create("));
+ print_arguments(str, query_type);
+ str->append(')');
+}
+
+String *Item_func_dyncol_json::val_str(String *str)
+{
+ DYNAMIC_STRING json, col;
+ String *res;
+ enum enum_dyncol_func_result rc;
+
+ res= args[0]->val_str(str);
+ if (args[0]->null_value)
+ goto null;
+
+ col.str= (char *)res->ptr();
+ col.length= res->length();
+ if ((rc= mariadb_dyncol_json(&col, &json)))
+ {
+ dynamic_column_error_message(rc);
+ goto null;
+ }
+ bzero(&col, sizeof(col));
+ {
+ /* Move result from DYNAMIC_COLUMN to str */
+ char *ptr;
+ size_t length, alloc_length;
+ dynstr_reassociate(&json, &ptr, &length, &alloc_length);
+ str->reassociate(ptr, (uint32) length, (uint32) alloc_length,
+ &my_charset_utf8_general_ci);
+ null_value= FALSE;
+ }
+ return str;
+
+null:
+ bzero(&col, sizeof(col));
+ null_value= TRUE;
+ return NULL;
+}
+
+String *Item_func_dyncol_add::val_str(String *str)
+{
+ DYNAMIC_COLUMN col;
+ String *res;
+ uint column_count= (arg_count / 2);
+ enum enum_dyncol_func_result rc;
+ DBUG_ASSERT((arg_count & 0x1) == 1); // odd number of arguments
+
+ /* We store the packed data last */
+ res= args[arg_count - 1]->val_str(str);
+ if (args[arg_count - 1]->null_value ||
+ init_dynamic_string(&col, NULL, res->length() + STRING_BUFFER_USUAL_SIZE,
+ STRING_BUFFER_USUAL_SIZE))
+ goto null;
+
+ col.length= res->length();
+ memcpy(col.str, res->ptr(), col.length);
+
+ if (prepare_arguments(mariadb_dyncol_has_names(&col)))
+ goto null;
+
+ if ((rc= ((names || force_names) ?
+ mariadb_dyncol_update_many_named(&col, column_count,
+ keys_str, vals) :
+ mariadb_dyncol_update_many_num(&col, column_count,
+ keys_num, vals))))
+ {
+ dynamic_column_error_message(rc);
+ mariadb_dyncol_free(&col);
+ goto null;
+ }
+
+ {
+ /* Move result from DYNAMIC_COLUMN to str */
+ char *ptr;
+ size_t length, alloc_length;
+ dynstr_reassociate(&col, &ptr, &length, &alloc_length);
+ str->reassociate(ptr, (uint32) length, (uint32) alloc_length,
+ &my_charset_bin);
+ null_value= FALSE;
+ }
+
+ return str;
+
+null:
+ null_value= TRUE;
+ return NULL;
+}
+
+
+void Item_func_dyncol_add::print(String *str,
+ enum_query_type query_type)
+{
+ DBUG_ASSERT((arg_count & 0x1) == 1); // odd number of arguments
+ str->append(STRING_WITH_LEN("column_create("));
+ args[arg_count - 1]->print(str, query_type);
+ str->append(',');
+ print_arguments(str, query_type);
+ str->append(')');
+}
+
+
+/**
+ Get value for a column stored in a dynamic column
+
+ @notes
+ This function ensures that null_value is set correctly
+*/
+
+bool Item_dyncol_get::get_dyn_value(DYNAMIC_COLUMN_VALUE *val, String *tmp)
+{
+ DYNAMIC_COLUMN dyn_str;
+ String *res;
+ longlong num= 0;
+ LEX_STRING buf, *name= NULL;
+ char nmstrbuf[11];
+ String nmbuf(nmstrbuf, sizeof(nmstrbuf), system_charset_info);
+ enum enum_dyncol_func_result rc;
+
+ if (args[1]->result_type() == INT_RESULT)
+ num= args[1]->val_int();
+ else
+ {
+ String *nm= args[1]->val_str(&nmbuf);
+ if (!nm || args[1]->null_value)
+ {
+ null_value= 1;
+ return 1;
+ }
+
+ if (my_charset_same(nm->charset(), &my_charset_utf8_general_ci))
+ {
+ buf.str= (char *) nm->ptr();
+ buf.length= nm->length();
+ }
+ else
+ {
+ uint strlen;
+ uint dummy_errors;
+ buf.str= (char *)sql_alloc((strlen= nm->length() *
+ my_charset_utf8_general_ci.mbmaxlen + 1));
+ if (buf.str)
+ {
+ buf.length=
+ copy_and_convert(buf.str, strlen, &my_charset_utf8_general_ci,
+ nm->ptr(), nm->length(), nm->charset(),
+ &dummy_errors);
+ }
+ else
+ buf.length= 0;
+ }
+ name= &buf;
+ }
+
+
+ if (args[1]->null_value || num < 0 || num > INT_MAX)
+ {
+ null_value= 1;
+ return 1;
+ }
+
+ res= args[0]->val_str(tmp);
+ if (args[0]->null_value)
+ {
+ null_value= 1;
+ return 1;
+ }
+
+ dyn_str.str= (char*) res->ptr();
+ dyn_str.length= res->length();
+ if ((rc= ((name == NULL) ?
+ mariadb_dyncol_get_num(&dyn_str, (uint) num, val) :
+ mariadb_dyncol_get_named(&dyn_str, name, val))))
+ {
+ dynamic_column_error_message(rc);
+ null_value= 1;
+ return 1;
+ }
+
+ null_value= 0;
+ return 0; // ok
+}
+
+
+String *Item_dyncol_get::val_str(String *str_result)
+{
+ DYNAMIC_COLUMN_VALUE val;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+
+ if (get_dyn_value(&val, &tmp))
+ return NULL;
+
+ switch (val.type) {
+ case DYN_COL_NULL:
+ goto null;
+ case DYN_COL_INT:
+ case DYN_COL_UINT:
+ str_result->set_int(val.x.long_value, MY_TEST(val.type == DYN_COL_UINT),
+ &my_charset_latin1);
+ break;
+ case DYN_COL_DOUBLE:
+ str_result->set_real(val.x.double_value, NOT_FIXED_DEC, &my_charset_latin1);
+ break;
+ case DYN_COL_DYNCOL:
+ case DYN_COL_STRING:
+ if ((char*) tmp.ptr() <= val.x.string.value.str &&
+ (char*) tmp.ptr() + tmp.length() >= val.x.string.value.str)
+ {
+ /* value is allocated in tmp buffer; We have to make a copy */
+ str_result->copy(val.x.string.value.str, val.x.string.value.length,
+ val.x.string.charset);
+ }
+ else
+ {
+ /*
+ It's safe to use the current value because it's either pointing
+ into a field or in a buffer for another item and this buffer
+ is not going to be deleted during expression evaluation
+ */
+ str_result->set(val.x.string.value.str, val.x.string.value.length,
+ val.x.string.charset);
+ }
+ break;
+ case DYN_COL_DECIMAL:
+ {
+ int res;
+ int length= decimal_string_size(&val.x.decimal.value);
+ if (str_result->alloc(length))
+ goto null;
+ if ((res= decimal2string(&val.x.decimal.value, (char*) str_result->ptr(),
+ &length, 0, 0, ' ')) != E_DEC_OK)
+ {
+ char buff[40];
+ int len= sizeof(buff);
+ DBUG_ASSERT(length < (int)sizeof(buff));
+ decimal2string(&val.x.decimal.value, buff, &len, 0, 0, ' ');
+ decimal_operation_results(res, buff, "CHAR");
+ }
+ str_result->set_charset(&my_charset_latin1);
+ str_result->length(length);
+ break;
+ }
+ case DYN_COL_DATETIME:
+ case DYN_COL_DATE:
+ case DYN_COL_TIME:
+ {
+ int length;
+ /*
+ We use AUTO_SEC_PART_DIGITS here to ensure that we do not loose
+ any microseconds from the data. This is safe to do as we are
+ asked to return the time argument as a string.
+ */
+ if (str_result->alloc(MAX_DATE_STRING_REP_LENGTH) ||
+ !(length= my_TIME_to_str(&val.x.time_value, (char*) str_result->ptr(),
+ AUTO_SEC_PART_DIGITS)))
+ goto null;
+ str_result->set_charset(&my_charset_latin1);
+ str_result->length(length);
+ break;
+ }
+ }
+ return str_result;
+
+null:
+ null_value= TRUE;
+ return 0;
+}
+
+
+longlong Item_dyncol_get::val_int()
+{
+ DYNAMIC_COLUMN_VALUE val;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+
+ if (get_dyn_value(&val, &tmp))
+ return 0;
+
+ switch (val.type) {
+ case DYN_COL_DYNCOL:
+ case DYN_COL_NULL:
+ goto null;
+ case DYN_COL_UINT:
+ unsigned_flag= 1; // Make it possible for caller to detect sign
+ return val.x.long_value;
+ case DYN_COL_INT:
+ unsigned_flag= 0; // Make it possible for caller to detect sign
+ return val.x.long_value;
+ case DYN_COL_DOUBLE:
+ {
+ bool error;
+ longlong num;
+
+ num= double_to_longlong(val.x.double_value, unsigned_flag, &error);
+ if (error)
+ {
+ char buff[30];
+ sprintf(buff, "%lg", val.x.double_value);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_DATA_OVERFLOW,
+ ER(ER_DATA_OVERFLOW),
+ buff,
+ unsigned_flag ? "UNSIGNED INT" : "INT");
+ }
+ return num;
+ }
+ case DYN_COL_STRING:
+ {
+ int error;
+ longlong num;
+ char *end= val.x.string.value.str + val.x.string.value.length, *org_end= end;
+
+ num= my_strtoll10(val.x.string.value.str, &end, &error);
+ if (end != org_end || error > 0)
+ {
+ char buff[80];
+ strmake(buff, val.x.string.value.str, MY_MIN(sizeof(buff)-1,
+ val.x.string.value.length));
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BAD_DATA,
+ ER(ER_BAD_DATA),
+ buff,
+ unsigned_flag ? "UNSIGNED INT" : "INT");
+ }
+ unsigned_flag= error >= 0;
+ return num;
+ }
+ case DYN_COL_DECIMAL:
+ {
+ longlong num;
+ my_decimal2int(E_DEC_FATAL_ERROR, &val.x.decimal.value, unsigned_flag,
+ &num);
+ return num;
+ }
+ case DYN_COL_DATETIME:
+ case DYN_COL_DATE:
+ case DYN_COL_TIME:
+ unsigned_flag= !val.x.time_value.neg;
+ if (unsigned_flag)
+ return TIME_to_ulonglong(&val.x.time_value);
+ else
+ return -(longlong)TIME_to_ulonglong(&val.x.time_value);
+ }
+
+null:
+ null_value= TRUE;
+ return 0;
+}
+
+
+double Item_dyncol_get::val_real()
+{
+ DYNAMIC_COLUMN_VALUE val;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+
+ if (get_dyn_value(&val, &tmp))
+ return 0.0;
+
+ switch (val.type) {
+ case DYN_COL_DYNCOL:
+ case DYN_COL_NULL:
+ goto null;
+ case DYN_COL_UINT:
+ return ulonglong2double(val.x.ulong_value);
+ case DYN_COL_INT:
+ return (double) val.x.long_value;
+ case DYN_COL_DOUBLE:
+ return (double) val.x.double_value;
+ case DYN_COL_STRING:
+ {
+ int error;
+ char *end;
+ double res= my_strntod(val.x.string.charset, (char*) val.x.string.value.str,
+ val.x.string.value.length, &end, &error);
+
+ if (end != (char*) val.x.string.value.str + val.x.string.value.length ||
+ error)
+ {
+ char buff[80];
+ strmake(buff, val.x.string.value.str, MY_MIN(sizeof(buff)-1,
+ val.x.string.value.length));
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BAD_DATA,
+ ER(ER_BAD_DATA),
+ buff, "DOUBLE");
+ }
+ return res;
+ }
+ case DYN_COL_DECIMAL:
+ {
+ double res;
+ /* This will always succeed */
+ decimal2double(&val.x.decimal.value, &res);
+ return res;
+ }
+ case DYN_COL_DATETIME:
+ case DYN_COL_DATE:
+ case DYN_COL_TIME:
+ return TIME_to_double(&val.x.time_value);
+ }
+
+null:
+ null_value= TRUE;
+ return 0.0;
+}
+
+
+my_decimal *Item_dyncol_get::val_decimal(my_decimal *decimal_value)
+{
+ DYNAMIC_COLUMN_VALUE val;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+
+ if (get_dyn_value(&val, &tmp))
+ return NULL;
+
+ switch (val.type) {
+ case DYN_COL_DYNCOL:
+ case DYN_COL_NULL:
+ goto null;
+ case DYN_COL_UINT:
+ int2my_decimal(E_DEC_FATAL_ERROR, val.x.long_value, TRUE, decimal_value);
+ break;
+ case DYN_COL_INT:
+ int2my_decimal(E_DEC_FATAL_ERROR, val.x.long_value, FALSE, decimal_value);
+ break;
+ case DYN_COL_DOUBLE:
+ double2my_decimal(E_DEC_FATAL_ERROR, val.x.double_value, decimal_value);
+ break;
+ case DYN_COL_STRING:
+ {
+ int rc;
+ rc= str2my_decimal(0, val.x.string.value.str, val.x.string.value.length,
+ val.x.string.charset, decimal_value);
+ char buff[80];
+ strmake(buff, val.x.string.value.str, MY_MIN(sizeof(buff)-1,
+ val.x.string.value.length));
+ if (rc != E_DEC_OK)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BAD_DATA,
+ ER(ER_BAD_DATA),
+ buff, "DECIMAL");
+ }
+ break;
+ }
+ case DYN_COL_DECIMAL:
+ decimal2my_decimal(&val.x.decimal.value, decimal_value);
+ break;
+ case DYN_COL_DATETIME:
+ case DYN_COL_DATE:
+ case DYN_COL_TIME:
+ decimal_value= TIME_to_my_decimal(&val.x.time_value, decimal_value);
+ break;
+ }
+ return decimal_value;
+
+null:
+ null_value= TRUE;
+ return 0;
+}
+
+
+bool Item_dyncol_get::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DYNAMIC_COLUMN_VALUE val;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+ bool signed_value= 0;
+
+ if (get_dyn_value(&val, &tmp))
+ return 1; // Error
+
+ switch (val.type) {
+ case DYN_COL_DYNCOL:
+ case DYN_COL_NULL:
+ goto null;
+ case DYN_COL_INT:
+ signed_value= 1; // For error message
+ /* fall_trough */
+ case DYN_COL_UINT:
+ if (signed_value || val.x.ulong_value <= LONGLONG_MAX)
+ {
+ bool neg= val.x.ulong_value > LONGLONG_MAX;
+ if (int_to_datetime_with_warn(neg, neg ? -val.x.ulong_value :
+ val.x.ulong_value,
+ ltime, fuzzy_date, 0 /* TODO */))
+ goto null;
+ return 0;
+ }
+ /* let double_to_datetime_with_warn() issue the warning message */
+ val.x.double_value= static_cast<double>(ULONGLONG_MAX);
+ /* fall_trough */
+ case DYN_COL_DOUBLE:
+ if (double_to_datetime_with_warn(val.x.double_value, ltime, fuzzy_date,
+ 0 /* TODO */))
+ goto null;
+ return 0;
+ case DYN_COL_DECIMAL:
+ if (decimal_to_datetime_with_warn((my_decimal*)&val.x.decimal.value, ltime,
+ fuzzy_date, 0 /* TODO */))
+ goto null;
+ return 0;
+ case DYN_COL_STRING:
+ if (str_to_datetime_with_warn(&my_charset_numeric,
+ val.x.string.value.str,
+ val.x.string.value.length,
+ ltime, fuzzy_date))
+ goto null;
+ return 0;
+ case DYN_COL_DATETIME:
+ case DYN_COL_DATE:
+ case DYN_COL_TIME:
+ *ltime= val.x.time_value;
+ return 0;
+ }
+
+null:
+ null_value= TRUE;
+ return 1;
+}
+
+void Item_dyncol_get::print(String *str, enum_query_type query_type)
+{
+ /* see create_func_dyncol_get */
+ DBUG_ASSERT(str->length() >= 5);
+ DBUG_ASSERT(strncmp(str->ptr() + str->length() - 5, "cast(", 5) == 0);
+
+ str->length(str->length() - 5); // removing "cast("
+ str->append(STRING_WITH_LEN("column_get("));
+ args[0]->print(str, query_type);
+ str->append(',');
+ args[1]->print(str, query_type);
+ /* let the parent cast item add " as <type>)" */
+}
+
+
+String *Item_func_dyncol_list::val_str(String *str)
+{
+ uint i;
+ enum enum_dyncol_func_result rc;
+ LEX_STRING *names= 0;
+ uint count;
+ DYNAMIC_COLUMN col;
+ String *res= args[0]->val_str(str);
+
+ if (args[0]->null_value)
+ goto null;
+ col.length= res->length();
+ /* We do not change the string, so could do this trick */
+ col.str= (char *)res->ptr();
+ if ((rc= mariadb_dyncol_list_named(&col, &count, &names)))
+ {
+ bzero(&col, sizeof(col));
+ dynamic_column_error_message(rc);
+ goto null;
+ }
+ bzero(&col, sizeof(col));
+
+ /*
+ We estimate average name length as 10
+ */
+ if (str->alloc(count * 13))
+ goto null;
+
+ str->length(0);
+ str->set_charset(&my_charset_utf8_general_ci);
+ for (i= 0; i < count; i++)
+ {
+ append_identifier(current_thd, str, names[i].str, names[i].length);
+ if (i < count - 1)
+ str->qs_append(',');
+ }
+ null_value= FALSE;
+ if (names)
+ my_free(names);
+ return str;
+
+null:
+ null_value= TRUE;
+ if (names)
+ my_free(names);
+ return NULL;
+}
+
diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h
new file mode 100644
index 00000000000..2886cb68f9b
--- /dev/null
+++ b/sql/item_strfunc.h
@@ -0,0 +1,1232 @@
+#ifndef ITEM_STRFUNC_INCLUDED
+#define ITEM_STRFUNC_INCLUDED
+
+/*
+ Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* This file defines all string functions */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+extern size_t username_char_length;
+
+class MY_LOCALE;
+
+class Item_str_func :public Item_func
+{
+protected:
+ /**
+ Sets the result value of the function an empty string, using the current
+ character set. No memory is allocated.
+ @retval A pointer to the str_value member.
+ */
+ String *make_empty_result()
+ {
+ /*
+ Reset string length to an empty string. We don't use str_value.set() as
+ we don't want to free and potentially have to reallocate the buffer
+ for each call.
+ */
+ str_value.length(0);
+ str_value.set_charset(collation.collation);
+ return &str_value;
+ }
+public:
+ Item_str_func() :Item_func() { decimals=NOT_FIXED_DEC; }
+ Item_str_func(Item *a) :Item_func(a) {decimals=NOT_FIXED_DEC; }
+ Item_str_func(Item *a,Item *b) :Item_func(a,b) { decimals=NOT_FIXED_DEC; }
+ Item_str_func(Item *a,Item *b,Item *c) :Item_func(a,b,c) { decimals=NOT_FIXED_DEC; }
+ Item_str_func(Item *a,Item *b,Item *c,Item *d) :Item_func(a,b,c,d) {decimals=NOT_FIXED_DEC; }
+ Item_str_func(Item *a,Item *b,Item *c,Item *d, Item* e) :Item_func(a,b,c,d,e) {decimals=NOT_FIXED_DEC; }
+ Item_str_func(List<Item> &list) :Item_func(list) {decimals=NOT_FIXED_DEC; }
+ longlong val_int();
+ double val_real();
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type () const { return STRING_RESULT; }
+ void left_right_max_length();
+ bool fix_fields(THD *thd, Item **ref);
+};
+
+
+
+/*
+ Functions that return values with ASCII repertoire
+*/
+class Item_str_ascii_func :public Item_str_func
+{
+ String ascii_buf;
+public:
+ Item_str_ascii_func() :Item_str_func() {}
+ Item_str_ascii_func(Item *a) :Item_str_func(a) {}
+ Item_str_ascii_func(Item *a,Item *b) :Item_str_func(a,b) {}
+ Item_str_ascii_func(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {}
+ String *val_str(String *str)
+ {
+ return val_str_from_val_str_ascii(str, &ascii_buf);
+ }
+ virtual String *val_str_ascii(String *)= 0;
+};
+
+
+class Item_func_md5 :public Item_str_ascii_func
+{
+ String tmp_value;
+public:
+ Item_func_md5(Item *a) :Item_str_ascii_func(a) {}
+ String *val_str_ascii(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "md5"; }
+};
+
+
+class Item_func_sha :public Item_str_ascii_func
+{
+public:
+ Item_func_sha(Item *a) :Item_str_ascii_func(a) {}
+ String *val_str_ascii(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "sha"; }
+};
+
+class Item_func_sha2 :public Item_str_ascii_func
+{
+public:
+ Item_func_sha2(Item *a, Item *b) :Item_str_ascii_func(a, b) {}
+ String *val_str_ascii(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "sha2"; }
+};
+
+class Item_func_to_base64 :public Item_str_ascii_func
+{
+ String tmp_value;
+public:
+ Item_func_to_base64(Item *a) :Item_str_ascii_func(a) {}
+ String *val_str_ascii(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "to_base64"; }
+};
+
+class Item_func_from_base64 :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_from_base64(Item *a) :Item_str_func(a) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "from_base64"; }
+};
+
+
+class Item_func_aes_encrypt :public Item_str_func
+{
+public:
+ Item_func_aes_encrypt(Item *a, Item *b) :Item_str_func(a,b) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "aes_encrypt"; }
+};
+
+class Item_func_aes_decrypt :public Item_str_func
+{
+public:
+ Item_func_aes_decrypt(Item *a, Item *b) :Item_str_func(a,b) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "aes_decrypt"; }
+};
+
+
+class Item_func_concat :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_concat(List<Item> &list) :Item_str_func(list) {}
+ Item_func_concat(Item *a,Item *b) :Item_str_func(a,b) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "concat"; }
+};
+
+class Item_func_decode_histogram :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_decode_histogram(Item *a, Item *b)
+ :Item_str_func(a, b) {}
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ collation.set(system_charset_info);
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+ }
+ const char *func_name() const { return "decode_histogram"; }
+};
+
+class Item_func_concat_ws :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_concat_ws(List<Item> &list) :Item_str_func(list) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "concat_ws"; }
+ table_map not_null_tables() const { return 0; }
+};
+
+class Item_func_reverse :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_reverse(Item *a) :Item_str_func(a) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "reverse"; }
+};
+
+
+class Item_func_replace :public Item_str_func
+{
+ String tmp_value,tmp_value2;
+public:
+ Item_func_replace(Item *org,Item *find,Item *replace)
+ :Item_str_func(org,find,replace) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "replace"; }
+};
+
+
+class Item_func_regexp_replace :public Item_str_func
+{
+ Regexp_processor_pcre re;
+ bool append_replacement(String *str,
+ const LEX_CSTRING *source,
+ const LEX_CSTRING *replace);
+public:
+ Item_func_regexp_replace(Item *a, Item *b, Item *c)
+ :Item_str_func(a, b, c)
+ {}
+ void cleanup()
+ {
+ DBUG_ENTER("Item_func_regex::cleanup");
+ Item_str_func::cleanup();
+ re.cleanup();
+ DBUG_VOID_RETURN;
+ }
+ String *val_str(String *str);
+ void fix_length_and_dec();
+ const char *func_name() const { return "regexp_replace"; }
+};
+
+
+class Item_func_regexp_substr :public Item_str_func
+{
+ Regexp_processor_pcre re;
+public:
+ Item_func_regexp_substr(Item *a, Item *b)
+ :Item_str_func(a, b)
+ {}
+ void cleanup()
+ {
+ DBUG_ENTER("Item_func_regex::cleanup");
+ Item_str_func::cleanup();
+ re.cleanup();
+ DBUG_VOID_RETURN;
+ }
+ String *val_str(String *str);
+ void fix_length_and_dec();
+ const char *func_name() const { return "regexp_substr"; }
+};
+
+
+class Item_func_insert :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_insert(Item *org,Item *start,Item *length,Item *new_str)
+ :Item_str_func(org,start,length,new_str) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "insert"; }
+};
+
+
+class Item_str_conv :public Item_str_func
+{
+protected:
+ uint multiply;
+ my_charset_conv_case converter;
+ String tmp_value;
+public:
+ Item_str_conv(Item *item) :Item_str_func(item) {}
+ String *val_str(String *);
+};
+
+
+class Item_func_lcase :public Item_str_conv
+{
+public:
+ Item_func_lcase(Item *item) :Item_str_conv(item) {}
+ const char *func_name() const { return "lcase"; }
+ void fix_length_and_dec();
+};
+
+class Item_func_ucase :public Item_str_conv
+{
+public:
+ Item_func_ucase(Item *item) :Item_str_conv(item) {}
+ const char *func_name() const { return "ucase"; }
+ void fix_length_and_dec();
+};
+
+
+class Item_func_left :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_left(Item *a,Item *b) :Item_str_func(a,b) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "left"; }
+};
+
+
+class Item_func_right :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_right(Item *a,Item *b) :Item_str_func(a,b) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "right"; }
+};
+
+
+class Item_func_substr :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_substr(Item *a,Item *b) :Item_str_func(a,b) {}
+ Item_func_substr(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "substr"; }
+};
+
+
+class Item_func_substr_index :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_substr_index(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "substring_index"; }
+};
+
+
+class Item_func_trim :public Item_str_func
+{
+protected:
+ String tmp_value;
+ String remove;
+ String *trimmed_value(String *res, uint32 offset, uint32 length)
+ {
+ tmp_value.set(*res, offset, length);
+ /*
+ Make sure to return correct charset and collation:
+ TRIM(0x000000 FROM _ucs2 0x0061)
+ should set charset to "binary" rather than to "ucs2".
+ */
+ tmp_value.set_charset(collation.collation);
+ return &tmp_value;
+ }
+ String *non_trimmed_value(String *res)
+ {
+ return trimmed_value(res, 0, res->length());
+ }
+public:
+ Item_func_trim(Item *a,Item *b) :Item_str_func(a,b) {}
+ Item_func_trim(Item *a) :Item_str_func(a) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "trim"; }
+ virtual void print(String *str, enum_query_type query_type);
+ virtual const char *mode_name() const { return "both"; }
+};
+
+
+class Item_func_ltrim :public Item_func_trim
+{
+public:
+ Item_func_ltrim(Item *a,Item *b) :Item_func_trim(a,b) {}
+ Item_func_ltrim(Item *a) :Item_func_trim(a) {}
+ String *val_str(String *);
+ const char *func_name() const { return "ltrim"; }
+ const char *mode_name() const { return "leading"; }
+};
+
+
+class Item_func_rtrim :public Item_func_trim
+{
+public:
+ Item_func_rtrim(Item *a,Item *b) :Item_func_trim(a,b) {}
+ Item_func_rtrim(Item *a) :Item_func_trim(a) {}
+ String *val_str(String *);
+ const char *func_name() const { return "rtrim"; }
+ const char *mode_name() const { return "trailing"; }
+};
+
+
+/*
+ Item_func_password -- new (4.1.1) PASSWORD() function implementation.
+ Returns strcat('*', octet2hex(sha1(sha1(password)))). '*' stands for new
+ password format, sha1(sha1(password) is so-called hash_stage2 value.
+ Length of returned string is always 41 byte. To find out how entire
+ authentication procedure works, see comments in password.c.
+*/
+
+class Item_func_password :public Item_str_ascii_func
+{
+ char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH+1];
+public:
+ Item_func_password(Item *a) :Item_str_ascii_func(a) {}
+ String *val_str_ascii(String *str);
+ void fix_length_and_dec()
+ {
+ fix_length_and_charset(SCRAMBLED_PASSWORD_CHAR_LENGTH, default_charset());
+ }
+ const char *func_name() const { return "password"; }
+ static char *alloc(THD *thd, const char *password, size_t pass_len);
+};
+
+
+/*
+ Item_func_old_password -- PASSWORD() implementation used in MySQL 3.21 - 4.0
+ compatibility mode. This item is created in sql_yacc.yy when
+ 'old_passwords' session variable is set, and to handle OLD_PASSWORD()
+ function.
+*/
+
+class Item_func_old_password :public Item_str_ascii_func
+{
+ char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1];
+public:
+ Item_func_old_password(Item *a) :Item_str_ascii_func(a) {}
+ String *val_str_ascii(String *str);
+ void fix_length_and_dec()
+ {
+ fix_length_and_charset(SCRAMBLED_PASSWORD_CHAR_LENGTH_323, default_charset());
+ }
+ const char *func_name() const { return "old_password"; }
+ static char *alloc(THD *thd, const char *password, size_t pass_len);
+};
+
+
+class Item_func_des_encrypt :public Item_str_func
+{
+ String tmp_value,tmp_arg;
+public:
+ Item_func_des_encrypt(Item *a) :Item_str_func(a) {}
+ Item_func_des_encrypt(Item *a, Item *b): Item_str_func(a,b) {}
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ maybe_null=1;
+ /* 9 = MAX ((8- (arg_len % 8)) + 1) */
+ max_length = args[0]->max_length + 9;
+ }
+ const char *func_name() const { return "des_encrypt"; }
+};
+
+class Item_func_des_decrypt :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_des_decrypt(Item *a) :Item_str_func(a) {}
+ Item_func_des_decrypt(Item *a, Item *b): Item_str_func(a,b) {}
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ maybe_null=1;
+ /* 9 = MAX ((8- (arg_len % 8)) + 1) */
+ max_length= args[0]->max_length;
+ if (max_length >= 9U)
+ max_length-= 9U;
+ }
+ const char *func_name() const { return "des_decrypt"; }
+};
+
+class Item_func_encrypt :public Item_str_func
+{
+ String tmp_value;
+
+ /* Encapsulate common constructor actions */
+ void constructor_helper()
+ {
+ collation.set(&my_charset_bin);
+ }
+public:
+ Item_func_encrypt(Item *a) :Item_str_func(a)
+ {
+ constructor_helper();
+ }
+ Item_func_encrypt(Item *a, Item *b): Item_str_func(a,b)
+ {
+ constructor_helper();
+ }
+ String *val_str(String *);
+ void fix_length_and_dec() { maybe_null=1; max_length = 13; }
+ const char *func_name() const { return "encrypt"; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+#include "sql_crypt.h"
+
+
+class Item_func_encode :public Item_str_func
+{
+private:
+ /** Whether the PRNG has already been seeded. */
+ bool seeded;
+protected:
+ SQL_CRYPT sql_crypt;
+public:
+ Item_func_encode(Item *a, Item *seed):
+ Item_str_func(a, seed) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "encode"; }
+protected:
+ virtual void crypto_transform(String *);
+private:
+ /** Provide a seed for the PRNG sequence. */
+ bool seed();
+};
+
+
+class Item_func_decode :public Item_func_encode
+{
+public:
+ Item_func_decode(Item *a, Item *seed): Item_func_encode(a, seed) {}
+ const char *func_name() const { return "decode"; }
+protected:
+ void crypto_transform(String *);
+};
+
+
+class Item_func_sysconst :public Item_str_func
+{
+public:
+ Item_func_sysconst()
+ { collation.set(system_charset_info,DERIVATION_SYSCONST); }
+ Item *safe_charset_converter(CHARSET_INFO *tocs)
+ {
+ return const_charset_converter(tocs, true, fully_qualified_func_name());
+ }
+ /*
+ Used to create correct Item name in new converted item in
+ safe_charset_converter, return string representation of this function
+ call
+ */
+ virtual const char *fully_qualified_func_name() const = 0;
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(
+ fully_qualified_func_name());
+ }
+};
+
+
+class Item_func_database :public Item_func_sysconst
+{
+public:
+ Item_func_database() :Item_func_sysconst() {}
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ max_length= MAX_FIELD_NAME * system_charset_info->mbmaxlen;
+ maybe_null=1;
+ }
+ const char *func_name() const { return "database"; }
+ const char *fully_qualified_func_name() const { return "database()"; }
+};
+
+
+class Item_func_user :public Item_func_sysconst
+{
+protected:
+ bool init (const char *user, const char *host);
+
+public:
+ Item_func_user()
+ {
+ str_value.set("", 0, system_charset_info);
+ }
+ String *val_str(String *)
+ {
+ DBUG_ASSERT(fixed == 1);
+ return (null_value ? 0 : &str_value);
+ }
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec()
+ {
+ max_length= (username_char_length +
+ HOSTNAME_LENGTH + 1) * SYSTEM_CHARSET_MBMAXLEN;
+ }
+ const char *func_name() const { return "user"; }
+ const char *fully_qualified_func_name() const { return "user()"; }
+ int save_in_field(Field *field, bool no_conversions)
+ {
+ return save_str_value_in_field(field, &str_value);
+ }
+};
+
+
+class Item_func_current_user :public Item_func_user
+{
+ Name_resolution_context *context;
+
+public:
+ Item_func_current_user(Name_resolution_context *context_arg)
+ : context(context_arg) {}
+ bool fix_fields(THD *thd, Item **ref);
+ const char *func_name() const { return "current_user"; }
+ const char *fully_qualified_func_name() const { return "current_user()"; }
+};
+
+
+class Item_func_current_role :public Item_func_sysconst
+{
+ Name_resolution_context *context;
+
+public:
+ Item_func_current_role(Name_resolution_context *context_arg)
+ : context(context_arg) {}
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec()
+ { max_length= username_char_length * SYSTEM_CHARSET_MBMAXLEN; }
+ int save_in_field(Field *field, bool no_conversions)
+ { return save_str_value_in_field(field, &str_value); }
+ const char *func_name() const { return "current_role"; }
+ const char *fully_qualified_func_name() const { return "current_role()"; }
+ String *val_str(String *)
+ {
+ DBUG_ASSERT(fixed == 1);
+ return (null_value ? 0 : &str_value);
+ }
+};
+
+
+class Item_func_soundex :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_soundex(Item *a) :Item_str_func(a) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "soundex"; }
+};
+
+
+class Item_func_elt :public Item_str_func
+{
+public:
+ Item_func_elt(List<Item> &list) :Item_str_func(list) {}
+ double val_real();
+ longlong val_int();
+ String *val_str(String *str);
+ void fix_length_and_dec();
+ const char *func_name() const { return "elt"; }
+};
+
+
+class Item_func_make_set :public Item_str_func
+{
+ String tmp_str;
+
+public:
+ Item_func_make_set(List<Item> &list) :Item_str_func(list) {}
+ String *val_str(String *str);
+ void fix_length_and_dec();
+ const char *func_name() const { return "make_set"; }
+};
+
+
+class Item_func_format :public Item_str_ascii_func
+{
+ String tmp_str;
+ MY_LOCALE *locale;
+public:
+ Item_func_format(Item *org, Item *dec): Item_str_ascii_func(org, dec) {}
+ Item_func_format(Item *org, Item *dec, Item *lang):
+ Item_str_ascii_func(org, dec, lang) {}
+
+ MY_LOCALE *get_locale(Item *item);
+ String *val_str_ascii(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "format"; }
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_func_char :public Item_str_func
+{
+public:
+ Item_func_char(List<Item> &list) :Item_str_func(list)
+ { collation.set(&my_charset_bin); }
+ Item_func_char(List<Item> &list, CHARSET_INFO *cs) :Item_str_func(list)
+ { collation.set(cs); }
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ max_length= arg_count * 4;
+ }
+ const char *func_name() const { return "char"; }
+};
+
+
+class Item_func_repeat :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_repeat(Item *arg1,Item *arg2) :Item_str_func(arg1,arg2) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "repeat"; }
+};
+
+
+class Item_func_space :public Item_str_func
+{
+public:
+ Item_func_space(Item *arg1):Item_str_func(arg1) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "space"; }
+};
+
+
+class Item_func_binlog_gtid_pos :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_binlog_gtid_pos(Item *arg1,Item *arg2) :Item_str_func(arg1,arg2) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "binlog_gtid_pos"; }
+};
+
+
+class Item_func_rpad :public Item_str_func
+{
+ String tmp_value, rpad_str;
+public:
+ Item_func_rpad(Item *arg1,Item *arg2,Item *arg3)
+ :Item_str_func(arg1,arg2,arg3) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "rpad"; }
+};
+
+
+class Item_func_lpad :public Item_str_func
+{
+ String tmp_value, lpad_str;
+public:
+ Item_func_lpad(Item *arg1,Item *arg2,Item *arg3)
+ :Item_str_func(arg1,arg2,arg3) {}
+ String *val_str(String *);
+ void fix_length_and_dec();
+ const char *func_name() const { return "lpad"; }
+};
+
+
+class Item_func_conv :public Item_str_func
+{
+public:
+ Item_func_conv(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {}
+ const char *func_name() const { return "conv"; }
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ collation.set(default_charset());
+ max_length=64;
+ maybe_null= 1;
+ }
+};
+
+
+class Item_func_hex :public Item_str_ascii_func
+{
+ String tmp_value;
+public:
+ Item_func_hex(Item *a) :Item_str_ascii_func(a) {}
+ const char *func_name() const { return "hex"; }
+ String *val_str_ascii(String *);
+ void fix_length_and_dec()
+ {
+ collation.set(default_charset());
+ decimals=0;
+ fix_char_length(args[0]->max_length * 2);
+ }
+};
+
+class Item_func_unhex :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_unhex(Item *a) :Item_str_func(a)
+ {
+ /* there can be bad hex strings */
+ maybe_null= 1;
+ }
+ const char *func_name() const { return "unhex"; }
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ collation.set(&my_charset_bin);
+ decimals=0;
+ max_length=(1+args[0]->max_length)/2;
+ }
+};
+
+
+#ifndef DBUG_OFF
+class Item_func_like_range :public Item_str_func
+{
+protected:
+ String min_str;
+ String max_str;
+ const bool is_min;
+public:
+ Item_func_like_range(Item *a, Item *b, bool is_min_arg)
+ :Item_str_func(a, b), is_min(is_min_arg)
+ { maybe_null= 1; }
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ collation.set(args[0]->collation);
+ decimals=0;
+ max_length= MAX_BLOB_WIDTH;
+ }
+};
+
+
+class Item_func_like_range_min :public Item_func_like_range
+{
+public:
+ Item_func_like_range_min(Item *a, Item *b)
+ :Item_func_like_range(a, b, true) { }
+ const char *func_name() const { return "like_range_min"; }
+};
+
+
+class Item_func_like_range_max :public Item_func_like_range
+{
+public:
+ Item_func_like_range_max(Item *a, Item *b)
+ :Item_func_like_range(a, b, false) { }
+ const char *func_name() const { return "like_range_max"; }
+};
+#endif
+
+
+class Item_func_binary :public Item_str_func
+{
+public:
+ Item_func_binary(Item *a) :Item_str_func(a) {}
+ String *val_str(String *a)
+ {
+ DBUG_ASSERT(fixed == 1);
+ String *tmp=args[0]->val_str(a);
+ null_value=args[0]->null_value;
+ if (tmp)
+ tmp->set_charset(&my_charset_bin);
+ return tmp;
+ }
+ void fix_length_and_dec()
+ {
+ collation.set(&my_charset_bin);
+ max_length=args[0]->max_length;
+ }
+ virtual void print(String *str, enum_query_type query_type);
+ const char *func_name() const { return "cast_as_binary"; }
+};
+
+
+class Item_load_file :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_load_file(Item *a) :Item_str_func(a) {}
+ String *val_str(String *);
+ const char *func_name() const { return "load_file"; }
+ void fix_length_and_dec()
+ {
+ collation.set(&my_charset_bin, DERIVATION_COERCIBLE);
+ maybe_null=1;
+ max_length=MAX_BLOB_WIDTH;
+ }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_export_set: public Item_str_func
+{
+ public:
+ Item_func_export_set(Item *a,Item *b,Item* c) :Item_str_func(a,b,c) {}
+ Item_func_export_set(Item *a,Item *b,Item* c,Item* d) :Item_str_func(a,b,c,d) {}
+ Item_func_export_set(Item *a,Item *b,Item* c,Item* d,Item* e) :Item_str_func(a,b,c,d,e) {}
+ String *val_str(String *str);
+ void fix_length_and_dec();
+ const char *func_name() const { return "export_set"; }
+};
+
+
+class Item_func_quote :public Item_str_func
+{
+ String tmp_value;
+public:
+ Item_func_quote(Item *a) :Item_str_func(a) {}
+ const char *func_name() const { return "quote"; }
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ collation.set(args[0]->collation);
+ ulonglong max_result_length= (ulonglong) args[0]->max_length * 2 +
+ 2 * collation.collation->mbmaxlen;
+ max_length= (uint32) MY_MIN(max_result_length, MAX_BLOB_WIDTH);
+ }
+};
+
+class Item_func_conv_charset :public Item_str_func
+{
+ bool use_cached_value;
+ String tmp_value;
+public:
+ bool safe;
+ CHARSET_INFO *conv_charset; // keep it public
+ Item_func_conv_charset(Item *a, CHARSET_INFO *cs) :Item_str_func(a)
+ { conv_charset= cs; use_cached_value= 0; safe= 0; }
+ Item_func_conv_charset(Item *a, CHARSET_INFO *cs, bool cache_if_const)
+ :Item_str_func(a)
+ {
+ conv_charset= cs;
+ if (cache_if_const && args[0]->const_item() && !args[0]->is_expensive())
+ {
+ uint errors= 0;
+ String tmp, *str= args[0]->val_str(&tmp);
+ if (!str || str_value.copy(str->ptr(), str->length(),
+ str->charset(), conv_charset, &errors))
+ null_value= 1;
+ use_cached_value= 1;
+ str_value.mark_as_const();
+ safe= (errors == 0);
+ }
+ else
+ {
+ use_cached_value= 0;
+ /*
+ Conversion from and to "binary" is safe.
+ Conversion to Unicode is safe.
+ Other kind of conversions are potentially lossy.
+ */
+ safe= (args[0]->collation.collation == &my_charset_bin ||
+ cs == &my_charset_bin ||
+ (cs->state & MY_CS_UNICODE));
+ }
+ }
+ String *val_str(String *);
+ longlong val_int()
+ {
+ if (args[0]->result_type() == STRING_RESULT)
+ return Item_str_func::val_int();
+ longlong res= args[0]->val_int();
+ if ((null_value= args[0]->null_value))
+ return 0;
+ return res;
+ }
+ double val_real()
+ {
+ if (args[0]->result_type() == STRING_RESULT)
+ return Item_str_func::val_real();
+ double res= args[0]->val_real();
+ if ((null_value= args[0]->null_value))
+ return 0;
+ return res;
+ }
+ my_decimal *val_decimal(my_decimal *d)
+ {
+ if (args[0]->result_type() == STRING_RESULT)
+ return Item_str_func::val_decimal(d);
+ my_decimal *res= args[0]->val_decimal(d);
+ if ((null_value= args[0]->null_value))
+ return NULL;
+ return res;
+ }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate)
+ {
+ if (args[0]->result_type() == STRING_RESULT)
+ return Item_str_func::get_date(ltime, fuzzydate);
+ bool res= args[0]->get_date(ltime, fuzzydate);
+ if ((null_value= args[0]->null_value))
+ return 1;
+ return res;
+ }
+ void fix_length_and_dec();
+ const char *func_name() const { return "convert"; }
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+class Item_func_set_collation :public Item_str_func
+{
+public:
+ Item_func_set_collation(Item *a, Item *b) :Item_str_func(a,b) {};
+ String *val_str(String *);
+ void fix_length_and_dec();
+ bool eq(const Item *item, bool binary_cmp) const;
+ const char *func_name() const { return "collate"; }
+ enum Functype functype() const { return COLLATE_FUNC; }
+ virtual void print(String *str, enum_query_type query_type);
+ Item_field *field_for_view_update()
+ {
+ /* this function is transparent for view updating */
+ return args[0]->field_for_view_update();
+ }
+};
+
+class Item_func_charset :public Item_str_func
+{
+public:
+ Item_func_charset(Item *a) :Item_str_func(a) {}
+ String *val_str(String *);
+ const char *func_name() const { return "charset"; }
+ void fix_length_and_dec()
+ {
+ collation.set(system_charset_info);
+ max_length= 64 * collation.collation->mbmaxlen; // should be enough
+ maybe_null= 0;
+ };
+ table_map not_null_tables() const { return 0; }
+};
+
+class Item_func_collation :public Item_str_func
+{
+public:
+ Item_func_collation(Item *a) :Item_str_func(a) {}
+ String *val_str(String *);
+ const char *func_name() const { return "collation"; }
+ void fix_length_and_dec()
+ {
+ collation.set(system_charset_info);
+ max_length= 64 * collation.collation->mbmaxlen; // should be enough
+ maybe_null= 0;
+ };
+ table_map not_null_tables() const { return 0; }
+};
+
+class Item_func_weight_string :public Item_str_func
+{
+ String tmp_value;
+ uint flags;
+ uint nweights;
+ uint result_length;
+public:
+ Item_func_weight_string(Item *a, uint result_length_arg,
+ uint nweights_arg, uint flags_arg)
+ :Item_str_func(a)
+ {
+ nweights= nweights_arg;
+ flags= flags_arg;
+ result_length= result_length_arg;
+ }
+ const char *func_name() const { return "weight_string"; }
+ String *val_str(String *);
+ void fix_length_and_dec();
+ bool eq(const Item *item, bool binary_cmp) const
+ {
+ if (!Item_str_func::eq(item, binary_cmp))
+ return false;
+ Item_func_weight_string *that= (Item_func_weight_string *)item;
+ return this->flags == that->flags &&
+ this->nweights == that->nweights &&
+ this->result_length == that->result_length;
+ }
+};
+
+class Item_func_crc32 :public Item_int_func
+{
+ String value;
+public:
+ Item_func_crc32(Item *a) :Item_int_func(a) { unsigned_flag= 1; }
+ const char *func_name() const { return "crc32"; }
+ void fix_length_and_dec() { max_length=10; }
+ longlong val_int();
+};
+
+class Item_func_uncompressed_length : public Item_int_func
+{
+ String value;
+public:
+ Item_func_uncompressed_length(Item *a):Item_int_func(a){}
+ const char *func_name() const{return "uncompressed_length";}
+ void fix_length_and_dec() { max_length=10; maybe_null= true; }
+ longlong val_int();
+};
+
+#ifdef HAVE_COMPRESS
+#define ZLIB_DEPENDED_FUNCTION ;
+#else
+#define ZLIB_DEPENDED_FUNCTION { null_value=1; return 0; }
+#endif
+
+class Item_func_compress: public Item_str_func
+{
+ String buffer;
+public:
+ Item_func_compress(Item *a):Item_str_func(a){}
+ void fix_length_and_dec(){max_length= (args[0]->max_length*120)/100+12;}
+ const char *func_name() const{return "compress";}
+ String *val_str(String *) ZLIB_DEPENDED_FUNCTION
+};
+
+class Item_func_uncompress: public Item_str_func
+{
+ String buffer;
+public:
+ Item_func_uncompress(Item *a): Item_str_func(a){}
+ void fix_length_and_dec(){ maybe_null= 1; max_length= MAX_BLOB_WIDTH; }
+ const char *func_name() const{return "uncompress";}
+ String *val_str(String *) ZLIB_DEPENDED_FUNCTION
+};
+
+
+class Item_func_uuid: public Item_str_func
+{
+public:
+ Item_func_uuid(): Item_str_func() {}
+ void fix_length_and_dec()
+ {
+ collation.set(system_charset_info,
+ DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII);
+ fix_char_length(MY_UUID_STRING_LENGTH);
+ }
+ const char *func_name() const{ return "uuid"; }
+ String *val_str(String *);
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_dyncol_create: public Item_str_func
+{
+protected:
+ DYNCALL_CREATE_DEF *defs;
+ DYNAMIC_COLUMN_VALUE *vals;
+ uint *keys_num;
+ LEX_STRING *keys_str;
+ bool names, force_names;
+ bool prepare_arguments(bool force_names);
+ void print_arguments(String *str, enum_query_type query_type);
+public:
+ Item_func_dyncol_create(List<Item> &args, DYNCALL_CREATE_DEF *dfs);
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec();
+ const char *func_name() const{ return "column_create"; }
+ String *val_str(String *);
+ virtual void print(String *str, enum_query_type query_type);
+ virtual enum Functype functype() const { return DYNCOL_FUNC; }
+};
+
+
+class Item_func_dyncol_add: public Item_func_dyncol_create
+{
+public:
+ Item_func_dyncol_add(List<Item> &args, DYNCALL_CREATE_DEF *dfs)
+ :Item_func_dyncol_create(args, dfs)
+ {}
+ const char *func_name() const{ return "column_add"; }
+ String *val_str(String *);
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+class Item_func_dyncol_json: public Item_str_func
+{
+public:
+ Item_func_dyncol_json(Item *str) :Item_str_func(str) {}
+ const char *func_name() const{ return "column_json"; }
+ String *val_str(String *);
+ void fix_length_and_dec()
+ {
+ max_length= MAX_BLOB_WIDTH;
+ maybe_null= 1;
+ collation.set(&my_charset_bin);
+ decimals= 0;
+ }
+};
+
+/*
+ The following functions is always called from an Item_cast function
+*/
+
+class Item_dyncol_get: public Item_str_func
+{
+public:
+ Item_dyncol_get(Item *str, Item *num)
+ :Item_str_func(str, num)
+ {}
+ void fix_length_and_dec()
+ { maybe_null= 1;; max_length= MAX_BLOB_WIDTH; }
+ /* Mark that collation can change between calls */
+ bool dynamic_result() { return 1; }
+
+ const char *func_name() const { return "column_get"; }
+ String *val_str(String *);
+ longlong val_int();
+ double val_real();
+ my_decimal *val_decimal(my_decimal *);
+ bool get_dyn_value(DYNAMIC_COLUMN_VALUE *val, String *tmp);
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_func_dyncol_list: public Item_str_func
+{
+public:
+ Item_func_dyncol_list(Item *str) :Item_str_func(str) {};
+ void fix_length_and_dec() { maybe_null= 1; max_length= MAX_BLOB_WIDTH; };
+ const char *func_name() const{ return "column_list"; }
+ String *val_str(String *);
+};
+
+#endif /* ITEM_STRFUNC_INCLUDED */
+
diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc
new file mode 100644
index 00000000000..a0fca36452c
--- /dev/null
+++ b/sql/item_subselect.cc
@@ -0,0 +1,6531 @@
+/* Copyright (c) 2002, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2012, Monty Program Ab
+
+ 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 */
+
+/**
+ @file
+
+ @brief
+ subselect Item
+
+ @todo
+ - add function from mysql_select that use JOIN* as parameter to JOIN
+ methods (sql_select.h/sql_select.cc)
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h"
+#include "sql_select.h"
+#include "sql_parse.h" // check_stack_overrun
+#include "sql_test.h"
+
+double get_post_group_estimate(JOIN* join, double join_op_rows);
+
+const char *exists_outer_expr_name= "<exists outer expr>";
+
+int check_and_do_in_subquery_rewrites(JOIN *join);
+
+Item_subselect::Item_subselect():
+ Item_result_field(), value_assigned(0), own_engine(0), thd(0), old_engine(0),
+ used_tables_cache(0), have_to_be_excluded(0), const_item_cache(1),
+ inside_first_fix_fields(0), done_first_fix_fields(FALSE),
+ expr_cache(0), forced_const(FALSE), substitution(0), engine(0), eliminated(FALSE),
+ changed(0), is_correlated(FALSE)
+{
+ DBUG_ENTER("Item_subselect::Item_subselect");
+ DBUG_PRINT("enter", ("this: 0x%lx", (ulong) this));
+#ifndef DBUG_OFF
+ exec_counter= 0;
+#endif
+ with_subselect= 1;
+ reset();
+ /*
+ Item value is NULL if select_result_interceptor didn't change this value
+ (i.e. some rows will be found returned)
+ */
+ null_value= TRUE;
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_subselect::init(st_select_lex *select_lex,
+ select_result_interceptor *result)
+{
+ /*
+ Please see Item_singlerow_subselect::invalidate_and_restore_select_lex(),
+ which depends on alterations to the parse tree implemented here.
+ */
+
+ DBUG_ENTER("Item_subselect::init");
+ DBUG_PRINT("enter", ("select_lex: 0x%lx this: 0x%lx",
+ (ulong) select_lex, (ulong) this));
+ unit= select_lex->master_unit();
+ thd= unit->thd;
+
+ if (unit->item)
+ {
+ engine= unit->item->engine;
+ parsing_place= unit->item->parsing_place;
+ if (unit->item->substype() == EXISTS_SUBS &&
+ ((Item_exists_subselect *)unit->item)->exists_transformed)
+ {
+ /* it is permanent transformation of EXISTS to IN */
+ unit->item= this;
+ engine->change_result(this, result, FALSE);
+ }
+ else
+ {
+ /*
+ Item can be changed in JOIN::prepare while engine in JOIN::optimize
+ => we do not copy old_engine here
+ */
+ thd->change_item_tree((Item**)&unit->item, this);
+ engine->change_result(this, result, TRUE);
+ }
+ }
+ else
+ {
+ SELECT_LEX *outer_select= unit->outer_select();
+ /*
+ do not take into account expression inside aggregate functions because
+ they can access original table fields
+ */
+ parsing_place= (outer_select->in_sum_expr ?
+ NO_MATTER :
+ outer_select->parsing_place);
+ if (unit->is_union())
+ engine= new subselect_union_engine(thd, unit, result, this);
+ else
+ engine= new subselect_single_select_engine(thd, select_lex, result, this);
+ }
+ {
+ SELECT_LEX *upper= unit->outer_select();
+ if (upper->parsing_place == IN_HAVING)
+ upper->subquery_in_having= 1;
+ /* The subquery is an expression cache candidate */
+ upper->expr_cache_may_be_used[upper->parsing_place]= TRUE;
+ }
+ DBUG_PRINT("info", ("engine: 0x%lx", (ulong)engine));
+ DBUG_VOID_RETURN;
+}
+
+st_select_lex *
+Item_subselect::get_select_lex()
+{
+ return unit->first_select();
+}
+
+void Item_subselect::cleanup()
+{
+ DBUG_ENTER("Item_subselect::cleanup");
+ Item_result_field::cleanup();
+ if (old_engine)
+ {
+ if (engine)
+ engine->cleanup();
+ engine= old_engine;
+ old_engine= 0;
+ }
+ if (engine)
+ engine->cleanup();
+ reset();
+ value_assigned= 0;
+ expr_cache= 0;
+ forced_const= FALSE;
+ DBUG_PRINT("info", ("exec_counter: %d", exec_counter));
+#ifndef DBUG_OFF
+ exec_counter= 0;
+#endif
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_singlerow_subselect::cleanup()
+{
+ DBUG_ENTER("Item_singlerow_subselect::cleanup");
+ value= 0; row= 0;
+ Item_subselect::cleanup();
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_in_subselect::cleanup()
+{
+ DBUG_ENTER("Item_in_subselect::cleanup");
+ if (left_expr_cache)
+ {
+ left_expr_cache->delete_elements();
+ delete left_expr_cache;
+ left_expr_cache= NULL;
+ }
+ /*
+ TODO: This breaks the commented assert in add_strategy().
+ in_strategy&= ~SUBS_STRATEGY_CHOSEN;
+ */
+ first_execution= TRUE;
+ pushed_cond_guards= NULL;
+ Item_subselect::cleanup();
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_allany_subselect::cleanup()
+{
+ /*
+ The MAX/MIN transformation through injection is reverted through the
+ change_item_tree() mechanism. Revert the select_lex object of the
+ query to its initial state.
+ */
+ for (SELECT_LEX *sl= unit->first_select();
+ sl; sl= sl->next_select())
+ if (test_set_strategy(SUBS_MAXMIN_INJECTED))
+ sl->with_sum_func= false;
+ Item_in_subselect::cleanup();
+}
+
+
+Item_subselect::~Item_subselect()
+{
+ DBUG_ENTER("Item_subselect::~Item_subselect");
+ DBUG_PRINT("enter", ("this: 0x%lx", (ulong) this));
+ if (own_engine)
+ delete engine;
+ else
+ engine->cleanup();
+ engine= NULL;
+ DBUG_VOID_RETURN;
+}
+
+bool
+Item_subselect::select_transformer(JOIN *join)
+{
+ DBUG_ENTER("Item_subselect::select_transformer");
+ DBUG_RETURN(false);
+}
+
+
+bool Item_subselect::fix_fields(THD *thd_param, Item **ref)
+{
+ char const *save_where= thd_param->where;
+ uint8 uncacheable;
+ bool res;
+
+ status_var_increment(thd_param->status_var.feature_subquery);
+
+ DBUG_ASSERT(fixed == 0);
+ engine->set_thd((thd= thd_param));
+ if (!done_first_fix_fields)
+ {
+ done_first_fix_fields= TRUE;
+ inside_first_fix_fields= TRUE;
+ upper_refs.empty();
+ /*
+ psergey-todo: remove _first_fix_fields calls, we need changes on every
+ execution
+ */
+ }
+
+ eliminated= FALSE;
+ parent_select= thd_param->lex->current_select;
+
+ if (check_stack_overrun(thd, STACK_MIN_SIZE, (uchar*)&res))
+ return TRUE;
+
+
+ if (!(res= engine->prepare()))
+ {
+ // all transformation is done (used by prepared statements)
+ changed= 1;
+ inside_first_fix_fields= FALSE;
+
+ /*
+ Substitute the current item with an Item_in_optimizer that was
+ created by Item_in_subselect::select_in_like_transformer and
+ call fix_fields for the substituted item which in turn calls
+ engine->prepare for the subquery predicate.
+ */
+ if (substitution)
+ {
+ /*
+ If the top item of the WHERE/HAVING condition changed,
+ set correct WHERE/HAVING for PS.
+ */
+ if (unit->outer_select()->where == (*ref))
+ unit->outer_select()->where= substitution;
+ else if (unit->outer_select()->having == (*ref))
+ unit->outer_select()->having= substitution;
+
+ (*ref)= substitution;
+ substitution->name= name;
+ substitution->name_length= name_length;
+ if (have_to_be_excluded)
+ engine->exclude();
+ substitution= 0;
+ thd->where= "checking transformed subquery";
+ if (!(*ref)->fixed)
+ res= (*ref)->fix_fields(thd, ref);
+ goto end;
+
+ }
+ // Is it one field subselect?
+ if (engine->cols() > max_columns)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
+
+ goto end;
+ }
+ fix_length_and_dec();
+ }
+ else
+ goto end;
+
+ if ((uncacheable= engine->uncacheable() & ~UNCACHEABLE_EXPLAIN))
+ {
+ const_item_cache= 0;
+ if (uncacheable & UNCACHEABLE_RAND)
+ used_tables_cache|= RAND_TABLE_BIT;
+ }
+ fixed= 1;
+
+end:
+ done_first_fix_fields= FALSE;
+ inside_first_fix_fields= FALSE;
+ thd->where= save_where;
+ return res;
+}
+
+
+bool Item_subselect::enumerate_field_refs_processor(uchar *arg)
+{
+ List_iterator<Ref_to_outside> it(upper_refs);
+ Ref_to_outside *upper;
+
+ while ((upper= it++))
+ {
+ if (upper->item->walk(&Item::enumerate_field_refs_processor, FALSE, arg))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+bool Item_subselect::mark_as_eliminated_processor(uchar *arg)
+{
+ eliminated= TRUE;
+ return FALSE;
+}
+
+
+/**
+ Remove a subselect item from its unit so that the unit no longer
+ represents a subquery.
+
+ @param arg unused parameter
+
+ @return
+ FALSE to force the evaluation of the processor for the subsequent items.
+*/
+
+bool Item_subselect::eliminate_subselect_processor(uchar *arg)
+{
+ unit->item= NULL;
+ unit->exclude_from_tree();
+ eliminated= TRUE;
+ return FALSE;
+}
+
+
+/**
+ Adjust the master select of the subquery to be the fake_select which
+ represents the whole UNION right above the subquery, instead of the
+ last query of the UNION.
+
+ @param arg pointer to the fake select
+
+ @return
+ FALSE to force the evaluation of the processor for the subsequent items.
+*/
+
+bool Item_subselect::set_fake_select_as_master_processor(uchar *arg)
+{
+ SELECT_LEX *fake_select= (SELECT_LEX*) arg;
+ /*
+ Move the st_select_lex_unit of a subquery from a global ORDER BY clause to
+ become a direct child of the fake_select of a UNION. In this way the
+ ORDER BY that is applied to the temporary table that contains the result of
+ the whole UNION, and all columns in the subquery are resolved against this
+ table. The transformation is applied only for immediate child subqueries of
+ a UNION query.
+ */
+ if (unit->outer_select()->master_unit()->fake_select_lex == fake_select)
+ {
+ /*
+ Set the master of the subquery to be the fake select (i.e. the whole
+ UNION), instead of the last query in the UNION.
+ */
+ fake_select->add_slave(unit);
+ DBUG_ASSERT(unit->outer_select() == fake_select);
+ /* Adjust the name resolution context hierarchy accordingly. */
+ for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
+ sl->context.outer_context= &(fake_select->context);
+ /*
+ Undo Item_subselect::eliminate_subselect_processor because at that phase
+ we don't know yet that the ORDER clause will be moved to the fake select.
+ */
+ unit->item= this;
+ eliminated= FALSE;
+ }
+ return FALSE;
+}
+
+
+bool Item_subselect::mark_as_dependent(THD *thd, st_select_lex *select,
+ Item *item)
+{
+ if (inside_first_fix_fields)
+ {
+ is_correlated= TRUE;
+ Ref_to_outside *upper;
+ if (!(upper= new (thd->stmt_arena->mem_root) Ref_to_outside()))
+ return TRUE;
+ upper->select= select;
+ upper->item= item;
+ if (upper_refs.push_back(upper, thd->stmt_arena->mem_root))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Adjust attributes after our parent select has been merged into grandparent
+
+ DESCRIPTION
+ Subquery is a composite object which may be correlated, that is, it may
+ have
+ 1. references to tables of the parent select (i.e. one that has the clause
+ with the subquery predicate)
+ 2. references to tables of the grandparent select
+ 3. references to tables of further ancestors.
+
+ Before the pullout, this item indicates:
+ - #1 with table bits in used_tables()
+ - #2 and #3 with OUTER_REF_TABLE_BIT.
+
+ After parent has been merged with grandparent:
+ - references to parent and grandparent tables should be indicated with
+ table bits.
+ - references to greatgrandparent and further ancestors - with
+ OUTER_REF_TABLE_BIT.
+*/
+
+void Item_subselect::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ recalc_used_tables(new_parent, TRUE);
+ parent_select= new_parent;
+}
+
+
+class Field_fixer: public Field_enumerator
+{
+public:
+ table_map used_tables; /* Collect used_tables here */
+ st_select_lex *new_parent; /* Select we're in */
+ virtual void visit_field(Item_field *item)
+ {
+ //for (TABLE_LIST *tbl= new_parent->leaf_tables; tbl; tbl= tbl->next_local)
+ //{
+ // if (tbl->table == field->table)
+ // {
+ used_tables|= item->field->table->map;
+ // return;
+ // }
+ //}
+ //used_tables |= OUTER_REF_TABLE_BIT;
+ }
+};
+
+
+/*
+ Recalculate used_tables_cache
+*/
+
+void Item_subselect::recalc_used_tables(st_select_lex *new_parent,
+ bool after_pullout)
+{
+ List_iterator_fast<Ref_to_outside> it(upper_refs);
+ Ref_to_outside *upper;
+
+ used_tables_cache= 0;
+ while ((upper= it++))
+ {
+ bool found= FALSE;
+ /*
+ Check if
+ 1. the upper reference refers to the new immediate parent select, or
+ 2. one of the further ancestors.
+
+ We rely on the fact that the tree of selects is modified by some kind of
+ 'flattening', i.e. a process where child selects are merged into their
+ parents.
+ The merged selects are removed from the select tree but keep pointers to
+ their parents.
+ */
+ for (st_select_lex *sel= upper->select; sel; sel= sel->outer_select())
+ {
+ /*
+ If we've reached the new parent select by walking upwards from
+ reference's original select, this means that the reference is now
+ referring to the direct parent:
+ */
+ if (sel == new_parent)
+ {
+ found= TRUE;
+ /*
+ upper->item may be NULL when we've referred to a grouping function,
+ in which case we don't care about what it's table_map really is,
+ because item->with_sum_func==1 will ensure correct placement of the
+ item.
+ */
+ if (upper->item)
+ {
+ // Now, iterate over fields and collect used_tables() attribute:
+ Field_fixer fixer;
+ fixer.used_tables= 0;
+ fixer.new_parent= new_parent;
+ upper->item->walk(&Item::enumerate_field_refs_processor, FALSE,
+ (uchar*)&fixer);
+ used_tables_cache |= fixer.used_tables;
+ upper->item->walk(&Item::update_table_bitmaps_processor, FALSE, NULL);
+/*
+ if (after_pullout)
+ upper->item->fix_after_pullout(new_parent, &(upper->item));
+ upper->item->update_used_tables();
+*/
+ }
+ }
+ }
+ if (!found)
+ used_tables_cache|= OUTER_REF_TABLE_BIT;
+ }
+ /*
+ Don't update const_tables_cache yet as we don't yet know which of the
+ parent's tables are constant. Parent will call update_used_tables() after
+ he has done const table detection, and that will be our chance to update
+ const_tables_cache.
+ */
+}
+
+
+/**
+ Determine if a subquery is expensive to execute during query optimization.
+
+ @details The cost of execution of a subquery is estimated based on an
+ estimate of the number of rows the subquery will access during execution.
+ This measure is used instead of JOIN::read_time, because it is considered
+ to be much more reliable than the cost estimate.
+
+ @return true if the subquery is expensive
+ @return false otherwise
+*/
+bool Item_subselect::is_expensive()
+{
+ double examined_rows= 0;
+
+ for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
+ {
+ JOIN *cur_join= sl->join;
+ if (!cur_join)
+ continue;
+
+ /*
+ Subqueries whose result is known after optimization are not expensive.
+ Such subqueries have all tables optimized away, thus have no join plan.
+ */
+ if (cur_join->optimized &&
+ (cur_join->zero_result_cause || !cur_join->tables_list))
+ return false;
+
+ /*
+ If a subquery is not optimized we cannot estimate its cost. A subquery is
+ considered optimized if it has a join plan.
+ */
+ if (!(cur_join->optimized && cur_join->join_tab))
+ return true;
+
+ if (sl->first_inner_unit())
+ {
+ /*
+ Subqueries that contain subqueries are considered expensive.
+ @todo: accumulate the cost of subqueries.
+ */
+ return true;
+ }
+
+ examined_rows+= cur_join->get_examined_rows();
+ }
+
+ return (examined_rows > thd->variables.expensive_subquery_limit);
+}
+
+
+bool Item_subselect::walk(Item_processor processor, bool walk_subquery,
+ uchar *argument)
+{
+ if (!(unit->uncacheable & ~UNCACHEABLE_DEPENDENT) && engine->is_executed() &&
+ !unit->describe)
+ {
+ /*
+ The subquery has already been executed (for real, it wasn't EXPLAIN's
+ fake execution) so it should not matter what it has inside.
+
+ The actual reason for not walking inside is that parts of the subquery
+ (e.g. JTBM join nests and their IN-equality conditions may have been
+ invalidated by irreversible cleanups (those happen after an uncorrelated
+ subquery has been executed).
+ */
+ return (this->*processor)(argument);
+ }
+
+ if (walk_subquery)
+ {
+ for (SELECT_LEX *lex= unit->first_select(); lex; lex= lex->next_select())
+ {
+ List_iterator<Item> li(lex->item_list);
+ Item *item;
+ ORDER *order;
+
+ if (lex->where && (lex->where)->walk(processor, walk_subquery, argument))
+ return 1;
+ if (lex->having && (lex->having)->walk(processor, walk_subquery,
+ argument))
+ return 1;
+ /* TODO: why does this walk WHERE/HAVING but not ON expressions of outer joins? */
+
+ while ((item=li++))
+ {
+ if (item->walk(processor, walk_subquery, argument))
+ return 1;
+ }
+ for (order= lex->order_list.first ; order; order= order->next)
+ {
+ if ((*order->item)->walk(processor, walk_subquery, argument))
+ return 1;
+ }
+ for (order= lex->group_list.first ; order; order= order->next)
+ {
+ if ((*order->item)->walk(processor, walk_subquery, argument))
+ return 1;
+ }
+ }
+ }
+ return (this->*processor)(argument);
+}
+
+
+bool Item_subselect::exec()
+{
+ subselect_engine *org_engine= engine;
+
+ DBUG_ENTER("Item_subselect::exec");
+
+ /*
+ Do not execute subselect in case of a fatal error
+ or if the query has been killed.
+ */
+ if (thd->is_error() || thd->killed)
+ DBUG_RETURN(true);
+
+ DBUG_ASSERT(!thd->lex->context_analysis_only);
+ /*
+ Simulate a failure in sub-query execution. Used to test e.g.
+ out of memory or query being killed conditions.
+ */
+ DBUG_EXECUTE_IF("subselect_exec_fail", DBUG_RETURN(true););
+
+ bool res= engine->exec();
+
+#ifndef DBUG_OFF
+ ++exec_counter;
+#endif
+ if (engine != org_engine)
+ {
+ /*
+ If the subquery engine changed during execution due to lazy subquery
+ optimization, or because the original engine found a more efficient other
+ engine, re-execute the subquery with the new engine.
+ */
+ DBUG_RETURN(exec());
+ }
+ DBUG_RETURN(res);
+}
+
+
+void Item_subselect::get_cache_parameters(List<Item> &parameters)
+{
+ Collect_deps_prm prm= {&parameters, // parameters
+ unit->first_select()->nest_level_base, // nest_level_base
+ 0, // count
+ unit->first_select()->nest_level, // nest_level
+ TRUE // collect
+ };
+ walk(&Item::collect_outer_ref_processor, TRUE, (uchar*)&prm);
+}
+
+int Item_in_subselect::optimize(double *out_rows, double *cost)
+{
+ int res;
+ DBUG_ENTER("Item_in_subselect::optimize");
+ SELECT_LEX *save_select= thd->lex->current_select;
+ JOIN *join= unit->first_select()->join;
+
+ thd->lex->current_select= join->select_lex;
+ if ((res= join->optimize()))
+ DBUG_RETURN(res);
+
+ /* Calculate #rows and cost of join execution */
+ join->get_partial_cost_and_fanout(join->table_count - join->const_tables,
+ table_map(-1),
+ cost, out_rows);
+
+ /*
+ Adjust join output cardinality. There can be these cases:
+ - Have no GROUP BY and no aggregate funcs: we won't get into this
+ function because such join will be processed as a merged semi-join
+ (TODO: does it really mean we don't need to handle such cases here at
+ all? put ASSERT)
+ - Have no GROUP BY but have aggregate funcs: output is 1 record.
+ - Have GROUP BY and have (or not) aggregate funcs: need to adjust output
+ cardinality.
+ */
+ thd->lex->current_select= save_select;
+ if (!join->group_list && !join->group_optimized_away &&
+ join->tmp_table_param.sum_func_count)
+ {
+ DBUG_PRINT("info",("Materialized join will have only 1 row (it has "
+ "aggregates but no GROUP BY"));
+ *out_rows= 1;
+ }
+
+ /* Now with grouping */
+ if (join->group_list)
+ {
+ DBUG_PRINT("info",("Materialized join has grouping, trying to estimate it"));
+ double output_rows= get_post_group_estimate(join, *out_rows);
+ DBUG_PRINT("info",("Got value of %g", output_rows));
+ *out_rows= output_rows;
+ }
+
+ DBUG_RETURN(res);
+
+}
+
+
+/**
+ Check if an expression cache is needed for this subquery
+
+ @param thd Thread handle
+
+ @details
+ The function checks whether a cache is needed for a subquery and whether
+ the result of the subquery can be put in cache.
+
+ @retval TRUE cache is needed
+ @retval FALSE otherwise
+*/
+
+bool Item_subselect::expr_cache_is_needed(THD *thd)
+{
+ return ((engine->uncacheable() & UNCACHEABLE_DEPENDENT) &&
+ engine->cols() == 1 &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_SUBQUERY_CACHE) &&
+ !(engine->uncacheable() & (UNCACHEABLE_RAND |
+ UNCACHEABLE_SIDEEFFECT)));
+}
+
+
+/**
+ Check if the left IN argument contains NULL values.
+
+ @retval TRUE there are NULLs
+ @retval FALSE otherwise
+*/
+
+inline bool Item_in_subselect::left_expr_has_null()
+{
+ return (*(optimizer->get_cache()))->null_value;
+}
+
+
+/**
+ Check if an expression cache is needed for this subquery
+
+ @param thd Thread handle
+
+ @details
+ The function checks whether a cache is needed for a subquery and whether
+ the result of the subquery can be put in cache.
+
+ @note
+ This method allows many columns in the subquery because it is supported by
+ Item_in optimizer and result of the IN subquery will be scalar in this
+ case.
+
+ @retval TRUE cache is needed
+ @retval FALSE otherwise
+*/
+
+bool Item_in_subselect::expr_cache_is_needed(THD *thd)
+{
+ return (optimizer_flag(thd, OPTIMIZER_SWITCH_SUBQUERY_CACHE) &&
+ !(engine->uncacheable() & (UNCACHEABLE_RAND |
+ UNCACHEABLE_SIDEEFFECT)));
+}
+
+
+/*
+ Compute the IN predicate if the left operand's cache changed.
+*/
+
+bool Item_in_subselect::exec()
+{
+ DBUG_ENTER("Item_in_subselect::exec");
+ /*
+ Initialize the cache of the left predicate operand. This has to be done as
+ late as now, because Cached_item directly contains a resolved field (not
+ an item, and in some cases (when temp tables are created), these fields
+ end up pointing to the wrong field. One solution is to change Cached_item
+ to not resolve its field upon creation, but to resolve it dynamically
+ from a given Item_ref object.
+ TODO: the cache should be applied conditionally based on:
+ - rules - e.g. only if the left operand is known to be ordered, and/or
+ - on a cost-based basis, that takes into account the cost of a cache
+ lookup, the cache hit rate, and the savings per cache hit.
+ */
+ if (!left_expr_cache && (test_strategy(SUBS_MATERIALIZATION)))
+ init_left_expr_cache();
+
+ /*
+ If the new left operand is already in the cache, reuse the old result.
+ Use the cached result only if this is not the first execution of IN
+ because the cache is not valid for the first execution.
+ */
+ if (!first_execution && left_expr_cache &&
+ test_if_item_cache_changed(*left_expr_cache) < 0)
+ DBUG_RETURN(FALSE);
+
+ /*
+ The exec() method below updates item::value, and item::null_value, thus if
+ we don't call it, the next call to item::val_int() will return whatever
+ result was computed by its previous call.
+ */
+ DBUG_RETURN(Item_subselect::exec());
+}
+
+
+Item::Type Item_subselect::type() const
+{
+ return SUBSELECT_ITEM;
+}
+
+
+void Item_subselect::fix_length_and_dec()
+{
+ engine->fix_length_and_dec(0);
+}
+
+
+table_map Item_subselect::used_tables() const
+{
+ return (table_map) ((engine->uncacheable() & ~UNCACHEABLE_EXPLAIN)?
+ used_tables_cache : 0L);
+}
+
+
+bool Item_subselect::const_item() const
+{
+ return (thd->lex->context_analysis_only ?
+ FALSE :
+ forced_const || const_item_cache);
+}
+
+Item *Item_subselect::get_tmp_table_item(THD *thd_arg)
+{
+ if (!with_sum_func && !const_item())
+ return new Item_field(result_field);
+ return copy_or_same(thd_arg);
+}
+
+void Item_subselect::update_used_tables()
+{
+ if (!forced_const)
+ {
+ recalc_used_tables(parent_select, FALSE);
+ if (!engine->uncacheable())
+ {
+ // did all used tables become static?
+ if (!(used_tables_cache & ~engine->upper_select_const_tables()))
+ const_item_cache= 1;
+ }
+ }
+}
+
+
+void Item_subselect::print(String *str, enum_query_type query_type)
+{
+ if (engine)
+ {
+ str->append('(');
+ engine->print(str, query_type);
+ str->append(')');
+ }
+ else
+ str->append("(...)");
+}
+
+
+Item_singlerow_subselect::Item_singlerow_subselect(st_select_lex *select_lex)
+ :Item_subselect(), value(0)
+{
+ DBUG_ENTER("Item_singlerow_subselect::Item_singlerow_subselect");
+ init(select_lex, new select_singlerow_subselect(this));
+ maybe_null= 1;
+ max_columns= UINT_MAX;
+ DBUG_VOID_RETURN;
+}
+
+st_select_lex *
+Item_singlerow_subselect::invalidate_and_restore_select_lex()
+{
+ DBUG_ENTER("Item_singlerow_subselect::invalidate_and_restore_select_lex");
+ st_select_lex *result= get_select_lex();
+
+ DBUG_ASSERT(result);
+
+ /*
+ This code restore the parse tree in it's state before the execution of
+ Item_singlerow_subselect::Item_singlerow_subselect(),
+ and in particular decouples this object from the SELECT_LEX,
+ so that the SELECT_LEX can be used with a different flavor
+ or Item_subselect instead, as part of query rewriting.
+ */
+ unit->item= NULL;
+
+ DBUG_RETURN(result);
+}
+
+Item_maxmin_subselect::Item_maxmin_subselect(THD *thd_param,
+ Item_subselect *parent,
+ st_select_lex *select_lex,
+ bool max_arg)
+ :Item_singlerow_subselect(), was_values(TRUE)
+{
+ DBUG_ENTER("Item_maxmin_subselect::Item_maxmin_subselect");
+ max= max_arg;
+ init(select_lex,
+ new select_max_min_finder_subselect(this, max_arg,
+ parent->substype() ==
+ Item_subselect::ALL_SUBS));
+ max_columns= 1;
+ maybe_null= 1;
+ max_columns= 1;
+
+ /*
+ Following information was collected during performing fix_fields()
+ of Items belonged to subquery, which will be not repeated
+ */
+ used_tables_cache= parent->get_used_tables_cache();
+ const_item_cache= parent->const_item();
+
+ /*
+ this subquery always creates during preparation, so we can assign
+ thd here
+ */
+ thd= thd_param;
+
+ DBUG_VOID_RETURN;
+}
+
+void Item_maxmin_subselect::cleanup()
+{
+ DBUG_ENTER("Item_maxmin_subselect::cleanup");
+ Item_singlerow_subselect::cleanup();
+
+ /*
+ By default it is TRUE to avoid TRUE reporting by
+ Item_func_not_all/Item_func_nop_all if this item was never called.
+
+ Engine exec() set it to FALSE by reset_value_registration() call.
+ select_max_min_finder_subselect::send_data() set it back to TRUE if some
+ value will be found.
+ */
+ was_values= TRUE;
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_maxmin_subselect::print(String *str, enum_query_type query_type)
+{
+ str->append(max?"<max>":"<min>", 5);
+ Item_singlerow_subselect::print(str, query_type);
+}
+
+
+void Item_maxmin_subselect::no_rows_in_result()
+{
+ /*
+ Subquery predicates outside of the SELECT list must be evaluated in order
+ to possibly filter the special result row generated for implicit grouping
+ if the subquery is in the HAVING clause.
+ If the predicate is constant, we need its actual value in the only result
+ row for queries with implicit grouping.
+ */
+ if (parsing_place != SELECT_LIST || const_item())
+ return;
+ value= Item_cache::get_cache(new Item_null());
+ null_value= 0;
+ was_values= 0;
+ make_const();
+}
+
+
+void Item_singlerow_subselect::no_rows_in_result()
+{
+ /*
+ Subquery predicates outside of the SELECT list must be evaluated in order
+ to possibly filter the special result row generated for implicit grouping
+ if the subquery is in the HAVING clause.
+ If the predicate is constant, we need its actual value in the only result
+ row for queries with implicit grouping.
+ */
+ if (parsing_place != SELECT_LIST || const_item())
+ return;
+ value= Item_cache::get_cache(new Item_null());
+ reset();
+ make_const();
+}
+
+
+void Item_singlerow_subselect::reset()
+{
+ Item_subselect::reset();
+ if (value)
+ {
+ for(uint i= 0; i < engine->cols(); i++)
+ row[i]->set_null();
+ }
+}
+
+
+/**
+ @todo
+ - We cant change name of Item_field or Item_ref, because it will
+ prevent it's correct resolving, but we should save name of
+ removed item => we do not make optimization if top item of
+ list is field or reference.
+ - switch off this optimization for prepare statement,
+ because we do not rollback this changes.
+ Make rollback for it, or special name resolving mode in 5.0.
+
+ @param join Join object of the subquery (i.e. 'child' join).
+
+ @retval false The subquery was transformed
+*/
+bool
+Item_singlerow_subselect::select_transformer(JOIN *join)
+{
+ DBUG_ENTER("Item_singlerow_subselect::select_transformer");
+ if (changed)
+ DBUG_RETURN(false);
+
+ SELECT_LEX *select_lex= join->select_lex;
+ Query_arena *arena= thd->stmt_arena;
+
+ if (!select_lex->master_unit()->is_union() &&
+ !select_lex->table_list.elements &&
+ select_lex->item_list.elements == 1 &&
+ !select_lex->item_list.head()->with_sum_func &&
+ /*
+ We cant change name of Item_field or Item_ref, because it will
+ prevent it's correct resolving, but we should save name of
+ removed item => we do not make optimization if top item of
+ list is field or reference.
+ TODO: solve above problem
+ */
+ !(select_lex->item_list.head()->type() == FIELD_ITEM ||
+ select_lex->item_list.head()->type() == REF_ITEM) &&
+ !join->conds && !join->having &&
+ /*
+ switch off this optimization for prepare statement,
+ because we do not rollback this changes
+ TODO: make rollback for it, or special name resolving mode in 5.0.
+ */
+ !arena->is_stmt_prepare_or_first_sp_execute()
+ )
+ {
+ have_to_be_excluded= 1;
+ if (thd->lex->describe)
+ {
+ char warn_buff[MYSQL_ERRMSG_SIZE];
+ sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number);
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SELECT_REDUCED, warn_buff);
+ }
+ substitution= select_lex->item_list.head();
+ /*
+ as far as we moved content to upper level we have to fix dependences & Co
+ */
+ substitution->fix_after_pullout(select_lex->outer_select(), &substitution);
+ }
+ DBUG_RETURN(false);
+}
+
+
+void Item_singlerow_subselect::store(uint i, Item *item)
+{
+ row[i]->store(item);
+ row[i]->cache_value();
+}
+
+enum Item_result Item_singlerow_subselect::result_type() const
+{
+ return engine->type();
+}
+
+enum Item_result Item_singlerow_subselect::cmp_type() const
+{
+ return engine->cmptype();
+}
+
+/*
+ Don't rely on the result type to calculate field type.
+ Ask the engine instead.
+*/
+enum_field_types Item_singlerow_subselect::field_type() const
+{
+ return engine->field_type();
+}
+
+void Item_singlerow_subselect::fix_length_and_dec()
+{
+ if ((max_columns= engine->cols()) == 1)
+ {
+ engine->fix_length_and_dec(row= &value);
+ }
+ else
+ {
+ if (!(row= (Item_cache**) sql_alloc(sizeof(Item_cache*)*max_columns)))
+ return;
+ engine->fix_length_and_dec(row);
+ value= *row;
+ }
+ unsigned_flag= value->unsigned_flag;
+ /*
+ If there are not tables in subquery then ability to have NULL value
+ depends on SELECT list (if single row subquery have tables then it
+ always can be NULL if there are not records fetched).
+ */
+ if (engine->no_tables())
+ maybe_null= engine->may_be_null();
+ else
+ {
+ for (uint i= 0; i < max_columns; i++)
+ row[i]->maybe_null= TRUE;
+ }
+}
+
+
+/**
+ Add an expression cache for this subquery if it is needed
+
+ @param thd_arg Thread handle
+
+ @details
+ The function checks whether an expression cache is needed for this item
+ and if if so wraps the item into an item of the class
+ Item_exp_cache_wrapper with an appropriate expression cache set up there.
+
+ @note
+ used from Item::transform()
+
+ @return
+ new wrapper item if an expression cache is needed,
+ this item - otherwise
+*/
+
+Item* Item_singlerow_subselect::expr_cache_insert_transformer(uchar *thd_arg)
+{
+ THD *thd= (THD*) thd_arg;
+ DBUG_ENTER("Item_singlerow_subselect::expr_cache_insert_transformer");
+
+ if (expr_cache)
+ DBUG_RETURN(expr_cache);
+
+ if (expr_cache_is_needed(thd) &&
+ (expr_cache= set_expr_cache(thd)))
+ DBUG_RETURN(expr_cache);
+ DBUG_RETURN(this);
+}
+
+
+uint Item_singlerow_subselect::cols()
+{
+ return engine->cols();
+}
+
+bool Item_singlerow_subselect::check_cols(uint c)
+{
+ if (c != engine->cols())
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), c);
+ return 1;
+ }
+ return 0;
+}
+
+bool Item_singlerow_subselect::null_inside()
+{
+ for (uint i= 0; i < max_columns ; i++)
+ {
+ if (row[i]->null_value)
+ return 1;
+ }
+ return 0;
+}
+
+void Item_singlerow_subselect::bring_value()
+{
+ if (!exec() && assigned())
+ null_value= 0;
+ else
+ reset();
+}
+
+double Item_singlerow_subselect::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value->val_real();
+ if (!exec() && !value->null_value)
+ {
+ null_value= FALSE;
+ return value->val_real();
+ }
+ else
+ {
+ reset();
+ return 0;
+ }
+}
+
+longlong Item_singlerow_subselect::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value->val_int();
+ if (!exec() && !value->null_value)
+ {
+ null_value= FALSE;
+ return value->val_int();
+ }
+ else
+ {
+ reset();
+ return 0;
+ }
+}
+
+String *Item_singlerow_subselect::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value->val_str(str);
+ if (!exec() && !value->null_value)
+ {
+ null_value= FALSE;
+ return value->val_str(str);
+ }
+ else
+ {
+ reset();
+ return 0;
+ }
+}
+
+
+my_decimal *Item_singlerow_subselect::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value->val_decimal(decimal_value);
+ if (!exec() && !value->null_value)
+ {
+ null_value= FALSE;
+ return value->val_decimal(decimal_value);
+ }
+ else
+ {
+ reset();
+ return 0;
+ }
+}
+
+
+bool Item_singlerow_subselect::val_bool()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value->val_bool();
+ if (!exec() && !value->null_value)
+ {
+ null_value= FALSE;
+ return value->val_bool();
+ }
+ else
+ {
+ reset();
+ return 0;
+ }
+}
+
+
+bool Item_singlerow_subselect::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value->get_date(ltime, fuzzydate);
+ if (!exec() && !value->null_value)
+ {
+ null_value= FALSE;
+ return value->get_date(ltime, fuzzydate);
+ }
+ else
+ {
+ reset();
+ return 1;
+ }
+}
+
+
+Item_exists_subselect::Item_exists_subselect(st_select_lex *select_lex):
+ Item_subselect(), upper_not(NULL), abort_on_null(0),
+ emb_on_expr_nest(NULL), optimizer(0), exists_transformed(0)
+{
+ DBUG_ENTER("Item_exists_subselect::Item_exists_subselect");
+ init(select_lex, new select_exists_subselect(this));
+ max_columns= UINT_MAX;
+ null_value= FALSE; //can't be NULL
+ maybe_null= 0; //can't be NULL
+ value= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_exists_subselect::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("exists"));
+ Item_subselect::print(str, query_type);
+}
+
+
+bool Item_in_subselect::test_limit(st_select_lex_unit *unit_arg)
+{
+ if (unit_arg->fake_select_lex &&
+ unit_arg->fake_select_lex->test_limit())
+ return(1);
+
+ SELECT_LEX *sl= unit_arg->first_select();
+ for (; sl; sl= sl->next_select())
+ {
+ if (sl->test_limit())
+ return(1);
+ }
+ return(0);
+}
+
+Item_in_subselect::Item_in_subselect(Item * left_exp,
+ st_select_lex *select_lex):
+ Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE),
+ in_strategy(SUBS_NOT_TRANSFORMED),
+ pushed_cond_guards(NULL), is_jtbm_merged(FALSE), is_jtbm_const_tab(FALSE),
+ is_flattenable_semijoin(FALSE), is_registered_semijoin(FALSE),
+ upper_item(0)
+{
+ DBUG_ENTER("Item_in_subselect::Item_in_subselect");
+ DBUG_PRINT("info", ("in_strategy: %u", (uint)in_strategy));
+ left_expr= left_exp;
+ func= &eq_creator;
+ init(select_lex, new select_exists_subselect(this));
+ max_columns= UINT_MAX;
+ maybe_null= 1;
+ reset();
+ //if test_limit will fail then error will be reported to client
+ test_limit(select_lex->master_unit());
+ DBUG_VOID_RETURN;
+}
+
+int Item_in_subselect::get_identifier()
+{
+ return engine->get_identifier();
+}
+
+Item_allany_subselect::Item_allany_subselect(Item * left_exp,
+ chooser_compare_func_creator fc,
+ st_select_lex *select_lex,
+ bool all_arg)
+ :Item_in_subselect(), func_creator(fc), all(all_arg)
+{
+ DBUG_ENTER("Item_allany_subselect::Item_allany_subselect");
+ left_expr= left_exp;
+ func= func_creator(all_arg);
+ init(select_lex, new select_exists_subselect(this));
+ max_columns= 1;
+ abort_on_null= 0;
+ reset();
+ //if test_limit will fail then error will be reported to client
+ test_limit(select_lex->master_unit());
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Initialize length and decimals for EXISTS and inherited (IN/ALL/ANY)
+ subqueries
+*/
+
+void Item_exists_subselect::init_length_and_dec()
+{
+ decimals= 0;
+ max_length= 1;
+ max_columns= engine->cols();
+}
+
+
+void Item_exists_subselect::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_exists_subselect::fix_length_and_dec");
+ init_length_and_dec();
+ /*
+ We need only 1 row to determine existence (i.e. any EXISTS that is not
+ an IN always requires LIMIT 1)
+ */
+ thd->change_item_tree(&unit->global_parameters->select_limit,
+ new Item_int((int32) 1));
+ DBUG_PRINT("info", ("Set limit to 1"));
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_in_subselect::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_in_subselect::fix_length_and_dec");
+ init_length_and_dec();
+ /*
+ Unlike Item_exists_subselect, LIMIT 1 is set later for
+ Item_in_subselect, depending on the chosen strategy.
+ */
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Add an expression cache for this subquery if it is needed
+
+ @param thd_arg Thread handle
+
+ @details
+ The function checks whether an expression cache is needed for this item
+ and if if so wraps the item into an item of the class
+ Item_exp_cache_wrapper with an appropriate expression cache set up there.
+
+ @note
+ used from Item::transform()
+
+ @return
+ new wrapper item if an expression cache is needed,
+ this item - otherwise
+*/
+
+Item* Item_exists_subselect::expr_cache_insert_transformer(uchar *thd_arg)
+{
+ THD *thd= (THD*) thd_arg;
+ DBUG_ENTER("Item_exists_subselect::expr_cache_insert_transformer");
+
+ if (expr_cache)
+ DBUG_RETURN(expr_cache);
+
+ if (substype() == EXISTS_SUBS && expr_cache_is_needed(thd) &&
+ (expr_cache= set_expr_cache(thd)))
+ DBUG_RETURN(expr_cache);
+ DBUG_RETURN(this);
+}
+
+
+void Item_exists_subselect::no_rows_in_result()
+{
+ /*
+ Subquery predicates outside of the SELECT list must be evaluated in order
+ to possibly filter the special result row generated for implicit grouping
+ if the subquery is in the HAVING clause.
+ If the predicate is constant, we need its actual value in the only result
+ row for queries with implicit grouping.
+ */
+ if (parsing_place != SELECT_LIST || const_item())
+ return;
+ value= 0;
+ null_value= 0;
+ make_const();
+}
+
+double Item_exists_subselect::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!forced_const && exec())
+ {
+ reset();
+ return 0;
+ }
+ return (double) value;
+}
+
+longlong Item_exists_subselect::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!forced_const && exec())
+ {
+ reset();
+ return 0;
+ }
+ return value;
+}
+
+
+/**
+ Return the result of EXISTS as a string value
+
+ Converts the true/false result into a string value.
+ Note that currently this cannot be NULL, so if the query exection fails
+ it will return 0.
+
+ @param decimal_value[out] buffer to hold the resulting string value
+ @retval Pointer to the converted string.
+ Can't be a NULL pointer, as currently
+ EXISTS cannot return NULL.
+*/
+
+String *Item_exists_subselect::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!forced_const && exec())
+ reset();
+ str->set((ulonglong)value,&my_charset_bin);
+ return str;
+}
+
+
+/**
+ Return the result of EXISTS as a decimal value
+
+ Converts the true/false result into a decimal value.
+ Note that currently this cannot be NULL, so if the query exection fails
+ it will return 0.
+
+ @param decimal_value[out] Buffer to hold the resulting decimal value
+ @retval Pointer to the converted decimal.
+ Can't be a NULL pointer, as currently
+ EXISTS cannot return NULL.
+*/
+
+my_decimal *Item_exists_subselect::val_decimal(my_decimal *decimal_value)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!forced_const && exec())
+ reset();
+ int2my_decimal(E_DEC_FATAL_ERROR, value, 0, decimal_value);
+ return decimal_value;
+}
+
+
+bool Item_exists_subselect::val_bool()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (!forced_const && exec())
+ {
+ reset();
+ return 0;
+ }
+ return value != 0;
+}
+
+
+double Item_in_subselect::val_real()
+{
+ /*
+ As far as Item_in_subselect called only from Item_in_optimizer this
+ method should not be used
+ */
+ DBUG_ASSERT(0);
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value;
+ DBUG_ASSERT((engine->uncacheable() & ~UNCACHEABLE_EXPLAIN) ||
+ ! engine->is_executed());
+ null_value= was_null= FALSE;
+ if (exec())
+ {
+ reset();
+ return 0;
+ }
+ if (was_null && !value)
+ null_value= TRUE;
+ return (double) value;
+}
+
+
+longlong Item_in_subselect::val_int()
+{
+ /*
+ As far as Item_in_subselect called only from Item_in_optimizer this
+ method should not be used
+ */
+ DBUG_ASSERT(0);
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value;
+ DBUG_ASSERT((engine->uncacheable() & ~UNCACHEABLE_EXPLAIN) ||
+ ! engine->is_executed());
+ null_value= was_null= FALSE;
+ if (exec())
+ {
+ reset();
+ return 0;
+ }
+ if (was_null && !value)
+ null_value= TRUE;
+ return value;
+}
+
+
+String *Item_in_subselect::val_str(String *str)
+{
+ /*
+ As far as Item_in_subselect called only from Item_in_optimizer this
+ method should not be used
+ */
+ DBUG_ASSERT(0);
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ goto value_is_ready;
+ DBUG_ASSERT((engine->uncacheable() & ~UNCACHEABLE_EXPLAIN) ||
+ ! engine->is_executed());
+ null_value= was_null= FALSE;
+ if (exec())
+ {
+ reset();
+ return 0;
+ }
+ if (was_null && !value)
+ {
+ null_value= TRUE;
+ return 0;
+ }
+value_is_ready:
+ str->set((ulonglong)value, &my_charset_bin);
+ return str;
+}
+
+
+bool Item_in_subselect::val_bool()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (forced_const)
+ return value;
+ DBUG_ASSERT((engine->uncacheable() & ~UNCACHEABLE_EXPLAIN) ||
+ ! engine->is_executed());
+ null_value= was_null= FALSE;
+ if (exec())
+ {
+ reset();
+ return 0;
+ }
+ if (was_null && !value)
+ null_value= TRUE;
+ return value;
+}
+
+my_decimal *Item_in_subselect::val_decimal(my_decimal *decimal_value)
+{
+ /*
+ As far as Item_in_subselect called only from Item_in_optimizer this
+ method should not be used
+ */
+ DBUG_ASSERT(0);
+ if (forced_const)
+ goto value_is_ready;
+ DBUG_ASSERT((engine->uncacheable() & ~UNCACHEABLE_EXPLAIN) ||
+ ! engine->is_executed());
+ null_value= was_null= FALSE;
+ DBUG_ASSERT(fixed == 1);
+ if (exec())
+ {
+ reset();
+ return 0;
+ }
+ if (was_null && !value)
+ null_value= TRUE;
+value_is_ready:
+ int2my_decimal(E_DEC_FATAL_ERROR, value, 0, decimal_value);
+ return decimal_value;
+}
+
+
+/**
+ Prepare a single-column IN/ALL/ANY subselect for rewriting.
+
+ @param join Join object of the subquery (i.e. 'child' join).
+
+ @details
+
+ Prepare a single-column subquery to be rewritten. Given the subquery.
+
+ If the subquery has no tables it will be turned to an expression between
+ left part and SELECT list.
+
+ In other cases the subquery will be wrapped with Item_in_optimizer which
+ allow later to turn it to EXISTS or MAX/MIN.
+
+ @retval false The subquery was transformed
+ @retval true Error
+*/
+
+bool
+Item_in_subselect::single_value_transformer(JOIN *join)
+{
+ SELECT_LEX *select_lex= join->select_lex;
+ DBUG_ENTER("Item_in_subselect::single_value_transformer");
+
+ /*
+ Check that the right part of the subselect contains no more than one
+ column. E.g. in SELECT 1 IN (SELECT * ..) the right part is (SELECT * ...)
+ */
+ // psergey: duplicated_subselect_card_check
+ if (select_lex->item_list.elements > 1)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
+ DBUG_RETURN(true);
+ }
+
+ Item* join_having= join->having ? join->having : join->tmp_having;
+ if (!(join_having || select_lex->with_sum_func ||
+ select_lex->group_list.elements) &&
+ select_lex->table_list.elements == 0 &&
+ !select_lex->master_unit()->is_union())
+ {
+ Item *where_item= (Item*) select_lex->item_list.head();
+ /*
+ it is single select without tables => possible optimization
+ remove the dependence mark since the item is moved to upper
+ select and is not outer anymore.
+ */
+ where_item->walk(&Item::remove_dependence_processor, 0,
+ (uchar *) select_lex->outer_select());
+ /*
+ fix_field of substitution item will be done in time of
+ substituting.
+ Note that real_item() should be used instead of
+ original left expression because left_expr can be
+ runtime created Ref item which is deleted at the end
+ of the statement. Thus one of 'substitution' arguments
+ can be broken in case of PS.
+ */
+ substitution= func->create(left_expr, where_item);
+ have_to_be_excluded= 1;
+ if (thd->lex->describe)
+ {
+ char warn_buff[MYSQL_ERRMSG_SIZE];
+ sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number);
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SELECT_REDUCED, warn_buff);
+ }
+ DBUG_RETURN(false);
+ }
+
+ /*
+ Wrap the current IN predicate in an Item_in_optimizer. The actual
+ substitution in the Item tree takes place in Item_subselect::fix_fields.
+ */
+ if (!substitution)
+ {
+ /* We're invoked for the 1st (or the only) SELECT in the subquery UNION */
+ substitution= optimizer;
+
+ SELECT_LEX *current= thd->lex->current_select;
+
+ thd->lex->current_select= current->return_after_parsing();
+ if (!optimizer || optimizer->fix_left(thd))
+ {
+ thd->lex->current_select= current;
+ DBUG_RETURN(true);
+ }
+ thd->lex->current_select= current;
+
+ /* We will refer to upper level cache array => we have to save it for SP */
+ optimizer->keep_top_level_cache();
+
+ /*
+ As far as Item_ref_in_optimizer do not substitute itself on fix_fields
+ we can use same item for all selects.
+ */
+ expr= new Item_direct_ref(&select_lex->context,
+ (Item**)optimizer->get_cache(),
+ (char *)"<no matter>",
+ (char *)in_left_expr_name);
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Apply transformation max/min transwormation to ALL/ANY subquery if it is
+ possible.
+
+ @param join Join object of the subquery (i.e. 'child' join).
+
+ @details
+
+ If this is an ALL/ANY single-value subselect, try to rewrite it with
+ a MIN/MAX subselect. We can do that if a possible NULL result of the
+ subselect can be ignored.
+ E.g. SELECT * FROM t1 WHERE b > ANY (SELECT a FROM t2) can be rewritten
+ with SELECT * FROM t1 WHERE b > (SELECT MAX(a) FROM t2).
+ We can't check that this optimization is safe if it's not a top-level
+ item of the WHERE clause (e.g. because the WHERE clause can contain IS
+ NULL/IS NOT NULL functions). If so, we rewrite ALL/ANY with NOT EXISTS
+ later in this method.
+
+ @retval false The subquery was transformed
+ @retval true Error
+*/
+
+bool Item_allany_subselect::transform_into_max_min(JOIN *join)
+{
+ DBUG_ENTER("Item_allany_subselect::transform_into_max_min");
+ if (!test_strategy(SUBS_MAXMIN_INJECTED | SUBS_MAXMIN_ENGINE))
+ DBUG_RETURN(false);
+ Item **place= optimizer->arguments() + 1;
+ THD *thd= join->thd;
+ SELECT_LEX *select_lex= join->select_lex;
+ Item *subs;
+
+ /*
+ */
+ DBUG_ASSERT(!substitution);
+
+ /*
+ Check if optimization with aggregate min/max possible
+ 1 There is no aggregate in the subquery
+ 2 It is not UNION
+ 3 There is tables
+ 4 It is not ALL subquery with possible NULLs in the SELECT list
+ */
+ if (!select_lex->group_list.elements && /*1*/
+ !select_lex->having && /*1*/
+ !select_lex->with_sum_func && /*1*/
+ !(select_lex->next_select()) && /*2*/
+ select_lex->table_list.elements && /*3*/
+ (!select_lex->ref_pointer_array[0]->maybe_null || /*4*/
+ substype() != Item_subselect::ALL_SUBS)) /*4*/
+ {
+ Item_sum_hybrid *item;
+ nesting_map save_allow_sum_func;
+ if (func->l_op())
+ {
+ /*
+ (ALL && (> || =>)) || (ANY && (< || =<))
+ for ALL condition is inverted
+ */
+ item= new Item_sum_max(*select_lex->ref_pointer_array);
+ }
+ else
+ {
+ /*
+ (ALL && (< || =<)) || (ANY && (> || =>))
+ for ALL condition is inverted
+ */
+ item= new Item_sum_min(*select_lex->ref_pointer_array);
+ }
+ if (upper_item)
+ upper_item->set_sum_test(item);
+ thd->change_item_tree(select_lex->ref_pointer_array, item);
+ {
+ List_iterator<Item> it(select_lex->item_list);
+ it++;
+ thd->change_item_tree(it.ref(), item);
+ }
+
+ DBUG_EXECUTE("where",
+ print_where(item, "rewrite with MIN/MAX", QT_ORDINARY););
+
+ save_allow_sum_func= thd->lex->allow_sum_func;
+ thd->lex->allow_sum_func|=
+ (nesting_map)1 << thd->lex->current_select->nest_level;
+ /*
+ Item_sum_(max|min) can't substitute other item => we can use 0 as
+ reference, also Item_sum_(max|min) can't be fixed after creation, so
+ we do not check item->fixed
+ */
+ if (item->fix_fields(thd, 0))
+ DBUG_RETURN(true);
+ thd->lex->allow_sum_func= save_allow_sum_func;
+ /* we added aggregate function => we have to change statistic */
+ count_field_types(select_lex, &join->tmp_table_param, join->all_fields,
+ 0);
+ if (join->prepare_stage2())
+ DBUG_RETURN(true);
+ subs= new Item_singlerow_subselect(select_lex);
+
+ /*
+ Remove other strategies if any (we already changed the query and
+ can't apply other strategy).
+ */
+ set_strategy(SUBS_MAXMIN_INJECTED);
+ }
+ else
+ {
+ Item_maxmin_subselect *item;
+ subs= item= new Item_maxmin_subselect(thd, this, select_lex, func->l_op());
+ if (upper_item)
+ upper_item->set_sub_test(item);
+ /*
+ Remove other strategies if any (we already changed the query and
+ can't apply other strategy).
+ */
+ set_strategy(SUBS_MAXMIN_ENGINE);
+ }
+ /*
+ The swap is needed for expressions of type 'f1 < ALL ( SELECT ....)'
+ where we want to evaluate the sub query even if f1 would be null.
+ */
+ subs= func->create_swap(*(optimizer->get_cache()), subs);
+ thd->change_item_tree(place, subs);
+ if (subs->fix_fields(thd, &subs))
+ DBUG_RETURN(true);
+ DBUG_ASSERT(subs == (*place)); // There was no substitutions
+
+ select_lex->master_unit()->uncacheable&= ~UNCACHEABLE_DEPENDENT_INJECTED;
+ select_lex->uncacheable&= ~UNCACHEABLE_DEPENDENT_INJECTED;
+
+ DBUG_RETURN(false);
+}
+
+
+bool Item_in_subselect::fix_having(Item *having, SELECT_LEX *select_lex)
+{
+ bool fix_res= 0;
+ if (!having->fixed)
+ {
+ select_lex->having_fix_field= 1;
+ fix_res= having->fix_fields(thd, 0);
+ select_lex->having_fix_field= 0;
+ }
+ return fix_res;
+}
+
+bool Item_allany_subselect::is_maxmin_applicable(JOIN *join)
+{
+ /*
+ Check if max/min optimization applicable: It is top item of
+ WHERE condition.
+ */
+ return (abort_on_null || (upper_item && upper_item->is_top_level_item())) &&
+ !(join->select_lex->master_unit()->uncacheable & ~UNCACHEABLE_EXPLAIN) && !func->eqne_op();
+}
+
+
+/**
+ Create the predicates needed to transform a single-column IN/ALL/ANY
+ subselect into a correlated EXISTS via predicate injection.
+
+ @param join[in] Join object of the subquery (i.e. 'child' join).
+ @param where_item[out] the in-to-exists addition to the where clause
+ @param having_item[out] the in-to-exists addition to the having clause
+
+ @details
+ The correlated predicates are created as follows:
+
+ - If the subquery has aggregates, GROUP BY, or HAVING, convert to
+
+ SELECT ie FROM ... HAVING subq_having AND
+ trigcond(oe $cmp$ ref_or_null_helper<ie>)
+
+ the addition is wrapped into trigger only when we want to distinguish
+ between NULL and FALSE results.
+
+ - Otherwise (no aggregates/GROUP BY/HAVING) convert it to one of the
+ following:
+
+ = If we don't need to distinguish between NULL and FALSE subquery:
+
+ SELECT ie FROM ... WHERE subq_where AND (oe $cmp$ ie)
+
+ = If we need to distinguish between those:
+
+ SELECT ie FROM ...
+ WHERE subq_where AND trigcond((oe $cmp$ ie) OR (ie IS NULL))
+ HAVING trigcond(<is_not_null_test>(ie))
+
+ @retval false If the new conditions were created successfully
+ @retval true Error
+*/
+
+bool
+Item_in_subselect::create_single_in_to_exists_cond(JOIN * join,
+ Item **where_item,
+ Item **having_item)
+{
+ SELECT_LEX *select_lex= join->select_lex;
+ /*
+ The non-transformed HAVING clause of 'join' may be stored in two ways
+ during JOIN::optimize: this->tmp_having= this->having; this->having= 0;
+ */
+ Item* join_having= join->having ? join->having : join->tmp_having;
+
+ DBUG_ENTER("Item_in_subselect::create_single_in_to_exists_cond");
+
+ *where_item= NULL;
+ *having_item= NULL;
+
+ if (join_having || select_lex->with_sum_func ||
+ select_lex->group_list.elements)
+ {
+ Item *item= func->create(expr,
+ new Item_ref_null_helper(&select_lex->context,
+ this,
+ select_lex->
+ ref_pointer_array,
+ (char *)"<ref>",
+ this->full_name()));
+ if (!abort_on_null && left_expr->maybe_null)
+ {
+ /*
+ We can encounter "NULL IN (SELECT ...)". Wrap the added condition
+ within a trig_cond.
+ */
+ item= new Item_func_trig_cond(item, get_cond_guard(0));
+ }
+
+ if (!join_having)
+ item->name= (char*) in_having_cond;
+ if (fix_having(item, select_lex))
+ DBUG_RETURN(true);
+ *having_item= item;
+ }
+ else
+ {
+ Item *item= (Item*) select_lex->item_list.head()->real_item();
+
+ if (select_lex->table_list.elements)
+ {
+ Item *having= item;
+ Item *orig_item= item;
+
+ item= func->create(expr, item);
+ if (!abort_on_null && orig_item->maybe_null)
+ {
+ having= new Item_is_not_null_test(this, having);
+ if (left_expr->maybe_null)
+ {
+ if (!(having= new Item_func_trig_cond(having,
+ get_cond_guard(0))))
+ DBUG_RETURN(true);
+ }
+ having->name= (char*) in_having_cond;
+ if (fix_having(having, select_lex))
+ DBUG_RETURN(true);
+ *having_item= having;
+
+ item= new Item_cond_or(item,
+ new Item_func_isnull(orig_item));
+ }
+ /*
+ If we may encounter NULL IN (SELECT ...) and care whether subquery
+ result is NULL or FALSE, wrap condition in a trig_cond.
+ */
+ if (!abort_on_null && left_expr->maybe_null)
+ {
+ if (!(item= new Item_func_trig_cond(item, get_cond_guard(0))))
+ DBUG_RETURN(true);
+ }
+
+ /*
+ TODO: figure out why the following is done here in
+ single_value_transformer but there is no corresponding action in
+ row_value_transformer?
+ */
+ item->name= (char *) in_additional_cond;
+ if (!item->fixed && item->fix_fields(thd, 0))
+ DBUG_RETURN(true);
+ *where_item= item;
+ }
+ else
+ {
+ if (select_lex->master_unit()->is_union())
+ {
+ Item *new_having=
+ func->create(expr,
+ new Item_ref_null_helper(&select_lex->context, this,
+ select_lex->ref_pointer_array,
+ (char *)"<no matter>",
+ (char *)"<result>"));
+ if (!abort_on_null && left_expr->maybe_null)
+ {
+ if (!(new_having= new Item_func_trig_cond(new_having,
+ get_cond_guard(0))))
+ DBUG_RETURN(true);
+ }
+
+ new_having->name= (char*) in_having_cond;
+ if (fix_having(new_having, select_lex))
+ DBUG_RETURN(true);
+ *having_item= new_having;
+ }
+ else
+ DBUG_ASSERT(false);
+ }
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Wrap a multi-column IN/ALL/ANY subselect into an Item_in_optimizer.
+
+ @param join Join object of the subquery (i.e. 'child' join).
+
+ @details
+ The subquery predicate is wrapped into an Item_in_optimizer. Later the query
+ optimization phase chooses whether the subquery under the Item_in_optimizer
+ will be further transformed into an equivalent correlated EXISTS by injecting
+ additional predicates, or will be executed via subquery materialization in its
+ unmodified form.
+
+ @retval false The subquery was transformed
+ @retval true Error
+*/
+
+bool
+Item_in_subselect::row_value_transformer(JOIN *join)
+{
+ SELECT_LEX *select_lex= join->select_lex;
+ uint cols_num= left_expr->cols();
+
+ DBUG_ENTER("Item_in_subselect::row_value_transformer");
+
+ // psergey: duplicated_subselect_card_check
+ if (select_lex->item_list.elements != cols_num)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), cols_num);
+ DBUG_RETURN(true);
+ }
+
+ /*
+ Wrap the current IN predicate in an Item_in_optimizer. The actual
+ substitution in the Item tree takes place in Item_subselect::fix_fields.
+ */
+ if (!substitution)
+ {
+ //first call for this unit
+ SELECT_LEX_UNIT *master_unit= select_lex->master_unit();
+ substitution= optimizer;
+
+ SELECT_LEX *current= thd->lex->current_select;
+ thd->lex->current_select= current->return_after_parsing();
+ if (!optimizer || optimizer->fix_left(thd))
+ {
+ thd->lex->current_select= current;
+ DBUG_RETURN(true);
+ }
+
+ // we will refer to upper level cache array => we have to save it in PS
+ optimizer->keep_top_level_cache();
+
+ thd->lex->current_select= current;
+ /*
+ The uncacheable property controls a number of actions, e.g. whether to
+ save/restore (via init_save_join_tab/restore_tmp) the original JOIN for
+ plans with a temp table where the original JOIN was overriden by
+ make_simple_join. The UNCACHEABLE_EXPLAIN is ignored by EXPLAIN, thus
+ non-correlated subqueries will not appear as such to EXPLAIN.
+ */
+ master_unit->uncacheable|= UNCACHEABLE_EXPLAIN;
+ select_lex->uncacheable|= UNCACHEABLE_EXPLAIN;
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Create the predicates needed to transform a multi-column IN/ALL/ANY
+ subselect into a correlated EXISTS via predicate injection.
+
+ @details
+ The correlated predicates are created as follows:
+
+ - If the subquery has aggregates, GROUP BY, or HAVING, convert to
+
+ (l1, l2, l3) IN (SELECT v1, v2, v3 ... HAVING having)
+ =>
+ EXISTS (SELECT ... HAVING having and
+ (l1 = v1 or is null v1) and
+ (l2 = v2 or is null v2) and
+ (l3 = v3 or is null v3) and
+ is_not_null_test(v1) and
+ is_not_null_test(v2) and
+ is_not_null_test(v3))
+
+ where is_not_null_test used to register nulls in case if we have
+ not found matching to return correct NULL value.
+
+ - Otherwise (no aggregates/GROUP BY/HAVING) convert the subquery as follows:
+
+ (l1, l2, l3) IN (SELECT v1, v2, v3 ... WHERE where)
+ =>
+ EXISTS (SELECT ... WHERE where and
+ (l1 = v1 or is null v1) and
+ (l2 = v2 or is null v2) and
+ (l3 = v3 or is null v3)
+ HAVING is_not_null_test(v1) and
+ is_not_null_test(v2) and
+ is_not_null_test(v3))
+ where is_not_null_test registers NULLs values but reject rows.
+
+ in case when we do not need correct NULL, we have simplier construction:
+ EXISTS (SELECT ... WHERE where and
+ (l1 = v1) and
+ (l2 = v2) and
+ (l3 = v3)
+
+ @param join[in] Join object of the subquery (i.e. 'child' join).
+ @param where_item[out] the in-to-exists addition to the where clause
+ @param having_item[out] the in-to-exists addition to the having clause
+
+ @retval false If the new conditions were created successfully
+ @retval true Error
+*/
+
+bool
+Item_in_subselect::create_row_in_to_exists_cond(JOIN * join,
+ Item **where_item,
+ Item **having_item)
+{
+ SELECT_LEX *select_lex= join->select_lex;
+ uint cols_num= left_expr->cols();
+ /*
+ The non-transformed HAVING clause of 'join' may be stored in two ways
+ during JOIN::optimize: this->tmp_having= this->having; this->having= 0;
+ */
+ Item* join_having= join->having ? join->having : join->tmp_having;
+ bool is_having_used= (join_having || select_lex->with_sum_func ||
+ select_lex->group_list.first ||
+ !select_lex->table_list.elements);
+
+ DBUG_ENTER("Item_in_subselect::create_row_in_to_exists_cond");
+
+ *where_item= NULL;
+ *having_item= NULL;
+
+ if (is_having_used)
+ {
+ /* TODO: say here explicitly if the order of AND parts matters or not. */
+ Item *item_having_part2= 0;
+ for (uint i= 0; i < cols_num; i++)
+ {
+ DBUG_ASSERT((left_expr->fixed &&
+
+ select_lex->ref_pointer_array[i]->fixed) ||
+ (select_lex->ref_pointer_array[i]->type() == REF_ITEM &&
+ ((Item_ref*)(select_lex->ref_pointer_array[i]))->ref_type() ==
+ Item_ref::OUTER_REF));
+ if (select_lex->ref_pointer_array[i]->
+ check_cols(left_expr->element_index(i)->cols()))
+ DBUG_RETURN(true);
+ Item *item_eq=
+ new Item_func_eq(new
+ Item_direct_ref(&select_lex->context,
+ (*optimizer->get_cache())->
+ addr(i),
+ (char *)"<no matter>",
+ (char *)in_left_expr_name),
+ new
+ Item_ref(&select_lex->context,
+ select_lex->ref_pointer_array + i,
+ (char *)"<no matter>",
+ (char *)"<list ref>"));
+ Item *item_isnull=
+ new Item_func_isnull(new
+ Item_ref(&select_lex->context,
+ select_lex->ref_pointer_array+i,
+ (char *)"<no matter>",
+ (char *)"<list ref>"));
+ Item *col_item= new Item_cond_or(item_eq, item_isnull);
+ if (!abort_on_null && left_expr->element_index(i)->maybe_null)
+ {
+ if (!(col_item= new Item_func_trig_cond(col_item, get_cond_guard(i))))
+ DBUG_RETURN(true);
+ }
+ *having_item= and_items(*having_item, col_item);
+
+ Item *item_nnull_test=
+ new Item_is_not_null_test(this,
+ new Item_ref(&select_lex->context,
+ select_lex->
+ ref_pointer_array + i,
+ (char *)"<no matter>",
+ (char *)"<list ref>"));
+ if (!abort_on_null && left_expr->element_index(i)->maybe_null)
+ {
+ if (!(item_nnull_test=
+ new Item_func_trig_cond(item_nnull_test, get_cond_guard(i))))
+ DBUG_RETURN(true);
+ }
+ item_having_part2= and_items(item_having_part2, item_nnull_test);
+ item_having_part2->top_level_item();
+ }
+ *having_item= and_items(*having_item, item_having_part2);
+ }
+ else
+ {
+ for (uint i= 0; i < cols_num; i++)
+ {
+ Item *item, *item_isnull;
+ DBUG_ASSERT((left_expr->fixed &&
+ select_lex->ref_pointer_array[i]->fixed) ||
+ (select_lex->ref_pointer_array[i]->type() == REF_ITEM &&
+ ((Item_ref*)(select_lex->ref_pointer_array[i]))->ref_type() ==
+ Item_ref::OUTER_REF));
+ if (select_lex->ref_pointer_array[i]->
+ check_cols(left_expr->element_index(i)->cols()))
+ DBUG_RETURN(true);
+ item=
+ new Item_func_eq(new
+ Item_direct_ref(&select_lex->context,
+ (*optimizer->get_cache())->
+ addr(i),
+ (char *)"<no matter>",
+ (char *)in_left_expr_name),
+ new
+ Item_direct_ref(&select_lex->context,
+ select_lex->
+ ref_pointer_array+i,
+ (char *)"<no matter>",
+ (char *)"<list ref>"));
+ if (!abort_on_null && select_lex->ref_pointer_array[i]->maybe_null)
+ {
+ Item *having_col_item=
+ new Item_is_not_null_test(this,
+ new
+ Item_ref(&select_lex->context,
+ select_lex->ref_pointer_array + i,
+ (char *)"<no matter>",
+ (char *)"<list ref>"));
+
+
+ item_isnull= new
+ Item_func_isnull(new
+ Item_direct_ref(&select_lex->context,
+ select_lex->
+ ref_pointer_array+i,
+ (char *)"<no matter>",
+ (char *)"<list ref>"));
+ item= new Item_cond_or(item, item_isnull);
+ if (left_expr->element_index(i)->maybe_null)
+ {
+ if (!(item= new Item_func_trig_cond(item, get_cond_guard(i))))
+ DBUG_RETURN(true);
+ if (!(having_col_item=
+ new Item_func_trig_cond(having_col_item, get_cond_guard(i))))
+ DBUG_RETURN(true);
+ }
+ *having_item= and_items(*having_item, having_col_item);
+ }
+ if (!abort_on_null && left_expr->element_index(i)->maybe_null)
+ {
+ if (!(item= new Item_func_trig_cond(item, get_cond_guard(i))))
+ DBUG_RETURN(true);
+ }
+ *where_item= and_items(*where_item, item);
+ }
+ }
+
+ if (*where_item)
+ {
+ if (!(*where_item)->fixed && (*where_item)->fix_fields(thd, 0))
+ DBUG_RETURN(true);
+ (*where_item)->top_level_item();
+ }
+
+ if (*having_item)
+ {
+ if (!join_having)
+ (*having_item)->name= (char*) in_having_cond;
+ if (fix_having(*having_item, select_lex))
+ DBUG_RETURN(true);
+ (*having_item)->top_level_item();
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+bool
+Item_in_subselect::select_transformer(JOIN *join)
+{
+ return select_in_like_transformer(join);
+}
+
+bool
+Item_exists_subselect::select_transformer(JOIN *join)
+{
+ return select_prepare_to_be_in();
+}
+
+
+/**
+ Create the predicates needed to transform an IN/ALL/ANY subselect into a
+ correlated EXISTS via predicate injection.
+
+ @param join_arg Join object of the subquery.
+
+ @retval FALSE ok
+ @retval TRUE error
+*/
+
+bool Item_in_subselect::create_in_to_exists_cond(JOIN *join_arg)
+{
+ bool res;
+
+ DBUG_ASSERT(engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE ||
+ engine->engine_type() == subselect_engine::UNION_ENGINE);
+ /*
+ TODO: the call to init_cond_guards allocates and initializes an
+ array of booleans that may not be used later because we may choose
+ materialization.
+ The two calls below to create_XYZ_cond depend on this boolean array.
+ If the dependency is removed, the call can be moved to a later phase.
+ */
+ init_cond_guards();
+ if (left_expr->cols() == 1)
+ res= create_single_in_to_exists_cond(join_arg,
+ &(join_arg->in_to_exists_where),
+ &(join_arg->in_to_exists_having));
+ else
+ res= create_row_in_to_exists_cond(join_arg,
+ &(join_arg->in_to_exists_where),
+ &(join_arg->in_to_exists_having));
+
+ /*
+ The IN=>EXISTS transformation makes non-correlated subqueries correlated.
+ */
+ if (!left_expr->const_item() || left_expr->is_expensive())
+ {
+ join_arg->select_lex->uncacheable|= UNCACHEABLE_DEPENDENT_INJECTED;
+ join_arg->select_lex->master_unit()->uncacheable|=
+ UNCACHEABLE_DEPENDENT_INJECTED;
+ }
+ /*
+ The uncacheable property controls a number of actions, e.g. whether to
+ save/restore (via init_save_join_tab/restore_tmp) the original JOIN for
+ plans with a temp table where the original JOIN was overriden by
+ make_simple_join. The UNCACHEABLE_EXPLAIN is ignored by EXPLAIN, thus
+ non-correlated subqueries will not appear as such to EXPLAIN.
+ */
+ join_arg->select_lex->master_unit()->uncacheable|= UNCACHEABLE_EXPLAIN;
+ join_arg->select_lex->uncacheable|= UNCACHEABLE_EXPLAIN;
+ return (res);
+}
+
+
+/**
+ Transform an IN/ALL/ANY subselect into a correlated EXISTS via injecting
+ correlated in-to-exists predicates.
+
+ @param join_arg Join object of the subquery.
+
+ @retval FALSE ok
+ @retval TRUE error
+*/
+
+bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg)
+{
+ SELECT_LEX *select_lex= join_arg->select_lex;
+ Item *where_item= join_arg->in_to_exists_where;
+ Item *having_item= join_arg->in_to_exists_having;
+
+ DBUG_ENTER("Item_in_subselect::inject_in_to_exists_cond");
+
+ if (where_item)
+ {
+ List<Item> *and_args= NULL;
+ /*
+ If the top-level Item of the WHERE clause is an AND, detach the multiple
+ equality list that was attached to the end of the AND argument list by
+ build_equal_items_for_cond(). The multiple equalities must be detached
+ because fix_fields merges lower level AND arguments into the upper AND.
+ As a result, the arguments from lower-level ANDs are concatenated after
+ the multiple equalities. When the multiple equality list is treated as
+ such, it turns out that it contains non-Item_equal object which is wrong.
+ */
+ if (join_arg->conds && join_arg->conds->type() == Item::COND_ITEM &&
+ ((Item_cond*) join_arg->conds)->functype() == Item_func::COND_AND_FUNC)
+ {
+ and_args= ((Item_cond*) join_arg->conds)->argument_list();
+ if (join_arg->cond_equal)
+ and_args->disjoin((List<Item> *) &join_arg->cond_equal->current_level);
+ }
+
+ where_item= and_items(join_arg->conds, where_item);
+ if (!where_item->fixed && where_item->fix_fields(thd, 0))
+ DBUG_RETURN(true);
+ // TIMOUR TODO: call optimize_cond() for the new where clause
+ thd->change_item_tree(&select_lex->where, where_item);
+ select_lex->where->top_level_item();
+ join_arg->conds= select_lex->where;
+
+ /* Attach back the list of multiple equalities to the new top-level AND. */
+ if (and_args && join_arg->cond_equal)
+ {
+ /* The argument list of the top-level AND may change after fix fields. */
+ and_args= ((Item_cond*) join_arg->conds)->argument_list();
+ List_iterator<Item_equal> li(join_arg->cond_equal->current_level);
+ Item_equal *elem;
+ while ((elem= li++))
+ {
+ and_args->push_back(elem);
+ }
+ }
+ }
+
+ if (having_item)
+ {
+ Item* join_having= join_arg->having ? join_arg->having:join_arg->tmp_having;
+ having_item= and_items(join_having, having_item);
+ if (fix_having(having_item, select_lex))
+ DBUG_RETURN(true);
+ // TIMOUR TODO: call optimize_cond() for the new having clause
+ thd->change_item_tree(&select_lex->having, having_item);
+ select_lex->having->top_level_item();
+ join_arg->having= select_lex->having;
+ }
+ join_arg->thd->change_item_tree(&unit->global_parameters->select_limit,
+ new Item_int((int32) 1));
+ unit->select_limit_cnt= 1;
+
+ DBUG_RETURN(false);
+}
+
+
+/*
+ If this select can potentially be converted by EXISTS->IN conversion, wrap it
+ in an Item_in_optimizer object. Final decision whether to do the conversion
+ is done at a later phase.
+*/
+
+bool Item_exists_subselect::select_prepare_to_be_in()
+{
+ bool trans_res= FALSE;
+ DBUG_ENTER("Item_exists_subselect::select_prepare_to_be_in");
+ if (!optimizer &&
+ thd->lex->sql_command == SQLCOM_SELECT &&
+ !unit->first_select()->is_part_of_union() &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_EXISTS_TO_IN) &&
+ (is_top_level_item() ||
+ (upper_not && upper_not->is_top_level_item())))
+ {
+ Query_arena *arena, backup;
+ bool result;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ result= (!(optimizer= new Item_in_optimizer(new Item_int(1), this)));
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ if (result)
+ trans_res= TRUE;
+ else
+ substitution= optimizer;
+ }
+ DBUG_RETURN(trans_res);
+}
+
+/**
+ Check if 'func' is an equality in form "inner_table.column = outer_expr"
+
+ @param func Expression to check
+ @param local_field OUT Return "inner_table.column" here
+ @param outer_expr OUT Return outer_expr here
+
+ @return true - 'func' is an Equality.
+*/
+
+static bool check_equality_for_exist2in(Item_func *func,
+ Item_ident **local_field,
+ Item **outer_exp)
+{
+ Item **args;
+ if (func->functype() != Item_func::EQ_FUNC)
+ return FALSE;
+ DBUG_ASSERT(func->arg_count == 2);
+ args= func->arguments();
+ if (args[0]->real_type() == Item::FIELD_ITEM &&
+ args[0]->all_used_tables() != OUTER_REF_TABLE_BIT &&
+ args[1]->all_used_tables() == OUTER_REF_TABLE_BIT)
+ {
+ /* It is Item_field or Item_direct_view_ref) */
+ DBUG_ASSERT(args[0]->type() == Item::FIELD_ITEM ||
+ args[0]->type() == Item::REF_ITEM);
+ *local_field= (Item_ident *)args[0];
+ *outer_exp= args[1];
+ return TRUE;
+ }
+ else if (args[1]->real_type() == Item::FIELD_ITEM &&
+ args[1]->all_used_tables() != OUTER_REF_TABLE_BIT &&
+ args[0]->all_used_tables() == OUTER_REF_TABLE_BIT)
+ {
+ /* It is Item_field or Item_direct_view_ref) */
+ DBUG_ASSERT(args[0]->type() == Item::FIELD_ITEM ||
+ args[0]->type() == Item::REF_ITEM);
+ *local_field= (Item_ident *)args[1];
+ *outer_exp= args[0];
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+typedef struct st_eq_field_outer
+{
+ Item **eq_ref;
+ Item_ident *local_field;
+ Item *outer_exp;
+} EQ_FIELD_OUTER;
+
+
+/**
+ Check if 'conds' is a set of AND-ed outer_expr=inner_table.col equalities
+
+ @detail
+ Check if 'conds' has form
+
+ outer1=inner_tbl1.col1 AND ... AND outer2=inner_tbl1.col2 AND remainder_cond
+
+ @param conds Condition to be checked
+ @parm result Array to collect EQ_FIELD_OUTER elements describing
+ inner-vs-outer equalities the function has found.
+ @return
+ false - some inner-vs-outer equalities were found
+ true - otherwise.
+*/
+
+static bool find_inner_outer_equalities(Item **conds,
+ Dynamic_array<EQ_FIELD_OUTER> &result)
+{
+ bool found= FALSE;
+ EQ_FIELD_OUTER element;
+ if (is_cond_and(*conds))
+ {
+ List_iterator<Item> li(*((Item_cond*)*conds)->argument_list());
+ Item *item;
+ while ((item= li++))
+ {
+ if (item->type() == Item::FUNC_ITEM &&
+ check_equality_for_exist2in((Item_func *)item,
+ &element.local_field,
+ &element.outer_exp))
+ {
+ found= TRUE;
+ element.eq_ref= li.ref();
+ if (result.append(element))
+ goto alloc_err;
+ }
+ }
+ }
+ else if ((*conds)->type() == Item::FUNC_ITEM &&
+ check_equality_for_exist2in((Item_func *)*conds,
+ &element.local_field,
+ &element.outer_exp))
+ {
+ found= TRUE;
+ element.eq_ref= conds;
+ if (result.append(element))
+ goto alloc_err;
+ }
+
+ return !found;
+alloc_err:
+ return TRUE;
+}
+
+/**
+ Converts EXISTS subquery to IN subquery if it is possible and has sense
+
+ @param opt_arg Pointer on THD
+
+ @return TRUE in case of error and FALSE otherwise.
+*/
+
+bool Item_exists_subselect::exists2in_processor(uchar *opt_arg)
+{
+ THD *thd= (THD *)opt_arg;
+ SELECT_LEX *first_select=unit->first_select(), *save_select;
+ JOIN *join= first_select->join;
+ Item **eq_ref= NULL;
+ Item_ident *local_field= NULL;
+ Item *outer_exp= NULL;
+ Item *left_exp= NULL; Item_in_subselect *in_subs;
+ Query_arena *arena= NULL, backup;
+ int res= FALSE;
+ List<Item> outer;
+ Dynamic_array<EQ_FIELD_OUTER> eqs(5, 5);
+ bool will_be_correlated;
+ DBUG_ENTER("Item_exists_subselect::exists2in_processor");
+
+ if (!optimizer ||
+ !optimizer_flag(thd, OPTIMIZER_SWITCH_EXISTS_TO_IN) ||
+ (!is_top_level_item() && (!upper_not ||
+ !upper_not->is_top_level_item())) ||
+ first_select->is_part_of_union() ||
+ first_select->group_list.elements ||
+ first_select->order_list.elements ||
+ join->having ||
+ first_select->with_sum_func ||
+ !first_select->leaf_tables.elements||
+ !join->conds)
+ DBUG_RETURN(FALSE);
+
+ DBUG_ASSERT(first_select->order_list.elements == 0 &&
+ first_select->group_list.elements == 0 &&
+ first_select->having == NULL);
+
+ if (find_inner_outer_equalities(&join->conds, eqs))
+ DBUG_RETURN(FALSE);
+
+ DBUG_ASSERT(eqs.elements() != 0);
+
+ save_select= thd->lex->current_select;
+ thd->lex->current_select= first_select;
+
+ /* check that the subquery has only dependencies we are going pull out */
+ {
+ List<Item> unused;
+ Collect_deps_prm prm= {&unused, // parameters
+ unit->first_select()->nest_level_base, // nest_level_base
+ 0, // count
+ unit->first_select()->nest_level, // nest_level
+ FALSE // collect
+ };
+ walk(&Item::collect_outer_ref_processor, TRUE, (uchar*)&prm);
+ DBUG_ASSERT(prm.count > 0);
+ DBUG_ASSERT(prm.count >= (uint)eqs.elements());
+ will_be_correlated= prm.count > (uint)eqs.elements();
+ if (upper_not && will_be_correlated)
+ goto out;
+ }
+
+ if ((uint)eqs.elements() > (first_select->item_list.elements +
+ first_select->select_n_reserved))
+ goto out;
+ /* It is simple query */
+ DBUG_ASSERT(first_select->join->all_fields.elements ==
+ first_select->item_list.elements);
+
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ while (first_select->item_list.elements > (uint)eqs.elements())
+ {
+ first_select->item_list.pop();
+ first_select->join->all_fields.elements--;
+ }
+ {
+ List_iterator<Item> it(first_select->item_list);
+
+ for (uint i= 0; i < (uint)eqs.elements(); i++)
+ {
+ Item *item= it++;
+ eq_ref= eqs.at(i).eq_ref;
+ local_field= eqs.at(i).local_field;
+ outer_exp= eqs.at(i).outer_exp;
+ /* Add the field to the SELECT_LIST */
+ if (item)
+ it.replace(local_field);
+ else
+ {
+ first_select->item_list.push_back(local_field);
+ first_select->join->all_fields.elements++;
+ }
+ first_select->ref_pointer_array[i]= (Item *)local_field;
+
+ /* remove the parts from condition */
+ if (!upper_not || !local_field->maybe_null)
+ *eq_ref= new Item_int(1);
+ else
+ {
+ *eq_ref= new Item_func_isnotnull(
+ new Item_field(thd,
+ ((Item_field*)(local_field->real_item()))->context,
+ ((Item_field*)(local_field->real_item()))->field));
+ if((*eq_ref)->fix_fields(thd, (Item **)eq_ref))
+ {
+ res= TRUE;
+ goto out;
+ }
+ }
+ outer_exp->fix_after_pullout(unit->outer_select(), &outer_exp);
+ outer_exp->update_used_tables();
+ outer.push_back(outer_exp);
+ }
+ }
+
+ join->conds->update_used_tables();
+
+ /* make IN SUBQUERY and put outer_exp as left part */
+ if (eqs.elements() == 1)
+ left_exp= outer_exp;
+ else
+ {
+ if (!(left_exp= new Item_row(outer)))
+ {
+ res= TRUE;
+ goto out;
+ }
+ }
+
+ /* make EXISTS->IN permanet (see Item_subselect::init()) */
+ set_exists_transformed();
+
+ first_select->select_limit= NULL;
+ if (!(in_subs= new Item_in_subselect(left_exp, first_select)))
+ {
+ res= TRUE;
+ goto out;
+ }
+ in_subs->set_exists_transformed();
+ optimizer->arguments()[0]= left_exp;
+ optimizer->arguments()[1]= in_subs;
+ in_subs->optimizer= optimizer;
+ DBUG_ASSERT(is_top_level_item() ||
+ (upper_not && upper_not->is_top_level_item()));
+ in_subs->top_level_item();
+ {
+ SELECT_LEX *current= thd->lex->current_select;
+ optimizer->reset_cache(); // renew cache, and we will not keep it
+ thd->lex->current_select= unit->outer_select();
+ DBUG_ASSERT(optimizer);
+ if (optimizer->fix_left(thd))
+ {
+ res= TRUE;
+ /*
+ We should not restore thd->lex->current_select because it will be
+ reset on exit from this procedure
+ */
+ goto out;
+ }
+ /*
+ As far as Item_ref_in_optimizer do not substitute itself on fix_fields
+ we can use same item for all selects.
+ */
+ in_subs->expr= new Item_direct_ref(&first_select->context,
+ (Item**)optimizer->get_cache(),
+ (char *)"<no matter>",
+ (char *)in_left_expr_name);
+ if (in_subs->fix_fields(thd, optimizer->arguments() + 1))
+ {
+ res= TRUE;
+ /*
+ We should not restore thd->lex->current_select because it will be
+ reset on exit from this procedure
+ */
+ goto out;
+ }
+ {
+ /* Move dependence list */
+ List_iterator_fast<Ref_to_outside> it(upper_refs);
+ Ref_to_outside *upper;
+ while ((upper= it++))
+ {
+ uint i;
+ for (i= 0; i < (uint)eqs.elements(); i++)
+ if (eqs.at(i).outer_exp->
+ walk(&Item::find_item_processor, TRUE, (uchar*)upper->item))
+ break;
+ if (i == (uint)eqs.elements() &&
+ (in_subs->upper_refs.push_back(upper, thd->stmt_arena->mem_root)))
+ goto out;
+ }
+ }
+ in_subs->update_used_tables();
+ /*
+ The engine of the subquery is fixed so above fix_fields() is not
+ complete and should be fixed
+ */
+ in_subs->upper_refs= upper_refs;
+ upper_refs.empty();
+ thd->lex->current_select= current;
+ }
+
+ DBUG_ASSERT(unit->item == in_subs);
+ DBUG_ASSERT(join == first_select->join);
+ /*
+ Fix dependency info
+ */
+ in_subs->is_correlated= will_be_correlated;
+ if (!will_be_correlated)
+ {
+ first_select->uncacheable&= ~UNCACHEABLE_DEPENDENT_GENERATED;
+ unit->uncacheable&= ~UNCACHEABLE_DEPENDENT_GENERATED;
+ }
+ /*
+ set possible optimization strategies
+ */
+ in_subs->emb_on_expr_nest= emb_on_expr_nest;
+ res= check_and_do_in_subquery_rewrites(join);
+ first_select->join->prepare_stage2();
+
+ first_select->fix_prepare_information(thd, &join->conds, &join->having);
+
+ if (upper_not)
+ {
+ Item *exp;
+ if (eqs.elements() == 1)
+ {
+ exp= (optimizer->arguments()[0]->maybe_null ?
+ (Item*)
+ new Item_cond_and(
+ new Item_func_isnotnull(
+ new Item_direct_ref(&unit->outer_select()->context,
+ optimizer->arguments(),
+ (char *)"<no matter>",
+ (char *)exists_outer_expr_name)),
+ optimizer) :
+ (Item *)optimizer);
+ }
+ else
+ {
+ List<Item> *and_list= new List<Item>;
+ if (!and_list)
+ {
+ res= TRUE;
+ goto out;
+ }
+ for (size_t i= 0; i < eqs.elements(); i++)
+ {
+ if (optimizer->arguments()[0]->maybe_null)
+ {
+ and_list->
+ push_front(
+ new Item_func_isnotnull(
+ new Item_direct_ref(&unit->outer_select()->context,
+ optimizer->arguments()[0]->addr(i),
+ (char *)"<no matter>",
+ (char *)exists_outer_expr_name)));
+ }
+ }
+ if (and_list->elements > 0)
+ {
+ and_list->push_front(optimizer);
+ exp= new Item_cond_and(*and_list);
+ }
+ else
+ exp= optimizer;
+ }
+ upper_not->arguments()[0]= exp;
+ if (!exp->fixed && exp->fix_fields(thd, upper_not->arguments()))
+ {
+ res= TRUE;
+ goto out;
+ }
+ }
+
+out:
+ thd->lex->current_select= save_select;
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Prepare IN/ALL/ANY/SOME subquery transformation and call the appropriate
+ transformation function.
+
+ @param join JOIN object of transforming subquery
+
+ @notes
+ To decide which transformation procedure (scalar or row) applicable here
+ we have to call fix_fields() for the left expression to be able to call
+ cols() method on it. Also this method makes arena management for
+ underlying transformation methods.
+
+ @retval false OK
+ @retval true Error
+*/
+
+bool
+Item_in_subselect::select_in_like_transformer(JOIN *join)
+{
+ Query_arena *arena= 0, backup;
+ SELECT_LEX *current= thd->lex->current_select;
+ const char *save_where= thd->where;
+ bool trans_res= true;
+ bool result;
+
+ DBUG_ENTER("Item_in_subselect::select_in_like_transformer");
+
+ /*
+ IN/SOME/ALL/ANY subqueries aren't support LIMIT clause. Without it
+ ORDER BY clause becomes meaningless thus we drop it here.
+ */
+ for (SELECT_LEX *sl= current->master_unit()->first_select();
+ sl; sl= sl->next_select())
+ {
+ if (sl->join)
+ {
+ sl->join->order= 0;
+ sl->join->skip_sort_order= 1;
+ }
+ }
+
+ thd->where= "IN/ALL/ANY subquery";
+
+ /*
+ In some optimisation cases we will not need this Item_in_optimizer
+ object, but we can't know it here, but here we need address correct
+ reference on left expresion.
+
+ note: we won't need Item_in_optimizer when handling degenerate cases
+ like "... IN (SELECT 1)"
+ */
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ if (!optimizer)
+ {
+ result= (!(optimizer= new Item_in_optimizer(left_expr, this)));
+ if (result)
+ goto out;
+ }
+
+ thd->lex->current_select= current->return_after_parsing();
+ result= optimizer->fix_left(thd);
+ /* fix_fields can change reference to left_expr, we need reassign it */
+ left_expr= optimizer->arguments()[0];
+ thd->lex->current_select= current;
+
+ if (changed)
+ {
+ trans_res= false;
+ goto out;
+ }
+
+
+ if (result)
+ goto out;
+
+ /*
+ Both transformers call fix_fields() only for Items created inside them,
+ and all that items do not make permanent changes in current item arena
+ which allow to us call them with changed arena (if we do not know nature
+ of Item, we have to call fix_fields() for it only with original arena to
+ avoid memory leack)
+ */
+ if (left_expr->cols() == 1)
+ trans_res= single_value_transformer(join);
+ else
+ {
+ /* we do not support row operation for ALL/ANY/SOME */
+ if (func != &eq_creator)
+ {
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
+ DBUG_RETURN(true);
+ }
+ trans_res= row_value_transformer(join);
+ }
+out:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ thd->where= save_where;
+ DBUG_RETURN(trans_res);
+}
+
+
+void Item_in_subselect::print(String *str, enum_query_type query_type)
+{
+ if (test_strategy(SUBS_IN_TO_EXISTS))
+ str->append(STRING_WITH_LEN("<exists>"));
+ else
+ {
+ left_expr->print(str, query_type);
+ str->append(STRING_WITH_LEN(" in "));
+ }
+ Item_subselect::print(str, query_type);
+}
+
+bool Item_exists_subselect::fix_fields(THD *thd_arg, Item **ref)
+{
+ DBUG_ENTER("Item_exists_subselect::fix_fields");
+ if (exists_transformed)
+ DBUG_RETURN( !( (*ref)= new Item_int(1)));
+ DBUG_RETURN(Item_subselect::fix_fields(thd_arg, ref));
+}
+
+
+bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref)
+{
+ uint outer_cols_num;
+ List<Item> *inner_cols;
+ DBUG_ENTER("Item_in_subselect::fix_fields");
+
+ if (test_strategy(SUBS_SEMI_JOIN))
+ DBUG_RETURN( !( (*ref)= new Item_int(1)) );
+
+ /*
+ Check if the outer and inner IN operands match in those cases when we
+ will not perform IN=>EXISTS transformation. Currently this is when we
+ use subquery materialization.
+
+ The condition below is true when this method was called recursively from
+ inside JOIN::prepare for the JOIN object created by the call chain
+ Item_subselect::fix_fields -> subselect_single_select_engine::prepare,
+ which creates a JOIN object for the subquery and calls JOIN::prepare for
+ the JOIN of the subquery.
+ Notice that in some cases, this doesn't happen, and the check_cols()
+ test for each Item happens later in
+ Item_in_subselect::row_value_in_to_exists_transformer.
+ The reason for this mess is that our JOIN::prepare phase works top-down
+ instead of bottom-up, so we first do name resoluton and semantic checks
+ for the outer selects, then for the inner.
+ */
+ if (engine &&
+ engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE &&
+ ((subselect_single_select_engine*)engine)->join)
+ {
+ outer_cols_num= left_expr->cols();
+
+ if (unit->is_union())
+ inner_cols= &(unit->types);
+ else
+ inner_cols= &(unit->first_select()->item_list);
+ if (outer_cols_num != inner_cols->elements)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), outer_cols_num);
+ DBUG_RETURN(TRUE);
+ }
+ if (outer_cols_num > 1)
+ {
+ List_iterator<Item> inner_col_it(*inner_cols);
+ Item *inner_col;
+ for (uint i= 0; i < outer_cols_num; i++)
+ {
+ inner_col= inner_col_it++;
+ if (inner_col->check_cols(left_expr->element_index(i)->cols()))
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+
+ if (thd_arg->lex->is_view_context_analysis() &&
+ left_expr && !left_expr->fixed &&
+ left_expr->fix_fields(thd_arg, &left_expr))
+ DBUG_RETURN(TRUE);
+ else
+ if (Item_subselect::fix_fields(thd_arg, ref))
+ DBUG_RETURN(TRUE);
+ fixed= TRUE;
+ DBUG_RETURN(FALSE);
+}
+
+
+void Item_in_subselect::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+ left_expr->fix_after_pullout(new_parent, &left_expr);
+ Item_subselect::fix_after_pullout(new_parent, ref);
+ used_tables_cache |= left_expr->used_tables();
+}
+
+void Item_in_subselect::update_used_tables()
+{
+ Item_subselect::update_used_tables();
+ left_expr->update_used_tables();
+ //used_tables_cache |= left_expr->used_tables();
+ used_tables_cache= Item_subselect::used_tables() | left_expr->used_tables();
+}
+
+
+/**
+ Try to create and initialize an engine to compute a subselect via
+ materialization.
+
+ @details
+ The method creates a new engine for materialized execution, and initializes
+ the engine. The initialization may fail
+ - either because it wasn't possible to create the needed temporary table
+ and its index,
+ - or because of a memory allocation error,
+
+ @returns
+ @retval TRUE memory allocation error occurred
+ @retval FALSE an execution method was chosen successfully
+*/
+
+bool Item_in_subselect::setup_mat_engine()
+{
+ subselect_hash_sj_engine *mat_engine= NULL;
+ subselect_single_select_engine *select_engine;
+
+ DBUG_ENTER("Item_in_subselect::setup_mat_engine");
+
+ /*
+ The select_engine (that executes transformed IN=>EXISTS subselects) is
+ pre-created at parse time, and is stored in statment memory (preserved
+ across PS executions).
+ */
+ DBUG_ASSERT(engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE);
+ select_engine= (subselect_single_select_engine*) engine;
+
+ /* Create/initialize execution objects. */
+ if (!(mat_engine= new subselect_hash_sj_engine(thd, this, select_engine)))
+ DBUG_RETURN(TRUE);
+
+ if (mat_engine->init(&select_engine->join->fields_list,
+ engine->get_identifier()))
+ DBUG_RETURN(TRUE);
+
+ engine= mat_engine;
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Initialize the cache of the left operand of the IN predicate.
+
+ @note This method has the same purpose as alloc_group_fields(),
+ but it takes a different kind of collection of items, and the
+ list we push to is dynamically allocated.
+
+ @retval TRUE if a memory allocation error occurred or the cache is
+ not applicable to the current query
+ @retval FALSE if success
+*/
+
+bool Item_in_subselect::init_left_expr_cache()
+{
+ JOIN *outer_join;
+
+ outer_join= unit->outer_select()->join;
+ /*
+ An IN predicate might be evaluated in a query for which all tables have
+ been optimzied away.
+ */
+ if (!outer_join || !outer_join->table_count || !outer_join->tables_list)
+ return TRUE;
+
+ if (!(left_expr_cache= new List<Cached_item>))
+ return TRUE;
+
+ for (uint i= 0; i < left_expr->cols(); i++)
+ {
+ Cached_item *cur_item_cache= new_Cached_item(thd,
+ left_expr->element_index(i),
+ FALSE);
+ if (!cur_item_cache || left_expr_cache->push_front(cur_item_cache))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+bool Item_in_subselect::init_cond_guards()
+{
+ uint cols_num= left_expr->cols();
+ if (!abort_on_null && left_expr->maybe_null && !pushed_cond_guards)
+ {
+ if (!(pushed_cond_guards= (bool*)thd->alloc(sizeof(bool) * cols_num)))
+ return TRUE;
+ for (uint i= 0; i < cols_num; i++)
+ pushed_cond_guards[i]= TRUE;
+ }
+ return FALSE;
+}
+
+
+bool
+Item_allany_subselect::select_transformer(JOIN *join)
+{
+ DBUG_ENTER("Item_allany_subselect::select_transformer");
+ DBUG_ASSERT((in_strategy & ~(SUBS_MAXMIN_INJECTED | SUBS_MAXMIN_ENGINE |
+ SUBS_IN_TO_EXISTS | SUBS_STRATEGY_CHOSEN)) == 0);
+ if (upper_item)
+ upper_item->show= 1;
+ DBUG_RETURN(select_in_like_transformer(join));
+}
+
+
+void Item_allany_subselect::print(String *str, enum_query_type query_type)
+{
+ if (test_strategy(SUBS_IN_TO_EXISTS))
+ str->append(STRING_WITH_LEN("<exists>"));
+ else
+ {
+ left_expr->print(str, query_type);
+ str->append(' ');
+ str->append(func->symbol(all));
+ str->append(all ? " all " : " any ", 5);
+ }
+ Item_subselect::print(str, query_type);
+}
+
+
+void Item_allany_subselect::no_rows_in_result()
+{
+ /*
+ Subquery predicates outside of the SELECT list must be evaluated in order
+ to possibly filter the special result row generated for implicit grouping
+ if the subquery is in the HAVING clause.
+ If the predicate is constant, we need its actual value in the only result
+ row for queries with implicit grouping.
+ */
+ if (parsing_place != SELECT_LIST || const_item())
+ return;
+ value= 0;
+ null_value= 0;
+ was_null= 0;
+ make_const();
+}
+
+
+void subselect_engine::set_thd(THD *thd_arg)
+{
+ thd= thd_arg;
+ if (result)
+ result->set_thd(thd_arg);
+}
+
+
+subselect_single_select_engine::
+subselect_single_select_engine(THD *thd_arg, st_select_lex *select,
+ select_result_interceptor *result_arg,
+ Item_subselect *item_arg)
+ :subselect_engine(thd_arg, item_arg, result_arg),
+ prepared(0), executed(0),
+ select_lex(select), join(0)
+{
+ select_lex->master_unit()->item= item_arg;
+}
+
+int subselect_single_select_engine::get_identifier()
+{
+ return select_lex->select_number;
+}
+
+void subselect_single_select_engine::cleanup()
+{
+ DBUG_ENTER("subselect_single_select_engine::cleanup");
+ prepared= executed= 0;
+ join= 0;
+ result->cleanup();
+ select_lex->uncacheable&= ~UNCACHEABLE_DEPENDENT_INJECTED;
+ DBUG_VOID_RETURN;
+}
+
+
+void subselect_union_engine::cleanup()
+{
+ DBUG_ENTER("subselect_union_engine::cleanup");
+ unit->reinit_exec_mechanism();
+ result->cleanup();
+ unit->uncacheable&= ~UNCACHEABLE_DEPENDENT_INJECTED;
+ for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
+ sl->uncacheable&= ~UNCACHEABLE_DEPENDENT_INJECTED;
+ DBUG_VOID_RETURN;
+}
+
+
+bool subselect_union_engine::is_executed() const
+{
+ return unit->executed;
+}
+
+
+/*
+ Check if last execution of the subquery engine produced any rows
+
+ SYNOPSIS
+ subselect_union_engine::no_rows()
+
+ DESCRIPTION
+ Check if last execution of the subquery engine produced any rows. The
+ return value is undefined if last execution ended in an error.
+
+ RETURN
+ TRUE - Last subselect execution has produced no rows
+ FALSE - Otherwise
+*/
+
+bool subselect_union_engine::no_rows()
+{
+ /* Check if we got any rows when reading UNION result from temp. table: */
+ return MY_TEST(!unit->fake_select_lex->join->send_records);
+}
+
+
+void subselect_uniquesubquery_engine::cleanup()
+{
+ DBUG_ENTER("subselect_uniquesubquery_engine::cleanup");
+ /*
+ Note for mergers: we don't have to, and actually must not de-initialize
+ tab->table->file here.
+ - We don't have to, because free_tmp_table() will call ha_index_or_rnd_end
+ - We must not do it, because tab->table may be a derived table which
+ has been already dropped by close_thread_tables(), while we here are
+ called from cleanup_items()
+ */
+ DBUG_VOID_RETURN;
+}
+
+
+subselect_union_engine::subselect_union_engine(THD *thd_arg, st_select_lex_unit *u,
+ select_result_interceptor *result_arg,
+ Item_subselect *item_arg)
+ :subselect_engine(thd_arg, item_arg, result_arg)
+{
+ unit= u;
+ unit->item= item_arg;
+}
+
+
+/**
+ Create and prepare the JOIN object that represents the query execution
+ plan for the subquery.
+
+ @details
+ This method is called from Item_subselect::fix_fields. For prepared
+ statements it is called both during the PREPARE and EXECUTE phases in the
+ following ways:
+ - During PREPARE the optimizer needs some properties
+ (join->fields_list.elements) of the JOIN to proceed with preparation of
+ the remaining query (namely to complete ::fix_fields for the subselect
+ related classes. In the end of PREPARE the JOIN is deleted.
+ - When we EXECUTE the query, Item_subselect::fix_fields is called again, and
+ the JOIN object is re-created again, prepared and executed. In the end of
+ execution it is deleted.
+ In all cases the JOIN is created in runtime memory (not in the permanent
+ memory root).
+
+ @todo
+ Re-check what properties of 'join' are needed during prepare, and see if
+ we can avoid creating a JOIN during JOIN::prepare of the outer join.
+
+ @retval 0 if success
+ @retval 1 if error
+*/
+
+int subselect_single_select_engine::prepare()
+{
+ if (prepared)
+ return 0;
+ if (select_lex->join)
+ {
+ select_lex->cleanup();
+ }
+ join= new JOIN(thd, select_lex->item_list,
+ select_lex->options | SELECT_NO_UNLOCK, result);
+ if (!join || !result)
+ return 1; /* Fatal error is set already. */
+ prepared= 1;
+ SELECT_LEX *save_select= thd->lex->current_select;
+ thd->lex->current_select= select_lex;
+ if (join->prepare(&select_lex->ref_pointer_array,
+ select_lex->table_list.first,
+ select_lex->with_wild,
+ select_lex->where,
+ select_lex->order_list.elements +
+ select_lex->group_list.elements,
+ select_lex->order_list.first,
+ false,
+ select_lex->group_list.first,
+ select_lex->having,
+ NULL, select_lex,
+ select_lex->master_unit()))
+ return 1;
+ thd->lex->current_select= save_select;
+ return 0;
+}
+
+int subselect_union_engine::prepare()
+{
+ return unit->prepare(thd, result, SELECT_NO_UNLOCK);
+}
+
+int subselect_uniquesubquery_engine::prepare()
+{
+ /* Should never be called. */
+ DBUG_ASSERT(FALSE);
+ return 1;
+}
+
+
+/*
+ Check if last execution of the subquery engine produced any rows
+
+ SYNOPSIS
+ subselect_single_select_engine::no_rows()
+
+ DESCRIPTION
+ Check if last execution of the subquery engine produced any rows. The
+ return value is undefined if last execution ended in an error.
+
+ RETURN
+ TRUE - Last subselect execution has produced no rows
+ FALSE - Otherwise
+*/
+
+bool subselect_single_select_engine::no_rows()
+{
+ return !item->assigned();
+}
+
+
+/*
+ makes storage for the output values for the subquery and calcuates
+ their data and column types and their nullability.
+*/
+void subselect_engine::set_row(List<Item> &item_list, Item_cache **row)
+{
+ Item *sel_item;
+ List_iterator_fast<Item> li(item_list);
+ cmp_type= res_type= STRING_RESULT;
+ res_field_type= MYSQL_TYPE_VAR_STRING;
+ for (uint i= 0; (sel_item= li++); i++)
+ {
+ item->max_length= sel_item->max_length;
+ res_type= sel_item->result_type();
+ cmp_type= sel_item->cmp_type();
+ res_field_type= sel_item->field_type();
+ item->decimals= sel_item->decimals;
+ item->unsigned_flag= sel_item->unsigned_flag;
+ maybe_null= sel_item->maybe_null;
+ if (!(row[i]= Item_cache::get_cache(sel_item, sel_item->cmp_type())))
+ return;
+ row[i]->setup(sel_item);
+ //psergey-backport-timours: row[i]->store(sel_item);
+ }
+ if (item_list.elements > 1)
+ cmp_type= res_type= ROW_RESULT;
+}
+
+void subselect_single_select_engine::fix_length_and_dec(Item_cache **row)
+{
+ DBUG_ASSERT(row || select_lex->item_list.elements==1);
+ set_row(select_lex->item_list, row);
+ item->collation.set(row[0]->collation);
+ if (cols() != 1)
+ maybe_null= 0;
+}
+
+void subselect_union_engine::fix_length_and_dec(Item_cache **row)
+{
+ DBUG_ASSERT(row || unit->first_select()->item_list.elements==1);
+
+ if (unit->first_select()->item_list.elements == 1)
+ {
+ set_row(unit->types, row);
+ item->collation.set(row[0]->collation);
+ }
+ else
+ {
+ bool maybe_null_saved= maybe_null;
+ set_row(unit->types, row);
+ maybe_null= maybe_null_saved;
+ }
+}
+
+void subselect_uniquesubquery_engine::fix_length_and_dec(Item_cache **row)
+{
+ //this never should be called
+ DBUG_ASSERT(0);
+}
+
+int read_first_record_seq(JOIN_TAB *tab);
+int rr_sequential(READ_RECORD *info);
+int join_read_always_key_or_null(JOIN_TAB *tab);
+int join_read_next_same_or_null(READ_RECORD *info);
+
+int subselect_single_select_engine::exec()
+{
+ DBUG_ENTER("subselect_single_select_engine::exec");
+
+ char const *save_where= thd->where;
+ SELECT_LEX *save_select= thd->lex->current_select;
+ thd->lex->current_select= select_lex;
+
+ if (!join->optimized)
+ {
+ SELECT_LEX_UNIT *unit= select_lex->master_unit();
+
+ unit->set_limit(unit->global_parameters);
+ if (join->optimize())
+ {
+ thd->where= save_where;
+ executed= 1;
+ thd->lex->current_select= save_select;
+ DBUG_RETURN(join->error ? join->error : 1);
+ }
+ if (!select_lex->uncacheable && thd->lex->describe &&
+ !(join->select_options & SELECT_DESCRIBE))
+ {
+ item->update_used_tables();
+ if (item->const_item())
+ {
+ /*
+ It's necessary to keep original JOIN table because
+ create_sort_index() function may overwrite original
+ JOIN_TAB::type and wrong optimization method can be
+ selected on re-execution.
+ */
+ select_lex->uncacheable|= UNCACHEABLE_EXPLAIN;
+ select_lex->master_unit()->uncacheable|= UNCACHEABLE_EXPLAIN;
+ /*
+ Force join->join_tmp creation, because this subquery will be replaced
+ by a simple select from the materialization temp table by optimize()
+ called by EXPLAIN and we need to preserve the initial query structure
+ so we can display it.
+ */
+ if (join->need_tmp && join->init_save_join_tab())
+ DBUG_RETURN(1); /* purecov: inspected */
+ }
+ }
+ if (item->engine_changed(this))
+ DBUG_RETURN(1);
+ }
+ if (select_lex->uncacheable &&
+ select_lex->uncacheable != UNCACHEABLE_EXPLAIN
+ && executed)
+ {
+ if (join->reinit())
+ {
+ thd->where= save_where;
+ thd->lex->current_select= save_select;
+ DBUG_RETURN(1);
+ }
+ item->reset();
+ item->assigned((executed= 0));
+ }
+ if (!executed)
+ {
+ item->reset_value_registration();
+ JOIN_TAB *changed_tabs[MAX_TABLES];
+ JOIN_TAB **last_changed_tab= changed_tabs;
+ if (item->have_guarded_conds())
+ {
+ /*
+ For at least one of the pushed predicates the following is true:
+ We should not apply optimizations based on the condition that was
+ pushed down into the subquery. Those optimizations are ref[_or_null]
+ acceses. Change them to be full table scans.
+ */
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab; tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+ if (tab && tab->keyuse)
+ {
+ for (uint i= 0; i < tab->ref.key_parts; i++)
+ {
+ bool *cond_guard= tab->ref.cond_guards[i];
+ if (cond_guard && !*cond_guard)
+ {
+ /* Change the access method to full table scan */
+ tab->save_read_first_record= tab->read_first_record;
+ tab->save_read_record= tab->read_record.read_record;
+ tab->read_record.read_record= rr_sequential;
+ tab->read_first_record= read_first_record_seq;
+ tab->read_record.record= tab->table->record[0];
+ tab->read_record.thd= join->thd;
+ tab->read_record.ref_length= tab->table->file->ref_length;
+ tab->read_record.unlock_row= rr_unlock_row;
+ *(last_changed_tab++)= tab;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ join->exec();
+
+ /* Enable the optimizations back */
+ for (JOIN_TAB **ptab= changed_tabs; ptab != last_changed_tab; ptab++)
+ {
+ JOIN_TAB *tab= *ptab;
+ tab->read_record.record= 0;
+ tab->read_record.ref_length= 0;
+ tab->read_first_record= tab->save_read_first_record;
+ tab->read_record.read_record= tab->save_read_record;
+ }
+ executed= 1;
+ if (!(uncacheable() & ~UNCACHEABLE_EXPLAIN))
+ item->make_const();
+ thd->where= save_where;
+ thd->lex->current_select= save_select;
+ DBUG_RETURN(join->error || thd->is_fatal_error || thd->is_error());
+ }
+ thd->where= save_where;
+ thd->lex->current_select= save_select;
+ DBUG_RETURN(0);
+}
+
+int subselect_union_engine::exec()
+{
+ char const *save_where= thd->where;
+ int res= unit->exec();
+ thd->where= save_where;
+ return res;
+}
+
+
+/*
+ Search for at least one row satisfying select condition
+
+ SYNOPSIS
+ subselect_uniquesubquery_engine::scan_table()
+
+ DESCRIPTION
+ Scan the table using sequential access until we find at least one row
+ satisfying select condition.
+
+ The caller must set this->empty_result_set=FALSE before calling this
+ function. This function will set it to TRUE if it finds a matching row.
+
+ RETURN
+ FALSE - OK
+ TRUE - Error
+*/
+
+int subselect_uniquesubquery_engine::scan_table()
+{
+ int error;
+ TABLE *table= tab->table;
+ DBUG_ENTER("subselect_uniquesubquery_engine::scan_table");
+
+ if ((table->file->inited &&
+ (error= table->file->ha_index_end())) ||
+ (error= table->file->ha_rnd_init(1)))
+ {
+ (void) report_error(table, error);
+ DBUG_RETURN(true);
+ }
+
+ table->file->extra_opt(HA_EXTRA_CACHE,
+ current_thd->variables.read_buff_size);
+ table->null_row= 0;
+ for (;;)
+ {
+ error=table->file->ha_rnd_next(table->record[0]);
+ if (error) {
+ if (error == HA_ERR_RECORD_DELETED)
+ {
+ error= 0;
+ continue;
+ }
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ error= 0;
+ break;
+ }
+ else
+ {
+ error= report_error(table, error);
+ break;
+ }
+ }
+
+ if (!cond || cond->val_int())
+ {
+ empty_result_set= FALSE;
+ break;
+ }
+ }
+
+ table->file->ha_rnd_end();
+ DBUG_RETURN(error != 0);
+}
+
+
+/**
+ Copy ref key for index access into the only subquery table.
+
+ @details
+ Copy ref key and check for conversion problems.
+ If there is an error converting the left IN operand to the column type of
+ the right IN operand count it as no match. In this case IN has the value of
+ FALSE. We mark the subquery table cursor as having no more rows (to ensure
+ that the processing that follows will not find a match) and return FALSE,
+ so IN is not treated as returning NULL.
+
+ @returns
+ @retval FALSE The outer ref was copied into an index lookup key.
+ @retval TRUE The outer ref cannot possibly match any row, IN is FALSE.
+*/
+
+bool subselect_uniquesubquery_engine::copy_ref_key(bool skip_constants)
+{
+ DBUG_ENTER("subselect_uniquesubquery_engine::copy_ref_key");
+
+ for (store_key **copy= tab->ref.key_copy ; *copy ; copy++)
+ {
+ enum store_key::store_key_result store_res;
+ if (skip_constants && (*copy)->store_key_is_const())
+ continue;
+ store_res= (*copy)->copy();
+ tab->ref.key_err= store_res;
+
+ if (store_res == store_key::STORE_KEY_FATAL)
+ {
+ /*
+ Error converting the left IN operand to the column type of the right
+ IN operand.
+ */
+ DBUG_RETURN(true);
+ }
+ }
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Execute subselect via unique index lookup
+
+ @details
+ Find rows corresponding to the ref key using index access.
+ If some part of the lookup key is NULL, then we're evaluating
+ NULL IN (SELECT ... )
+ This is a special case, we don't need to search for NULL in the table,
+ instead, the result value is
+ - NULL if select produces empty row set
+ - FALSE otherwise.
+
+ In some cases (IN subselect is a top level item, i.e. abort_on_null==TRUE)
+ the caller doesn't distinguish between NULL and FALSE result and we just
+ return FALSE.
+ Otherwise we make a full table scan to see if there is at least one
+ matching row.
+
+ The result of this function (info about whether a row was found) is
+ stored in this->empty_result_set.
+
+ @returns
+ @retval 0 OK
+ @retval 1 notify caller to call Item_subselect::reset(),
+ in most cases reset() sets the result to NULL
+*/
+
+int subselect_uniquesubquery_engine::exec()
+{
+ DBUG_ENTER("subselect_uniquesubquery_engine::exec");
+ int error;
+ TABLE *table= tab->table;
+ empty_result_set= TRUE;
+ table->status= 0;
+ Item_in_subselect *in_subs= (Item_in_subselect *) item;
+
+ if (!tab->preread_init_done && tab->preread_init())
+ DBUG_RETURN(1);
+
+ if (in_subs->left_expr_has_null())
+ {
+ /*
+ The case when all values in left_expr are NULL is handled by
+ Item_in_optimizer::val_int().
+ */
+ if (in_subs->is_top_level_item())
+ DBUG_RETURN(1); /* notify caller to call reset() and set NULL value. */
+ else
+ DBUG_RETURN(scan_table());
+ }
+
+ if (copy_ref_key(true))
+ {
+ /* We know that there will be no rows even if we scan. */
+ in_subs->value= 0;
+ DBUG_RETURN(0);
+ }
+
+ if (!table->file->inited &&
+ (error= table->file->ha_index_init(tab->ref.key, 0)))
+ {
+ (void) report_error(table, error);
+ DBUG_RETURN(true);
+ }
+
+ error= table->file->ha_index_read_map(table->record[0],
+ tab->ref.key_buff,
+ make_prev_keypart_map(tab->
+ ref.key_parts),
+ HA_READ_KEY_EXACT);
+ if (error &&
+ error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ error= report_error(table, error);
+ else
+ {
+ error= 0;
+ table->null_row= 0;
+ if (!table->status && (!cond || cond->val_int()))
+ {
+ ((Item_in_subselect *) item)->value= 1;
+ empty_result_set= FALSE;
+ }
+ else
+ ((Item_in_subselect *) item)->value= 0;
+ }
+
+ DBUG_RETURN(error != 0);
+}
+
+
+/*
+ TIMOUR: write comment
+*/
+
+int subselect_uniquesubquery_engine::index_lookup()
+{
+ DBUG_ENTER("subselect_uniquesubquery_engine::index_lookup");
+ int error;
+ TABLE *table= tab->table;
+
+ if (!table->file->inited)
+ table->file->ha_index_init(tab->ref.key, 0);
+ error= table->file->ha_index_read_map(table->record[0],
+ tab->ref.key_buff,
+ make_prev_keypart_map(tab->
+ ref.key_parts),
+ HA_READ_KEY_EXACT);
+ DBUG_PRINT("info", ("lookup result: %i", error));
+
+ if (error && error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ {
+ /*
+ TIMOUR: I don't understand at all when do we need to call report_error.
+ In most places where we access an index, we don't do this. Why here?
+ */
+ error= report_error(table, error);
+ DBUG_RETURN(error);
+ }
+
+ table->null_row= 0;
+ if (!error && (!cond || cond->val_int()))
+ ((Item_in_subselect *) item)->value= 1;
+ else
+ ((Item_in_subselect *) item)->value= 0;
+
+ DBUG_RETURN(0);
+}
+
+
+
+subselect_uniquesubquery_engine::~subselect_uniquesubquery_engine()
+{
+ /* Tell handler we don't need the index anymore */
+ //psergey-merge-todo: the following was gone in 6.0:
+ //psergey-merge: don't need this after all: tab->table->file->ha_index_end();
+}
+
+
+/**
+ Execute subselect via unique index lookup
+
+ @details
+ The engine is used to resolve subqueries in form
+
+ oe IN (SELECT key FROM tbl WHERE subq_where)
+
+ The value of the predicate is calculated as follows:
+ 1. If oe IS NULL, this is a special case, do a full table scan on
+ table tbl and search for row that satisfies subq_where. If such
+ row is found, return NULL, otherwise return FALSE.
+ 2. Make an index lookup via key=oe, search for a row that satisfies
+ subq_where. If found, return TRUE.
+ 3. If check_null==TRUE, make another lookup via key=NULL, search for a
+ row that satisfies subq_where. If found, return NULL, otherwise
+ return FALSE.
+
+ @todo
+ The step #1 can be optimized further when the index has several key
+ parts. Consider a subquery:
+
+ (oe1, oe2) IN (SELECT keypart1, keypart2 FROM tbl WHERE subq_where)
+
+ and suppose we need to evaluate it for {oe1, oe2}=={const1, NULL}.
+ Current code will do a full table scan and obtain correct result. There
+ is a better option: instead of evaluating
+
+ SELECT keypart1, keypart2 FROM tbl WHERE subq_where (1)
+
+ and checking if it has produced any matching rows, evaluate
+
+ SELECT keypart2 FROM tbl WHERE subq_where AND keypart1=const1 (2)
+
+ If this query produces a row, the result is NULL (as we're evaluating
+ "(const1, NULL) IN { (const1, X), ... }", which has a value of UNKNOWN,
+ i.e. NULL). If the query produces no rows, the result is FALSE.
+
+ We currently evaluate (1) by doing a full table scan. (2) can be
+ evaluated by doing a "ref" scan on "keypart1=const1", which can be much
+ cheaper. We can use index statistics to quickly check whether "ref" scan
+ will be cheaper than full table scan.
+
+ @returns
+ @retval 0 OK
+ @retval 1 notify caller to call Item_subselect::reset(),
+ in most cases reset() sets the result to NULL
+*/
+
+int subselect_indexsubquery_engine::exec()
+{
+ DBUG_ENTER("subselect_indexsubquery_engine");
+ int error;
+ bool null_finding= 0;
+ TABLE *table= tab->table;
+ Item_in_subselect *in_subs= (Item_in_subselect *) item;
+
+ ((Item_in_subselect *) item)->value= 0;
+ empty_result_set= TRUE;
+ table->status= 0;
+
+ if (check_null)
+ {
+ /* We need to check for NULL if there wasn't a matching value */
+ *tab->ref.null_ref_key= 0; // Search first for not null
+ ((Item_in_subselect *) item)->was_null= 0;
+ }
+
+ if (!tab->preread_init_done && tab->preread_init())
+ DBUG_RETURN(1);
+
+ if (in_subs->left_expr_has_null())
+ {
+ /*
+ The case when all values in left_expr are NULL is handled by
+ Item_in_optimizer::val_int().
+ */
+ if (in_subs->is_top_level_item())
+ DBUG_RETURN(1); /* notify caller to call reset() and set NULL value. */
+ else
+ DBUG_RETURN(scan_table());
+ }
+
+ if (copy_ref_key(true))
+ {
+ /* We know that there will be no rows even if we scan. */
+ in_subs->value= 0;
+ DBUG_RETURN(0);
+ }
+
+ if (!table->file->inited &&
+ (error= table->file->ha_index_init(tab->ref.key, 1)))
+ {
+ (void) report_error(table, error);
+ DBUG_RETURN(true);
+ }
+
+ error= table->file->ha_index_read_map(table->record[0],
+ tab->ref.key_buff,
+ make_prev_keypart_map(tab->
+ ref.key_parts),
+ HA_READ_KEY_EXACT);
+ if (error &&
+ error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ error= report_error(table, error);
+ else
+ {
+ for (;;)
+ {
+ error= 0;
+ table->null_row= 0;
+ if (!table->status)
+ {
+ if ((!cond || cond->val_int()) && (!having || having->val_int()))
+ {
+ empty_result_set= FALSE;
+ if (null_finding)
+ ((Item_in_subselect *) item)->was_null= 1;
+ else
+ ((Item_in_subselect *) item)->value= 1;
+ break;
+ }
+ error= table->file->ha_index_next_same(table->record[0],
+ tab->ref.key_buff,
+ tab->ref.key_length);
+ if (error && error != HA_ERR_END_OF_FILE)
+ {
+ error= report_error(table, error);
+ break;
+ }
+ }
+ else
+ {
+ if (!check_null || null_finding)
+ break; /* We don't need to check nulls */
+ *tab->ref.null_ref_key= 1;
+ null_finding= 1;
+ /* Check if there exists a row with a null value in the index */
+ if ((error= (safe_index_read(tab) == 1)))
+ break;
+ }
+ }
+ }
+ DBUG_RETURN(error != 0);
+}
+
+
+uint subselect_single_select_engine::cols()
+{
+ //psergey-sj-backport: the following assert was gone in 6.0:
+ //DBUG_ASSERT(select_lex->join != 0); // should be called after fix_fields()
+ //return select_lex->join->fields_list.elements;
+ return select_lex->item_list.elements;
+}
+
+
+uint subselect_union_engine::cols()
+{
+ DBUG_ASSERT(unit->is_prepared()); // should be called after fix_fields()
+ return unit->types.elements;
+}
+
+
+uint8 subselect_single_select_engine::uncacheable()
+{
+ return select_lex->uncacheable;
+}
+
+
+uint8 subselect_union_engine::uncacheable()
+{
+ return unit->uncacheable;
+}
+
+
+void subselect_single_select_engine::exclude()
+{
+ select_lex->master_unit()->exclude_level();
+}
+
+void subselect_union_engine::exclude()
+{
+ unit->exclude_level();
+}
+
+
+void subselect_uniquesubquery_engine::exclude()
+{
+ //this never should be called
+ DBUG_ASSERT(0);
+}
+
+
+table_map subselect_engine::calc_const_tables(List<TABLE_LIST> &list)
+{
+ table_map map= 0;
+ List_iterator<TABLE_LIST> ti(list);
+ TABLE_LIST *table;
+ //for (; table; table= table->next_leaf)
+ while ((table= ti++))
+ {
+ TABLE *tbl= table->table;
+ if (tbl && tbl->const_table)
+ map|= tbl->map;
+ }
+ return map;
+}
+
+
+table_map subselect_single_select_engine::upper_select_const_tables()
+{
+ return calc_const_tables(select_lex->outer_select()->leaf_tables);
+}
+
+
+table_map subselect_union_engine::upper_select_const_tables()
+{
+ return calc_const_tables(unit->outer_select()->leaf_tables);
+}
+
+
+void subselect_single_select_engine::print(String *str,
+ enum_query_type query_type)
+{
+ select_lex->print(thd, str, query_type);
+}
+
+
+void subselect_union_engine::print(String *str, enum_query_type query_type)
+{
+ unit->print(str, query_type);
+}
+
+
+void subselect_uniquesubquery_engine::print(String *str,
+ enum_query_type query_type)
+{
+ char *table_name= tab->table->s->table_name.str;
+ str->append(STRING_WITH_LEN("<primary_index_lookup>("));
+ tab->ref.items[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" in "));
+ if (tab->table->s->table_category == TABLE_CATEGORY_TEMPORARY)
+ {
+ /*
+ Temporary tables' names change across runs, so they can't be used for
+ EXPLAIN EXTENDED.
+ */
+ str->append(STRING_WITH_LEN("<temporary table>"));
+ }
+ else
+ str->append(table_name, tab->table->s->table_name.length);
+ KEY *key_info= tab->table->key_info+ tab->ref.key;
+ str->append(STRING_WITH_LEN(" on "));
+ str->append(key_info->name);
+ if (cond)
+ {
+ str->append(STRING_WITH_LEN(" where "));
+ cond->print(str, query_type);
+ }
+ str->append(')');
+}
+
+/*
+TODO:
+The above ::print method should be changed as below. Do it after
+all other tests pass.
+
+void subselect_uniquesubquery_engine::print(String *str)
+{
+ KEY *key_info= tab->table->key_info + tab->ref.key;
+ str->append(STRING_WITH_LEN("<primary_index_lookup>("));
+ for (uint i= 0; i < key_info->user_defined_key_parts; i++)
+ tab->ref.items[i]->print(str);
+ str->append(STRING_WITH_LEN(" in "));
+ str->append(tab->table->s->table_name.str, tab->table->s->table_name.length);
+ str->append(STRING_WITH_LEN(" on "));
+ str->append(key_info->name);
+ if (cond)
+ {
+ str->append(STRING_WITH_LEN(" where "));
+ cond->print(str);
+ }
+ str->append(')');
+}
+*/
+
+void subselect_indexsubquery_engine::print(String *str,
+ enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("<index_lookup>("));
+ tab->ref.items[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" in "));
+ str->append(tab->table->s->table_name.str, tab->table->s->table_name.length);
+ KEY *key_info= tab->table->key_info+ tab->ref.key;
+ str->append(STRING_WITH_LEN(" on "));
+ str->append(key_info->name);
+ if (check_null)
+ str->append(STRING_WITH_LEN(" checking NULL"));
+ if (cond)
+ {
+ str->append(STRING_WITH_LEN(" where "));
+ cond->print(str, query_type);
+ }
+ if (having)
+ {
+ str->append(STRING_WITH_LEN(" having "));
+ having->print(str, query_type);
+ }
+ str->append(')');
+}
+
+/**
+ change select_result object of engine.
+
+ @param si new subselect Item
+ @param res new select_result object
+ @param temp temporary assignment
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+bool
+subselect_single_select_engine::change_result(Item_subselect *si,
+ select_result_interceptor *res,
+ bool temp)
+{
+ DBUG_ENTER("subselect_single_select_engine::change_result");
+ item= si;
+ if (temp)
+ {
+ /*
+ Here we reuse change_item_tree to roll back assignment. It has
+ nothing special about Item* pointer so it is safe conversion. We do
+ not change the interface to be compatible with MySQL.
+ */
+ thd->change_item_tree((Item**) &result, (Item*)res);
+ }
+ else
+ result= res;
+
+ /*
+ We can't use 'result' below as gcc 4.2.4's alias optimization
+ assumes that result was not changed by thd->change_item_tree().
+ I tried to find a solution to make gcc happy, but could not find anything
+ that would not require a lot of extra code that would be harder to manage
+ than the current code.
+ */
+ DBUG_RETURN(select_lex->join->change_result(res));
+}
+
+
+/**
+ change select_result object of engine.
+
+ @param si new subselect Item
+ @param res new select_result object
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+bool subselect_union_engine::change_result(Item_subselect *si,
+ select_result_interceptor *res,
+ bool temp)
+{
+ item= si;
+ int rc= unit->change_result(res, result);
+ if (temp)
+ thd->change_item_tree((Item**) &result, (Item*)res);
+ else
+ result= res;
+ return rc;
+}
+
+
+/**
+ change select_result emulation, never should be called.
+
+ @param si new subselect Item
+ @param res new select_result object
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+bool
+subselect_uniquesubquery_engine::change_result(Item_subselect *si,
+ select_result_interceptor *res,
+ bool temp
+ __attribute__((unused)))
+{
+ DBUG_ASSERT(0);
+ return TRUE;
+}
+
+
+/**
+ Report about presence of tables in subquery.
+
+ @retval
+ TRUE there are not tables used in subquery
+ @retval
+ FALSE there are some tables in subquery
+*/
+bool subselect_single_select_engine::no_tables()
+{
+ return(select_lex->table_list.elements == 0);
+}
+
+
+/*
+ Check statically whether the subquery can return NULL
+
+ SINOPSYS
+ subselect_single_select_engine::may_be_null()
+
+ RETURN
+ FALSE can guarantee that the subquery never return NULL
+ TRUE otherwise
+*/
+bool subselect_single_select_engine::may_be_null()
+{
+ return ((no_tables() && !join->conds && !join->having) ? maybe_null : 1);
+}
+
+
+/**
+ Report about presence of tables in subquery.
+
+ @retval
+ TRUE there are not tables used in subquery
+ @retval
+ FALSE there are some tables in subquery
+*/
+bool subselect_union_engine::no_tables()
+{
+ for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
+ {
+ if (sl->table_list.elements)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/**
+ Report about presence of tables in subquery.
+
+ @retval
+ TRUE there are not tables used in subquery
+ @retval
+ FALSE there are some tables in subquery
+*/
+
+bool subselect_uniquesubquery_engine::no_tables()
+{
+ /* returning value is correct, but this method should never be called */
+ DBUG_ASSERT(FALSE);
+ return 0;
+}
+
+
+/******************************************************************************
+ WL#1110 - Implementation of class subselect_hash_sj_engine
+******************************************************************************/
+
+
+/**
+ Check if an IN predicate should be executed via partial matching using
+ only schema information.
+
+ @details
+ This test essentially has three results:
+ - partial matching is applicable, but cannot be executed due to a
+ limitation in the total number of indexes, as a result we can't
+ use subquery materialization at all.
+ - partial matching is either applicable or not, and this can be
+ determined by looking at 'this->max_keys'.
+ If max_keys > 1, then we need partial matching because there are
+ more indexes than just the one we use during materialization to
+ remove duplicates.
+
+ @note
+ TIMOUR: The schema-based analysis for partial matching can be done once for
+ prepared statement and remembered. It is done here to remove the need to
+ save/restore all related variables between each re-execution, thus making
+ the code simpler.
+
+ @retval PARTIAL_MATCH if a partial match should be used
+ @retval COMPLETE_MATCH if a complete match (index lookup) should be used
+*/
+
+subselect_hash_sj_engine::exec_strategy
+subselect_hash_sj_engine::get_strategy_using_schema()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+
+ if (item_in->is_top_level_item())
+ return COMPLETE_MATCH;
+ else
+ {
+ List_iterator<Item> inner_col_it(*item_in->unit->get_unit_column_types());
+ Item *outer_col, *inner_col;
+
+ for (uint i= 0; i < item_in->left_expr->cols(); i++)
+ {
+ outer_col= item_in->left_expr->element_index(i);
+ inner_col= inner_col_it++;
+
+ if (!inner_col->maybe_null && !outer_col->maybe_null)
+ bitmap_set_bit(&non_null_key_parts, i);
+ else
+ {
+ bitmap_set_bit(&partial_match_key_parts, i);
+ ++count_partial_match_columns;
+ }
+ }
+ }
+
+ /* If no column contains NULLs use regular hash index lookups. */
+ if (count_partial_match_columns)
+ return PARTIAL_MATCH;
+ return COMPLETE_MATCH;
+}
+
+
+/**
+ Test whether an IN predicate must be computed via partial matching
+ based on the NULL statistics for each column of a materialized subquery.
+
+ @details The procedure analyzes column NULL statistics, updates the
+ matching type of columns that cannot be NULL or that contain only NULLs.
+ Based on this, the procedure determines the final execution strategy for
+ the [NOT] IN predicate.
+
+ @retval PARTIAL_MATCH if a partial match should be used
+ @retval COMPLETE_MATCH if a complete match (index lookup) should be used
+*/
+
+subselect_hash_sj_engine::exec_strategy
+subselect_hash_sj_engine::get_strategy_using_data()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+ Item *outer_col;
+
+ /*
+ If we already determined that a complete match is enough based on schema
+ information, nothing can be better.
+ */
+ if (strategy == COMPLETE_MATCH)
+ return COMPLETE_MATCH;
+
+ for (uint i= 0; i < item_in->left_expr->cols(); i++)
+ {
+ if (!bitmap_is_set(&partial_match_key_parts, i))
+ continue;
+ outer_col= item_in->left_expr->element_index(i);
+ /*
+ If column 'i' doesn't contain NULLs, and the corresponding outer reference
+ cannot have a NULL value, then 'i' is a non-nullable column.
+ */
+ if (result_sink->get_null_count_of_col(i) == 0 && !outer_col->maybe_null)
+ {
+ bitmap_clear_bit(&partial_match_key_parts, i);
+ bitmap_set_bit(&non_null_key_parts, i);
+ --count_partial_match_columns;
+ }
+ if (result_sink->get_null_count_of_col(i) == tmp_table->file->stats.records)
+ ++count_null_only_columns;
+ if (result_sink->get_null_count_of_col(i))
+ ++count_columns_with_nulls;
+ }
+
+ /* If no column contains NULLs use regular hash index lookups. */
+ if (!count_partial_match_columns)
+ return COMPLETE_MATCH;
+ return PARTIAL_MATCH;
+}
+
+
+void
+subselect_hash_sj_engine::choose_partial_match_strategy(
+ bool has_non_null_key, bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts)
+{
+ ulonglong pm_buff_size;
+
+ DBUG_ASSERT(strategy == PARTIAL_MATCH);
+ /*
+ Choose according to global optimizer switch. If only one of the switches is
+ 'ON', then the remaining strategy is the only possible one. The only cases
+ when this will be overriden is when the total size of all buffers for the
+ merge strategy is bigger than the 'rowid_merge_buff_size' system variable,
+ or if there isn't enough physical memory to allocate the buffers.
+ */
+ if (!optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN))
+ strategy= PARTIAL_MATCH_SCAN;
+ else if
+ ( optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) &&
+ !optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN))
+ strategy= PARTIAL_MATCH_MERGE;
+
+ /*
+ If both switches are ON, or both are OFF, we interpret that as "let the
+ optimizer decide". Perform a cost based choice between the two partial
+ matching strategies.
+ */
+ /*
+ TIMOUR: the above interpretation of the switch values could be changed to:
+ - if both are ON - let the optimizer decide,
+ - if both are OFF - do not use partial matching, therefore do not use
+ materialization in non-top-level predicates.
+ The problem with this is that we know for sure if we need partial matching
+ only after the subquery is materialized, and this is too late to revert to
+ the IN=>EXISTS strategy.
+ */
+ if (strategy == PARTIAL_MATCH)
+ {
+ /*
+ TIMOUR: Currently we use a super simplistic measure. This will be
+ addressed in a separate task.
+ */
+ if (tmp_table->file->stats.records < 100)
+ strategy= PARTIAL_MATCH_SCAN;
+ else
+ strategy= PARTIAL_MATCH_MERGE;
+ }
+
+ /* Check if there is enough memory for the rowid merge strategy. */
+ if (strategy == PARTIAL_MATCH_MERGE)
+ {
+ pm_buff_size= rowid_merge_buff_size(has_non_null_key,
+ has_covering_null_row,
+ partial_match_key_parts);
+ if (pm_buff_size > thd->variables.rowid_merge_buff_size)
+ strategy= PARTIAL_MATCH_SCAN;
+ }
+}
+
+
+/*
+ Compute the memory size of all buffers proportional to the number of rows
+ in tmp_table.
+
+ @details
+ If the result is bigger than thd->variables.rowid_merge_buff_size, partial
+ matching via merging is not applicable.
+*/
+
+ulonglong subselect_hash_sj_engine::rowid_merge_buff_size(
+ bool has_non_null_key, bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts)
+{
+ /* Total size of all buffers used by partial matching. */
+ ulonglong buff_size;
+ ha_rows row_count= tmp_table->file->stats.records;
+ uint rowid_length= tmp_table->file->ref_length;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+ ha_rows max_null_row;
+
+ /* Size of the subselect_rowid_merge_engine::row_num_to_rowid buffer. */
+ buff_size= row_count * rowid_length * sizeof(uchar);
+
+ if (has_non_null_key)
+ {
+ /* Add the size of Ordered_key::key_buff of the only non-NULL key. */
+ buff_size+= row_count * sizeof(rownum_t);
+ }
+
+ if (!has_covering_null_row)
+ {
+ for (uint i= 0; i < partial_match_key_parts->n_bits; i++)
+ {
+ if (!bitmap_is_set(partial_match_key_parts, i) ||
+ result_sink->get_null_count_of_col(i) == row_count)
+ continue; /* In these cases we wouldn't construct Ordered keys. */
+
+ /* Add the size of Ordered_key::key_buff */
+ buff_size+= (row_count - result_sink->get_null_count_of_col(i)) *
+ sizeof(rownum_t);
+ /* Add the size of Ordered_key::null_key */
+ max_null_row= result_sink->get_max_null_of_col(i);
+ if (max_null_row >= UINT_MAX)
+ {
+ /*
+ There can be at most UINT_MAX bits in a MY_BITMAP that is used to
+ store NULLs in an Ordered_key. Return a number of bytes bigger than
+ the maximum allowed memory buffer for partial matching to disable
+ the rowid merge strategy.
+ */
+ return ULONGLONG_MAX;
+ }
+ buff_size+= bitmap_buffer_size(max_null_row);
+ }
+ }
+
+ return buff_size;
+}
+
+
+/*
+ Initialize a MY_BITMAP with a buffer allocated on the current
+ memory root.
+ TIMOUR: move to bitmap C file?
+*/
+
+static my_bool
+my_bitmap_init_memroot(MY_BITMAP *map, uint n_bits, MEM_ROOT *mem_root)
+{
+ my_bitmap_map *bitmap_buf;
+
+ if (!(bitmap_buf= (my_bitmap_map*) alloc_root(mem_root,
+ bitmap_buffer_size(n_bits))) ||
+ my_bitmap_init(map, bitmap_buf, n_bits, FALSE))
+ return TRUE;
+ bitmap_clear_all(map);
+ return FALSE;
+}
+
+
+/**
+ Create all structures needed for IN execution that can live between PS
+ reexecution.
+
+ @param tmp_columns the items that produce the data for the temp table
+ @param subquery_id subquery's identifier (to make "<subquery%d>" name for
+ EXPLAIN)
+
+ @details
+ - Create a temporary table to store the result of the IN subquery. The
+ temporary table has one hash index on all its columns.
+ - Create a new result sink that sends the result stream of the subquery to
+ the temporary table,
+
+ @notice:
+ Currently Item_subselect::init() already chooses and creates at parse
+ time an engine with a corresponding JOIN to execute the subquery.
+
+ @retval TRUE if error
+ @retval FALSE otherwise
+*/
+
+bool subselect_hash_sj_engine::init(List<Item> *tmp_columns, uint subquery_id)
+{
+ select_union *result_sink;
+ /* Options to create_tmp_table. */
+ ulonglong tmp_create_options= thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS;
+ /* | TMP_TABLE_FORCE_MYISAM; TIMOUR: force MYISAM */
+
+ DBUG_ENTER("subselect_hash_sj_engine::init");
+
+ if (my_bitmap_init_memroot(&non_null_key_parts, tmp_columns->elements,
+ thd->mem_root) ||
+ my_bitmap_init_memroot(&partial_match_key_parts, tmp_columns->elements,
+ thd->mem_root))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Create and initialize a select result interceptor that stores the
+ result stream in a temporary table. The temporary table itself is
+ managed (created/filled/etc) internally by the interceptor.
+ */
+/*
+ TIMOUR:
+ Select a more efficient result sink when we know there is no need to collect
+ data statistics.
+
+ if (strategy == COMPLETE_MATCH)
+ {
+ if (!(result= new select_union))
+ DBUG_RETURN(TRUE);
+ }
+ else if (strategy == PARTIAL_MATCH)
+ {
+ if (!(result= new select_materialize_with_stats))
+ DBUG_RETURN(TRUE);
+ }
+*/
+ if (!(result_sink= new select_materialize_with_stats))
+ DBUG_RETURN(TRUE);
+
+ char buf[32];
+ uint len= my_snprintf(buf, sizeof(buf), "<subquery%d>", subquery_id);
+ char *name;
+ if (!(name= (char*)thd->alloc(len + 1)))
+ DBUG_RETURN(TRUE);
+ memcpy(name, buf, len+1);
+
+ result_sink->get_tmp_table_param()->materialized_subquery= true;
+ if (item->substype() == Item_subselect::IN_SUBS &&
+ ((Item_in_subselect*)item)->is_jtbm_merged)
+ {
+ result_sink->get_tmp_table_param()->force_not_null_cols= true;
+ }
+ if (result_sink->create_result_table(thd, tmp_columns, TRUE,
+ tmp_create_options,
+ name, TRUE, TRUE))
+ DBUG_RETURN(TRUE);
+
+ tmp_table= result_sink->table;
+ result= result_sink;
+
+ /*
+ If the subquery has blobs, or the total key lenght is bigger than
+ some length, or the total number of key parts is more than the
+ allowed maximum (currently MAX_REF_PARTS == 16), then the created
+ index cannot be used for lookups and we can't use hash semi
+ join. If this is the case, delete the temporary table since it
+ will not be used, and tell the caller we failed to initialize the
+ engine.
+ */
+ if (tmp_table->s->keys == 0)
+ {
+ //fprintf(stderr, "Q: %s\n", current_thd->query());
+ DBUG_ASSERT(0);
+ DBUG_ASSERT(
+ tmp_table->s->uniques ||
+ tmp_table->key_info->key_length >= tmp_table->file->max_key_length() ||
+ tmp_table->key_info->user_defined_key_parts >
+ tmp_table->file->max_key_parts());
+ free_tmp_table(thd, tmp_table);
+ tmp_table= NULL;
+ delete result;
+ result= NULL;
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Make sure there is only one index on the temp table, and it doesn't have
+ the extra key part created when s->uniques > 0.
+ */
+ DBUG_ASSERT(tmp_table->s->keys == 1 &&
+ ((Item_in_subselect *) item)->left_expr->cols() ==
+ tmp_table->key_info->user_defined_key_parts);
+
+ if (make_semi_join_conds() ||
+ /* A unique_engine is used both for complete and partial matching. */
+ !(lookup_engine= make_unique_engine()))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Repeat name resolution for 'cond' since cond is not part of any
+ clause of the query, and it is not 'fixed' during JOIN::prepare.
+ */
+ if (semi_join_conds && !semi_join_conds->fixed &&
+ semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds))
+ DBUG_RETURN(TRUE);
+ /* Let our engine reuse this query plan for materialization. */
+ materialize_join= materialize_engine->join;
+ materialize_join->change_result(result);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Create an artificial condition to post-filter those rows matched by index
+ lookups that cannot be distinguished by the index lookup procedure.
+
+ @notes
+ The need for post-filtering may occur e.g. because of
+ truncation. Prepared statements execution requires that fix_fields is
+ called for every execution. In order to call fix_fields we need to
+ create a Name_resolution_context and a corresponding TABLE_LIST for
+ the temporary table for the subquery, so that all column references
+ to the materialized subquery table can be resolved correctly.
+
+ @returns
+ @retval TRUE memory allocation error occurred
+ @retval FALSE the conditions were created and resolved (fixed)
+*/
+
+bool subselect_hash_sj_engine::make_semi_join_conds()
+{
+ /*
+ Table reference for tmp_table that is used to resolve column references
+ (Item_fields) to columns in tmp_table.
+ */
+ TABLE_LIST *tmp_table_ref;
+ /* Name resolution context for all tmp_table columns created below. */
+ Name_resolution_context *context;
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+
+ DBUG_ENTER("subselect_hash_sj_engine::make_semi_join_conds");
+ DBUG_ASSERT(semi_join_conds == NULL);
+
+ if (!(semi_join_conds= new Item_cond_and))
+ DBUG_RETURN(TRUE);
+
+ if (!(tmp_table_ref= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST))))
+ DBUG_RETURN(TRUE);
+
+ tmp_table_ref->init_one_table(STRING_WITH_LEN(""),
+ tmp_table->alias.c_ptr(),
+ tmp_table->alias.length(),
+ NULL, TL_READ);
+ tmp_table_ref->table= tmp_table;
+
+ context= new Name_resolution_context;
+ context->init();
+ context->first_name_resolution_table=
+ context->last_name_resolution_table= tmp_table_ref;
+ semi_join_conds_context= context;
+
+ for (uint i= 0; i < item_in->left_expr->cols(); i++)
+ {
+ Item_func_eq *eq_cond; /* New equi-join condition for the current column. */
+ /* Item for the corresponding field from the materialized temp table. */
+ Item_field *right_col_item;
+
+ if (!(right_col_item= new Item_field(thd, context, tmp_table->field[i])) ||
+ !(eq_cond= new Item_func_eq(item_in->left_expr->element_index(i),
+ right_col_item)) ||
+ (((Item_cond_and*)semi_join_conds)->add(eq_cond)))
+ {
+ delete semi_join_conds;
+ semi_join_conds= NULL;
+ DBUG_RETURN(TRUE);
+ }
+ }
+ if (semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Create a new uniquesubquery engine for the execution of an IN predicate.
+
+ @details
+ Create and initialize a new JOIN_TAB, and Table_ref objects to perform
+ lookups into the indexed temporary table.
+
+ @retval A new subselect_hash_sj_engine object
+ @retval NULL if a memory allocation error occurs
+*/
+
+subselect_uniquesubquery_engine*
+subselect_hash_sj_engine::make_unique_engine()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+ Item_iterator_row it(item_in->left_expr);
+ /* The only index on the temporary table. */
+ KEY *tmp_key= tmp_table->key_info;
+ JOIN_TAB *tab;
+
+ DBUG_ENTER("subselect_hash_sj_engine::make_unique_engine");
+
+ /*
+ Create and initialize the JOIN_TAB that represents an index lookup
+ plan operator into the materialized subquery result. Notice that:
+ - this JOIN_TAB has no corresponding JOIN (and doesn't need one), and
+ - here we initialize only those members that are used by
+ subselect_uniquesubquery_engine, so these objects are incomplete.
+ */
+ if (!(tab= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB))))
+ DBUG_RETURN(NULL);
+
+ tab->table= tmp_table;
+ tab->preread_init_done= FALSE;
+ tab->ref.tmp_table_index_lookup_init(thd, tmp_key, it, FALSE);
+
+ DBUG_RETURN(new subselect_uniquesubquery_engine(thd, tab, item,
+ semi_join_conds));
+}
+
+
+subselect_hash_sj_engine::~subselect_hash_sj_engine()
+{
+ delete lookup_engine;
+ delete result;
+ if (tmp_table)
+ free_tmp_table(thd, tmp_table);
+}
+
+
+int subselect_hash_sj_engine::prepare()
+{
+ /*
+ Create and optimize the JOIN that will be used to materialize
+ the subquery if not yet created.
+ */
+ return materialize_engine->prepare();
+}
+
+
+/**
+ Cleanup performed after each PS execution.
+
+ @details
+ Called in the end of JOIN::prepare for PS from Item_subselect::cleanup.
+*/
+
+void subselect_hash_sj_engine::cleanup()
+{
+ enum_engine_type lookup_engine_type= lookup_engine->engine_type();
+ is_materialized= FALSE;
+ bitmap_clear_all(&non_null_key_parts);
+ bitmap_clear_all(&partial_match_key_parts);
+ count_partial_match_columns= 0;
+ count_null_only_columns= 0;
+ strategy= UNDEFINED;
+ materialize_engine->cleanup();
+ /*
+ Restore the original Item_in_subselect engine. This engine is created once
+ at parse time and stored across executions, while all other materialization
+ related engines are created and chosen for each execution.
+ */
+ ((Item_in_subselect *) item)->engine= materialize_engine;
+ if (lookup_engine_type == TABLE_SCAN_ENGINE ||
+ lookup_engine_type == ROWID_MERGE_ENGINE)
+ {
+ subselect_engine *inner_lookup_engine;
+ inner_lookup_engine=
+ ((subselect_partial_match_engine*) lookup_engine)->lookup_engine;
+ /*
+ Partial match engines are recreated for each PS execution inside
+ subselect_hash_sj_engine::exec().
+ */
+ delete lookup_engine;
+ lookup_engine= inner_lookup_engine;
+ }
+ DBUG_ASSERT(lookup_engine->engine_type() == UNIQUESUBQUERY_ENGINE);
+ lookup_engine->cleanup();
+ result->cleanup(); /* Resets the temp table as well. */
+ DBUG_ASSERT(tmp_table);
+ free_tmp_table(thd, tmp_table);
+ tmp_table= NULL;
+}
+
+
+/*
+ Get fanout produced by tables specified in the table_map
+*/
+
+double get_fanout_with_deps(JOIN *join, table_map tset)
+{
+ /* Handle the case of "Impossible WHERE" */
+ if (join->table_count == 0)
+ return 0.0;
+
+ /* First, recursively get all tables we depend on */
+ table_map deps_to_check= tset;
+ table_map checked_deps= 0;
+ table_map further_deps;
+ do
+ {
+ further_deps= 0;
+ Table_map_iterator tm_it(deps_to_check);
+ int tableno;
+ while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
+ {
+ /* get tableno's dependency tables that are not in needed_set */
+ further_deps |= join->map2table[tableno]->ref.depend_map & ~checked_deps;
+ }
+
+ checked_deps |= deps_to_check;
+ deps_to_check= further_deps;
+ } while (further_deps != 0);
+
+
+ /* Now, walk the join order and calculate the fanout */
+ double fanout= 1;
+ for (JOIN_TAB *tab= first_top_level_tab(join, WITHOUT_CONST_TABLES); tab;
+ tab= next_top_level_tab(join, tab))
+ {
+ /*
+ Ignore SJM nests. They have tab->table==NULL. There is no point to walk
+ inside them, because GROUP BY clause cannot refer to tables from within
+ subquery.
+ */
+ if (!tab->is_sjm_nest() && (tab->table->map & checked_deps) &&
+ !tab->emb_sj_nest &&
+ tab->records_read != 0)
+ {
+ fanout *= tab->records_read;
+ }
+ }
+ return fanout;
+}
+
+
+#if 0
+void check_out_index_stats(JOIN *join)
+{
+ ORDER *order;
+ uint n_order_items;
+
+ /*
+ First, collect the keys that we can use in each table.
+ We can use a key if
+ - all tables refer to it.
+ */
+ key_map key_start_use[MAX_TABLES];
+ key_map key_infix_use[MAX_TABLES];
+ table_map key_used=0;
+ table_map non_key_used= 0;
+
+ bzero(&key_start_use, sizeof(key_start_use)); //psergey-todo: safe initialization!
+ bzero(&key_infix_use, sizeof(key_infix_use));
+
+ for (order= join->group_list; order; order= order->next)
+ {
+ Item *item= order->item[0];
+
+ if (item->real_type() == Item::FIELD_ITEM)
+ {
+ if (item->used_tables() & OUTER_REF_TABLE_BIT)
+ continue; /* outside references are like constants for us */
+
+ Field *field= ((Item_field*)item->real_item())->field;
+ uint table_no= field->table->tablenr;
+ if (!(non_key_used && table_map(1) << table_no) &&
+ !field->part_of_key.is_clear_all())
+ {
+ key_map infix_map= field->part_of_key;
+ infix_map.subtract(field->key_start);
+ key_start_use[table_no].merge(field->key_start);
+ key_infix_use[table_no].merge(infix_map);
+ key_used |= table_no;
+ }
+ continue;
+ }
+ /*
+ Note: the below will cause clauses like GROUP BY YEAR(date) not to be
+ handled.
+ */
+ non_key_used |= item->used_tables();
+ }
+
+ Table_map_iterator tm_it(key_used & ~non_key_used);
+ int tableno;
+ while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
+ {
+ key_map::iterator key_it(key_start_use);
+ int keyno;
+ while ((keyno = tm_it.next_bit()) != key_map::iterator::BITMAP_END)
+ {
+ for (order= join->group_list; order; order= order->next)
+ {
+ Item *item= order->item[0];
+ if (item->used_tables() & (table_map(1) << tableno))
+ {
+ DBUG_ASSERT(item->real_type() == Item::FIELD_ITEM);
+ }
+ }
+ /*
+ if (continuation)
+ {
+ walk through list and find which key parts are occupied;
+ // note that the above can't be made any faster.
+ }
+ else
+ use rec_per_key[0];
+
+ find out the cardinality.
+ check if cardinality decreases if we use it;
+ */
+ }
+ }
+}
+#endif
+
+
+/*
+ Get an estimate of how many records will be produced after the GROUP BY
+ operation.
+
+ @param join Join we're operating on
+ @param join_op_rows How many records will be produced by the join
+ operations (this is what join optimizer produces)
+
+ @seealso
+ See also optimize_semijoin_nests(), grep for "Adjust output cardinality
+ estimates". Very similar code there that is not joined with this one
+ because we operate on different data structs and too much effort is
+ needed to abstract them out.
+
+ @return
+ Number of records we expect to get after the GROUP BY operation
+*/
+
+double get_post_group_estimate(JOIN* join, double join_op_rows)
+{
+ table_map tables_in_group_list= table_map(0);
+
+ /* Find out which tables are used in GROUP BY list */
+ for (ORDER *order= join->group_list; order; order= order->next)
+ {
+ Item *item= order->item[0];
+ if (item->used_tables() & RAND_TABLE_BIT)
+ {
+ /* Each join output record will be in its own group */
+ return join_op_rows;
+ }
+ tables_in_group_list|= item->used_tables();
+ }
+ tables_in_group_list &= ~PSEUDO_TABLE_BITS;
+
+ /*
+ Use join fanouts to calculate the max. number of records in the group-list
+ */
+ double fanout_rows[MAX_KEY];
+ bzero(&fanout_rows, sizeof(fanout_rows));
+ double out_rows;
+
+ out_rows= get_fanout_with_deps(join, tables_in_group_list);
+
+#if 0
+ /* The following will be needed when making use of index stats: */
+ /*
+ Also generate max. number of records for each of the tables mentioned
+ in the group-list. We'll use that a baseline number that we'll try to
+ reduce by using
+ - #table-records
+ - index statistics.
+ */
+ Table_map_iterator tm_it(tables_in_group_list);
+ int tableno;
+ while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
+ {
+ fanout_rows[tableno]= get_fanout_with_deps(join, table_map(1) << tableno);
+ }
+
+ /*
+ Try to bring down estimates using index statistics.
+ */
+ //check_out_index_stats(join);
+#endif
+
+ return out_rows;
+}
+
+
+/**
+ Execute a subquery IN predicate via materialization.
+
+ @details
+ If needed materialize the subquery into a temporary table, then
+ copmpute the predicate via a lookup into this table.
+
+ @retval TRUE if error
+ @retval FALSE otherwise
+*/
+
+int subselect_hash_sj_engine::exec()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+ SELECT_LEX *save_select= thd->lex->current_select;
+ subselect_partial_match_engine *pm_engine= NULL;
+ int res= 0;
+
+ DBUG_ENTER("subselect_hash_sj_engine::exec");
+
+ /*
+ Optimize and materialize the subquery during the first execution of
+ the subquery predicate.
+ */
+ thd->lex->current_select= materialize_engine->select_lex;
+ /* The subquery should be optimized, and materialized only once. */
+ DBUG_ASSERT(materialize_join->optimized && !is_materialized);
+ materialize_join->exec();
+ if ((res= MY_TEST(materialize_join->error || thd->is_fatal_error ||
+ thd->is_error())))
+ goto err;
+
+ /*
+ TODO:
+ - Unlock all subquery tables as we don't need them. To implement this
+ we need to add new functionality to JOIN::join_free that can unlock
+ all tables in a subquery (and all its subqueries).
+ - The temp table used for grouping in the subquery can be freed
+ immediately after materialization (yet it's done together with
+ unlocking).
+ */
+ is_materialized= TRUE;
+ /*
+ If the subquery returned no rows, the temporary table is empty, so we know
+ directly that the result of IN is FALSE. We first update the table
+ statistics, then we test if the temporary table for the query result is
+ empty.
+ */
+ tmp_table->file->info(HA_STATUS_VARIABLE);
+ if (!tmp_table->file->stats.records)
+ {
+ /* The value of IN will not change during this execution. */
+ item_in->reset();
+ item_in->make_const();
+ item_in->set_first_execution();
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ TIMOUR: The schema-based analysis for partial matching can be done once for
+ prepared statement and remembered. It is done here to remove the need to
+ save/restore all related variables between each re-execution, thus making
+ the code simpler.
+ */
+ strategy= get_strategy_using_schema();
+ /* This call may discover that we don't need partial matching at all. */
+ strategy= get_strategy_using_data();
+ if (strategy == PARTIAL_MATCH)
+ {
+ uint count_pm_keys; /* Total number of keys needed for partial matching. */
+ MY_BITMAP *nn_key_parts= NULL; /* Key parts of the only non-NULL index. */
+ uint count_non_null_columns= 0; /* Number of columns in nn_key_parts. */
+ bool has_covering_null_row;
+ bool has_covering_null_columns;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+ uint field_count= tmp_table->s->fields;
+
+ if (count_partial_match_columns < field_count)
+ {
+ nn_key_parts= &non_null_key_parts;
+ count_non_null_columns= bitmap_bits_set(nn_key_parts);
+ }
+ has_covering_null_row= (result_sink->get_max_nulls_in_row() == field_count);
+ has_covering_null_columns= (count_non_null_columns +
+ count_null_only_columns == field_count);
+
+ if (has_covering_null_row && has_covering_null_columns)
+ {
+ /*
+ The whole table consist of only NULL values. The result of IN is
+ a constant UNKNOWN.
+ */
+ DBUG_ASSERT(tmp_table->file->stats.records == 1);
+ item_in->value= 0;
+ item_in->null_value= 1;
+ item_in->make_const();
+ item_in->set_first_execution();
+ DBUG_RETURN(FALSE);
+ }
+
+ if (has_covering_null_row)
+ {
+ DBUG_ASSERT(count_partial_match_columns = field_count);
+ count_pm_keys= 0;
+ }
+ else if (has_covering_null_columns)
+ count_pm_keys= 1;
+ else
+ count_pm_keys= count_partial_match_columns - count_null_only_columns +
+ (nn_key_parts ? 1 : 0);
+
+ choose_partial_match_strategy(MY_TEST(nn_key_parts),
+ has_covering_null_row,
+ &partial_match_key_parts);
+ DBUG_ASSERT(strategy == PARTIAL_MATCH_MERGE ||
+ strategy == PARTIAL_MATCH_SCAN);
+ if (strategy == PARTIAL_MATCH_MERGE)
+ {
+ pm_engine=
+ new subselect_rowid_merge_engine(thd, (subselect_uniquesubquery_engine*)
+ lookup_engine, tmp_table,
+ count_pm_keys,
+ has_covering_null_row,
+ has_covering_null_columns,
+ count_columns_with_nulls,
+ item, result,
+ semi_join_conds->argument_list());
+ if (!pm_engine ||
+ ((subselect_rowid_merge_engine*) pm_engine)->
+ init(nn_key_parts, &partial_match_key_parts))
+ {
+ /*
+ The call to init() would fail if there was not enough memory to allocate
+ all buffers for the rowid merge strategy. In this case revert to table
+ scanning which doesn't need any big buffers.
+ */
+ delete pm_engine;
+ pm_engine= NULL;
+ strategy= PARTIAL_MATCH_SCAN;
+ }
+ }
+
+ if (strategy == PARTIAL_MATCH_SCAN)
+ {
+ if (!(pm_engine=
+ new subselect_table_scan_engine(thd, (subselect_uniquesubquery_engine*)
+ lookup_engine, tmp_table,
+ item, result,
+ semi_join_conds->argument_list(),
+ has_covering_null_row,
+ has_covering_null_columns,
+ count_columns_with_nulls)))
+ {
+ /* This is an irrecoverable error. */
+ res= 1;
+ goto err;
+ }
+ }
+ }
+
+ if (pm_engine)
+ lookup_engine= pm_engine;
+ item_in->change_engine(lookup_engine);
+
+err:
+ thd->lex->current_select= save_select;
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Print the state of this engine into a string for debugging and views.
+*/
+
+void subselect_hash_sj_engine::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN(" <materialize> ("));
+ materialize_engine->print(str, query_type);
+ str->append(STRING_WITH_LEN(" ), "));
+
+ if (lookup_engine)
+ lookup_engine->print(str, query_type);
+ else
+ str->append(STRING_WITH_LEN(
+ "<engine selected at execution time>"
+ ));
+}
+
+void subselect_hash_sj_engine::fix_length_and_dec(Item_cache** row)
+{
+ DBUG_ASSERT(FALSE);
+}
+
+void subselect_hash_sj_engine::exclude()
+{
+ DBUG_ASSERT(FALSE);
+}
+
+bool subselect_hash_sj_engine::no_tables()
+{
+ DBUG_ASSERT(FALSE);
+ return FALSE;
+}
+
+bool subselect_hash_sj_engine::change_result(Item_subselect *si,
+ select_result_interceptor *res,
+ bool temp __attribute__((unused)))
+{
+ DBUG_ASSERT(FALSE);
+ return TRUE;
+}
+
+
+Ordered_key::Ordered_key(uint keyid_arg, TABLE *tbl_arg, Item *search_key_arg,
+ ha_rows null_count_arg, ha_rows min_null_row_arg,
+ ha_rows max_null_row_arg, uchar *row_num_to_rowid_arg)
+ : keyid(keyid_arg), tbl(tbl_arg), search_key(search_key_arg),
+ row_num_to_rowid(row_num_to_rowid_arg), null_count(null_count_arg)
+{
+ DBUG_ASSERT(tbl->file->stats.records > null_count);
+ key_buff_elements= tbl->file->stats.records - null_count;
+ cur_key_idx= HA_POS_ERROR;
+
+ DBUG_ASSERT((null_count && min_null_row_arg && max_null_row_arg) ||
+ (!null_count && !min_null_row_arg && !max_null_row_arg));
+ if (null_count)
+ {
+ /* The counters are 1-based, for key access we need 0-based indexes. */
+ min_null_row= min_null_row_arg - 1;
+ max_null_row= max_null_row_arg - 1;
+ }
+ else
+ min_null_row= max_null_row= 0;
+}
+
+
+Ordered_key::~Ordered_key()
+{
+ my_free(key_buff);
+ my_bitmap_free(&null_key);
+}
+
+
+/*
+ Cleanup that needs to be done for each PS (re)execution.
+*/
+
+void Ordered_key::cleanup()
+{
+ /*
+ Currently these keys are recreated for each PS re-execution, thus
+ there is nothing to cleanup, the whole object goes away after execution
+ is over. All handler related initialization/deinitialization is done by
+ the parent subselect_rowid_merge_engine object.
+ */
+}
+
+
+/*
+ Initialize a multi-column index.
+*/
+
+bool Ordered_key::init(MY_BITMAP *columns_to_index)
+{
+ THD *thd= tbl->in_use;
+ uint cur_key_col= 0;
+ Item_field *cur_tmp_field;
+ Item_func_lt *fn_less_than;
+
+ key_column_count= bitmap_bits_set(columns_to_index);
+ key_columns= (Item_field**) thd->alloc(key_column_count *
+ sizeof(Item_field*));
+ compare_pred= (Item_func_lt**) thd->alloc(key_column_count *
+ sizeof(Item_func_lt*));
+
+ if (!key_columns || !compare_pred)
+ return TRUE; /* Revert to table scan partial match. */
+
+ for (uint i= 0; i < columns_to_index->n_bits; i++)
+ {
+ if (!bitmap_is_set(columns_to_index, i))
+ continue;
+ cur_tmp_field= new Item_field(tbl->field[i]);
+ /* Create the predicate (tmp_column[i] < outer_ref[i]). */
+ fn_less_than= new Item_func_lt(cur_tmp_field,
+ search_key->element_index(i));
+ fn_less_than->fix_fields(thd, (Item**) &fn_less_than);
+ key_columns[cur_key_col]= cur_tmp_field;
+ compare_pred[cur_key_col]= fn_less_than;
+ ++cur_key_col;
+ }
+
+ if (alloc_keys_buffers())
+ {
+ /* TIMOUR revert to partial match via table scan. */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Initialize a single-column index.
+*/
+
+bool Ordered_key::init(int col_idx)
+{
+ THD *thd= tbl->in_use;
+
+ key_column_count= 1;
+
+ // TIMOUR: check for mem allocation err, revert to scan
+
+ key_columns= (Item_field**) thd->alloc(sizeof(Item_field*));
+ compare_pred= (Item_func_lt**) thd->alloc(sizeof(Item_func_lt*));
+
+ key_columns[0]= new Item_field(tbl->field[col_idx]);
+ /* Create the predicate (tmp_column[i] < outer_ref[i]). */
+ compare_pred[0]= new Item_func_lt(key_columns[0],
+ search_key->element_index(col_idx));
+ compare_pred[0]->fix_fields(thd, (Item**)&compare_pred[0]);
+
+ if (alloc_keys_buffers())
+ {
+ /* TIMOUR revert to partial match via table scan. */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Allocate the buffers for both the row number, and the NULL-bitmap indexes.
+*/
+
+bool Ordered_key::alloc_keys_buffers()
+{
+ DBUG_ASSERT(key_buff_elements > 0);
+
+ if (!(key_buff= (rownum_t*) my_malloc((size_t)(key_buff_elements *
+ sizeof(rownum_t)), MYF(MY_WME | MY_THREAD_SPECIFIC))))
+ return TRUE;
+
+ /*
+ TIMOUR: it is enough to create bitmaps with size
+ (max_null_row - min_null_row), and then use min_null_row as
+ lookup offset.
+ */
+ /* Notice that max_null_row is max array index, we need count, so +1. */
+ if (my_bitmap_init(&null_key, NULL, (uint)(max_null_row + 1), FALSE))
+ return TRUE;
+
+ cur_key_idx= HA_POS_ERROR;
+
+ return FALSE;
+}
+
+
+/*
+ Quick sort comparison function that compares two rows of the same table
+ indentfied with their row numbers.
+
+ @retval -1
+ @retval 0
+ @retval +1
+*/
+
+int
+Ordered_key::cmp_keys_by_row_data(ha_rows a, ha_rows b)
+{
+ uchar *rowid_a, *rowid_b;
+ int __attribute__((unused)) error;
+ int cmp_res;
+ /* The length in bytes of the rowids (positions) of tmp_table. */
+ uint rowid_length= tbl->file->ref_length;
+
+ if (a == b)
+ return 0;
+ /* Get the corresponding rowids. */
+ rowid_a= row_num_to_rowid + a * rowid_length;
+ rowid_b= row_num_to_rowid + b * rowid_length;
+ /* Fetch the rows for comparison. */
+ if ((error= tbl->file->ha_rnd_pos(tbl->record[0], rowid_a)))
+ {
+ /* purecov: begin inspected */
+ tbl->file->print_error(error, MYF(ME_FATALERROR)); // Sets fatal_error
+ return 0;
+ /* purecov: end */
+ }
+ if ((error= tbl->file->ha_rnd_pos(tbl->record[1], rowid_b)))
+ {
+ /* purecov: begin inspected */
+ tbl->file->print_error(error, MYF(ME_FATALERROR)); // Sets fatal_error
+ return 0;
+ /* purecov: end */
+ }
+ /*
+ Compare the two rows by the corresponding values of the indexed
+ columns.
+ */
+ for (uint i= 0; i < key_column_count; i++)
+ {
+ Field *cur_field= key_columns[i]->field;
+ if ((cmp_res= cur_field->cmp_offset(tbl->s->rec_buff_length)))
+ return (cmp_res > 0 ? 1 : -1);
+ }
+ return 0;
+}
+
+
+int
+Ordered_key::cmp_keys_by_row_data_and_rownum(Ordered_key *key,
+ rownum_t* a, rownum_t* b)
+{
+ /* The result of comparing the two keys according to their row data. */
+ int cmp_row_res= key->cmp_keys_by_row_data(*a, *b);
+ if (cmp_row_res)
+ return cmp_row_res;
+ return (*a < *b) ? -1 : (*a > *b) ? 1 : 0;
+}
+
+
+void Ordered_key::sort_keys()
+{
+ my_qsort2(key_buff, (size_t) key_buff_elements, sizeof(rownum_t),
+ (qsort2_cmp) &cmp_keys_by_row_data_and_rownum, (void*) this);
+ /* Invalidate the current row position. */
+ cur_key_idx= HA_POS_ERROR;
+}
+
+
+/*
+ The fraction of rows that do not contain NULL in the columns indexed by
+ this key.
+
+ @retval 1 if there are no NULLs
+ @retval 0 if only NULLs
+*/
+
+double Ordered_key::null_selectivity()
+{
+ /* We should not be processing empty tables. */
+ DBUG_ASSERT(tbl->file->stats.records);
+ return (1 - (double) null_count / (double) tbl->file->stats.records);
+}
+
+
+/*
+ Compare the value(s) of the current key in 'search_key' with the
+ data of the current table record.
+
+ @notes The comparison result follows from the way compare_pred
+ is created in Ordered_key::init. Currently compare_pred compares
+ a field in of the current row with the corresponding Item that
+ contains the search key.
+
+ @param row_num Number of the row (not index in the key_buff array)
+
+ @retval -1 if (current row < search_key)
+ @retval 0 if (current row == search_key)
+ @retval +1 if (current row > search_key)
+*/
+
+int Ordered_key::cmp_key_with_search_key(rownum_t row_num)
+{
+ /* The length in bytes of the rowids (positions) of tmp_table. */
+ uint rowid_length= tbl->file->ref_length;
+ uchar *cur_rowid= row_num_to_rowid + row_num * rowid_length;
+ int __attribute__((unused)) error;
+ int cmp_res;
+
+ if ((error= tbl->file->ha_rnd_pos(tbl->record[0], cur_rowid)))
+ {
+ /* purecov: begin inspected */
+ tbl->file->print_error(error, MYF(ME_FATALERROR)); // Sets fatal_error
+ return 0;
+ /* purecov: end */
+ }
+
+ for (uint i= 0; i < key_column_count; i++)
+ {
+ cmp_res= compare_pred[i]->get_comparator()->compare();
+ /* Unlike Arg_comparator::compare_row() here there should be no NULLs. */
+ DBUG_ASSERT(!compare_pred[i]->null_value);
+ if (cmp_res)
+ return (cmp_res > 0 ? 1 : -1);
+ }
+ return 0;
+}
+
+
+/*
+ Find a key in a sorted array of keys via binary search.
+
+ see create_subq_in_equalities()
+*/
+
+bool Ordered_key::lookup()
+{
+ DBUG_ASSERT(key_buff_elements);
+
+ ha_rows lo= 0;
+ ha_rows hi= key_buff_elements - 1;
+ ha_rows mid;
+ int cmp_res;
+
+ while (lo <= hi)
+ {
+ mid= lo + (hi - lo) / 2;
+ cmp_res= cmp_key_with_search_key(key_buff[mid]);
+ /*
+ In order to find the minimum match, check if the pevious element is
+ equal or smaller than the found one. If equal, we need to search further
+ to the left.
+ */
+ if (!cmp_res && mid > 0)
+ cmp_res= !cmp_key_with_search_key(key_buff[mid - 1]) ? 1 : 0;
+
+ if (cmp_res == -1)
+ {
+ /* row[mid] < search_key */
+ lo= mid + 1;
+ }
+ else if (cmp_res == 1)
+ {
+ /* row[mid] > search_key */
+ if (!mid)
+ goto not_found;
+ hi= mid - 1;
+ }
+ else
+ {
+ /* row[mid] == search_key */
+ cur_key_idx= mid;
+ return TRUE;
+ }
+ }
+not_found:
+ cur_key_idx= HA_POS_ERROR;
+ return FALSE;
+}
+
+
+/*
+ Move the current index pointer to the next key with the same column
+ values as the current key. Since the index is sorted, all such keys
+ are contiguous.
+*/
+
+bool Ordered_key::next_same()
+{
+ DBUG_ASSERT(key_buff_elements);
+
+ if (cur_key_idx < key_buff_elements - 1)
+ {
+ /*
+ TIMOUR:
+ The below is quite inefficient, since as a result we will fetch every
+ row (except the last one) twice. There must be a more efficient way,
+ e.g. swapping record[0] and record[1], and reading only the new record.
+ */
+ if (!cmp_keys_by_row_data(key_buff[cur_key_idx], key_buff[cur_key_idx + 1]))
+ {
+ ++cur_key_idx;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+void Ordered_key::print(String *str)
+{
+ uint i;
+ str->append("{idx=");
+ str->qs_append(keyid);
+ str->append(", (");
+ for (i= 0; i < key_column_count - 1; i++)
+ {
+ str->append(key_columns[i]->field->field_name);
+ str->append(", ");
+ }
+ str->append(key_columns[i]->field->field_name);
+ str->append("), ");
+
+ str->append("null_bitmap: (bits=");
+ str->qs_append(null_key.n_bits);
+ str->append(", nulls= ");
+ str->qs_append((double)null_count);
+ str->append(", min_null= ");
+ str->qs_append((double)min_null_row);
+ str->append(", max_null= ");
+ str->qs_append((double)max_null_row);
+ str->append("), ");
+
+ str->append('}');
+}
+
+
+subselect_partial_match_engine::subselect_partial_match_engine(
+ THD *thd_arg, subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ bool has_covering_null_row_arg,
+ bool has_covering_null_columns_arg,
+ uint count_columns_with_nulls_arg)
+ :subselect_engine(thd_arg, item_arg, result_arg),
+ tmp_table(tmp_table_arg), lookup_engine(engine_arg),
+ equi_join_conds(equi_join_conds_arg),
+ has_covering_null_row(has_covering_null_row_arg),
+ has_covering_null_columns(has_covering_null_columns_arg),
+ count_columns_with_nulls(count_columns_with_nulls_arg)
+{}
+
+
+int subselect_partial_match_engine::exec()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+ int lookup_res;
+
+ DBUG_ASSERT(!(item_in->left_expr_has_null() &&
+ item_in->is_top_level_item()));
+
+ if (!item_in->left_expr_has_null())
+ {
+ /* Try to find a matching row by index lookup. */
+ if (lookup_engine->copy_ref_key(false))
+ {
+ /* The result is FALSE based on the outer reference. */
+ item_in->value= 0;
+ item_in->null_value= 0;
+ return 0;
+ }
+ else
+ {
+ /* Search for a complete match. */
+ if ((lookup_res= lookup_engine->index_lookup()))
+ {
+ /* An error occured during lookup(). */
+ item_in->value= 0;
+ item_in->null_value= 0;
+ return lookup_res;
+ }
+ else if (item_in->value || !count_columns_with_nulls)
+ {
+ /*
+ A complete match was found, the result of IN is TRUE.
+ If no match was found, and there are no NULLs in the materialized
+ subquery, then the result is guaranteed to be false because this
+ branch is executed when the outer reference has no NULLs as well.
+ Notice: (this->item == lookup_engine->item)
+ */
+ return 0;
+ }
+ }
+ }
+
+ if (has_covering_null_row)
+ {
+ /*
+ If there is a NULL-only row that coveres all columns the result of IN
+ is UNKNOWN.
+ */
+ item_in->value= 0;
+ /*
+ TIMOUR: which one is the right way to propagate an UNKNOWN result?
+ Should we also set empty_result_set= FALSE; ???
+ */
+ //item_in->was_null= 1;
+ item_in->null_value= 1;
+ return 0;
+ }
+
+ /*
+ There is no complete match. Look for a partial match (UNKNOWN result), or
+ no match (FALSE).
+ */
+ if (tmp_table->file->inited)
+ tmp_table->file->ha_index_end();
+
+ if (partial_match())
+ {
+ /* The result of IN is UNKNOWN. */
+ item_in->value= 0;
+ /*
+ TIMOUR: which one is the right way to propagate an UNKNOWN result?
+ Should we also set empty_result_set= FALSE; ???
+ */
+ //item_in->was_null= 1;
+ item_in->null_value= 1;
+ }
+ else
+ {
+ /* The result of IN is FALSE. */
+ item_in->value= 0;
+ /*
+ TIMOUR: which one is the right way to propagate an UNKNOWN result?
+ Should we also set empty_result_set= FALSE; ???
+ */
+ //item_in->was_null= 0;
+ item_in->null_value= 0;
+ }
+
+ return 0;
+}
+
+
+void subselect_partial_match_engine::print(String *str,
+ enum_query_type query_type)
+{
+ /*
+ Should never be called as the actual engine cannot be known at query
+ optimization time.
+ DBUG_ASSERT(FALSE);
+ */
+}
+
+
+/*
+ @param non_null_key_parts
+ @param partial_match_key_parts A union of all single-column NULL key parts.
+
+ @retval FALSE the engine was initialized successfully
+ @retval TRUE there was some (memory allocation) error during initialization,
+ such errors should be interpreted as revert to other strategy
+*/
+
+bool
+subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts,
+ MY_BITMAP *partial_match_key_parts)
+{
+ /* The length in bytes of the rowids (positions) of tmp_table. */
+ uint rowid_length= tmp_table->file->ref_length;
+ ha_rows row_count= tmp_table->file->stats.records;
+ rownum_t cur_rownum= 0;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+ uint cur_keyid= 0;
+ Item_in_subselect *item_in= (Item_in_subselect*) item;
+ int error;
+
+ if (merge_keys_count == 0)
+ {
+ DBUG_ASSERT(bitmap_bits_set(partial_match_key_parts) == 0 ||
+ has_covering_null_row);
+ /* There is nothing to initialize, we will only do regular lookups. */
+ return FALSE;
+ }
+
+ /*
+ If all nullable columns contain only NULLs, there must be one index
+ over all non-null columns.
+ */
+ DBUG_ASSERT(!has_covering_null_columns ||
+ (has_covering_null_columns &&
+ merge_keys_count == 1 && non_null_key_parts));
+ /*
+ Allocate buffers to hold the merged keys and the mapping between rowids and
+ row numbers. All small buffers are allocated in the runtime memroot. Big
+ buffers are allocated from the OS via malloc.
+ */
+ if (!(merge_keys= (Ordered_key**) thd->alloc(merge_keys_count *
+ sizeof(Ordered_key*))) ||
+ !(null_bitmaps= (MY_BITMAP**) thd->alloc(merge_keys_count *
+ sizeof(MY_BITMAP*))) ||
+ !(row_num_to_rowid= (uchar*) my_malloc((size_t)(row_count * rowid_length),
+ MYF(MY_WME | MY_THREAD_SPECIFIC))))
+ return TRUE;
+
+ /* Create the only non-NULL key if there is any. */
+ if (non_null_key_parts)
+ {
+ non_null_key= new Ordered_key(cur_keyid, tmp_table, item_in->left_expr,
+ 0, 0, 0, row_num_to_rowid);
+ if (non_null_key->init(non_null_key_parts))
+ return TRUE;
+ merge_keys[cur_keyid]= non_null_key;
+ merge_keys[cur_keyid]->first();
+ ++cur_keyid;
+ }
+
+ /*
+ If all nullable columns contain NULLs, the only key that is needed is the
+ only non-NULL key that is already created above.
+ */
+ if (!has_covering_null_columns)
+ {
+ if (my_bitmap_init_memroot(&matching_keys, merge_keys_count, thd->mem_root) ||
+ my_bitmap_init_memroot(&matching_outer_cols, merge_keys_count, thd->mem_root))
+ return TRUE;
+
+ /*
+ Create one single-column NULL-key for each column in
+ partial_match_key_parts.
+ */
+ for (uint i= 0; i < partial_match_key_parts->n_bits; i++)
+ {
+ /* Skip columns that have no NULLs, or contain only NULLs. */
+ if (!bitmap_is_set(partial_match_key_parts, i) ||
+ result_sink->get_null_count_of_col(i) == row_count)
+ continue;
+
+ merge_keys[cur_keyid]= new Ordered_key(
+ cur_keyid, tmp_table,
+ item_in->left_expr->element_index(i),
+ result_sink->get_null_count_of_col(i),
+ result_sink->get_min_null_of_col(i),
+ result_sink->get_max_null_of_col(i),
+ row_num_to_rowid);
+ if (merge_keys[cur_keyid]->init(i))
+ return TRUE;
+ merge_keys[cur_keyid]->first();
+ ++cur_keyid;
+ }
+ }
+ DBUG_ASSERT(cur_keyid == merge_keys_count);
+
+ /* Populate the indexes with data from the temporary table. */
+ if (tmp_table->file->ha_rnd_init_with_error(1))
+ return TRUE;
+ tmp_table->file->extra_opt(HA_EXTRA_CACHE,
+ current_thd->variables.read_buff_size);
+ tmp_table->null_row= 0;
+ while (TRUE)
+ {
+ error= tmp_table->file->ha_rnd_next(tmp_table->record[0]);
+ if (error == HA_ERR_RECORD_DELETED)
+ {
+ /* We get this for duplicate records that should not be in tmp_table. */
+ continue;
+ }
+ /*
+ This is a temp table that we fully own, there should be no other
+ cause to stop the iteration than EOF.
+ */
+ DBUG_ASSERT(!error || error == HA_ERR_END_OF_FILE);
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ DBUG_ASSERT(cur_rownum == tmp_table->file->stats.records);
+ break;
+ }
+
+ /*
+ Save the position of this record in the row_num -> rowid mapping.
+ */
+ tmp_table->file->position(tmp_table->record[0]);
+ memcpy(row_num_to_rowid + cur_rownum * rowid_length,
+ tmp_table->file->ref, rowid_length);
+
+ /* Add the current row number to the corresponding keys. */
+ if (non_null_key)
+ {
+ /* By definition there are no NULLs in the non-NULL key. */
+ non_null_key->add_key(cur_rownum);
+ }
+
+ for (uint i= (non_null_key ? 1 : 0); i < merge_keys_count; i++)
+ {
+ /*
+ Check if the first and only indexed column contains NULL in the curent
+ row, and add the row number to the corresponding key.
+ */
+ if (tmp_table->field[merge_keys[i]->get_field_idx(0)]->is_null())
+ merge_keys[i]->set_null(cur_rownum);
+ else
+ merge_keys[i]->add_key(cur_rownum);
+ }
+ ++cur_rownum;
+ }
+
+ tmp_table->file->ha_rnd_end();
+
+ /* Sort all the keys by their NULL selectivity. */
+ my_qsort(merge_keys, merge_keys_count, sizeof(Ordered_key*),
+ (qsort_cmp) cmp_keys_by_null_selectivity);
+
+ /* Sort the keys in each of the indexes. */
+ for (uint i= 0; i < merge_keys_count; i++)
+ merge_keys[i]->sort_keys();
+
+ if (init_queue(&pq, merge_keys_count, 0, FALSE,
+ subselect_rowid_merge_engine::cmp_keys_by_cur_rownum, NULL,
+ 0, 0))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+subselect_rowid_merge_engine::~subselect_rowid_merge_engine()
+{
+ /* None of the resources below is allocated if there are no ordered keys. */
+ if (merge_keys_count)
+ {
+ my_free(row_num_to_rowid);
+ for (uint i= 0; i < merge_keys_count; i++)
+ delete merge_keys[i];
+ delete_queue(&pq);
+ if (tmp_table->file->inited == handler::RND)
+ tmp_table->file->ha_rnd_end();
+ }
+}
+
+
+void subselect_rowid_merge_engine::cleanup()
+{
+}
+
+
+/*
+ Quick sort comparison function to compare keys in order of decreasing bitmap
+ selectivity, so that the most selective keys come first.
+
+ @param k1 first key to compare
+ @param k2 second key to compare
+
+ @retval 1 if k1 is less selective than k2
+ @retval 0 if k1 is equally selective as k2
+ @retval -1 if k1 is more selective than k2
+*/
+
+int
+subselect_rowid_merge_engine::cmp_keys_by_null_selectivity(Ordered_key **k1,
+ Ordered_key **k2)
+{
+ double k1_sel= (*k1)->null_selectivity();
+ double k2_sel= (*k2)->null_selectivity();
+ if (k1_sel < k2_sel)
+ return 1;
+ if (k1_sel > k2_sel)
+ return -1;
+ return 0;
+}
+
+
+/*
+*/
+
+int
+subselect_rowid_merge_engine::cmp_keys_by_cur_rownum(void *arg,
+ uchar *k1, uchar *k2)
+{
+ rownum_t r1= ((Ordered_key*) k1)->current();
+ rownum_t r2= ((Ordered_key*) k2)->current();
+
+ return (r1 < r2) ? -1 : (r1 > r2) ? 1 : 0;
+}
+
+
+/*
+ Check if certain table row contains a NULL in all columns for which there is
+ no match in the corresponding value index.
+
+ @note
+ There is no need to check the columns that contain only NULLs, because
+ those are guaranteed to match.
+
+ @retval TRUE if a NULL row exists
+ @retval FALSE otherwise
+*/
+
+bool subselect_rowid_merge_engine::test_null_row(rownum_t row_num)
+{
+ Ordered_key *cur_key;
+ for (uint i = 0; i < merge_keys_count; i++)
+ {
+ cur_key= merge_keys[i];
+ if (bitmap_is_set(&matching_keys, cur_key->get_keyid()))
+ {
+ /*
+ The key 'i' (with id 'cur_keyid') already matches a value in row
+ 'row_num', thus we skip it as it can't possibly match a NULL.
+ */
+ continue;
+ }
+ if (!cur_key->is_null(row_num))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/**
+ Test if a subset of NULL-able columns contains a row of NULLs.
+ @retval TRUE if such a row exists
+ @retval FALSE no complementing null row
+*/
+
+bool subselect_rowid_merge_engine::
+exists_complementing_null_row(MY_BITMAP *keys_to_complement)
+{
+ rownum_t highest_min_row= 0;
+ rownum_t lowest_max_row= UINT_MAX;
+ uint count_null_keys, i;
+ Ordered_key *cur_key;
+
+ if (!count_columns_with_nulls)
+ {
+ /*
+ If there are both NULLs and non-NUll values in the outer reference, and
+ the subquery contains no NULLs, a complementing NULL row cannot exist.
+ */
+ return FALSE;
+ }
+
+ for (i= (non_null_key ? 1 : 0), count_null_keys= 0; i < merge_keys_count; i++)
+ {
+ cur_key= merge_keys[i];
+ if (bitmap_is_set(keys_to_complement, cur_key->get_keyid()))
+ continue;
+ if (!cur_key->get_null_count())
+ {
+ /* If there is column without NULLs, there cannot be a partial match. */
+ return FALSE;
+ }
+ if (cur_key->get_min_null_row() > highest_min_row)
+ highest_min_row= cur_key->get_min_null_row();
+ if (cur_key->get_max_null_row() < lowest_max_row)
+ lowest_max_row= cur_key->get_max_null_row();
+ null_bitmaps[count_null_keys++]= cur_key->get_null_key();
+ }
+
+ if (lowest_max_row < highest_min_row)
+ {
+ /* The intersection of NULL rows is empty. */
+ return FALSE;
+ }
+
+ return bitmap_exists_intersection((const MY_BITMAP**) null_bitmaps,
+ count_null_keys,
+ (uint)highest_min_row, (uint)lowest_max_row);
+}
+
+
+/*
+ @retval TRUE there is a partial match (UNKNOWN)
+ @retval FALSE there is no match at all (FALSE)
+*/
+
+bool subselect_rowid_merge_engine::partial_match()
+{
+ Ordered_key *min_key; /* Key that contains the current minimum position. */
+ rownum_t min_row_num; /* Current row number of min_key. */
+ Ordered_key *cur_key;
+ rownum_t cur_row_num;
+ uint count_nulls_in_search_key= 0;
+ uint max_null_in_any_row=
+ ((select_materialize_with_stats *) result)->get_max_nulls_in_row();
+ bool res= FALSE;
+
+ /* If there is a non-NULL key, it must be the first key in the keys array. */
+ DBUG_ASSERT(!non_null_key || (non_null_key && merge_keys[0] == non_null_key));
+ /* The prioryty queue for keys must be empty. */
+ DBUG_ASSERT(!pq.elements);
+
+ /* All data accesses during execution are via handler::ha_rnd_pos() */
+ if (tmp_table->file->ha_rnd_init_with_error(0))
+ {
+ res= FALSE;
+ goto end;
+ }
+
+ /* Check if there is a match for the columns of the only non-NULL key. */
+ if (non_null_key && !non_null_key->lookup())
+ {
+ res= FALSE;
+ goto end;
+ }
+
+ /*
+ If all nullable columns contain only NULLs, then there is a guranteed
+ partial match, and we don't need to search for a matching row.
+ */
+ if (has_covering_null_columns)
+ {
+ res= TRUE;
+ goto end;
+ }
+
+ if (non_null_key)
+ queue_insert(&pq, (uchar *) non_null_key);
+ /*
+ Do not add the non_null_key, since it was already processed above.
+ */
+ bitmap_clear_all(&matching_outer_cols);
+ for (uint i= MY_TEST(non_null_key); i < merge_keys_count; i++)
+ {
+ DBUG_ASSERT(merge_keys[i]->get_column_count() == 1);
+ if (merge_keys[i]->get_search_key(0)->null_value)
+ {
+ ++count_nulls_in_search_key;
+ bitmap_set_bit(&matching_outer_cols, merge_keys[i]->get_keyid());
+ }
+ else if (merge_keys[i]->lookup())
+ queue_insert(&pq, (uchar *) merge_keys[i]);
+ }
+
+ /*
+ If the outer reference consists of only NULLs, or if it has NULLs in all
+ nullable columns (above we guarantee there is a match for the non-null
+ coumns), the result is UNKNOWN.
+ */
+ if (count_nulls_in_search_key == merge_keys_count - MY_TEST(non_null_key))
+ {
+ res= TRUE;
+ goto end;
+ }
+
+ /*
+ If the outer row has NULLs in some columns, and
+ there is no match for any of the remaining columns, and
+ there is a subquery row with NULLs in all unmatched columns,
+ then there is a partial match, otherwise the result is FALSE.
+ */
+ if (count_nulls_in_search_key && !pq.elements)
+ {
+ DBUG_ASSERT(!non_null_key);
+ /*
+ Check if the intersection of all NULL bitmaps of all keys that
+ are not in matching_outer_cols is non-empty.
+ */
+ res= exists_complementing_null_row(&matching_outer_cols);
+ goto end;
+ }
+
+ /*
+ If there is no NULL (sub)row that covers all NULL columns, and there is no
+ match for any of the NULL columns, the result is FALSE. Notice that if there
+ is a non-null key, and there is only one matching key, the non-null key is
+ the matching key. This is so, because this method returns FALSE if the
+ non-null key doesn't have a match.
+ */
+ if (!count_nulls_in_search_key &&
+ (!pq.elements ||
+ (pq.elements == 1 && non_null_key &&
+ max_null_in_any_row < merge_keys_count-1)))
+ {
+ if (!pq.elements)
+ {
+ DBUG_ASSERT(!non_null_key);
+ /*
+ The case of a covering null row is handled by
+ subselect_partial_match_engine::exec()
+ */
+ DBUG_ASSERT(max_null_in_any_row != tmp_table->s->fields);
+ }
+ res= FALSE;
+ goto end;
+ }
+
+ DBUG_ASSERT(pq.elements);
+
+ min_key= (Ordered_key*) queue_remove_top(&pq);
+ min_row_num= min_key->current();
+ bitmap_set_bit(&matching_keys, min_key->get_keyid());
+ bitmap_union(&matching_keys, &matching_outer_cols);
+ if (min_key->next_same())
+ queue_insert(&pq, (uchar *) min_key);
+
+ if (pq.elements == 0)
+ {
+ /*
+ Check the only matching row of the only key min_key for NULL matches
+ in the other columns.
+ */
+ res= test_null_row(min_row_num);
+ goto end;
+ }
+
+ while (TRUE)
+ {
+ cur_key= (Ordered_key*) queue_remove_top(&pq);
+ cur_row_num= cur_key->current();
+
+ if (cur_row_num == min_row_num)
+ bitmap_set_bit(&matching_keys, cur_key->get_keyid());
+ else
+ {
+ /* Follows from the correct use of priority queue. */
+ DBUG_ASSERT(cur_row_num > min_row_num);
+ if (test_null_row(min_row_num))
+ {
+ res= TRUE;
+ goto end;
+ }
+ else
+ {
+ min_key= cur_key;
+ min_row_num= cur_row_num;
+ bitmap_clear_all(&matching_keys);
+ bitmap_set_bit(&matching_keys, min_key->get_keyid());
+ bitmap_union(&matching_keys, &matching_outer_cols);
+ }
+ }
+
+ if (cur_key->next_same())
+ queue_insert(&pq, (uchar *) cur_key);
+
+ if (pq.elements == 0)
+ {
+ /* Check the last row of the last column in PQ for NULL matches. */
+ res= test_null_row(min_row_num);
+ goto end;
+ }
+ }
+
+ /* We should never get here - all branches must be handled explicitly above. */
+ DBUG_ASSERT(FALSE);
+
+end:
+ if (!has_covering_null_columns)
+ bitmap_clear_all(&matching_keys);
+ queue_remove_all(&pq);
+ tmp_table->file->ha_rnd_end();
+ return res;
+}
+
+
+subselect_table_scan_engine::subselect_table_scan_engine(
+ THD *thd_arg, subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg,
+ Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ bool has_covering_null_row_arg,
+ bool has_covering_null_columns_arg,
+ uint count_columns_with_nulls_arg)
+ :subselect_partial_match_engine(thd_arg, engine_arg, tmp_table_arg, item_arg,
+ result_arg, equi_join_conds_arg,
+ has_covering_null_row_arg,
+ has_covering_null_columns_arg,
+ count_columns_with_nulls_arg)
+{}
+
+
+/*
+ TIMOUR:
+ This method is based on subselect_uniquesubquery_engine::scan_table().
+ Consider refactoring somehow, 80% of the code is the same.
+
+ for each row_i in tmp_table
+ {
+ count_matches= 0;
+ for each row element row_i[j]
+ {
+ if (outer_ref[j] is NULL || row_i[j] is NULL || outer_ref[j] == row_i[j])
+ ++count_matches;
+ }
+ if (count_matches == outer_ref.elements)
+ return TRUE
+ }
+ return FALSE
+*/
+
+bool subselect_table_scan_engine::partial_match()
+{
+ List_iterator_fast<Item> equality_it(*equi_join_conds);
+ Item *cur_eq;
+ uint count_matches;
+ int error;
+ bool res;
+
+ if (tmp_table->file->ha_rnd_init_with_error(1))
+ {
+ res= FALSE;
+ goto end;
+ }
+
+ tmp_table->file->extra_opt(HA_EXTRA_CACHE,
+ current_thd->variables.read_buff_size);
+ for (;;)
+ {
+ error= tmp_table->file->ha_rnd_next(tmp_table->record[0]);
+ if (error) {
+ if (error == HA_ERR_RECORD_DELETED)
+ {
+ error= 0;
+ continue;
+ }
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ error= 0;
+ break;
+ }
+ else
+ {
+ error= report_error(tmp_table, error);
+ break;
+ }
+ }
+
+ equality_it.rewind();
+ count_matches= 0;
+ while ((cur_eq= equality_it++))
+ {
+ DBUG_ASSERT(cur_eq->type() == Item::FUNC_ITEM &&
+ ((Item_func*)cur_eq)->functype() == Item_func::EQ_FUNC);
+ if (!cur_eq->val_int() && !cur_eq->null_value)
+ break;
+ ++count_matches;
+ }
+ if (count_matches == tmp_table->s->fields)
+ {
+ res= TRUE; /* Found a matching row. */
+ goto end;
+ }
+ }
+
+ res= FALSE;
+end:
+ tmp_table->file->ha_rnd_end();
+ return res;
+}
+
+
+void subselect_table_scan_engine::cleanup()
+{
+}
+
diff --git a/sql/item_subselect.h b/sql/item_subselect.h
new file mode 100644
index 00000000000..92b269d02f1
--- /dev/null
+++ b/sql/item_subselect.h
@@ -0,0 +1,1467 @@
+#ifndef ITEM_SUBSELECT_INCLUDED
+#define ITEM_SUBSELECT_INCLUDED
+
+/* Copyright (c) 2002, 2011, Oracle and/or its affiliates.
+
+ 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 */
+
+/* subselect Item */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include <queues.h>
+
+class st_select_lex;
+class st_select_lex_unit;
+class JOIN;
+class select_result_interceptor;
+class subselect_engine;
+class subselect_hash_sj_engine;
+class Item_bool_func2;
+class Comp_creator;
+
+typedef class st_select_lex SELECT_LEX;
+
+/**
+ Convenience typedef used in this file, and further used by any files
+ including this file.
+*/
+typedef Comp_creator* (*chooser_compare_func_creator)(bool invert);
+class Cached_item;
+
+/* base class for subselects */
+
+class Item_subselect :public Item_result_field
+{
+ bool value_assigned; /* value already assigned to subselect */
+ bool own_engine; /* the engine was not taken from other Item_subselect */
+protected:
+ /* thread handler, will be assigned in fix_fields only */
+ THD *thd;
+ /* old engine if engine was changed */
+ subselect_engine *old_engine;
+ /* cache of used external tables */
+ table_map used_tables_cache;
+ /* allowed number of columns (1 for single value subqueries) */
+ uint max_columns;
+ /* where subquery is placed */
+ enum_parsing_place parsing_place;
+ /* work with 'substitution' */
+ bool have_to_be_excluded;
+ /* cache of constant state */
+ bool const_item_cache;
+
+ bool inside_first_fix_fields;
+ bool done_first_fix_fields;
+ Item *expr_cache;
+ /*
+ Set to TRUE if at optimization or execution time we determine that this
+ item's value is a constant. We need this member because it is not possible
+ to substitute 'this' with a constant item.
+ */
+ bool forced_const;
+#ifndef DBUG_OFF
+ /* Count the number of times this subquery predicate has been executed. */
+ uint exec_counter;
+#endif
+public:
+ /*
+ Used inside Item_subselect::fix_fields() according to this scenario:
+ > Item_subselect::fix_fields
+ > engine->prepare
+ > child_join->prepare
+ (Here we realize we need to do the rewrite and set
+ substitution= some new Item, eg. Item_in_optimizer )
+ < child_join->prepare
+ < engine->prepare
+ *ref= substitution;
+ substitution= NULL;
+ < Item_subselect::fix_fields
+ */
+ /* TODO make this protected member again. */
+ Item *substitution;
+ /* engine that perform execution of subselect (single select or union) */
+ /* TODO make this protected member again. */
+ subselect_engine *engine;
+ /* unit of subquery */
+ st_select_lex_unit *unit;
+ /* A reference from inside subquery predicate to somewhere outside of it */
+ class Ref_to_outside : public Sql_alloc
+ {
+ public:
+ st_select_lex *select; /* Select where the reference is pointing to */
+ /*
+ What is being referred. This may be NULL when we're referring to an
+ aggregate function.
+ */
+ Item *item;
+ };
+ /*
+ References from within this subquery to somewhere outside of it (i.e. to
+ parent select, grandparent select, etc)
+ */
+ List<Ref_to_outside> upper_refs;
+ st_select_lex *parent_select;
+
+ /*
+ TRUE<=>Table Elimination has made it redundant to evaluate this select
+ (and so it is not part of QEP, etc)
+ */
+ bool eliminated;
+
+ /* subquery is transformed */
+ bool changed;
+
+ /* TRUE <=> The underlying SELECT is correlated w.r.t some ancestor select */
+ bool is_correlated;
+
+ enum subs_type {UNKNOWN_SUBS, SINGLEROW_SUBS,
+ EXISTS_SUBS, IN_SUBS, ALL_SUBS, ANY_SUBS};
+
+ Item_subselect();
+
+ virtual subs_type substype() { return UNKNOWN_SUBS; }
+ bool is_in_predicate()
+ {
+ return (substype() == Item_subselect::IN_SUBS ||
+ substype() == Item_subselect::ALL_SUBS ||
+ substype() == Item_subselect::ANY_SUBS);
+ }
+
+ /*
+ We need this method, because some compilers do not allow 'this'
+ pointer in constructor initialization list, but we need to pass a pointer
+ to subselect Item class to select_result_interceptor's constructor.
+ */
+ virtual void init (st_select_lex *select_lex,
+ select_result_interceptor *result);
+
+ ~Item_subselect();
+ void cleanup();
+ virtual void reset()
+ {
+ eliminated= FALSE;
+ null_value= 1;
+ }
+ /**
+ Set the subquery result to a default value consistent with the semantics of
+ the result row produced for queries with implicit grouping.
+ */
+ void no_rows_in_result()= 0;
+ virtual bool select_transformer(JOIN *join);
+ bool assigned() { return value_assigned; }
+ void assigned(bool a) { value_assigned= a; }
+ enum Type type() const;
+ bool is_null()
+ {
+ update_null_value();
+ return null_value;
+ }
+ bool fix_fields(THD *thd, Item **ref);
+ bool mark_as_dependent(THD *thd, st_select_lex *select, Item *item);
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ void recalc_used_tables(st_select_lex *new_parent, bool after_pullout);
+ virtual bool exec();
+ /*
+ If subquery optimization or execution determines that the subquery has
+ an empty result, mark the subquery predicate as a constant value.
+ */
+ void make_const()
+ {
+ used_tables_cache= 0;
+ const_item_cache= 0;
+ forced_const= TRUE;
+ }
+ virtual void fix_length_and_dec();
+ table_map used_tables() const;
+ table_map not_null_tables() const { return 0; }
+ bool const_item() const;
+ inline table_map get_used_tables_cache() { return used_tables_cache; }
+ Item *get_tmp_table_item(THD *thd);
+ void update_used_tables();
+ virtual void print(String *str, enum_query_type query_type);
+ virtual bool have_guarded_conds() { return FALSE; }
+ bool change_engine(subselect_engine *eng)
+ {
+ old_engine= engine;
+ engine= eng;
+ return eng == 0;
+ }
+ bool engine_changed(subselect_engine *eng) { return engine != eng; }
+ /*
+ True if this subquery has been already evaluated. Implemented only for
+ single select and union subqueries only.
+ */
+ bool is_evaluated() const;
+ bool is_uncacheable() const;
+ bool is_expensive();
+
+ /*
+ Used by max/min subquery to initialize value presence registration
+ mechanism. Engine call this method before rexecution query.
+ */
+ virtual void reset_value_registration() {}
+ enum_parsing_place place() { return parsing_place; }
+ bool walk(Item_processor processor, bool walk_subquery, uchar *arg);
+ bool mark_as_eliminated_processor(uchar *arg);
+ bool eliminate_subselect_processor(uchar *arg);
+ bool set_fake_select_as_master_processor(uchar *arg);
+ bool enumerate_field_refs_processor(uchar *arg);
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("subselect");
+ }
+ /**
+ Callback to test if an IN predicate is expensive.
+
+ @notes
+ The return value affects the behavior of make_cond_for_table().
+
+ @retval TRUE if the predicate is expensive
+ @retval FALSE otherwise
+ */
+ bool is_expensive_processor(uchar *arg) { return is_expensive(); }
+
+ /**
+ Get the SELECT_LEX structure associated with this Item.
+ @return the SELECT_LEX structure associated with this Item
+ */
+ st_select_lex* get_select_lex();
+ const char *func_name() const { DBUG_ASSERT(0); return "subselect"; }
+ virtual bool expr_cache_is_needed(THD *);
+ virtual void get_cache_parameters(List<Item> &parameters);
+ virtual bool is_subquery_processor (uchar *opt_arg) { return 1; }
+ bool exists2in_processor(uchar *opt_arg) { return 0; }
+ bool limit_index_condition_pushdown_processor(uchar *opt_arg)
+ {
+ return TRUE;
+ }
+
+ friend class select_result_interceptor;
+ friend class Item_in_optimizer;
+ friend bool Item_field::fix_fields(THD *, Item **);
+ friend int Item_field::fix_outer_field(THD *, Field **, Item **);
+ friend bool Item_ref::fix_fields(THD *, Item **);
+ friend void mark_select_range_as_dependent(THD*,
+ st_select_lex*, st_select_lex*,
+ Field*, Item*, Item_ident*);
+ friend bool convert_join_subqueries_to_semijoins(JOIN *join);
+};
+
+/* single value subselect */
+
+class Item_cache;
+class Item_singlerow_subselect :public Item_subselect
+{
+protected:
+ Item_cache *value, **row;
+public:
+ Item_singlerow_subselect(st_select_lex *select_lex);
+ Item_singlerow_subselect() :Item_subselect(), value(0), row (0)
+ {}
+
+ void cleanup();
+ subs_type substype() { return SINGLEROW_SUBS; }
+
+ void reset();
+ void no_rows_in_result();
+ bool select_transformer(JOIN *join);
+ void store(uint i, Item* item);
+ double val_real();
+ longlong val_int ();
+ String *val_str (String *);
+ my_decimal *val_decimal(my_decimal *);
+ bool val_bool();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
+ enum Item_result result_type() const;
+ enum Item_result cmp_type() const;
+ enum_field_types field_type() const;
+ void fix_length_and_dec();
+
+ uint cols();
+ Item* element_index(uint i) { return reinterpret_cast<Item*>(row[i]); }
+ Item** addr(uint i) { return (Item**)row + i; }
+ bool check_cols(uint c);
+ bool null_inside();
+ void bring_value();
+
+ /**
+ This method is used to implement a special case of semantic tree
+ rewriting, mandated by a SQL:2003 exception in the specification.
+ The only caller of this method is handle_sql2003_note184_exception(),
+ see the code there for more details.
+ Note that this method breaks the object internal integrity, by
+ removing it's association with the corresponding SELECT_LEX,
+ making this object orphan from the parse tree.
+ No other method, beside the destructor, should be called on this
+ object, as it is now invalid.
+ @return the SELECT_LEX structure that was given in the constructor.
+ */
+ st_select_lex* invalidate_and_restore_select_lex();
+
+ Item* expr_cache_insert_transformer(uchar *thd_arg);
+
+ friend class select_singlerow_subselect;
+};
+
+/* used in static ALL/ANY optimization */
+class select_max_min_finder_subselect;
+class Item_maxmin_subselect :public Item_singlerow_subselect
+{
+protected:
+ bool max;
+ bool was_values; // Set if we have found at least one row
+public:
+ Item_maxmin_subselect(THD *thd, Item_subselect *parent,
+ st_select_lex *select_lex, bool max);
+ virtual void print(String *str, enum_query_type query_type);
+ void cleanup();
+ bool any_value() { return was_values; }
+ void register_value() { was_values= TRUE; }
+ void reset_value_registration() { was_values= FALSE; }
+ void no_rows_in_result();
+};
+
+/* exists subselect */
+
+class Item_exists_subselect :public Item_subselect
+{
+protected:
+ Item_func_not *upper_not;
+ bool value; /* value of this item (boolean: exists/not-exists) */
+ bool abort_on_null;
+
+ void init_length_and_dec();
+ bool select_prepare_to_be_in();
+
+public:
+ /*
+ Used by subquery optimizations to keep track about in which clause this
+ subquery predicate is located:
+ NO_JOIN_NEST - the predicate is an AND-part of the WHERE
+ join nest pointer - the predicate is an AND-part of ON expression
+ of a join nest
+ NULL - for all other locations
+ */
+ TABLE_LIST *emb_on_expr_nest;
+ /**
+ Reference on the Item_in_optimizer wrapper of this subquery
+ */
+ Item_in_optimizer *optimizer;
+ /* true if we got this from EXISTS or to IN */
+ bool exists_transformed;
+
+ Item_exists_subselect(st_select_lex *select_lex);
+ Item_exists_subselect()
+ :Item_subselect(), upper_not(NULL),abort_on_null(0),
+ emb_on_expr_nest(NULL), optimizer(0), exists_transformed(0)
+ {}
+
+ subs_type substype() { return EXISTS_SUBS; }
+ void reset()
+ {
+ eliminated= FALSE;
+ value= 0;
+ }
+ void no_rows_in_result();
+
+ enum Item_result result_type() const { return INT_RESULT;}
+ longlong val_int();
+ double val_real();
+ String *val_str(String*);
+ my_decimal *val_decimal(my_decimal *);
+ bool val_bool();
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec();
+ virtual void print(String *str, enum_query_type query_type);
+ bool select_transformer(JOIN *join);
+ void top_level_item() { abort_on_null=1; }
+ inline bool is_top_level_item() { return abort_on_null; }
+ bool exists2in_processor(uchar *opt_arg);
+
+ Item* expr_cache_insert_transformer(uchar *thd_arg);
+
+ void mark_as_condition_AND_part(TABLE_LIST *embedding)
+ {
+ emb_on_expr_nest= embedding;
+ }
+ virtual void under_not(Item_func_not *upper) { upper_not= upper; };
+
+ void set_exists_transformed() { exists_transformed= TRUE; }
+
+ friend class select_exists_subselect;
+ friend class subselect_uniquesubquery_engine;
+ friend class subselect_indexsubquery_engine;
+};
+
+
+TABLE_LIST * const NO_JOIN_NEST=(TABLE_LIST*)0x1;
+
+/*
+ Possible methods to execute an IN predicate. These are set by the optimizer
+ based on user-set optimizer switches, semantic analysis and cost comparison.
+*/
+#define SUBS_NOT_TRANSFORMED 0 /* No execution method was chosen for this IN. */
+/* The Final decision about the strategy is made. */
+#define SUBS_STRATEGY_CHOSEN 1
+#define SUBS_SEMI_JOIN 2 /* IN was converted to semi-join. */
+#define SUBS_IN_TO_EXISTS 4 /* IN was converted to correlated EXISTS. */
+#define SUBS_MATERIALIZATION 8 /* Execute IN via subquery materialization. */
+/* Partial matching substrategies of MATERIALIZATION. */
+#define SUBS_PARTIAL_MATCH_ROWID_MERGE 16
+#define SUBS_PARTIAL_MATCH_TABLE_SCAN 32
+/* ALL/ANY will be transformed with max/min optimization */
+/* The subquery has not aggregates, transform it into a MAX/MIN query. */
+#define SUBS_MAXMIN_INJECTED 64
+/* The subquery has aggregates, use a special max/min subselect engine. */
+#define SUBS_MAXMIN_ENGINE 128
+
+
+/**
+ Representation of IN subquery predicates of the form
+ "left_expr IN (SELECT ...)".
+
+ @details
+ This class has:
+ - A "subquery execution engine" (as a subclass of Item_subselect) that allows
+ it to evaluate subqueries. (and this class participates in execution by
+ having was_null variable where part of execution result is stored.
+ - Transformation methods (todo: more on this).
+
+ This class is not used directly, it is "wrapped" into Item_in_optimizer
+ which provides some small bits of subquery evaluation.
+*/
+
+class Item_in_subselect :public Item_exists_subselect
+{
+protected:
+ /*
+ Cache of the left operand of the subquery predicate. Allocated in the
+ runtime memory root, for each execution, thus need not be freed.
+ */
+ List<Cached_item> *left_expr_cache;
+ bool first_execution;
+
+ /*
+ expr & optimizer used in subselect rewriting to store Item for
+ all JOIN in UNION
+ */
+ Item *expr;
+ bool was_null;
+ /* A bitmap of possible execution strategies for an IN predicate. */
+ uchar in_strategy;
+protected:
+ /* Used to trigger on/off conditions that were pushed down to subselect */
+ bool *pushed_cond_guards;
+ Comp_creator *func;
+
+protected:
+ bool init_cond_guards();
+ bool select_in_like_transformer(JOIN *join);
+ bool single_value_transformer(JOIN *join);
+ bool row_value_transformer(JOIN * join);
+ bool fix_having(Item *having, st_select_lex *select_lex);
+ bool create_single_in_to_exists_cond(JOIN * join,
+ Item **where_item,
+ Item **having_item);
+ bool create_row_in_to_exists_cond(JOIN * join,
+ Item **where_item,
+ Item **having_item);
+public:
+ Item *left_expr;
+ /* Priority of this predicate in the convert-to-semi-join-nest process. */
+ int sj_convert_priority;
+ /*
+ Types of left_expr and subquery's select list allow to perform subquery
+ materialization. Currently, we set this to FALSE when it as well could
+ be TRUE. This is to be properly addressed with fix for BUG#36752.
+ */
+ bool types_allow_materialization;
+
+ /*
+ Same as above, but they also allow to scan the materialized table.
+ */
+ bool sjm_scan_allowed;
+
+ /*
+ JoinTaB Materialization (JTBM) members
+ */
+
+ /*
+ TRUE <=> This subselect has been converted into non-mergeable semi-join
+ table.
+ */
+ bool is_jtbm_merged;
+
+ /* (Applicable if is_jtbm_merged==TRUE) Time required to run the materialized join */
+ double jtbm_read_time;
+
+ /* (Applicable if is_jtbm_merged==TRUE) Number of output rows in materialized join */
+ double jtbm_record_count;
+
+ /*
+ (Applicable if is_jtbm_merged==TRUE) TRUE <=> The materialized subselect is
+ a degenerate subselect which produces 0 or 1 rows, which we know at
+ optimization phase.
+ Examples:
+ 1. subquery has "Impossible WHERE":
+
+ SELECT * FROM ot WHERE ot.column IN (SELECT it.col FROM it WHERE 2 > 3)
+
+ 2. Subquery produces one row which opt_sum.cc is able to get with one lookup:
+
+ SELECT * FROM ot WHERE ot.column IN (SELECT MAX(it.key_col) FROM it)
+ */
+ bool is_jtbm_const_tab;
+
+ /*
+ (Applicable if is_jtbm_const_tab==TRUE) Whether the subquery has produced
+ the row (or not)
+ */
+ bool jtbm_const_row_found;
+
+ /*
+ TRUE<=>this is a flattenable semi-join, false overwise.
+ */
+ bool is_flattenable_semijoin;
+
+ /*
+ TRUE<=>registered in the list of semijoins in outer select
+ */
+ bool is_registered_semijoin;
+
+ /*
+ Used to determine how this subselect item is represented in the item tree,
+ in case there is a need to locate it there and replace with something else.
+ Two options are possible:
+ 1. This item is there 'as-is'.
+ 1. This item is wrapped within Item_in_optimizer.
+ */
+ Item *original_item()
+ {
+ return (is_flattenable_semijoin && !exists_transformed ?
+ (Item*)this :
+ (Item*)optimizer);
+ }
+
+ bool *get_cond_guard(int i)
+ {
+ return pushed_cond_guards ? pushed_cond_guards + i : NULL;
+ }
+ void set_cond_guard_var(int i, bool v)
+ {
+ if ( pushed_cond_guards)
+ pushed_cond_guards[i]= v;
+ }
+ bool have_guarded_conds() { return MY_TEST(pushed_cond_guards); }
+
+ Item_func_not_all *upper_item; // point on NOT/NOP before ALL/SOME subquery
+
+ Item_in_subselect(Item * left_expr, st_select_lex *select_lex);
+ Item_in_subselect()
+ :Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE),
+ in_strategy(SUBS_NOT_TRANSFORMED),
+ pushed_cond_guards(NULL), func(NULL), is_jtbm_merged(FALSE),
+ is_jtbm_const_tab(FALSE), upper_item(0) {}
+ void cleanup();
+ subs_type substype() { return IN_SUBS; }
+ void reset()
+ {
+ eliminated= FALSE;
+ value= 0;
+ null_value= 0;
+ was_null= 0;
+ }
+ bool select_transformer(JOIN *join);
+ bool create_in_to_exists_cond(JOIN *join_arg);
+ bool inject_in_to_exists_cond(JOIN *join_arg);
+
+ virtual bool exec();
+ longlong val_int();
+ double val_real();
+ String *val_str(String*);
+ my_decimal *val_decimal(my_decimal *);
+ void update_null_value () { (void) val_bool(); }
+ bool val_bool();
+ bool test_limit(st_select_lex_unit *unit);
+ virtual void print(String *str, enum_query_type query_type);
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec();
+ void fix_after_pullout(st_select_lex *new_parent, Item **ref);
+ bool const_item() const
+ {
+ return Item_subselect::const_item() && left_expr->const_item();
+ }
+ void update_used_tables();
+ bool setup_mat_engine();
+ bool init_left_expr_cache();
+ /* Inform 'this' that it was computed, and contains a valid result. */
+ void set_first_execution() { if (first_execution) first_execution= FALSE; }
+ bool expr_cache_is_needed(THD *thd);
+ inline bool left_expr_has_null();
+
+ int optimize(double *out_rows, double *cost);
+ /*
+ Return the identifier that we could use to identify the subquery for the
+ user.
+ */
+ int get_identifier();
+
+ bool test_strategy(uchar strategy)
+ { return MY_TEST(in_strategy & strategy); }
+
+ /**
+ Test that the IN strategy was chosen for execution. This is so
+ when the CHOSEN flag is ON, and there is no other strategy.
+ */
+ bool test_set_strategy(uchar strategy)
+ {
+ DBUG_ASSERT(strategy == SUBS_SEMI_JOIN ||
+ strategy == SUBS_IN_TO_EXISTS ||
+ strategy == SUBS_MATERIALIZATION ||
+ strategy == SUBS_PARTIAL_MATCH_ROWID_MERGE ||
+ strategy == SUBS_PARTIAL_MATCH_TABLE_SCAN ||
+ strategy == SUBS_MAXMIN_INJECTED ||
+ strategy == SUBS_MAXMIN_ENGINE);
+ return ((in_strategy & SUBS_STRATEGY_CHOSEN) &&
+ (in_strategy & ~SUBS_STRATEGY_CHOSEN) == strategy);
+ }
+
+ bool is_set_strategy()
+ { return MY_TEST(in_strategy & SUBS_STRATEGY_CHOSEN); }
+
+ bool has_strategy()
+ { return in_strategy != SUBS_NOT_TRANSFORMED; }
+
+ void add_strategy (uchar strategy)
+ {
+ DBUG_ENTER("Item_in_subselect::add_strategy");
+ DBUG_PRINT("enter", ("current: %u add: %u",
+ (uint) in_strategy, (uint) strategy));
+ DBUG_ASSERT(strategy != SUBS_NOT_TRANSFORMED);
+ DBUG_ASSERT(!(strategy & SUBS_STRATEGY_CHOSEN));
+ /*
+ TODO: PS re-execution breaks this condition, because
+ check_and_do_in_subquery_rewrites() is called for each reexecution
+ and re-adds the same strategies.
+ DBUG_ASSERT(!(in_strategy & SUBS_STRATEGY_CHOSEN));
+ */
+ in_strategy|= strategy;
+ DBUG_VOID_RETURN;
+ }
+
+ void reset_strategy(uchar strategy)
+ {
+ DBUG_ENTER("Item_in_subselect::reset_strategy");
+ DBUG_PRINT("enter", ("current: %u new: %u",
+ (uint) in_strategy, (uint) strategy));
+ DBUG_ASSERT(strategy != SUBS_NOT_TRANSFORMED);
+ in_strategy= strategy;
+ DBUG_VOID_RETURN;
+ }
+
+ void set_strategy(uchar strategy)
+ {
+ DBUG_ENTER("Item_in_subselect::set_strategy");
+ DBUG_PRINT("enter", ("current: %u set: %u",
+ (uint) in_strategy,
+ (uint) (SUBS_STRATEGY_CHOSEN | strategy)));
+ /* Check that only one strategy is set for execution. */
+ DBUG_ASSERT(strategy == SUBS_SEMI_JOIN ||
+ strategy == SUBS_IN_TO_EXISTS ||
+ strategy == SUBS_MATERIALIZATION ||
+ strategy == SUBS_PARTIAL_MATCH_ROWID_MERGE ||
+ strategy == SUBS_PARTIAL_MATCH_TABLE_SCAN ||
+ strategy == SUBS_MAXMIN_INJECTED ||
+ strategy == SUBS_MAXMIN_ENGINE);
+ in_strategy= (SUBS_STRATEGY_CHOSEN | strategy);
+ DBUG_VOID_RETURN;
+ }
+ bool exists2in_processor(uchar *opt_arg __attribute__((unused)))
+ {
+ return 0;
+ };
+
+ friend class Item_ref_null_helper;
+ friend class Item_is_not_null_test;
+ friend class Item_in_optimizer;
+ friend class subselect_indexsubquery_engine;
+ friend class subselect_hash_sj_engine;
+ friend class subselect_partial_match_engine;
+ friend class Item_exists_subselect;
+};
+
+
+/* ALL/ANY/SOME subselect */
+class Item_allany_subselect :public Item_in_subselect
+{
+public:
+ chooser_compare_func_creator func_creator;
+ bool all;
+
+ Item_allany_subselect(Item * left_expr, chooser_compare_func_creator fc,
+ st_select_lex *select_lex, bool all);
+
+ void cleanup();
+ // only ALL subquery has upper not
+ subs_type substype() { return all?ALL_SUBS:ANY_SUBS; }
+ bool select_transformer(JOIN *join);
+ void create_comp_func(bool invert) { func= func_creator(invert); }
+ virtual void print(String *str, enum_query_type query_type);
+ bool is_maxmin_applicable(JOIN *join);
+ bool transform_into_max_min(JOIN *join);
+ void no_rows_in_result();
+};
+
+
+class subselect_engine: public Sql_alloc
+{
+protected:
+ select_result_interceptor *result; /* results storage class */
+ THD *thd; /* pointer to current THD */
+ Item_subselect *item; /* item, that use this engine */
+ enum Item_result res_type; /* type of results */
+ enum Item_result cmp_type; /* how to compare the results */
+ enum_field_types res_field_type; /* column type of the results */
+ bool maybe_null; /* may be null (first item in select) */
+public:
+
+ enum enum_engine_type {ABSTRACT_ENGINE, SINGLE_SELECT_ENGINE,
+ UNION_ENGINE, UNIQUESUBQUERY_ENGINE,
+ INDEXSUBQUERY_ENGINE, HASH_SJ_ENGINE,
+ ROWID_MERGE_ENGINE, TABLE_SCAN_ENGINE};
+
+ subselect_engine(THD *thd_arg, Item_subselect *si,
+ select_result_interceptor *res)
+ {
+ result= res;
+ item= si;
+ cmp_type= res_type= STRING_RESULT;
+ res_field_type= MYSQL_TYPE_VAR_STRING;
+ maybe_null= 0;
+ set_thd(thd_arg);
+ }
+ virtual ~subselect_engine() {}; // to satisfy compiler
+ virtual void cleanup()= 0;
+
+ /*
+ Also sets "thd" for subselect_engine::result.
+ Should be called before prepare().
+ */
+ void set_thd(THD *thd_arg);
+ THD * get_thd() { return thd; }
+ virtual int prepare()= 0;
+ virtual void fix_length_and_dec(Item_cache** row)= 0;
+ /*
+ Execute the engine
+
+ SYNOPSIS
+ exec()
+
+ DESCRIPTION
+ Execute the engine. The result of execution is subquery value that is
+ either captured by previously set up select_result-based 'sink' or
+ stored somewhere by the exec() method itself.
+
+ A required side effect: If at least one pushed-down predicate is
+ disabled, subselect_engine->no_rows() must return correct result after
+ the exec() call.
+
+ RETURN
+ 0 - OK
+ 1 - Either an execution error, or the engine was "changed", and the
+ caller should call exec() again for the new engine.
+ */
+ virtual int exec()= 0;
+ virtual uint cols()= 0; /* return number of columns in select */
+ virtual uint8 uncacheable()= 0; /* query is uncacheable */
+ enum Item_result type() { return res_type; }
+ enum Item_result cmptype() { return cmp_type; }
+ enum_field_types field_type() { return res_field_type; }
+ virtual void exclude()= 0;
+ virtual bool may_be_null() { return maybe_null; };
+ virtual table_map upper_select_const_tables()= 0;
+ static table_map calc_const_tables(TABLE_LIST *);
+ static table_map calc_const_tables(List<TABLE_LIST> &list);
+ virtual void print(String *str, enum_query_type query_type)= 0;
+ virtual bool change_result(Item_subselect *si,
+ select_result_interceptor *result,
+ bool temp= FALSE)= 0;
+ virtual bool no_tables()= 0;
+ virtual bool is_executed() const { return FALSE; }
+ /* Check if subquery produced any rows during last query execution */
+ virtual bool no_rows() = 0;
+ virtual enum_engine_type engine_type() { return ABSTRACT_ENGINE; }
+ virtual int get_identifier() { DBUG_ASSERT(0); return 0; }
+protected:
+ void set_row(List<Item> &item_list, Item_cache **row);
+};
+
+
+class subselect_single_select_engine: public subselect_engine
+{
+ bool prepared; /* simple subselect is prepared */
+ bool executed; /* simple subselect is executed */
+ st_select_lex *select_lex; /* corresponding select_lex */
+ JOIN * join; /* corresponding JOIN structure */
+public:
+ subselect_single_select_engine(THD *thd_arg, st_select_lex *select,
+ select_result_interceptor *result,
+ Item_subselect *item);
+ void cleanup();
+ int prepare();
+ void fix_length_and_dec(Item_cache** row);
+ int exec();
+ uint cols();
+ uint8 uncacheable();
+ void exclude();
+ table_map upper_select_const_tables();
+ virtual void print (String *str, enum_query_type query_type);
+ bool change_result(Item_subselect *si,
+ select_result_interceptor *result,
+ bool temp);
+ bool no_tables();
+ bool may_be_null();
+ bool is_executed() const { return executed; }
+ bool no_rows();
+ virtual enum_engine_type engine_type() { return SINGLE_SELECT_ENGINE; }
+ int get_identifier();
+
+ friend class subselect_hash_sj_engine;
+ friend class Item_in_subselect;
+ friend bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list,
+ Item **join_where);
+
+};
+
+
+class subselect_union_engine: public subselect_engine
+{
+ st_select_lex_unit *unit; /* corresponding unit structure */
+public:
+ subselect_union_engine(THD *thd_arg, st_select_lex_unit *u,
+ select_result_interceptor *result,
+ Item_subselect *item);
+ void cleanup();
+ int prepare();
+ void fix_length_and_dec(Item_cache** row);
+ int exec();
+ uint cols();
+ uint8 uncacheable();
+ void exclude();
+ table_map upper_select_const_tables();
+ virtual void print (String *str, enum_query_type query_type);
+ bool change_result(Item_subselect *si,
+ select_result_interceptor *result,
+ bool temp= FALSE);
+ bool no_tables();
+ bool is_executed() const;
+ bool no_rows();
+ virtual enum_engine_type engine_type() { return UNION_ENGINE; }
+};
+
+
+struct st_join_table;
+
+
+/*
+ A subquery execution engine that evaluates the subquery by doing one index
+ lookup in a unique index.
+
+ This engine is used to resolve subqueries in forms
+
+ outer_expr IN (SELECT tbl.unique_key FROM tbl WHERE subq_where)
+
+ or, tuple-based:
+
+ (oe1, .. oeN) IN (SELECT uniq_key_part1, ... uniq_key_partK
+ FROM tbl WHERE subqwhere)
+
+ i.e. the subquery is a single table SELECT without GROUP BY, aggregate
+ functions, etc.
+*/
+
+class subselect_uniquesubquery_engine: public subselect_engine
+{
+protected:
+ st_join_table *tab;
+ Item *cond; /* The WHERE condition of subselect */
+ /*
+ TRUE<=> last execution produced empty set. Valid only when left
+ expression is NULL.
+ */
+ bool empty_result_set;
+public:
+
+ // constructor can assign THD because it will be called after JOIN::prepare
+ subselect_uniquesubquery_engine(THD *thd_arg, st_join_table *tab_arg,
+ Item_subselect *subs, Item *where)
+ :subselect_engine(thd_arg, subs, 0), tab(tab_arg), cond(where)
+ {}
+ ~subselect_uniquesubquery_engine();
+ void cleanup();
+ int prepare();
+ void fix_length_and_dec(Item_cache** row);
+ int exec();
+ uint cols() { return 1; }
+ uint8 uncacheable() { return UNCACHEABLE_DEPENDENT_INJECTED; }
+ void exclude();
+ table_map upper_select_const_tables() { return 0; }
+ virtual void print (String *str, enum_query_type query_type);
+ bool change_result(Item_subselect *si,
+ select_result_interceptor *result,
+ bool temp= FALSE);
+ bool no_tables();
+ int index_lookup(); /* TIMOUR: this method needs refactoring. */
+ int scan_table();
+ bool copy_ref_key(bool skip_constants);
+ bool no_rows() { return empty_result_set; }
+ virtual enum_engine_type engine_type() { return UNIQUESUBQUERY_ENGINE; }
+};
+
+
+class subselect_indexsubquery_engine: public subselect_uniquesubquery_engine
+{
+ /* FALSE for 'ref', TRUE for 'ref-or-null'. */
+ bool check_null;
+ /*
+ The "having" clause. This clause (further reffered to as "artificial
+ having") was inserted by subquery transformation code. It contains
+ Item(s) that have a side-effect: they record whether the subquery has
+ produced a row with NULL certain components. We need to use it for cases
+ like
+ (oe1, oe2) IN (SELECT t.key, t.no_key FROM t1)
+ where we do index lookup on t.key=oe1 but need also to check if there
+ was a row such that t.no_key IS NULL.
+
+ NOTE: This is currently here and not in the uniquesubquery_engine. Ideally
+ it should have been in uniquesubquery_engine in order to allow execution of
+ subqueries like
+
+ (oe1, oe2) IN (SELECT primary_key, non_key_maybe_null_field FROM tbl)
+
+ We could use uniquesubquery_engine for the first component and let
+ Item_is_not_null_test( non_key_maybe_null_field) to handle the second.
+
+ However, subqueries like the above are currently not handled by index
+ lookup-based subquery engines, the engine applicability check misses
+ them: it doesn't switch the engine for case of artificial having and
+ [eq_]ref access (only for artifical having + ref_or_null or no having).
+ The above example subquery is handled as a full-blown SELECT with eq_ref
+ access to one table.
+
+ Due to this limitation, the "artificial having" currently needs to be
+ checked by only in indexsubquery_engine.
+ */
+ Item *having;
+public:
+
+ // constructor can assign THD because it will be called after JOIN::prepare
+ subselect_indexsubquery_engine(THD *thd_arg, st_join_table *tab_arg,
+ Item_subselect *subs, Item *where,
+ Item *having_arg, bool chk_null)
+ :subselect_uniquesubquery_engine(thd_arg, tab_arg, subs, where),
+ check_null(chk_null),
+ having(having_arg)
+ {}
+ int exec();
+ virtual void print (String *str, enum_query_type query_type);
+ virtual enum_engine_type engine_type() { return INDEXSUBQUERY_ENGINE; }
+};
+
+/*
+ This function is actually defined in sql_parse.cc, but it depends on
+ chooser_compare_func_creator defined in this file.
+ */
+Item * all_any_subquery_creator(Item *left_expr,
+ chooser_compare_func_creator cmp,
+ bool all,
+ SELECT_LEX *select_lex);
+
+
+inline bool Item_subselect::is_evaluated() const
+{
+ return engine->is_executed();
+}
+
+
+inline bool Item_subselect::is_uncacheable() const
+{
+ return engine->uncacheable();
+}
+
+/**
+ Compute an IN predicate via a hash semi-join. This class is responsible for
+ the materialization of the subquery, and the selection of the correct and
+ optimal execution method (e.g. direct index lookup, or partial matching) for
+ the IN predicate.
+*/
+
+class subselect_hash_sj_engine : public subselect_engine
+{
+public:
+ /* The table into which the subquery is materialized. */
+ TABLE *tmp_table;
+ /* TRUE if the subquery was materialized into a temp table. */
+ bool is_materialized;
+ /*
+ The old engine already chosen at parse time and stored in permanent memory.
+ Through this member we can re-create and re-prepare materialize_join for
+ each execution of a prepared statement. We also reuse the functionality
+ of subselect_single_select_engine::[prepare | cols].
+ */
+ subselect_single_select_engine *materialize_engine;
+ /*
+ QEP to execute the subquery and materialize its result into a
+ temporary table. Created during the first call to exec().
+ */
+ JOIN *materialize_join;
+ /*
+ A conjunction of all the equality condtions between all pairs of expressions
+ that are arguments of an IN predicate. We need these to post-filter some
+ IN results because index lookups sometimes match values that are actually
+ not equal to the search key in SQL terms.
+ */
+ Item_cond_and *semi_join_conds;
+ Name_resolution_context *semi_join_conds_context;
+
+
+ subselect_hash_sj_engine(THD *thd, Item_subselect *in_predicate,
+ subselect_single_select_engine *old_engine)
+ : subselect_engine(thd, in_predicate, NULL),
+ tmp_table(NULL), is_materialized(FALSE), materialize_engine(old_engine),
+ materialize_join(NULL), semi_join_conds(NULL), lookup_engine(NULL),
+ count_partial_match_columns(0), count_null_only_columns(0),
+ count_columns_with_nulls(0), strategy(UNDEFINED)
+ {}
+ ~subselect_hash_sj_engine();
+
+ bool init(List<Item> *tmp_columns, uint subquery_id);
+ void cleanup();
+ int prepare();
+ int exec();
+ virtual void print(String *str, enum_query_type query_type);
+ uint cols()
+ {
+ return materialize_engine->cols();
+ }
+ uint8 uncacheable() { return materialize_engine->uncacheable(); }
+ table_map upper_select_const_tables() { return 0; }
+ bool no_rows() { return !tmp_table->file->stats.records; }
+ virtual enum_engine_type engine_type() { return HASH_SJ_ENGINE; }
+ /*
+ TODO: factor out all these methods in a base subselect_index_engine class
+ because all of them have dummy implementations and should never be called.
+ */
+ void fix_length_and_dec(Item_cache** row);//=>base class
+ void exclude(); //=>base class
+ //=>base class
+ bool change_result(Item_subselect *si,
+ select_result_interceptor *result,
+ bool temp= FALSE);
+ bool no_tables();//=>base class
+
+protected:
+ /* The engine used to compute the IN predicate. */
+ subselect_engine *lookup_engine;
+ /* Keyparts of the only non-NULL composite index in a rowid merge. */
+ MY_BITMAP non_null_key_parts;
+ /* Keyparts of the single column indexes with NULL, one keypart per index. */
+ MY_BITMAP partial_match_key_parts;
+ uint count_partial_match_columns;
+ uint count_null_only_columns;
+ uint count_columns_with_nulls;
+ /* Possible execution strategies that can be used to compute hash semi-join.*/
+ enum exec_strategy {
+ UNDEFINED,
+ COMPLETE_MATCH, /* Use regular index lookups. */
+ PARTIAL_MATCH, /* Use some partial matching strategy. */
+ PARTIAL_MATCH_MERGE, /* Use partial matching through index merging. */
+ PARTIAL_MATCH_SCAN, /* Use partial matching through table scan. */
+ IMPOSSIBLE /* Subquery materialization is not applicable. */
+ };
+ /* The chosen execution strategy. Computed after materialization. */
+ exec_strategy strategy;
+ exec_strategy get_strategy_using_schema();
+ exec_strategy get_strategy_using_data();
+ ulonglong rowid_merge_buff_size(bool has_non_null_key,
+ bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts);
+ void choose_partial_match_strategy(bool has_non_null_key,
+ bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts);
+ bool make_semi_join_conds();
+ subselect_uniquesubquery_engine* make_unique_engine();
+
+};
+
+
+/*
+ Distinguish the type of (0-based) row numbers from the type of the index into
+ an array of row numbers.
+*/
+typedef ha_rows rownum_t;
+
+
+/*
+ An Ordered_key is an in-memory table index that allows O(log(N)) time
+ lookups of a multi-part key.
+
+ If the index is over a single column, then this column may contain NULLs, and
+ the NULLs are stored and tested separately for NULL in O(1) via is_null().
+ Multi-part indexes assume that the indexed columns do not contain NULLs.
+
+ TODO:
+ = Due to the unnatural assymetry between single and multi-part indexes, it
+ makes sense to somehow refactor or extend the class.
+
+ = This class can be refactored into a base abstract interface, and two
+ subclasses:
+ - one to represent single-column indexes, and
+ - another to represent multi-column indexes.
+ Such separation would allow slightly more efficient implementation of
+ the single-column indexes.
+ = The current design requires such indexes to be fully recreated for each
+ PS (re)execution, however most of the comprising objects can be reused.
+*/
+
+class Ordered_key : public Sql_alloc
+{
+protected:
+ /*
+ Index of the key in an array of keys. This index allows to
+ construct (sub)sets of keys represented by bitmaps.
+ */
+ uint keyid;
+ /* The table being indexed. */
+ TABLE *tbl;
+ /* The columns being indexed. */
+ Item_field **key_columns;
+ /* Number of elements in 'key_columns' (number of key parts). */
+ uint key_column_count;
+ /*
+ An expression, or sequence of expressions that forms the search key.
+ The search key is a sequence when it is Item_row. Each element of the
+ sequence is accessible via Item::element_index(int i).
+ */
+ Item *search_key;
+
+/* Value index related members. */
+ /*
+ The actual value index, consists of a sorted sequence of row numbers.
+ */
+ rownum_t *key_buff;
+ /* Number of elements in key_buff. */
+ ha_rows key_buff_elements;
+ /* Current element in 'key_buff'. */
+ ha_rows cur_key_idx;
+ /*
+ Mapping from row numbers to row ids. The element row_num_to_rowid[i]
+ contains a buffer with the rowid for the row numbered 'i'.
+ The memory for this member is not maintanined by this class because
+ all Ordered_key indexes of the same table share the same mapping.
+ */
+ uchar *row_num_to_rowid;
+ /*
+ A sequence of predicates to compare the search key with the corresponding
+ columns of a table row from the index.
+ */
+ Item_func_lt **compare_pred;
+
+/* Null index related members. */
+ MY_BITMAP null_key;
+ /* Count of NULLs per column. */
+ ha_rows null_count;
+ /* The row number that contains the first NULL in a column. */
+ rownum_t min_null_row;
+ /* The row number that contains the last NULL in a column. */
+ rownum_t max_null_row;
+
+protected:
+ bool alloc_keys_buffers();
+ /*
+ Quick sort comparison function that compares two rows of the same table
+ indentfied with their row numbers.
+ */
+ int cmp_keys_by_row_data(rownum_t a, rownum_t b);
+ static int cmp_keys_by_row_data_and_rownum(Ordered_key *key,
+ rownum_t* a, rownum_t* b);
+
+ int cmp_key_with_search_key(rownum_t row_num);
+
+public:
+ Ordered_key(uint keyid_arg, TABLE *tbl_arg,
+ Item *search_key_arg, ha_rows null_count_arg,
+ ha_rows min_null_row_arg, ha_rows max_null_row_arg,
+ uchar *row_num_to_rowid_arg);
+ ~Ordered_key();
+ void cleanup();
+ /* Initialize a multi-column index. */
+ bool init(MY_BITMAP *columns_to_index);
+ /* Initialize a single-column index. */
+ bool init(int col_idx);
+
+ uint get_column_count() { return key_column_count; }
+ uint get_keyid() { return keyid; }
+ uint get_field_idx(uint i)
+ {
+ DBUG_ASSERT(i < key_column_count);
+ return key_columns[i]->field->field_index;
+ }
+ rownum_t get_min_null_row() { return min_null_row; }
+ rownum_t get_max_null_row() { return max_null_row; }
+ MY_BITMAP * get_null_key() { return &null_key; }
+ ha_rows get_null_count() { return null_count; }
+ /*
+ Get the search key element that corresponds to the i-th key part of this
+ index.
+ */
+ Item *get_search_key(uint i)
+ {
+ return search_key->element_index(key_columns[i]->field->field_index);
+ }
+ void add_key(rownum_t row_num)
+ {
+ /* The caller must know how many elements to add. */
+ DBUG_ASSERT(key_buff_elements && cur_key_idx < key_buff_elements);
+ key_buff[cur_key_idx]= row_num;
+ ++cur_key_idx;
+ }
+
+ void sort_keys();
+ double null_selectivity();
+
+ /*
+ Position the current element at the first row that matches the key.
+ The key itself is propagated by evaluating the current value(s) of
+ this->search_key.
+ */
+ bool lookup();
+ /* Move the current index cursor to the first key. */
+ void first()
+ {
+ DBUG_ASSERT(key_buff_elements);
+ cur_key_idx= 0;
+ }
+ /* TODO */
+ bool next_same();
+ /* Move the current index cursor to the next key. */
+ bool next()
+ {
+ DBUG_ASSERT(key_buff_elements);
+ if (cur_key_idx < key_buff_elements - 1)
+ {
+ ++cur_key_idx;
+ return TRUE;
+ }
+ return FALSE;
+ };
+ /* Return the current index element. */
+ rownum_t current()
+ {
+ DBUG_ASSERT(key_buff_elements && cur_key_idx < key_buff_elements);
+ return key_buff[cur_key_idx];
+ }
+
+ void set_null(rownum_t row_num)
+ {
+ bitmap_set_bit(&null_key, (uint)row_num);
+ }
+ bool is_null(rownum_t row_num)
+ {
+ /*
+ Indexes consisting of only NULLs do not have a bitmap buffer at all.
+ Their only initialized member is 'n_bits', which is equal to the number
+ of temp table rows.
+ */
+ if (null_count == tbl->file->stats.records)
+ {
+ DBUG_ASSERT(tbl->file->stats.records == null_key.n_bits);
+ return TRUE;
+ }
+ if (row_num > max_null_row || row_num < min_null_row)
+ return FALSE;
+ return bitmap_is_set(&null_key, (uint)row_num);
+ }
+ void print(String *str);
+};
+
+
+class subselect_partial_match_engine : public subselect_engine
+{
+protected:
+ /* The temporary table that contains a materialized subquery. */
+ TABLE *tmp_table;
+ /*
+ The engine used to check whether an IN predicate is TRUE or not. If not
+ TRUE, then subselect_rowid_merge_engine further distinguishes between
+ FALSE and UNKNOWN.
+ */
+ subselect_uniquesubquery_engine *lookup_engine;
+ /* A list of equalities between each pair of IN operands. */
+ List<Item> *equi_join_conds;
+ /*
+ True if there is an all NULL row in tmp_table. If so, then if there is
+ no complete match, there is a guaranteed partial match.
+ */
+ bool has_covering_null_row;
+
+ /*
+ True if all nullable columns of tmp_table consist of only NULL values.
+ If so, then if there is a match in the non-null columns, there is a
+ guaranteed partial match.
+ */
+ bool has_covering_null_columns;
+ uint count_columns_with_nulls;
+
+protected:
+ virtual bool partial_match()= 0;
+public:
+ subselect_partial_match_engine(THD *thd_arg,
+ subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ bool has_covering_null_row_arg,
+ bool has_covering_null_columns_arg,
+ uint count_columns_with_nulls_arg);
+ int prepare() { return 0; }
+ int exec();
+ void fix_length_and_dec(Item_cache**) {}
+ uint cols() { /* TODO: what is the correct value? */ return 1; }
+ uint8 uncacheable() { return UNCACHEABLE_DEPENDENT; }
+ void exclude() {}
+ table_map upper_select_const_tables() { return 0; }
+ bool change_result(Item_subselect*,
+ select_result_interceptor*,
+ bool temp= FALSE)
+ { DBUG_ASSERT(FALSE); return false; }
+ bool no_tables() { return false; }
+ bool no_rows()
+ {
+ /*
+ TODO: It is completely unclear what is the semantics of this
+ method. The current result is computed so that the call to no_rows()
+ from Item_in_optimizer::val_int() sets Item_in_optimizer::null_value
+ correctly.
+ */
+ return !(((Item_in_subselect *) item)->null_value);
+ }
+ void print(String*, enum_query_type);
+
+ friend void subselect_hash_sj_engine::cleanup();
+};
+
+
+class subselect_rowid_merge_engine: public subselect_partial_match_engine
+{
+protected:
+ /*
+ Mapping from row numbers to row ids. The rowids are stored sequentially
+ in the array - rowid[i] is located in row_num_to_rowid + i * rowid_length.
+ */
+ uchar *row_num_to_rowid;
+ /*
+ A subset of all the keys for which there is a match for the same row.
+ Used during execution. Computed for each outer reference
+ */
+ MY_BITMAP matching_keys;
+ /*
+ The columns of the outer reference that are NULL. Computed for each
+ outer reference.
+ */
+ MY_BITMAP matching_outer_cols;
+ /*
+ Indexes of row numbers, sorted by <column_value, row_number>. If an
+ index may contain NULLs, the NULLs are stored efficiently in a bitmap.
+
+ The indexes are sorted by the selectivity of their NULL sub-indexes, the
+ one with the fewer NULLs is first. Thus, if there is any index on
+ non-NULL columns, it is contained in keys[0].
+ */
+ Ordered_key **merge_keys;
+ /* The number of elements in merge_keys. */
+ uint merge_keys_count;
+ /* The NULL bitmaps of merge keys.*/
+ MY_BITMAP **null_bitmaps;
+ /*
+ An index on all non-NULL columns of 'tmp_table'. The index has the
+ logical form: <[v_i1 | ... | v_ik], rownum>. It allows to find the row
+ number where the columns c_i1,...,c1_k contain the values v_i1,...,v_ik.
+ If such an index exists, it is always the first element of 'merge_keys'.
+ */
+ Ordered_key *non_null_key;
+ /*
+ Priority queue of Ordered_key indexes, one per NULLable column.
+ This queue is used by the partial match algorithm in method exec().
+ */
+ QUEUE pq;
+protected:
+ /*
+ Comparison function to compare keys in order of decreasing bitmap
+ selectivity.
+ */
+ static int cmp_keys_by_null_selectivity(Ordered_key **k1, Ordered_key **k2);
+ /*
+ Comparison function used by the priority queue pq, the 'smaller' key
+ is the one with the smaller current row number.
+ */
+ static int cmp_keys_by_cur_rownum(void *arg, uchar *k1, uchar *k2);
+
+ bool test_null_row(rownum_t row_num);
+ bool exists_complementing_null_row(MY_BITMAP *keys_to_complement);
+ bool partial_match();
+public:
+ subselect_rowid_merge_engine(THD *thd_arg,
+ subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, uint merge_keys_count_arg,
+ bool has_covering_null_row_arg,
+ bool has_covering_null_columns_arg,
+ uint count_columns_with_nulls_arg,
+ Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg)
+ :subselect_partial_match_engine(thd_arg, engine_arg, tmp_table_arg,
+ item_arg, result_arg, equi_join_conds_arg,
+ has_covering_null_row_arg,
+ has_covering_null_columns_arg,
+ count_columns_with_nulls_arg),
+ merge_keys_count(merge_keys_count_arg), non_null_key(NULL)
+ {}
+ ~subselect_rowid_merge_engine();
+ bool init(MY_BITMAP *non_null_key_parts, MY_BITMAP *partial_match_key_parts);
+ void cleanup();
+ virtual enum_engine_type engine_type() { return ROWID_MERGE_ENGINE; }
+};
+
+
+class subselect_table_scan_engine: public subselect_partial_match_engine
+{
+protected:
+ bool partial_match();
+public:
+ subselect_table_scan_engine(THD *thd_arg,
+ subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ bool has_covering_null_row_arg,
+ bool has_covering_null_columns_arg,
+ uint count_columns_with_nulls_arg);
+ void cleanup();
+ virtual enum_engine_type engine_type() { return TABLE_SCAN_ENGINE; }
+};
+#endif /* ITEM_SUBSELECT_INCLUDED */
diff --git a/sql/item_sum.cc b/sql/item_sum.cc
new file mode 100644
index 00000000000..21f14ae8435
--- /dev/null
+++ b/sql/item_sum.cc
@@ -0,0 +1,3660 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2014, SkySQL Ab.
+
+ 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 */
+
+
+/**
+ @file
+
+ @brief
+ Sum functions (COUNT, MIN...)
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_select.h"
+
+/**
+ Calculate the affordable RAM limit for structures like TREE or Unique
+ used in Item_sum_*
+*/
+
+ulonglong Item_sum::ram_limitation(THD *thd)
+{
+ return MY_MIN(thd->variables.tmp_table_size,
+ thd->variables.max_heap_table_size);
+}
+
+
+/**
+ Prepare an aggregate function item for checking context conditions.
+
+ The function initializes the members of the Item_sum object created
+ for a set function that are used to check validity of the set function
+ occurrence.
+ If the set function is not allowed in any subquery where it occurs
+ an error is reported immediately.
+
+ @param thd reference to the thread context info
+
+ @note
+ This function is to be called for any item created for a set function
+ object when the traversal of trees built for expressions used in the query
+ is performed at the phase of context analysis. This function is to
+ be invoked at the descent of this traversal.
+ @retval
+ TRUE if an error is reported
+ @retval
+ FALSE otherwise
+*/
+
+bool Item_sum::init_sum_func_check(THD *thd)
+{
+ SELECT_LEX *curr_sel= thd->lex->current_select;
+ if (!curr_sel->name_visibility_map)
+ {
+ for (SELECT_LEX *sl= curr_sel; sl; sl= sl->context.outer_select())
+ {
+ curr_sel->name_visibility_map|= (1 << sl-> nest_level);
+ }
+ }
+ if (!(thd->lex->allow_sum_func & curr_sel->name_visibility_map))
+ {
+ my_message(ER_INVALID_GROUP_FUNC_USE, ER(ER_INVALID_GROUP_FUNC_USE),
+ MYF(0));
+ return TRUE;
+ }
+ /* Set a reference to the nesting set function if there is any */
+ in_sum_func= thd->lex->in_sum_func;
+ /* Save a pointer to object to be used in items for nested set functions */
+ thd->lex->in_sum_func= this;
+ nest_level= thd->lex->current_select->nest_level;
+ ref_by= 0;
+ aggr_level= -1;
+ aggr_sel= NULL;
+ max_arg_level= -1;
+ max_sum_func_level= -1;
+ outer_fields.empty();
+ return FALSE;
+}
+
+/**
+ Check constraints imposed on a usage of a set function.
+
+ The method verifies whether context conditions imposed on a usage
+ of any set function are met for this occurrence.
+ It checks whether the set function occurs in the position where it
+ can be aggregated and, when it happens to occur in argument of another
+ set function, the method checks that these two functions are aggregated in
+ different subqueries.
+ If the context conditions are not met the method reports an error.
+ If the set function is aggregated in some outer subquery the method
+ adds it to the chain of items for such set functions that is attached
+ to the the st_select_lex structure for this subquery.
+
+ A number of designated members of the object are used to check the
+ conditions. They are specified in the comment before the Item_sum
+ class declaration.
+ Additionally a bitmap variable called allow_sum_func is employed.
+ It is included into the thd->lex structure.
+ The bitmap contains 1 at n-th position if the set function happens
+ to occur under a construct of the n-th level subquery where usage
+ of set functions are allowed (i.e either in the SELECT list or
+ in the HAVING clause of the corresponding subquery)
+ Consider the query:
+ @code
+ SELECT SUM(t1.b) FROM t1 GROUP BY t1.a
+ HAVING t1.a IN (SELECT t2.c FROM t2 WHERE AVG(t1.b) > 20) AND
+ t1.a > (SELECT MIN(t2.d) FROM t2);
+ @endcode
+ allow_sum_func will contain:
+ - for SUM(t1.b) - 1 at the first position
+ - for AVG(t1.b) - 1 at the first position, 0 at the second position
+ - for MIN(t2.d) - 1 at the first position, 1 at the second position.
+
+ @param thd reference to the thread context info
+ @param ref location of the pointer to this item in the embedding expression
+
+ @note
+ This function is to be called for any item created for a set function
+ object when the traversal of trees built for expressions used in the query
+ is performed at the phase of context analysis. This function is to
+ be invoked at the ascent of this traversal.
+
+ @retval
+ TRUE if an error is reported
+ @retval
+ FALSE otherwise
+*/
+
+bool Item_sum::check_sum_func(THD *thd, Item **ref)
+{
+ SELECT_LEX *curr_sel= thd->lex->current_select;
+ nesting_map allow_sum_func= (thd->lex->allow_sum_func &
+ curr_sel->name_visibility_map);
+ bool invalid= FALSE;
+ DBUG_ASSERT(curr_sel->name_visibility_map); // should be set already
+ /*
+ The value of max_arg_level is updated if an argument of the set function
+ contains a column reference resolved against a subquery whose level is
+ greater than the current value of max_arg_level.
+ max_arg_level cannot be greater than nest level.
+ nest level is always >= 0
+ */
+ if (nest_level == max_arg_level)
+ {
+ /*
+ The function must be aggregated in the current subquery,
+ If it is there under a construct where it is not allowed
+ we report an error.
+ */
+ invalid= !(allow_sum_func & ((nesting_map)1 << max_arg_level));
+ }
+ else if (max_arg_level >= 0 ||
+ !(allow_sum_func & ((nesting_map)1 << nest_level)))
+ {
+ /*
+ The set function can be aggregated only in outer subqueries.
+ Try to find a subquery where it can be aggregated;
+ If we fail to find such a subquery report an error.
+ */
+ if (register_sum_func(thd, ref))
+ return TRUE;
+ invalid= aggr_level < 0 &&
+ !(allow_sum_func & ((nesting_map)1 << nest_level));
+ if (!invalid && thd->variables.sql_mode & MODE_ANSI)
+ invalid= aggr_level < 0 && max_arg_level < nest_level;
+ }
+ if (!invalid && aggr_level < 0)
+ {
+ aggr_level= nest_level;
+ aggr_sel= curr_sel;
+ }
+ /*
+ By this moment we either found a subquery where the set function is
+ to be aggregated and assigned a value that is >= 0 to aggr_level,
+ or set the value of 'invalid' to TRUE to report later an error.
+ */
+ /*
+ Additionally we have to check whether possible nested set functions
+ are acceptable here: they are not, if the level of aggregation of
+ some of them is less than aggr_level.
+ */
+ if (!invalid)
+ invalid= aggr_level <= max_sum_func_level;
+ if (invalid)
+ {
+ my_message(ER_INVALID_GROUP_FUNC_USE, ER(ER_INVALID_GROUP_FUNC_USE),
+ MYF(0));
+ return TRUE;
+ }
+
+ if (in_sum_func)
+ {
+ /*
+ If the set function is nested adjust the value of
+ max_sum_func_level for the nesting set function.
+ We take into account only enclosed set functions that are to be
+ aggregated on the same level or above of the nest level of
+ the enclosing set function.
+ But we must always pass up the max_sum_func_level because it is
+ the maximum nested level of all directly and indirectly enclosed
+ set functions. We must do that even for set functions that are
+ aggregated inside of their enclosing set function's nest level
+ because the enclosing function may contain another enclosing
+ function that is to be aggregated outside or on the same level
+ as its parent's nest level.
+ */
+ if (in_sum_func->nest_level >= aggr_level)
+ set_if_bigger(in_sum_func->max_sum_func_level, aggr_level);
+ set_if_bigger(in_sum_func->max_sum_func_level, max_sum_func_level);
+ }
+
+ /*
+ Check that non-aggregated fields and sum functions aren't mixed in the
+ same select in the ONLY_FULL_GROUP_BY mode.
+ */
+ if (outer_fields.elements)
+ {
+ Item_field *field;
+ /*
+ Here we compare the nesting level of the select to which an outer field
+ belongs to with the aggregation level of the sum function. All fields in
+ the outer_fields list are checked.
+
+ If the nesting level is equal to the aggregation level then the field is
+ aggregated by this sum function.
+ If the nesting level is less than the aggregation level then the field
+ belongs to an outer select. In this case if there is an embedding sum
+ function add current field to functions outer_fields list. If there is
+ no embedding function then the current field treated as non aggregated
+ and the select it belongs to is marked accordingly.
+ If the nesting level is greater than the aggregation level then it means
+ that this field was added by an inner sum function.
+ Consider an example:
+
+ select avg ( <-- we are here, checking outer.f1
+ select (
+ select sum(outer.f1 + inner.f1) from inner
+ ) from outer)
+ from most_outer;
+
+ In this case we check that no aggregate functions are used in the
+ select the field belongs to. If there are some then an error is
+ raised.
+ */
+ List_iterator<Item_field> of(outer_fields);
+ while ((field= of++))
+ {
+ SELECT_LEX *sel= field->field->table->pos_in_table_list->select_lex;
+ if (sel->nest_level < aggr_level)
+ {
+ if (in_sum_func)
+ {
+ /*
+ Let upper function decide whether this field is a non
+ aggregated one.
+ */
+ in_sum_func->outer_fields.push_back(field);
+ }
+ else
+ sel->set_non_agg_field_used(true);
+ }
+ if (sel->nest_level > aggr_level &&
+ (sel->agg_func_used()) &&
+ !sel->group_list.elements)
+ {
+ my_message(ER_MIX_OF_GROUP_FUNC_AND_FIELDS,
+ ER(ER_MIX_OF_GROUP_FUNC_AND_FIELDS), MYF(0));
+ return TRUE;
+ }
+ }
+ }
+ aggr_sel->set_agg_func_used(true);
+ update_used_tables();
+ thd->lex->in_sum_func= in_sum_func;
+ return FALSE;
+}
+
+/**
+ Attach a set function to the subquery where it must be aggregated.
+
+ The function looks for an outer subquery where the set function must be
+ aggregated. If it finds such a subquery then aggr_level is set to
+ the nest level of this subquery and the item for the set function
+ is added to the list of set functions used in nested subqueries
+ inner_sum_func_list defined for each subquery. When the item is placed
+ there the field 'ref_by' is set to ref.
+
+ @note
+ Now we 'register' only set functions that are aggregated in outer
+ subqueries. Actually it makes sense to link all set function for
+ a subquery in one chain. It would simplify the process of 'splitting'
+ for set functions.
+
+ @param thd reference to the thread context info
+ @param ref location of the pointer to this item in the embedding expression
+
+ @retval
+ FALSE if the executes without failures (currently always)
+ @retval
+ TRUE otherwise
+*/
+
+bool Item_sum::register_sum_func(THD *thd, Item **ref)
+{
+ SELECT_LEX *sl;
+ nesting_map allow_sum_func= thd->lex->allow_sum_func;
+ for (sl= thd->lex->current_select->context.outer_select() ;
+ sl && sl->nest_level > max_arg_level;
+ sl= sl->context.outer_select())
+ {
+ if (aggr_level < 0 &&
+ (allow_sum_func & ((nesting_map)1 << sl->nest_level)))
+ {
+ /* Found the most nested subquery where the function can be aggregated */
+ aggr_level= sl->nest_level;
+ aggr_sel= sl;
+ }
+ }
+ if (sl && (allow_sum_func & ((nesting_map)1 << sl->nest_level)))
+ {
+ /*
+ We reached the subquery of level max_arg_level and checked
+ that the function can be aggregated here.
+ The set function will be aggregated in this subquery.
+ */
+ aggr_level= sl->nest_level;
+ aggr_sel= sl;
+
+ }
+ if (aggr_level >= 0)
+ {
+ ref_by= ref;
+ /* Add the object to the list of registered objects assigned to aggr_sel */
+ if (!aggr_sel->inner_sum_func_list)
+ next= this;
+ else
+ {
+ next= aggr_sel->inner_sum_func_list->next;
+ aggr_sel->inner_sum_func_list->next= this;
+ }
+ aggr_sel->inner_sum_func_list= this;
+ aggr_sel->with_sum_func= 1;
+
+ /*
+ Mark Item_subselect(s) as containing aggregate function all the way up
+ to aggregate function's calculation context.
+ Note that we must not mark the Item of calculation context itself
+ because with_sum_func on the calculation context st_select_lex is
+ already set above.
+
+ with_sum_func being set for an Item means that this Item refers
+ (somewhere in it, e.g. one of its arguments if it's a function) directly
+ or through intermediate items to an aggregate function that is calculated
+ in a context "outside" of the Item (e.g. in the current or outer select).
+
+ with_sum_func being set for an st_select_lex means that this st_select_lex
+ has aggregate functions directly referenced (i.e. not through a sub-select).
+ */
+ for (sl= thd->lex->current_select;
+ sl && sl != aggr_sel && sl->master_unit()->item;
+ sl= sl->master_unit()->outer_select() )
+ sl->master_unit()->item->with_sum_func= 1;
+ }
+ thd->lex->current_select->mark_as_dependent(thd, aggr_sel, NULL);
+ return FALSE;
+}
+
+
+bool Item_sum::collect_outer_ref_processor(uchar *param)
+{
+ Collect_deps_prm *prm= (Collect_deps_prm *)param;
+ SELECT_LEX *ds;
+ if ((ds= depended_from()) &&
+ ds->nest_level_base == prm->nest_level_base &&
+ ds->nest_level < prm->nest_level)
+ {
+ if (prm->collect)
+ prm->parameters->add_unique(this, &cmp_items);
+ else
+ prm->count++;
+ }
+ return FALSE;
+}
+
+
+Item_sum::Item_sum(List<Item> &list) :arg_count(list.elements),
+ forced_const(FALSE)
+{
+ if ((args=(Item**) sql_alloc(sizeof(Item*)*arg_count)))
+ {
+ uint i=0;
+ List_iterator_fast<Item> li(list);
+ Item *item;
+
+ while ((item=li++))
+ {
+ args[i++]= item;
+ }
+ }
+ if (!(orig_args= (Item **) sql_alloc(sizeof(Item *) * arg_count)))
+ {
+ args= NULL;
+ }
+ mark_as_sum_func();
+ init_aggregator();
+ list.empty(); // Fields are used
+}
+
+
+/**
+ Constructor used in processing select with temporary tebles.
+*/
+
+Item_sum::Item_sum(THD *thd, Item_sum *item):
+ Item_result_field(thd, item),
+ aggr_sel(item->aggr_sel),
+ nest_level(item->nest_level), aggr_level(item->aggr_level),
+ quick_group(item->quick_group),
+ arg_count(item->arg_count), orig_args(NULL),
+ used_tables_cache(item->used_tables_cache),
+ forced_const(item->forced_const)
+{
+ if (arg_count <= 2)
+ {
+ args=tmp_args;
+ orig_args=tmp_orig_args;
+ }
+ else
+ {
+ if (!(args= (Item**) thd->alloc(sizeof(Item*)*arg_count)))
+ return;
+ if (!(orig_args= (Item**) thd->alloc(sizeof(Item*)*arg_count)))
+ return;
+ }
+ memcpy(args, item->args, sizeof(Item*)*arg_count);
+ memcpy(orig_args, item->orig_args, sizeof(Item*)*arg_count);
+ init_aggregator();
+ with_distinct= item->with_distinct;
+ if (item->aggr)
+ set_aggregator(item->aggr->Aggrtype());
+}
+
+
+void Item_sum::mark_as_sum_func()
+{
+ SELECT_LEX *cur_select= current_thd->lex->current_select;
+ cur_select->n_sum_items++;
+ cur_select->with_sum_func= 1;
+ with_sum_func= 1;
+ with_field= 0;
+}
+
+
+void Item_sum::print(String *str, enum_query_type query_type)
+{
+ /* orig_args is not filled with valid values until fix_fields() */
+ Item **pargs= fixed ? orig_args : args;
+ str->append(func_name());
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ if (i)
+ str->append(',');
+ pargs[i]->print(str, query_type);
+ }
+ str->append(')');
+}
+
+void Item_sum::fix_num_length_and_dec()
+{
+ decimals=0;
+ for (uint i=0 ; i < arg_count ; i++)
+ set_if_bigger(decimals,args[i]->decimals);
+ max_length=float_length(decimals);
+}
+
+Item *Item_sum::get_tmp_table_item(THD *thd)
+{
+ Item_sum* sum_item= (Item_sum *) copy_or_same(thd);
+ if (sum_item && sum_item->result_field) // If not a const sum func
+ {
+ Field *result_field_tmp= sum_item->result_field;
+ for (uint i=0 ; i < sum_item->arg_count ; i++)
+ {
+ Item *arg= sum_item->args[i];
+ if (!arg->const_item())
+ {
+ if (arg->type() == Item::FIELD_ITEM)
+ ((Item_field*) arg)->field= result_field_tmp++;
+ else
+ sum_item->args[i]= new Item_field(result_field_tmp++);
+ }
+ }
+ }
+ return sum_item;
+}
+
+
+bool Item_sum::walk (Item_processor processor, bool walk_subquery,
+ uchar *argument)
+{
+ if (arg_count)
+ {
+ Item **arg,**arg_end;
+ for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++)
+ {
+ if ((*arg)->walk(processor, walk_subquery, argument))
+ return 1;
+ }
+ }
+ return (this->*processor)(argument);
+}
+
+
+Field *Item_sum::create_tmp_field(bool group, TABLE *table,
+ uint convert_blob_length)
+{
+ Field *UNINIT_VAR(field);
+ switch (result_type()) {
+ case REAL_RESULT:
+ field= new Field_double(max_length, maybe_null, name, decimals, TRUE);
+ break;
+ case INT_RESULT:
+ field= new Field_longlong(max_length, maybe_null, name, unsigned_flag);
+ break;
+ case STRING_RESULT:
+ if (max_length/collation.collation->mbmaxlen <= 255 ||
+ convert_blob_length > Field_varstring::MAX_SIZE ||
+ !convert_blob_length)
+ return make_string_field(table);
+ field= new Field_varstring(convert_blob_length, maybe_null,
+ name, table->s, collation.collation);
+ break;
+ case DECIMAL_RESULT:
+ field= Field_new_decimal::create_from_item(this);
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ // This case should never be choosen
+ DBUG_ASSERT(0);
+ return 0;
+ }
+ if (field)
+ field->init(table);
+ return field;
+}
+
+
+void Item_sum::update_used_tables ()
+{
+ if (!forced_const)
+ {
+ used_tables_cache= 0;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ args[i]->update_used_tables();
+ used_tables_cache|= args[i]->used_tables();
+ }
+ /*
+ MariaDB: don't run the following {
+
+ used_tables_cache&= PSEUDO_TABLE_BITS;
+
+ // the aggregate function is aggregated into its local context
+ used_tables_cache|= ((table_map)1 << aggr_sel->join->tables) - 1;
+
+ } because if we do it, table elimination will assume that
+ - constructs like "COUNT(*)" use columns from all tables
+ - so, it is not possible to eliminate any table
+ our solution for COUNT(*) is that it has
+ item->used_tables() == 0 && !item->const_item()
+ */
+ }
+}
+
+
+Item *Item_sum::set_arg(uint i, THD *thd, Item *new_val)
+{
+ thd->change_item_tree(args + i, new_val);
+ return new_val;
+}
+
+
+int Item_sum::set_aggregator(Aggregator::Aggregator_type aggregator)
+{
+ /*
+ Dependent subselects may be executed multiple times, making
+ set_aggregator to be called multiple times. The aggregator type
+ will be the same, but it needs to be reset so that it is
+ reevaluated with the new dependent data.
+ This function may also be called multiple times during query optimization.
+ In this case, the type may change, so we delete the old aggregator,
+ and create a new one.
+ */
+ if (aggr && aggregator == aggr->Aggrtype())
+ {
+ aggr->clear();
+ return FALSE;
+ }
+
+ delete aggr;
+ switch (aggregator)
+ {
+ case Aggregator::DISTINCT_AGGREGATOR:
+ aggr= new Aggregator_distinct(this);
+ break;
+ case Aggregator::SIMPLE_AGGREGATOR:
+ aggr= new Aggregator_simple(this);
+ break;
+ };
+ return aggr ? FALSE : TRUE;
+}
+
+
+void Item_sum::cleanup()
+{
+ if (aggr)
+ {
+ delete aggr;
+ aggr= NULL;
+ }
+ Item_result_field::cleanup();
+ forced_const= FALSE;
+}
+
+
+/**
+ Compare keys consisting of single field that cannot be compared as binary.
+
+ Used by the Unique class to compare keys. Will do correct comparisons
+ for all field types.
+
+ @param arg Pointer to the relevant Field class instance
+ @param key1 left key image
+ @param key2 right key image
+ @return comparison result
+ @retval < 0 if key1 < key2
+ @retval = 0 if key1 = key2
+ @retval > 0 if key1 > key2
+*/
+
+int simple_str_key_cmp(void* arg, uchar* key1, uchar* key2)
+{
+ Field *f= (Field*) arg;
+ return f->cmp(key1, key2);
+}
+
+
+C_MODE_START
+
+int count_distinct_walk(void *elem, element_count count, void *arg)
+{
+ (*((ulonglong*)arg))++;
+ return 0;
+}
+
+C_MODE_END
+
+
+/**
+ Correctly compare composite keys.
+
+ Used by the Unique class to compare keys. Will do correct comparisons
+ for composite keys with various field types.
+
+ @param arg Pointer to the relevant Aggregator_distinct instance
+ @param key1 left key image
+ @param key2 right key image
+ @return comparison result
+ @retval <0 if key1 < key2
+ @retval =0 if key1 = key2
+ @retval >0 if key1 > key2
+*/
+
+int Aggregator_distinct::composite_key_cmp(void* arg, uchar* key1, uchar* key2)
+{
+ Aggregator_distinct *aggr= (Aggregator_distinct *) arg;
+ Field **field = aggr->table->field;
+ Field **field_end= field + aggr->table->s->fields;
+ uint32 *lengths=aggr->field_lengths;
+ for (; field < field_end; ++field)
+ {
+ Field* f = *field;
+ int len = *lengths++;
+ int res = f->cmp(key1, key2);
+ if (res)
+ return res;
+ key1 += len;
+ key2 += len;
+ }
+ return 0;
+}
+
+
+static enum enum_field_types
+calc_tmp_field_type(enum enum_field_types table_field_type,
+ Item_result result_type)
+{
+ /* Adjust tmp table type according to the chosen aggregation type */
+ switch (result_type) {
+ case STRING_RESULT:
+ case REAL_RESULT:
+ if (table_field_type != MYSQL_TYPE_FLOAT)
+ table_field_type= MYSQL_TYPE_DOUBLE;
+ break;
+ case INT_RESULT:
+ table_field_type= MYSQL_TYPE_LONGLONG;
+ /* fallthrough */
+ case DECIMAL_RESULT:
+ if (table_field_type != MYSQL_TYPE_LONGLONG)
+ table_field_type= MYSQL_TYPE_NEWDECIMAL;
+ break;
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0);
+ }
+ return table_field_type;
+}
+
+
+/***************************************************************************/
+
+C_MODE_START
+
+/* Declarations for auxilary C-callbacks */
+
+int simple_raw_key_cmp(void* arg, const void* key1, const void* key2)
+{
+ return memcmp(key1, key2, *(uint *) arg);
+}
+
+
+static int item_sum_distinct_walk_for_count(void *element,
+ element_count num_of_dups,
+ void *item)
+{
+ return ((Aggregator_distinct*) (item))->unique_walk_function_for_count(element);
+}
+
+
+static int item_sum_distinct_walk(void *element, element_count num_of_dups,
+ void *item)
+{
+ return ((Aggregator_distinct*) (item))->unique_walk_function(element);
+}
+
+C_MODE_END
+
+/***************************************************************************/
+/**
+ Called before feeding the first row. Used to allocate/setup
+ the internal structures used for aggregation.
+
+ @param thd Thread descriptor
+ @return status
+ @retval FALSE success
+ @retval TRUE faliure
+
+ Prepares Aggregator_distinct to process the incoming stream.
+ Creates the temporary table and the Unique class if needed.
+ Called by Item_sum::aggregator_setup()
+*/
+
+bool Aggregator_distinct::setup(THD *thd)
+{
+ endup_done= FALSE;
+ /*
+ Setup can be called twice for ROLLUP items. This is a bug.
+ Please add DBUG_ASSERT(tree == 0) here when it's fixed.
+ */
+ if (tree || table || tmp_table_param)
+ return FALSE;
+
+ if (item_sum->setup(thd))
+ return TRUE;
+ if (item_sum->sum_func() == Item_sum::COUNT_FUNC ||
+ item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC)
+ {
+ List<Item> list;
+ SELECT_LEX *select_lex= thd->lex->current_select;
+
+ if (!(tmp_table_param= new TMP_TABLE_PARAM))
+ return TRUE;
+
+ /* Create a table with an unique key over all parameters */
+ for (uint i=0; i < item_sum->get_arg_count() ; i++)
+ {
+ Item *item=item_sum->get_arg(i);
+ if (list.push_back(item))
+ return TRUE; // End of memory
+ if (item->const_item() && item->is_null())
+ always_null= true;
+ }
+ if (always_null)
+ return FALSE;
+ count_field_types(select_lex, tmp_table_param, list, 0);
+ tmp_table_param->force_copy_fields= item_sum->has_force_copy_fields();
+ DBUG_ASSERT(table == 0);
+ /*
+ Make create_tmp_table() convert BIT columns to BIGINT.
+ This is needed because BIT fields store parts of their data in table's
+ null bits, and we don't have methods to compare two table records, which
+ is needed by Unique which is used when HEAP table is used.
+ */
+ {
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ while ((item= li++))
+ {
+ if (item->type() == Item::FIELD_ITEM &&
+ ((Item_field*)item)->field->type() == FIELD_TYPE_BIT)
+ item->marker=4;
+ }
+ }
+ if (!(table= create_tmp_table(thd, tmp_table_param, list, (ORDER*) 0, 1,
+ 0,
+ (select_lex->options | thd->variables.option_bits),
+ HA_POS_ERROR, const_cast<char*>(""))))
+ return TRUE;
+ table->file->extra(HA_EXTRA_NO_ROWS); // Don't update rows
+ table->no_rows=1;
+
+ if (table->s->db_type() == heap_hton)
+ {
+ /*
+ No blobs, otherwise it would have been MyISAM: set up a compare
+ function and its arguments to use with Unique.
+ */
+ qsort_cmp2 compare_key;
+ void* cmp_arg;
+ Field **field= table->field;
+ Field **field_end= field + table->s->fields;
+ bool all_binary= TRUE;
+
+ for (tree_key_length= 0; field < field_end; ++field)
+ {
+ Field *f= *field;
+ enum enum_field_types type= f->type();
+ tree_key_length+= f->pack_length();
+ if ((type == MYSQL_TYPE_VARCHAR) ||
+ (!f->binary() && (type == MYSQL_TYPE_STRING ||
+ type == MYSQL_TYPE_VAR_STRING)))
+ {
+ all_binary= FALSE;
+ break;
+ }
+ }
+ if (all_binary)
+ {
+ cmp_arg= (void*) &tree_key_length;
+ compare_key= (qsort_cmp2) simple_raw_key_cmp;
+ }
+ else
+ {
+ if (table->s->fields == 1)
+ {
+ /*
+ If we have only one field, which is the most common use of
+ count(distinct), it is much faster to use a simpler key
+ compare method that can take advantage of not having to worry
+ about other fields.
+ */
+ compare_key= (qsort_cmp2) simple_str_key_cmp;
+ cmp_arg= (void*) table->field[0];
+ /* tree_key_length has been set already */
+ }
+ else
+ {
+ uint32 *length;
+ compare_key= (qsort_cmp2) composite_key_cmp;
+ cmp_arg= (void*) this;
+ field_lengths= (uint32*) thd->alloc(table->s->fields * sizeof(uint32));
+ for (tree_key_length= 0, length= field_lengths, field= table->field;
+ field < field_end; ++field, ++length)
+ {
+ *length= (*field)->pack_length();
+ tree_key_length+= *length;
+ }
+ }
+ }
+ DBUG_ASSERT(tree == 0);
+ tree= new Unique(compare_key, cmp_arg, tree_key_length,
+ item_sum->ram_limitation(thd));
+ /*
+ The only time tree_key_length could be 0 is if someone does
+ count(distinct) on a char(0) field - stupid thing to do,
+ but this has to be handled - otherwise someone can crash
+ the server with a DoS attack
+ */
+ if (! tree)
+ return TRUE;
+ }
+ return FALSE;
+ }
+ else
+ {
+ List<Create_field> field_list;
+ Create_field field_def; /* field definition */
+ Item *arg;
+ DBUG_ENTER("Aggregator_distinct::setup");
+ /* It's legal to call setup() more than once when in a subquery */
+ if (tree)
+ DBUG_RETURN(FALSE);
+
+ /*
+ Virtual table and the tree are created anew on each re-execution of
+ PS/SP. Hence all further allocations are performed in the runtime
+ mem_root.
+ */
+ if (field_list.push_back(&field_def))
+ DBUG_RETURN(TRUE);
+
+ item_sum->null_value= item_sum->maybe_null= 1;
+ item_sum->quick_group= 0;
+
+ DBUG_ASSERT(item_sum->get_arg(0)->fixed);
+
+ arg= item_sum->get_arg(0);
+ if (arg->const_item())
+ {
+ (void) arg->val_int();
+ if (arg->null_value)
+ always_null= true;
+ }
+
+ if (always_null)
+ DBUG_RETURN(FALSE);
+
+ enum enum_field_types field_type;
+
+ field_type= calc_tmp_field_type(arg->field_type(),
+ arg->result_type());
+ field_def.init_for_tmp_table(field_type,
+ arg->max_length,
+ arg->decimals,
+ arg->maybe_null,
+ arg->unsigned_flag);
+
+ if (! (table= create_virtual_tmp_table(thd, field_list)))
+ DBUG_RETURN(TRUE);
+
+ /* XXX: check that the case of CHAR(0) works OK */
+ tree_key_length= table->s->reclength - table->s->null_bytes;
+
+ /*
+ Unique handles all unique elements in a tree until they can't fit
+ in. Then the tree is dumped to the temporary file. We can use
+ simple_raw_key_cmp because the table contains numbers only; decimals
+ are converted to binary representation as well.
+ */
+ tree= new Unique(simple_raw_key_cmp, &tree_key_length, tree_key_length,
+ item_sum->ram_limitation(thd));
+
+ DBUG_RETURN(tree == 0);
+ }
+}
+
+
+/**
+ Invalidate calculated value and clear the distinct rows.
+
+ Frees space used by the internal data structures.
+ Removes the accumulated distinct rows. Invalidates the calculated result.
+*/
+
+void Aggregator_distinct::clear()
+{
+ endup_done= FALSE;
+ item_sum->clear();
+ if (tree)
+ tree->reset();
+ /* tree and table can be both null only if always_null */
+ if (item_sum->sum_func() == Item_sum::COUNT_FUNC ||
+ item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC)
+ {
+ if (!tree && table)
+ {
+ table->file->extra(HA_EXTRA_NO_CACHE);
+ table->file->ha_delete_all_rows();
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ }
+ }
+ else
+ {
+ item_sum->null_value= 1;
+ }
+}
+
+
+/**
+ Process incoming row.
+
+ Add it to Unique/temp hash table if it's unique. Skip the row if
+ not unique.
+ Prepare Aggregator_distinct to process the incoming stream.
+ Create the temporary table and the Unique class if needed.
+ Called by Item_sum::aggregator_add().
+ To actually get the result value in item_sum's buffers
+ Aggregator_distinct::endup() must be called.
+
+ @return status
+ @retval FALSE success
+ @retval TRUE failure
+*/
+
+bool Aggregator_distinct::add()
+{
+ if (always_null)
+ return 0;
+
+ if (item_sum->sum_func() == Item_sum::COUNT_FUNC ||
+ item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC)
+ {
+ int error;
+ copy_fields(tmp_table_param);
+ if (copy_funcs(tmp_table_param->items_to_copy, table->in_use))
+ return TRUE;
+
+ for (Field **field=table->field ; *field ; field++)
+ if ((*field)->is_real_null(0))
+ return 0; // Don't count NULL
+
+ if (tree)
+ {
+ /*
+ The first few bytes of record (at least one) are just markers
+ for deleted and NULLs. We want to skip them since they will
+ bloat the tree without providing any valuable info. Besides,
+ key_length used to initialize the tree didn't include space for them.
+ */
+ return tree->unique_add(table->record[0] + table->s->null_bytes);
+ }
+ if ((error= table->file->ha_write_tmp_row(table->record[0])) &&
+ table->file->is_fatal_error(error, HA_CHECK_DUP))
+ return TRUE;
+ return FALSE;
+ }
+ else
+ {
+ item_sum->get_arg(0)->save_in_field(table->field[0], FALSE);
+ if (table->field[0]->is_null())
+ return 0;
+ DBUG_ASSERT(tree);
+ item_sum->null_value= 0;
+ /*
+ '0' values are also stored in the tree. This doesn't matter
+ for SUM(DISTINCT), but is important for AVG(DISTINCT)
+ */
+ return tree->unique_add(table->field[0]->ptr);
+ }
+}
+
+
+/**
+ Calculate the aggregate function value.
+
+ Since Distinct_aggregator::add() just collects the distinct rows,
+ we must go over the distinct rows and feed them to the aggregation
+ function before returning its value.
+ This is what endup () does. It also sets the result validity flag
+ endup_done to TRUE so it will not recalculate the aggregate value
+ again if the Item_sum hasn't been reset.
+*/
+
+void Aggregator_distinct::endup()
+{
+ /* prevent consecutive recalculations */
+ if (endup_done)
+ return;
+
+ /* we are going to calculate the aggregate value afresh */
+ item_sum->clear();
+
+ /* The result will definitely be null : no more calculations needed */
+ if (always_null)
+ return;
+
+ if (item_sum->sum_func() == Item_sum::COUNT_FUNC ||
+ item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC)
+ {
+ DBUG_ASSERT(item_sum->fixed == 1);
+ Item_sum_count *sum= (Item_sum_count *)item_sum;
+ if (tree && tree->elements == 0)
+ {
+ /* everything fits in memory */
+ sum->count= (longlong) tree->elements_in_tree();
+ endup_done= TRUE;
+ }
+ if (!tree)
+ {
+ /* there were blobs */
+ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
+ sum->count= table->file->stats.records;
+ endup_done= TRUE;
+ }
+ }
+
+ /*
+ We don't have a tree only if 'setup()' hasn't been called;
+ this is the case of sql_executor.cc:return_zero_rows.
+ */
+ if (tree && !endup_done)
+ {
+ /*
+ All tree's values are not NULL.
+ Note that value of field is changed as we walk the tree, in
+ Aggregator_distinct::unique_walk_function, but it's always not NULL.
+ */
+ table->field[0]->set_notnull();
+ /* go over the tree of distinct keys and calculate the aggregate value */
+ use_distinct_values= TRUE;
+ tree_walk_action func;
+ if (item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC)
+ func= item_sum_distinct_walk_for_count;
+ else
+ func= item_sum_distinct_walk;
+ tree->walk(table, func, (void*) this);
+ use_distinct_values= FALSE;
+ }
+ /* prevent consecutive recalculations */
+ endup_done= TRUE;
+}
+
+
+String *
+Item_sum_num::val_str(String *str)
+{
+ return val_string_from_real(str);
+}
+
+
+my_decimal *Item_sum_num::val_decimal(my_decimal *decimal_value)
+{
+ return val_decimal_from_real(decimal_value);
+}
+
+
+String *
+Item_sum_int::val_str(String *str)
+{
+ return val_string_from_int(str);
+}
+
+
+my_decimal *Item_sum_int::val_decimal(my_decimal *decimal_value)
+{
+ return val_decimal_from_int(decimal_value);
+}
+
+
+bool
+Item_sum_num::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+
+ if (init_sum_func_check(thd))
+ return TRUE;
+
+ decimals=0;
+ maybe_null= sum_func() != COUNT_FUNC;
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ if (args[i]->fix_fields(thd, args + i) || args[i]->check_cols(1))
+ return TRUE;
+ set_if_bigger(decimals, args[i]->decimals);
+ with_subselect|= args[i]->with_subselect;
+ }
+ result_field=0;
+ max_length=float_length(decimals);
+ null_value=1;
+ fix_length_and_dec();
+
+ if (check_sum_func(thd, ref))
+ return TRUE;
+
+ memcpy (orig_args, args, sizeof (Item *) * arg_count);
+ fixed= 1;
+ return FALSE;
+}
+
+
+bool
+Item_sum_hybrid::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+
+ Item *item= args[0];
+
+ if (init_sum_func_check(thd))
+ return TRUE;
+
+ // 'item' can be changed during fix_fields
+ if ((!item->fixed && item->fix_fields(thd, args)) ||
+ (item= args[0])->check_cols(1))
+ return TRUE;
+ decimals=item->decimals;
+ with_subselect= args[0]->with_subselect;
+
+ switch (hybrid_type= item->result_type()) {
+ case INT_RESULT:
+ case DECIMAL_RESULT:
+ case STRING_RESULT:
+ max_length= item->max_length;
+ break;
+ case REAL_RESULT:
+ max_length= float_length(decimals);
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ };
+ setup_hybrid(args[0], NULL);
+ /* MIN/MAX can return NULL for empty set indepedent of the used column */
+ maybe_null= 1;
+ unsigned_flag=item->unsigned_flag;
+ result_field=0;
+ null_value=1;
+ fix_length_and_dec();
+ item= item->real_item();
+ if (item->type() == Item::FIELD_ITEM)
+ hybrid_field_type= ((Item_field*) item)->field->type();
+ else
+ hybrid_field_type= Item::field_type();
+
+ if (check_sum_func(thd, ref))
+ return TRUE;
+
+ orig_args[0]= args[0];
+ fixed= 1;
+ return FALSE;
+}
+
+
+/**
+ MIN/MAX function setup.
+
+ @param item argument of MIN/MAX function
+ @param value_arg calculated value of MIN/MAX function
+
+ @details
+ Setup cache/comparator of MIN/MAX functions. When called by the
+ copy_or_same function value_arg parameter contains calculated value
+ of the original MIN/MAX object and it is saved in this object's cache.
+
+ We mark the value and arg_cache with 'RAND_TABLE_BIT' to ensure
+ that Arg_comparator::compare_datetime() doesn't allocate new
+ item inside of Arg_comparator. This would cause compare_datetime()
+ and Item_sum_min::add() to use different values!
+*/
+
+void Item_sum_hybrid::setup_hybrid(Item *item, Item *value_arg)
+{
+ if (!(value= Item_cache::get_cache(item, item->cmp_type())))
+ return;
+ value->setup(item);
+ value->store(value_arg);
+ /* Don't cache value, as it will change */
+ if (!item->const_item())
+ value->set_used_tables(RAND_TABLE_BIT);
+ if (!(arg_cache= Item_cache::get_cache(item, item->cmp_type())))
+ return;
+ arg_cache->setup(item);
+ /* Don't cache value, as it will change */
+ if (!item->const_item())
+ arg_cache->set_used_tables(RAND_TABLE_BIT);
+ cmp= new Arg_comparator();
+ if (cmp)
+ cmp->set_cmp_func(this, (Item**)&arg_cache, (Item**)&value, FALSE);
+ collation.set(item->collation);
+}
+
+
+Field *Item_sum_hybrid::create_tmp_field(bool group, TABLE *table,
+ uint convert_blob_length)
+{
+ Field *field;
+ if (args[0]->type() == Item::FIELD_ITEM)
+ {
+ field= ((Item_field*) args[0])->field;
+
+ if ((field= create_tmp_field_from_field(current_thd, field, name, table,
+ NULL, convert_blob_length)))
+ field->flags&= ~NOT_NULL_FLAG;
+ return field;
+ }
+ /*
+ DATE/TIME fields have STRING_RESULT result types.
+ In order to preserve field type, it's needed to handle DATE/TIME
+ fields creations separately.
+ */
+ switch (args[0]->field_type()) {
+ case MYSQL_TYPE_DATE:
+ field= new Field_newdate(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE,
+ name);
+ break;
+ case MYSQL_TYPE_TIME:
+ field= new_Field_time(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE,
+ name, decimals);
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_DATETIME:
+ field= new_Field_datetime(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE,
+ name, decimals);
+ break;
+ default:
+ return Item_sum::create_tmp_field(group, table, convert_blob_length);
+ }
+ if (field)
+ field->init(table);
+ return field;
+}
+
+
+/***********************************************************************
+** reset and add of sum_func
+***********************************************************************/
+
+/**
+ @todo
+ check if the following assignments are really needed
+*/
+Item_sum_sum::Item_sum_sum(THD *thd, Item_sum_sum *item)
+ :Item_sum_num(thd, item), hybrid_type(item->hybrid_type),
+ curr_dec_buff(item->curr_dec_buff)
+{
+ /* TODO: check if the following assignments are really needed */
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ my_decimal2decimal(item->dec_buffs, dec_buffs);
+ my_decimal2decimal(item->dec_buffs + 1, dec_buffs + 1);
+ }
+ else
+ sum= item->sum;
+}
+
+Item *Item_sum_sum::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_sum(thd, this);
+}
+
+
+void Item_sum_sum::clear()
+{
+ DBUG_ENTER("Item_sum_sum::clear");
+ null_value=1;
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ curr_dec_buff= 0;
+ my_decimal_set_zero(dec_buffs);
+ }
+ else
+ sum= 0.0;
+ DBUG_VOID_RETURN;
+}
+
+
+void Item_sum_sum::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_sum_sum::fix_length_and_dec");
+ maybe_null=null_value=1;
+ decimals= args[0]->decimals;
+ switch (args[0]->cast_to_int_type()) {
+ case REAL_RESULT:
+ case STRING_RESULT:
+ hybrid_type= REAL_RESULT;
+ sum= 0.0;
+ break;
+ case INT_RESULT:
+ case TIME_RESULT:
+ case DECIMAL_RESULT:
+ {
+ /* SUM result can't be longer than length(arg) + length(MAX_ROWS) */
+ int precision= args[0]->decimal_precision() + DECIMAL_LONGLONG_DIGITS;
+ max_length= my_decimal_precision_to_length_no_truncation(precision,
+ decimals,
+ unsigned_flag);
+ curr_dec_buff= 0;
+ hybrid_type= DECIMAL_RESULT;
+ my_decimal_set_zero(dec_buffs);
+ break;
+ }
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ DBUG_PRINT("info", ("Type: %s (%d, %d)",
+ (hybrid_type == REAL_RESULT ? "REAL_RESULT" :
+ hybrid_type == DECIMAL_RESULT ? "DECIMAL_RESULT" :
+ hybrid_type == INT_RESULT ? "INT_RESULT" :
+ "--ILLEGAL!!!--"),
+ max_length,
+ (int)decimals));
+ DBUG_VOID_RETURN;
+}
+
+
+bool Item_sum_sum::add()
+{
+ DBUG_ENTER("Item_sum_sum::add");
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ my_decimal value;
+ const my_decimal *val= aggr->arg_val_decimal(&value);
+ if (!aggr->arg_is_null(true))
+ {
+ my_decimal_add(E_DEC_FATAL_ERROR, dec_buffs + (curr_dec_buff^1),
+ val, dec_buffs + curr_dec_buff);
+ curr_dec_buff^= 1;
+ null_value= 0;
+ }
+ }
+ else
+ {
+ sum+= aggr->arg_val_real();
+ if (!aggr->arg_is_null(true))
+ null_value= 0;
+ }
+ DBUG_RETURN(0);
+}
+
+
+longlong Item_sum_sum::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (aggr)
+ aggr->endup();
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ longlong result;
+ my_decimal2int(E_DEC_FATAL_ERROR, dec_buffs + curr_dec_buff, unsigned_flag,
+ &result);
+ return result;
+ }
+ return (longlong) rint(val_real());
+}
+
+
+double Item_sum_sum::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (aggr)
+ aggr->endup();
+ if (hybrid_type == DECIMAL_RESULT)
+ my_decimal2double(E_DEC_FATAL_ERROR, dec_buffs + curr_dec_buff, &sum);
+ return sum;
+}
+
+
+String *Item_sum_sum::val_str(String *str)
+{
+ if (aggr)
+ aggr->endup();
+ if (hybrid_type == DECIMAL_RESULT)
+ return val_string_from_decimal(str);
+ return val_string_from_real(str);
+}
+
+
+my_decimal *Item_sum_sum::val_decimal(my_decimal *val)
+{
+ if (aggr)
+ aggr->endup();
+ if (hybrid_type == DECIMAL_RESULT)
+ return (dec_buffs + curr_dec_buff);
+ return val_decimal_from_real(val);
+}
+
+/**
+ Aggregate a distinct row from the distinct hash table.
+
+ Called for each row into the hash table 'Aggregator_distinct::table'.
+ Includes the current distinct row into the calculation of the
+ aggregate value. Uses the Field classes to get the value from the row.
+ This function is used for AVG/SUM(DISTINCT). For COUNT(DISTINCT)
+ it's called only when there are no blob arguments and the data don't
+ fit into memory (so Unique makes persisted trees on disk).
+
+ @param element pointer to the row data.
+
+ @return status
+ @retval FALSE success
+ @retval TRUE failure
+*/
+
+bool Aggregator_distinct::unique_walk_function(void *element)
+{
+ memcpy(table->field[0]->ptr, element, tree_key_length);
+ item_sum->add();
+ return 0;
+}
+
+
+/*
+ A variant of unique_walk_function() that is to be used with Item_sum_count.
+
+ COUNT is a special aggregate function: it doesn't need the values, it only
+ needs to count them. COUNT needs to know the values are not NULLs, but NULL
+ values are not put into the Unique, so we don't need to check for NULLs here.
+*/
+
+bool Aggregator_distinct::unique_walk_function_for_count(void *element)
+{
+ Item_sum_count *sum= (Item_sum_count *)item_sum;
+ sum->count++;
+ return 0;
+}
+
+
+Aggregator_distinct::~Aggregator_distinct()
+{
+ if (tree)
+ {
+ delete tree;
+ tree= NULL;
+ }
+ if (table)
+ {
+ free_tmp_table(table->in_use, table);
+ table=NULL;
+ }
+ if (tmp_table_param)
+ {
+ delete tmp_table_param;
+ tmp_table_param= NULL;
+ }
+}
+
+
+my_decimal *Aggregator_simple::arg_val_decimal(my_decimal *value)
+{
+ return item_sum->args[0]->val_decimal(value);
+}
+
+
+double Aggregator_simple::arg_val_real()
+{
+ return item_sum->args[0]->val_real();
+}
+
+
+bool Aggregator_simple::arg_is_null(bool use_null_value)
+{
+ Item **item= item_sum->args;
+ const uint item_count= item_sum->arg_count;
+ if (use_null_value)
+ {
+ for (uint i= 0; i < item_count; i++)
+ {
+ if (item[i]->null_value)
+ return true;
+ }
+ }
+ else
+ {
+ for (uint i= 0; i < item_count; i++)
+ {
+ if (item[i]->maybe_null && item[i]->is_null())
+ return true;
+ }
+ }
+ return false;
+}
+
+
+my_decimal *Aggregator_distinct::arg_val_decimal(my_decimal * value)
+{
+ return use_distinct_values ? table->field[0]->val_decimal(value) :
+ item_sum->args[0]->val_decimal(value);
+}
+
+
+double Aggregator_distinct::arg_val_real()
+{
+ return use_distinct_values ? table->field[0]->val_real() :
+ item_sum->args[0]->val_real();
+}
+
+
+bool Aggregator_distinct::arg_is_null(bool use_null_value)
+{
+ if (use_distinct_values)
+ {
+ const bool rc= table->field[0]->is_null();
+ DBUG_ASSERT(!rc); // NULLs are never stored in 'tree'
+ return rc;
+ }
+ return use_null_value ?
+ item_sum->args[0]->null_value :
+ (item_sum->args[0]->maybe_null && item_sum->args[0]->is_null());
+}
+
+
+Item *Item_sum_count::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_count(thd, this);
+}
+
+
+void Item_sum_count::clear()
+{
+ count= 0;
+}
+
+
+bool Item_sum_count::add()
+{
+ if (aggr->arg_is_null(false))
+ return 0;
+ count++;
+ return 0;
+}
+
+longlong Item_sum_count::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (aggr)
+ aggr->endup();
+ return (longlong) count;
+}
+
+
+void Item_sum_count::cleanup()
+{
+ DBUG_ENTER("Item_sum_count::cleanup");
+ count= 0;
+ Item_sum_int::cleanup();
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Avgerage
+*/
+void Item_sum_avg::fix_length_and_dec()
+{
+ Item_sum_sum::fix_length_and_dec();
+ maybe_null=null_value=1;
+ prec_increment= current_thd->variables.div_precincrement;
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ int precision= args[0]->decimal_precision() + prec_increment;
+ decimals= MY_MIN(args[0]->decimals + prec_increment, DECIMAL_MAX_SCALE);
+ max_length= my_decimal_precision_to_length_no_truncation(precision,
+ decimals,
+ unsigned_flag);
+ f_precision= MY_MIN(precision+DECIMAL_LONGLONG_DIGITS, DECIMAL_MAX_PRECISION);
+ f_scale= args[0]->decimals;
+ dec_bin_size= my_decimal_get_binary_size(f_precision, f_scale);
+ }
+ else
+ {
+ decimals= MY_MIN(args[0]->decimals + prec_increment, NOT_FIXED_DEC);
+ max_length= MY_MIN(args[0]->max_length + prec_increment, float_length(decimals));
+ }
+}
+
+
+Item *Item_sum_avg::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_avg(thd, this);
+}
+
+
+Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table,
+ uint convert_blob_len)
+{
+ Field *field;
+ if (group)
+ {
+ /*
+ We must store both value and counter in the temporary table in one field.
+ The easiest way is to do this is to store both value in a string
+ and unpack on access.
+ */
+ field= new Field_string(((hybrid_type == DECIMAL_RESULT) ?
+ dec_bin_size : sizeof(double)) + sizeof(longlong),
+ 0, name, &my_charset_bin);
+ }
+ else if (hybrid_type == DECIMAL_RESULT)
+ field= Field_new_decimal::create_from_item(this);
+ else
+ field= new Field_double(max_length, maybe_null, name, decimals, TRUE);
+ if (field)
+ field->init(table);
+ return field;
+}
+
+
+void Item_sum_avg::clear()
+{
+ Item_sum_sum::clear();
+ count=0;
+}
+
+
+bool Item_sum_avg::add()
+{
+ if (Item_sum_sum::add())
+ return TRUE;
+ if (!aggr->arg_is_null(true))
+ count++;
+ return FALSE;
+}
+
+double Item_sum_avg::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (aggr)
+ aggr->endup();
+ if (!count)
+ {
+ null_value=1;
+ return 0.0;
+ }
+ return Item_sum_sum::val_real() / ulonglong2double(count);
+}
+
+
+my_decimal *Item_sum_avg::val_decimal(my_decimal *val)
+{
+ my_decimal cnt;
+ const my_decimal *sum_dec;
+ DBUG_ASSERT(fixed == 1);
+ if (aggr)
+ aggr->endup();
+ if (!count)
+ {
+ null_value=1;
+ return NULL;
+ }
+
+ /*
+ For non-DECIMAL hybrid_type the division will be done in
+ Item_sum_avg::val_real().
+ */
+ if (hybrid_type != DECIMAL_RESULT)
+ return val_decimal_from_real(val);
+
+ sum_dec= dec_buffs + curr_dec_buff;
+ int2my_decimal(E_DEC_FATAL_ERROR, count, 0, &cnt);
+ my_decimal_div(E_DEC_FATAL_ERROR, val, sum_dec, &cnt, prec_increment);
+ return val;
+}
+
+
+String *Item_sum_avg::val_str(String *str)
+{
+ if (aggr)
+ aggr->endup();
+ if (hybrid_type == DECIMAL_RESULT)
+ return val_string_from_decimal(str);
+ return val_string_from_real(str);
+}
+
+
+/*
+ Standard deviation
+*/
+
+double Item_sum_std::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ double nr= Item_sum_variance::val_real();
+ DBUG_ASSERT(nr >= 0.0);
+ return sqrt(nr);
+}
+
+Item *Item_sum_std::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_std(thd, this);
+}
+
+
+/*
+ Variance
+*/
+
+
+/**
+ Variance implementation for floating-point implementations, without
+ catastrophic cancellation, from Knuth's _TAoCP_, 3rd ed, volume 2, pg232.
+ This alters the value at m, s, and increments count.
+*/
+
+/*
+ These two functions are used by the Item_sum_variance and the
+ Item_variance_field classes, which are unrelated, and each need to calculate
+ variance. The difference between the two classes is that the first is used
+ for a mundane SELECT, while the latter is used in a GROUPing SELECT.
+*/
+static void variance_fp_recurrence_next(double *m, double *s, ulonglong *count, double nr)
+{
+ *count += 1;
+
+ if (*count == 1)
+ {
+ *m= nr;
+ *s= 0;
+ }
+ else
+ {
+ double m_kminusone= *m;
+ *m= m_kminusone + (nr - m_kminusone) / (double) *count;
+ *s= *s + (nr - m_kminusone) * (nr - *m);
+ }
+}
+
+
+static double variance_fp_recurrence_result(double s, ulonglong count, bool is_sample_variance)
+{
+ if (count == 1)
+ return 0.0;
+
+ if (is_sample_variance)
+ return s / (count - 1);
+
+ /* else, is a population variance */
+ return s / count;
+}
+
+
+Item_sum_variance::Item_sum_variance(THD *thd, Item_sum_variance *item):
+ Item_sum_num(thd, item), hybrid_type(item->hybrid_type),
+ count(item->count), sample(item->sample),
+ prec_increment(item->prec_increment)
+{
+ recurrence_m= item->recurrence_m;
+ recurrence_s= item->recurrence_s;
+}
+
+
+void Item_sum_variance::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_sum_variance::fix_length_and_dec");
+ maybe_null= null_value= 1;
+ prec_increment= current_thd->variables.div_precincrement;
+
+ /*
+ According to the SQL2003 standard (Part 2, Foundations; sec 10.9,
+ aggregate function; paragraph 7h of Syntax Rules), "the declared
+ type of the result is an implementation-defined aproximate numeric
+ type.
+ */
+ hybrid_type= REAL_RESULT;
+
+ switch (args[0]->result_type()) {
+ case REAL_RESULT:
+ case STRING_RESULT:
+ decimals= MY_MIN(args[0]->decimals + 4, NOT_FIXED_DEC);
+ break;
+ case INT_RESULT:
+ case DECIMAL_RESULT:
+ {
+ int precision= args[0]->decimal_precision()*2 + prec_increment;
+ decimals= MY_MIN(args[0]->decimals + prec_increment, DECIMAL_MAX_SCALE);
+ max_length= my_decimal_precision_to_length_no_truncation(precision,
+ decimals,
+ unsigned_flag);
+
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+ DBUG_PRINT("info", ("Type: REAL_RESULT (%d, %d)", max_length, (int)decimals));
+ DBUG_VOID_RETURN;
+}
+
+
+Item *Item_sum_variance::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_variance(thd, this);
+}
+
+
+/**
+ Create a new field to match the type of value we're expected to yield.
+ If we're grouping, then we need some space to serialize variables into, to
+ pass around.
+*/
+Field *Item_sum_variance::create_tmp_field(bool group, TABLE *table,
+ uint convert_blob_len)
+{
+ Field *field;
+ if (group)
+ {
+ /*
+ We must store both value and counter in the temporary table in one field.
+ The easiest way is to do this is to store both value in a string
+ and unpack on access.
+ */
+ field= new Field_string(sizeof(double)*2 + sizeof(longlong), 0, name, &my_charset_bin);
+ }
+ else
+ field= new Field_double(max_length, maybe_null, name, decimals, TRUE);
+
+ if (field != NULL)
+ field->init(table);
+
+ return field;
+}
+
+
+void Item_sum_variance::clear()
+{
+ count= 0;
+}
+
+bool Item_sum_variance::add()
+{
+ /*
+ Why use a temporary variable? We don't know if it is null until we
+ evaluate it, which has the side-effect of setting null_value .
+ */
+ double nr= args[0]->val_real();
+
+ if (!args[0]->null_value)
+ variance_fp_recurrence_next(&recurrence_m, &recurrence_s, &count, nr);
+ return 0;
+}
+
+double Item_sum_variance::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+
+ /*
+ 'sample' is a 1/0 boolean value. If it is 1/true, id est this is a sample
+ variance call, then we should set nullness when the count of the items
+ is one or zero. If it's zero, i.e. a population variance, then we only
+ set nullness when the count is zero.
+
+ Another way to read it is that 'sample' is the numerical threshhold, at and
+ below which a 'count' number of items is called NULL.
+ */
+ DBUG_ASSERT((sample == 0) || (sample == 1));
+ if (count <= sample)
+ {
+ null_value=1;
+ return 0.0;
+ }
+
+ null_value=0;
+ return variance_fp_recurrence_result(recurrence_s, count, sample);
+}
+
+
+my_decimal *Item_sum_variance::val_decimal(my_decimal *dec_buf)
+{
+ DBUG_ASSERT(fixed == 1);
+ return val_decimal_from_real(dec_buf);
+}
+
+
+void Item_sum_variance::reset_field()
+{
+ double nr;
+ uchar *res= result_field->ptr;
+
+ nr= args[0]->val_real(); /* sets null_value as side-effect */
+
+ if (args[0]->null_value)
+ bzero(res,sizeof(double)*2+sizeof(longlong));
+ else
+ {
+ /* Serialize format is (double)m, (double)s, (longlong)count */
+ ulonglong tmp_count;
+ double tmp_s;
+ float8store(res, nr); /* recurrence variable m */
+ tmp_s= 0.0;
+ float8store(res + sizeof(double), tmp_s);
+ tmp_count= 1;
+ int8store(res + sizeof(double)*2, tmp_count);
+ }
+}
+
+
+void Item_sum_variance::update_field()
+{
+ ulonglong field_count;
+ uchar *res=result_field->ptr;
+
+ double nr= args[0]->val_real(); /* sets null_value as side-effect */
+
+ if (args[0]->null_value)
+ return;
+
+ /* Serialize format is (double)m, (double)s, (longlong)count */
+ double field_recurrence_m, field_recurrence_s;
+ float8get(field_recurrence_m, res);
+ float8get(field_recurrence_s, res + sizeof(double));
+ field_count=sint8korr(res+sizeof(double)*2);
+
+ variance_fp_recurrence_next(&field_recurrence_m, &field_recurrence_s, &field_count, nr);
+
+ float8store(res, field_recurrence_m);
+ float8store(res + sizeof(double), field_recurrence_s);
+ res+= sizeof(double)*2;
+ int8store(res,field_count);
+}
+
+
+/* min & max */
+
+void Item_sum_hybrid::clear()
+{
+ value->clear();
+ null_value= 1;
+}
+
+double Item_sum_hybrid::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (null_value)
+ return 0.0;
+ double retval= value->val_real();
+ if ((null_value= value->null_value))
+ DBUG_ASSERT(retval == 0.0);
+ return retval;
+}
+
+longlong Item_sum_hybrid::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ if (null_value)
+ return 0;
+ longlong retval= value->val_int();
+ if ((null_value= value->null_value))
+ DBUG_ASSERT(retval == 0);
+ return retval;
+}
+
+
+my_decimal *Item_sum_hybrid::val_decimal(my_decimal *val)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (null_value)
+ return 0;
+ my_decimal *retval= value->val_decimal(val);
+ if ((null_value= value->null_value))
+ DBUG_ASSERT(retval == NULL);
+ return retval;
+}
+
+
+String *
+Item_sum_hybrid::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (null_value)
+ return 0;
+ String *retval= value->val_str(str);
+ if ((null_value= value->null_value))
+ DBUG_ASSERT(retval == NULL);
+ return retval;
+}
+
+
+void Item_sum_hybrid::cleanup()
+{
+ DBUG_ENTER("Item_sum_hybrid::cleanup");
+ Item_sum::cleanup();
+ forced_const= FALSE;
+ if (cmp)
+ delete cmp;
+ cmp= 0;
+ /*
+ by default it is TRUE to avoid TRUE reporting by
+ Item_func_not_all/Item_func_nop_all if this item was never called.
+
+ no_rows_in_result() set it to FALSE if was not results found.
+ If some results found it will be left unchanged.
+ */
+ was_values= TRUE;
+ DBUG_VOID_RETURN;
+}
+
+void Item_sum_hybrid::no_rows_in_result()
+{
+ /* We may be called here twice in case of ref field in function */
+ if (was_values)
+ {
+ was_values= FALSE;
+ was_null_value= value->null_value;
+ clear();
+ }
+}
+
+void Item_sum_hybrid::restore_to_before_no_rows_in_result()
+{
+ if (!was_values)
+ {
+ was_values= TRUE;
+ null_value= value->null_value= was_null_value;
+ }
+}
+
+
+Item *Item_sum_min::copy_or_same(THD* thd)
+{
+ Item_sum_min *item= new (thd->mem_root) Item_sum_min(thd, this);
+ item->setup_hybrid(args[0], value);
+ return item;
+}
+
+
+bool Item_sum_min::add()
+{
+ /* args[0] < value */
+ arg_cache->cache_value();
+ if (!arg_cache->null_value &&
+ (null_value || cmp->compare() < 0))
+ {
+ value->store(arg_cache);
+ value->cache_value();
+ null_value= 0;
+ }
+ return 0;
+}
+
+
+Item *Item_sum_max::copy_or_same(THD* thd)
+{
+ Item_sum_max *item= new (thd->mem_root) Item_sum_max(thd, this);
+ item->setup_hybrid(args[0], value);
+ return item;
+}
+
+
+bool Item_sum_max::add()
+{
+ /* args[0] > value */
+ arg_cache->cache_value();
+ if (!arg_cache->null_value &&
+ (null_value || cmp->compare() > 0))
+ {
+ value->store(arg_cache);
+ value->cache_value();
+ null_value= 0;
+ }
+ return 0;
+}
+
+
+/* bit_or and bit_and */
+
+longlong Item_sum_bit::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ return (longlong) bits;
+}
+
+
+void Item_sum_bit::clear()
+{
+ bits= reset_bits;
+}
+
+Item *Item_sum_or::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_or(thd, this);
+}
+
+
+bool Item_sum_or::add()
+{
+ ulonglong value= (ulonglong) args[0]->val_int();
+ if (!args[0]->null_value)
+ bits|=value;
+ return 0;
+}
+
+Item *Item_sum_xor::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_xor(thd, this);
+}
+
+
+bool Item_sum_xor::add()
+{
+ ulonglong value= (ulonglong) args[0]->val_int();
+ if (!args[0]->null_value)
+ bits^=value;
+ return 0;
+}
+
+Item *Item_sum_and::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_and(thd, this);
+}
+
+
+bool Item_sum_and::add()
+{
+ ulonglong value= (ulonglong) args[0]->val_int();
+ if (!args[0]->null_value)
+ bits&=value;
+ return 0;
+}
+
+/************************************************************************
+** reset result of a Item_sum with is saved in a tmp_table
+*************************************************************************/
+
+void Item_sum_num::reset_field()
+{
+ double nr= args[0]->val_real();
+ uchar *res=result_field->ptr;
+
+ if (maybe_null)
+ {
+ if (args[0]->null_value)
+ {
+ nr=0.0;
+ result_field->set_null();
+ }
+ else
+ result_field->set_notnull();
+ }
+ float8store(res,nr);
+}
+
+
+void Item_sum_hybrid::reset_field()
+{
+ switch(hybrid_type) {
+ case STRING_RESULT:
+ {
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff),result_field->charset()),*res;
+
+ res=args[0]->val_str(&tmp);
+ if (args[0]->null_value)
+ {
+ result_field->set_null();
+ result_field->reset();
+ }
+ else
+ {
+ result_field->set_notnull();
+ result_field->store(res->ptr(),res->length(),tmp.charset());
+ }
+ break;
+ }
+ case INT_RESULT:
+ {
+ longlong nr=args[0]->val_int();
+
+ if (maybe_null)
+ {
+ if (args[0]->null_value)
+ {
+ nr=0;
+ result_field->set_null();
+ }
+ else
+ result_field->set_notnull();
+ }
+ result_field->store(nr, unsigned_flag);
+ break;
+ }
+ case REAL_RESULT:
+ {
+ double nr= args[0]->val_real();
+
+ if (maybe_null)
+ {
+ if (args[0]->null_value)
+ {
+ nr=0.0;
+ result_field->set_null();
+ }
+ else
+ result_field->set_notnull();
+ }
+ result_field->store(nr);
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ my_decimal value_buff, *arg_dec= args[0]->val_decimal(&value_buff);
+
+ if (maybe_null)
+ {
+ if (args[0]->null_value)
+ result_field->set_null();
+ else
+ result_field->set_notnull();
+ }
+ /*
+ We must store zero in the field as we will use the field value in
+ add()
+ */
+ if (!arg_dec) // Null
+ arg_dec= &decimal_zero;
+ result_field->store_decimal(arg_dec);
+ break;
+ }
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ DBUG_ASSERT(0);
+ }
+}
+
+
+void Item_sum_sum::reset_field()
+{
+ DBUG_ASSERT (aggr->Aggrtype() != Aggregator::DISTINCT_AGGREGATOR);
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ my_decimal value, *arg_val= args[0]->val_decimal(&value);
+ if (!arg_val) // Null
+ arg_val= &decimal_zero;
+ result_field->store_decimal(arg_val);
+ }
+ else
+ {
+ DBUG_ASSERT(hybrid_type == REAL_RESULT);
+ double nr= args[0]->val_real(); // Nulls also return 0
+ float8store(result_field->ptr, nr);
+ }
+ if (args[0]->null_value)
+ result_field->set_null();
+ else
+ result_field->set_notnull();
+}
+
+
+void Item_sum_count::reset_field()
+{
+ uchar *res=result_field->ptr;
+ longlong nr=0;
+ DBUG_ASSERT (aggr->Aggrtype() != Aggregator::DISTINCT_AGGREGATOR);
+
+ if (!args[0]->maybe_null || !args[0]->is_null())
+ nr=1;
+ int8store(res,nr);
+}
+
+
+void Item_sum_avg::reset_field()
+{
+ uchar *res=result_field->ptr;
+ DBUG_ASSERT (aggr->Aggrtype() != Aggregator::DISTINCT_AGGREGATOR);
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ longlong tmp;
+ my_decimal value, *arg_dec= args[0]->val_decimal(&value);
+ if (args[0]->null_value)
+ {
+ arg_dec= &decimal_zero;
+ tmp= 0;
+ }
+ else
+ tmp= 1;
+ my_decimal2binary(E_DEC_FATAL_ERROR, arg_dec, res, f_precision, f_scale);
+ res+= dec_bin_size;
+ int8store(res, tmp);
+ }
+ else
+ {
+ double nr= args[0]->val_real();
+
+ if (args[0]->null_value)
+ bzero(res,sizeof(double)+sizeof(longlong));
+ else
+ {
+ longlong tmp= 1;
+ float8store(res,nr);
+ res+=sizeof(double);
+ int8store(res,tmp);
+ }
+ }
+}
+
+
+void Item_sum_bit::reset_field()
+{
+ reset_and_add();
+ int8store(result_field->ptr, bits);
+}
+
+void Item_sum_bit::update_field()
+{
+ uchar *res=result_field->ptr;
+ bits= uint8korr(res);
+ add();
+ int8store(res, bits);
+}
+
+
+/**
+ calc next value and merge it with field_value.
+*/
+
+void Item_sum_sum::update_field()
+{
+ DBUG_ASSERT (aggr->Aggrtype() != Aggregator::DISTINCT_AGGREGATOR);
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ my_decimal value, *arg_val= args[0]->val_decimal(&value);
+ if (!args[0]->null_value)
+ {
+ if (!result_field->is_null())
+ {
+ my_decimal field_value,
+ *field_val= result_field->val_decimal(&field_value);
+ my_decimal_add(E_DEC_FATAL_ERROR, dec_buffs, arg_val, field_val);
+ result_field->store_decimal(dec_buffs);
+ }
+ else
+ {
+ result_field->store_decimal(arg_val);
+ result_field->set_notnull();
+ }
+ }
+ }
+ else
+ {
+ double old_nr,nr;
+ uchar *res=result_field->ptr;
+
+ float8get(old_nr,res);
+ nr= args[0]->val_real();
+ if (!args[0]->null_value)
+ {
+ old_nr+=nr;
+ result_field->set_notnull();
+ }
+ float8store(res,old_nr);
+ }
+}
+
+
+void Item_sum_count::update_field()
+{
+ longlong nr;
+ uchar *res=result_field->ptr;
+
+ nr=sint8korr(res);
+ if (!args[0]->maybe_null || !args[0]->is_null())
+ nr++;
+ int8store(res,nr);
+}
+
+
+void Item_sum_avg::update_field()
+{
+ longlong field_count;
+ uchar *res=result_field->ptr;
+
+ DBUG_ASSERT (aggr->Aggrtype() != Aggregator::DISTINCT_AGGREGATOR);
+
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ my_decimal value, *arg_val= args[0]->val_decimal(&value);
+ if (!args[0]->null_value)
+ {
+ binary2my_decimal(E_DEC_FATAL_ERROR, res,
+ dec_buffs + 1, f_precision, f_scale);
+ field_count= sint8korr(res + dec_bin_size);
+ my_decimal_add(E_DEC_FATAL_ERROR, dec_buffs, arg_val, dec_buffs + 1);
+ my_decimal2binary(E_DEC_FATAL_ERROR, dec_buffs,
+ res, f_precision, f_scale);
+ res+= dec_bin_size;
+ field_count++;
+ int8store(res, field_count);
+ }
+ }
+ else
+ {
+ double nr;
+
+ nr= args[0]->val_real();
+ if (!args[0]->null_value)
+ {
+ double old_nr;
+ float8get(old_nr, res);
+ field_count= sint8korr(res + sizeof(double));
+ old_nr+= nr;
+ float8store(res,old_nr);
+ res+= sizeof(double);
+ field_count++;
+ int8store(res, field_count);
+ }
+ }
+}
+
+
+void Item_sum_hybrid::update_field()
+{
+ switch (hybrid_type) {
+ case STRING_RESULT:
+ min_max_update_str_field();
+ break;
+ case INT_RESULT:
+ min_max_update_int_field();
+ break;
+ case DECIMAL_RESULT:
+ min_max_update_decimal_field();
+ break;
+ default:
+ min_max_update_real_field();
+ }
+}
+
+
+void
+Item_sum_hybrid::min_max_update_str_field()
+{
+ DBUG_ASSERT(cmp);
+ String *res_str=args[0]->val_str(&cmp->value1);
+
+ if (!args[0]->null_value)
+ {
+ result_field->val_str(&cmp->value2);
+
+ if (result_field->is_null() ||
+ (cmp_sign * sortcmp(res_str,&cmp->value2,collation.collation)) < 0)
+ result_field->store(res_str->ptr(),res_str->length(),res_str->charset());
+ result_field->set_notnull();
+ }
+}
+
+
+void
+Item_sum_hybrid::min_max_update_real_field()
+{
+ double nr,old_nr;
+
+ old_nr=result_field->val_real();
+ nr= args[0]->val_real();
+ if (!args[0]->null_value)
+ {
+ if (result_field->is_null(0) ||
+ (cmp_sign > 0 ? old_nr > nr : old_nr < nr))
+ old_nr=nr;
+ result_field->set_notnull();
+ }
+ else if (result_field->is_null(0))
+ result_field->set_null();
+ result_field->store(old_nr);
+}
+
+
+void
+Item_sum_hybrid::min_max_update_int_field()
+{
+ longlong nr,old_nr;
+
+ old_nr=result_field->val_int();
+ nr=args[0]->val_int();
+ if (!args[0]->null_value)
+ {
+ if (result_field->is_null(0))
+ old_nr=nr;
+ else
+ {
+ bool res=(unsigned_flag ?
+ (ulonglong) old_nr > (ulonglong) nr :
+ old_nr > nr);
+ /* (cmp_sign > 0 && res) || (!(cmp_sign > 0) && !res) */
+ if ((cmp_sign > 0) ^ (!res))
+ old_nr=nr;
+ }
+ result_field->set_notnull();
+ }
+ else if (result_field->is_null(0))
+ result_field->set_null();
+ result_field->store(old_nr, unsigned_flag);
+}
+
+
+/**
+ @todo
+ optimize: do not get result_field in case of args[0] is NULL
+*/
+void
+Item_sum_hybrid::min_max_update_decimal_field()
+{
+ my_decimal old_val, nr_val;
+ const my_decimal *old_nr;
+ const my_decimal *nr= args[0]->val_decimal(&nr_val);
+ if (!args[0]->null_value)
+ {
+ if (result_field->is_null(0))
+ old_nr=nr;
+ else
+ {
+ old_nr= result_field->val_decimal(&old_val);
+ bool res= my_decimal_cmp(old_nr, nr) > 0;
+ /* (cmp_sign > 0 && res) || (!(cmp_sign > 0) && !res) */
+ if ((cmp_sign > 0) ^ (!res))
+ old_nr=nr;
+ }
+ result_field->set_notnull();
+ result_field->store_decimal(old_nr);
+ }
+ else if (result_field->is_null(0))
+ result_field->set_null();
+}
+
+
+Item_avg_field::Item_avg_field(Item_result res_type, Item_sum_avg *item)
+{
+ name=item->name;
+ decimals=item->decimals;
+ max_length= item->max_length;
+ unsigned_flag= item->unsigned_flag;
+ field=item->result_field;
+ maybe_null=1;
+ hybrid_type= res_type;
+ prec_increment= item->prec_increment;
+ if (hybrid_type == DECIMAL_RESULT)
+ {
+ f_scale= item->f_scale;
+ f_precision= item->f_precision;
+ dec_bin_size= item->dec_bin_size;
+ }
+}
+
+double Item_avg_field::val_real()
+{
+ // fix_fields() never calls for this Item
+ double nr;
+ longlong count;
+ uchar *res;
+
+ if (hybrid_type == DECIMAL_RESULT)
+ return val_real_from_decimal();
+
+ float8get(nr,field->ptr);
+ res= (field->ptr+sizeof(double));
+ count= sint8korr(res);
+
+ if ((null_value= !count))
+ return 0.0;
+ return nr/(double) count;
+}
+
+
+longlong Item_avg_field::val_int()
+{
+ return (longlong) rint(val_real());
+}
+
+
+my_decimal *Item_avg_field::val_decimal(my_decimal *dec_buf)
+{
+ // fix_fields() never calls for this Item
+ if (hybrid_type == REAL_RESULT)
+ return val_decimal_from_real(dec_buf);
+
+ longlong count= sint8korr(field->ptr + dec_bin_size);
+ if ((null_value= !count))
+ return 0;
+
+ my_decimal dec_count, dec_field;
+ binary2my_decimal(E_DEC_FATAL_ERROR,
+ field->ptr, &dec_field, f_precision, f_scale);
+ int2my_decimal(E_DEC_FATAL_ERROR, count, 0, &dec_count);
+ my_decimal_div(E_DEC_FATAL_ERROR, dec_buf,
+ &dec_field, &dec_count, prec_increment);
+ return dec_buf;
+}
+
+
+String *Item_avg_field::val_str(String *str)
+{
+ // fix_fields() never calls for this Item
+ if (hybrid_type == DECIMAL_RESULT)
+ return val_string_from_decimal(str);
+ return val_string_from_real(str);
+}
+
+
+Item_std_field::Item_std_field(Item_sum_std *item)
+ : Item_variance_field(item)
+{
+}
+
+
+double Item_std_field::val_real()
+{
+ double nr;
+ // fix_fields() never calls for this Item
+ nr= Item_variance_field::val_real();
+ DBUG_ASSERT(nr >= 0.0);
+ return sqrt(nr);
+}
+
+
+my_decimal *Item_std_field::val_decimal(my_decimal *dec_buf)
+{
+ /*
+ We can't call val_decimal_from_real() for DECIMAL_RESULT as
+ Item_variance_field::val_real() would cause an infinite loop
+ */
+ my_decimal tmp_dec, *dec;
+ double nr;
+ if (hybrid_type == REAL_RESULT)
+ return val_decimal_from_real(dec_buf);
+
+ dec= Item_variance_field::val_decimal(dec_buf);
+ if (!dec)
+ return 0;
+ my_decimal2double(E_DEC_FATAL_ERROR, dec, &nr);
+ DBUG_ASSERT(nr >= 0.0);
+ nr= sqrt(nr);
+ double2my_decimal(E_DEC_FATAL_ERROR, nr, &tmp_dec);
+ my_decimal_round(E_DEC_FATAL_ERROR, &tmp_dec, decimals, FALSE, dec_buf);
+ return dec_buf;
+}
+
+
+Item_variance_field::Item_variance_field(Item_sum_variance *item)
+{
+ name=item->name;
+ decimals=item->decimals;
+ max_length=item->max_length;
+ unsigned_flag= item->unsigned_flag;
+ field=item->result_field;
+ maybe_null=1;
+ sample= item->sample;
+ prec_increment= item->prec_increment;
+ if ((hybrid_type= item->hybrid_type) == DECIMAL_RESULT)
+ {
+ f_scale0= item->f_scale0;
+ f_precision0= item->f_precision0;
+ dec_bin_size0= item->dec_bin_size0;
+ f_scale1= item->f_scale1;
+ f_precision1= item->f_precision1;
+ dec_bin_size1= item->dec_bin_size1;
+ }
+}
+
+
+double Item_variance_field::val_real()
+{
+ // fix_fields() never calls for this Item
+ if (hybrid_type == DECIMAL_RESULT)
+ return val_real_from_decimal();
+
+ double recurrence_s;
+ ulonglong count;
+ float8get(recurrence_s, (field->ptr + sizeof(double)));
+ count=sint8korr(field->ptr+sizeof(double)*2);
+
+ if ((null_value= (count <= sample)))
+ return 0.0;
+
+ return variance_fp_recurrence_result(recurrence_s, count, sample);
+}
+
+
+/****************************************************************************
+** Functions to handle dynamic loadable aggregates
+** Original source by: Alexis Mikhailov <root@medinf.chuvashia.su>
+** Adapted for UDAs by: Andreas F. Bobak <bobak@relog.ch>.
+** Rewritten by: Monty.
+****************************************************************************/
+
+#ifdef HAVE_DLOPEN
+
+void Item_udf_sum::clear()
+{
+ DBUG_ENTER("Item_udf_sum::clear");
+ udf.clear();
+ DBUG_VOID_RETURN;
+}
+
+bool Item_udf_sum::add()
+{
+ my_bool tmp_null_value;
+ DBUG_ENTER("Item_udf_sum::add");
+ udf.add(&tmp_null_value);
+ null_value= tmp_null_value;
+ DBUG_RETURN(0);
+}
+
+void Item_udf_sum::cleanup()
+{
+ /*
+ udf_handler::cleanup() nicely handles case when we have not
+ original item but one created by copy_or_same() method.
+ */
+ udf.cleanup();
+ Item_sum::cleanup();
+}
+
+
+void Item_udf_sum::print(String *str, enum_query_type query_type)
+{
+ str->append(func_name());
+ str->append('(');
+ for (uint i=0 ; i < arg_count ; i++)
+ {
+ if (i)
+ str->append(',');
+ args[i]->print(str, query_type);
+ }
+ str->append(')');
+}
+
+
+Item *Item_sum_udf_float::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_udf_float(thd, this);
+}
+
+double Item_sum_udf_float::val_real()
+{
+ my_bool tmp_null_value;
+ double res;
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_sum_udf_float::val");
+ DBUG_PRINT("info",("result_type: %d arg_count: %d",
+ args[0]->result_type(), arg_count));
+ res= udf.val(&tmp_null_value);
+ null_value= tmp_null_value;
+ DBUG_RETURN(res);
+}
+
+
+String *Item_sum_udf_float::val_str(String *str)
+{
+ return val_string_from_real(str);
+}
+
+
+my_decimal *Item_sum_udf_float::val_decimal(my_decimal *dec)
+{
+ return val_decimal_from_real(dec);
+}
+
+
+String *Item_sum_udf_decimal::val_str(String *str)
+{
+ return val_string_from_decimal(str);
+}
+
+
+double Item_sum_udf_decimal::val_real()
+{
+ return val_real_from_decimal();
+}
+
+
+longlong Item_sum_udf_decimal::val_int()
+{
+ return val_int_from_decimal();
+}
+
+
+my_decimal *Item_sum_udf_decimal::val_decimal(my_decimal *dec_buf)
+{
+ my_decimal *res;
+ my_bool tmp_null_value;
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_func_udf_decimal::val_decimal");
+ DBUG_PRINT("info",("result_type: %d arg_count: %d",
+ args[0]->result_type(), arg_count));
+
+ res= udf.val_decimal(&tmp_null_value, dec_buf);
+ null_value= tmp_null_value;
+ DBUG_RETURN(res);
+}
+
+
+Item *Item_sum_udf_decimal::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_udf_decimal(thd, this);
+}
+
+
+Item *Item_sum_udf_int::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_udf_int(thd, this);
+}
+
+longlong Item_sum_udf_int::val_int()
+{
+ my_bool tmp_null_value;
+ longlong res;
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_sum_udf_int::val_int");
+ DBUG_PRINT("info",("result_type: %d arg_count: %d",
+ args[0]->result_type(), arg_count));
+ res= udf.val_int(&tmp_null_value);
+ null_value= tmp_null_value;
+ DBUG_RETURN(res);
+}
+
+
+String *Item_sum_udf_int::val_str(String *str)
+{
+ return val_string_from_int(str);
+}
+
+my_decimal *Item_sum_udf_int::val_decimal(my_decimal *dec)
+{
+ return val_decimal_from_int(dec);
+}
+
+
+/** Default max_length is max argument length. */
+
+void Item_sum_udf_str::fix_length_and_dec()
+{
+ DBUG_ENTER("Item_sum_udf_str::fix_length_and_dec");
+ max_length=0;
+ for (uint i = 0; i < arg_count; i++)
+ set_if_bigger(max_length,args[i]->max_length);
+ DBUG_VOID_RETURN;
+}
+
+
+Item *Item_sum_udf_str::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_sum_udf_str(thd, this);
+}
+
+
+my_decimal *Item_sum_udf_str::val_decimal(my_decimal *dec)
+{
+ return val_decimal_from_string(dec);
+}
+
+String *Item_sum_udf_str::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ENTER("Item_sum_udf_str::str");
+ String *res=udf.val_str(str,&str_value);
+ null_value = !res;
+ DBUG_RETURN(res);
+}
+
+#endif /* HAVE_DLOPEN */
+
+
+/*****************************************************************************
+ GROUP_CONCAT function
+
+ SQL SYNTAX:
+ GROUP_CONCAT([DISTINCT] expr,... [ORDER BY col [ASC|DESC],...]
+ [SEPARATOR str_const])
+
+ concat of values from "group by" operation
+
+ BUGS
+ Blobs doesn't work with DISTINCT or ORDER BY
+*****************************************************************************/
+
+
+
+/**
+ Compares the values for fields in expr list of GROUP_CONCAT.
+ @note
+
+ GROUP_CONCAT([DISTINCT] expr [,expr ...]
+ [ORDER BY {unsigned_integer | col_name | expr}
+ [ASC | DESC] [,col_name ...]]
+ [SEPARATOR str_val])
+
+ @return
+ @retval -1 : key1 < key2
+ @retval 0 : key1 = key2
+ @retval 1 : key1 > key2
+*/
+
+extern "C"
+int group_concat_key_cmp_with_distinct(void* arg, const void* key1,
+ const void* key2)
+{
+ Item_func_group_concat *item_func= (Item_func_group_concat*)arg;
+
+ for (uint i= 0; i < item_func->arg_count_field; i++)
+ {
+ Item *item= item_func->args[i];
+ /*
+ If item is a const item then either get_tmp_table_field returns 0
+ or it is an item over a const table.
+ */
+ if (item->const_item())
+ continue;
+ /*
+ We have to use get_tmp_table_field() instead of
+ real_item()->get_tmp_table_field() because we want the field in
+ the temporary table, not the original field
+ */
+ Field *field= item->get_tmp_table_field();
+
+ if (!field)
+ continue;
+
+ uint offset= (field->offset(field->table->record[0]) -
+ field->table->s->null_bytes);
+ int res= field->cmp((uchar*)key1 + offset, (uchar*)key2 + offset);
+ if (res)
+ return res;
+ }
+ return 0;
+}
+
+
+/**
+ function of sort for syntax: GROUP_CONCAT(expr,... ORDER BY col,... )
+*/
+
+extern "C"
+int group_concat_key_cmp_with_order(void* arg, const void* key1,
+ const void* key2)
+{
+ Item_func_group_concat* grp_item= (Item_func_group_concat*) arg;
+ ORDER **order_item, **end;
+
+ for (order_item= grp_item->order, end=order_item+ grp_item->arg_count_order;
+ order_item < end;
+ order_item++)
+ {
+ Item *item= *(*order_item)->item;
+ /*
+ If field_item is a const item then either get_tmp_table_field returns 0
+ or it is an item over a const table.
+ */
+ if (item->const_item())
+ continue;
+ /*
+ If item is a const item then either get_tmp_table_field returns 0
+ or it is an item over a const table.
+ */
+ if (item->const_item())
+ continue;
+ /*
+ We have to use get_tmp_table_field() instead of
+ real_item()->get_tmp_table_field() because we want the field in
+ the temporary table, not the original field
+
+ Note that for the case of ROLLUP, field may point to another table
+ tham grp_item->table. This is however ok as the table definitions are
+ the same.
+ */
+ Field *field= item->get_tmp_table_field();
+ if (!field)
+ continue;
+
+ uint offset= (field->offset(field->table->record[0]) -
+ field->table->s->null_bytes);
+ int res= field->cmp((uchar*)key1 + offset, (uchar*)key2 + offset);
+ if (res)
+ return (*order_item)->asc ? res : -res;
+ }
+ /*
+ We can't return 0 because in that case the tree class would remove this
+ item as double value. This would cause problems for case-changes and
+ if the returned values are not the same we do the sort on.
+ */
+ return 1;
+}
+
+
+/**
+ Append data from current leaf to item->result.
+*/
+
+extern "C"
+int dump_leaf_key(void* key_arg, element_count count __attribute__((unused)),
+ void* item_arg)
+{
+ Item_func_group_concat *item= (Item_func_group_concat *) item_arg;
+ TABLE *table= item->table;
+ uint max_length= table->in_use->variables.group_concat_max_len;
+ String tmp((char *)table->record[1], table->s->reclength,
+ default_charset_info);
+ String tmp2;
+ uchar *key= (uchar *) key_arg;
+ String *result= &item->result;
+ Item **arg= item->args, **arg_end= item->args + item->arg_count_field;
+ uint old_length= result->length();
+
+ if (item->no_appended)
+ item->no_appended= FALSE;
+ else
+ result->append(*item->separator);
+
+ tmp.length(0);
+
+ for (; arg < arg_end; arg++)
+ {
+ String *res;
+ /*
+ We have to use get_tmp_table_field() instead of
+ real_item()->get_tmp_table_field() because we want the field in
+ the temporary table, not the original field
+ We also can't use table->field array to access the fields
+ because it contains both order and arg list fields.
+ */
+ if ((*arg)->const_item())
+ res= (*arg)->val_str(&tmp);
+ else
+ {
+ Field *field= (*arg)->get_tmp_table_field();
+ if (field)
+ {
+ uint offset= (field->offset(field->table->record[0]) -
+ table->s->null_bytes);
+ DBUG_ASSERT(offset < table->s->reclength);
+ res= field->val_str(&tmp, key + offset);
+ }
+ else
+ res= (*arg)->val_str(&tmp);
+ }
+ if (res)
+ result->append(*res);
+ }
+
+ item->row_count++;
+
+ /* stop if length of result more than max_length */
+ if (result->length() > max_length)
+ {
+ int well_formed_error;
+ CHARSET_INFO *cs= item->collation.collation;
+ const char *ptr= result->ptr();
+ uint add_length;
+ /*
+ It's ok to use item->result.length() as the fourth argument
+ as this is never used to limit the length of the data.
+ Cut is done with the third argument.
+ */
+ add_length= cs->cset->well_formed_len(cs,
+ ptr + old_length,
+ ptr + max_length,
+ result->length(),
+ &well_formed_error);
+ result->length(old_length + add_length);
+ item->warning_for_row= TRUE;
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_CUT_VALUE_GROUP_CONCAT, ER(ER_CUT_VALUE_GROUP_CONCAT),
+ item->row_count);
+
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Constructor of Item_func_group_concat.
+
+ @param distinct_arg distinct
+ @param select_list list of expression for show values
+ @param order_list list of sort columns
+ @param separator_arg string value of separator.
+*/
+
+Item_func_group_concat::
+Item_func_group_concat(Name_resolution_context *context_arg,
+ bool distinct_arg, List<Item> *select_list,
+ const SQL_I_List<ORDER> &order_list,
+ String *separator_arg)
+ :tmp_table_param(0), separator(separator_arg), tree(0),
+ unique_filter(NULL), table(0),
+ order(0), context(context_arg),
+ arg_count_order(order_list.elements),
+ arg_count_field(select_list->elements),
+ row_count(0),
+ distinct(distinct_arg),
+ warning_for_row(FALSE),
+ force_copy_fields(0), original(0)
+{
+ Item *item_select;
+ Item **arg_ptr;
+
+ quick_group= FALSE;
+ arg_count= arg_count_field + arg_count_order;
+
+ /*
+ We need to allocate:
+ args - arg_count_field+arg_count_order
+ (for possible order items in temporary tables)
+ order - arg_count_order
+ */
+ if (!(args= (Item**) sql_alloc(sizeof(Item*) * arg_count * 2 +
+ sizeof(ORDER*)*arg_count_order)))
+ return;
+
+ order= (ORDER**)(args + arg_count);
+
+ /* fill args items of show and sort */
+ List_iterator_fast<Item> li(*select_list);
+
+ for (arg_ptr=args ; (item_select= li++) ; arg_ptr++)
+ *arg_ptr= item_select;
+
+ if (arg_count_order)
+ {
+ ORDER **order_ptr= order;
+ for (ORDER *order_item= order_list.first;
+ order_item != NULL;
+ order_item= order_item->next)
+ {
+ (*order_ptr++)= order_item;
+ *arg_ptr= *order_item->item;
+ order_item->item= arg_ptr++;
+ }
+ }
+
+ /* orig_args is only used for print() */
+ orig_args= (Item**) (order + arg_count_order);
+ memcpy(orig_args, args, sizeof(Item*) * arg_count);
+}
+
+
+Item_func_group_concat::Item_func_group_concat(THD *thd,
+ Item_func_group_concat *item)
+ :Item_sum(thd, item),
+ tmp_table_param(item->tmp_table_param),
+ separator(item->separator),
+ tree(item->tree),
+ unique_filter(item->unique_filter),
+ table(item->table),
+ context(item->context),
+ arg_count_order(item->arg_count_order),
+ arg_count_field(item->arg_count_field),
+ row_count(item->row_count),
+ distinct(item->distinct),
+ warning_for_row(item->warning_for_row),
+ always_null(item->always_null),
+ force_copy_fields(item->force_copy_fields),
+ original(item)
+{
+ quick_group= item->quick_group;
+ result.set_charset(collation.collation);
+
+ /*
+ Since the ORDER structures pointed to by the elements of the 'order' array
+ may be modified in find_order_in_list() called from
+ Item_func_group_concat::setup(), create a copy of those structures so that
+ such modifications done in this object would not have any effect on the
+ object being copied.
+ */
+ ORDER *tmp;
+ if (!(tmp= (ORDER *) thd->alloc(sizeof(ORDER *) * arg_count_order +
+ sizeof(ORDER) * arg_count_order)))
+ return;
+ order= (ORDER **)(tmp + arg_count_order);
+ for (uint i= 0; i < arg_count_order; i++, tmp++)
+ {
+ /*
+ Compiler generated copy constructor is used to
+ to copy all the members of ORDER struct.
+ It's also necessary to update ORDER::next pointer
+ so that it points to new ORDER element.
+ */
+ new (tmp) st_order(*(item->order[i]));
+ tmp->next= (i + 1 == arg_count_order) ? NULL : (tmp + 1);
+ order[i]= tmp;
+ }
+}
+
+
+void Item_func_group_concat::cleanup()
+{
+ DBUG_ENTER("Item_func_group_concat::cleanup");
+ Item_sum::cleanup();
+
+ /*
+ Free table and tree if they belong to this item (if item have not pointer
+ to original item from which was made copy => it own its objects )
+ */
+ if (!original)
+ {
+ delete tmp_table_param;
+ tmp_table_param= 0;
+ if (table)
+ {
+ THD *thd= table->in_use;
+ free_tmp_table(thd, table);
+ table= 0;
+ if (tree)
+ {
+ delete_tree(tree);
+ tree= 0;
+ }
+ if (unique_filter)
+ {
+ delete unique_filter;
+ unique_filter= NULL;
+ }
+ }
+ DBUG_ASSERT(tree == 0);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+Field *Item_func_group_concat::make_string_field(TABLE *table)
+{
+ Field *field;
+ DBUG_ASSERT(collation.collation);
+ if (too_big_for_varchar())
+ field= new Field_blob(max_length,
+ maybe_null, name, collation.collation, TRUE);
+ else
+ field= new Field_varstring(max_length,
+ maybe_null, name, table->s, collation.collation);
+
+ if (field)
+ field->init(table);
+ return field;
+}
+
+
+Item *Item_func_group_concat::copy_or_same(THD* thd)
+{
+ return new (thd->mem_root) Item_func_group_concat(thd, this);
+}
+
+
+void Item_func_group_concat::clear()
+{
+ result.length(0);
+ result.copy();
+ null_value= TRUE;
+ warning_for_row= FALSE;
+ no_appended= TRUE;
+ if (tree)
+ reset_tree(tree);
+ if (unique_filter)
+ unique_filter->reset();
+ /* No need to reset the table as we never call write_row */
+}
+
+
+bool Item_func_group_concat::add()
+{
+ if (always_null)
+ return 0;
+ copy_fields(tmp_table_param);
+ if (copy_funcs(tmp_table_param->items_to_copy, table->in_use))
+ return TRUE;
+
+ for (uint i= 0; i < arg_count_field; i++)
+ {
+ Item *show_item= args[i];
+ if (show_item->const_item())
+ continue;
+
+ Field *field= show_item->get_tmp_table_field();
+ if (field && field->is_null_in_record((const uchar*) table->record[0]))
+ return 0; // Skip row if it contains null
+ }
+
+ null_value= FALSE;
+ bool row_eligible= TRUE;
+
+ if (distinct)
+ {
+ /* Filter out duplicate rows. */
+ uint count= unique_filter->elements_in_tree();
+ unique_filter->unique_add(table->record[0] + table->s->null_bytes);
+ if (count == unique_filter->elements_in_tree())
+ row_eligible= FALSE;
+ }
+
+ TREE_ELEMENT *el= 0; // Only for safety
+ if (row_eligible && tree)
+ {
+ el= tree_insert(tree, table->record[0] + table->s->null_bytes, 0,
+ tree->custom_arg);
+ /* check if there was enough memory to insert the row */
+ if (!el)
+ return 1;
+ }
+ /*
+ If the row is not a duplicate (el->count == 1)
+ we can dump the row here in case of GROUP_CONCAT(DISTINCT...)
+ instead of doing tree traverse later.
+ */
+ if (row_eligible && !warning_for_row &&
+ (!tree || (el->count == 1 && distinct && !arg_count_order)))
+ dump_leaf_key(table->record[0] + table->s->null_bytes, 1, this);
+
+ return 0;
+}
+
+
+bool
+Item_func_group_concat::fix_fields(THD *thd, Item **ref)
+{
+ uint i; /* for loop variable */
+ DBUG_ASSERT(fixed == 0);
+
+ if (init_sum_func_check(thd))
+ return TRUE;
+
+ maybe_null= 1;
+
+ /*
+ Fix fields for select list and ORDER clause
+ */
+
+ for (i=0 ; i < arg_count ; i++)
+ {
+ if ((!args[i]->fixed &&
+ args[i]->fix_fields(thd, args + i)) ||
+ args[i]->check_cols(1))
+ return TRUE;
+ with_subselect|= args[i]->with_subselect;
+ }
+
+ /* skip charset aggregation for order columns */
+ if (agg_item_charsets_for_string_result(collation, func_name(),
+ args, arg_count - arg_count_order))
+ return 1;
+
+ result.set_charset(collation.collation);
+ result_field= 0;
+ null_value= 1;
+ max_length= thd->variables.group_concat_max_len
+ / collation.collation->mbminlen
+ * collation.collation->mbmaxlen;
+
+ uint32 offset;
+ if (separator->needs_conversion(separator->length(), separator->charset(),
+ collation.collation, &offset))
+ {
+ uint32 buflen= collation.collation->mbmaxlen * separator->length();
+ uint errors, conv_length;
+ char *buf;
+ String *new_separator;
+
+ if (!(buf= (char*) thd->stmt_arena->alloc(buflen)) ||
+ !(new_separator= new(thd->stmt_arena->mem_root)
+ String(buf, buflen, collation.collation)))
+ return TRUE;
+
+ conv_length= copy_and_convert(buf, buflen, collation.collation,
+ separator->ptr(), separator->length(),
+ separator->charset(), &errors);
+ new_separator->length(conv_length);
+ separator= new_separator;
+ }
+
+ if (check_sum_func(thd, ref))
+ return TRUE;
+
+ fixed= 1;
+ return FALSE;
+}
+
+
+bool Item_func_group_concat::setup(THD *thd)
+{
+ List<Item> list;
+ SELECT_LEX *select_lex= thd->lex->current_select;
+ DBUG_ENTER("Item_func_group_concat::setup");
+
+ /*
+ Currently setup() can be called twice. Please add
+ assertion here when this is fixed.
+ */
+ if (table || tree)
+ DBUG_RETURN(FALSE);
+
+ if (!(tmp_table_param= new TMP_TABLE_PARAM))
+ DBUG_RETURN(TRUE);
+
+ /* We'll convert all blobs to varchar fields in the temporary table */
+ tmp_table_param->convert_blob_length= max_length *
+ collation.collation->mbmaxlen;
+ /* Push all not constant fields to the list and create a temp table */
+ always_null= 0;
+ for (uint i= 0; i < arg_count_field; i++)
+ {
+ Item *item= args[i];
+ if (list.push_back(item))
+ DBUG_RETURN(TRUE);
+ if (item->const_item())
+ {
+ if (item->is_null())
+ {
+ always_null= 1;
+ DBUG_RETURN(FALSE);
+ }
+ }
+ }
+
+ List<Item> all_fields(list);
+ /*
+ Try to find every ORDER expression in the list of GROUP_CONCAT
+ arguments. If an expression is not found, prepend it to
+ "all_fields". The resulting field list is used as input to create
+ tmp table columns.
+ */
+ if (arg_count_order &&
+ setup_order(thd, args, context->table_list, list, all_fields, *order))
+ DBUG_RETURN(TRUE);
+
+ count_field_types(select_lex, tmp_table_param, all_fields, 0);
+ tmp_table_param->force_copy_fields= force_copy_fields;
+ DBUG_ASSERT(table == 0);
+ if (arg_count_order > 0 || distinct)
+ {
+ /*
+ Currently we have to force conversion of BLOB values to VARCHAR's
+ if we are to store them in TREE objects used for ORDER BY and
+ DISTINCT. This leads to truncation if the BLOB's size exceeds
+ Field_varstring::MAX_SIZE.
+ */
+ set_if_smaller(tmp_table_param->convert_blob_length,
+ Field_varstring::MAX_SIZE);
+
+ /*
+ Force the create_tmp_table() to convert BIT columns to INT
+ as we cannot compare two table records containg BIT fields
+ stored in the the tree used for distinct/order by.
+ Moreover we don't even save in the tree record null bits
+ where BIT fields store parts of their data.
+ */
+ List_iterator_fast<Item> li(all_fields);
+ Item *item;
+ while ((item= li++))
+ {
+ if (item->type() == Item::FIELD_ITEM &&
+ ((Item_field*) item)->field->type() == FIELD_TYPE_BIT)
+ item->marker= 4;
+ }
+ }
+
+ /*
+ We have to create a temporary table to get descriptions of fields
+ (types, sizes and so on).
+
+ Note that in the table, we first have the ORDER BY fields, then the
+ field list.
+ */
+ if (!(table= create_tmp_table(thd, tmp_table_param, all_fields,
+ (ORDER*) 0, 0, TRUE,
+ (select_lex->options |
+ thd->variables.option_bits),
+ HA_POS_ERROR, (char*) "")))
+ DBUG_RETURN(TRUE);
+ table->file->extra(HA_EXTRA_NO_ROWS);
+ table->no_rows= 1;
+
+ /*
+ Need sorting or uniqueness: init tree and choose a function to sort.
+ Don't reserve space for NULLs: if any of gconcat arguments is NULL,
+ the row is not added to the result.
+ */
+ uint tree_key_length= table->s->reclength - table->s->null_bytes;
+
+ if (arg_count_order)
+ {
+ tree= &tree_base;
+ /*
+ Create a tree for sorting. The tree is used to sort (according to the
+ syntax of this function). If there is no ORDER BY clause, we don't
+ create this tree.
+ */
+ init_tree(tree, (uint) MY_MIN(thd->variables.max_heap_table_size,
+ thd->variables.sortbuff_size/16), 0,
+ tree_key_length,
+ group_concat_key_cmp_with_order, NULL, (void*) this,
+ MYF(MY_THREAD_SPECIFIC));
+ }
+
+ if (distinct)
+ unique_filter= new Unique(group_concat_key_cmp_with_distinct,
+ (void*)this,
+ tree_key_length,
+ ram_limitation(thd));
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/* This is used by rollup to create a separate usable copy of the function */
+
+void Item_func_group_concat::make_unique()
+{
+ tmp_table_param= 0;
+ table=0;
+ original= 0;
+ force_copy_fields= 1;
+ tree= 0;
+}
+
+
+String* Item_func_group_concat::val_str(String* str)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (null_value)
+ return 0;
+ if (no_appended && tree)
+ /* Tree is used for sorting as in ORDER BY */
+ tree_walk(tree, &dump_leaf_key, this, left_root_right);
+ return &result;
+}
+
+
+void Item_func_group_concat::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("group_concat("));
+ if (distinct)
+ str->append(STRING_WITH_LEN("distinct "));
+ for (uint i= 0; i < arg_count_field; i++)
+ {
+ if (i)
+ str->append(',');
+ orig_args[i]->print(str, query_type);
+ }
+ if (arg_count_order)
+ {
+ str->append(STRING_WITH_LEN(" order by "));
+ for (uint i= 0 ; i < arg_count_order ; i++)
+ {
+ if (i)
+ str->append(',');
+ orig_args[i + arg_count_field]->print(str, query_type);
+ if (order[i]->asc)
+ str->append(STRING_WITH_LEN(" ASC"));
+ else
+ str->append(STRING_WITH_LEN(" DESC"));
+ }
+ }
+ str->append(STRING_WITH_LEN(" separator \'"));
+ str->append(*separator);
+ str->append(STRING_WITH_LEN("\')"));
+}
+
+
+Item_func_group_concat::~Item_func_group_concat()
+{
+ if (!original && unique_filter)
+ delete unique_filter;
+}
diff --git a/sql/item_sum.h b/sql/item_sum.h
new file mode 100644
index 00000000000..d28c654c438
--- /dev/null
+++ b/sql/item_sum.h
@@ -0,0 +1,1492 @@
+#ifndef ITEM_SUM_INCLUDED
+#define ITEM_SUM_INCLUDED
+/* Copyright (c) 2000, 2013 Oracle and/or its affiliates.
+ Copyright (c) 2008, 2013 Monty Program Ab.
+
+ 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 */
+
+
+/* classes for sum functions */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include <my_tree.h>
+#include "sql_udf.h" /* udf_handler */
+
+class Item_sum;
+class Aggregator_distinct;
+class Aggregator_simple;
+
+/**
+ The abstract base class for the Aggregator_* classes.
+ It implements the data collection functions (setup/add/clear)
+ as either pass-through to the real functionality or
+ as collectors into an Unique (for distinct) structure.
+
+ Note that update_field/reset_field are not in that
+ class, because they're simply not called when
+ GROUP BY/DISTINCT can be handled with help of index on grouped
+ fields (quick_group = 0);
+*/
+
+class Aggregator : public Sql_alloc
+{
+ friend class Item_sum;
+ friend class Item_sum_sum;
+ friend class Item_sum_count;
+ friend class Item_sum_avg;
+
+ /*
+ All members are protected as this class is not usable outside of an
+ Item_sum descendant.
+ */
+protected:
+ /* the aggregate function class to act on */
+ Item_sum *item_sum;
+
+public:
+ Aggregator (Item_sum *arg): item_sum(arg) {}
+ virtual ~Aggregator () {} /* Keep gcc happy */
+
+ enum Aggregator_type { SIMPLE_AGGREGATOR, DISTINCT_AGGREGATOR };
+ virtual Aggregator_type Aggrtype() = 0;
+
+ /**
+ Called before adding the first row.
+ Allocates and sets up the internal aggregation structures used,
+ e.g. the Unique instance used to calculate distinct.
+ */
+ virtual bool setup(THD *) = 0;
+
+ /**
+ Called when we need to wipe out all the data from the aggregator :
+ all the values acumulated and all the state.
+ Cleans up the internal structures and resets them to their initial state.
+ */
+ virtual void clear() = 0;
+
+ /**
+ Called when there's a new value to be aggregated.
+ Updates the internal state of the aggregator to reflect the new value.
+ */
+ virtual bool add() = 0;
+
+ /**
+ Called when there are no more data and the final value is to be retrieved.
+ Finalises the state of the aggregator, so the final result can be retrieved.
+ */
+ virtual void endup() = 0;
+
+ /** Decimal value of being-aggregated argument */
+ virtual my_decimal *arg_val_decimal(my_decimal * value) = 0;
+ /** Floating point value of being-aggregated argument */
+ virtual double arg_val_real() = 0;
+ /**
+ NULLness of being-aggregated argument.
+
+ @param use_null_value Optimization: to determine if the argument is NULL
+ we must, in the general case, call is_null() on it, which itself might
+ call val_*() on it, which might be costly. If you just have called
+ arg_val*(), you can pass use_null_value=true; this way, arg_is_null()
+ might avoid is_null() and instead do a cheap read of the Item's null_value
+ (updated by arg_val*()).
+ */
+ virtual bool arg_is_null(bool use_null_value) = 0;
+};
+
+
+class st_select_lex;
+
+/**
+ Class Item_sum is the base class used for special expressions that SQL calls
+ 'set functions'. These expressions are formed with the help of aggregate
+ functions such as SUM, MAX, GROUP_CONCAT etc.
+
+ GENERAL NOTES
+
+ A set function cannot be used in certain positions where expressions are
+ accepted. There are some quite explicable restrictions for the usage of
+ set functions.
+
+ In the query:
+ SELECT AVG(b) FROM t1 WHERE SUM(b) > 20 GROUP by a
+ the usage of the set function AVG(b) is legal, while the usage of SUM(b)
+ is illegal. A WHERE condition must contain expressions that can be
+ evaluated for each row of the table. Yet the expression SUM(b) can be
+ evaluated only for each group of rows with the same value of column a.
+ In the query:
+ SELECT AVG(b) FROM t1 WHERE c > 30 GROUP BY a HAVING SUM(b) > 20
+ both set function expressions AVG(b) and SUM(b) are legal.
+
+ We can say that in a query without nested selects an occurrence of a
+ set function in an expression of the SELECT list or/and in the HAVING
+ clause is legal, while in the WHERE clause it's illegal.
+
+ The general rule to detect whether a set function is legal in a query with
+ nested subqueries is much more complicated.
+
+ Consider the the following query:
+ SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a > ALL (SELECT t2.c FROM t2 WHERE SUM(t1.b) < t2.c).
+ The set function SUM(b) is used here in the WHERE clause of the subquery.
+ Nevertheless it is legal since it is under the HAVING clause of the query
+ to which this function relates. The expression SUM(t1.b) is evaluated
+ for each group defined in the main query, not for groups of the subquery.
+
+ The problem of finding the query where to aggregate a particular
+ set function is not so simple as it seems to be.
+
+ In the query:
+ SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a > ALL(SELECT t2.c FROM t2 GROUP BY t2.c
+ HAVING SUM(t1.a) < t2.c)
+ the set function can be evaluated for both outer and inner selects.
+ If we evaluate SUM(t1.a) for the outer query then we get the value of t1.a
+ multiplied by the cardinality of a group in table t1. In this case
+ in each correlated subquery SUM(t1.a) is used as a constant. But we also
+ can evaluate SUM(t1.a) for the inner query. In this case t1.a will be a
+ constant for each correlated subquery and summation is performed
+ for each group of table t2.
+ (Here it makes sense to remind that the query
+ SELECT c FROM t GROUP BY a HAVING SUM(1) < a
+ is quite legal in our SQL).
+
+ So depending on what query we assign the set function to we
+ can get different result sets.
+
+ The general rule to detect the query where a set function is to be
+ evaluated can be formulated as follows.
+ Consider a set function S(E) where E is an expression with occurrences
+ of column references C1, ..., CN. Resolve these column references against
+ subqueries that contain the set function S(E). Let Q be the innermost
+ subquery of those subqueries. (It should be noted here that S(E)
+ in no way can be evaluated in the subquery embedding the subquery Q,
+ otherwise S(E) would refer to at least one unbound column reference)
+ If S(E) is used in a construct of Q where set functions are allowed then
+ we evaluate S(E) in Q.
+ Otherwise we look for a innermost subquery containing S(E) of those where
+ usage of S(E) is allowed.
+
+ Let's demonstrate how this rule is applied to the following queries.
+
+ 1. SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a > ALL(SELECT t2.b FROM t2 GROUP BY t2.b
+ HAVING t2.b > ALL(SELECT t3.c FROM t3 GROUP BY t3.c
+ HAVING SUM(t1.a+t2.b) < t3.c))
+ For this query the set function SUM(t1.a+t2.b) depends on t1.a and t2.b
+ with t1.a defined in the outermost query, and t2.b defined for its
+ subquery. The set function is in the HAVING clause of the subquery and can
+ be evaluated in this subquery.
+
+ 2. SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a > ALL(SELECT t2.b FROM t2
+ WHERE t2.b > ALL (SELECT t3.c FROM t3 GROUP BY t3.c
+ HAVING SUM(t1.a+t2.b) < t3.c))
+ Here the set function SUM(t1.a+t2.b)is in the WHERE clause of the second
+ subquery - the most upper subquery where t1.a and t2.b are defined.
+ If we evaluate the function in this subquery we violate the context rules.
+ So we evaluate the function in the third subquery (over table t3) where it
+ is used under the HAVING clause.
+
+ 3. SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a > ALL(SELECT t2.b FROM t2
+ WHERE t2.b > ALL (SELECT t3.c FROM t3
+ WHERE SUM(t1.a+t2.b) < t3.c))
+ In this query evaluation of SUM(t1.a+t2.b) is not legal neither in the second
+ nor in the third subqueries. So this query is invalid.
+
+ Mostly set functions cannot be nested. In the query
+ SELECT t1.a from t1 GROUP BY t1.a HAVING AVG(SUM(t1.b)) > 20
+ the expression SUM(b) is not acceptable, though it is under a HAVING clause.
+ Yet it is acceptable in the query:
+ SELECT t.1 FROM t1 GROUP BY t1.a HAVING SUM(t1.b) > 20.
+
+ An argument of a set function does not have to be a reference to a table
+ column as we saw it in examples above. This can be a more complex expression
+ SELECT t1.a FROM t1 GROUP BY t1.a HAVING SUM(t1.b+1) > 20.
+ The expression SUM(t1.b+1) has a very clear semantics in this context:
+ we sum up the values of t1.b+1 where t1.b varies for all values within a
+ group of rows that contain the same t1.a value.
+
+ A set function for an outer query yields a constant within a subquery. So
+ the semantics of the query
+ SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a IN (SELECT t2.c FROM t2 GROUP BY t2.c
+ HAVING AVG(t2.c+SUM(t1.b)) > 20)
+ is still clear. For a group of the rows with the same t1.a values we
+ calculate the value of SUM(t1.b). This value 's' is substituted in the
+ the subquery:
+ SELECT t2.c FROM t2 GROUP BY t2.c HAVING AVG(t2.c+s)
+ than returns some result set.
+
+ By the same reason the following query with a subquery
+ SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a IN (SELECT t2.c FROM t2 GROUP BY t2.c
+ HAVING AVG(SUM(t1.b)) > 20)
+ is also acceptable.
+
+ IMPLEMENTATION NOTES
+
+ Three methods were added to the class to check the constraints specified
+ in the previous section. These methods utilize several new members.
+
+ The field 'nest_level' contains the number of the level for the subquery
+ containing the set function. The main SELECT is of level 0, its subqueries
+ are of levels 1, the subqueries of the latter are of level 2 and so on.
+
+ The field 'aggr_level' is to contain the nest level of the subquery
+ where the set function is aggregated.
+
+ The field 'max_arg_level' is for the maximun of the nest levels of the
+ unbound column references occurred in the set function. A column reference
+ is unbound within a set function if it is not bound by any subquery
+ used as a subexpression in this function. A column reference is bound by
+ a subquery if it is a reference to the column by which the aggregation
+ of some set function that is used in the subquery is calculated.
+ For the set function used in the query
+ SELECT t1.a FROM t1 GROUP BY t1.a
+ HAVING t1.a > ALL(SELECT t2.b FROM t2 GROUP BY t2.b
+ HAVING t2.b > ALL(SELECT t3.c FROM t3 GROUP BY t3.c
+ HAVING SUM(t1.a+t2.b) < t3.c))
+ the value of max_arg_level is equal to 1 since t1.a is bound in the main
+ query, and t2.b is bound by the first subquery whose nest level is 1.
+ Obviously a set function cannot be aggregated in the subquery whose
+ nest level is less than max_arg_level. (Yet it can be aggregated in the
+ subqueries whose nest level is greater than max_arg_level.)
+ In the query
+ SELECT t.a FROM t1 HAVING AVG(t1.a+(SELECT MIN(t2.c) FROM t2))
+ the value of the max_arg_level for the AVG set function is 0 since
+ the reference t2.c is bound in the subquery.
+
+ The field 'max_sum_func_level' is to contain the maximum of the
+ nest levels of the set functions that are used as subexpressions of
+ the arguments of the given set function, but not aggregated in any
+ subquery within this set function. A nested set function s1 can be
+ used within set function s0 only if s1.max_sum_func_level <
+ s0.max_sum_func_level. Set function s1 is considered as nested
+ for set function s0 if s1 is not calculated in any subquery
+ within s0.
+
+ A set function that is used as a subexpression in an argument of another
+ set function refers to the latter via the field 'in_sum_func'.
+
+ The condition imposed on the usage of set functions are checked when
+ we traverse query subexpressions with the help of the recursive method
+ fix_fields. When we apply this method to an object of the class
+ Item_sum, first, on the descent, we call the method init_sum_func_check
+ that initialize members used at checking. Then, on the ascent, we
+ call the method check_sum_func that validates the set function usage
+ and reports an error if it is illegal.
+ The method register_sum_func serves to link the items for the set functions
+ that are aggregated in the embedding (sub)queries. Circular chains of such
+ functions are attached to the corresponding st_select_lex structures
+ through the field inner_sum_func_list.
+
+ Exploiting the fact that the members mentioned above are used in one
+ recursive function we could have allocated them on the thread stack.
+ Yet we don't do it now.
+
+ We assume that the nesting level of subquries does not exceed 127.
+ TODO: to catch queries where the limit is exceeded to make the
+ code clean here.
+
+*/
+
+class Item_sum :public Item_result_field
+{
+ friend class Aggregator_distinct;
+ friend class Aggregator_simple;
+
+protected:
+ /**
+ Aggregator class instance. Not set initially. Allocated only after
+ it is determined if the incoming data are already distinct.
+ */
+ Aggregator *aggr;
+
+private:
+ /**
+ Used in making ROLLUP. Set for the ROLLUP copies of the original
+ Item_sum and passed to create_tmp_field() to cause it to work
+ over the temp table buffer that is referenced by
+ Item_result_field::result_field.
+ */
+ bool force_copy_fields;
+
+ /**
+ Indicates how the aggregate function was specified by the parser :
+ 1 if it was written as AGGREGATE(DISTINCT),
+ 0 if it was AGGREGATE()
+ */
+ bool with_distinct;
+
+public:
+
+ bool has_force_copy_fields() const { return force_copy_fields; }
+ bool has_with_distinct() const { return with_distinct; }
+
+ enum Sumfunctype
+ { COUNT_FUNC, COUNT_DISTINCT_FUNC, SUM_FUNC, SUM_DISTINCT_FUNC, AVG_FUNC,
+ AVG_DISTINCT_FUNC, MIN_FUNC, MAX_FUNC, STD_FUNC,
+ VARIANCE_FUNC, SUM_BIT_FUNC, UDF_SUM_FUNC, GROUP_CONCAT_FUNC
+ };
+
+ Item **ref_by; /* pointer to a ref to the object used to register it */
+ Item_sum *next; /* next in the circular chain of registered objects */
+ Item_sum *in_sum_func; /* embedding set function if any */
+ st_select_lex * aggr_sel; /* select where the function is aggregated */
+ int8 nest_level; /* number of the nesting level of the set function */
+ int8 aggr_level; /* nesting level of the aggregating subquery */
+ int8 max_arg_level; /* max level of unbound column references */
+ int8 max_sum_func_level;/* max level of aggregation for embedded functions */
+ bool quick_group; /* If incremental update of fields */
+ /*
+ This list is used by the check for mixing non aggregated fields and
+ sum functions in the ONLY_FULL_GROUP_BY_MODE. We save all outer fields
+ directly or indirectly used under this function it as it's unclear
+ at the moment of fixing outer field whether it's aggregated or not.
+ */
+ List<Item_field> outer_fields;
+
+protected:
+ uint arg_count;
+ Item **args, *tmp_args[2];
+ /*
+ Copy of the arguments list to hold the original set of arguments.
+ Used in EXPLAIN EXTENDED instead of the current argument list because
+ the current argument list can be altered by usage of temporary tables.
+ */
+ Item **orig_args, *tmp_orig_args[2];
+ table_map used_tables_cache;
+
+ /*
+ TRUE <=> We've managed to calculate the value of this Item in
+ opt_sum_query(), hence it can be considered constant at all subsequent
+ steps.
+ */
+ bool forced_const;
+ static ulonglong ram_limitation(THD *thd);
+
+public:
+
+ void mark_as_sum_func();
+ Item_sum() :quick_group(1), arg_count(0), forced_const(FALSE)
+ {
+ mark_as_sum_func();
+ init_aggregator();
+ }
+ Item_sum(Item *a) :quick_group(1), arg_count(1), args(tmp_args),
+ orig_args(tmp_orig_args), forced_const(FALSE)
+ {
+ args[0]=a;
+ mark_as_sum_func();
+ init_aggregator();
+ }
+ Item_sum( Item *a, Item *b ) :quick_group(1), arg_count(2), args(tmp_args),
+ orig_args(tmp_orig_args), forced_const(FALSE)
+ {
+ args[0]=a; args[1]=b;
+ mark_as_sum_func();
+ init_aggregator();
+ }
+ Item_sum(List<Item> &list);
+ //Copy constructor, need to perform subselects with temporary tables
+ Item_sum(THD *thd, Item_sum *item);
+ enum Type type() const { return SUM_FUNC_ITEM; }
+ virtual enum Sumfunctype sum_func () const=0;
+ /**
+ Resets the aggregate value to its default and aggregates the current
+ value of its attribute(s).
+ */
+ inline bool reset_and_add()
+ {
+ aggregator_clear();
+ return aggregator_add();
+ };
+
+ /*
+ Called when new group is started and results are being saved in
+ a temporary table. Similarly to reset_and_add() it resets the
+ value to its default and aggregates the value of its
+ attribute(s), but must also store it in result_field.
+ This set of methods (result_item(), reset_field, update_field()) of
+ Item_sum is used only if quick_group is not null. Otherwise
+ copy_or_same() is used to obtain a copy of this item.
+ */
+ virtual void reset_field()=0;
+ /*
+ Called for each new value in the group, when temporary table is in use.
+ Similar to add(), but uses temporary table field to obtain current value,
+ Updated value is then saved in the field.
+ */
+ virtual void update_field()=0;
+ virtual bool keep_field_type(void) const { return 0; }
+ virtual void fix_length_and_dec() { maybe_null=1; null_value=1; }
+ virtual Item *result_item(Field *field)
+ { return new Item_field(field); }
+ /*
+ Return bitmap of tables that are needed to evaluate the item.
+
+ The implementation takes into account the used strategy: items resolved
+ at optimization phase will report 0.
+ Items that depend on the number of join output records, but not columns
+ of any particular table (like COUNT(*)) will report 0 from used_tables(),
+ but will still return false from const_item().
+ */
+ table_map used_tables() const { return used_tables_cache; }
+ void update_used_tables ();
+ bool is_null() { return null_value; }
+ void make_const ()
+ {
+ used_tables_cache= 0;
+ forced_const= TRUE;
+ }
+ virtual bool const_item() const { return forced_const; }
+ virtual bool const_during_execution() const { return false; }
+ virtual void print(String *str, enum_query_type query_type);
+ void fix_num_length_and_dec();
+
+ /**
+ Mark an aggregate as having no rows.
+
+ This function is called by the execution engine to assign 'NO ROWS
+ FOUND' value to an aggregate item, when the underlying result set
+ has no rows. Such value, in a general case, may be different from
+ the default value of the item after 'clear()': e.g. a numeric item
+ may be initialized to 0 by clear() and to NULL by
+ no_rows_in_result().
+ */
+ virtual void no_rows_in_result()
+ {
+ set_aggregator(with_distinct ?
+ Aggregator::DISTINCT_AGGREGATOR :
+ Aggregator::SIMPLE_AGGREGATOR);
+ aggregator_clear();
+ }
+ virtual void make_unique() { force_copy_fields= TRUE; }
+ Item *get_tmp_table_item(THD *thd);
+ virtual Field *create_tmp_field(bool group, TABLE *table,
+ uint convert_blob_length);
+ bool walk(Item_processor processor, bool walk_subquery, uchar *argument);
+ virtual bool collect_outer_ref_processor(uchar *param);
+ bool init_sum_func_check(THD *thd);
+ bool check_sum_func(THD *thd, Item **ref);
+ bool register_sum_func(THD *thd, Item **ref);
+ st_select_lex *depended_from()
+ { return (nest_level == aggr_level ? 0 : aggr_sel); }
+
+ Item *get_arg(uint i) { return args[i]; }
+ Item *set_arg(uint i, THD *thd, Item *new_val);
+ uint get_arg_count() const { return arg_count; }
+
+ /* Initialization of distinct related members */
+ void init_aggregator()
+ {
+ aggr= NULL;
+ with_distinct= FALSE;
+ force_copy_fields= FALSE;
+ }
+
+ /**
+ Called to initialize the aggregator.
+ */
+
+ inline bool aggregator_setup(THD *thd) { return aggr->setup(thd); };
+
+ /**
+ Called to cleanup the aggregator.
+ */
+
+ inline void aggregator_clear() { aggr->clear(); }
+
+ /**
+ Called to add value to the aggregator.
+ */
+
+ inline bool aggregator_add() { return aggr->add(); };
+
+ /* stores the declared DISTINCT flag (from the parser) */
+ void set_distinct(bool distinct)
+ {
+ with_distinct= distinct;
+ quick_group= with_distinct ? 0 : 1;
+ }
+
+ /*
+ Set the type of aggregation : DISTINCT or not.
+
+ May be called multiple times.
+ */
+
+ int set_aggregator(Aggregator::Aggregator_type aggregator);
+
+ virtual void clear()= 0;
+ virtual bool add()= 0;
+ virtual bool setup(THD *thd) { return false; }
+
+ virtual void cleanup();
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Unique;
+
+
+/**
+ The distinct aggregator.
+ Implements AGGFN (DISTINCT ..)
+ Collects all the data into an Unique (similarly to what Item_sum_distinct
+ does currently) and then (if applicable) iterates over the list of
+ unique values and pumps them back into its object
+*/
+
+class Aggregator_distinct : public Aggregator
+{
+ friend class Item_sum_sum;
+
+ /*
+ flag to prevent consecutive runs of endup(). Normally in endup there are
+ expensive calculations (like walking the distinct tree for example)
+ which we must do only once if there are no data changes.
+ We can re-use the data for the second and subsequent val_xxx() calls.
+ endup_done set to TRUE also means that the calculated values for
+ the aggregate functions are correct and don't need recalculation.
+ */
+ bool endup_done;
+
+ /*
+ Used depending on the type of the aggregate function and the presence of
+ blob columns in it:
+ - For COUNT(DISTINCT) and no blob fields this points to a real temporary
+ table. It's used as a hash table.
+ - For AVG/SUM(DISTINCT) or COUNT(DISTINCT) with blob fields only the
+ in-memory data structure of a temporary table is constructed.
+ It's used by the Field classes to transform data into row format.
+ */
+ TABLE *table;
+
+ /*
+ An array of field lengths on row allocated and used only for
+ COUNT(DISTINCT) with multiple columns and no blobs. Used in
+ Aggregator_distinct::composite_key_cmp (called from Unique to compare
+ nodes
+ */
+ uint32 *field_lengths;
+
+ /*
+ Used in conjunction with 'table' to support the access to Field classes
+ for COUNT(DISTINCT). Needed by copy_fields()/copy_funcs().
+ */
+ TMP_TABLE_PARAM *tmp_table_param;
+
+ /*
+ If there are no blobs in the COUNT(DISTINCT) arguments, we can use a tree,
+ which is faster than heap table. In that case, we still use the table
+ to help get things set up, but we insert nothing in it.
+ For AVG/SUM(DISTINCT) we always use this tree (as it takes a single
+ argument) to get the distinct rows.
+ */
+ Unique *tree;
+
+ /*
+ The length of the temp table row. Must be a member of the class as it
+ gets passed down to simple_raw_key_cmp () as a compare function argument
+ to Unique. simple_raw_key_cmp () is used as a fast comparison function
+ when the entire row can be binary compared.
+ */
+ uint tree_key_length;
+
+ /*
+ Set to true if the result is known to be always NULL.
+ If set deactivates creation and usage of the temporary table (in the
+ 'table' member) and the Unique instance (in the 'tree' member) as well as
+ the calculation of the final value on the first call to
+ Item_[sum|avg|count]::val_xxx().
+ */
+ bool always_null;
+
+ /**
+ When feeding back the data in endup() from Unique/temp table back to
+ Item_sum::add() methods we must read the data from Unique (and not
+ recalculate the functions that are given as arguments to the aggregate
+ function.
+ This flag is to tell the arg_*() methods to take the data from the Unique
+ instead of calling the relevant val_..() method.
+ */
+ bool use_distinct_values;
+
+public:
+ Aggregator_distinct (Item_sum *sum) :
+ Aggregator(sum), table(NULL), tmp_table_param(NULL), tree(NULL),
+ always_null(false), use_distinct_values(false) {}
+ virtual ~Aggregator_distinct ();
+ Aggregator_type Aggrtype() { return DISTINCT_AGGREGATOR; }
+
+ bool setup(THD *);
+ void clear();
+ bool add();
+ void endup();
+ virtual my_decimal *arg_val_decimal(my_decimal * value);
+ virtual double arg_val_real();
+ virtual bool arg_is_null(bool use_null_value);
+
+ bool unique_walk_function(void *element);
+ bool unique_walk_function_for_count(void *element);
+ static int composite_key_cmp(void* arg, uchar* key1, uchar* key2);
+};
+
+
+/**
+ The pass-through aggregator.
+ Implements AGGFN (DISTINCT ..) by knowing it gets distinct data on input.
+ So it just pumps them back to the Item_sum descendant class.
+*/
+class Aggregator_simple : public Aggregator
+{
+public:
+
+ Aggregator_simple (Item_sum *sum) :
+ Aggregator(sum) {}
+ Aggregator_type Aggrtype() { return Aggregator::SIMPLE_AGGREGATOR; }
+
+ bool setup(THD * thd) { return item_sum->setup(thd); }
+ void clear() { item_sum->clear(); }
+ bool add() { return item_sum->add(); }
+ void endup() {};
+ virtual my_decimal *arg_val_decimal(my_decimal * value);
+ virtual double arg_val_real();
+ virtual bool arg_is_null(bool use_null_value);
+};
+
+
+class Item_sum_num :public Item_sum
+{
+protected:
+ /*
+ val_xxx() functions may be called several times during the execution of a
+ query. Derived classes that require extensive calculation in val_xxx()
+ maintain cache of aggregate value. This variable governs the validity of
+ that cache.
+ */
+ bool is_evaluated;
+public:
+ Item_sum_num() :Item_sum(),is_evaluated(FALSE) {}
+ Item_sum_num(Item *item_par)
+ :Item_sum(item_par), is_evaluated(FALSE) {}
+ Item_sum_num(Item *a, Item* b) :Item_sum(a,b),is_evaluated(FALSE) {}
+ Item_sum_num(List<Item> &list)
+ :Item_sum(list), is_evaluated(FALSE) {}
+ Item_sum_num(THD *thd, Item_sum_num *item)
+ :Item_sum(thd, item),is_evaluated(item->is_evaluated) {}
+ bool fix_fields(THD *, Item **);
+ longlong val_int()
+ {
+ DBUG_ASSERT(fixed == 1);
+ return (longlong) rint(val_real()); /* Real as default */
+ }
+ String *val_str(String*str);
+ my_decimal *val_decimal(my_decimal *);
+ void reset_field();
+};
+
+
+class Item_sum_int :public Item_sum_num
+{
+public:
+ Item_sum_int(Item *item_par) :Item_sum_num(item_par) {}
+ Item_sum_int(List<Item> &list) :Item_sum_num(list) {}
+ Item_sum_int(THD *thd, Item_sum_int *item) :Item_sum_num(thd, item) {}
+ double val_real() { DBUG_ASSERT(fixed == 1); return (double) val_int(); }
+ String *val_str(String*str);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type () const { return INT_RESULT; }
+ void fix_length_and_dec()
+ { decimals=0; max_length=21; maybe_null=null_value=0; }
+};
+
+
+class Item_sum_sum :public Item_sum_num
+{
+protected:
+ Item_result hybrid_type;
+ double sum;
+ my_decimal dec_buffs[2];
+ uint curr_dec_buff;
+ void fix_length_and_dec();
+
+public:
+ Item_sum_sum(Item *item_par, bool distinct) :Item_sum_num(item_par)
+ {
+ set_distinct(distinct);
+ }
+ Item_sum_sum(THD *thd, Item_sum_sum *item);
+ enum Sumfunctype sum_func () const
+ {
+ return has_with_distinct() ? SUM_DISTINCT_FUNC : SUM_FUNC;
+ }
+ void clear();
+ bool add();
+ double val_real();
+ longlong val_int();
+ String *val_str(String*str);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type () const { return hybrid_type; }
+ void reset_field();
+ void update_field();
+ void no_rows_in_result() {}
+ const char *func_name() const
+ {
+ return has_with_distinct() ? "sum(distinct " : "sum(";
+ }
+ Item *copy_or_same(THD* thd);
+};
+
+
+class Item_sum_count :public Item_sum_int
+{
+ longlong count;
+
+ friend class Aggregator_distinct;
+
+ void clear();
+ bool add();
+ void cleanup();
+
+ public:
+ Item_sum_count(Item *item_par)
+ :Item_sum_int(item_par),count(0)
+ {}
+
+ /**
+ Constructs an instance for COUNT(DISTINCT)
+
+ @param list a list of the arguments to the aggregate function
+
+ This constructor is called by the parser only for COUNT (DISTINCT).
+ */
+
+ Item_sum_count(List<Item> &list)
+ :Item_sum_int(list),count(0)
+ {
+ set_distinct(TRUE);
+ }
+ Item_sum_count(THD *thd, Item_sum_count *item)
+ :Item_sum_int(thd, item), count(item->count)
+ {}
+ enum Sumfunctype sum_func () const
+ {
+ return has_with_distinct() ? COUNT_DISTINCT_FUNC : COUNT_FUNC;
+ }
+ void no_rows_in_result() { count=0; }
+ void make_const(longlong count_arg)
+ {
+ count=count_arg;
+ Item_sum::make_const();
+ }
+ longlong val_int();
+ void reset_field();
+ void update_field();
+ const char *func_name() const
+ {
+ return has_with_distinct() ? "count(distinct " : "count(";
+ }
+ Item *copy_or_same(THD* thd);
+};
+
+
+/* Item to get the value of a stored sum function */
+
+class Item_sum_avg;
+
+class Item_avg_field :public Item_result_field
+{
+public:
+ Field *field;
+ Item_result hybrid_type;
+ uint f_precision, f_scale, dec_bin_size;
+ uint prec_increment;
+ Item_avg_field(Item_result res_type, Item_sum_avg *item);
+ enum Type type() const { return FIELD_AVG_ITEM; }
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *);
+ bool is_null() { update_null_value(); return null_value; }
+ String *val_str(String*);
+ enum_field_types field_type() const
+ {
+ return hybrid_type == DECIMAL_RESULT ?
+ MYSQL_TYPE_NEWDECIMAL : MYSQL_TYPE_DOUBLE;
+ }
+ void fix_length_and_dec() {}
+ enum Item_result result_type () const { return hybrid_type; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("avg_field");
+ }
+ const char *func_name() const { DBUG_ASSERT(0); return "avg_field"; }
+};
+
+
+class Item_sum_avg :public Item_sum_sum
+{
+public:
+ ulonglong count;
+ uint prec_increment;
+ uint f_precision, f_scale, dec_bin_size;
+
+ Item_sum_avg(Item *item_par, bool distinct)
+ :Item_sum_sum(item_par, distinct), count(0)
+ {}
+ Item_sum_avg(THD *thd, Item_sum_avg *item)
+ :Item_sum_sum(thd, item), count(item->count),
+ prec_increment(item->prec_increment) {}
+
+ void fix_length_and_dec();
+ enum Sumfunctype sum_func () const
+ {
+ return has_with_distinct() ? AVG_DISTINCT_FUNC : AVG_FUNC;
+ }
+ void clear();
+ bool add();
+ double val_real();
+ // In SPs we might force the "wrong" type with select into a declare variable
+ longlong val_int() { return (longlong) rint(val_real()); }
+ my_decimal *val_decimal(my_decimal *);
+ String *val_str(String *str);
+ void reset_field();
+ void update_field();
+ Item *result_item(Field *field)
+ { return new Item_avg_field(hybrid_type, this); }
+ void no_rows_in_result() {}
+ const char *func_name() const
+ {
+ return has_with_distinct() ? "avg(distinct " : "avg(";
+ }
+ Item *copy_or_same(THD* thd);
+ Field *create_tmp_field(bool group, TABLE *table, uint convert_blob_length);
+ void cleanup()
+ {
+ count= 0;
+ Item_sum_sum::cleanup();
+ }
+};
+
+class Item_sum_variance;
+
+class Item_variance_field :public Item_result_field
+{
+public:
+ Field *field;
+ Item_result hybrid_type;
+ uint f_precision0, f_scale0;
+ uint f_precision1, f_scale1;
+ uint dec_bin_size0, dec_bin_size1;
+ uint sample;
+ uint prec_increment;
+ Item_variance_field(Item_sum_variance *item);
+ enum Type type() const {return FIELD_VARIANCE_ITEM; }
+ double val_real();
+ longlong val_int()
+ { /* can't be fix_fields()ed */ return (longlong) rint(val_real()); }
+ String *val_str(String *str)
+ { return val_string_from_real(str); }
+ my_decimal *val_decimal(my_decimal *dec_buf)
+ { return val_decimal_from_real(dec_buf); }
+ bool is_null() { update_null_value(); return null_value; }
+ enum_field_types field_type() const
+ {
+ return hybrid_type == DECIMAL_RESULT ?
+ MYSQL_TYPE_NEWDECIMAL : MYSQL_TYPE_DOUBLE;
+ }
+ void fix_length_and_dec() {}
+ enum Item_result result_type () const { return hybrid_type; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("var_field");
+ }
+ const char *func_name() const { DBUG_ASSERT(0); return "variance_field"; }
+};
+
+
+/*
+ variance(a) =
+
+ = sum (ai - avg(a))^2 / count(a) )
+ = sum (ai^2 - 2*ai*avg(a) + avg(a)^2) / count(a)
+ = (sum(ai^2) - sum(2*ai*avg(a)) + sum(avg(a)^2))/count(a) =
+ = (sum(ai^2) - 2*avg(a)*sum(a) + count(a)*avg(a)^2)/count(a) =
+ = (sum(ai^2) - 2*sum(a)*sum(a)/count(a) + count(a)*sum(a)^2/count(a)^2 )/count(a) =
+ = (sum(ai^2) - 2*sum(a)^2/count(a) + sum(a)^2/count(a) )/count(a) =
+ = (sum(ai^2) - sum(a)^2/count(a))/count(a)
+
+But, this falls prey to catastrophic cancellation. Instead, use the recurrence formulas
+
+ M_{1} = x_{1}, ~ M_{k} = M_{k-1} + (x_{k} - M_{k-1}) / k newline
+ S_{1} = 0, ~ S_{k} = S_{k-1} + (x_{k} - M_{k-1}) times (x_{k} - M_{k}) newline
+ for 2 <= k <= n newline
+ ital variance = S_{n} / (n-1)
+
+*/
+
+class Item_sum_variance : public Item_sum_num
+{
+ void fix_length_and_dec();
+
+public:
+ Item_result hybrid_type;
+ int cur_dec;
+ double recurrence_m, recurrence_s; /* Used in recurrence relation. */
+ ulonglong count;
+ uint f_precision0, f_scale0;
+ uint f_precision1, f_scale1;
+ uint dec_bin_size0, dec_bin_size1;
+ uint sample;
+ uint prec_increment;
+
+ Item_sum_variance(Item *item_par, uint sample_arg) :Item_sum_num(item_par),
+ hybrid_type(REAL_RESULT), count(0), sample(sample_arg)
+ {}
+ Item_sum_variance(THD *thd, Item_sum_variance *item);
+ enum Sumfunctype sum_func () const { return VARIANCE_FUNC; }
+ void clear();
+ bool add();
+ double val_real();
+ my_decimal *val_decimal(my_decimal *);
+ void reset_field();
+ void update_field();
+ Item *result_item(Field *field)
+ { return new Item_variance_field(this); }
+ void no_rows_in_result() {}
+ const char *func_name() const
+ { return sample ? "var_samp(" : "variance("; }
+ Item *copy_or_same(THD* thd);
+ Field *create_tmp_field(bool group, TABLE *table, uint convert_blob_length);
+ enum Item_result result_type () const { return REAL_RESULT; }
+ void cleanup()
+ {
+ count= 0;
+ Item_sum_num::cleanup();
+ }
+};
+
+class Item_sum_std;
+
+class Item_std_field :public Item_variance_field
+{
+public:
+ Item_std_field(Item_sum_std *item);
+ enum Type type() const { return FIELD_STD_ITEM; }
+ double val_real();
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type () const { return REAL_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE;}
+ const char *func_name() const { DBUG_ASSERT(0); return "std_field"; }
+};
+
+/*
+ standard_deviation(a) = sqrt(variance(a))
+*/
+
+class Item_sum_std :public Item_sum_variance
+{
+ public:
+ Item_sum_std(Item *item_par, uint sample_arg)
+ :Item_sum_variance(item_par, sample_arg) {}
+ Item_sum_std(THD *thd, Item_sum_std *item)
+ :Item_sum_variance(thd, item)
+ {}
+ enum Sumfunctype sum_func () const { return STD_FUNC; }
+ double val_real();
+ Item *result_item(Field *field)
+ { return new Item_std_field(this); }
+ const char *func_name() const { return "std("; }
+ Item *copy_or_same(THD* thd);
+ enum Item_result result_type () const { return REAL_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE;}
+};
+
+// This class is a string or number function depending on num_func
+class Arg_comparator;
+class Item_cache;
+class Item_sum_hybrid :public Item_sum
+{
+protected:
+ Item_cache *value, *arg_cache;
+ Arg_comparator *cmp;
+ Item_result hybrid_type;
+ enum_field_types hybrid_field_type;
+ int cmp_sign;
+ bool was_values; // Set if we have found at least one row (for max/min only)
+ bool was_null_value;
+
+ public:
+ Item_sum_hybrid(Item *item_par,int sign)
+ :Item_sum(item_par), value(0), arg_cache(0), cmp(0),
+ hybrid_type(INT_RESULT), hybrid_field_type(MYSQL_TYPE_LONGLONG),
+ cmp_sign(sign), was_values(TRUE)
+ { collation.set(&my_charset_bin); }
+ Item_sum_hybrid(THD *thd, Item_sum_hybrid *item)
+ :Item_sum(thd, item), value(item->value), arg_cache(0),
+ hybrid_type(item->hybrid_type), hybrid_field_type(item->hybrid_field_type),
+ cmp_sign(item->cmp_sign), was_values(item->was_values)
+ { }
+ bool fix_fields(THD *, Item **);
+ void setup_hybrid(Item *item, Item *value_arg);
+ void clear();
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *);
+ void reset_field();
+ String *val_str(String *);
+ bool keep_field_type(void) const { return 1; }
+ enum Item_result result_type () const { return hybrid_type; }
+ enum enum_field_types field_type() const { return hybrid_field_type; }
+ void update_field();
+ void min_max_update_str_field();
+ void min_max_update_real_field();
+ void min_max_update_int_field();
+ void min_max_update_decimal_field();
+ void cleanup();
+ bool any_value() { return was_values; }
+ void no_rows_in_result();
+ void restore_to_before_no_rows_in_result();
+ Field *create_tmp_field(bool group, TABLE *table,
+ uint convert_blob_length);
+};
+
+
+class Item_sum_min :public Item_sum_hybrid
+{
+public:
+ Item_sum_min(Item *item_par) :Item_sum_hybrid(item_par,1) {}
+ Item_sum_min(THD *thd, Item_sum_min *item) :Item_sum_hybrid(thd, item) {}
+ enum Sumfunctype sum_func () const {return MIN_FUNC;}
+
+ bool add();
+ const char *func_name() const { return "min("; }
+ Item *copy_or_same(THD* thd);
+};
+
+
+class Item_sum_max :public Item_sum_hybrid
+{
+public:
+ Item_sum_max(Item *item_par) :Item_sum_hybrid(item_par,-1) {}
+ Item_sum_max(THD *thd, Item_sum_max *item) :Item_sum_hybrid(thd, item) {}
+ enum Sumfunctype sum_func () const {return MAX_FUNC;}
+
+ bool add();
+ const char *func_name() const { return "max("; }
+ Item *copy_or_same(THD* thd);
+};
+
+
+class Item_sum_bit :public Item_sum_int
+{
+protected:
+ ulonglong reset_bits,bits;
+
+public:
+ Item_sum_bit(Item *item_par,ulonglong reset_arg)
+ :Item_sum_int(item_par),reset_bits(reset_arg),bits(reset_arg) {}
+ Item_sum_bit(THD *thd, Item_sum_bit *item):
+ Item_sum_int(thd, item), reset_bits(item->reset_bits), bits(item->bits) {}
+ enum Sumfunctype sum_func () const {return SUM_BIT_FUNC;}
+ void clear();
+ longlong val_int();
+ void reset_field();
+ void update_field();
+ void fix_length_and_dec()
+ { decimals= 0; max_length=21; unsigned_flag= 1; maybe_null= null_value= 0; }
+ void cleanup()
+ {
+ bits= reset_bits;
+ Item_sum_int::cleanup();
+ }
+};
+
+
+class Item_sum_or :public Item_sum_bit
+{
+public:
+ Item_sum_or(Item *item_par) :Item_sum_bit(item_par, 0) {}
+ Item_sum_or(THD *thd, Item_sum_or *item) :Item_sum_bit(thd, item) {}
+ bool add();
+ const char *func_name() const { return "bit_or("; }
+ Item *copy_or_same(THD* thd);
+};
+
+
+class Item_sum_and :public Item_sum_bit
+{
+ public:
+ Item_sum_and(Item *item_par) :Item_sum_bit(item_par, ULONGLONG_MAX) {}
+ Item_sum_and(THD *thd, Item_sum_and *item) :Item_sum_bit(thd, item) {}
+ bool add();
+ const char *func_name() const { return "bit_and("; }
+ Item *copy_or_same(THD* thd);
+};
+
+class Item_sum_xor :public Item_sum_bit
+{
+ public:
+ Item_sum_xor(Item *item_par) :Item_sum_bit(item_par, 0) {}
+ Item_sum_xor(THD *thd, Item_sum_xor *item) :Item_sum_bit(thd, item) {}
+ bool add();
+ const char *func_name() const { return "bit_xor("; }
+ Item *copy_or_same(THD* thd);
+};
+
+
+/*
+ User defined aggregates
+*/
+
+#ifdef HAVE_DLOPEN
+
+class Item_udf_sum : public Item_sum
+{
+protected:
+ udf_handler udf;
+
+public:
+ Item_udf_sum(udf_func *udf_arg)
+ :Item_sum(), udf(udf_arg)
+ { quick_group=0; }
+ Item_udf_sum(udf_func *udf_arg, List<Item> &list)
+ :Item_sum(list), udf(udf_arg)
+ { quick_group=0;}
+ Item_udf_sum(THD *thd, Item_udf_sum *item)
+ :Item_sum(thd, item), udf(item->udf)
+ { udf.not_original= TRUE; }
+ const char *func_name() const { return udf.name(); }
+ bool fix_fields(THD *thd, Item **ref)
+ {
+ DBUG_ASSERT(fixed == 0);
+
+ if (init_sum_func_check(thd))
+ return TRUE;
+
+ fixed= 1;
+ if (udf.fix_fields(thd, this, this->arg_count, this->args))
+ return TRUE;
+
+ memcpy (orig_args, args, sizeof (Item *) * arg_count);
+ return check_sum_func(thd, ref);
+ }
+ enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; }
+ virtual bool have_field_update(void) const { return 0; }
+
+ void clear();
+ bool add();
+ void reset_field() {};
+ void update_field() {};
+ void cleanup();
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_sum_udf_float :public Item_udf_sum
+{
+ public:
+ Item_sum_udf_float(udf_func *udf_arg)
+ :Item_udf_sum(udf_arg) {}
+ Item_sum_udf_float(udf_func *udf_arg, List<Item> &list)
+ :Item_udf_sum(udf_arg, list) {}
+ Item_sum_udf_float(THD *thd, Item_sum_udf_float *item)
+ :Item_udf_sum(thd, item) {}
+ longlong val_int()
+ {
+ DBUG_ASSERT(fixed == 1);
+ return (longlong) rint(Item_sum_udf_float::val_real());
+ }
+ double val_real();
+ String *val_str(String*str);
+ my_decimal *val_decimal(my_decimal *);
+ void fix_length_and_dec() { fix_num_length_and_dec(); }
+ Item *copy_or_same(THD* thd);
+};
+
+
+class Item_sum_udf_int :public Item_udf_sum
+{
+public:
+ Item_sum_udf_int(udf_func *udf_arg)
+ :Item_udf_sum(udf_arg) {}
+ Item_sum_udf_int(udf_func *udf_arg, List<Item> &list)
+ :Item_udf_sum(udf_arg, list) {}
+ Item_sum_udf_int(THD *thd, Item_sum_udf_int *item)
+ :Item_udf_sum(thd, item) {}
+ longlong val_int();
+ double val_real()
+ { DBUG_ASSERT(fixed == 1); return (double) Item_sum_udf_int::val_int(); }
+ String *val_str(String*str);
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type () const { return INT_RESULT; }
+ void fix_length_and_dec() { decimals=0; max_length=21; }
+ Item *copy_or_same(THD* thd);
+};
+
+
+class Item_sum_udf_str :public Item_udf_sum
+{
+public:
+ Item_sum_udf_str(udf_func *udf_arg)
+ :Item_udf_sum(udf_arg) {}
+ Item_sum_udf_str(udf_func *udf_arg, List<Item> &list)
+ :Item_udf_sum(udf_arg,list) {}
+ Item_sum_udf_str(THD *thd, Item_sum_udf_str *item)
+ :Item_udf_sum(thd, item) {}
+ String *val_str(String *);
+ double val_real()
+ {
+ int err_not_used;
+ char *end_not_used;
+ String *res;
+ res=val_str(&str_value);
+ return res ? my_strntod(res->charset(),(char*) res->ptr(),res->length(),
+ &end_not_used, &err_not_used) : 0.0;
+ }
+ longlong val_int()
+ {
+ int err_not_used;
+ char *end;
+ String *res;
+ CHARSET_INFO *cs;
+
+ if (!(res= val_str(&str_value)))
+ return 0; /* Null value */
+ cs= res->charset();
+ end= (char*) res->ptr()+res->length();
+ return cs->cset->strtoll10(cs, res->ptr(), &end, &err_not_used);
+ }
+ my_decimal *val_decimal(my_decimal *dec);
+ enum Item_result result_type () const { return STRING_RESULT; }
+ void fix_length_and_dec();
+ Item *copy_or_same(THD* thd);
+};
+
+
+class Item_sum_udf_decimal :public Item_udf_sum
+{
+public:
+ Item_sum_udf_decimal(udf_func *udf_arg)
+ :Item_udf_sum(udf_arg) {}
+ Item_sum_udf_decimal(udf_func *udf_arg, List<Item> &list)
+ :Item_udf_sum(udf_arg, list) {}
+ Item_sum_udf_decimal(THD *thd, Item_sum_udf_decimal *item)
+ :Item_udf_sum(thd, item) {}
+ String *val_str(String *);
+ double val_real();
+ longlong val_int();
+ my_decimal *val_decimal(my_decimal *);
+ enum Item_result result_type () const { return DECIMAL_RESULT; }
+ void fix_length_and_dec() { fix_num_length_and_dec(); }
+ Item *copy_or_same(THD* thd);
+};
+
+#else /* Dummy functions to get sql_yacc.cc compiled */
+
+class Item_sum_udf_float :public Item_sum_num
+{
+ public:
+ Item_sum_udf_float(udf_func *udf_arg)
+ :Item_sum_num() {}
+ Item_sum_udf_float(udf_func *udf_arg, List<Item> &list) :Item_sum_num() {}
+ Item_sum_udf_float(THD *thd, Item_sum_udf_float *item)
+ :Item_sum_num(thd, item) {}
+ enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; }
+ double val_real() { DBUG_ASSERT(fixed == 1); return 0.0; }
+ void clear() {}
+ bool add() { return 0; }
+ void update_field() {}
+};
+
+
+class Item_sum_udf_int :public Item_sum_num
+{
+public:
+ Item_sum_udf_int(udf_func *udf_arg)
+ :Item_sum_num() {}
+ Item_sum_udf_int(udf_func *udf_arg, List<Item> &list) :Item_sum_num() {}
+ Item_sum_udf_int(THD *thd, Item_sum_udf_int *item)
+ :Item_sum_num(thd, item) {}
+ enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; }
+ longlong val_int() { DBUG_ASSERT(fixed == 1); return 0; }
+ double val_real() { DBUG_ASSERT(fixed == 1); return 0; }
+ void clear() {}
+ bool add() { return 0; }
+ void update_field() {}
+};
+
+
+class Item_sum_udf_decimal :public Item_sum_num
+{
+ public:
+ Item_sum_udf_decimal(udf_func *udf_arg)
+ :Item_sum_num() {}
+ Item_sum_udf_decimal(udf_func *udf_arg, List<Item> &list)
+ :Item_sum_num() {}
+ Item_sum_udf_decimal(THD *thd, Item_sum_udf_float *item)
+ :Item_sum_num(thd, item) {}
+ enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; }
+ double val_real() { DBUG_ASSERT(fixed == 1); return 0.0; }
+ my_decimal *val_decimal(my_decimal *) { DBUG_ASSERT(fixed == 1); return 0; }
+ void clear() {}
+ bool add() { return 0; }
+ void update_field() {}
+};
+
+
+class Item_sum_udf_str :public Item_sum_num
+{
+public:
+ Item_sum_udf_str(udf_func *udf_arg)
+ :Item_sum_num() {}
+ Item_sum_udf_str(udf_func *udf_arg, List<Item> &list)
+ :Item_sum_num() {}
+ Item_sum_udf_str(THD *thd, Item_sum_udf_str *item)
+ :Item_sum_num(thd, item) {}
+ String *val_str(String *)
+ { DBUG_ASSERT(fixed == 1); null_value=1; return 0; }
+ double val_real() { DBUG_ASSERT(fixed == 1); null_value=1; return 0.0; }
+ longlong val_int() { DBUG_ASSERT(fixed == 1); null_value=1; return 0; }
+ enum Item_result result_type () const { return STRING_RESULT; }
+ void fix_length_and_dec() { maybe_null=1; max_length=0; }
+ enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; }
+ void clear() {}
+ bool add() { return 0; }
+ void update_field() {}
+};
+
+#endif /* HAVE_DLOPEN */
+
+C_MODE_START
+int group_concat_key_cmp_with_distinct(void* arg, const void* key1,
+ const void* key2);
+int group_concat_key_cmp_with_order(void* arg, const void* key1,
+ const void* key2);
+int dump_leaf_key(void* key_arg,
+ element_count count __attribute__((unused)),
+ void* item_arg);
+C_MODE_END
+
+class Item_func_group_concat : public Item_sum
+{
+ TMP_TABLE_PARAM *tmp_table_param;
+ String result;
+ String *separator;
+ TREE tree_base;
+ TREE *tree;
+
+ /**
+ If DISTINCT is used with this GROUP_CONCAT, this member is used to filter
+ out duplicates.
+ @see Item_func_group_concat::setup
+ @see Item_func_group_concat::add
+ @see Item_func_group_concat::clear
+ */
+ Unique *unique_filter;
+ TABLE *table;
+ ORDER **order;
+ Name_resolution_context *context;
+ /** The number of ORDER BY items. */
+ uint arg_count_order;
+ /** The number of selected items, aka the expr list. */
+ uint arg_count_field;
+ uint row_count;
+ bool distinct;
+ bool warning_for_row;
+ bool always_null;
+ bool force_copy_fields;
+ bool no_appended;
+ /*
+ Following is 0 normal object and pointer to original one for copy
+ (to correctly free resources)
+ */
+ Item_func_group_concat *original;
+
+ friend int group_concat_key_cmp_with_distinct(void* arg, const void* key1,
+ const void* key2);
+ friend int group_concat_key_cmp_with_order(void* arg, const void* key1,
+ const void* key2);
+ friend int dump_leaf_key(void* key_arg,
+ element_count count __attribute__((unused)),
+ void* item_arg);
+
+public:
+ Item_func_group_concat(Name_resolution_context *context_arg,
+ bool is_distinct, List<Item> *is_select,
+ const SQL_I_List<ORDER> &is_order, String *is_separator);
+
+ Item_func_group_concat(THD *thd, Item_func_group_concat *item);
+ ~Item_func_group_concat();
+ void cleanup();
+
+ enum Sumfunctype sum_func () const {return GROUP_CONCAT_FUNC;}
+ const char *func_name() const { return "group_concat"; }
+ virtual Item_result result_type () const { return STRING_RESULT; }
+ virtual Field *make_string_field(TABLE *table);
+ enum_field_types field_type() const
+ {
+ if (too_big_for_varchar())
+ return MYSQL_TYPE_BLOB;
+ else
+ return MYSQL_TYPE_VARCHAR;
+ }
+ void clear();
+ bool add();
+ void reset_field() { DBUG_ASSERT(0); } // not used
+ void update_field() { DBUG_ASSERT(0); } // not used
+ bool fix_fields(THD *,Item **);
+ bool setup(THD *thd);
+ void make_unique();
+ double val_real()
+ {
+ int error;
+ const char *end;
+ String *res;
+ if (!(res= val_str(&str_value)))
+ return 0.0;
+ end= res->ptr() + res->length();
+ return (my_strtod(res->ptr(), (char**) &end, &error));
+ }
+ longlong val_int()
+ {
+ String *res;
+ char *end_ptr;
+ int error;
+ if (!(res= val_str(&str_value)))
+ return (longlong) 0;
+ end_ptr= (char*) res->ptr()+ res->length();
+ return my_strtoll10(res->ptr(), &end_ptr, &error);
+ }
+ my_decimal *val_decimal(my_decimal *decimal_value)
+ {
+ return val_decimal_from_string(decimal_value);
+ }
+ String* val_str(String* str);
+ Item *copy_or_same(THD* thd);
+ void no_rows_in_result() {}
+ virtual void print(String *str, enum_query_type query_type);
+ virtual bool change_context_processor(uchar *cntx)
+ { context= (Name_resolution_context *)cntx; return FALSE; }
+};
+
+#endif /* ITEM_SUM_INCLUDED */
diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc
new file mode 100644
index 00000000000..fb55b7660cb
--- /dev/null
+++ b/sql/item_timefunc.cc
@@ -0,0 +1,3240 @@
+/*
+ Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab
+
+ 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 */
+
+
+/**
+ @file
+
+ @brief
+ This file defines all time functions
+
+ @todo
+ Move month and days to language files
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h"
+#include "sql_locale.h" // MY_LOCALE my_locale_en_US
+#include "strfunc.h" // check_word
+#include "sql_time.h" // make_truncated_value_warning,
+ // get_date_from_daynr,
+ // calc_weekday, calc_week,
+ // convert_month_to_period,
+ // convert_period_to_month,
+ // TIME_to_timestamp,
+ // calc_time_diff,
+ // calc_time_from_sec,
+ // get_date_time_format_str
+#include "tztime.h" // struct Time_zone
+#include "sql_class.h" // THD
+#include <m_ctype.h>
+#include <time.h>
+
+/** Day number for Dec 31st, 9999. */
+#define MAX_DAY_NUMBER 3652424L
+
+/*
+ Date formats corresponding to compound %r and %T conversion specifiers
+
+ Note: We should init at least first element of "positions" array
+ (first member) or hpux11 compiler will die horribly.
+*/
+static DATE_TIME_FORMAT time_ampm_format= {{0}, '\0', 0,
+ {(char *)"%I:%i:%S %p", 11}};
+static DATE_TIME_FORMAT time_24hrs_format= {{0}, '\0', 0,
+ {(char *)"%H:%i:%S", 8}};
+
+/**
+ Extract datetime value to MYSQL_TIME struct from string value
+ according to format string.
+
+ @param format date/time format specification
+ @param val String to decode
+ @param length Length of string
+ @param l_time Store result here
+ @param cached_timestamp_type It uses to get an appropriate warning
+ in the case when the value is truncated.
+ @param sub_pattern_end if non-zero then we are parsing string which
+ should correspond compound specifier (like %T or
+ %r) and this parameter is pointer to place where
+ pointer to end of string matching this specifier
+ should be stored.
+
+ @note
+ Possibility to parse strings matching to patterns equivalent to compound
+ specifiers is mainly intended for use from inside of this function in
+ order to understand %T and %r conversion specifiers, so number of
+ conversion specifiers that can be used in such sub-patterns is limited.
+ Also most of checks are skipped in this case.
+
+ @note
+ If one adds new format specifiers to this function he should also
+ consider adding them to get_date_time_result_type() function.
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+static bool extract_date_time(DATE_TIME_FORMAT *format,
+ const char *val, uint length, MYSQL_TIME *l_time,
+ timestamp_type cached_timestamp_type,
+ const char **sub_pattern_end,
+ const char *date_time_type,
+ ulonglong fuzzy_date)
+{
+ int weekday= 0, yearday= 0, daypart= 0;
+ int week_number= -1;
+ int error= 0;
+ int strict_week_number_year= -1;
+ int frac_part;
+ bool usa_time= 0;
+ bool UNINIT_VAR(sunday_first_n_first_week_non_iso);
+ bool UNINIT_VAR(strict_week_number);
+ bool UNINIT_VAR(strict_week_number_year_type);
+ const char *val_begin= val;
+ const char *val_end= val + length;
+ const char *ptr= format->format.str;
+ const char *end= ptr + format->format.length;
+ CHARSET_INFO *cs= &my_charset_bin;
+ DBUG_ENTER("extract_date_time");
+
+ if (!sub_pattern_end)
+ bzero((char*) l_time, sizeof(*l_time));
+
+ l_time->time_type= cached_timestamp_type;
+
+ for (; ptr != end && val != val_end; ptr++)
+ {
+ /* Skip pre-space between each argument */
+ if ((val+= cs->cset->scan(cs, val, val_end, MY_SEQ_SPACES)) >= val_end)
+ break;
+
+ if (*ptr == '%' && ptr+1 != end)
+ {
+ int val_len;
+ char *tmp;
+
+ error= 0;
+
+ val_len= (uint) (val_end - val);
+ switch (*++ptr) {
+ /* Year */
+ case 'Y':
+ tmp= (char*) val + MY_MIN(4, val_len);
+ l_time->year= (int) my_strtoll10(val, &tmp, &error);
+ if ((int) (tmp-val) <= 2)
+ l_time->year= year_2000_handling(l_time->year);
+ val= tmp;
+ break;
+ case 'y':
+ tmp= (char*) val + MY_MIN(2, val_len);
+ l_time->year= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ l_time->year= year_2000_handling(l_time->year);
+ break;
+
+ /* Month */
+ case 'm':
+ case 'c':
+ tmp= (char*) val + MY_MIN(2, val_len);
+ l_time->month= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ break;
+ case 'M':
+ if ((l_time->month= check_word(my_locale_en_US.month_names,
+ val, val_end, &val)) <= 0)
+ goto err;
+ break;
+ case 'b':
+ if ((l_time->month= check_word(my_locale_en_US.ab_month_names,
+ val, val_end, &val)) <= 0)
+ goto err;
+ break;
+ /* Day */
+ case 'd':
+ case 'e':
+ tmp= (char*) val + MY_MIN(2, val_len);
+ l_time->day= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ break;
+ case 'D':
+ tmp= (char*) val + MY_MIN(2, val_len);
+ l_time->day= (int) my_strtoll10(val, &tmp, &error);
+ /* Skip 'st, 'nd, 'th .. */
+ val= tmp + MY_MIN((int) (val_end-tmp), 2);
+ break;
+
+ /* Hour */
+ case 'h':
+ case 'I':
+ case 'l':
+ usa_time= 1;
+ /* fall through */
+ case 'k':
+ case 'H':
+ tmp= (char*) val + MY_MIN(2, val_len);
+ l_time->hour= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ break;
+
+ /* Minute */
+ case 'i':
+ tmp= (char*) val + MY_MIN(2, val_len);
+ l_time->minute= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ break;
+
+ /* Second */
+ case 's':
+ case 'S':
+ tmp= (char*) val + MY_MIN(2, val_len);
+ l_time->second= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ break;
+
+ /* Second part */
+ case 'f':
+ tmp= (char*) val_end;
+ if (tmp - val > 6)
+ tmp= (char*) val + 6;
+ l_time->second_part= (int) my_strtoll10(val, &tmp, &error);
+ frac_part= 6 - (int) (tmp - val);
+ if (frac_part > 0)
+ l_time->second_part*= (ulong) log_10_int[frac_part];
+ val= tmp;
+ break;
+
+ /* AM / PM */
+ case 'p':
+ if (val_len < 2 || ! usa_time)
+ goto err;
+ if (!my_strnncoll(&my_charset_latin1,
+ (const uchar *) val, 2,
+ (const uchar *) "PM", 2))
+ daypart= 12;
+ else if (my_strnncoll(&my_charset_latin1,
+ (const uchar *) val, 2,
+ (const uchar *) "AM", 2))
+ goto err;
+ val+= 2;
+ break;
+
+ /* Exotic things */
+ case 'W':
+ if ((weekday= check_word(my_locale_en_US.day_names, val, val_end, &val)) <= 0)
+ goto err;
+ break;
+ case 'a':
+ if ((weekday= check_word(my_locale_en_US.ab_day_names, val, val_end, &val)) <= 0)
+ goto err;
+ break;
+ case 'w':
+ tmp= (char*) val + 1;
+ if ((weekday= (int) my_strtoll10(val, &tmp, &error)) < 0 ||
+ weekday >= 7)
+ goto err;
+ /* We should use the same 1 - 7 scale for %w as for %W */
+ if (!weekday)
+ weekday= 7;
+ val= tmp;
+ break;
+ case 'j':
+ tmp= (char*) val + MY_MIN(val_len, 3);
+ yearday= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ break;
+
+ /* Week numbers */
+ case 'V':
+ case 'U':
+ case 'v':
+ case 'u':
+ sunday_first_n_first_week_non_iso= (*ptr=='U' || *ptr== 'V');
+ strict_week_number= (*ptr=='V' || *ptr=='v');
+ tmp= (char*) val + MY_MIN(val_len, 2);
+ if ((week_number= (int) my_strtoll10(val, &tmp, &error)) < 0 ||
+ (strict_week_number && !week_number) ||
+ week_number > 53)
+ goto err;
+ val= tmp;
+ break;
+
+ /* Year used with 'strict' %V and %v week numbers */
+ case 'X':
+ case 'x':
+ strict_week_number_year_type= (*ptr=='X');
+ tmp= (char*) val + MY_MIN(4, val_len);
+ strict_week_number_year= (int) my_strtoll10(val, &tmp, &error);
+ val= tmp;
+ break;
+
+ /* Time in AM/PM notation */
+ case 'r':
+ /*
+ We can't just set error here, as we don't want to generate two
+ warnings in case of errors
+ */
+ if (extract_date_time(&time_ampm_format, val,
+ (uint)(val_end - val), l_time,
+ cached_timestamp_type, &val, "time", fuzzy_date))
+ DBUG_RETURN(1);
+ break;
+
+ /* Time in 24-hour notation */
+ case 'T':
+ if (extract_date_time(&time_24hrs_format, val,
+ (uint)(val_end - val), l_time,
+ cached_timestamp_type, &val, "time", fuzzy_date))
+ DBUG_RETURN(1);
+ break;
+
+ /* Conversion specifiers that match classes of characters */
+ case '.':
+ while (my_ispunct(cs, *val) && val != val_end)
+ val++;
+ break;
+ case '@':
+ while (my_isalpha(cs, *val) && val != val_end)
+ val++;
+ break;
+ case '#':
+ while (my_isdigit(cs, *val) && val != val_end)
+ val++;
+ break;
+ default:
+ goto err;
+ }
+ if (error) // Error from my_strtoll10
+ goto err;
+ }
+ else if (!my_isspace(cs, *ptr))
+ {
+ if (*val != *ptr)
+ goto err;
+ val++;
+ }
+ }
+ if (usa_time)
+ {
+ if (l_time->hour > 12 || l_time->hour < 1)
+ goto err;
+ l_time->hour= l_time->hour%12+daypart;
+ }
+
+ /*
+ If we are recursively called for parsing string matching compound
+ specifiers we are already done.
+ */
+ if (sub_pattern_end)
+ {
+ *sub_pattern_end= val;
+ DBUG_RETURN(0);
+ }
+
+ if (yearday > 0)
+ {
+ uint days;
+ days= calc_daynr(l_time->year,1,1) + yearday - 1;
+ if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day))
+ goto err;
+ }
+
+ if (week_number >= 0 && weekday)
+ {
+ int days;
+ uint weekday_b;
+
+ /*
+ %V,%v require %X,%x resprectively,
+ %U,%u should be used with %Y and not %X or %x
+ */
+ if ((strict_week_number &&
+ (strict_week_number_year < 0 ||
+ strict_week_number_year_type !=
+ sunday_first_n_first_week_non_iso)) ||
+ (!strict_week_number && strict_week_number_year >= 0))
+ goto err;
+
+ /* Number of days since year 0 till 1st Jan of this year */
+ days= calc_daynr((strict_week_number ? strict_week_number_year :
+ l_time->year),
+ 1, 1);
+ /* Which day of week is 1st Jan of this year */
+ weekday_b= calc_weekday(days, sunday_first_n_first_week_non_iso);
+
+ /*
+ Below we are going to sum:
+ 1) number of days since year 0 till 1st day of 1st week of this year
+ 2) number of days between 1st week and our week
+ 3) and position of our day in the week
+ */
+ if (sunday_first_n_first_week_non_iso)
+ {
+ days+= ((weekday_b == 0) ? 0 : 7) - weekday_b +
+ (week_number - 1) * 7 +
+ weekday % 7;
+ }
+ else
+ {
+ days+= ((weekday_b <= 3) ? 0 : 7) - weekday_b +
+ (week_number - 1) * 7 +
+ (weekday - 1);
+ }
+
+ if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day))
+ goto err;
+ }
+
+ if (l_time->month > 12 || l_time->day > 31 || l_time->hour > 23 ||
+ l_time->minute > 59 || l_time->second > 59)
+ goto err;
+
+ int was_cut;
+ if (check_date(l_time, fuzzy_date | TIME_INVALID_DATES, &was_cut))
+ goto err;
+
+ if (val != val_end)
+ {
+ do
+ {
+ if (!my_isspace(&my_charset_latin1,*val))
+ {
+ make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ val_begin, length,
+ cached_timestamp_type, NullS);
+ break;
+ }
+ } while (++val != val_end);
+ }
+ DBUG_RETURN(0);
+
+err:
+ {
+ char buff[128];
+ strmake(buff, val_begin, MY_MIN(length, sizeof(buff)-1));
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_VALUE_FOR_TYPE, ER(ER_WRONG_VALUE_FOR_TYPE),
+ date_time_type, buff, "str_to_date");
+ }
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Create a formated date/time value in a string.
+*/
+
+static bool make_date_time(DATE_TIME_FORMAT *format, MYSQL_TIME *l_time,
+ timestamp_type type, MY_LOCALE *locale, String *str)
+{
+ char intbuff[15];
+ uint hours_i;
+ uint weekday;
+ ulong length;
+ const char *ptr, *end;
+
+ str->length(0);
+
+ if (l_time->neg)
+ str->append('-');
+
+ end= (ptr= format->format.str) + format->format.length;
+ for (; ptr != end ; ptr++)
+ {
+ if (*ptr != '%' || ptr+1 == end)
+ str->append(*ptr);
+ else
+ {
+ switch (*++ptr) {
+ case 'M':
+ if (!l_time->month)
+ return 1;
+ str->append(locale->month_names->type_names[l_time->month-1],
+ (uint) strlen(locale->month_names->type_names[l_time->month-1]),
+ system_charset_info);
+ break;
+ case 'b':
+ if (!l_time->month)
+ return 1;
+ str->append(locale->ab_month_names->type_names[l_time->month-1],
+ (uint) strlen(locale->ab_month_names->type_names[l_time->month-1]),
+ system_charset_info);
+ break;
+ case 'W':
+ if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
+ return 1;
+ weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
+ l_time->day),0);
+ str->append(locale->day_names->type_names[weekday],
+ (uint) strlen(locale->day_names->type_names[weekday]),
+ system_charset_info);
+ break;
+ case 'a':
+ if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
+ return 1;
+ weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
+ l_time->day),0);
+ str->append(locale->ab_day_names->type_names[weekday],
+ (uint) strlen(locale->ab_day_names->type_names[weekday]),
+ system_charset_info);
+ break;
+ case 'D':
+ if (type == MYSQL_TIMESTAMP_TIME)
+ return 1;
+ length= (uint) (int10_to_str(l_time->day, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 1, '0');
+ if (l_time->day >= 10 && l_time->day <= 19)
+ str->append(STRING_WITH_LEN("th"));
+ else
+ {
+ switch (l_time->day %10) {
+ case 1:
+ str->append(STRING_WITH_LEN("st"));
+ break;
+ case 2:
+ str->append(STRING_WITH_LEN("nd"));
+ break;
+ case 3:
+ str->append(STRING_WITH_LEN("rd"));
+ break;
+ default:
+ str->append(STRING_WITH_LEN("th"));
+ break;
+ }
+ }
+ break;
+ case 'Y':
+ length= (uint) (int10_to_str(l_time->year, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 4, '0');
+ break;
+ case 'y':
+ length= (uint) (int10_to_str(l_time->year%100, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ break;
+ case 'm':
+ length= (uint) (int10_to_str(l_time->month, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ break;
+ case 'c':
+ length= (uint) (int10_to_str(l_time->month, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 1, '0');
+ break;
+ case 'd':
+ length= (uint) (int10_to_str(l_time->day, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ break;
+ case 'e':
+ length= (uint) (int10_to_str(l_time->day, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 1, '0');
+ break;
+ case 'f':
+ length= (uint) (int10_to_str(l_time->second_part, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 6, '0');
+ break;
+ case 'H':
+ length= (uint) (int10_to_str(l_time->hour, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ break;
+ case 'h':
+ case 'I':
+ hours_i= (l_time->hour%24 + 11)%12+1;
+ length= (uint) (int10_to_str(hours_i, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ break;
+ case 'i': /* minutes */
+ length= (uint) (int10_to_str(l_time->minute, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ break;
+ case 'j':
+ if (type == MYSQL_TIMESTAMP_TIME)
+ return 1;
+ length= (uint) (int10_to_str(calc_daynr(l_time->year,l_time->month,
+ l_time->day) -
+ calc_daynr(l_time->year,1,1) + 1, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 3, '0');
+ break;
+ case 'k':
+ length= (uint) (int10_to_str(l_time->hour, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 1, '0');
+ break;
+ case 'l':
+ hours_i= (l_time->hour%24 + 11)%12+1;
+ length= (uint) (int10_to_str(hours_i, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 1, '0');
+ break;
+ case 'p':
+ hours_i= l_time->hour%24;
+ str->append(hours_i < 12 ? "AM" : "PM",2);
+ break;
+ case 'r':
+ length= sprintf(intbuff, ((l_time->hour % 24) < 12) ?
+ "%02d:%02d:%02d AM" : "%02d:%02d:%02d PM",
+ (l_time->hour+11)%12+1,
+ l_time->minute,
+ l_time->second);
+ str->append(intbuff, length);
+ break;
+ case 'S':
+ case 's':
+ length= (uint) (int10_to_str(l_time->second, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ break;
+ case 'T':
+ length= sprintf(intbuff, "%02d:%02d:%02d",
+ l_time->hour, l_time->minute, l_time->second);
+ str->append(intbuff, length);
+ break;
+ case 'U':
+ case 'u':
+ {
+ uint year;
+ if (type == MYSQL_TIMESTAMP_TIME)
+ return 1;
+ length= (uint) (int10_to_str(calc_week(l_time,
+ (*ptr) == 'U' ?
+ WEEK_FIRST_WEEKDAY : WEEK_MONDAY_FIRST,
+ &year),
+ intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ }
+ break;
+ case 'v':
+ case 'V':
+ {
+ uint year;
+ if (type == MYSQL_TIMESTAMP_TIME)
+ return 1;
+ length= (uint) (int10_to_str(calc_week(l_time,
+ ((*ptr) == 'V' ?
+ (WEEK_YEAR | WEEK_FIRST_WEEKDAY) :
+ (WEEK_YEAR | WEEK_MONDAY_FIRST)),
+ &year),
+ intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 2, '0');
+ }
+ break;
+ case 'x':
+ case 'X':
+ {
+ uint year;
+ if (type == MYSQL_TIMESTAMP_TIME)
+ return 1;
+ (void) calc_week(l_time,
+ ((*ptr) == 'X' ?
+ WEEK_YEAR | WEEK_FIRST_WEEKDAY :
+ WEEK_YEAR | WEEK_MONDAY_FIRST),
+ &year);
+ length= (uint) (int10_to_str(year, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 4, '0');
+ }
+ break;
+ case 'w':
+ if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
+ return 1;
+ weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
+ l_time->day),1);
+ length= (uint) (int10_to_str(weekday, intbuff, 10) - intbuff);
+ str->append_with_prefill(intbuff, length, 1, '0');
+ break;
+
+ default:
+ str->append(*ptr);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/**
+ @details
+ Get a array of positive numbers from a string object.
+ Each number is separated by 1 non digit character
+ Return error if there is too many numbers.
+ If there is too few numbers, assume that the numbers are left out
+ from the high end. This allows one to give:
+ DAY_TO_SECOND as "D MM:HH:SS", "MM:HH:SS" "HH:SS" or as seconds.
+
+ @param length: length of str
+ @param cs: charset of str
+ @param values: array of results
+ @param count: count of elements in result array
+ @param transform_msec: if value is true we suppose
+ that the last part of string value is microseconds
+ and we should transform value to six digit value.
+ For example, '1.1' -> '1.100000'
+*/
+
+static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs,
+ uint count, ulonglong *values,
+ bool transform_msec)
+{
+ const char *end=str+length;
+ uint i;
+ long msec_length= 0;
+
+ while (str != end && !my_isdigit(cs,*str))
+ str++;
+
+ for (i=0 ; i < count ; i++)
+ {
+ longlong value;
+ const char *start= str;
+ for (value=0; str != end && my_isdigit(cs, *str) ; str++)
+ value= value*10 + *str - '0';
+ msec_length= 6 - (str - start);
+ values[i]= value;
+ while (str != end && !my_isdigit(cs,*str))
+ str++;
+ if (str == end && i != count-1)
+ {
+ i++;
+ /* Change values[0...i-1] -> values[0...count-1] */
+ bmove_upp((uchar*) (values+count), (uchar*) (values+i),
+ sizeof(*values)*i);
+ bzero((uchar*) values, sizeof(*values)*(count-i));
+ break;
+ }
+ }
+
+ if (transform_msec && msec_length > 0)
+ values[count - 1] *= (long) log_10_int[msec_length];
+
+ return (str != end);
+}
+
+
+longlong Item_func_period_add::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ ulong period=(ulong) args[0]->val_int();
+ int months=(int) args[1]->val_int();
+
+ if ((null_value=args[0]->null_value || args[1]->null_value) ||
+ period == 0L)
+ return 0; /* purecov: inspected */
+ return (longlong)
+ convert_month_to_period((uint) ((int) convert_period_to_month(period)+
+ months));
+}
+
+
+longlong Item_func_period_diff::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ ulong period1=(ulong) args[0]->val_int();
+ ulong period2=(ulong) args[1]->val_int();
+
+ if ((null_value=args[0]->null_value || args[1]->null_value))
+ return 0; /* purecov: inspected */
+ return (longlong) ((long) convert_period_to_month(period1)-
+ (long) convert_period_to_month(period2));
+}
+
+
+
+longlong Item_func_to_days::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_arg0_date(&ltime, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE))
+ return 0;
+ return (longlong) calc_daynr(ltime.year,ltime.month,ltime.day);
+}
+
+
+longlong Item_func_to_seconds::val_int_endpoint(bool left_endp,
+ bool *incl_endp)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ longlong seconds;
+ longlong days;
+ int dummy; /* unused */
+ if (get_arg0_date(&ltime, TIME_FUZZY_DATES))
+ {
+ /* got NULL, leave the incl_endp intact */
+ return LONGLONG_MIN;
+ }
+ seconds= ltime.hour * 3600L + ltime.minute * 60 + ltime.second;
+ seconds= ltime.neg ? -seconds : seconds;
+ days= (longlong) calc_daynr(ltime.year, ltime.month, ltime.day);
+ seconds+= days * 24L * 3600L;
+ /* Set to NULL if invalid date, but keep the value */
+ null_value= check_date(&ltime,
+ (ltime.year || ltime.month || ltime.day),
+ (TIME_NO_ZERO_IN_DATE | TIME_NO_ZERO_DATE),
+ &dummy);
+ /*
+ Even if the evaluation return NULL, seconds is useful for pruning
+ */
+ return seconds;
+}
+
+longlong Item_func_to_seconds::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ longlong seconds;
+ longlong days;
+ if (get_arg0_date(&ltime, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE))
+ return 0;
+ seconds= ltime.hour * 3600L + ltime.minute * 60 + ltime.second;
+ seconds=ltime.neg ? -seconds : seconds;
+ days= (longlong) calc_daynr(ltime.year, ltime.month, ltime.day);
+ return seconds + days * 24L * 3600L;
+}
+
+/*
+ Get information about this Item tree monotonicity
+
+ SYNOPSIS
+ Item_func_to_days::get_monotonicity_info()
+
+ DESCRIPTION
+ Get information about monotonicity of the function represented by this item
+ tree.
+
+ RETURN
+ See enum_monotonicity_info.
+*/
+
+enum_monotonicity_info Item_func_to_days::get_monotonicity_info() const
+{
+ if (args[0]->type() == Item::FIELD_ITEM)
+ {
+ if (args[0]->field_type() == MYSQL_TYPE_DATE)
+ return MONOTONIC_STRICT_INCREASING_NOT_NULL;
+ if (args[0]->field_type() == MYSQL_TYPE_DATETIME)
+ return MONOTONIC_INCREASING_NOT_NULL;
+ }
+ return NON_MONOTONIC;
+}
+
+enum_monotonicity_info Item_func_to_seconds::get_monotonicity_info() const
+{
+ if (args[0]->type() == Item::FIELD_ITEM)
+ {
+ if (args[0]->field_type() == MYSQL_TYPE_DATE ||
+ args[0]->field_type() == MYSQL_TYPE_DATETIME)
+ return MONOTONIC_STRICT_INCREASING_NOT_NULL;
+ }
+ return NON_MONOTONIC;
+}
+
+
+longlong Item_func_to_days::val_int_endpoint(bool left_endp, bool *incl_endp)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ longlong res;
+ int dummy; /* unused */
+ if (get_arg0_date(&ltime, 0))
+ {
+ /* got NULL, leave the incl_endp intact */
+ return LONGLONG_MIN;
+ }
+ res=(longlong) calc_daynr(ltime.year,ltime.month,ltime.day);
+ /* Set to NULL if invalid date, but keep the value */
+ null_value= check_date(&ltime,
+ (TIME_NO_ZERO_IN_DATE | TIME_NO_ZERO_DATE),
+ &dummy);
+ if (null_value)
+ {
+ /*
+ Even if the evaluation return NULL, the calc_daynr is useful for pruning
+ */
+ if (args[0]->field_type() != MYSQL_TYPE_DATE)
+ *incl_endp= TRUE;
+ return res;
+ }
+
+ if (args[0]->field_type() == MYSQL_TYPE_DATE)
+ {
+ // TO_DAYS() is strictly monotonic for dates, leave incl_endp intact
+ return res;
+ }
+
+ /*
+ Handle the special but practically useful case of datetime values that
+ point to day bound ("strictly less" comparison stays intact):
+
+ col < '2007-09-15 00:00:00' -> TO_DAYS(col) < TO_DAYS('2007-09-15')
+ col > '2007-09-15 23:59:59' -> TO_DAYS(col) > TO_DAYS('2007-09-15')
+
+ which is different from the general case ("strictly less" changes to
+ "less or equal"):
+
+ col < '2007-09-15 12:34:56' -> TO_DAYS(col) <= TO_DAYS('2007-09-15')
+ */
+ if ((!left_endp && !(ltime.hour || ltime.minute || ltime.second ||
+ ltime.second_part)) ||
+ (left_endp && ltime.hour == 23 && ltime.minute == 59 &&
+ ltime.second == 59))
+ /* do nothing */
+ ;
+ else
+ *incl_endp= TRUE;
+ return res;
+}
+
+
+longlong Item_func_dayofyear::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_arg0_date(&ltime, TIME_NO_ZERO_IN_DATE | TIME_NO_ZERO_DATE))
+ return 0;
+ return (longlong) calc_daynr(ltime.year,ltime.month,ltime.day) -
+ calc_daynr(ltime.year,1,1) + 1;
+}
+
+longlong Item_func_dayofmonth::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ return get_arg0_date(&ltime, 0) ? 0 : (longlong) ltime.day;
+}
+
+longlong Item_func_month::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ return get_arg0_date(&ltime, 0) ? 0 : (longlong) ltime.month;
+}
+
+
+void Item_func_monthname::fix_length_and_dec()
+{
+ THD* thd= current_thd;
+ CHARSET_INFO *cs= thd->variables.collation_connection;
+ uint32 repertoire= my_charset_repertoire(cs);
+ locale= thd->variables.lc_time_names;
+ collation.set(cs, DERIVATION_COERCIBLE, repertoire);
+ decimals=0;
+ max_length= locale->max_month_name_length * collation.collation->mbmaxlen;
+ maybe_null=1;
+}
+
+
+String* Item_func_monthname::val_str(String* str)
+{
+ DBUG_ASSERT(fixed == 1);
+ const char *month_name;
+ uint err;
+ MYSQL_TIME ltime;
+
+ if ((null_value= (get_arg0_date(&ltime, 0) || !ltime.month)))
+ return (String *) 0;
+
+ month_name= locale->month_names->type_names[ltime.month - 1];
+ str->copy(month_name, (uint) strlen(month_name), &my_charset_utf8_bin,
+ collation.collation, &err);
+ return str;
+}
+
+
+/**
+ Returns the quarter of the year.
+*/
+
+longlong Item_func_quarter::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_arg0_date(&ltime, 0))
+ return 0;
+ return (longlong) ((ltime.month+2)/3);
+}
+
+longlong Item_func_hour::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ return get_arg0_time(&ltime) ? 0 : ltime.hour;
+}
+
+longlong Item_func_minute::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ return get_arg0_time(&ltime) ? 0 : ltime.minute;
+}
+
+/**
+ Returns the second in time_exp in the range of 0 - 59.
+*/
+longlong Item_func_second::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ return get_arg0_time(&ltime) ? 0 : ltime.second;
+}
+
+
+uint week_mode(uint mode)
+{
+ uint week_format= (mode & 7);
+ if (!(week_format & WEEK_MONDAY_FIRST))
+ week_format^= WEEK_FIRST_WEEKDAY;
+ return week_format;
+}
+
+/**
+ @verbatim
+ The bits in week_format(for calc_week() function) has the following meaning:
+ WEEK_MONDAY_FIRST (0) If not set Sunday is first day of week
+ If set Monday is first day of week
+ WEEK_YEAR (1) If not set Week is in range 0-53
+
+ Week 0 is returned for the the last week of the previous year (for
+ a date at start of january) In this case one can get 53 for the
+ first week of next year. This flag ensures that the week is
+ relevant for the given year. Note that this flag is only
+ releveant if WEEK_JANUARY is not set.
+
+ If set Week is in range 1-53.
+
+ In this case one may get week 53 for a date in January (when
+ the week is that last week of previous year) and week 1 for a
+ date in December.
+
+ WEEK_FIRST_WEEKDAY (2) If not set Weeks are numbered according
+ to ISO 8601:1988
+ If set The week that contains the first
+ 'first-day-of-week' is week 1.
+
+ ISO 8601:1988 means that if the week containing January 1 has
+ four or more days in the new year, then it is week 1;
+ Otherwise it is the last week of the previous year, and the
+ next week is week 1.
+ @endverbatim
+*/
+
+longlong Item_func_week::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint year;
+ MYSQL_TIME ltime;
+ if (get_arg0_date(&ltime, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE))
+ return 0;
+ return (longlong) calc_week(&ltime,
+ week_mode((uint) args[1]->val_int()),
+ &year);
+}
+
+
+longlong Item_func_yearweek::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ uint year,week;
+ MYSQL_TIME ltime;
+ if (get_arg0_date(&ltime, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE))
+ return 0;
+ week= calc_week(&ltime,
+ (week_mode((uint) args[1]->val_int()) | WEEK_YEAR),
+ &year);
+ return week+year*100;
+}
+
+
+longlong Item_func_weekday::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+
+ if (get_arg0_date(&ltime, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE))
+ return 0;
+
+ return (longlong) calc_weekday(calc_daynr(ltime.year, ltime.month,
+ ltime.day),
+ odbc_type) + MY_TEST(odbc_type);
+}
+
+void Item_func_dayname::fix_length_and_dec()
+{
+ THD* thd= current_thd;
+ CHARSET_INFO *cs= thd->variables.collation_connection;
+ uint32 repertoire= my_charset_repertoire(cs);
+ locale= thd->variables.lc_time_names;
+ collation.set(cs, DERIVATION_COERCIBLE, repertoire);
+ decimals=0;
+ max_length= locale->max_day_name_length * collation.collation->mbmaxlen;
+ maybe_null=1;
+}
+
+
+String* Item_func_dayname::val_str(String* str)
+{
+ DBUG_ASSERT(fixed == 1);
+ uint weekday=(uint) val_int(); // Always Item_func_daynr()
+ const char *day_name;
+ uint err;
+
+ if (null_value)
+ return (String*) 0;
+
+ day_name= locale->day_names->type_names[weekday];
+ str->copy(day_name, (uint) strlen(day_name), &my_charset_utf8_bin,
+ collation.collation, &err);
+ return str;
+}
+
+
+longlong Item_func_year::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ return get_arg0_date(&ltime, 0) ? 0 : (longlong) ltime.year;
+}
+
+
+/*
+ Get information about this Item tree monotonicity
+
+ SYNOPSIS
+ Item_func_year::get_monotonicity_info()
+
+ DESCRIPTION
+ Get information about monotonicity of the function represented by this item
+ tree.
+
+ RETURN
+ See enum_monotonicity_info.
+*/
+
+enum_monotonicity_info Item_func_year::get_monotonicity_info() const
+{
+ if (args[0]->type() == Item::FIELD_ITEM &&
+ (args[0]->field_type() == MYSQL_TYPE_DATE ||
+ args[0]->field_type() == MYSQL_TYPE_DATETIME))
+ return MONOTONIC_INCREASING;
+ return NON_MONOTONIC;
+}
+
+
+longlong Item_func_year::val_int_endpoint(bool left_endp, bool *incl_endp)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_arg0_date(&ltime, 0))
+ {
+ /* got NULL, leave the incl_endp intact */
+ return LONGLONG_MIN;
+ }
+
+ /*
+ Handle the special but practically useful case of datetime values that
+ point to year bound ("strictly less" comparison stays intact) :
+
+ col < '2007-01-01 00:00:00' -> YEAR(col) < 2007
+
+ which is different from the general case ("strictly less" changes to
+ "less or equal"):
+
+ col < '2007-09-15 23:00:00' -> YEAR(col) <= 2007
+ */
+ if (!left_endp && ltime.day == 1 && ltime.month == 1 &&
+ !(ltime.hour || ltime.minute || ltime.second || ltime.second_part))
+ ; /* do nothing */
+ else
+ *incl_endp= TRUE;
+ return ltime.year;
+}
+
+
+bool Item_func_unix_timestamp::get_timestamp_value(my_time_t *seconds,
+ ulong *second_part)
+{
+ DBUG_ASSERT(fixed == 1);
+ if (args[0]->type() == FIELD_ITEM)
+ { // Optimize timestamp field
+ Field *field=((Item_field*) args[0])->field;
+ if (field->type() == MYSQL_TYPE_TIMESTAMP)
+ {
+ if ((null_value= field->is_null()))
+ return 1;
+ *seconds= ((Field_timestamp*)field)->get_timestamp(second_part);
+ return 0;
+ }
+ }
+
+ MYSQL_TIME ltime;
+ if (get_arg0_date(&ltime, TIME_NO_ZERO_IN_DATE))
+ return 1;
+
+ uint error_code;
+ *seconds= TIME_to_timestamp(current_thd, &ltime, &error_code);
+ *second_part= ltime.second_part;
+ return (null_value= (error_code == ER_WARN_DATA_OUT_OF_RANGE));
+}
+
+
+longlong Item_func_unix_timestamp::int_op()
+{
+ if (arg_count == 0)
+ return (longlong) current_thd->query_start();
+
+ ulong second_part;
+ my_time_t seconds;
+ if (get_timestamp_value(&seconds, &second_part))
+ return 0;
+
+ return seconds;
+}
+
+
+my_decimal *Item_func_unix_timestamp::decimal_op(my_decimal* buf)
+{
+ ulong second_part;
+ my_time_t seconds;
+ if (get_timestamp_value(&seconds, &second_part))
+ return 0;
+
+ return seconds2my_decimal(seconds < 0, seconds < 0 ? -seconds : seconds,
+ second_part, buf);
+}
+
+
+enum_monotonicity_info Item_func_unix_timestamp::get_monotonicity_info() const
+{
+ if (args[0]->type() == Item::FIELD_ITEM &&
+ (args[0]->field_type() == MYSQL_TYPE_TIMESTAMP))
+ return MONOTONIC_INCREASING;
+ return NON_MONOTONIC;
+}
+
+
+longlong Item_func_unix_timestamp::val_int_endpoint(bool left_endp, bool *incl_endp)
+{
+ DBUG_ASSERT(fixed == 1);
+ DBUG_ASSERT(arg_count == 1 &&
+ args[0]->type() == Item::FIELD_ITEM &&
+ args[0]->field_type() == MYSQL_TYPE_TIMESTAMP);
+ Field_timestamp *field=(Field_timestamp *)(((Item_field*)args[0])->field);
+ /* Leave the incl_endp intact */
+ ulong unused;
+ my_time_t ts= field->get_timestamp(&unused);
+ null_value= field->is_null();
+ return ts;
+}
+
+
+longlong Item_func_time_to_sec::int_op()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_arg0_time(&ltime))
+ return 0;
+
+ longlong seconds=ltime.hour*3600L+ltime.minute*60+ltime.second;
+ return ltime.neg ? -seconds : seconds;
+}
+
+
+my_decimal *Item_func_time_to_sec::decimal_op(my_decimal* buf)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (get_arg0_time(&ltime))
+ return 0;
+
+ longlong seconds= ltime.hour*3600L+ltime.minute*60+ltime.second;
+ return seconds2my_decimal(ltime.neg, seconds, ltime.second_part, buf);
+}
+
+
+/**
+ Convert a string to a interval value.
+
+ To make code easy, allow interval objects without separators.
+*/
+
+bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval)
+{
+ ulonglong array[5];
+ longlong UNINIT_VAR(value);
+ const char *UNINIT_VAR(str);
+ size_t UNINIT_VAR(length);
+ CHARSET_INFO *UNINIT_VAR(cs);
+ char buf[100];
+ String str_value(buf, sizeof(buf), &my_charset_bin);
+
+ bzero((char*) interval,sizeof(*interval));
+ if (int_type == INTERVAL_SECOND && args->decimals)
+ {
+ my_decimal decimal_value, *val;
+ ulonglong second;
+ ulong second_part;
+ if (!(val= args->val_decimal(&decimal_value)))
+ return true;
+ interval->neg= my_decimal2seconds(val, &second, &second_part);
+ if (second == LONGLONG_MAX)
+ {
+ ErrConvDecimal err(val);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), "DECIMAL",
+ err.ptr());
+ return true;
+ }
+
+ interval->second= second;
+ interval->second_part= second_part;
+ return false;
+ }
+ else if ((int) int_type <= INTERVAL_MICROSECOND)
+ {
+ value= args->val_int();
+ if (args->null_value)
+ return 1;
+ if (value < 0)
+ {
+ interval->neg=1;
+ value= -value;
+ }
+ }
+ else
+ {
+ String *res;
+ if (!(res= args->val_str_ascii(&str_value)))
+ return (1);
+
+ /* record negative intervalls in interval->neg */
+ str=res->ptr();
+ cs= res->charset();
+ const char *end=str+res->length();
+ while (str != end && my_isspace(cs,*str))
+ str++;
+ if (str != end && *str == '-')
+ {
+ interval->neg=1;
+ str++;
+ }
+ length= (size_t) (end-str); // Set up pointers to new str
+ }
+
+ switch (int_type) {
+ case INTERVAL_YEAR:
+ interval->year= (ulong) value;
+ break;
+ case INTERVAL_QUARTER:
+ interval->month= (ulong)(value*3);
+ break;
+ case INTERVAL_MONTH:
+ interval->month= (ulong) value;
+ break;
+ case INTERVAL_WEEK:
+ interval->day= (ulong)(value*7);
+ break;
+ case INTERVAL_DAY:
+ interval->day= (ulong) value;
+ break;
+ case INTERVAL_HOUR:
+ interval->hour= (ulong) value;
+ break;
+ case INTERVAL_MICROSECOND:
+ interval->second_part=value;
+ break;
+ case INTERVAL_MINUTE:
+ interval->minute=value;
+ break;
+ case INTERVAL_SECOND:
+ interval->second=value;
+ break;
+ case INTERVAL_YEAR_MONTH: // Allow YEAR-MONTH YYYYYMM
+ if (get_interval_info(str,length,cs,2,array,0))
+ return (1);
+ interval->year= (ulong) array[0];
+ interval->month= (ulong) array[1];
+ break;
+ case INTERVAL_DAY_HOUR:
+ if (get_interval_info(str,length,cs,2,array,0))
+ return (1);
+ interval->day= (ulong) array[0];
+ interval->hour= (ulong) array[1];
+ break;
+ case INTERVAL_DAY_MICROSECOND:
+ if (get_interval_info(str,length,cs,5,array,1))
+ return (1);
+ interval->day= (ulong) array[0];
+ interval->hour= (ulong) array[1];
+ interval->minute= array[2];
+ interval->second= array[3];
+ interval->second_part= array[4];
+ break;
+ case INTERVAL_DAY_MINUTE:
+ if (get_interval_info(str,length,cs,3,array,0))
+ return (1);
+ interval->day= (ulong) array[0];
+ interval->hour= (ulong) array[1];
+ interval->minute= array[2];
+ break;
+ case INTERVAL_DAY_SECOND:
+ if (get_interval_info(str,length,cs,4,array,0))
+ return (1);
+ interval->day= (ulong) array[0];
+ interval->hour= (ulong) array[1];
+ interval->minute= array[2];
+ interval->second= array[3];
+ break;
+ case INTERVAL_HOUR_MICROSECOND:
+ if (get_interval_info(str,length,cs,4,array,1))
+ return (1);
+ interval->hour= (ulong) array[0];
+ interval->minute= array[1];
+ interval->second= array[2];
+ interval->second_part= array[3];
+ break;
+ case INTERVAL_HOUR_MINUTE:
+ if (get_interval_info(str,length,cs,2,array,0))
+ return (1);
+ interval->hour= (ulong) array[0];
+ interval->minute= array[1];
+ break;
+ case INTERVAL_HOUR_SECOND:
+ if (get_interval_info(str,length,cs,3,array,0))
+ return (1);
+ interval->hour= (ulong) array[0];
+ interval->minute= array[1];
+ interval->second= array[2];
+ break;
+ case INTERVAL_MINUTE_MICROSECOND:
+ if (get_interval_info(str,length,cs,3,array,1))
+ return (1);
+ interval->minute= array[0];
+ interval->second= array[1];
+ interval->second_part= array[2];
+ break;
+ case INTERVAL_MINUTE_SECOND:
+ if (get_interval_info(str,length,cs,2,array,0))
+ return (1);
+ interval->minute= array[0];
+ interval->second= array[1];
+ break;
+ case INTERVAL_SECOND_MICROSECOND:
+ if (get_interval_info(str,length,cs,2,array,1))
+ return (1);
+ interval->second= array[0];
+ interval->second_part= array[1];
+ break;
+ case INTERVAL_LAST: /* purecov: begin deadcode */
+ DBUG_ASSERT(0);
+ break; /* purecov: end */
+ }
+ return 0;
+}
+
+
+void Item_temporal_func::fix_length_and_dec()
+{
+ uint char_length= mysql_temporal_int_part_length(field_type());
+ /*
+ We set maybe_null to 1 as default as any bad argument with date or
+ time can get us to return NULL.
+ */
+ maybe_null= 1;
+ if (decimals)
+ {
+ if (decimals == NOT_FIXED_DEC)
+ char_length+= TIME_SECOND_PART_DIGITS + 1;
+ else
+ {
+ set_if_smaller(decimals, TIME_SECOND_PART_DIGITS);
+ char_length+= decimals + 1;
+ }
+ }
+ sql_mode= current_thd->variables.sql_mode &
+ (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE);
+ collation.set(field_type() == MYSQL_TYPE_STRING ?
+ default_charset() : &my_charset_numeric,
+ field_type() == MYSQL_TYPE_STRING ?
+ DERIVATION_COERCIBLE : DERIVATION_NUMERIC,
+ MY_REPERTOIRE_ASCII);
+ fix_char_length(char_length);
+}
+
+String *Item_temporal_func::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ return val_string_from_date(str);
+}
+
+
+bool Item_temporal_hybrid_func::fix_temporal_type(MYSQL_TIME *ltime)
+{
+ if (ltime->time_type < 0) /* MYSQL_TIMESTAMP_NONE, MYSQL_TIMESTAMP_ERROR */
+ return false;
+
+ if (ltime->time_type != MYSQL_TIMESTAMP_TIME)
+ goto date_or_datetime_value;
+
+ /* Convert TIME to DATE or DATETIME */
+ switch (field_type())
+ {
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIMESTAMP:
+ {
+ MYSQL_TIME tmp;
+ if (time_to_datetime_with_warn(current_thd, ltime, &tmp, 0))
+ return (null_value= true);
+ *ltime= tmp;
+ if (field_type() == MYSQL_TYPE_DATE)
+ datetime_to_date(ltime);
+ return false;
+ }
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_STRING: /* DATE_ADD, ADDTIME can return VARCHAR */
+ return false;
+ default:
+ DBUG_ASSERT(0);
+ return (null_value= true);
+ }
+
+date_or_datetime_value:
+ /* Convert DATE or DATETIME to TIME, DATE, or DATETIME */
+ switch (field_type())
+ {
+ case MYSQL_TYPE_TIME:
+ datetime_to_time(ltime);
+ return false;
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIMESTAMP:
+ date_to_datetime(ltime);
+ return false;
+ case MYSQL_TYPE_DATE:
+ datetime_to_date(ltime);
+ return false;
+ case MYSQL_TYPE_STRING: /* DATE_ADD, ADDTIME can return VARCHAR */
+ return false;
+ default:
+ DBUG_ASSERT(0);
+ return (null_value= true);
+ }
+ return false;
+}
+
+
+String *Item_temporal_hybrid_func::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+
+ if (get_date(&ltime, 0) || fix_temporal_type(&ltime) ||
+ (null_value= my_TIME_to_str(&ltime, str, decimals)))
+ return (String *) 0;
+
+ /* Check that the returned timestamp type matches to the function type */
+ DBUG_ASSERT(cached_field_type == MYSQL_TYPE_STRING ||
+ ltime.time_type == MYSQL_TIMESTAMP_NONE ||
+ mysql_type_to_time_type(cached_field_type) == ltime.time_type);
+ return str;
+}
+
+
+bool Item_func_from_days::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ longlong value=args[0]->val_int();
+ if ((null_value= (args[0]->null_value ||
+ ((fuzzy_date & TIME_NO_ZERO_DATE) && value == 0))))
+ return true;
+ bzero(ltime, sizeof(MYSQL_TIME));
+ if (get_date_from_daynr((long) value, &ltime->year, &ltime->month,
+ &ltime->day))
+ return 0;
+
+ ltime->time_type= MYSQL_TIMESTAMP_DATE;
+ return 0;
+}
+
+
+void Item_func_curdate::fix_length_and_dec()
+{
+ store_now_in_TIME(&ltime);
+
+ /* We don't need to set second_part and neg because they already 0 */
+ ltime.hour= ltime.minute= ltime.second= 0;
+ ltime.time_type= MYSQL_TIMESTAMP_DATE;
+ Item_datefunc::fix_length_and_dec();
+ maybe_null= false;
+}
+
+/**
+ Converts current time in my_time_t to MYSQL_TIME represenatation for local
+ time zone. Defines time zone (local) used for whole CURDATE function.
+*/
+void Item_func_curdate_local::store_now_in_TIME(MYSQL_TIME *now_time)
+{
+ THD *thd= current_thd;
+ thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
+ thd->time_zone_used= 1;
+}
+
+
+/**
+ Converts current time in my_time_t to MYSQL_TIME represenatation for UTC
+ time zone. Defines time zone (UTC) used for whole UTC_DATE function.
+*/
+void Item_func_curdate_utc::store_now_in_TIME(MYSQL_TIME *now_time)
+{
+ THD *thd= current_thd;
+ my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
+ /*
+ We are not flagging this query as using time zone, since it uses fixed
+ UTC-SYSTEM time-zone.
+ */
+}
+
+
+bool Item_func_curdate::get_date(MYSQL_TIME *res,
+ ulonglong fuzzy_date __attribute__((unused)))
+{
+ *res=ltime;
+ return 0;
+}
+
+
+bool Item_func_curtime::fix_fields(THD *thd, Item **items)
+{
+ if (decimals > TIME_SECOND_PART_DIGITS)
+ {
+ my_error(ER_TOO_BIG_PRECISION, MYF(0), decimals, func_name(),
+ TIME_SECOND_PART_DIGITS);
+ return 1;
+ }
+ return Item_timefunc::fix_fields(thd, items);
+}
+
+bool Item_func_curtime::get_date(MYSQL_TIME *res,
+ ulonglong fuzzy_date __attribute__((unused)))
+{
+ *res= ltime;
+ return 0;
+}
+
+static void set_sec_part(ulong sec_part, MYSQL_TIME *ltime, Item *item)
+{
+ DBUG_ASSERT(item->decimals == AUTO_SEC_PART_DIGITS ||
+ item->decimals <= TIME_SECOND_PART_DIGITS);
+ if (item->decimals)
+ {
+ ltime->second_part= sec_part;
+ if (item->decimals < TIME_SECOND_PART_DIGITS)
+ my_time_trunc(ltime, item->decimals);
+ }
+}
+
+/**
+ Converts current time in my_time_t to MYSQL_TIME represenatation for local
+ time zone. Defines time zone (local) used for whole CURTIME function.
+*/
+void Item_func_curtime_local::store_now_in_TIME(MYSQL_TIME *now_time)
+{
+ THD *thd= current_thd;
+ thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
+ now_time->year= now_time->month= now_time->day= 0;
+ now_time->time_type= MYSQL_TIMESTAMP_TIME;
+ set_sec_part(thd->query_start_sec_part(), now_time, this);
+ thd->time_zone_used= 1;
+}
+
+
+/**
+ Converts current time in my_time_t to MYSQL_TIME represenatation for UTC
+ time zone. Defines time zone (UTC) used for whole UTC_TIME function.
+*/
+void Item_func_curtime_utc::store_now_in_TIME(MYSQL_TIME *now_time)
+{
+ THD *thd= current_thd;
+ my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
+ now_time->year= now_time->month= now_time->day= 0;
+ now_time->time_type= MYSQL_TIMESTAMP_TIME;
+ set_sec_part(thd->query_start_sec_part(), now_time, this);
+ /*
+ We are not flagging this query as using time zone, since it uses fixed
+ UTC-SYSTEM time-zone.
+ */
+}
+
+bool Item_func_now::fix_fields(THD *thd, Item **items)
+{
+ if (decimals > TIME_SECOND_PART_DIGITS)
+ {
+ my_error(ER_TOO_BIG_PRECISION, MYF(0), decimals, func_name(),
+ TIME_SECOND_PART_DIGITS);
+ return 1;
+ }
+ return Item_temporal_func::fix_fields(thd, items);
+}
+
+/**
+ Converts current time in my_time_t to MYSQL_TIME represenatation for local
+ time zone. Defines time zone (local) used for whole NOW function.
+*/
+void Item_func_now_local::store_now_in_TIME(MYSQL_TIME *now_time)
+{
+ THD *thd= current_thd;
+ thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
+ set_sec_part(thd->query_start_sec_part(), now_time, this);
+ thd->time_zone_used= 1;
+}
+
+
+/**
+ Converts current time in my_time_t to MYSQL_TIME represenatation for UTC
+ time zone. Defines time zone (UTC) used for whole UTC_TIMESTAMP function.
+*/
+void Item_func_now_utc::store_now_in_TIME(MYSQL_TIME *now_time)
+{
+ THD *thd= current_thd;
+ my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
+ set_sec_part(thd->query_start_sec_part(), now_time, this);
+ /*
+ We are not flagging this query as using time zone, since it uses fixed
+ UTC-SYSTEM time-zone.
+ */
+}
+
+
+bool Item_func_now::get_date(MYSQL_TIME *res,
+ ulonglong fuzzy_date __attribute__((unused)))
+{
+ *res= ltime;
+ return 0;
+}
+
+
+/**
+ Converts current time in my_time_t to MYSQL_TIME represenatation for local
+ time zone. Defines time zone (local) used for whole SYSDATE function.
+*/
+void Item_func_sysdate_local::store_now_in_TIME(MYSQL_TIME *now_time)
+{
+ THD *thd= current_thd;
+ my_hrtime_t now= my_hrtime();
+ thd->variables.time_zone->gmt_sec_to_TIME(now_time, hrtime_to_my_time(now));
+ set_sec_part(hrtime_sec_part(now), now_time, this);
+ thd->time_zone_used= 1;
+}
+
+
+bool Item_func_sysdate_local::get_date(MYSQL_TIME *res,
+ ulonglong fuzzy_date __attribute__((unused)))
+{
+ store_now_in_TIME(res);
+ return 0;
+}
+
+bool Item_func_sec_to_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed == 1);
+ bool sign;
+ ulonglong sec;
+ ulong sec_part;
+
+ bzero((char *)ltime, sizeof(*ltime));
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+
+ sign= args[0]->get_seconds(&sec, &sec_part);
+
+ if ((null_value= args[0]->null_value))
+ return 1;
+
+ ltime->neg= sign;
+ if (sec > TIME_MAX_VALUE_SECONDS)
+ goto overflow;
+
+ DBUG_ASSERT(sec_part <= TIME_MAX_SECOND_PART);
+
+ ltime->hour= (uint) (sec/3600);
+ ltime->minute= (uint) (sec % 3600) /60;
+ ltime->second= (uint) sec % 60;
+ ltime->second_part= sec_part;
+
+ return 0;
+
+overflow:
+ /* use check_time_range() to set ltime to the max value depending on dec */
+ int unused;
+ char buf[100];
+ String tmp(buf, sizeof(buf), &my_charset_bin), *err= args[0]->val_str(&tmp);
+
+ ltime->hour= TIME_MAX_HOUR+1;
+ check_time_range(ltime, decimals, &unused);
+ make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ err->ptr(), err->length(),
+ MYSQL_TIMESTAMP_TIME, NullS);
+ return 0;
+}
+
+void Item_func_date_format::fix_length_and_dec()
+{
+ THD* thd= current_thd;
+ locale= thd->variables.lc_time_names;
+
+ /*
+ Must use this_item() in case it's a local SP variable
+ (for ->max_length and ->str_value)
+ */
+ Item *arg1= args[1]->this_item();
+
+ decimals=0;
+ CHARSET_INFO *cs= thd->variables.collation_connection;
+ uint32 repertoire= arg1->collation.repertoire;
+ if (!thd->variables.lc_time_names->is_ascii)
+ repertoire|= MY_REPERTOIRE_EXTENDED;
+ collation.set(cs, arg1->collation.derivation, repertoire);
+ if (arg1->type() == STRING_ITEM)
+ { // Optimize the normal case
+ fixed_length=1;
+ max_length= format_length(arg1->val_str(NULL)) *
+ collation.collation->mbmaxlen;
+ }
+ else
+ {
+ fixed_length=0;
+ max_length=MY_MIN(arg1->max_length, MAX_BLOB_WIDTH) * 10 *
+ collation.collation->mbmaxlen;
+ set_if_smaller(max_length,MAX_BLOB_WIDTH);
+ }
+ maybe_null=1; // If wrong date
+}
+
+
+bool Item_func_date_format::eq(const Item *item, bool binary_cmp) const
+{
+ Item_func_date_format *item_func;
+
+ if (item->type() != FUNC_ITEM)
+ return 0;
+ if (func_name() != ((Item_func*) item)->func_name())
+ return 0;
+ if (this == item)
+ return 1;
+ item_func= (Item_func_date_format*) item;
+ if (!args[0]->eq(item_func->args[0], binary_cmp))
+ return 0;
+ /*
+ We must compare format string case sensitive.
+ This needed because format modifiers with different case,
+ for example %m and %M, have different meaning.
+ */
+ if (!args[1]->eq(item_func->args[1], 1))
+ return 0;
+ return 1;
+}
+
+
+
+uint Item_func_date_format::format_length(const String *format)
+{
+ uint size=0;
+ const char *ptr=format->ptr();
+ const char *end=ptr+format->length();
+
+ for (; ptr != end ; ptr++)
+ {
+ if (*ptr != '%' || ptr == end-1)
+ size++;
+ else
+ {
+ switch(*++ptr) {
+ case 'M': /* month, textual */
+ case 'W': /* day (of the week), textual */
+ size += 64; /* large for UTF8 locale data */
+ break;
+ case 'D': /* day (of the month), numeric plus english suffix */
+ case 'Y': /* year, numeric, 4 digits */
+ case 'x': /* Year, used with 'v' */
+ case 'X': /* Year, used with 'v, where week starts with Monday' */
+ size += 4;
+ break;
+ case 'a': /* locale's abbreviated weekday name (Sun..Sat) */
+ case 'b': /* locale's abbreviated month name (Jan.Dec) */
+ size += 32; /* large for UTF8 locale data */
+ break;
+ case 'j': /* day of year (001..366) */
+ size += 3;
+ break;
+ case 'U': /* week (00..52) */
+ case 'u': /* week (00..52), where week starts with Monday */
+ case 'V': /* week 1..53 used with 'x' */
+ case 'v': /* week 1..53 used with 'x', where week starts with Monday */
+ case 'y': /* year, numeric, 2 digits */
+ case 'm': /* month, numeric */
+ case 'd': /* day (of the month), numeric */
+ case 'h': /* hour (01..12) */
+ case 'I': /* --||-- */
+ case 'i': /* minutes, numeric */
+ case 'l': /* hour ( 1..12) */
+ case 'p': /* locale's AM or PM */
+ case 'S': /* second (00..61) */
+ case 's': /* seconds, numeric */
+ case 'c': /* month (0..12) */
+ case 'e': /* day (0..31) */
+ size += 2;
+ break;
+ case 'k': /* hour ( 0..23) */
+ case 'H': /* hour (00..23; value > 23 OK, padding always 2-digit) */
+ size += 7; /* docs allow > 23, range depends on sizeof(unsigned int) */
+ break;
+ case 'r': /* time, 12-hour (hh:mm:ss [AP]M) */
+ size += 11;
+ break;
+ case 'T': /* time, 24-hour (hh:mm:ss) */
+ size += 8;
+ break;
+ case 'f': /* microseconds */
+ size += 6;
+ break;
+ case 'w': /* day (of the week), numeric */
+ case '%':
+ default:
+ size++;
+ break;
+ }
+ }
+ }
+ return size;
+}
+
+
+String *Item_func_date_format::val_str(String *str)
+{
+ String *format;
+ MYSQL_TIME l_time;
+ uint size;
+ int is_time_flag = is_time_format ? TIME_TIME_ONLY : 0;
+ DBUG_ASSERT(fixed == 1);
+
+ if (get_arg0_date(&l_time, is_time_flag))
+ return 0;
+
+ if (!(format = args[1]->val_str(str)) || !format->length())
+ goto null_date;
+
+ if (fixed_length)
+ size=max_length;
+ else
+ size=format_length(format);
+
+ if (size < MAX_DATE_STRING_REP_LENGTH)
+ size= MAX_DATE_STRING_REP_LENGTH;
+
+ if (format == str)
+ str= &value; // Save result here
+ if (str->alloc(size))
+ goto null_date;
+
+ DATE_TIME_FORMAT date_time_format;
+ date_time_format.format.str= (char*) format->ptr();
+ date_time_format.format.length= format->length();
+
+ /* Create the result string */
+ str->set_charset(collation.collation);
+ if (!make_date_time(&date_time_format, &l_time,
+ is_time_format ? MYSQL_TIMESTAMP_TIME :
+ MYSQL_TIMESTAMP_DATE,
+ locale, str))
+ return str;
+
+null_date:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_from_unixtime::fix_length_and_dec()
+{
+ THD *thd= current_thd;
+ thd->time_zone_used= 1;
+ tz= thd->variables.time_zone;
+ decimals= args[0]->decimals;
+ Item_temporal_func::fix_length_and_dec();
+}
+
+
+bool Item_func_from_unixtime::get_date(MYSQL_TIME *ltime,
+ ulonglong fuzzy_date __attribute__((unused)))
+{
+ bool sign;
+ ulonglong sec;
+ ulong sec_part;
+
+ bzero((char *)ltime, sizeof(*ltime));
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+
+ sign= args[0]->get_seconds(&sec, &sec_part);
+
+ if (args[0]->null_value || sign || sec > TIMESTAMP_MAX_VALUE)
+ return (null_value= 1);
+
+ tz->gmt_sec_to_TIME(ltime, (my_time_t)sec);
+
+ ltime->second_part= sec_part;
+
+ return (null_value= 0);
+}
+
+
+void Item_func_convert_tz::fix_length_and_dec()
+{
+ decimals= args[0]->temporal_precision(MYSQL_TYPE_DATETIME);
+ Item_temporal_func::fix_length_and_dec();
+}
+
+
+bool Item_func_convert_tz::get_date(MYSQL_TIME *ltime,
+ ulonglong fuzzy_date __attribute__((unused)))
+{
+ my_time_t my_time_tmp;
+ String str;
+ THD *thd= current_thd;
+
+ if (!from_tz_cached)
+ {
+ from_tz= my_tz_find(thd, args[1]->val_str_ascii(&str));
+ from_tz_cached= args[1]->const_item();
+ }
+
+ if (!to_tz_cached)
+ {
+ to_tz= my_tz_find(thd, args[2]->val_str_ascii(&str));
+ to_tz_cached= args[2]->const_item();
+ }
+
+ if (from_tz==0 || to_tz==0 ||
+ get_arg0_date(ltime, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE))
+ return (null_value= 1);
+
+ {
+ uint not_used;
+ my_time_tmp= from_tz->TIME_to_gmt_sec(ltime, &not_used);
+ ulong sec_part= ltime->second_part;
+ /* my_time_tmp is guranteed to be in the allowed range */
+ if (my_time_tmp)
+ to_tz->gmt_sec_to_TIME(ltime, my_time_tmp);
+ /* we rely on the fact that no timezone conversion can change sec_part */
+ ltime->second_part= sec_part;
+ }
+
+ return (null_value= 0);
+}
+
+
+void Item_func_convert_tz::cleanup()
+{
+ from_tz_cached= to_tz_cached= 0;
+ Item_temporal_func::cleanup();
+}
+
+
+void Item_date_add_interval::fix_length_and_dec()
+{
+ enum_field_types arg0_field_type;
+
+ /*
+ The field type for the result of an Item_date function is defined as
+ follows:
+
+ - If first arg is a MYSQL_TYPE_DATETIME result is MYSQL_TYPE_DATETIME
+ - If first arg is a MYSQL_TYPE_DATE and the interval type uses hours,
+ minutes or seconds then type is MYSQL_TYPE_DATETIME
+ otherwise it's MYSQL_TYPE_DATE
+ - if first arg is a MYSQL_TYPE_TIME and the interval type isn't using
+ anything larger than days, then the result is MYSQL_TYPE_TIME,
+ otherwise - MYSQL_TYPE_DATETIME.
+ - Otherwise the result is MYSQL_TYPE_STRING
+ (This is because you can't know if the string contains a DATE,
+ MYSQL_TIME or DATETIME argument)
+ */
+ cached_field_type= MYSQL_TYPE_STRING;
+ arg0_field_type= args[0]->field_type();
+ uint interval_dec= 0;
+ if (int_type == INTERVAL_MICROSECOND ||
+ (int_type >= INTERVAL_DAY_MICROSECOND &&
+ int_type <= INTERVAL_SECOND_MICROSECOND))
+ interval_dec= TIME_SECOND_PART_DIGITS;
+ else if (int_type == INTERVAL_SECOND && args[1]->decimals > 0)
+ interval_dec= MY_MIN(args[1]->decimals, TIME_SECOND_PART_DIGITS);
+
+ if (arg0_field_type == MYSQL_TYPE_DATETIME ||
+ arg0_field_type == MYSQL_TYPE_TIMESTAMP)
+ {
+ decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec);
+ cached_field_type= MYSQL_TYPE_DATETIME;
+ }
+ else if (arg0_field_type == MYSQL_TYPE_DATE)
+ {
+ if (int_type <= INTERVAL_DAY || int_type == INTERVAL_YEAR_MONTH)
+ cached_field_type= arg0_field_type;
+ else
+ {
+ decimals= interval_dec;
+ cached_field_type= MYSQL_TYPE_DATETIME;
+ }
+ }
+ else if (arg0_field_type == MYSQL_TYPE_TIME)
+ {
+ decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME), interval_dec);
+ if (int_type >= INTERVAL_DAY && int_type != INTERVAL_YEAR_MONTH)
+ cached_field_type= arg0_field_type;
+ else
+ cached_field_type= MYSQL_TYPE_DATETIME;
+ }
+ else
+ decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec);
+ Item_temporal_func::fix_length_and_dec();
+}
+
+
+/* Here arg[1] is a Item_interval object */
+
+bool Item_date_add_interval::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ INTERVAL interval;
+
+ if (args[0]->get_date(ltime,
+ cached_field_type == MYSQL_TYPE_TIME ?
+ TIME_TIME_ONLY : 0) ||
+ get_interval_value(args[1], int_type, &interval))
+ return (null_value=1);
+
+ if (ltime->time_type != MYSQL_TIMESTAMP_TIME &&
+ check_date_with_warn(ltime, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE,
+ MYSQL_TIMESTAMP_ERROR))
+ return (null_value=1);
+
+ if (date_sub_interval)
+ interval.neg = !interval.neg;
+
+ if (date_add_interval(ltime, int_type, interval))
+ return (null_value=1);
+ return (null_value= 0);
+}
+
+
+bool Item_date_add_interval::eq(const Item *item, bool binary_cmp) const
+{
+ Item_date_add_interval *other= (Item_date_add_interval*) item;
+ if (!Item_func::eq(item, binary_cmp))
+ return 0;
+ return ((int_type == other->int_type) &&
+ (date_sub_interval == other->date_sub_interval));
+}
+
+/*
+ 'interval_names' reflects the order of the enumeration interval_type.
+ See item_timefunc.h
+ */
+
+static const char *interval_names[]=
+{
+ "year", "quarter", "month", "week", "day",
+ "hour", "minute", "second", "microsecond",
+ "year_month", "day_hour", "day_minute",
+ "day_second", "hour_minute", "hour_second",
+ "minute_second", "day_microsecond",
+ "hour_microsecond", "minute_microsecond",
+ "second_microsecond"
+};
+
+void Item_date_add_interval::print(String *str, enum_query_type query_type)
+{
+ str->append('(');
+ args[0]->print(str, query_type);
+ str->append(date_sub_interval?" - interval ":" + interval ");
+ args[1]->print(str, query_type);
+ str->append(' ');
+ str->append(interval_names[int_type]);
+ str->append(')');
+}
+
+void Item_extract::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("extract("));
+ str->append(interval_names[int_type]);
+ str->append(STRING_WITH_LEN(" from "));
+ args[0]->print(str, query_type);
+ str->append(')');
+}
+
+void Item_extract::fix_length_and_dec()
+{
+ maybe_null=1; // If wrong date
+ switch (int_type) {
+ case INTERVAL_YEAR: max_length=4; date_value=1; break;
+ case INTERVAL_YEAR_MONTH: max_length=6; date_value=1; break;
+ case INTERVAL_QUARTER: max_length=2; date_value=1; break;
+ case INTERVAL_MONTH: max_length=2; date_value=1; break;
+ case INTERVAL_WEEK: max_length=2; date_value=1; break;
+ case INTERVAL_DAY: max_length=2; date_value=1; break;
+ case INTERVAL_DAY_HOUR: max_length=9; date_value=0; break;
+ case INTERVAL_DAY_MINUTE: max_length=11; date_value=0; break;
+ case INTERVAL_DAY_SECOND: max_length=13; date_value=0; break;
+ case INTERVAL_HOUR: max_length=2; date_value=0; break;
+ case INTERVAL_HOUR_MINUTE: max_length=4; date_value=0; break;
+ case INTERVAL_HOUR_SECOND: max_length=6; date_value=0; break;
+ case INTERVAL_MINUTE: max_length=2; date_value=0; break;
+ case INTERVAL_MINUTE_SECOND: max_length=4; date_value=0; break;
+ case INTERVAL_SECOND: max_length=2; date_value=0; break;
+ case INTERVAL_MICROSECOND: max_length=2; date_value=0; break;
+ case INTERVAL_DAY_MICROSECOND: max_length=20; date_value=0; break;
+ case INTERVAL_HOUR_MICROSECOND: max_length=13; date_value=0; break;
+ case INTERVAL_MINUTE_MICROSECOND: max_length=11; date_value=0; break;
+ case INTERVAL_SECOND_MICROSECOND: max_length=9; date_value=0; break;
+ case INTERVAL_LAST: DBUG_ASSERT(0); break; /* purecov: deadcode */
+ }
+}
+
+
+longlong Item_extract::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ uint year;
+ ulong week_format;
+ long neg;
+ int is_time_flag = date_value ? 0 : TIME_TIME_ONLY;
+
+ // Not using get_arg0_date to avoid automatic TIME to DATETIME conversion
+ if ((null_value= args[0]->get_date(&ltime, is_time_flag)))
+ return 0;
+
+ neg= ltime.neg ? -1 : 1;
+
+ DBUG_ASSERT(ltime.time_type != MYSQL_TIMESTAMP_TIME || ltime.day == 0);
+ if (ltime.time_type == MYSQL_TIMESTAMP_TIME)
+ time_to_daytime_interval(&ltime);
+
+ switch (int_type) {
+ case INTERVAL_YEAR: return ltime.year;
+ case INTERVAL_YEAR_MONTH: return ltime.year*100L+ltime.month;
+ case INTERVAL_QUARTER: return (ltime.month+2)/3;
+ case INTERVAL_MONTH: return ltime.month;
+ case INTERVAL_WEEK:
+ {
+ week_format= current_thd->variables.default_week_format;
+ return calc_week(&ltime, week_mode(week_format), &year);
+ }
+ case INTERVAL_DAY: return ltime.day;
+ case INTERVAL_DAY_HOUR: return (long) (ltime.day*100L+ltime.hour)*neg;
+ case INTERVAL_DAY_MINUTE: return (long) (ltime.day*10000L+
+ ltime.hour*100L+
+ ltime.minute)*neg;
+ case INTERVAL_DAY_SECOND: return ((longlong) ltime.day*1000000L+
+ (longlong) (ltime.hour*10000L+
+ ltime.minute*100+
+ ltime.second))*neg;
+ case INTERVAL_HOUR: return (long) ltime.hour*neg;
+ case INTERVAL_HOUR_MINUTE: return (long) (ltime.hour*100+ltime.minute)*neg;
+ case INTERVAL_HOUR_SECOND: return (long) (ltime.hour*10000+ltime.minute*100+
+ ltime.second)*neg;
+ case INTERVAL_MINUTE: return (long) ltime.minute*neg;
+ case INTERVAL_MINUTE_SECOND: return (long) (ltime.minute*100+ltime.second)*neg;
+ case INTERVAL_SECOND: return (long) ltime.second*neg;
+ case INTERVAL_MICROSECOND: return (long) ltime.second_part*neg;
+ case INTERVAL_DAY_MICROSECOND: return (((longlong)ltime.day*1000000L +
+ (longlong)ltime.hour*10000L +
+ ltime.minute*100 +
+ ltime.second)*1000000L +
+ ltime.second_part)*neg;
+ case INTERVAL_HOUR_MICROSECOND: return (((longlong)ltime.hour*10000L +
+ ltime.minute*100 +
+ ltime.second)*1000000L +
+ ltime.second_part)*neg;
+ case INTERVAL_MINUTE_MICROSECOND: return (((longlong)(ltime.minute*100+
+ ltime.second))*1000000L+
+ ltime.second_part)*neg;
+ case INTERVAL_SECOND_MICROSECOND: return ((longlong)ltime.second*1000000L+
+ ltime.second_part)*neg;
+ case INTERVAL_LAST: DBUG_ASSERT(0); break; /* purecov: deadcode */
+ }
+ return 0; // Impossible
+}
+
+bool Item_extract::eq(const Item *item, bool binary_cmp) const
+{
+ if (this == item)
+ return 1;
+ if (item->type() != FUNC_ITEM ||
+ functype() != ((Item_func*)item)->functype())
+ return 0;
+
+ Item_extract* ie= (Item_extract*)item;
+ if (ie->int_type != int_type)
+ return 0;
+
+ if (!args[0]->eq(ie->args[0], binary_cmp))
+ return 0;
+ return 1;
+}
+
+
+bool Item_char_typecast::eq(const Item *item, bool binary_cmp) const
+{
+ if (this == item)
+ return 1;
+ if (item->type() != FUNC_ITEM ||
+ functype() != ((Item_func*)item)->functype())
+ return 0;
+
+ Item_char_typecast *cast= (Item_char_typecast*)item;
+ if (cast_length != cast->cast_length ||
+ cast_cs != cast->cast_cs)
+ return 0;
+
+ if (!args[0]->eq(cast->args[0], binary_cmp))
+ return 0;
+ return 1;
+}
+
+void Item_temporal_typecast::print(String *str, enum_query_type query_type)
+{
+ char buf[32];
+ str->append(STRING_WITH_LEN("cast("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" as "));
+ str->append(cast_type());
+ if (decimals)
+ {
+ str->append('(');
+ str->append(llstr(decimals, buf));
+ str->append(')');
+ }
+ str->append(')');
+}
+
+
+void Item_char_typecast::print(String *str, enum_query_type query_type)
+{
+ str->append(STRING_WITH_LEN("cast("));
+ args[0]->print(str, query_type);
+ str->append(STRING_WITH_LEN(" as char"));
+ if (cast_length != ~0U)
+ {
+ str->append('(');
+ char buffer[20];
+ // my_charset_bin is good enough for numbers
+ String st(buffer, sizeof(buffer), &my_charset_bin);
+ st.set(static_cast<ulonglong>(cast_length), &my_charset_bin);
+ str->append(st);
+ str->append(')');
+ }
+ if (cast_cs)
+ {
+ str->append(STRING_WITH_LEN(" charset "));
+ str->append(cast_cs->csname);
+ }
+ str->append(')');
+}
+
+String *Item_char_typecast::val_str(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ String *res;
+ uint32 length;
+
+ if (cast_length != ~0U &&
+ cast_length > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ cast_cs == &my_charset_bin ?
+ "cast_as_binary" : func_name(),
+ current_thd->variables.max_allowed_packet);
+ cast_length= current_thd->variables.max_allowed_packet;
+ }
+
+ if (!charset_conversion)
+ {
+ if (!(res= args[0]->val_str(str)))
+ {
+ null_value= 1;
+ return 0;
+ }
+ }
+ else
+ {
+ /*
+ Convert character set if differ
+ from_cs is 0 in the case where the result set may vary between calls,
+ for example with dynamic columns.
+ */
+ uint dummy_errors;
+ if (!(res= args[0]->val_str(str)) ||
+ tmp_value.copy(res->ptr(), res->length(),
+ from_cs ? from_cs : res->charset(),
+ cast_cs, &dummy_errors))
+ {
+ null_value= 1;
+ return 0;
+ }
+ res= &tmp_value;
+ }
+
+ res->set_charset(cast_cs);
+
+ /*
+ Cut the tail if cast with length
+ and the result is longer than cast length, e.g.
+ CAST('string' AS CHAR(1))
+ */
+ if (cast_length != ~0U)
+ {
+ if (res->length() > (length= (uint32) res->charpos(cast_length)))
+ { // Safe even if const arg
+ char char_type[40];
+ my_snprintf(char_type, sizeof(char_type), "%s(%lu)",
+ cast_cs == &my_charset_bin ? "BINARY" : "CHAR",
+ (ulong) length);
+
+ if (!res->alloced_length())
+ { // Don't change const str
+ str_value= *res; // Not malloced string
+ res= &str_value;
+ }
+ ErrConvString err(res);
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), char_type,
+ err.ptr());
+ res->length((uint) length);
+ }
+ else if (cast_cs == &my_charset_bin && res->length() < cast_length)
+ {
+ if (res->alloced_length() < cast_length)
+ {
+ str_value.alloc(cast_length);
+ str_value.copy(*res);
+ res= &str_value;
+ }
+ bzero((char*) res->ptr() + res->length(), cast_length - res->length());
+ res->length(cast_length);
+ }
+ }
+ null_value= 0;
+
+ if (res->length() > current_thd->variables.max_allowed_packet)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_ALLOWED_PACKET_OVERFLOWED,
+ ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED),
+ cast_cs == &my_charset_bin ?
+ "cast_as_binary" : func_name(),
+ current_thd->variables.max_allowed_packet);
+ null_value= 1;
+ return 0;
+ }
+ return res;
+}
+
+
+void Item_char_typecast::fix_length_and_dec()
+{
+ uint32 char_length;
+ /*
+ We always force character set conversion if cast_cs
+ is a multi-byte character set. It garantees that the
+ result of CAST is a well-formed string.
+ For single-byte character sets we allow just to copy
+ from the argument. A single-byte character sets string
+ is always well-formed.
+
+ There is a special trick to convert form a number to ucs2.
+ As numbers have my_charset_bin as their character set,
+ it wouldn't do conversion to ucs2 without an additional action.
+ To force conversion, we should pretend to be non-binary.
+ Let's choose from_cs this way:
+ - If the argument in a number and cast_cs is ucs2 (i.e. mbminlen > 1),
+ then from_cs is set to latin1, to perform latin1 -> ucs2 conversion.
+ - If the argument is a number and cast_cs is ASCII-compatible
+ (i.e. mbminlen == 1), then from_cs is set to cast_cs,
+ which allows just to take over the args[0]->val_str() result
+ and thus avoid unnecessary character set conversion.
+ - If the argument is not a number, then from_cs is set to
+ the argument's charset.
+ - If argument has a dynamic collation (can change from call to call)
+ we set from_cs to 0 as a marker that we have to take the collation
+ from the result string.
+
+ Note (TODO): we could use repertoire technique here.
+ */
+ from_cs= ((args[0]->result_type() == INT_RESULT ||
+ args[0]->result_type() == DECIMAL_RESULT ||
+ args[0]->result_type() == REAL_RESULT) ?
+ (cast_cs->mbminlen == 1 ? cast_cs : &my_charset_latin1) :
+ args[0]->dynamic_result() ? 0 :
+ args[0]->collation.collation);
+ charset_conversion= !from_cs || (cast_cs->mbmaxlen > 1) ||
+ (!my_charset_same(from_cs, cast_cs) &&
+ from_cs != &my_charset_bin &&
+ cast_cs != &my_charset_bin);
+ collation.set(cast_cs, DERIVATION_IMPLICIT);
+ char_length= ((cast_length != ~0U) ? cast_length :
+ args[0]->max_length /
+ (cast_cs == &my_charset_bin ? 1 :
+ args[0]->collation.collation->mbmaxlen));
+ max_length= char_length * cast_cs->mbmaxlen;
+}
+
+
+bool Item_time_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ if (get_arg0_time(ltime))
+ return 1;
+ if (decimals < TIME_SECOND_PART_DIGITS)
+ my_time_trunc(ltime, decimals);
+ /*
+ MYSQL_TIMESTAMP_TIME value can have non-zero day part,
+ which we should not lose.
+ */
+ if (ltime->time_type != MYSQL_TIMESTAMP_TIME)
+ ltime->year= ltime->month= ltime->day= 0;
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+ return (fuzzy_date & TIME_TIME_ONLY) ? 0 :
+ (null_value= check_date_with_warn(ltime, fuzzy_date,
+ MYSQL_TIMESTAMP_ERROR));
+}
+
+
+bool Item_date_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ fuzzy_date |= sql_mode_for_dates(current_thd);
+ if (get_arg0_date(ltime, fuzzy_date & ~TIME_TIME_ONLY))
+ return 1;
+
+ if (make_date_with_warn(ltime, fuzzy_date, MYSQL_TIMESTAMP_DATE))
+ return (null_value= 1);
+
+ return 0;
+}
+
+
+bool Item_datetime_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ fuzzy_date |= sql_mode_for_dates(current_thd);
+ if (get_arg0_date(ltime, fuzzy_date & ~TIME_TIME_ONLY))
+ return 1;
+
+ if (decimals < TIME_SECOND_PART_DIGITS)
+ my_time_trunc(ltime, decimals);
+
+ DBUG_ASSERT(ltime->time_type != MYSQL_TIMESTAMP_TIME);
+ ltime->time_type= MYSQL_TIMESTAMP_DATETIME;
+ return 0;
+}
+
+
+/**
+ MAKEDATE(a,b) is a date function that creates a date value
+ from a year and day value.
+
+ NOTES:
+ As arguments are integers, we can't know if the year is a 2 digit
+ or 4 digit year. In this case we treat all years < 100 as 2 digit
+ years. Ie, this is not safe for dates between 0000-01-01 and
+ 0099-12-31
+*/
+
+bool Item_func_makedate::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed == 1);
+ long daynr= (long) args[1]->val_int();
+ long year= (long) args[0]->val_int();
+ long days;
+
+ if (args[0]->null_value || args[1]->null_value ||
+ year < 0 || year > 9999 || daynr <= 0)
+ goto err;
+
+ if (year < 100)
+ year= year_2000_handling(year);
+
+ days= calc_daynr(year,1,1) + daynr - 1;
+ if (get_date_from_daynr(days, &ltime->year, &ltime->month, &ltime->day))
+ goto err;
+ ltime->time_type= MYSQL_TIMESTAMP_DATE;
+ ltime->neg= 0;
+ ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0;
+ return (null_value= 0);
+
+err:
+ return (null_value= 1);
+}
+
+
+void Item_func_add_time::fix_length_and_dec()
+{
+ enum_field_types arg0_field_type;
+ decimals= MY_MAX(args[0]->decimals, args[1]->decimals);
+
+ /*
+ The field type for the result of an Item_func_add_time function is defined
+ as follows:
+
+ - If first arg is a MYSQL_TYPE_DATETIME or MYSQL_TYPE_TIMESTAMP
+ result is MYSQL_TYPE_DATETIME
+ - If first arg is a MYSQL_TYPE_TIME result is MYSQL_TYPE_TIME
+ - Otherwise the result is MYSQL_TYPE_STRING
+ */
+
+ cached_field_type= MYSQL_TYPE_STRING;
+ arg0_field_type= args[0]->field_type();
+ if (arg0_field_type == MYSQL_TYPE_DATE ||
+ arg0_field_type == MYSQL_TYPE_DATETIME ||
+ arg0_field_type == MYSQL_TYPE_TIMESTAMP ||
+ is_date)
+ {
+ cached_field_type= MYSQL_TYPE_DATETIME;
+ decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME),
+ args[1]->temporal_precision(MYSQL_TYPE_TIME));
+ }
+ else if (arg0_field_type == MYSQL_TYPE_TIME)
+ {
+ cached_field_type= MYSQL_TYPE_TIME;
+ decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME),
+ args[1]->temporal_precision(MYSQL_TYPE_TIME));
+ }
+ Item_temporal_func::fix_length_and_dec();
+}
+
+/**
+ ADDTIME(t,a) and SUBTIME(t,a) are time functions that calculate a
+ time/datetime value
+
+ t: time_or_datetime_expression
+ a: time_expression
+
+ Result: Time value or datetime value
+*/
+
+bool Item_func_add_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME l_time1, l_time2;
+ bool is_time= 0;
+ long days, microseconds;
+ longlong seconds;
+ int l_sign= sign;
+
+ if (cached_field_type == MYSQL_TYPE_DATETIME)
+ {
+ // TIMESTAMP function OR the first argument is DATE/DATETIME/TIMESTAMP
+ if (get_arg0_date(&l_time1, 0) ||
+ args[1]->get_time(&l_time2) ||
+ l_time1.time_type == MYSQL_TIMESTAMP_TIME ||
+ l_time2.time_type != MYSQL_TIMESTAMP_TIME)
+ return (null_value= 1);
+ }
+ else
+ {
+ // ADDTIME function AND the first argument is TIME
+ if (args[0]->get_time(&l_time1) ||
+ args[1]->get_time(&l_time2) ||
+ l_time2.time_type == MYSQL_TIMESTAMP_DATETIME)
+ return (null_value= 1);
+ is_time= (l_time1.time_type == MYSQL_TIMESTAMP_TIME);
+ }
+ if (l_time1.neg != l_time2.neg)
+ l_sign= -l_sign;
+
+ bzero(ltime, sizeof(*ltime));
+
+ ltime->neg= calc_time_diff(&l_time1, &l_time2, -l_sign,
+ &seconds, &microseconds);
+
+ /*
+ If first argument was negative and diff between arguments
+ is non-zero we need to swap sign to get proper result.
+ */
+ if (l_time1.neg && (seconds || microseconds))
+ ltime->neg= 1-ltime->neg; // Swap sign of result
+
+ if (!is_time && ltime->neg)
+ return (null_value= 1);
+
+ days= (long) (seconds / SECONDS_IN_24H);
+
+ calc_time_from_sec(ltime, (long)(seconds % SECONDS_IN_24H), microseconds);
+
+ ltime->time_type= is_time ? MYSQL_TIMESTAMP_TIME : MYSQL_TIMESTAMP_DATETIME;
+
+ if (!is_time)
+ {
+ if (get_date_from_daynr(days,&ltime->year,&ltime->month,&ltime->day) ||
+ !ltime->day)
+ return (null_value= 1);
+ return (null_value= 0);
+ }
+
+ ltime->hour+= days*24;
+ return (null_value= adjust_time_range_with_warn(ltime, decimals));
+}
+
+
+void Item_func_add_time::print(String *str, enum_query_type query_type)
+{
+ if (is_date)
+ {
+ DBUG_ASSERT(sign > 0);
+ str->append(STRING_WITH_LEN("timestamp("));
+ }
+ else
+ {
+ if (sign > 0)
+ str->append(STRING_WITH_LEN("addtime("));
+ else
+ str->append(STRING_WITH_LEN("subtime("));
+ }
+ args[0]->print(str, query_type);
+ str->append(',');
+ args[1]->print(str, query_type);
+ str->append(')');
+}
+
+
+/**
+ TIMEDIFF(t,s) is a time function that calculates the
+ time value between a start and end time.
+
+ t and s: time_or_datetime_expression
+ Result: Time value
+*/
+
+bool Item_func_timediff::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed == 1);
+ longlong seconds;
+ long microseconds;
+ int l_sign= 1;
+ MYSQL_TIME l_time1,l_time2,l_time3;
+ ErrConvTime str(&l_time3);
+
+ /* the following may be true in, for example, date_add(timediff(...), ... */
+ if (fuzzy_date & TIME_NO_ZERO_IN_DATE)
+ return (null_value= 1);
+
+ if (args[0]->get_time(&l_time1) ||
+ args[1]->get_time(&l_time2) ||
+ l_time1.time_type != l_time2.time_type)
+ return (null_value= 1);
+
+ if (l_time1.neg != l_time2.neg)
+ l_sign= -l_sign;
+
+ bzero((char *)&l_time3, sizeof(l_time3));
+
+ l_time3.neg= calc_time_diff(&l_time1, &l_time2, l_sign,
+ &seconds, &microseconds);
+
+ /*
+ For MYSQL_TIMESTAMP_TIME only:
+ If first argument was negative and diff between arguments
+ is non-zero we need to swap sign to get proper result.
+ */
+ if (l_time1.neg && (seconds || microseconds))
+ l_time3.neg= 1-l_time3.neg; // Swap sign of result
+
+ /*
+ seconds is longlong, when casted to long it may become a small number
+ even if the original seconds value was too large and invalid.
+ as a workaround we limit seconds by a large invalid long number
+ ("invalid" means > TIME_MAX_SECOND)
+ */
+ set_if_smaller(seconds, INT_MAX32);
+
+ calc_time_from_sec(&l_time3, (long) seconds, microseconds);
+
+ if ((fuzzy_date & TIME_NO_ZERO_DATE) && (seconds == 0) &&
+ (microseconds == 0))
+ return (null_value= 1);
+
+ *ltime= l_time3;
+ return (null_value= adjust_time_range_with_warn(ltime, decimals));
+}
+
+/**
+ MAKETIME(h,m,s) is a time function that calculates a time value
+ from the total number of hours, minutes, and seconds.
+ Result: Time value
+*/
+
+bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DBUG_ASSERT(fixed == 1);
+ bool overflow= 0;
+ longlong hour= args[0]->val_int();
+ longlong minute= args[1]->val_int();
+ ulonglong second;
+ ulong microsecond;
+ bool neg= args[2]->get_seconds(&second, &microsecond);
+
+ if (args[0]->null_value || args[1]->null_value || args[2]->null_value ||
+ minute < 0 || minute > 59 || neg || second > 59)
+ return (null_value= 1);
+
+ bzero(ltime, sizeof(*ltime));
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+
+ /* Check for integer overflows */
+ if (hour < 0)
+ {
+ if (args[0]->unsigned_flag)
+ overflow= 1;
+ else
+ ltime->neg= 1;
+ }
+ if (-hour > TIME_MAX_HOUR || hour > TIME_MAX_HOUR)
+ overflow= 1;
+
+ if (!overflow)
+ {
+ ltime->hour= (uint) ((hour < 0 ? -hour : hour));
+ ltime->minute= (uint) minute;
+ ltime->second= (uint) second;
+ ltime->second_part= microsecond;
+ }
+ else
+ {
+ ltime->hour= TIME_MAX_HOUR;
+ ltime->minute= TIME_MAX_MINUTE;
+ ltime->second= TIME_MAX_SECOND;
+ char buf[28];
+ char *ptr= longlong10_to_str(hour, buf, args[0]->unsigned_flag ? 10 : -10);
+ int len = (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u", (uint)minute, (uint)second);
+ make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ buf, len, MYSQL_TIMESTAMP_TIME,
+ NullS);
+ }
+
+ return (null_value= 0);
+}
+
+
+/**
+ MICROSECOND(a) is a function ( extraction) that extracts the microseconds
+ from a.
+
+ a: Datetime or time value
+ Result: int value
+*/
+
+longlong Item_func_microsecond::val_int()
+{
+ DBUG_ASSERT(fixed == 1);
+ MYSQL_TIME ltime;
+ if (!get_arg0_date(&ltime, TIME_TIME_ONLY))
+ return ltime.second_part;
+ return 0;
+}
+
+
+longlong Item_func_timestamp_diff::val_int()
+{
+ MYSQL_TIME ltime1, ltime2;
+ longlong seconds;
+ long microseconds;
+ long months= 0;
+ int neg= 1;
+
+ null_value= 0;
+ if (args[0]->get_date_with_conversion(&ltime1,
+ TIME_NO_ZERO_DATE |
+ TIME_NO_ZERO_IN_DATE) ||
+ args[1]->get_date_with_conversion(&ltime2,
+ TIME_NO_ZERO_DATE |
+ TIME_NO_ZERO_IN_DATE))
+ goto null_date;
+
+ if (calc_time_diff(&ltime2,&ltime1, 1,
+ &seconds, &microseconds))
+ neg= -1;
+
+ if (int_type == INTERVAL_YEAR ||
+ int_type == INTERVAL_QUARTER ||
+ int_type == INTERVAL_MONTH)
+ {
+ uint year_beg, year_end, month_beg, month_end, day_beg, day_end;
+ uint years= 0;
+ uint second_beg, second_end, microsecond_beg, microsecond_end;
+
+ if (neg == -1)
+ {
+ year_beg= ltime2.year;
+ year_end= ltime1.year;
+ month_beg= ltime2.month;
+ month_end= ltime1.month;
+ day_beg= ltime2.day;
+ day_end= ltime1.day;
+ second_beg= ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second;
+ second_end= ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second;
+ microsecond_beg= ltime2.second_part;
+ microsecond_end= ltime1.second_part;
+ }
+ else
+ {
+ year_beg= ltime1.year;
+ year_end= ltime2.year;
+ month_beg= ltime1.month;
+ month_end= ltime2.month;
+ day_beg= ltime1.day;
+ day_end= ltime2.day;
+ second_beg= ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second;
+ second_end= ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second;
+ microsecond_beg= ltime1.second_part;
+ microsecond_end= ltime2.second_part;
+ }
+
+ /* calc years */
+ years= year_end - year_beg;
+ if (month_end < month_beg || (month_end == month_beg && day_end < day_beg))
+ years-= 1;
+
+ /* calc months */
+ months= 12*years;
+ if (month_end < month_beg || (month_end == month_beg && day_end < day_beg))
+ months+= 12 - (month_beg - month_end);
+ else
+ months+= (month_end - month_beg);
+
+ if (day_end < day_beg)
+ months-= 1;
+ else if ((day_end == day_beg) &&
+ ((second_end < second_beg) ||
+ (second_end == second_beg && microsecond_end < microsecond_beg)))
+ months-= 1;
+ }
+
+ switch (int_type) {
+ case INTERVAL_YEAR:
+ return months/12*neg;
+ case INTERVAL_QUARTER:
+ return months/3*neg;
+ case INTERVAL_MONTH:
+ return months*neg;
+ case INTERVAL_WEEK:
+ return seconds / SECONDS_IN_24H / 7L * neg;
+ case INTERVAL_DAY:
+ return seconds / SECONDS_IN_24H * neg;
+ case INTERVAL_HOUR:
+ return seconds/3600L*neg;
+ case INTERVAL_MINUTE:
+ return seconds/60L*neg;
+ case INTERVAL_SECOND:
+ return seconds*neg;
+ case INTERVAL_MICROSECOND:
+ /*
+ In MySQL difference between any two valid datetime values
+ in microseconds fits into longlong.
+ */
+ return (seconds*1000000L+microseconds)*neg;
+ default:
+ break;
+ }
+
+null_date:
+ null_value=1;
+ return 0;
+}
+
+
+void Item_func_timestamp_diff::print(String *str, enum_query_type query_type)
+{
+ str->append(func_name());
+ str->append('(');
+
+ switch (int_type) {
+ case INTERVAL_YEAR:
+ str->append(STRING_WITH_LEN("YEAR"));
+ break;
+ case INTERVAL_QUARTER:
+ str->append(STRING_WITH_LEN("QUARTER"));
+ break;
+ case INTERVAL_MONTH:
+ str->append(STRING_WITH_LEN("MONTH"));
+ break;
+ case INTERVAL_WEEK:
+ str->append(STRING_WITH_LEN("WEEK"));
+ break;
+ case INTERVAL_DAY:
+ str->append(STRING_WITH_LEN("DAY"));
+ break;
+ case INTERVAL_HOUR:
+ str->append(STRING_WITH_LEN("HOUR"));
+ break;
+ case INTERVAL_MINUTE:
+ str->append(STRING_WITH_LEN("MINUTE"));
+ break;
+ case INTERVAL_SECOND:
+ str->append(STRING_WITH_LEN("SECOND"));
+ break;
+ case INTERVAL_MICROSECOND:
+ str->append(STRING_WITH_LEN("SECOND_FRAC"));
+ break;
+ default:
+ break;
+ }
+
+ for (uint i=0 ; i < 2 ; i++)
+ {
+ str->append(',');
+ args[i]->print(str, query_type);
+ }
+ str->append(')');
+}
+
+
+String *Item_func_get_format::val_str_ascii(String *str)
+{
+ DBUG_ASSERT(fixed == 1);
+ const char *format_name;
+ KNOWN_DATE_TIME_FORMAT *format;
+ String *val= args[0]->val_str_ascii(str);
+ ulong val_len;
+
+ if ((null_value= args[0]->null_value))
+ return 0;
+
+ val_len= val->length();
+ for (format= &known_date_time_formats[0];
+ (format_name= format->format_name);
+ format++)
+ {
+ uint format_name_len;
+ format_name_len= (uint) strlen(format_name);
+ if (val_len == format_name_len &&
+ !my_strnncoll(&my_charset_latin1,
+ (const uchar *) val->ptr(), val_len,
+ (const uchar *) format_name, val_len))
+ {
+ const char *format_str= get_date_time_format_str(format, type);
+ str->set(format_str, (uint) strlen(format_str), &my_charset_numeric);
+ return str;
+ }
+ }
+
+ null_value= 1;
+ return 0;
+}
+
+
+void Item_func_get_format::print(String *str, enum_query_type query_type)
+{
+ str->append(func_name());
+ str->append('(');
+
+ switch (type) {
+ case MYSQL_TIMESTAMP_DATE:
+ str->append(STRING_WITH_LEN("DATE, "));
+ break;
+ case MYSQL_TIMESTAMP_DATETIME:
+ str->append(STRING_WITH_LEN("DATETIME, "));
+ break;
+ case MYSQL_TIMESTAMP_TIME:
+ str->append(STRING_WITH_LEN("TIME, "));
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ args[0]->print(str, query_type);
+ str->append(')');
+}
+
+
+/**
+ Get type of datetime value (DATE/TIME/...) which will be produced
+ according to format string.
+
+ @param format format string
+ @param length length of format string
+
+ @note
+ We don't process day format's characters('D', 'd', 'e') because day
+ may be a member of all date/time types.
+
+ @note
+ Format specifiers supported by this function should be in sync with
+ specifiers supported by extract_date_time() function.
+
+ @return
+ One of date_time_format_types values:
+ - DATE_TIME_MICROSECOND
+ - DATE_TIME
+ - DATE_ONLY
+ - TIME_MICROSECOND
+ - TIME_ONLY
+*/
+
+static date_time_format_types
+get_date_time_result_type(const char *format, uint length)
+{
+ const char *time_part_frms= "HISThiklrs";
+ const char *date_part_frms= "MVUXYWabcjmvuxyw";
+ bool date_part_used= 0, time_part_used= 0, frac_second_used= 0;
+
+ const char *val= format;
+ const char *end= format + length;
+
+ for (; val != end && val != end; val++)
+ {
+ if (*val == '%' && val+1 != end)
+ {
+ val++;
+ if (*val == 'f')
+ frac_second_used= time_part_used= 1;
+ else if (!time_part_used && strchr(time_part_frms, *val))
+ time_part_used= 1;
+ else if (!date_part_used && strchr(date_part_frms, *val))
+ date_part_used= 1;
+ if (date_part_used && frac_second_used)
+ {
+ /*
+ frac_second_used implies time_part_used, and thus we already
+ have all types of date-time components and can end our search.
+ */
+ return DATE_TIME_MICROSECOND;
+ }
+ }
+ }
+
+ /* We don't have all three types of date-time components */
+ if (frac_second_used)
+ return TIME_MICROSECOND;
+ if (time_part_used)
+ {
+ if (date_part_used)
+ return DATE_TIME;
+ return TIME_ONLY;
+ }
+ return DATE_ONLY;
+}
+
+
+void Item_func_str_to_date::fix_length_and_dec()
+{
+ if (agg_arg_charsets(collation, args, 2, MY_COLL_ALLOW_CONV, 1))
+ return;
+ if (collation.collation->mbminlen > 1)
+ {
+#if MYSQL_VERSION_ID > 50500
+ internal_charset= &my_charset_utf8mb4_general_ci;
+#else
+ internal_charset= &my_charset_utf8_general_ci;
+#endif
+ }
+
+ cached_field_type= MYSQL_TYPE_DATETIME;
+ decimals= TIME_SECOND_PART_DIGITS;
+ if ((const_item= args[1]->const_item()))
+ {
+ char format_buff[64];
+ String format_str(format_buff, sizeof(format_buff), &my_charset_bin);
+ String *format= args[1]->val_str(&format_str, &format_converter,
+ internal_charset);
+ decimals= 0;
+ if (!args[1]->null_value)
+ {
+ date_time_format_types cached_format_type=
+ get_date_time_result_type(format->ptr(), format->length());
+ switch (cached_format_type) {
+ case DATE_ONLY:
+ cached_field_type= MYSQL_TYPE_DATE;
+ break;
+ case TIME_MICROSECOND:
+ decimals= 6;
+ /* fall through */
+ case TIME_ONLY:
+ cached_field_type= MYSQL_TYPE_TIME;
+ break;
+ case DATE_TIME_MICROSECOND:
+ decimals= 6;
+ /* fall through */
+ case DATE_TIME:
+ cached_field_type= MYSQL_TYPE_DATETIME;
+ break;
+ }
+ }
+ }
+ cached_timestamp_type= mysql_type_to_time_type(cached_field_type);
+ Item_temporal_func::fix_length_and_dec();
+}
+
+
+bool Item_func_str_to_date::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ DATE_TIME_FORMAT date_time_format;
+ char val_buff[64], format_buff[64];
+ String val_string(val_buff, sizeof(val_buff), &my_charset_bin), *val;
+ String format_str(format_buff, sizeof(format_buff), &my_charset_bin),
+ *format;
+
+ val= args[0]->val_str(&val_string, &subject_converter, internal_charset);
+ format= args[1]->val_str(&format_str, &format_converter, internal_charset);
+ if (args[0]->null_value || args[1]->null_value)
+ return (null_value=1);
+
+ date_time_format.format.str= (char*) format->ptr();
+ date_time_format.format.length= format->length();
+ if (extract_date_time(&date_time_format, val->ptr(), val->length(),
+ ltime, cached_timestamp_type, 0, "datetime",
+ fuzzy_date | sql_mode_for_dates(current_thd)))
+ return (null_value=1);
+ if (cached_timestamp_type == MYSQL_TIMESTAMP_TIME && ltime->day)
+ {
+ /*
+ Day part for time type can be nonzero value and so
+ we should add hours from day part to hour part to
+ keep valid time value.
+ */
+ ltime->hour+= ltime->day*24;
+ ltime->day= 0;
+ }
+ return (null_value= 0);
+}
+
+
+bool Item_func_last_day::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
+{
+ if (get_arg0_date(ltime, fuzzy_date) ||
+ (ltime->month == 0))
+ return (null_value=1);
+ uint month_idx= ltime->month-1;
+ ltime->day= days_in_month[month_idx];
+ if ( month_idx == 1 && calc_days_in_year(ltime->year) == 366)
+ ltime->day= 29;
+ ltime->hour= ltime->minute= ltime->second= 0;
+ ltime->second_part= 0;
+ ltime->time_type= MYSQL_TIMESTAMP_DATE;
+ return (null_value= 0);
+}
diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h
new file mode 100644
index 00000000000..839a5a4845d
--- /dev/null
+++ b/sql/item_timefunc.h
@@ -0,0 +1,1092 @@
+#ifndef ITEM_TIMEFUNC_INCLUDED
+#define ITEM_TIMEFUNC_INCLUDED
+/* Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2009-2011, Monty Program Ab
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* Function items used by mysql */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+class MY_LOCALE;
+
+enum date_time_format_types
+{
+ TIME_ONLY= 0, TIME_MICROSECOND, DATE_ONLY, DATE_TIME, DATE_TIME_MICROSECOND
+};
+
+
+static inline uint
+mysql_temporal_int_part_length(enum enum_field_types mysql_type)
+{
+ static uint max_time_type_width[5]=
+ { MAX_DATETIME_WIDTH, MAX_DATETIME_WIDTH, MAX_DATE_WIDTH,
+ MAX_DATETIME_WIDTH, MIN_TIME_WIDTH };
+ return max_time_type_width[mysql_type_to_time_type(mysql_type)+2];
+}
+
+
+bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval);
+
+class Item_func_period_add :public Item_int_func
+{
+public:
+ Item_func_period_add(Item *a,Item *b) :Item_int_func(a,b) {}
+ longlong val_int();
+ const char *func_name() const { return "period_add"; }
+ void fix_length_and_dec()
+ {
+ max_length=6*MY_CHARSET_BIN_MB_MAXLEN;
+ }
+};
+
+
+class Item_func_period_diff :public Item_int_func
+{
+public:
+ Item_func_period_diff(Item *a,Item *b) :Item_int_func(a,b) {}
+ longlong val_int();
+ const char *func_name() const { return "period_diff"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=6*MY_CHARSET_BIN_MB_MAXLEN;
+ }
+};
+
+
+class Item_func_to_days :public Item_int_func
+{
+public:
+ Item_func_to_days(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "to_days"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=6*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ enum_monotonicity_info get_monotonicity_info() const;
+ longlong val_int_endpoint(bool left_endp, bool *incl_endp);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_to_seconds :public Item_int_func
+{
+public:
+ Item_func_to_seconds(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "to_seconds"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=6*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null= 1;
+ }
+ enum_monotonicity_info get_monotonicity_info() const;
+ longlong val_int_endpoint(bool left_endp, bool *incl_endp);
+ bool check_partition_func_processor(uchar *bool_arg) { return FALSE;}
+
+ bool intro_version(uchar *int_arg)
+ {
+ int *input_version= (int*)int_arg;
+ /* This function was introduced in 5.5 */
+ int output_version= MY_MAX(*input_version, 50500);
+ *input_version= output_version;
+ return 0;
+ }
+
+ /* Only meaningful with date part and optional time part */
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_dayofmonth :public Item_int_func
+{
+public:
+ Item_func_dayofmonth(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "dayofmonth"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=2*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_month :public Item_func
+{
+public:
+ Item_func_month(Item *a) :Item_func(a) { collation.set_numeric(); }
+ longlong val_int();
+ double val_real()
+ { DBUG_ASSERT(fixed == 1); return (double) Item_func_month::val_int(); }
+ String *val_str(String *str)
+ {
+ longlong nr= val_int();
+ if (null_value)
+ return 0;
+ str->set(nr, collation.collation);
+ return str;
+ }
+ const char *func_name() const { return "month"; }
+ enum Item_result result_type () const { return INT_RESULT; }
+ void fix_length_and_dec()
+ {
+ decimals= 0;
+ fix_char_length(2);
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_monthname :public Item_str_func
+{
+ MY_LOCALE *locale;
+public:
+ Item_func_monthname(Item *a) :Item_str_func(a) {}
+ const char *func_name() const { return "monthname"; }
+ String *val_str(String *str);
+ void fix_length_and_dec();
+ bool check_partition_func_processor(uchar *int_arg) {return TRUE;}
+ bool check_vcol_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_dayofyear :public Item_int_func
+{
+public:
+ Item_func_dayofyear(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "dayofyear"; }
+ void fix_length_and_dec()
+ {
+ decimals= 0;
+ fix_char_length(3);
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_hour :public Item_int_func
+{
+public:
+ Item_func_hour(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "hour"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=2*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_time_args();
+ }
+};
+
+
+class Item_func_minute :public Item_int_func
+{
+public:
+ Item_func_minute(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "minute"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=2*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_time_args();
+ }
+};
+
+
+class Item_func_quarter :public Item_int_func
+{
+public:
+ Item_func_quarter(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "quarter"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=1*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_second :public Item_int_func
+{
+public:
+ Item_func_second(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "second"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=2*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_time_args();
+ }
+};
+
+
+class Item_func_week :public Item_int_func
+{
+public:
+ Item_func_week(Item *a,Item *b) :Item_int_func(a,b) {}
+ longlong val_int();
+ const char *func_name() const { return "week"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=2*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+};
+
+class Item_func_yearweek :public Item_int_func
+{
+public:
+ Item_func_yearweek(Item *a,Item *b) :Item_int_func(a,b) {}
+ longlong val_int();
+ const char *func_name() const { return "yearweek"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=6*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_year :public Item_int_func
+{
+public:
+ Item_func_year(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "year"; }
+ enum_monotonicity_info get_monotonicity_info() const;
+ longlong val_int_endpoint(bool left_endp, bool *incl_endp);
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ max_length=4*MY_CHARSET_BIN_MB_MAXLEN;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+
+class Item_func_weekday :public Item_func
+{
+ bool odbc_type;
+public:
+ Item_func_weekday(Item *a,bool type_arg)
+ :Item_func(a), odbc_type(type_arg) { collation.set_numeric(); }
+ longlong val_int();
+ double val_real() { DBUG_ASSERT(fixed == 1); return (double) val_int(); }
+ String *val_str(String *str)
+ {
+ DBUG_ASSERT(fixed == 1);
+ str->set(val_int(), &my_charset_bin);
+ return null_value ? 0 : str;
+ }
+ const char *func_name() const
+ {
+ return (odbc_type ? "dayofweek" : "weekday");
+ }
+ enum Item_result result_type () const { return INT_RESULT; }
+ void fix_length_and_dec()
+ {
+ decimals= 0;
+ fix_char_length(1);
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_date_args();
+ }
+};
+
+class Item_func_dayname :public Item_func_weekday
+{
+ MY_LOCALE *locale;
+ public:
+ Item_func_dayname(Item *a) :Item_func_weekday(a,0) {}
+ const char *func_name() const { return "dayname"; }
+ String *val_str(String *str);
+ enum Item_result result_type () const { return STRING_RESULT; }
+ void fix_length_and_dec();
+ bool check_partition_func_processor(uchar *int_arg) {return TRUE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+};
+
+
+class Item_func_seconds_hybrid: public Item_func_numhybrid
+{
+protected:
+ virtual enum_field_types arg0_expected_type() const = 0;
+public:
+ Item_func_seconds_hybrid() :Item_func_numhybrid() {}
+ Item_func_seconds_hybrid(Item *a) :Item_func_numhybrid(a) {}
+ void fix_length_and_dec()
+ {
+ if (arg_count)
+ decimals= args[0]->temporal_precision(arg0_expected_type());
+ set_if_smaller(decimals, TIME_SECOND_PART_DIGITS);
+ max_length=17 + (decimals ? decimals + 1 : 0);
+ maybe_null= true;
+ cached_result_type= decimals ? DECIMAL_RESULT : INT_RESULT;
+ }
+ double real_op() { DBUG_ASSERT(0); return 0; }
+ String *str_op(String *str) { DBUG_ASSERT(0); return 0; }
+ bool date_op(MYSQL_TIME *ltime, uint fuzzydate) { DBUG_ASSERT(0); return true; }
+};
+
+
+class Item_func_unix_timestamp :public Item_func_seconds_hybrid
+{
+ bool get_timestamp_value(my_time_t *seconds, ulong *second_part);
+protected:
+ enum_field_types arg0_expected_type() const { return MYSQL_TYPE_DATETIME; }
+public:
+ Item_func_unix_timestamp() :Item_func_seconds_hybrid() {}
+ Item_func_unix_timestamp(Item *a) :Item_func_seconds_hybrid(a) {}
+ const char *func_name() const { return "unix_timestamp"; }
+ enum_monotonicity_info get_monotonicity_info() const;
+ longlong val_int_endpoint(bool left_endp, bool *incl_endp);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ /*
+ UNIX_TIMESTAMP() depends on the current timezone
+ (and thus may not be used as a partitioning function)
+ when its argument is NOT of the TIMESTAMP type.
+ */
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_timestamp_args();
+ }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ /*
+ TODO: Allow UNIX_TIMESTAMP called with an argument to be a part
+ of the expression for a virtual column
+ */
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+ longlong int_op();
+ my_decimal *decimal_op(my_decimal* buf);
+};
+
+
+class Item_func_time_to_sec :public Item_func_seconds_hybrid
+{
+protected:
+ enum_field_types arg0_expected_type() const { return MYSQL_TYPE_TIME; }
+public:
+ Item_func_time_to_sec(Item *item) :Item_func_seconds_hybrid(item) {}
+ const char *func_name() const { return "time_to_sec"; }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_time_args();
+ }
+ longlong int_op();
+ my_decimal *decimal_op(my_decimal* buf);
+};
+
+
+class Item_temporal_func: public Item_func
+{
+ ulonglong sql_mode;
+public:
+ Item_temporal_func() :Item_func() {}
+ Item_temporal_func(Item *a) :Item_func(a) {}
+ Item_temporal_func(Item *a, Item *b) :Item_func(a,b) {}
+ Item_temporal_func(Item *a, Item *b, Item *c) :Item_func(a,b,c) {}
+ enum Item_result result_type () const { return STRING_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; }
+ Item_result cmp_type() const { return TIME_RESULT; }
+ String *val_str(String *str);
+ longlong val_int() { return val_int_from_date(); }
+ double val_real() { return val_real_from_date(); }
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date) { DBUG_ASSERT(0); return 1; }
+ my_decimal *val_decimal(my_decimal *decimal_value)
+ { return val_decimal_from_date(decimal_value); }
+ Field *tmp_table_field(TABLE *table)
+ { return tmp_table_field_from_field_type(table, 0); }
+ int save_in_field(Field *field, bool no_conversions)
+ { return save_date_in_field(field); }
+ void fix_length_and_dec();
+};
+
+
+/**
+ Abstract class for functions returning TIME, DATE, DATETIME or string values,
+ whose data type depends on parameters and is set at fix_fields time.
+*/
+class Item_temporal_hybrid_func: public Item_temporal_func
+{
+protected:
+ enum_field_types cached_field_type; // TIME, DATE, DATETIME or STRING
+ String ascii_buf; // Conversion buffer
+public:
+ Item_temporal_hybrid_func(Item *a,Item *b)
+ :Item_temporal_func(a,b) {}
+ enum_field_types field_type() const { return cached_field_type; }
+ Item_result cmp_type() const
+ {
+ return cached_field_type == MYSQL_TYPE_STRING ?
+ STRING_RESULT : TIME_RESULT;
+ }
+ const CHARSET_INFO *charset_for_protocol() const
+ {
+ /*
+ Can return TIME, DATE, DATETIME or VARCHAR depending on arguments.
+ Send using "binary" when TIME, DATE or DATETIME,
+ or using collation.collation when VARCHAR
+ (which is fixed from @@collation_connection in fix_length_and_dec).
+ */
+ DBUG_ASSERT(fixed == 1);
+ return cached_field_type == MYSQL_TYPE_STRING ?
+ collation.collation : &my_charset_bin;
+ }
+ /**
+ Fix the returned timestamp to match field_type(),
+ which is important for val_str().
+ */
+ bool fix_temporal_type(MYSQL_TIME *ltime);
+ /**
+ Return string value in ASCII character set.
+ */
+ String *val_str_ascii(String *str);
+ /**
+ Return string value in @@character_set_connection.
+ */
+ String *val_str(String *str)
+ {
+ return val_str_from_val_str_ascii(str, &ascii_buf);
+ }
+};
+
+
+class Item_datefunc :public Item_temporal_func
+{
+public:
+ Item_datefunc() :Item_temporal_func() { }
+ Item_datefunc(Item *a) :Item_temporal_func(a) { }
+ enum_field_types field_type() const { return MYSQL_TYPE_DATE; }
+};
+
+
+class Item_timefunc :public Item_temporal_func
+{
+public:
+ Item_timefunc() :Item_temporal_func() {}
+ Item_timefunc(Item *a) :Item_temporal_func(a) {}
+ Item_timefunc(Item *a,Item *b) :Item_temporal_func(a,b) {}
+ Item_timefunc(Item *a, Item *b, Item *c) :Item_temporal_func(a, b ,c) {}
+ enum_field_types field_type() const { return MYSQL_TYPE_TIME; }
+};
+
+
+/* Abstract CURTIME function. Children should define what time zone is used */
+
+class Item_func_curtime :public Item_timefunc
+{
+ MYSQL_TIME ltime;
+public:
+ Item_func_curtime(uint dec) :Item_timefunc() { decimals= dec; }
+ bool fix_fields(THD *, Item **);
+ void fix_length_and_dec()
+ {
+ store_now_in_TIME(&ltime);
+ Item_timefunc::fix_length_and_dec();
+ maybe_null= false;
+ }
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ /*
+ Abstract method that defines which time zone is used for conversion.
+ Converts time current time in my_time_t representation to broken-down
+ MYSQL_TIME representation using UTC-SYSTEM or per-thread time zone.
+ */
+ virtual void store_now_in_TIME(MYSQL_TIME *now_time)=0;
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_curtime_local :public Item_func_curtime
+{
+public:
+ Item_func_curtime_local(uint dec) :Item_func_curtime(dec) {}
+ const char *func_name() const { return "curtime"; }
+ virtual void store_now_in_TIME(MYSQL_TIME *now_time);
+};
+
+
+class Item_func_curtime_utc :public Item_func_curtime
+{
+public:
+ Item_func_curtime_utc(uint dec) :Item_func_curtime(dec) {}
+ const char *func_name() const { return "utc_time"; }
+ virtual void store_now_in_TIME(MYSQL_TIME *now_time);
+};
+
+
+/* Abstract CURDATE function. See also Item_func_curtime. */
+
+class Item_func_curdate :public Item_datefunc
+{
+ MYSQL_TIME ltime;
+public:
+ Item_func_curdate() :Item_datefunc() {}
+ void fix_length_and_dec();
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ virtual void store_now_in_TIME(MYSQL_TIME *now_time)=0;
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_curdate_local :public Item_func_curdate
+{
+public:
+ Item_func_curdate_local() :Item_func_curdate() {}
+ const char *func_name() const { return "curdate"; }
+ void store_now_in_TIME(MYSQL_TIME *now_time);
+};
+
+
+class Item_func_curdate_utc :public Item_func_curdate
+{
+public:
+ Item_func_curdate_utc() :Item_func_curdate() {}
+ const char *func_name() const { return "utc_date"; }
+ void store_now_in_TIME(MYSQL_TIME *now_time);
+};
+
+
+/* Abstract CURRENT_TIMESTAMP function. See also Item_func_curtime */
+
+
+class Item_func_now :public Item_temporal_func
+{
+ MYSQL_TIME ltime;
+public:
+ Item_func_now(uint dec) :Item_temporal_func() { decimals= dec; }
+ bool fix_fields(THD *, Item **);
+ void fix_length_and_dec()
+ {
+ store_now_in_TIME(&ltime);
+ Item_temporal_func::fix_length_and_dec();
+ maybe_null= false;
+ }
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ virtual void store_now_in_TIME(MYSQL_TIME *now_time)=0;
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_now_local :public Item_func_now
+{
+public:
+ Item_func_now_local(uint dec) :Item_func_now(dec) {}
+ const char *func_name() const { return "now"; }
+ virtual void store_now_in_TIME(MYSQL_TIME *now_time);
+ virtual enum Functype functype() const { return NOW_FUNC; }
+};
+
+
+class Item_func_now_utc :public Item_func_now
+{
+public:
+ Item_func_now_utc(uint dec) :Item_func_now(dec) {}
+ const char *func_name() const { return "utc_timestamp"; }
+ virtual void store_now_in_TIME(MYSQL_TIME *now_time);
+};
+
+
+/*
+ This is like NOW(), but always uses the real current time, not the
+ query_start(). This matches the Oracle behavior.
+*/
+class Item_func_sysdate_local :public Item_func_now
+{
+public:
+ Item_func_sysdate_local(uint dec) :Item_func_now(dec) {}
+ bool const_item() const { return 0; }
+ const char *func_name() const { return "sysdate"; }
+ void store_now_in_TIME(MYSQL_TIME *now_time);
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ void update_used_tables()
+ {
+ Item_func_now::update_used_tables();
+ maybe_null= 0;
+ used_tables_cache|= RAND_TABLE_BIT;
+ }
+};
+
+
+class Item_func_from_days :public Item_datefunc
+{
+public:
+ Item_func_from_days(Item *a) :Item_datefunc(a) {}
+ const char *func_name() const { return "from_days"; }
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return has_date_args() || has_time_args();
+ }
+};
+
+
+class Item_func_date_format :public Item_str_func
+{
+ MY_LOCALE *locale;
+ int fixed_length;
+ const bool is_time_format;
+ String value;
+public:
+ Item_func_date_format(Item *a,Item *b,bool is_time_format_arg)
+ :Item_str_func(a,b),is_time_format(is_time_format_arg) {}
+ String *val_str(String *str);
+ const char *func_name() const
+ { return is_time_format ? "time_format" : "date_format"; }
+ void fix_length_and_dec();
+ uint format_length(const String *format);
+ bool eq(const Item *item, bool binary_cmp) const;
+};
+
+
+class Item_func_from_unixtime :public Item_temporal_func
+{
+ Time_zone *tz;
+ public:
+ Item_func_from_unixtime(Item *a) :Item_temporal_func(a) {}
+ const char *func_name() const { return "from_unixtime"; }
+ void fix_length_and_dec();
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+};
+
+
+/*
+ We need Time_zone class declaration for storing pointers in
+ Item_func_convert_tz.
+*/
+class Time_zone;
+
+/*
+ This class represents CONVERT_TZ() function.
+ The important fact about this function that it is handled in special way.
+ When such function is met in expression time_zone system tables are added
+ to global list of tables to open, so later those already opened and locked
+ tables can be used during this function calculation for loading time zone
+ descriptions.
+*/
+class Item_func_convert_tz :public Item_temporal_func
+{
+ /*
+ If time zone parameters are constants we are caching objects that
+ represent them (we use separate from_tz_cached/to_tz_cached members
+ to indicate this fact, since NULL is legal value for from_tz/to_tz
+ members.
+ */
+ bool from_tz_cached, to_tz_cached;
+ Time_zone *from_tz, *to_tz;
+ public:
+ Item_func_convert_tz(Item *a, Item *b, Item *c):
+ Item_temporal_func(a, b, c), from_tz_cached(0), to_tz_cached(0) {}
+ const char *func_name() const { return "convert_tz"; }
+ void fix_length_and_dec();
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ void cleanup();
+};
+
+
+class Item_func_sec_to_time :public Item_timefunc
+{
+public:
+ Item_func_sec_to_time(Item *item) :Item_timefunc(item) {}
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ void fix_length_and_dec()
+ {
+ decimals= MY_MIN(args[0]->decimals, TIME_SECOND_PART_DIGITS);
+ Item_timefunc::fix_length_and_dec();
+ }
+ const char *func_name() const { return "sec_to_time"; }
+};
+
+
+class Item_date_add_interval :public Item_temporal_hybrid_func
+{
+public:
+ const interval_type int_type; // keep it public
+ const bool date_sub_interval; // keep it public
+ Item_date_add_interval(Item *a,Item *b,interval_type type_arg,bool neg_arg)
+ :Item_temporal_hybrid_func(a,b),int_type(type_arg), date_sub_interval(neg_arg) {}
+ const char *func_name() const { return "date_add_interval"; }
+ void fix_length_and_dec();
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+ bool eq(const Item *item, bool binary_cmp) const;
+ void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_extract :public Item_int_func
+{
+ bool date_value;
+ public:
+ const interval_type int_type; // keep it public
+ Item_extract(interval_type type_arg, Item *a)
+ :Item_int_func(a), int_type(type_arg) {}
+ longlong val_int();
+ enum Functype functype() const { return EXTRACT_FUNC; }
+ const char *func_name() const { return "extract"; }
+ void fix_length_and_dec();
+ bool eq(const Item *item, bool binary_cmp) const;
+ void print(String *str, enum_query_type query_type);
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ switch (int_type) {
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_QUARTER:
+ case INTERVAL_MONTH:
+ /* case INTERVAL_WEEK: Not allowed as partitioning function, bug#57071 */
+ case INTERVAL_DAY:
+ return !has_date_args();
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_DAY_MICROSECOND:
+ return !has_datetime_args();
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ case INTERVAL_MICROSECOND:
+ case INTERVAL_HOUR_MICROSECOND:
+ case INTERVAL_MINUTE_MICROSECOND:
+ case INTERVAL_SECOND_MICROSECOND:
+ return !has_time_args();
+ default:
+ /*
+ INTERVAL_LAST is only an end marker,
+ INTERVAL_WEEK depends on default_week_format which is a session
+ variable and cannot be used for partitioning. See bug#57071.
+ */
+ break;
+ }
+ return true;
+ }
+};
+
+
+class Item_char_typecast :public Item_str_func
+{
+ uint cast_length;
+ CHARSET_INFO *cast_cs, *from_cs;
+ bool charset_conversion;
+ String tmp_value;
+public:
+ Item_char_typecast(Item *a, uint length_arg, CHARSET_INFO *cs_arg)
+ :Item_str_func(a), cast_length(length_arg), cast_cs(cs_arg) {}
+ enum Functype functype() const { return CHAR_TYPECAST_FUNC; }
+ bool eq(const Item *item, bool binary_cmp) const;
+ const char *func_name() const { return "cast_as_char"; }
+ String *val_str(String *a);
+ void fix_length_and_dec();
+ void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_temporal_typecast: public Item_temporal_func
+{
+public:
+ Item_temporal_typecast(Item *a) :Item_temporal_func(a) {}
+ virtual const char *cast_type() const = 0;
+ void print(String *str, enum_query_type query_type);
+ void fix_length_and_dec()
+ {
+ if (decimals == NOT_FIXED_DEC)
+ decimals= args[0]->temporal_precision(field_type());
+ Item_temporal_func::fix_length_and_dec();
+ }
+};
+
+class Item_date_typecast :public Item_temporal_typecast
+{
+public:
+ Item_date_typecast(Item *a) :Item_temporal_typecast(a) {}
+ const char *func_name() const { return "cast_as_date"; }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+ const char *cast_type() const { return "date"; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DATE; }
+};
+
+
+class Item_time_typecast :public Item_temporal_typecast
+{
+public:
+ Item_time_typecast(Item *a, uint dec_arg)
+ :Item_temporal_typecast(a) { decimals= dec_arg; }
+ const char *func_name() const { return "cast_as_time"; }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+ const char *cast_type() const { return "time"; }
+ enum_field_types field_type() const { return MYSQL_TYPE_TIME; }
+};
+
+
+class Item_datetime_typecast :public Item_temporal_typecast
+{
+public:
+ Item_datetime_typecast(Item *a, uint dec_arg)
+ :Item_temporal_typecast(a) { decimals= dec_arg; }
+ const char *func_name() const { return "cast_as_datetime"; }
+ const char *cast_type() const { return "datetime"; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+};
+
+
+class Item_func_makedate :public Item_temporal_func
+{
+public:
+ Item_func_makedate(Item *a,Item *b) :Item_temporal_func(a,b) {}
+ const char *func_name() const { return "makedate"; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DATE; }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+};
+
+
+class Item_func_add_time :public Item_temporal_hybrid_func
+{
+ const bool is_date;
+ int sign;
+
+public:
+ Item_func_add_time(Item *a, Item *b, bool type_arg, bool neg_arg)
+ :Item_temporal_hybrid_func(a, b), is_date(type_arg) { sign= neg_arg ? -1 : 1; }
+ void fix_length_and_dec();
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+ void print(String *str, enum_query_type query_type);
+ const char *func_name() const { return "add_time"; }
+};
+
+class Item_func_timediff :public Item_timefunc
+{
+public:
+ Item_func_timediff(Item *a, Item *b)
+ :Item_timefunc(a, b) {}
+ const char *func_name() const { return "timediff"; }
+ void fix_length_and_dec()
+ {
+ decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME),
+ args[1]->temporal_precision(MYSQL_TYPE_TIME));
+ Item_timefunc::fix_length_and_dec();
+ }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+};
+
+class Item_func_maketime :public Item_timefunc
+{
+public:
+ Item_func_maketime(Item *a, Item *b, Item *c)
+ :Item_timefunc(a, b, c)
+ {}
+ void fix_length_and_dec()
+ {
+ decimals= MY_MIN(args[2]->decimals, TIME_SECOND_PART_DIGITS);
+ Item_timefunc::fix_length_and_dec();
+ }
+ const char *func_name() const { return "maketime"; }
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+};
+
+
+class Item_func_microsecond :public Item_int_func
+{
+public:
+ Item_func_microsecond(Item *a) :Item_int_func(a) {}
+ longlong val_int();
+ const char *func_name() const { return "microsecond"; }
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ maybe_null=1;
+ }
+ bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
+ bool check_vcol_func_processor(uchar *int_arg) { return FALSE;}
+ bool check_valid_arguments_processor(uchar *int_arg)
+ {
+ return !has_time_args();
+ }
+};
+
+
+class Item_func_timestamp_diff :public Item_int_func
+{
+ const interval_type int_type;
+public:
+ Item_func_timestamp_diff(Item *a,Item *b,interval_type type_arg)
+ :Item_int_func(a,b), int_type(type_arg) {}
+ const char *func_name() const { return "timestampdiff"; }
+ longlong val_int();
+ void fix_length_and_dec()
+ {
+ decimals=0;
+ maybe_null=1;
+ }
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+enum date_time_format
+{
+ USA_FORMAT, JIS_FORMAT, ISO_FORMAT, EUR_FORMAT, INTERNAL_FORMAT
+};
+
+class Item_func_get_format :public Item_str_ascii_func
+{
+public:
+ const timestamp_type type; // keep it public
+ Item_func_get_format(timestamp_type type_arg, Item *a)
+ :Item_str_ascii_func(a), type(type_arg)
+ {}
+ String *val_str_ascii(String *str);
+ const char *func_name() const { return "get_format"; }
+ void fix_length_and_dec()
+ {
+ maybe_null= 1;
+ decimals=0;
+ fix_length_and_charset(17, default_charset());
+ }
+ virtual void print(String *str, enum_query_type query_type);
+};
+
+
+class Item_func_str_to_date :public Item_temporal_hybrid_func
+{
+ timestamp_type cached_timestamp_type;
+ bool const_item;
+ String subject_converter;
+ String format_converter;
+ CHARSET_INFO *internal_charset;
+public:
+ Item_func_str_to_date(Item *a, Item *b)
+ :Item_temporal_hybrid_func(a, b), const_item(false),
+ internal_charset(NULL)
+ {}
+ bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
+ const char *func_name() const { return "str_to_date"; }
+ void fix_length_and_dec();
+};
+
+
+class Item_func_last_day :public Item_datefunc
+{
+public:
+ Item_func_last_day(Item *a) :Item_datefunc(a) {}
+ const char *func_name() const { return "last_day"; }
+ bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
+};
+
+#endif /* ITEM_TIMEFUNC_INCLUDED */
diff --git a/sql/item_xmlfunc.cc b/sql/item_xmlfunc.cc
new file mode 100644
index 00000000000..f8bf7cbf93a
--- /dev/null
+++ b/sql/item_xmlfunc.cc
@@ -0,0 +1,2973 @@
+/* Copyright (c) 2005, 2013, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifdef __GNUC__
+#pragma implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h"
+#include "my_xml.h"
+#include "sp_pcontext.h"
+#include "sql_class.h" // THD
+
+/*
+ TODO: future development directions:
+ 1. add real constants for XPATH_NODESET_CMP and XPATH_NODESET
+ into enum Type in item.h.
+ 2. add nodeset_to_nodeset_comparator
+ 3. add lacking functions:
+ - name()
+ - lang()
+ - string()
+ - id()
+ - translate()
+ - local-name()
+ - starts-with()
+ - namespace-uri()
+ - substring-after()
+ - normalize-space()
+ - substring-before()
+ 4. add lacking axis:
+ - following-sibling
+ - following,
+ - preceding-sibling
+ - preceding
+*/
+
+
+/* Structure to store a parsed XML tree */
+typedef struct my_xml_node_st
+{
+ uint level; /* level in XML tree, 0 means root node */
+ enum my_xml_node_type type; /* node type: node, or attribute, or text */
+ uint parent; /* link to the parent */
+ const char *beg; /* beginning of the name or text */
+ const char *end; /* end of the name or text */
+ const char *tagend; /* where this tag ends */
+} MY_XML_NODE;
+
+
+/* Lexical analizer token */
+typedef struct my_xpath_lex_st
+{
+ int term; /* token type, see MY_XPATH_LEX_XXXXX below */
+ const char *beg; /* beginnign of the token */
+ const char *end; /* end of the token */
+} MY_XPATH_LEX;
+
+
+/* Structure to store nodesets */
+typedef struct my_xpath_flt_st
+{
+ uint num; /* absolute position in MY_XML_NODE array */
+ uint pos; /* relative position in context */
+ uint size; /* context size */
+} MY_XPATH_FLT;
+
+
+/* XPath function creator */
+typedef struct my_xpath_function_names_st
+{
+ const char *name; /* function name */
+ size_t length; /* function name length */
+ size_t minargs; /* min number of arguments */
+ size_t maxargs; /* max number of arguments */
+ Item *(*create)(struct my_xpath_st *xpath, Item **args, uint nargs);
+} MY_XPATH_FUNC;
+
+
+/* XPath query parser */
+typedef struct my_xpath_st
+{
+ int debug;
+ MY_XPATH_LEX query; /* Whole query */
+ MY_XPATH_LEX lasttok; /* last scanned token */
+ MY_XPATH_LEX prevtok; /* previous scanned token */
+ int axis; /* last scanned axis */
+ int extra; /* last scanned "extra", context dependent */
+ MY_XPATH_FUNC *func; /* last scanned function creator */
+ Item *item; /* current expression */
+ Item *context; /* last scanned context */
+ Item *rootelement; /* The root element */
+ String *context_cache; /* last context provider */
+ String *pxml; /* Parsed XML, an array of MY_XML_NODE */
+ CHARSET_INFO *cs; /* character set/collation string comparison */
+ int error;
+} MY_XPATH;
+
+
+/* Dynamic array of MY_XPATH_FLT */
+class XPathFilter :public String
+{
+public:
+ XPathFilter() :String() {}
+ inline bool append_element(MY_XPATH_FLT *flt)
+ {
+ String *str= this;
+ return str->append((const char*)flt, (uint32) sizeof(MY_XPATH_FLT));
+ }
+ inline bool append_element(uint32 num, uint32 pos)
+ {
+ MY_XPATH_FLT add;
+ add.num= num;
+ add.pos= pos;
+ add.size= 0;
+ return append_element(&add);
+ }
+ inline bool append_element(uint32 num, uint32 pos, uint32 size)
+ {
+ MY_XPATH_FLT add;
+ add.num= num;
+ add.pos= pos;
+ add.size= size;
+ return append_element(&add);
+ }
+ inline MY_XPATH_FLT *element(uint i)
+ {
+ return (MY_XPATH_FLT*) (ptr() + i * sizeof(MY_XPATH_FLT));
+ }
+ inline uint32 numelements()
+ {
+ return length() / sizeof(MY_XPATH_FLT);
+ }
+};
+
+
+/*
+ Common features of the functions returning a node set.
+*/
+class Item_nodeset_func :public Item_str_func
+{
+protected:
+ String tmp_value, tmp2_value;
+ MY_XPATH_FLT *fltbeg, *fltend;
+ MY_XML_NODE *nodebeg, *nodeend;
+ uint numnodes;
+public:
+ String *pxml;
+ String context_cache;
+ Item_nodeset_func(String *pxml_arg) :Item_str_func(), pxml(pxml_arg) {}
+ Item_nodeset_func(Item *a, String *pxml_arg)
+ :Item_str_func(a), pxml(pxml_arg) {}
+ Item_nodeset_func(Item *a, Item *b, String *pxml_arg)
+ :Item_str_func(a, b), pxml(pxml_arg) {}
+ Item_nodeset_func(Item *a, Item *b, Item *c, String *pxml_arg)
+ :Item_str_func(a,b,c), pxml(pxml_arg) {}
+ void prepare_nodes()
+ {
+ nodebeg= (MY_XML_NODE*) pxml->ptr();
+ nodeend= (MY_XML_NODE*) (pxml->ptr() + pxml->length());
+ numnodes= nodeend - nodebeg;
+ }
+ void prepare(String *nodeset)
+ {
+ prepare_nodes();
+ String *res= args[0]->val_nodeset(&tmp_value);
+ fltbeg= (MY_XPATH_FLT*) res->ptr();
+ fltend= (MY_XPATH_FLT*) (res->ptr() + res->length());
+ nodeset->length(0);
+ }
+ enum Type type() const { return XPATH_NODESET; }
+ String *val_str(String *str)
+ {
+ prepare_nodes();
+ String *res= val_nodeset(&tmp2_value);
+ fltbeg= (MY_XPATH_FLT*) res->ptr();
+ fltend= (MY_XPATH_FLT*) (res->ptr() + res->length());
+ String active;
+ active.alloc(numnodes);
+ bzero((char*) active.ptr(), numnodes);
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ MY_XML_NODE *node;
+ uint j;
+ for (j=0, node= nodebeg ; j < numnodes; j++, node++)
+ {
+ if (node->type == MY_XML_NODE_TEXT &&
+ node->parent == flt->num)
+ active[j]= 1;
+ }
+ }
+
+ str->length(0);
+ str->set_charset(collation.collation);
+ for (uint i=0 ; i < numnodes; i++)
+ {
+ if(active[i])
+ {
+ if (str->length())
+ str->append(" ", 1, &my_charset_latin1);
+ str->append(nodebeg[i].beg, nodebeg[i].end - nodebeg[i].beg);
+ }
+ }
+ return str;
+ }
+ enum Item_result result_type () const { return STRING_RESULT; }
+ void fix_length_and_dec()
+ {
+ max_length= MAX_BLOB_WIDTH;
+ collation.collation= pxml->charset();
+ // To avoid premature evaluation, mark all nodeset functions as non-const.
+ used_tables_cache= RAND_TABLE_BIT;
+ const_item_cache= false;
+ }
+ const char *func_name() const { return "nodeset"; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+
+};
+
+
+/* Returns an XML root */
+class Item_nodeset_func_rootelement :public Item_nodeset_func
+{
+public:
+ Item_nodeset_func_rootelement(String *pxml): Item_nodeset_func(pxml) {}
+ const char *func_name() const { return "xpath_rootelement"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Returns a Union of two node sets */
+class Item_nodeset_func_union :public Item_nodeset_func
+{
+public:
+ Item_nodeset_func_union(Item *a, Item *b, String *pxml)
+ :Item_nodeset_func(a, b, pxml) {}
+ const char *func_name() const { return "xpath_union"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Makes one step towards the given axis */
+class Item_nodeset_func_axisbyname :public Item_nodeset_func
+{
+ const char *node_name;
+ uint node_namelen;
+public:
+ Item_nodeset_func_axisbyname(Item *a, const char *n_arg, uint l_arg,
+ String *pxml):
+ Item_nodeset_func(a, pxml), node_name(n_arg), node_namelen(l_arg) { }
+ const char *func_name() const { return "xpath_axisbyname"; }
+ bool validname(MY_XML_NODE *n)
+ {
+ if (node_name[0] == '*')
+ return 1;
+ return (node_namelen == (uint) (n->end - n->beg)) &&
+ !memcmp(node_name, n->beg, node_namelen);
+ }
+};
+
+
+/* Returns self */
+class Item_nodeset_func_selfbyname: public Item_nodeset_func_axisbyname
+{
+public:
+ Item_nodeset_func_selfbyname(Item *a, const char *n_arg, uint l_arg,
+ String *pxml):
+ Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {}
+ const char *func_name() const { return "xpath_selfbyname"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Returns children */
+class Item_nodeset_func_childbyname: public Item_nodeset_func_axisbyname
+{
+public:
+ Item_nodeset_func_childbyname(Item *a, const char *n_arg, uint l_arg,
+ String *pxml):
+ Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {}
+ const char *func_name() const { return "xpath_childbyname"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Returns descendants */
+class Item_nodeset_func_descendantbyname: public Item_nodeset_func_axisbyname
+{
+ bool need_self;
+public:
+ Item_nodeset_func_descendantbyname(Item *a, const char *n_arg, uint l_arg,
+ String *pxml, bool need_self_arg):
+ Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml),
+ need_self(need_self_arg) {}
+ const char *func_name() const { return "xpath_descendantbyname"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Returns ancestors */
+class Item_nodeset_func_ancestorbyname: public Item_nodeset_func_axisbyname
+{
+ bool need_self;
+public:
+ Item_nodeset_func_ancestorbyname(Item *a, const char *n_arg, uint l_arg,
+ String *pxml, bool need_self_arg):
+ Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml),
+ need_self(need_self_arg) {}
+ const char *func_name() const { return "xpath_ancestorbyname"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Returns parents */
+class Item_nodeset_func_parentbyname: public Item_nodeset_func_axisbyname
+{
+public:
+ Item_nodeset_func_parentbyname(Item *a, const char *n_arg, uint l_arg,
+ String *pxml):
+ Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {}
+ const char *func_name() const { return "xpath_parentbyname"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Returns attributes */
+class Item_nodeset_func_attributebyname: public Item_nodeset_func_axisbyname
+{
+public:
+ Item_nodeset_func_attributebyname(Item *a, const char *n_arg, uint l_arg,
+ String *pxml):
+ Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {}
+ const char *func_name() const { return "xpath_attributebyname"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/*
+ Condition iterator: goes through all nodes in the current
+ context and checks a condition, returning those nodes
+ giving TRUE condition result.
+*/
+class Item_nodeset_func_predicate :public Item_nodeset_func
+{
+public:
+ Item_nodeset_func_predicate(Item *a, Item *b, String *pxml):
+ Item_nodeset_func(a, b, pxml) {}
+ const char *func_name() const { return "xpath_predicate"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/* Selects nodes with a given position in context */
+class Item_nodeset_func_elementbyindex :public Item_nodeset_func
+{
+public:
+ Item_nodeset_func_elementbyindex(Item *a, Item *b, String *pxml):
+ Item_nodeset_func(a, b, pxml) { }
+ const char *func_name() const { return "xpath_elementbyindex"; }
+ String *val_nodeset(String *nodeset);
+};
+
+
+/*
+ We need to distinguish a number from a boolean:
+ a[1] and a[true] are different things in XPath.
+*/
+class Item_bool :public Item_int
+{
+public:
+ Item_bool(int32 i): Item_int(i) {}
+ const char *func_name() const { return "xpath_bool"; }
+ bool is_bool_func() { return 1; }
+};
+
+
+/*
+ Converts its argument into a boolean value.
+ * a number is true if it is non-zero
+ * a node-set is true if and only if it is non-empty
+ * a string is true if and only if its length is non-zero
+*/
+class Item_xpath_cast_bool :public Item_int_func
+{
+ String *pxml;
+ String tmp_value;
+public:
+ Item_xpath_cast_bool(Item *a, String *pxml_arg)
+ :Item_int_func(a), pxml(pxml_arg) {}
+ const char *func_name() const { return "xpath_cast_bool"; }
+ bool is_bool_func() { return 1; }
+ longlong val_int()
+ {
+ if (args[0]->type() == XPATH_NODESET)
+ {
+ String *flt= args[0]->val_nodeset(&tmp_value);
+ return flt->length() == sizeof(MY_XPATH_FLT) ? 1 : 0;
+ }
+ return args[0]->val_real() ? 1 : 0;
+ }
+};
+
+
+/*
+ Converts its argument into a number
+*/
+class Item_xpath_cast_number :public Item_real_func
+{
+public:
+ Item_xpath_cast_number(Item *a): Item_real_func(a) {}
+ const char *func_name() const { return "xpath_cast_number"; }
+ virtual double val_real() { return args[0]->val_real(); }
+};
+
+
+/*
+ Context cache, for predicate
+*/
+class Item_nodeset_context_cache :public Item_nodeset_func
+{
+public:
+ String *string_cache;
+ Item_nodeset_context_cache(String *str_arg, String *pxml):
+ Item_nodeset_func(pxml), string_cache(str_arg) { }
+ String *val_nodeset(String *res)
+ { return string_cache; }
+ void fix_length_and_dec() { max_length= MAX_BLOB_WIDTH; }
+};
+
+
+class Item_func_xpath_position :public Item_int_func
+{
+ String *pxml;
+ String tmp_value;
+public:
+ Item_func_xpath_position(Item *a, String *p)
+ :Item_int_func(a), pxml(p) {}
+ const char *func_name() const { return "xpath_position"; }
+ void fix_length_and_dec() { max_length=10; }
+ longlong val_int()
+ {
+ String *flt= args[0]->val_nodeset(&tmp_value);
+ if (flt->length() == sizeof(MY_XPATH_FLT))
+ return ((MY_XPATH_FLT*)flt->ptr())->pos + 1;
+ return 0;
+ }
+};
+
+
+class Item_func_xpath_count :public Item_int_func
+{
+ String *pxml;
+ String tmp_value;
+public:
+ Item_func_xpath_count(Item *a, String *p)
+ :Item_int_func(a), pxml(p) {}
+ const char *func_name() const { return "xpath_count"; }
+ void fix_length_and_dec() { max_length=10; }
+ longlong val_int()
+ {
+ uint predicate_supplied_context_size;
+ String *res= args[0]->val_nodeset(&tmp_value);
+ if (res->length() == sizeof(MY_XPATH_FLT) &&
+ (predicate_supplied_context_size= ((MY_XPATH_FLT*)res->ptr())->size))
+ return predicate_supplied_context_size;
+ return res->length() / sizeof(MY_XPATH_FLT);
+ }
+};
+
+
+class Item_func_xpath_sum :public Item_real_func
+{
+ String *pxml;
+ String tmp_value;
+public:
+ Item_func_xpath_sum(Item *a, String *p)
+ :Item_real_func(a), pxml(p) {}
+
+ const char *func_name() const { return "xpath_sum"; }
+ double val_real()
+ {
+ double sum= 0;
+ String *res= args[0]->val_nodeset(&tmp_value);
+ MY_XPATH_FLT *fltbeg= (MY_XPATH_FLT*) res->ptr();
+ MY_XPATH_FLT *fltend= (MY_XPATH_FLT*) (res->ptr() + res->length());
+ uint numnodes= pxml->length() / sizeof(MY_XML_NODE);
+ MY_XML_NODE *nodebeg= (MY_XML_NODE*) pxml->ptr();
+
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ MY_XML_NODE *self= &nodebeg[flt->num];
+ for (uint j= flt->num + 1; j < numnodes; j++)
+ {
+ MY_XML_NODE *node= &nodebeg[j];
+ if (node->level <= self->level)
+ break;
+ if ((node->parent == flt->num) &&
+ (node->type == MY_XML_NODE_TEXT))
+ {
+ char *end;
+ int err;
+ double add= my_strntod(collation.collation, (char*) node->beg,
+ node->end - node->beg, &end, &err);
+ if (!err)
+ sum+= add;
+ }
+ }
+ }
+ return sum;
+ }
+};
+
+
+/**
+ A string whose value may be changed during execution.
+*/
+class Item_string_xml_non_const: public Item_string
+{
+public:
+ Item_string_xml_non_const(const char *str, uint length, CHARSET_INFO *cs)
+ :Item_string(str, length, cs)
+ { }
+ bool const_item() const { return false ; }
+ bool basic_const_item() const { return false; }
+ void set_value(const char *str, uint length, CHARSET_INFO *cs)
+ {
+ str_value.set(str, length, cs);
+ }
+ Item *safe_charset_converter(CHARSET_INFO *tocs)
+ {
+ /*
+ Item_string::safe_charset_converter() does not accept non-constants.
+ Note, conversion is not really needed here anyway.
+ */
+ return this;
+ }
+};
+
+
+class Item_nodeset_to_const_comparator :public Item_bool_func
+{
+ String *pxml;
+ String tmp_nodeset;
+public:
+ Item_nodeset_to_const_comparator(Item *nodeset, Item *cmpfunc, String *p)
+ :Item_bool_func(nodeset,cmpfunc), pxml(p) {}
+ enum Type type() const { return XPATH_NODESET_CMP; };
+ const char *func_name() const { return "xpath_nodeset_to_const_comparator"; }
+ bool is_bool_func() { return 1; }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+
+ longlong val_int()
+ {
+ Item_func *comp= (Item_func*)args[1];
+ Item_string_xml_non_const *fake=
+ (Item_string_xml_non_const*)(comp->arguments()[0]);
+ String *res= args[0]->val_nodeset(&tmp_nodeset);
+ MY_XPATH_FLT *fltbeg= (MY_XPATH_FLT*) res->ptr();
+ MY_XPATH_FLT *fltend= (MY_XPATH_FLT*) (res->ptr() + res->length());
+ MY_XML_NODE *nodebeg= (MY_XML_NODE*) pxml->ptr();
+ uint numnodes= pxml->length() / sizeof(MY_XML_NODE);
+
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ MY_XML_NODE *self= &nodebeg[flt->num];
+ for (uint j= flt->num + 1; j < numnodes; j++)
+ {
+ MY_XML_NODE *node= &nodebeg[j];
+ if (node->level <= self->level)
+ break;
+ if ((node->parent == flt->num) &&
+ (node->type == MY_XML_NODE_TEXT))
+ {
+ fake->set_value(node->beg, node->end - node->beg,
+ collation.collation);
+ if (args[1]->val_int())
+ return 1;
+ }
+ }
+ }
+ return 0;
+ }
+};
+
+
+String *Item_nodeset_func_rootelement::val_nodeset(String *nodeset)
+{
+ nodeset->length(0);
+ ((XPathFilter*)nodeset)->append_element(0, 0);
+ return nodeset;
+}
+
+
+String * Item_nodeset_func_union::val_nodeset(String *nodeset)
+{
+ uint num_nodes= pxml->length() / sizeof(MY_XML_NODE);
+ String set0, *s0= args[0]->val_nodeset(&set0);
+ String set1, *s1= args[1]->val_nodeset(&set1);
+ String both_str;
+ both_str.alloc(num_nodes);
+ char *both= (char*) both_str.ptr();
+ bzero((void*)both, num_nodes);
+ MY_XPATH_FLT *flt;
+
+ fltbeg= (MY_XPATH_FLT*) s0->ptr();
+ fltend= (MY_XPATH_FLT*) (s0->ptr() + s0->length());
+ for (flt= fltbeg; flt < fltend; flt++)
+ both[flt->num]= 1;
+
+ fltbeg= (MY_XPATH_FLT*) s1->ptr();
+ fltend= (MY_XPATH_FLT*) (s1->ptr() + s1->length());
+ for (flt= fltbeg; flt < fltend; flt++)
+ both[flt->num]= 1;
+
+ nodeset->length(0);
+ for (uint i= 0, pos= 0; i < num_nodes; i++)
+ {
+ if (both[i])
+ ((XPathFilter*)nodeset)->append_element(i, pos++);
+ }
+ return nodeset;
+}
+
+
+String *Item_nodeset_func_selfbyname::val_nodeset(String *nodeset)
+{
+ prepare(nodeset);
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ uint pos= 0;
+ MY_XML_NODE *self= &nodebeg[flt->num];
+ if (validname(self))
+ ((XPathFilter*)nodeset)->append_element(flt->num,pos++);
+ }
+ return nodeset;
+}
+
+
+String *Item_nodeset_func_childbyname::val_nodeset(String *nodeset)
+{
+ prepare(nodeset);
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ MY_XML_NODE *self= &nodebeg[flt->num];
+ for (uint pos= 0, j= flt->num + 1 ; j < numnodes; j++)
+ {
+ MY_XML_NODE *node= &nodebeg[j];
+ if (node->level <= self->level)
+ break;
+ if ((node->parent == flt->num) &&
+ (node->type == MY_XML_NODE_TAG) &&
+ validname(node))
+ ((XPathFilter*)nodeset)->append_element(j, pos++);
+ }
+ }
+ return nodeset;
+}
+
+
+String *Item_nodeset_func_descendantbyname::val_nodeset(String *nodeset)
+{
+ prepare(nodeset);
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ uint pos= 0;
+ MY_XML_NODE *self= &nodebeg[flt->num];
+ if (need_self && validname(self))
+ ((XPathFilter*)nodeset)->append_element(flt->num,pos++);
+ for (uint j= flt->num + 1 ; j < numnodes ; j++)
+ {
+ MY_XML_NODE *node= &nodebeg[j];
+ if (node->level <= self->level)
+ break;
+ if ((node->type == MY_XML_NODE_TAG) && validname(node))
+ ((XPathFilter*)nodeset)->append_element(j,pos++);
+ }
+ }
+ return nodeset;
+}
+
+
+String *Item_nodeset_func_ancestorbyname::val_nodeset(String *nodeset)
+{
+ char *active;
+ String active_str;
+ prepare(nodeset);
+ active_str.alloc(numnodes);
+ active= (char*) active_str.ptr();
+ bzero((void*)active, numnodes);
+ uint pos= 0;
+
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ /*
+ Go to the root and add all nodes on the way.
+ Don't add the root if context is the root itelf
+ */
+ MY_XML_NODE *self= &nodebeg[flt->num];
+ if (need_self && validname(self))
+ {
+ active[flt->num]= 1;
+ pos++;
+ }
+
+ for (uint j= self->parent; nodebeg[j].parent != j; j= nodebeg[j].parent)
+ {
+ if (flt->num && validname(&nodebeg[j]))
+ {
+ active[j]= 1;
+ pos++;
+ }
+ }
+ }
+
+ for (uint j= 0; j < numnodes ; j++)
+ {
+ if (active[j])
+ ((XPathFilter*)nodeset)->append_element(j, --pos);
+ }
+ return nodeset;
+}
+
+
+String *Item_nodeset_func_parentbyname::val_nodeset(String *nodeset)
+{
+ char *active;
+ String active_str;
+ prepare(nodeset);
+ active_str.alloc(numnodes);
+ active= (char*) active_str.ptr();
+ bzero((void*)active, numnodes);
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ uint j= nodebeg[flt->num].parent;
+ if (flt->num && validname(&nodebeg[j]))
+ active[j]= 1;
+ }
+ for (uint j= 0, pos= 0; j < numnodes ; j++)
+ {
+ if (active[j])
+ ((XPathFilter*)nodeset)->append_element(j, pos++);
+ }
+ return nodeset;
+}
+
+
+String *Item_nodeset_func_attributebyname::val_nodeset(String *nodeset)
+{
+ prepare(nodeset);
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ MY_XML_NODE *self= &nodebeg[flt->num];
+ for (uint pos=0, j= flt->num + 1 ; j < numnodes; j++)
+ {
+ MY_XML_NODE *node= &nodebeg[j];
+ if (node->level <= self->level)
+ break;
+ if ((node->parent == flt->num) &&
+ (node->type == MY_XML_NODE_ATTR) &&
+ validname(node))
+ ((XPathFilter*)nodeset)->append_element(j, pos++);
+ }
+ }
+ return nodeset;
+}
+
+
+String *Item_nodeset_func_predicate::val_nodeset(String *str)
+{
+ Item_nodeset_func *nodeset_func= (Item_nodeset_func*) args[0];
+ Item_func *comp_func= (Item_func*)args[1];
+ uint pos= 0, size;
+ prepare(str);
+ size= fltend - fltbeg;
+ for (MY_XPATH_FLT *flt= fltbeg; flt < fltend; flt++)
+ {
+ nodeset_func->context_cache.length(0);
+ ((XPathFilter*)(&nodeset_func->context_cache))->append_element(flt->num,
+ flt->pos,
+ size);
+ if (comp_func->val_int())
+ ((XPathFilter*)str)->append_element(flt->num, pos++);
+ }
+ return str;
+}
+
+
+String *Item_nodeset_func_elementbyindex::val_nodeset(String *nodeset)
+{
+ Item_nodeset_func *nodeset_func= (Item_nodeset_func*) args[0];
+ prepare(nodeset);
+ MY_XPATH_FLT *flt;
+ uint pos, size= fltend - fltbeg;
+ for (pos= 0, flt= fltbeg; flt < fltend; flt++)
+ {
+ nodeset_func->context_cache.length(0);
+ ((XPathFilter*)(&nodeset_func->context_cache))->append_element(flt->num,
+ flt->pos,
+ size);
+ int index= (int) (args[1]->val_int()) - 1;
+ if (index >= 0 && (flt->pos == (uint) index || args[1]->is_bool_func()))
+ ((XPathFilter*)nodeset)->append_element(flt->num, pos++);
+ }
+ return nodeset;
+}
+
+
+/*
+ If item is a node set, then casts it to boolean,
+ otherwise returns the item itself.
+*/
+static Item* nodeset2bool(MY_XPATH *xpath, Item *item)
+{
+ if (item->type() == Item::XPATH_NODESET)
+ return new Item_xpath_cast_bool(item, xpath->pxml);
+ return item;
+}
+
+
+/*
+ XPath lexical tokens
+*/
+#define MY_XPATH_LEX_DIGITS 'd'
+#define MY_XPATH_LEX_IDENT 'i'
+#define MY_XPATH_LEX_STRING 's'
+#define MY_XPATH_LEX_SLASH '/'
+#define MY_XPATH_LEX_LB '['
+#define MY_XPATH_LEX_RB ']'
+#define MY_XPATH_LEX_LP '('
+#define MY_XPATH_LEX_RP ')'
+#define MY_XPATH_LEX_EQ '='
+#define MY_XPATH_LEX_LESS '<'
+#define MY_XPATH_LEX_GREATER '>'
+#define MY_XPATH_LEX_AT '@'
+#define MY_XPATH_LEX_COLON ':'
+#define MY_XPATH_LEX_ASTERISK '*'
+#define MY_XPATH_LEX_DOT '.'
+#define MY_XPATH_LEX_VLINE '|'
+#define MY_XPATH_LEX_MINUS '-'
+#define MY_XPATH_LEX_PLUS '+'
+#define MY_XPATH_LEX_EXCL '!'
+#define MY_XPATH_LEX_COMMA ','
+#define MY_XPATH_LEX_DOLLAR '$'
+#define MY_XPATH_LEX_ERROR 'A'
+#define MY_XPATH_LEX_EOF 'B'
+#define MY_XPATH_LEX_AND 'C'
+#define MY_XPATH_LEX_OR 'D'
+#define MY_XPATH_LEX_DIV 'E'
+#define MY_XPATH_LEX_MOD 'F'
+#define MY_XPATH_LEX_FUNC 'G'
+#define MY_XPATH_LEX_NODETYPE 'H'
+#define MY_XPATH_LEX_AXIS 'I'
+#define MY_XPATH_LEX_LE 'J'
+#define MY_XPATH_LEX_GE 'K'
+
+
+/*
+ XPath axis type
+*/
+#define MY_XPATH_AXIS_ANCESTOR 0
+#define MY_XPATH_AXIS_ANCESTOR_OR_SELF 1
+#define MY_XPATH_AXIS_ATTRIBUTE 2
+#define MY_XPATH_AXIS_CHILD 3
+#define MY_XPATH_AXIS_DESCENDANT 4
+#define MY_XPATH_AXIS_DESCENDANT_OR_SELF 5
+#define MY_XPATH_AXIS_FOLLOWING 6
+#define MY_XPATH_AXIS_FOLLOWING_SIBLING 7
+#define MY_XPATH_AXIS_NAMESPACE 8
+#define MY_XPATH_AXIS_PARENT 9
+#define MY_XPATH_AXIS_PRECEDING 10
+#define MY_XPATH_AXIS_PRECEDING_SIBLING 11
+#define MY_XPATH_AXIS_SELF 12
+
+
+/*
+ Create scalar comparator
+
+ SYNOPSYS
+ Create a comparator function for scalar arguments,
+ for the given arguments and operation.
+
+ RETURN
+ The newly created item.
+*/
+static Item *eq_func(int oper, Item *a, Item *b)
+{
+ switch (oper)
+ {
+ case '=': return new Item_func_eq(a, b);
+ case '!': return new Item_func_ne(a, b);
+ case MY_XPATH_LEX_GE: return new Item_func_ge(a, b);
+ case MY_XPATH_LEX_LE: return new Item_func_le(a, b);
+ case MY_XPATH_LEX_GREATER: return new Item_func_gt(a, b);
+ case MY_XPATH_LEX_LESS: return new Item_func_lt(a, b);
+ }
+ return 0;
+}
+
+
+/*
+ Create scalar comparator
+
+ SYNOPSYS
+ Create a comparator function for scalar arguments,
+ for the given arguments and reverse operation, e.g.
+
+ A > B is converted into B < A
+
+ RETURN
+ The newly created item.
+*/
+static Item *eq_func_reverse(int oper, Item *a, Item *b)
+{
+ switch (oper)
+ {
+ case '=': return new Item_func_eq(a, b);
+ case '!': return new Item_func_ne(a, b);
+ case MY_XPATH_LEX_GE: return new Item_func_le(a, b);
+ case MY_XPATH_LEX_LE: return new Item_func_ge(a, b);
+ case MY_XPATH_LEX_GREATER: return new Item_func_lt(a, b);
+ case MY_XPATH_LEX_LESS: return new Item_func_gt(a, b);
+ }
+ return 0;
+}
+
+
+/*
+ Create a comparator
+
+ SYNOPSYS
+ Create a comparator for scalar or non-scalar arguments,
+ for the given arguments and operation.
+
+ RETURN
+ The newly created item.
+*/
+static Item *create_comparator(MY_XPATH *xpath,
+ int oper, MY_XPATH_LEX *context,
+ Item *a, Item *b)
+{
+ if (a->type() != Item::XPATH_NODESET &&
+ b->type() != Item::XPATH_NODESET)
+ {
+ return eq_func(oper, a, b); // two scalar arguments
+ }
+ else if (a->type() == Item::XPATH_NODESET &&
+ b->type() == Item::XPATH_NODESET)
+ {
+ uint len= xpath->query.end - context->beg;
+ set_if_smaller(len, 32);
+ my_printf_error(ER_UNKNOWN_ERROR,
+ "XPATH error: "
+ "comparison of two nodesets is not supported: '%.*s'",
+ MYF(0), len, context->beg);
+
+ return 0; // TODO: Comparison of two nodesets
+ }
+ else
+ {
+ /*
+ Compare a node set to a scalar value.
+ We just create a fake Item_string_xml_non_const() argument,
+ which will be filled to the partular value
+ in a loop through all of the nodes in the node set.
+ */
+
+ Item_string *fake= new Item_string_xml_non_const("", 0, xpath->cs);
+ Item_nodeset_func *nodeset;
+ Item *scalar, *comp;
+ if (a->type() == Item::XPATH_NODESET)
+ {
+ nodeset= (Item_nodeset_func*) a;
+ scalar= b;
+ comp= eq_func(oper, (Item*)fake, scalar);
+ }
+ else
+ {
+ nodeset= (Item_nodeset_func*) b;
+ scalar= a;
+ comp= eq_func_reverse(oper, fake, scalar);
+ }
+ return new Item_nodeset_to_const_comparator(nodeset, comp, xpath->pxml);
+ }
+}
+
+
+/*
+ Create a step
+
+ SYNOPSYS
+ Create a step function for the given argument and axis.
+
+ RETURN
+ The newly created item.
+*/
+static Item* nametestfunc(MY_XPATH *xpath,
+ int type, Item *arg, const char *beg, uint len)
+{
+ DBUG_ASSERT(arg != 0);
+ DBUG_ASSERT(arg->type() == Item::XPATH_NODESET);
+ DBUG_ASSERT(beg != 0);
+ DBUG_ASSERT(len > 0);
+
+ Item *res;
+ switch (type)
+ {
+ case MY_XPATH_AXIS_ANCESTOR:
+ res= new Item_nodeset_func_ancestorbyname(arg, beg, len, xpath->pxml, 0);
+ break;
+ case MY_XPATH_AXIS_ANCESTOR_OR_SELF:
+ res= new Item_nodeset_func_ancestorbyname(arg, beg, len, xpath->pxml, 1);
+ break;
+ case MY_XPATH_AXIS_PARENT:
+ res= new Item_nodeset_func_parentbyname(arg, beg, len, xpath->pxml);
+ break;
+ case MY_XPATH_AXIS_DESCENDANT:
+ res= new Item_nodeset_func_descendantbyname(arg, beg, len, xpath->pxml, 0);
+ break;
+ case MY_XPATH_AXIS_DESCENDANT_OR_SELF:
+ res= new Item_nodeset_func_descendantbyname(arg, beg, len, xpath->pxml, 1);
+ break;
+ case MY_XPATH_AXIS_ATTRIBUTE:
+ res= new Item_nodeset_func_attributebyname(arg, beg, len, xpath->pxml);
+ break;
+ case MY_XPATH_AXIS_SELF:
+ res= new Item_nodeset_func_selfbyname(arg, beg, len, xpath->pxml);
+ break;
+ default:
+ res= new Item_nodeset_func_childbyname(arg, beg, len, xpath->pxml);
+ }
+ return res;
+}
+
+
+/*
+ Tokens consisting of one character, for faster lexical analizer.
+*/
+static char simpletok[128]=
+{
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+/*
+ ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
+ @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
+ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ \200
+*/
+ 0,1,0,0,1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,0,
+ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0
+};
+
+
+/*
+ XPath keywords
+*/
+struct my_xpath_keyword_names_st
+{
+ int tok;
+ const char *name;
+ size_t length;
+ int extra;
+};
+
+
+static struct my_xpath_keyword_names_st my_keyword_names[] =
+{
+ {MY_XPATH_LEX_AND , "and" , 3, 0 },
+ {MY_XPATH_LEX_OR , "or" , 2, 0 },
+ {MY_XPATH_LEX_DIV , "div" , 3, 0 },
+ {MY_XPATH_LEX_MOD , "mod" , 3, 0 },
+ {0,NULL,0,0}
+};
+
+
+static struct my_xpath_keyword_names_st my_axis_names[]=
+{
+ {MY_XPATH_LEX_AXIS,"ancestor" , 8,MY_XPATH_AXIS_ANCESTOR },
+ {MY_XPATH_LEX_AXIS,"ancestor-or-self" ,16,MY_XPATH_AXIS_ANCESTOR_OR_SELF },
+ {MY_XPATH_LEX_AXIS,"attribute" , 9,MY_XPATH_AXIS_ATTRIBUTE },
+ {MY_XPATH_LEX_AXIS,"child" , 5,MY_XPATH_AXIS_CHILD },
+ {MY_XPATH_LEX_AXIS,"descendant" ,10,MY_XPATH_AXIS_DESCENDANT },
+ {MY_XPATH_LEX_AXIS,"descendant-or-self",18,MY_XPATH_AXIS_DESCENDANT_OR_SELF},
+ {MY_XPATH_LEX_AXIS,"following" , 9,MY_XPATH_AXIS_FOLLOWING },
+ {MY_XPATH_LEX_AXIS,"following-sibling" ,17,MY_XPATH_AXIS_FOLLOWING_SIBLING },
+ {MY_XPATH_LEX_AXIS,"namespace" , 9,MY_XPATH_AXIS_NAMESPACE },
+ {MY_XPATH_LEX_AXIS,"parent" , 6,MY_XPATH_AXIS_PARENT },
+ {MY_XPATH_LEX_AXIS,"preceding" , 9,MY_XPATH_AXIS_PRECEDING },
+ {MY_XPATH_LEX_AXIS,"preceding-sibling" ,17,MY_XPATH_AXIS_PRECEDING_SIBLING },
+ {MY_XPATH_LEX_AXIS,"self" , 4,MY_XPATH_AXIS_SELF },
+ {0,NULL,0,0}
+};
+
+
+static struct my_xpath_keyword_names_st my_nodetype_names[]=
+{
+ {MY_XPATH_LEX_NODETYPE, "comment" , 7, 0 },
+ {MY_XPATH_LEX_NODETYPE, "text" , 4, 0 },
+ {MY_XPATH_LEX_NODETYPE, "processing-instruction" , 22,0 },
+ {MY_XPATH_LEX_NODETYPE, "node" , 4, 0 },
+ {0,NULL,0,0}
+};
+
+
+/*
+ Lookup a keyword
+
+ SYNOPSYS
+ Check that the last scanned identifier is a keyword.
+
+ RETURN
+ - Token type, on lookup success.
+ - MY_XPATH_LEX_IDENT, on lookup failure.
+*/
+static int
+my_xpath_keyword(MY_XPATH *x,
+ struct my_xpath_keyword_names_st *keyword_names,
+ const char *beg, const char *end)
+{
+ struct my_xpath_keyword_names_st *k;
+ size_t length= end-beg;
+ for (k= keyword_names; k->name; k++)
+ {
+ if (length == k->length && !strncasecmp(beg, k->name, length))
+ {
+ x->extra= k->extra;
+ return k->tok;
+ }
+ }
+ return MY_XPATH_LEX_IDENT;
+}
+
+
+/*
+ Functions to create an item, a-la those in item_create.cc
+*/
+
+static Item *create_func_true(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_bool(1);
+}
+
+
+static Item *create_func_false(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_bool(0);
+}
+
+
+static Item *create_func_not(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_func_not(nodeset2bool(xpath, args[0]));
+}
+
+
+static Item *create_func_ceiling(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_func_ceiling(args[0]);
+}
+
+
+static Item *create_func_floor(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_func_floor(args[0]);
+}
+
+
+static Item *create_func_bool(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_xpath_cast_bool(args[0], xpath->pxml);
+}
+
+
+static Item *create_func_number(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_xpath_cast_number(args[0]);
+}
+
+
+static Item *create_func_string_length(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ Item *arg= nargs ? args[0] : xpath->context;
+ return arg ? new Item_func_char_length(arg) : 0;
+}
+
+
+static Item *create_func_round(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_func_round(args[0], new Item_int((char*)"0",0,1),0);
+}
+
+
+static Item *create_func_last(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return xpath->context ?
+ new Item_func_xpath_count(xpath->context, xpath->pxml) : NULL;
+}
+
+
+static Item *create_func_position(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return xpath->context ?
+ new Item_func_xpath_position(xpath->context, xpath->pxml) : NULL;
+}
+
+
+static Item *create_func_contains(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_xpath_cast_bool(new Item_func_locate(args[0], args[1]),
+ xpath->pxml);
+}
+
+
+static Item *create_func_concat(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ return new Item_func_concat(args[0], args[1]);
+}
+
+
+static Item *create_func_substr(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ if (nargs == 2)
+ return new Item_func_substr(args[0], args[1]);
+ else
+ return new Item_func_substr(args[0], args[1], args[2]);
+}
+
+
+static Item *create_func_count(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ if (args[0]->type() != Item::XPATH_NODESET)
+ return 0;
+ return new Item_func_xpath_count(args[0], xpath->pxml);
+}
+
+
+static Item *create_func_sum(MY_XPATH *xpath, Item **args, uint nargs)
+{
+ if (args[0]->type() != Item::XPATH_NODESET)
+ return 0;
+ return new Item_func_xpath_sum(args[0], xpath->pxml);
+}
+
+
+/*
+ Functions names. Separate lists for names with
+ lengths 3,4,5 and 6 for faster lookups.
+*/
+static MY_XPATH_FUNC my_func_names3[]=
+{
+ {"sum", 3, 1 , 1 , create_func_sum},
+ {"not", 3, 1 , 1 , create_func_not},
+ {0 , 0, 0 , 0, 0}
+};
+
+
+static MY_XPATH_FUNC my_func_names4[]=
+{
+ {"last", 4, 0, 0, create_func_last},
+ {"true", 4, 0, 0, create_func_true},
+ {"name", 4, 0, 1, 0},
+ {"lang", 4, 1, 1, 0},
+ {0 , 0, 0, 0, 0}
+};
+
+
+static MY_XPATH_FUNC my_func_names5[]=
+{
+ {"count", 5, 1, 1, create_func_count},
+ {"false", 5, 0, 0, create_func_false},
+ {"floor", 5, 1, 1, create_func_floor},
+ {"round", 5, 1, 1, create_func_round},
+ {0 , 0, 0, 0, 0}
+};
+
+
+static MY_XPATH_FUNC my_func_names6[]=
+{
+ {"concat", 6, 2, 255, create_func_concat},
+ {"number", 6, 0, 1 , create_func_number},
+ {"string", 6, 0, 1 , 0},
+ {0 , 0, 0, 0 , 0}
+};
+
+
+/* Other functions, with name longer than 6, all together */
+static MY_XPATH_FUNC my_func_names[] =
+{
+ {"id" , 2 , 1 , 1 , 0},
+ {"boolean" , 7 , 1 , 1 , create_func_bool},
+ {"ceiling" , 7 , 1 , 1 , create_func_ceiling},
+ {"position" , 8 , 0 , 0 , create_func_position},
+ {"contains" , 8 , 2 , 2 , create_func_contains},
+ {"substring" , 9 , 2 , 3 , create_func_substr},
+ {"translate" , 9 , 3 , 3 , 0},
+
+ {"local-name" , 10 , 0 , 1 , 0},
+ {"starts-with" , 11 , 2 , 2 , 0},
+ {"namespace-uri" , 13 , 0 , 1 , 0},
+ {"string-length" , 13 , 0 , 1 , create_func_string_length},
+ {"substring-after" , 15 , 2 , 2 , 0},
+ {"normalize-space" , 15 , 0 , 1 , 0},
+ {"substring-before" , 16 , 2 , 2 , 0},
+
+ {NULL,0,0,0,0}
+};
+
+
+/*
+ Lookup a function by name
+
+ SYNOPSYS
+ Lookup a function by its name.
+
+ RETURN
+ Pointer to a MY_XPATH_FUNC variable on success.
+ 0 - on failure.
+
+*/
+MY_XPATH_FUNC *
+my_xpath_function(const char *beg, const char *end)
+{
+ MY_XPATH_FUNC *k, *function_names;
+ uint length= end-beg;
+ switch (length)
+ {
+ case 1: return 0;
+ case 3: function_names= my_func_names3; break;
+ case 4: function_names= my_func_names4; break;
+ case 5: function_names= my_func_names5; break;
+ case 6: function_names= my_func_names6; break;
+ default: function_names= my_func_names;
+ }
+ for (k= function_names; k->name; k++)
+ if (k->create && length == k->length && !strncasecmp(beg, k->name, length))
+ return k;
+ return NULL;
+}
+
+
+/* Initialize a lex analizer token */
+static void
+my_xpath_lex_init(MY_XPATH_LEX *lex,
+ const char *str, const char *strend)
+{
+ lex->beg= str;
+ lex->end= strend;
+}
+
+
+/* Initialize an XPath query parser */
+static void
+my_xpath_init(MY_XPATH *xpath)
+{
+ bzero((void*)xpath, sizeof(xpath[0]));
+}
+
+
+static int
+my_xdigit(int c)
+{
+ return ((c) >= '0' && (c) <= '9');
+}
+
+
+/*
+ Scan the next token
+
+ SYNOPSYS
+ Scan the next token from the input.
+ lex->term is set to the scanned token type.
+ lex->beg and lex->end are set to the beginnig
+ and to the end of the token.
+ RETURN
+ N/A
+*/
+static void
+my_xpath_lex_scan(MY_XPATH *xpath,
+ MY_XPATH_LEX *lex, const char *beg, const char *end)
+{
+ int ch, ctype, length;
+ for ( ; beg < end && *beg == ' ' ; beg++) ; // skip leading spaces
+ lex->beg= beg;
+
+ if (beg >= end)
+ {
+ lex->end= beg;
+ lex->term= MY_XPATH_LEX_EOF; // end of line reached
+ return;
+ }
+
+ // Check ident, or a function call, or a keyword
+ if ((length= xpath->cs->cset->ctype(xpath->cs, &ctype,
+ (const uchar*) beg,
+ (const uchar*) end)) > 0 &&
+ ((ctype & (_MY_L | _MY_U)) || *beg == '_'))
+ {
+ // scan untill the end of the idenfitier
+ for (beg+= length;
+ (length= xpath->cs->cset->ctype(xpath->cs, &ctype,
+ (const uchar*) beg,
+ (const uchar*) end)) > 0 &&
+ ((ctype & (_MY_L | _MY_U | _MY_NMR)) ||
+ *beg == '_' || *beg == '-' || *beg == '.') ;
+ beg+= length) /* no op */;
+ lex->end= beg;
+
+ if (beg < end)
+ {
+ if (*beg == '(')
+ {
+ /*
+ check if a function call, e.g.: count(/a/b)
+ or a nodetype test, e.g.: /a/b/text()
+ */
+ if ((xpath->func= my_xpath_function(lex->beg, beg)))
+ lex->term= MY_XPATH_LEX_FUNC;
+ else
+ lex->term= my_xpath_keyword(xpath, my_nodetype_names,
+ lex->beg, beg);
+ return;
+ }
+ // check if an axis specifier, e.g.: /a/b/child::*
+ else if (*beg == ':' && beg + 1 < end && beg[1] == ':')
+ {
+ lex->term= my_xpath_keyword(xpath, my_axis_names,
+ lex->beg, beg);
+ return;
+ }
+ }
+ // check if a keyword
+ lex->term= my_xpath_keyword(xpath, my_keyword_names,
+ lex->beg, beg);
+ return;
+ }
+
+
+ ch= *beg++;
+
+ if (ch > 0 && ch < 128 && simpletok[ch])
+ {
+ // a token consisting of one character found
+ lex->end= beg;
+ lex->term= ch;
+ return;
+ }
+
+
+ if (my_xdigit(ch)) // a sequence of digits
+ {
+ for ( ; beg < end && my_xdigit(*beg) ; beg++) ;
+ lex->end= beg;
+ lex->term= MY_XPATH_LEX_DIGITS;
+ return;
+ }
+
+ if (ch == '"' || ch == '\'') // a string: either '...' or "..."
+ {
+ for ( ; beg < end && *beg != ch ; beg++) ;
+ if (beg < end)
+ {
+ lex->end= beg+1;
+ lex->term= MY_XPATH_LEX_STRING;
+ return;
+ }
+ else
+ {
+ // unexpected end-of-line, without closing quot sign
+ lex->end= end;
+ lex->term= MY_XPATH_LEX_ERROR;
+ return;
+ }
+ }
+
+ lex->end= beg;
+ lex->term= MY_XPATH_LEX_ERROR; // unknown character
+ return;
+}
+
+
+/*
+ Scan the given token
+
+ SYNOPSYS
+ Scan the given token and rotate lasttok to prevtok on success.
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int
+my_xpath_parse_term(MY_XPATH *xpath, int term)
+{
+ if (xpath->lasttok.term == term && !xpath->error)
+ {
+ xpath->prevtok= xpath->lasttok;
+ my_xpath_lex_scan(xpath, &xpath->lasttok,
+ xpath->lasttok.end, xpath->query.end);
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ Scan AxisName
+
+ SYNOPSYS
+ Scan an axis name and store the scanned axis type into xpath->axis.
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AxisName(MY_XPATH *xpath)
+{
+ int rc= my_xpath_parse_term(xpath, MY_XPATH_LEX_AXIS);
+ xpath->axis= xpath->extra;
+ return rc;
+}
+
+
+/*********************************************
+** Grammar rules, according to http://www.w3.org/TR/xpath
+** Implemented using recursive descendant method.
+** All the following grammar processing functions accept
+** a signle "xpath" argument and return 1 on success and 0 on error.
+** They also modify "xpath" argument by creating new items.
+*/
+
+/* [9] PredicateExpr ::= Expr */
+#define my_xpath_parse_PredicateExpr(x) my_xpath_parse_Expr((x))
+
+/* [14] Expr ::= OrExpr */
+#define my_xpath_parse_Expr(x) my_xpath_parse_OrExpr((x))
+
+static int my_xpath_parse_LocationPath(MY_XPATH *xpath);
+static int my_xpath_parse_AbsoluteLocationPath(MY_XPATH *xpath);
+static int my_xpath_parse_RelativeLocationPath(MY_XPATH *xpath);
+static int my_xpath_parse_AbbreviatedStep(MY_XPATH *xpath);
+static int my_xpath_parse_Step(MY_XPATH *xpath);
+static int my_xpath_parse_AxisSpecifier(MY_XPATH *xpath);
+static int my_xpath_parse_NodeTest(MY_XPATH *xpath);
+static int my_xpath_parse_AbbreviatedAxisSpecifier(MY_XPATH *xpath);
+static int my_xpath_parse_NameTest(MY_XPATH *xpath);
+static int my_xpath_parse_FunctionCall(MY_XPATH *xpath);
+static int my_xpath_parse_Number(MY_XPATH *xpath);
+static int my_xpath_parse_FilterExpr(MY_XPATH *xpath);
+static int my_xpath_parse_PathExpr(MY_XPATH *xpath);
+static int my_xpath_parse_OrExpr(MY_XPATH *xpath);
+static int my_xpath_parse_UnaryExpr(MY_XPATH *xpath);
+static int my_xpath_parse_MultiplicativeExpr(MY_XPATH *xpath);
+static int my_xpath_parse_AdditiveExpr(MY_XPATH *xpath);
+static int my_xpath_parse_RelationalExpr(MY_XPATH *xpath);
+static int my_xpath_parse_AndExpr(MY_XPATH *xpath);
+static int my_xpath_parse_EqualityExpr(MY_XPATH *xpath);
+static int my_xpath_parse_VariableReference(MY_XPATH *xpath);
+
+
+/*
+ Scan LocationPath
+
+ SYNOPSYS
+
+ [1] LocationPath ::= RelativeLocationPath
+ | AbsoluteLocationPath
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_LocationPath(MY_XPATH *xpath)
+{
+ Item *context= xpath->context;
+
+ if (!xpath->context)
+ xpath->context= xpath->rootelement;
+ int rc= my_xpath_parse_RelativeLocationPath(xpath) ||
+ my_xpath_parse_AbsoluteLocationPath(xpath);
+
+ xpath->item= xpath->context;
+ xpath->context= context;
+ return rc;
+}
+
+
+/*
+ Scan Absolute Location Path
+
+ SYNOPSYS
+
+ [2] AbsoluteLocationPath ::= '/' RelativeLocationPath?
+ | AbbreviatedAbsoluteLocationPath
+ [10] AbbreviatedAbsoluteLocationPath ::= '//' RelativeLocationPath
+
+ We combine these two rules into one rule for better performance:
+
+ [2,10] AbsoluteLocationPath ::= '/' RelativeLocationPath?
+ | '//' RelativeLocationPath
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AbsoluteLocationPath(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH))
+ return 0;
+
+ xpath->context= xpath->rootelement;
+
+ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH))
+ {
+ xpath->context= new Item_nodeset_func_descendantbyname(xpath->context,
+ "*", 1,
+ xpath->pxml, 1);
+ return my_xpath_parse_RelativeLocationPath(xpath);
+ }
+
+ my_xpath_parse_RelativeLocationPath(xpath);
+
+ return (xpath->error == 0);
+}
+
+
+/*
+ Scan Relative Location Path
+
+ SYNOPSYS
+
+ For better performance we combine these two rules
+
+ [3] RelativeLocationPath ::= Step
+ | RelativeLocationPath '/' Step
+ | AbbreviatedRelativeLocationPath
+ [11] AbbreviatedRelativeLocationPath ::= RelativeLocationPath '//' Step
+
+
+ Into this one:
+
+ [3-11] RelativeLocationPath ::= Step
+ | RelativeLocationPath '/' Step
+ | RelativeLocationPath '//' Step
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_RelativeLocationPath(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_Step(xpath))
+ return 0;
+ while (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH))
+ {
+ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH))
+ xpath->context= new Item_nodeset_func_descendantbyname(xpath->context,
+ "*", 1,
+ xpath->pxml, 1);
+ if (!my_xpath_parse_Step(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/*
+ Scan non-abbreviated or abbreviated Step
+
+ SYNOPSYS
+
+ [4] Step ::= AxisSpecifier NodeTest Predicate*
+ | AbbreviatedStep
+ [8] Predicate ::= '[' PredicateExpr ']'
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int
+my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_AxisSpecifier(xpath))
+ return 0;
+
+ if (!my_xpath_parse_NodeTest(xpath))
+ return 0;
+
+ while (my_xpath_parse_term(xpath, MY_XPATH_LEX_LB))
+ {
+ Item *prev_context= xpath->context;
+ String *context_cache;
+ context_cache= &((Item_nodeset_func*)xpath->context)->context_cache;
+ xpath->context= new Item_nodeset_context_cache(context_cache, xpath->pxml);
+ xpath->context_cache= context_cache;
+
+ if(!my_xpath_parse_PredicateExpr(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_RB))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+
+ xpath->item= nodeset2bool(xpath, xpath->item);
+
+ if (xpath->item->is_bool_func())
+ {
+ xpath->context= new Item_nodeset_func_predicate(prev_context,
+ xpath->item,
+ xpath->pxml);
+ }
+ else
+ {
+ xpath->context= new Item_nodeset_func_elementbyindex(prev_context,
+ xpath->item,
+ xpath->pxml);
+ }
+ }
+ return 1;
+}
+
+
+static int my_xpath_parse_Step(MY_XPATH *xpath)
+{
+ return
+ my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list(xpath) ||
+ my_xpath_parse_AbbreviatedStep(xpath);
+}
+
+
+/*
+ Scan Abbreviated Axis Specifier
+
+ SYNOPSYS
+ [5] AxisSpecifier ::= AxisName '::'
+ | AbbreviatedAxisSpecifier
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AbbreviatedAxisSpecifier(MY_XPATH *xpath)
+{
+ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_AT))
+ xpath->axis= MY_XPATH_AXIS_ATTRIBUTE;
+ else
+ xpath->axis= MY_XPATH_AXIS_CHILD;
+ return 1;
+}
+
+
+/*
+ Scan non-abbreviated axis specifier
+
+ SYNOPSYS
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AxisName_colon_colon(MY_XPATH *xpath)
+{
+ return my_xpath_parse_AxisName(xpath) &&
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_COLON) &&
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_COLON);
+}
+
+
+/*
+ Scan Abbreviated AxisSpecifier
+
+ SYNOPSYS
+ [13] AbbreviatedAxisSpecifier ::= '@'?
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AxisSpecifier(MY_XPATH *xpath)
+{
+ return my_xpath_parse_AxisName_colon_colon(xpath) ||
+ my_xpath_parse_AbbreviatedAxisSpecifier(xpath);
+}
+
+
+/*
+ Scan NodeType followed by parens
+
+ SYNOPSYS
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_NodeTest_lp_rp(MY_XPATH *xpath)
+{
+ return my_xpath_parse_term(xpath, MY_XPATH_LEX_NODETYPE) &&
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_LP) &&
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_RP);
+}
+
+
+/*
+ Scan NodeTest
+
+ SYNOPSYS
+
+ [7] NodeTest ::= NameTest
+ | NodeType '(' ')'
+ | 'processing-instruction' '(' Literal ')'
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_NodeTest(MY_XPATH *xpath)
+{
+ return my_xpath_parse_NameTest(xpath) ||
+ my_xpath_parse_NodeTest_lp_rp(xpath);
+}
+
+
+/*
+ Scan Abbreviated Step
+
+ SYNOPSYS
+
+ [12] AbbreviatedStep ::= '.' | '..'
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AbbreviatedStep(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DOT))
+ return 0;
+ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_DOT))
+ xpath->context= new Item_nodeset_func_parentbyname(xpath->context, "*", 1,
+ xpath->pxml);
+ return 1;
+}
+
+
+/*
+ Scan Primary Expression
+
+ SYNOPSYS
+
+ [15] PrimaryExpr ::= VariableReference
+ | '(' Expr ')'
+ | Literal
+ | Number
+ | FunctionCall
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_lp_Expr_rp(MY_XPATH *xpath)
+{
+ return my_xpath_parse_term(xpath, MY_XPATH_LEX_LP) &&
+ my_xpath_parse_Expr(xpath) &&
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_RP);
+}
+static int my_xpath_parse_PrimaryExpr_literal(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_STRING))
+ return 0;
+ xpath->item= new Item_string(xpath->prevtok.beg + 1,
+ xpath->prevtok.end - xpath->prevtok.beg - 2,
+ xpath->cs);
+ return 1;
+}
+static int my_xpath_parse_PrimaryExpr(MY_XPATH *xpath)
+{
+ return
+ my_xpath_parse_lp_Expr_rp(xpath) ||
+ my_xpath_parse_VariableReference(xpath) ||
+ my_xpath_parse_PrimaryExpr_literal(xpath) ||
+ my_xpath_parse_Number(xpath) ||
+ my_xpath_parse_FunctionCall(xpath);
+}
+
+
+/*
+ Scan Function Call
+
+ SYNOPSYS
+ [16] FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument )* )? ')'
+ [17] Argument ::= Expr
+
+ RETURN
+ 1 - success
+ 0 - failure
+
+*/
+static int my_xpath_parse_FunctionCall(MY_XPATH *xpath)
+{
+ Item *args[256];
+ uint nargs;
+
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_FUNC))
+ return 0;
+
+ MY_XPATH_FUNC *func= xpath->func;
+
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_LP))
+ return 0;
+
+ for (nargs= 0 ; nargs < func->maxargs; )
+ {
+ if (!my_xpath_parse_Expr(xpath))
+ {
+ if (nargs < func->minargs)
+ return 0;
+ goto right_paren;
+ }
+ args[nargs++]= xpath->item;
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_COMMA))
+ {
+ if (nargs < func->minargs)
+ return 0;
+ else
+ break;
+ }
+ }
+
+right_paren:
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_RP))
+ return 0;
+
+ return ((xpath->item= func->create(xpath, args, nargs))) ? 1 : 0;
+}
+
+
+/*
+ Scan Union Expression
+
+ SYNOPSYS
+ [18] UnionExpr ::= PathExpr
+ | UnionExpr '|' PathExpr
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_UnionExpr(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_PathExpr(xpath))
+ return 0;
+
+ while (my_xpath_parse_term(xpath, MY_XPATH_LEX_VLINE))
+ {
+ Item *prev= xpath->item;
+ if (prev->type() != Item::XPATH_NODESET)
+ return 0;
+
+ if (!my_xpath_parse_PathExpr(xpath)
+ || xpath->item->type() != Item::XPATH_NODESET)
+ {
+ xpath->error= 1;
+ return 0;
+ }
+ xpath->item= new Item_nodeset_func_union(prev, xpath->item, xpath->pxml);
+ }
+ return 1;
+}
+
+
+/*
+ Scan Path Expression
+
+ SYNOPSYS
+
+ [19] PathExpr ::= LocationPath
+ | FilterExpr
+ | FilterExpr '/' RelativeLocationPath
+ | FilterExpr '//' RelativeLocationPath
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int
+my_xpath_parse_FilterExpr_opt_slashes_RelativeLocationPath(MY_XPATH *xpath)
+{
+ Item *context= xpath->context;
+ int rc;
+
+ if (!my_xpath_parse_FilterExpr(xpath))
+ return 0;
+
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH))
+ return 1;
+
+ if (xpath->item->type() != Item::XPATH_NODESET)
+ {
+ xpath->lasttok= xpath->prevtok;
+ xpath->error= 1;
+ return 0;
+ }
+
+ /*
+ The context for the next relative path is the nodeset
+ returned by FilterExpr
+ */
+ xpath->context= xpath->item;
+
+ /* treat double slash (//) as /descendant-or-self::node()/ */
+ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH))
+ xpath->context= new Item_nodeset_func_descendantbyname(xpath->context,
+ "*", 1, xpath->pxml, 1);
+ rc= my_xpath_parse_RelativeLocationPath(xpath);
+
+ /* push back the context and restore the item */
+ xpath->item= xpath->context;
+ xpath->context= context;
+ return rc;
+}
+static int my_xpath_parse_PathExpr(MY_XPATH *xpath)
+{
+ return my_xpath_parse_LocationPath(xpath) ||
+ my_xpath_parse_FilterExpr_opt_slashes_RelativeLocationPath(xpath);
+}
+
+
+
+/*
+ Scan Filter Expression
+
+ SYNOPSYS
+ [20] FilterExpr ::= PrimaryExpr
+ | FilterExpr Predicate
+
+ or in other words:
+
+ [20] FilterExpr ::= PrimaryExpr Predicate*
+
+ RETURN
+ 1 - success
+ 0 - failure
+
+*/
+static int my_xpath_parse_FilterExpr(MY_XPATH *xpath)
+{
+ return my_xpath_parse_PrimaryExpr(xpath);
+}
+
+
+/*
+ Scan Or Expression
+
+ SYNOPSYS
+ [21] OrExpr ::= AndExpr
+ | OrExpr 'or' AndExpr
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_OrExpr(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_AndExpr(xpath))
+ return 0;
+
+ while (my_xpath_parse_term(xpath, MY_XPATH_LEX_OR))
+ {
+ Item *prev= xpath->item;
+ if (!my_xpath_parse_AndExpr(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+ xpath->item= new Item_cond_or(nodeset2bool(xpath, prev),
+ nodeset2bool(xpath, xpath->item));
+ }
+ return 1;
+}
+
+
+/*
+ Scan And Expression
+
+ SYNOPSYS
+ [22] AndExpr ::= EqualityExpr
+ | AndExpr 'and' EqualityExpr
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AndExpr(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_EqualityExpr(xpath))
+ return 0;
+
+ while (my_xpath_parse_term(xpath, MY_XPATH_LEX_AND))
+ {
+ Item *prev= xpath->item;
+ if (!my_xpath_parse_EqualityExpr(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+
+ xpath->item= new Item_cond_and(nodeset2bool(xpath,prev),
+ nodeset2bool(xpath,xpath->item));
+ }
+ return 1;
+}
+
+
+/*
+ Scan Equality Expression
+
+ SYNOPSYS
+ [23] EqualityExpr ::= RelationalExpr
+ | EqualityExpr '=' RelationalExpr
+ | EqualityExpr '!=' RelationalExpr
+ or in other words:
+
+ [23] EqualityExpr ::= RelationalExpr ( EqualityOperator EqualityExpr )*
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_ne(MY_XPATH *xpath)
+{
+ MY_XPATH_LEX prevtok= xpath->prevtok;
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_EXCL))
+ return 0;
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_EQ))
+ {
+ /* Unget the exclamation mark */
+ xpath->lasttok= xpath->prevtok;
+ xpath->prevtok= prevtok;
+ return 0;
+ }
+ return 1;
+}
+static int my_xpath_parse_EqualityOperator(MY_XPATH *xpath)
+{
+ if (my_xpath_parse_ne(xpath))
+ {
+ xpath->extra= '!';
+ return 1;
+ }
+ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_EQ))
+ {
+ xpath->extra= '=';
+ return 1;
+ }
+ return 0;
+}
+static int my_xpath_parse_EqualityExpr(MY_XPATH *xpath)
+{
+ MY_XPATH_LEX operator_context;
+ if (!my_xpath_parse_RelationalExpr(xpath))
+ return 0;
+
+ operator_context= xpath->lasttok;
+ while (my_xpath_parse_EqualityOperator(xpath))
+ {
+ Item *prev= xpath->item;
+ int oper= xpath->extra;
+ if (!my_xpath_parse_RelationalExpr(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+
+ if (!(xpath->item= create_comparator(xpath, oper, &operator_context,
+ prev, xpath->item)))
+ return 0;
+
+ operator_context= xpath->lasttok;
+ }
+ return 1;
+}
+
+
+/*
+ Scan Relational Expression
+
+ SYNOPSYS
+
+ [24] RelationalExpr ::= AdditiveExpr
+ | RelationalExpr '<' AdditiveExpr
+ | RelationalExpr '>' AdditiveExpr
+ | RelationalExpr '<=' AdditiveExpr
+ | RelationalExpr '>=' AdditiveExpr
+ or in other words:
+
+ [24] RelationalExpr ::= AdditiveExpr (RelationalOperator RelationalExpr)*
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_RelationalOperator(MY_XPATH *xpath)
+{
+ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_LESS))
+ {
+ xpath->extra= my_xpath_parse_term(xpath, MY_XPATH_LEX_EQ) ?
+ MY_XPATH_LEX_LE : MY_XPATH_LEX_LESS;
+ return 1;
+ }
+ else if (my_xpath_parse_term(xpath, MY_XPATH_LEX_GREATER))
+ {
+ xpath->extra= my_xpath_parse_term(xpath, MY_XPATH_LEX_EQ) ?
+ MY_XPATH_LEX_GE : MY_XPATH_LEX_GREATER;
+ return 1;
+ }
+ return 0;
+}
+static int my_xpath_parse_RelationalExpr(MY_XPATH *xpath)
+{
+ MY_XPATH_LEX operator_context;
+ if (!my_xpath_parse_AdditiveExpr(xpath))
+ return 0;
+ operator_context= xpath->lasttok;
+ while (my_xpath_parse_RelationalOperator(xpath))
+ {
+ Item *prev= xpath->item;
+ int oper= xpath->extra;
+
+ if (!my_xpath_parse_AdditiveExpr(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+
+ if (!(xpath->item= create_comparator(xpath, oper, &operator_context,
+ prev, xpath->item)))
+ return 0;
+ operator_context= xpath->lasttok;
+ }
+ return 1;
+}
+
+
+/*
+ Scan Additive Expression
+
+ SYNOPSYS
+
+ [25] AdditiveExpr ::= MultiplicativeExpr
+ | AdditiveExpr '+' MultiplicativeExpr
+ | AdditiveExpr '-' MultiplicativeExpr
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_AdditiveOperator(MY_XPATH *xpath)
+{
+ return my_xpath_parse_term(xpath, MY_XPATH_LEX_PLUS) ||
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_MINUS);
+}
+static int my_xpath_parse_AdditiveExpr(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_MultiplicativeExpr(xpath))
+ return 0;
+
+ while (my_xpath_parse_AdditiveOperator(xpath))
+ {
+ int oper= xpath->prevtok.term;
+ Item *prev= xpath->item;
+ if (!my_xpath_parse_MultiplicativeExpr(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+
+ if (oper == MY_XPATH_LEX_PLUS)
+ xpath->item= new Item_func_plus(prev, xpath->item);
+ else
+ xpath->item= new Item_func_minus(prev, xpath->item);
+ };
+ return 1;
+}
+
+
+/*
+ Scan Multiplicative Expression
+
+ SYNOPSYS
+
+ [26] MultiplicativeExpr ::= UnaryExpr
+ | MultiplicativeExpr MultiplyOperator UnaryExpr
+ | MultiplicativeExpr 'div' UnaryExpr
+ | MultiplicativeExpr 'mod' UnaryExpr
+ or in other words:
+
+ [26] MultiplicativeExpr ::= UnaryExpr (MulOper MultiplicativeExpr)*
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_MultiplicativeOperator(MY_XPATH *xpath)
+{
+ return
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_ASTERISK) ||
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_DIV) ||
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_MOD);
+}
+static int my_xpath_parse_MultiplicativeExpr(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_UnaryExpr(xpath))
+ return 0;
+
+ while (my_xpath_parse_MultiplicativeOperator(xpath))
+ {
+ int oper= xpath->prevtok.term;
+ Item *prev= xpath->item;
+ if (!my_xpath_parse_UnaryExpr(xpath))
+ {
+ xpath->error= 1;
+ return 0;
+ }
+ switch (oper)
+ {
+ case MY_XPATH_LEX_ASTERISK:
+ xpath->item= new Item_func_mul(prev, xpath->item);
+ break;
+ case MY_XPATH_LEX_DIV:
+ xpath->item= new Item_func_int_div(prev, xpath->item);
+ break;
+ case MY_XPATH_LEX_MOD:
+ xpath->item= new Item_func_mod(prev, xpath->item);
+ break;
+ }
+ }
+ return 1;
+}
+
+
+/*
+ Scan Unary Expression
+
+ SYNOPSYS
+
+ [27] UnaryExpr ::= UnionExpr
+ | '-' UnaryExpr
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_UnaryExpr(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_MINUS))
+ return my_xpath_parse_UnionExpr(xpath);
+ if (!my_xpath_parse_UnaryExpr(xpath))
+ return 0;
+ xpath->item= new Item_func_neg(xpath->item);
+ return 1;
+}
+
+
+/*
+ Scan Number
+
+ SYNOPSYS
+
+ [30] Number ::= Digits ('.' Digits?)? | '.' Digits)
+
+ or in other words:
+
+ [30] Number ::= Digits
+ | Digits '.'
+ | Digits '.' Digits
+ | '.' Digits
+
+ Note: the last rule is not supported yet,
+ as it is in conflict with abbreviated step.
+ 1 + .123 does not work,
+ 1 + 0.123 does.
+ Perhaps it is better to move this code into lex analizer.
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int my_xpath_parse_Number(MY_XPATH *xpath)
+{
+ const char *beg;
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DIGITS))
+ return 0;
+ beg= xpath->prevtok.beg;
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DOT))
+ {
+ xpath->item= new Item_int(xpath->prevtok.beg,
+ xpath->prevtok.end - xpath->prevtok.beg);
+ return 1;
+ }
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_DIGITS);
+
+ xpath->item= new Item_float(beg, xpath->prevtok.end - beg);
+ return 1;
+}
+
+
+/*
+ Scan NCName.
+
+ SYNOPSYS
+
+ The keywords AND, OR, MOD, DIV are valid identitiers
+ when they are in identifier context:
+
+ SELECT
+ ExtractValue('<and><or><mod><div>VALUE</div></mod></or></and>',
+ '/and/or/mod/div')
+ -> VALUE
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+
+static int
+my_xpath_parse_NCName(MY_XPATH *xpath)
+{
+ return
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_IDENT) ||
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_AND) ||
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_OR) ||
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_MOD) ||
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_DIV) ? 1 : 0;
+}
+
+
+/*
+ QName grammar can be found in a separate document
+ http://www.w3.org/TR/REC-xml-names/#NT-QName
+
+ [6] QName ::= (Prefix ':')? LocalPart
+ [7] Prefix ::= NCName
+ [8] LocalPart ::= NCName
+*/
+
+static int
+my_xpath_parse_QName(MY_XPATH *xpath)
+{
+ const char *beg;
+ if (!my_xpath_parse_NCName(xpath))
+ return 0;
+ beg= xpath->prevtok.beg;
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_COLON))
+ return 1; /* Non qualified name */
+ if (!my_xpath_parse_NCName(xpath))
+ return 0;
+ xpath->prevtok.beg= beg;
+ return 1;
+}
+
+
+/**
+ Scan Variable reference
+
+ @details Implements parsing of two syntax structures:
+
+ 1. Standard XPath syntax [36], for SP variables:
+
+ VariableReference ::= '$' QName
+
+ Finds a SP variable with the given name.
+ If outside of a SP context, or variable with
+ the given name doesn't exists, then error is returned.
+
+ 2. Non-standard syntax - MySQL extension for user variables:
+
+ VariableReference ::= '$' '@' QName
+
+ Item, corresponding to the variable, is returned
+ in xpath->item in both cases.
+
+ @param xpath pointer to XPath structure
+
+ @return Operation status
+ @retval 1 Success
+ @retval 0 Failure
+*/
+
+static int
+my_xpath_parse_VariableReference(MY_XPATH *xpath)
+{
+ LEX_STRING name;
+ int user_var;
+ const char *dollar_pos;
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DOLLAR) ||
+ (!(dollar_pos= xpath->prevtok.beg)) ||
+ (!((user_var= my_xpath_parse_term(xpath, MY_XPATH_LEX_AT) &&
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_IDENT))) &&
+ !my_xpath_parse_term(xpath, MY_XPATH_LEX_IDENT)))
+ return 0;
+
+ name.length= xpath->prevtok.end - xpath->prevtok.beg;
+ name.str= (char*) xpath->prevtok.beg;
+
+ if (user_var)
+ xpath->item= new Item_func_get_user_var(name);
+ else
+ {
+ sp_variable *spv;
+ sp_pcontext *spc;
+ LEX *lex;
+ if ((lex= current_thd->lex) &&
+ (spc= lex->spcont) &&
+ (spv= spc->find_variable(name, false)))
+ {
+ Item_splocal *splocal= new Item_splocal(name, spv->offset, spv->type, 0);
+#ifndef DBUG_OFF
+ if (splocal)
+ splocal->m_sp= lex->sphead;
+#endif
+ xpath->item= (Item*) splocal;
+ }
+ else
+ {
+ xpath->item= NULL;
+ DBUG_ASSERT(xpath->query.end > dollar_pos);
+ uint len= xpath->query.end - dollar_pos;
+ set_if_smaller(len, 32);
+ my_printf_error(ER_UNKNOWN_ERROR, "Unknown XPATH variable at: '%.*s'",
+ MYF(0), len, dollar_pos);
+ }
+ }
+ return xpath->item ? 1 : 0;
+}
+
+
+/*
+ Scan Name Test
+
+ SYNOPSYS
+
+ [37] NameTest ::= '*'
+ | NCName ':' '*'
+ | QName
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int
+my_xpath_parse_NodeTest_QName(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_QName(xpath))
+ return 0;
+ DBUG_ASSERT(xpath->context);
+ uint len= xpath->prevtok.end - xpath->prevtok.beg;
+ xpath->context= nametestfunc(xpath, xpath->axis, xpath->context,
+ xpath->prevtok.beg, len);
+ return 1;
+}
+static int
+my_xpath_parse_NodeTest_asterisk(MY_XPATH *xpath)
+{
+ if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_ASTERISK))
+ return 0;
+ DBUG_ASSERT(xpath->context);
+ xpath->context= nametestfunc(xpath, xpath->axis, xpath->context, "*", 1);
+ return 1;
+}
+static int
+my_xpath_parse_NameTest(MY_XPATH *xpath)
+{
+ return my_xpath_parse_NodeTest_asterisk(xpath) ||
+ my_xpath_parse_NodeTest_QName(xpath);
+}
+
+
+/*
+ Scan an XPath expression
+
+ SYNOPSYS
+ Scan xpath expression.
+ The expression is returned in xpath->expr.
+
+ RETURN
+ 1 - success
+ 0 - failure
+*/
+static int
+my_xpath_parse(MY_XPATH *xpath, const char *str, const char *strend)
+{
+ my_xpath_lex_init(&xpath->query, str, strend);
+ my_xpath_lex_init(&xpath->prevtok, str, strend);
+ my_xpath_lex_scan(xpath, &xpath->lasttok, str, strend);
+
+ xpath->rootelement= new Item_nodeset_func_rootelement(xpath->pxml);
+
+ return
+ my_xpath_parse_Expr(xpath) &&
+ my_xpath_parse_term(xpath, MY_XPATH_LEX_EOF);
+}
+
+
+void Item_xml_str_func::fix_length_and_dec()
+{
+ max_length= MAX_BLOB_WIDTH;
+ agg_arg_charsets_for_comparison(collation, args, arg_count);
+}
+
+
+bool Item_xml_str_func::fix_fields(THD *thd, Item **ref)
+{
+ String *xp, tmp;
+ MY_XPATH xpath;
+ int rc;
+
+ if (Item_str_func::fix_fields(thd, ref))
+ return true;
+
+ status_var_increment(current_thd->status_var.feature_xml);
+
+ nodeset_func= 0;
+
+
+ if (collation.collation->mbminlen > 1)
+ {
+ /* UCS2 is not supported */
+ my_printf_error(ER_UNKNOWN_ERROR,
+ "Character set '%s' is not supported by XPATH",
+ MYF(0), collation.collation->csname);
+ return true;
+ }
+
+ if (!args[1]->const_item())
+ {
+ my_printf_error(ER_UNKNOWN_ERROR,
+ "Only constant XPATH queries are supported", MYF(0));
+ return true;
+ }
+
+ if (!(xp= args[1]->val_str(&tmp)))
+ return false; // Will return NULL
+ my_xpath_init(&xpath);
+ xpath.cs= collation.collation;
+ xpath.debug= 0;
+ xpath.pxml= xml.parsed();
+ xml.set_charset(collation.collation);
+
+ rc= my_xpath_parse(&xpath, xp->ptr(), xp->ptr() + xp->length());
+
+ if (!rc)
+ {
+ uint clen= xpath.query.end - xpath.lasttok.beg;
+ set_if_smaller(clen, 32);
+ my_printf_error(ER_UNKNOWN_ERROR, "XPATH syntax error: '%.*s'",
+ MYF(0), clen, xpath.lasttok.beg);
+ return true;
+ }
+
+ /*
+ Parsing XML is a heavy operation, so if the first argument is constant,
+ then parse XML only one time and cache the parsed representation
+ together with raw text representation.
+
+ Note, we cannot cache the entire function result even if
+ the first and the second arguments are constants, because
+ the XPath expression may have user and SP variable references,
+ so the function result can vary between executions.
+ */
+ if ((args[0]->const_item() && get_xml(&xml, true)) ||
+ !(nodeset_func= xpath.item))
+ return false; // Will return NULL
+
+ return nodeset_func->fix_fields(thd, &nodeset_func);
+}
+
+
+#define MAX_LEVEL 256
+typedef struct
+{
+ uint level;
+ String *pxml; // parsed XML
+ uint pos[MAX_LEVEL]; // Tag position stack
+ uint parent; // Offset of the parent of the current node
+} MY_XML_USER_DATA;
+
+
+static bool
+append_node(String *str, MY_XML_NODE *node)
+{
+ /*
+ If "str" doesn't have space for a new node,
+ it will allocate two times more space that it has had so far.
+ (2*len+512) is a heuristic value,
+ which gave the best performance during tests.
+ The ideas behind this formula are:
+ - It allows to have a very small number of reallocs:
+ about 10 reallocs on a 1Mb-long XML value.
+ - At the same time, it avoids excessive memory use.
+ */
+ if (str->reserve(sizeof(MY_XML_NODE), 2 * str->length() + 512))
+ return TRUE;
+ str->q_append((const char*) node, sizeof(MY_XML_NODE));
+ return FALSE;
+}
+
+
+/*
+ Process tag beginning
+
+ SYNOPSYS
+
+ A call-back function executed when XML parser
+ is entering a tag or an attribue.
+ Appends the new node into data->pxml.
+ Increments data->level.
+
+ RETURN
+ Currently only MY_XML_OK
+*/
+extern "C" int xml_enter(MY_XML_PARSER *st,const char *attr, size_t len);
+
+int xml_enter(MY_XML_PARSER *st,const char *attr, size_t len)
+{
+ MY_XML_USER_DATA *data= (MY_XML_USER_DATA*)st->user_data;
+ uint numnodes= data->pxml->length() / sizeof(MY_XML_NODE);
+ MY_XML_NODE node;
+
+ node.parent= data->parent; // Set parent for the new node to old parent
+ data->parent= numnodes; // Remember current node as new parent
+ DBUG_ASSERT(data->level <= MAX_LEVEL);
+ data->pos[data->level]= numnodes;
+ if (data->level < MAX_LEVEL)
+ node.level= data->level++;
+ else
+ return MY_XML_ERROR;
+ node.type= st->current_node_type; // TAG or ATTR
+ node.beg= attr;
+ node.end= attr + len;
+ return append_node(data->pxml, &node) ? MY_XML_ERROR : MY_XML_OK;
+}
+
+
+/*
+ Process text node
+
+ SYNOPSYS
+
+ A call-back function executed when XML parser
+ is entering into a tag or an attribue textual value.
+ The value is appended into data->pxml.
+
+ RETURN
+ Currently only MY_XML_OK
+*/
+extern "C" int xml_value(MY_XML_PARSER *st,const char *attr, size_t len);
+
+int xml_value(MY_XML_PARSER *st,const char *attr, size_t len)
+{
+ MY_XML_USER_DATA *data= (MY_XML_USER_DATA*)st->user_data;
+ MY_XML_NODE node;
+
+ node.parent= data->parent; // Set parent for the new text node to old parent
+ node.level= data->level;
+ node.type= MY_XML_NODE_TEXT;
+ node.beg= attr;
+ node.end= attr + len;
+ return append_node(data->pxml, &node) ? MY_XML_ERROR : MY_XML_OK;
+}
+
+
+/*
+ Leave a tag or an attribute
+
+ SYNOPSYS
+
+ A call-back function executed when XML parser
+ is leaving a tag or an attribue.
+ Decrements data->level.
+
+ RETURN
+ Currently only MY_XML_OK
+*/
+extern "C" int xml_leave(MY_XML_PARSER *st,const char *attr, size_t len);
+
+int xml_leave(MY_XML_PARSER *st,const char *attr, size_t len)
+{
+ MY_XML_USER_DATA *data= (MY_XML_USER_DATA*)st->user_data;
+ DBUG_ASSERT(data->level > 0);
+ data->level--;
+
+ MY_XML_NODE *nodes= (MY_XML_NODE*) data->pxml->ptr();
+ data->parent= nodes[data->parent].parent;
+ nodes+= data->pos[data->level];
+ nodes->tagend= st->cur;
+
+ return MY_XML_OK;
+}
+
+
+/*
+ Parse raw XML
+
+ SYNOPSYS
+
+ RETURN
+ false on success
+ true on error
+*/
+bool Item_xml_str_func::XML::parse()
+{
+ MY_XML_PARSER p;
+ MY_XML_USER_DATA user_data;
+ int rc;
+
+ m_parsed_buf.length(0);
+
+ /* Prepare XML parser */
+ my_xml_parser_create(&p);
+ p.flags= MY_XML_FLAG_RELATIVE_NAMES | MY_XML_FLAG_SKIP_TEXT_NORMALIZATION;
+ user_data.level= 0;
+ user_data.pxml= &m_parsed_buf;
+ user_data.parent= 0;
+ my_xml_set_enter_handler(&p, xml_enter);
+ my_xml_set_value_handler(&p, xml_value);
+ my_xml_set_leave_handler(&p, xml_leave);
+ my_xml_set_user_data(&p, (void*) &user_data);
+
+ /* Add root node */
+ p.current_node_type= MY_XML_NODE_TAG;
+ xml_enter(&p, m_raw_ptr->ptr(), 0);
+
+ /* Execute XML parser */
+ if ((rc= my_xml_parse(&p, m_raw_ptr->ptr(), m_raw_ptr->length())) != MY_XML_OK)
+ {
+ char buf[128];
+ my_snprintf(buf, sizeof(buf)-1, "parse error at line %d pos %lu: %s",
+ my_xml_error_lineno(&p) + 1,
+ (ulong) my_xml_error_pos(&p) + 1,
+ my_xml_error_string(&p));
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_VALUE,
+ ER(ER_WRONG_VALUE), "XML", buf);
+ m_raw_ptr= (String *) 0;
+ }
+ my_xml_parser_free(&p);
+
+ return rc != MY_XML_OK;
+}
+
+
+/*
+ Parse the raw XML from the given source,
+ optionally cache the raw XML,
+ remember the pointer to the raw XML.
+*/
+bool Item_xml_str_func::XML::parse(String *raw_xml, bool cache)
+{
+ m_raw_ptr= raw_xml;
+ if (cache)
+ {
+ m_cached= true;
+ if (m_raw_ptr != &m_raw_buf && m_raw_buf.copy(*m_raw_ptr))
+ {
+ m_raw_ptr= (String *) 0;
+ return true;
+ }
+ m_raw_ptr= &m_raw_buf;
+ }
+ return parse();
+}
+
+
+const MY_XML_NODE *Item_xml_str_func::XML::node(uint idx)
+{
+ const MY_XML_NODE *nodebeg= (MY_XML_NODE*) m_parsed_buf.ptr();
+ DBUG_ASSERT(idx < m_parsed_buf.length() / sizeof (MY_XML_NODE));
+ return nodebeg + idx;
+}
+
+
+String *Item_func_xml_extractvalue::val_str(String *str)
+{
+ String *res;
+ null_value= 0;
+ if (!nodeset_func || get_xml(&xml) ||
+ !(res= nodeset_func->val_str(str)))
+ {
+ null_value= 1;
+ return 0;
+ }
+ return res;
+}
+
+
+bool Item_func_xml_update::collect_result(String *str,
+ const MY_XML_NODE *cut,
+ const String *replace)
+{
+ uint offs= cut->type == MY_XML_NODE_TAG ? 1 : 0;
+ const char *end= cut->tagend + offs;
+ str->length(0);
+ str->set_charset(collation.collation);
+ return
+ /* Put the XML part preceding the replaced piece */
+ str->append(xml.raw()->ptr(), cut->beg - xml.raw()->ptr() - offs) ||
+ /* Put the replacement */
+ str->append(replace->ptr(), replace->length()) ||
+ /* Put the XML part following the replaced piece */
+ str->append(end, xml.raw()->ptr() + xml.raw()->length() - end);
+}
+
+
+String *Item_func_xml_update::val_str(String *str)
+{
+ String *nodeset, *rep;
+
+ null_value= 0;
+ if (!nodeset_func || get_xml(&xml) ||
+ !(rep= args[2]->val_str(&tmp_value3)) ||
+ !(nodeset= nodeset_func->val_nodeset(&tmp_value2)))
+ {
+ null_value= 1;
+ return 0;
+ }
+
+ MY_XPATH_FLT *fltbeg= (MY_XPATH_FLT*) nodeset->ptr();
+ MY_XPATH_FLT *fltend= (MY_XPATH_FLT*) (nodeset->ptr() + nodeset->length());
+
+ /* Allow replacing of one tag only */
+ if (fltend - fltbeg != 1)
+ {
+ /* TODO: perhaps add a warning that more than one tag selected */
+ return xml.raw();
+ }
+
+ const MY_XML_NODE *nodebeg= xml.node(fltbeg->num);
+
+ if (!nodebeg->level)
+ {
+ /*
+ Root element, without NameTest:
+ UpdateXML(xml, '/', 'replacement');
+ Just return the replacement string.
+ */
+ return rep;
+ }
+
+ return collect_result(str, nodebeg, rep) ? (String *) NULL : str;
+}
diff --git a/sql/item_xmlfunc.h b/sql/item_xmlfunc.h
new file mode 100644
index 00000000000..637f505e12e
--- /dev/null
+++ b/sql/item_xmlfunc.h
@@ -0,0 +1,123 @@
+#ifndef ITEM_XMLFUNC_INCLUDED
+#define ITEM_XMLFUNC_INCLUDED
+
+/* Copyright (c) 2000-2007 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+
+/* This file defines all XML functions */
+
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+
+typedef struct my_xml_node_st MY_XML_NODE;
+
+
+class Item_xml_str_func: public Item_str_func
+{
+protected:
+ /*
+ A helper class to store raw and parsed XML.
+ */
+ class XML
+ {
+ bool m_cached;
+ String *m_raw_ptr; // Pointer to text representation
+ String m_raw_buf; // Cached text representation
+ String m_parsed_buf; // Array of MY_XML_NODEs, pointing to raw_buffer
+ bool parse();
+ void reset()
+ {
+ m_cached= false;
+ m_raw_ptr= (String *) 0;
+ }
+ public:
+ XML() { reset(); }
+ void set_charset(CHARSET_INFO *cs) { m_parsed_buf.set_charset(cs); }
+ String *raw() { return m_raw_ptr; }
+ String *parsed() { return &m_parsed_buf; }
+ const MY_XML_NODE *node(uint idx);
+ bool cached() { return m_cached; }
+ bool parse(String *raw, bool cache);
+ bool parse(Item *item, bool cache)
+ {
+ String *res;
+ if (!(res= item->val_str(&m_raw_buf)))
+ {
+ m_raw_ptr= (String *) 0;
+ m_cached= cache;
+ return true;
+ }
+ return parse(res, cache);
+ }
+ };
+ Item *nodeset_func;
+ XML xml;
+ bool get_xml(XML *xml, bool cache= false)
+ {
+ if (!cache && xml->cached())
+ return xml->raw() == 0;
+ return xml->parse(args[0], cache);
+ }
+public:
+ Item_xml_str_func(Item *a, Item *b):
+ Item_str_func(a,b)
+ {
+ maybe_null= TRUE;
+ }
+ Item_xml_str_func(Item *a, Item *b, Item *c):
+ Item_str_func(a,b,c)
+ {
+ maybe_null= TRUE;
+ }
+ bool fix_fields(THD *thd, Item **ref);
+ void fix_length_and_dec();
+ bool const_item() const
+ {
+ return const_item_cache && (!nodeset_func || nodeset_func->const_item());
+ }
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor(func_name());
+ }
+};
+
+
+class Item_func_xml_extractvalue: public Item_xml_str_func
+{
+public:
+ Item_func_xml_extractvalue(Item *a,Item *b) :Item_xml_str_func(a,b) {}
+ const char *func_name() const { return "extractvalue"; }
+ String *val_str(String *);
+};
+
+
+class Item_func_xml_update: public Item_xml_str_func
+{
+ String tmp_value2, tmp_value3;
+ bool collect_result(String *str,
+ const MY_XML_NODE *cut,
+ const String *replace);
+public:
+ Item_func_xml_update(Item *a,Item *b,Item *c) :Item_xml_str_func(a,b,c) {}
+ const char *func_name() const { return "updatexml"; }
+ String *val_str(String *);
+};
+
+#endif /* ITEM_XMLFUNC_INCLUDED */
diff --git a/sql/key.cc b/sql/key.cc
new file mode 100644
index 00000000000..e3787ea7869
--- /dev/null
+++ b/sql/key.cc
@@ -0,0 +1,919 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/* Functions to handle keys and fields in forms */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: by includes later
+#include "key.h" // key_rec_cmp
+#include "field.h" // Field
+
+using std::min;
+using std::max;
+
+/*
+ Search after a key that starts with 'field'
+
+ SYNOPSIS
+ find_ref_key()
+ key First key to check
+ key_count How many keys to check
+ record Start of record
+ field Field to search after
+ key_length On partial match, contains length of fields before
+ field
+ keypart key part # of a field
+
+ NOTES
+ Used when calculating key for NEXT_NUMBER
+
+ IMPLEMENTATION
+ If no key starts with field test if field is part of some key. If we find
+ one, then return first key and set key_length to the number of bytes
+ preceding 'field'.
+
+ RETURN
+ -1 field is not part of the key
+ # Key part for key matching key.
+ key_length is set to length of key before (not including) field
+*/
+
+int find_ref_key(KEY *key, uint key_count, uchar *record, Field *field,
+ uint *key_length, uint *keypart)
+{
+ reg2 int i;
+ reg3 KEY *key_info;
+ uint fieldpos;
+
+ fieldpos= field->offset(record);
+
+ /* Test if some key starts as fieldpos */
+ for (i= 0, key_info= key ;
+ i < (int) key_count ;
+ i++, key_info++)
+ {
+ if (key_info->key_part[0].offset == fieldpos)
+ { /* Found key. Calc keylength */
+ *key_length= *keypart= 0;
+ return i; /* Use this key */
+ }
+ }
+
+ /* Test if some key contains fieldpos */
+ for (i= 0, key_info= key;
+ i < (int) key_count ;
+ i++, key_info++)
+ {
+ uint j;
+ KEY_PART_INFO *key_part;
+ *key_length=0;
+ for (j=0, key_part=key_info->key_part ;
+ j < key_info->user_defined_key_parts ;
+ j++, key_part++)
+ {
+ if (key_part->offset == fieldpos)
+ {
+ *keypart= j;
+ return i; /* Use this key */
+ }
+ *key_length+= key_part->store_length;
+ }
+ }
+ return(-1); /* No key is ok */
+}
+
+
+/**
+ Copy part of a record that forms a key or key prefix to a buffer.
+
+ The function takes a complete table record (as e.g. retrieved by
+ handler::index_read()), and a description of an index on the same table,
+ and extracts the first key_length bytes of the record which are part of a
+ key into to_key. If length == 0 then copy all bytes from the record that
+ form a key.
+
+ @param to_key buffer that will be used as a key
+ @param from_record full record to be copied from
+ @param key_info descriptor of the index
+ @param key_length specifies length of all keyparts that will be copied
+ @param with_zerofill skipped bytes in the key buffer to be filled with 0
+*/
+
+void key_copy(uchar *to_key, uchar *from_record, KEY *key_info,
+ uint key_length, bool with_zerofill)
+{
+ uint length;
+ KEY_PART_INFO *key_part;
+
+ if (key_length == 0)
+ key_length= key_info->key_length;
+ for (key_part= key_info->key_part;
+ (int) key_length > 0;
+ key_part++, to_key+= length, key_length-= length)
+ {
+ if (key_part->null_bit)
+ {
+ *to_key++= MY_TEST(from_record[key_part->null_offset] &
+ key_part->null_bit);
+ key_length--;
+ if (to_key[-1])
+ {
+ /*
+ Don't copy data for null values
+ The -1 below is to subtract the null byte which is already handled
+ */
+ length= min<uint>(key_length, key_part->store_length-1);
+ if (with_zerofill)
+ bzero((char*) to_key, length);
+ continue;
+ }
+ }
+ if (key_part->key_part_flag & HA_BLOB_PART ||
+ key_part->key_part_flag & HA_VAR_LENGTH_PART)
+ {
+ key_length-= HA_KEY_BLOB_LENGTH;
+ length= min<uint>(key_length, key_part->length);
+ uint bytes= key_part->field->get_key_image(to_key, length, Field::itRAW);
+ if (with_zerofill && bytes < length)
+ bzero((char*) to_key + bytes, length - bytes);
+ to_key+= HA_KEY_BLOB_LENGTH;
+ }
+ else
+ {
+ length= min<uint>(key_length, key_part->length);
+ Field *field= key_part->field;
+ CHARSET_INFO *cs= field->charset();
+ uint bytes= field->get_key_image(to_key, length, Field::itRAW);
+ if (bytes < length)
+ cs->cset->fill(cs, (char*) to_key + bytes, length - bytes, ' ');
+ }
+ }
+}
+
+
+/**
+ Restore a key from some buffer to record.
+
+ This function converts a key into record format. It can be used in cases
+ when we want to return a key as a result row.
+
+ @param to_record record buffer where the key will be restored to
+ @param from_key buffer that contains a key
+ @param key_info descriptor of the index
+ @param key_length specifies length of all keyparts that will be restored
+*/
+
+void key_restore(uchar *to_record, uchar *from_key, KEY *key_info,
+ uint key_length)
+{
+ uint length;
+ KEY_PART_INFO *key_part;
+
+ if (key_length == 0)
+ {
+ key_length= key_info->key_length;
+ }
+ for (key_part= key_info->key_part ;
+ (int) key_length > 0 ;
+ key_part++, from_key+= length, key_length-= length)
+ {
+ uchar used_uneven_bits= 0;
+ if (key_part->null_bit)
+ {
+ bool null_value;
+ if ((null_value= *from_key++))
+ to_record[key_part->null_offset]|= key_part->null_bit;
+ else
+ to_record[key_part->null_offset]&= ~key_part->null_bit;
+ key_length--;
+ if (null_value)
+ {
+ /*
+ Don't copy data for null bytes
+ The -1 below is to subtract the null byte which is already handled
+ */
+ length= min<uint>(key_length, key_part->store_length-1);
+ continue;
+ }
+ }
+ if (key_part->type == HA_KEYTYPE_BIT)
+ {
+ Field_bit *field= (Field_bit *) (key_part->field);
+ if (field->bit_len)
+ {
+ uchar bits= *(from_key + key_part->length -
+ field->pack_length_in_rec() - 1);
+ set_rec_bits(bits, to_record + key_part->null_offset +
+ (key_part->null_bit == 128),
+ field->bit_ofs, field->bit_len);
+ /* we have now used the byte with 'uneven' bits */
+ used_uneven_bits= 1;
+ }
+ }
+ if (key_part->key_part_flag & HA_BLOB_PART)
+ {
+ /*
+ This in fact never happens, as we have only partial BLOB
+ keys yet anyway, so it's difficult to find any sence to
+ restore the part of a record.
+ Maybe this branch is to be removed, but now we
+ have to ignore GCov compaining.
+ */
+ uint blob_length= uint2korr(from_key);
+ Field_blob *field= (Field_blob*) key_part->field;
+ from_key+= HA_KEY_BLOB_LENGTH;
+ key_length-= HA_KEY_BLOB_LENGTH;
+ field->set_ptr_offset(to_record - field->table->record[0],
+ (ulong) blob_length, from_key);
+ length= key_part->length;
+ }
+ else if (key_part->key_part_flag & HA_VAR_LENGTH_PART)
+ {
+ Field *field= key_part->field;
+ my_bitmap_map *old_map;
+ my_ptrdiff_t ptrdiff= to_record - field->table->record[0];
+ field->move_field_offset(ptrdiff);
+ key_length-= HA_KEY_BLOB_LENGTH;
+ length= min<uint>(key_length, key_part->length);
+ old_map= dbug_tmp_use_all_columns(field->table, field->table->write_set);
+ field->set_key_image(from_key, length);
+ dbug_tmp_restore_column_map(field->table->write_set, old_map);
+ from_key+= HA_KEY_BLOB_LENGTH;
+ field->move_field_offset(-ptrdiff);
+ }
+ else
+ {
+ length= min<uint>(key_length, key_part->length);
+ /* skip the byte with 'uneven' bits, if used */
+ memcpy(to_record + key_part->offset, from_key + used_uneven_bits
+ , (size_t) length - used_uneven_bits);
+ }
+ }
+}
+
+
+/**
+ Compare if a key has changed.
+
+ @param table TABLE
+ @param key key to compare to row
+ @param idx Index used
+ @param key_length Length of key
+
+ @note
+ In theory we could just call field->cmp() for all field types,
+ but as we are only interested if a key has changed (not if the key is
+ larger or smaller than the previous value) we can do things a bit
+ faster by using memcmp() instead.
+
+ @retval
+ 0 If key is equal
+ @retval
+ 1 Key has changed
+*/
+
+bool key_cmp_if_same(TABLE *table,const uchar *key,uint idx,uint key_length)
+{
+ uint store_length;
+ KEY_PART_INFO *key_part;
+ const uchar *key_end= key + key_length;;
+
+ for (key_part=table->key_info[idx].key_part;
+ key < key_end ;
+ key_part++, key+= store_length)
+ {
+ uint length;
+ store_length= key_part->store_length;
+
+ if (key_part->null_bit)
+ {
+ if (*key != MY_TEST(table->record[0][key_part->null_offset] &
+ key_part->null_bit))
+ return 1;
+ if (*key)
+ continue;
+ key++;
+ store_length--;
+ }
+ if (!(key_part->key_part_flag & HA_CAN_MEMCMP))
+ {
+ if (key_part->field->key_cmp(key, key_part->length))
+ return 1;
+ continue;
+ }
+ length= min((uint) (key_end-key), store_length);
+ if (!(key_part->key_type & (FIELDFLAG_NUMBER+FIELDFLAG_BINARY+
+ FIELDFLAG_PACK)))
+ {
+ CHARSET_INFO *cs= key_part->field->charset();
+ uint char_length= key_part->length / cs->mbmaxlen;
+ const uchar *pos= table->record[0] + key_part->offset;
+ if (length > char_length)
+ {
+ char_length= my_charpos(cs, pos, pos + length, char_length);
+ set_if_smaller(char_length, length);
+ }
+ if (cs->coll->strnncollsp(cs,
+ (const uchar*) key, length,
+ (const uchar*) pos, char_length, 0))
+ return 1;
+ continue;
+ }
+ if (memcmp(key,table->record[0]+key_part->offset,length))
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Unpack a field and append it.
+
+ @param[inout] to String to append the field contents to.
+ @param field Field to unpack.
+ @param rec Record which contains the field data.
+ @param max_length Maximum length of field to unpack
+ or 0 for unlimited.
+ @param prefix_key The field is used as a prefix key.
+*/
+
+void field_unpack(String *to, Field *field, const uchar *rec, uint max_length,
+ bool prefix_key)
+{
+ String tmp;
+ DBUG_ENTER("field_unpack");
+ if (!max_length)
+ max_length= field->pack_length();
+ if (field)
+ {
+ if (field->is_null())
+ {
+ to->append(STRING_WITH_LEN("NULL"));
+ DBUG_VOID_RETURN;
+ }
+ CHARSET_INFO *cs= field->charset();
+ field->val_str(&tmp);
+ /*
+ For BINARY(N) strip trailing zeroes to make
+ the error message nice-looking
+ */
+ if (field->binary() && field->type() == MYSQL_TYPE_STRING && tmp.length())
+ {
+ const char *tmp_end= tmp.ptr() + tmp.length();
+ while (tmp_end > tmp.ptr() && !*--tmp_end) ;
+ tmp.length(tmp_end - tmp.ptr() + 1);
+ }
+ if (cs->mbmaxlen > 1 && prefix_key)
+ {
+ /*
+ Prefix key, multi-byte charset.
+ For the columns of type CHAR(N), the above val_str()
+ call will return exactly "key_part->length" bytes,
+ which can break a multi-byte characters in the middle.
+ Align, returning not more than "char_length" characters.
+ */
+ uint charpos, char_length= max_length / cs->mbmaxlen;
+ if ((charpos= my_charpos(cs, tmp.ptr(),
+ tmp.ptr() + tmp.length(),
+ char_length)) < tmp.length())
+ tmp.length(charpos);
+ }
+ if (max_length < field->pack_length())
+ tmp.length(min(tmp.length(),max_length));
+ ErrConvString err(&tmp);
+ to->append(err.ptr());
+ }
+ else
+ to->append(STRING_WITH_LEN("???"));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ unpack key-fields from record to some buffer.
+
+ This is used mainly to get a good error message. We temporary
+ change the column bitmap so that all columns are readable.
+
+ @param
+ to Store value here in an easy to read form
+ @param
+ table Table to use
+ @param
+ key Key
+*/
+
+void key_unpack(String *to, TABLE *table, KEY *key)
+{
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ DBUG_ENTER("key_unpack");
+
+ to->length(0);
+ KEY_PART_INFO *key_part_end= key->key_part + key->user_defined_key_parts;
+ for (KEY_PART_INFO *key_part= key->key_part;
+ key_part < key_part_end;
+ key_part++)
+ {
+ if (to->length())
+ to->append('-');
+ if (key_part->null_bit)
+ {
+ if (table->record[0][key_part->null_offset] & key_part->null_bit)
+ {
+ to->append(STRING_WITH_LEN("NULL"));
+ continue;
+ }
+ }
+ field_unpack(to, key_part->field, table->record[0], key_part->length,
+ MY_TEST(key_part->key_part_flag & HA_PART_KEY_SEG));
+ }
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Check if key uses field that is marked in passed field bitmap.
+
+ SYNOPSIS
+ is_key_used()
+ table TABLE object with which keys and fields are associated.
+ idx Key to be checked.
+ fields Bitmap of fields to be checked.
+
+ NOTE
+ This function uses TABLE::tmp_set bitmap so the caller should care
+ about saving/restoring its state if it also uses this bitmap.
+
+ RETURN VALUE
+ TRUE Key uses field from bitmap
+ FALSE Otherwise
+*/
+
+bool is_key_used(TABLE *table, uint idx, const MY_BITMAP *fields)
+{
+ bitmap_clear_all(&table->tmp_set);
+ table->mark_columns_used_by_index_no_reset(idx, &table->tmp_set);
+ if (bitmap_is_overlapping(&table->tmp_set, fields))
+ return 1;
+
+ /*
+ If table handler has primary key as part of the index, check that primary
+ key is not updated
+ */
+ if (idx != table->s->primary_key && table->s->primary_key < MAX_KEY &&
+ (table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX))
+ return is_key_used(table, table->s->primary_key, fields);
+ return 0;
+}
+
+
+/**
+ Compare key in row to a given key.
+
+ @param key_part Key part handler
+ @param key Key to compare to value in table->record[0]
+ @param key_length length of 'key'
+
+ @return
+ The return value is SIGN(key_in_row - range_key):
+ - 0 Key is equal to range or 'range' == 0 (no range)
+ - -1 Key is less than range
+ - 1 Key is larger than range
+*/
+
+int key_cmp(KEY_PART_INFO *key_part, const uchar *key, uint key_length)
+{
+ uint store_length;
+
+ for (const uchar *end=key + key_length;
+ key < end;
+ key+= store_length, key_part++)
+ {
+ int cmp;
+ store_length= key_part->store_length;
+ if (key_part->null_bit)
+ {
+ /* This key part allows null values; NULL is lower than everything */
+ register bool field_is_null= key_part->field->is_null();
+ if (*key) // If range key is null
+ {
+ /* the range is expecting a null value */
+ if (!field_is_null)
+ return 1; // Found key is > range
+ /* null -- exact match, go to next key part */
+ continue;
+ }
+ else if (field_is_null)
+ return -1; // NULL is less than any value
+ key++; // Skip null byte
+ store_length--;
+ }
+ if ((cmp=key_part->field->key_cmp(key, key_part->length)) < 0)
+ return -1;
+ if (cmp > 0)
+ return 1;
+ }
+ return 0; // Keys are equal
+}
+
+
+/**
+ Compare two records in index order.
+
+ This method is set-up such that it can be called directly from the
+ priority queue and it is attempted to be optimised as much as possible
+ since this will be called O(N * log N) times while performing a merge
+ sort in various places in the code.
+
+ We retrieve the pointer to table->record[0] using the fact that key_parts
+ have an offset making it possible to calculate the start of the record.
+ We need to get the diff to the compared record since none of the records
+ being compared are stored in table->record[0].
+
+ We first check for NULL values, if there are no NULL values we use
+ a compare method that gets two field pointers and a max length
+ and return the result of the comparison.
+
+ key is a null terminated array, since in some cases (clustered
+ primary key) it must compare more than one index.
+
+ @param key Null terminated array of index information
+ @param first_rec Pointer to record compare with
+ @param second_rec Pointer to record compare against first_rec
+
+ @return Return value is SIGN(first_rec - second_rec)
+ @retval 0 Keys are equal
+ @retval -1 second_rec is greater than first_rec
+ @retval +1 first_rec is greater than second_rec
+*/
+
+int key_rec_cmp(void *key_p, uchar *first_rec, uchar *second_rec)
+{
+ KEY **key= (KEY**) key_p;
+ KEY *key_info= *(key++); // Start with first key
+ uint key_parts, key_part_num;
+ KEY_PART_INFO *key_part= key_info->key_part;
+ uchar *rec0= key_part->field->ptr - key_part->offset;
+ my_ptrdiff_t first_diff= first_rec - rec0, sec_diff= second_rec - rec0;
+ int result= 0;
+ Field *field;
+ DBUG_ENTER("key_rec_cmp");
+
+ /* loop over all given keys */
+ do
+ {
+ key_parts= key_info->user_defined_key_parts;
+ key_part= key_info->key_part;
+ key_part_num= 0;
+
+ /* loop over every key part */
+ do
+ {
+ field= key_part->field;
+
+ if (key_part->null_bit)
+ {
+ /* The key_part can contain NULL values */
+ bool first_is_null= field->is_real_null(first_diff);
+ bool sec_is_null= field->is_real_null(sec_diff);
+ /*
+ NULL is smaller then everything so if first is NULL and the other
+ not then we know that we should return -1 and for the opposite
+ we should return +1. If both are NULL then we call it equality
+ although it is a strange form of equality, we have equally little
+ information of the real value.
+ */
+ if (!first_is_null)
+ {
+ if (!sec_is_null)
+ ; /* Fall through, no NULL fields */
+ else
+ {
+ DBUG_RETURN(+1);
+ }
+ }
+ else if (!sec_is_null)
+ {
+ DBUG_RETURN(-1);
+ }
+ else
+ goto next_loop; /* Both were NULL */
+ }
+ /*
+ No null values in the fields
+ We use the virtual method cmp_max with a max length parameter.
+ For most field types this translates into a cmp without
+ max length. The exceptions are the BLOB and VARCHAR field types
+ that take the max length into account.
+ */
+ if ((result= field->cmp_max(field->ptr+first_diff, field->ptr+sec_diff,
+ key_part->length)))
+ DBUG_RETURN(result);
+next_loop:
+ key_part++;
+ key_part_num++;
+ } while (key_part_num < key_parts); /* this key is done */
+
+ key_info= *(key++);
+ } while (key_info); /* no more keys to test */
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Compare two key tuples.
+
+ @brief
+ Compare two key tuples, i.e. two key values in KeyTupleFormat.
+
+ @param part KEY_PART_INFO with key description
+ @param key1 First key to compare
+ @param key2 Second key to compare
+ @param tuple_length Length of key1 (and key2, they are the same) in bytes.
+
+ @return
+ @retval 0 key1 == key2
+ @retval -1 key1 < key2
+ @retval +1 key1 > key2
+*/
+
+int key_tuple_cmp(KEY_PART_INFO *part, uchar *key1, uchar *key2,
+ uint tuple_length)
+{
+ uchar *key1_end= key1 + tuple_length;
+ int len;
+ int res;
+ LINT_INIT(len);
+ for (;key1 < key1_end; key1 += len, key2 += len, part++)
+ {
+ len= part->store_length;
+ if (part->null_bit)
+ {
+ if (*key1) // key1 == NULL
+ {
+ if (!*key2) // key1(NULL) < key2(notNULL)
+ return -1;
+ continue;
+ }
+ else if (*key2) // key1(notNULL) > key2 (NULL)
+ return 1;
+ /* Step over the NULL bytes for key_cmp() call */
+ key1++;
+ key2++;
+ len--;
+ }
+ if ((res= part->field->key_cmp(key1, key2)))
+ return res;
+ }
+ return 0;
+}
+
+
+/**
+ Get hash value for the key from a key buffer
+
+ @param key_info the key descriptor
+ @param used_key_part number of key parts used for the key
+ @param key pointer to the buffer with the key value
+
+ @datails
+ When hashing we should take special care only of:
+ 1. NULLs (and keyparts which can be null so one byte reserved for it);
+ 2. Strings for which we have to take into account their collations
+ and the values of their lengths in the prefixes.
+
+ @return hash value calculated for the key
+*/
+
+ulong key_hashnr(KEY *key_info, uint used_key_parts, const uchar *key)
+{
+ ulong nr=1, nr2=4;
+ KEY_PART_INFO *key_part= key_info->key_part;
+ KEY_PART_INFO *end_key_part= key_part + used_key_parts;
+
+ for (; key_part < end_key_part; key_part++)
+ {
+ uchar *pos= (uchar*)key;
+ CHARSET_INFO *cs;
+ uint length, pack_length;
+ bool is_string= TRUE;
+ LINT_INIT(cs);
+ LINT_INIT(length);
+ LINT_INIT(pack_length);
+
+ key+= key_part->length;
+ if (key_part->null_bit)
+ {
+ key++; /* Skip null byte */
+ if (*pos) /* Found null */
+ {
+ nr^= (nr << 1) | 1;
+ /* Add key pack length to key for VARCHAR segments */
+ switch (key_part->type) {
+ case HA_KEYTYPE_VARTEXT1:
+ case HA_KEYTYPE_VARBINARY1:
+ case HA_KEYTYPE_VARTEXT2:
+ case HA_KEYTYPE_VARBINARY2:
+ key+= 2;
+ break;
+ default:
+ ;
+ }
+ continue;
+ }
+ pos++; /* Skip null byte */
+ }
+ /* If it is string set parameters of the string */
+ switch (key_part->type) {
+ case HA_KEYTYPE_TEXT:
+ cs= key_part->field->charset();
+ length= key_part->length;
+ pack_length= 0;
+ break;
+ case HA_KEYTYPE_BINARY :
+ cs= &my_charset_bin;
+ length= key_part->length;
+ pack_length= 0;
+ break;
+ case HA_KEYTYPE_VARTEXT1:
+ case HA_KEYTYPE_VARTEXT2:
+ cs= key_part->field->charset();
+ length= uint2korr(pos);
+ pack_length= 2;
+ break;
+ case HA_KEYTYPE_VARBINARY1:
+ case HA_KEYTYPE_VARBINARY2:
+ cs= &my_charset_bin;
+ length= uint2korr(pos);
+ pack_length= 2;
+ break;
+ default:
+ is_string= FALSE;
+ }
+
+ if (is_string)
+ {
+ if (cs->mbmaxlen > 1)
+ {
+ uint char_length= my_charpos(cs, pos + pack_length,
+ pos + pack_length + length,
+ length / cs->mbmaxlen);
+ set_if_smaller(length, char_length);
+ }
+ cs->coll->hash_sort(cs, pos+pack_length, length, &nr, &nr2);
+ key+= pack_length;
+ }
+ else
+ {
+ for (; pos < (uchar*)key ; pos++)
+ {
+ nr^=(ulong) ((((uint) nr & 63)+nr2)*((uint) *pos)) + (nr << 8);
+ nr2+=3;
+ }
+ }
+ }
+ DBUG_PRINT("exit", ("hash: %lx", nr));
+ return(nr);
+}
+
+
+/**
+ Check whether two keys in the key buffers are equal
+
+ @param key_info the key descriptor
+ @param used_key_part number of key parts used for the keys
+ @param key1 pointer to the buffer with the first key
+ @param key2 pointer to the buffer with the second key
+
+ @detail See details of key_hashnr().
+
+ @retval TRUE keys in the buffers are NOT equal
+ @retval FALSE keys in the buffers are equal
+*/
+
+bool key_buf_cmp(KEY *key_info, uint used_key_parts,
+ const uchar *key1, const uchar *key2)
+{
+ KEY_PART_INFO *key_part= key_info->key_part;
+ KEY_PART_INFO *end_key_part= key_part + used_key_parts;
+
+ for (; key_part < end_key_part; key_part++)
+ {
+ uchar *pos1= (uchar*)key1;
+ uchar *pos2= (uchar*)key2;
+ CHARSET_INFO *cs;
+ uint length1, length2, pack_length;
+ bool is_string= TRUE;
+ LINT_INIT(cs);
+ LINT_INIT(length1);
+ LINT_INIT(length2);
+ LINT_INIT(pack_length);
+
+ key1+= key_part->length;
+ key2+= key_part->length;
+ if (key_part->null_bit)
+ {
+ key1++; key2++; /* Skip null byte */
+ if (*pos1 && *pos2) /* Both are null */
+ {
+ /* Add key pack length to key for VARCHAR segments */
+ switch (key_part->type) {
+ case HA_KEYTYPE_VARTEXT1:
+ case HA_KEYTYPE_VARBINARY1:
+ case HA_KEYTYPE_VARTEXT2:
+ case HA_KEYTYPE_VARBINARY2:
+ key1+= 2; key2+= 2;
+ break;
+ default:
+ ;
+ }
+ continue;
+ }
+ if (*pos1 != *pos2)
+ return TRUE;
+ pos1++; pos2++;
+ }
+
+ /* If it is string set parameters of the string */
+ switch (key_part->type) {
+ case HA_KEYTYPE_TEXT:
+ cs= key_part->field->charset();
+ length1= length2= key_part->length;
+ pack_length= 0;
+ break;
+ case HA_KEYTYPE_BINARY :
+ cs= &my_charset_bin;
+ length1= length2= key_part->length;
+ pack_length= 0;
+ break;
+ case HA_KEYTYPE_VARTEXT1:
+ case HA_KEYTYPE_VARTEXT2:
+ cs= key_part->field->charset();
+ length1= uint2korr(pos1);
+ length2= uint2korr(pos2);
+ pack_length= 2;
+ break;
+ case HA_KEYTYPE_VARBINARY1:
+ case HA_KEYTYPE_VARBINARY2:
+ cs= &my_charset_bin;
+ length1= uint2korr(pos1);
+ length2= uint2korr(pos2);
+ pack_length= 2;
+ break;
+ default:
+ is_string= FALSE;
+ }
+
+ if (is_string)
+ {
+ /*
+ Compare the strings taking into account length in characters
+ and collation
+ */
+ uint byte_len1= length1, byte_len2= length2;
+ if (cs->mbmaxlen > 1)
+ {
+ uint char_length1= my_charpos(cs, pos1 + pack_length,
+ pos1 + pack_length + length1,
+ length1 / cs->mbmaxlen);
+ uint char_length2= my_charpos(cs, pos2 + pack_length,
+ pos2 + pack_length + length2,
+ length2 / cs->mbmaxlen);
+ set_if_smaller(length1, char_length1);
+ set_if_smaller(length2, char_length2);
+ }
+ if (length1 != length2 ||
+ cs->coll->strnncollsp(cs,
+ pos1 + pack_length, byte_len1,
+ pos2 + pack_length, byte_len2,
+ 1))
+ return TRUE;
+ key1+= pack_length; key2+= pack_length;
+ }
+ else
+ {
+ /* it is OK to compare non-string byte per byte */
+ for (; pos1 < (uchar*)key1 ; pos1++, pos2++)
+ {
+ if (pos1[0] != pos2[0])
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
diff --git a/sql/key.h b/sql/key.h
new file mode 100644
index 00000000000..47b981f5298
--- /dev/null
+++ b/sql/key.h
@@ -0,0 +1,46 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef KEY_INCLUDED
+#define KEY_INCLUDED
+
+#include "my_global.h" /* uchar */
+
+class Field;
+class String;
+struct TABLE;
+typedef struct st_bitmap MY_BITMAP;
+typedef struct st_key KEY;
+typedef struct st_key_part_info KEY_PART_INFO;
+
+int find_ref_key(KEY *key, uint key_count, uchar *record, Field *field,
+ uint *key_length, uint *keypart);
+void key_copy(uchar *to_key, uchar *from_record, KEY *key_info, uint key_length,
+ bool with_zerofill= FALSE);
+void key_restore(uchar *to_record, uchar *from_key, KEY *key_info,
+ uint key_length);
+bool key_cmp_if_same(TABLE *form,const uchar *key,uint index,uint key_length);
+void key_unpack(String *to, TABLE *table, KEY *key);
+void field_unpack(String *to, Field *field, const uchar *rec, uint max_length,
+ bool prefix_key);
+bool is_key_used(TABLE *table, uint idx, const MY_BITMAP *fields);
+int key_cmp(KEY_PART_INFO *key_part, const uchar *key, uint key_length);
+ulong key_hashnr(KEY *key_info, uint used_key_parts, const uchar *key);
+bool key_buf_cmp(KEY *key_info, uint used_key_parts,
+ const uchar *key1, const uchar *key2);
+extern "C" int key_rec_cmp(void *key_info, uchar *a, uchar *b);
+int key_tuple_cmp(KEY_PART_INFO *part, uchar *key1, uchar *key2, uint tuple_length);
+
+#endif /* KEY_INCLUDED */
diff --git a/sql/keycaches.cc b/sql/keycaches.cc
new file mode 100644
index 00000000000..120aa7e1029
--- /dev/null
+++ b/sql/keycaches.cc
@@ -0,0 +1,231 @@
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "keycaches.h"
+
+/****************************************************************************
+ Named list handling
+****************************************************************************/
+
+NAMED_ILIST key_caches;
+NAMED_ILIST rpl_filters;
+
+/**
+ ilink (intrusive list element) with a name
+*/
+class NAMED_ILINK :public ilink
+{
+public:
+ const char *name;
+ uint name_length;
+ uchar* data;
+
+ NAMED_ILINK(I_List<NAMED_ILINK> *links, const char *name_arg,
+ uint name_length_arg, uchar* data_arg)
+ :name_length(name_length_arg), data(data_arg)
+ {
+ name= my_strndup(name_arg, name_length, MYF(MY_WME));
+ links->push_back(this);
+ }
+ inline bool cmp(const char *name_cmp, uint length)
+ {
+ return length == name_length && !memcmp(name, name_cmp, length);
+ }
+ ~NAMED_ILINK()
+ {
+ my_free((void *) name);
+ }
+};
+
+uchar* find_named(I_List<NAMED_ILINK> *list, const char *name, uint length,
+ NAMED_ILINK **found)
+{
+ I_List_iterator<NAMED_ILINK> it(*list);
+ NAMED_ILINK *element;
+ while ((element= it++))
+ {
+ if (element->cmp(name, length))
+ {
+ if (found)
+ *found= element;
+ return element->data;
+ }
+ }
+ return 0;
+}
+
+
+bool NAMED_ILIST::delete_element(const char *name, uint length, void (*free_element)(const char *name, uchar*))
+{
+ I_List_iterator<NAMED_ILINK> it(*this);
+ NAMED_ILINK *element;
+ DBUG_ENTER("NAMED_ILIST::delete_element");
+ while ((element= it++))
+ {
+ if (element->cmp(name, length))
+ {
+ (*free_element)(element->name, element->data);
+ delete element;
+ DBUG_RETURN(0);
+ }
+ }
+ DBUG_RETURN(1);
+}
+
+void NAMED_ILIST::delete_elements(void (*free_element)(const char *name, uchar*))
+{
+ NAMED_ILINK *element;
+ DBUG_ENTER("NAMED_ILIST::delete_elements");
+ while ((element= get()))
+ {
+ (*free_element)(element->name, element->data);
+ delete element;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/* Key cache functions */
+
+LEX_STRING default_key_cache_base= {C_STRING_WITH_LEN("default")};
+
+KEY_CACHE zero_key_cache; ///< @@nonexistent_cache.param->value_ptr() points here
+
+KEY_CACHE *get_key_cache(LEX_STRING *cache_name)
+{
+ if (!cache_name || ! cache_name->length)
+ cache_name= &default_key_cache_base;
+ return ((KEY_CACHE*) find_named(&key_caches,
+ cache_name->str, cache_name->length, 0));
+}
+
+KEY_CACHE *create_key_cache(const char *name, uint length)
+{
+ KEY_CACHE *key_cache;
+ DBUG_ENTER("create_key_cache");
+ DBUG_PRINT("enter",("name: %.*s", length, name));
+
+ if ((key_cache= (KEY_CACHE*) my_malloc(sizeof(KEY_CACHE),
+ MYF(MY_ZEROFILL | MY_WME))))
+ {
+ if (!new NAMED_ILINK(&key_caches, name, length, (uchar*) key_cache))
+ {
+ my_free(key_cache);
+ key_cache= 0;
+ }
+ else
+ {
+ /*
+ Set default values for a key cache
+ The values in dflt_key_cache_var is set by my_getopt() at startup
+
+ We don't set 'buff_size' as this is used to enable the key cache
+ */
+ key_cache->param_block_size= dflt_key_cache_var.param_block_size;
+ key_cache->param_division_limit= dflt_key_cache_var.param_division_limit;
+ key_cache->param_age_threshold= dflt_key_cache_var.param_age_threshold;
+ key_cache->param_partitions= dflt_key_cache_var.param_partitions;
+ }
+ }
+ DBUG_RETURN(key_cache);
+}
+
+
+KEY_CACHE *get_or_create_key_cache(const char *name, uint length)
+{
+ LEX_STRING key_cache_name;
+ KEY_CACHE *key_cache;
+
+ key_cache_name.str= (char *) name;
+ key_cache_name.length= length;
+ if (!(key_cache= get_key_cache(&key_cache_name)))
+ key_cache= create_key_cache(name, length);
+ return key_cache;
+}
+
+
+void free_key_cache(const char *name, KEY_CACHE *key_cache)
+{
+ end_key_cache(key_cache, 1); // Can never fail
+ my_free(key_cache);
+}
+
+
+bool process_key_caches(process_key_cache_t func, void *param)
+{
+ I_List_iterator<NAMED_ILINK> it(key_caches);
+ NAMED_ILINK *element;
+ int res= 0;
+
+ while ((element= it++))
+ {
+ KEY_CACHE *key_cache= (KEY_CACHE *) element->data;
+ res |= func(element->name, key_cache, param);
+ }
+ return res != 0;
+}
+
+/* Rpl_filter functions */
+
+LEX_STRING default_rpl_filter_base= {C_STRING_WITH_LEN("")};
+
+Rpl_filter *get_rpl_filter(LEX_STRING *filter_name)
+{
+ if (!filter_name->length)
+ filter_name= &default_rpl_filter_base;
+ return ((Rpl_filter*) find_named(&rpl_filters,
+ filter_name->str, filter_name->length, 0));
+}
+
+Rpl_filter *create_rpl_filter(const char *name, uint length)
+{
+ Rpl_filter *filter;
+ DBUG_ENTER("create_rpl_filter");
+ DBUG_PRINT("enter",("name: %.*s", length, name));
+
+ filter= new Rpl_filter;
+ if (filter)
+ {
+ if (!new NAMED_ILINK(&rpl_filters, name, length, (uchar*) filter))
+ {
+ delete filter;
+ filter= 0;
+ }
+ }
+ DBUG_RETURN(filter);
+}
+
+
+Rpl_filter *get_or_create_rpl_filter(const char *name, uint length)
+{
+ LEX_STRING rpl_filter_name;
+ Rpl_filter *filter;
+
+ rpl_filter_name.str= (char *) name;
+ rpl_filter_name.length= length;
+ if (!(filter= get_rpl_filter(&rpl_filter_name)))
+ filter= create_rpl_filter(name, length);
+ return filter;
+}
+
+void free_rpl_filter(const char *name, Rpl_filter *filter)
+{
+ delete filter;
+}
+
+void free_all_rpl_filters()
+{
+ rpl_filters.delete_elements((void (*)(const char*, uchar*)) free_rpl_filter);
+}
diff --git a/sql/keycaches.h b/sql/keycaches.h
new file mode 100644
index 00000000000..32537339e2e
--- /dev/null
+++ b/sql/keycaches.h
@@ -0,0 +1,58 @@
+#ifndef KEYCACHES_INCLUDED
+#define KEYCACHES_INCLUDED
+
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "sql_list.h"
+#include <keycache.h>
+#include <rpl_filter.h>
+
+extern "C"
+{
+ typedef int (*process_key_cache_t) (const char *, KEY_CACHE *, void *);
+}
+
+class NAMED_ILINK;
+
+class NAMED_ILIST: public I_List<NAMED_ILINK>
+{
+ public:
+ void delete_elements(void (*free_element)(const char*, uchar*));
+ bool delete_element(const char *name, uint length, void (*free_element)(const char*, uchar*));
+};
+
+/* For key cache */
+extern LEX_STRING default_key_cache_base;
+extern KEY_CACHE zero_key_cache;
+extern NAMED_ILIST key_caches;
+
+KEY_CACHE *create_key_cache(const char *name, uint length);
+KEY_CACHE *get_key_cache(LEX_STRING *cache_name);
+KEY_CACHE *get_or_create_key_cache(const char *name, uint length);
+void free_key_cache(const char *name, KEY_CACHE *key_cache);
+bool process_key_caches(process_key_cache_t func, void *param);
+
+/* For Rpl_filter */
+extern LEX_STRING default_rpl_filter_base;
+extern NAMED_ILIST rpl_filters;
+
+Rpl_filter *create_rpl_filter(const char *name, uint length);
+Rpl_filter *get_rpl_filter(LEX_STRING *filter_name);
+Rpl_filter *get_or_create_rpl_filter(const char *name, uint length);
+void free_rpl_filter(const char *name, Rpl_filter *filter);
+void free_all_rpl_filters(void);
+
+#endif /* KEYCACHES_INCLUDED */
diff --git a/sql/lex.h b/sql/lex.h
new file mode 100644
index 00000000000..a272504c0f2
--- /dev/null
+++ b/sql/lex.h
@@ -0,0 +1,711 @@
+#ifndef LEX_INCLUDED
+#define LEX_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+
+/* This file includes all reserved words and functions */
+
+#include "lex_symbol.h"
+
+SYM_GROUP sym_group_common= {"", ""};
+SYM_GROUP sym_group_geom= {"Spatial extentions", "HAVE_SPATIAL"};
+SYM_GROUP sym_group_rtree= {"RTree keys", "HAVE_RTREE_KEYS"};
+
+/* We don't want to include sql_yacc.h into gen_lex_hash */
+#ifdef NO_YACC_SYMBOLS
+#define SYM_OR_NULL(A) 0
+#else
+#define SYM_OR_NULL(A) A
+#endif
+
+#define SYM(A) SYM_OR_NULL(A),0,&sym_group_common
+
+/*
+ Symbols are broken into separated arrays to allow field names with
+ same name as functions.
+ These are kept sorted for human lookup (the symbols are hashed).
+
+ NOTE! The symbol tables should be the same regardless of what features
+ are compiled into the server. Don't add ifdef'ed symbols to the
+ lists
+*/
+
+static SYMBOL symbols[] = {
+ { "&&", SYM(AND_AND_SYM)},
+ { "<", SYM(LT)},
+ { "<=", SYM(LE)},
+ { "<>", SYM(NE)},
+ { "!=", SYM(NE)},
+ { "=", SYM(EQ)},
+ { ">", SYM(GT_SYM)},
+ { ">=", SYM(GE)},
+ { "<<", SYM(SHIFT_LEFT)},
+ { ">>", SYM(SHIFT_RIGHT)},
+ { "<=>", SYM(EQUAL_SYM)},
+ { "ACCESSIBLE", SYM(ACCESSIBLE_SYM)},
+ { "ACTION", SYM(ACTION)},
+ { "ADD", SYM(ADD)},
+ { "ADMIN", SYM(ADMIN_SYM)},
+ { "AFTER", SYM(AFTER_SYM)},
+ { "AGAINST", SYM(AGAINST)},
+ { "AGGREGATE", SYM(AGGREGATE_SYM)},
+ { "ALL", SYM(ALL)},
+ { "ALGORITHM", SYM(ALGORITHM_SYM)},
+ { "ALTER", SYM(ALTER)},
+ { "ALWAYS", SYM(ALWAYS_SYM)},
+ { "ANALYZE", SYM(ANALYZE_SYM)},
+ { "AND", SYM(AND_SYM)},
+ { "ANY", SYM(ANY_SYM)},
+ { "AS", SYM(AS)},
+ { "ASC", SYM(ASC)},
+ { "ASCII", SYM(ASCII_SYM)},
+ { "ASENSITIVE", SYM(ASENSITIVE_SYM)},
+ { "AT", SYM(AT_SYM)},
+ { "AUTHORS", SYM(AUTHORS_SYM)},
+ { "AUTO_INCREMENT", SYM(AUTO_INC)},
+ { "AUTOEXTEND_SIZE", SYM(AUTOEXTEND_SIZE_SYM)},
+ { "AUTO", SYM(AUTO_SYM)},
+ { "AVG", SYM(AVG_SYM)},
+ { "AVG_ROW_LENGTH", SYM(AVG_ROW_LENGTH)},
+ { "BACKUP", SYM(BACKUP_SYM)},
+ { "BEFORE", SYM(BEFORE_SYM)},
+ { "BEGIN", SYM(BEGIN_SYM)},
+ { "BETWEEN", SYM(BETWEEN_SYM)},
+ { "BIGINT", SYM(BIGINT)},
+ { "BINARY", SYM(BINARY)},
+ { "BINLOG", SYM(BINLOG_SYM)},
+ { "BIT", SYM(BIT_SYM)},
+ { "BLOB", SYM(BLOB_SYM)},
+ { "BLOCK", SYM(BLOCK_SYM)},
+ { "BOOL", SYM(BOOL_SYM)},
+ { "BOOLEAN", SYM(BOOLEAN_SYM)},
+ { "BOTH", SYM(BOTH)},
+ { "BTREE", SYM(BTREE_SYM)},
+ { "BY", SYM(BY)},
+ { "BYTE", SYM(BYTE_SYM)},
+ { "CACHE", SYM(CACHE_SYM)},
+ { "CALL", SYM(CALL_SYM)},
+ { "CASCADE", SYM(CASCADE)},
+ { "CASCADED", SYM(CASCADED)},
+ { "CASE", SYM(CASE_SYM)},
+ { "CATALOG_NAME", SYM(CATALOG_NAME_SYM)},
+ { "CHAIN", SYM(CHAIN_SYM)},
+ { "CHANGE", SYM(CHANGE)},
+ { "CHANGED", SYM(CHANGED)},
+ { "CHAR", SYM(CHAR_SYM)},
+ { "CHARACTER", SYM(CHAR_SYM)},
+ { "CHARSET", SYM(CHARSET)},
+ { "CHECK", SYM(CHECK_SYM)},
+ { "CHECKPOINT", SYM(CHECKPOINT_SYM)},
+ { "CHECKSUM", SYM(CHECKSUM_SYM)},
+ { "CIPHER", SYM(CIPHER_SYM)},
+ { "CLASS_ORIGIN", SYM(CLASS_ORIGIN_SYM)},
+ { "CLIENT", SYM(CLIENT_SYM)},
+ { "CLIENT_STATISTICS", SYM(CLIENT_STATS_SYM)},
+ { "CLOSE", SYM(CLOSE_SYM)},
+ { "COALESCE", SYM(COALESCE)},
+ { "CODE", SYM(CODE_SYM)},
+ { "COLLATE", SYM(COLLATE_SYM)},
+ { "COLLATION", SYM(COLLATION_SYM)},
+ { "COLUMN", SYM(COLUMN_SYM)},
+ { "COLUMN_NAME", SYM(COLUMN_NAME_SYM)},
+ { "COLUMNS", SYM(COLUMNS)},
+ { "COLUMN_ADD", SYM(COLUMN_ADD_SYM)},
+ { "COLUMN_CHECK", SYM(COLUMN_CHECK_SYM)},
+ { "COLUMN_CREATE", SYM(COLUMN_CREATE_SYM)},
+ { "COLUMN_DELETE", SYM(COLUMN_DELETE_SYM)},
+ { "COLUMN_GET", SYM(COLUMN_GET_SYM)},
+ { "COMMENT", SYM(COMMENT_SYM)},
+ { "COMMIT", SYM(COMMIT_SYM)},
+ { "COMMITTED", SYM(COMMITTED_SYM)},
+ { "COMPACT", SYM(COMPACT_SYM)},
+ { "COMPLETION", SYM(COMPLETION_SYM)},
+ { "COMPRESSED", SYM(COMPRESSED_SYM)},
+ { "CONCURRENT", SYM(CONCURRENT)},
+ { "CONDITION", SYM(CONDITION_SYM)},
+ { "CONNECTION", SYM(CONNECTION_SYM)},
+ { "CONSISTENT", SYM(CONSISTENT_SYM)},
+ { "CONSTRAINT", SYM(CONSTRAINT)},
+ { "CONSTRAINT_CATALOG", SYM(CONSTRAINT_CATALOG_SYM)},
+ { "CONSTRAINT_NAME", SYM(CONSTRAINT_NAME_SYM)},
+ { "CONSTRAINT_SCHEMA", SYM(CONSTRAINT_SCHEMA_SYM)},
+ { "CONTAINS", SYM(CONTAINS_SYM)},
+ { "CONTEXT", SYM(CONTEXT_SYM)},
+ { "CONTINUE", SYM(CONTINUE_SYM)},
+ { "CONTRIBUTORS", SYM(CONTRIBUTORS_SYM)},
+ { "CONVERT", SYM(CONVERT_SYM)},
+ { "CPU", SYM(CPU_SYM)},
+ { "CREATE", SYM(CREATE)},
+ { "CROSS", SYM(CROSS)},
+ { "CUBE", SYM(CUBE_SYM)},
+ { "CURRENT", SYM(CURRENT_SYM)},
+ { "CURRENT_DATE", SYM(CURDATE)},
+ { "CURRENT_POS", SYM(CURRENT_POS_SYM)},
+ { "CURRENT_ROLE", SYM(CURRENT_ROLE)},
+ { "CURRENT_TIME", SYM(CURTIME)},
+ { "CURRENT_TIMESTAMP", SYM(NOW_SYM)},
+ { "CURRENT_USER", SYM(CURRENT_USER)},
+ { "CURSOR", SYM(CURSOR_SYM)},
+ { "CURSOR_NAME", SYM(CURSOR_NAME_SYM)},
+ { "DATA", SYM(DATA_SYM)},
+ { "DATABASE", SYM(DATABASE)},
+ { "DATABASES", SYM(DATABASES)},
+ { "DATAFILE", SYM(DATAFILE_SYM)},
+ { "DATE", SYM(DATE_SYM)},
+ { "DATETIME", SYM(DATETIME)},
+ { "DAY", SYM(DAY_SYM)},
+ { "DAY_HOUR", SYM(DAY_HOUR_SYM)},
+ { "DAY_MICROSECOND", SYM(DAY_MICROSECOND_SYM)},
+ { "DAY_MINUTE", SYM(DAY_MINUTE_SYM)},
+ { "DAY_SECOND", SYM(DAY_SECOND_SYM)},
+ { "DEALLOCATE", SYM(DEALLOCATE_SYM)},
+ { "DEC", SYM(DECIMAL_SYM)},
+ { "DECIMAL", SYM(DECIMAL_SYM)},
+ { "DECLARE", SYM(DECLARE_SYM)},
+ { "DEFAULT", SYM(DEFAULT)},
+ { "DEFINER", SYM(DEFINER_SYM)},
+ { "DELAYED", SYM(DELAYED_SYM)},
+ { "DELAY_KEY_WRITE", SYM(DELAY_KEY_WRITE_SYM)},
+ { "DELETE", SYM(DELETE_SYM)},
+ { "DESC", SYM(DESC)},
+ { "DESCRIBE", SYM(DESCRIBE)},
+ { "DES_KEY_FILE", SYM(DES_KEY_FILE)},
+ { "DETERMINISTIC", SYM(DETERMINISTIC_SYM)},
+ { "DIAGNOSTICS", SYM(DIAGNOSTICS_SYM)},
+ { "DIRECTORY", SYM(DIRECTORY_SYM)},
+ { "DISABLE", SYM(DISABLE_SYM)},
+ { "DISCARD", SYM(DISCARD)},
+ { "DISK", SYM(DISK_SYM)},
+ { "DISTINCT", SYM(DISTINCT)},
+ { "DISTINCTROW", SYM(DISTINCT)}, /* Access likes this */
+ { "DIV", SYM(DIV_SYM)},
+ { "DO", SYM(DO_SYM)},
+ { "DOUBLE", SYM(DOUBLE_SYM)},
+ { "DROP", SYM(DROP)},
+ { "DUAL", SYM(DUAL_SYM)},
+ { "DUMPFILE", SYM(DUMPFILE)},
+ { "DUPLICATE", SYM(DUPLICATE_SYM)},
+ { "DYNAMIC", SYM(DYNAMIC_SYM)},
+ { "EACH", SYM(EACH_SYM)},
+ { "ELSE", SYM(ELSE)},
+ { "ELSEIF", SYM(ELSEIF_SYM)},
+ { "ENABLE", SYM(ENABLE_SYM)},
+ { "ENCLOSED", SYM(ENCLOSED)},
+ { "END", SYM(END)},
+ { "ENDS", SYM(ENDS_SYM)},
+ { "ENGINE", SYM(ENGINE_SYM)},
+ { "ENGINES", SYM(ENGINES_SYM)},
+ { "ENUM", SYM(ENUM)},
+ { "ERROR", SYM(ERROR_SYM)},
+ { "ERRORS", SYM(ERRORS)},
+ { "ESCAPE", SYM(ESCAPE_SYM)},
+ { "ESCAPED", SYM(ESCAPED)},
+ { "EVENT", SYM(EVENT_SYM)},
+ { "EVENTS", SYM(EVENTS_SYM)},
+ { "EVERY", SYM(EVERY_SYM)},
+ { "EXAMINED", SYM(EXAMINED_SYM)},
+ { "EXCHANGE", SYM(EXCHANGE_SYM)},
+ { "EXECUTE", SYM(EXECUTE_SYM)},
+ { "EXISTS", SYM(EXISTS)},
+ { "EXIT", SYM(EXIT_SYM)},
+ { "EXPANSION", SYM(EXPANSION_SYM)},
+ { "EXPORT", SYM(EXPORT_SYM)},
+ { "EXPLAIN", SYM(DESCRIBE)},
+ { "EXTENDED", SYM(EXTENDED_SYM)},
+ { "EXTENT_SIZE", SYM(EXTENT_SIZE_SYM)},
+ { "FALSE", SYM(FALSE_SYM)},
+ { "FAST", SYM(FAST_SYM)},
+ { "FAULTS", SYM(FAULTS_SYM)},
+ { "FETCH", SYM(FETCH_SYM)},
+ { "FIELDS", SYM(COLUMNS)},
+ { "FILE", SYM(FILE_SYM)},
+ { "FIRST", SYM(FIRST_SYM)},
+ { "FIXED", SYM(FIXED_SYM)},
+ { "FLOAT", SYM(FLOAT_SYM)},
+ { "FLOAT4", SYM(FLOAT_SYM)},
+ { "FLOAT8", SYM(DOUBLE_SYM)},
+ { "FLUSH", SYM(FLUSH_SYM)},
+ { "FOR", SYM(FOR_SYM)},
+ { "FORCE", SYM(FORCE_SYM)},
+ { "FOREIGN", SYM(FOREIGN)},
+ { "FOUND", SYM(FOUND_SYM)},
+ { "FROM", SYM(FROM)},
+ { "FULL", SYM(FULL)},
+ { "FULLTEXT", SYM(FULLTEXT_SYM)},
+ { "FUNCTION", SYM(FUNCTION_SYM)},
+ { "GENERAL", SYM(GENERAL)},
+ { "GENERATED", SYM(GENERATED_SYM)},
+ { "GEOMETRY", SYM(GEOMETRY_SYM)},
+ { "GEOMETRYCOLLECTION",SYM(GEOMETRYCOLLECTION)},
+ { "GET_FORMAT", SYM(GET_FORMAT)},
+ { "GET", SYM(GET_SYM)},
+ { "GLOBAL", SYM(GLOBAL_SYM)},
+ { "GRANT", SYM(GRANT)},
+ { "GRANTS", SYM(GRANTS)},
+ { "GROUP", SYM(GROUP_SYM)},
+ { "HANDLER", SYM(HANDLER_SYM)},
+ { "HARD", SYM(HARD_SYM)},
+ { "HASH", SYM(HASH_SYM)},
+ { "HAVING", SYM(HAVING)},
+ { "HELP", SYM(HELP_SYM)},
+ { "HIGH_PRIORITY", SYM(HIGH_PRIORITY)},
+ { "HOST", SYM(HOST_SYM)},
+ { "HOSTS", SYM(HOSTS_SYM)},
+ { "HOUR", SYM(HOUR_SYM)},
+ { "HOUR_MICROSECOND", SYM(HOUR_MICROSECOND_SYM)},
+ { "HOUR_MINUTE", SYM(HOUR_MINUTE_SYM)},
+ { "HOUR_SECOND", SYM(HOUR_SECOND_SYM)},
+ { "ID", SYM(ID_SYM)},
+ { "IDENTIFIED", SYM(IDENTIFIED_SYM)},
+ { "IF", SYM(IF)},
+ { "IGNORE", SYM(IGNORE_SYM)},
+ { "IGNORE_SERVER_IDS", SYM(IGNORE_SERVER_IDS_SYM)},
+ { "IMPORT", SYM(IMPORT)},
+ { "IN", SYM(IN_SYM)},
+ { "INDEX", SYM(INDEX_SYM)},
+ { "INDEXES", SYM(INDEXES)},
+ { "INDEX_STATISTICS", SYM(INDEX_STATS_SYM)},
+ { "INFILE", SYM(INFILE)},
+ { "INITIAL_SIZE", SYM(INITIAL_SIZE_SYM)},
+ { "INNER", SYM(INNER_SYM)},
+ { "INOUT", SYM(INOUT_SYM)},
+ { "INSENSITIVE", SYM(INSENSITIVE_SYM)},
+ { "INSERT", SYM(INSERT)},
+ { "INSERT_METHOD", SYM(INSERT_METHOD)},
+ { "INSTALL", SYM(INSTALL_SYM)},
+ { "INT", SYM(INT_SYM)},
+ { "INT1", SYM(TINYINT)},
+ { "INT2", SYM(SMALLINT)},
+ { "INT3", SYM(MEDIUMINT)},
+ { "INT4", SYM(INT_SYM)},
+ { "INT8", SYM(BIGINT)},
+ { "INTEGER", SYM(INT_SYM)},
+ { "INTERVAL", SYM(INTERVAL_SYM)},
+ { "INTO", SYM(INTO)},
+ { "IO", SYM(IO_SYM)},
+ { "IO_THREAD", SYM(RELAY_THREAD)},
+ { "IPC", SYM(IPC_SYM)},
+ { "IS", SYM(IS)},
+ { "ISOLATION", SYM(ISOLATION)},
+ { "ISSUER", SYM(ISSUER_SYM)},
+ { "ITERATE", SYM(ITERATE_SYM)},
+ { "INVOKER", SYM(INVOKER_SYM)},
+ { "JOIN", SYM(JOIN_SYM)},
+ { "KEY", SYM(KEY_SYM)},
+ { "KEYS", SYM(KEYS)},
+ { "KEY_BLOCK_SIZE", SYM(KEY_BLOCK_SIZE)},
+ { "KILL", SYM(KILL_SYM)},
+ { "LANGUAGE", SYM(LANGUAGE_SYM)},
+ { "LAST", SYM(LAST_SYM)},
+ { "LAST_VALUE", SYM(LAST_VALUE)},
+ { "LEADING", SYM(LEADING)},
+ { "LEAVE", SYM(LEAVE_SYM)},
+ { "LEAVES", SYM(LEAVES)},
+ { "LEFT", SYM(LEFT)},
+ { "LESS", SYM(LESS_SYM)},
+ { "LEVEL", SYM(LEVEL_SYM)},
+ { "LIKE", SYM(LIKE)},
+ { "LIMIT", SYM(LIMIT)},
+ { "LINEAR", SYM(LINEAR_SYM)},
+ { "LINES", SYM(LINES)},
+ { "LINESTRING", SYM(LINESTRING)},
+ { "LIST", SYM(LIST_SYM)},
+ { "LOAD", SYM(LOAD)},
+ { "LOCAL", SYM(LOCAL_SYM)},
+ { "LOCALTIME", SYM(NOW_SYM)},
+ { "LOCALTIMESTAMP", SYM(NOW_SYM)},
+ { "LOCK", SYM(LOCK_SYM)},
+ { "LOCKS", SYM(LOCKS_SYM)},
+ { "LOGFILE", SYM(LOGFILE_SYM)},
+ { "LOGS", SYM(LOGS_SYM)},
+ { "LONG", SYM(LONG_SYM)},
+ { "LONGBLOB", SYM(LONGBLOB)},
+ { "LONGTEXT", SYM(LONGTEXT)},
+ { "LOOP", SYM(LOOP_SYM)},
+ { "LOW_PRIORITY", SYM(LOW_PRIORITY)},
+ { "MASTER", SYM(MASTER_SYM)},
+ { "MASTER_CONNECT_RETRY", SYM(MASTER_CONNECT_RETRY_SYM)},
+ { "MASTER_GTID_POS", SYM(MASTER_GTID_POS_SYM)},
+ { "MASTER_HOST", SYM(MASTER_HOST_SYM)},
+ { "MASTER_LOG_FILE", SYM(MASTER_LOG_FILE_SYM)},
+ { "MASTER_LOG_POS", SYM(MASTER_LOG_POS_SYM)},
+ { "MASTER_PASSWORD", SYM(MASTER_PASSWORD_SYM)},
+ { "MASTER_PORT", SYM(MASTER_PORT_SYM)},
+ { "MASTER_SERVER_ID", SYM(MASTER_SERVER_ID_SYM)},
+ { "MASTER_SSL", SYM(MASTER_SSL_SYM)},
+ { "MASTER_SSL_CA", SYM(MASTER_SSL_CA_SYM)},
+ { "MASTER_SSL_CAPATH",SYM(MASTER_SSL_CAPATH_SYM)},
+ { "MASTER_SSL_CERT", SYM(MASTER_SSL_CERT_SYM)},
+ { "MASTER_SSL_CIPHER",SYM(MASTER_SSL_CIPHER_SYM)},
+ { "MASTER_SSL_CRL", SYM(MASTER_SSL_CRL_SYM)},
+ { "MASTER_SSL_CRLPATH",SYM(MASTER_SSL_CRLPATH_SYM)},
+ { "MASTER_SSL_KEY", SYM(MASTER_SSL_KEY_SYM)},
+ { "MASTER_SSL_VERIFY_SERVER_CERT", SYM(MASTER_SSL_VERIFY_SERVER_CERT_SYM)},
+ { "MASTER_USER", SYM(MASTER_USER_SYM)},
+ { "MASTER_USE_GTID", SYM(MASTER_USE_GTID_SYM)},
+ { "MASTER_HEARTBEAT_PERIOD", SYM(MASTER_HEARTBEAT_PERIOD_SYM)},
+ { "MATCH", SYM(MATCH)},
+ { "MAX_CONNECTIONS_PER_HOUR", SYM(MAX_CONNECTIONS_PER_HOUR)},
+ { "MAX_QUERIES_PER_HOUR", SYM(MAX_QUERIES_PER_HOUR)},
+ { "MAX_ROWS", SYM(MAX_ROWS)},
+ { "MAX_SIZE", SYM(MAX_SIZE_SYM)},
+ { "MAX_UPDATES_PER_HOUR", SYM(MAX_UPDATES_PER_HOUR)},
+ { "MAX_USER_CONNECTIONS", SYM(MAX_USER_CONNECTIONS_SYM)},
+ { "MAXVALUE", SYM(MAX_VALUE_SYM)},
+ { "MEDIUM", SYM(MEDIUM_SYM)},
+ { "MEDIUMBLOB", SYM(MEDIUMBLOB)},
+ { "MEDIUMINT", SYM(MEDIUMINT)},
+ { "MEDIUMTEXT", SYM(MEDIUMTEXT)},
+ { "MEMORY", SYM(MEMORY_SYM)},
+ { "MERGE", SYM(MERGE_SYM)},
+ { "MESSAGE_TEXT", SYM(MESSAGE_TEXT_SYM)},
+ { "MICROSECOND", SYM(MICROSECOND_SYM)},
+ { "MIDDLEINT", SYM(MEDIUMINT)}, /* For powerbuilder */
+ { "MIGRATE", SYM(MIGRATE_SYM)},
+ { "MINUTE", SYM(MINUTE_SYM)},
+ { "MINUTE_MICROSECOND", SYM(MINUTE_MICROSECOND_SYM)},
+ { "MINUTE_SECOND", SYM(MINUTE_SECOND_SYM)},
+ { "MIN_ROWS", SYM(MIN_ROWS)},
+ { "MOD", SYM(MOD_SYM)},
+ { "MODE", SYM(MODE_SYM)},
+ { "MODIFIES", SYM(MODIFIES_SYM)},
+ { "MODIFY", SYM(MODIFY_SYM)},
+ { "MONTH", SYM(MONTH_SYM)},
+ { "MULTILINESTRING", SYM(MULTILINESTRING)},
+ { "MULTIPOINT", SYM(MULTIPOINT)},
+ { "MULTIPOLYGON", SYM(MULTIPOLYGON)},
+ { "MUTEX", SYM(MUTEX_SYM)},
+ { "MYSQL", SYM(MYSQL_SYM)},
+ { "MYSQL_ERRNO", SYM(MYSQL_ERRNO_SYM)},
+ { "NAME", SYM(NAME_SYM)},
+ { "NAMES", SYM(NAMES_SYM)},
+ { "NATIONAL", SYM(NATIONAL_SYM)},
+ { "NATURAL", SYM(NATURAL)},
+ { "NDB", SYM(NDBCLUSTER_SYM)},
+ { "NDBCLUSTER", SYM(NDBCLUSTER_SYM)},
+ { "NCHAR", SYM(NCHAR_SYM)},
+ { "NEW", SYM(NEW_SYM)},
+ { "NEXT", SYM(NEXT_SYM)},
+ { "NO", SYM(NO_SYM)},
+ { "NO_WAIT", SYM(NO_WAIT_SYM)},
+ { "NODEGROUP", SYM(NODEGROUP_SYM)},
+ { "NONE", SYM(NONE_SYM)},
+ { "NOT", SYM(NOT_SYM)},
+ { "NO_WRITE_TO_BINLOG", SYM(NO_WRITE_TO_BINLOG)},
+ { "NULL", SYM(NULL_SYM)},
+ { "NUMBER", SYM(NUMBER_SYM)},
+ { "NUMERIC", SYM(NUMERIC_SYM)},
+ { "NVARCHAR", SYM(NVARCHAR_SYM)},
+ { "OFFSET", SYM(OFFSET_SYM)},
+ { "OLD_PASSWORD", SYM(OLD_PASSWORD)},
+ { "ON", SYM(ON)},
+ { "ONE", SYM(ONE_SYM)},
+ { "ONLINE", SYM(ONLINE_SYM)},
+ { "ONLY", SYM(ONLY_SYM)},
+ { "OPEN", SYM(OPEN_SYM)},
+ { "OPTIMIZE", SYM(OPTIMIZE)},
+ { "OPTIONS", SYM(OPTIONS_SYM)},
+ { "OPTION", SYM(OPTION)},
+ { "OPTIONALLY", SYM(OPTIONALLY)},
+ { "OR", SYM(OR_SYM)},
+ { "ORDER", SYM(ORDER_SYM)},
+ { "OUT", SYM(OUT_SYM)},
+ { "OUTER", SYM(OUTER)},
+ { "OUTFILE", SYM(OUTFILE)},
+ { "OWNER", SYM(OWNER_SYM)},
+ { "PACK_KEYS", SYM(PACK_KEYS_SYM)},
+ { "PAGE", SYM(PAGE_SYM)},
+ { "PAGE_CHECKSUM", SYM(PAGE_CHECKSUM_SYM)},
+ { "PARSER", SYM(PARSER_SYM)},
+ { "PARSE_VCOL_EXPR", SYM(PARSE_VCOL_EXPR_SYM)},
+ { "PARTIAL", SYM(PARTIAL)},
+ { "PARTITION", SYM(PARTITION_SYM)},
+ { "PARTITIONING", SYM(PARTITIONING_SYM)},
+ { "PARTITIONS", SYM(PARTITIONS_SYM)},
+ { "PASSWORD", SYM(PASSWORD)},
+ { "PERSISTENT", SYM(PERSISTENT_SYM)},
+ { "PHASE", SYM(PHASE_SYM)},
+ { "PLUGIN", SYM(PLUGIN_SYM)},
+ { "PLUGINS", SYM(PLUGINS_SYM)},
+ { "POINT", SYM(POINT_SYM)},
+ { "POLYGON", SYM(POLYGON)},
+ { "PORT", SYM(PORT_SYM)},
+ { "PRECISION", SYM(PRECISION)},
+ { "PREPARE", SYM(PREPARE_SYM)},
+ { "PRESERVE", SYM(PRESERVE_SYM)},
+ { "PREV", SYM(PREV_SYM)},
+ { "PRIMARY", SYM(PRIMARY_SYM)},
+ { "PRIVILEGES", SYM(PRIVILEGES)},
+ { "PROCEDURE", SYM(PROCEDURE_SYM)},
+ { "PROCESS" , SYM(PROCESS)},
+ { "PROCESSLIST", SYM(PROCESSLIST_SYM)},
+ { "PROFILE", SYM(PROFILE_SYM)},
+ { "PROFILES", SYM(PROFILES_SYM)},
+ { "PROXY", SYM(PROXY_SYM)},
+ { "PURGE", SYM(PURGE)},
+ { "QUARTER", SYM(QUARTER_SYM)},
+ { "QUERY", SYM(QUERY_SYM)},
+ { "QUICK", SYM(QUICK)},
+ { "RANGE", SYM(RANGE_SYM)},
+ { "READ", SYM(READ_SYM)},
+ { "READ_ONLY", SYM(READ_ONLY_SYM)},
+ { "READ_WRITE", SYM(READ_WRITE_SYM)},
+ { "READS", SYM(READS_SYM)},
+ { "REAL", SYM(REAL)},
+ { "REBUILD", SYM(REBUILD_SYM)},
+ { "RECOVER", SYM(RECOVER_SYM)},
+ { "REDO_BUFFER_SIZE", SYM(REDO_BUFFER_SIZE_SYM)},
+ { "REDOFILE", SYM(REDOFILE_SYM)},
+ { "REDUNDANT", SYM(REDUNDANT_SYM)},
+ { "REFERENCES", SYM(REFERENCES)},
+ { "REGEXP", SYM(REGEXP)},
+ { "RELAY", SYM(RELAY)},
+ { "RELAYLOG", SYM(RELAYLOG_SYM)},
+ { "RELAY_LOG_FILE", SYM(RELAY_LOG_FILE_SYM)},
+ { "RELAY_LOG_POS", SYM(RELAY_LOG_POS_SYM)},
+ { "RELAY_THREAD", SYM(RELAY_THREAD)},
+ { "RELEASE", SYM(RELEASE_SYM)},
+ { "RELOAD", SYM(RELOAD)},
+ { "REMOVE", SYM(REMOVE_SYM)},
+ { "RENAME", SYM(RENAME)},
+ { "REORGANIZE", SYM(REORGANIZE_SYM)},
+ { "REPAIR", SYM(REPAIR)},
+ { "REPEATABLE", SYM(REPEATABLE_SYM)},
+ { "REPLACE", SYM(REPLACE)},
+ { "REPLICATION", SYM(REPLICATION)},
+ { "REPEAT", SYM(REPEAT_SYM)},
+ { "REQUIRE", SYM(REQUIRE_SYM)},
+ { "RESET", SYM(RESET_SYM)},
+ { "RESIGNAL", SYM(RESIGNAL_SYM)},
+ { "RESTORE", SYM(RESTORE_SYM)},
+ { "RESTRICT", SYM(RESTRICT)},
+ { "RESUME", SYM(RESUME_SYM)},
+ { "RETURNED_SQLSTATE",SYM(RETURNED_SQLSTATE_SYM)},
+ { "RETURN", SYM(RETURN_SYM)},
+ { "RETURNING", SYM(RETURNING_SYM)},
+ { "RETURNS", SYM(RETURNS_SYM)},
+ { "REVERSE", SYM(REVERSE_SYM)},
+ { "REVOKE", SYM(REVOKE)},
+ { "RIGHT", SYM(RIGHT)},
+ { "RLIKE", SYM(REGEXP)}, /* Like in mSQL2 */
+ { "ROLE", SYM(ROLE_SYM)},
+ { "ROLLBACK", SYM(ROLLBACK_SYM)},
+ { "ROLLUP", SYM(ROLLUP_SYM)},
+ { "ROUTINE", SYM(ROUTINE_SYM)},
+ { "ROW", SYM(ROW_SYM)},
+ { "ROW_COUNT", SYM(ROW_COUNT_SYM)},
+ { "ROWS", SYM(ROWS_SYM)},
+ { "ROW_FORMAT", SYM(ROW_FORMAT_SYM)},
+ { "RTREE", SYM(RTREE_SYM)},
+ { "SAVEPOINT", SYM(SAVEPOINT_SYM)},
+ { "SCHEDULE", SYM(SCHEDULE_SYM)},
+ { "SCHEMA", SYM(DATABASE)},
+ { "SCHEMA_NAME", SYM(SCHEMA_NAME_SYM)},
+ { "SCHEMAS", SYM(DATABASES)},
+ { "SECOND", SYM(SECOND_SYM)},
+ { "SECOND_MICROSECOND", SYM(SECOND_MICROSECOND_SYM)},
+ { "SECURITY", SYM(SECURITY_SYM)},
+ { "SELECT", SYM(SELECT_SYM)},
+ { "SENSITIVE", SYM(SENSITIVE_SYM)},
+ { "SEPARATOR", SYM(SEPARATOR_SYM)},
+ { "SERIAL", SYM(SERIAL_SYM)},
+ { "SERIALIZABLE", SYM(SERIALIZABLE_SYM)},
+ { "SESSION", SYM(SESSION_SYM)},
+ { "SERVER", SYM(SERVER_SYM)},
+ { "SET", SYM(SET)},
+ { "SHARE", SYM(SHARE_SYM)},
+ { "SHOW", SYM(SHOW)},
+ { "SHUTDOWN", SYM(SHUTDOWN)},
+ { "SIGNAL", SYM(SIGNAL_SYM)},
+ { "SIGNED", SYM(SIGNED_SYM)},
+ { "SIMPLE", SYM(SIMPLE_SYM)},
+ { "SLAVE", SYM(SLAVE)},
+ { "SLAVES", SYM(SLAVES)},
+ { "SLAVE_POS", SYM(SLAVE_POS_SYM)},
+ { "SLOW", SYM(SLOW)},
+ { "SNAPSHOT", SYM(SNAPSHOT_SYM)},
+ { "SMALLINT", SYM(SMALLINT)},
+ { "SOCKET", SYM(SOCKET_SYM)},
+ { "SOFT", SYM(SOFT_SYM)},
+ { "SOME", SYM(ANY_SYM)},
+ { "SONAME", SYM(SONAME_SYM)},
+ { "SOUNDS", SYM(SOUNDS_SYM)},
+ { "SOURCE", SYM(SOURCE_SYM)},
+ { "SPATIAL", SYM(SPATIAL_SYM)},
+ { "SPECIFIC", SYM(SPECIFIC_SYM)},
+ { "SQL", SYM(SQL_SYM)},
+ { "SQLEXCEPTION", SYM(SQLEXCEPTION_SYM)},
+ { "SQLSTATE", SYM(SQLSTATE_SYM)},
+ { "SQLWARNING", SYM(SQLWARNING_SYM)},
+ { "SQL_BIG_RESULT", SYM(SQL_BIG_RESULT)},
+ { "SQL_BUFFER_RESULT", SYM(SQL_BUFFER_RESULT)},
+ { "SQL_CACHE", SYM(SQL_CACHE_SYM)},
+ { "SQL_CALC_FOUND_ROWS", SYM(SQL_CALC_FOUND_ROWS)},
+ { "SQL_NO_CACHE", SYM(SQL_NO_CACHE_SYM)},
+ { "SQL_SMALL_RESULT", SYM(SQL_SMALL_RESULT)},
+ { "SQL_THREAD", SYM(SQL_THREAD)},
+ { "SQL_TSI_SECOND", SYM(SECOND_SYM)},
+ { "SQL_TSI_MINUTE", SYM(MINUTE_SYM)},
+ { "SQL_TSI_HOUR", SYM(HOUR_SYM)},
+ { "SQL_TSI_DAY", SYM(DAY_SYM)},
+ { "SQL_TSI_WEEK", SYM(WEEK_SYM)},
+ { "SQL_TSI_MONTH", SYM(MONTH_SYM)},
+ { "SQL_TSI_QUARTER", SYM(QUARTER_SYM)},
+ { "SQL_TSI_YEAR", SYM(YEAR_SYM)},
+ { "SSL", SYM(SSL_SYM)},
+ { "START", SYM(START_SYM)},
+ { "STARTING", SYM(STARTING)},
+ { "STARTS", SYM(STARTS_SYM)},
+ { "STATS_AUTO_RECALC",SYM(STATS_AUTO_RECALC_SYM)},
+ { "STATS_PERSISTENT", SYM(STATS_PERSISTENT_SYM)},
+ { "STATS_SAMPLE_PAGES",SYM(STATS_SAMPLE_PAGES_SYM)},
+ { "STATUS", SYM(STATUS_SYM)},
+ { "STOP", SYM(STOP_SYM)},
+ { "STORAGE", SYM(STORAGE_SYM)},
+ { "STRAIGHT_JOIN", SYM(STRAIGHT_JOIN)},
+ { "STRING", SYM(STRING_SYM)},
+ { "SUBCLASS_ORIGIN", SYM(SUBCLASS_ORIGIN_SYM)},
+ { "SUBJECT", SYM(SUBJECT_SYM)},
+ { "SUBPARTITION", SYM(SUBPARTITION_SYM)},
+ { "SUBPARTITIONS", SYM(SUBPARTITIONS_SYM)},
+ { "SUPER", SYM(SUPER_SYM)},
+ { "SUSPEND", SYM(SUSPEND_SYM)},
+ { "SWAPS", SYM(SWAPS_SYM)},
+ { "SWITCHES", SYM(SWITCHES_SYM)},
+ { "TABLE", SYM(TABLE_SYM)},
+ { "TABLE_NAME", SYM(TABLE_NAME_SYM)},
+ { "TABLES", SYM(TABLES)},
+ { "TABLESPACE", SYM(TABLESPACE)},
+ { "TABLE_STATISTICS", SYM(TABLE_STATS_SYM)},
+ { "TABLE_CHECKSUM", SYM(TABLE_CHECKSUM_SYM)},
+ { "TEMPORARY", SYM(TEMPORARY)},
+ { "TEMPTABLE", SYM(TEMPTABLE_SYM)},
+ { "TERMINATED", SYM(TERMINATED)},
+ { "TEXT", SYM(TEXT_SYM)},
+ { "THAN", SYM(THAN_SYM)},
+ { "THEN", SYM(THEN_SYM)},
+ { "TIME", SYM(TIME_SYM)},
+ { "TIMESTAMP", SYM(TIMESTAMP)},
+ { "TIMESTAMPADD", SYM(TIMESTAMP_ADD)},
+ { "TIMESTAMPDIFF", SYM(TIMESTAMP_DIFF)},
+ { "TINYBLOB", SYM(TINYBLOB)},
+ { "TINYINT", SYM(TINYINT)},
+ { "TINYTEXT", SYM(TINYTEXT)},
+ { "TO", SYM(TO_SYM)},
+ { "TRAILING", SYM(TRAILING)},
+ { "TRANSACTION", SYM(TRANSACTION_SYM)},
+ { "TRANSACTIONAL", SYM(TRANSACTIONAL_SYM)},
+ { "TRIGGER", SYM(TRIGGER_SYM)},
+ { "TRIGGERS", SYM(TRIGGERS_SYM)},
+ { "TRUE", SYM(TRUE_SYM)},
+ { "TRUNCATE", SYM(TRUNCATE_SYM)},
+ { "TYPE", SYM(TYPE_SYM)},
+ { "TYPES", SYM(TYPES_SYM)},
+ { "UNCOMMITTED", SYM(UNCOMMITTED_SYM)},
+ { "UNDEFINED", SYM(UNDEFINED_SYM)},
+ { "UNDO_BUFFER_SIZE", SYM(UNDO_BUFFER_SIZE_SYM)},
+ { "UNDOFILE", SYM(UNDOFILE_SYM)},
+ { "UNDO", SYM(UNDO_SYM)},
+ { "UNICODE", SYM(UNICODE_SYM)},
+ { "UNION", SYM(UNION_SYM)},
+ { "UNIQUE", SYM(UNIQUE_SYM)},
+ { "UNKNOWN", SYM(UNKNOWN_SYM)},
+ { "UNLOCK", SYM(UNLOCK_SYM)},
+ { "UNINSTALL", SYM(UNINSTALL_SYM)},
+ { "UNSIGNED", SYM(UNSIGNED)},
+ { "UNTIL", SYM(UNTIL_SYM)},
+ { "UPDATE", SYM(UPDATE_SYM)},
+ { "UPGRADE", SYM(UPGRADE_SYM)},
+ { "USAGE", SYM(USAGE)},
+ { "USE", SYM(USE_SYM)},
+ { "USER", SYM(USER)},
+ { "USER_RESOURCES", SYM(RESOURCES)},
+ { "USER_STATISTICS", SYM(USER_STATS_SYM)},
+ { "USE_FRM", SYM(USE_FRM)},
+ { "USING", SYM(USING)},
+ { "UTC_DATE", SYM(UTC_DATE_SYM)},
+ { "UTC_TIME", SYM(UTC_TIME_SYM)},
+ { "UTC_TIMESTAMP", SYM(UTC_TIMESTAMP_SYM)},
+ { "VALUE", SYM(VALUE_SYM)},
+ { "VALUES", SYM(VALUES)},
+ { "VARBINARY", SYM(VARBINARY)},
+ { "VARCHAR", SYM(VARCHAR)},
+ { "VARCHARACTER", SYM(VARCHAR)},
+ { "VARIABLES", SYM(VARIABLES)},
+ { "VARYING", SYM(VARYING)},
+ { "VIA", SYM(VIA_SYM)},
+ { "VIEW", SYM(VIEW_SYM)},
+ { "VIRTUAL", SYM(VIRTUAL_SYM)},
+ { "WAIT", SYM(WAIT_SYM)},
+ { "WARNINGS", SYM(WARNINGS)},
+ { "WEEK", SYM(WEEK_SYM)},
+ { "WEIGHT_STRING", SYM(WEIGHT_STRING_SYM)},
+ { "WHEN", SYM(WHEN_SYM)},
+ { "WHERE", SYM(WHERE)},
+ { "WHILE", SYM(WHILE_SYM)},
+ { "WITH", SYM(WITH)},
+ { "WORK", SYM(WORK_SYM)},
+ { "WRAPPER", SYM(WRAPPER_SYM)},
+ { "WRITE", SYM(WRITE_SYM)},
+ { "X509", SYM(X509_SYM)},
+ { "XOR", SYM(XOR)},
+ { "XA", SYM(XA_SYM)},
+ { "XML", SYM(XML_SYM)}, /* LOAD XML Arnold/Erik */
+ { "YEAR", SYM(YEAR_SYM)},
+ { "YEAR_MONTH", SYM(YEAR_MONTH_SYM)},
+ { "ZEROFILL", SYM(ZEROFILL)},
+ { "||", SYM(OR_OR_SYM)}
+};
+
+
+static SYMBOL sql_functions[] = {
+ { "ADDDATE", SYM(ADDDATE_SYM)},
+ { "BIT_AND", SYM(BIT_AND)},
+ { "BIT_OR", SYM(BIT_OR)},
+ { "BIT_XOR", SYM(BIT_XOR)},
+ { "CAST", SYM(CAST_SYM)},
+ { "COUNT", SYM(COUNT_SYM)},
+ { "CURDATE", SYM(CURDATE)},
+ { "CURTIME", SYM(CURTIME)},
+ { "DATE_ADD", SYM(DATE_ADD_INTERVAL)},
+ { "DATE_SUB", SYM(DATE_SUB_INTERVAL)},
+ { "EXTRACT", SYM(EXTRACT_SYM)},
+ { "GROUP_CONCAT", SYM(GROUP_CONCAT_SYM)},
+ { "MAX", SYM(MAX_SYM)},
+ { "MID", SYM(SUBSTRING)}, /* unireg function */
+ { "MIN", SYM(MIN_SYM)},
+ { "NOW", SYM(NOW_SYM)},
+ { "POSITION", SYM(POSITION_SYM)},
+ { "SESSION_USER", SYM(USER)},
+ { "STD", SYM(STD_SYM)},
+ { "STDDEV", SYM(STD_SYM)},
+ { "STDDEV_POP", SYM(STD_SYM)},
+ { "STDDEV_SAMP", SYM(STDDEV_SAMP_SYM)},
+ { "SUBDATE", SYM(SUBDATE_SYM)},
+ { "SUBSTR", SYM(SUBSTRING)},
+ { "SUBSTRING", SYM(SUBSTRING)},
+ { "SUM", SYM(SUM_SYM)},
+ { "SYSDATE", SYM(SYSDATE)},
+ { "SYSTEM_USER", SYM(USER)},
+ { "TRIM", SYM(TRIM)},
+ { "VARIANCE", SYM(VARIANCE_SYM)},
+ { "VAR_POP", SYM(VARIANCE_SYM)},
+ { "VAR_SAMP", SYM(VAR_SAMP_SYM)},
+};
+
+#endif /* LEX_INCLUDED */
diff --git a/sql/lex_symbol.h b/sql/lex_symbol.h
new file mode 100644
index 00000000000..d48ca57df85
--- /dev/null
+++ b/sql/lex_symbol.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2000, 2001, 2004, 2006, 2007 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* This struct includes all reserved words and functions */
+
+#ifndef _lex_symbol_h
+#define _lex_symbol_h
+
+struct st_sym_group;
+
+typedef struct st_symbol {
+ const char *name;
+ uint tok;
+ uint length;
+ struct st_sym_group *group;
+} SYMBOL;
+
+typedef struct st_lex_symbol
+{
+ SYMBOL *symbol;
+ char *str;
+ uint length;
+} LEX_SYMBOL;
+
+typedef struct st_sym_group {
+ const char *name;
+ const char *needed_define;
+} SYM_GROUP;
+
+extern SYM_GROUP sym_group_common;
+extern SYM_GROUP sym_group_geom;
+extern SYM_GROUP sym_group_rtree;
+
+#endif /* _lex_symbol_h */
diff --git a/sql/lock.cc b/sql/lock.cc
new file mode 100644
index 00000000000..e713990bd58
--- /dev/null
+++ b/sql/lock.cc
@@ -0,0 +1,1120 @@
+/*
+ Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+
+/**
+ @file
+
+ Locking functions for mysql.
+
+ Because of the new concurrent inserts, we must first get external locks
+ before getting internal locks. If we do it in the other order, the status
+ information is not up to date when called from the lock handler.
+
+ GENERAL DESCRIPTION OF LOCKING
+
+ When not using LOCK TABLES:
+
+ - For each SQL statement mysql_lock_tables() is called for all involved
+ tables.
+ - mysql_lock_tables() will call
+ table_handler->external_lock(thd,locktype) for each table.
+ This is followed by a call to thr_multi_lock() for all tables.
+
+ - When statement is done, we call mysql_unlock_tables().
+ table_handler->external_lock(thd, F_UNLCK) followed by
+ thr_multi_unlock() for each table.
+
+ - Note that mysql_unlock_tables() may be called several times as
+ MySQL in some cases can free some tables earlier than others.
+
+ - The above is true both for normal and temporary tables.
+
+ - Temporary non transactional tables are never passed to thr_multi_lock()
+ and we never call external_lock(thd, F_UNLOCK) on these.
+
+ When using LOCK TABLES:
+
+ - LOCK TABLE will call mysql_lock_tables() for all tables.
+ mysql_lock_tables() will call
+ table_handler->external_lock(thd,locktype) for each table.
+ This is followed by a call to thr_multi_lock() for all tables.
+
+ - For each statement, we will call table_handler->start_stmt(THD)
+ to inform the table handler that we are using the table.
+
+ The tables used can only be tables used in LOCK TABLES or a
+ temporary table.
+
+ - When statement is done, we will call ha_commit_stmt(thd);
+
+ - When calling UNLOCK TABLES we call mysql_unlock_tables() for all
+ tables used in LOCK TABLES
+
+ If table_handler->external_lock(thd, locktype) fails, we call
+ table_handler->external_lock(thd, F_UNLCK) for each table that was locked,
+ excluding one that caused failure. That means handler must cleanup itself
+ in case external_lock() fails.
+
+ @todo
+ Change to use my_malloc() ONLY when using LOCK TABLES command or when
+ we are forced to use mysql_lock_merge.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "debug_sync.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "lock.h"
+#include "sql_base.h" // close_tables_for_reopen
+#include "sql_parse.h" // is_log_table_write_query
+#include "sql_acl.h" // SUPER_ACL
+#include <hash.h>
+
+/**
+ @defgroup Locking Locking
+ @{
+*/
+
+extern HASH open_cache;
+
+static int lock_external(THD *thd, TABLE **table,uint count);
+static int unlock_external(THD *thd, TABLE **table,uint count);
+static void print_lock_error(int error, TABLE *);
+
+/* Map the return value of thr_lock to an error from errmsg.txt */
+static int thr_lock_errno_to_mysql[]=
+{ 0, ER_LOCK_ABORTED, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK };
+
+/**
+ Perform semantic checks for mysql_lock_tables.
+ @param thd The current thread
+ @param tables The tables to lock
+ @param count The number of tables to lock
+ @param flags Lock flags
+ @return 0 if all the check passed, non zero if a check failed.
+*/
+
+static int
+lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags)
+{
+ uint system_count, i;
+ bool is_superuser, log_table_write_query;
+
+ DBUG_ENTER("lock_tables_check");
+
+ system_count= 0;
+ is_superuser= thd->security_ctx->master_access & SUPER_ACL;
+ log_table_write_query= (is_log_table_write_query(thd->lex->sql_command)
+ || ((flags & MYSQL_LOCK_LOG_TABLE) != 0));
+
+ for (i=0 ; i<count; i++)
+ {
+ TABLE *t= tables[i];
+
+ /* Protect against 'fake' partially initialized TABLE_SHARE */
+ DBUG_ASSERT(t->s->table_category != TABLE_UNKNOWN_CATEGORY);
+
+ /*
+ Table I/O to performance schema tables is performed
+ only internally by the server implementation.
+ When a user is requesting a lock, the following
+ constraints are enforced:
+ */
+ if (t->s->require_write_privileges() &&
+ ! log_table_write_query)
+ {
+ /*
+ A user should not be able to prevent writes,
+ or hold any type of lock in a session,
+ since this would be a DOS attack.
+ */
+ if ((t->reginfo.lock_type >= TL_READ_NO_INSERT)
+ || (thd->lex->sql_command == SQLCOM_LOCK_TABLES))
+ {
+ my_error(ER_CANT_LOCK_LOG_TABLE, MYF(0));
+ DBUG_RETURN(1);
+ }
+ }
+
+ if (t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ if (t->s->table_category == TABLE_CATEGORY_SYSTEM)
+ system_count++;
+
+ if (t->db_stat & HA_READ_ONLY)
+ {
+ my_error(ER_OPEN_AS_READONLY, MYF(0), t->alias.c_ptr_safe());
+ DBUG_RETURN(1);
+ }
+ }
+
+ /*
+ If we are going to lock a non-temporary table we must own metadata
+ lock of appropriate type on it (I.e. for table to be locked for
+ write we must own metadata lock of MDL_SHARED_WRITE or stronger
+ type. For table to be locked for read we must own metadata lock
+ of MDL_SHARED_READ or stronger type).
+ The only exception are HANDLER statements which are allowed to
+ lock table for read while having only MDL_SHARED lock on it.
+ */
+ DBUG_ASSERT(t->s->tmp_table ||
+ thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ t->s->db.str, t->s->table_name.str,
+ t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ) ||
+ (t->open_by_handler &&
+ thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ t->s->db.str, t->s->table_name.str,
+ MDL_SHARED)));
+
+ /*
+ Prevent modifications to base tables if READ_ONLY is activated.
+ In any case, read only does not apply to temporary tables.
+ */
+ if (!(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY) && !t->s->tmp_table)
+ {
+ if (t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE &&
+ !is_superuser && opt_readonly && !thd->slave_thread)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
+ DBUG_RETURN(1);
+ }
+ }
+ }
+
+ /*
+ Locking of system tables is restricted:
+ locking a mix of system and non-system tables in the same lock
+ is prohibited, to prevent contention.
+ */
+ if ((system_count > 0) && (system_count < count))
+ {
+ my_error(ER_WRONG_LOCK_OF_SYSTEM_TABLE, MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+/**
+ Reset lock type in lock data
+
+ @param mysql_lock Lock structures to reset.
+ @param unlock If set, then set lock type to TL_UNLOCK,
+ otherwise set to original lock type from
+ get_store_lock().
+
+ @note After a locking error we want to quit the locking of the table(s).
+ The test case in the bug report for Bug #18544 has the following
+ cases: 1. Locking error in lock_external() due to InnoDB timeout.
+ 2. Locking error in get_lock_data() due to missing write permission.
+ 3. Locking error in wait_if_global_read_lock() due to lock conflict.
+
+ @note In all these cases we have already set the lock type into the lock
+ data of the open table(s). If the table(s) are in the open table
+ cache, they could be reused with the non-zero lock type set. This
+ could lead to ignoring a different lock type with the next lock.
+
+ @note Clear the lock type of all lock data. This ensures that the next
+ lock request will set its lock type properly.
+*/
+
+
+void reset_lock_data(MYSQL_LOCK *sql_lock, bool unlock)
+{
+ THR_LOCK_DATA **ldata, **ldata_end;
+ DBUG_ENTER("reset_lock_data");
+
+ /* Clear the lock type of all lock data to avoid reusage. */
+ for (ldata= sql_lock->locks, ldata_end= ldata + sql_lock->lock_count;
+ ldata < ldata_end;
+ ldata++)
+ (*ldata)->type= unlock ? TL_UNLOCK : (*ldata)->org_type;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Lock tables.
+
+ @param thd The current thread.
+ @param tables An array of pointers to the tables to lock.
+ @param count The number of tables to lock.
+ @param flags Options:
+ MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY
+ MYSQL_LOCK_IGNORE_TIMEOUT Use maximum timeout value.
+
+ @retval A lock structure pointer on success.
+ @retval NULL if an error or if wait on a lock was killed.
+*/
+
+MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags)
+{
+ MYSQL_LOCK *sql_lock;
+ DBUG_ENTER("mysql_lock_tables(tables)");
+
+ if (lock_tables_check(thd, tables, count, flags))
+ DBUG_RETURN(NULL);
+
+ if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS)))
+ DBUG_RETURN(NULL);
+
+ if (mysql_lock_tables(thd, sql_lock, flags))
+ {
+ /* Clear the lock type of all lock data to avoid reusage. */
+ reset_lock_data(sql_lock, 1);
+ my_free(sql_lock);
+ sql_lock= 0;
+ }
+ DBUG_RETURN(sql_lock);
+}
+
+/**
+ Lock tables based on a MYSQL_LOCK structure.
+
+ mysql_lock_tables()
+
+ @param thd The current thread.
+ @param sql_lock Tables that should be locked
+ @param flags See mysql_lock_tables() above
+
+ @return 0 ok
+ @return 1 error
+*/
+
+bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, uint flags)
+{
+ int rc= 1;
+ ulong timeout= (flags & MYSQL_LOCK_IGNORE_TIMEOUT) ?
+ LONG_TIMEOUT : thd->variables.lock_wait_timeout;
+ PSI_stage_info org_stage;
+ DBUG_ENTER("mysql_lock_tables(sql_lock)");
+
+ thd->enter_stage(&stage_system_lock, &org_stage, __func__, __FILE__,
+ __LINE__);
+ if (sql_lock->table_count && lock_external(thd, sql_lock->table,
+ sql_lock->table_count))
+ goto end;
+
+ THD_STAGE_INFO(thd, stage_table_lock);
+
+ /* Copy the lock data array. thr_multi_lock() reorders its contents. */
+ memmove(sql_lock->locks + sql_lock->lock_count, sql_lock->locks,
+ sql_lock->lock_count * sizeof(*sql_lock->locks));
+ /* Lock on the copied half of the lock data array. */
+ rc= thr_lock_errno_to_mysql[(int) thr_multi_lock(sql_lock->locks +
+ sql_lock->lock_count,
+ sql_lock->lock_count,
+ &thd->lock_info, timeout)];
+ if (rc && sql_lock->table_count)
+ (void) unlock_external(thd, sql_lock->table, sql_lock->table_count);
+
+end:
+ THD_STAGE_INFO(thd, org_stage);
+
+ if (thd->killed)
+ {
+ thd->send_kill_message();
+ if (!rc)
+ mysql_unlock_tables(thd, sql_lock, 0);
+ rc= 1;
+ }
+ else if (rc > 1)
+ my_error(rc, MYF(0));
+
+ thd->set_time_after_lock();
+ DBUG_RETURN(rc);
+}
+
+
+static int lock_external(THD *thd, TABLE **tables, uint count)
+{
+ reg1 uint i;
+ int lock_type,error;
+ DBUG_ENTER("lock_external");
+
+ DBUG_PRINT("info", ("count %d", count));
+ for (i=1 ; i <= count ; i++, tables++)
+ {
+ DBUG_ASSERT((*tables)->reginfo.lock_type >= TL_READ);
+ lock_type=F_WRLCK; /* Lock exclusive */
+ if ((*tables)->db_stat & HA_READ_ONLY ||
+ ((*tables)->reginfo.lock_type >= TL_READ &&
+ (*tables)->reginfo.lock_type <= TL_READ_NO_INSERT))
+ lock_type=F_RDLCK;
+
+ if ((error=(*tables)->file->ha_external_lock(thd,lock_type)))
+ {
+ print_lock_error(error, *tables);
+ while (--i)
+ {
+ tables--;
+ (*tables)->file->ha_external_lock(thd, F_UNLCK);
+ (*tables)->current_lock=F_UNLCK;
+ }
+ DBUG_RETURN(error);
+ }
+ else
+ {
+ (*tables)->db_stat &= ~ HA_BLOCK_LOCK;
+ (*tables)->current_lock= lock_type;
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock, bool free_lock)
+{
+ DBUG_ENTER("mysql_unlock_tables");
+ if (sql_lock->table_count)
+ unlock_external(thd, sql_lock->table, sql_lock->table_count);
+ if (sql_lock->lock_count)
+ thr_multi_unlock(sql_lock->locks, sql_lock->lock_count, 0);
+ if (free_lock)
+ my_free(sql_lock);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Unlock some of the tables locked by mysql_lock_tables.
+
+ This will work even if get_lock_data fails (next unlock will free all)
+*/
+
+void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count)
+{
+ MYSQL_LOCK *sql_lock;
+ if ((sql_lock= get_lock_data(thd, table, count, GET_LOCK_UNLOCK)))
+ mysql_unlock_tables(thd, sql_lock, 1);
+}
+
+
+/**
+ unlock all tables locked for read.
+*/
+
+void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock)
+{
+ uint i,found;
+ DBUG_ENTER("mysql_unlock_read_tables");
+
+ /* Call external lock for all tables to be unlocked */
+
+ /* Move all write locked tables first */
+ TABLE **table=sql_lock->table;
+ for (i=found=0 ; i < sql_lock->table_count ; i++)
+ {
+ DBUG_ASSERT(sql_lock->table[i]->lock_position == i);
+ if ((uint) sql_lock->table[i]->reginfo.lock_type > TL_WRITE_ALLOW_WRITE)
+ {
+ swap_variables(TABLE *, *table, sql_lock->table[i]);
+ table++;
+ found++;
+ }
+ }
+ /* Unlock all read locked tables */
+ if (i != found)
+ {
+ (void) unlock_external(thd,table,i-found);
+ sql_lock->table_count=found;
+ }
+
+ /* Call thr_unlock() for all tables to be unlocked */
+
+ /* Move all write locks first */
+ THR_LOCK_DATA **lock=sql_lock->locks;
+ for (i=found=0 ; i < sql_lock->lock_count ; i++)
+ {
+ if (sql_lock->locks[i]->type >= TL_WRITE_ALLOW_WRITE)
+ {
+ swap_variables(THR_LOCK_DATA *, *lock, sql_lock->locks[i]);
+ lock++;
+ found++;
+ }
+ }
+ /* unlock the read locked tables */
+ if (i != found)
+ {
+ thr_multi_unlock(lock, i-found, 0);
+ sql_lock->lock_count= found;
+ }
+
+ /* Fix the lock positions in TABLE */
+ table= sql_lock->table;
+ found= 0;
+ for (i= 0; i < sql_lock->table_count; i++)
+ {
+ TABLE *tbl= *table;
+ tbl->lock_position= (uint) (table - sql_lock->table);
+ tbl->lock_data_start= found;
+ found+= tbl->lock_count;
+ table++;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Try to find the table in the list of locked tables.
+ In case of success, unlock the table and remove it from this list.
+ If a table has more than one lock instance, removes them all.
+
+ @param thd thread context
+ @param locked list of locked tables
+ @param table the table to unlock
+*/
+
+void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table)
+{
+ if (locked)
+ {
+ reg1 uint i;
+ for (i=0; i < locked->table_count; i++)
+ {
+ if (locked->table[i] == table)
+ {
+ uint j, removed_locks, old_tables;
+ TABLE *tbl;
+ uint lock_data_end;
+
+ DBUG_ASSERT(table->lock_position == i);
+
+ /* Unlock the table. */
+ mysql_unlock_some_tables(thd, &table, /* table count */ 1);
+
+ /* Decrement table_count in advance, making below expressions easier */
+ old_tables= --locked->table_count;
+
+ /* The table has 'removed_locks' lock data elements in locked->locks */
+ removed_locks= table->lock_count;
+
+ /* Move down all table pointers above 'i'. */
+ bmove((char*) (locked->table+i),
+ (char*) (locked->table+i+1),
+ (old_tables - i) * sizeof(TABLE*));
+
+ lock_data_end= table->lock_data_start + table->lock_count;
+ /* Move down all lock data pointers above 'table->lock_data_end-1' */
+ bmove((char*) (locked->locks + table->lock_data_start),
+ (char*) (locked->locks + lock_data_end),
+ (locked->lock_count - lock_data_end) *
+ sizeof(THR_LOCK_DATA*));
+
+ /*
+ Fix moved table elements.
+ lock_position is the index in the 'locked->table' array,
+ it must be fixed by one.
+ table->lock_data_start is pointer to the lock data for this table
+ in the 'locked->locks' array, they must be fixed by 'removed_locks',
+ the lock data count of the removed table.
+ */
+ for (j= i ; j < old_tables; j++)
+ {
+ tbl= locked->table[j];
+ tbl->lock_position--;
+ DBUG_ASSERT(tbl->lock_position == j);
+ tbl->lock_data_start-= removed_locks;
+ }
+
+ /* Finally adjust lock_count. */
+ locked->lock_count-= removed_locks;
+ break;
+ }
+ }
+ }
+}
+
+
+/** Abort all other threads waiting to get lock in table. */
+
+void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock)
+{
+ MYSQL_LOCK *locked;
+ DBUG_ENTER("mysql_lock_abort");
+
+ if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK)))
+ {
+ for (uint i=0; i < locked->lock_count; i++)
+ thr_abort_locks(locked->locks[i]->lock, upgrade_lock);
+ my_free(locked);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Abort one thread / table combination.
+
+ @param thd Thread handler
+ @param table Table that should be removed from lock queue
+
+ @retval
+ 0 Table was not locked by another thread
+ @retval
+ 1 Table was locked by at least one other thread
+*/
+
+bool mysql_lock_abort_for_thread(THD *thd, TABLE *table)
+{
+ MYSQL_LOCK *locked;
+ bool result= FALSE;
+ DBUG_ENTER("mysql_lock_abort_for_thread");
+
+ if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK)))
+ {
+ for (uint i=0; i < locked->lock_count; i++)
+ {
+ if (thr_abort_locks_for_thread(locked->locks[i]->lock,
+ table->in_use->thread_id))
+ result= TRUE;
+ }
+ my_free(locked);
+ }
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Merge two thr_lock:s
+ mysql_lock_merge()
+
+ @param a Original locks
+ @param b New locks
+
+ @retval New lock structure that contains a and b
+
+ @note
+ a and b are freed with my_free()
+*/
+
+MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b)
+{
+ MYSQL_LOCK *sql_lock;
+ TABLE **table, **end_table;
+ DBUG_ENTER("mysql_lock_merge");
+ DBUG_PRINT("enter", ("a->lock_count: %u b->lock_count: %u",
+ a->lock_count, b->lock_count));
+
+ if (!(sql_lock= (MYSQL_LOCK*)
+ my_malloc(sizeof(*sql_lock)+
+ sizeof(THR_LOCK_DATA*)*((a->lock_count+b->lock_count)*2) +
+ sizeof(TABLE*)*(a->table_count+b->table_count),MYF(MY_WME))))
+ DBUG_RETURN(0); // Fatal error
+ sql_lock->lock_count=a->lock_count+b->lock_count;
+ sql_lock->table_count=a->table_count+b->table_count;
+ sql_lock->locks=(THR_LOCK_DATA**) (sql_lock+1);
+ sql_lock->table=(TABLE**) (sql_lock->locks+sql_lock->lock_count*2);
+ memcpy(sql_lock->locks,a->locks,a->lock_count*sizeof(*a->locks));
+ memcpy(sql_lock->locks+a->lock_count,b->locks,
+ b->lock_count*sizeof(*b->locks));
+ memcpy(sql_lock->table,a->table,a->table_count*sizeof(*a->table));
+ memcpy(sql_lock->table+a->table_count,b->table,
+ b->table_count*sizeof(*b->table));
+
+ /*
+ Now adjust lock_position and lock_data_start for all objects that was
+ moved in 'b' (as there is now all objects in 'a' before these).
+ */
+ for (table= sql_lock->table + a->table_count,
+ end_table= table + b->table_count;
+ table < end_table;
+ table++)
+ {
+ (*table)->lock_position+= a->table_count;
+ (*table)->lock_data_start+= a->lock_count;
+ }
+
+ /*
+ Ensure that locks of the same tables share same data structures if we
+ reopen a table that is already open. This can happen for example with
+ MERGE tables.
+ */
+
+ /* Copy the lock data array. thr_merge_lock() reorders its content */
+ memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks,
+ sql_lock->lock_count * sizeof(*sql_lock->locks));
+ thr_merge_locks(sql_lock->locks + sql_lock->lock_count,
+ a->lock_count, b->lock_count);
+
+ /* Delete old, not needed locks */
+ my_free(a);
+ my_free(b);
+ DBUG_RETURN(sql_lock);
+}
+
+
+/** Unlock a set of external. */
+
+static int unlock_external(THD *thd, TABLE **table,uint count)
+{
+ int error,error_code;
+ DBUG_ENTER("unlock_external");
+
+ error_code=0;
+ do
+ {
+ if ((*table)->current_lock != F_UNLCK)
+ {
+ (*table)->current_lock = F_UNLCK;
+ if ((error=(*table)->file->ha_external_lock(thd, F_UNLCK)))
+ {
+ error_code=error;
+ print_lock_error(error_code, *table);
+ }
+ }
+ table++;
+ } while (--count);
+ DBUG_RETURN(error_code);
+}
+
+
+/**
+ Get lock structures from table structs and initialize locks.
+
+ @param thd Thread handler
+ @param table_ptr Pointer to tables that should be locks
+ @param flags One of:
+ - GET_LOCK_UNLOCK : If we should send TL_IGNORE to store lock
+ - GET_LOCK_STORE_LOCKS : Store lock info in TABLE
+*/
+
+MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags)
+{
+ uint i,lock_count,table_count;
+ MYSQL_LOCK *sql_lock;
+ THR_LOCK_DATA **locks, **locks_buf;
+ TABLE **to, **table_buf;
+ DBUG_ENTER("get_lock_data");
+
+ DBUG_ASSERT((flags == GET_LOCK_UNLOCK) || (flags == GET_LOCK_STORE_LOCKS));
+ DBUG_PRINT("info", ("count %d", count));
+
+ for (i=lock_count=table_count=0 ; i < count ; i++)
+ {
+ TABLE *t= table_ptr[i];
+
+ if (t->s->tmp_table != NON_TRANSACTIONAL_TMP_TABLE &&
+ t->s->tmp_table != INTERNAL_TMP_TABLE)
+ {
+ lock_count+= t->file->lock_count();
+ table_count++;
+ }
+ }
+
+ /*
+ Allocating twice the number of pointers for lock data for use in
+ thr_multi_lock(). This function reorders the lock data, but cannot
+ update the table values. So the second part of the array is copied
+ from the first part immediately before calling thr_multi_lock().
+ */
+ if (!(sql_lock= (MYSQL_LOCK*)
+ my_malloc(sizeof(*sql_lock) +
+ sizeof(THR_LOCK_DATA*) * lock_count * 2 +
+ sizeof(table_ptr) * table_count,
+ MYF(0))))
+ DBUG_RETURN(0);
+ locks= locks_buf= sql_lock->locks= (THR_LOCK_DATA**) (sql_lock + 1);
+ to= table_buf= sql_lock->table= (TABLE**) (locks + lock_count * 2);
+ sql_lock->table_count= table_count;
+
+ for (i=0 ; i < count ; i++)
+ {
+ TABLE *table;
+ enum thr_lock_type lock_type;
+ THR_LOCK_DATA **locks_start;
+ table= table_ptr[i];
+ if (table->s->tmp_table == NON_TRANSACTIONAL_TMP_TABLE ||
+ table->s->tmp_table == INTERNAL_TMP_TABLE)
+ continue;
+ lock_type= table->reginfo.lock_type;
+ DBUG_ASSERT(lock_type != TL_WRITE_DEFAULT && lock_type != TL_READ_DEFAULT);
+ locks_start= locks;
+ locks= table->file->store_lock(thd, locks,
+ (flags & GET_LOCK_UNLOCK) ? TL_IGNORE :
+ lock_type);
+ if (flags & GET_LOCK_STORE_LOCKS)
+ {
+ table->lock_position= (uint) (to - table_buf);
+ table->lock_data_start= (uint) (locks_start - locks_buf);
+ table->lock_count= (uint) (locks - locks_start);
+ }
+ *to++= table;
+ if (locks)
+ {
+ for ( ; locks_start != locks ; locks_start++)
+ {
+ (*locks_start)->debug_print_param= (void *) table;
+ (*locks_start)->m_psi= table->file->m_psi;
+ (*locks_start)->lock->name= table->alias.c_ptr();
+ (*locks_start)->org_type= (*locks_start)->type;
+ }
+ }
+ }
+ /*
+ We do not use 'lock_count', because there are cases where store_lock()
+ returns less locks than lock_count() claimed. This can happen when
+ a FLUSH TABLES tries to abort locks from a MERGE table of another
+ thread. When that thread has just opened the table, but not yet
+ attached its children, it cannot return the locks. lock_count()
+ always returns the number of locks that an attached table has.
+ This is done to avoid the reverse situation: If lock_count() would
+ return 0 for a non-attached MERGE table, and that table becomes
+ attached between the calls to lock_count() and store_lock(), then
+ we would have allocated too little memory for the lock data. Now
+ we may allocate too much, but better safe than memory overrun.
+ And in the FLUSH case, the memory is released quickly anyway.
+ */
+ sql_lock->lock_count= locks - locks_buf;
+ DBUG_ASSERT(sql_lock->lock_count <= lock_count);
+ DBUG_PRINT("info", ("sql_lock->table_count %d sql_lock->lock_count %d",
+ sql_lock->table_count, sql_lock->lock_count));
+ DBUG_RETURN(sql_lock);
+}
+
+
+/**
+ Obtain an exclusive metadata lock on a schema name.
+
+ @param thd Thread handle.
+ @param db The database name.
+
+ To avoid deadlocks, we do not try to obtain exclusive metadata
+ locks in LOCK TABLES mode, since in this mode there may be
+ other metadata locks already taken by the current connection,
+ and we must not wait for MDL locks while holding locks.
+
+ @retval FALSE Success.
+ @retval TRUE Failure: we're in LOCK TABLES mode, or out of memory,
+ or this connection was killed.
+*/
+
+bool lock_schema_name(THD *thd, const char *db)
+{
+ MDL_request_list mdl_requests;
+ MDL_request global_request;
+ MDL_request mdl_request;
+
+ if (thd->locked_tables_mode)
+ {
+ my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
+ ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
+ return TRUE;
+ }
+
+ if (thd->global_read_lock.can_acquire_protection())
+ return TRUE;
+ global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_STATEMENT);
+ mdl_request.init(MDL_key::SCHEMA, db, "", MDL_EXCLUSIVE, MDL_TRANSACTION);
+
+ mdl_requests.push_front(&mdl_request);
+ mdl_requests.push_front(&global_request);
+
+ if (thd->mdl_context.acquire_locks(&mdl_requests,
+ thd->variables.lock_wait_timeout))
+ return TRUE;
+
+ DEBUG_SYNC(thd, "after_wait_locked_schema_name");
+ return FALSE;
+}
+
+
+/**
+ Obtain an exclusive metadata lock on an object name.
+
+ @param thd Thread handle.
+ @param mdl_type Object type (currently functions, procedures
+ and events can be name-locked).
+ @param db The schema the object belongs to.
+ @param name Object name in the schema.
+
+ This function assumes that no metadata locks were acquired
+ before calling it. It is enforced by asserts in MDL_context::acquire_locks().
+ To avoid deadlocks, we do not try to obtain exclusive metadata
+ locks in LOCK TABLES mode, since in this mode there may be
+ other metadata locks already taken by the current connection,
+ and we must not wait for MDL locks while holding locks.
+
+ @retval FALSE Success.
+ @retval TRUE Failure: we're in LOCK TABLES mode, or out of memory,
+ or this connection was killed.
+*/
+
+bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type,
+ const char *db, const char *name)
+{
+ MDL_request_list mdl_requests;
+ MDL_request global_request;
+ MDL_request schema_request;
+ MDL_request mdl_request;
+
+ DBUG_ASSERT(ok_for_lower_case_names(db));
+
+ if (thd->locked_tables_mode)
+ {
+ my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
+ ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
+ return TRUE;
+ }
+
+ DBUG_ASSERT(name);
+ DEBUG_SYNC(thd, "before_wait_locked_pname");
+
+ if (thd->global_read_lock.can_acquire_protection())
+ return TRUE;
+ global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_STATEMENT);
+ schema_request.init(MDL_key::SCHEMA, db, "", MDL_INTENTION_EXCLUSIVE,
+ MDL_TRANSACTION);
+ mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE, MDL_TRANSACTION);
+
+ mdl_requests.push_front(&mdl_request);
+ mdl_requests.push_front(&schema_request);
+ mdl_requests.push_front(&global_request);
+
+ if (thd->mdl_context.acquire_locks(&mdl_requests,
+ thd->variables.lock_wait_timeout))
+ return TRUE;
+
+ DEBUG_SYNC(thd, "after_wait_locked_pname");
+ return FALSE;
+}
+
+
+static void print_lock_error(int error, TABLE *table)
+{
+ int textno;
+ DBUG_ENTER("print_lock_error");
+
+ switch (error) {
+ case HA_ERR_LOCK_WAIT_TIMEOUT:
+ textno=ER_LOCK_WAIT_TIMEOUT;
+ break;
+ case HA_ERR_READ_ONLY_TRANSACTION:
+ textno=ER_READ_ONLY_TRANSACTION;
+ break;
+ case HA_ERR_LOCK_DEADLOCK:
+ textno=ER_LOCK_DEADLOCK;
+ break;
+ case HA_ERR_WRONG_COMMAND:
+ my_error(ER_ILLEGAL_HA, MYF(0), table->file->table_type(),
+ table->s->db.str, table->s->table_name.str);
+ DBUG_VOID_RETURN;
+ default:
+ textno=ER_CANT_LOCK;
+ break;
+ }
+
+ my_error(textno, MYF(0), error);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/****************************************************************************
+ Handling of global read locks
+
+ Global read lock is implemented using metadata lock infrastructure.
+
+ Taking the global read lock is TWO steps (2nd step is optional; without
+ it, COMMIT of existing transactions will be allowed):
+ lock_global_read_lock() THEN make_global_read_lock_block_commit().
+
+ How blocking of threads by global read lock is achieved: that's
+ semi-automatic. We assume that any statement which should be blocked
+ by global read lock will either open and acquires write-lock on tables
+ or acquires metadata locks on objects it is going to modify. For any
+ such statement global IX metadata lock is automatically acquired for
+ its duration (in case of LOCK TABLES until end of LOCK TABLES mode).
+ And lock_global_read_lock() simply acquires global S metadata lock
+ and thus prohibits execution of statements which modify data (unless
+ they modify only temporary tables). If deadlock happens it is detected
+ by MDL subsystem and resolved in the standard fashion (by backing-off
+ metadata locks acquired so far and restarting open tables process
+ if possible).
+
+ Why does FLUSH TABLES WITH READ LOCK need to block COMMIT: because it's used
+ to read a non-moving SHOW MASTER STATUS, and a COMMIT writes to the binary
+ log.
+
+ Why getting the global read lock is two steps and not one. Because FLUSH
+ TABLES WITH READ LOCK needs to insert one other step between the two:
+ flushing tables. So the order is
+ 1) lock_global_read_lock() (prevents any new table write locks, i.e. stalls
+ all new updates)
+ 2) close_cached_tables() (the FLUSH TABLES), which will wait for tables
+ currently opened and being updated to close (so it's possible that there is
+ a moment where all new updates of server are stalled *and* FLUSH TABLES WITH
+ READ LOCK is, too).
+ 3) make_global_read_lock_block_commit().
+ If we have merged 1) and 3) into 1), we would have had this deadlock:
+ imagine thread 1 and 2, in non-autocommit mode, thread 3, and an InnoDB
+ table t.
+ thd1: SELECT * FROM t FOR UPDATE;
+ thd2: UPDATE t SET a=1; # blocked by row-level locks of thd1
+ thd3: FLUSH TABLES WITH READ LOCK; # blocked in close_cached_tables() by the
+ table instance of thd2
+ thd1: COMMIT; # blocked by thd3.
+ thd1 blocks thd2 which blocks thd3 which blocks thd1: deadlock.
+
+ Note that we need to support that one thread does
+ FLUSH TABLES WITH READ LOCK; and then COMMIT;
+ (that's what innobackup does, for some good reason).
+ So in this exceptional case the COMMIT should not be blocked by the FLUSH
+ TABLES WITH READ LOCK.
+
+****************************************************************************/
+
+/**
+ Take global read lock, wait if there is protection against lock.
+
+ If the global read lock is already taken by this thread, then nothing is done.
+
+ See also "Handling of global read locks" above.
+
+ @param thd Reference to thread.
+
+ @retval False Success, global read lock set, commits are NOT blocked.
+ @retval True Failure, thread was killed.
+*/
+
+bool Global_read_lock::lock_global_read_lock(THD *thd)
+{
+ DBUG_ENTER("lock_global_read_lock");
+
+ if (!m_state)
+ {
+ MDL_request mdl_request;
+
+ DBUG_ASSERT(! thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
+ MDL_SHARED));
+ mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED, MDL_EXPLICIT);
+
+ if (thd->mdl_context.acquire_lock(&mdl_request,
+ thd->variables.lock_wait_timeout))
+ DBUG_RETURN(1);
+
+ m_mdl_global_shared_lock= mdl_request.ticket;
+ m_state= GRL_ACQUIRED;
+ }
+ /*
+ We DON'T set global_read_lock_blocks_commit now, it will be set after
+ tables are flushed (as the present function serves for FLUSH TABLES WITH
+ READ LOCK only). Doing things in this order is necessary to avoid
+ deadlocks (we must allow COMMIT until all tables are closed; we should not
+ forbid it before, or we can have a 3-thread deadlock if 2 do SELECT FOR
+ UPDATE and one does FLUSH TABLES WITH READ LOCK).
+ */
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Unlock global read lock.
+
+ Commits may or may not be blocked when this function is called.
+
+ See also "Handling of global read locks" above.
+
+ @param thd Reference to thread.
+*/
+
+void Global_read_lock::unlock_global_read_lock(THD *thd)
+{
+ DBUG_ENTER("unlock_global_read_lock");
+
+ DBUG_ASSERT(m_mdl_global_shared_lock && m_state);
+
+ if (thd->global_disable_checkpoint)
+ {
+ thd->global_disable_checkpoint= 0;
+ if (!--global_disable_checkpoint)
+ {
+ ha_checkpoint_state(0); // Enable checkpoints
+ }
+ }
+
+ if (m_mdl_blocks_commits_lock)
+ {
+ thd->mdl_context.release_lock(m_mdl_blocks_commits_lock);
+ m_mdl_blocks_commits_lock= NULL;
+ }
+ thd->mdl_context.release_lock(m_mdl_global_shared_lock);
+ m_mdl_global_shared_lock= NULL;
+ m_state= GRL_NONE;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Make global read lock also block commits.
+
+ The scenario is:
+ - This thread has the global read lock.
+ - Global read lock blocking of commits is not set.
+
+ See also "Handling of global read locks" above.
+
+ @param thd Reference to thread.
+
+ @retval False Success, global read lock set, commits are blocked.
+ @retval True Failure, thread was killed.
+*/
+
+bool Global_read_lock::make_global_read_lock_block_commit(THD *thd)
+{
+ MDL_request mdl_request;
+ DBUG_ENTER("make_global_read_lock_block_commit");
+ /*
+ If we didn't succeed lock_global_read_lock(), or if we already suceeded
+ make_global_read_lock_block_commit(), do nothing.
+ */
+ if (m_state != GRL_ACQUIRED)
+ DBUG_RETURN(0);
+
+ mdl_request.init(MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT);
+
+ if (thd->mdl_context.acquire_lock(&mdl_request,
+ thd->variables.lock_wait_timeout))
+ DBUG_RETURN(TRUE);
+
+ m_mdl_blocks_commits_lock= mdl_request.ticket;
+ m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT;
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Set explicit duration for metadata locks which are used to implement GRL.
+
+ @param thd Reference to thread.
+*/
+
+void Global_read_lock::set_explicit_lock_duration(THD *thd)
+{
+ if (m_mdl_global_shared_lock)
+ thd->mdl_context.set_lock_duration(m_mdl_global_shared_lock, MDL_EXPLICIT);
+ if (m_mdl_blocks_commits_lock)
+ thd->mdl_context.set_lock_duration(m_mdl_blocks_commits_lock, MDL_EXPLICIT);
+}
+
+/**
+ @} (end of group Locking)
+*/
diff --git a/sql/lock.h b/sql/lock.h
new file mode 100644
index 00000000000..a4833cdc38e
--- /dev/null
+++ b/sql/lock.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef LOCK_INCLUDED
+#define LOCK_INCLUDED
+
+#include "thr_lock.h" /* thr_lock_type */
+#include "mdl.h"
+
+// Forward declarations
+struct TABLE;
+struct TABLE_LIST;
+class THD;
+typedef struct st_mysql_lock MYSQL_LOCK;
+
+
+MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags);
+bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, uint flags);
+void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock, bool free_lock= 1);
+void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);
+void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count);
+void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table);
+void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock);
+bool mysql_lock_abort_for_thread(THD *thd, TABLE *table);
+MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b);
+/* Lock based on name */
+bool lock_schema_name(THD *thd, const char *db);
+/* Lock based on stored routine name */
+bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type,
+ const char *db, const char *name);
+
+/* flags for get_lock_data */
+#define GET_LOCK_UNLOCK 1
+#define GET_LOCK_STORE_LOCKS 2
+
+MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags);
+void reset_lock_data(MYSQL_LOCK *sql_lock, bool unlock);
+
+#endif /* LOCK_INCLUDED */
diff --git a/sql/log.cc b/sql/log.cc
new file mode 100644
index 00000000000..99d3fb69b18
--- /dev/null
+++ b/sql/log.cc
@@ -0,0 +1,9852 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/**
+ @file
+
+ @brief
+ logging of commands
+
+ @todo
+ Abort logging when we get an error in reading or writing log files
+*/
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "log.h"
+#include "sql_base.h" // open_log_table
+#include "sql_repl.h"
+#include "sql_delete.h" // mysql_truncate
+#include "sql_parse.h" // command_name
+#include "sql_time.h" // calc_time_from_sec, my_time_compare
+#include "tztime.h" // my_tz_OFFSET0, struct Time_zone
+#include "sql_acl.h" // SUPER_ACL
+#include "log_event.h" // Query_log_event
+#include "rpl_filter.h"
+#include "rpl_rli.h"
+#include "sql_audit.h"
+#include "log_slow.h"
+#include "mysqld.h"
+
+#include <my_dir.h>
+#include <stdarg.h>
+#include <m_ctype.h> // For test_if_number
+
+#ifdef _WIN32
+#include "message.h"
+#endif
+
+#include "sql_plugin.h"
+#include "rpl_handler.h"
+#include "debug_sync.h"
+#include "sql_show.h"
+#include "my_pthread.h"
+
+/* max size of the log message */
+#define MAX_LOG_BUFFER_SIZE 1024
+#define MAX_TIME_SIZE 32
+#define MY_OFF_T_UNDEF (~(my_off_t)0UL)
+
+#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
+
+LOGGER logger;
+
+MYSQL_BIN_LOG mysql_bin_log(&sync_binlog_period);
+
+static bool test_if_number(const char *str,
+ ulong *res, bool allow_wildcards);
+static int binlog_init(void *p);
+static int binlog_close_connection(handlerton *hton, THD *thd);
+static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv);
+static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv);
+static bool binlog_savepoint_rollback_can_release_mdl(handlerton *hton,
+ THD *thd);
+static int binlog_commit(handlerton *hton, THD *thd, bool all);
+static int binlog_rollback(handlerton *hton, THD *thd, bool all);
+static int binlog_prepare(handlerton *hton, THD *thd, bool all);
+static int binlog_start_consistent_snapshot(handlerton *hton, THD *thd);
+
+static LEX_STRING const write_error_msg=
+ { C_STRING_WITH_LEN("error writing to the binary log") };
+
+static my_bool opt_optimize_thread_scheduling= TRUE;
+ulong binlog_checksum_options;
+#ifndef DBUG_OFF
+ulong opt_binlog_dbug_fsync_sleep= 0;
+#endif
+
+mysql_mutex_t LOCK_prepare_ordered;
+mysql_cond_t COND_prepare_ordered;
+mysql_mutex_t LOCK_commit_ordered;
+
+static ulonglong binlog_status_var_num_commits;
+static ulonglong binlog_status_var_num_group_commits;
+static ulonglong binlog_status_group_commit_trigger_count;
+static ulonglong binlog_status_group_commit_trigger_lock_wait;
+static ulonglong binlog_status_group_commit_trigger_timeout;
+static char binlog_snapshot_file[FN_REFLEN];
+static ulonglong binlog_snapshot_position;
+
+static SHOW_VAR binlog_status_vars_detail[]=
+{
+ {"commits",
+ (char *)&binlog_status_var_num_commits, SHOW_LONGLONG},
+ {"group_commits",
+ (char *)&binlog_status_var_num_group_commits, SHOW_LONGLONG},
+ {"group_commit_trigger_count",
+ (char *)&binlog_status_group_commit_trigger_count, SHOW_LONGLONG},
+ {"group_commit_trigger_lock_wait",
+ (char *)&binlog_status_group_commit_trigger_lock_wait, SHOW_LONGLONG},
+ {"group_commit_trigger_timeout",
+ (char *)&binlog_status_group_commit_trigger_timeout, SHOW_LONGLONG},
+ {"snapshot_file",
+ (char *)&binlog_snapshot_file, SHOW_CHAR},
+ {"snapshot_position",
+ (char *)&binlog_snapshot_position, SHOW_LONGLONG},
+ {NullS, NullS, SHOW_LONG}
+};
+
+/*
+ Variables for the binlog background thread.
+ Protected by the MYSQL_BIN_LOG::LOCK_binlog_background_thread mutex.
+ */
+static bool binlog_background_thread_started= false;
+static bool binlog_background_thread_stop= false;
+static MYSQL_BIN_LOG::xid_count_per_binlog *
+ binlog_background_thread_queue= NULL;
+
+static bool start_binlog_background_thread();
+
+static rpl_binlog_state rpl_global_gtid_binlog_state;
+
+/**
+ purge logs, master and slave sides both, related error code
+ convertor.
+ Called from @c purge_error_message(), @c MYSQL_BIN_LOG::reset_logs()
+
+ @param res an internal to purging routines error code
+
+ @return the user level error code ER_*
+*/
+uint purge_log_get_error_code(int res)
+{
+ uint errcode= 0;
+
+ switch (res) {
+ case 0: break;
+ case LOG_INFO_EOF: errcode= ER_UNKNOWN_TARGET_BINLOG; break;
+ case LOG_INFO_IO: errcode= ER_IO_ERR_LOG_INDEX_READ; break;
+ case LOG_INFO_INVALID:errcode= ER_BINLOG_PURGE_PROHIBITED; break;
+ case LOG_INFO_SEEK: errcode= ER_FSEEK_FAIL; break;
+ case LOG_INFO_MEM: errcode= ER_OUT_OF_RESOURCES; break;
+ case LOG_INFO_FATAL: errcode= ER_BINLOG_PURGE_FATAL_ERR; break;
+ case LOG_INFO_IN_USE: errcode= ER_LOG_IN_USE; break;
+ case LOG_INFO_EMFILE: errcode= ER_BINLOG_PURGE_EMFILE; break;
+ default: errcode= ER_LOG_PURGE_UNKNOWN_ERR; break;
+ }
+
+ return errcode;
+}
+
+/**
+ Silence all errors and warnings reported when performing a write
+ to a log table.
+ Errors and warnings are not reported to the client or SQL exception
+ handlers, so that the presence of logging does not interfere and affect
+ the logic of an application.
+*/
+class Silence_log_table_errors : public Internal_error_handler
+{
+ char m_message[MYSQL_ERRMSG_SIZE];
+public:
+ Silence_log_table_errors()
+ {
+ m_message[0]= '\0';
+ }
+
+ virtual ~Silence_log_table_errors() {}
+
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sql_state,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+ const char *message() const { return m_message; }
+};
+
+bool
+Silence_log_table_errors::handle_condition(THD *,
+ uint,
+ const char*,
+ Sql_condition::enum_warning_level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ strmake_buf(m_message, msg);
+ return TRUE;
+}
+
+sql_print_message_func sql_print_message_handlers[3] =
+{
+ sql_print_information,
+ sql_print_warning,
+ sql_print_error
+};
+
+
+/**
+ Create the name of the log file
+
+ @param[OUT] out a pointer to a new allocated name will go there
+ @param[IN] log_ext The extension for the file (e.g .log)
+ @param[IN] once whether to use malloc_once or a normal malloc.
+*/
+void make_default_log_name(char **out, const char* log_ext, bool once)
+{
+ char buff[FN_REFLEN+10];
+ fn_format(buff, opt_log_basename, "", log_ext, MYF(MY_REPLACE_EXT));
+ if (once)
+ *out= my_once_strdup(buff, MYF(MY_WME));
+ else
+ {
+ my_free(*out);
+ *out= my_strdup(buff, MYF(MY_WME));
+ }
+}
+
+
+/*
+ Helper classes to store non-transactional and transactional data
+ before copying it to the binary log.
+*/
+class binlog_cache_data
+{
+public:
+ binlog_cache_data(): m_pending(0), before_stmt_pos(MY_OFF_T_UNDEF),
+ incident(FALSE), changes_to_non_trans_temp_table_flag(FALSE),
+ saved_max_binlog_cache_size(0), ptr_binlog_cache_use(0),
+ ptr_binlog_cache_disk_use(0)
+ { }
+
+ ~binlog_cache_data()
+ {
+ DBUG_ASSERT(empty());
+ close_cached_file(&cache_log);
+ }
+
+ bool empty() const
+ {
+ return pending() == NULL && my_b_tell(&cache_log) == 0;
+ }
+
+ Rows_log_event *pending() const
+ {
+ return m_pending;
+ }
+
+ void set_pending(Rows_log_event *const pending)
+ {
+ m_pending= pending;
+ }
+
+ void set_incident(void)
+ {
+ incident= TRUE;
+ }
+
+ bool has_incident(void)
+ {
+ return(incident);
+ }
+
+ void set_changes_to_non_trans_temp_table()
+ {
+ changes_to_non_trans_temp_table_flag= TRUE;
+ }
+
+ bool changes_to_non_trans_temp_table()
+ {
+ return (changes_to_non_trans_temp_table_flag);
+ }
+
+ void reset()
+ {
+ compute_statistics();
+ truncate(0);
+ changes_to_non_trans_temp_table_flag= FALSE;
+ incident= FALSE;
+ before_stmt_pos= MY_OFF_T_UNDEF;
+ /*
+ The truncate function calls reinit_io_cache that calls my_b_flush_io_cache
+ which may increase disk_writes. This breaks the disk_writes use by the
+ binary log which aims to compute the ratio between in-memory cache usage
+ and disk cache usage. To avoid this undesirable behavior, we reset the
+ variable after truncating the cache.
+ */
+ cache_log.disk_writes= 0;
+ DBUG_ASSERT(empty());
+ }
+
+ my_off_t get_byte_position() const
+ {
+ return my_b_tell(&cache_log);
+ }
+
+ my_off_t get_prev_position()
+ {
+ return(before_stmt_pos);
+ }
+
+ void set_prev_position(my_off_t pos)
+ {
+ before_stmt_pos= pos;
+ }
+
+ void restore_prev_position()
+ {
+ truncate(before_stmt_pos);
+ }
+
+ void restore_savepoint(my_off_t pos)
+ {
+ truncate(pos);
+ if (pos < before_stmt_pos)
+ before_stmt_pos= MY_OFF_T_UNDEF;
+ }
+
+ void set_binlog_cache_info(my_off_t param_max_binlog_cache_size,
+ ulong *param_ptr_binlog_cache_use,
+ ulong *param_ptr_binlog_cache_disk_use)
+ {
+ /*
+ The assertions guarantee that the set_binlog_cache_info is
+ called just once and information passed as parameters are
+ never zero.
+
+ This is done while calling the constructor binlog_cache_mngr.
+ We cannot set informaton in the constructor binlog_cache_data
+ because the space for binlog_cache_mngr is allocated through
+ a placement new.
+
+ In the future, we can refactor this and change it to avoid
+ the set_binlog_info.
+ */
+ DBUG_ASSERT(saved_max_binlog_cache_size == 0 &&
+ param_max_binlog_cache_size != 0 &&
+ ptr_binlog_cache_use == 0 &&
+ param_ptr_binlog_cache_use != 0 &&
+ ptr_binlog_cache_disk_use == 0 &&
+ param_ptr_binlog_cache_disk_use != 0);
+
+ saved_max_binlog_cache_size= param_max_binlog_cache_size;
+ ptr_binlog_cache_use= param_ptr_binlog_cache_use;
+ ptr_binlog_cache_disk_use= param_ptr_binlog_cache_disk_use;
+ cache_log.end_of_file= saved_max_binlog_cache_size;
+ }
+
+ /*
+ Cache to store data before copying it to the binary log.
+ */
+ IO_CACHE cache_log;
+
+private:
+ /*
+ Pending binrows event. This event is the event where the rows are currently
+ written.
+ */
+ Rows_log_event *m_pending;
+
+ /*
+ Binlog position before the start of the current statement.
+ */
+ my_off_t before_stmt_pos;
+
+ /*
+ This indicates that some events did not get into the cache and most likely
+ it is corrupted.
+ */
+ bool incident;
+
+ /*
+ This flag indicates if the cache has changes to temporary tables.
+ @TODO This a temporary fix and should be removed after BUG#54562.
+ */
+ bool changes_to_non_trans_temp_table_flag;
+
+ /**
+ This function computes binlog cache and disk usage.
+ */
+ void compute_statistics()
+ {
+ if (!empty())
+ {
+ statistic_increment(*ptr_binlog_cache_use, &LOCK_status);
+ if (cache_log.disk_writes != 0)
+ statistic_increment(*ptr_binlog_cache_disk_use, &LOCK_status);
+ }
+ }
+
+ /*
+ Stores the values of maximum size of the cache allowed when this cache
+ is configured. This corresponds to either
+ . max_binlog_cache_size or max_binlog_stmt_cache_size.
+ */
+ my_off_t saved_max_binlog_cache_size;
+
+ /*
+ Stores a pointer to the status variable that keeps track of the in-memory
+ cache usage. This corresponds to either
+ . binlog_cache_use or binlog_stmt_cache_use.
+ */
+ ulong *ptr_binlog_cache_use;
+
+ /*
+ Stores a pointer to the status variable that keeps track of the disk
+ cache usage. This corresponds to either
+ . binlog_cache_disk_use or binlog_stmt_cache_disk_use.
+ */
+ ulong *ptr_binlog_cache_disk_use;
+
+ /*
+ It truncates the cache to a certain position. This includes deleting the
+ pending event.
+ */
+ void truncate(my_off_t pos)
+ {
+ DBUG_PRINT("info", ("truncating to position %lu", (ulong) pos));
+ if (pending())
+ {
+ delete pending();
+ set_pending(0);
+ }
+ reinit_io_cache(&cache_log, WRITE_CACHE, pos, 0, 0);
+ cache_log.end_of_file= saved_max_binlog_cache_size;
+ }
+
+ binlog_cache_data& operator=(const binlog_cache_data& info);
+ binlog_cache_data(const binlog_cache_data& info);
+};
+
+class binlog_cache_mngr {
+public:
+ binlog_cache_mngr(my_off_t param_max_binlog_stmt_cache_size,
+ my_off_t param_max_binlog_cache_size,
+ ulong *param_ptr_binlog_stmt_cache_use,
+ ulong *param_ptr_binlog_stmt_cache_disk_use,
+ ulong *param_ptr_binlog_cache_use,
+ ulong *param_ptr_binlog_cache_disk_use)
+ : last_commit_pos_offset(0), using_xa(FALSE), xa_xid(0)
+ {
+ stmt_cache.set_binlog_cache_info(param_max_binlog_stmt_cache_size,
+ param_ptr_binlog_stmt_cache_use,
+ param_ptr_binlog_stmt_cache_disk_use);
+ trx_cache.set_binlog_cache_info(param_max_binlog_cache_size,
+ param_ptr_binlog_cache_use,
+ param_ptr_binlog_cache_disk_use);
+ last_commit_pos_file[0]= 0;
+ }
+
+ void reset(bool do_stmt, bool do_trx)
+ {
+ if (do_stmt)
+ stmt_cache.reset();
+ if (do_trx)
+ {
+ trx_cache.reset();
+ using_xa= FALSE;
+ last_commit_pos_file[0]= 0;
+ last_commit_pos_offset= 0;
+ }
+ }
+
+ binlog_cache_data* get_binlog_cache_data(bool is_transactional)
+ {
+ return (is_transactional ? &trx_cache : &stmt_cache);
+ }
+
+ IO_CACHE* get_binlog_cache_log(bool is_transactional)
+ {
+ return (is_transactional ? &trx_cache.cache_log : &stmt_cache.cache_log);
+ }
+
+ binlog_cache_data stmt_cache;
+
+ binlog_cache_data trx_cache;
+
+ /*
+ Binlog position for current transaction.
+ For START TRANSACTION WITH CONSISTENT SNAPSHOT, this is the binlog
+ position corresponding to the snapshot taken. During (and after) commit,
+ this is set to the binlog position corresponding to just after the
+ commit (so storage engines can store it in their transaction log).
+ */
+ char last_commit_pos_file[FN_REFLEN];
+ my_off_t last_commit_pos_offset;
+
+ /*
+ Flag set true if this transaction is committed with log_xid() as part of
+ XA, false if not.
+ */
+ bool using_xa;
+ my_xid xa_xid;
+ bool need_unlog;
+ /*
+ Id of binlog that transaction was written to; only needed if need_unlog is
+ true.
+ */
+ ulong binlog_id;
+ /* Set if we get an error during commit that must be returned from unlog(). */
+ bool delayed_error;
+
+private:
+
+ binlog_cache_mngr& operator=(const binlog_cache_mngr& info);
+ binlog_cache_mngr(const binlog_cache_mngr& info);
+};
+
+handlerton *binlog_hton;
+
+bool LOGGER::is_log_table_enabled(uint log_table_type)
+{
+ switch (log_table_type) {
+ case QUERY_LOG_SLOW:
+ return (table_log_handler != NULL) && opt_slow_log;
+ case QUERY_LOG_GENERAL:
+ return (table_log_handler != NULL) && opt_log ;
+ default:
+ DBUG_ASSERT(0);
+ return FALSE; /* make compiler happy */
+ }
+}
+
+
+/**
+ Check if a given table is opened log table
+
+ @param table Table to check
+ @param check_if_opened Only fail if it's a log table in use
+ @param error_msg String to put in error message if not ok.
+ No error message if 0
+ @return 0 ok
+ @return # Type of log file
+ */
+
+int check_if_log_table(const TABLE_LIST *table,
+ bool check_if_opened,
+ const char *error_msg)
+{
+ int result= 0;
+ if (table->db_length == 5 &&
+ !my_strcasecmp(table_alias_charset, table->db, "mysql"))
+ {
+ const char *table_name= table->table_name;
+
+ if (table->table_name_length == 11 &&
+ !my_strcasecmp(table_alias_charset, table_name, "general_log"))
+ {
+ result= QUERY_LOG_GENERAL;
+ goto end;
+ }
+
+ if (table->table_name_length == 8 &&
+ !my_strcasecmp(table_alias_charset, table_name, "slow_log"))
+ {
+ result= QUERY_LOG_SLOW;
+ goto end;
+ }
+ }
+ return 0;
+
+end:
+ if (!check_if_opened || logger.is_log_table_enabled(result))
+ {
+ if (error_msg)
+ my_error(ER_BAD_LOG_STATEMENT, MYF(0), error_msg);
+ return result;
+ }
+ return 0;
+}
+
+
+Log_to_csv_event_handler::Log_to_csv_event_handler()
+{
+}
+
+
+Log_to_csv_event_handler::~Log_to_csv_event_handler()
+{
+}
+
+
+void Log_to_csv_event_handler::cleanup()
+{
+ logger.is_log_tables_initialized= FALSE;
+}
+
+/* log event handlers */
+
+/**
+ Log command to the general log table
+
+ Log given command to the general log table.
+
+ @param event_time command start timestamp
+ @param user_host the pointer to the string with user@host info
+ @param user_host_len length of the user_host string. this is computed
+ once and passed to all general log event handlers
+ @param thread_id Id of the thread, issued a query
+ @param command_type the type of the command being logged
+ @param command_type_len the length of the string above
+ @param sql_text the very text of the query being executed
+ @param sql_text_len the length of sql_text string
+
+
+ @return This function attempts to never call my_error(). This is
+ necessary, because general logging happens already after a statement
+ status has been sent to the client, so the client can not see the
+ error anyway. Besides, the error is not related to the statement
+ being executed and is internal, and thus should be handled
+ internally (@todo: how?).
+ If a write to the table has failed, the function attempts to
+ write to a short error message to the file. The failure is also
+ indicated in the return value.
+
+ @retval FALSE OK
+ @retval TRUE error occured
+*/
+
+bool Log_to_csv_event_handler::
+ log_general(THD *thd, my_hrtime_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len,
+ CHARSET_INFO *client_cs)
+{
+ TABLE_LIST table_list;
+ TABLE *table;
+ bool result= TRUE;
+ bool need_close= FALSE;
+ bool need_pop= FALSE;
+ bool need_rnd_end= FALSE;
+ uint field_index;
+ Silence_log_table_errors error_handler;
+ Open_tables_backup open_tables_backup;
+ ulonglong save_thd_options;
+ bool save_time_zone_used;
+ DBUG_ENTER("log_general");
+
+ /*
+ CSV uses TIME_to_timestamp() internally if table needs to be repaired
+ which will set thd->time_zone_used
+ */
+ save_time_zone_used= thd->time_zone_used;
+
+ save_thd_options= thd->variables.option_bits;
+ thd->variables.option_bits&= ~OPTION_BIN_LOG;
+
+ table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length,
+ GENERAL_LOG_NAME.str, GENERAL_LOG_NAME.length,
+ GENERAL_LOG_NAME.str,
+ TL_WRITE_CONCURRENT_INSERT);
+
+ /*
+ 1) open_log_table generates an error of the
+ table can not be opened or is corrupted.
+ 2) "INSERT INTO general_log" can generate warning sometimes.
+
+ Suppress these warnings and errors, they can't be dealt with
+ properly anyway.
+
+ QQ: this problem needs to be studied in more detail.
+ Comment this 2 lines and run "cast.test" to see what's happening.
+ */
+ thd->push_internal_handler(& error_handler);
+ need_pop= TRUE;
+
+ if (!(table= open_log_table(thd, &table_list, &open_tables_backup)))
+ goto err;
+
+ need_close= TRUE;
+
+ if (table->file->extra(HA_EXTRA_MARK_AS_LOG_TABLE) ||
+ table->file->ha_rnd_init_with_error(0))
+ goto err;
+
+ need_rnd_end= TRUE;
+
+ /* Honor next number columns if present */
+ table->next_number_field= table->found_next_number_field;
+
+ /*
+ NOTE: we do not call restore_record() here, as all fields are
+ filled by the Logger (=> no need to load default ones).
+ */
+
+ /*
+ We do not set a value for table->field[0], as it will use
+ default value (which is CURRENT_TIMESTAMP).
+ */
+
+ /* check that all columns exist */
+ if (table->s->fields < 6)
+ goto err;
+
+ DBUG_ASSERT(table->field[0]->type() == MYSQL_TYPE_TIMESTAMP);
+
+ ((Field_timestamp*) table->field[0])->store_TIME(
+ hrtime_to_my_time(event_time), hrtime_sec_part(event_time));
+
+ /* do a write */
+ if (table->field[1]->store(user_host, user_host_len, client_cs) ||
+ table->field[2]->store((longlong) thread_id, TRUE) ||
+ table->field[3]->store((longlong) global_system_variables.server_id,
+ TRUE) ||
+ table->field[4]->store(command_type, command_type_len, client_cs))
+ goto err;
+
+ /*
+ A positive return value in store() means truncation.
+ Still logging a message in the log in this case.
+ */
+ table->field[5]->flags|= FIELDFLAG_HEX_ESCAPE;
+ if (table->field[5]->store(sql_text, sql_text_len, client_cs) < 0)
+ goto err;
+
+ /* mark all fields as not null */
+ table->field[1]->set_notnull();
+ table->field[2]->set_notnull();
+ table->field[3]->set_notnull();
+ table->field[4]->set_notnull();
+ table->field[5]->set_notnull();
+
+ /* Set any extra columns to their default values */
+ for (field_index= 6 ; field_index < table->s->fields ; field_index++)
+ {
+ table->field[field_index]->set_default();
+ }
+
+ /* log table entries are not replicated */
+ if (table->file->ha_write_row(table->record[0]))
+ goto err;
+
+ result= FALSE;
+
+err:
+ if (result && !thd->killed)
+ sql_print_error("Failed to write to mysql.general_log: %s",
+ error_handler.message());
+
+ if (need_rnd_end)
+ {
+ table->file->ha_rnd_end();
+ table->file->ha_release_auto_increment();
+ }
+ if (need_pop)
+ thd->pop_internal_handler();
+ if (need_close)
+ close_log_table(thd, &open_tables_backup);
+
+ thd->variables.option_bits= save_thd_options;
+ thd->time_zone_used= save_time_zone_used;
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Log a query to the slow log table
+
+ SYNOPSIS
+ log_slow()
+ thd THD of the query
+ current_time current timestamp
+ user_host the pointer to the string with user@host info
+ user_host_len length of the user_host string. this is computed once
+ and passed to all general log event handlers
+ query_time Amount of time the query took to execute (in microseconds)
+ lock_time Amount of time the query was locked (in microseconds)
+ is_command The flag, which determines, whether the sql_text is a
+ query or an administrator command (these are treated
+ differently by the old logging routines)
+ sql_text the very text of the query or administrator command
+ processed
+ sql_text_len the length of sql_text string
+
+ DESCRIPTION
+
+ Log a query to the slow log table
+
+ RETURN
+ FALSE - OK
+ TRUE - error occured
+*/
+
+bool Log_to_csv_event_handler::
+ log_slow(THD *thd, my_hrtime_t current_time,
+ const char *user_host, uint user_host_len,
+ ulonglong query_utime, ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len)
+{
+ TABLE_LIST table_list;
+ TABLE *table;
+ bool result= TRUE;
+ bool need_close= FALSE;
+ bool need_rnd_end= FALSE;
+ Silence_log_table_errors error_handler;
+ Open_tables_backup open_tables_backup;
+ CHARSET_INFO *client_cs= thd->variables.character_set_client;
+ bool save_time_zone_used;
+ long query_time= (long) MY_MIN(query_utime/1000000, TIME_MAX_VALUE_SECONDS);
+ long lock_time= (long) MY_MIN(lock_utime/1000000, TIME_MAX_VALUE_SECONDS);
+ long query_time_micro= (long) (query_utime % 1000000);
+ long lock_time_micro= (long) (lock_utime % 1000000);
+
+ DBUG_ENTER("Log_to_csv_event_handler::log_slow");
+
+ thd->push_internal_handler(& error_handler);
+ /*
+ CSV uses TIME_to_timestamp() internally if table needs to be repaired
+ which will set thd->time_zone_used
+ */
+ save_time_zone_used= thd->time_zone_used;
+
+ table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length,
+ SLOW_LOG_NAME.str, SLOW_LOG_NAME.length,
+ SLOW_LOG_NAME.str,
+ TL_WRITE_CONCURRENT_INSERT);
+
+ if (!(table= open_log_table(thd, &table_list, &open_tables_backup)))
+ goto err;
+
+ need_close= TRUE;
+
+ if (table->file->extra(HA_EXTRA_MARK_AS_LOG_TABLE) ||
+ table->file->ha_rnd_init_with_error(0))
+ goto err;
+
+ need_rnd_end= TRUE;
+
+ /* Honor next number columns if present */
+ table->next_number_field= table->found_next_number_field;
+
+ restore_record(table, s->default_values); // Get empty record
+
+ /* check that all columns exist */
+ if (table->s->fields < 11)
+ goto err;
+
+ /* store the time and user values */
+ DBUG_ASSERT(table->field[0]->type() == MYSQL_TYPE_TIMESTAMP);
+ ((Field_timestamp*) table->field[0])->store_TIME(
+ hrtime_to_my_time(current_time), hrtime_sec_part(current_time));
+ if (table->field[1]->store(user_host, user_host_len, client_cs))
+ goto err;
+
+ /*
+ A TIME field can not hold the full longlong range; query_time or
+ lock_time may be truncated without warning here, if greater than
+ 839 hours (~35 days)
+ */
+ MYSQL_TIME t;
+ t.neg= 0;
+
+ /* fill in query_time field */
+ calc_time_from_sec(&t, query_time, query_time_micro);
+ if (table->field[2]->store_time(&t))
+ goto err;
+ /* lock_time */
+ calc_time_from_sec(&t, lock_time, lock_time_micro);
+ if (table->field[3]->store_time(&t))
+ goto err;
+ /* rows_sent */
+ if (table->field[4]->store((longlong) thd->get_sent_row_count(), TRUE))
+ goto err;
+ /* rows_examined */
+ if (table->field[5]->store((longlong) thd->get_examined_row_count(), TRUE))
+ goto err;
+
+ /* fill database field */
+ if (thd->db)
+ {
+ if (table->field[6]->store(thd->db, thd->db_length, client_cs))
+ goto err;
+ table->field[6]->set_notnull();
+ }
+
+ if (thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt)
+ {
+ if (table->
+ field[7]->store((longlong)
+ thd->first_successful_insert_id_in_prev_stmt_for_binlog,
+ TRUE))
+ goto err;
+ table->field[7]->set_notnull();
+ }
+
+ /*
+ Set value if we do an insert on autoincrement column. Note that for
+ some engines (those for which get_auto_increment() does not leave a
+ table lock until the statement ends), this is just the first value and
+ the next ones used may not be contiguous to it.
+ */
+ if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0)
+ {
+ if (table->
+ field[8]->store((longlong)
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.minimum(), TRUE))
+ goto err;
+ table->field[8]->set_notnull();
+ }
+
+ if (table->field[9]->store((longlong)global_system_variables.server_id, TRUE))
+ goto err;
+ table->field[9]->set_notnull();
+
+ /*
+ Column sql_text.
+ A positive return value in store() means truncation.
+ Still logging a message in the log in this case.
+ */
+ if (table->field[10]->store(sql_text, sql_text_len, client_cs) < 0)
+ goto err;
+
+ if (table->field[11]->store((longlong) thd->thread_id, TRUE))
+ goto err;
+
+ /* log table entries are not replicated */
+ if (table->file->ha_write_row(table->record[0]))
+ goto err;
+
+ result= FALSE;
+
+err:
+ thd->pop_internal_handler();
+
+ if (result && !thd->killed)
+ sql_print_error("Failed to write to mysql.slow_log: %s",
+ error_handler.message());
+
+ if (need_rnd_end)
+ {
+ table->file->ha_rnd_end();
+ table->file->ha_release_auto_increment();
+ }
+ if (need_close)
+ close_log_table(thd, &open_tables_backup);
+ thd->time_zone_used= save_time_zone_used;
+ DBUG_RETURN(result);
+}
+
+int Log_to_csv_event_handler::
+ activate_log(THD *thd, uint log_table_type)
+{
+ TABLE_LIST table_list;
+ TABLE *table;
+ LEX_STRING *UNINIT_VAR(log_name);
+ int result;
+ Open_tables_backup open_tables_backup;
+
+ DBUG_ENTER("Log_to_csv_event_handler::activate_log");
+
+ if (log_table_type == QUERY_LOG_GENERAL)
+ {
+ log_name= &GENERAL_LOG_NAME;
+ }
+ else
+ {
+ DBUG_ASSERT(log_table_type == QUERY_LOG_SLOW);
+
+ log_name= &SLOW_LOG_NAME;
+ }
+ table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length,
+ log_name->str, log_name->length, log_name->str,
+ TL_WRITE_CONCURRENT_INSERT);
+
+ table= open_log_table(thd, &table_list, &open_tables_backup);
+ if (table)
+ {
+ result= 0;
+ close_log_table(thd, &open_tables_backup);
+ }
+ else
+ result= 1;
+
+ DBUG_RETURN(result);
+}
+
+bool Log_to_csv_event_handler::
+ log_error(enum loglevel level, const char *format, va_list args)
+{
+ /* No log table is implemented */
+ DBUG_ASSERT(0);
+ return FALSE;
+}
+
+bool Log_to_file_event_handler::
+ log_error(enum loglevel level, const char *format,
+ va_list args)
+{
+ return vprint_msg_to_log(level, format, args);
+}
+
+void Log_to_file_event_handler::init_pthread_objects()
+{
+ mysql_log.init_pthread_objects();
+ mysql_slow_log.init_pthread_objects();
+}
+
+
+/** Wrapper around MYSQL_LOG::write() for slow log. */
+
+bool Log_to_file_event_handler::
+ log_slow(THD *thd, my_hrtime_t current_time,
+ const char *user_host, uint user_host_len,
+ ulonglong query_utime, ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len)
+{
+ Silence_log_table_errors error_handler;
+ thd->push_internal_handler(&error_handler);
+ bool retval= mysql_slow_log.write(thd, hrtime_to_my_time(current_time),
+ user_host, user_host_len,
+ query_utime, lock_utime, is_command,
+ sql_text, sql_text_len);
+ thd->pop_internal_handler();
+ return retval;
+}
+
+
+/**
+ Wrapper around MYSQL_LOG::write() for general log. We need it since we
+ want all log event handlers to have the same signature.
+*/
+
+bool Log_to_file_event_handler::
+ log_general(THD *thd, my_hrtime_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len,
+ CHARSET_INFO *client_cs)
+{
+ Silence_log_table_errors error_handler;
+ thd->push_internal_handler(&error_handler);
+ bool retval= mysql_log.write(hrtime_to_time(event_time), user_host,
+ user_host_len,
+ thread_id, command_type, command_type_len,
+ sql_text, sql_text_len);
+ thd->pop_internal_handler();
+ return retval;
+}
+
+
+bool Log_to_file_event_handler::init()
+{
+ if (!is_initialized)
+ {
+ if (opt_slow_log)
+ mysql_slow_log.open_slow_log(opt_slow_logname);
+
+ if (opt_log)
+ mysql_log.open_query_log(opt_logname);
+
+ is_initialized= TRUE;
+ }
+
+ return FALSE;
+}
+
+
+void Log_to_file_event_handler::cleanup()
+{
+ mysql_log.cleanup();
+ mysql_slow_log.cleanup();
+}
+
+void Log_to_file_event_handler::flush()
+{
+ /* reopen log files */
+ if (opt_log)
+ mysql_log.reopen_file();
+ if (opt_slow_log)
+ mysql_slow_log.reopen_file();
+}
+
+/*
+ Log error with all enabled log event handlers
+
+ SYNOPSIS
+ error_log_print()
+
+ level The level of the error significance: NOTE,
+ WARNING or ERROR.
+ format format string for the error message
+ args list of arguments for the format string
+
+ RETURN
+ FALSE - OK
+ TRUE - error occured
+*/
+
+bool LOGGER::error_log_print(enum loglevel level, const char *format,
+ va_list args)
+{
+ bool error= FALSE;
+ Log_event_handler **current_handler;
+
+ /* currently we don't need locking here as there is no error_log table */
+ for (current_handler= error_log_handler_list ; *current_handler ;)
+ error= (*current_handler++)->log_error(level, format, args) || error;
+
+ return error;
+}
+
+
+void LOGGER::cleanup_base()
+{
+ DBUG_ASSERT(inited == 1);
+ mysql_rwlock_destroy(&LOCK_logger);
+ if (table_log_handler)
+ {
+ table_log_handler->cleanup();
+ delete table_log_handler;
+ table_log_handler= NULL;
+ }
+ if (file_log_handler)
+ file_log_handler->cleanup();
+}
+
+
+void LOGGER::cleanup_end()
+{
+ DBUG_ASSERT(inited == 1);
+ if (file_log_handler)
+ {
+ delete file_log_handler;
+ file_log_handler=NULL;
+ }
+ inited= 0;
+}
+
+
+/**
+ Perform basic log initialization: create file-based log handler and
+ init error log.
+*/
+void LOGGER::init_base()
+{
+ DBUG_ASSERT(inited == 0);
+ inited= 1;
+
+ /*
+ Here we create file log handler. We don't do it for the table log handler
+ here as it cannot be created so early. The reason is THD initialization,
+ which depends on the system variables (parsed later).
+ */
+ if (!file_log_handler)
+ file_log_handler= new Log_to_file_event_handler;
+
+ /* by default we use traditional error log */
+ init_error_log(LOG_FILE);
+
+ file_log_handler->init_pthread_objects();
+ mysql_rwlock_init(key_rwlock_LOCK_logger, &LOCK_logger);
+}
+
+
+void LOGGER::init_log_tables()
+{
+ if (!table_log_handler)
+ table_log_handler= new Log_to_csv_event_handler;
+
+ if (!is_log_tables_initialized &&
+ !table_log_handler->init() && !file_log_handler->init())
+ is_log_tables_initialized= TRUE;
+}
+
+
+bool LOGGER::flush_logs(THD *thd)
+{
+ /*
+ Now we lock logger, as nobody should be able to use logging routines while
+ log tables are closed
+ */
+ logger.lock_exclusive();
+
+ /* reopen log files */
+ file_log_handler->flush();
+
+ /* end of log flush */
+ logger.unlock();
+ return 0;
+}
+
+
+/**
+ Close and reopen the slow log (with locks).
+
+ @returns FALSE.
+*/
+bool LOGGER::flush_slow_log()
+{
+ /*
+ Now we lock logger, as nobody should be able to use logging routines while
+ log tables are closed
+ */
+ logger.lock_exclusive();
+
+ /* Reopen slow log file */
+ if (opt_slow_log)
+ file_log_handler->get_mysql_slow_log()->reopen_file();
+
+ /* End of log flush */
+ logger.unlock();
+
+ return 0;
+}
+
+
+/**
+ Close and reopen the general log (with locks).
+
+ @returns FALSE.
+*/
+bool LOGGER::flush_general_log()
+{
+ /*
+ Now we lock logger, as nobody should be able to use logging routines while
+ log tables are closed
+ */
+ logger.lock_exclusive();
+
+ /* Reopen general log file */
+ if (opt_log)
+ file_log_handler->get_mysql_log()->reopen_file();
+
+ /* End of log flush */
+ logger.unlock();
+
+ return 0;
+}
+
+
+/*
+ Log slow query with all enabled log event handlers
+
+ SYNOPSIS
+ slow_log_print()
+
+ thd THD of the query being logged
+ query The query being logged
+ query_length The length of the query string
+ current_utime Current time in microseconds (from undefined start)
+
+ RETURN
+ FALSE OK
+ TRUE error occured
+*/
+
+bool LOGGER::slow_log_print(THD *thd, const char *query, uint query_length,
+ ulonglong current_utime)
+
+{
+ bool error= FALSE;
+ Log_event_handler **current_handler;
+ bool is_command= FALSE;
+ char user_host_buff[MAX_USER_HOST_SIZE + 1];
+ Security_context *sctx= thd->security_ctx;
+ uint user_host_len= 0;
+ ulonglong query_utime, lock_utime;
+
+ DBUG_ASSERT(thd->enable_slow_log);
+ /*
+ Print the message to the buffer if we have slow log enabled
+ */
+
+ if (*slow_log_handler_list)
+ {
+ /* do not log slow queries from replication threads */
+ if (thd->slave_thread && !opt_log_slow_slave_statements)
+ return 0;
+
+ lock_shared();
+ if (!opt_slow_log)
+ {
+ unlock();
+ return 0;
+ }
+
+ /* fill in user_host value: the format is "%s[%s] @ %s [%s]" */
+ user_host_len= (strxnmov(user_host_buff, MAX_USER_HOST_SIZE,
+ sctx->priv_user ? sctx->priv_user : "", "[",
+ sctx->user ? sctx->user : (thd->slave_thread ? "SQL_SLAVE" : ""), "] @ ",
+ sctx->host ? sctx->host : "", " [",
+ sctx->ip ? sctx->ip : "", "]", NullS) -
+ user_host_buff);
+
+ DBUG_ASSERT(thd->start_utime);
+ DBUG_ASSERT(thd->start_time);
+ query_utime= (current_utime - thd->start_utime);
+ lock_utime= (thd->utime_after_lock - thd->start_utime);
+ my_hrtime_t current_time= { hrtime_from_time(thd->start_time) +
+ thd->start_time_sec_part + query_utime };
+
+ if (!query)
+ {
+ is_command= TRUE;
+ query= command_name[thd->get_command()].str;
+ query_length= command_name[thd->get_command()].length;
+ }
+
+ for (current_handler= slow_log_handler_list; *current_handler ;)
+ error= (*current_handler++)->log_slow(thd, current_time,
+ user_host_buff, user_host_len,
+ query_utime, lock_utime, is_command,
+ query, query_length) || error;
+
+ unlock();
+ }
+ return error;
+}
+
+bool LOGGER::general_log_write(THD *thd, enum enum_server_command command,
+ const char *query, uint query_length)
+{
+ bool error= FALSE;
+ Log_event_handler **current_handler= general_log_handler_list;
+ char user_host_buff[MAX_USER_HOST_SIZE + 1];
+ uint user_host_len= 0;
+ my_hrtime_t current_time;
+
+ DBUG_ASSERT(thd);
+
+ user_host_len= make_user_name(thd, user_host_buff);
+
+ current_time= my_hrtime();
+
+ mysql_audit_general_log(thd, hrtime_to_time(current_time),
+ user_host_buff, user_host_len,
+ command_name[(uint) command].str,
+ command_name[(uint) command].length,
+ query, query_length);
+
+ if (opt_log && log_command(thd, command))
+ {
+ lock_shared();
+ while (*current_handler)
+ error|= (*current_handler++)->
+ log_general(thd, current_time, user_host_buff,
+ user_host_len, thd->thread_id,
+ command_name[(uint) command].str,
+ command_name[(uint) command].length,
+ query, query_length,
+ thd->variables.character_set_client) || error;
+ unlock();
+ }
+
+ return error;
+}
+
+bool LOGGER::general_log_print(THD *thd, enum enum_server_command command,
+ const char *format, va_list args)
+{
+ uint message_buff_len= 0;
+ char message_buff[MAX_LOG_BUFFER_SIZE];
+
+ /* prepare message */
+ if (format)
+ message_buff_len= my_vsnprintf(message_buff, sizeof(message_buff),
+ format, args);
+ else
+ message_buff[0]= '\0';
+
+ return general_log_write(thd, command, message_buff, message_buff_len);
+}
+
+void LOGGER::init_error_log(ulonglong error_log_printer)
+{
+ if (error_log_printer & LOG_NONE)
+ {
+ error_log_handler_list[0]= 0;
+ return;
+ }
+
+ switch (error_log_printer) {
+ case LOG_FILE:
+ error_log_handler_list[0]= file_log_handler;
+ error_log_handler_list[1]= 0;
+ break;
+ /* these two are disabled for now */
+ case LOG_TABLE:
+ DBUG_ASSERT(0);
+ break;
+ case LOG_TABLE|LOG_FILE:
+ DBUG_ASSERT(0);
+ break;
+ }
+}
+
+void LOGGER::init_slow_log(ulonglong slow_log_printer)
+{
+ if (slow_log_printer & LOG_NONE)
+ {
+ slow_log_handler_list[0]= 0;
+ return;
+ }
+
+ switch (slow_log_printer) {
+ case LOG_FILE:
+ slow_log_handler_list[0]= file_log_handler;
+ slow_log_handler_list[1]= 0;
+ break;
+ case LOG_TABLE:
+ slow_log_handler_list[0]= table_log_handler;
+ slow_log_handler_list[1]= 0;
+ break;
+ case LOG_TABLE|LOG_FILE:
+ slow_log_handler_list[0]= file_log_handler;
+ slow_log_handler_list[1]= table_log_handler;
+ slow_log_handler_list[2]= 0;
+ break;
+ }
+}
+
+void LOGGER::init_general_log(ulonglong general_log_printer)
+{
+ if (general_log_printer & LOG_NONE)
+ {
+ general_log_handler_list[0]= 0;
+ return;
+ }
+
+ switch (general_log_printer) {
+ case LOG_FILE:
+ general_log_handler_list[0]= file_log_handler;
+ general_log_handler_list[1]= 0;
+ break;
+ case LOG_TABLE:
+ general_log_handler_list[0]= table_log_handler;
+ general_log_handler_list[1]= 0;
+ break;
+ case LOG_TABLE|LOG_FILE:
+ general_log_handler_list[0]= file_log_handler;
+ general_log_handler_list[1]= table_log_handler;
+ general_log_handler_list[2]= 0;
+ break;
+ }
+}
+
+
+bool LOGGER::activate_log_handler(THD* thd, uint log_type)
+{
+ MYSQL_QUERY_LOG *file_log;
+ bool res= FALSE;
+ lock_exclusive();
+ switch (log_type) {
+ case QUERY_LOG_SLOW:
+ if (!opt_slow_log)
+ {
+ file_log= file_log_handler->get_mysql_slow_log();
+
+ file_log->open_slow_log(opt_slow_logname);
+ if (table_log_handler->activate_log(thd, QUERY_LOG_SLOW))
+ {
+ /* Error printed by open table in activate_log() */
+ res= TRUE;
+ file_log->close(0);
+ }
+ else
+ {
+ init_slow_log(log_output_options);
+ opt_slow_log= TRUE;
+ }
+ }
+ break;
+ case QUERY_LOG_GENERAL:
+ if (!opt_log)
+ {
+ file_log= file_log_handler->get_mysql_log();
+
+ file_log->open_query_log(opt_logname);
+ if (table_log_handler->activate_log(thd, QUERY_LOG_GENERAL))
+ {
+ /* Error printed by open table in activate_log() */
+ res= TRUE;
+ file_log->close(0);
+ }
+ else
+ {
+ init_general_log(log_output_options);
+ opt_log= TRUE;
+ }
+ }
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ unlock();
+ return res;
+}
+
+
+void LOGGER::deactivate_log_handler(THD *thd, uint log_type)
+{
+ my_bool *tmp_opt= 0;
+ MYSQL_LOG *file_log;
+ LINT_INIT(file_log);
+
+ switch (log_type) {
+ case QUERY_LOG_SLOW:
+ tmp_opt= &opt_slow_log;
+ file_log= file_log_handler->get_mysql_slow_log();
+ break;
+ case QUERY_LOG_GENERAL:
+ tmp_opt= &opt_log;
+ file_log= file_log_handler->get_mysql_log();
+ break;
+ default:
+ MY_ASSERT_UNREACHABLE();
+ }
+
+ if (!(*tmp_opt))
+ return;
+
+ lock_exclusive();
+ file_log->close(0);
+ *tmp_opt= FALSE;
+ unlock();
+}
+
+
+/* the parameters are unused for the log tables */
+bool Log_to_csv_event_handler::init()
+{
+ return 0;
+}
+
+int LOGGER::set_handlers(ulonglong error_log_printer,
+ ulonglong slow_log_printer,
+ ulonglong general_log_printer)
+{
+ /* error log table is not supported yet */
+ DBUG_ASSERT(error_log_printer < LOG_TABLE);
+
+ lock_exclusive();
+
+ if ((slow_log_printer & LOG_TABLE || general_log_printer & LOG_TABLE) &&
+ !is_log_tables_initialized)
+ {
+ slow_log_printer= (slow_log_printer & ~LOG_TABLE) | LOG_FILE;
+ general_log_printer= (general_log_printer & ~LOG_TABLE) | LOG_FILE;
+
+ sql_print_error("Failed to initialize log tables. "
+ "Falling back to the old-fashioned logs");
+ }
+
+ init_error_log(error_log_printer);
+ init_slow_log(slow_log_printer);
+ init_general_log(general_log_printer);
+
+ unlock();
+
+ return 0;
+}
+
+ /*
+ Save position of binary log transaction cache.
+
+ SYNPOSIS
+ binlog_trans_log_savepos()
+
+ thd The thread to take the binlog data from
+ pos Pointer to variable where the position will be stored
+
+ DESCRIPTION
+
+ Save the current position in the binary log transaction cache into
+ the variable pointed to by 'pos'
+ */
+
+static void
+binlog_trans_log_savepos(THD *thd, my_off_t *pos)
+{
+ DBUG_ENTER("binlog_trans_log_savepos");
+ DBUG_ASSERT(pos != NULL);
+ binlog_cache_mngr *const cache_mngr= thd->binlog_setup_trx_data();
+ DBUG_ASSERT(mysql_bin_log.is_open());
+ *pos= cache_mngr->trx_cache.get_byte_position();
+ DBUG_PRINT("return", ("*pos: %lu", (ulong) *pos));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Truncate the binary log transaction cache.
+
+ SYNPOSIS
+ binlog_trans_log_truncate()
+
+ thd The thread to take the binlog data from
+ pos Position to truncate to
+
+ DESCRIPTION
+
+ Truncate the binary log to the given position. Will not change
+ anything else.
+
+ */
+static void
+binlog_trans_log_truncate(THD *thd, my_off_t pos)
+{
+ DBUG_ENTER("binlog_trans_log_truncate");
+ DBUG_PRINT("enter", ("pos: %lu", (ulong) pos));
+
+ DBUG_ASSERT(thd_get_ha_data(thd, binlog_hton) != NULL);
+ /* Only true if binlog_trans_log_savepos() wasn't called before */
+ DBUG_ASSERT(pos != ~(my_off_t) 0);
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ cache_mngr->trx_cache.restore_savepoint(pos);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ this function is mostly a placeholder.
+ conceptually, binlog initialization (now mostly done in MYSQL_BIN_LOG::open)
+ should be moved here.
+*/
+
+int binlog_init(void *p)
+{
+ binlog_hton= (handlerton *)p;
+ binlog_hton->state=opt_bin_log ? SHOW_OPTION_YES : SHOW_OPTION_NO;
+ binlog_hton->db_type=DB_TYPE_BINLOG;
+ binlog_hton->savepoint_offset= sizeof(my_off_t);
+ binlog_hton->close_connection= binlog_close_connection;
+ binlog_hton->savepoint_set= binlog_savepoint_set;
+ binlog_hton->savepoint_rollback= binlog_savepoint_rollback;
+ binlog_hton->savepoint_rollback_can_release_mdl=
+ binlog_savepoint_rollback_can_release_mdl;
+ binlog_hton->commit= binlog_commit;
+ binlog_hton->rollback= binlog_rollback;
+ binlog_hton->prepare= binlog_prepare;
+ binlog_hton->start_consistent_snapshot= binlog_start_consistent_snapshot;
+ binlog_hton->flags= HTON_NOT_USER_SELECTABLE | HTON_HIDDEN;
+ return 0;
+}
+
+static int binlog_close_connection(handlerton *hton, THD *thd)
+{
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ DBUG_ASSERT(cache_mngr->trx_cache.empty() && cache_mngr->stmt_cache.empty());
+ thd_set_ha_data(thd, binlog_hton, NULL);
+ cache_mngr->~binlog_cache_mngr();
+ my_free(cache_mngr);
+ return 0;
+}
+
+/*
+ This function flushes a cache upon commit/rollback.
+
+ SYNOPSIS
+ binlog_flush_cache()
+
+ thd The thread whose transaction should be ended
+ cache_mngr Pointer to the binlog_cache_mngr to use
+ all True if the entire transaction should be ended, false if
+ only the statement transaction should be ended.
+ end_ev The end event to use (COMMIT, ROLLBACK, or commit XID)
+ using_stmt True if the statement cache should be flushed
+ using_trx True if the transaction cache should be flushed
+
+ DESCRIPTION
+
+ End the currently transaction or statement. The transaction can be either
+ a real transaction or a statement transaction.
+
+ This can be to commit a transaction, with a COMMIT query event or an XA
+ commit XID event. But it can also be to rollback a transaction with a
+ ROLLBACK query event, used for rolling back transactions which also
+ contain updates to non-transactional tables. Or it can be a flush of
+ a statement cache.
+ */
+
+static int
+binlog_flush_cache(THD *thd, binlog_cache_mngr *cache_mngr,
+ Log_event *end_ev, bool all, bool using_stmt,
+ bool using_trx)
+{
+ int error= 0;
+ DBUG_ENTER("binlog_flush_cache");
+ DBUG_PRINT("enter", ("end_ev: %p", end_ev));
+
+ if ((using_stmt && !cache_mngr->stmt_cache.empty()) ||
+ (using_trx && !cache_mngr->trx_cache.empty()))
+ {
+ if (using_stmt && thd->binlog_flush_pending_rows_event(TRUE, FALSE))
+ DBUG_RETURN(1);
+ if (using_trx && thd->binlog_flush_pending_rows_event(TRUE, TRUE))
+ DBUG_RETURN(1);
+
+ /*
+ Doing a commit or a rollback including non-transactional tables,
+ i.e., ending a transaction where we might write the transaction
+ cache to the binary log.
+
+ We can always end the statement when ending a transaction since
+ transactions are not allowed inside stored functions. If they
+ were, we would have to ensure that we're not ending a statement
+ inside a stored function.
+ */
+ error= mysql_bin_log.write_transaction_to_binlog(thd, cache_mngr,
+ end_ev, all,
+ using_stmt, using_trx);
+ }
+ else
+ {
+ /*
+ This can happen in row-format binlog with something like
+ BEGIN; INSERT INTO nontrans_table; INSERT IGNORE INTO trans_table;
+ The nontrans_table is written directly into the binlog before commit,
+ and if the trans_table is ignored there will be no rows to write when
+ we get here.
+
+ So there is no work to do. Therefore, we will not increment any XID
+ count, so we must not decrement any XID count in unlog().
+ */
+ cache_mngr->need_unlog= 0;
+ }
+ cache_mngr->reset(using_stmt, using_trx);
+
+ DBUG_ASSERT((!using_stmt || cache_mngr->stmt_cache.empty()) &&
+ (!using_trx || cache_mngr->trx_cache.empty()));
+ DBUG_RETURN(error);
+}
+
+
+/**
+ This function flushes the stmt-cache upon commit.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache manager
+
+ @return
+ nonzero if an error pops up when flushing the cache.
+*/
+static inline int
+binlog_commit_flush_stmt_cache(THD *thd, bool all,
+ binlog_cache_mngr *cache_mngr)
+{
+ DBUG_ENTER("binlog_commit_flush_stmt_cache");
+ Query_log_event end_evt(thd, STRING_WITH_LEN("COMMIT"),
+ FALSE, TRUE, TRUE, 0);
+ DBUG_RETURN(binlog_flush_cache(thd, cache_mngr, &end_evt, all, TRUE, FALSE));
+}
+
+/**
+ This function flushes the trx-cache upon commit.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache manager
+
+ @return
+ nonzero if an error pops up when flushing the cache.
+*/
+static inline int
+binlog_commit_flush_trx_cache(THD *thd, bool all, binlog_cache_mngr *cache_mngr)
+{
+ DBUG_ENTER("binlog_commit_flush_trx_cache");
+ Query_log_event end_evt(thd, STRING_WITH_LEN("COMMIT"),
+ TRUE, TRUE, TRUE, 0);
+ DBUG_RETURN(binlog_flush_cache(thd, cache_mngr, &end_evt, all, FALSE, TRUE));
+}
+
+/**
+ This function flushes the trx-cache upon rollback.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache manager
+
+ @return
+ nonzero if an error pops up when flushing the cache.
+*/
+static inline int
+binlog_rollback_flush_trx_cache(THD *thd, bool all,
+ binlog_cache_mngr *cache_mngr)
+{
+ Query_log_event end_evt(thd, STRING_WITH_LEN("ROLLBACK"),
+ TRUE, TRUE, TRUE, 0);
+ return (binlog_flush_cache(thd, cache_mngr, &end_evt, all, FALSE, TRUE));
+}
+
+/**
+ This function flushes the trx-cache upon commit.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache manager
+ @param xid Transaction Id
+
+ @return
+ nonzero if an error pops up when flushing the cache.
+*/
+static inline int
+binlog_commit_flush_xid_caches(THD *thd, binlog_cache_mngr *cache_mngr,
+ bool all, my_xid xid)
+{
+ if (xid)
+ {
+ Xid_log_event end_evt(thd, xid, TRUE);
+ return (binlog_flush_cache(thd, cache_mngr, &end_evt, all, TRUE, TRUE));
+ }
+ else
+ {
+ /*
+ Empty xid occurs in XA COMMIT ... ONE PHASE.
+ In this case, we do not have a MySQL xid for the transaction, and the
+ external XA transaction coordinator will have to handle recovery if
+ needed. So we end the transaction with a plain COMMIT query event.
+ */
+ Query_log_event end_evt(thd, STRING_WITH_LEN("COMMIT"),
+ TRUE, TRUE, TRUE, 0);
+ return (binlog_flush_cache(thd, cache_mngr, &end_evt, all, TRUE, TRUE));
+ }
+}
+
+/**
+ This function truncates the transactional cache upon committing or rolling
+ back either a transaction or a statement.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache data to be flushed
+ @param all @c true means truncate the transaction, otherwise the
+ statement must be truncated.
+
+ @return
+ nonzero if an error pops up when truncating the transactional cache.
+*/
+static int
+binlog_truncate_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr, bool all)
+{
+ DBUG_ENTER("binlog_truncate_trx_cache");
+ int error=0;
+ /*
+ This function handles transactional changes and as such this flag
+ equals to true.
+ */
+ bool const is_transactional= TRUE;
+
+ DBUG_PRINT("info", ("thd->options={ %s %s}, transaction: %s",
+ FLAGSTR(thd->variables.option_bits, OPTION_NOT_AUTOCOMMIT),
+ FLAGSTR(thd->variables.option_bits, OPTION_BEGIN),
+ all ? "all" : "stmt"));
+
+ thd->binlog_remove_pending_rows_event(TRUE, is_transactional);
+ /*
+ If rolling back an entire transaction or a single statement not
+ inside a transaction, we reset the transaction cache.
+ */
+ if (ending_trans(thd, all))
+ {
+ if (cache_mngr->trx_cache.has_incident())
+ error= mysql_bin_log.write_incident(thd);
+
+ thd->clear_binlog_table_maps();
+
+ cache_mngr->reset(false, true);
+ }
+ /*
+ If rolling back a statement in a transaction, we truncate the
+ transaction cache to remove the statement.
+ */
+ else
+ cache_mngr->trx_cache.restore_prev_position();
+
+ DBUG_ASSERT(thd->binlog_get_pending_rows_event(is_transactional) == NULL);
+ DBUG_RETURN(error);
+}
+
+static int binlog_prepare(handlerton *hton, THD *thd, bool all)
+{
+ /*
+ do nothing.
+ just pretend we can do 2pc, so that MySQL won't
+ switch to 1pc.
+ real work will be done in MYSQL_BIN_LOG::log_and_order()
+ */
+ return 0;
+}
+
+/*
+ We flush the cache wrapped in a beging/rollback if:
+ . aborting a single or multi-statement transaction and;
+ . the OPTION_KEEP_LOG is active or;
+ . the format is STMT and a non-trans table was updated or;
+ . the format is MIXED and a temporary non-trans table was
+ updated or;
+ . the format is MIXED, non-trans table was updated and
+ aborting a single statement transaction;
+*/
+static bool trans_cannot_safely_rollback(THD *thd, bool all)
+{
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ return ((thd->variables.option_bits & OPTION_KEEP_LOG) ||
+ (trans_has_updated_non_trans_table(thd) &&
+ thd->variables.binlog_format == BINLOG_FORMAT_STMT) ||
+ (cache_mngr->trx_cache.changes_to_non_trans_temp_table() &&
+ thd->variables.binlog_format == BINLOG_FORMAT_MIXED) ||
+ (trans_has_updated_non_trans_table(thd) &&
+ ending_single_stmt_trans(thd,all) &&
+ thd->variables.binlog_format == BINLOG_FORMAT_MIXED));
+}
+
+
+/**
+ This function is called once after each statement.
+
+ It has the responsibility to flush the caches to the binary log on commits.
+
+ @param hton The binlog handlerton.
+ @param thd The client thread that executes the transaction.
+ @param all This is @c true if this is a real transaction commit, and
+ @false otherwise.
+
+ @see handlerton::commit
+*/
+static int binlog_commit(handlerton *hton, THD *thd, bool all)
+{
+ int error= 0;
+ DBUG_ENTER("binlog_commit");
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ DBUG_PRINT("debug",
+ ("all: %d, in_transaction: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
+ all,
+ YESNO(thd->in_multi_stmt_transaction_mode()),
+ YESNO(thd->transaction.all.modified_non_trans_table),
+ YESNO(thd->transaction.stmt.modified_non_trans_table)));
+
+ if (!cache_mngr->stmt_cache.empty())
+ {
+ error= binlog_commit_flush_stmt_cache(thd, all, cache_mngr);
+ }
+
+ if (cache_mngr->trx_cache.empty())
+ {
+ /*
+ we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid()
+ */
+ cache_mngr->reset(false, true);
+ DBUG_RETURN(error);
+ }
+
+ /*
+ We commit the transaction if:
+ - We are not in a transaction and committing a statement, or
+ - We are in a transaction and a full transaction is committed.
+ Otherwise, we accumulate the changes.
+ */
+ if (!error && ending_trans(thd, all))
+ error= binlog_commit_flush_trx_cache(thd, all, cache_mngr);
+
+ /*
+ This is part of the stmt rollback.
+ */
+ if (!all)
+ cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);
+
+ DBUG_RETURN(error);
+}
+
+/**
+ This function is called when a transaction or a statement is rolled back.
+
+ @param hton The binlog handlerton.
+ @param thd The client thread that executes the transaction.
+ @param all This is @c true if this is a real transaction rollback, and
+ @false otherwise.
+
+ @see handlerton::rollback
+*/
+static int binlog_rollback(handlerton *hton, THD *thd, bool all)
+{
+ DBUG_ENTER("binlog_rollback");
+ int error= 0;
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ DBUG_PRINT("debug", ("all: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
+ YESNO(all),
+ YESNO(thd->transaction.all.modified_non_trans_table),
+ YESNO(thd->transaction.stmt.modified_non_trans_table)));
+
+ /*
+ If an incident event is set we do not flush the content of the statement
+ cache because it may be corrupted.
+ */
+ if (cache_mngr->stmt_cache.has_incident())
+ {
+ error= mysql_bin_log.write_incident(thd);
+ cache_mngr->reset(true, false);
+ }
+ else if (!cache_mngr->stmt_cache.empty())
+ {
+ error= binlog_commit_flush_stmt_cache(thd, all, cache_mngr);
+ }
+
+ if (cache_mngr->trx_cache.empty())
+ {
+ /*
+ we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid()
+ */
+ cache_mngr->reset(false, true);
+ DBUG_RETURN(error);
+ }
+
+ if (mysql_bin_log.check_write_error(thd))
+ {
+ /*
+ "all == true" means that a "rollback statement" triggered the error and
+ this function was called. However, this must not happen as a rollback
+ is written directly to the binary log. And in auto-commit mode, a single
+ statement that is rolled back has the flag all == false.
+ */
+ DBUG_ASSERT(!all);
+ /*
+ We reach this point if the effect of a statement did not properly get into
+ a cache and need to be rolled back.
+ */
+ error |= binlog_truncate_trx_cache(thd, cache_mngr, all);
+ }
+ else if (!error)
+ {
+ if (ending_trans(thd, all) && trans_cannot_safely_rollback(thd, all))
+ error= binlog_rollback_flush_trx_cache(thd, all, cache_mngr);
+ /*
+ Truncate the cache if:
+ . aborting a single or multi-statement transaction or;
+ . the OPTION_KEEP_LOG is not active and;
+ . the format is not STMT or no non-trans table was
+ updated and;
+ . the format is not MIXED or no temporary non-trans table
+ was updated.
+ */
+ else if (ending_trans(thd, all) ||
+ (!(thd->variables.option_bits & OPTION_KEEP_LOG) &&
+ (!stmt_has_updated_non_trans_table(thd) ||
+ thd->variables.binlog_format != BINLOG_FORMAT_STMT) &&
+ (!cache_mngr->trx_cache.changes_to_non_trans_temp_table() ||
+ thd->variables.binlog_format != BINLOG_FORMAT_MIXED)))
+ error= binlog_truncate_trx_cache(thd, cache_mngr, all);
+ }
+
+ /*
+ This is part of the stmt rollback.
+ */
+ if (!all)
+ cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);
+
+ DBUG_RETURN(error);
+}
+
+
+void binlog_reset_cache(THD *thd)
+{
+ binlog_cache_mngr *const cache_mngr= opt_bin_log ?
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton) : 0;
+ DBUG_ENTER("binlog_reset_cache");
+ if (cache_mngr)
+ {
+ thd->binlog_remove_pending_rows_event(TRUE, TRUE);
+ cache_mngr->reset(true, true);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void MYSQL_BIN_LOG::set_write_error(THD *thd, bool is_transactional)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::set_write_error");
+
+ write_error= 1;
+
+ if (check_write_error(thd))
+ DBUG_VOID_RETURN;
+
+ if (my_errno == EFBIG)
+ {
+ if (is_transactional)
+ {
+ my_message(ER_TRANS_CACHE_FULL, ER(ER_TRANS_CACHE_FULL), MYF(MY_WME));
+ }
+ else
+ {
+ my_message(ER_STMT_CACHE_FULL, ER(ER_STMT_CACHE_FULL), MYF(MY_WME));
+ }
+ }
+ else
+ {
+ my_error(ER_ERROR_ON_WRITE, MYF(MY_WME), name, errno);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+bool MYSQL_BIN_LOG::check_write_error(THD *thd)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::check_write_error");
+
+ bool checked= FALSE;
+
+ if (!thd->is_error())
+ DBUG_RETURN(checked);
+
+ switch (thd->get_stmt_da()->sql_errno())
+ {
+ case ER_TRANS_CACHE_FULL:
+ case ER_STMT_CACHE_FULL:
+ case ER_ERROR_ON_WRITE:
+ case ER_BINLOG_LOGGING_IMPOSSIBLE:
+ checked= TRUE;
+ break;
+ }
+
+ DBUG_RETURN(checked);
+}
+
+
+/**
+ @note
+ How do we handle this (unlikely but legal) case:
+ @verbatim
+ [transaction] + [update to non-trans table] + [rollback to savepoint] ?
+ @endverbatim
+ The problem occurs when a savepoint is before the update to the
+ non-transactional table. Then when there's a rollback to the savepoint, if we
+ simply truncate the binlog cache, we lose the part of the binlog cache where
+ the update is. If we want to not lose it, we need to write the SAVEPOINT
+ command and the ROLLBACK TO SAVEPOINT command to the binlog cache. The latter
+ is easy: it's just write at the end of the binlog cache, but the former
+ should be *inserted* to the place where the user called SAVEPOINT. The
+ solution is that when the user calls SAVEPOINT, we write it to the binlog
+ cache (so no need to later insert it). As transactions are never intermixed
+ in the binary log (i.e. they are serialized), we won't have conflicts with
+ savepoint names when using mysqlbinlog or in the slave SQL thread.
+ Then when ROLLBACK TO SAVEPOINT is called, if we updated some
+ non-transactional table, we don't truncate the binlog cache but instead write
+ ROLLBACK TO SAVEPOINT to it; otherwise we truncate the binlog cache (which
+ will chop the SAVEPOINT command from the binlog cache, which is good as in
+ that case there is no need to have it in the binlog).
+*/
+
+static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv)
+{
+ DBUG_ENTER("binlog_savepoint_set");
+ int error= 1;
+
+ char buf[1024];
+ String log_query(buf, sizeof(buf), &my_charset_bin);
+ if (log_query.copy(STRING_WITH_LEN("SAVEPOINT "), &my_charset_bin) ||
+ append_identifier(thd, &log_query,
+ thd->lex->ident.str, thd->lex->ident.length))
+ DBUG_RETURN(1);
+ int errcode= query_error_code(thd, thd->killed == NOT_KILLED);
+ Query_log_event qinfo(thd, log_query.c_ptr_safe(), log_query.length(),
+ TRUE, FALSE, TRUE, errcode);
+ /*
+ We cannot record the position before writing the statement
+ because a rollback to a savepoint (.e.g. consider it "S") would
+ prevent the savepoint statement (i.e. "SAVEPOINT S") from being
+ written to the binary log despite the fact that the server could
+ still issue other rollback statements to the same savepoint (i.e.
+ "S").
+ Given that the savepoint is valid until the server releases it,
+ ie, until the transaction commits or it is released explicitly,
+ we need to log it anyway so that we don't have "ROLLBACK TO S"
+ or "RELEASE S" without the preceding "SAVEPOINT S" in the binary
+ log.
+ */
+ if (!(error= mysql_bin_log.write(&qinfo)))
+ binlog_trans_log_savepos(thd, (my_off_t*) sv);
+
+ DBUG_RETURN(error);
+}
+
+static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv)
+{
+ DBUG_ENTER("binlog_savepoint_rollback");
+
+ /*
+ Write ROLLBACK TO SAVEPOINT to the binlog cache if we have updated some
+ non-transactional table. Otherwise, truncate the binlog cache starting
+ from the SAVEPOINT command.
+ */
+ if (unlikely(trans_has_updated_non_trans_table(thd) ||
+ (thd->variables.option_bits & OPTION_KEEP_LOG)))
+ {
+ char buf[1024];
+ String log_query(buf, sizeof(buf), &my_charset_bin);
+ if (log_query.copy(STRING_WITH_LEN("ROLLBACK TO "), &my_charset_bin) ||
+ append_identifier(thd, &log_query,
+ thd->lex->ident.str, thd->lex->ident.length))
+ DBUG_RETURN(1);
+ int errcode= query_error_code(thd, thd->killed == NOT_KILLED);
+ Query_log_event qinfo(thd, log_query.ptr(), log_query.length(),
+ TRUE, FALSE, TRUE, errcode);
+ DBUG_RETURN(mysql_bin_log.write(&qinfo));
+ }
+ binlog_trans_log_truncate(thd, *(my_off_t*)sv);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Check whether binlog state allows to safely release MDL locks after
+ rollback to savepoint.
+
+ @param hton The binlog handlerton.
+ @param thd The client thread that executes the transaction.
+
+ @return true - It is safe to release MDL locks.
+ false - If it is not.
+*/
+static bool binlog_savepoint_rollback_can_release_mdl(handlerton *hton,
+ THD *thd)
+{
+ DBUG_ENTER("binlog_savepoint_rollback_can_release_mdl");
+ /*
+ If we have not updated any non-transactional tables rollback
+ to savepoint will simply truncate binlog cache starting from
+ SAVEPOINT command. So it should be safe to release MDL acquired
+ after SAVEPOINT command in this case.
+ */
+ DBUG_RETURN(!trans_cannot_safely_rollback(thd, true));
+}
+
+
+int check_binlog_magic(IO_CACHE* log, const char** errmsg)
+{
+ uchar magic[4];
+ DBUG_ASSERT(my_b_tell(log) == 0);
+
+ if (my_b_read(log, magic, sizeof(magic)))
+ {
+ *errmsg = "I/O error reading the header from the binary log";
+ sql_print_error("%s, errno=%d, io cache code=%d", *errmsg, my_errno,
+ log->error);
+ return 1;
+ }
+ if (bcmp(magic, BINLOG_MAGIC, sizeof(magic)))
+ {
+ *errmsg = "Binlog has bad magic number; It's not a binary log file that can be used by this version of MySQL";
+ return 1;
+ }
+ return 0;
+}
+
+
+File open_binlog(IO_CACHE *log, const char *log_file_name, const char **errmsg)
+{
+ File file;
+ DBUG_ENTER("open_binlog");
+
+ if ((file= mysql_file_open(key_file_binlog,
+ log_file_name, O_RDONLY | O_BINARY | O_SHARE,
+ MYF(MY_WME))) < 0)
+ {
+ sql_print_error("Failed to open log (file '%s', errno %d)",
+ log_file_name, my_errno);
+ *errmsg = "Could not open log file";
+ goto err;
+ }
+ if (init_io_cache(log, file, IO_SIZE*2, READ_CACHE, 0, 0,
+ MYF(MY_WME|MY_DONT_CHECK_FILESIZE)))
+ {
+ sql_print_error("Failed to create a cache on log (file '%s')",
+ log_file_name);
+ *errmsg = "Could not open log file";
+ goto err;
+ }
+ if (check_binlog_magic(log,errmsg))
+ goto err;
+ DBUG_RETURN(file);
+
+err:
+ if (file >= 0)
+ {
+ mysql_file_close(file, MYF(0));
+ end_io_cache(log);
+ }
+ DBUG_RETURN(-1);
+}
+
+#ifdef _WIN32
+static int eventSource = 0;
+
+static void setup_windows_event_source()
+{
+ HKEY hRegKey= NULL;
+ DWORD dwError= 0;
+ TCHAR szPath[MAX_PATH];
+ DWORD dwTypes;
+
+ if (eventSource) // Ensure that we are only called once
+ return;
+ eventSource= 1;
+
+ // Create the event source registry key
+ dwError= RegCreateKey(HKEY_LOCAL_MACHINE,
+ "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\MySQL",
+ &hRegKey);
+
+ /* Name of the PE module that contains the message resource */
+ GetModuleFileName(NULL, szPath, MAX_PATH);
+
+ /* Register EventMessageFile */
+ dwError = RegSetValueEx(hRegKey, "EventMessageFile", 0, REG_EXPAND_SZ,
+ (PBYTE) szPath, (DWORD) (strlen(szPath) + 1));
+
+ /* Register supported event types */
+ dwTypes= (EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
+ EVENTLOG_INFORMATION_TYPE);
+ dwError= RegSetValueEx(hRegKey, "TypesSupported", 0, REG_DWORD,
+ (LPBYTE) &dwTypes, sizeof dwTypes);
+
+ RegCloseKey(hRegKey);
+}
+
+#endif /* _WIN32 */
+
+
+/**
+ Find a unique filename for 'filename.#'.
+
+ Set '#' to the number next to the maximum found in the most
+ recent log file extension.
+
+ This function will return nonzero if: (i) the generated name
+ exceeds FN_REFLEN; (ii) if the number of extensions is exhausted;
+ or (iii) some other error happened while examining the filesystem.
+
+ @return
+ nonzero if not possible to get unique filename.
+*/
+
+static int find_uniq_filename(char *name)
+{
+ uint i;
+ char buff[FN_REFLEN], ext_buf[FN_REFLEN];
+ struct st_my_dir *dir_info;
+ reg1 struct fileinfo *file_info;
+ ulong max_found= 0, next= 0, number= 0;
+ size_t buf_length, length;
+ char *start, *end;
+ int error= 0;
+ DBUG_ENTER("find_uniq_filename");
+
+ length= dirname_part(buff, name, &buf_length);
+ start= name + length;
+ end= strend(start);
+
+ *end='.';
+ length= (size_t) (end - start + 1);
+
+ if ((DBUG_EVALUATE_IF("error_unique_log_filename", 1,
+ !(dir_info= my_dir(buff,MYF(MY_DONT_SORT))))))
+ { // This shouldn't happen
+ strmov(end,".1"); // use name+1
+ DBUG_RETURN(1);
+ }
+ file_info= dir_info->dir_entry;
+ for (i= dir_info->number_of_files ; i-- ; file_info++)
+ {
+ if (strncmp(file_info->name, start, length) == 0 &&
+ test_if_number(file_info->name+length, &number,0))
+ {
+ set_if_bigger(max_found,(ulong) number);
+ }
+ }
+ my_dirend(dir_info);
+
+ /* check if reached the maximum possible extension number */
+ if (max_found == MAX_LOG_UNIQUE_FN_EXT)
+ {
+ sql_print_error("Log filename extension number exhausted: %06lu. \
+Please fix this by archiving old logs and \
+updating the index files.", max_found);
+ error= 1;
+ goto end;
+ }
+
+ next= max_found + 1;
+ if (sprintf(ext_buf, "%06lu", next)<0)
+ {
+ error= 1;
+ goto end;
+ }
+ *end++='.';
+
+ /*
+ Check if the generated extension size + the file name exceeds the
+ buffer size used. If one did not check this, then the filename might be
+ truncated, resulting in error.
+ */
+ if (((strlen(ext_buf) + (end - name)) >= FN_REFLEN))
+ {
+ sql_print_error("Log filename too large: %s%s (%zu). \
+Please fix this by archiving old logs and updating the \
+index files.", name, ext_buf, (strlen(ext_buf) + (end - name)));
+ error= 1;
+ goto end;
+ }
+
+ if (sprintf(end, "%06lu", next)<0)
+ {
+ error= 1;
+ goto end;
+ }
+
+ /* print warning if reaching the end of available extensions. */
+ if ((next > (MAX_LOG_UNIQUE_FN_EXT - LOG_WARN_UNIQUE_FN_EXT_LEFT)))
+ sql_print_warning("Next log extension: %lu. \
+Remaining log filename extensions: %lu. \
+Please consider archiving some logs.", next, (MAX_LOG_UNIQUE_FN_EXT - next));
+
+end:
+ DBUG_RETURN(error);
+}
+
+
+void MYSQL_LOG::init(enum_log_type log_type_arg,
+ enum cache_type io_cache_type_arg)
+{
+ DBUG_ENTER("MYSQL_LOG::init");
+ log_type= log_type_arg;
+ io_cache_type= io_cache_type_arg;
+ DBUG_PRINT("info",("log_type: %d", log_type));
+ DBUG_VOID_RETURN;
+}
+
+
+bool MYSQL_LOG::init_and_set_log_file_name(const char *log_name,
+ const char *new_name,
+ enum_log_type log_type_arg,
+ enum cache_type io_cache_type_arg)
+{
+ init(log_type_arg, io_cache_type_arg);
+
+ if (new_name && !strmov(log_file_name, new_name))
+ return TRUE;
+ else if (!new_name && generate_new_name(log_file_name, log_name))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/*
+ Open a (new) log file.
+
+ SYNOPSIS
+ open()
+
+ log_name The name of the log to open
+ log_type_arg The type of the log. E.g. LOG_NORMAL
+ new_name The new name for the logfile. This is only needed
+ when the method is used to open the binlog file.
+ io_cache_type_arg The type of the IO_CACHE to use for this log file
+
+ DESCRIPTION
+ Open the logfile, init IO_CACHE and write startup messages
+ (in case of general and slow query logs).
+
+ RETURN VALUES
+ 0 ok
+ 1 error
+*/
+
+bool MYSQL_LOG::open(
+#ifdef HAVE_PSI_INTERFACE
+ PSI_file_key log_file_key,
+#endif
+ const char *log_name, enum_log_type log_type_arg,
+ const char *new_name, enum cache_type io_cache_type_arg)
+{
+ char buff[FN_REFLEN];
+ MY_STAT f_stat;
+ File file= -1;
+ my_off_t seek_offset;
+ bool is_fifo = false;
+ int open_flags= O_CREAT | O_BINARY;
+ DBUG_ENTER("MYSQL_LOG::open");
+ DBUG_PRINT("enter", ("log_type: %d", (int) log_type_arg));
+
+ write_error= 0;
+
+ if (!(name= my_strdup(log_name, MYF(MY_WME))))
+ {
+ name= (char *)log_name; // for the error message
+ goto err;
+ }
+
+ if (init_and_set_log_file_name(name, new_name,
+ log_type_arg, io_cache_type_arg))
+ goto err;
+
+ is_fifo = my_stat(log_file_name, &f_stat, MYF(0)) &&
+ MY_S_ISFIFO(f_stat.st_mode);
+
+ if (io_cache_type == SEQ_READ_APPEND)
+ open_flags |= O_RDWR | O_APPEND;
+ else
+ open_flags |= O_WRONLY | (log_type == LOG_BIN ? 0 : O_APPEND);
+
+ if (is_fifo)
+ open_flags |= O_NONBLOCK;
+
+ db[0]= 0;
+
+#ifdef HAVE_PSI_INTERFACE
+ /* Keep the key for reopen */
+ m_log_file_key= log_file_key;
+#endif
+
+ if ((file= mysql_file_open(log_file_key, log_file_name, open_flags,
+ MYF(MY_WME | ME_WAITTANG))) < 0)
+ goto err;
+
+ if (is_fifo)
+ seek_offset= 0;
+ else if ((seek_offset= mysql_file_tell(file, MYF(MY_WME))))
+ goto err;
+
+ if (init_io_cache(&log_file, file, IO_SIZE, io_cache_type, seek_offset, 0,
+ MYF(MY_WME | MY_NABP |
+ ((log_type == LOG_BIN) ? MY_WAIT_IF_FULL : 0))))
+ goto err;
+
+ if (log_type == LOG_NORMAL)
+ {
+ char *end;
+ int len=my_snprintf(buff, sizeof(buff), "%s, Version: %s (%s). "
+#ifdef EMBEDDED_LIBRARY
+ "embedded library\n",
+ my_progname, server_version, MYSQL_COMPILATION_COMMENT
+#elif _WIN32
+ "started with:\nTCP Port: %d, Named Pipe: %s\n",
+ my_progname, server_version, MYSQL_COMPILATION_COMMENT,
+ mysqld_port, mysqld_unix_port
+#else
+ "started with:\nTcp port: %d Unix socket: %s\n",
+ my_progname, server_version, MYSQL_COMPILATION_COMMENT,
+ mysqld_port, mysqld_unix_port
+#endif
+ );
+ end= strnmov(buff + len, "Time Id Command Argument\n",
+ sizeof(buff) - len);
+ if (my_b_write(&log_file, (uchar*) buff, (uint) (end-buff)) ||
+ flush_io_cache(&log_file))
+ goto err;
+ }
+
+ log_state= LOG_OPENED;
+ DBUG_RETURN(0);
+
+err:
+ sql_print_error("Could not use %s for logging (error %d). \
+Turning logging off for the whole duration of the MySQL server process. \
+To turn it on again: fix the cause, \
+shutdown the MySQL server and restart it.", name, errno);
+ if (file >= 0)
+ mysql_file_close(file, MYF(0));
+ end_io_cache(&log_file);
+ my_free(name);
+ name= NULL;
+ log_state= LOG_CLOSED;
+ DBUG_RETURN(1);
+}
+
+MYSQL_LOG::MYSQL_LOG()
+ : name(0), write_error(FALSE), inited(FALSE), log_type(LOG_UNKNOWN),
+ log_state(LOG_CLOSED)
+{
+ /*
+ We don't want to initialize LOCK_Log here as such initialization depends on
+ safe_mutex (when using safe_mutex) which depends on MY_INIT(), which is
+ called only in main(). Doing initialization here would make it happen
+ before main().
+ */
+ bzero((char*) &log_file, sizeof(log_file));
+}
+
+void MYSQL_LOG::init_pthread_objects()
+{
+ DBUG_ASSERT(inited == 0);
+ inited= 1;
+ mysql_mutex_init(key_LOG_LOCK_log, &LOCK_log, MY_MUTEX_INIT_SLOW);
+}
+
+/*
+ Close the log file
+
+ SYNOPSIS
+ close()
+ exiting Bitmask. LOG_CLOSE_TO_BE_OPENED is used if we intend to call
+ open at once after close. LOG_CLOSE_DELAYED_CLOSE is used for
+ binlog rotation, to delay actual close of the old file until
+ we have successfully created the new file.
+
+ NOTES
+ One can do an open on the object at once after doing a close.
+ The internal structures are not freed until cleanup() is called
+*/
+
+void MYSQL_LOG::close(uint exiting)
+{ // One can't set log_type here!
+ DBUG_ENTER("MYSQL_LOG::close");
+ DBUG_PRINT("enter",("exiting: %d", (int) exiting));
+ if (log_state == LOG_OPENED)
+ {
+ end_io_cache(&log_file);
+
+ if (log_type == LOG_BIN && mysql_file_sync(log_file.file, MYF(MY_WME)) && ! write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER_THD_OR_DEFAULT(current_thd, ER_ERROR_ON_WRITE), name, errno);
+ }
+
+ if (!(exiting & LOG_CLOSE_DELAYED_CLOSE) &&
+ mysql_file_close(log_file.file, MYF(MY_WME)) && ! write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER_THD_OR_DEFAULT(current_thd, ER_ERROR_ON_WRITE), name, errno);
+ }
+ }
+
+ log_state= (exiting & LOG_CLOSE_TO_BE_OPENED) ? LOG_TO_BE_OPENED : LOG_CLOSED;
+ my_free(name);
+ name= NULL;
+ DBUG_VOID_RETURN;
+}
+
+/** This is called only once. */
+
+void MYSQL_LOG::cleanup()
+{
+ DBUG_ENTER("cleanup");
+ if (inited)
+ {
+ inited= 0;
+ mysql_mutex_destroy(&LOCK_log);
+ close(0);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+int MYSQL_LOG::generate_new_name(char *new_name, const char *log_name)
+{
+ fn_format(new_name, log_name, mysql_data_home, "", 4);
+ if (log_type == LOG_BIN)
+ {
+ if (!fn_ext(log_name)[0])
+ {
+ if (DBUG_EVALUATE_IF("binlog_inject_new_name_error", TRUE, FALSE) ||
+ find_uniq_filename(new_name))
+ {
+ if (current_thd)
+ my_printf_error(ER_NO_UNIQUE_LOGFILE, ER(ER_NO_UNIQUE_LOGFILE),
+ MYF(ME_FATALERROR), log_name);
+ sql_print_error(ER_DEFAULT(ER_NO_UNIQUE_LOGFILE), log_name);
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*
+ Reopen the log file
+
+ SYNOPSIS
+ reopen_file()
+
+ DESCRIPTION
+ Reopen the log file. The method is used during FLUSH LOGS
+ and locks LOCK_log mutex
+*/
+
+
+void MYSQL_QUERY_LOG::reopen_file()
+{
+ char *save_name;
+
+ DBUG_ENTER("MYSQL_LOG::reopen_file");
+ if (!is_open())
+ {
+ DBUG_PRINT("info",("log is closed"));
+ DBUG_VOID_RETURN;
+ }
+
+ mysql_mutex_lock(&LOCK_log);
+
+ save_name= name;
+ name= 0; // Don't free name
+ close(LOG_CLOSE_TO_BE_OPENED);
+
+ /*
+ Note that at this point, log_state != LOG_CLOSED (important for is_open()).
+ */
+
+ open(
+#ifdef HAVE_PSI_INTERFACE
+ m_log_file_key,
+#endif
+ save_name, log_type, 0, io_cache_type);
+ my_free(save_name);
+
+ mysql_mutex_unlock(&LOCK_log);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Write a command to traditional general log file
+
+ SYNOPSIS
+ write()
+
+ event_time command start timestamp
+ user_host the pointer to the string with user@host info
+ user_host_len length of the user_host string. this is computed once
+ and passed to all general log event handlers
+ thread_id Id of the thread, issued a query
+ command_type the type of the command being logged
+ command_type_len the length of the string above
+ sql_text the very text of the query being executed
+ sql_text_len the length of sql_text string
+
+ DESCRIPTION
+
+ Log given command to to normal (not rotable) log file
+
+ RETURN
+ FASE - OK
+ TRUE - error occured
+*/
+
+bool MYSQL_QUERY_LOG::write(time_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len)
+{
+ char buff[32];
+ uint length= 0;
+ char local_time_buff[MAX_TIME_SIZE];
+ struct tm start;
+ uint time_buff_len= 0;
+
+ mysql_mutex_lock(&LOCK_log);
+
+ /* Test if someone closed between the is_open test and lock */
+ if (is_open())
+ {
+ /* for testing output of timestamp and thread id */
+ DBUG_EXECUTE_IF("reset_log_last_time", last_time= 0;);
+
+ /* Note that my_b_write() assumes it knows the length for this */
+ if (event_time != last_time)
+ {
+ last_time= event_time;
+
+ localtime_r(&event_time, &start);
+
+ time_buff_len= my_snprintf(local_time_buff, MAX_TIME_SIZE,
+ "%02d%02d%02d %2d:%02d:%02d\t",
+ start.tm_year % 100, start.tm_mon + 1,
+ start.tm_mday, start.tm_hour,
+ start.tm_min, start.tm_sec);
+
+ if (my_b_write(&log_file, (uchar*) local_time_buff, time_buff_len))
+ goto err;
+ }
+ else
+ if (my_b_write(&log_file, (uchar*) "\t\t" ,2) < 0)
+ goto err;
+
+ /* command_type, thread_id */
+ length= my_snprintf(buff, 32, "%5ld ", (long) thread_id);
+
+ if (my_b_write(&log_file, (uchar*) buff, length))
+ goto err;
+
+ if (my_b_write(&log_file, (uchar*) command_type, command_type_len))
+ goto err;
+
+ if (my_b_write(&log_file, (uchar*) "\t", 1))
+ goto err;
+
+ /* sql_text */
+ if (my_b_write(&log_file, (uchar*) sql_text, sql_text_len))
+ goto err;
+
+ if (my_b_write(&log_file, (uchar*) "\n", 1) ||
+ flush_io_cache(&log_file))
+ goto err;
+ }
+
+ mysql_mutex_unlock(&LOCK_log);
+ return FALSE;
+err:
+
+ if (!write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
+ }
+ mysql_mutex_unlock(&LOCK_log);
+ return TRUE;
+}
+
+
+/*
+ Log a query to the traditional slow log file
+
+ SYNOPSIS
+ write()
+
+ thd THD of the query
+ current_time current timestamp
+ user_host the pointer to the string with user@host info
+ user_host_len length of the user_host string. this is computed once
+ and passed to all general log event handlers
+ query_utime Amount of time the query took to execute (in microseconds)
+ lock_utime Amount of time the query was locked (in microseconds)
+ is_command The flag, which determines, whether the sql_text is a
+ query or an administrator command.
+ sql_text the very text of the query or administrator command
+ processed
+ sql_text_len the length of sql_text string
+
+ DESCRIPTION
+
+ Log a query to the slow log file.
+
+ RETURN
+ FALSE - OK
+ TRUE - error occured
+*/
+
+bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time,
+ const char *user_host,
+ uint user_host_len, ulonglong query_utime,
+ ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len)
+{
+ bool error= 0;
+ DBUG_ENTER("MYSQL_QUERY_LOG::write");
+
+ mysql_mutex_lock(&LOCK_log);
+
+ if (!is_open())
+ {
+ mysql_mutex_unlock(&LOCK_log);
+ DBUG_RETURN(0);
+ }
+
+ if (is_open())
+ { // Safety agains reopen
+ int tmp_errno= 0;
+ char buff[80], *end;
+ char query_time_buff[22+7], lock_time_buff[22+7];
+ uint buff_len;
+ end= buff;
+
+ if (!(specialflag & SPECIAL_SHORT_LOG_FORMAT))
+ {
+ if (current_time != last_time)
+ {
+ last_time= current_time;
+ struct tm start;
+ localtime_r(&current_time, &start);
+
+ buff_len= my_snprintf(buff, sizeof buff,
+ "# Time: %02d%02d%02d %2d:%02d:%02d\n",
+ start.tm_year % 100, start.tm_mon + 1,
+ start.tm_mday, start.tm_hour,
+ start.tm_min, start.tm_sec);
+
+ /* Note that my_b_write() assumes it knows the length for this */
+ if (my_b_write(&log_file, (uchar*) buff, buff_len))
+ tmp_errno= errno;
+ }
+ const uchar uh[]= "# User@Host: ";
+ if (my_b_write(&log_file, uh, sizeof(uh) - 1))
+ tmp_errno= errno;
+ if (my_b_write(&log_file, (uchar*) user_host, user_host_len))
+ tmp_errno= errno;
+ if (my_b_write(&log_file, (uchar*) "\n", 1))
+ tmp_errno= errno;
+ }
+
+ /* For slow query log */
+ sprintf(query_time_buff, "%.6f", ulonglong2double(query_utime)/1000000.0);
+ sprintf(lock_time_buff, "%.6f", ulonglong2double(lock_utime)/1000000.0);
+ if (my_b_printf(&log_file,
+ "# Thread_id: %lu Schema: %s QC_hit: %s\n" \
+ "# Query_time: %s Lock_time: %s Rows_sent: %lu Rows_examined: %lu\n",
+ (ulong) thd->thread_id, (thd->db ? thd->db : ""),
+ ((thd->query_plan_flags & QPLAN_QC) ? "Yes" : "No"),
+ query_time_buff, lock_time_buff,
+ (ulong) thd->get_sent_row_count(),
+ (ulong) thd->get_examined_row_count()) == (size_t) -1)
+ tmp_errno= errno;
+ if ((thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_QUERY_PLAN) &&
+ (thd->query_plan_flags &
+ (QPLAN_FULL_SCAN | QPLAN_FULL_JOIN | QPLAN_TMP_TABLE |
+ QPLAN_TMP_DISK | QPLAN_FILESORT | QPLAN_FILESORT_DISK)) &&
+ my_b_printf(&log_file,
+ "# Full_scan: %s Full_join: %s "
+ "Tmp_table: %s Tmp_table_on_disk: %s\n"
+ "# Filesort: %s Filesort_on_disk: %s Merge_passes: %lu "
+ "Priority_queue: %s\n",
+ ((thd->query_plan_flags & QPLAN_FULL_SCAN) ? "Yes" : "No"),
+ ((thd->query_plan_flags & QPLAN_FULL_JOIN) ? "Yes" : "No"),
+ ((thd->query_plan_flags & QPLAN_TMP_TABLE) ? "Yes" : "No"),
+ ((thd->query_plan_flags & QPLAN_TMP_DISK) ? "Yes" : "No"),
+ ((thd->query_plan_flags & QPLAN_FILESORT) ? "Yes" : "No"),
+ ((thd->query_plan_flags & QPLAN_FILESORT_DISK) ?
+ "Yes" : "No"),
+ thd->query_plan_fsort_passes,
+ ((thd->query_plan_flags & QPLAN_FILESORT_PRIORITY_QUEUE) ?
+ "Yes" : "No")
+ ) == (size_t) -1)
+ tmp_errno= errno;
+ if (thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_EXPLAIN &&
+ thd->lex->explain)
+ {
+ StringBuffer<128> buf;
+ DBUG_ASSERT(!thd->free_list);
+ if (!print_explain_query(thd->lex, thd, &buf))
+ my_b_printf(&log_file, "%s", buf.c_ptr_safe());
+ thd->free_items();
+ }
+ if (thd->db && strcmp(thd->db, db))
+ { // Database changed
+ if (my_b_printf(&log_file,"use %s;\n",thd->db) == (size_t) -1)
+ tmp_errno= errno;
+ strmov(db,thd->db);
+ }
+ if (thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt)
+ {
+ end=strmov(end, ",last_insert_id=");
+ end=longlong10_to_str((longlong)
+ thd->first_successful_insert_id_in_prev_stmt_for_binlog,
+ end, -10);
+ }
+ // Save value if we do an insert.
+ if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0)
+ {
+ if (!(specialflag & SPECIAL_SHORT_LOG_FORMAT))
+ {
+ end=strmov(end,",insert_id=");
+ end=longlong10_to_str((longlong)
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.minimum(),
+ end, -10);
+ }
+ }
+
+ /*
+ This info used to show up randomly, depending on whether the query
+ checked the query start time or not. now we always write current
+ timestamp to the slow log
+ */
+ end= strmov(end, ",timestamp=");
+ end= int10_to_str((long) current_time, end, 10);
+
+ if (end != buff)
+ {
+ *end++=';';
+ *end='\n';
+ if (my_b_write(&log_file, (uchar*) "SET ", 4) ||
+ my_b_write(&log_file, (uchar*) buff + 1, (uint) (end-buff)))
+ tmp_errno= errno;
+ }
+ if (is_command)
+ {
+ end= strxmov(buff, "# administrator command: ", NullS);
+ buff_len= (ulong) (end - buff);
+ DBUG_EXECUTE_IF("simulate_slow_log_write_error",
+ {DBUG_SET("+d,simulate_file_write_error");});
+ if(my_b_write(&log_file, (uchar*) buff, buff_len))
+ tmp_errno= errno;
+ }
+ if (my_b_write(&log_file, (uchar*) sql_text, sql_text_len) ||
+ my_b_write(&log_file, (uchar*) ";\n",2) ||
+ flush_io_cache(&log_file))
+ tmp_errno= errno;
+ if (tmp_errno)
+ {
+ error= 1;
+ if (! write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), name, error);
+ }
+ }
+ }
+ mysql_mutex_unlock(&LOCK_log);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ @todo
+ The following should be using fn_format(); We just need to
+ first change fn_format() to cut the file name if it's too long.
+*/
+const char *MYSQL_LOG::generate_name(const char *log_name,
+ const char *suffix,
+ bool strip_ext, char *buff)
+{
+ if (!log_name || !log_name[0])
+ {
+ strmake(buff, pidfile_name, FN_REFLEN - strlen(suffix) - 1);
+ return (const char *)
+ fn_format(buff, buff, "", suffix, MYF(MY_REPLACE_EXT|MY_REPLACE_DIR));
+ }
+ // get rid of extension if the log is binary to avoid problems
+ if (strip_ext)
+ {
+ char *p= fn_ext(log_name);
+ uint length= (uint) (p - log_name);
+ strmake(buff, log_name, MY_MIN(length, FN_REFLEN-1));
+ return (const char*)buff;
+ }
+ return log_name;
+}
+
+
+
+MYSQL_BIN_LOG::MYSQL_BIN_LOG(uint *sync_period)
+ :reset_master_pending(0), mark_xid_done_waiting(0),
+ bytes_written(0), file_id(1), open_count(1),
+ group_commit_queue(0), group_commit_queue_busy(FALSE),
+ num_commits(0), num_group_commits(0),
+ group_commit_trigger_count(0), group_commit_trigger_timeout(0),
+ group_commit_trigger_lock_wait(0),
+ sync_period_ptr(sync_period), sync_counter(0),
+ state_file_deleted(false), binlog_state_recover_done(false),
+ is_relay_log(0), signal_cnt(0),
+ checksum_alg_reset(BINLOG_CHECKSUM_ALG_UNDEF),
+ relay_log_checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF),
+ description_event_for_exec(0), description_event_for_queue(0),
+ current_binlog_id(0)
+{
+ /*
+ We don't want to initialize locks here as such initialization depends on
+ safe_mutex (when using safe_mutex) which depends on MY_INIT(), which is
+ called only in main(). Doing initialization here would make it happen
+ before main().
+ */
+ index_file_name[0] = 0;
+ bzero((char*) &index_file, sizeof(index_file));
+ bzero((char*) &purge_index_file, sizeof(purge_index_file));
+}
+
+/* this is called only once */
+
+void MYSQL_BIN_LOG::cleanup()
+{
+ DBUG_ENTER("cleanup");
+ if (inited)
+ {
+ xid_count_per_binlog *b;
+
+ /* Wait for the binlog background thread to stop. */
+ if (!is_relay_log && binlog_background_thread_started)
+ {
+ mysql_mutex_lock(&LOCK_binlog_background_thread);
+ binlog_background_thread_stop= true;
+ mysql_cond_signal(&COND_binlog_background_thread);
+ while (binlog_background_thread_stop)
+ mysql_cond_wait(&COND_binlog_background_thread_end,
+ &LOCK_binlog_background_thread);
+ mysql_mutex_unlock(&LOCK_binlog_background_thread);
+ binlog_background_thread_started= false;
+ }
+
+ inited= 0;
+ close(LOG_CLOSE_INDEX|LOG_CLOSE_STOP_EVENT);
+ delete description_event_for_queue;
+ delete description_event_for_exec;
+
+ while ((b= binlog_xid_count_list.get()))
+ {
+ /*
+ There should be no pending XIDs at shutdown, and only one entry (for
+ the active binlog file) in the list.
+ */
+ DBUG_ASSERT(b->xid_count == 0);
+ DBUG_ASSERT(!binlog_xid_count_list.head());
+ my_free(b);
+ }
+
+ mysql_mutex_destroy(&LOCK_log);
+ mysql_mutex_destroy(&LOCK_index);
+ mysql_mutex_destroy(&LOCK_xid_list);
+ mysql_mutex_destroy(&LOCK_binlog_background_thread);
+ mysql_cond_destroy(&update_cond);
+ mysql_cond_destroy(&COND_queue_busy);
+ mysql_cond_destroy(&COND_xid_list);
+ mysql_cond_destroy(&COND_binlog_background_thread);
+ mysql_cond_destroy(&COND_binlog_background_thread_end);
+ }
+
+ /*
+ Free data for global binlog state.
+ We can't do that automaticly as we need to do this before
+ safemalloc is shut down
+ */
+ if (!is_relay_log)
+ rpl_global_gtid_binlog_state.free();
+ DBUG_VOID_RETURN;
+}
+
+
+/* Init binlog-specific vars */
+void MYSQL_BIN_LOG::init(ulong max_size_arg)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::init");
+ max_size= max_size_arg;
+ DBUG_PRINT("info",("max_size: %lu", max_size));
+ DBUG_VOID_RETURN;
+}
+
+
+void MYSQL_BIN_LOG::init_pthread_objects()
+{
+ MYSQL_LOG::init_pthread_objects();
+ mysql_mutex_init(m_key_LOCK_index, &LOCK_index, MY_MUTEX_INIT_SLOW);
+ mysql_mutex_setflags(&LOCK_index, MYF_NO_DEADLOCK_DETECTION);
+ mysql_mutex_init(key_BINLOG_LOCK_xid_list,
+ &LOCK_xid_list, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(m_key_update_cond, &update_cond, 0);
+ mysql_cond_init(m_key_COND_queue_busy, &COND_queue_busy, 0);
+ mysql_cond_init(key_BINLOG_COND_xid_list, &COND_xid_list, 0);
+
+ mysql_mutex_init(key_BINLOG_LOCK_binlog_background_thread,
+ &LOCK_binlog_background_thread, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_BINLOG_COND_binlog_background_thread,
+ &COND_binlog_background_thread, 0);
+ mysql_cond_init(key_BINLOG_COND_binlog_background_thread_end,
+ &COND_binlog_background_thread_end, 0);
+}
+
+
+bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,
+ const char *log_name, bool need_mutex)
+{
+ File index_file_nr= -1;
+ DBUG_ASSERT(!my_b_inited(&index_file));
+
+ /*
+ First open of this class instance
+ Create an index file that will hold all file names uses for logging.
+ Add new entries to the end of it.
+ */
+ myf opt= MY_UNPACK_FILENAME;
+ if (!index_file_name_arg)
+ {
+ index_file_name_arg= log_name; // Use same basename for index file
+ opt= MY_UNPACK_FILENAME | MY_REPLACE_EXT;
+ }
+ fn_format(index_file_name, index_file_name_arg, mysql_data_home,
+ ".index", opt);
+ if ((index_file_nr= mysql_file_open(m_key_file_log_index,
+ index_file_name,
+ O_RDWR | O_CREAT | O_BINARY,
+ MYF(MY_WME))) < 0 ||
+ mysql_file_sync(index_file_nr, MYF(MY_WME)) ||
+ init_io_cache(&index_file, index_file_nr,
+ IO_SIZE, WRITE_CACHE,
+ mysql_file_seek(index_file_nr, 0L, MY_SEEK_END, MYF(0)),
+ 0, MYF(MY_WME | MY_WAIT_IF_FULL)) ||
+ DBUG_EVALUATE_IF("fault_injection_openning_index", 1, 0))
+ {
+ /*
+ TODO: all operations creating/deleting the index file or a log, should
+ call my_sync_dir() or my_sync_dir_by_file() to be durable.
+ TODO: file creation should be done with mysql_file_create()
+ not mysql_file_open().
+ */
+ if (index_file_nr >= 0)
+ mysql_file_close(index_file_nr, MYF(0));
+ return TRUE;
+ }
+
+#ifdef HAVE_REPLICATION
+ /*
+ Sync the index by purging any binary log file that is not registered.
+ In other words, either purge binary log files that were removed from
+ the index but not purged from the file system due to a crash or purge
+ any binary log file that was created but not register in the index
+ due to a crash.
+ */
+
+ if (set_purge_index_file_name(index_file_name_arg) ||
+ open_purge_index_file(FALSE) ||
+ purge_index_entry(NULL, NULL, need_mutex) ||
+ close_purge_index_file() ||
+ DBUG_EVALUATE_IF("fault_injection_recovering_index", 1, 0))
+ {
+ sql_print_error("MYSQL_BIN_LOG::open_index_file failed to sync the index "
+ "file.");
+ return TRUE;
+ }
+#endif
+
+ return FALSE;
+}
+
+
+/**
+ Open a (new) binlog file.
+
+ - Open the log file and the index file. Register the new
+ file name in it
+ - When calling this when the file is in use, you must have a locks
+ on LOCK_log and LOCK_index.
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool MYSQL_BIN_LOG::open(const char *log_name,
+ enum_log_type log_type_arg,
+ const char *new_name,
+ enum cache_type io_cache_type_arg,
+ ulong max_size_arg,
+ bool null_created_arg,
+ bool need_mutex)
+{
+ File file= -1;
+ xid_count_per_binlog *new_xid_list_entry= NULL, *b;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::open");
+ DBUG_PRINT("enter",("log_type: %d",(int) log_type_arg));
+
+ if (!is_relay_log)
+ {
+ if (!binlog_state_recover_done)
+ {
+ binlog_state_recover_done= true;
+ if (do_binlog_recovery(opt_bin_logname, false))
+ DBUG_RETURN(1);
+ }
+
+ if (!binlog_background_thread_started &&
+ start_binlog_background_thread())
+ DBUG_RETURN(1);
+ }
+
+ if (init_and_set_log_file_name(log_name, new_name, log_type_arg,
+ io_cache_type_arg))
+ {
+ sql_print_error("MSYQL_BIN_LOG::open failed to generate new file name.");
+ DBUG_RETURN(1);
+ }
+
+#ifdef HAVE_REPLICATION
+ if (open_purge_index_file(TRUE) ||
+ register_create_index_entry(log_file_name) ||
+ sync_purge_index_file() ||
+ DBUG_EVALUATE_IF("fault_injection_registering_index", 1, 0))
+ {
+ /**
+ TODO: although this was introduced to appease valgrind
+ when injecting emulated faults using fault_injection_registering_index
+ it may be good to consider what actually happens when
+ open_purge_index_file succeeds but register or sync fails.
+
+ Perhaps we might need the code below in MYSQL_LOG_BIN::cleanup
+ for "real life" purposes as well?
+ */
+ DBUG_EXECUTE_IF("fault_injection_registering_index", {
+ if (my_b_inited(&purge_index_file))
+ {
+ end_io_cache(&purge_index_file);
+ my_close(purge_index_file.file, MYF(0));
+ }
+ });
+
+ sql_print_error("MSYQL_BIN_LOG::open failed to sync the index file.");
+ DBUG_RETURN(1);
+ }
+ DBUG_EXECUTE_IF("crash_create_non_critical_before_update_index", DBUG_SUICIDE(););
+#endif
+
+ write_error= 0;
+
+ /* open the main log file */
+ if (MYSQL_LOG::open(
+#ifdef HAVE_PSI_INTERFACE
+ m_key_file_log,
+#endif
+ log_name, log_type_arg, new_name, io_cache_type_arg))
+ {
+#ifdef HAVE_REPLICATION
+ close_purge_index_file();
+#endif
+ DBUG_RETURN(1); /* all warnings issued */
+ }
+
+ init(max_size_arg);
+
+ open_count++;
+
+ DBUG_ASSERT(log_type == LOG_BIN);
+
+ {
+ bool write_file_name_to_index_file=0;
+
+ if (!my_b_filelength(&log_file))
+ {
+ /*
+ The binary log file was empty (probably newly created)
+ This is the normal case and happens when the user doesn't specify
+ an extension for the binary log files.
+ In this case we write a standard header to it.
+ */
+ if (my_b_safe_write(&log_file, BINLOG_MAGIC,
+ BIN_LOG_HEADER_SIZE))
+ goto err;
+ bytes_written+= BIN_LOG_HEADER_SIZE;
+ write_file_name_to_index_file= 1;
+ }
+
+ {
+ /*
+ In 4.x we put Start event only in the first binlog. But from 5.0 we
+ want a Start event even if this is not the very first binlog.
+ */
+ Format_description_log_event s(BINLOG_VERSION);
+ /*
+ don't set LOG_EVENT_BINLOG_IN_USE_F for SEQ_READ_APPEND io_cache
+ as we won't be able to reset it later
+ */
+ if (io_cache_type == WRITE_CACHE)
+ s.flags |= LOG_EVENT_BINLOG_IN_USE_F;
+ s.checksum_alg= is_relay_log ?
+ /* relay-log */
+ /* inherit master's A descriptor if one has been received */
+ (relay_log_checksum_alg=
+ (relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) ?
+ relay_log_checksum_alg :
+ /* otherwise use slave's local preference of RL events verification */
+ (opt_slave_sql_verify_checksum == 0) ?
+ (uint8) BINLOG_CHECKSUM_ALG_OFF : (uint8) binlog_checksum_options):
+ /* binlog */
+ (uint8) binlog_checksum_options;
+ DBUG_ASSERT(s.checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
+ if (!s.is_valid())
+ goto err;
+ s.dont_set_created= null_created_arg;
+ if (s.write(&log_file))
+ goto err;
+ bytes_written+= s.data_written;
+
+ if (!is_relay_log)
+ {
+ char buf[FN_REFLEN];
+
+ /*
+ Output a Gtid_list_log_event at the start of the binlog file.
+
+ This is used to quickly determine which GTIDs are found in binlog
+ files earlier than this one, and which are found in this (or later)
+ binlogs.
+
+ The list gives a mapping from (domain_id, server_id) -> seq_no (so
+ this means that there is at most one entry for every unique pair
+ (domain_id, server_id) in the list). It indicates that this seq_no is
+ the last one found in an earlier binlog file for this (domain_id,
+ server_id) combination - so any higher seq_no should be search for
+ from this binlog file, or a later one.
+
+ This allows to locate the binlog file containing a given GTID by
+ scanning backwards, reading just the Gtid_list_log_event at the
+ start of each file, and scanning only the relevant binlog file when
+ found, not all binlog files.
+
+ The existence of a given entry (domain_id, server_id, seq_no)
+ guarantees only that this seq_no will not be found in this or any
+ later binlog file. It does not guarantee that it can be found it an
+ earlier binlog file, for example the file may have been purged.
+
+ If there is no entry for a given (domain_id, server_id) pair, then
+ it means that no such GTID exists in any earlier binlog. It is
+ permissible to remove such pair from future Gtid_list_log_events
+ if all previous binlog files containing such GTIDs have been purged
+ (though such optimization is not performed at the time of this
+ writing). So if there is no entry for given GTID it means that such
+ GTID should be search for in this or later binlog file, same as if
+ there had been an entry (domain_id, server_id, 0).
+ */
+
+ Gtid_list_log_event gl_ev(&rpl_global_gtid_binlog_state, 0);
+ if (gl_ev.write(&log_file))
+ goto err;
+
+ /* Output a binlog checkpoint event at the start of the binlog file. */
+
+ /*
+ Construct an entry in the binlog_xid_count_list for the new binlog
+ file (we will not link it into the list until we know the new file
+ is successfully created; otherwise we would have to remove it again
+ if creation failed, which gets tricky since other threads may have
+ seen the entry in the meantime - and we do not want to hold
+ LOCK_xid_list for long periods of time).
+
+ Write the current binlog checkpoint into the log, so XA recovery will
+ know from where to start recovery.
+ */
+ uint off= dirname_length(log_file_name);
+ uint len= strlen(log_file_name) - off;
+ char *entry_mem, *name_mem;
+ if (!(new_xid_list_entry = (xid_count_per_binlog *)
+ my_multi_malloc(MYF(MY_WME),
+ &entry_mem, sizeof(xid_count_per_binlog),
+ &name_mem, len,
+ NULL)))
+ goto err;
+ memcpy(name_mem, log_file_name+off, len);
+ new_xid_list_entry->binlog_name= name_mem;
+ new_xid_list_entry->binlog_name_len= len;
+ new_xid_list_entry->xid_count= 0;
+
+ /*
+ Find the name for the Initial binlog checkpoint.
+
+ Normally this will just be the first entry, as we delete entries
+ when their count drops to zero. But we scan the list to handle any
+ corner case, eg. for the first binlog file opened after startup, the
+ list will be empty.
+ */
+ mysql_mutex_lock(&LOCK_xid_list);
+ I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list);
+ while ((b= it++) && b->xid_count == 0)
+ ;
+ mysql_mutex_unlock(&LOCK_xid_list);
+ if (!b)
+ b= new_xid_list_entry;
+ strmake(buf, b->binlog_name, b->binlog_name_len);
+ Binlog_checkpoint_log_event ev(buf, len);
+ DBUG_EXECUTE_IF("crash_before_write_checkpoint_event",
+ flush_io_cache(&log_file);
+ mysql_file_sync(log_file.file, MYF(MY_WME));
+ DBUG_SUICIDE(););
+ if (ev.write(&log_file))
+ goto err;
+ bytes_written+= ev.data_written;
+ }
+ }
+ if (description_event_for_queue &&
+ description_event_for_queue->binlog_version>=4)
+ {
+ /*
+ This is a relay log written to by the I/O slave thread.
+ Write the event so that others can later know the format of this relay
+ log.
+ Note that this event is very close to the original event from the
+ master (it has binlog version of the master, event types of the
+ master), so this is suitable to parse the next relay log's event. It
+ has been produced by
+ Format_description_log_event::Format_description_log_event(char* buf,).
+ Why don't we want to write the description_event_for_queue if this
+ event is for format<4 (3.23 or 4.x): this is because in that case, the
+ description_event_for_queue describes the data received from the
+ master, but not the data written to the relay log (*conversion*),
+ which is in format 4 (slave's).
+ */
+ /*
+ Set 'created' to 0, so that in next relay logs this event does not
+ trigger cleaning actions on the slave in
+ Format_description_log_event::apply_event_impl().
+ */
+ description_event_for_queue->created= 0;
+ /* Don't set log_pos in event header */
+ description_event_for_queue->set_artificial_event();
+
+ if (description_event_for_queue->write(&log_file))
+ goto err;
+ bytes_written+= description_event_for_queue->data_written;
+ }
+ if (flush_io_cache(&log_file) ||
+ mysql_file_sync(log_file.file, MYF(MY_WME|MY_SYNC_FILESIZE)))
+ goto err;
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ strmake_buf(last_commit_pos_file, log_file_name);
+ last_commit_pos_offset= my_b_tell(&log_file);
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+
+ if (write_file_name_to_index_file)
+ {
+#ifdef HAVE_REPLICATION
+#ifdef ENABLED_DEBUG_SYNC
+ if (current_thd)
+ DEBUG_SYNC(current_thd, "binlog_open_before_update_index");
+#endif
+ DBUG_EXECUTE_IF("crash_create_critical_before_update_index", DBUG_SUICIDE(););
+#endif
+
+ DBUG_ASSERT(my_b_inited(&index_file) != 0);
+ reinit_io_cache(&index_file, WRITE_CACHE,
+ my_b_filelength(&index_file), 0, 0);
+ /*
+ As this is a new log file, we write the file name to the index
+ file. As every time we write to the index file, we sync it.
+ */
+ if (DBUG_EVALUATE_IF("fault_injection_updating_index", 1, 0) ||
+ my_b_write(&index_file, (uchar*) log_file_name,
+ strlen(log_file_name)) ||
+ my_b_write(&index_file, (uchar*) "\n", 1) ||
+ flush_io_cache(&index_file) ||
+ mysql_file_sync(index_file.file, MYF(MY_WME|MY_SYNC_FILESIZE)))
+ goto err;
+
+#ifdef HAVE_REPLICATION
+ DBUG_EXECUTE_IF("crash_create_after_update_index", DBUG_SUICIDE(););
+#endif
+ }
+ }
+
+ if (!is_relay_log)
+ {
+ /*
+ Now the file was created successfully, so we can link in the entry for
+ the new binlog file in binlog_xid_count_list.
+ */
+ mysql_mutex_lock(&LOCK_xid_list);
+ ++current_binlog_id;
+ new_xid_list_entry->binlog_id= current_binlog_id;
+ /* Remove any initial entries with no pending XIDs. */
+ while ((b= binlog_xid_count_list.head()) && b->xid_count == 0)
+ my_free(binlog_xid_count_list.get());
+ binlog_xid_count_list.push_back(new_xid_list_entry);
+ mysql_mutex_unlock(&LOCK_xid_list);
+
+ /*
+ Now that we have synced a new binlog file with an initial Gtid_list
+ event, it is safe to delete the binlog state file. We will write out
+ a new, updated file at shutdown, and if we crash before we can recover
+ the state from the newly written binlog file.
+
+ Since the state file will contain out-of-date data as soon as the first
+ new GTID is binlogged, it is better to remove it, to avoid any risk of
+ accidentally reading incorrect data later.
+ */
+ if (!state_file_deleted)
+ {
+ char buf[FN_REFLEN];
+ fn_format(buf, opt_bin_logname, mysql_data_home, ".state",
+ MY_UNPACK_FILENAME);
+ my_delete(buf, MY_SYNC_DIR);
+ state_file_deleted= true;
+ }
+ }
+
+ log_state= LOG_OPENED;
+
+#ifdef HAVE_REPLICATION
+ close_purge_index_file();
+#endif
+
+ DBUG_RETURN(0);
+
+err:
+#ifdef HAVE_REPLICATION
+ if (is_inited_purge_index_file())
+ purge_index_entry(NULL, NULL, need_mutex);
+ close_purge_index_file();
+#endif
+ sql_print_error("Could not use %s for logging (error %d). \
+Turning logging off for the whole duration of the MySQL server process. \
+To turn it on again: fix the cause, \
+shutdown the MySQL server and restart it.", name, errno);
+ if (new_xid_list_entry)
+ my_free(new_xid_list_entry);
+ if (file >= 0)
+ mysql_file_close(file, MYF(0));
+ close(LOG_CLOSE_INDEX);
+ DBUG_RETURN(1);
+}
+
+
+int MYSQL_BIN_LOG::get_current_log(LOG_INFO* linfo)
+{
+ mysql_mutex_lock(&LOCK_log);
+ int ret = raw_get_current_log(linfo);
+ mysql_mutex_unlock(&LOCK_log);
+ return ret;
+}
+
+int MYSQL_BIN_LOG::raw_get_current_log(LOG_INFO* linfo)
+{
+ strmake_buf(linfo->log_file_name, log_file_name);
+ linfo->pos = my_b_tell(&log_file);
+ return 0;
+}
+
+/**
+ Move all data up in a file in an filename index file.
+
+ We do the copy outside of the IO_CACHE as the cache buffers would just
+ make things slower and more complicated.
+ In most cases the copy loop should only do one read.
+
+ @param index_file File to move
+ @param offset Move everything from here to beginning
+
+ @note
+ File will be truncated to be 'offset' shorter or filled up with newlines
+
+ @retval
+ 0 ok
+*/
+
+#ifdef HAVE_REPLICATION
+
+static bool copy_up_file_and_fill(IO_CACHE *index_file, my_off_t offset)
+{
+ int bytes_read;
+ my_off_t init_offset= offset;
+ File file= index_file->file;
+ uchar io_buf[IO_SIZE*2];
+ DBUG_ENTER("copy_up_file_and_fill");
+
+ for (;; offset+= bytes_read)
+ {
+ mysql_file_seek(file, offset, MY_SEEK_SET, MYF(0));
+ if ((bytes_read= (int) mysql_file_read(file, io_buf, sizeof(io_buf),
+ MYF(MY_WME)))
+ < 0)
+ goto err;
+ if (!bytes_read)
+ break; // end of file
+ mysql_file_seek(file, offset-init_offset, MY_SEEK_SET, MYF(0));
+ if (mysql_file_write(file, io_buf, bytes_read,
+ MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)))
+ goto err;
+ }
+ /* The following will either truncate the file or fill the end with \n' */
+ if (mysql_file_chsize(file, offset - init_offset, '\n', MYF(MY_WME)) ||
+ mysql_file_sync(file, MYF(MY_WME|MY_SYNC_FILESIZE)))
+ goto err;
+
+ /* Reset data in old index cache */
+ reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 1);
+ DBUG_RETURN(0);
+
+err:
+ DBUG_RETURN(1);
+}
+
+#endif /* HAVE_REPLICATION */
+
+/**
+ Find the position in the log-index-file for the given log name.
+
+ @param linfo Store here the found log file name and position to
+ the NEXT log file name in the index file.
+ @param log_name Filename to find in the index file.
+ Is a null pointer if we want to read the first entry
+ @param need_lock Set this to 1 if the parent doesn't already have a
+ lock on LOCK_index
+
+ @note
+ On systems without the truncate function the file will end with one or
+ more empty lines. These will be ignored when reading the file.
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF End of log-index-file found
+ @retval
+ LOG_INFO_IO Got IO error while reading file
+*/
+
+int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name,
+ bool need_lock)
+{
+ int error= 0;
+ char *full_fname= linfo->log_file_name;
+ char full_log_name[FN_REFLEN], fname[FN_REFLEN];
+ uint log_name_len= 0, fname_len= 0;
+ DBUG_ENTER("find_log_pos");
+ full_log_name[0]= full_fname[0]= 0;
+
+ /*
+ Mutex needed because we need to make sure the file pointer does not
+ move from under our feet
+ */
+ if (need_lock)
+ mysql_mutex_lock(&LOCK_index);
+ mysql_mutex_assert_owner(&LOCK_index);
+
+ // extend relative paths for log_name to be searched
+ if (log_name)
+ {
+ if(normalize_binlog_name(full_log_name, log_name, is_relay_log))
+ {
+ error= LOG_INFO_EOF;
+ goto end;
+ }
+ }
+
+ log_name_len= log_name ? (uint) strlen(full_log_name) : 0;
+ DBUG_PRINT("enter", ("log_name: %s, full_log_name: %s",
+ log_name ? log_name : "NULL", full_log_name));
+
+ /* As the file is flushed, we can't get an error here */
+ (void) reinit_io_cache(&index_file, READ_CACHE, (my_off_t) 0, 0, 0);
+
+ for (;;)
+ {
+ uint length;
+ my_off_t offset= my_b_tell(&index_file);
+
+ DBUG_EXECUTE_IF("simulate_find_log_pos_error",
+ error= LOG_INFO_EOF; break;);
+ /* If we get 0 or 1 characters, this is the end of the file */
+ if ((length= my_b_gets(&index_file, fname, FN_REFLEN)) <= 1)
+ {
+ /* Did not find the given entry; Return not found or error */
+ error= !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO;
+ break;
+ }
+
+ // extend relative paths and match against full path
+ if (normalize_binlog_name(full_fname, fname, is_relay_log))
+ {
+ error= LOG_INFO_EOF;
+ break;
+ }
+ fname_len= (uint) strlen(full_fname);
+
+ // if the log entry matches, null string matching anything
+ if (!log_name ||
+ (log_name_len == fname_len-1 && full_fname[log_name_len] == '\n' &&
+ !memcmp(full_fname, full_log_name, log_name_len)))
+ {
+ DBUG_PRINT("info", ("Found log file entry"));
+ full_fname[fname_len-1]= 0; // remove last \n
+ linfo->index_file_start_offset= offset;
+ linfo->index_file_offset = my_b_tell(&index_file);
+ break;
+ }
+ }
+
+end:
+ if (need_lock)
+ mysql_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Find the position in the log-index-file for the given log name.
+
+ @param
+ linfo Store here the next log file name and position to
+ the file name after that.
+ @param
+ need_lock Set this to 1 if the parent doesn't already have a
+ lock on LOCK_index
+
+ @note
+ - Before calling this function, one has to call find_log_pos()
+ to set up 'linfo'
+ - Mutex needed because we need to make sure the file pointer does not move
+ from under our feet
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF End of log-index-file found
+ @retval
+ LOG_INFO_IO Got IO error while reading file
+*/
+
+int MYSQL_BIN_LOG::find_next_log(LOG_INFO* linfo, bool need_lock)
+{
+ int error= 0;
+ uint length;
+ char fname[FN_REFLEN];
+ char *full_fname= linfo->log_file_name;
+
+ if (need_lock)
+ mysql_mutex_lock(&LOCK_index);
+ mysql_mutex_assert_owner(&LOCK_index);
+
+ /* As the file is flushed, we can't get an error here */
+ (void) reinit_io_cache(&index_file, READ_CACHE, linfo->index_file_offset, 0,
+ 0);
+
+ linfo->index_file_start_offset= linfo->index_file_offset;
+ if ((length=my_b_gets(&index_file, fname, FN_REFLEN)) <= 1)
+ {
+ error = !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO;
+ goto err;
+ }
+
+ if (fname[0] != 0)
+ {
+ if(normalize_binlog_name(full_fname, fname, is_relay_log))
+ {
+ error= LOG_INFO_EOF;
+ goto err;
+ }
+ length= strlen(full_fname);
+ }
+
+ full_fname[length-1]= 0; // kill \n
+ linfo->index_file_offset= my_b_tell(&index_file);
+
+err:
+ if (need_lock)
+ mysql_mutex_unlock(&LOCK_index);
+ return error;
+}
+
+
+/**
+ Delete all logs refered to in the index file.
+
+ The new index file will only contain this file.
+
+ @param thd Thread
+ @param create_new_log 1 if we should start writing to a new log file
+
+ @note
+ If not called from slave thread, write start event to new log
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log,
+ rpl_gtid *init_state, uint32 init_state_len)
+{
+ LOG_INFO linfo;
+ bool error=0;
+ int err;
+ const char* save_name;
+ DBUG_ENTER("reset_logs");
+
+ if (!is_relay_log)
+ {
+ if (init_state && !is_empty_state())
+ {
+ my_error(ER_BINLOG_MUST_BE_EMPTY, MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ /*
+ Mark that a RESET MASTER is in progress.
+ This ensures that a binlog checkpoint will not try to write binlog
+ checkpoint events, which would be useless (as we are deleting the binlog
+ anyway) and could deadlock, as we are holding LOCK_log.
+
+ Wait for any mark_xid_done() calls that might be already running to
+ complete (mark_xid_done_waiting counter to drop to zero); we need to
+ do this before we take the LOCK_log to not deadlock.
+ */
+ mysql_mutex_lock(&LOCK_xid_list);
+ reset_master_pending++;
+ while (mark_xid_done_waiting > 0)
+ mysql_cond_wait(&COND_xid_list, &LOCK_xid_list);
+ mysql_mutex_unlock(&LOCK_xid_list);
+ }
+
+ DEBUG_SYNC(thd, "reset_logs_after_set_reset_master_pending");
+ if (thd)
+ ha_reset_logs(thd);
+ /*
+ We need to get both locks to be sure that no one is trying to
+ write to the index log file.
+ */
+ mysql_mutex_lock(&LOCK_log);
+ mysql_mutex_lock(&LOCK_index);
+
+ if (!is_relay_log)
+ {
+ /*
+ We are going to nuke all binary log files.
+ Without binlog, we cannot XA recover prepared-but-not-committed
+ transactions in engines. So force a commit checkpoint first.
+
+ Note that we take and immediately release LOCK_commit_ordered. This has
+ the effect to ensure that any on-going group commit (in
+ trx_group_commit_leader()) has completed before we request the checkpoint,
+ due to the chaining of LOCK_log and LOCK_commit_ordered in that function.
+ (We are holding LOCK_log, so no new group commit can start).
+
+ Without this, it is possible (though perhaps unlikely) that the RESET
+ MASTER could run in-between the write to the binlog and the
+ commit_ordered() in the engine of some transaction, and then a crash
+ later would leave such transaction not recoverable.
+ */
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+
+ mark_xids_active(current_binlog_id, 1);
+ do_checkpoint_request(current_binlog_id);
+
+ /* Now wait for all checkpoint requests and pending unlog() to complete. */
+ mysql_mutex_lock(&LOCK_xid_list);
+ for (;;)
+ {
+ if (is_xidlist_idle_nolock())
+ break;
+ /*
+ Wait until signalled that one more binlog dropped to zero, then check
+ again.
+ */
+ mysql_cond_wait(&COND_xid_list, &LOCK_xid_list);
+ }
+
+ /*
+ Now all XIDs are fully flushed to disk, and we are holding LOCK_log so
+ no new ones will be written. So we can proceed to delete the logs.
+ */
+ mysql_mutex_unlock(&LOCK_xid_list);
+ }
+
+ /*
+ The following mutex is needed to ensure that no threads call
+ 'delete thd' as we would then risk missing a 'rollback' from this
+ thread. If the transaction involved MyISAM tables, it should go
+ into binlog even on rollback.
+ */
+ mysql_mutex_lock(&LOCK_thread_count);
+
+ /* Save variables so that we can reopen the log */
+ save_name=name;
+ name=0; // Protect against free
+ close(LOG_CLOSE_TO_BE_OPENED);
+
+ /*
+ First delete all old log files and then update the index file.
+ As we first delete the log files and do not use sort of logging,
+ a crash may lead to an inconsistent state where the index has
+ references to non-existent files.
+
+ We need to invert the steps and use the purge_index_file methods
+ in order to make the operation safe.
+ */
+
+ if ((err= find_log_pos(&linfo, NullS, 0)) != 0)
+ {
+ uint errcode= purge_log_get_error_code(err);
+ sql_print_error("Failed to locate old binlog or relay log files");
+ my_message(errcode, ER(errcode), MYF(0));
+ error= 1;
+ goto err;
+ }
+
+ for (;;)
+ {
+ if ((error= my_delete(linfo.log_file_name, MYF(0))) != 0)
+ {
+ if (my_errno == ENOENT)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ linfo.log_file_name);
+ sql_print_information("Failed to delete file '%s'",
+ linfo.log_file_name);
+ my_errno= 0;
+ error= 0;
+ }
+ else
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ linfo.log_file_name);
+ error= 1;
+ goto err;
+ }
+ }
+ if (find_next_log(&linfo, 0))
+ break;
+ }
+
+ if (!is_relay_log)
+ {
+ if (init_state)
+ rpl_global_gtid_binlog_state.load(init_state, init_state_len);
+ else
+ rpl_global_gtid_binlog_state.reset();
+ }
+
+ /* Start logging with a new file */
+ close(LOG_CLOSE_INDEX | LOG_CLOSE_TO_BE_OPENED);
+ if ((error= my_delete(index_file_name, MYF(0)))) // Reset (open will update)
+ {
+ if (my_errno == ENOENT)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ index_file_name);
+ sql_print_information("Failed to delete file '%s'",
+ index_file_name);
+ my_errno= 0;
+ error= 0;
+ }
+ else
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ index_file_name);
+ error= 1;
+ goto err;
+ }
+ }
+ if (create_new_log && !open_index_file(index_file_name, 0, FALSE))
+ if ((error= open(save_name, log_type, 0, io_cache_type, max_size, 0, FALSE)))
+ goto err;
+ my_free((void *) save_name);
+
+err:
+ if (error == 1)
+ name= const_cast<char*>(save_name);
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ if (!is_relay_log)
+ {
+ xid_count_per_binlog *b;
+ /*
+ Remove all entries in the xid_count list except the last.
+ Normally we will just be deleting all the entries that we waited for to
+ drop to zero above. But if we fail during RESET MASTER for some reason
+ then we will not have created any new log file, and we may keep the last
+ of the old entries.
+ */
+ mysql_mutex_lock(&LOCK_xid_list);
+ for (;;)
+ {
+ b= binlog_xid_count_list.head();
+ DBUG_ASSERT(b /* List can never become empty. */);
+ if (b->binlog_id == current_binlog_id)
+ break;
+ DBUG_ASSERT(b->xid_count == 0);
+ my_free(binlog_xid_count_list.get());
+ }
+ reset_master_pending--;
+ mysql_mutex_unlock(&LOCK_xid_list);
+ }
+
+ mysql_mutex_unlock(&LOCK_index);
+ mysql_mutex_unlock(&LOCK_log);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Delete relay log files prior to rli->group_relay_log_name
+ (i.e. all logs which are not involved in a non-finished group
+ (transaction)), remove them from the index file and start on next
+ relay log.
+
+ IMPLEMENTATION
+ - Protects index file with LOCK_index
+ - Delete relevant relay log files
+ - Copy all file names after these ones to the front of the index file
+ - If the OS has truncate, truncate the file, else fill it with \n'
+ - Read the next file name from the index file and store in rli->linfo
+
+ @param rli Relay log information
+ @param included If false, all relay logs that are strictly before
+ rli->group_relay_log_name are deleted ; if true, the
+ latter is deleted too (i.e. all relay logs
+ read by the SQL slave thread are deleted).
+
+ @note
+ - This is only called from the slave-execute thread when it has read
+ all commands from a relay log and want to switch to a new relay log.
+ - When this happens, we can be in an active transaction as
+ a transaction can span over two relay logs
+ (although it is always written as a single block to the master's binary
+ log, hence cannot span over two master's binary logs).
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF End of log-index-file found
+ @retval
+ LOG_INFO_SEEK Could not allocate IO cache
+ @retval
+ LOG_INFO_IO Got IO error while reading file
+*/
+
+#ifdef HAVE_REPLICATION
+
+int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included)
+{
+ int error;
+ char *to_purge_if_included= NULL;
+ inuse_relaylog *ir;
+ DBUG_ENTER("purge_first_log");
+
+ DBUG_ASSERT(is_open());
+ DBUG_ASSERT(rli->slave_running == MYSQL_SLAVE_RUN_NOT_CONNECT);
+ DBUG_ASSERT(!strcmp(rli->linfo.log_file_name,rli->event_relay_log_name));
+
+ mysql_mutex_lock(&LOCK_index);
+
+ ir= rli->inuse_relaylog_list;
+ while (ir)
+ {
+ inuse_relaylog *next= ir->next;
+ if (!ir->completed || ir->dequeued_count < ir->queued_count)
+ {
+ included= false;
+ break;
+ }
+ if (!included && !strcmp(ir->name, rli->group_relay_log_name))
+ break;
+ if (!next)
+ {
+ rli->last_inuse_relaylog= NULL;
+ included= 1;
+ to_purge_if_included= my_strdup(ir->name, MYF(0));
+ }
+ rli->free_inuse_relaylog(ir);
+ ir= next;
+ }
+ rli->inuse_relaylog_list= ir;
+ if (ir)
+ to_purge_if_included= my_strdup(ir->name, MYF(0));
+
+ /*
+ Read the next log file name from the index file and pass it back to
+ the caller.
+ */
+ if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0)) ||
+ (error=find_next_log(&rli->linfo, 0)))
+ {
+ char buff[22];
+ sql_print_error("next log error: %d offset: %s log: %s included: %d",
+ error,
+ llstr(rli->linfo.index_file_offset,buff),
+ rli->event_relay_log_name,
+ included);
+ goto err;
+ }
+
+ /*
+ Reset rli's coordinates to the current log.
+ */
+ rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE;
+ strmake_buf(rli->event_relay_log_name,rli->linfo.log_file_name);
+
+ /*
+ If we removed the rli->group_relay_log_name file,
+ we must update the rli->group* coordinates, otherwise do not touch it as the
+ group's execution is not finished (e.g. COMMIT not executed)
+ */
+ if (included)
+ {
+ rli->group_relay_log_pos = BIN_LOG_HEADER_SIZE;
+ strmake_buf(rli->group_relay_log_name,rli->linfo.log_file_name);
+ rli->notify_group_relay_log_name_update();
+ }
+
+ /* Store where we are in the new file for the execution thread */
+ flush_relay_log_info(rli);
+
+ DBUG_EXECUTE_IF("crash_before_purge_logs", DBUG_SUICIDE(););
+
+ mysql_mutex_lock(&rli->log_space_lock);
+ rli->relay_log.purge_logs(to_purge_if_included, included,
+ 0, 0, &rli->log_space_total);
+ mysql_mutex_unlock(&rli->log_space_lock);
+
+ /*
+ Ok to broadcast after the critical region as there is no risk of
+ the mutex being destroyed by this thread later - this helps save
+ context switches
+ */
+ mysql_cond_broadcast(&rli->log_space_cond);
+
+ /*
+ * Need to update the log pos because purge logs has been called
+ * after fetching initially the log pos at the begining of the method.
+ */
+ if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0)))
+ {
+ char buff[22];
+ sql_print_error("next log error: %d offset: %s log: %s included: %d",
+ error,
+ llstr(rli->linfo.index_file_offset,buff),
+ rli->group_relay_log_name,
+ included);
+ goto err;
+ }
+
+ /* If included was passed, rli->linfo should be the first entry. */
+ DBUG_ASSERT(!included || rli->linfo.index_file_start_offset == 0);
+
+err:
+ my_free(to_purge_if_included);
+ mysql_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+
+/**
+ Update log index_file.
+*/
+
+int MYSQL_BIN_LOG::update_log_index(LOG_INFO* log_info, bool need_update_threads)
+{
+ if (copy_up_file_and_fill(&index_file, log_info->index_file_start_offset))
+ return LOG_INFO_IO;
+
+ // now update offsets in index file for running threads
+ if (need_update_threads)
+ adjust_linfo_offsets(log_info->index_file_start_offset);
+ return 0;
+}
+
+/**
+ Remove all logs before the given log from disk and from the index file.
+
+ @param to_log Delete all log file name before this file.
+ @param included If true, to_log is deleted too.
+ @param need_mutex
+ @param need_update_threads If we want to update the log coordinates of
+ all threads. False for relay logs, true otherwise.
+ @param freed_log_space If not null, decrement this variable of
+ the amount of log space freed
+
+ @note
+ If any of the logs before the deleted one is in use,
+ only purge logs up to this one.
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF to_log not found
+ LOG_INFO_EMFILE too many files opened
+ LOG_INFO_FATAL if any other than ENOENT error from
+ mysql_file_stat() or mysql_file_delete()
+*/
+
+int MYSQL_BIN_LOG::purge_logs(const char *to_log,
+ bool included,
+ bool need_mutex,
+ bool need_update_threads,
+ ulonglong *decrease_log_space)
+{
+ int error= 0;
+ bool exit_loop= 0;
+ LOG_INFO log_info;
+ THD *thd= current_thd;
+ DBUG_ENTER("purge_logs");
+ DBUG_PRINT("info",("to_log= %s",to_log));
+
+ if (need_mutex)
+ mysql_mutex_lock(&LOCK_index);
+ if ((error=find_log_pos(&log_info, to_log, 0 /*no mutex*/)))
+ {
+ sql_print_error("MYSQL_BIN_LOG::purge_logs was called with file %s not "
+ "listed in the index.", to_log);
+ goto err;
+ }
+
+ if ((error= open_purge_index_file(TRUE)))
+ {
+ sql_print_error("MYSQL_BIN_LOG::purge_logs failed to sync the index file.");
+ goto err;
+ }
+
+ /*
+ File name exists in index file; delete until we find this file
+ or a file that is used.
+ */
+ if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))
+ goto err;
+ while ((strcmp(to_log,log_info.log_file_name) || (exit_loop=included)) &&
+ can_purge_log(log_info.log_file_name))
+ {
+ if ((error= register_purge_index_entry(log_info.log_file_name)))
+ {
+ sql_print_error("MYSQL_BIN_LOG::purge_logs failed to copy %s to register file.",
+ log_info.log_file_name);
+ goto err;
+ }
+
+ if (find_next_log(&log_info, 0) || exit_loop)
+ break;
+ }
+
+ DBUG_EXECUTE_IF("crash_purge_before_update_index", DBUG_SUICIDE(););
+
+ if ((error= sync_purge_index_file()))
+ {
+ sql_print_error("MSYQL_BIN_LOG::purge_logs failed to flush register file.");
+ goto err;
+ }
+
+ /* We know how many files to delete. Update index file. */
+ if ((error=update_log_index(&log_info, need_update_threads)))
+ {
+ sql_print_error("MSYQL_BIN_LOG::purge_logs failed to update the index file");
+ goto err;
+ }
+
+ DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", DBUG_SUICIDE(););
+
+err:
+ /* Read each entry from purge_index_file and delete the file. */
+ if (is_inited_purge_index_file() &&
+ (error= purge_index_entry(thd, decrease_log_space, FALSE)))
+ sql_print_error("MSYQL_BIN_LOG::purge_logs failed to process registered files"
+ " that would be purged.");
+ close_purge_index_file();
+
+ DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", DBUG_SUICIDE(););
+
+ if (need_mutex)
+ mysql_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::set_purge_index_file_name(const char *base_file_name)
+{
+ int error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::set_purge_index_file_name");
+ if (fn_format(purge_index_file_name, base_file_name, mysql_data_home,
+ ".~rec~", MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH |
+ MY_REPLACE_EXT)) == NULL)
+ {
+ error= 1;
+ sql_print_error("MYSQL_BIN_LOG::set_purge_index_file_name failed to set "
+ "file name.");
+ }
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::open_purge_index_file(bool destroy)
+{
+ int error= 0;
+ File file= -1;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::open_purge_index_file");
+
+ if (destroy)
+ close_purge_index_file();
+
+ if (!my_b_inited(&purge_index_file))
+ {
+ if ((file= my_open(purge_index_file_name, O_RDWR | O_CREAT | O_BINARY,
+ MYF(MY_WME | ME_WAITTANG))) < 0 ||
+ init_io_cache(&purge_index_file, file, IO_SIZE,
+ (destroy ? WRITE_CACHE : READ_CACHE),
+ 0, 0, MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)))
+ {
+ error= 1;
+ sql_print_error("MYSQL_BIN_LOG::open_purge_index_file failed to open register "
+ " file.");
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::close_purge_index_file()
+{
+ int error= 0;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::close_purge_index_file");
+
+ if (my_b_inited(&purge_index_file))
+ {
+ end_io_cache(&purge_index_file);
+ error= my_close(purge_index_file.file, MYF(0));
+ }
+ my_delete(purge_index_file_name, MYF(0));
+ bzero((char*) &purge_index_file, sizeof(purge_index_file));
+
+ DBUG_RETURN(error);
+}
+
+bool MYSQL_BIN_LOG::is_inited_purge_index_file()
+{
+ return my_b_inited(&purge_index_file);
+}
+
+int MYSQL_BIN_LOG::sync_purge_index_file()
+{
+ int error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::sync_purge_index_file");
+
+ if ((error= flush_io_cache(&purge_index_file)) ||
+ (error= my_sync(purge_index_file.file, MYF(MY_WME|MY_SYNC_FILESIZE))))
+ DBUG_RETURN(error);
+
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::register_purge_index_entry(const char *entry)
+{
+ int error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::register_purge_index_entry");
+
+ if ((error=my_b_write(&purge_index_file, (const uchar*)entry, strlen(entry))) ||
+ (error=my_b_write(&purge_index_file, (const uchar*)"\n", 1)))
+ DBUG_RETURN (error);
+
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::register_create_index_entry(const char *entry)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::register_create_index_entry");
+ DBUG_RETURN(register_purge_index_entry(entry));
+}
+
+int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *decrease_log_space,
+ bool need_mutex)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG:purge_index_entry");
+ MY_STAT s;
+ int error= 0;
+ LOG_INFO log_info;
+ LOG_INFO check_log_info;
+
+ DBUG_ASSERT(my_b_inited(&purge_index_file));
+
+ if ((error=reinit_io_cache(&purge_index_file, READ_CACHE, 0, 0, 0)))
+ {
+ sql_print_error("MSYQL_BIN_LOG::purge_index_entry failed to reinit register file "
+ "for read");
+ goto err;
+ }
+
+ for (;;)
+ {
+ uint length;
+
+ if ((length=my_b_gets(&purge_index_file, log_info.log_file_name,
+ FN_REFLEN)) <= 1)
+ {
+ if (purge_index_file.error)
+ {
+ error= purge_index_file.error;
+ sql_print_error("MSYQL_BIN_LOG::purge_index_entry error %d reading from "
+ "register file.", error);
+ goto err;
+ }
+
+ /* Reached EOF */
+ break;
+ }
+
+ /* Get rid of the trailing '\n' */
+ log_info.log_file_name[length-1]= 0;
+
+ if (!mysql_file_stat(m_key_file_log, log_info.log_file_name, &s, MYF(0)))
+ {
+ if (my_errno == ENOENT)
+ {
+ /*
+ It's not fatal if we can't stat a log file that does not exist;
+ If we could not stat, we won't delete.
+ */
+ if (thd)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ log_info.log_file_name);
+ }
+ sql_print_information("Failed to execute mysql_file_stat on file '%s'",
+ log_info.log_file_name);
+ my_errno= 0;
+ }
+ else
+ {
+ /*
+ Other than ENOENT are fatal
+ */
+ if (thd)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with getting info on being purged %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete log file '%s'; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ error= LOG_INFO_FATAL;
+ goto err;
+ }
+ }
+ else
+ {
+ if ((error= find_log_pos(&check_log_info, log_info.log_file_name, need_mutex)))
+ {
+ if (error != LOG_INFO_EOF)
+ {
+ if (thd)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s and "
+ "reading the binlog index file",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete file '%s' and "
+ "read the binlog index file",
+ log_info.log_file_name);
+ }
+ goto err;
+ }
+
+ error= 0;
+ if (!need_mutex)
+ {
+ /*
+ This is to avoid triggering an error in NDB.
+ */
+ ha_binlog_index_purge_file(current_thd, log_info.log_file_name);
+ }
+
+ DBUG_PRINT("info",("purging %s",log_info.log_file_name));
+ if (!my_delete(log_info.log_file_name, MYF(0)))
+ {
+ if (decrease_log_space)
+ *decrease_log_space-= s.st_size;
+ }
+ else
+ {
+ if (my_errno == ENOENT)
+ {
+ if (thd)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ log_info.log_file_name);
+ }
+ sql_print_information("Failed to delete file '%s'",
+ log_info.log_file_name);
+ my_errno= 0;
+ }
+ else
+ {
+ if (thd)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete file '%s'; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ if (my_errno == EMFILE)
+ {
+ DBUG_PRINT("info",
+ ("my_errno: %d, set ret = LOG_INFO_EMFILE", my_errno));
+ error= LOG_INFO_EMFILE;
+ goto err;
+ }
+ error= LOG_INFO_FATAL;
+ goto err;
+ }
+ }
+ }
+ }
+ }
+
+err:
+ DBUG_RETURN(error);
+}
+
+/**
+ Remove all logs before the given file date from disk and from the
+ index file.
+
+ @param thd Thread pointer
+ @param purge_time Delete all log files before given date.
+
+ @note
+ If any of the logs before the deleted one is in use,
+ only purge logs up to this one.
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_PURGE_NO_ROTATE Binary file that can't be rotated
+ LOG_INFO_FATAL if any other than ENOENT error from
+ mysql_file_stat() or mysql_file_delete()
+*/
+
+int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time)
+{
+ int error;
+ char to_log[FN_REFLEN];
+ LOG_INFO log_info;
+ MY_STAT stat_area;
+ THD *thd= current_thd;
+
+ DBUG_ENTER("purge_logs_before_date");
+
+ mysql_mutex_lock(&LOCK_index);
+ to_log[0]= 0;
+
+ if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))
+ goto err;
+
+ while (strcmp(log_file_name, log_info.log_file_name) &&
+ can_purge_log(log_info.log_file_name))
+ {
+ if (!mysql_file_stat(m_key_file_log,
+ log_info.log_file_name, &stat_area, MYF(0)))
+ {
+ if (my_errno == ENOENT)
+ {
+ /*
+ It's not fatal if we can't stat a log file that does not exist.
+ */
+ my_errno= 0;
+ }
+ else
+ {
+ /*
+ Other than ENOENT are fatal
+ */
+ if (thd)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with getting info on being purged %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete log file '%s'",
+ log_info.log_file_name);
+ }
+ error= LOG_INFO_FATAL;
+ goto err;
+ }
+ }
+ else
+ {
+ if (stat_area.st_mtime < purge_time)
+ strmake_buf(to_log, log_info.log_file_name);
+ else
+ break;
+ }
+ if (find_next_log(&log_info, 0))
+ break;
+ }
+
+ error= (to_log[0] ? purge_logs(to_log, 1, 0, 1, (ulonglong *) 0) : 0);
+
+err:
+ mysql_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+
+
+bool
+MYSQL_BIN_LOG::can_purge_log(const char *log_file_name)
+{
+ xid_count_per_binlog *b;
+
+ if (is_active(log_file_name))
+ return false;
+ mysql_mutex_lock(&LOCK_xid_list);
+ {
+ I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list);
+ while ((b= it++) &&
+ 0 != strncmp(log_file_name+dirname_length(log_file_name),
+ b->binlog_name, b->binlog_name_len))
+ ;
+ }
+ mysql_mutex_unlock(&LOCK_xid_list);
+ if (b)
+ return false;
+ return !log_in_use(log_file_name);
+}
+#endif /* HAVE_REPLICATION */
+
+
+bool
+MYSQL_BIN_LOG::is_xidlist_idle()
+{
+ bool res;
+ mysql_mutex_lock(&LOCK_xid_list);
+ res= is_xidlist_idle_nolock();
+ mysql_mutex_unlock(&LOCK_xid_list);
+ return res;
+}
+
+
+bool
+MYSQL_BIN_LOG::is_xidlist_idle_nolock()
+{
+ xid_count_per_binlog *b;
+
+ I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list);
+ while ((b= it++))
+ {
+ if (b->xid_count > 0)
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ Create a new log file name.
+
+ @param buf buf of at least FN_REFLEN where new name is stored
+
+ @note
+ If file name will be longer then FN_REFLEN it will be truncated
+*/
+
+void MYSQL_BIN_LOG::make_log_name(char* buf, const char* log_ident)
+{
+ uint dir_len = dirname_length(log_file_name);
+ if (dir_len >= FN_REFLEN)
+ dir_len=FN_REFLEN-1;
+ strnmov(buf, log_file_name, dir_len);
+ strmake(buf+dir_len, log_ident, FN_REFLEN - dir_len -1);
+}
+
+
+/**
+ Check if we are writing/reading to the given log file.
+*/
+
+bool MYSQL_BIN_LOG::is_active(const char *log_file_name_arg)
+{
+ return !strcmp(log_file_name, log_file_name_arg);
+}
+
+
+/*
+ Wrappers around new_file_impl to avoid using argument
+ to control locking. The argument 1) less readable 2) breaks
+ incapsulation 3) allows external access to the class without
+ a lock (which is not possible with private new_file_without_locking
+ method).
+
+ @retval
+ nonzero - error
+*/
+
+int MYSQL_BIN_LOG::new_file()
+{
+ return new_file_impl(1);
+}
+
+/*
+ @retval
+ nonzero - error
+ */
+int MYSQL_BIN_LOG::new_file_without_locking()
+{
+ return new_file_impl(0);
+}
+
+
+/**
+ Start writing to a new log file or reopen the old file.
+
+ @param need_lock Set to 1 if caller has not locked LOCK_log
+
+ @retval
+ nonzero - error
+
+ @note
+ The new file name is stored last in the index file
+*/
+
+int MYSQL_BIN_LOG::new_file_impl(bool need_lock)
+{
+ int error= 0, close_on_error= FALSE;
+ char new_name[FN_REFLEN], *new_name_ptr, *old_name, *file_to_open;
+ uint close_flag;
+ bool delay_close= false;
+ File old_file;
+ LINT_INIT(old_file);
+
+ DBUG_ENTER("MYSQL_BIN_LOG::new_file_impl");
+ if (!is_open())
+ {
+ DBUG_PRINT("info",("log is closed"));
+ DBUG_RETURN(error);
+ }
+
+ if (need_lock)
+ mysql_mutex_lock(&LOCK_log);
+ mysql_mutex_lock(&LOCK_index);
+
+ mysql_mutex_assert_owner(&LOCK_log);
+ mysql_mutex_assert_owner(&LOCK_index);
+
+ /* Reuse old name if not binlog and not update log */
+ new_name_ptr= name;
+
+ /*
+ If user hasn't specified an extension, generate a new log name
+ We have to do this here and not in open as we want to store the
+ new file name in the current binary log file.
+ */
+ if ((error= generate_new_name(new_name, name)))
+ goto end;
+ new_name_ptr=new_name;
+
+ if (log_type == LOG_BIN)
+ {
+ {
+ /*
+ We log the whole file name for log file as the user may decide
+ to change base names at some point.
+ */
+ Rotate_log_event r(new_name+dirname_length(new_name), 0, LOG_EVENT_OFFSET,
+ is_relay_log ? Rotate_log_event::RELAY_LOG : 0);
+ /*
+ The current relay-log's closing Rotate event must have checksum
+ value computed with an algorithm of the last relay-logged FD event.
+ */
+ if (is_relay_log)
+ r.checksum_alg= relay_log_checksum_alg;
+ DBUG_ASSERT(!is_relay_log || relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
+ if(DBUG_EVALUATE_IF("fault_injection_new_file_rotate_event", (error=close_on_error=TRUE), FALSE) ||
+ (error= r.write(&log_file)))
+ {
+ DBUG_EXECUTE_IF("fault_injection_new_file_rotate_event", errno=2;);
+ close_on_error= TRUE;
+ my_printf_error(ER_ERROR_ON_WRITE, ER(ER_CANT_OPEN_FILE), MYF(ME_FATALERROR), name, errno);
+ goto end;
+ }
+ bytes_written += r.data_written;
+ }
+ /*
+ Update needs to be signalled even if there is no rotate event
+ log rotation should give the waiting thread a signal to
+ discover EOF and move on to the next log.
+ */
+ signal_update();
+ }
+ old_name=name;
+ name=0; // Don't free name
+ close_flag= LOG_CLOSE_TO_BE_OPENED | LOG_CLOSE_INDEX;
+ if (!is_relay_log)
+ {
+ /*
+ We need to keep the old binlog file open (and marked as in-use) until
+ the new one is fully created and synced to disk and index. Otherwise we
+ leave a window where if we crash, there is no binlog file marked as
+ crashed for server restart to detect the need for recovery.
+ */
+ old_file= log_file.file;
+ close_flag|= LOG_CLOSE_DELAYED_CLOSE;
+ delay_close= true;
+ }
+ close(close_flag);
+ if (log_type == LOG_BIN && checksum_alg_reset != BINLOG_CHECKSUM_ALG_UNDEF)
+ {
+ DBUG_ASSERT(!is_relay_log);
+ DBUG_ASSERT(binlog_checksum_options != checksum_alg_reset);
+ binlog_checksum_options= checksum_alg_reset;
+ }
+ /*
+ Note that at this point, log_state != LOG_CLOSED (important for is_open()).
+ */
+
+ /*
+ new_file() is only used for rotation (in FLUSH LOGS or because size >
+ max_binlog_size or max_relay_log_size).
+ If this is a binary log, the Format_description_log_event at the beginning of
+ the new file should have created=0 (to distinguish with the
+ Format_description_log_event written at server startup, which should
+ trigger temp tables deletion on slaves.
+ */
+
+ /* reopen index binlog file, BUG#34582 */
+ file_to_open= index_file_name;
+ error= open_index_file(index_file_name, 0, FALSE);
+ if (!error)
+ {
+ /* reopen the binary log file. */
+ file_to_open= new_name_ptr;
+ error= open(old_name, log_type, new_name_ptr, io_cache_type,
+ max_size, 1, FALSE);
+ }
+
+ /* handle reopening errors */
+ if (error)
+ {
+ my_printf_error(ER_CANT_OPEN_FILE, ER(ER_CANT_OPEN_FILE),
+ MYF(ME_FATALERROR), file_to_open, error);
+ close_on_error= TRUE;
+ }
+
+ my_free(old_name);
+
+end:
+
+ if (delay_close)
+ {
+ clear_inuse_flag_when_closing(old_file);
+ mysql_file_close(old_file, MYF(MY_WME));
+ }
+
+ if (error && close_on_error /* rotate or reopen failed */)
+ {
+ /*
+ Close whatever was left opened.
+
+ We are keeping the behavior as it exists today, ie,
+ we disable logging and move on (see: BUG#51014).
+
+ TODO: as part of WL#1790 consider other approaches:
+ - kill mysql (safety);
+ - try multiple locations for opening a log file;
+ - switch server to protected/readonly mode
+ - ...
+ */
+ close(LOG_CLOSE_INDEX);
+ sql_print_error("Could not open %s for logging (error %d). "
+ "Turning logging off for the whole duration "
+ "of the MySQL server process. To turn it on "
+ "again: fix the cause, shutdown the MySQL "
+ "server and restart it.",
+ new_name_ptr, errno);
+ }
+
+ if (need_lock)
+ mysql_mutex_unlock(&LOCK_log);
+ mysql_mutex_unlock(&LOCK_index);
+
+ DBUG_RETURN(error);
+}
+
+
+bool
+MYSQL_BIN_LOG::append(Log_event *ev)
+{
+ bool res;
+ mysql_mutex_lock(&LOCK_log);
+ res= append_no_lock(ev);
+ mysql_mutex_unlock(&LOCK_log);
+ return res;
+}
+
+
+bool MYSQL_BIN_LOG::append_no_lock(Log_event* ev)
+{
+ bool error = 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::append");
+
+ mysql_mutex_assert_owner(&LOCK_log);
+ DBUG_ASSERT(log_file.type == SEQ_READ_APPEND);
+ /*
+ Log_event::write() is smart enough to use my_b_write() or
+ my_b_append() depending on the kind of cache we have.
+ */
+ if (ev->write(&log_file))
+ {
+ error=1;
+ goto err;
+ }
+ bytes_written+= ev->data_written;
+ DBUG_PRINT("info",("max_size: %lu",max_size));
+ if (flush_and_sync(0))
+ goto err;
+ if (my_b_append_tell(&log_file) > max_size)
+ error= new_file_without_locking();
+err:
+ signal_update(); // Safe as we don't call close
+ DBUG_RETURN(error);
+}
+
+
+bool MYSQL_BIN_LOG::appendv(const char* buf, uint len,...)
+{
+ bool error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::appendv");
+ va_list(args);
+ va_start(args,len);
+
+ DBUG_ASSERT(log_file.type == SEQ_READ_APPEND);
+
+ mysql_mutex_assert_owner(&LOCK_log);
+ do
+ {
+ if (my_b_append(&log_file,(uchar*) buf,len))
+ {
+ error= 1;
+ goto err;
+ }
+ bytes_written += len;
+ } while ((buf=va_arg(args,const char*)) && (len=va_arg(args,uint)));
+ DBUG_PRINT("info",("max_size: %lu",max_size));
+ if (flush_and_sync(0))
+ goto err;
+ if (my_b_append_tell(&log_file) > max_size)
+ error= new_file_without_locking();
+err:
+ if (!error)
+ signal_update();
+ DBUG_RETURN(error);
+}
+
+bool MYSQL_BIN_LOG::flush_and_sync(bool *synced)
+{
+ int err=0, fd=log_file.file;
+ if (synced)
+ *synced= 0;
+ mysql_mutex_assert_owner(&LOCK_log);
+ if (flush_io_cache(&log_file))
+ return 1;
+ uint sync_period= get_sync_period();
+ if (sync_period && ++sync_counter >= sync_period)
+ {
+ sync_counter= 0;
+ err= mysql_file_sync(fd, MYF(MY_WME|MY_SYNC_FILESIZE));
+ if (synced)
+ *synced= 1;
+#ifndef DBUG_OFF
+ if (opt_binlog_dbug_fsync_sleep > 0)
+ my_sleep(opt_binlog_dbug_fsync_sleep);
+#endif
+ }
+ return err;
+}
+
+void MYSQL_BIN_LOG::start_union_events(THD *thd, query_id_t query_id_param)
+{
+ DBUG_ASSERT(!thd->binlog_evt_union.do_union);
+ thd->binlog_evt_union.do_union= TRUE;
+ thd->binlog_evt_union.unioned_events= FALSE;
+ thd->binlog_evt_union.unioned_events_trans= FALSE;
+ thd->binlog_evt_union.first_query_id= query_id_param;
+}
+
+void MYSQL_BIN_LOG::stop_union_events(THD *thd)
+{
+ DBUG_ASSERT(thd->binlog_evt_union.do_union);
+ thd->binlog_evt_union.do_union= FALSE;
+}
+
+bool MYSQL_BIN_LOG::is_query_in_union(THD *thd, query_id_t query_id_param)
+{
+ return (thd->binlog_evt_union.do_union &&
+ query_id_param >= thd->binlog_evt_union.first_query_id);
+}
+
+/**
+ This function checks if a transactional table was updated by the
+ current transaction.
+
+ @param thd The client thread that executed the current statement.
+ @return
+ @c true if a transactional table was updated, @c false otherwise.
+*/
+bool
+trans_has_updated_trans_table(const THD* thd)
+{
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ return (cache_mngr ? !cache_mngr->trx_cache.empty() : 0);
+}
+
+/**
+ This function checks if a transactional table was updated by the
+ current statement.
+
+ @param thd The client thread that executed the current statement.
+ @return
+ @c true if a transactional table was updated, @c false otherwise.
+*/
+bool
+stmt_has_updated_trans_table(const THD *thd)
+{
+ Ha_trx_info *ha_info;
+
+ for (ha_info= thd->transaction.stmt.ha_list; ha_info;
+ ha_info= ha_info->next())
+ {
+ if (ha_info->is_trx_read_write() && ha_info->ht() != binlog_hton)
+ return (TRUE);
+ }
+ return (FALSE);
+}
+
+/**
+ This function checks if either a trx-cache or a non-trx-cache should
+ be used. If @c bin_log_direct_non_trans_update is active or the format
+ is either MIXED or ROW, the cache to be used depends on the flag @c
+ is_transactional.
+
+ On the other hand, if binlog_format is STMT or direct option is
+ OFF, the trx-cache should be used if and only if the statement is
+ transactional or the trx-cache is not empty. Otherwise, the
+ non-trx-cache should be used.
+
+ @param thd The client thread.
+ @param is_transactional The changes are related to a trx-table.
+ @return
+ @c true if a trx-cache should be used, @c false otherwise.
+*/
+bool use_trans_cache(const THD* thd, bool is_transactional)
+{
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ return
+ ((thd->is_current_stmt_binlog_format_row() ||
+ thd->variables.binlog_direct_non_trans_update) ? is_transactional :
+ (is_transactional || !cache_mngr->trx_cache.empty()));
+}
+
+/**
+ This function checks if a transaction, either a multi-statement
+ or a single statement transaction is about to commit or not.
+
+ @param thd The client thread that executed the current statement.
+ @param all Committing a transaction (i.e. TRUE) or a statement
+ (i.e. FALSE).
+ @return
+ @c true if committing a transaction, otherwise @c false.
+*/
+bool ending_trans(THD* thd, const bool all)
+{
+ return (all || ending_single_stmt_trans(thd, all));
+}
+
+/**
+ This function checks if a single statement transaction is about
+ to commit or not.
+
+ @param thd The client thread that executed the current statement.
+ @param all Committing a transaction (i.e. TRUE) or a statement
+ (i.e. FALSE).
+ @return
+ @c true if committing a single statement transaction, otherwise
+ @c false.
+*/
+bool ending_single_stmt_trans(THD* thd, const bool all)
+{
+ return (!all && !thd->in_multi_stmt_transaction_mode());
+}
+
+/**
+ This function checks if a non-transactional table was updated by
+ the current transaction.
+
+ @param thd The client thread that executed the current statement.
+ @return
+ @c true if a non-transactional table was updated, @c false
+ otherwise.
+*/
+bool trans_has_updated_non_trans_table(const THD* thd)
+{
+ return (thd->transaction.all.modified_non_trans_table ||
+ thd->transaction.stmt.modified_non_trans_table);
+}
+
+/**
+ This function checks if a non-transactional table was updated by the
+ current statement.
+
+ @param thd The client thread that executed the current statement.
+ @return
+ @c true if a non-transactional table was updated, @c false otherwise.
+*/
+bool stmt_has_updated_non_trans_table(const THD* thd)
+{
+ return (thd->transaction.stmt.modified_non_trans_table);
+}
+
+/*
+ These functions are placed in this file since they need access to
+ binlog_hton, which has internal linkage.
+*/
+
+binlog_cache_mngr *THD::binlog_setup_trx_data()
+{
+ DBUG_ENTER("THD::binlog_setup_trx_data");
+ binlog_cache_mngr *cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ if (cache_mngr)
+ DBUG_RETURN(cache_mngr); // Already set up
+
+ cache_mngr= (binlog_cache_mngr*) my_malloc(sizeof(binlog_cache_mngr), MYF(MY_ZEROFILL));
+ if (!cache_mngr ||
+ open_cached_file(&cache_mngr->stmt_cache.cache_log, mysql_tmpdir,
+ LOG_PREFIX, binlog_stmt_cache_size, MYF(MY_WME)) ||
+ open_cached_file(&cache_mngr->trx_cache.cache_log, mysql_tmpdir,
+ LOG_PREFIX, binlog_cache_size, MYF(MY_WME)))
+ {
+ my_free(cache_mngr);
+ DBUG_RETURN(0); // Didn't manage to set it up
+ }
+ thd_set_ha_data(this, binlog_hton, cache_mngr);
+
+ cache_mngr= new (cache_mngr)
+ binlog_cache_mngr(max_binlog_stmt_cache_size,
+ max_binlog_cache_size,
+ &binlog_stmt_cache_use,
+ &binlog_stmt_cache_disk_use,
+ &binlog_cache_use,
+ &binlog_cache_disk_use);
+ DBUG_RETURN(cache_mngr);
+}
+
+/*
+ Function to start a statement and optionally a transaction for the
+ binary log.
+
+ SYNOPSIS
+ binlog_start_trans_and_stmt()
+
+ DESCRIPTION
+
+ This function does three things:
+ - Start a transaction if not in autocommit mode or if a BEGIN
+ statement has been seen.
+
+ - Start a statement transaction to allow us to truncate the cache.
+
+ - Save the currrent binlog position so that we can roll back the
+ statement by truncating the cache.
+
+ We only update the saved position if the old one was undefined,
+ the reason is that there are some cases (e.g., for CREATE-SELECT)
+ where the position is saved twice (e.g., both in
+ select_create::prepare() and THD::binlog_write_table_map()) , but
+ we should use the first. This means that calls to this function
+ can be used to start the statement before the first table map
+ event, to include some extra events.
+ */
+
+void
+THD::binlog_start_trans_and_stmt()
+{
+ binlog_cache_mngr *cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+ DBUG_ENTER("binlog_start_trans_and_stmt");
+ DBUG_PRINT("enter", ("cache_mngr: %p cache_mngr->trx_cache.get_prev_position(): %lu",
+ cache_mngr,
+ (cache_mngr ? (ulong) cache_mngr->trx_cache.get_prev_position() :
+ (ulong) 0)));
+
+ if (cache_mngr == NULL ||
+ cache_mngr->trx_cache.get_prev_position() == MY_OFF_T_UNDEF)
+ {
+ this->binlog_set_stmt_begin();
+ if (in_multi_stmt_transaction_mode())
+ trans_register_ha(this, TRUE, binlog_hton);
+ trans_register_ha(this, FALSE, binlog_hton);
+ /*
+ Mark statement transaction as read/write. We never start
+ a binary log transaction and keep it read-only,
+ therefore it's best to mark the transaction read/write just
+ at the same time we start it.
+ Not necessary to mark the normal transaction read/write
+ since the statement-level flag will be propagated automatically
+ inside ha_commit_trans.
+ */
+ ha_data[binlog_hton->slot].ha_info[0].set_trx_read_write();
+ }
+ DBUG_VOID_RETURN;
+}
+
+void THD::binlog_set_stmt_begin() {
+ binlog_cache_mngr *cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ /*
+ The call to binlog_trans_log_savepos() might create the cache_mngr
+ structure, if it didn't exist before, so we save the position
+ into an auto variable and then write it into the transaction
+ data for the binary log (i.e., cache_mngr).
+ */
+ my_off_t pos= 0;
+ binlog_trans_log_savepos(this, &pos);
+ cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+ cache_mngr->trx_cache.set_prev_position(pos);
+}
+
+static int
+binlog_start_consistent_snapshot(handlerton *hton, THD *thd)
+{
+ int err= 0;
+ DBUG_ENTER("binlog_start_consistent_snapshot");
+
+ binlog_cache_mngr *const cache_mngr= thd->binlog_setup_trx_data();
+
+ /* Server layer calls us with LOCK_commit_ordered locked, so this is safe. */
+ strmake_buf(cache_mngr->last_commit_pos_file, mysql_bin_log.last_commit_pos_file);
+ cache_mngr->last_commit_pos_offset= mysql_bin_log.last_commit_pos_offset;
+
+ trans_register_ha(thd, TRUE, hton);
+
+ DBUG_RETURN(err);
+}
+
+/**
+ This function writes a table map to the binary log.
+ Note that in order to keep the signature uniform with related methods,
+ we use a redundant parameter to indicate whether a transactional table
+ was changed or not.
+
+ If with_annotate != NULL and
+ *with_annotate = TRUE write also Annotate_rows before the table map.
+
+ @param table a pointer to the table.
+ @param is_transactional @c true indicates a transactional table,
+ otherwise @c false a non-transactional.
+ @return
+ nonzero if an error pops up when writing the table map event.
+*/
+int THD::binlog_write_table_map(TABLE *table, bool is_transactional,
+ my_bool *with_annotate)
+{
+ int error;
+ DBUG_ENTER("THD::binlog_write_table_map");
+ DBUG_PRINT("enter", ("table: 0x%lx (%s: #%lu)",
+ (long) table, table->s->table_name.str,
+ table->s->table_map_id));
+
+ /* Ensure that all events in a GTID group are in the same cache */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ is_transactional= 1;
+
+ /* Pre-conditions */
+ DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open());
+ DBUG_ASSERT(table->s->table_map_id != ULONG_MAX);
+
+ Table_map_log_event
+ the_event(this, table, table->s->table_map_id, is_transactional);
+
+ if (binlog_table_maps == 0)
+ binlog_start_trans_and_stmt();
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ IO_CACHE *file=
+ cache_mngr->get_binlog_cache_log(use_trans_cache(this, is_transactional));
+ if (with_annotate && *with_annotate)
+ {
+ Annotate_rows_log_event anno(table->in_use, is_transactional, false);
+ /* Annotate event should be written not more than once */
+ *with_annotate= 0;
+ if ((error= anno.write(file)))
+ DBUG_RETURN(error);
+ }
+ if ((error= the_event.write(file)))
+ DBUG_RETURN(error);
+
+ binlog_table_maps++;
+ DBUG_RETURN(0);
+}
+
+/**
+ This function retrieves a pending row event from a cache which is
+ specified through the parameter @c is_transactional. Respectively, when it
+ is @c true, the pending event is returned from the transactional cache.
+ Otherwise from the non-transactional cache.
+
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+ @return
+ The row event if any.
+*/
+Rows_log_event*
+THD::binlog_get_pending_rows_event(bool is_transactional) const
+{
+ Rows_log_event* rows= NULL;
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ /*
+ This is less than ideal, but here's the story: If there is no cache_mngr,
+ prepare_pending_rows_event() has never been called (since the cache_mngr
+ is set up there). In that case, we just return NULL.
+ */
+ if (cache_mngr)
+ {
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
+
+ rows= cache_data->pending();
+ }
+ return (rows);
+}
+
+/**
+ This function stores a pending row event into a cache which is specified
+ through the parameter @c is_transactional. Respectively, when it is @c
+ true, the pending event is stored into the transactional cache. Otherwise
+ into the non-transactional cache.
+
+ @param evt a pointer to the row event.
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+*/
+void
+THD::binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional)
+{
+ binlog_cache_mngr *const cache_mngr= binlog_setup_trx_data();
+
+ DBUG_ASSERT(cache_mngr);
+
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
+
+ cache_data->set_pending(ev);
+}
+
+
+/**
+ This function removes the pending rows event, discarding any outstanding
+ rows. If there is no pending rows event available, this is effectively a
+ no-op.
+
+ @param thd a pointer to the user thread.
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+*/
+int
+MYSQL_BIN_LOG::remove_pending_rows_event(THD *thd, bool is_transactional)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::remove_pending_rows_event");
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ DBUG_ASSERT(cache_mngr);
+
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
+
+ if (Rows_log_event* pending= cache_data->pending())
+ {
+ delete pending;
+ cache_data->set_pending(NULL);
+ }
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Moves the last bunch of rows from the pending Rows event to a cache (either
+ transactional cache if is_transaction is @c true, or the non-transactional
+ cache otherwise. Sets a new pending event.
+
+ @param thd a pointer to the user thread.
+ @param evt a pointer to the row event.
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+*/
+int
+MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd,
+ Rows_log_event* event,
+ bool is_transactional)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::flush_and_set_pending_rows_event(event)");
+ DBUG_ASSERT(mysql_bin_log.is_open());
+ DBUG_PRINT("enter", ("event: 0x%lx", (long) event));
+
+ int error= 0;
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ DBUG_ASSERT(cache_mngr);
+
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
+
+ DBUG_PRINT("info", ("cache_mngr->pending(): 0x%lx", (long) cache_data->pending()));
+
+ if (Rows_log_event* pending= cache_data->pending())
+ {
+ IO_CACHE *file= &cache_data->cache_log;
+
+ /*
+ Write pending event to the cache.
+ */
+ DBUG_EXECUTE_IF("simulate_disk_full_at_flush_pending",
+ {DBUG_SET("+d,simulate_file_write_error");});
+ if (pending->write(file))
+ {
+ set_write_error(thd, is_transactional);
+ if (check_write_error(thd) && cache_data &&
+ stmt_has_updated_non_trans_table(thd))
+ cache_data->set_incident();
+ delete pending;
+ cache_data->set_pending(NULL);
+ DBUG_EXECUTE_IF("simulate_disk_full_at_flush_pending",
+ {DBUG_SET("-d,simulate_file_write_error");});
+ DBUG_RETURN(1);
+ }
+
+ delete pending;
+ }
+
+ thd->binlog_set_pending_rows_event(event, is_transactional);
+
+ DBUG_RETURN(error);
+}
+
+
+/* Generate a new global transaction ID, and write it to the binlog */
+
+bool
+MYSQL_BIN_LOG::write_gtid_event(THD *thd, bool standalone,
+ bool is_transactional, uint64 commit_id)
+{
+ rpl_gtid gtid;
+ uint32 domain_id= thd->variables.gtid_domain_id;
+ uint32 server_id= thd->variables.server_id;
+ uint64 seq_no= thd->variables.gtid_seq_no;
+ int err;
+ DBUG_ENTER("write_gtid_event");
+ DBUG_PRINT("enter", ("standalone: %d", standalone));
+
+ if (thd->variables.option_bits & OPTION_GTID_BEGIN)
+ {
+ DBUG_PRINT("error", ("OPTION_GTID_BEGIN is set. "
+ "Master and slave will have different GTID values"));
+ /* Reset the flag, as we will write out a GTID anyway */
+ thd->variables.option_bits&= ~OPTION_GTID_BEGIN;
+ }
+
+ /*
+ Reset the session variable gtid_seq_no, to reduce the risk of accidentally
+ producing a duplicate GTID.
+ */
+ thd->variables.gtid_seq_no= 0;
+ if (seq_no != 0)
+ {
+ /* Use the specified sequence number. */
+ gtid.domain_id= domain_id;
+ gtid.server_id= server_id;
+ gtid.seq_no= seq_no;
+ err= rpl_global_gtid_binlog_state.update(&gtid, opt_gtid_strict_mode);
+ if (err && thd->get_stmt_da()->sql_errno()==ER_GTID_STRICT_OUT_OF_ORDER)
+ errno= ER_GTID_STRICT_OUT_OF_ORDER;
+ }
+ else
+ {
+ /* Allocate the next sequence number for the GTID. */
+ err= rpl_global_gtid_binlog_state.update_with_next_gtid(domain_id,
+ server_id, &gtid);
+ seq_no= gtid.seq_no;
+ }
+ if (err)
+ DBUG_RETURN(true);
+ thd->last_commit_gtid= gtid;
+
+ Gtid_log_event gtid_event(thd, seq_no, domain_id, standalone,
+ LOG_EVENT_SUPPRESS_USE_F, is_transactional,
+ commit_id);
+
+ /* Write the event to the binary log. */
+ if (gtid_event.write(&mysql_bin_log.log_file))
+ DBUG_RETURN(true);
+ status_var_add(thd->status_var.binlog_bytes_written, gtid_event.data_written);
+
+ DBUG_RETURN(false);
+}
+
+
+int
+MYSQL_BIN_LOG::write_state_to_file()
+{
+ File file_no;
+ IO_CACHE cache;
+ char buf[FN_REFLEN];
+ int err;
+ bool opened= false;
+ bool inited= false;
+
+ fn_format(buf, opt_bin_logname, mysql_data_home, ".state",
+ MY_UNPACK_FILENAME);
+ if ((file_no= mysql_file_open(key_file_binlog_state, buf,
+ O_RDWR|O_CREAT|O_TRUNC|O_BINARY,
+ MYF(MY_WME))) < 0)
+ {
+ err= 1;
+ goto err;
+ }
+ opened= true;
+ if ((err= init_io_cache(&cache, file_no, IO_SIZE, WRITE_CACHE, 0, 0,
+ MYF(MY_WME|MY_WAIT_IF_FULL))))
+ goto err;
+ inited= true;
+ if ((err= rpl_global_gtid_binlog_state.write_to_iocache(&cache)))
+ goto err;
+ inited= false;
+ if ((err= end_io_cache(&cache)))
+ goto err;
+ if ((err= mysql_file_sync(file_no, MYF(MY_WME|MY_SYNC_FILESIZE))))
+ goto err;
+ goto end;
+
+err:
+ sql_print_error("Error writing binlog state to file '%s'.\n", buf);
+ if (inited)
+ end_io_cache(&cache);
+end:
+ if (opened)
+ mysql_file_close(file_no, MYF(0));
+
+ return err;
+}
+
+
+/*
+ Initialize the binlog state from the master-bin.state file, at server startup.
+
+ Returns:
+ 0 for success.
+ 2 for when .state file did not exist.
+ 1 for other error.
+*/
+int
+MYSQL_BIN_LOG::read_state_from_file()
+{
+ File file_no;
+ IO_CACHE cache;
+ char buf[FN_REFLEN];
+ int err;
+ bool opened= false;
+ bool inited= false;
+
+ fn_format(buf, opt_bin_logname, mysql_data_home, ".state",
+ MY_UNPACK_FILENAME);
+ if ((file_no= mysql_file_open(key_file_binlog_state, buf,
+ O_RDONLY|O_BINARY, MYF(0))) < 0)
+ {
+ if (my_errno != ENOENT)
+ {
+ err= 1;
+ goto err;
+ }
+ else
+ {
+ /*
+ If the state file does not exist, this is the first server startup
+ with GTID enabled. So initialize to empty state.
+ */
+ rpl_global_gtid_binlog_state.reset();
+ err= 2;
+ goto end;
+ }
+ }
+ opened= true;
+ if ((err= init_io_cache(&cache, file_no, IO_SIZE, READ_CACHE, 0, 0,
+ MYF(MY_WME|MY_WAIT_IF_FULL))))
+ goto err;
+ inited= true;
+ if ((err= rpl_global_gtid_binlog_state.read_from_iocache(&cache)))
+ goto err;
+ goto end;
+
+err:
+ sql_print_error("Error reading binlog GTID state from file '%s'.\n", buf);
+end:
+ if (inited)
+ end_io_cache(&cache);
+ if (opened)
+ mysql_file_close(file_no, MYF(0));
+
+ return err;
+}
+
+
+int
+MYSQL_BIN_LOG::get_most_recent_gtid_list(rpl_gtid **list, uint32 *size)
+{
+ return rpl_global_gtid_binlog_state.get_most_recent_gtid_list(list, size);
+}
+
+
+bool
+MYSQL_BIN_LOG::append_state_pos(String *str)
+{
+ return rpl_global_gtid_binlog_state.append_pos(str);
+}
+
+
+bool
+MYSQL_BIN_LOG::append_state(String *str)
+{
+ return rpl_global_gtid_binlog_state.append_state(str);
+}
+
+
+bool
+MYSQL_BIN_LOG::is_empty_state()
+{
+ return (rpl_global_gtid_binlog_state.count() == 0);
+}
+
+
+bool
+MYSQL_BIN_LOG::find_in_binlog_state(uint32 domain_id, uint32 server_id,
+ rpl_gtid *out_gtid)
+{
+ rpl_gtid *gtid;
+ if ((gtid= rpl_global_gtid_binlog_state.find(domain_id, server_id)))
+ *out_gtid= *gtid;
+ return gtid != NULL;
+}
+
+
+bool
+MYSQL_BIN_LOG::lookup_domain_in_binlog_state(uint32 domain_id,
+ rpl_gtid *out_gtid)
+{
+ rpl_gtid *found_gtid;
+
+ if ((found_gtid= rpl_global_gtid_binlog_state.find_most_recent(domain_id)))
+ {
+ *out_gtid= *found_gtid;
+ return true;
+ }
+
+ return false;
+}
+
+
+int
+MYSQL_BIN_LOG::bump_seq_no_counter_if_needed(uint32 domain_id, uint64 seq_no)
+{
+ return rpl_global_gtid_binlog_state.bump_seq_no_if_needed(domain_id, seq_no);
+}
+
+
+bool
+MYSQL_BIN_LOG::check_strict_gtid_sequence(uint32 domain_id, uint32 server_id,
+ uint64 seq_no)
+{
+ return rpl_global_gtid_binlog_state.check_strict_sequence(domain_id,
+ server_id, seq_no);
+}
+
+
+/**
+ Write an event to the binary log. If with_annotate != NULL and
+ *with_annotate = TRUE write also Annotate_rows before the event
+ (this should happen only if the event is a Table_map).
+*/
+
+bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate)
+{
+ THD *thd= event_info->thd;
+ bool error= 1;
+ binlog_cache_data *cache_data= 0;
+ bool is_trans_cache= FALSE;
+ bool using_trans= event_info->use_trans_cache();
+ bool direct= event_info->use_direct_logging();
+ ulong prev_binlog_id;
+ DBUG_ENTER("MYSQL_BIN_LOG::write(Log_event *)");
+ LINT_INIT(prev_binlog_id);
+
+ if (thd->variables.option_bits & OPTION_GTID_BEGIN)
+ {
+ DBUG_PRINT("info", ("OPTION_GTID_BEGIN was set"));
+ /* Wait for commit from binary log before we commit */
+ direct= 0;
+ using_trans= 1;
+ }
+
+ if (thd->binlog_evt_union.do_union)
+ {
+ /*
+ In Stored function; Remember that function call caused an update.
+ We will log the function call to the binary log on function exit
+ */
+ thd->binlog_evt_union.unioned_events= TRUE;
+ thd->binlog_evt_union.unioned_events_trans |= using_trans;
+ DBUG_RETURN(0);
+ }
+
+ /*
+ We only end the statement if we are in a top-level statement. If
+ we are inside a stored function, we do not end the statement since
+ this will close all tables on the slave.
+ */
+ bool const end_stmt=
+ thd->locked_tables_mode && thd->lex->requires_prelocking();
+ if (thd->binlog_flush_pending_rows_event(end_stmt, using_trans))
+ DBUG_RETURN(error);
+
+ /*
+ In most cases this is only called if 'is_open()' is true; in fact this is
+ mostly called if is_open() *was* true a few instructions before, but it
+ could have changed since.
+ */
+ if (likely(is_open()))
+ {
+ my_off_t UNINIT_VAR(my_org_b_tell);
+#ifdef HAVE_REPLICATION
+ /*
+ In the future we need to add to the following if tests like
+ "do the involved tables match (to be implemented)
+ binlog_[wild_]{do|ignore}_table?" (WL#1049)"
+ */
+ const char *local_db= event_info->get_db();
+ if ((!(thd->variables.option_bits & OPTION_BIN_LOG)) ||
+ (thd->lex->sql_command != SQLCOM_ROLLBACK_TO_SAVEPOINT &&
+ thd->lex->sql_command != SQLCOM_SAVEPOINT &&
+ !binlog_filter->db_ok(local_db)))
+ DBUG_RETURN(0);
+#endif /* HAVE_REPLICATION */
+
+ IO_CACHE *file= NULL;
+
+ if (direct)
+ {
+ int res;
+ uint64 commit_id= 0;
+ DBUG_PRINT("info", ("direct is set"));
+ if ((res= thd->wait_for_prior_commit()))
+ DBUG_RETURN(res);
+ file= &log_file;
+ my_org_b_tell= my_b_tell(file);
+ mysql_mutex_lock(&LOCK_log);
+ prev_binlog_id= current_binlog_id;
+ DBUG_EXECUTE_IF("binlog_force_commit_id",
+ {
+ const LEX_STRING name= { C_STRING_WITH_LEN("commit_id") };
+ bool null_value;
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars,
+ (uchar*) name.str, name.length);
+ commit_id= entry->val_int(&null_value);
+ });
+ if (write_gtid_event(thd, true, using_trans, commit_id))
+ goto err;
+ }
+ else
+ {
+ binlog_cache_mngr *const cache_mngr= thd->binlog_setup_trx_data();
+ if (!cache_mngr)
+ goto err;
+
+ is_trans_cache= use_trans_cache(thd, using_trans);
+ file= cache_mngr->get_binlog_cache_log(is_trans_cache);
+ cache_data= cache_mngr->get_binlog_cache_data(is_trans_cache);
+
+ if (thd->lex->stmt_accessed_non_trans_temp_table())
+ cache_data->set_changes_to_non_trans_temp_table();
+
+ thd->binlog_start_trans_and_stmt();
+ }
+ DBUG_PRINT("info",("event type: %d",event_info->get_type_code()));
+
+ /*
+ No check for auto events flag here - this write method should
+ never be called if auto-events are enabled.
+
+ Write first log events which describe the 'run environment'
+ of the SQL command. If row-based binlogging, Insert_id, Rand
+ and other kind of "setting context" events are not needed.
+ */
+
+ if (with_annotate && *with_annotate)
+ {
+ DBUG_ASSERT(event_info->get_type_code() == TABLE_MAP_EVENT);
+ Annotate_rows_log_event anno(thd, using_trans, direct);
+ /* Annotate event should be written not more than once */
+ *with_annotate= 0;
+ if (anno.write(file))
+ goto err;
+ }
+
+ if (thd)
+ {
+ if (!thd->is_current_stmt_binlog_format_row())
+ {
+
+ if (thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt)
+ {
+ Intvar_log_event e(thd,(uchar) LAST_INSERT_ID_EVENT,
+ thd->first_successful_insert_id_in_prev_stmt_for_binlog,
+ using_trans, direct);
+ if (e.write(file))
+ goto err;
+ }
+ if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0)
+ {
+ DBUG_PRINT("info",("number of auto_inc intervals: %u",
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.
+ nb_elements()));
+ Intvar_log_event e(thd, (uchar) INSERT_ID_EVENT,
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.
+ minimum(), using_trans, direct);
+ if (e.write(file))
+ goto err;
+ }
+ if (thd->rand_used)
+ {
+ Rand_log_event e(thd,thd->rand_saved_seed1,thd->rand_saved_seed2,
+ using_trans, direct);
+ if (e.write(file))
+ goto err;
+ }
+ if (thd->user_var_events.elements)
+ {
+ for (uint i= 0; i < thd->user_var_events.elements; i++)
+ {
+ BINLOG_USER_VAR_EVENT *user_var_event;
+ get_dynamic(&thd->user_var_events,(uchar*) &user_var_event, i);
+
+ /* setting flags for user var log event */
+ uchar flags= User_var_log_event::UNDEF_F;
+ if (user_var_event->unsigned_flag)
+ flags|= User_var_log_event::UNSIGNED_F;
+
+ User_var_log_event e(thd, user_var_event->user_var_event->name.str,
+ user_var_event->user_var_event->name.length,
+ user_var_event->value,
+ user_var_event->length,
+ user_var_event->type,
+ user_var_event->charset_number,
+ flags,
+ using_trans,
+ direct);
+ if (e.write(file))
+ goto err;
+ }
+ }
+ }
+ }
+
+ /*
+ Write the event.
+ */
+ if (event_info->write(file) ||
+ DBUG_EVALUATE_IF("injecting_fault_writing", 1, 0))
+ goto err;
+
+ error= 0;
+err:
+ if (direct)
+ {
+ my_off_t offset= my_b_tell(file);
+ bool check_purge= false;
+
+ if (!error)
+ {
+ bool synced;
+
+ if ((error= flush_and_sync(&synced)))
+ {
+ }
+ else if ((error= RUN_HOOK(binlog_storage, after_flush,
+ (thd, log_file_name, file->pos_in_file, synced))))
+ {
+ sql_print_error("Failed to run 'after_flush' hooks");
+ }
+ else
+ {
+ signal_update();
+ if ((error= rotate(false, &check_purge)))
+ check_purge= false;
+ }
+ }
+
+ status_var_add(thd->status_var.binlog_bytes_written,
+ offset - my_org_b_tell);
+
+ /*
+ Take mutex to protect against a reader seeing partial writes of 64-bit
+ offset on 32-bit CPUs.
+ */
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ last_commit_pos_offset= offset;
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+ mysql_mutex_unlock(&LOCK_log);
+
+ if (check_purge)
+ checkpoint_and_purge(prev_binlog_id);
+ }
+
+ if (error)
+ {
+ set_write_error(thd, is_trans_cache);
+ if (check_write_error(thd) && cache_data &&
+ stmt_has_updated_non_trans_table(thd))
+ cache_data->set_incident();
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+int error_log_print(enum loglevel level, const char *format,
+ va_list args)
+{
+ return logger.error_log_print(level, format, args);
+}
+
+
+bool slow_log_print(THD *thd, const char *query, uint query_length,
+ ulonglong current_utime)
+{
+ return logger.slow_log_print(thd, query, query_length, current_utime);
+}
+
+
+bool LOGGER::log_command(THD *thd, enum enum_server_command command)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *sctx= thd->security_ctx;
+#endif
+ /*
+ Log command if we have at least one log event handler enabled and want
+ to log this king of commands
+ */
+ if (*general_log_handler_list && (what_to_log & (1L << (uint) command)))
+ {
+ if ((thd->variables.option_bits & OPTION_LOG_OFF)
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ && (sctx->master_access & SUPER_ACL)
+#endif
+ )
+ {
+ /* No logging */
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+bool general_log_print(THD *thd, enum enum_server_command command,
+ const char *format, ...)
+{
+ va_list args;
+ uint error= 0;
+
+ /* Print the message to the buffer if we want to log this king of commands */
+ if (! logger.log_command(thd, command))
+ return FALSE;
+
+ va_start(args, format);
+ error= logger.general_log_print(thd, command, format, args);
+ va_end(args);
+
+ return error;
+}
+
+bool general_log_write(THD *thd, enum enum_server_command command,
+ const char *query, uint query_length)
+{
+ /* Write the message to the log if we want to log this king of commands */
+ if (logger.log_command(thd, command) || mysql_audit_general_enabled())
+ return logger.general_log_write(thd, command, query, query_length);
+
+ return FALSE;
+}
+
+
+static void
+binlog_checkpoint_callback(void *cookie)
+{
+ MYSQL_BIN_LOG::xid_count_per_binlog *entry=
+ (MYSQL_BIN_LOG::xid_count_per_binlog *)cookie;
+ /*
+ For every supporting engine, we increment the xid_count and issue a
+ commit_checkpoint_request(). Then we can count when all
+ commit_checkpoint_notify() callbacks have occured, and then log a new
+ binlog checkpoint event.
+ */
+ mysql_bin_log.mark_xids_active(entry->binlog_id, 1);
+}
+
+
+/*
+ Request a commit checkpoint from each supporting engine.
+ This must be called after each binlog rotate, and after LOCK_log has been
+ released. The xid_count value in the xid_count_per_binlog entry was
+ incremented by 1 and will be decremented in this function; this ensures
+ that the entry will not go away early despite LOCK_log not being held.
+*/
+void
+MYSQL_BIN_LOG::do_checkpoint_request(ulong binlog_id)
+{
+ xid_count_per_binlog *entry;
+
+ /*
+ Find the binlog entry, and invoke commit_checkpoint_request() on it in
+ each supporting storage engine.
+ */
+ mysql_mutex_lock(&LOCK_xid_list);
+ I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list);
+ do {
+ entry= it++;
+ DBUG_ASSERT(entry /* binlog_id is always somewhere in the list. */);
+ } while (entry->binlog_id != binlog_id);
+ mysql_mutex_unlock(&LOCK_xid_list);
+
+ ha_commit_checkpoint_request(entry, binlog_checkpoint_callback);
+ /*
+ When we rotated the binlog, we incremented xid_count to make sure the
+ entry would not go away until this point, where we have done all necessary
+ commit_checkpoint_request() calls.
+ So now we can (and must) decrease the count - when it reaches zero, we
+ will know that both all pending unlog() and all pending
+ commit_checkpoint_notify() calls are done, and we can log a new binlog
+ checkpoint.
+ */
+ mark_xid_done(binlog_id, true);
+}
+
+
+/**
+ The method executes rotation when LOCK_log is already acquired
+ by the caller.
+
+ @param force_rotate caller can request the log rotation
+ @param check_purge is set to true if rotation took place
+
+ @note
+ Caller _must_ check the check_purge variable. If this is set, it means
+ that the binlog was rotated, and caller _must_ ensure that
+ do_checkpoint_request() is called later with the binlog_id of the rotated
+ binlog file. The call to do_checkpoint_request() must happen after
+ LOCK_log is released (which is why we cannot simply do it here).
+ Usually, checkpoint_and_purge() is appropriate, as it will both handle
+ the checkpointing and any needed purging of old logs.
+
+ @note
+ If rotation fails, for instance the server was unable
+ to create a new log file, we still try to write an
+ incident event to the current log.
+
+ @retval
+ nonzero - error in rotating routine.
+*/
+int MYSQL_BIN_LOG::rotate(bool force_rotate, bool* check_purge)
+{
+ int error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::rotate");
+
+ //todo: fix the macro def and restore safe_mutex_assert_owner(&LOCK_log);
+ *check_purge= false;
+
+ if (force_rotate || (my_b_tell(&log_file) >= (my_off_t) max_size))
+ {
+ ulong binlog_id= current_binlog_id;
+ /*
+ We rotate the binlog, so we need to start a commit checkpoint in all
+ supporting engines - when it finishes, we can log a new binlog checkpoint
+ event.
+
+ But we cannot start the checkpoint here - there could be a group commit
+ still in progress which needs to be included in the checkpoint, and
+ besides we do not want to do the (possibly expensive) checkpoint while
+ LOCK_log is held.
+
+ On the other hand, we must be sure that the xid_count entry for the
+ previous log does not go away until we start the checkpoint - which it
+ could do as it is no longer the most recent. So we increment xid_count
+ (to count the pending checkpoint request) - this will fix the entry in
+ place until we decrement again in do_checkpoint_request().
+ */
+ mark_xids_active(binlog_id, 1);
+
+ if ((error= new_file_without_locking()))
+ {
+ /**
+ Be conservative... There are possible lost events (eg,
+ failing to log the Execute_load_query_log_event
+ on a LOAD DATA while using a non-transactional
+ table)!
+
+ We give it a shot and try to write an incident event anyway
+ to the current log.
+ */
+ if (!write_incident_already_locked(current_thd))
+ flush_and_sync(0);
+
+ /*
+ We failed to rotate - so we have to decrement the xid_count back that
+ we incremented before attempting the rotate.
+ */
+ mark_xid_done(binlog_id, false);
+ }
+ else
+ *check_purge= true;
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ The method executes logs purging routine.
+
+ @retval
+ nonzero - error in rotating routine.
+*/
+void MYSQL_BIN_LOG::purge()
+{
+ mysql_mutex_assert_not_owner(&LOCK_log);
+#ifdef HAVE_REPLICATION
+ if (expire_logs_days)
+ {
+ DEBUG_SYNC(current_thd, "at_purge_logs_before_date");
+ time_t purge_time= my_time(0) - expire_logs_days*24*60*60;
+ if (purge_time >= 0)
+ {
+ purge_logs_before_date(purge_time);
+ }
+ DEBUG_SYNC(current_thd, "after_purge_logs_before_date");
+ }
+#endif
+}
+
+
+void MYSQL_BIN_LOG::checkpoint_and_purge(ulong binlog_id)
+{
+ do_checkpoint_request(binlog_id);
+ purge();
+}
+
+/**
+ The method is a shortcut of @c rotate() and @c purge().
+ LOCK_log is acquired prior to rotate and is released after it.
+
+ @param force_rotate caller can request the log rotation
+
+ @retval
+ nonzero - error in rotating routine.
+*/
+int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate)
+{
+ int error= 0;
+ ulong prev_binlog_id;
+ DBUG_ENTER("MYSQL_BIN_LOG::rotate_and_purge");
+ bool check_purge= false;
+
+ //todo: fix the macro def and restore safe_mutex_assert_not_owner(&LOCK_log);
+ mysql_mutex_lock(&LOCK_log);
+ prev_binlog_id= current_binlog_id;
+ if ((error= rotate(force_rotate, &check_purge)))
+ check_purge= false;
+ /*
+ NOTE: Run purge_logs wo/ holding LOCK_log because it does not need
+ the mutex. Otherwise causes various deadlocks.
+ */
+ mysql_mutex_unlock(&LOCK_log);
+
+ if (check_purge)
+ checkpoint_and_purge(prev_binlog_id);
+
+ DBUG_RETURN(error);
+}
+
+uint MYSQL_BIN_LOG::next_file_id()
+{
+ uint res;
+ mysql_mutex_lock(&LOCK_log);
+ res = file_id++;
+ mysql_mutex_unlock(&LOCK_log);
+ return res;
+}
+
+
+/**
+ Calculate checksum of possibly a part of an event containing at least
+ the whole common header.
+
+ @param buf the pointer to trans cache's buffer
+ @param off the offset of the beginning of the event in the buffer
+ @param event_len no-checksum length of the event
+ @param length the current size of the buffer
+
+ @param crc [in-out] the checksum
+
+ Event size in incremented by @c BINLOG_CHECKSUM_LEN.
+
+ @return 0 or number of unprocessed yet bytes of the event excluding
+ the checksum part.
+*/
+ static ulong fix_log_event_crc(uchar *buf, uint off, uint event_len,
+ uint length, ha_checksum *crc)
+{
+ ulong ret;
+ uchar *event_begin= buf + off;
+
+ ret= length >= off + event_len ? 0 : off + event_len - length;
+ *crc= my_checksum(*crc, event_begin, event_len - ret);
+ return ret;
+}
+
+/*
+ Write the contents of a cache to the binary log.
+
+ SYNOPSIS
+ write_cache()
+ thd Current_thread
+ cache Cache to write to the binary log
+
+ DESCRIPTION
+ Write the contents of the cache to the binary log. The cache will
+ be reset as a READ_CACHE to be able to read the contents from it.
+
+ Reading from the trans cache with possible (per @c binlog_checksum_options)
+ adding checksum value and then fixing the length and the end_log_pos of
+ events prior to fill in the binlog cache.
+*/
+
+int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache)
+{
+ mysql_mutex_assert_owner(&LOCK_log);
+ if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0))
+ return ER_ERROR_ON_WRITE;
+ uint length= my_b_bytes_in_cache(cache), group, carry, hdr_offs;
+ ulong remains= 0; // part of unprocessed yet netto length of the event
+ long val;
+ ulong end_log_pos_inc= 0; // each event processed adds BINLOG_CHECKSUM_LEN 2 t
+ uchar header[LOG_EVENT_HEADER_LEN];
+ ha_checksum crc= 0, crc_0= 0; // assignments to keep compiler happy
+ my_bool do_checksum= (binlog_checksum_options != BINLOG_CHECKSUM_ALG_OFF);
+ uchar buf[BINLOG_CHECKSUM_LEN];
+
+ // while there is just one alg the following must hold:
+ DBUG_ASSERT(!do_checksum ||
+ binlog_checksum_options == BINLOG_CHECKSUM_ALG_CRC32);
+
+ /*
+ The events in the buffer have incorrect end_log_pos data
+ (relative to beginning of group rather than absolute),
+ so we'll recalculate them in situ so the binlog is always
+ correct, even in the middle of a group. This is possible
+ because we now know the start position of the group (the
+ offset of this cache in the log, if you will); all we need
+ to do is to find all event-headers, and add the position of
+ the group to the end_log_pos of each event. This is pretty
+ straight forward, except that we read the cache in segments,
+ so an event-header might end up on the cache-border and get
+ split.
+ */
+
+ group= (uint)my_b_tell(&log_file);
+ hdr_offs= carry= 0;
+ if (do_checksum)
+ crc= crc_0= my_checksum(0L, NULL, 0);
+
+ do
+ {
+ /*
+ if we only got a partial header in the last iteration,
+ get the other half now and process a full header.
+ */
+ if (unlikely(carry > 0))
+ {
+ DBUG_ASSERT(carry < LOG_EVENT_HEADER_LEN);
+
+ /* assemble both halves */
+ memcpy(&header[carry], (char *)cache->read_pos,
+ LOG_EVENT_HEADER_LEN - carry);
+
+ /* fix end_log_pos */
+ val= uint4korr(&header[LOG_POS_OFFSET]) + group +
+ (end_log_pos_inc+= (do_checksum ? BINLOG_CHECKSUM_LEN : 0));
+ int4store(&header[LOG_POS_OFFSET], val);
+
+ if (do_checksum)
+ {
+ ulong len= uint4korr(&header[EVENT_LEN_OFFSET]);
+ /* fix len */
+ int4store(&header[EVENT_LEN_OFFSET], len + BINLOG_CHECKSUM_LEN);
+ }
+
+ /* write the first half of the split header */
+ if (my_b_write(&log_file, header, carry))
+ return ER_ERROR_ON_WRITE;
+ status_var_add(thd->status_var.binlog_bytes_written, carry);
+
+ /*
+ copy fixed second half of header to cache so the correct
+ version will be written later.
+ */
+ memcpy((char *)cache->read_pos, &header[carry],
+ LOG_EVENT_HEADER_LEN - carry);
+
+ /* next event header at ... */
+ hdr_offs= uint4korr(&header[EVENT_LEN_OFFSET]) - carry -
+ (do_checksum ? BINLOG_CHECKSUM_LEN : 0);
+
+ if (do_checksum)
+ {
+ DBUG_ASSERT(crc == crc_0 && remains == 0);
+ crc= my_checksum(crc, header, carry);
+ remains= uint4korr(header + EVENT_LEN_OFFSET) - carry -
+ BINLOG_CHECKSUM_LEN;
+ }
+ carry= 0;
+ }
+
+ /* if there is anything to write, process it. */
+
+ if (likely(length > 0))
+ {
+ /*
+ process all event-headers in this (partial) cache.
+ if next header is beyond current read-buffer,
+ we'll get it later (though not necessarily in the
+ very next iteration, just "eventually").
+ */
+
+ /* crc-calc the whole buffer */
+ if (do_checksum && hdr_offs >= length)
+ {
+
+ DBUG_ASSERT(remains != 0 && crc != crc_0);
+
+ crc= my_checksum(crc, cache->read_pos, length);
+ remains -= length;
+ if (my_b_write(&log_file, cache->read_pos, length))
+ return ER_ERROR_ON_WRITE;
+ if (remains == 0)
+ {
+ int4store(buf, crc);
+ if (my_b_write(&log_file, buf, BINLOG_CHECKSUM_LEN))
+ return ER_ERROR_ON_WRITE;
+ crc= crc_0;
+ }
+ }
+
+ while (hdr_offs < length)
+ {
+ /*
+ partial header only? save what we can get, process once
+ we get the rest.
+ */
+
+ if (do_checksum)
+ {
+ if (remains != 0)
+ {
+ /*
+ finish off with remains of the last event that crawls
+ from previous into the current buffer
+ */
+ DBUG_ASSERT(crc != crc_0);
+ crc= my_checksum(crc, cache->read_pos, hdr_offs);
+ int4store(buf, crc);
+ remains -= hdr_offs;
+ DBUG_ASSERT(remains == 0);
+ if (my_b_write(&log_file, cache->read_pos, hdr_offs) ||
+ my_b_write(&log_file, buf, BINLOG_CHECKSUM_LEN))
+ return ER_ERROR_ON_WRITE;
+ crc= crc_0;
+ }
+ }
+
+ if (hdr_offs + LOG_EVENT_HEADER_LEN > length)
+ {
+ carry= length - hdr_offs;
+ memcpy(header, (char *)cache->read_pos + hdr_offs, carry);
+ length= hdr_offs;
+ }
+ else
+ {
+ /* we've got a full event-header, and it came in one piece */
+ uchar *ev= (uchar *)cache->read_pos + hdr_offs;
+ uint event_len= uint4korr(ev + EVENT_LEN_OFFSET); // netto len
+ uchar *log_pos= ev + LOG_POS_OFFSET;
+
+ /* fix end_log_pos */
+ val= uint4korr(log_pos) + group +
+ (end_log_pos_inc += (do_checksum ? BINLOG_CHECKSUM_LEN : 0));
+ int4store(log_pos, val);
+
+ /* fix CRC */
+ if (do_checksum)
+ {
+ /* fix length */
+ int4store(ev + EVENT_LEN_OFFSET, event_len + BINLOG_CHECKSUM_LEN);
+ remains= fix_log_event_crc(cache->read_pos, hdr_offs, event_len,
+ length, &crc);
+ if (my_b_write(&log_file, ev,
+ remains == 0 ? event_len : length - hdr_offs))
+ return ER_ERROR_ON_WRITE;
+ if (remains == 0)
+ {
+ int4store(buf, crc);
+ if (my_b_write(&log_file, buf, BINLOG_CHECKSUM_LEN))
+ return ER_ERROR_ON_WRITE;
+ crc= crc_0; // crc is complete
+ }
+ }
+
+ /* next event header at ... */
+ hdr_offs += event_len; // incr by the netto len
+
+ DBUG_ASSERT(!do_checksum || remains == 0 || hdr_offs >= length);
+ }
+ }
+
+ /*
+ Adjust hdr_offs. Note that it may still point beyond the segment
+ read in the next iteration; if the current event is very long,
+ it may take a couple of read-iterations (and subsequent adjustments
+ of hdr_offs) for it to point into the then-current segment.
+ If we have a split header (!carry), hdr_offs will be set at the
+ beginning of the next iteration, overwriting the value we set here:
+ */
+ hdr_offs -= length;
+ }
+
+ /* Write data to the binary log file */
+ DBUG_EXECUTE_IF("fail_binlog_write_1",
+ errno= 28; return ER_ERROR_ON_WRITE;);
+ if (!do_checksum)
+ if (my_b_write(&log_file, cache->read_pos, length))
+ return ER_ERROR_ON_WRITE;
+ status_var_add(thd->status_var.binlog_bytes_written, length);
+
+ cache->read_pos=cache->read_end; // Mark buffer used up
+ } while ((length= my_b_fill(cache)));
+
+ DBUG_ASSERT(carry == 0);
+ DBUG_ASSERT(!do_checksum || remains == 0);
+ DBUG_ASSERT(!do_checksum || crc == crc_0);
+
+ return 0; // All OK
+}
+
+/*
+ Helper function to get the error code of the query to be binlogged.
+ */
+int query_error_code(THD *thd, bool not_killed)
+{
+ int error;
+
+ if (not_killed || (killed_mask_hard(thd->killed) == KILL_BAD_DATA))
+ {
+ error= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0;
+
+ /* thd->get_get_stmt_da()->sql_errno() might be ER_SERVER_SHUTDOWN or
+ ER_QUERY_INTERRUPTED, So here we need to make sure that error
+ is not set to these errors when specified not_killed by the
+ caller.
+ */
+ if (error == ER_SERVER_SHUTDOWN || error == ER_QUERY_INTERRUPTED ||
+ error == ER_NEW_ABORTING_CONNECTION || error == ER_CONNECTION_KILLED)
+ error= 0;
+ }
+ else
+ {
+ /* killed status for DELAYED INSERT thread should never be used */
+ DBUG_ASSERT(!(thd->system_thread & SYSTEM_THREAD_DELAYED_INSERT));
+ error= thd->killed_errno();
+ }
+
+ return error;
+}
+
+
+bool MYSQL_BIN_LOG::write_incident_already_locked(THD *thd)
+{
+ uint error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::write_incident_already_locked");
+ Incident incident= INCIDENT_LOST_EVENTS;
+ Incident_log_event ev(thd, incident, write_error_msg);
+
+ if (likely(is_open()))
+ {
+ error= ev.write(&log_file);
+ status_var_add(thd->status_var.binlog_bytes_written, ev.data_written);
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+bool MYSQL_BIN_LOG::write_incident(THD *thd)
+{
+ uint error= 0;
+ my_off_t offset;
+ bool check_purge= false;
+ ulong prev_binlog_id;
+ DBUG_ENTER("MYSQL_BIN_LOG::write_incident");
+
+ mysql_mutex_lock(&LOCK_log);
+ if (likely(is_open()))
+ {
+ prev_binlog_id= current_binlog_id;
+ if (!(error= write_incident_already_locked(thd)) &&
+ !(error= flush_and_sync(0)))
+ {
+ signal_update();
+ if ((error= rotate(false, &check_purge)))
+ check_purge= false;
+ }
+
+ offset= my_b_tell(&log_file);
+ /*
+ Take mutex to protect against a reader seeing partial writes of 64-bit
+ offset on 32-bit CPUs.
+ */
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ last_commit_pos_offset= offset;
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+ mysql_mutex_unlock(&LOCK_log);
+
+ if (check_purge)
+ checkpoint_and_purge(prev_binlog_id);
+ }
+ else
+ {
+ mysql_mutex_unlock(&LOCK_log);
+ }
+
+ DBUG_RETURN(error);
+}
+
+void
+MYSQL_BIN_LOG::write_binlog_checkpoint_event_already_locked(const char *name,
+ uint len)
+{
+ my_off_t offset;
+ Binlog_checkpoint_log_event ev(name, len);
+ /*
+ Note that we must sync the binlog checkpoint to disk.
+ Otherwise a subsequent log purge could delete binlogs that XA recovery
+ thinks are needed (even though they are not really).
+ */
+ if (!ev.write(&log_file) && !flush_and_sync(0))
+ {
+ signal_update();
+ }
+ else
+ {
+ /*
+ If we fail to write the checkpoint event, something is probably really
+ bad with the binlog. We complain in the error log.
+
+ Note that failure to write binlog checkpoint does not compromise the
+ ability to do crash recovery - crash recovery will just have to scan a
+ bit more of the binlog than strictly necessary.
+ */
+ sql_print_error("Failed to write binlog checkpoint event to binary log\n");
+ }
+
+ offset= my_b_tell(&log_file);
+ /*
+ Take mutex to protect against a reader seeing partial writes of 64-bit
+ offset on 32-bit CPUs.
+ */
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ last_commit_pos_offset= offset;
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+}
+
+
+/**
+ Write a cached log entry to the binary log.
+ - To support transaction over replication, we wrap the transaction
+ with BEGIN/COMMIT or BEGIN/ROLLBACK in the binary log.
+ We want to write a BEGIN/ROLLBACK block when a non-transactional table
+ was updated in a transaction which was rolled back. This is to ensure
+ that the same updates are run on the slave.
+
+ @param thd
+ @param cache The cache to copy to the binlog
+ @param commit_event The commit event to print after writing the
+ contents of the cache.
+ @param incident Defines if an incident event should be created to
+ notify that some non-transactional changes did
+ not get into the binlog.
+
+ @note
+ We only come here if there is something in the cache.
+ @note
+ The thing in the cache is always a complete transaction.
+ @note
+ 'cache' needs to be reinitialized after this functions returns.
+*/
+
+bool
+MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd,
+ binlog_cache_mngr *cache_mngr,
+ Log_event *end_ev, bool all,
+ bool using_stmt_cache,
+ bool using_trx_cache)
+{
+ group_commit_entry entry;
+ Ha_trx_info *ha_info;
+ DBUG_ENTER("MYSQL_BIN_LOG::write_transaction_to_binlog");
+
+ entry.thd= thd;
+ entry.cache_mngr= cache_mngr;
+ entry.error= 0;
+ entry.all= all;
+ entry.using_stmt_cache= using_stmt_cache;
+ entry.using_trx_cache= using_trx_cache;
+ entry.need_unlog= false;
+ ha_info= all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list;
+ for (; ha_info; ha_info= ha_info->next())
+ {
+ if (ha_info->is_started() && ha_info->ht() != binlog_hton &&
+ !ha_info->ht()->commit_checkpoint_request)
+ entry.need_unlog= true;
+ break;
+ }
+
+ entry.end_event= end_ev;
+ if (cache_mngr->stmt_cache.has_incident() ||
+ cache_mngr->trx_cache.has_incident())
+ {
+ Incident_log_event inc_ev(thd, INCIDENT_LOST_EVENTS, write_error_msg);
+ entry.incident_event= &inc_ev;
+ DBUG_RETURN(write_transaction_to_binlog_events(&entry));
+ }
+ else
+ {
+ entry.incident_event= NULL;
+ DBUG_RETURN(write_transaction_to_binlog_events(&entry));
+ }
+}
+
+
+/*
+ Put a transaction that is ready to commit in the group commit queue.
+ The transaction is identified by the ENTRY object passed into this function.
+
+ To facilitate group commit for the binlog, we first queue up ourselves in
+ this function. Then later the first thread to enter the queue waits for
+ the LOCK_log mutex, and commits for everyone in the queue once it gets the
+ lock. Any other threads in the queue just wait for the first one to finish
+ the commit and wake them up. This way, all transactions in the queue get
+ committed in a single disk operation.
+
+ The main work in this function is when the commit in one transaction has
+ been marked to wait for the commit of another transaction to happen
+ first. This is used to support in-order parallel replication, where
+ transactions can execute out-of-order but need to be committed in-order with
+ how they happened on the master. The waiting of one commit on another needs
+ to be integrated with the group commit queue, to ensure that the waiting
+ transaction can participate in the same group commit as the waited-for
+ transaction.
+
+ So when we put a transaction in the queue, we check if there were other
+ transactions already prepared to commit but just waiting for the first one
+ to commit. If so, we add those to the queue as well, transitively for all
+ waiters.
+
+ And if a transaction is marked to wait for a prior transaction, but that
+ prior transaction is already queued for group commit, then we can queue the
+ new transaction directly to participate in the group commit.
+
+ @retval < 0 Error
+ @retval > 0 If queued as the first entry in the queue (meaning this
+ is the leader)
+ @retval 0 Otherwise (queued as participant, leader handles the commit)
+*/
+
+int
+MYSQL_BIN_LOG::queue_for_group_commit(group_commit_entry *orig_entry)
+{
+ group_commit_entry *entry, *orig_queue, *last;
+ wait_for_commit *cur;
+ wait_for_commit *wfc;
+ DBUG_ENTER("MYSQL_BIN_LOG::queue_for_group_commit");
+
+ /*
+ Check if we need to wait for another transaction to commit before us.
+
+ It is safe to do a quick check without lock first in the case where we do
+ not have to wait. But if the quick check shows we need to wait, we must do
+ another safe check under lock, to avoid the race where the other
+ transaction wakes us up between the check and the wait.
+ */
+ wfc= orig_entry->thd->wait_for_commit_ptr;
+ orig_entry->queued_by_other= false;
+ if (wfc && wfc->waitee)
+ {
+ mysql_mutex_lock(&wfc->LOCK_wait_commit);
+ /*
+ Do an extra check here, this time safely under lock.
+
+ If waitee->commit_started is set, it means that the transaction we need
+ to wait for has already queued up for group commit. In this case it is
+ safe for us to queue up immediately as well, increasing the opprtunities
+ for group commit. Because waitee has taken the LOCK_prepare_ordered
+ before setting the flag, so there is no risk that we can queue ahead of
+ it.
+ */
+ if (wfc->waitee && !wfc->waitee->commit_started)
+ {
+ PSI_stage_info old_stage;
+ wait_for_commit *loc_waitee;
+
+ /*
+ By setting wfc->opaque_pointer to our own entry, we mark that we are
+ ready to commit, but waiting for another transaction to commit before
+ us.
+
+ This other transaction may then take over the commit process for us to
+ get us included in its own group commit. If this happens, the
+ queued_by_other flag is set.
+
+ Setting this flag may or may not be seen by the other thread, but we
+ are safe in any case: The other thread will set queued_by_other under
+ its LOCK_wait_commit, and we will not check queued_by_other only after
+ we have been woken up.
+ */
+ wfc->opaque_pointer= orig_entry;
+ DEBUG_SYNC(orig_entry->thd, "group_commit_waiting_for_prior");
+ orig_entry->thd->ENTER_COND(&wfc->COND_wait_commit,
+ &wfc->LOCK_wait_commit,
+ &stage_waiting_for_prior_transaction_to_commit,
+ &old_stage);
+ while ((loc_waitee= wfc->waitee) && !orig_entry->thd->check_killed())
+ mysql_cond_wait(&wfc->COND_wait_commit, &wfc->LOCK_wait_commit);
+ wfc->opaque_pointer= NULL;
+ DBUG_PRINT("info", ("After waiting for prior commit, queued_by_other=%d",
+ orig_entry->queued_by_other));
+
+ if (loc_waitee)
+ {
+ /* Wait terminated due to kill. */
+ mysql_mutex_lock(&loc_waitee->LOCK_wait_commit);
+ if (loc_waitee->wakeup_subsequent_commits_running ||
+ orig_entry->queued_by_other)
+ {
+ /* Our waitee is already waking us up, so ignore the kill. */
+ mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit);
+ do
+ {
+ mysql_cond_wait(&wfc->COND_wait_commit, &wfc->LOCK_wait_commit);
+ } while (wfc->waitee);
+ }
+ else
+ {
+ /* We were killed, so remove us from the list of waitee. */
+ wfc->remove_from_list(&loc_waitee->subsequent_commits_list);
+ mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit);
+ wfc->waitee= NULL;
+
+ orig_entry->thd->EXIT_COND(&old_stage);
+ /* Interrupted by kill. */
+ DEBUG_SYNC(orig_entry->thd, "group_commit_waiting_for_prior_killed");
+ wfc->wakeup_error= orig_entry->thd->killed_errno();
+ if (!wfc->wakeup_error)
+ wfc->wakeup_error= ER_QUERY_INTERRUPTED;
+ my_message(wfc->wakeup_error, ER(wfc->wakeup_error), MYF(0));
+ DBUG_RETURN(-1);
+ }
+ }
+ orig_entry->thd->EXIT_COND(&old_stage);
+ }
+ else
+ mysql_mutex_unlock(&wfc->LOCK_wait_commit);
+ }
+ /*
+ If the transaction we were waiting for has already put us into the group
+ commit queue (and possibly already done the entire binlog commit for us),
+ then there is nothing else to do.
+ */
+ if (orig_entry->queued_by_other)
+ DBUG_RETURN(0);
+
+ if (wfc && wfc->wakeup_error)
+ {
+ my_error(ER_PRIOR_COMMIT_FAILED, MYF(0));
+ DBUG_RETURN(-1);
+ }
+
+ /* Now enqueue ourselves in the group commit queue. */
+ DEBUG_SYNC(orig_entry->thd, "commit_before_enqueue");
+ orig_entry->thd->clear_wakeup_ready();
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ orig_queue= group_commit_queue;
+
+ /*
+ Iteratively process everything added to the queue, looking for waiters,
+ and their waiters, and so on. If a waiter is ready to commit, we
+ immediately add it to the queue, and mark it as queued_by_other.
+
+ This would be natural to do with recursion, but we want to avoid
+ potentially unbounded recursion blowing the C stack, so we use the list
+ approach instead.
+
+ We keep a list of the group_commit_entry of all the waiters that need to
+ be processed. Initially this list contains only the entry passed into this
+ function.
+
+ We process entries in the list one by one. The element currently being
+ processed is pointed to by `entry`, and the element at the end of the list
+ is pointed to by `last` (we do not use NULL to terminate the list).
+
+ As we process an entry, any waiters for that entry are added at the end of
+ the list, to be processed in subsequent iterations. The the entry is added
+ to the group_commit_queue. This continues until the list is exhausted,
+ with all entries ever added eventually processed.
+
+ The end result is a breath-first traversal of the tree of waiters,
+ re-using the `next' pointers of the group_commit_entry objects in place of
+ extra stack space in a recursive traversal.
+
+ The temporary list linked through these `next' pointers is not used by the
+ caller or any other function; it only exists while doing the iterative
+ tree traversal. After, all the processed entries are linked into the
+ group_commit_queue.
+ */
+
+ cur= wfc;
+ last= orig_entry;
+ entry= orig_entry;
+ for (;;)
+ {
+ group_commit_entry *next_entry;
+
+ if (entry->cache_mngr->using_xa)
+ {
+ DEBUG_SYNC(entry->thd, "commit_before_prepare_ordered");
+ run_prepare_ordered(entry->thd, entry->all);
+ DEBUG_SYNC(entry->thd, "commit_after_prepare_ordered");
+ }
+
+ if (cur)
+ {
+ /*
+ Now that we have taken LOCK_prepare_ordered and will queue up in the
+ group commit queue, it is safe for following transactions to queue
+ themselves. We will grab here any transaction that is now ready to
+ queue up, but after that, more transactions may become ready while the
+ leader is waiting to start the group commit. So set the flag
+ `commit_started', so that later transactions can still participate in
+ the group commit..
+ */
+ cur->commit_started= true;
+
+ /*
+ Check if this transaction has other transaction waiting for it to
+ commit.
+
+ If so, process the waiting transactions, and their waiters and so on,
+ transitively.
+ */
+ if (cur->subsequent_commits_list)
+ {
+ wait_for_commit *waiter, **waiter_ptr;
+
+ mysql_mutex_lock(&cur->LOCK_wait_commit);
+ /*
+ Grab the list, now safely under lock, and process it if still
+ non-empty.
+ */
+ waiter= cur->subsequent_commits_list;
+ waiter_ptr= &cur->subsequent_commits_list;
+ while (waiter)
+ {
+ wait_for_commit *next_waiter= waiter->next_subsequent_commit;
+ group_commit_entry *entry2=
+ (group_commit_entry *)waiter->opaque_pointer;
+ if (entry2)
+ {
+ /*
+ This is another transaction ready to be written to the binary
+ log. We can put it into the queue directly, without needing a
+ separate context switch to the other thread. We just set a flag
+ so that the other thread will know when it wakes up that it was
+ already processed.
+
+ So remove it from the list of our waiters, and instead put it at
+ the end of the list to be processed in a subsequent iteration of
+ the outer loop.
+ */
+ *waiter_ptr= next_waiter;
+ entry2->queued_by_other= true;
+ last->next= entry2;
+ last= entry2;
+ /*
+ As a small optimisation, we do not actually need to set
+ entry2->next to NULL, as we can use the pointer `last' to check
+ for end-of-list.
+ */
+ }
+ else
+ {
+ /*
+ This transaction is not ready to participate in the group commit
+ yet, so leave it in the waiter list. It might join the group
+ commit later, if it completes soon enough to do so (it will see
+ our wfc->commit_started flag set), or it might commit later in a
+ later group commit.
+ */
+ waiter_ptr= &waiter->next_subsequent_commit;
+ }
+ waiter= next_waiter;
+ }
+ mysql_mutex_unlock(&cur->LOCK_wait_commit);
+ }
+ }
+
+ /*
+ Handle the heuristics that if another transaction is waiting for this
+ transaction (or if it does so later), then we want to trigger group
+ commit immediately, without waiting for the binlog_commit_wait_usec
+ timeout to expire.
+ */
+ entry->thd->waiting_on_group_commit= true;
+
+ /* Add the entry to the group commit queue. */
+ next_entry= entry->next;
+ entry->next= group_commit_queue;
+ group_commit_queue= entry;
+ if (entry == last)
+ break;
+ /*
+ Move to the next entry in the flattened list of waiting transactions
+ that still need to be processed transitively.
+ */
+ entry= next_entry;
+ DBUG_ASSERT(entry != NULL);
+ cur= entry->thd->wait_for_commit_ptr;
+ }
+
+ if (opt_binlog_commit_wait_count > 0 && orig_queue != NULL)
+ mysql_cond_signal(&COND_prepare_ordered);
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+ DEBUG_SYNC(orig_entry->thd, "commit_after_release_LOCK_prepare_ordered");
+
+ DBUG_PRINT("info", ("Queued for group commit as %s\n",
+ (orig_queue == NULL) ? "leader" : "participant"));
+ DBUG_RETURN(orig_queue == NULL);
+}
+
+bool
+MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry)
+{
+ int is_leader= queue_for_group_commit(entry);
+
+ /*
+ The first in the queue handles group commit for all; the others just wait
+ to be signalled when group commit is done.
+ */
+ if (is_leader < 0)
+ return true; /* Error */
+ else if (is_leader)
+ trx_group_commit_leader(entry);
+ else if (!entry->queued_by_other)
+ entry->thd->wait_for_wakeup_ready();
+ else
+ {
+ /*
+ If we were queued by another prior commit, then we are woken up
+ only when the leader has already completed the commit for us.
+ So nothing to do here then.
+ */
+ }
+
+ if (!opt_optimize_thread_scheduling)
+ {
+ /* For the leader, trx_group_commit_leader() already took the lock. */
+ if (!is_leader)
+ mysql_mutex_lock(&LOCK_commit_ordered);
+
+ DEBUG_SYNC(entry->thd, "commit_loop_entry_commit_ordered");
+ ++num_commits;
+ if (entry->cache_mngr->using_xa && !entry->error)
+ run_commit_ordered(entry->thd, entry->all);
+
+ group_commit_entry *next= entry->next;
+ if (!next)
+ {
+ group_commit_queue_busy= FALSE;
+ mysql_cond_signal(&COND_queue_busy);
+ DEBUG_SYNC(entry->thd, "commit_after_group_run_commit_ordered");
+ }
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+ entry->thd->wakeup_subsequent_commits(entry->error);
+
+ if (next)
+ {
+ /*
+ Wake up the next thread in the group commit.
+
+ The next thread can be waiting in two different ways, depending on
+ whether it put itself in the queue, or if it was put in queue by us
+ because it had to wait for us to commit first.
+
+ So execute the appropriate wakeup, identified by the queued_by_other
+ field.
+ */
+ if (next->queued_by_other)
+ next->thd->wait_for_commit_ptr->wakeup(entry->error);
+ else
+ next->thd->signal_wakeup_ready();
+ }
+ else
+ {
+ /*
+ If we rotated the binlog, and if we are using the unoptimized thread
+ scheduling where every thread runs its own commit_ordered(), then we
+ must do the commit checkpoint and log purge here, after all
+ commit_ordered() calls have finished, and locks have been released.
+ */
+ if (entry->check_purge)
+ checkpoint_and_purge(entry->binlog_id);
+ }
+
+ }
+
+ if (likely(!entry->error))
+ return entry->thd->wait_for_prior_commit();
+
+ switch (entry->error)
+ {
+ case ER_ERROR_ON_WRITE:
+ my_error(ER_ERROR_ON_WRITE, MYF(ME_NOREFRESH), name, entry->commit_errno);
+ break;
+ case ER_ERROR_ON_READ:
+ my_error(ER_ERROR_ON_READ, MYF(ME_NOREFRESH),
+ entry->error_cache->file_name, entry->commit_errno);
+ break;
+ default:
+ /*
+ There are not (and should not be) any errors thrown not covered above.
+ But just in case one is added later without updating the above switch
+ statement, include a catch-all.
+ */
+ my_printf_error(entry->error,
+ "Error writing transaction to binary log: %d",
+ MYF(ME_NOREFRESH), entry->error);
+ }
+
+ /*
+ Since we return error, this transaction XID will not be committed, so
+ we need to mark it as not needed for recovery (unlog() is not called
+ for a transaction if log_xid() fails).
+ */
+ if (entry->cache_mngr->using_xa && entry->cache_mngr->xa_xid &&
+ entry->cache_mngr->need_unlog)
+ mark_xid_done(entry->cache_mngr->binlog_id, true);
+
+ return 1;
+}
+
+/*
+ Do binlog group commit as the lead thread.
+
+ This must be called when this statement/transaction is queued at the start of
+ the group_commit_queue. It will wait to obtain the LOCK_log mutex, then group
+ commit all the transactions in the queue (more may have entered while waiting
+ for LOCK_log). After commit is done, all other threads in the queue will be
+ signalled.
+
+ */
+void
+MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader)
+{
+ uint xid_count= 0;
+ my_off_t UNINIT_VAR(commit_offset);
+ group_commit_entry *current, *last_in_queue;
+ group_commit_entry *queue= NULL;
+ bool check_purge= false;
+ ulong binlog_id;
+ uint64 commit_id;
+ DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader");
+ LINT_INIT(binlog_id);
+
+ {
+ DBUG_EXECUTE_IF("inject_binlog_commit_before_get_LOCK_log",
+ DBUG_ASSERT(!debug_sync_set_action(leader->thd, STRING_WITH_LEN
+ ("commit_before_get_LOCK_log SIGNAL waiting WAIT_FOR cont TIMEOUT 1")));
+ );
+ /*
+ Lock the LOCK_log(), and once we get it, collect any additional writes
+ that queued up while we were waiting.
+ */
+ DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_log");
+ mysql_mutex_lock(&LOCK_log);
+ DEBUG_SYNC(leader->thd, "commit_after_get_LOCK_log");
+
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ if (opt_binlog_commit_wait_count)
+ wait_for_sufficient_commits();
+ /*
+ Note that wait_for_sufficient_commits() may have released and
+ re-acquired the LOCK_log and LOCK_prepare_ordered if it needed to wait.
+ */
+ current= group_commit_queue;
+ group_commit_queue= NULL;
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+ binlog_id= current_binlog_id;
+
+ /* As the queue is in reverse order of entering, reverse it. */
+ last_in_queue= current;
+ while (current)
+ {
+ group_commit_entry *next= current->next;
+ /*
+ Now that group commit is started, we can clear the flag; there is no
+ longer any use in waiters on this commit trying to trigger it early.
+ */
+ current->thd->waiting_on_group_commit= false;
+ current->next= queue;
+ queue= current;
+ current= next;
+ }
+ DBUG_ASSERT(leader == queue /* the leader should be first in queue */);
+
+ /* Now we have in queue the list of transactions to be committed in order. */
+ }
+
+ DBUG_ASSERT(is_open());
+ if (likely(is_open())) // Should always be true
+ {
+ commit_id= (last_in_queue == leader ? 0 : (uint64)leader->thd->query_id);
+ DBUG_EXECUTE_IF("binlog_force_commit_id",
+ {
+ const LEX_STRING name= { C_STRING_WITH_LEN("commit_id") };
+ bool null_value;
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&leader->thd->user_vars,
+ (uchar*) name.str, name.length);
+ commit_id= entry->val_int(&null_value);
+ });
+ /*
+ Commit every transaction in the queue.
+
+ Note that we are doing this in a different thread than the one running
+ the transaction! So we are limited in the operations we can do. In
+ particular, we cannot call my_error() on behalf of a transaction, as
+ that obtains the THD from thread local storage. Instead, we must set
+ current->error and let the thread do the error reporting itself once
+ we wake it up.
+ */
+ for (current= queue; current != NULL; current= current->next)
+ {
+ binlog_cache_mngr *cache_mngr= current->cache_mngr;
+
+ /*
+ We already checked before that at least one cache is non-empty; if both
+ are empty we would have skipped calling into here.
+ */
+ DBUG_ASSERT(!cache_mngr->stmt_cache.empty() || !cache_mngr->trx_cache.empty());
+
+ if ((current->error= write_transaction_or_stmt(current, commit_id)))
+ current->commit_errno= errno;
+
+ strmake_buf(cache_mngr->last_commit_pos_file, log_file_name);
+ commit_offset= my_b_write_tell(&log_file);
+ cache_mngr->last_commit_pos_offset= commit_offset;
+ if (cache_mngr->using_xa && cache_mngr->xa_xid)
+ {
+ /*
+ If all storage engines support commit_checkpoint_request(), then we
+ do not need to keep track of when this XID is durably committed.
+ Instead we will just ask the storage engine to durably commit all its
+ XIDs when we rotate a binlog file.
+ */
+ if (current->need_unlog)
+ {
+ xid_count++;
+ cache_mngr->need_unlog= true;
+ cache_mngr->binlog_id= binlog_id;
+ }
+ else
+ cache_mngr->need_unlog= false;
+
+ cache_mngr->delayed_error= false;
+ }
+ }
+
+ bool synced= 0;
+ if (flush_and_sync(&synced))
+ {
+ for (current= queue; current != NULL; current= current->next)
+ {
+ if (!current->error)
+ {
+ current->error= ER_ERROR_ON_WRITE;
+ current->commit_errno= errno;
+ current->error_cache= NULL;
+ }
+ }
+ }
+ else
+ {
+ bool any_error= false;
+ bool all_error= true;
+ for (current= queue; current != NULL; current= current->next)
+ {
+ if (!current->error &&
+ RUN_HOOK(binlog_storage, after_flush,
+ (current->thd, log_file_name,
+ current->cache_mngr->last_commit_pos_offset, synced)))
+ {
+ current->error= ER_ERROR_ON_WRITE;
+ current->commit_errno= -1;
+ current->error_cache= NULL;
+ any_error= true;
+ }
+ else
+ all_error= false;
+ }
+
+ if (any_error)
+ sql_print_error("Failed to run 'after_flush' hooks");
+ if (!all_error)
+ signal_update();
+ }
+
+ /*
+ If any commit_events are Xid_log_event, increase the number of pending
+ XIDs in current binlog (it's decreased in ::unlog()). When the count in
+ a (not active) binlog file reaches zero, we know that it is no longer
+ needed in XA recovery, and we can log a new binlog checkpoint event.
+ */
+ if (xid_count > 0)
+ {
+ mark_xids_active(binlog_id, xid_count);
+ }
+
+ if (rotate(false, &check_purge))
+ {
+ /*
+ If we fail to rotate, which thread should get the error?
+ We give the error to the leader, as any my_error() thrown inside
+ rotate() will have been registered for the leader THD.
+
+ However we must not return error from here - that would cause
+ ha_commit_trans() to abort and rollback the transaction, which would
+ leave an inconsistent state with the transaction committed in the
+ binlog but rolled back in the engine.
+
+ Instead set a flag so that we can return error later, from unlog(),
+ when the transaction has been safely committed in the engine.
+ */
+ leader->cache_mngr->delayed_error= true;
+ my_error(ER_ERROR_ON_WRITE, MYF(ME_NOREFRESH), name, errno);
+ check_purge= false;
+ }
+ /* In case of binlog rotate, update the correct current binlog offset. */
+ commit_offset= my_b_write_tell(&log_file);
+ }
+
+ DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_commit_ordered");
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ last_commit_pos_offset= commit_offset;
+ /*
+ We cannot unlock LOCK_log until we have locked LOCK_commit_ordered;
+ otherwise scheduling could allow the next group commit to run ahead of us,
+ messing up the order of commit_ordered() calls. But as soon as
+ LOCK_commit_ordered is obtained, we can let the next group commit start.
+ */
+ mysql_mutex_unlock(&LOCK_log);
+
+ DEBUG_SYNC(leader->thd, "commit_after_release_LOCK_log");
+ ++num_group_commits;
+
+ if (!opt_optimize_thread_scheduling)
+ {
+ /*
+ If we want to run commit_ordered() each in the transaction's own thread
+ context, then we need to mark the queue reserved; we need to finish all
+ threads in one group commit before the next group commit can be allowed
+ to proceed, and we cannot unlock a simple pthreads mutex in a different
+ thread from the one that locked it.
+ */
+
+ while (group_commit_queue_busy)
+ mysql_cond_wait(&COND_queue_busy, &LOCK_commit_ordered);
+ group_commit_queue_busy= TRUE;
+
+ /*
+ Set these so parent can run checkpoint_and_purge() in last thread.
+ (When using optimized thread scheduling, we run checkpoint_and_purge()
+ in this function, so parent does not need to and we need not set these
+ values).
+ */
+ last_in_queue->check_purge= check_purge;
+ last_in_queue->binlog_id= binlog_id;
+
+ /* Note that we return with LOCK_commit_ordered locked! */
+ DBUG_VOID_RETURN;
+ }
+
+ /*
+ Wakeup each participant waiting for our group commit, first calling the
+ commit_ordered() methods for any transactions doing 2-phase commit.
+ */
+ current= queue;
+ while (current != NULL)
+ {
+ group_commit_entry *next;
+
+ DEBUG_SYNC(leader->thd, "commit_loop_entry_commit_ordered");
+ ++num_commits;
+ if (current->cache_mngr->using_xa && !current->error &&
+ DBUG_EVALUATE_IF("skip_commit_ordered", 0, 1))
+ run_commit_ordered(current->thd, current->all);
+ current->thd->wakeup_subsequent_commits(current->error);
+
+ /*
+ Careful not to access current->next after waking up the other thread! As
+ it may change immediately after wakeup.
+ */
+ next= current->next;
+ if (current != leader) // Don't wake up ourself
+ {
+ if (current->queued_by_other)
+ current->thd->wait_for_commit_ptr->wakeup(current->error);
+ else
+ current->thd->signal_wakeup_ready();
+ }
+ current= next;
+ }
+ DEBUG_SYNC(leader->thd, "commit_after_group_run_commit_ordered");
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+ DEBUG_SYNC(leader->thd, "commit_after_group_release_commit_ordered");
+
+ if (check_purge)
+ checkpoint_and_purge(binlog_id);
+
+ DBUG_VOID_RETURN;
+}
+
+
+int
+MYSQL_BIN_LOG::write_transaction_or_stmt(group_commit_entry *entry,
+ uint64 commit_id)
+{
+ binlog_cache_mngr *mngr= entry->cache_mngr;
+ DBUG_ENTER("MYSQL_BIN_LOG::write_transaction_or_stmt");
+
+ if (write_gtid_event(entry->thd, false, entry->using_trx_cache, commit_id))
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+
+ if (entry->using_stmt_cache && !mngr->stmt_cache.empty() &&
+ write_cache(entry->thd, mngr->get_binlog_cache_log(FALSE)))
+ {
+ entry->error_cache= &mngr->stmt_cache.cache_log;
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+ }
+
+ if (entry->using_trx_cache && !mngr->trx_cache.empty())
+ {
+ DBUG_EXECUTE_IF("crash_before_writing_xid",
+ {
+ if ((write_cache(entry->thd,
+ mngr->get_binlog_cache_log(TRUE))))
+ DBUG_PRINT("info", ("error writing binlog cache"));
+ else
+ flush_and_sync(0);
+
+ DBUG_PRINT("info", ("crashing before writing xid"));
+ DBUG_SUICIDE();
+ });
+
+ if (write_cache(entry->thd, mngr->get_binlog_cache_log(TRUE)))
+ {
+ entry->error_cache= &mngr->trx_cache.cache_log;
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+ }
+ }
+
+ DBUG_EXECUTE_IF("inject_error_writing_xid",
+ {
+ entry->error_cache= NULL;
+ errno= 28;
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+ });
+
+ if (entry->end_event->write(&log_file))
+ {
+ entry->error_cache= NULL;
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+ }
+ status_var_add(entry->thd->status_var.binlog_bytes_written,
+ entry->end_event->data_written);
+
+ if (entry->incident_event)
+ {
+ if (entry->incident_event->write(&log_file))
+ {
+ entry->error_cache= NULL;
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+ }
+ }
+
+ if (mngr->get_binlog_cache_log(FALSE)->error) // Error on read
+ {
+ entry->error_cache= &mngr->stmt_cache.cache_log;
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+ }
+ if (mngr->get_binlog_cache_log(TRUE)->error) // Error on read
+ {
+ entry->error_cache= &mngr->trx_cache.cache_log;
+ DBUG_RETURN(ER_ERROR_ON_WRITE);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Wait for sufficient commits to queue up for group commit, according to the
+ values of binlog_commit_wait_count and binlog_commit_wait_usec.
+
+ Note that this function may release and re-acquire LOCK_log and
+ LOCK_prepare_ordered if it needs to wait.
+*/
+
+void
+MYSQL_BIN_LOG::wait_for_sufficient_commits()
+{
+ size_t count;
+ group_commit_entry *e;
+ group_commit_entry *last_head;
+ struct timespec wait_until;
+
+ mysql_mutex_assert_owner(&LOCK_log);
+ mysql_mutex_assert_owner(&LOCK_prepare_ordered);
+
+ for (e= last_head= group_commit_queue, count= 0; e; e= e->next)
+ {
+ if (++count >= opt_binlog_commit_wait_count)
+ {
+ group_commit_trigger_count++;
+ return;
+ }
+ if (unlikely(e->thd->has_waiter))
+ {
+ group_commit_trigger_lock_wait++;
+ return;
+ }
+ }
+
+ mysql_mutex_unlock(&LOCK_log);
+ set_timespec_nsec(wait_until, (ulonglong)1000*opt_binlog_commit_wait_usec);
+
+ for (;;)
+ {
+ int err;
+ group_commit_entry *head;
+
+ err= mysql_cond_timedwait(&COND_prepare_ordered, &LOCK_prepare_ordered,
+ &wait_until);
+ if (err == ETIMEDOUT)
+ {
+ group_commit_trigger_timeout++;
+ break;
+ }
+ if (unlikely(last_head->thd->has_waiter))
+ {
+ group_commit_trigger_lock_wait++;
+ break;
+ }
+ head= group_commit_queue;
+ for (e= head; e && e != last_head; e= e->next)
+ {
+ ++count;
+ if (unlikely(e->thd->has_waiter))
+ {
+ group_commit_trigger_lock_wait++;
+ goto after_loop;
+ }
+ }
+ if (count >= opt_binlog_commit_wait_count)
+ {
+ group_commit_trigger_count++;
+ break;
+ }
+ last_head= head;
+ }
+after_loop:
+
+ /*
+ We must not wait for LOCK_log while holding LOCK_prepare_ordered.
+ LOCK_log can be held for long periods (eg. we do I/O under it), while
+ LOCK_prepare_ordered must only be held for short periods.
+
+ In addition, waiting for LOCK_log while holding LOCK_prepare_ordered would
+ violate locking order of LOCK_log-before-LOCK_prepare_ordered. This could
+ cause SAFEMUTEX warnings (even if it cannot actually deadlock with current
+ code, as there can be at most one group commit leader thread at a time).
+
+ So release and re-acquire LOCK_prepare_ordered if we need to wait for the
+ LOCK_log.
+ */
+ if (mysql_mutex_trylock(&LOCK_log))
+ {
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+ mysql_mutex_lock(&LOCK_log);
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ }
+}
+
+
+void
+MYSQL_BIN_LOG::binlog_trigger_immediate_group_commit()
+{
+ group_commit_entry *head;
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ head= group_commit_queue;
+ if (head)
+ {
+ head->thd->has_waiter= true;
+ mysql_cond_signal(&COND_prepare_ordered);
+ }
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+}
+
+
+/*
+ This function is called when a transaction T1 goes to wait for another
+ transaction T2. It is used to cut short any binlog group commit delay from
+ --binlog-commit-wait-count in the case where another transaction is stalled
+ on the wait due to conflicting row locks.
+
+ If T2 is already ready to group commit, any waiting group commit will be
+ signalled to proceed immediately. Otherwise, a flag will be set in T2, and
+ when T2 later becomes ready, immediate group commit will be triggered.
+*/
+void
+binlog_report_wait_for(THD *thd1, THD *thd2)
+{
+ if (opt_binlog_commit_wait_count == 0)
+ return;
+ thd2->has_waiter= true;
+ if (thd2->waiting_on_group_commit)
+ mysql_bin_log.binlog_trigger_immediate_group_commit();
+}
+
+
+/**
+ Wait until we get a signal that the relay log has been updated.
+
+ @param thd Thread variable
+
+ @note
+ One must have a lock on LOCK_log before calling this function.
+ This lock will be released before return! That's required by
+ THD::enter_cond() (see NOTES in sql_class.h).
+*/
+
+void MYSQL_BIN_LOG::wait_for_update_relay_log(THD* thd)
+{
+ PSI_stage_info old_stage;
+ DBUG_ENTER("wait_for_update_relay_log");
+
+ thd->ENTER_COND(&update_cond, &LOCK_log,
+ &stage_slave_has_read_all_relay_log,
+ &old_stage);
+ mysql_cond_wait(&update_cond, &LOCK_log);
+ thd->EXIT_COND(&old_stage);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Wait until we get a signal that the binary log has been updated.
+ Applies to master only.
+
+ NOTES
+ @param[in] thd a THD struct
+ @param[in] timeout a pointer to a timespec;
+ NULL means to wait w/o timeout.
+ @retval 0 if got signalled on update
+ @retval non-0 if wait timeout elapsed
+ @note
+ LOCK_log must be taken before calling this function.
+ LOCK_log is being released while the thread is waiting.
+ LOCK_log is released by the caller.
+*/
+
+int MYSQL_BIN_LOG::wait_for_update_bin_log(THD* thd,
+ const struct timespec *timeout)
+{
+ int ret= 0;
+ DBUG_ENTER("wait_for_update_bin_log");
+
+ if (!timeout)
+ mysql_cond_wait(&update_cond, &LOCK_log);
+ else
+ ret= mysql_cond_timedwait(&update_cond, &LOCK_log,
+ const_cast<struct timespec *>(timeout));
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Close the log file.
+
+ @param exiting Bitmask for one or more of the following bits:
+ - LOG_CLOSE_INDEX : if we should close the index file
+ - LOG_CLOSE_TO_BE_OPENED : if we intend to call open
+ at once after close.
+ - LOG_CLOSE_STOP_EVENT : write a 'stop' event to the log
+ - LOG_CLOSE_DELAYED_CLOSE : do not yet close the file and clear the
+ LOG_EVENT_BINLOG_IN_USE_F flag
+
+ @note
+ One can do an open on the object at once after doing a close.
+ The internal structures are not freed until cleanup() is called
+*/
+
+void MYSQL_BIN_LOG::close(uint exiting)
+{ // One can't set log_type here!
+ bool failed_to_save_state= false;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::close");
+ DBUG_PRINT("enter",("exiting: %d", (int) exiting));
+ if (log_state == LOG_OPENED)
+ {
+#ifdef HAVE_REPLICATION
+ if (log_type == LOG_BIN &&
+ (exiting & LOG_CLOSE_STOP_EVENT))
+ {
+ Stop_log_event s;
+ // the checksumming rule for relay-log case is similar to Rotate
+ s.checksum_alg= is_relay_log ?
+ (uint8) relay_log_checksum_alg : (uint8) binlog_checksum_options;
+ DBUG_ASSERT(!is_relay_log ||
+ relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
+ s.write(&log_file);
+ bytes_written+= s.data_written;
+ signal_update();
+
+ /*
+ When we shut down server, write out the binlog state to a separate
+ file so we do not have to scan an entire binlog file to recover it
+ at next server start.
+
+ Note that this must be written and synced to disk before marking the
+ last binlog file as "not crashed".
+ */
+ if (!is_relay_log && write_state_to_file())
+ {
+ sql_print_error("Failed to save binlog GTID state during shutdown. "
+ "Binlog will be marked as crashed, so that crash "
+ "recovery can recover the state at next server "
+ "startup.");
+ /*
+ Leave binlog file marked as crashed, so we can recover state by
+ scanning it now that we failed to write out the state properly.
+ */
+ failed_to_save_state= true;
+ }
+ }
+#endif /* HAVE_REPLICATION */
+
+ /* don't pwrite in a file opened with O_APPEND - it doesn't work */
+ if (log_file.type == WRITE_CACHE && log_type == LOG_BIN
+ && !(exiting & LOG_CLOSE_DELAYED_CLOSE))
+ {
+ my_off_t org_position= mysql_file_tell(log_file.file, MYF(0));
+ if (!failed_to_save_state)
+ clear_inuse_flag_when_closing(log_file.file);
+ /*
+ Restore position so that anything we have in the IO_cache is written
+ to the correct position.
+ We need the seek here, as mysql_file_pwrite() is not guaranteed to keep the
+ original position on system that doesn't support pwrite().
+ */
+ mysql_file_seek(log_file.file, org_position, MY_SEEK_SET, MYF(0));
+ }
+
+ /* this will cleanup IO_CACHE, sync and close the file */
+ MYSQL_LOG::close(exiting);
+ }
+
+ /*
+ The following test is needed even if is_open() is not set, as we may have
+ called a not complete close earlier and the index file is still open.
+ */
+
+ if ((exiting & LOG_CLOSE_INDEX) && my_b_inited(&index_file))
+ {
+ end_io_cache(&index_file);
+ if (mysql_file_close(index_file.file, MYF(0)) < 0 && ! write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), index_file_name, errno);
+ }
+ }
+ log_state= (exiting & LOG_CLOSE_TO_BE_OPENED) ? LOG_TO_BE_OPENED : LOG_CLOSED;
+ my_free(name);
+ name= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Clear the LOG_EVENT_BINLOG_IN_USE_F; this marks the binlog file as cleanly
+ closed and not needing crash recovery.
+*/
+void MYSQL_BIN_LOG::clear_inuse_flag_when_closing(File file)
+{
+ my_off_t offset= BIN_LOG_HEADER_SIZE + FLAGS_OFFSET;
+ uchar flags= 0; // clearing LOG_EVENT_BINLOG_IN_USE_F
+ mysql_file_pwrite(file, &flags, 1, offset, MYF(0));
+}
+
+
+void MYSQL_BIN_LOG::set_max_size(ulong max_size_arg)
+{
+ /*
+ We need to take locks, otherwise this may happen:
+ new_file() is called, calls open(old_max_size), then before open() starts,
+ set_max_size() sets max_size to max_size_arg, then open() starts and
+ uses the old_max_size argument, so max_size_arg has been overwritten and
+ it's like if the SET command was never run.
+ */
+ DBUG_ENTER("MYSQL_BIN_LOG::set_max_size");
+ mysql_mutex_lock(&LOCK_log);
+ if (is_open())
+ max_size= max_size_arg;
+ mysql_mutex_unlock(&LOCK_log);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Check if a string is a valid number.
+
+ @param str String to test
+ @param res Store value here
+ @param allow_wildcards Set to 1 if we should ignore '%' and '_'
+
+ @note
+ For the moment the allow_wildcards argument is not used
+ Should be move to some other file.
+
+ @retval
+ 1 String is a number
+ @retval
+ 0 String is not a number
+*/
+
+static bool test_if_number(register const char *str,
+ ulong *res, bool allow_wildcards)
+{
+ reg2 int flag;
+ const char *start;
+ DBUG_ENTER("test_if_number");
+
+ flag=0; start=str;
+ while (*str++ == ' ') ;
+ if (*--str == '-' || *str == '+')
+ str++;
+ while (my_isdigit(files_charset_info,*str) ||
+ (allow_wildcards && (*str == wild_many || *str == wild_one)))
+ {
+ flag=1;
+ str++;
+ }
+ if (*str == '.')
+ {
+ for (str++ ;
+ my_isdigit(files_charset_info,*str) ||
+ (allow_wildcards && (*str == wild_many || *str == wild_one)) ;
+ str++, flag=1) ;
+ }
+ if (*str != 0 || flag == 0)
+ DBUG_RETURN(0);
+ if (res)
+ *res=atol(start);
+ DBUG_RETURN(1); /* Number ok */
+} /* test_if_number */
+
+
+void sql_perror(const char *message)
+{
+#if defined(_WIN32)
+ char* buf;
+ DWORD dw= GetLastError();
+ if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buf, 0, NULL ) > 0)
+ {
+ sql_print_error("%s: %s",message, buf);
+ LocalFree((HLOCAL)buf);
+ }
+ else
+ {
+ sql_print_error("%s", message);
+ }
+#elif defined(HAVE_STRERROR)
+ sql_print_error("%s: %s",message, strerror(errno));
+#else
+ perror(message);
+#endif
+}
+
+
+/*
+ Change the file associated with two output streams. Used to
+ redirect stdout and stderr to a file. The streams are reopened
+ only for appending (writing at end of file).
+*/
+extern "C" my_bool reopen_fstreams(const char *filename,
+ FILE *outstream, FILE *errstream)
+{
+ if (outstream && !my_freopen(filename, "a", outstream))
+ return TRUE;
+
+ if (errstream && !my_freopen(filename, "a", errstream))
+ return TRUE;
+
+ /* The error stream must be unbuffered. */
+ if (errstream)
+ setbuf(errstream, NULL);
+
+ return FALSE;
+}
+
+
+/*
+ Unfortunately, there seems to be no good way
+ to restore the original streams upon failure.
+*/
+static bool redirect_std_streams(const char *file)
+{
+ if (reopen_fstreams(file, stdout, stderr))
+ return TRUE;
+
+ setbuf(stderr, NULL);
+ return FALSE;
+}
+
+
+bool flush_error_log()
+{
+ bool result= 0;
+ if (opt_error_log)
+ {
+ mysql_mutex_lock(&LOCK_error_log);
+ if (redirect_std_streams(log_error_file))
+ result= 1;
+ mysql_mutex_unlock(&LOCK_error_log);
+ }
+ return result;
+}
+
+void MYSQL_BIN_LOG::signal_update()
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::signal_update");
+ signal_cnt++;
+ mysql_cond_broadcast(&update_cond);
+ DBUG_VOID_RETURN;
+}
+
+#ifdef _WIN32
+static void print_buffer_to_nt_eventlog(enum loglevel level, char *buff,
+ size_t length, size_t buffLen)
+{
+ HANDLE event;
+ char *buffptr= buff;
+ DBUG_ENTER("print_buffer_to_nt_eventlog");
+
+ /* Add ending CR/LF's to string, overwrite last chars if necessary */
+ strmov(buffptr+MY_MIN(length, buffLen-5), "\r\n\r\n");
+
+ setup_windows_event_source();
+ if ((event= RegisterEventSource(NULL,"MySQL")))
+ {
+ switch (level) {
+ case ERROR_LEVEL:
+ ReportEvent(event, EVENTLOG_ERROR_TYPE, 0, MSG_DEFAULT, NULL, 1, 0,
+ (LPCSTR*)&buffptr, NULL);
+ break;
+ case WARNING_LEVEL:
+ ReportEvent(event, EVENTLOG_WARNING_TYPE, 0, MSG_DEFAULT, NULL, 1, 0,
+ (LPCSTR*) &buffptr, NULL);
+ break;
+ case INFORMATION_LEVEL:
+ ReportEvent(event, EVENTLOG_INFORMATION_TYPE, 0, MSG_DEFAULT, NULL, 1,
+ 0, (LPCSTR*) &buffptr, NULL);
+ break;
+ }
+ DeregisterEventSource(event);
+ }
+
+ DBUG_VOID_RETURN;
+}
+#endif /* _WIN32 */
+
+
+#ifndef EMBEDDED_LIBRARY
+static void print_buffer_to_file(enum loglevel level, const char *buffer,
+ size_t length)
+{
+ time_t skr;
+ struct tm tm_tmp;
+ struct tm *start;
+ THD *thd;
+ int tag_length= 0;
+ char tag[NAME_LEN];
+ DBUG_ENTER("print_buffer_to_file");
+ DBUG_PRINT("enter",("buffer: %s", buffer));
+
+ if (mysqld_server_initialized && (thd= current_thd))
+ {
+ if (thd->connection_name.length)
+ {
+ /*
+ Add tag for slaves so that the user can see from which connection
+ the error originates.
+ */
+ tag_length= my_snprintf(tag, sizeof(tag), ER(ER_MASTER_LOG_PREFIX),
+ (int) thd->connection_name.length,
+ thd->connection_name.str);
+ }
+ }
+
+ mysql_mutex_lock(&LOCK_error_log);
+
+ skr= my_time(0);
+ localtime_r(&skr, &tm_tmp);
+ start=&tm_tmp;
+
+ fprintf(stderr, "%02d%02d%02d %2d:%02d:%02d [%s] %.*s%.*s\n",
+ start->tm_year % 100,
+ start->tm_mon+1,
+ start->tm_mday,
+ start->tm_hour,
+ start->tm_min,
+ start->tm_sec,
+ (level == ERROR_LEVEL ? "ERROR" : level == WARNING_LEVEL ?
+ "Warning" : "Note"),
+ tag_length, tag,
+ (int) length, buffer);
+
+ fflush(stderr);
+
+ mysql_mutex_unlock(&LOCK_error_log);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Prints a printf style message to the error log and, under NT, to the
+ Windows event log.
+
+ This function prints the message into a buffer and then sends that buffer
+ to other functions to write that message to other logging sources.
+
+ @param level The level of the msg significance
+ @param format Printf style format of message
+ @param args va_list list of arguments for the message
+
+ @returns
+ The function always returns 0. The return value is present in the
+ signature to be compatible with other logging routines, which could
+ return an error (e.g. logging to the log tables)
+*/
+int vprint_msg_to_log(enum loglevel level, const char *format, va_list args)
+{
+ char buff[1024];
+ size_t length;
+ DBUG_ENTER("vprint_msg_to_log");
+
+ length= my_vsnprintf(buff, sizeof(buff), format, args);
+ print_buffer_to_file(level, buff, length);
+
+#ifdef _WIN32
+ print_buffer_to_nt_eventlog(level, buff, length, sizeof(buff));
+#endif
+
+ DBUG_RETURN(0);
+}
+#endif /* EMBEDDED_LIBRARY */
+
+
+void sql_print_error(const char *format, ...)
+{
+ va_list args;
+ DBUG_ENTER("sql_print_error");
+
+ va_start(args, format);
+ error_log_print(ERROR_LEVEL, format, args);
+ va_end(args);
+
+ DBUG_VOID_RETURN;
+}
+
+
+void sql_print_warning(const char *format, ...)
+{
+ va_list args;
+ DBUG_ENTER("sql_print_warning");
+
+ va_start(args, format);
+ error_log_print(WARNING_LEVEL, format, args);
+ va_end(args);
+
+ DBUG_VOID_RETURN;
+}
+
+
+void sql_print_information(const char *format, ...)
+{
+ va_list args;
+ DBUG_ENTER("sql_print_information");
+
+ va_start(args, format);
+ error_log_print(INFORMATION_LEVEL, format, args);
+ va_end(args);
+
+ DBUG_VOID_RETURN;
+}
+
+
+void
+TC_LOG::run_prepare_ordered(THD *thd, bool all)
+{
+ Ha_trx_info *ha_info=
+ all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list;
+
+ mysql_mutex_assert_owner(&LOCK_prepare_ordered);
+ for (; ha_info; ha_info= ha_info->next())
+ {
+ handlerton *ht= ha_info->ht();
+ if (!ht->prepare_ordered)
+ continue;
+ ht->prepare_ordered(ht, thd, all);
+ }
+}
+
+
+void
+TC_LOG::run_commit_ordered(THD *thd, bool all)
+{
+ Ha_trx_info *ha_info=
+ all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list;
+
+ mysql_mutex_assert_owner(&LOCK_commit_ordered);
+ for (; ha_info; ha_info= ha_info->next())
+ {
+ handlerton *ht= ha_info->ht();
+ if (!ht->commit_ordered)
+ continue;
+ ht->commit_ordered(ht, thd, all);
+ DEBUG_SYNC(thd, "commit_after_run_commit_ordered");
+ }
+}
+
+
+int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all,
+ bool need_prepare_ordered,
+ bool need_commit_ordered)
+{
+ int cookie;
+ struct commit_entry entry;
+ bool is_group_commit_leader;
+ LINT_INIT(is_group_commit_leader);
+
+ if (need_prepare_ordered)
+ {
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ run_prepare_ordered(thd, all);
+ if (need_commit_ordered)
+ {
+ /*
+ Must put us in queue so we can run_commit_ordered() in same sequence
+ as we did run_prepare_ordered().
+ */
+ thd->clear_wakeup_ready();
+ entry.thd= thd;
+ commit_entry *previous_queue= commit_ordered_queue;
+ entry.next= previous_queue;
+ commit_ordered_queue= &entry;
+ is_group_commit_leader= (previous_queue == NULL);
+ }
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+ }
+
+ if (thd->wait_for_prior_commit())
+ return 0;
+
+ cookie= 0;
+ if (xid)
+ cookie= log_one_transaction(xid);
+
+ if (need_commit_ordered)
+ {
+ if (need_prepare_ordered)
+ {
+ /*
+ We did the run_prepare_ordered() serialised, then ran the log_xid() in
+ parallel. Now we have to do run_commit_ordered() serialised in the
+ same sequence as run_prepare_ordered().
+
+ We do this starting from the head of the queue, each thread doing
+ run_commit_ordered() and signalling the next in queue.
+ */
+ if (is_group_commit_leader)
+ {
+ /* The first in queue starts the ball rolling. */
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ while (commit_ordered_queue_busy)
+ mysql_cond_wait(&COND_queue_busy, &LOCK_prepare_ordered);
+ commit_entry *queue= commit_ordered_queue;
+ commit_ordered_queue= NULL;
+ /*
+ Mark the queue busy while we bounce it from one thread to the
+ next.
+ */
+ commit_ordered_queue_busy= true;
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+
+ /* Reverse the queue list so we get correct order. */
+ commit_entry *prev= NULL;
+ while (queue)
+ {
+ commit_entry *next= queue->next;
+ queue->next= prev;
+ prev= queue;
+ queue= next;
+ }
+ DBUG_ASSERT(prev == &entry && prev->thd == thd);
+ }
+ else
+ {
+ /* Not first in queue; just wait until previous thread wakes us up. */
+ thd->wait_for_wakeup_ready();
+ }
+ }
+
+ /* Only run commit_ordered() if log_xid was successful. */
+ if (cookie)
+ {
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ run_commit_ordered(thd, all);
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+ }
+
+ if (need_prepare_ordered)
+ {
+ commit_entry *next= entry.next;
+ if (next)
+ {
+ next->thd->signal_wakeup_ready();
+ }
+ else
+ {
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ commit_ordered_queue_busy= false;
+ mysql_cond_signal(&COND_queue_busy);
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+ }
+ }
+ }
+
+ return cookie;
+}
+
+
+/********* transaction coordinator log for 2pc - mmap() based solution *******/
+
+/*
+ the log consists of a file, mapped to memory.
+ file is divided into pages of tc_log_page_size size.
+ (usable size of the first page is smaller because of the log header)
+ there is a PAGE control structure for each page
+ each page (or rather its PAGE control structure) can be in one of
+ the three states - active, syncing, pool.
+ there could be only one page in the active or syncing state,
+ but many in pool - pool is a fifo queue.
+ the usual lifecycle of a page is pool->active->syncing->pool.
+ the "active" page is a page where new xid's are logged.
+ the page stays active as long as the syncing slot is taken.
+ the "syncing" page is being synced to disk. no new xid can be added to it.
+ when the syncing is done the page is moved to a pool and an active page
+ becomes "syncing".
+
+ the result of such an architecture is a natural "commit grouping" -
+ If commits are coming faster than the system can sync, they do not
+ stall. Instead, all commits that came since the last sync are
+ logged to the same "active" page, and they all are synced with the next -
+ one - sync. Thus, thought individual commits are delayed, throughput
+ is not decreasing.
+
+ when an xid is added to an active page, the thread of this xid waits
+ for a page's condition until the page is synced. when syncing slot
+ becomes vacant one of these waiters is awaken to take care of syncing.
+ it syncs the page and signals all waiters that the page is synced.
+ PAGE::waiters is used to count these waiters, and a page may never
+ become active again until waiters==0 (that is all waiters from the
+ previous sync have noticed that the sync was completed)
+
+ note, that the page becomes "dirty" and has to be synced only when a
+ new xid is added into it. Removing a xid from a page does not make it
+ dirty - we don't sync xid removals to disk.
+*/
+
+ulong tc_log_page_waits= 0;
+
+#ifdef HAVE_MMAP
+
+#define TC_LOG_HEADER_SIZE (sizeof(tc_log_magic)+1)
+
+static const uchar tc_log_magic[]={(uchar) 254, 0x23, 0x05, 0x74};
+
+ulong opt_tc_log_size= TC_LOG_MIN_SIZE;
+ulong tc_log_max_pages_used=0, tc_log_page_size=0, tc_log_cur_pages_used=0;
+
+int TC_LOG_MMAP::open(const char *opt_name)
+{
+ uint i;
+ bool crashed=FALSE;
+ PAGE *pg;
+
+ DBUG_ASSERT(total_ha_2pc > 1);
+ DBUG_ASSERT(opt_name && opt_name[0]);
+
+ tc_log_page_size= my_getpagesize();
+ DBUG_ASSERT(TC_LOG_PAGE_SIZE % tc_log_page_size == 0);
+
+ fn_format(logname,opt_name,mysql_data_home,"",MY_UNPACK_FILENAME);
+ if ((fd= mysql_file_open(key_file_tclog, logname, O_RDWR, MYF(0))) < 0)
+ {
+ if (my_errno != ENOENT)
+ goto err;
+ if (using_heuristic_recover())
+ return 1;
+ if ((fd= mysql_file_create(key_file_tclog, logname, CREATE_MODE,
+ O_RDWR, MYF(MY_WME))) < 0)
+ goto err;
+ inited=1;
+ file_length= opt_tc_log_size;
+ if (mysql_file_chsize(fd, file_length, 0, MYF(MY_WME)))
+ goto err;
+ }
+ else
+ {
+ inited= 1;
+ crashed= TRUE;
+ sql_print_information("Recovering after a crash using %s", opt_name);
+ if (tc_heuristic_recover)
+ {
+ sql_print_error("Cannot perform automatic crash recovery when "
+ "--tc-heuristic-recover is used");
+ goto err;
+ }
+ file_length= mysql_file_seek(fd, 0L, MY_SEEK_END, MYF(MY_WME+MY_FAE));
+ if (file_length == MY_FILEPOS_ERROR || file_length % tc_log_page_size)
+ goto err;
+ }
+
+ data= (uchar *)my_mmap(0, (size_t)file_length, PROT_READ|PROT_WRITE,
+ MAP_NOSYNC|MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED)
+ {
+ my_errno=errno;
+ goto err;
+ }
+ inited=2;
+
+ npages=(uint)file_length/tc_log_page_size;
+ if (npages < 3) // to guarantee non-empty pool
+ goto err;
+ if (!(pages=(PAGE *)my_malloc(npages*sizeof(PAGE), MYF(MY_WME|MY_ZEROFILL))))
+ goto err;
+ inited=3;
+ for (pg=pages, i=0; i < npages; i++, pg++)
+ {
+ pg->next=pg+1;
+ pg->waiters=0;
+ pg->state=PS_POOL;
+ mysql_mutex_init(key_PAGE_lock, &pg->lock, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_PAGE_cond, &pg->cond, 0);
+ pg->ptr= pg->start=(my_xid *)(data + i*tc_log_page_size);
+ pg->size=pg->free=tc_log_page_size/sizeof(my_xid);
+ pg->end=pg->start + pg->size;
+ }
+ pages[0].size=pages[0].free=
+ (tc_log_page_size-TC_LOG_HEADER_SIZE)/sizeof(my_xid);
+ pages[0].start=pages[0].end-pages[0].size;
+ pages[npages-1].next=0;
+ inited=4;
+
+ if (crashed && recover())
+ goto err;
+
+ memcpy(data, tc_log_magic, sizeof(tc_log_magic));
+ data[sizeof(tc_log_magic)]= (uchar)total_ha_2pc;
+ my_msync(fd, data, tc_log_page_size, MS_SYNC);
+ inited=5;
+
+ mysql_mutex_init(key_LOCK_sync, &LOCK_sync, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_active, &LOCK_active, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_pool, &LOCK_pool, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_pending_checkpoint, &LOCK_pending_checkpoint,
+ MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_active, &COND_active, 0);
+ mysql_cond_init(key_COND_pool, &COND_pool, 0);
+ mysql_cond_init(key_TC_LOG_MMAP_COND_queue_busy, &COND_queue_busy, 0);
+
+ inited=6;
+
+ syncing= 0;
+ active=pages;
+ DBUG_ASSERT(npages >= 2);
+ pool=pages+1;
+ pool_last_ptr= &((pages+npages-1)->next);
+ commit_ordered_queue= NULL;
+ commit_ordered_queue_busy= false;
+
+ return 0;
+
+err:
+ close();
+ return 1;
+}
+
+/**
+ there is no active page, let's got one from the pool.
+
+ Two strategies here:
+ -# take the first from the pool
+ -# if there're waiters - take the one with the most free space.
+
+ @todo
+ page merging. try to allocate adjacent page first,
+ so that they can be flushed both in one sync
+*/
+
+void TC_LOG_MMAP::get_active_from_pool()
+{
+ PAGE **p, **best_p=0;
+ int best_free;
+
+ mysql_mutex_lock(&LOCK_pool);
+
+ do
+ {
+ best_p= p= &pool;
+ if ((*p)->waiters == 0 && (*p)->free > 0) // can the first page be used ?
+ break; // yes - take it.
+
+ best_free=0; // no - trying second strategy
+ for (p=&(*p)->next; *p; p=&(*p)->next)
+ {
+ if ((*p)->waiters == 0 && (*p)->free > best_free)
+ {
+ best_free=(*p)->free;
+ best_p=p;
+ }
+ }
+ }
+ while ((*best_p == 0 || best_free == 0) && overflow());
+
+ mysql_mutex_assert_owner(&LOCK_active);
+ active=*best_p;
+
+ /* Unlink the page from the pool. */
+ if (!(*best_p)->next)
+ pool_last_ptr= best_p;
+ *best_p=(*best_p)->next;
+ mysql_mutex_unlock(&LOCK_pool);
+
+ mysql_mutex_lock(&active->lock);
+ if (active->free == active->size) // we've chosen an empty page
+ {
+ tc_log_cur_pages_used++;
+ set_if_bigger(tc_log_max_pages_used, tc_log_cur_pages_used);
+ }
+}
+
+/**
+ @todo
+ perhaps, increase log size ?
+*/
+int TC_LOG_MMAP::overflow()
+{
+ /*
+ simple overflow handling - just wait
+ TODO perhaps, increase log size ?
+ let's check the behaviour of tc_log_page_waits first
+ */
+ tc_log_page_waits++;
+ mysql_cond_wait(&COND_pool, &LOCK_pool);
+ return 1; // always return 1
+}
+
+/**
+ Record that transaction XID is committed on the persistent storage.
+
+ This function is called in the middle of two-phase commit:
+ First all resources prepare the transaction, then tc_log->log() is called,
+ then all resources commit the transaction, then tc_log->unlog() is called.
+
+ All access to active page is serialized but it's not a problem, as
+ we're assuming that fsync() will be a main bottleneck.
+ That is, parallelizing writes to log pages we'll decrease number of
+ threads waiting for a page, but then all these threads will be waiting
+ for a fsync() anyway
+
+ If tc_log == MYSQL_LOG then tc_log writes transaction to binlog and
+ records XID in a special Xid_log_event.
+ If tc_log = TC_LOG_MMAP then xid is written in a special memory-mapped
+ log.
+
+ @retval
+ 0 - error
+ @retval
+ \# - otherwise, "cookie", a number that will be passed as an argument
+ to unlog() call. tc_log can define it any way it wants,
+ and use for whatever purposes. TC_LOG_MMAP sets it
+ to the position in memory where xid was logged to.
+*/
+
+int TC_LOG_MMAP::log_one_transaction(my_xid xid)
+{
+ int err;
+ PAGE *p;
+ ulong cookie;
+
+ mysql_mutex_lock(&LOCK_active);
+
+ /*
+ if the active page is full - just wait...
+ frankly speaking, active->free here accessed outside of mutex
+ protection, but it's safe, because it only means we may miss an
+ unlog() for the active page, and we're not waiting for it here -
+ unlog() does not signal COND_active.
+ */
+ while (unlikely(active && active->free == 0))
+ mysql_cond_wait(&COND_active, &LOCK_active);
+
+ /* no active page ? take one from the pool */
+ if (active == 0)
+ get_active_from_pool();
+ else
+ mysql_mutex_lock(&active->lock);
+
+ p=active;
+
+ /*
+ p->free is always > 0 here because to decrease it one needs
+ to take p->lock and before it one needs to take LOCK_active.
+ But checked that active->free > 0 under LOCK_active and
+ haven't release it ever since
+ */
+
+ /* searching for an empty slot */
+ while (*p->ptr)
+ {
+ p->ptr++;
+ DBUG_ASSERT(p->ptr < p->end); // because p->free > 0
+ }
+
+ /* found! store xid there and mark the page dirty */
+ cookie= (ulong)((uchar *)p->ptr - data); // can never be zero
+ *p->ptr++= xid;
+ p->free--;
+ p->state= PS_DIRTY;
+ mysql_mutex_unlock(&p->lock);
+
+ mysql_mutex_lock(&LOCK_sync);
+ if (syncing)
+ { // somebody's syncing. let's wait
+ mysql_mutex_unlock(&LOCK_active);
+ mysql_mutex_lock(&p->lock);
+ p->waiters++;
+ while (p->state == PS_DIRTY && syncing)
+ {
+ mysql_mutex_unlock(&p->lock);
+ mysql_cond_wait(&p->cond, &LOCK_sync);
+ mysql_mutex_lock(&p->lock);
+ }
+ p->waiters--;
+ err= p->state == PS_ERROR;
+ if (p->state != PS_DIRTY) // page was synced
+ {
+ mysql_mutex_unlock(&LOCK_sync);
+ if (p->waiters == 0)
+ mysql_cond_signal(&COND_pool); // in case somebody's waiting
+ mysql_mutex_unlock(&p->lock);
+ goto done; // we're done
+ }
+ DBUG_ASSERT(!syncing);
+ mysql_mutex_unlock(&p->lock);
+ syncing = p;
+ mysql_mutex_unlock(&LOCK_sync);
+
+ mysql_mutex_lock(&LOCK_active);
+ active=0; // page is not active anymore
+ mysql_cond_broadcast(&COND_active);
+ mysql_mutex_unlock(&LOCK_active);
+ }
+ else
+ {
+ syncing = p; // place is vacant - take it
+ mysql_mutex_unlock(&LOCK_sync);
+ active = 0; // page is not active anymore
+ mysql_cond_broadcast(&COND_active);
+ mysql_mutex_unlock(&LOCK_active);
+ }
+ err= sync();
+
+done:
+ return err ? 0 : cookie;
+}
+
+int TC_LOG_MMAP::sync()
+{
+ int err;
+
+ DBUG_ASSERT(syncing != active);
+
+ /*
+ sit down and relax - this can take a while...
+ note - no locks are held at this point
+ */
+ err= my_msync(fd, syncing->start, syncing->size * sizeof(my_xid), MS_SYNC);
+
+ /* page is synced. let's move it to the pool */
+ mysql_mutex_lock(&LOCK_pool);
+ (*pool_last_ptr)=syncing;
+ pool_last_ptr=&(syncing->next);
+ syncing->next=0;
+ syncing->state= err ? PS_ERROR : PS_POOL;
+ mysql_cond_signal(&COND_pool); // in case somebody's waiting
+ mysql_mutex_unlock(&LOCK_pool);
+
+ /* marking 'syncing' slot free */
+ mysql_mutex_lock(&LOCK_sync);
+ mysql_cond_broadcast(&syncing->cond); // signal "sync done"
+ syncing=0;
+ /*
+ we check the "active" pointer without LOCK_active. Still, it's safe -
+ "active" can change from NULL to not NULL any time, but it
+ will take LOCK_sync before waiting on active->cond. That is, it can never
+ miss a signal.
+ And "active" can change to NULL only by the syncing thread
+ (the thread that will send a signal below)
+ */
+ if (active)
+ mysql_cond_signal(&active->cond); // wake up a new syncer
+ mysql_mutex_unlock(&LOCK_sync);
+ return err;
+}
+
+static void
+mmap_do_checkpoint_callback(void *data)
+{
+ TC_LOG_MMAP::pending_cookies *pending=
+ static_cast<TC_LOG_MMAP::pending_cookies *>(data);
+ ++pending->pending_count;
+}
+
+int TC_LOG_MMAP::unlog(ulong cookie, my_xid xid)
+{
+ pending_cookies *full_buffer= NULL;
+ DBUG_ASSERT(*(my_xid *)(data+cookie) == xid);
+
+ /*
+ Do not delete the entry immediately, as there may be participating storage
+ engines which implement commit_checkpoint_request(), and thus have not yet
+ flushed the commit durably to disk.
+
+ Instead put it in a queue - and periodically, we will request a checkpoint
+ from all engines and delete a whole batch at once.
+ */
+ mysql_mutex_lock(&LOCK_pending_checkpoint);
+ if (pending_checkpoint == NULL)
+ {
+ uint32 size= sizeof(*pending_checkpoint);
+ if (!(pending_checkpoint=
+ (pending_cookies *)my_malloc(size, MYF(MY_ZEROFILL))))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), size);
+ mysql_mutex_unlock(&LOCK_pending_checkpoint);
+ return 1;
+ }
+ }
+
+ pending_checkpoint->cookies[pending_checkpoint->count++]= cookie;
+ if (pending_checkpoint->count == sizeof(pending_checkpoint->cookies) /
+ sizeof(pending_checkpoint->cookies[0]))
+ {
+ full_buffer= pending_checkpoint;
+ pending_checkpoint= NULL;
+ }
+ mysql_mutex_unlock(&LOCK_pending_checkpoint);
+
+ if (full_buffer)
+ {
+ /*
+ We do an extra increment and notify here - this ensures that
+ things work also if there are no engines at all that support
+ commit_checkpoint_request.
+ */
+ ++full_buffer->pending_count;
+ ha_commit_checkpoint_request(full_buffer, mmap_do_checkpoint_callback);
+ commit_checkpoint_notify(full_buffer);
+ }
+ return 0;
+}
+
+
+void
+TC_LOG_MMAP::commit_checkpoint_notify(void *cookie)
+{
+ uint count;
+ pending_cookies *pending= static_cast<pending_cookies *>(cookie);
+ mysql_mutex_lock(&LOCK_pending_checkpoint);
+ DBUG_ASSERT(pending->pending_count > 0);
+ count= --pending->pending_count;
+ mysql_mutex_unlock(&LOCK_pending_checkpoint);
+ if (count == 0)
+ {
+ uint i;
+ for (i= 0; i < sizeof(pending->cookies)/sizeof(pending->cookies[0]); ++i)
+ delete_entry(pending->cookies[i]);
+ my_free(pending);
+ }
+}
+
+
+/**
+ erase xid from the page, update page free space counters/pointers.
+ cookie points directly to the memory where xid was logged.
+*/
+
+int TC_LOG_MMAP::delete_entry(ulong cookie)
+{
+ PAGE *p=pages+(cookie/tc_log_page_size);
+ my_xid *x=(my_xid *)(data+cookie);
+
+ DBUG_ASSERT(x >= p->start && x < p->end);
+
+ mysql_mutex_lock(&p->lock);
+ *x=0;
+ p->free++;
+ DBUG_ASSERT(p->free <= p->size);
+ set_if_smaller(p->ptr, x);
+ if (p->free == p->size) // the page is completely empty
+ statistic_decrement(tc_log_cur_pages_used, &LOCK_status);
+ if (p->waiters == 0) // the page is in pool and ready to rock
+ mysql_cond_signal(&COND_pool); // ping ... for overflow()
+ mysql_mutex_unlock(&p->lock);
+ return 0;
+}
+
+void TC_LOG_MMAP::close()
+{
+ uint i;
+ switch (inited) {
+ case 6:
+ mysql_mutex_destroy(&LOCK_sync);
+ mysql_mutex_destroy(&LOCK_active);
+ mysql_mutex_destroy(&LOCK_pool);
+ mysql_mutex_destroy(&LOCK_pending_checkpoint);
+ mysql_cond_destroy(&COND_pool);
+ mysql_cond_destroy(&COND_active);
+ mysql_cond_destroy(&COND_queue_busy);
+ case 5:
+ data[0]='A'; // garble the first (signature) byte, in case mysql_file_delete fails
+ case 4:
+ for (i=0; i < npages; i++)
+ {
+ if (pages[i].ptr == 0)
+ break;
+ mysql_mutex_destroy(&pages[i].lock);
+ mysql_cond_destroy(&pages[i].cond);
+ }
+ case 3:
+ my_free(pages);
+ case 2:
+ my_munmap((char*)data, (size_t)file_length);
+ case 1:
+ mysql_file_close(fd, MYF(0));
+ }
+ if (inited>=5) // cannot do in the switch because of Windows
+ mysql_file_delete(key_file_tclog, logname, MYF(MY_WME));
+ if (pending_checkpoint)
+ my_free(pending_checkpoint);
+ inited=0;
+}
+
+
+int TC_LOG_MMAP::recover()
+{
+ HASH xids;
+ PAGE *p=pages, *end_p=pages+npages;
+
+ if (bcmp(data, tc_log_magic, sizeof(tc_log_magic)))
+ {
+ sql_print_error("Bad magic header in tc log");
+ goto err1;
+ }
+
+ /*
+ the first byte after magic signature is set to current
+ number of storage engines on startup
+ */
+ if (data[sizeof(tc_log_magic)] != total_ha_2pc)
+ {
+ sql_print_error("Recovery failed! You must enable "
+ "exactly %d storage engines that support "
+ "two-phase commit protocol",
+ data[sizeof(tc_log_magic)]);
+ goto err1;
+ }
+
+ if (my_hash_init(&xids, &my_charset_bin, tc_log_page_size/3, 0,
+ sizeof(my_xid), 0, 0, MYF(0)))
+ goto err1;
+
+ for ( ; p < end_p ; p++)
+ {
+ for (my_xid *x=p->start; x < p->end; x++)
+ if (*x && my_hash_insert(&xids, (uchar *)x))
+ goto err2; // OOM
+ }
+
+ if (ha_recover(&xids))
+ goto err2;
+
+ my_hash_free(&xids);
+ bzero(data, (size_t)file_length);
+ return 0;
+
+err2:
+ my_hash_free(&xids);
+err1:
+ sql_print_error("Crash recovery failed. Either correct the problem "
+ "(if it's, for example, out of memory error) and restart, "
+ "or delete tc log and start mysqld with "
+ "--tc-heuristic-recover={commit|rollback}");
+ return 1;
+}
+#endif
+
+TC_LOG *tc_log;
+TC_LOG_DUMMY tc_log_dummy;
+TC_LOG_MMAP tc_log_mmap;
+
+/**
+ Perform heuristic recovery, if --tc-heuristic-recover was used.
+
+ @note
+ no matter whether heuristic recovery was successful or not
+ mysqld must exit. So, return value is the same in both cases.
+
+ @retval
+ 0 no heuristic recovery was requested
+ @retval
+ 1 heuristic recovery was performed
+*/
+
+int TC_LOG::using_heuristic_recover()
+{
+ if (!tc_heuristic_recover)
+ return 0;
+
+ sql_print_information("Heuristic crash recovery mode");
+ if (ha_recover(0))
+ sql_print_error("Heuristic crash recovery failed");
+ sql_print_information("Please restart mysqld without --tc-heuristic-recover");
+ return 1;
+}
+
+/****** transaction coordinator log for 2pc - binlog() based solution ******/
+#define TC_LOG_BINLOG MYSQL_BIN_LOG
+
+int TC_LOG_BINLOG::open(const char *opt_name)
+{
+ int error= 1;
+
+ DBUG_ASSERT(total_ha_2pc > 1);
+ DBUG_ASSERT(opt_name && opt_name[0]);
+
+ if (!my_b_inited(&index_file))
+ {
+ /* There was a failure to open the index file, can't open the binlog */
+ cleanup();
+ return 1;
+ }
+
+ if (using_heuristic_recover())
+ {
+ /* generate a new binlog to mask a corrupted one */
+ open(opt_name, LOG_BIN, 0, WRITE_CACHE, max_binlog_size, 0, TRUE);
+ cleanup();
+ return 1;
+ }
+
+ error= do_binlog_recovery(opt_name, true);
+ binlog_state_recover_done= true;
+ return error;
+}
+
+/** This is called on shutdown, after ha_panic. */
+void TC_LOG_BINLOG::close()
+{
+}
+
+/*
+ Do a binlog log_xid() for a group of transactions, linked through
+ thd->next_commit_ordered.
+*/
+int
+TC_LOG_BINLOG::log_and_order(THD *thd, my_xid xid, bool all,
+ bool need_prepare_ordered __attribute__((unused)),
+ bool need_commit_ordered __attribute__((unused)))
+{
+ int err;
+ DBUG_ENTER("TC_LOG_BINLOG::log_and_order");
+
+ binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data();
+ if (!cache_mngr)
+ DBUG_RETURN(0);
+
+ cache_mngr->using_xa= TRUE;
+ cache_mngr->xa_xid= xid;
+ err= binlog_commit_flush_xid_caches(thd, cache_mngr, all, xid);
+
+ DEBUG_SYNC(thd, "binlog_after_log_and_order");
+
+ if (err)
+ DBUG_RETURN(0);
+ /*
+ If using explicit user XA, we will not have XID. We must still return a
+ non-zero cookie (as zero cookie signals error).
+ */
+ if (!xid || !cache_mngr->need_unlog)
+ DBUG_RETURN(BINLOG_COOKIE_DUMMY(cache_mngr->delayed_error));
+ else
+ DBUG_RETURN(BINLOG_COOKIE_MAKE(cache_mngr->binlog_id,
+ cache_mngr->delayed_error));
+}
+
+/*
+ After an XID is logged, we need to hold on to the current binlog file until
+ it is fully committed in the storage engine. The reason is that crash
+ recovery only looks at the latest binlog, so we must make sure there are no
+ outstanding prepared (but not committed) transactions before rotating the
+ binlog.
+
+ To handle this, we keep a count of outstanding XIDs. This function is used
+ to increase this count when committing one or more transactions to the
+ binary log.
+*/
+void
+TC_LOG_BINLOG::mark_xids_active(ulong binlog_id, uint xid_count)
+{
+ xid_count_per_binlog *b;
+
+ DBUG_ENTER("TC_LOG_BINLOG::mark_xids_active");
+ DBUG_PRINT("info", ("binlog_id=%lu xid_count=%u", binlog_id, xid_count));
+
+ mysql_mutex_lock(&LOCK_xid_list);
+ I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list);
+ while ((b= it++))
+ {
+ if (b->binlog_id == binlog_id)
+ {
+ b->xid_count += xid_count;
+ break;
+ }
+ }
+ /*
+ As we do not delete elements until count reach zero, elements should always
+ be found.
+ */
+ DBUG_ASSERT(b);
+ mysql_mutex_unlock(&LOCK_xid_list);
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Once an XID is committed, it can no longer be needed during crash recovery,
+ as it has been durably recorded on disk as "committed".
+
+ This function is called to mark an XID this way. It needs to decrease the
+ count of pending XIDs in the corresponding binlog. When the count reaches
+ zero (for an "old" binlog that is not the active one), that binlog file no
+ longer need to be scanned during crash recovery, so we can log a new binlog
+ checkpoint.
+*/
+void
+TC_LOG_BINLOG::mark_xid_done(ulong binlog_id, bool write_checkpoint)
+{
+ xid_count_per_binlog *b;
+ bool first;
+ ulong current;
+
+ DBUG_ENTER("TC_LOG_BINLOG::mark_xid_done");
+
+ mysql_mutex_lock(&LOCK_xid_list);
+ current= current_binlog_id;
+ I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list);
+ first= true;
+ while ((b= it++))
+ {
+ if (b->binlog_id == binlog_id)
+ {
+ --b->xid_count;
+ break;
+ }
+ first= false;
+ }
+ /* Binlog is always found, as we do not remove until count reaches 0 */
+ DBUG_ASSERT(b);
+ /*
+ If a RESET MASTER is pending, we are about to remove all log files, and
+ the RESET MASTER thread is waiting for all pending unlog() calls to
+ complete while holding LOCK_log. In this case we should not log a binlog
+ checkpoint event (it would be deleted immediately anyway and we would
+ deadlock on LOCK_log) but just signal the thread.
+ */
+ if (unlikely(reset_master_pending))
+ {
+ mysql_cond_signal(&COND_xid_list);
+ mysql_mutex_unlock(&LOCK_xid_list);
+ DBUG_VOID_RETURN;
+ }
+
+ if (likely(binlog_id == current) || b->xid_count != 0 || !first ||
+ !write_checkpoint)
+ {
+ /* No new binlog checkpoint reached yet. */
+ mysql_mutex_unlock(&LOCK_xid_list);
+ DBUG_VOID_RETURN;
+ }
+
+ /*
+ Now log a binlog checkpoint for the first binlog file with a non-zero count.
+
+ Note that it is possible (though perhaps unlikely) that when count of
+ binlog (N-2) drops to zero, binlog (N-1) is already at zero. So we may
+ need to skip several entries before we find the one to log in the binlog
+ checkpoint event.
+
+ We chain the locking of LOCK_xid_list and LOCK_log, so that we ensure that
+ Binlog_checkpoint_events are logged in order. This simplifies recovery a
+ bit, as it can just take the last binlog checkpoint in the log, rather
+ than compare all found against each other to find the one pointing to the
+ most recent binlog.
+
+ Note also that we need to first release LOCK_xid_list, then aquire
+ LOCK_log, then re-aquire LOCK_xid_list. If we were to take LOCK_log while
+ holding LOCK_xid_list, we might deadlock with other threads that take the
+ locks in the opposite order.
+ */
+
+ ++mark_xid_done_waiting;
+ mysql_mutex_unlock(&LOCK_xid_list);
+ mysql_mutex_lock(&LOCK_log);
+ mysql_mutex_lock(&LOCK_xid_list);
+ --mark_xid_done_waiting;
+ if (unlikely(reset_master_pending))
+ mysql_cond_signal(&COND_xid_list);
+ /* We need to reload current_binlog_id due to release/re-take of lock. */
+ current= current_binlog_id;
+
+ for (;;)
+ {
+ /* Remove initial element(s) with zero count. */
+ b= binlog_xid_count_list.head();
+ /*
+ We must not remove all elements in the list - the entry for the current
+ binlog must be present always.
+ */
+ DBUG_ASSERT(b);
+ if (b->binlog_id == current || b->xid_count > 0)
+ break;
+ my_free(binlog_xid_count_list.get());
+ }
+
+ mysql_mutex_unlock(&LOCK_xid_list);
+ write_binlog_checkpoint_event_already_locked(b->binlog_name,
+ b->binlog_name_len);
+ mysql_mutex_unlock(&LOCK_log);
+ DBUG_VOID_RETURN;
+}
+
+int TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid)
+{
+ DBUG_ENTER("TC_LOG_BINLOG::unlog");
+ if (!xid)
+ DBUG_RETURN(0);
+
+ if (!BINLOG_COOKIE_IS_DUMMY(cookie))
+ mark_xid_done(BINLOG_COOKIE_GET_ID(cookie), true);
+ /*
+ See comment in trx_group_commit_leader() - if rotate() gave a failure,
+ we delay the return of error code to here.
+ */
+ DBUG_RETURN(BINLOG_COOKIE_GET_ERROR_FLAG(cookie));
+}
+
+void
+TC_LOG_BINLOG::commit_checkpoint_notify(void *cookie)
+{
+ xid_count_per_binlog *entry= static_cast<xid_count_per_binlog *>(cookie);
+ mysql_mutex_lock(&LOCK_binlog_background_thread);
+ entry->next_in_queue= binlog_background_thread_queue;
+ binlog_background_thread_queue= entry;
+ mysql_cond_signal(&COND_binlog_background_thread);
+ mysql_mutex_unlock(&LOCK_binlog_background_thread);
+}
+
+/*
+ Binlog background thread.
+
+ This thread is used to log binlog checkpoints in the background, rather than
+ in the context of random storage engine threads that happen to call
+ commit_checkpoint_notify_ha() and may not like the delays while syncing
+ binlog to disk or may not be setup with all my_thread_init() and other
+ necessary stuff.
+
+ In the future, this thread could also be used to do log rotation in the
+ background, which could elimiate all stalls around binlog rotations.
+*/
+pthread_handler_t
+binlog_background_thread(void *arg __attribute__((unused)))
+{
+ bool stop;
+ MYSQL_BIN_LOG::xid_count_per_binlog *queue, *next;
+ THD *thd;
+ my_thread_init();
+ DBUG_ENTER("binlog_background_thread");
+
+ thd= new THD;
+ thd->system_thread= SYSTEM_THREAD_BINLOG_BACKGROUND;
+ thd->thread_stack= (char*) &thd; /* Set approximate stack start */
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->thread_id= thread_id++;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ thd->store_globals();
+ thd->security_ctx->skip_grants();
+ thd->set_command(COM_DAEMON);
+
+ /*
+ Load the slave replication GTID state from the mysql.gtid_slave_pos
+ table.
+
+ This is mostly so that we can start our seq_no counter from the highest
+ seq_no seen by a slave. This way, we have a way to tell if a transaction
+ logged by ourselves as master is newer or older than a replicated
+ transaction.
+ */
+#ifdef HAVE_REPLICATION
+ if (rpl_load_gtid_slave_state(thd))
+ sql_print_warning("Failed to load slave replication state from table "
+ "%s.%s: %u: %s", "mysql",
+ rpl_gtid_slave_state_table_name.str,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+#endif
+
+ mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread);
+ binlog_background_thread_started= true;
+ mysql_cond_signal(&mysql_bin_log.COND_binlog_background_thread_end);
+ mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread);
+
+ for (;;)
+ {
+ /*
+ Wait until there is something in the queue to process, or we are asked
+ to shut down.
+ */
+ THD_STAGE_INFO(thd, stage_binlog_waiting_background_tasks);
+ mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread);
+ for (;;)
+ {
+ stop= binlog_background_thread_stop;
+ queue= binlog_background_thread_queue;
+ if (stop && !mysql_bin_log.is_xidlist_idle())
+ {
+ /*
+ Delay stop until all pending binlog checkpoints have been processed.
+ */
+ stop= false;
+ }
+ if (stop || queue)
+ break;
+ mysql_cond_wait(&mysql_bin_log.COND_binlog_background_thread,
+ &mysql_bin_log.LOCK_binlog_background_thread);
+ }
+ /* Grab the queue, if any. */
+ binlog_background_thread_queue= NULL;
+ mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread);
+
+ /* Process any incoming commit_checkpoint_notify() calls. */
+ DBUG_EXECUTE_IF("inject_binlog_background_thread_before_mark_xid_done",
+ DBUG_ASSERT(!debug_sync_set_action(
+ thd,
+ STRING_WITH_LEN("binlog_background_thread_before_mark_xid_done "
+ "SIGNAL injected_binlog_background_thread "
+ "WAIT_FOR something_that_will_never_happen "
+ "TIMEOUT 2")));
+ );
+ while (queue)
+ {
+ THD_STAGE_INFO(thd, stage_binlog_processing_checkpoint_notify);
+ DEBUG_SYNC(current_thd, "binlog_background_thread_before_mark_xid_done");
+ /* Grab next pointer first, as mark_xid_done() may free the element. */
+ next= queue->next_in_queue;
+ mysql_bin_log.mark_xid_done(queue->binlog_id, true);
+ queue= next;
+
+ DBUG_EXECUTE_IF("binlog_background_checkpoint_processed",
+ DBUG_ASSERT(!debug_sync_set_action(
+ thd,
+ STRING_WITH_LEN("now SIGNAL binlog_background_checkpoint_processed")));
+ );
+ }
+
+ if (stop)
+ break;
+ }
+
+ THD_STAGE_INFO(thd, stage_binlog_stopping_background_thread);
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ delete thd;
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ my_thread_end();
+
+ /* Signal that we are (almost) stopped. */
+ mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread);
+ binlog_background_thread_stop= false;
+ mysql_cond_signal(&mysql_bin_log.COND_binlog_background_thread_end);
+ mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread);
+
+ DBUG_RETURN(0);
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_thread_key key_thread_binlog;
+
+static PSI_thread_info all_binlog_threads[]=
+{
+ { &key_thread_binlog, "binlog_background", PSI_FLAG_GLOBAL},
+};
+#endif /* HAVE_PSI_INTERFACE */
+
+static bool
+start_binlog_background_thread()
+{
+ pthread_t th;
+
+#ifdef HAVE_PSI_INTERFACE
+ if (PSI_server)
+ PSI_server->register_thread("sql", all_binlog_threads,
+ array_elements(all_binlog_threads));
+#endif
+
+ if (mysql_thread_create(key_thread_binlog, &th, &connection_attrib,
+ binlog_background_thread, NULL))
+ return 1;
+
+ /*
+ Wait for the thread to have started (so we know that the slave replication
+ state is loaded and we have correct global_gtid_counter).
+ */
+ mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread);
+ while (!binlog_background_thread_started)
+ mysql_cond_wait(&mysql_bin_log.COND_binlog_background_thread_end,
+ &mysql_bin_log.LOCK_binlog_background_thread);
+ mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread);
+
+ return 0;
+}
+
+
+int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
+ IO_CACHE *first_log,
+ Format_description_log_event *fdle, bool do_xa)
+{
+ Log_event *ev= NULL;
+ HASH xids;
+ MEM_ROOT mem_root;
+ char binlog_checkpoint_name[FN_REFLEN];
+ bool binlog_checkpoint_found;
+ bool first_round;
+ IO_CACHE log;
+ File file= -1;
+ const char *errmsg;
+#ifdef HAVE_REPLICATION
+ rpl_gtid last_gtid;
+ bool last_gtid_standalone= false;
+ bool last_gtid_valid= false;
+#endif
+
+ if (! fdle->is_valid() ||
+ (do_xa && my_hash_init(&xids, &my_charset_bin, TC_LOG_PAGE_SIZE/3, 0,
+ sizeof(my_xid), 0, 0, MYF(0))))
+ goto err1;
+
+ if (do_xa)
+ init_alloc_root(&mem_root, TC_LOG_PAGE_SIZE, TC_LOG_PAGE_SIZE, MYF(0));
+
+ fdle->flags&= ~LOG_EVENT_BINLOG_IN_USE_F; // abort on the first error
+
+ /*
+ Scan the binlog for XIDs that need to be committed if still in the
+ prepared stage.
+
+ Start with the latest binlog file, then continue with any other binlog
+ files if the last found binlog checkpoint indicates it is needed.
+ */
+
+ binlog_checkpoint_found= false;
+ first_round= true;
+ for (;;)
+ {
+ while ((ev= Log_event::read_log_event(first_round ? first_log : &log,
+ 0, fdle, opt_master_verify_checksum))
+ && ev->is_valid())
+ {
+ enum Log_event_type typ= ev->get_type_code();
+ switch (typ)
+ {
+ case XID_EVENT:
+ {
+ if (do_xa)
+ {
+ Xid_log_event *xev=(Xid_log_event *)ev;
+ uchar *x= (uchar *) memdup_root(&mem_root, (uchar*) &xev->xid,
+ sizeof(xev->xid));
+ if (!x || my_hash_insert(&xids, x))
+ goto err2;
+ break;
+ }
+ }
+ case BINLOG_CHECKPOINT_EVENT:
+ if (first_round && do_xa)
+ {
+ uint dir_len;
+ Binlog_checkpoint_log_event *cev= (Binlog_checkpoint_log_event *)ev;
+ if (cev->binlog_file_len >= FN_REFLEN)
+ sql_print_warning("Incorrect binlog checkpoint event with too "
+ "long file name found.");
+ else
+ {
+ /*
+ Note that we cannot use make_log_name() here, as we have not yet
+ initialised MYSQL_BIN_LOG::log_file_name.
+ */
+ dir_len= dirname_length(last_log_name);
+ strmake(strnmov(binlog_checkpoint_name, last_log_name, dir_len),
+ cev->binlog_file_name, FN_REFLEN - 1 - dir_len);
+ binlog_checkpoint_found= true;
+ }
+ }
+ break;
+ case GTID_LIST_EVENT:
+ if (first_round)
+ {
+ Gtid_list_log_event *glev= (Gtid_list_log_event *)ev;
+
+ /* Initialise the binlog state from the Gtid_list event. */
+ if (rpl_global_gtid_binlog_state.load(glev->list, glev->count))
+ goto err2;
+ }
+ break;
+
+#ifdef HAVE_REPLICATION
+ case GTID_EVENT:
+ if (first_round)
+ {
+ Gtid_log_event *gev= (Gtid_log_event *)ev;
+
+ /* Update the binlog state with any GTID logged after Gtid_list. */
+ last_gtid.domain_id= gev->domain_id;
+ last_gtid.server_id= gev->server_id;
+ last_gtid.seq_no= gev->seq_no;
+ last_gtid_standalone=
+ ((gev->flags2 & Gtid_log_event::FL_STANDALONE) ? true : false);
+ last_gtid_valid= true;
+ }
+ break;
+#endif
+
+ default:
+ /* Nothing. */
+ break;
+ }
+
+#ifdef HAVE_REPLICATION
+ if (last_gtid_valid &&
+ ((last_gtid_standalone && !ev->is_part_of_group(typ)) ||
+ (!last_gtid_standalone &&
+ (typ == XID_EVENT ||
+ (typ == QUERY_EVENT &&
+ (((Query_log_event *)ev)->is_commit() ||
+ ((Query_log_event *)ev)->is_rollback()))))))
+ {
+ if (rpl_global_gtid_binlog_state.update_nolock(&last_gtid, false))
+ goto err2;
+ last_gtid_valid= false;
+ }
+#endif
+
+ delete ev;
+ ev= NULL;
+ }
+
+ if (!do_xa)
+ break;
+ /*
+ If the last binlog checkpoint event points to an older log, we have to
+ scan all logs from there also, to get all possible XIDs to recover.
+
+ If there was no binlog checkpoint event at all, this means the log was
+ written by an older version of MariaDB (or MySQL) - these always have an
+ (implicit) binlog checkpoint event at the start of the last binlog file.
+ */
+ if (first_round)
+ {
+ if (!binlog_checkpoint_found)
+ break;
+ first_round= false;
+ DBUG_EXECUTE_IF("xa_recover_expect_master_bin_000004",
+ if (0 != strcmp("./master-bin.000004", binlog_checkpoint_name) &&
+ 0 != strcmp(".\\master-bin.000004", binlog_checkpoint_name))
+ DBUG_SUICIDE();
+ );
+ if (find_log_pos(linfo, binlog_checkpoint_name, 1))
+ {
+ sql_print_error("Binlog file '%s' not found in binlog index, needed "
+ "for recovery. Aborting.", binlog_checkpoint_name);
+ goto err2;
+ }
+ }
+ else
+ {
+ end_io_cache(&log);
+ mysql_file_close(file, MYF(MY_WME));
+ file= -1;
+ }
+
+ if (!strcmp(linfo->log_file_name, last_log_name))
+ break; // No more files to do
+ if ((file= open_binlog(&log, linfo->log_file_name, &errmsg)) < 0)
+ {
+ sql_print_error("%s", errmsg);
+ goto err2;
+ }
+ /*
+ We do not need to read the Format_description_log_event of other binlog
+ files. It is not possible for a binlog checkpoint to span multiple
+ binlog files written by different versions of the server. So we can use
+ the first one read for reading from all binlog files.
+ */
+ if (find_next_log(linfo, 1))
+ {
+ sql_print_error("Error reading binlog files during recovery. Aborting.");
+ goto err2;
+ }
+ }
+
+ if (do_xa)
+ {
+ if (ha_recover(&xids))
+ goto err2;
+
+ free_root(&mem_root, MYF(0));
+ my_hash_free(&xids);
+ }
+ return 0;
+
+err2:
+ delete ev;
+ if (file >= 0)
+ {
+ end_io_cache(&log);
+ mysql_file_close(file, MYF(MY_WME));
+ }
+ if (do_xa)
+ {
+ free_root(&mem_root, MYF(0));
+ my_hash_free(&xids);
+ }
+err1:
+ sql_print_error("Crash recovery failed. Either correct the problem "
+ "(if it's, for example, out of memory error) and restart, "
+ "or delete (or rename) binary log and start mysqld with "
+ "--tc-heuristic-recover={commit|rollback}");
+ return 1;
+}
+
+
+int
+MYSQL_BIN_LOG::do_binlog_recovery(const char *opt_name, bool do_xa_recovery)
+{
+ LOG_INFO log_info;
+ const char *errmsg;
+ IO_CACHE log;
+ File file;
+ Log_event *ev= 0;
+ Format_description_log_event fdle(BINLOG_VERSION);
+ char log_name[FN_REFLEN];
+ int error;
+
+ if ((error= find_log_pos(&log_info, NullS, 1)))
+ {
+ /*
+ If there are no binlog files (LOG_INFO_EOF), then we still try to read
+ the .state file to restore the binlog state. This allows to copy a server
+ to provision a new one without copying the binlog files (except the
+ master-bin.state file) and still preserve the correct binlog state.
+ */
+ if (error != LOG_INFO_EOF)
+ sql_print_error("find_log_pos() failed (error: %d)", error);
+ else
+ {
+ error= read_state_from_file();
+ if (error == 2)
+ {
+ /*
+ No binlog files and no binlog state is not an error (eg. just initial
+ server start after fresh installation).
+ */
+ error= 0;
+ }
+ }
+ return error;
+ }
+
+ if (! fdle.is_valid())
+ return 1;
+
+ do
+ {
+ strmake_buf(log_name, log_info.log_file_name);
+ } while (!(error= find_next_log(&log_info, 1)));
+
+ if (error != LOG_INFO_EOF)
+ {
+ sql_print_error("find_log_pos() failed (error: %d)", error);
+ return error;
+ }
+
+ if ((file= open_binlog(&log, log_name, &errmsg)) < 0)
+ {
+ sql_print_error("%s", errmsg);
+ return 1;
+ }
+
+ if ((ev= Log_event::read_log_event(&log, 0, &fdle,
+ opt_master_verify_checksum)) &&
+ ev->get_type_code() == FORMAT_DESCRIPTION_EVENT)
+ {
+ if (ev->flags & LOG_EVENT_BINLOG_IN_USE_F)
+ {
+ sql_print_information("Recovering after a crash using %s", opt_name);
+ error= recover(&log_info, log_name, &log,
+ (Format_description_log_event *)ev, do_xa_recovery);
+ }
+ else
+ {
+ error= read_state_from_file();
+ if (error == 2)
+ {
+ /*
+ The binlog exists, but the .state file is missing. This is normal if
+ this is the first master start after a major upgrade to 10.0 (with
+ GTID support).
+
+ However, it could also be that the .state file was lost somehow, and
+ in this case it could be a serious issue, as we would set the wrong
+ binlog state in the next binlog file to be created, and GTID
+ processing would be corrupted. A common way would be copying files
+ from an old server to a new one and forgetting the .state file.
+
+ So in this case, we want to try to recover the binlog state by
+ scanning the last binlog file (but we do not need any XA recovery).
+
+ ToDo: We could avoid one scan at first start after major upgrade, by
+ detecting that there is no GTID_LIST event at the start of the
+ binlog file, and stopping the scan in that case.
+ */
+ error= recover(&log_info, log_name, &log,
+ (Format_description_log_event *)ev, false);
+ }
+ }
+ }
+
+ delete ev;
+ end_io_cache(&log);
+ mysql_file_close(file, MYF(MY_WME));
+
+ return error;
+}
+
+
+#ifdef INNODB_COMPATIBILITY_HOOKS
+/**
+ Get the file name of the MySQL binlog.
+ @return the name of the binlog file
+*/
+extern "C"
+const char* mysql_bin_log_file_name(void)
+{
+ return mysql_bin_log.get_log_fname();
+}
+/**
+ Get the current position of the MySQL binlog.
+ @return byte offset from the beginning of the binlog
+*/
+extern "C"
+ulonglong mysql_bin_log_file_pos(void)
+{
+ return (ulonglong) mysql_bin_log.get_log_file()->pos_in_file;
+}
+/*
+ Get the current position of the MySQL binlog for transaction currently being
+ committed.
+
+ This is valid to call from within storage engine commit_ordered() and
+ commit() methods only.
+
+ Since it stores the position inside THD, it is safe to call without any
+ locking.
+*/
+void
+mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file)
+{
+ binlog_cache_mngr *cache_mngr;
+ if (opt_bin_log &&
+ (cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton)))
+ {
+ *out_file= cache_mngr->last_commit_pos_file;
+ *out_pos= (ulonglong)(cache_mngr->last_commit_pos_offset);
+ }
+ else
+ {
+ *out_file= NULL;
+ *out_pos= 0;
+ }
+}
+#endif /* INNODB_COMPATIBILITY_HOOKS */
+
+
+static void
+binlog_checksum_update(MYSQL_THD thd, struct st_mysql_sys_var *var,
+ void *var_ptr, const void *save)
+{
+ ulong value= *((ulong *)save);
+ bool check_purge= false;
+ ulong prev_binlog_id;
+ LINT_INIT(prev_binlog_id);
+
+ mysql_mutex_lock(mysql_bin_log.get_log_lock());
+ if(mysql_bin_log.is_open())
+ {
+ prev_binlog_id= mysql_bin_log.current_binlog_id;
+ if (binlog_checksum_options != value)
+ mysql_bin_log.checksum_alg_reset= (uint8) value;
+ if (mysql_bin_log.rotate(true, &check_purge))
+ check_purge= false;
+ }
+ else
+ {
+ binlog_checksum_options= value;
+ }
+ DBUG_ASSERT(binlog_checksum_options == value);
+ mysql_bin_log.checksum_alg_reset= BINLOG_CHECKSUM_ALG_UNDEF;
+ mysql_mutex_unlock(mysql_bin_log.get_log_lock());
+ if (check_purge)
+ mysql_bin_log.checkpoint_and_purge(prev_binlog_id);
+}
+
+
+static int show_binlog_vars(THD *thd, SHOW_VAR *var, char *buff)
+{
+ mysql_bin_log.set_status_variables(thd);
+ var->type= SHOW_ARRAY;
+ var->value= (char *)&binlog_status_vars_detail;
+ return 0;
+}
+
+static SHOW_VAR binlog_status_vars_top[]= {
+ {"Binlog", (char *) &show_binlog_vars, SHOW_FUNC},
+ {NullS, NullS, SHOW_LONG}
+};
+
+static MYSQL_SYSVAR_BOOL(
+ optimize_thread_scheduling,
+ opt_optimize_thread_scheduling,
+ PLUGIN_VAR_READONLY,
+ "Run fast part of group commit in a single thread, to optimize kernel "
+ "thread scheduling. On by default. Disable to run each transaction in group "
+ "commit in its own thread, which can be slower at very high concurrency. "
+ "This option is mostly for testing one algorithm versus the other, and it "
+ "should not normally be necessary to change it.",
+ NULL,
+ NULL,
+ 1);
+
+static MYSQL_SYSVAR_ENUM(
+ checksum,
+ binlog_checksum_options,
+ PLUGIN_VAR_RQCMDARG,
+ "Type of BINLOG_CHECKSUM_ALG. Include checksum for "
+ "log events in the binary log. Possible values are NONE and CRC32; "
+ "default is NONE.",
+ NULL,
+ binlog_checksum_update,
+ BINLOG_CHECKSUM_ALG_OFF,
+ &binlog_checksum_typelib);
+
+static struct st_mysql_sys_var *binlog_sys_vars[]=
+{
+ MYSQL_SYSVAR(optimize_thread_scheduling),
+ MYSQL_SYSVAR(checksum),
+ NULL
+};
+
+
+/*
+ Copy out the non-directory part of binlog position filename for the
+ `binlog_snapshot_file' status variable, same way as it is done for
+ SHOW MASTER STATUS.
+*/
+static void
+set_binlog_snapshot_file(const char *src)
+{
+ int dir_len = dirname_length(src);
+ strmake_buf(binlog_snapshot_file, src + dir_len);
+}
+
+/*
+ Copy out current values of status variables, for SHOW STATUS or
+ information_schema.global_status.
+
+ This is called only under LOCK_show_status, so we can fill in a static array.
+*/
+void
+TC_LOG_BINLOG::set_status_variables(THD *thd)
+{
+ binlog_cache_mngr *cache_mngr;
+
+ if (thd && opt_bin_log)
+ cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ else
+ cache_mngr= 0;
+
+ bool have_snapshot= (cache_mngr && cache_mngr->last_commit_pos_file[0] != 0);
+ mysql_mutex_lock(&LOCK_commit_ordered);
+ binlog_status_var_num_commits= this->num_commits;
+ binlog_status_var_num_group_commits= this->num_group_commits;
+ if (!have_snapshot)
+ {
+ set_binlog_snapshot_file(last_commit_pos_file);
+ binlog_snapshot_position= last_commit_pos_offset;
+ }
+ mysql_mutex_unlock(&LOCK_commit_ordered);
+ mysql_mutex_lock(&LOCK_prepare_ordered);
+ binlog_status_group_commit_trigger_count= this->group_commit_trigger_count;
+ binlog_status_group_commit_trigger_timeout= this->group_commit_trigger_timeout;
+ binlog_status_group_commit_trigger_lock_wait= this->group_commit_trigger_lock_wait;
+ mysql_mutex_unlock(&LOCK_prepare_ordered);
+
+ if (have_snapshot)
+ {
+ set_binlog_snapshot_file(cache_mngr->last_commit_pos_file);
+ binlog_snapshot_position= cache_mngr->last_commit_pos_offset;
+ }
+}
+
+struct st_mysql_storage_engine binlog_storage_engine=
+{ MYSQL_HANDLERTON_INTERFACE_VERSION };
+
+maria_declare_plugin(binlog)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &binlog_storage_engine,
+ "binlog",
+ "MySQL AB",
+ "This is a pseudo storage engine to represent the binlog in a transaction",
+ PLUGIN_LICENSE_GPL,
+ binlog_init, /* Plugin Init */
+ NULL, /* Plugin Deinit */
+ 0x0100 /* 1.0 */,
+ binlog_status_vars_top, /* status variables */
+ binlog_sys_vars, /* system variables */
+ "1.0", /* string version */
+ MariaDB_PLUGIN_MATURITY_STABLE /* maturity */
+}
+maria_declare_plugin_end;
diff --git a/sql/log.h b/sql/log.h
new file mode 100644
index 00000000000..d3540aa4499
--- /dev/null
+++ b/sql/log.h
@@ -0,0 +1,1094 @@
+/* Copyright (c) 2005, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2012, Monty Program Ab
+
+ 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 */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include "unireg.h" // REQUIRED: for other includes
+#include "handler.h" /* my_xid */
+
+class Relay_log_info;
+
+class Format_description_log_event;
+
+bool trans_has_updated_trans_table(const THD* thd);
+bool stmt_has_updated_trans_table(const THD *thd);
+bool use_trans_cache(const THD* thd, bool is_transactional);
+bool ending_trans(THD* thd, const bool all);
+bool ending_single_stmt_trans(THD* thd, const bool all);
+bool trans_has_updated_non_trans_table(const THD* thd);
+bool stmt_has_updated_non_trans_table(const THD* thd);
+
+/*
+ Transaction Coordinator log - a base abstract class
+ for two different implementations
+*/
+class TC_LOG
+{
+ public:
+ int using_heuristic_recover();
+ TC_LOG() {}
+ virtual ~TC_LOG() {}
+
+ virtual int open(const char *opt_name)=0;
+ virtual void close()=0;
+ /*
+ Transaction coordinator 2-phase commit.
+
+ Must invoke the run_prepare_ordered and run_commit_ordered methods, as
+ described below for these methods.
+
+ In addition, must invoke THD::wait_for_prior_commit(), or equivalent
+ wait, to ensure that one commit waits for another if registered to do so.
+ */
+ virtual int log_and_order(THD *thd, my_xid xid, bool all,
+ bool need_prepare_ordered,
+ bool need_commit_ordered) = 0;
+ virtual int unlog(ulong cookie, my_xid xid)=0;
+ virtual void commit_checkpoint_notify(void *cookie)= 0;
+
+protected:
+ /*
+ These methods are meant to be invoked from log_and_order() implementations
+ to run any prepare_ordered() respectively commit_ordered() methods in
+ participating handlers.
+
+ They must be called using suitable thread syncronisation to ensure that
+ they are each called in the correct commit order among all
+ transactions. However, it is only necessary to call them if the
+ corresponding flag passed to log_and_order is set (it is safe, but not
+ required, to call them when the flag is false).
+
+ The caller must be holding LOCK_prepare_ordered respectively
+ LOCK_commit_ordered when calling these methods.
+ */
+ void run_prepare_ordered(THD *thd, bool all);
+ void run_commit_ordered(THD *thd, bool all);
+};
+
+/*
+ Locks used to ensure serialised execution of TC_LOG::run_prepare_ordered()
+ and TC_LOG::run_commit_ordered(), or any other code that calls handler
+ prepare_ordered() or commit_ordered() methods.
+*/
+extern mysql_mutex_t LOCK_prepare_ordered;
+extern mysql_cond_t COND_prepare_ordered;
+extern mysql_mutex_t LOCK_commit_ordered;
+#ifdef HAVE_PSI_INTERFACE
+extern PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered;
+extern PSI_cond_key key_COND_prepare_ordered;
+#endif
+
+class TC_LOG_DUMMY: public TC_LOG // use it to disable the logging
+{
+public:
+ TC_LOG_DUMMY() {}
+ int open(const char *opt_name) { return 0; }
+ void close() { }
+ /*
+ TC_LOG_DUMMY is only used when there are <= 1 XA-capable engines, and we
+ only use internal XA during commit when >= 2 XA-capable engines
+ participate.
+ */
+ int log_and_order(THD *thd, my_xid xid, bool all,
+ bool need_prepare_ordered, bool need_commit_ordered)
+ {
+ DBUG_ASSERT(0 /* Internal error - TC_LOG_DUMMY::log_and_order() called */);
+ return 1;
+ }
+ int unlog(ulong cookie, my_xid xid) { return 0; }
+ void commit_checkpoint_notify(void *cookie) { DBUG_ASSERT(0); };
+};
+
+#define TC_LOG_PAGE_SIZE 8192
+#define TC_LOG_MIN_SIZE (3*TC_LOG_PAGE_SIZE)
+
+#ifdef HAVE_MMAP
+class TC_LOG_MMAP: public TC_LOG
+{
+ public: // only to keep Sun Forte on sol9x86 happy
+ typedef enum {
+ PS_POOL, // page is in pool
+ PS_ERROR, // last sync failed
+ PS_DIRTY // new xids added since last sync
+ } PAGE_STATE;
+
+ struct pending_cookies {
+ uint count;
+ uint pending_count;
+ ulong cookies[TC_LOG_PAGE_SIZE/sizeof(my_xid)];
+ };
+
+ private:
+ typedef struct st_page {
+ struct st_page *next; // page a linked in a fifo queue
+ my_xid *start, *end; // usable area of a page
+ my_xid *ptr; // next xid will be written here
+ int size, free; // max and current number of free xid slots on the page
+ int waiters; // number of waiters on condition
+ PAGE_STATE state; // see above
+ mysql_mutex_t lock; // to access page data or control structure
+ mysql_cond_t cond; // to wait for a sync
+ } PAGE;
+
+ /* List of THDs for which to invoke commit_ordered(), in order. */
+ struct commit_entry
+ {
+ struct commit_entry *next;
+ THD *thd;
+ };
+
+ char logname[FN_REFLEN];
+ File fd;
+ my_off_t file_length;
+ uint npages, inited;
+ uchar *data;
+ struct st_page *pages, *syncing, *active, *pool, **pool_last_ptr;
+ /*
+ note that, e.g. LOCK_active is only used to protect
+ 'active' pointer, to protect the content of the active page
+ one has to use active->lock.
+ Same for LOCK_pool and LOCK_sync
+ */
+ mysql_mutex_t LOCK_active, LOCK_pool, LOCK_sync, LOCK_pending_checkpoint;
+ mysql_cond_t COND_pool, COND_active;
+ /*
+ Queue of threads that need to call commit_ordered().
+ Access to this queue must be protected by LOCK_prepare_ordered.
+ */
+ commit_entry *commit_ordered_queue;
+ /*
+ This flag and condition is used to reserve the queue while threads in it
+ each run the commit_ordered() methods one after the other. Only once the
+ last commit_ordered() in the queue is done can we start on a new queue
+ run.
+
+ Since we start this process in the first thread in the queue and finish in
+ the last (and possibly different) thread, we need a condition variable for
+ this (we cannot unlock a mutex in a different thread than the one who
+ locked it).
+
+ The condition is used together with the LOCK_prepare_ordered mutex.
+ */
+ mysql_cond_t COND_queue_busy;
+ my_bool commit_ordered_queue_busy;
+ pending_cookies* pending_checkpoint;
+
+ public:
+ TC_LOG_MMAP(): inited(0), pending_checkpoint(0) {}
+ int open(const char *opt_name);
+ void close();
+ int log_and_order(THD *thd, my_xid xid, bool all,
+ bool need_prepare_ordered, bool need_commit_ordered);
+ int unlog(ulong cookie, my_xid xid);
+ void commit_checkpoint_notify(void *cookie);
+ int recover();
+
+ private:
+ int log_one_transaction(my_xid xid);
+ void get_active_from_pool();
+ int sync();
+ int overflow();
+ int delete_entry(ulong cookie);
+};
+#else
+#define TC_LOG_MMAP TC_LOG_DUMMY
+#endif
+
+extern TC_LOG *tc_log;
+extern TC_LOG_MMAP tc_log_mmap;
+extern TC_LOG_DUMMY tc_log_dummy;
+
+/* log info errors */
+#define LOG_INFO_EOF -1
+#define LOG_INFO_IO -2
+#define LOG_INFO_INVALID -3
+#define LOG_INFO_SEEK -4
+#define LOG_INFO_MEM -6
+#define LOG_INFO_FATAL -7
+#define LOG_INFO_IN_USE -8
+#define LOG_INFO_EMFILE -9
+
+
+/* bitmap to SQL_LOG::close() */
+#define LOG_CLOSE_INDEX 1
+#define LOG_CLOSE_TO_BE_OPENED 2
+#define LOG_CLOSE_STOP_EVENT 4
+#define LOG_CLOSE_DELAYED_CLOSE 8
+
+/*
+ Maximum unique log filename extension.
+ Note: setting to 0x7FFFFFFF due to atol windows
+ overflow/truncate.
+ */
+#define MAX_LOG_UNIQUE_FN_EXT 0x7FFFFFFF
+
+/*
+ Number of warnings that will be printed to error log
+ before extension number is exhausted.
+*/
+#define LOG_WARN_UNIQUE_FN_EXT_LEFT 1000
+
+class Relay_log_info;
+
+#ifdef HAVE_PSI_INTERFACE
+extern PSI_mutex_key key_LOG_INFO_lock;
+#endif
+
+/*
+ Note that we destroy the lock mutex in the desctructor here.
+ This means that object instances cannot be destroyed/go out of scope,
+ until we have reset thd->current_linfo to NULL;
+ */
+typedef struct st_log_info
+{
+ char log_file_name[FN_REFLEN];
+ my_off_t index_file_offset, index_file_start_offset;
+ my_off_t pos;
+ bool fatal; // if the purge happens to give us a negative offset
+ mysql_mutex_t lock;
+ st_log_info() : index_file_offset(0), index_file_start_offset(0),
+ pos(0), fatal(0)
+ {
+ DBUG_ENTER("LOG_INFO");
+ log_file_name[0] = '\0';
+ mysql_mutex_init(key_LOG_INFO_lock, &lock, MY_MUTEX_INIT_FAST);
+ DBUG_VOID_RETURN;
+ }
+ ~st_log_info()
+ {
+ DBUG_ENTER("~LOG_INFO");
+ mysql_mutex_destroy(&lock);
+ DBUG_VOID_RETURN;
+ }
+} LOG_INFO;
+
+/*
+ Currently we have only 3 kinds of logging functions: old-fashioned
+ logs, stdout and csv logging routines.
+*/
+#define MAX_LOG_HANDLERS_NUM 3
+
+/* log event handler flags */
+#define LOG_NONE 1
+#define LOG_FILE 2
+#define LOG_TABLE 4
+
+class Log_event;
+class Rows_log_event;
+
+enum enum_log_type { LOG_UNKNOWN, LOG_NORMAL, LOG_BIN };
+enum enum_log_state { LOG_OPENED, LOG_CLOSED, LOG_TO_BE_OPENED };
+
+/*
+ TODO use mmap instead of IO_CACHE for binlog
+ (mmap+fsync is two times faster than write+fsync)
+*/
+
+class MYSQL_LOG
+{
+public:
+ MYSQL_LOG();
+ void init_pthread_objects();
+ void cleanup();
+ bool open(
+#ifdef HAVE_PSI_INTERFACE
+ PSI_file_key log_file_key,
+#endif
+ const char *log_name,
+ enum_log_type log_type,
+ const char *new_name,
+ enum cache_type io_cache_type_arg);
+ bool init_and_set_log_file_name(const char *log_name,
+ const char *new_name,
+ enum_log_type log_type_arg,
+ enum cache_type io_cache_type_arg);
+ void init(enum_log_type log_type_arg,
+ enum cache_type io_cache_type_arg);
+ void close(uint exiting);
+ inline bool is_open() { return log_state != LOG_CLOSED; }
+ const char *generate_name(const char *log_name, const char *suffix,
+ bool strip_ext, char *buff);
+ int generate_new_name(char *new_name, const char *log_name);
+ protected:
+ /* LOCK_log is inited by init_pthread_objects() */
+ mysql_mutex_t LOCK_log;
+ char *name;
+ char log_file_name[FN_REFLEN];
+ char time_buff[20], db[NAME_LEN + 1];
+ bool write_error, inited;
+ IO_CACHE log_file;
+ enum_log_type log_type;
+ volatile enum_log_state log_state;
+ enum cache_type io_cache_type;
+ friend class Log_event;
+#ifdef HAVE_PSI_INTERFACE
+ /** Instrumentation key to use for file io in @c log_file */
+ PSI_file_key m_log_file_key;
+#endif
+};
+
+class MYSQL_QUERY_LOG: public MYSQL_LOG
+{
+public:
+ MYSQL_QUERY_LOG() : last_time(0) {}
+ void reopen_file();
+ bool write(time_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len);
+ bool write(THD *thd, time_t current_time,
+ const char *user_host, uint user_host_len,
+ ulonglong query_utime, ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len);
+ bool open_slow_log(const char *log_name)
+ {
+ char buf[FN_REFLEN];
+ return open(
+#ifdef HAVE_PSI_INTERFACE
+ key_file_slow_log,
+#endif
+ generate_name(log_name, "-slow.log", 0, buf),
+ LOG_NORMAL, 0, WRITE_CACHE);
+ }
+ bool open_query_log(const char *log_name)
+ {
+ char buf[FN_REFLEN];
+ return open(
+#ifdef HAVE_PSI_INTERFACE
+ key_file_query_log,
+#endif
+ generate_name(log_name, ".log", 0, buf),
+ LOG_NORMAL, 0, WRITE_CACHE);
+ }
+
+private:
+ time_t last_time;
+};
+
+/*
+ We assign each binlog file an internal ID, used to identify them for unlog().
+ The IDs start from 0 and increment for each new binlog created.
+
+ In unlog() we need to know the ID of the binlog file that the corresponding
+ transaction was written into. We also need a special value for a corner
+ case where there is no corresponding binlog id (since nothing was logged).
+ And we need an error flag to mark that unlog() must return failure.
+
+ We use the following macros to pack all of this information into the single
+ ulong available with log_and_order() / unlog().
+
+ Note that we cannot use the value 0 for cookie, as that is reserved as error
+ return value from log_and_order().
+ */
+#define BINLOG_COOKIE_ERROR_RETURN 0
+#define BINLOG_COOKIE_DUMMY_ID 1
+#define BINLOG_COOKIE_BASE 2
+#define BINLOG_COOKIE_DUMMY(error_flag) \
+ ( (BINLOG_COOKIE_DUMMY_ID<<1) | ((error_flag)&1) )
+#define BINLOG_COOKIE_MAKE(id, error_flag) \
+ ( (((id)+BINLOG_COOKIE_BASE)<<1) | ((error_flag)&1) )
+#define BINLOG_COOKIE_GET_ERROR_FLAG(c) ((c) & 1)
+#define BINLOG_COOKIE_GET_ID(c) ( ((ulong)(c)>>1) - BINLOG_COOKIE_BASE )
+#define BINLOG_COOKIE_IS_DUMMY(c) \
+ ( ((ulong)(c)>>1) == BINLOG_COOKIE_DUMMY_ID )
+
+class binlog_cache_mngr;
+struct rpl_gtid;
+struct wait_for_commit;
+class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
+{
+ private:
+#ifdef HAVE_PSI_INTERFACE
+ /** The instrumentation key to use for @ LOCK_index. */
+ PSI_mutex_key m_key_LOCK_index;
+ /** The instrumentation key to use for @ update_cond. */
+ PSI_cond_key m_key_update_cond;
+ /** The instrumentation key to use for opening the log file. */
+ PSI_file_key m_key_file_log;
+ /** The instrumentation key to use for opening the log index file. */
+ PSI_file_key m_key_file_log_index;
+
+ PSI_file_key m_key_COND_queue_busy;
+#endif
+
+ struct group_commit_entry
+ {
+ struct group_commit_entry *next;
+ THD *thd;
+ binlog_cache_mngr *cache_mngr;
+ bool using_stmt_cache;
+ bool using_trx_cache;
+ /*
+ Extra events (COMMIT/ROLLBACK/XID, and possibly INCIDENT) to be
+ written during group commit. The incident_event is only valid if
+ trx_data->has_incident() is true.
+ */
+ Log_event *end_event;
+ Log_event *incident_event;
+ /* Set during group commit to record any per-thread error. */
+ int error;
+ int commit_errno;
+ IO_CACHE *error_cache;
+ /* This is the `all' parameter for ha_commit_ordered(). */
+ bool all;
+ /*
+ True if we need to increment xid_count in trx_group_commit_leader() and
+ decrement in unlog() (this is needed if there is a participating engine
+ that does not implement the commit_checkpoint_request() handlerton
+ method).
+ */
+ bool need_unlog;
+ /*
+ Fields used to pass the necessary information to the last thread in a
+ group commit, only used when opt_optimize_thread_scheduling is not set.
+ */
+ bool check_purge;
+ /* Flag used to optimise around wait_for_prior_commit. */
+ bool queued_by_other;
+ ulong binlog_id;
+ };
+
+ /*
+ When this is set, a RESET MASTER is in progress.
+
+ Then we should not write any binlog checkpoints into the binlog (that
+ could result in deadlock on LOCK_log, and we will delete all binlog files
+ anyway). Instead we should signal COND_xid_list whenever a new binlog
+ checkpoint arrives - when all have arrived, RESET MASTER will complete.
+ */
+ uint reset_master_pending;
+ ulong mark_xid_done_waiting;
+
+ /* LOCK_log and LOCK_index are inited by init_pthread_objects() */
+ mysql_mutex_t LOCK_index;
+ mysql_mutex_t LOCK_xid_list;
+ mysql_cond_t COND_xid_list;
+ mysql_cond_t update_cond;
+ ulonglong bytes_written;
+ IO_CACHE index_file;
+ char index_file_name[FN_REFLEN];
+ /*
+ purge_file is a temp file used in purge_logs so that the index file
+ can be updated before deleting files from disk, yielding better crash
+ recovery. It is created on demand the first time purge_logs is called
+ and then reused for subsequent calls. It is cleaned up in cleanup().
+ */
+ IO_CACHE purge_index_file;
+ char purge_index_file_name[FN_REFLEN];
+ /*
+ The max size before rotation (usable only if log_type == LOG_BIN: binary
+ logs and relay logs).
+ For a binlog, max_size should be max_binlog_size.
+ max_size is set in init(), and dynamically changed (when one does SET
+ GLOBAL MAX_BINLOG_SIZE|MAX_RELAY_LOG_SIZE) from sys_vars.cc
+ */
+ ulong max_size;
+ // current file sequence number for load data infile binary logging
+ uint file_id;
+ uint open_count; // For replication
+ int readers_count;
+ /* Queue of transactions queued up to participate in group commit. */
+ group_commit_entry *group_commit_queue;
+ /*
+ Condition variable to mark that the group commit queue is busy.
+ Used when each thread does it's own commit_ordered() (when
+ binlog_optimize_thread_scheduling=1).
+ Used with the LOCK_commit_ordered mutex.
+ */
+ my_bool group_commit_queue_busy;
+ mysql_cond_t COND_queue_busy;
+ /* Total number of committed transactions. */
+ ulonglong num_commits;
+ /* Number of group commits done. */
+ ulonglong num_group_commits;
+ /* The reason why the group commit was grouped */
+ ulonglong group_commit_trigger_count, group_commit_trigger_timeout;
+ ulonglong group_commit_trigger_lock_wait;
+
+ /* pointer to the sync period variable, for binlog this will be
+ sync_binlog_period, for relay log this will be
+ sync_relay_log_period
+ */
+ uint *sync_period_ptr;
+ uint sync_counter;
+ bool state_file_deleted;
+ bool binlog_state_recover_done;
+
+ inline uint get_sync_period()
+ {
+ return *sync_period_ptr;
+ }
+
+ int write_to_file(IO_CACHE *cache);
+ /*
+ This is used to start writing to a new log file. The difference from
+ new_file() is locking. new_file_without_locking() does not acquire
+ LOCK_log.
+ */
+ int new_file_without_locking();
+ int new_file_impl(bool need_lock);
+ void do_checkpoint_request(ulong binlog_id);
+ void purge();
+ int write_transaction_or_stmt(group_commit_entry *entry, uint64 commit_id);
+ int queue_for_group_commit(group_commit_entry *entry);
+ bool write_transaction_to_binlog_events(group_commit_entry *entry);
+ void trx_group_commit_leader(group_commit_entry *leader);
+ bool is_xidlist_idle_nolock();
+
+public:
+ /*
+ A list of struct xid_count_per_binlog is used to keep track of how many
+ XIDs are in prepared, but not committed, state in each binlog. And how
+ many commit_checkpoint_request()'s are pending.
+
+ When count drops to zero in a binlog after rotation, it means that there
+ are no more XIDs in prepared state, so that binlog is no longer needed
+ for XA crash recovery, and we can log a new binlog checkpoint event.
+
+ The list is protected against simultaneous access from multiple
+ threads by LOCK_xid_list.
+ */
+ struct xid_count_per_binlog : public ilink {
+ char *binlog_name;
+ uint binlog_name_len;
+ ulong binlog_id;
+ /* Total prepared XIDs and pending checkpoint requests in this binlog. */
+ long xid_count;
+ /* For linking in requests to the binlog background thread. */
+ xid_count_per_binlog *next_in_queue;
+ xid_count_per_binlog(); /* Give link error if constructor used. */
+ };
+ I_List<xid_count_per_binlog> binlog_xid_count_list;
+ mysql_mutex_t LOCK_binlog_background_thread;
+ mysql_cond_t COND_binlog_background_thread;
+ mysql_cond_t COND_binlog_background_thread_end;
+
+ using MYSQL_LOG::generate_name;
+ using MYSQL_LOG::is_open;
+
+ /* This is relay log */
+ bool is_relay_log;
+ ulong signal_cnt; // update of the counter is checked by heartbeat
+ uint8 checksum_alg_reset; // to contain a new value when binlog is rotated
+ /*
+ Holds the last seen in Relay-Log FD's checksum alg value.
+ The initial value comes from the slave's local FD that heads
+ the very first Relay-Log file. In the following the value may change
+ with each received master's FD_m.
+ Besides to be used in verification events that IO thread receives
+ (except the 1st fake Rotate, see @c Master_info:: checksum_alg_before_fd),
+ the value specifies if/how to compute checksum for slave's local events
+ and the first fake Rotate (R_f^1) coming from the master.
+ R_f^1 needs logging checksum-compatibly with the RL's heading FD_s.
+
+ Legends for the checksum related comments:
+
+ FD - Format-Description event,
+ R - Rotate event
+ R_f - the fake Rotate event
+ E - an arbirary event
+
+ The underscore indexes for any event
+ `_s' indicates the event is generated by Slave
+ `_m' - by Master
+
+ Two special underscore indexes of FD:
+ FD_q - Format Description event for queuing (relay-logging)
+ FD_e - Format Description event for executing (relay-logging)
+
+ Upper indexes:
+ E^n - n:th event is a sequence
+
+ RL - Relay Log
+ (A) - checksum algorithm descriptor value
+ FD.(A) - the value of (A) in FD
+ */
+ uint8 relay_log_checksum_alg;
+ /*
+ These describe the log's format. This is used only for relay logs.
+ _for_exec is used by the SQL thread, _for_queue by the I/O thread. It's
+ necessary to have 2 distinct objects, because the I/O thread may be reading
+ events in a different format from what the SQL thread is reading (consider
+ the case of a master which has been upgraded from 5.0 to 5.1 without doing
+ RESET MASTER, or from 4.x to 5.0).
+ */
+ Format_description_log_event *description_event_for_exec,
+ *description_event_for_queue;
+ /*
+ Binlog position of last commit (or non-transactional write) to the binlog.
+ Access to this is protected by LOCK_commit_ordered.
+ */
+ char last_commit_pos_file[FN_REFLEN];
+ my_off_t last_commit_pos_offset;
+ ulong current_binlog_id;
+
+ MYSQL_BIN_LOG(uint *sync_period);
+ /*
+ note that there's no destructor ~MYSQL_BIN_LOG() !
+ The reason is that we don't want it to be automatically called
+ on exit() - but only during the correct shutdown process
+ */
+
+#ifdef HAVE_PSI_INTERFACE
+ void set_psi_keys(PSI_mutex_key key_LOCK_index,
+ PSI_cond_key key_update_cond,
+ PSI_file_key key_file_log,
+ PSI_file_key key_file_log_index,
+ PSI_file_key key_COND_queue_busy)
+ {
+ m_key_LOCK_index= key_LOCK_index;
+ m_key_update_cond= key_update_cond;
+ m_key_file_log= key_file_log;
+ m_key_file_log_index= key_file_log_index;
+ m_key_COND_queue_busy= key_COND_queue_busy;
+ }
+#endif
+
+ int open(const char *opt_name);
+ void close();
+ int log_and_order(THD *thd, my_xid xid, bool all,
+ bool need_prepare_ordered, bool need_commit_ordered);
+ int unlog(ulong cookie, my_xid xid);
+ void commit_checkpoint_notify(void *cookie);
+ int recover(LOG_INFO *linfo, const char *last_log_name, IO_CACHE *first_log,
+ Format_description_log_event *fdle, bool do_xa);
+ int do_binlog_recovery(const char *opt_name, bool do_xa_recovery);
+#if !defined(MYSQL_CLIENT)
+
+ int flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event,
+ bool is_transactional);
+ int remove_pending_rows_event(THD *thd, bool is_transactional);
+
+#endif /* !defined(MYSQL_CLIENT) */
+ void reset_bytes_written()
+ {
+ bytes_written = 0;
+ }
+ void harvest_bytes_written(ulonglong* counter)
+ {
+#ifndef DBUG_OFF
+ char buf1[22],buf2[22];
+#endif
+ DBUG_ENTER("harvest_bytes_written");
+ (*counter)+=bytes_written;
+ DBUG_PRINT("info",("counter: %s bytes_written: %s", llstr(*counter,buf1),
+ llstr(bytes_written,buf2)));
+ bytes_written=0;
+ DBUG_VOID_RETURN;
+ }
+ void set_max_size(ulong max_size_arg);
+ void signal_update();
+ void wait_for_sufficient_commits();
+ void binlog_trigger_immediate_group_commit();
+ void wait_for_update_relay_log(THD* thd);
+ int wait_for_update_bin_log(THD* thd, const struct timespec * timeout);
+ void init(ulong max_size);
+ void init_pthread_objects();
+ void cleanup();
+ bool open(const char *log_name,
+ enum_log_type log_type,
+ const char *new_name,
+ enum cache_type io_cache_type_arg,
+ ulong max_size,
+ bool null_created,
+ bool need_mutex);
+ bool open_index_file(const char *index_file_name_arg,
+ const char *log_name, bool need_mutex);
+ /* Use this to start writing a new log file */
+ int new_file();
+
+ bool write(Log_event* event_info,
+ my_bool *with_annotate= 0); // binary log write
+ bool write_transaction_to_binlog(THD *thd, binlog_cache_mngr *cache_mngr,
+ Log_event *end_ev, bool all,
+ bool using_stmt_cache, bool using_trx_cache);
+
+ bool write_incident_already_locked(THD *thd);
+ bool write_incident(THD *thd);
+ void write_binlog_checkpoint_event_already_locked(const char *name, uint len);
+ int write_cache(THD *thd, IO_CACHE *cache);
+ void set_write_error(THD *thd, bool is_transactional);
+ bool check_write_error(THD *thd);
+
+ void start_union_events(THD *thd, query_id_t query_id_param);
+ void stop_union_events(THD *thd);
+ bool is_query_in_union(THD *thd, query_id_t query_id_param);
+
+ /*
+ v stands for vector
+ invoked as appendv(buf1,len1,buf2,len2,...,bufn,lenn,0)
+ */
+ bool appendv(const char* buf,uint len,...);
+ bool append(Log_event* ev);
+ bool append_no_lock(Log_event* ev);
+
+ void mark_xids_active(ulong cookie, uint xid_count);
+ void mark_xid_done(ulong cookie, bool write_checkpoint);
+ void make_log_name(char* buf, const char* log_ident);
+ bool is_active(const char* log_file_name);
+ bool can_purge_log(const char *log_file_name);
+ int update_log_index(LOG_INFO* linfo, bool need_update_threads);
+ int rotate(bool force_rotate, bool* check_purge);
+ void checkpoint_and_purge(ulong binlog_id);
+ int rotate_and_purge(bool force_rotate);
+ /**
+ Flush binlog cache and synchronize to disk.
+
+ This function flushes events in binlog cache to binary log file,
+ it will do synchronizing according to the setting of system
+ variable 'sync_binlog'. If file is synchronized, @c synced will
+ be set to 1, otherwise 0.
+
+ @param[out] synced if not NULL, set to 1 if file is synchronized, otherwise 0
+
+ @retval 0 Success
+ @retval other Failure
+ */
+ bool flush_and_sync(bool *synced);
+ int purge_logs(const char *to_log, bool included,
+ bool need_mutex, bool need_update_threads,
+ ulonglong *decrease_log_space);
+ int purge_logs_before_date(time_t purge_time);
+ int purge_first_log(Relay_log_info* rli, bool included);
+ int set_purge_index_file_name(const char *base_file_name);
+ int open_purge_index_file(bool destroy);
+ bool is_inited_purge_index_file();
+ int close_purge_index_file();
+ int clean_purge_index_file();
+ int sync_purge_index_file();
+ int register_purge_index_entry(const char* entry);
+ int register_create_index_entry(const char* entry);
+ int purge_index_entry(THD *thd, ulonglong *decrease_log_space,
+ bool need_mutex);
+ bool reset_logs(THD* thd, bool create_new_log,
+ rpl_gtid *init_state, uint32 init_state_len);
+ void close(uint exiting);
+ void clear_inuse_flag_when_closing(File file);
+
+ // iterating through the log index file
+ int find_log_pos(LOG_INFO* linfo, const char* log_name,
+ bool need_mutex);
+ int find_next_log(LOG_INFO* linfo, bool need_mutex);
+ int get_current_log(LOG_INFO* linfo);
+ int raw_get_current_log(LOG_INFO* linfo);
+ uint next_file_id();
+ inline char* get_index_fname() { return index_file_name;}
+ inline char* get_log_fname() { return log_file_name; }
+ inline char* get_name() { return name; }
+ inline mysql_mutex_t* get_log_lock() { return &LOCK_log; }
+ inline mysql_cond_t* get_log_cond() { return &update_cond; }
+ inline IO_CACHE* get_log_file() { return &log_file; }
+
+ inline void lock_index() { mysql_mutex_lock(&LOCK_index);}
+ inline void unlock_index() { mysql_mutex_unlock(&LOCK_index);}
+ inline IO_CACHE *get_index_file() { return &index_file;}
+ inline uint32 get_open_count() { return open_count; }
+ void set_status_variables(THD *thd);
+ bool is_xidlist_idle();
+ bool write_gtid_event(THD *thd, bool standalone, bool is_transactional,
+ uint64 commit_id);
+ int read_state_from_file();
+ int write_state_to_file();
+ int get_most_recent_gtid_list(rpl_gtid **list, uint32 *size);
+ bool append_state_pos(String *str);
+ bool append_state(String *str);
+ bool is_empty_state();
+ bool find_in_binlog_state(uint32 domain_id, uint32 server_id,
+ rpl_gtid *out_gtid);
+ bool lookup_domain_in_binlog_state(uint32 domain_id, rpl_gtid *out_gtid);
+ int bump_seq_no_counter_if_needed(uint32 domain_id, uint64 seq_no);
+ bool check_strict_gtid_sequence(uint32 domain_id, uint32 server_id,
+ uint64 seq_no);
+};
+
+class Log_event_handler
+{
+public:
+ Log_event_handler() {}
+ virtual bool init()= 0;
+ virtual void cleanup()= 0;
+
+ virtual bool log_slow(THD *thd, my_hrtime_t current_time,
+ const char *user_host,
+ uint user_host_len, ulonglong query_utime,
+ ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len)= 0;
+ virtual bool log_error(enum loglevel level, const char *format,
+ va_list args)= 0;
+ virtual bool log_general(THD *thd, my_hrtime_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len,
+ CHARSET_INFO *client_cs)= 0;
+ virtual ~Log_event_handler() {}
+};
+
+
+int check_if_log_table(const TABLE_LIST *table, bool check_if_opened,
+ const char *errmsg);
+
+class Log_to_csv_event_handler: public Log_event_handler
+{
+ friend class LOGGER;
+
+public:
+ Log_to_csv_event_handler();
+ ~Log_to_csv_event_handler();
+ virtual bool init();
+ virtual void cleanup();
+
+ virtual bool log_slow(THD *thd, my_hrtime_t current_time,
+ const char *user_host,
+ uint user_host_len, ulonglong query_utime,
+ ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len);
+ virtual bool log_error(enum loglevel level, const char *format,
+ va_list args);
+ virtual bool log_general(THD *thd, my_hrtime_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len,
+ CHARSET_INFO *client_cs);
+
+ int activate_log(THD *thd, uint log_type);
+};
+
+
+/* type of the log table */
+#define QUERY_LOG_SLOW 1
+#define QUERY_LOG_GENERAL 2
+
+class Log_to_file_event_handler: public Log_event_handler
+{
+ MYSQL_QUERY_LOG mysql_log;
+ MYSQL_QUERY_LOG mysql_slow_log;
+ bool is_initialized;
+public:
+ Log_to_file_event_handler(): is_initialized(FALSE)
+ {}
+ virtual bool init();
+ virtual void cleanup();
+
+ virtual bool log_slow(THD *thd, my_hrtime_t current_time,
+ const char *user_host,
+ uint user_host_len, ulonglong query_utime,
+ ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len);
+ virtual bool log_error(enum loglevel level, const char *format,
+ va_list args);
+ virtual bool log_general(THD *thd, my_hrtime_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len,
+ CHARSET_INFO *client_cs);
+ void flush();
+ void init_pthread_objects();
+ MYSQL_QUERY_LOG *get_mysql_slow_log() { return &mysql_slow_log; }
+ MYSQL_QUERY_LOG *get_mysql_log() { return &mysql_log; }
+};
+
+
+/* Class which manages slow, general and error log event handlers */
+class LOGGER
+{
+ mysql_rwlock_t LOCK_logger;
+ /* flag to check whether logger mutex is initialized */
+ uint inited;
+
+ /* available log handlers */
+ Log_to_csv_event_handler *table_log_handler;
+ Log_to_file_event_handler *file_log_handler;
+
+ /* NULL-terminated arrays of log handlers */
+ Log_event_handler *error_log_handler_list[MAX_LOG_HANDLERS_NUM + 1];
+ Log_event_handler *slow_log_handler_list[MAX_LOG_HANDLERS_NUM + 1];
+ Log_event_handler *general_log_handler_list[MAX_LOG_HANDLERS_NUM + 1];
+
+public:
+
+ bool is_log_tables_initialized;
+
+ LOGGER() : inited(0), table_log_handler(NULL),
+ file_log_handler(NULL), is_log_tables_initialized(FALSE)
+ {}
+ void lock_shared() { mysql_rwlock_rdlock(&LOCK_logger); }
+ void lock_exclusive() { mysql_rwlock_wrlock(&LOCK_logger); }
+ void unlock() { mysql_rwlock_unlock(&LOCK_logger); }
+ bool is_log_table_enabled(uint log_table_type);
+ bool log_command(THD *thd, enum enum_server_command command);
+
+ /*
+ We want to initialize all log mutexes as soon as possible,
+ but we cannot do it in constructor, as safe_mutex relies on
+ initialization, performed by MY_INIT(). This why this is done in
+ this function.
+ */
+ void init_base();
+ void init_log_tables();
+ bool flush_logs(THD *thd);
+ bool flush_slow_log();
+ bool flush_general_log();
+ /* Perform basic logger cleanup. this will leave e.g. error log open. */
+ void cleanup_base();
+ /* Free memory. Nothing could be logged after this function is called */
+ void cleanup_end();
+ bool error_log_print(enum loglevel level, const char *format,
+ va_list args);
+ bool slow_log_print(THD *thd, const char *query, uint query_length,
+ ulonglong current_utime);
+ bool general_log_print(THD *thd,enum enum_server_command command,
+ const char *format, va_list args);
+ bool general_log_write(THD *thd, enum enum_server_command command,
+ const char *query, uint query_length);
+
+ /* we use this function to setup all enabled log event handlers */
+ int set_handlers(ulonglong error_log_printer,
+ ulonglong slow_log_printer,
+ ulonglong general_log_printer);
+ void init_error_log(ulonglong error_log_printer);
+ void init_slow_log(ulonglong slow_log_printer);
+ void init_general_log(ulonglong general_log_printer);
+ void deactivate_log_handler(THD* thd, uint log_type);
+ bool activate_log_handler(THD* thd, uint log_type);
+ MYSQL_QUERY_LOG *get_slow_log_file_handler() const
+ {
+ if (file_log_handler)
+ return file_log_handler->get_mysql_slow_log();
+ return NULL;
+ }
+ MYSQL_QUERY_LOG *get_log_file_handler() const
+ {
+ if (file_log_handler)
+ return file_log_handler->get_mysql_log();
+ return NULL;
+ }
+};
+
+enum enum_binlog_format {
+ BINLOG_FORMAT_MIXED= 0, ///< statement if safe, otherwise row - autodetected
+ BINLOG_FORMAT_STMT= 1, ///< statement-based
+ BINLOG_FORMAT_ROW= 2, ///< row-based
+ BINLOG_FORMAT_UNSPEC=3 ///< thd_binlog_format() returns it when binlog is closed
+};
+
+int query_error_code(THD *thd, bool not_killed);
+uint purge_log_get_error_code(int res);
+
+int vprint_msg_to_log(enum loglevel level, const char *format, va_list args);
+void sql_print_error(const char *format, ...);
+void sql_print_warning(const char *format, ...) ATTRIBUTE_FORMAT(printf, 1, 2);
+void sql_print_information(const char *format, ...)
+ ATTRIBUTE_FORMAT(printf, 1, 2);
+typedef void (*sql_print_message_func)(const char *format, ...)
+ ATTRIBUTE_FORMAT_FPTR(printf, 1, 2);
+extern sql_print_message_func sql_print_message_handlers[];
+
+int error_log_print(enum loglevel level, const char *format,
+ va_list args);
+
+bool slow_log_print(THD *thd, const char *query, uint query_length,
+ ulonglong current_utime);
+
+bool general_log_print(THD *thd, enum enum_server_command command,
+ const char *format,...);
+
+bool general_log_write(THD *thd, enum enum_server_command command,
+ const char *query, uint query_length);
+
+void binlog_report_wait_for(THD *thd, THD *other_thd);
+void sql_perror(const char *message);
+bool flush_error_log();
+
+File open_binlog(IO_CACHE *log, const char *log_file_name,
+ const char **errmsg);
+
+void make_default_log_name(char **out, const char* log_ext, bool once);
+void binlog_reset_cache(THD *thd);
+
+extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log;
+extern LOGGER logger;
+
+
+/**
+ Turns a relative log binary log path into a full path, based on the
+ opt_bin_logname or opt_relay_logname.
+
+ @param from The log name we want to make into an absolute path.
+ @param to The buffer where to put the results of the
+ normalization.
+ @param is_relay_log Switch that makes is used inside to choose which
+ option (opt_bin_logname or opt_relay_logname) to
+ use when calculating the base path.
+
+ @returns true if a problem occurs, false otherwise.
+ */
+
+inline bool normalize_binlog_name(char *to, const char *from, bool is_relay_log)
+{
+ DBUG_ENTER("normalize_binlog_name");
+ bool error= false;
+ char buff[FN_REFLEN];
+ char *ptr= (char*) from;
+ char *opt_name= is_relay_log ? opt_relay_logname : opt_bin_logname;
+
+ DBUG_ASSERT(from);
+
+ /* opt_name is not null and not empty and from is a relative path */
+ if (opt_name && opt_name[0] && from && !test_if_hard_path(from))
+ {
+ // take the path from opt_name
+ // take the filename from from
+ char log_dirpart[FN_REFLEN], log_dirname[FN_REFLEN];
+ size_t log_dirpart_len, log_dirname_len;
+ dirname_part(log_dirpart, opt_name, &log_dirpart_len);
+ dirname_part(log_dirname, from, &log_dirname_len);
+
+ /* log may be empty => relay-log or log-bin did not
+ hold paths, just filename pattern */
+ if (log_dirpart_len > 0)
+ {
+ /* create the new path name */
+ if(fn_format(buff, from+log_dirname_len, log_dirpart, "",
+ MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH)) == NULL)
+ {
+ error= true;
+ goto end;
+ }
+
+ ptr= buff;
+ }
+ }
+
+ DBUG_ASSERT(ptr);
+
+ if (ptr)
+ strmake(to, ptr, strlen(ptr));
+
+end:
+ DBUG_RETURN(error);
+}
+
+static inline TC_LOG *get_tc_log_implementation()
+{
+ if (total_ha_2pc <= 1)
+ return &tc_log_dummy;
+ if (opt_bin_log)
+ return &mysql_bin_log;
+ return &tc_log_mmap;
+}
+
+#endif /* LOG_H */
diff --git a/sql/log_event.cc b/sql/log_event.cc
new file mode 100644
index 00000000000..29cb10c0abf
--- /dev/null
+++ b/sql/log_event.cc
@@ -0,0 +1,12703 @@
+/*
+ Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, Monty Program Ab.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "mysqld_error.h"
+
+#ifndef MYSQL_CLIENT
+#include "unireg.h"
+#include "log_event.h"
+#include "sql_base.h" // close_thread_tables
+#include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE
+#include "sql_locale.h" // MY_LOCALE, my_locale_by_number, my_locale_en_US
+#include "key.h" // key_copy
+#include "lock.h" // mysql_unlock_tables
+#include "sql_parse.h" // mysql_test_parse_for_slave
+#include "tztime.h" // struct Time_zone
+#include "sql_load.h" // mysql_load
+#include "sql_db.h" // load_db_opt_by_name
+#include "slave.h"
+#include "rpl_rli.h"
+#include "rpl_mi.h"
+#include "rpl_filter.h"
+#include "rpl_record.h"
+#include "transaction.h"
+#include <my_dir.h>
+#include "sql_show.h" // append_identifier
+#include <mysql/psi/mysql_statement.h>
+#include <strfunc.h>
+#include "compat56.h"
+
+#endif /* MYSQL_CLIENT */
+
+#include <base64.h>
+#include <my_bitmap.h>
+#include "rpl_utility.h"
+#include "sql_digest.h"
+
+#define my_b_write_string(A, B) my_b_write((A), (B), (uint) (sizeof(B) - 1))
+
+using std::max;
+
+/**
+ BINLOG_CHECKSUM variable.
+*/
+const char *binlog_checksum_type_names[]= {
+ "NONE",
+ "CRC32",
+ NullS
+};
+
+unsigned int binlog_checksum_type_length[]= {
+ sizeof("NONE") - 1,
+ sizeof("CRC32") - 1,
+ 0
+};
+
+TYPELIB binlog_checksum_typelib=
+{
+ array_elements(binlog_checksum_type_names) - 1, "",
+ binlog_checksum_type_names,
+ binlog_checksum_type_length
+};
+
+
+
+#define log_cs &my_charset_latin1
+
+#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
+
+/*
+ Size of buffer for printing a double in format %.<PREC>g
+
+ optional '-' + optional zero + '.' + PREC digits + 'e' + sign +
+ exponent digits + '\0'
+*/
+#define FMT_G_BUFSIZE(PREC) (3 + (PREC) + 5 + 1)
+
+/*
+ replication event checksum is introduced in the following "checksum-home" version.
+ The checksum-aware servers extract FD's version to decide whether the FD event
+ carries checksum info.
+
+ TODO: correct the constant when it has been determined
+ (which main tree to push and when)
+*/
+const uchar checksum_version_split_mysql[3]= {5, 6, 1};
+const ulong checksum_version_product_mysql=
+ (checksum_version_split_mysql[0] * 256 +
+ checksum_version_split_mysql[1]) * 256 +
+ checksum_version_split_mysql[2];
+const uchar checksum_version_split_mariadb[3]= {5, 3, 0};
+const ulong checksum_version_product_mariadb=
+ (checksum_version_split_mariadb[0] * 256 +
+ checksum_version_split_mariadb[1]) * 256 +
+ checksum_version_split_mariadb[2];
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+static int rows_event_stmt_cleanup(rpl_group_info *rgi, THD* thd);
+
+static const char *HA_ERR(int i)
+{
+ /*
+ This function should only be called in case of an error
+ was detected
+ */
+ DBUG_ASSERT(i != 0);
+ switch (i) {
+ case HA_ERR_KEY_NOT_FOUND: return "HA_ERR_KEY_NOT_FOUND";
+ case HA_ERR_FOUND_DUPP_KEY: return "HA_ERR_FOUND_DUPP_KEY";
+ case HA_ERR_RECORD_CHANGED: return "HA_ERR_RECORD_CHANGED";
+ case HA_ERR_WRONG_INDEX: return "HA_ERR_WRONG_INDEX";
+ case HA_ERR_CRASHED: return "HA_ERR_CRASHED";
+ case HA_ERR_WRONG_IN_RECORD: return "HA_ERR_WRONG_IN_RECORD";
+ case HA_ERR_OUT_OF_MEM: return "HA_ERR_OUT_OF_MEM";
+ case HA_ERR_NOT_A_TABLE: return "HA_ERR_NOT_A_TABLE";
+ case HA_ERR_WRONG_COMMAND: return "HA_ERR_WRONG_COMMAND";
+ case HA_ERR_OLD_FILE: return "HA_ERR_OLD_FILE";
+ case HA_ERR_NO_ACTIVE_RECORD: return "HA_ERR_NO_ACTIVE_RECORD";
+ case HA_ERR_RECORD_DELETED: return "HA_ERR_RECORD_DELETED";
+ case HA_ERR_RECORD_FILE_FULL: return "HA_ERR_RECORD_FILE_FULL";
+ case HA_ERR_INDEX_FILE_FULL: return "HA_ERR_INDEX_FILE_FULL";
+ case HA_ERR_END_OF_FILE: return "HA_ERR_END_OF_FILE";
+ case HA_ERR_UNSUPPORTED: return "HA_ERR_UNSUPPORTED";
+ case HA_ERR_TO_BIG_ROW: return "HA_ERR_TO_BIG_ROW";
+ case HA_WRONG_CREATE_OPTION: return "HA_WRONG_CREATE_OPTION";
+ case HA_ERR_FOUND_DUPP_UNIQUE: return "HA_ERR_FOUND_DUPP_UNIQUE";
+ case HA_ERR_UNKNOWN_CHARSET: return "HA_ERR_UNKNOWN_CHARSET";
+ case HA_ERR_WRONG_MRG_TABLE_DEF: return "HA_ERR_WRONG_MRG_TABLE_DEF";
+ case HA_ERR_CRASHED_ON_REPAIR: return "HA_ERR_CRASHED_ON_REPAIR";
+ case HA_ERR_CRASHED_ON_USAGE: return "HA_ERR_CRASHED_ON_USAGE";
+ case HA_ERR_LOCK_WAIT_TIMEOUT: return "HA_ERR_LOCK_WAIT_TIMEOUT";
+ case HA_ERR_LOCK_TABLE_FULL: return "HA_ERR_LOCK_TABLE_FULL";
+ case HA_ERR_READ_ONLY_TRANSACTION: return "HA_ERR_READ_ONLY_TRANSACTION";
+ case HA_ERR_LOCK_DEADLOCK: return "HA_ERR_LOCK_DEADLOCK";
+ case HA_ERR_CANNOT_ADD_FOREIGN: return "HA_ERR_CANNOT_ADD_FOREIGN";
+ case HA_ERR_NO_REFERENCED_ROW: return "HA_ERR_NO_REFERENCED_ROW";
+ case HA_ERR_ROW_IS_REFERENCED: return "HA_ERR_ROW_IS_REFERENCED";
+ case HA_ERR_NO_SAVEPOINT: return "HA_ERR_NO_SAVEPOINT";
+ case HA_ERR_NON_UNIQUE_BLOCK_SIZE: return "HA_ERR_NON_UNIQUE_BLOCK_SIZE";
+ case HA_ERR_NO_SUCH_TABLE: return "HA_ERR_NO_SUCH_TABLE";
+ case HA_ERR_TABLE_EXIST: return "HA_ERR_TABLE_EXIST";
+ case HA_ERR_NO_CONNECTION: return "HA_ERR_NO_CONNECTION";
+ case HA_ERR_NULL_IN_SPATIAL: return "HA_ERR_NULL_IN_SPATIAL";
+ case HA_ERR_TABLE_DEF_CHANGED: return "HA_ERR_TABLE_DEF_CHANGED";
+ case HA_ERR_NO_PARTITION_FOUND: return "HA_ERR_NO_PARTITION_FOUND";
+ case HA_ERR_RBR_LOGGING_FAILED: return "HA_ERR_RBR_LOGGING_FAILED";
+ case HA_ERR_DROP_INDEX_FK: return "HA_ERR_DROP_INDEX_FK";
+ case HA_ERR_FOREIGN_DUPLICATE_KEY: return "HA_ERR_FOREIGN_DUPLICATE_KEY";
+ case HA_ERR_TABLE_NEEDS_UPGRADE: return "HA_ERR_TABLE_NEEDS_UPGRADE";
+ case HA_ERR_TABLE_READONLY: return "HA_ERR_TABLE_READONLY";
+ case HA_ERR_AUTOINC_READ_FAILED: return "HA_ERR_AUTOINC_READ_FAILED";
+ case HA_ERR_AUTOINC_ERANGE: return "HA_ERR_AUTOINC_ERANGE";
+ case HA_ERR_GENERIC: return "HA_ERR_GENERIC";
+ case HA_ERR_RECORD_IS_THE_SAME: return "HA_ERR_RECORD_IS_THE_SAME";
+ case HA_ERR_LOGGING_IMPOSSIBLE: return "HA_ERR_LOGGING_IMPOSSIBLE";
+ case HA_ERR_CORRUPT_EVENT: return "HA_ERR_CORRUPT_EVENT";
+ case HA_ERR_ROWS_EVENT_APPLY : return "HA_ERR_ROWS_EVENT_APPLY";
+ }
+ return "No Error!";
+}
+
+
+/*
+ Return true if an error caught during event execution is a temporary error
+ that will cause automatic retry of the event group during parallel
+ replication, false otherwise.
+
+ In parallel replication, conflicting transactions can occasionally cause
+ deadlocks; such errors are handled automatically by rolling back re-trying
+ the transactions, so should not pollute the error log.
+*/
+static bool
+is_parallel_retry_error(rpl_group_info *rgi, int err)
+{
+ if (!rgi->is_parallel_exec)
+ return false;
+ if (rgi->killed_for_retry &&
+ (err == ER_QUERY_INTERRUPTED || err == ER_CONNECTION_KILLED))
+ return true;
+ return has_temporary_error(rgi->thd);
+}
+
+
+/**
+ Error reporting facility for Rows_log_event::do_apply_event
+
+ @param level error, warning or info
+ @param ha_error HA_ERR_ code
+ @param rli pointer to the active Relay_log_info instance
+ @param thd pointer to the slave thread's thd
+ @param table pointer to the event's table object
+ @param type the type of the event
+ @param log_name the master binlog file name
+ @param pos the master binlog file pos (the next after the event)
+
+*/
+static void inline slave_rows_error_report(enum loglevel level, int ha_error,
+ rpl_group_info *rgi, THD *thd,
+ TABLE *table, const char * type,
+ const char *log_name, ulong pos)
+{
+ const char *handler_error= (ha_error ? HA_ERR(ha_error) : NULL);
+ char buff[MAX_SLAVE_ERRMSG], *slider;
+ const char *buff_end= buff + sizeof(buff);
+ uint len;
+ Diagnostics_area::Sql_condition_iterator it=
+ thd->get_stmt_da()->sql_conditions();
+ Relay_log_info const *rli= rgi->rli;
+ const Sql_condition *err;
+ buff[0]= 0;
+ int errcode= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0;
+
+ /*
+ In parallel replication, deadlocks or other temporary errors can happen
+ occasionally in normal operation, they will be handled correctly and
+ automatically by re-trying the transactions. So do not pollute the error
+ log with messages about them.
+ */
+ if (is_parallel_retry_error(rgi, errcode))
+ return;
+
+ for (err= it++, slider= buff; err && slider < buff_end - 1;
+ slider += len, err= it++)
+ {
+ len= my_snprintf(slider, buff_end - slider,
+ " %s, Error_code: %d;", err->get_message_text(),
+ err->get_sql_errno());
+ }
+
+ if (ha_error != 0)
+ rli->report(level, errcode, rgi->gtid_info(),
+ "Could not execute %s event on table %s.%s;"
+ "%s handler error %s; "
+ "the event's master log %s, end_log_pos %lu",
+ type, table->s->db.str, table->s->table_name.str,
+ buff, handler_error == NULL ? "<unknown>" : handler_error,
+ log_name, pos);
+ else
+ rli->report(level, errcode, rgi->gtid_info(),
+ "Could not execute %s event on table %s.%s;"
+ "%s the event's master log %s, end_log_pos %lu",
+ type, table->s->db.str, table->s->table_name.str,
+ buff, log_name, pos);
+}
+#endif
+
+/*
+ Cache that will automatically be written to a dedicated file on
+ destruction.
+
+ DESCRIPTION
+
+ */
+class Write_on_release_cache
+{
+public:
+ enum flag
+ {
+ FLUSH_F
+ };
+
+ typedef unsigned short flag_set;
+
+ /*
+ Constructor.
+
+ SYNOPSIS
+ Write_on_release_cache
+ cache Pointer to cache to use
+ file File to write cache to upon destruction
+ flags Flags for the cache
+
+ DESCRIPTION
+
+ Class used to guarantee copy of cache to file before exiting the
+ current block. On successful copy of the cache, the cache will
+ be reinited as a WRITE_CACHE.
+
+ Currently, a pointer to the cache is provided in the
+ constructor, but it would be possible to create a subclass
+ holding the IO_CACHE itself.
+ */
+ Write_on_release_cache(IO_CACHE *cache, FILE *file, flag_set flags = 0)
+ : m_cache(cache), m_file(file), m_flags(flags)
+ {
+ reinit_io_cache(m_cache, WRITE_CACHE, 0L, FALSE, TRUE);
+ }
+
+ ~Write_on_release_cache()
+ {
+ copy_event_cache_to_file_and_reinit(m_cache, m_file);
+ if (m_flags | FLUSH_F)
+ fflush(m_file);
+ }
+
+ /*
+ Return a pointer to the internal IO_CACHE.
+
+ SYNOPSIS
+ operator&()
+
+ DESCRIPTION
+
+ Function to return a pointer to the internal cache, so that the
+ object can be treated as a IO_CACHE and used with the my_b_*
+ IO_CACHE functions
+
+ RETURN VALUE
+ A pointer to the internal IO_CACHE.
+ */
+ IO_CACHE *operator&()
+ {
+ return m_cache;
+ }
+
+private:
+ // Hidden, to prevent usage.
+ Write_on_release_cache(Write_on_release_cache const&);
+
+ IO_CACHE *m_cache;
+ FILE *m_file;
+ flag_set m_flags;
+};
+
+/*
+ pretty_print_str()
+*/
+
+#ifdef MYSQL_CLIENT
+static void pretty_print_str(IO_CACHE* cache, const char* str, int len)
+{
+ const char* end = str + len;
+ my_b_write_byte(cache, '\'');
+ while (str < end)
+ {
+ char c;
+ switch ((c=*str++)) {
+ case '\n': my_b_write(cache, "\\n", 2); break;
+ case '\r': my_b_write(cache, "\\r", 2); break;
+ case '\\': my_b_write(cache, "\\\\", 2); break;
+ case '\b': my_b_write(cache, "\\b", 2); break;
+ case '\t': my_b_write(cache, "\\t", 2); break;
+ case '\'': my_b_write(cache, "\\'", 2); break;
+ case 0 : my_b_write(cache, "\\0", 2); break;
+ default:
+ my_b_write_byte(cache, c);
+ break;
+ }
+ }
+ my_b_write_byte(cache, '\'');
+}
+#endif /* MYSQL_CLIENT */
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+static void clear_all_errors(THD *thd, Relay_log_info *rli)
+{
+ thd->is_slave_error = 0;
+ thd->clear_error();
+}
+
+inline int idempotent_error_code(int err_code)
+{
+ int ret= 0;
+
+ switch (err_code)
+ {
+ case 0:
+ ret= 1;
+ break;
+ /*
+ The following list of "idempotent" errors
+ means that an error from the list might happen
+ because of idempotent (more than once)
+ applying of a binlog file.
+ Notice, that binlog has a ddl operation its
+ second applying may cause
+
+ case HA_ERR_TABLE_DEF_CHANGED:
+ case HA_ERR_CANNOT_ADD_FOREIGN:
+
+ which are not included into to the list.
+
+ Note that HA_ERR_RECORD_DELETED is not in the list since
+ do_exec_row() should not return that error code.
+ */
+ case HA_ERR_RECORD_CHANGED:
+ case HA_ERR_KEY_NOT_FOUND:
+ case HA_ERR_END_OF_FILE:
+ case HA_ERR_FOUND_DUPP_KEY:
+ case HA_ERR_FOUND_DUPP_UNIQUE:
+ case HA_ERR_FOREIGN_DUPLICATE_KEY:
+ case HA_ERR_NO_REFERENCED_ROW:
+ case HA_ERR_ROW_IS_REFERENCED:
+ ret= 1;
+ break;
+ default:
+ ret= 0;
+ break;
+ }
+ return (ret);
+}
+
+/**
+ Ignore error code specified on command line.
+*/
+
+inline int ignored_error_code(int err_code)
+{
+#ifdef HAVE_NDB_BINLOG
+ /*
+ The following error codes are hard-coded and will always be ignored.
+ */
+ switch (err_code)
+ {
+ case ER_DB_CREATE_EXISTS:
+ case ER_DB_DROP_EXISTS:
+ return 1;
+ default:
+ /* Nothing to do */
+ break;
+ }
+#endif
+ return ((err_code == ER_SLAVE_IGNORED_TABLE) ||
+ (use_slave_mask && bitmap_is_set(&slave_error_mask, err_code)));
+}
+
+/*
+ This function converts an engine's error to a server error.
+
+ If the thread does not have an error already reported, it tries to
+ define it by calling the engine's method print_error. However, if a
+ mapping is not found, it uses the ER_UNKNOWN_ERROR and prints out a
+ warning message.
+*/
+int convert_handler_error(int error, THD* thd, TABLE *table)
+{
+ uint actual_error= (thd->is_error() ? thd->get_stmt_da()->sql_errno() :
+ 0);
+
+ if (actual_error == 0)
+ {
+ table->file->print_error(error, MYF(0));
+ actual_error= (thd->is_error() ? thd->get_stmt_da()->sql_errno() :
+ ER_UNKNOWN_ERROR);
+ if (actual_error == ER_UNKNOWN_ERROR)
+ if (global_system_variables.log_warnings)
+ sql_print_warning("Unknown error detected %d in handler", error);
+ }
+
+ return (actual_error);
+}
+
+inline bool concurrency_error_code(int error)
+{
+ switch (error)
+ {
+ case ER_LOCK_WAIT_TIMEOUT:
+ case ER_LOCK_DEADLOCK:
+ case ER_XA_RBDEADLOCK:
+ return TRUE;
+ default:
+ return (FALSE);
+ }
+}
+
+inline bool unexpected_error_code(int unexpected_error)
+{
+ switch (unexpected_error)
+ {
+ case ER_NET_READ_ERROR:
+ case ER_NET_ERROR_ON_WRITE:
+ case ER_QUERY_INTERRUPTED:
+ case ER_CONNECTION_KILLED:
+ case ER_SERVER_SHUTDOWN:
+ case ER_NEW_ABORTING_CONNECTION:
+ return(TRUE);
+ default:
+ return(FALSE);
+ }
+}
+
+/*
+ pretty_print_str()
+*/
+
+static void
+pretty_print_str(String *packet, const char *str, int len)
+{
+ const char *end= str + len;
+ packet->append(STRING_WITH_LEN("'"));
+ while (str < end)
+ {
+ char c;
+ switch ((c=*str++)) {
+ case '\n': packet->append(STRING_WITH_LEN("\\n")); break;
+ case '\r': packet->append(STRING_WITH_LEN("\\r")); break;
+ case '\\': packet->append(STRING_WITH_LEN("\\\\")); break;
+ case '\b': packet->append(STRING_WITH_LEN("\\b")); break;
+ case '\t': packet->append(STRING_WITH_LEN("\\t")); break;
+ case '\'': packet->append(STRING_WITH_LEN("\\'")); break;
+ case 0 : packet->append(STRING_WITH_LEN("\\0")); break;
+ default:
+ packet->append(&c, 1);
+ break;
+ }
+ }
+ packet->append(STRING_WITH_LEN("'"));
+}
+#endif /* !MYSQL_CLIENT */
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+/**
+ Create a prefix for the temporary files that is to be used for
+ load data file name for this master
+
+ @param name Store prefix of name here
+ @param connection_name Connection name
+
+ @return pointer to end of name
+
+ @description
+ We assume that FN_REFLEN is big enough to hold
+ MAX_CONNECTION_NAME * MAX_FILENAME_MBWIDTH characters + 2 numbers +
+ a short extension.
+
+ The resulting file name has the following parts, each separated with a '-'
+ - PREFIX_SQL_LOAD (SQL_LOAD-)
+ - If a connection name is given (multi-master setup):
+ - Add an extra '-' to mark that this is a multi-master file
+ - connection name in lower case, converted to safe file characters.
+ (see create_logfile_name_with_suffix()).
+ - server_id
+ - A last '-' (after server_id).
+*/
+
+static char *load_data_tmp_prefix(char *name,
+ LEX_STRING *connection_name)
+{
+ name= strmov(name, PREFIX_SQL_LOAD);
+ if (connection_name->length)
+ {
+ uint buf_length;
+ uint errors;
+ /* Add marker that this is a multi-master-file */
+ *name++='-';
+ /* Convert connection_name to a safe filename */
+ buf_length= strconvert(system_charset_info, connection_name->str, FN_REFLEN,
+ &my_charset_filename, name, FN_REFLEN, &errors);
+ name+= buf_length;
+ *name++= '-';
+ }
+ name= int10_to_str(global_system_variables.server_id, name, 10);
+ *name++ = '-';
+ *name= '\0'; // For testing prefixes
+ return name;
+}
+
+
+/**
+ Creates a temporary name for LOAD DATA INFILE
+
+ @param buf Store new filename here
+ @param file_id File_id (part of file name)
+ @param event_server_id Event_id (part of file name)
+ @param ext Extension for file name
+
+ @return
+ Pointer to start of extension
+*/
+
+static char *slave_load_file_stem(char *buf, uint file_id,
+ int event_server_id, const char *ext,
+ LEX_STRING *connection_name)
+{
+ char *res;
+ res= buf+ unpack_dirname(buf, slave_load_tmpdir);
+ to_unix_path(buf);
+ buf= load_data_tmp_prefix(res, connection_name);
+ buf= int10_to_str(event_server_id, buf, 10);
+ *buf++ = '-';
+ res= int10_to_str(file_id, buf, 10);
+ strmov(res, ext); // Add extension last
+ return res; // Pointer to extension
+}
+#endif
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+/**
+ Delete all temporary files used for SQL_LOAD.
+*/
+
+static void cleanup_load_tmpdir(LEX_STRING *connection_name)
+{
+ MY_DIR *dirp;
+ FILEINFO *file;
+ uint i;
+ char dir[FN_REFLEN], fname[FN_REFLEN];
+ char prefbuf[31 + MAX_CONNECTION_NAME* MAX_FILENAME_MBWIDTH + 1];
+ DBUG_ENTER("cleanup_load_tmpdir");
+
+ unpack_dirname(dir, slave_load_tmpdir);
+ if (!(dirp=my_dir(dir, MYF(MY_WME))))
+ return;
+
+ /*
+ When we are deleting temporary files, we should only remove
+ the files associated with the server id of our server.
+ We don't use event_server_id here because since we've disabled
+ direct binlogging of Create_file/Append_file/Exec_load events
+ we cannot meet Start_log event in the middle of events from one
+ LOAD DATA.
+ */
+
+ load_data_tmp_prefix(prefbuf, connection_name);
+ DBUG_PRINT("enter", ("dir: '%s' prefix: '%s'", dir, prefbuf));
+
+ for (i=0 ; i < (uint)dirp->number_of_files; i++)
+ {
+ file=dirp->dir_entry+i;
+ if (is_prefix(file->name, prefbuf))
+ {
+ fn_format(fname,file->name,slave_load_tmpdir,"",MY_UNPACK_FILENAME);
+ mysql_file_delete(key_file_misc, fname, MYF(0));
+ }
+ }
+
+ my_dirend(dirp);
+ DBUG_VOID_RETURN;
+}
+#endif
+
+
+/*
+ write_str()
+*/
+
+static bool write_str(IO_CACHE *file, const char *str, uint length)
+{
+ uchar tmp[1];
+ tmp[0]= (uchar) length;
+ return (my_b_safe_write(file, tmp, sizeof(tmp)) ||
+ my_b_safe_write(file, (uchar*) str, length));
+}
+
+
+/*
+ read_str()
+*/
+
+static inline int read_str(const char **buf, const char *buf_end,
+ const char **str, uint8 *len)
+{
+ if (*buf + ((uint) (uchar) **buf) >= buf_end)
+ return 1;
+ *len= (uint8) **buf;
+ *str= (*buf)+1;
+ (*buf)+= (uint) *len+1;
+ return 0;
+}
+
+
+/**
+ Transforms a string into "" or its expression in X'HHHH' form.
+*/
+
+char *str_to_hex(char *to, const char *from, uint len)
+{
+ if (len)
+ {
+ *to++= 'X';
+ *to++= '\'';
+ to= octet2hex(to, from, len);
+ *to++= '\'';
+ *to= '\0';
+ }
+ else
+ to= strmov(to, "\"\"");
+ return to; // pointer to end 0 of 'to'
+}
+
+#ifndef MYSQL_CLIENT
+
+/**
+ Append a version of the 'str' string suitable for use in a query to
+ the 'to' string. To generate a correct escaping, the character set
+ information in 'csinfo' is used.
+*/
+
+int append_query_string(CHARSET_INFO *csinfo, String *to,
+ const char *str, size_t len, bool no_backslash)
+{
+ char *beg, *ptr;
+ uint32 const orig_len= to->length();
+ if (to->reserve(orig_len + len * 2 + 4))
+ return 1;
+
+ beg= (char*) to->ptr() + to->length();
+ ptr= beg;
+ if (csinfo->escape_with_backslash_is_dangerous)
+ ptr= str_to_hex(ptr, str, len);
+ else
+ {
+ *ptr++= '\'';
+ if (!no_backslash)
+ {
+ ptr+= escape_string_for_mysql(csinfo, ptr, 0, str, len);
+ }
+ else
+ {
+ const char *frm_str= str;
+
+ for (; frm_str < (str + len); frm_str++)
+ {
+ /* Using '' way to represent "'" */
+ if (*frm_str == '\'')
+ *ptr++= *frm_str;
+
+ *ptr++= *frm_str;
+ }
+ }
+
+ *ptr++= '\'';
+ }
+ to->length(orig_len + ptr - beg);
+ return 0;
+}
+#endif
+
+
+/**
+ Prints a "session_var=value" string. Used by mysqlbinlog to print some SET
+ commands just before it prints a query.
+*/
+
+#ifdef MYSQL_CLIENT
+
+static void print_set_option(IO_CACHE* file, uint32 bits_changed,
+ uint32 option, uint32 flags, const char* name,
+ bool* need_comma)
+{
+ if (bits_changed & option)
+ {
+ if (*need_comma)
+ my_b_write(file, ", ", 2);
+ my_b_printf(file, "%s=%d", name, MY_TEST(flags & option));
+ *need_comma= 1;
+ }
+}
+#endif
+/**************************************************************************
+ Log_event methods (= the parent class of all events)
+**************************************************************************/
+
+/**
+ @return
+ returns the human readable name of the event's type
+*/
+
+const char* Log_event::get_type_str(Log_event_type type)
+{
+ switch(type) {
+ case START_EVENT_V3: return "Start_v3";
+ case STOP_EVENT: return "Stop";
+ case QUERY_EVENT: return "Query";
+ case ROTATE_EVENT: return "Rotate";
+ case INTVAR_EVENT: return "Intvar";
+ case LOAD_EVENT: return "Load";
+ case NEW_LOAD_EVENT: return "New_load";
+ case SLAVE_EVENT: return "Slave";
+ case CREATE_FILE_EVENT: return "Create_file";
+ case APPEND_BLOCK_EVENT: return "Append_block";
+ case DELETE_FILE_EVENT: return "Delete_file";
+ case EXEC_LOAD_EVENT: return "Exec_load";
+ case RAND_EVENT: return "RAND";
+ case XID_EVENT: return "Xid";
+ case USER_VAR_EVENT: return "User var";
+ case FORMAT_DESCRIPTION_EVENT: return "Format_desc";
+ case TABLE_MAP_EVENT: return "Table_map";
+ case PRE_GA_WRITE_ROWS_EVENT: return "Write_rows_event_old";
+ case PRE_GA_UPDATE_ROWS_EVENT: return "Update_rows_event_old";
+ case PRE_GA_DELETE_ROWS_EVENT: return "Delete_rows_event_old";
+ case WRITE_ROWS_EVENT_V1: return "Write_rows_v1";
+ case UPDATE_ROWS_EVENT_V1: return "Update_rows_v1";
+ case DELETE_ROWS_EVENT_V1: return "Delete_rows_v1";
+ case WRITE_ROWS_EVENT: return "Write_rows";
+ case UPDATE_ROWS_EVENT: return "Update_rows";
+ case DELETE_ROWS_EVENT: return "Delete_rows";
+ case BEGIN_LOAD_QUERY_EVENT: return "Begin_load_query";
+ case EXECUTE_LOAD_QUERY_EVENT: return "Execute_load_query";
+ case INCIDENT_EVENT: return "Incident";
+ case ANNOTATE_ROWS_EVENT: return "Annotate_rows";
+ case BINLOG_CHECKPOINT_EVENT: return "Binlog_checkpoint";
+ case GTID_EVENT: return "Gtid";
+ case GTID_LIST_EVENT: return "Gtid_list";
+ default: return "Unknown"; /* impossible */
+ }
+}
+
+const char* Log_event::get_type_str()
+{
+ return get_type_str(get_type_code());
+}
+
+
+/*
+ Log_event::Log_event()
+*/
+
+#ifndef MYSQL_CLIENT
+Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans)
+ :log_pos(0), temp_buf(0), exec_time(0),
+ crc(0), thd(thd_arg),
+ checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF)
+{
+ server_id= thd->variables.server_id;
+ when= thd->start_time;
+ when_sec_part=thd->start_time_sec_part;
+
+ if (using_trans)
+ cache_type= Log_event::EVENT_TRANSACTIONAL_CACHE;
+ else
+ cache_type= Log_event::EVENT_STMT_CACHE;
+ flags= flags_arg |
+ (thd->variables.option_bits & OPTION_SKIP_REPLICATION ?
+ LOG_EVENT_SKIP_REPLICATION_F : 0);
+}
+
+/**
+ This minimal constructor is for when you are not even sure that there
+ is a valid THD. For example in the server when we are shutting down or
+ flushing logs after receiving a SIGHUP (then we must write a Rotate to
+ the binlog but we have no THD, so we need this minimal constructor).
+*/
+
+Log_event::Log_event()
+ :temp_buf(0), exec_time(0), flags(0),
+ cache_type(Log_event::EVENT_INVALID_CACHE), crc(0),
+ thd(0), checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF)
+{
+ server_id= global_system_variables.server_id;
+ /*
+ We can't call my_time() here as this would cause a call before
+ my_init() is called
+ */
+ when= 0;
+ when_sec_part=0;
+ log_pos= 0;
+}
+#endif /* !MYSQL_CLIENT */
+
+
+/*
+ Log_event::Log_event()
+*/
+
+Log_event::Log_event(const char* buf,
+ const Format_description_log_event* description_event)
+ :temp_buf(0), exec_time(0), cache_type(Log_event::EVENT_INVALID_CACHE),
+ crc(0), checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF)
+{
+#ifndef MYSQL_CLIENT
+ thd = 0;
+#endif
+ when = uint4korr(buf);
+ when_sec_part= 0;
+ server_id = uint4korr(buf + SERVER_ID_OFFSET);
+ data_written= uint4korr(buf + EVENT_LEN_OFFSET);
+ if (description_event->binlog_version==1)
+ {
+ log_pos= 0;
+ flags= 0;
+ return;
+ }
+ /* 4.0 or newer */
+ log_pos= uint4korr(buf + LOG_POS_OFFSET);
+ /*
+ If the log is 4.0 (so here it can only be a 4.0 relay log read by
+ the SQL thread or a 4.0 master binlog read by the I/O thread),
+ log_pos is the beginning of the event: we transform it into the end
+ of the event, which is more useful.
+ But how do you know that the log is 4.0: you know it if
+ description_event is version 3 *and* you are not reading a
+ Format_desc (remember that mysqlbinlog starts by assuming that 5.0
+ logs are in 4.0 format, until it finds a Format_desc).
+ */
+ if (description_event->binlog_version==3 &&
+ (uchar)buf[EVENT_TYPE_OFFSET]<FORMAT_DESCRIPTION_EVENT && log_pos)
+ {
+ /*
+ If log_pos=0, don't change it. log_pos==0 is a marker to mean
+ "don't change rli->group_master_log_pos" (see
+ inc_group_relay_log_pos()). As it is unreal log_pos, adding the
+ event len's is nonsense. For example, a fake Rotate event should
+ not have its log_pos (which is 0) changed or it will modify
+ Exec_master_log_pos in SHOW SLAVE STATUS, displaying a nonsense
+ value of (a non-zero offset which does not exist in the master's
+ binlog, so which will cause problems if the user uses this value
+ in CHANGE MASTER).
+ */
+ log_pos+= data_written; /* purecov: inspected */
+ }
+ DBUG_PRINT("info", ("log_pos: %lu", (ulong) log_pos));
+
+ flags= uint2korr(buf + FLAGS_OFFSET);
+ if (((uchar)buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT) ||
+ ((uchar)buf[EVENT_TYPE_OFFSET] == ROTATE_EVENT))
+ {
+ /*
+ These events always have a header which stops here (i.e. their
+ header is FROZEN).
+ */
+ /*
+ Initialization to zero of all other Log_event members as they're
+ not specified. Currently there are no such members; in the future
+ there will be an event UID (but Format_description and Rotate
+ don't need this UID, as they are not propagated through
+ --log-slave-updates (remember the UID is used to not play a query
+ twice when you have two masters which are slaves of a 3rd master).
+ Then we are done.
+ */
+ return;
+ }
+ /* otherwise, go on with reading the header from buf (nothing now) */
+}
+
+#ifndef MYSQL_CLIENT
+#ifdef HAVE_REPLICATION
+
+int Log_event::do_update_pos(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ DBUG_ENTER("Log_event::do_update_pos");
+
+ /*
+ rli is null when (as far as I (Guilhem) know) the caller is
+ Load_log_event::do_apply_event *and* that one is called from
+ Execute_load_log_event::do_apply_event. In this case, we don't
+ do anything here ; Execute_load_log_event::do_apply_event will
+ call Log_event::do_apply_event again later with the proper rli.
+ Strictly speaking, if we were sure that rli is null only in the
+ case discussed above, 'if (rli)' is useless here. But as we are
+ not 100% sure, keep it for now.
+
+ Matz: I don't think we will need this check with this refactoring.
+ */
+ if (rli)
+ {
+ /*
+ In parallel execution, delay position update for the events that are
+ not part of event groups (format description, rotate, and such) until
+ the actual event execution reaches that point.
+ */
+ if (!rgi->is_parallel_exec || is_group_event(get_type_code()))
+ rli->stmt_done(log_pos, thd, rgi);
+ }
+ DBUG_RETURN(0); // Cannot fail currently
+}
+
+
+Log_event::enum_skip_reason
+Log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ DBUG_PRINT("info", ("ev->server_id: %lu, ::server_id: %lu,"
+ " rli->replicate_same_server_id: %d,"
+ " rli->slave_skip_counter: %llu",
+ (ulong) server_id,
+ (ulong) global_system_variables.server_id,
+ rli->replicate_same_server_id,
+ rli->slave_skip_counter));
+ if ((server_id == global_system_variables.server_id &&
+ !rli->replicate_same_server_id) ||
+ (rli->slave_skip_counter == 1 && rli->is_in_group()) ||
+ (flags & LOG_EVENT_SKIP_REPLICATION_F &&
+ opt_replicate_events_marked_for_skip != RPL_SKIP_REPLICATE))
+ return EVENT_SKIP_IGNORE;
+ if (rli->slave_skip_counter > 0)
+ return EVENT_SKIP_COUNT;
+ return EVENT_SKIP_NOT;
+}
+
+
+/*
+ Log_event::pack_info()
+*/
+
+void Log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ protocol->store("", &my_charset_bin);
+}
+
+
+/**
+ Only called by SHOW BINLOG EVENTS
+*/
+int Log_event::net_send(THD *thd, Protocol *protocol, const char* log_name,
+ my_off_t pos)
+{
+ const char *p= strrchr(log_name, FN_LIBCHAR);
+ const char *event_type;
+ if (p)
+ log_name = p + 1;
+
+ protocol->prepare_for_resend();
+ protocol->store(log_name, &my_charset_bin);
+ protocol->store((ulonglong) pos);
+ event_type = get_type_str();
+ protocol->store(event_type, strlen(event_type), &my_charset_bin);
+ protocol->store((uint32) server_id);
+ protocol->store((ulonglong) log_pos);
+ pack_info(thd, protocol);
+ return protocol->write();
+}
+#endif /* HAVE_REPLICATION */
+
+
+/**
+ init_show_field_list() prepares the column names and types for the
+ output of SHOW BINLOG EVENTS; it is used only by SHOW BINLOG
+ EVENTS.
+*/
+
+void Log_event::init_show_field_list(List<Item>* field_list)
+{
+ field_list->push_back(new Item_empty_string("Log_name", 20));
+ field_list->push_back(new Item_return_int("Pos", MY_INT32_NUM_DECIMAL_DIGITS,
+ MYSQL_TYPE_LONGLONG));
+ field_list->push_back(new Item_empty_string("Event_type", 20));
+ field_list->push_back(new Item_return_int("Server_id", 10,
+ MYSQL_TYPE_LONG));
+ field_list->push_back(new Item_return_int("End_log_pos",
+ MY_INT32_NUM_DECIMAL_DIGITS,
+ MYSQL_TYPE_LONGLONG));
+ field_list->push_back(new Item_empty_string("Info", 20));
+}
+
+/**
+ A decider of whether to trigger checksum computation or not.
+ To be invoked in Log_event::write() stack.
+ The decision is positive
+
+ S,M) if it's been marked for checksumming with @c checksum_alg
+
+ M) otherwise, if @@global.binlog_checksum is not NONE and the event is
+ directly written to the binlog file.
+ The to-be-cached event decides at @c write_cache() time.
+
+ Otherwise the decision is negative.
+
+ @note A side effect of the method is altering Log_event::checksum_alg
+ it the latter was undefined at calling.
+
+ @return true (positive) or false (negative)
+*/
+my_bool Log_event::need_checksum()
+{
+ DBUG_ENTER("Log_event::need_checksum");
+ my_bool ret;
+ /*
+ few callers of Log_event::write
+ (incl FD::write, FD constructing code on the slave side, Rotate relay log
+ and Stop event)
+ provides their checksum alg preference through Log_event::checksum_alg.
+ */
+ ret= ((checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) ?
+ (checksum_alg != BINLOG_CHECKSUM_ALG_OFF) :
+ ((binlog_checksum_options != BINLOG_CHECKSUM_ALG_OFF) &&
+ (cache_type == Log_event::EVENT_NO_CACHE)) ?
+ MY_TEST(binlog_checksum_options) : FALSE);
+
+ /*
+ FD calls the methods before data_written has been calculated.
+ The following invariant claims if the current is not the first
+ call (and therefore data_written is not zero) then `ret' must be
+ TRUE. It may not be null because FD is always checksummed.
+ */
+
+ DBUG_ASSERT(get_type_code() != FORMAT_DESCRIPTION_EVENT || ret ||
+ data_written == 0);
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF)
+ checksum_alg= ret ? // calculated value stored
+ (uint8) binlog_checksum_options : (uint8) BINLOG_CHECKSUM_ALG_OFF;
+
+ DBUG_ASSERT(!ret ||
+ ((checksum_alg == binlog_checksum_options ||
+ /*
+ Stop event closes the relay-log and its checksum alg
+ preference is set by the caller can be different
+ from the server's binlog_checksum_options.
+ */
+ get_type_code() == STOP_EVENT ||
+ /*
+ Rotate:s can be checksummed regardless of the server's
+ binlog_checksum_options. That applies to both
+ the local RL's Rotate and the master's Rotate
+ which IO thread instantiates via queue_binlog_ver_3_event.
+ */
+ get_type_code() == ROTATE_EVENT
+ || /* FD is always checksummed */
+ get_type_code() == FORMAT_DESCRIPTION_EVENT) &&
+ checksum_alg != BINLOG_CHECKSUM_ALG_OFF));
+
+ DBUG_ASSERT(checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
+
+ DBUG_ASSERT(((get_type_code() != ROTATE_EVENT &&
+ get_type_code() != STOP_EVENT) ||
+ get_type_code() != FORMAT_DESCRIPTION_EVENT) ||
+ cache_type == Log_event::EVENT_NO_CACHE);
+
+ DBUG_RETURN(ret);
+}
+
+bool Log_event::wrapper_my_b_safe_write(IO_CACHE* file, const uchar* buf, ulong size)
+{
+ if (need_checksum() && size != 0)
+ crc= my_checksum(crc, buf, size);
+
+ return my_b_safe_write(file, buf, size);
+}
+
+bool Log_event::write_footer(IO_CACHE* file)
+{
+ /*
+ footer contains the checksum-algorithm descriptor
+ followed by the checksum value
+ */
+ if (need_checksum())
+ {
+ uchar buf[BINLOG_CHECKSUM_LEN];
+ int4store(buf, crc);
+ return (my_b_safe_write(file, (uchar*) buf, sizeof(buf)));
+ }
+ return 0;
+}
+
+/*
+ Log_event::write()
+*/
+
+bool Log_event::write_header(IO_CACHE* file, ulong event_data_length)
+{
+ uchar header[LOG_EVENT_HEADER_LEN];
+ ulong now;
+ bool ret;
+ DBUG_ENTER("Log_event::write_header");
+ DBUG_PRINT("enter", ("filepos: %lld length: %lu type: %d",
+ (longlong) my_b_tell(file), event_data_length,
+ (int) get_type_code()));
+
+ /* Store number of bytes that will be written by this event */
+ data_written= event_data_length + sizeof(header);
+
+ if (need_checksum())
+ {
+ crc= my_checksum(0L, NULL, 0);
+ data_written += BINLOG_CHECKSUM_LEN;
+ }
+
+ /*
+ log_pos != 0 if this is relay-log event. In this case we should not
+ change the position
+ */
+
+ if (is_artificial_event())
+ {
+ /*
+ Artificial events are automatically generated and do not exist
+ in master's binary log, so log_pos should be set to 0.
+ */
+ log_pos= 0;
+ }
+ else if (!log_pos)
+ {
+ /*
+ Calculate position of end of event
+
+ Note that with a SEQ_READ_APPEND cache, my_b_tell() does not
+ work well. So this will give slightly wrong positions for the
+ Format_desc/Rotate/Stop events which the slave writes to its
+ relay log. For example, the initial Format_desc will have
+ end_log_pos=91 instead of 95. Because after writing the first 4
+ bytes of the relay log, my_b_tell() still reports 0. Because
+ my_b_append() does not update the counter which my_b_tell()
+ later uses (one should probably use my_b_append_tell() to work
+ around this). To get right positions even when writing to the
+ relay log, we use the (new) my_b_safe_tell().
+
+ Note that this raises a question on the correctness of all these
+ DBUG_ASSERT(my_b_tell()=rli->event_relay_log_pos).
+
+ If in a transaction, the log_pos which we calculate below is not
+ very good (because then my_b_safe_tell() returns start position
+ of the BEGIN, so it's like the statement was at the BEGIN's
+ place), but it's not a very serious problem (as the slave, when
+ it is in a transaction, does not take those end_log_pos into
+ account (as it calls inc_event_relay_log_pos()). To be fixed
+ later, so that it looks less strange. But not bug.
+ */
+
+ log_pos= my_b_safe_tell(file)+data_written;
+ }
+
+ now= get_time(); // Query start time
+
+ /*
+ Header will be of size LOG_EVENT_HEADER_LEN for all events, except for
+ FORMAT_DESCRIPTION_EVENT and ROTATE_EVENT, where it will be
+ LOG_EVENT_MINIMAL_HEADER_LEN (remember these 2 have a frozen header,
+ because we read them before knowing the format).
+ */
+
+ int4store(header, now); // timestamp
+ header[EVENT_TYPE_OFFSET]= get_type_code();
+ int4store(header+ SERVER_ID_OFFSET, server_id);
+ int4store(header+ EVENT_LEN_OFFSET, data_written);
+ int4store(header+ LOG_POS_OFFSET, log_pos);
+ /*
+ recording checksum of FD event computed with dropped
+ possibly active LOG_EVENT_BINLOG_IN_USE_F flag.
+ Similar step at verication: the active flag is dropped before
+ checksum computing.
+ */
+ if (header[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT ||
+ !need_checksum() || !(flags & LOG_EVENT_BINLOG_IN_USE_F))
+ {
+ int2store(header+ FLAGS_OFFSET, flags);
+ ret= wrapper_my_b_safe_write(file, header, sizeof(header)) != 0;
+ }
+ else
+ {
+ ret= (wrapper_my_b_safe_write(file, header, FLAGS_OFFSET) != 0);
+ if (!ret)
+ {
+ flags &= ~LOG_EVENT_BINLOG_IN_USE_F;
+ int2store(header + FLAGS_OFFSET, flags);
+ crc= my_checksum(crc, header + FLAGS_OFFSET, sizeof(flags));
+ flags |= LOG_EVENT_BINLOG_IN_USE_F;
+ int2store(header + FLAGS_OFFSET, flags);
+ ret= (my_b_safe_write(file, header + FLAGS_OFFSET, sizeof(flags)) != 0);
+ }
+ if (!ret)
+ ret= (wrapper_my_b_safe_write(file, header + FLAGS_OFFSET + sizeof(flags),
+ sizeof(header)
+ - (FLAGS_OFFSET + sizeof(flags))) != 0);
+ }
+ DBUG_RETURN( ret);
+}
+
+
+/**
+ This needn't be format-tolerant, because we only read
+ LOG_EVENT_MINIMAL_HEADER_LEN (we just want to read the event's length).
+*/
+
+int Log_event::read_log_event(IO_CACHE* file, String* packet,
+ mysql_mutex_t* log_lock,
+ uint8 checksum_alg_arg,
+ const char *log_file_name_arg,
+ bool* is_binlog_active)
+{
+ ulong data_len;
+ int result=0;
+ char buf[LOG_EVENT_MINIMAL_HEADER_LEN];
+ uchar ev_offset= packet->length();
+ DBUG_ENTER("Log_event::read_log_event");
+
+ if (log_lock)
+ mysql_mutex_lock(log_lock);
+
+ if (log_file_name_arg)
+ *is_binlog_active= mysql_bin_log.is_active(log_file_name_arg);
+
+ if (my_b_read(file, (uchar*) buf, sizeof(buf)))
+ {
+ /*
+ If the read hits eof, we must report it as eof so the caller
+ will know it can go into cond_wait to be woken up on the next
+ update to the log.
+ */
+ DBUG_PRINT("error",("file->error: %d", file->error));
+ if (!file->error)
+ result= LOG_READ_EOF;
+ else
+ result= (file->error > 0 ? LOG_READ_TRUNC : LOG_READ_IO);
+ goto end;
+ }
+ data_len= uint4korr(buf + EVENT_LEN_OFFSET);
+ if (data_len < LOG_EVENT_MINIMAL_HEADER_LEN ||
+ data_len > max(current_thd->variables.max_allowed_packet,
+ opt_binlog_rows_event_max_size + MAX_LOG_EVENT_HEADER))
+ {
+ DBUG_PRINT("error",("data_len: %lu", data_len));
+ result= ((data_len < LOG_EVENT_MINIMAL_HEADER_LEN) ? LOG_READ_BOGUS :
+ LOG_READ_TOO_LARGE);
+ goto end;
+ }
+
+ /* Append the log event header to packet */
+ if (packet->append(buf, sizeof(buf)))
+ {
+ /* Failed to allocate packet */
+ result= LOG_READ_MEM;
+ goto end;
+ }
+ data_len-= LOG_EVENT_MINIMAL_HEADER_LEN;
+ if (data_len)
+ {
+ /* Append rest of event, read directly from file into packet */
+ if (packet->append(file, data_len))
+ {
+ /*
+ Fatal error occured when appending rest of the event
+ to packet, possible failures:
+ 1. EOF occured when reading from file, it's really an error
+ as data_len is >=0 there's supposed to be more bytes available.
+ file->error will have been set to number of bytes left to read
+ 2. Read was interrupted, file->error would normally be set to -1
+ 3. Failed to allocate memory for packet, my_errno
+ will be ENOMEM(file->error shuold be 0, but since the
+ memory allocation occurs before the call to read it might
+ be uninitialized)
+ */
+ result= (my_errno == ENOMEM ? LOG_READ_MEM :
+ (file->error >= 0 ? LOG_READ_TRUNC: LOG_READ_IO));
+ /* Implicit goto end; */
+ }
+ else
+ {
+ /* Corrupt the event for Dump thread*/
+ DBUG_EXECUTE_IF("corrupt_read_log_event2",
+ uchar *debug_event_buf_c = (uchar*) packet->ptr() + ev_offset;
+ if (debug_event_buf_c[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT)
+ {
+ int debug_cor_pos = rand() % (data_len + sizeof(buf) - BINLOG_CHECKSUM_LEN);
+ debug_event_buf_c[debug_cor_pos] =~ debug_event_buf_c[debug_cor_pos];
+ DBUG_PRINT("info", ("Corrupt the event at Log_event::read_log_event: byte on position %d", debug_cor_pos));
+ DBUG_SET("-d,corrupt_read_log_event2");
+ }
+ );
+ /*
+ CRC verification of the Dump thread
+ */
+ if (opt_master_verify_checksum &&
+ event_checksum_test((uchar*) packet->ptr() + ev_offset,
+ data_len + sizeof(buf),
+ checksum_alg_arg))
+ {
+ result= LOG_READ_CHECKSUM_FAILURE;
+ goto end;
+ }
+ }
+ }
+
+end:
+ if (log_lock)
+ mysql_mutex_unlock(log_lock);
+ DBUG_RETURN(result);
+}
+#endif /* !MYSQL_CLIENT */
+
+#ifndef MYSQL_CLIENT
+#define UNLOCK_MUTEX if (log_lock) mysql_mutex_unlock(log_lock);
+#define LOCK_MUTEX if (log_lock) mysql_mutex_lock(log_lock);
+#else
+#define UNLOCK_MUTEX
+#define LOCK_MUTEX
+#endif
+
+#ifndef MYSQL_CLIENT
+/**
+ @note
+ Allocates memory; The caller is responsible for clean-up.
+*/
+Log_event* Log_event::read_log_event(IO_CACHE* file,
+ mysql_mutex_t* log_lock,
+ const Format_description_log_event
+ *description_event,
+ my_bool crc_check)
+#else
+Log_event* Log_event::read_log_event(IO_CACHE* file,
+ const Format_description_log_event
+ *description_event,
+ my_bool crc_check)
+#endif
+{
+ DBUG_ENTER("Log_event::read_log_event");
+ DBUG_ASSERT(description_event != 0);
+ char head[LOG_EVENT_MINIMAL_HEADER_LEN];
+ /*
+ First we only want to read at most LOG_EVENT_MINIMAL_HEADER_LEN, just to
+ check the event for sanity and to know its length; no need to really parse
+ it. We say "at most" because this could be a 3.23 master, which has header
+ of 13 bytes, whereas LOG_EVENT_MINIMAL_HEADER_LEN is 19 bytes (it's
+ "minimal" over the set {MySQL >=4.0}).
+ */
+ uint header_size= MY_MIN(description_event->common_header_len,
+ LOG_EVENT_MINIMAL_HEADER_LEN);
+
+ LOCK_MUTEX;
+ DBUG_PRINT("info", ("my_b_tell: %lu", (ulong) my_b_tell(file)));
+ if (my_b_read(file, (uchar *) head, header_size))
+ {
+ DBUG_PRINT("info", ("Log_event::read_log_event(IO_CACHE*,Format_desc*) \
+failed my_b_read"));
+ UNLOCK_MUTEX;
+ /*
+ No error here; it could be that we are at the file's end. However
+ if the next my_b_read() fails (below), it will be an error as we
+ were able to read the first bytes.
+ */
+ DBUG_RETURN(0);
+ }
+ ulong data_len = uint4korr(head + EVENT_LEN_OFFSET);
+ char *buf= 0;
+ const char *error= 0;
+ Log_event *res= 0;
+#ifndef max_allowed_packet
+ THD *thd=current_thd;
+ uint max_allowed_packet= thd ? slave_max_allowed_packet:~(uint)0;
+#endif
+
+ if (data_len > max<ulong>(max_allowed_packet,
+ opt_binlog_rows_event_max_size + MAX_LOG_EVENT_HEADER))
+ {
+ error = "Event too big";
+ goto err;
+ }
+
+ if (data_len < header_size)
+ {
+ error = "Event too small";
+ goto err;
+ }
+
+ // some events use the extra byte to null-terminate strings
+ if (!(buf = (char*) my_malloc(data_len+1, MYF(MY_WME))))
+ {
+ error = "Out of memory";
+ goto err;
+ }
+ buf[data_len] = 0;
+ memcpy(buf, head, header_size);
+ if (my_b_read(file, (uchar*) buf + header_size, data_len - header_size))
+ {
+ error = "read error";
+ goto err;
+ }
+ if ((res= read_log_event(buf, data_len, &error, description_event, crc_check)))
+ res->register_temp_buf(buf, TRUE);
+
+err:
+ UNLOCK_MUTEX;
+ if (!res)
+ {
+ DBUG_ASSERT(error != 0);
+ sql_print_error("Error in Log_event::read_log_event(): "
+ "'%s', data_len: %lu, event_type: %d",
+ error,data_len,(uchar)(head[EVENT_TYPE_OFFSET]));
+ my_free(buf);
+ /*
+ The SQL slave thread will check if file->error<0 to know
+ if there was an I/O error. Even if there is no "low-level" I/O errors
+ with 'file', any of the high-level above errors is worrying
+ enough to stop the SQL thread now ; as we are skipping the current event,
+ going on with reading and successfully executing other events can
+ only corrupt the slave's databases. So stop.
+ */
+ file->error= -1;
+ }
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Binlog format tolerance is in (buf, event_len, description_event)
+ constructors.
+*/
+
+Log_event* Log_event::read_log_event(const char* buf, uint event_len,
+ const char **error,
+ const Format_description_log_event *description_event,
+ my_bool crc_check)
+{
+ Log_event* ev;
+ uint8 alg;
+ DBUG_ENTER("Log_event::read_log_event(char*,...)");
+ DBUG_ASSERT(description_event != 0);
+ DBUG_PRINT("info", ("binlog_version: %d", description_event->binlog_version));
+ DBUG_DUMP("data", (unsigned char*) buf, event_len);
+
+ /* Check the integrity */
+ if (event_len < EVENT_LEN_OFFSET ||
+ (uchar)buf[EVENT_TYPE_OFFSET] >= ENUM_END_EVENT ||
+ (uint) event_len != uint4korr(buf+EVENT_LEN_OFFSET))
+ {
+ *error="Sanity check failed"; // Needed to free buffer
+ DBUG_RETURN(NULL); // general sanity check - will fail on a partial read
+ }
+
+ uint event_type= (uchar)buf[EVENT_TYPE_OFFSET];
+ // all following START events in the current file are without checksum
+ if (event_type == START_EVENT_V3)
+ (const_cast< Format_description_log_event *>(description_event))->checksum_alg= BINLOG_CHECKSUM_ALG_OFF;
+ /*
+ CRC verification by SQL and Show-Binlog-Events master side.
+ The caller has to provide @description_event->checksum_alg to
+ be the last seen FD's (A) descriptor.
+ If event is FD the descriptor is in it.
+ Notice, FD of the binlog can be only in one instance and therefore
+ Show-Binlog-Events executing master side thread needs just to know
+ the only FD's (A) value - whereas RL can contain more.
+ In the RL case, the alg is kept in FD_e (@description_event) which is reset
+ to the newer read-out event after its execution with possibly new alg descriptor.
+ Therefore in a typical sequence of RL:
+ {FD_s^0, FD_m, E_m^1} E_m^1
+ will be verified with (A) of FD_m.
+
+ See legends definition on MYSQL_BIN_LOG::relay_log_checksum_alg docs
+ lines (log.h).
+
+ Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF.
+ */
+ alg= (event_type != FORMAT_DESCRIPTION_EVENT) ?
+ description_event->checksum_alg : get_checksum_alg(buf, event_len);
+ // Emulate the corruption during reading an event
+ DBUG_EXECUTE_IF("corrupt_read_log_event_char",
+ if (event_type != FORMAT_DESCRIPTION_EVENT)
+ {
+ char *debug_event_buf_c = (char *)buf;
+ int debug_cor_pos = rand() % (event_len - BINLOG_CHECKSUM_LEN);
+ debug_event_buf_c[debug_cor_pos] =~ debug_event_buf_c[debug_cor_pos];
+ DBUG_PRINT("info", ("Corrupt the event at Log_event::read_log_event(char*,...): byte on position %d", debug_cor_pos));
+ DBUG_SET("-d,corrupt_read_log_event_char");
+ }
+ );
+ if (crc_check &&
+ event_checksum_test((uchar *) buf, event_len, alg))
+ {
+#ifdef MYSQL_CLIENT
+ *error= "Event crc check failed! Most likely there is event corruption.";
+ if (force_opt)
+ {
+ ev= new Unknown_log_event(buf, description_event);
+ DBUG_RETURN(ev);
+ }
+ else
+ DBUG_RETURN(NULL);
+#else
+ *error= ER(ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE);
+ sql_print_error("%s", ER(ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE));
+ DBUG_RETURN(NULL);
+#endif
+ }
+
+ if (event_type > description_event->number_of_event_types &&
+ event_type != FORMAT_DESCRIPTION_EVENT)
+ {
+ /*
+ It is unsafe to use the description_event if its post_header_len
+ array does not include the event type.
+ */
+ DBUG_PRINT("error", ("event type %d found, but the current "
+ "Format_description_log_event supports only %d event "
+ "types", event_type,
+ description_event->number_of_event_types));
+ ev= NULL;
+ }
+ else
+ {
+ /*
+ In some previuos versions (see comment in
+ Format_description_log_event::Format_description_log_event(char*,...)),
+ event types were assigned different id numbers than in the
+ present version. In order to replicate from such versions to the
+ present version, we must map those event type id's to our event
+ type id's. The mapping is done with the event_type_permutation
+ array, which was set up when the Format_description_log_event
+ was read.
+ */
+ if (description_event->event_type_permutation)
+ {
+ int new_event_type= description_event->event_type_permutation[event_type];
+ DBUG_PRINT("info", ("converting event type %d to %d (%s)",
+ event_type, new_event_type,
+ get_type_str((Log_event_type)new_event_type)));
+ event_type= new_event_type;
+ }
+
+ if (alg != BINLOG_CHECKSUM_ALG_UNDEF &&
+ (event_type == FORMAT_DESCRIPTION_EVENT ||
+ alg != BINLOG_CHECKSUM_ALG_OFF))
+ event_len= event_len - BINLOG_CHECKSUM_LEN;
+
+ switch(event_type) {
+ case QUERY_EVENT:
+ ev = new Query_log_event(buf, event_len, description_event, QUERY_EVENT);
+ break;
+ case LOAD_EVENT:
+ ev = new Load_log_event(buf, event_len, description_event);
+ break;
+ case NEW_LOAD_EVENT:
+ ev = new Load_log_event(buf, event_len, description_event);
+ break;
+ case ROTATE_EVENT:
+ ev = new Rotate_log_event(buf, event_len, description_event);
+ break;
+ case BINLOG_CHECKPOINT_EVENT:
+ ev = new Binlog_checkpoint_log_event(buf, event_len, description_event);
+ break;
+ case GTID_EVENT:
+ ev = new Gtid_log_event(buf, event_len, description_event);
+ break;
+ case GTID_LIST_EVENT:
+ ev = new Gtid_list_log_event(buf, event_len, description_event);
+ break;
+#ifdef HAVE_REPLICATION
+ case SLAVE_EVENT: /* can never happen (unused event) */
+ ev = new Slave_log_event(buf, event_len, description_event);
+ break;
+#endif /* HAVE_REPLICATION */
+ case CREATE_FILE_EVENT:
+ ev = new Create_file_log_event(buf, event_len, description_event);
+ break;
+ case APPEND_BLOCK_EVENT:
+ ev = new Append_block_log_event(buf, event_len, description_event);
+ break;
+ case DELETE_FILE_EVENT:
+ ev = new Delete_file_log_event(buf, event_len, description_event);
+ break;
+ case EXEC_LOAD_EVENT:
+ ev = new Execute_load_log_event(buf, event_len, description_event);
+ break;
+ case START_EVENT_V3: /* this is sent only by MySQL <=4.x */
+ ev = new Start_log_event_v3(buf, event_len, description_event);
+ break;
+ case STOP_EVENT:
+ ev = new Stop_log_event(buf, description_event);
+ break;
+ case INTVAR_EVENT:
+ ev = new Intvar_log_event(buf, description_event);
+ break;
+ case XID_EVENT:
+ ev = new Xid_log_event(buf, description_event);
+ break;
+ case RAND_EVENT:
+ ev = new Rand_log_event(buf, description_event);
+ break;
+ case USER_VAR_EVENT:
+ ev = new User_var_log_event(buf, event_len, description_event);
+ break;
+ case FORMAT_DESCRIPTION_EVENT:
+ ev = new Format_description_log_event(buf, event_len, description_event);
+ break;
+#if defined(HAVE_REPLICATION)
+ case PRE_GA_WRITE_ROWS_EVENT:
+ ev = new Write_rows_log_event_old(buf, event_len, description_event);
+ break;
+ case PRE_GA_UPDATE_ROWS_EVENT:
+ ev = new Update_rows_log_event_old(buf, event_len, description_event);
+ break;
+ case PRE_GA_DELETE_ROWS_EVENT:
+ ev = new Delete_rows_log_event_old(buf, event_len, description_event);
+ break;
+ case WRITE_ROWS_EVENT_V1:
+ case WRITE_ROWS_EVENT:
+ ev = new Write_rows_log_event(buf, event_len, description_event);
+ break;
+ case UPDATE_ROWS_EVENT_V1:
+ case UPDATE_ROWS_EVENT:
+ ev = new Update_rows_log_event(buf, event_len, description_event);
+ break;
+ case DELETE_ROWS_EVENT_V1:
+ case DELETE_ROWS_EVENT:
+ ev = new Delete_rows_log_event(buf, event_len, description_event);
+ break;
+ case TABLE_MAP_EVENT:
+ ev = new Table_map_log_event(buf, event_len, description_event);
+ break;
+#endif
+ case BEGIN_LOAD_QUERY_EVENT:
+ ev = new Begin_load_query_log_event(buf, event_len, description_event);
+ break;
+ case EXECUTE_LOAD_QUERY_EVENT:
+ ev= new Execute_load_query_log_event(buf, event_len, description_event);
+ break;
+ case INCIDENT_EVENT:
+ ev = new Incident_log_event(buf, event_len, description_event);
+ break;
+ case ANNOTATE_ROWS_EVENT:
+ ev = new Annotate_rows_log_event(buf, event_len, description_event);
+ break;
+ default:
+ DBUG_PRINT("error",("Unknown event code: %d",
+ (int) buf[EVENT_TYPE_OFFSET]));
+ ev= NULL;
+ break;
+ }
+ }
+
+ if (ev)
+ {
+ ev->checksum_alg= alg;
+ if (ev->checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ ev->checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ ev->crc= uint4korr(buf + (event_len));
+ }
+
+ DBUG_PRINT("read_event", ("%s(type_code: %d; event_len: %d)",
+ ev ? ev->get_type_str() : "<unknown>",
+ buf[EVENT_TYPE_OFFSET],
+ event_len));
+ /*
+ is_valid() are small event-specific sanity tests which are
+ important; for example there are some my_malloc() in constructors
+ (e.g. Query_log_event::Query_log_event(char*...)); when these
+ my_malloc() fail we can't return an error out of the constructor
+ (because constructor is "void") ; so instead we leave the pointer we
+ wanted to allocate (e.g. 'query') to 0 and we test it in is_valid().
+ Same for Format_description_log_event, member 'post_header_len'.
+
+ SLAVE_EVENT is never used, so it should not be read ever.
+ */
+ if (!ev || !ev->is_valid() || (event_type == SLAVE_EVENT))
+ {
+ DBUG_PRINT("error",("Found invalid event in binary log"));
+
+ delete ev;
+#ifdef MYSQL_CLIENT
+ if (!force_opt) /* then mysqlbinlog dies */
+ {
+ *error= "Found invalid event in binary log";
+ DBUG_RETURN(0);
+ }
+ ev= new Unknown_log_event(buf, description_event);
+#else
+ *error= "Found invalid event in binary log";
+ DBUG_RETURN(0);
+#endif
+ }
+ DBUG_RETURN(ev);
+}
+
+#ifdef MYSQL_CLIENT
+
+static void hexdump_minimal_header_to_io_cache(IO_CACHE *file,
+ my_off_t offset,
+ uchar *ptr)
+{
+ DBUG_ASSERT(LOG_EVENT_MINIMAL_HEADER_LEN == 19);
+
+ /*
+ Pretty-print the first LOG_EVENT_MINIMAL_HEADER_LEN (19) bytes of the
+ common header, which contains the basic information about the log event.
+ Every event will have at least this much header, but events could contain
+ more headers (which must be printed by other methods, if desired).
+ */
+ char emit_buf[120]; // Enough for storing one line
+ my_b_printf(file,
+ "# "
+ "|Timestamp "
+ "|Type "
+ "|Master ID "
+ "|Size "
+ "|Master Pos "
+ "|Flags\n");
+ size_t const emit_buf_written=
+ my_snprintf(emit_buf, sizeof(emit_buf),
+ "# %8llx " /* Position */
+ "|%02x %02x %02x %02x " /* Timestamp */
+ "|%02x " /* Type */
+ "|%02x %02x %02x %02x " /* Master ID */
+ "|%02x %02x %02x %02x " /* Size */
+ "|%02x %02x %02x %02x " /* Master Pos */
+ "|%02x %02x\n", /* Flags */
+ (ulonglong) offset, /* Position */
+ ptr[0], ptr[1], ptr[2], ptr[3], /* Timestamp */
+ ptr[4], /* Type */
+ ptr[5], ptr[6], ptr[7], ptr[8], /* Master ID */
+ ptr[9], ptr[10], ptr[11], ptr[12], /* Size */
+ ptr[13], ptr[14], ptr[15], ptr[16], /* Master Pos */
+ ptr[17], ptr[18]); /* Flags */
+
+ DBUG_ASSERT(static_cast<size_t>(emit_buf_written) < sizeof(emit_buf));
+ my_b_write(file, reinterpret_cast<uchar*>(emit_buf), emit_buf_written);
+ my_b_write(file, "#\n", 2);
+}
+
+
+/*
+ The number of bytes to print per line. Should be an even number,
+ and "hexdump -C" uses 16, so we'll duplicate that here.
+*/
+#define HEXDUMP_BYTES_PER_LINE 16
+
+static void format_hex_line(char *emit_buff)
+{
+ memset(emit_buff + 1, ' ',
+ 1 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 +
+ HEXDUMP_BYTES_PER_LINE);
+ emit_buff[0]= '#';
+ emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 1]= '|';
+ emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 +
+ HEXDUMP_BYTES_PER_LINE]= '|';
+ emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 +
+ HEXDUMP_BYTES_PER_LINE + 1]= '\n';
+ emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 +
+ HEXDUMP_BYTES_PER_LINE + 2]= '\0';
+}
+
+static void hexdump_data_to_io_cache(IO_CACHE *file,
+ my_off_t offset,
+ uchar *ptr,
+ my_off_t size)
+{
+ /*
+ 2 = '# '
+ 8 = address
+ 2 = ' '
+ (HEXDUMP_BYTES_PER_LINE * 3 + 1) = Each byte prints as two hex digits,
+ plus a space
+ 2 = ' |'
+ HEXDUMP_BYTES_PER_LINE = text representation
+ 2 = '|\n'
+ 1 = '\0'
+ */
+ char emit_buffer[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 +
+ HEXDUMP_BYTES_PER_LINE + 2 + 1 ];
+ char *h,*c;
+ my_off_t i;
+
+ if (size == 0)
+ return;
+
+ format_hex_line(emit_buffer);
+ /*
+ Print the rest of the event (without common header)
+ */
+ my_off_t starting_offset = offset;
+ for (i= 0,
+ c= emit_buffer + 2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2,
+ h= emit_buffer + 2 + 8 + 2;
+ i < size;
+ i++, ptr++)
+ {
+ my_snprintf(h, 4, "%02x ", *ptr);
+ h+= 3;
+
+ *c++= my_isprint(&my_charset_bin, *ptr) ? *ptr : '.';
+
+ /* Print in groups of HEXDUMP_BYTES_PER_LINE characters. */
+ if ((i % HEXDUMP_BYTES_PER_LINE) == (HEXDUMP_BYTES_PER_LINE - 1))
+ {
+ /* remove \0 left after printing hex byte representation */
+ *h= ' ';
+ /* prepare space to print address */
+ memset(emit_buffer + 2, ' ', 8);
+ /* print address */
+ size_t const emit_buf_written= my_snprintf(emit_buffer + 2, 9, "%8llx",
+ (ulonglong) starting_offset);
+ /* remove \0 left after printing address */
+ emit_buffer[2 + emit_buf_written]= ' ';
+ my_b_write(file, reinterpret_cast<uchar*>(emit_buffer),
+ sizeof(emit_buffer) - 1);
+ c= emit_buffer + 2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2;
+ h= emit_buffer + 2 + 8 + 2;
+ format_hex_line(emit_buffer);
+ starting_offset+= HEXDUMP_BYTES_PER_LINE;
+ }
+ else if ((i % (HEXDUMP_BYTES_PER_LINE / 2))
+ == ((HEXDUMP_BYTES_PER_LINE / 2) - 1))
+ {
+ /*
+ In the middle of the group of HEXDUMP_BYTES_PER_LINE, emit an extra
+ space in the hex string, to make two groups.
+ */
+ *h++= ' ';
+ }
+
+ }
+
+ /*
+ There is still data left in our buffer, which means that the previous
+ line was not perfectly HEXDUMP_BYTES_PER_LINE characters, so write an
+ incomplete line, with spaces to pad out to the same length as a full
+ line would be, to make things more readable.
+ */
+ if (h != emit_buffer + 2 + 8 + 2)
+ {
+ *h= ' ';
+ *c++= '|'; *c++= '\n';
+ memset(emit_buffer + 2, ' ', 8);
+ size_t const emit_buf_written= my_snprintf(emit_buffer + 2, 9, "%8llx",
+ (ulonglong) starting_offset);
+ emit_buffer[2 + emit_buf_written]= ' ';
+ /* pad unprinted area */
+ memset(h, ' ',
+ (HEXDUMP_BYTES_PER_LINE * 3 + 1) - (h - (emit_buffer + 2 + 8 + 2)));
+ my_b_write(file, reinterpret_cast<uchar*>(emit_buffer),
+ c - emit_buffer);
+ }
+ my_b_write(file, "#\n", 2);
+}
+
+/*
+ Log_event::print_header()
+*/
+
+void Log_event::print_header(IO_CACHE* file,
+ PRINT_EVENT_INFO* print_event_info,
+ bool is_more __attribute__((unused)))
+{
+ char llbuff[22];
+ my_off_t hexdump_from= print_event_info->hexdump_from;
+ DBUG_ENTER("Log_event::print_header");
+
+ my_b_write_byte(file, '#');
+ print_timestamp(file);
+ my_b_printf(file, " server id %lu end_log_pos %s ", (ulong) server_id,
+ llstr(log_pos,llbuff));
+
+ /* print the checksum */
+
+ if (checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ {
+ char checksum_buf[BINLOG_CHECKSUM_LEN * 2 + 4]; // to fit to "0x%lx "
+ size_t const bytes_written=
+ my_snprintf(checksum_buf, sizeof(checksum_buf), "0x%08lx ", (ulong) crc);
+ my_b_printf(file, "%s ", get_type(&binlog_checksum_typelib, checksum_alg));
+ my_b_printf(file, checksum_buf, bytes_written);
+ }
+
+ /* mysqlbinlog --hexdump */
+ if (print_event_info->hexdump_from)
+ {
+ my_b_write_byte(file, '\n');
+ uchar *ptr= (uchar*)temp_buf;
+ my_off_t size= uint4korr(ptr + EVENT_LEN_OFFSET);
+ my_off_t hdr_len= get_header_len(print_event_info->common_header_len);
+
+ size-= hdr_len;
+
+ my_b_printf(file, "# Position\n");
+
+ /* Write the header, nicely formatted by field. */
+ hexdump_minimal_header_to_io_cache(file, hexdump_from, ptr);
+
+ ptr+= hdr_len;
+ hexdump_from+= hdr_len;
+
+ /* Print the rest of the data, mimicking "hexdump -C" output. */
+ hexdump_data_to_io_cache(file, hexdump_from, ptr, size);
+
+ /*
+ Prefix the next line so that the output from print_helper()
+ will appear as a comment.
+ */
+ my_b_write(file, "# Event: ", 9);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Prints a quoted string to io cache.
+ Control characters are displayed as hex sequence, e.g. \x00
+ Single-quote and backslash characters are escaped with a \
+
+ @param[in] file IO cache
+ @param[in] prt Pointer to string
+ @param[in] length String length
+*/
+
+static void
+my_b_write_quoted(IO_CACHE *file, const uchar *ptr, uint length)
+{
+ const uchar *s;
+ my_b_write_byte(file, '\'');
+ for (s= ptr; length > 0 ; s++, length--)
+ {
+ if (*s > 0x1F)
+ my_b_write_byte(file, *s);
+ else if (*s == '\'')
+ my_b_write(file, "\\'", 2);
+ else if (*s == '\\')
+ my_b_write(file, "\\\\", 2);
+ else
+ {
+ uchar hex[10];
+ size_t len= my_snprintf((char*) hex, sizeof(hex), "%s%02x", "\\x", *s);
+ my_b_write(file, hex, len);
+ }
+ }
+ my_b_write_byte(file, '\'');
+}
+
+
+/**
+ Prints a bit string to io cache in format b'1010'.
+
+ @param[in] file IO cache
+ @param[in] ptr Pointer to string
+ @param[in] nbits Number of bits
+*/
+static void
+my_b_write_bit(IO_CACHE *file, const uchar *ptr, uint nbits)
+{
+ uint bitnum, nbits8= ((nbits + 7) / 8) * 8, skip_bits= nbits8 - nbits;
+ my_b_write(file, "b'", 2);
+ for (bitnum= skip_bits ; bitnum < nbits8; bitnum++)
+ {
+ int is_set= (ptr[(bitnum) / 8] >> (7 - bitnum % 8)) & 0x01;
+ my_b_write_byte(file, (is_set ? '1' : '0'));
+ }
+ my_b_write_byte(file, '\'');
+}
+
+
+/**
+ Prints a packed string to io cache.
+ The string consists of length packed to 1 or 2 bytes,
+ followed by string data itself.
+
+ @param[in] file IO cache
+ @param[in] ptr Pointer to string
+ @param[in] length String size
+
+ @retval - number of bytes scanned.
+*/
+static size_t
+my_b_write_quoted_with_length(IO_CACHE *file, const uchar *ptr, uint length)
+{
+ if (length < 256)
+ {
+ length= *ptr;
+ my_b_write_quoted(file, ptr + 1, length);
+ return length + 1;
+ }
+ else
+ {
+ length= uint2korr(ptr);
+ my_b_write_quoted(file, ptr + 2, length);
+ return length + 2;
+ }
+}
+
+
+/**
+ Prints a 32-bit number in both signed and unsigned representation
+
+ @param[in] file IO cache
+ @param[in] sl Signed number
+ @param[in] ul Unsigned number
+*/
+static void
+my_b_write_sint32_and_uint32(IO_CACHE *file, int32 si, uint32 ui)
+{
+ my_b_printf(file, "%d", si);
+ if (si < 0)
+ my_b_printf(file, " (%u)", ui);
+}
+
+
+/**
+ Print a packed value of the given SQL type into IO cache
+
+ @param[in] file IO cache
+ @param[in] ptr Pointer to string
+ @param[in] type Column type
+ @param[in] meta Column meta information
+ @param[out] typestr SQL type string buffer (for verbose output)
+ @param[out] typestr_length Size of typestr
+
+ @retval - number of bytes scanned from ptr.
+*/
+
+static size_t
+log_event_print_value(IO_CACHE *file, const uchar *ptr,
+ uint type, uint meta,
+ char *typestr, size_t typestr_length)
+{
+ uint32 length= 0;
+
+ if (type == MYSQL_TYPE_STRING)
+ {
+ if (meta >= 256)
+ {
+ uint byte0= meta >> 8;
+ uint byte1= meta & 0xFF;
+
+ if ((byte0 & 0x30) != 0x30)
+ {
+ /* a long CHAR() field: see #37426 */
+ length= byte1 | (((byte0 & 0x30) ^ 0x30) << 4);
+ type= byte0 | 0x30;
+ }
+ else
+ length = meta & 0xFF;
+ }
+ else
+ length= meta;
+ }
+
+ switch (type) {
+ case MYSQL_TYPE_LONG:
+ {
+ int32 si= sint4korr(ptr);
+ uint32 ui= uint4korr(ptr);
+ my_b_write_sint32_and_uint32(file, si, ui);
+ strmake(typestr, "INT", typestr_length);
+ return 4;
+ }
+
+ case MYSQL_TYPE_TINY:
+ {
+ my_b_write_sint32_and_uint32(file, (int) (signed char) *ptr,
+ (uint) (unsigned char) *ptr);
+ strmake(typestr, "TINYINT", typestr_length);
+ return 1;
+ }
+
+ case MYSQL_TYPE_SHORT:
+ {
+ int32 si= (int32) sint2korr(ptr);
+ uint32 ui= (uint32) uint2korr(ptr);
+ my_b_write_sint32_and_uint32(file, si, ui);
+ strmake(typestr, "SHORTINT", typestr_length);
+ return 2;
+ }
+
+ case MYSQL_TYPE_INT24:
+ {
+ int32 si= sint3korr(ptr);
+ uint32 ui= uint3korr(ptr);
+ my_b_write_sint32_and_uint32(file, si, ui);
+ strmake(typestr, "MEDIUMINT", typestr_length);
+ return 3;
+ }
+
+ case MYSQL_TYPE_LONGLONG:
+ {
+ char tmp[64];
+ size_t length;
+ longlong si= sint8korr(ptr);
+ length= (longlong10_to_str(si, tmp, -10) - tmp);
+ my_b_write(file, tmp, length);
+ if (si < 0)
+ {
+ ulonglong ui= uint8korr(ptr);
+ longlong10_to_str((longlong) ui, tmp, 10);
+ my_b_printf(file, " (%s)", tmp);
+ }
+ strmake(typestr, "LONGINT", typestr_length);
+ return 8;
+ }
+
+ case MYSQL_TYPE_NEWDECIMAL:
+ {
+ uint precision= meta >> 8;
+ uint decimals= meta & 0xFF;
+ uint bin_size= my_decimal_get_binary_size(precision, decimals);
+ uint length;
+ my_decimal dec;
+ binary2my_decimal(E_DEC_FATAL_ERROR, (uchar*) ptr, &dec,
+ precision, decimals);
+ int i, end;
+ char buff[512], *pos;
+ pos= buff;
+ pos+= sprintf(buff, "%s", dec.sign() ? "-" : "");
+ end= ROUND_UP(dec.frac) + ROUND_UP(dec.intg)-1;
+ for (i=0; i < end; i++)
+ pos+= sprintf(pos, "%09d.", dec.buf[i]);
+ pos+= sprintf(pos, "%09d", dec.buf[i]);
+ length= (uint) (pos - buff);
+ my_b_write(file, buff, length);
+ my_snprintf(typestr, typestr_length, "DECIMAL(%d,%d)",
+ precision, decimals);
+ return bin_size;
+ }
+
+ case MYSQL_TYPE_FLOAT:
+ {
+ float fl;
+ float4get(fl, ptr);
+ char tmp[320];
+ sprintf(tmp, "%-20g", (double) fl);
+ my_b_printf(file, "%s", tmp); /* my_snprintf doesn't support %-20g */
+ strmake(typestr, "FLOAT", typestr_length);
+ return 4;
+ }
+
+ case MYSQL_TYPE_DOUBLE:
+ {
+ double dbl;
+ float8get(dbl, ptr);
+ char tmp[320];
+ sprintf(tmp, "%-.20g", dbl); /* strmake doesn't support %-20g */
+ my_b_printf(file, tmp, "%s");
+ strcpy(typestr, "DOUBLE");
+ return 8;
+ }
+
+ case MYSQL_TYPE_BIT:
+ {
+ /* Meta-data: bit_len, bytes_in_rec, 2 bytes */
+ uint nbits= ((meta >> 8) * 8) + (meta & 0xFF);
+ length= (nbits + 7) / 8;
+ my_b_write_bit(file, ptr, nbits);
+ my_snprintf(typestr, typestr_length, "BIT(%d)", nbits);
+ return length;
+ }
+
+ case MYSQL_TYPE_TIMESTAMP:
+ {
+ uint32 i32= uint4korr(ptr);
+ my_b_printf(file, "%d", i32);
+ strmake(typestr, "TIMESTAMP", typestr_length);
+ return 4;
+ }
+
+ case MYSQL_TYPE_TIMESTAMP2:
+ {
+ char buf[MAX_DATE_STRING_REP_LENGTH];
+ struct timeval tm;
+ my_timestamp_from_binary(&tm, ptr, meta);
+ int buflen= my_timeval_to_str(&tm, buf, meta);
+ my_b_write(file, buf, buflen);
+ my_snprintf(typestr, typestr_length, "TIMESTAMP(%d)", meta);
+ return my_timestamp_binary_length(meta);
+ }
+
+ case MYSQL_TYPE_DATETIME:
+ {
+ ulong d, t;
+ uint64 i64= uint8korr(ptr); /* YYYYMMDDhhmmss */
+ d= (ulong) (i64 / 1000000);
+ t= (ulong) (i64 % 1000000);
+
+ my_b_printf(file, "%04d-%02d-%02d %02d:%02d:%02d",
+ (int) (d / 10000), (int) (d % 10000) / 100, (int) (d % 100),
+ (int) (t / 10000), (int) (t % 10000) / 100, (int) t % 100);
+ strmake(typestr, "DATETIME", typestr_length);
+ return 8;
+ }
+
+ case MYSQL_TYPE_DATETIME2:
+ {
+ char buf[MAX_DATE_STRING_REP_LENGTH];
+ MYSQL_TIME ltime;
+ longlong packed= my_datetime_packed_from_binary(ptr, meta);
+ TIME_from_longlong_datetime_packed(&ltime, packed);
+ int buflen= my_datetime_to_str(&ltime, buf, meta);
+ my_b_write_quoted(file, (uchar *) buf, buflen);
+ my_snprintf(typestr, typestr_length, "DATETIME(%d)", meta);
+ return my_datetime_binary_length(meta);
+ }
+
+ case MYSQL_TYPE_TIME:
+ {
+ int32 tmp= sint3korr(ptr);
+ int32 i32= tmp >= 0 ? tmp : - tmp;
+ const char *sign= tmp < 0 ? "-" : "";
+ my_b_printf(file, "'%s%02d:%02d:%02d'",
+ sign, i32 / 10000, (i32 % 10000) / 100, i32 % 100, i32);
+ strmake(typestr, "TIME", typestr_length);
+ return 3;
+ }
+
+ case MYSQL_TYPE_TIME2:
+ {
+ char buf[MAX_DATE_STRING_REP_LENGTH];
+ MYSQL_TIME ltime;
+ longlong packed= my_time_packed_from_binary(ptr, meta);
+ TIME_from_longlong_time_packed(&ltime, packed);
+ int buflen= my_time_to_str(&ltime, buf, meta);
+ my_b_write_quoted(file, (uchar *) buf, buflen);
+ my_snprintf(typestr, typestr_length, "TIME(%d)", meta);
+ return my_time_binary_length(meta);
+ }
+
+ case MYSQL_TYPE_NEWDATE:
+ {
+ uint32 tmp= uint3korr(ptr);
+ int part;
+ char buf[11];
+ char *pos= &buf[10]; // start from '\0' to the beginning
+
+ /* Copied from field.cc */
+ *pos--=0; // End NULL
+ part=(int) (tmp & 31);
+ *pos--= (char) ('0'+part%10);
+ *pos--= (char) ('0'+part/10);
+ *pos--= ':';
+ part=(int) (tmp >> 5 & 15);
+ *pos--= (char) ('0'+part%10);
+ *pos--= (char) ('0'+part/10);
+ *pos--= ':';
+ part=(int) (tmp >> 9);
+ *pos--= (char) ('0'+part%10); part/=10;
+ *pos--= (char) ('0'+part%10); part/=10;
+ *pos--= (char) ('0'+part%10); part/=10;
+ *pos= (char) ('0'+part);
+ my_b_printf(file , "'%s'", buf);
+ strmake(typestr, "DATE", typestr_length);
+ return 3;
+ }
+
+ case MYSQL_TYPE_DATE:
+ {
+ uint i32= uint3korr(ptr);
+ my_b_printf(file , "'%04d:%02d:%02d'",
+ (int)(i32 / (16L * 32L)), (int)(i32 / 32L % 16L),
+ (int)(i32 % 32L));
+ strmake(typestr, "DATE", typestr_length);
+ return 3;
+ }
+
+ case MYSQL_TYPE_YEAR:
+ {
+ uint32 i32= *ptr;
+ my_b_printf(file, "%04d", i32+ 1900);
+ strmake(typestr, "YEAR", typestr_length);
+ return 1;
+ }
+
+ case MYSQL_TYPE_ENUM:
+ switch (meta & 0xFF) {
+ case 1:
+ my_b_printf(file, "%d", (int) *ptr);
+ strmake(typestr, "ENUM(1 byte)", typestr_length);
+ return 1;
+ case 2:
+ {
+ int32 i32= uint2korr(ptr);
+ my_b_printf(file, "%d", i32);
+ strmake(typestr, "ENUM(2 bytes)", typestr_length);
+ return 2;
+ }
+ default:
+ my_b_printf(file, "!! Unknown ENUM packlen=%d", meta & 0xFF);
+ return 0;
+ }
+ break;
+
+ case MYSQL_TYPE_SET:
+ my_b_write_bit(file, ptr , (meta & 0xFF) * 8);
+ my_snprintf(typestr, typestr_length, "SET(%d bytes)", meta & 0xFF);
+ return meta & 0xFF;
+
+ case MYSQL_TYPE_BLOB:
+ switch (meta) {
+ case 1:
+ length= *ptr;
+ my_b_write_quoted(file, ptr + 1, length);
+ strmake(typestr, "TINYBLOB/TINYTEXT", typestr_length);
+ return length + 1;
+ case 2:
+ length= uint2korr(ptr);
+ my_b_write_quoted(file, ptr + 2, length);
+ strmake(typestr, "BLOB/TEXT", typestr_length);
+ return length + 2;
+ case 3:
+ length= uint3korr(ptr);
+ my_b_write_quoted(file, ptr + 3, length);
+ strmake(typestr, "MEDIUMBLOB/MEDIUMTEXT", typestr_length);
+ return length + 3;
+ case 4:
+ length= uint4korr(ptr);
+ my_b_write_quoted(file, ptr + 4, length);
+ strmake(typestr, "LONGBLOB/LONGTEXT", typestr_length);
+ return length + 4;
+ default:
+ my_b_printf(file, "!! Unknown BLOB packlen=%d", length);
+ return 0;
+ }
+
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_VAR_STRING:
+ length= meta;
+ my_snprintf(typestr, typestr_length, "VARSTRING(%d)", length);
+ return my_b_write_quoted_with_length(file, ptr, length);
+
+ case MYSQL_TYPE_STRING:
+ my_snprintf(typestr, typestr_length, "STRING(%d)", length);
+ return my_b_write_quoted_with_length(file, ptr, length);
+
+ default:
+ {
+ char tmp[5];
+ my_snprintf(tmp, sizeof(tmp), "%04x", meta);
+ my_b_printf(file,
+ "!! Don't know how to handle column type=%d meta=%d (%s)",
+ type, meta, tmp);
+ }
+ break;
+ }
+ *typestr= 0;
+ return 0;
+}
+
+
+/**
+ Print a packed row into IO cache
+
+ @param[in] file IO cache
+ @param[in] td Table definition
+ @param[in] print_event_into Print parameters
+ @param[in] cols_bitmap Column bitmaps.
+ @param[in] value Pointer to packed row
+ @param[in] prefix Row's SQL clause ("SET", "WHERE", etc)
+
+ @retval - number of bytes scanned.
+*/
+
+
+size_t
+Rows_log_event::print_verbose_one_row(IO_CACHE *file, table_def *td,
+ PRINT_EVENT_INFO *print_event_info,
+ MY_BITMAP *cols_bitmap,
+ const uchar *value, const uchar *prefix)
+{
+ const uchar *value0= value;
+ const uchar *null_bits= value;
+ uint null_bit_index= 0;
+ char typestr[64]= "";
+
+ value+= (m_width + 7) / 8;
+
+ my_b_printf(file, "%s", prefix);
+
+ for (size_t i= 0; i < td->size(); i ++)
+ {
+ int is_null= (null_bits[null_bit_index / 8]
+ >> (null_bit_index % 8)) & 0x01;
+
+ if (bitmap_is_set(cols_bitmap, i) == 0)
+ continue;
+
+ if (is_null)
+ {
+ my_b_printf(file, "### @%lu=NULL", (ulong)i + 1);
+ }
+ else
+ {
+ my_b_printf(file, "### @%lu=", (ulong)i + 1);
+ size_t fsize= td->calc_field_size((uint)i, (uchar*) value);
+ if (value + fsize > m_rows_end)
+ {
+ my_b_printf(file, "***Corrupted replication event was detected."
+ " Not printing the value***\n");
+ value+= fsize;
+ return 0;
+ }
+ size_t size= log_event_print_value(file, value,
+ td->type(i), td->field_metadata(i),
+ typestr, sizeof(typestr));
+ if (!size)
+ return 0;
+
+ value+= size;
+ }
+
+ if (print_event_info->verbose > 1)
+ {
+ my_b_write(file, " /* ", 4);
+
+ if (typestr[0])
+ my_b_printf(file, "%s ", typestr);
+ else
+ my_b_printf(file, "type=%d ", td->type(i));
+
+ my_b_printf(file, "meta=%d nullable=%d is_null=%d ",
+ td->field_metadata(i),
+ td->maybe_null(i), is_null);
+ my_b_write(file, "*/", 2);
+ }
+
+ my_b_write_byte(file, '\n');
+
+ null_bit_index++;
+ }
+ return value - value0;
+}
+
+
+/**
+ Print a row event into IO cache in human readable form (in SQL format)
+
+ @param[in] file IO cache
+ @param[in] print_event_into Print parameters
+*/
+void Rows_log_event::print_verbose(IO_CACHE *file,
+ PRINT_EVENT_INFO *print_event_info)
+{
+ Table_map_log_event *map;
+ table_def *td;
+ const char *sql_command, *sql_clause1, *sql_clause2;
+ Log_event_type general_type_code= get_general_type_code();
+
+ if (m_extra_row_data)
+ {
+ uint8 extra_data_len= m_extra_row_data[EXTRA_ROW_INFO_LEN_OFFSET];
+ uint8 extra_payload_len= extra_data_len - EXTRA_ROW_INFO_HDR_BYTES;
+ assert(extra_data_len >= EXTRA_ROW_INFO_HDR_BYTES);
+
+ my_b_printf(file, "### Extra row data format: %u, len: %u :",
+ m_extra_row_data[EXTRA_ROW_INFO_FORMAT_OFFSET],
+ extra_payload_len);
+ if (extra_payload_len)
+ {
+ /*
+ Buffer for hex view of string, including '0x' prefix,
+ 2 hex chars / byte and trailing 0
+ */
+ const int buff_len= 2 + (256 * 2) + 1;
+ char buff[buff_len];
+ str_to_hex(buff, (const char*) &m_extra_row_data[EXTRA_ROW_INFO_HDR_BYTES],
+ extra_payload_len);
+ my_b_printf(file, "%s", buff);
+ }
+ my_b_printf(file, "\n");
+ }
+
+ switch (general_type_code) {
+ case WRITE_ROWS_EVENT:
+ sql_command= "INSERT INTO";
+ sql_clause1= "### SET\n";
+ sql_clause2= NULL;
+ break;
+ case DELETE_ROWS_EVENT:
+ sql_command= "DELETE FROM";
+ sql_clause1= "### WHERE\n";
+ sql_clause2= NULL;
+ break;
+ case UPDATE_ROWS_EVENT:
+ sql_command= "UPDATE";
+ sql_clause1= "### WHERE\n";
+ sql_clause2= "### SET\n";
+ break;
+ default:
+ sql_command= sql_clause1= sql_clause2= NULL;
+ DBUG_ASSERT(0); /* Not possible */
+ }
+
+ if (!(map= print_event_info->m_table_map.get_table(m_table_id)) ||
+ !(td= map->create_table_def()))
+ {
+ my_b_printf(file, "### Row event for unknown table #%lu",
+ (ulong) m_table_id);
+ return;
+ }
+
+ /* If the write rows event contained no values for the AI */
+ if (((general_type_code == WRITE_ROWS_EVENT) && (m_rows_buf==m_rows_end)))
+ {
+ my_b_printf(file, "### INSERT INTO %`s.%`s VALUES ()\n",
+ map->get_db_name(), map->get_table_name());
+ goto end;
+ }
+
+ for (const uchar *value= m_rows_buf; value < m_rows_end; )
+ {
+ size_t length;
+ my_b_printf(file, "### %s %`s.%`s\n",
+ sql_command,
+ map->get_db_name(), map->get_table_name());
+ /* Print the first image */
+ if (!(length= print_verbose_one_row(file, td, print_event_info,
+ &m_cols, value,
+ (const uchar*) sql_clause1)))
+ goto end;
+ value+= length;
+
+ /* Print the second image (for UPDATE only) */
+ if (sql_clause2)
+ {
+ if (!(length= print_verbose_one_row(file, td, print_event_info,
+ &m_cols_ai, value,
+ (const uchar*) sql_clause2)))
+ goto end;
+ value+= length;
+ }
+ }
+
+end:
+ delete td;
+}
+
+void free_table_map_log_event(Table_map_log_event *event)
+{
+ delete event;
+}
+
+void Log_event::print_base64(IO_CACHE* file,
+ PRINT_EVENT_INFO* print_event_info,
+ bool more)
+{
+ const uchar *ptr= (const uchar *)temp_buf;
+ uint32 size= uint4korr(ptr + EVENT_LEN_OFFSET);
+ DBUG_ENTER("Log_event::print_base64");
+
+ size_t const tmp_str_sz= base64_needed_encoded_length((int) size);
+ char *const tmp_str= (char *) my_malloc(tmp_str_sz, MYF(MY_WME));
+ if (!tmp_str) {
+ fprintf(stderr, "\nError: Out of memory. "
+ "Could not print correct binlog event.\n");
+ DBUG_VOID_RETURN;
+ }
+
+ if (base64_encode(ptr, (size_t) size, tmp_str))
+ {
+ DBUG_ASSERT(0);
+ }
+
+ if (print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS)
+ {
+ if (my_b_tell(file) == 0)
+ my_b_write_string(file, "\nBINLOG '\n");
+
+ my_b_printf(file, "%s\n", tmp_str);
+
+ if (!more)
+ my_b_printf(file, "'%s\n", print_event_info->delimiter);
+ }
+
+ if (print_event_info->verbose)
+ {
+ Rows_log_event *ev= NULL;
+ Log_event_type et= (Log_event_type) ptr[EVENT_TYPE_OFFSET];
+
+ if (checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF &&
+ checksum_alg != BINLOG_CHECKSUM_ALG_OFF)
+ size-= BINLOG_CHECKSUM_LEN; // checksum is displayed through the header
+
+ switch (et)
+ {
+ case TABLE_MAP_EVENT:
+ {
+ Table_map_log_event *map;
+ map= new Table_map_log_event((const char*) ptr, size,
+ glob_description_event);
+ print_event_info->m_table_map.set_table(map->get_table_id(), map);
+ break;
+ }
+ case WRITE_ROWS_EVENT:
+ case WRITE_ROWS_EVENT_V1:
+ {
+ ev= new Write_rows_log_event((const char*) ptr, size,
+ glob_description_event);
+ break;
+ }
+ case DELETE_ROWS_EVENT:
+ case DELETE_ROWS_EVENT_V1:
+ {
+ ev= new Delete_rows_log_event((const char*) ptr, size,
+ glob_description_event);
+ break;
+ }
+ case UPDATE_ROWS_EVENT:
+ case UPDATE_ROWS_EVENT_V1:
+ {
+ ev= new Update_rows_log_event((const char*) ptr, size,
+ glob_description_event);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (ev)
+ {
+ ev->print_verbose(file, print_event_info);
+ delete ev;
+ }
+ }
+
+ my_free(tmp_str);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Log_event::print_timestamp()
+*/
+
+void Log_event::print_timestamp(IO_CACHE* file, time_t* ts)
+{
+ struct tm *res;
+ time_t my_when= when;
+ DBUG_ENTER("Log_event::print_timestamp");
+ if (!ts)
+ ts = &my_when;
+ res=localtime(ts);
+
+ my_b_printf(file,"%02d%02d%02d %2d:%02d:%02d",
+ res->tm_year % 100,
+ res->tm_mon+1,
+ res->tm_mday,
+ res->tm_hour,
+ res->tm_min,
+ res->tm_sec);
+ DBUG_VOID_RETURN;
+}
+
+#endif /* MYSQL_CLIENT */
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+inline Log_event::enum_skip_reason
+Log_event::continue_group(rpl_group_info *rgi)
+{
+ if (rgi->rli->slave_skip_counter == 1)
+ return Log_event::EVENT_SKIP_IGNORE;
+ return Log_event::do_shall_skip(rgi);
+}
+#endif
+
+/**************************************************************************
+ Query_log_event methods
+**************************************************************************/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+/**
+ This (which is used only for SHOW BINLOG EVENTS) could be updated to
+ print SET @@session_var=. But this is not urgent, as SHOW BINLOG EVENTS is
+ only an information, it does not produce suitable queries to replay (for
+ example it does not print LOAD DATA INFILE).
+ @todo
+ show the catalog ??
+*/
+
+void Query_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ // TODO: show the catalog ??
+ char buf_mem[1024];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ buf.real_alloc(9 + db_len + q_len);
+ if (!(flags & LOG_EVENT_SUPPRESS_USE_F)
+ && db && db_len)
+ {
+ buf.append(STRING_WITH_LEN("use "));
+ append_identifier(thd, &buf, db, db_len);
+ buf.append(STRING_WITH_LEN("; "));
+ }
+ if (query && q_len)
+ buf.append(query, q_len);
+ protocol->store(&buf);
+}
+#endif
+
+#ifndef MYSQL_CLIENT
+
+/**
+ Utility function for the next method (Query_log_event::write()) .
+*/
+static void write_str_with_code_and_len(uchar **dst, const char *src,
+ uint len, uint code)
+{
+ /*
+ only 1 byte to store the length of catalog, so it should not
+ surpass 255
+ */
+ DBUG_ASSERT(len <= 255);
+ DBUG_ASSERT(src);
+ *((*dst)++)= (uchar) code;
+ *((*dst)++)= (uchar) len;
+ bmove(*dst, src, len);
+ (*dst)+= len;
+}
+
+
+/**
+ Query_log_event::write().
+
+ @note
+ In this event we have to modify the header to have the correct
+ EVENT_LEN_OFFSET as we don't yet know how many status variables we
+ will print!
+*/
+
+bool Query_log_event::write(IO_CACHE* file)
+{
+ uchar buf[QUERY_HEADER_LEN + MAX_SIZE_LOG_EVENT_STATUS];
+ uchar *start, *start_of_status;
+ ulong event_length;
+
+ if (!query)
+ return 1; // Something wrong with event
+
+ /*
+ We want to store the thread id:
+ (- as an information for the user when he reads the binlog)
+ - if the query uses temporary table: for the slave SQL thread to know to
+ which master connection the temp table belongs.
+ Now imagine we (write()) are called by the slave SQL thread (we are
+ logging a query executed by this thread; the slave runs with
+ --log-slave-updates). Then this query will be logged with
+ thread_id=the_thread_id_of_the_SQL_thread. Imagine that 2 temp tables of
+ the same name were created simultaneously on the master (in the master
+ binlog you have
+ CREATE TEMPORARY TABLE t; (thread 1)
+ CREATE TEMPORARY TABLE t; (thread 2)
+ ...)
+ then in the slave's binlog there will be
+ CREATE TEMPORARY TABLE t; (thread_id_of_the_slave_SQL_thread)
+ CREATE TEMPORARY TABLE t; (thread_id_of_the_slave_SQL_thread)
+ which is bad (same thread id!).
+
+ To avoid this, we log the thread's thread id EXCEPT for the SQL
+ slave thread for which we log the original (master's) thread id.
+ Now this moves the bug: what happens if the thread id on the
+ master was 10 and when the slave replicates the query, a
+ connection number 10 is opened by a normal client on the slave,
+ and updates a temp table of the same name? We get a problem
+ again. To avoid this, in the handling of temp tables (sql_base.cc)
+ we use thread_id AND server_id. TODO when this is merged into
+ 4.1: in 4.1, slave_proxy_id has been renamed to pseudo_thread_id
+ and is a session variable: that's to make mysqlbinlog work with
+ temp tables. We probably need to introduce
+
+ SET PSEUDO_SERVER_ID
+ for mysqlbinlog in 4.1. mysqlbinlog would print:
+ SET PSEUDO_SERVER_ID=
+ SET PSEUDO_THREAD_ID=
+ for each query using temp tables.
+ */
+ int4store(buf + Q_THREAD_ID_OFFSET, slave_proxy_id);
+ int4store(buf + Q_EXEC_TIME_OFFSET, exec_time);
+ buf[Q_DB_LEN_OFFSET] = (char) db_len;
+ int2store(buf + Q_ERR_CODE_OFFSET, error_code);
+
+ /*
+ You MUST always write status vars in increasing order of code. This
+ guarantees that a slightly older slave will be able to parse those he
+ knows.
+ */
+ start_of_status= start= buf+QUERY_HEADER_LEN;
+ if (flags2_inited)
+ {
+ *start++= Q_FLAGS2_CODE;
+ int4store(start, flags2);
+ start+= 4;
+ }
+ if (sql_mode_inited)
+ {
+ *start++= Q_SQL_MODE_CODE;
+ int8store(start, (ulonglong)sql_mode);
+ start+= 8;
+ }
+ if (catalog_len) // i.e. this var is inited (false for 4.0 events)
+ {
+ write_str_with_code_and_len(&start,
+ catalog, catalog_len, Q_CATALOG_NZ_CODE);
+ /*
+ In 5.0.x where x<4 masters we used to store the end zero here. This was
+ a waste of one byte so we don't do it in x>=4 masters. We change code to
+ Q_CATALOG_NZ_CODE, because re-using the old code would make x<4 slaves
+ of this x>=4 master segfault (expecting a zero when there is
+ none). Remaining compatibility problems are: the older slave will not
+ find the catalog; but it is will not crash, and it's not an issue
+ that it does not find the catalog as catalogs were not used in these
+ older MySQL versions (we store it in binlog and read it from relay log
+ but do nothing useful with it). What is an issue is that the older slave
+ will stop processing the Q_* blocks (and jumps to the db/query) as soon
+ as it sees unknown Q_CATALOG_NZ_CODE; so it will not be able to read
+ Q_AUTO_INCREMENT*, Q_CHARSET and so replication will fail silently in
+ various ways. Documented that you should not mix alpha/beta versions if
+ they are not exactly the same version, with example of 5.0.3->5.0.2 and
+ 5.0.4->5.0.3. If replication is from older to new, the new will
+ recognize Q_CATALOG_CODE and have no problem.
+ */
+ }
+ if (auto_increment_increment != 1 || auto_increment_offset != 1)
+ {
+ *start++= Q_AUTO_INCREMENT;
+ int2store(start, auto_increment_increment);
+ int2store(start+2, auto_increment_offset);
+ start+= 4;
+ }
+ if (charset_inited)
+ {
+ *start++= Q_CHARSET_CODE;
+ memcpy(start, charset, 6);
+ start+= 6;
+ }
+ if (time_zone_len)
+ {
+ /* In the TZ sys table, column Name is of length 64 so this should be ok */
+ DBUG_ASSERT(time_zone_len <= MAX_TIME_ZONE_NAME_LENGTH);
+ write_str_with_code_and_len(&start,
+ time_zone_str, time_zone_len, Q_TIME_ZONE_CODE);
+ }
+ if (lc_time_names_number)
+ {
+ DBUG_ASSERT(lc_time_names_number <= 0xFFFF);
+ *start++= Q_LC_TIME_NAMES_CODE;
+ int2store(start, lc_time_names_number);
+ start+= 2;
+ }
+ if (charset_database_number)
+ {
+ DBUG_ASSERT(charset_database_number <= 0xFFFF);
+ *start++= Q_CHARSET_DATABASE_CODE;
+ int2store(start, charset_database_number);
+ start+= 2;
+ }
+ if (table_map_for_update)
+ {
+ *start++= Q_TABLE_MAP_FOR_UPDATE_CODE;
+ int8store(start, table_map_for_update);
+ start+= 8;
+ }
+ if (master_data_written != 0)
+ {
+ /*
+ Q_MASTER_DATA_WRITTEN_CODE only exists in relay logs where the master
+ has binlog_version<4 and the slave has binlog_version=4. See comment
+ for master_data_written in log_event.h for details.
+ */
+ *start++= Q_MASTER_DATA_WRITTEN_CODE;
+ int4store(start, master_data_written);
+ start+= 4;
+ }
+
+ if (thd && thd->need_binlog_invoker())
+ {
+ LEX_STRING user;
+ LEX_STRING host;
+ memset(&user, 0, sizeof(user));
+ memset(&host, 0, sizeof(host));
+
+ if (thd->slave_thread && thd->has_invoker())
+ {
+ /* user will be null, if master is older than this patch */
+ user= thd->get_invoker_user();
+ host= thd->get_invoker_host();
+ }
+ else
+ {
+ Security_context *ctx= thd->security_ctx;
+
+ if (thd->need_binlog_invoker() == THD::INVOKER_USER)
+ {
+ user.str= ctx->priv_user;
+ host.str= ctx->priv_host;
+ host.length= strlen(host.str);
+ }
+ else
+ {
+ user.str= ctx->priv_role;
+ host= empty_lex_str;
+ }
+ user.length= strlen(user.str);
+ }
+
+ if (user.length > 0)
+ {
+ *start++= Q_INVOKER;
+
+ /*
+ Store user length and user. The max length of use is 16, so 1 byte is
+ enough to store the user's length.
+ */
+ *start++= (uchar)user.length;
+ memcpy(start, user.str, user.length);
+ start+= user.length;
+
+ /*
+ Store host length and host. The max length of host is 60, so 1 byte is
+ enough to store the host's length.
+ */
+ *start++= (uchar)host.length;
+ memcpy(start, host.str, host.length);
+ start+= host.length;
+ }
+ }
+
+ if (thd && thd->query_start_sec_part_used)
+ {
+ *start++= Q_HRNOW;
+ get_time();
+ int3store(start, when_sec_part);
+ start+= 3;
+ }
+ /*
+ NOTE: When adding new status vars, please don't forget to update
+ the MAX_SIZE_LOG_EVENT_STATUS in log_event.h and update the function
+ code_name() in this file.
+
+ Here there could be code like
+ if (command-line-option-which-says-"log_this_variable" && inited)
+ {
+ *start++= Q_THIS_VARIABLE_CODE;
+ int4store(start, this_variable);
+ start+= 4;
+ }
+ */
+
+ /* Store length of status variables */
+ status_vars_len= (uint) (start-start_of_status);
+ DBUG_ASSERT(status_vars_len <= MAX_SIZE_LOG_EVENT_STATUS);
+ int2store(buf + Q_STATUS_VARS_LEN_OFFSET, status_vars_len);
+
+ /*
+ Calculate length of whole event
+ The "1" below is the \0 in the db's length
+ */
+ event_length= (uint) (start-buf) + get_post_header_size_for_derived() + db_len + 1 + q_len;
+
+ return (write_header(file, event_length) ||
+ wrapper_my_b_safe_write(file, (uchar*) buf, QUERY_HEADER_LEN) ||
+ write_post_header_for_derived(file) ||
+ wrapper_my_b_safe_write(file, (uchar*) start_of_status,
+ (uint) (start-start_of_status)) ||
+ wrapper_my_b_safe_write(file, (db) ? (uchar*) db : (uchar*)"", db_len + 1) ||
+ wrapper_my_b_safe_write(file, (uchar*) query, q_len) ||
+ write_footer(file)) ? 1 : 0;
+}
+
+/**
+ The simplest constructor that could possibly work. This is used for
+ creating static objects that have a special meaning and are invisible
+ to the log.
+*/
+Query_log_event::Query_log_event()
+ :Log_event(), data_buf(0)
+{
+ memset(&user, 0, sizeof(user));
+ memset(&host, 0, sizeof(host));
+}
+
+
+/*
+ SYNOPSIS
+ Query_log_event::Query_log_event()
+ thd_arg - thread handle
+ query_arg - array of char representing the query
+ query_length - size of the `query_arg' array
+ using_trans - there is a modified transactional table
+ suppress_use - suppress the generation of 'USE' statements
+ errcode - the error code of the query
+
+ DESCRIPTION
+ Creates an event for binlogging
+ The value for `errcode' should be supplied by caller.
+*/
+Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg,
+ ulong query_length, bool using_trans,
+ bool direct, bool suppress_use, int errcode)
+
+ :Log_event(thd_arg,
+ (thd_arg->thread_specific_used ? LOG_EVENT_THREAD_SPECIFIC_F :
+ 0) |
+ (suppress_use ? LOG_EVENT_SUPPRESS_USE_F : 0),
+ using_trans),
+ data_buf(0), query(query_arg), catalog(thd_arg->catalog),
+ db(thd_arg->db), q_len((uint32) query_length),
+ thread_id(thd_arg->thread_id),
+ /* save the original thread id; we already know the server id */
+ slave_proxy_id(thd_arg->variables.pseudo_thread_id),
+ flags2_inited(1), sql_mode_inited(1), charset_inited(1),
+ sql_mode(thd_arg->variables.sql_mode),
+ auto_increment_increment(thd_arg->variables.auto_increment_increment),
+ auto_increment_offset(thd_arg->variables.auto_increment_offset),
+ lc_time_names_number(thd_arg->variables.lc_time_names->number),
+ charset_database_number(0),
+ table_map_for_update((ulonglong)thd_arg->table_map_for_update),
+ master_data_written(0)
+{
+ time_t end_time;
+
+ memset(&user, 0, sizeof(user));
+ memset(&host, 0, sizeof(host));
+
+ error_code= errcode;
+
+ end_time= my_time(0);
+ exec_time = (ulong) (end_time - thd_arg->start_time);
+ /**
+ @todo this means that if we have no catalog, then it is replicated
+ as an existing catalog of length zero. is that safe? /sven
+ */
+ catalog_len = (catalog) ? (uint32) strlen(catalog) : 0;
+ /* status_vars_len is set just before writing the event */
+ db_len = (db) ? (uint32) strlen(db) : 0;
+ if (thd_arg->variables.collation_database != thd_arg->db_charset)
+ charset_database_number= thd_arg->variables.collation_database->number;
+
+ /*
+ We only replicate over the bits of flags2 that we need: the rest
+ are masked out by "& OPTIONS_WRITTEN_TO_BINLOG".
+
+ We also force AUTOCOMMIT=1. Rationale (cf. BUG#29288): After
+ fixing BUG#26395, we always write BEGIN and COMMIT around all
+ transactions (even single statements in autocommit mode). This is
+ so that replication from non-transactional to transactional table
+ and error recovery from XA to non-XA table should work as
+ expected. The BEGIN/COMMIT are added in log.cc. However, there is
+ one exception: MyISAM bypasses log.cc and writes directly to the
+ binlog. So if autocommit is off, master has MyISAM, and slave has
+ a transactional engine, then the slave will just see one long
+ never-ending transaction. The only way to bypass explicit
+ BEGIN/COMMIT in the binlog is by using a non-transactional table.
+ So setting AUTOCOMMIT=1 will make this work as expected.
+
+ Note: explicitly replicate AUTOCOMMIT=1 from master. We do not
+ assume AUTOCOMMIT=1 on slave; the slave still reads the state of
+ the autocommit flag as written by the master to the binlog. This
+ behavior may change after WL#4162 has been implemented.
+ */
+ flags2= (uint32) (thd_arg->variables.option_bits &
+ (OPTIONS_WRITTEN_TO_BIN_LOG & ~OPTION_NOT_AUTOCOMMIT));
+ DBUG_ASSERT(thd_arg->variables.character_set_client->number < 256*256);
+ DBUG_ASSERT(thd_arg->variables.collation_connection->number < 256*256);
+ DBUG_ASSERT(thd_arg->variables.collation_server->number < 256*256);
+ DBUG_ASSERT(thd_arg->variables.character_set_client->mbminlen == 1);
+ int2store(charset, thd_arg->variables.character_set_client->number);
+ int2store(charset+2, thd_arg->variables.collation_connection->number);
+ int2store(charset+4, thd_arg->variables.collation_server->number);
+ if (thd_arg->time_zone_used)
+ {
+ /*
+ Note that our event becomes dependent on the Time_zone object
+ representing the time zone. Fortunately such objects are never deleted
+ or changed during mysqld's lifetime.
+ */
+ time_zone_len= thd_arg->variables.time_zone->get_name()->length();
+ time_zone_str= thd_arg->variables.time_zone->get_name()->ptr();
+ }
+ else
+ time_zone_len= 0;
+
+ LEX *lex= thd->lex;
+ /*
+ Defines that the statement will be written directly to the binary log
+ without being wrapped by a BEGIN...COMMIT. Otherwise, the statement
+ will be written to either the trx-cache or stmt-cache.
+
+ Note that a cache will not be used if the parameter direct is TRUE.
+ */
+ bool use_cache= FALSE;
+ /*
+ TRUE defines that the trx-cache must be used and by consequence the
+ use_cache is TRUE.
+
+ Note that a cache will not be used if the parameter direct is TRUE.
+ */
+ bool trx_cache= FALSE;
+ cache_type= Log_event::EVENT_INVALID_CACHE;
+
+ switch (lex->sql_command)
+ {
+ case SQLCOM_DROP_TABLE:
+ use_cache= (lex->drop_temporary && thd->in_multi_stmt_transaction_mode());
+ break;
+
+ case SQLCOM_CREATE_TABLE:
+ trx_cache= (lex->select_lex.item_list.elements &&
+ thd->is_current_stmt_binlog_format_row());
+ use_cache= (lex->create_info.tmp_table() &&
+ thd->in_multi_stmt_transaction_mode()) || trx_cache;
+ break;
+ case SQLCOM_SET_OPTION:
+ if (lex->autocommit)
+ use_cache= trx_cache= FALSE;
+ else
+ use_cache= TRUE;
+ break;
+ case SQLCOM_RELEASE_SAVEPOINT:
+ case SQLCOM_ROLLBACK_TO_SAVEPOINT:
+ case SQLCOM_SAVEPOINT:
+ use_cache= trx_cache= TRUE;
+ break;
+ default:
+ use_cache= sqlcom_can_generate_row_events(thd);
+ break;
+ }
+
+ if (!use_cache || direct)
+ {
+ cache_type= Log_event::EVENT_NO_CACHE;
+ }
+ else if (using_trans || trx_cache || stmt_has_updated_trans_table(thd) ||
+ thd->lex->is_mixed_stmt_unsafe(thd->in_multi_stmt_transaction_mode(),
+ thd->variables.binlog_direct_non_trans_update,
+ trans_has_updated_trans_table(thd),
+ thd->tx_isolation))
+ cache_type= Log_event::EVENT_TRANSACTIONAL_CACHE;
+ else
+ cache_type= Log_event::EVENT_STMT_CACHE;
+ DBUG_ASSERT(cache_type != Log_event::EVENT_INVALID_CACHE);
+ DBUG_PRINT("info",("Query_log_event has flags2: %lu sql_mode: %llu",
+ (ulong) flags2, sql_mode));
+}
+#endif /* MYSQL_CLIENT */
+
+
+/* 2 utility functions for the next method */
+
+/**
+ Read a string with length from memory.
+
+ This function reads the string-with-length stored at
+ <code>src</code> and extract the length into <code>*len</code> and
+ a pointer to the start of the string into <code>*dst</code>. The
+ string can then be copied using <code>memcpy()</code> with the
+ number of bytes given in <code>*len</code>.
+
+ @param src Pointer to variable holding a pointer to the memory to
+ read the string from.
+ @param dst Pointer to variable holding a pointer where the actual
+ string starts. Starting from this position, the string
+ can be copied using @c memcpy().
+ @param len Pointer to variable where the length will be stored.
+ @param end One-past-the-end of the memory where the string is
+ stored.
+
+ @return Zero if the entire string can be copied successfully,
+ @c UINT_MAX if the length could not be read from memory
+ (that is, if <code>*src >= end</code>), otherwise the
+ number of bytes that are missing to read the full
+ string, which happends <code>*dst + *len >= end</code>.
+*/
+static int
+get_str_len_and_pointer(const Log_event::Byte **src,
+ const char **dst,
+ uint *len,
+ const Log_event::Byte *end)
+{
+ if (*src >= end)
+ return -1; // Will be UINT_MAX in two-complement arithmetics
+ uint length= **src;
+ if (length > 0)
+ {
+ if (*src + length >= end)
+ return *src + length - end + 1; // Number of bytes missing
+ *dst= (char *)*src + 1; // Will be copied later
+ }
+ *len= length;
+ *src+= length + 1;
+ return 0;
+}
+
+static void copy_str_and_move(const char **src,
+ Log_event::Byte **dst,
+ uint len)
+{
+ memcpy(*dst, *src, len);
+ *src= (const char *)*dst;
+ (*dst)+= len;
+ *(*dst)++= 0;
+}
+
+
+#ifndef DBUG_OFF
+static char const *
+code_name(int code)
+{
+ static char buf[255];
+ switch (code) {
+ case Q_FLAGS2_CODE: return "Q_FLAGS2_CODE";
+ case Q_SQL_MODE_CODE: return "Q_SQL_MODE_CODE";
+ case Q_CATALOG_CODE: return "Q_CATALOG_CODE";
+ case Q_AUTO_INCREMENT: return "Q_AUTO_INCREMENT";
+ case Q_CHARSET_CODE: return "Q_CHARSET_CODE";
+ case Q_TIME_ZONE_CODE: return "Q_TIME_ZONE_CODE";
+ case Q_CATALOG_NZ_CODE: return "Q_CATALOG_NZ_CODE";
+ case Q_LC_TIME_NAMES_CODE: return "Q_LC_TIME_NAMES_CODE";
+ case Q_CHARSET_DATABASE_CODE: return "Q_CHARSET_DATABASE_CODE";
+ case Q_TABLE_MAP_FOR_UPDATE_CODE: return "Q_TABLE_MAP_FOR_UPDATE_CODE";
+ case Q_MASTER_DATA_WRITTEN_CODE: return "Q_MASTER_DATA_WRITTEN_CODE";
+ case Q_HRNOW: return "Q_HRNOW";
+ }
+ sprintf(buf, "CODE#%d", code);
+ return buf;
+}
+#endif
+
+/**
+ Macro to check that there is enough space to read from memory.
+
+ @param PTR Pointer to memory
+ @param END End of memory
+ @param CNT Number of bytes that should be read.
+ */
+#define CHECK_SPACE(PTR,END,CNT) \
+ do { \
+ DBUG_PRINT("info", ("Read %s", code_name(pos[-1]))); \
+ DBUG_ASSERT((PTR) + (CNT) <= (END)); \
+ if ((PTR) + (CNT) > (END)) { \
+ DBUG_PRINT("info", ("query= 0")); \
+ query= 0; \
+ DBUG_VOID_RETURN; \
+ } \
+ } while (0)
+
+
+/**
+ This is used by the SQL slave thread to prepare the event before execution.
+*/
+Query_log_event::Query_log_event(const char* buf, uint event_len,
+ const Format_description_log_event
+ *description_event,
+ Log_event_type event_type)
+ :Log_event(buf, description_event), data_buf(0), query(NullS),
+ db(NullS), catalog_len(0), status_vars_len(0),
+ flags2_inited(0), sql_mode_inited(0), charset_inited(0),
+ auto_increment_increment(1), auto_increment_offset(1),
+ time_zone_len(0), lc_time_names_number(0), charset_database_number(0),
+ table_map_for_update(0), master_data_written(0)
+{
+ ulong data_len;
+ uint32 tmp;
+ uint8 common_header_len, post_header_len;
+ Log_event::Byte *start;
+ const Log_event::Byte *end;
+ bool catalog_nz= 1;
+ DBUG_ENTER("Query_log_event::Query_log_event(char*,...)");
+
+ memset(&user, 0, sizeof(user));
+ memset(&host, 0, sizeof(host));
+ common_header_len= description_event->common_header_len;
+ post_header_len= description_event->post_header_len[event_type-1];
+ DBUG_PRINT("info",("event_len: %u common_header_len: %d post_header_len: %d",
+ event_len, common_header_len, post_header_len));
+
+ /*
+ We test if the event's length is sensible, and if so we compute data_len.
+ We cannot rely on QUERY_HEADER_LEN here as it would not be format-tolerant.
+ We use QUERY_HEADER_MINIMAL_LEN which is the same for 3.23, 4.0 & 5.0.
+ */
+ if (event_len < (uint)(common_header_len + post_header_len))
+ DBUG_VOID_RETURN;
+ data_len = event_len - (common_header_len + post_header_len);
+ buf+= common_header_len;
+
+ slave_proxy_id= thread_id = uint4korr(buf + Q_THREAD_ID_OFFSET);
+ exec_time = uint4korr(buf + Q_EXEC_TIME_OFFSET);
+ db_len = (uint)buf[Q_DB_LEN_OFFSET]; // TODO: add a check of all *_len vars
+ error_code = uint2korr(buf + Q_ERR_CODE_OFFSET);
+
+ /*
+ 5.0 format starts here.
+ Depending on the format, we may or not have affected/warnings etc
+ The remnent post-header to be parsed has length:
+ */
+ tmp= post_header_len - QUERY_HEADER_MINIMAL_LEN;
+ if (tmp)
+ {
+ status_vars_len= uint2korr(buf + Q_STATUS_VARS_LEN_OFFSET);
+ /*
+ Check if status variable length is corrupt and will lead to very
+ wrong data. We could be even more strict and require data_len to
+ be even bigger, but this will suffice to catch most corruption
+ errors that can lead to a crash.
+ */
+ if (status_vars_len > MY_MIN(data_len, MAX_SIZE_LOG_EVENT_STATUS))
+ {
+ DBUG_PRINT("info", ("status_vars_len (%u) > data_len (%lu); query= 0",
+ status_vars_len, data_len));
+ query= 0;
+ DBUG_VOID_RETURN;
+ }
+ data_len-= status_vars_len;
+ DBUG_PRINT("info", ("Query_log_event has status_vars_len: %u",
+ (uint) status_vars_len));
+ tmp-= 2;
+ }
+ else
+ {
+ /*
+ server version < 5.0 / binlog_version < 4 master's event is
+ relay-logged with storing the original size of the event in
+ Q_MASTER_DATA_WRITTEN_CODE status variable.
+ The size is to be restored at reading Q_MASTER_DATA_WRITTEN_CODE-marked
+ event from the relay log.
+ */
+ DBUG_ASSERT(description_event->binlog_version < 4);
+ master_data_written= data_written;
+ }
+ /*
+ We have parsed everything we know in the post header for QUERY_EVENT,
+ the rest of post header is either comes from older version MySQL or
+ dedicated to derived events (e.g. Execute_load_query...)
+ */
+
+ /* variable-part: the status vars; only in MySQL 5.0 */
+
+ start= (Log_event::Byte*) (buf+post_header_len);
+ end= (const Log_event::Byte*) (start+status_vars_len);
+ for (const Log_event::Byte* pos= start; pos < end;)
+ {
+ switch (*pos++) {
+ case Q_FLAGS2_CODE:
+ CHECK_SPACE(pos, end, 4);
+ flags2_inited= 1;
+ flags2= uint4korr(pos);
+ DBUG_PRINT("info",("In Query_log_event, read flags2: %lu", (ulong) flags2));
+ pos+= 4;
+ break;
+ case Q_SQL_MODE_CODE:
+ {
+#ifndef DBUG_OFF
+ char buff[22];
+#endif
+ CHECK_SPACE(pos, end, 8);
+ sql_mode_inited= 1;
+ sql_mode= (ulong) uint8korr(pos); // QQ: Fix when sql_mode is ulonglong
+ DBUG_PRINT("info",("In Query_log_event, read sql_mode: %s",
+ llstr(sql_mode, buff)));
+ pos+= 8;
+ break;
+ }
+ case Q_CATALOG_NZ_CODE:
+ DBUG_PRINT("info", ("case Q_CATALOG_NZ_CODE; pos: 0x%lx; end: 0x%lx",
+ (ulong) pos, (ulong) end));
+ if (get_str_len_and_pointer(&pos, &catalog, &catalog_len, end))
+ {
+ DBUG_PRINT("info", ("query= 0"));
+ query= 0;
+ DBUG_VOID_RETURN;
+ }
+ break;
+ case Q_AUTO_INCREMENT:
+ CHECK_SPACE(pos, end, 4);
+ auto_increment_increment= uint2korr(pos);
+ auto_increment_offset= uint2korr(pos+2);
+ pos+= 4;
+ break;
+ case Q_CHARSET_CODE:
+ {
+ CHECK_SPACE(pos, end, 6);
+ charset_inited= 1;
+ memcpy(charset, pos, 6);
+ pos+= 6;
+ break;
+ }
+ case Q_TIME_ZONE_CODE:
+ {
+ if (get_str_len_and_pointer(&pos, &time_zone_str, &time_zone_len, end))
+ {
+ DBUG_PRINT("info", ("Q_TIME_ZONE_CODE: query= 0"));
+ query= 0;
+ DBUG_VOID_RETURN;
+ }
+ break;
+ }
+ case Q_CATALOG_CODE: /* for 5.0.x where 0<=x<=3 masters */
+ CHECK_SPACE(pos, end, 1);
+ if ((catalog_len= *pos))
+ catalog= (char*) pos+1; // Will be copied later
+ CHECK_SPACE(pos, end, catalog_len + 2);
+ pos+= catalog_len+2; // leap over end 0
+ catalog_nz= 0; // catalog has end 0 in event
+ break;
+ case Q_LC_TIME_NAMES_CODE:
+ CHECK_SPACE(pos, end, 2);
+ lc_time_names_number= uint2korr(pos);
+ pos+= 2;
+ break;
+ case Q_CHARSET_DATABASE_CODE:
+ CHECK_SPACE(pos, end, 2);
+ charset_database_number= uint2korr(pos);
+ pos+= 2;
+ break;
+ case Q_TABLE_MAP_FOR_UPDATE_CODE:
+ CHECK_SPACE(pos, end, 8);
+ table_map_for_update= uint8korr(pos);
+ pos+= 8;
+ break;
+ case Q_MASTER_DATA_WRITTEN_CODE:
+ CHECK_SPACE(pos, end, 4);
+ data_written= master_data_written= uint4korr(pos);
+ pos+= 4;
+ break;
+ case Q_INVOKER:
+ {
+ CHECK_SPACE(pos, end, 1);
+ user.length= *pos++;
+ CHECK_SPACE(pos, end, user.length);
+ user.str= (char *)pos;
+ pos+= user.length;
+
+ CHECK_SPACE(pos, end, 1);
+ host.length= *pos++;
+ CHECK_SPACE(pos, end, host.length);
+ host.str= (char *)pos;
+ pos+= host.length;
+ break;
+ }
+ case Q_HRNOW:
+ {
+ CHECK_SPACE(pos, end, 3);
+ when_sec_part= uint3korr(pos);
+ pos+= 3;
+ break;
+ }
+ default:
+ /* That's why you must write status vars in growing order of code */
+ DBUG_PRINT("info",("Query_log_event has unknown status vars (first has\
+ code: %u), skipping the rest of them", (uint) *(pos-1)));
+ pos= (const uchar*) end; // Break loop
+ }
+ }
+
+ /**
+ Layout for the data buffer is as follows
+ +--------+-----------+------+------+---------+----+-------+
+ | catlog | time_zone | user | host | db name | \0 | Query |
+ +--------+-----------+------+------+---------+----+-------+
+
+ To support the query cache we append the following buffer to the above
+ +-------+----------------------------------------+-------+
+ |db len | uninitiatlized space of size of db len | FLAGS |
+ +-------+----------------------------------------+-------+
+
+ The area of buffer starting from Query field all the way to the end belongs
+ to the Query buffer and its structure is described in alloc_query() in
+ sql_parse.cc
+ */
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_QUERY_CACHE)
+ if (!(start= data_buf = (Log_event::Byte*) my_malloc(catalog_len + 1
+ + time_zone_len + 1
+ + user.length + 1
+ + host.length + 1
+ + data_len + 1
+ + sizeof(size_t)//for db_len
+ + db_len + 1
+ + QUERY_CACHE_DB_LENGTH_SIZE
+ + QUERY_CACHE_FLAGS_SIZE,
+ MYF(MY_WME))))
+#else
+ if (!(start= data_buf = (Log_event::Byte*) my_malloc(catalog_len + 1
+ + time_zone_len + 1
+ + user.length + 1
+ + host.length + 1
+ + data_len + 1,
+ MYF(MY_WME))))
+#endif
+ DBUG_VOID_RETURN;
+ if (catalog_len) // If catalog is given
+ {
+ /**
+ @todo we should clean up and do only copy_str_and_move; it
+ works for both cases. Then we can remove the catalog_nz
+ flag. /sven
+ */
+ if (likely(catalog_nz)) // true except if event comes from 5.0.0|1|2|3.
+ copy_str_and_move(&catalog, &start, catalog_len);
+ else
+ {
+ memcpy(start, catalog, catalog_len+1); // copy end 0
+ catalog= (const char *)start;
+ start+= catalog_len+1;
+ }
+ }
+ if (time_zone_len)
+ copy_str_and_move(&time_zone_str, &start, time_zone_len);
+
+ if (user.length > 0)
+ copy_str_and_move((const char **)&(user.str), &start, user.length);
+ if (host.length > 0)
+ copy_str_and_move((const char **)&(host.str), &start, host.length);
+
+ /**
+ if time_zone_len or catalog_len are 0, then time_zone and catalog
+ are uninitialized at this point. shouldn't they point to the
+ zero-length null-terminated strings we allocated space for in the
+ my_alloc call above? /sven
+ */
+
+ /* A 2nd variable part; this is common to all versions */
+ memcpy((char*) start, end, data_len); // Copy db and query
+ start[data_len]= '\0'; // End query with \0 (For safetly)
+ db= (char *)start;
+ query= (char *)(start + db_len + 1);
+ q_len= data_len - db_len -1;
+ /**
+ Append the db length at the end of the buffer. This will be used by
+ Query_cache::send_result_to_client() in case the query cache is On.
+ */
+#if !defined(MYSQL_CLIENT) && defined(HAVE_QUERY_CACHE)
+ size_t db_length= (size_t)db_len;
+ memcpy(start + data_len + 1, &db_length, sizeof(size_t));
+#endif
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Replace a binlog event read into a packet with a dummy event. Either a
+ Query_log_event that has just a comment, or if that will not fit in the
+ space used for the event to be replaced, then a NULL user_var event.
+
+ This is used when sending binlog data to a slave which does not understand
+ this particular event and which is too old to support informational events
+ or holes in the event stream.
+
+ This allows to write such events into the binlog on the master and still be
+ able to replicate against old slaves without them breaking.
+
+ Clears the flag LOG_EVENT_THREAD_SPECIFIC_F and set LOG_EVENT_SUPPRESS_USE_F.
+ Overwrites the type with QUERY_EVENT (or USER_VAR_EVENT), and replaces the
+ body with a minimal query / NULL user var.
+
+ Returns zero on success, -1 if error due to too little space in original
+ event. A minimum of 25 bytes (19 bytes fixed header + 6 bytes in the body)
+ is needed in any event to be replaced with a dummy event.
+*/
+int
+Query_log_event::dummy_event(String *packet, ulong ev_offset,
+ uint8 checksum_alg)
+{
+ uchar *p= (uchar *)packet->ptr() + ev_offset;
+ size_t data_len= packet->length() - ev_offset;
+ uint16 flags;
+ static const size_t min_user_var_event_len=
+ LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE + 1 + UV_VAL_IS_NULL; // 25
+ static const size_t min_query_event_len=
+ LOG_EVENT_HEADER_LEN + QUERY_HEADER_LEN + 1 + 1; // 34
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ data_len-= BINLOG_CHECKSUM_LEN;
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ if (data_len < min_user_var_event_len)
+ /* Cannot replace with dummy, event too short. */
+ return -1;
+
+ flags= uint2korr(p + FLAGS_OFFSET);
+ flags&= ~LOG_EVENT_THREAD_SPECIFIC_F;
+ flags|= LOG_EVENT_SUPPRESS_USE_F;
+ int2store(p + FLAGS_OFFSET, flags);
+
+ if (data_len < min_query_event_len)
+ {
+ /*
+ Have to use dummy user_var event for such a short packet.
+
+ This works, but the event will be considered part of an event group with
+ the following event. So for example @@global.sql_slave_skip_counter=1
+ will skip not only the dummy event, but also the immediately following
+ event.
+
+ We write a NULL user var with the name @`!dummyvar` (or as much
+ as that as will fit within the size of the original event - so
+ possibly just @`!`).
+ */
+ static const char var_name[]= "!dummyvar";
+ uint name_len= data_len - (min_user_var_event_len - 1);
+
+ p[EVENT_TYPE_OFFSET]= USER_VAR_EVENT;
+ int4store(p + LOG_EVENT_HEADER_LEN, name_len);
+ memcpy(p + LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE, var_name, name_len);
+ p[LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE + name_len]= 1; // indicates NULL
+ }
+ else
+ {
+ /*
+ Use a dummy query event, just a comment.
+ */
+ static const char message[]=
+ "# Dummy event replacing event type %u that slave cannot handle.";
+ char buf[sizeof(message)+1]; /* +1, as %u can expand to 3 digits. */
+ uchar old_type= p[EVENT_TYPE_OFFSET];
+ uchar *q= p + LOG_EVENT_HEADER_LEN;
+ size_t comment_len, len;
+
+ p[EVENT_TYPE_OFFSET]= QUERY_EVENT;
+ int4store(q + Q_THREAD_ID_OFFSET, 0);
+ int4store(q + Q_EXEC_TIME_OFFSET, 0);
+ q[Q_DB_LEN_OFFSET]= 0;
+ int2store(q + Q_ERR_CODE_OFFSET, 0);
+ int2store(q + Q_STATUS_VARS_LEN_OFFSET, 0);
+ q[Q_DATA_OFFSET]= 0; /* Zero terminator for empty db */
+ q+= Q_DATA_OFFSET + 1;
+ len= my_snprintf(buf, sizeof(buf), message, old_type);
+ comment_len= data_len - (min_query_event_len - 1);
+ if (comment_len <= len)
+ memcpy(q, buf, comment_len);
+ else
+ {
+ memcpy(q, buf, len);
+ memset(q+len, ' ', comment_len - len);
+ }
+ }
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ ha_checksum crc= my_checksum(0L, p, data_len);
+ int4store(p + data_len, crc);
+ }
+ return 0;
+}
+
+/*
+ Replace an event (GTID event) with a BEGIN query event, to be compatible
+ with an old slave.
+*/
+int
+Query_log_event::begin_event(String *packet, ulong ev_offset,
+ uint8 checksum_alg)
+{
+ uchar *p= (uchar *)packet->ptr() + ev_offset;
+ uchar *q= p + LOG_EVENT_HEADER_LEN;
+ size_t data_len= packet->length() - ev_offset;
+ uint16 flags;
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ data_len-= BINLOG_CHECKSUM_LEN;
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ /*
+ Currently we only need to replace GTID event.
+ The length of GTID differs depending on whether it contains commit id.
+ */
+ DBUG_ASSERT(data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN ||
+ data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2);
+ if (data_len != LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN &&
+ data_len != LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2)
+ return 1;
+
+ flags= uint2korr(p + FLAGS_OFFSET);
+ flags&= ~LOG_EVENT_THREAD_SPECIFIC_F;
+ flags|= LOG_EVENT_SUPPRESS_USE_F;
+ int2store(p + FLAGS_OFFSET, flags);
+
+ p[EVENT_TYPE_OFFSET]= QUERY_EVENT;
+ int4store(q + Q_THREAD_ID_OFFSET, 0);
+ int4store(q + Q_EXEC_TIME_OFFSET, 0);
+ q[Q_DB_LEN_OFFSET]= 0;
+ int2store(q + Q_ERR_CODE_OFFSET, 0);
+ if (data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN)
+ {
+ int2store(q + Q_STATUS_VARS_LEN_OFFSET, 0);
+ q[Q_DATA_OFFSET]= 0; /* Zero terminator for empty db */
+ q+= Q_DATA_OFFSET + 1;
+ }
+ else
+ {
+ DBUG_ASSERT(data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2);
+ /* Put in an empty time_zone_str to take up the extra 2 bytes. */
+ int2store(q + Q_STATUS_VARS_LEN_OFFSET, 2);
+ q[Q_DATA_OFFSET]= Q_TIME_ZONE_CODE;
+ q[Q_DATA_OFFSET+1]= 0; /* Zero length for empty time_zone_str */
+ q[Q_DATA_OFFSET+2]= 0; /* Zero terminator for empty db */
+ q+= Q_DATA_OFFSET + 3;
+ }
+ memcpy(q, "BEGIN", 5);
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ ha_checksum crc= my_checksum(0L, p, data_len);
+ int4store(p + data_len, crc);
+ }
+ return 0;
+}
+
+
+#ifdef MYSQL_CLIENT
+/**
+ Query_log_event::print().
+
+ @todo
+ print the catalog ??
+*/
+void Query_log_event::print_query_header(IO_CACHE* file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ // TODO: print the catalog ??
+ char buff[64], *end; // Enough for SET TIMESTAMP
+ bool different_db= 1;
+ uint32 tmp;
+
+ if (!print_event_info->short_form)
+ {
+ print_header(file, print_event_info, FALSE);
+ my_b_printf(file, "\t%s\tthread_id=%lu\texec_time=%lu\terror_code=%d\n",
+ get_type_str(), (ulong) thread_id, (ulong) exec_time,
+ error_code);
+ }
+
+ if ((flags & LOG_EVENT_SUPPRESS_USE_F))
+ {
+ if (!is_trans_keyword())
+ print_event_info->db[0]= '\0';
+ }
+ else if (db)
+ {
+ different_db= memcmp(print_event_info->db, db, db_len + 1);
+ if (different_db)
+ memcpy(print_event_info->db, db, db_len + 1);
+ if (db[0] && different_db)
+ my_b_printf(file, "use %`s%s\n", db, print_event_info->delimiter);
+ }
+
+ end=int10_to_str((long) when, strmov(buff,"SET TIMESTAMP="),10);
+ if (when_sec_part)
+ {
+ *end++= '.';
+ end=int10_to_str(when_sec_part, end, 10);
+ }
+ end= strmov(end, print_event_info->delimiter);
+ *end++='\n';
+ my_b_write(file, (uchar*) buff, (uint) (end-buff));
+ if ((!print_event_info->thread_id_printed ||
+ ((flags & LOG_EVENT_THREAD_SPECIFIC_F) &&
+ thread_id != print_event_info->thread_id)))
+ {
+ // If --short-form, print deterministic value instead of pseudo_thread_id.
+ my_b_printf(file,"SET @@session.pseudo_thread_id=%lu%s\n",
+ short_form ? 999999999 : (ulong)thread_id,
+ print_event_info->delimiter);
+ print_event_info->thread_id= thread_id;
+ print_event_info->thread_id_printed= 1;
+ }
+
+ /*
+ If flags2_inited==0, this is an event from 3.23 or 4.0; nothing to
+ print (remember we don't produce mixed relay logs so there cannot be
+ 5.0 events before that one so there is nothing to reset).
+ */
+ if (likely(flags2_inited)) /* likely as this will mainly read 5.0 logs */
+ {
+ /* tmp is a bitmask of bits which have changed. */
+ if (likely(print_event_info->flags2_inited))
+ /* All bits which have changed */
+ tmp= (print_event_info->flags2) ^ flags2;
+ else /* that's the first Query event we read */
+ {
+ print_event_info->flags2_inited= 1;
+ tmp= ~((uint32)0); /* all bits have changed */
+ }
+
+ if (unlikely(tmp)) /* some bits have changed */
+ {
+ bool need_comma= 0;
+ my_b_write_string(file, "SET ");
+ print_set_option(file, tmp, OPTION_NO_FOREIGN_KEY_CHECKS, ~flags2,
+ "@@session.foreign_key_checks", &need_comma);
+ print_set_option(file, tmp, OPTION_AUTO_IS_NULL, flags2,
+ "@@session.sql_auto_is_null", &need_comma);
+ print_set_option(file, tmp, OPTION_RELAXED_UNIQUE_CHECKS, ~flags2,
+ "@@session.unique_checks", &need_comma);
+ print_set_option(file, tmp, OPTION_NOT_AUTOCOMMIT, ~flags2,
+ "@@session.autocommit", &need_comma);
+ my_b_printf(file,"%s\n", print_event_info->delimiter);
+ print_event_info->flags2= flags2;
+ }
+ }
+
+ /*
+ Now the session variables;
+ it's more efficient to pass SQL_MODE as a number instead of a
+ comma-separated list.
+ FOREIGN_KEY_CHECKS, SQL_AUTO_IS_NULL, UNIQUE_CHECKS are session-only
+ variables (they have no global version; they're not listed in
+ sql_class.h), The tests below work for pure binlogs or pure relay
+ logs. Won't work for mixed relay logs but we don't create mixed
+ relay logs (that is, there is no relay log with a format change
+ except within the 3 first events, which mysqlbinlog handles
+ gracefully). So this code should always be good.
+ */
+
+ if (likely(sql_mode_inited) &&
+ (unlikely(print_event_info->sql_mode != sql_mode ||
+ !print_event_info->sql_mode_inited)))
+ {
+ my_b_printf(file,"SET @@session.sql_mode=%lu%s\n",
+ (ulong)sql_mode, print_event_info->delimiter);
+ print_event_info->sql_mode= sql_mode;
+ print_event_info->sql_mode_inited= 1;
+ }
+ if (print_event_info->auto_increment_increment != auto_increment_increment ||
+ print_event_info->auto_increment_offset != auto_increment_offset)
+ {
+ my_b_printf(file,"SET @@session.auto_increment_increment=%lu, @@session.auto_increment_offset=%lu%s\n",
+ auto_increment_increment,auto_increment_offset,
+ print_event_info->delimiter);
+ print_event_info->auto_increment_increment= auto_increment_increment;
+ print_event_info->auto_increment_offset= auto_increment_offset;
+ }
+
+ /* TODO: print the catalog when we feature SET CATALOG */
+
+ if (likely(charset_inited) &&
+ (unlikely(!print_event_info->charset_inited ||
+ memcmp(print_event_info->charset, charset, 6))))
+ {
+ CHARSET_INFO *cs_info= get_charset(uint2korr(charset), MYF(MY_WME));
+ if (cs_info)
+ {
+ /* for mysql client */
+ my_b_printf(file, "/*!\\C %s */%s\n",
+ cs_info->csname, print_event_info->delimiter);
+ }
+ my_b_printf(file,"SET "
+ "@@session.character_set_client=%d,"
+ "@@session.collation_connection=%d,"
+ "@@session.collation_server=%d"
+ "%s\n",
+ uint2korr(charset),
+ uint2korr(charset+2),
+ uint2korr(charset+4),
+ print_event_info->delimiter);
+ memcpy(print_event_info->charset, charset, 6);
+ print_event_info->charset_inited= 1;
+ }
+ if (time_zone_len)
+ {
+ if (memcmp(print_event_info->time_zone_str,
+ time_zone_str, time_zone_len+1))
+ {
+ my_b_printf(file,"SET @@session.time_zone='%s'%s\n",
+ time_zone_str, print_event_info->delimiter);
+ memcpy(print_event_info->time_zone_str, time_zone_str, time_zone_len+1);
+ }
+ }
+ if (lc_time_names_number != print_event_info->lc_time_names_number)
+ {
+ my_b_printf(file, "SET @@session.lc_time_names=%d%s\n",
+ lc_time_names_number, print_event_info->delimiter);
+ print_event_info->lc_time_names_number= lc_time_names_number;
+ }
+ if (charset_database_number != print_event_info->charset_database_number)
+ {
+ if (charset_database_number)
+ my_b_printf(file, "SET @@session.collation_database=%d%s\n",
+ charset_database_number, print_event_info->delimiter);
+ else
+ my_b_printf(file, "SET @@session.collation_database=DEFAULT%s\n",
+ print_event_info->delimiter);
+ print_event_info->charset_database_number= charset_database_number;
+ }
+}
+
+
+void Query_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+
+ /**
+ reduce the size of io cache so that the write function is called
+ for every call to my_b_write().
+ */
+ DBUG_EXECUTE_IF ("simulate_file_write_error",
+ {(&cache)->write_pos= (&cache)->write_end- 500;});
+ print_query_header(&cache, print_event_info);
+ my_b_write(&cache, (uchar*) query, q_len);
+ my_b_printf(&cache, "\n%s\n", print_event_info->delimiter);
+}
+#endif /* MYSQL_CLIENT */
+
+
+/*
+ Query_log_event::do_apply_event()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+int Query_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ return do_apply_event(rgi, query, q_len);
+}
+
+/**
+ Compare if two errors should be regarded as equal.
+ This is to handle the case when you can get slightly different errors
+ on master and slave for the same thing.
+ @param
+ expected_error Error we got on master
+ actual_error Error we got on slave
+
+ @return
+ 1 Errors are equal
+ 0 Errors are different
+*/
+
+bool test_if_equal_repl_errors(int expected_error, int actual_error)
+{
+ if (expected_error == actual_error)
+ return 1;
+ switch (expected_error) {
+ case ER_DUP_ENTRY:
+ case ER_AUTOINC_READ_FAILED:
+ return (actual_error == ER_AUTOINC_READ_FAILED ||
+ actual_error == HA_ERR_AUTOINC_ERANGE);
+ case ER_UNKNOWN_TABLE:
+ return actual_error == ER_IT_IS_A_VIEW;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+/**
+ @todo
+ Compare the values of "affected rows" around here. Something
+ like:
+ @code
+ if ((uint32) affected_in_event != (uint32) affected_on_slave)
+ {
+ sql_print_error("Slave: did not get the expected number of affected \
+ rows running query from master - expected %d, got %d (this numbers \
+ should have matched modulo 4294967296).", 0, ...);
+ thd->query_error = 1;
+ }
+ @endcode
+ We may also want an option to tell the slave to ignore "affected"
+ mismatch. This mismatch could be implemented with a new ER_ code, and
+ to ignore it you would use --slave-skip-errors...
+*/
+int Query_log_event::do_apply_event(rpl_group_info *rgi,
+ const char *query_arg, uint32 q_len_arg)
+{
+ LEX_STRING new_db;
+ int expected_error,actual_error= 0;
+ HA_CREATE_INFO db_options;
+ uint64 sub_id= 0;
+ rpl_gtid gtid;
+ Relay_log_info const *rli= rgi->rli;
+ Rpl_filter *rpl_filter= rli->mi->rpl_filter;
+ bool current_stmt_is_commit;
+ DBUG_ENTER("Query_log_event::do_apply_event");
+
+ /*
+ Colleagues: please never free(thd->catalog) in MySQL. This would
+ lead to bugs as here thd->catalog is a part of an alloced block,
+ not an entire alloced block (see
+ Query_log_event::do_apply_event()). Same for thd->db. Thank
+ you.
+ */
+ thd->catalog= catalog_len ? (char *) catalog : (char *)"";
+ new_db.length= db_len;
+ new_db.str= (char *) rpl_filter->get_rewrite_db(db, &new_db.length);
+ thd->set_db(new_db.str, new_db.length); /* allocates a copy of 'db' */
+
+ /*
+ Setting the character set and collation of the current database thd->db.
+ */
+ load_db_opt_by_name(thd, thd->db, &db_options);
+ if (db_options.default_table_charset)
+ thd->db_charset= db_options.default_table_charset;
+ thd->variables.auto_increment_increment= auto_increment_increment;
+ thd->variables.auto_increment_offset= auto_increment_offset;
+
+ DBUG_PRINT("info", ("log_pos: %lu", (ulong) log_pos));
+
+ clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
+ current_stmt_is_commit= is_commit();
+
+ if (current_stmt_is_commit && rgi->tables_to_lock)
+ {
+ /*
+ Cleaning-up the last statement context:
+ the terminal event of the current statement flagged with
+ STMT_END_F got filtered out in ndb circular replication.
+ */
+ int error;
+ char llbuff[22];
+ if ((error= rows_event_stmt_cleanup(rgi, thd)) &&
+ !is_parallel_retry_error(rgi, error))
+ {
+ rli->report(ERROR_LEVEL, error, rgi->gtid_info(),
+ "Error in cleaning up after an event preceding the commit; "
+ "the group log file/position: %s %s",
+ const_cast<Relay_log_info*>(rli)->group_master_log_name,
+ llstr(const_cast<Relay_log_info*>(rli)->group_master_log_pos,
+ llbuff));
+ }
+ /*
+ Executing a part of rli->stmt_done() logics that does not deal
+ with group position change. The part is redundant now but is
+ future-change-proof addon, e.g if COMMIT handling will start checking
+ invariants like IN_STMT flag must be off at committing the transaction.
+ */
+ rgi->inc_event_relay_log_pos();
+ }
+ else
+ {
+ rgi->slave_close_thread_tables(thd);
+ }
+
+ /*
+ Note: We do not need to execute reset_one_shot_variables() if this
+ db_ok() test fails.
+ Reason: The db stored in binlog events is the same for SET and for
+ its companion query. If the SET is ignored because of
+ db_ok(), the companion query will also be ignored, and if
+ the companion query is ignored in the db_ok() test of
+ ::do_apply_event(), then the companion SET also have so
+ we don't need to reset_one_shot_variables().
+ */
+ if (is_trans_keyword() || rpl_filter->db_ok(thd->db))
+ {
+ thd->set_time(when, when_sec_part);
+ thd->set_query_and_id((char*)query_arg, q_len_arg,
+ thd->charset(), next_query_id());
+ thd->variables.pseudo_thread_id= thread_id; // for temp tables
+ DBUG_PRINT("query",("%s", thd->query()));
+
+ if (!(expected_error= error_code) ||
+ ignored_error_code(expected_error) ||
+ !unexpected_error_code(expected_error))
+ {
+ thd->slave_expected_error= expected_error;
+ if (flags2_inited)
+ /*
+ all bits of thd->variables.option_bits which are 1 in OPTIONS_WRITTEN_TO_BIN_LOG
+ must take their value from flags2.
+ */
+ thd->variables.option_bits= flags2|(thd->variables.option_bits & ~OPTIONS_WRITTEN_TO_BIN_LOG);
+ /*
+ else, we are in a 3.23/4.0 binlog; we previously received a
+ Rotate_log_event which reset thd->variables.option_bits and sql_mode etc, so
+ nothing to do.
+ */
+ /*
+ We do not replicate MODE_NO_DIR_IN_CREATE. That is, if the master is a
+ slave which runs with SQL_MODE=MODE_NO_DIR_IN_CREATE, this should not
+ force us to ignore the dir too. Imagine you are a ring of machines, and
+ one has a disk problem so that you temporarily need
+ MODE_NO_DIR_IN_CREATE on this machine; you don't want it to propagate
+ elsewhere (you don't want all slaves to start ignoring the dirs).
+ */
+ if (sql_mode_inited)
+ thd->variables.sql_mode=
+ (ulong) ((thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE) |
+ (sql_mode & ~(ulong) MODE_NO_DIR_IN_CREATE));
+ if (charset_inited)
+ {
+ rpl_sql_thread_info *sql_info= thd->system_thread_info.rpl_sql_info;
+ if (sql_info->cached_charset_compare(charset))
+ {
+ /* Verify that we support the charsets found in the event. */
+ if (!(thd->variables.character_set_client=
+ get_charset(uint2korr(charset), MYF(MY_WME))) ||
+ !(thd->variables.collation_connection=
+ get_charset(uint2korr(charset+2), MYF(MY_WME))) ||
+ !(thd->variables.collation_server=
+ get_charset(uint2korr(charset+4), MYF(MY_WME))))
+ {
+ /*
+ We updated the thd->variables with nonsensical values (0). Let's
+ set them to something safe (i.e. which avoids crash), and we'll
+ stop with EE_UNKNOWN_CHARSET in compare_errors (unless set to
+ ignore this error).
+ */
+ set_slave_thread_default_charset(thd, rgi);
+ goto compare_errors;
+ }
+ thd->update_charset(); // for the charset change to take effect
+ /*
+ Reset thd->query_string.cs to the newly set value.
+ Note, there is a small flaw here. For a very short time frame
+ if the new charset is different from the old charset and
+ if another thread executes "SHOW PROCESSLIST" after
+ the above thd->set_query_and_id() and before this thd->set_query(),
+ and if the current query has some non-ASCII characters,
+ the another thread may see some '?' marks in the PROCESSLIST
+ result. This should be acceptable now. This is a reminder
+ to fix this if any refactoring happens here sometime.
+ */
+ thd->set_query((char*) query_arg, q_len_arg, thd->charset());
+ }
+ }
+ if (time_zone_len)
+ {
+ String tmp(time_zone_str, time_zone_len, &my_charset_bin);
+ if (!(thd->variables.time_zone= my_tz_find(thd, &tmp)))
+ {
+ my_error(ER_UNKNOWN_TIME_ZONE, MYF(0), tmp.c_ptr());
+ thd->variables.time_zone= global_system_variables.time_zone;
+ goto compare_errors;
+ }
+ }
+ if (lc_time_names_number)
+ {
+ if (!(thd->variables.lc_time_names=
+ my_locale_by_number(lc_time_names_number)))
+ {
+ my_printf_error(ER_UNKNOWN_ERROR,
+ "Unknown locale: '%d'", MYF(0), lc_time_names_number);
+ thd->variables.lc_time_names= &my_locale_en_US;
+ goto compare_errors;
+ }
+ }
+ else
+ thd->variables.lc_time_names= &my_locale_en_US;
+ if (charset_database_number)
+ {
+ CHARSET_INFO *cs;
+ if (!(cs= get_charset(charset_database_number, MYF(0))))
+ {
+ char buf[20];
+ int10_to_str((int) charset_database_number, buf, -10);
+ my_error(ER_UNKNOWN_COLLATION, MYF(0), buf);
+ goto compare_errors;
+ }
+ thd->variables.collation_database= cs;
+ }
+ else
+ thd->variables.collation_database= thd->db_charset;
+
+ /*
+ Record any GTID in the same transaction, so slave state is
+ transactionally consistent.
+ */
+ if (current_stmt_is_commit)
+ {
+ thd->variables.option_bits&= ~OPTION_GTID_BEGIN;
+ if (rgi->gtid_pending)
+ {
+ sub_id= rgi->gtid_sub_id;
+ rgi->gtid_pending= false;
+
+ gtid= rgi->current_gtid;
+ if (rpl_global_gtid_slave_state.record_gtid(thd, &gtid, sub_id, true, false))
+ {
+ int errcode= thd->get_stmt_da()->sql_errno();
+ if (!is_parallel_retry_error(rgi, errcode))
+ rli->report(ERROR_LEVEL, ER_CANNOT_UPDATE_GTID_STATE,
+ rgi->gtid_info(),
+ "Error during COMMIT: failed to update GTID state in "
+ "%s.%s: %d: %s",
+ "mysql", rpl_gtid_slave_state_table_name.str,
+ errcode,
+ thd->get_stmt_da()->message());
+ trans_rollback(thd);
+ sub_id= 0;
+ thd->is_slave_error= 1;
+ goto end;
+ }
+ }
+ }
+
+ thd->table_map_for_update= (table_map)table_map_for_update;
+ thd->set_invoker(&user, &host);
+ /*
+ Flag if we need to rollback the statement transaction on
+ slave if it by chance succeeds.
+ If we expected a non-zero error code and get nothing and,
+ it is a concurrency issue or ignorable issue, effects
+ of the statement should be rolled back.
+ */
+ if (expected_error &&
+ (ignored_error_code(expected_error) ||
+ concurrency_error_code(expected_error)))
+ {
+ thd->variables.option_bits|= OPTION_MASTER_SQL_ERROR;
+ thd->variables.option_bits&= ~OPTION_GTID_BEGIN;
+ }
+ /* Execute the query (note that we bypass dispatch_command()) */
+ Parser_state parser_state;
+ if (!parser_state.init(thd, thd->query(), thd->query_length()))
+ {
+ DBUG_ASSERT(thd->m_digest == NULL);
+ thd->m_digest= & thd->m_digest_state;
+ DBUG_ASSERT(thd->m_statement_psi == NULL);
+ thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state,
+ stmt_info_rpl.m_key,
+ thd->db, thd->db_length,
+ thd->charset());
+ THD_STAGE_INFO(thd, stage_init);
+ MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, thd->query(), thd->query_length());
+ if (thd->m_digest != NULL)
+ thd->m_digest->reset(thd->m_token_array, max_digest_length);
+
+ mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);
+ /* Finalize server status flags after executing a statement. */
+ thd->update_server_status();
+ log_slow_statement(thd);
+ }
+
+ thd->variables.option_bits&= ~OPTION_MASTER_SQL_ERROR;
+
+ /*
+ Resetting the enable_slow_log thd variable.
+
+ We need to reset it back to the opt_log_slow_slave_statements
+ value after the statement execution (and slow logging
+ is done). It might have changed if the statement was an
+ admin statement (in which case, down in mysql_parse execution
+ thd->enable_slow_log is set to the value of
+ opt_log_slow_admin_statements).
+ */
+ thd->enable_slow_log= opt_log_slow_slave_statements;
+ }
+ else
+ {
+ /*
+ The query got a really bad error on the master (thread killed etc),
+ which could be inconsistent. Parse it to test the table names: if the
+ replicate-*-do|ignore-table rules say "this query must be ignored" then
+ we exit gracefully; otherwise we warn about the bad error and tell DBA
+ to check/fix it.
+ */
+ if (mysql_test_parse_for_slave(thd, thd->query(), thd->query_length()))
+ clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); /* Can ignore query */
+ else
+ {
+ rli->report(ERROR_LEVEL, expected_error, rgi->gtid_info(),
+ "\
+Query partially completed on the master (error on master: %d) \
+and was aborted. There is a chance that your master is inconsistent at this \
+point. If you are sure that your master is ok, run this query manually on the \
+slave and then restart the slave with SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; \
+START SLAVE; . Query: '%s'", expected_error, thd->query());
+ thd->is_slave_error= 1;
+ }
+ goto end;
+ }
+
+ /* If the query was not ignored, it is printed to the general log */
+ if (!thd->is_error() ||
+ thd->get_stmt_da()->sql_errno() != ER_SLAVE_IGNORED_TABLE)
+ general_log_write(thd, COM_QUERY, thd->query(), thd->query_length());
+ else
+ {
+ /*
+ Bug#54201: If we skip an INSERT query that uses auto_increment, then we
+ should reset any @@INSERT_ID set by an Intvar_log_event associated with
+ the query; otherwise the @@INSERT_ID will linger until the next INSERT
+ that uses auto_increment and may affect extra triggers on the slave etc.
+
+ We reset INSERT_ID unconditionally; it is probably cheaper than
+ checking if it is necessary.
+ */
+ thd->auto_inc_intervals_forced.empty();
+ }
+
+compare_errors:
+ /*
+ In the slave thread, we may sometimes execute some DROP / * 40005
+ TEMPORARY * / TABLE that come from parts of binlogs (likely if we
+ use RESET SLAVE or CHANGE MASTER TO), while the temporary table
+ has already been dropped. To ignore such irrelevant "table does
+ not exist errors", we silently clear the error if TEMPORARY was used.
+ */
+ if (thd->lex->sql_command == SQLCOM_DROP_TABLE && thd->lex->drop_temporary &&
+ thd->is_error() && thd->get_stmt_da()->sql_errno() == ER_BAD_TABLE_ERROR &&
+ !expected_error)
+ thd->get_stmt_da()->reset_diagnostics_area();
+ /*
+ If we expected a non-zero error code, and we don't get the same error
+ code, and it should be ignored or is related to a concurrency issue.
+ */
+ actual_error= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0;
+ DBUG_PRINT("info",("expected_error: %d sql_errno: %d",
+ expected_error, actual_error));
+
+ if ((expected_error &&
+ !test_if_equal_repl_errors(expected_error, actual_error) &&
+ !concurrency_error_code(expected_error)) &&
+ !ignored_error_code(actual_error) &&
+ !ignored_error_code(expected_error))
+ {
+ rli->report(ERROR_LEVEL, 0, rgi->gtid_info(),
+ "Query caused different errors on master and slave. "
+ "Error on master: message (format)='%s' error code=%d ; "
+ "Error on slave: actual message='%s', error code=%d. "
+ "Default database: '%s'. Query: '%s'",
+ ER_SAFE(expected_error),
+ expected_error,
+ actual_error ? thd->get_stmt_da()->message() : "no error",
+ actual_error,
+ print_slave_db_safe(db), query_arg);
+ thd->is_slave_error= 1;
+ }
+ /*
+ If we get the same error code as expected and it is not a concurrency
+ issue, or should be ignored.
+ */
+ else if ((test_if_equal_repl_errors(expected_error, actual_error) &&
+ !concurrency_error_code(expected_error)) ||
+ ignored_error_code(actual_error))
+ {
+ DBUG_PRINT("info",("error ignored"));
+ clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
+ if (actual_error == ER_QUERY_INTERRUPTED ||
+ actual_error == ER_CONNECTION_KILLED)
+ thd->reset_killed();
+ }
+ /*
+ Other cases: mostly we expected no error and get one.
+ */
+ else if (thd->is_slave_error || thd->is_fatal_error)
+ {
+ if (!is_parallel_retry_error(rgi, actual_error))
+ rli->report(ERROR_LEVEL, actual_error, rgi->gtid_info(),
+ "Error '%s' on query. Default database: '%s'. Query: '%s'",
+ (actual_error ? thd->get_stmt_da()->message() :
+ "unexpected success or fatal error"),
+ print_slave_db_safe(thd->db), query_arg);
+ thd->is_slave_error= 1;
+ }
+
+ /*
+ TODO: compare the values of "affected rows" around here. Something
+ like:
+ if ((uint32) affected_in_event != (uint32) affected_on_slave)
+ {
+ sql_print_error("Slave: did not get the expected number of affected \
+ rows running query from master - expected %d, got %d (this numbers \
+ should have matched modulo 4294967296).", 0, ...);
+ thd->is_slave_error = 1;
+ }
+ We may also want an option to tell the slave to ignore "affected"
+ mismatch. This mismatch could be implemented with a new ER_ code, and
+ to ignore it you would use --slave-skip-errors...
+
+ To do the comparison we need to know the value of "affected" which the
+ above mysql_parse() computed. And we need to know the value of
+ "affected" in the master's binlog. Both will be implemented later. The
+ important thing is that we now have the format ready to log the values
+ of "affected" in the binlog. So we can release 5.0.0 before effectively
+ logging "affected" and effectively comparing it.
+ */
+ } /* End of if (db_ok(... */
+
+ {
+ /**
+ The following failure injecion works in cooperation with tests
+ setting @@global.debug= 'd,stop_slave_middle_group'.
+ The sql thread receives the killed status and will proceed
+ to shutdown trying to finish incomplete events group.
+ */
+ DBUG_EXECUTE_IF("stop_slave_middle_group",
+ if (!current_stmt_is_commit && is_begin() == 0)
+ {
+ if (thd->transaction.all.modified_non_trans_table)
+ const_cast<Relay_log_info*>(rli)->abort_slave= 1;
+ };);
+ }
+
+end:
+ if (sub_id && !thd->is_slave_error)
+ rpl_global_gtid_slave_state.update_state_hash(sub_id, &gtid, rgi);
+
+ /*
+ Probably we have set thd->query, thd->db, thd->catalog to point to places
+ in the data_buf of this event. Now the event is going to be deleted
+ probably, so data_buf will be freed, so the thd->... listed above will be
+ pointers to freed memory.
+ So we must set them to 0, so that those bad pointers values are not later
+ used. Note that "cleanup" queries like automatic DROP TEMPORARY TABLE
+ don't suffer from these assignments to 0 as DROP TEMPORARY
+ TABLE uses the db.table syntax.
+ */
+ thd->catalog= 0;
+ thd->set_db(NULL, 0); /* will free the current database */
+ thd->reset_query();
+ DBUG_PRINT("info", ("end: query= 0"));
+
+ /* Mark the statement completed. */
+ MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
+ thd->m_statement_psi= NULL;
+ thd->m_digest= NULL;
+
+ /*
+ As a disk space optimization, future masters will not log an event for
+ LAST_INSERT_ID() if that function returned 0 (and thus they will be able
+ to replace the THD::stmt_depends_on_first_successful_insert_id_in_prev_stmt
+ variable by (THD->first_successful_insert_id_in_prev_stmt > 0) ; with the
+ resetting below we are ready to support that.
+ */
+ thd->first_successful_insert_id_in_prev_stmt_for_binlog= 0;
+ thd->first_successful_insert_id_in_prev_stmt= 0;
+ thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0;
+ free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
+ DBUG_RETURN(thd->is_slave_error);
+}
+
+int Query_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ return Log_event::do_update_pos(rgi);
+}
+
+
+Log_event::enum_skip_reason
+Query_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ DBUG_ENTER("Query_log_event::do_shall_skip");
+ DBUG_PRINT("debug", ("query: '%s' q_len: %d", query, q_len));
+ DBUG_ASSERT(query && q_len > 0);
+ DBUG_ASSERT(thd == rgi->thd);
+
+ /*
+ An event skipped due to @@skip_replication must not be counted towards the
+ number of events to be skipped due to @@sql_slave_skip_counter.
+ */
+ if (flags & LOG_EVENT_SKIP_REPLICATION_F &&
+ opt_replicate_events_marked_for_skip != RPL_SKIP_REPLICATE)
+ DBUG_RETURN(Log_event::EVENT_SKIP_IGNORE);
+
+ if (rli->slave_skip_counter > 0)
+ {
+ if (is_begin())
+ {
+ thd->variables.option_bits|= OPTION_BEGIN | OPTION_GTID_BEGIN;
+ DBUG_RETURN(Log_event::continue_group(rgi));
+ }
+
+ if (is_commit() || is_rollback())
+ {
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_GTID_BEGIN);
+ DBUG_RETURN(Log_event::EVENT_SKIP_COUNT);
+ }
+ }
+ DBUG_RETURN(Log_event::do_shall_skip(rgi));
+}
+
+
+bool
+Query_log_event::peek_is_commit_rollback(const char *event_start,
+ size_t event_len, uint8 checksum_alg)
+{
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ if (event_len > BINLOG_CHECKSUM_LEN)
+ event_len-= BINLOG_CHECKSUM_LEN;
+ else
+ event_len= 0;
+ }
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ if (event_len < LOG_EVENT_HEADER_LEN + QUERY_HEADER_LEN || event_len < 9)
+ return false;
+ return !memcmp(event_start + (event_len-7), "\0COMMIT", 7) ||
+ !memcmp(event_start + (event_len-9), "\0ROLLBACK", 9);
+}
+
+#endif
+
+
+/**************************************************************************
+ Start_log_event_v3 methods
+**************************************************************************/
+
+#ifndef MYSQL_CLIENT
+Start_log_event_v3::Start_log_event_v3()
+ :Log_event(), created(0), binlog_version(BINLOG_VERSION),
+ dont_set_created(0)
+{
+ memcpy(server_version, ::server_version, ST_SERVER_VER_LEN);
+}
+#endif
+
+/*
+ Start_log_event_v3::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Start_log_event_v3::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[12 + ST_SERVER_VER_LEN + 14 + 22], *pos;
+ pos= strmov(buf, "Server ver: ");
+ pos= strmov(pos, server_version);
+ pos= strmov(pos, ", Binlog ver: ");
+ pos= int10_to_str(binlog_version, pos, 10);
+ protocol->store(buf, (uint) (pos-buf), &my_charset_bin);
+}
+#endif
+
+
+/*
+ Start_log_event_v3::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Start_log_event_v3::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ DBUG_ENTER("Start_log_event_v3::print");
+
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ if (!print_event_info->short_form)
+ {
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\tStart: binlog v %d, server v %s created ",
+ binlog_version, server_version);
+ print_timestamp(&cache);
+ if (created)
+ my_b_printf(&cache," at startup");
+ my_b_printf(&cache, "\n");
+ if (flags & LOG_EVENT_BINLOG_IN_USE_F)
+ my_b_printf(&cache, "# Warning: this binlog is either in use or was not "
+ "closed properly.\n");
+ }
+ if (!is_artificial_event() && created)
+ {
+#ifdef WHEN_WE_HAVE_THE_RESET_CONNECTION_SQL_COMMAND
+ /*
+ This is for mysqlbinlog: like in replication, we want to delete the stale
+ tmp files left by an unclean shutdown of mysqld (temporary tables)
+ and rollback unfinished transaction.
+ Probably this can be done with RESET CONNECTION (syntax to be defined).
+ */
+ my_b_printf(&cache,"RESET CONNECTION%s\n", print_event_info->delimiter);
+#else
+ my_b_printf(&cache,"ROLLBACK%s\n", print_event_info->delimiter);
+#endif
+ }
+ if (temp_buf &&
+ print_event_info->base64_output_mode != BASE64_OUTPUT_NEVER &&
+ !print_event_info->short_form)
+ {
+ if (print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS)
+ my_b_printf(&cache, "BINLOG '\n");
+ print_base64(&cache, print_event_info, FALSE);
+ print_event_info->printed_fd_event= TRUE;
+ }
+ DBUG_VOID_RETURN;
+}
+#endif /* MYSQL_CLIENT */
+
+/*
+ Start_log_event_v3::Start_log_event_v3()
+*/
+
+Start_log_event_v3::Start_log_event_v3(const char* buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ :Log_event(buf, description_event), binlog_version(BINLOG_VERSION)
+{
+ if (event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET)
+ {
+ server_version[0]= 0;
+ return;
+ }
+ buf+= LOG_EVENT_MINIMAL_HEADER_LEN;
+ binlog_version= uint2korr(buf+ST_BINLOG_VER_OFFSET);
+ memcpy(server_version, buf+ST_SERVER_VER_OFFSET,
+ ST_SERVER_VER_LEN);
+ // prevent overrun if log is corrupted on disk
+ server_version[ST_SERVER_VER_LEN-1]= 0;
+ created= uint4korr(buf+ST_CREATED_OFFSET);
+ dont_set_created= 1;
+}
+
+
+/*
+ Start_log_event_v3::write()
+*/
+
+#ifndef MYSQL_CLIENT
+bool Start_log_event_v3::write(IO_CACHE* file)
+{
+ char buff[START_V3_HEADER_LEN];
+ int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version);
+ memcpy(buff + ST_SERVER_VER_OFFSET,server_version,ST_SERVER_VER_LEN);
+ if (!dont_set_created)
+ created= get_time(); // this sets when and when_sec_part as a side effect
+ int4store(buff + ST_CREATED_OFFSET,created);
+ return (write_header(file, sizeof(buff)) ||
+ wrapper_my_b_safe_write(file, (uchar*) buff, sizeof(buff)) ||
+ write_footer(file));
+}
+#endif
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+/**
+ Start_log_event_v3::do_apply_event() .
+ The master started
+
+ IMPLEMENTATION
+ - To handle the case where the master died without having time to write
+ DROP TEMPORARY TABLE, DO RELEASE_LOCK (prepared statements' deletion is
+ TODO), we clean up all temporary tables that we got, if we are sure we
+ can (see below).
+
+ @todo
+ - Remove all active user locks.
+ Guilhem 2003-06: this is true but not urgent: the worst it can cause is
+ the use of a bit of memory for a user lock which will not be used
+ anymore. If the user lock is later used, the old one will be released. In
+ other words, no deadlock problem.
+*/
+
+int Start_log_event_v3::do_apply_event(rpl_group_info *rgi)
+{
+ DBUG_ENTER("Start_log_event_v3::do_apply_event");
+ int error= 0;
+ Relay_log_info *rli= rgi->rli;
+
+ switch (binlog_version)
+ {
+ case 3:
+ case 4:
+ /*
+ This can either be 4.x (then a Start_log_event_v3 is only at master
+ startup so we are sure the master has restarted and cleared his temp
+ tables; the event always has 'created'>0) or 5.0 (then we have to test
+ 'created').
+ */
+ if (created)
+ {
+ rli->close_temporary_tables();
+
+ /*
+ The following is only false if we get here with a BINLOG statement
+ */
+ if (rli->mi)
+ cleanup_load_tmpdir(&rli->mi->cmp_connection_name);
+ }
+ break;
+
+ /*
+ Now the older formats; in that case load_tmpdir is cleaned up by the I/O
+ thread.
+ */
+ case 1:
+ if (strncmp(rli->relay_log.description_event_for_exec->server_version,
+ "3.23.57",7) >= 0 && created)
+ {
+ /*
+ Can distinguish, based on the value of 'created': this event was
+ generated at master startup.
+ */
+ rli->close_temporary_tables();
+ }
+ /*
+ Otherwise, can't distinguish a Start_log_event generated at
+ master startup and one generated by master FLUSH LOGS, so cannot
+ be sure temp tables have to be dropped. So do nothing.
+ */
+ break;
+ default:
+ /* this case is impossible */
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(error);
+}
+#endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */
+
+/***************************************************************************
+ Format_description_log_event methods
+****************************************************************************/
+
+/**
+ Format_description_log_event 1st ctor.
+
+ Ctor. Can be used to create the event to write to the binary log (when the
+ server starts or when FLUSH LOGS), or to create artificial events to parse
+ binlogs from MySQL 3.23 or 4.x.
+ When in a client, only the 2nd use is possible.
+
+ @param binlog_version the binlog version for which we want to build
+ an event. Can be 1 (=MySQL 3.23), 3 (=4.0.x
+ x>=2 and 4.1) or 4 (MySQL 5.0). Note that the
+ old 4.0 (binlog version 2) is not supported;
+ it should not be used for replication with
+ 5.0.
+ @param server_ver a string containing the server version.
+*/
+
+Format_description_log_event::
+Format_description_log_event(uint8 binlog_ver, const char* server_ver)
+ :Start_log_event_v3(), event_type_permutation(0)
+{
+ binlog_version= binlog_ver;
+ switch (binlog_ver) {
+ case 4: /* MySQL 5.0 */
+ memcpy(server_version, ::server_version, ST_SERVER_VER_LEN);
+ DBUG_EXECUTE_IF("pretend_version_50034_in_binlog",
+ strmov(server_version, "5.0.34"););
+ common_header_len= LOG_EVENT_HEADER_LEN;
+ number_of_event_types= LOG_EVENT_TYPES;
+ /* we'll catch my_malloc() error in is_valid() */
+ post_header_len=(uint8*) my_malloc(number_of_event_types*sizeof(uint8)
+ + BINLOG_CHECKSUM_ALG_DESC_LEN,
+ MYF(0));
+ /*
+ This long list of assignments is not beautiful, but I see no way to
+ make it nicer, as the right members are #defines, not array members, so
+ it's impossible to write a loop.
+ */
+ if (post_header_len)
+ {
+#ifndef DBUG_OFF
+ // Allows us to sanity-check that all events initialized their
+ // events (see the end of this 'if' block).
+ memset(post_header_len, 255, number_of_event_types*sizeof(uint8));
+#endif
+
+ /* Note: all event types must explicitly fill in their lengths here. */
+ post_header_len[START_EVENT_V3-1]= START_V3_HEADER_LEN;
+ post_header_len[QUERY_EVENT-1]= QUERY_HEADER_LEN;
+ post_header_len[STOP_EVENT-1]= STOP_HEADER_LEN;
+ post_header_len[ROTATE_EVENT-1]= ROTATE_HEADER_LEN;
+ post_header_len[INTVAR_EVENT-1]= INTVAR_HEADER_LEN;
+ post_header_len[LOAD_EVENT-1]= LOAD_HEADER_LEN;
+ post_header_len[SLAVE_EVENT-1]= SLAVE_HEADER_LEN;
+ post_header_len[CREATE_FILE_EVENT-1]= CREATE_FILE_HEADER_LEN;
+ post_header_len[APPEND_BLOCK_EVENT-1]= APPEND_BLOCK_HEADER_LEN;
+ post_header_len[EXEC_LOAD_EVENT-1]= EXEC_LOAD_HEADER_LEN;
+ post_header_len[DELETE_FILE_EVENT-1]= DELETE_FILE_HEADER_LEN;
+ post_header_len[NEW_LOAD_EVENT-1]= NEW_LOAD_HEADER_LEN;
+ post_header_len[RAND_EVENT-1]= RAND_HEADER_LEN;
+ post_header_len[USER_VAR_EVENT-1]= USER_VAR_HEADER_LEN;
+ post_header_len[FORMAT_DESCRIPTION_EVENT-1]= FORMAT_DESCRIPTION_HEADER_LEN;
+ post_header_len[XID_EVENT-1]= XID_HEADER_LEN;
+ post_header_len[BEGIN_LOAD_QUERY_EVENT-1]= BEGIN_LOAD_QUERY_HEADER_LEN;
+ post_header_len[EXECUTE_LOAD_QUERY_EVENT-1]= EXECUTE_LOAD_QUERY_HEADER_LEN;
+ /*
+ The PRE_GA events are never be written to any binlog, but
+ their lengths are included in Format_description_log_event.
+ Hence, we need to be assign some value here, to avoid reading
+ uninitialized memory when the array is written to disk.
+ */
+ post_header_len[PRE_GA_WRITE_ROWS_EVENT-1] = 0;
+ post_header_len[PRE_GA_UPDATE_ROWS_EVENT-1] = 0;
+ post_header_len[PRE_GA_DELETE_ROWS_EVENT-1] = 0;
+
+ post_header_len[TABLE_MAP_EVENT-1]= TABLE_MAP_HEADER_LEN;
+ post_header_len[WRITE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ post_header_len[UPDATE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ post_header_len[DELETE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ /*
+ We here have the possibility to simulate a master of before we changed
+ the table map id to be stored in 6 bytes: when it was stored in 4
+ bytes (=> post_header_len was 6). This is used to test backward
+ compatibility.
+ This code can be removed after a few months (today is Dec 21st 2005),
+ when we know that the 4-byte masters are not deployed anymore (check
+ with Tomas Ulin first!), and the accompanying test (rpl_row_4_bytes)
+ too.
+ */
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ post_header_len[TABLE_MAP_EVENT-1]=
+ post_header_len[WRITE_ROWS_EVENT_V1-1]=
+ post_header_len[UPDATE_ROWS_EVENT_V1-1]=
+ post_header_len[DELETE_ROWS_EVENT_V1-1]= 6;);
+ post_header_len[INCIDENT_EVENT-1]= INCIDENT_HEADER_LEN;
+ post_header_len[HEARTBEAT_LOG_EVENT-1]= 0;
+ post_header_len[IGNORABLE_LOG_EVENT-1]= 0;
+ post_header_len[ROWS_QUERY_LOG_EVENT-1]= 0;
+ post_header_len[WRITE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2;
+ post_header_len[UPDATE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2;
+ post_header_len[DELETE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2;
+
+ // Set header length of the reserved events to 0
+ memset(post_header_len + MYSQL_EVENTS_END - 1, 0,
+ (MARIA_EVENTS_BEGIN - MYSQL_EVENTS_END)*sizeof(uint8));
+
+ // Set header lengths of Maria events
+ post_header_len[ANNOTATE_ROWS_EVENT-1]= ANNOTATE_ROWS_HEADER_LEN;
+ post_header_len[BINLOG_CHECKPOINT_EVENT-1]=
+ BINLOG_CHECKPOINT_HEADER_LEN;
+ post_header_len[GTID_EVENT-1]= GTID_HEADER_LEN;
+ post_header_len[GTID_LIST_EVENT-1]= GTID_LIST_HEADER_LEN;
+
+ // Sanity-check that all post header lengths are initialized.
+ int i;
+ for (i=0; i<number_of_event_types; i++)
+ DBUG_ASSERT(post_header_len[i] != 255);
+ }
+ break;
+
+ case 1: /* 3.23 */
+ case 3: /* 4.0.x x>=2 */
+ /*
+ We build an artificial (i.e. not sent by the master) event, which
+ describes what those old master versions send.
+ */
+ if (binlog_ver==1)
+ strmov(server_version, server_ver ? server_ver : "3.23");
+ else
+ strmov(server_version, server_ver ? server_ver : "4.0");
+ common_header_len= binlog_ver==1 ? OLD_HEADER_LEN :
+ LOG_EVENT_MINIMAL_HEADER_LEN;
+ /*
+ The first new event in binlog version 4 is Format_desc. So any event type
+ after that does not exist in older versions. We use the events known by
+ version 3, even if version 1 had only a subset of them (this is not a
+ problem: it uses a few bytes for nothing but unifies code; it does not
+ make the slave detect less corruptions).
+ */
+ number_of_event_types= FORMAT_DESCRIPTION_EVENT - 1;
+ post_header_len=(uint8*) my_malloc(number_of_event_types*sizeof(uint8),
+ MYF(0));
+ if (post_header_len)
+ {
+ post_header_len[START_EVENT_V3-1]= START_V3_HEADER_LEN;
+ post_header_len[QUERY_EVENT-1]= QUERY_HEADER_MINIMAL_LEN;
+ post_header_len[STOP_EVENT-1]= 0;
+ post_header_len[ROTATE_EVENT-1]= (binlog_ver==1) ? 0 : ROTATE_HEADER_LEN;
+ post_header_len[INTVAR_EVENT-1]= 0;
+ post_header_len[LOAD_EVENT-1]= LOAD_HEADER_LEN;
+ post_header_len[SLAVE_EVENT-1]= 0;
+ post_header_len[CREATE_FILE_EVENT-1]= CREATE_FILE_HEADER_LEN;
+ post_header_len[APPEND_BLOCK_EVENT-1]= APPEND_BLOCK_HEADER_LEN;
+ post_header_len[EXEC_LOAD_EVENT-1]= EXEC_LOAD_HEADER_LEN;
+ post_header_len[DELETE_FILE_EVENT-1]= DELETE_FILE_HEADER_LEN;
+ post_header_len[NEW_LOAD_EVENT-1]= post_header_len[LOAD_EVENT-1];
+ post_header_len[RAND_EVENT-1]= 0;
+ post_header_len[USER_VAR_EVENT-1]= 0;
+ }
+ break;
+ default: /* Includes binlog version 2 i.e. 4.0.x x<=1 */
+ post_header_len= 0; /* will make is_valid() fail */
+ break;
+ }
+ calc_server_version_split();
+ checksum_alg= (uint8) BINLOG_CHECKSUM_ALG_UNDEF;
+}
+
+
+/**
+ The problem with this constructor is that the fixed header may have a
+ length different from this version, but we don't know this length as we
+ have not read the Format_description_log_event which says it, yet. This
+ length is in the post-header of the event, but we don't know where the
+ post-header starts.
+
+ So this type of event HAS to:
+ - either have the header's length at the beginning (in the header, at a
+ fixed position which will never be changed), not in the post-header. That
+ would make the header be "shifted" compared to other events.
+ - or have a header of size LOG_EVENT_MINIMAL_HEADER_LEN (19), in all future
+ versions, so that we know for sure.
+
+ I (Guilhem) chose the 2nd solution. Rotate has the same constraint (because
+ it is sent before Format_description_log_event).
+*/
+
+Format_description_log_event::
+Format_description_log_event(const char* buf,
+ uint event_len,
+ const
+ Format_description_log_event*
+ description_event)
+ :Start_log_event_v3(buf, event_len, description_event),
+ common_header_len(0), post_header_len(NULL), event_type_permutation(0)
+{
+ DBUG_ENTER("Format_description_log_event::Format_description_log_event(char*,...)");
+ if (!Start_log_event_v3::is_valid())
+ DBUG_VOID_RETURN; /* sanity check */
+ buf+= LOG_EVENT_MINIMAL_HEADER_LEN;
+ if ((common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < OLD_HEADER_LEN)
+ DBUG_VOID_RETURN; /* sanity check */
+ number_of_event_types=
+ event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET + 1);
+ DBUG_PRINT("info", ("common_header_len=%d number_of_event_types=%d",
+ common_header_len, number_of_event_types));
+ /* If alloc fails, we'll detect it in is_valid() */
+
+ post_header_len= (uint8*) my_memdup((uchar*)buf+ST_COMMON_HEADER_LEN_OFFSET+1,
+ number_of_event_types*
+ sizeof(*post_header_len),
+ MYF(0));
+ calc_server_version_split();
+ if (!is_version_before_checksum(&server_version_split))
+ {
+ /* the last bytes are the checksum alg desc and value (or value's room) */
+ number_of_event_types -= BINLOG_CHECKSUM_ALG_DESC_LEN;
+ checksum_alg= post_header_len[number_of_event_types];
+ }
+ else
+ {
+ checksum_alg= (uint8) BINLOG_CHECKSUM_ALG_UNDEF;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+#ifndef MYSQL_CLIENT
+bool Format_description_log_event::write(IO_CACHE* file)
+{
+ bool ret;
+ bool no_checksum;
+ /*
+ We don't call Start_log_event_v3::write() because this would make 2
+ my_b_safe_write().
+ */
+ uchar buff[START_V3_HEADER_LEN+1];
+ size_t rec_size= sizeof(buff) + BINLOG_CHECKSUM_ALG_DESC_LEN +
+ number_of_event_types;
+ int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version);
+ memcpy((char*) buff + ST_SERVER_VER_OFFSET,server_version,ST_SERVER_VER_LEN);
+ if (!dont_set_created)
+ created= get_time();
+ int4store(buff + ST_CREATED_OFFSET,created);
+ buff[ST_COMMON_HEADER_LEN_OFFSET]= common_header_len;
+ /*
+ if checksum is requested
+ record the checksum-algorithm descriptor next to
+ post_header_len vector which will be followed by the checksum value.
+ Master is supposed to trigger checksum computing by binlog_checksum_options,
+ slave does it via marking the event according to
+ FD_queue checksum_alg value.
+ */
+ compile_time_assert(sizeof(BINLOG_CHECKSUM_ALG_DESC_LEN == 1));
+#ifndef DBUG_OFF
+ data_written= 0; // to prepare for need_checksum assert
+#endif
+ uchar checksum_byte= need_checksum() ?
+ checksum_alg : (uint8) BINLOG_CHECKSUM_ALG_OFF;
+ /*
+ FD of checksum-aware server is always checksum-equipped, (V) is in,
+ regardless of @@global.binlog_checksum policy.
+ Thereby a combination of (A) == 0, (V) != 0 means
+ it's the checksum-aware server's FD event that heads checksum-free binlog
+ file.
+ Here 0 stands for checksumming OFF to evaluate (V) as 0 is that case.
+ A combination of (A) != 0, (V) != 0 denotes FD of the checksum-aware server
+ heading the checksummed binlog.
+ (A), (V) presence in FD of the checksum-aware server makes the event
+ 1 + 4 bytes bigger comparing to the former FD.
+ */
+
+ if ((no_checksum= (checksum_alg == BINLOG_CHECKSUM_ALG_OFF)))
+ {
+ checksum_alg= BINLOG_CHECKSUM_ALG_CRC32; // Forcing (V) room to fill anyway
+ }
+ ret= (write_header(file, rec_size) ||
+ wrapper_my_b_safe_write(file, buff, sizeof(buff)) ||
+ wrapper_my_b_safe_write(file, (uchar*)post_header_len,
+ number_of_event_types) ||
+ wrapper_my_b_safe_write(file, &checksum_byte, sizeof(checksum_byte)) ||
+ write_footer(file));
+ if (no_checksum)
+ checksum_alg= BINLOG_CHECKSUM_ALG_OFF;
+ return ret;
+}
+#endif
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int Format_description_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ int ret= 0;
+ Relay_log_info const *rli= rgi->rli;
+ DBUG_ENTER("Format_description_log_event::do_apply_event");
+
+ /*
+ As a transaction NEVER spans on 2 or more binlogs:
+ if we have an active transaction at this point, the master died
+ while writing the transaction to the binary log, i.e. while
+ flushing the binlog cache to the binlog. XA guarantees that master has
+ rolled back. So we roll back.
+ Note: this event could be sent by the master to inform us of the
+ format of its binlog; in other words maybe it is not at its
+ original place when it comes to us; we'll know this by checking
+ log_pos ("artificial" events have log_pos == 0).
+ */
+ if (!is_artificial_event() && created && thd->transaction.all.ha_list)
+ {
+ /* This is not an error (XA is safe), just an information */
+ rli->report(INFORMATION_LEVEL, 0, NULL,
+ "Rolling back unfinished transaction (no COMMIT "
+ "or ROLLBACK in relay log). A probable cause is that "
+ "the master died while writing the transaction to "
+ "its binary log, thus rolled back too.");
+ rgi->cleanup_context(thd, 1);
+ }
+
+ /*
+ If this event comes from ourselves, there is no cleaning task to
+ perform, we don't call Start_log_event_v3::do_apply_event()
+ (this was just to update the log's description event).
+ */
+ if (server_id != (uint32) global_system_variables.server_id)
+ {
+ /*
+ If the event was not requested by the slave i.e. the master sent
+ it while the slave asked for a position >4, the event will make
+ rli->group_master_log_pos advance. Say that the slave asked for
+ position 1000, and the Format_desc event's end is 96. Then in
+ the beginning of replication rli->group_master_log_pos will be
+ 0, then 96, then jump to first really asked event (which is
+ >96). So this is ok.
+ */
+ ret= Start_log_event_v3::do_apply_event(rgi);
+ }
+
+ if (!ret)
+ {
+ /* Save the information describing this binlog */
+ delete rli->relay_log.description_event_for_exec;
+ const_cast<Relay_log_info *>(rli)->relay_log.description_event_for_exec= this;
+ }
+
+ DBUG_RETURN(ret);
+}
+
+int Format_description_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ if (server_id == (uint32) global_system_variables.server_id)
+ {
+ /*
+ We only increase the relay log position if we are skipping
+ events and do not touch any group_* variables, nor flush the
+ relay log info. If there is a crash, we will have to re-skip
+ the events again, but that is a minor issue.
+
+ If we do not skip stepping the group log position (and the
+ server id was changed when restarting the server), it might well
+ be that we start executing at a position that is invalid, e.g.,
+ at a Rows_log_event or a Query_log_event preceeded by a
+ Intvar_log_event instead of starting at a Table_map_log_event or
+ the Intvar_log_event respectively.
+ */
+ rgi->inc_event_relay_log_pos();
+ return 0;
+ }
+ else
+ {
+ return Log_event::do_update_pos(rgi);
+ }
+}
+
+Log_event::enum_skip_reason
+Format_description_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ return Log_event::EVENT_SKIP_NOT;
+}
+
+#endif
+
+static inline void
+do_server_version_split(char* version,
+ Format_description_log_event::master_version_split *split_versions)
+{
+ char *p= version, *r;
+ ulong number;
+ for (uint i= 0; i<=2; i++)
+ {
+ number= strtoul(p, &r, 10);
+ /*
+ It is an invalid version if any version number greater than 255 or
+ first number is not followed by '.'.
+ */
+ if (number < 256 && (*r == '.' || i != 0))
+ split_versions->ver[i]= (uchar) number;
+ else
+ {
+ split_versions->ver[0]= 0;
+ split_versions->ver[1]= 0;
+ split_versions->ver[2]= 0;
+ break;
+ }
+
+ p= r;
+ if (*r == '.')
+ p++; // skip the dot
+ }
+ if (strstr(p, "MariaDB") != 0 || strstr(p, "-maria-") != 0)
+ split_versions->kind=
+ Format_description_log_event::master_version_split::KIND_MARIADB;
+ else
+ split_versions->kind=
+ Format_description_log_event::master_version_split::KIND_MYSQL;
+}
+
+
+/**
+ Splits the event's 'server_version' string into three numeric pieces stored
+ into 'server_version_split':
+ X.Y.Zabc (X,Y,Z numbers, a not a digit) -> {X,Y,Z}
+ X.Yabc -> {X,Y,0}
+ 'server_version_split' is then used for lookups to find if the server which
+ created this event has some known bug.
+*/
+void Format_description_log_event::calc_server_version_split()
+{
+ do_server_version_split(server_version, &server_version_split);
+
+ DBUG_PRINT("info",("Format_description_log_event::server_version_split:"
+ " '%s' %d %d %d", server_version,
+ server_version_split.ver[0],
+ server_version_split.ver[1], server_version_split.ver[2]));
+}
+
+static inline ulong
+version_product(const Format_description_log_event::master_version_split* version_split)
+{
+ return ((version_split->ver[0] * 256 + version_split->ver[1]) * 256
+ + version_split->ver[2]);
+}
+
+/**
+ @return TRUE is the event's version is earlier than one that introduced
+ the replication event checksum. FALSE otherwise.
+*/
+bool
+Format_description_log_event::is_version_before_checksum(const master_version_split
+ *version_split)
+{
+ return version_product(version_split) <
+ (version_split->kind == master_version_split::KIND_MARIADB ?
+ checksum_version_product_mariadb : checksum_version_product_mysql);
+}
+
+/**
+ @param buf buffer holding serialized FD event
+ @param len netto (possible checksum is stripped off) length of the event buf
+
+ @return the version-safe checksum alg descriptor where zero
+ designates no checksum, 255 - the orginator is
+ checksum-unaware (effectively no checksum) and the actuall
+ [1-254] range alg descriptor.
+*/
+uint8 get_checksum_alg(const char* buf, ulong len)
+{
+ uint8 ret;
+ char version[ST_SERVER_VER_LEN];
+ Format_description_log_event::master_version_split version_split;
+
+ DBUG_ENTER("get_checksum_alg");
+ DBUG_ASSERT(buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT);
+
+ memcpy(version,
+ buf + LOG_EVENT_MINIMAL_HEADER_LEN + ST_SERVER_VER_OFFSET,
+ ST_SERVER_VER_LEN);
+ version[ST_SERVER_VER_LEN - 1]= 0;
+
+ do_server_version_split(version, &version_split);
+ ret= Format_description_log_event::is_version_before_checksum(&version_split) ?
+ (uint8) BINLOG_CHECKSUM_ALG_UNDEF :
+ * (uint8*) (buf + len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN);
+ DBUG_ASSERT(ret == BINLOG_CHECKSUM_ALG_OFF ||
+ ret == BINLOG_CHECKSUM_ALG_UNDEF ||
+ ret == BINLOG_CHECKSUM_ALG_CRC32);
+ DBUG_RETURN(ret);
+}
+
+
+ /**************************************************************************
+ Load_log_event methods
+ General note about Load_log_event: the binlogging of LOAD DATA INFILE is
+ going to be changed in 5.0 (or maybe in 5.1; not decided yet).
+ However, the 5.0 slave could still have to read such events (from a 4.x
+ master), convert them (which just means maybe expand the header, when 5.0
+ servers have a UID in events) (remember that whatever is after the header
+ will be like in 4.x, as this event's format is not modified in 5.0 as we
+ will use new types of events to log the new LOAD DATA INFILE features).
+ To be able to read/convert, we just need to not assume that the common
+ header is of length LOG_EVENT_HEADER_LEN (we must use the description
+ event).
+ Note that I (Guilhem) manually tested replication of a big LOAD DATA INFILE
+ between 3.23 and 5.0, and between 4.0 and 5.0, and it works fine (and the
+ positions displayed in SHOW SLAVE STATUS then are fine too).
+ **************************************************************************/
+
+/*
+ Load_log_event::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Load_log_event::print_query(THD *thd, bool need_db, const char *cs,
+ String *buf, my_off_t *fn_start,
+ my_off_t *fn_end, const char *qualify_db)
+{
+ if (need_db && db && db_len)
+ {
+ buf->append(STRING_WITH_LEN("use "));
+ append_identifier(thd, buf, db, db_len);
+ buf->append(STRING_WITH_LEN("; "));
+ }
+
+ buf->append(STRING_WITH_LEN("LOAD DATA "));
+
+ if (is_concurrent)
+ buf->append(STRING_WITH_LEN("CONCURRENT "));
+
+ if (fn_start)
+ *fn_start= buf->length();
+
+ if (check_fname_outside_temp_buf())
+ buf->append(STRING_WITH_LEN("LOCAL "));
+ buf->append(STRING_WITH_LEN("INFILE '"));
+ buf->append_for_single_quote(fname, fname_len);
+ buf->append(STRING_WITH_LEN("' "));
+
+ if (sql_ex.opt_flags & REPLACE_FLAG)
+ buf->append(STRING_WITH_LEN("REPLACE "));
+ else if (sql_ex.opt_flags & IGNORE_FLAG)
+ buf->append(STRING_WITH_LEN("IGNORE "));
+
+ buf->append(STRING_WITH_LEN("INTO"));
+
+ if (fn_end)
+ *fn_end= buf->length();
+
+ buf->append(STRING_WITH_LEN(" TABLE "));
+ if (qualify_db)
+ {
+ append_identifier(thd, buf, qualify_db, strlen(qualify_db));
+ buf->append(STRING_WITH_LEN("."));
+ }
+ append_identifier(thd, buf, table_name, table_name_len);
+
+ if (cs != NULL)
+ {
+ buf->append(STRING_WITH_LEN(" CHARACTER SET "));
+ buf->append(cs, strlen(cs));
+ }
+
+ /* We have to create all optional fields as the default is not empty */
+ buf->append(STRING_WITH_LEN(" FIELDS TERMINATED BY "));
+ pretty_print_str(buf, sql_ex.field_term, sql_ex.field_term_len);
+ if (sql_ex.opt_flags & OPT_ENCLOSED_FLAG)
+ buf->append(STRING_WITH_LEN(" OPTIONALLY "));
+ buf->append(STRING_WITH_LEN(" ENCLOSED BY "));
+ pretty_print_str(buf, sql_ex.enclosed, sql_ex.enclosed_len);
+
+ buf->append(STRING_WITH_LEN(" ESCAPED BY "));
+ pretty_print_str(buf, sql_ex.escaped, sql_ex.escaped_len);
+
+ buf->append(STRING_WITH_LEN(" LINES TERMINATED BY "));
+ pretty_print_str(buf, sql_ex.line_term, sql_ex.line_term_len);
+ if (sql_ex.line_start_len)
+ {
+ buf->append(STRING_WITH_LEN(" STARTING BY "));
+ pretty_print_str(buf, sql_ex.line_start, sql_ex.line_start_len);
+ }
+
+ if ((long) skip_lines > 0)
+ {
+ buf->append(STRING_WITH_LEN(" IGNORE "));
+ buf->append_ulonglong(skip_lines);
+ buf->append(STRING_WITH_LEN(" LINES "));
+ }
+
+ if (num_fields)
+ {
+ uint i;
+ const char *field= fields;
+ buf->append(STRING_WITH_LEN(" ("));
+ for (i = 0; i < num_fields; i++)
+ {
+ if (i)
+ {
+ /*
+ Yes, the space and comma is reversed here. But this is mostly dead
+ code, at most used when reading really old binlogs from old servers,
+ so better just leave it as is...
+ */
+ buf->append(STRING_WITH_LEN(" ,"));
+ }
+ append_identifier(thd, buf, field, field_lens[i]);
+ field+= field_lens[i] + 1;
+ }
+ buf->append(STRING_WITH_LEN(")"));
+ }
+}
+
+
+void Load_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char query_buffer[1024];
+ String query_str(query_buffer, sizeof(query_buffer), system_charset_info);
+
+ query_str.length(0);
+ print_query(thd, TRUE, NULL, &query_str, 0, 0, NULL);
+ protocol->store(query_str.ptr(), query_str.length(), &my_charset_bin);
+}
+#endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */
+
+
+#ifndef MYSQL_CLIENT
+
+/*
+ Load_log_event::write_data_header()
+*/
+
+bool Load_log_event::write_data_header(IO_CACHE* file)
+{
+ char buf[LOAD_HEADER_LEN];
+ int4store(buf + L_THREAD_ID_OFFSET, slave_proxy_id);
+ int4store(buf + L_EXEC_TIME_OFFSET, exec_time);
+ int4store(buf + L_SKIP_LINES_OFFSET, skip_lines);
+ buf[L_TBL_LEN_OFFSET] = (char)table_name_len;
+ buf[L_DB_LEN_OFFSET] = (char)db_len;
+ int4store(buf + L_NUM_FIELDS_OFFSET, num_fields);
+ return my_b_safe_write(file, (uchar*)buf, LOAD_HEADER_LEN) != 0;
+}
+
+
+/*
+ Load_log_event::write_data_body()
+*/
+
+bool Load_log_event::write_data_body(IO_CACHE* file)
+{
+ if (sql_ex.write_data(file))
+ return 1;
+ if (num_fields && fields && field_lens)
+ {
+ if (my_b_safe_write(file, (uchar*)field_lens, num_fields) ||
+ my_b_safe_write(file, (uchar*)fields, field_block_len))
+ return 1;
+ }
+ return (my_b_safe_write(file, (uchar*)table_name, table_name_len + 1) ||
+ my_b_safe_write(file, (uchar*)db, db_len + 1) ||
+ my_b_safe_write(file, (uchar*)fname, fname_len));
+}
+
+
+/*
+ Load_log_event::Load_log_event()
+*/
+
+Load_log_event::Load_log_event(THD *thd_arg, sql_exchange *ex,
+ const char *db_arg, const char *table_name_arg,
+ List<Item> &fields_arg,
+ bool is_concurrent_arg,
+ enum enum_duplicates handle_dup,
+ bool ignore, bool using_trans)
+ :Log_event(thd_arg,
+ thd_arg->thread_specific_used ? LOG_EVENT_THREAD_SPECIFIC_F : 0,
+ using_trans),
+ thread_id(thd_arg->thread_id),
+ slave_proxy_id(thd_arg->variables.pseudo_thread_id),
+ num_fields(0),fields(0),
+ field_lens(0),field_block_len(0),
+ table_name(table_name_arg ? table_name_arg : ""),
+ db(db_arg), fname(ex->file_name), local_fname(FALSE),
+ is_concurrent(is_concurrent_arg)
+{
+ time_t end_time;
+ time(&end_time);
+ exec_time = (ulong) (end_time - thd_arg->start_time);
+ /* db can never be a zero pointer in 4.0 */
+ db_len = (uint32) strlen(db);
+ table_name_len = (uint32) strlen(table_name);
+ fname_len = (fname) ? (uint) strlen(fname) : 0;
+ sql_ex.field_term = (char*) ex->field_term->ptr();
+ sql_ex.field_term_len = (uint8) ex->field_term->length();
+ sql_ex.enclosed = (char*) ex->enclosed->ptr();
+ sql_ex.enclosed_len = (uint8) ex->enclosed->length();
+ sql_ex.line_term = (char*) ex->line_term->ptr();
+ sql_ex.line_term_len = (uint8) ex->line_term->length();
+ sql_ex.line_start = (char*) ex->line_start->ptr();
+ sql_ex.line_start_len = (uint8) ex->line_start->length();
+ sql_ex.escaped = (char*) ex->escaped->ptr();
+ sql_ex.escaped_len = (uint8) ex->escaped->length();
+ sql_ex.opt_flags = 0;
+ sql_ex.cached_new_format = -1;
+
+ if (ex->dumpfile)
+ sql_ex.opt_flags|= DUMPFILE_FLAG;
+ if (ex->opt_enclosed)
+ sql_ex.opt_flags|= OPT_ENCLOSED_FLAG;
+
+ sql_ex.empty_flags= 0;
+
+ switch (handle_dup) {
+ case DUP_REPLACE:
+ sql_ex.opt_flags|= REPLACE_FLAG;
+ break;
+ case DUP_UPDATE: // Impossible here
+ case DUP_ERROR:
+ break;
+ }
+ if (ignore)
+ sql_ex.opt_flags|= IGNORE_FLAG;
+
+ if (!ex->field_term->length())
+ sql_ex.empty_flags |= FIELD_TERM_EMPTY;
+ if (!ex->enclosed->length())
+ sql_ex.empty_flags |= ENCLOSED_EMPTY;
+ if (!ex->line_term->length())
+ sql_ex.empty_flags |= LINE_TERM_EMPTY;
+ if (!ex->line_start->length())
+ sql_ex.empty_flags |= LINE_START_EMPTY;
+ if (!ex->escaped->length())
+ sql_ex.empty_flags |= ESCAPED_EMPTY;
+
+ skip_lines = ex->skip_lines;
+
+ List_iterator<Item> li(fields_arg);
+ field_lens_buf.length(0);
+ fields_buf.length(0);
+ Item* item;
+ while ((item = li++))
+ {
+ num_fields++;
+ uchar len = (uchar) strlen(item->name);
+ field_block_len += len + 1;
+ fields_buf.append(item->name, len + 1);
+ field_lens_buf.append((char*)&len, 1);
+ }
+
+ field_lens = (const uchar*)field_lens_buf.ptr();
+ fields = fields_buf.ptr();
+}
+#endif /* !MYSQL_CLIENT */
+
+
+/**
+ @note
+ The caller must do buf[event_len] = 0 before he starts using the
+ constructed event.
+*/
+Load_log_event::Load_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event)
+ :Log_event(buf, description_event), num_fields(0), fields(0),
+ field_lens(0),field_block_len(0),
+ table_name(0), db(0), fname(0), local_fname(FALSE),
+ /*
+ Load_log_event which comes from the binary log does not contain
+ information about the type of insert which was used on the master.
+ Assume that it was an ordinary, non-concurrent LOAD DATA.
+ */
+ is_concurrent(FALSE)
+{
+ DBUG_ENTER("Load_log_event");
+ /*
+ I (Guilhem) manually tested replication of LOAD DATA INFILE for 3.23->5.0,
+ 4.0->5.0 and 5.0->5.0 and it works.
+ */
+ if (event_len)
+ copy_log_event(buf, event_len,
+ (((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ?
+ LOAD_HEADER_LEN +
+ description_event->common_header_len :
+ LOAD_HEADER_LEN + LOG_EVENT_HEADER_LEN),
+ description_event);
+ /* otherwise it's a derived class, will call copy_log_event() itself */
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Load_log_event::copy_log_event()
+*/
+
+int Load_log_event::copy_log_event(const char *buf, ulong event_len,
+ int body_offset,
+ const Format_description_log_event *description_event)
+{
+ DBUG_ENTER("Load_log_event::copy_log_event");
+ uint data_len;
+ char* buf_end = (char*)buf + event_len;
+ /* this is the beginning of the post-header */
+ const char* data_head = buf + description_event->common_header_len;
+ slave_proxy_id= thread_id= uint4korr(data_head + L_THREAD_ID_OFFSET);
+ exec_time = uint4korr(data_head + L_EXEC_TIME_OFFSET);
+ skip_lines = uint4korr(data_head + L_SKIP_LINES_OFFSET);
+ table_name_len = (uint)data_head[L_TBL_LEN_OFFSET];
+ db_len = (uint)data_head[L_DB_LEN_OFFSET];
+ num_fields = uint4korr(data_head + L_NUM_FIELDS_OFFSET);
+
+ if ((int) event_len < body_offset)
+ DBUG_RETURN(1);
+ /*
+ Sql_ex.init() on success returns the pointer to the first byte after
+ the sql_ex structure, which is the start of field lengths array.
+ */
+ if (!(field_lens= (uchar*)sql_ex.init((char*)buf + body_offset,
+ buf_end,
+ (uchar)buf[EVENT_TYPE_OFFSET] != LOAD_EVENT)))
+ DBUG_RETURN(1);
+
+ data_len = event_len - body_offset;
+ if (num_fields > data_len) // simple sanity check against corruption
+ DBUG_RETURN(1);
+ for (uint i = 0; i < num_fields; i++)
+ field_block_len += (uint)field_lens[i] + 1;
+
+ fields = (char*)field_lens + num_fields;
+ table_name = fields + field_block_len;
+ db = table_name + table_name_len + 1;
+ DBUG_EXECUTE_IF ("simulate_invalid_address",
+ db_len = data_len;);
+ fname = db + db_len + 1;
+ if ((db_len > data_len) || (fname > buf_end))
+ goto err;
+ fname_len = (uint) strlen(fname);
+ if ((fname_len > data_len) || (fname + fname_len > buf_end))
+ goto err;
+ // null termination is accomplished by the caller doing buf[event_len]=0
+
+ DBUG_RETURN(0);
+
+err:
+ // Invalid event.
+ table_name = 0;
+ DBUG_RETURN(1);
+}
+
+
+/*
+ Load_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Load_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ print(file, print_event_info, 0);
+}
+
+
+void Load_log_event::print(FILE* file_arg, PRINT_EVENT_INFO* print_event_info,
+ bool commented)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file_arg);
+
+ DBUG_ENTER("Load_log_event::print");
+ if (!print_event_info->short_form)
+ {
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\tQuery\tthread_id=%ld\texec_time=%ld\n",
+ thread_id, exec_time);
+ }
+
+ bool different_db= 1;
+ if (db)
+ {
+ /*
+ If the database is different from the one of the previous statement, we
+ need to print the "use" command, and we update the last_db.
+ But if commented, the "use" is going to be commented so we should not
+ update the last_db.
+ */
+ if ((different_db= memcmp(print_event_info->db, db, db_len + 1)) &&
+ !commented)
+ memcpy(print_event_info->db, db, db_len + 1);
+ }
+
+ if (db && db[0] && different_db)
+ my_b_printf(&cache, "%suse %`s%s\n",
+ commented ? "# " : "",
+ db, print_event_info->delimiter);
+
+ if (flags & LOG_EVENT_THREAD_SPECIFIC_F)
+ my_b_printf(&cache,"%sSET @@session.pseudo_thread_id=%lu%s\n",
+ commented ? "# " : "", (ulong)thread_id,
+ print_event_info->delimiter);
+ my_b_printf(&cache, "%sLOAD DATA ",
+ commented ? "# " : "");
+ if (check_fname_outside_temp_buf())
+ my_b_write_string(&cache, "LOCAL ");
+ my_b_printf(&cache, "INFILE '%-*s' ", fname_len, fname);
+
+ if (sql_ex.opt_flags & REPLACE_FLAG)
+ my_b_write_string(&cache, "REPLACE ");
+ else if (sql_ex.opt_flags & IGNORE_FLAG)
+ my_b_write_string(&cache, "IGNORE ");
+
+ my_b_printf(&cache, "INTO TABLE `%s`", table_name);
+ my_b_write_string(&cache, " FIELDS TERMINATED BY ");
+ pretty_print_str(&cache, sql_ex.field_term, sql_ex.field_term_len);
+
+ if (sql_ex.opt_flags & OPT_ENCLOSED_FLAG)
+ my_b_write_string(&cache, " OPTIONALLY ");
+ my_b_write_string(&cache, " ENCLOSED BY ");
+ pretty_print_str(&cache, sql_ex.enclosed, sql_ex.enclosed_len);
+
+ my_b_write_string(&cache, " ESCAPED BY ");
+ pretty_print_str(&cache, sql_ex.escaped, sql_ex.escaped_len);
+
+ my_b_write_string(&cache, " LINES TERMINATED BY ");
+ pretty_print_str(&cache, sql_ex.line_term, sql_ex.line_term_len);
+
+
+ if (sql_ex.line_start)
+ {
+ my_b_write_string(&cache," STARTING BY ");
+ pretty_print_str(&cache, sql_ex.line_start, sql_ex.line_start_len);
+ }
+ if ((long) skip_lines > 0)
+ my_b_printf(&cache, " IGNORE %ld LINES", (long) skip_lines);
+
+ if (num_fields)
+ {
+ uint i;
+ const char* field = fields;
+ my_b_write_string(&cache, " (");
+ for (i = 0; i < num_fields; i++)
+ {
+ if (i)
+ my_b_write_byte(&cache, ',');
+ my_b_printf(&cache, "%`s", field);
+
+ field += field_lens[i] + 1;
+ }
+ my_b_write_byte(&cache, ')');
+ }
+
+ my_b_printf(&cache, "%s\n", print_event_info->delimiter);
+ DBUG_VOID_RETURN;
+}
+#endif /* MYSQL_CLIENT */
+
+#ifndef MYSQL_CLIENT
+
+/**
+ Load_log_event::set_fields()
+
+ @note
+ This function can not use the member variable
+ for the database, since LOAD DATA INFILE on the slave
+ can be for a different database than the current one.
+ This is the reason for the affected_db argument to this method.
+*/
+
+void Load_log_event::set_fields(const char* affected_db,
+ List<Item> &field_list,
+ Name_resolution_context *context)
+{
+ uint i;
+ const char* field = fields;
+ for (i= 0; i < num_fields; i++)
+ {
+ field_list.push_back(new Item_field(context,
+ affected_db, table_name, field));
+ field+= field_lens[i] + 1;
+ }
+}
+#endif /* !MYSQL_CLIENT */
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+/**
+ Does the data loading job when executing a LOAD DATA on the slave.
+
+ @param net
+ @param rli
+ @param use_rli_only_for_errors If set to 1, rli is provided to
+ Load_log_event::exec_event only for this
+ function to have RPL_LOG_NAME and
+ rli->last_slave_error, both being used by
+ error reports. rli's position advancing
+ is skipped (done by the caller which is
+ Execute_load_log_event::exec_event).
+ If set to 0, rli is provided for full use,
+ i.e. for error reports and position
+ advancing.
+
+ @todo
+ fix this; this can be done by testing rules in
+ Create_file_log_event::exec_event() and then discarding Append_block and
+ al.
+ @todo
+ this is a bug - this needs to be moved to the I/O thread
+
+ @retval
+ 0 Success
+ @retval
+ 1 Failure
+*/
+
+int Load_log_event::do_apply_event(NET* net, rpl_group_info *rgi,
+ bool use_rli_only_for_errors)
+{
+ LEX_STRING new_db;
+ Relay_log_info const *rli= rgi->rli;
+ Rpl_filter *rpl_filter= rli->mi->rpl_filter;
+ DBUG_ENTER("Load_log_event::do_apply_event");
+
+ new_db.length= db_len;
+ new_db.str= (char *) rpl_filter->get_rewrite_db(db, &new_db.length);
+ thd->set_db(new_db.str, new_db.length);
+ DBUG_ASSERT(thd->query() == 0);
+ thd->reset_query_inner(); // Should not be needed
+ thd->is_slave_error= 0;
+ clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
+
+ /* see Query_log_event::do_apply_event() and BUG#13360 */
+ DBUG_ASSERT(!rgi->m_table_map.count());
+ /*
+ Usually lex_start() is called by mysql_parse(), but we need it here
+ as the present method does not call mysql_parse().
+ */
+ lex_start(thd);
+ thd->lex->local_file= local_fname;
+ mysql_reset_thd_for_next_command(thd);
+
+ /*
+ We test replicate_*_db rules. Note that we have already prepared
+ the file to load, even if we are going to ignore and delete it
+ now. So it is possible that we did a lot of disk writes for
+ nothing. In other words, a big LOAD DATA INFILE on the master will
+ still consume a lot of space on the slave (space in the relay log
+ + space of temp files: twice the space of the file to load...)
+ even if it will finally be ignored. TODO: fix this; this can be
+ done by testing rules in Create_file_log_event::do_apply_event()
+ and then discarding Append_block and al. Another way is do the
+ filtering in the I/O thread (more efficient: no disk writes at
+ all).
+
+
+ Note: We do not need to execute reset_one_shot_variables() if this
+ db_ok() test fails.
+ Reason: The db stored in binlog events is the same for SET and for
+ its companion query. If the SET is ignored because of
+ db_ok(), the companion query will also be ignored, and if
+ the companion query is ignored in the db_ok() test of
+ ::do_apply_event(), then the companion SET also have so
+ we don't need to reset_one_shot_variables().
+ */
+ if (rpl_filter->db_ok(thd->db))
+ {
+ thd->set_time(when, when_sec_part);
+ thd->set_query_id(next_query_id());
+ thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
+
+ TABLE_LIST tables;
+ tables.init_one_table(thd->strmake(thd->db, thd->db_length),
+ thd->db_length,
+ table_name, strlen(table_name),
+ table_name, TL_WRITE);
+ tables.updating= 1;
+
+ // the table will be opened in mysql_load
+ if (rpl_filter->is_on() && !rpl_filter->tables_ok(thd->db, &tables))
+ {
+ // TODO: this is a bug - this needs to be moved to the I/O thread
+ if (net)
+ skip_load_data_infile(net);
+ }
+ else
+ {
+ char llbuff[22];
+ enum enum_duplicates handle_dup;
+ bool ignore= 0;
+ char query_buffer[1024];
+ String query_str(query_buffer, sizeof(query_buffer), system_charset_info);
+ char *load_data_query;
+
+ query_str.length(0);
+ /*
+ Forge LOAD DATA INFILE query which will be used in SHOW PROCESS LIST
+ and written to slave's binlog if binlogging is on.
+ */
+ print_query(thd, FALSE, NULL, &query_str, NULL, NULL, NULL);
+ if (!(load_data_query= (char *)thd->strmake(query_str.ptr(),
+ query_str.length())))
+ {
+ /*
+ This will set thd->fatal_error in case of OOM. So we surely will notice
+ that something is wrong.
+ */
+ goto error;
+ }
+
+ thd->set_query(load_data_query, (uint) (query_str.length()));
+
+ if (sql_ex.opt_flags & REPLACE_FLAG)
+ handle_dup= DUP_REPLACE;
+ else if (sql_ex.opt_flags & IGNORE_FLAG)
+ {
+ ignore= 1;
+ handle_dup= DUP_ERROR;
+ }
+ else
+ {
+ /*
+ When replication is running fine, if it was DUP_ERROR on the
+ master then we could choose IGNORE here, because if DUP_ERROR
+ suceeded on master, and data is identical on the master and slave,
+ then there should be no uniqueness errors on slave, so IGNORE is
+ the same as DUP_ERROR. But in the unlikely case of uniqueness errors
+ (because the data on the master and slave happen to be different
+ (user error or bug), we want LOAD DATA to print an error message on
+ the slave to discover the problem.
+
+ If reading from net (a 3.23 master), mysql_load() will change this
+ to IGNORE.
+ */
+ handle_dup= DUP_ERROR;
+ }
+ /*
+ We need to set thd->lex->sql_command and thd->lex->duplicates
+ since InnoDB tests these variables to decide if this is a LOAD
+ DATA ... REPLACE INTO ... statement even though mysql_parse()
+ is not called. This is not needed in 5.0 since there the LOAD
+ DATA ... statement is replicated using mysql_parse(), which
+ sets the thd->lex fields correctly.
+ */
+ thd->lex->sql_command= SQLCOM_LOAD;
+ thd->lex->duplicates= handle_dup;
+
+ sql_exchange ex((char*)fname, sql_ex.opt_flags & DUMPFILE_FLAG);
+ String field_term(sql_ex.field_term,sql_ex.field_term_len,log_cs);
+ String enclosed(sql_ex.enclosed,sql_ex.enclosed_len,log_cs);
+ String line_term(sql_ex.line_term,sql_ex.line_term_len,log_cs);
+ String line_start(sql_ex.line_start,sql_ex.line_start_len,log_cs);
+ String escaped(sql_ex.escaped,sql_ex.escaped_len, log_cs);
+ ex.field_term= &field_term;
+ ex.enclosed= &enclosed;
+ ex.line_term= &line_term;
+ ex.line_start= &line_start;
+ ex.escaped= &escaped;
+
+ ex.opt_enclosed = (sql_ex.opt_flags & OPT_ENCLOSED_FLAG);
+ if (sql_ex.empty_flags & FIELD_TERM_EMPTY)
+ ex.field_term->length(0);
+
+ ex.skip_lines = skip_lines;
+ List<Item> field_list;
+ thd->lex->select_lex.context.resolve_in_table_list_only(&tables);
+ set_fields(tables.db, field_list, &thd->lex->select_lex.context);
+ thd->variables.pseudo_thread_id= thread_id;
+ if (net)
+ {
+ // mysql_load will use thd->net to read the file
+ thd->net.vio = net->vio;
+ // Make sure the client does not get confused about the packet sequence
+ thd->net.pkt_nr = net->pkt_nr;
+ }
+ /*
+ It is safe to use tmp_list twice because we are not going to
+ update it inside mysql_load().
+ */
+ List<Item> tmp_list;
+ if (open_temporary_tables(thd, &tables) ||
+ mysql_load(thd, &ex, &tables, field_list, tmp_list, tmp_list,
+ handle_dup, ignore, net != 0))
+ thd->is_slave_error= 1;
+ if (thd->cuted_fields)
+ {
+ /* log_pos is the position of the LOAD event in the master log */
+ sql_print_warning("Slave: load data infile on table '%s' at "
+ "log position %s in log '%s' produced %ld "
+ "warning(s). Default database: '%s'",
+ (char*) table_name,
+ llstr(log_pos,llbuff), RPL_LOG_NAME,
+ (ulong) thd->cuted_fields,
+ print_slave_db_safe(thd->db));
+ }
+ if (net)
+ net->pkt_nr= thd->net.pkt_nr;
+ }
+ }
+ else
+ {
+ /*
+ We will just ask the master to send us /dev/null if we do not
+ want to load the data.
+ TODO: this a bug - needs to be done in I/O thread
+ */
+ if (net)
+ skip_load_data_infile(net);
+ }
+
+error:
+ thd->net.vio = 0;
+ const char *remember_db= thd->db;
+ thd->catalog= 0;
+ thd->set_db(NULL, 0); /* will free the current database */
+ thd->reset_query();
+ thd->get_stmt_da()->set_overwrite_status(true);
+ thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_GTID_BEGIN);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ close_thread_tables(thd);
+ /*
+ - If transaction rollback was requested due to deadlock
+ perform it and release metadata locks.
+ - If inside a multi-statement transaction,
+ defer the release of metadata locks until the current
+ transaction is either committed or rolled back. This prevents
+ other statements from modifying the table for the entire
+ duration of this transaction. This provides commit ordering
+ and guarantees serializability across multiple transactions.
+ - If in autocommit mode, or outside a transactional context,
+ automatically release metadata locks of the current statement.
+ */
+ if (thd->transaction_rollback_request)
+ {
+ trans_rollback_implicit(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+ else if (! thd->in_multi_stmt_transaction_mode())
+ thd->mdl_context.release_transactional_locks();
+ else
+ thd->mdl_context.release_statement_locks();
+
+ DBUG_EXECUTE_IF("LOAD_DATA_INFILE_has_fatal_error",
+ thd->is_slave_error= 0; thd->is_fatal_error= 1;);
+
+ if (thd->is_slave_error)
+ {
+ /* this err/sql_errno code is copy-paste from net_send_error() */
+ const char *err;
+ int sql_errno;
+ if (thd->is_error())
+ {
+ err= thd->get_stmt_da()->message();
+ sql_errno= thd->get_stmt_da()->sql_errno();
+ }
+ else
+ {
+ sql_errno=ER_UNKNOWN_ERROR;
+ err=ER(sql_errno);
+ }
+ rli->report(ERROR_LEVEL, sql_errno, rgi->gtid_info(), "\
+Error '%s' running LOAD DATA INFILE on table '%s'. Default database: '%s'",
+ err, (char*)table_name, print_slave_db_safe(remember_db));
+ free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
+ DBUG_RETURN(1);
+ }
+ free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
+
+ if (thd->is_fatal_error)
+ {
+ char buf[256];
+ my_snprintf(buf, sizeof(buf),
+ "Running LOAD DATA INFILE on table '%-.64s'."
+ " Default database: '%-.64s'",
+ (char*)table_name,
+ print_slave_db_safe(remember_db));
+
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(),
+ ER(ER_SLAVE_FATAL_ERROR), buf);
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN( use_rli_only_for_errors ? 0 : Log_event::do_apply_event(rgi) );
+}
+#endif
+
+
+/**************************************************************************
+ Rotate_log_event methods
+**************************************************************************/
+
+/*
+ Rotate_log_event::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Rotate_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf1[256], buf[22];
+ String tmp(buf1, sizeof(buf1), log_cs);
+ tmp.length(0);
+ tmp.append(new_log_ident, ident_len);
+ tmp.append(STRING_WITH_LEN(";pos="));
+ tmp.append(llstr(pos,buf));
+ protocol->store(tmp.ptr(), tmp.length(), &my_charset_bin);
+}
+#endif
+
+
+/*
+ Rotate_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Rotate_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ char buf[22];
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ if (print_event_info->short_form)
+ return;
+ print_header(&cache, print_event_info, FALSE);
+ my_b_write_string(&cache, "\tRotate to ");
+ if (new_log_ident)
+ my_b_write(&cache, (uchar*) new_log_ident, (uint)ident_len);
+ my_b_printf(&cache, " pos: %s\n", llstr(pos, buf));
+}
+#endif /* MYSQL_CLIENT */
+
+
+
+/*
+ Rotate_log_event::Rotate_log_event() (2 constructors)
+*/
+
+
+#ifndef MYSQL_CLIENT
+Rotate_log_event::Rotate_log_event(const char* new_log_ident_arg,
+ uint ident_len_arg, ulonglong pos_arg,
+ uint flags_arg)
+ :Log_event(), new_log_ident(new_log_ident_arg),
+ pos(pos_arg),ident_len(ident_len_arg ? ident_len_arg :
+ (uint) strlen(new_log_ident_arg)), flags(flags_arg)
+{
+#ifndef DBUG_OFF
+ char buff[22];
+ DBUG_ENTER("Rotate_log_event::Rotate_log_event(...,flags)");
+ DBUG_PRINT("enter",("new_log_ident: %s pos: %s flags: %lu", new_log_ident_arg,
+ llstr(pos_arg, buff), (ulong) flags));
+#endif
+ cache_type= EVENT_NO_CACHE;
+ if (flags & DUP_NAME)
+ new_log_ident= my_strndup(new_log_ident_arg, ident_len, MYF(MY_WME));
+ if (flags & RELAY_LOG)
+ set_relay_log_event();
+ DBUG_VOID_RETURN;
+}
+#endif
+
+
+Rotate_log_event::Rotate_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event) ,new_log_ident(0), flags(DUP_NAME)
+{
+ DBUG_ENTER("Rotate_log_event::Rotate_log_event(char*,...)");
+ // The caller will ensure that event_len is what we have at EVENT_LEN_OFFSET
+ uint8 post_header_len= description_event->post_header_len[ROTATE_EVENT-1];
+ uint ident_offset;
+ if (event_len < LOG_EVENT_MINIMAL_HEADER_LEN)
+ DBUG_VOID_RETURN;
+ buf+= LOG_EVENT_MINIMAL_HEADER_LEN;
+ pos= post_header_len ? uint8korr(buf + R_POS_OFFSET) : 4;
+ ident_len= (uint)(event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + post_header_len));
+ ident_offset= post_header_len;
+ set_if_smaller(ident_len,FN_REFLEN-1);
+ new_log_ident= my_strndup(buf + ident_offset, (uint) ident_len, MYF(MY_WME));
+ DBUG_PRINT("debug", ("new_log_ident: '%s'", new_log_ident));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Rotate_log_event::write()
+*/
+
+#ifndef MYSQL_CLIENT
+bool Rotate_log_event::write(IO_CACHE* file)
+{
+ char buf[ROTATE_HEADER_LEN];
+ int8store(buf + R_POS_OFFSET, pos);
+ return (write_header(file, ROTATE_HEADER_LEN + ident_len) ||
+ wrapper_my_b_safe_write(file, (uchar*) buf, ROTATE_HEADER_LEN) ||
+ wrapper_my_b_safe_write(file, (uchar*) new_log_ident,
+ (uint) ident_len) ||
+ write_footer(file));
+}
+#endif
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+/*
+ Got a rotate log event from the master.
+
+ This is mainly used so that we can later figure out the logname and
+ position for the master.
+
+ We can't rotate the slave's BINlog as this will cause infinitive rotations
+ in a A -> B -> A setup.
+ The NOTES below is a wrong comment which will disappear when 4.1 is merged.
+
+ @retval
+ 0 ok
+*/
+int Rotate_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ DBUG_ENTER("Rotate_log_event::do_update_pos");
+#ifndef DBUG_OFF
+ char buf[32];
+#endif
+
+ DBUG_PRINT("info", ("server_id=%lu; ::server_id=%lu",
+ (ulong) this->server_id, (ulong) global_system_variables.server_id));
+ DBUG_PRINT("info", ("new_log_ident: %s", this->new_log_ident));
+ DBUG_PRINT("info", ("pos: %s", llstr(this->pos, buf)));
+
+ /*
+ If we are in a transaction or in a group: the only normal case is
+ when the I/O thread was copying a big transaction, then it was
+ stopped and restarted: we have this in the relay log:
+
+ BEGIN
+ ...
+ ROTATE (a fake one)
+ ...
+ COMMIT or ROLLBACK
+
+ In that case, we don't want to touch the coordinates which
+ correspond to the beginning of the transaction. Starting from
+ 5.0.0, there also are some rotates from the slave itself, in the
+ relay log, which shall not change the group positions.
+
+ In parallel replication, rotate event is executed out-of-band with normal
+ events, so we cannot update group_master_log_name or _pos here, it will
+ be updated with the next normal event instead.
+ */
+ if ((server_id != global_system_variables.server_id ||
+ rli->replicate_same_server_id) &&
+ !is_relay_log_event() &&
+ !rli->is_in_group() &&
+ !rgi->is_parallel_exec)
+ {
+ mysql_mutex_lock(&rli->data_lock);
+ DBUG_PRINT("info", ("old group_master_log_name: '%s' "
+ "old group_master_log_pos: %lu",
+ rli->group_master_log_name,
+ (ulong) rli->group_master_log_pos));
+ memcpy(rli->group_master_log_name, new_log_ident, ident_len+1);
+ rli->notify_group_master_log_name_update();
+ rli->inc_group_relay_log_pos(pos, rgi, TRUE /* skip_lock */);
+ DBUG_PRINT("info", ("new group_master_log_name: '%s' "
+ "new group_master_log_pos: %lu",
+ rli->group_master_log_name,
+ (ulong) rli->group_master_log_pos));
+ mysql_mutex_unlock(&rli->data_lock);
+ rpl_global_gtid_slave_state.record_and_update_gtid(thd, rgi);
+ flush_relay_log_info(rli);
+
+ /*
+ Reset thd->variables.option_bits and sql_mode etc, because this could
+ be the signal of a master's downgrade from 5.0 to 4.0.
+ However, no need to reset description_event_for_exec: indeed, if the next
+ master is 5.0 (even 5.0.1) we will soon get a Format_desc; if the next
+ master is 4.0 then the events are in the slave's format (conversion).
+ */
+ set_slave_thread_options(thd);
+ set_slave_thread_default_charset(thd, rgi);
+ thd->variables.sql_mode= global_system_variables.sql_mode;
+ thd->variables.auto_increment_increment=
+ thd->variables.auto_increment_offset= 1;
+ }
+ else
+ rgi->inc_event_relay_log_pos();
+
+
+ DBUG_RETURN(0);
+}
+
+
+Log_event::enum_skip_reason
+Rotate_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ enum_skip_reason reason= Log_event::do_shall_skip(rgi);
+
+ switch (reason) {
+ case Log_event::EVENT_SKIP_NOT:
+ case Log_event::EVENT_SKIP_COUNT:
+ return Log_event::EVENT_SKIP_NOT;
+
+ case Log_event::EVENT_SKIP_IGNORE:
+ return Log_event::EVENT_SKIP_IGNORE;
+ }
+ DBUG_ASSERT(0);
+ return Log_event::EVENT_SKIP_NOT; // To keep compiler happy
+}
+
+#endif
+
+
+/**************************************************************************
+ Binlog_checkpoint_log_event methods
+**************************************************************************/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Binlog_checkpoint_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ protocol->store(binlog_file_name, binlog_file_len, &my_charset_bin);
+}
+
+
+Log_event::enum_skip_reason
+Binlog_checkpoint_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ enum_skip_reason reason= Log_event::do_shall_skip(rgi);
+ if (reason == EVENT_SKIP_COUNT)
+ reason= EVENT_SKIP_NOT;
+ return reason;
+}
+#endif
+
+
+#ifdef MYSQL_CLIENT
+void Binlog_checkpoint_log_event::print(FILE *file,
+ PRINT_EVENT_INFO *print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ if (print_event_info->short_form)
+ return;
+ print_header(&cache, print_event_info, FALSE);
+ my_b_write_string(&cache, "\tBinlog checkpoint ");
+ my_b_write(&cache, (uchar*)binlog_file_name, binlog_file_len);
+ my_b_write_byte(&cache, '\n');
+}
+#endif /* MYSQL_CLIENT */
+
+
+#ifdef MYSQL_SERVER
+Binlog_checkpoint_log_event::Binlog_checkpoint_log_event(
+ const char *binlog_file_name_arg,
+ uint binlog_file_len_arg)
+ :Log_event(),
+ binlog_file_name(my_strndup(binlog_file_name_arg, binlog_file_len_arg,
+ MYF(MY_WME))),
+ binlog_file_len(binlog_file_len_arg)
+{
+ cache_type= EVENT_NO_CACHE;
+}
+#endif /* MYSQL_SERVER */
+
+
+Binlog_checkpoint_log_event::Binlog_checkpoint_log_event(
+ const char *buf, uint event_len,
+ const Format_description_log_event *description_event)
+ :Log_event(buf, description_event), binlog_file_name(0)
+{
+ uint8 header_size= description_event->common_header_len;
+ uint8 post_header_len=
+ description_event->post_header_len[BINLOG_CHECKPOINT_EVENT-1];
+ if (event_len < header_size + post_header_len ||
+ post_header_len < BINLOG_CHECKPOINT_HEADER_LEN)
+ return;
+ buf+= header_size;
+ /* See uint4korr and int4store below */
+ compile_time_assert(BINLOG_CHECKPOINT_HEADER_LEN == 4);
+ binlog_file_len= uint4korr(buf);
+ if (event_len - (header_size + post_header_len) < binlog_file_len)
+ return;
+ binlog_file_name= my_strndup(buf + post_header_len, binlog_file_len,
+ MYF(MY_WME));
+ return;
+}
+
+
+#ifndef MYSQL_CLIENT
+bool Binlog_checkpoint_log_event::write(IO_CACHE *file)
+{
+ uchar buf[BINLOG_CHECKPOINT_HEADER_LEN];
+ int4store(buf, binlog_file_len);
+ return write_header(file, BINLOG_CHECKPOINT_HEADER_LEN + binlog_file_len) ||
+ wrapper_my_b_safe_write(file, buf, BINLOG_CHECKPOINT_HEADER_LEN) ||
+ wrapper_my_b_safe_write(file, (const uchar *)binlog_file_name,
+ binlog_file_len) ||
+ write_footer(file);
+}
+#endif /* MYSQL_CLIENT */
+
+
+/**************************************************************************
+ Global transaction ID stuff
+**************************************************************************/
+
+Gtid_log_event::Gtid_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event)
+ : Log_event(buf, description_event), seq_no(0), commit_id(0)
+{
+ uint8 header_size= description_event->common_header_len;
+ uint8 post_header_len= description_event->post_header_len[GTID_EVENT-1];
+ if (event_len < header_size + post_header_len ||
+ post_header_len < GTID_HEADER_LEN)
+ return;
+
+ buf+= header_size;
+ seq_no= uint8korr(buf);
+ buf+= 8;
+ domain_id= uint4korr(buf);
+ buf+= 4;
+ flags2= *buf;
+ if (flags2 & FL_GROUP_COMMIT_ID)
+ {
+ if (event_len < (uint)header_size + GTID_HEADER_LEN + 2)
+ {
+ seq_no= 0; // So is_valid() returns false
+ return;
+ }
+ ++buf;
+ commit_id= uint8korr(buf);
+ }
+}
+
+
+#ifdef MYSQL_SERVER
+
+Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg,
+ uint32 domain_id_arg, bool standalone,
+ uint16 flags_arg, bool is_transactional,
+ uint64 commit_id_arg)
+ : Log_event(thd_arg, flags_arg, is_transactional),
+ seq_no(seq_no_arg), commit_id(commit_id_arg), domain_id(domain_id_arg),
+ flags2((standalone ? FL_STANDALONE : 0) | (commit_id_arg ? FL_GROUP_COMMIT_ID : 0))
+{
+ cache_type= Log_event::EVENT_NO_CACHE;
+}
+
+
+/*
+ Used to record GTID while sending binlog to slave, without having to
+ fully contruct every Gtid_log_event() needlessly.
+*/
+bool
+Gtid_log_event::peek(const char *event_start, size_t event_len,
+ uint8 checksum_alg,
+ uint32 *domain_id, uint32 *server_id, uint64 *seq_no,
+ uchar *flags2, const Format_description_log_event *fdev)
+{
+ const char *p;
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ if (event_len > BINLOG_CHECKSUM_LEN)
+ event_len-= BINLOG_CHECKSUM_LEN;
+ else
+ event_len= 0;
+ }
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ if (event_len < (uint32)fdev->common_header_len + GTID_HEADER_LEN)
+ return true;
+ *server_id= uint4korr(event_start + SERVER_ID_OFFSET);
+ p= event_start + fdev->common_header_len;
+ *seq_no= uint8korr(p);
+ p+= 8;
+ *domain_id= uint4korr(p);
+ p+= 4;
+ *flags2= (uchar)*p;
+ return false;
+}
+
+
+bool
+Gtid_log_event::write(IO_CACHE *file)
+{
+ uchar buf[GTID_HEADER_LEN+2];
+ size_t write_len;
+
+ int8store(buf, seq_no);
+ int4store(buf+8, domain_id);
+ buf[12]= flags2;
+ if (flags2 & FL_GROUP_COMMIT_ID)
+ {
+ int8store(buf+13, commit_id);
+ write_len= GTID_HEADER_LEN + 2;
+ }
+ else
+ {
+ bzero(buf+13, GTID_HEADER_LEN-13);
+ write_len= GTID_HEADER_LEN;
+ }
+ return write_header(file, write_len) ||
+ wrapper_my_b_safe_write(file, buf, write_len) ||
+ write_footer(file);
+}
+
+
+/*
+ Replace a GTID event with either a BEGIN event, dummy event, or nothing, as
+ appropriate to work with old slave that does not know global transaction id.
+
+ The need_dummy_event argument is an IN/OUT argument. It is passed as TRUE
+ if slave has capability lower than MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES.
+ It is returned TRUE if we return a BEGIN (or dummy) event to be sent to the
+ slave, FALSE if event should be skipped completely.
+*/
+int
+Gtid_log_event::make_compatible_event(String *packet, bool *need_dummy_event,
+ ulong ev_offset, uint8 checksum_alg)
+{
+ uchar flags2;
+ if (packet->length() - ev_offset < LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN)
+ return 1;
+ flags2= (*packet)[ev_offset + LOG_EVENT_HEADER_LEN + 12];
+ if (flags2 & FL_STANDALONE)
+ {
+ if (*need_dummy_event)
+ return Query_log_event::dummy_event(packet, ev_offset, checksum_alg);
+ return 0;
+ }
+
+ *need_dummy_event= true;
+ return Query_log_event::begin_event(packet, ev_offset, checksum_alg);
+}
+
+
+#ifdef HAVE_REPLICATION
+void
+Gtid_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[6+5+10+1+10+1+20+1+4+20+1];
+ char *p;
+ p = strmov(buf, (flags2 & FL_STANDALONE ? "GTID " : "BEGIN GTID "));
+ p= longlong10_to_str(domain_id, p, 10);
+ *p++= '-';
+ p= longlong10_to_str(server_id, p, 10);
+ *p++= '-';
+ p= longlong10_to_str(seq_no, p, 10);
+ if (flags2 & FL_GROUP_COMMIT_ID)
+ {
+ p= strmov(p, " cid=");
+ p= longlong10_to_str(commit_id, p, 10);
+ }
+
+ protocol->store(buf, p-buf, &my_charset_bin);
+}
+
+static char gtid_begin_string[] = "BEGIN";
+
+int
+Gtid_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ thd->variables.server_id= this->server_id;
+ thd->variables.gtid_domain_id= this->domain_id;
+ thd->variables.gtid_seq_no= this->seq_no;
+ mysql_reset_thd_for_next_command(thd);
+
+ if (opt_gtid_strict_mode && opt_bin_log && opt_log_slave_updates)
+ {
+ if (mysql_bin_log.check_strict_gtid_sequence(this->domain_id,
+ this->server_id, this->seq_no))
+ return 1;
+ }
+
+ DBUG_ASSERT((thd->variables.option_bits & OPTION_GTID_BEGIN) == 0);
+ if (flags2 & FL_STANDALONE)
+ return 0;
+
+ /* Execute this like a BEGIN query event. */
+ thd->variables.option_bits|= OPTION_GTID_BEGIN;
+ DBUG_PRINT("info", ("Set OPTION_GTID_BEGIN"));
+ thd->set_query_and_id(gtid_begin_string, sizeof(gtid_begin_string)-1,
+ &my_charset_bin, next_query_id());
+ thd->lex->sql_command= SQLCOM_BEGIN;
+ thd->is_slave_error= 0;
+ status_var_increment(thd->status_var.com_stat[thd->lex->sql_command]);
+ if (trans_begin(thd, 0))
+ {
+ DBUG_PRINT("error", ("trans_begin() failed"));
+ thd->is_slave_error= 1;
+ }
+ thd->update_stats();
+
+ if (likely(!thd->is_slave_error))
+ general_log_write(thd, COM_QUERY, thd->query(), thd->query_length());
+
+ thd->reset_query();
+ free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
+ return thd->is_slave_error;
+}
+
+
+int
+Gtid_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ rgi->inc_event_relay_log_pos();
+ return 0;
+}
+
+
+Log_event::enum_skip_reason
+Gtid_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ /*
+ An event skipped due to @@skip_replication must not be counted towards the
+ number of events to be skipped due to @@sql_slave_skip_counter.
+ */
+ if (flags & LOG_EVENT_SKIP_REPLICATION_F &&
+ opt_replicate_events_marked_for_skip != RPL_SKIP_REPLICATE)
+ return Log_event::EVENT_SKIP_IGNORE;
+
+ if (rli->slave_skip_counter > 0)
+ {
+ if (!(flags2 & FL_STANDALONE))
+ {
+ thd->variables.option_bits|= OPTION_BEGIN;
+ DBUG_ASSERT(rgi->rli->get_flag(Relay_log_info::IN_TRANSACTION));
+ }
+ return Log_event::continue_group(rgi);
+ }
+ return Log_event::do_shall_skip(rgi);
+}
+
+
+#endif /* HAVE_REPLICATION */
+
+#else /* !MYSQL_SERVER */
+
+void
+Gtid_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+ char buf[21];
+ char buf2[21];
+
+ if (!print_event_info->short_form)
+ {
+ print_header(&cache, print_event_info, FALSE);
+ longlong10_to_str(seq_no, buf, 10);
+ if (flags2 & FL_GROUP_COMMIT_ID)
+ {
+ longlong10_to_str(commit_id, buf2, 10);
+ my_b_printf(&cache, "\tGTID %u-%u-%s cid=%s\n",
+ domain_id, server_id, buf, buf2);
+ }
+ else
+ my_b_printf(&cache, "\tGTID %u-%u-%s\n", domain_id, server_id, buf);
+
+ if (!print_event_info->domain_id_printed ||
+ print_event_info->domain_id != domain_id)
+ {
+ my_b_printf(&cache, "/*!100001 SET @@session.gtid_domain_id=%u*/%s\n",
+ domain_id, print_event_info->delimiter);
+ print_event_info->domain_id= domain_id;
+ print_event_info->domain_id_printed= true;
+ }
+
+ if (!print_event_info->server_id_printed ||
+ print_event_info->server_id != server_id)
+ {
+ my_b_printf(&cache, "/*!100001 SET @@session.server_id=%u*/%s\n",
+ server_id, print_event_info->delimiter);
+ print_event_info->server_id= server_id;
+ print_event_info->server_id_printed= true;
+ }
+
+ my_b_printf(&cache, "/*!100001 SET @@session.gtid_seq_no=%s*/%s\n",
+ buf, print_event_info->delimiter);
+ }
+ if (!(flags2 & FL_STANDALONE))
+ my_b_printf(&cache, "BEGIN\n%s\n", print_event_info->delimiter);
+}
+
+#endif /* MYSQL_SERVER */
+
+
+/* GTID list. */
+
+Gtid_list_log_event::Gtid_list_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event)
+ : Log_event(buf, description_event), count(0), list(0), sub_id_list(0)
+{
+ uint32 i;
+ uint32 val;
+ uint8 header_size= description_event->common_header_len;
+ uint8 post_header_len= description_event->post_header_len[GTID_LIST_EVENT-1];
+ if (event_len < header_size + post_header_len ||
+ post_header_len < GTID_LIST_HEADER_LEN)
+ return;
+
+ buf+= header_size;
+ val= uint4korr(buf);
+ count= val & ((1<<28)-1);
+ gl_flags= val & ((uint32)0xf << 28);
+ buf+= 4;
+ if (event_len - (header_size + post_header_len) < count*element_size ||
+ (!(list= (rpl_gtid *)my_malloc(count*sizeof(*list) + (count == 0),
+ MYF(MY_WME)))))
+ return;
+
+ for (i= 0; i < count; ++i)
+ {
+ list[i].domain_id= uint4korr(buf);
+ buf+= 4;
+ list[i].server_id= uint4korr(buf);
+ buf+= 4;
+ list[i].seq_no= uint8korr(buf);
+ buf+= 8;
+ }
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+ if ((gl_flags & FLAG_IGN_GTIDS))
+ {
+ uint32 i;
+ if (!(sub_id_list= (uint64 *)my_malloc(count*sizeof(uint64), MYF(MY_WME))))
+ {
+ my_free(list);
+ list= NULL;
+ return;
+ }
+ for (i= 0; i < count; ++i)
+ {
+ if (!(sub_id_list[i]=
+ rpl_global_gtid_slave_state.next_sub_id(list[i].domain_id)))
+ {
+ my_free(list);
+ my_free(sub_id_list);
+ list= NULL;
+ sub_id_list= NULL;
+ return;
+ }
+ }
+ }
+#endif
+}
+
+
+#ifdef MYSQL_SERVER
+
+Gtid_list_log_event::Gtid_list_log_event(rpl_binlog_state *gtid_set,
+ uint32 gl_flags_)
+ : count(gtid_set->count()), gl_flags(gl_flags_), list(0), sub_id_list(0)
+{
+ cache_type= EVENT_NO_CACHE;
+ /* Failure to allocate memory will be caught by is_valid() returning false. */
+ if (count < (1<<28) &&
+ (list = (rpl_gtid *)my_malloc(count * sizeof(*list) + (count == 0),
+ MYF(MY_WME))))
+ gtid_set->get_gtid_list(list, count);
+}
+
+
+Gtid_list_log_event::Gtid_list_log_event(slave_connection_state *gtid_set,
+ uint32 gl_flags_)
+ : count(gtid_set->count()), gl_flags(gl_flags_), list(0), sub_id_list(0)
+{
+ cache_type= EVENT_NO_CACHE;
+ /* Failure to allocate memory will be caught by is_valid() returning false. */
+ if (count < (1<<28) &&
+ (list = (rpl_gtid *)my_malloc(count * sizeof(*list) + (count == 0),
+ MYF(MY_WME))))
+ {
+ gtid_set->get_gtid_list(list, count);
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+ if (gl_flags & FLAG_IGN_GTIDS)
+ {
+ uint32 i;
+
+ if (!(sub_id_list= (uint64 *)my_malloc(count * sizeof(uint64),
+ MYF(MY_WME))))
+ {
+ my_free(list);
+ list= NULL;
+ return;
+ }
+ for (i= 0; i < count; ++i)
+ {
+ if (!(sub_id_list[i]=
+ rpl_global_gtid_slave_state.next_sub_id(list[i].domain_id)))
+ {
+ my_free(list);
+ my_free(sub_id_list);
+ list= NULL;
+ sub_id_list= NULL;
+ return;
+ }
+ }
+ }
+#endif
+ }
+}
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+bool
+Gtid_list_log_event::to_packet(String *packet)
+{
+ uint32 i;
+ uchar *p;
+ uint32 needed_length;
+
+ DBUG_ASSERT(count < 1<<28);
+
+ needed_length= packet->length() + get_data_size();
+ if (packet->reserve(needed_length))
+ return true;
+ p= (uchar *)packet->ptr() + packet->length();;
+ packet->length(needed_length);
+ int4store(p, (count & ((1<<28)-1)) | gl_flags);
+ p += 4;
+ /* Initialise the padding for empty Gtid_list. */
+ if (count == 0)
+ int2store(p, 0);
+ for (i= 0; i < count; ++i)
+ {
+ int4store(p, list[i].domain_id);
+ int4store(p+4, list[i].server_id);
+ int8store(p+8, list[i].seq_no);
+ p += 16;
+ }
+
+ return false;
+}
+
+
+bool
+Gtid_list_log_event::write(IO_CACHE *file)
+{
+ char buf[128];
+ String packet(buf, sizeof(buf), system_charset_info);
+
+ packet.length(0);
+ if (to_packet(&packet))
+ return true;
+ return
+ write_header(file, get_data_size()) ||
+ wrapper_my_b_safe_write(file, (uchar *)packet.ptr(), packet.length()) ||
+ write_footer(file);
+}
+
+
+int
+Gtid_list_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= const_cast<Relay_log_info*>(rgi->rli);
+ int ret;
+ if (gl_flags & FLAG_IGN_GTIDS)
+ {
+ uint32 i;
+ for (i= 0; i < count; ++i)
+ {
+ if ((ret= rpl_global_gtid_slave_state.record_gtid(thd, &list[i],
+ sub_id_list[i],
+ false, false)))
+ return ret;
+ rpl_global_gtid_slave_state.update_state_hash(sub_id_list[i], &list[i],
+ NULL);
+ }
+ }
+ ret= Log_event::do_apply_event(rgi);
+ if (rli->until_condition == Relay_log_info::UNTIL_GTID &&
+ (gl_flags & FLAG_UNTIL_REACHED))
+ {
+ char str_buf[128];
+ String str(str_buf, sizeof(str_buf), system_charset_info);
+ rli->until_gtid_pos.to_string(&str);
+ sql_print_information("Slave SQL thread stops because it reached its"
+ " UNTIL master_gtid_pos %s", str.c_ptr_safe());
+ rli->abort_slave= true;
+ rli->stop_for_until= true;
+ }
+ return ret;
+}
+
+
+Log_event::enum_skip_reason
+Gtid_list_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ enum_skip_reason reason= Log_event::do_shall_skip(rgi);
+ if (reason == EVENT_SKIP_COUNT)
+ reason= EVENT_SKIP_NOT;
+ return reason;
+}
+
+
+void
+Gtid_list_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf_mem[1024];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ uint32 i;
+ bool first;
+
+ buf.length(0);
+ buf.append(STRING_WITH_LEN("["));
+ first= true;
+ for (i= 0; i < count; ++i)
+ rpl_slave_state_tostring_helper(&buf, &list[i], &first);
+ buf.append(STRING_WITH_LEN("]"));
+
+ protocol->store(&buf);
+}
+#endif /* HAVE_REPLICATION */
+
+#else /* !MYSQL_SERVER */
+
+void
+Gtid_list_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info)
+{
+ if (!print_event_info->short_form)
+ {
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+ char buf[21];
+ uint32 i;
+
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\tGtid list [");
+ for (i= 0; i < count; ++i)
+ {
+ longlong10_to_str(list[i].seq_no, buf, 10);
+ my_b_printf(&cache, "%u-%u-%s", list[i].domain_id,
+ list[i].server_id, buf);
+ if (i < count-1)
+ my_b_printf(&cache, ",\n# ");
+ }
+ my_b_printf(&cache, "]\n");
+ }
+}
+
+#endif /* MYSQL_SERVER */
+
+
+/*
+ Used to record gtid_list event while sending binlog to slave, without having to
+ fully contruct the event object.
+*/
+bool
+Gtid_list_log_event::peek(const char *event_start, uint32 event_len,
+ uint8 checksum_alg,
+ rpl_gtid **out_gtid_list, uint32 *out_list_len,
+ const Format_description_log_event *fdev)
+{
+ const char *p;
+ uint32 count_field, count;
+ rpl_gtid *gtid_list;
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ if (event_len > BINLOG_CHECKSUM_LEN)
+ event_len-= BINLOG_CHECKSUM_LEN;
+ else
+ event_len= 0;
+ }
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ if (event_len < (uint32)fdev->common_header_len + GTID_LIST_HEADER_LEN)
+ return true;
+ p= event_start + fdev->common_header_len;
+ count_field= uint4korr(p);
+ p+= 4;
+ count= count_field & ((1<<28)-1);
+ if (event_len < (uint32)fdev->common_header_len + GTID_LIST_HEADER_LEN +
+ 16 * count)
+ return true;
+ if (!(gtid_list= (rpl_gtid *)my_malloc(sizeof(rpl_gtid)*count + (count == 0),
+ MYF(MY_WME))))
+ return true;
+ *out_gtid_list= gtid_list;
+ *out_list_len= count;
+ while (count--)
+ {
+ gtid_list->domain_id= uint4korr(p);
+ p+= 4;
+ gtid_list->server_id= uint4korr(p);
+ p+= 4;
+ gtid_list->seq_no= uint8korr(p);
+ p+= 8;
+ ++gtid_list;
+ }
+
+ return false;
+}
+
+
+/**************************************************************************
+ Intvar_log_event methods
+**************************************************************************/
+
+/*
+ Intvar_log_event::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Intvar_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[256], *pos;
+ pos= strmake(buf, get_var_type_name(), sizeof(buf)-23);
+ *pos++= '=';
+ pos= longlong10_to_str(val, pos, -10);
+ protocol->store(buf, (uint) (pos-buf), &my_charset_bin);
+}
+#endif
+
+
+/*
+ Intvar_log_event::Intvar_log_event()
+*/
+
+Intvar_log_event::Intvar_log_event(const char* buf,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+{
+ /* The Post-Header is empty. The Varible Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[INTVAR_EVENT-1];
+ type= buf[I_TYPE_OFFSET];
+ val= uint8korr(buf+I_VAL_OFFSET);
+}
+
+
+/*
+ Intvar_log_event::get_var_type_name()
+*/
+
+const char* Intvar_log_event::get_var_type_name()
+{
+ switch(type) {
+ case LAST_INSERT_ID_EVENT: return "LAST_INSERT_ID";
+ case INSERT_ID_EVENT: return "INSERT_ID";
+ default: /* impossible */ return "UNKNOWN";
+ }
+}
+
+
+/*
+ Intvar_log_event::write()
+*/
+
+#ifndef MYSQL_CLIENT
+bool Intvar_log_event::write(IO_CACHE* file)
+{
+ uchar buf[9];
+ buf[I_TYPE_OFFSET]= (uchar) type;
+ int8store(buf + I_VAL_OFFSET, val);
+ return (write_header(file, sizeof(buf)) ||
+ wrapper_my_b_safe_write(file, buf, sizeof(buf)) ||
+ write_footer(file));
+}
+#endif
+
+
+/*
+ Intvar_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Intvar_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ char llbuff[22];
+ const char *msg;
+ LINT_INIT(msg);
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ if (!print_event_info->short_form)
+ {
+ print_header(&cache, print_event_info, FALSE);
+ my_b_write_string(&cache, "\tIntvar\n");
+ }
+
+ my_b_printf(&cache, "SET ");
+ switch (type) {
+ case LAST_INSERT_ID_EVENT:
+ msg="LAST_INSERT_ID";
+ break;
+ case INSERT_ID_EVENT:
+ msg="INSERT_ID";
+ break;
+ case INVALID_INT_EVENT:
+ default: // cannot happen
+ msg="INVALID_INT";
+ break;
+ }
+ my_b_printf(&cache, "%s=%s%s\n",
+ msg, llstr(val,llbuff), print_event_info->delimiter);
+}
+#endif
+
+
+#if defined(HAVE_REPLICATION)&& !defined(MYSQL_CLIENT)
+
+/*
+ Intvar_log_event::do_apply_event()
+*/
+
+int Intvar_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ DBUG_ENTER("Intvar_log_event::do_apply_event");
+ if (rgi->deferred_events_collecting)
+ {
+ DBUG_PRINT("info",("deferring event"));
+ DBUG_RETURN(rgi->deferred_events->add(this));
+ }
+
+ switch (type) {
+ case LAST_INSERT_ID_EVENT:
+ thd->first_successful_insert_id_in_prev_stmt= val;
+ DBUG_PRINT("info",("last_insert_id_event: %ld", (long) val));
+ break;
+ case INSERT_ID_EVENT:
+ thd->force_one_auto_inc_interval(val);
+ break;
+ }
+ DBUG_RETURN(0);
+}
+
+int Intvar_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ rgi->inc_event_relay_log_pos();
+ return 0;
+}
+
+
+Log_event::enum_skip_reason
+Intvar_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ /*
+ It is a common error to set the slave skip counter to 1 instead of
+ 2 when recovering from an insert which used a auto increment,
+ rand, or user var. Therefore, if the slave skip counter is 1, we
+ just say that this event should be skipped by ignoring it, meaning
+ that we do not change the value of the slave skip counter since it
+ will be decreased by the following insert event.
+ */
+ return continue_group(rgi);
+}
+
+#endif
+
+
+/**************************************************************************
+ Rand_log_event methods
+**************************************************************************/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Rand_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf1[256], *pos;
+ pos= strmov(buf1,"rand_seed1=");
+ pos= int10_to_str((long) seed1, pos, 10);
+ pos= strmov(pos, ",rand_seed2=");
+ pos= int10_to_str((long) seed2, pos, 10);
+ protocol->store(buf1, (uint) (pos-buf1), &my_charset_bin);
+}
+#endif
+
+
+Rand_log_event::Rand_log_event(const char* buf,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+{
+ /* The Post-Header is empty. The Variable Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[RAND_EVENT-1];
+ seed1= uint8korr(buf+RAND_SEED1_OFFSET);
+ seed2= uint8korr(buf+RAND_SEED2_OFFSET);
+}
+
+
+#ifndef MYSQL_CLIENT
+bool Rand_log_event::write(IO_CACHE* file)
+{
+ uchar buf[16];
+ int8store(buf + RAND_SEED1_OFFSET, seed1);
+ int8store(buf + RAND_SEED2_OFFSET, seed2);
+ return (write_header(file, sizeof(buf)) ||
+ wrapper_my_b_safe_write(file, buf, sizeof(buf)) ||
+ write_footer(file));
+}
+#endif
+
+
+#ifdef MYSQL_CLIENT
+void Rand_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ char llbuff[22],llbuff2[22];
+ if (!print_event_info->short_form)
+ {
+ print_header(&cache, print_event_info, FALSE);
+ my_b_write_string(&cache, "\tRand\n");
+ }
+ my_b_printf(&cache, "SET @@RAND_SEED1=%s, @@RAND_SEED2=%s%s\n",
+ llstr(seed1, llbuff),llstr(seed2, llbuff2),
+ print_event_info->delimiter);
+}
+#endif /* MYSQL_CLIENT */
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int Rand_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ if (rgi->deferred_events_collecting)
+ return rgi->deferred_events->add(this);
+
+ thd->rand.seed1= (ulong) seed1;
+ thd->rand.seed2= (ulong) seed2;
+ return 0;
+}
+
+int Rand_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ rgi->inc_event_relay_log_pos();
+ return 0;
+}
+
+
+Log_event::enum_skip_reason
+Rand_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ /*
+ It is a common error to set the slave skip counter to 1 instead of
+ 2 when recovering from an insert which used a auto increment,
+ rand, or user var. Therefore, if the slave skip counter is 1, we
+ just say that this event should be skipped by ignoring it, meaning
+ that we do not change the value of the slave skip counter since it
+ will be decreased by the following insert event.
+ */
+ return continue_group(rgi);
+}
+
+/**
+ Exec deferred Int-, Rand- and User- var events prefixing
+ a Query-log-event event.
+
+ @param thd THD handle
+
+ @return false on success, true if a failure in an event applying occurred.
+*/
+bool slave_execute_deferred_events(THD *thd)
+{
+ bool res= false;
+ rpl_group_info *rgi= thd->rgi_slave;
+
+ DBUG_ASSERT(rgi && (!rgi->deferred_events_collecting || rgi->deferred_events));
+
+ if (!rgi->deferred_events_collecting || rgi->deferred_events->is_empty())
+ return res;
+
+ res= rgi->deferred_events->execute(rgi);
+
+ return res;
+}
+
+#endif /* !MYSQL_CLIENT */
+
+
+/**************************************************************************
+ Xid_log_event methods
+**************************************************************************/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Xid_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[128], *pos;
+ pos= strmov(buf, "COMMIT /* xid=");
+ pos= longlong10_to_str(xid, pos, 10);
+ pos= strmov(pos, " */");
+ protocol->store(buf, (uint) (pos-buf), &my_charset_bin);
+}
+#endif
+
+/**
+ @note
+ It's ok not to use int8store here,
+ as long as xid_t::set(ulonglong) and
+ xid_t::get_my_xid doesn't do it either.
+ We don't care about actual values of xids as long as
+ identical numbers compare identically
+*/
+
+Xid_log_event::
+Xid_log_event(const char* buf,
+ const Format_description_log_event *description_event)
+ :Log_event(buf, description_event)
+{
+ /* The Post-Header is empty. The Variable Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[XID_EVENT-1];
+ memcpy((char*) &xid, buf, sizeof(xid));
+}
+
+
+#ifndef MYSQL_CLIENT
+bool Xid_log_event::write(IO_CACHE* file)
+{
+ DBUG_EXECUTE_IF("do_not_write_xid", return 0;);
+ return (write_header(file, sizeof(xid)) ||
+ wrapper_my_b_safe_write(file, (uchar*) &xid, sizeof(xid)) ||
+ write_footer(file));
+}
+#endif
+
+
+#ifdef MYSQL_CLIENT
+void Xid_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ if (!print_event_info->short_form)
+ {
+ char buf[64];
+ longlong10_to_str(xid, buf, 10);
+
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\tXid = %s\n", buf);
+ }
+ my_b_printf(&cache, "COMMIT%s\n", print_event_info->delimiter);
+}
+#endif /* MYSQL_CLIENT */
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int Xid_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ bool res;
+ int err;
+ rpl_gtid gtid;
+ uint64 sub_id= 0;
+ Relay_log_info const *rli= rgi->rli;
+
+ /*
+ XID_EVENT works like a COMMIT statement. And it also updates the
+ mysql.gtid_slave_pos table with the GTID of the current transaction.
+
+ Therefore, it acts much like a normal SQL statement, so we need to do
+ mysql_reset_thd_for_next_command() as if starting a new statement.
+ */
+ mysql_reset_thd_for_next_command(thd);
+ /*
+ Record any GTID in the same transaction, so slave state is transactionally
+ consistent.
+ */
+ if (rgi->gtid_pending)
+ {
+ sub_id= rgi->gtid_sub_id;
+ rgi->gtid_pending= false;
+
+ gtid= rgi->current_gtid;
+ err= rpl_global_gtid_slave_state.record_gtid(thd, &gtid, sub_id, true, false);
+ if (err)
+ {
+ int ec= thd->get_stmt_da()->sql_errno();
+ /*
+ Do not report an error if this is really a kill due to a deadlock.
+ In this case, the transaction will be re-tried instead.
+ */
+ if (!is_parallel_retry_error(rgi, ec))
+ rli->report(ERROR_LEVEL, ER_CANNOT_UPDATE_GTID_STATE, rgi->gtid_info(),
+ "Error during XID COMMIT: failed to update GTID state in "
+ "%s.%s: %d: %s",
+ "mysql", rpl_gtid_slave_state_table_name.str, ec,
+ thd->get_stmt_da()->message());
+ trans_rollback(thd);
+ thd->is_slave_error= 1;
+ return err;
+ }
+
+ DBUG_EXECUTE_IF("gtid_fail_after_record_gtid",
+ { my_error(ER_ERROR_DURING_COMMIT, MYF(0), HA_ERR_WRONG_COMMAND);
+ thd->is_slave_error= 1;
+ return 1;
+ });
+ }
+
+ /* For a slave Xid_log_event is COMMIT */
+ general_log_print(thd, COM_QUERY,
+ "COMMIT /* implicit, from Xid_log_event */");
+ thd->variables.option_bits&= ~OPTION_GTID_BEGIN;
+ res= trans_commit(thd); /* Automatically rolls back on error. */
+ thd->mdl_context.release_transactional_locks();
+
+ if (!res && sub_id)
+ rpl_global_gtid_slave_state.update_state_hash(sub_id, &gtid, rgi);
+
+ /*
+ Increment the global status commit count variable
+ */
+ status_var_increment(thd->status_var.com_stat[SQLCOM_COMMIT]);
+
+ return res;
+}
+
+Log_event::enum_skip_reason
+Xid_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ DBUG_ENTER("Xid_log_event::do_shall_skip");
+ if (rgi->rli->slave_skip_counter > 0)
+ {
+ DBUG_ASSERT(!rgi->rli->get_flag(Relay_log_info::IN_TRANSACTION));
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_GTID_BEGIN);
+ DBUG_RETURN(Log_event::EVENT_SKIP_COUNT);
+ }
+ DBUG_RETURN(Log_event::do_shall_skip(rgi));
+}
+#endif /* !MYSQL_CLIENT */
+
+
+/**************************************************************************
+ User_var_log_event methods
+**************************************************************************/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+static bool
+user_var_append_name_part(THD *thd, String *buf,
+ const char *name, size_t name_len)
+{
+ return buf->append("@") ||
+ append_identifier(thd, buf, name, name_len) ||
+ buf->append("=");
+}
+
+void User_var_log_event::pack_info(THD *thd, Protocol* protocol)
+{
+ if (is_null)
+ {
+ char buf_mem[FN_REFLEN+7];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ buf.length(0);
+ if (user_var_append_name_part(thd, &buf, name, name_len) ||
+ buf.append("NULL"))
+ return;
+ protocol->store(buf.ptr(), buf.length(), &my_charset_bin);
+ }
+ else
+ {
+ switch (type) {
+ case REAL_RESULT:
+ {
+ double real_val;
+ char buf2[MY_GCVT_MAX_FIELD_WIDTH+1];
+ char buf_mem[FN_REFLEN + MY_GCVT_MAX_FIELD_WIDTH + 1];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ float8get(real_val, val);
+ buf.length(0);
+ if (user_var_append_name_part(thd, &buf, name, name_len) ||
+ buf.append(buf2, my_gcvt(real_val, MY_GCVT_ARG_DOUBLE,
+ MY_GCVT_MAX_FIELD_WIDTH, buf2, NULL)))
+ return;
+ protocol->store(buf.ptr(), buf.length(), &my_charset_bin);
+ break;
+ }
+ case INT_RESULT:
+ {
+ char buf2[22];
+ char buf_mem[FN_REFLEN + 22];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ buf.length(0);
+ if (user_var_append_name_part(thd, &buf, name, name_len) ||
+ buf.append(buf2,
+ longlong10_to_str(uint8korr(val), buf2,
+ ((flags & User_var_log_event::UNSIGNED_F) ? 10 : -10))-buf2))
+ return;
+ protocol->store(buf.ptr(), buf.length(), &my_charset_bin);
+ break;
+ }
+ case DECIMAL_RESULT:
+ {
+ char buf_mem[FN_REFLEN + DECIMAL_MAX_STR_LENGTH];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ char buf2[DECIMAL_MAX_STR_LENGTH+1];
+ String str(buf2, sizeof(buf2), &my_charset_bin);
+ my_decimal dec;
+ buf.length(0);
+ binary2my_decimal(E_DEC_FATAL_ERROR, (uchar*) (val+2), &dec, val[0],
+ val[1]);
+ my_decimal2string(E_DEC_FATAL_ERROR, &dec, 0, 0, 0, &str);
+ if (user_var_append_name_part(thd, &buf, name, name_len) ||
+ buf.append(buf2))
+ return;
+ protocol->store(buf.ptr(), buf.length(), &my_charset_bin);
+ break;
+ }
+ case STRING_RESULT:
+ {
+ /* 15 is for 'COLLATE' and other chars */
+ char buf_mem[FN_REFLEN + 512 + 1 + 2*MY_CS_NAME_SIZE+15];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ CHARSET_INFO *cs;
+ buf.length(0);
+ if (!(cs= get_charset(charset_number, MYF(0))))
+ {
+ if (buf.append("???"))
+ return;
+ }
+ else
+ {
+ size_t old_len;
+ char *beg, *end;
+ if (user_var_append_name_part(thd, &buf, name, name_len) ||
+ buf.append("_") ||
+ buf.append(cs->csname) ||
+ buf.append(" "))
+ return;
+ old_len= buf.length();
+ if (buf.reserve(old_len + val_len * 2 + 3 + sizeof(" COLLATE ") +
+ MY_CS_NAME_SIZE))
+ return;
+ beg= const_cast<char *>(buf.ptr()) + old_len;
+ end= str_to_hex(beg, val, val_len);
+ buf.length(old_len + (end - beg));
+ if (buf.append(" COLLATE ") ||
+ buf.append(cs->name))
+ return;
+ }
+ protocol->store(buf.ptr(), buf.length(), &my_charset_bin);
+ break;
+ }
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0);
+ return;
+ }
+ }
+}
+#endif /* !MYSQL_CLIENT */
+
+
+User_var_log_event::
+User_var_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+#ifndef MYSQL_CLIENT
+ , deferred(false), query_id(0)
+#endif
+{
+ bool error= false;
+ const char* buf_start= buf, *buf_end= buf + event_len;
+
+ /* The Post-Header is empty. The Variable Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[USER_VAR_EVENT-1];
+ name_len= uint4korr(buf);
+ name= (char *) buf + UV_NAME_LEN_SIZE;
+
+ /*
+ We don't know yet is_null value, so we must assume that name_len
+ may have the bigger value possible, is_null= True and there is no
+ payload for val, or even that name_len is 0.
+ */
+ if (name + name_len + UV_VAL_IS_NULL > buf_end)
+ {
+ error= true;
+ goto err;
+ }
+
+ buf+= UV_NAME_LEN_SIZE + name_len;
+ is_null= (bool) *buf;
+ flags= User_var_log_event::UNDEF_F; // defaults to UNDEF_F
+ if (is_null)
+ {
+ type= STRING_RESULT;
+ charset_number= my_charset_bin.number;
+ val_len= 0;
+ val= 0;
+ }
+ else
+ {
+ val= (char *) (buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
+ UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE);
+
+ if (val > buf_end)
+ {
+ error= true;
+ goto err;
+ }
+
+ type= (Item_result) buf[UV_VAL_IS_NULL];
+ charset_number= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE);
+ val_len= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
+ UV_CHARSET_NUMBER_SIZE);
+
+ if (val + val_len > buf_end)
+ {
+ error= true;
+ goto err;
+ }
+
+ /**
+ We need to check if this is from an old server
+ that did not pack information for flags.
+ We do this by checking if there are extra bytes
+ after the packed value. If there are we take the
+ extra byte and it's value is assumed to contain
+ the flags value.
+
+ Old events will not have this extra byte, thence,
+ we keep the flags set to UNDEF_F.
+ */
+ uint bytes_read= ((val + val_len) - buf_start);
+#ifndef DBUG_OFF
+ bool old_pre_checksum_fd= description_event->is_version_before_checksum(
+ &description_event->server_version_split);
+#endif
+ DBUG_ASSERT((bytes_read == data_written -
+ (old_pre_checksum_fd ||
+ (description_event->checksum_alg ==
+ BINLOG_CHECKSUM_ALG_OFF)) ?
+ 0 : BINLOG_CHECKSUM_LEN)
+ ||
+ (bytes_read == data_written -1 -
+ (old_pre_checksum_fd ||
+ (description_event->checksum_alg ==
+ BINLOG_CHECKSUM_ALG_OFF)) ?
+ 0 : BINLOG_CHECKSUM_LEN));
+ if ((data_written - bytes_read) > 0)
+ {
+ flags= (uint) *(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
+ UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE +
+ val_len);
+ }
+ }
+
+err:
+ if (error)
+ name= 0;
+}
+
+
+#ifndef MYSQL_CLIENT
+bool User_var_log_event::write(IO_CACHE* file)
+{
+ char buf[UV_NAME_LEN_SIZE];
+ char buf1[UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
+ UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE];
+ uchar buf2[MY_MAX(8, DECIMAL_MAX_FIELD_SIZE + 2)], *pos= buf2;
+ uint unsigned_len= 0;
+ uint buf1_length;
+ ulong event_length;
+
+ int4store(buf, name_len);
+
+ if ((buf1[0]= is_null))
+ {
+ buf1_length= 1;
+ val_len= 0; // Length of 'pos'
+ }
+ else
+ {
+ buf1[1]= type;
+ int4store(buf1 + 2, charset_number);
+
+ switch (type) {
+ case REAL_RESULT:
+ float8store(buf2, *(double*) val);
+ break;
+ case INT_RESULT:
+ int8store(buf2, *(longlong*) val);
+ unsigned_len= 1;
+ break;
+ case DECIMAL_RESULT:
+ {
+ my_decimal *dec= (my_decimal *)val;
+ dec->fix_buffer_pointer();
+ buf2[0]= (char)(dec->intg + dec->frac);
+ buf2[1]= (char)dec->frac;
+ decimal2bin((decimal_t*)val, buf2+2, buf2[0], buf2[1]);
+ val_len= decimal_bin_size(buf2[0], buf2[1]) + 2;
+ break;
+ }
+ case STRING_RESULT:
+ pos= (uchar*) val;
+ break;
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0);
+ return 0;
+ }
+ int4store(buf1 + 2 + UV_CHARSET_NUMBER_SIZE, val_len);
+ buf1_length= 10;
+ }
+
+ /* Length of the whole event */
+ event_length= sizeof(buf)+ name_len + buf1_length + val_len + unsigned_len;
+
+ return (write_header(file, event_length) ||
+ wrapper_my_b_safe_write(file, (uchar*) buf, sizeof(buf)) ||
+ wrapper_my_b_safe_write(file, (uchar*) name, name_len) ||
+ wrapper_my_b_safe_write(file, (uchar*) buf1, buf1_length) ||
+ wrapper_my_b_safe_write(file, pos, val_len) ||
+ wrapper_my_b_safe_write(file, &flags, unsigned_len) ||
+ write_footer(file));
+}
+#endif
+
+
+/*
+ User_var_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ if (!print_event_info->short_form)
+ {
+ print_header(&cache, print_event_info, FALSE);
+ my_b_write_string(&cache, "\tUser_var\n");
+ }
+
+ my_b_write_string(&cache, "SET @");
+ my_b_write_backtick_quote(&cache, name, name_len);
+
+ if (is_null)
+ {
+ my_b_printf(&cache, ":=NULL%s\n", print_event_info->delimiter);
+ }
+ else
+ {
+ switch (type) {
+ case REAL_RESULT:
+ double real_val;
+ char real_buf[FMT_G_BUFSIZE(14)];
+ float8get(real_val, val);
+ sprintf(real_buf, "%.14g", real_val);
+ my_b_printf(&cache, ":=%s%s\n", real_buf, print_event_info->delimiter);
+ break;
+ case INT_RESULT:
+ char int_buf[22];
+ longlong10_to_str(uint8korr(val), int_buf,
+ ((flags & User_var_log_event::UNSIGNED_F) ? 10 : -10));
+ my_b_printf(&cache, ":=%s%s\n", int_buf, print_event_info->delimiter);
+ break;
+ case DECIMAL_RESULT:
+ {
+ char str_buf[200];
+ int str_len= sizeof(str_buf) - 1;
+ int precision= (int)val[0];
+ int scale= (int)val[1];
+ decimal_digit_t dec_buf[10];
+ decimal_t dec;
+ dec.len= 10;
+ dec.buf= dec_buf;
+
+ bin2decimal((uchar*) val+2, &dec, precision, scale);
+ decimal2string(&dec, str_buf, &str_len, 0, 0, 0);
+ str_buf[str_len]= 0;
+ my_b_printf(&cache, ":=%s%s\n", str_buf, print_event_info->delimiter);
+ break;
+ }
+ case STRING_RESULT:
+ {
+ /*
+ Let's express the string in hex. That's the most robust way. If we
+ print it in character form instead, we need to escape it with
+ character_set_client which we don't know (we will know it in 5.0, but
+ in 4.1 we don't know it easily when we are printing
+ User_var_log_event). Explanation why we would need to bother with
+ character_set_client (quoting Bar):
+ > Note, the parser doesn't switch to another unescaping mode after
+ > it has met a character set introducer.
+ > For example, if an SJIS client says something like:
+ > SET @a= _ucs2 \0a\0b'
+ > the string constant is still unescaped according to SJIS, not
+ > according to UCS2.
+ */
+ char *hex_str;
+ CHARSET_INFO *cs;
+
+ // 2 hex digits / byte
+ hex_str= (char *) my_malloc(2 * val_len + 1 + 3, MYF(MY_WME));
+ if (!hex_str)
+ return;
+ str_to_hex(hex_str, val, val_len);
+ /*
+ For proper behaviour when mysqlbinlog|mysql, we need to explicitely
+ specify the variable's collation. It will however cause problems when
+ people want to mysqlbinlog|mysql into another server not supporting the
+ character set. But there's not much to do about this and it's unlikely.
+ */
+ if (!(cs= get_charset(charset_number, MYF(0))))
+ /*
+ Generate an unusable command (=> syntax error) is probably the best
+ thing we can do here.
+ */
+ my_b_printf(&cache, ":=???%s\n", print_event_info->delimiter);
+ else
+ my_b_printf(&cache, ":=_%s %s COLLATE `%s`%s\n",
+ cs->csname, hex_str, cs->name,
+ print_event_info->delimiter);
+ my_free(hex_str);
+ }
+ break;
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0);
+ return;
+ }
+ }
+}
+#endif
+
+
+/*
+ User_var_log_event::do_apply_event()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int User_var_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ Item *it= 0;
+ CHARSET_INFO *charset;
+ DBUG_ENTER("User_var_log_event::do_apply_event");
+ query_id_t sav_query_id= 0; /* memorize orig id when deferred applying */
+
+ if (rgi->deferred_events_collecting)
+ {
+ set_deferred(current_thd->query_id);
+ DBUG_RETURN(rgi->deferred_events->add(this));
+ }
+ else if (is_deferred())
+ {
+ sav_query_id= current_thd->query_id;
+ current_thd->query_id= query_id; /* recreating original time context */
+ }
+
+ if (!(charset= get_charset(charset_number, MYF(MY_WME))))
+ DBUG_RETURN(1);
+ LEX_STRING user_var_name;
+ user_var_name.str= name;
+ user_var_name.length= name_len;
+ double real_val;
+ longlong int_val;
+
+ if (is_null)
+ {
+ it= new Item_null();
+ }
+ else
+ {
+ switch (type) {
+ case REAL_RESULT:
+ float8get(real_val, val);
+ it= new Item_float(real_val, 0);
+ val= (char*) &real_val; // Pointer to value in native format
+ val_len= 8;
+ break;
+ case INT_RESULT:
+ int_val= (longlong) uint8korr(val);
+ it= new Item_int(int_val);
+ val= (char*) &int_val; // Pointer to value in native format
+ val_len= 8;
+ break;
+ case DECIMAL_RESULT:
+ {
+ Item_decimal *dec= new Item_decimal((uchar*) val+2, val[0], val[1]);
+ it= dec;
+ val= (char *)dec->val_decimal(NULL);
+ val_len= sizeof(my_decimal);
+ break;
+ }
+ case STRING_RESULT:
+ it= new Item_string(val, val_len, charset);
+ break;
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0);
+ DBUG_RETURN(0);
+ }
+ }
+
+ Item_func_set_user_var *e= new Item_func_set_user_var(user_var_name, it);
+ /*
+ Item_func_set_user_var can't substitute something else on its place =>
+ 0 can be passed as last argument (reference on item)
+
+ Fix_fields() can fail, in which case a call of update_hash() might
+ crash the server, so if fix fields fails, we just return with an
+ error.
+ */
+ if (e->fix_fields(thd, 0))
+ DBUG_RETURN(1);
+
+ /*
+ A variable can just be considered as a table with
+ a single record and with a single column. Thus, like
+ a column value, it could always have IMPLICIT derivation.
+ */
+ e->update_hash(val, val_len, type, charset, DERIVATION_IMPLICIT,
+ (flags & User_var_log_event::UNSIGNED_F));
+ if (!is_deferred())
+ free_root(thd->mem_root, 0);
+ else
+ current_thd->query_id= sav_query_id; /* restore current query's context */
+
+ DBUG_RETURN(0);
+}
+
+int User_var_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ rgi->inc_event_relay_log_pos();
+ return 0;
+}
+
+Log_event::enum_skip_reason
+User_var_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ /*
+ It is a common error to set the slave skip counter to 1 instead
+ of 2 when recovering from an insert which used a auto increment,
+ rand, or user var. Therefore, if the slave skip counter is 1, we
+ just say that this event should be skipped by ignoring it, meaning
+ that we do not change the value of the slave skip counter since it
+ will be decreased by the following insert event.
+ */
+ return continue_group(rgi);
+}
+#endif /* !MYSQL_CLIENT */
+
+
+/**************************************************************************
+ Slave_log_event methods
+**************************************************************************/
+
+#ifdef HAVE_REPLICATION
+#ifdef MYSQL_CLIENT
+void Unknown_log_event::print(FILE* file_arg, PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file_arg);
+
+ if (print_event_info->short_form)
+ return;
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\n# %s", "Unknown event\n");
+}
+#endif
+
+#ifndef MYSQL_CLIENT
+void Slave_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[256+HOSTNAME_LENGTH], *pos;
+ pos= strmov(buf, "host=");
+ pos= strnmov(pos, master_host, HOSTNAME_LENGTH);
+ pos= strmov(pos, ",port=");
+ pos= int10_to_str((long) master_port, pos, 10);
+ pos= strmov(pos, ",log=");
+ pos= strmov(pos, master_log);
+ pos= strmov(pos, ",pos=");
+ pos= longlong10_to_str(master_pos, pos, 10);
+ protocol->store(buf, pos-buf, &my_charset_bin);
+}
+#endif /* !MYSQL_CLIENT */
+
+
+#ifndef MYSQL_CLIENT
+/**
+ @todo
+ re-write this better without holding both locks at the same time
+*/
+Slave_log_event::Slave_log_event(THD* thd_arg,
+ Relay_log_info* rli)
+ :Log_event(thd_arg, 0, 0) , mem_pool(0), master_host(0)
+{
+ DBUG_ENTER("Slave_log_event");
+ if (!rli->inited) // QQ When can this happen ?
+ DBUG_VOID_RETURN;
+
+ Master_info* mi = rli->mi;
+ // TODO: re-write this better without holding both locks at the same time
+ mysql_mutex_lock(&mi->data_lock);
+ mysql_mutex_lock(&rli->data_lock);
+ master_host_len = strlen(mi->host);
+ master_log_len = strlen(rli->group_master_log_name);
+ // on OOM, just do not initialize the structure and print the error
+ if ((mem_pool = (char*)my_malloc(get_data_size() + 1,
+ MYF(MY_WME))))
+ {
+ master_host = mem_pool + SL_MASTER_HOST_OFFSET ;
+ memcpy(master_host, mi->host, master_host_len + 1);
+ master_log = master_host + master_host_len + 1;
+ memcpy(master_log, rli->group_master_log_name, master_log_len + 1);
+ master_port = mi->port;
+ master_pos = rli->group_master_log_pos;
+ DBUG_PRINT("info", ("master_log: %s pos: %lu", master_log,
+ (ulong) master_pos));
+ }
+ else
+ sql_print_error("Out of memory while recording slave event");
+ mysql_mutex_unlock(&rli->data_lock);
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_VOID_RETURN;
+}
+#endif /* !MYSQL_CLIENT */
+
+
+Slave_log_event::~Slave_log_event()
+{
+ my_free(mem_pool);
+}
+
+
+#ifdef MYSQL_CLIENT
+void Slave_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+
+ char llbuff[22];
+ if (print_event_info->short_form)
+ return;
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\n\
+Slave: master_host: '%s' master_port: %d master_log: '%s' master_pos: %s\n",
+ master_host, master_port, master_log, llstr(master_pos, llbuff));
+}
+#endif /* MYSQL_CLIENT */
+
+
+int Slave_log_event::get_data_size()
+{
+ return master_host_len + master_log_len + 1 + SL_MASTER_HOST_OFFSET;
+}
+
+
+#ifndef MYSQL_CLIENT
+bool Slave_log_event::write(IO_CACHE* file)
+{
+ ulong event_length= get_data_size();
+ int8store(mem_pool + SL_MASTER_POS_OFFSET, master_pos);
+ int2store(mem_pool + SL_MASTER_PORT_OFFSET, master_port);
+ // log and host are already there
+
+ return (write_header(file, event_length) ||
+ my_b_safe_write(file, (uchar*) mem_pool, event_length));
+}
+#endif
+
+
+void Slave_log_event::init_from_mem_pool(int data_size)
+{
+ master_pos = uint8korr(mem_pool + SL_MASTER_POS_OFFSET);
+ master_port = uint2korr(mem_pool + SL_MASTER_PORT_OFFSET);
+ master_host = mem_pool + SL_MASTER_HOST_OFFSET;
+ master_host_len = (uint) strlen(master_host);
+ // safety
+ master_log = master_host + master_host_len + 1;
+ if (master_log > mem_pool + data_size)
+ {
+ master_host = 0;
+ return;
+ }
+ master_log_len = (uint) strlen(master_log);
+}
+
+
+/** This code is not used, so has not been updated to be format-tolerant. */
+/* We are using description_event so that slave does not crash on Log_event
+ constructor */
+Slave_log_event::Slave_log_event(const char* buf,
+ uint event_len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf,description_event),mem_pool(0),master_host(0)
+{
+ if (event_len < LOG_EVENT_HEADER_LEN)
+ return;
+ event_len -= LOG_EVENT_HEADER_LEN;
+ if (!(mem_pool = (char*) my_malloc(event_len + 1, MYF(MY_WME))))
+ return;
+ memcpy(mem_pool, buf + LOG_EVENT_HEADER_LEN, event_len);
+ mem_pool[event_len] = 0;
+ init_from_mem_pool(event_len);
+}
+
+
+#ifndef MYSQL_CLIENT
+int Slave_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ if (mysql_bin_log.is_open())
+ return mysql_bin_log.write(this);
+ return 0;
+}
+#endif /* !MYSQL_CLIENT */
+
+
+/**************************************************************************
+ Stop_log_event methods
+**************************************************************************/
+
+/*
+ Stop_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Stop_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file,
+ Write_on_release_cache::FLUSH_F);
+
+ if (print_event_info->short_form)
+ return;
+
+ print_header(&cache, print_event_info, FALSE);
+ my_b_write_string(&cache, "\tStop\n");
+}
+#endif /* MYSQL_CLIENT */
+
+
+#ifndef MYSQL_CLIENT
+/*
+ The master stopped. We used to clean up all temporary tables but
+ this is useless as, as the master has shut down properly, it has
+ written all DROP TEMPORARY TABLE (prepared statements' deletion is
+ TODO only when we binlog prep stmts). We used to clean up
+ slave_load_tmpdir, but this is useless as it has been cleared at the
+ end of LOAD DATA INFILE. So we have nothing to do here. The place
+ were we must do this cleaning is in
+ Start_log_event_v3::do_apply_event(), not here. Because if we come
+ here, the master was sane.
+*/
+
+int Stop_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ DBUG_ENTER("Stop_log_event::do_update_pos");
+ /*
+ We do not want to update master_log pos because we get a rotate event
+ before stop, so by now group_master_log_name is set to the next log.
+ If we updated it, we will have incorrect master coordinates and this
+ could give false triggers in MASTER_POS_WAIT() that we have reached
+ the target position when in fact we have not.
+ */
+ if (rli->get_flag(Relay_log_info::IN_TRANSACTION))
+ rgi->inc_event_relay_log_pos();
+ else if (!rgi->is_parallel_exec)
+ {
+ rpl_global_gtid_slave_state.record_and_update_gtid(thd, rgi);
+ rli->inc_group_relay_log_pos(0, rgi);
+ flush_relay_log_info(rli);
+ }
+ DBUG_RETURN(0);
+}
+
+#endif /* !MYSQL_CLIENT */
+#endif /* HAVE_REPLICATION */
+
+
+/**************************************************************************
+ Create_file_log_event methods
+**************************************************************************/
+
+/*
+ Create_file_log_event ctor
+*/
+
+#ifndef MYSQL_CLIENT
+Create_file_log_event::
+Create_file_log_event(THD* thd_arg, sql_exchange* ex,
+ const char* db_arg, const char* table_name_arg,
+ List<Item>& fields_arg,
+ bool is_concurrent_arg,
+ enum enum_duplicates handle_dup,
+ bool ignore,
+ uchar* block_arg, uint block_len_arg, bool using_trans)
+ :Load_log_event(thd_arg, ex, db_arg, table_name_arg, fields_arg,
+ is_concurrent_arg,
+ handle_dup, ignore, using_trans),
+ fake_base(0), block(block_arg), event_buf(0), block_len(block_len_arg),
+ file_id(thd_arg->file_id = mysql_bin_log.next_file_id())
+{
+ DBUG_ENTER("Create_file_log_event");
+ sql_ex.force_new_format();
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Create_file_log_event::write_data_body()
+*/
+
+bool Create_file_log_event::write_data_body(IO_CACHE* file)
+{
+ bool res;
+ if ((res= Load_log_event::write_data_body(file)) || fake_base)
+ return res;
+ return (my_b_safe_write(file, (uchar*) "", 1) ||
+ my_b_safe_write(file, (uchar*) block, block_len));
+}
+
+
+/*
+ Create_file_log_event::write_data_header()
+*/
+
+bool Create_file_log_event::write_data_header(IO_CACHE* file)
+{
+ bool res;
+ uchar buf[CREATE_FILE_HEADER_LEN];
+ if ((res= Load_log_event::write_data_header(file)) || fake_base)
+ return res;
+ int4store(buf + CF_FILE_ID_OFFSET, file_id);
+ return my_b_safe_write(file, buf, CREATE_FILE_HEADER_LEN) != 0;
+}
+
+
+/*
+ Create_file_log_event::write_base()
+*/
+
+bool Create_file_log_event::write_base(IO_CACHE* file)
+{
+ bool res;
+ fake_base= 1; // pretend we are Load event
+ res= write(file);
+ fake_base= 0;
+ return res;
+}
+
+#endif /* !MYSQL_CLIENT */
+
+/*
+ Create_file_log_event ctor
+*/
+
+Create_file_log_event::Create_file_log_event(const char* buf, uint len,
+ const Format_description_log_event* description_event)
+ :Load_log_event(buf,0,description_event),fake_base(0),block(0),inited_from_old(0)
+{
+ DBUG_ENTER("Create_file_log_event::Create_file_log_event(char*,...)");
+ uint block_offset;
+ uint header_len= description_event->common_header_len;
+ uint8 load_header_len= description_event->post_header_len[LOAD_EVENT-1];
+ uint8 create_file_header_len= description_event->post_header_len[CREATE_FILE_EVENT-1];
+ if (!(event_buf= (char*) my_memdup(buf, len, MYF(MY_WME))) ||
+ copy_log_event(event_buf,len,
+ (((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ?
+ load_header_len + header_len :
+ (fake_base ? (header_len+load_header_len) :
+ (header_len+load_header_len) +
+ create_file_header_len)),
+ description_event))
+ DBUG_VOID_RETURN;
+ if (description_event->binlog_version!=1)
+ {
+ file_id= uint4korr(buf +
+ header_len +
+ load_header_len + CF_FILE_ID_OFFSET);
+ /*
+ Note that it's ok to use get_data_size() below, because it is computed
+ with values we have already read from this event (because we called
+ copy_log_event()); we are not using slave's format info to decode
+ master's format, we are really using master's format info.
+ Anyway, both formats should be identical (except the common_header_len)
+ as these Load events are not changed between 4.0 and 5.0 (as logging of
+ LOAD DATA INFILE does not use Load_log_event in 5.0).
+
+ The + 1 is for \0 terminating fname
+ */
+ block_offset= (description_event->common_header_len +
+ Load_log_event::get_data_size() +
+ create_file_header_len + 1);
+ if (len < block_offset)
+ DBUG_VOID_RETURN;
+ block = (uchar*)buf + block_offset;
+ block_len = len - block_offset;
+ }
+ else
+ {
+ sql_ex.force_new_format();
+ inited_from_old = 1;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Create_file_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Create_file_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info,
+ bool enable_local)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+
+ if (print_event_info->short_form)
+ {
+ if (enable_local && check_fname_outside_temp_buf())
+ Load_log_event::print(file, print_event_info);
+ return;
+ }
+
+ if (enable_local)
+ {
+ Load_log_event::print(file, print_event_info,
+ !check_fname_outside_temp_buf());
+ /**
+ reduce the size of io cache so that the write function is called
+ for every call to my_b_printf().
+ */
+ DBUG_EXECUTE_IF ("simulate_create_event_write_error",
+ {(&cache)->write_pos= (&cache)->write_end;
+ DBUG_SET("+d,simulate_file_write_error");});
+ /*
+ That one is for "file_id: etc" below: in mysqlbinlog we want the #, in
+ SHOW BINLOG EVENTS we don't.
+ */
+ my_b_write_byte(&cache, '#');
+ }
+
+ my_b_printf(&cache, " file_id: %d block_len: %d\n", file_id, block_len);
+}
+
+
+void Create_file_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
+{
+ print(file, print_event_info, 0);
+}
+#endif /* MYSQL_CLIENT */
+
+
+/*
+ Create_file_log_event::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Create_file_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[SAFE_NAME_LEN*2 + 30 + 21*2], *pos;
+ pos= strmov(buf, "db=");
+ memcpy(pos, db, db_len);
+ pos= strmov(pos + db_len, ";table=");
+ memcpy(pos, table_name, table_name_len);
+ pos= strmov(pos + table_name_len, ";file_id=");
+ pos= int10_to_str((long) file_id, pos, 10);
+ pos= strmov(pos, ";block_len=");
+ pos= int10_to_str((long) block_len, pos, 10);
+ protocol->store(buf, (uint) (pos-buf), &my_charset_bin);
+}
+#endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */
+
+
+/**
+ Create_file_log_event::do_apply_event()
+ Constructor for Create_file_log_event to intantiate an event
+ from the relay log on the slave.
+
+ @retval
+ 0 Success
+ @retval
+ 1 Failure
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int Create_file_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ char fname_buf[FN_REFLEN];
+ char *ext;
+ int fd = -1;
+ IO_CACHE file;
+ int error = 1;
+ Relay_log_info const *rli= rgi->rli;
+
+ THD_STAGE_INFO(thd, stage_making_temp_file_create_before_load_data);
+ bzero((char*)&file, sizeof(file));
+ ext= slave_load_file_stem(fname_buf, file_id, server_id, ".info",
+ &rli->mi->connection_name);
+ /* old copy may exist already */
+ mysql_file_delete(key_file_log_event_info, fname_buf, MYF(0));
+ if ((fd= mysql_file_create(key_file_log_event_info,
+ fname_buf, CREATE_MODE,
+ O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW,
+ MYF(MY_WME))) < 0 ||
+ init_io_cache(&file, fd, IO_SIZE, WRITE_CACHE, (my_off_t)0, 0,
+ MYF(MY_WME|MY_NABP)))
+ {
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in Create_file event: could not open file '%s'",
+ fname_buf);
+ goto err;
+ }
+
+ // a trick to avoid allocating another buffer
+ fname= fname_buf;
+ fname_len= (uint) (strmov(ext, ".data") - fname);
+ if (write_base(&file))
+ {
+ strmov(ext, ".info"); // to have it right in the error message
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in Create_file event: could not write to file '%s'",
+ fname_buf);
+ goto err;
+ }
+ end_io_cache(&file);
+ mysql_file_close(fd, MYF(0));
+
+ // fname_buf now already has .data, not .info, because we did our trick
+ /* old copy may exist already */
+ mysql_file_delete(key_file_log_event_data, fname_buf, MYF(0));
+ if ((fd= mysql_file_create(key_file_log_event_data,
+ fname_buf, CREATE_MODE,
+ O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW,
+ MYF(MY_WME))) < 0)
+ {
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in Create_file event: could not open file '%s'",
+ fname_buf);
+ goto err;
+ }
+ if (mysql_file_write(fd, (uchar*) block, block_len, MYF(MY_WME+MY_NABP)))
+ {
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in Create_file event: write to '%s' failed",
+ fname_buf);
+ goto err;
+ }
+ error=0; // Everything is ok
+
+err:
+ if (error)
+ end_io_cache(&file);
+ if (fd >= 0)
+ mysql_file_close(fd, MYF(0));
+ return error != 0;
+}
+#endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */
+
+
+/**************************************************************************
+ Append_block_log_event methods
+**************************************************************************/
+
+/*
+ Append_block_log_event ctor
+*/
+
+#ifndef MYSQL_CLIENT
+Append_block_log_event::Append_block_log_event(THD *thd_arg,
+ const char *db_arg,
+ uchar *block_arg,
+ uint block_len_arg,
+ bool using_trans)
+ :Log_event(thd_arg,0, using_trans), block(block_arg),
+ block_len(block_len_arg), file_id(thd_arg->file_id), db(db_arg)
+{
+}
+#endif
+
+
+/*
+ Append_block_log_event ctor
+*/
+
+Append_block_log_event::Append_block_log_event(const char* buf, uint len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event),block(0)
+{
+ DBUG_ENTER("Append_block_log_event::Append_block_log_event(char*,...)");
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 append_block_header_len=
+ description_event->post_header_len[APPEND_BLOCK_EVENT-1];
+ uint total_header_len= common_header_len+append_block_header_len;
+ if (len < total_header_len)
+ DBUG_VOID_RETURN;
+ file_id= uint4korr(buf + common_header_len + AB_FILE_ID_OFFSET);
+ block= (uchar*)buf + total_header_len;
+ block_len= len - total_header_len;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Append_block_log_event::write()
+*/
+
+#ifndef MYSQL_CLIENT
+bool Append_block_log_event::write(IO_CACHE* file)
+{
+ uchar buf[APPEND_BLOCK_HEADER_LEN];
+ int4store(buf + AB_FILE_ID_OFFSET, file_id);
+ return (write_header(file, APPEND_BLOCK_HEADER_LEN + block_len) ||
+ wrapper_my_b_safe_write(file, buf, APPEND_BLOCK_HEADER_LEN) ||
+ wrapper_my_b_safe_write(file, (uchar*) block, block_len) ||
+ write_footer(file));
+}
+#endif
+
+
+/*
+ Append_block_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Append_block_log_event::print(FILE* file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+
+ if (print_event_info->short_form)
+ return;
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\n#%s: file_id: %d block_len: %d\n",
+ get_type_str(), file_id, block_len);
+}
+#endif /* MYSQL_CLIENT */
+
+
+/*
+ Append_block_log_event::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Append_block_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[256];
+ uint length;
+ length= (uint) sprintf(buf, ";file_id=%u;block_len=%u", file_id, block_len);
+ protocol->store(buf, length, &my_charset_bin);
+}
+
+
+/*
+ Append_block_log_event::get_create_or_append()
+*/
+
+int Append_block_log_event::get_create_or_append() const
+{
+ return 0; /* append to the file, fail if not exists */
+}
+
+/*
+ Append_block_log_event::do_apply_event()
+*/
+
+int Append_block_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ char fname[FN_REFLEN];
+ int fd;
+ int error = 1;
+ Relay_log_info const *rli= rgi->rli;
+ DBUG_ENTER("Append_block_log_event::do_apply_event");
+
+ THD_STAGE_INFO(thd, stage_making_temp_file_append_before_load_data);
+ slave_load_file_stem(fname, file_id, server_id, ".data",
+ &rli->mi->cmp_connection_name);
+ if (get_create_or_append())
+ {
+ /*
+ Usually lex_start() is called by mysql_parse(), but we need it here
+ as the present method does not call mysql_parse().
+ */
+ lex_start(thd);
+ mysql_reset_thd_for_next_command(thd);
+ /* old copy may exist already */
+ mysql_file_delete(key_file_log_event_data, fname, MYF(0));
+ if ((fd= mysql_file_create(key_file_log_event_data,
+ fname, CREATE_MODE,
+ O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW,
+ MYF(MY_WME))) < 0)
+ {
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in %s event: could not create file '%s'",
+ get_type_str(), fname);
+ goto err;
+ }
+ }
+ else if ((fd= mysql_file_open(key_file_log_event_data,
+ fname,
+ O_WRONLY | O_APPEND | O_BINARY | O_NOFOLLOW,
+ MYF(MY_WME))) < 0)
+ {
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in %s event: could not open file '%s'",
+ get_type_str(), fname);
+ goto err;
+ }
+
+ DBUG_EXECUTE_IF("remove_slave_load_file_before_write",
+ {
+ my_delete(fname, MYF(0));
+ });
+
+ if (mysql_file_write(fd, (uchar*) block, block_len, MYF(MY_WME+MY_NABP)))
+ {
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in %s event: write to '%s' failed",
+ get_type_str(), fname);
+ goto err;
+ }
+ error=0;
+
+err:
+ if (fd >= 0)
+ mysql_file_close(fd, MYF(0));
+ DBUG_RETURN(error);
+}
+#endif
+
+
+/**************************************************************************
+ Delete_file_log_event methods
+**************************************************************************/
+
+/*
+ Delete_file_log_event ctor
+*/
+
+#ifndef MYSQL_CLIENT
+Delete_file_log_event::Delete_file_log_event(THD *thd_arg, const char* db_arg,
+ bool using_trans)
+ :Log_event(thd_arg, 0, using_trans), file_id(thd_arg->file_id), db(db_arg)
+{
+}
+#endif
+
+/*
+ Delete_file_log_event ctor
+*/
+
+Delete_file_log_event::Delete_file_log_event(const char* buf, uint len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event),file_id(0)
+{
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 delete_file_header_len= description_event->post_header_len[DELETE_FILE_EVENT-1];
+ if (len < (uint)(common_header_len + delete_file_header_len))
+ return;
+ file_id= uint4korr(buf + common_header_len + DF_FILE_ID_OFFSET);
+}
+
+
+/*
+ Delete_file_log_event::write()
+*/
+
+#ifndef MYSQL_CLIENT
+bool Delete_file_log_event::write(IO_CACHE* file)
+{
+ uchar buf[DELETE_FILE_HEADER_LEN];
+ int4store(buf + DF_FILE_ID_OFFSET, file_id);
+ return (write_header(file, sizeof(buf)) ||
+ wrapper_my_b_safe_write(file, buf, sizeof(buf)) ||
+ write_footer(file));
+}
+#endif
+
+
+/*
+ Delete_file_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Delete_file_log_event::print(FILE* file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+
+ if (print_event_info->short_form)
+ return;
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\n#Delete_file: file_id=%u\n", file_id);
+}
+#endif /* MYSQL_CLIENT */
+
+/*
+ Delete_file_log_event::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Delete_file_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[64];
+ uint length;
+ length= (uint) sprintf(buf, ";file_id=%u", (uint) file_id);
+ protocol->store(buf, (int32) length, &my_charset_bin);
+}
+#endif
+
+/*
+ Delete_file_log_event::do_apply_event()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int Delete_file_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ char fname[FN_REFLEN+10];
+ Relay_log_info const *rli= rgi->rli;
+ char *ext= slave_load_file_stem(fname, file_id, server_id, ".data",
+ &rli->mi->cmp_connection_name);
+ mysql_file_delete(key_file_log_event_data, fname, MYF(MY_WME));
+ strmov(ext, ".info");
+ mysql_file_delete(key_file_log_event_info, fname, MYF(MY_WME));
+ return 0;
+}
+#endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */
+
+
+/**************************************************************************
+ Execute_load_log_event methods
+**************************************************************************/
+
+/*
+ Execute_load_log_event ctor
+*/
+
+#ifndef MYSQL_CLIENT
+Execute_load_log_event::Execute_load_log_event(THD *thd_arg,
+ const char* db_arg,
+ bool using_trans)
+ :Log_event(thd_arg, 0, using_trans), file_id(thd_arg->file_id), db(db_arg)
+{
+}
+#endif
+
+
+/*
+ Execute_load_log_event ctor
+*/
+
+Execute_load_log_event::Execute_load_log_event(const char* buf, uint len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event), file_id(0)
+{
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 exec_load_header_len= description_event->post_header_len[EXEC_LOAD_EVENT-1];
+ if (len < (uint)(common_header_len+exec_load_header_len))
+ return;
+ file_id= uint4korr(buf + common_header_len + EL_FILE_ID_OFFSET);
+}
+
+
+/*
+ Execute_load_log_event::write()
+*/
+
+#ifndef MYSQL_CLIENT
+bool Execute_load_log_event::write(IO_CACHE* file)
+{
+ uchar buf[EXEC_LOAD_HEADER_LEN];
+ int4store(buf + EL_FILE_ID_OFFSET, file_id);
+ return (write_header(file, sizeof(buf)) ||
+ wrapper_my_b_safe_write(file, buf, sizeof(buf)) ||
+ write_footer(file));
+}
+#endif
+
+
+/*
+ Execute_load_log_event::print()
+*/
+
+#ifdef MYSQL_CLIENT
+void Execute_load_log_event::print(FILE* file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+
+ if (print_event_info->short_form)
+ return;
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\n#Exec_load: file_id=%d\n",
+ file_id);
+}
+#endif
+
+/*
+ Execute_load_log_event::pack_info()
+*/
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Execute_load_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[64];
+ uint length;
+ length= (uint) sprintf(buf, ";file_id=%u", (uint) file_id);
+ protocol->store(buf, (int32) length, &my_charset_bin);
+}
+
+
+/*
+ Execute_load_log_event::do_apply_event()
+*/
+
+int Execute_load_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ char fname[FN_REFLEN+10];
+ char *ext;
+ int fd;
+ int error= 1;
+ IO_CACHE file;
+ Load_log_event *lev= 0;
+ Relay_log_info const *rli= rgi->rli;
+
+ ext= slave_load_file_stem(fname, file_id, server_id, ".info",
+ &rli->mi->cmp_connection_name);
+ if ((fd= mysql_file_open(key_file_log_event_info,
+ fname, O_RDONLY | O_BINARY | O_NOFOLLOW,
+ MYF(MY_WME))) < 0 ||
+ init_io_cache(&file, fd, IO_SIZE, READ_CACHE, (my_off_t)0, 0,
+ MYF(MY_WME|MY_NABP)))
+ {
+ rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(),
+ "Error in Exec_load event: could not open file '%s'",
+ fname);
+ goto err;
+ }
+ if (!(lev= (Load_log_event*)
+ Log_event::read_log_event(&file,
+ (mysql_mutex_t*)0,
+ rli->relay_log.description_event_for_exec,
+ opt_slave_sql_verify_checksum)) ||
+ lev->get_type_code() != NEW_LOAD_EVENT)
+ {
+ rli->report(ERROR_LEVEL, 0, rgi->gtid_info(), "Error in Exec_load event: "
+ "file '%s' appears corrupted", fname);
+ goto err;
+ }
+ lev->thd = thd;
+ /*
+ lev->do_apply_event should use rli only for errors i.e. should
+ not advance rli's position.
+
+ lev->do_apply_event is the place where the table is loaded (it
+ calls mysql_load()).
+ */
+
+ if (lev->do_apply_event(0,rgi,1))
+ {
+ /*
+ We want to indicate the name of the file that could not be loaded
+ (SQL_LOADxxx).
+ But as we are here we are sure the error is in rli->last_slave_error and
+ rli->last_slave_errno (example of error: duplicate entry for key), so we
+ don't want to overwrite it with the filename.
+ What we want instead is add the filename to the current error message.
+ */
+ char *tmp= my_strdup(rli->last_error().message, MYF(MY_WME));
+ if (tmp)
+ {
+ rli->report(ERROR_LEVEL, rli->last_error().number, rgi->gtid_info(),
+ "%s. Failed executing load from '%s'", tmp, fname);
+ my_free(tmp);
+ }
+ goto err;
+ }
+ /*
+ We have an open file descriptor to the .info file; we need to close it
+ or Windows will refuse to delete the file in mysql_file_delete().
+ */
+ if (fd >= 0)
+ {
+ mysql_file_close(fd, MYF(0));
+ end_io_cache(&file);
+ fd= -1;
+ }
+ mysql_file_delete(key_file_log_event_info, fname, MYF(MY_WME));
+ memcpy(ext, ".data", 6);
+ mysql_file_delete(key_file_log_event_data, fname, MYF(MY_WME));
+ error = 0;
+
+err:
+ delete lev;
+ if (fd >= 0)
+ {
+ mysql_file_close(fd, MYF(0));
+ end_io_cache(&file);
+ }
+ return error;
+}
+
+#endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */
+
+
+/**************************************************************************
+ Begin_load_query_log_event methods
+**************************************************************************/
+
+#ifndef MYSQL_CLIENT
+Begin_load_query_log_event::
+Begin_load_query_log_event(THD* thd_arg, const char* db_arg, uchar* block_arg,
+ uint block_len_arg, bool using_trans)
+ :Append_block_log_event(thd_arg, db_arg, block_arg, block_len_arg,
+ using_trans)
+{
+ file_id= thd_arg->file_id= mysql_bin_log.next_file_id();
+}
+#endif
+
+
+Begin_load_query_log_event::
+Begin_load_query_log_event(const char* buf, uint len,
+ const Format_description_log_event* desc_event)
+ :Append_block_log_event(buf, len, desc_event)
+{
+}
+
+
+#if defined( HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int Begin_load_query_log_event::get_create_or_append() const
+{
+ return 1; /* create the file */
+}
+#endif /* defined( HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+Log_event::enum_skip_reason
+Begin_load_query_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ /*
+ If the slave skip counter is 1, then we should not start executing
+ on the next event.
+ */
+ return continue_group(rgi);
+}
+#endif
+
+
+/**************************************************************************
+ Execute_load_query_log_event methods
+**************************************************************************/
+
+
+#ifndef MYSQL_CLIENT
+Execute_load_query_log_event::
+Execute_load_query_log_event(THD *thd_arg, const char* query_arg,
+ ulong query_length_arg, uint fn_pos_start_arg,
+ uint fn_pos_end_arg,
+ enum_load_dup_handling dup_handling_arg,
+ bool using_trans, bool direct, bool suppress_use,
+ int errcode):
+ Query_log_event(thd_arg, query_arg, query_length_arg, using_trans, direct,
+ suppress_use, errcode),
+ file_id(thd_arg->file_id), fn_pos_start(fn_pos_start_arg),
+ fn_pos_end(fn_pos_end_arg), dup_handling(dup_handling_arg)
+{
+}
+#endif /* !MYSQL_CLIENT */
+
+
+Execute_load_query_log_event::
+Execute_load_query_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* desc_event):
+ Query_log_event(buf, event_len, desc_event, EXECUTE_LOAD_QUERY_EVENT),
+ file_id(0), fn_pos_start(0), fn_pos_end(0)
+{
+ if (!Query_log_event::is_valid())
+ return;
+
+ buf+= desc_event->common_header_len;
+
+ fn_pos_start= uint4korr(buf + ELQ_FN_POS_START_OFFSET);
+ fn_pos_end= uint4korr(buf + ELQ_FN_POS_END_OFFSET);
+ dup_handling= (enum_load_dup_handling)(*(buf + ELQ_DUP_HANDLING_OFFSET));
+
+ if (fn_pos_start > q_len || fn_pos_end > q_len ||
+ dup_handling > LOAD_DUP_REPLACE)
+ return;
+
+ file_id= uint4korr(buf + ELQ_FILE_ID_OFFSET);
+}
+
+
+ulong Execute_load_query_log_event::get_post_header_size_for_derived()
+{
+ return EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN;
+}
+
+
+#ifndef MYSQL_CLIENT
+bool
+Execute_load_query_log_event::write_post_header_for_derived(IO_CACHE* file)
+{
+ uchar buf[EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN];
+ int4store(buf, file_id);
+ int4store(buf + 4, fn_pos_start);
+ int4store(buf + 4 + 4, fn_pos_end);
+ *(buf + 4 + 4 + 4)= (uchar) dup_handling;
+ return wrapper_my_b_safe_write(file, buf, EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN);
+}
+#endif
+
+
+#ifdef MYSQL_CLIENT
+void Execute_load_query_log_event::print(FILE* file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ print(file, print_event_info, 0);
+}
+
+/**
+ Prints the query as LOAD DATA LOCAL and with rewritten filename.
+*/
+void Execute_load_query_log_event::print(FILE* file,
+ PRINT_EVENT_INFO* print_event_info,
+ const char *local_fname)
+{
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+
+ print_query_header(&cache, print_event_info);
+ /**
+ reduce the size of io cache so that the write function is called
+ for every call to my_b_printf().
+ */
+ DBUG_EXECUTE_IF ("simulate_execute_event_write_error",
+ {(&cache)->write_pos= (&cache)->write_end;
+ DBUG_SET("+d,simulate_file_write_error");});
+
+ if (local_fname)
+ {
+ my_b_write(&cache, (uchar*) query, fn_pos_start);
+ my_b_write_string(&cache, " LOCAL INFILE ");
+ pretty_print_str(&cache, local_fname, strlen(local_fname));
+
+ if (dup_handling == LOAD_DUP_REPLACE)
+ my_b_write_string(&cache, " REPLACE");
+ my_b_write_string(&cache, " INTO");
+ my_b_write(&cache, (uchar*) query + fn_pos_end, q_len-fn_pos_end);
+ my_b_printf(&cache, "\n%s\n", print_event_info->delimiter);
+ }
+ else
+ {
+ my_b_write(&cache, (uchar*) query, q_len);
+ my_b_printf(&cache, "\n%s\n", print_event_info->delimiter);
+ }
+
+ if (!print_event_info->short_form)
+ my_b_printf(&cache, "# file_id: %d \n", file_id);
+}
+#endif
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Execute_load_query_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf_mem[1024];
+ String buf(buf_mem, sizeof(buf_mem), system_charset_info);
+ buf.real_alloc(9 + db_len + q_len + 10 + 21);
+ if (db && db_len)
+ {
+ if (buf.append(STRING_WITH_LEN("use ")) ||
+ append_identifier(thd, &buf, db, db_len) ||
+ buf.append(STRING_WITH_LEN("; ")))
+ return;
+ }
+ if (query && q_len && buf.append(query, q_len))
+ return;
+ if (buf.append(" ;file_id=") ||
+ buf.append_ulonglong(file_id))
+ return;
+ protocol->store(buf.ptr(), buf.length(), &my_charset_bin);
+}
+
+
+int
+Execute_load_query_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ char *p;
+ char *buf;
+ char *fname;
+ char *fname_end;
+ int error;
+ Relay_log_info const *rli= rgi->rli;
+
+ buf= (char*) my_malloc(q_len + 1 - (fn_pos_end - fn_pos_start) +
+ (FN_REFLEN + 10) + 10 + 8 + 5, MYF(MY_WME));
+
+ DBUG_EXECUTE_IF("LOAD_DATA_INFILE_has_fatal_error", my_free(buf); buf= NULL;);
+
+ /* Replace filename and LOCAL keyword in query before executing it */
+ if (buf == NULL)
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(),
+ ER(ER_SLAVE_FATAL_ERROR), "Not enough memory");
+ return 1;
+ }
+
+ p= buf;
+ memcpy(p, query, fn_pos_start);
+ p+= fn_pos_start;
+ fname= (p= strmake(p, STRING_WITH_LEN(" INFILE \'")));
+ p= slave_load_file_stem(p, file_id, server_id, ".data",
+ &rli->mi->cmp_connection_name);
+ fname_end= p= strend(p); // Safer than p=p+5
+ *(p++)='\'';
+ switch (dup_handling) {
+ case LOAD_DUP_IGNORE:
+ p= strmake(p, STRING_WITH_LEN(" IGNORE"));
+ break;
+ case LOAD_DUP_REPLACE:
+ p= strmake(p, STRING_WITH_LEN(" REPLACE"));
+ break;
+ default:
+ /* Ordinary load data */
+ break;
+ }
+ p= strmake(p, STRING_WITH_LEN(" INTO "));
+ p= strmake(p, query+fn_pos_end, q_len-fn_pos_end);
+
+ error= Query_log_event::do_apply_event(rgi, buf, p-buf);
+
+ /* Forging file name for deletion in same buffer */
+ *fname_end= 0;
+
+ /*
+ If there was an error the slave is going to stop, leave the
+ file so that we can re-execute this event at START SLAVE.
+ */
+ if (!error)
+ mysql_file_delete(key_file_log_event_data, fname, MYF(MY_WME));
+
+ my_free(buf);
+ return error;
+}
+#endif
+
+
+/**************************************************************************
+ sql_ex_info methods
+**************************************************************************/
+
+/*
+ sql_ex_info::write_data()
+*/
+
+bool sql_ex_info::write_data(IO_CACHE* file)
+{
+ if (new_format())
+ {
+ return (write_str(file, field_term, (uint) field_term_len) ||
+ write_str(file, enclosed, (uint) enclosed_len) ||
+ write_str(file, line_term, (uint) line_term_len) ||
+ write_str(file, line_start, (uint) line_start_len) ||
+ write_str(file, escaped, (uint) escaped_len) ||
+ my_b_safe_write(file,(uchar*) &opt_flags,1));
+ }
+ else
+ {
+ /**
+ @todo This is sensitive to field padding. We should write a
+ char[7], not an old_sql_ex. /sven
+ */
+ old_sql_ex old_ex;
+ old_ex.field_term= *field_term;
+ old_ex.enclosed= *enclosed;
+ old_ex.line_term= *line_term;
+ old_ex.line_start= *line_start;
+ old_ex.escaped= *escaped;
+ old_ex.opt_flags= opt_flags;
+ old_ex.empty_flags=empty_flags;
+ return my_b_safe_write(file, (uchar*) &old_ex, sizeof(old_ex)) != 0;
+ }
+}
+
+
+/*
+ sql_ex_info::init()
+*/
+
+const char *sql_ex_info::init(const char *buf, const char *buf_end,
+ bool use_new_format)
+{
+ cached_new_format = use_new_format;
+ if (use_new_format)
+ {
+ empty_flags=0;
+ /*
+ The code below assumes that buf will not disappear from
+ under our feet during the lifetime of the event. This assumption
+ holds true in the slave thread if the log is in new format, but is not
+ the case when we have old format because we will be reusing net buffer
+ to read the actual file before we write out the Create_file event.
+ */
+ if (read_str(&buf, buf_end, &field_term, &field_term_len) ||
+ read_str(&buf, buf_end, &enclosed, &enclosed_len) ||
+ read_str(&buf, buf_end, &line_term, &line_term_len) ||
+ read_str(&buf, buf_end, &line_start, &line_start_len) ||
+ read_str(&buf, buf_end, &escaped, &escaped_len))
+ return 0;
+ opt_flags = *buf++;
+ }
+ else
+ {
+ field_term_len= enclosed_len= line_term_len= line_start_len= escaped_len=1;
+ field_term = buf++; // Use first byte in string
+ enclosed= buf++;
+ line_term= buf++;
+ line_start= buf++;
+ escaped= buf++;
+ opt_flags = *buf++;
+ empty_flags= *buf++;
+ if (empty_flags & FIELD_TERM_EMPTY)
+ field_term_len=0;
+ if (empty_flags & ENCLOSED_EMPTY)
+ enclosed_len=0;
+ if (empty_flags & LINE_TERM_EMPTY)
+ line_term_len=0;
+ if (empty_flags & LINE_START_EMPTY)
+ line_start_len=0;
+ if (empty_flags & ESCAPED_EMPTY)
+ escaped_len=0;
+ }
+ return buf;
+}
+
+
+/**************************************************************************
+ Rows_log_event member functions
+**************************************************************************/
+
+#ifndef MYSQL_CLIENT
+Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid,
+ MY_BITMAP const *cols, bool is_transactional,
+ Log_event_type event_type)
+ : Log_event(thd_arg, 0, is_transactional),
+ m_row_count(0),
+ m_table(tbl_arg),
+ m_table_id(tid),
+ m_width(tbl_arg ? tbl_arg->s->fields : 1),
+ m_rows_buf(0), m_rows_cur(0), m_rows_end(0), m_flags(0),
+ m_type(event_type), m_extra_row_data(0)
+#ifdef HAVE_REPLICATION
+ , m_curr_row(NULL), m_curr_row_end(NULL),
+ m_key(NULL), m_key_info(NULL), m_key_nr(0),
+ master_had_triggers(0)
+#endif
+{
+ /*
+ We allow a special form of dummy event when the table, and cols
+ are null and the table id is ~0UL. This is a temporary
+ solution, to be able to terminate a started statement in the
+ binary log: the extraneous events will be removed in the future.
+ */
+ DBUG_ASSERT((tbl_arg && tbl_arg->s && tid != ~0UL) ||
+ (!tbl_arg && !cols && tid == ~0UL));
+
+ if (thd_arg->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS)
+ set_flags(NO_FOREIGN_KEY_CHECKS_F);
+ if (thd_arg->variables.option_bits & OPTION_RELAXED_UNIQUE_CHECKS)
+ set_flags(RELAXED_UNIQUE_CHECKS_F);
+ /* if my_bitmap_init fails, caught in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols,
+ m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL,
+ m_width,
+ false)))
+ {
+ /* Cols can be zero if this is a dummy binrows event */
+ if (likely(cols != NULL))
+ {
+ memcpy(m_cols.bitmap, cols->bitmap, no_bytes_in_map(cols));
+ create_last_word_mask(&m_cols);
+ }
+ }
+ else
+ {
+ // Needed because my_bitmap_init() does not set it to null on failure
+ m_cols.bitmap= 0;
+ }
+}
+#endif
+
+Rows_log_event::Rows_log_event(const char *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Log_event(buf, description_event),
+ m_row_count(0),
+#ifndef MYSQL_CLIENT
+ m_table(NULL),
+#endif
+ m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0),
+ m_extra_row_data(0)
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ , m_curr_row(NULL), m_curr_row_end(NULL),
+ m_key(NULL), m_key_info(NULL), m_key_nr(0),
+ master_had_triggers(0)
+#endif
+{
+ DBUG_ENTER("Rows_log_event::Rows_log_event(const char*,...)");
+ uint8 const common_header_len= description_event->common_header_len;
+ Log_event_type event_type= (Log_event_type) buf[EVENT_TYPE_OFFSET];
+ m_type= event_type;
+
+ uint8 const post_header_len= description_event->post_header_len[event_type-1];
+
+ DBUG_PRINT("enter",("event_len: %u common_header_len: %d "
+ "post_header_len: %d",
+ event_len, common_header_len,
+ post_header_len));
+
+ const char *post_start= buf + common_header_len;
+ post_start+= RW_MAPID_OFFSET;
+ if (post_header_len == 6)
+ {
+ /* Master is of an intermediate source tree before 5.1.4. Id is 4 bytes */
+ m_table_id= uint4korr(post_start);
+ post_start+= 4;
+ }
+ else
+ {
+ m_table_id= (ulong) uint6korr(post_start);
+ post_start+= RW_FLAGS_OFFSET;
+ }
+
+ m_flags= uint2korr(post_start);
+ post_start+= 2;
+
+ uint16 var_header_len= 0;
+ if (post_header_len == ROWS_HEADER_LEN_V2)
+ {
+ /*
+ Have variable length header, check length,
+ which includes length bytes
+ */
+ var_header_len= uint2korr(post_start);
+ assert(var_header_len >= 2);
+ var_header_len-= 2;
+
+ /* Iterate over var-len header, extracting 'chunks' */
+ const char* start= post_start + 2;
+ const char* end= start + var_header_len;
+ for (const char* pos= start; pos < end;)
+ {
+ switch(*pos++)
+ {
+ case RW_V_EXTRAINFO_TAG:
+ {
+ /* Have an 'extra info' section, read it in */
+ assert((end - pos) >= EXTRA_ROW_INFO_HDR_BYTES);
+ uint8 infoLen= pos[EXTRA_ROW_INFO_LEN_OFFSET];
+ assert((end - pos) >= infoLen);
+ /* Just store/use the first tag of this type, skip others */
+ if (likely(!m_extra_row_data))
+ {
+ m_extra_row_data= (uchar*) my_malloc(infoLen,
+ MYF(MY_WME));
+ if (likely(m_extra_row_data != NULL))
+ {
+ memcpy(m_extra_row_data, pos, infoLen);
+ }
+ }
+ pos+= infoLen;
+ break;
+ }
+ default:
+ /* Unknown code, we will not understand anything further here */
+ pos= end; /* Break loop */
+ }
+ }
+ }
+
+ uchar const *const var_start=
+ (const uchar *)buf + common_header_len + post_header_len + var_header_len;
+ uchar const *const ptr_width= var_start;
+ uchar *ptr_after_width= (uchar*) ptr_width;
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ m_width = net_field_length(&ptr_after_width);
+ DBUG_PRINT("debug", ("m_width=%lu", m_width));
+ /* if my_bitmap_init fails, catched in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols,
+ m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL,
+ m_width,
+ false)))
+ {
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ memcpy(m_cols.bitmap, ptr_after_width, (m_width + 7) / 8);
+ create_last_word_mask(&m_cols);
+ ptr_after_width+= (m_width + 7) / 8;
+ DBUG_DUMP("m_cols", (uchar*) m_cols.bitmap, no_bytes_in_map(&m_cols));
+ }
+ else
+ {
+ // Needed because my_bitmap_init() does not set it to null on failure
+ m_cols.bitmap= NULL;
+ DBUG_VOID_RETURN;
+ }
+
+ m_cols_ai.bitmap= m_cols.bitmap; /* See explanation in is_valid() */
+
+ if ((event_type == UPDATE_ROWS_EVENT) ||
+ (event_type == UPDATE_ROWS_EVENT_V1))
+ {
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+
+ /* if my_bitmap_init fails, caught in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols_ai,
+ m_width <= sizeof(m_bitbuf_ai)*8 ? m_bitbuf_ai : NULL,
+ m_width,
+ false)))
+ {
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ memcpy(m_cols_ai.bitmap, ptr_after_width, (m_width + 7) / 8);
+ create_last_word_mask(&m_cols_ai);
+ ptr_after_width+= (m_width + 7) / 8;
+ DBUG_DUMP("m_cols_ai", (uchar*) m_cols_ai.bitmap,
+ no_bytes_in_map(&m_cols_ai));
+ }
+ else
+ {
+ // Needed because my_bitmap_init() does not set it to null on failure
+ m_cols_ai.bitmap= 0;
+ DBUG_VOID_RETURN;
+ }
+ }
+
+ const uchar* const ptr_rows_data= (const uchar*) ptr_after_width;
+
+ size_t const data_size= event_len - (ptr_rows_data - (const uchar *) buf);
+ DBUG_PRINT("info",("m_table_id: %lu m_flags: %d m_width: %lu data_size: %lu",
+ m_table_id, m_flags, m_width, (ulong) data_size));
+
+ m_rows_buf= (uchar*) my_malloc(data_size, MYF(MY_WME));
+ if (likely((bool)m_rows_buf))
+ {
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ m_curr_row= m_rows_buf;
+#endif
+ m_rows_end= m_rows_buf + data_size;
+ m_rows_cur= m_rows_end;
+ memcpy(m_rows_buf, ptr_rows_data, data_size);
+ }
+ else
+ m_cols.bitmap= 0; // to not free it
+
+ DBUG_VOID_RETURN;
+}
+
+Rows_log_event::~Rows_log_event()
+{
+ if (m_cols.bitmap == m_bitbuf) // no my_malloc happened
+ m_cols.bitmap= 0; // so no my_free in my_bitmap_free
+ my_bitmap_free(&m_cols); // To pair with my_bitmap_init().
+ my_free(m_rows_buf);
+ my_free(m_extra_row_data);
+}
+
+int Rows_log_event::get_data_size()
+{
+ int const general_type_code= get_general_type_code();
+
+ uchar buf[MAX_INT_WIDTH];
+ uchar *end= net_store_length(buf, m_width);
+
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ return 6 + no_bytes_in_map(&m_cols) + (end - buf) +
+ (general_type_code == UPDATE_ROWS_EVENT ? no_bytes_in_map(&m_cols_ai) : 0) +
+ (m_rows_cur - m_rows_buf););
+
+ int data_size= 0;
+ bool is_v2_event= get_type_code() > DELETE_ROWS_EVENT_V1;
+ if (is_v2_event)
+ {
+ data_size= ROWS_HEADER_LEN_V2 +
+ (m_extra_row_data ?
+ RW_V_TAG_LEN + m_extra_row_data[EXTRA_ROW_INFO_LEN_OFFSET]:
+ 0);
+ }
+ else
+ {
+ data_size= ROWS_HEADER_LEN_V1;
+ }
+ data_size+= no_bytes_in_map(&m_cols);
+ data_size+= (uint) (end - buf);
+
+ if (general_type_code == UPDATE_ROWS_EVENT)
+ data_size+= no_bytes_in_map(&m_cols_ai);
+
+ data_size+= (uint) (m_rows_cur - m_rows_buf);
+ return data_size;
+}
+
+
+#ifndef MYSQL_CLIENT
+int Rows_log_event::do_add_row_data(uchar *row_data, size_t length)
+{
+ /*
+ When the table has a primary key, we would probably want, by default, to
+ log only the primary key value instead of the entire "before image". This
+ would save binlog space. TODO
+ */
+ DBUG_ENTER("Rows_log_event::do_add_row_data");
+ DBUG_PRINT("enter", ("row_data: 0x%lx length: %lu", (ulong) row_data,
+ (ulong) length));
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("row_data", row_data, MY_MIN(length, 32));
+#endif
+
+ DBUG_ASSERT(m_rows_buf <= m_rows_cur);
+ DBUG_ASSERT(!m_rows_buf || (m_rows_end && m_rows_buf < m_rows_end));
+ DBUG_ASSERT(m_rows_cur <= m_rows_end);
+
+ /* The cast will always work since m_rows_cur <= m_rows_end */
+ if (static_cast<size_t>(m_rows_end - m_rows_cur) <= length)
+ {
+ size_t const block_size= 1024;
+ ulong cur_size= m_rows_cur - m_rows_buf;
+ DBUG_EXECUTE_IF("simulate_too_big_row_case1",
+ cur_size= UINT_MAX32 - (block_size * 10);
+ length= UINT_MAX32 - (block_size * 10););
+ DBUG_EXECUTE_IF("simulate_too_big_row_case2",
+ cur_size= UINT_MAX32 - (block_size * 10);
+ length= block_size * 10;);
+ DBUG_EXECUTE_IF("simulate_too_big_row_case3",
+ cur_size= block_size * 10;
+ length= UINT_MAX32 - (block_size * 10););
+ DBUG_EXECUTE_IF("simulate_too_big_row_case4",
+ cur_size= UINT_MAX32 - (block_size * 10);
+ length= (block_size * 10) - block_size + 1;);
+ ulong remaining_space= UINT_MAX32 - cur_size;
+ /* Check that the new data fits within remaining space and we can add
+ block_size without wrapping.
+ */
+ if (length > remaining_space ||
+ ((length + block_size) > remaining_space))
+ {
+ sql_print_error("The row data is greater than 4GB, which is too big to "
+ "write to the binary log.");
+ DBUG_RETURN(ER_BINLOG_ROW_LOGGING_FAILED);
+ }
+ ulong const new_alloc=
+ block_size * ((cur_size + length + block_size - 1) / block_size);
+
+ uchar* const new_buf= (uchar*)my_realloc((uchar*)m_rows_buf, (uint) new_alloc,
+ MYF(MY_ALLOW_ZERO_PTR|MY_WME));
+ if (unlikely(!new_buf))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+ /* If the memory moved, we need to move the pointers */
+ if (new_buf != m_rows_buf)
+ {
+ m_rows_buf= new_buf;
+ m_rows_cur= m_rows_buf + cur_size;
+ }
+
+ /*
+ The end pointer should always be changed to point to the end of
+ the allocated memory.
+ */
+ m_rows_end= m_rows_buf + new_alloc;
+ }
+
+ DBUG_ASSERT(m_rows_cur + length <= m_rows_end);
+ memcpy(m_rows_cur, row_data, length);
+ m_rows_cur+= length;
+ m_row_count++;
+ DBUG_RETURN(0);
+}
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+
+/**
+ Restores empty table list as it was before trigger processing.
+
+ @note We have a lot of ASSERTS that check the lists when we close tables.
+ There was the same problem with MERGE MYISAM tables and so here we try to
+ go the same way.
+*/
+static void restore_empty_query_table_list(LEX *lex)
+{
+#ifdef RBR_TRIGGERS
+ if (lex->first_not_own_table())
+ (*lex->first_not_own_table()->prev_global)= NULL;
+ lex->query_tables= NULL;
+ lex->query_tables_last= &lex->query_tables;
+#endif //RBR_TRIGGERS
+}
+
+
+int Rows_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ Relay_log_info const *rli= rgi->rli;
+ TABLE* table;
+ DBUG_ENTER("Rows_log_event::do_apply_event(Relay_log_info*)");
+ int error= 0;
+ /*
+ If m_table_id == ~0UL, then we have a dummy event that does not
+ contain any data. In that case, we just remove all tables in the
+ tables_to_lock list, close the thread tables, and return with
+ success.
+ */
+ if (m_table_id == ~0UL)
+ {
+ /*
+ This one is supposed to be set: just an extra check so that
+ nothing strange has happened.
+ */
+ DBUG_ASSERT(get_flags(STMT_END_F));
+
+ rgi->slave_close_thread_tables(thd);
+ thd->clear_error();
+ DBUG_RETURN(0);
+ }
+
+ /*
+ 'thd' has been set by exec_relay_log_event(), just before calling
+ do_apply_event(). We still check here to prevent future coding
+ errors.
+ */
+ DBUG_ASSERT(rgi->thd == thd);
+
+ /*
+ If there is no locks taken, this is the first binrow event seen
+ after the table map events. We should then lock all the tables
+ used in the transaction and proceed with execution of the actual
+ event.
+ */
+ if (!thd->lock)
+ {
+ /*
+ Lock_tables() reads the contents of thd->lex, so they must be
+ initialized.
+
+ We also call the mysql_reset_thd_for_next_command(), since this
+ is the logical start of the next "statement". Note that this
+ call might reset the value of current_stmt_binlog_format, so
+ we need to do any changes to that value after this function.
+ */
+ delete_explain_query(thd->lex);
+ lex_start(thd);
+ mysql_reset_thd_for_next_command(thd);
+ /*
+ The current statement is just about to begin and
+ has not yet modified anything. Note, all.modified is reset
+ by mysql_reset_thd_for_next_command.
+ */
+ thd->transaction.stmt.modified_non_trans_table= FALSE;
+ /*
+ This is a row injection, so we flag the "statement" as
+ such. Note that this code is called both when the slave does row
+ injections and when the BINLOG statement is used to do row
+ injections.
+ */
+ thd->lex->set_stmt_row_injection();
+
+ /*
+ There are a few flags that are replicated with each row event.
+ Make sure to set/clear them before executing the main body of
+ the event.
+ */
+ if (get_flags(NO_FOREIGN_KEY_CHECKS_F))
+ thd->variables.option_bits|= OPTION_NO_FOREIGN_KEY_CHECKS;
+ else
+ thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS;
+
+ if (get_flags(RELAXED_UNIQUE_CHECKS_F))
+ thd->variables.option_bits|= OPTION_RELAXED_UNIQUE_CHECKS;
+ else
+ thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS;
+ /* A small test to verify that objects have consistent types */
+ DBUG_ASSERT(sizeof(thd->variables.option_bits) == sizeof(OPTION_RELAXED_UNIQUE_CHECKS));
+
+ if (slave_run_triggers_for_rbr)
+ {
+ LEX *lex= thd->lex;
+ uint8 new_trg_event_map= get_trg_event_map();
+
+ /*
+ Trigger's procedures work with global table list. So we have to add
+ rgi->tables_to_lock content there to get trigger's in the list.
+
+ Then restore_empty_query_table_list() restore the list as it was
+ */
+ DBUG_ASSERT(lex->query_tables == NULL);
+ if ((lex->query_tables= rgi->tables_to_lock))
+ rgi->tables_to_lock->prev_global= &lex->query_tables;
+
+ for (TABLE_LIST *tables= rgi->tables_to_lock; tables;
+ tables= tables->next_global)
+ {
+ tables->trg_event_map= new_trg_event_map;
+ lex->query_tables_last= &tables->next_global;
+ }
+ }
+ if (open_and_lock_tables(thd, rgi->tables_to_lock, FALSE, 0))
+ {
+ uint actual_error= thd->get_stmt_da()->sql_errno();
+ if ((thd->is_slave_error || thd->is_fatal_error) &&
+ !is_parallel_retry_error(rgi, actual_error))
+ {
+ /*
+ Error reporting borrowed from Query_log_event with many excessive
+ simplifications.
+ We should not honour --slave-skip-errors at this point as we are
+ having severe errors which should not be skiped.
+ */
+ rli->report(ERROR_LEVEL, actual_error, rgi->gtid_info(),
+ "Error executing row event: '%s'",
+ (actual_error ? thd->get_stmt_da()->message() :
+ "unexpected success or fatal error"));
+ thd->is_slave_error= 1;
+ }
+ /* remove trigger's tables */
+ error= actual_error;
+ goto err;
+ }
+
+ /*
+ When the open and locking succeeded, we check all tables to
+ ensure that they still have the correct type.
+
+ We can use a down cast here since we know that every table added
+ to the tables_to_lock is a RPL_TABLE_LIST.
+ */
+
+ {
+ DBUG_PRINT("debug", ("Checking compability of tables to lock - tables_to_lock: %p",
+ rgi->tables_to_lock));
+
+ /**
+ When using RBR and MyISAM MERGE tables the base tables that make
+ up the MERGE table can be appended to the list of tables to lock.
+
+ Thus, we just check compatibility for those that tables that have
+ a correspondent table map event (ie, those that are actually going
+ to be accessed while applying the event). That's why the loop stops
+ at rli->tables_to_lock_count .
+
+ NOTE: The base tables are added here are removed when
+ close_thread_tables is called.
+ */
+ RPL_TABLE_LIST *ptr= rgi->tables_to_lock;
+ for (uint i= 0 ; ptr && (i < rgi->tables_to_lock_count);
+ ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_global), i++)
+ {
+ DBUG_ASSERT(ptr->m_tabledef_valid);
+ TABLE *conv_table;
+ if (!ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table))
+ {
+ DBUG_PRINT("debug", ("Table: %s.%s is not compatible with master",
+ ptr->table->s->db.str,
+ ptr->table->s->table_name.str));
+ /*
+ We should not honour --slave-skip-errors at this point as we are
+ having severe errors which should not be skiped.
+ */
+ thd->is_slave_error= 1;
+ /* remove trigger's tables */
+ error= ERR_BAD_TABLE_DEF;
+ goto err;
+ }
+ DBUG_PRINT("debug", ("Table: %s.%s is compatible with master"
+ " - conv_table: %p",
+ ptr->table->s->db.str,
+ ptr->table->s->table_name.str, conv_table));
+ ptr->m_conv_table= conv_table;
+ }
+ }
+
+ /*
+ ... and then we add all the tables to the table map and but keep
+ them in the tables to lock list.
+
+ We also invalidate the query cache for all the tables, since
+ they will now be changed.
+
+ TODO [/Matz]: Maybe the query cache should not be invalidated
+ here? It might be that a table is not changed, even though it
+ was locked for the statement. We do know that each
+ Rows_log_event contain at least one row, so after processing one
+ Rows_log_event, we can invalidate the query cache for the
+ associated table.
+ */
+ TABLE_LIST *ptr= rgi->tables_to_lock;
+ for (uint i=0 ; ptr && (i < rgi->tables_to_lock_count); ptr= ptr->next_global, i++)
+ {
+ rgi->m_table_map.set_table(ptr->table_id, ptr->table);
+ /*
+ Following is passing flag about triggers on the server. The problem was
+ to pass it between table map event and row event. I do it via extended
+ TABLE_LIST (RPL_TABLE_LIST) but row event uses only TABLE so I need to
+ find somehow the corresponding TABLE_LIST.
+ */
+ if (m_table_id == ptr->table_id)
+ {
+ ptr->table->master_had_triggers=
+ ((RPL_TABLE_LIST*)ptr)->master_had_triggers;
+ }
+ }
+
+#ifdef HAVE_QUERY_CACHE
+ query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock);
+#endif
+ }
+
+ table= m_table= rgi->m_table_map.get_table(m_table_id);
+
+ DBUG_PRINT("debug", ("m_table: 0x%lx, m_table_id: %lu%s",
+ (ulong) m_table, m_table_id,
+ table && master_had_triggers ?
+ " (master had triggers)" : ""));
+ if (table)
+ {
+ master_had_triggers= table->master_had_triggers;
+ bool transactional_table= table->file->has_transactions();
+ /*
+ table == NULL means that this table should not be replicated
+ (this was set up by Table_map_log_event::do_apply_event()
+ which tested replicate-* rules).
+ */
+
+ /*
+ It's not needed to set_time() but
+ 1) it continues the property that "Time" in SHOW PROCESSLIST shows how
+ much slave is behind
+ 2) it will be needed when we allow replication from a table with no
+ TIMESTAMP column to a table with one.
+ So we call set_time(), like in SBR. Presently it changes nothing.
+ */
+ thd->set_time(when, when_sec_part);
+
+ if (m_width == table->s->fields && bitmap_is_set_all(&m_cols))
+ set_flags(COMPLETE_ROWS_F);
+
+ /*
+ Set tables write and read sets.
+
+ Read_set contains all slave columns (in case we are going to fetch
+ a complete record from slave)
+
+ Write_set equals the m_cols bitmap sent from master but it can be
+ longer if slave has extra columns.
+ */
+
+ DBUG_PRINT_BITSET("debug", "Setting table's write_set from: %s", &m_cols);
+
+ bitmap_set_all(table->read_set);
+ bitmap_set_all(table->write_set);
+ if (!get_flags(COMPLETE_ROWS_F))
+ bitmap_intersect(table->write_set,&m_cols);
+
+ this->slave_exec_mode= slave_exec_mode_options; // fix the mode
+
+ // Do event specific preparations
+ error= do_before_row_operations(rli);
+
+ /*
+ Bug#56662 Assertion failed: next_insert_id == 0, file handler.cc
+ Don't allow generation of auto_increment value when processing
+ rows event by setting 'MODE_NO_AUTO_VALUE_ON_ZERO'. The exception
+ to this rule happens when the auto_inc column exists on some
+ extra columns on the slave. In that case, do not force
+ MODE_NO_AUTO_VALUE_ON_ZERO.
+ */
+ ulonglong saved_sql_mode= thd->variables.sql_mode;
+ if (!is_auto_inc_in_extra_columns())
+ thd->variables.sql_mode= MODE_NO_AUTO_VALUE_ON_ZERO;
+
+ // row processing loop
+
+ /*
+ set the initial time of this ROWS statement if it was not done
+ before in some other ROWS event.
+ */
+ rgi->set_row_stmt_start_timestamp();
+
+ THD_STAGE_INFO(thd, stage_executing);
+ while (error == 0 && m_curr_row < m_rows_end)
+ {
+ /* in_use can have been set to NULL in close_tables_for_reopen */
+ THD* old_thd= table->in_use;
+ if (!table->in_use)
+ table->in_use= thd;
+
+ error= do_exec_row(rgi);
+
+ if (error)
+ DBUG_PRINT("info", ("error: %s", HA_ERR(error)));
+ DBUG_ASSERT(error != HA_ERR_RECORD_DELETED);
+
+ table->in_use = old_thd;
+
+ if (error)
+ {
+ int actual_error= convert_handler_error(error, thd, table);
+ bool idempotent_error= (idempotent_error_code(error) &&
+ (slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT));
+ bool ignored_error= (idempotent_error == 0 ?
+ ignored_error_code(actual_error) : 0);
+
+ if (idempotent_error || ignored_error)
+ {
+ if (global_system_variables.log_warnings)
+ slave_rows_error_report(WARNING_LEVEL, error, rgi, thd, table,
+ get_type_str(),
+ RPL_LOG_NAME, (ulong) log_pos);
+ clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
+ error= 0;
+ if (idempotent_error == 0)
+ break;
+ }
+ }
+
+ /*
+ If m_curr_row_end was not set during event execution (e.g., because
+ of errors) we can't proceed to the next row. If the error is transient
+ (i.e., error==0 at this point) we must call unpack_current_row() to set
+ m_curr_row_end.
+ */
+
+ DBUG_PRINT("info", ("curr_row: 0x%lu; curr_row_end: 0x%lu; rows_end: 0x%lu",
+ (ulong) m_curr_row, (ulong) m_curr_row_end, (ulong) m_rows_end));
+
+ if (!m_curr_row_end && !error)
+ error= unpack_current_row(rgi);
+
+ // at this moment m_curr_row_end should be set
+ DBUG_ASSERT(error || m_curr_row_end != NULL);
+ DBUG_ASSERT(error || m_curr_row < m_curr_row_end);
+ DBUG_ASSERT(error || m_curr_row_end <= m_rows_end);
+
+ m_curr_row= m_curr_row_end;
+
+ if (error == 0 && !transactional_table)
+ thd->transaction.all.modified_non_trans_table=
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ } // row processing loop
+
+ /*
+ Restore the sql_mode after the rows event is processed.
+ */
+ thd->variables.sql_mode= saved_sql_mode;
+
+ {/**
+ The following failure injecion works in cooperation with tests
+ setting @@global.debug= 'd,stop_slave_middle_group'.
+ The sql thread receives the killed status and will proceed
+ to shutdown trying to finish incomplete events group.
+ */
+ DBUG_EXECUTE_IF("stop_slave_middle_group",
+ if (thd->transaction.all.modified_non_trans_table)
+ const_cast<Relay_log_info*>(rli)->abort_slave= 1;);
+ }
+
+ if ((error= do_after_row_operations(rli, error)) &&
+ ignored_error_code(convert_handler_error(error, thd, table)))
+ {
+
+ if (global_system_variables.log_warnings)
+ slave_rows_error_report(WARNING_LEVEL, error, rgi, thd, table,
+ get_type_str(),
+ RPL_LOG_NAME, (ulong) log_pos);
+ clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
+ error= 0;
+ }
+ } // if (table)
+
+
+ if (error)
+ {
+ slave_rows_error_report(ERROR_LEVEL, error, rgi, thd, table,
+ get_type_str(),
+ RPL_LOG_NAME, (ulong) log_pos);
+ /*
+ @todo We should probably not call
+ reset_current_stmt_binlog_format_row() from here.
+
+ Note: this applies to log_event_old.cc too.
+ /Sven
+ */
+ thd->reset_current_stmt_binlog_format_row();
+ thd->is_slave_error= 1;
+ /* remove trigger's tables */
+ goto err;
+ }
+
+ /* remove trigger's tables */
+ if (slave_run_triggers_for_rbr)
+ restore_empty_query_table_list(thd->lex);
+ if (get_flags(STMT_END_F) && (error= rows_event_stmt_cleanup(rgi, thd)))
+ slave_rows_error_report(ERROR_LEVEL,
+ thd->is_error() ? 0 : error,
+ rgi, thd, table,
+ get_type_str(),
+ RPL_LOG_NAME, (ulong) log_pos);
+ DBUG_RETURN(error);
+
+err:
+ if (slave_run_triggers_for_rbr)
+ restore_empty_query_table_list(thd->lex);
+ rgi->slave_close_thread_tables(thd);
+ DBUG_RETURN(error);
+}
+
+Log_event::enum_skip_reason
+Rows_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ /*
+ If the slave skip counter is 1 and this event does not end a
+ statement, then we should not start executing on the next event.
+ Otherwise, we defer the decision to the normal skipping logic.
+ */
+ if (rgi->rli->slave_skip_counter == 1 && !get_flags(STMT_END_F))
+ return Log_event::EVENT_SKIP_IGNORE;
+ else
+ return Log_event::do_shall_skip(rgi);
+}
+
+/**
+ The function is called at Rows_log_event statement commit time,
+ normally from Rows_log_event::do_update_pos() and possibly from
+ Query_log_event::do_apply_event() of the COMMIT.
+ The function commits the last statement for engines, binlog and
+ releases resources have been allocated for the statement.
+
+ @retval 0 Ok.
+ @retval non-zero Error at the commit.
+ */
+
+static int rows_event_stmt_cleanup(rpl_group_info *rgi, THD * thd)
+{
+ int error;
+ DBUG_ENTER("rows_event_stmt_cleanup");
+
+ {
+ /*
+ This is the end of a statement or transaction, so close (and
+ unlock) the tables we opened when processing the
+ Table_map_log_event starting the statement.
+
+ OBSERVER. This will clear *all* mappings, not only those that
+ are open for the table. There is not good handle for on-close
+ actions for tables.
+
+ NOTE. Even if we have no table ('table' == 0) we still need to be
+ here, so that we increase the group relay log position. If we didn't, we
+ could have a group relay log position which lags behind "forever"
+ (assume the last master's transaction is ignored by the slave because of
+ replicate-ignore rules).
+ */
+ error= thd->binlog_flush_pending_rows_event(TRUE);
+
+ /*
+ If this event is not in a transaction, the call below will, if some
+ transactional storage engines are involved, commit the statement into
+ them and flush the pending event to binlog.
+ If this event is in a transaction, the call will do nothing, but a
+ Xid_log_event will come next which will, if some transactional engines
+ are involved, commit the transaction and flush the pending event to the
+ binlog.
+ If there was a deadlock the transaction should have been rolled back
+ already. So there should be no need to rollback the transaction.
+ */
+ DBUG_ASSERT(! thd->transaction_rollback_request);
+ error|= (error ? trans_rollback_stmt(thd) : trans_commit_stmt(thd));
+
+ /*
+ Now what if this is not a transactional engine? we still need to
+ flush the pending event to the binlog; we did it with
+ thd->binlog_flush_pending_rows_event(). Note that we imitate
+ what is done for real queries: a call to
+ ha_autocommit_or_rollback() (sometimes only if involves a
+ transactional engine), and a call to be sure to have the pending
+ event flushed.
+ */
+
+ /*
+ @todo We should probably not call
+ reset_current_stmt_binlog_format_row() from here.
+
+ Note: this applies to log_event_old.cc too
+
+ Btw, the previous comment about transactional engines does not
+ seem related to anything that happens here.
+ /Sven
+ */
+ thd->reset_current_stmt_binlog_format_row();
+
+ /*
+ Reset modified_non_trans_table that we have set in
+ rows_log_event::do_apply_event()
+ */
+ if (!thd->in_multi_stmt_transaction_mode())
+ thd->transaction.all.modified_non_trans_table= 0;
+
+ rgi->cleanup_context(thd, 0);
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ The method either increments the relay log position or
+ commits the current statement and increments the master group
+ possition if the event is STMT_END_F flagged and
+ the statement corresponds to the autocommit query (i.e replicated
+ without wrapping in BEGIN/COMMIT)
+
+ @retval 0 Success
+ @retval non-zero Error in the statement commit
+ */
+int
+Rows_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ DBUG_ENTER("Rows_log_event::do_update_pos");
+ int error= 0;
+
+ DBUG_PRINT("info", ("flags: %s",
+ get_flags(STMT_END_F) ? "STMT_END_F " : ""));
+
+ if (get_flags(STMT_END_F))
+ {
+ /*
+ Indicate that a statement is finished.
+ Step the group log position if we are not in a transaction,
+ otherwise increase the event log position.
+ */
+ rli->stmt_done(log_pos, thd, rgi);
+ /*
+ Clear any errors in thd->net.last_err*. It is not known if this is
+ needed or not. It is believed that any errors that may exist in
+ thd->net.last_err* are allowed. Examples of errors are "key not
+ found", which is produced in the test case rpl_row_conflicts.test
+ */
+ thd->clear_error();
+ }
+ else
+ {
+ rgi->inc_event_relay_log_pos();
+ }
+
+ DBUG_RETURN(error);
+}
+
+#endif //defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+
+#ifndef MYSQL_CLIENT
+bool Rows_log_event::write_data_header(IO_CACHE *file)
+{
+ uchar buf[ROWS_HEADER_LEN_V2]; // No need to init the buffer
+ DBUG_ASSERT(m_table_id != ~0UL);
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ {
+ int4store(buf + 0, m_table_id);
+ int2store(buf + 4, m_flags);
+ return (wrapper_my_b_safe_write(file, buf, 6));
+ });
+ int6store(buf + RW_MAPID_OFFSET, (ulonglong)m_table_id);
+ int2store(buf + RW_FLAGS_OFFSET, m_flags);
+ return (wrapper_my_b_safe_write(file, buf, ROWS_HEADER_LEN));
+}
+
+bool Rows_log_event::write_data_body(IO_CACHE*file)
+{
+ /*
+ Note that this should be the number of *bits*, not the number of
+ bytes.
+ */
+ uchar sbuf[MAX_INT_WIDTH];
+ my_ptrdiff_t const data_size= m_rows_cur - m_rows_buf;
+ bool res= false;
+ uchar *const sbuf_end= net_store_length(sbuf, (size_t) m_width);
+ DBUG_ASSERT(static_cast<size_t>(sbuf_end - sbuf) <= sizeof(sbuf));
+
+ DBUG_DUMP("m_width", sbuf, (size_t) (sbuf_end - sbuf));
+ res= res || wrapper_my_b_safe_write(file, sbuf, (size_t) (sbuf_end - sbuf));
+
+ DBUG_DUMP("m_cols", (uchar*) m_cols.bitmap, no_bytes_in_map(&m_cols));
+ res= res || wrapper_my_b_safe_write(file, (uchar*) m_cols.bitmap,
+ no_bytes_in_map(&m_cols));
+ /*
+ TODO[refactor write]: Remove the "down cast" here (and elsewhere).
+ */
+ if (get_general_type_code() == UPDATE_ROWS_EVENT)
+ {
+ DBUG_DUMP("m_cols_ai", (uchar*) m_cols_ai.bitmap,
+ no_bytes_in_map(&m_cols_ai));
+ res= res || wrapper_my_b_safe_write(file, (uchar*) m_cols_ai.bitmap,
+ no_bytes_in_map(&m_cols_ai));
+ }
+ DBUG_DUMP("rows", m_rows_buf, data_size);
+ res= res || wrapper_my_b_safe_write(file, m_rows_buf, (size_t) data_size);
+
+ return res;
+
+}
+#endif
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Rows_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[256];
+ char const *const flagstr=
+ get_flags(STMT_END_F) ? " flags: STMT_END_F" : "";
+ size_t bytes= my_snprintf(buf, sizeof(buf),
+ "table_id: %lu%s", m_table_id, flagstr);
+ protocol->store(buf, bytes, &my_charset_bin);
+}
+#endif
+
+#ifdef MYSQL_CLIENT
+void Rows_log_event::print_helper(FILE *file,
+ PRINT_EVENT_INFO *print_event_info,
+ char const *const name)
+{
+ IO_CACHE *const head= &print_event_info->head_cache;
+ IO_CACHE *const body= &print_event_info->body_cache;
+ if (!print_event_info->short_form)
+ {
+ bool const last_stmt_event= get_flags(STMT_END_F);
+ print_header(head, print_event_info, !last_stmt_event);
+ my_b_printf(head, "\t%s: table id %lu%s\n",
+ name, m_table_id,
+ last_stmt_event ? " flags: STMT_END_F" : "");
+ print_base64(body, print_event_info, !last_stmt_event);
+ }
+
+ if (get_flags(STMT_END_F))
+ {
+ copy_event_cache_to_file_and_reinit(head, file);
+ copy_event_cache_to_file_and_reinit(body, file);
+ }
+}
+#endif
+
+/**************************************************************************
+ Annotate_rows_log_event member functions
+**************************************************************************/
+
+#ifndef MYSQL_CLIENT
+Annotate_rows_log_event::Annotate_rows_log_event(THD *thd,
+ bool using_trans,
+ bool direct)
+ : Log_event(thd, 0, using_trans),
+ m_save_thd_query_txt(0),
+ m_save_thd_query_len(0)
+{
+ m_query_txt= thd->query();
+ m_query_len= thd->query_length();
+ if (direct)
+ cache_type= Log_event::EVENT_NO_CACHE;
+}
+#endif
+
+Annotate_rows_log_event::Annotate_rows_log_event(const char *buf,
+ uint event_len,
+ const Format_description_log_event *desc)
+ : Log_event(buf, desc),
+ m_save_thd_query_txt(0),
+ m_save_thd_query_len(0)
+{
+ m_query_len= event_len - desc->common_header_len;
+ m_query_txt= (char*) buf + desc->common_header_len;
+}
+
+Annotate_rows_log_event::~Annotate_rows_log_event()
+{
+#ifndef MYSQL_CLIENT
+ if (m_save_thd_query_txt)
+ thd->set_query(m_save_thd_query_txt, m_save_thd_query_len);
+#endif
+}
+
+int Annotate_rows_log_event::get_data_size()
+{
+ return m_query_len;
+}
+
+Log_event_type Annotate_rows_log_event::get_type_code()
+{
+ return ANNOTATE_ROWS_EVENT;
+}
+
+bool Annotate_rows_log_event::is_valid() const
+{
+ return (m_query_txt != NULL && m_query_len != 0);
+}
+
+#ifndef MYSQL_CLIENT
+bool Annotate_rows_log_event::write_data_header(IO_CACHE *file)
+{
+ return 0;
+}
+#endif
+
+#ifndef MYSQL_CLIENT
+bool Annotate_rows_log_event::write_data_body(IO_CACHE *file)
+{
+ return wrapper_my_b_safe_write(file, (uchar*) m_query_txt, m_query_len);
+}
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+void Annotate_rows_log_event::pack_info(THD *thd, Protocol* protocol)
+{
+ if (m_query_txt && m_query_len)
+ protocol->store(m_query_txt, m_query_len, &my_charset_bin);
+}
+#endif
+
+#ifdef MYSQL_CLIENT
+void Annotate_rows_log_event::print(FILE *file, PRINT_EVENT_INFO *pinfo)
+{
+ if (pinfo->short_form)
+ return;
+
+ print_header(&pinfo->head_cache, pinfo, TRUE);
+ my_b_printf(&pinfo->head_cache, "\tAnnotate_rows:\n");
+
+ char *pbeg; // beginning of the next line
+ char *pend; // end of the next line
+ uint cnt= 0; // characters counter
+
+ for (pbeg= m_query_txt; ; pbeg= pend)
+ {
+ // skip all \r's and \n's at the beginning of the next line
+ for (;; pbeg++)
+ {
+ if (++cnt > m_query_len)
+ return;
+
+ if (*pbeg != '\r' && *pbeg != '\n')
+ break;
+ }
+
+ // find end of the next line
+ for (pend= pbeg + 1;
+ ++cnt <= m_query_len && *pend != '\r' && *pend != '\n';
+ pend++)
+ ;
+
+ // print next line
+ my_b_write(&pinfo->head_cache, (const uchar*) "#Q> ", 4);
+ my_b_write(&pinfo->head_cache, (const uchar*) pbeg, pend - pbeg);
+ my_b_write(&pinfo->head_cache, (const uchar*) "\n", 1);
+ }
+}
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int Annotate_rows_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ m_save_thd_query_txt= thd->query();
+ m_save_thd_query_len= thd->query_length();
+ thd->set_query(m_query_txt, m_query_len);
+ return 0;
+}
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int Annotate_rows_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ rgi->inc_event_relay_log_pos();
+ return 0;
+}
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+Log_event::enum_skip_reason
+Annotate_rows_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ return continue_group(rgi);
+}
+#endif
+
+/**************************************************************************
+ Table_map_log_event member functions and support functions
+**************************************************************************/
+
+/**
+ @page How replication of field metadata works.
+
+ When a table map is created, the master first calls
+ Table_map_log_event::save_field_metadata() which calculates how many
+ values will be in the field metadata. Only those fields that require the
+ extra data are added. The method also loops through all of the fields in
+ the table calling the method Field::save_field_metadata() which returns the
+ values for the field that will be saved in the metadata and replicated to
+ the slave. Once all fields have been processed, the table map is written to
+ the binlog adding the size of the field metadata and the field metadata to
+ the end of the body of the table map.
+
+ When a table map is read on the slave, the field metadata is read from the
+ table map and passed to the table_def class constructor which saves the
+ field metadata from the table map into an array based on the type of the
+ field. Field metadata values not present (those fields that do not use extra
+ data) in the table map are initialized as zero (0). The array size is the
+ same as the columns for the table on the slave.
+
+ Additionally, values saved for field metadata on the master are saved as a
+ string of bytes (uchar) in the binlog. A field may require 1 or more bytes
+ to store the information. In cases where values require multiple bytes
+ (e.g. values > 255), the endian-safe methods are used to properly encode
+ the values on the master and decode them on the slave. When the field
+ metadata values are captured on the slave, they are stored in an array of
+ type uint16. This allows the least number of casts to prevent casting bugs
+ when the field metadata is used in comparisons of field attributes. When
+ the field metadata is used for calculating addresses in pointer math, the
+ type used is uint32.
+*/
+
+#if !defined(MYSQL_CLIENT)
+/**
+ Save the field metadata based on the real_type of the field.
+ The metadata saved depends on the type of the field. Some fields
+ store a single byte for pack_length() while others store two bytes
+ for field_length (max length).
+
+ @retval 0 Ok.
+
+ @todo
+ We may want to consider changing the encoding of the information.
+ Currently, the code attempts to minimize the number of bytes written to
+ the tablemap. There are at least two other alternatives; 1) using
+ net_store_length() to store the data allowing it to choose the number of
+ bytes that are appropriate thereby making the code much easier to
+ maintain (only 1 place to change the encoding), or 2) use a fixed number
+ of bytes for each field. The problem with option 1 is that net_store_length()
+ will use one byte if the value < 251, but 3 bytes if it is > 250. Thus,
+ for fields like CHAR which can be no larger than 255 characters, the method
+ will use 3 bytes when the value is > 250. Further, every value that is
+ encoded using 2 parts (e.g., pack_length, field_length) will be numerically
+ > 250 therefore will use 3 bytes for eah value. The problem with option 2
+ is less wasteful for space but does waste 1 byte for every field that does
+ not encode 2 parts.
+*/
+int Table_map_log_event::save_field_metadata()
+{
+ DBUG_ENTER("Table_map_log_event::save_field_metadata");
+ int index= 0;
+ for (unsigned int i= 0 ; i < m_table->s->fields ; i++)
+ {
+ DBUG_PRINT("debug", ("field_type: %d", m_coltype[i]));
+ index+= m_table->s->field[i]->save_field_metadata(&m_field_metadata[index]);
+ }
+ DBUG_RETURN(index);
+}
+#endif /* !defined(MYSQL_CLIENT) */
+
+/*
+ Constructor used to build an event for writing to the binary log.
+ Mats says tbl->s lives longer than this event so it's ok to copy pointers
+ (tbl->s->db etc) and not pointer content.
+ */
+#if !defined(MYSQL_CLIENT)
+Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid,
+ bool is_transactional)
+ : Log_event(thd, 0, is_transactional),
+ m_table(tbl),
+ m_dbnam(tbl->s->db.str),
+ m_dblen(m_dbnam ? tbl->s->db.length : 0),
+ m_tblnam(tbl->s->table_name.str),
+ m_tbllen(tbl->s->table_name.length),
+ m_colcnt(tbl->s->fields),
+ m_memory(NULL),
+ m_table_id(tid),
+ m_flags(TM_BIT_LEN_EXACT_F),
+ m_data_size(0),
+ m_field_metadata(0),
+ m_field_metadata_size(0),
+ m_null_bits(0),
+ m_meta_memory(NULL)
+{
+ uchar cbuf[MAX_INT_WIDTH];
+ uchar *cbuf_end;
+ DBUG_ENTER("Table_map_log_event::Table_map_log_event(TABLE)");
+ DBUG_ASSERT(m_table_id != ~0UL);
+ /*
+ In TABLE_SHARE, "db" and "table_name" are 0-terminated (see this comment in
+ table.cc / alloc_table_share():
+ Use the fact the key is db/0/table_name/0
+ As we rely on this let's assert it.
+ */
+ DBUG_ASSERT((tbl->s->db.str == 0) ||
+ (tbl->s->db.str[tbl->s->db.length] == 0));
+ DBUG_ASSERT(tbl->s->table_name.str[tbl->s->table_name.length] == 0);
+
+
+ m_data_size= TABLE_MAP_HEADER_LEN;
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master", m_data_size= 6;);
+ m_data_size+= m_dblen + 2; // Include length and terminating \0
+ m_data_size+= m_tbllen + 2; // Include length and terminating \0
+ cbuf_end= net_store_length(cbuf, (size_t) m_colcnt);
+ DBUG_ASSERT(static_cast<size_t>(cbuf_end - cbuf) <= sizeof(cbuf));
+ m_data_size+= (cbuf_end - cbuf) + m_colcnt; // COLCNT and column types
+
+#ifdef RBR_TRIGGERS
+ if (tbl->triggers)
+ m_flags|= TM_BIT_HAS_TRIGGERS_F;
+#endif //RBR_TRIGGERS
+
+ /* If malloc fails, caught in is_valid() */
+ if ((m_memory= (uchar*) my_malloc(m_colcnt, MYF(MY_WME))))
+ {
+ m_coltype= reinterpret_cast<uchar*>(m_memory);
+ for (unsigned int i= 0 ; i < m_table->s->fields ; ++i)
+ m_coltype[i]= m_table->field[i]->binlog_type();
+ }
+
+ /*
+ Calculate a bitmap for the results of maybe_null() for all columns.
+ The bitmap is used to determine when there is a column from the master
+ that is not on the slave and is null and thus not in the row data during
+ replication.
+ */
+ uint num_null_bytes= (m_table->s->fields + 7) / 8;
+ m_data_size+= num_null_bytes;
+ m_meta_memory= (uchar *)my_multi_malloc(MYF(MY_WME),
+ &m_null_bits, num_null_bytes,
+ &m_field_metadata, (m_colcnt * 2),
+ NULL);
+
+ bzero(m_field_metadata, (m_colcnt * 2));
+
+ /*
+ Create an array for the field metadata and store it.
+ */
+ m_field_metadata_size= save_field_metadata();
+ DBUG_ASSERT(m_field_metadata_size <= (m_colcnt * 2));
+
+ /*
+ Now set the size of the data to the size of the field metadata array
+ plus one or three bytes (see pack.c:net_store_length) for number of
+ elements in the field metadata array.
+ */
+ if (m_field_metadata_size < 251)
+ m_data_size+= m_field_metadata_size + 1;
+ else
+ m_data_size+= m_field_metadata_size + 3;
+
+ bzero(m_null_bits, num_null_bytes);
+ for (unsigned int i= 0 ; i < m_table->s->fields ; ++i)
+ if (m_table->field[i]->maybe_null())
+ m_null_bits[(i / 8)]+= 1 << (i % 8);
+
+ DBUG_VOID_RETURN;
+}
+#endif /* !defined(MYSQL_CLIENT) */
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#if defined(HAVE_REPLICATION)
+Table_map_log_event::Table_map_log_event(const char *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+
+ : Log_event(buf, description_event),
+#ifndef MYSQL_CLIENT
+ m_table(NULL),
+#endif
+ m_dbnam(NULL), m_dblen(0), m_tblnam(NULL), m_tbllen(0),
+ m_colcnt(0), m_coltype(0),
+ m_memory(NULL), m_table_id(ULONG_MAX), m_flags(0),
+ m_data_size(0), m_field_metadata(0), m_field_metadata_size(0),
+ m_null_bits(0), m_meta_memory(NULL)
+{
+ unsigned int bytes_read= 0;
+ DBUG_ENTER("Table_map_log_event::Table_map_log_event(const char*,uint,...)");
+
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 post_header_len= description_event->post_header_len[TABLE_MAP_EVENT-1];
+ DBUG_PRINT("info",("event_len: %u common_header_len: %d post_header_len: %d",
+ event_len, common_header_len, post_header_len));
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("event buffer", (uchar*) buf, event_len);
+#endif
+
+ /* Read the post-header */
+ const char *post_start= buf + common_header_len;
+
+ post_start+= TM_MAPID_OFFSET;
+ if (post_header_len == 6)
+ {
+ /* Master is of an intermediate source tree before 5.1.4. Id is 4 bytes */
+ m_table_id= uint4korr(post_start);
+ post_start+= 4;
+ }
+ else
+ {
+ DBUG_ASSERT(post_header_len == TABLE_MAP_HEADER_LEN);
+ m_table_id= (ulong) uint6korr(post_start);
+ post_start+= TM_FLAGS_OFFSET;
+ }
+
+ DBUG_ASSERT(m_table_id != ~0UL);
+
+ m_flags= uint2korr(post_start);
+
+ /* Read the variable part of the event */
+ const char *const vpart= buf + common_header_len + post_header_len;
+
+ /* Extract the length of the various parts from the buffer */
+ uchar const *const ptr_dblen= (uchar const*)vpart + 0;
+ m_dblen= *(uchar*) ptr_dblen;
+
+ /* Length of database name + counter + terminating null */
+ uchar const *const ptr_tbllen= ptr_dblen + m_dblen + 2;
+ m_tbllen= *(uchar*) ptr_tbllen;
+
+ /* Length of table name + counter + terminating null */
+ uchar const *const ptr_colcnt= ptr_tbllen + m_tbllen + 2;
+ uchar *ptr_after_colcnt= (uchar*) ptr_colcnt;
+ m_colcnt= net_field_length(&ptr_after_colcnt);
+
+ DBUG_PRINT("info",("m_dblen: %lu off: %ld m_tbllen: %lu off: %ld m_colcnt: %lu off: %ld",
+ (ulong) m_dblen, (long) (ptr_dblen-(const uchar*)vpart),
+ (ulong) m_tbllen, (long) (ptr_tbllen-(const uchar*)vpart),
+ m_colcnt, (long) (ptr_colcnt-(const uchar*)vpart)));
+
+ /* Allocate mem for all fields in one go. If fails, caught in is_valid() */
+ m_memory= (uchar*) my_multi_malloc(MYF(MY_WME),
+ &m_dbnam, (uint) m_dblen + 1,
+ &m_tblnam, (uint) m_tbllen + 1,
+ &m_coltype, (uint) m_colcnt,
+ NullS);
+
+ if (m_memory)
+ {
+ /* Copy the different parts into their memory */
+ strncpy(const_cast<char*>(m_dbnam), (const char*)ptr_dblen + 1, m_dblen + 1);
+ strncpy(const_cast<char*>(m_tblnam), (const char*)ptr_tbllen + 1, m_tbllen + 1);
+ memcpy(m_coltype, ptr_after_colcnt, m_colcnt);
+
+ ptr_after_colcnt= ptr_after_colcnt + m_colcnt;
+ bytes_read= (uint) (ptr_after_colcnt - (uchar *)buf);
+ DBUG_PRINT("info", ("Bytes read: %d.\n", bytes_read));
+ if (bytes_read < event_len)
+ {
+ m_field_metadata_size= net_field_length(&ptr_after_colcnt);
+ DBUG_ASSERT(m_field_metadata_size <= (m_colcnt * 2));
+ uint num_null_bytes= (m_colcnt + 7) / 8;
+ m_meta_memory= (uchar *)my_multi_malloc(MYF(MY_WME),
+ &m_null_bits, num_null_bytes,
+ &m_field_metadata, m_field_metadata_size,
+ NULL);
+ memcpy(m_field_metadata, ptr_after_colcnt, m_field_metadata_size);
+ ptr_after_colcnt= (uchar*)ptr_after_colcnt + m_field_metadata_size;
+ memcpy(m_null_bits, ptr_after_colcnt, num_null_bytes);
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+#endif
+
+Table_map_log_event::~Table_map_log_event()
+{
+ my_free(m_meta_memory);
+ my_free(m_memory);
+}
+
+
+#ifdef MYSQL_CLIENT
+
+/*
+ Rewrite database name for the event to name specified by new_db
+ SYNOPSIS
+ new_db Database name to change to
+ new_len Length
+ desc Event describing binlog that we're writing to.
+
+ DESCRIPTION
+ Reset db name. This function assumes that temp_buf member contains event
+ representation taken from a binary log. It resets m_dbnam and m_dblen and
+ rewrites temp_buf with new db name.
+
+ RETURN
+ 0 - Success
+ other - Error
+*/
+
+int Table_map_log_event::rewrite_db(const char* new_db, size_t new_len,
+ const Format_description_log_event* desc)
+{
+ DBUG_ENTER("Table_map_log_event::rewrite_db");
+ DBUG_ASSERT(temp_buf);
+
+ uint header_len= MY_MIN(desc->common_header_len,
+ LOG_EVENT_MINIMAL_HEADER_LEN) + TABLE_MAP_HEADER_LEN;
+ int len_diff;
+
+ if (!(len_diff= new_len - m_dblen))
+ {
+ memcpy((void*) (temp_buf + header_len + 1), new_db, m_dblen + 1);
+ memcpy((void*) m_dbnam, new_db, m_dblen + 1);
+ DBUG_RETURN(0);
+ }
+
+ // Create new temp_buf
+ ulong event_cur_len= uint4korr(temp_buf + EVENT_LEN_OFFSET);
+ ulong event_new_len= event_cur_len + len_diff;
+ char* new_temp_buf= (char*) my_malloc(event_new_len, MYF(MY_WME));
+
+ if (!new_temp_buf)
+ {
+ sql_print_error("Table_map_log_event::rewrite_db: "
+ "failed to allocate new temp_buf (%d bytes required)",
+ event_new_len);
+ DBUG_RETURN(-1);
+ }
+
+ // Rewrite temp_buf
+ char* ptr= new_temp_buf;
+ ulong cnt= 0;
+
+ // Copy header and change event length
+ memcpy(ptr, temp_buf, header_len);
+ int4store(ptr + EVENT_LEN_OFFSET, event_new_len);
+ ptr += header_len;
+ cnt += header_len;
+
+ // Write new db name length and new name
+ *ptr++ = new_len;
+ memcpy(ptr, new_db, new_len + 1);
+ ptr += new_len + 1;
+ cnt += m_dblen + 2;
+
+ // Copy rest part
+ memcpy(ptr, temp_buf + cnt, event_cur_len - cnt);
+
+ // Reregister temp buf
+ free_temp_buf();
+ register_temp_buf(new_temp_buf, TRUE);
+
+ // Reset m_dbnam and m_dblen members
+ m_dblen= new_len;
+
+ // m_dbnam resides in m_memory together with m_tblnam and m_coltype
+ uchar* memory= m_memory;
+ char const* tblnam= m_tblnam;
+ uchar* coltype= m_coltype;
+
+ m_memory= (uchar*) my_multi_malloc(MYF(MY_WME),
+ &m_dbnam, (uint) m_dblen + 1,
+ &m_tblnam, (uint) m_tbllen + 1,
+ &m_coltype, (uint) m_colcnt,
+ NullS);
+
+ if (!m_memory)
+ {
+ sql_print_error("Table_map_log_event::rewrite_db: "
+ "failed to allocate new m_memory (%d + %d + %d bytes required)",
+ m_dblen + 1, m_tbllen + 1, m_colcnt);
+ DBUG_RETURN(-1);
+ }
+
+ memcpy((void*)m_dbnam, new_db, m_dblen + 1);
+ memcpy((void*)m_tblnam, tblnam, m_tbllen + 1);
+ memcpy(m_coltype, coltype, m_colcnt);
+
+ my_free(memory);
+ DBUG_RETURN(0);
+}
+#endif /* MYSQL_CLIENT */
+
+
+/*
+ Return value is an error code, one of:
+
+ -1 Failure to open table [from open_tables()]
+ 0 Success
+ 1 No room for more tables [from set_table()]
+ 2 Out of memory [from set_table()]
+ 3 Wrong table definition
+ 4 Daisy-chaining RBR with SBR not possible
+ */
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+enum enum_tbl_map_status
+{
+ /* no duplicate identifier found */
+ OK_TO_PROCESS= 0,
+
+ /* this table map must be filtered out */
+ FILTERED_OUT= 1,
+
+ /* identifier mapping table with different properties */
+ SAME_ID_MAPPING_DIFFERENT_TABLE= 2,
+
+ /* a duplicate identifier was found mapping the same table */
+ SAME_ID_MAPPING_SAME_TABLE= 3
+};
+
+/*
+ Checks if this table map event should be processed or not. First
+ it checks the filtering rules, and then looks for duplicate identifiers
+ in the existing list of rli->tables_to_lock.
+
+ It checks that there hasn't been any corruption by verifying that there
+ are no duplicate entries with different properties.
+
+ In some cases, some binary logs could get corrupted, showing several
+ tables mapped to the same table_id, 0 (see: BUG#56226). Thus we do this
+ early sanity check for such cases and avoid that the server crashes
+ later.
+
+ In some corner cases, the master logs duplicate table map events, i.e.,
+ same id, same database name, same table name (see: BUG#37137). This is
+ different from the above as it's the same table that is mapped again
+ to the same identifier. Thus we cannot just check for same ids and
+ assume that the event is corrupted we need to check every property.
+
+ NOTE: in the event that BUG#37137 ever gets fixed, this extra check
+ will still be valid because we would need to support old binary
+ logs anyway.
+
+ @param rli The relay log info reference.
+ @param table_list A list element containing the table to check against.
+ @return OK_TO_PROCESS
+ if there was no identifier already in rli->tables_to_lock
+
+ FILTERED_OUT
+ if the event is filtered according to the filtering rules
+
+ SAME_ID_MAPPING_DIFFERENT_TABLE
+ if the same identifier already maps a different table in
+ rli->tables_to_lock
+
+ SAME_ID_MAPPING_SAME_TABLE
+ if the same identifier already maps the same table in
+ rli->tables_to_lock.
+*/
+static enum_tbl_map_status
+check_table_map(rpl_group_info *rgi, RPL_TABLE_LIST *table_list)
+{
+ DBUG_ENTER("check_table_map");
+ enum_tbl_map_status res= OK_TO_PROCESS;
+ Relay_log_info *rli= rgi->rli;
+
+ if (rgi->thd->slave_thread /* filtering is for slave only */ &&
+ (!rli->mi->rpl_filter->db_ok(table_list->db) ||
+ (rli->mi->rpl_filter->is_on() && !rli->mi->rpl_filter->tables_ok("", table_list))))
+ res= FILTERED_OUT;
+ else
+ {
+ RPL_TABLE_LIST *ptr= static_cast<RPL_TABLE_LIST*>(rgi->tables_to_lock);
+ for(uint i=0 ; ptr && (i< rgi->tables_to_lock_count);
+ ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_local), i++)
+ {
+ if (ptr->table_id == table_list->table_id)
+ {
+
+ if (strcmp(ptr->db, table_list->db) ||
+ strcmp(ptr->alias, table_list->table_name) ||
+ ptr->lock_type != TL_WRITE) // the ::do_apply_event always sets TL_WRITE
+ res= SAME_ID_MAPPING_DIFFERENT_TABLE;
+ else
+ res= SAME_ID_MAPPING_SAME_TABLE;
+
+ break;
+ }
+ }
+ }
+
+ DBUG_PRINT("debug", ("check of table map ended up with: %u", res));
+
+ DBUG_RETURN(res);
+}
+
+int Table_map_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ RPL_TABLE_LIST *table_list;
+ char *db_mem, *tname_mem;
+ size_t dummy_len;
+ void *memory;
+ Rpl_filter *filter;
+ Relay_log_info const *rli= rgi->rli;
+ DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)");
+
+ /* Step the query id to mark what columns that are actually used. */
+ thd->set_query_id(next_query_id());
+
+ if (!(memory= my_multi_malloc(MYF(MY_WME),
+ &table_list, (uint) sizeof(RPL_TABLE_LIST),
+ &db_mem, (uint) NAME_LEN + 1,
+ &tname_mem, (uint) NAME_LEN + 1,
+ NullS)))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+ /* call from mysql_client_binlog_statement() will not set rli->mi */
+ filter= rgi->thd->slave_thread ? rli->mi->rpl_filter : global_rpl_filter;
+ strmov(db_mem, filter->get_rewrite_db(m_dbnam, &dummy_len));
+ strmov(tname_mem, m_tblnam);
+
+ table_list->init_one_table(db_mem, strlen(db_mem),
+ tname_mem, strlen(tname_mem),
+ tname_mem, TL_WRITE);
+
+ table_list->table_id= DBUG_EVALUATE_IF("inject_tblmap_same_id_maps_diff_table", 0, m_table_id);
+ table_list->updating= 1;
+ table_list->required_type= FRMTYPE_TABLE;
+
+ DBUG_PRINT("debug", ("table: %s is mapped to %u", table_list->table_name,
+ table_list->table_id));
+#ifdef RBR_TRIGGERS
+ table_list->master_had_triggers= ((m_flags & TM_BIT_HAS_TRIGGERS_F) ? 1 : 0);
+ DBUG_PRINT("debug", ("table->master_had_triggers=%d",
+ (int)table_list->master_had_triggers));
+#endif //RBR_TRIGGERS
+
+ enum_tbl_map_status tblmap_status= check_table_map(rgi, table_list);
+ if (tblmap_status == OK_TO_PROCESS)
+ {
+ DBUG_ASSERT(thd->lex->query_tables != table_list);
+
+ /*
+ Use placement new to construct the table_def instance in the
+ memory allocated for it inside table_list.
+
+ The memory allocated by the table_def structure (i.e., not the
+ memory allocated *for* the table_def structure) is released
+ inside Relay_log_info::clear_tables_to_lock() by calling the
+ table_def destructor explicitly.
+ */
+ new (&table_list->m_tabledef)
+ table_def(m_coltype, m_colcnt,
+ m_field_metadata, m_field_metadata_size,
+ m_null_bits, m_flags);
+ table_list->m_tabledef_valid= TRUE;
+ table_list->m_conv_table= NULL;
+ table_list->open_type= OT_BASE_ONLY;
+
+ /*
+ We record in the slave's information that the table should be
+ locked by linking the table into the list of tables to lock.
+ */
+ table_list->next_global= table_list->next_local= rgi->tables_to_lock;
+ rgi->tables_to_lock= table_list;
+ rgi->tables_to_lock_count++;
+ /* 'memory' is freed in clear_tables_to_lock */
+ }
+ else // FILTERED_OUT, SAME_ID_MAPPING_*
+ {
+ /*
+ If mapped already but with different properties, we raise an
+ error.
+ If mapped already but with same properties we skip the event.
+ If filtered out we skip the event.
+
+ In all three cases, we need to free the memory previously
+ allocated.
+ */
+ if (tblmap_status == SAME_ID_MAPPING_DIFFERENT_TABLE)
+ {
+ /*
+ Something bad has happened. We need to stop the slave as strange things
+ could happen if we proceed: slave crash, wrong table being updated, ...
+ As a consequence we push an error in this case.
+ */
+
+ char buf[256];
+
+ my_snprintf(buf, sizeof(buf),
+ "Found table map event mapping table id %u which "
+ "was already mapped but with different settings.",
+ table_list->table_id);
+
+ if (thd->slave_thread)
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(),
+ ER(ER_SLAVE_FATAL_ERROR), buf);
+ else
+ /*
+ For the cases in which a 'BINLOG' statement is set to
+ execute in a user session
+ */
+ my_printf_error(ER_SLAVE_FATAL_ERROR, ER(ER_SLAVE_FATAL_ERROR),
+ MYF(0), buf);
+ }
+
+ my_free(memory);
+ }
+
+ DBUG_RETURN(tblmap_status == SAME_ID_MAPPING_DIFFERENT_TABLE);
+}
+
+Log_event::enum_skip_reason
+Table_map_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ /*
+ If the slave skip counter is 1, then we should not start executing
+ on the next event.
+ */
+ return continue_group(rgi);
+}
+
+int Table_map_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ rgi->inc_event_relay_log_pos();
+ return 0;
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+#ifndef MYSQL_CLIENT
+bool Table_map_log_event::write_data_header(IO_CACHE *file)
+{
+ DBUG_ASSERT(m_table_id != ~0UL);
+ uchar buf[TABLE_MAP_HEADER_LEN];
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ {
+ int4store(buf + 0, m_table_id);
+ int2store(buf + 4, m_flags);
+ return (wrapper_my_b_safe_write(file, buf, 6));
+ });
+ int6store(buf + TM_MAPID_OFFSET, (ulonglong)m_table_id);
+ int2store(buf + TM_FLAGS_OFFSET, m_flags);
+ return (wrapper_my_b_safe_write(file, buf, TABLE_MAP_HEADER_LEN));
+}
+
+bool Table_map_log_event::write_data_body(IO_CACHE *file)
+{
+ DBUG_ASSERT(m_dbnam != NULL);
+ DBUG_ASSERT(m_tblnam != NULL);
+ /* We use only one byte per length for storage in event: */
+ DBUG_ASSERT(m_dblen < 128);
+ DBUG_ASSERT(m_tbllen < 128);
+
+ uchar const dbuf[]= { (uchar) m_dblen };
+ uchar const tbuf[]= { (uchar) m_tbllen };
+
+ uchar cbuf[MAX_INT_WIDTH];
+ uchar *const cbuf_end= net_store_length(cbuf, (size_t) m_colcnt);
+ DBUG_ASSERT(static_cast<size_t>(cbuf_end - cbuf) <= sizeof(cbuf));
+
+ /*
+ Store the size of the field metadata.
+ */
+ uchar mbuf[MAX_INT_WIDTH];
+ uchar *const mbuf_end= net_store_length(mbuf, m_field_metadata_size);
+
+ return (wrapper_my_b_safe_write(file, dbuf, sizeof(dbuf)) ||
+ wrapper_my_b_safe_write(file, (const uchar*)m_dbnam, m_dblen+1) ||
+ wrapper_my_b_safe_write(file, tbuf, sizeof(tbuf)) ||
+ wrapper_my_b_safe_write(file, (const uchar*)m_tblnam, m_tbllen+1) ||
+ wrapper_my_b_safe_write(file, cbuf, (size_t) (cbuf_end - cbuf)) ||
+ wrapper_my_b_safe_write(file, m_coltype, m_colcnt) ||
+ wrapper_my_b_safe_write(file, mbuf, (size_t) (mbuf_end - mbuf)) ||
+ wrapper_my_b_safe_write(file, m_field_metadata, m_field_metadata_size),
+ wrapper_my_b_safe_write(file, m_null_bits, (m_colcnt + 7) / 8));
+ }
+#endif
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+/*
+ Print some useful information for the SHOW BINARY LOG information
+ field.
+ */
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Table_map_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[256];
+ size_t bytes= my_snprintf(buf, sizeof(buf),
+ "table_id: %lu (%s.%s)",
+ m_table_id, m_dbnam, m_tblnam);
+ protocol->store(buf, bytes, &my_charset_bin);
+}
+#endif
+
+
+#endif
+
+
+#ifdef MYSQL_CLIENT
+void Table_map_log_event::print(FILE *, PRINT_EVENT_INFO *print_event_info)
+{
+ if (!print_event_info->short_form)
+ {
+ print_header(&print_event_info->head_cache, print_event_info, TRUE);
+ my_b_printf(&print_event_info->head_cache,
+ "\tTable_map: %`s.%`s mapped to number %lu%s\n",
+ m_dbnam, m_tblnam, m_table_id,
+ ((m_flags & TM_BIT_HAS_TRIGGERS_F) ?
+ " (has triggers)" : ""));
+ print_base64(&print_event_info->body_cache, print_event_info, TRUE);
+ }
+}
+#endif
+
+/**************************************************************************
+ Write_rows_log_event member functions
+**************************************************************************/
+
+/*
+ Constructor used to build an event for writing to the binary log.
+ */
+#if !defined(MYSQL_CLIENT)
+Write_rows_log_event::Write_rows_log_event(THD *thd_arg, TABLE *tbl_arg,
+ ulong tid_arg,
+ MY_BITMAP const *cols,
+ bool is_transactional)
+ : Rows_log_event(thd_arg, tbl_arg, tid_arg, cols, is_transactional, WRITE_ROWS_EVENT_V1)
+{
+}
+#endif
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Write_rows_log_event::Write_rows_log_event(const char *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+: Rows_log_event(buf, event_len, description_event)
+{
+}
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int
+Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability *const)
+{
+ int error= 0;
+
+ /*
+ Increment the global status insert count variable
+ */
+ if (get_flags(STMT_END_F))
+ status_var_increment(thd->status_var.com_stat[SQLCOM_INSERT]);
+
+ /**
+ todo: to introduce a property for the event (handler?) which forces
+ applying the event in the replace (idempotent) fashion.
+ */
+ if ((slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT) ||
+ (m_table->s->db_type()->db_type == DB_TYPE_NDBCLUSTER))
+ {
+ /*
+ We are using REPLACE semantics and not INSERT IGNORE semantics
+ when writing rows, that is: new rows replace old rows. We need to
+ inform the storage engine that it should use this behaviour.
+ */
+
+ /* Tell the storage engine that we are using REPLACE semantics. */
+ thd->lex->duplicates= DUP_REPLACE;
+
+ /*
+ Pretend we're executing a REPLACE command: this is needed for
+ InnoDB and NDB Cluster since they are not (properly) checking the
+ lex->duplicates flag.
+ */
+ thd->lex->sql_command= SQLCOM_REPLACE;
+ /*
+ Do not raise the error flag in case of hitting to an unique attribute
+ */
+ m_table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ /*
+ NDB specific: update from ndb master wrapped as Write_rows
+ so that the event should be applied to replace slave's row
+
+ Also following is needed in case if we have AFTER DELETE triggers.
+ */
+ m_table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ /*
+ NDB specific: if update from ndb master wrapped as Write_rows
+ does not find the row it's assumed idempotent binlog applying
+ is taking place; don't raise the error.
+ */
+ m_table->file->extra(HA_EXTRA_IGNORE_NO_KEY);
+ /*
+ TODO: the cluster team (Tomas?) says that it's better if the engine knows
+ how many rows are going to be inserted, then it can allocate needed memory
+ from the start.
+ */
+ }
+ if (slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers )
+ m_table->prepare_triggers_for_insert_stmt_or_event();
+
+ /* Honor next number column if present */
+ m_table->next_number_field= m_table->found_next_number_field;
+ /*
+ * Fixed Bug#45999, In RBR, Store engine of Slave auto-generates new
+ * sequence numbers for auto_increment fields if the values of them are 0.
+ * If generateing a sequence number is decided by the values of
+ * table->auto_increment_field_not_null and SQL_MODE(if includes
+ * MODE_NO_AUTO_VALUE_ON_ZERO) in update_auto_increment function.
+ * SQL_MODE of slave sql thread is always consistency with master's.
+ * In RBR, auto_increment fields never are NULL, except if the auto_inc
+ * column exists only on the slave side (i.e., in an extra column
+ * on the slave's table).
+ */
+ if (!is_auto_inc_in_extra_columns())
+ m_table->auto_increment_field_not_null= TRUE;
+ else
+ {
+ /*
+ Here we have checked that there is an extra field
+ on this server's table that has an auto_inc column.
+
+ Mark that the auto_increment field is null and mark
+ the read and write set bits.
+
+ (There can only be one AUTO_INC column, it is always
+ indexed and it cannot have a DEFAULT value).
+ */
+ m_table->auto_increment_field_not_null= FALSE;
+ m_table->mark_auto_increment_column();
+ }
+
+ return error;
+}
+
+int
+Write_rows_log_event::do_after_row_operations(const Slave_reporting_capability *const,
+ int error)
+{
+ int local_error= 0;
+
+ /**
+ Clear the write_set bit for auto_inc field that only
+ existed on the destination table as an extra column.
+ */
+ if (is_auto_inc_in_extra_columns())
+ {
+ bitmap_clear_bit(m_table->write_set, m_table->next_number_field->field_index);
+ bitmap_clear_bit( m_table->read_set, m_table->next_number_field->field_index);
+
+ if (get_flags(STMT_END_F))
+ m_table->file->ha_release_auto_increment();
+ }
+ m_table->next_number_field=0;
+ m_table->auto_increment_field_not_null= FALSE;
+ if ((slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT) ||
+ m_table->s->db_type()->db_type == DB_TYPE_NDBCLUSTER)
+ {
+ m_table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ m_table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+ /*
+ resetting the extra with
+ table->file->extra(HA_EXTRA_NO_IGNORE_NO_KEY);
+ fires bug#27077
+ explanation: file->reset() performs this duty
+ ultimately. Still todo: fix
+ */
+ }
+ if ((local_error= m_table->file->ha_end_bulk_insert()))
+ {
+ m_table->file->print_error(local_error, MYF(0));
+ }
+ return error? error : local_error;
+}
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+bool Rows_log_event::process_triggers(trg_event_type event,
+ trg_action_time_type time_type,
+ bool old_row_is_record1)
+{
+#ifdef RBR_TRIGGERS
+ bool result;
+ DBUG_ENTER("Rows_log_event::process_triggers");
+ if (slave_run_triggers_for_rbr == SLAVE_RUN_TRIGGERS_FOR_RBR_YES)
+ {
+ tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */
+ result= m_table->triggers->process_triggers(thd, event,
+ time_type, old_row_is_record1);
+ reenable_binlog(thd);
+ }
+ else
+ result= m_table->triggers->process_triggers(thd, event,
+ time_type, old_row_is_record1);
+
+ DBUG_RETURN(result);
+#else
+ return TRUE;
+#endif //RBR_TRIGGERS
+}
+/*
+ Check if there are more UNIQUE keys after the given key.
+*/
+static int
+last_uniq_key(TABLE *table, uint keyno)
+{
+ while (++keyno < table->s->keys)
+ if (table->key_info[keyno].flags & HA_NOSAME)
+ return 0;
+ return 1;
+}
+
+/**
+ Check if an error is a duplicate key error.
+
+ This function is used to check if an error code is one of the
+ duplicate key error, i.e., and error code for which it is sensible
+ to do a <code>get_dup_key()</code> to retrieve the duplicate key.
+
+ @param errcode The error code to check.
+
+ @return <code>true</code> if the error code is such that
+ <code>get_dup_key()</code> will return true, <code>false</code>
+ otherwise.
+ */
+bool
+is_duplicate_key_error(int errcode)
+{
+ switch (errcode)
+ {
+ case HA_ERR_FOUND_DUPP_KEY:
+ case HA_ERR_FOUND_DUPP_UNIQUE:
+ return true;
+ }
+ return false;
+}
+
+/**
+ Write the current row into event's table.
+
+ The row is located in the row buffer, pointed by @c m_curr_row member.
+ Number of columns of the row is stored in @c m_width member (it can be
+ different from the number of columns in the table to which we insert).
+ Bitmap @c m_cols indicates which columns are present in the row. It is assumed
+ that event's table is already open and pointed by @c m_table.
+
+ If the same record already exists in the table it can be either overwritten
+ or an error is reported depending on the value of @c overwrite flag
+ (error reporting not yet implemented). Note that the matching record can be
+ different from the row we insert if we use primary keys to identify records in
+ the table.
+
+ The row to be inserted can contain values only for selected columns. The
+ missing columns are filled with default values using @c prepare_record()
+ function. If a matching record is found in the table and @c overwritte is
+ true, the missing columns are taken from it.
+
+ @param rli Relay log info (needed for row unpacking).
+ @param overwrite
+ Shall we overwrite if the row already exists or signal
+ error (currently ignored).
+
+ @returns Error code on failure, 0 on success.
+
+ This method, if successful, sets @c m_curr_row_end pointer to point at the
+ next row in the rows buffer. This is done when unpacking the row to be
+ inserted.
+
+ @note If a matching record is found, it is either updated using
+ @c ha_update_row() or first deleted and then new record written.
+*/
+
+int
+Rows_log_event::write_row(rpl_group_info *rgi,
+ const bool overwrite)
+{
+ DBUG_ENTER("write_row");
+ DBUG_ASSERT(m_table != NULL && thd != NULL);
+
+ TABLE *table= m_table; // pointer to event's table
+ int error;
+ int UNINIT_VAR(keynum);
+ const bool invoke_triggers=
+ slave_run_triggers_for_rbr && !master_had_triggers && table->triggers;
+ auto_afree_ptr<char> key(NULL);
+
+ prepare_record(table, m_width,
+ table->file->ht->db_type != DB_TYPE_NDBCLUSTER);
+
+ /* unpack row into table->record[0] */
+ if ((error= unpack_current_row(rgi)))
+ DBUG_RETURN(error);
+
+ if (m_curr_row == m_rows_buf && !invoke_triggers)
+ {
+ /*
+ This table has no triggers so we can do bulk insert.
+
+ This is the first row to be inserted, we estimate the rows with
+ the size of the first row and use that value to initialize
+ storage engine for bulk insertion.
+ */
+ ulong estimated_rows= (m_rows_end - m_curr_row) / (m_curr_row_end - m_curr_row);
+ table->file->ha_start_bulk_insert(estimated_rows);
+ }
+
+ /*
+ Explicitly set the auto_inc to null to make sure that
+ it gets an auto_generated value.
+ */
+ if (is_auto_inc_in_extra_columns())
+ m_table->next_number_field->set_null();
+
+#ifndef DBUG_OFF
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+ DBUG_PRINT_BITSET("debug", "write_set = %s", table->write_set);
+ DBUG_PRINT_BITSET("debug", "read_set = %s", table->read_set);
+#endif
+
+ if (invoke_triggers &&
+ process_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE, TRUE))
+ {
+ DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet
+ }
+
+ /*
+ Try to write record. If a corresponding record already exists in the table,
+ we try to change it using ha_update_row() if possible. Otherwise we delete
+ it and repeat the whole process again.
+
+ TODO: Add safety measures against infinite looping.
+ */
+
+ while ((error= table->file->ha_write_row(table->record[0])))
+ {
+ if (error == HA_ERR_LOCK_DEADLOCK ||
+ error == HA_ERR_LOCK_WAIT_TIMEOUT ||
+ (keynum= table->file->get_dup_key(error)) < 0 ||
+ !overwrite)
+ {
+ DBUG_PRINT("info",("get_dup_key returns %d)", keynum));
+ /*
+ Deadlock, waiting for lock or just an error from the handler
+ such as HA_ERR_FOUND_DUPP_KEY when overwrite is false.
+ Retrieval of the duplicate key number may fail
+ - either because the error was not "duplicate key" error
+ - or because the information which key is not available
+ */
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ /*
+ We need to retrieve the old row into record[1] to be able to
+ either update or delete the offending record. We either:
+
+ - use rnd_pos() with a row-id (available as dupp_row) to the
+ offending row, if that is possible (MyISAM and Blackhole), or else
+
+ - use index_read_idx() with the key that is duplicated, to
+ retrieve the offending row.
+ */
+ if (table->file->ha_table_flags() & HA_DUPLICATE_POS)
+ {
+ DBUG_PRINT("info",("Locating offending record using rnd_pos()"));
+ error= table->file->ha_rnd_pos(table->record[1], table->file->dup_ref);
+ if (error)
+ {
+ DBUG_PRINT("info",("rnd_pos() returns error %d",error));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info",("Locating offending record using index_read_idx()"));
+
+ if (table->file->extra(HA_EXTRA_FLUSH_CACHE))
+ {
+ DBUG_PRINT("info",("Error when setting HA_EXTRA_FLUSH_CACHE"));
+ DBUG_RETURN(my_errno);
+ }
+
+ if (key.get() == NULL)
+ {
+ key.assign(static_cast<char*>(my_alloca(table->s->max_unique_length)));
+ if (key.get() == NULL)
+ {
+ DBUG_PRINT("info",("Can't allocate key buffer"));
+ DBUG_RETURN(ENOMEM);
+ }
+ }
+
+ key_copy((uchar*)key.get(), table->record[0], table->key_info + keynum,
+ 0);
+ error= table->file->ha_index_read_idx_map(table->record[1], keynum,
+ (const uchar*)key.get(),
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT);
+ if (error)
+ {
+ DBUG_PRINT("info",("index_read_idx() returns %s", HA_ERR(error)));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ }
+
+ /*
+ Now, record[1] should contain the offending row. That
+ will enable us to update it or, alternatively, delete it (so
+ that we can insert the new row afterwards).
+ */
+
+ /*
+ If row is incomplete we will use the record found to fill
+ missing columns.
+ */
+ if (!get_flags(COMPLETE_ROWS_F))
+ {
+ restore_record(table,record[1]);
+ error= unpack_current_row(rgi);
+ }
+
+#ifndef DBUG_OFF
+ DBUG_PRINT("debug",("preparing for update: before and after image"));
+ DBUG_DUMP("record[1] (before)", table->record[1], table->s->reclength);
+ DBUG_DUMP("record[0] (after)", table->record[0], table->s->reclength);
+#endif
+
+ /*
+ REPLACE is defined as either INSERT or DELETE + INSERT. If
+ possible, we can replace it with an UPDATE, but that will not
+ work on InnoDB if FOREIGN KEY checks are necessary.
+
+ I (Matz) am not sure of the reason for the last_uniq_key()
+ check as, but I'm guessing that it's something along the
+ following lines.
+
+ Suppose that we got the duplicate key to be a key that is not
+ the last unique key for the table and we perform an update:
+ then there might be another key for which the unique check will
+ fail, so we're better off just deleting the row and inserting
+ the correct row.
+ */
+ if (last_uniq_key(table, keynum) &&
+ !table->file->referenced_by_foreign_key())
+ {
+ DBUG_PRINT("info",("Updating row using ha_update_row()"));
+ if (invoke_triggers &&
+ process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, FALSE))
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+ else
+ {
+ error= table->file->ha_update_row(table->record[1],
+ table->record[0]);
+ switch (error) {
+
+ case HA_ERR_RECORD_IS_THE_SAME:
+ DBUG_PRINT("info",("ignoring HA_ERR_RECORD_IS_THE_SAME error from"
+ " ha_update_row()"));
+ error= 0;
+
+ case 0:
+ break;
+
+ default:
+ DBUG_PRINT("info",("ha_update_row() returns error %d",error));
+ table->file->print_error(error, MYF(0));
+ }
+ if (invoke_triggers && !error &&
+ (process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER, TRUE) ||
+ process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, TRUE)))
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+ }
+
+ DBUG_RETURN(error);
+ }
+ else
+ {
+ DBUG_PRINT("info",("Deleting offending row and trying to write new one again"));
+ if (invoke_triggers &&
+ process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, TRUE))
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+ else
+ {
+ if ((error= table->file->ha_delete_row(table->record[1])))
+ {
+ DBUG_PRINT("info",("ha_delete_row() returns error %d",error));
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ if (invoke_triggers &&
+ process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, TRUE))
+ DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet
+ }
+ /* Will retry ha_write_row() with the offending row removed. */
+ }
+ }
+
+ if (invoke_triggers &&
+ process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, TRUE))
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+
+ DBUG_RETURN(error);
+}
+
+#endif
+
+int
+Write_rows_log_event::do_exec_row(rpl_group_info *rgi)
+{
+ DBUG_ASSERT(m_table != NULL);
+ int error= write_row(rgi, slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT);
+
+ if (error && !thd->is_error())
+ {
+ DBUG_ASSERT(0);
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ }
+
+ return error;
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+#ifdef MYSQL_CLIENT
+void Write_rows_log_event::print(FILE *file, PRINT_EVENT_INFO* print_event_info)
+{
+ DBUG_EXECUTE_IF("simulate_cache_read_error",
+ {DBUG_SET("+d,simulate_my_b_fill_error");});
+ Rows_log_event::print_helper(file, print_event_info, "Write_rows");
+}
+#endif
+
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+uint8 Write_rows_log_event::get_trg_event_map()
+{
+ return (static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_INSERT)) |
+ static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)) |
+ static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)));
+}
+#endif
+
+/**************************************************************************
+ Delete_rows_log_event member functions
+**************************************************************************/
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+/*
+ Compares table->record[0] and table->record[1]
+
+ Returns TRUE if different.
+*/
+static bool record_compare(TABLE *table)
+{
+ /*
+ Need to set the X bit and the filler bits in both records since
+ there are engines that do not set it correctly.
+
+ In addition, since MyISAM checks that one hasn't tampered with the
+ record, it is necessary to restore the old bytes into the record
+ after doing the comparison.
+
+ TODO[record format ndb]: Remove it once NDB returns correct
+ records. Check that the other engines also return correct records.
+ */
+
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+ DBUG_DUMP("record[1]", table->record[1], table->s->reclength);
+
+ bool result= FALSE;
+ uchar saved_x[2]= {0, 0}, saved_filler[2]= {0, 0};
+
+ if (table->s->null_bytes > 0)
+ {
+ for (int i = 0 ; i < 2 ; ++i)
+ {
+ /*
+ If we have an X bit then we need to take care of it.
+ */
+ if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD))
+ {
+ saved_x[i]= table->record[i][0];
+ table->record[i][0]|= 1U;
+ }
+
+ /*
+ If (last_null_bit_pos == 0 && null_bytes > 1), then:
+
+ X bit (if any) + N nullable fields + M Field_bit fields = 8 bits
+
+ Ie, the entire byte is used.
+ */
+ if (table->s->last_null_bit_pos > 0)
+ {
+ saved_filler[i]= table->record[i][table->s->null_bytes - 1];
+ table->record[i][table->s->null_bytes - 1]|=
+ 256U - (1U << table->s->last_null_bit_pos);
+ }
+ }
+ }
+
+ /**
+ Compare full record only if:
+ - there are no blob fields (otherwise we would also need
+ to compare blobs contents as well);
+ - there are no varchar fields (otherwise we would also need
+ to compare varchar contents as well);
+ - there are no null fields, otherwise NULLed fields
+ contents (i.e., the don't care bytes) may show arbitrary
+ values, depending on how each engine handles internally.
+ */
+ if ((table->s->blob_fields +
+ table->s->varchar_fields +
+ table->s->null_fields) == 0)
+ {
+ result= cmp_record(table,record[1]);
+ goto record_compare_exit;
+ }
+
+ /* Compare null bits */
+ if (memcmp(table->null_flags,
+ table->null_flags+table->s->rec_buff_length,
+ table->s->null_bytes))
+ {
+ result= TRUE; // Diff in NULL value
+ goto record_compare_exit;
+ }
+
+ /* Compare fields */
+ for (Field **ptr=table->field ; *ptr ; ptr++)
+ {
+
+ /**
+ We only compare field contents that are not null.
+ NULL fields (i.e., their null bits) were compared
+ earlier.
+ */
+ if (!(*(ptr))->is_null())
+ {
+ if ((*ptr)->cmp_binary_offset(table->s->rec_buff_length))
+ {
+ result= TRUE;
+ goto record_compare_exit;
+ }
+ }
+ }
+
+record_compare_exit:
+ /*
+ Restore the saved bytes.
+
+ TODO[record format ndb]: Remove this code once NDB returns the
+ correct record format.
+ */
+ if (table->s->null_bytes > 0)
+ {
+ for (int i = 0 ; i < 2 ; ++i)
+ {
+ if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD))
+ table->record[i][0]= saved_x[i];
+
+ if (table->s->last_null_bit_pos)
+ table->record[i][table->s->null_bytes - 1]= saved_filler[i];
+ }
+ }
+
+ return result;
+}
+
+
+/**
+ Find the best key to use when locating the row in @c find_row().
+
+ A primary key is preferred if it exists; otherwise a unique index is
+ preferred. Else we pick the index with the smalles rec_per_key value.
+
+ If a suitable key is found, set @c m_key, @c m_key_nr and @c m_key_info
+ member fields appropriately.
+
+ @returns Error code on failure, 0 on success.
+*/
+int Rows_log_event::find_key()
+{
+ uint i, best_key_nr, last_part;
+ KEY *key, *best_key;
+ ulong best_rec_per_key, tmp;
+ DBUG_ENTER("Rows_log_event::find_key");
+ DBUG_ASSERT(m_table);
+
+ best_key_nr= MAX_KEY;
+ LINT_INIT(best_key);
+ LINT_INIT(best_rec_per_key);
+
+ /*
+ Keys are sorted so that any primary key is first, followed by unique keys,
+ followed by any other. So we will automatically pick the primary key if
+ it exists.
+ */
+ for (i= 0, key= m_table->key_info; i < m_table->s->keys; i++, key++)
+ {
+ if (!m_table->s->keys_in_use.is_set(i))
+ continue;
+ /*
+ We cannot use a unique key with NULL-able columns to uniquely identify
+ a row (but we can still select it for range scan below if nothing better
+ is available).
+ */
+ if ((key->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME)
+ {
+ best_key_nr= i;
+ best_key= key;
+ break;
+ }
+ /*
+ We can only use a non-unique key if it allows range scans (ie. skip
+ FULLTEXT indexes and such).
+ */
+ last_part= key->user_defined_key_parts - 1;
+ DBUG_PRINT("info", ("Index %s rec_per_key[%u]= %lu",
+ key->name, last_part, key->rec_per_key[last_part]));
+ if (!(m_table->file->index_flags(i, last_part, 1) & HA_READ_NEXT))
+ continue;
+
+ tmp= key->rec_per_key[last_part];
+ if (best_key_nr == MAX_KEY || (tmp > 0 && tmp < best_rec_per_key))
+ {
+ best_key_nr= i;
+ best_key= key;
+ best_rec_per_key= tmp;
+ }
+ }
+
+ if (best_key_nr == MAX_KEY)
+ {
+ m_key_info= NULL;
+ DBUG_RETURN(0);
+ }
+
+ // Allocate buffer for key searches
+ m_key= (uchar *) my_malloc(best_key->key_length, MYF(MY_WME));
+ if (m_key == NULL)
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ m_key_info= best_key;
+ m_key_nr= best_key_nr;
+
+ DBUG_RETURN(0);;
+}
+
+
+/*
+ Check if we are already spending too much time on this statement.
+ if we are, warn user that it might be because table does not have
+ a PK, but only if the warning was not printed before for this STMT.
+
+ @param type The event type code.
+ @param table_name The name of the table that the slave is
+ operating.
+ @param is_index_scan States whether the slave is doing an index scan
+ or not.
+ @param rli The relay metadata info.
+*/
+static inline
+void issue_long_find_row_warning(Log_event_type type,
+ const char *table_name,
+ bool is_index_scan,
+ rpl_group_info *rgi)
+{
+ if ((global_system_variables.log_warnings > 1 &&
+ !rgi->is_long_find_row_note_printed()))
+ {
+ time_t now= my_time(0);
+ time_t stmt_ts= rgi->get_row_stmt_start_timestamp();
+
+ DBUG_EXECUTE_IF("inject_long_find_row_note",
+ stmt_ts-=(LONG_FIND_ROW_THRESHOLD*2););
+
+ long delta= (long) (now - stmt_ts);
+
+ if (delta > LONG_FIND_ROW_THRESHOLD)
+ {
+ rgi->set_long_find_row_note_printed();
+ const char* evt_type= type == DELETE_ROWS_EVENT ? " DELETE" : "n UPDATE";
+ const char* scan_type= is_index_scan ? "scanning an index" : "scanning the table";
+
+ sql_print_information("The slave is applying a ROW event on behalf of a%s statement "
+ "on table %s and is currently taking a considerable amount "
+ "of time (%ld seconds). This is due to the fact that it is %s "
+ "while looking up records to be processed. Consider adding a "
+ "primary key (or unique key) to the table to improve "
+ "performance.", evt_type, table_name, delta, scan_type);
+ }
+ }
+}
+
+
+/**
+ Locate the current row in event's table.
+
+ The current row is pointed by @c m_curr_row. Member @c m_width tells
+ how many columns are there in the row (this can be differnet from
+ the number of columns in the table). It is assumed that event's
+ table is already open and pointed by @c m_table.
+
+ If a corresponding record is found in the table it is stored in
+ @c m_table->record[0]. Note that when record is located based on a primary
+ key, it is possible that the record found differs from the row being located.
+
+ If no key is specified or table does not have keys, a table scan is used to
+ find the row. In that case the row should be complete and contain values for
+ all columns. However, it can still be shorter than the table, i.e. the table
+ can contain extra columns not present in the row. It is also possible that
+ the table has fewer columns than the row being located.
+
+ @returns Error code on failure, 0 on success.
+
+ @post In case of success @c m_table->record[0] contains the record found.
+ Also, the internal "cursor" of the table is positioned at the record found.
+
+ @note If the engine allows random access of the records, a combination of
+ @c position() and @c rnd_pos() will be used.
+
+ Note that one MUST call ha_index_or_rnd_end() after this function if
+ it returns 0 as we must leave the row position in the handler intact
+ for any following update/delete command.
+*/
+
+int Rows_log_event::find_row(rpl_group_info *rgi)
+{
+ DBUG_ENTER("Rows_log_event::find_row");
+
+ DBUG_ASSERT(m_table && m_table->in_use != NULL);
+
+ TABLE *table= m_table;
+ int error= 0;
+ bool is_table_scan= false, is_index_scan= false;
+
+ /*
+ rpl_row_tabledefs.test specifies that
+ if the extra field on the slave does not have a default value
+ and this is okay with Delete or Update events.
+ Todo: fix wl3228 hld that requires defauls for all types of events
+ */
+
+ prepare_record(table, m_width, FALSE);
+ error= unpack_current_row(rgi);
+
+#ifndef DBUG_OFF
+ DBUG_PRINT("info",("looking for the following record"));
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+#endif
+
+ if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
+ table->s->primary_key < MAX_KEY)
+ {
+ /*
+ Use a more efficient method to fetch the record given by
+ table->record[0] if the engine allows it. We first compute a
+ row reference using the position() member function (it will be
+ stored in table->file->ref) and the use rnd_pos() to position
+ the "cursor" (i.e., record[0] in this case) at the correct row.
+
+ TODO: Add a check that the correct record has been fetched by
+ comparing with the original record. Take into account that the
+ record on the master and slave can be of different
+ length. Something along these lines should work:
+
+ ADD>>> store_record(table,record[1]);
+ int error= table->file->ha_rnd_pos(table->record[0],
+ table->file->ref);
+ ADD>>> DBUG_ASSERT(memcmp(table->record[1], table->record[0],
+ table->s->reclength) == 0);
+
+ */
+ int error;
+ DBUG_PRINT("info",("locating record using primary key (position)"));
+
+ if (!table->file->inited &&
+ (error= table->file->ha_rnd_init_with_error(0)))
+ DBUG_RETURN(error);
+
+ error= table->file->ha_rnd_pos_by_record(table->record[0]);
+ if (error)
+ {
+ DBUG_PRINT("info",("rnd_pos returns error %d",error));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ }
+ DBUG_RETURN(error);
+ }
+
+ // We can't use position() - try other methods.
+
+ /*
+ We need to retrieve all fields
+ TODO: Move this out from this function to main loop
+ */
+ table->use_all_columns();
+
+ /*
+ Save copy of the record in table->record[1]. It might be needed
+ later if linear search is used to find exact match.
+ */
+ store_record(table,record[1]);
+
+ if (m_key_info)
+ {
+ DBUG_PRINT("info",("locating record using key #%u [%s] (index_read)",
+ m_key_nr, m_key_info->name));
+ /* We use this to test that the correct key is used in test cases. */
+ DBUG_EXECUTE_IF("slave_crash_if_wrong_index",
+ if(0 != strcmp(m_key_info->name,"expected_key")) abort(););
+
+ /* The key is active: search the table using the index */
+ if (!table->file->inited &&
+ (error= table->file->ha_index_init(m_key_nr, FALSE)))
+ {
+ DBUG_PRINT("info",("ha_index_init returns error %d",error));
+ table->file->print_error(error, MYF(0));
+ goto end;
+ }
+
+ /* Fill key data for the row */
+
+ DBUG_ASSERT(m_key);
+ key_copy(m_key, table->record[0], m_key_info, 0);
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("key data", m_key, m_key_info->key_length);
+#endif
+
+ /*
+ We need to set the null bytes to ensure that the filler bit are
+ all set when returning. There are storage engines that just set
+ the necessary bits on the bytes and don't set the filler bits
+ correctly.
+ */
+ if (table->s->null_bytes > 0)
+ table->record[0][table->s->null_bytes - 1]|=
+ 256U - (1U << table->s->last_null_bit_pos);
+
+ if ((error= table->file->ha_index_read_map(table->record[0], m_key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT)))
+ {
+ DBUG_PRINT("info",("no record matching the key found in the table"));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ table->file->ha_index_end();
+ goto end;
+ }
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_PRINT("info",("found first matching record"));
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+#endif
+ /*
+ Below is a minor "optimization". If the key (i.e., key number
+ 0) has the HA_NOSAME flag set, we know that we have found the
+ correct record (since there can be no duplicates); otherwise, we
+ have to compare the record with the one found to see if it is
+ the correct one.
+
+ CAVEAT! This behaviour is essential for the replication of,
+ e.g., the mysql.proc table since the correct record *shall* be
+ found using the primary key *only*. There shall be no
+ comparison of non-PK columns to decide if the correct record is
+ found. I can see no scenario where it would be incorrect to
+ chose the row to change only using a PK or an UNNI.
+ */
+ if (table->key_info->flags & HA_NOSAME)
+ {
+ /* Unique does not have non nullable part */
+ if (!(table->key_info->flags & (HA_NULL_PART_KEY)))
+ {
+ error= 0;
+ goto end;
+ }
+ else
+ {
+ KEY *keyinfo= table->key_info;
+ /*
+ Unique has nullable part. We need to check if there is any
+ field in the BI image that is null and part of UNNI.
+ */
+ bool null_found= FALSE;
+ for (uint i=0; i < keyinfo->user_defined_key_parts && !null_found; i++)
+ {
+ uint fieldnr= keyinfo->key_part[i].fieldnr - 1;
+ Field **f= table->field+fieldnr;
+ null_found= (*f)->is_null();
+ }
+
+ if (!null_found)
+ {
+ error= 0;
+ goto end;
+ }
+
+ /* else fall through to index scan */
+ }
+ }
+
+ is_index_scan=true;
+
+ /*
+ In case key is not unique, we still have to iterate over records found
+ and find the one which is identical to the row given. A copy of the
+ record we are looking for is stored in record[1].
+ */
+ DBUG_PRINT("info",("non-unique index, scanning it to find matching record"));
+ /* We use this to test that the correct key is used in test cases. */
+ DBUG_EXECUTE_IF("slave_crash_if_index_scan", abort(););
+
+ while (record_compare(table))
+ {
+ /*
+ We need to set the null bytes to ensure that the filler bit
+ are all set when returning. There are storage engines that
+ just set the necessary bits on the bytes and don't set the
+ filler bits correctly.
+
+ TODO[record format ndb]: Remove this code once NDB returns the
+ correct record format.
+ */
+ if (table->s->null_bytes > 0)
+ {
+ table->record[0][table->s->null_bytes - 1]|=
+ 256U - (1U << table->s->last_null_bit_pos);
+ }
+
+ while ((error= table->file->ha_index_next(table->record[0])))
+ {
+ /* We just skip records that has already been deleted */
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ DBUG_PRINT("info",("no record matching the given row found"));
+ table->file->print_error(error, MYF(0));
+ table->file->ha_index_end();
+ goto end;
+ }
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info",("locating record using table scan (rnd_next)"));
+ /* We use this to test that the correct key is used in test cases. */
+ DBUG_EXECUTE_IF("slave_crash_if_table_scan", abort(););
+
+ /* We don't have a key: search the table using rnd_next() */
+ if ((error= table->file->ha_rnd_init_with_error(1)))
+ {
+ DBUG_PRINT("info",("error initializing table scan"
+ " (ha_rnd_init returns %d)",error));
+ goto end;
+ }
+
+ is_table_scan= true;
+
+ /* Continue until we find the right record or have made a full loop */
+ do
+ {
+ restart_rnd_next:
+ error= table->file->ha_rnd_next(table->record[0]);
+
+ if (error)
+ DBUG_PRINT("info", ("error: %s", HA_ERR(error)));
+ switch (error) {
+
+ case 0:
+ DBUG_DUMP("record found", table->record[0], table->s->reclength);
+ break;
+
+ case HA_ERR_END_OF_FILE:
+ DBUG_PRINT("info", ("Record not found"));
+ table->file->ha_rnd_end();
+ goto end;
+
+ /*
+ If the record was deleted, we pick the next one without doing
+ any comparisons.
+ */
+ case HA_ERR_RECORD_DELETED:
+ goto restart_rnd_next;
+
+ default:
+ DBUG_PRINT("info", ("Failed to get next record"
+ " (rnd_next returns %d)",error));
+ table->file->print_error(error, MYF(0));
+ table->file->ha_rnd_end();
+ goto end;
+ }
+ }
+ while (record_compare(table));
+
+ /*
+ Note: above record_compare will take into accout all record fields
+ which might be incorrect in case a partial row was given in the event
+ */
+
+ DBUG_ASSERT(error == HA_ERR_END_OF_FILE || error == 0);
+ }
+
+end:
+ if (is_table_scan || is_index_scan)
+ issue_long_find_row_warning(get_general_type_code(), m_table->alias.c_ptr(),
+ is_index_scan, rgi);
+ table->default_column_bitmaps();
+ DBUG_RETURN(error);
+}
+
+#endif
+
+/*
+ Constructor used to build an event for writing to the binary log.
+ */
+
+#ifndef MYSQL_CLIENT
+Delete_rows_log_event::Delete_rows_log_event(THD *thd_arg, TABLE *tbl_arg,
+ ulong tid, MY_BITMAP const *cols,
+ bool is_transactional)
+ : Rows_log_event(thd_arg, tbl_arg, tid, cols, is_transactional, DELETE_ROWS_EVENT_V1)
+{
+}
+#endif /* #if !defined(MYSQL_CLIENT) */
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Delete_rows_log_event::Delete_rows_log_event(const char *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Rows_log_event(buf, event_len, description_event)
+{
+}
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+int
+Delete_rows_log_event::do_before_row_operations(const Slave_reporting_capability *const)
+{
+ /*
+ Increment the global status delete count variable
+ */
+ if (get_flags(STMT_END_F))
+ status_var_increment(thd->status_var.com_stat[SQLCOM_DELETE]);
+
+ if ((m_table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
+ m_table->s->primary_key < MAX_KEY)
+ {
+ /*
+ We don't need to allocate any memory for m_key since it is not used.
+ */
+ return 0;
+ }
+#ifdef RBR_TRIGGERS
+ if (slave_run_triggers_for_rbr && !master_had_triggers)
+ m_table->prepare_triggers_for_delete_stmt_or_event();
+#endif //RBR_TRIGGERS
+
+ return find_key();
+}
+
+int
+Delete_rows_log_event::do_after_row_operations(const Slave_reporting_capability *const,
+ int error)
+{
+ /*error= ToDo:find out what this should really be, this triggers close_scan in nbd, returning error?*/
+ m_table->file->ha_index_or_rnd_end();
+ my_free(m_key);
+ m_key= NULL;
+ m_key_info= NULL;
+
+ return error;
+}
+
+int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
+{
+ int error;
+ const bool invoke_triggers=
+ slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers;
+ DBUG_ASSERT(m_table != NULL);
+
+ if (!(error= find_row(rgi)))
+ {
+ /*
+ Delete the record found, located in record[0]
+ */
+ if (invoke_triggers &&
+ process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE))
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+ if (!error)
+ error= m_table->file->ha_delete_row(m_table->record[0]);
+ if (invoke_triggers && !error &&
+ process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE))
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+ m_table->file->ha_index_or_rnd_end();
+ }
+ return error;
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+#ifdef MYSQL_CLIENT
+void Delete_rows_log_event::print(FILE *file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Rows_log_event::print_helper(file, print_event_info, "Delete_rows");
+}
+#endif
+
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+uint8 Delete_rows_log_event::get_trg_event_map()
+{
+ return static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE));
+}
+#endif
+
+/**************************************************************************
+ Update_rows_log_event member functions
+**************************************************************************/
+
+/*
+ Constructor used to build an event for writing to the binary log.
+ */
+#if !defined(MYSQL_CLIENT)
+Update_rows_log_event::Update_rows_log_event(THD *thd_arg, TABLE *tbl_arg,
+ ulong tid,
+ MY_BITMAP const *cols_bi,
+ MY_BITMAP const *cols_ai,
+ bool is_transactional)
+: Rows_log_event(thd_arg, tbl_arg, tid, cols_bi, is_transactional, UPDATE_ROWS_EVENT_V1)
+{
+ init(cols_ai);
+}
+
+Update_rows_log_event::Update_rows_log_event(THD *thd_arg, TABLE *tbl_arg,
+ ulong tid,
+ MY_BITMAP const *cols,
+ bool is_transactional)
+: Rows_log_event(thd_arg, tbl_arg, tid, cols, is_transactional, UPDATE_ROWS_EVENT_V1)
+{
+ init(cols);
+}
+
+void Update_rows_log_event::init(MY_BITMAP const *cols)
+{
+ /* if my_bitmap_init fails, caught in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols_ai,
+ m_width <= sizeof(m_bitbuf_ai)*8 ? m_bitbuf_ai : NULL,
+ m_width,
+ false)))
+ {
+ /* Cols can be zero if this is a dummy binrows event */
+ if (likely(cols != NULL))
+ {
+ memcpy(m_cols_ai.bitmap, cols->bitmap, no_bytes_in_map(cols));
+ create_last_word_mask(&m_cols_ai);
+ }
+ }
+}
+#endif /* !defined(MYSQL_CLIENT) */
+
+
+Update_rows_log_event::~Update_rows_log_event()
+{
+ if (m_cols_ai.bitmap == m_bitbuf_ai) // no my_malloc happened
+ m_cols_ai.bitmap= 0; // so no my_free in my_bitmap_free
+ my_bitmap_free(&m_cols_ai); // To pair with my_bitmap_init().
+}
+
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Update_rows_log_event::Update_rows_log_event(const char *buf, uint event_len,
+ const
+ Format_description_log_event
+ *description_event)
+ : Rows_log_event(buf, event_len, description_event)
+{
+}
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+int
+Update_rows_log_event::do_before_row_operations(const Slave_reporting_capability *const)
+{
+ /*
+ Increment the global status update count variable
+ */
+ if (get_flags(STMT_END_F))
+ status_var_increment(thd->status_var.com_stat[SQLCOM_UPDATE]);
+
+ int err;
+ if ((err= find_key()))
+ return err;
+
+ if (slave_run_triggers_for_rbr && !master_had_triggers)
+ m_table->prepare_triggers_for_update_stmt_or_event();
+
+ return 0;
+}
+
+int
+Update_rows_log_event::do_after_row_operations(const Slave_reporting_capability *const,
+ int error)
+{
+ /*error= ToDo:find out what this should really be, this triggers close_scan in nbd, returning error?*/
+ m_table->file->ha_index_or_rnd_end();
+ my_free(m_key); // Free for multi_malloc
+ m_key= NULL;
+ m_key_info= NULL;
+
+ return error;
+}
+
+int
+Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
+{
+ const bool invoke_triggers=
+ slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers;
+ DBUG_ASSERT(m_table != NULL);
+
+ int error= find_row(rgi);
+ if (error)
+ {
+ /*
+ We need to read the second image in the event of error to be
+ able to skip to the next pair of updates
+ */
+ m_curr_row= m_curr_row_end;
+ unpack_current_row(rgi);
+ return error;
+ }
+
+ /*
+ This is the situation after locating BI:
+
+ ===|=== before image ====|=== after image ===|===
+ ^ ^
+ m_curr_row m_curr_row_end
+
+ BI found in the table is stored in record[0]. We copy it to record[1]
+ and unpack AI to record[0].
+ */
+
+ store_record(m_table,record[1]);
+
+ m_curr_row= m_curr_row_end;
+ /* this also updates m_curr_row_end */
+ if ((error= unpack_current_row(rgi)))
+ goto err;
+
+ /*
+ Now we have the right row to update. The old row (the one we're
+ looking for) is in record[1] and the new row is in record[0].
+ */
+#ifndef HAVE_valgrind
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+ DBUG_PRINT("info",("Updating row in table"));
+ DBUG_DUMP("old record", m_table->record[1], m_table->s->reclength);
+ DBUG_DUMP("new values", m_table->record[0], m_table->s->reclength);
+#endif
+
+ if (invoke_triggers &&
+ process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, TRUE))
+ {
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+ goto err;
+ }
+
+ error= m_table->file->ha_update_row(m_table->record[1], m_table->record[0]);
+ if (error == HA_ERR_RECORD_IS_THE_SAME)
+ error= 0;
+
+ if (invoke_triggers && !error &&
+ process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER, TRUE))
+ error= HA_ERR_GENERIC; // in case if error is not set yet
+
+err:
+ m_table->file->ha_index_or_rnd_end();
+ return error;
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+#ifdef MYSQL_CLIENT
+void Update_rows_log_event::print(FILE *file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Rows_log_event::print_helper(file, print_event_info, "Update_rows");
+}
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+uint8 Update_rows_log_event::get_trg_event_map()
+{
+ return static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE));
+}
+#endif
+
+Incident_log_event::Incident_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *descr_event)
+ : Log_event(buf, descr_event)
+{
+ DBUG_ENTER("Incident_log_event::Incident_log_event");
+ uint8 const common_header_len=
+ descr_event->common_header_len;
+ uint8 const post_header_len=
+ descr_event->post_header_len[INCIDENT_EVENT-1];
+
+ DBUG_PRINT("info",("event_len: %u; common_header_len: %d; post_header_len: %d",
+ event_len, common_header_len, post_header_len));
+
+ m_message.str= NULL;
+ m_message.length= 0;
+ int incident_number= uint2korr(buf + common_header_len);
+ if (incident_number >= INCIDENT_COUNT ||
+ incident_number <= INCIDENT_NONE)
+ {
+ // If the incident is not recognized, this binlog event is
+ // invalid. If we set incident_number to INCIDENT_NONE, the
+ // invalidity will be detected by is_valid().
+ m_incident= INCIDENT_NONE;
+ DBUG_VOID_RETURN;
+ }
+ m_incident= static_cast<Incident>(incident_number);
+ char const *ptr= buf + common_header_len + post_header_len;
+ char const *const str_end= buf + event_len;
+ uint8 len= 0; // Assignment to keep compiler happy
+ const char *str= NULL; // Assignment to keep compiler happy
+ read_str(&ptr, str_end, &str, &len);
+ if (!(m_message.str= (char*) my_malloc(len+1, MYF(MY_WME))))
+ {
+ /* Mark this event invalid */
+ m_incident= INCIDENT_NONE;
+ DBUG_VOID_RETURN;
+ }
+ strmake(m_message.str, str, len);
+ m_message.length= len;
+ DBUG_PRINT("info", ("m_incident: %d", m_incident));
+ DBUG_VOID_RETURN;
+}
+
+
+Incident_log_event::~Incident_log_event()
+{
+ if (m_message.str)
+ my_free(m_message.str);
+}
+
+
+const char *
+Incident_log_event::description() const
+{
+ static const char *const description[]= {
+ "NOTHING", // Not used
+ "LOST_EVENTS"
+ };
+
+ DBUG_PRINT("info", ("m_incident: %d", m_incident));
+ return description[m_incident];
+}
+
+
+#ifndef MYSQL_CLIENT
+void Incident_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[256];
+ size_t bytes;
+ if (m_message.length > 0)
+ bytes= my_snprintf(buf, sizeof(buf), "#%d (%s)",
+ m_incident, description());
+ else
+ bytes= my_snprintf(buf, sizeof(buf), "#%d (%s): %s",
+ m_incident, description(), m_message.str);
+ protocol->store(buf, bytes, &my_charset_bin);
+}
+#endif
+
+
+#ifdef MYSQL_CLIENT
+void
+Incident_log_event::print(FILE *file,
+ PRINT_EVENT_INFO *print_event_info)
+{
+ if (print_event_info->short_form)
+ return;
+
+ Write_on_release_cache cache(&print_event_info->head_cache, file);
+ print_header(&cache, print_event_info, FALSE);
+ my_b_printf(&cache, "\n# Incident: %s\nRELOAD DATABASE; # Shall generate syntax error\n", description());
+}
+#endif
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+int
+Incident_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ Relay_log_info const *rli= rgi->rli;
+ DBUG_ENTER("Incident_log_event::do_apply_event");
+
+ if (ignored_error_code(ER_SLAVE_INCIDENT))
+ {
+ DBUG_PRINT("info", ("Ignoring Incident"));
+ DBUG_RETURN(0);
+ }
+
+ rli->report(ERROR_LEVEL, ER_SLAVE_INCIDENT, NULL,
+ ER(ER_SLAVE_INCIDENT),
+ description(),
+ m_message.length > 0 ? m_message.str : "<none>");
+ DBUG_RETURN(1);
+}
+#endif
+
+bool
+Incident_log_event::write_data_header(IO_CACHE *file)
+{
+ DBUG_ENTER("Incident_log_event::write_data_header");
+ DBUG_PRINT("enter", ("m_incident: %d", m_incident));
+ uchar buf[sizeof(int16)];
+ int2store(buf, (int16) m_incident);
+#ifndef MYSQL_CLIENT
+ DBUG_RETURN(wrapper_my_b_safe_write(file, buf, sizeof(buf)));
+#else
+ DBUG_RETURN(my_b_safe_write(file, buf, sizeof(buf)));
+#endif
+}
+
+bool
+Incident_log_event::write_data_body(IO_CACHE *file)
+{
+ uchar tmp[1];
+ DBUG_ENTER("Incident_log_event::write_data_body");
+ tmp[0]= (uchar) m_message.length;
+ crc= my_checksum(crc, (uchar*) tmp, 1);
+ if (m_message.length > 0)
+ {
+ crc= my_checksum(crc, (uchar*) m_message.str, m_message.length);
+ // todo: report a bug on write_str accepts uint but treats it as uchar
+ }
+ DBUG_RETURN(write_str(file, m_message.str, (uint) m_message.length));
+}
+
+
+#ifdef MYSQL_CLIENT
+/**
+ The default values for these variables should be values that are
+ *incorrect*, i.e., values that cannot occur in an event. This way,
+ they will always be printed for the first event.
+*/
+st_print_event_info::st_print_event_info()
+ :flags2_inited(0), sql_mode_inited(0), sql_mode(0),
+ auto_increment_increment(0),auto_increment_offset(0), charset_inited(0),
+ lc_time_names_number(~0),
+ charset_database_number(ILLEGAL_CHARSET_INFO_NUMBER),
+ thread_id(0), thread_id_printed(false), server_id(0),
+ server_id_printed(false), domain_id(0), domain_id_printed(false),
+ skip_replication(0),
+ base64_output_mode(BASE64_OUTPUT_UNSPEC), printed_fd_event(FALSE)
+{
+ /*
+ Currently we only use static PRINT_EVENT_INFO objects, so zeroed at
+ program's startup, but these explicit bzero() is for the day someone
+ creates dynamic instances.
+ */
+ bzero(db, sizeof(db));
+ bzero(charset, sizeof(charset));
+ bzero(time_zone_str, sizeof(time_zone_str));
+ delimiter[0]= ';';
+ delimiter[1]= 0;
+ myf const flags = MYF(MY_WME | MY_NABP);
+ open_cached_file(&head_cache, NULL, NULL, 0, flags);
+ open_cached_file(&body_cache, NULL, NULL, 0, flags);
+}
+#endif
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+Heartbeat_log_event::Heartbeat_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+{
+ uint8 header_size= description_event->common_header_len;
+ ident_len = event_len - header_size;
+ set_if_smaller(ident_len,FN_REFLEN-1);
+ log_ident= buf + header_size;
+}
+#endif
+
+#if defined(MYSQL_SERVER)
+/*
+ Access to the current replication position.
+
+ There is a dummy replacement for this in the embedded library that returns
+ FALSE; this is used by XtraDB to allow it to access replication stuff while
+ still being able to use the same plugin in both stand-alone and embedded.
+
+ In this function it's ok to use active_mi, as this is only called for
+ the main replication server.
+*/
+bool rpl_get_position_info(const char **log_file_name, ulonglong *log_pos,
+ const char **group_relay_log_name,
+ ulonglong *relay_log_pos)
+{
+#if defined(EMBEDDED_LIBRARY) || !defined(HAVE_REPLICATION)
+ return FALSE;
+#else
+ const Relay_log_info *rli= &(active_mi->rli);
+ if (opt_slave_parallel_threads == 0)
+ {
+ *log_file_name= rli->group_master_log_name;
+ *log_pos= rli->group_master_log_pos +
+ (rli->future_event_relay_log_pos - rli->group_relay_log_pos);
+ *group_relay_log_name= rli->group_relay_log_name;
+ *relay_log_pos= rli->future_event_relay_log_pos;
+ }
+ else
+ {
+ *log_file_name= "";
+ *log_pos= 0;
+ *group_relay_log_name= "";
+ *relay_log_pos= 0;
+ }
+ return TRUE;
+#endif
+}
+#endif
diff --git a/sql/log_event.h b/sql/log_event.h
new file mode 100644
index 00000000000..6a3e6f174bb
--- /dev/null
+++ b/sql/log_event.h
@@ -0,0 +1,4807 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, Monty Program Ab.
+
+ 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 */
+
+/**
+ @addtogroup Replication
+ @{
+
+ @file
+
+ @brief Binary log event definitions. This includes generic code
+ common to all types of log events, as well as specific code for each
+ type of log event.
+*/
+
+
+#ifndef _log_event_h
+#define _log_event_h
+
+#if defined(USE_PRAGMA_INTERFACE) && defined(MYSQL_SERVER)
+#pragma interface /* gcc class implementation */
+#endif
+
+#include <my_bitmap.h>
+#include "rpl_constants.h"
+
+#ifdef MYSQL_CLIENT
+#include "sql_const.h"
+#include "rpl_utility.h"
+#include "hash.h"
+#include "rpl_tblmap.h"
+#endif
+
+#ifdef MYSQL_SERVER
+#include "rpl_record.h"
+#include "rpl_reporting.h"
+#include "sql_class.h" /* THD */
+#endif
+
+#include "rpl_gtid.h"
+
+/* Forward declarations */
+class String;
+
+#define PREFIX_SQL_LOAD "SQL_LOAD-"
+#define LONG_FIND_ROW_THRESHOLD 60 /* seconds */
+
+/**
+ Either assert or return an error.
+
+ In debug build, the condition will be checked, but in non-debug
+ builds, the error code given will be returned instead.
+
+ @param COND Condition to check
+ @param ERRNO Error number to return in non-debug builds
+*/
+#ifdef DBUG_OFF
+#define ASSERT_OR_RETURN_ERROR(COND, ERRNO) \
+ do { if (!(COND)) return ERRNO; } while (0)
+#else
+#define ASSERT_OR_RETURN_ERROR(COND, ERRNO) \
+ DBUG_ASSERT(COND)
+#endif
+
+#define LOG_READ_EOF -1
+#define LOG_READ_BOGUS -2
+#define LOG_READ_IO -3
+#define LOG_READ_MEM -5
+#define LOG_READ_TRUNC -6
+#define LOG_READ_TOO_LARGE -7
+#define LOG_READ_CHECKSUM_FAILURE -8
+
+#define LOG_EVENT_OFFSET 4
+
+/*
+ 3 is MySQL 4.x; 4 is MySQL 5.0.0.
+ Compared to version 3, version 4 has:
+ - a different Start_log_event, which includes info about the binary log
+ (sizes of headers); this info is included for better compatibility if the
+ master's MySQL version is different from the slave's.
+ - all events have a unique ID (the triplet (server_id, timestamp at server
+ start, other) to be sure an event is not executed more than once in a
+ multimaster setup, example:
+ M1
+ / \
+ v v
+ M2 M3
+ \ /
+ v v
+ S
+ if a query is run on M1, it will arrive twice on S, so we need that S
+ remembers the last unique ID it has processed, to compare and know if the
+ event should be skipped or not. Example of ID: we already have the server id
+ (4 bytes), plus:
+ timestamp_when_the_master_started (4 bytes), a counter (a sequence number
+ which increments every time we write an event to the binlog) (3 bytes).
+ Q: how do we handle when the counter is overflowed and restarts from 0 ?
+
+ - Query and Load (Create or Execute) events may have a more precise
+ timestamp (with microseconds), number of matched/affected/warnings rows
+ and fields of session variables: SQL_MODE,
+ FOREIGN_KEY_CHECKS, UNIQUE_CHECKS, SQL_AUTO_IS_NULL, the collations and
+ charsets, the PASSWORD() version (old/new/...).
+*/
+#define BINLOG_VERSION 4
+
+/*
+ We could have used SERVER_VERSION_LENGTH, but this introduces an
+ obscure dependency - if somebody decided to change SERVER_VERSION_LENGTH
+ this would break the replication protocol
+*/
+#define ST_SERVER_VER_LEN 50
+
+/*
+ These are flags and structs to handle all the LOAD DATA INFILE options (LINES
+ TERMINATED etc).
+*/
+
+/*
+ These are flags and structs to handle all the LOAD DATA INFILE options (LINES
+ TERMINATED etc).
+ DUMPFILE_FLAG is probably useless (DUMPFILE is a clause of SELECT, not of LOAD
+ DATA).
+*/
+#define DUMPFILE_FLAG 0x1
+#define OPT_ENCLOSED_FLAG 0x2
+#define REPLACE_FLAG 0x4
+#define IGNORE_FLAG 0x8
+
+#define FIELD_TERM_EMPTY 0x1
+#define ENCLOSED_EMPTY 0x2
+#define LINE_TERM_EMPTY 0x4
+#define LINE_START_EMPTY 0x8
+#define ESCAPED_EMPTY 0x10
+
+/*****************************************************************************
+
+ old_sql_ex struct
+
+ ****************************************************************************/
+struct old_sql_ex
+{
+ char field_term;
+ char enclosed;
+ char line_term;
+ char line_start;
+ char escaped;
+ char opt_flags;
+ char empty_flags;
+};
+
+#define NUM_LOAD_DELIM_STRS 5
+
+/*****************************************************************************
+
+ sql_ex_info struct
+
+ ****************************************************************************/
+struct sql_ex_info
+{
+ sql_ex_info() {} /* Remove gcc warning */
+ const char* field_term;
+ const char* enclosed;
+ const char* line_term;
+ const char* line_start;
+ const char* escaped;
+ int cached_new_format;
+ uint8 field_term_len,enclosed_len,line_term_len,line_start_len, escaped_len;
+ char opt_flags;
+ char empty_flags;
+
+ // store in new format even if old is possible
+ void force_new_format() { cached_new_format = 1;}
+ int data_size()
+ {
+ return (new_format() ?
+ field_term_len + enclosed_len + line_term_len +
+ line_start_len + escaped_len + 6 : 7);
+ }
+ bool write_data(IO_CACHE* file);
+ const char* init(const char* buf, const char* buf_end, bool use_new_format);
+ bool new_format()
+ {
+ return ((cached_new_format != -1) ? cached_new_format :
+ (cached_new_format=(field_term_len > 1 ||
+ enclosed_len > 1 ||
+ line_term_len > 1 || line_start_len > 1 ||
+ escaped_len > 1)));
+ }
+};
+
+/*****************************************************************************
+
+ MySQL Binary Log
+
+ This log consists of events. Each event has a fixed-length header,
+ possibly followed by a variable length data body.
+
+ The data body consists of an optional fixed length segment (post-header)
+ and an optional variable length segment.
+
+ See the #defines below for the format specifics.
+
+ The events which really update data are Query_log_event,
+ Execute_load_query_log_event and old Load_log_event and
+ Execute_load_log_event events (Execute_load_query is used together with
+ Begin_load_query and Append_block events to replicate LOAD DATA INFILE.
+ Create_file/Append_block/Execute_load (which includes Load_log_event)
+ were used to replicate LOAD DATA before the 5.0.3).
+
+ ****************************************************************************/
+
+#define LOG_EVENT_HEADER_LEN 19 /* the fixed header length */
+#define OLD_HEADER_LEN 13 /* the fixed header length in 3.23 */
+/*
+ Fixed header length, where 4.x and 5.0 agree. That is, 5.0 may have a longer
+ header (it will for sure when we have the unique event's ID), but at least
+ the first 19 bytes are the same in 4.x and 5.0. So when we have the unique
+ event's ID, LOG_EVENT_HEADER_LEN will be something like 26, but
+ LOG_EVENT_MINIMAL_HEADER_LEN will remain 19.
+*/
+#define LOG_EVENT_MINIMAL_HEADER_LEN 19
+
+/* event-specific post-header sizes */
+// where 3.23, 4.x and 5.0 agree
+#define QUERY_HEADER_MINIMAL_LEN (4 + 4 + 1 + 2)
+// where 5.0 differs: 2 for len of N-bytes vars.
+#define QUERY_HEADER_LEN (QUERY_HEADER_MINIMAL_LEN + 2)
+#define STOP_HEADER_LEN 0
+#define LOAD_HEADER_LEN (4 + 4 + 4 + 1 +1 + 4)
+#define SLAVE_HEADER_LEN 0
+#define START_V3_HEADER_LEN (2 + ST_SERVER_VER_LEN + 4)
+#define ROTATE_HEADER_LEN 8 // this is FROZEN (the Rotate post-header is frozen)
+#define INTVAR_HEADER_LEN 0
+#define CREATE_FILE_HEADER_LEN 4
+#define APPEND_BLOCK_HEADER_LEN 4
+#define EXEC_LOAD_HEADER_LEN 4
+#define DELETE_FILE_HEADER_LEN 4
+#define NEW_LOAD_HEADER_LEN LOAD_HEADER_LEN
+#define RAND_HEADER_LEN 0
+#define USER_VAR_HEADER_LEN 0
+#define FORMAT_DESCRIPTION_HEADER_LEN (START_V3_HEADER_LEN+1+LOG_EVENT_TYPES)
+#define XID_HEADER_LEN 0
+#define BEGIN_LOAD_QUERY_HEADER_LEN APPEND_BLOCK_HEADER_LEN
+#define ROWS_HEADER_LEN_V1 8
+#define TABLE_MAP_HEADER_LEN 8
+#define EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN (4 + 4 + 4 + 1)
+#define EXECUTE_LOAD_QUERY_HEADER_LEN (QUERY_HEADER_LEN + EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN)
+#define INCIDENT_HEADER_LEN 2
+#define HEARTBEAT_HEADER_LEN 0
+#define ROWS_HEADER_LEN_V2 10
+#define ANNOTATE_ROWS_HEADER_LEN 0
+#define BINLOG_CHECKPOINT_HEADER_LEN 4
+#define GTID_HEADER_LEN 19
+#define GTID_LIST_HEADER_LEN 4
+
+/*
+ Max number of possible extra bytes in a replication event compared to a
+ packet (i.e. a query) sent from client to master;
+ First, an auxiliary log_event status vars estimation:
+*/
+#define MAX_SIZE_LOG_EVENT_STATUS (1 + 4 /* type, flags2 */ + \
+ 1 + 8 /* type, sql_mode */ + \
+ 1 + 1 + 255 /* type, length, catalog */ + \
+ 1 + 4 /* type, auto_increment */ + \
+ 1 + 6 /* type, charset */ + \
+ 1 + 1 + 255 /* type, length, time_zone */ + \
+ 1 + 2 /* type, lc_time_names_number */ + \
+ 1 + 2 /* type, charset_database_number */ + \
+ 1 + 8 /* type, table_map_for_update */ + \
+ 1 + 4 /* type, master_data_written */ + \
+ 1 + 3 /* type, sec_part of NOW() */ + \
+ 1 + 16 + 1 + 60/* type, user_len, user, host_len, host */)
+#define MAX_LOG_EVENT_HEADER ( /* in order of Query_log_event::write */ \
+ LOG_EVENT_HEADER_LEN + /* write_header */ \
+ QUERY_HEADER_LEN + /* write_data */ \
+ EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN + /*write_post_header_for_derived */ \
+ MAX_SIZE_LOG_EVENT_STATUS + /* status */ \
+ NAME_LEN + 1)
+
+/*
+ The new option is added to handle large packets that are sent from the master
+ to the slave. It is used to increase the thd(max_allowed) for both the
+ DUMP thread on the master and the SQL/IO thread on the slave.
+*/
+#define MAX_MAX_ALLOWED_PACKET 1024*1024*1024
+
+/*
+ Event header offsets;
+ these point to places inside the fixed header.
+*/
+
+#define EVENT_TYPE_OFFSET 4
+#define SERVER_ID_OFFSET 5
+#define EVENT_LEN_OFFSET 9
+#define LOG_POS_OFFSET 13
+#define FLAGS_OFFSET 17
+
+/* start event post-header (for v3 and v4) */
+
+#define ST_BINLOG_VER_OFFSET 0
+#define ST_SERVER_VER_OFFSET 2
+#define ST_CREATED_OFFSET (ST_SERVER_VER_OFFSET + ST_SERVER_VER_LEN)
+#define ST_COMMON_HEADER_LEN_OFFSET (ST_CREATED_OFFSET + 4)
+
+/* slave event post-header (this event is never written) */
+
+#define SL_MASTER_PORT_OFFSET 8
+#define SL_MASTER_POS_OFFSET 0
+#define SL_MASTER_HOST_OFFSET 10
+
+/* query event post-header */
+
+#define Q_THREAD_ID_OFFSET 0
+#define Q_EXEC_TIME_OFFSET 4
+#define Q_DB_LEN_OFFSET 8
+#define Q_ERR_CODE_OFFSET 9
+#define Q_STATUS_VARS_LEN_OFFSET 11
+#define Q_DATA_OFFSET QUERY_HEADER_LEN
+/* these are codes, not offsets; not more than 256 values (1 byte). */
+#define Q_FLAGS2_CODE 0
+#define Q_SQL_MODE_CODE 1
+/*
+ Q_CATALOG_CODE is catalog with end zero stored; it is used only by MySQL
+ 5.0.x where 0<=x<=3. We have to keep it to be able to replicate these
+ old masters.
+*/
+#define Q_CATALOG_CODE 2
+#define Q_AUTO_INCREMENT 3
+#define Q_CHARSET_CODE 4
+#define Q_TIME_ZONE_CODE 5
+/*
+ Q_CATALOG_NZ_CODE is catalog withOUT end zero stored; it is used by MySQL
+ 5.0.x where x>=4. Saves one byte in every Query_log_event in binlog,
+ compared to Q_CATALOG_CODE. The reason we didn't simply re-use
+ Q_CATALOG_CODE is that then a 5.0.3 slave of this 5.0.x (x>=4) master would
+ crash (segfault etc) because it would expect a 0 when there is none.
+*/
+#define Q_CATALOG_NZ_CODE 6
+
+#define Q_LC_TIME_NAMES_CODE 7
+
+#define Q_CHARSET_DATABASE_CODE 8
+
+#define Q_TABLE_MAP_FOR_UPDATE_CODE 9
+
+#define Q_MASTER_DATA_WRITTEN_CODE 10
+
+#define Q_INVOKER 11
+
+#define Q_HRNOW 128
+
+/* Intvar event post-header */
+
+/* Intvar event data */
+#define I_TYPE_OFFSET 0
+#define I_VAL_OFFSET 1
+
+/* Rand event data */
+#define RAND_SEED1_OFFSET 0
+#define RAND_SEED2_OFFSET 8
+
+/* User_var event data */
+#define UV_VAL_LEN_SIZE 4
+#define UV_VAL_IS_NULL 1
+#define UV_VAL_TYPE_SIZE 1
+#define UV_NAME_LEN_SIZE 4
+#define UV_CHARSET_NUMBER_SIZE 4
+
+/* Load event post-header */
+#define L_THREAD_ID_OFFSET 0
+#define L_EXEC_TIME_OFFSET 4
+#define L_SKIP_LINES_OFFSET 8
+#define L_TBL_LEN_OFFSET 12
+#define L_DB_LEN_OFFSET 13
+#define L_NUM_FIELDS_OFFSET 14
+#define L_SQL_EX_OFFSET 18
+#define L_DATA_OFFSET LOAD_HEADER_LEN
+
+/* Rotate event post-header */
+#define R_POS_OFFSET 0
+#define R_IDENT_OFFSET 8
+
+/* CF to DF handle LOAD DATA INFILE */
+
+/* CF = "Create File" */
+#define CF_FILE_ID_OFFSET 0
+#define CF_DATA_OFFSET CREATE_FILE_HEADER_LEN
+
+/* AB = "Append Block" */
+#define AB_FILE_ID_OFFSET 0
+#define AB_DATA_OFFSET APPEND_BLOCK_HEADER_LEN
+
+/* EL = "Execute Load" */
+#define EL_FILE_ID_OFFSET 0
+
+/* DF = "Delete File" */
+#define DF_FILE_ID_OFFSET 0
+
+/* TM = "Table Map" */
+#define TM_MAPID_OFFSET 0
+#define TM_FLAGS_OFFSET 6
+
+/* RW = "RoWs" */
+#define RW_MAPID_OFFSET 0
+#define RW_FLAGS_OFFSET 6
+#define RW_VHLEN_OFFSET 8
+#define RW_V_TAG_LEN 1
+#define RW_V_EXTRAINFO_TAG 0
+
+/* ELQ = "Execute Load Query" */
+#define ELQ_FILE_ID_OFFSET QUERY_HEADER_LEN
+#define ELQ_FN_POS_START_OFFSET ELQ_FILE_ID_OFFSET + 4
+#define ELQ_FN_POS_END_OFFSET ELQ_FILE_ID_OFFSET + 8
+#define ELQ_DUP_HANDLING_OFFSET ELQ_FILE_ID_OFFSET + 12
+
+/* 4 bytes which all binlogs should begin with */
+#define BINLOG_MAGIC (const uchar*) "\xfe\x62\x69\x6e"
+
+/*
+ The 2 flags below were useless :
+ - the first one was never set
+ - the second one was set in all Rotate events on the master, but not used for
+ anything useful.
+ So they are now removed and their place may later be reused for other
+ flags. Then one must remember that Rotate events in 4.x have
+ LOG_EVENT_FORCED_ROTATE_F set, so one should not rely on the value of the
+ replacing flag when reading a Rotate event.
+ I keep the defines here just to remember what they were.
+*/
+#ifdef TO_BE_REMOVED
+#define LOG_EVENT_TIME_F 0x1
+#define LOG_EVENT_FORCED_ROTATE_F 0x2
+#endif
+
+/*
+ This flag only makes sense for Format_description_log_event. It is set
+ when the event is written, and *reset* when a binlog file is
+ closed (yes, it's the only case when MySQL modifies already written
+ part of binlog). Thus it is a reliable indicator that binlog was
+ closed correctly. (Stop_log_event is not enough, there's always a
+ small chance that mysqld crashes in the middle of insert and end of
+ the binlog would look like a Stop_log_event).
+
+ This flag is used to detect a restart after a crash, and to provide
+ "unbreakable" binlog. The problem is that on a crash storage engines
+ rollback automatically, while binlog does not. To solve this we use this
+ flag and automatically append ROLLBACK to every non-closed binlog (append
+ virtually, on reading, file itself is not changed). If this flag is found,
+ mysqlbinlog simply prints "ROLLBACK" Replication master does not abort on
+ binlog corruption, but takes it as EOF, and replication slave forces a
+ rollback in this case.
+
+ Note, that old binlogs does not have this flag set, so we get a
+ a backward-compatible behaviour.
+*/
+
+#define LOG_EVENT_BINLOG_IN_USE_F 0x1
+
+/**
+ @def LOG_EVENT_THREAD_SPECIFIC_F
+
+ If the query depends on the thread (for example: TEMPORARY TABLE).
+ Currently this is used by mysqlbinlog to know it must print
+ SET @@PSEUDO_THREAD_ID=xx; before the query (it would not hurt to print it
+ for every query but this would be slow).
+*/
+#define LOG_EVENT_THREAD_SPECIFIC_F 0x4
+
+/**
+ @def LOG_EVENT_SUPPRESS_USE_F
+
+ Suppress the generation of 'USE' statements before the actual
+ statement. This flag should be set for any events that does not need
+ the current database set to function correctly. Most notable cases
+ are 'CREATE DATABASE' and 'DROP DATABASE'.
+
+ This flags should only be used in exceptional circumstances, since
+ it introduce a significant change in behaviour regarding the
+ replication logic together with the flags --binlog-do-db and
+ --replicated-do-db.
+ */
+#define LOG_EVENT_SUPPRESS_USE_F 0x8
+
+/*
+ Note: this is a place holder for the flag
+ LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F (0x10), which is not used any
+ more, please do not reused this value for other flags.
+ */
+
+/**
+ @def LOG_EVENT_ARTIFICIAL_F
+
+ Artificial events are created arbitarily and not written to binary
+ log
+
+ These events should not update the master log position when slave
+ SQL thread executes them.
+*/
+#define LOG_EVENT_ARTIFICIAL_F 0x20
+
+/**
+ @def LOG_EVENT_RELAY_LOG_F
+
+ Events with this flag set are created by slave IO thread and written
+ to relay log
+*/
+#define LOG_EVENT_RELAY_LOG_F 0x40
+
+/**
+ @def LOG_EVENT_SKIP_REPLICATION_F
+
+ Flag set by application creating the event (with @@skip_replication); the
+ slave will skip replication of such events if
+ --replicate-events-marked-for-skip is not set to REPLICATE.
+
+ This is a MariaDB flag; we allocate it from the end of the available
+ values to reduce risk of conflict with new MySQL flags.
+*/
+#define LOG_EVENT_SKIP_REPLICATION_F 0x8000
+
+
+/**
+ @def OPTIONS_WRITTEN_TO_BIN_LOG
+
+ OPTIONS_WRITTEN_TO_BIN_LOG are the bits of thd->options which must
+ be written to the binlog. OPTIONS_WRITTEN_TO_BIN_LOG could be
+ written into the Format_description_log_event, so that if later we
+ don't want to replicate a variable we did replicate, or the
+ contrary, it's doable. But it should not be too hard to decide once
+ for all of what we replicate and what we don't, among the fixed 32
+ bits of thd->options.
+
+ I (Guilhem) have read through every option's usage, and it looks
+ like OPTION_AUTO_IS_NULL and OPTION_NO_FOREIGN_KEYS are the only
+ ones which alter how the query modifies the table. It's good to
+ replicate OPTION_RELAXED_UNIQUE_CHECKS too because otherwise, the
+ slave may insert data slower than the master, in InnoDB.
+ OPTION_BIG_SELECTS is not needed (the slave thread runs with
+ max_join_size=HA_POS_ERROR) and OPTION_BIG_TABLES is not needed
+ either, as the manual says (because a too big in-memory temp table
+ is automatically written to disk).
+*/
+#define OPTIONS_WRITTEN_TO_BIN_LOG \
+ (OPTION_AUTO_IS_NULL | OPTION_NO_FOREIGN_KEY_CHECKS | \
+ OPTION_RELAXED_UNIQUE_CHECKS | OPTION_NOT_AUTOCOMMIT)
+
+/* Shouldn't be defined before */
+#define EXPECTED_OPTIONS \
+ ((1ULL << 14) | (1ULL << 26) | (1ULL << 27) | (1ULL << 19))
+
+#if OPTIONS_WRITTEN_TO_BIN_LOG != EXPECTED_OPTIONS
+#error OPTIONS_WRITTEN_TO_BIN_LOG must NOT change their values!
+#endif
+#undef EXPECTED_OPTIONS /* You shouldn't use this one */
+
+enum enum_binlog_checksum_alg {
+ BINLOG_CHECKSUM_ALG_OFF= 0, // Events are without checksum though its generator
+ // is checksum-capable New Master (NM).
+ BINLOG_CHECKSUM_ALG_CRC32= 1, // CRC32 of zlib algorithm.
+ BINLOG_CHECKSUM_ALG_ENUM_END, // the cut line: valid alg range is [1, 0x7f].
+ BINLOG_CHECKSUM_ALG_UNDEF= 255 // special value to tag undetermined yet checksum
+ // or events from checksum-unaware servers
+};
+
+#define CHECKSUM_CRC32_SIGNATURE_LEN 4
+/**
+ defined statically while there is just one alg implemented
+*/
+#define BINLOG_CHECKSUM_LEN CHECKSUM_CRC32_SIGNATURE_LEN
+#define BINLOG_CHECKSUM_ALG_DESC_LEN 1 /* 1 byte checksum alg descriptor */
+
+/*
+ These are capability numbers for MariaDB slave servers.
+
+ Newer MariaDB slaves set this to inform the master about their capabilities.
+ This allows the master to decide which events it can send to the slave
+ without breaking replication on old slaves that maybe do not understand
+ all events from newer masters.
+
+ As new releases are backwards compatible, a given capability implies also
+ all capabilities with smaller number.
+
+ Older MariaDB slaves and other MySQL slave servers do not set this, so they
+ are recorded with capability 0.
+*/
+
+/* MySQL or old MariaDB slave with no announced capability. */
+#define MARIA_SLAVE_CAPABILITY_UNKNOWN 0
+/* MariaDB >= 5.3, which understands ANNOTATE_ROWS_EVENT. */
+#define MARIA_SLAVE_CAPABILITY_ANNOTATE 1
+/*
+ MariaDB >= 5.5. This version has the capability to tolerate events omitted
+ from the binlog stream without breaking replication (MySQL slaves fail
+ because they mis-compute the offsets into the master's binlog).
+*/
+#define MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES 2
+/* MariaDB >= 10.0, which knows about binlog_checkpoint_log_event. */
+#define MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT 3
+/* MariaDB >= 10.0.1, which knows about global transaction id events. */
+#define MARIA_SLAVE_CAPABILITY_GTID 4
+
+/* Our capability. */
+#define MARIA_SLAVE_CAPABILITY_MINE MARIA_SLAVE_CAPABILITY_GTID
+
+
+/**
+ @enum Log_event_type
+
+ Enumeration type for the different types of log events.
+*/
+enum Log_event_type
+{
+ /*
+ Every time you update this enum (when you add a type), you have to
+ fix Format_description_log_event::Format_description_log_event().
+ */
+ UNKNOWN_EVENT= 0,
+ START_EVENT_V3= 1,
+ QUERY_EVENT= 2,
+ STOP_EVENT= 3,
+ ROTATE_EVENT= 4,
+ INTVAR_EVENT= 5,
+ LOAD_EVENT= 6,
+ SLAVE_EVENT= 7,
+ CREATE_FILE_EVENT= 8,
+ APPEND_BLOCK_EVENT= 9,
+ EXEC_LOAD_EVENT= 10,
+ DELETE_FILE_EVENT= 11,
+ /*
+ NEW_LOAD_EVENT is like LOAD_EVENT except that it has a longer
+ sql_ex, allowing multibyte TERMINATED BY etc; both types share the
+ same class (Load_log_event)
+ */
+ NEW_LOAD_EVENT= 12,
+ RAND_EVENT= 13,
+ USER_VAR_EVENT= 14,
+ FORMAT_DESCRIPTION_EVENT= 15,
+ XID_EVENT= 16,
+ BEGIN_LOAD_QUERY_EVENT= 17,
+ EXECUTE_LOAD_QUERY_EVENT= 18,
+
+ TABLE_MAP_EVENT = 19,
+
+ /*
+ These event numbers were used for 5.1.0 to 5.1.15 and are
+ therefore obsolete.
+ */
+ PRE_GA_WRITE_ROWS_EVENT = 20,
+ PRE_GA_UPDATE_ROWS_EVENT = 21,
+ PRE_GA_DELETE_ROWS_EVENT = 22,
+
+ /*
+ These event numbers are used from 5.1.16 until mysql-5.6.6,
+ and in MariaDB
+ */
+ WRITE_ROWS_EVENT_V1 = 23,
+ UPDATE_ROWS_EVENT_V1 = 24,
+ DELETE_ROWS_EVENT_V1 = 25,
+
+ /*
+ Something out of the ordinary happened on the master
+ */
+ INCIDENT_EVENT= 26,
+
+ /*
+ Heartbeat event to be send by master at its idle time
+ to ensure master's online status to slave
+ */
+ HEARTBEAT_LOG_EVENT= 27,
+
+ /*
+ In some situations, it is necessary to send over ignorable
+ data to the slave: data that a slave can handle in case there
+ is code for handling it, but which can be ignored if it is not
+ recognized.
+
+ These mysql-5.6 events are not recognized (and ignored) by MariaDB
+ */
+ IGNORABLE_LOG_EVENT= 28,
+ ROWS_QUERY_LOG_EVENT= 29,
+
+ /* Version 2 of the Row events, generated only by mysql-5.6.6+ */
+ WRITE_ROWS_EVENT = 30,
+ UPDATE_ROWS_EVENT = 31,
+ DELETE_ROWS_EVENT = 32,
+
+ /*
+ Add new events here - right above this comment!
+ Existing events (except ENUM_END_EVENT) should never change their numbers
+ */
+
+ /* New MySQL/Sun events are to be added right above this comment */
+ MYSQL_EVENTS_END,
+
+ MARIA_EVENTS_BEGIN= 160,
+ /* New Maria event numbers start from here */
+ ANNOTATE_ROWS_EVENT= 160,
+ /*
+ Binlog checkpoint event. Used for XA crash recovery on the master, not used
+ in replication.
+ A binlog checkpoint event specifies a binlog file such that XA crash
+ recovery can start from that file - and it is guaranteed to find all XIDs
+ that are prepared in storage engines but not yet committed.
+ */
+ BINLOG_CHECKPOINT_EVENT= 161,
+ /*
+ Gtid event. For global transaction ID, used to start a new event group,
+ instead of the old BEGIN query event, and also to mark stand-alone
+ events.
+ */
+ GTID_EVENT= 162,
+ /*
+ Gtid list event. Logged at the start of every binlog, to record the
+ current replication state. This consists of the last GTID seen for
+ each replication domain.
+ */
+ GTID_LIST_EVENT= 163,
+
+ /* Add new MariaDB events here - right above this comment! */
+
+ ENUM_END_EVENT /* end marker */
+};
+
+/*
+ The number of types we handle in Format_description_log_event (UNKNOWN_EVENT
+ is not to be handled, it does not exist in binlogs, it does not have a
+ format).
+*/
+#define LOG_EVENT_TYPES (ENUM_END_EVENT-1)
+
+enum Int_event_type
+{
+ INVALID_INT_EVENT = 0, LAST_INSERT_ID_EVENT = 1, INSERT_ID_EVENT = 2
+};
+
+
+#ifdef MYSQL_SERVER
+class String;
+class MYSQL_BIN_LOG;
+class THD;
+#endif
+
+class Format_description_log_event;
+class Relay_log_info;
+
+#ifdef MYSQL_CLIENT
+enum enum_base64_output_mode {
+ BASE64_OUTPUT_NEVER= 0,
+ BASE64_OUTPUT_AUTO= 1,
+ BASE64_OUTPUT_ALWAYS= 2,
+ BASE64_OUTPUT_UNSPEC= 3,
+ BASE64_OUTPUT_DECODE_ROWS= 4,
+ /* insert new output modes here */
+ BASE64_OUTPUT_MODE_COUNT
+};
+
+/*
+ A structure for mysqlbinlog to know how to print events
+
+ This structure is passed to the event's print() methods,
+
+ There are two types of settings stored here:
+ 1. Last db, flags2, sql_mode etc comes from the last printed event.
+ They are stored so that only the necessary USE and SET commands
+ are printed.
+ 2. Other information on how to print the events, e.g. short_form,
+ hexdump_from. These are not dependent on the last event.
+*/
+typedef struct st_print_event_info
+{
+ /*
+ Settings for database, sql_mode etc that comes from the last event
+ that was printed. We cache these so that we don't have to print
+ them if they are unchanged.
+ */
+ // TODO: have the last catalog here ??
+ char db[FN_REFLEN+1]; // TODO: make this a LEX_STRING when thd->db is
+ bool flags2_inited;
+ uint32 flags2;
+ bool sql_mode_inited;
+ ulonglong sql_mode; /* must be same as THD.variables.sql_mode */
+ ulong auto_increment_increment, auto_increment_offset;
+ bool charset_inited;
+ char charset[6]; // 3 variables, each of them storable in 2 bytes
+ char time_zone_str[MAX_TIME_ZONE_NAME_LENGTH];
+ uint lc_time_names_number;
+ uint charset_database_number;
+ uint thread_id;
+ bool thread_id_printed;
+ uint32 server_id;
+ bool server_id_printed;
+ uint32 domain_id;
+ bool domain_id_printed;
+
+ /*
+ Track when @@skip_replication changes so we need to output a SET
+ statement for it.
+ */
+ int skip_replication;
+
+ st_print_event_info();
+
+ ~st_print_event_info() {
+ close_cached_file(&head_cache);
+ close_cached_file(&body_cache);
+ }
+ bool init_ok() /* tells if construction was successful */
+ { return my_b_inited(&head_cache) && my_b_inited(&body_cache); }
+
+
+ /* Settings on how to print the events */
+ bool short_form;
+ enum_base64_output_mode base64_output_mode;
+ /*
+ This is set whenever a Format_description_event is printed.
+ Later, when an event is printed in base64, this flag is tested: if
+ no Format_description_event has been seen, it is unsafe to print
+ the base64 event, so an error message is generated.
+ */
+ bool printed_fd_event;
+ my_off_t hexdump_from;
+ uint8 common_header_len;
+ char delimiter[16];
+
+ uint verbose;
+ table_mapping m_table_map;
+ table_mapping m_table_map_ignored;
+
+ /*
+ These two caches are used by the row-based replication events to
+ collect the header information and the main body of the events
+ making up a statement.
+ */
+ IO_CACHE head_cache;
+ IO_CACHE body_cache;
+} PRINT_EVENT_INFO;
+#endif
+
+/**
+ the struct aggregates two paramenters that identify an event
+ uniquely in scope of communication of a particular master and slave couple.
+ I.e there can not be 2 events from the same staying connected master which
+ have the same coordinates.
+ @note
+ Such identifier is not yet unique generally as the event originating master
+ is resetable. Also the crashed master can be replaced with some other.
+*/
+typedef struct event_coordinates
+{
+ char * file_name; // binlog file name (directories stripped)
+ my_off_t pos; // event's position in the binlog file
+} LOG_POS_COORD;
+
+/**
+ @class Log_event
+
+ This is the abstract base class for binary log events.
+
+ @section Log_event_binary_format Binary Format
+
+ Any @c Log_event saved on disk consists of the following three
+ components.
+
+ - Common-Header
+ - Post-Header
+ - Body
+
+ The Common-Header, documented in the table @ref Table_common_header
+ "below", always has the same form and length within one version of
+ MySQL. Each event type specifies a format and length of the
+ Post-Header. The length of the Common-Header is the same for all
+ events of the same type. The Body may be of different format and
+ length even for different events of the same type. The binary
+ formats of Post-Header and Body are documented separately in each
+ subclass. The binary format of Common-Header is as follows.
+
+ <table>
+ <caption>Common-Header</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>timestamp</td>
+ <td>4 byte unsigned integer</td>
+ <td>The time when the query started, in seconds since 1970.
+ </td>
+ </tr>
+
+ <tr>
+ <td>type</td>
+ <td>1 byte enumeration</td>
+ <td>See enum #Log_event_type.</td>
+ </tr>
+
+ <tr>
+ <td>server_id</td>
+ <td>4 byte unsigned integer</td>
+ <td>Server ID of the server that created the event.</td>
+ </tr>
+
+ <tr>
+ <td>total_size</td>
+ <td>4 byte unsigned integer</td>
+ <td>The total size of this event, in bytes. In other words, this
+ is the sum of the sizes of Common-Header, Post-Header, and Body.
+ </td>
+ </tr>
+
+ <tr>
+ <td>master_position</td>
+ <td>4 byte unsigned integer</td>
+ <td>The position of the next event in the master binary log, in
+ bytes from the beginning of the file. In a binlog that is not a
+ relay log, this is just the position of the next event, in bytes
+ from the beginning of the file. In a relay log, this is
+ the position of the next event in the master's binlog.
+ </td>
+ </tr>
+
+ <tr>
+ <td>flags</td>
+ <td>2 byte bitfield</td>
+ <td>See Log_event::flags.</td>
+ </tr>
+ </table>
+
+ Summing up the numbers above, we see that the total size of the
+ common header is 19 bytes.
+
+ @subsection Log_event_format_of_atomic_primitives Format of Atomic Primitives
+
+ - All numbers, whether they are 16-, 24-, 32-, or 64-bit numbers,
+ are stored in little endian, i.e., the least significant byte first,
+ unless otherwise specified.
+
+ @anchor packed_integer
+ - Some events use a special format for efficient representation of
+ unsigned integers, called Packed Integer. A Packed Integer has the
+ capacity of storing up to 8-byte integers, while small integers
+ still can use 1, 3, or 4 bytes. The value of the first byte
+ determines how to read the number, according to the following table:
+
+ <table>
+ <caption>Format of Packed Integer</caption>
+
+ <tr>
+ <th>First byte</th>
+ <th>Format</th>
+ </tr>
+
+ <tr>
+ <td>0-250</td>
+ <td>The first byte is the number (in the range 0-250), and no more
+ bytes are used.</td>
+ </tr>
+
+ <tr>
+ <td>252</td>
+ <td>Two more bytes are used. The number is in the range
+ 251-0xffff.</td>
+ </tr>
+
+ <tr>
+ <td>253</td>
+ <td>Three more bytes are used. The number is in the range
+ 0xffff-0xffffff.</td>
+ </tr>
+
+ <tr>
+ <td>254</td>
+ <td>Eight more bytes are used. The number is in the range
+ 0xffffff-0xffffffffffffffff.</td>
+ </tr>
+
+ </table>
+
+ - Strings are stored in various formats. The format of each string
+ is documented separately.
+*/
+class Log_event
+{
+public:
+ /**
+ Enumeration of what kinds of skipping (and non-skipping) that can
+ occur when the slave executes an event.
+
+ @see shall_skip
+ @see do_shall_skip
+ */
+ enum enum_skip_reason {
+ /**
+ Don't skip event.
+ */
+ EVENT_SKIP_NOT,
+
+ /**
+ Skip event by ignoring it.
+
+ This means that the slave skip counter will not be changed.
+ */
+ EVENT_SKIP_IGNORE,
+
+ /**
+ Skip event and decrease skip counter.
+ */
+ EVENT_SKIP_COUNT
+ };
+
+ enum enum_event_cache_type
+ {
+ EVENT_INVALID_CACHE,
+ /*
+ If possible the event should use a non-transactional cache before
+ being flushed to the binary log. This means that it must be flushed
+ right after its correspondent statement is completed.
+ */
+ EVENT_STMT_CACHE,
+ /*
+ The event should use a transactional cache before being flushed to
+ the binary log. This means that it must be flushed upon commit or
+ rollback.
+ */
+ EVENT_TRANSACTIONAL_CACHE,
+ /*
+ The event must be written directly to the binary log without going
+ through a cache.
+ */
+ EVENT_NO_CACHE,
+ /**
+ If there is a need for different types, introduce them before this.
+ */
+ EVENT_CACHE_COUNT
+ };
+
+ /*
+ The following type definition is to be used whenever data is placed
+ and manipulated in a common buffer. Use this typedef for buffers
+ that contain data containing binary and character data.
+ */
+ typedef unsigned char Byte;
+
+ /*
+ The offset in the log where this event originally appeared (it is
+ preserved in relay logs, making SHOW SLAVE STATUS able to print
+ coordinates of the event in the master's binlog). Note: when a
+ transaction is written by the master to its binlog (wrapped in
+ BEGIN/COMMIT) the log_pos of all the queries it contains is the
+ one of the BEGIN (this way, when one does SHOW SLAVE STATUS it
+ sees the offset of the BEGIN, which is logical as rollback may
+ occur), except the COMMIT query which has its real offset.
+ */
+ my_off_t log_pos;
+ /*
+ A temp buffer for read_log_event; it is later analysed according to the
+ event's type, and its content is distributed in the event-specific fields.
+ */
+ char *temp_buf;
+
+ /*
+ TRUE <=> this event 'owns' temp_buf and should call my_free() when done
+ with it
+ */
+ bool event_owns_temp_buf;
+
+ /*
+ Timestamp on the master(for debugging and replication of
+ NOW()/TIMESTAMP). It is important for queries and LOAD DATA
+ INFILE. This is set at the event's creation time, except for Query
+ and Load (et al.) events where this is set at the query's
+ execution time, which guarantees good replication (otherwise, we
+ could have a query and its event with different timestamps).
+ */
+ my_time_t when;
+ ulong when_sec_part;
+ /* The number of seconds the query took to run on the master. */
+ ulong exec_time;
+ /* Number of bytes written by write() function */
+ ulong data_written;
+
+ /*
+ The master's server id (is preserved in the relay log; used to
+ prevent from infinite loops in circular replication).
+ */
+ uint32 server_id;
+
+ /**
+ Some 16 flags. See the definitions above for LOG_EVENT_TIME_F,
+ LOG_EVENT_FORCED_ROTATE_F, LOG_EVENT_THREAD_SPECIFIC_F,
+ LOG_EVENT_SUPPRESS_USE_F, and LOG_EVENT_SKIP_REPLICATION_F for notes.
+ */
+ uint16 flags;
+
+ uint16 cache_type;
+
+ /**
+ A storage to cache the global system variable's value.
+ Handling of a separate event will be governed its member.
+ */
+ ulong slave_exec_mode;
+
+ /**
+ Placeholder for event checksum while writing to binlog.
+ */
+ ha_checksum crc;
+
+#ifdef MYSQL_SERVER
+ THD* thd;
+
+ Log_event();
+ Log_event(THD* thd_arg, uint16 flags_arg, bool is_transactional);
+ /*
+ read_log_event() functions read an event from a binlog or relay
+ log; used by SHOW BINLOG EVENTS, the binlog_dump thread on the
+ master (reads master's binlog), the slave IO thread (reads the
+ event sent by binlog_dump), the slave SQL thread (reads the event
+ from the relay log). If mutex is 0, the read will proceed without
+ mutex. We need the description_event to be able to parse the
+ event (to know the post-header's size); in fact in read_log_event
+ we detect the event's type, then call the specific event's
+ constructor and pass description_event as an argument.
+ */
+ static Log_event* read_log_event(IO_CACHE* file,
+ mysql_mutex_t* log_lock,
+ const Format_description_log_event
+ *description_event,
+ my_bool crc_check);
+
+ /**
+ Reads an event from a binlog or relay log. Used by the dump thread
+ this method reads the event into a raw buffer without parsing it.
+
+ @Note If mutex is 0, the read will proceed without mutex.
+
+ @Note If a log name is given than the method will check if the
+ given binlog is still active.
+
+ @param[in] file log file to be read
+ @param[out] packet packet to hold the event
+ @param[in] lock the lock to be used upon read
+ @param[in] log_file_name_arg the log's file name
+ @param[out] is_binlog_active is the current log still active
+
+ @retval 0 success
+ @retval LOG_READ_EOF end of file, nothing was read
+ @retval LOG_READ_BOGUS malformed event
+ @retval LOG_READ_IO io error while reading
+ @retval LOG_READ_MEM packet memory allocation failed
+ @retval LOG_READ_TRUNC only a partial event could be read
+ @retval LOG_READ_TOO_LARGE event too large
+ */
+ static int read_log_event(IO_CACHE* file, String* packet,
+ mysql_mutex_t* log_lock,
+ uint8 checksum_alg_arg,
+ const char *log_file_name_arg = NULL,
+ bool* is_binlog_active = NULL);
+ /*
+ init_show_field_list() prepares the column names and types for the
+ output of SHOW BINLOG EVENTS; it is used only by SHOW BINLOG
+ EVENTS.
+ */
+ static void init_show_field_list(List<Item>* field_list);
+#ifdef HAVE_REPLICATION
+ int net_send(THD *thd, Protocol *protocol, const char* log_name,
+ my_off_t pos);
+
+ /*
+ pack_info() is used by SHOW BINLOG EVENTS; as print() it prepares and sends
+ a string to display to the user, so it resembles print().
+ */
+
+ virtual void pack_info(THD *thd, Protocol *protocol);
+
+#endif /* HAVE_REPLICATION */
+ virtual const char* get_db()
+ {
+ return thd ? thd->db : 0;
+ }
+#else
+ Log_event() : temp_buf(0), flags(0) {}
+ /* avoid having to link mysqlbinlog against libpthread */
+ static Log_event* read_log_event(IO_CACHE* file,
+ const Format_description_log_event
+ *description_event, my_bool crc_check);
+ /* print*() functions are used by mysqlbinlog */
+ virtual void print(FILE* file, PRINT_EVENT_INFO* print_event_info) = 0;
+ void print_timestamp(IO_CACHE* file, time_t *ts = 0);
+ void print_header(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info,
+ bool is_more);
+ void print_base64(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info,
+ bool is_more);
+#endif
+ /*
+ The value is set by caller of FD constructor and
+ Log_event::write_header() for the rest.
+ In the FD case it's propagated into the last byte
+ of post_header_len[] at FD::write().
+ On the slave side the value is assigned from post_header_len[last]
+ of the last seen FD event.
+ */
+ uint8 checksum_alg;
+
+ static void *operator new(size_t size)
+ {
+ return (void*) my_malloc((uint)size, MYF(MY_WME|MY_FAE));
+ }
+
+ static void operator delete(void *ptr, size_t)
+ {
+ my_free(ptr);
+ }
+
+ /* Placement version of the above operators */
+ static void *operator new(size_t, void* ptr) { return ptr; }
+ static void operator delete(void*, void*) { }
+ bool wrapper_my_b_safe_write(IO_CACHE* file, const uchar* buf, ulong data_length);
+
+#ifdef MYSQL_SERVER
+ bool write_header(IO_CACHE* file, ulong data_length);
+ bool write_footer(IO_CACHE* file);
+ my_bool need_checksum();
+
+ virtual bool write(IO_CACHE* file)
+ {
+ return(write_header(file, get_data_size()) ||
+ write_data_header(file) ||
+ write_data_body(file) ||
+ write_footer(file));
+ }
+ virtual bool write_data_header(IO_CACHE* file)
+ { return 0; }
+ virtual bool write_data_body(IO_CACHE* file __attribute__((unused)))
+ { return 0; }
+ inline my_time_t get_time()
+ {
+ THD *tmp_thd;
+ if (when)
+ return when;
+ if (thd)
+ {
+ when= thd->start_time;
+ when_sec_part= thd->start_time_sec_part;
+ return when;
+ }
+ /* thd will only be 0 here at time of log creation */
+ if ((tmp_thd= current_thd))
+ {
+ when= tmp_thd->start_time;
+ when_sec_part= tmp_thd->start_time_sec_part;
+ return when;
+ }
+ my_hrtime_t hrtime= my_hrtime();
+ when= hrtime_to_my_time(hrtime);
+ when_sec_part= hrtime_sec_part(hrtime);
+ return when;
+ }
+#endif
+ virtual Log_event_type get_type_code() = 0;
+ virtual bool is_valid() const = 0;
+ virtual my_off_t get_header_len(my_off_t len) { return len; }
+ void set_artificial_event() { flags |= LOG_EVENT_ARTIFICIAL_F; }
+ void set_relay_log_event() { flags |= LOG_EVENT_RELAY_LOG_F; }
+ bool is_artificial_event() const { return flags & LOG_EVENT_ARTIFICIAL_F; }
+ bool is_relay_log_event() const { return flags & LOG_EVENT_RELAY_LOG_F; }
+ inline bool use_trans_cache() const
+ {
+ return (cache_type == Log_event::EVENT_TRANSACTIONAL_CACHE);
+ }
+ inline void set_direct_logging()
+ {
+ cache_type = Log_event::EVENT_NO_CACHE;
+ }
+ inline bool use_direct_logging()
+ {
+ return (cache_type == Log_event::EVENT_NO_CACHE);
+ }
+ Log_event(const char* buf, const Format_description_log_event
+ *description_event);
+ virtual ~Log_event() { free_temp_buf();}
+ void register_temp_buf(char* buf, bool must_free)
+ {
+ temp_buf= buf;
+ event_owns_temp_buf= must_free;
+ }
+ void free_temp_buf()
+ {
+ if (temp_buf)
+ {
+ if (event_owns_temp_buf)
+ my_free(temp_buf);
+ temp_buf = 0;
+ }
+ }
+ /*
+ Get event length for simple events. For complicated events the length
+ is calculated during write()
+ */
+ virtual int get_data_size() { return 0;}
+ static Log_event* read_log_event(const char* buf, uint event_len,
+ const char **error,
+ const Format_description_log_event
+ *description_event, my_bool crc_check);
+ /**
+ Returns the human readable name of the given event type.
+ */
+ static const char* get_type_str(Log_event_type type);
+ /**
+ Returns the human readable name of this event's type.
+ */
+ const char* get_type_str();
+
+ /* Return start of query time or current time */
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+public:
+
+ /**
+ Apply the event to the database.
+
+ This function represents the public interface for applying an
+ event.
+
+ @see do_apply_event
+ */
+ int apply_event(rpl_group_info *rgi)
+ {
+ return do_apply_event(rgi);
+ }
+
+
+ /**
+ Update the relay log position.
+
+ This function represents the public interface for "stepping over"
+ the event and will update the relay log information.
+
+ @see do_update_pos
+ */
+ int update_pos(rpl_group_info *rgi)
+ {
+ return do_update_pos(rgi);
+ }
+
+ /**
+ Decide if the event shall be skipped, and the reason for skipping
+ it.
+
+ @see do_shall_skip
+ */
+ enum_skip_reason shall_skip(rpl_group_info *rgi)
+ {
+ return do_shall_skip(rgi);
+ }
+
+
+ /*
+ Check if an event is non-final part of a stand-alone event group,
+ such as Intvar_log_event (such events should be processed as part
+ of the following event group, not individually).
+ See also is_part_of_group()
+ */
+ static bool is_part_of_group(enum Log_event_type ev_type)
+ {
+ switch (ev_type)
+ {
+ case GTID_EVENT:
+ case INTVAR_EVENT:
+ case RAND_EVENT:
+ case USER_VAR_EVENT:
+ case TABLE_MAP_EVENT:
+ case ANNOTATE_ROWS_EVENT:
+ return true;
+ case DELETE_ROWS_EVENT:
+ case UPDATE_ROWS_EVENT:
+ case WRITE_ROWS_EVENT:
+ /*
+ ToDo: also check for non-final Rows_log_event (though such events
+ are usually in a BEGIN-COMMIT group).
+ */
+ default:
+ return false;
+ }
+ }
+ /*
+ Same as above, but works on the object. In addition this is true for all
+ rows event except the last one.
+ */
+ virtual bool is_part_of_group() { return 0; }
+
+ static bool is_group_event(enum Log_event_type ev_type)
+ {
+ switch (ev_type)
+ {
+ case START_EVENT_V3:
+ case STOP_EVENT:
+ case ROTATE_EVENT:
+ case SLAVE_EVENT:
+ case FORMAT_DESCRIPTION_EVENT:
+ case INCIDENT_EVENT:
+ case HEARTBEAT_LOG_EVENT:
+ case BINLOG_CHECKPOINT_EVENT:
+ case GTID_LIST_EVENT:
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+protected:
+
+ /**
+ Helper function to ignore an event w.r.t. the slave skip counter.
+
+ This function can be used inside do_shall_skip() for functions
+ that cannot end a group. If the slave skip counter is 1 when
+ seeing such an event, the event shall be ignored, the counter
+ left intact, and processing continue with the next event.
+
+ A typical usage is:
+ @code
+ enum_skip_reason do_shall_skip(rpl_group_info *rgi) {
+ return continue_group(rgi);
+ }
+ @endcode
+
+ @return Skip reason
+ */
+ enum_skip_reason continue_group(rpl_group_info *rgi);
+
+ /**
+ Primitive to apply an event to the database.
+
+ This is where the change to the database is made.
+
+ @note The primitive is protected instead of private, since there
+ is a hierarchy of actions to be performed in some cases.
+
+ @see Format_description_log_event::do_apply_event()
+
+ @param rli Pointer to relay log info structure
+
+ @retval 0 Event applied successfully
+ @retval errno Error code if event application failed
+ */
+ virtual int do_apply_event(rpl_group_info *rgi)
+ {
+ return 0; /* Default implementation does nothing */
+ }
+
+
+ /**
+ Advance relay log coordinates.
+
+ This function is called to advance the relay log coordinates to
+ just after the event. It is essential that both the relay log
+ coordinate and the group log position is updated correctly, since
+ this function is used also for skipping events.
+
+ Normally, each implementation of do_update_pos() shall:
+
+ - Update the event position to refer to the position just after
+ the event.
+
+ - Update the group log position to refer to the position just
+ after the event <em>if the event is last in a group</em>
+
+ @param rli Pointer to relay log info structure
+
+ @retval 0 Coordinates changed successfully
+ @retval errno Error code if advancing failed (usually just
+ 1). Observe that handler errors are returned by the
+ do_apply_event() function, and not by this one.
+ */
+ virtual int do_update_pos(rpl_group_info *rgi);
+
+
+ /**
+ Decide if this event shall be skipped or not and the reason for
+ skipping it.
+
+ The default implementation decide that the event shall be skipped
+ if either:
+
+ - the server id of the event is the same as the server id of the
+ server and <code>rli->replicate_same_server_id</code> is true,
+ or
+
+ - if <code>rli->slave_skip_counter</code> is greater than zero.
+
+ @see do_apply_event
+ @see do_update_pos
+
+ @retval Log_event::EVENT_SKIP_NOT
+ The event shall not be skipped and should be applied.
+
+ @retval Log_event::EVENT_SKIP_IGNORE
+ The event shall be skipped by just ignoring it, i.e., the slave
+ skip counter shall not be changed. This happends if, for example,
+ the originating server id of the event is the same as the server
+ id of the slave.
+
+ @retval Log_event::EVENT_SKIP_COUNT
+ The event shall be skipped because the slave skip counter was
+ non-zero. The caller shall decrease the counter by one.
+ */
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+
+/*
+ One class for each type of event.
+ Two constructors for each class:
+ - one to create the event for logging (when the server acts as a master),
+ called after an update to the database is done,
+ which accepts parameters like the query, the database, the options for LOAD
+ DATA INFILE...
+ - one to create the event from a packet (when the server acts as a slave),
+ called before reproducing the update, which accepts parameters (like a
+ buffer). Used to read from the master, from the relay log, and in
+ mysqlbinlog. This constructor must be format-tolerant.
+*/
+
+/**
+ @class Query_log_event
+
+ A @c Query_log_event is created for each query that modifies the
+ database, unless the query is logged row-based.
+
+ @section Query_log_event_binary_format Binary format
+
+ See @ref Log_event_binary_format "Binary format for log events" for
+ a general discussion and introduction to the binary format of binlog
+ events.
+
+ The Post-Header has five components:
+
+ <table>
+ <caption>Post-Header for Query_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>slave_proxy_id</td>
+ <td>4 byte unsigned integer</td>
+ <td>An integer identifying the client thread that issued the
+ query. The id is unique per server. (Note, however, that two
+ threads on different servers may have the same slave_proxy_id.)
+ This is used when a client thread creates a temporary table local
+ to the client. The slave_proxy_id is used to distinguish
+ temporary tables that belong to different clients.
+ </td>
+ </tr>
+
+ <tr>
+ <td>exec_time</td>
+ <td>4 byte unsigned integer</td>
+ <td>The time from when the query started to when it was logged in
+ the binlog, in seconds.</td>
+ </tr>
+
+ <tr>
+ <td>db_len</td>
+ <td>1 byte integer</td>
+ <td>The length of the name of the currently selected database.</td>
+ </tr>
+
+ <tr>
+ <td>error_code</td>
+ <td>2 byte unsigned integer</td>
+ <td>Error code generated by the master. If the master fails, the
+ slave will fail with the same error code, except for the error
+ codes ER_DB_CREATE_EXISTS == 1007 and ER_DB_DROP_EXISTS == 1008.
+ </td>
+ </tr>
+
+ <tr>
+ <td>status_vars_len</td>
+ <td>2 byte unsigned integer</td>
+ <td>The length of the status_vars block of the Body, in bytes. See
+ @ref query_log_event_status_vars "below".
+ </td>
+ </tr>
+ </table>
+
+ The Body has the following components:
+
+ <table>
+ <caption>Body for Query_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>@anchor query_log_event_status_vars status_vars</td>
+ <td>status_vars_len bytes</td>
+ <td>Zero or more status variables. Each status variable consists
+ of one byte identifying the variable stored, followed by the value
+ of the variable. The possible variables are listed separately in
+ the table @ref Table_query_log_event_status_vars "below". MySQL
+ always writes events in the order defined below; however, it is
+ capable of reading them in any order. </td>
+ </tr>
+
+ <tr>
+ <td>db</td>
+ <td>db_len+1</td>
+ <td>The currently selected database, as a null-terminated string.
+
+ (The trailing zero is redundant since the length is already known;
+ it is db_len from Post-Header.)
+ </td>
+ </tr>
+
+ <tr>
+ <td>query</td>
+ <td>variable length string without trailing zero, extending to the
+ end of the event (determined by the length field of the
+ Common-Header)
+ </td>
+ <td>The SQL query.</td>
+ </tr>
+ </table>
+
+ The following table lists the status variables that may appear in
+ the status_vars field.
+
+ @anchor Table_query_log_event_status_vars
+ <table>
+ <caption>Status variables for Query_log_event</caption>
+
+ <tr>
+ <th>Status variable</th>
+ <th>1 byte identifier</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>flags2</td>
+ <td>Q_FLAGS2_CODE == 0</td>
+ <td>4 byte bitfield</td>
+ <td>The flags in @c thd->options, binary AND-ed with @c
+ OPTIONS_WRITTEN_TO_BIN_LOG. The @c thd->options bitfield contains
+ options for "SELECT". @c OPTIONS_WRITTEN identifies those options
+ that need to be written to the binlog (not all do). Specifically,
+ @c OPTIONS_WRITTEN_TO_BIN_LOG equals (@c OPTION_AUTO_IS_NULL | @c
+ OPTION_NO_FOREIGN_KEY_CHECKS | @c OPTION_RELAXED_UNIQUE_CHECKS |
+ @c OPTION_NOT_AUTOCOMMIT), or 0x0c084000 in hex.
+
+ These flags correspond to the SQL variables SQL_AUTO_IS_NULL,
+ FOREIGN_KEY_CHECKS, UNIQUE_CHECKS, and AUTOCOMMIT, documented in
+ the "SET Syntax" section of the MySQL Manual.
+
+ This field is always written to the binlog in version >= 5.0, and
+ never written in version < 5.0.
+ </td>
+ </tr>
+
+ <tr>
+ <td>sql_mode</td>
+ <td>Q_SQL_MODE_CODE == 1</td>
+ <td>8 byte bitfield</td>
+ <td>The @c sql_mode variable. See the section "SQL Modes" in the
+ MySQL manual, and see sql_priv.h for a list of the possible
+ flags. Currently (2007-10-04), the following flags are available:
+ <pre>
+ MODE_REAL_AS_FLOAT==0x1
+ MODE_PIPES_AS_CONCAT==0x2
+ MODE_ANSI_QUOTES==0x4
+ MODE_IGNORE_SPACE==0x8
+ MODE_IGNORE_BAD_TABLE_OPTIONS==0x10
+ MODE_ONLY_FULL_GROUP_BY==0x20
+ MODE_NO_UNSIGNED_SUBTRACTION==0x40
+ MODE_NO_DIR_IN_CREATE==0x80
+ MODE_POSTGRESQL==0x100
+ MODE_ORACLE==0x200
+ MODE_MSSQL==0x400
+ MODE_DB2==0x800
+ MODE_MAXDB==0x1000
+ MODE_NO_KEY_OPTIONS==0x2000
+ MODE_NO_TABLE_OPTIONS==0x4000
+ MODE_NO_FIELD_OPTIONS==0x8000
+ MODE_MYSQL323==0x10000
+ MODE_MYSQL323==0x20000
+ MODE_MYSQL40==0x40000
+ MODE_ANSI==0x80000
+ MODE_NO_AUTO_VALUE_ON_ZERO==0x100000
+ MODE_NO_BACKSLASH_ESCAPES==0x200000
+ MODE_STRICT_TRANS_TABLES==0x400000
+ MODE_STRICT_ALL_TABLES==0x800000
+ MODE_NO_ZERO_IN_DATE==0x1000000
+ MODE_NO_ZERO_DATE==0x2000000
+ MODE_INVALID_DATES==0x4000000
+ MODE_ERROR_FOR_DIVISION_BY_ZERO==0x8000000
+ MODE_TRADITIONAL==0x10000000
+ MODE_NO_AUTO_CREATE_USER==0x20000000
+ MODE_HIGH_NOT_PRECEDENCE==0x40000000
+ MODE_PAD_CHAR_TO_FULL_LENGTH==0x80000000
+ </pre>
+ All these flags are replicated from the server. However, all
+ flags except @c MODE_NO_DIR_IN_CREATE are honored by the slave;
+ the slave always preserves its old value of @c
+ MODE_NO_DIR_IN_CREATE. For a rationale, see comment in
+ @c Query_log_event::do_apply_event in @c log_event.cc.
+
+ This field is always written to the binlog.
+ </td>
+ </tr>
+
+ <tr>
+ <td>catalog</td>
+ <td>Q_CATALOG_NZ_CODE == 6</td>
+ <td>Variable-length string: the length in bytes (1 byte) followed
+ by the characters (at most 255 bytes)
+ </td>
+ <td>Stores the client's current catalog. Every database belongs
+ to a catalog, the same way that every table belongs to a
+ database. Currently, there is only one catalog, "std".
+
+ This field is written if the length of the catalog is > 0;
+ otherwise it is not written.
+ </td>
+ </tr>
+
+ <tr>
+ <td>auto_increment</td>
+ <td>Q_AUTO_INCREMENT == 3</td>
+ <td>two 2 byte unsigned integers, totally 2+2=4 bytes</td>
+
+ <td>The two variables auto_increment_increment and
+ auto_increment_offset, in that order. For more information, see
+ "System variables" in the MySQL manual.
+
+ This field is written if auto_increment > 1. Otherwise, it is not
+ written.
+ </td>
+ </tr>
+
+ <tr>
+ <td>charset</td>
+ <td>Q_CHARSET_CODE == 4</td>
+ <td>three 2 byte unsigned integers, totally 2+2+2=6 bytes</td>
+ <td>The three variables character_set_client,
+ collation_connection, and collation_server, in that order.
+ character_set_client is a code identifying the character set and
+ collation used by the client to encode the query.
+ collation_connection identifies the character set and collation
+ that the master converts the query to when it receives it; this is
+ useful when comparing literal strings. collation_server is the
+ default character set and collation used when a new database is
+ created.
+
+ See also "Connection Character Sets and Collations" in the MySQL
+ 5.1 manual.
+
+ All three variables are codes identifying a (character set,
+ collation) pair. To see which codes map to which pairs, run the
+ query "SELECT id, character_set_name, collation_name FROM
+ COLLATIONS".
+
+ Cf. Q_CHARSET_DATABASE_CODE below.
+
+ This field is always written.
+ </td>
+ </tr>
+
+ <tr>
+ <td>time_zone</td>
+ <td>Q_TIME_ZONE_CODE == 5</td>
+ <td>Variable-length string: the length in bytes (1 byte) followed
+ by the characters (at most 255 bytes).
+ <td>The time_zone of the master.
+
+ See also "System Variables" and "MySQL Server Time Zone Support"
+ in the MySQL manual.
+
+ This field is written if the length of the time zone string is >
+ 0; otherwise, it is not written.
+ </td>
+ </tr>
+
+ <tr>
+ <td>lc_time_names_number</td>
+ <td>Q_LC_TIME_NAMES_CODE == 7</td>
+ <td>2 byte integer</td>
+ <td>A code identifying a table of month and day names. The
+ mapping from codes to languages is defined in @c sql_locale.cc.
+
+ This field is written if it is not 0, i.e., if the locale is not
+ en_US.
+ </td>
+ </tr>
+
+ <tr>
+ <td>charset_database_number</td>
+ <td>Q_CHARSET_DATABASE_CODE == 8</td>
+ <td>2 byte integer</td>
+
+ <td>The value of the collation_database system variable (in the
+ source code stored in @c thd->variables.collation_database), which
+ holds the code for a (character set, collation) pair as described
+ above (see Q_CHARSET_CODE).
+
+ collation_database was used in old versions (???WHEN). Its value
+ was loaded when issuing a "use db" query and could be changed by
+ issuing a "SET collation_database=xxx" query. It used to affect
+ the "LOAD DATA INFILE" and "CREATE TABLE" commands.
+
+ In newer versions, "CREATE TABLE" has been changed to take the
+ character set from the database of the created table, rather than
+ the character set of the current database. This makes a
+ difference when creating a table in another database than the
+ current one. "LOAD DATA INFILE" has not yet changed to do this,
+ but there are plans to eventually do it, and to make
+ collation_database read-only.
+
+ This field is written if it is not 0.
+ </td>
+ </tr>
+ <tr>
+ <td>table_map_for_update</td>
+ <td>Q_TABLE_MAP_FOR_UPDATE_CODE == 9</td>
+ <td>8 byte integer</td>
+
+ <td>The value of the table map that is to be updated by the
+ multi-table update query statement. Every bit of this variable
+ represents a table, and is set to 1 if the corresponding table is
+ to be updated by this statement.
+
+ The value of this variable is set when executing a multi-table update
+ statement and used by slave to apply filter rules without opening
+ all the tables on slave. This is required because some tables may
+ not exist on slave because of the filter rules.
+ </td>
+ </tr>
+ </table>
+
+ @subsection Query_log_event_notes_on_previous_versions Notes on Previous Versions
+
+ * Status vars were introduced in version 5.0. To read earlier
+ versions correctly, check the length of the Post-Header.
+
+ * The status variable Q_CATALOG_CODE == 2 existed in MySQL 5.0.x,
+ where 0<=x<=3. It was identical to Q_CATALOG_CODE, except that the
+ string had a trailing '\0'. The '\0' was removed in 5.0.4 since it
+ was redundant (the string length is stored before the string). The
+ Q_CATALOG_CODE will never be written by a new master, but can still
+ be understood by a new slave.
+
+ * See Q_CHARSET_DATABASE_CODE in the table above.
+
+ * When adding new status vars, please don't forget to update the
+ MAX_SIZE_LOG_EVENT_STATUS, and update function code_name
+
+*/
+class Query_log_event: public Log_event
+{
+ LEX_STRING user;
+ LEX_STRING host;
+protected:
+ Log_event::Byte* data_buf;
+public:
+ const char* query;
+ const char* catalog;
+ const char* db;
+ /*
+ If we already know the length of the query string
+ we pass it with q_len, so we would not have to call strlen()
+ otherwise, set it to 0, in which case, we compute it with strlen()
+ */
+ uint32 q_len;
+ uint32 db_len;
+ uint16 error_code;
+ ulong thread_id;
+ /*
+ For events created by Query_log_event::do_apply_event (and
+ Load_log_event::do_apply_event()) we need the *original* thread
+ id, to be able to log the event with the original (=master's)
+ thread id (fix for BUG#1686).
+ */
+ ulong slave_proxy_id;
+
+ /*
+ Binlog format 3 and 4 start to differ (as far as class members are
+ concerned) from here.
+ */
+
+ uint catalog_len; // <= 255 char; 0 means uninited
+
+ /*
+ We want to be able to store a variable number of N-bit status vars:
+ (generally N=32; but N=64 for SQL_MODE) a user may want to log the number
+ of affected rows (for debugging) while another does not want to lose 4
+ bytes in this.
+ The storage on disk is the following:
+ status_vars_len is part of the post-header,
+ status_vars are in the variable-length part, after the post-header, before
+ the db & query.
+ status_vars on disk is a sequence of pairs (code, value) where 'code' means
+ 'sql_mode', 'affected' etc. Sometimes 'value' must be a short string, so
+ its first byte is its length. For now the order of status vars is:
+ flags2 - sql_mode - catalog - autoinc - charset
+ We should add the same thing to Load_log_event, but in fact
+ LOAD DATA INFILE is going to be logged with a new type of event (logging of
+ the plain text query), so Load_log_event would be frozen, so no need. The
+ new way of logging LOAD DATA INFILE would use a derived class of
+ Query_log_event, so automatically benefit from the work already done for
+ status variables in Query_log_event.
+ */
+ uint16 status_vars_len;
+
+ /*
+ 'flags2' is a second set of flags (on top of those in Log_event), for
+ session variables. These are thd->options which is & against a mask
+ (OPTIONS_WRITTEN_TO_BIN_LOG).
+ flags2_inited helps make a difference between flags2==0 (3.23 or 4.x
+ master, we don't know flags2, so use the slave server's global options) and
+ flags2==0 (5.0 master, we know this has a meaning of flags all down which
+ must influence the query).
+ */
+ bool flags2_inited;
+ bool sql_mode_inited;
+ bool charset_inited;
+
+ uint32 flags2;
+ /* In connections sql_mode is 32 bits now but will be 64 bits soon */
+ ulonglong sql_mode;
+ ulong auto_increment_increment, auto_increment_offset;
+ char charset[6];
+ uint time_zone_len; /* 0 means uninited */
+ const char *time_zone_str;
+ uint lc_time_names_number; /* 0 means en_US */
+ uint charset_database_number;
+ /*
+ map for tables that will be updated for a multi-table update query
+ statement, for other query statements, this will be zero.
+ */
+ ulonglong table_map_for_update;
+ /*
+ Holds the original length of a Query_log_event that comes from a
+ master of version < 5.0 (i.e., binlog_version < 4). When the IO
+ thread writes the relay log, it augments the Query_log_event with a
+ Q_MASTER_DATA_WRITTEN_CODE status_var that holds the original event
+ length. This field is initialized to non-zero in the SQL thread when
+ it reads this augmented event. SQL thread does not write
+ Q_MASTER_DATA_WRITTEN_CODE to the slave's server binlog.
+ */
+ uint32 master_data_written;
+
+#ifdef MYSQL_SERVER
+
+ Query_log_event(THD* thd_arg, const char* query_arg, ulong query_length,
+ bool using_trans, bool direct, bool suppress_use, int error);
+ const char* get_db() { return db; }
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print_query_header(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info);
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Query_log_event();
+ Query_log_event(const char* buf, uint event_len,
+ const Format_description_log_event *description_event,
+ Log_event_type event_type);
+ ~Query_log_event()
+ {
+ if (data_buf)
+ my_free(data_buf);
+ }
+ Log_event_type get_type_code() { return QUERY_EVENT; }
+ static int dummy_event(String *packet, ulong ev_offset, uint8 checksum_alg);
+ static int begin_event(String *packet, ulong ev_offset, uint8 checksum_alg);
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+ virtual bool write_post_header_for_derived(IO_CACHE* file) { return FALSE; }
+#endif
+ bool is_valid() const { return query != 0; }
+
+ /*
+ Returns number of bytes additionally written to post header by derived
+ events (so far it is only Execute_load_query event).
+ */
+ virtual ulong get_post_header_size_for_derived() { return 0; }
+ /* Writes derived event-specific part of post header. */
+
+public: /* !!! Public in this patch to allow old usage */
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+
+ int do_apply_event(rpl_group_info *rgi,
+ const char *query_arg,
+ uint32 q_len_arg);
+ static bool peek_is_commit_rollback(const char *event_start,
+ size_t event_len, uint8 checksum_alg);
+#endif /* HAVE_REPLICATION */
+ /*
+ If true, the event always be applied by slave SQL thread or be printed by
+ mysqlbinlog
+ */
+ bool is_trans_keyword()
+ {
+ /*
+ Before the patch for bug#50407, The 'SAVEPOINT and ROLLBACK TO'
+ queries input by user was written into log events directly.
+ So the keywords can be written in both upper case and lower case
+ together, strncasecmp is used to check both cases. they also could be
+ binlogged with comments in the front of these keywords. for examples:
+ / * bla bla * / SAVEPOINT a;
+ / * bla bla * / ROLLBACK TO a;
+ but we don't handle these cases and after the patch, both quiries are
+ binlogged in upper case with no comments.
+ */
+ return !strncmp(query, "BEGIN", q_len) ||
+ !strncmp(query, "COMMIT", q_len) ||
+ !strncasecmp(query, "SAVEPOINT", 9) ||
+ !strncasecmp(query, "ROLLBACK", 8);
+ }
+ bool is_begin() { return !strcmp(query, "BEGIN"); }
+ bool is_commit() { return !strcmp(query, "COMMIT"); }
+ bool is_rollback() { return !strcmp(query, "ROLLBACK"); }
+};
+
+
+#ifdef HAVE_REPLICATION
+
+/**
+ @class Slave_log_event
+
+ Note that this class is currently not used at all; no code writes a
+ @c Slave_log_event (though some code in @c repl_failsafe.cc reads @c
+ Slave_log_event). So it's not a problem if this code is not
+ maintained.
+
+ @section Slave_log_event_binary_format Binary Format
+
+ This event type has no Post-Header. The Body has the following
+ four components.
+
+ <table>
+ <caption>Body for Slave_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>master_pos</td>
+ <td>8 byte integer</td>
+ <td>???TODO
+ </td>
+ </tr>
+
+ <tr>
+ <td>master_port</td>
+ <td>2 byte integer</td>
+ <td>???TODO</td>
+ </tr>
+
+ <tr>
+ <td>master_host</td>
+ <td>null-terminated string</td>
+ <td>???TODO</td>
+ </tr>
+
+ <tr>
+ <td>master_log</td>
+ <td>null-terminated string</td>
+ <td>???TODO</td>
+ </tr>
+ </table>
+*/
+class Slave_log_event: public Log_event
+{
+protected:
+ char* mem_pool;
+ void init_from_mem_pool(int data_size);
+public:
+ my_off_t master_pos;
+ char* master_host;
+ char* master_log;
+ int master_host_len;
+ int master_log_len;
+ uint16 master_port;
+
+#ifdef MYSQL_SERVER
+ Slave_log_event(THD* thd_arg, Relay_log_info* rli);
+ void pack_info(THD *thd, Protocol* protocol);
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Slave_log_event(const char* buf,
+ uint event_len,
+ const Format_description_log_event *description_event);
+ ~Slave_log_event();
+ int get_data_size();
+ bool is_valid() const { return master_host != 0; }
+ Log_event_type get_type_code() { return SLAVE_EVENT; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+#endif
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+#endif
+};
+
+#endif /* HAVE_REPLICATION */
+
+
+/**
+ @class Load_log_event
+
+ This log event corresponds to a "LOAD DATA INFILE" SQL query on the
+ following form:
+
+ @verbatim
+ (1) USE db;
+ (2) LOAD DATA [CONCURRENT] [LOCAL] INFILE 'file_name'
+ (3) [REPLACE | IGNORE]
+ (4) INTO TABLE 'table_name'
+ (5) [FIELDS
+ (6) [TERMINATED BY 'field_term']
+ (7) [[OPTIONALLY] ENCLOSED BY 'enclosed']
+ (8) [ESCAPED BY 'escaped']
+ (9) ]
+ (10) [LINES
+ (11) [TERMINATED BY 'line_term']
+ (12) [LINES STARTING BY 'line_start']
+ (13) ]
+ (14) [IGNORE skip_lines LINES]
+ (15) (field_1, field_2, ..., field_n)@endverbatim
+
+ @section Load_log_event_binary_format Binary Format
+
+ The Post-Header consists of the following six components.
+
+ <table>
+ <caption>Post-Header for Load_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>slave_proxy_id</td>
+ <td>4 byte unsigned integer</td>
+ <td>An integer identifying the client thread that issued the
+ query. The id is unique per server. (Note, however, that two
+ threads on different servers may have the same slave_proxy_id.)
+ This is used when a client thread creates a temporary table local
+ to the client. The slave_proxy_id is used to distinguish
+ temporary tables that belong to different clients.
+ </td>
+ </tr>
+
+ <tr>
+ <td>exec_time</td>
+ <td>4 byte unsigned integer</td>
+ <td>The time from when the query started to when it was logged in
+ the binlog, in seconds.</td>
+ </tr>
+
+ <tr>
+ <td>skip_lines</td>
+ <td>4 byte unsigned integer</td>
+ <td>The number on line (14) above, if present, or 0 if line (14)
+ is left out.
+ </td>
+ </tr>
+
+ <tr>
+ <td>table_name_len</td>
+ <td>1 byte unsigned integer</td>
+ <td>The length of 'table_name' on line (4) above.</td>
+ </tr>
+
+ <tr>
+ <td>db_len</td>
+ <td>1 byte unsigned integer</td>
+ <td>The length of 'db' on line (1) above.</td>
+ </tr>
+
+ <tr>
+ <td>num_fields</td>
+ <td>4 byte unsigned integer</td>
+ <td>The number n of fields on line (15) above.</td>
+ </tr>
+ </table>
+
+ The Body contains the following components.
+
+ <table>
+ <caption>Body of Load_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>sql_ex</td>
+ <td>variable length</td>
+
+ <td>Describes the part of the query on lines (3) and
+ (5)&ndash;(13) above. More precisely, it stores the five strings
+ (on lines) field_term (6), enclosed (7), escaped (8), line_term
+ (11), and line_start (12); as well as a bitfield indicating the
+ presence of the keywords REPLACE (3), IGNORE (3), and OPTIONALLY
+ (7).
+
+ The data is stored in one of two formats, called "old" and "new".
+ The type field of Common-Header determines which of these two
+ formats is used: type LOAD_EVENT means that the old format is
+ used, and type NEW_LOAD_EVENT means that the new format is used.
+ When MySQL writes a Load_log_event, it uses the new format if at
+ least one of the five strings is two or more bytes long.
+ Otherwise (i.e., if all strings are 0 or 1 bytes long), the old
+ format is used.
+
+ The new and old format differ in the way the five strings are
+ stored.
+
+ <ul>
+ <li> In the new format, the strings are stored in the order
+ field_term, enclosed, escaped, line_term, line_start. Each string
+ consists of a length (1 byte), followed by a sequence of
+ characters (0-255 bytes). Finally, a boolean combination of the
+ following flags is stored in 1 byte: REPLACE_FLAG==0x4,
+ IGNORE_FLAG==0x8, and OPT_ENCLOSED_FLAG==0x2. If a flag is set,
+ it indicates the presence of the corresponding keyword in the SQL
+ query.
+
+ <li> In the old format, we know that each string has length 0 or
+ 1. Therefore, only the first byte of each string is stored. The
+ order of the strings is the same as in the new format. These five
+ bytes are followed by the same 1 byte bitfield as in the new
+ format. Finally, a 1 byte bitfield called empty_flags is stored.
+ The low 5 bits of empty_flags indicate which of the five strings
+ have length 0. For each of the following flags that is set, the
+ corresponding string has length 0; for the flags that are not set,
+ the string has length 1: FIELD_TERM_EMPTY==0x1,
+ ENCLOSED_EMPTY==0x2, LINE_TERM_EMPTY==0x4, LINE_START_EMPTY==0x8,
+ ESCAPED_EMPTY==0x10.
+ </ul>
+
+ Thus, the size of the new format is 6 bytes + the sum of the sizes
+ of the five strings. The size of the old format is always 7
+ bytes.
+ </td>
+ </tr>
+
+ <tr>
+ <td>field_lens</td>
+ <td>num_fields 1 byte unsigned integers</td>
+ <td>An array of num_fields integers representing the length of
+ each field in the query. (num_fields is from the Post-Header).
+ </td>
+ </tr>
+
+ <tr>
+ <td>fields</td>
+ <td>num_fields null-terminated strings</td>
+ <td>An array of num_fields null-terminated strings, each
+ representing a field in the query. (The trailing zero is
+ redundant, since the length are stored in the num_fields array.)
+ The total length of all strings equals to the sum of all
+ field_lens, plus num_fields bytes for all the trailing zeros.
+ </td>
+ </tr>
+
+ <tr>
+ <td>table_name</td>
+ <td>null-terminated string of length table_len+1 bytes</td>
+ <td>The 'table_name' from the query, as a null-terminated string.
+ (The trailing zero is actually redundant since the table_len is
+ known from Post-Header.)
+ </td>
+ </tr>
+
+ <tr>
+ <td>db</td>
+ <td>null-terminated string of length db_len+1 bytes</td>
+ <td>The 'db' from the query, as a null-terminated string.
+ (The trailing zero is actually redundant since the db_len is known
+ from Post-Header.)
+ </td>
+ </tr>
+
+ <tr>
+ <td>file_name</td>
+ <td>variable length string without trailing zero, extending to the
+ end of the event (determined by the length field of the
+ Common-Header)
+ </td>
+ <td>The 'file_name' from the query.
+ </td>
+ </tr>
+
+ </table>
+
+ @subsection Load_log_event_notes_on_previous_versions Notes on Previous Versions
+
+ This event type is understood by current versions, but only
+ generated by MySQL 3.23 and earlier.
+*/
+class Load_log_event: public Log_event
+{
+private:
+protected:
+ int copy_log_event(const char *buf, ulong event_len,
+ int body_offset,
+ const Format_description_log_event* description_event);
+
+public:
+ void print_query(THD *thd, bool need_db, const char *cs, String *buf,
+ my_off_t *fn_start, my_off_t *fn_end,
+ const char *qualify_db);
+ ulong thread_id;
+ ulong slave_proxy_id;
+ uint32 table_name_len;
+ /*
+ No need to have a catalog, as these events can only come from 4.x.
+ TODO: this may become false if Dmitri pushes his new LOAD DATA INFILE in
+ 5.0 only (not in 4.x).
+ */
+ uint32 db_len;
+ uint32 fname_len;
+ uint32 num_fields;
+ const char* fields;
+ const uchar* field_lens;
+ uint32 field_block_len;
+
+ const char* table_name;
+ const char* db;
+ const char* fname;
+ uint32 skip_lines;
+ sql_ex_info sql_ex;
+ bool local_fname;
+ /**
+ Indicates that this event corresponds to LOAD DATA CONCURRENT,
+
+ @note Since Load_log_event event coming from the binary log
+ lacks information whether LOAD DATA on master was concurrent
+ or not, this flag is only set to TRUE for an auxiliary
+ Load_log_event object which is used in mysql_load() to
+ re-construct LOAD DATA statement from function parameters,
+ for logging.
+ */
+ bool is_concurrent;
+
+ /* fname doesn't point to memory inside Log_event::temp_buf */
+ void set_fname_outside_temp_buf(const char *afname, uint alen)
+ {
+ fname= afname;
+ fname_len= alen;
+ local_fname= TRUE;
+ }
+ /* fname doesn't point to memory inside Log_event::temp_buf */
+ int check_fname_outside_temp_buf()
+ {
+ return local_fname;
+ }
+
+#ifdef MYSQL_SERVER
+ String field_lens_buf;
+ String fields_buf;
+
+ Load_log_event(THD* thd, sql_exchange* ex, const char* db_arg,
+ const char* table_name_arg,
+ List<Item>& fields_arg,
+ bool is_concurrent_arg,
+ enum enum_duplicates handle_dup, bool ignore,
+ bool using_trans);
+ void set_fields(const char* db, List<Item> &fields_arg,
+ Name_resolution_context *context);
+ const char* get_db() { return db; }
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info, bool commented);
+#endif
+
+ /*
+ Note that for all the events related to LOAD DATA (Load_log_event,
+ Create_file/Append/Exec/Delete, we pass description_event; however as
+ logging of LOAD DATA is going to be changed in 4.1 or 5.0, this is only used
+ for the common_header_len (post_header_len will not be changed).
+ */
+ Load_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event);
+ ~Load_log_event()
+ {}
+ Log_event_type get_type_code()
+ {
+ return sql_ex.new_format() ? NEW_LOAD_EVENT: LOAD_EVENT;
+ }
+#ifdef MYSQL_SERVER
+ bool write_data_header(IO_CACHE* file);
+ bool write_data_body(IO_CACHE* file);
+#endif
+ bool is_valid() const { return table_name != 0; }
+ int get_data_size()
+ {
+ return (table_name_len + db_len + 2 + fname_len
+ + LOAD_HEADER_LEN
+ + sql_ex.data_size() + field_block_len + num_fields);
+ }
+
+public: /* !!! Public in this patch to allow old usage */
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi)
+ {
+ return do_apply_event(thd->slave_net,rgi,0);
+ }
+
+ int do_apply_event(NET *net, rpl_group_info *rgi,
+ bool use_rli_only_for_errors);
+#endif
+};
+
+/**
+ @class Start_log_event_v3
+
+ Start_log_event_v3 is the Start_log_event of binlog format 3 (MySQL 3.23 and
+ 4.x).
+
+ Format_description_log_event derives from Start_log_event_v3; it is
+ the Start_log_event of binlog format 4 (MySQL 5.0), that is, the
+ event that describes the other events' Common-Header/Post-Header
+ lengths. This event is sent by MySQL 5.0 whenever it starts sending
+ a new binlog if the requested position is >4 (otherwise if ==4 the
+ event will be sent naturally).
+
+ @section Start_log_event_v3_binary_format Binary Format
+*/
+class Start_log_event_v3: public Log_event
+{
+public:
+ /*
+ If this event is at the start of the first binary log since server
+ startup 'created' should be the timestamp when the event (and the
+ binary log) was created. In the other case (i.e. this event is at
+ the start of a binary log created by FLUSH LOGS or automatic
+ rotation), 'created' should be 0. This "trick" is used by MySQL
+ >=4.0.14 slaves to know whether they must drop stale temporary
+ tables and whether they should abort unfinished transaction.
+
+ Note that when 'created'!=0, it is always equal to the event's
+ timestamp; indeed Start_log_event is written only in log.cc where
+ the first constructor below is called, in which 'created' is set
+ to 'when'. So in fact 'created' is a useless variable. When it is
+ 0 we can read the actual value from timestamp ('when') and when it
+ is non-zero we can read the same value from timestamp
+ ('when'). Conclusion:
+ - we use timestamp to print when the binlog was created.
+ - we use 'created' only to know if this is a first binlog or not.
+ In 3.23.57 we did not pay attention to this identity, so mysqlbinlog in
+ 3.23.57 does not print 'created the_date' if created was zero. This is now
+ fixed.
+ */
+ time_t created;
+ uint16 binlog_version;
+ char server_version[ST_SERVER_VER_LEN];
+ /*
+ We set this to 1 if we don't want to have the created time in the log,
+ which is the case when we rollover to a new log.
+ */
+ bool dont_set_created;
+
+#ifdef MYSQL_SERVER
+ Start_log_event_v3();
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ Start_log_event_v3() {}
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Start_log_event_v3(const char* buf, uint event_len,
+ const Format_description_log_event* description_event);
+ ~Start_log_event_v3() {}
+ Log_event_type get_type_code() { return START_EVENT_V3;}
+ my_off_t get_header_len(my_off_t l __attribute__((unused)))
+ { return LOG_EVENT_MINIMAL_HEADER_LEN; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+#endif
+ bool is_valid() const { return server_version[0] != 0; }
+ int get_data_size()
+ {
+ return START_V3_HEADER_LEN; //no variable-sized part
+ }
+
+protected:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info*)
+ {
+ /*
+ Events from ourself should be skipped, but they should not
+ decrease the slave skip counter.
+ */
+ if (this->server_id == global_system_variables.server_id)
+ return Log_event::EVENT_SKIP_IGNORE;
+ else
+ return Log_event::EVENT_SKIP_NOT;
+ }
+#endif
+};
+
+
+/**
+ @class Format_description_log_event
+
+ For binlog version 4.
+ This event is saved by threads which read it, as they need it for future
+ use (to decode the ordinary events).
+
+ @section Format_description_log_event_binary_format Binary Format
+*/
+
+class Format_description_log_event: public Start_log_event_v3
+{
+public:
+ /*
+ The size of the fixed header which _all_ events have
+ (for binlogs written by this version, this is equal to
+ LOG_EVENT_HEADER_LEN), except FORMAT_DESCRIPTION_EVENT and ROTATE_EVENT
+ (those have a header of size LOG_EVENT_MINIMAL_HEADER_LEN).
+ */
+ uint8 common_header_len;
+ uint8 number_of_event_types;
+ /*
+ The list of post-headers' lengths followed
+ by the checksum alg decription byte
+ */
+ uint8 *post_header_len;
+ struct master_version_split {
+ enum {KIND_MYSQL, KIND_MARIADB};
+ int kind;
+ uchar ver[3];
+ };
+ master_version_split server_version_split;
+ const uint8 *event_type_permutation;
+
+ Format_description_log_event(uint8 binlog_ver, const char* server_ver=0);
+ Format_description_log_event(const char* buf, uint event_len,
+ const Format_description_log_event
+ *description_event);
+ ~Format_description_log_event()
+ {
+ my_free(post_header_len);
+ }
+ Log_event_type get_type_code() { return FORMAT_DESCRIPTION_EVENT;}
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+#endif
+ bool header_is_valid() const
+ {
+ return ((common_header_len >= ((binlog_version==1) ? OLD_HEADER_LEN :
+ LOG_EVENT_MINIMAL_HEADER_LEN)) &&
+ (post_header_len != NULL));
+ }
+
+ bool version_is_valid() const
+ {
+ /* It is invalid only when all version numbers are 0 */
+ return !(server_version_split.ver[0] == 0 &&
+ server_version_split.ver[1] == 0 &&
+ server_version_split.ver[2] == 0);
+ }
+
+ bool is_valid() const
+ {
+ return header_is_valid() && version_is_valid();
+ }
+
+ int get_data_size()
+ {
+ /*
+ The vector of post-header lengths is considered as part of the
+ post-header, because in a given version it never changes (contrary to the
+ query in a Query_log_event).
+ */
+ return FORMAT_DESCRIPTION_HEADER_LEN;
+ }
+
+ void calc_server_version_split();
+ static bool is_version_before_checksum(const master_version_split *version_split);
+protected:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Intvar_log_event
+
+ An Intvar_log_event will be created just before a Query_log_event,
+ if the query uses one of the variables LAST_INSERT_ID or INSERT_ID.
+ Each Intvar_log_event holds the value of one of these variables.
+
+ @section Intvar_log_event_binary_format Binary Format
+
+ The Post-Header for this event type is empty. The Body has two
+ components:
+
+ <table>
+ <caption>Body for Intvar_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>type</td>
+ <td>1 byte enumeration</td>
+ <td>One byte identifying the type of variable stored. Currently,
+ two identifiers are supported: LAST_INSERT_ID_EVENT==1 and
+ INSERT_ID_EVENT==2.
+ </td>
+ </tr>
+
+ <tr>
+ <td>value</td>
+ <td>8 byte unsigned integer</td>
+ <td>The value of the variable.</td>
+ </tr>
+
+ </table>
+*/
+class Intvar_log_event: public Log_event
+{
+public:
+ ulonglong val;
+ uchar type;
+
+#ifdef MYSQL_SERVER
+Intvar_log_event(THD* thd_arg,uchar type_arg, ulonglong val_arg,
+ bool using_trans, bool direct)
+ :Log_event(thd_arg,0,using_trans),val(val_arg),type(type_arg)
+ {
+ if (direct)
+ cache_type= Log_event::EVENT_NO_CACHE;
+ }
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Intvar_log_event(const char* buf,
+ const Format_description_log_event *description_event);
+ ~Intvar_log_event() {}
+ Log_event_type get_type_code() { return INTVAR_EVENT;}
+ const char* get_var_type_name();
+ int get_data_size() { return 9; /* sizeof(type) + sizeof(val) */;}
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+#endif
+ bool is_valid() const { return 1; }
+ bool is_part_of_group() { return 1; }
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Rand_log_event
+
+ Logs random seed used by the next RAND(), and by PASSWORD() in 4.1.0.
+ 4.1.1 does not need it (it's repeatable again) so this event needn't be
+ written in 4.1.1 for PASSWORD() (but the fact that it is written is just a
+ waste, it does not cause bugs).
+
+ The state of the random number generation consists of 128 bits,
+ which are stored internally as two 64-bit numbers.
+
+ @section Rand_log_event_binary_format Binary Format
+
+ The Post-Header for this event type is empty. The Body has two
+ components:
+
+ <table>
+ <caption>Body for Rand_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>seed1</td>
+ <td>8 byte unsigned integer</td>
+ <td>64 bit random seed1.</td>
+ </tr>
+
+ <tr>
+ <td>seed2</td>
+ <td>8 byte unsigned integer</td>
+ <td>64 bit random seed2.</td>
+ </tr>
+ </table>
+*/
+
+class Rand_log_event: public Log_event
+{
+ public:
+ ulonglong seed1;
+ ulonglong seed2;
+
+#ifdef MYSQL_SERVER
+ Rand_log_event(THD* thd_arg, ulonglong seed1_arg, ulonglong seed2_arg,
+ bool using_trans, bool direct)
+ :Log_event(thd_arg,0,using_trans),seed1(seed1_arg),seed2(seed2_arg)
+ {
+ if (direct)
+ cache_type= Log_event::EVENT_NO_CACHE;
+ }
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Rand_log_event(const char* buf,
+ const Format_description_log_event *description_event);
+ ~Rand_log_event() {}
+ Log_event_type get_type_code() { return RAND_EVENT;}
+ int get_data_size() { return 16; /* sizeof(ulonglong) * 2*/ }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+#endif
+ bool is_valid() const { return 1; }
+ bool is_part_of_group() { return 1; }
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+/**
+ @class Xid_log_event
+
+ Logs xid of the transaction-to-be-committed in the 2pc protocol.
+ Has no meaning in replication, slaves ignore it.
+
+ @section Xid_log_event_binary_format Binary Format
+*/
+#ifdef MYSQL_CLIENT
+typedef ulonglong my_xid; // this line is the same as in handler.h
+#endif
+
+class Xid_log_event: public Log_event
+{
+ public:
+ my_xid xid;
+
+#ifdef MYSQL_SERVER
+ Xid_log_event(THD* thd_arg, my_xid x, bool direct):
+ Log_event(thd_arg, 0, TRUE), xid(x)
+ {
+ if (direct)
+ cache_type= Log_event::EVENT_NO_CACHE;
+ }
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Xid_log_event(const char* buf,
+ const Format_description_log_event *description_event);
+ ~Xid_log_event() {}
+ Log_event_type get_type_code() { return XID_EVENT;}
+ int get_data_size() { return sizeof(xid); }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+#endif
+ bool is_valid() const { return 1; }
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+/**
+ @class User_var_log_event
+
+ Every time a query uses the value of a user variable, a User_var_log_event is
+ written before the Query_log_event, to set the user variable.
+
+ @section User_var_log_event_binary_format Binary Format
+*/
+
+class User_var_log_event: public Log_event
+{
+public:
+ enum {
+ UNDEF_F= 0,
+ UNSIGNED_F= 1
+ };
+ char *name;
+ uint name_len;
+ char *val;
+ ulong val_len;
+ Item_result type;
+ uint charset_number;
+ bool is_null;
+ uchar flags;
+#ifdef MYSQL_SERVER
+ bool deferred;
+ query_id_t query_id;
+ User_var_log_event(THD* thd_arg, char *name_arg, uint name_len_arg,
+ char *val_arg, ulong val_len_arg, Item_result type_arg,
+ uint charset_number_arg, uchar flags_arg,
+ bool using_trans, bool direct)
+ :Log_event(thd_arg, 0, using_trans),
+ name(name_arg), name_len(name_len_arg), val(val_arg),
+ val_len(val_len_arg), type(type_arg), charset_number(charset_number_arg),
+ flags(flags_arg), deferred(false)
+ {
+ is_null= !val;
+ if (direct)
+ cache_type= Log_event::EVENT_NO_CACHE;
+ }
+ void pack_info(THD *thd, Protocol* protocol);
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ User_var_log_event(const char* buf, uint event_len,
+ const Format_description_log_event *description_event);
+ ~User_var_log_event() {}
+ Log_event_type get_type_code() { return USER_VAR_EVENT;}
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+ /*
+ Getter and setter for deferred User-event.
+ Returns true if the event is not applied directly
+ and which case the applier adjusts execution path.
+ */
+ bool is_deferred() { return deferred; }
+ /*
+ In case of the deffered applying the variable instance is flagged
+ and the parsing time query id is stored to be used at applying time.
+ */
+ void set_deferred(query_id_t qid) { deferred= true; query_id= qid; }
+#endif
+ bool is_valid() const { return name != 0; }
+ bool is_part_of_group() { return 1; }
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Stop_log_event
+
+ @section Stop_log_event_binary_format Binary Format
+
+ The Post-Header and Body for this event type are empty; it only has
+ the Common-Header.
+*/
+class Stop_log_event: public Log_event
+{
+public:
+#ifdef MYSQL_SERVER
+ Stop_log_event() :Log_event()
+ {}
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Stop_log_event(const char* buf,
+ const Format_description_log_event *description_event):
+ Log_event(buf, description_event)
+ {}
+ ~Stop_log_event() {}
+ Log_event_type get_type_code() { return STOP_EVENT;}
+ bool is_valid() const { return 1; }
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi)
+ {
+ /*
+ Events from ourself should be skipped, but they should not
+ decrease the slave skip counter.
+ */
+ if (this->server_id == global_system_variables.server_id)
+ return Log_event::EVENT_SKIP_IGNORE;
+ else
+ return Log_event::EVENT_SKIP_NOT;
+ }
+#endif
+};
+
+/**
+ @class Rotate_log_event
+
+ This will be deprecated when we move to using sequence ids.
+
+ @section Rotate_log_event_binary_format Binary Format
+
+ The Post-Header has one component:
+
+ <table>
+ <caption>Post-Header for Rotate_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>position</td>
+ <td>8 byte integer</td>
+ <td>The position within the binlog to rotate to.</td>
+ </tr>
+
+ </table>
+
+ The Body has one component:
+
+ <table>
+ <caption>Body for Rotate_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>new_log</td>
+ <td>variable length string without trailing zero, extending to the
+ end of the event (determined by the length field of the
+ Common-Header)
+ </td>
+ <td>Name of the binlog to rotate to.</td>
+ </tr>
+
+ </table>
+*/
+
+class Rotate_log_event: public Log_event
+{
+public:
+ enum {
+ DUP_NAME= 2, // if constructor should dup the string argument
+ RELAY_LOG=4 // rotate event for relay log
+ };
+ const char* new_log_ident;
+ ulonglong pos;
+ uint ident_len;
+ uint flags;
+#ifdef MYSQL_SERVER
+ Rotate_log_event(const char* new_log_ident_arg,
+ uint ident_len_arg,
+ ulonglong pos_arg, uint flags);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Rotate_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event);
+ ~Rotate_log_event()
+ {
+ if (flags & DUP_NAME)
+ my_free((void*) new_log_ident);
+ }
+ Log_event_type get_type_code() { return ROTATE_EVENT;}
+ my_off_t get_header_len(my_off_t l __attribute__((unused)))
+ { return LOG_EVENT_MINIMAL_HEADER_LEN; }
+ int get_data_size() { return ident_len + ROTATE_HEADER_LEN;}
+ bool is_valid() const { return new_log_ident != 0; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+#endif
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+
+class Binlog_checkpoint_log_event: public Log_event
+{
+public:
+ char *binlog_file_name;
+ uint binlog_file_len;
+
+#ifdef MYSQL_SERVER
+ Binlog_checkpoint_log_event(const char *binlog_file_name_arg,
+ uint binlog_file_len_arg);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol *protocol);
+#endif
+#else
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+ Binlog_checkpoint_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+ ~Binlog_checkpoint_log_event() { my_free(binlog_file_name); }
+ Log_event_type get_type_code() { return BINLOG_CHECKPOINT_EVENT;}
+ int get_data_size() { return binlog_file_len + BINLOG_CHECKPOINT_HEADER_LEN;}
+ bool is_valid() const { return binlog_file_name != 0; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+ enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Gtid_log_event
+
+ This event is logged as part of every event group to give the global
+ transaction id (GTID) of that group.
+
+ It replaces the BEGIN query event used in earlier versions to begin most
+ event groups, but is also used for events that used to be stand-alone.
+
+ @section Gtid_log_event_binary_format Binary Format
+
+ The binary format for Gtid_log_event has 6 extra reserved bytes to make the
+ length a total of 19 byte (+ 19 bytes of header in common with all events).
+ This is just the minimal size for a BEGIN query event, which makes it easy
+ to replace this event with such BEGIN event to remain compatible with old
+ slave servers.
+
+ <table>
+ <caption>Post-Header</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>seq_no</td>
+ <td>8 byte unsigned integer</td>
+ <td>increasing id within one server_id. Starts at 1, holes in the sequence
+ may occur</td>
+ </tr>
+
+ <tr>
+ <td>domain_id</td>
+ <td>4 byte unsigned integer</td>
+ <td>Replication domain id, identifying independent replication streams></td>
+ </tr>
+
+ <tr>
+ <td>flags</td>
+ <td>1 byte bitfield</td>
+ <td>Bit 0 set indicates stand-alone event (no terminating COMMIT)</td>
+ <td>Bit 1 set indicates group commit, and that commit id exists</td>
+ </tr>
+
+ <tr>
+ <td>Reserved (no group commit) / commit id (group commit) (see flags bit 1)</td>
+ <td>6 bytes / 8 bytes</td>
+ <td>Reserved bytes, set to 0. Maybe be used for future expansion (no
+ group commit). OR commit id, same for all GTIDs in the same group
+ commit (see flags bit 1).</td>
+ </tr>
+ </table>
+
+ The Body of Gtid_log_event is empty. The total event size is 19 bytes +
+ the normal 19 bytes common-header.
+*/
+
+class Gtid_log_event: public Log_event
+{
+public:
+ uint64 seq_no;
+ uint64 commit_id;
+ uint32 domain_id;
+ uchar flags2;
+
+ /* Flags2. */
+
+ /* FL_STANDALONE is set when there is no terminating COMMIT event. */
+ static const uchar FL_STANDALONE= 1;
+ /*
+ FL_GROUP_COMMIT_ID is set when event group is part of a group commit on the
+ master. Groups with same commit_id are part of the same group commit.
+ */
+ static const uchar FL_GROUP_COMMIT_ID= 2;
+
+#ifdef MYSQL_SERVER
+ Gtid_log_event(THD *thd_arg, uint64 seq_no, uint32 domain_id, bool standalone,
+ uint16 flags, bool is_transactional, uint64 commit_id);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol *protocol);
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+#else
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+ Gtid_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+ ~Gtid_log_event() { }
+ Log_event_type get_type_code() { return GTID_EVENT; }
+ int get_data_size()
+ {
+ return GTID_HEADER_LEN + ((flags2 & FL_GROUP_COMMIT_ID) ? 2 : 0);
+ }
+ bool is_valid() const { return seq_no != 0; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE *file);
+ static int make_compatible_event(String *packet, bool *need_dummy_event,
+ ulong ev_offset, uint8 checksum_alg);
+ static bool peek(const char *event_start, size_t event_len,
+ uint8 checksum_alg,
+ uint32 *domain_id, uint32 *server_id, uint64 *seq_no,
+ uchar *flags2, const Format_description_log_event *fdev);
+#endif
+};
+
+
+/**
+ @class Gtid_list_log_event
+
+ This event is logged at the start of every binlog file to record the
+ current replication state: the last global transaction id (GTID) applied
+ on the server within each replication domain.
+
+ It consists of a list of GTIDs, one for each replication domain ever seen
+ on the server.
+
+ @section Gtid_list_log_event_binary_format Binary Format
+
+ <table>
+ <caption>Post-Header</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>count</td>
+ <td>4 byte unsigned integer</td>
+ <td>The lower 28 bits are the number of GTIDs. The upper 4 bits are
+ flags bits.</td>
+ </tr>
+ </table>
+
+ <table>
+ <caption>Body</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>domain_id</td>
+ <td>4 byte unsigned integer</td>
+ <td>Replication domain id of one GTID</td>
+ </tr>
+
+ <tr>
+ <td>server_id</td>
+ <td>4 byte unsigned integer</td>
+ <td>Server id of one GTID</td>
+ </tr>
+
+ <tr>
+ <td>seq_no</td>
+ <td>8 byte unsigned integer</td>
+ <td>sequence number of one GTID</td>
+ </tr>
+ </table>
+
+ The three elements in the body repeat COUNT times to form the GTID list.
+
+ At the time of writing, only one flag bit is in use.
+
+ Bit 28 of `count' is used for flag FLAG_UNTIL_REACHED, which is sent in a
+ Gtid_list event from the master to the slave to indicate that the START
+ SLAVE UNTIL master_gtid_pos=xxx condition has been reached. (This flag is
+ only sent in "fake" events generated on the fly, it is not written into
+ the binlog).
+*/
+
+class Gtid_list_log_event: public Log_event
+{
+public:
+ uint32 count;
+ uint32 gl_flags;
+ struct rpl_gtid *list;
+ uint64 *sub_id_list;
+
+ static const uint element_size= 4+4+8;
+ static const uint32 FLAG_UNTIL_REACHED= (1<<28);
+ static const uint32 FLAG_IGN_GTIDS= (1<<29);
+
+#ifdef MYSQL_SERVER
+ Gtid_list_log_event(rpl_binlog_state *gtid_set, uint32 gl_flags);
+ Gtid_list_log_event(slave_connection_state *gtid_set, uint32 gl_flags);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol *protocol);
+#endif
+#else
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+ Gtid_list_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+ ~Gtid_list_log_event() { my_free(list); my_free(sub_id_list); }
+ Log_event_type get_type_code() { return GTID_LIST_EVENT; }
+ int get_data_size() {
+ /*
+ Replacing with dummy event, needed for older slaves, requires a minimum
+ of 6 bytes in the body.
+ */
+ return (count==0 ?
+ GTID_LIST_HEADER_LEN+2 : GTID_LIST_HEADER_LEN+count*element_size);
+ }
+ bool is_valid() const { return list != NULL; }
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ bool to_packet(String *packet);
+ bool write(IO_CACHE *file);
+ virtual int do_apply_event(rpl_group_info *rgi);
+ enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+ static bool peek(const char *event_start, uint32 event_len,
+ uint8 checksum_alg,
+ rpl_gtid **out_gtid_list, uint32 *out_list_len,
+ const Format_description_log_event *fdev);
+};
+
+
+/* the classes below are for the new LOAD DATA INFILE logging */
+
+/**
+ @class Create_file_log_event
+
+ @section Create_file_log_event_binary_format Binary Format
+*/
+
+class Create_file_log_event: public Load_log_event
+{
+protected:
+ /*
+ Pretend we are Load event, so we can write out just
+ our Load part - used on the slave when writing event out to
+ SQL_LOAD-*.info file
+ */
+ bool fake_base;
+public:
+ uchar* block;
+ const char *event_buf;
+ uint block_len;
+ uint file_id;
+ bool inited_from_old;
+
+#ifdef MYSQL_SERVER
+ Create_file_log_event(THD* thd, sql_exchange* ex, const char* db_arg,
+ const char* table_name_arg,
+ List<Item>& fields_arg,
+ bool is_concurrent_arg,
+ enum enum_duplicates handle_dup, bool ignore,
+ uchar* block_arg, uint block_len_arg,
+ bool using_trans);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info,
+ bool enable_local);
+#endif
+
+ Create_file_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event);
+ ~Create_file_log_event()
+ {
+ my_free((void*) event_buf);
+ }
+
+ Log_event_type get_type_code()
+ {
+ return fake_base ? Load_log_event::get_type_code() : CREATE_FILE_EVENT;
+ }
+ int get_data_size()
+ {
+ return (fake_base ? Load_log_event::get_data_size() :
+ Load_log_event::get_data_size() +
+ 4 + 1 + block_len);
+ }
+ bool is_valid() const { return inited_from_old || block != 0; }
+#ifdef MYSQL_SERVER
+ bool write_data_header(IO_CACHE* file);
+ bool write_data_body(IO_CACHE* file);
+ /*
+ Cut out Create_file extentions and
+ write it as Load event - used on the slave
+ */
+ bool write_base(IO_CACHE* file);
+#endif
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Append_block_log_event
+
+ @section Append_block_log_event_binary_format Binary Format
+*/
+
+class Append_block_log_event: public Log_event
+{
+public:
+ uchar* block;
+ uint block_len;
+ uint file_id;
+ /*
+ 'db' is filled when the event is created in mysql_load() (the
+ event needs to have a 'db' member to be well filtered by
+ binlog-*-db rules). 'db' is not written to the binlog (it's not
+ used by Append_block_log_event::write()), so it can't be read in
+ the Append_block_log_event(const char* buf, int event_len)
+ constructor. In other words, 'db' is used only for filtering by
+ binlog-*-db rules. Create_file_log_event is different: it's 'db'
+ (which is inherited from Load_log_event) is written to the binlog
+ and can be re-read.
+ */
+ const char* db;
+
+#ifdef MYSQL_SERVER
+ Append_block_log_event(THD* thd, const char* db_arg, uchar* block_arg,
+ uint block_len_arg, bool using_trans);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+ virtual int get_create_or_append() const;
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Append_block_log_event(const char* buf, uint event_len,
+ const Format_description_log_event
+ *description_event);
+ ~Append_block_log_event() {}
+ Log_event_type get_type_code() { return APPEND_BLOCK_EVENT;}
+ int get_data_size() { return block_len + APPEND_BLOCK_HEADER_LEN ;}
+ bool is_valid() const { return block != 0; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+ const char* get_db() { return db; }
+#endif
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Delete_file_log_event
+
+ @section Delete_file_log_event_binary_format Binary Format
+*/
+
+class Delete_file_log_event: public Log_event
+{
+public:
+ uint file_id;
+ const char* db; /* see comment in Append_block_log_event */
+
+#ifdef MYSQL_SERVER
+ Delete_file_log_event(THD* thd, const char* db_arg, bool using_trans);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info,
+ bool enable_local);
+#endif
+
+ Delete_file_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event);
+ ~Delete_file_log_event() {}
+ Log_event_type get_type_code() { return DELETE_FILE_EVENT;}
+ int get_data_size() { return DELETE_FILE_HEADER_LEN ;}
+ bool is_valid() const { return file_id != 0; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+ const char* get_db() { return db; }
+#endif
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Execute_load_log_event
+
+ @section Delete_file_log_event_binary_format Binary Format
+*/
+
+class Execute_load_log_event: public Log_event
+{
+public:
+ uint file_id;
+ const char* db; /* see comment in Append_block_log_event */
+
+#ifdef MYSQL_SERVER
+ Execute_load_log_event(THD* thd, const char* db_arg, bool using_trans);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+#endif
+
+ Execute_load_log_event(const char* buf, uint event_len,
+ const Format_description_log_event
+ *description_event);
+ ~Execute_load_log_event() {}
+ Log_event_type get_type_code() { return EXEC_LOAD_EVENT;}
+ int get_data_size() { return EXEC_LOAD_HEADER_LEN ;}
+ bool is_valid() const { return file_id != 0; }
+#ifdef MYSQL_SERVER
+ bool write(IO_CACHE* file);
+ const char* get_db() { return db; }
+#endif
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+#endif
+};
+
+
+/**
+ @class Begin_load_query_log_event
+
+ Event for the first block of file to be loaded, its only difference from
+ Append_block event is that this event creates or truncates existing file
+ before writing data.
+
+ @section Begin_load_query_log_event_binary_format Binary Format
+*/
+class Begin_load_query_log_event: public Append_block_log_event
+{
+public:
+#ifdef MYSQL_SERVER
+ Begin_load_query_log_event(THD* thd_arg, const char *db_arg,
+ uchar* block_arg, uint block_len_arg,
+ bool using_trans);
+#ifdef HAVE_REPLICATION
+ Begin_load_query_log_event(THD* thd);
+ int get_create_or_append() const;
+#endif /* HAVE_REPLICATION */
+#endif
+ Begin_load_query_log_event(const char* buf, uint event_len,
+ const Format_description_log_event
+ *description_event);
+ ~Begin_load_query_log_event() {}
+ Log_event_type get_type_code() { return BEGIN_LOAD_QUERY_EVENT; }
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+};
+
+
+/*
+ Elements of this enum describe how LOAD DATA handles duplicates.
+*/
+enum enum_load_dup_handling { LOAD_DUP_ERROR= 0, LOAD_DUP_IGNORE,
+ LOAD_DUP_REPLACE };
+
+/**
+ @class Execute_load_query_log_event
+
+ Event responsible for LOAD DATA execution, it similar to Query_log_event
+ but before executing the query it substitutes original filename in LOAD DATA
+ query with name of temporary file.
+
+ @section Execute_load_query_log_event_binary_format Binary Format
+*/
+class Execute_load_query_log_event: public Query_log_event
+{
+public:
+ uint file_id; // file_id of temporary file
+ uint fn_pos_start; // pointer to the part of the query that should
+ // be substituted
+ uint fn_pos_end; // pointer to the end of this part of query
+ /*
+ We have to store type of duplicate handling explicitly, because
+ for LOAD DATA it also depends on LOCAL option. And this part
+ of query will be rewritten during replication so this information
+ may be lost...
+ */
+ enum_load_dup_handling dup_handling;
+
+#ifdef MYSQL_SERVER
+ Execute_load_query_log_event(THD* thd, const char* query_arg,
+ ulong query_length, uint fn_pos_start_arg,
+ uint fn_pos_end_arg,
+ enum_load_dup_handling dup_handling_arg,
+ bool using_trans, bool direct,
+ bool suppress_use, int errcode);
+#ifdef HAVE_REPLICATION
+ void pack_info(THD *thd, Protocol* protocol);
+#endif /* HAVE_REPLICATION */
+#else
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+ /* Prints the query as LOAD DATA LOCAL and with rewritten filename */
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info,
+ const char *local_fname);
+#endif
+ Execute_load_query_log_event(const char* buf, uint event_len,
+ const Format_description_log_event
+ *description_event);
+ ~Execute_load_query_log_event() {}
+
+ Log_event_type get_type_code() { return EXECUTE_LOAD_QUERY_EVENT; }
+ bool is_valid() const { return Query_log_event::is_valid() && file_id != 0; }
+
+ ulong get_post_header_size_for_derived();
+#ifdef MYSQL_SERVER
+ bool write_post_header_for_derived(IO_CACHE* file);
+#endif
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+#endif
+};
+
+
+#ifdef MYSQL_CLIENT
+/**
+ @class Unknown_log_event
+
+ @section Unknown_log_event_binary_format Binary Format
+*/
+class Unknown_log_event: public Log_event
+{
+public:
+ /*
+ Even if this is an unknown event, we still pass description_event to
+ Log_event's ctor, this way we can extract maximum information from the
+ event's header (the unique ID for example).
+ */
+ Unknown_log_event(const char* buf,
+ const Format_description_log_event *description_event):
+ Log_event(buf, description_event)
+ {}
+ ~Unknown_log_event() {}
+ void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
+ Log_event_type get_type_code() { return UNKNOWN_EVENT;}
+ bool is_valid() const { return 1; }
+};
+#endif
+char *str_to_hex(char *to, const char *from, uint len);
+
+/**
+ @class Annotate_rows_log_event
+
+ In row-based mode, if binlog_annotate_row_events = ON, each group of
+ Table_map_log_events is preceded by an Annotate_rows_log_event which
+ contains the query which caused the subsequent rows operations.
+
+ The Annotate_rows_log_event has no post-header and its body contains
+ the corresponding query (without trailing zero). Note. The query length
+ is to be calculated as a difference between the whole event length and
+ the common header length.
+*/
+class Annotate_rows_log_event: public Log_event
+{
+public:
+#ifndef MYSQL_CLIENT
+ Annotate_rows_log_event(THD*, bool using_trans, bool direct);
+#endif
+ Annotate_rows_log_event(const char *buf, uint event_len,
+ const Format_description_log_event*);
+ ~Annotate_rows_log_event();
+
+ virtual int get_data_size();
+ virtual Log_event_type get_type_code();
+ virtual bool is_valid() const;
+ virtual bool is_part_of_group() { return 1; }
+
+#ifndef MYSQL_CLIENT
+ virtual bool write_data_header(IO_CACHE*);
+ virtual bool write_data_body(IO_CACHE*);
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ virtual void pack_info(THD *thd, Protocol*);
+#endif
+
+#ifdef MYSQL_CLIENT
+ virtual void print(FILE*, PRINT_EVENT_INFO*);
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+private:
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info*);
+#endif
+
+private:
+ char *m_query_txt;
+ uint m_query_len;
+ char *m_save_thd_query_txt;
+ uint m_save_thd_query_len;
+};
+
+/**
+ @class Table_map_log_event
+
+ In row-based mode, every row operation event is preceded by a
+ Table_map_log_event which maps a table definition to a number. The
+ table definition consists of database name, table name, and column
+ definitions.
+
+ @section Table_map_log_event_binary_format Binary Format
+
+ The Post-Header has the following components:
+
+ <table>
+ <caption>Post-Header for Table_map_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>table_id</td>
+ <td>6 bytes unsigned integer</td>
+ <td>The number that identifies the table.</td>
+ </tr>
+
+ <tr>
+ <td>flags</td>
+ <td>2 byte bitfield</td>
+ <td>Reserved for future use; currently always 0.</td>
+ </tr>
+
+ </table>
+
+ The Body has the following components:
+
+ <table>
+ <caption>Body for Table_map_log_event</caption>
+
+ <tr>
+ <th>Name</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>database_name</td>
+ <td>one byte string length, followed by null-terminated string</td>
+ <td>The name of the database in which the table resides. The name
+ is represented as a one byte unsigned integer representing the
+ number of bytes in the name, followed by length bytes containing
+ the database name, followed by a terminating 0 byte. (Note the
+ redundancy in the representation of the length.) </td>
+ </tr>
+
+ <tr>
+ <td>table_name</td>
+ <td>one byte string length, followed by null-terminated string</td>
+ <td>The name of the table, encoded the same way as the database
+ name above.</td>
+ </tr>
+
+ <tr>
+ <td>column_count</td>
+ <td>@ref packed_integer "Packed Integer"</td>
+ <td>The number of columns in the table, represented as a packed
+ variable-length integer.</td>
+ </tr>
+
+ <tr>
+ <td>column_type</td>
+ <td>List of column_count 1 byte enumeration values</td>
+ <td>The type of each column in the table, listed from left to
+ right. Each byte is mapped to a column type according to the
+ enumeration type enum_field_types defined in mysql_com.h. The
+ mapping of types to numbers is listed in the table @ref
+ Table_table_map_log_event_column_types "below" (along with
+ description of the associated metadata field). </td>
+ </tr>
+
+ <tr>
+ <td>metadata_length</td>
+ <td>@ref packed_integer "Packed Integer"</td>
+ <td>The length of the following metadata block</td>
+ </tr>
+
+ <tr>
+ <td>metadata</td>
+ <td>list of metadata for each column</td>
+ <td>For each column from left to right, a chunk of data who's
+ length and semantics depends on the type of the column. The
+ length and semantics for the metadata for each column are listed
+ in the table @ref Table_table_map_log_event_column_types
+ "below".</td>
+ </tr>
+
+ <tr>
+ <td>null_bits</td>
+ <td>column_count bits, rounded up to nearest byte</td>
+ <td>For each column, a bit indicating whether data in the column
+ can be NULL or not. The number of bytes needed for this is
+ int((column_count+7)/8). The flag for the first column from the
+ left is in the least-significant bit of the first byte, the second
+ is in the second least significant bit of the first byte, the
+ ninth is in the least significant bit of the second byte, and so
+ on. </td>
+ </tr>
+
+ </table>
+
+ The table below lists all column types, along with the numerical
+ identifier for it and the size and interpretation of meta-data used
+ to describe the type.
+
+ @anchor Table_table_map_log_event_column_types
+ <table>
+ <caption>Table_map_log_event column types: numerical identifier and
+ metadata</caption>
+ <tr>
+ <th>Name</th>
+ <th>Identifier</th>
+ <th>Size of metadata in bytes</th>
+ <th>Description of metadata</th>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_DECIMAL</td><td>0</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_TINY</td><td>1</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_SHORT</td><td>2</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_LONG</td><td>3</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_FLOAT</td><td>4</td>
+ <td>1 byte</td>
+ <td>1 byte unsigned integer, representing the "pack_length", which
+ is equal to sizeof(float) on the server from which the event
+ originates.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_DOUBLE</td><td>5</td>
+ <td>1 byte</td>
+ <td>1 byte unsigned integer, representing the "pack_length", which
+ is equal to sizeof(double) on the server from which the event
+ originates.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_NULL</td><td>6</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_TIMESTAMP</td><td>7</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_LONGLONG</td><td>8</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_INT24</td><td>9</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_DATE</td><td>10</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_TIME</td><td>11</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_DATETIME</td><td>12</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_YEAR</td><td>13</td>
+ <td>0</td>
+ <td>No column metadata.</td>
+ </tr>
+
+ <tr>
+ <td><i>MYSQL_TYPE_NEWDATE</i></td><td><i>14</i></td>
+ <td>&ndash;</td>
+ <td><i>This enumeration value is only used internally and cannot
+ exist in a binlog.</i></td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_VARCHAR</td><td>15</td>
+ <td>2 bytes</td>
+ <td>2 byte unsigned integer representing the maximum length of
+ the string.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_BIT</td><td>16</td>
+ <td>2 bytes</td>
+ <td>A 1 byte unsigned int representing the length in bits of the
+ bitfield (0 to 64), followed by a 1 byte unsigned int
+ representing the number of bytes occupied by the bitfield. The
+ number of bytes is either int((length+7)/8) or int(length/8).</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_NEWDECIMAL</td><td>246</td>
+ <td>2 bytes</td>
+ <td>A 1 byte unsigned int representing the precision, followed
+ by a 1 byte unsigned int representing the number of decimals.</td>
+ </tr>
+
+ <tr>
+ <td><i>MYSQL_TYPE_ENUM</i></td><td><i>247</i></td>
+ <td>&ndash;</td>
+ <td><i>This enumeration value is only used internally and cannot
+ exist in a binlog.</i></td>
+ </tr>
+
+ <tr>
+ <td><i>MYSQL_TYPE_SET</i></td><td><i>248</i></td>
+ <td>&ndash;</td>
+ <td><i>This enumeration value is only used internally and cannot
+ exist in a binlog.</i></td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_TINY_BLOB</td><td>249</td>
+ <td>&ndash;</td>
+ <td><i>This enumeration value is only used internally and cannot
+ exist in a binlog.</i></td>
+ </tr>
+
+ <tr>
+ <td><i>MYSQL_TYPE_MEDIUM_BLOB</i></td><td><i>250</i></td>
+ <td>&ndash;</td>
+ <td><i>This enumeration value is only used internally and cannot
+ exist in a binlog.</i></td>
+ </tr>
+
+ <tr>
+ <td><i>MYSQL_TYPE_LONG_BLOB</i></td><td><i>251</i></td>
+ <td>&ndash;</td>
+ <td><i>This enumeration value is only used internally and cannot
+ exist in a binlog.</i></td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_BLOB</td><td>252</td>
+ <td>1 byte</td>
+ <td>The pack length, i.e., the number of bytes needed to represent
+ the length of the blob: 1, 2, 3, or 4.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_VAR_STRING</td><td>253</td>
+ <td>2 bytes</td>
+ <td>This is used to store both strings and enumeration values.
+ The first byte is a enumeration value storing the <i>real
+ type</i>, which may be either MYSQL_TYPE_VAR_STRING or
+ MYSQL_TYPE_ENUM. The second byte is a 1 byte unsigned integer
+ representing the field size, i.e., the number of bytes needed to
+ store the length of the string.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_STRING</td><td>254</td>
+ <td>2 bytes</td>
+ <td>The first byte is always MYSQL_TYPE_VAR_STRING (i.e., 253).
+ The second byte is the field size, i.e., the number of bytes in
+ the representation of size of the string: 3 or 4.</td>
+ </tr>
+
+ <tr>
+ <td>MYSQL_TYPE_GEOMETRY</td><td>255</td>
+ <td>1 byte</td>
+ <td>The pack length, i.e., the number of bytes needed to represent
+ the length of the geometry: 1, 2, 3, or 4.</td>
+ </tr>
+
+ </table>
+*/
+class Table_map_log_event : public Log_event
+{
+public:
+ /* Constants */
+ enum
+ {
+ TYPE_CODE = TABLE_MAP_EVENT
+ };
+
+ /**
+ Enumeration of the errors that can be returned.
+ */
+ enum enum_error
+ {
+ ERR_OPEN_FAILURE = -1, /**< Failure to open table */
+ ERR_OK = 0, /**< No error */
+ ERR_TABLE_LIMIT_EXCEEDED = 1, /**< No more room for tables */
+ ERR_OUT_OF_MEM = 2, /**< Out of memory */
+ ERR_BAD_TABLE_DEF = 3, /**< Table definition does not match */
+ ERR_RBR_TO_SBR = 4 /**< daisy-chanining RBR to SBR not allowed */
+ };
+
+ enum enum_flag
+ {
+ /*
+ Nothing here right now, but the flags support is there in
+ preparation for changes that are coming. Need to add a
+ constant to make it compile under HP-UX: aCC does not like
+ empty enumerations.
+ */
+ ENUM_FLAG_COUNT
+ };
+
+ typedef uint16 flag_set;
+
+ /* Special constants representing sets of flags */
+ enum
+ {
+ TM_NO_FLAGS = 0U,
+ TM_BIT_LEN_EXACT_F = (1U << 0),
+ // MariaDB flags (we starts from the other end)
+ TM_BIT_HAS_TRIGGERS_F= (1U << 14)
+ };
+
+ flag_set get_flags(flag_set flag) const { return m_flags & flag; }
+
+#ifdef MYSQL_SERVER
+ Table_map_log_event(THD *thd, TABLE *tbl, ulong tid, bool is_transactional);
+#endif
+#ifdef HAVE_REPLICATION
+ Table_map_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+#endif
+
+ ~Table_map_log_event();
+
+#ifdef MYSQL_CLIENT
+ table_def *create_table_def()
+ {
+ return new table_def(m_coltype, m_colcnt, m_field_metadata,
+ m_field_metadata_size, m_null_bits, m_flags);
+ }
+ int rewrite_db(const char* new_name, size_t new_name_len,
+ const Format_description_log_event*);
+#endif
+ ulong get_table_id() const { return m_table_id; }
+ const char *get_table_name() const { return m_tblnam; }
+ const char *get_db_name() const { return m_dbnam; }
+
+ virtual Log_event_type get_type_code() { return TABLE_MAP_EVENT; }
+ virtual bool is_valid() const { return m_memory != NULL; /* we check malloc */ }
+ virtual bool is_part_of_group() { return 1; }
+
+ virtual int get_data_size() { return (uint) m_data_size; }
+#ifdef MYSQL_SERVER
+ virtual int save_field_metadata();
+ virtual bool write_data_header(IO_CACHE *file);
+ virtual bool write_data_body(IO_CACHE *file);
+ virtual const char *get_db() { return m_dbnam; }
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual void pack_info(THD *thd, Protocol *protocol);
+#endif
+
+#ifdef MYSQL_CLIENT
+ virtual void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+
+private:
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+#endif
+
+#ifdef MYSQL_SERVER
+ TABLE *m_table;
+#endif
+ char const *m_dbnam;
+ size_t m_dblen;
+ char const *m_tblnam;
+ size_t m_tbllen;
+ ulong m_colcnt;
+ uchar *m_coltype;
+
+ uchar *m_memory;
+ ulong m_table_id;
+ flag_set m_flags;
+
+ size_t m_data_size;
+
+ uchar *m_field_metadata; // buffer for field metadata
+ /*
+ The size of field metadata buffer set by calling save_field_metadata()
+ */
+ ulong m_field_metadata_size;
+ uchar *m_null_bits;
+ uchar *m_meta_memory;
+};
+
+
+/**
+ @class Rows_log_event
+
+ Common base class for all row-containing log events.
+
+ RESPONSIBILITIES
+
+ Encode the common parts of all events containing rows, which are:
+ - Write data header and data body to an IO_CACHE.
+ - Provide an interface for adding an individual row to the event.
+
+ @section Rows_log_event_binary_format Binary Format
+*/
+
+
+class Rows_log_event : public Log_event
+{
+public:
+ /**
+ Enumeration of the errors that can be returned.
+ */
+ enum enum_error
+ {
+ ERR_OPEN_FAILURE = -1, /**< Failure to open table */
+ ERR_OK = 0, /**< No error */
+ ERR_TABLE_LIMIT_EXCEEDED = 1, /**< No more room for tables */
+ ERR_OUT_OF_MEM = 2, /**< Out of memory */
+ ERR_BAD_TABLE_DEF = 3, /**< Table definition does not match */
+ ERR_RBR_TO_SBR = 4 /**< daisy-chanining RBR to SBR not allowed */
+ };
+
+ /*
+ These definitions allow you to combine the flags into an
+ appropriate flag set using the normal bitwise operators. The
+ implicit conversion from an enum-constant to an integer is
+ accepted by the compiler, which is then used to set the real set
+ of flags.
+ */
+ enum enum_flag
+ {
+ /* Last event of a statement */
+ STMT_END_F = (1U << 0),
+
+ /* Value of the OPTION_NO_FOREIGN_KEY_CHECKS flag in thd->options */
+ NO_FOREIGN_KEY_CHECKS_F = (1U << 1),
+
+ /* Value of the OPTION_RELAXED_UNIQUE_CHECKS flag in thd->options */
+ RELAXED_UNIQUE_CHECKS_F = (1U << 2),
+
+ /**
+ Indicates that rows in this event are complete, that is contain
+ values for all columns of the table.
+ */
+ COMPLETE_ROWS_F = (1U << 3)
+ };
+
+ typedef uint16 flag_set;
+
+ /* Special constants representing sets of flags */
+ enum
+ {
+ RLE_NO_FLAGS = 0U
+ };
+
+ virtual ~Rows_log_event();
+
+ void set_flags(flag_set flags_arg) { m_flags |= flags_arg; }
+ void clear_flags(flag_set flags_arg) { m_flags &= ~flags_arg; }
+ flag_set get_flags(flag_set flags_arg) const { return m_flags & flags_arg; }
+
+ Log_event_type get_type_code() { return m_type; } /* Specific type (_V1 etc) */
+ virtual Log_event_type get_general_type_code() = 0; /* General rows op type, no version */
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual void pack_info(THD *thd, Protocol *protocol);
+#endif
+
+#ifdef MYSQL_CLIENT
+ /* not for direct call, each derived has its own ::print() */
+ virtual void print(FILE *file, PRINT_EVENT_INFO *print_event_info)= 0;
+ void print_verbose(IO_CACHE *file,
+ PRINT_EVENT_INFO *print_event_info);
+ size_t print_verbose_one_row(IO_CACHE *file, table_def *td,
+ PRINT_EVENT_INFO *print_event_info,
+ MY_BITMAP *cols_bitmap,
+ const uchar *ptr, const uchar *prefix);
+#endif
+
+#ifdef MYSQL_SERVER
+ int add_row_data(uchar *data, size_t length)
+ {
+ return do_add_row_data(data,length);
+ }
+#endif
+
+ /* Member functions to implement superclass interface */
+ virtual int get_data_size();
+
+ MY_BITMAP const *get_cols() const { return &m_cols; }
+ size_t get_width() const { return m_width; }
+ ulong get_table_id() const { return m_table_id; }
+
+#ifdef MYSQL_SERVER
+ virtual bool write_data_header(IO_CACHE *file);
+ virtual bool write_data_body(IO_CACHE *file);
+ virtual const char *get_db() { return m_table->s->db.str; }
+#endif
+ /*
+ Check that malloc() succeeded in allocating memory for the rows
+ buffer and the COLS vector. Checking that an Update_rows_log_event
+ is valid is done in the Update_rows_log_event::is_valid()
+ function.
+ */
+ virtual bool is_valid() const
+ {
+ return m_rows_buf && m_cols.bitmap;
+ }
+ bool is_part_of_group() { return get_flags(STMT_END_F) != 0; }
+
+ uint m_row_count; /* The number of rows added to the event */
+
+ const uchar* get_extra_row_data() const { return m_extra_row_data; }
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual uint8 get_trg_event_map()= 0;
+#endif
+
+protected:
+ /*
+ The constructors are protected since you're supposed to inherit
+ this class, not create instances of this class.
+ */
+#ifdef MYSQL_SERVER
+ Rows_log_event(THD*, TABLE*, ulong table_id,
+ MY_BITMAP const *cols, bool is_transactional,
+ Log_event_type event_type);
+#endif
+ Rows_log_event(const char *row_data, uint event_len,
+ const Format_description_log_event *description_event);
+
+#ifdef MYSQL_CLIENT
+ void print_helper(FILE *, PRINT_EVENT_INFO *, char const *const name);
+#endif
+
+#ifdef MYSQL_SERVER
+ virtual int do_add_row_data(uchar *data, size_t length);
+#endif
+
+#ifdef MYSQL_SERVER
+ TABLE *m_table; /* The table the rows belong to */
+#endif
+ ulong m_table_id; /* Table ID */
+ MY_BITMAP m_cols; /* Bitmap denoting columns available */
+ ulong m_width; /* The width of the columns bitmap */
+ /*
+ Bitmap for columns available in the after image, if present. These
+ fields are only available for Update_rows events. Observe that the
+ width of both the before image COLS vector and the after image
+ COLS vector is the same: the number of columns of the table on the
+ master.
+ */
+ MY_BITMAP m_cols_ai;
+
+ ulong m_master_reclength; /* Length of record on master side */
+
+ /* Bit buffers in the same memory as the class */
+ uint32 m_bitbuf[128/(sizeof(uint32)*8)];
+ uint32 m_bitbuf_ai[128/(sizeof(uint32)*8)];
+
+ uchar *m_rows_buf; /* The rows in packed format */
+ uchar *m_rows_cur; /* One-after the end of the data */
+ uchar *m_rows_end; /* One-after the end of the allocated space */
+
+ flag_set m_flags; /* Flags for row-level events */
+
+ Log_event_type m_type; /* Actual event type */
+
+ uchar *m_extra_row_data; /* Pointer to extra row data if any */
+ /* If non null, first byte is length */
+
+
+ /* helper functions */
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ const uchar *m_curr_row; /* Start of the row being processed */
+ const uchar *m_curr_row_end; /* One-after the end of the current row */
+ uchar *m_key; /* Buffer to keep key value during searches */
+ KEY *m_key_info; /* Pointer to KEY info for m_key_nr */
+ uint m_key_nr; /* Key number */
+ bool master_had_triggers; /* set after tables opening */
+
+ int find_key(); // Find a best key to use in find_row()
+ int find_row(rpl_group_info *);
+ int write_row(rpl_group_info *, const bool);
+
+ // Unpack the current row into m_table->record[0]
+ int unpack_current_row(rpl_group_info *rgi)
+ {
+ DBUG_ASSERT(m_table);
+
+ ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT);
+ return ::unpack_row(rgi, m_table, m_width, m_curr_row, &m_cols,
+ &m_curr_row_end, &m_master_reclength, m_rows_end);
+ }
+ bool process_triggers(trg_event_type event,
+ trg_action_time_type time_type,
+ bool old_row_is_record1);
+
+ /**
+ Helper function to check whether there is an auto increment
+ column on the table where the event is to be applied.
+
+ @return true if there is an autoincrement field on the extra
+ columns, false otherwise.
+ */
+ inline bool is_auto_inc_in_extra_columns()
+ {
+ DBUG_ASSERT(m_table);
+ return (m_table->next_number_field &&
+ m_table->next_number_field->field_index >= m_width);
+ }
+#endif
+
+private:
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+
+ /*
+ Primitive to prepare for a sequence of row executions.
+
+ DESCRIPTION
+
+ Before doing a sequence of do_prepare_row() and do_exec_row()
+ calls, this member function should be called to prepare for the
+ entire sequence. Typically, this member function will allocate
+ space for any buffers that are needed for the two member
+ functions mentioned above.
+
+ RETURN VALUE
+
+ The member function will return 0 if all went OK, or a non-zero
+ error code otherwise.
+ */
+ virtual
+ int do_before_row_operations(const Slave_reporting_capability *const log) = 0;
+
+ /*
+ Primitive to clean up after a sequence of row executions.
+
+ DESCRIPTION
+
+ After doing a sequence of do_prepare_row() and do_exec_row(),
+ this member function should be called to clean up and release
+ any allocated buffers.
+
+ The error argument, if non-zero, indicates an error which happened during
+ row processing before this function was called. In this case, even if
+ function is successful, it should return the error code given in the argument.
+ */
+ virtual
+ int do_after_row_operations(const Slave_reporting_capability *const log,
+ int error) = 0;
+
+ /*
+ Primitive to do the actual execution necessary for a row.
+
+ DESCRIPTION
+ The member function will do the actual execution needed to handle a row.
+ The row is located at m_curr_row. When the function returns,
+ m_curr_row_end should point at the next row (one byte after the end
+ of the current row).
+
+ RETURN VALUE
+ 0 if execution succeeded, 1 if execution failed.
+
+ */
+ virtual int do_exec_row(rpl_group_info *rli) = 0;
+#endif /* defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) */
+
+ friend class Old_rows_log_event;
+};
+
+/**
+ @class Write_rows_log_event
+
+ Log row insertions and updates. The event contain several
+ insert/update rows for a table. Note that each event contains only
+ rows for one table.
+
+ @section Write_rows_log_event_binary_format Binary Format
+*/
+class Write_rows_log_event : public Rows_log_event
+{
+public:
+ enum
+ {
+ /* Support interface to THD::binlog_prepare_pending_rows_event */
+ TYPE_CODE = WRITE_ROWS_EVENT
+ };
+
+#if defined(MYSQL_SERVER)
+ Write_rows_log_event(THD*, TABLE*, ulong table_id,
+ MY_BITMAP const *cols, bool is_transactional);
+#endif
+#ifdef HAVE_REPLICATION
+ Write_rows_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+#endif
+#if defined(MYSQL_SERVER)
+ static bool binlog_row_logging_function(THD *thd, TABLE *table,
+ bool is_transactional,
+ MY_BITMAP *cols,
+ uint fields,
+ const uchar *before_record
+ __attribute__((unused)),
+ const uchar *after_record)
+ {
+ return thd->binlog_write_row(table, is_transactional,
+ cols, fields, after_record);
+ }
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ uint8 get_trg_event_map();
+#endif
+
+private:
+ virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
+
+#ifdef MYSQL_CLIENT
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_before_row_operations(const Slave_reporting_capability *const);
+ virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
+ virtual int do_exec_row(rpl_group_info *);
+#endif
+};
+
+
+/**
+ @class Update_rows_log_event
+
+ Log row updates with a before image. The event contain several
+ update rows for a table. Note that each event contains only rows for
+ one table.
+
+ Also note that the row data consists of pairs of row data: one row
+ for the old data and one row for the new data.
+
+ @section Update_rows_log_event_binary_format Binary Format
+*/
+class Update_rows_log_event : public Rows_log_event
+{
+public:
+ enum
+ {
+ /* Support interface to THD::binlog_prepare_pending_rows_event */
+ TYPE_CODE = UPDATE_ROWS_EVENT
+ };
+
+#ifdef MYSQL_SERVER
+ Update_rows_log_event(THD*, TABLE*, ulong table_id,
+ MY_BITMAP const *cols_bi,
+ MY_BITMAP const *cols_ai,
+ bool is_transactional);
+
+ Update_rows_log_event(THD*, TABLE*, ulong table_id,
+ MY_BITMAP const *cols,
+ bool is_transactional);
+
+ void init(MY_BITMAP const *cols);
+#endif
+
+ virtual ~Update_rows_log_event();
+
+#ifdef HAVE_REPLICATION
+ Update_rows_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+#endif
+
+#ifdef MYSQL_SERVER
+ static bool binlog_row_logging_function(THD *thd, TABLE *table,
+ bool is_transactional,
+ MY_BITMAP *cols,
+ uint fields,
+ const uchar *before_record,
+ const uchar *after_record)
+ {
+ return thd->binlog_update_row(table, is_transactional,
+ cols, fields, before_record, after_record);
+ }
+#endif
+
+ virtual bool is_valid() const
+ {
+ return Rows_log_event::is_valid() && m_cols_ai.bitmap;
+ }
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ uint8 get_trg_event_map();
+#endif
+
+protected:
+ virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
+
+#ifdef MYSQL_CLIENT
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_before_row_operations(const Slave_reporting_capability *const);
+ virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
+ virtual int do_exec_row(rpl_group_info *);
+#endif /* defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) */
+};
+
+/**
+ @class Delete_rows_log_event
+
+ Log row deletions. The event contain several delete rows for a
+ table. Note that each event contains only rows for one table.
+
+ RESPONSIBILITIES
+
+ - Act as a container for rows that has been deleted on the master
+ and should be deleted on the slave.
+
+ COLLABORATION
+
+ Row_writer
+ Create the event and add rows to the event.
+ Row_reader
+ Extract the rows from the event.
+
+ @section Delete_rows_log_event_binary_format Binary Format
+*/
+class Delete_rows_log_event : public Rows_log_event
+{
+public:
+ enum
+ {
+ /* Support interface to THD::binlog_prepare_pending_rows_event */
+ TYPE_CODE = DELETE_ROWS_EVENT
+ };
+
+#ifdef MYSQL_SERVER
+ Delete_rows_log_event(THD*, TABLE*, ulong,
+ MY_BITMAP const *cols, bool is_transactional);
+#endif
+#ifdef HAVE_REPLICATION
+ Delete_rows_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+#endif
+#ifdef MYSQL_SERVER
+ static bool binlog_row_logging_function(THD *thd, TABLE *table,
+ bool is_transactional,
+ MY_BITMAP *cols,
+ uint fields,
+ const uchar *before_record,
+ const uchar *after_record
+ __attribute__((unused)))
+ {
+ return thd->binlog_delete_row(table, is_transactional,
+ cols, fields, before_record);
+ }
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ uint8 get_trg_event_map();
+#endif
+
+protected:
+ virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
+
+#ifdef MYSQL_CLIENT
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_before_row_operations(const Slave_reporting_capability *const);
+ virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
+ virtual int do_exec_row(rpl_group_info *);
+#endif
+};
+
+
+#include "log_event_old.h"
+
+/**
+ @class Incident_log_event
+
+ Class representing an incident, an occurance out of the ordinary,
+ that happened on the master.
+
+ The event is used to inform the slave that something out of the
+ ordinary happened on the master that might cause the database to be
+ in an inconsistent state.
+
+ <table id="IncidentFormat">
+ <caption>Incident event format</caption>
+ <tr>
+ <th>Symbol</th>
+ <th>Format</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>INCIDENT</td>
+ <td align="right">2</td>
+ <td>Incident number as an unsigned integer</td>
+ </tr>
+ <tr>
+ <td>MSGLEN</td>
+ <td align="right">1</td>
+ <td>Message length as an unsigned integer</td>
+ </tr>
+ <tr>
+ <td>MESSAGE</td>
+ <td align="right">MSGLEN</td>
+ <td>The message, if present. Not null terminated.</td>
+ </tr>
+ </table>
+
+ @section Delete_rows_log_event_binary_format Binary Format
+*/
+class Incident_log_event : public Log_event {
+public:
+#ifdef MYSQL_SERVER
+ Incident_log_event(THD *thd_arg, Incident incident)
+ : Log_event(thd_arg, 0, FALSE), m_incident(incident)
+ {
+ DBUG_ENTER("Incident_log_event::Incident_log_event");
+ DBUG_PRINT("enter", ("m_incident: %d", m_incident));
+ m_message.str= NULL; /* Just as a precaution */
+ m_message.length= 0;
+ set_direct_logging();
+ /* Replicate the incident irregardless of @@skip_replication. */
+ flags&= ~LOG_EVENT_SKIP_REPLICATION_F;
+ DBUG_VOID_RETURN;
+ }
+
+ Incident_log_event(THD *thd_arg, Incident incident, LEX_STRING const msg)
+ : Log_event(thd_arg, 0, FALSE), m_incident(incident)
+ {
+ DBUG_ENTER("Incident_log_event::Incident_log_event");
+ DBUG_PRINT("enter", ("m_incident: %d", m_incident));
+ m_message.str= NULL;
+ m_message.length= 0;
+ if (!(m_message.str= (char*) my_malloc(msg.length+1, MYF(MY_WME))))
+ {
+ /* Mark this event invalid */
+ m_incident= INCIDENT_NONE;
+ DBUG_VOID_RETURN;
+ }
+ strmake(m_message.str, msg.str, msg.length);
+ m_message.length= msg.length;
+ set_direct_logging();
+ /* Replicate the incident irregardless of @@skip_replication. */
+ flags&= ~LOG_EVENT_SKIP_REPLICATION_F;
+ DBUG_VOID_RETURN;
+ }
+#endif
+
+#ifdef MYSQL_SERVER
+ void pack_info(THD *thd, Protocol*);
+#endif
+
+ Incident_log_event(const char *buf, uint event_len,
+ const Format_description_log_event *descr_event);
+
+ virtual ~Incident_log_event();
+
+#ifdef MYSQL_CLIENT
+ virtual void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+#endif
+
+ virtual bool write_data_header(IO_CACHE *file);
+ virtual bool write_data_body(IO_CACHE *file);
+
+ virtual Log_event_type get_type_code() { return INCIDENT_EVENT; }
+
+ virtual bool is_valid() const
+ {
+ return m_incident > INCIDENT_NONE && m_incident < INCIDENT_COUNT;
+ }
+ virtual int get_data_size() {
+ return INCIDENT_HEADER_LEN + 1 + (uint) m_message.length;
+ }
+
+private:
+ const char *description() const;
+
+ Incident m_incident;
+ LEX_STRING m_message;
+};
+
+static inline bool copy_event_cache_to_file_and_reinit(IO_CACHE *cache,
+ FILE *file)
+{
+ return
+ my_b_copy_to_file(cache, file) ||
+ reinit_io_cache(cache, WRITE_CACHE, 0, FALSE, TRUE);
+}
+
+#ifdef MYSQL_SERVER
+/*****************************************************************************
+
+ Heartbeat Log Event class
+
+ Replication event to ensure to slave that master is alive.
+ The event is originated by master's dump thread and sent straight to
+ slave without being logged. Slave itself does not store it in relay log
+ but rather uses a data for immediate checks and throws away the event.
+
+ Two members of the class log_ident and Log_event::log_pos comprise
+ @see the event_coordinates instance. The coordinates that a heartbeat
+ instance carries correspond to the last event master has sent from
+ its binlog.
+
+ ****************************************************************************/
+class Heartbeat_log_event: public Log_event
+{
+public:
+ Heartbeat_log_event(const char* buf, uint event_len,
+ const Format_description_log_event* description_event);
+ Log_event_type get_type_code() { return HEARTBEAT_LOG_EVENT; }
+ bool is_valid() const
+ {
+ return (log_ident != NULL &&
+ log_pos >= BIN_LOG_HEADER_SIZE);
+ }
+ const char * get_log_ident() { return log_ident; }
+ uint get_ident_len() { return ident_len; }
+
+private:
+ const char* log_ident;
+ uint ident_len;
+};
+
+/**
+ The function is called by slave applier in case there are
+ active table filtering rules to force gathering events associated
+ with Query-log-event into an array to execute
+ them once the fate of the Query is determined for execution.
+*/
+bool slave_execute_deferred_events(THD *thd);
+#endif
+
+bool rpl_get_position_info(const char **log_file_name, ulonglong *log_pos,
+ const char **group_relay_log_name,
+ ulonglong *relay_log_pos);
+
+bool event_checksum_test(uchar *buf, ulong event_len, uint8 alg);
+uint8 get_checksum_alg(const char* buf, ulong len);
+extern TYPELIB binlog_checksum_typelib;
+
+/**
+ @} (end of group Replication)
+*/
+
+#endif /* _log_event_h */
diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc
new file mode 100644
index 00000000000..e6c05aeb849
--- /dev/null
+++ b/sql/log_event_old.cc
@@ -0,0 +1,2850 @@
+/* Copyright (c) 2007, 2013, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#ifndef MYSQL_CLIENT
+#include "unireg.h"
+#endif
+#include "log_event.h"
+#ifndef MYSQL_CLIENT
+#include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE
+#include "sql_base.h" // close_tables_for_reopen
+#include "key.h" // key_copy
+#include "lock.h" // mysql_unlock_tables
+#include "sql_parse.h" // mysql_reset_thd_for_next_command
+#include "rpl_rli.h"
+#include "rpl_utility.h"
+#endif
+#include "log_event_old.h"
+#include "rpl_record_old.h"
+#include "transaction.h"
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+// Old implementation of do_apply_event()
+int
+Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, rpl_group_info *rgi)
+{
+ DBUG_ENTER("Old_rows_log_event::do_apply_event(st_relay_log_info*)");
+ int error= 0;
+ THD *ev_thd= ev->thd;
+ uchar const *row_start= ev->m_rows_buf;
+ const Relay_log_info *rli= rgi->rli;
+
+ /*
+ If m_table_id == ~0UL, then we have a dummy event that does not
+ contain any data. In that case, we just remove all tables in the
+ tables_to_lock list, close the thread tables, and return with
+ success.
+ */
+ if (ev->m_table_id == ~0UL)
+ {
+ /*
+ This one is supposed to be set: just an extra check so that
+ nothing strange has happened.
+ */
+ DBUG_ASSERT(ev->get_flags(Old_rows_log_event::STMT_END_F));
+
+ rgi->slave_close_thread_tables(ev_thd);
+ ev_thd->clear_error();
+ DBUG_RETURN(0);
+ }
+
+ /*
+ 'ev_thd' has been set by exec_relay_log_event(), just before calling
+ do_apply_event(). We still check here to prevent future coding
+ errors.
+ */
+ DBUG_ASSERT(rgi->thd == ev_thd);
+
+ /*
+ If there is no locks taken, this is the first binrow event seen
+ after the table map events. We should then lock all the tables
+ used in the transaction and proceed with execution of the actual
+ event.
+ */
+ if (!ev_thd->lock)
+ {
+ /*
+ Lock_tables() reads the contents of ev_thd->lex, so they must be
+ initialized.
+
+ We also call the mysql_reset_thd_for_next_command(), since this
+ is the logical start of the next "statement". Note that this
+ call might reset the value of current_stmt_binlog_format, so
+ we need to do any changes to that value after this function.
+ */
+ delete_explain_query(thd->lex);
+ lex_start(ev_thd);
+ mysql_reset_thd_for_next_command(ev_thd);
+
+ /*
+ This is a row injection, so we flag the "statement" as
+ such. Note that this code is called both when the slave does row
+ injections and when the BINLOG statement is used to do row
+ injections.
+ */
+ ev_thd->lex->set_stmt_row_injection();
+
+ if (open_and_lock_tables(ev_thd, rgi->tables_to_lock, FALSE, 0))
+ {
+ uint actual_error= ev_thd->get_stmt_da()->sql_errno();
+ if (ev_thd->is_slave_error || ev_thd->is_fatal_error)
+ {
+ /*
+ Error reporting borrowed from Query_log_event with many excessive
+ simplifications (we don't honour --slave-skip-errors)
+ */
+ rli->report(ERROR_LEVEL, actual_error, NULL,
+ "Error '%s' on opening tables",
+ (actual_error ? ev_thd->get_stmt_da()->message() :
+ "unexpected success or fatal error"));
+ ev_thd->is_slave_error= 1;
+ }
+ rgi->slave_close_thread_tables(thd);
+ DBUG_RETURN(actual_error);
+ }
+
+ /*
+ When the open and locking succeeded, we check all tables to
+ ensure that they still have the correct type.
+
+ We can use a down cast here since we know that every table added
+ to the tables_to_lock is a RPL_TABLE_LIST.
+ */
+
+ {
+ RPL_TABLE_LIST *ptr= rgi->tables_to_lock;
+ for (uint i= 0 ; ptr&& (i< rgi->tables_to_lock_count);
+ ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_global), i++)
+ {
+ DBUG_ASSERT(ptr->m_tabledef_valid);
+ TABLE *conv_table;
+ if (!ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table))
+ {
+ ev_thd->is_slave_error= 1;
+ rgi->slave_close_thread_tables(ev_thd);
+ DBUG_RETURN(Old_rows_log_event::ERR_BAD_TABLE_DEF);
+ }
+ DBUG_PRINT("debug", ("Table: %s.%s is compatible with master"
+ " - conv_table: %p",
+ ptr->table->s->db.str,
+ ptr->table->s->table_name.str, conv_table));
+ ptr->m_conv_table= conv_table;
+ }
+ }
+
+ /*
+ ... and then we add all the tables to the table map and remove
+ them from tables to lock.
+
+ We also invalidate the query cache for all the tables, since
+ they will now be changed.
+
+ TODO [/Matz]: Maybe the query cache should not be invalidated
+ here? It might be that a table is not changed, even though it
+ was locked for the statement. We do know that each
+ Old_rows_log_event contain at least one row, so after processing one
+ Old_rows_log_event, we can invalidate the query cache for the
+ associated table.
+ */
+ TABLE_LIST *ptr= rgi->tables_to_lock;
+ for (uint i=0; ptr && (i < rgi->tables_to_lock_count); ptr= ptr->next_global, i++)
+ rgi->m_table_map.set_table(ptr->table_id, ptr->table);
+#ifdef HAVE_QUERY_CACHE
+ query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock);
+#endif
+ }
+
+ TABLE* table= rgi->m_table_map.get_table(ev->m_table_id);
+
+ if (table)
+ {
+ /*
+ table == NULL means that this table should not be replicated
+ (this was set up by Table_map_log_event::do_apply_event()
+ which tested replicate-* rules).
+ */
+
+ /*
+ It's not needed to set_time() but
+ 1) it continues the property that "Time" in SHOW PROCESSLIST shows how
+ much slave is behind
+ 2) it will be needed when we allow replication from a table with no
+ TIMESTAMP column to a table with one.
+ So we call set_time(), like in SBR. Presently it changes nothing.
+ */
+ ev_thd->set_time(ev->when, ev->when_sec_part);
+ /*
+ There are a few flags that are replicated with each row event.
+ Make sure to set/clear them before executing the main body of
+ the event.
+ */
+ if (ev->get_flags(Old_rows_log_event::NO_FOREIGN_KEY_CHECKS_F))
+ ev_thd->variables.option_bits|= OPTION_NO_FOREIGN_KEY_CHECKS;
+ else
+ ev_thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS;
+
+ if (ev->get_flags(Old_rows_log_event::RELAXED_UNIQUE_CHECKS_F))
+ ev_thd->variables.option_bits|= OPTION_RELAXED_UNIQUE_CHECKS;
+ else
+ ev_thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS;
+ /* A small test to verify that objects have consistent types */
+ DBUG_ASSERT(sizeof(ev_thd->variables.option_bits) == sizeof(OPTION_RELAXED_UNIQUE_CHECKS));
+
+ error= do_before_row_operations(table);
+ while (error == 0 && row_start < ev->m_rows_end)
+ {
+ uchar const *row_end= NULL;
+ if ((error= do_prepare_row(ev_thd, rgi, table, row_start, &row_end)))
+ break; // We should perform the after-row operation even in
+ // the case of error
+
+ DBUG_ASSERT(row_end != NULL); // cannot happen
+ DBUG_ASSERT(row_end <= ev->m_rows_end);
+
+ /* in_use can have been set to NULL in close_tables_for_reopen */
+ THD* old_thd= table->in_use;
+ if (!table->in_use)
+ table->in_use= ev_thd;
+ error= do_exec_row(table);
+ table->in_use = old_thd;
+ switch (error)
+ {
+ /* Some recoverable errors */
+ case HA_ERR_RECORD_CHANGED:
+ case HA_ERR_KEY_NOT_FOUND: /* Idempotency support: OK if
+ tuple does not exist */
+ error= 0;
+ case 0:
+ break;
+
+ default:
+ rli->report(ERROR_LEVEL, ev_thd->get_stmt_da()->sql_errno(), NULL,
+ "Error in %s event: row application failed. %s",
+ ev->get_type_str(),
+ ev_thd->is_error() ? ev_thd->get_stmt_da()->message() : "");
+ thd->is_slave_error= 1;
+ break;
+ }
+
+ row_start= row_end;
+ }
+ DBUG_EXECUTE_IF("stop_slave_middle_group",
+ const_cast<Relay_log_info*>(rli)->abort_slave= 1;);
+ error= do_after_row_operations(table, error);
+ }
+
+ if (error)
+ { /* error has occured during the transaction */
+ rli->report(ERROR_LEVEL, ev_thd->get_stmt_da()->sql_errno(), NULL,
+ "Error in %s event: error during transaction execution "
+ "on table %s.%s. %s",
+ ev->get_type_str(), table->s->db.str,
+ table->s->table_name.str,
+ ev_thd->is_error() ? ev_thd->get_stmt_da()->message() : "");
+
+ /*
+ If one day we honour --skip-slave-errors in row-based replication, and
+ the error should be skipped, then we would clear mappings, rollback,
+ close tables, but the slave SQL thread would not stop and then may
+ assume the mapping is still available, the tables are still open...
+ So then we should clear mappings/rollback/close here only if this is a
+ STMT_END_F.
+ For now we code, knowing that error is not skippable and so slave SQL
+ thread is certainly going to stop.
+ rollback at the caller along with sbr.
+ */
+ ev_thd->reset_current_stmt_binlog_format_row();
+ rgi->cleanup_context(ev_thd, error);
+ ev_thd->is_slave_error= 1;
+ DBUG_RETURN(error);
+ }
+
+ DBUG_RETURN(0);
+}
+#endif
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+/*
+ Check if there are more UNIQUE keys after the given key.
+*/
+static int
+last_uniq_key(TABLE *table, uint keyno)
+{
+ while (++keyno < table->s->keys)
+ if (table->key_info[keyno].flags & HA_NOSAME)
+ return 0;
+ return 1;
+}
+
+
+/*
+ Compares table->record[0] and table->record[1]
+
+ Returns TRUE if different.
+*/
+static bool record_compare(TABLE *table)
+{
+ /*
+ Need to set the X bit and the filler bits in both records since
+ there are engines that do not set it correctly.
+
+ In addition, since MyISAM checks that one hasn't tampered with the
+ record, it is necessary to restore the old bytes into the record
+ after doing the comparison.
+
+ TODO[record format ndb]: Remove it once NDB returns correct
+ records. Check that the other engines also return correct records.
+ */
+
+ bool result= FALSE;
+ uchar saved_x[2]= {0, 0}, saved_filler[2]= {0, 0};
+
+ if (table->s->null_bytes > 0)
+ {
+ for (int i = 0 ; i < 2 ; ++i)
+ {
+ /*
+ If we have an X bit then we need to take care of it.
+ */
+ if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD))
+ {
+ saved_x[i]= table->record[i][0];
+ table->record[i][0]|= 1U;
+ }
+
+ /*
+ If (last_null_bit_pos == 0 && null_bytes > 1), then:
+
+ X bit (if any) + N nullable fields + M Field_bit fields = 8 bits
+
+ Ie, the entire byte is used.
+ */
+ if (table->s->last_null_bit_pos > 0)
+ {
+ saved_filler[i]= table->record[i][table->s->null_bytes - 1];
+ table->record[i][table->s->null_bytes - 1]|=
+ 256U - (1U << table->s->last_null_bit_pos);
+ }
+ }
+ }
+
+ if (table->s->blob_fields + table->s->varchar_fields == 0)
+ {
+ result= cmp_record(table,record[1]);
+ goto record_compare_exit;
+ }
+
+ /* Compare null bits */
+ if (memcmp(table->null_flags,
+ table->null_flags+table->s->rec_buff_length,
+ table->s->null_bytes))
+ {
+ result= TRUE; // Diff in NULL value
+ goto record_compare_exit;
+ }
+
+ /* Compare updated fields */
+ for (Field **ptr=table->field ; *ptr ; ptr++)
+ {
+ if ((*ptr)->cmp_binary_offset(table->s->rec_buff_length))
+ {
+ result= TRUE;
+ goto record_compare_exit;
+ }
+ }
+
+record_compare_exit:
+ /*
+ Restore the saved bytes.
+
+ TODO[record format ndb]: Remove this code once NDB returns the
+ correct record format.
+ */
+ if (table->s->null_bytes > 0)
+ {
+ for (int i = 0 ; i < 2 ; ++i)
+ {
+ if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD))
+ table->record[i][0]= saved_x[i];
+
+ if (table->s->last_null_bit_pos > 0)
+ table->record[i][table->s->null_bytes - 1]= saved_filler[i];
+ }
+ }
+
+ return result;
+}
+
+
+/*
+ Copy "extra" columns from record[1] to record[0].
+
+ Copy the extra fields that are not present on the master but are
+ present on the slave from record[1] to record[0]. This is used
+ after fetching a record that are to be updated, either inside
+ replace_record() or as part of executing an update_row().
+ */
+static int
+copy_extra_record_fields(TABLE *table,
+ size_t master_reclength,
+ my_ptrdiff_t master_fields)
+{
+ DBUG_ENTER("copy_extra_record_fields(table, master_reclen, master_fields)");
+ DBUG_PRINT("info", ("Copying to 0x%lx "
+ "from field %lu at offset %lu "
+ "to field %d at offset %lu",
+ (long) table->record[0],
+ (ulong) master_fields, (ulong) master_reclength,
+ table->s->fields, table->s->reclength));
+ /*
+ Copying the extra fields of the slave that does not exist on
+ master into record[0] (which are basically the default values).
+ */
+
+ if (table->s->fields < (uint) master_fields)
+ DBUG_RETURN(0);
+
+ DBUG_ASSERT(master_reclength <= table->s->reclength);
+ if (master_reclength < table->s->reclength)
+ memcpy(table->record[0] + master_reclength,
+ table->record[1] + master_reclength,
+ table->s->reclength - master_reclength);
+
+ /*
+ Bit columns are special. We iterate over all the remaining
+ columns and copy the "extra" bits to the new record. This is
+ not a very good solution: it should be refactored on
+ opportunity.
+
+ REFACTORING SUGGESTION (Matz). Introduce a member function
+ similar to move_field_offset() called copy_field_offset() to
+ copy field values and implement it for all Field subclasses. Use
+ this function to copy data from the found record to the record
+ that are going to be inserted.
+
+ The copy_field_offset() function need to be a virtual function,
+ which in this case will prevent copying an entire range of
+ fields efficiently.
+ */
+ {
+ Field **field_ptr= table->field + master_fields;
+ for ( ; *field_ptr ; ++field_ptr)
+ {
+ /*
+ Set the null bit according to the values in record[1]
+ */
+ if ((*field_ptr)->maybe_null() &&
+ (*field_ptr)->is_null_in_record(reinterpret_cast<uchar*>(table->record[1])))
+ (*field_ptr)->set_null();
+ else
+ (*field_ptr)->set_notnull();
+
+ /*
+ Do the extra work for special columns.
+ */
+ switch ((*field_ptr)->real_type())
+ {
+ default:
+ /* Nothing to do */
+ break;
+
+ case MYSQL_TYPE_BIT:
+ Field_bit *f= static_cast<Field_bit*>(*field_ptr);
+ if (f->bit_len > 0)
+ {
+ my_ptrdiff_t const offset= table->record[1] - table->record[0];
+ uchar const bits=
+ get_rec_bits(f->bit_ptr + offset, f->bit_ofs, f->bit_len);
+ set_rec_bits(bits, f->bit_ptr, f->bit_ofs, f->bit_len);
+ }
+ break;
+ }
+ }
+ }
+ DBUG_RETURN(0); // All OK
+}
+
+
+/*
+ Replace the provided record in the database.
+
+ SYNOPSIS
+ replace_record()
+ thd Thread context for writing the record.
+ table Table to which record should be written.
+ master_reclength
+ Offset to first column that is not present on the master,
+ alternatively the length of the record on the master
+ side.
+
+ RETURN VALUE
+ Error code on failure, 0 on success.
+
+ DESCRIPTION
+ Similar to how it is done in mysql_insert(), we first try to do
+ a ha_write_row() and of that fails due to duplicated keys (or
+ indices), we do an ha_update_row() or a ha_delete_row() instead.
+ */
+static int
+replace_record(THD *thd, TABLE *table,
+ ulong const master_reclength,
+ uint const master_fields)
+{
+ DBUG_ENTER("replace_record");
+ DBUG_ASSERT(table != NULL && thd != NULL);
+
+ int error;
+ int keynum;
+ auto_afree_ptr<char> key(NULL);
+
+#ifndef DBUG_OFF
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+ DBUG_PRINT_BITSET("debug", "write_set = %s", table->write_set);
+ DBUG_PRINT_BITSET("debug", "read_set = %s", table->read_set);
+#endif
+
+ while ((error= table->file->ha_write_row(table->record[0])))
+ {
+ if (error == HA_ERR_LOCK_DEADLOCK || error == HA_ERR_LOCK_WAIT_TIMEOUT)
+ {
+ table->file->print_error(error, MYF(0)); /* to check at exec_relay_log_event */
+ DBUG_RETURN(error);
+ }
+ if ((keynum= table->file->get_dup_key(error)) < 0)
+ {
+ table->file->print_error(error, MYF(0));
+ /*
+ We failed to retrieve the duplicate key
+ - either because the error was not "duplicate key" error
+ - or because the information which key is not available
+ */
+ DBUG_RETURN(error);
+ }
+
+ /*
+ We need to retrieve the old row into record[1] to be able to
+ either update or delete the offending record. We either:
+
+ - use rnd_pos() with a row-id (available as dupp_row) to the
+ offending row, if that is possible (MyISAM and Blackhole), or else
+
+ - use index_read_idx() with the key that is duplicated, to
+ retrieve the offending row.
+ */
+ if (table->file->ha_table_flags() & HA_DUPLICATE_POS)
+ {
+ error= table->file->ha_rnd_pos(table->record[1], table->file->dup_ref);
+ if (error)
+ {
+ DBUG_PRINT("info",("rnd_pos() returns error %d",error));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ }
+ else
+ {
+ if (table->file->extra(HA_EXTRA_FLUSH_CACHE))
+ {
+ DBUG_RETURN(my_errno);
+ }
+
+ if (key.get() == NULL)
+ {
+ key.assign(static_cast<char*>(my_alloca(table->s->max_unique_length)));
+ if (key.get() == NULL)
+ DBUG_RETURN(ENOMEM);
+ }
+
+ key_copy((uchar*)key.get(), table->record[0], table->key_info + keynum,
+ 0);
+ error= table->file->ha_index_read_idx_map(table->record[1], keynum,
+ (const uchar*)key.get(),
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT);
+ if (error)
+ {
+ DBUG_PRINT("info", ("index_read_idx() returns error %d", error));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ }
+
+ /*
+ Now, table->record[1] should contain the offending row. That
+ will enable us to update it or, alternatively, delete it (so
+ that we can insert the new row afterwards).
+
+ First we copy the columns into table->record[0] that are not
+ present on the master from table->record[1], if there are any.
+ */
+ copy_extra_record_fields(table, master_reclength, master_fields);
+
+ /*
+ REPLACE is defined as either INSERT or DELETE + INSERT. If
+ possible, we can replace it with an UPDATE, but that will not
+ work on InnoDB if FOREIGN KEY checks are necessary.
+
+ I (Matz) am not sure of the reason for the last_uniq_key()
+ check as, but I'm guessing that it's something along the
+ following lines.
+
+ Suppose that we got the duplicate key to be a key that is not
+ the last unique key for the table and we perform an update:
+ then there might be another key for which the unique check will
+ fail, so we're better off just deleting the row and inserting
+ the correct row.
+ */
+ if (last_uniq_key(table, keynum) &&
+ !table->file->referenced_by_foreign_key())
+ {
+ error=table->file->ha_update_row(table->record[1],
+ table->record[0]);
+ if (error && error != HA_ERR_RECORD_IS_THE_SAME)
+ table->file->print_error(error, MYF(0));
+ else
+ error= 0;
+ DBUG_RETURN(error);
+ }
+ else
+ {
+ if ((error= table->file->ha_delete_row(table->record[1])))
+ {
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ /* Will retry ha_write_row() with the offending row removed. */
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Find the row given by 'key', if the table has keys, or else use a table scan
+ to find (and fetch) the row.
+
+ If the engine allows random access of the records, a combination of
+ position() and rnd_pos() will be used.
+
+ @param table Pointer to table to search
+ @param key Pointer to key to use for search, if table has key
+
+ @pre <code>table->record[0]</code> shall contain the row to locate
+ and <code>key</code> shall contain a key to use for searching, if
+ the engine has a key.
+
+ @post If the return value is zero, <code>table->record[1]</code>
+ will contain the fetched row and the internal "cursor" will refer to
+ the row. If the return value is non-zero,
+ <code>table->record[1]</code> is undefined. In either case,
+ <code>table->record[0]</code> is undefined.
+
+ @return Zero if the row was successfully fetched into
+ <code>table->record[1]</code>, error code otherwise.
+ */
+
+static int find_and_fetch_row(TABLE *table, uchar *key)
+{
+ DBUG_ENTER("find_and_fetch_row(TABLE *table, uchar *key, uchar *record)");
+ DBUG_PRINT("enter", ("table: 0x%lx, key: 0x%lx record: 0x%lx",
+ (long) table, (long) key, (long) table->record[1]));
+
+ DBUG_ASSERT(table->in_use != NULL);
+
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+
+ if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
+ table->s->primary_key < MAX_KEY)
+ {
+ /*
+ Use a more efficient method to fetch the record given by
+ table->record[0] if the engine allows it. We first compute a
+ row reference using the position() member function (it will be
+ stored in table->file->ref) and the use rnd_pos() to position
+ the "cursor" (i.e., record[0] in this case) at the correct row.
+
+ TODO: Add a check that the correct record has been fetched by
+ comparing with the original record. Take into account that the
+ record on the master and slave can be of different
+ length. Something along these lines should work:
+
+ ADD>>> store_record(table,record[1]);
+ int error= table->file->ha_rnd_pos(table->record[0], table->file->ref);
+ ADD>>> DBUG_ASSERT(memcmp(table->record[1], table->record[0],
+ table->s->reclength) == 0);
+
+ */
+ table->file->position(table->record[0]);
+ int error= table->file->ha_rnd_pos(table->record[0], table->file->ref);
+ /*
+ rnd_pos() returns the record in table->record[0], so we have to
+ move it to table->record[1].
+ */
+ memcpy(table->record[1], table->record[0], table->s->reclength);
+ DBUG_RETURN(error);
+ }
+
+ /* We need to retrieve all fields */
+ /* TODO: Move this out from this function to main loop */
+ table->use_all_columns();
+
+ if (table->s->keys > 0)
+ {
+ int error;
+ /* We have a key: search the table using the index */
+ if (!table->file->inited && (error= table->file->ha_index_init(0, FALSE)))
+ {
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("table->record[0]", table->record[0], table->s->reclength);
+ DBUG_DUMP("table->record[1]", table->record[1], table->s->reclength);
+#endif
+
+ /*
+ We need to set the null bytes to ensure that the filler bit are
+ all set when returning. There are storage engines that just set
+ the necessary bits on the bytes and don't set the filler bits
+ correctly.
+ */
+ my_ptrdiff_t const pos=
+ table->s->null_bytes > 0 ? table->s->null_bytes - 1 : 0;
+ table->record[1][pos]= 0xFF;
+ if ((error= table->file->ha_index_read_map(table->record[1], key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT)))
+ {
+ table->file->print_error(error, MYF(0));
+ table->file->ha_index_end();
+ DBUG_RETURN(error);
+ }
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("table->record[0]", table->record[0], table->s->reclength);
+ DBUG_DUMP("table->record[1]", table->record[1], table->s->reclength);
+#endif
+ /*
+ Below is a minor "optimization". If the key (i.e., key number
+ 0) has the HA_NOSAME flag set, we know that we have found the
+ correct record (since there can be no duplicates); otherwise, we
+ have to compare the record with the one found to see if it is
+ the correct one.
+
+ CAVEAT! This behaviour is essential for the replication of,
+ e.g., the mysql.proc table since the correct record *shall* be
+ found using the primary key *only*. There shall be no
+ comparison of non-PK columns to decide if the correct record is
+ found. I can see no scenario where it would be incorrect to
+ chose the row to change only using a PK or an UNNI.
+ */
+ if (table->key_info->flags & HA_NOSAME)
+ {
+ table->file->ha_index_end();
+ DBUG_RETURN(0);
+ }
+
+ while (record_compare(table))
+ {
+ int error;
+
+ /*
+ We need to set the null bytes to ensure that the filler bit
+ are all set when returning. There are storage engines that
+ just set the necessary bits on the bytes and don't set the
+ filler bits correctly.
+
+ TODO[record format ndb]: Remove this code once NDB returns the
+ correct record format.
+ */
+ if (table->s->null_bytes > 0)
+ {
+ table->record[1][table->s->null_bytes - 1]|=
+ 256U - (1U << table->s->last_null_bit_pos);
+ }
+
+ while ((error= table->file->ha_index_next(table->record[1])))
+ {
+ /* We just skip records that has already been deleted */
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ table->file->print_error(error, MYF(0));
+ table->file->ha_index_end();
+ DBUG_RETURN(error);
+ }
+ }
+
+ /*
+ Have to restart the scan to be able to fetch the next row.
+ */
+ table->file->ha_index_end();
+ }
+ else
+ {
+ int restart_count= 0; // Number of times scanning has restarted from top
+ int error;
+
+ /* We don't have a key: search the table using rnd_next() */
+ if ((error= table->file->ha_rnd_init_with_error(1)))
+ return error;
+
+ /* Continue until we find the right record or have made a full loop */
+ do
+ {
+ restart_rnd_next:
+ error= table->file->ha_rnd_next(table->record[1]);
+
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+ DBUG_DUMP("record[1]", table->record[1], table->s->reclength);
+
+ switch (error) {
+ case 0:
+ break;
+
+ /*
+ If the record was deleted, we pick the next one without doing
+ any comparisons.
+ */
+ case HA_ERR_RECORD_DELETED:
+ goto restart_rnd_next;
+
+ case HA_ERR_END_OF_FILE:
+ if (++restart_count < 2)
+ {
+ int error2;
+ if ((error2= table->file->ha_rnd_init_with_error(1)))
+ DBUG_RETURN(error2);
+ }
+ break;
+
+ default:
+ table->file->print_error(error, MYF(0));
+ DBUG_PRINT("info", ("Record not found"));
+ (void) table->file->ha_rnd_end();
+ DBUG_RETURN(error);
+ }
+ }
+ while (restart_count < 2 && record_compare(table));
+
+ /*
+ Have to restart the scan to be able to fetch the next row.
+ */
+ DBUG_PRINT("info", ("Record %sfound", restart_count == 2 ? "not " : ""));
+ table->file->ha_rnd_end();
+
+ DBUG_ASSERT(error == HA_ERR_END_OF_FILE || error == 0);
+ DBUG_RETURN(error);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**********************************************************
+ Row handling primitives for Write_rows_log_event_old
+ **********************************************************/
+
+int Write_rows_log_event_old::do_before_row_operations(TABLE *table)
+{
+ int error= 0;
+
+ /*
+ We are using REPLACE semantics and not INSERT IGNORE semantics
+ when writing rows, that is: new rows replace old rows. We need to
+ inform the storage engine that it should use this behaviour.
+ */
+
+ /* Tell the storage engine that we are using REPLACE semantics. */
+ thd->lex->duplicates= DUP_REPLACE;
+
+ /*
+ Pretend we're executing a REPLACE command: this is needed for
+ InnoDB and NDB Cluster since they are not (properly) checking the
+ lex->duplicates flag.
+ */
+ thd->lex->sql_command= SQLCOM_REPLACE;
+ /*
+ Do not raise the error flag in case of hitting to an unique attribute
+ */
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ /*
+ NDB specific: update from ndb master wrapped as Write_rows
+ */
+ /*
+ so that the event should be applied to replace slave's row
+ */
+ table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ /*
+ NDB specific: if update from ndb master wrapped as Write_rows
+ does not find the row it's assumed idempotent binlog applying
+ is taking place; don't raise the error.
+ */
+ table->file->extra(HA_EXTRA_IGNORE_NO_KEY);
+ /*
+ TODO: the cluster team (Tomas?) says that it's better if the engine knows
+ how many rows are going to be inserted, then it can allocate needed memory
+ from the start.
+ */
+ table->file->ha_start_bulk_insert(0);
+ return error;
+}
+
+
+int Write_rows_log_event_old::do_after_row_operations(TABLE *table, int error)
+{
+ int local_error= 0;
+ table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+ /*
+ reseting the extra with
+ table->file->extra(HA_EXTRA_NO_IGNORE_NO_KEY);
+ fires bug#27077
+ todo: explain or fix
+ */
+ if ((local_error= table->file->ha_end_bulk_insert()))
+ {
+ table->file->print_error(local_error, MYF(0));
+ }
+ return error? error : local_error;
+}
+
+
+int
+Write_rows_log_event_old::do_prepare_row(THD *thd_arg,
+ rpl_group_info *rgi,
+ TABLE *table,
+ uchar const *row_start,
+ uchar const **row_end)
+{
+ DBUG_ASSERT(table != NULL);
+ DBUG_ASSERT(row_start && row_end);
+
+ int error;
+ error= unpack_row_old(rgi,
+ table, m_width, table->record[0],
+ row_start, m_rows_end,
+ &m_cols, row_end, &m_master_reclength,
+ table->write_set, PRE_GA_WRITE_ROWS_EVENT);
+ bitmap_copy(table->read_set, table->write_set);
+ return error;
+}
+
+
+int Write_rows_log_event_old::do_exec_row(TABLE *table)
+{
+ DBUG_ASSERT(table != NULL);
+ int error= replace_record(thd, table, m_master_reclength, m_width);
+ return error;
+}
+
+
+/**********************************************************
+ Row handling primitives for Delete_rows_log_event_old
+ **********************************************************/
+
+int Delete_rows_log_event_old::do_before_row_operations(TABLE *table)
+{
+ DBUG_ASSERT(m_memory == NULL);
+
+ if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
+ table->s->primary_key < MAX_KEY)
+ {
+ /*
+ We don't need to allocate any memory for m_after_image and
+ m_key since they are not used.
+ */
+ return 0;
+ }
+
+ int error= 0;
+
+ if (table->s->keys > 0)
+ {
+ m_memory= (uchar*) my_multi_malloc(MYF(MY_WME),
+ &m_after_image,
+ (uint) table->s->reclength,
+ &m_key,
+ (uint) table->key_info->key_length,
+ NullS);
+ }
+ else
+ {
+ m_after_image= (uchar*) my_malloc(table->s->reclength, MYF(MY_WME));
+ m_memory= (uchar*)m_after_image;
+ m_key= NULL;
+ }
+ if (!m_memory)
+ return HA_ERR_OUT_OF_MEM;
+
+ return error;
+}
+
+
+int Delete_rows_log_event_old::do_after_row_operations(TABLE *table, int error)
+{
+ /*error= ToDo:find out what this should really be, this triggers close_scan in nbd, returning error?*/
+ table->file->ha_index_or_rnd_end();
+ my_free(m_memory); // Free for multi_malloc
+ m_memory= NULL;
+ m_after_image= NULL;
+ m_key= NULL;
+
+ return error;
+}
+
+
+int
+Delete_rows_log_event_old::do_prepare_row(THD *thd_arg,
+ rpl_group_info *rgi,
+ TABLE *table,
+ uchar const *row_start,
+ uchar const **row_end)
+{
+ int error;
+ DBUG_ASSERT(row_start && row_end);
+ /*
+ This assertion actually checks that there is at least as many
+ columns on the slave as on the master.
+ */
+ DBUG_ASSERT(table->s->fields >= m_width);
+
+ error= unpack_row_old(rgi,
+ table, m_width, table->record[0],
+ row_start, m_rows_end,
+ &m_cols, row_end, &m_master_reclength,
+ table->read_set, PRE_GA_DELETE_ROWS_EVENT);
+ /*
+ If we will access rows using the random access method, m_key will
+ be set to NULL, so we do not need to make a key copy in that case.
+ */
+ if (m_key)
+ {
+ KEY *const key_info= table->key_info;
+
+ key_copy(m_key, table->record[0], key_info, 0);
+ }
+
+ return error;
+}
+
+
+int Delete_rows_log_event_old::do_exec_row(TABLE *table)
+{
+ int error;
+ DBUG_ASSERT(table != NULL);
+
+ if (!(error= ::find_and_fetch_row(table, m_key)))
+ {
+ /*
+ Now we should have the right row to delete. We are using
+ record[0] since it is guaranteed to point to a record with the
+ correct value.
+ */
+ error= table->file->ha_delete_row(table->record[0]);
+ }
+ return error;
+}
+
+
+/**********************************************************
+ Row handling primitives for Update_rows_log_event_old
+ **********************************************************/
+
+int Update_rows_log_event_old::do_before_row_operations(TABLE *table)
+{
+ DBUG_ASSERT(m_memory == NULL);
+
+ int error= 0;
+
+ if (table->s->keys > 0)
+ {
+ m_memory= (uchar*) my_multi_malloc(MYF(MY_WME),
+ &m_after_image,
+ (uint) table->s->reclength,
+ &m_key,
+ (uint) table->key_info->key_length,
+ NullS);
+ }
+ else
+ {
+ m_after_image= (uchar*) my_malloc(table->s->reclength, MYF(MY_WME));
+ m_memory= m_after_image;
+ m_key= NULL;
+ }
+ if (!m_memory)
+ return HA_ERR_OUT_OF_MEM;
+
+ return error;
+}
+
+
+int Update_rows_log_event_old::do_after_row_operations(TABLE *table, int error)
+{
+ /*error= ToDo:find out what this should really be, this triggers close_scan in nbd, returning error?*/
+ table->file->ha_index_or_rnd_end();
+ my_free(m_memory);
+ m_memory= NULL;
+ m_after_image= NULL;
+ m_key= NULL;
+
+ return error;
+}
+
+
+int Update_rows_log_event_old::do_prepare_row(THD *thd_arg,
+ rpl_group_info *rgi,
+ TABLE *table,
+ uchar const *row_start,
+ uchar const **row_end)
+{
+ int error;
+ DBUG_ASSERT(row_start && row_end);
+ /*
+ This assertion actually checks that there is at least as many
+ columns on the slave as on the master.
+ */
+ DBUG_ASSERT(table->s->fields >= m_width);
+
+ /* record[0] is the before image for the update */
+ error= unpack_row_old(rgi,
+ table, m_width, table->record[0],
+ row_start, m_rows_end,
+ &m_cols, row_end, &m_master_reclength,
+ table->read_set, PRE_GA_UPDATE_ROWS_EVENT);
+ row_start = *row_end;
+ /* m_after_image is the after image for the update */
+ error= unpack_row_old(rgi,
+ table, m_width, m_after_image,
+ row_start, m_rows_end,
+ &m_cols, row_end, &m_master_reclength,
+ table->write_set, PRE_GA_UPDATE_ROWS_EVENT);
+
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+ DBUG_DUMP("m_after_image", m_after_image, table->s->reclength);
+
+ /*
+ If we will access rows using the random access method, m_key will
+ be set to NULL, so we do not need to make a key copy in that case.
+ */
+ if (m_key)
+ {
+ KEY *const key_info= table->key_info;
+
+ key_copy(m_key, table->record[0], key_info, 0);
+ }
+
+ return error;
+}
+
+
+int Update_rows_log_event_old::do_exec_row(TABLE *table)
+{
+ DBUG_ASSERT(table != NULL);
+
+ int error= ::find_and_fetch_row(table, m_key);
+ if (error)
+ return error;
+
+ /*
+ We have to ensure that the new record (i.e., the after image) is
+ in record[0] and the old record (i.e., the before image) is in
+ record[1]. This since some storage engines require this (for
+ example, the partition engine).
+
+ Since find_and_fetch_row() puts the fetched record (i.e., the old
+ record) in record[1], we can keep it there. We put the new record
+ (i.e., the after image) into record[0], and copy the fields that
+ are on the slave (i.e., in record[1]) into record[0], effectively
+ overwriting the default values that where put there by the
+ unpack_row() function.
+ */
+ memcpy(table->record[0], m_after_image, table->s->reclength);
+ copy_extra_record_fields(table, m_master_reclength, m_width);
+
+ /*
+ Now we have the right row to update. The old row (the one we're
+ looking for) is in record[1] and the new row has is in record[0].
+ We also have copied the original values already in the slave's
+ database into the after image delivered from the master.
+ */
+ error= table->file->ha_update_row(table->record[1], table->record[0]);
+ if (error == HA_ERR_RECORD_IS_THE_SAME)
+ error= 0;
+
+ return error;
+}
+
+#endif
+
+
+/**************************************************************************
+ Rows_log_event member functions
+**************************************************************************/
+
+#ifndef MYSQL_CLIENT
+Old_rows_log_event::Old_rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid,
+ MY_BITMAP const *cols,
+ bool is_transactional)
+ : Log_event(thd_arg, 0, is_transactional),
+ m_row_count(0),
+ m_table(tbl_arg),
+ m_table_id(tid),
+ m_width(tbl_arg ? tbl_arg->s->fields : 1),
+ m_rows_buf(0), m_rows_cur(0), m_rows_end(0), m_flags(0)
+#ifdef HAVE_REPLICATION
+ , m_curr_row(NULL), m_curr_row_end(NULL), m_key(NULL)
+#endif
+{
+
+ // This constructor should not be reached.
+ assert(0);
+
+ /*
+ We allow a special form of dummy event when the table, and cols
+ are null and the table id is ~0UL. This is a temporary
+ solution, to be able to terminate a started statement in the
+ binary log: the extraneous events will be removed in the future.
+ */
+ DBUG_ASSERT((tbl_arg && tbl_arg->s && tid != ~0UL) ||
+ (!tbl_arg && !cols && tid == ~0UL));
+
+ if (thd_arg->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS)
+ set_flags(NO_FOREIGN_KEY_CHECKS_F);
+ if (thd_arg->variables.option_bits & OPTION_RELAXED_UNIQUE_CHECKS)
+ set_flags(RELAXED_UNIQUE_CHECKS_F);
+ /* if my_bitmap_init fails, caught in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols,
+ m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL,
+ m_width,
+ false)))
+ {
+ /* Cols can be zero if this is a dummy binrows event */
+ if (likely(cols != NULL))
+ {
+ memcpy(m_cols.bitmap, cols->bitmap, no_bytes_in_map(cols));
+ create_last_word_mask(&m_cols);
+ }
+ }
+ else
+ {
+ // Needed because my_bitmap_init() does not set it to null on failure
+ m_cols.bitmap= 0;
+ }
+}
+#endif
+
+
+Old_rows_log_event::Old_rows_log_event(const char *buf, uint event_len,
+ Log_event_type event_type,
+ const Format_description_log_event
+ *description_event)
+ : Log_event(buf, description_event),
+ m_row_count(0),
+#ifndef MYSQL_CLIENT
+ m_table(NULL),
+#endif
+ m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0)
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ , m_curr_row(NULL), m_curr_row_end(NULL), m_key(NULL)
+#endif
+{
+ DBUG_ENTER("Old_rows_log_event::Old_Rows_log_event(const char*,...)");
+ uint8 const common_header_len= description_event->common_header_len;
+ uint8 const post_header_len= description_event->post_header_len[event_type-1];
+
+ DBUG_PRINT("enter",("event_len: %u common_header_len: %d "
+ "post_header_len: %d",
+ event_len, common_header_len,
+ post_header_len));
+
+ const char *post_start= buf + common_header_len;
+ DBUG_DUMP("post_header", (uchar*) post_start, post_header_len);
+ post_start+= RW_MAPID_OFFSET;
+ if (post_header_len == 6)
+ {
+ /* Master is of an intermediate source tree before 5.1.4. Id is 4 bytes */
+ m_table_id= uint4korr(post_start);
+ post_start+= 4;
+ }
+ else
+ {
+ m_table_id= (ulong) uint6korr(post_start);
+ post_start+= RW_FLAGS_OFFSET;
+ }
+
+ m_flags= uint2korr(post_start);
+
+ uchar const *const var_start=
+ (const uchar *)buf + common_header_len + post_header_len;
+ uchar const *const ptr_width= var_start;
+ uchar *ptr_after_width= (uchar*) ptr_width;
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ m_width = net_field_length(&ptr_after_width);
+ DBUG_PRINT("debug", ("m_width=%lu", m_width));
+ /* if my_bitmap_init fails, catched in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols,
+ m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL,
+ m_width,
+ false)))
+ {
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ memcpy(m_cols.bitmap, ptr_after_width, (m_width + 7) / 8);
+ create_last_word_mask(&m_cols);
+ ptr_after_width+= (m_width + 7) / 8;
+ DBUG_DUMP("m_cols", (uchar*) m_cols.bitmap, no_bytes_in_map(&m_cols));
+ }
+ else
+ {
+ // Needed because my_bitmap_init() does not set it to null on failure
+ m_cols.bitmap= NULL;
+ DBUG_VOID_RETURN;
+ }
+
+ const uchar* const ptr_rows_data= (const uchar*) ptr_after_width;
+ size_t const data_size= event_len - (ptr_rows_data - (const uchar *) buf);
+ DBUG_PRINT("info",("m_table_id: %lu m_flags: %d m_width: %lu data_size: %lu",
+ m_table_id, m_flags, m_width, (ulong) data_size));
+ DBUG_DUMP("rows_data", (uchar*) ptr_rows_data, data_size);
+
+ m_rows_buf= (uchar*) my_malloc(data_size, MYF(MY_WME));
+ if (likely((bool)m_rows_buf))
+ {
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ m_curr_row= m_rows_buf;
+#endif
+ m_rows_end= m_rows_buf + data_size;
+ m_rows_cur= m_rows_end;
+ memcpy(m_rows_buf, ptr_rows_data, data_size);
+ }
+ else
+ m_cols.bitmap= 0; // to not free it
+
+ DBUG_VOID_RETURN;
+}
+
+
+Old_rows_log_event::~Old_rows_log_event()
+{
+ if (m_cols.bitmap == m_bitbuf) // no my_malloc happened
+ m_cols.bitmap= 0; // so no my_free in my_bitmap_free
+ my_bitmap_free(&m_cols); // To pair with my_bitmap_init().
+ my_free(m_rows_buf);
+}
+
+
+int Old_rows_log_event::get_data_size()
+{
+ uchar buf[MAX_INT_WIDTH];
+ uchar *end= net_store_length(buf, (m_width + 7) / 8);
+
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ return 6 + no_bytes_in_map(&m_cols) + (end - buf) +
+ (m_rows_cur - m_rows_buf););
+ int data_size= ROWS_HEADER_LEN;
+ data_size+= no_bytes_in_map(&m_cols);
+ data_size+= (uint) (end - buf);
+
+ data_size+= (uint) (m_rows_cur - m_rows_buf);
+ return data_size;
+}
+
+
+#ifndef MYSQL_CLIENT
+int Old_rows_log_event::do_add_row_data(uchar *row_data, size_t length)
+{
+ /*
+ When the table has a primary key, we would probably want, by default, to
+ log only the primary key value instead of the entire "before image". This
+ would save binlog space. TODO
+ */
+ DBUG_ENTER("Old_rows_log_event::do_add_row_data");
+ DBUG_PRINT("enter", ("row_data: 0x%lx length: %lu", (ulong) row_data,
+ (ulong) length));
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("row_data", row_data, MY_MIN(length, 32));
+#endif
+
+ DBUG_ASSERT(m_rows_buf <= m_rows_cur);
+ DBUG_ASSERT(!m_rows_buf || (m_rows_end && m_rows_buf < m_rows_end));
+ DBUG_ASSERT(m_rows_cur <= m_rows_end);
+
+ /* The cast will always work since m_rows_cur <= m_rows_end */
+ if (static_cast<size_t>(m_rows_end - m_rows_cur) <= length)
+ {
+ size_t const block_size= 1024;
+ my_ptrdiff_t const cur_size= m_rows_cur - m_rows_buf;
+ my_ptrdiff_t const new_alloc=
+ block_size * ((cur_size + length + block_size - 1) / block_size);
+
+ uchar* const new_buf= (uchar*)my_realloc((uchar*)m_rows_buf, (uint) new_alloc,
+ MYF(MY_ALLOW_ZERO_PTR|MY_WME));
+ if (unlikely(!new_buf))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+ /* If the memory moved, we need to move the pointers */
+ if (new_buf != m_rows_buf)
+ {
+ m_rows_buf= new_buf;
+ m_rows_cur= m_rows_buf + cur_size;
+ }
+
+ /*
+ The end pointer should always be changed to point to the end of
+ the allocated memory.
+ */
+ m_rows_end= m_rows_buf + new_alloc;
+ }
+
+ DBUG_ASSERT(m_rows_cur + length <= m_rows_end);
+ memcpy(m_rows_cur, row_data, length);
+ m_rows_cur+= length;
+ m_row_count++;
+ DBUG_RETURN(0);
+}
+#endif
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int Old_rows_log_event::do_apply_event(rpl_group_info *rgi)
+{
+ DBUG_ENTER("Old_rows_log_event::do_apply_event(Relay_log_info*)");
+ int error= 0;
+ Relay_log_info const *rli= rgi->rli;
+
+ /*
+ If m_table_id == ~0UL, then we have a dummy event that does not
+ contain any data. In that case, we just remove all tables in the
+ tables_to_lock list, close the thread tables, and return with
+ success.
+ */
+ if (m_table_id == ~0UL)
+ {
+ /*
+ This one is supposed to be set: just an extra check so that
+ nothing strange has happened.
+ */
+ DBUG_ASSERT(get_flags(STMT_END_F));
+
+ rgi->slave_close_thread_tables(thd);
+ thd->clear_error();
+ DBUG_RETURN(0);
+ }
+
+ /*
+ 'thd' has been set by exec_relay_log_event(), just before calling
+ do_apply_event(). We still check here to prevent future coding
+ errors.
+ */
+ DBUG_ASSERT(rgi->thd == thd);
+
+ /*
+ If there is no locks taken, this is the first binrow event seen
+ after the table map events. We should then lock all the tables
+ used in the transaction and proceed with execution of the actual
+ event.
+ */
+ if (!thd->lock)
+ {
+ /*
+ lock_tables() reads the contents of thd->lex, so they must be
+ initialized. Contrary to in
+ Table_map_log_event::do_apply_event() we don't call
+ mysql_init_query() as that may reset the binlog format.
+ */
+ lex_start(thd);
+
+ if ((error= lock_tables(thd, rgi->tables_to_lock,
+ rgi->tables_to_lock_count, 0)))
+ {
+ if (thd->is_slave_error || thd->is_fatal_error)
+ {
+ /*
+ Error reporting borrowed from Query_log_event with many excessive
+ simplifications (we don't honour --slave-skip-errors)
+ */
+ uint actual_error= thd->net.last_errno;
+ rli->report(ERROR_LEVEL, actual_error, NULL,
+ "Error '%s' in %s event: when locking tables",
+ (actual_error ? thd->net.last_error :
+ "unexpected success or fatal error"),
+ get_type_str());
+ thd->is_fatal_error= 1;
+ }
+ else
+ {
+ rli->report(ERROR_LEVEL, error, NULL,
+ "Error in %s event: when locking tables",
+ get_type_str());
+ }
+ rgi->slave_close_thread_tables(thd);
+ DBUG_RETURN(error);
+ }
+
+ /*
+ When the open and locking succeeded, we check all tables to
+ ensure that they still have the correct type.
+
+ We can use a down cast here since we know that every table added
+ to the tables_to_lock is a RPL_TABLE_LIST.
+ */
+
+ {
+ RPL_TABLE_LIST *ptr= rgi->tables_to_lock;
+ for (uint i= 0 ; ptr&& (i< rgi->tables_to_lock_count);
+ ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_global), i++)
+ {
+ TABLE *conv_table;
+ if (ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table))
+ {
+ thd->is_slave_error= 1;
+ rgi->slave_close_thread_tables(thd);
+ DBUG_RETURN(ERR_BAD_TABLE_DEF);
+ }
+ ptr->m_conv_table= conv_table;
+ }
+ }
+
+ /*
+ ... and then we add all the tables to the table map but keep
+ them in the tables to lock list.
+
+
+ We also invalidate the query cache for all the tables, since
+ they will now be changed.
+
+ TODO [/Matz]: Maybe the query cache should not be invalidated
+ here? It might be that a table is not changed, even though it
+ was locked for the statement. We do know that each
+ Old_rows_log_event contain at least one row, so after processing one
+ Old_rows_log_event, we can invalidate the query cache for the
+ associated table.
+ */
+ for (TABLE_LIST *ptr= rgi->tables_to_lock ; ptr ; ptr= ptr->next_global)
+ {
+ rgi->m_table_map.set_table(ptr->table_id, ptr->table);
+ }
+#ifdef HAVE_QUERY_CACHE
+ query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock);
+#endif
+ }
+
+ TABLE*
+ table=
+ m_table= rgi->m_table_map.get_table(m_table_id);
+
+ if (table)
+ {
+ /*
+ table == NULL means that this table should not be replicated
+ (this was set up by Table_map_log_event::do_apply_event()
+ which tested replicate-* rules).
+ */
+
+ /*
+ It's not needed to set_time() but
+ 1) it continues the property that "Time" in SHOW PROCESSLIST shows how
+ much slave is behind
+ 2) it will be needed when we allow replication from a table with no
+ TIMESTAMP column to a table with one.
+ So we call set_time(), like in SBR. Presently it changes nothing.
+ */
+ thd->set_time(when, when_sec_part);
+ /*
+ There are a few flags that are replicated with each row event.
+ Make sure to set/clear them before executing the main body of
+ the event.
+ */
+ if (get_flags(NO_FOREIGN_KEY_CHECKS_F))
+ thd->variables.option_bits|= OPTION_NO_FOREIGN_KEY_CHECKS;
+ else
+ thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS;
+
+ if (get_flags(RELAXED_UNIQUE_CHECKS_F))
+ thd->variables.option_bits|= OPTION_RELAXED_UNIQUE_CHECKS;
+ else
+ thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS;
+ /* A small test to verify that objects have consistent types */
+ DBUG_ASSERT(sizeof(thd->variables.option_bits) == sizeof(OPTION_RELAXED_UNIQUE_CHECKS));
+
+ if ( m_width == table->s->fields && bitmap_is_set_all(&m_cols))
+ set_flags(COMPLETE_ROWS_F);
+
+ /*
+ Set tables write and read sets.
+
+ Read_set contains all slave columns (in case we are going to fetch
+ a complete record from slave)
+
+ Write_set equals the m_cols bitmap sent from master but it can be
+ longer if slave has extra columns.
+ */
+
+ DBUG_PRINT_BITSET("debug", "Setting table's write_set from: %s", &m_cols);
+
+ bitmap_set_all(table->read_set);
+ bitmap_set_all(table->write_set);
+ if (!get_flags(COMPLETE_ROWS_F))
+ bitmap_intersect(table->write_set,&m_cols);
+
+ // Do event specific preparations
+
+ error= do_before_row_operations(rli);
+
+ // row processing loop
+
+ while (error == 0 && m_curr_row < m_rows_end)
+ {
+ /* in_use can have been set to NULL in close_tables_for_reopen */
+ THD* old_thd= table->in_use;
+ if (!table->in_use)
+ table->in_use= thd;
+
+ error= do_exec_row(rgi);
+
+ DBUG_PRINT("info", ("error: %d", error));
+ DBUG_ASSERT(error != HA_ERR_RECORD_DELETED);
+
+ table->in_use = old_thd;
+ switch (error)
+ {
+ case 0:
+ break;
+
+ /* Some recoverable errors */
+ case HA_ERR_RECORD_CHANGED:
+ case HA_ERR_KEY_NOT_FOUND: /* Idempotency support: OK if
+ tuple does not exist */
+ error= 0;
+ break;
+
+ default:
+ rli->report(ERROR_LEVEL, thd->net.last_errno, NULL,
+ "Error in %s event: row application failed. %s",
+ get_type_str(),
+ thd->net.last_error ? thd->net.last_error : "");
+ thd->is_slave_error= 1;
+ break;
+ }
+
+ /*
+ If m_curr_row_end was not set during event execution (e.g., because
+ of errors) we can't proceed to the next row. If the error is transient
+ (i.e., error==0 at this point) we must call unpack_current_row() to set
+ m_curr_row_end.
+ */
+
+ DBUG_PRINT("info", ("error: %d", error));
+ DBUG_PRINT("info", ("curr_row: 0x%lu; curr_row_end: 0x%lu; rows_end: 0x%lu",
+ (ulong) m_curr_row, (ulong) m_curr_row_end, (ulong) m_rows_end));
+
+ if (!m_curr_row_end && !error)
+ unpack_current_row(rgi);
+
+ // at this moment m_curr_row_end should be set
+ DBUG_ASSERT(error || m_curr_row_end != NULL);
+ DBUG_ASSERT(error || m_curr_row < m_curr_row_end);
+ DBUG_ASSERT(error || m_curr_row_end <= m_rows_end);
+
+ m_curr_row= m_curr_row_end;
+
+ } // row processing loop
+
+ DBUG_EXECUTE_IF("stop_slave_middle_group",
+ const_cast<Relay_log_info*>(rli)->abort_slave= 1;);
+ error= do_after_row_operations(rli, error);
+ } // if (table)
+
+ if (error)
+ { /* error has occured during the transaction */
+ rli->report(ERROR_LEVEL, thd->net.last_errno, NULL,
+ "Error in %s event: error during transaction execution "
+ "on table %s.%s. %s",
+ get_type_str(), table->s->db.str,
+ table->s->table_name.str,
+ thd->net.last_error ? thd->net.last_error : "");
+
+ /*
+ If one day we honour --skip-slave-errors in row-based replication, and
+ the error should be skipped, then we would clear mappings, rollback,
+ close tables, but the slave SQL thread would not stop and then may
+ assume the mapping is still available, the tables are still open...
+ So then we should clear mappings/rollback/close here only if this is a
+ STMT_END_F.
+ For now we code, knowing that error is not skippable and so slave SQL
+ thread is certainly going to stop.
+ rollback at the caller along with sbr.
+ */
+ thd->reset_current_stmt_binlog_format_row();
+ rgi->cleanup_context(thd, error);
+ thd->is_slave_error= 1;
+ DBUG_RETURN(error);
+ }
+
+ /*
+ This code would ideally be placed in do_update_pos() instead, but
+ since we have no access to table there, we do the setting of
+ last_event_start_time here instead.
+ */
+ if (table && (table->s->primary_key == MAX_KEY) &&
+ !use_trans_cache() && get_flags(STMT_END_F) == RLE_NO_FLAGS)
+ {
+ /*
+ ------------ Temporary fix until WL#2975 is implemented ---------
+
+ This event is not the last one (no STMT_END_F). If we stop now
+ (in case of terminate_slave_thread()), how will we restart? We
+ have to restart from Table_map_log_event, but as this table is
+ not transactional, the rows already inserted will still be
+ present, and idempotency is not guaranteed (no PK) so we risk
+ that repeating leads to double insert. So we desperately try to
+ continue, hope we'll eventually leave this buggy situation (by
+ executing the final Old_rows_log_event). If we are in a hopeless
+ wait (reached end of last relay log and nothing gets appended
+ there), we timeout after one minute, and notify DBA about the
+ problem. When WL#2975 is implemented, just remove the member
+ Relay_log_info::last_event_start_time and all its occurrences.
+ */
+ rgi->last_event_start_time= my_time(0);
+ }
+
+ if (get_flags(STMT_END_F))
+ {
+ /*
+ This is the end of a statement or transaction, so close (and
+ unlock) the tables we opened when processing the
+ Table_map_log_event starting the statement.
+
+ OBSERVER. This will clear *all* mappings, not only those that
+ are open for the table. There is not good handle for on-close
+ actions for tables.
+
+ NOTE. Even if we have no table ('table' == 0) we still need to be
+ here, so that we increase the group relay log position. If we didn't, we
+ could have a group relay log position which lags behind "forever"
+ (assume the last master's transaction is ignored by the slave because of
+ replicate-ignore rules).
+ */
+ int binlog_error= thd->binlog_flush_pending_rows_event(TRUE);
+
+ /*
+ If this event is not in a transaction, the call below will, if some
+ transactional storage engines are involved, commit the statement into
+ them and flush the pending event to binlog.
+ If this event is in a transaction, the call will do nothing, but a
+ Xid_log_event will come next which will, if some transactional engines
+ are involved, commit the transaction and flush the pending event to the
+ binlog.
+ If there was a deadlock the transaction should have been rolled back
+ already. So there should be no need to rollback the transaction.
+ */
+ DBUG_ASSERT(! thd->transaction_rollback_request);
+ if ((error= (binlog_error ? trans_rollback_stmt(thd) : trans_commit_stmt(thd))))
+ rli->report(ERROR_LEVEL, error, NULL,
+ "Error in %s event: commit of row events failed, "
+ "table `%s`.`%s`",
+ get_type_str(), m_table->s->db.str,
+ m_table->s->table_name.str);
+ error|= binlog_error;
+
+ /*
+ Now what if this is not a transactional engine? we still need to
+ flush the pending event to the binlog; we did it with
+ thd->binlog_flush_pending_rows_event(). Note that we imitate
+ what is done for real queries: a call to
+ ha_autocommit_or_rollback() (sometimes only if involves a
+ transactional engine), and a call to be sure to have the pending
+ event flushed.
+ */
+
+ thd->reset_current_stmt_binlog_format_row();
+ rgi->cleanup_context(thd, 0);
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+Log_event::enum_skip_reason
+Old_rows_log_event::do_shall_skip(rpl_group_info *rgi)
+{
+ /*
+ If the slave skip counter is 1 and this event does not end a
+ statement, then we should not start executing on the next event.
+ Otherwise, we defer the decision to the normal skipping logic.
+ */
+ if (rgi->rli->slave_skip_counter == 1 && !get_flags(STMT_END_F))
+ return Log_event::EVENT_SKIP_IGNORE;
+ else
+ return Log_event::do_shall_skip(rgi);
+}
+
+int
+Old_rows_log_event::do_update_pos(rpl_group_info *rgi)
+{
+ Relay_log_info *rli= rgi->rli;
+ DBUG_ENTER("Old_rows_log_event::do_update_pos");
+ int error= 0;
+
+ DBUG_PRINT("info", ("flags: %s",
+ get_flags(STMT_END_F) ? "STMT_END_F " : ""));
+
+ if (get_flags(STMT_END_F))
+ {
+ /*
+ Indicate that a statement is finished.
+ Step the group log position if we are not in a transaction,
+ otherwise increase the event log position.
+ */
+ rli->stmt_done(log_pos, thd, rgi);
+ /*
+ Clear any errors in thd->net.last_err*. It is not known if this is
+ needed or not. It is believed that any errors that may exist in
+ thd->net.last_err* are allowed. Examples of errors are "key not
+ found", which is produced in the test case rpl_row_conflicts.test
+ */
+ thd->clear_error();
+ }
+ else
+ {
+ rgi->inc_event_relay_log_pos();
+ }
+
+ DBUG_RETURN(error);
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+
+#ifndef MYSQL_CLIENT
+bool Old_rows_log_event::write_data_header(IO_CACHE *file)
+{
+ uchar buf[ROWS_HEADER_LEN]; // No need to init the buffer
+
+ // This method should not be reached.
+ assert(0);
+
+ DBUG_ASSERT(m_table_id != ~0UL);
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ {
+ int4store(buf + 0, m_table_id);
+ int2store(buf + 4, m_flags);
+ return (my_b_safe_write(file, buf, 6));
+ });
+ int6store(buf + RW_MAPID_OFFSET, (ulonglong)m_table_id);
+ int2store(buf + RW_FLAGS_OFFSET, m_flags);
+ return (my_b_safe_write(file, buf, ROWS_HEADER_LEN));
+}
+
+
+bool Old_rows_log_event::write_data_body(IO_CACHE*file)
+{
+ /*
+ Note that this should be the number of *bits*, not the number of
+ bytes.
+ */
+ uchar sbuf[MAX_INT_WIDTH];
+ my_ptrdiff_t const data_size= m_rows_cur - m_rows_buf;
+
+ // This method should not be reached.
+ assert(0);
+
+ bool res= false;
+ uchar *const sbuf_end= net_store_length(sbuf, (size_t) m_width);
+ DBUG_ASSERT(static_cast<size_t>(sbuf_end - sbuf) <= sizeof(sbuf));
+
+ DBUG_DUMP("m_width", sbuf, (size_t) (sbuf_end - sbuf));
+ res= res || my_b_safe_write(file, sbuf, (size_t) (sbuf_end - sbuf));
+
+ DBUG_DUMP("m_cols", (uchar*) m_cols.bitmap, no_bytes_in_map(&m_cols));
+ res= res || my_b_safe_write(file, (uchar*) m_cols.bitmap,
+ no_bytes_in_map(&m_cols));
+ DBUG_DUMP("rows", m_rows_buf, data_size);
+ res= res || my_b_safe_write(file, m_rows_buf, (size_t) data_size);
+
+ return res;
+
+}
+#endif
+
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+void Old_rows_log_event::pack_info(THD *thd, Protocol *protocol)
+{
+ char buf[256];
+ char const *const flagstr=
+ get_flags(STMT_END_F) ? " flags: STMT_END_F" : "";
+ size_t bytes= my_snprintf(buf, sizeof(buf),
+ "table_id: %lu%s", m_table_id, flagstr);
+ protocol->store(buf, bytes, &my_charset_bin);
+}
+#endif
+
+
+#ifdef MYSQL_CLIENT
+void Old_rows_log_event::print_helper(FILE *file,
+ PRINT_EVENT_INFO *print_event_info,
+ char const *const name)
+{
+ IO_CACHE *const head= &print_event_info->head_cache;
+ IO_CACHE *const body= &print_event_info->body_cache;
+ if (!print_event_info->short_form)
+ {
+ bool const last_stmt_event= get_flags(STMT_END_F);
+ print_header(head, print_event_info, !last_stmt_event);
+ my_b_printf(head, "\t%s: table id %lu%s\n",
+ name, m_table_id,
+ last_stmt_event ? " flags: STMT_END_F" : "");
+ print_base64(body, print_event_info, !last_stmt_event);
+ }
+
+ if (get_flags(STMT_END_F))
+ {
+ copy_event_cache_to_file_and_reinit(head, file);
+ copy_event_cache_to_file_and_reinit(body, file);
+ }
+}
+#endif
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+/**
+ Write the current row into event's table.
+
+ The row is located in the row buffer, pointed by @c m_curr_row member.
+ Number of columns of the row is stored in @c m_width member (it can be
+ different from the number of columns in the table to which we insert).
+ Bitmap @c m_cols indicates which columns are present in the row. It is assumed
+ that event's table is already open and pointed by @c m_table.
+
+ If the same record already exists in the table it can be either overwritten
+ or an error is reported depending on the value of @c overwrite flag
+ (error reporting not yet implemented). Note that the matching record can be
+ different from the row we insert if we use primary keys to identify records in
+ the table.
+
+ The row to be inserted can contain values only for selected columns. The
+ missing columns are filled with default values using @c prepare_record()
+ function. If a matching record is found in the table and @c overwritte is
+ true, the missing columns are taken from it.
+
+ @param rli Relay log info (needed for row unpacking).
+ @param overwrite
+ Shall we overwrite if the row already exists or signal
+ error (currently ignored).
+
+ @returns Error code on failure, 0 on success.
+
+ This method, if successful, sets @c m_curr_row_end pointer to point at the
+ next row in the rows buffer. This is done when unpacking the row to be
+ inserted.
+
+ @note If a matching record is found, it is either updated using
+ @c ha_update_row() or first deleted and then new record written.
+*/
+
+int
+Old_rows_log_event::write_row(rpl_group_info *rgi, const bool overwrite)
+{
+ DBUG_ENTER("write_row");
+ DBUG_ASSERT(m_table != NULL && thd != NULL);
+
+ TABLE *table= m_table; // pointer to event's table
+ int error;
+ int keynum;
+ auto_afree_ptr<char> key(NULL);
+
+ /* fill table->record[0] with default values */
+
+ if ((error= prepare_record(table, m_width,
+ TRUE /* check if columns have def. values */)))
+ DBUG_RETURN(error);
+
+ /* unpack row into table->record[0] */
+ error= unpack_current_row(rgi); // TODO: how to handle errors?
+
+#ifndef DBUG_OFF
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+ DBUG_PRINT_BITSET("debug", "write_set = %s", table->write_set);
+ DBUG_PRINT_BITSET("debug", "read_set = %s", table->read_set);
+#endif
+
+ /*
+ Try to write record. If a corresponding record already exists in the table,
+ we try to change it using ha_update_row() if possible. Otherwise we delete
+ it and repeat the whole process again.
+
+ TODO: Add safety measures against infinite looping.
+ */
+
+ while ((error= table->file->ha_write_row(table->record[0])))
+ {
+ if (error == HA_ERR_LOCK_DEADLOCK || error == HA_ERR_LOCK_WAIT_TIMEOUT)
+ {
+ table->file->print_error(error, MYF(0)); /* to check at exec_relay_log_event */
+ DBUG_RETURN(error);
+ }
+ if ((keynum= table->file->get_dup_key(error)) < 0)
+ {
+ DBUG_PRINT("info",("Can't locate duplicate key (get_dup_key returns %d)",keynum));
+ table->file->print_error(error, MYF(0));
+ /*
+ We failed to retrieve the duplicate key
+ - either because the error was not "duplicate key" error
+ - or because the information which key is not available
+ */
+ DBUG_RETURN(error);
+ }
+
+ /*
+ We need to retrieve the old row into record[1] to be able to
+ either update or delete the offending record. We either:
+
+ - use rnd_pos() with a row-id (available as dupp_row) to the
+ offending row, if that is possible (MyISAM and Blackhole), or else
+
+ - use index_read_idx() with the key that is duplicated, to
+ retrieve the offending row.
+ */
+ if (table->file->ha_table_flags() & HA_DUPLICATE_POS)
+ {
+ DBUG_PRINT("info",("Locating offending record using rnd_pos()"));
+ error= table->file->ha_rnd_pos(table->record[1], table->file->dup_ref);
+ if (error)
+ {
+ DBUG_PRINT("info",("rnd_pos() returns error %d",error));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info",("Locating offending record using index_read_idx()"));
+
+ if (table->file->extra(HA_EXTRA_FLUSH_CACHE))
+ {
+ DBUG_PRINT("info",("Error when setting HA_EXTRA_FLUSH_CACHE"));
+ DBUG_RETURN(my_errno);
+ }
+
+ if (key.get() == NULL)
+ {
+ key.assign(static_cast<char*>(my_alloca(table->s->max_unique_length)));
+ if (key.get() == NULL)
+ {
+ DBUG_PRINT("info",("Can't allocate key buffer"));
+ DBUG_RETURN(ENOMEM);
+ }
+ }
+
+ key_copy((uchar*)key.get(), table->record[0], table->key_info + keynum,
+ 0);
+ error= table->file->ha_index_read_idx_map(table->record[1], keynum,
+ (const uchar*)key.get(),
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT);
+ if (error)
+ {
+ DBUG_PRINT("info",("index_read_idx() returns error %d", error));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ }
+
+ /*
+ Now, record[1] should contain the offending row. That
+ will enable us to update it or, alternatively, delete it (so
+ that we can insert the new row afterwards).
+ */
+
+ /*
+ If row is incomplete we will use the record found to fill
+ missing columns.
+ */
+ if (!get_flags(COMPLETE_ROWS_F))
+ {
+ restore_record(table,record[1]);
+ error= unpack_current_row(rgi);
+ }
+
+#ifndef DBUG_OFF
+ DBUG_PRINT("debug",("preparing for update: before and after image"));
+ DBUG_DUMP("record[1] (before)", table->record[1], table->s->reclength);
+ DBUG_DUMP("record[0] (after)", table->record[0], table->s->reclength);
+#endif
+
+ /*
+ REPLACE is defined as either INSERT or DELETE + INSERT. If
+ possible, we can replace it with an UPDATE, but that will not
+ work on InnoDB if FOREIGN KEY checks are necessary.
+
+ I (Matz) am not sure of the reason for the last_uniq_key()
+ check as, but I'm guessing that it's something along the
+ following lines.
+
+ Suppose that we got the duplicate key to be a key that is not
+ the last unique key for the table and we perform an update:
+ then there might be another key for which the unique check will
+ fail, so we're better off just deleting the row and inserting
+ the correct row.
+ */
+ if (last_uniq_key(table, keynum) &&
+ !table->file->referenced_by_foreign_key())
+ {
+ DBUG_PRINT("info",("Updating row using ha_update_row()"));
+ error=table->file->ha_update_row(table->record[1],
+ table->record[0]);
+ switch (error) {
+
+ case HA_ERR_RECORD_IS_THE_SAME:
+ DBUG_PRINT("info",("ignoring HA_ERR_RECORD_IS_THE_SAME error from"
+ " ha_update_row()"));
+ error= 0;
+
+ case 0:
+ break;
+
+ default:
+ DBUG_PRINT("info",("ha_update_row() returns error %d",error));
+ table->file->print_error(error, MYF(0));
+ }
+
+ DBUG_RETURN(error);
+ }
+ else
+ {
+ DBUG_PRINT("info",("Deleting offending row and trying to write new one again"));
+ if ((error= table->file->ha_delete_row(table->record[1])))
+ {
+ DBUG_PRINT("info",("ha_delete_row() returns error %d",error));
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ /* Will retry ha_write_row() with the offending row removed. */
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Locate the current row in event's table.
+
+ The current row is pointed by @c m_curr_row. Member @c m_width tells how many
+ columns are there in the row (this can be differnet from the number of columns
+ in the table). It is assumed that event's table is already open and pointed
+ by @c m_table.
+
+ If a corresponding record is found in the table it is stored in
+ @c m_table->record[0]. Note that when record is located based on a primary
+ key, it is possible that the record found differs from the row being located.
+
+ If no key is specified or table does not have keys, a table scan is used to
+ find the row. In that case the row should be complete and contain values for
+ all columns. However, it can still be shorter than the table, i.e. the table
+ can contain extra columns not present in the row. It is also possible that
+ the table has fewer columns than the row being located.
+
+ @returns Error code on failure, 0 on success.
+
+ @post In case of success @c m_table->record[0] contains the record found.
+ Also, the internal "cursor" of the table is positioned at the record found.
+
+ @note If the engine allows random access of the records, a combination of
+ @c position() and @c rnd_pos() will be used.
+
+ Note that one MUST call ha_index_or_rnd_end() after this function if
+ it returns 0 as we must leave the row position in the handler intact
+ for any following update/delete command.
+*/
+
+int Old_rows_log_event::find_row(rpl_group_info *rgi)
+{
+ DBUG_ENTER("find_row");
+
+ DBUG_ASSERT(m_table && m_table->in_use != NULL);
+
+ TABLE *table= m_table;
+ int error;
+
+ /* unpack row - missing fields get default values */
+
+ // TODO: shall we check and report errors here?
+ prepare_record(table, m_width, FALSE /* don't check errors */);
+ error= unpack_current_row(rgi);
+
+#ifndef DBUG_OFF
+ DBUG_PRINT("info",("looking for the following record"));
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+#endif
+
+ if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
+ table->s->primary_key < MAX_KEY)
+ {
+ /*
+ Use a more efficient method to fetch the record given by
+ table->record[0] if the engine allows it. We first compute a
+ row reference using the position() member function (it will be
+ stored in table->file->ref) and the use rnd_pos() to position
+ the "cursor" (i.e., record[0] in this case) at the correct row.
+
+ TODO: Add a check that the correct record has been fetched by
+ comparing with the original record. Take into account that the
+ record on the master and slave can be of different
+ length. Something along these lines should work:
+
+ ADD>>> store_record(table,record[1]);
+ int error= table->file->ha_rnd_pos(table->record[0], table->file->ref);
+ ADD>>> DBUG_ASSERT(memcmp(table->record[1], table->record[0],
+ table->s->reclength) == 0);
+
+ */
+ DBUG_PRINT("info",("locating record using primary key (position)"));
+ int error= table->file->ha_rnd_pos_by_record(table->record[0]);
+ if (error)
+ {
+ DBUG_PRINT("info",("rnd_pos returns error %d",error));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ }
+ DBUG_RETURN(error);
+ }
+
+ // We can't use position() - try other methods.
+
+ /*
+ We need to retrieve all fields
+ TODO: Move this out from this function to main loop
+ */
+ table->use_all_columns();
+
+ /*
+ Save copy of the record in table->record[1]. It might be needed
+ later if linear search is used to find exact match.
+ */
+ store_record(table,record[1]);
+
+ if (table->s->keys > 0)
+ {
+ DBUG_PRINT("info",("locating record using primary key (index_read)"));
+
+ /* We have a key: search the table using the index */
+ if (!table->file->inited && (error= table->file->ha_index_init(0, FALSE)))
+ {
+ DBUG_PRINT("info",("ha_index_init returns error %d",error));
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+
+ /* Fill key data for the row */
+
+ DBUG_ASSERT(m_key);
+ key_copy(m_key, table->record[0], table->key_info, 0);
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("key data", m_key, table->key_info->key_length);
+#endif
+
+ /*
+ We need to set the null bytes to ensure that the filler bit are
+ all set when returning. There are storage engines that just set
+ the necessary bits on the bytes and don't set the filler bits
+ correctly.
+ */
+ my_ptrdiff_t const pos=
+ table->s->null_bytes > 0 ? table->s->null_bytes - 1 : 0;
+ table->record[0][pos]= 0xFF;
+
+ if ((error= table->file->ha_index_read_map(table->record[0], m_key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT)))
+ {
+ DBUG_PRINT("info",("no record matching the key found in the table"));
+ if (error == HA_ERR_RECORD_DELETED)
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->file->print_error(error, MYF(0));
+ table->file->ha_index_end();
+ DBUG_RETURN(error);
+ }
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_PRINT("info",("found first matching record"));
+ DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
+#endif
+ /*
+ Below is a minor "optimization". If the key (i.e., key number
+ 0) has the HA_NOSAME flag set, we know that we have found the
+ correct record (since there can be no duplicates); otherwise, we
+ have to compare the record with the one found to see if it is
+ the correct one.
+
+ CAVEAT! This behaviour is essential for the replication of,
+ e.g., the mysql.proc table since the correct record *shall* be
+ found using the primary key *only*. There shall be no
+ comparison of non-PK columns to decide if the correct record is
+ found. I can see no scenario where it would be incorrect to
+ chose the row to change only using a PK or an UNNI.
+ */
+ if (table->key_info->flags & HA_NOSAME)
+ {
+ /* Unique does not have non nullable part */
+ if (!(table->key_info->flags & (HA_NULL_PART_KEY)))
+ {
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ KEY *keyinfo= table->key_info;
+ /*
+ Unique has nullable part. We need to check if there is any
+ field in the BI image that is null and part of UNNI.
+ */
+ bool null_found= FALSE;
+ for (uint i=0; i < keyinfo->user_defined_key_parts && !null_found; i++)
+ {
+ uint fieldnr= keyinfo->key_part[i].fieldnr - 1;
+ Field **f= table->field+fieldnr;
+ null_found= (*f)->is_null();
+ }
+
+ if (!null_found)
+ {
+ DBUG_RETURN(0);
+ }
+
+ /* else fall through to index scan */
+ }
+ }
+
+ /*
+ In case key is not unique, we still have to iterate over records found
+ and find the one which is identical to the row given. A copy of the
+ record we are looking for is stored in record[1].
+ */
+ DBUG_PRINT("info",("non-unique index, scanning it to find matching record"));
+
+ while (record_compare(table))
+ {
+ /*
+ We need to set the null bytes to ensure that the filler bit
+ are all set when returning. There are storage engines that
+ just set the necessary bits on the bytes and don't set the
+ filler bits correctly.
+
+ TODO[record format ndb]: Remove this code once NDB returns the
+ correct record format.
+ */
+ if (table->s->null_bytes > 0)
+ {
+ table->record[0][table->s->null_bytes - 1]|=
+ 256U - (1U << table->s->last_null_bit_pos);
+ }
+
+ while ((error= table->file->ha_index_next(table->record[0])))
+ {
+ /* We just skip records that has already been deleted */
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ DBUG_PRINT("info",("no record matching the given row found"));
+ table->file->print_error(error, MYF(0));
+ (void) table->file->ha_index_end();
+ DBUG_RETURN(error);
+ }
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info",("locating record using table scan (rnd_next)"));
+
+ int restart_count= 0; // Number of times scanning has restarted from top
+
+ /* We don't have a key: search the table using rnd_next() */
+ if ((error= table->file->ha_rnd_init_with_error(1)))
+ {
+ DBUG_PRINT("info",("error initializing table scan"
+ " (ha_rnd_init returns %d)",error));
+ DBUG_RETURN(error);
+ }
+
+ /* Continue until we find the right record or have made a full loop */
+ do
+ {
+ restart_rnd_next:
+ error= table->file->ha_rnd_next(table->record[0]);
+
+ switch (error) {
+
+ case 0:
+ break;
+
+ case HA_ERR_RECORD_DELETED:
+ goto restart_rnd_next;
+
+ case HA_ERR_END_OF_FILE:
+ if (++restart_count < 2)
+ {
+ int error2;
+ table->file->ha_rnd_end();
+ if ((error2= table->file->ha_rnd_init_with_error(1)))
+ DBUG_RETURN(error2);
+ goto restart_rnd_next;
+ }
+ break;
+
+ default:
+ DBUG_PRINT("info", ("Failed to get next record"
+ " (rnd_next returns %d)",error));
+ table->file->print_error(error, MYF(0));
+ table->file->ha_rnd_end();
+ DBUG_RETURN(error);
+ }
+ }
+ while (restart_count < 2 && record_compare(table));
+
+ /*
+ Note: above record_compare will take into accout all record fields
+ which might be incorrect in case a partial row was given in the event
+ */
+
+ /*
+ Have to restart the scan to be able to fetch the next row.
+ */
+ if (restart_count == 2)
+ DBUG_PRINT("info", ("Record not found"));
+ else
+ DBUG_DUMP("record found", table->record[0], table->s->reclength);
+ if (error)
+ table->file->ha_rnd_end();
+
+ DBUG_ASSERT(error == HA_ERR_END_OF_FILE || error == 0);
+ DBUG_RETURN(error);
+ }
+
+ DBUG_RETURN(0);
+}
+
+#endif
+
+
+/**************************************************************************
+ Write_rows_log_event member functions
+**************************************************************************/
+
+/*
+ Constructor used to build an event for writing to the binary log.
+ */
+#if !defined(MYSQL_CLIENT)
+Write_rows_log_event_old::Write_rows_log_event_old(THD *thd_arg,
+ TABLE *tbl_arg,
+ ulong tid_arg,
+ MY_BITMAP const *cols,
+ bool is_transactional)
+ : Old_rows_log_event(thd_arg, tbl_arg, tid_arg, cols, is_transactional)
+{
+
+ // This constructor should not be reached.
+ assert(0);
+
+}
+#endif
+
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Write_rows_log_event_old::Write_rows_log_event_old(const char *buf,
+ uint event_len,
+ const Format_description_log_event
+ *description_event)
+: Old_rows_log_event(buf, event_len, PRE_GA_WRITE_ROWS_EVENT,
+ description_event)
+{
+}
+#endif
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int
+Write_rows_log_event_old::do_before_row_operations(const Slave_reporting_capability *const)
+{
+ int error= 0;
+
+ /*
+ We are using REPLACE semantics and not INSERT IGNORE semantics
+ when writing rows, that is: new rows replace old rows. We need to
+ inform the storage engine that it should use this behaviour.
+ */
+
+ /* Tell the storage engine that we are using REPLACE semantics. */
+ thd->lex->duplicates= DUP_REPLACE;
+
+ /*
+ Pretend we're executing a REPLACE command: this is needed for
+ InnoDB and NDB Cluster since they are not (properly) checking the
+ lex->duplicates flag.
+ */
+ thd->lex->sql_command= SQLCOM_REPLACE;
+ /*
+ Do not raise the error flag in case of hitting to an unique attribute
+ */
+ m_table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ /*
+ NDB specific: update from ndb master wrapped as Write_rows
+ */
+ /*
+ so that the event should be applied to replace slave's row
+ */
+ m_table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ /*
+ NDB specific: if update from ndb master wrapped as Write_rows
+ does not find the row it's assumed idempotent binlog applying
+ is taking place; don't raise the error.
+ */
+ m_table->file->extra(HA_EXTRA_IGNORE_NO_KEY);
+ /*
+ TODO: the cluster team (Tomas?) says that it's better if the engine knows
+ how many rows are going to be inserted, then it can allocate needed memory
+ from the start.
+ */
+ m_table->file->ha_start_bulk_insert(0);
+ return error;
+}
+
+
+int
+Write_rows_log_event_old::do_after_row_operations(const Slave_reporting_capability *const,
+ int error)
+{
+ int local_error= 0;
+ m_table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ m_table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+ /*
+ reseting the extra with
+ table->file->extra(HA_EXTRA_NO_IGNORE_NO_KEY);
+ fires bug#27077
+ todo: explain or fix
+ */
+ if ((local_error= m_table->file->ha_end_bulk_insert()))
+ {
+ m_table->file->print_error(local_error, MYF(0));
+ }
+ return error? error : local_error;
+}
+
+
+int
+Write_rows_log_event_old::do_exec_row(rpl_group_info *rgi)
+{
+ DBUG_ASSERT(m_table != NULL);
+ int error= write_row(rgi, TRUE /* overwrite */);
+
+ if (error && !thd->net.last_errno)
+ thd->net.last_errno= error;
+
+ return error;
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+
+#ifdef MYSQL_CLIENT
+void Write_rows_log_event_old::print(FILE *file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Old_rows_log_event::print_helper(file, print_event_info, "Write_rows_old");
+}
+#endif
+
+
+/**************************************************************************
+ Delete_rows_log_event member functions
+**************************************************************************/
+
+/*
+ Constructor used to build an event for writing to the binary log.
+ */
+
+#ifndef MYSQL_CLIENT
+Delete_rows_log_event_old::Delete_rows_log_event_old(THD *thd_arg,
+ TABLE *tbl_arg,
+ ulong tid,
+ MY_BITMAP const *cols,
+ bool is_transactional)
+ : Old_rows_log_event(thd_arg, tbl_arg, tid, cols, is_transactional),
+ m_after_image(NULL), m_memory(NULL)
+{
+
+ // This constructor should not be reached.
+ assert(0);
+
+}
+#endif /* #if !defined(MYSQL_CLIENT) */
+
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Delete_rows_log_event_old::Delete_rows_log_event_old(const char *buf,
+ uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Old_rows_log_event(buf, event_len, PRE_GA_DELETE_ROWS_EVENT,
+ description_event),
+ m_after_image(NULL), m_memory(NULL)
+{
+}
+#endif
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+int
+Delete_rows_log_event_old::do_before_row_operations(const Slave_reporting_capability *const)
+{
+ if ((m_table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
+ m_table->s->primary_key < MAX_KEY)
+ {
+ /*
+ We don't need to allocate any memory for m_key since it is not used.
+ */
+ return 0;
+ }
+
+ if (m_table->s->keys > 0)
+ {
+ // Allocate buffer for key searches
+ m_key= (uchar*)my_malloc(m_table->key_info->key_length, MYF(MY_WME));
+ if (!m_key)
+ return HA_ERR_OUT_OF_MEM;
+ }
+ return 0;
+}
+
+
+int
+Delete_rows_log_event_old::do_after_row_operations(const Slave_reporting_capability *const,
+ int error)
+{
+ /*error= ToDo:find out what this should really be, this triggers close_scan in nbd, returning error?*/
+ m_table->file->ha_index_or_rnd_end();
+ my_free(m_key);
+ m_key= NULL;
+
+ return error;
+}
+
+
+int Delete_rows_log_event_old::do_exec_row(rpl_group_info *rgi)
+{
+ int error;
+ DBUG_ASSERT(m_table != NULL);
+
+ if (!(error= find_row(rgi)))
+ {
+ /*
+ Delete the record found, located in record[0]
+ */
+ error= m_table->file->ha_delete_row(m_table->record[0]);
+ m_table->file->ha_index_or_rnd_end();
+ }
+ return error;
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+
+#ifdef MYSQL_CLIENT
+void Delete_rows_log_event_old::print(FILE *file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Old_rows_log_event::print_helper(file, print_event_info, "Delete_rows_old");
+}
+#endif
+
+
+/**************************************************************************
+ Update_rows_log_event member functions
+**************************************************************************/
+
+/*
+ Constructor used to build an event for writing to the binary log.
+ */
+#if !defined(MYSQL_CLIENT)
+Update_rows_log_event_old::Update_rows_log_event_old(THD *thd_arg,
+ TABLE *tbl_arg,
+ ulong tid,
+ MY_BITMAP const *cols,
+ bool is_transactional)
+ : Old_rows_log_event(thd_arg, tbl_arg, tid, cols, is_transactional),
+ m_after_image(NULL), m_memory(NULL)
+{
+
+ // This constructor should not be reached.
+ assert(0);
+}
+#endif /* !defined(MYSQL_CLIENT) */
+
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Update_rows_log_event_old::Update_rows_log_event_old(const char *buf,
+ uint event_len,
+ const
+ Format_description_log_event
+ *description_event)
+ : Old_rows_log_event(buf, event_len, PRE_GA_UPDATE_ROWS_EVENT,
+ description_event),
+ m_after_image(NULL), m_memory(NULL)
+{
+}
+#endif
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+int
+Update_rows_log_event_old::do_before_row_operations(const Slave_reporting_capability *const)
+{
+ if (m_table->s->keys > 0)
+ {
+ // Allocate buffer for key searches
+ m_key= (uchar*)my_malloc(m_table->key_info->key_length, MYF(MY_WME));
+ if (!m_key)
+ return HA_ERR_OUT_OF_MEM;
+ }
+
+ return 0;
+}
+
+
+int
+Update_rows_log_event_old::do_after_row_operations(const Slave_reporting_capability *const,
+ int error)
+{
+ /*error= ToDo:find out what this should really be, this triggers close_scan in nbd, returning error?*/
+ m_table->file->ha_index_or_rnd_end();
+ my_free(m_key); // Free for multi_malloc
+ m_key= NULL;
+
+ return error;
+}
+
+
+int
+Update_rows_log_event_old::do_exec_row(rpl_group_info *rgi)
+{
+ DBUG_ASSERT(m_table != NULL);
+
+ int error= find_row(rgi);
+ if (error)
+ {
+ /*
+ We need to read the second image in the event of error to be
+ able to skip to the next pair of updates
+ */
+ m_curr_row= m_curr_row_end;
+ unpack_current_row(rgi);
+ return error;
+ }
+
+ /*
+ This is the situation after locating BI:
+
+ ===|=== before image ====|=== after image ===|===
+ ^ ^
+ m_curr_row m_curr_row_end
+
+ BI found in the table is stored in record[0]. We copy it to record[1]
+ and unpack AI to record[0].
+ */
+
+ store_record(m_table,record[1]);
+
+ m_curr_row= m_curr_row_end;
+ error= unpack_current_row(rgi); // this also updates m_curr_row_end
+
+ /*
+ Now we have the right row to update. The old row (the one we're
+ looking for) is in record[1] and the new row is in record[0].
+ */
+#ifndef HAVE_valgrind
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+ DBUG_PRINT("info",("Updating row in table"));
+ DBUG_DUMP("old record", m_table->record[1], m_table->s->reclength);
+ DBUG_DUMP("new values", m_table->record[0], m_table->s->reclength);
+#endif
+
+ error= m_table->file->ha_update_row(m_table->record[1], m_table->record[0]);
+ m_table->file->ha_index_or_rnd_end();
+
+ if (error == HA_ERR_RECORD_IS_THE_SAME)
+ error= 0;
+
+ return error;
+}
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+
+#ifdef MYSQL_CLIENT
+void Update_rows_log_event_old::print(FILE *file,
+ PRINT_EVENT_INFO* print_event_info)
+{
+ Old_rows_log_event::print_helper(file, print_event_info, "Update_rows_old");
+}
+#endif
diff --git a/sql/log_event_old.h b/sql/log_event_old.h
new file mode 100644
index 00000000000..7408e121f96
--- /dev/null
+++ b/sql/log_event_old.h
@@ -0,0 +1,573 @@
+/* Copyright (c) 2007, 2013, Oracle and/or its affiliates.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#ifndef LOG_EVENT_OLD_H
+#define LOG_EVENT_OLD_H
+
+/*
+ Need to include this file at the proper position of log_event.h
+ */
+
+
+/**
+ @file
+
+ @brief This file contains classes handling old formats of row-based
+ binlog events.
+*/
+/*
+ Around 2007-10-31, I made these classes completely separated from
+ the new classes (before, there was a complex class hierarchy
+ involving multiple inheritance; see BUG#31581), by simply copying
+ and pasting the entire contents of Rows_log_event into
+ Old_rows_log_event and the entire contents of
+ {Write|Update|Delete}_rows_log_event into
+ {Write|Update|Delete}_rows_log_event_old. For clarity, I will keep
+ the comments marking which code was cut-and-pasted for some time.
+ With the classes collapsed into one, there is probably some
+ redundancy (maybe some methods can be simplified and/or removed),
+ but we keep them this way for now. /Sven
+*/
+
+/* These classes are based on the v1 RowsHeaderLen */
+#undef ROWS_HEADER_LEN
+#define ROWS_HEADER_LEN ROWS_HEADER_LEN_V1
+
+/**
+ @class Old_rows_log_event
+
+ Base class for the three types of row-based events
+ {Write|Update|Delete}_row_log_event_old, with event type codes
+ PRE_GA_{WRITE|UPDATE|DELETE}_ROWS_EVENT. These events are never
+ created any more, except when reading a relay log created by an old
+ server.
+*/
+class Old_rows_log_event : public Log_event
+{
+ /********** BEGIN CUT & PASTE FROM Rows_log_event **********/
+public:
+ /**
+ Enumeration of the errors that can be returned.
+ */
+ enum enum_error
+ {
+ ERR_OPEN_FAILURE = -1, /**< Failure to open table */
+ ERR_OK = 0, /**< No error */
+ ERR_TABLE_LIMIT_EXCEEDED = 1, /**< No more room for tables */
+ ERR_OUT_OF_MEM = 2, /**< Out of memory */
+ ERR_BAD_TABLE_DEF = 3, /**< Table definition does not match */
+ ERR_RBR_TO_SBR = 4 /**< daisy-chanining RBR to SBR not allowed */
+ };
+
+ /*
+ These definitions allow you to combine the flags into an
+ appropriate flag set using the normal bitwise operators. The
+ implicit conversion from an enum-constant to an integer is
+ accepted by the compiler, which is then used to set the real set
+ of flags.
+ */
+ enum enum_flag
+ {
+ /* Last event of a statement */
+ STMT_END_F = (1U << 0),
+
+ /* Value of the OPTION_NO_FOREIGN_KEY_CHECKS flag in thd->options */
+ NO_FOREIGN_KEY_CHECKS_F = (1U << 1),
+
+ /* Value of the OPTION_RELAXED_UNIQUE_CHECKS flag in thd->options */
+ RELAXED_UNIQUE_CHECKS_F = (1U << 2),
+
+ /**
+ Indicates that rows in this event are complete, that is contain
+ values for all columns of the table.
+ */
+ COMPLETE_ROWS_F = (1U << 3)
+ };
+
+ typedef uint16 flag_set;
+
+ /* Special constants representing sets of flags */
+ enum
+ {
+ RLE_NO_FLAGS = 0U
+ };
+
+ virtual ~Old_rows_log_event();
+
+ void set_flags(flag_set flags_arg) { m_flags |= flags_arg; }
+ void clear_flags(flag_set flags_arg) { m_flags &= ~flags_arg; }
+ flag_set get_flags(flag_set flags_arg) const { return m_flags & flags_arg; }
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ virtual void pack_info(THD *thd, Protocol *protocol);
+#endif
+
+#ifdef MYSQL_CLIENT
+ /* not for direct call, each derived has its own ::print() */
+ virtual void print(FILE *file, PRINT_EVENT_INFO *print_event_info)= 0;
+#endif
+
+#ifndef MYSQL_CLIENT
+ int add_row_data(uchar *data, size_t length)
+ {
+ return do_add_row_data(data,length);
+ }
+#endif
+
+ /* Member functions to implement superclass interface */
+ virtual int get_data_size();
+
+ MY_BITMAP const *get_cols() const { return &m_cols; }
+ size_t get_width() const { return m_width; }
+ ulong get_table_id() const { return m_table_id; }
+
+#ifndef MYSQL_CLIENT
+ virtual bool write_data_header(IO_CACHE *file);
+ virtual bool write_data_body(IO_CACHE *file);
+ virtual const char *get_db() { return m_table->s->db.str; }
+#endif
+ /*
+ Check that malloc() succeeded in allocating memory for the rows
+ buffer and the COLS vector. Checking that an Update_rows_log_event_old
+ is valid is done in the Update_rows_log_event_old::is_valid()
+ function.
+ */
+ virtual bool is_valid() const
+ {
+ return m_rows_buf && m_cols.bitmap;
+ }
+ bool is_part_of_group() { return 1; }
+
+ uint m_row_count; /* The number of rows added to the event */
+
+protected:
+ /*
+ The constructors are protected since you're supposed to inherit
+ this class, not create instances of this class.
+ */
+#ifndef MYSQL_CLIENT
+ Old_rows_log_event(THD*, TABLE*, ulong table_id,
+ MY_BITMAP const *cols, bool is_transactional);
+#endif
+ Old_rows_log_event(const char *row_data, uint event_len,
+ Log_event_type event_type,
+ const Format_description_log_event *description_event);
+
+#ifdef MYSQL_CLIENT
+ void print_helper(FILE *, PRINT_EVENT_INFO *, char const *const name);
+#endif
+
+#ifndef MYSQL_CLIENT
+ virtual int do_add_row_data(uchar *data, size_t length);
+#endif
+
+#ifndef MYSQL_CLIENT
+ TABLE *m_table; /* The table the rows belong to */
+#endif
+ ulong m_table_id; /* Table ID */
+ MY_BITMAP m_cols; /* Bitmap denoting columns available */
+ ulong m_width; /* The width of the columns bitmap */
+
+ ulong m_master_reclength; /* Length of record on master side */
+
+ /* Bit buffers in the same memory as the class */
+ uint32 m_bitbuf[128/(sizeof(uint32)*8)];
+ uint32 m_bitbuf_ai[128/(sizeof(uint32)*8)];
+
+ uchar *m_rows_buf; /* The rows in packed format */
+ uchar *m_rows_cur; /* One-after the end of the data */
+ uchar *m_rows_end; /* One-after the end of the allocated space */
+
+ flag_set m_flags; /* Flags for row-level events */
+
+ /* helper functions */
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ const uchar *m_curr_row; /* Start of the row being processed */
+ const uchar *m_curr_row_end; /* One-after the end of the current row */
+ uchar *m_key; /* Buffer to keep key value during searches */
+
+ int find_row(rpl_group_info *);
+ int write_row(rpl_group_info *, const bool);
+
+ // Unpack the current row into m_table->record[0]
+ int unpack_current_row(rpl_group_info *rgi)
+ {
+ DBUG_ASSERT(m_table);
+ ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT);
+ return ::unpack_row(rgi, m_table, m_width, m_curr_row, &m_cols,
+ &m_curr_row_end, &m_master_reclength, m_rows_end);
+ }
+#endif
+
+private:
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ virtual int do_apply_event(rpl_group_info *rgi);
+ virtual int do_update_pos(rpl_group_info *rgi);
+ virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
+
+ /*
+ Primitive to prepare for a sequence of row executions.
+
+ DESCRIPTION
+
+ Before doing a sequence of do_prepare_row() and do_exec_row()
+ calls, this member function should be called to prepare for the
+ entire sequence. Typically, this member function will allocate
+ space for any buffers that are needed for the two member
+ functions mentioned above.
+
+ RETURN VALUE
+
+ The member function will return 0 if all went OK, or a non-zero
+ error code otherwise.
+ */
+ virtual
+ int do_before_row_operations(const Slave_reporting_capability *const log) = 0;
+
+ /*
+ Primitive to clean up after a sequence of row executions.
+
+ DESCRIPTION
+
+ After doing a sequence of do_prepare_row() and do_exec_row(),
+ this member function should be called to clean up and release
+ any allocated buffers.
+
+ The error argument, if non-zero, indicates an error which happened during
+ row processing before this function was called. In this case, even if
+ function is successful, it should return the error code given in the argument.
+ */
+ virtual
+ int do_after_row_operations(const Slave_reporting_capability *const log,
+ int error) = 0;
+
+ /*
+ Primitive to do the actual execution necessary for a row.
+
+ DESCRIPTION
+ The member function will do the actual execution needed to handle a row.
+ The row is located at m_curr_row. When the function returns,
+ m_curr_row_end should point at the next row (one byte after the end
+ of the current row).
+
+ RETURN VALUE
+ 0 if execution succeeded, 1 if execution failed.
+
+ */
+ virtual int do_exec_row(rpl_group_info *rgi) = 0;
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+
+ /********** END OF CUT & PASTE FROM Rows_log_event **********/
+ protected:
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+
+ int do_apply_event(Old_rows_log_event*, rpl_group_info *rgi);
+
+ /*
+ Primitive to prepare for a sequence of row executions.
+
+ DESCRIPTION
+
+ Before doing a sequence of do_prepare_row() and do_exec_row()
+ calls, this member function should be called to prepare for the
+ entire sequence. Typically, this member function will allocate
+ space for any buffers that are needed for the two member
+ functions mentioned above.
+
+ RETURN VALUE
+
+ The member function will return 0 if all went OK, or a non-zero
+ error code otherwise.
+ */
+ virtual int do_before_row_operations(TABLE *table) = 0;
+
+ /*
+ Primitive to clean up after a sequence of row executions.
+
+ DESCRIPTION
+
+ After doing a sequence of do_prepare_row() and do_exec_row(),
+ this member function should be called to clean up and release
+ any allocated buffers.
+ */
+ virtual int do_after_row_operations(TABLE *table, int error) = 0;
+
+ /*
+ Primitive to prepare for handling one row in a row-level event.
+
+ DESCRIPTION
+
+ The member function prepares for execution of operations needed for one
+ row in a row-level event by reading up data from the buffer containing
+ the row. No specific interpretation of the data is normally done here,
+ since SQL thread specific data is not available: that data is made
+ available for the do_exec function.
+
+ A pointer to the start of the next row, or NULL if the preparation
+ failed. Currently, preparation cannot fail, but don't rely on this
+ behavior.
+
+ RETURN VALUE
+ Error code, if something went wrong, 0 otherwise.
+ */
+ virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*,
+ uchar const *row_start,
+ uchar const **row_end) = 0;
+
+ /*
+ Primitive to do the actual execution necessary for a row.
+
+ DESCRIPTION
+ The member function will do the actual execution needed to handle a row.
+
+ RETURN VALUE
+ 0 if execution succeeded, 1 if execution failed.
+
+ */
+ virtual int do_exec_row(TABLE *table) = 0;
+
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+};
+
+
+/**
+ @class Write_rows_log_event_old
+
+ Old class for binlog events that write new rows to a table (event
+ type code PRE_GA_WRITE_ROWS_EVENT). Such events are never produced
+ by this version of the server, but they may be read from a relay log
+ created by an old server. New servers create events of class
+ Write_rows_log_event (event type code WRITE_ROWS_EVENT) instead.
+*/
+class Write_rows_log_event_old : public Old_rows_log_event
+{
+ /********** BEGIN CUT & PASTE FROM Write_rows_log_event **********/
+public:
+#if !defined(MYSQL_CLIENT)
+ Write_rows_log_event_old(THD*, TABLE*, ulong table_id,
+ MY_BITMAP const *cols, bool is_transactional);
+#endif
+#ifdef HAVE_REPLICATION
+ Write_rows_log_event_old(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+#endif
+#if !defined(MYSQL_CLIENT)
+ static bool binlog_row_logging_function(THD *thd, TABLE *table,
+ bool is_transactional,
+ MY_BITMAP *cols,
+ uint fields,
+ const uchar *before_record
+ __attribute__((unused)),
+ const uchar *after_record)
+ {
+ return thd->binlog_write_row(table, is_transactional,
+ cols, fields, after_record);
+ }
+#endif
+
+private:
+#ifdef MYSQL_CLIENT
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ virtual int do_before_row_operations(const Slave_reporting_capability *const);
+ virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
+ virtual int do_exec_row(rpl_group_info *);
+#endif
+ /********** END OF CUT & PASTE FROM Write_rows_log_event **********/
+
+public:
+ enum
+ {
+ /* Support interface to THD::binlog_prepare_pending_rows_event */
+ TYPE_CODE = PRE_GA_WRITE_ROWS_EVENT
+ };
+
+private:
+ virtual Log_event_type get_type_code() { return (Log_event_type)TYPE_CODE; }
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ // use old definition of do_apply_event()
+ virtual int do_apply_event(rpl_group_info *rgi)
+ { return Old_rows_log_event::do_apply_event(this, rgi); }
+
+ // primitives for old version of do_apply_event()
+ virtual int do_before_row_operations(TABLE *table);
+ virtual int do_after_row_operations(TABLE *table, int error);
+ virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*,
+ uchar const *row_start, uchar const **row_end);
+ virtual int do_exec_row(TABLE *table);
+
+#endif
+};
+
+
+/**
+ @class Update_rows_log_event_old
+
+ Old class for binlog events that modify existing rows to a table
+ (event type code PRE_GA_UPDATE_ROWS_EVENT). Such events are never
+ produced by this version of the server, but they may be read from a
+ relay log created by an old server. New servers create events of
+ class Update_rows_log_event (event type code UPDATE_ROWS_EVENT)
+ instead.
+*/
+class Update_rows_log_event_old : public Old_rows_log_event
+{
+ /********** BEGIN CUT & PASTE FROM Update_rows_log_event **********/
+public:
+#ifndef MYSQL_CLIENT
+ Update_rows_log_event_old(THD*, TABLE*, ulong table_id,
+ MY_BITMAP const *cols,
+ bool is_transactional);
+#endif
+
+#ifdef HAVE_REPLICATION
+ Update_rows_log_event_old(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+#endif
+
+#if !defined(MYSQL_CLIENT)
+ static bool binlog_row_logging_function(THD *thd, TABLE *table,
+ bool is_transactional,
+ MY_BITMAP *cols,
+ uint fields,
+ const uchar *before_record,
+ const uchar *after_record)
+ {
+ return thd->binlog_update_row(table, is_transactional,
+ cols, fields, before_record, after_record);
+ }
+#endif
+
+protected:
+#ifdef MYSQL_CLIENT
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ virtual int do_before_row_operations(const Slave_reporting_capability *const);
+ virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
+ virtual int do_exec_row(rpl_group_info *);
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+ /********** END OF CUT & PASTE FROM Update_rows_log_event **********/
+
+ uchar *m_after_image, *m_memory;
+
+public:
+ enum
+ {
+ /* Support interface to THD::binlog_prepare_pending_rows_event */
+ TYPE_CODE = PRE_GA_UPDATE_ROWS_EVENT
+ };
+
+private:
+ virtual Log_event_type get_type_code() { return (Log_event_type)TYPE_CODE; }
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ // use old definition of do_apply_event()
+ virtual int do_apply_event(rpl_group_info *rgi)
+ { return Old_rows_log_event::do_apply_event(this, rgi); }
+
+ // primitives for old version of do_apply_event()
+ virtual int do_before_row_operations(TABLE *table);
+ virtual int do_after_row_operations(TABLE *table, int error);
+ virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*,
+ uchar const *row_start, uchar const **row_end);
+ virtual int do_exec_row(TABLE *table);
+#endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */
+};
+
+
+/**
+ @class Delete_rows_log_event_old
+
+ Old class for binlog events that delete existing rows from a table
+ (event type code PRE_GA_DELETE_ROWS_EVENT). Such events are never
+ produced by this version of the server, but they may be read from a
+ relay log created by an old server. New servers create events of
+ class Delete_rows_log_event (event type code DELETE_ROWS_EVENT)
+ instead.
+*/
+class Delete_rows_log_event_old : public Old_rows_log_event
+{
+ /********** BEGIN CUT & PASTE FROM Update_rows_log_event **********/
+public:
+#ifndef MYSQL_CLIENT
+ Delete_rows_log_event_old(THD*, TABLE*, ulong,
+ MY_BITMAP const *cols, bool is_transactional);
+#endif
+#ifdef HAVE_REPLICATION
+ Delete_rows_log_event_old(const char *buf, uint event_len,
+ const Format_description_log_event *description_event);
+#endif
+#if !defined(MYSQL_CLIENT)
+ static bool binlog_row_logging_function(THD *thd, TABLE *table,
+ bool is_transactional,
+ MY_BITMAP *cols,
+ uint fields,
+ const uchar *before_record,
+ const uchar *after_record
+ __attribute__((unused)))
+ {
+ return thd->binlog_delete_row(table, is_transactional,
+ cols, fields, before_record);
+ }
+#endif
+
+protected:
+#ifdef MYSQL_CLIENT
+ void print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ virtual int do_before_row_operations(const Slave_reporting_capability *const);
+ virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
+ virtual int do_exec_row(rpl_group_info *);
+#endif
+ /********** END CUT & PASTE FROM Delete_rows_log_event **********/
+
+ uchar *m_after_image, *m_memory;
+
+public:
+ enum
+ {
+ /* Support interface to THD::binlog_prepare_pending_rows_event */
+ TYPE_CODE = PRE_GA_DELETE_ROWS_EVENT
+ };
+
+private:
+ virtual Log_event_type get_type_code() { return (Log_event_type)TYPE_CODE; }
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ // use old definition of do_apply_event()
+ virtual int do_apply_event(rpl_group_info *rgi)
+ { return Old_rows_log_event::do_apply_event(this, rgi); }
+
+ // primitives for old version of do_apply_event()
+ virtual int do_before_row_operations(TABLE *table);
+ virtual int do_after_row_operations(TABLE *table, int error);
+ virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*,
+ uchar const *row_start, uchar const **row_end);
+ virtual int do_exec_row(TABLE *table);
+#endif
+};
+
+
+#endif
diff --git a/sql/log_slow.h b/sql/log_slow.h
new file mode 100644
index 00000000000..2ae07da97c3
--- /dev/null
+++ b/sql/log_slow.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2009 Monty Program Ab
+
+ 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 or later 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/* Defining what to log to slow log */
+
+#define LOG_SLOW_VERBOSITY_INIT 0
+#define LOG_SLOW_VERBOSITY_INNODB 1 << 0
+#define LOG_SLOW_VERBOSITY_QUERY_PLAN 1 << 1
+#define LOG_SLOW_VERBOSITY_EXPLAIN 1 << 2
+
+#define QPLAN_INIT QPLAN_QC_NO
+
+#define QPLAN_ADMIN 1 << 0
+#define QPLAN_FILESORT 1 << 1
+#define QPLAN_FILESORT_DISK 1 << 2
+#define QPLAN_FULL_JOIN 1 << 3
+#define QPLAN_FULL_SCAN 1 << 4
+#define QPLAN_QC 1 << 5
+#define QPLAN_QC_NO 1 << 6
+#define QPLAN_TMP_DISK 1 << 7
+#define QPLAN_TMP_TABLE 1 << 8
+#define QPLAN_FILESORT_PRIORITY_QUEUE 1 << 9
+
+/* ... */
+#define QPLAN_MAX ((ulong) 1) << 31 /* reserved as placeholder */
+
diff --git a/sql/main.cc b/sql/main.cc
new file mode 100644
index 00000000000..10141c132a6
--- /dev/null
+++ b/sql/main.cc
@@ -0,0 +1,26 @@
+/* Copyright (c) 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+/*
+ main() for mysqld.
+ Calls mysqld_main() entry point exported by sql library.
+*/
+extern int mysqld_main(int argc, char **argv);
+
+int main(int argc, char **argv)
+{
+ return mysqld_main(argc, argv);
+}
diff --git a/sql/mdl.cc b/sql/mdl.cc
new file mode 100644
index 00000000000..b94a3710fd1
--- /dev/null
+++ b/sql/mdl.cc
@@ -0,0 +1,3225 @@
+/* Copyright (c) 2007, 2012, Oracle and/or its affiliates.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+
+#include "sql_class.h"
+#include "debug_sync.h"
+#include "sql_array.h"
+#include <hash.h>
+#include <mysqld_error.h>
+#include <mysql/plugin.h>
+#include <mysql/service_thd_wait.h>
+#include <mysql/psi/mysql_stage.h>
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_MDL_map_mutex;
+static PSI_mutex_key key_MDL_wait_LOCK_wait_status;
+
+static PSI_mutex_info all_mdl_mutexes[]=
+{
+ { &key_MDL_map_mutex, "MDL_map::mutex", 0},
+ { &key_MDL_wait_LOCK_wait_status, "MDL_wait::LOCK_wait_status", 0}
+};
+
+static PSI_rwlock_key key_MDL_lock_rwlock;
+static PSI_rwlock_key key_MDL_context_LOCK_waiting_for;
+
+static PSI_rwlock_info all_mdl_rwlocks[]=
+{
+ { &key_MDL_lock_rwlock, "MDL_lock::rwlock", 0},
+ { &key_MDL_context_LOCK_waiting_for, "MDL_context::LOCK_waiting_for", 0}
+};
+
+static PSI_cond_key key_MDL_wait_COND_wait_status;
+
+static PSI_cond_info all_mdl_conds[]=
+{
+ { &key_MDL_wait_COND_wait_status, "MDL_context::COND_wait_status", 0}
+};
+
+/**
+ Initialise all the performance schema instrumentation points
+ used by the MDL subsystem.
+*/
+static void init_mdl_psi_keys(void)
+{
+ int count;
+
+ count= array_elements(all_mdl_mutexes);
+ mysql_mutex_register("sql", all_mdl_mutexes, count);
+
+ count= array_elements(all_mdl_rwlocks);
+ mysql_rwlock_register("sql", all_mdl_rwlocks, count);
+
+ count= array_elements(all_mdl_conds);
+ mysql_cond_register("sql", all_mdl_conds, count);
+
+ MDL_key::init_psi_keys();
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+
+/**
+ Thread state names to be used in case when we have to wait on resource
+ belonging to certain namespace.
+*/
+
+PSI_stage_info MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]=
+{
+ {0, "Waiting for global read lock", 0},
+ {0, "Waiting for schema metadata lock", 0},
+ {0, "Waiting for table metadata lock", 0},
+ {0, "Waiting for stored function metadata lock", 0},
+ {0, "Waiting for stored procedure metadata lock", 0},
+ {0, "Waiting for trigger metadata lock", 0},
+ {0, "Waiting for event metadata lock", 0},
+ {0, "Waiting for commit lock", 0},
+ {0, "User lock", 0} /* Be compatible with old status. */
+};
+
+#ifdef HAVE_PSI_INTERFACE
+void MDL_key::init_psi_keys()
+{
+ int i;
+ int count;
+ PSI_stage_info *info __attribute__((unused));
+
+ count= array_elements(MDL_key::m_namespace_to_wait_state_name);
+ for (i= 0; i<count; i++)
+ {
+ /* mysql_stage_register wants an array of pointers, registering 1 by 1. */
+ info= & MDL_key::m_namespace_to_wait_state_name[i];
+ mysql_stage_register("sql", &info, 1);
+ }
+}
+#endif
+
+static bool mdl_initialized= 0;
+
+
+class MDL_object_lock;
+class MDL_object_lock_cache_adapter;
+
+
+/**
+ A partition in a collection of all MDL locks.
+ MDL_map is partitioned for scalability reasons.
+ Maps MDL_key to MDL_lock instances.
+*/
+
+class MDL_map_partition
+{
+public:
+ MDL_map_partition();
+ ~MDL_map_partition();
+ inline MDL_lock *find_or_insert(const MDL_key *mdl_key);
+ unsigned long get_lock_owner(const MDL_key *key);
+ inline void remove(MDL_lock *lock);
+private:
+ bool move_from_hash_to_lock_mutex(MDL_lock *lock);
+ /** A partition of all acquired locks in the server. */
+ HASH m_locks;
+ /* Protects access to m_locks hash. */
+ mysql_mutex_t m_mutex;
+ /**
+ Cache of (unused) MDL_lock objects available for re-use.
+
+ On some systems (e.g. Windows XP) constructing/destructing
+ MDL_lock objects can be fairly expensive. We use this cache
+ to avoid these costs in scenarios in which they can have
+ significant negative effect on performance. For example, when
+ there is only one thread constantly executing statements in
+ auto-commit mode and thus constantly causing creation/
+ destruction of MDL_lock objects for the tables it uses.
+
+ Note that this cache contains only MDL_object_lock objects.
+
+ Protected by m_mutex mutex.
+ */
+ typedef I_P_List<MDL_object_lock, MDL_object_lock_cache_adapter,
+ I_P_List_counter>
+ Lock_cache;
+ Lock_cache m_unused_locks_cache;
+ friend int mdl_iterate(int (*)(MDL_ticket *, void *), void *);
+};
+
+
+/**
+ Start-up parameter for the number of partitions of the MDL_lock hash.
+*/
+ulong mdl_locks_hash_partitions;
+
+/**
+ A collection of all MDL locks. A singleton,
+ there is only one instance of the map in the server.
+ Contains instances of MDL_map_partition
+*/
+
+class MDL_map
+{
+public:
+ void init();
+ void destroy();
+ MDL_lock *find_or_insert(const MDL_key *key);
+ unsigned long get_lock_owner(const MDL_key *key);
+ void remove(MDL_lock *lock);
+private:
+ /** Array of partitions where the locks are actually stored. */
+ Dynamic_array<MDL_map_partition *> m_partitions;
+ /** Pre-allocated MDL_lock object for GLOBAL namespace. */
+ MDL_lock *m_global_lock;
+ /** Pre-allocated MDL_lock object for COMMIT namespace. */
+ MDL_lock *m_commit_lock;
+ friend int mdl_iterate(int (*)(MDL_ticket *, void *), void *);
+};
+
+
+/**
+ A context of the recursive traversal through all contexts
+ in all sessions in search for deadlock.
+*/
+
+class Deadlock_detection_visitor: public MDL_wait_for_graph_visitor
+{
+public:
+ Deadlock_detection_visitor(MDL_context *start_node_arg)
+ : m_start_node(start_node_arg),
+ m_victim(NULL),
+ m_current_search_depth(0),
+ m_found_deadlock(FALSE)
+ {}
+ virtual bool enter_node(MDL_context *node);
+ virtual void leave_node(MDL_context *node);
+
+ virtual bool inspect_edge(MDL_context *dest);
+
+ MDL_context *get_victim() const { return m_victim; }
+private:
+ /**
+ Change the deadlock victim to a new one if it has lower deadlock
+ weight.
+ */
+ void opt_change_victim_to(MDL_context *new_victim);
+private:
+ /**
+ The context which has initiated the search. There
+ can be multiple searches happening in parallel at the same time.
+ */
+ MDL_context *m_start_node;
+ /** If a deadlock is found, the context that identifies the victim. */
+ MDL_context *m_victim;
+ /** Set to the 0 at start. Increased whenever
+ we descend into another MDL context (aka traverse to the next
+ wait-for graph node). When MAX_SEARCH_DEPTH is reached, we
+ assume that a deadlock is found, even if we have not found a
+ loop.
+ */
+ uint m_current_search_depth;
+ /** TRUE if we found a deadlock. */
+ bool m_found_deadlock;
+ /**
+ Maximum depth for deadlock searches. After this depth is
+ achieved we will unconditionally declare that there is a
+ deadlock.
+
+ @note This depth should be small enough to avoid stack
+ being exhausted by recursive search algorithm.
+
+ TODO: Find out what is the optimal value for this parameter.
+ Current value is safe, but probably sub-optimal,
+ as there is an anecdotal evidence that real-life
+ deadlocks are even shorter typically.
+ */
+ static const uint MAX_SEARCH_DEPTH= 32;
+};
+
+
+/**
+ Enter a node of a wait-for graph. After
+ a node is entered, inspect_edge() will be called
+ for all wait-for destinations of this node. Then
+ leave_node() will be called.
+ We call "enter_node()" for all nodes we inspect,
+ including the starting node.
+
+ @retval TRUE Maximum search depth exceeded.
+ @retval FALSE OK.
+*/
+
+bool Deadlock_detection_visitor::enter_node(MDL_context *node)
+{
+ m_found_deadlock= ++m_current_search_depth >= MAX_SEARCH_DEPTH;
+ if (m_found_deadlock)
+ {
+ DBUG_ASSERT(! m_victim);
+ opt_change_victim_to(node);
+ }
+ return m_found_deadlock;
+}
+
+
+/**
+ Done inspecting this node. Decrease the search
+ depth. If a deadlock is found, and we are
+ backtracking to the start node, optionally
+ change the deadlock victim to one with lower
+ deadlock weight.
+*/
+
+void Deadlock_detection_visitor::leave_node(MDL_context *node)
+{
+ --m_current_search_depth;
+ if (m_found_deadlock)
+ opt_change_victim_to(node);
+}
+
+
+/**
+ Inspect a wait-for graph edge from one MDL context to another.
+
+ @retval TRUE A loop is found.
+ @retval FALSE No loop is found.
+*/
+
+bool Deadlock_detection_visitor::inspect_edge(MDL_context *node)
+{
+ m_found_deadlock= node == m_start_node;
+ return m_found_deadlock;
+}
+
+
+/**
+ Change the deadlock victim to a new one if it has lower deadlock
+ weight.
+
+ @retval new_victim Victim is not changed.
+ @retval !new_victim New victim became the current.
+*/
+
+void
+Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim)
+{
+ if (m_victim == NULL ||
+ m_victim->get_deadlock_weight() >= new_victim->get_deadlock_weight())
+ {
+ /* Swap victims, unlock the old one. */
+ MDL_context *tmp= m_victim;
+ m_victim= new_victim;
+ m_victim->lock_deadlock_victim();
+ if (tmp)
+ tmp->unlock_deadlock_victim();
+ }
+}
+
+
+/**
+ Get a bit corresponding to enum_mdl_type value in a granted/waiting bitmaps
+ and compatibility matrices.
+*/
+
+#define MDL_BIT(A) static_cast<MDL_lock::bitmap_t>(1U << A)
+
+/**
+ The lock context. Created internally for an acquired lock.
+ For a given name, there exists only one MDL_lock instance,
+ and it exists only when the lock has been granted.
+ Can be seen as an MDL subsystem's version of TABLE_SHARE.
+
+ This is an abstract class which lacks information about
+ compatibility rules for lock types. They should be specified
+ in its descendants.
+*/
+
+class MDL_lock
+{
+public:
+ typedef unsigned short bitmap_t;
+
+ class Ticket_list
+ {
+ public:
+ typedef I_P_List<MDL_ticket,
+ I_P_List_adapter<MDL_ticket,
+ &MDL_ticket::next_in_lock,
+ &MDL_ticket::prev_in_lock>,
+ I_P_List_null_counter,
+ I_P_List_fast_push_back<MDL_ticket> >
+ List;
+ operator const List &() const { return m_list; }
+ Ticket_list() :m_bitmap(0) {}
+
+ void add_ticket(MDL_ticket *ticket);
+ void remove_ticket(MDL_ticket *ticket);
+ bool is_empty() const { return m_list.is_empty(); }
+ bitmap_t bitmap() const { return m_bitmap; }
+ private:
+ void clear_bit_if_not_in_list(enum_mdl_type type);
+ private:
+ /** List of tickets. */
+ List m_list;
+ /** Bitmap of types of tickets in this list. */
+ bitmap_t m_bitmap;
+ };
+
+ typedef Ticket_list::List::Iterator Ticket_iterator;
+
+public:
+ /** The key of the object (data) being protected. */
+ MDL_key key;
+ /**
+ Read-write lock protecting this lock context.
+
+ @note The fact that we use read-write lock prefers readers here is
+ important as deadlock detector won't work correctly otherwise.
+
+ For example, imagine that we have following waiters graph:
+
+ ctxA -> obj1 -> ctxB -> obj1 -|
+ ^ |
+ |----------------------------|
+
+ and both ctxA and ctxB start deadlock detection process:
+
+ ctxA read-locks obj1 ctxB read-locks obj2
+ ctxA goes deeper ctxB goes deeper
+
+ Now ctxC comes in who wants to start waiting on obj1, also
+ ctxD comes in who wants to start waiting on obj2.
+
+ ctxC tries to write-lock obj1 ctxD tries to write-lock obj2
+ ctxC is blocked ctxD is blocked
+
+ Now ctxA and ctxB resume their search:
+
+ ctxA tries to read-lock obj2 ctxB tries to read-lock obj1
+
+ If m_rwlock prefers writes (or fair) both ctxA and ctxB would be
+ blocked because of pending write locks from ctxD and ctxC
+ correspondingly. Thus we will get a deadlock in deadlock detector.
+ If m_wrlock prefers readers (actually ignoring pending writers is
+ enough) ctxA and ctxB will continue and no deadlock will occur.
+ */
+ mysql_prlock_t m_rwlock;
+
+ bool is_empty() const
+ {
+ return (m_granted.is_empty() && m_waiting.is_empty());
+ }
+
+ virtual const bitmap_t *incompatible_granted_types_bitmap() const = 0;
+ virtual const bitmap_t *incompatible_waiting_types_bitmap() const = 0;
+
+ bool has_pending_conflicting_lock(enum_mdl_type type);
+
+ bool can_grant_lock(enum_mdl_type type, MDL_context *requstor_ctx,
+ bool ignore_lock_priority) const;
+
+ inline static MDL_lock *create(const MDL_key *key,
+ MDL_map_partition *map_part);
+
+ inline unsigned long get_lock_owner() const;
+
+ void reschedule_waiters();
+
+ void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket);
+
+ bool visit_subgraph(MDL_ticket *waiting_ticket,
+ MDL_wait_for_graph_visitor *gvisitor);
+
+ virtual bool needs_notification(const MDL_ticket *ticket) const = 0;
+ virtual void notify_conflicting_locks(MDL_context *ctx) = 0;
+
+ virtual bitmap_t hog_lock_types_bitmap() const = 0;
+
+ /** List of granted tickets for this lock. */
+ Ticket_list m_granted;
+ /** Tickets for contexts waiting to acquire a lock. */
+ Ticket_list m_waiting;
+
+ /**
+ Number of times high priority lock requests have been granted while
+ low priority lock requests were waiting.
+ */
+ ulong m_hog_lock_count;
+
+public:
+
+ MDL_lock(const MDL_key *key_arg, MDL_map_partition *map_part)
+ : key(key_arg),
+ m_hog_lock_count(0),
+ m_ref_usage(0),
+ m_ref_release(0),
+ m_is_destroyed(FALSE),
+ m_version(0),
+ m_map_part(map_part)
+ {
+ mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock);
+ }
+
+ virtual ~MDL_lock()
+ {
+ mysql_prlock_destroy(&m_rwlock);
+ }
+ inline static void destroy(MDL_lock *lock);
+public:
+ /**
+ These three members are used to make it possible to separate
+ the MDL_map_partition::m_mutex mutex and MDL_lock::m_rwlock in
+ MDL_map::find_or_insert() for increased scalability.
+ The 'm_is_destroyed' member is only set by destroyers that
+ have both the MDL_map_partition::m_mutex and MDL_lock::m_rwlock, thus
+ holding any of the mutexes is sufficient to read it.
+ The 'm_ref_usage; is incremented under protection by
+ MDL_map_partition::m_mutex, but when 'm_is_destroyed' is set to TRUE, this
+ member is moved to be protected by the MDL_lock::m_rwlock.
+ This means that the MDL_map::find_or_insert() which only
+ holds the MDL_lock::m_rwlock can compare it to 'm_ref_release'
+ without acquiring MDL_map_partition::m_mutex again and if equal
+ it can also destroy the lock object safely.
+ The 'm_ref_release' is incremented under protection by
+ MDL_lock::m_rwlock.
+ Note since we are only interested in equality of these two
+ counters we don't have to worry about overflows as long as
+ their size is big enough to hold maximum number of concurrent
+ threads on the system.
+ */
+ uint m_ref_usage;
+ uint m_ref_release;
+ bool m_is_destroyed;
+ /**
+ We use the same idea and an additional version counter to support
+ caching of unused MDL_lock object for further re-use.
+ This counter is incremented while holding both MDL_map_partition::m_mutex
+ and MDL_lock::m_rwlock locks each time when a MDL_lock is moved from
+ the partitioned hash to the paritioned unused objects list (or destroyed).
+ A thread, which has found a MDL_lock object for the key in the hash
+ and then released the MDL_map_partition::m_mutex before acquiring the
+ MDL_lock::m_rwlock, can determine that this object was moved to the
+ unused objects list (or destroyed) while it held no locks by comparing
+ the version value which it read while holding the MDL_map_partition::m_mutex
+ with the value read after acquiring the MDL_lock::m_rwlock.
+ Note that since it takes several years to overflow this counter such
+ theoretically possible overflows should not have any practical effects.
+ */
+ ulonglong m_version;
+ /**
+ Partition of MDL_map where the lock is stored.
+ */
+ MDL_map_partition *m_map_part;
+};
+
+
+/**
+ An implementation of the scoped metadata lock. The only locking modes
+ which are supported at the moment are SHARED and INTENTION EXCLUSIVE
+ and EXCLUSIVE
+*/
+
+class MDL_scoped_lock : public MDL_lock
+{
+public:
+ MDL_scoped_lock(const MDL_key *key_arg, MDL_map_partition *map_part)
+ : MDL_lock(key_arg, map_part)
+ { }
+
+ virtual const bitmap_t *incompatible_granted_types_bitmap() const
+ {
+ return m_granted_incompatible;
+ }
+ virtual const bitmap_t *incompatible_waiting_types_bitmap() const
+ {
+ return m_waiting_incompatible;
+ }
+ virtual bool needs_notification(const MDL_ticket *ticket) const
+ {
+ return (ticket->get_type() == MDL_SHARED);
+ }
+ virtual void notify_conflicting_locks(MDL_context *ctx);
+
+ /*
+ In scoped locks, only IX lock request would starve because of X/S. But that
+ is practically very rare case. So just return 0 from this function.
+ */
+ virtual bitmap_t hog_lock_types_bitmap() const
+ {
+ return 0;
+ }
+
+private:
+ static const bitmap_t m_granted_incompatible[MDL_TYPE_END];
+ static const bitmap_t m_waiting_incompatible[MDL_TYPE_END];
+};
+
+
+/**
+ An implementation of a per-object lock. Supports SHARED, SHARED_UPGRADABLE,
+ SHARED HIGH PRIORITY and EXCLUSIVE locks.
+*/
+
+class MDL_object_lock : public MDL_lock
+{
+public:
+ MDL_object_lock(const MDL_key *key_arg, MDL_map_partition *map_part)
+ : MDL_lock(key_arg, map_part)
+ { }
+
+ /**
+ Reset unused MDL_object_lock object to represent the lock context for a
+ different object.
+ */
+ void reset(const MDL_key *new_key)
+ {
+ /* We need to change only object's key. */
+ key.mdl_key_init(new_key);
+ /* m_granted and m_waiting should be already in the empty/initial state. */
+ DBUG_ASSERT(is_empty());
+ /* Object should not be marked as destroyed. */
+ DBUG_ASSERT(! m_is_destroyed);
+ /*
+ Values of the rest of the fields should be preserved between old and
+ new versions of the object. E.g., m_version and m_ref_usage/release
+ should be kept intact to properly handle possible remaining references
+ to the old version of the object.
+ */
+ }
+
+ virtual const bitmap_t *incompatible_granted_types_bitmap() const
+ {
+ return m_granted_incompatible;
+ }
+ virtual const bitmap_t *incompatible_waiting_types_bitmap() const
+ {
+ return m_waiting_incompatible;
+ }
+ virtual bool needs_notification(const MDL_ticket *ticket) const
+ {
+ return (ticket->get_type() >= MDL_SHARED_NO_WRITE);
+ }
+ virtual void notify_conflicting_locks(MDL_context *ctx);
+
+ /*
+ To prevent starvation, these lock types that are only granted
+ max_write_lock_count times in a row while other lock types are
+ waiting.
+ */
+ virtual bitmap_t hog_lock_types_bitmap() const
+ {
+ return (MDL_BIT(MDL_SHARED_NO_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_EXCLUSIVE));
+ }
+
+private:
+ static const bitmap_t m_granted_incompatible[MDL_TYPE_END];
+ static const bitmap_t m_waiting_incompatible[MDL_TYPE_END];
+
+public:
+ /** Members for linking the object into the list of unused objects. */
+ MDL_object_lock *next_in_cache, **prev_in_cache;
+};
+
+
+/**
+ Helper class for linking MDL_object_lock objects into the unused objects list.
+*/
+class MDL_object_lock_cache_adapter :
+ public I_P_List_adapter<MDL_object_lock, &MDL_object_lock::next_in_cache,
+ &MDL_object_lock::prev_in_cache>
+{
+};
+
+
+static MDL_map mdl_locks;
+/**
+ Start-up parameter for the maximum size of the unused MDL_lock objects cache.
+*/
+ulong mdl_locks_cache_size;
+
+
+extern "C"
+{
+static uchar *
+mdl_locks_key(const uchar *record, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ MDL_lock *lock=(MDL_lock*) record;
+ *length= lock->key.length();
+ return (uchar*) lock->key.ptr();
+}
+} /* extern "C" */
+
+
+/**
+ Initialize the metadata locking subsystem.
+
+ This function is called at server startup.
+
+ In particular, initializes the new global mutex and
+ the associated condition variable: LOCK_mdl and COND_mdl.
+ These locking primitives are implementation details of the MDL
+ subsystem and are private to it.
+*/
+
+void mdl_init()
+{
+ DBUG_ASSERT(! mdl_initialized);
+ mdl_initialized= TRUE;
+
+#ifdef HAVE_PSI_INTERFACE
+ init_mdl_psi_keys();
+#endif
+
+ mdl_locks.init();
+}
+
+
+/**
+ Release resources of metadata locking subsystem.
+
+ Destroys the global mutex and the condition variable.
+ Called at server shutdown.
+*/
+
+void mdl_destroy()
+{
+ if (mdl_initialized)
+ {
+ mdl_initialized= FALSE;
+ mdl_locks.destroy();
+ }
+}
+
+
+static inline int mdl_iterate_lock(MDL_lock *lock,
+ int (*callback)(MDL_ticket *ticket, void *arg),
+ void *arg)
+{
+ MDL_lock::Ticket_iterator ticket_it(lock->m_granted);
+ MDL_ticket *ticket;
+ int res= 0;
+ mysql_prlock_rdlock(&lock->m_rwlock);
+ while ((ticket= ticket_it++) && !(res= callback(ticket, arg))) /* no-op */;
+ mysql_prlock_unlock(&lock->m_rwlock);
+ return res;
+}
+
+
+int mdl_iterate(int (*callback)(MDL_ticket *ticket, void *arg), void *arg)
+{
+ DYNAMIC_ARRAY locks;
+ uint i, j;
+ int res;
+ DBUG_ENTER("mdl_iterate");
+
+ if ((res= mdl_iterate_lock(mdl_locks.m_global_lock, callback, arg)) ||
+ (res= mdl_iterate_lock(mdl_locks.m_commit_lock, callback, arg)))
+ DBUG_RETURN(res);
+
+ my_init_dynamic_array(&locks, sizeof(MDL_lock*), 512, 1, MYF(0));
+
+ for (i= 0; i < mdl_locks.m_partitions.elements(); i++)
+ {
+ MDL_map_partition *part= mdl_locks.m_partitions.at(i);
+ /* Collect all locks first */
+ mysql_mutex_lock(&part->m_mutex);
+ if (allocate_dynamic(&locks, part->m_locks.records))
+ {
+ res= 1;
+ mysql_mutex_unlock(&part->m_mutex);
+ break;
+ }
+ reset_dynamic(&locks);
+ for (j= 0; j < part->m_locks.records; j++)
+ {
+ MDL_lock *lock= (MDL_lock*) my_hash_element(&part->m_locks, j);
+ lock->m_ref_usage++;
+ insert_dynamic(&locks, &lock);
+ }
+ mysql_mutex_unlock(&part->m_mutex);
+
+ /* Now show them */
+ for (j= 0; j < locks.elements; j++)
+ {
+ MDL_lock *lock= (MDL_lock*) *dynamic_element(&locks, j, MDL_lock**);
+ res= mdl_iterate_lock(lock, callback, arg);
+
+ mysql_prlock_wrlock(&lock->m_rwlock);
+ uint ref_usage= lock->m_ref_usage;
+ uint ref_release= ++lock->m_ref_release;
+ bool is_destroyed= lock->m_is_destroyed;
+ mysql_prlock_unlock(&lock->m_rwlock);
+
+ if (unlikely(is_destroyed && ref_usage == ref_release))
+ MDL_lock::destroy(lock);
+
+ if (res)
+ break;
+ }
+ }
+ delete_dynamic(&locks);
+ DBUG_RETURN(res);
+}
+
+
+/** Initialize the container for all MDL locks. */
+
+void MDL_map::init()
+{
+ MDL_key global_lock_key(MDL_key::GLOBAL, "", "");
+ MDL_key commit_lock_key(MDL_key::COMMIT, "", "");
+
+ m_global_lock= MDL_lock::create(&global_lock_key, NULL);
+ m_commit_lock= MDL_lock::create(&commit_lock_key, NULL);
+
+ for (uint i= 0; i < mdl_locks_hash_partitions; i++)
+ {
+ MDL_map_partition *part= new (std::nothrow) MDL_map_partition();
+ m_partitions.append(part);
+ }
+}
+
+
+my_hash_value_type mdl_hash_function(const CHARSET_INFO *cs,
+ const uchar *key, size_t length)
+{
+ MDL_key *mdl_key= (MDL_key*) (key - offsetof(MDL_key, m_ptr));
+ return mdl_key->hash_value();
+}
+
+
+/** Initialize the partition in the container with all MDL locks. */
+
+MDL_map_partition::MDL_map_partition()
+{
+ mysql_mutex_init(key_MDL_map_mutex, &m_mutex, NULL);
+ my_hash_init2(&m_locks, 0, &my_charset_bin, 16 /* FIXME */, 0, 0,
+ mdl_locks_key, mdl_hash_function, 0, 0);
+};
+
+
+/**
+ Destroy the container for all MDL locks.
+ @pre It must be empty.
+*/
+
+void MDL_map::destroy()
+{
+ MDL_lock::destroy(m_global_lock);
+ MDL_lock::destroy(m_commit_lock);
+
+ while (m_partitions.elements() > 0)
+ {
+ MDL_map_partition *part= m_partitions.pop();
+ delete part;
+ }
+}
+
+
+/**
+ Destroy the partition in container for all MDL locks.
+ @pre It must be empty.
+*/
+
+MDL_map_partition::~MDL_map_partition()
+{
+ DBUG_ASSERT(!m_locks.records);
+ mysql_mutex_destroy(&m_mutex);
+ my_hash_free(&m_locks);
+
+ MDL_object_lock *lock;
+ while ((lock= m_unused_locks_cache.pop_front()))
+ MDL_lock::destroy(lock);
+}
+
+
+/**
+ Find MDL_lock object corresponding to the key, create it
+ if it does not exist.
+
+ @retval non-NULL - Success. MDL_lock instance for the key with
+ locked MDL_lock::m_rwlock.
+ @retval NULL - Failure (OOM).
+*/
+
+MDL_lock* MDL_map::find_or_insert(const MDL_key *mdl_key)
+{
+ MDL_lock *lock;
+
+ if (mdl_key->mdl_namespace() == MDL_key::GLOBAL ||
+ mdl_key->mdl_namespace() == MDL_key::COMMIT)
+ {
+ /*
+ Avoid locking any m_mutex when lock for GLOBAL or COMMIT namespace is
+ requested. Return pointer to pre-allocated MDL_lock instance instead.
+ Such an optimization allows to save one mutex lock/unlock for any
+ statement changing data.
+
+ It works since these namespaces contain only one element so keys
+ for them look like '<namespace-id>\0\0'.
+ */
+ DBUG_ASSERT(mdl_key->length() == 3);
+
+ lock= (mdl_key->mdl_namespace() == MDL_key::GLOBAL) ? m_global_lock :
+ m_commit_lock;
+
+ mysql_prlock_wrlock(&lock->m_rwlock);
+
+ return lock;
+ }
+
+ uint part_id= mdl_key->hash_value() % mdl_locks_hash_partitions;
+ MDL_map_partition *part= m_partitions.at(part_id);
+
+ return part->find_or_insert(mdl_key);
+}
+
+
+/**
+ Find MDL_lock object corresponding to the key and hash value in
+ MDL_map partition, create it if it does not exist.
+
+ @retval non-NULL - Success. MDL_lock instance for the key with
+ locked MDL_lock::m_rwlock.
+ @retval NULL - Failure (OOM).
+*/
+
+MDL_lock* MDL_map_partition::find_or_insert(const MDL_key *mdl_key)
+{
+ MDL_lock *lock;
+
+retry:
+ mysql_mutex_lock(&m_mutex);
+ if (!(lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks,
+ mdl_key->hash_value(),
+ mdl_key->ptr(),
+ mdl_key->length())))
+ {
+ MDL_object_lock *unused_lock= NULL;
+
+ /*
+ No lock object found so we need to create a new one
+ or reuse an existing unused object.
+ */
+ if (mdl_key->mdl_namespace() != MDL_key::SCHEMA &&
+ m_unused_locks_cache.elements())
+ {
+ /*
+ We need a MDL_object_lock type of object and the unused objects
+ cache has some. Get the first object from the cache and set a new
+ key for it.
+ */
+ DBUG_ASSERT(mdl_key->mdl_namespace() != MDL_key::GLOBAL &&
+ mdl_key->mdl_namespace() != MDL_key::COMMIT);
+
+ unused_lock= m_unused_locks_cache.pop_front();
+ unused_lock->reset(mdl_key);
+
+ lock= unused_lock;
+ }
+ else
+ {
+ lock= MDL_lock::create(mdl_key, this);
+ }
+
+ if (!lock || my_hash_insert(&m_locks, (uchar*)lock))
+ {
+ if (unused_lock)
+ {
+ /*
+ Note that we can't easily destroy an object from cache here as it
+ still might be referenced by other threads. So we simply put it
+ back into the cache.
+ */
+ m_unused_locks_cache.push_front(unused_lock);
+ }
+ else
+ {
+ MDL_lock::destroy(lock);
+ }
+ mysql_mutex_unlock(&m_mutex);
+ return NULL;
+ }
+ }
+
+ if (move_from_hash_to_lock_mutex(lock))
+ goto retry;
+
+ return lock;
+}
+
+
+/**
+ Release MDL_map_partition::m_mutex mutex and lock MDL_lock::m_rwlock for lock
+ object from the hash. Handle situation when object was released
+ while we held no locks.
+
+ @retval FALSE - Success.
+ @retval TRUE - Object was released while we held no mutex, caller
+ should re-try looking up MDL_lock object in the hash.
+*/
+
+bool MDL_map_partition::move_from_hash_to_lock_mutex(MDL_lock *lock)
+{
+ ulonglong version;
+
+ DBUG_ASSERT(! lock->m_is_destroyed);
+ mysql_mutex_assert_owner(&m_mutex);
+
+ /*
+ We increment m_ref_usage which is a reference counter protected by
+ MDL_map_partition::m_mutex under the condition it is present in the hash
+ and m_is_destroyed is FALSE.
+ */
+ lock->m_ref_usage++;
+ /* Read value of the version counter under protection of m_mutex lock. */
+ version= lock->m_version;
+ mysql_mutex_unlock(&m_mutex);
+
+ mysql_prlock_wrlock(&lock->m_rwlock);
+ lock->m_ref_release++;
+
+ if (unlikely(lock->m_version != version))
+ {
+ /*
+ If the current value of version differs from one that was read while
+ we held m_mutex mutex, this MDL_lock object was moved to the unused
+ objects list or destroyed while we held no locks.
+ We should retry our search. But first we should destroy the MDL_lock
+ object if necessary.
+ */
+ if (unlikely(lock->m_is_destroyed))
+ {
+ /*
+ Object was released while we held no locks, we need to
+ release it if no others hold references to it, while our own
+ reference count ensured that the object as such haven't got
+ its memory released yet. We can also safely compare
+ m_ref_usage and m_ref_release since the object is no longer
+ present in the hash (or unused objects list) so no one will
+ be able to find it and increment m_ref_usage anymore.
+ */
+ uint ref_usage= lock->m_ref_usage;
+ uint ref_release= lock->m_ref_release;
+ mysql_prlock_unlock(&lock->m_rwlock);
+ if (ref_usage == ref_release)
+ MDL_lock::destroy(lock);
+ }
+ else
+ {
+ /*
+ Object was not destroyed but its version has changed.
+ This means that it was moved to the unused objects list
+ (and even might be already re-used). So now it might
+ correspond to a different key, therefore we should simply
+ retry our search.
+ */
+ mysql_prlock_unlock(&lock->m_rwlock);
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ * Return thread id of the owner of the lock, if it is owned.
+ */
+
+unsigned long
+MDL_map::get_lock_owner(const MDL_key *mdl_key)
+{
+ MDL_lock *lock;
+ unsigned long res= 0;
+
+ if (mdl_key->mdl_namespace() == MDL_key::GLOBAL ||
+ mdl_key->mdl_namespace() == MDL_key::COMMIT)
+ {
+ lock= (mdl_key->mdl_namespace() == MDL_key::GLOBAL) ? m_global_lock :
+ m_commit_lock;
+ mysql_prlock_rdlock(&lock->m_rwlock);
+ res= lock->get_lock_owner();
+ mysql_prlock_unlock(&lock->m_rwlock);
+ }
+ else
+ {
+ uint part_id= mdl_key->hash_value() % mdl_locks_hash_partitions;
+ MDL_map_partition *part= m_partitions.at(part_id);
+ res= part->get_lock_owner(mdl_key);
+ }
+ return res;
+}
+
+
+
+unsigned long
+MDL_map_partition::get_lock_owner(const MDL_key *mdl_key)
+{
+ MDL_lock *lock;
+ unsigned long res= 0;
+
+ mysql_mutex_lock(&m_mutex);
+ lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks,
+ mdl_key->hash_value(),
+ mdl_key->ptr(),
+ mdl_key->length());
+ if (lock)
+ res= lock->get_lock_owner();
+ mysql_mutex_unlock(&m_mutex);
+
+ return res;
+}
+
+
+/**
+ Destroy MDL_lock object or delegate this responsibility to
+ whatever thread that holds the last outstanding reference to
+ it.
+*/
+
+void MDL_map::remove(MDL_lock *lock)
+{
+ if (lock->key.mdl_namespace() == MDL_key::GLOBAL ||
+ lock->key.mdl_namespace() == MDL_key::COMMIT)
+ {
+ /*
+ Never destroy pre-allocated MDL_lock objects for GLOBAL and
+ COMMIT namespaces.
+ */
+ mysql_prlock_unlock(&lock->m_rwlock);
+ return;
+ }
+
+ lock->m_map_part->remove(lock);
+}
+
+
+/**
+ Destroy MDL_lock object belonging to specific MDL_map
+ partition or delegate this responsibility to whatever
+ thread that holds the last outstanding reference to it.
+*/
+
+void MDL_map_partition::remove(MDL_lock *lock)
+{
+ mysql_mutex_lock(&m_mutex);
+ my_hash_delete(&m_locks, (uchar*) lock);
+ /*
+ To let threads holding references to the MDL_lock object know that it was
+ moved to the list of unused objects or destroyed, we increment the version
+ counter under protection of both MDL_map_partition::m_mutex and
+ MDL_lock::m_rwlock locks. This allows us to read the version value while
+ having either one of those locks.
+ */
+ lock->m_version++;
+
+ if ((lock->key.mdl_namespace() != MDL_key::SCHEMA) &&
+ (m_unused_locks_cache.elements() <
+ mdl_locks_cache_size/mdl_locks_hash_partitions))
+ {
+ /*
+ This is an object of MDL_object_lock type and the cache of unused
+ objects has not reached its maximum size yet. So instead of destroying
+ object we move it to the list of unused objects to allow its later
+ re-use with possibly different key. Any threads holding references to
+ this object (owning MDL_map_partition::m_mutex or MDL_lock::m_rwlock)
+ will notice this thanks to the fact that we have changed the
+ MDL_lock::m_version counter.
+ */
+ DBUG_ASSERT(lock->key.mdl_namespace() != MDL_key::GLOBAL &&
+ lock->key.mdl_namespace() != MDL_key::COMMIT);
+
+ m_unused_locks_cache.push_front((MDL_object_lock*)lock);
+ mysql_mutex_unlock(&m_mutex);
+ mysql_prlock_unlock(&lock->m_rwlock);
+ }
+ else
+ {
+ /*
+ Destroy the MDL_lock object, but ensure that anyone that is
+ holding a reference to the object is not remaining, if so he
+ has the responsibility to release it.
+
+ Setting of m_is_destroyed to TRUE while holding _both_
+ MDL_map_partition::m_mutex and MDL_lock::m_rwlock mutexes transfers
+ the protection of m_ref_usage from MDL_map_partition::m_mutex to
+ MDL_lock::m_rwlock while removal of the object from the hash
+ (and cache of unused objects) makes it read-only. Therefore
+ whoever acquires MDL_lock::m_rwlock next will see the most up
+ to date version of m_ref_usage.
+
+ This means that when m_is_destroyed is TRUE and we hold the
+ MDL_lock::m_rwlock we can safely read the m_ref_usage
+ member.
+ */
+ uint ref_usage, ref_release;
+
+ lock->m_is_destroyed= TRUE;
+ ref_usage= lock->m_ref_usage;
+ ref_release= lock->m_ref_release;
+ mysql_mutex_unlock(&m_mutex);
+ mysql_prlock_unlock(&lock->m_rwlock);
+ if (ref_usage == ref_release)
+ MDL_lock::destroy(lock);
+ }
+}
+
+
+/**
+ Initialize a metadata locking context.
+
+ This is to be called when a new server connection is created.
+*/
+
+MDL_context::MDL_context()
+ :
+ m_owner(NULL),
+ m_needs_thr_lock_abort(FALSE),
+ m_waiting_for(NULL)
+{
+ mysql_prlock_init(key_MDL_context_LOCK_waiting_for, &m_LOCK_waiting_for);
+}
+
+
+/**
+ Destroy metadata locking context.
+
+ Assumes and asserts that there are no active or pending locks
+ associated with this context at the time of the destruction.
+
+ Currently does nothing. Asserts that there are no pending
+ or satisfied lock requests. The pending locks must be released
+ prior to destruction. This is a new way to express the assertion
+ that all tables are closed before a connection is destroyed.
+*/
+
+void MDL_context::destroy()
+{
+ DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty());
+ DBUG_ASSERT(m_tickets[MDL_TRANSACTION].is_empty());
+ DBUG_ASSERT(m_tickets[MDL_EXPLICIT].is_empty());
+
+ mysql_prlock_destroy(&m_LOCK_waiting_for);
+}
+
+
+/**
+ Initialize a lock request.
+
+ This is to be used for every lock request.
+
+ Note that initialization and allocation are split into two
+ calls. This is to allow flexible memory management of lock
+ requests. Normally a lock request is stored in statement memory
+ (e.g. is a member of struct TABLE_LIST), but we would also like
+ to allow allocation of lock requests in other memory roots,
+ for example in the grant subsystem, to lock privilege tables.
+
+ The MDL subsystem does not own or manage memory of lock requests.
+
+ @param mdl_namespace Id of namespace of object to be locked
+ @param db Name of database to which the object belongs
+ @param name Name of of the object
+ @param mdl_type The MDL lock type for the request.
+*/
+
+void MDL_request::init(MDL_key::enum_mdl_namespace mdl_namespace,
+ const char *db_arg,
+ const char *name_arg,
+ enum_mdl_type mdl_type_arg,
+ enum_mdl_duration mdl_duration_arg)
+{
+ key.mdl_key_init(mdl_namespace, db_arg, name_arg);
+ type= mdl_type_arg;
+ duration= mdl_duration_arg;
+ ticket= NULL;
+}
+
+
+/**
+ Initialize a lock request using pre-built MDL_key.
+
+ @sa MDL_request::init(namespace, db, name, type).
+
+ @param key_arg The pre-built MDL key for the request.
+ @param mdl_type_arg The MDL lock type for the request.
+*/
+
+void MDL_request::init(const MDL_key *key_arg,
+ enum_mdl_type mdl_type_arg,
+ enum_mdl_duration mdl_duration_arg)
+{
+ key.mdl_key_init(key_arg);
+ type= mdl_type_arg;
+ duration= mdl_duration_arg;
+ ticket= NULL;
+}
+
+
+/**
+ Auxiliary functions needed for creation/destruction of MDL_lock objects.
+
+ @note Also chooses an MDL_lock descendant appropriate for object namespace.
+*/
+
+inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key,
+ MDL_map_partition *map_part)
+{
+ switch (mdl_key->mdl_namespace())
+ {
+ case MDL_key::GLOBAL:
+ case MDL_key::SCHEMA:
+ case MDL_key::COMMIT:
+ return new (std::nothrow) MDL_scoped_lock(mdl_key, map_part);
+ default:
+ return new (std::nothrow) MDL_object_lock(mdl_key, map_part);
+ }
+}
+
+
+void MDL_lock::destroy(MDL_lock *lock)
+{
+ delete lock;
+}
+
+
+/**
+ Auxiliary functions needed for creation/destruction of MDL_ticket
+ objects.
+
+ @todo This naive implementation should be replaced with one that saves
+ on memory allocation by reusing released objects.
+*/
+
+MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg
+#ifndef DBUG_OFF
+ , enum_mdl_duration duration_arg
+#endif
+ )
+{
+ return new (std::nothrow)
+ MDL_ticket(ctx_arg, type_arg
+#ifndef DBUG_OFF
+ , duration_arg
+#endif
+ );
+}
+
+
+void MDL_ticket::destroy(MDL_ticket *ticket)
+{
+ delete ticket;
+}
+
+
+/**
+ Return the 'weight' of this ticket for the
+ victim selection algorithm. Requests with
+ lower weight are preferred to requests
+ with higher weight when choosing a victim.
+*/
+
+uint MDL_ticket::get_deadlock_weight() const
+{
+ return (m_lock->key.mdl_namespace() == MDL_key::GLOBAL ||
+ m_type >= MDL_SHARED_UPGRADABLE ?
+ DEADLOCK_WEIGHT_DDL : DEADLOCK_WEIGHT_DML);
+}
+
+
+/** Construct an empty wait slot. */
+
+MDL_wait::MDL_wait()
+ :m_wait_status(EMPTY)
+{
+ mysql_mutex_init(key_MDL_wait_LOCK_wait_status, &m_LOCK_wait_status, NULL);
+ mysql_cond_init(key_MDL_wait_COND_wait_status, &m_COND_wait_status, NULL);
+}
+
+
+/** Destroy system resources. */
+
+MDL_wait::~MDL_wait()
+{
+ mysql_mutex_destroy(&m_LOCK_wait_status);
+ mysql_cond_destroy(&m_COND_wait_status);
+}
+
+
+/**
+ Set the status unless it's already set. Return FALSE if set,
+ TRUE otherwise.
+*/
+
+bool MDL_wait::set_status(enum_wait_status status_arg)
+{
+ bool was_occupied= TRUE;
+ mysql_mutex_lock(&m_LOCK_wait_status);
+ if (m_wait_status == EMPTY)
+ {
+ was_occupied= FALSE;
+ m_wait_status= status_arg;
+ mysql_cond_signal(&m_COND_wait_status);
+ }
+ mysql_mutex_unlock(&m_LOCK_wait_status);
+ return was_occupied;
+}
+
+
+/** Query the current value of the wait slot. */
+
+MDL_wait::enum_wait_status MDL_wait::get_status()
+{
+ enum_wait_status result;
+ mysql_mutex_lock(&m_LOCK_wait_status);
+ result= m_wait_status;
+ mysql_mutex_unlock(&m_LOCK_wait_status);
+ return result;
+}
+
+
+/** Clear the current value of the wait slot. */
+
+void MDL_wait::reset_status()
+{
+ mysql_mutex_lock(&m_LOCK_wait_status);
+ m_wait_status= EMPTY;
+ mysql_mutex_unlock(&m_LOCK_wait_status);
+}
+
+
+/**
+ Wait for the status to be assigned to this wait slot.
+
+ @param owner MDL context owner.
+ @param abs_timeout Absolute time after which waiting should stop.
+ @param set_status_on_timeout TRUE - If in case of timeout waiting
+ context should close the wait slot by
+ sending TIMEOUT to itself.
+ FALSE - Otherwise.
+ @param wait_state_name Thread state name to be set for duration of wait.
+
+ @returns Signal posted.
+*/
+
+MDL_wait::enum_wait_status
+MDL_wait::timed_wait(MDL_context_owner *owner, struct timespec *abs_timeout,
+ bool set_status_on_timeout,
+ const PSI_stage_info *wait_state_name)
+{
+ PSI_stage_info old_stage;
+ enum_wait_status result;
+ int wait_result= 0;
+ DBUG_ENTER("MDL_wait::timed_wait");
+
+ mysql_mutex_lock(&m_LOCK_wait_status);
+
+ owner->ENTER_COND(&m_COND_wait_status, &m_LOCK_wait_status,
+ wait_state_name, & old_stage);
+ thd_wait_begin(NULL, THD_WAIT_META_DATA_LOCK);
+ while (!m_wait_status && !owner->is_killed() &&
+ wait_result != ETIMEDOUT && wait_result != ETIME)
+ {
+ wait_result= mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status,
+ abs_timeout);
+ }
+ thd_wait_end(NULL);
+
+ if (m_wait_status == EMPTY)
+ {
+ /*
+ Wait has ended not due to a status being set from another
+ thread but due to this connection/statement being killed or a
+ time out.
+ To avoid races, which may occur if another thread sets
+ GRANTED status before the code which calls this method
+ processes the abort/timeout, we assign the status under
+ protection of the m_LOCK_wait_status, within the critical
+ section. An exception is when set_status_on_timeout is
+ false, which means that the caller intends to restart the
+ wait.
+ */
+ if (owner->is_killed())
+ m_wait_status= KILLED;
+ else if (set_status_on_timeout)
+ m_wait_status= TIMEOUT;
+ }
+ result= m_wait_status;
+
+ owner->EXIT_COND(& old_stage);
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Clear bit corresponding to the type of metadata lock in bitmap representing
+ set of such types if list of tickets does not contain ticket with such type.
+
+ @param[in,out] bitmap Bitmap representing set of types of locks.
+ @param[in] list List to inspect.
+ @param[in] type Type of metadata lock to look up in the list.
+*/
+
+void MDL_lock::Ticket_list::clear_bit_if_not_in_list(enum_mdl_type type)
+{
+ MDL_lock::Ticket_iterator it(m_list);
+ const MDL_ticket *ticket;
+
+ while ((ticket= it++))
+ if (ticket->get_type() == type)
+ return;
+ m_bitmap&= ~ MDL_BIT(type);
+}
+
+
+/**
+ Add ticket to MDL_lock's list of waiting requests and
+ update corresponding bitmap of lock types.
+*/
+
+void MDL_lock::Ticket_list::add_ticket(MDL_ticket *ticket)
+{
+ /*
+ Ticket being added to the list must have MDL_ticket::m_lock set,
+ since for such tickets methods accessing this member might be
+ called by other threads.
+ */
+ DBUG_ASSERT(ticket->get_lock());
+ /*
+ Add ticket to the *back* of the queue to ensure fairness
+ among requests with the same priority.
+ */
+ m_list.push_back(ticket);
+ m_bitmap|= MDL_BIT(ticket->get_type());
+}
+
+
+/**
+ Remove ticket from MDL_lock's list of requests and
+ update corresponding bitmap of lock types.
+*/
+
+void MDL_lock::Ticket_list::remove_ticket(MDL_ticket *ticket)
+{
+ m_list.remove(ticket);
+ /*
+ Check if waiting queue has another ticket with the same type as
+ one which was removed. If there is no such ticket, i.e. we have
+ removed last ticket of particular type, then we need to update
+ bitmap of waiting ticket's types.
+ Note that in most common case, i.e. when shared lock is removed
+ from waiting queue, we are likely to find ticket of the same
+ type early without performing full iteration through the list.
+ So this method should not be too expensive.
+ */
+ clear_bit_if_not_in_list(ticket->get_type());
+}
+
+
+/**
+ Determine waiting contexts which requests for the lock can be
+ satisfied, grant lock to them and wake them up.
+
+ @note Together with MDL_lock::add_ticket() this method implements
+ fair scheduling among requests with the same priority.
+ It tries to grant lock from the head of waiters list, while
+ add_ticket() adds new requests to the back of this list.
+
+*/
+
+void MDL_lock::reschedule_waiters()
+{
+ MDL_lock::Ticket_iterator it(m_waiting);
+ MDL_ticket *ticket;
+ bool skip_high_priority= false;
+ bitmap_t hog_lock_types= hog_lock_types_bitmap();
+
+ if (m_hog_lock_count >= max_write_lock_count)
+ {
+ /*
+ If number of successively granted high-prio, strong locks has exceeded
+ max_write_lock_count give a way to low-prio, weak locks to avoid their
+ starvation.
+ */
+
+ if ((m_waiting.bitmap() & ~hog_lock_types) != 0)
+ {
+ /*
+ Even though normally when m_hog_lock_count is non-0 there is
+ some pending low-prio lock, we still can encounter situation
+ when m_hog_lock_count is non-0 and there are no pending low-prio
+ locks. This, for example, can happen when a ticket for pending
+ low-prio lock was removed from waiters list due to timeout,
+ and reschedule_waiters() is called after that to update the
+ waiters queue. m_hog_lock_count will be reset to 0 at the
+ end of this call in such case.
+
+ Note that it is not an issue if we fail to wake up any pending
+ waiters for weak locks in the loop below. This would mean that
+ all of them are either killed, timed out or chosen as a victim
+ by deadlock resolver, but have not managed to remove ticket
+ from the waiters list yet. After tickets will be removed from
+ the waiters queue there will be another call to
+ reschedule_waiters() with pending bitmap updated to reflect new
+ state of waiters queue.
+ */
+ skip_high_priority= true;
+ }
+ }
+
+ /*
+ Find the first (and hence the oldest) waiting request which
+ can be satisfied (taking into account priority). Grant lock to it.
+ Repeat the process for the remainder of waiters.
+ Note we don't need to re-start iteration from the head of the
+ list after satisfying the first suitable request as in our case
+ all compatible types of requests have the same priority.
+
+ TODO/FIXME: We should:
+ - Either switch to scheduling without priorities
+ which will allow to stop iteration through the
+ list of waiters once we found the first ticket
+ which can't be satisfied
+ - Or implement some check using bitmaps which will
+ allow to stop iteration in cases when, e.g., we
+ grant SNRW lock and there are no pending S or
+ SH locks.
+ */
+ while ((ticket= it++))
+ {
+ /*
+ Skip high-prio, strong locks if earlier we have decided to give way to
+ low-prio, weaker locks.
+ */
+ if (skip_high_priority &&
+ ((MDL_BIT(ticket->get_type()) & hog_lock_types) != 0))
+ continue;
+
+ if (can_grant_lock(ticket->get_type(), ticket->get_ctx(),
+ skip_high_priority))
+ {
+ if (! ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED))
+ {
+ /*
+ Satisfy the found request by updating lock structures.
+ It is OK to do so even after waking up the waiter since any
+ session which tries to get any information about the state of
+ this lock has to acquire MDL_lock::m_rwlock first and thus,
+ when manages to do so, already sees an updated state of the
+ MDL_lock object.
+ */
+ m_waiting.remove_ticket(ticket);
+ m_granted.add_ticket(ticket);
+
+ /*
+ Increase counter of successively granted high-priority strong locks,
+ if we have granted one.
+ */
+ if ((MDL_BIT(ticket->get_type()) & hog_lock_types) != 0)
+ m_hog_lock_count++;
+ }
+ /*
+ If we could not update the wait slot of the waiter,
+ it can be due to fact that its connection/statement was
+ killed or it has timed out (i.e. the slot is not empty).
+ Since in all such cases the waiter assumes that the lock was
+ not been granted, we should keep the request in the waiting
+ queue and look for another request to reschedule.
+ */
+ }
+ }
+
+ if ((m_waiting.bitmap() & ~hog_lock_types) == 0)
+ {
+ /*
+ Reset number of successively granted high-prio, strong locks
+ if there are no pending low-prio, weak locks.
+ This ensures:
+ - That m_hog_lock_count is correctly reset after strong lock
+ is released and weak locks are granted (or there are no
+ other lock requests).
+ - That situation when SNW lock is granted along with some SR
+ locks, but SW locks are still blocked are handled correctly.
+ - That m_hog_lock_count is zero in most cases when there are no pending
+ weak locks (see comment at the start of this method for example of
+ exception). This allows to save on checks at the start of this method.
+ */
+ m_hog_lock_count= 0;
+ }
+}
+
+
+/**
+ Compatibility (or rather "incompatibility") matrices for scoped metadata
+ lock. Arrays of bitmaps which elements specify which granted/waiting locks
+ are incompatible with type of lock being requested.
+
+ The first array specifies if particular type of request can be satisfied
+ if there is granted scoped lock of certain type.
+
+ | Type of active |
+ Request | scoped lock |
+ type | IS(*) IX S X |
+ ---------+------------------+
+ IS | + + + + |
+ IX | + + - - |
+ S | + - + - |
+ X | + - - - |
+
+ The second array specifies if particular type of request can be satisfied
+ if there is already waiting request for the scoped lock of certain type.
+ I.e. it specifies what is the priority of different lock types.
+
+ | Pending |
+ Request | scoped lock |
+ type | IS(*) IX S X |
+ ---------+-----------------+
+ IS | + + + + |
+ IX | + + - - |
+ S | + + + - |
+ X | + + + + |
+
+ Here: "+" -- means that request can be satisfied
+ "-" -- means that request can't be satisfied and should wait
+
+ (*) Since intention shared scoped locks are compatible with all other
+ type of locks we don't even have any accounting for them.
+
+ Note that relation between scoped locks and objects locks requested
+ by statement is not straightforward and is therefore fully defined
+ by SQL-layer.
+ For example, in order to support global read lock implementation
+ SQL-layer acquires IX lock in GLOBAL namespace for each statement
+ that can modify metadata or data (i.e. for each statement that
+ needs SW, SU, SNW, SNRW or X object locks). OTOH, to ensure that
+ DROP DATABASE works correctly with concurrent DDL, IX metadata locks
+ in SCHEMA namespace are acquired for DDL statements which can update
+ metadata in the schema (i.e. which acquire SU, SNW, SNRW and X locks
+ on schema objects) and aren't acquired for DML.
+*/
+
+const MDL_lock::bitmap_t MDL_scoped_lock::m_granted_incompatible[MDL_TYPE_END] =
+{
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_INTENTION_EXCLUSIVE), 0, 0, 0, 0, 0, 0,
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED) | MDL_BIT(MDL_INTENTION_EXCLUSIVE)
+};
+
+const MDL_lock::bitmap_t MDL_scoped_lock::m_waiting_incompatible[MDL_TYPE_END] =
+{
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED),
+ MDL_BIT(MDL_EXCLUSIVE), 0, 0, 0, 0, 0, 0, 0
+};
+
+
+/**
+ Compatibility (or rather "incompatibility") matrices for per-object
+ metadata lock. Arrays of bitmaps which elements specify which granted/
+ waiting locks are incompatible with type of lock being requested.
+
+ The first array specifies if particular type of request can be satisfied
+ if there is granted lock of certain type.
+
+ Request | Granted requests for lock |
+ type | S SH SR SW SU SNW SNRW X |
+ ----------+----------------------------------+
+ S | + + + + + + + - |
+ SH | + + + + + + + - |
+ SR | + + + + + + - - |
+ SW | + + + + + - - - |
+ SU | + + + + - - - - |
+ SNW | + + + - - - - - |
+ SNRW | + + - - - - - - |
+ X | - - - - - - - - |
+ SU -> X | - - - - 0 0 0 0 |
+ SNW -> X | - - - 0 0 0 0 0 |
+ SNRW -> X | - - 0 0 0 0 0 0 |
+
+ The second array specifies if particular type of request can be satisfied
+ if there is waiting request for the same lock of certain type. In other
+ words it specifies what is the priority of different lock types.
+
+ Request | Pending requests for lock |
+ type | S SH SR SW SU SNW SNRW X |
+ ----------+---------------------------------+
+ S | + + + + + + + - |
+ SH | + + + + + + + + |
+ SR | + + + + + + - - |
+ SW | + + + + + - - - |
+ SU | + + + + + + + - |
+ SNW | + + + + + + + - |
+ SNRW | + + + + + + + - |
+ X | + + + + + + + + |
+ SU -> X | + + + + + + + + |
+ SNW -> X | + + + + + + + + |
+ SNRW -> X | + + + + + + + + |
+
+ Here: "+" -- means that request can be satisfied
+ "-" -- means that request can't be satisfied and should wait
+ "0" -- means impossible situation which will trigger assert
+
+ @note In cases then current context already has "stronger" type
+ of lock on the object it will be automatically granted
+ thanks to usage of the MDL_context::find_ticket() method.
+
+ @note IX locks are excluded since they are not used for per-object
+ metadata locks.
+*/
+
+const MDL_lock::bitmap_t
+MDL_object_lock::m_granted_incompatible[MDL_TYPE_END] =
+{
+ 0,
+ MDL_BIT(MDL_EXCLUSIVE),
+ MDL_BIT(MDL_EXCLUSIVE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE) |
+ MDL_BIT(MDL_SHARED_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE) |
+ MDL_BIT(MDL_SHARED_WRITE) | MDL_BIT(MDL_SHARED_READ),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE) |
+ MDL_BIT(MDL_SHARED_WRITE) | MDL_BIT(MDL_SHARED_READ) |
+ MDL_BIT(MDL_SHARED_HIGH_PRIO) | MDL_BIT(MDL_SHARED)
+};
+
+
+const MDL_lock::bitmap_t
+MDL_object_lock::m_waiting_incompatible[MDL_TYPE_END] =
+{
+ 0,
+ MDL_BIT(MDL_EXCLUSIVE),
+ 0,
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE),
+ MDL_BIT(MDL_EXCLUSIVE),
+ MDL_BIT(MDL_EXCLUSIVE),
+ 0
+};
+
+
+/**
+ Check if request for the metadata lock can be satisfied given its
+ current state.
+
+ @param type_arg The requested lock type.
+ @param requestor_ctx The MDL context of the requestor.
+ @param ignore_lock_priority Ignore lock priority.
+
+ @retval TRUE Lock request can be satisfied
+ @retval FALSE There is some conflicting lock.
+
+ @note In cases then current context already has "stronger" type
+ of lock on the object it will be automatically granted
+ thanks to usage of the MDL_context::find_ticket() method.
+*/
+
+bool
+MDL_lock::can_grant_lock(enum_mdl_type type_arg,
+ MDL_context *requestor_ctx,
+ bool ignore_lock_priority) const
+{
+ bool can_grant= FALSE;
+ bitmap_t waiting_incompat_map= incompatible_waiting_types_bitmap()[type_arg];
+ bitmap_t granted_incompat_map= incompatible_granted_types_bitmap()[type_arg];
+
+ /*
+ New lock request can be satisfied iff:
+ - There are no incompatible types of satisfied requests
+ in other contexts
+ - There are no waiting requests which have higher priority
+ than this request when priority was not ignored.
+ */
+ if (ignore_lock_priority || !(m_waiting.bitmap() & waiting_incompat_map))
+ {
+ if (! (m_granted.bitmap() & granted_incompat_map))
+ can_grant= TRUE;
+ else
+ {
+ Ticket_iterator it(m_granted);
+ MDL_ticket *ticket;
+
+ /* Check that the incompatible lock belongs to some other context. */
+ while ((ticket= it++))
+ {
+ if (ticket->get_ctx() != requestor_ctx &&
+ ticket->is_incompatible_when_granted(type_arg))
+ break;
+ }
+ if (ticket == NULL) /* Incompatible locks are our own. */
+ can_grant= TRUE;
+ }
+ }
+ return can_grant;
+}
+
+
+/**
+ Return thread id of the thread to which the first ticket was
+ granted.
+*/
+
+inline unsigned long
+MDL_lock::get_lock_owner() const
+{
+ Ticket_iterator it(m_granted);
+ MDL_ticket *ticket;
+
+ if ((ticket= it++))
+ return ticket->get_ctx()->get_thread_id();
+ return 0;
+}
+
+
+/** Remove a ticket from waiting or pending queue and wakeup up waiters. */
+
+void MDL_lock::remove_ticket(Ticket_list MDL_lock::*list, MDL_ticket *ticket)
+{
+ mysql_prlock_wrlock(&m_rwlock);
+ (this->*list).remove_ticket(ticket);
+ if (is_empty())
+ mdl_locks.remove(this);
+ else
+ {
+ /*
+ There can be some contexts waiting to acquire a lock
+ which now might be able to do it. Grant the lock to
+ them and wake them up!
+
+ We always try to reschedule locks, since there is no easy way
+ (i.e. by looking at the bitmaps) to find out whether it is
+ required or not.
+ In a general case, even when the queue's bitmap is not changed
+ after removal of the ticket, there is a chance that some request
+ can be satisfied (due to the fact that a granted request
+ reflected in the bitmap might belong to the same context as a
+ pending request).
+ */
+ reschedule_waiters();
+ mysql_prlock_unlock(&m_rwlock);
+ }
+}
+
+
+/**
+ Check if we have any pending locks which conflict with existing
+ shared lock.
+
+ @pre The ticket must match an acquired lock.
+
+ @return TRUE if there is a conflicting lock request, FALSE otherwise.
+*/
+
+bool MDL_lock::has_pending_conflicting_lock(enum_mdl_type type)
+{
+ bool result;
+
+ mysql_prlock_rdlock(&m_rwlock);
+ result= (m_waiting.bitmap() & incompatible_granted_types_bitmap()[type]);
+ mysql_prlock_unlock(&m_rwlock);
+ return result;
+}
+
+
+MDL_wait_for_graph_visitor::~MDL_wait_for_graph_visitor()
+{
+}
+
+
+MDL_wait_for_subgraph::~MDL_wait_for_subgraph()
+{
+}
+
+/**
+ Check if ticket represents metadata lock of "stronger" or equal type
+ than specified one. I.e. if metadata lock represented by ticket won't
+ allow any of locks which are not allowed by specified type of lock.
+
+ @return TRUE if ticket has stronger or equal type
+ FALSE otherwise.
+*/
+
+bool MDL_ticket::has_stronger_or_equal_type(enum_mdl_type type) const
+{
+ const MDL_lock::bitmap_t *
+ granted_incompat_map= m_lock->incompatible_granted_types_bitmap();
+
+ return ! (granted_incompat_map[type] & ~(granted_incompat_map[m_type]));
+}
+
+
+bool MDL_ticket::is_incompatible_when_granted(enum_mdl_type type) const
+{
+ return (MDL_BIT(m_type) &
+ m_lock->incompatible_granted_types_bitmap()[type]);
+}
+
+
+bool MDL_ticket::is_incompatible_when_waiting(enum_mdl_type type) const
+{
+ return (MDL_BIT(m_type) &
+ m_lock->incompatible_waiting_types_bitmap()[type]);
+}
+
+
+/**
+ Check whether the context already holds a compatible lock ticket
+ on an object.
+ Start searching from list of locks for the same duration as lock
+ being requested. If not look at lists for other durations.
+
+ @param mdl_request Lock request object for lock to be acquired
+ @param[out] result_duration Duration of lock which was found.
+
+ @note Tickets which correspond to lock types "stronger" than one
+ being requested are also considered compatible.
+
+ @return A pointer to the lock ticket for the object or NULL otherwise.
+*/
+
+MDL_ticket *
+MDL_context::find_ticket(MDL_request *mdl_request,
+ enum_mdl_duration *result_duration)
+{
+ MDL_ticket *ticket;
+ int i;
+
+ for (i= 0; i < MDL_DURATION_END; i++)
+ {
+ enum_mdl_duration duration= (enum_mdl_duration)((mdl_request->duration+i) %
+ MDL_DURATION_END);
+ Ticket_iterator it(m_tickets[duration]);
+
+ while ((ticket= it++))
+ {
+ if (mdl_request->key.is_equal(&ticket->m_lock->key) &&
+ ticket->has_stronger_or_equal_type(mdl_request->type))
+ {
+ DBUG_PRINT("info", ("Adding mdl lock %d to %d",
+ mdl_request->type, ticket->m_type));
+ *result_duration= duration;
+ return ticket;
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ Try to acquire one lock.
+
+ Unlike exclusive locks, shared locks are acquired one by
+ one. This is interface is chosen to simplify introduction of
+ the new locking API to the system. MDL_context::try_acquire_lock()
+ is currently used from open_table(), and there we have only one
+ table to work with.
+
+ This function may also be used to try to acquire an exclusive
+ lock on a destination table, by ALTER TABLE ... RENAME.
+
+ Returns immediately without any side effect if encounters a lock
+ conflict. Otherwise takes the lock.
+
+ FIXME: Compared to lock_table_name_if_not_cached() (from 5.1)
+ it gives slightly more false negatives.
+
+ @param mdl_request [in/out] Lock request object for lock to be acquired
+
+ @retval FALSE Success. The lock may have not been acquired.
+ Check the ticket, if it's NULL, a conflicting lock
+ exists.
+ @retval TRUE Out of resources, an error has been reported.
+*/
+
+bool
+MDL_context::try_acquire_lock(MDL_request *mdl_request)
+{
+ MDL_ticket *ticket;
+
+ if (try_acquire_lock_impl(mdl_request, &ticket))
+ return TRUE;
+
+ if (! mdl_request->ticket)
+ {
+ /*
+ Our attempt to acquire lock without waiting has failed.
+ Let us release resources which were acquired in the process.
+ We can't get here if we allocated a new lock object so there
+ is no need to release it.
+ */
+ DBUG_ASSERT(! ticket->m_lock->is_empty());
+ mysql_prlock_unlock(&ticket->m_lock->m_rwlock);
+ MDL_ticket::destroy(ticket);
+ }
+
+ return FALSE;
+}
+
+
+/**
+ Auxiliary method for acquiring lock without waiting.
+
+ @param mdl_request [in/out] Lock request object for lock to be acquired
+ @param out_ticket [out] Ticket for the request in case when lock
+ has not been acquired.
+
+ @retval FALSE Success. The lock may have not been acquired.
+ Check MDL_request::ticket, if it's NULL, a conflicting
+ lock exists. In this case "out_ticket" out parameter
+ points to ticket which was constructed for the request.
+ MDL_ticket::m_lock points to the corresponding MDL_lock
+ object and MDL_lock::m_rwlock write-locked.
+ @retval TRUE Out of resources, an error has been reported.
+*/
+
+bool
+MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
+ MDL_ticket **out_ticket)
+{
+ MDL_lock *lock;
+ MDL_key *key= &mdl_request->key;
+ MDL_ticket *ticket;
+ enum_mdl_duration found_duration;
+
+ DBUG_ASSERT(mdl_request->type != MDL_EXCLUSIVE ||
+ is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
+ DBUG_ASSERT(mdl_request->ticket == NULL);
+
+ /* Don't take chances in production. */
+ mdl_request->ticket= NULL;
+
+ /*
+ Check whether the context already holds a shared lock on the object,
+ and if so, grant the request.
+ */
+ if ((ticket= find_ticket(mdl_request, &found_duration)))
+ {
+ DBUG_ASSERT(ticket->m_lock);
+ DBUG_ASSERT(ticket->has_stronger_or_equal_type(mdl_request->type));
+ /*
+ If the request is for a transactional lock, and we found
+ a transactional lock, just reuse the found ticket.
+
+ It's possible that we found a transactional lock,
+ but the request is for a HANDLER lock. In that case HANDLER
+ code will clone the ticket (see below why it's needed).
+
+ If the request is for a transactional lock, and we found
+ a HANDLER lock, create a copy, to make sure that when user
+ does HANDLER CLOSE, the transactional lock is not released.
+
+ If the request is for a handler lock, and we found a
+ HANDLER lock, also do the clone. HANDLER CLOSE for one alias
+ should not release the lock on the table HANDLER opened through
+ a different alias.
+ */
+ mdl_request->ticket= ticket;
+ if ((found_duration != mdl_request->duration ||
+ mdl_request->duration == MDL_EXPLICIT) &&
+ clone_ticket(mdl_request))
+ {
+ /* Clone failed. */
+ mdl_request->ticket= NULL;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ if (!(ticket= MDL_ticket::create(this, mdl_request->type
+#ifndef DBUG_OFF
+ , mdl_request->duration
+#endif
+ )))
+ return TRUE;
+
+ /* The below call implicitly locks MDL_lock::m_rwlock on success. */
+ if (!(lock= mdl_locks.find_or_insert(key)))
+ {
+ MDL_ticket::destroy(ticket);
+ return TRUE;
+ }
+
+ ticket->m_lock= lock;
+
+ if (lock->can_grant_lock(mdl_request->type, this, false))
+ {
+ lock->m_granted.add_ticket(ticket);
+
+ mysql_prlock_unlock(&lock->m_rwlock);
+
+ m_tickets[mdl_request->duration].push_front(ticket);
+
+ mdl_request->ticket= ticket;
+ }
+ else
+ *out_ticket= ticket;
+
+ return FALSE;
+}
+
+
+/**
+ Create a copy of a granted ticket.
+ This is used to make sure that HANDLER ticket
+ is never shared with a ticket that belongs to
+ a transaction, so that when we HANDLER CLOSE,
+ we don't release a transactional ticket, and
+ vice versa -- when we COMMIT, we don't mistakenly
+ release a ticket for an open HANDLER.
+
+ @retval TRUE Out of memory.
+ @retval FALSE Success.
+*/
+
+bool
+MDL_context::clone_ticket(MDL_request *mdl_request)
+{
+ MDL_ticket *ticket;
+
+ /*
+ By submitting mdl_request->type to MDL_ticket::create()
+ we effectively downgrade the cloned lock to the level of
+ the request.
+ */
+ if (!(ticket= MDL_ticket::create(this, mdl_request->type
+#ifndef DBUG_OFF
+ , mdl_request->duration
+#endif
+ )))
+ return TRUE;
+
+ /* clone() is not supposed to be used to get a stronger lock. */
+ DBUG_ASSERT(mdl_request->ticket->has_stronger_or_equal_type(ticket->m_type));
+
+ ticket->m_lock= mdl_request->ticket->m_lock;
+ mdl_request->ticket= ticket;
+
+ mysql_prlock_wrlock(&ticket->m_lock->m_rwlock);
+ ticket->m_lock->m_granted.add_ticket(ticket);
+ mysql_prlock_unlock(&ticket->m_lock->m_rwlock);
+
+ m_tickets[mdl_request->duration].push_front(ticket);
+
+ return FALSE;
+}
+
+
+/**
+ Notify threads holding a shared metadata locks on object which
+ conflict with a pending X, SNW or SNRW lock.
+
+ @param ctx MDL_context for current thread.
+*/
+
+void MDL_object_lock::notify_conflicting_locks(MDL_context *ctx)
+{
+ Ticket_iterator it(m_granted);
+ MDL_ticket *conflicting_ticket;
+
+ while ((conflicting_ticket= it++))
+ {
+ /* Only try to abort locks on which we back off. */
+ if (conflicting_ticket->get_ctx() != ctx &&
+ conflicting_ticket->get_type() < MDL_SHARED_UPGRADABLE)
+
+ {
+ MDL_context *conflicting_ctx= conflicting_ticket->get_ctx();
+
+ /*
+ If thread which holds conflicting lock is waiting on table-level
+ lock or some other non-MDL resource we might need to wake it up
+ by calling code outside of MDL.
+ */
+ ctx->get_owner()->
+ notify_shared_lock(conflicting_ctx->get_owner(),
+ conflicting_ctx->get_needs_thr_lock_abort());
+ }
+ }
+}
+
+
+/**
+ Notify threads holding scoped IX locks which conflict with a pending S lock.
+
+ @param ctx MDL_context for current thread.
+*/
+
+void MDL_scoped_lock::notify_conflicting_locks(MDL_context *ctx)
+{
+ Ticket_iterator it(m_granted);
+ MDL_ticket *conflicting_ticket;
+
+ while ((conflicting_ticket= it++))
+ {
+ if (conflicting_ticket->get_ctx() != ctx &&
+ conflicting_ticket->get_type() == MDL_INTENTION_EXCLUSIVE)
+
+ {
+ MDL_context *conflicting_ctx= conflicting_ticket->get_ctx();
+
+ /*
+ Thread which holds global IX lock can be a handler thread for
+ insert delayed. We need to kill such threads in order to get
+ global shared lock. We do this my calling code outside of MDL.
+ */
+ ctx->get_owner()->
+ notify_shared_lock(conflicting_ctx->get_owner(),
+ conflicting_ctx->get_needs_thr_lock_abort());
+ }
+ }
+}
+
+
+/**
+ Acquire one lock with waiting for conflicting locks to go away if needed.
+
+ @param mdl_request [in/out] Lock request object for lock to be acquired
+
+ @param lock_wait_timeout [in] Seconds to wait before timeout.
+
+ @retval FALSE Success. MDL_request::ticket points to the ticket
+ for the lock.
+ @retval TRUE Failure (Out of resources or waiting is aborted),
+*/
+
+bool
+MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
+{
+ MDL_lock *lock;
+ MDL_ticket *ticket;
+ struct timespec abs_timeout;
+ MDL_wait::enum_wait_status wait_status;
+ DBUG_ENTER("MDL_context::acquire_lock");
+ DBUG_PRINT("enter", ("lock_type: %d", mdl_request->type));
+
+ /* Do some work outside the critical section. */
+ set_timespec(abs_timeout, lock_wait_timeout);
+
+ if (try_acquire_lock_impl(mdl_request, &ticket))
+ DBUG_RETURN(TRUE);
+
+ if (mdl_request->ticket)
+ {
+ /*
+ We have managed to acquire lock without waiting.
+ MDL_lock, MDL_context and MDL_request were updated
+ accordingly, so we can simply return success.
+ */
+ DBUG_PRINT("info", ("Got lock without waiting"));
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ Our attempt to acquire lock without waiting has failed.
+ As a result of this attempt we got MDL_ticket with m_lock
+ member pointing to the corresponding MDL_lock object which
+ has MDL_lock::m_rwlock write-locked.
+ */
+ lock= ticket->m_lock;
+
+ lock->m_waiting.add_ticket(ticket);
+
+ /*
+ Once we added a pending ticket to the waiting queue,
+ we must ensure that our wait slot is empty, so
+ that our lock request can be scheduled. Do that in the
+ critical section formed by the acquired write lock on MDL_lock.
+ */
+ m_wait.reset_status();
+
+ /*
+ Don't break conflicting locks if timeout is 0 as 0 is used
+ To check if there is any conflicting locks...
+ */
+ if (lock->needs_notification(ticket) && lock_wait_timeout)
+ lock->notify_conflicting_locks(this);
+
+ mysql_prlock_unlock(&lock->m_rwlock);
+
+ will_wait_for(ticket);
+
+ /* There is a shared or exclusive lock on the object. */
+ DEBUG_SYNC(get_thd(), "mdl_acquire_lock_wait");
+
+ find_deadlock();
+
+ struct timespec abs_shortwait;
+ set_timespec(abs_shortwait, 1);
+ wait_status= MDL_wait::EMPTY;
+
+ while (cmp_timespec(abs_shortwait, abs_timeout) <= 0)
+ {
+ /* abs_timeout is far away. Wait a short while and notify locks. */
+ wait_status= m_wait.timed_wait(m_owner, &abs_shortwait, FALSE,
+ mdl_request->key.get_wait_state_name());
+
+ if (wait_status != MDL_wait::EMPTY)
+ break;
+ /* Check if the client is gone while we were waiting. */
+ if (! thd_is_connected(m_owner->get_thd()))
+ {
+ /*
+ * The client is disconnected. Don't wait forever:
+ * assume it's the same as a wait timeout, this
+ * ensures all error handling is correct.
+ */
+ wait_status= MDL_wait::TIMEOUT;
+ break;
+ }
+
+ mysql_prlock_wrlock(&lock->m_rwlock);
+ if (lock->needs_notification(ticket))
+ lock->notify_conflicting_locks(this);
+ mysql_prlock_unlock(&lock->m_rwlock);
+ set_timespec(abs_shortwait, 1);
+ }
+ if (wait_status == MDL_wait::EMPTY)
+ wait_status= m_wait.timed_wait(m_owner, &abs_timeout, TRUE,
+ mdl_request->key.get_wait_state_name());
+
+ done_waiting_for();
+
+ if (wait_status != MDL_wait::GRANTED)
+ {
+ lock->remove_ticket(&MDL_lock::m_waiting, ticket);
+ MDL_ticket::destroy(ticket);
+ switch (wait_status)
+ {
+ case MDL_wait::VICTIM:
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ break;
+ case MDL_wait::TIMEOUT:
+ my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
+ break;
+ case MDL_wait::KILLED:
+ get_thd()->send_kill_message();
+ break;
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ We have been granted our request.
+ State of MDL_lock object is already being appropriately updated by a
+ concurrent thread (@sa MDL_lock:reschedule_waiters()).
+ So all we need to do is to update MDL_context and MDL_request objects.
+ */
+ DBUG_ASSERT(wait_status == MDL_wait::GRANTED);
+
+ m_tickets[mdl_request->duration].push_front(ticket);
+
+ mdl_request->ticket= ticket;
+
+ DBUG_RETURN(FALSE);
+}
+
+
+extern "C" int mdl_request_ptr_cmp(const void* ptr1, const void* ptr2)
+{
+ MDL_request *req1= *(MDL_request**)ptr1;
+ MDL_request *req2= *(MDL_request**)ptr2;
+ return req1->key.cmp(&req2->key);
+}
+
+
+/**
+ Acquire exclusive locks. There must be no granted locks in the
+ context.
+
+ This is a replacement of lock_table_names(). It is used in
+ RENAME, DROP and other DDL SQL statements.
+
+ @param mdl_requests List of requests for locks to be acquired.
+
+ @param lock_wait_timeout Seconds to wait before timeout.
+
+ @note The list of requests should not contain non-exclusive lock requests.
+ There should not be any acquired locks in the context.
+
+ @note Assumes that one already owns scoped intention exclusive lock.
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool MDL_context::acquire_locks(MDL_request_list *mdl_requests,
+ ulong lock_wait_timeout)
+{
+ MDL_request_list::Iterator it(*mdl_requests);
+ MDL_request **sort_buf, **p_req;
+ MDL_savepoint mdl_svp= mdl_savepoint();
+ ssize_t req_count= static_cast<ssize_t>(mdl_requests->elements());
+ DBUG_ENTER("MDL_context::acquire_locks");
+
+ if (req_count == 0)
+ DBUG_RETURN(FALSE);
+
+ /* Sort requests according to MDL_key. */
+ if (! (sort_buf= (MDL_request **)my_malloc(req_count *
+ sizeof(MDL_request*),
+ MYF(MY_WME))))
+ DBUG_RETURN(TRUE);
+
+ for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++)
+ *p_req= it++;
+
+ my_qsort(sort_buf, req_count, sizeof(MDL_request*),
+ mdl_request_ptr_cmp);
+
+ for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++)
+ {
+ if (acquire_lock(*p_req, lock_wait_timeout))
+ goto err;
+ }
+ my_free(sort_buf);
+ DBUG_RETURN(FALSE);
+
+err:
+ /*
+ Release locks we have managed to acquire so far.
+ Use rollback_to_savepoint() since there may be duplicate
+ requests that got assigned the same ticket.
+ */
+ rollback_to_savepoint(mdl_svp);
+ /* Reset lock requests back to its initial state. */
+ for (req_count= p_req - sort_buf, p_req= sort_buf;
+ p_req < sort_buf + req_count; p_req++)
+ {
+ (*p_req)->ticket= NULL;
+ }
+ my_free(sort_buf);
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Upgrade a shared metadata lock.
+
+ Used in ALTER TABLE.
+
+ @param mdl_ticket Lock to upgrade.
+ @param new_type Lock type to upgrade to.
+ @param lock_wait_timeout Seconds to wait before timeout.
+
+ @note In case of failure to upgrade lock (e.g. because upgrader
+ was killed) leaves lock in its original state (locked in
+ shared mode).
+
+ @note There can be only one upgrader for a lock or we will have deadlock.
+ This invariant is ensured by the fact that upgradeable locks SU, SNW
+ and SNRW are not compatible with each other and themselves.
+
+ @retval FALSE Success
+ @retval TRUE Failure (thread was killed)
+*/
+
+bool
+MDL_context::upgrade_shared_lock(MDL_ticket *mdl_ticket,
+ enum_mdl_type new_type,
+ ulong lock_wait_timeout)
+{
+ MDL_request mdl_xlock_request;
+ MDL_savepoint mdl_svp= mdl_savepoint();
+ bool is_new_ticket;
+ DBUG_ENTER("MDL_context::upgrade_shared_lock");
+ DBUG_PRINT("enter",("new_type: %d lock_wait_timeout: %lu", new_type,
+ lock_wait_timeout));
+ DEBUG_SYNC(get_thd(), "mdl_upgrade_lock");
+
+ /*
+ Do nothing if already upgraded. Used when we FLUSH TABLE under
+ LOCK TABLES and a table is listed twice in LOCK TABLES list.
+ */
+ if (mdl_ticket->has_stronger_or_equal_type(new_type))
+ DBUG_RETURN(FALSE);
+
+ /* Only allow upgrades from SHARED_UPGRADABLE/NO_WRITE/NO_READ_WRITE */
+ DBUG_ASSERT(mdl_ticket->m_type == MDL_SHARED_UPGRADABLE ||
+ mdl_ticket->m_type == MDL_SHARED_NO_WRITE ||
+ mdl_ticket->m_type == MDL_SHARED_NO_READ_WRITE);
+
+ mdl_xlock_request.init(&mdl_ticket->m_lock->key, new_type,
+ MDL_TRANSACTION);
+
+ if (acquire_lock(&mdl_xlock_request, lock_wait_timeout))
+ DBUG_RETURN(TRUE);
+
+ is_new_ticket= ! has_lock(mdl_svp, mdl_xlock_request.ticket);
+
+ /* Merge the acquired and the original lock. @todo: move to a method. */
+ mysql_prlock_wrlock(&mdl_ticket->m_lock->m_rwlock);
+ if (is_new_ticket)
+ mdl_ticket->m_lock->m_granted.remove_ticket(mdl_xlock_request.ticket);
+ /*
+ Set the new type of lock in the ticket. To update state of
+ MDL_lock object correctly we need to temporarily exclude
+ ticket from the granted queue and then include it back.
+ */
+ mdl_ticket->m_lock->m_granted.remove_ticket(mdl_ticket);
+ mdl_ticket->m_type= new_type;
+ mdl_ticket->m_lock->m_granted.add_ticket(mdl_ticket);
+
+ mysql_prlock_unlock(&mdl_ticket->m_lock->m_rwlock);
+
+ if (is_new_ticket)
+ {
+ m_tickets[MDL_TRANSACTION].remove(mdl_xlock_request.ticket);
+ MDL_ticket::destroy(mdl_xlock_request.ticket);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ A fragment of recursive traversal of the wait-for graph
+ in search for deadlocks. Direct the deadlock visitor to all
+ contexts that own the lock the current node in the wait-for
+ graph is waiting for.
+ As long as the initial node is remembered in the visitor,
+ a deadlock is found when the same node is seen twice.
+*/
+
+bool MDL_lock::visit_subgraph(MDL_ticket *waiting_ticket,
+ MDL_wait_for_graph_visitor *gvisitor)
+{
+ MDL_ticket *ticket;
+ MDL_context *src_ctx= waiting_ticket->get_ctx();
+ bool result= TRUE;
+
+ mysql_prlock_rdlock(&m_rwlock);
+
+ /* Must be initialized after taking a read lock. */
+ Ticket_iterator granted_it(m_granted);
+ Ticket_iterator waiting_it(m_waiting);
+
+ /*
+ MDL_lock's waiting and granted queues and MDL_context::m_waiting_for
+ member are updated by different threads when the lock is granted
+ (see MDL_context::acquire_lock() and MDL_lock::reschedule_waiters()).
+ As a result, here we may encounter a situation when MDL_lock data
+ already reflects the fact that the lock was granted but
+ m_waiting_for member has not been updated yet.
+
+ For example, imagine that:
+
+ thread1: Owns SNW lock on table t1.
+ thread2: Attempts to acquire SW lock on t1,
+ but sees an active SNW lock.
+ Thus adds the ticket to the waiting queue and
+ sets m_waiting_for to point to the ticket.
+ thread1: Releases SNW lock, updates MDL_lock object to
+ grant SW lock to thread2 (moves the ticket for
+ SW from waiting to the active queue).
+ Attempts to acquire a new SNW lock on t1,
+ sees an active SW lock (since it is present in the
+ active queue), adds ticket for SNW lock to the waiting
+ queue, sets m_waiting_for to point to this ticket.
+
+ At this point deadlock detection algorithm run by thread1 will see that:
+ - Thread1 waits for SNW lock on t1 (since m_waiting_for is set).
+ - SNW lock is not granted, because it conflicts with active SW lock
+ owned by thread 2 (since ticket for SW is present in granted queue).
+ - Thread2 waits for SW lock (since its m_waiting_for has not been
+ updated yet!).
+ - SW lock is not granted because there is pending SNW lock from thread1.
+ Therefore deadlock should exist [sic!].
+
+ To avoid detection of such false deadlocks we need to check the "actual"
+ status of the ticket being waited for, before analyzing its blockers.
+ We do this by checking the wait status of the context which is waiting
+ for it. To avoid races this has to be done under protection of
+ MDL_lock::m_rwlock lock.
+ */
+ if (src_ctx->m_wait.get_status() != MDL_wait::EMPTY)
+ {
+ result= FALSE;
+ goto end;
+ }
+
+ /*
+ To avoid visiting nodes which were already marked as victims of
+ deadlock detection (or whose requests were already satisfied) we
+ enter the node only after peeking at its wait status.
+ This is necessary to avoid active waiting in a situation
+ when previous searches for a deadlock already selected the
+ node we're about to enter as a victim (see the comment
+ in MDL_context::find_deadlock() for explanation why several searches
+ can be performed for the same wait).
+ There is no guarantee that the node isn't chosen a victim while we
+ are visiting it but this is OK: in the worst case we might do some
+ extra work and one more context might be chosen as a victim.
+ */
+ if (gvisitor->enter_node(src_ctx))
+ goto end;
+
+ /*
+ We do a breadth-first search first -- that is, inspect all
+ edges of the current node, and only then follow up to the next
+ node. In workloads that involve wait-for graph loops this
+ has proven to be a more efficient strategy [citation missing].
+ */
+ while ((ticket= granted_it++))
+ {
+ /* Filter out edges that point to the same node. */
+ if (ticket->get_ctx() != src_ctx &&
+ ticket->is_incompatible_when_granted(waiting_ticket->get_type()) &&
+ gvisitor->inspect_edge(ticket->get_ctx()))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ while ((ticket= waiting_it++))
+ {
+ /* Filter out edges that point to the same node. */
+ if (ticket->get_ctx() != src_ctx &&
+ ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) &&
+ gvisitor->inspect_edge(ticket->get_ctx()))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ /* Recurse and inspect all adjacent nodes. */
+ granted_it.rewind();
+ while ((ticket= granted_it++))
+ {
+ if (ticket->get_ctx() != src_ctx &&
+ ticket->is_incompatible_when_granted(waiting_ticket->get_type()) &&
+ ticket->get_ctx()->visit_subgraph(gvisitor))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ waiting_it.rewind();
+ while ((ticket= waiting_it++))
+ {
+ if (ticket->get_ctx() != src_ctx &&
+ ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) &&
+ ticket->get_ctx()->visit_subgraph(gvisitor))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ result= FALSE;
+
+end_leave_node:
+ gvisitor->leave_node(src_ctx);
+
+end:
+ mysql_prlock_unlock(&m_rwlock);
+ return result;
+}
+
+
+/**
+ Traverse a portion of wait-for graph which is reachable
+ through the edge represented by this ticket and search
+ for deadlocks.
+
+ @retval TRUE A deadlock is found. A pointer to deadlock
+ victim is saved in the visitor.
+ @retval FALSE
+*/
+
+bool MDL_ticket::accept_visitor(MDL_wait_for_graph_visitor *gvisitor)
+{
+ return m_lock->visit_subgraph(this, gvisitor);
+}
+
+
+/**
+ A fragment of recursive traversal of the wait-for graph of
+ MDL contexts in the server in search for deadlocks.
+ Assume this MDL context is a node in the wait-for graph,
+ and direct the visitor to all adjacent nodes. As long
+ as the starting node is remembered in the visitor, a
+ deadlock is found when the same node is visited twice.
+ One MDL context is connected to another in the wait-for
+ graph if it waits on a resource that is held by the other
+ context.
+
+ @retval TRUE A deadlock is found. A pointer to deadlock
+ victim is saved in the visitor.
+ @retval FALSE
+*/
+
+bool MDL_context::visit_subgraph(MDL_wait_for_graph_visitor *gvisitor)
+{
+ bool result= FALSE;
+
+ mysql_prlock_rdlock(&m_LOCK_waiting_for);
+
+ if (m_waiting_for)
+ result= m_waiting_for->accept_visitor(gvisitor);
+
+ mysql_prlock_unlock(&m_LOCK_waiting_for);
+
+ return result;
+}
+
+
+/**
+ Try to find a deadlock. This function produces no errors.
+
+ @note If during deadlock resolution context which performs deadlock
+ detection is chosen as a victim it will be informed about the
+ fact by setting VICTIM status to its wait slot.
+*/
+
+void MDL_context::find_deadlock()
+{
+ while (1)
+ {
+ /*
+ The fact that we use fresh instance of gvisitor for each
+ search performed by find_deadlock() below is important,
+ the code responsible for victim selection relies on this.
+ */
+ Deadlock_detection_visitor dvisitor(this);
+ MDL_context *victim;
+
+ if (! visit_subgraph(&dvisitor))
+ {
+ /* No deadlocks are found! */
+ break;
+ }
+
+ victim= dvisitor.get_victim();
+
+ /*
+ Failure to change status of the victim is OK as it means
+ that the victim has received some other message and is
+ about to stop its waiting/to break deadlock loop.
+ Even when the initiator of the deadlock search is
+ chosen the victim, we need to set the respective wait
+ result in order to "close" it for any attempt to
+ schedule the request.
+ This is needed to avoid a possible race during
+ cleanup in case when the lock request on which the
+ context was waiting is concurrently satisfied.
+ */
+ (void) victim->m_wait.set_status(MDL_wait::VICTIM);
+ victim->unlock_deadlock_victim();
+
+ if (victim == this)
+ break;
+ /*
+ After adding a new edge to the waiting graph we found that it
+ creates a loop (i.e. there is a deadlock). We decided to destroy
+ this loop by removing an edge, but not the one that we added.
+ Since this doesn't guarantee that all loops created by addition
+ of the new edge are destroyed, we have to repeat the search.
+ */
+ }
+}
+
+
+/**
+ Release lock.
+
+ @param duration Lock duration.
+ @param ticket Ticket for lock to be released.
+
+*/
+
+void MDL_context::release_lock(enum_mdl_duration duration, MDL_ticket *ticket)
+{
+ MDL_lock *lock= ticket->m_lock;
+ DBUG_ENTER("MDL_context::release_lock");
+ DBUG_PRINT("enter", ("db: '%s' name: '%s'",
+ lock->key.db_name(), lock->key.name()));
+
+ DBUG_ASSERT(this == ticket->get_ctx());
+
+ lock->remove_ticket(&MDL_lock::m_granted, ticket);
+
+ m_tickets[duration].remove(ticket);
+ MDL_ticket::destroy(ticket);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release lock with explicit duration.
+
+ @param ticket Ticket for lock to be released.
+
+*/
+
+void MDL_context::release_lock(MDL_ticket *ticket)
+{
+ DBUG_ASSERT(ticket->m_duration == MDL_EXPLICIT);
+
+ release_lock(MDL_EXPLICIT, ticket);
+}
+
+
+/**
+ Release all locks associated with the context. If the sentinel
+ is not NULL, do not release locks stored in the list after and
+ including the sentinel.
+
+ Statement and transactional locks are added to the beginning of
+ the corresponding lists, i.e. stored in reverse temporal order.
+ This allows to employ this function to:
+ - back off in case of a lock conflict.
+ - release all locks in the end of a statement or transaction
+ - rollback to a savepoint.
+*/
+
+void MDL_context::release_locks_stored_before(enum_mdl_duration duration,
+ MDL_ticket *sentinel)
+{
+ MDL_ticket *ticket;
+ Ticket_iterator it(m_tickets[duration]);
+ DBUG_ENTER("MDL_context::release_locks_stored_before");
+
+ if (m_tickets[duration].is_empty())
+ DBUG_VOID_RETURN;
+
+ while ((ticket= it++) && ticket != sentinel)
+ {
+ DBUG_PRINT("info", ("found lock to release ticket=%p", ticket));
+ release_lock(duration, ticket);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release all explicit locks in the context which correspond to the
+ same name/object as this lock request.
+
+ @param ticket One of the locks for the name/object for which all
+ locks should be released.
+*/
+
+void MDL_context::release_all_locks_for_name(MDL_ticket *name)
+{
+ /* Use MDL_ticket::m_lock to identify other locks for the same object. */
+ MDL_lock *lock= name->m_lock;
+
+ /* Remove matching lock tickets from the context. */
+ MDL_ticket *ticket;
+ Ticket_iterator it_ticket(m_tickets[MDL_EXPLICIT]);
+
+ while ((ticket= it_ticket++))
+ {
+ DBUG_ASSERT(ticket->m_lock);
+ if (ticket->m_lock == lock)
+ release_lock(MDL_EXPLICIT, ticket);
+ }
+}
+
+
+/**
+ Downgrade an EXCLUSIVE or SHARED_NO_WRITE lock to shared metadata lock.
+
+ @param type Type of lock to which exclusive lock should be downgraded.
+*/
+
+void MDL_ticket::downgrade_lock(enum_mdl_type type)
+{
+ /*
+ Do nothing if already downgraded. Used when we FLUSH TABLE under
+ LOCK TABLES and a table is listed twice in LOCK TABLES list.
+ Note that this code might even try to "downgrade" a weak lock
+ (e.g. SW) to a stronger one (e.g SNRW). So we can't even assert
+ here that target lock is weaker than existing lock.
+ */
+ if (m_type == type || !has_stronger_or_equal_type(type))
+ return;
+
+ /* Only allow downgrade from EXCLUSIVE and SHARED_NO_WRITE. */
+ DBUG_ASSERT(m_type == MDL_EXCLUSIVE ||
+ m_type == MDL_SHARED_NO_WRITE);
+
+ mysql_prlock_wrlock(&m_lock->m_rwlock);
+ /*
+ To update state of MDL_lock object correctly we need to temporarily
+ exclude ticket from the granted queue and then include it back.
+ */
+ m_lock->m_granted.remove_ticket(this);
+ m_type= type;
+ m_lock->m_granted.add_ticket(this);
+ m_lock->reschedule_waiters();
+ mysql_prlock_unlock(&m_lock->m_rwlock);
+}
+
+
+/**
+ Auxiliary function which allows to check if we have some kind of lock on
+ a object. Returns TRUE if we have a lock of a given or stronger type.
+
+ @param mdl_namespace Id of object namespace
+ @param db Name of the database
+ @param name Name of the object
+ @param mdl_type Lock type. Pass in the weakest type to find
+ out if there is at least some lock.
+
+ @return TRUE if current context contains satisfied lock for the object,
+ FALSE otherwise.
+*/
+
+bool
+MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
+ const char *db, const char *name,
+ enum_mdl_type mdl_type)
+{
+ MDL_request mdl_request;
+ enum_mdl_duration not_unused;
+ /* We don't care about exact duration of lock here. */
+ mdl_request.init(mdl_namespace, db, name, mdl_type, MDL_TRANSACTION);
+ MDL_ticket *ticket= find_ticket(&mdl_request, &not_unused);
+
+ DBUG_ASSERT(ticket == NULL || ticket->m_lock);
+
+ return ticket;
+}
+
+
+/**
+ Return thread id of the owner of the lock or 0 if
+ there is no owner.
+ @note: Lock type is not considered at all, the function
+ simply checks that there is some lock for the given key.
+
+ @return thread id of the owner of the lock or 0
+*/
+
+unsigned long
+MDL_context::get_lock_owner(MDL_key *key)
+{
+ return mdl_locks.get_lock_owner(key);
+}
+
+
+/**
+ Check if we have any pending locks which conflict with existing shared lock.
+
+ @pre The ticket must match an acquired lock.
+
+ @return TRUE if there is a conflicting lock request, FALSE otherwise.
+*/
+
+bool MDL_ticket::has_pending_conflicting_lock() const
+{
+ return m_lock->has_pending_conflicting_lock(m_type);
+}
+
+/** Return a key identifying this lock. */
+MDL_key *MDL_ticket::get_key() const
+{
+ return &m_lock->key;
+}
+
+/**
+ Releases metadata locks that were acquired after a specific savepoint.
+
+ @note Used to release tickets acquired during a savepoint unit.
+ @note It's safe to iterate and unlock any locks after taken after this
+ savepoint because other statements that take other special locks
+ cause a implicit commit (ie LOCK TABLES).
+*/
+
+void MDL_context::rollback_to_savepoint(const MDL_savepoint &mdl_savepoint)
+{
+ DBUG_ENTER("MDL_context::rollback_to_savepoint");
+
+ /* If savepoint is NULL, it is from the start of the transaction. */
+ release_locks_stored_before(MDL_STATEMENT, mdl_savepoint.m_stmt_ticket);
+ release_locks_stored_before(MDL_TRANSACTION, mdl_savepoint.m_trans_ticket);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release locks acquired by normal statements (SELECT, UPDATE,
+ DELETE, etc) in the course of a transaction. Do not release
+ HANDLER locks, if there are any.
+
+ This method is used at the end of a transaction, in
+ implementation of COMMIT (implicit or explicit) and ROLLBACK.
+*/
+
+void MDL_context::release_transactional_locks()
+{
+ DBUG_ENTER("MDL_context::release_transactional_locks");
+ release_locks_stored_before(MDL_STATEMENT, NULL);
+ release_locks_stored_before(MDL_TRANSACTION, NULL);
+ DBUG_VOID_RETURN;
+}
+
+
+void MDL_context::release_statement_locks()
+{
+ DBUG_ENTER("MDL_context::release_transactional_locks");
+ release_locks_stored_before(MDL_STATEMENT, NULL);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Does this savepoint have this lock?
+
+ @retval TRUE The ticket is older than the savepoint or
+ is an LT, HA or GLR ticket. Thus it belongs
+ to the savepoint or has explicit duration.
+ @retval FALSE The ticket is newer than the savepoint.
+ and is not an LT, HA or GLR ticket.
+*/
+
+bool MDL_context::has_lock(const MDL_savepoint &mdl_savepoint,
+ MDL_ticket *mdl_ticket)
+{
+ MDL_ticket *ticket;
+ /* Start from the beginning, most likely mdl_ticket's been just acquired. */
+ MDL_context::Ticket_iterator s_it(m_tickets[MDL_STATEMENT]);
+ MDL_context::Ticket_iterator t_it(m_tickets[MDL_TRANSACTION]);
+
+ while ((ticket= s_it++) && ticket != mdl_savepoint.m_stmt_ticket)
+ {
+ if (ticket == mdl_ticket)
+ return FALSE;
+ }
+
+ while ((ticket= t_it++) && ticket != mdl_savepoint.m_trans_ticket)
+ {
+ if (ticket == mdl_ticket)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/**
+ Change lock duration for transactional lock.
+
+ @param ticket Ticket representing lock.
+ @param duration Lock duration to be set.
+
+ @note This method only supports changing duration of
+ transactional lock to some other duration.
+*/
+
+void MDL_context::set_lock_duration(MDL_ticket *mdl_ticket,
+ enum_mdl_duration duration)
+{
+ DBUG_ASSERT(mdl_ticket->m_duration == MDL_TRANSACTION &&
+ duration != MDL_TRANSACTION);
+
+ m_tickets[MDL_TRANSACTION].remove(mdl_ticket);
+ m_tickets[duration].push_front(mdl_ticket);
+#ifndef DBUG_OFF
+ mdl_ticket->m_duration= duration;
+#endif
+}
+
+
+/**
+ Set explicit duration for all locks in the context.
+*/
+
+void MDL_context::set_explicit_duration_for_all_locks()
+{
+ int i;
+ MDL_ticket *ticket;
+
+ /*
+ In the most common case when this function is called list
+ of transactional locks is bigger than list of locks with
+ explicit duration. So we start by swapping these two lists
+ and then move elements from new list of transactional
+ locks and list of statement locks to list of locks with
+ explicit duration.
+ */
+
+ m_tickets[MDL_EXPLICIT].swap(m_tickets[MDL_TRANSACTION]);
+
+ for (i= 0; i < MDL_EXPLICIT; i++)
+ {
+ Ticket_iterator it_ticket(m_tickets[i]);
+
+ while ((ticket= it_ticket++))
+ {
+ m_tickets[i].remove(ticket);
+ m_tickets[MDL_EXPLICIT].push_front(ticket);
+ }
+ }
+
+#ifndef DBUG_OFF
+ Ticket_iterator exp_it(m_tickets[MDL_EXPLICIT]);
+
+ while ((ticket= exp_it++))
+ ticket->m_duration= MDL_EXPLICIT;
+#endif
+}
+
+
+/**
+ Set transactional duration for all locks in the context.
+*/
+
+void MDL_context::set_transaction_duration_for_all_locks()
+{
+ MDL_ticket *ticket;
+
+ /*
+ In the most common case when this function is called list
+ of explicit locks is bigger than two other lists (in fact,
+ list of statement locks is always empty). So we start by
+ swapping list of explicit and transactional locks and then
+ move contents of new list of explicit locks to list of
+ locks with transactional duration.
+ */
+
+ DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty());
+
+ m_tickets[MDL_TRANSACTION].swap(m_tickets[MDL_EXPLICIT]);
+
+ Ticket_iterator it_ticket(m_tickets[MDL_EXPLICIT]);
+
+ while ((ticket= it_ticket++))
+ {
+ m_tickets[MDL_EXPLICIT].remove(ticket);
+ m_tickets[MDL_TRANSACTION].push_front(ticket);
+ }
+
+#ifndef DBUG_OFF
+ Ticket_iterator trans_it(m_tickets[MDL_TRANSACTION]);
+
+ while ((ticket= trans_it++))
+ ticket->m_duration= MDL_TRANSACTION;
+#endif
+}
diff --git a/sql/mdl.h b/sql/mdl.h
new file mode 100644
index 00000000000..c4d792acd29
--- /dev/null
+++ b/sql/mdl.h
@@ -0,0 +1,1000 @@
+#ifndef MDL_H
+#define MDL_H
+/* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#if defined(__IBMC__) || defined(__IBMCPP__)
+/* Further down, "next_in_lock" and "next_in_context" have the same type,
+ and in "sql_plist.h" this leads to an identical signature, which causes
+ problems in function overloading.
+*/
+#pragma namemangling(v5)
+#endif
+
+
+#include "sql_plist.h"
+#include <my_sys.h>
+#include <m_string.h>
+#include <mysql_com.h>
+#include <hash.h>
+
+#include <algorithm>
+
+class THD;
+
+class MDL_context;
+class MDL_lock;
+class MDL_ticket;
+bool ok_for_lower_case_names(const char *name);
+
+/**
+ @def ENTER_COND(C, M, S, O)
+ Start a wait on a condition.
+ @param C the condition to wait on
+ @param M the associated mutex
+ @param S the new stage to enter
+ @param O the previous stage
+ @sa EXIT_COND().
+*/
+#define ENTER_COND(C, M, S, O) enter_cond(C, M, S, O, __func__, __FILE__, __LINE__)
+
+/**
+ @def EXIT_COND(S)
+ End a wait on a condition
+ @param S the new stage to enter
+*/
+#define EXIT_COND(S) exit_cond(S, __func__, __FILE__, __LINE__)
+
+/**
+ An interface to separate the MDL module from the THD, and the rest of the
+ server code.
+ */
+
+class MDL_context_owner
+{
+public:
+ virtual ~MDL_context_owner() {}
+
+ /**
+ Enter a condition wait.
+ For @c enter_cond() / @c exit_cond() to work the mutex must be held before
+ @c enter_cond(); this mutex is then released by @c exit_cond().
+ Usage must be: lock mutex; enter_cond(); your code; exit_cond().
+ @param cond the condition to wait on
+ @param mutex the associated mutex
+ @param [in] stage the stage to enter, or NULL
+ @param [out] old_stage the previous stage, or NULL
+ @param src_function function name of the caller
+ @param src_file file name of the caller
+ @param src_line line number of the caller
+ @sa ENTER_COND(), THD::enter_cond()
+ @sa EXIT_COND(), THD::exit_cond()
+ */
+ virtual void enter_cond(mysql_cond_t *cond, mysql_mutex_t *mutex,
+ const PSI_stage_info *stage, PSI_stage_info *old_stage,
+ const char *src_function, const char *src_file,
+ int src_line) = 0;
+
+ /**
+ @def EXIT_COND(S)
+ End a wait on a condition
+ @param [in] stage the new stage to enter
+ @param src_function function name of the caller
+ @param src_file file name of the caller
+ @param src_line line number of the caller
+ @sa ENTER_COND(), THD::enter_cond()
+ @sa EXIT_COND(), THD::exit_cond()
+ */
+ virtual void exit_cond(const PSI_stage_info *stage,
+ const char *src_function, const char *src_file,
+ int src_line) = 0;
+ /**
+ Has the owner thread been killed?
+ */
+ virtual int is_killed() = 0;
+
+ /**
+ This one is only used for DEBUG_SYNC.
+ (Do not use it to peek/poke into other parts of THD.)
+ */
+ virtual THD* get_thd() = 0;
+
+ /**
+ @see THD::notify_shared_lock()
+ */
+ virtual bool notify_shared_lock(MDL_context_owner *in_use,
+ bool needs_thr_lock_abort) = 0;
+};
+
+/**
+ Type of metadata lock request.
+
+ @sa Comments for MDL_object_lock::can_grant_lock() and
+ MDL_scoped_lock::can_grant_lock() for details.
+*/
+
+enum enum_mdl_type {
+ /*
+ An intention exclusive metadata lock. Used only for scoped locks.
+ Owner of this type of lock can acquire upgradable exclusive locks on
+ individual objects.
+ Compatible with other IX locks, but is incompatible with scoped S and
+ X locks.
+ */
+ MDL_INTENTION_EXCLUSIVE= 0,
+ /*
+ A shared metadata lock.
+ To be used in cases when we are interested in object metadata only
+ and there is no intention to access object data (e.g. for stored
+ routines or during preparing prepared statements).
+ We also mis-use this type of lock for open HANDLERs, since lock
+ acquired by this statement has to be compatible with lock acquired
+ by LOCK TABLES ... WRITE statement, i.e. SNRW (We can't get by by
+ acquiring S lock at HANDLER ... OPEN time and upgrading it to SR
+ lock for HANDLER ... READ as it doesn't solve problem with need
+ to abort DML statements which wait on table level lock while having
+ open HANDLER in the same connection).
+ To avoid deadlock which may occur when SNRW lock is being upgraded to
+ X lock for table on which there is an active S lock which is owned by
+ thread which waits in its turn for table-level lock owned by thread
+ performing upgrade we have to use thr_abort_locks_for_thread()
+ facility in such situation.
+ This problem does not arise for locks on stored routines as we don't
+ use SNRW locks for them. It also does not arise when S locks are used
+ during PREPARE calls as table-level locks are not acquired in this
+ case.
+ */
+ MDL_SHARED,
+ /*
+ A high priority shared metadata lock.
+ Used for cases when there is no intention to access object data (i.e.
+ data in the table).
+ "High priority" means that, unlike other shared locks, it is granted
+ ignoring pending requests for exclusive locks. Intended for use in
+ cases when we only need to access metadata and not data, e.g. when
+ filling an INFORMATION_SCHEMA table.
+ Since SH lock is compatible with SNRW lock, the connection that
+ holds SH lock lock should not try to acquire any kind of table-level
+ or row-level lock, as this can lead to a deadlock. Moreover, after
+ acquiring SH lock, the connection should not wait for any other
+ resource, as it might cause starvation for X locks and a potential
+ deadlock during upgrade of SNW or SNRW to X lock (e.g. if the
+ upgrading connection holds the resource that is being waited for).
+ */
+ MDL_SHARED_HIGH_PRIO,
+ /*
+ A shared metadata lock for cases when there is an intention to read data
+ from table.
+ A connection holding this kind of lock can read table metadata and read
+ table data (after acquiring appropriate table and row-level locks).
+ This means that one can only acquire TL_READ, TL_READ_NO_INSERT, and
+ similar table-level locks on table if one holds SR MDL lock on it.
+ To be used for tables in SELECTs, subqueries, and LOCK TABLE ... READ
+ statements.
+ */
+ MDL_SHARED_READ,
+ /*
+ A shared metadata lock for cases when there is an intention to modify
+ (and not just read) data in the table.
+ A connection holding SW lock can read table metadata and modify or read
+ table data (after acquiring appropriate table and row-level locks).
+ To be used for tables to be modified by INSERT, UPDATE, DELETE
+ statements, but not LOCK TABLE ... WRITE or DDL). Also taken by
+ SELECT ... FOR UPDATE.
+ */
+ MDL_SHARED_WRITE,
+ /*
+ An upgradable shared metadata lock for cases when there is an intention
+ to modify (and not just read) data in the table.
+ Can be upgraded to MDL_SHARED_NO_WRITE and MDL_EXCLUSIVE.
+ A connection holding SU lock can read table metadata and modify or read
+ table data (after acquiring appropriate table and row-level locks).
+ To be used for the first phase of ALTER TABLE.
+ */
+ MDL_SHARED_UPGRADABLE,
+ /*
+ An upgradable shared metadata lock which blocks all attempts to update
+ table data, allowing reads.
+ A connection holding this kind of lock can read table metadata and read
+ table data.
+ Can be upgraded to X metadata lock.
+ Note, that since this type of lock is not compatible with SNRW or SW
+ lock types, acquiring appropriate engine-level locks for reading
+ (TL_READ* for MyISAM, shared row locks in InnoDB) should be
+ contention-free.
+ To be used for the first phase of ALTER TABLE, when copying data between
+ tables, to allow concurrent SELECTs from the table, but not UPDATEs.
+ */
+ MDL_SHARED_NO_WRITE,
+ /*
+ An upgradable shared metadata lock which allows other connections
+ to access table metadata, but not data.
+ It blocks all attempts to read or update table data, while allowing
+ INFORMATION_SCHEMA and SHOW queries.
+ A connection holding this kind of lock can read table metadata modify and
+ read table data.
+ Can be upgraded to X metadata lock.
+ To be used for LOCK TABLES WRITE statement.
+ Not compatible with any other lock type except S and SH.
+ */
+ MDL_SHARED_NO_READ_WRITE,
+ /*
+ An exclusive metadata lock.
+ A connection holding this lock can modify both table's metadata and data.
+ No other type of metadata lock can be granted while this lock is held.
+ To be used for CREATE/DROP/RENAME TABLE statements and for execution of
+ certain phases of other DDL statements.
+ */
+ MDL_EXCLUSIVE,
+ /* This should be the last !!! */
+ MDL_TYPE_END};
+
+
+/** Duration of metadata lock. */
+
+enum enum_mdl_duration {
+ /**
+ Locks with statement duration are automatically released at the end
+ of statement or transaction.
+ */
+ MDL_STATEMENT= 0,
+ /**
+ Locks with transaction duration are automatically released at the end
+ of transaction.
+ */
+ MDL_TRANSACTION,
+ /**
+ Locks with explicit duration survive the end of statement and transaction.
+ They have to be released explicitly by calling MDL_context::release_lock().
+ */
+ MDL_EXPLICIT,
+ /* This should be the last ! */
+ MDL_DURATION_END };
+
+
+/** Maximal length of key for metadata locking subsystem. */
+#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1)
+
+
+/**
+ Metadata lock object key.
+
+ A lock is requested or granted based on a fully qualified name and type.
+ E.g. They key for a table consists of <0 (=table)>+<database>+<table name>.
+ Elsewhere in the comments this triple will be referred to simply as "key"
+ or "name".
+*/
+
+class MDL_key
+{
+public:
+#ifdef HAVE_PSI_INTERFACE
+ static void init_psi_keys();
+#endif
+
+ /**
+ Object namespaces.
+ Sic: when adding a new member to this enum make sure to
+ update m_namespace_to_wait_state_name array in mdl.cc!
+
+ Different types of objects exist in different namespaces
+ - TABLE is for tables and views.
+ - FUNCTION is for stored functions.
+ - PROCEDURE is for stored procedures.
+ - TRIGGER is for triggers.
+ - EVENT is for event scheduler events
+ Note that although there isn't metadata locking on triggers,
+ it's necessary to have a separate namespace for them since
+ MDL_key is also used outside of the MDL subsystem.
+ */
+ enum enum_mdl_namespace { GLOBAL=0,
+ SCHEMA,
+ TABLE,
+ FUNCTION,
+ PROCEDURE,
+ TRIGGER,
+ EVENT,
+ COMMIT,
+ USER_LOCK, /* user level locks. */
+ /* This should be the last ! */
+ NAMESPACE_END };
+
+ const uchar *ptr() const { return (uchar*) m_ptr; }
+ uint length() const { return m_length; }
+
+ const char *db_name() const { return m_ptr + 1; }
+ uint db_name_length() const { return m_db_name_length; }
+
+ const char *name() const { return m_ptr + m_db_name_length + 2; }
+ uint name_length() const { return m_length - m_db_name_length - 3; }
+
+ enum_mdl_namespace mdl_namespace() const
+ { return (enum_mdl_namespace)(m_ptr[0]); }
+
+ /**
+ Construct a metadata lock key from a triplet (mdl_namespace,
+ database and name).
+
+ @remark The key for a table is <mdl_namespace>+<database name>+<table name>
+
+ @param mdl_namespace Id of namespace of object to be locked
+ @param db Name of database to which the object belongs
+ @param name Name of of the object
+ @param key Where to store the the MDL key.
+ */
+ void mdl_key_init(enum_mdl_namespace mdl_namespace,
+ const char *db, const char *name)
+ {
+ m_ptr[0]= (char) mdl_namespace;
+ /*
+ It is responsibility of caller to ensure that db and object names
+ are not longer than NAME_LEN. Still we play safe and try to avoid
+ buffer overruns.
+ */
+ DBUG_ASSERT(strlen(db) <= NAME_LEN);
+ DBUG_ASSERT(strlen(name) <= NAME_LEN);
+ m_db_name_length= static_cast<uint16>(strmake(m_ptr + 1, db, NAME_LEN) -
+ m_ptr - 1);
+ m_length= static_cast<uint16>(strmake(m_ptr + m_db_name_length + 2, name,
+ NAME_LEN) - m_ptr + 1);
+ m_hash_value= my_hash_sort(&my_charset_bin, (uchar*) m_ptr + 1,
+ m_length - 1);
+ DBUG_ASSERT(mdl_namespace == USER_LOCK || ok_for_lower_case_names(db));
+ }
+ void mdl_key_init(const MDL_key *rhs)
+ {
+ memcpy(m_ptr, rhs->m_ptr, rhs->m_length);
+ m_length= rhs->m_length;
+ m_db_name_length= rhs->m_db_name_length;
+ m_hash_value= rhs->m_hash_value;
+ }
+ bool is_equal(const MDL_key *rhs) const
+ {
+ return (m_length == rhs->m_length &&
+ memcmp(m_ptr, rhs->m_ptr, m_length) == 0);
+ }
+ /**
+ Compare two MDL keys lexicographically.
+ */
+ int cmp(const MDL_key *rhs) const
+ {
+ /*
+ The key buffer is always '\0'-terminated. Since key
+ character set is utf-8, we can safely assume that no
+ character starts with a zero byte.
+ */
+ using std::min;
+ return memcmp(m_ptr, rhs->m_ptr, min(m_length, rhs->m_length));
+ }
+
+ MDL_key(const MDL_key *rhs)
+ {
+ mdl_key_init(rhs);
+ }
+ MDL_key(enum_mdl_namespace namespace_arg,
+ const char *db_arg, const char *name_arg)
+ {
+ mdl_key_init(namespace_arg, db_arg, name_arg);
+ }
+ MDL_key() {} /* To use when part of MDL_request. */
+
+ /**
+ Get thread state name to be used in case when we have to
+ wait on resource identified by key.
+ */
+ const PSI_stage_info * get_wait_state_name() const
+ {
+ return & m_namespace_to_wait_state_name[(int)mdl_namespace()];
+ }
+ my_hash_value_type hash_value() const
+ {
+ return m_hash_value + mdl_namespace();
+ }
+ my_hash_value_type tc_hash_value() const
+ {
+ return m_hash_value;
+ }
+
+private:
+ uint16 m_length;
+ uint16 m_db_name_length;
+ my_hash_value_type m_hash_value;
+ char m_ptr[MAX_MDLKEY_LENGTH];
+ static PSI_stage_info m_namespace_to_wait_state_name[NAMESPACE_END];
+private:
+ MDL_key(const MDL_key &); /* not implemented */
+ MDL_key &operator=(const MDL_key &); /* not implemented */
+ friend my_hash_value_type mdl_hash_function(const CHARSET_INFO *,
+ const uchar *, size_t);
+};
+
+
+/**
+ A pending metadata lock request.
+
+ A lock request and a granted metadata lock are represented by
+ different classes because they have different allocation
+ sites and hence different lifetimes. The allocation of lock requests is
+ controlled from outside of the MDL subsystem, while allocation of granted
+ locks (tickets) is controlled within the MDL subsystem.
+
+ MDL_request is a C structure, you don't need to call a constructor
+ or destructor for it.
+*/
+
+class MDL_request
+{
+public:
+ /** Type of metadata lock. */
+ enum enum_mdl_type type;
+ /** Duration for requested lock. */
+ enum enum_mdl_duration duration;
+
+ /**
+ Pointers for participating in the list of lock requests for this context.
+ */
+ MDL_request *next_in_list;
+ MDL_request **prev_in_list;
+ /**
+ Pointer to the lock ticket object for this lock request.
+ Valid only if this lock request is satisfied.
+ */
+ MDL_ticket *ticket;
+
+ /** A lock is requested based on a fully qualified name and type. */
+ MDL_key key;
+
+public:
+ static void *operator new(size_t size, MEM_ROOT *mem_root) throw ()
+ { return alloc_root(mem_root, size); }
+ static void operator delete(void *ptr, MEM_ROOT *mem_root) {}
+
+ void init(MDL_key::enum_mdl_namespace namespace_arg,
+ const char *db_arg, const char *name_arg,
+ enum_mdl_type mdl_type_arg,
+ enum_mdl_duration mdl_duration_arg);
+ void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg,
+ enum_mdl_duration mdl_duration_arg);
+ /** Set type of lock request. Can be only applied to pending locks. */
+ inline void set_type(enum_mdl_type type_arg)
+ {
+ DBUG_ASSERT(ticket == NULL);
+ type= type_arg;
+ }
+
+ /*
+ This is to work around the ugliness of TABLE_LIST
+ compiler-generated assignment operator. It is currently used
+ in several places to quickly copy "most" of the members of the
+ table list. These places currently never assume that the mdl
+ request is carried over to the new TABLE_LIST, or shared
+ between lists.
+
+ This method does not initialize the instance being assigned!
+ Use of init() for initialization after this assignment operator
+ is mandatory. Can only be used before the request has been
+ granted.
+ */
+ MDL_request& operator=(const MDL_request &rhs)
+ {
+ ticket= NULL;
+ /* Do nothing, in particular, don't try to copy the key. */
+ return *this;
+ }
+ /* Another piece of ugliness for TABLE_LIST constructor */
+ MDL_request() {}
+
+ MDL_request(const MDL_request *rhs)
+ :type(rhs->type),
+ duration(rhs->duration),
+ ticket(NULL),
+ key(&rhs->key)
+ {}
+};
+
+
+typedef void (*mdl_cached_object_release_hook)(void *);
+
+
+/**
+ An abstract class for inspection of a connected
+ subgraph of the wait-for graph.
+*/
+
+class MDL_wait_for_graph_visitor
+{
+public:
+ virtual bool enter_node(MDL_context *node) = 0;
+ virtual void leave_node(MDL_context *node) = 0;
+
+ virtual bool inspect_edge(MDL_context *dest) = 0;
+ virtual ~MDL_wait_for_graph_visitor();
+ MDL_wait_for_graph_visitor() {}
+};
+
+/**
+ Abstract class representing an edge in the waiters graph
+ to be traversed by deadlock detection algorithm.
+*/
+
+class MDL_wait_for_subgraph
+{
+public:
+ virtual ~MDL_wait_for_subgraph();
+
+ /**
+ Accept a wait-for graph visitor to inspect the node
+ this edge is leading to.
+ */
+ virtual bool accept_visitor(MDL_wait_for_graph_visitor *gvisitor) = 0;
+
+ enum enum_deadlock_weight
+ {
+ DEADLOCK_WEIGHT_DML= 0,
+ DEADLOCK_WEIGHT_DDL= 100
+ };
+ /* A helper used to determine which lock request should be aborted. */
+ virtual uint get_deadlock_weight() const = 0;
+};
+
+
+/**
+ A granted metadata lock.
+
+ @warning MDL_ticket members are private to the MDL subsystem.
+
+ @note Multiple shared locks on a same object are represented by a
+ single ticket. The same does not apply for other lock types.
+
+ @note There are two groups of MDL_ticket members:
+ - "Externally accessible". These members can be accessed from
+ threads/contexts different than ticket owner in cases when
+ ticket participates in some list of granted or waiting tickets
+ for a lock. Therefore one should change these members before
+ including then to waiting/granted lists or while holding lock
+ protecting those lists.
+ - "Context private". Such members are private to thread/context
+ owning this ticket. I.e. they should not be accessed from other
+ threads/contexts.
+*/
+
+class MDL_ticket : public MDL_wait_for_subgraph
+{
+public:
+ /**
+ Pointers for participating in the list of lock requests for this context.
+ Context private.
+ */
+ MDL_ticket *next_in_context;
+ MDL_ticket **prev_in_context;
+ /**
+ Pointers for participating in the list of satisfied/pending requests
+ for the lock. Externally accessible.
+ */
+ MDL_ticket *next_in_lock;
+ MDL_ticket **prev_in_lock;
+public:
+ bool has_pending_conflicting_lock() const;
+
+ MDL_context *get_ctx() const { return m_ctx; }
+ bool is_upgradable_or_exclusive() const
+ {
+ return m_type == MDL_SHARED_UPGRADABLE ||
+ m_type == MDL_SHARED_NO_WRITE ||
+ m_type == MDL_SHARED_NO_READ_WRITE ||
+ m_type == MDL_EXCLUSIVE;
+ }
+ enum_mdl_type get_type() const { return m_type; }
+ MDL_lock *get_lock() const { return m_lock; }
+ MDL_key *get_key() const;
+ void downgrade_lock(enum_mdl_type type);
+
+ bool has_stronger_or_equal_type(enum_mdl_type type) const;
+
+ bool is_incompatible_when_granted(enum_mdl_type type) const;
+ bool is_incompatible_when_waiting(enum_mdl_type type) const;
+
+ /** Implement MDL_wait_for_subgraph interface. */
+ virtual bool accept_visitor(MDL_wait_for_graph_visitor *dvisitor);
+ virtual uint get_deadlock_weight() const;
+private:
+ friend class MDL_context;
+
+ MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg
+#ifndef DBUG_OFF
+ , enum_mdl_duration duration_arg
+#endif
+ )
+ : m_type(type_arg),
+#ifndef DBUG_OFF
+ m_duration(duration_arg),
+#endif
+ m_ctx(ctx_arg),
+ m_lock(NULL)
+ {}
+
+ static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg
+#ifndef DBUG_OFF
+ , enum_mdl_duration duration_arg
+#endif
+ );
+ static void destroy(MDL_ticket *ticket);
+private:
+ /** Type of metadata lock. Externally accessible. */
+ enum enum_mdl_type m_type;
+#ifndef DBUG_OFF
+ /**
+ Duration of lock represented by this ticket.
+ Context private. Debug-only.
+ */
+ enum_mdl_duration m_duration;
+#endif
+ /**
+ Context of the owner of the metadata lock ticket. Externally accessible.
+ */
+ MDL_context *m_ctx;
+
+ /**
+ Pointer to the lock object for this lock ticket. Externally accessible.
+ */
+ MDL_lock *m_lock;
+
+private:
+ MDL_ticket(const MDL_ticket &); /* not implemented */
+ MDL_ticket &operator=(const MDL_ticket &); /* not implemented */
+};
+
+
+/**
+ Savepoint for MDL context.
+
+ Doesn't include metadata locks with explicit duration as
+ they are not released during rollback to savepoint.
+*/
+
+class MDL_savepoint
+{
+public:
+ MDL_savepoint() {};
+
+private:
+ MDL_savepoint(MDL_ticket *stmt_ticket, MDL_ticket *trans_ticket)
+ : m_stmt_ticket(stmt_ticket), m_trans_ticket(trans_ticket)
+ {}
+
+ friend class MDL_context;
+
+private:
+ /**
+ Pointer to last lock with statement duration which was taken
+ before creation of savepoint.
+ */
+ MDL_ticket *m_stmt_ticket;
+ /**
+ Pointer to last lock with transaction duration which was taken
+ before creation of savepoint.
+ */
+ MDL_ticket *m_trans_ticket;
+};
+
+
+/**
+ A reliable way to wait on an MDL lock.
+*/
+
+class MDL_wait
+{
+public:
+ MDL_wait();
+ ~MDL_wait();
+
+ enum enum_wait_status { EMPTY = 0, GRANTED, VICTIM, TIMEOUT, KILLED };
+
+ bool set_status(enum_wait_status result_arg);
+ enum_wait_status get_status();
+ void reset_status();
+ enum_wait_status timed_wait(MDL_context_owner *owner,
+ struct timespec *abs_timeout,
+ bool signal_timeout,
+ const PSI_stage_info *wait_state_name);
+private:
+ /**
+ Condvar which is used for waiting until this context's pending
+ request can be satisfied or this thread has to perform actions
+ to resolve a potential deadlock (we subscribe to such
+ notification by adding a ticket corresponding to the request
+ to an appropriate queue of waiters).
+ */
+ mysql_mutex_t m_LOCK_wait_status;
+ mysql_cond_t m_COND_wait_status;
+ enum_wait_status m_wait_status;
+};
+
+
+typedef I_P_List<MDL_request, I_P_List_adapter<MDL_request,
+ &MDL_request::next_in_list,
+ &MDL_request::prev_in_list>,
+ I_P_List_counter>
+ MDL_request_list;
+
+/**
+ Context of the owner of metadata locks. I.e. each server
+ connection has such a context.
+*/
+
+class MDL_context
+{
+public:
+ typedef I_P_List<MDL_ticket,
+ I_P_List_adapter<MDL_ticket,
+ &MDL_ticket::next_in_context,
+ &MDL_ticket::prev_in_context> >
+ Ticket_list;
+
+ typedef Ticket_list::Iterator Ticket_iterator;
+
+ MDL_context();
+ void destroy();
+
+ bool try_acquire_lock(MDL_request *mdl_request);
+ bool acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout);
+ bool acquire_locks(MDL_request_list *requests, ulong lock_wait_timeout);
+ bool upgrade_shared_lock(MDL_ticket *mdl_ticket,
+ enum_mdl_type new_type,
+ ulong lock_wait_timeout);
+
+ bool clone_ticket(MDL_request *mdl_request);
+
+ void release_all_locks_for_name(MDL_ticket *ticket);
+ void release_lock(MDL_ticket *ticket);
+
+ bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
+ const char *db, const char *name,
+ enum_mdl_type mdl_type);
+ unsigned long get_lock_owner(MDL_key *mdl_key);
+
+ bool has_lock(const MDL_savepoint &mdl_savepoint, MDL_ticket *mdl_ticket);
+
+ inline bool has_locks() const
+ {
+ return !(m_tickets[MDL_STATEMENT].is_empty() &&
+ m_tickets[MDL_TRANSACTION].is_empty() &&
+ m_tickets[MDL_EXPLICIT].is_empty());
+ }
+
+ MDL_savepoint mdl_savepoint()
+ {
+ return MDL_savepoint(m_tickets[MDL_STATEMENT].front(),
+ m_tickets[MDL_TRANSACTION].front());
+ }
+
+ void set_explicit_duration_for_all_locks();
+ void set_transaction_duration_for_all_locks();
+ void set_lock_duration(MDL_ticket *mdl_ticket, enum_mdl_duration duration);
+
+ void release_statement_locks();
+ void release_transactional_locks();
+ void rollback_to_savepoint(const MDL_savepoint &mdl_savepoint);
+
+ MDL_context_owner *get_owner() { return m_owner; }
+
+ /** @pre Only valid if we started waiting for lock. */
+ inline uint get_deadlock_weight() const
+ { return m_waiting_for->get_deadlock_weight(); }
+ /**
+ Post signal to the context (and wake it up if necessary).
+
+ @retval FALSE - Success, signal was posted.
+ @retval TRUE - Failure, signal was not posted since context
+ already has received some signal or closed
+ signal slot.
+ */
+ void init(MDL_context_owner *arg) { m_owner= arg; }
+
+ void set_needs_thr_lock_abort(bool needs_thr_lock_abort)
+ {
+ /*
+ @note In theory, this member should be modified under protection
+ of some lock since it can be accessed from different threads.
+ In practice, this is not necessary as code which reads this
+ value and so might miss the fact that value was changed will
+ always re-try reading it after small timeout and therefore
+ will see the new value eventually.
+ */
+ m_needs_thr_lock_abort= needs_thr_lock_abort;
+ }
+ bool get_needs_thr_lock_abort() const
+ {
+ return m_needs_thr_lock_abort;
+ }
+public:
+ /**
+ If our request for a lock is scheduled, or aborted by the deadlock
+ detector, the result is recorded in this class.
+ */
+ MDL_wait m_wait;
+private:
+ /**
+ Lists of all MDL tickets acquired by this connection.
+
+ Lists of MDL tickets:
+ ---------------------
+ The entire set of locks acquired by a connection can be separated
+ in three subsets according to their duration: locks released at
+ the end of statement, at the end of transaction and locks are
+ released explicitly.
+
+ Statement and transactional locks are locks with automatic scope.
+ They are accumulated in the course of a transaction, and released
+ either at the end of uppermost statement (for statement locks) or
+ on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT (for transactional
+ locks). They must not be (and never are) released manually,
+ i.e. with release_lock() call.
+
+ Tickets with explicit duration are taken for locks that span
+ multiple transactions or savepoints.
+ These are: HANDLER SQL locks (HANDLER SQL is
+ transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc
+ under LOCK TABLES, and the locked tables stay locked), user level
+ locks (GET_LOCK()/RELEASE_LOCK() functions) and
+ locks implementing "global read lock".
+
+ Statement/transactional locks are always prepended to the
+ beginning of the appropriate list. In other words, they are
+ stored in reverse temporal order. Thus, when we rollback to
+ a savepoint, we start popping and releasing tickets from the
+ front until we reach the last ticket acquired after the savepoint.
+
+ Locks with explicit duration are not stored in any
+ particular order, and among each other can be split into
+ four sets:
+
+ [LOCK TABLES locks] [USER locks] [HANDLER locks] [GLOBAL READ LOCK locks]
+
+ The following is known about these sets:
+
+ * GLOBAL READ LOCK locks are always stored last.
+ This is because one can't say SET GLOBAL read_only=1 or
+ FLUSH TABLES WITH READ LOCK if one has locked tables. One can,
+ however, LOCK TABLES after having entered the read only mode.
+ Note, that subsequent LOCK TABLES statement will unlock the previous
+ set of tables, but not the GRL!
+ There are no HANDLER locks after GRL locks because
+ SET GLOBAL read_only performs a FLUSH TABLES WITH
+ READ LOCK internally, and FLUSH TABLES, in turn, implicitly
+ closes all open HANDLERs.
+ However, one can open a few HANDLERs after entering the
+ read only mode.
+ * LOCK TABLES locks include intention exclusive locks on
+ involved schemas and global intention exclusive lock.
+ */
+ Ticket_list m_tickets[MDL_DURATION_END];
+ MDL_context_owner *m_owner;
+ /**
+ TRUE - if for this context we will break protocol and try to
+ acquire table-level locks while having only S lock on
+ some table.
+ To avoid deadlocks which might occur during concurrent
+ upgrade of SNRW lock on such object to X lock we have to
+ abort waits for table-level locks for such connections.
+ FALSE - Otherwise.
+ */
+ bool m_needs_thr_lock_abort;
+
+ /**
+ Read-write lock protecting m_waiting_for member.
+
+ @note The fact that this read-write lock prefers readers is
+ important as deadlock detector won't work correctly
+ otherwise. @sa Comment for MDL_lock::m_rwlock.
+ */
+ mysql_prlock_t m_LOCK_waiting_for;
+ /**
+ Tell the deadlock detector what metadata lock or table
+ definition cache entry this session is waiting for.
+ In principle, this is redundant, as information can be found
+ by inspecting waiting queues, but we'd very much like it to be
+ readily available to the wait-for graph iterator.
+ */
+ MDL_wait_for_subgraph *m_waiting_for;
+private:
+ THD *get_thd() const { return m_owner->get_thd(); }
+ MDL_ticket *find_ticket(MDL_request *mdl_req,
+ enum_mdl_duration *duration);
+ void release_locks_stored_before(enum_mdl_duration duration, MDL_ticket *sentinel);
+ void release_lock(enum_mdl_duration duration, MDL_ticket *ticket);
+ bool try_acquire_lock_impl(MDL_request *mdl_request,
+ MDL_ticket **out_ticket);
+
+public:
+ void find_deadlock();
+
+ ulong get_thread_id() const { return thd_get_thread_id(get_thd()); }
+
+ bool visit_subgraph(MDL_wait_for_graph_visitor *dvisitor);
+
+ /** Inform the deadlock detector there is an edge in the wait-for graph. */
+ void will_wait_for(MDL_wait_for_subgraph *waiting_for_arg)
+ {
+ mysql_prlock_wrlock(&m_LOCK_waiting_for);
+ m_waiting_for= waiting_for_arg;
+ mysql_prlock_unlock(&m_LOCK_waiting_for);
+ }
+
+ /** Remove the wait-for edge from the graph after we're done waiting. */
+ void done_waiting_for()
+ {
+ mysql_prlock_wrlock(&m_LOCK_waiting_for);
+ m_waiting_for= NULL;
+ mysql_prlock_unlock(&m_LOCK_waiting_for);
+ }
+ void lock_deadlock_victim()
+ {
+ mysql_prlock_rdlock(&m_LOCK_waiting_for);
+ }
+ void unlock_deadlock_victim()
+ {
+ mysql_prlock_unlock(&m_LOCK_waiting_for);
+ }
+private:
+ MDL_context(const MDL_context &rhs); /* not implemented */
+ MDL_context &operator=(MDL_context &rhs); /* not implemented */
+
+ /* metadata_lock_info plugin */
+ friend int i_s_metadata_lock_info_fill_row(MDL_ticket*, void*);
+};
+
+
+void mdl_init();
+void mdl_destroy();
+
+extern "C" unsigned long thd_get_thread_id(const MYSQL_THD thd);
+
+/**
+ Check if a connection in question is no longer connected.
+
+ @details
+ Replication apply thread is always connected. Otherwise,
+ does a poll on the associated socket to check if the client
+ is gone.
+*/
+extern "C" int thd_is_connected(MYSQL_THD thd);
+
+
+/*
+ Start-up parameter for the maximum size of the unused MDL_lock objects cache
+ and a constant for its default value.
+*/
+extern ulong mdl_locks_cache_size;
+static const ulong MDL_LOCKS_CACHE_SIZE_DEFAULT = 1024;
+
+/*
+ Start-up parameter for the number of partitions of the hash
+ containing all the MDL_lock objects and a constant for
+ its default value.
+*/
+extern ulong mdl_locks_hash_partitions;
+static const ulong MDL_LOCKS_HASH_PARTITIONS_DEFAULT = 8;
+
+/*
+ Metadata locking subsystem tries not to grant more than
+ max_write_lock_count high-prio, strong locks successively,
+ to avoid starving out weak, low-prio locks.
+*/
+extern "C" ulong max_write_lock_count;
+
+extern MYSQL_PLUGIN_IMPORT
+int mdl_iterate(int (*callback)(MDL_ticket *ticket, void *arg), void *arg);
+#endif
diff --git a/sql/mem_root_array.h b/sql/mem_root_array.h
new file mode 100644
index 00000000000..2dcc475cd7b
--- /dev/null
+++ b/sql/mem_root_array.h
@@ -0,0 +1,175 @@
+/* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+#ifndef MEM_ROOT_ARRAY_INCLUDED
+#define MEM_ROOT_ARRAY_INCLUDED
+
+#include <my_alloc.h>
+
+/**
+ A typesafe replacement for DYNAMIC_ARRAY.
+ We use MEM_ROOT for allocating storage, rather than the C++ heap.
+ The interface is chosen to be similar to std::vector.
+
+ @remark
+ Unlike DYNAMIC_ARRAY, elements are properly copied
+ (rather than memcpy()d) if the underlying array needs to be expanded.
+
+ @remark
+ Depending on has_trivial_destructor, we destroy objects which are
+ removed from the array (including when the array object itself is destroyed).
+
+ @remark
+ Note that MEM_ROOT has no facility for reusing free space,
+ so don't use this if multiple re-expansions are likely to happen.
+
+ @param Element_type The type of the elements of the container.
+ Elements must be copyable.
+ @param has_trivial_destructor If true, we don't destroy elements.
+ We could have used type traits to determine this.
+ __has_trivial_destructor is supported by some (but not all)
+ compilers we use.
+*/
+template<typename Element_type, bool has_trivial_destructor>
+class Mem_root_array
+{
+public:
+ Mem_root_array(MEM_ROOT *root)
+ : m_root(root), m_array(NULL), m_size(0), m_capacity(0)
+ {
+ DBUG_ASSERT(m_root != NULL);
+ }
+
+ ~Mem_root_array()
+ {
+ clear();
+ }
+
+ Element_type &at(size_t n)
+ {
+ DBUG_ASSERT(n < size());
+ return m_array[n];
+ }
+
+ const Element_type &at(size_t n) const
+ {
+ DBUG_ASSERT(n < size());
+ return m_array[n];
+ }
+
+ // Returns a pointer to the first element in the array.
+ Element_type *begin() { return &m_array[0]; }
+
+ // Returns a pointer to the past-the-end element in the array.
+ Element_type *end() { return &m_array[size()]; }
+
+ // Erases all of the elements.
+ void clear()
+ {
+ if (!empty())
+ chop(0);
+ }
+
+ /*
+ Chops the tail off the array, erasing all tail elements.
+ @param pos Index of first element to erase.
+ */
+ void chop(const size_t pos)
+ {
+ DBUG_ASSERT(pos < m_size);
+ if (!has_trivial_destructor)
+ {
+ for (size_t ix= pos; ix < m_size; ++ix)
+ {
+ Element_type *p= &m_array[ix];
+ p->~Element_type(); // Destroy discarded element.
+ }
+ }
+ m_size= pos;
+ }
+
+ /*
+ Reserves space for array elements.
+ Copies over existing elements, in case we are re-expanding the array.
+
+ @param n number of elements.
+ @retval true if out-of-memory, false otherwise.
+ */
+ bool reserve(size_t n)
+ {
+ if (n <= m_capacity)
+ return false;
+
+ void *mem= alloc_root(m_root, n * element_size());
+ if (!mem)
+ return true;
+ Element_type *array= static_cast<Element_type*>(mem);
+
+ // Copy all the existing elements into the new array.
+ for (size_t ix= 0; ix < m_size; ++ix)
+ {
+ Element_type *new_p= &array[ix];
+ Element_type *old_p= &m_array[ix];
+ new (new_p) Element_type(*old_p); // Copy into new location.
+ if (!has_trivial_destructor)
+ old_p->~Element_type(); // Destroy the old element.
+ }
+
+ // Forget the old array.
+ m_array= array;
+ m_capacity= n;
+ return false;
+ }
+
+ /*
+ Adds a new element at the end of the array, after its current last
+ element. The content of this new element is initialized to a copy of
+ the input argument.
+
+ @param element Object to copy.
+ @retval true if out-of-memory, false otherwise.
+ */
+ bool push_back(const Element_type &element)
+ {
+ const size_t min_capacity= 20;
+ const size_t expansion_factor= 2;
+ if (0 == m_capacity && reserve(min_capacity))
+ return true;
+ if (m_size == m_capacity && reserve(m_capacity * expansion_factor))
+ return true;
+ Element_type *p= &m_array[m_size++];
+ new (p) Element_type(element);
+ return false;
+ }
+
+ size_t capacity() const { return m_capacity; }
+ size_t element_size() const { return sizeof(Element_type); }
+ bool empty() const { return size() == 0; }
+ size_t size() const { return m_size; }
+
+private:
+ MEM_ROOT *const m_root;
+ Element_type *m_array;
+ size_t m_size;
+ size_t m_capacity;
+
+ // Not (yet) implemented.
+ Mem_root_array(const Mem_root_array&);
+ Mem_root_array &operator=(const Mem_root_array&);
+};
+
+
+#endif // MEM_ROOT_ARRAY_INCLUDED
diff --git a/sql/message.h b/sql/message.h
new file mode 100644
index 00000000000..6641453a965
--- /dev/null
+++ b/sql/message.h
@@ -0,0 +1,77 @@
+#ifndef MESSAGE_INCLUDED
+#define MESSAGE_INCLUDED
+/* Copyright (c) 2008, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+/*
+ To change or add messages mysqld writes to the Windows error log, run
+ mc.exe message.mc
+ and checkin generated messages.h, messages.rc and msg000001.bin under the
+ source control.
+ mc.exe can be installed with Windows SDK, some Visual Studio distributions
+ do not include it.
+*/
+
+
+//
+// Values are 32 bit values layed out as follows:
+//
+// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
+// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+// +---+-+-+-----------------------+-------------------------------+
+// |Sev|C|R| Facility | Code |
+// +---+-+-+-----------------------+-------------------------------+
+//
+// where
+//
+// Sev - is the severity code
+//
+// 00 - Success
+// 01 - Informational
+// 10 - Warning
+// 11 - Error
+//
+// C - is the Customer code flag
+//
+// R - is a reserved bit
+//
+// Facility - is the facility code
+//
+// Code - is the facility's status code
+//
+//
+// Define the facility codes
+//
+
+
+//
+// Define the severity codes
+//
+
+
+//
+// MessageId: MSG_DEFAULT
+//
+// MessageText:
+//
+// %1For more information, see Help and Support Center at http://www.mysql.com.
+//
+//
+//
+#define MSG_DEFAULT 0xC0000064L
+
+#endif /* MESSAGE_INCLUDED */
+
diff --git a/sql/message.mc b/sql/message.mc
new file mode 100644
index 00000000000..8d68d599365
--- /dev/null
+++ b/sql/message.mc
@@ -0,0 +1,16 @@
+;/*
+; To change or add messages mysqld writes to the Windows error log, run
+; mc.exe message.mc
+; and checkin generated messages.h, messages.rc and msg000001.bin under the
+; source control.
+; mc.exe can be installed with Windows SDK, some Visual Studio distributions
+; do not include it.
+;*/
+MessageId = 100
+Severity = Error
+Facility = Application
+SymbolicName = MSG_DEFAULT
+Language = English
+%1For more information, see Help and Support Center at http://www.mysql.com.
+
+
diff --git a/sql/message.rc b/sql/message.rc
new file mode 100644
index 00000000000..116522b7d48
--- /dev/null
+++ b/sql/message.rc
@@ -0,0 +1,2 @@
+LANGUAGE 0x9,0x1
+1 11 MSG00001.bin
diff --git a/sql/mf_iocache.cc b/sql/mf_iocache.cc
new file mode 100644
index 00000000000..6535f16445b
--- /dev/null
+++ b/sql/mf_iocache.cc
@@ -0,0 +1,98 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/**
+ @file
+
+ @details
+ Caching of files with only does (sequential) read or writes of fixed-
+ length records. A read isn't allowed to go over file-length. A read is ok
+ if it ends at file-length and next read can try to read after file-length
+ (and get a EOF-error).
+ Possibly use of asyncronic io.
+ macros for read and writes for faster io.
+ Used instead of FILE when reading or writing whole files.
+ This will make mf_rec_cache obsolete.
+ One can change info->pos_in_file to a higher value to skip bytes in file if
+ also info->rc_pos is set to info->rc_end.
+ If called through open_cached_file(), then the temporary file will
+ only be created if a write exeeds the file buffer or if one calls
+ flush_io_cache().
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_class.h" // THD
+#ifdef HAVE_REPLICATION
+
+extern "C" {
+
+/**
+ Read buffered from the net.
+
+ @retval
+ 1 if can't read requested characters
+ @retval
+ 0 if record read
+*/
+
+
+int _my_b_net_read(register IO_CACHE *info, uchar *Buffer,
+ size_t Count __attribute__((unused)))
+{
+ ulong read_length;
+ NET *net= &(current_thd)->net;
+ DBUG_ENTER("_my_b_net_read");
+
+ if (!info->end_of_file)
+ DBUG_RETURN(1); /* because my_b_get (no _) takes 1 byte at a time */
+ read_length= my_net_read_packet(net, 0);
+ if (read_length == packet_error)
+ {
+ info->error= -1;
+ DBUG_RETURN(1);
+ }
+ if (read_length == 0)
+ {
+ info->end_of_file= 0; /* End of file from client */
+ DBUG_RETURN(1);
+ }
+ /* to set up stuff for my_b_get (no _) */
+ info->read_end = (info->read_pos = (uchar*) net->read_pos) + read_length;
+ Buffer[0] = info->read_pos[0]; /* length is always 1 */
+
+ /*
+ info->request_pos is used by log_loaded_block() to know the size
+ of the current block.
+ info->pos_in_file is used by log_loaded_block() too.
+ */
+ info->pos_in_file+= read_length;
+ info->request_pos=info->read_pos;
+
+ info->read_pos++;
+
+ DBUG_RETURN(0);
+}
+
+} /* extern "C" */
+
+#elif defined(__WIN__)
+
+// Remove linker warning 4221 about empty file
+namespace { char dummy; };
+
+#endif /* HAVE_REPLICATION */
+
+
diff --git a/sql/multi_range_read.cc b/sql/multi_range_read.cc
new file mode 100644
index 00000000000..3f55ff3684d
--- /dev/null
+++ b/sql/multi_range_read.cc
@@ -0,0 +1,1870 @@
+/* Copyright (C) 2010, 2011 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include "sql_parse.h"
+#include <my_bit.h>
+#include "sql_select.h"
+#include "key.h"
+
+/****************************************************************************
+ * Default MRR implementation (MRR to non-MRR converter)
+ ***************************************************************************/
+
+/**
+ Get cost and other information about MRR scan over a known list of ranges
+
+ Calculate estimated cost and other information about an MRR scan for given
+ sequence of ranges.
+
+ @param keyno Index number
+ @param seq Range sequence to be traversed
+ @param seq_init_param First parameter for seq->init()
+ @param n_ranges_arg Number of ranges in the sequence, or 0 if the caller
+ can't efficiently determine it
+ @param bufsz INOUT IN: Size of the buffer available for use
+ OUT: Size of the buffer that is expected to be actually
+ used, or 0 if buffer is not needed.
+ @param flags INOUT A combination of HA_MRR_* flags
+ @param cost OUT Estimated cost of MRR access
+
+ @note
+ This method (or an overriding one in a derived class) must check for
+ thd->killed and return HA_POS_ERROR if it is not zero. This is required
+ for a user to be able to interrupt the calculation by killing the
+ connection/query.
+
+ @retval
+ HA_POS_ERROR Error or the engine is unable to perform the requested
+ scan. Values of OUT parameters are undefined.
+ @retval
+ other OK, *cost contains cost of the scan, *bufsz and *flags
+ contain scan parameters.
+*/
+
+ha_rows
+handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq,
+ void *seq_init_param, uint n_ranges_arg,
+ uint *bufsz, uint *flags, Cost_estimate *cost)
+{
+ KEY_MULTI_RANGE range;
+ range_seq_t seq_it;
+ ha_rows rows, total_rows= 0;
+ uint n_ranges=0;
+ THD *thd= current_thd;
+
+ /* Default MRR implementation doesn't need buffer */
+ *bufsz= 0;
+
+ seq_it= seq->init(seq_init_param, n_ranges, *flags);
+ while (!seq->next(seq_it, &range))
+ {
+ if (unlikely(thd->killed != 0))
+ return HA_POS_ERROR;
+
+ n_ranges++;
+ key_range *min_endp, *max_endp;
+ if (range.range_flag & GEOM_FLAG)
+ {
+ /* In this case tmp_min_flag contains the handler-read-function */
+ range.start_key.flag= (ha_rkey_function) (range.range_flag ^ GEOM_FLAG);
+ min_endp= &range.start_key;
+ max_endp= NULL;
+ }
+ else
+ {
+ min_endp= range.start_key.length? &range.start_key : NULL;
+ max_endp= range.end_key.length? &range.end_key : NULL;
+ }
+ if ((range.range_flag & UNIQUE_RANGE) && !(range.range_flag & NULL_RANGE))
+ rows= 1; /* there can be at most one row */
+ else
+ {
+ if (HA_POS_ERROR == (rows= this->records_in_range(keyno, min_endp,
+ max_endp)))
+ {
+ /* Can't scan one range => can't do MRR scan at all */
+ total_rows= HA_POS_ERROR;
+ break;
+ }
+ }
+ total_rows += rows;
+ }
+
+ if (total_rows != HA_POS_ERROR)
+ {
+ /* The following calculation is the same as in multi_range_read_info(): */
+ *flags |= HA_MRR_USE_DEFAULT_IMPL;
+ cost->reset();
+ cost->avg_io_cost= 1; /* assume random seeks */
+ if ((*flags & HA_MRR_INDEX_ONLY) && total_rows > 2)
+ cost->io_count= keyread_time(keyno, n_ranges, (uint)total_rows);
+ else
+ cost->io_count= read_time(keyno, n_ranges, total_rows);
+ cost->cpu_cost= (double) total_rows / TIME_FOR_COMPARE + 0.01;
+ }
+ return total_rows;
+}
+
+
+/**
+ Get cost and other information about MRR scan over some sequence of ranges
+
+ Calculate estimated cost and other information about an MRR scan for some
+ sequence of ranges.
+
+ The ranges themselves will be known only at execution phase. When this
+ function is called we only know number of ranges and a (rough) E(#records)
+ within those ranges.
+
+ Currently this function is only called for "n-keypart singlepoint" ranges,
+ i.e. each range is "keypart1=someconst1 AND ... AND keypartN=someconstN"
+
+ The flags parameter is a combination of those flags: HA_MRR_SORTED,
+ HA_MRR_INDEX_ONLY, HA_MRR_NO_ASSOCIATION, HA_MRR_LIMITS.
+
+ @param keyno Index number
+ @param n_ranges Estimated number of ranges (i.e. intervals) in the
+ range sequence.
+ @param n_rows Estimated total number of records contained within all
+ of the ranges
+ @param bufsz INOUT IN: Size of the buffer available for use
+ OUT: Size of the buffer that will be actually used, or
+ 0 if buffer is not needed.
+ @param flags INOUT A combination of HA_MRR_* flags
+ @param cost OUT Estimated cost of MRR access
+
+ @retval
+ 0 OK, *cost contains cost of the scan, *bufsz and *flags contain scan
+ parameters.
+ @retval
+ other Error or can't perform the requested scan
+*/
+
+ha_rows handler::multi_range_read_info(uint keyno, uint n_ranges, uint n_rows,
+ uint key_parts, uint *bufsz,
+ uint *flags, Cost_estimate *cost)
+{
+ /*
+ Currently we expect this function to be called only in preparation of scan
+ with HA_MRR_SINGLE_POINT property.
+ */
+ DBUG_ASSERT(*flags | HA_MRR_SINGLE_POINT);
+
+ *bufsz= 0; /* Default implementation doesn't need a buffer */
+ *flags |= HA_MRR_USE_DEFAULT_IMPL;
+
+ cost->reset();
+ cost->avg_io_cost= 1; /* assume random seeks */
+
+ /* Produce the same cost as non-MRR code does */
+ if (*flags & HA_MRR_INDEX_ONLY)
+ cost->io_count= keyread_time(keyno, n_ranges, n_rows);
+ else
+ cost->io_count= read_time(keyno, n_ranges, n_rows);
+ return 0;
+}
+
+
+/**
+ Initialize the MRR scan
+
+ Initialize the MRR scan. This function may do heavyweight scan
+ initialization like row prefetching/sorting/etc (NOTE: but better not do
+ it here as we may not need it, e.g. if we never satisfy WHERE clause on
+ previous tables. For many implementations it would be natural to do such
+ initializations in the first multi_read_range_next() call)
+
+ mode is a combination of the following flags: HA_MRR_SORTED,
+ HA_MRR_INDEX_ONLY, HA_MRR_NO_ASSOCIATION
+
+ @param seq Range sequence to be traversed
+ @param seq_init_param First parameter for seq->init()
+ @param n_ranges Number of ranges in the sequence
+ @param mode Flags, see the description section for the details
+ @param buf INOUT: memory buffer to be used
+
+ @note
+ One must have called index_init() before calling this function. Several
+ multi_range_read_init() calls may be made in course of one query.
+
+ Until WL#2623 is done (see its text, section 3.2), the following will
+ also hold:
+ The caller will guarantee that if "seq->init == mrr_ranges_array_init"
+ then seq_init_param is an array of n_ranges KEY_MULTI_RANGE structures.
+ This property will only be used by NDB handler until WL#2623 is done.
+
+ Buffer memory management is done according to the following scenario:
+ The caller allocates the buffer and provides it to the callee by filling
+ the members of HANDLER_BUFFER structure.
+ The callee consumes all or some fraction of the provided buffer space, and
+ sets the HANDLER_BUFFER members accordingly.
+ The callee may use the buffer memory until the next multi_range_read_init()
+ call is made, all records have been read, or until index_end() call is
+ made, whichever comes first.
+
+ @retval 0 OK
+ @retval 1 Error
+*/
+
+int
+handler::multi_range_read_init(RANGE_SEQ_IF *seq_funcs, void *seq_init_param,
+ uint n_ranges, uint mode, HANDLER_BUFFER *buf)
+{
+ DBUG_ENTER("handler::multi_range_read_init");
+ mrr_iter= seq_funcs->init(seq_init_param, n_ranges, mode);
+ mrr_funcs= *seq_funcs;
+ mrr_is_output_sorted= MY_TEST(mode & HA_MRR_SORTED);
+ mrr_have_range= FALSE;
+ DBUG_RETURN(0);
+}
+
+/**
+ Get next record in MRR scan
+
+ Default MRR implementation: read the next record
+
+ @param range_info OUT Undefined if HA_MRR_NO_ASSOCIATION flag is in effect
+ Otherwise, the opaque value associated with the range
+ that contains the returned record.
+
+ @retval 0 OK
+ @retval other Error code
+*/
+
+int handler::multi_range_read_next(range_id_t *range_info)
+{
+ int result= HA_ERR_END_OF_FILE;
+ bool range_res;
+ DBUG_ENTER("handler::multi_range_read_next");
+
+ if (!mrr_have_range)
+ {
+ mrr_have_range= TRUE;
+ goto start;
+ }
+
+ do
+ {
+ /* Save a call if there can be only one row in range. */
+ if (mrr_cur_range.range_flag != (UNIQUE_RANGE | EQ_RANGE))
+ {
+ result= read_range_next();
+ /* On success or non-EOF errors jump to the end. */
+ if (result != HA_ERR_END_OF_FILE)
+ break;
+ }
+ else
+ {
+ if (was_semi_consistent_read())
+ {
+ /*
+ The following assignment is redundant, but for extra safety and to
+ remove the compiler warning:
+ */
+ range_res= FALSE;
+ goto scan_it_again;
+ }
+ /*
+ We need to set this for the last range only, but checking this
+ condition is more expensive than just setting the result code.
+ */
+ result= HA_ERR_END_OF_FILE;
+ }
+
+start:
+ /* Try the next range(s) until one matches a record. */
+ while (!(range_res= mrr_funcs.next(mrr_iter, &mrr_cur_range)))
+ {
+scan_it_again:
+ result= read_range_first(mrr_cur_range.start_key.keypart_map ?
+ &mrr_cur_range.start_key : 0,
+ mrr_cur_range.end_key.keypart_map ?
+ &mrr_cur_range.end_key : 0,
+ MY_TEST(mrr_cur_range.range_flag & EQ_RANGE),
+ mrr_is_output_sorted);
+ if (result != HA_ERR_END_OF_FILE)
+ break;
+ }
+ }
+ while ((result == HA_ERR_END_OF_FILE) && !range_res);
+
+ *range_info= mrr_cur_range.ptr;
+ DBUG_PRINT("exit",("handler::multi_range_read_next result %d", result));
+ DBUG_RETURN(result);
+}
+
+/****************************************************************************
+ * Mrr_*_reader classes (building blocks for DS-MRR)
+ ***************************************************************************/
+
+int Mrr_simple_index_reader::init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
+ void *seq_init_param, uint n_ranges,
+ uint mode, Key_parameters *key_par_arg,
+ Lifo_buffer *key_buffer_arg,
+ Buffer_manager *buf_manager_arg)
+{
+ HANDLER_BUFFER no_buffer = {NULL, NULL, NULL};
+ file= h_arg;
+ return file->handler::multi_range_read_init(seq_funcs, seq_init_param,
+ n_ranges, mode, &no_buffer);
+}
+
+
+int Mrr_simple_index_reader::get_next(range_id_t *range_info)
+{
+ int res;
+ while (!(res= file->handler::multi_range_read_next(range_info)))
+ {
+ KEY_MULTI_RANGE *curr_range= &file->handler::mrr_cur_range;
+ if (!file->mrr_funcs.skip_index_tuple ||
+ !file->mrr_funcs.skip_index_tuple(file->mrr_iter, curr_range->ptr))
+ break;
+ }
+ if (res && res != HA_ERR_END_OF_FILE && res != HA_ERR_KEY_NOT_FOUND)
+ file->print_error(res, MYF(0)); // Fatal error
+ return res;
+}
+
+
+/**
+ @brief Get next index record
+
+ @param range_info OUT identifier of range that the returned record belongs to
+
+ @note
+ We actually iterate over nested sequences:
+ - an ordered sequence of groups of identical keys
+ - each key group has key value, which has multiple matching records
+ - thus, each record matches all members of the key group
+
+ @retval 0 OK, next record was successfully read
+ @retval HA_ERR_END_OF_FILE End of records
+ @retval Other Some other error; Error is printed
+*/
+
+int Mrr_ordered_index_reader::get_next(range_id_t *range_info)
+{
+ int res;
+ DBUG_ENTER("Mrr_ordered_index_reader::get_next");
+
+ for(;;)
+ {
+ if (!scanning_key_val_iter)
+ {
+ while ((res= kv_it.init(this)))
+ {
+ if ((res != HA_ERR_KEY_NOT_FOUND && res != HA_ERR_END_OF_FILE))
+ DBUG_RETURN(res); /* Some fatal error */
+
+ if (key_buffer->is_empty())
+ {
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ }
+ scanning_key_val_iter= TRUE;
+ }
+
+ if ((res= kv_it.get_next(range_info)))
+ {
+ scanning_key_val_iter= FALSE;
+ if ((res != HA_ERR_KEY_NOT_FOUND && res != HA_ERR_END_OF_FILE))
+ DBUG_RETURN(res);
+ kv_it.move_to_next_key_value();
+ continue;
+ }
+ if (!skip_index_tuple(*range_info) &&
+ !skip_record(*range_info, NULL))
+ {
+ break;
+ }
+ /* Go get another (record, range_id) combination */
+ } /* while */
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Supply index reader with the O(1)space it needs for scan interrupt/restore
+ operation
+*/
+
+bool Mrr_ordered_index_reader::set_interruption_temp_buffer(uint rowid_length,
+ uint key_len,
+ uint saved_pk_len,
+ uchar **space_start,
+ uchar *space_end)
+{
+ if (space_end - *space_start <= (ptrdiff_t)(rowid_length + key_len + saved_pk_len))
+ return TRUE;
+ support_scan_interruptions= TRUE;
+
+ saved_rowid= *space_start;
+ *space_start += rowid_length;
+
+ if (saved_pk_len)
+ {
+ saved_primary_key= *space_start;
+ *space_start += saved_pk_len;
+ }
+ else
+ saved_primary_key= NULL;
+
+ saved_key_tuple= *space_start;
+ *space_start += key_len;
+
+ have_saved_rowid= FALSE;
+ read_was_interrupted= FALSE;
+ return FALSE;
+}
+
+void Mrr_ordered_index_reader::set_no_interruption_temp_buffer()
+{
+ support_scan_interruptions= FALSE;
+ saved_key_tuple= saved_rowid= saved_primary_key= NULL; /* safety */
+ have_saved_rowid= FALSE;
+ read_was_interrupted= FALSE;
+}
+
+void Mrr_ordered_index_reader::interrupt_read()
+{
+ DBUG_ASSERT(support_scan_interruptions);
+ TABLE *table= file->get_table();
+ KEY *used_index= &table->key_info[file->active_index];
+ /* Save the current key value */
+ key_copy(saved_key_tuple, table->record[0],
+ used_index, used_index->key_length);
+
+ if (saved_primary_key)
+ {
+ key_copy(saved_primary_key, table->record[0],
+ &table->key_info[table->s->primary_key],
+ table->key_info[table->s->primary_key].key_length);
+ }
+ read_was_interrupted= TRUE;
+
+ /* Save the last rowid */
+ memcpy(saved_rowid, file->ref, file->ref_length);
+ have_saved_rowid= TRUE;
+}
+
+void Mrr_ordered_index_reader::position()
+{
+ if (have_saved_rowid)
+ memcpy(file->ref, saved_rowid, file->ref_length);
+ else
+ Mrr_index_reader::position();
+}
+
+void Mrr_ordered_index_reader::resume_read()
+{
+ TABLE *table= file->get_table();
+
+ if (!read_was_interrupted)
+ return;
+
+ KEY *used_index= &table->key_info[file->active_index];
+ key_restore(table->record[0], saved_key_tuple,
+ used_index, used_index->key_length);
+ if (saved_primary_key)
+ {
+ key_restore(table->record[0], saved_primary_key,
+ &table->key_info[table->s->primary_key],
+ table->key_info[table->s->primary_key].key_length);
+ }
+}
+
+
+/**
+ Fill the buffer with (lookup_tuple, range_id) pairs and sort
+
+ @return
+ 0 OK, the buffer is non-empty and sorted
+ HA_ERR_END_OF_FILE Source exhausted, the buffer is empty.
+*/
+
+int Mrr_ordered_index_reader::refill_buffer(bool initial)
+{
+ KEY_MULTI_RANGE cur_range;
+ DBUG_ENTER("Mrr_ordered_index_reader::refill_buffer");
+
+ DBUG_ASSERT(key_buffer->is_empty());
+
+ if (source_exhausted)
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+
+ buf_manager->reset_buffer_sizes(buf_manager->arg);
+ key_buffer->reset();
+ key_buffer->setup_writing(keypar.key_size_in_keybuf,
+ is_mrr_assoc? sizeof(range_id_t) : 0);
+
+ while (key_buffer->can_write() &&
+ !(source_exhausted= mrr_funcs.next(mrr_iter, &cur_range)))
+ {
+ DBUG_ASSERT(cur_range.range_flag & EQ_RANGE);
+
+ /* Put key, or {key, range_id} pair into the buffer */
+ key_buffer->write_ptr1= keypar.use_key_pointers ?
+ (uchar*)&cur_range.start_key.key :
+ (uchar*)cur_range.start_key.key;
+ key_buffer->write_ptr2= (uchar*)&cur_range.ptr;
+ key_buffer->write();
+ }
+
+ /* Force get_next() to start with kv_it.init() call: */
+ scanning_key_val_iter= FALSE;
+
+ if (source_exhausted && key_buffer->is_empty())
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+
+ if (!initial)
+ {
+ /* This is a non-initial buffer fill and we've got a non-empty buffer */
+ THD *thd= current_thd;
+ status_var_increment(thd->status_var.ha_mrr_key_refills_count);
+ }
+
+ key_buffer->sort((key_buffer->type() == Lifo_buffer::FORWARD)?
+ (qsort2_cmp)Mrr_ordered_index_reader::compare_keys_reverse :
+ (qsort2_cmp)Mrr_ordered_index_reader::compare_keys,
+ this);
+ DBUG_RETURN(0);
+}
+
+
+int Mrr_ordered_index_reader::init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
+ void *seq_init_param, uint n_ranges,
+ uint mode, Key_parameters *key_par_arg,
+ Lifo_buffer *key_buffer_arg,
+ Buffer_manager *buf_manager_arg)
+{
+ file= h_arg;
+ key_buffer= key_buffer_arg;
+ buf_manager= buf_manager_arg;
+ keypar= *key_par_arg;
+
+ KEY *key_info= &file->get_table()->key_info[file->active_index];
+ keypar.index_ranges_unique= MY_TEST(key_info->flags & HA_NOSAME &&
+ key_info->user_defined_key_parts ==
+ my_count_bits(keypar.key_tuple_map));
+
+ mrr_iter= seq_funcs->init(seq_init_param, n_ranges, mode);
+ is_mrr_assoc= !MY_TEST(mode & HA_MRR_NO_ASSOCIATION);
+ mrr_funcs= *seq_funcs;
+ source_exhausted= FALSE;
+ read_was_interrupted= false;
+ have_saved_rowid= FALSE;
+ return 0;
+}
+
+
+static int rowid_cmp_reverse(void *file, uchar *a, uchar *b)
+{
+ return - ((handler*)file)->cmp_ref(a, b);
+}
+
+
+int Mrr_ordered_rndpos_reader::init(handler *h_arg,
+ Mrr_index_reader *index_reader_arg,
+ uint mode,
+ Lifo_buffer *buf)
+{
+ file= h_arg;
+ index_reader= index_reader_arg;
+ rowid_buffer= buf;
+ is_mrr_assoc= !MY_TEST(mode & HA_MRR_NO_ASSOCIATION);
+ index_reader_exhausted= FALSE;
+ index_reader_needs_refill= TRUE;
+ return 0;
+}
+
+
+/**
+ DS-MRR: Fill and sort the rowid buffer
+
+ Scan the MRR ranges and collect ROWIDs (or {ROWID, range_id} pairs) into
+ buffer. When the buffer is full or scan is completed, sort the buffer by
+ rowid and return.
+
+ When this function returns, either rowid buffer is not empty, or the source
+ of lookup keys (i.e. ranges) is exhaused.
+
+ @retval 0 OK, the next portion of rowids is in the buffer,
+ properly ordered
+ @retval other Error
+*/
+
+int Mrr_ordered_rndpos_reader::refill_buffer(bool initial)
+{
+ int res;
+ bool first_call= initial;
+ DBUG_ENTER("Mrr_ordered_rndpos_reader::refill_buffer");
+
+ if (index_reader_exhausted)
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+
+ while (initial || index_reader_needs_refill ||
+ (res= refill_from_index_reader()) == HA_ERR_END_OF_FILE)
+ {
+ if ((res= index_reader->refill_buffer(initial)))
+ {
+ if (res == HA_ERR_END_OF_FILE)
+ index_reader_exhausted= TRUE;
+ break;
+ }
+ initial= FALSE;
+ index_reader_needs_refill= FALSE;
+ }
+
+ if (!first_call && !index_reader_exhausted)
+ {
+ /* Ok, this was a successful buffer refill operation */
+ THD *thd= current_thd;
+ status_var_increment(thd->status_var.ha_mrr_rowid_refills_count);
+ }
+
+ DBUG_RETURN(res);
+}
+
+
+void Mrr_index_reader::position()
+{
+ file->position(file->get_table()->record[0]);
+}
+
+
+/*
+ @brief Try to refill the rowid buffer without calling
+ index_reader->refill_buffer().
+*/
+
+int Mrr_ordered_rndpos_reader::refill_from_index_reader()
+{
+ range_id_t range_info;
+ int res;
+ DBUG_ENTER("Mrr_ordered_rndpos_reader::refill_from_index_reader");
+
+ DBUG_ASSERT(rowid_buffer->is_empty());
+ index_rowid= index_reader->get_rowid_ptr();
+ rowid_buffer->reset();
+ rowid_buffer->setup_writing(file->ref_length,
+ is_mrr_assoc? sizeof(range_id_t) : 0);
+
+ last_identical_rowid= NULL;
+
+ index_reader->resume_read();
+ while (rowid_buffer->can_write())
+ {
+ res= index_reader->get_next(&range_info);
+
+ if (res)
+ {
+ if (res != HA_ERR_END_OF_FILE)
+ DBUG_RETURN(res);
+ index_reader_needs_refill=TRUE;
+ break;
+ }
+
+ index_reader->position();
+
+ /* Put rowid, or {rowid, range_id} pair into the buffer */
+ rowid_buffer->write_ptr1= index_rowid;
+ rowid_buffer->write_ptr2= (uchar*)&range_info;
+ rowid_buffer->write();
+ }
+
+ /*
+ When index_reader_needs_refill=TRUE, this means we've got all of index
+ tuples for lookups keys that index_reader had. We are not in the middle
+ of an index read, so there is no need to call interrupt_read.
+
+ Actually, we must not call interrupt_read(), because it could be that we
+ haven't read a single row (because all index lookups returned
+ HA_ERR_KEY_NOT_FOUND). In this case, interrupt_read() will cause [harmless]
+ valgrind warnings when trying to save garbage from table->record[0].
+ */
+ if (!index_reader_needs_refill)
+ index_reader->interrupt_read();
+ /* Sort the buffer contents by rowid */
+ rowid_buffer->sort((qsort2_cmp)rowid_cmp_reverse, (void*)file);
+
+ rowid_buffer->setup_reading(file->ref_length,
+ is_mrr_assoc ? sizeof(range_id_t) : 0);
+ DBUG_RETURN(rowid_buffer->is_empty()? HA_ERR_END_OF_FILE : 0);
+}
+
+
+/*
+ Get the next {record, range_id} using ordered array of rowid+range_id pairs
+
+ @note
+ Since we have sorted rowids, we try not to make multiple rnd_pos() calls
+ with the same rowid value.
+*/
+
+int Mrr_ordered_rndpos_reader::get_next(range_id_t *range_info)
+{
+ int res;
+
+ /*
+ First, check if rowid buffer has elements with the same rowid value as
+ the previous.
+ */
+ while (last_identical_rowid)
+ {
+ /*
+ Current record (the one we've returned in previous call) was obtained
+ from a rowid that matched multiple range_ids. Return this record again,
+ with next matching range_id.
+ */
+ (void)rowid_buffer->read();
+
+ if (rowid_buffer->read_ptr1 == last_identical_rowid)
+ last_identical_rowid= NULL; /* reached the last of identical rowids */
+
+ if (!is_mrr_assoc)
+ return 0;
+
+ memcpy(range_info, rowid_buffer->read_ptr2, sizeof(range_id_t));
+ if (!index_reader->skip_record(*range_info, rowid_buffer->read_ptr1))
+ return 0;
+ }
+
+ /*
+ Ok, last_identical_rowid==NULL, it's time to read next different rowid
+ value and get record for it.
+ */
+ for(;;)
+ {
+ /* Return eof if there are no rowids in the buffer after re-fill attempt */
+ if (rowid_buffer->read())
+ return HA_ERR_END_OF_FILE;
+
+ if (is_mrr_assoc)
+ {
+ memcpy(range_info, rowid_buffer->read_ptr2, sizeof(range_id_t));
+ if (index_reader->skip_record(*range_info, rowid_buffer->read_ptr1))
+ continue;
+ }
+
+ res= file->ha_rnd_pos(file->get_table()->record[0],
+ rowid_buffer->read_ptr1);
+
+ if (res == HA_ERR_RECORD_DELETED)
+ {
+ /* not likely to get this code with current storage engines, but still */
+ continue;
+ }
+
+ if (res)
+ return res; /* Some fatal error */
+
+ break; /* Got another record */
+ }
+
+ /*
+ Check if subsequent buffer elements have the same rowid value as this
+ one. If yes, remember this fact so that we don't make any more rnd_pos()
+ calls with this value.
+
+ Note: this implies that SQL layer doesn't touch table->record[0]
+ between calls.
+ */
+ Lifo_buffer_iterator it;
+ it.init(rowid_buffer);
+ while (!it.read())
+ {
+ if (file->cmp_ref(it.read_ptr1, rowid_buffer->read_ptr1))
+ break;
+ last_identical_rowid= it.read_ptr1;
+ }
+ return 0;
+}
+
+
+/****************************************************************************
+ * Top-level DS-MRR implementation functions (the ones called by storage engine)
+ ***************************************************************************/
+
+/**
+ DS-MRR: Initialize and start MRR scan
+
+ Initialize and start the MRR scan. Depending on the mode parameter, this
+ may use default or DS-MRR implementation.
+
+ @param h_arg Table handler to be used
+ @param key Index to be used
+ @param seq_funcs Interval sequence enumeration functions
+ @param seq_init_param Interval sequence enumeration parameter
+ @param n_ranges Number of ranges in the sequence.
+ @param mode HA_MRR_* modes to use
+ @param buf INOUT Buffer to use
+
+ @retval 0 Ok, Scan started.
+ @retval other Error
+*/
+
+int DsMrr_impl::dsmrr_init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
+ void *seq_init_param, uint n_ranges, uint mode,
+ HANDLER_BUFFER *buf)
+{
+ THD *thd= current_thd;
+ int res;
+ Key_parameters keypar;
+ uint key_buff_elem_size;
+ handler *h_idx;
+ Mrr_ordered_rndpos_reader *disk_strategy= NULL;
+ bool do_sort_keys= FALSE;
+ DBUG_ENTER("DsMrr_impl::dsmrr_init");
+ LINT_INIT(key_buff_elem_size); /* set/used when do_sort_keys==TRUE */
+ /*
+ index_merge may invoke a scan on an object for which dsmrr_info[_const]
+ has not been called, so set the owner handler here as well.
+ */
+ primary_file= h_arg;
+ is_mrr_assoc= !MY_TEST(mode & HA_MRR_NO_ASSOCIATION);
+
+ strategy_exhausted= FALSE;
+
+ /* By default, have do-nothing buffer manager */
+ buf_manager.arg= this;
+ buf_manager.reset_buffer_sizes= do_nothing;
+ buf_manager.redistribute_buffer_space= do_nothing;
+
+ if (mode & (HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED))
+ goto use_default_impl;
+
+ /*
+ Determine whether we'll need to do key sorting and/or rnd_pos() scan
+ */
+ index_strategy= NULL;
+ if ((mode & HA_MRR_SINGLE_POINT) &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_SORT_KEYS))
+ {
+ do_sort_keys= TRUE;
+ index_strategy= &reader_factory.ordered_index_reader;
+ }
+ else
+ index_strategy= &reader_factory.simple_index_reader;
+
+ strategy= index_strategy;
+ /*
+ We don't need a rowid-to-rndpos step if
+ - We're doing a scan on clustered primary key
+ - [In the future] We're doing an index_only read
+ */
+ DBUG_ASSERT(primary_file->inited == handler::INDEX ||
+ (primary_file->inited == handler::RND &&
+ secondary_file &&
+ secondary_file->inited == handler::INDEX));
+
+ h_idx= (primary_file->inited == handler::INDEX)? primary_file: secondary_file;
+ keyno= h_idx->active_index;
+
+ if (!(keyno == table->s->primary_key && h_idx->primary_key_is_clustered()))
+ {
+ strategy= disk_strategy= &reader_factory.ordered_rndpos_reader;
+ }
+
+ full_buf= buf->buffer;
+ full_buf_end= buf->buffer_end;
+
+ if (do_sort_keys)
+ {
+ /* Pre-calculate some parameters of key sorting */
+ keypar.use_key_pointers= MY_TEST(mode & HA_MRR_MATERIALIZED_KEYS);
+ seq_funcs->get_key_info(seq_init_param, &keypar.key_tuple_length,
+ &keypar.key_tuple_map);
+ keypar.key_size_in_keybuf= keypar.use_key_pointers?
+ sizeof(char*) : keypar.key_tuple_length;
+ key_buff_elem_size= keypar.key_size_in_keybuf + (int)is_mrr_assoc * sizeof(void*);
+
+ /* Ordered index reader needs some space to store an index tuple */
+ if (strategy != index_strategy)
+ {
+ uint saved_pk_length=0;
+ if (h_idx->primary_key_is_clustered())
+ {
+ uint pk= h_idx->get_table()->s->primary_key;
+ if (pk != MAX_KEY)
+ saved_pk_length= h_idx->get_table()->key_info[pk].key_length;
+ }
+
+ KEY *used_index= &h_idx->get_table()->key_info[h_idx->active_index];
+ if (reader_factory.ordered_index_reader.
+ set_interruption_temp_buffer(primary_file->ref_length,
+ used_index->key_length,
+ saved_pk_length,
+ &full_buf, full_buf_end))
+ goto use_default_impl;
+ }
+ else
+ reader_factory.ordered_index_reader.set_no_interruption_temp_buffer();
+ }
+
+ if (strategy == index_strategy)
+ {
+ /*
+ Index strategy alone handles the record retrieval. Give all buffer space
+ to it. Key buffer should have forward orientation so we can return the
+ end of it.
+ */
+ key_buffer= &forward_key_buf;
+ key_buffer->set_buffer_space(full_buf, full_buf_end);
+
+ /* Safety: specify that rowid buffer has zero size: */
+ rowid_buffer.set_buffer_space(full_buf_end, full_buf_end);
+
+ if (do_sort_keys && !key_buffer->have_space_for(key_buff_elem_size))
+ goto use_default_impl;
+
+ if ((res= index_strategy->init(primary_file, seq_funcs, seq_init_param, n_ranges,
+ mode, &keypar, key_buffer, &buf_manager)))
+ goto error;
+ }
+ else
+ {
+ /* We'll have both index and rndpos strategies working together */
+ if (do_sort_keys)
+ {
+ /* Both strategies will need buffer space, share the buffer */
+ if (setup_buffer_sharing(keypar.key_size_in_keybuf, keypar.key_tuple_map))
+ goto use_default_impl;
+
+ buf_manager.reset_buffer_sizes= reset_buffer_sizes;
+ buf_manager.redistribute_buffer_space= redistribute_buffer_space;
+ }
+ else
+ {
+ /* index strategy doesn't need buffer, give all space to rowids*/
+ rowid_buffer.set_buffer_space(full_buf, full_buf_end);
+ if (!rowid_buffer.have_space_for(primary_file->ref_length +
+ (int)is_mrr_assoc * sizeof(range_id_t)))
+ goto use_default_impl;
+ }
+
+ if ((res= setup_two_handlers()))
+ goto error;
+
+ if ((res= index_strategy->init(secondary_file, seq_funcs, seq_init_param,
+ n_ranges, mode, &keypar, key_buffer,
+ &buf_manager)) ||
+ (res= disk_strategy->init(primary_file, index_strategy, mode,
+ &rowid_buffer)))
+ {
+ goto error;
+ }
+ }
+
+ /*
+ At this point, we're sure that we're running a native MRR scan (i.e. we
+ didnt fall back to default implementation for some reason).
+ */
+ status_var_increment(thd->status_var.ha_mrr_init_count);
+
+ res= strategy->refill_buffer(TRUE);
+ if (res)
+ {
+ if (res != HA_ERR_END_OF_FILE)
+ goto error;
+ strategy_exhausted= TRUE;
+ }
+
+ /*
+ If we have scanned through all intervals in *seq, then adjust *buf to
+ indicate that the remaining buffer space will not be used.
+ */
+// if (dsmrr_eof)
+// buf->end_of_used_area= rowid_buffer.end_of_space();
+
+
+ DBUG_RETURN(0);
+error:
+ close_second_handler();
+ /* Safety, not really needed but: */
+ strategy= NULL;
+ DBUG_RETURN(res);
+
+use_default_impl:
+ if (primary_file->inited != handler::INDEX)
+ {
+ /* We can get here when
+ - we've previously successfully done a DS-MRR scan (and so have
+ secondary_file!= NULL, secondary_file->inited= INDEX,
+ primary_file->inited=RND)
+ - for this invocation, we haven't got enough buffer space, and so we
+ have to use the default MRR implementation.
+
+ note: primary_file->ha_index_end() will call dsmrr_close() which will
+ close/destroy the secondary_file, this is intentional.
+ (Yes this is slow, but one can't expect performance with join buffer
+ so small that it can accomodate one rowid and one index tuple)
+ */
+ if ((res= primary_file->ha_rnd_end()) ||
+ (res= primary_file->ha_index_init(keyno, MY_TEST(mode & HA_MRR_SORTED))))
+ {
+ DBUG_RETURN(res);
+ }
+ }
+ /* Call correct init function and assign to top level object */
+ Mrr_simple_index_reader *s= &reader_factory.simple_index_reader;
+ res= s->init(primary_file, seq_funcs, seq_init_param, n_ranges, mode, NULL,
+ NULL, NULL);
+ strategy= s;
+ DBUG_RETURN(res);
+}
+
+
+/*
+ Whatever the current state is, make it so that we have two handler objects:
+ - primary_file - initialized for rnd_pos() scan
+ - secondary_file - initialized for scanning the index specified in
+ this->keyno
+ RETURN
+ 0 OK
+ HA_XXX Error code
+*/
+
+int DsMrr_impl::setup_two_handlers()
+{
+ int res;
+ THD *thd= primary_file->get_table()->in_use;
+ DBUG_ENTER("DsMrr_impl::setup_two_handlers");
+ if (!secondary_file)
+ {
+ handler *new_h2;
+ Item *pushed_cond= NULL;
+ DBUG_ASSERT(primary_file->inited == handler::INDEX);
+ /* Create a separate handler object to do rnd_pos() calls. */
+ /*
+ ::clone() takes up a lot of stack, especially on 64 bit platforms.
+ The constant 5 is an empiric result.
+ */
+ if (check_stack_overrun(thd, 5*STACK_MIN_SIZE, (uchar*) &new_h2))
+ DBUG_RETURN(1);
+
+ /* Create a separate handler object to do rnd_pos() calls. */
+ if (!(new_h2= primary_file->clone(primary_file->get_table()->s->
+ normalized_path.str,
+ thd->mem_root)) ||
+ new_h2->ha_external_lock(thd, F_RDLCK))
+ {
+ delete new_h2;
+ DBUG_RETURN(1);
+ }
+
+ if (keyno == primary_file->pushed_idx_cond_keyno)
+ pushed_cond= primary_file->pushed_idx_cond;
+
+ Mrr_reader *save_strategy= strategy;
+ strategy= NULL;
+ /*
+ Caution: this call will invoke this->dsmrr_close(). Do not put the
+ created secondary table handler new_h2 into this->secondary_file or it
+ will delete it. Also, save the picked strategy
+ */
+ res= primary_file->ha_index_end();
+
+ strategy= save_strategy;
+ secondary_file= new_h2;
+
+ if (res || (res= (primary_file->ha_rnd_init(FALSE))))
+ goto error;
+
+ table->prepare_for_position();
+ secondary_file->extra(HA_EXTRA_KEYREAD);
+ secondary_file->mrr_iter= primary_file->mrr_iter;
+
+ if ((res= secondary_file->ha_index_init(keyno, FALSE)))
+ goto error;
+
+ if (pushed_cond)
+ secondary_file->idx_cond_push(keyno, pushed_cond);
+ }
+ else
+ {
+ DBUG_ASSERT(secondary_file && secondary_file->inited==handler::INDEX);
+ /*
+ We get here when the access alternates betwen MRR scan(s) and non-MRR
+ scans.
+
+ Calling primary_file->index_end() will invoke dsmrr_close() for this object,
+ which will delete secondary_file. We need to keep it, so put it away and dont
+ let it be deleted:
+ */
+ if (primary_file->inited == handler::INDEX)
+ {
+ handler *save_h2= secondary_file;
+ Mrr_reader *save_strategy= strategy;
+ secondary_file= NULL;
+ strategy= NULL;
+ res= primary_file->ha_index_end();
+ secondary_file= save_h2;
+ strategy= save_strategy;
+ if (res)
+ goto error;
+ }
+ if ((primary_file->inited != handler::RND) &&
+ (res= primary_file->ha_rnd_init(FALSE)))
+ goto error;
+ }
+ DBUG_RETURN(0);
+
+error:
+ DBUG_RETURN(res);
+}
+
+
+void DsMrr_impl::close_second_handler()
+{
+ if (secondary_file)
+ {
+ secondary_file->extra(HA_EXTRA_NO_KEYREAD);
+ secondary_file->ha_index_or_rnd_end();
+ secondary_file->ha_external_lock(current_thd, F_UNLCK);
+ secondary_file->ha_close();
+ delete secondary_file;
+ secondary_file= NULL;
+ }
+}
+
+
+void DsMrr_impl::dsmrr_close()
+{
+ DBUG_ENTER("DsMrr_impl::dsmrr_close");
+ close_second_handler();
+ strategy= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ my_qsort2-compatible static member function to compare key tuples
+*/
+
+int Mrr_ordered_index_reader::compare_keys(void* arg, uchar* key1_arg,
+ uchar* key2_arg)
+{
+ Mrr_ordered_index_reader *reader= (Mrr_ordered_index_reader*)arg;
+ TABLE *table= reader->file->get_table();
+ KEY_PART_INFO *part= table->key_info[reader->file->active_index].key_part;
+ uchar *key1, *key2;
+
+ if (reader->keypar.use_key_pointers)
+ {
+ /* the buffer stores pointers to keys, get to the keys */
+ memcpy(&key1, key1_arg, sizeof(char*));
+ memcpy(&key2, key2_arg, sizeof(char*));
+ }
+ else
+ {
+ key1= key1_arg;
+ key2= key2_arg;
+ }
+
+ return key_tuple_cmp(part, key1, key2, reader->keypar.key_tuple_length);
+}
+
+
+int Mrr_ordered_index_reader::compare_keys_reverse(void* arg, uchar* key1,
+ uchar* key2)
+{
+ return -compare_keys(arg, key1, key2);
+}
+
+
+/**
+ Set the buffer space to be shared between rowid and key buffer
+
+ @return FALSE ok
+ @return TRUE There is so little buffer space that we won't be able to use
+ the strategy.
+ This happens when we don't have enough space for one rowid
+ element and one key element so this is mainly targeted at
+ testing.
+*/
+
+bool DsMrr_impl::setup_buffer_sharing(uint key_size_in_keybuf,
+ key_part_map key_tuple_map)
+{
+ long key_buff_elem_size= key_size_in_keybuf +
+ (int)is_mrr_assoc * sizeof(range_id_t);
+
+ KEY *key_info= &primary_file->get_table()->key_info[keyno];
+ /*
+ Ok if we got here we need to allocate one part of the buffer
+ for keys and another part for rowids.
+ */
+ ulonglong rowid_buf_elem_size= primary_file->ref_length +
+ (int)is_mrr_assoc * sizeof(range_id_t);
+
+ /*
+ Use rec_per_key statistics as a basis to find out how many rowids
+ we'll get for each key value.
+ TODO: what should be the default value to use when there is no
+ statistics?
+ */
+ uint parts= my_count_bits(key_tuple_map);
+ ha_rows rpc;
+ ulonglong rowids_size= rowid_buf_elem_size;
+ if ((rpc= (ha_rows) key_info->actual_rec_per_key(parts - 1)))
+ rowids_size= rowid_buf_elem_size * rpc;
+
+ double fraction_for_rowids=
+ (ulonglong2double(rowids_size) /
+ (ulonglong2double(rowids_size) + key_buff_elem_size));
+
+ ptrdiff_t bytes_for_rowids=
+ (ptrdiff_t)floor(0.5 + fraction_for_rowids * (full_buf_end - full_buf));
+
+ ptrdiff_t bytes_for_keys= (full_buf_end - full_buf) - bytes_for_rowids;
+
+ if (bytes_for_keys < key_buff_elem_size + 1)
+ {
+ ptrdiff_t add= key_buff_elem_size + 1 - bytes_for_keys;
+ bytes_for_keys= key_buff_elem_size + 1;
+ bytes_for_rowids -= add;
+ }
+
+ if (bytes_for_rowids < (ptrdiff_t)rowid_buf_elem_size + 1)
+ {
+ ptrdiff_t add= (ptrdiff_t)(rowid_buf_elem_size + 1 - bytes_for_rowids);
+ bytes_for_rowids= (ptrdiff_t)rowid_buf_elem_size + 1;
+ bytes_for_keys -= add;
+ }
+
+ rowid_buffer_end= full_buf + bytes_for_rowids;
+ rowid_buffer.set_buffer_space(full_buf, rowid_buffer_end);
+ key_buffer= &backward_key_buf;
+ key_buffer->set_buffer_space(rowid_buffer_end, full_buf_end);
+
+ if (!key_buffer->have_space_for(key_buff_elem_size) ||
+ !rowid_buffer.have_space_for((size_t)rowid_buf_elem_size))
+ return TRUE; /* Failed to provide minimum space for one of the buffers */
+
+ return FALSE;
+}
+
+
+void DsMrr_impl::do_nothing(void *dsmrr_arg)
+{
+ /* Do nothing */
+}
+
+
+void DsMrr_impl::reset_buffer_sizes(void *dsmrr_arg)
+{
+ DsMrr_impl *dsmrr= (DsMrr_impl*)dsmrr_arg;
+ dsmrr->rowid_buffer.set_buffer_space(dsmrr->full_buf,
+ dsmrr->rowid_buffer_end);
+ dsmrr->key_buffer->set_buffer_space(dsmrr->rowid_buffer_end,
+ dsmrr->full_buf_end);
+}
+
+
+/*
+ Take unused space from the key buffer and give it to the rowid buffer
+*/
+
+void DsMrr_impl::redistribute_buffer_space(void *dsmrr_arg)
+{
+ DsMrr_impl *dsmrr= (DsMrr_impl*)dsmrr_arg;
+ uchar *unused_start, *unused_end;
+ dsmrr->key_buffer->remove_unused_space(&unused_start, &unused_end);
+ dsmrr->rowid_buffer.grow(unused_start, unused_end);
+}
+
+
+/*
+ @brief Initialize the iterator
+
+ @note
+ Initialize the iterator to produce matches for the key of the first element
+ in owner_arg->key_buffer
+
+ @retval 0 OK
+ @retval HA_ERR_END_OF_FILE Either the owner->key_buffer is empty or
+ no matches for the key we've tried (check
+ key_buffer->is_empty() to tell these apart)
+ @retval other code Fatal error
+*/
+
+int Key_value_records_iterator::init(Mrr_ordered_index_reader *owner_arg)
+{
+ int res;
+ owner= owner_arg;
+
+ identical_key_it.init(owner->key_buffer);
+ owner->key_buffer->setup_reading(owner->keypar.key_size_in_keybuf,
+ owner->is_mrr_assoc ? sizeof(void*) : 0);
+
+ if (identical_key_it.read())
+ return HA_ERR_END_OF_FILE;
+
+ uchar *key_in_buf= last_identical_key_ptr= identical_key_it.read_ptr1;
+
+ uchar *index_tuple= key_in_buf;
+ if (owner->keypar.use_key_pointers)
+ memcpy(&index_tuple, key_in_buf, sizeof(char*));
+
+ /* Check out how many more identical keys are following */
+ while (!identical_key_it.read())
+ {
+ if (Mrr_ordered_index_reader::compare_keys(owner, key_in_buf,
+ identical_key_it.read_ptr1))
+ break;
+ last_identical_key_ptr= identical_key_it.read_ptr1;
+ }
+ identical_key_it.init(owner->key_buffer);
+ res= owner->file->ha_index_read_map(owner->file->get_table()->record[0],
+ index_tuple,
+ owner->keypar.key_tuple_map,
+ HA_READ_KEY_EXACT);
+
+ if (res)
+ {
+ /* Failed to find any matching records */
+ move_to_next_key_value();
+ return res;
+ }
+ owner->have_saved_rowid= FALSE;
+ get_next_row= FALSE;
+ return 0;
+}
+
+
+int Key_value_records_iterator::get_next(range_id_t *range_info)
+{
+ int res;
+
+ if (get_next_row)
+ {
+ if (owner->keypar.index_ranges_unique)
+ {
+ /* We're using a full unique key, no point to call index_next_same */
+ return HA_ERR_END_OF_FILE;
+ }
+
+ handler *h= owner->file;
+ uchar *lookup_key;
+ if (owner->keypar.use_key_pointers)
+ memcpy(&lookup_key, identical_key_it.read_ptr1, sizeof(void*));
+ else
+ lookup_key= identical_key_it.read_ptr1;
+
+ if ((res= h->ha_index_next_same(h->get_table()->record[0],
+ lookup_key,
+ owner->keypar.key_tuple_length)))
+ {
+ /* It's either HA_ERR_END_OF_FILE or some other error */
+ return res;
+ }
+ identical_key_it.init(owner->key_buffer);
+ owner->have_saved_rowid= FALSE;
+ get_next_row= FALSE;
+ }
+
+ identical_key_it.read(); /* This gets us next range_id */
+ memcpy(range_info, identical_key_it.read_ptr2, sizeof(range_id_t));
+
+ if (!last_identical_key_ptr ||
+ (identical_key_it.read_ptr1 == last_identical_key_ptr))
+ {
+ /*
+ We've reached the last of the identical keys that current record is a
+ match for. Set get_next_row=TRUE so that we read the next index record
+ on the next call to this function.
+ */
+ get_next_row= TRUE;
+ }
+ return 0;
+}
+
+
+void Key_value_records_iterator::move_to_next_key_value()
+{
+ while (!owner->key_buffer->read() &&
+ (owner->key_buffer->read_ptr1 != last_identical_key_ptr)) {}
+}
+
+
+/**
+ DS-MRR implementation: multi_range_read_next() function.
+
+ Calling convention is like multi_range_read_next() has.
+*/
+
+int DsMrr_impl::dsmrr_next(range_id_t *range_info)
+{
+ int res;
+ if (strategy_exhausted)
+ return HA_ERR_END_OF_FILE;
+
+ while ((res= strategy->get_next(range_info)) == HA_ERR_END_OF_FILE)
+ {
+ if ((res= strategy->refill_buffer(FALSE)))
+ break; /* EOF or error */
+ }
+ return res;
+}
+
+
+/**
+ DS-MRR implementation: multi_range_read_info() function
+*/
+ha_rows DsMrr_impl::dsmrr_info(uint keyno, uint n_ranges, uint rows,
+ uint key_parts,
+ uint *bufsz, uint *flags, Cost_estimate *cost)
+{
+ ha_rows res __attribute__((unused));
+ uint def_flags= *flags;
+ uint def_bufsz= *bufsz;
+
+ /* Get cost/flags/mem_usage of default MRR implementation */
+ res= primary_file->handler::multi_range_read_info(keyno, n_ranges, rows,
+ key_parts, &def_bufsz,
+ &def_flags, cost);
+ DBUG_ASSERT(!res);
+
+ if ((*flags & HA_MRR_USE_DEFAULT_IMPL) ||
+ choose_mrr_impl(keyno, rows, flags, bufsz, cost))
+ {
+ /* Default implementation is choosen */
+ DBUG_PRINT("info", ("Default MRR implementation choosen"));
+ *flags= def_flags;
+ *bufsz= def_bufsz;
+ }
+ else
+ {
+ /* *flags and *bufsz were set by choose_mrr_impl */
+ DBUG_PRINT("info", ("DS-MRR implementation choosen"));
+ }
+ return 0;
+}
+
+
+/**
+ DS-MRR Implementation: multi_range_read_info_const() function
+*/
+
+ha_rows DsMrr_impl::dsmrr_info_const(uint keyno, RANGE_SEQ_IF *seq,
+ void *seq_init_param, uint n_ranges,
+ uint *bufsz, uint *flags, Cost_estimate *cost)
+{
+ ha_rows rows;
+ uint def_flags= *flags;
+ uint def_bufsz= *bufsz;
+ /* Get cost/flags/mem_usage of default MRR implementation */
+ rows= primary_file->handler::multi_range_read_info_const(keyno, seq,
+ seq_init_param,
+ n_ranges,
+ &def_bufsz,
+ &def_flags, cost);
+ if (rows == HA_POS_ERROR)
+ {
+ /* Default implementation can't perform MRR scan => we can't either */
+ return rows;
+ }
+
+ /*
+ If HA_MRR_USE_DEFAULT_IMPL has been passed to us, that is an order to
+ use the default MRR implementation (we need it for UPDATE/DELETE).
+ Otherwise, make a choice based on cost and @@optimizer_switch settings
+ */
+ if ((*flags & HA_MRR_USE_DEFAULT_IMPL) ||
+ choose_mrr_impl(keyno, rows, flags, bufsz, cost))
+ {
+ DBUG_PRINT("info", ("Default MRR implementation choosen"));
+ *flags= def_flags;
+ *bufsz= def_bufsz;
+ }
+ else
+ {
+ /* *flags and *bufsz were set by choose_mrr_impl */
+ DBUG_PRINT("info", ("DS-MRR implementation choosen"));
+ }
+ return rows;
+}
+
+
+/**
+ Check if key has partially-covered columns
+
+ We can't use DS-MRR to perform range scans when the ranges are over
+ partially-covered keys, because we'll not have full key part values
+ (we'll have their prefixes from the index) and will not be able to check
+ if we've reached the end the range.
+
+ @param keyno Key to check
+
+ @todo
+ Allow use of DS-MRR in cases where the index has partially-covered
+ components but they are not used for scanning.
+
+ @retval TRUE Yes
+ @retval FALSE No
+*/
+
+bool key_uses_partial_cols(TABLE_SHARE *share, uint keyno)
+{
+ KEY_PART_INFO *kp= share->key_info[keyno].key_part;
+ KEY_PART_INFO *kp_end= kp + share->key_info[keyno].user_defined_key_parts;
+ for (; kp != kp_end; kp++)
+ {
+ if (!kp->field->part_of_key.is_set(keyno))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Check if key/flags allow DS-MRR/CPK strategy to be used
+
+ @param thd
+ @param keyno Index that will be used
+ @param mrr_flags
+
+ @retval TRUE DS-MRR/CPK should be used
+ @retval FALSE Otherwise
+*/
+
+bool DsMrr_impl::check_cpk_scan(THD *thd, TABLE_SHARE *share, uint keyno,
+ uint mrr_flags)
+{
+ return MY_TEST((mrr_flags & HA_MRR_SINGLE_POINT) &&
+ keyno == share->primary_key &&
+ primary_file->primary_key_is_clustered() &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_SORT_KEYS));
+}
+
+
+/*
+ DS-MRR Internals: Choose between Default MRR implementation and DS-MRR
+
+ Make the choice between using Default MRR implementation and DS-MRR.
+ This function contains common functionality factored out of dsmrr_info()
+ and dsmrr_info_const(). The function assumes that the default MRR
+ implementation's applicability requirements are satisfied.
+
+ @param keyno Index number
+ @param rows E(full rows to be retrieved)
+ @param flags IN MRR flags provided by the MRR user
+ OUT If DS-MRR is choosen, flags of DS-MRR implementation
+ else the value is not modified
+ @param bufsz IN If DS-MRR is choosen, buffer use of DS-MRR implementation
+ else the value is not modified
+ @param cost IN Cost of default MRR implementation
+ OUT If DS-MRR is choosen, cost of DS-MRR scan
+ else the value is not modified
+
+ @retval TRUE Default MRR implementation should be used
+ @retval FALSE DS-MRR implementation should be used
+*/
+
+
+bool DsMrr_impl::choose_mrr_impl(uint keyno, ha_rows rows, uint *flags,
+ uint *bufsz, Cost_estimate *cost)
+{
+ Cost_estimate dsmrr_cost;
+ bool res;
+ THD *thd= current_thd;
+ TABLE_SHARE *share= primary_file->get_table_share();
+
+ bool doing_cpk_scan= check_cpk_scan(thd, share, keyno, *flags);
+ bool using_cpk= MY_TEST(keyno == share->primary_key &&
+ primary_file->primary_key_is_clustered());
+ *flags &= ~HA_MRR_IMPLEMENTATION_FLAGS;
+ if (!optimizer_flag(thd, OPTIMIZER_SWITCH_MRR) ||
+ *flags & HA_MRR_INDEX_ONLY ||
+ (using_cpk && !doing_cpk_scan) || key_uses_partial_cols(share, keyno))
+ {
+ /* Use the default implementation */
+ *flags |= HA_MRR_USE_DEFAULT_IMPL;
+ *flags &= ~HA_MRR_IMPLEMENTATION_FLAGS;
+ return TRUE;
+ }
+
+ uint add_len= share->key_info[keyno].key_length + primary_file->ref_length;
+ *bufsz -= add_len;
+ if (get_disk_sweep_mrr_cost(keyno, rows, *flags, bufsz, &dsmrr_cost))
+ return TRUE;
+ *bufsz += add_len;
+
+ bool force_dsmrr;
+ /*
+ If mrr_cost_based flag is not set, then set cost of DS-MRR to be minimum of
+ DS-MRR and Default implementations cost. This allows one to force use of
+ DS-MRR whenever it is applicable without affecting other cost-based
+ choices.
+ */
+ if ((force_dsmrr= !optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_COST_BASED)) &&
+ dsmrr_cost.total_cost() > cost->total_cost())
+ dsmrr_cost= *cost;
+
+ if (force_dsmrr || dsmrr_cost.total_cost() <= cost->total_cost())
+ {
+ *flags &= ~HA_MRR_USE_DEFAULT_IMPL; /* Use the DS-MRR implementation */
+ *flags &= ~HA_MRR_SORTED; /* We will return unordered output */
+ *cost= dsmrr_cost;
+ res= FALSE;
+
+
+ if ((using_cpk && doing_cpk_scan) ||
+ (optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_SORT_KEYS) &&
+ *flags & HA_MRR_SINGLE_POINT))
+ {
+ *flags |= DSMRR_IMPL_SORT_KEYS;
+ }
+
+ if (!(using_cpk && doing_cpk_scan) &&
+ !(*flags & HA_MRR_INDEX_ONLY))
+ {
+ *flags |= DSMRR_IMPL_SORT_ROWIDS;
+ }
+ /*
+ if ((*flags & HA_MRR_SINGLE_POINT) &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_SORT_KEYS))
+ *flags |= HA_MRR_MATERIALIZED_KEYS;
+ */
+ }
+ else
+ {
+ /* Use the default MRR implementation */
+ res= TRUE;
+ }
+ return res;
+}
+
+/*
+ Take the flags we've returned previously and print one of
+ - Key-ordered scan
+ - Rowid-ordered scan
+ - Key-ordered Rowid-ordered scan
+*/
+
+int DsMrr_impl::dsmrr_explain_info(uint mrr_mode, char *str, size_t size)
+{
+ const char *key_ordered= "Key-ordered scan";
+ const char *rowid_ordered= "Rowid-ordered scan";
+ const char *both_ordered= "Key-ordered Rowid-ordered scan";
+ const char *used_str="";
+ const uint BOTH_FLAGS= (DSMRR_IMPL_SORT_KEYS | DSMRR_IMPL_SORT_ROWIDS);
+
+ if (!(mrr_mode & HA_MRR_USE_DEFAULT_IMPL))
+ {
+ if ((mrr_mode & BOTH_FLAGS) == BOTH_FLAGS)
+ used_str= both_ordered;
+ else if (mrr_mode & DSMRR_IMPL_SORT_KEYS)
+ used_str= key_ordered;
+ else if (mrr_mode & DSMRR_IMPL_SORT_ROWIDS)
+ used_str= rowid_ordered;
+
+ uint used_str_len= strlen(used_str);
+ uint copy_len= MY_MIN(used_str_len, size);
+ memcpy(str, used_str, copy_len);
+ return copy_len;
+ }
+ return 0;
+}
+
+
+static void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, Cost_estimate *cost);
+
+
+/**
+ Get cost of DS-MRR scan
+
+ @param keynr Index to be used
+ @param rows E(Number of rows to be scanned)
+ @param flags Scan parameters (HA_MRR_* flags)
+ @param buffer_size INOUT Buffer size
+ @param cost OUT The cost
+
+ @retval FALSE OK
+ @retval TRUE Error, DS-MRR cannot be used (the buffer is too small
+ for even 1 rowid)
+*/
+
+bool DsMrr_impl::get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags,
+ uint *buffer_size, Cost_estimate *cost)
+{
+ ulong max_buff_entries, elem_size;
+ ha_rows rows_in_full_step;
+ ha_rows rows_in_last_step;
+ uint n_full_steps;
+ double index_read_cost;
+
+ elem_size= primary_file->ref_length +
+ sizeof(void*) * (!MY_TEST(flags & HA_MRR_NO_ASSOCIATION));
+ max_buff_entries = *buffer_size / elem_size;
+
+ if (!max_buff_entries)
+ return TRUE; /* Buffer has not enough space for even 1 rowid */
+
+ /* Number of iterations we'll make with full buffer */
+ n_full_steps= (uint)floor(rows2double(rows) / max_buff_entries);
+
+ /*
+ Get numbers of rows we'll be processing in
+ - non-last sweep, with full buffer
+ - last iteration, with non-full buffer
+ */
+ rows_in_full_step= max_buff_entries;
+ rows_in_last_step= rows % max_buff_entries;
+
+ /* Adjust buffer size if we expect to use only part of the buffer */
+ if (n_full_steps)
+ {
+ get_sort_and_sweep_cost(table, rows_in_full_step, cost);
+ cost->multiply(n_full_steps);
+ }
+ else
+ {
+ cost->reset();
+ *buffer_size= MY_MAX(*buffer_size,
+ (size_t)(1.2*rows_in_last_step) * elem_size +
+ primary_file->ref_length + table->key_info[keynr].key_length);
+ }
+
+ Cost_estimate last_step_cost;
+ get_sort_and_sweep_cost(table, rows_in_last_step, &last_step_cost);
+ cost->add(&last_step_cost);
+
+ if (n_full_steps != 0)
+ cost->mem_cost= *buffer_size;
+ else
+ cost->mem_cost= (double)rows_in_last_step * elem_size;
+
+ /* Total cost of all index accesses */
+ index_read_cost= primary_file->keyread_time(keynr, 1, rows);
+ cost->add_io(index_read_cost, 1 /* Random seeks */);
+ return FALSE;
+}
+
+
+/*
+ Get cost of one sort-and-sweep step
+
+ It consists of two parts:
+ - sort an array of #nrows ROWIDs using qsort
+ - read #nrows records from table in a sweep.
+
+ @param table Table being accessed
+ @param nrows Number of rows to be sorted and retrieved
+ @param cost OUT The cost of scan
+*/
+
+static
+void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, Cost_estimate *cost)
+{
+ if (nrows)
+ {
+ get_sweep_read_cost(table, nrows, FALSE, cost);
+ /* Add cost of qsort call: n * log2(n) * cost(rowid_comparison) */
+ double cmp_op= rows2double(nrows) * (1.0 / TIME_FOR_COMPARE_ROWID);
+ if (cmp_op < 3)
+ cmp_op= 3;
+ cost->cpu_cost += cmp_op * log2(cmp_op);
+ }
+ else
+ cost->reset();
+}
+
+
+/**
+ Get cost of reading nrows table records in a "disk sweep"
+
+ A disk sweep read is a sequence of handler->rnd_pos(rowid) calls that made
+ for an ordered sequence of rowids.
+
+ We assume hard disk IO. The read is performed as follows:
+
+ 1. The disk head is moved to the needed cylinder
+ 2. The controller waits for the plate to rotate
+ 3. The data is transferred
+
+ Time to do #3 is insignificant compared to #2+#1.
+
+ Time to move the disk head is proportional to head travel distance.
+
+ Time to wait for the plate to rotate depends on whether the disk head
+ was moved or not.
+
+ If disk head wasn't moved, the wait time is proportional to distance
+ between the previous block and the block we're reading.
+
+ If the head was moved, we don't know how much we'll need to wait for the
+ plate to rotate. We assume the wait time to be a variate with a mean of
+ 0.5 of full rotation time.
+
+ Our cost units are "random disk seeks". The cost of random disk seek is
+ actually not a constant, it depends one range of cylinders we're going
+ to access. We make it constant by introducing a fuzzy concept of "typical
+ datafile length" (it's fuzzy as it's hard to tell whether it should
+ include index file, temp.tables etc). Then random seek cost is:
+
+ 1 = half_rotation_cost + move_cost * 1/3 * typical_data_file_length
+
+ We define half_rotation_cost as DISK_SEEK_BASE_COST=0.9.
+
+ @param table Table to be accessed
+ @param nrows Number of rows to retrieve
+ @param interrupted TRUE <=> Assume that the disk sweep will be
+ interrupted by other disk IO. FALSE - otherwise.
+ @param cost OUT The cost.
+*/
+
+void get_sweep_read_cost(TABLE *table, ha_rows nrows, bool interrupted,
+ Cost_estimate *cost)
+{
+ DBUG_ENTER("get_sweep_read_cost");
+
+ cost->reset();
+ if (table->file->primary_key_is_clustered())
+ {
+ cost->io_count= table->file->read_time(table->s->primary_key,
+ (uint) nrows, nrows);
+ }
+ else
+ {
+ double n_blocks=
+ ceil(ulonglong2double(table->file->stats.data_file_length) / IO_SIZE);
+ double busy_blocks=
+ n_blocks * (1.0 - pow(1.0 - 1.0/n_blocks, rows2double(nrows)));
+ if (busy_blocks < 1.0)
+ busy_blocks= 1.0;
+
+ DBUG_PRINT("info",("sweep: nblocks=%g, busy_blocks=%g", n_blocks,
+ busy_blocks));
+ cost->io_count= busy_blocks;
+
+ if (!interrupted)
+ {
+ /* Assume reading is done in one 'sweep' */
+ cost->avg_io_cost= (DISK_SEEK_BASE_COST +
+ DISK_SEEK_PROP_COST*n_blocks/busy_blocks);
+ }
+ }
+ DBUG_PRINT("info",("returning cost=%g", cost->total_cost()));
+ DBUG_VOID_RETURN;
+}
+
+
+/* **************************************************************************
+ * DS-MRR implementation ends
+ ***************************************************************************/
+
+
diff --git a/sql/multi_range_read.h b/sql/multi_range_read.h
new file mode 100644
index 00000000000..ffae6d63124
--- /dev/null
+++ b/sql/multi_range_read.h
@@ -0,0 +1,659 @@
+/*
+ Copyright (c) 2009, 2011, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @defgroup DS-MRR declarations
+ @{
+*/
+
+/**
+ A Disk-Sweep implementation of MRR Interface (DS-MRR for short)
+
+ This is a "plugin"(*) for storage engines that allows to
+ 1. When doing index scans, read table rows in rowid order;
+ 2. when making many index lookups, do them in key order and don't
+ lookup the same key value multiple times;
+ 3. Do both #1 and #2, when applicable.
+ These changes are expected to speed up query execution for disk-based
+ storage engines running io-bound loads and "big" queries (ie. queries that
+ do joins and enumerate lots of records).
+
+ (*) - only conceptually. No dynamic loading or binary compatibility of any
+ kind.
+
+ General scheme of things:
+
+ SQL Layer code
+ | | |
+ v v v
+ -|---|---|---- handler->multi_range_read_XXX() function calls
+ | | |
+ _____________________________________
+ / DS-MRR module \
+ | (order/de-duplicate lookup keys, |
+ | scan indexes in key order, |
+ | order/de-duplicate rowids, |
+ | retrieve full record reads in rowid |
+ | order) |
+ \_____________________________________/
+ | | |
+ -|---|---|----- handler->read_range_first()/read_range_next(),
+ | | | handler->index_read(), handler->rnd_pos() calls.
+ | | |
+ v v v
+ Storage engine internals
+
+
+ Currently DS-MRR is used by MyISAM, InnoDB/XtraDB and Maria storage engines.
+ Potentially it can be used with any table handler that has disk-based data
+ storage and has better performance when reading data in rowid order.
+*/
+
+#include "sql_lifo_buffer.h"
+
+class DsMrr_impl;
+class Mrr_ordered_index_reader;
+
+
+/* A structure with key parameters that's shared among several classes */
+class Key_parameters
+{
+public:
+ uint key_tuple_length; /* Length of index lookup tuple, in bytes */
+ key_part_map key_tuple_map; /* keyparts used in index lookup tuples */
+
+ /*
+ This is
+ = key_tuple_length if we copy keys to buffer
+ = sizeof(void*) if we're using pointers to materialized keys.
+ */
+ uint key_size_in_keybuf;
+
+ /* TRUE <=> don't copy key values, use pointers to them instead. */
+ bool use_key_pointers;
+
+ /* TRUE <=> We can get at most one index tuple for a lookup key */
+ bool index_ranges_unique;
+};
+
+
+/**
+ A class to enumerate (record, range_id) pairs that match given key value.
+
+ @note
+
+ The idea is that we have a Lifo_buffer which holds (key, range_id) pairs
+ ordered by key value. From the front of the buffer we see
+
+ (key_val1, range_id1), (key_val1, range_id2) ... (key_val2, range_idN)
+
+ we take the first elements that have the same key value (key_val1 in the
+ example above), and make lookup into the table. The table will have
+ multiple matches for key_val1:
+
+ == Table Index ==
+ ...
+ key_val1 -> key_val1, index_tuple1
+ key_val1, index_tuple2
+ ...
+ key_val1, index_tupleN
+ ...
+
+ Our goal is to produce all possible combinations, i.e. we need:
+
+ {(key_val1, index_tuple1), range_id1}
+ {(key_val1, index_tuple1), range_id2}
+ ... ... |
+ {(key_val1, index_tuple1), range_idN},
+
+ {(key_val1, index_tuple2), range_id1}
+ {(key_val1, index_tuple2), range_id2}
+ ... ... |
+ {(key_val1, index_tuple2), range_idN},
+
+ ... ... ...
+
+ {(key_val1, index_tupleK), range_idN}
+*/
+
+class Key_value_records_iterator
+{
+ /* Use this to get table handler, key buffer and other parameters */
+ Mrr_ordered_index_reader *owner;
+
+ /* Iterator to get (key, range_id) pairs from */
+ Lifo_buffer_iterator identical_key_it;
+
+ /*
+ Last of the identical key values (when we get this pointer from
+ identical_key_it, it will be time to stop).
+ */
+ uchar *last_identical_key_ptr;
+
+ /*
+ FALSE <=> we're right after the init() call, the record has been already
+ read with owner->file->index_read_map() call
+ */
+ bool get_next_row;
+
+public:
+ int init(Mrr_ordered_index_reader *owner_arg);
+ int get_next(range_id_t *range_info);
+ void move_to_next_key_value();
+};
+
+
+/*
+ Buffer manager interface. Mrr_reader objects use it to inqure DsMrr_impl
+ to manage buffer space for them.
+*/
+typedef struct st_buffer_manager
+{
+public:
+ /* Opaque value to be passed as the first argument to all member functions */
+ void *arg;
+
+ /*
+ This is called when we've freed more space from the rowid buffer. The
+ callee will get the unused space from the rowid buffer and give it to the
+ key buffer.
+ */
+ void (*redistribute_buffer_space)(void *arg);
+
+ /*
+ This is called when both key and rowid buffers are empty, and so it's time
+ to reset them to their original size (They've lost their original size,
+ because we were dynamically growing rowid buffer and shrinking key buffer).
+ */
+ void (*reset_buffer_sizes)(void *arg);
+
+} Buffer_manager;
+
+
+/*
+ Mrr_reader - DS-MRR execution strategy abstraction
+
+ A reader produces ([index]_record, range_info) pairs, and requires periodic
+ refill operations.
+
+ - one starts using the reader by calling reader->get_next(),
+ - when a get_next() call returns HA_ERR_END_OF_FILE, one must call
+ refill_buffer() before they can make more get_next() calls.
+ - when refill_buffer() returns HA_ERR_END_OF_FILE, this means the real
+ end of stream and get_next() should not be called anymore.
+
+ Both functions can return other error codes, these mean unrecoverable errors
+ after which one cannot continue.
+*/
+
+class Mrr_reader
+{
+public:
+ virtual int get_next(range_id_t *range_info) = 0;
+ virtual int refill_buffer(bool initial) = 0;
+ virtual ~Mrr_reader() {}; /* just to remove compiler warning */
+};
+
+
+/*
+ A common base for readers that do index scans and produce index tuples
+*/
+
+class Mrr_index_reader : public Mrr_reader
+{
+protected:
+ handler *file; /* Handler object to use */
+public:
+ virtual int init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
+ void *seq_init_param, uint n_ranges,
+ uint mode, Key_parameters *key_par,
+ Lifo_buffer *key_buffer,
+ Buffer_manager *buf_manager_arg) = 0;
+
+ /* Get pointer to place where every get_next() call will put rowid */
+ virtual uchar *get_rowid_ptr() = 0;
+ /* Get the rowid (call this after get_next() call) */
+ virtual void position();
+ virtual bool skip_record(range_id_t range_id, uchar *rowid) = 0;
+
+ virtual void interrupt_read() {}
+ virtual void resume_read() {}
+};
+
+
+/*
+ A "bypass" index reader that just does and index scan. The index scan is done
+ by calling default MRR implementation (i.e. handler::multi_range_read_XXX())
+ functions.
+*/
+
+class Mrr_simple_index_reader : public Mrr_index_reader
+{
+public:
+ int init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
+ void *seq_init_param, uint n_ranges,
+ uint mode, Key_parameters *key_par,
+ Lifo_buffer *key_buffer,
+ Buffer_manager *buf_manager_arg);
+ int get_next(range_id_t *range_info);
+ int refill_buffer(bool initial) { return initial? 0: HA_ERR_END_OF_FILE; }
+ uchar *get_rowid_ptr() { return file->ref; }
+ bool skip_record(range_id_t range_id, uchar *rowid)
+ {
+ return (file->mrr_funcs.skip_record &&
+ file->mrr_funcs.skip_record(file->mrr_iter, range_id, rowid));
+ }
+};
+
+
+/*
+ A reader that sorts the key values before it makes the index lookups.
+*/
+
+class Mrr_ordered_index_reader : public Mrr_index_reader
+{
+public:
+ int init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
+ void *seq_init_param, uint n_ranges,
+ uint mode, Key_parameters *key_par,
+ Lifo_buffer *key_buffer,
+ Buffer_manager *buf_manager_arg);
+ int get_next(range_id_t *range_info);
+ int refill_buffer(bool initial);
+ uchar *get_rowid_ptr() { return file->ref; }
+
+ bool skip_record(range_id_t range_info, uchar *rowid)
+ {
+ return (mrr_funcs.skip_record &&
+ mrr_funcs.skip_record(mrr_iter, range_info, rowid));
+ }
+
+ bool skip_index_tuple(range_id_t range_info)
+ {
+ return (mrr_funcs.skip_index_tuple &&
+ mrr_funcs.skip_index_tuple(mrr_iter, range_info));
+ }
+
+ bool set_interruption_temp_buffer(uint rowid_length, uint key_len,
+ uint saved_pk_len,
+ uchar **space_start, uchar *space_end);
+ void set_no_interruption_temp_buffer();
+
+ void interrupt_read();
+ void resume_read();
+ void position();
+private:
+ Key_value_records_iterator kv_it;
+
+ bool scanning_key_val_iter;
+
+ /* Buffer to store (key, range_id) pairs */
+ Lifo_buffer *key_buffer;
+
+ /* This manages key buffer allocation and sizing for us */
+ Buffer_manager *buf_manager;
+
+ Key_parameters keypar; /* index scan and lookup tuple parameters */
+
+ /* TRUE <=> need range association, buffers hold {rowid, range_id} pairs */
+ bool is_mrr_assoc;
+
+ /* Range sequence iteration members */
+ RANGE_SEQ_IF mrr_funcs;
+ range_seq_t mrr_iter;
+
+ /* TRUE == reached eof when enumerating ranges */
+ bool source_exhausted;
+
+ /*
+ Following members are for interrupt_read()/resume_read(). The idea is that
+ in some cases index scan that is done by this object is interrupted by
+ rnd_pos() calls made by Mrr_ordered_rndpos_reader. The problem is that
+ we're sharing handler->record[0] with that object, and it destroys its
+ contents.
+ We need to save/restore our current
+ - index tuple (for pushed index condition checks)
+ - clustered primary key values (again, for pushed index condition checks)
+ - rowid of the last record we've retrieved (in case this rowid matches
+ multiple ranges and we'll need to return it again)
+ */
+ bool support_scan_interruptions;
+ /* Space where we save the rowid of the last record we've returned */
+ uchar *saved_rowid;
+
+ /* TRUE <=> saved_rowid has the last saved rowid */
+ bool have_saved_rowid;
+
+ uchar *saved_key_tuple; /* Saved current key tuple */
+ uchar *saved_primary_key; /* Saved current primary key tuple */
+
+ /*
+ TRUE<=> saved_key_tuple (and saved_primary_key when applicable) have
+ valid values.
+ */
+ bool read_was_interrupted;
+
+ static int compare_keys(void* arg, uchar* key1, uchar* key2);
+ static int compare_keys_reverse(void* arg, uchar* key1, uchar* key2);
+
+ friend class Key_value_records_iterator;
+ friend class DsMrr_impl;
+ friend class Mrr_ordered_rndpos_reader;
+};
+
+
+/*
+ A reader that gets rowids from an Mrr_index_reader, and then sorts them
+ before getting full records with handler->rndpos() calls.
+*/
+
+class Mrr_ordered_rndpos_reader : public Mrr_reader
+{
+public:
+ int init(handler *file, Mrr_index_reader *index_reader, uint mode,
+ Lifo_buffer *buf);
+ int get_next(range_id_t *range_info);
+ int refill_buffer(bool initial);
+private:
+ handler *file; /* Handler to use */
+
+ /* This what we get (rowid, range_info) pairs from */
+ Mrr_index_reader *index_reader;
+
+ /* index_reader->get_next() puts rowid here */
+ uchar *index_rowid;
+
+ /* TRUE <=> index_reader->refill_buffer() call has returned EOF */
+ bool index_reader_exhausted;
+
+ /*
+ TRUE <=> We should call index_reader->refill_buffer(). This happens if
+ 1. we've made index_reader->get_next() call which returned EOF
+ 2. we haven't made any index_reader calls (and our first call should
+ be index_reader->refill_buffer(initial=TRUE)
+ */
+ bool index_reader_needs_refill;
+
+ /* TRUE <=> need range association, buffers hold {rowid, range_id} pairs */
+ bool is_mrr_assoc;
+
+ /*
+ When reading from ordered rowid buffer: the rowid element of the last
+ buffer element that has rowid identical to this one.
+ */
+ uchar *last_identical_rowid;
+
+ /* Buffer to store (rowid, range_id) pairs */
+ Lifo_buffer *rowid_buffer;
+
+ int refill_from_index_reader();
+};
+
+
+/*
+ A primitive "factory" of various Mrr_*_reader classes (the point is to
+ get various kinds of readers without having to allocate them on the heap)
+*/
+
+class Mrr_reader_factory
+{
+public:
+ Mrr_ordered_rndpos_reader ordered_rndpos_reader;
+ Mrr_ordered_index_reader ordered_index_reader;
+ Mrr_simple_index_reader simple_index_reader;
+};
+
+
+#define DSMRR_IMPL_SORT_KEYS HA_MRR_IMPLEMENTATION_FLAG1
+#define DSMRR_IMPL_SORT_ROWIDS HA_MRR_IMPLEMENTATION_FLAG2
+
+/*
+ DS-MRR implementation for one table. Create/use one object of this class for
+ each ha_{myisam/innobase/etc} object. That object will be further referred to
+ as "the handler"
+
+ DsMrr_impl supports has the following execution strategies:
+
+ - Bypass DS-MRR, pass all calls to default MRR implementation, which is
+ an MRR-to-non-MRR call converter.
+ - Key-Ordered Retrieval
+ - Rowid-Ordered Retrieval
+
+ DsMrr_impl will use one of the above strategies, or a combination of them,
+ according to the following diagram:
+
+ (mrr function calls)
+ |
+ +----------------->-----------------+
+ | |
+ ___________v______________ _______________v________________
+ / default: use lookup keys \ / KEY-ORDERED RETRIEVAL: \
+ | (or ranges) in whatever | | sort lookup keys and then make |
+ | order they are supplied | | index lookups in index order |
+ \__________________________/ \________________________________/
+ | | | | |
+ +---<---+ | +--------------->-----------|----+
+ | | | |
+ | | +---------------+ |
+ | ______v___ ______ | _______________v_______________
+ | / default: read \ | / ROWID-ORDERED RETRIEVAL: \
+ | | table records | | | Before reading table records, |
+ v | in random order | v | sort their rowids and then |
+ | \_________________/ | | read them in rowid order |
+ | | | \_______________________________/
+ | | | |
+ | | | |
+ +-->---+ | +----<------+-----------<--------+
+ | | |
+ v v v
+ (table records and range_ids)
+
+ The choice of strategy depends on MRR scan properties, table properties
+ (whether we're scanning clustered primary key), and @@optimizer_switch
+ settings.
+
+ Key-Ordered Retrieval
+ ---------------------
+ The idea is: if MRR scan is essentially a series of lookups on
+
+ tbl.key=value1 OR tbl.key=value2 OR ... OR tbl.key=valueN
+
+ then it makes sense to collect and order the set of lookup values, i.e.
+
+ sort(value1, value2, .. valueN)
+
+ and then do index lookups in index order. This results in fewer index page
+ fetch operations, and we also can avoid making multiple index lookups for the
+ same value. That is, if value1=valueN we can easily discover that after
+ sorting and make one index lookup for them instead of two.
+
+ Rowid-Ordered Retrieval
+ -----------------------
+ If we do a regular index scan or a series of index lookups, we'll be hitting
+ table records at random. For disk-based engines, this is much slower than
+ reading the same records in disk order. We assume that disk ordering of
+ rows is the same as ordering of their rowids (which is provided by
+ handler::cmp_ref())
+ In order to retrieve records in different order, we must separate index
+ scanning and record fetching, that is, MRR scan uses the following steps:
+
+ 1. Scan the index (and only index, that is, with HA_EXTRA_KEYREAD on) and
+ fill a buffer with {rowid, range_id} pairs
+ 2. Sort the buffer by rowid value
+ 3. for each {rowid, range_id} pair in the buffer
+ get record by rowid and return the {record, range_id} pair
+ 4. Repeat the above steps until we've exhausted the list of ranges we're
+ scanning.
+
+ Buffer space management considerations
+ --------------------------------------
+ With regards to buffer/memory management, MRR interface specifies that
+ - SQL layer provides multi_range_read_init() with buffer of certain size.
+ - MRR implementation may use (i.e. have at its disposal till the end of
+ the MRR scan) all of the buffer, or return the unused end of the buffer
+ to SQL layer.
+
+ DS-MRR needs buffer in order to accumulate and sort rowids and/or keys. When
+ we need to accumulate/sort only keys (or only rowids), it is fairly trivial.
+
+ When we need to accumulate/sort both keys and rowids, efficient buffer use
+ gets complicated. We need to:
+ - First, accumulate keys and sort them
+ - Then use the keys (smaller values go first) to obtain rowids. A key is not
+ needed after we've got matching rowids for it.
+ - Make sure that rowids are accumulated at the front of the buffer, so that we
+ can return the end part of the buffer to SQL layer, should there be too
+ few rowid values to occupy the buffer.
+
+ All of these goals are achieved by using the following scheme:
+
+ | | We get an empty buffer from SQL layer.
+
+ | *-|
+ | *----| First, we fill the buffer with keys. Key_buffer
+ | *-------| part grows from end of the buffer space to start
+ | *----------| (In this picture, the buffer is big enough to
+ | *-------------| accomodate all keys and even have some space left)
+
+ | *=============| We want to do key-ordered index scan, so we sort
+ the keys
+
+ |-x *===========| Then we use the keys get rowids. Rowids are
+ |----x *========| stored from start of buffer space towards the end.
+ |--------x *=====| The part of the buffer occupied with keys
+ |------------x *===| gradually frees up space for rowids. In this
+ |--------------x *=| picture we run out of keys before we've ran out
+ |----------------x | of buffer space (it can be other way as well).
+
+ |================x | Then we sort the rowids.
+
+ | |~~~| The unused part of the buffer is at the end, so
+ we can return it to the SQL layer.
+
+ |================* Sorted rowids are then used to read table records
+ in disk order
+
+*/
+
+class DsMrr_impl
+{
+public:
+ typedef void (handler::*range_check_toggle_func_t)(bool on);
+
+ DsMrr_impl()
+ : secondary_file(NULL) {};
+
+ void init(handler *h_arg, TABLE *table_arg)
+ {
+ primary_file= h_arg;
+ table= table_arg;
+ }
+ int dsmrr_init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
+ void *seq_init_param, uint n_ranges, uint mode,
+ HANDLER_BUFFER *buf);
+ void dsmrr_close();
+ int dsmrr_next(range_id_t *range_info);
+
+ ha_rows dsmrr_info(uint keyno, uint n_ranges, uint keys, uint key_parts,
+ uint *bufsz, uint *flags, Cost_estimate *cost);
+
+ ha_rows dsmrr_info_const(uint keyno, RANGE_SEQ_IF *seq,
+ void *seq_init_param, uint n_ranges, uint *bufsz,
+ uint *flags, Cost_estimate *cost);
+
+ int dsmrr_explain_info(uint mrr_mode, char *str, size_t size);
+private:
+ /* Buffer to store (key, range_id) pairs */
+ Lifo_buffer *key_buffer;
+
+ /*
+ The "owner" handler object (the one that is expected to "own" this object
+ and call its functions).
+ */
+ handler *primary_file;
+ TABLE *table; /* Always equal to primary_file->table */
+
+ /*
+ Secondary handler object. (created when needed, we need it when we need
+ to run both index scan and rnd_pos() scan at the same time)
+ */
+ handler *secondary_file;
+
+ uint keyno; /* index we're running the scan on */
+ /* TRUE <=> need range association, buffers hold {rowid, range_id} pairs */
+ bool is_mrr_assoc;
+
+ Mrr_reader_factory reader_factory;
+
+ Mrr_reader *strategy;
+ bool strategy_exhausted;
+
+ Mrr_index_reader *index_strategy;
+
+ /* The whole buffer space that we're using */
+ uchar *full_buf;
+ uchar *full_buf_end;
+
+ /*
+ When using both rowid and key buffers: the boundary between key and rowid
+ parts of the buffer. This is the "original" value, actual memory ranges
+ used by key and rowid parts may be different because of dynamic space
+ reallocation between them.
+ */
+ uchar *rowid_buffer_end;
+
+ /*
+ One of the following two is used for key buffer: forward is used when
+ we only need key buffer, backward is used when we need both key and rowid
+ buffers.
+ */
+ Forward_lifo_buffer forward_key_buf;
+ Backward_lifo_buffer backward_key_buf;
+
+ /*
+ Buffer to store (rowid, range_id) pairs, or just rowids if
+ is_mrr_assoc==FALSE
+ */
+ Forward_lifo_buffer rowid_buffer;
+
+ bool choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, uint *bufsz,
+ Cost_estimate *cost);
+ bool get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags,
+ uint *buffer_size, Cost_estimate *cost);
+ bool check_cpk_scan(THD *thd, TABLE_SHARE *share, uint keyno, uint mrr_flags);
+
+ bool setup_buffer_sharing(uint key_size_in_keybuf, key_part_map key_tuple_map);
+
+ /* Buffer_manager and its member functions */
+ Buffer_manager buf_manager;
+ static void redistribute_buffer_space(void *dsmrr_arg);
+ static void reset_buffer_sizes(void *dsmrr_arg);
+ static void do_nothing(void *dsmrr_arg);
+
+ Lifo_buffer* get_key_buffer() { return key_buffer; }
+
+ friend class Key_value_records_iterator;
+ friend class Mrr_ordered_index_reader;
+ friend class Mrr_ordered_rndpos_reader;
+
+ int setup_two_handlers();
+ void close_second_handler();
+};
+
+/**
+ @} (end of group DS-MRR declarations)
+*/
+
diff --git a/sql/my_apc.cc b/sql/my_apc.cc
new file mode 100644
index 00000000000..17660688be0
--- /dev/null
+++ b/sql/my_apc.cc
@@ -0,0 +1,270 @@
+/*
+ Copyright (c) 2011, 2013 Monty Program Ab.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#ifndef MY_APC_STANDALONE
+
+#include "sql_class.h"
+
+#endif
+
+/* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */
+
+/*
+ Initialize the target.
+
+ @note
+ Initialization must be done prior to enabling/disabling the target, or making
+ any call requests to it.
+ Initial state after initialization is 'disabled'.
+*/
+void Apc_target::init(mysql_mutex_t *target_mutex)
+{
+ DBUG_ASSERT(!enabled);
+ LOCK_thd_data_ptr= target_mutex;
+#ifndef DBUG_OFF
+ n_calls_processed= 0;
+#endif
+}
+
+
+/*
+ Destroy the target. The target must be disabled when this call is made.
+*/
+void Apc_target::destroy()
+{
+ DBUG_ASSERT(!enabled);
+}
+
+
+/*
+ Enter ther state where the target is available for serving APC requests
+*/
+void Apc_target::enable()
+{
+ /* Ok to do without getting/releasing the mutex: */
+ enabled++;
+}
+
+
+/*
+ Make the target unavailable for serving APC requests.
+
+ @note
+ This call will serve all requests that were already enqueued
+*/
+
+void Apc_target::disable()
+{
+ bool process= FALSE;
+ DBUG_ASSERT(enabled);
+ mysql_mutex_lock(LOCK_thd_data_ptr);
+ if (!(--enabled))
+ process= TRUE;
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
+ if (process)
+ process_apc_requests();
+}
+
+
+/* [internal] Put request qe into the request list */
+
+void Apc_target::enqueue_request(Call_request *qe)
+{
+ mysql_mutex_assert_owner(LOCK_thd_data_ptr);
+ if (apc_calls)
+ {
+ Call_request *after= apc_calls->prev;
+ qe->next= apc_calls;
+ apc_calls->prev= qe;
+
+ qe->prev= after;
+ after->next= qe;
+ }
+ else
+ {
+ apc_calls= qe;
+ qe->next= qe->prev= qe;
+ }
+}
+
+
+/*
+ [internal] Remove request qe from the request queue.
+
+ The request is not necessarily first in the queue.
+*/
+
+void Apc_target::dequeue_request(Call_request *qe)
+{
+ mysql_mutex_assert_owner(LOCK_thd_data_ptr);
+ if (apc_calls == qe)
+ {
+ if ((apc_calls= apc_calls->next) == qe)
+ {
+ apc_calls= NULL;
+ }
+ }
+
+ qe->prev->next= qe->next;
+ qe->next->prev= qe->prev;
+}
+
+#ifdef HAVE_PSI_INTERFACE
+
+/* One key for all conds */
+PSI_cond_key key_show_explain_request_COND;
+
+static PSI_cond_info show_explain_psi_conds[]=
+{
+ { &key_show_explain_request_COND, "show_explain", 0 /* not using PSI_FLAG_GLOBAL*/ }
+};
+
+void init_show_explain_psi_keys(void)
+{
+ if (PSI_server == NULL)
+ return;
+
+ PSI_server->register_cond("sql", show_explain_psi_conds,
+ array_elements(show_explain_psi_conds));
+}
+#endif
+
+
+/*
+ Make an APC (Async Procedure Call) to another thread.
+
+ @detail
+ Make an APC call: schedule it for execution and wait until the target
+ thread has executed it.
+
+ - The caller is responsible for making sure he's not posting request
+ to the thread he's calling this function from.
+
+ - The caller must have locked target_mutex. The function will release it.
+
+ @retval FALSE - Ok, the call has been made
+ @retval TRUE - Call wasnt made (either the target is in disabled state or
+ timeout occured)
+*/
+
+bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call,
+ int timeout_sec, bool *timed_out)
+{
+ bool res= TRUE;
+ *timed_out= FALSE;
+
+ if (enabled)
+ {
+ /* Create and post the request */
+ Call_request apc_request;
+ apc_request.call= call;
+ apc_request.processed= FALSE;
+ mysql_cond_init(key_show_explain_request_COND, &apc_request.COND_request,
+ NULL);
+ enqueue_request(&apc_request);
+ apc_request.what="enqueued by make_apc_call";
+
+ struct timespec abstime;
+ const int timeout= timeout_sec;
+ set_timespec(abstime, timeout);
+
+ int wait_res= 0;
+ PSI_stage_info old_stage;
+ caller_thd->ENTER_COND(&apc_request.COND_request, LOCK_thd_data_ptr,
+ &stage_show_explain, &old_stage);
+ /* todo: how about processing other errors here? */
+ while (!apc_request.processed && (wait_res != ETIMEDOUT))
+ {
+ /* We own LOCK_thd_data_ptr */
+ wait_res= mysql_cond_timedwait(&apc_request.COND_request,
+ LOCK_thd_data_ptr, &abstime);
+ // &apc_request.LOCK_request, &abstime);
+ if (caller_thd->killed)
+ break;
+ }
+
+ if (!apc_request.processed)
+ {
+ /*
+ The wait has timed out, or this thread was KILLed.
+ Remove the request from the queue (ok to do because we own
+ LOCK_thd_data_ptr)
+ */
+ apc_request.processed= TRUE;
+ dequeue_request(&apc_request);
+ *timed_out= TRUE;
+ res= TRUE;
+ }
+ else
+ {
+ /* Request was successfully executed and dequeued by the target thread */
+ res= FALSE;
+ }
+ /*
+ exit_cond() will call mysql_mutex_unlock(LOCK_thd_data_ptr) for us:
+ */
+ caller_thd->EXIT_COND(&old_stage);
+
+ /* Destroy all APC request data */
+ mysql_cond_destroy(&apc_request.COND_request);
+ }
+ else
+ {
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
+ }
+ return res;
+}
+
+
+/*
+ Process all APC requests.
+ This should be called periodically by the APC target thread.
+*/
+
+void Apc_target::process_apc_requests()
+{
+ while (1)
+ {
+ Call_request *request;
+
+ mysql_mutex_lock(LOCK_thd_data_ptr);
+ if (!(request= get_first_in_queue()))
+ {
+ /* No requests in the queue */
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
+ break;
+ }
+
+ /*
+ Remove the request from the queue (we're holding queue lock so we can be
+ sure that request owner won't try to remove it)
+ */
+ request->what="dequeued by process_apc_requests";
+ dequeue_request(request);
+ request->processed= TRUE;
+
+ request->call->call_in_target_thread();
+ request->what="func called by process_apc_requests";
+
+#ifndef DBUG_OFF
+ n_calls_processed++;
+#endif
+ mysql_cond_signal(&request->COND_request);
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
+ }
+}
+
diff --git a/sql/my_apc.h b/sql/my_apc.h
new file mode 100644
index 00000000000..dfeef5eb8ac
--- /dev/null
+++ b/sql/my_apc.h
@@ -0,0 +1,142 @@
+#ifndef SQL_MY_APC_INCLUDED
+#define SQL_MY_APC_INCLUDED
+/*
+ Copyright (c) 2011, 2013 Monty Program Ab.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/*
+ Interface
+ ~~~~~~~~~
+ (
+ - This is an APC request queue
+ - We assume there is a particular owner thread which periodically calls
+ process_apc_requests() to serve the call requests.
+ - Other threads can post call requests, and block until they are exectued.
+ )
+
+ Implementation
+ ~~~~~~~~~~~~~~
+ - The target has a mutex-guarded request queue.
+
+ - After the request has been put into queue, the requestor waits for request
+ to be satisfied. The worker satisifes the request and signals the
+ requestor.
+*/
+
+class THD;
+
+/*
+ Target for asynchronous procedure calls (APCs).
+ - A target is running in some particular thread,
+ - One can make calls to it from other threads.
+*/
+class Apc_target
+{
+ mysql_mutex_t *LOCK_thd_data_ptr;
+public:
+ Apc_target() : enabled(0), apc_calls(NULL) {}
+ ~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);}
+
+ void init(mysql_mutex_t *target_mutex);
+ void destroy();
+ void enable();
+ void disable();
+
+ void process_apc_requests();
+ /*
+ A lightweight function, intended to be used in frequent checks like this:
+
+ if (apc_target.have_requests()) apc_target.process_apc_requests()
+ */
+ inline bool have_apc_requests()
+ {
+ return MY_TEST(apc_calls);
+ }
+
+ inline bool is_enabled() { return enabled; }
+
+ /* Functor class for calls you can schedule */
+ class Apc_call
+ {
+ public:
+ /* This function will be called in the target thread */
+ virtual void call_in_target_thread()= 0;
+ virtual ~Apc_call() {}
+ };
+
+ /* Make a call in the target thread (see function definition for details) */
+ bool make_apc_call(THD *caller_thd, Apc_call *call, int timeout_sec, bool *timed_out);
+
+#ifndef DBUG_OFF
+ int n_calls_processed; /* Number of calls served by this target */
+#endif
+private:
+ class Call_request;
+
+ /*
+ Non-zero value means we're enabled. It's an int, not bool, because one can
+ call enable() N times (and then needs to call disable() N times before the
+ target is really disabled)
+ */
+ int enabled;
+
+ /*
+ Circular, double-linked list of all enqueued call requests.
+ We use this structure, because we
+ - process requests sequentially: requests are added at the end of the
+ list and removed from the front. With circular list, we can keep one
+ pointer, and access both front an back of the list with it.
+ - a thread that has posted a request may time out (or be KILLed) and
+ cancel the request, which means we need a fast request-removal
+ operation.
+ */
+ Call_request *apc_calls;
+
+ class Call_request
+ {
+ public:
+ Apc_call *call; /* Functor to be called */
+
+ /* The caller will actually wait for "processed==TRUE" */
+ bool processed;
+
+ /* Condition that will be signalled when the request has been served */
+ mysql_cond_t COND_request;
+
+ /* Double linked-list linkage */
+ Call_request *next;
+ Call_request *prev;
+
+ const char *what; /* (debug) state of the request */
+ };
+
+ void enqueue_request(Call_request *qe);
+ void dequeue_request(Call_request *qe);
+
+ /* return the first call request in queue, or NULL if there are none enqueued */
+ Call_request *get_first_in_queue()
+ {
+ return apc_calls;
+ }
+};
+
+#ifdef HAVE_PSI_INTERFACE
+void init_show_explain_psi_keys(void);
+#else
+#define init_show_explain_psi_keys() /* no-op */
+#endif
+
+#endif //SQL_MY_APC_INCLUDED
+
diff --git a/sql/my_decimal.cc b/sql/my_decimal.cc
new file mode 100644
index 00000000000..c11bf671cb1
--- /dev/null
+++ b/sql/my_decimal.cc
@@ -0,0 +1,422 @@
+/*
+ Copyright (c) 2005, 2010, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include <time.h>
+
+#ifndef MYSQL_CLIENT
+#include "sql_class.h" // THD
+#endif
+
+#define DIG_BASE 1000000000
+#define DIG_PER_DEC1 9
+#define ROUND_UP(X) (((X)+DIG_PER_DEC1-1)/DIG_PER_DEC1)
+
+#ifndef MYSQL_CLIENT
+/**
+ report result of decimal operation.
+
+ @param result decimal library return code (E_DEC_* see include/decimal.h)
+
+ @todo
+ Fix error messages
+
+ @return
+ result
+*/
+
+int decimal_operation_results(int result, const char *value, const char *type)
+{
+ switch (result) {
+ case E_DEC_OK:
+ break;
+ case E_DEC_TRUNCATED:
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_DATA_TRUNCATED, ER(ER_DATA_TRUNCATED),
+ value, type);
+ break;
+ case E_DEC_OVERFLOW:
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_DATA_OVERFLOW, ER(ER_DATA_OVERFLOW),
+ value, type);
+ break;
+ case E_DEC_DIV_ZERO:
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_DIVISION_BY_ZERO, ER(ER_DIVISION_BY_ZERO));
+ break;
+ case E_DEC_BAD_NUM:
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BAD_DATA, ER(ER_BAD_DATA),
+ value, type);
+ break;
+ case E_DEC_OOM:
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return result;
+}
+
+
+/**
+ @brief Converting decimal to string
+
+ @details Convert given my_decimal to String; allocate buffer as needed.
+
+ @param[in] mask what problems to warn on (mask of E_DEC_* values)
+ @param[in] d the decimal to print
+ @param[in] fixed_prec overall number of digits if ZEROFILL, 0 otherwise
+ @param[in] fixed_dec number of decimal places (if fixed_prec != 0)
+ @param[in] filler what char to pad with (ZEROFILL et al.)
+ @param[out] *str where to store the resulting string
+
+ @return error coce
+ @retval E_DEC_OK
+ @retval E_DEC_TRUNCATED
+ @retval E_DEC_OVERFLOW
+ @retval E_DEC_OOM
+*/
+
+int my_decimal2string(uint mask, const my_decimal *d,
+ uint fixed_prec, uint fixed_dec,
+ char filler, String *str)
+{
+ /*
+ Calculate the size of the string: For DECIMAL(a,b), fixed_prec==a
+ holds true iff the type is also ZEROFILL, which in turn implies
+ UNSIGNED. Hence the buffer for a ZEROFILLed value is the length
+ the user requested, plus one for a possible decimal point, plus
+ one if the user only wanted decimal places, but we force a leading
+ zero on them, plus one for the '\0' terminator. Because the type
+ is implicitly UNSIGNED, we do not need to reserve a character for
+ the sign. For all other cases, fixed_prec will be 0, and
+ my_decimal_string_length() will be called instead to calculate the
+ required size of the buffer.
+ */
+ int length= (fixed_prec
+ ? (fixed_prec + ((fixed_prec == fixed_dec) ? 1 : 0) + 1)
+ : my_decimal_string_length(d));
+ int result;
+ if (str->alloc(length))
+ return check_result(mask, E_DEC_OOM);
+ result= decimal2string((decimal_t*) d, (char*) str->ptr(),
+ &length, (int)fixed_prec, fixed_dec,
+ filler);
+ str->length(length);
+ str->set_charset(&my_charset_numeric);
+ return check_result(mask, result);
+}
+
+
+/**
+ @brief Converting decimal to string with character set conversion
+
+ @details Convert given my_decimal to String; allocate buffer as needed.
+
+ @param[in] mask what problems to warn on (mask of E_DEC_* values)
+ @param[in] val the decimal to print
+ @param[in] fixed_prec overall number of digits if ZEROFILL, 0 otherwise
+ @param[in] fixed_dec number of decimal places (if fixed_prec != 0)
+ @param[in] filler what char to pad with (ZEROFILL et al.)
+ @param[out] *str where to store the resulting string
+ @param[in] cs character set
+
+ @return error coce
+ @retval E_DEC_OK
+ @retval E_DEC_TRUNCATED
+ @retval E_DEC_OVERFLOW
+ @retval E_DEC_OOM
+
+ Would be great to make it a method of the String class,
+ but this would need to include
+ my_decimal.h from sql_string.h and sql_string.cc, which is not desirable.
+*/
+bool
+str_set_decimal(uint mask, const my_decimal *val,
+ uint fixed_prec, uint fixed_dec, char filler,
+ String *str, CHARSET_INFO *cs)
+{
+ if (!(cs->state & MY_CS_NONASCII))
+ {
+ /* For ASCII-compatible character sets we can use my_decimal2string */
+ my_decimal2string(mask, val, fixed_prec, fixed_dec, filler, str);
+ str->set_charset(cs);
+ return FALSE;
+ }
+ else
+ {
+ /*
+ For ASCII-incompatible character sets (like UCS2) we
+ call my_decimal2string() on a temporary buffer first,
+ and then convert the result to the target character
+ with help of str->copy().
+ */
+ uint errors;
+ char buf[DECIMAL_MAX_STR_LENGTH];
+ String tmp(buf, sizeof(buf), &my_charset_latin1);
+ my_decimal2string(mask, val, fixed_prec, fixed_dec, filler, &tmp);
+ return str->copy(tmp.ptr(), tmp.length(), &my_charset_latin1, cs, &errors);
+ }
+}
+
+
+/*
+ Convert from decimal to binary representation
+
+ SYNOPSIS
+ my_decimal2binary()
+ mask error processing mask
+ d number for conversion
+ bin pointer to buffer where to write result
+ prec overall number of decimal digits
+ scale number of decimal digits after decimal point
+
+ NOTE
+ Before conversion we round number if it need but produce truncation
+ error in this case
+
+ RETURN
+ E_DEC_OK
+ E_DEC_TRUNCATED
+ E_DEC_OVERFLOW
+*/
+
+int my_decimal2binary(uint mask, const my_decimal *d, uchar *bin, int prec,
+ int scale)
+{
+ int err1= E_DEC_OK, err2;
+ my_decimal rounded;
+ my_decimal2decimal(d, &rounded);
+ rounded.frac= decimal_actual_fraction(&rounded);
+ if (scale < rounded.frac)
+ {
+ err1= E_DEC_TRUNCATED;
+ /* decimal_round can return only E_DEC_TRUNCATED */
+ decimal_round(&rounded, &rounded, scale, HALF_UP);
+ }
+ err2= decimal2bin(&rounded, bin, prec, scale);
+ if (!err2)
+ err2= err1;
+ return check_result(mask, err2);
+}
+
+
+/*
+ Convert string for decimal when string can be in some multibyte charset
+
+ SYNOPSIS
+ str2my_decimal()
+ mask error processing mask
+ from string to process
+ length length of given string
+ charset charset of given string
+ decimal_value buffer for result storing
+
+ RESULT
+ E_DEC_OK
+ E_DEC_TRUNCATED
+ E_DEC_OVERFLOW
+ E_DEC_BAD_NUM
+ E_DEC_OOM
+*/
+
+int str2my_decimal(uint mask, const char *from, uint length,
+ CHARSET_INFO *charset, my_decimal *decimal_value)
+{
+ char *end, *from_end;
+ int err;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String tmp(buff, sizeof(buff), &my_charset_bin);
+ if (charset->mbminlen > 1)
+ {
+ uint dummy_errors;
+ tmp.copy(from, length, charset, &my_charset_latin1, &dummy_errors);
+ from= tmp.ptr();
+ length= tmp.length();
+ charset= &my_charset_bin;
+ }
+ from_end= end= (char*) from+length;
+ err= string2decimal((char *)from, (decimal_t*) decimal_value, &end);
+ if (end != from_end && !err)
+ {
+ /* Give warning if there is something other than end space */
+ for ( ; end < from_end; end++)
+ {
+ if (!my_isspace(&my_charset_latin1, *end))
+ {
+ err= E_DEC_TRUNCATED;
+ break;
+ }
+ }
+ }
+ check_result_and_overflow(mask, err, decimal_value);
+ return err;
+}
+
+
+/**
+ converts a decimal into a pair of integers - for integer and fractional parts
+
+ special version, for decimals representing number of seconds.
+ integer part cannot be larger that 1e18 (otherwise it's an overflow).
+ fractional part is microseconds.
+*/
+bool my_decimal2seconds(const my_decimal *d, ulonglong *sec, ulong *microsec)
+{
+ int pos;
+
+ if (d->intg)
+ {
+ pos= (d->intg-1)/DIG_PER_DEC1;
+ *sec= d->buf[pos];
+ if (pos > 0)
+ *sec+= static_cast<longlong>(d->buf[pos-1]) * DIG_BASE;
+ }
+ else
+ {
+ *sec=0;
+ pos= -1;
+ }
+
+ *microsec= d->frac ? static_cast<longlong>(d->buf[pos+1]) / (DIG_BASE/1000000) : 0;
+
+ if (pos > 1)
+ {
+ for (int i=0; i < pos-1; i++)
+ if (d->buf[i])
+ {
+ *sec= LONGLONG_MAX;
+ break;
+ }
+ }
+ return d->sign();
+}
+
+
+/**
+ converts a pair of integers (seconds, microseconds) into a decimal
+*/
+my_decimal *seconds2my_decimal(bool sign,
+ ulonglong sec, ulong microsec, my_decimal *d)
+{
+ d->init();
+ longlong2decimal(sec, d); // cannot fail
+ if (microsec)
+ {
+ d->buf[(d->intg-1) / DIG_PER_DEC1 + 1]= microsec * (DIG_BASE/1000000);
+ d->frac= 6;
+ }
+ ((decimal_t *)d)->sign= sign;
+ return d;
+}
+
+
+my_decimal *date2my_decimal(MYSQL_TIME *ltime, my_decimal *dec)
+{
+ longlong date= (ltime->year*100L + ltime->month)*100L + ltime->day;
+ if (ltime->time_type > MYSQL_TIMESTAMP_DATE)
+ date= ((date*100L + ltime->hour)*100L+ ltime->minute)*100L + ltime->second;
+ return seconds2my_decimal(ltime->neg, date, ltime->second_part, dec);
+}
+
+
+void my_decimal_trim(ulong *precision, uint *scale)
+{
+ if (!(*precision) && !(*scale))
+ {
+ *precision= 10;
+ *scale= 0;
+ return;
+ }
+}
+
+
+/*
+ Convert a decimal to an ulong with a descriptive error message
+*/
+
+int my_decimal2int(uint mask, const decimal_t *d, bool unsigned_flag,
+ longlong *l)
+{
+ int res;
+ my_decimal rounded;
+ /* decimal_round can return only E_DEC_TRUNCATED */
+ decimal_round(d, &rounded, 0, HALF_UP);
+ res= (unsigned_flag ?
+ decimal2ulonglong(&rounded, (ulonglong *) l) :
+ decimal2longlong(&rounded, l));
+ if (res & mask)
+ {
+ char buff[DECIMAL_MAX_STR_LENGTH];
+ int length= sizeof(buff);
+ decimal2string(d, buff, &length, 0, 0, 0);
+
+ decimal_operation_results(res, buff,
+ unsigned_flag ? "UNSIGNED INT" :
+ "INT");
+ }
+ return res;
+}
+
+
+#ifndef DBUG_OFF
+/* routines for debugging print */
+
+/* print decimal */
+void
+print_decimal(const my_decimal *dec)
+{
+ int i, end;
+ char buff[512], *pos;
+ pos= buff;
+ pos+= sprintf(buff, "Decimal: sign: %d intg: %d frac: %d { ",
+ dec->sign(), dec->intg, dec->frac);
+ end= ROUND_UP(dec->frac)+ROUND_UP(dec->intg)-1;
+ for (i=0; i < end; i++)
+ pos+= sprintf(pos, "%09d, ", dec->buf[i]);
+ pos+= sprintf(pos, "%09d }\n", dec->buf[i]);
+ fputs(buff, DBUG_FILE);
+}
+
+
+/* print decimal with its binary representation */
+void
+print_decimal_buff(const my_decimal *dec, const uchar* ptr, int length)
+{
+ print_decimal(dec);
+ fprintf(DBUG_FILE, "Record: ");
+ for (int i= 0; i < length; i++)
+ {
+ fprintf(DBUG_FILE, "%02X ", (uint)((uchar *)ptr)[i]);
+ }
+ fprintf(DBUG_FILE, "\n");
+}
+
+
+const char *dbug_decimal_as_string(char *buff, const my_decimal *val)
+{
+ int length= DECIMAL_MAX_STR_LENGTH + 1; /* minimum size for buff */
+ if (!val)
+ return "NULL";
+ (void)decimal2string((decimal_t*) val, buff, &length, 0,0,0);
+ return buff;
+}
+
+
+#endif /*DBUG_OFF*/
+#endif /*MYSQL_CLIENT*/
diff --git a/sql/my_decimal.h b/sql/my_decimal.h
new file mode 100644
index 00000000000..fa85b41d70c
--- /dev/null
+++ b/sql/my_decimal.h
@@ -0,0 +1,493 @@
+/* Copyright (c) 2005, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2014, SkySQL Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/**
+ @file
+
+ It is interface module to fixed precision decimals library.
+
+ Most functions use 'uint mask' as parameter, if during operation error
+ which fit in this mask is detected then it will be processed automatically
+ here. (errors are E_DEC_* constants, see include/decimal.h)
+
+ Most function are just inline wrappers around library calls
+*/
+
+#ifndef my_decimal_h
+#define my_decimal_h
+
+#if defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY)
+#include "sql_string.h" /* String */
+#endif
+
+C_MODE_START
+#include <decimal.h>
+#include <my_decimal_limits.h>
+C_MODE_END
+
+class String;
+typedef struct st_mysql_time MYSQL_TIME;
+
+/**
+ maximum size of packet length.
+*/
+#define DECIMAL_MAX_FIELD_SIZE DECIMAL_MAX_PRECISION
+
+
+inline uint my_decimal_size(uint precision, uint scale)
+{
+ /*
+ Always allocate more space to allow library to put decimal point
+ where it want
+ */
+ return decimal_size(precision, scale) + 1;
+}
+
+
+inline int my_decimal_int_part(uint precision, uint decimals)
+{
+ return precision - ((decimals == DECIMAL_NOT_SPECIFIED) ? 0 : decimals);
+}
+
+
+/**
+ my_decimal class limits 'decimal_t' type to what we need in MySQL.
+
+ It contains internally all necessary space needed by the instance so
+ no extra memory is needed. One should call fix_buffer_pointer() function
+ when he moves my_decimal objects in memory.
+*/
+
+class my_decimal :public decimal_t
+{
+ /*
+ Several of the routines in strings/decimal.c have had buffer
+ overrun/underrun problems. These are *not* caught by valgrind.
+ To catch them, we allocate dummy fields around the buffer,
+ and test that their values do not change.
+ */
+#if !defined(DBUG_OFF)
+ int foo1;
+#endif
+
+ decimal_digit_t buffer[DECIMAL_BUFF_LENGTH];
+
+#if !defined(DBUG_OFF)
+ int foo2;
+ static const int test_value= 123;
+#endif
+
+public:
+
+ my_decimal(const my_decimal &rhs) : decimal_t(rhs)
+ {
+ init();
+ for (uint i= 0; i < DECIMAL_BUFF_LENGTH; i++)
+ buffer[i]= rhs.buffer[i];
+ }
+
+ my_decimal& operator=(const my_decimal &rhs)
+ {
+ if (this == &rhs)
+ return *this;
+ decimal_t::operator=(rhs);
+ for (uint i= 0; i < DECIMAL_BUFF_LENGTH; i++)
+ buffer[i]= rhs.buffer[i];
+ fix_buffer_pointer();
+ return *this;
+ }
+
+ void init()
+ {
+#if !defined(DBUG_OFF)
+ foo1= test_value;
+ foo2= test_value;
+#endif
+ len= DECIMAL_BUFF_LENGTH;
+ buf= buffer;
+ TRASH_ALLOC(buffer, sizeof(buffer));
+ }
+
+ my_decimal()
+ {
+ init();
+ }
+ ~my_decimal()
+ {
+ sanity_check();
+ }
+
+ void sanity_check()
+ {
+ DBUG_ASSERT(foo1 == test_value);
+ DBUG_ASSERT(foo2 == test_value);
+ }
+
+ void fix_buffer_pointer() { buf= buffer; }
+
+ bool sign() const { return decimal_t::sign; }
+ void sign(bool s) { decimal_t::sign= s; }
+ uint precision() const { return intg + frac; }
+
+ /** Swap two my_decimal values */
+ void swap(my_decimal &rhs)
+ {
+ swap_variables(my_decimal, *this, rhs);
+ }
+};
+
+
+#ifndef DBUG_OFF
+void print_decimal(const my_decimal *dec);
+void print_decimal_buff(const my_decimal *dec, const uchar* ptr, int length);
+const char *dbug_decimal_as_string(char *buff, const my_decimal *val);
+#else
+#define dbug_decimal_as_string(A) NULL
+#endif
+
+bool str_set_decimal(uint mask, const my_decimal *val, uint fixed_prec,
+ uint fixed_dec, char filler, String *str,
+ CHARSET_INFO *cs);
+
+extern my_decimal decimal_zero;
+
+#ifndef MYSQL_CLIENT
+int decimal_operation_results(int result, const char *value, const char *type);
+#else
+inline int decimal_operation_results(int result, const char *value,
+ const char *type)
+{
+ return result;
+}
+#endif /*MYSQL_CLIENT*/
+
+inline
+void max_my_decimal(my_decimal *to, int precision, int frac)
+{
+ DBUG_ASSERT((precision <= DECIMAL_MAX_PRECISION)&&
+ (frac <= DECIMAL_MAX_SCALE));
+ max_decimal(precision, frac, to);
+}
+
+inline void max_internal_decimal(my_decimal *to)
+{
+ max_my_decimal(to, DECIMAL_MAX_PRECISION, 0);
+}
+
+inline int check_result(uint mask, int result)
+{
+ if (result & mask)
+ decimal_operation_results(result, "", "DECIMAL");
+ return result;
+}
+
+inline int check_result_and_overflow(uint mask, int result, my_decimal *val)
+{
+ if (check_result(mask, result) & E_DEC_OVERFLOW)
+ {
+ bool sign= val->sign();
+ val->fix_buffer_pointer();
+ max_internal_decimal(val);
+ val->sign(sign);
+ }
+ return result;
+}
+
+inline uint my_decimal_length_to_precision(uint length, uint scale,
+ bool unsigned_flag)
+{
+ /* Precision can't be negative thus ignore unsigned_flag when length is 0. */
+ DBUG_ASSERT(length || !scale);
+ return (uint) (length - (scale>0 ? 1:0) -
+ (unsigned_flag || !length ? 0:1));
+}
+
+inline uint32 my_decimal_precision_to_length_no_truncation(uint precision,
+ uint8 scale,
+ bool unsigned_flag)
+{
+ /*
+ When precision is 0 it means that original length was also 0. Thus
+ unsigned_flag is ignored in this case.
+ */
+ DBUG_ASSERT(precision || !scale);
+ return (uint32)(precision + (scale > 0 ? 1 : 0) +
+ (unsigned_flag || !precision ? 0 : 1));
+}
+
+inline uint32 my_decimal_precision_to_length(uint precision, uint8 scale,
+ bool unsigned_flag)
+{
+ /*
+ When precision is 0 it means that original length was also 0. Thus
+ unsigned_flag is ignored in this case.
+ */
+ DBUG_ASSERT(precision || !scale);
+ set_if_smaller(precision, DECIMAL_MAX_PRECISION);
+ return my_decimal_precision_to_length_no_truncation(precision, scale,
+ unsigned_flag);
+}
+
+inline
+int my_decimal_string_length(const my_decimal *d)
+{
+ /* length of string representation including terminating '\0' */
+ return decimal_string_size(d);
+}
+
+
+inline
+int my_decimal_max_length(const my_decimal *d)
+{
+ /* -1 because we do not count \0 */
+ return decimal_string_size(d) - 1;
+}
+
+
+inline
+int my_decimal_get_binary_size(uint precision, uint scale)
+{
+ return decimal_bin_size((int)precision, (int)scale);
+}
+
+
+inline
+void my_decimal2decimal(const my_decimal *from, my_decimal *to)
+{
+ *to= *from;
+}
+
+
+int my_decimal2binary(uint mask, const my_decimal *d, uchar *bin, int prec,
+ int scale);
+
+
+inline
+int binary2my_decimal(uint mask, const uchar *bin, my_decimal *d, int prec,
+ int scale)
+{
+ return check_result(mask, bin2decimal(bin, d, prec, scale));
+}
+
+
+inline
+int my_decimal_set_zero(my_decimal *d)
+{
+ /*
+ We need the up-cast here, since my_decimal has sign() member functions,
+ which conflicts with decimal_t::size
+ (and decimal_make_zero is a macro, rather than a funcion).
+ */
+ decimal_make_zero(static_cast<decimal_t*>(d));
+ return 0;
+}
+
+
+inline
+bool my_decimal_is_zero(const my_decimal *decimal_value)
+{
+ return decimal_is_zero(decimal_value);
+}
+
+
+inline
+int my_decimal_round(uint mask, const my_decimal *from, int scale,
+ bool truncate, my_decimal *to)
+{
+ return check_result(mask, decimal_round(from, to, scale,
+ (truncate ? TRUNCATE : HALF_UP)));
+}
+
+
+inline
+int my_decimal_floor(uint mask, const my_decimal *from, my_decimal *to)
+{
+ return check_result(mask, decimal_round(from, to, 0, FLOOR));
+}
+
+
+inline
+int my_decimal_ceiling(uint mask, const my_decimal *from, my_decimal *to)
+{
+ return check_result(mask, decimal_round(from, to, 0, CEILING));
+}
+
+
+inline bool str_set_decimal(const my_decimal *val, String *str,
+ CHARSET_INFO *cs)
+{
+ return str_set_decimal(E_DEC_FATAL_ERROR, val, 0, 0, 0, str, cs);
+}
+
+#ifndef MYSQL_CLIENT
+class String;
+int my_decimal2string(uint mask, const my_decimal *d, uint fixed_prec,
+ uint fixed_dec, char filler, String *str);
+#endif
+
+bool my_decimal2seconds(const my_decimal *d, ulonglong *sec, ulong *microsec);
+
+my_decimal *seconds2my_decimal(bool sign, ulonglong sec, ulong microsec,
+ my_decimal *d);
+
+#define TIME_to_my_decimal(TIME, DECIMAL) \
+ seconds2my_decimal((TIME)->neg, TIME_to_ulonglong(TIME), \
+ (TIME)->second_part, (DECIMAL))
+
+int my_decimal2int(uint mask, const decimal_t *d, bool unsigned_flag,
+ longlong *l);
+
+inline
+int my_decimal2double(uint, const decimal_t *d, double *result)
+{
+ /* No need to call check_result as this will always succeed */
+ return decimal2double(d, result);
+}
+
+
+inline
+int str2my_decimal(uint mask, const char *str, my_decimal *d, char **end)
+{
+ return check_result_and_overflow(mask, string2decimal(str, d, end), d);
+}
+
+
+int str2my_decimal(uint mask, const char *from, uint length,
+ CHARSET_INFO *charset, my_decimal *decimal_value);
+
+#if defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY)
+inline
+int string2my_decimal(uint mask, const String *str, my_decimal *d)
+{
+ return str2my_decimal(mask, str->ptr(), str->length(), str->charset(), d);
+}
+
+
+my_decimal *date2my_decimal(MYSQL_TIME *ltime, my_decimal *dec);
+
+
+#endif /*defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) */
+
+inline
+int double2my_decimal(uint mask, double val, my_decimal *d)
+{
+ return check_result_and_overflow(mask, double2decimal(val, d), d);
+}
+
+
+inline
+int int2my_decimal(uint mask, longlong i, my_bool unsigned_flag, my_decimal *d)
+{
+ return check_result(mask, (unsigned_flag ?
+ ulonglong2decimal((ulonglong)i, d) :
+ longlong2decimal(i, d)));
+}
+
+inline
+void decimal2my_decimal(decimal_t *from, my_decimal *to)
+{
+ DBUG_ASSERT(to->len >= from->len);
+ to->intg= from->intg;
+ to->frac= from->frac;
+ to->sign(from->sign);
+ memcpy(to->buf, from->buf, to->len*sizeof(decimal_digit_t));
+}
+
+
+inline
+void my_decimal_neg(decimal_t *arg)
+{
+ if (decimal_is_zero(arg))
+ {
+ arg->sign= 0;
+ return;
+ }
+ decimal_neg(arg);
+}
+
+
+inline
+int my_decimal_add(uint mask, my_decimal *res, const my_decimal *a,
+ const my_decimal *b)
+{
+ return check_result_and_overflow(mask,
+ decimal_add(a, b, res),
+ res);
+}
+
+
+inline
+int my_decimal_sub(uint mask, my_decimal *res, const my_decimal *a,
+ const my_decimal *b)
+{
+ return check_result_and_overflow(mask,
+ decimal_sub(a, b, res),
+ res);
+}
+
+
+inline
+int my_decimal_mul(uint mask, my_decimal *res, const my_decimal *a,
+ const my_decimal *b)
+{
+ return check_result_and_overflow(mask,
+ decimal_mul(a, b, res),
+ res);
+}
+
+
+inline
+int my_decimal_div(uint mask, my_decimal *res, const my_decimal *a,
+ const my_decimal *b, int div_scale_inc)
+{
+ return check_result_and_overflow(mask,
+ decimal_div(a, b, res, div_scale_inc),
+ res);
+}
+
+
+inline
+int my_decimal_mod(uint mask, my_decimal *res, const my_decimal *a,
+ const my_decimal *b)
+{
+ return check_result_and_overflow(mask,
+ decimal_mod(a, b, res),
+ res);
+}
+
+/**
+ @return
+ -1 if a<b, 1 if a>b and 0 if a==b
+*/
+inline
+int my_decimal_cmp(const my_decimal *a, const my_decimal *b)
+{
+ return decimal_cmp(a, b);
+}
+
+
+inline
+int my_decimal_intg(const my_decimal *a)
+{
+ return decimal_intg(a);
+}
+
+
+void my_decimal_trim(ulong *precision, uint *scale);
+
+
+#endif /*my_decimal_h*/
+
diff --git a/sql/mysql_install_db.cc b/sql/mysql_install_db.cc
new file mode 100644
index 00000000000..50454f0f66a
--- /dev/null
+++ b/sql/mysql_install_db.cc
@@ -0,0 +1,662 @@
+/* Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/*
+ mysql_install_db creates a new database instance (optionally as service)
+ on Windows.
+*/
+#define DONT_DEFINE_VOID
+#include <my_global.h>
+#include <my_getopt.h>
+#include <my_sys.h>
+#include <m_string.h>
+
+#include <windows.h>
+#include <shellapi.h>
+#include <accctrl.h>
+#include <aclapi.h>
+
+#define USAGETEXT \
+"mysql_install_db.exe Ver 1.00 for Windows\n" \
+"Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub\n" \
+"This software comes with ABSOLUTELY NO WARRANTY. This is free software,\n" \
+"and you are welcome to modify and redistribute it under the GPL v2 license\n" \
+"Usage: mysql_install_db.exe [OPTIONS]\n" \
+"OPTIONS:"
+
+extern "C" const char* mysql_bootstrap_sql[];
+
+char default_os_user[]= "NT AUTHORITY\\NetworkService";
+static int create_db_instance();
+static uint opt_silent;
+static char datadir_buffer[FN_REFLEN];
+static char mysqld_path[FN_REFLEN];
+static char *opt_datadir;
+static char *opt_service;
+static char *opt_password;
+static int opt_port;
+static char *opt_socket;
+static char *opt_os_user;
+static char *opt_os_password;
+static my_bool opt_default_user;
+static my_bool opt_allow_remote_root_access;
+static my_bool opt_skip_networking;
+static my_bool opt_verbose_bootstrap;
+static my_bool verbose_errors;
+
+
+static struct my_option my_long_options[]=
+{
+ {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"datadir", 'd', "Data directory of the new database",
+ &opt_datadir, &opt_datadir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"service", 'S', "Name of the Windows service",
+ &opt_service, &opt_service, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"password", 'p', "Root password",
+ &opt_password, &opt_password, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"port", 'P', "mysql port",
+ &opt_port, &opt_port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"socket", 'W',
+ "named pipe name (if missing, it will be set the same as service)",
+ &opt_socket, &opt_socket, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"default-user", 'D', "Create default user",
+ &opt_default_user, &opt_default_user, 0 , GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"allow-remote-root-access", 'R',
+ "Allows remote access from network for user root",
+ &opt_allow_remote_root_access, &opt_allow_remote_root_access, 0 , GET_BOOL,
+ OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"skip-networking", 'N', "Do not use TCP connections, use pipe instead",
+ &opt_skip_networking, &opt_skip_networking, 0 , GET_BOOL, OPT_ARG, 0, 0, 0, 0,
+ 0, 0},
+ {"silent", 's', "Print less information", &opt_silent,
+ &opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"verbose-bootstrap", 'o', "Include mysqld bootstrap output",&opt_verbose_bootstrap,
+ &opt_verbose_bootstrap, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+};
+
+
+static my_bool
+get_one_option(int optid,
+ const struct my_option *opt __attribute__ ((unused)),
+ char *argument __attribute__ ((unused)))
+{
+ DBUG_ENTER("get_one_option");
+ switch (optid) {
+ case '?':
+ printf("%s\n", USAGETEXT);
+ my_print_help(my_long_options);
+ exit(0);
+ break;
+ }
+ DBUG_RETURN(0);
+}
+
+
+static void die(const char *fmt, ...)
+{
+ va_list args;
+ DBUG_ENTER("die");
+
+ /* Print the error message */
+ va_start(args, fmt);
+ fprintf(stderr, "FATAL ERROR: ");
+ vfprintf(stderr, fmt, args);
+ fputc('\n', stderr);
+ if (verbose_errors)
+ {
+ fprintf(stderr,
+ "http://kb.askmonty.org/v/installation-issues-on-windows contains some help\n"
+ "for solving the most common problems. If this doesn't help you, please\n"
+ "leave a comment in the Knowledgebase or file a bug report at\n"
+ "http://mariadb.org/jira");
+ }
+ fflush(stderr);
+ va_end(args);
+ my_end(0);
+ exit(1);
+}
+
+
+static void verbose(const char *fmt, ...)
+{
+ va_list args;
+
+ if (opt_silent)
+ return;
+
+ /* Print the verbose message */
+ va_start(args, fmt);
+ vfprintf(stdout, fmt, args);
+ fputc('\n', stdout);
+ fflush(stdout);
+ va_end(args);
+}
+
+
+int main(int argc, char **argv)
+{
+ int error;
+ char self_name[FN_REFLEN];
+ char *p;
+
+ MY_INIT(argv[0]);
+ GetModuleFileName(NULL, self_name, FN_REFLEN);
+ strcpy(mysqld_path,self_name);
+ p= strrchr(mysqld_path, FN_LIBCHAR);
+ if (p)
+ {
+ strcpy(p, "\\mysqld.exe");
+ }
+
+ if ((error= handle_options(&argc, &argv, my_long_options, get_one_option)))
+ exit(error);
+ if (!opt_datadir)
+ {
+ my_print_help(my_long_options);
+ die("parameter --datadir=# is mandatory");
+ }
+
+ /* Print some help on errors */
+ verbose_errors= TRUE;
+
+ if (!opt_os_user)
+ {
+ opt_os_user= default_os_user;
+ opt_os_password= NULL;
+ }
+ /* Workaround WiX bug (strip possible quote character at the end of path) */
+ size_t len= strlen(opt_datadir);
+ if (len > 0)
+ {
+ if (opt_datadir[len-1] == '"')
+ {
+ opt_datadir[len-1]= 0;
+ }
+ }
+ GetFullPathName(opt_datadir, FN_REFLEN, datadir_buffer, NULL);
+ opt_datadir= datadir_buffer;
+
+ if (create_db_instance())
+ {
+ die("database creation failed");
+ }
+
+ printf("Creation of the database was successfull");
+ return 0;
+}
+
+
+
+/**
+ Convert slashes in paths into MySQL-compatible form
+*/
+
+static void convert_slashes(char *s)
+{
+ for (; *s ; s++)
+ if (*s == '\\')
+ *s= '/';
+}
+
+
+/**
+ Calculate basedir from mysqld.exe path.
+ Basedir assumed to be is one level up from the mysqld.exe directory location.
+ E.g basedir for C:\my\bin\mysqld.exe would be C:\my
+*/
+
+static void get_basedir(char *basedir, int size, const char *mysqld_path)
+{
+ strcpy_s(basedir, size, mysqld_path);
+ convert_slashes(basedir);
+ char *p= strrchr(basedir,'/');
+ if (p)
+ {
+ *p = 0;
+ p= strrchr(basedir, '/');
+ if (p)
+ *p= 0;
+ }
+}
+
+
+/**
+ Allocate and initialize command line for mysqld --bootstrap.
+ The resulting string is passed to popen, so it has a lot of quoting
+ quoting around the full string plus quoting around parameters with spaces.
+*/
+
+static char *init_bootstrap_command_line(char *cmdline, size_t size)
+{
+ char basedir[MAX_PATH];
+ get_basedir(basedir, sizeof(basedir), mysqld_path);
+
+ my_snprintf(cmdline, size-1,
+ "\"\"%s\" --no-defaults %s --bootstrap"
+ " \"--lc-messages-dir=%s/share\""
+ " --basedir=. --datadir=. --default-storage-engine=myisam"
+ " --max_allowed_packet=9M "
+ " --net-buffer-length=16k\"", mysqld_path,
+ opt_verbose_bootstrap?"--console":"", basedir );
+ return cmdline;
+}
+
+
+/**
+ Create my.ini in current directory (this is assumed to be
+ data directory as well).
+*/
+
+static int create_myini()
+{
+ my_bool enable_named_pipe= FALSE;
+ printf("Creating my.ini file\n");
+
+ char path_buf[MAX_PATH];
+ GetCurrentDirectory(MAX_PATH, path_buf);
+
+ /* Create ini file. */
+ FILE *myini= fopen("my.ini","wt");
+ if (!myini)
+ {
+ die("Cannot create my.ini in data directory");
+ }
+
+ /* Write out server settings. */
+ fprintf(myini, "[mysqld]\n");
+ convert_slashes(path_buf);
+ fprintf(myini, "datadir=%s\n", path_buf);
+ if (opt_skip_networking)
+ {
+ fprintf(myini,"skip-networking\n");
+ if (!opt_socket)
+ opt_socket= opt_service;
+ }
+ enable_named_pipe= (my_bool)
+ ((opt_socket && opt_socket[0]) || opt_skip_networking);
+
+ if (enable_named_pipe)
+ {
+ fprintf(myini,"enable-named-pipe\n");
+ }
+
+ if (opt_socket && opt_socket[0])
+ {
+ fprintf(myini, "socket=%s\n", opt_socket);
+ }
+ if (opt_port)
+ {
+ fprintf(myini,"port=%d\n", opt_port);
+ }
+
+ /* Write out client settings. */
+ fprintf(myini, "[client]\n");
+
+ /* Used for named pipes */
+ if (opt_socket && opt_socket[0])
+ fprintf(myini,"socket=%s\n",opt_socket);
+ if (opt_skip_networking)
+ fprintf(myini,"protocol=pipe\n");
+ else if (opt_port)
+ fprintf(myini,"port=%d\n",opt_port);
+ fclose(myini);
+ return 0;
+}
+
+
+static const char update_root_passwd_part1[]=
+ "UPDATE mysql.user SET Password = PASSWORD(";
+static const char update_root_passwd_part2[]=
+ ") where User='root';\n";
+static const char remove_default_user_cmd[]=
+ "DELETE FROM mysql.user where User='';\n";
+static const char allow_remote_root_access_cmd[]=
+ "CREATE TEMPORARY TABLE tmp_user LIKE user;\n"
+ "INSERT INTO tmp_user SELECT * from user where user='root' "
+ " AND host='localhost';\n"
+ "UPDATE tmp_user SET host='%';\n"
+ "INSERT INTO user SELECT * FROM tmp_user;\n"
+ "DROP TABLE tmp_user;\n";
+static const char end_of_script[]="-- end.";
+
+/* Register service. Assume my.ini is in datadir */
+
+static int register_service()
+{
+ char buf[3*MAX_PATH +32]; /* path to mysqld.exe, to my.ini, service name */
+ SC_HANDLE sc_manager, sc_service;
+
+ size_t datadir_len= strlen(opt_datadir);
+ const char *backslash_after_datadir= "\\";
+
+ if (datadir_len && opt_datadir[datadir_len-1] == '\\')
+ backslash_after_datadir= "";
+
+ verbose("Registering service '%s'", opt_service);
+ my_snprintf(buf, sizeof(buf)-1,
+ "\"%s\" \"--defaults-file=%s%smy.ini\" \"%s\"" , mysqld_path, opt_datadir,
+ backslash_after_datadir, opt_service);
+
+ /* Get a handle to the SCM database. */
+ sc_manager= OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (!sc_manager)
+ {
+ die("OpenSCManager failed (%u)\n", GetLastError());
+ }
+
+ /* Create the service. */
+ sc_service= CreateService(sc_manager, opt_service, opt_service,
+ SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
+ SERVICE_ERROR_NORMAL, buf, NULL, NULL, NULL, opt_os_user, opt_os_password);
+
+ if (!sc_service)
+ {
+ CloseServiceHandle(sc_manager);
+ die("CreateService failed (%u)", GetLastError());
+ }
+
+ SERVICE_DESCRIPTION sd= { "MariaDB database server" };
+ ChangeServiceConfig2(sc_service, SERVICE_CONFIG_DESCRIPTION, &sd);
+ CloseServiceHandle(sc_service);
+ CloseServiceHandle(sc_manager);
+ return 0;
+}
+
+
+static void clean_directory(const char *dir)
+{
+ char dir2[MAX_PATH+2];
+ *(strmake_buf(dir2, dir)+1)= 0;
+
+ SHFILEOPSTRUCT fileop;
+ fileop.hwnd= NULL; /* no status display */
+ fileop.wFunc= FO_DELETE; /* delete operation */
+ fileop.pFrom= dir2; /* source file name as double null terminated string */
+ fileop.pTo= NULL; /* no destination needed */
+ fileop.fFlags= FOF_NOCONFIRMATION|FOF_SILENT; /* do not prompt the user */
+
+
+ fileop.fAnyOperationsAborted= FALSE;
+ fileop.lpszProgressTitle= NULL;
+ fileop.hNameMappings= NULL;
+
+ SHFileOperation(&fileop);
+}
+
+
+/*
+ Define directory permission to have inheritable all access for a user
+ (defined as username or group string or as SID)
+*/
+
+static int set_directory_permissions(const char *dir, const char *os_user)
+{
+
+ struct{
+ TOKEN_USER tokenUser;
+ BYTE buffer[SECURITY_MAX_SID_SIZE];
+ } tokenInfoBuffer;
+
+ HANDLE hDir= CreateFile(dir,READ_CONTROL|WRITE_DAC,0,NULL,OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,NULL);
+ if (hDir == INVALID_HANDLE_VALUE)
+ return -1;
+ ACL* pOldDACL;
+ SECURITY_DESCRIPTOR* pSD= NULL;
+ EXPLICIT_ACCESS ea={0};
+ BOOL isWellKnownSID= FALSE;
+ WELL_KNOWN_SID_TYPE wellKnownSidType = WinNullSid;
+ PSID pSid= NULL;
+
+ GetSecurityInfo(hDir, SE_FILE_OBJECT , DACL_SECURITY_INFORMATION,NULL, NULL,
+ &pOldDACL, NULL, (void**)&pSD);
+
+ if (os_user)
+ {
+ /* Check for 3 predefined service users
+ They might have localized names in non-English Windows, thus they need
+ to be handled using well-known SIDs.
+ */
+ if (stricmp(os_user, "NT AUTHORITY\\NetworkService") == 0)
+ {
+ wellKnownSidType= WinNetworkServiceSid;
+ }
+ else if (stricmp(os_user, "NT AUTHORITY\\LocalService") == 0)
+ {
+ wellKnownSidType= WinLocalServiceSid;
+ }
+ else if (stricmp(os_user, "NT AUTHORITY\\LocalSystem") == 0)
+ {
+ wellKnownSidType= WinLocalSystemSid;
+ }
+
+ if (wellKnownSidType != WinNullSid)
+ {
+ DWORD size= SECURITY_MAX_SID_SIZE;
+ pSid= (PSID)tokenInfoBuffer.buffer;
+ if (!CreateWellKnownSid(wellKnownSidType, NULL, pSid,
+ &size))
+ {
+ return 1;
+ }
+ ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
+ ea.Trustee.ptstrName= (LPTSTR)pSid;
+ }
+ else
+ {
+ ea.Trustee.TrusteeForm= TRUSTEE_IS_NAME;
+ ea.Trustee.ptstrName= (LPSTR)os_user;
+ }
+ }
+ else
+ {
+ HANDLE token;
+ if (OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY, &token))
+ {
+
+ DWORD length= (DWORD) sizeof(tokenInfoBuffer);
+ if (GetTokenInformation(token, TokenUser, &tokenInfoBuffer,
+ length, &length))
+ {
+ pSid= tokenInfoBuffer.tokenUser.User.Sid;
+ }
+ }
+ if (!pSid)
+ return 0;
+ ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
+ ea.Trustee.ptstrName= (LPTSTR)pSid;
+ }
+ ea.grfAccessMode= GRANT_ACCESS;
+ ea.grfAccessPermissions= GENERIC_ALL;
+ ea.grfInheritance= CONTAINER_INHERIT_ACE|OBJECT_INHERIT_ACE;
+ ea.Trustee.TrusteeType= TRUSTEE_IS_UNKNOWN;
+ ACL* pNewDACL= 0;
+ DWORD err= SetEntriesInAcl(1,&ea,pOldDACL,&pNewDACL);
+ if (pNewDACL)
+ {
+ SetSecurityInfo(hDir,SE_FILE_OBJECT,DACL_SECURITY_INFORMATION,NULL, NULL,
+ pNewDACL, NULL);
+ }
+ if (pSD != NULL)
+ LocalFree((HLOCAL) pSD);
+ if (pNewDACL != NULL)
+ LocalFree((HLOCAL) pNewDACL);
+ CloseHandle(hDir);
+ return 0;
+}
+
+
+/*
+ Give directory permissions for special service user NT SERVICE\servicename
+ this user is available only on Win7 and later.
+*/
+
+void grant_directory_permissions_to_service()
+{
+ char service_user[MAX_PATH+ 12];
+ OSVERSIONINFO info;
+ info.dwOSVersionInfoSize= sizeof(info);
+ GetVersionEx(&info);
+ if (info.dwMajorVersion >6 ||
+ (info.dwMajorVersion== 6 && info.dwMinorVersion > 0)
+ && opt_service)
+ {
+ my_snprintf(service_user,sizeof(service_user), "NT SERVICE\\%s",
+ opt_service);
+ set_directory_permissions(opt_datadir, service_user);
+ }
+}
+
+
+/* Create database instance (including registering as service etc) .*/
+
+static int create_db_instance()
+{
+ int ret= 0;
+ char cwd[MAX_PATH];
+ DWORD cwd_len= MAX_PATH;
+ char cmdline[3*MAX_PATH];
+ FILE *in;
+
+ verbose("Running bootstrap");
+
+ GetCurrentDirectory(cwd_len, cwd);
+ CreateDirectory(opt_datadir, NULL); /*ignore error, it might already exist */
+
+ if (!SetCurrentDirectory(opt_datadir))
+ {
+ die("Cannot set current directory to '%s'\n",opt_datadir);
+ return -1;
+ }
+
+ CreateDirectory("mysql",NULL);
+ CreateDirectory("test", NULL);
+
+ /*
+ Set data directory permissions for both current user and
+ default_os_user (the one who runs services).
+ */
+ set_directory_permissions(opt_datadir, NULL);
+ set_directory_permissions(opt_datadir, default_os_user);
+
+ /* Do mysqld --bootstrap. */
+ init_bootstrap_command_line(cmdline, sizeof(cmdline));
+
+ if(opt_verbose_bootstrap)
+ printf("Executing %s\n", cmdline);
+
+ in= popen(cmdline, "wt");
+ if (!in)
+ goto end;
+
+ if (fwrite("use mysql;\n",11,1, in) != 1)
+ {
+ verbose("ERROR: Cannot write to mysqld's stdin");
+ ret= 1;
+ goto end;
+ }
+
+ int i;
+ for (i=0; mysql_bootstrap_sql[i]; i++)
+ {
+ /* Write the bootstrap script to stdin. */
+ if (fwrite(mysql_bootstrap_sql[i], strlen(mysql_bootstrap_sql[i]), 1, in) != 1)
+ {
+ verbose("ERROR: Cannot write to mysqld's stdin");
+ ret= 1;
+ goto end;
+ }
+ }
+
+ /* Remove default user, if requested. */
+ if (!opt_default_user)
+ {
+ verbose("Removing default user",remove_default_user_cmd);
+ fputs(remove_default_user_cmd, in);
+ fflush(in);
+ }
+
+ if (opt_allow_remote_root_access)
+ {
+ verbose("Allowing remote access for user root",remove_default_user_cmd);
+ fputs(allow_remote_root_access_cmd,in);
+ fflush(in);
+ }
+
+ /* Change root password if requested. */
+ if (opt_password && opt_password[0])
+ {
+ verbose("Setting root password",remove_default_user_cmd);
+ fputs(update_root_passwd_part1, in);
+
+ /* Use hex encoding for password, to avoid escaping problems.*/
+ fputc('0', in);
+ fputc('x', in);
+ for(int i= 0; opt_password[i]; i++)
+ {
+ fprintf(in,"%02x",opt_password[i]);
+ }
+
+ fputs(update_root_passwd_part2, in);
+ fflush(in);
+ }
+
+ /*
+ On some reason, bootstrap chokes if last command sent via stdin ends with
+ newline, so we supply a dummy comment, that does not end with newline.
+ */
+ fputs(end_of_script, in);
+ fflush(in);
+
+ /* Check if bootstrap has completed successfully. */
+ ret= pclose(in);
+ if (ret)
+ {
+ verbose("mysqld returned error %d in pclose",ret);
+ goto end;
+ }
+
+ /*
+ Remove innodb log files if they exist (this works around "different size logs"
+ error in MSI installation). TODO : remove this with the next Innodb, where
+ different size is handled gracefully.
+ */
+ DeleteFile("ib_logfile0");
+ DeleteFile("ib_logfile1");
+
+ /* Create my.ini file in data directory.*/
+ ret= create_myini();
+ if (ret)
+ goto end;
+
+ /* Register service if requested. */
+ if (opt_service && opt_service[0])
+ {
+ ret= register_service();
+ grant_directory_permissions_to_service();
+ if (ret)
+ goto end;
+ }
+
+end:
+ if (ret)
+ {
+ SetCurrentDirectory(cwd);
+ clean_directory(opt_datadir);
+ }
+ return ret;
+}
diff --git a/sql/mysql_upgrade_service.cc b/sql/mysql_upgrade_service.cc
new file mode 100644
index 00000000000..db916101eb1
--- /dev/null
+++ b/sql/mysql_upgrade_service.cc
@@ -0,0 +1,522 @@
+/* Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/*
+ mysql_upgrade_service upgrades mysql service on Windows.
+ It changes service definition to point to the new mysqld.exe, restarts the
+ server and runs mysql_upgrade
+*/
+
+#define DONT_DEFINE_VOID
+#include <process.h>
+#include <my_global.h>
+#include <my_getopt.h>
+#include <my_sys.h>
+#include <m_string.h>
+#include <mysql_version.h>
+#include <winservice.h>
+
+#include <windows.h>
+
+/* We're using version APIs */
+#pragma comment(lib, "version")
+
+#define USAGETEXT \
+"mysql_upgrade_service.exe Ver 1.00 for Windows\n" \
+"Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub" \
+"This software comes with ABSOLUTELY NO WARRANTY. This is free software,\n" \
+"and you are welcome to modify and redistribute it under the GPL v2 license\n" \
+"Usage: mysql_upgrade_service.exe [OPTIONS]\n" \
+"OPTIONS:"
+
+static char mysqld_path[MAX_PATH];
+static char mysqladmin_path[MAX_PATH];
+static char mysqlupgrade_path[MAX_PATH];
+
+static char defaults_file_param[MAX_PATH + 16]; /*--defaults-file=<path> */
+static char logfile_path[MAX_PATH];
+static char *opt_service;
+static SC_HANDLE service;
+static SC_HANDLE scm;
+HANDLE mysqld_process; // mysqld.exe started for upgrade
+DWORD initial_service_state= -1; // initial state of the service
+HANDLE logfile_handle;
+
+/*
+ Startup and shutdown timeouts, in seconds.
+ Maybe,they can be made parameters
+*/
+static unsigned int startup_timeout= 60;
+static unsigned int shutdown_timeout= 60;
+
+static struct my_option my_long_options[]=
+{
+ {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"service", 'S', "Name of the existing Windows service",
+ &opt_service, &opt_service, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+};
+
+
+
+static my_bool
+get_one_option(int optid,
+ const struct my_option *opt __attribute__ ((unused)),
+ char *argument __attribute__ ((unused)))
+{
+ DBUG_ENTER("get_one_option");
+ switch (optid) {
+ case '?':
+ printf("%s\n", USAGETEXT);
+ my_print_help(my_long_options);
+ exit(0);
+ break;
+ }
+ DBUG_RETURN(0);
+}
+
+
+
+static void log(const char *fmt, ...)
+{
+ va_list args;
+ /* Print the error message */
+ va_start(args, fmt);
+ vfprintf(stdout,fmt, args);
+ va_end(args);
+ fputc('\n', stdout);
+ fflush(stdout);
+}
+
+
+static void die(const char *fmt, ...)
+{
+ va_list args;
+ DBUG_ENTER("die");
+
+ /* Print the error message */
+ va_start(args, fmt);
+
+ fprintf(stderr, "FATAL ERROR: ");
+ vfprintf(stderr, fmt, args);
+ if (logfile_path[0])
+ {
+ fprintf(stderr, "Additional information can be found in the log file %s",
+ logfile_path);
+ }
+ va_end(args);
+ fputc('\n', stderr);
+ fflush(stdout);
+ /* Cleanup */
+
+ /*
+ Stop service that we started, if it was not initally running at
+ program start.
+ */
+ if (initial_service_state != -1 && initial_service_state != SERVICE_RUNNING)
+ {
+ SERVICE_STATUS service_status;
+ ControlService(service, SERVICE_CONTROL_STOP, &service_status);
+ }
+
+ if (scm)
+ CloseServiceHandle(scm);
+ if (service)
+ CloseServiceHandle(service);
+ /* Stop mysqld.exe, if it was started for upgrade */
+ if (mysqld_process)
+ TerminateProcess(mysqld_process, 3);
+ if (logfile_handle)
+ CloseHandle(logfile_handle);
+ my_end(0);
+
+ exit(1);
+}
+
+
+/*
+ spawn-like function to run subprocesses.
+ We also redirect the full output to the log file.
+
+ Typical usage could be something like
+ run_tool(P_NOWAIT, "cmd.exe", "/c" , "echo", "foo", NULL)
+
+ @param wait_flag (P_WAIT or P_NOWAIT)
+ @program program to run
+
+ Rest of the parameters is NULL terminated strings building command line.
+
+ @return intptr containing either process handle, if P_NOWAIT is used
+ or return code of the process (if P_WAIT is used)
+*/
+
+static intptr_t run_tool(int wait_flag, const char *program,...)
+{
+ static char cmdline[32*1024];
+ char *end;
+ va_list args;
+ va_start(args, program);
+ if (!program)
+ die("Invalid call to run_tool");
+ end= strxmov(cmdline, "\"", program, "\"", NullS);
+
+ for(;;)
+ {
+ char *param= va_arg(args,char *);
+ if(!param)
+ break;
+ end= strxmov(end, " \"", param, "\"", NullS);
+ }
+ va_end(args);
+
+ /* Create output file if not alredy done */
+ if (!logfile_handle)
+ {
+ char tmpdir[FN_REFLEN];
+ GetTempPath(FN_REFLEN, tmpdir);
+ sprintf_s(logfile_path, "%s\\mysql_upgrade_service.%s.log", tmpdir,
+ opt_service);
+ logfile_handle= CreateFile(logfile_path, GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, TRUNCATE_EXISTING, 0, NULL);
+ if (!logfile_handle)
+ {
+ die("Cannot open log file %s, windows error %u",
+ logfile_path, GetLastError());
+ }
+ }
+
+ /* Start child process */
+ STARTUPINFO si= {0};
+ si.cb= sizeof(si);
+ si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
+ si.hStdError= logfile_handle;
+ si.hStdOutput= logfile_handle;
+ si.dwFlags= STARTF_USESTDHANDLES;
+ PROCESS_INFORMATION pi;
+ if (!CreateProcess(NULL, cmdline, NULL,
+ NULL, TRUE, NULL, NULL, NULL, &si, &pi))
+ {
+ die("CreateProcess failed (commandline %s)", cmdline);
+ }
+ CloseHandle(pi.hThread);
+
+ if (wait_flag == P_NOWAIT)
+ {
+ /* Do not wait for process to complete, return handle. */
+ return (intptr_t)pi.hProcess;
+ }
+
+ /* Wait for process to complete. */
+ if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0)
+ {
+ die("WaitForSingleObject() failed");
+ }
+ DWORD exit_code;
+ if (!GetExitCodeProcess(pi.hProcess, &exit_code))
+ {
+ die("GetExitCodeProcess() failed");
+ }
+ return (intptr_t)exit_code;
+}
+
+
+void stop_mysqld_service()
+{
+ DWORD needed;
+ SERVICE_STATUS_PROCESS ssp;
+ int timeout= shutdown_timeout*1000;
+ for(;;)
+ {
+ if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO,
+ (LPBYTE)&ssp,
+ sizeof(SERVICE_STATUS_PROCESS),
+ &needed))
+ {
+ die("QueryServiceStatusEx failed (%u)\n", GetLastError());
+ }
+
+ /*
+ Remeber initial state of the service, we will restore it on
+ exit.
+ */
+ if(initial_service_state == -1)
+ initial_service_state= ssp.dwCurrentState;
+
+ switch(ssp.dwCurrentState)
+ {
+ case SERVICE_STOPPED:
+ return;
+ case SERVICE_RUNNING:
+ if(!ControlService(service, SERVICE_CONTROL_STOP,
+ (SERVICE_STATUS *)&ssp))
+ die("ControlService failed, error %u\n", GetLastError());
+ case SERVICE_START_PENDING:
+ case SERVICE_STOP_PENDING:
+ if(timeout < 0)
+ die("Service does not stop after %d seconds timeout",shutdown_timeout);
+ Sleep(100);
+ timeout -= 100;
+ break;
+ default:
+ die("Unexpected service state %d",ssp.dwCurrentState);
+ }
+ }
+}
+
+
+/*
+ Shutdown mysql server. Not using mysqladmin, since
+ our --skip-grant-tables do not work anymore after mysql_upgrade
+ that does "flush privileges". Instead, the shutdown event is set.
+*/
+void initiate_mysqld_shutdown()
+{
+ char event_name[32];
+ DWORD pid= GetProcessId(mysqld_process);
+ sprintf_s(event_name, "MySQLShutdown%d", pid);
+ HANDLE shutdown_handle= OpenEvent(EVENT_MODIFY_STATE, FALSE, event_name);
+ if(!shutdown_handle)
+ {
+ die("OpenEvent() failed for shutdown event");
+ }
+
+ if(!SetEvent(shutdown_handle))
+ {
+ die("SetEvent() failed");
+ }
+}
+
+
+/*
+ Change service configuration (binPath) to point to mysqld from
+ this installation.
+*/
+static void change_service_config()
+{
+
+ char defaults_file[MAX_PATH];
+ char default_character_set[64];
+ char buf[MAX_PATH];
+ char commandline[3*MAX_PATH + 19];
+ int i;
+
+ scm= OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if(!scm)
+ die("OpenSCManager failed with %u", GetLastError());
+ service= OpenService(scm, opt_service, SERVICE_ALL_ACCESS);
+ if (!service)
+ die("OpenService failed with %u", GetLastError());
+
+ BYTE config_buffer[8*1024];
+ LPQUERY_SERVICE_CONFIGW config= (LPQUERY_SERVICE_CONFIGW)config_buffer;
+ DWORD size= sizeof(config_buffer);
+ DWORD needed;
+ if (!QueryServiceConfigW(service, config, size, &needed))
+ die("QueryServiceConfig failed with %u", GetLastError());
+
+ mysqld_service_properties props;
+ if (get_mysql_service_properties(config->lpBinaryPathName, &props))
+ {
+ die("Not a valid MySQL service");
+ }
+
+ int my_major= MYSQL_VERSION_ID/10000;
+ int my_minor= (MYSQL_VERSION_ID %10000)/100;
+ int my_patch= MYSQL_VERSION_ID%100;
+
+ if(my_major < props.version_major ||
+ (my_major == props.version_major && my_minor < props.version_minor))
+ {
+ die("Can not downgrade, the service is currently running as version %d.%d.%d"
+ ", my version is %d.%d.%d", props.version_major, props.version_minor,
+ props.version_patch, my_major, my_minor, my_patch);
+ }
+
+ if(props.inifile[0] == 0)
+ {
+ /*
+ Weird case, no --defaults-file in service definition, need to create one.
+ */
+ sprintf_s(props.inifile, MAX_PATH, "%s\\my.ini", props.datadir);
+ }
+
+ /*
+ Write datadir to my.ini, after converting backslashes to
+ unix style slashes.
+ */
+ strcpy_s(buf, MAX_PATH, props.datadir);
+ for(i= 0; buf[i]; i++)
+ {
+ if (buf[i] == '\\')
+ buf[i]= '/';
+ }
+ WritePrivateProfileString("mysqld", "datadir",buf, props.inifile);
+
+ /*
+ Remove basedir from defaults file, otherwise the service wont come up in
+ the new version, and will complain about mismatched message file.
+ */
+ WritePrivateProfileString("mysqld", "basedir",NULL, props.inifile);
+
+ /*
+ Replace default-character-set with character-set-server, to avoid
+ "default-character-set is deprecated and will be replaced ..."
+ message.
+ */
+ default_character_set[0]= 0;
+ GetPrivateProfileString("mysqld", "default-character-set", NULL,
+ default_character_set, sizeof(default_character_set), defaults_file);
+ if (default_character_set[0])
+ {
+ WritePrivateProfileString("mysqld", "default-character-set", NULL,
+ defaults_file);
+ WritePrivateProfileString("mysqld", "character-set-server",
+ default_character_set, defaults_file);
+ }
+
+ sprintf(defaults_file_param,"--defaults-file=%s", props.inifile);
+ sprintf_s(commandline, "\"%s\" \"%s\" \"%s\"", mysqld_path,
+ defaults_file_param, opt_service);
+ if (!ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
+ SERVICE_NO_CHANGE, commandline, NULL, NULL, NULL, NULL, NULL, NULL))
+ {
+ die("ChangeServiceConfig failed with %u", GetLastError());
+ }
+
+}
+
+
+int main(int argc, char **argv)
+{
+ int error;
+ MY_INIT(argv[0]);
+ char bindir[FN_REFLEN];
+ char *p;
+
+ /* Parse options */
+ if ((error= handle_options(&argc, &argv, my_long_options, get_one_option)))
+ die("");
+ if (!opt_service)
+ die("--service=# parameter is mandatory");
+
+ /*
+ Get full path to mysqld, we need it when changing service configuration.
+ Assume installation layout, i.e mysqld.exe, mysqladmin.exe, mysqlupgrade.exe
+ and mysql_upgrade_service.exe are in the same directory.
+ */
+ GetModuleFileName(NULL, bindir, FN_REFLEN);
+ p= strrchr(bindir, FN_LIBCHAR);
+ if(p)
+ {
+ *p= 0;
+ }
+ sprintf_s(mysqld_path, "%s\\mysqld.exe", bindir);
+ sprintf_s(mysqladmin_path, "%s\\mysqladmin.exe", bindir);
+ sprintf_s(mysqlupgrade_path, "%s\\mysql_upgrade.exe", bindir);
+
+ char *paths[]= {mysqld_path, mysqladmin_path, mysqlupgrade_path};
+ for(int i= 0; i< 3;i++)
+ {
+ if(GetFileAttributes(paths[i]) == INVALID_FILE_ATTRIBUTES)
+ die("File %s does not exist", paths[i]);
+ }
+
+ /*
+ Messages written on stdout should not be buffered, GUI upgrade program
+ reads them from pipe and uses as progress indicator.
+ */
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ log("Phase 1/8: Changing service configuration");
+ change_service_config();
+
+ log("Phase 2/8: Stopping service");
+ stop_mysqld_service();
+
+ /*
+ Start mysqld.exe as non-service skipping privileges (so we do not
+ care about the password). But disable networking and enable pipe
+ for communication, for security reasons.
+ */
+ char socket_param[FN_REFLEN];
+ sprintf_s(socket_param,"--socket=mysql_upgrade_service_%d",
+ GetCurrentProcessId());
+
+ log("Phase 3/8: Starting mysqld for upgrade");
+ mysqld_process= (HANDLE)run_tool(P_NOWAIT, mysqld_path,
+ defaults_file_param, "--skip-networking", "--skip-grant-tables",
+ "--enable-named-pipe", socket_param, NULL);
+
+ if (mysqld_process == INVALID_HANDLE_VALUE)
+ {
+ die("Cannot start mysqld.exe process, errno=%d", errno);
+ }
+
+ log("Phase 4/8: Waiting for startup to complete");
+ DWORD start_duration_ms= 0;
+ for(;;)
+ {
+ if (WaitForSingleObject(mysqld_process, 0) != WAIT_TIMEOUT)
+ die("mysqld.exe did not start");
+
+ if (run_tool(P_WAIT, mysqladmin_path, "--protocol=pipe",
+ socket_param, "ping", NULL) == 0)
+ {
+ break;
+ }
+ if (start_duration_ms > startup_timeout*1000)
+ die("Server did not come up in %d seconds",startup_timeout);
+ Sleep(500);
+ start_duration_ms+= 500;
+ }
+
+ log("Phase 5/8: Running mysql_upgrade");
+ int upgrade_err= (int) run_tool(P_WAIT, mysqlupgrade_path,
+ "--protocol=pipe", "--force", socket_param,
+ NULL);
+
+ if (upgrade_err)
+ die("mysql_upgrade failed with error code %d\n", upgrade_err);
+
+ log("Phase 6/8: Initiating server shutdown");
+ initiate_mysqld_shutdown();
+
+ log("Phase 7/8: Waiting for shutdown to complete");
+ if (WaitForSingleObject(mysqld_process, shutdown_timeout*1000)
+ != WAIT_OBJECT_0)
+ {
+ /* Shutdown takes too long */
+ die("mysqld does not shutdown.");
+ }
+ CloseHandle(mysqld_process);
+ mysqld_process= NULL;
+
+ log("Phase 8/8: Starting service%s",
+ (initial_service_state == SERVICE_RUNNING)?"":" (skipped)");
+ if (initial_service_state == SERVICE_RUNNING)
+ {
+ StartService(service, NULL, NULL);
+ }
+
+ log("Service '%s' successfully upgraded.\nLog file is written to %s",
+ opt_service, logfile_path);
+ CloseServiceHandle(service);
+ CloseServiceHandle(scm);
+ if (logfile_handle)
+ CloseHandle(logfile_handle);
+ my_end(0);
+ exit(0);
+} \ No newline at end of file
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
new file mode 100644
index 00000000000..6f28244f6c2
--- /dev/null
+++ b/sql/mysqld.cc
@@ -0,0 +1,9740 @@
+/* Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#include "sql_plugin.h" // Includes my_global.h
+#include "sql_priv.h"
+#include "unireg.h"
+#include <signal.h>
+#ifndef __WIN__
+#include <netdb.h> // getservbyname, servent
+#endif
+#include "sql_parse.h" // test_if_data_home_dir
+#include "sql_cache.h" // query_cache, query_cache_*
+#include "sql_locale.h" // MY_LOCALES, my_locales, my_locale_by_name
+#include "sql_show.h" // free_status_vars, add_status_vars,
+ // reset_status_vars
+#include "strfunc.h" // find_set_from_flags
+#include "parse_file.h" // File_parser_dummy_hook
+#include "sql_db.h" // my_dboptions_cache_free
+ // my_dboptions_cache_init
+#include "sql_table.h" // release_ddl_log, execute_ddl_log_recovery
+#include "sql_connect.h" // free_max_user_conn, init_max_user_conn,
+ // handle_one_connection
+#include "sql_time.h" // known_date_time_formats,
+ // get_date_time_format_str,
+ // date_time_format_make
+#include "tztime.h" // my_tz_free, my_tz_init, my_tz_SYSTEM
+#include "hostname.h" // hostname_cache_free, hostname_cache_init
+#include "sql_acl.h" // acl_free, grant_free, acl_init,
+ // grant_init
+#include "sql_base.h"
+#include "sql_test.h" // mysql_print_status
+#include "item_create.h" // item_create_cleanup, item_create_init
+#include "sql_servers.h" // servers_free, servers_init
+#include "init.h" // unireg_init
+#include "derror.h" // init_errmessage
+#include "derror.h" // init_errmessage
+#include "des_key_file.h" // load_des_key_file
+#include "sql_manager.h" // stop_handle_manager, start_handle_manager
+#include "sql_expression_cache.h" // subquery_cache_miss, subquery_cache_hit
+#include "sys_vars_shared.h"
+
+#include <m_ctype.h>
+#include <my_dir.h>
+#include <my_bit.h>
+#include "slave.h"
+#include "rpl_mi.h"
+#include "sql_repl.h"
+#include "rpl_filter.h"
+#include "client_settings.h"
+#include "repl_failsafe.h"
+#include <sql_common.h>
+#include <my_stacktrace.h>
+#include "mysqld_suffix.h"
+#include "mysys_err.h"
+#include "events.h"
+#include "sql_audit.h"
+#include "probes_mysql.h"
+#include "scheduler.h"
+#include <waiting_threads.h>
+#include "debug_sync.h"
+#include "sql_callback.h"
+#include "threadpool.h"
+
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+#include "../storage/perfschema/pfs_server.h"
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+#include <mysql/psi/mysql_idle.h>
+#include <mysql/psi/mysql_socket.h>
+#include <mysql/psi/mysql_statement.h>
+#include "mysql_com_server.h"
+
+#include "keycaches.h"
+#include "../storage/myisam/ha_myisam.h"
+#include "set_var.h"
+
+#include "rpl_injector.h"
+
+#include "rpl_handler.h"
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+#include <thr_alarm.h>
+#include <ft_global.h>
+#include <errmsg.h>
+#include "sp_rcontext.h"
+#include "sp_cache.h"
+#include "sql_reload.h" // reload_acl_and_cache
+
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
+
+#define mysqld_charset &my_charset_latin1
+
+/* We have HAVE_valgrind below as this speeds up the shutdown of MySQL */
+
+#if defined(SIGNALS_DONT_BREAK_READ) || defined(HAVE_valgrind) && defined(__linux__)
+#define HAVE_CLOSE_SERVER_SOCK 1
+#endif
+
+extern "C" { // Because of SCO 3.2V4.2
+#include <sys/stat.h>
+#ifndef __GNU_LIBRARY__
+#define __GNU_LIBRARY__ // Skip warnings in getopt.h
+#endif
+#include <my_getopt.h>
+#ifdef HAVE_SYSENT_H
+#include <sysent.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h> // For getpwent
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#include <my_net.h>
+
+#if !defined(__WIN__)
+#include <sys/resource.h>
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_SELECT_H
+#include <select.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/utsname.h>
+#endif /* __WIN__ */
+
+#include <my_libwrap.h>
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#ifdef __WIN__
+#include <crtdbg.h>
+#endif
+
+#ifdef HAVE_SOLARIS_LARGE_PAGES
+#include <sys/mman.h>
+#if defined(__sun__) && defined(__GNUC__) && defined(__cplusplus) \
+ && defined(_XOPEN_SOURCE)
+extern int getpagesizes(size_t *, int);
+extern int getpagesizes2(size_t *, int);
+extern int memcntl(caddr_t, size_t, int, caddr_t, int, int);
+#endif /* __sun__ ... */
+#endif /* HAVE_SOLARIS_LARGE_PAGES */
+
+#ifdef _AIX41
+int initgroups(const char *,unsigned int);
+#endif
+
+#if defined(__FreeBSD__) && defined(HAVE_IEEEFP_H) && !defined(HAVE_FEDISABLEEXCEPT)
+#include <ieeefp.h>
+#ifdef HAVE_FP_EXCEPT // Fix type conflict
+typedef fp_except fp_except_t;
+#endif
+#endif /* __FreeBSD__ && HAVE_IEEEFP_H && !HAVE_FEDISABLEEXCEPT */
+#ifdef HAVE_SYS_FPU_H
+/* for IRIX to use set_fpc_csr() */
+#include <sys/fpu.h>
+#endif
+#ifdef HAVE_FPU_CONTROL_H
+#include <fpu_control.h>
+#endif
+#if defined(__i386__) && !defined(HAVE_FPU_CONTROL_H)
+# define fpu_control_t unsigned int
+# define _FPU_EXTENDED 0x300
+# define _FPU_DOUBLE 0x200
+# if defined(__GNUC__) || (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x590)
+# define _FPU_GETCW(cw) asm volatile ("fnstcw %0" : "=m" (*&cw))
+# define _FPU_SETCW(cw) asm volatile ("fldcw %0" : : "m" (*&cw))
+# else
+# define _FPU_GETCW(cw) (cw= 0)
+# define _FPU_SETCW(cw)
+# endif
+#endif
+
+#ifndef HAVE_FCNTL
+#define fcntl(X,Y,Z) 0
+#endif
+
+extern "C" my_bool reopen_fstreams(const char *filename,
+ FILE *outstream, FILE *errstream);
+
+inline void setup_fpu()
+{
+#if defined(__FreeBSD__) && defined(HAVE_IEEEFP_H) && !defined(HAVE_FEDISABLEEXCEPT)
+ /* We can't handle floating point exceptions with threads, so disable
+ this on freebsd
+ Don't fall for overflow, underflow,divide-by-zero or loss of precision.
+ fpsetmask() is deprecated in favor of fedisableexcept() in C99.
+ */
+#if defined(FP_X_DNML)
+ fpsetmask(~(FP_X_INV | FP_X_DNML | FP_X_OFL | FP_X_UFL | FP_X_DZ |
+ FP_X_IMP));
+#else
+ fpsetmask(~(FP_X_INV | FP_X_OFL | FP_X_UFL | FP_X_DZ |
+ FP_X_IMP));
+#endif /* FP_X_DNML */
+#endif /* __FreeBSD__ && HAVE_IEEEFP_H && !HAVE_FEDISABLEEXCEPT */
+
+#ifdef HAVE_FEDISABLEEXCEPT
+ fedisableexcept(FE_ALL_EXCEPT);
+#endif
+
+#ifdef HAVE_FESETROUND
+ /* Set FPU rounding mode to "round-to-nearest" */
+ fesetround(FE_TONEAREST);
+#endif /* HAVE_FESETROUND */
+
+ /*
+ x86 (32-bit) requires FPU precision to be explicitly set to 64 bit
+ (double precision) for portable results of floating point operations.
+ However, there is no need to do so if compiler is using SSE2 for floating
+ point, double values will be stored and processed in 64 bits anyway.
+ */
+#if defined(__i386__) && !defined(__SSE2_MATH__)
+#if defined(_WIN32)
+#if !defined(_WIN64)
+ _control87(_PC_53, MCW_PC);
+#endif /* !_WIN64 */
+#else /* !_WIN32 */
+ fpu_control_t cw;
+ _FPU_GETCW(cw);
+ cw= (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE;
+ _FPU_SETCW(cw);
+#endif /* _WIN32 && */
+#endif /* __i386__ */
+
+#if defined(__sgi) && defined(HAVE_SYS_FPU_H)
+ /* Enable denormalized DOUBLE values support for IRIX */
+ union fpc_csr n;
+ n.fc_word = get_fpc_csr();
+ n.fc_struct.flush = 0;
+ set_fpc_csr(n.fc_word);
+#endif
+}
+
+} /* cplusplus */
+
+#define MYSQL_KILL_SIGNAL SIGTERM
+
+#include <my_pthread.h> // For thr_setconcurency()
+
+#ifdef SOLARIS
+extern "C" int gethostname(char *name, int namelen);
+#endif
+
+extern "C" sig_handler handle_fatal_signal(int sig);
+
+#if defined(__linux__)
+#define ENABLE_TEMP_POOL 1
+#else
+#define ENABLE_TEMP_POOL 0
+#endif
+
+/* Constants */
+
+#include <welcome_copyright_notice.h> // ORACLE_WELCOME_COPYRIGHT_NOTICE
+
+const char *show_comp_option_name[]= {"YES", "NO", "DISABLED"};
+
+static const char *tc_heuristic_recover_names[]=
+{
+ "COMMIT", "ROLLBACK", NullS
+};
+static TYPELIB tc_heuristic_recover_typelib=
+{
+ array_elements(tc_heuristic_recover_names)-1,"",
+ tc_heuristic_recover_names, NULL
+};
+
+const char *first_keyword= "first", *binary_keyword= "BINARY";
+const char *my_localhost= "localhost", *delayed_user= "DELAYED";
+
+bool opt_large_files= sizeof(my_off_t) > 4;
+static my_bool opt_autocommit; ///< for --autocommit command-line option
+
+/*
+ Used with --help for detailed option
+*/
+static my_bool opt_verbose= 0;
+
+arg_cmp_func Arg_comparator::comparator_matrix[6][2] =
+{{&Arg_comparator::compare_string, &Arg_comparator::compare_e_string},
+ {&Arg_comparator::compare_real, &Arg_comparator::compare_e_real},
+ {&Arg_comparator::compare_int_signed, &Arg_comparator::compare_e_int},
+ {&Arg_comparator::compare_row, &Arg_comparator::compare_e_row},
+ {&Arg_comparator::compare_decimal, &Arg_comparator::compare_e_decimal},
+ {&Arg_comparator::compare_datetime, &Arg_comparator::compare_e_datetime}};
+
+/* static variables */
+
+#ifdef HAVE_PSI_INTERFACE
+#if (defined(_WIN32) || defined(HAVE_SMEM)) && !defined(EMBEDDED_LIBRARY)
+static PSI_thread_key key_thread_handle_con_namedpipes;
+static PSI_cond_key key_COND_handler_count;
+#endif /* _WIN32 || HAVE_SMEM && !EMBEDDED_LIBRARY */
+
+#if defined(HAVE_SMEM) && !defined(EMBEDDED_LIBRARY)
+static PSI_thread_key key_thread_handle_con_sharedmem;
+#endif /* HAVE_SMEM && !EMBEDDED_LIBRARY */
+
+#if (defined(_WIN32) || defined(HAVE_SMEM)) && !defined(EMBEDDED_LIBRARY)
+static PSI_thread_key key_thread_handle_con_sockets;
+#endif /* _WIN32 || HAVE_SMEM && !EMBEDDED_LIBRARY */
+
+#ifdef __WIN__
+static PSI_thread_key key_thread_handle_shutdown;
+#endif /* __WIN__ */
+
+#if defined (HAVE_OPENSSL) && !defined(HAVE_YASSL)
+static PSI_rwlock_key key_rwlock_openssl;
+#endif
+#endif /* HAVE_PSI_INTERFACE */
+
+#ifdef HAVE_NPTL
+volatile sig_atomic_t ld_assume_kernel_is_set= 0;
+#endif
+
+/**
+ Statement instrumentation key for replication.
+*/
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+PSI_statement_info stmt_info_rpl;
+#endif
+
+/* the default log output is log tables */
+static bool lower_case_table_names_used= 0;
+static bool max_long_data_size_used= false;
+static bool volatile select_thread_in_use, signal_thread_in_use;
+static volatile bool ready_to_exit;
+static my_bool opt_debugging= 0, opt_external_locking= 0, opt_console= 0;
+static my_bool opt_short_log_format= 0;
+static uint kill_cached_threads, wake_thread;
+ulong max_used_connections;
+static volatile ulong cached_thread_count= 0;
+static char *mysqld_user, *mysqld_chroot;
+static char *default_character_set_name;
+static char *character_set_filesystem_name;
+static char *lc_messages;
+static char *lc_time_names_name;
+static char *my_bind_addr_str;
+static char *default_collation_name;
+char *default_storage_engine;
+static char compiled_default_collation_name[]= MYSQL_DEFAULT_COLLATION_NAME;
+static I_List<THD> thread_cache;
+static bool binlog_format_used= false;
+LEX_STRING opt_init_connect, opt_init_slave;
+static mysql_cond_t COND_thread_cache, COND_flush_thread_cache;
+mysql_cond_t COND_slave_init;
+static DYNAMIC_ARRAY all_options;
+
+/* Global variables */
+
+bool opt_bin_log, opt_bin_log_used=0, opt_ignore_builtin_innodb= 0;
+my_bool opt_log, opt_slow_log, debug_assert_if_crashed_table= 0, opt_help= 0;
+static my_bool opt_abort;
+ulonglong log_output_options;
+my_bool opt_userstat_running;
+my_bool opt_log_queries_not_using_indexes= 0;
+bool opt_error_log= IF_WIN(1,0);
+bool opt_disable_networking=0, opt_skip_show_db=0;
+bool opt_skip_name_resolve=0;
+my_bool opt_character_set_client_handshake= 1;
+bool server_id_supplied = 0;
+bool opt_endinfo, using_udf_functions;
+my_bool locked_in_memory;
+bool opt_using_transactions;
+bool volatile abort_loop;
+bool volatile shutdown_in_progress;
+uint volatile global_disable_checkpoint;
+#if defined(_WIN32) && !defined(EMBEDDED_LIBRARY)
+ulong slow_start_timeout;
+#endif
+/*
+ True if the bootstrap thread is running. Protected by LOCK_thread_count,
+ just like thread_count.
+ Used in bootstrap() function to determine if the bootstrap thread
+ has completed. Note, that we can't use 'thread_count' instead,
+ since in 5.1, in presence of the Event Scheduler, there may be
+ event threads running in parallel, so it's impossible to know
+ what value of 'thread_count' is a sign of completion of the
+ bootstrap thread.
+
+ At the same time, we can't start the event scheduler after
+ bootstrap either, since we want to be able to process event-related
+ SQL commands in the init file and in --bootstrap mode.
+*/
+bool in_bootstrap= FALSE;
+/**
+ @brief 'grant_option' is used to indicate if privileges needs
+ to be checked, in which case the lock, LOCK_grant, is used
+ to protect access to the grant table.
+ @note This flag is dropped in 5.1
+ @see grant_init()
+ */
+bool volatile grant_option;
+
+my_bool opt_skip_slave_start = 0; ///< If set, slave is not autostarted
+my_bool opt_reckless_slave = 0;
+my_bool opt_enable_named_pipe= 0;
+my_bool opt_local_infile, opt_slave_compressed_protocol;
+my_bool opt_safe_user_create = 0;
+my_bool opt_show_slave_auth_info;
+my_bool opt_log_slave_updates= 0;
+my_bool opt_replicate_annotate_row_events= 0;
+char *opt_slave_skip_errors;
+
+/*
+ Legacy global handlerton. These will be removed (please do not add more).
+*/
+handlerton *heap_hton;
+handlerton *myisam_hton;
+handlerton *partition_hton;
+
+my_bool read_only= 0, opt_readonly= 0;
+my_bool use_temp_pool, relay_log_purge;
+my_bool relay_log_recovery;
+my_bool opt_sync_frm, opt_allow_suspicious_udfs;
+my_bool opt_secure_auth= 0;
+char* opt_secure_file_priv;
+my_bool opt_log_slow_admin_statements= 0;
+my_bool opt_log_slow_slave_statements= 0;
+my_bool lower_case_file_system= 0;
+my_bool opt_large_pages= 0;
+my_bool opt_super_large_pages= 0;
+my_bool opt_myisam_use_mmap= 0;
+uint opt_large_page_size= 0;
+#if defined(ENABLED_DEBUG_SYNC)
+MYSQL_PLUGIN_IMPORT uint opt_debug_sync_timeout= 0;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+my_bool opt_old_style_user_limits= 0, trust_function_creators= 0;
+ulong opt_replicate_events_marked_for_skip;
+
+/*
+ True if there is at least one per-hour limit for some user, so we should
+ check them before each query (and possibly reset counters when hour is
+ changed). False otherwise.
+*/
+volatile bool mqh_used = 0;
+my_bool opt_noacl;
+my_bool sp_automatic_privileges= 1;
+
+ulong opt_binlog_rows_event_max_size;
+my_bool opt_master_verify_checksum= 0;
+my_bool opt_slave_sql_verify_checksum= 1;
+const char *binlog_format_names[]= {"MIXED", "STATEMENT", "ROW", NullS};
+#ifdef HAVE_INITGROUPS
+volatile sig_atomic_t calling_initgroups= 0; /**< Used in SIGSEGV handler. */
+#endif
+uint mysqld_port, test_flags, select_errors, dropping_tables, ha_open_options;
+uint mysqld_extra_port;
+uint mysqld_port_timeout;
+ulong delay_key_write_options;
+uint protocol_version;
+uint lower_case_table_names;
+ulong tc_heuristic_recover= 0;
+int32 thread_count;
+int32 thread_running;
+int32 slave_open_temp_tables;
+ulong thread_created;
+ulong back_log, connect_timeout, concurrency, server_id;
+ulong what_to_log;
+ulong slow_launch_time;
+ulong open_files_limit, max_binlog_size;
+ulong slave_trans_retries;
+uint slave_net_timeout;
+ulong slave_exec_mode_options;
+#ifdef RBR_TRIGGERS
+ulong slave_run_triggers_for_rbr= 0;
+#endif //RBR_TRIGGERS
+ulong slave_ddl_exec_mode_options= SLAVE_EXEC_MODE_IDEMPOTENT;
+ulonglong slave_type_conversions_options;
+ulong thread_cache_size=0;
+ulonglong binlog_cache_size=0;
+ulonglong max_binlog_cache_size=0;
+ulong slave_max_allowed_packet= 0;
+ulonglong binlog_stmt_cache_size=0;
+ulonglong max_binlog_stmt_cache_size=0;
+ulonglong query_cache_size=0;
+ulong query_cache_limit=0;
+ulong executed_events=0;
+query_id_t global_query_id;
+my_atomic_rwlock_t global_query_id_lock;
+my_atomic_rwlock_t thread_running_lock;
+my_atomic_rwlock_t thread_count_lock;
+my_atomic_rwlock_t statistics_lock;
+my_atomic_rwlock_t slave_executed_entries_lock;
+ulong aborted_threads, aborted_connects;
+ulong delayed_insert_timeout, delayed_insert_limit, delayed_queue_size;
+ulong delayed_insert_threads, delayed_insert_writes, delayed_rows_in_use;
+ulong delayed_insert_errors,flush_time;
+ulong specialflag=0;
+ulong binlog_cache_use= 0, binlog_cache_disk_use= 0;
+ulong binlog_stmt_cache_use= 0, binlog_stmt_cache_disk_use= 0;
+ulong max_connections, max_connect_errors;
+ulong extra_max_connections;
+ulong max_digest_length= 0;
+ulong slave_retried_transactions;
+ulong feature_files_opened_with_delayed_keys;
+ulonglong denied_connections;
+my_decimal decimal_zero;
+
+/*
+ Maximum length of parameter value which can be set through
+ mysql_send_long_data() call.
+*/
+ulong max_long_data_size;
+
+/* Limits for internal temporary tables (MyISAM or Aria) */
+uint internal_tmp_table_max_key_length;
+uint internal_tmp_table_max_key_segments;
+
+bool max_user_connections_checking=0;
+/**
+ Limit of the total number of prepared statements in the server.
+ Is necessary to protect the server against out-of-memory attacks.
+*/
+ulong max_prepared_stmt_count;
+/**
+ Current total number of prepared statements in the server. This number
+ is exact, and therefore may not be equal to the difference between
+ `com_stmt_prepare' and `com_stmt_close' (global status variables), as
+ the latter ones account for all registered attempts to prepare
+ a statement (including unsuccessful ones). Prepared statements are
+ currently connection-local: if the same SQL query text is prepared in
+ two different connections, this counts as two distinct prepared
+ statements.
+*/
+ulong prepared_stmt_count=0;
+ulong thread_id=1L,current_pid;
+ulong slow_launch_threads = 0;
+uint sync_binlog_period= 0, sync_relaylog_period= 0,
+ sync_relayloginfo_period= 0, sync_masterinfo_period= 0;
+ulong expire_logs_days = 0;
+ulong rpl_recovery_rank=0;
+/**
+ Soft upper limit for number of sp_head objects that can be stored
+ in the sp_cache for one connection.
+*/
+ulong stored_program_cache_size= 0;
+
+ulong opt_slave_parallel_threads= 0;
+ulong opt_slave_domain_parallel_threads= 0;
+ulong opt_binlog_commit_wait_count= 0;
+ulong opt_binlog_commit_wait_usec= 0;
+ulong opt_slave_parallel_max_queued= 131072;
+my_bool opt_gtid_ignore_duplicates= FALSE;
+
+const double log_10[] = {
+ 1e000, 1e001, 1e002, 1e003, 1e004, 1e005, 1e006, 1e007, 1e008, 1e009,
+ 1e010, 1e011, 1e012, 1e013, 1e014, 1e015, 1e016, 1e017, 1e018, 1e019,
+ 1e020, 1e021, 1e022, 1e023, 1e024, 1e025, 1e026, 1e027, 1e028, 1e029,
+ 1e030, 1e031, 1e032, 1e033, 1e034, 1e035, 1e036, 1e037, 1e038, 1e039,
+ 1e040, 1e041, 1e042, 1e043, 1e044, 1e045, 1e046, 1e047, 1e048, 1e049,
+ 1e050, 1e051, 1e052, 1e053, 1e054, 1e055, 1e056, 1e057, 1e058, 1e059,
+ 1e060, 1e061, 1e062, 1e063, 1e064, 1e065, 1e066, 1e067, 1e068, 1e069,
+ 1e070, 1e071, 1e072, 1e073, 1e074, 1e075, 1e076, 1e077, 1e078, 1e079,
+ 1e080, 1e081, 1e082, 1e083, 1e084, 1e085, 1e086, 1e087, 1e088, 1e089,
+ 1e090, 1e091, 1e092, 1e093, 1e094, 1e095, 1e096, 1e097, 1e098, 1e099,
+ 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
+ 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119,
+ 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129,
+ 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139,
+ 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149,
+ 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
+ 1e160, 1e161, 1e162, 1e163, 1e164, 1e165, 1e166, 1e167, 1e168, 1e169,
+ 1e170, 1e171, 1e172, 1e173, 1e174, 1e175, 1e176, 1e177, 1e178, 1e179,
+ 1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186, 1e187, 1e188, 1e189,
+ 1e190, 1e191, 1e192, 1e193, 1e194, 1e195, 1e196, 1e197, 1e198, 1e199,
+ 1e200, 1e201, 1e202, 1e203, 1e204, 1e205, 1e206, 1e207, 1e208, 1e209,
+ 1e210, 1e211, 1e212, 1e213, 1e214, 1e215, 1e216, 1e217, 1e218, 1e219,
+ 1e220, 1e221, 1e222, 1e223, 1e224, 1e225, 1e226, 1e227, 1e228, 1e229,
+ 1e230, 1e231, 1e232, 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239,
+ 1e240, 1e241, 1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249,
+ 1e250, 1e251, 1e252, 1e253, 1e254, 1e255, 1e256, 1e257, 1e258, 1e259,
+ 1e260, 1e261, 1e262, 1e263, 1e264, 1e265, 1e266, 1e267, 1e268, 1e269,
+ 1e270, 1e271, 1e272, 1e273, 1e274, 1e275, 1e276, 1e277, 1e278, 1e279,
+ 1e280, 1e281, 1e282, 1e283, 1e284, 1e285, 1e286, 1e287, 1e288, 1e289,
+ 1e290, 1e291, 1e292, 1e293, 1e294, 1e295, 1e296, 1e297, 1e298, 1e299,
+ 1e300, 1e301, 1e302, 1e303, 1e304, 1e305, 1e306, 1e307, 1e308
+};
+
+time_t server_start_time, flush_status_time;
+
+char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], system_time_zone[30];
+char *default_tz_name;
+char log_error_file[FN_REFLEN], glob_hostname[FN_REFLEN], *opt_log_basename;
+char mysql_real_data_home[FN_REFLEN],
+ lc_messages_dir[FN_REFLEN], reg_ext[FN_EXTLEN],
+ mysql_charsets_dir[FN_REFLEN],
+ *opt_init_file, *opt_tc_log_file;
+char *lc_messages_dir_ptr= lc_messages_dir, *log_error_file_ptr;
+char mysql_unpacked_real_data_home[FN_REFLEN];
+int mysql_unpacked_real_data_home_len;
+uint mysql_real_data_home_len, mysql_data_home_len= 1;
+uint reg_ext_length;
+const key_map key_map_empty(0);
+key_map key_map_full(0); // Will be initialized later
+
+DATE_TIME_FORMAT global_date_format, global_datetime_format, global_time_format;
+Time_zone *default_tz;
+
+const char *mysql_real_data_home_ptr= mysql_real_data_home;
+char server_version[SERVER_VERSION_LENGTH];
+char *mysqld_unix_port, *opt_mysql_tmpdir;
+ulong thread_handling;
+
+/** name of reference on left expression in rewritten IN subquery */
+const char *in_left_expr_name= "<left expr>";
+/** name of additional condition */
+const char *in_additional_cond= "<IN COND>";
+const char *in_having_cond= "<IN HAVING>";
+
+/** Number of connection errors when selecting on the listening port */
+ulong connection_errors_select= 0;
+/** Number of connection errors when accepting sockets in the listening port. */
+ulong connection_errors_accept= 0;
+/** Number of connection errors from TCP wrappers. */
+ulong connection_errors_tcpwrap= 0;
+/** Number of connection errors from internal server errors. */
+ulong connection_errors_internal= 0;
+/** Number of connection errors from the server max_connection limit. */
+ulong connection_errors_max_connection= 0;
+/** Number of errors when reading the peer address. */
+ulong connection_errors_peer_addr= 0;
+
+/* classes for comparation parsing/processing */
+Eq_creator eq_creator;
+Ne_creator ne_creator;
+Gt_creator gt_creator;
+Lt_creator lt_creator;
+Ge_creator ge_creator;
+Le_creator le_creator;
+
+MYSQL_FILE *bootstrap_file;
+int bootstrap_error;
+
+I_List<THD> threads;
+Rpl_filter* cur_rpl_filter;
+Rpl_filter* global_rpl_filter;
+Rpl_filter* binlog_filter;
+
+THD *first_global_thread()
+{
+ if (threads.is_empty())
+ return NULL;
+ return threads.head();
+}
+
+THD *next_global_thread(THD *thd)
+{
+ if (threads.is_last(thd))
+ return NULL;
+ struct ilink *next= thd->next;
+ return static_cast<THD*>(next);
+}
+
+struct system_variables global_system_variables;
+struct system_variables max_system_variables;
+struct system_status_var global_status_var;
+
+MY_TMPDIR mysql_tmpdir_list;
+MY_BITMAP temp_pool;
+
+CHARSET_INFO *system_charset_info, *files_charset_info ;
+CHARSET_INFO *national_charset_info, *table_alias_charset;
+CHARSET_INFO *character_set_filesystem;
+CHARSET_INFO *error_message_charset_info;
+
+MY_LOCALE *my_default_lc_messages;
+MY_LOCALE *my_default_lc_time_names;
+
+SHOW_COMP_OPTION have_ssl, have_symlink, have_dlopen, have_query_cache;
+SHOW_COMP_OPTION have_geometry, have_rtree_keys;
+SHOW_COMP_OPTION have_crypt, have_compress;
+SHOW_COMP_OPTION have_profiling;
+SHOW_COMP_OPTION have_openssl;
+
+/* Thread specific variables */
+
+pthread_key(MEM_ROOT**,THR_MALLOC);
+pthread_key(THD*, THR_THD);
+mysql_mutex_t LOCK_thread_count, LOCK_thread_cache;
+mysql_mutex_t
+ LOCK_status, LOCK_show_status, LOCK_error_log, LOCK_short_uuid_generator,
+ LOCK_delayed_insert, LOCK_delayed_status, LOCK_delayed_create,
+ LOCK_crypt,
+ LOCK_global_system_variables,
+ LOCK_user_conn, LOCK_slave_list, LOCK_active_mi,
+ LOCK_connection_count, LOCK_error_messages, LOCK_slave_init;
+
+mysql_mutex_t LOCK_stats, LOCK_global_user_client_stats,
+ LOCK_global_table_stats, LOCK_global_index_stats;
+
+/**
+ The below lock protects access to two global server variables:
+ max_prepared_stmt_count and prepared_stmt_count. These variables
+ set the limit and hold the current total number of prepared statements
+ in the server, respectively. As PREPARE/DEALLOCATE rate in a loaded
+ server may be fairly high, we need a dedicated lock.
+*/
+mysql_mutex_t LOCK_prepared_stmt_count;
+#ifdef HAVE_OPENSSL
+mysql_mutex_t LOCK_des_key_file;
+#endif
+mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
+mysql_rwlock_t LOCK_system_variables_hash;
+mysql_cond_t COND_thread_count;
+pthread_t signal_thread;
+pthread_attr_t connection_attrib;
+mysql_mutex_t LOCK_server_started;
+mysql_cond_t COND_server_started;
+
+int mysqld_server_started=0, mysqld_server_initialized= 0;
+File_parser_dummy_hook file_parser_dummy_hook;
+
+/* replication parameters, if master_host is not NULL, we are a slave */
+uint report_port= 0;
+ulong master_retry_count=0;
+char *master_info_file;
+char *relay_log_info_file, *report_user, *report_password, *report_host;
+char *opt_relay_logname = 0, *opt_relaylog_index_name=0;
+char *opt_logname, *opt_slow_logname, *opt_bin_logname;
+
+/* Static variables */
+
+static volatile sig_atomic_t kill_in_progress;
+my_bool opt_stack_trace;
+my_bool opt_expect_abort= 0, opt_bootstrap= 0;
+static my_bool opt_myisam_log;
+static int cleanup_done;
+static ulong opt_specialflag;
+static char *opt_binlog_index_name;
+char *mysql_home_ptr, *pidfile_name_ptr;
+/** Initial command line arguments (count), after load_defaults().*/
+static int defaults_argc;
+/**
+ Initial command line arguments (arguments), after load_defaults().
+ This memory is allocated by @c load_defaults() and should be freed
+ using @c free_defaults().
+ Do not modify defaults_argc / defaults_argv,
+ use remaining_argc / remaining_argv instead to parse the command
+ line arguments in multiple steps.
+*/
+static char **defaults_argv;
+/** Remaining command line arguments (count), filtered by handle_options().*/
+static int remaining_argc;
+/** Remaining command line arguments (arguments), filtered by handle_options().*/
+static char **remaining_argv;
+
+int orig_argc;
+char **orig_argv;
+
+static struct my_option pfs_early_options[]=
+{
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ {"performance_schema_instrument", OPT_PFS_INSTRUMENT,
+ "Default startup value for a performance schema instrument.",
+ &pfs_param.m_pfs_instrument, &pfs_param.m_pfs_instrument, 0, GET_STR,
+ OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_stages_current", 0,
+ "Default startup value for the events_stages_current consumer.",
+ &pfs_param.m_consumer_events_stages_current_enabled,
+ &pfs_param.m_consumer_events_stages_current_enabled, 0, GET_BOOL,
+ OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_stages_history", 0,
+ "Default startup value for the events_stages_history consumer.",
+ &pfs_param.m_consumer_events_stages_history_enabled,
+ &pfs_param.m_consumer_events_stages_history_enabled, 0,
+ GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_stages_history_long", 0,
+ "Default startup value for the events_stages_history_long consumer.",
+ &pfs_param.m_consumer_events_stages_history_long_enabled,
+ &pfs_param.m_consumer_events_stages_history_long_enabled, 0,
+ GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_statements_current", 0,
+ "Default startup value for the events_statements_current consumer.",
+ &pfs_param.m_consumer_events_statements_current_enabled,
+ &pfs_param.m_consumer_events_statements_current_enabled, 0,
+ GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_statements_history", 0,
+ "Default startup value for the events_statements_history consumer.",
+ &pfs_param.m_consumer_events_statements_history_enabled,
+ &pfs_param.m_consumer_events_statements_history_enabled, 0,
+ GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_statements_history_long", 0,
+ "Default startup value for the events_statements_history_long consumer.",
+ &pfs_param.m_consumer_events_statements_history_long_enabled,
+ &pfs_param.m_consumer_events_statements_history_long_enabled, 0,
+ GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_waits_current", 0,
+ "Default startup value for the events_waits_current consumer.",
+ &pfs_param.m_consumer_events_waits_current_enabled,
+ &pfs_param.m_consumer_events_waits_current_enabled, 0,
+ GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_waits_history", 0,
+ "Default startup value for the events_waits_history consumer.",
+ &pfs_param.m_consumer_events_waits_history_enabled,
+ &pfs_param.m_consumer_events_waits_history_enabled, 0,
+ GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_events_waits_history_long", 0,
+ "Default startup value for the events_waits_history_long consumer.",
+ &pfs_param.m_consumer_events_waits_history_long_enabled,
+ &pfs_param.m_consumer_events_waits_history_long_enabled, 0,
+ GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_global_instrumentation", 0,
+ "Default startup value for the global_instrumentation consumer.",
+ &pfs_param.m_consumer_global_instrumentation_enabled,
+ &pfs_param.m_consumer_global_instrumentation_enabled, 0,
+ GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_thread_instrumentation", 0,
+ "Default startup value for the thread_instrumentation consumer.",
+ &pfs_param.m_consumer_thread_instrumentation_enabled,
+ &pfs_param.m_consumer_thread_instrumentation_enabled, 0,
+ GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0},
+ {"performance_schema_consumer_statements_digest", 0,
+ "Default startup value for the statements_digest consumer.",
+ &pfs_param.m_consumer_statement_digest_enabled,
+ &pfs_param.m_consumer_statement_digest_enabled, 0,
+ GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0}
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+};
+
+#ifdef HAVE_PSI_INTERFACE
+#ifdef HAVE_MMAP
+PSI_mutex_key key_PAGE_lock, key_LOCK_sync, key_LOCK_active, key_LOCK_pool,
+ key_LOCK_pending_checkpoint;
+#endif /* HAVE_MMAP */
+
+#ifdef HAVE_OPENSSL
+PSI_mutex_key key_LOCK_des_key_file;
+#endif /* HAVE_OPENSSL */
+
+PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list,
+ key_BINLOG_LOCK_binlog_background_thread,
+ key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi,
+ key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create,
+ key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log,
+ key_LOCK_gdl, key_LOCK_global_system_variables,
+ key_LOCK_manager,
+ key_LOCK_prepared_stmt_count,
+ key_LOCK_rpl_status, key_LOCK_server_started,
+ key_LOCK_status, key_LOCK_show_status,
+ key_LOCK_system_variables_hash, key_LOCK_thd_data,
+ key_LOCK_user_conn, key_LOCK_uuid_short_generator, key_LOG_LOCK_log,
+ key_master_info_data_lock, key_master_info_run_lock,
+ key_master_info_sleep_lock,
+ key_mutex_slave_reporting_capability_err_lock, key_relay_log_info_data_lock,
+ key_rpl_group_info_sleep_lock,
+ key_relay_log_info_log_space_lock, key_relay_log_info_run_lock,
+ key_structure_guard_mutex, key_TABLE_SHARE_LOCK_ha_data,
+ key_LOCK_error_messages, key_LOG_INFO_lock,
+ key_LOCK_thread_count, key_LOCK_thread_cache,
+ key_PARTITION_LOCK_auto_inc;
+PSI_mutex_key key_RELAYLOG_LOCK_index;
+PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state,
+ key_LOCK_rpl_thread, key_LOCK_rpl_thread_pool, key_LOCK_parallel_entry;
+
+PSI_mutex_key key_LOCK_stats,
+ key_LOCK_global_user_client_stats, key_LOCK_global_table_stats,
+ key_LOCK_global_index_stats,
+ key_LOCK_wakeup_ready, key_LOCK_wait_commit;
+PSI_mutex_key key_LOCK_gtid_waiting;
+
+PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered,
+ key_LOCK_slave_init;
+PSI_mutex_key key_TABLE_SHARE_LOCK_share;
+
+static PSI_mutex_info all_server_mutexes[]=
+{
+#ifdef HAVE_MMAP
+ { &key_PAGE_lock, "PAGE::lock", 0},
+ { &key_LOCK_sync, "TC_LOG_MMAP::LOCK_sync", 0},
+ { &key_LOCK_active, "TC_LOG_MMAP::LOCK_active", 0},
+ { &key_LOCK_pool, "TC_LOG_MMAP::LOCK_pool", 0},
+ { &key_LOCK_pool, "TC_LOG_MMAP::LOCK_pending_checkpoint", 0},
+#endif /* HAVE_MMAP */
+
+#ifdef HAVE_OPENSSL
+ { &key_LOCK_des_key_file, "LOCK_des_key_file", PSI_FLAG_GLOBAL},
+#endif /* HAVE_OPENSSL */
+
+ { &key_BINLOG_LOCK_index, "MYSQL_BIN_LOG::LOCK_index", 0},
+ { &key_BINLOG_LOCK_xid_list, "MYSQL_BIN_LOG::LOCK_xid_list", 0},
+ { &key_BINLOG_LOCK_binlog_background_thread, "MYSQL_BIN_LOG::LOCK_binlog_background_thread", 0},
+ { &key_RELAYLOG_LOCK_index, "MYSQL_RELAY_LOG::LOCK_index", 0},
+ { &key_delayed_insert_mutex, "Delayed_insert::mutex", 0},
+ { &key_hash_filo_lock, "hash_filo::lock", 0},
+ { &key_LOCK_active_mi, "LOCK_active_mi", PSI_FLAG_GLOBAL},
+ { &key_LOCK_connection_count, "LOCK_connection_count", PSI_FLAG_GLOBAL},
+ { &key_LOCK_crypt, "LOCK_crypt", PSI_FLAG_GLOBAL},
+ { &key_LOCK_delayed_create, "LOCK_delayed_create", PSI_FLAG_GLOBAL},
+ { &key_LOCK_delayed_insert, "LOCK_delayed_insert", PSI_FLAG_GLOBAL},
+ { &key_LOCK_delayed_status, "LOCK_delayed_status", PSI_FLAG_GLOBAL},
+ { &key_LOCK_error_log, "LOCK_error_log", PSI_FLAG_GLOBAL},
+ { &key_LOCK_gdl, "LOCK_gdl", PSI_FLAG_GLOBAL},
+ { &key_LOCK_global_system_variables, "LOCK_global_system_variables", PSI_FLAG_GLOBAL},
+ { &key_LOCK_manager, "LOCK_manager", PSI_FLAG_GLOBAL},
+ { &key_LOCK_prepared_stmt_count, "LOCK_prepared_stmt_count", PSI_FLAG_GLOBAL},
+ { &key_LOCK_rpl_status, "LOCK_rpl_status", PSI_FLAG_GLOBAL},
+ { &key_LOCK_server_started, "LOCK_server_started", PSI_FLAG_GLOBAL},
+ { &key_LOCK_status, "LOCK_status", PSI_FLAG_GLOBAL},
+ { &key_LOCK_show_status, "LOCK_show_status", PSI_FLAG_GLOBAL},
+ { &key_LOCK_system_variables_hash, "LOCK_system_variables_hash", PSI_FLAG_GLOBAL},
+ { &key_LOCK_stats, "LOCK_stats", PSI_FLAG_GLOBAL},
+ { &key_LOCK_global_user_client_stats, "LOCK_global_user_client_stats", PSI_FLAG_GLOBAL},
+ { &key_LOCK_global_table_stats, "LOCK_global_table_stats", PSI_FLAG_GLOBAL},
+ { &key_LOCK_global_index_stats, "LOCK_global_index_stats", PSI_FLAG_GLOBAL},
+ { &key_LOCK_wakeup_ready, "THD::LOCK_wakeup_ready", 0},
+ { &key_LOCK_wait_commit, "wait_for_commit::LOCK_wait_commit", 0},
+ { &key_LOCK_gtid_waiting, "gtid_waiting::LOCK_gtid_waiting", 0},
+ { &key_LOCK_thd_data, "THD::LOCK_thd_data", 0},
+ { &key_LOCK_user_conn, "LOCK_user_conn", PSI_FLAG_GLOBAL},
+ { &key_LOCK_uuid_short_generator, "LOCK_uuid_short_generator", PSI_FLAG_GLOBAL},
+ { &key_LOG_LOCK_log, "LOG::LOCK_log", 0},
+ { &key_master_info_data_lock, "Master_info::data_lock", 0},
+ { &key_master_info_run_lock, "Master_info::run_lock", 0},
+ { &key_master_info_sleep_lock, "Master_info::sleep_lock", 0},
+ { &key_mutex_slave_reporting_capability_err_lock, "Slave_reporting_capability::err_lock", 0},
+ { &key_relay_log_info_data_lock, "Relay_log_info::data_lock", 0},
+ { &key_relay_log_info_log_space_lock, "Relay_log_info::log_space_lock", 0},
+ { &key_relay_log_info_run_lock, "Relay_log_info::run_lock", 0},
+ { &key_rpl_group_info_sleep_lock, "Rpl_group_info::sleep_lock", 0},
+ { &key_structure_guard_mutex, "Query_cache::structure_guard_mutex", 0},
+ { &key_TABLE_SHARE_LOCK_ha_data, "TABLE_SHARE::LOCK_ha_data", 0},
+ { &key_TABLE_SHARE_LOCK_share, "TABLE_SHARE::LOCK_share", 0},
+ { &key_LOCK_error_messages, "LOCK_error_messages", PSI_FLAG_GLOBAL},
+ { &key_LOCK_prepare_ordered, "LOCK_prepare_ordered", PSI_FLAG_GLOBAL},
+ { &key_LOCK_commit_ordered, "LOCK_commit_ordered", PSI_FLAG_GLOBAL},
+ { &key_LOCK_slave_init, "LOCK_slave_init", PSI_FLAG_GLOBAL},
+ { &key_LOG_INFO_lock, "LOG_INFO::lock", 0},
+ { &key_LOCK_thread_count, "LOCK_thread_count", PSI_FLAG_GLOBAL},
+ { &key_LOCK_thread_cache, "LOCK_thread_cache", PSI_FLAG_GLOBAL},
+ { &key_PARTITION_LOCK_auto_inc, "HA_DATA_PARTITION::LOCK_auto_inc", 0},
+ { &key_LOCK_slave_state, "LOCK_slave_state", 0},
+ { &key_LOCK_binlog_state, "LOCK_binlog_state", 0},
+ { &key_LOCK_rpl_thread, "LOCK_rpl_thread", 0},
+ { &key_LOCK_rpl_thread_pool, "LOCK_rpl_thread_pool", 0},
+ { &key_LOCK_parallel_entry, "LOCK_parallel_entry", 0}
+};
+
+PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger,
+ key_rwlock_LOCK_sys_init_connect, key_rwlock_LOCK_sys_init_slave,
+ key_rwlock_LOCK_system_variables_hash, key_rwlock_query_cache_query_lock;
+
+static PSI_rwlock_info all_server_rwlocks[]=
+{
+#if defined (HAVE_OPENSSL) && !defined(HAVE_YASSL)
+ { &key_rwlock_openssl, "CRYPTO_dynlock_value::lock", 0},
+#endif
+ { &key_rwlock_LOCK_grant, "LOCK_grant", PSI_FLAG_GLOBAL},
+ { &key_rwlock_LOCK_logger, "LOGGER::LOCK_logger", 0},
+ { &key_rwlock_LOCK_sys_init_connect, "LOCK_sys_init_connect", PSI_FLAG_GLOBAL},
+ { &key_rwlock_LOCK_sys_init_slave, "LOCK_sys_init_slave", PSI_FLAG_GLOBAL},
+ { &key_rwlock_LOCK_system_variables_hash, "LOCK_system_variables_hash", PSI_FLAG_GLOBAL},
+ { &key_rwlock_query_cache_query_lock, "Query_cache_query::lock", 0}
+};
+
+#ifdef HAVE_MMAP
+PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool;
+#endif /* HAVE_MMAP */
+
+PSI_cond_key key_BINLOG_COND_xid_list, key_BINLOG_update_cond,
+ key_BINLOG_COND_binlog_background_thread,
+ key_BINLOG_COND_binlog_background_thread_end,
+ key_COND_cache_status_changed, key_COND_manager,
+ key_COND_rpl_status, key_COND_server_started,
+ key_delayed_insert_cond, key_delayed_insert_cond_client,
+ key_item_func_sleep_cond, key_master_info_data_cond,
+ key_master_info_start_cond, key_master_info_stop_cond,
+ key_master_info_sleep_cond,
+ key_relay_log_info_data_cond, key_relay_log_info_log_space_cond,
+ key_relay_log_info_start_cond, key_relay_log_info_stop_cond,
+ key_rpl_group_info_sleep_cond,
+ key_TABLE_SHARE_cond, key_user_level_lock_cond,
+ key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache,
+ key_BINLOG_COND_queue_busy;
+PSI_cond_key key_RELAYLOG_update_cond, key_COND_wakeup_ready,
+ key_COND_wait_commit;
+PSI_cond_key key_RELAYLOG_COND_queue_busy;
+PSI_cond_key key_TC_LOG_MMAP_COND_queue_busy;
+PSI_cond_key key_COND_rpl_thread_queue, key_COND_rpl_thread,
+ key_COND_rpl_thread_pool,
+ key_COND_parallel_entry, key_COND_group_commit_orderer,
+ key_COND_prepare_ordered, key_COND_slave_init;
+PSI_cond_key key_COND_wait_gtid, key_COND_gtid_ignore_duplicates;
+
+static PSI_cond_info all_server_conds[]=
+{
+#if (defined(_WIN32) || defined(HAVE_SMEM)) && !defined(EMBEDDED_LIBRARY)
+ { &key_COND_handler_count, "COND_handler_count", PSI_FLAG_GLOBAL},
+#endif /* _WIN32 || HAVE_SMEM && !EMBEDDED_LIBRARY */
+#ifdef HAVE_MMAP
+ { &key_PAGE_cond, "PAGE::cond", 0},
+ { &key_COND_active, "TC_LOG_MMAP::COND_active", 0},
+ { &key_COND_pool, "TC_LOG_MMAP::COND_pool", 0},
+ { &key_TC_LOG_MMAP_COND_queue_busy, "TC_LOG_MMAP::COND_queue_busy", 0},
+#endif /* HAVE_MMAP */
+ { &key_BINLOG_COND_xid_list, "MYSQL_BIN_LOG::COND_xid_list", 0},
+ { &key_BINLOG_update_cond, "MYSQL_BIN_LOG::update_cond", 0},
+ { &key_BINLOG_COND_binlog_background_thread, "MYSQL_BIN_LOG::COND_binlog_background_thread", 0},
+ { &key_BINLOG_COND_binlog_background_thread_end, "MYSQL_BIN_LOG::COND_binlog_background_thread_end", 0},
+ { &key_BINLOG_COND_queue_busy, "MYSQL_BIN_LOG::COND_queue_busy", 0},
+ { &key_RELAYLOG_update_cond, "MYSQL_RELAY_LOG::update_cond", 0},
+ { &key_RELAYLOG_COND_queue_busy, "MYSQL_RELAY_LOG::COND_queue_busy", 0},
+ { &key_COND_wakeup_ready, "THD::COND_wakeup_ready", 0},
+ { &key_COND_wait_commit, "wait_for_commit::COND_wait_commit", 0},
+ { &key_COND_cache_status_changed, "Query_cache::COND_cache_status_changed", 0},
+ { &key_COND_manager, "COND_manager", PSI_FLAG_GLOBAL},
+ { &key_COND_server_started, "COND_server_started", PSI_FLAG_GLOBAL},
+ { &key_delayed_insert_cond, "Delayed_insert::cond", 0},
+ { &key_delayed_insert_cond_client, "Delayed_insert::cond_client", 0},
+ { &key_item_func_sleep_cond, "Item_func_sleep::cond", 0},
+ { &key_master_info_data_cond, "Master_info::data_cond", 0},
+ { &key_master_info_start_cond, "Master_info::start_cond", 0},
+ { &key_master_info_stop_cond, "Master_info::stop_cond", 0},
+ { &key_master_info_sleep_cond, "Master_info::sleep_cond", 0},
+ { &key_relay_log_info_data_cond, "Relay_log_info::data_cond", 0},
+ { &key_relay_log_info_log_space_cond, "Relay_log_info::log_space_cond", 0},
+ { &key_relay_log_info_start_cond, "Relay_log_info::start_cond", 0},
+ { &key_relay_log_info_stop_cond, "Relay_log_info::stop_cond", 0},
+ { &key_rpl_group_info_sleep_cond, "Rpl_group_info::sleep_cond", 0},
+ { &key_TABLE_SHARE_cond, "TABLE_SHARE::cond", 0},
+ { &key_user_level_lock_cond, "User_level_lock::cond", 0},
+ { &key_COND_thread_count, "COND_thread_count", PSI_FLAG_GLOBAL},
+ { &key_COND_thread_cache, "COND_thread_cache", PSI_FLAG_GLOBAL},
+ { &key_COND_flush_thread_cache, "COND_flush_thread_cache", PSI_FLAG_GLOBAL},
+ { &key_COND_rpl_thread, "COND_rpl_thread", 0},
+ { &key_COND_rpl_thread_queue, "COND_rpl_thread_queue", 0},
+ { &key_COND_rpl_thread_pool, "COND_rpl_thread_pool", 0},
+ { &key_COND_parallel_entry, "COND_parallel_entry", 0},
+ { &key_COND_group_commit_orderer, "COND_group_commit_orderer", 0},
+ { &key_COND_prepare_ordered, "COND_prepare_ordered", 0},
+ { &key_COND_slave_init, "COND_slave_init", 0},
+ { &key_COND_wait_gtid, "COND_wait_gtid", 0},
+ { &key_COND_gtid_ignore_duplicates, "COND_gtid_ignore_duplicates", 0}
+};
+
+PSI_thread_key key_thread_bootstrap, key_thread_delayed_insert,
+ key_thread_handle_manager, key_thread_main,
+ key_thread_one_connection, key_thread_signal_hand,
+ key_thread_slave_init, key_rpl_parallel_thread;
+
+static PSI_thread_info all_server_threads[]=
+{
+#if (defined(_WIN32) || defined(HAVE_SMEM)) && !defined(EMBEDDED_LIBRARY)
+ { &key_thread_handle_con_namedpipes, "con_named_pipes", PSI_FLAG_GLOBAL},
+#endif /* _WIN32 || HAVE_SMEM && !EMBEDDED_LIBRARY */
+
+#if defined(HAVE_SMEM) && !defined(EMBEDDED_LIBRARY)
+ { &key_thread_handle_con_sharedmem, "con_shared_mem", PSI_FLAG_GLOBAL},
+#endif /* HAVE_SMEM && !EMBEDDED_LIBRARY */
+
+#if (defined(_WIN32) || defined(HAVE_SMEM)) && !defined(EMBEDDED_LIBRARY)
+ { &key_thread_handle_con_sockets, "con_sockets", PSI_FLAG_GLOBAL},
+#endif /* _WIN32 || HAVE_SMEM && !EMBEDDED_LIBRARY */
+
+#ifdef __WIN__
+ { &key_thread_handle_shutdown, "shutdown", PSI_FLAG_GLOBAL},
+#endif /* __WIN__ */
+
+ { &key_thread_bootstrap, "bootstrap", PSI_FLAG_GLOBAL},
+ { &key_thread_delayed_insert, "delayed_insert", 0},
+ { &key_thread_handle_manager, "manager", PSI_FLAG_GLOBAL},
+ { &key_thread_main, "main", PSI_FLAG_GLOBAL},
+ { &key_thread_one_connection, "one_connection", 0},
+ { &key_thread_signal_hand, "signal_handler", PSI_FLAG_GLOBAL},
+ { &key_thread_slave_init, "slave_init", PSI_FLAG_GLOBAL},
+ { &key_rpl_parallel_thread, "rpl_parallel_thread", 0}
+};
+
+#ifdef HAVE_MMAP
+PSI_file_key key_file_map;
+#endif /* HAVE_MMAP */
+
+PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest,
+ key_file_dbopt, key_file_des_key_file, key_file_ERRMSG, key_select_to_file,
+ key_file_fileparser, key_file_frm, key_file_global_ddl_log, key_file_load,
+ key_file_loadfile, key_file_log_event_data, key_file_log_event_info,
+ key_file_master_info, key_file_misc, key_file_partition,
+ key_file_pid, key_file_relay_log_info, key_file_send_file, key_file_tclog,
+ key_file_trg, key_file_trn, key_file_init;
+PSI_file_key key_file_query_log, key_file_slow_log;
+PSI_file_key key_file_relaylog, key_file_relaylog_index;
+PSI_file_key key_file_binlog_state;
+
+#endif /* HAVE_PSI_INTERFACE */
+
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+PSI_statement_info stmt_info_new_packet;
+#endif
+
+#ifndef EMBEDDED_LIBRARY
+void net_before_header_psi(struct st_net *net, void *user_data, size_t /* unused: count */)
+{
+ THD *thd;
+ thd= static_cast<THD*> (user_data);
+ DBUG_ASSERT(thd != NULL);
+
+ /*
+ We only come where when the server is IDLE, waiting for the next command.
+ Technically, it is a wait on a socket, which may take a long time,
+ because the call is blocking.
+ Disable the socket instrumentation, to avoid recording a SOCKET event.
+ Instead, start explicitly an IDLE event.
+ */
+ MYSQL_SOCKET_SET_STATE(net->vio->mysql_socket, PSI_SOCKET_STATE_IDLE);
+ MYSQL_START_IDLE_WAIT(thd->m_idle_psi, &thd->m_idle_state);
+}
+
+void net_after_header_psi(struct st_net *net, void *user_data,
+ size_t /* unused: count */, my_bool rc)
+{
+ THD *thd;
+ thd= static_cast<THD*> (user_data);
+ DBUG_ASSERT(thd != NULL);
+
+ /*
+ The server just got data for a network packet header,
+ from the network layer.
+ The IDLE event is now complete, since we now have a message to process.
+ We need to:
+ - start a new STATEMENT event
+ - start a new STAGE event, within this statement,
+ - start recording SOCKET WAITS events, within this stage.
+ The proper order is critical to get events numbered correctly,
+ and nested in the proper parent.
+ */
+ MYSQL_END_IDLE_WAIT(thd->m_idle_psi);
+
+ if (! rc)
+ {
+ thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state,
+ stmt_info_new_packet.m_key,
+ thd->db, thd->db_length,
+ thd->charset());
+
+ THD_STAGE_INFO(thd, stage_init);
+ }
+
+ /*
+ TODO: consider recording a SOCKET event for the bytes just read,
+ by also passing count here.
+ */
+ MYSQL_SOCKET_SET_STATE(net->vio->mysql_socket, PSI_SOCKET_STATE_ACTIVE);
+}
+
+
+void init_net_server_extension(THD *thd)
+{
+ /* Start with a clean state for connection events. */
+ thd->m_idle_psi= NULL;
+ thd->m_statement_psi= NULL;
+ /* Hook up the NET_SERVER callback in the net layer. */
+ thd->m_net_server_extension.m_user_data= thd;
+ thd->m_net_server_extension.m_before_header= net_before_header_psi;
+ thd->m_net_server_extension.m_after_header= net_after_header_psi;
+ /* Activate this private extension for the mysqld server. */
+ thd->net.extension= & thd->m_net_server_extension;
+}
+#endif /* EMBEDDED_LIBRARY */
+
+/**
+ A log message for the error log, buffered in memory.
+ Log messages are temporarily buffered when generated before the error log
+ is initialized, and then printed once the error log is ready.
+*/
+class Buffered_log : public Sql_alloc
+{
+public:
+ Buffered_log(enum loglevel level, const char *message);
+
+ ~Buffered_log()
+ {}
+
+ void print(void);
+
+private:
+ /** Log message level. */
+ enum loglevel m_level;
+ /** Log message text. */
+ String m_message;
+};
+
+/**
+ Constructor.
+ @param level the message log level
+ @param message the message text
+*/
+Buffered_log::Buffered_log(enum loglevel level, const char *message)
+ : m_level(level), m_message()
+{
+ m_message.copy(message, strlen(message), &my_charset_latin1);
+}
+
+/**
+ Print a buffered log to the real log file.
+*/
+void Buffered_log::print()
+{
+ /*
+ Since messages are buffered, they can be printed out
+ of order with other entries in the log.
+ Add "Buffered xxx" to the message text to prevent confusion.
+ */
+ switch(m_level)
+ {
+ case ERROR_LEVEL:
+ sql_print_error("Buffered error: %s\n", m_message.c_ptr_safe());
+ break;
+ case WARNING_LEVEL:
+ sql_print_warning("Buffered warning: %s\n", m_message.c_ptr_safe());
+ break;
+ case INFORMATION_LEVEL:
+ /*
+ Messages printed as "information" still end up in the mysqld *error* log,
+ but with a [Note] tag instead of an [ERROR] tag.
+ While this is probably fine for a human reading the log,
+ it is upsetting existing automated scripts used to parse logs,
+ because such scripts are likely to not already handle [Note] properly.
+ INFORMATION_LEVEL messages are simply silenced, on purpose,
+ to avoid un needed verbosity.
+ */
+ break;
+ }
+}
+
+/**
+ Collection of all the buffered log messages.
+*/
+class Buffered_logs
+{
+public:
+ Buffered_logs()
+ {}
+
+ ~Buffered_logs()
+ {}
+
+ void init();
+ void cleanup();
+
+ void buffer(enum loglevel m_level, const char *msg);
+ void print();
+private:
+ /**
+ Memory root to use to store buffered logs.
+ This memory root lifespan is between init and cleanup.
+ Once the buffered logs are printed, they are not needed anymore,
+ and all the memory used is reclaimed.
+ */
+ MEM_ROOT m_root;
+ /** List of buffered log messages. */
+ List<Buffered_log> m_list;
+};
+
+void Buffered_logs::init()
+{
+ init_alloc_root(&m_root, 1024, 0, MYF(0));
+}
+
+void Buffered_logs::cleanup()
+{
+ m_list.delete_elements();
+ free_root(&m_root, MYF(0));
+}
+
+/**
+ Add a log message to the buffer.
+*/
+void Buffered_logs::buffer(enum loglevel level, const char *msg)
+{
+ /*
+ Do not let Sql_alloc::operator new(size_t) allocate memory,
+ there is no memory root associated with the main() thread.
+ Give explicitly the proper memory root to use to
+ Sql_alloc::operator new(size_t, MEM_ROOT *) instead.
+ */
+ Buffered_log *log= new (&m_root) Buffered_log(level, msg);
+ if (log)
+ m_list.push_back(log, &m_root);
+}
+
+/**
+ Print buffered log messages.
+*/
+void Buffered_logs::print()
+{
+ Buffered_log *log;
+ List_iterator_fast<Buffered_log> it(m_list);
+ while ((log= it++))
+ log->print();
+}
+
+/** Logs reported before a logger is available. */
+static Buffered_logs buffered_logs;
+
+static MYSQL_SOCKET unix_sock, base_ip_sock, extra_ip_sock;
+struct my_rnd_struct sql_rand; ///< used by sql_class.cc:THD::THD()
+
+#ifndef EMBEDDED_LIBRARY
+/**
+ Error reporter that buffer log messages.
+ @param level log message level
+ @param format log message format string
+*/
+C_MODE_START
+static void buffered_option_error_reporter(enum loglevel level,
+ const char *format, ...)
+{
+ va_list args;
+ char buffer[1024];
+
+ va_start(args, format);
+ my_vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+ buffered_logs.buffer(level, buffer);
+}
+
+
+/**
+ Character set and collation error reporter that prints to sql error log.
+ @param level log message level
+ @param format log message format string
+
+ This routine is used to print character set and collation
+ warnings and errors inside an already running mysqld server,
+ e.g. when a character set or collation is requested for the very first time
+ and its initialization does not go well for some reasons.
+
+ Note: At early mysqld initialization stage,
+ when error log is not yet available,
+ we use buffered_option_error_reporter() instead,
+ to print general character set subsystem initialization errors,
+ such as Index.xml syntax problems, bad XML tag hierarchy, etc.
+*/
+static void charset_error_reporter(enum loglevel level,
+ const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vprint_msg_to_log(level, format, args);
+ va_end(args);
+}
+C_MODE_END
+
+struct passwd *user_info;
+static pthread_t select_thread;
+#endif
+
+/* OS specific variables */
+
+#ifdef __WIN__
+#undef getpid
+#include <process.h>
+
+static mysql_cond_t COND_handler_count;
+static uint handler_count;
+static bool start_mode=0, use_opt_args;
+static int opt_argc;
+static char **opt_argv;
+
+#if !defined(EMBEDDED_LIBRARY)
+static HANDLE hEventShutdown;
+static char shutdown_event_name[40];
+#include "nt_servc.h"
+static NTService Service; ///< Service object for WinNT
+#endif /* EMBEDDED_LIBRARY */
+#endif /* __WIN__ */
+
+#ifdef _WIN32
+static char pipe_name[512];
+static SECURITY_ATTRIBUTES saPipeSecurity;
+static SECURITY_DESCRIPTOR sdPipeDescriptor;
+static HANDLE hPipe = INVALID_HANDLE_VALUE;
+#endif
+
+#ifndef EMBEDDED_LIBRARY
+bool mysqld_embedded=0;
+#else
+bool mysqld_embedded=1;
+#endif
+
+static my_bool plugins_are_initialized= FALSE;
+
+#ifndef DBUG_OFF
+static const char* default_dbug_option;
+#endif
+static const char *current_dbug_option="disabled";
+#ifdef HAVE_LIBWRAP
+const char *libwrapName= NULL;
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_WARNING;
+#endif
+#ifdef HAVE_QUERY_CACHE
+ulong query_cache_min_res_unit= QUERY_CACHE_MIN_RESULT_DATA_SIZE;
+Query_cache query_cache;
+#endif
+#ifdef HAVE_SMEM
+char *shared_memory_base_name= default_shared_memory_base_name;
+my_bool opt_enable_shared_memory;
+HANDLE smem_event_connect_request= 0;
+#endif
+
+my_bool opt_use_ssl = 0;
+char *opt_ssl_ca= NULL, *opt_ssl_capath= NULL, *opt_ssl_cert= NULL,
+ *opt_ssl_cipher= NULL, *opt_ssl_key= NULL, *opt_ssl_crl= NULL,
+ *opt_ssl_crlpath= NULL;
+
+
+static scheduler_functions thread_scheduler_struct, extra_thread_scheduler_struct;
+scheduler_functions *thread_scheduler= &thread_scheduler_struct,
+ *extra_thread_scheduler= &extra_thread_scheduler_struct;
+
+#ifdef HAVE_OPENSSL
+#include <openssl/crypto.h>
+#ifndef HAVE_YASSL
+typedef struct CRYPTO_dynlock_value
+{
+ mysql_rwlock_t lock;
+} openssl_lock_t;
+
+static openssl_lock_t *openssl_stdlocks;
+static openssl_lock_t *openssl_dynlock_create(const char *, int);
+static void openssl_dynlock_destroy(openssl_lock_t *, const char *, int);
+static void openssl_lock_function(int, int, const char *, int);
+static void openssl_lock(int, openssl_lock_t *, const char *, int);
+static unsigned long openssl_id_function();
+#endif
+char *des_key_file;
+#ifndef EMBEDDED_LIBRARY
+struct st_VioSSLFd *ssl_acceptor_fd;
+#endif
+#endif /* HAVE_OPENSSL */
+
+/**
+ Number of currently active user connections. The variable is protected by
+ LOCK_connection_count.
+*/
+uint connection_count= 0, extra_connection_count= 0;
+
+my_bool opt_gtid_strict_mode= FALSE;
+
+
+/* Function declarations */
+
+pthread_handler_t signal_hand(void *arg);
+static int mysql_init_variables(void);
+static int get_options(int *argc_ptr, char ***argv_ptr);
+static bool add_terminator(DYNAMIC_ARRAY *options);
+static bool add_many_options(DYNAMIC_ARRAY *, my_option *, size_t);
+extern "C" my_bool mysqld_get_one_option(int, const struct my_option *, char *);
+static int init_thread_environment();
+static char *get_relative_path(const char *path);
+static int fix_paths(void);
+void handle_connections_sockets();
+#ifdef _WIN32
+pthread_handler_t handle_connections_sockets_thread(void *arg);
+#endif
+pthread_handler_t kill_server_thread(void *arg);
+static void bootstrap(MYSQL_FILE *file);
+static bool read_init_file(char *file_name);
+#ifdef _WIN32
+pthread_handler_t handle_connections_namedpipes(void *arg);
+#endif
+#ifdef HAVE_SMEM
+pthread_handler_t handle_connections_shared_memory(void *arg);
+#endif
+pthread_handler_t handle_slave(void *arg);
+static void clean_up(bool print_message);
+static int test_if_case_insensitive(const char *dir_name);
+
+#ifndef EMBEDDED_LIBRARY
+static bool pid_file_created= false;
+static void usage(void);
+static void start_signal_handler(void);
+static void close_server_sock();
+static void clean_up_mutexes(void);
+static void wait_for_signal_thread_to_end(void);
+static void create_pid_file();
+static void mysqld_exit(int exit_code) __attribute__((noreturn));
+#endif
+static void delete_pid_file(myf flags);
+static void end_ssl();
+
+
+#ifndef EMBEDDED_LIBRARY
+/****************************************************************************
+** Code to end mysqld
+****************************************************************************/
+
+static void close_connections(void)
+{
+#ifdef EXTRA_DEBUG
+ int count=0;
+#endif
+ DBUG_ENTER("close_connections");
+
+ /* Clear thread cache */
+ kill_cached_threads++;
+ flush_thread_cache();
+
+ /* kill connection thread */
+#if !defined(__WIN__)
+ DBUG_PRINT("quit", ("waiting for select thread: 0x%lx",
+ (ulong) select_thread));
+ mysql_mutex_lock(&LOCK_thread_count);
+
+ while (select_thread_in_use)
+ {
+ struct timespec abstime;
+ int error;
+ LINT_INIT(error);
+ DBUG_PRINT("info",("Waiting for select thread"));
+
+#ifndef DONT_USE_THR_ALARM
+ if (pthread_kill(select_thread, thr_client_alarm))
+ break; // allready dead
+#endif
+ set_timespec(abstime, 2);
+ for (uint tmp=0 ; tmp < 10 && select_thread_in_use; tmp++)
+ {
+ error= mysql_cond_timedwait(&COND_thread_count, &LOCK_thread_count,
+ &abstime);
+ if (error != EINTR)
+ break;
+ }
+#ifdef EXTRA_DEBUG
+ if (error != 0 && error != ETIMEDOUT && !count++)
+ sql_print_error("Got error %d from mysql_cond_timedwait", error);
+#endif
+ close_server_sock();
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+#endif /* __WIN__ */
+
+
+ /* Abort listening to new connections */
+ DBUG_PRINT("quit",("Closing sockets"));
+ if (!opt_disable_networking )
+ {
+ if (mysql_socket_getfd(base_ip_sock) != INVALID_SOCKET)
+ {
+ (void) mysql_socket_shutdown(base_ip_sock, SHUT_RDWR);
+ (void) mysql_socket_close(base_ip_sock);
+ base_ip_sock= MYSQL_INVALID_SOCKET;
+ }
+ if (mysql_socket_getfd(extra_ip_sock) != INVALID_SOCKET)
+ {
+ (void) mysql_socket_shutdown(extra_ip_sock, SHUT_RDWR);
+ (void) mysql_socket_close(extra_ip_sock);
+ extra_ip_sock= MYSQL_INVALID_SOCKET;
+ }
+ }
+#ifdef _WIN32
+ if (hPipe != INVALID_HANDLE_VALUE && opt_enable_named_pipe)
+ {
+ HANDLE temp;
+ DBUG_PRINT("quit", ("Closing named pipes") );
+
+ /* Create connection to the handle named pipe handler to break the loop */
+ if ((temp = CreateFile(pipe_name,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL )) != INVALID_HANDLE_VALUE)
+ {
+ WaitNamedPipe(pipe_name, 1000);
+ DWORD dwMode = PIPE_READMODE_BYTE | PIPE_WAIT;
+ SetNamedPipeHandleState(temp, &dwMode, NULL, NULL);
+ CancelIo(temp);
+ DisconnectNamedPipe(temp);
+ CloseHandle(temp);
+ }
+ }
+#endif
+#ifdef HAVE_SYS_UN_H
+ if (mysql_socket_getfd(unix_sock) != INVALID_SOCKET)
+ {
+ (void) mysql_socket_shutdown(unix_sock, SHUT_RDWR);
+ (void) mysql_socket_close(unix_sock);
+ (void) unlink(mysqld_unix_port);
+ unix_sock= MYSQL_INVALID_SOCKET;
+ }
+#endif
+ end_thr_alarm(0); // Abort old alarms.
+
+ /*
+ First signal all threads that it's time to die
+ This will give the threads some time to gracefully abort their
+ statements and inform their clients that the server is about to die.
+ */
+
+ THD *tmp;
+ mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
+
+ I_List_iterator<THD> it(threads);
+ while ((tmp=it++))
+ {
+ DBUG_PRINT("quit",("Informing thread %ld that it's time to die",
+ tmp->thread_id));
+ /* We skip slave threads & scheduler on this first loop through. */
+ if (tmp->slave_thread)
+ continue;
+
+ tmp->killed= KILL_SERVER_HARD;
+ MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (tmp));
+ mysql_mutex_lock(&tmp->LOCK_thd_data);
+ if (tmp->mysys_var)
+ {
+ tmp->mysys_var->abort=1;
+ mysql_mutex_lock(&tmp->mysys_var->mutex);
+ if (tmp->mysys_var->current_cond)
+ {
+ uint i;
+ for (i=0; i < 2; i++)
+ {
+ int ret= mysql_mutex_trylock(tmp->mysys_var->current_mutex);
+ mysql_cond_broadcast(tmp->mysys_var->current_cond);
+ if (!ret)
+ {
+ /* Thread has surely got the signal, unlock and abort */
+ mysql_mutex_unlock(tmp->mysys_var->current_mutex);
+ break;
+ }
+ sleep(1);
+ }
+ }
+ mysql_mutex_unlock(&tmp->mysys_var->mutex);
+ }
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+ }
+ mysql_mutex_unlock(&LOCK_thread_count); // For unlink from list
+
+ Events::deinit();
+ end_slave();
+
+ /*
+ Give threads time to die.
+
+ In 5.5, this was waiting 100 rounds @ 20 milliseconds/round, so as little
+ as 2 seconds, depending on thread scheduling.
+
+ From 10.0, we increase this to 1000 rounds / 20 seconds. The rationale is
+ that on a server with heavy I/O load, it is quite possible for eg. an
+ fsync() of the binlog or whatever to cause something like LOCK_log to be
+ held for more than 2 seconds. We do not want to force kill threads in
+ such cases, if it can be avoided. Note that normally, the wait will be
+ much smaller than even 2 seconds, this is only a safety fallback against
+ stuck threads so server shutdown is not held up forever.
+ */
+ for (int i= 0; *(volatile int32*) &thread_count && i < 1000; i++)
+ my_sleep(20000);
+
+ /*
+ Force remaining threads to die by closing the connection to the client
+ This will ensure that threads that are waiting for a command from the
+ client on a blocking read call are aborted.
+ */
+
+ for (;;)
+ {
+ DBUG_PRINT("quit",("Locking LOCK_thread_count"));
+ mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
+ if (!(tmp=threads.get()))
+ {
+ DBUG_PRINT("quit",("Unlocking LOCK_thread_count"));
+ mysql_mutex_unlock(&LOCK_thread_count);
+ break;
+ }
+#ifndef __bsdi__ // Bug in BSDI kernel
+ if (tmp->vio_ok())
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_warning(ER_DEFAULT(ER_FORCING_CLOSE),my_progname,
+ tmp->thread_id,
+ (tmp->main_security_ctx.user ?
+ tmp->main_security_ctx.user : ""));
+ close_connection(tmp,ER_SERVER_SHUTDOWN);
+ }
+#endif
+ DBUG_PRINT("quit",("Unlocking LOCK_thread_count"));
+ mysql_mutex_unlock(&LOCK_thread_count);
+ }
+ /* All threads has now been aborted */
+ DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count));
+ mysql_mutex_lock(&LOCK_thread_count);
+ while (thread_count)
+ {
+ mysql_cond_wait(&COND_thread_count, &LOCK_thread_count);
+ DBUG_PRINT("quit",("One thread died (count=%u)",thread_count));
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ DBUG_PRINT("quit",("close_connections thread"));
+ DBUG_VOID_RETURN;
+}
+
+
+#ifdef HAVE_CLOSE_SERVER_SOCK
+static void close_socket(MYSQL_SOCKET sock, const char *info)
+{
+ DBUG_ENTER("close_socket");
+
+ if (mysql_socket_getfd(sock) != INVALID_SOCKET)
+ {
+ DBUG_PRINT("info", ("calling shutdown on %s socket", info));
+ (void) mysql_socket_shutdown(sock, SHUT_RDWR);
+ }
+ DBUG_VOID_RETURN;
+}
+#endif
+
+
+static void close_server_sock()
+{
+#ifdef HAVE_CLOSE_SERVER_SOCK
+ DBUG_ENTER("close_server_sock");
+
+ close_socket(base_ip_sock, "TCP/IP");
+ close_socket(extra_ip_sock, "TCP/IP");
+ close_socket(unix_sock, "unix/IP");
+
+ if (mysql_socket_getfd(unix_sock) != INVALID_SOCKET)
+ (void) unlink(mysqld_unix_port);
+ base_ip_sock= extra_ip_sock= unix_sock= MYSQL_INVALID_SOCKET;
+
+ DBUG_VOID_RETURN;
+#endif
+}
+
+#endif /*EMBEDDED_LIBRARY*/
+
+
+void kill_mysql(void)
+{
+ DBUG_ENTER("kill_mysql");
+
+#if defined(SIGNALS_DONT_BREAK_READ) && !defined(EMBEDDED_LIBRARY)
+ abort_loop=1; // Break connection loops
+ close_server_sock(); // Force accept to wake up
+#endif
+
+#if defined(__WIN__)
+#if !defined(EMBEDDED_LIBRARY)
+ {
+ if (!SetEvent(hEventShutdown))
+ {
+ DBUG_PRINT("error",("Got error: %ld from SetEvent",GetLastError()));
+ }
+ /*
+ or:
+ HANDLE hEvent=OpenEvent(0, FALSE, "MySqlShutdown");
+ SetEvent(hEventShutdown);
+ CloseHandle(hEvent);
+ */
+ }
+#endif
+#elif defined(HAVE_PTHREAD_KILL)
+ if (pthread_kill(signal_thread, MYSQL_KILL_SIGNAL))
+ {
+ DBUG_PRINT("error",("Got error %d from pthread_kill",errno)); /* purecov: inspected */
+ }
+#elif !defined(SIGNALS_DONT_BREAK_READ)
+ kill(current_pid, MYSQL_KILL_SIGNAL);
+#endif
+ DBUG_PRINT("quit",("After pthread_kill"));
+ shutdown_in_progress=1; // Safety if kill didn't work
+#ifdef SIGNALS_DONT_BREAK_READ
+ if (!kill_in_progress)
+ {
+ pthread_t tmp;
+ int error;
+ abort_loop=1;
+ if ((error= mysql_thread_create(0, /* Not instrumented */
+ &tmp, &connection_attrib,
+ kill_server_thread, (void*) 0)))
+ sql_print_error("Can't create thread to kill server (errno= %d).", error);
+ }
+#endif
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Force server down. Kill all connections and threads and exit.
+
+ @param sig_ptr Signal number that caused kill_server to be called.
+
+ @note
+ A signal number of 0 mean that the function was not called
+ from a signal handler and there is thus no signal to block
+ or stop, we just want to kill the server.
+*/
+
+#if !defined(__WIN__)
+static void *kill_server(void *sig_ptr)
+#define RETURN_FROM_KILL_SERVER return 0
+#else
+static void __cdecl kill_server(int sig_ptr)
+#define RETURN_FROM_KILL_SERVER return
+#endif
+{
+ DBUG_ENTER("kill_server");
+#ifndef EMBEDDED_LIBRARY
+ int sig=(int) (long) sig_ptr; // This is passed a int
+ // if there is a signal during the kill in progress, ignore the other
+ if (kill_in_progress) // Safety
+ {
+ DBUG_LEAVE;
+ RETURN_FROM_KILL_SERVER;
+ }
+ kill_in_progress=TRUE;
+ abort_loop=1; // This should be set
+ if (sig != 0) // 0 is not a valid signal number
+ my_sigset(sig, SIG_IGN); /* purify inspected */
+ if (sig == MYSQL_KILL_SIGNAL || sig == 0)
+ sql_print_information(ER_DEFAULT(ER_NORMAL_SHUTDOWN),my_progname);
+ else
+ sql_print_error(ER_DEFAULT(ER_GOT_SIGNAL),my_progname,sig); /* purecov: inspected */
+
+#ifdef HAVE_SMEM
+ /*
+ Send event to smem_event_connect_request for aborting
+ */
+ if (opt_enable_shared_memory)
+ {
+ if (!SetEvent(smem_event_connect_request))
+ {
+ DBUG_PRINT("error",
+ ("Got error: %ld from SetEvent of smem_event_connect_request",
+ GetLastError()));
+ }
+ }
+#endif
+
+ close_connections();
+ if (sig != MYSQL_KILL_SIGNAL &&
+ sig != 0)
+ unireg_abort(1); /* purecov: inspected */
+ else
+ unireg_end();
+
+ /* purecov: begin deadcode */
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+ my_thread_end();
+ pthread_exit(0);
+ /* purecov: end */
+
+ RETURN_FROM_KILL_SERVER; // Avoid compiler warnings
+
+#else /* EMBEDDED_LIBRARY*/
+
+ DBUG_LEAVE;
+ RETURN_FROM_KILL_SERVER;
+
+#endif /* EMBEDDED_LIBRARY */
+}
+
+
+#if defined(USE_ONE_SIGNAL_HAND)
+pthread_handler_t kill_server_thread(void *arg __attribute__((unused)))
+{
+ my_thread_init(); // Initialize new thread
+ kill_server(0);
+ /* purecov: begin deadcode */
+ my_thread_end();
+ pthread_exit(0);
+ return 0;
+ /* purecov: end */
+}
+#endif
+
+
+extern "C" sig_handler print_signal_warning(int sig)
+{
+ if (global_system_variables.log_warnings)
+ sql_print_warning("Got signal %d from thread %ld", sig,my_thread_id());
+#ifdef SIGNAL_HANDLER_RESET_ON_DELIVERY
+ my_sigset(sig,print_signal_warning); /* int. thread system calls */
+#endif
+#if !defined(__WIN__)
+ if (sig == SIGALRM)
+ alarm(2); /* reschedule alarm */
+#endif
+}
+
+#ifndef EMBEDDED_LIBRARY
+
+static void init_error_log_mutex()
+{
+ mysql_mutex_init(key_LOCK_error_log, &LOCK_error_log, MY_MUTEX_INIT_FAST);
+}
+
+
+static void clean_up_error_log_mutex()
+{
+ mysql_mutex_destroy(&LOCK_error_log);
+}
+
+
+/**
+ cleanup all memory and end program nicely.
+
+ If SIGNALS_DONT_BREAK_READ is defined, this function is called
+ by the main thread. To get MySQL to shut down nicely in this case
+ (Mac OS X) we have to call exit() instead if pthread_exit().
+
+ @note
+ This function never returns.
+*/
+void unireg_end(void)
+{
+ clean_up(1);
+ my_thread_end();
+#if defined(SIGNALS_DONT_BREAK_READ)
+ exit(0);
+#else
+ pthread_exit(0); // Exit is in main thread
+#endif
+}
+
+
+extern "C" void unireg_abort(int exit_code)
+{
+ DBUG_ENTER("unireg_abort");
+
+ if (opt_help)
+ usage();
+ if (exit_code)
+ sql_print_error("Aborting\n");
+ clean_up(!opt_abort && (exit_code || !opt_bootstrap)); /* purecov: inspected */
+ DBUG_PRINT("quit",("done with cleanup in unireg_abort"));
+ mysqld_exit(exit_code);
+}
+
+static void mysqld_exit(int exit_code)
+{
+ DBUG_ENTER("mysqld_exit");
+ /*
+ Important note: we wait for the signal thread to end,
+ but if a kill -15 signal was sent, the signal thread did
+ spawn the kill_server_thread thread, which is running concurrently.
+ */
+ rpl_deinit_gtid_waiting();
+ rpl_deinit_gtid_slave_state();
+ wait_for_signal_thread_to_end();
+ mysql_audit_finalize();
+ clean_up_mutexes();
+ clean_up_error_log_mutex();
+ my_end((opt_endinfo ? MY_CHECK_ERROR | MY_GIVE_INFO : 0));
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ shutdown_performance_schema(); // we do it as late as possible
+#endif
+ DBUG_LEAVE;
+ exit(exit_code); /* purecov: inspected */
+}
+
+#endif /* !EMBEDDED_LIBRARY */
+
+void clean_up(bool print_message)
+{
+ DBUG_PRINT("exit",("clean_up"));
+ if (cleanup_done++)
+ return; /* purecov: inspected */
+
+#ifdef HAVE_REPLICATION
+ // We must call end_slave() as clean_up may have been called during startup
+ end_slave();
+ if (use_slave_mask)
+ my_bitmap_free(&slave_error_mask);
+#endif
+ stop_handle_manager();
+ release_ddl_log();
+
+ /*
+ make sure that handlers finish up
+ what they have that is dependent on the binlog
+ */
+ ha_binlog_end(current_thd);
+
+ logger.cleanup_base();
+
+ injector::free_instance();
+ mysql_bin_log.cleanup();
+
+ my_tz_free();
+ my_dboptions_cache_free();
+ ignore_db_dirs_free();
+ servers_free(1);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ acl_free(1);
+ grant_free();
+#endif
+ query_cache_destroy();
+ hostname_cache_free();
+ item_func_sleep_free();
+ lex_free(); /* Free some memory */
+ item_create_cleanup();
+ if (!opt_noacl)
+ {
+#ifdef HAVE_DLOPEN
+ udf_free();
+#endif
+ }
+ tdc_start_shutdown();
+ plugin_shutdown();
+ ha_end();
+ if (tc_log)
+ tc_log->close();
+ delegates_destroy();
+ xid_cache_free();
+ tdc_deinit();
+ mdl_destroy();
+ key_caches.delete_elements((void (*)(const char*, uchar*)) free_key_cache);
+ wt_end();
+ multi_keycache_free();
+ sp_cache_end();
+ free_status_vars();
+ end_thr_alarm(1); /* Free allocated memory */
+ my_free_open_file_info();
+ if (defaults_argv)
+ free_defaults(defaults_argv);
+ free_tmpdir(&mysql_tmpdir_list);
+ my_bitmap_free(&temp_pool);
+ free_max_user_conn();
+ free_global_user_stats();
+ free_global_client_stats();
+ free_global_table_stats();
+ free_global_index_stats();
+ delete_dynamic(&all_options);
+ free_all_rpl_filters();
+#ifdef HAVE_REPLICATION
+ end_slave_list();
+#endif
+ my_uuid_end();
+ delete binlog_filter;
+ delete global_rpl_filter;
+ end_ssl();
+#ifndef EMBEDDED_LIBRARY
+ vio_end();
+#endif /*!EMBEDDED_LIBRARY*/
+#if defined(ENABLED_DEBUG_SYNC)
+ /* End the debug sync facility. See debug_sync.cc. */
+ debug_sync_end();
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
+ delete_pid_file(MYF(0));
+
+ if (print_message && my_default_lc_messages && server_start_time)
+ sql_print_information(ER_DEFAULT(ER_SHUTDOWN_COMPLETE),my_progname);
+ cleanup_errmsgs();
+ MYSQL_CALLBACK(thread_scheduler, end, ());
+ mysql_library_end();
+ finish_client_errs();
+ (void) my_error_unregister(ER_ERROR_FIRST, ER_ERROR_LAST); // finish server errs
+ DBUG_PRINT("quit", ("Error messages freed"));
+ /* Tell main we are ready */
+ logger.cleanup_end();
+ sys_var_end();
+ my_atomic_rwlock_destroy(&global_query_id_lock);
+ my_atomic_rwlock_destroy(&thread_running_lock);
+ my_atomic_rwlock_destroy(&thread_count_lock);
+ my_atomic_rwlock_destroy(&statistics_lock);
+ my_atomic_rwlock_destroy(&slave_executed_entries_lock);
+ free_charsets();
+ mysql_mutex_lock(&LOCK_thread_count);
+ DBUG_PRINT("quit", ("got thread count lock"));
+ ready_to_exit=1;
+ /* do the broadcast inside the lock to ensure that my_end() is not called */
+ mysql_cond_broadcast(&COND_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ free_list(opt_plugin_load_list_ptr);
+
+ if (THR_THD)
+ (void) pthread_key_delete(THR_THD);
+
+ if (THR_MALLOC)
+ (void) pthread_key_delete(THR_MALLOC);
+
+ /*
+ The following lines may never be executed as the main thread may have
+ killed us
+ */
+ DBUG_PRINT("quit", ("done with cleanup"));
+} /* clean_up */
+
+
+#ifndef EMBEDDED_LIBRARY
+
+/**
+ This is mainly needed when running with purify, but it's still nice to
+ know that all child threads have died when mysqld exits.
+*/
+static void wait_for_signal_thread_to_end()
+{
+ uint i;
+ /*
+ Wait up to 10 seconds for signal thread to die. We use this mainly to
+ avoid getting warnings that my_thread_end has not been called
+ */
+ for (i= 0 ; i < 100 && signal_thread_in_use; i++)
+ {
+ if (pthread_kill(signal_thread, MYSQL_KILL_SIGNAL) == ESRCH)
+ break;
+ my_sleep(100); // Give it time to die
+ }
+}
+#endif /*EMBEDDED_LIBRARY*/
+
+static void clean_up_mutexes()
+{
+ DBUG_ENTER("clean_up_mutexes");
+ mysql_rwlock_destroy(&LOCK_grant);
+ mysql_mutex_destroy(&LOCK_thread_count);
+ mysql_mutex_destroy(&LOCK_thread_cache);
+ mysql_mutex_destroy(&LOCK_status);
+ mysql_mutex_destroy(&LOCK_show_status);
+ mysql_mutex_destroy(&LOCK_delayed_insert);
+ mysql_mutex_destroy(&LOCK_delayed_status);
+ mysql_mutex_destroy(&LOCK_delayed_create);
+ mysql_mutex_destroy(&LOCK_crypt);
+ mysql_mutex_destroy(&LOCK_user_conn);
+ mysql_mutex_destroy(&LOCK_connection_count);
+ mysql_mutex_destroy(&LOCK_stats);
+ mysql_mutex_destroy(&LOCK_global_user_client_stats);
+ mysql_mutex_destroy(&LOCK_global_table_stats);
+ mysql_mutex_destroy(&LOCK_global_index_stats);
+#ifdef HAVE_OPENSSL
+ mysql_mutex_destroy(&LOCK_des_key_file);
+#ifndef HAVE_YASSL
+ for (int i= 0; i < CRYPTO_num_locks(); ++i)
+ mysql_rwlock_destroy(&openssl_stdlocks[i].lock);
+ OPENSSL_free(openssl_stdlocks);
+#endif /* HAVE_YASSL */
+#endif /* HAVE_OPENSSL */
+#ifdef HAVE_REPLICATION
+ mysql_mutex_destroy(&LOCK_rpl_status);
+#endif /* HAVE_REPLICATION */
+ mysql_mutex_destroy(&LOCK_active_mi);
+ mysql_rwlock_destroy(&LOCK_sys_init_connect);
+ mysql_rwlock_destroy(&LOCK_sys_init_slave);
+ mysql_mutex_destroy(&LOCK_global_system_variables);
+ mysql_rwlock_destroy(&LOCK_system_variables_hash);
+ mysql_mutex_destroy(&LOCK_short_uuid_generator);
+ mysql_mutex_destroy(&LOCK_prepared_stmt_count);
+ mysql_mutex_destroy(&LOCK_error_messages);
+ mysql_cond_destroy(&COND_thread_count);
+ mysql_cond_destroy(&COND_thread_cache);
+ mysql_cond_destroy(&COND_flush_thread_cache);
+ mysql_mutex_destroy(&LOCK_server_started);
+ mysql_cond_destroy(&COND_server_started);
+ mysql_mutex_destroy(&LOCK_prepare_ordered);
+ mysql_cond_destroy(&COND_prepare_ordered);
+ mysql_mutex_destroy(&LOCK_commit_ordered);
+ mysql_mutex_destroy(&LOCK_slave_init);
+ mysql_cond_destroy(&COND_slave_init);
+ DBUG_VOID_RETURN;
+}
+
+
+/****************************************************************************
+** Init IP and UNIX socket
+****************************************************************************/
+
+#ifndef EMBEDDED_LIBRARY
+static void set_ports()
+{
+ char *env;
+ if (!mysqld_port && !opt_disable_networking)
+ { // Get port if not from commandline
+ mysqld_port= MYSQL_PORT;
+
+ /*
+ if builder specifically requested a default port, use that
+ (even if it coincides with our factory default).
+ only if they didn't do we check /etc/services (and, failing
+ on that, fall back to the factory default of 3306).
+ either default can be overridden by the environment variable
+ MYSQL_TCP_PORT, which in turn can be overridden with command
+ line options.
+ */
+
+#if MYSQL_PORT_DEFAULT == 0
+ struct servent *serv_ptr;
+ if ((serv_ptr= getservbyname("mysql", "tcp")))
+ mysqld_port= ntohs((u_short) serv_ptr->s_port); /* purecov: inspected */
+#endif
+ if ((env = getenv("MYSQL_TCP_PORT")))
+ mysqld_port= (uint) atoi(env); /* purecov: inspected */
+ }
+ if (!mysqld_unix_port)
+ {
+#ifdef __WIN__
+ mysqld_unix_port= (char*) MYSQL_NAMEDPIPE;
+#else
+ mysqld_unix_port= (char*) MYSQL_UNIX_ADDR;
+#endif
+ if ((env = getenv("MYSQL_UNIX_PORT")))
+ mysqld_unix_port= env; /* purecov: inspected */
+ }
+}
+
+/* Change to run as another user if started with --user */
+
+static struct passwd *check_user(const char *user)
+{
+#if !defined(__WIN__)
+ struct passwd *tmp_user_info;
+ uid_t user_id= geteuid();
+
+ // Don't bother if we aren't superuser
+ if (user_id)
+ {
+ if (user)
+ {
+ /* Don't give a warning, if real user is same as given with --user */
+ /* purecov: begin tested */
+ tmp_user_info= getpwnam(user);
+ if ((!tmp_user_info || user_id != tmp_user_info->pw_uid) &&
+ global_system_variables.log_warnings)
+ sql_print_warning(
+ "One can only use the --user switch if running as root\n");
+ /* purecov: end */
+ }
+ return NULL;
+ }
+ if (!user)
+ {
+ if (!opt_bootstrap && !opt_help)
+ {
+ sql_print_error("Fatal error: Please consult the Knowledge Base "
+ "to find out how to run mysqld as root!\n");
+ unireg_abort(1);
+ }
+ return NULL;
+ }
+ /* purecov: begin tested */
+ if (!strcmp(user,"root"))
+ return NULL; // Avoid problem with dynamic libraries
+
+ if (!(tmp_user_info= getpwnam(user)))
+ {
+ // Allow a numeric uid to be used
+ const char *pos;
+ for (pos= user; my_isdigit(mysqld_charset,*pos); pos++) ;
+ if (*pos) // Not numeric id
+ goto err;
+ if (!(tmp_user_info= getpwuid(atoi(user))))
+ goto err;
+ }
+
+ return tmp_user_info;
+ /* purecov: end */
+
+err:
+ sql_print_error("Fatal error: Can't change to run as user '%s' ; Please check that the user exists!\n",user);
+ unireg_abort(1);
+#endif
+ return NULL;
+}
+
+static inline void allow_coredumps()
+{
+#ifdef PR_SET_DUMPABLE
+ if (test_flags & TEST_CORE_ON_SIGNAL)
+ {
+ /* inform kernel that process is dumpable */
+ (void) prctl(PR_SET_DUMPABLE, 1);
+ }
+#endif
+}
+
+
+static void set_user(const char *user, struct passwd *user_info_arg)
+{
+ /* purecov: begin tested */
+#if !defined(__WIN__)
+ DBUG_ASSERT(user_info_arg != 0);
+#ifdef HAVE_INITGROUPS
+ /*
+ We can get a SIGSEGV when calling initgroups() on some systems when NSS
+ is configured to use LDAP and the server is statically linked. We set
+ calling_initgroups as a flag to the SIGSEGV handler that is then used to
+ output a specific message to help the user resolve this problem.
+ */
+ calling_initgroups= 1;
+ initgroups((char*) user, user_info_arg->pw_gid);
+ calling_initgroups= 0;
+#endif
+ if (setgid(user_info_arg->pw_gid) == -1)
+ {
+ sql_perror("setgid");
+ unireg_abort(1);
+ }
+ if (setuid(user_info_arg->pw_uid) == -1)
+ {
+ sql_perror("setuid");
+ unireg_abort(1);
+ }
+ allow_coredumps();
+#endif
+ /* purecov: end */
+}
+
+
+static void set_effective_user(struct passwd *user_info_arg)
+{
+#if !defined(__WIN__)
+ DBUG_ASSERT(user_info_arg != 0);
+ if (setregid((gid_t)-1, user_info_arg->pw_gid) == -1)
+ {
+ sql_perror("setregid");
+ unireg_abort(1);
+ }
+ if (setreuid((uid_t)-1, user_info_arg->pw_uid) == -1)
+ {
+ sql_perror("setreuid");
+ unireg_abort(1);
+ }
+ allow_coredumps();
+#endif
+}
+
+
+/** Change root user if started with @c --chroot . */
+static void set_root(const char *path)
+{
+#if !defined(__WIN__)
+ if (chroot(path) == -1)
+ {
+ sql_perror("chroot");
+ unireg_abort(1);
+ }
+ my_setwd("/", MYF(0));
+#endif
+}
+
+/**
+ Activate usage of a tcp port
+*/
+
+static MYSQL_SOCKET activate_tcp_port(uint port)
+{
+ struct addrinfo *ai, *a;
+ struct addrinfo hints;
+ int error;
+ int arg;
+ char port_buf[NI_MAXSERV];
+ const char *real_bind_addr_str;
+ MYSQL_SOCKET ip_sock= MYSQL_INVALID_SOCKET;
+ DBUG_ENTER("activate_tcp_port");
+ DBUG_PRINT("general",("IP Socket is %d",port));
+
+ bzero(&hints, sizeof (hints));
+ hints.ai_flags= AI_PASSIVE;
+ hints.ai_socktype= SOCK_STREAM;
+ hints.ai_family= AF_UNSPEC;
+
+ if (my_bind_addr_str && strcmp(my_bind_addr_str, "*") == 0)
+ real_bind_addr_str= NULL; // windows doesn't seem to support * here
+ else
+ real_bind_addr_str= my_bind_addr_str;
+
+ my_snprintf(port_buf, NI_MAXSERV, "%d", port);
+ error= getaddrinfo(real_bind_addr_str, port_buf, &hints, &ai);
+ if (error != 0)
+ {
+ DBUG_PRINT("error",("Got error: %d from getaddrinfo()", error));
+
+ sql_print_error("%s: %s", ER_DEFAULT(ER_IPSOCK_ERROR), gai_strerror(error));
+ unireg_abort(1); /* purecov: tested */
+ }
+
+ /*
+ special case: for wildcard addresses prefer ipv6 over ipv4,
+ because we later switch off IPV6_V6ONLY, so ipv6 wildcard
+ addresses will work for ipv4 too
+ */
+ if (!real_bind_addr_str && ai->ai_family == AF_INET && ai->ai_next
+ && ai->ai_next->ai_family == AF_INET6)
+ {
+ a= ai;
+ ai= ai->ai_next;
+ a->ai_next= ai->ai_next;
+ ai->ai_next= a;
+ }
+
+ for (a= ai; a != NULL; a= a->ai_next)
+ {
+ ip_sock= mysql_socket_socket(key_socket_tcpip, a->ai_family,
+ a->ai_socktype, a->ai_protocol);
+
+ char ip_addr[INET6_ADDRSTRLEN];
+ if (vio_get_normalized_ip_string(a->ai_addr, a->ai_addrlen,
+ ip_addr, sizeof (ip_addr)))
+ {
+ ip_addr[0]= 0;
+ }
+
+ if (mysql_socket_getfd(ip_sock) == INVALID_SOCKET)
+ {
+ sql_print_error("Failed to create a socket for %s '%s': errno: %d.",
+ (a->ai_family == AF_INET) ? "IPv4" : "IPv6",
+ (const char *) ip_addr,
+ (int) socket_errno);
+ }
+ else
+ {
+ sql_print_information("Server socket created on IP: '%s'.",
+ (const char *) ip_addr);
+ break;
+ }
+ }
+
+ if (mysql_socket_getfd(ip_sock) == INVALID_SOCKET)
+ {
+ DBUG_PRINT("error",("Got error: %d from socket()",socket_errno));
+ sql_perror(ER_DEFAULT(ER_IPSOCK_ERROR)); /* purecov: tested */
+ unireg_abort(1); /* purecov: tested */
+ }
+
+ mysql_socket_set_thread_owner(ip_sock);
+
+#ifndef __WIN__
+ /*
+ We should not use SO_REUSEADDR on windows as this would enable a
+ user to open two mysqld servers with the same TCP/IP port.
+ */
+ arg= 1;
+ (void) mysql_socket_setsockopt(ip_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&arg,
+ sizeof(arg));
+#endif /* __WIN__ */
+
+#ifdef IPV6_V6ONLY
+ /*
+ For interoperability with older clients, IPv6 socket should
+ listen on both IPv6 and IPv4 wildcard addresses.
+ Turn off IPV6_V6ONLY option.
+
+ NOTE: this will work starting from Windows Vista only.
+ On Windows XP dual stack is not available, so it will not
+ listen on the corresponding IPv4-address.
+ */
+ if (a->ai_family == AF_INET6)
+ {
+ arg= 0;
+ (void) mysql_socket_setsockopt(ip_sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char*)&arg, sizeof(arg));
+ }
+#endif
+ /*
+ Sometimes the port is not released fast enough when stopping and
+ restarting the server. This happens quite often with the test suite
+ on busy Linux systems. Retry to bind the address at these intervals:
+ Sleep intervals: 1, 2, 4, 6, 9, 13, 17, 22, ...
+ Retry at second: 1, 3, 7, 13, 22, 35, 52, 74, ...
+ Limit the sequence by mysqld_port_timeout (set --port-open-timeout=#).
+ */
+ int ret;
+ uint waited, retry, this_wait;
+ for (waited= 0, retry= 1; ; retry++, waited+= this_wait)
+ {
+ if (((ret= mysql_socket_bind(ip_sock, a->ai_addr, a->ai_addrlen)) >= 0 ) ||
+ (socket_errno != SOCKET_EADDRINUSE) ||
+ (waited >= mysqld_port_timeout))
+ break;
+ sql_print_information("Retrying bind on TCP/IP port %u", port);
+ this_wait= retry * retry / 3 + 1;
+ sleep(this_wait);
+ }
+ freeaddrinfo(ai);
+ if (ret < 0)
+ {
+ char buff[100];
+ sprintf(buff, "Can't start server: Bind on TCP/IP port. Got error: %d",
+ (int) socket_errno);
+ sql_perror(buff);
+ sql_print_error("Do you already have another mysqld server running on "
+ "port: %u ?", port);
+ unireg_abort(1);
+ }
+ if (mysql_socket_listen(ip_sock,(int) back_log) < 0)
+ {
+ sql_perror("Can't start server: listen() on TCP/IP port");
+ sql_print_error("listen() on TCP/IP failed with error %d",
+ socket_errno);
+ unireg_abort(1);
+ }
+ DBUG_RETURN(ip_sock);
+}
+
+static void network_init(void)
+{
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un UNIXaddr;
+ int arg;
+#endif
+ DBUG_ENTER("network_init");
+
+ if (MYSQL_CALLBACK_ELSE(thread_scheduler, init, (), 0))
+ unireg_abort(1); /* purecov: inspected */
+
+ set_ports();
+
+ if (report_port == 0)
+ {
+ report_port= mysqld_port;
+ }
+#ifndef DBUG_OFF
+ if (!opt_disable_networking)
+ DBUG_ASSERT(report_port != 0);
+#endif
+ if (!opt_disable_networking && !opt_bootstrap)
+ {
+ if (mysqld_port)
+ base_ip_sock= activate_tcp_port(mysqld_port);
+ if (mysqld_extra_port)
+ extra_ip_sock= activate_tcp_port(mysqld_extra_port);
+ }
+
+#ifdef _WIN32
+ /* create named pipe */
+ if (Service.IsNT() && mysqld_unix_port[0] && !opt_bootstrap &&
+ opt_enable_named_pipe)
+ {
+
+ strxnmov(pipe_name, sizeof(pipe_name)-1, "\\\\.\\pipe\\",
+ mysqld_unix_port, NullS);
+ bzero((char*) &saPipeSecurity, sizeof(saPipeSecurity));
+ bzero((char*) &sdPipeDescriptor, sizeof(sdPipeDescriptor));
+ if (!InitializeSecurityDescriptor(&sdPipeDescriptor,
+ SECURITY_DESCRIPTOR_REVISION))
+ {
+ sql_perror("Can't start server : Initialize security descriptor");
+ unireg_abort(1);
+ }
+ if (!SetSecurityDescriptorDacl(&sdPipeDescriptor, TRUE, NULL, FALSE))
+ {
+ sql_perror("Can't start server : Set security descriptor");
+ unireg_abort(1);
+ }
+ saPipeSecurity.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saPipeSecurity.lpSecurityDescriptor = &sdPipeDescriptor;
+ saPipeSecurity.bInheritHandle = FALSE;
+ if ((hPipe= CreateNamedPipe(pipe_name,
+ PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE |
+ PIPE_READMODE_BYTE |
+ PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES,
+ (int) global_system_variables.net_buffer_length,
+ (int) global_system_variables.net_buffer_length,
+ NMPWAIT_USE_DEFAULT_WAIT,
+ &saPipeSecurity)) == INVALID_HANDLE_VALUE)
+ {
+ LPVOID lpMsgBuf;
+ int error=GetLastError();
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf, 0, NULL );
+ sql_perror((char *)lpMsgBuf);
+ LocalFree(lpMsgBuf);
+ unireg_abort(1);
+ }
+ }
+#endif
+
+#if defined(HAVE_SYS_UN_H)
+ /*
+ ** Create the UNIX socket
+ */
+ if (mysqld_unix_port[0] && !opt_bootstrap)
+ {
+ DBUG_PRINT("general",("UNIX Socket is %s",mysqld_unix_port));
+
+ if (strlen(mysqld_unix_port) > (sizeof(UNIXaddr.sun_path) - 1))
+ {
+ sql_print_error("The socket file path is too long (> %u): %s",
+ (uint) sizeof(UNIXaddr.sun_path) - 1, mysqld_unix_port);
+ unireg_abort(1);
+ }
+ unix_sock= mysql_socket_socket(key_socket_unix, AF_UNIX, SOCK_STREAM, 0);
+ if (mysql_socket_getfd(unix_sock) < 0)
+ {
+ sql_perror("Can't start server : UNIX Socket "); /* purecov: inspected */
+ unireg_abort(1); /* purecov: inspected */
+ }
+
+ mysql_socket_set_thread_owner(unix_sock);
+
+ bzero((char*) &UNIXaddr, sizeof(UNIXaddr));
+ UNIXaddr.sun_family = AF_UNIX;
+ strmov(UNIXaddr.sun_path, mysqld_unix_port);
+ (void) unlink(mysqld_unix_port);
+ arg= 1;
+ (void) mysql_socket_setsockopt(unix_sock,SOL_SOCKET,SO_REUSEADDR,
+ (char*)&arg, sizeof(arg));
+ umask(0);
+ if (mysql_socket_bind(unix_sock,
+ reinterpret_cast<struct sockaddr *>(&UNIXaddr),
+ sizeof(UNIXaddr)) < 0)
+ {
+ sql_perror("Can't start server : Bind on unix socket"); /* purecov: tested */
+ sql_print_error("Do you already have another mysqld server running on socket: %s ?",mysqld_unix_port);
+ unireg_abort(1); /* purecov: tested */
+ }
+ umask(((~my_umask) & 0666));
+#if defined(S_IFSOCK) && defined(SECURE_SOCKETS)
+ (void) chmod(mysqld_unix_port,S_IFSOCK); /* Fix solaris 2.6 bug */
+#endif
+ if (mysql_socket_listen(unix_sock,(int) back_log) < 0)
+ sql_print_warning("listen() on Unix socket failed with error %d",
+ socket_errno);
+ }
+#endif
+ DBUG_PRINT("info",("server started"));
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Close a connection.
+
+ @param thd Thread handle.
+ @param sql_errno The error code to send before disconnect.
+
+ @note
+ For the connection that is doing shutdown, this is called twice
+*/
+void close_connection(THD *thd, uint sql_errno)
+{
+ DBUG_ENTER("close_connection");
+
+ if (sql_errno)
+ net_send_error(thd, sql_errno, ER_DEFAULT(sql_errno), NULL);
+
+ thd->print_aborted_warning(3, sql_errno ? ER_DEFAULT(sql_errno)
+ : "CLOSE_CONNECTION");
+
+ thd->disconnect();
+
+ MYSQL_CONNECTION_DONE((int) sql_errno, thd->thread_id);
+
+ if (MYSQL_CONNECTION_DONE_ENABLED())
+ {
+ sleep(0); /* Workaround to avoid tailcall optimisation */
+ }
+ mysql_audit_notify_connection_disconnect(thd, sql_errno);
+ DBUG_VOID_RETURN;
+}
+#endif /* EMBEDDED_LIBRARY */
+
+
+/** Called when mysqld is aborted with ^C */
+/* ARGSUSED */
+extern "C" sig_handler end_mysqld_signal(int sig __attribute__((unused)))
+{
+ DBUG_ENTER("end_mysqld_signal");
+ /* Don't call kill_mysql() if signal thread is not running */
+ if (signal_thread_in_use)
+ kill_mysql(); // Take down mysqld nicely
+ DBUG_VOID_RETURN; /* purecov: deadcode */
+}
+
+
+/*
+ Cleanup THD object
+
+ SYNOPSIS
+ thd_cleanup()
+ thd Thread handler
+*/
+
+void thd_cleanup(THD *thd)
+{
+ thd->cleanup();
+}
+
+/*
+ Decrease number of connections
+
+ SYNOPSIS
+ dec_connection_count()
+*/
+
+void dec_connection_count(THD *thd)
+{
+ mysql_mutex_lock(&LOCK_connection_count);
+ (*thd->scheduler->connection_count)--;
+ mysql_mutex_unlock(&LOCK_connection_count);
+}
+
+
+/*
+ Delete THD and decrement thread counters, including thread_running
+*/
+
+void delete_running_thd(THD *thd)
+{
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->unlink();
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ delete thd;
+ dec_thread_running();
+ thread_safe_decrement32(&thread_count, &thread_count_lock);
+ if (!thread_count)
+ {
+ mysql_mutex_lock(&LOCK_thread_count);
+ mysql_cond_broadcast(&COND_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ }
+}
+
+
+/*
+ Unlink thd from global list of available connections and free thd
+
+ SYNOPSIS
+ unlink_thd()
+ thd Thread handler
+
+ NOTES
+ LOCK_thread_count is locked and left locked
+*/
+
+void unlink_thd(THD *thd)
+{
+ DBUG_ENTER("unlink_thd");
+ DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd));
+
+ thd_cleanup(thd);
+ dec_connection_count(thd);
+
+ thd->add_status_to_global();
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->unlink();
+ /*
+ Used by binlog_reset_master. It would be cleaner to use
+ DEBUG_SYNC here, but that's not possible because the THD's debug
+ sync feature has been shut down at this point.
+ */
+ DBUG_EXECUTE_IF("sleep_after_lock_thread_count_before_delete_thd", sleep(5););
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ delete thd;
+ thread_safe_decrement32(&thread_count, &thread_count_lock);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Store thread in cache for reuse by new connections
+
+ SYNOPSIS
+ cache_thread()
+
+ NOTES
+ LOCK_thread_cache is used to protect the cache variables
+
+ RETURN
+ 0 Thread was not put in cache
+ 1 Thread is to be reused by new connection.
+ (ie, caller should return, not abort with pthread_exit())
+*/
+
+
+static bool cache_thread()
+{
+ DBUG_ENTER("cache_thread");
+
+ mysql_mutex_lock(&LOCK_thread_cache);
+ if (cached_thread_count < thread_cache_size &&
+ ! abort_loop && !kill_cached_threads)
+ {
+ /* Don't kill the thread, just put it in cache for reuse */
+ DBUG_PRINT("info", ("Adding thread to cache"));
+ cached_thread_count++;
+
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ /*
+ Delete the instrumentation for the job that just completed,
+ before parking this pthread in the cache (blocked on COND_thread_cache).
+ */
+ PSI_THREAD_CALL(delete_current_thread)();
+#endif
+
+ while (!abort_loop && ! wake_thread && ! kill_cached_threads)
+ mysql_cond_wait(&COND_thread_cache, &LOCK_thread_cache);
+ cached_thread_count--;
+ if (kill_cached_threads)
+ mysql_cond_signal(&COND_flush_thread_cache);
+ if (wake_thread)
+ {
+ THD *thd;
+ wake_thread--;
+ thd= thread_cache.get();
+ mysql_mutex_unlock(&LOCK_thread_cache);
+
+ thd->thread_stack= (char*) &thd; // For store_globals
+ (void) thd->store_globals();
+
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ /*
+ Create new instrumentation for the new THD job,
+ and attach it to this running pthread.
+ */
+ PSI_thread *psi= PSI_THREAD_CALL(new_thread)(key_thread_one_connection,
+ thd, thd->thread_id);
+ PSI_THREAD_CALL(set_thread)(psi);
+#endif
+
+ /*
+ THD::mysys_var::abort is associated with physical thread rather
+ than with THD object. So we need to reset this flag before using
+ this thread for handling of new THD object/connection.
+ */
+ thd->mysys_var->abort= 0;
+ thd->thr_create_utime= microsecond_interval_timer();
+ thd->start_utime= thd->thr_create_utime;
+
+ /* Link thd into list of all active threads (THD's) */
+ mysql_mutex_lock(&LOCK_thread_count);
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_RETURN(1);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_thread_cache);
+ DBUG_RETURN(0);
+}
+
+
+/*
+ End thread for the current connection
+
+ SYNOPSIS
+ one_thread_per_connection_end()
+ thd Thread handler
+ put_in_cache Store thread in cache, if there is room in it
+ Normally this is true in all cases except when we got
+ out of resources initializing the current thread
+
+ NOTES
+ If thread is cached, we will wait until thread is scheduled to be
+ reused and then we will return.
+ If thread is not cached, we end the thread.
+
+ RETURN
+ 0 Signal to handle_one_connection to reuse connection
+*/
+
+bool one_thread_per_connection_end(THD *thd, bool put_in_cache)
+{
+ DBUG_ENTER("one_thread_per_connection_end");
+ unlink_thd(thd);
+ /* Mark that current_thd is not valid anymore */
+ set_current_thd(0);
+ if (put_in_cache && cache_thread())
+ DBUG_RETURN(0); // Thread is reused
+
+ /*
+ It's safe to check for thread_count outside of the mutex
+ as we are only interested to see if it was counted to 0 by the
+ above unlink_thd() call. We should only signal COND_thread_count if
+ thread_count is likely to be 0. (false positives are ok)
+ */
+ if (!thread_count)
+ {
+ mysql_mutex_lock(&LOCK_thread_count);
+ DBUG_PRINT("signal", ("Broadcasting COND_thread_count"));
+ mysql_cond_broadcast(&COND_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ }
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+ ERR_remove_state(0);
+#endif
+ my_thread_end();
+
+ pthread_exit(0);
+ return 0; // Avoid compiler warnings
+}
+
+
+void flush_thread_cache()
+{
+ DBUG_ENTER("flush_thread_cache");
+ mysql_mutex_lock(&LOCK_thread_cache);
+ kill_cached_threads++;
+ while (cached_thread_count)
+ {
+ mysql_cond_broadcast(&COND_thread_cache);
+ mysql_cond_wait(&COND_flush_thread_cache, &LOCK_thread_cache);
+ }
+ kill_cached_threads--;
+ mysql_mutex_unlock(&LOCK_thread_cache);
+ DBUG_VOID_RETURN;
+}
+
+
+/******************************************************************************
+ Setup a signal thread with handles all signals.
+ Because Linux doesn't support schemas use a mutex to check that
+ the signal thread is ready before continuing
+******************************************************************************/
+
+#if defined(__WIN__)
+
+
+/*
+ On Windows, we use native SetConsoleCtrlHandler for handle events like Ctrl-C
+ with graceful shutdown.
+ Also, we do not use signal(), but SetUnhandledExceptionFilter instead - as it
+ provides possibility to pass the exception to just-in-time debugger, collect
+ dumps and potentially also the exception and thread context used to output
+ callstack.
+*/
+
+static BOOL WINAPI console_event_handler( DWORD type )
+{
+ DBUG_ENTER("console_event_handler");
+#ifndef EMBEDDED_LIBRARY
+ if(type == CTRL_C_EVENT)
+ {
+ /*
+ Do not shutdown before startup is finished and shutdown
+ thread is initialized. Otherwise there is a race condition
+ between main thread doing initialization and CTRL-C thread doing
+ cleanup, which can result into crash.
+ */
+#ifndef EMBEDDED_LIBRARY
+ if(hEventShutdown)
+ kill_mysql();
+ else
+#endif
+ sql_print_warning("CTRL-C ignored during startup");
+ DBUG_RETURN(TRUE);
+ }
+#endif
+ DBUG_RETURN(FALSE);
+}
+
+
+
+
+#ifdef DEBUG_UNHANDLED_EXCEPTION_FILTER
+#define DEBUGGER_ATTACH_TIMEOUT 120
+/*
+ Wait for debugger to attach and break into debugger. If debugger is
+ not attached, resume after timeout.
+*/
+static void wait_for_debugger(int timeout_sec)
+{
+ if(!IsDebuggerPresent())
+ {
+ int i;
+ printf("Waiting for debugger to attach, pid=%u\n",GetCurrentProcessId());
+ fflush(stdout);
+ for(i= 0; i < timeout_sec; i++)
+ {
+ Sleep(1000);
+ if(IsDebuggerPresent())
+ {
+ /* Break into debugger */
+ __debugbreak();
+ return;
+ }
+ }
+ printf("pid=%u, debugger not attached after %d seconds, resuming\n",GetCurrentProcessId(),
+ timeout_sec);
+ fflush(stdout);
+ }
+}
+#endif /* DEBUG_UNHANDLED_EXCEPTION_FILTER */
+
+LONG WINAPI my_unhandler_exception_filter(EXCEPTION_POINTERS *ex_pointers)
+{
+ static BOOL first_time= TRUE;
+ if(!first_time)
+ {
+ /*
+ This routine can be called twice, typically
+ when detaching in JIT debugger.
+ Return EXCEPTION_EXECUTE_HANDLER to terminate process.
+ */
+ return EXCEPTION_EXECUTE_HANDLER;
+ }
+ first_time= FALSE;
+#ifdef DEBUG_UNHANDLED_EXCEPTION_FILTER
+ /*
+ Unfortunately there is no clean way to debug unhandled exception filters,
+ as debugger does not stop there(also documented in MSDN)
+ To overcome, one could put a MessageBox, but this will not work in service.
+ Better solution is to print error message and sleep some minutes
+ until debugger is attached
+ */
+ wait_for_debugger(DEBUGGER_ATTACH_TIMEOUT);
+#endif /* DEBUG_UNHANDLED_EXCEPTION_FILTER */
+ __try
+ {
+ my_set_exception_pointers(ex_pointers);
+ handle_fatal_signal(ex_pointers->ExceptionRecord->ExceptionCode);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ DWORD written;
+ const char msg[] = "Got exception in exception handler!\n";
+ WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),msg, sizeof(msg)-1,
+ &written,NULL);
+ }
+ /*
+ Return EXCEPTION_CONTINUE_SEARCH to give JIT debugger
+ (drwtsn32 or vsjitdebugger) possibility to attach,
+ if JIT debugger is configured.
+ Windows Error reporting might generate a dump here.
+ */
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+
+static void init_signals(void)
+{
+ if(opt_console)
+ SetConsoleCtrlHandler(console_event_handler,TRUE);
+
+ /* Avoid MessageBox()es*/
+ _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+
+ /*
+ Do not use SEM_NOGPFAULTERRORBOX in the following SetErrorMode (),
+ because it would prevent JIT debugger and Windows error reporting
+ from working. We need WER or JIT-debugging, since our own unhandled
+ exception filter is not guaranteed to work in all situation
+ (like heap corruption or stack overflow)
+ */
+ SetErrorMode(SetErrorMode(0) | SEM_FAILCRITICALERRORS
+ | SEM_NOOPENFILEERRORBOX);
+ SetUnhandledExceptionFilter(my_unhandler_exception_filter);
+}
+
+
+static void start_signal_handler(void)
+{
+#ifndef EMBEDDED_LIBRARY
+ // Save vm id of this process
+ if (!opt_bootstrap)
+ create_pid_file();
+#endif /* EMBEDDED_LIBRARY */
+}
+
+
+static void check_data_home(const char *path)
+{}
+
+#endif /* __WIN__ */
+
+
+#if BACKTRACE_DEMANGLE
+#include <cxxabi.h>
+extern "C" char *my_demangle(const char *mangled_name, int *status)
+{
+ return abi::__cxa_demangle(mangled_name, NULL, NULL, status);
+}
+#endif
+
+
+/*
+ pthread_attr_setstacksize() without so much platform-dependency
+
+ Return: The actual stack size if possible.
+*/
+
+#ifndef EMBEDDED_LIBRARY
+static size_t my_setstacksize(pthread_attr_t *attr, size_t stacksize)
+{
+ size_t guard_size __attribute__((unused))= 0;
+
+#if defined(__ia64__) || defined(__ia64)
+ /*
+ On IA64, half of the requested stack size is used for "normal stack"
+ and half for "register stack". The space measured by check_stack_overrun
+ is the "normal stack", so double the request to make sure we have the
+ caller-expected amount of normal stack.
+
+ NOTE: there is no guarantee that the register stack can't grow faster
+ than normal stack, so it's very unclear that we won't dump core due to
+ stack overrun despite check_stack_overrun's efforts. Experimentation
+ shows that in the execution_constants test, the register stack grows
+ less than half as fast as normal stack, but perhaps other scenarios are
+ less forgiving. If it turns out that more space is needed for the
+ register stack, that could be forced (rather inefficiently) by using a
+ multiplier higher than 2 here.
+ */
+ stacksize *= 2;
+#endif
+
+ /*
+ On many machines, the "guard space" is subtracted from the requested
+ stack size, and that space is quite large on some platforms. So add
+ it to our request, if we can find out what it is.
+ */
+#ifdef HAVE_PTHREAD_ATTR_GETGUARDSIZE
+ if (pthread_attr_getguardsize(attr, &guard_size))
+ guard_size = 0; /* if can't find it out, treat as 0 */
+#endif
+
+ pthread_attr_setstacksize(attr, stacksize + guard_size);
+
+ /* Retrieve actual stack size if possible */
+#ifdef HAVE_PTHREAD_ATTR_GETSTACKSIZE
+ {
+ size_t real_stack_size= 0;
+ /* We must ignore real_stack_size = 0 as Solaris 2.9 can return 0 here */
+ if (pthread_attr_getstacksize(attr, &real_stack_size) == 0 &&
+ real_stack_size > guard_size)
+ {
+ real_stack_size -= guard_size;
+ if (real_stack_size < stacksize)
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_warning("Asked for %zu thread stack, but got %zu",
+ stacksize, real_stack_size);
+ stacksize= real_stack_size;
+ }
+ }
+ }
+#endif /* !EMBEDDED_LIBRARY */
+
+#if defined(__ia64__) || defined(__ia64)
+ stacksize /= 2;
+#endif
+ return stacksize;
+}
+#endif
+
+
+#if !defined(__WIN__)
+#ifndef SA_RESETHAND
+#define SA_RESETHAND 0
+#endif /* SA_RESETHAND */
+#ifndef SA_NODEFER
+#define SA_NODEFER 0
+#endif /* SA_NODEFER */
+
+#ifndef EMBEDDED_LIBRARY
+
+static void init_signals(void)
+{
+ sigset_t set;
+ struct sigaction sa;
+ DBUG_ENTER("init_signals");
+
+ my_sigset(THR_SERVER_ALARM,print_signal_warning); // Should never be called!
+
+ if (opt_stack_trace || (test_flags & TEST_CORE_ON_SIGNAL))
+ {
+ sa.sa_flags = SA_RESETHAND | SA_NODEFER;
+ sigemptyset(&sa.sa_mask);
+ sigprocmask(SIG_SETMASK,&sa.sa_mask,NULL);
+
+ my_init_stacktrace();
+#if defined(__amiga__)
+ sa.sa_handler=(void(*)())handle_fatal_signal;
+#else
+ sa.sa_handler=handle_fatal_signal;
+#endif
+ sigaction(SIGSEGV, &sa, NULL);
+ sigaction(SIGABRT, &sa, NULL);
+#ifdef SIGBUS
+ sigaction(SIGBUS, &sa, NULL);
+#endif
+ sigaction(SIGILL, &sa, NULL);
+ sigaction(SIGFPE, &sa, NULL);
+ }
+
+#ifdef HAVE_GETRLIMIT
+ if (test_flags & TEST_CORE_ON_SIGNAL)
+ {
+ /* Change limits so that we will get a core file */
+ STRUCT_RLIMIT rl;
+ rl.rlim_cur = rl.rlim_max = (rlim_t) RLIM_INFINITY;
+ if (setrlimit(RLIMIT_CORE, &rl) && global_system_variables.log_warnings)
+ sql_print_warning("setrlimit could not change the size of core files to 'infinity'; We may not be able to generate a core file on signals");
+ }
+#endif
+ (void) sigemptyset(&set);
+ my_sigset(SIGPIPE,SIG_IGN);
+ sigaddset(&set,SIGPIPE);
+#ifndef IGNORE_SIGHUP_SIGQUIT
+ sigaddset(&set,SIGQUIT);
+ sigaddset(&set,SIGHUP);
+#endif
+ sigaddset(&set,SIGTERM);
+
+ /* Fix signals if blocked by parents (can happen on Mac OS X) */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = print_signal_warning;
+ sigaction(SIGTERM, &sa, (struct sigaction*) 0);
+ sa.sa_flags = 0;
+ sa.sa_handler = print_signal_warning;
+ sigaction(SIGHUP, &sa, (struct sigaction*) 0);
+ if (thd_lib_detected != THD_LIB_LT)
+ sigaddset(&set,THR_SERVER_ALARM);
+ if (test_flags & TEST_SIGINT)
+ {
+ /* Allow SIGINT to break mysqld. This is for debugging with --gdb */
+ my_sigset(SIGINT, end_mysqld_signal);
+ sigdelset(&set, SIGINT);
+ }
+ else
+ {
+ sigaddset(&set,SIGINT);
+#ifdef SIGTSTP
+ sigaddset(&set,SIGTSTP);
+#endif
+ }
+
+ sigprocmask(SIG_SETMASK,&set,NULL);
+ pthread_sigmask(SIG_SETMASK,&set,NULL);
+ DBUG_VOID_RETURN;
+}
+
+
+static void start_signal_handler(void)
+{
+ int error;
+ pthread_attr_t thr_attr;
+ DBUG_ENTER("start_signal_handler");
+
+ (void) pthread_attr_init(&thr_attr);
+ pthread_attr_setscope(&thr_attr,PTHREAD_SCOPE_SYSTEM);
+ (void) pthread_attr_setdetachstate(&thr_attr,PTHREAD_CREATE_DETACHED);
+ (void) my_setstacksize(&thr_attr,my_thread_stack_size);
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ if ((error= mysql_thread_create(key_thread_signal_hand,
+ &signal_thread, &thr_attr, signal_hand, 0)))
+ {
+ sql_print_error("Can't create interrupt-thread (error %d, errno: %d)",
+ error,errno);
+ exit(1);
+ }
+ mysql_cond_wait(&COND_thread_count, &LOCK_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ (void) pthread_attr_destroy(&thr_attr);
+ DBUG_VOID_RETURN;
+}
+
+
+/** This threads handles all signals and alarms. */
+/* ARGSUSED */
+pthread_handler_t signal_hand(void *arg __attribute__((unused)))
+{
+ sigset_t set;
+ int sig;
+ my_thread_init(); // Init new thread
+ DBUG_ENTER("signal_hand");
+ signal_thread_in_use= 1;
+
+ /*
+ Setup alarm handler
+ This should actually be '+ max_number_of_slaves' instead of +10,
+ but the +10 should be quite safe.
+ */
+ init_thr_alarm(thread_scheduler->max_threads + extra_max_connections +
+ global_system_variables.max_insert_delayed_threads + 10);
+ if (test_flags & TEST_SIGINT)
+ {
+ /* Allow SIGINT to break mysqld. This is for debugging with --gdb */
+ (void) sigemptyset(&set);
+ (void) sigaddset(&set,SIGINT);
+ (void) pthread_sigmask(SIG_UNBLOCK,&set,NULL);
+ }
+ (void) sigemptyset(&set); // Setup up SIGINT for debug
+#ifdef USE_ONE_SIGNAL_HAND
+ (void) sigaddset(&set,THR_SERVER_ALARM); // For alarms
+#endif
+#ifndef IGNORE_SIGHUP_SIGQUIT
+ (void) sigaddset(&set,SIGQUIT);
+ (void) sigaddset(&set,SIGHUP);
+#endif
+ (void) sigaddset(&set,SIGTERM);
+ (void) sigaddset(&set,SIGTSTP);
+
+ /* Save pid to this process (or thread on Linux) */
+ if (!opt_bootstrap)
+ create_pid_file();
+
+ /*
+ signal to start_signal_handler that we are ready
+ This works by waiting for start_signal_handler to free mutex,
+ after which we signal it that we are ready.
+ At this pointer there is no other threads running, so there
+ should not be any other mysql_cond_signal() calls.
+ */
+ mysql_mutex_lock(&LOCK_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ mysql_cond_broadcast(&COND_thread_count);
+
+ (void) pthread_sigmask(SIG_BLOCK,&set,NULL);
+ for (;;)
+ {
+ int error; // Used when debugging
+ if (shutdown_in_progress && !abort_loop)
+ {
+ sig= SIGTERM;
+ error=0;
+ }
+ else
+ while ((error=my_sigwait(&set,&sig)) == EINTR) ;
+ if (cleanup_done)
+ {
+ DBUG_PRINT("quit",("signal_handler: calling my_thread_end()"));
+ my_thread_end();
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+ signal_thread_in_use= 0;
+ pthread_exit(0); // Safety
+ return 0; // Avoid compiler warnings
+ }
+ switch (sig) {
+ case SIGTERM:
+ case SIGQUIT:
+ case SIGKILL:
+#ifdef EXTRA_DEBUG
+ sql_print_information("Got signal %d to shutdown mysqld",sig);
+#endif
+ /* switch to the old log message processing */
+ logger.set_handlers(LOG_FILE, opt_slow_log ? LOG_FILE:LOG_NONE,
+ opt_log ? LOG_FILE:LOG_NONE);
+ DBUG_PRINT("info",("Got signal: %d abort_loop: %d",sig,abort_loop));
+ if (!abort_loop)
+ {
+ abort_loop=1; // mark abort for threads
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ /* Delete the instrumentation for the signal thread */
+ PSI_THREAD_CALL(delete_current_thread)();
+#endif
+#ifdef USE_ONE_SIGNAL_HAND
+ pthread_t tmp;
+ if ((error= mysql_thread_create(0, /* Not instrumented */
+ &tmp, &connection_attrib,
+ kill_server_thread,
+ (void*) &sig)))
+ sql_print_error("Can't create thread to kill server (errno= %d)",
+ error);
+#else
+ kill_server((void*) sig); // MIT THREAD has a alarm thread
+#endif
+ }
+ break;
+ case SIGHUP:
+ if (!abort_loop)
+ {
+ int not_used;
+ mysql_print_status(); // Print some debug info
+ reload_acl_and_cache((THD*) 0,
+ (REFRESH_LOG | REFRESH_TABLES | REFRESH_FAST |
+ REFRESH_GRANT |
+ REFRESH_THREADS | REFRESH_HOSTS),
+ (TABLE_LIST*) 0, &not_used); // Flush logs
+ }
+ /* reenable logs after the options were reloaded */
+ if (log_output_options & LOG_NONE)
+ {
+ logger.set_handlers(LOG_FILE,
+ opt_slow_log ? LOG_TABLE : LOG_NONE,
+ opt_log ? LOG_TABLE : LOG_NONE);
+ }
+ else
+ {
+ logger.set_handlers(LOG_FILE,
+ opt_slow_log ? log_output_options : LOG_NONE,
+ opt_log ? log_output_options : LOG_NONE);
+ }
+ break;
+#ifdef USE_ONE_SIGNAL_HAND
+ case THR_SERVER_ALARM:
+ process_alarm(sig); // Trigger alarms.
+ break;
+#endif
+ default:
+#ifdef EXTRA_DEBUG
+ sql_print_warning("Got signal: %d error: %d",sig,error); /* purecov: tested */
+#endif
+ break; /* purecov: tested */
+ }
+ }
+ return(0); /* purecov: deadcode */
+}
+
+static void check_data_home(const char *path)
+{}
+
+#endif /*!EMBEDDED_LIBRARY*/
+#endif /* __WIN__*/
+
+
+/**
+ All global error messages are sent here where the first one is stored
+ for the client.
+*/
+/* ARGSUSED */
+extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);
+
+void my_message_sql(uint error, const char *str, myf MyFlags)
+{
+ THD *thd= current_thd;
+ Sql_condition::enum_warning_level level;
+ sql_print_message_func func;
+ DBUG_ENTER("my_message_sql");
+ DBUG_PRINT("error", ("error: %u message: '%s' Flag: %lu", error, str,
+ MyFlags));
+
+ DBUG_ASSERT(str != NULL);
+ DBUG_ASSERT(error != 0);
+
+ if (MyFlags & ME_JUST_INFO)
+ {
+ level= Sql_condition::WARN_LEVEL_NOTE;
+ func= sql_print_information;
+ }
+ else if (MyFlags & ME_JUST_WARNING)
+ {
+ level= Sql_condition::WARN_LEVEL_WARN;
+ func= sql_print_warning;
+ }
+ else
+ {
+ level= Sql_condition::WARN_LEVEL_ERROR;
+ func= sql_print_error;
+ }
+
+ if (thd)
+ {
+ if (MyFlags & ME_FATALERROR)
+ thd->is_fatal_error= 1;
+ (void) thd->raise_condition(error, NULL, level, str);
+ }
+ else
+ mysql_audit_general(0, MYSQL_AUDIT_GENERAL_ERROR, error, str);
+
+ /* When simulating OOM, skip writing to error log to avoid mtr errors */
+ DBUG_EXECUTE_IF("simulate_out_of_memory", DBUG_VOID_RETURN;);
+
+ if (!thd || thd->log_all_errors || (MyFlags & ME_NOREFRESH))
+ (*func)("%s: %s", my_progname_short, str); /* purecov: inspected */
+ DBUG_VOID_RETURN;
+}
+
+
+extern "C" void *my_str_malloc_mysqld(size_t size);
+extern "C" void my_str_free_mysqld(void *ptr);
+extern "C" void *my_str_realloc_mysqld(void *ptr, size_t size);
+
+void *my_str_malloc_mysqld(size_t size)
+{
+ return my_malloc(size, MYF(MY_FAE));
+}
+
+
+void my_str_free_mysqld(void *ptr)
+{
+ my_free(ptr);
+}
+
+void *my_str_realloc_mysqld(void *ptr, size_t size)
+{
+ return my_realloc(ptr, size, MYF(MY_FAE));
+}
+
+
+#ifdef __WIN__
+
+pthread_handler_t handle_shutdown(void *arg)
+{
+ MSG msg;
+ my_thread_init();
+
+ /* this call should create the message queue for this thread */
+ PeekMessage(&msg, NULL, 1, 65534,PM_NOREMOVE);
+#if !defined(EMBEDDED_LIBRARY)
+ if (WaitForSingleObject(hEventShutdown,INFINITE)==WAIT_OBJECT_0)
+#endif /* EMBEDDED_LIBRARY */
+ kill_server(MYSQL_KILL_SIGNAL);
+ return 0;
+}
+#endif
+
+#include <mysqld_default_groups.h>
+
+#if defined(__WIN__) && !defined(EMBEDDED_LIBRARY)
+static const int load_default_groups_sz=
+sizeof(load_default_groups)/sizeof(load_default_groups[0]);
+#endif
+
+
+#ifndef EMBEDDED_LIBRARY
+/**
+ This function is used to check for stack overrun for pathological
+ cases of regular expressions and 'like' expressions.
+*/
+extern "C" int
+check_enough_stack_size_slow()
+{
+ uchar stack_top;
+ THD *my_thd= current_thd;
+ if (my_thd != NULL)
+ return check_stack_overrun(my_thd, STACK_MIN_SIZE * 2, &stack_top);
+ return 0;
+}
+
+
+/*
+ The call to current_thd in check_enough_stack_size_slow is quite expensive,
+ so we try to avoid it for the normal cases.
+ The size of each stack frame for the wildcmp() routines is ~128 bytes,
+ so checking *every* recursive call is not necessary.
+ */
+extern "C" int
+check_enough_stack_size(int recurse_level)
+{
+ if (recurse_level % 16 != 0)
+ return 0;
+ return check_enough_stack_size_slow();
+}
+#endif
+
+
+
+/*
+ Initialize my_str_malloc() and my_str_free()
+*/
+static void init_libstrings()
+{
+ my_str_malloc= &my_str_malloc_mysqld;
+ my_str_free= &my_str_free_mysqld;
+ my_str_realloc= &my_str_realloc_mysqld;
+#ifndef EMBEDDED_LIBRARY
+ my_string_stack_guard= check_enough_stack_size;
+#endif
+}
+
+
+static void init_pcre()
+{
+ pcre_malloc= pcre_stack_malloc= my_str_malloc_mysqld;
+ pcre_free= pcre_stack_free= my_str_free_mysqld;
+#ifndef EMBEDDED_LIBRARY
+ pcre_stack_guard= check_enough_stack_size_slow;
+#endif
+}
+
+
+/**
+ Initialize one of the global date/time format variables.
+
+ @param format_type What kind of format should be supported
+ @param var_ptr Pointer to variable that should be updated
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+static bool init_global_datetime_format(timestamp_type format_type,
+ DATE_TIME_FORMAT *format)
+{
+ /*
+ Get command line option
+ format->format.str is already set by my_getopt
+ */
+ format->format.length= strlen(format->format.str);
+
+ if (parse_date_time_format(format_type, format))
+ {
+ fprintf(stderr, "Wrong date/time format specifier: %s\n",
+ format->format.str);
+ return true;
+ }
+ return false;
+}
+
+SHOW_VAR com_status_vars[]= {
+ {"admin_commands", (char*) offsetof(STATUS_VAR, com_other), SHOW_LONG_STATUS},
+ {"alter_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_DB]), SHOW_LONG_STATUS},
+ {"alter_db_upgrade", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_DB_UPGRADE]), SHOW_LONG_STATUS},
+ {"alter_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_EVENT]), SHOW_LONG_STATUS},
+ {"alter_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_FUNCTION]), SHOW_LONG_STATUS},
+ {"alter_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_PROCEDURE]), SHOW_LONG_STATUS},
+ {"alter_server", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_SERVER]), SHOW_LONG_STATUS},
+ {"alter_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_TABLE]), SHOW_LONG_STATUS},
+ {"alter_tablespace", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_TABLESPACE]), SHOW_LONG_STATUS},
+ {"analyze", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ANALYZE]), SHOW_LONG_STATUS},
+ {"assign_to_keycache", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ASSIGN_TO_KEYCACHE]), SHOW_LONG_STATUS},
+ {"begin", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_BEGIN]), SHOW_LONG_STATUS},
+ {"binlog", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_BINLOG_BASE64_EVENT]), SHOW_LONG_STATUS},
+ {"call_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CALL]), SHOW_LONG_STATUS},
+ {"change_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHANGE_DB]), SHOW_LONG_STATUS},
+ {"change_master", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHANGE_MASTER]), SHOW_LONG_STATUS},
+ {"check", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHECK]), SHOW_LONG_STATUS},
+ {"checksum", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHECKSUM]), SHOW_LONG_STATUS},
+ {"commit", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_COMMIT]), SHOW_LONG_STATUS},
+ {"create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_DB]), SHOW_LONG_STATUS},
+ {"create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_EVENT]), SHOW_LONG_STATUS},
+ {"create_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_SPFUNCTION]), SHOW_LONG_STATUS},
+ {"create_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_INDEX]), SHOW_LONG_STATUS},
+ {"create_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_PROCEDURE]), SHOW_LONG_STATUS},
+ {"create_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_ROLE]), SHOW_LONG_STATUS},
+ {"create_server", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_SERVER]), SHOW_LONG_STATUS},
+ {"create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_TABLE]), SHOW_LONG_STATUS},
+ {"create_trigger", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_TRIGGER]), SHOW_LONG_STATUS},
+ {"create_udf", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_FUNCTION]), SHOW_LONG_STATUS},
+ {"create_user", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_USER]), SHOW_LONG_STATUS},
+ {"create_view", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_VIEW]), SHOW_LONG_STATUS},
+ {"dealloc_sql", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DEALLOCATE_PREPARE]), SHOW_LONG_STATUS},
+ {"delete", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DELETE]), SHOW_LONG_STATUS},
+ {"delete_multi", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DELETE_MULTI]), SHOW_LONG_STATUS},
+ {"do", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DO]), SHOW_LONG_STATUS},
+ {"drop_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_DB]), SHOW_LONG_STATUS},
+ {"drop_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_EVENT]), SHOW_LONG_STATUS},
+ {"drop_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_FUNCTION]), SHOW_LONG_STATUS},
+ {"drop_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_INDEX]), SHOW_LONG_STATUS},
+ {"drop_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_PROCEDURE]), SHOW_LONG_STATUS},
+ {"drop_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_ROLE]), SHOW_LONG_STATUS},
+ {"drop_server", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_SERVER]), SHOW_LONG_STATUS},
+ {"drop_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_TABLE]), SHOW_LONG_STATUS},
+ {"drop_trigger", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_TRIGGER]), SHOW_LONG_STATUS},
+ {"drop_user", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_USER]), SHOW_LONG_STATUS},
+ {"drop_view", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_VIEW]), SHOW_LONG_STATUS},
+ {"empty_query", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_EMPTY_QUERY]), SHOW_LONG_STATUS},
+ {"execute_sql", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_EXECUTE]), SHOW_LONG_STATUS},
+ {"flush", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_FLUSH]), SHOW_LONG_STATUS},
+ {"get_diagnostics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_GET_DIAGNOSTICS]), SHOW_LONG_STATUS},
+ {"grant", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_GRANT]), SHOW_LONG_STATUS},
+ {"grant_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_GRANT_ROLE]), SHOW_LONG_STATUS},
+ {"ha_close", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HA_CLOSE]), SHOW_LONG_STATUS},
+ {"ha_open", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HA_OPEN]), SHOW_LONG_STATUS},
+ {"ha_read", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HA_READ]), SHOW_LONG_STATUS},
+ {"help", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HELP]), SHOW_LONG_STATUS},
+ {"insert", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_INSERT]), SHOW_LONG_STATUS},
+ {"insert_select", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_INSERT_SELECT]), SHOW_LONG_STATUS},
+ {"install_plugin", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_INSTALL_PLUGIN]), SHOW_LONG_STATUS},
+ {"kill", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_KILL]), SHOW_LONG_STATUS},
+ {"load", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_LOAD]), SHOW_LONG_STATUS},
+ {"lock_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_LOCK_TABLES]), SHOW_LONG_STATUS},
+ {"optimize", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_OPTIMIZE]), SHOW_LONG_STATUS},
+ {"preload_keys", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PRELOAD_KEYS]), SHOW_LONG_STATUS},
+ {"prepare_sql", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PREPARE]), SHOW_LONG_STATUS},
+ {"purge", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE]), SHOW_LONG_STATUS},
+ {"purge_before_date", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BEFORE]), SHOW_LONG_STATUS},
+ {"release_savepoint", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RELEASE_SAVEPOINT]), SHOW_LONG_STATUS},
+ {"rename_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RENAME_TABLE]), SHOW_LONG_STATUS},
+ {"rename_user", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RENAME_USER]), SHOW_LONG_STATUS},
+ {"repair", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REPAIR]), SHOW_LONG_STATUS},
+ {"replace", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REPLACE]), SHOW_LONG_STATUS},
+ {"replace_select", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REPLACE_SELECT]), SHOW_LONG_STATUS},
+ {"reset", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RESET]), SHOW_LONG_STATUS},
+ {"resignal", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RESIGNAL]), SHOW_LONG_STATUS},
+ {"revoke", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REVOKE]), SHOW_LONG_STATUS},
+ {"revoke_all", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REVOKE_ALL]), SHOW_LONG_STATUS},
+ {"revoke_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REVOKE_ROLE]), SHOW_LONG_STATUS},
+ {"rollback", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ROLLBACK]), SHOW_LONG_STATUS},
+ {"rollback_to_savepoint",(char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ROLLBACK_TO_SAVEPOINT]), SHOW_LONG_STATUS},
+ {"savepoint", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SAVEPOINT]), SHOW_LONG_STATUS},
+ {"select", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SELECT]), SHOW_LONG_STATUS},
+ {"set_option", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SET_OPTION]), SHOW_LONG_STATUS},
+ {"show_authors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_AUTHORS]), SHOW_LONG_STATUS},
+ {"show_binlog_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_BINLOG_EVENTS]), SHOW_LONG_STATUS},
+ {"show_binlogs", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_BINLOGS]), SHOW_LONG_STATUS},
+ {"show_charsets", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CHARSETS]), SHOW_LONG_STATUS},
+ {"show_client_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CLIENT_STATS]), SHOW_LONG_STATUS},
+ {"show_collations", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_COLLATIONS]), SHOW_LONG_STATUS},
+ {"show_contributors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CONTRIBUTORS]), SHOW_LONG_STATUS},
+ {"show_create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_DB]), SHOW_LONG_STATUS},
+ {"show_create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_EVENT]), SHOW_LONG_STATUS},
+ {"show_create_func", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_FUNC]), SHOW_LONG_STATUS},
+ {"show_create_proc", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_PROC]), SHOW_LONG_STATUS},
+ {"show_create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE]), SHOW_LONG_STATUS},
+ {"show_create_trigger", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_TRIGGER]), SHOW_LONG_STATUS},
+ {"show_databases", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_DATABASES]), SHOW_LONG_STATUS},
+ {"show_engine_logs", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_LOGS]), SHOW_LONG_STATUS},
+ {"show_engine_mutex", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_MUTEX]), SHOW_LONG_STATUS},
+ {"show_engine_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_STATUS]), SHOW_LONG_STATUS},
+ {"show_errors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ERRORS]), SHOW_LONG_STATUS},
+ {"show_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EVENTS]), SHOW_LONG_STATUS},
+ {"show_explain", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EXPLAIN]), SHOW_LONG_STATUS},
+ {"show_fields", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FIELDS]), SHOW_LONG_STATUS},
+#ifndef DBUG_OFF
+ {"show_function_code", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FUNC_CODE]), SHOW_LONG_STATUS},
+#endif
+ {"show_function_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STATUS_FUNC]), SHOW_LONG_STATUS},
+ {"show_grants", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_GRANTS]), SHOW_LONG_STATUS},
+ {"show_index_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_INDEX_STATS]), SHOW_LONG_STATUS},
+ {"show_keys", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_KEYS]), SHOW_LONG_STATUS},
+ {"show_master_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_MASTER_STAT]), SHOW_LONG_STATUS},
+ {"show_open_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_OPEN_TABLES]), SHOW_LONG_STATUS},
+ {"show_plugins", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PLUGINS]), SHOW_LONG_STATUS},
+ {"show_privileges", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PRIVILEGES]), SHOW_LONG_STATUS},
+#ifndef DBUG_OFF
+ {"show_procedure_code", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROC_CODE]), SHOW_LONG_STATUS},
+#endif
+ {"show_procedure_status",(char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STATUS_PROC]), SHOW_LONG_STATUS},
+ {"show_processlist", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROCESSLIST]), SHOW_LONG_STATUS},
+ {"show_profile", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROFILE]), SHOW_LONG_STATUS},
+ {"show_profiles", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROFILES]), SHOW_LONG_STATUS},
+ {"show_relaylog_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_RELAYLOG_EVENTS]), SHOW_LONG_STATUS},
+ {"show_slave_hosts", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_SLAVE_HOSTS]), SHOW_LONG_STATUS},
+ {"show_slave_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_SLAVE_STAT]), SHOW_LONG_STATUS},
+ {"show_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STATUS]), SHOW_LONG_STATUS},
+ {"show_storage_engines", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STORAGE_ENGINES]), SHOW_LONG_STATUS},
+ {"show_table_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TABLE_STATS]), SHOW_LONG_STATUS},
+ {"show_table_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TABLE_STATUS]), SHOW_LONG_STATUS},
+ {"show_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TABLES]), SHOW_LONG_STATUS},
+ {"show_triggers", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TRIGGERS]), SHOW_LONG_STATUS},
+ {"show_user_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_USER_STATS]), SHOW_LONG_STATUS},
+ {"show_variables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_VARIABLES]), SHOW_LONG_STATUS},
+ {"show_warnings", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_WARNS]), SHOW_LONG_STATUS},
+ {"shutdown", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHUTDOWN]), SHOW_LONG_STATUS},
+ {"signal", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SIGNAL]), SHOW_LONG_STATUS},
+ {"start_all_slaves", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_ALL_START]), SHOW_LONG_STATUS},
+ {"start_slave", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_START]), SHOW_LONG_STATUS},
+ {"stmt_close", (char*) offsetof(STATUS_VAR, com_stmt_close), SHOW_LONG_STATUS},
+ {"stmt_execute", (char*) offsetof(STATUS_VAR, com_stmt_execute), SHOW_LONG_STATUS},
+ {"stmt_fetch", (char*) offsetof(STATUS_VAR, com_stmt_fetch), SHOW_LONG_STATUS},
+ {"stmt_prepare", (char*) offsetof(STATUS_VAR, com_stmt_prepare), SHOW_LONG_STATUS},
+ {"stmt_reprepare", (char*) offsetof(STATUS_VAR, com_stmt_reprepare), SHOW_LONG_STATUS},
+ {"stmt_reset", (char*) offsetof(STATUS_VAR, com_stmt_reset), SHOW_LONG_STATUS},
+ {"stmt_send_long_data", (char*) offsetof(STATUS_VAR, com_stmt_send_long_data), SHOW_LONG_STATUS},
+ {"stop_all_slaves", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_ALL_STOP]), SHOW_LONG_STATUS},
+ {"stop_slave", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_STOP]), SHOW_LONG_STATUS},
+ {"truncate", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_TRUNCATE]), SHOW_LONG_STATUS},
+ {"uninstall_plugin", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UNINSTALL_PLUGIN]), SHOW_LONG_STATUS},
+ {"unlock_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UNLOCK_TABLES]), SHOW_LONG_STATUS},
+ {"update", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UPDATE]), SHOW_LONG_STATUS},
+ {"update_multi", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UPDATE_MULTI]), SHOW_LONG_STATUS},
+ {"xa_commit", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_COMMIT]),SHOW_LONG_STATUS},
+ {"xa_end", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_END]),SHOW_LONG_STATUS},
+ {"xa_prepare", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_PREPARE]),SHOW_LONG_STATUS},
+ {"xa_recover", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_RECOVER]),SHOW_LONG_STATUS},
+ {"xa_rollback", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_ROLLBACK]),SHOW_LONG_STATUS},
+ {"xa_start", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_START]),SHOW_LONG_STATUS},
+ {NullS, NullS, SHOW_LONG}
+};
+
+
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+PSI_statement_info sql_statement_info[(uint) SQLCOM_END + 1];
+PSI_statement_info com_statement_info[(uint) COM_END + 1];
+
+/**
+ Initialize the command names array.
+ Since we do not want to maintain a separate array,
+ this is populated from data mined in com_status_vars,
+ which already has one name for each command.
+*/
+void init_sql_statement_info()
+{
+ char *first_com= (char*) offsetof(STATUS_VAR, com_stat[0]);
+ char *last_com= (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_END]);
+ int record_size= (char*) offsetof(STATUS_VAR, com_stat[1])
+ - (char*) offsetof(STATUS_VAR, com_stat[0]);
+ char *ptr;
+ uint i;
+ uint com_index;
+
+ static const char* dummy= "";
+ for (i= 0; i < ((uint) SQLCOM_END + 1); i++)
+ {
+ sql_statement_info[i].m_name= dummy;
+ sql_statement_info[i].m_flags= 0;
+ }
+
+ SHOW_VAR *var= &com_status_vars[0];
+ while (var->name != NULL)
+ {
+ ptr= var->value;
+ if ((first_com <= ptr) && (ptr <= last_com))
+ {
+ com_index= ((int)(ptr - first_com))/record_size;
+ DBUG_ASSERT(com_index < (uint) SQLCOM_END);
+ sql_statement_info[com_index].m_name= var->name;
+ }
+ var++;
+ }
+
+ DBUG_ASSERT(strcmp(sql_statement_info[(uint) SQLCOM_SELECT].m_name, "select") == 0);
+ DBUG_ASSERT(strcmp(sql_statement_info[(uint) SQLCOM_SIGNAL].m_name, "signal") == 0);
+
+ sql_statement_info[(uint) SQLCOM_END].m_name= "error";
+}
+
+void init_com_statement_info()
+{
+ uint index;
+
+ for (index= 0; index < (uint) COM_END + 1; index++)
+ {
+ com_statement_info[index].m_name= command_name[index].str;
+ com_statement_info[index].m_flags= 0;
+ }
+
+ /* "statement/abstract/query" can mutate into "statement/sql/..." */
+ com_statement_info[(uint) COM_QUERY].m_flags= PSI_FLAG_MUTABLE;
+}
+#endif
+
+
+#ifdef SAFEMALLOC
+/*
+ Return the id for the current THD, to allow safemalloc to associate
+ the memory with the right id.
+*/
+
+extern "C" my_thread_id mariadb_dbug_id()
+{
+ THD *thd;
+ if ((thd= current_thd))
+ {
+ return thd->thread_id;
+ }
+ return my_thread_dbug_id();
+}
+#endif /* SAFEMALLOC */
+
+/* Thread Mem Usage By P.Linux */
+extern "C" {
+static void my_malloc_size_cb_func(long long size, my_bool is_thread_specific)
+{
+ /* If thread specific memory */
+ if (is_thread_specific)
+ {
+ THD *thd= current_thd;
+ if (mysqld_server_initialized || thd)
+ {
+ /*
+ THD may not be set if we are called from my_net_init() before THD
+ thread has started.
+ However, this should never happen, so better to assert and
+ fix this.
+ */
+ DBUG_ASSERT(thd);
+ if (thd)
+ {
+ DBUG_PRINT("info", ("memory_used: %lld size: %lld",
+ (longlong) thd->status_var.memory_used, size));
+ thd->status_var.memory_used+= size;
+ DBUG_ASSERT((longlong) thd->status_var.memory_used >= 0);
+ }
+ }
+ }
+ // workaround for gcc 4.2.4-1ubuntu4 -fPIE (from DEB_BUILD_HARDENING=1)
+ int64 volatile * volatile ptr=&global_status_var.memory_used;
+ my_atomic_add64(ptr, size);
+}
+}
+
+
+static int init_common_variables()
+{
+ umask(((~my_umask) & 0666));
+ connection_errors_select= 0;
+ connection_errors_accept= 0;
+ connection_errors_tcpwrap= 0;
+ connection_errors_internal= 0;
+ connection_errors_max_connection= 0;
+ connection_errors_peer_addr= 0;
+ my_decimal_set_zero(&decimal_zero); // set decimal_zero constant;
+
+ if (pthread_key_create(&THR_THD,NULL) ||
+ pthread_key_create(&THR_MALLOC,NULL))
+ {
+ sql_print_error("Can't create thread-keys");
+ return 1;
+ }
+
+ set_current_thd(0);
+ set_malloc_size_cb(my_malloc_size_cb_func);
+
+ init_libstrings();
+ tzset(); // Set tzname
+
+ sf_leaking_memory= 0; // no memory leaks from now on
+#ifdef SAFEMALLOC
+ sf_malloc_dbug_id= mariadb_dbug_id;
+#endif
+
+ max_system_variables.pseudo_thread_id= (ulong)~0;
+ server_start_time= flush_status_time= my_time(0);
+
+ global_rpl_filter= new Rpl_filter;
+ binlog_filter= new Rpl_filter;
+ if (!global_rpl_filter || !binlog_filter)
+ {
+ sql_perror("Could not allocate replication and binlog filters");
+ return 1;
+ }
+
+ if (init_thread_environment() ||
+ mysql_init_variables())
+ return 1;
+
+ if (ignore_db_dirs_init())
+ return 1;
+
+#ifdef HAVE_TZNAME
+ struct tm tm_tmp;
+ localtime_r(&server_start_time,&tm_tmp);
+ const char *tz_name= tzname[tm_tmp.tm_isdst != 0 ? 1 : 0];
+#ifdef _WIN32
+ /*
+ Time zone name may be localized and contain non-ASCII characters,
+ Convert from ANSI encoding to UTF8.
+ */
+ wchar_t wtz_name[sizeof(system_time_zone)];
+ mbstowcs(wtz_name, tz_name, sizeof(system_time_zone)-1);
+ WideCharToMultiByte(CP_UTF8,0, wtz_name, -1, system_time_zone,
+ sizeof(system_time_zone) - 1, NULL, NULL);
+#else
+ strmake_buf(system_time_zone, tz_name);
+#endif /* _WIN32 */
+#endif /* HAVE_TZNAME */
+
+ /*
+ We set SYSTEM time zone as reasonable default and
+ also for failure of my_tz_init() and bootstrap mode.
+ If user explicitly set time zone with --default-time-zone
+ option we will change this value in my_tz_init().
+ */
+ global_system_variables.time_zone= my_tz_SYSTEM;
+
+#ifdef HAVE_PSI_INTERFACE
+ /*
+ Complete the mysql_bin_log initialization.
+ Instrumentation keys are known only after the performance schema
+ initialization, and can not be set in the MYSQL_BIN_LOG
+ constructor (called before main()).
+ */
+ mysql_bin_log.set_psi_keys(key_BINLOG_LOCK_index,
+ key_BINLOG_update_cond,
+ key_file_binlog,
+ key_file_binlog_index,
+ key_BINLOG_COND_queue_busy);
+#endif
+
+ /*
+ Init mutexes for the global MYSQL_BIN_LOG objects.
+ As safe_mutex depends on what MY_INIT() does, we can't init the mutexes of
+ global MYSQL_BIN_LOGs in their constructors, because then they would be
+ inited before MY_INIT(). So we do it here.
+ */
+ mysql_bin_log.init_pthread_objects();
+
+ /* TODO: remove this when my_time_t is 64 bit compatible */
+ if (!IS_TIME_T_VALID_FOR_TIMESTAMP(server_start_time))
+ {
+ sql_print_error("This MySQL server doesn't support dates later then 2038");
+ return 1;
+ }
+
+ if (gethostname(glob_hostname,sizeof(glob_hostname)) < 0)
+ {
+ /*
+ Get hostname of computer (used by 'show variables') and as default
+ basename for the pid file if --log-basename is not given.
+ */
+ strmake(glob_hostname, STRING_WITH_LEN("localhost"));
+ sql_print_warning("gethostname failed, using '%s' as hostname",
+ glob_hostname);
+ opt_log_basename= const_cast<char *>("mysql");
+ }
+ else
+ opt_log_basename= glob_hostname;
+
+ if (!*pidfile_name)
+ {
+ strmake(pidfile_name, opt_log_basename, sizeof(pidfile_name)-5);
+ strmov(fn_ext(pidfile_name),".pid"); // Add proper extension
+ }
+
+ /*
+ The default-storage-engine entry in my_long_options should have a
+ non-null default value. It was earlier intialized as
+ (longlong)"MyISAM" in my_long_options but this triggered a
+ compiler error in the Sun Studio 12 compiler. As a work-around we
+ set the def_value member to 0 in my_long_options and initialize it
+ to the correct value here.
+
+ From MySQL 5.5 onwards, the default storage engine is InnoDB
+ (except in the embedded server, where the default continues to
+ be MyISAM)
+ */
+#if defined(WITH_INNOBASE_STORAGE_ENGINE) || defined(WITH_XTRADB_STORAGE_ENGINE)
+ default_storage_engine= const_cast<char *>("InnoDB");
+#else
+ default_storage_engine= const_cast<char *>("MyISAM");
+#endif
+
+ /*
+ Add server status variables to the dynamic list of
+ status variables that is shown by SHOW STATUS.
+ Later, in plugin_init, and mysql_install_plugin
+ new entries could be added to that list.
+ */
+ if (add_status_vars(status_vars))
+ return 1; // an error was already reported
+
+#ifndef DBUG_OFF
+ /*
+ We have few debug-only commands in com_status_vars, only visible in debug
+ builds. for simplicity we enable the assert only in debug builds
+
+ There are 8 Com_ variables which don't have corresponding SQLCOM_ values:
+ (TODO strictly speaking they shouldn't be here, should not have Com_ prefix
+ that is. Perhaps Stmt_ ? Comstmt_ ? Prepstmt_ ?)
+
+ Com_admin_commands => com_other
+ Com_stmt_close => com_stmt_close
+ Com_stmt_execute => com_stmt_execute
+ Com_stmt_fetch => com_stmt_fetch
+ Com_stmt_prepare => com_stmt_prepare
+ Com_stmt_reprepare => com_stmt_reprepare
+ Com_stmt_reset => com_stmt_reset
+ Com_stmt_send_long_data => com_stmt_send_long_data
+
+ With this correction the number of Com_ variables (number of elements in
+ the array, excluding the last element - terminator) must match the number
+ of SQLCOM_ constants.
+ */
+ compile_time_assert(sizeof(com_status_vars)/sizeof(com_status_vars[0]) - 1 ==
+ SQLCOM_END + 8);
+#endif
+
+ if (get_options(&remaining_argc, &remaining_argv))
+ return 1;
+ set_server_version();
+
+ if (!opt_help)
+ sql_print_information("%s (mysqld %s) starting as process %lu ...",
+ my_progname, server_version, (ulong) getpid());
+
+#ifndef EMBEDDED_LIBRARY
+ if (opt_abort && !opt_verbose)
+ unireg_abort(0);
+#endif /*!EMBEDDED_LIBRARY*/
+
+ DBUG_PRINT("info",("%s Ver %s for %s on %s\n",my_progname,
+ server_version, SYSTEM_TYPE,MACHINE_TYPE));
+
+#ifdef HAVE_LARGE_PAGES
+ /* Initialize large page size */
+ if (opt_large_pages && (opt_large_page_size= my_get_large_page_size()))
+ {
+ DBUG_PRINT("info", ("Large page set, large_page_size = %d",
+ opt_large_page_size));
+ my_use_large_pages= 1;
+ my_large_page_size= opt_large_page_size;
+ }
+ else
+ {
+ opt_large_pages= 0;
+ /*
+ Either not configured to use large pages or Linux haven't
+ been compiled with large page support
+ */
+ }
+#endif /* HAVE_LARGE_PAGES */
+#ifdef HAVE_SOLARIS_LARGE_PAGES
+#define LARGE_PAGESIZE (4*1024*1024) /* 4MB */
+#define SUPER_LARGE_PAGESIZE (256*1024*1024) /* 256MB */
+ if (opt_large_pages)
+ {
+ /*
+ tell the kernel that we want to use 4/256MB page for heap storage
+ and also for the stack. We use 4 MByte as default and if the
+ super-large-page is set we increase it to 256 MByte. 256 MByte
+ is for server installations with GBytes of RAM memory where
+ the MySQL Server will have page caches and other memory regions
+ measured in a number of GBytes.
+ We use as big pages as possible which isn't bigger than the above
+ desired page sizes.
+ */
+ int nelem;
+ size_t max_desired_page_size;
+ if (opt_super_large_pages)
+ max_desired_page_size= SUPER_LARGE_PAGESIZE;
+ else
+ max_desired_page_size= LARGE_PAGESIZE;
+ nelem = getpagesizes(NULL, 0);
+ if (nelem > 0)
+ {
+ size_t *pagesize = (size_t *) malloc(sizeof(size_t) * nelem);
+ if (pagesize != NULL && getpagesizes(pagesize, nelem) > 0)
+ {
+ size_t max_page_size= 0;
+ for (int i= 0; i < nelem; i++)
+ {
+ if (pagesize[i] > max_page_size &&
+ pagesize[i] <= max_desired_page_size)
+ max_page_size= pagesize[i];
+ }
+ free(pagesize);
+ if (max_page_size > 0)
+ {
+ struct memcntl_mha mpss;
+
+ mpss.mha_cmd= MHA_MAPSIZE_BSSBRK;
+ mpss.mha_pagesize= max_page_size;
+ mpss.mha_flags= 0;
+ memcntl(NULL, 0, MC_HAT_ADVISE, (caddr_t)&mpss, 0, 0);
+ mpss.mha_cmd= MHA_MAPSIZE_STACK;
+ memcntl(NULL, 0, MC_HAT_ADVISE, (caddr_t)&mpss, 0, 0);
+ }
+ }
+ }
+ }
+#endif /* HAVE_SOLARIS_LARGE_PAGES */
+
+ /* connections and databases needs lots of files */
+ {
+ uint files, wanted_files, max_open_files;
+
+ /* MyISAM requires two file handles per table. */
+ wanted_files= (10 + max_connections + extra_max_connections +
+ tc_size * 2);
+ /*
+ We are trying to allocate no less than max_connections*5 file
+ handles (i.e. we are trying to set the limit so that they will
+ be available). In addition, we allocate no less than how much
+ was already allocated. However below we report a warning and
+ recompute values only if we got less file handles than were
+ explicitly requested. No warning and re-computation occur if we
+ can't get max_connections*5 but still got no less than was
+ requested (value of wanted_files).
+ */
+ max_open_files= MY_MAX(MY_MAX(wanted_files,
+ (max_connections + extra_max_connections)*5),
+ open_files_limit);
+ files= my_set_max_open_files(max_open_files);
+
+ if (files < wanted_files)
+ {
+ if (!open_files_limit)
+ {
+ /*
+ If we have requested too much file handles than we bring
+ max_connections in supported bounds.
+ */
+ max_connections= (ulong) MY_MIN(files-10-TABLE_OPEN_CACHE_MIN*2,
+ max_connections);
+ /*
+ Decrease tc_size according to max_connections, but
+ not below TABLE_OPEN_CACHE_MIN. Outer MY_MIN() ensures that we
+ never increase tc_size automatically (that could
+ happen if max_connections is decreased above).
+ */
+ tc_size= (ulong) MY_MIN(MY_MAX((files - 10 - max_connections) / 2,
+ TABLE_OPEN_CACHE_MIN), tc_size);
+ DBUG_PRINT("warning",
+ ("Changed limits: max_open_files: %u max_connections: %ld table_cache: %ld",
+ files, max_connections, tc_size));
+ if (global_system_variables.log_warnings)
+ sql_print_warning("Changed limits: max_open_files: %u max_connections: %ld table_cache: %ld",
+ files, max_connections, tc_size);
+ }
+ else if (global_system_variables.log_warnings)
+ sql_print_warning("Could not increase number of max_open_files to more than %u (request: %u)", files, wanted_files);
+ }
+ open_files_limit= files;
+ }
+ unireg_init(opt_specialflag); /* Set up extern variabels */
+ if (!(my_default_lc_messages=
+ my_locale_by_name(lc_messages)))
+ {
+ sql_print_error("Unknown locale: '%s'", lc_messages);
+ return 1;
+ }
+ global_system_variables.lc_messages= my_default_lc_messages;
+ if (init_errmessage()) /* Read error messages from file */
+ return 1;
+ init_client_errs();
+ mysql_library_init(unused,unused,unused); /* for replication */
+ lex_init();
+ if (item_create_init())
+ return 1;
+ item_init();
+ init_pcre();
+ /*
+ Process a comma-separated character set list and choose
+ the first available character set. This is mostly for
+ test purposes, to be able to start "mysqld" even if
+ the requested character set is not available (see bug#18743).
+ */
+ for (;;)
+ {
+ char *next_character_set_name= strchr(default_character_set_name, ',');
+ if (next_character_set_name)
+ *next_character_set_name++= '\0';
+ if (!(default_charset_info=
+ get_charset_by_csname(default_character_set_name,
+ MY_CS_PRIMARY, MYF(MY_WME))))
+ {
+ if (next_character_set_name)
+ {
+ default_character_set_name= next_character_set_name;
+ default_collation_name= 0; // Ignore collation
+ }
+ else
+ return 1; // Eof of the list
+ }
+ else
+ break;
+ }
+
+ if (default_collation_name)
+ {
+ CHARSET_INFO *default_collation;
+ default_collation= get_charset_by_name(default_collation_name, MYF(0));
+ if (!default_collation)
+ {
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ buffered_logs.print();
+ buffered_logs.cleanup();
+#endif
+ sql_print_error(ER_DEFAULT(ER_UNKNOWN_COLLATION), default_collation_name);
+ return 1;
+ }
+ if (!my_charset_same(default_charset_info, default_collation))
+ {
+ sql_print_error(ER_DEFAULT(ER_COLLATION_CHARSET_MISMATCH),
+ default_collation_name,
+ default_charset_info->csname);
+ return 1;
+ }
+ default_charset_info= default_collation;
+ }
+ /* Set collactions that depends on the default collation */
+ global_system_variables.collation_server= default_charset_info;
+ global_system_variables.collation_database= default_charset_info;
+ global_system_variables.collation_connection= default_charset_info;
+ global_system_variables.character_set_results= default_charset_info;
+ if (default_charset_info->mbminlen > 1)
+ {
+ global_system_variables.character_set_client= &my_charset_latin1;
+ sql_print_warning("Cannot use %s as character_set_client, %s will be used instead",
+ default_charset_info->csname,
+ global_system_variables.character_set_client->csname);
+ }
+ else
+ global_system_variables.character_set_client= default_charset_info;
+
+ if (!(character_set_filesystem=
+ get_charset_by_csname(character_set_filesystem_name,
+ MY_CS_PRIMARY, MYF(MY_WME))))
+ return 1;
+ global_system_variables.character_set_filesystem= character_set_filesystem;
+
+ if (!(my_default_lc_time_names=
+ my_locale_by_name(lc_time_names_name)))
+ {
+ sql_print_error("Unknown locale: '%s'", lc_time_names_name);
+ return 1;
+ }
+ global_system_variables.lc_time_names= my_default_lc_time_names;
+
+ /* check log options and issue warnings if needed */
+ if (opt_log && opt_logname && *opt_logname &&
+ !(log_output_options & (LOG_FILE | LOG_NONE)))
+ sql_print_warning("Although a path was specified for the "
+ "--log option, log tables are used. "
+ "To enable logging to files use the --log-output option.");
+
+ if (opt_slow_log && opt_slow_logname && *opt_slow_logname &&
+ !(log_output_options & (LOG_FILE | LOG_NONE)))
+ sql_print_warning("Although a path was specified for the "
+ "--log-slow-queries option, log tables are used. "
+ "To enable logging to files use the --log-output=file option.");
+
+ if (!opt_logname || !*opt_logname)
+ make_default_log_name(&opt_logname, ".log", false);
+ if (!opt_slow_logname || !*opt_slow_logname)
+ make_default_log_name(&opt_slow_logname, "-slow.log", false);
+
+#if defined(ENABLED_DEBUG_SYNC)
+ /* Initialize the debug sync facility. See debug_sync.cc. */
+ if (debug_sync_init())
+ return 1; /* purecov: tested */
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
+#if (ENABLE_TEMP_POOL)
+ if (use_temp_pool && my_bitmap_init(&temp_pool,0,1024,1))
+ return 1;
+#else
+ use_temp_pool= 0;
+#endif
+
+ if (my_dboptions_cache_init())
+ return 1;
+
+ /*
+ Ensure that lower_case_table_names is set on system where we have case
+ insensitive names. If this is not done the users MyISAM tables will
+ get corrupted if accesses with names of different case.
+ */
+ DBUG_PRINT("info", ("lower_case_table_names: %d", lower_case_table_names));
+ lower_case_file_system= test_if_case_insensitive(mysql_real_data_home);
+ if (!lower_case_table_names && lower_case_file_system == 1)
+ {
+ if (lower_case_table_names_used)
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_warning("You have forced lower_case_table_names to 0 through "
+ "a command-line option, even though your file system "
+ "'%s' is case insensitive. This means that you can "
+ "corrupt a MyISAM table by accessing it with "
+ "different cases. You should consider changing "
+ "lower_case_table_names to 1 or 2",
+ mysql_real_data_home);
+ }
+ else
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_warning("Setting lower_case_table_names=2 because file system for %s is case insensitive", mysql_real_data_home);
+ lower_case_table_names= 2;
+ }
+ }
+ else if (lower_case_table_names == 2 &&
+ !(lower_case_file_system= (lower_case_file_system == 1)))
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_warning("lower_case_table_names was set to 2, even though your "
+ "the file system '%s' is case sensitive. Now setting "
+ "lower_case_table_names to 0 to avoid future problems.",
+ mysql_real_data_home);
+ lower_case_table_names= 0;
+ }
+ else
+ {
+ lower_case_file_system= (lower_case_file_system == 1);
+ }
+
+ /* Reset table_alias_charset, now that lower_case_table_names is set. */
+ table_alias_charset= (lower_case_table_names ?
+ files_charset_info :
+ &my_charset_bin);
+
+ if (ignore_db_dirs_process_additions())
+ {
+ sql_print_error("An error occurred while storing ignore_db_dirs to a hash.");
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static int init_thread_environment()
+{
+ DBUG_ENTER("init_thread_environment");
+ mysql_mutex_init(key_LOCK_thread_count, &LOCK_thread_count, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_thread_cache, &LOCK_thread_cache, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_status, &LOCK_status, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_show_status, &LOCK_show_status, MY_MUTEX_INIT_SLOW);
+ mysql_mutex_init(key_LOCK_delayed_insert,
+ &LOCK_delayed_insert, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_delayed_status,
+ &LOCK_delayed_status, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_delayed_create,
+ &LOCK_delayed_create, MY_MUTEX_INIT_SLOW);
+ mysql_mutex_init(key_LOCK_crypt, &LOCK_crypt, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_user_conn, &LOCK_user_conn, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_active_mi, &LOCK_active_mi, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_global_system_variables,
+ &LOCK_global_system_variables, MY_MUTEX_INIT_FAST);
+ mysql_mutex_record_order(&LOCK_active_mi, &LOCK_global_system_variables);
+ mysql_rwlock_init(key_rwlock_LOCK_system_variables_hash,
+ &LOCK_system_variables_hash);
+ mysql_mutex_init(key_LOCK_prepared_stmt_count,
+ &LOCK_prepared_stmt_count, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_error_messages,
+ &LOCK_error_messages, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_uuid_short_generator,
+ &LOCK_short_uuid_generator, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_connection_count,
+ &LOCK_connection_count, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_stats, &LOCK_stats, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_global_user_client_stats,
+ &LOCK_global_user_client_stats, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_global_table_stats,
+ &LOCK_global_table_stats, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_global_index_stats,
+ &LOCK_global_index_stats, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_prepare_ordered, &LOCK_prepare_ordered,
+ MY_MUTEX_INIT_SLOW);
+ mysql_cond_init(key_COND_prepare_ordered, &COND_prepare_ordered, NULL);
+ mysql_mutex_init(key_LOCK_commit_ordered, &LOCK_commit_ordered,
+ MY_MUTEX_INIT_SLOW);
+ mysql_mutex_init(key_LOCK_slave_init, &LOCK_slave_init,
+ MY_MUTEX_INIT_SLOW);
+ mysql_cond_init(key_COND_slave_init, &COND_slave_init, NULL);
+
+#ifdef HAVE_OPENSSL
+ mysql_mutex_init(key_LOCK_des_key_file,
+ &LOCK_des_key_file, MY_MUTEX_INIT_FAST);
+#ifndef HAVE_YASSL
+ openssl_stdlocks= (openssl_lock_t*) OPENSSL_malloc(CRYPTO_num_locks() *
+ sizeof(openssl_lock_t));
+ for (int i= 0; i < CRYPTO_num_locks(); ++i)
+ mysql_rwlock_init(key_rwlock_openssl, &openssl_stdlocks[i].lock);
+ CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
+ CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
+ CRYPTO_set_dynlock_lock_callback(openssl_lock);
+ CRYPTO_set_locking_callback(openssl_lock_function);
+ CRYPTO_set_id_callback(openssl_id_function);
+#endif
+#endif
+ mysql_rwlock_init(key_rwlock_LOCK_sys_init_connect, &LOCK_sys_init_connect);
+ mysql_rwlock_init(key_rwlock_LOCK_sys_init_slave, &LOCK_sys_init_slave);
+ mysql_rwlock_init(key_rwlock_LOCK_grant, &LOCK_grant);
+ mysql_cond_init(key_COND_thread_count, &COND_thread_count, NULL);
+ mysql_cond_init(key_COND_thread_cache, &COND_thread_cache, NULL);
+ mysql_cond_init(key_COND_flush_thread_cache, &COND_flush_thread_cache, NULL);
+#ifdef HAVE_REPLICATION
+ mysql_mutex_init(key_LOCK_rpl_status, &LOCK_rpl_status, MY_MUTEX_INIT_FAST);
+#endif
+ mysql_mutex_init(key_LOCK_server_started,
+ &LOCK_server_started, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_server_started, &COND_server_started, NULL);
+ sp_cache_init();
+#ifdef HAVE_EVENT_SCHEDULER
+ Events::init_mutexes();
+#endif
+ init_show_explain_psi_keys();
+ /* Parameter for threads created for connections */
+ (void) pthread_attr_init(&connection_attrib);
+ (void) pthread_attr_setdetachstate(&connection_attrib,
+ PTHREAD_CREATE_DETACHED);
+ pthread_attr_setscope(&connection_attrib, PTHREAD_SCOPE_SYSTEM);
+
+#ifdef HAVE_REPLICATION
+ rpl_init_gtid_slave_state();
+ rpl_init_gtid_waiting();
+#endif
+
+ DBUG_RETURN(0);
+}
+
+
+#if defined(HAVE_OPENSSL) && !defined(HAVE_YASSL)
+static unsigned long openssl_id_function()
+{
+ return (unsigned long) pthread_self();
+}
+
+
+static openssl_lock_t *openssl_dynlock_create(const char *file, int line)
+{
+ openssl_lock_t *lock= new openssl_lock_t;
+ mysql_rwlock_init(key_rwlock_openssl, &lock->lock);
+ return lock;
+}
+
+
+static void openssl_dynlock_destroy(openssl_lock_t *lock, const char *file,
+ int line)
+{
+ mysql_rwlock_destroy(&lock->lock);
+ delete lock;
+}
+
+
+static void openssl_lock_function(int mode, int n, const char *file, int line)
+{
+ if (n < 0 || n > CRYPTO_num_locks())
+ {
+ /* Lock number out of bounds. */
+ sql_print_error("Fatal: OpenSSL interface problem (n = %d)", n);
+ abort();
+ }
+ openssl_lock(mode, &openssl_stdlocks[n], file, line);
+}
+
+
+static void openssl_lock(int mode, openssl_lock_t *lock, const char *file,
+ int line)
+{
+ int err;
+ char const *what;
+
+ switch (mode) {
+ case CRYPTO_LOCK|CRYPTO_READ:
+ what = "read lock";
+ err= mysql_rwlock_rdlock(&lock->lock);
+ break;
+ case CRYPTO_LOCK|CRYPTO_WRITE:
+ what = "write lock";
+ err= mysql_rwlock_wrlock(&lock->lock);
+ break;
+ case CRYPTO_UNLOCK|CRYPTO_READ:
+ case CRYPTO_UNLOCK|CRYPTO_WRITE:
+ what = "unlock";
+ err= mysql_rwlock_unlock(&lock->lock);
+ break;
+ default:
+ /* Unknown locking mode. */
+ sql_print_error("Fatal: OpenSSL interface problem (mode=0x%x)", mode);
+ abort();
+ }
+ if (err)
+ {
+ sql_print_error("Fatal: can't %s OpenSSL lock", what);
+ abort();
+ }
+}
+#endif /* HAVE_OPENSSL */
+
+
+static void init_ssl()
+{
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+ if (opt_use_ssl)
+ {
+ enum enum_ssl_init_error error= SSL_INITERR_NOERROR;
+
+ /* having ssl_acceptor_fd != 0 signals the use of SSL */
+ ssl_acceptor_fd= new_VioSSLAcceptorFd(opt_ssl_key, opt_ssl_cert,
+ opt_ssl_ca, opt_ssl_capath,
+ opt_ssl_cipher, &error,
+ opt_ssl_crl, opt_ssl_crlpath);
+ DBUG_PRINT("info",("ssl_acceptor_fd: 0x%lx", (long) ssl_acceptor_fd));
+ if (!ssl_acceptor_fd)
+ {
+ sql_print_warning("Failed to setup SSL");
+ sql_print_warning("SSL error: %s", sslGetErrString(error));
+ opt_use_ssl = 0;
+ have_ssl= SHOW_OPTION_DISABLED;
+ }
+ if (global_system_variables.log_warnings > 0)
+ {
+ ulong err;
+ while ((err= ERR_get_error()))
+ sql_print_warning("SSL error: %s", ERR_error_string(err, NULL));
+ }
+ else
+ ERR_remove_state(0);
+ }
+ else
+ {
+ have_ssl= SHOW_OPTION_DISABLED;
+ }
+ if (des_key_file)
+ load_des_key_file(des_key_file);
+#endif /* HAVE_OPENSSL && ! EMBEDDED_LIBRARY */
+}
+
+
+static void end_ssl()
+{
+#ifdef HAVE_OPENSSL
+#ifndef EMBEDDED_LIBRARY
+ if (ssl_acceptor_fd)
+ {
+ free_vio_ssl_acceptor_fd(ssl_acceptor_fd);
+ ssl_acceptor_fd= 0;
+ }
+#endif /* ! EMBEDDED_LIBRARY */
+#endif /* HAVE_OPENSSL */
+}
+
+#ifdef _WIN32
+/**
+ Registers a file to be collected when Windows Error Reporting creates a crash
+ report.
+
+ @note only works on Vista and later, since WerRegisterFile() is not available
+ on earlier Windows.
+*/
+#include <werapi.h>
+static void add_file_to_crash_report(char *file)
+{
+ /* Load WerRegisterFile function dynamically.*/
+ HRESULT (WINAPI *pWerRegisterFile)(PCWSTR, WER_REGISTER_FILE_TYPE, DWORD)
+ =(HRESULT (WINAPI *) (PCWSTR, WER_REGISTER_FILE_TYPE, DWORD))
+ GetProcAddress(GetModuleHandle("kernel32"),"WerRegisterFile");
+
+ if (pWerRegisterFile)
+ {
+ wchar_t wfile[MAX_PATH+1]= {0};
+ if (mbstowcs(wfile, file, MAX_PATH) != (size_t)-1)
+ {
+ pWerRegisterFile(wfile, WerRegFileTypeOther, WER_FILE_ANONYMOUS_DATA);
+ }
+ }
+}
+#endif
+
+static int init_server_components()
+{
+ DBUG_ENTER("init_server_components");
+ /*
+ We need to call each of these following functions to ensure that
+ all things are initialized so that unireg_abort() doesn't fail
+ */
+ mdl_init();
+ if (tdc_init() | hostname_cache_init())
+ unireg_abort(1);
+
+ query_cache_set_min_res_unit(query_cache_min_res_unit);
+ query_cache_init();
+ query_cache_resize(query_cache_size);
+ query_cache_result_size_limit(query_cache_limit);
+ my_rnd_init(&sql_rand,(ulong) server_start_time,(ulong) server_start_time/2);
+ setup_fpu();
+ init_thr_lock();
+ my_uuid_init((ulong) (my_rnd(&sql_rand))*12345,12345);
+#ifdef HAVE_REPLICATION
+ init_slave_list();
+#endif
+ wt_init();
+
+ /* Setup logs */
+
+ /*
+ Enable old-fashioned error log, except when the user has requested
+ help information. Since the implementation of plugin server
+ variables the help output is now written much later.
+ */
+ if (opt_error_log && !opt_abort)
+ {
+ if (!log_error_file_ptr[0])
+ fn_format(log_error_file, pidfile_name, mysql_data_home, ".err",
+ MY_REPLACE_EXT); /* replace '.<domain>' by '.err', bug#4997 */
+ else
+ fn_format(log_error_file, log_error_file_ptr, mysql_data_home, ".err",
+ MY_UNPACK_FILENAME | MY_SAFE_PATH);
+ /*
+ _ptr may have been set to my_disabled_option or "" if no argument was
+ passed, but we need to show the real name in SHOW VARIABLES:
+ */
+ log_error_file_ptr= log_error_file;
+ if (!log_error_file[0])
+ opt_error_log= 0; // Too long file name
+ else
+ {
+ my_bool res;
+#ifndef EMBEDDED_LIBRARY
+ res= reopen_fstreams(log_error_file, stdout, stderr);
+#else
+ res= reopen_fstreams(log_error_file, NULL, stderr);
+#endif
+
+ if (!res)
+ setbuf(stderr, NULL);
+
+#ifdef _WIN32
+ /* Add error log to windows crash reporting. */
+ add_file_to_crash_report(log_error_file);
+#endif
+ }
+ }
+
+ /* set up the hook before initializing plugins which may use it */
+ error_handler_hook= my_message_sql;
+ proc_info_hook= set_thd_stage_info;
+
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ /*
+ Parsing the performance schema command line option may have reported
+ warnings/information messages.
+ Now that the logger is finally available, and redirected
+ to the proper file when the --log--error option is used,
+ print the buffered messages to the log.
+ */
+ buffered_logs.print();
+ buffered_logs.cleanup();
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+
+#ifndef EMBEDDED_LIBRARY
+ /*
+ Now that the logger is available, redirect character set
+ errors directly to the logger
+ (instead of the buffered_logs used at the server startup time).
+ */
+ my_charset_error_reporter= charset_error_reporter;
+#endif
+
+ if (xid_cache_init())
+ {
+ sql_print_error("Out of memory");
+ unireg_abort(1);
+ }
+
+ /*
+ initialize delegates for extension observers, errors have already
+ been reported in the function
+ */
+ if (delegates_init())
+ unireg_abort(1);
+
+ /* need to configure logging before initializing storage engines */
+ if (!opt_bin_log_used)
+ {
+ if (opt_log_slave_updates)
+ sql_print_warning("You need to use --log-bin to make "
+ "--log-slave-updates work.");
+ if (binlog_format_used)
+ sql_print_warning("You need to use --log-bin to make "
+ "--binlog-format work.");
+ }
+
+ /* Check that we have not let the format to unspecified at this point */
+ DBUG_ASSERT((uint)global_system_variables.binlog_format <=
+ array_elements(binlog_format_names)-1);
+
+#ifdef HAVE_REPLICATION
+ if (opt_log_slave_updates && replicate_same_server_id)
+ {
+ if (opt_bin_log)
+ {
+ sql_print_error("using --replicate-same-server-id in conjunction with "
+ "--log-slave-updates is impossible, it would lead to "
+ "infinite loops in this server.");
+ unireg_abort(1);
+ }
+ else
+ sql_print_warning("using --replicate-same-server-id in conjunction with "
+ "--log-slave-updates would lead to infinite loops in "
+ "this server. However this will be ignored as the "
+ "--log-bin option is not defined.");
+ }
+#endif
+
+ DBUG_ASSERT(!opt_bin_log || opt_bin_logname);
+
+ if (opt_bin_log)
+ {
+ /* Reports an error and aborts, if the --log-bin's path
+ is a directory.*/
+ if (opt_bin_logname[0] &&
+ opt_bin_logname[strlen(opt_bin_logname) - 1] == FN_LIBCHAR)
+ {
+ sql_print_error("Path '%s' is a directory name, please specify "
+ "a file name for --log-bin option", opt_bin_logname);
+ unireg_abort(1);
+ }
+
+ /* Reports an error and aborts, if the --log-bin-index's path
+ is a directory.*/
+ if (opt_binlog_index_name &&
+ opt_binlog_index_name[strlen(opt_binlog_index_name) - 1]
+ == FN_LIBCHAR)
+ {
+ sql_print_error("Path '%s' is a directory name, please specify "
+ "a file name for --log-bin-index option",
+ opt_binlog_index_name);
+ unireg_abort(1);
+ }
+
+ char buf[FN_REFLEN];
+ const char *ln;
+ ln= mysql_bin_log.generate_name(opt_bin_logname, "-bin", 1, buf);
+ if (!opt_bin_logname[0] && !opt_binlog_index_name)
+ {
+ /*
+ User didn't give us info to name the binlog index file.
+ Picking `hostname`-bin.index like did in 4.x, causes replication to
+ fail if the hostname is changed later. So, we would like to instead
+ require a name. But as we don't want to break many existing setups, we
+ only give warning, not error.
+ */
+ sql_print_warning("No argument was provided to --log-bin and "
+ "neither --log-basename or --log-bin-index where "
+ "used; This may cause repliction to break when this "
+ "server acts as a master and has its hostname "
+ "changed! Please use '--log-basename=%s' or "
+ "'--log-bin=%s' to avoid this problem.",
+ opt_log_basename, ln);
+ }
+ if (ln == buf)
+ {
+ opt_bin_logname= my_once_strdup(buf, MYF(MY_WME));
+ }
+ if (mysql_bin_log.open_index_file(opt_binlog_index_name, ln, TRUE))
+ {
+ unireg_abort(1);
+ }
+ }
+
+ /* call ha_init_key_cache() on all key caches to init them */
+ process_key_caches(&ha_init_key_cache, 0);
+
+ init_global_table_stats();
+ init_global_index_stats();
+
+ /* Allow storage engine to give real error messages */
+ if (ha_init_errors())
+ DBUG_RETURN(1);
+
+ tc_log= 0; // ha_initialize_handlerton() needs that
+
+ if (plugin_init(&remaining_argc, remaining_argv,
+ (opt_noacl ? PLUGIN_INIT_SKIP_PLUGIN_TABLE : 0) |
+ (opt_abort ? PLUGIN_INIT_SKIP_INITIALIZATION : 0)))
+ {
+ sql_print_error("Failed to initialize plugins.");
+ unireg_abort(1);
+ }
+ plugins_are_initialized= TRUE; /* Don't separate from init function */
+
+ /* we do want to exit if there are any other unknown options */
+ if (remaining_argc > 1)
+ {
+ int ho_error;
+ struct my_option no_opts[]=
+ {
+ {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+ };
+ /*
+ We need to eat any 'loose' arguments first before we conclude
+ that there are unprocessed options.
+ */
+ my_getopt_skip_unknown= 0;
+
+ if ((ho_error= handle_options(&remaining_argc, &remaining_argv, no_opts,
+ mysqld_get_one_option)))
+ unireg_abort(ho_error);
+ /* Add back the program name handle_options removes */
+ remaining_argc++;
+ remaining_argv--;
+ my_getopt_skip_unknown= TRUE;
+
+ if (remaining_argc > 1)
+ {
+ fprintf(stderr, "%s: Too many arguments (first extra is '%s').\n",
+ my_progname, remaining_argv[1]);
+ unireg_abort(1);
+ }
+ }
+
+ if (opt_abort)
+ unireg_abort(0);
+
+ /* if the errmsg.sys is not loaded, terminate to maintain behaviour */
+ if (!DEFAULT_ERRMSGS[0][0])
+ unireg_abort(1);
+
+ /* We have to initialize the storage engines before CSV logging */
+ if (ha_init())
+ {
+ sql_print_error("Can't init databases");
+ unireg_abort(1);
+ }
+
+ if (opt_bootstrap)
+ log_output_options= LOG_FILE;
+ else
+ logger.init_log_tables();
+
+ if (log_output_options & LOG_NONE)
+ {
+ /*
+ Issue a warining if there were specified additional options to the
+ log-output along with NONE. Probably this wasn't what user wanted.
+ */
+ if ((log_output_options & LOG_NONE) && (log_output_options & ~LOG_NONE))
+ sql_print_warning("There were other values specified to "
+ "log-output besides NONE. Disabling slow "
+ "and general logs anyway.");
+ logger.set_handlers(LOG_FILE, LOG_NONE, LOG_NONE);
+ }
+ else
+ {
+ /* fall back to the log files if tables are not present */
+ LEX_STRING csv_name={C_STRING_WITH_LEN("csv")};
+ if (!plugin_is_ready(&csv_name, MYSQL_STORAGE_ENGINE_PLUGIN))
+ {
+ /* purecov: begin inspected */
+ sql_print_error("CSV engine is not present, falling back to the "
+ "log files");
+ log_output_options= (log_output_options & ~LOG_TABLE) | LOG_FILE;
+ /* purecov: end */
+ }
+
+ logger.set_handlers(LOG_FILE, opt_slow_log ? log_output_options:LOG_NONE,
+ opt_log ? log_output_options:LOG_NONE);
+ }
+
+ /*
+ Set the default storage engine
+ */
+ LEX_STRING name= { default_storage_engine, strlen(default_storage_engine) };
+ plugin_ref plugin;
+ handlerton *hton;
+ if ((plugin= ha_resolve_by_name(0, &name)))
+ hton= plugin_hton(plugin);
+ else
+ {
+ sql_print_error("Unknown/unsupported storage engine: %s",
+ default_storage_engine);
+ unireg_abort(1);
+ }
+ if (!ha_storage_engine_is_enabled(hton))
+ {
+ if (!opt_bootstrap)
+ {
+ sql_print_error("Default storage engine (%s) is not available",
+ default_storage_engine);
+ unireg_abort(1);
+ }
+ DBUG_ASSERT(global_system_variables.table_plugin);
+ }
+ else
+ {
+ /*
+ Need to unlock as global_system_variables.table_plugin
+ was acquired during plugin_init()
+ */
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ plugin_unlock(0, global_system_variables.table_plugin);
+ global_system_variables.table_plugin= plugin;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ }
+#ifdef USE_ARIA_FOR_TMP_TABLES
+ if (!ha_storage_engine_is_enabled(maria_hton) && !opt_bootstrap)
+ {
+ sql_print_error("Aria engine is not enabled or did not start. The Aria engine must be enabled to continue as mysqld was configured with --with-aria-tmp-tables");
+ unireg_abort(1);
+ }
+ internal_tmp_table_max_key_length= maria_max_key_length();
+ internal_tmp_table_max_key_segments= maria_max_key_segments();
+#else
+ internal_tmp_table_max_key_length= myisam_max_key_length();
+ internal_tmp_table_max_key_segments= myisam_max_key_segments();
+#endif
+
+ tc_log= get_tc_log_implementation();
+
+ if (tc_log->open(opt_bin_log ? opt_bin_logname : opt_tc_log_file))
+ {
+ sql_print_error("Can't init tc log");
+ unireg_abort(1);
+ }
+
+ if (ha_recover(0))
+ {
+ unireg_abort(1);
+ }
+
+ if (opt_bin_log && mysql_bin_log.open(opt_bin_logname, LOG_BIN, 0,
+ WRITE_CACHE, max_binlog_size, 0, TRUE))
+ unireg_abort(1);
+
+#ifdef HAVE_REPLICATION
+ if (opt_bin_log && expire_logs_days)
+ {
+ time_t purge_time= server_start_time - expire_logs_days*24*60*60;
+ if (purge_time >= 0)
+ mysql_bin_log.purge_logs_before_date(purge_time);
+ }
+#endif
+
+ if (opt_myisam_log)
+ (void) mi_log(1);
+
+#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT) && !defined(EMBEDDED_LIBRARY)
+ if (locked_in_memory && !getuid())
+ {
+ if (setreuid((uid_t)-1, 0) == -1)
+ { // this should never happen
+ sql_perror("setreuid");
+ unireg_abort(1);
+ }
+ if (mlockall(MCL_CURRENT))
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_warning("Failed to lock memory. Errno: %d\n",errno);
+ locked_in_memory= 0;
+ }
+ if (user_info)
+ set_user(mysqld_user, user_info);
+ }
+ else
+#endif
+ locked_in_memory=0;
+
+ ft_init_stopwords();
+
+ init_max_user_conn();
+ init_update_queries();
+ init_global_user_stats();
+ init_global_client_stats();
+ if (!opt_bootstrap)
+ servers_init(0);
+ init_status_vars();
+ DBUG_RETURN(0);
+}
+
+
+#ifndef EMBEDDED_LIBRARY
+
+static void create_shutdown_thread()
+{
+#ifdef __WIN__
+ hEventShutdown=CreateEvent(0, FALSE, FALSE, shutdown_event_name);
+ pthread_t hThread;
+ int error;
+ if ((error= mysql_thread_create(key_thread_handle_shutdown,
+ &hThread, &connection_attrib,
+ handle_shutdown, 0)))
+ sql_print_warning("Can't create thread to handle shutdown requests"
+ " (errno= %d)", error);
+
+ // On "Stop Service" we have to do regular shutdown
+ Service.SetShutdownEvent(hEventShutdown);
+#endif /* __WIN__ */
+}
+
+#endif /* EMBEDDED_LIBRARY */
+
+
+#if (defined(_WIN32) || defined(HAVE_SMEM)) && !defined(EMBEDDED_LIBRARY)
+static void handle_connections_methods()
+{
+ pthread_t hThread;
+ int error;
+ DBUG_ENTER("handle_connections_methods");
+ if (hPipe == INVALID_HANDLE_VALUE &&
+ (!have_tcpip || opt_disable_networking) &&
+ !opt_enable_shared_memory)
+ {
+ sql_print_error("TCP/IP, --shared-memory, or --named-pipe should be configured on NT OS");
+ unireg_abort(1); // Will not return
+ }
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ mysql_cond_init(key_COND_handler_count, &COND_handler_count, NULL);
+ handler_count=0;
+ if (hPipe != INVALID_HANDLE_VALUE)
+ {
+ handler_count++;
+ if ((error= mysql_thread_create(key_thread_handle_con_namedpipes,
+ &hThread, &connection_attrib,
+ handle_connections_namedpipes, 0)))
+ {
+ sql_print_warning("Can't create thread to handle named pipes"
+ " (errno= %d)", error);
+ handler_count--;
+ }
+ }
+ if (have_tcpip && !opt_disable_networking)
+ {
+ handler_count++;
+ if ((error= mysql_thread_create(key_thread_handle_con_sockets,
+ &hThread, &connection_attrib,
+ handle_connections_sockets_thread, 0)))
+ {
+ sql_print_warning("Can't create thread to handle TCP/IP",
+ " (errno= %d)", error);
+ handler_count--;
+ }
+ }
+#ifdef HAVE_SMEM
+ if (opt_enable_shared_memory)
+ {
+ handler_count++;
+ if ((error= mysql_thread_create(key_thread_handle_con_sharedmem,
+ &hThread, &connection_attrib,
+ handle_connections_shared_memory, 0)))
+ {
+ sql_print_warning("Can't create thread to handle shared memory",
+ " (errno= %d)", error);
+ handler_count--;
+ }
+ }
+#endif
+
+ while (handler_count > 0)
+ mysql_cond_wait(&COND_handler_count, &LOCK_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_VOID_RETURN;
+}
+
+void decrement_handler_count()
+{
+ mysql_mutex_lock(&LOCK_thread_count);
+ handler_count--;
+ mysql_cond_signal(&COND_handler_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ my_thread_end();
+}
+#else
+#define decrement_handler_count()
+#endif /* defined(_WIN32) || defined(HAVE_SMEM) */
+
+
+#ifndef EMBEDDED_LIBRARY
+
+LEX_STRING sql_statement_names[(uint) SQLCOM_END + 1];
+
+static void init_sql_statement_names()
+{
+ char *first_com= (char*) offsetof(STATUS_VAR, com_stat[0]);
+ char *last_com= (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_END]);
+ int record_size= (char*) offsetof(STATUS_VAR, com_stat[1])
+ - (char*) offsetof(STATUS_VAR, com_stat[0]);
+ char *ptr;
+ uint i;
+ uint com_index;
+
+ for (i= 0; i < ((uint) SQLCOM_END + 1); i++)
+ sql_statement_names[i]= empty_lex_str;
+
+ SHOW_VAR *var= &com_status_vars[0];
+ while (var->name != NULL)
+ {
+ ptr= var->value;
+ if ((first_com <= ptr) && (ptr <= last_com))
+ {
+ com_index= ((int)(ptr - first_com))/record_size;
+ DBUG_ASSERT(com_index < (uint) SQLCOM_END);
+ sql_statement_names[com_index].str= const_cast<char *>(var->name);
+ sql_statement_names[com_index].length= strlen(var->name);
+ }
+ var++;
+ }
+
+ DBUG_ASSERT(strcmp(sql_statement_names[(uint) SQLCOM_SELECT].str, "select") == 0);
+ DBUG_ASSERT(strcmp(sql_statement_names[(uint) SQLCOM_SIGNAL].str, "signal") == 0);
+
+ sql_statement_names[(uint) SQLCOM_END].str= const_cast<char*>("error");
+}
+
+#ifndef DBUG_OFF
+/*
+ Debugging helper function to keep the locale database
+ (see sql_locale.cc) and max_month_name_length and
+ max_day_name_length variable values in consistent state.
+*/
+static void test_lc_time_sz()
+{
+ DBUG_ENTER("test_lc_time_sz");
+ for (MY_LOCALE **loc= my_locales; *loc; loc++)
+ {
+ uint max_month_len= 0;
+ uint max_day_len = 0;
+ for (const char **month= (*loc)->month_names->type_names; *month; month++)
+ {
+ set_if_bigger(max_month_len,
+ my_numchars_mb(&my_charset_utf8_general_ci,
+ *month, *month + strlen(*month)));
+ }
+ for (const char **day= (*loc)->day_names->type_names; *day; day++)
+ {
+ set_if_bigger(max_day_len,
+ my_numchars_mb(&my_charset_utf8_general_ci,
+ *day, *day + strlen(*day)));
+ }
+ if ((*loc)->max_month_name_length != max_month_len ||
+ (*loc)->max_day_name_length != max_day_len)
+ {
+ DBUG_PRINT("Wrong max day name(or month name) length for locale:",
+ ("%s", (*loc)->name));
+ DBUG_ASSERT(0);
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+#endif//DBUG_OFF
+
+
+#ifdef __WIN__
+int win_main(int argc, char **argv)
+#else
+int mysqld_main(int argc, char **argv)
+#endif
+{
+ /*
+ Perform basic thread library and malloc initialization,
+ to be able to read defaults files and parse options.
+ */
+ my_progname= argv[0];
+ sf_leaking_memory= 1; // no safemalloc memory leak reports if we exit early
+ mysqld_server_started= mysqld_server_initialized= 0;
+
+#ifdef HAVE_NPTL
+ ld_assume_kernel_is_set= (getenv("LD_ASSUME_KERNEL") != 0);
+#endif
+#ifndef _WIN32
+ // For windows, my_init() is called from the win specific mysqld_main
+ if (my_init()) // init my_sys library & pthreads
+ {
+ fprintf(stderr, "my_init() failed.");
+ return 1;
+ }
+#endif
+
+ orig_argc= argc;
+ orig_argv= argv;
+ my_getopt_use_args_separator= TRUE;
+ if (load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv))
+ return 1;
+ my_getopt_use_args_separator= FALSE;
+ defaults_argc= argc;
+ defaults_argv= argv;
+ remaining_argc= argc;
+ remaining_argv= argv;
+
+ /* Must be initialized early for comparison of options name */
+ system_charset_info= &my_charset_utf8_general_ci;
+
+ init_sql_statement_names();
+ sys_var_init();
+
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ /*
+ Initialize the array of performance schema instrument configurations.
+ */
+ init_pfs_instrument_array();
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+ /*
+ Logs generated while parsing the command line
+ options are buffered and printed later.
+ */
+ buffered_logs.init();
+ my_getopt_error_reporter= buffered_option_error_reporter;
+ my_charset_error_reporter= buffered_option_error_reporter;
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ pfs_param.m_pfs_instrument= const_cast<char*>("");
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+
+ int ho_error __attribute__((unused))= handle_early_options();
+
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ if (ho_error == 0)
+ {
+ if (pfs_param.m_enabled && !opt_help && !opt_bootstrap)
+ {
+ /* Add sizing hints from the server sizing parameters. */
+ pfs_param.m_hints.m_table_definition_cache= tdc_size;
+ pfs_param.m_hints.m_table_open_cache= tc_size;
+ pfs_param.m_hints.m_max_connections= max_connections;
+ pfs_param.m_hints.m_open_files_limit= open_files_limit;
+ /* the performance schema digest size is the same as the SQL layer */
+ pfs_param.m_max_digest_length= max_digest_length;
+ PSI_hook= initialize_performance_schema(&pfs_param);
+ if (PSI_hook == NULL)
+ {
+ pfs_param.m_enabled= false;
+ buffered_logs.buffer(WARNING_LEVEL,
+ "Performance schema disabled (reason: init failed).");
+ }
+ }
+ }
+#else
+ /*
+ Other provider of the instrumentation interface should
+ initialize PSI_hook here:
+ - HAVE_PSI_INTERFACE is for the instrumentation interface
+ - WITH_PERFSCHEMA_STORAGE_ENGINE is for one implementation
+ of the interface,
+ but there could be alternate implementations, which is why
+ these two defines are kept separate.
+ */
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+
+#ifdef HAVE_PSI_INTERFACE
+ /*
+ Obtain the current performance schema instrumentation interface,
+ if available.
+ */
+ if (PSI_hook)
+ {
+ PSI *psi_server= (PSI*) PSI_hook->get_interface(PSI_CURRENT_VERSION);
+ if (likely(psi_server != NULL))
+ {
+ set_psi_server(psi_server);
+
+ /*
+ Now that we have parsed the command line arguments, and have
+ initialized the performance schema itself, the next step is to
+ register all the server instruments.
+ */
+ init_server_psi_keys();
+ /* Instrument the main thread */
+ PSI_thread *psi= PSI_THREAD_CALL(new_thread)(key_thread_main, NULL, 0);
+ PSI_THREAD_CALL(set_thread)(psi);
+
+ /*
+ Now that some instrumentation is in place,
+ recreate objects which were initialised early,
+ so that they are instrumented as well.
+ */
+ my_thread_global_reinit();
+ }
+ }
+#endif /* HAVE_PSI_INTERFACE */
+
+ init_error_log_mutex();
+
+ /* Initialize audit interface globals. Audit plugins are inited later. */
+ mysql_audit_initialize();
+
+ /*
+ Perform basic logger initialization logger. Should be called after
+ MY_INIT, as it initializes mutexes. Log tables are inited later.
+ */
+ logger.init_base();
+
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ if (ho_error)
+ {
+ /*
+ Parsing command line option failed,
+ Since we don't have a workable remaining_argc/remaining_argv
+ to continue the server initialization, this is as far as this
+ code can go.
+ This is the best effort to log meaningful messages:
+ - messages will be printed to stderr, which is not redirected yet,
+ - messages will be printed in the NT event log, for windows.
+ */
+ buffered_logs.print();
+ buffered_logs.cleanup();
+ /*
+ Not enough initializations for unireg_abort()
+ Using exit() for windows.
+ */
+ exit (ho_error);
+ }
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+
+#ifdef _CUSTOMSTARTUPCONFIG_
+ if (_cust_check_startup())
+ {
+ / * _cust_check_startup will report startup failure error * /
+ exit(1);
+ }
+#endif
+
+ if (init_common_variables())
+ unireg_abort(1); // Will do exit
+
+ init_signals();
+
+ my_thread_stack_size= my_setstacksize(&connection_attrib,
+ my_thread_stack_size);
+
+ (void) thr_setconcurrency(concurrency); // 10 by default
+
+ select_thread=pthread_self();
+ select_thread_in_use=1;
+
+#ifdef HAVE_LIBWRAP
+ libwrapName= my_progname+dirname_length(my_progname);
+ openlog(libwrapName, LOG_PID, LOG_AUTH);
+#endif
+
+#ifndef DBUG_OFF
+ test_lc_time_sz();
+ srand((uint) time(NULL));
+#endif
+
+ /*
+ We have enough space for fiddling with the argv, continue
+ */
+ check_data_home(mysql_real_data_home);
+ if (my_setwd(mysql_real_data_home, opt_abort ? 0 : MYF(MY_WME)) && !opt_abort)
+ unireg_abort(1); /* purecov: inspected */
+
+ if ((user_info= check_user(mysqld_user)))
+ {
+#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT)
+ if (locked_in_memory) // getuid() == 0 here
+ set_effective_user(user_info);
+ else
+#endif
+ set_user(mysqld_user, user_info);
+ }
+
+ if (opt_bin_log && !global_system_variables.server_id)
+ {
+ global_system_variables.server_id= ::server_id= 1;
+#ifdef EXTRA_DEBUG
+ sql_print_warning("You have enabled the binary log, but you haven't set "
+ "server-id to a non-zero value: we force server id to 1; "
+ "updates will be logged to the binary log, but "
+ "connections from slaves will not be accepted.");
+#endif
+ }
+
+ /*
+ The subsequent calls may take a long time : e.g. innodb log read.
+ Thus set the long running service control manager timeout
+ */
+#if defined(_WIN32) && !defined(EMBEDDED_LIBRARY)
+ Service.SetSlowStarting(slow_start_timeout);
+#endif
+
+ if (init_server_components())
+ unireg_abort(1);
+
+ init_ssl();
+ network_init();
+
+#ifdef __WIN__
+ if (!opt_console)
+ {
+ if (reopen_fstreams(log_error_file, stdout, stderr))
+ unireg_abort(1);
+ setbuf(stderr, NULL);
+ FreeConsole(); // Remove window
+ }
+#endif
+
+ /*
+ init signals & alarm
+ After this we can't quit by a simple unireg_abort
+ */
+ start_signal_handler(); // Creates pidfile
+
+ if (mysql_rm_tmp_tables() || acl_init(opt_noacl) ||
+ my_tz_init((THD *)0, default_tz_name, opt_bootstrap))
+ {
+ abort_loop=1;
+ select_thread_in_use=0;
+
+ (void) pthread_kill(signal_thread, MYSQL_KILL_SIGNAL);
+
+ delete_pid_file(MYF(MY_WME));
+
+ if (mysql_socket_getfd(unix_sock) != INVALID_SOCKET)
+ unlink(mysqld_unix_port);
+ exit(1);
+ }
+
+ if (!opt_noacl)
+ (void) grant_init();
+
+ if (!opt_noacl)
+ {
+#ifdef HAVE_DLOPEN
+ udf_init();
+#endif
+ }
+
+ if (opt_bootstrap) /* If running with bootstrap, do not start replication. */
+ opt_skip_slave_start= 1;
+
+ binlog_unsafe_map_init();
+
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+ initialize_performance_schema_acl(opt_bootstrap);
+ /*
+ Do not check the structure of the performance schema tables
+ during bootstrap:
+ - the tables are not supposed to exist yet, bootstrap will create them
+ - a check would print spurious error messages
+ */
+ if (! opt_bootstrap)
+ check_performance_schema();
+#endif
+
+ initialize_information_schema_acl();
+
+ execute_ddl_log_recovery();
+
+ if (Events::init(opt_noacl || opt_bootstrap))
+ unireg_abort(1);
+
+ if (opt_bootstrap)
+ {
+ select_thread_in_use= 0; // Allow 'kill' to work
+ bootstrap(mysql_stdin);
+ if (!kill_in_progress)
+ unireg_abort(bootstrap_error ? 1 : 0);
+ else
+ {
+ sleep(2); // Wait for kill
+ exit(0);
+ }
+ }
+
+ /* It's now safe to use thread specific memory */
+ mysqld_server_initialized= 1;
+
+ create_shutdown_thread();
+ start_handle_manager();
+
+ /* Copy default global rpl_filter to global_rpl_filter */
+ copy_filter_setting(global_rpl_filter, get_or_create_rpl_filter("", 0));
+
+ /*
+ init_slave() must be called after the thread keys are created.
+ Some parts of the code (e.g. SHOW STATUS LIKE 'slave_running' and other
+ places) assume that active_mi != 0, so let's fail if it's 0 (out of
+ memory); a message has already been printed.
+ */
+ if (init_slave() && !active_mi)
+ {
+ unireg_abort(1);
+ }
+
+ if (opt_init_file && *opt_init_file)
+ {
+ if (read_init_file(opt_init_file))
+ unireg_abort(1);
+ }
+
+ sql_print_information(ER_DEFAULT(ER_STARTUP),my_progname,server_version,
+ ((mysql_socket_getfd(unix_sock) == INVALID_SOCKET) ?
+ (char*) "" : mysqld_unix_port),
+ mysqld_port,
+ MYSQL_COMPILATION_COMMENT);
+ fclose(stdin);
+#if defined(_WIN32) && !defined(EMBEDDED_LIBRARY)
+ Service.SetRunning();
+#endif
+
+ /* Signal threads waiting for server to be started */
+ mysql_mutex_lock(&LOCK_server_started);
+ mysqld_server_started= 1;
+ mysql_cond_signal(&COND_server_started);
+ mysql_mutex_unlock(&LOCK_server_started);
+
+#if defined(_WIN32) || defined(HAVE_SMEM)
+ handle_connections_methods();
+#else
+ handle_connections_sockets();
+#endif /* _WIN32 || HAVE_SMEM */
+
+ /* (void) pthread_attr_destroy(&connection_attrib); */
+
+ DBUG_PRINT("quit",("Exiting main thread"));
+
+#ifndef __WIN__
+#ifdef EXTRA_DEBUG2
+ sql_print_error("Before Lock_thread_count");
+#endif
+ mysql_mutex_lock(&LOCK_thread_count);
+ DBUG_PRINT("quit", ("Got thread_count mutex"));
+ select_thread_in_use=0; // For close_connections
+ mysql_mutex_unlock(&LOCK_thread_count);
+ mysql_cond_broadcast(&COND_thread_count);
+#ifdef EXTRA_DEBUG2
+ sql_print_error("After lock_thread_count");
+#endif
+#endif /* __WIN__ */
+
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ /*
+ Disable the main thread instrumentation,
+ to avoid recording events during the shutdown.
+ */
+ PSI_THREAD_CALL(delete_current_thread)();
+#endif
+
+ /* Wait until cleanup is done */
+ mysql_mutex_lock(&LOCK_thread_count);
+ while (!ready_to_exit)
+ mysql_cond_wait(&COND_thread_count, &LOCK_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+#if defined(__WIN__) && !defined(EMBEDDED_LIBRARY)
+ if (Service.IsNT() && start_mode)
+ Service.Stop();
+ else
+ {
+ Service.SetShutdownEvent(0);
+ if (hEventShutdown)
+ CloseHandle(hEventShutdown);
+ }
+#endif
+ mysqld_exit(0);
+ return 0;
+}
+
+#endif /* !EMBEDDED_LIBRARY */
+
+
+/****************************************************************************
+ Main and thread entry function for Win32
+ (all this is needed only to run mysqld as a service on WinNT)
+****************************************************************************/
+
+#if defined(__WIN__) && !defined(EMBEDDED_LIBRARY)
+int mysql_service(void *p)
+{
+ if (my_thread_init())
+ return 1;
+
+ if (use_opt_args)
+ win_main(opt_argc, opt_argv);
+ else
+ win_main(Service.my_argc, Service.my_argv);
+
+ my_thread_end();
+ return 0;
+}
+
+
+/* Quote string if it contains space, else copy */
+
+static char *add_quoted_string(char *to, const char *from, char *to_end)
+{
+ uint length= (uint) (to_end-to);
+
+ if (!strchr(from, ' '))
+ return strmake(to, from, length-1);
+ return strxnmov(to, length-1, "\"", from, "\"", NullS);
+}
+
+
+/**
+ Handle basic handling of services, like installation and removal.
+
+ @param argv Pointer to argument list
+ @param servicename Internal name of service
+ @param displayname Display name of service (in taskbar ?)
+ @param file_path Path to this program
+ @param startup_option Startup option to mysqld
+
+ @retval 0 option handled
+ @retval 1 Could not handle option
+*/
+
+static bool
+default_service_handling(char **argv,
+ const char *servicename,
+ const char *displayname,
+ const char *file_path,
+ const char *extra_opt,
+ const char *account_name)
+{
+ char path_and_service[FN_REFLEN+FN_REFLEN+32], *pos, *end;
+ const char *opt_delim;
+ end= path_and_service + sizeof(path_and_service)-3;
+
+ /* We have to quote filename if it contains spaces */
+ pos= add_quoted_string(path_and_service, file_path, end);
+ if (extra_opt && *extra_opt)
+ {
+ /*
+ Add option after file_path. There will be zero or one extra option. It's
+ assumed to be --defaults-file=file but isn't checked. The variable (not
+ the option name) should be quoted if it contains a string.
+ */
+ *pos++= ' ';
+ if (opt_delim= strchr(extra_opt, '='))
+ {
+ size_t length= ++opt_delim - extra_opt;
+ pos= strnmov(pos, extra_opt, length);
+ }
+ else
+ opt_delim= extra_opt;
+
+ pos= add_quoted_string(pos, opt_delim, end);
+ }
+ /* We must have servicename last */
+ *pos++= ' ';
+ (void) add_quoted_string(pos, servicename, end);
+
+ if (Service.got_service_option(argv, "install"))
+ {
+ Service.Install(1, servicename, displayname, path_and_service,
+ account_name);
+ return 0;
+ }
+ if (Service.got_service_option(argv, "install-manual"))
+ {
+ Service.Install(0, servicename, displayname, path_and_service,
+ account_name);
+ return 0;
+ }
+ if (Service.got_service_option(argv, "remove"))
+ {
+ Service.Remove(servicename);
+ return 0;
+ }
+ return 1;
+}
+
+
+int mysqld_main(int argc, char **argv)
+{
+ my_progname= argv[0];
+
+ /*
+ When several instances are running on the same machine, we
+ need to have an unique named hEventShudown through the
+ application PID e.g.: MySQLShutdown1890; MySQLShutdown2342
+ */
+ int10_to_str((int) GetCurrentProcessId(),strmov(shutdown_event_name,
+ "MySQLShutdown"), 10);
+
+ /* Must be initialized early for comparison of service name */
+ system_charset_info= &my_charset_utf8_general_ci;
+
+ if (my_init())
+ {
+ fprintf(stderr, "my_init() failed.");
+ return 1;
+ }
+
+ if (Service.GetOS()) /* true NT family */
+ {
+ char file_path[FN_REFLEN];
+ my_path(file_path, argv[0], ""); /* Find name in path */
+ fn_format(file_path,argv[0],file_path,"",
+ MY_REPLACE_DIR | MY_UNPACK_FILENAME | MY_RESOLVE_SYMLINKS);
+
+ if (argc == 2)
+ {
+ if (!default_service_handling(argv, MYSQL_SERVICENAME, MYSQL_SERVICENAME,
+ file_path, "", NULL))
+ return 0;
+ if (Service.IsService(argv[1])) /* Start an optional service */
+ {
+ /*
+ Only add the service name to the groups read from the config file
+ if it's not "MySQL". (The default service name should be 'mysqld'
+ but we started a bad tradition by calling it MySQL from the start
+ and we are now stuck with it.
+ */
+ if (my_strcasecmp(system_charset_info, argv[1],"mysql"))
+ load_default_groups[load_default_groups_sz-2]= argv[1];
+ start_mode= 1;
+ Service.Init(argv[1], mysql_service);
+ return 0;
+ }
+ }
+ else if (argc == 3) /* install or remove any optional service */
+ {
+ if (!default_service_handling(argv, argv[2], argv[2], file_path, "",
+ NULL))
+ return 0;
+ if (Service.IsService(argv[2]))
+ {
+ /*
+ mysqld was started as
+ mysqld --defaults-file=my_path\my.ini service-name
+ */
+ use_opt_args=1;
+ opt_argc= 2; // Skip service-name
+ opt_argv=argv;
+ start_mode= 1;
+ if (my_strcasecmp(system_charset_info, argv[2],"mysql"))
+ load_default_groups[load_default_groups_sz-2]= argv[2];
+ Service.Init(argv[2], mysql_service);
+ return 0;
+ }
+ }
+ else if (argc == 4 || argc == 5)
+ {
+ /*
+ This may seem strange, because we handle --local-service while
+ preserving 4.1's behavior of allowing any one other argument that is
+ passed to the service on startup. (The assumption is that this is
+ --defaults-file=file, but that was not enforced in 4.1, so we don't
+ enforce it here.)
+ */
+ const char *extra_opt= NullS;
+ const char *account_name = NullS;
+ int index;
+ for (index = 3; index < argc; index++)
+ {
+ if (!strcmp(argv[index], "--local-service"))
+ account_name= "NT AUTHORITY\\LocalService";
+ else
+ extra_opt= argv[index];
+ }
+
+ if (argc == 4 || account_name)
+ if (!default_service_handling(argv, argv[2], argv[2], file_path,
+ extra_opt, account_name))
+ return 0;
+ }
+ else if (argc == 1 && Service.IsService(MYSQL_SERVICENAME))
+ {
+ /* start the default service */
+ start_mode= 1;
+ Service.Init(MYSQL_SERVICENAME, mysql_service);
+ return 0;
+ }
+ }
+ /* Start as standalone server */
+ Service.my_argc=argc;
+ Service.my_argv=argv;
+ mysql_service(NULL);
+ return 0;
+}
+#endif
+
+
+/**
+ Execute all commands from a file. Used by the mysql_install_db script to
+ create MySQL privilege tables without having to start a full MySQL server.
+*/
+
+static void bootstrap(MYSQL_FILE *file)
+{
+ DBUG_ENTER("bootstrap");
+
+ THD *thd= new THD;
+ thd->bootstrap=1;
+ my_net_init(&thd->net,(st_vio*) 0, MYF(0));
+ thd->max_client_packet_length= thd->net.max_packet;
+ thd->security_ctx->master_access= ~(ulong)0;
+ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
+ thread_count++; // Safe as only one thread running
+ in_bootstrap= TRUE;
+
+ bootstrap_file=file;
+#ifndef EMBEDDED_LIBRARY // TODO: Enable this
+ int error;
+ if ((error= mysql_thread_create(key_thread_bootstrap,
+ &thd->real_id, &connection_attrib,
+ handle_bootstrap,
+ (void*) thd)))
+ {
+ sql_print_warning("Can't create thread to handle bootstrap (errno= %d)",
+ error);
+ bootstrap_error=-1;
+ DBUG_VOID_RETURN;
+ }
+ /* Wait for thread to die */
+ mysql_mutex_lock(&LOCK_thread_count);
+ while (in_bootstrap)
+ {
+ mysql_cond_wait(&COND_thread_count, &LOCK_thread_count);
+ DBUG_PRINT("quit",("One thread died (count=%u)",thread_count));
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+#else
+ thd->mysql= 0;
+ do_handle_bootstrap(thd);
+#endif
+
+ DBUG_VOID_RETURN;
+}
+
+
+static bool read_init_file(char *file_name)
+{
+ MYSQL_FILE *file;
+ DBUG_ENTER("read_init_file");
+ DBUG_PRINT("enter",("name: %s",file_name));
+ if (!(file= mysql_file_fopen(key_file_init, file_name,
+ O_RDONLY, MYF(MY_WME))))
+ DBUG_RETURN(TRUE);
+ bootstrap(file);
+ mysql_file_fclose(file, MYF(MY_WME));
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Increment number of created threads
+*/
+void inc_thread_created(void)
+{
+ thread_created++;
+}
+
+#ifndef EMBEDDED_LIBRARY
+
+/*
+ Simple scheduler that use the main thread to handle the request
+
+ NOTES
+ This is only used for debugging, when starting mysqld with
+ --thread-handling=no-threads or --one-thread
+
+ When we enter this function, LOCK_thread_count is hold!
+*/
+
+void handle_connection_in_main_thread(THD *thd)
+{
+ mysql_mutex_assert_owner(&LOCK_thread_count);
+ thread_cache_size=0; // Safety
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ thd->start_utime= microsecond_interval_timer();
+ do_handle_one_connection(thd);
+}
+
+
+/*
+ Scheduler that uses one thread per connection
+*/
+
+void create_thread_to_handle_connection(THD *thd)
+{
+ DBUG_ENTER("create_thread_to_handle_connection");
+ mysql_mutex_assert_owner(&LOCK_thread_count);
+
+ /* Check if we can get thread from the cache */
+ if (cached_thread_count > wake_thread)
+ {
+ mysql_mutex_lock(&LOCK_thread_cache);
+ /* Recheck condition when we have the lock */
+ if (cached_thread_count > wake_thread)
+ {
+ mysql_mutex_unlock(&LOCK_thread_count);
+ /* Get thread from cache */
+ thread_cache.push_back(thd);
+ wake_thread++;
+ mysql_cond_signal(&COND_thread_cache);
+ mysql_mutex_unlock(&LOCK_thread_cache);
+ DBUG_PRINT("info",("Thread created"));
+ DBUG_VOID_RETURN;
+ }
+ mysql_mutex_unlock(&LOCK_thread_cache);
+ }
+
+ char error_message_buff[MYSQL_ERRMSG_SIZE];
+ /* Create new thread to handle connection */
+ int error;
+ thread_created++;
+ threads.append(thd);
+ DBUG_PRINT("info",(("creating thread %lu"), thd->thread_id));
+ thd->prior_thr_create_utime= microsecond_interval_timer();
+ if ((error= mysql_thread_create(key_thread_one_connection,
+ &thd->real_id, &connection_attrib,
+ handle_one_connection,
+ (void*) thd)))
+ {
+ /* purecov: begin inspected */
+ DBUG_PRINT("error",
+ ("Can't create thread to handle request (error %d)",
+ error));
+ thd->killed= KILL_CONNECTION; // Safety
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ mysql_mutex_lock(&LOCK_connection_count);
+ (*thd->scheduler->connection_count)--;
+ mysql_mutex_unlock(&LOCK_connection_count);
+
+ statistic_increment(aborted_connects,&LOCK_status);
+ statistic_increment(connection_errors_internal, &LOCK_status);
+ /* Can't use my_error() since store_globals has not been called. */
+ my_snprintf(error_message_buff, sizeof(error_message_buff),
+ ER_THD(thd, ER_CANT_CREATE_THREAD), error);
+ net_send_error(thd, ER_CANT_CREATE_THREAD, error_message_buff, NULL);
+ close_connection(thd, ER_OUT_OF_RESOURCES);
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->unlink();
+ mysql_mutex_unlock(&LOCK_thread_count);
+ delete thd;
+ thread_safe_decrement32(&thread_count, &thread_count_lock);
+ return;
+ /* purecov: end */
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_PRINT("info",("Thread created"));
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Create new thread to handle incoming connection.
+
+ This function will create new thread to handle the incoming
+ connection. If there are idle cached threads one will be used.
+ 'thd' will be pushed into 'threads'.
+
+ In single-threaded mode (\#define ONE_THREAD) connection will be
+ handled inside this function.
+
+ @param[in,out] thd Thread handle of future thread.
+*/
+
+static void create_new_thread(THD *thd)
+{
+ DBUG_ENTER("create_new_thread");
+
+ /*
+ Don't allow too many connections. We roughly check here that we allow
+ only (max_connections + 1) connections.
+ */
+
+ mysql_mutex_lock(&LOCK_connection_count);
+
+ if (*thd->scheduler->connection_count >=
+ *thd->scheduler->max_connections + 1|| abort_loop)
+ {
+ mysql_mutex_unlock(&LOCK_connection_count);
+
+ DBUG_PRINT("error",("Too many connections"));
+ close_connection(thd, ER_CON_COUNT_ERROR);
+ statistic_increment(denied_connections, &LOCK_status);
+ delete thd;
+ statistic_increment(connection_errors_max_connection, &LOCK_status);
+ DBUG_VOID_RETURN;
+ }
+
+ ++*thd->scheduler->connection_count;
+
+ if (connection_count + extra_connection_count > max_used_connections)
+ max_used_connections= connection_count + extra_connection_count;
+
+ mysql_mutex_unlock(&LOCK_connection_count);
+
+ thread_safe_increment32(&thread_count, &thread_count_lock);
+
+ /* Start a new thread to handle connection. */
+ mysql_mutex_lock(&LOCK_thread_count);
+ /*
+ The initialization of thread_id is done in create_embedded_thd() for
+ the embedded library.
+ TODO: refactor this to avoid code duplication there
+ */
+ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
+
+ MYSQL_CALLBACK(thd->scheduler, add_connection, (thd));
+
+ DBUG_VOID_RETURN;
+}
+#endif /* EMBEDDED_LIBRARY */
+
+
+#ifdef SIGNALS_DONT_BREAK_READ
+inline void kill_broken_server()
+{
+ /* hack to get around signals ignored in syscalls for problem OS's */
+ if (mysql_socket_getfd(unix_sock) == INVALID_SOCKET ||
+ (!opt_disable_networking &&
+ mysql_socket_getfd(base_ip_sock) == INVALID_SOCKET))
+ {
+ select_thread_in_use = 0;
+ /* The following call will never return */
+ DBUG_PRINT("general", ("killing server because socket is closed"));
+ kill_server((void*) MYSQL_KILL_SIGNAL);
+ }
+}
+#define MAYBE_BROKEN_SYSCALL kill_broken_server();
+#else
+#define MAYBE_BROKEN_SYSCALL
+#endif
+
+ /* Handle new connections and spawn new process to handle them */
+
+#ifndef EMBEDDED_LIBRARY
+
+void handle_connections_sockets()
+{
+ MYSQL_SOCKET sock= mysql_socket_invalid();
+ MYSQL_SOCKET new_sock= mysql_socket_invalid();
+ uint error_count=0;
+ THD *thd;
+ struct sockaddr_storage cAddr;
+ int ip_flags __attribute__((unused))=0;
+ int socket_flags __attribute__((unused))= 0;
+ int extra_ip_flags __attribute__((unused))=0;
+ int flags=0,retval;
+ st_vio *vio_tmp;
+ bool is_unix_sock;
+#ifdef HAVE_POLL
+ int socket_count= 0;
+ struct pollfd fds[3]; // for ip_sock, unix_sock and extra_ip_sock
+ MYSQL_SOCKET pfs_fds[3]; // for performance schema
+#define setup_fds(X) \
+ mysql_socket_set_thread_owner(X); \
+ pfs_fds[socket_count]= (X); \
+ fds[socket_count].fd= mysql_socket_getfd(X); \
+ fds[socket_count].events= POLLIN; \
+ socket_count++
+#else
+#define setup_fds(X) FD_SET(mysql_socket_getfd(X),&clientFDs)
+ fd_set readFDs,clientFDs;
+ FD_ZERO(&clientFDs);
+#endif
+
+ DBUG_ENTER("handle_connections_sockets");
+
+ if (mysql_socket_getfd(base_ip_sock) != INVALID_SOCKET)
+ {
+ setup_fds(base_ip_sock);
+ ip_flags = fcntl(mysql_socket_getfd(base_ip_sock), F_GETFL, 0);
+ }
+ if (mysql_socket_getfd(extra_ip_sock) != INVALID_SOCKET)
+ {
+ setup_fds(extra_ip_sock);
+ extra_ip_flags = fcntl(mysql_socket_getfd(extra_ip_sock), F_GETFL, 0);
+ }
+#ifdef HAVE_SYS_UN_H
+ setup_fds(unix_sock);
+ socket_flags=fcntl(mysql_socket_getfd(unix_sock), F_GETFL, 0);
+#endif
+
+ DBUG_PRINT("general",("Waiting for connections."));
+ MAYBE_BROKEN_SYSCALL;
+ while (!abort_loop)
+ {
+#ifdef HAVE_POLL
+ retval= poll(fds, socket_count, -1);
+#else
+ readFDs=clientFDs;
+ retval= select((int) 0,&readFDs,0,0,0);
+#endif
+
+ if (retval < 0)
+ {
+ if (socket_errno != SOCKET_EINTR)
+ {
+ /*
+ select(2)/poll(2) failed on the listening port.
+ There is not much details to report about the client,
+ increment the server global status variable.
+ */
+ statistic_increment(connection_errors_accept, &LOCK_status);
+ if (!select_errors++ && !abort_loop) /* purecov: inspected */
+ sql_print_error("mysqld: Got error %d from select",socket_errno); /* purecov: inspected */
+ }
+ MAYBE_BROKEN_SYSCALL
+ continue;
+ }
+
+ if (abort_loop)
+ {
+ MAYBE_BROKEN_SYSCALL;
+ break;
+ }
+
+ /* Is this a new connection request ? */
+#ifdef HAVE_POLL
+ for (int i= 0; i < socket_count; ++i)
+ {
+ if (fds[i].revents & POLLIN)
+ {
+ sock= pfs_fds[i];
+ flags= fcntl(mysql_socket_getfd(sock), F_GETFL, 0);
+ break;
+ }
+ }
+#else // HAVE_POLL
+ if (FD_ISSET(mysql_socket_getfd(base_ip_sock),&readFDs))
+ {
+ sock= base_ip_sock;
+ flags= ip_flags;
+ }
+ else
+ if (FD_ISSET(mysql_socket_getfd(extra_ip_sock),&readFDs))
+ {
+ sock= extra_ip_sock;
+ flags= extra_ip_flags;
+ }
+ else
+ {
+ sock = unix_sock;
+ flags= socket_flags;
+ }
+#endif // HAVE_POLL
+
+#if !defined(NO_FCNTL_NONBLOCK)
+ if (!(test_flags & TEST_BLOCKING))
+ {
+#if defined(O_NONBLOCK)
+ fcntl(mysql_socket_getfd(sock), F_SETFL, flags | O_NONBLOCK);
+#elif defined(O_NDELAY)
+ fcntl(mysql_socket_getfd(sock), F_SETFL, flags | O_NDELAY);
+#endif
+ }
+#endif /* NO_FCNTL_NONBLOCK */
+ for (uint retry=0; retry < MAX_ACCEPT_RETRY; retry++)
+ {
+ size_socket length= sizeof(struct sockaddr_storage);
+ new_sock= mysql_socket_accept(key_socket_client_connection, sock,
+ (struct sockaddr *)(&cAddr),
+ &length);
+ if (mysql_socket_getfd(new_sock) != INVALID_SOCKET ||
+ (socket_errno != SOCKET_EINTR && socket_errno != SOCKET_EAGAIN))
+ break;
+ MAYBE_BROKEN_SYSCALL;
+#if !defined(NO_FCNTL_NONBLOCK)
+ if (!(test_flags & TEST_BLOCKING))
+ {
+ if (retry == MAX_ACCEPT_RETRY - 1)
+ {
+ // Try without O_NONBLOCK
+ fcntl(mysql_socket_getfd(sock), F_SETFL, flags);
+ }
+ }
+#endif
+ }
+#if !defined(NO_FCNTL_NONBLOCK)
+ if (!(test_flags & TEST_BLOCKING))
+ fcntl(mysql_socket_getfd(sock), F_SETFL, flags);
+#endif
+ if (mysql_socket_getfd(new_sock) == INVALID_SOCKET)
+ {
+ /*
+ accept(2) failed on the listening port, after many retries.
+ There is not much details to report about the client,
+ increment the server global status variable.
+ */
+ statistic_increment(connection_errors_accept, &LOCK_status);
+ if ((error_count++ & 255) == 0) // This can happen often
+ sql_perror("Error in accept");
+ MAYBE_BROKEN_SYSCALL;
+ if (socket_errno == SOCKET_ENFILE || socket_errno == SOCKET_EMFILE)
+ sleep(1); // Give other threads some time
+ continue;
+ }
+
+#ifdef HAVE_LIBWRAP
+ {
+ if (mysql_socket_getfd(sock) == mysql_socket_getfd(base_ip_sock) ||
+ mysql_socket_getfd(sock) == mysql_socket_getfd(extra_ip_sock))
+ {
+ struct request_info req;
+ signal(SIGCHLD, SIG_DFL);
+ request_init(&req, RQ_DAEMON, libwrapName, RQ_FILE,
+ mysql_socket_getfd(new_sock), NULL);
+ my_fromhost(&req);
+ if (!my_hosts_access(&req))
+ {
+ /*
+ This may be stupid but refuse() includes an exit(0)
+ which we surely don't want...
+ clean_exit() - same stupid thing ...
+ */
+ syslog(deny_severity, "refused connect from %s",
+ my_eval_client(&req));
+
+ /*
+ C++ sucks (the gibberish in front just translates the supplied
+ sink function pointer in the req structure from a void (*sink)();
+ to a void(*sink)(int) if you omit the cast, the C++ compiler
+ will cry...
+ */
+ if (req.sink)
+ ((void (*)(int))req.sink)(req.fd);
+
+ (void) mysql_socket_shutdown(new_sock, SHUT_RDWR);
+ (void) mysql_socket_close(new_sock);
+ /*
+ The connection was refused by TCP wrappers.
+ There are no details (by client IP) available to update the
+ host_cache.
+ */
+ statistic_increment(connection_errors_tcpwrap, &LOCK_status);
+ continue;
+ }
+ }
+ }
+#endif /* HAVE_LIBWRAP */
+
+ /*
+ ** Don't allow too many connections
+ */
+
+ DBUG_PRINT("info", ("Creating THD for new connection"));
+ if (!(thd= new THD))
+ {
+ (void) mysql_socket_shutdown(new_sock, SHUT_RDWR);
+ (void) mysql_socket_close(new_sock);
+ statistic_increment(connection_errors_internal, &LOCK_status);
+ continue;
+ }
+ /* Set to get io buffers to be part of THD */
+ set_current_thd(thd);
+
+ is_unix_sock= (mysql_socket_getfd(sock) ==
+ mysql_socket_getfd(unix_sock));
+
+ if (!(vio_tmp=
+ mysql_socket_vio_new(new_sock,
+ is_unix_sock ? VIO_TYPE_SOCKET : VIO_TYPE_TCPIP,
+ is_unix_sock ? VIO_LOCALHOST: 0)) ||
+ my_net_init(&thd->net, vio_tmp, MYF(MY_THREAD_SPECIFIC)))
+ {
+ /*
+ Only delete the temporary vio if we didn't already attach it to the
+ NET object. The destructor in THD will delete any initialized net
+ structure.
+ */
+ if (vio_tmp && thd->net.vio != vio_tmp)
+ vio_delete(vio_tmp);
+ else
+ {
+ (void) mysql_socket_shutdown(new_sock, SHUT_RDWR);
+ (void) mysql_socket_close(new_sock);
+ }
+ delete thd;
+ set_current_thd(0);
+ statistic_increment(connection_errors_internal, &LOCK_status);
+ continue;
+ }
+
+ init_net_server_extension(thd);
+ if (is_unix_sock)
+ thd->security_ctx->host=(char*) my_localhost;
+
+ if (mysql_socket_getfd(sock) == mysql_socket_getfd(extra_ip_sock))
+ {
+ thd->extra_port= 1;
+ thd->scheduler= extra_thread_scheduler;
+ }
+ create_new_thread(thd);
+ set_current_thd(0);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+#ifdef _WIN32
+pthread_handler_t handle_connections_sockets_thread(void *arg)
+{
+ my_thread_init();
+ handle_connections_sockets();
+ decrement_handler_count();
+ return 0;
+}
+
+pthread_handler_t handle_connections_namedpipes(void *arg)
+{
+ HANDLE hConnectedPipe;
+ OVERLAPPED connectOverlapped= {0};
+ THD *thd;
+ my_thread_init();
+ DBUG_ENTER("handle_connections_namedpipes");
+ connectOverlapped.hEvent= CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!connectOverlapped.hEvent)
+ {
+ sql_print_error("Can't create event, last error=%u", GetLastError());
+ unireg_abort(1);
+ }
+ DBUG_PRINT("general",("Waiting for named pipe connections."));
+ while (!abort_loop)
+ {
+ /* wait for named pipe connection */
+ BOOL fConnected= ConnectNamedPipe(hPipe, &connectOverlapped);
+ if (!fConnected && (GetLastError() == ERROR_IO_PENDING))
+ {
+ /*
+ ERROR_IO_PENDING says async IO has started but not yet finished.
+ GetOverlappedResult will wait for completion.
+ */
+ DWORD bytes;
+ fConnected= GetOverlappedResult(hPipe, &connectOverlapped,&bytes, TRUE);
+ }
+ if (abort_loop)
+ break;
+ if (!fConnected)
+ fConnected = GetLastError() == ERROR_PIPE_CONNECTED;
+ if (!fConnected)
+ {
+ CloseHandle(hPipe);
+ if ((hPipe= CreateNamedPipe(pipe_name,
+ PIPE_ACCESS_DUPLEX |
+ FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE |
+ PIPE_READMODE_BYTE |
+ PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES,
+ (int) global_system_variables.
+ net_buffer_length,
+ (int) global_system_variables.
+ net_buffer_length,
+ NMPWAIT_USE_DEFAULT_WAIT,
+ &saPipeSecurity)) ==
+ INVALID_HANDLE_VALUE)
+ {
+ sql_perror("Can't create new named pipe!");
+ break; // Abort
+ }
+ }
+ hConnectedPipe = hPipe;
+ /* create new pipe for new connection */
+ if ((hPipe = CreateNamedPipe(pipe_name,
+ PIPE_ACCESS_DUPLEX |
+ FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE |
+ PIPE_READMODE_BYTE |
+ PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES,
+ (int) global_system_variables.net_buffer_length,
+ (int) global_system_variables.net_buffer_length,
+ NMPWAIT_USE_DEFAULT_WAIT,
+ &saPipeSecurity)) ==
+ INVALID_HANDLE_VALUE)
+ {
+ sql_perror("Can't create new named pipe!");
+ hPipe=hConnectedPipe;
+ continue; // We have to try again
+ }
+
+ if (!(thd = new THD))
+ {
+ DisconnectNamedPipe(hConnectedPipe);
+ CloseHandle(hConnectedPipe);
+ continue;
+ }
+ set_current_thd(thd);
+ if (!(thd->net.vio= vio_new_win32pipe(hConnectedPipe)) ||
+ my_net_init(&thd->net, thd->net.vio, MYF(MY_THREAD_SPECIFIC)))
+ {
+ close_connection(thd, ER_OUT_OF_RESOURCES);
+ delete thd;
+ set_current_thd(0);
+ continue;
+ }
+ /* Host is unknown */
+ thd->security_ctx->host= my_strdup(my_localhost, MYF(0));
+ create_new_thread(thd);
+ set_current_thd(0);
+ }
+ CloseHandle(connectOverlapped.hEvent);
+ DBUG_LEAVE;
+ decrement_handler_count();
+ return 0;
+}
+#endif /* _WIN32 */
+
+
+#ifdef HAVE_SMEM
+
+/**
+ Thread of shared memory's service.
+
+ @param arg Arguments of thread
+*/
+pthread_handler_t handle_connections_shared_memory(void *arg)
+{
+ /* file-mapping object, use for create shared memory */
+ HANDLE handle_connect_file_map= 0;
+ char *handle_connect_map= 0; // pointer on shared memory
+ HANDLE event_connect_answer= 0;
+ ulong smem_buffer_length= shared_memory_buffer_length + 4;
+ ulong connect_number= 1;
+ char *tmp= NULL;
+ char *suffix_pos;
+ char connect_number_char[22], *p;
+ const char *errmsg= 0;
+ SECURITY_ATTRIBUTES *sa_event= 0, *sa_mapping= 0;
+ my_thread_init();
+ DBUG_ENTER("handle_connections_shared_memorys");
+ DBUG_PRINT("general",("Waiting for allocated shared memory."));
+
+ /*
+ get enough space base-name + '_' + longest suffix we might ever send
+ */
+ if (!(tmp= (char *)my_malloc(strlen(shared_memory_base_name) + 32L, MYF(MY_FAE))))
+ goto error;
+
+ if (my_security_attr_create(&sa_event, &errmsg,
+ GENERIC_ALL, SYNCHRONIZE | EVENT_MODIFY_STATE))
+ goto error;
+
+ if (my_security_attr_create(&sa_mapping, &errmsg,
+ GENERIC_ALL, FILE_MAP_READ | FILE_MAP_WRITE))
+ goto error;
+
+ /*
+ The name of event and file-mapping events create agree next rule:
+ shared_memory_base_name+unique_part
+ Where:
+ shared_memory_base_name is unique value for each server
+ unique_part is unique value for each object (events and file-mapping)
+ */
+ suffix_pos= strxmov(tmp,shared_memory_base_name,"_",NullS);
+ strmov(suffix_pos, "CONNECT_REQUEST");
+ if ((smem_event_connect_request= CreateEvent(sa_event,
+ FALSE, FALSE, tmp)) == 0)
+ {
+ errmsg= "Could not create request event";
+ goto error;
+ }
+ strmov(suffix_pos, "CONNECT_ANSWER");
+ if ((event_connect_answer= CreateEvent(sa_event, FALSE, FALSE, tmp)) == 0)
+ {
+ errmsg="Could not create answer event";
+ goto error;
+ }
+ strmov(suffix_pos, "CONNECT_DATA");
+ if ((handle_connect_file_map=
+ CreateFileMapping(INVALID_HANDLE_VALUE, sa_mapping,
+ PAGE_READWRITE, 0, sizeof(connect_number), tmp)) == 0)
+ {
+ errmsg= "Could not create file mapping";
+ goto error;
+ }
+ if ((handle_connect_map= (char *)MapViewOfFile(handle_connect_file_map,
+ FILE_MAP_WRITE,0,0,
+ sizeof(DWORD))) == 0)
+ {
+ errmsg= "Could not create shared memory service";
+ goto error;
+ }
+
+ while (!abort_loop)
+ {
+ /* Wait a request from client */
+ WaitForSingleObject(smem_event_connect_request,INFINITE);
+
+ /*
+ it can be after shutdown command
+ */
+ if (abort_loop)
+ goto error;
+
+ HANDLE handle_client_file_map= 0;
+ char *handle_client_map= 0;
+ HANDLE event_client_wrote= 0;
+ HANDLE event_client_read= 0; // for transfer data server <-> client
+ HANDLE event_server_wrote= 0;
+ HANDLE event_server_read= 0;
+ HANDLE event_conn_closed= 0;
+ THD *thd= 0;
+
+ p= int10_to_str(connect_number, connect_number_char, 10);
+ /*
+ The name of event and file-mapping events create agree next rule:
+ shared_memory_base_name+unique_part+number_of_connection
+ Where:
+ shared_memory_base_name is uniquel value for each server
+ unique_part is unique value for each object (events and file-mapping)
+ number_of_connection is connection-number between server and client
+ */
+ suffix_pos= strxmov(tmp,shared_memory_base_name,"_",connect_number_char,
+ "_",NullS);
+ strmov(suffix_pos, "DATA");
+ if ((handle_client_file_map=
+ CreateFileMapping(INVALID_HANDLE_VALUE, sa_mapping,
+ PAGE_READWRITE, 0, smem_buffer_length, tmp)) == 0)
+ {
+ errmsg= "Could not create file mapping";
+ goto errorconn;
+ }
+ if ((handle_client_map= (char*)MapViewOfFile(handle_client_file_map,
+ FILE_MAP_WRITE,0,0,
+ smem_buffer_length)) == 0)
+ {
+ errmsg= "Could not create memory map";
+ goto errorconn;
+ }
+ strmov(suffix_pos, "CLIENT_WROTE");
+ if ((event_client_wrote= CreateEvent(sa_event, FALSE, FALSE, tmp)) == 0)
+ {
+ errmsg= "Could not create client write event";
+ goto errorconn;
+ }
+ strmov(suffix_pos, "CLIENT_READ");
+ if ((event_client_read= CreateEvent(sa_event, FALSE, FALSE, tmp)) == 0)
+ {
+ errmsg= "Could not create client read event";
+ goto errorconn;
+ }
+ strmov(suffix_pos, "SERVER_READ");
+ if ((event_server_read= CreateEvent(sa_event, FALSE, FALSE, tmp)) == 0)
+ {
+ errmsg= "Could not create server read event";
+ goto errorconn;
+ }
+ strmov(suffix_pos, "SERVER_WROTE");
+ if ((event_server_wrote= CreateEvent(sa_event,
+ FALSE, FALSE, tmp)) == 0)
+ {
+ errmsg= "Could not create server write event";
+ goto errorconn;
+ }
+ strmov(suffix_pos, "CONNECTION_CLOSED");
+ if ((event_conn_closed= CreateEvent(sa_event,
+ TRUE, FALSE, tmp)) == 0)
+ {
+ errmsg= "Could not create closed connection event";
+ goto errorconn;
+ }
+ if (abort_loop)
+ goto errorconn;
+ if (!(thd= new THD))
+ goto errorconn;
+ /* Send number of connection to client */
+ int4store(handle_connect_map, connect_number);
+ if (!SetEvent(event_connect_answer))
+ {
+ errmsg= "Could not send answer event";
+ goto errorconn;
+ }
+ /* Set event that client should receive data */
+ if (!SetEvent(event_client_read))
+ {
+ errmsg= "Could not set client to read mode";
+ goto errorconn;
+ }
+ set_current_thd(thd);
+ if (!(thd->net.vio= vio_new_win32shared_memory(handle_client_file_map,
+ handle_client_map,
+ event_client_wrote,
+ event_client_read,
+ event_server_wrote,
+ event_server_read,
+ event_conn_closed)) ||
+ my_net_init(&thd->net, thd->net.vio, MYF(MY_THREAD_SPECIFIC)))
+ {
+ close_connection(thd, ER_OUT_OF_RESOURCES);
+ errmsg= 0;
+ goto errorconn;
+ }
+ thd->security_ctx->host= my_strdup(my_localhost, MYF(0)); /* Host is unknown */
+ create_new_thread(thd);
+ connect_number++;
+ set_current_thd(thd);
+ continue;
+
+errorconn:
+ /* Could not form connection; Free used handlers/memort and retry */
+ if (errmsg)
+ {
+ char buff[180];
+ strxmov(buff, "Can't create shared memory connection: ", errmsg, ".",
+ NullS);
+ sql_perror(buff);
+ }
+ if (handle_client_file_map)
+ CloseHandle(handle_client_file_map);
+ if (handle_client_map)
+ UnmapViewOfFile(handle_client_map);
+ if (event_server_wrote)
+ CloseHandle(event_server_wrote);
+ if (event_server_read)
+ CloseHandle(event_server_read);
+ if (event_client_wrote)
+ CloseHandle(event_client_wrote);
+ if (event_client_read)
+ CloseHandle(event_client_read);
+ if (event_conn_closed)
+ CloseHandle(event_conn_closed);
+ delete thd;
+ }
+ set_current_thd(0);
+
+ /* End shared memory handling */
+error:
+ if (tmp)
+ my_free(tmp);
+
+ if (errmsg)
+ {
+ char buff[180];
+ strxmov(buff, "Can't create shared memory service: ", errmsg, ".", NullS);
+ sql_perror(buff);
+ }
+ my_security_attr_free(sa_event);
+ my_security_attr_free(sa_mapping);
+ if (handle_connect_map) UnmapViewOfFile(handle_connect_map);
+ if (handle_connect_file_map) CloseHandle(handle_connect_file_map);
+ if (event_connect_answer) CloseHandle(event_connect_answer);
+ if (smem_event_connect_request) CloseHandle(smem_event_connect_request);
+ DBUG_LEAVE;
+ decrement_handler_count();
+ return 0;
+}
+#endif /* HAVE_SMEM */
+#endif /* EMBEDDED_LIBRARY */
+
+
+/****************************************************************************
+ Handle start options
+******************************************************************************/
+
+
+/**
+ Process command line options flagged as 'early'.
+ Some components needs to be initialized as early as possible,
+ because the rest of the server initialization depends on them.
+ Options that needs to be parsed early includes:
+ - the performance schema, when compiled in,
+ - options related to the help,
+ - options related to the bootstrap
+ The performance schema needs to be initialized as early as possible,
+ before to-be-instrumented objects of the server are initialized.
+*/
+
+int handle_early_options()
+{
+ int ho_error;
+ DYNAMIC_ARRAY all_early_options;
+
+ my_getopt_register_get_addr(NULL);
+ /* Skip unknown options so that they may be processed later */
+ my_getopt_skip_unknown= TRUE;
+
+ /* prepare all_early_options array */
+ my_init_dynamic_array(&all_early_options, sizeof(my_option), 100, 25, MYF(0));
+ add_many_options(&all_early_options, pfs_early_options,
+ array_elements(pfs_early_options));
+ sys_var_add_options(&all_early_options, sys_var::PARSE_EARLY);
+ add_terminator(&all_early_options);
+
+ ho_error= handle_options(&remaining_argc, &remaining_argv,
+ (my_option*)(all_early_options.buffer),
+ mysqld_get_one_option);
+ if (ho_error == 0)
+ {
+ /* Add back the program name handle_options removes */
+ remaining_argc++;
+ remaining_argv--;
+ }
+
+ delete_dynamic(&all_early_options);
+
+ return ho_error;
+}
+
+
+#define MYSQL_COMPATIBILITY_OPTION(option) \
+ { option, OPT_MYSQL_COMPATIBILITY, \
+ 0, 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 }
+
+#define MYSQL_TO_BE_IMPLEMENTED_OPTION(option) \
+ { option, OPT_MYSQL_TO_BE_IMPLEMENTED, \
+ 0, 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 }
+
+#define MYSQL_SUGGEST_ANALOG_OPTION(option, str) \
+ { option, OPT_MYSQL_COMPATIBILITY, \
+ 0, 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 }
+
+
+/**
+ System variables are automatically command-line options (few
+ exceptions are documented in sys_var.h), so don't need
+ to be listed here.
+*/
+
+struct my_option my_long_options[]=
+{
+ {"help", '?', "Display this help and exit.",
+ &opt_help, &opt_help, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0,
+ 0, 0},
+ {"allow-suspicious-udfs", 0,
+ "Allows use of UDFs consisting of only one symbol xxx() "
+ "without corresponding xxx_init() or xxx_deinit(). That also means "
+ "that one can load any function from any library, for example exit() "
+ "from libc.so",
+ &opt_allow_suspicious_udfs, &opt_allow_suspicious_udfs,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"ansi", 'a', "Use ANSI SQL syntax instead of MySQL syntax. This mode "
+ "will also set transaction isolation level 'serializable'.", 0, 0, 0,
+ GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+ /*
+ Because Sys_var_bit does not support command-line options, we need to
+ explicitely add one for --autocommit
+ */
+ {"autocommit", 0, "Set default value for autocommit (0 or 1)",
+ &opt_autocommit, &opt_autocommit, 0,
+ GET_BOOL, OPT_ARG, 1, 0, 0, 0, 0, NULL},
+ {"bind-address", 0, "IP address to bind to.",
+ &my_bind_addr_str, &my_bind_addr_str, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"binlog-do-db", OPT_BINLOG_DO_DB,
+ "Tells the master it should log updates for the specified database, "
+ "and exclude all others not explicitly mentioned.",
+ 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"binlog-ignore-db", OPT_BINLOG_IGNORE_DB,
+ "Tells the master that updates to the given database should not be logged to the binary log.",
+ 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"binlog-row-event-max-size", 0,
+ "The maximum size of a row-based binary log event in bytes. Rows will be "
+ "grouped into events smaller than this size if possible. "
+ "The value has to be a multiple of 256.",
+ &opt_binlog_rows_event_max_size, &opt_binlog_rows_event_max_size,
+ 0, GET_ULONG, REQUIRED_ARG,
+ /* def_value */ 1024, /* min_value */ 256, /* max_value */ ULONG_MAX,
+ /* sub_size */ 0, /* block_size */ 256,
+ /* app_type */ 0
+ },
+#ifndef DISABLE_GRANT_OPTIONS
+ {"bootstrap", OPT_BOOTSTRAP, "Used by mysql installation scripts.", 0, 0, 0,
+ GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+#endif
+ {"character-set-client-handshake", 0,
+ "Don't ignore client side character set value sent during handshake.",
+ &opt_character_set_client_handshake,
+ &opt_character_set_client_handshake,
+ 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
+ {"character-set-filesystem", 0,
+ "Set the filesystem character set.",
+ &character_set_filesystem_name,
+ &character_set_filesystem_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+ {"character-set-server", 'C', "Set the default character set.",
+ &default_character_set_name, &default_character_set_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+ {"chroot", 'r', "Chroot mysqld daemon during startup.",
+ &mysqld_chroot, &mysqld_chroot, 0, GET_STR, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+ {"collation-server", 0, "Set the default collation.",
+ &default_collation_name, &default_collation_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+ {"console", OPT_CONSOLE, "Write error output on screen; don't remove the console window on windows.",
+ &opt_console, &opt_console, 0, GET_BOOL, NO_ARG, 0, 0, 0,
+ 0, 0, 0},
+ {"core-file", OPT_WANT_CORE, "Write core on errors.", 0, 0, 0, GET_NO_ARG,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ /* default-storage-engine should have "MyISAM" as def_value. Instead
+ of initializing it here it is done in init_common_variables() due
+ to a compiler bug in Sun Studio compiler. */
+#ifdef DBUG_OFF
+ {"debug", '#', "Built in DBUG debugger. Disabled in this build.",
+ &current_dbug_option, &current_dbug_option, 0, GET_STR, OPT_ARG,
+ 0, 0, 0, 0, 0, 0},
+#endif
+#ifdef HAVE_REPLICATION
+ {"debug-abort-slave-event-count", 0,
+ "Option used by mysql-test for debugging and testing of replication.",
+ &abort_slave_event_count, &abort_slave_event_count,
+ 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+#endif /* HAVE_REPLICATION */
+#ifndef DBUG_OFF
+ {"debug-assert-on-error", 0,
+ "Do an assert in various functions if we get a fatal error",
+ &my_assert_on_error, &my_assert_on_error,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"debug-assert-if-crashed-table", 0,
+ "Do an assert in handler::print_error() if we get a crashed table",
+ &debug_assert_if_crashed_table, &debug_assert_if_crashed_table,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+#endif
+#ifdef HAVE_REPLICATION
+ {"debug-disconnect-slave-event-count", 0,
+ "Option used by mysql-test for debugging and testing of replication.",
+ &disconnect_slave_event_count, &disconnect_slave_event_count,
+ 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+#endif /* HAVE_REPLICATION */
+ {"debug-exit-info", 'T', "Used for debugging. Use at your own risk.",
+ 0, 0, 0, GET_LONG, OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"debug-gdb", 0,
+ "Set up signals usable for debugging.",
+ &opt_debugging, &opt_debugging,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+#ifdef HAVE_REPLICATION
+ {"debug-max-binlog-dump-events", 0,
+ "Option used by mysql-test for debugging and testing of replication.",
+ &max_binlog_dump_events, &max_binlog_dump_events, 0,
+ GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+#endif /* HAVE_REPLICATION */
+#ifdef SAFE_MUTEX
+ {"debug-mutex-deadlock-detector", 0,
+ "Enable checking of wrong mutex usage.",
+ &safe_mutex_deadlock_detector,
+ &safe_mutex_deadlock_detector,
+ 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
+#endif
+ {"debug-no-sync", 0,
+ "Disables system sync calls. Only for running tests or debugging!",
+ &my_disable_sync, &my_disable_sync, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+#ifdef HAVE_REPLICATION
+ {"debug-sporadic-binlog-dump-fail", 0,
+ "Option used by mysql-test for debugging and testing of replication.",
+ &opt_sporadic_binlog_dump_fail,
+ &opt_sporadic_binlog_dump_fail, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0,
+ 0},
+#endif /* HAVE_REPLICATION */
+ {"default-storage-engine", 0, "The default storage engine for new tables",
+ &default_storage_engine, 0, 0, GET_STR, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0 },
+ {"default-time-zone", 0, "Set the default time zone.",
+ &default_tz_name, &default_tz_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+#if defined(ENABLED_DEBUG_SYNC)
+ {"debug-sync-timeout", OPT_DEBUG_SYNC_TIMEOUT,
+ "Enable the debug sync facility "
+ "and optionally specify a default wait timeout in seconds. "
+ "A zero value keeps the facility disabled.",
+ &opt_debug_sync_timeout, 0,
+ 0, GET_UINT, OPT_ARG, 0, 0, UINT_MAX, 0, 0, 0},
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+#ifdef HAVE_OPENSSL
+ {"des-key-file", 0,
+ "Load keys for des_encrypt() and des_encrypt from given file.",
+ &des_key_file, &des_key_file, 0, GET_STR, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+#endif /* HAVE_OPENSSL */
+#ifdef HAVE_STACKTRACE
+ {"stack-trace", 0 , "Print a symbolic stack trace on failure",
+ &opt_stack_trace, &opt_stack_trace, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
+#endif /* HAVE_STACKTRACE */
+ {"external-locking", 0, "Use system (external) locking (disabled by "
+ "default). With this option enabled you can run myisamchk to test "
+ "(not repair) tables while the MySQL server is running. Disable with "
+ "--skip-external-locking.", &opt_external_locking, &opt_external_locking,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ /* We must always support the next option to make scripts like mysqltest
+ easier to do */
+ {"gdb", 0,
+ "Set up signals usable for debugging. Deprecated, use --debug-gdb instead.",
+ &opt_debugging, &opt_debugging,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+#ifdef HAVE_LARGE_PAGE_OPTION
+ {"super-large-pages", 0, "Enable support for super large pages.",
+ &opt_super_large_pages, &opt_super_large_pages, 0,
+ GET_BOOL, OPT_ARG, 0, 0, 1, 0, 1, 0},
+#endif
+ {"language", 'L',
+ "Client error messages in given language. May be given as a full path. "
+ "Deprecated. Use --lc-messages-dir instead.",
+ 0, 0, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"lc-messages", 0,
+ "Set the language used for the error messages.",
+ &lc_messages, &lc_messages, 0, GET_STR, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0 },
+ {"lc-time-names", 0,
+ "Set the language used for the month names and the days of the week.",
+ &lc_time_names_name, &lc_time_names_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+ {"log-basename", OPT_LOG_BASENAME,
+ "Basename for all log files and the .pid file. This sets all log file "
+ "names at once (in 'datadir') and is normally the only option you need "
+ "for specifying log files. Sets names for --log-bin, --log-bin-index, "
+ "--relay-log, --relay-log-index, --general-log-file, "
+ "--log-slow-query-log-file, --log-error-file, and --pid-file",
+ &opt_log_basename, &opt_log_basename, 0, GET_STR, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+ {"log-bin", OPT_BIN_LOG,
+ "Log update queries in binary format. Optional argument should be name for "
+ "binary log. If not given "
+ "'datadir'/'log-basename'-bin or 'datadir'/mysql-bin will be used (the later if "
+ "--log-basename is not specified). We strongly recommend to use either "
+ "--log-basename or specify a filename to ensure that replication doesn't "
+ "stop if the real hostname of the computer changes.",
+ &opt_bin_logname, &opt_bin_logname, 0, GET_STR,
+ OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"log-bin-index", 0,
+ "File that holds the names for last binary log files.",
+ &opt_binlog_index_name, &opt_binlog_index_name, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"log-isam", OPT_ISAM_LOG, "Log all MyISAM changes to file.",
+ &myisam_log_filename, &myisam_log_filename, 0, GET_STR,
+ OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"log-short-format", 0,
+ "Don't log extra information to update and slow-query logs.",
+ &opt_short_log_format, &opt_short_log_format,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"log-slow-admin-statements", 0,
+ "Log slow OPTIMIZE, ANALYZE, ALTER and other administrative statements to "
+ "the slow log if it is open.", &opt_log_slow_admin_statements,
+ &opt_log_slow_admin_statements, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"log-slow-slave-statements", 0,
+ "Log slow statements executed by slave thread to the slow log if it is open.",
+ &opt_log_slow_slave_statements, &opt_log_slow_slave_statements,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"log-tc", 0,
+ "Path to transaction coordinator log (used for transactions that affect "
+ "more than one storage engine, when binary log is disabled).",
+ &opt_tc_log_file, &opt_tc_log_file, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+#ifdef HAVE_MMAP
+ {"log-tc-size", 0, "Size of transaction coordinator log.",
+ &opt_tc_log_size, &opt_tc_log_size, 0, GET_ULONG,
+ REQUIRED_ARG, TC_LOG_MIN_SIZE, TC_LOG_MIN_SIZE, (ulonglong) ULONG_MAX, 0,
+ TC_LOG_PAGE_SIZE, 0},
+#endif
+ {"master-info-file", 0,
+ "The location and name of the file that remembers the master and where "
+ "the I/O replication thread is in the master's binlogs. Defaults to "
+ "master.info",
+ &master_info_file, &master_info_file, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"master-retry-count", 0,
+ "The number of tries the slave will make to connect to the master before giving up.",
+ &master_retry_count, &master_retry_count, 0, GET_ULONG,
+ REQUIRED_ARG, 3600*24, 0, 0, 0, 0, 0},
+#ifdef HAVE_REPLICATION
+ {"init-rpl-role", 0, "Set the replication role.",
+ &rpl_status, &rpl_status, &rpl_role_typelib,
+ GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+#endif /* HAVE_REPLICATION */
+ {"memlock", 0, "Lock mysqld in memory.", &locked_in_memory,
+ &locked_in_memory, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"old-style-user-limits", 0,
+ "Enable old-style user limits (before 5.0.3, user resources were counted "
+ "per each user+host vs. per account).",
+ &opt_old_style_user_limits, &opt_old_style_user_limits,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"port-open-timeout", 0,
+ "Maximum time in seconds to wait for the port to become free. "
+ "(Default: No wait).", &mysqld_port_timeout, &mysqld_port_timeout, 0,
+ GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"replicate-do-db", OPT_REPLICATE_DO_DB,
+ "Tells the slave thread to restrict replication to the specified database. "
+ "To specify more than one database, use the directive multiple times, "
+ "once for each database. Note that this will only work if you do not use "
+ "cross-database queries such as UPDATE some_db.some_table SET foo='bar' "
+ "while having selected a different or no database. If you need cross "
+ "database updates to work, make sure you have 3.23.28 or later, and use "
+ "replicate-wild-do-table=db_name.%.",
+ 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"replicate-do-table", OPT_REPLICATE_DO_TABLE,
+ "Tells the slave thread to restrict replication to the specified table. "
+ "To specify more than one table, use the directive multiple times, once "
+ "for each table. This will work for cross-database updates, in contrast "
+ "to replicate-do-db.", 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"replicate-ignore-db", OPT_REPLICATE_IGNORE_DB,
+ "Tells the slave thread to not replicate to the specified database. To "
+ "specify more than one database to ignore, use the directive multiple "
+ "times, once for each database. This option will not work if you use "
+ "cross database updates. If you need cross database updates to work, "
+ "make sure you have 3.23.28 or later, and use replicate-wild-ignore-"
+ "table=db_name.%. ", 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"replicate-ignore-table", OPT_REPLICATE_IGNORE_TABLE,
+ "Tells the slave thread to not replicate to the specified table. To specify "
+ "more than one table to ignore, use the directive multiple times, once for "
+ "each table. This will work for cross-database updates, in contrast to "
+ "replicate-ignore-db.", 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"replicate-rewrite-db", OPT_REPLICATE_REWRITE_DB,
+ "Updates to a database with a different name than the original. Example: "
+ "replicate-rewrite-db=master_db_name->slave_db_name.",
+ 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+#ifdef HAVE_REPLICATION
+ {"replicate-same-server-id", 0,
+ "In replication, if set to 1, do not skip events having our server id. "
+ "Default value is 0 (to break infinite loops in circular replication). "
+ "Can't be set to 1 if --log-slave-updates is used.",
+ &replicate_same_server_id, &replicate_same_server_id,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+#endif
+ {"replicate-wild-do-table", OPT_REPLICATE_WILD_DO_TABLE,
+ "Tells the slave thread to restrict replication to the tables that match "
+ "the specified wildcard pattern. To specify more than one table, use the "
+ "directive multiple times, once for each table. This will work for cross-"
+ "database updates. Example: replicate-wild-do-table=foo%.bar% will "
+ "replicate only updates to tables in all databases that start with foo "
+ "and whose table names start with bar.",
+ 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"replicate-wild-ignore-table", OPT_REPLICATE_WILD_IGNORE_TABLE,
+ "Tells the slave thread to not replicate to the tables that match the "
+ "given wildcard pattern. To specify more than one table to ignore, use "
+ "the directive multiple times, once for each table. This will work for "
+ "cross-database updates. Example: replicate-wild-ignore-table=foo%.bar% "
+ "will not do updates to tables in databases that start with foo and whose "
+ "table names start with bar.",
+ 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"safe-mode", OPT_SAFE, "Skip some optimize stages (for testing). Deprecated.",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"safe-user-create", 0,
+ "Don't allow new user creation by the user who has no write privileges to the mysql.user table.",
+ &opt_safe_user_create, &opt_safe_user_create, 0, GET_BOOL,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"show-slave-auth-info", 0,
+ "Show user and password in SHOW SLAVE HOSTS on this master.",
+ &opt_show_slave_auth_info, &opt_show_slave_auth_info, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"skip-bdb", OPT_DEPRECATED_OPTION,
+ "Deprecated option; Exist only for compatiblity with old my.cnf files",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+#ifndef DISABLE_GRANT_OPTIONS
+ {"skip-grant-tables", 0,
+ "Start without grant tables. This gives all users FULL ACCESS to all tables.",
+ &opt_noacl, &opt_noacl, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0,
+ 0},
+#endif
+ {"skip-host-cache", OPT_SKIP_HOST_CACHE, "Don't cache host names.", 0, 0, 0,
+ GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"skip-slave-start", 0,
+ "If set, slave is not autostarted.", &opt_skip_slave_start,
+ &opt_skip_slave_start, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+#if defined(_WIN32) && !defined(EMBEDDED_LIBRARY)
+ {"slow-start-timeout", 0,
+ "Maximum number of milliseconds that the service control manager should wait "
+ "before trying to kill the windows service during startup"
+ "(Default: 15000).", &slow_start_timeout, &slow_start_timeout, 0,
+ GET_ULONG, REQUIRED_ARG, 15000, 0, 0, 0, 0, 0},
+#endif
+#ifdef HAVE_OPENSSL
+ {"ssl", 0,
+ "Enable SSL for connection (automatically enabled if an ssl option is used).",
+ &opt_use_ssl, &opt_use_ssl, 0, GET_BOOL, OPT_ARG, 0, 0, 0,
+ 0, 0, 0},
+#endif
+#ifdef __WIN__
+ {"standalone", 0,
+ "Dummy option to start as a standalone program (NT).", 0, 0, 0, GET_NO_ARG,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+#endif
+ {"symbolic-links", 's', "Enable symbolic link support.",
+ &my_use_symdir, &my_use_symdir, 0, GET_BOOL, NO_ARG,
+ /*
+ The system call realpath() produces warnings under valgrind and
+ purify. These are not suppressed: instead we disable symlinks
+ option if compiled with valgrind support.
+ Also disable by default on Windows, due to high overhead for checking .sym
+ files.
+ */
+ IF_VALGRIND(0,IF_WIN(0,1)), 0, 0, 0, 0, 0},
+ {"sysdate-is-now", 0,
+ "Non-default option to alias SYSDATE() to NOW() to make it safe-replicable. "
+ "Since 5.0, SYSDATE() returns a `dynamic' value different for different "
+ "invocations, even within the same statement.",
+ &global_system_variables.sysdate_is_now,
+ 0, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 1, 0},
+ {"tc-heuristic-recover", 0,
+ "Decision to use in heuristic recover process. Possible values are COMMIT "
+ "or ROLLBACK.", &tc_heuristic_recover, &tc_heuristic_recover,
+ &tc_heuristic_recover_typelib, GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"temp-pool", 0,
+#if (ENABLE_TEMP_POOL)
+ "Using this option will cause most temporary files created to use a small "
+ "set of names, rather than a unique name for each new file.",
+#else
+ "This option is ignored on this OS.",
+#endif
+ &use_temp_pool, &use_temp_pool, 0, GET_BOOL, NO_ARG, 1,
+ 0, 0, 0, 0, 0},
+ {"transaction-isolation", 0,
+ "Default transaction isolation level.",
+ &global_system_variables.tx_isolation,
+ &global_system_variables.tx_isolation, &tx_isolation_typelib,
+ GET_ENUM, REQUIRED_ARG, ISO_REPEATABLE_READ, 0, 0, 0, 0, 0},
+ {"transaction-read-only", 0,
+ "Default transaction access mode. "
+ "True if transactions are read-only.",
+ &global_system_variables.tx_read_only,
+ &global_system_variables.tx_read_only, 0,
+ GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"user", 'u', "Run mysqld daemon as user.", 0, 0, 0, GET_STR, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+ {"verbose", 'v', "Used with --help option for detailed help.",
+ &opt_verbose, &opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"plugin-load", OPT_PLUGIN_LOAD,
+ "Semicolon-separated list of plugins to load, where each plugin is "
+ "specified as ether a plugin_name=library_file pair or only a library_file. "
+ "If the latter case, all plugins from a given library_file will be loaded.",
+ 0, 0, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"plugin-load-add", OPT_PLUGIN_LOAD_ADD,
+ "Optional semicolon-separated list of plugins to load. This option adds "
+ "to the list specified by --plugin-load in an incremental way. "
+ "It can be specified many times, adding more plugins every time.",
+ 0, 0, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"table_cache", 0, "Deprecated; use --table-open-cache instead.",
+ &tc_size, &tc_size, 0, GET_ULONG,
+ REQUIRED_ARG, TABLE_OPEN_CACHE_DEFAULT, 1, 512*1024L, 0, 1, 0},
+
+ /* The following options exist in 5.6 but not in 10.0 */
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("default-tmp-storage-engine"),
+ MYSQL_COMPATIBILITY_OPTION("log-raw"),
+ MYSQL_COMPATIBILITY_OPTION("log-bin-use-v1-row-events"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("default-authentication-plugin"),
+ MYSQL_COMPATIBILITY_OPTION("binlog-max-flush-queue-time"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("binlog-row-image"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("explicit-defaults-for-timestamp"),
+ MYSQL_COMPATIBILITY_OPTION("master-info-repository"),
+ MYSQL_COMPATIBILITY_OPTION("relay-log-info-repository"),
+ MYSQL_SUGGEST_ANALOG_OPTION("binlog-rows-query-log-events", "--binlog-annotate-row-events"),
+ MYSQL_COMPATIBILITY_OPTION("binlog-order-commits"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("log-throttle-queries-not-using-indexes"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("end-markers-in-json"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace"), // OPTIMIZER_TRACE
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-features"), // OPTIMIZER_TRACE
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-offset"), // OPTIMIZER_TRACE
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-limit"), // OPTIMIZER_TRACE
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-max-mem-size"), // OPTIMIZER_TRACE
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("eq-range-index-dive-limit"),
+ MYSQL_COMPATIBILITY_OPTION("server-id-bits"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("slave-rows-search-algorithms"), // HAVE_REPLICATION
+ MYSQL_COMPATIBILITY_OPTION("table-open-cache-instances"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("slave-allow-batching"), // HAVE_REPLICATION
+ MYSQL_COMPATIBILITY_OPTION("slave-checkpoint-period"), // HAVE_REPLICATION
+ MYSQL_COMPATIBILITY_OPTION("slave-checkpoint-group"), // HAVE_REPLICATION
+ MYSQL_SUGGEST_ANALOG_OPTION("slave-parallel-workers", "--slave-parallel-threads"), // HAVE_REPLICATION
+ MYSQL_SUGGEST_ANALOG_OPTION("slave-pending-jobs-size-max", "--slave-parallel-max-queued"), // HAVE_REPLICATION
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("disconnect-on-expired-password"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("sha256-password-private-key-path"), // HAVE_OPENSSL && !HAVE_YASSL
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("sha256-password-public-key-path"), // HAVE_OPENSSL && !HAVE_YASSL
+
+ /* The following options exist in 5.5 and 5.6 but not in 10.0 */
+ MYSQL_SUGGEST_ANALOG_OPTION("abort-slave-event-count", "--debug-abort-slave-event-count"),
+ MYSQL_SUGGEST_ANALOG_OPTION("disconnect-slave-event-count", "--debug-disconnect-slave-event-count"),
+ MYSQL_SUGGEST_ANALOG_OPTION("exit-info", "--debug-exit-info"),
+ MYSQL_SUGGEST_ANALOG_OPTION("max-binlog-dump-events", "--debug-max-binlog-dump-events"),
+ MYSQL_SUGGEST_ANALOG_OPTION("sporadic-binlog-dump-fail", "--debug-sporadic-binlog-dump-fail"),
+ MYSQL_COMPATIBILITY_OPTION("new"),
+
+ /* The following options were added after 5.6.10 */
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("rpl-stop-slave-timeout"),
+ MYSQL_TO_BE_IMPLEMENTED_OPTION("validate-user-plugins") // NO_EMBEDDED_ACCESS_CHECKS
+};
+
+static int show_queries(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONGLONG;
+ var->value= (char *)&thd->query_id;
+ return 0;
+}
+
+
+static int show_net_compression(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_MY_BOOL;
+ var->value= (char *)&thd->net.compress;
+ return 0;
+}
+
+static int show_starttime(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (long) (thd->query_start() - server_start_time);
+ return 0;
+}
+
+#ifdef ENABLED_PROFILING
+static int show_flushstatustime(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (long) (thd->query_start() - flush_status_time);
+ return 0;
+}
+#endif
+
+#ifdef HAVE_REPLICATION
+static int show_rpl_status(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_CHAR;
+ var->value= const_cast<char*>(rpl_status_type[(int)rpl_status]);
+ return 0;
+}
+
+static int show_slave_running(THD *thd, SHOW_VAR *var, char *buff)
+{
+ Master_info *mi= NULL;
+ bool tmp;
+ LINT_INIT(tmp);
+
+ var->type= SHOW_MY_BOOL;
+ var->value= buff;
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index)
+ {
+ mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_NOTE);
+ if (mi)
+ tmp= (my_bool) (mi->slave_running == MYSQL_SLAVE_RUN_CONNECT &&
+ mi->rli.slave_running);
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (mi)
+ *((my_bool *)buff)= tmp;
+ else
+ var->type= SHOW_UNDEF;
+ return 0;
+}
+
+
+static int show_slave_received_heartbeats(THD *thd, SHOW_VAR *var, char *buff)
+{
+ Master_info *mi= NULL;
+ longlong tmp;
+ LINT_INIT(tmp);
+
+ var->type= SHOW_LONGLONG;
+ var->value= buff;
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index)
+ {
+ mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_NOTE);
+ if (mi)
+ tmp= mi->received_heartbeats;
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (mi)
+ *((longlong *)buff)= tmp;
+ else
+ var->type= SHOW_UNDEF;
+ return 0;
+}
+
+
+static int show_heartbeat_period(THD *thd, SHOW_VAR *var, char *buff)
+{
+ Master_info *mi= NULL;
+ float tmp;
+ LINT_INIT(tmp);
+
+ var->type= SHOW_CHAR;
+ var->value= buff;
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index)
+ {
+ mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_NOTE);
+ if (mi)
+ tmp= mi->heartbeat_period;
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (mi)
+ sprintf(buff, "%.3f", tmp);
+ else
+ var->type= SHOW_UNDEF;
+ return 0;
+}
+
+
+#endif /* HAVE_REPLICATION */
+
+static int show_open_tables(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *) buff)= (long) tc_records();
+ return 0;
+}
+
+static int show_prepared_stmt_count(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ mysql_mutex_lock(&LOCK_prepared_stmt_count);
+ *((long *)buff)= (long)prepared_stmt_count;
+ mysql_mutex_unlock(&LOCK_prepared_stmt_count);
+ return 0;
+}
+
+static int show_table_definitions(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *) buff)= (long) tdc_records();
+ return 0;
+}
+
+
+static int show_flush_commands(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *) buff)= (long) tdc_refresh_version();
+ return 0;
+}
+
+
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+/* Functions relying on CTX */
+static int show_ssl_ctx_sess_accept(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_accept(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_accept_good(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_accept_good(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_connect_good(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_connect_good(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_accept_renegotiate(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_accept_renegotiate(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_connect_renegotiate(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_connect_renegotiate(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_cb_hits(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_cb_hits(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_hits(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_hits(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_cache_full(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_cache_full(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_misses(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_misses(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_timeouts(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_timeouts(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_number(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_number(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_connect(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_connect(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_sess_get_cache_size(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_sess_get_cache_size(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_get_verify_mode(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ *((long *)buff)= (!ssl_acceptor_fd ? 0 :
+ SSL_CTX_get_verify_depth(ssl_acceptor_fd->ssl_context));
+ return 0;
+}
+
+static int show_ssl_ctx_get_session_cache_mode(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_CHAR;
+ if (!ssl_acceptor_fd)
+ var->value= const_cast<char*>("NONE");
+ else
+ switch (SSL_CTX_get_session_cache_mode(ssl_acceptor_fd->ssl_context))
+ {
+ case SSL_SESS_CACHE_OFF:
+ var->value= const_cast<char*>("OFF"); break;
+ case SSL_SESS_CACHE_CLIENT:
+ var->value= const_cast<char*>("CLIENT"); break;
+ case SSL_SESS_CACHE_SERVER:
+ var->value= const_cast<char*>("SERVER"); break;
+ case SSL_SESS_CACHE_BOTH:
+ var->value= const_cast<char*>("BOTH"); break;
+ case SSL_SESS_CACHE_NO_AUTO_CLEAR:
+ var->value= const_cast<char*>("NO_AUTO_CLEAR"); break;
+ case SSL_SESS_CACHE_NO_INTERNAL_LOOKUP:
+ var->value= const_cast<char*>("NO_INTERNAL_LOOKUP"); break;
+ default:
+ var->value= const_cast<char*>("Unknown"); break;
+ }
+ return 0;
+}
+
+/*
+ Functions relying on SSL
+ Note: In the show_ssl_* functions, we need to check if we have a
+ valid vio-object since this isn't always true, specifically
+ when session_status or global_status is requested from
+ inside an Event.
+ */
+static int show_ssl_get_version(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_CHAR;
+ if( thd->vio_ok() && thd->net.vio->ssl_arg )
+ var->value= const_cast<char*>(SSL_get_version((SSL*) thd->net.vio->ssl_arg));
+ else
+ var->value= (char *)"";
+ return 0;
+}
+
+static int show_ssl_session_reused(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ if( thd->vio_ok() && thd->net.vio->ssl_arg )
+ *((long *)buff)= (long)SSL_session_reused((SSL*) thd->net.vio->ssl_arg);
+ else
+ *((long *)buff)= 0;
+ return 0;
+}
+
+static int show_ssl_get_default_timeout(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ if( thd->vio_ok() && thd->net.vio->ssl_arg )
+ *((long *)buff)= (long)SSL_get_default_timeout((SSL*)thd->net.vio->ssl_arg);
+ else
+ *((long *)buff)= 0;
+ return 0;
+}
+
+static int show_ssl_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ if( thd->net.vio && thd->net.vio->ssl_arg )
+ *((long *)buff)= (long)SSL_get_verify_mode((SSL*)thd->net.vio->ssl_arg);
+ else
+ *((long *)buff)= 0;
+ return 0;
+}
+
+static int show_ssl_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_LONG;
+ var->value= buff;
+ if( thd->vio_ok() && thd->net.vio->ssl_arg )
+ *((long *)buff)= (long)SSL_get_verify_depth((SSL*)thd->net.vio->ssl_arg);
+ else
+ *((long *)buff)= 0;
+ return 0;
+}
+
+static int show_ssl_get_cipher(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_CHAR;
+ if( thd->vio_ok() && thd->net.vio->ssl_arg )
+ var->value= const_cast<char*>(SSL_get_cipher((SSL*) thd->net.vio->ssl_arg));
+ else
+ var->value= (char *)"";
+ return 0;
+}
+
+static int show_ssl_get_cipher_list(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_CHAR;
+ var->value= buff;
+ if (thd->vio_ok() && thd->net.vio->ssl_arg)
+ {
+ int i;
+ const char *p;
+ char *end= buff + SHOW_VAR_FUNC_BUFF_SIZE;
+ for (i=0; (p= SSL_get_cipher_list((SSL*) thd->net.vio->ssl_arg,i)) &&
+ buff < end; i++)
+ {
+ buff= strnmov(buff, p, end-buff-1);
+ *buff++= ':';
+ }
+ if (i)
+ buff--;
+ }
+ *buff=0;
+ return 0;
+}
+
+
+#ifdef HAVE_YASSL
+
+static char *
+my_asn1_time_to_string(ASN1_TIME *time, char *buf, size_t len)
+{
+ return yaSSL_ASN1_TIME_to_string(time, buf, len);
+}
+
+#else /* openssl */
+
+static char *
+my_asn1_time_to_string(ASN1_TIME *time, char *buf, size_t len)
+{
+ int n_read;
+ char *res= NULL;
+ BIO *bio= BIO_new(BIO_s_mem());
+
+ if (bio == NULL)
+ return NULL;
+
+ if (!ASN1_TIME_print(bio, time))
+ goto end;
+
+ n_read= BIO_read(bio, buf, (int) (len - 1));
+
+ if (n_read > 0)
+ {
+ buf[n_read]= 0;
+ res= buf;
+ }
+
+end:
+ BIO_free(bio);
+ return res;
+}
+
+#endif
+
+
+/**
+ Handler function for the 'ssl_get_server_not_before' variable
+
+ @param thd the mysql thread structure
+ @param var the data for the variable
+ @param[out] buf the string to put the value of the variable into
+
+ @return status
+ @retval 0 success
+*/
+
+static int
+show_ssl_get_server_not_before(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_CHAR;
+ if(thd->vio_ok() && thd->net.vio->ssl_arg)
+ {
+ SSL *ssl= (SSL*) thd->net.vio->ssl_arg;
+ X509 *cert= SSL_get_certificate(ssl);
+ ASN1_TIME *not_before= X509_get_notBefore(cert);
+
+ var->value= my_asn1_time_to_string(not_before, buff,
+ SHOW_VAR_FUNC_BUFF_SIZE);
+ if (!var->value)
+ return 1;
+ var->value= buff;
+ }
+ else
+ var->value= empty_c_string;
+ return 0;
+}
+
+
+/**
+ Handler function for the 'ssl_get_server_not_after' variable
+
+ @param thd the mysql thread structure
+ @param var the data for the variable
+ @param[out] buf the string to put the value of the variable into
+
+ @return status
+ @retval 0 success
+*/
+
+static int
+show_ssl_get_server_not_after(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_CHAR;
+ if(thd->vio_ok() && thd->net.vio->ssl_arg)
+ {
+ SSL *ssl= (SSL*) thd->net.vio->ssl_arg;
+ X509 *cert= SSL_get_certificate(ssl);
+ ASN1_TIME *not_after= X509_get_notAfter(cert);
+
+ var->value= my_asn1_time_to_string(not_after, buff,
+ SHOW_VAR_FUNC_BUFF_SIZE);
+ if (!var->value)
+ return 1;
+ }
+ else
+ var->value= empty_c_string;
+ return 0;
+}
+
+#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */
+
+static int show_default_keycache(THD *thd, SHOW_VAR *var, char *buff)
+{
+ struct st_data {
+ KEY_CACHE_STATISTICS stats;
+ SHOW_VAR var[8];
+ } *data;
+ SHOW_VAR *v;
+
+ data=(st_data *)buff;
+ v= data->var;
+
+ var->type= SHOW_ARRAY;
+ var->value= (char*)v;
+
+ get_key_cache_statistics(dflt_key_cache, 0, &data->stats);
+
+#define set_one_keycache_var(X,Y) \
+ v->name= X; \
+ v->type= SHOW_LONGLONG; \
+ v->value= (char*)&data->stats.Y; \
+ v++;
+
+ set_one_keycache_var("blocks_not_flushed", blocks_changed);
+ set_one_keycache_var("blocks_unused", blocks_unused);
+ set_one_keycache_var("blocks_used", blocks_used);
+ set_one_keycache_var("blocks_warm", blocks_warm);
+ set_one_keycache_var("read_requests", read_requests);
+ set_one_keycache_var("reads", reads);
+ set_one_keycache_var("write_requests", write_requests);
+ set_one_keycache_var("writes", writes);
+
+ v->name= 0;
+
+ DBUG_ASSERT((char*)(v+1) <= buff + SHOW_VAR_FUNC_BUFF_SIZE);
+
+#undef set_one_keycache_var
+
+ return 0;
+}
+
+#ifndef DBUG_OFF
+static int debug_status_func(THD *thd, SHOW_VAR *var, char *buff)
+{
+#define add_var(X,Y,Z) \
+ v->name= X; \
+ v->value= (char*)Y; \
+ v->type= Z; \
+ v++;
+
+ var->type= SHOW_ARRAY;
+ var->value= buff;
+
+ SHOW_VAR *v= (SHOW_VAR *)buff;
+
+ if (_db_keyword_(0, "role_merge_stats", 1))
+ {
+ static SHOW_VAR roles[]= {
+ {"global", (char*) &role_global_merges, SHOW_ULONG},
+ {"db", (char*) &role_db_merges, SHOW_ULONG},
+ {"table", (char*) &role_table_merges, SHOW_ULONG},
+ {"column", (char*) &role_column_merges, SHOW_ULONG},
+ {"routine", (char*) &role_routine_merges, SHOW_ULONG},
+ {NullS, NullS, SHOW_LONG}
+ };
+
+ add_var("role_merges", roles, SHOW_ARRAY);
+ }
+
+ v->name= 0;
+
+#undef add_var
+
+ return 0;
+}
+#endif
+
+#ifdef HAVE_POOL_OF_THREADS
+int show_threadpool_idle_threads(THD *thd, SHOW_VAR *var, char *buff)
+{
+ var->type= SHOW_INT;
+ var->value= buff;
+ *(int *)buff= tp_get_idle_thread_count();
+ return 0;
+}
+#endif
+
+/*
+ Variables shown by SHOW STATUS in alphabetical order
+*/
+
+SHOW_VAR status_vars[]= {
+ {"Aborted_clients", (char*) &aborted_threads, SHOW_LONG},
+ {"Aborted_connects", (char*) &aborted_connects, SHOW_LONG},
+ {"Access_denied_errors", (char*) offsetof(STATUS_VAR, access_denied_errors), SHOW_LONG_STATUS},
+ {"Binlog_bytes_written", (char*) offsetof(STATUS_VAR, binlog_bytes_written), SHOW_LONGLONG_STATUS},
+ {"Binlog_cache_disk_use", (char*) &binlog_cache_disk_use, SHOW_LONG},
+ {"Binlog_cache_use", (char*) &binlog_cache_use, SHOW_LONG},
+ {"Binlog_stmt_cache_disk_use",(char*) &binlog_stmt_cache_disk_use, SHOW_LONG},
+ {"Binlog_stmt_cache_use", (char*) &binlog_stmt_cache_use, SHOW_LONG},
+ {"Busy_time", (char*) offsetof(STATUS_VAR, busy_time), SHOW_DOUBLE_STATUS},
+ {"Bytes_received", (char*) offsetof(STATUS_VAR, bytes_received), SHOW_LONGLONG_STATUS},
+ {"Bytes_sent", (char*) offsetof(STATUS_VAR, bytes_sent), SHOW_LONGLONG_STATUS},
+ {"Com", (char*) com_status_vars, SHOW_ARRAY},
+ {"Compression", (char*) &show_net_compression, SHOW_SIMPLE_FUNC},
+ {"Connections", (char*) &thread_id, SHOW_LONG_NOFLUSH},
+ {"Connection_errors_accept", (char*) &connection_errors_accept, SHOW_LONG},
+ {"Connection_errors_internal", (char*) &connection_errors_internal, SHOW_LONG},
+ {"Connection_errors_max_connections", (char*) &connection_errors_max_connection, SHOW_LONG},
+ {"Connection_errors_peer_address", (char*) &connection_errors_peer_addr, SHOW_LONG},
+ {"Connection_errors_select", (char*) &connection_errors_select, SHOW_LONG},
+ {"Connection_errors_tcpwrap", (char*) &connection_errors_tcpwrap, SHOW_LONG},
+ {"Cpu_time", (char*) offsetof(STATUS_VAR, cpu_time), SHOW_DOUBLE_STATUS},
+ {"Created_tmp_disk_tables", (char*) offsetof(STATUS_VAR, created_tmp_disk_tables_), SHOW_LONG_STATUS},
+ {"Created_tmp_files", (char*) &my_tmp_file_created, SHOW_LONG},
+ {"Created_tmp_tables", (char*) offsetof(STATUS_VAR, created_tmp_tables_), SHOW_LONG_STATUS},
+#ifndef DBUG_OFF
+ {"Debug", (char*) &debug_status_func, SHOW_FUNC},
+#endif
+ {"Delayed_errors", (char*) &delayed_insert_errors, SHOW_LONG},
+ {"Delayed_insert_threads", (char*) &delayed_insert_threads, SHOW_LONG_NOFLUSH},
+ {"Delayed_writes", (char*) &delayed_insert_writes, SHOW_LONG},
+ {"Empty_queries", (char*) offsetof(STATUS_VAR, empty_queries), SHOW_LONG_STATUS},
+ {"Executed_events", (char*) &executed_events, SHOW_LONG_NOFLUSH },
+ {"Executed_triggers", (char*) offsetof(STATUS_VAR, executed_triggers), SHOW_LONG_STATUS},
+ {"Feature_delay_key_write", (char*) &feature_files_opened_with_delayed_keys, SHOW_LONG },
+ {"Feature_dynamic_columns", (char*) offsetof(STATUS_VAR, feature_dynamic_columns), SHOW_LONG_STATUS},
+ {"Feature_fulltext", (char*) offsetof(STATUS_VAR, feature_fulltext), SHOW_LONG_STATUS},
+ {"Feature_gis", (char*) offsetof(STATUS_VAR, feature_gis), SHOW_LONG_STATUS},
+ {"Feature_locale", (char*) offsetof(STATUS_VAR, feature_locale), SHOW_LONG_STATUS},
+ {"Feature_subquery", (char*) offsetof(STATUS_VAR, feature_subquery), SHOW_LONG_STATUS},
+ {"Feature_timezone", (char*) offsetof(STATUS_VAR, feature_timezone), SHOW_LONG_STATUS},
+ {"Feature_trigger", (char*) offsetof(STATUS_VAR, feature_trigger), SHOW_LONG_STATUS},
+ {"Feature_xml", (char*) offsetof(STATUS_VAR, feature_xml), SHOW_LONG_STATUS},
+ {"Flush_commands", (char*) &show_flush_commands, SHOW_SIMPLE_FUNC},
+ {"Handler_commit", (char*) offsetof(STATUS_VAR, ha_commit_count), SHOW_LONG_STATUS},
+ {"Handler_delete", (char*) offsetof(STATUS_VAR, ha_delete_count), SHOW_LONG_STATUS},
+ {"Handler_discover", (char*) offsetof(STATUS_VAR, ha_discover_count), SHOW_LONG_STATUS},
+ {"Handler_external_lock", (char*) offsetof(STATUS_VAR, ha_external_lock_count), SHOW_LONGLONG_STATUS},
+ {"Handler_icp_attempts", (char*) offsetof(STATUS_VAR, ha_icp_attempts), SHOW_LONG_STATUS},
+ {"Handler_icp_match", (char*) offsetof(STATUS_VAR, ha_icp_match), SHOW_LONG_STATUS},
+ {"Handler_mrr_init", (char*) offsetof(STATUS_VAR, ha_mrr_init_count), SHOW_LONG_STATUS},
+ {"Handler_mrr_key_refills", (char*) offsetof(STATUS_VAR, ha_mrr_key_refills_count), SHOW_LONG_STATUS},
+ {"Handler_mrr_rowid_refills",(char*) offsetof(STATUS_VAR, ha_mrr_rowid_refills_count), SHOW_LONG_STATUS},
+ {"Handler_prepare", (char*) offsetof(STATUS_VAR, ha_prepare_count), SHOW_LONG_STATUS},
+ {"Handler_read_first", (char*) offsetof(STATUS_VAR, ha_read_first_count), SHOW_LONG_STATUS},
+ {"Handler_read_key", (char*) offsetof(STATUS_VAR, ha_read_key_count), SHOW_LONG_STATUS},
+ {"Handler_read_last", (char*) offsetof(STATUS_VAR, ha_read_last_count), SHOW_LONG_STATUS},
+ {"Handler_read_next", (char*) offsetof(STATUS_VAR, ha_read_next_count), SHOW_LONG_STATUS},
+ {"Handler_read_prev", (char*) offsetof(STATUS_VAR, ha_read_prev_count), SHOW_LONG_STATUS},
+ {"Handler_read_rnd", (char*) offsetof(STATUS_VAR, ha_read_rnd_count), SHOW_LONG_STATUS},
+ {"Handler_read_rnd_deleted", (char*) offsetof(STATUS_VAR, ha_read_rnd_deleted_count), SHOW_LONG_STATUS},
+ {"Handler_read_rnd_next", (char*) offsetof(STATUS_VAR, ha_read_rnd_next_count), SHOW_LONG_STATUS},
+ {"Handler_rollback", (char*) offsetof(STATUS_VAR, ha_rollback_count), SHOW_LONG_STATUS},
+ {"Handler_savepoint", (char*) offsetof(STATUS_VAR, ha_savepoint_count), SHOW_LONG_STATUS},
+ {"Handler_savepoint_rollback",(char*) offsetof(STATUS_VAR, ha_savepoint_rollback_count), SHOW_LONG_STATUS},
+ {"Handler_tmp_update", (char*) offsetof(STATUS_VAR, ha_tmp_update_count), SHOW_LONG_STATUS},
+ {"Handler_tmp_write", (char*) offsetof(STATUS_VAR, ha_tmp_write_count), SHOW_LONG_STATUS},
+ {"Handler_update", (char*) offsetof(STATUS_VAR, ha_update_count), SHOW_LONG_STATUS},
+ {"Handler_write", (char*) offsetof(STATUS_VAR, ha_write_count), SHOW_LONG_STATUS},
+ {"Key", (char*) &show_default_keycache, SHOW_FUNC},
+ {"Last_query_cost", (char*) offsetof(STATUS_VAR, last_query_cost), SHOW_DOUBLE_STATUS},
+ {"Max_used_connections", (char*) &max_used_connections, SHOW_LONG},
+ {"Memory_used", (char*) offsetof(STATUS_VAR, memory_used), SHOW_LONGLONG_STATUS},
+ {"Not_flushed_delayed_rows", (char*) &delayed_rows_in_use, SHOW_LONG_NOFLUSH},
+ {"Open_files", (char*) &my_file_opened, SHOW_LONG_NOFLUSH},
+ {"Open_streams", (char*) &my_stream_opened, SHOW_LONG_NOFLUSH},
+ {"Open_table_definitions", (char*) &show_table_definitions, SHOW_SIMPLE_FUNC},
+ {"Open_tables", (char*) &show_open_tables, SHOW_SIMPLE_FUNC},
+ {"Opened_files", (char*) &my_file_total_opened, SHOW_LONG_NOFLUSH},
+ {"Opened_plugin_libraries", (char*) &dlopen_count, SHOW_LONG},
+ {"Opened_table_definitions", (char*) offsetof(STATUS_VAR, opened_shares), SHOW_LONG_STATUS},
+ {"Opened_tables", (char*) offsetof(STATUS_VAR, opened_tables), SHOW_LONG_STATUS},
+ {"Opened_views", (char*) offsetof(STATUS_VAR, opened_views), SHOW_LONG_STATUS},
+ {"Prepared_stmt_count", (char*) &show_prepared_stmt_count, SHOW_SIMPLE_FUNC},
+ {"Rows_sent", (char*) offsetof(STATUS_VAR, rows_sent), SHOW_LONGLONG_STATUS},
+ {"Rows_read", (char*) offsetof(STATUS_VAR, rows_read), SHOW_LONGLONG_STATUS},
+ {"Rows_tmp_read", (char*) offsetof(STATUS_VAR, rows_tmp_read), SHOW_LONGLONG_STATUS},
+#ifdef HAVE_QUERY_CACHE
+ {"Qcache_free_blocks", (char*) &query_cache.free_memory_blocks, SHOW_LONG_NOFLUSH},
+ {"Qcache_free_memory", (char*) &query_cache.free_memory, SHOW_LONG_NOFLUSH},
+ {"Qcache_hits", (char*) &query_cache.hits, SHOW_LONG},
+ {"Qcache_inserts", (char*) &query_cache.inserts, SHOW_LONG},
+ {"Qcache_lowmem_prunes", (char*) &query_cache.lowmem_prunes, SHOW_LONG},
+ {"Qcache_not_cached", (char*) &query_cache.refused, SHOW_LONG},
+ {"Qcache_queries_in_cache", (char*) &query_cache.queries_in_cache, SHOW_LONG_NOFLUSH},
+ {"Qcache_total_blocks", (char*) &query_cache.total_blocks, SHOW_LONG_NOFLUSH},
+#endif /*HAVE_QUERY_CACHE*/
+ {"Queries", (char*) &show_queries, SHOW_SIMPLE_FUNC},
+ {"Questions", (char*) offsetof(STATUS_VAR, questions), SHOW_LONG_STATUS},
+#ifdef HAVE_REPLICATION
+ {"Rpl_status", (char*) &show_rpl_status, SHOW_SIMPLE_FUNC},
+#endif
+ {"Select_full_join", (char*) offsetof(STATUS_VAR, select_full_join_count_), SHOW_LONG_STATUS},
+ {"Select_full_range_join", (char*) offsetof(STATUS_VAR, select_full_range_join_count_), SHOW_LONG_STATUS},
+ {"Select_range", (char*) offsetof(STATUS_VAR, select_range_count_), SHOW_LONG_STATUS},
+ {"Select_range_check", (char*) offsetof(STATUS_VAR, select_range_check_count_), SHOW_LONG_STATUS},
+ {"Select_scan", (char*) offsetof(STATUS_VAR, select_scan_count_), SHOW_LONG_STATUS},
+ {"Slave_open_temp_tables", (char*) &slave_open_temp_tables, SHOW_INT},
+#ifdef HAVE_REPLICATION
+ {"Slave_heartbeat_period", (char*) &show_heartbeat_period, SHOW_SIMPLE_FUNC},
+ {"Slave_received_heartbeats",(char*) &show_slave_received_heartbeats, SHOW_SIMPLE_FUNC},
+ {"Slave_retried_transactions",(char*)&slave_retried_transactions, SHOW_LONG},
+ {"Slave_running", (char*) &show_slave_running, SHOW_SIMPLE_FUNC},
+#endif
+ {"Slow_launch_threads", (char*) &slow_launch_threads, SHOW_LONG},
+ {"Slow_queries", (char*) offsetof(STATUS_VAR, long_query_count), SHOW_LONG_STATUS},
+ {"Sort_merge_passes", (char*) offsetof(STATUS_VAR, filesort_merge_passes_), SHOW_LONG_STATUS},
+ {"Sort_priority_queue_sorts",(char*) offsetof(STATUS_VAR, filesort_pq_sorts_), SHOW_LONG_STATUS},
+ {"Sort_range", (char*) offsetof(STATUS_VAR, filesort_range_count_), SHOW_LONG_STATUS},
+ {"Sort_rows", (char*) offsetof(STATUS_VAR, filesort_rows_), SHOW_LONG_STATUS},
+ {"Sort_scan", (char*) offsetof(STATUS_VAR, filesort_scan_count_), SHOW_LONG_STATUS},
+#ifdef HAVE_OPENSSL
+#ifndef EMBEDDED_LIBRARY
+ {"Ssl_accept_renegotiates", (char*) &show_ssl_ctx_sess_accept_renegotiate, SHOW_SIMPLE_FUNC},
+ {"Ssl_accepts", (char*) &show_ssl_ctx_sess_accept, SHOW_SIMPLE_FUNC},
+ {"Ssl_callback_cache_hits", (char*) &show_ssl_ctx_sess_cb_hits, SHOW_SIMPLE_FUNC},
+ {"Ssl_cipher", (char*) &show_ssl_get_cipher, SHOW_SIMPLE_FUNC},
+ {"Ssl_cipher_list", (char*) &show_ssl_get_cipher_list, SHOW_SIMPLE_FUNC},
+ {"Ssl_client_connects", (char*) &show_ssl_ctx_sess_connect, SHOW_SIMPLE_FUNC},
+ {"Ssl_connect_renegotiates", (char*) &show_ssl_ctx_sess_connect_renegotiate, SHOW_SIMPLE_FUNC},
+ {"Ssl_ctx_verify_depth", (char*) &show_ssl_ctx_get_verify_depth, SHOW_SIMPLE_FUNC},
+ {"Ssl_ctx_verify_mode", (char*) &show_ssl_ctx_get_verify_mode, SHOW_SIMPLE_FUNC},
+ {"Ssl_default_timeout", (char*) &show_ssl_get_default_timeout, SHOW_SIMPLE_FUNC},
+ {"Ssl_finished_accepts", (char*) &show_ssl_ctx_sess_accept_good, SHOW_SIMPLE_FUNC},
+ {"Ssl_finished_connects", (char*) &show_ssl_ctx_sess_connect_good, SHOW_SIMPLE_FUNC},
+ {"Ssl_server_not_after", (char*) &show_ssl_get_server_not_after, SHOW_SIMPLE_FUNC},
+ {"Ssl_server_not_before", (char*) &show_ssl_get_server_not_before, SHOW_SIMPLE_FUNC},
+ {"Ssl_session_cache_hits", (char*) &show_ssl_ctx_sess_hits, SHOW_SIMPLE_FUNC},
+ {"Ssl_session_cache_misses", (char*) &show_ssl_ctx_sess_misses, SHOW_SIMPLE_FUNC},
+ {"Ssl_session_cache_mode", (char*) &show_ssl_ctx_get_session_cache_mode, SHOW_SIMPLE_FUNC},
+ {"Ssl_session_cache_overflows", (char*) &show_ssl_ctx_sess_cache_full, SHOW_SIMPLE_FUNC},
+ {"Ssl_session_cache_size", (char*) &show_ssl_ctx_sess_get_cache_size, SHOW_SIMPLE_FUNC},
+ {"Ssl_session_cache_timeouts", (char*) &show_ssl_ctx_sess_timeouts, SHOW_SIMPLE_FUNC},
+ {"Ssl_sessions_reused", (char*) &show_ssl_session_reused, SHOW_SIMPLE_FUNC},
+ {"Ssl_used_session_cache_entries",(char*) &show_ssl_ctx_sess_number, SHOW_SIMPLE_FUNC},
+ {"Ssl_verify_depth", (char*) &show_ssl_get_verify_depth, SHOW_SIMPLE_FUNC},
+ {"Ssl_verify_mode", (char*) &show_ssl_get_verify_mode, SHOW_SIMPLE_FUNC},
+ {"Ssl_version", (char*) &show_ssl_get_version, SHOW_SIMPLE_FUNC},
+#endif
+#endif /* HAVE_OPENSSL */
+ {"Syncs", (char*) &my_sync_count, SHOW_LONG_NOFLUSH},
+ /*
+ Expression cache used only for caching subqueries now, so its statistic
+ variables we call subquery_cache*.
+ */
+ {"Subquery_cache_hit", (char*) &subquery_cache_hit, SHOW_LONG},
+ {"Subquery_cache_miss", (char*) &subquery_cache_miss, SHOW_LONG},
+ {"Table_locks_immediate", (char*) &locks_immediate, SHOW_LONG},
+ {"Table_locks_waited", (char*) &locks_waited, SHOW_LONG},
+#ifdef HAVE_MMAP
+ {"Tc_log_max_pages_used", (char*) &tc_log_max_pages_used, SHOW_LONG},
+ {"Tc_log_page_size", (char*) &tc_log_page_size, SHOW_LONG_NOFLUSH},
+ {"Tc_log_page_waits", (char*) &tc_log_page_waits, SHOW_LONG},
+#endif
+#ifdef HAVE_POOL_OF_THREADS
+ {"Threadpool_idle_threads", (char *) &show_threadpool_idle_threads, SHOW_SIMPLE_FUNC},
+ {"Threadpool_threads", (char *) &tp_stats.num_worker_threads, SHOW_INT},
+#endif
+ {"Threads_cached", (char*) &cached_thread_count, SHOW_LONG_NOFLUSH},
+ {"Threads_connected", (char*) &connection_count, SHOW_INT},
+ {"Threads_created", (char*) &thread_created, SHOW_LONG_NOFLUSH},
+ {"Threads_running", (char*) &thread_running, SHOW_INT},
+ {"Uptime", (char*) &show_starttime, SHOW_SIMPLE_FUNC},
+#ifdef ENABLED_PROFILING
+ {"Uptime_since_flush_status",(char*) &show_flushstatustime, SHOW_SIMPLE_FUNC},
+#endif
+ {NullS, NullS, SHOW_LONG}
+};
+
+static bool add_terminator(DYNAMIC_ARRAY *options)
+{
+ my_option empty_element= {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0};
+ return insert_dynamic(options, (uchar *)&empty_element);
+}
+
+static bool add_many_options(DYNAMIC_ARRAY *options, my_option *list,
+ size_t elements)
+{
+ for (my_option *opt= list; opt < list + elements; opt++)
+ if (insert_dynamic(options, opt))
+ return 1;
+ return 0;
+}
+
+#ifndef EMBEDDED_LIBRARY
+static void print_version(void)
+{
+ set_server_version();
+
+ printf("%s Ver %s for %s on %s (%s)\n",my_progname,
+ server_version,SYSTEM_TYPE,MACHINE_TYPE, MYSQL_COMPILATION_COMMENT);
+}
+
+/** Compares two options' names, treats - and _ the same */
+static int option_cmp(my_option *a, my_option *b)
+{
+ const char *sa= a->name;
+ const char *sb= b->name;
+ for (; *sa || *sb; sa++, sb++)
+ {
+ if (*sa < *sb)
+ {
+ if (*sa == '-' && *sb == '_')
+ continue;
+ else
+ return -1;
+ }
+ if (*sa > *sb)
+ {
+ if (*sa == '_' && *sb == '-')
+ continue;
+ else
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void print_help()
+{
+ MEM_ROOT mem_root;
+ init_alloc_root(&mem_root, 4096, 4096, MYF(0));
+
+ pop_dynamic(&all_options);
+ add_many_options(&all_options, pfs_early_options,
+ array_elements(pfs_early_options));
+ sys_var_add_options(&all_options, sys_var::PARSE_EARLY);
+ add_plugin_options(&all_options, &mem_root);
+ sort_dynamic(&all_options, (qsort_cmp) option_cmp);
+ sort_dynamic(&all_options, (qsort_cmp) option_cmp);
+ add_terminator(&all_options);
+
+ my_print_help((my_option*) all_options.buffer);
+
+ /* Add variables that can be shown but not changed, like version numbers */
+ pop_dynamic(&all_options);
+ sys_var_add_options(&all_options, sys_var::SHOW_VALUE_IN_HELP);
+ sort_dynamic(&all_options, (qsort_cmp) option_cmp);
+ add_terminator(&all_options);
+ my_print_variables((my_option*) all_options.buffer);
+
+ free_root(&mem_root, MYF(0));
+}
+
+static void usage(void)
+{
+ DBUG_ENTER("usage");
+ if (!(default_charset_info= get_charset_by_csname(default_character_set_name,
+ MY_CS_PRIMARY,
+ MYF(MY_WME))))
+ exit(1);
+ if (!default_collation_name)
+ default_collation_name= (char*) default_charset_info->name;
+ print_version();
+ puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
+ puts("Starts the MariaDB database server.\n");
+ printf("Usage: %s [OPTIONS]\n", my_progname);
+ if (!opt_verbose)
+ puts("\nFor more help options (several pages), use mysqld --verbose --help.");
+ else
+ {
+#ifdef __WIN__
+ puts("NT and Win32 specific options:\n"
+ " --install Install the default service (NT).\n"
+ " --install-manual Install the default service started manually (NT).\n"
+ " --install service_name Install an optional service (NT).\n"
+ " --install-manual service_name Install an optional service started manually (NT).\n"
+ " --remove Remove the default service from the service list (NT).\n"
+ " --remove service_name Remove the service_name from the service list (NT).\n"
+ " --enable-named-pipe Only to be used for the default server (NT).\n"
+ " --standalone Dummy option to start as a standalone server (NT).");
+ puts("");
+#endif
+ print_defaults(MYSQL_CONFIG_NAME,load_default_groups);
+ puts("");
+ set_ports();
+
+ /* Print out all the options including plugin supplied options */
+ print_help();
+
+ if (! plugins_are_initialized)
+ {
+ puts("\nPlugins have parameters that are not reflected in this list"
+ "\nbecause execution stopped before plugins were initialized.");
+ }
+
+ puts("\nTo see what values a running MySQL server is using, type"
+ "\n'mysqladmin variables' instead of 'mysqld --verbose --help'.");
+ }
+ DBUG_VOID_RETURN;
+}
+#endif /*!EMBEDDED_LIBRARY*/
+
+/**
+ Initialize MySQL global variables to default values.
+
+ @note
+ The reason to set a lot of global variables to zero is to allow one to
+ restart the embedded server with a clean environment
+ It's also needed on some exotic platforms where global variables are
+ not set to 0 when a program starts.
+
+ We don't need to set variables refered to in my_long_options
+ as these are initialized by my_getopt.
+*/
+
+static int mysql_init_variables(void)
+{
+ /* Things reset to zero */
+ opt_skip_slave_start= opt_reckless_slave = 0;
+ mysql_home[0]= pidfile_name[0]= log_error_file[0]= 0;
+#if defined(HAVE_REALPATH) && !defined(HAVE_valgrind) && !defined(HAVE_BROKEN_REALPATH)
+ /* We can only test for sub paths if my_symlink.c is using realpath */
+ myisam_test_invalid_symlink= test_if_data_home_dir;
+#endif
+ opt_log= opt_slow_log= 0;
+ opt_bin_log= opt_bin_log_used= 0;
+ opt_disable_networking= opt_skip_show_db=0;
+ opt_skip_name_resolve= 0;
+ opt_ignore_builtin_innodb= 0;
+ opt_logname= opt_binlog_index_name= opt_slow_logname= 0;
+ opt_log_basename= 0;
+ opt_tc_log_file= (char *)"tc.log"; // no hostname in tc_log file name !
+ opt_secure_auth= 0;
+ opt_bootstrap= opt_myisam_log= 0;
+ mqh_used= 0;
+ kill_in_progress= 0;
+ cleanup_done= 0;
+ server_id_supplied= 0;
+ test_flags= select_errors= dropping_tables= ha_open_options=0;
+ thread_count= thread_running= kill_cached_threads= wake_thread=0;
+ slave_open_temp_tables= 0;
+ cached_thread_count= 0;
+ opt_endinfo= using_udf_functions= 0;
+ opt_using_transactions= 0;
+ abort_loop= select_thread_in_use= signal_thread_in_use= 0;
+ ready_to_exit= shutdown_in_progress= grant_option= 0;
+ aborted_threads= aborted_connects= 0;
+ subquery_cache_miss= subquery_cache_hit= 0;
+ delayed_insert_threads= delayed_insert_writes= delayed_rows_in_use= 0;
+ delayed_insert_errors= thread_created= 0;
+ specialflag= 0;
+ binlog_cache_use= binlog_cache_disk_use= 0;
+ max_used_connections= slow_launch_threads = 0;
+ mysqld_user= mysqld_chroot= opt_init_file= opt_bin_logname = 0;
+ prepared_stmt_count= 0;
+ mysqld_unix_port= opt_mysql_tmpdir= my_bind_addr_str= NullS;
+ bzero((uchar*) &mysql_tmpdir_list, sizeof(mysql_tmpdir_list));
+ bzero((char *) &global_status_var, sizeof(global_status_var));
+ opt_large_pages= 0;
+ opt_super_large_pages= 0;
+#if defined(ENABLED_DEBUG_SYNC)
+ opt_debug_sync_timeout= 0;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+ key_map_full.set_all();
+
+ /* Character sets */
+ system_charset_info= &my_charset_utf8_general_ci;
+ files_charset_info= &my_charset_utf8_general_ci;
+ national_charset_info= &my_charset_utf8_general_ci;
+ table_alias_charset= &my_charset_bin;
+ character_set_filesystem= &my_charset_bin;
+
+ opt_specialflag= SPECIAL_ENGLISH;
+ unix_sock= base_ip_sock= extra_ip_sock= MYSQL_INVALID_SOCKET;
+ mysql_home_ptr= mysql_home;
+ pidfile_name_ptr= pidfile_name;
+ log_error_file_ptr= log_error_file;
+ protocol_version= PROTOCOL_VERSION;
+ what_to_log= ~ (1L << (uint) COM_TIME);
+ denied_connections= 0;
+ executed_events= 0;
+ global_query_id= thread_id= 1L;
+ my_atomic_rwlock_init(&global_query_id_lock);
+ my_atomic_rwlock_init(&thread_running_lock);
+ my_atomic_rwlock_init(&thread_count_lock);
+ my_atomic_rwlock_init(&statistics_lock);
+ my_atomic_rwlock_init(&slave_executed_entries_lock);
+ strmov(server_version, MYSQL_SERVER_VERSION);
+ threads.empty();
+ thread_cache.empty();
+ key_caches.empty();
+ if (!(dflt_key_cache= get_or_create_key_cache(default_key_cache_base.str,
+ default_key_cache_base.length)))
+ {
+ sql_print_error("Cannot allocate the keycache");
+ return 1;
+ }
+
+ /* set key_cache_hash.default_value = dflt_key_cache */
+ multi_keycache_init();
+
+ /* Set directory paths */
+ mysql_real_data_home_len=
+ strmake_buf(mysql_real_data_home,
+ get_relative_path(MYSQL_DATADIR)) - mysql_real_data_home;
+ /* Replication parameters */
+ master_info_file= (char*) "master.info",
+ relay_log_info_file= (char*) "relay-log.info";
+ report_user= report_password = report_host= 0; /* TO BE DELETED */
+ opt_relay_logname= opt_relaylog_index_name= 0;
+ slave_retried_transactions= 0;
+
+ /* Variables in libraries */
+ charsets_dir= 0;
+ default_character_set_name= (char*) MYSQL_DEFAULT_CHARSET_NAME;
+ default_collation_name= compiled_default_collation_name;
+ character_set_filesystem_name= (char*) "binary";
+ lc_messages= (char*) "en_US";
+ lc_time_names_name= (char*) "en_US";
+
+ /* Variables that depends on compile options */
+#ifndef DBUG_OFF
+ default_dbug_option=IF_WIN("d:t:i:O,\\mysqld.trace",
+ "d:t:i:o,/tmp/mysqld.trace");
+ current_dbug_option= default_dbug_option;
+#endif
+ opt_error_log= IF_WIN(1,0);
+#ifdef ENABLED_PROFILING
+ have_profiling = SHOW_OPTION_YES;
+#else
+ have_profiling = SHOW_OPTION_NO;
+#endif
+
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+ have_ssl=SHOW_OPTION_YES;
+#if HAVE_YASSL
+ have_openssl= SHOW_OPTION_NO;
+#else
+ have_openssl= SHOW_OPTION_YES;
+#endif
+#else
+ have_openssl= have_ssl= SHOW_OPTION_NO;
+#endif
+#ifdef HAVE_BROKEN_REALPATH
+ have_symlink=SHOW_OPTION_NO;
+#else
+ have_symlink=SHOW_OPTION_YES;
+#endif
+#ifdef HAVE_DLOPEN
+ have_dlopen=SHOW_OPTION_YES;
+#else
+ have_dlopen=SHOW_OPTION_NO;
+#endif
+#ifdef HAVE_QUERY_CACHE
+ have_query_cache=SHOW_OPTION_YES;
+#else
+ have_query_cache=SHOW_OPTION_NO;
+#endif
+#ifdef HAVE_SPATIAL
+ have_geometry=SHOW_OPTION_YES;
+#else
+ have_geometry=SHOW_OPTION_NO;
+#endif
+#ifdef HAVE_RTREE_KEYS
+ have_rtree_keys=SHOW_OPTION_YES;
+#else
+ have_rtree_keys=SHOW_OPTION_NO;
+#endif
+#ifdef HAVE_CRYPT
+ have_crypt=SHOW_OPTION_YES;
+#else
+ have_crypt=SHOW_OPTION_NO;
+#endif
+#ifdef HAVE_COMPRESS
+ have_compress= SHOW_OPTION_YES;
+#else
+ have_compress= SHOW_OPTION_NO;
+#endif
+#ifdef HAVE_LIBWRAP
+ libwrapName= NullS;
+#endif
+#ifdef HAVE_OPENSSL
+ des_key_file = 0;
+#ifndef EMBEDDED_LIBRARY
+ ssl_acceptor_fd= 0;
+#endif /* ! EMBEDDED_LIBRARY */
+#endif /* HAVE_OPENSSL */
+#ifdef HAVE_SMEM
+ shared_memory_base_name= default_shared_memory_base_name;
+#endif
+
+#if defined(__WIN__)
+ /* Allow Win32 users to move MySQL anywhere */
+ {
+ char prg_dev[LIBLEN];
+ char executing_path_name[LIBLEN];
+ if (!test_if_hard_path(my_progname))
+ {
+ // we don't want to use GetModuleFileName inside of my_path since
+ // my_path is a generic path dereferencing function and here we care
+ // only about the executing binary.
+ GetModuleFileName(NULL, executing_path_name, sizeof(executing_path_name));
+ my_path(prg_dev, executing_path_name, NULL);
+ }
+ else
+ my_path(prg_dev, my_progname, "mysql/bin");
+ strcat(prg_dev,"/../"); // Remove 'bin' to get base dir
+ cleanup_dirname(mysql_home,prg_dev);
+ }
+#else
+ const char *tmpenv;
+ if (!(tmpenv = getenv("MY_BASEDIR_VERSION")))
+ tmpenv = DEFAULT_MYSQL_HOME;
+ strmake_buf(mysql_home, tmpenv);
+#endif
+ return 0;
+}
+
+my_bool
+mysqld_get_one_option(int optid,
+ const struct my_option *opt __attribute__((unused)),
+ char *argument)
+{
+ switch(optid) {
+ case '#':
+#ifndef DBUG_OFF
+ if (!argument)
+ argument= (char*) default_dbug_option;
+ if (argument[0] == '0' && !argument[1])
+ {
+ DEBUGGER_OFF;
+ break;
+ }
+ DEBUGGER_ON;
+ if (argument[0] == '1' && !argument[1])
+ break;
+ DBUG_SET_INITIAL(argument);
+ opt_endinfo=1; /* unireg: memory allocation */
+#else
+ sql_print_warning("'%s' is disabled in this build", opt->name);
+#endif
+ break;
+ case OPT_DEPRECATED_OPTION:
+ sql_print_warning("'%s' is deprecated. It does nothing and exists only "
+ "for compatiblity with old my.cnf files.",
+ opt->name);
+ break;
+ case OPT_MYSQL_COMPATIBILITY:
+ sql_print_warning("'%s' is MySQL 5.6 compatible option. Not used or needed "
+ "in MariaDB.", opt->name);
+ break;
+ case OPT_MYSQL_TO_BE_IMPLEMENTED:
+ sql_print_warning("'%s' is MySQL 5.6 compatible option. To be implemented "
+ "in later versions.", opt->name);
+ break;
+ case 'a':
+ global_system_variables.sql_mode= MODE_ANSI;
+ global_system_variables.tx_isolation= ISO_SERIALIZABLE;
+ break;
+ case 'b':
+ strmake_buf(mysql_home, argument);
+ break;
+ case 'C':
+ if (default_collation_name == compiled_default_collation_name)
+ default_collation_name= 0;
+ break;
+ case 'h':
+ strmake_buf(mysql_real_data_home, argument);
+ /* Correct pointer set by my_getopt (for embedded library) */
+ mysql_real_data_home_ptr= mysql_real_data_home;
+ break;
+ case 'u':
+ if (!mysqld_user || !strcmp(mysqld_user, argument))
+ mysqld_user= argument;
+ else
+ sql_print_warning("Ignoring user change to '%s' because the user was set to '%s' earlier on the command line\n", argument, mysqld_user);
+ break;
+ case 'L':
+ strmake_buf(lc_messages_dir, argument);
+ break;
+ case OPT_BINLOG_FORMAT:
+ binlog_format_used= true;
+ break;
+#include <sslopt-case.h>
+#ifndef EMBEDDED_LIBRARY
+ case 'V':
+ print_version();
+ opt_abort= 1; // Abort after parsing all options
+ break;
+#endif /*EMBEDDED_LIBRARY*/
+ case 'W':
+ if (!argument)
+ global_system_variables.log_warnings++;
+ else if (argument == disabled_my_option)
+ global_system_variables.log_warnings= 0L;
+ else
+ global_system_variables.log_warnings= atoi(argument);
+ break;
+ case 'T':
+ test_flags= argument ? (uint) atoi(argument) : 0;
+ opt_endinfo=1;
+ break;
+ case OPT_THREAD_CONCURRENCY:
+ WARN_DEPRECATED_NO_REPLACEMENT(NULL, "THREAD_CONCURRENCY");
+ break;
+ case (int) OPT_ISAM_LOG:
+ opt_myisam_log=1;
+ break;
+ case (int) OPT_BIN_LOG:
+ opt_bin_log= MY_TEST(argument != disabled_my_option);
+ opt_bin_log_used= 1;
+ break;
+ case (int) OPT_LOG_BASENAME:
+ {
+ if (opt_log_basename[0] == 0 || strchr(opt_log_basename, FN_EXTCHAR) ||
+ strchr(opt_log_basename,FN_LIBCHAR))
+ {
+ sql_print_error("Wrong argument for --log-basename. It can't be empty or contain '.' or '" FN_DIRSEP "'");
+ return 1;
+ }
+ if (log_error_file_ptr != disabled_my_option)
+ log_error_file_ptr= opt_log_basename;
+
+ make_default_log_name(&opt_logname, ".log", false);
+ make_default_log_name(&opt_slow_logname, "-slow.log", false);
+ make_default_log_name(&opt_bin_logname, "-bin", true);
+ make_default_log_name(&opt_binlog_index_name, "-bin.index", true);
+ make_default_log_name(&opt_relay_logname, "-relay-bin", true);
+ make_default_log_name(&opt_relaylog_index_name, "-relay-bin.index", true);
+
+ pidfile_name_ptr= pidfile_name;
+ strmake(pidfile_name, argument, sizeof(pidfile_name)-5);
+ strmov(fn_ext(pidfile_name),".pid");
+
+ /* check for errors */
+ if (!opt_bin_logname || !opt_relaylog_index_name || ! opt_logname ||
+ ! opt_slow_logname || !pidfile_name_ptr)
+ return 1; // out of memory error
+ break;
+ }
+#ifdef HAVE_REPLICATION
+ case (int)OPT_REPLICATE_IGNORE_DB:
+ {
+ cur_rpl_filter->add_ignore_db(argument);
+ break;
+ }
+ case (int)OPT_REPLICATE_DO_DB:
+ {
+ cur_rpl_filter->add_do_db(argument);
+ break;
+ }
+ case (int)OPT_REPLICATE_REWRITE_DB:
+ {
+ /* See also OPT_REWRITE_DB handling in client/mysqlbinlog.cc */
+ char* key = argument,*p, *val;
+
+ if (!(p= strstr(argument, "->")))
+ {
+ sql_print_error("Bad syntax in replicate-rewrite-db - missing '->'!\n");
+ return 1;
+ }
+ val= p--;
+ while (my_isspace(mysqld_charset, *p) && p > argument)
+ *p-- = 0;
+ if (p == argument)
+ {
+ sql_print_error("Bad syntax in replicate-rewrite-db - empty FROM db!\n");
+ return 1;
+ }
+ *val= 0;
+ val+= 2;
+ while (*val && my_isspace(mysqld_charset, *val))
+ val++;
+ if (!*val)
+ {
+ sql_print_error("Bad syntax in replicate-rewrite-db - empty TO db!\n");
+ return 1;
+ }
+
+ cur_rpl_filter->add_db_rewrite(key, val);
+ break;
+ }
+
+ case (int)OPT_BINLOG_IGNORE_DB:
+ {
+ binlog_filter->add_ignore_db(argument);
+ break;
+ }
+ case (int)OPT_BINLOG_DO_DB:
+ {
+ binlog_filter->add_do_db(argument);
+ break;
+ }
+ case (int)OPT_REPLICATE_DO_TABLE:
+ {
+ if (cur_rpl_filter->add_do_table(argument))
+ {
+ sql_print_error("Could not add do table rule '%s'!\n", argument);
+ return 1;
+ }
+ break;
+ }
+ case (int)OPT_REPLICATE_WILD_DO_TABLE:
+ {
+ if (cur_rpl_filter->add_wild_do_table(argument))
+ {
+ sql_print_error("Could not add do table rule '%s'!\n", argument);
+ return 1;
+ }
+ break;
+ }
+ case (int)OPT_REPLICATE_WILD_IGNORE_TABLE:
+ {
+ if (cur_rpl_filter->add_wild_ignore_table(argument))
+ {
+ sql_print_error("Could not add ignore table rule '%s'!\n", argument);
+ return 1;
+ }
+ break;
+ }
+ case (int)OPT_REPLICATE_IGNORE_TABLE:
+ {
+ if (cur_rpl_filter->add_ignore_table(argument))
+ {
+ sql_print_error("Could not add ignore table rule '%s'!\n", argument);
+ return 1;
+ }
+ break;
+ }
+#endif /* HAVE_REPLICATION */
+ case (int) OPT_SAFE:
+ opt_specialflag|= SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC;
+ delay_key_write_options= (uint) DELAY_KEY_WRITE_NONE;
+ myisam_recover_options= HA_RECOVER_DEFAULT;
+ ha_open_options&= ~(HA_OPEN_DELAY_KEY_WRITE);
+#ifdef HAVE_QUERY_CACHE
+ query_cache_size=0;
+#endif
+ sql_print_warning("The syntax '--safe-mode' is deprecated and will be "
+ "removed in a future release.");
+ break;
+ case (int) OPT_SKIP_HOST_CACHE:
+ opt_specialflag|= SPECIAL_NO_HOST_CACHE;
+ break;
+ case (int) OPT_SKIP_RESOLVE:
+ opt_skip_name_resolve= 1;
+ opt_specialflag|=SPECIAL_NO_RESOLVE;
+ break;
+ case (int) OPT_WANT_CORE:
+ test_flags |= TEST_CORE_ON_SIGNAL;
+ break;
+ case OPT_CONSOLE:
+ if (opt_console)
+ opt_error_log= 0; // Force logs to stdout
+ break;
+ case OPT_BOOTSTRAP:
+ opt_noacl=opt_bootstrap=1;
+ break;
+ case OPT_SERVER_ID:
+ server_id_supplied = 1;
+ ::server_id= global_system_variables.server_id;
+ break;
+ case OPT_LOWER_CASE_TABLE_NAMES:
+ lower_case_table_names_used= 1;
+ break;
+#if defined(ENABLED_DEBUG_SYNC)
+ case OPT_DEBUG_SYNC_TIMEOUT:
+ /*
+ Debug Sync Facility. See debug_sync.cc.
+ Default timeout for WAIT_FOR action.
+ Default value is zero (facility disabled).
+ If option is given without an argument, supply a non-zero value.
+ */
+ if (!argument)
+ {
+ /* purecov: begin tested */
+ opt_debug_sync_timeout= DEBUG_SYNC_DEFAULT_WAIT_TIMEOUT;
+ /* purecov: end */
+ }
+ break;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+ case OPT_LOG_ERROR:
+ /*
+ "No --log-error" == "write errors to stderr",
+ "--log-error without argument" == "write errors to a file".
+ */
+ if (argument == NULL) /* no argument */
+ log_error_file_ptr= const_cast<char*>("");
+ break;
+ case OPT_IGNORE_DB_DIRECTORY:
+ if (*argument == 0)
+ ignore_db_dirs_reset();
+ else
+ {
+ if (push_ignored_db_dir(argument))
+ {
+ sql_print_error("Can't start server: "
+ "cannot process --ignore-db-dir=%.*s",
+ FN_REFLEN, argument);
+ return 1;
+ }
+ }
+ break;
+
+ case OPT_PLUGIN_LOAD:
+ free_list(opt_plugin_load_list_ptr);
+ /* fall through */
+ case OPT_PLUGIN_LOAD_ADD:
+ opt_plugin_load_list_ptr->push_back(new i_string(argument));
+ break;
+ case OPT_MAX_LONG_DATA_SIZE:
+ max_long_data_size_used= true;
+ break;
+ case OPT_PFS_INSTRUMENT:
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+#ifndef EMBEDDED_LIBRARY
+ /* Parse instrument name and value from argument string */
+ char* name = argument,*p, *val;
+
+ /* Assignment required */
+ if (!(p= strchr(argument, '=')))
+ {
+ my_getopt_error_reporter(WARNING_LEVEL,
+ "Missing value for performance_schema_instrument "
+ "'%s'", argument);
+ return 0;
+ }
+
+ /* Option value */
+ val= p + 1;
+ if (!*val)
+ {
+ my_getopt_error_reporter(WARNING_LEVEL,
+ "Missing value for performance_schema_instrument "
+ "'%s'", argument);
+ return 0;
+ }
+
+ /* Trim leading spaces from instrument name */
+ while (*name && my_isspace(mysqld_charset, *name))
+ name++;
+
+ /* Trim trailing spaces and slashes from instrument name */
+ while (p > argument && (my_isspace(mysqld_charset, p[-1]) || p[-1] == '/'))
+ p--;
+ *p= 0;
+
+ if (!*name)
+ {
+ my_getopt_error_reporter(WARNING_LEVEL,
+ "Invalid instrument name for "
+ "performance_schema_instrument '%s'", argument);
+ return 0;
+ }
+
+ /* Trim leading spaces from option value */
+ while (*val && my_isspace(mysqld_charset, *val))
+ val++;
+
+ /* Trim trailing spaces from option value */
+ if ((p= my_strchr(mysqld_charset, val, val+strlen(val), ' ')) != NULL)
+ *p= 0;
+
+ if (!*val)
+ {
+ my_getopt_error_reporter(WARNING_LEVEL,
+ "Invalid value for performance_schema_instrument "
+ "'%s'", argument);
+ return 0;
+ }
+
+ /* Add instrument name and value to array of configuration options */
+ if (add_pfs_instr_to_array(name, val))
+ {
+ my_getopt_error_reporter(WARNING_LEVEL,
+ "Invalid value for performance_schema_instrument "
+ "'%s'", argument);
+ return 0;
+ }
+#endif /* EMBEDDED_LIBRARY */
+#endif
+ break;
+ }
+ return 0;
+}
+
+
+/** Handle arguments for multiple key caches. */
+
+C_MODE_START
+
+static void*
+mysql_getopt_value(const char *name, uint length,
+ const struct my_option *option, int *error)
+{
+ if (error)
+ *error= 0;
+ switch (option->id) {
+ case OPT_KEY_BUFFER_SIZE:
+ case OPT_KEY_CACHE_BLOCK_SIZE:
+ case OPT_KEY_CACHE_DIVISION_LIMIT:
+ case OPT_KEY_CACHE_AGE_THRESHOLD:
+ case OPT_KEY_CACHE_PARTITIONS:
+ case OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE:
+ {
+ KEY_CACHE *key_cache;
+ if (!(key_cache= get_or_create_key_cache(name, length)))
+ {
+ if (error)
+ *error= EXIT_OUT_OF_MEMORY;
+ return 0;
+ }
+ switch (option->id) {
+ case OPT_KEY_BUFFER_SIZE:
+ return &key_cache->param_buff_size;
+ case OPT_KEY_CACHE_BLOCK_SIZE:
+ return &key_cache->param_block_size;
+ case OPT_KEY_CACHE_DIVISION_LIMIT:
+ return &key_cache->param_division_limit;
+ case OPT_KEY_CACHE_AGE_THRESHOLD:
+ return &key_cache->param_age_threshold;
+ case OPT_KEY_CACHE_PARTITIONS:
+ return (uchar**) &key_cache->param_partitions;
+ case OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE:
+ return (uchar**) &key_cache->changed_blocks_hash_size;
+ }
+ }
+ case OPT_REPLICATE_DO_DB:
+ case OPT_REPLICATE_DO_TABLE:
+ case OPT_REPLICATE_IGNORE_DB:
+ case OPT_REPLICATE_IGNORE_TABLE:
+ case OPT_REPLICATE_WILD_DO_TABLE:
+ case OPT_REPLICATE_WILD_IGNORE_TABLE:
+ case OPT_REPLICATE_REWRITE_DB:
+ {
+ /* Store current filter for mysqld_get_one_option() */
+ if (!(cur_rpl_filter= get_or_create_rpl_filter(name, length)))
+ {
+ if (error)
+ *error= EXIT_OUT_OF_MEMORY;
+ }
+ return 0;
+ }
+ }
+ return option->value;
+}
+
+static void option_error_reporter(enum loglevel level, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+
+ /* Don't print warnings for --loose options during bootstrap */
+ if (level == ERROR_LEVEL || !opt_bootstrap ||
+ global_system_variables.log_warnings)
+ {
+ vprint_msg_to_log(level, format, args);
+ }
+ va_end(args);
+}
+
+C_MODE_END
+
+/**
+ Get server options from the command line,
+ and perform related server initializations.
+ @param [in, out] argc_ptr command line options (count)
+ @param [in, out] argv_ptr command line options (values)
+ @return 0 on success
+
+ @todo
+ - FIXME add EXIT_TOO_MANY_ARGUMENTS to "mysys_err.h" and return that code?
+*/
+static int get_options(int *argc_ptr, char ***argv_ptr)
+{
+ int ho_error;
+
+ my_getopt_register_get_addr(mysql_getopt_value);
+ my_getopt_error_reporter= option_error_reporter;
+
+ /* prepare all_options array */
+ my_init_dynamic_array(&all_options, sizeof(my_option),
+ array_elements(my_long_options),
+ array_elements(my_long_options)/4, MYF(0));
+ add_many_options(&all_options, my_long_options, array_elements(my_long_options));
+ sys_var_add_options(&all_options, 0);
+ add_terminator(&all_options);
+
+ /* Skip unknown options so that they may be processed later by plugins */
+ my_getopt_skip_unknown= TRUE;
+
+ if ((ho_error= handle_options(argc_ptr, argv_ptr, (my_option*)(all_options.buffer),
+ mysqld_get_one_option)))
+ return ho_error;
+
+ if (!opt_help)
+ delete_dynamic(&all_options);
+ else
+ opt_abort= 1;
+
+ /* Add back the program name handle_options removes */
+ (*argc_ptr)++;
+ (*argv_ptr)--;
+
+ /*
+ Options have been parsed. Now some of them need additional special
+ handling, like custom value checking, checking of incompatibilites
+ between options, setting of multiple variables, etc.
+ Do them here.
+ */
+
+ if ((opt_log_slow_admin_statements || opt_log_queries_not_using_indexes ||
+ opt_log_slow_slave_statements) &&
+ !opt_slow_log)
+ sql_print_warning("options --log-slow-admin-statements, --log-queries-not-using-indexes and --log-slow-slave-statements have no effect if --log_slow_queries is not set");
+ if (global_system_variables.net_buffer_length >
+ global_system_variables.max_allowed_packet)
+ {
+ sql_print_warning("net_buffer_length (%lu) is set to be larger "
+ "than max_allowed_packet (%lu). Please rectify.",
+ global_system_variables.net_buffer_length,
+ global_system_variables.max_allowed_packet);
+ }
+
+ if (log_error_file_ptr != disabled_my_option)
+ opt_error_log= 1;
+ else
+ log_error_file_ptr= const_cast<char*>("");
+
+ opt_init_connect.length=strlen(opt_init_connect.str);
+ opt_init_slave.length=strlen(opt_init_slave.str);
+
+ if (global_system_variables.low_priority_updates)
+ thr_upgraded_concurrent_insert_lock= TL_WRITE_LOW_PRIORITY;
+
+ if (ft_boolean_check_syntax_string((uchar*) ft_boolean_syntax))
+ {
+ sql_print_error("Invalid ft-boolean-syntax string: %s\n",
+ ft_boolean_syntax);
+ return 1;
+ }
+
+ if (opt_disable_networking)
+ mysqld_port= mysqld_extra_port= 0;
+
+ if (opt_skip_show_db)
+ opt_specialflag|= SPECIAL_SKIP_SHOW_DB;
+
+ if (myisam_flush)
+ flush_time= 0;
+
+#ifdef HAVE_REPLICATION
+ if (opt_slave_skip_errors)
+ init_slave_skip_errors(opt_slave_skip_errors);
+#endif
+
+ if (global_system_variables.max_join_size == HA_POS_ERROR)
+ global_system_variables.option_bits|= OPTION_BIG_SELECTS;
+ else
+ global_system_variables.option_bits&= ~OPTION_BIG_SELECTS;
+
+ // Synchronize @@global.autocommit on --autocommit
+ const ulonglong turn_bit_on= opt_autocommit ?
+ OPTION_AUTOCOMMIT : OPTION_NOT_AUTOCOMMIT;
+ global_system_variables.option_bits=
+ (global_system_variables.option_bits &
+ ~(OPTION_NOT_AUTOCOMMIT | OPTION_AUTOCOMMIT)) | turn_bit_on;
+
+ global_system_variables.sql_mode=
+ expand_sql_mode(global_system_variables.sql_mode);
+#if !defined(HAVE_REALPATH) || defined(HAVE_BROKEN_REALPATH)
+ my_use_symdir=0;
+ my_disable_symlinks=1;
+ have_symlink=SHOW_OPTION_NO;
+#else
+ if (!my_use_symdir)
+ {
+ my_disable_symlinks=1;
+ have_symlink=SHOW_OPTION_DISABLED;
+ }
+#endif
+ if (opt_debugging)
+ {
+ /* Allow break with SIGINT, no core or stack trace */
+ test_flags|= TEST_SIGINT;
+ opt_stack_trace= 1;
+ test_flags&= ~TEST_CORE_ON_SIGNAL;
+ }
+ /* Set global MyISAM variables from delay_key_write_options */
+ fix_delay_key_write(0, 0, OPT_GLOBAL);
+
+#ifndef EMBEDDED_LIBRARY
+ if (mysqld_chroot)
+ set_root(mysqld_chroot);
+#else
+ thread_handling = SCHEDULER_NO_THREADS;
+ max_allowed_packet= global_system_variables.max_allowed_packet;
+ net_buffer_length= global_system_variables.net_buffer_length;
+#endif
+ if (fix_paths())
+ return 1;
+
+ /*
+ Set some global variables from the global_system_variables
+ In most cases the global variables will not be used
+ */
+ my_disable_locking= myisam_single_user= MY_TEST(opt_external_locking == 0);
+ my_default_record_cache_size=global_system_variables.read_buff_size;
+
+ /*
+ Log mysys errors when we don't have a thd or thd->log_all_errors is set
+ (recovery) to the log. This is mainly useful for debugging strange system
+ errors.
+ */
+ if (global_system_variables.log_warnings >= 10)
+ my_global_flags= MY_WME | ME_JUST_INFO;
+ /* Log all errors not handled by thd->handle_error() to my_message_sql() */
+ if (global_system_variables.log_warnings >= 11)
+ my_global_flags|= ME_NOREFRESH;
+ if (my_assert_on_error)
+ debug_assert_if_crashed_table= 1;
+
+ global_system_variables.long_query_time= (ulonglong)
+ (global_system_variables.long_query_time_double * 1e6);
+
+ if (opt_short_log_format)
+ opt_specialflag|= SPECIAL_SHORT_LOG_FORMAT;
+
+ if (init_global_datetime_format(MYSQL_TIMESTAMP_DATE,
+ &global_date_format) ||
+ init_global_datetime_format(MYSQL_TIMESTAMP_TIME,
+ &global_time_format) ||
+ init_global_datetime_format(MYSQL_TIMESTAMP_DATETIME,
+ &global_datetime_format))
+ return 1;
+
+#ifdef EMBEDDED_LIBRARY
+ one_thread_scheduler(thread_scheduler);
+ one_thread_scheduler(extra_thread_scheduler);
+#else
+
+#ifdef _WIN32
+ /* workaround: disable thread pool on XP */
+ if (GetProcAddress(GetModuleHandle("kernel32"),"CreateThreadpool") == 0 &&
+ thread_handling > SCHEDULER_NO_THREADS)
+ thread_handling = SCHEDULER_ONE_THREAD_PER_CONNECTION;
+#endif
+
+ if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION)
+ one_thread_per_connection_scheduler(thread_scheduler, &max_connections,
+ &connection_count);
+ else if (thread_handling == SCHEDULER_NO_THREADS)
+ one_thread_scheduler(thread_scheduler);
+ else
+ pool_of_threads_scheduler(thread_scheduler, &max_connections,
+ &connection_count);
+
+ one_thread_per_connection_scheduler(extra_thread_scheduler,
+ &extra_max_connections,
+ &extra_connection_count);
+#endif
+
+ global_system_variables.engine_condition_pushdown=
+ MY_TEST(global_system_variables.optimizer_switch &
+ OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN);
+
+ opt_readonly= read_only;
+
+ /*
+ If max_long_data_size is not specified explicitly use
+ value of max_allowed_packet.
+ */
+ if (!max_long_data_size_used)
+ max_long_data_size= global_system_variables.max_allowed_packet;
+
+ /* Remember if max_user_connections was 0 at startup */
+ max_user_connections_checking= global_system_variables.max_user_connections != 0;
+
+ {
+ sys_var *max_relay_log_size_var, *max_binlog_size_var;
+ /* If max_relay_log_size is 0, then set it to max_binlog_size */
+ if (!global_system_variables.max_relay_log_size)
+ global_system_variables.max_relay_log_size= max_binlog_size;
+
+ /*
+ Fix so that DEFAULT and limit checking works with max_relay_log_size
+ (Yes, this is a hack, but it's required as the definition of
+ max_relay_log_size allows it to be set to 0).
+ */
+ max_relay_log_size_var= intern_find_sys_var("max_relay_log_size", 0);
+ max_binlog_size_var= intern_find_sys_var("max_binlog_size", 0);
+ if (max_binlog_size_var && max_relay_log_size_var)
+ {
+ max_relay_log_size_var->option.min_value=
+ max_binlog_size_var->option.min_value;
+ max_relay_log_size_var->option.def_value=
+ max_binlog_size_var->option.def_value;
+ }
+ }
+
+ /* Ensure that some variables are not set higher than needed */
+ if (back_log > max_connections)
+ back_log= max_connections;
+ if (thread_cache_size > max_connections)
+ thread_cache_size= max_connections;
+
+ return 0;
+}
+
+
+/*
+ Create version name for running mysqld version
+ We automaticly add suffixes -debug, -embedded and -log to the version
+ name to make the version more descriptive.
+ (MYSQL_SERVER_SUFFIX is set by the compilation environment)
+*/
+
+void set_server_version(void)
+{
+ char *end= strxmov(server_version, MYSQL_SERVER_VERSION,
+ MYSQL_SERVER_SUFFIX_STR, NullS);
+#ifdef EMBEDDED_LIBRARY
+ end= strmov(end, "-embedded");
+#endif
+#ifndef DBUG_OFF
+ if (!strstr(MYSQL_SERVER_SUFFIX_STR, "-debug"))
+ end= strmov(end, "-debug");
+#endif
+ if (opt_log || opt_slow_log || opt_bin_log)
+ strmov(end, "-log"); // This may slow down system
+}
+
+
+static char *get_relative_path(const char *path)
+{
+ if (test_if_hard_path(path) &&
+ is_prefix(path,DEFAULT_MYSQL_HOME) &&
+ strcmp(DEFAULT_MYSQL_HOME,FN_ROOTDIR))
+ {
+ path+=(uint) strlen(DEFAULT_MYSQL_HOME);
+ while (*path == FN_LIBCHAR || *path == FN_LIBCHAR2)
+ path++;
+ }
+ return (char*) path;
+}
+
+
+/**
+ Fix filename and replace extension where 'dir' is relative to
+ mysql_real_data_home.
+ @return
+ 1 if len(path) > FN_REFLEN
+*/
+
+bool
+fn_format_relative_to_data_home(char * to, const char *name,
+ const char *dir, const char *extension)
+{
+ char tmp_path[FN_REFLEN];
+ if (!test_if_hard_path(dir))
+ {
+ strxnmov(tmp_path,sizeof(tmp_path)-1, mysql_real_data_home,
+ dir, NullS);
+ dir=tmp_path;
+ }
+ return !fn_format(to, name, dir, extension,
+ MY_APPEND_EXT | MY_UNPACK_FILENAME | MY_SAFE_PATH);
+}
+
+
+/**
+ Test a file path to determine if the path is compatible with the secure file
+ path restriction.
+
+ @param path null terminated character string
+
+ @return
+ @retval TRUE The path is secure
+ @retval FALSE The path isn't secure
+*/
+
+bool is_secure_file_path(char *path)
+{
+ char buff1[FN_REFLEN], buff2[FN_REFLEN];
+ size_t opt_secure_file_priv_len;
+ /*
+ All paths are secure if opt_secure_file_path is 0
+ */
+ if (!opt_secure_file_priv)
+ return TRUE;
+
+ opt_secure_file_priv_len= strlen(opt_secure_file_priv);
+
+ if (strlen(path) >= FN_REFLEN)
+ return FALSE;
+
+ if (my_realpath(buff1, path, 0))
+ {
+ /*
+ The supplied file path might have been a file and not a directory.
+ */
+ size_t length= dirname_length(path); // Guaranteed to be < FN_REFLEN
+ memcpy(buff2, path, length);
+ buff2[length]= '\0';
+ if (length == 0 || my_realpath(buff1, buff2, 0))
+ return FALSE;
+ }
+ convert_dirname(buff2, buff1, NullS);
+ if (!lower_case_file_system)
+ {
+ if (strncmp(opt_secure_file_priv, buff2, opt_secure_file_priv_len))
+ return FALSE;
+ }
+ else
+ {
+ if (files_charset_info->coll->strnncoll(files_charset_info,
+ (uchar *) buff2, strlen(buff2),
+ (uchar *) opt_secure_file_priv,
+ opt_secure_file_priv_len,
+ TRUE))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+static int fix_paths(void)
+{
+ char buff[FN_REFLEN],*pos;
+ DBUG_ENTER("fix_paths");
+
+ convert_dirname(mysql_home,mysql_home,NullS);
+ /* Resolve symlinks to allow 'mysql_home' to be a relative symlink */
+ my_realpath(mysql_home,mysql_home,MYF(0));
+ /* Ensure that mysql_home ends in FN_LIBCHAR */
+ pos=strend(mysql_home);
+ if (pos[-1] != FN_LIBCHAR)
+ {
+ pos[0]= FN_LIBCHAR;
+ pos[1]= 0;
+ }
+ convert_dirname(lc_messages_dir, lc_messages_dir, NullS);
+ convert_dirname(mysql_real_data_home,mysql_real_data_home,NullS);
+ (void) my_load_path(mysql_home,mysql_home,""); // Resolve current dir
+ (void) my_load_path(mysql_real_data_home,mysql_real_data_home,mysql_home);
+ (void) my_load_path(pidfile_name, pidfile_name_ptr, mysql_real_data_home);
+
+ convert_dirname(opt_plugin_dir, opt_plugin_dir_ptr ? opt_plugin_dir_ptr :
+ get_relative_path(PLUGINDIR), NullS);
+ (void) my_load_path(opt_plugin_dir, opt_plugin_dir, mysql_home);
+ opt_plugin_dir_ptr= opt_plugin_dir;
+ pidfile_name_ptr= pidfile_name;
+
+ my_realpath(mysql_unpacked_real_data_home, mysql_real_data_home, MYF(0));
+ mysql_unpacked_real_data_home_len=
+ (int) strlen(mysql_unpacked_real_data_home);
+ if (mysql_unpacked_real_data_home[mysql_unpacked_real_data_home_len-1] == FN_LIBCHAR)
+ --mysql_unpacked_real_data_home_len;
+
+ char *sharedir=get_relative_path(SHAREDIR);
+ if (test_if_hard_path(sharedir))
+ strmake_buf(buff, sharedir); /* purecov: tested */
+ else
+ strxnmov(buff,sizeof(buff)-1,mysql_home,sharedir,NullS);
+ convert_dirname(buff,buff,NullS);
+ (void) my_load_path(lc_messages_dir, lc_messages_dir, buff);
+
+ /* If --character-sets-dir isn't given, use shared library dir */
+ if (charsets_dir)
+ strmake_buf(mysql_charsets_dir, charsets_dir);
+ else
+ strxnmov(mysql_charsets_dir, sizeof(mysql_charsets_dir)-1, buff,
+ CHARSET_DIR, NullS);
+ (void) my_load_path(mysql_charsets_dir, mysql_charsets_dir, buff);
+ convert_dirname(mysql_charsets_dir, mysql_charsets_dir, NullS);
+ charsets_dir=mysql_charsets_dir;
+
+ if (init_tmpdir(&mysql_tmpdir_list, opt_mysql_tmpdir))
+ DBUG_RETURN(1);
+ if (!opt_mysql_tmpdir)
+ opt_mysql_tmpdir= mysql_tmpdir;
+#ifdef HAVE_REPLICATION
+ if (!slave_load_tmpdir)
+ slave_load_tmpdir= mysql_tmpdir;
+#endif /* HAVE_REPLICATION */
+ /*
+ Convert the secure-file-priv option to system format, allowing
+ a quick strcmp to check if read or write is in an allowed dir
+ */
+ if (opt_secure_file_priv)
+ {
+ if (*opt_secure_file_priv == 0)
+ {
+ my_free(opt_secure_file_priv);
+ opt_secure_file_priv= 0;
+ }
+ else
+ {
+ if (strlen(opt_secure_file_priv) >= FN_REFLEN)
+ opt_secure_file_priv[FN_REFLEN-1]= '\0';
+ if (my_realpath(buff, opt_secure_file_priv, 0))
+ {
+ sql_print_warning("Failed to normalize the argument for --secure-file-priv.");
+ DBUG_RETURN(1);
+ }
+ char *secure_file_real_path= (char *)my_malloc(FN_REFLEN, MYF(MY_FAE));
+ convert_dirname(secure_file_real_path, buff, NullS);
+ my_free(opt_secure_file_priv);
+ opt_secure_file_priv= secure_file_real_path;
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+/**
+ Check if file system used for databases is case insensitive.
+
+ @param dir_name Directory to test
+
+ @retval -1 Don't know (Test failed)
+ @retval 0 File system is case sensitive
+ @retval 1 File system is case insensitive
+*/
+
+static int test_if_case_insensitive(const char *dir_name)
+{
+ int result= 0;
+ File file;
+ char buff[FN_REFLEN], buff2[FN_REFLEN];
+ MY_STAT stat_info;
+ DBUG_ENTER("test_if_case_insensitive");
+
+ fn_format(buff, glob_hostname, dir_name, ".lower-test",
+ MY_UNPACK_FILENAME | MY_REPLACE_EXT | MY_REPLACE_DIR);
+ fn_format(buff2, glob_hostname, dir_name, ".LOWER-TEST",
+ MY_UNPACK_FILENAME | MY_REPLACE_EXT | MY_REPLACE_DIR);
+ mysql_file_delete(key_file_casetest, buff2, MYF(0));
+ if ((file= mysql_file_create(key_file_casetest,
+ buff, 0666, O_RDWR, MYF(0))) < 0)
+ {
+ if (!opt_abort)
+ sql_print_warning("Can't create test file %s", buff);
+ DBUG_RETURN(-1);
+ }
+ mysql_file_close(file, MYF(0));
+ if (mysql_file_stat(key_file_casetest, buff2, &stat_info, MYF(0)))
+ result= 1; // Can access file
+ mysql_file_delete(key_file_casetest, buff, MYF(MY_WME));
+ DBUG_PRINT("exit", ("result: %d", result));
+ DBUG_RETURN(result);
+}
+
+
+#ifndef EMBEDDED_LIBRARY
+
+/**
+ Create file to store pid number.
+*/
+static void create_pid_file()
+{
+ File file;
+ if ((file= mysql_file_create(key_file_pid, pidfile_name, 0664,
+ O_WRONLY | O_TRUNC, MYF(MY_WME))) >= 0)
+ {
+ char buff[MAX_BIGINT_WIDTH + 1], *end;
+ end= int10_to_str((long) getpid(), buff, 10);
+ *end++= '\n';
+ if (!mysql_file_write(file, (uchar*) buff, (uint) (end-buff),
+ MYF(MY_WME | MY_NABP)))
+ {
+ mysql_file_close(file, MYF(0));
+ pid_file_created= true;
+ return;
+ }
+ mysql_file_close(file, MYF(0));
+ }
+ sql_perror("Can't start server: can't create PID file");
+ exit(1);
+}
+#endif /* EMBEDDED_LIBRARY */
+
+
+/**
+ Remove the process' pid file.
+
+ @param flags file operation flags
+*/
+
+static void delete_pid_file(myf flags)
+{
+#ifndef EMBEDDED_LIBRARY
+ if (pid_file_created)
+ {
+ mysql_file_delete(key_file_pid, pidfile_name, flags);
+ pid_file_created= false;
+ }
+#endif /* EMBEDDED_LIBRARY */
+ return;
+}
+
+
+/** Clear most status variables. */
+void refresh_status(THD *thd)
+{
+ mysql_mutex_lock(&LOCK_status);
+
+ /* Add thread's status variabes to global status */
+ add_to_status(&global_status_var, &thd->status_var);
+
+ /* Reset thread's status variables */
+ thd->set_status_var_init();
+ bzero((uchar*) &thd->org_status_var, sizeof(thd->org_status_var));
+ thd->start_bytes_received= 0;
+
+ /* Reset some global variables */
+ reset_status_vars();
+
+ /* Reset the counters of all key caches (default and named). */
+ process_key_caches(reset_key_cache_counters, 0);
+ flush_status_time= time((time_t*) 0);
+ mysql_mutex_unlock(&LOCK_status);
+
+ /*
+ Set max_used_connections to the number of currently open
+ connections. This is not perfect, but status data is not exact anyway.
+ */
+ max_used_connections= thread_count-delayed_insert_threads;
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_file_info all_server_files[]=
+{
+#ifdef HAVE_MMAP
+ { &key_file_map, "map", 0},
+#endif /* HAVE_MMAP */
+ { &key_file_binlog, "binlog", 0},
+ { &key_file_binlog_index, "binlog_index", 0},
+ { &key_file_relaylog, "relaylog", 0},
+ { &key_file_relaylog_index, "relaylog_index", 0},
+ { &key_file_casetest, "casetest", 0},
+ { &key_file_dbopt, "dbopt", 0},
+ { &key_file_des_key_file, "des_key_file", 0},
+ { &key_file_ERRMSG, "ERRMSG", 0},
+ { &key_select_to_file, "select_to_file", 0},
+ { &key_file_fileparser, "file_parser", 0},
+ { &key_file_frm, "FRM", 0},
+ { &key_file_global_ddl_log, "global_ddl_log", 0},
+ { &key_file_load, "load", 0},
+ { &key_file_loadfile, "LOAD_FILE", 0},
+ { &key_file_log_event_data, "log_event_data", 0},
+ { &key_file_log_event_info, "log_event_info", 0},
+ { &key_file_master_info, "master_info", 0},
+ { &key_file_misc, "misc", 0},
+ { &key_file_partition, "partition", 0},
+ { &key_file_pid, "pid", 0},
+ { &key_file_query_log, "query_log", 0},
+ { &key_file_relay_log_info, "relay_log_info", 0},
+ { &key_file_send_file, "send_file", 0},
+ { &key_file_slow_log, "slow_log", 0},
+ { &key_file_tclog, "tclog", 0},
+ { &key_file_trg, "trigger_name", 0},
+ { &key_file_trn, "trigger", 0},
+ { &key_file_init, "init", 0},
+ { &key_file_binlog_state, "binlog_state", 0}
+};
+#endif /* HAVE_PSI_INTERFACE */
+
+PSI_stage_info stage_after_create= { 0, "After create", 0};
+PSI_stage_info stage_after_opening_tables= { 0, "After opening tables", 0};
+PSI_stage_info stage_after_table_lock= { 0, "After table lock", 0};
+PSI_stage_info stage_allocating_local_table= { 0, "allocating local table", 0};
+PSI_stage_info stage_alter_inplace_prepare= { 0, "preparing for alter table", 0};
+PSI_stage_info stage_alter_inplace= { 0, "altering table", 0};
+PSI_stage_info stage_alter_inplace_commit= { 0, "committing alter table to storage engine", 0};
+PSI_stage_info stage_changing_master= { 0, "Changing master", 0};
+PSI_stage_info stage_checking_master_version= { 0, "Checking master version", 0};
+PSI_stage_info stage_checking_permissions= { 0, "checking permissions", 0};
+PSI_stage_info stage_checking_privileges_on_cached_query= { 0, "checking privileges on cached query", 0};
+PSI_stage_info stage_checking_query_cache_for_query= { 0, "checking query cache for query", 0};
+PSI_stage_info stage_cleaning_up= { 0, "cleaning up", 0};
+PSI_stage_info stage_closing_tables= { 0, "closing tables", 0};
+PSI_stage_info stage_connecting_to_master= { 0, "Connecting to master", 0};
+PSI_stage_info stage_converting_heap_to_myisam= { 0, "converting HEAP to " TMP_ENGINE_NAME, 0};
+PSI_stage_info stage_copying_to_group_table= { 0, "Copying to group table", 0};
+PSI_stage_info stage_copying_to_tmp_table= { 0, "Copying to tmp table", 0};
+PSI_stage_info stage_copy_to_tmp_table= { 0, "copy to tmp table", 0};
+PSI_stage_info stage_creating_delayed_handler= { 0, "Creating delayed handler", 0};
+PSI_stage_info stage_creating_sort_index= { 0, "Creating sort index", 0};
+PSI_stage_info stage_creating_table= { 0, "creating table", 0};
+PSI_stage_info stage_creating_tmp_table= { 0, "Creating tmp table", 0};
+PSI_stage_info stage_deleting_from_main_table= { 0, "deleting from main table", 0};
+PSI_stage_info stage_deleting_from_reference_tables= { 0, "deleting from reference tables", 0};
+PSI_stage_info stage_discard_or_import_tablespace= { 0, "discard_or_import_tablespace", 0};
+PSI_stage_info stage_enabling_keys= { 0, "enabling keys", 0};
+PSI_stage_info stage_end= { 0, "end", 0};
+PSI_stage_info stage_executing= { 0, "executing", 0};
+PSI_stage_info stage_execution_of_init_command= { 0, "Execution of init_command", 0};
+PSI_stage_info stage_explaining= { 0, "explaining", 0};
+PSI_stage_info stage_finding_key_cache= { 0, "Finding key cache", 0};
+PSI_stage_info stage_finished_reading_one_binlog_switching_to_next_binlog= { 0, "Finished reading one binlog; switching to next binlog", 0};
+PSI_stage_info stage_flushing_relay_log_and_master_info_repository= { 0, "Flushing relay log and master info repository.", 0};
+PSI_stage_info stage_flushing_relay_log_info_file= { 0, "Flushing relay-log info file.", 0};
+PSI_stage_info stage_freeing_items= { 0, "freeing items", 0};
+PSI_stage_info stage_fulltext_initialization= { 0, "FULLTEXT initialization", 0};
+PSI_stage_info stage_got_handler_lock= { 0, "got handler lock", 0};
+PSI_stage_info stage_got_old_table= { 0, "got old table", 0};
+PSI_stage_info stage_init= { 0, "init", 0};
+PSI_stage_info stage_insert= { 0, "insert", 0};
+PSI_stage_info stage_invalidating_query_cache_entries_table= { 0, "invalidating query cache entries (table)", 0};
+PSI_stage_info stage_invalidating_query_cache_entries_table_list= { 0, "invalidating query cache entries (table list)", 0};
+PSI_stage_info stage_killing_slave= { 0, "Killing slave", 0};
+PSI_stage_info stage_logging_slow_query= { 0, "logging slow query", 0};
+PSI_stage_info stage_making_temp_file_append_before_load_data= { 0, "Making temporary file (append) before replaying LOAD DATA INFILE.", 0};
+PSI_stage_info stage_making_temp_file_create_before_load_data= { 0, "Making temporary file (create) before replaying LOAD DATA INFILE.", 0};
+PSI_stage_info stage_manage_keys= { 0, "manage keys", 0};
+PSI_stage_info stage_master_has_sent_all_binlog_to_slave= { 0, "Master has sent all binlog to slave; waiting for binlog to be updated", 0};
+PSI_stage_info stage_opening_tables= { 0, "Opening tables", 0};
+PSI_stage_info stage_optimizing= { 0, "optimizing", 0};
+PSI_stage_info stage_preparing= { 0, "preparing", 0};
+PSI_stage_info stage_purging_old_relay_logs= { 0, "Purging old relay logs", 0};
+PSI_stage_info stage_query_end= { 0, "query end", 0};
+PSI_stage_info stage_queueing_master_event_to_the_relay_log= { 0, "Queueing master event to the relay log", 0};
+PSI_stage_info stage_reading_event_from_the_relay_log= { 0, "Reading event from the relay log", 0};
+PSI_stage_info stage_recreating_table= { 0, "recreating table", 0};
+PSI_stage_info stage_registering_slave_on_master= { 0, "Registering slave on master", 0};
+PSI_stage_info stage_removing_duplicates= { 0, "Removing duplicates", 0};
+PSI_stage_info stage_removing_tmp_table= { 0, "removing tmp table", 0};
+PSI_stage_info stage_rename= { 0, "rename", 0};
+PSI_stage_info stage_rename_result_table= { 0, "rename result table", 0};
+PSI_stage_info stage_requesting_binlog_dump= { 0, "Requesting binlog dump", 0};
+PSI_stage_info stage_reschedule= { 0, "reschedule", 0};
+PSI_stage_info stage_searching_rows_for_update= { 0, "Searching rows for update", 0};
+PSI_stage_info stage_sending_binlog_event_to_slave= { 0, "Sending binlog event to slave", 0};
+PSI_stage_info stage_sending_cached_result_to_client= { 0, "sending cached result to client", 0};
+PSI_stage_info stage_sending_data= { 0, "Sending data", 0};
+PSI_stage_info stage_setup= { 0, "setup", 0};
+PSI_stage_info stage_show_explain= { 0, "show explain", 0};
+PSI_stage_info stage_slave_has_read_all_relay_log= { 0, "Slave has read all relay log; waiting for the slave I/O thread to update it", 0};
+PSI_stage_info stage_sorting= { 0, "Sorting", 0};
+PSI_stage_info stage_sorting_for_group= { 0, "Sorting for group", 0};
+PSI_stage_info stage_sorting_for_order= { 0, "Sorting for order", 0};
+PSI_stage_info stage_sorting_result= { 0, "Sorting result", 0};
+PSI_stage_info stage_statistics= { 0, "statistics", 0};
+PSI_stage_info stage_sql_thd_waiting_until_delay= { 0, "Waiting until MASTER_DELAY seconds after master executed event", 0 };
+PSI_stage_info stage_storing_result_in_query_cache= { 0, "storing result in query cache", 0};
+PSI_stage_info stage_storing_row_into_queue= { 0, "storing row into queue", 0};
+PSI_stage_info stage_system_lock= { 0, "System lock", 0};
+PSI_stage_info stage_table_lock= { 0, "Table lock", 0};
+PSI_stage_info stage_filling_schema_table= { 0, "Filling schema table", 0};
+PSI_stage_info stage_update= { 0, "update", 0};
+PSI_stage_info stage_updating= { 0, "updating", 0};
+PSI_stage_info stage_updating_main_table= { 0, "updating main table", 0};
+PSI_stage_info stage_updating_reference_tables= { 0, "updating reference tables", 0};
+PSI_stage_info stage_upgrading_lock= { 0, "upgrading lock", 0};
+PSI_stage_info stage_user_lock= { 0, "User lock", 0};
+PSI_stage_info stage_user_sleep= { 0, "User sleep", 0};
+PSI_stage_info stage_verifying_table= { 0, "verifying table", 0};
+PSI_stage_info stage_waiting_for_delay_list= { 0, "waiting for delay_list", 0};
+PSI_stage_info stage_waiting_for_gtid_to_be_written_to_binary_log= { 0, "waiting for GTID to be written to binary log", 0};
+PSI_stage_info stage_waiting_for_handler_insert= { 0, "waiting for handler insert", 0};
+PSI_stage_info stage_waiting_for_handler_lock= { 0, "waiting for handler lock", 0};
+PSI_stage_info stage_waiting_for_handler_open= { 0, "waiting for handler open", 0};
+PSI_stage_info stage_waiting_for_insert= { 0, "Waiting for INSERT", 0};
+PSI_stage_info stage_waiting_for_master_to_send_event= { 0, "Waiting for master to send event", 0};
+PSI_stage_info stage_waiting_for_master_update= { 0, "Waiting for master update", 0};
+PSI_stage_info stage_waiting_for_relay_log_space= { 0, "Waiting for the slave SQL thread to free enough relay log space", 0};
+PSI_stage_info stage_waiting_for_slave_mutex_on_exit= { 0, "Waiting for slave mutex on exit", 0};
+PSI_stage_info stage_waiting_for_slave_thread_to_start= { 0, "Waiting for slave thread to start", 0};
+PSI_stage_info stage_waiting_for_table_flush= { 0, "Waiting for table flush", 0};
+PSI_stage_info stage_waiting_for_query_cache_lock= { 0, "Waiting for query cache lock", 0};
+PSI_stage_info stage_waiting_for_the_next_event_in_relay_log= { 0, "Waiting for the next event in relay log", 0};
+PSI_stage_info stage_waiting_for_the_slave_thread_to_advance_position= { 0, "Waiting for the slave SQL thread to advance position", 0};
+PSI_stage_info stage_waiting_to_finalize_termination= { 0, "Waiting to finalize termination", 0};
+PSI_stage_info stage_waiting_to_get_readlock= { 0, "Waiting to get readlock", 0};
+PSI_stage_info stage_slave_waiting_workers_to_exit= { 0, "Waiting for workers to exit", 0};
+PSI_stage_info stage_slave_waiting_worker_to_release_partition= { 0, "Waiting for Slave Worker to release partition", 0};
+PSI_stage_info stage_slave_waiting_worker_to_free_events= { 0, "Waiting for Slave Workers to free pending events", 0};
+PSI_stage_info stage_slave_waiting_worker_queue= { 0, "Waiting for Slave Worker queue", 0};
+PSI_stage_info stage_slave_waiting_event_from_coordinator= { 0, "Waiting for an event from Coordinator", 0};
+PSI_stage_info stage_binlog_waiting_background_tasks= { 0, "Waiting for background binlog tasks", 0};
+PSI_stage_info stage_binlog_processing_checkpoint_notify= { 0, "Processing binlog checkpoint notification", 0};
+PSI_stage_info stage_binlog_stopping_background_thread= { 0, "Stopping binlog background thread", 0};
+PSI_stage_info stage_waiting_for_work_from_sql_thread= { 0, "Waiting for work from SQL thread", 0};
+PSI_stage_info stage_waiting_for_prior_transaction_to_commit= { 0, "Waiting for prior transaction to commit", 0};
+PSI_stage_info stage_waiting_for_prior_transaction_to_start_commit= { 0, "Waiting for prior transaction to start commit before starting next transaction", 0};
+PSI_stage_info stage_waiting_for_room_in_worker_thread= { 0, "Waiting for room in worker thread event queue", 0};
+PSI_stage_info stage_master_gtid_wait_primary= { 0, "Waiting in MASTER_GTID_WAIT() (primary waiter)", 0};
+PSI_stage_info stage_master_gtid_wait= { 0, "Waiting in MASTER_GTID_WAIT()", 0};
+PSI_stage_info stage_gtid_wait_other_connection= { 0, "Waiting for other master connection to process GTID received on multiple master connections", 0};
+
+#ifdef HAVE_PSI_INTERFACE
+
+PSI_stage_info *all_server_stages[]=
+{
+ & stage_after_create,
+ & stage_after_opening_tables,
+ & stage_after_table_lock,
+ & stage_allocating_local_table,
+ & stage_alter_inplace,
+ & stage_alter_inplace_commit,
+ & stage_alter_inplace_prepare,
+ & stage_binlog_processing_checkpoint_notify,
+ & stage_binlog_stopping_background_thread,
+ & stage_binlog_waiting_background_tasks,
+ & stage_changing_master,
+ & stage_checking_master_version,
+ & stage_checking_permissions,
+ & stage_checking_privileges_on_cached_query,
+ & stage_checking_query_cache_for_query,
+ & stage_cleaning_up,
+ & stage_closing_tables,
+ & stage_connecting_to_master,
+ & stage_converting_heap_to_myisam,
+ & stage_copy_to_tmp_table,
+ & stage_copying_to_group_table,
+ & stage_copying_to_tmp_table,
+ & stage_creating_delayed_handler,
+ & stage_creating_sort_index,
+ & stage_creating_table,
+ & stage_creating_tmp_table,
+ & stage_deleting_from_main_table,
+ & stage_deleting_from_reference_tables,
+ & stage_discard_or_import_tablespace,
+ & stage_enabling_keys,
+ & stage_end,
+ & stage_executing,
+ & stage_execution_of_init_command,
+ & stage_explaining,
+ & stage_finding_key_cache,
+ & stage_finished_reading_one_binlog_switching_to_next_binlog,
+ & stage_flushing_relay_log_and_master_info_repository,
+ & stage_flushing_relay_log_info_file,
+ & stage_freeing_items,
+ & stage_fulltext_initialization,
+ & stage_got_handler_lock,
+ & stage_got_old_table,
+ & stage_init,
+ & stage_insert,
+ & stage_invalidating_query_cache_entries_table,
+ & stage_invalidating_query_cache_entries_table_list,
+ & stage_killing_slave,
+ & stage_logging_slow_query,
+ & stage_making_temp_file_append_before_load_data,
+ & stage_making_temp_file_create_before_load_data,
+ & stage_manage_keys,
+ & stage_master_has_sent_all_binlog_to_slave,
+ & stage_opening_tables,
+ & stage_optimizing,
+ & stage_preparing,
+ & stage_purging_old_relay_logs,
+ & stage_query_end,
+ & stage_queueing_master_event_to_the_relay_log,
+ & stage_reading_event_from_the_relay_log,
+ & stage_recreating_table,
+ & stage_registering_slave_on_master,
+ & stage_removing_duplicates,
+ & stage_removing_tmp_table,
+ & stage_rename,
+ & stage_rename_result_table,
+ & stage_requesting_binlog_dump,
+ & stage_reschedule,
+ & stage_searching_rows_for_update,
+ & stage_sending_binlog_event_to_slave,
+ & stage_sending_cached_result_to_client,
+ & stage_sending_data,
+ & stage_setup,
+ & stage_show_explain,
+ & stage_slave_has_read_all_relay_log,
+ & stage_slave_waiting_event_from_coordinator,
+ & stage_slave_waiting_worker_queue,
+ & stage_slave_waiting_worker_to_free_events,
+ & stage_slave_waiting_worker_to_release_partition,
+ & stage_slave_waiting_workers_to_exit,
+ & stage_sorting,
+ & stage_sorting_for_group,
+ & stage_sorting_for_order,
+ & stage_sorting_result,
+ & stage_sql_thd_waiting_until_delay,
+ & stage_statistics,
+ & stage_storing_result_in_query_cache,
+ & stage_storing_row_into_queue,
+ & stage_system_lock,
+ & stage_table_lock,
+ & stage_filling_schema_table,
+ & stage_update,
+ & stage_updating,
+ & stage_updating_main_table,
+ & stage_updating_reference_tables,
+ & stage_upgrading_lock,
+ & stage_user_lock,
+ & stage_user_sleep,
+ & stage_verifying_table,
+ & stage_waiting_for_delay_list,
+ & stage_waiting_for_gtid_to_be_written_to_binary_log,
+ & stage_waiting_for_handler_insert,
+ & stage_waiting_for_handler_lock,
+ & stage_waiting_for_handler_open,
+ & stage_waiting_for_insert,
+ & stage_waiting_for_master_to_send_event,
+ & stage_waiting_for_master_update,
+ & stage_waiting_for_prior_transaction_to_commit,
+ & stage_waiting_for_prior_transaction_to_start_commit,
+ & stage_waiting_for_query_cache_lock,
+ & stage_waiting_for_relay_log_space,
+ & stage_waiting_for_room_in_worker_thread,
+ & stage_waiting_for_slave_mutex_on_exit,
+ & stage_waiting_for_slave_thread_to_start,
+ & stage_waiting_for_table_flush,
+ & stage_waiting_for_the_next_event_in_relay_log,
+ & stage_waiting_for_the_slave_thread_to_advance_position,
+ & stage_waiting_for_work_from_sql_thread,
+ & stage_waiting_to_finalize_termination,
+ & stage_waiting_to_get_readlock,
+ & stage_master_gtid_wait_primary,
+ & stage_master_gtid_wait,
+ & stage_gtid_wait_other_connection
+};
+
+PSI_socket_key key_socket_tcpip, key_socket_unix, key_socket_client_connection;
+
+static PSI_socket_info all_server_sockets[]=
+{
+ { &key_socket_tcpip, "server_tcpip_socket", PSI_FLAG_GLOBAL},
+ { &key_socket_unix, "server_unix_socket", PSI_FLAG_GLOBAL},
+ { &key_socket_client_connection, "client_connection", 0}
+};
+
+/**
+ Initialise all the performance schema instrumentation points
+ used by the server.
+*/
+void init_server_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ count= array_elements(all_server_mutexes);
+ mysql_mutex_register(category, all_server_mutexes, count);
+
+ count= array_elements(all_server_rwlocks);
+ mysql_rwlock_register(category, all_server_rwlocks, count);
+
+ count= array_elements(all_server_conds);
+ mysql_cond_register(category, all_server_conds, count);
+
+ count= array_elements(all_server_threads);
+ mysql_thread_register(category, all_server_threads, count);
+
+ count= array_elements(all_server_files);
+ mysql_file_register(category, all_server_files, count);
+
+ count= array_elements(all_server_stages);
+ mysql_stage_register(category, all_server_stages, count);
+
+ count= array_elements(all_server_sockets);
+ mysql_socket_register(category, all_server_sockets, count);
+
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ init_sql_statement_info();
+ count= array_elements(sql_statement_info);
+ mysql_statement_register(category, sql_statement_info, count);
+
+ category= "com";
+ init_com_statement_info();
+
+ /*
+ Register [0 .. COM_QUERY - 1] as "statement/com/..."
+ */
+ count= (int) COM_QUERY;
+ mysql_statement_register(category, com_statement_info, count);
+
+ /*
+ Register [COM_QUERY + 1 .. COM_END] as "statement/com/..."
+ */
+ count= (int) COM_END - (int) COM_QUERY;
+ mysql_statement_register(category, & com_statement_info[(int) COM_QUERY + 1], count);
+
+ category= "abstract";
+ /*
+ Register [COM_QUERY] as "statement/abstract/com_query"
+ */
+ mysql_statement_register(category, & com_statement_info[(int) COM_QUERY], 1);
+
+ /*
+ When a new packet is received,
+ it is instrumented as "statement/abstract/new_packet".
+ Based on the packet type found, it later mutates to the
+ proper narrow type, for example
+ "statement/abstract/query" or "statement/com/ping".
+ In cases of "statement/abstract/query", SQL queries are given to
+ the parser, which mutates the statement type to an even more
+ narrow classification, for example "statement/sql/select".
+ */
+ stmt_info_new_packet.m_key= 0;
+ stmt_info_new_packet.m_name= "new_packet";
+ stmt_info_new_packet.m_flags= PSI_FLAG_MUTABLE;
+ mysql_statement_register(category, &stmt_info_new_packet, 1);
+
+ /*
+ Statements processed from the relay log are initially instrumented as
+ "statement/abstract/relay_log". The parser will mutate the statement type to
+ a more specific classification, for example "statement/sql/insert".
+ */
+ stmt_info_rpl.m_key= 0;
+ stmt_info_rpl.m_name= "relay_log";
+ stmt_info_rpl.m_flags= PSI_FLAG_MUTABLE;
+ mysql_statement_register(category, &stmt_info_rpl, 1);
+#endif
+}
+
+#endif /* HAVE_PSI_INTERFACE */
diff --git a/sql/mysqld.h b/sql/mysqld.h
new file mode 100644
index 00000000000..5f47c9d5b4e
--- /dev/null
+++ b/sql/mysqld.h
@@ -0,0 +1,760 @@
+/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef MYSQLD_INCLUDED
+#define MYSQLD_INCLUDED
+
+#include "my_global.h" /* MYSQL_PLUGIN_IMPORT, FN_REFLEN, FN_EXTLEN */
+#include "sql_bitmap.h" /* Bitmap */
+#include "my_decimal.h" /* my_decimal */
+#include "mysql_com.h" /* SERVER_VERSION_LENGTH */
+#include "my_atomic.h" /* my_atomic_rwlock_t */
+#include "mysql/psi/mysql_file.h" /* MYSQL_FILE */
+#include "sql_list.h" /* I_List */
+#include "sql_cmd.h"
+#include <my_rnd.h>
+
+class THD;
+struct handlerton;
+class Time_zone;
+
+struct scheduler_functions;
+
+typedef struct st_mysql_const_lex_string LEX_CSTRING;
+typedef struct st_mysql_show_var SHOW_VAR;
+
+#if MAX_INDEXES <= 64
+typedef Bitmap<64> key_map; /* Used for finding keys */
+#else
+typedef Bitmap<((MAX_INDEXES+7)/8*8)> key_map; /* Used for finding keys */
+#endif
+
+ /* Bits from testflag */
+#define TEST_PRINT_CACHED_TABLES 1
+#define TEST_NO_KEY_GROUP 2
+#define TEST_MIT_THREAD 4
+#define TEST_BLOCKING 8
+#define TEST_KEEP_TMP_TABLES 16
+#define TEST_READCHECK 64 /**< Force use of readcheck */
+#define TEST_NO_EXTRA 128
+#define TEST_CORE_ON_SIGNAL 256 /**< Give core if signal */
+#define TEST_SIGINT 1024 /**< Allow sigint on threads */
+#define TEST_SYNCHRONIZATION 2048 /**< get server to do sleep in
+ some places */
+/* Function prototypes */
+void kill_mysql(void);
+void close_connection(THD *thd, uint sql_errno= 0);
+void handle_connection_in_main_thread(THD *thd);
+void create_thread_to_handle_connection(THD *thd);
+void delete_running_thd(THD *thd);
+void unlink_thd(THD *thd);
+bool one_thread_per_connection_end(THD *thd, bool put_in_cache);
+void flush_thread_cache();
+void refresh_status(THD *thd);
+bool is_secure_file_path(char *path);
+
+extern "C" MYSQL_PLUGIN_IMPORT CHARSET_INFO *system_charset_info;
+extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *files_charset_info ;
+extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *national_charset_info;
+extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *table_alias_charset;
+
+/**
+ Character set of the buildin error messages loaded from errmsg.sys.
+*/
+extern CHARSET_INFO *error_message_charset_info;
+
+extern CHARSET_INFO *character_set_filesystem;
+
+extern MY_BITMAP temp_pool;
+extern bool opt_large_files, server_id_supplied;
+extern bool opt_update_log, opt_bin_log, opt_error_log;
+extern my_bool opt_log, opt_slow_log, opt_bootstrap;
+extern my_bool opt_backup_history_log;
+extern my_bool opt_backup_progress_log;
+extern ulonglong log_output_options;
+extern ulong log_backup_output_options;
+extern my_bool opt_log_queries_not_using_indexes;
+extern bool opt_disable_networking, opt_skip_show_db;
+extern bool opt_skip_name_resolve;
+extern bool opt_ignore_builtin_innodb;
+extern my_bool opt_character_set_client_handshake;
+extern bool volatile abort_loop;
+extern bool in_bootstrap;
+extern uint connection_count;
+extern my_bool opt_safe_user_create;
+extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap;
+extern my_bool opt_slave_compressed_protocol, use_temp_pool;
+extern ulong slave_exec_mode_options, slave_ddl_exec_mode_options;
+extern ulong slave_retried_transactions;
+#ifdef RBR_TRIGGERS
+extern ulong slave_run_triggers_for_rbr;
+#else
+#define slave_run_triggers_for_rbr 0
+#endif //RBR_TRIGGERS
+extern ulonglong slave_type_conversions_options;
+extern my_bool read_only, opt_readonly;
+extern my_bool lower_case_file_system;
+extern my_bool opt_enable_named_pipe, opt_sync_frm, opt_allow_suspicious_udfs;
+extern my_bool opt_secure_auth;
+extern char* opt_secure_file_priv;
+extern char* opt_secure_backup_file_priv;
+extern size_t opt_secure_backup_file_priv_len;
+extern my_bool opt_log_slow_admin_statements, opt_log_slow_slave_statements;
+extern my_bool sp_automatic_privileges, opt_noacl;
+extern ulong use_stat_tables;
+extern my_bool opt_old_style_user_limits, trust_function_creators;
+extern uint opt_crash_binlog_innodb;
+extern char *shared_memory_base_name, *mysqld_unix_port;
+extern my_bool opt_enable_shared_memory;
+extern ulong opt_replicate_events_marked_for_skip;
+extern char *default_tz_name;
+extern Time_zone *default_tz;
+extern char *default_storage_engine;
+extern bool opt_endinfo, using_udf_functions;
+extern my_bool locked_in_memory;
+extern bool opt_using_transactions;
+extern ulong max_long_data_size;
+extern ulong current_pid;
+extern ulong expire_logs_days;
+extern my_bool relay_log_recovery;
+extern uint sync_binlog_period, sync_relaylog_period,
+ sync_relayloginfo_period, sync_masterinfo_period;
+extern ulong opt_tc_log_size, tc_log_max_pages_used, tc_log_page_size;
+extern ulong tc_log_page_waits;
+extern my_bool relay_log_purge, opt_innodb_safe_binlog, opt_innodb;
+extern my_bool relay_log_recovery;
+extern uint test_flags,select_errors,ha_open_options;
+extern uint protocol_version, mysqld_port, dropping_tables;
+extern ulong delay_key_write_options;
+extern char *opt_logname, *opt_slow_logname, *opt_bin_logname,
+ *opt_relay_logname;
+extern char *opt_backup_history_logname, *opt_backup_progress_logname,
+ *opt_backup_settings_name;
+extern const char *log_output_str;
+extern const char *log_backup_output_str;
+extern char *mysql_home_ptr, *pidfile_name_ptr;
+extern MYSQL_PLUGIN_IMPORT char glob_hostname[FN_REFLEN];
+extern char mysql_home[FN_REFLEN];
+extern char pidfile_name[FN_REFLEN], system_time_zone[30], *opt_init_file;
+extern char default_logfile_name[FN_REFLEN];
+extern char log_error_file[FN_REFLEN], *opt_tc_log_file;
+extern const double log_10[309];
+extern ulonglong keybuff_size;
+extern ulonglong thd_startup_options;
+extern ulong thread_id;
+extern ulong binlog_cache_use, binlog_cache_disk_use;
+extern ulong binlog_stmt_cache_use, binlog_stmt_cache_disk_use;
+extern ulong aborted_threads,aborted_connects;
+extern ulong delayed_insert_timeout;
+extern ulong delayed_insert_limit, delayed_queue_size;
+extern ulong delayed_insert_threads, delayed_insert_writes;
+extern ulong delayed_rows_in_use,delayed_insert_errors;
+extern int32 slave_open_temp_tables;
+extern ulonglong query_cache_size;
+extern ulong query_cache_limit;
+extern ulong query_cache_min_res_unit;
+extern ulong slow_launch_threads, slow_launch_time;
+extern MYSQL_PLUGIN_IMPORT ulong max_connections;
+extern ulong max_digest_length;
+extern ulong max_connect_errors, connect_timeout;
+extern my_bool slave_allow_batching;
+extern my_bool allow_slave_start;
+extern LEX_CSTRING reason_slave_blocked;
+extern ulong slave_trans_retries;
+extern uint slave_net_timeout;
+extern int max_user_connections;
+extern ulong what_to_log,flush_time;
+extern ulong max_prepared_stmt_count, prepared_stmt_count;
+extern ulong open_files_limit;
+extern ulonglong binlog_cache_size, binlog_stmt_cache_size;
+extern ulonglong max_binlog_cache_size, max_binlog_stmt_cache_size;
+extern ulong max_binlog_size;
+extern ulong slave_max_allowed_packet;
+extern ulong opt_binlog_rows_event_max_size;
+extern ulong rpl_recovery_rank, thread_cache_size;
+extern ulong stored_program_cache_size;
+extern ulong opt_slave_parallel_threads;
+extern ulong opt_slave_domain_parallel_threads;
+extern ulong opt_slave_parallel_max_queued;
+extern ulong opt_binlog_commit_wait_count;
+extern ulong opt_binlog_commit_wait_usec;
+extern my_bool opt_gtid_ignore_duplicates;
+extern ulong back_log;
+extern ulong executed_events;
+extern char language[FN_REFLEN];
+extern "C" MYSQL_PLUGIN_IMPORT ulong server_id;
+extern ulong concurrency;
+extern time_t server_start_time, flush_status_time;
+extern char *opt_mysql_tmpdir, mysql_charsets_dir[];
+extern int mysql_unpacked_real_data_home_len;
+extern MYSQL_PLUGIN_IMPORT MY_TMPDIR mysql_tmpdir_list;
+extern const char *first_keyword, *delayed_user, *binary_keyword;
+extern MYSQL_PLUGIN_IMPORT const char *my_localhost;
+extern MYSQL_PLUGIN_IMPORT const char **errmesg; /* Error messages */
+extern const char *myisam_recover_options_str;
+extern const char *in_left_expr_name, *in_additional_cond, *in_having_cond;
+extern SHOW_VAR status_vars[];
+extern struct system_variables max_system_variables;
+extern struct system_status_var global_status_var;
+extern struct my_rnd_struct sql_rand;
+extern const char *opt_date_time_formats[];
+extern handlerton *partition_hton;
+extern handlerton *myisam_hton;
+extern handlerton *heap_hton;
+extern const char *load_default_groups[];
+extern struct my_option my_long_options[];
+int handle_early_options();
+extern int mysqld_server_started, mysqld_server_initialized;
+extern "C" MYSQL_PLUGIN_IMPORT int orig_argc;
+extern "C" MYSQL_PLUGIN_IMPORT char **orig_argv;
+extern pthread_attr_t connection_attrib;
+extern MYSQL_FILE *bootstrap_file;
+extern my_bool old_mode;
+extern LEX_STRING opt_init_connect, opt_init_slave;
+extern int bootstrap_error;
+extern I_List<THD> threads;
+extern char err_shared_dir[];
+extern ulong connection_errors_select;
+extern ulong connection_errors_accept;
+extern ulong connection_errors_tcpwrap;
+extern ulong connection_errors_internal;
+extern ulong connection_errors_max_connection;
+extern ulong connection_errors_peer_addr;
+extern ulong log_warnings;
+
+/*
+ THR_MALLOC is a key which will be used to set/get MEM_ROOT** for a thread,
+ using my_pthread_setspecific_ptr()/my_thread_getspecific_ptr().
+*/
+extern pthread_key(MEM_ROOT**,THR_MALLOC);
+
+#ifdef HAVE_PSI_INTERFACE
+#ifdef HAVE_MMAP
+extern PSI_mutex_key key_PAGE_lock, key_LOCK_sync, key_LOCK_active,
+ key_LOCK_pool, key_LOCK_pending_checkpoint;
+#endif /* HAVE_MMAP */
+
+#ifdef HAVE_OPENSSL
+extern PSI_mutex_key key_LOCK_des_key_file;
+#endif
+
+extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list,
+ key_BINLOG_LOCK_binlog_background_thread,
+ key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi,
+ key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create,
+ key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log,
+ key_LOCK_gdl, key_LOCK_global_system_variables,
+ key_LOCK_logger, key_LOCK_manager,
+ key_LOCK_prepared_stmt_count,
+ key_LOCK_rpl_status, key_LOCK_server_started,
+ key_LOCK_status, key_LOCK_show_status,
+ key_LOCK_thd_data,
+ key_LOCK_user_conn, key_LOG_LOCK_log,
+ key_master_info_data_lock, key_master_info_run_lock,
+ key_master_info_sleep_lock,
+ key_mutex_slave_reporting_capability_err_lock, key_relay_log_info_data_lock,
+ key_relay_log_info_log_space_lock, key_relay_log_info_run_lock,
+ key_rpl_group_info_sleep_lock,
+ key_structure_guard_mutex, key_TABLE_SHARE_LOCK_ha_data,
+ key_LOCK_error_messages, key_LOCK_thread_count, key_PARTITION_LOCK_auto_inc;
+extern PSI_mutex_key key_RELAYLOG_LOCK_index;
+extern PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state,
+ key_LOCK_rpl_thread, key_LOCK_rpl_thread_pool, key_LOCK_parallel_entry;
+
+extern PSI_mutex_key key_TABLE_SHARE_LOCK_share, key_LOCK_stats,
+ key_LOCK_global_user_client_stats, key_LOCK_global_table_stats,
+ key_LOCK_global_index_stats, key_LOCK_wakeup_ready, key_LOCK_wait_commit;
+extern PSI_mutex_key key_LOCK_gtid_waiting;
+
+extern PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger,
+ key_rwlock_LOCK_sys_init_connect, key_rwlock_LOCK_sys_init_slave,
+ key_rwlock_LOCK_system_variables_hash, key_rwlock_query_cache_query_lock;
+
+#ifdef HAVE_MMAP
+extern PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool;
+#endif /* HAVE_MMAP */
+
+extern PSI_cond_key key_BINLOG_COND_xid_list, key_BINLOG_update_cond,
+ key_BINLOG_COND_binlog_background_thread,
+ key_BINLOG_COND_binlog_background_thread_end,
+ key_COND_cache_status_changed, key_COND_manager,
+ key_COND_rpl_status, key_COND_server_started,
+ key_delayed_insert_cond, key_delayed_insert_cond_client,
+ key_item_func_sleep_cond, key_master_info_data_cond,
+ key_master_info_start_cond, key_master_info_stop_cond,
+ key_master_info_sleep_cond,
+ key_relay_log_info_data_cond, key_relay_log_info_log_space_cond,
+ key_relay_log_info_start_cond, key_relay_log_info_stop_cond,
+ key_rpl_group_info_sleep_cond,
+ key_TABLE_SHARE_cond, key_user_level_lock_cond,
+ key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache;
+extern PSI_cond_key key_RELAYLOG_update_cond, key_COND_wakeup_ready,
+ key_COND_wait_commit;
+extern PSI_cond_key key_RELAYLOG_COND_queue_busy;
+extern PSI_cond_key key_TC_LOG_MMAP_COND_queue_busy;
+extern PSI_cond_key key_COND_rpl_thread, key_COND_rpl_thread_queue,
+ key_COND_rpl_thread_pool,
+ key_COND_parallel_entry, key_COND_group_commit_orderer;
+extern PSI_cond_key key_COND_wait_gtid, key_COND_gtid_ignore_duplicates;
+
+extern PSI_thread_key key_thread_bootstrap, key_thread_delayed_insert,
+ key_thread_handle_manager, key_thread_kill_server, key_thread_main,
+ key_thread_one_connection, key_thread_signal_hand, key_thread_slave_init,
+ key_rpl_parallel_thread;
+
+extern PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest,
+ key_file_dbopt, key_file_des_key_file, key_file_ERRMSG, key_select_to_file,
+ key_file_fileparser, key_file_frm, key_file_global_ddl_log, key_file_load,
+ key_file_loadfile, key_file_log_event_data, key_file_log_event_info,
+ key_file_master_info, key_file_misc, key_file_partition,
+ key_file_pid, key_file_relay_log_info, key_file_send_file, key_file_tclog,
+ key_file_trg, key_file_trn, key_file_init;
+extern PSI_file_key key_file_query_log, key_file_slow_log;
+extern PSI_file_key key_file_relaylog, key_file_relaylog_index;
+extern PSI_socket_key key_socket_tcpip, key_socket_unix,
+ key_socket_client_connection;
+extern PSI_file_key key_file_binlog_state;
+
+void init_server_psi_keys();
+#endif /* HAVE_PSI_INTERFACE */
+
+/*
+ MAINTAINER: Please keep this list in order, to limit merge collisions.
+ Hint: grep PSI_stage_info | sort -u
+*/
+extern PSI_stage_info stage_after_create;
+extern PSI_stage_info stage_after_opening_tables;
+extern PSI_stage_info stage_after_table_lock;
+extern PSI_stage_info stage_allocating_local_table;
+extern PSI_stage_info stage_alter_inplace_prepare;
+extern PSI_stage_info stage_alter_inplace;
+extern PSI_stage_info stage_alter_inplace_commit;
+extern PSI_stage_info stage_changing_master;
+extern PSI_stage_info stage_checking_master_version;
+extern PSI_stage_info stage_checking_permissions;
+extern PSI_stage_info stage_checking_privileges_on_cached_query;
+extern PSI_stage_info stage_checking_query_cache_for_query;
+extern PSI_stage_info stage_cleaning_up;
+extern PSI_stage_info stage_closing_tables;
+extern PSI_stage_info stage_connecting_to_master;
+extern PSI_stage_info stage_converting_heap_to_myisam;
+extern PSI_stage_info stage_copying_to_group_table;
+extern PSI_stage_info stage_copying_to_tmp_table;
+extern PSI_stage_info stage_copy_to_tmp_table;
+extern PSI_stage_info stage_creating_delayed_handler;
+extern PSI_stage_info stage_creating_sort_index;
+extern PSI_stage_info stage_creating_table;
+extern PSI_stage_info stage_creating_tmp_table;
+extern PSI_stage_info stage_deleting_from_main_table;
+extern PSI_stage_info stage_deleting_from_reference_tables;
+extern PSI_stage_info stage_discard_or_import_tablespace;
+extern PSI_stage_info stage_end;
+extern PSI_stage_info stage_enabling_keys;
+extern PSI_stage_info stage_executing;
+extern PSI_stage_info stage_execution_of_init_command;
+extern PSI_stage_info stage_explaining;
+extern PSI_stage_info stage_finding_key_cache;
+extern PSI_stage_info stage_finished_reading_one_binlog_switching_to_next_binlog;
+extern PSI_stage_info stage_flushing_relay_log_and_master_info_repository;
+extern PSI_stage_info stage_flushing_relay_log_info_file;
+extern PSI_stage_info stage_freeing_items;
+extern PSI_stage_info stage_fulltext_initialization;
+extern PSI_stage_info stage_got_handler_lock;
+extern PSI_stage_info stage_got_old_table;
+extern PSI_stage_info stage_init;
+extern PSI_stage_info stage_insert;
+extern PSI_stage_info stage_invalidating_query_cache_entries_table;
+extern PSI_stage_info stage_invalidating_query_cache_entries_table_list;
+extern PSI_stage_info stage_killing_slave;
+extern PSI_stage_info stage_logging_slow_query;
+extern PSI_stage_info stage_making_temp_file_append_before_load_data;
+extern PSI_stage_info stage_making_temp_file_create_before_load_data;
+extern PSI_stage_info stage_manage_keys;
+extern PSI_stage_info stage_master_has_sent_all_binlog_to_slave;
+extern PSI_stage_info stage_opening_tables;
+extern PSI_stage_info stage_optimizing;
+extern PSI_stage_info stage_preparing;
+extern PSI_stage_info stage_purging_old_relay_logs;
+extern PSI_stage_info stage_query_end;
+extern PSI_stage_info stage_queueing_master_event_to_the_relay_log;
+extern PSI_stage_info stage_reading_event_from_the_relay_log;
+extern PSI_stage_info stage_recreating_table;
+extern PSI_stage_info stage_registering_slave_on_master;
+extern PSI_stage_info stage_removing_duplicates;
+extern PSI_stage_info stage_removing_tmp_table;
+extern PSI_stage_info stage_rename;
+extern PSI_stage_info stage_rename_result_table;
+extern PSI_stage_info stage_requesting_binlog_dump;
+extern PSI_stage_info stage_reschedule;
+extern PSI_stage_info stage_searching_rows_for_update;
+extern PSI_stage_info stage_sending_binlog_event_to_slave;
+extern PSI_stage_info stage_sending_cached_result_to_client;
+extern PSI_stage_info stage_sending_data;
+extern PSI_stage_info stage_setup;
+extern PSI_stage_info stage_slave_has_read_all_relay_log;
+extern PSI_stage_info stage_show_explain;
+extern PSI_stage_info stage_sorting;
+extern PSI_stage_info stage_sorting_for_group;
+extern PSI_stage_info stage_sorting_for_order;
+extern PSI_stage_info stage_sorting_result;
+extern PSI_stage_info stage_sql_thd_waiting_until_delay;
+extern PSI_stage_info stage_statistics;
+extern PSI_stage_info stage_storing_result_in_query_cache;
+extern PSI_stage_info stage_storing_row_into_queue;
+extern PSI_stage_info stage_system_lock;
+extern PSI_stage_info stage_table_lock;
+extern PSI_stage_info stage_filling_schema_table;
+extern PSI_stage_info stage_update;
+extern PSI_stage_info stage_updating;
+extern PSI_stage_info stage_updating_main_table;
+extern PSI_stage_info stage_updating_reference_tables;
+extern PSI_stage_info stage_upgrading_lock;
+extern PSI_stage_info stage_user_lock;
+extern PSI_stage_info stage_user_sleep;
+extern PSI_stage_info stage_verifying_table;
+extern PSI_stage_info stage_waiting_for_delay_list;
+extern PSI_stage_info stage_waiting_for_gtid_to_be_written_to_binary_log;
+extern PSI_stage_info stage_waiting_for_handler_insert;
+extern PSI_stage_info stage_waiting_for_handler_lock;
+extern PSI_stage_info stage_waiting_for_handler_open;
+extern PSI_stage_info stage_waiting_for_insert;
+extern PSI_stage_info stage_waiting_for_master_to_send_event;
+extern PSI_stage_info stage_waiting_for_master_update;
+extern PSI_stage_info stage_waiting_for_relay_log_space;
+extern PSI_stage_info stage_waiting_for_slave_mutex_on_exit;
+extern PSI_stage_info stage_waiting_for_slave_thread_to_start;
+extern PSI_stage_info stage_waiting_for_query_cache_lock;
+extern PSI_stage_info stage_waiting_for_table_flush;
+extern PSI_stage_info stage_waiting_for_the_next_event_in_relay_log;
+extern PSI_stage_info stage_waiting_for_the_slave_thread_to_advance_position;
+extern PSI_stage_info stage_waiting_to_finalize_termination;
+extern PSI_stage_info stage_waiting_to_get_readlock;
+extern PSI_stage_info stage_slave_waiting_worker_to_release_partition;
+extern PSI_stage_info stage_slave_waiting_worker_to_free_events;
+extern PSI_stage_info stage_slave_waiting_worker_queue;
+extern PSI_stage_info stage_slave_waiting_event_from_coordinator;
+extern PSI_stage_info stage_slave_waiting_workers_to_exit;
+extern PSI_stage_info stage_binlog_waiting_background_tasks;
+extern PSI_stage_info stage_binlog_processing_checkpoint_notify;
+extern PSI_stage_info stage_binlog_stopping_background_thread;
+extern PSI_stage_info stage_waiting_for_work_from_sql_thread;
+extern PSI_stage_info stage_waiting_for_prior_transaction_to_commit;
+extern PSI_stage_info stage_waiting_for_prior_transaction_to_start_commit;
+extern PSI_stage_info stage_waiting_for_room_in_worker_thread;
+extern PSI_stage_info stage_master_gtid_wait_primary;
+extern PSI_stage_info stage_master_gtid_wait;
+extern PSI_stage_info stage_gtid_wait_other_connection;
+
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+/**
+ Statement instrumentation keys (sql).
+ The last entry, at [SQLCOM_END], is for parsing errors.
+*/
+extern PSI_statement_info sql_statement_info[(uint) SQLCOM_END + 1];
+
+/**
+ Statement instrumentation keys (com).
+ The last entry, at [COM_END], is for packet errors.
+*/
+extern PSI_statement_info com_statement_info[(uint) COM_END + 1];
+
+/**
+ Statement instrumentation key for replication.
+*/
+extern PSI_statement_info stmt_info_rpl;
+
+void init_sql_statement_info();
+void init_com_statement_info();
+#endif /* HAVE_PSI_STATEMENT_INTERFACE */
+
+#ifndef __WIN__
+extern pthread_t signal_thread;
+#endif
+
+#ifdef HAVE_OPENSSL
+extern struct st_VioSSLFd * ssl_acceptor_fd;
+#endif /* HAVE_OPENSSL */
+
+/*
+ The following variables were under INNODB_COMPABILITY_HOOKS
+ */
+extern my_bool opt_large_pages;
+extern uint opt_large_page_size;
+extern char lc_messages_dir[FN_REFLEN];
+extern char *lc_messages_dir_ptr, *log_error_file_ptr;
+extern MYSQL_PLUGIN_IMPORT char reg_ext[FN_EXTLEN];
+extern MYSQL_PLUGIN_IMPORT uint reg_ext_length;
+extern MYSQL_PLUGIN_IMPORT uint lower_case_table_names;
+extern MYSQL_PLUGIN_IMPORT bool mysqld_embedded;
+extern ulong specialflag;
+extern uint mysql_data_home_len;
+extern uint mysql_real_data_home_len;
+extern const char *mysql_real_data_home_ptr;
+extern ulong thread_handling;
+extern "C" MYSQL_PLUGIN_IMPORT char server_version[SERVER_VERSION_LENGTH];
+extern MYSQL_PLUGIN_IMPORT char mysql_real_data_home[];
+extern char mysql_unpacked_real_data_home[];
+extern MYSQL_PLUGIN_IMPORT struct system_variables global_system_variables;
+extern char default_logfile_name[FN_REFLEN];
+
+#define mysql_tmpdir (my_tmpdir(&mysql_tmpdir_list))
+
+extern MYSQL_PLUGIN_IMPORT const key_map key_map_empty;
+extern MYSQL_PLUGIN_IMPORT key_map key_map_full; /* Should be threaded as const */
+
+/*
+ Server mutex locks and condition variables.
+ */
+extern mysql_mutex_t
+ LOCK_item_func_sleep, LOCK_status, LOCK_show_status,
+ LOCK_error_log, LOCK_delayed_insert, LOCK_short_uuid_generator,
+ LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone,
+ LOCK_slave_list, LOCK_active_mi, LOCK_manager,
+ LOCK_global_system_variables, LOCK_user_conn,
+ LOCK_prepared_stmt_count, LOCK_error_messages, LOCK_connection_count,
+ LOCK_slave_init;
+extern MYSQL_PLUGIN_IMPORT mysql_mutex_t LOCK_thread_count;
+#ifdef HAVE_OPENSSL
+extern mysql_mutex_t LOCK_des_key_file;
+#endif
+extern mysql_mutex_t LOCK_server_started;
+extern mysql_cond_t COND_server_started;
+extern mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
+extern mysql_rwlock_t LOCK_system_variables_hash;
+extern mysql_cond_t COND_thread_count;
+extern mysql_cond_t COND_manager;
+extern mysql_cond_t COND_slave_init;
+extern int32 thread_running;
+extern int32 thread_count;
+extern my_atomic_rwlock_t thread_running_lock, thread_count_lock;
+extern my_atomic_rwlock_t slave_executed_entries_lock;
+
+extern char *opt_ssl_ca, *opt_ssl_capath, *opt_ssl_cert, *opt_ssl_cipher,
+ *opt_ssl_key, *opt_ssl_crl, *opt_ssl_crlpath;
+
+extern MYSQL_PLUGIN_IMPORT pthread_key(THD*, THR_THD);
+
+#ifdef MYSQL_SERVER
+
+/**
+ only options that need special treatment in get_one_option() deserve
+ to be listed below
+*/
+enum options_mysqld
+{
+ OPT_to_set_the_start_number=256,
+ OPT_BINLOG_DO_DB,
+ OPT_BINLOG_FORMAT,
+ OPT_BINLOG_IGNORE_DB,
+ OPT_BIN_LOG,
+ OPT_BOOTSTRAP,
+ OPT_CONSOLE,
+ OPT_DEBUG_SYNC_TIMEOUT,
+ OPT_DEPRECATED_OPTION,
+ OPT_IGNORE_DB_DIRECTORY,
+ OPT_ISAM_LOG,
+ OPT_KEY_BUFFER_SIZE,
+ OPT_KEY_CACHE_AGE_THRESHOLD,
+ OPT_KEY_CACHE_BLOCK_SIZE,
+ OPT_KEY_CACHE_DIVISION_LIMIT,
+ OPT_KEY_CACHE_PARTITIONS,
+ OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE,
+ OPT_LOG_BASENAME,
+ OPT_LOG_ERROR,
+ OPT_LOWER_CASE_TABLE_NAMES,
+ OPT_MAX_LONG_DATA_SIZE,
+ OPT_PLUGIN_LOAD,
+ OPT_PLUGIN_LOAD_ADD,
+ OPT_PFS_INSTRUMENT,
+ OPT_REPLICATE_DO_DB,
+ OPT_REPLICATE_DO_TABLE,
+ OPT_REPLICATE_IGNORE_DB,
+ OPT_REPLICATE_IGNORE_TABLE,
+ OPT_REPLICATE_REWRITE_DB,
+ OPT_REPLICATE_WILD_DO_TABLE,
+ OPT_REPLICATE_WILD_IGNORE_TABLE,
+ OPT_SAFE,
+ OPT_SERVER_ID,
+ OPT_SKIP_HOST_CACHE,
+ OPT_SKIP_RESOLVE,
+ OPT_SSL_CA,
+ OPT_SSL_CAPATH,
+ OPT_SSL_CERT,
+ OPT_SSL_CIPHER,
+ OPT_SSL_CRL,
+ OPT_SSL_CRLPATH,
+ OPT_SSL_KEY,
+ OPT_THREAD_CONCURRENCY,
+ OPT_WANT_CORE,
+ OPT_MYSQL_COMPATIBILITY,
+ OPT_MYSQL_TO_BE_IMPLEMENTED,
+ OPT_which_is_always_the_last
+};
+#endif
+
+/**
+ Query type constants (usable as bitmap flags).
+*/
+enum enum_query_type
+{
+ /// Nothing specific, ordinary SQL query.
+ QT_ORDINARY= 0,
+ /// In utf8.
+ QT_TO_SYSTEM_CHARSET= (1 << 0),
+ /// Without character set introducers.
+ QT_WITHOUT_INTRODUCERS= (1 << 1),
+ /// view internal representation (like QT_ORDINARY except ORDER BY clause)
+ QT_VIEW_INTERNAL= (1 << 2)
+};
+
+/* query_id */
+typedef int64 query_id_t;
+extern query_id_t global_query_id;
+extern my_atomic_rwlock_t global_query_id_lock;
+extern my_atomic_rwlock_t statistics_lock;
+
+void unireg_end(void) __attribute__((noreturn));
+
+/* increment query_id and return it. */
+inline __attribute__((warn_unused_result)) query_id_t next_query_id()
+{
+ query_id_t id;
+ my_atomic_rwlock_wrlock(&global_query_id_lock);
+ id= my_atomic_add64(&global_query_id, 1);
+ my_atomic_rwlock_wrunlock(&global_query_id_lock);
+ return (id);
+}
+
+inline query_id_t get_query_id()
+{
+ query_id_t id;
+ my_atomic_rwlock_wrlock(&global_query_id_lock);
+ id= my_atomic_load64(&global_query_id);
+ my_atomic_rwlock_wrunlock(&global_query_id_lock);
+ return id;
+}
+
+
+/*
+ TODO: Replace this with an inline function.
+ */
+#ifndef EMBEDDED_LIBRARY
+extern "C" void unireg_abort(int exit_code) __attribute__((noreturn));
+#else
+extern "C" void unireg_clear(int exit_code);
+#define unireg_abort(exit_code) do { unireg_clear(exit_code); DBUG_RETURN(exit_code); } while(0)
+#endif
+
+inline void table_case_convert(char * name, uint length)
+{
+ if (lower_case_table_names)
+ files_charset_info->cset->casedn(files_charset_info,
+ name, length, name, length);
+}
+
+inline void thread_safe_increment32(int32 *value, my_atomic_rwlock_t *lock)
+{
+ my_atomic_rwlock_wrlock(lock);
+ (void) my_atomic_add32(value, 1);
+ my_atomic_rwlock_wrunlock(lock);
+}
+
+inline void thread_safe_decrement32(int32 *value, my_atomic_rwlock_t *lock)
+{
+ my_atomic_rwlock_wrlock(lock);
+ (void) my_atomic_add32(value, -1);
+ my_atomic_rwlock_wrunlock(lock);
+}
+
+inline void thread_safe_increment64(int64 *value, my_atomic_rwlock_t *lock)
+{
+ my_atomic_rwlock_wrlock(lock);
+ (void) my_atomic_add64(value, 1);
+ my_atomic_rwlock_wrunlock(lock);
+}
+
+inline void thread_safe_decrement64(int64 *value, my_atomic_rwlock_t *lock)
+{
+ my_atomic_rwlock_wrlock(lock);
+ (void) my_atomic_add64(value, -1);
+ my_atomic_rwlock_wrunlock(lock);
+}
+
+inline void
+inc_thread_running()
+{
+ thread_safe_increment32(&thread_running, &thread_running_lock);
+}
+
+inline void
+dec_thread_running()
+{
+ thread_safe_decrement32(&thread_running, &thread_running_lock);
+}
+
+void set_server_version(void);
+
+#if defined(MYSQL_DYNAMIC_PLUGIN) && defined(_WIN32)
+extern "C" THD *_current_thd_noinline();
+#define _current_thd() _current_thd_noinline()
+#else
+/*
+ THR_THD is a key which will be used to set/get THD* for a thread,
+ using my_pthread_setspecific_ptr()/my_thread_getspecific_ptr().
+*/
+extern pthread_key(THD*, THR_THD);
+inline THD *_current_thd(void)
+{
+ return my_pthread_getspecific_ptr(THD*,THR_THD);
+}
+#endif
+#define current_thd _current_thd()
+inline int set_current_thd(THD *thd)
+{
+ return my_pthread_setspecific_ptr(THR_THD, thd);
+}
+
+/*
+ @todo remove, make it static in ha_maria.cc
+ currently it's needed for sql_select.cc
+*/
+extern handlerton *maria_hton;
+
+extern uint extra_connection_count;
+extern uint64 global_gtid_counter;
+extern my_bool opt_gtid_strict_mode;
+extern my_bool opt_userstat_running, debug_assert_if_crashed_table;
+extern uint mysqld_extra_port;
+extern ulong opt_progress_report_time;
+extern ulong extra_max_connections;
+extern ulonglong denied_connections;
+extern ulong thread_created;
+extern scheduler_functions *thread_scheduler, *extra_thread_scheduler;
+extern char *opt_log_basename;
+extern my_bool opt_master_verify_checksum;
+extern my_bool opt_stack_trace;
+extern my_bool opt_expect_abort;
+extern my_bool opt_slave_sql_verify_checksum;
+extern ulong binlog_checksum_options;
+extern bool max_user_connections_checking;
+extern ulong opt_binlog_dbug_fsync_sleep;
+
+extern uint internal_tmp_table_max_key_length;
+extern uint internal_tmp_table_max_key_segments;
+
+extern uint volatile global_disable_checkpoint;
+extern my_bool opt_help;
+
+#endif /* MYSQLD_INCLUDED */
diff --git a/sql/mysqld_suffix.h b/sql/mysqld_suffix.h
new file mode 100644
index 00000000000..fd515ac5998
--- /dev/null
+++ b/sql/mysqld_suffix.h
@@ -0,0 +1,34 @@
+#ifndef MYSQLD_SUFFIX_INCLUDED
+#define MYSQLD_SUFFIX_INCLUDED
+
+/* Copyright (c) 2000-2004, 2006, 2007 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+/**
+ @file
+
+ Set MYSQL_SERVER_SUFFIX_STR.
+
+ The following code is quite ugly as there is no portable way to easily set a
+ string to the value of a macro
+*/
+
+#ifdef MYSQL_SERVER_SUFFIX
+#define MYSQL_SERVER_SUFFIX_STR STRINGIFY_ARG(MYSQL_SERVER_SUFFIX)
+#else
+#define MYSQL_SERVER_SUFFIX_STR MYSQL_SERVER_SUFFIX_DEF
+#endif
+#endif /* MYSQLD_SUFFIX_INCLUDED */
diff --git a/sql/net_serv.cc b/sql/net_serv.cc
new file mode 100644
index 00000000000..0ce0fa93f99
--- /dev/null
+++ b/sql/net_serv.cc
@@ -0,0 +1,1248 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2014, SkySQL Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/**
+ @file
+
+ This file is the net layer API for the MySQL client/server protocol.
+
+ Write and read of logical packets to/from socket.
+
+ Writes are cached into net_buffer_length big packets.
+ Read packets are reallocated dynamicly when reading big packets.
+ Each logical packet has the following pre-info:
+ 3 byte length & 1 byte package-number.
+
+ This file needs to be written in C as it's used by the libmysql client as a
+ C file.
+*/
+
+/*
+ HFTODO this must be hidden if we don't want client capabilities in
+ embedded library
+ */
+#include <my_global.h>
+#include <mysql.h>
+#include <mysql_com.h>
+#include <mysqld_error.h>
+#include <my_sys.h>
+#include <m_string.h>
+#include <my_net.h>
+#include <violite.h>
+#include <signal.h>
+#include "probes_mysql.h"
+
+#ifdef EMBEDDED_LIBRARY
+#undef MYSQL_SERVER
+#undef MYSQL_CLIENT
+#define MYSQL_CLIENT
+#endif /*EMBEDDED_LIBRARY */
+
+/*
+ to reduce the number of ifdef's in the code
+*/
+#ifdef EXTRA_DEBUG
+#define EXTRA_DEBUG_fprintf fprintf
+#define EXTRA_DEBUG_fflush fflush
+#else
+static void inline EXTRA_DEBUG_fprintf(...) {}
+static int inline EXTRA_DEBUG_fflush(...) { return 0; }
+#endif
+#ifdef MYSQL_SERVER
+#define MYSQL_SERVER_my_error my_error
+#else
+static void inline MYSQL_SERVER_my_error(...) {}
+#endif
+
+
+/*
+ The following handles the differences when this is linked between the
+ client and the server.
+
+ This gives an error if a too big packet is found.
+ The server can change this, but because the client can't normally do this
+ the client should have a bigger max_allowed_packet.
+*/
+
+#if defined(__WIN__) || !defined(MYSQL_SERVER)
+ /* The following is because alarms doesn't work on windows. */
+#ifndef NO_ALARM
+#define NO_ALARM
+#endif
+#endif
+
+#ifndef NO_ALARM
+#include "my_pthread.h"
+void sql_print_error(const char *format,...);
+#else
+#define DONT_USE_THR_ALARM
+#endif /* NO_ALARM */
+
+#include "thr_alarm.h"
+
+#ifdef MYSQL_SERVER
+/*
+ The following variables/functions should really not be declared
+ extern, but as it's hard to include sql_priv.h here, we have to
+ live with this for a while.
+*/
+extern uint test_flags;
+extern ulong bytes_sent, bytes_received, net_big_packet_count;
+#ifdef HAVE_QUERY_CACHE
+#define USE_QUERY_CACHE
+extern void query_cache_insert(const char *packet, ulong length,
+ unsigned pkt_nr);
+#endif // HAVE_QUERY_CACHE
+#define update_statistics(A) A
+#else
+#define update_statistics(A)
+#endif
+
+#ifdef MYSQL_SERVER
+/* Additional instrumentation hooks for the server */
+#include "mysql_com_server.h"
+#endif
+
+#define TEST_BLOCKING 8
+#define MAX_PACKET_LENGTH (256L*256L*256L-1)
+
+static my_bool net_write_buff(NET *, const uchar *, ulong);
+
+/** Init with packet info. */
+
+my_bool my_net_init(NET *net, Vio* vio, uint my_flags)
+{
+ DBUG_ENTER("my_net_init");
+ DBUG_PRINT("enter", ("my_flags: %u", my_flags));
+ net->vio = vio;
+ my_net_local_init(net); /* Set some limits */
+ if (!(net->buff=(uchar*) my_malloc((size_t) net->max_packet+
+ NET_HEADER_SIZE + COMP_HEADER_SIZE +1,
+ MYF(MY_WME | my_flags))))
+ DBUG_RETURN(1);
+ net->buff_end=net->buff+net->max_packet;
+ net->error=0; net->return_status=0;
+ net->pkt_nr=net->compress_pkt_nr=0;
+ net->write_pos=net->read_pos = net->buff;
+ net->last_error[0]=0;
+ net->compress=0; net->reading_or_writing=0;
+ net->where_b = net->remain_in_buf=0;
+ net->net_skip_rest_factor= 0;
+ net->last_errno=0;
+ net->unused= 0;
+ net->thread_specific_malloc= MY_TEST(my_flags & MY_THREAD_SPECIFIC);
+#ifdef MYSQL_SERVER
+ net->extension= NULL;
+#endif
+
+ if (vio)
+ {
+ /* For perl DBI/DBD. */
+ net->fd= vio_fd(vio);
+#if defined(MYSQL_SERVER) && !defined(__WIN__)
+ if (!(test_flags & TEST_BLOCKING))
+ {
+ my_bool old_mode;
+ vio_blocking(vio, FALSE, &old_mode);
+ }
+#endif
+ vio_fastsend(vio);
+ }
+ DBUG_RETURN(0);
+}
+
+
+void net_end(NET *net)
+{
+ DBUG_ENTER("net_end");
+ my_free(net->buff);
+ net->buff=0;
+ DBUG_VOID_RETURN;
+}
+
+
+/** Realloc the packet buffer. */
+
+my_bool net_realloc(NET *net, size_t length)
+{
+ uchar *buff;
+ size_t pkt_length;
+ DBUG_ENTER("net_realloc");
+ DBUG_PRINT("enter",("length: %lu", (ulong) length));
+
+ if (length >= net->max_packet_size)
+ {
+ DBUG_PRINT("error", ("Packet too large. Max size: %lu",
+ net->max_packet_size));
+ /* @todo: 1 and 2 codes are identical. */
+ net->error= 1;
+ net->last_errno= ER_NET_PACKET_TOO_LARGE;
+ MYSQL_SERVER_my_error(ER_NET_PACKET_TOO_LARGE, MYF(0));
+ DBUG_RETURN(1);
+ }
+ pkt_length = (length+IO_SIZE-1) & ~(IO_SIZE-1);
+ /*
+ We must allocate some extra bytes for the end 0 and to be able to
+ read big compressed blocks + 1 safety byte since uint3korr() in
+ my_real_read() may actually read 4 bytes depending on build flags and
+ platform.
+ */
+ if (!(buff= (uchar*) my_realloc((char*) net->buff, pkt_length +
+ NET_HEADER_SIZE + COMP_HEADER_SIZE + 1,
+ MYF(MY_WME |
+ (net->thread_specific_malloc ?
+ MY_THREAD_SPECIFIC : 0)))))
+ {
+ /* @todo: 1 and 2 codes are identical. */
+ net->error= 1;
+ net->last_errno= ER_OUT_OF_RESOURCES;
+ /* In the server the error is reported by MY_WME flag. */
+ DBUG_RETURN(1);
+ }
+ net->buff=net->write_pos=buff;
+ net->buff_end=buff+(net->max_packet= (ulong) pkt_length);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Check if there is any data to be read from the socket.
+
+ @param sd socket descriptor
+
+ @retval
+ 0 No data to read
+ @retval
+ 1 Data or EOF to read
+ @retval
+ -1 Don't know if data is ready or not
+*/
+
+#if !defined(EMBEDDED_LIBRARY) && defined(DBUG_OFF)
+
+static int net_data_is_ready(my_socket sd)
+{
+#ifdef HAVE_POLL
+ struct pollfd ufds;
+ int res;
+
+ ufds.fd= sd;
+ ufds.events= POLLIN | POLLPRI;
+ if (!(res= poll(&ufds, 1, 0)))
+ return 0;
+ if (res < 0 || !(ufds.revents & (POLLIN | POLLPRI)))
+ return 0;
+ return 1;
+#else
+ fd_set sfds;
+ struct timeval tv;
+ int res;
+
+#ifndef __WIN__
+ /* Windows uses an _array_ of 64 fd's as default, so it's safe */
+ if (sd >= FD_SETSIZE)
+ return -1;
+#define NET_DATA_IS_READY_CAN_RETURN_MINUS_ONE
+#endif
+
+ FD_ZERO(&sfds);
+ FD_SET(sd, &sfds);
+
+ tv.tv_sec= tv.tv_usec= 0;
+
+ if ((res= select((int) (sd + 1), &sfds, NULL, NULL, &tv)) < 0)
+ return 0;
+ else
+ return MY_TEST(res ? FD_ISSET(sd, &sfds) : 0);
+#endif /* HAVE_POLL */
+}
+
+#endif /* EMBEDDED_LIBRARY */
+
+/**
+ Clear (reinitialize) the NET structure for a new command.
+
+ @remark Performs debug checking of the socket buffer to
+ ensure that the protocol sequence is correct.
+
+ - Read from socket until there is nothing more to read. Discard
+ what is read.
+ - Initialize net for new net_read/net_write calls.
+
+ If there is anything when to read 'net_clear' is called this
+ normally indicates an error in the protocol. Normally one should not
+ need to do clear the communication buffer. If one compiles without
+ -DUSE_NET_CLEAR then one wins one read call / query.
+
+ When connection is properly closed (for TCP it means with
+ a FIN packet), then select() considers a socket "ready to read",
+ in the sense that there's EOF to read, but read() returns 0.
+
+ @param net NET handler
+ @param clear_buffer if <> 0, then clear all data from comm buff
+*/
+
+void net_clear(NET *net, my_bool clear_buffer __attribute__((unused)))
+{
+ DBUG_ENTER("net_clear");
+
+/*
+ We don't do a clear in case of not DBUG_OFF to catch bugs in the
+ protocol handling.
+*/
+
+#if (!defined(EMBEDDED_LIBRARY) && defined(DBUG_OFF)) || defined(USE_NET_CLEAR)
+ if (clear_buffer)
+ {
+ size_t count;
+ int ready;
+ while ((ready= net_data_is_ready(vio_fd(net->vio))) > 0)
+ {
+ /* The socket is ready */
+ if ((long) (count= vio_read(net->vio, net->buff,
+ (size_t) net->max_packet)) > 0)
+ {
+ DBUG_PRINT("info",("skipped %ld bytes from file: %s",
+ (long) count, vio_description(net->vio)));
+ EXTRA_DEBUG_fprintf(stderr,"Note: net_clear() skipped %ld bytes from file: %s\n",
+ (long) count, vio_description(net->vio));
+ }
+ else
+ {
+ DBUG_PRINT("info",("socket ready but only EOF to read - disconnected"));
+ net->error= 2;
+ break;
+ }
+ }
+#ifdef NET_DATA_IS_READY_CAN_RETURN_MINUS_ONE
+ /* 'net_data_is_ready' returned "don't know" */
+ if (ready == -1)
+ {
+ /* Read unblocking to clear net */
+ my_bool old_mode;
+ if (!vio_blocking(net->vio, FALSE, &old_mode))
+ {
+ while ((long) (count= vio_read(net->vio, net->buff,
+ (size_t) net->max_packet)) > 0)
+ DBUG_PRINT("info",("skipped %ld bytes from file: %s",
+ (long) count, vio_description(net->vio)));
+ vio_blocking(net->vio, TRUE, &old_mode);
+ }
+ }
+#endif /* NET_DATA_IS_READY_CAN_RETURN_MINUS_ONE */
+ }
+#endif /* EMBEDDED_LIBRARY */
+ net->pkt_nr=net->compress_pkt_nr=0; /* Ready for new command */
+ net->write_pos=net->buff;
+ DBUG_VOID_RETURN;
+}
+
+
+/** Flush write_buffer if not empty. */
+
+my_bool net_flush(NET *net)
+{
+ my_bool error= 0;
+ DBUG_ENTER("net_flush");
+ if (net->buff != net->write_pos)
+ {
+ error= MY_TEST(net_real_write(net, net->buff,
+ (size_t) (net->write_pos - net->buff)));
+ net->write_pos= net->buff;
+ }
+ /* Sync packet number if using compression */
+ if (net->compress)
+ net->pkt_nr=net->compress_pkt_nr;
+ DBUG_RETURN(error);
+}
+
+
+/*****************************************************************************
+** Write something to server/client buffer
+*****************************************************************************/
+
+/**
+ Write a logical packet with packet header.
+
+ Format: Packet length (3 bytes), packet number (1 byte)
+ When compression is used, a 3 byte compression length is added.
+
+ @note If compression is used, the original packet is modified!
+*/
+
+my_bool my_net_write(NET *net, const uchar *packet, size_t len)
+{
+ uchar buff[NET_HEADER_SIZE];
+ int rc;
+
+ if (unlikely(!net->vio)) /* nowhere to write */
+ return 0;
+
+ MYSQL_NET_WRITE_START(len);
+
+ /*
+ Big packets are handled by splitting them in packets of MAX_PACKET_LENGTH
+ length. The last packet is always a packet that is < MAX_PACKET_LENGTH.
+ (The last packet may even have a length of 0)
+ */
+ while (len >= MAX_PACKET_LENGTH)
+ {
+ const ulong z_size = MAX_PACKET_LENGTH;
+ int3store(buff, z_size);
+ buff[3]= (uchar) net->pkt_nr++;
+ if (net_write_buff(net, buff, NET_HEADER_SIZE) ||
+ net_write_buff(net, packet, z_size))
+ {
+ MYSQL_NET_WRITE_DONE(1);
+ return 1;
+ }
+ packet += z_size;
+ len-= z_size;
+ }
+ /* Write last packet */
+ int3store(buff,len);
+ buff[3]= (uchar) net->pkt_nr++;
+ if (net_write_buff(net, buff, NET_HEADER_SIZE))
+ {
+ MYSQL_NET_WRITE_DONE(1);
+ return 1;
+ }
+#ifndef DEBUG_DATA_PACKETS
+ DBUG_DUMP("packet_header", buff, NET_HEADER_SIZE);
+#endif
+ rc= MY_TEST(net_write_buff(net, packet, len));
+ MYSQL_NET_WRITE_DONE(rc);
+ return rc;
+}
+
+
+/**
+ Send a command to the server.
+
+ The reason for having both header and packet is so that libmysql
+ can easy add a header to a special command (like prepared statements)
+ without having to re-alloc the string.
+
+ As the command is part of the first data packet, we have to do some data
+ juggling to put the command in there, without having to create a new
+ packet.
+
+ This function will split big packets into sub-packets if needed.
+ (Each sub packet can only be 2^24 bytes)
+
+ @param net NET handler
+ @param command Command in MySQL server (enum enum_server_command)
+ @param header Header to write after command
+ @param head_len Length of header
+ @param packet Query or parameter to query
+ @param len Length of packet
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+my_bool
+net_write_command(NET *net,uchar command,
+ const uchar *header, size_t head_len,
+ const uchar *packet, size_t len)
+{
+ size_t length=len+1+head_len; /* 1 extra byte for command */
+ uchar buff[NET_HEADER_SIZE+1];
+ uint header_size=NET_HEADER_SIZE+1;
+ int rc;
+ DBUG_ENTER("net_write_command");
+ DBUG_PRINT("enter",("length: %lu", (ulong) len));
+
+ MYSQL_NET_WRITE_START(length);
+
+ buff[4]=command; /* For first packet */
+
+ if (length >= MAX_PACKET_LENGTH)
+ {
+ /* Take into account that we have the command in the first header */
+ len= MAX_PACKET_LENGTH - 1 - head_len;
+ do
+ {
+ int3store(buff, MAX_PACKET_LENGTH);
+ buff[3]= (uchar) net->pkt_nr++;
+ if (net_write_buff(net, buff, header_size) ||
+ net_write_buff(net, header, head_len) ||
+ net_write_buff(net, packet, len))
+ {
+ MYSQL_NET_WRITE_DONE(1);
+ DBUG_RETURN(1);
+ }
+ packet+= len;
+ length-= MAX_PACKET_LENGTH;
+ len= MAX_PACKET_LENGTH;
+ head_len= 0;
+ header_size= NET_HEADER_SIZE;
+ } while (length >= MAX_PACKET_LENGTH);
+ len=length; /* Data left to be written */
+ }
+ int3store(buff,length);
+ buff[3]= (uchar) net->pkt_nr++;
+ rc= MY_TEST(net_write_buff(net, buff, header_size) ||
+ (head_len && net_write_buff(net, header, head_len)) ||
+ net_write_buff(net, packet, len) || net_flush(net));
+ MYSQL_NET_WRITE_DONE(rc);
+ DBUG_RETURN(rc);
+}
+
+/**
+ Caching the data in a local buffer before sending it.
+
+ Fill up net->buffer and send it to the client when full.
+
+ If the rest of the to-be-sent-packet is bigger than buffer,
+ send it in one big block (to avoid copying to internal buffer).
+ If not, copy the rest of the data to the buffer and return without
+ sending data.
+
+ @param net Network handler
+ @param packet Packet to send
+ @param len Length of packet
+
+ @note
+ The cached buffer can be sent as it is with 'net_flush()'.
+ In this code we have to be careful to not send a packet longer than
+ MAX_PACKET_LENGTH to net_real_write() if we are using the compressed
+ protocol as we store the length of the compressed packet in 3 bytes.
+
+ @retval
+ 0 ok
+ @retval
+ 1
+*/
+
+static my_bool
+net_write_buff(NET *net, const uchar *packet, ulong len)
+{
+ ulong left_length;
+ if (net->compress && net->max_packet > MAX_PACKET_LENGTH)
+ left_length= (ulong) (MAX_PACKET_LENGTH - (net->write_pos - net->buff));
+ else
+ left_length= (ulong) (net->buff_end - net->write_pos);
+
+#ifdef DEBUG_DATA_PACKETS
+ DBUG_DUMP("data", packet, len);
+#endif
+ if (len > left_length)
+ {
+ if (net->write_pos != net->buff)
+ {
+ /* Fill up already used packet and write it */
+ memcpy((char*) net->write_pos,packet,left_length);
+ if (net_real_write(net, net->buff,
+ (size_t) (net->write_pos - net->buff) + left_length))
+ return 1;
+ net->write_pos= net->buff;
+ packet+= left_length;
+ len-= left_length;
+ }
+ if (net->compress)
+ {
+ /*
+ We can't have bigger packets than 16M with compression
+ Because the uncompressed length is stored in 3 bytes
+ */
+ left_length= MAX_PACKET_LENGTH;
+ while (len > left_length)
+ {
+ if (net_real_write(net, packet, left_length))
+ return 1;
+ packet+= left_length;
+ len-= left_length;
+ }
+ }
+ if (len > net->max_packet)
+ return net_real_write(net, packet, len) ? 1 : 0;
+ /* Send out rest of the blocks as full sized blocks */
+ }
+ memcpy((char*) net->write_pos,packet,len);
+ net->write_pos+= len;
+ return 0;
+}
+
+
+/**
+ Read and write one packet using timeouts.
+ If needed, the packet is compressed before sending.
+
+ @todo
+ - TODO is it needed to set this variable if we have no socket
+*/
+
+int
+net_real_write(NET *net,const uchar *packet, size_t len)
+{
+ size_t length;
+ const uchar *pos,*end;
+ thr_alarm_t alarmed;
+#ifndef NO_ALARM
+ ALARM alarm_buff;
+#endif
+ uint retry_count=0;
+ my_bool net_blocking = vio_is_blocking(net->vio);
+ DBUG_ENTER("net_real_write");
+
+#if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE)
+ query_cache_insert((char*) packet, len, net->pkt_nr);
+#endif
+
+ if (net->error == 2)
+ DBUG_RETURN(-1); /* socket can't be used */
+
+ net->reading_or_writing=2;
+#ifdef HAVE_COMPRESS
+ if (net->compress)
+ {
+ size_t complen;
+ uchar *b;
+ uint header_length=NET_HEADER_SIZE+COMP_HEADER_SIZE;
+ if (!(b= (uchar*) my_malloc(len + NET_HEADER_SIZE +
+ COMP_HEADER_SIZE + 1,
+ MYF(MY_WME |
+ (net->thread_specific_malloc ?
+ MY_THREAD_SPECIFIC : 0)))))
+ {
+ net->error= 2;
+ net->last_errno= ER_OUT_OF_RESOURCES;
+ /* In the server, the error is reported by MY_WME flag. */
+ net->reading_or_writing= 0;
+ DBUG_RETURN(1);
+ }
+ memcpy(b+header_length,packet,len);
+
+ if (my_compress(b+header_length, &len, &complen))
+ complen=0;
+ int3store(&b[NET_HEADER_SIZE],complen);
+ int3store(b,len);
+ b[3]=(uchar) (net->compress_pkt_nr++);
+ len+= header_length;
+ packet= b;
+ }
+#endif /* HAVE_COMPRESS */
+
+#ifdef DEBUG_DATA_PACKETS
+ DBUG_DUMP("data", packet, len);
+#endif
+
+#ifndef NO_ALARM
+ thr_alarm_init(&alarmed);
+ if (net_blocking)
+ thr_alarm(&alarmed, net->write_timeout, &alarm_buff);
+#else
+ alarmed=0;
+ /* Write timeout is set in my_net_set_write_timeout */
+#endif /* NO_ALARM */
+
+ pos= packet;
+ end=pos+len;
+ while (pos != end)
+ {
+ if ((long) (length= vio_write(net->vio,pos,(size_t) (end-pos))) <= 0)
+ {
+ my_bool interrupted = vio_should_retry(net->vio);
+#if !defined(__WIN__)
+ if ((interrupted || length == 0) && !thr_alarm_in_use(&alarmed))
+ {
+ if (!thr_alarm(&alarmed, net->write_timeout, &alarm_buff))
+ { /* Always true for client */
+ my_bool old_mode;
+ while (vio_blocking(net->vio, TRUE, &old_mode) < 0)
+ {
+ if (vio_should_retry(net->vio) && retry_count++ < net->retry_count)
+ continue;
+ EXTRA_DEBUG_fprintf(stderr,
+ "%s: my_net_write: fcntl returned error %d, aborting thread\n",
+ my_progname,vio_errno(net->vio));
+ net->error= 2; /* Close socket */
+ net->last_errno= ER_NET_PACKET_TOO_LARGE;
+ MYSQL_SERVER_my_error(ER_NET_PACKET_TOO_LARGE, MYF(0));
+ goto end;
+ }
+ retry_count=0;
+ continue;
+ }
+ }
+ else
+#endif /* !defined(__WIN__) */
+ if (thr_alarm_in_use(&alarmed) && !thr_got_alarm(&alarmed) &&
+ interrupted)
+ {
+ if (retry_count++ < net->retry_count)
+ continue;
+ EXTRA_DEBUG_fprintf(stderr, "%s: write looped, aborting thread\n",
+ my_progname);
+ }
+#ifndef MYSQL_SERVER
+ if (vio_errno(net->vio) == SOCKET_EINTR)
+ {
+ DBUG_PRINT("warning",("Interrupted write. Retrying..."));
+ continue;
+ }
+#endif /* !defined(MYSQL_SERVER) */
+ net->error= 2; /* Close socket */
+ net->last_errno= (interrupted ? ER_NET_WRITE_INTERRUPTED :
+ ER_NET_ERROR_ON_WRITE);
+ MYSQL_SERVER_my_error(net->last_errno, MYF(0));
+ break;
+ }
+ pos+=length;
+ update_statistics(thd_increment_bytes_sent(length));
+ }
+#ifndef __WIN__
+ end:
+#endif
+#ifdef HAVE_COMPRESS
+ if (net->compress)
+ my_free((void*) packet);
+#endif
+ if (thr_alarm_in_use(&alarmed))
+ {
+ my_bool old_mode;
+ thr_end_alarm(&alarmed);
+ if (!net_blocking)
+ vio_blocking(net->vio, net_blocking, &old_mode);
+ }
+ net->reading_or_writing=0;
+ DBUG_RETURN(((int) (pos != end)));
+}
+
+
+/*****************************************************************************
+** Read something from server/clinet
+*****************************************************************************/
+
+#ifndef NO_ALARM
+
+static my_bool net_safe_read(NET *net, uchar *buff, size_t length,
+ thr_alarm_t *alarmed)
+{
+ uint retry_count=0;
+ while (length > 0)
+ {
+ size_t tmp;
+ if ((long) (tmp= vio_read(net->vio, buff, length)) <= 0)
+ {
+ my_bool interrupted = vio_should_retry(net->vio);
+ if (!thr_got_alarm(alarmed) && interrupted)
+ { /* Probably in MIT threads */
+ if (retry_count++ < net->retry_count)
+ continue;
+ }
+ return 1;
+ }
+ length-= tmp;
+ buff+= tmp;
+ }
+ return 0;
+}
+
+/**
+ Help function to clear the commuication buffer when we get a too big packet.
+
+ @param net Communication handle
+ @param remain Bytes to read
+ @param alarmed Parameter for thr_alarm()
+ @param alarm_buff Parameter for thr_alarm()
+
+ @retval
+ 0 Was able to read the whole packet
+ @retval
+ 1 Got mailformed packet from client
+*/
+
+static my_bool my_net_skip_rest(NET *net, uint32 remain, thr_alarm_t *alarmed,
+ ALARM *alarm_buff)
+{
+ longlong limit= net->max_packet_size*net->net_skip_rest_factor;
+ uint32 old=remain;
+ DBUG_ENTER("my_net_skip_rest");
+ DBUG_PRINT("enter",("bytes_to_skip: %u", (uint) remain));
+
+ /* The following is good for debugging */
+ update_statistics(thd_increment_net_big_packet_count(1));
+
+ if (!thr_alarm_in_use(alarmed))
+ {
+ my_bool old_mode;
+ if (thr_alarm(alarmed,net->read_timeout, alarm_buff) ||
+ vio_blocking(net->vio, TRUE, &old_mode) < 0)
+ DBUG_RETURN(1); /* Can't setup, abort */
+ }
+ for (;;)
+ {
+ while (remain > 0)
+ {
+ size_t length= MY_MIN(remain, net->max_packet);
+ if (net_safe_read(net, net->buff, length, alarmed))
+ DBUG_RETURN(1);
+ update_statistics(thd_increment_bytes_received(length));
+ remain -= (uint32) length;
+ limit-= length;
+ if (limit < 0)
+ DBUG_RETURN(1);
+ }
+ if (old != MAX_PACKET_LENGTH)
+ break;
+ if (net_safe_read(net, net->buff, NET_HEADER_SIZE, alarmed))
+ DBUG_RETURN(1);
+ limit-= NET_HEADER_SIZE;
+ old=remain= uint3korr(net->buff);
+ net->pkt_nr++;
+ }
+ DBUG_RETURN(0);
+}
+#endif /* NO_ALARM */
+
+
+/**
+ Reads one packet to net->buff + net->where_b.
+ Long packets are handled by my_net_read().
+ This function reallocates the net->buff buffer if necessary.
+
+ @return
+ Returns length of packet.
+*/
+
+static ulong
+my_real_read(NET *net, size_t *complen,
+ my_bool header __attribute__((unused)))
+{
+ uchar *pos;
+ size_t length;
+ uint i,retry_count=0;
+ ulong len=packet_error;
+ thr_alarm_t alarmed;
+#ifndef NO_ALARM
+ ALARM alarm_buff;
+#endif
+ my_bool net_blocking=vio_is_blocking(net->vio);
+ uint32 remain= (net->compress ? NET_HEADER_SIZE+COMP_HEADER_SIZE :
+ NET_HEADER_SIZE);
+#ifdef MYSQL_SERVER
+ size_t count= remain;
+ struct st_net_server *server_extension= 0;
+
+ if (header)
+ {
+ server_extension= static_cast<st_net_server*> (net->extension);
+ if (server_extension != NULL)
+ {
+ void *user_data= server_extension->m_user_data;
+ server_extension->m_before_header(net, user_data, count);
+ }
+ }
+#endif
+
+ *complen = 0;
+
+ net->reading_or_writing=1;
+ thr_alarm_init(&alarmed);
+#ifndef NO_ALARM
+ if (net_blocking)
+ thr_alarm(&alarmed,net->read_timeout,&alarm_buff);
+#else
+ /* Read timeout is set in my_net_set_read_timeout */
+#endif /* NO_ALARM */
+
+ pos = net->buff + net->where_b; /* net->packet -4 */
+ for (i=0 ; i < 2 ; i++)
+ {
+ while (remain > 0)
+ {
+ /* First read is done with non blocking mode */
+ if ((long) (length= vio_read(net->vio, pos, remain)) <= 0L)
+ {
+ my_bool interrupted = vio_should_retry(net->vio);
+
+ DBUG_PRINT("info",("vio_read returned %ld errno: %d",
+ (long) length, vio_errno(net->vio)));
+#if !defined(__WIN__) && defined(MYSQL_SERVER)
+ /*
+ We got an error that there was no data on the socket. We now set up
+ an alarm to not 'read forever', change the socket to the blocking
+ mode and try again
+ */
+ if ((interrupted || length == 0) && !thr_alarm_in_use(&alarmed))
+ {
+ if (!thr_alarm(&alarmed,net->read_timeout,&alarm_buff)) /* Don't wait too long */
+ {
+ my_bool old_mode;
+ while (vio_blocking(net->vio, TRUE, &old_mode) < 0)
+ {
+ if (vio_should_retry(net->vio) &&
+ retry_count++ < net->retry_count)
+ continue;
+ DBUG_PRINT("error",
+ ("fcntl returned error %d, aborting thread",
+ vio_errno(net->vio)));
+ EXTRA_DEBUG_fprintf(stderr,
+ "%s: read: fcntl returned error %d, aborting thread\n",
+ my_progname,vio_errno(net->vio));
+ len= packet_error;
+ net->error= 2; /* Close socket */
+ net->last_errno= ER_NET_FCNTL_ERROR;
+ MYSQL_SERVER_my_error(ER_NET_FCNTL_ERROR, MYF(0));
+ goto end;
+ }
+ retry_count=0;
+ continue;
+ }
+ }
+#endif /* (!defined(__WIN__) && defined(MYSQL_SERVER) */
+ if (thr_alarm_in_use(&alarmed) && !thr_got_alarm(&alarmed) &&
+ interrupted)
+ { /* Probably in MIT threads */
+ if (retry_count++ < net->retry_count)
+ continue;
+ EXTRA_DEBUG_fprintf(stderr, "%s: read looped with error %d, aborting thread\n",
+ my_progname,vio_errno(net->vio));
+ }
+#ifndef MYSQL_SERVER
+ if (length != 0 && vio_errno(net->vio) == SOCKET_EINTR)
+ {
+ DBUG_PRINT("warning",("Interrupted read. Retrying..."));
+ continue;
+ }
+#endif
+ DBUG_PRINT("error",("Couldn't read packet: remain: %u errno: %d length: %ld",
+ remain, vio_errno(net->vio), (long) length));
+ len= packet_error;
+ net->error= 2; /* Close socket */
+ net->last_errno= (vio_was_timeout(net->vio) ?
+ ER_NET_READ_INTERRUPTED :
+ ER_NET_READ_ERROR);
+ MYSQL_SERVER_my_error(net->last_errno, MYF(0));
+ goto end;
+ }
+ remain -= (uint32) length;
+ pos+= length;
+ update_statistics(thd_increment_bytes_received(length));
+ }
+ if (i == 0)
+ { /* First parts is packet length */
+ ulong helping;
+ DBUG_DUMP("packet_header", net->buff+net->where_b,
+ NET_HEADER_SIZE);
+ if (net->buff[net->where_b + 3] != (uchar) net->pkt_nr)
+ {
+ if (net->buff[net->where_b] != (uchar) 255)
+ {
+ DBUG_PRINT("error",
+ ("Packets out of order (Found: %d, expected %u)",
+ (int) net->buff[net->where_b + 3],
+ net->pkt_nr));
+ /*
+ We don't make noise server side, since the client is expected
+ to break the protocol for e.g. --send LOAD DATA .. LOCAL where
+ the server expects the client to send a file, but the client
+ may reply with a new command instead.
+ */
+#ifndef MYSQL_SERVER
+ EXTRA_DEBUG_fflush(stdout);
+ EXTRA_DEBUG_fprintf(stderr,"Error: Packets out of order (Found: %d, expected %d)\n",
+ (int) net->buff[net->where_b + 3],
+ (uint) (uchar) net->pkt_nr);
+ EXTRA_DEBUG_fflush(stderr);
+#endif
+ }
+ len= packet_error;
+ /* Not a NET error on the client. XXX: why? */
+ MYSQL_SERVER_my_error(ER_NET_PACKETS_OUT_OF_ORDER, MYF(0));
+ goto end;
+ }
+ net->compress_pkt_nr= ++net->pkt_nr;
+#ifdef HAVE_COMPRESS
+ if (net->compress)
+ {
+ /*
+ The following uint3korr() may read 4 bytes, so make sure we don't
+ read unallocated or uninitialized memory. The right-hand expression
+ must match the size of the buffer allocated in net_realloc().
+ */
+ DBUG_ASSERT(net->where_b + NET_HEADER_SIZE + sizeof(uint32) <=
+ net->max_packet + NET_HEADER_SIZE + COMP_HEADER_SIZE + 1);
+ /*
+ If the packet is compressed then complen > 0 and contains the
+ number of bytes in the uncompressed packet
+ */
+ *complen=uint3korr(&(net->buff[net->where_b + NET_HEADER_SIZE]));
+ }
+#endif
+
+ len=uint3korr(net->buff+net->where_b);
+ if (!len) /* End of big multi-packet */
+ goto end;
+ helping = MY_MAX(len,*complen) + net->where_b;
+ /* The necessary size of net->buff */
+ if (helping >= net->max_packet)
+ {
+ if (net_realloc(net,helping))
+ {
+#if defined(MYSQL_SERVER) && !defined(NO_ALARM)
+ if (!net->compress &&
+ !my_net_skip_rest(net, (uint32) len, &alarmed, &alarm_buff))
+ net->error= 3; /* Successfully skiped packet */
+#endif
+ len= packet_error; /* Return error and close connection */
+ goto end;
+ }
+ }
+ pos=net->buff + net->where_b;
+ remain = (uint32) len;
+#ifdef MYSQL_SERVER
+ if (server_extension != NULL)
+ {
+ void *user_data= server_extension->m_user_data;
+ server_extension->m_after_header(net, user_data, count, 0);
+ server_extension= NULL;
+ }
+#endif
+ }
+ }
+
+end:
+ if (thr_alarm_in_use(&alarmed))
+ {
+ my_bool old_mode;
+ thr_end_alarm(&alarmed);
+ if (!net_blocking)
+ vio_blocking(net->vio, net_blocking, &old_mode);
+ }
+ net->reading_or_writing=0;
+#ifdef DEBUG_DATA_PACKETS
+ if (len != packet_error)
+ DBUG_DUMP("data", net->buff+net->where_b, len);
+#endif
+#ifdef MYSQL_SERVER
+ if (server_extension != NULL)
+ {
+ void *user_data= server_extension->m_user_data;
+ server_extension->m_after_header(net, user_data, count, 1);
+ DBUG_ASSERT(len == packet_error || len == 0);
+ }
+#endif
+ return(len);
+}
+
+
+/* Old interface. See my_net_read_packet() for function description */
+
+#undef my_net_read
+
+ulong my_net_read(NET *net)
+{
+ return my_net_read_packet(net, 0);
+}
+
+
+/**
+ Read a packet from the client/server and return it without the internal
+ package header.
+
+ If the packet is the first packet of a multi-packet packet
+ (which is indicated by the length of the packet = 0xffffff) then
+ all sub packets are read and concatenated.
+
+ If the packet was compressed, its uncompressed and the length of the
+ uncompressed packet is returned.
+
+ read_from_server is set when the server is reading a new command
+ from the client.
+
+ @return
+ The function returns the length of the found packet or packet_error.
+ net->read_pos points to the read data.
+*/
+
+
+ulong
+my_net_read_packet(NET *net, my_bool read_from_server)
+{
+ size_t len, complen;
+
+ MYSQL_NET_READ_START();
+
+#ifdef HAVE_COMPRESS
+ if (!net->compress)
+ {
+#endif
+ len = my_real_read(net,&complen, read_from_server);
+ if (len == MAX_PACKET_LENGTH)
+ {
+ /* First packet of a multi-packet. Concatenate the packets */
+ ulong save_pos = net->where_b;
+ size_t total_length= 0;
+ do
+ {
+ net->where_b += len;
+ total_length += len;
+ len = my_real_read(net,&complen, 0);
+ } while (len == MAX_PACKET_LENGTH);
+ if (len != packet_error)
+ len+= total_length;
+ net->where_b = save_pos;
+ }
+ net->read_pos = net->buff + net->where_b;
+ if (len != packet_error)
+ net->read_pos[len]=0; /* Safeguard for mysql_use_result */
+ MYSQL_NET_READ_DONE(0, len);
+ return len;
+#ifdef HAVE_COMPRESS
+ }
+ else
+ {
+ /* We are using the compressed protocol */
+
+ ulong buf_length;
+ ulong start_of_packet;
+ ulong first_packet_offset;
+ uint read_length, multi_byte_packet=0;
+
+ if (net->remain_in_buf)
+ {
+ buf_length= net->buf_length; /* Data left in old packet */
+ first_packet_offset= start_of_packet= (net->buf_length -
+ net->remain_in_buf);
+ /* Restore the character that was overwritten by the end 0 */
+ net->buff[start_of_packet]= net->save_char;
+ }
+ else
+ {
+ /* reuse buffer, as there is nothing in it that we need */
+ buf_length= start_of_packet= first_packet_offset= 0;
+ }
+ for (;;)
+ {
+ ulong packet_len;
+
+ if (buf_length - start_of_packet >= NET_HEADER_SIZE)
+ {
+ read_length = uint3korr(net->buff+start_of_packet);
+ if (!read_length)
+ {
+ /* End of multi-byte packet */
+ start_of_packet += NET_HEADER_SIZE;
+ break;
+ }
+ if (read_length + NET_HEADER_SIZE <= buf_length - start_of_packet)
+ {
+ if (multi_byte_packet)
+ {
+ /* Remove packet header for second packet */
+ memmove(net->buff + first_packet_offset + start_of_packet,
+ net->buff + first_packet_offset + start_of_packet +
+ NET_HEADER_SIZE,
+ buf_length - start_of_packet);
+ start_of_packet += read_length;
+ buf_length -= NET_HEADER_SIZE;
+ }
+ else
+ start_of_packet+= read_length + NET_HEADER_SIZE;
+
+ if (read_length != MAX_PACKET_LENGTH) /* last package */
+ {
+ multi_byte_packet= 0; /* No last zero len packet */
+ break;
+ }
+ multi_byte_packet= NET_HEADER_SIZE;
+ /* Move data down to read next data packet after current one */
+ if (first_packet_offset)
+ {
+ memmove(net->buff,net->buff+first_packet_offset,
+ buf_length-first_packet_offset);
+ buf_length-=first_packet_offset;
+ start_of_packet -= first_packet_offset;
+ first_packet_offset=0;
+ }
+ continue;
+ }
+ }
+ /* Move data down to read next data packet after current one */
+ if (first_packet_offset)
+ {
+ memmove(net->buff,net->buff+first_packet_offset,
+ buf_length-first_packet_offset);
+ buf_length-=first_packet_offset;
+ start_of_packet -= first_packet_offset;
+ first_packet_offset=0;
+ }
+
+ net->where_b=buf_length;
+ if ((packet_len = my_real_read(net,&complen, read_from_server))
+ == packet_error)
+ {
+ MYSQL_NET_READ_DONE(1, 0);
+ return packet_error;
+ }
+ read_from_server= 0;
+ if (my_uncompress(net->buff + net->where_b, packet_len,
+ &complen))
+ {
+ net->error= 2; /* caller will close socket */
+ net->last_errno= ER_NET_UNCOMPRESS_ERROR;
+ MYSQL_SERVER_my_error(ER_NET_UNCOMPRESS_ERROR, MYF(0));
+ MYSQL_NET_READ_DONE(1, 0);
+ return packet_error;
+ }
+ buf_length+= complen;
+ }
+
+ net->read_pos= net->buff+ first_packet_offset + NET_HEADER_SIZE;
+ net->buf_length= buf_length;
+ net->remain_in_buf= (ulong) (buf_length - start_of_packet);
+ len = ((ulong) (start_of_packet - first_packet_offset) - NET_HEADER_SIZE -
+ multi_byte_packet);
+ net->save_char= net->read_pos[len]; /* Must be saved */
+ net->read_pos[len]=0; /* Safeguard for mysql_use_result */
+ }
+#endif /* HAVE_COMPRESS */
+ MYSQL_NET_READ_DONE(0, len);
+ return len;
+}
+
+
+void my_net_set_read_timeout(NET *net, uint timeout)
+{
+ DBUG_ENTER("my_net_set_read_timeout");
+ DBUG_PRINT("enter", ("timeout: %d", timeout));
+ if (net->read_timeout != timeout)
+ {
+ net->read_timeout= timeout;
+ if (net->vio)
+ vio_timeout(net->vio, 0, timeout);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void my_net_set_write_timeout(NET *net, uint timeout)
+{
+ DBUG_ENTER("my_net_set_write_timeout");
+ DBUG_PRINT("enter", ("timeout: %d", timeout));
+ if (net->write_timeout != timeout)
+ {
+ net->write_timeout= timeout;
+ if (net->vio)
+ vio_timeout(net->vio, 1, timeout);
+ }
+ DBUG_VOID_RETURN;
+}
diff --git a/sql/nt_servc.cc b/sql/nt_servc.cc
new file mode 100644
index 00000000000..d6a8eac7ed5
--- /dev/null
+++ b/sql/nt_servc.cc
@@ -0,0 +1,569 @@
+/**
+ @file
+
+ @brief
+ Windows NT Service class library.
+
+ Copyright Abandoned 1998 Irena Pancirov - Irnet Snc
+ This file is public domain and comes with NO WARRANTY of any kind
+*/
+#include <windows.h>
+#include <process.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "nt_servc.h"
+
+
+static NTService *pService;
+
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+NTService::NTService()
+{
+
+ bOsNT = FALSE;
+ //service variables
+ ServiceName = NULL;
+ hExitEvent = 0;
+ bPause = FALSE;
+ bRunning = FALSE;
+ hThreadHandle = 0;
+ fpServiceThread = NULL;
+
+ //time-out variables
+ nStartTimeOut = 15000;
+ nStopTimeOut = 86400000;
+ nPauseTimeOut = 5000;
+ nResumeTimeOut = 5000;
+
+ //install variables
+ dwDesiredAccess = SERVICE_ALL_ACCESS;
+ dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ dwStartType = SERVICE_AUTO_START;
+ dwErrorControl = SERVICE_ERROR_NORMAL;
+ szLoadOrderGroup = NULL;
+ lpdwTagID = NULL;
+ szDependencies = NULL;
+
+ my_argc = 0;
+ my_argv = NULL;
+ hShutdownEvent = 0;
+ nError = 0;
+ dwState = 0;
+}
+
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+NTService::~NTService()
+{
+ if (ServiceName != NULL) delete[] ServiceName;
+}
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+
+BOOL NTService::GetOS()
+{
+ bOsNT = FALSE;
+ memset(&osVer, 0, sizeof(OSVERSIONINFO));
+ osVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ if (GetVersionEx(&osVer))
+ {
+ if (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ bOsNT = TRUE;
+ }
+ return bOsNT;
+}
+
+
+/**
+ Registers the main service thread with the service manager.
+
+ @param ServiceThread pointer to the main programs entry function
+ when the service is started
+*/
+
+
+long NTService::Init(LPCSTR szInternName,void *ServiceThread)
+{
+
+ pService = this;
+
+ fpServiceThread = (THREAD_FC)ServiceThread;
+ ServiceName = new char[lstrlen(szInternName)+1];
+ lstrcpy(ServiceName,szInternName);
+
+ SERVICE_TABLE_ENTRY stb[] =
+ {
+ { (char *)szInternName,(LPSERVICE_MAIN_FUNCTION) ServiceMain} ,
+ { NULL, NULL }
+ };
+
+ return StartServiceCtrlDispatcher(stb); //register with the Service Manager
+}
+
+
+/**
+ Installs the service with Service manager.
+
+ nError values:
+ - 0 success
+ - 1 Can't open the Service manager
+ - 2 Failed to create service.
+*/
+
+
+BOOL NTService::Install(int startType, LPCSTR szInternName,
+ LPCSTR szDisplayName,
+ LPCSTR szFullPath, LPCSTR szAccountName,
+ LPCSTR szPassword)
+{
+ BOOL ret_val=FALSE;
+ SC_HANDLE newService, scm;
+
+ if (!SeekStatus(szInternName,1))
+ return FALSE;
+
+ char szFilePath[_MAX_PATH];
+ GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));
+
+ // open a connection to the SCM
+ if (!(scm = OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE)))
+ printf("Failed to install the service (Couldn't open the SCM)\n");
+ else // Install the new service
+ {
+ if (!(newService=
+ CreateService(scm,
+ szInternName,
+ szDisplayName,
+ dwDesiredAccess,//default: SERVICE_ALL_ACCESS
+ dwServiceType, //default: SERVICE_WIN32_OWN_PROCESS
+ //default: SERVICE_AUTOSTART
+ (startType == 1 ? SERVICE_AUTO_START :
+ SERVICE_DEMAND_START),
+ dwErrorControl, //default: SERVICE_ERROR_NORMAL
+ szFullPath, //exec full path
+ szLoadOrderGroup, //default: NULL
+ lpdwTagID, //default: NULL
+ szDependencies, //default: NULL
+ szAccountName, //default: NULL
+ szPassword))) //default: NULL
+ printf("Failed to install the service (Couldn't create service)\n");
+ else
+ {
+ printf("Service successfully installed.\n");
+ CloseServiceHandle(newService);
+ ret_val=TRUE; // Everything went ok
+ }
+ CloseServiceHandle(scm);
+ }
+ return ret_val;
+}
+
+
+/**
+ Removes the service.
+
+ nError values:
+ - 0 success
+ - 1 Can't open the Service manager
+ - 2 Failed to locate service
+ - 3 Failed to delete service.
+*/
+
+
+BOOL NTService::Remove(LPCSTR szInternName)
+{
+ BOOL ret_value=FALSE;
+ SC_HANDLE service, scm;
+
+ if (!SeekStatus(szInternName,0))
+ return FALSE;
+
+ nError=0;
+
+ // open a connection to the SCM
+ if (!(scm = OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE)))
+ {
+ printf("Failed to remove the service (Couldn't open the SCM)\n");
+ }
+ else
+ {
+ if ((service = OpenService(scm,szInternName, DELETE)))
+ {
+ if (!DeleteService(service))
+ printf("Failed to remove the service\n");
+ else
+ {
+ printf("Service successfully removed.\n");
+ ret_value=TRUE; // everything went ok
+ }
+ CloseServiceHandle(service);
+ }
+ else
+ printf("Failed to remove the service (Couldn't open the service)\n");
+ CloseServiceHandle(scm);
+ }
+ return ret_value;
+}
+
+/**
+ this function should be called before the app. exits to stop
+ the service
+*/
+void NTService::Stop(void)
+{
+ SetStatus(SERVICE_STOP_PENDING,NO_ERROR, 0, 1, 60000);
+ StopService();
+ SetStatus(SERVICE_STOPPED, NO_ERROR, 0, 1, 1000);
+}
+
+/**
+ This is the function that is called from the
+ service manager to start the service.
+*/
+
+
+void NTService::ServiceMain(DWORD argc, LPTSTR *argv)
+{
+
+ // registration function
+ if (!(pService->hServiceStatusHandle =
+ RegisterServiceCtrlHandler(pService->ServiceName,
+ (LPHANDLER_FUNCTION)
+ NTService::ServiceCtrlHandler)))
+ goto error;
+
+ // notify SCM of progress
+ if (!pService->SetStatus(SERVICE_START_PENDING,NO_ERROR, 0, 1, 8000))
+ goto error;
+
+ // create the exit event
+ if (!(pService->hExitEvent = CreateEvent (0, TRUE, FALSE,0)))
+ goto error;
+
+ if (!pService->SetStatus(SERVICE_START_PENDING,NO_ERROR, 0, 3,
+ pService->nStartTimeOut))
+ goto error;
+
+ // save start arguments
+ pService->my_argc=argc;
+ pService->my_argv=argv;
+
+ // start the service
+ if (!pService->StartService())
+ goto error;
+
+ // wait for exit event
+ WaitForSingleObject (pService->hExitEvent, INFINITE);
+
+ // wait for thread to exit
+ if (WaitForSingleObject (pService->hThreadHandle, INFINITE) == WAIT_TIMEOUT)
+ CloseHandle(pService->hThreadHandle);
+
+ pService->Exit(0);
+ return;
+
+error:
+ pService->Exit(GetLastError());
+ return;
+}
+
+
+
+void NTService::SetRunning()
+{
+ if (pService)
+ pService->SetStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
+}
+
+void NTService::SetSlowStarting(unsigned long timeout)
+{
+ if (pService)
+ pService->SetStatus(SERVICE_START_PENDING,NO_ERROR, 0, 0, timeout);
+}
+
+
+/* ------------------------------------------------------------------------
+ StartService() - starts the application thread
+ -------------------------------------------------------------------------- */
+
+BOOL NTService::StartService()
+{
+ // Start the real service's thread (application)
+ if (!(hThreadHandle = (HANDLE) _beginthread((THREAD_FC)fpServiceThread,0,
+ (void *) this)))
+ return FALSE;
+ bRunning = TRUE;
+ return TRUE;
+}
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+void NTService::StopService()
+{
+ bRunning=FALSE;
+
+ // Set the event for application
+ if (hShutdownEvent)
+ SetEvent(hShutdownEvent);
+
+ // Set the event for ServiceMain
+ SetEvent(hExitEvent);
+}
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+void NTService::PauseService()
+{
+ bPause = TRUE;
+ SuspendThread(hThreadHandle);
+}
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+void NTService::ResumeService()
+{
+ bPause=FALSE;
+ ResumeThread(hThreadHandle);
+}
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+BOOL NTService::SetStatus (DWORD dwCurrentState,DWORD dwWin32ExitCode,
+ DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint,
+ DWORD dwWaitHint)
+{
+ BOOL bRet;
+ SERVICE_STATUS serviceStatus;
+
+ dwState=dwCurrentState;
+
+ serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ serviceStatus.dwCurrentState = dwCurrentState;
+
+ if (dwCurrentState == SERVICE_START_PENDING)
+ serviceStatus.dwControlsAccepted = 0; //don't accept control events
+ else
+ serviceStatus.dwControlsAccepted = (SERVICE_ACCEPT_STOP |
+ SERVICE_ACCEPT_PAUSE_CONTINUE |
+ SERVICE_ACCEPT_SHUTDOWN);
+
+ // if a specific exit code is defined,set up the win32 exit code properly
+ if (dwServiceSpecificExitCode == 0)
+ serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
+ else
+ serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
+
+ serviceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
+
+ serviceStatus.dwCheckPoint = dwCheckPoint;
+ serviceStatus.dwWaitHint = dwWaitHint;
+
+ // Pass the status to the Service Manager
+ if (!(bRet=SetServiceStatus (hServiceStatusHandle, &serviceStatus)))
+ StopService();
+
+ return bRet;
+}
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+void NTService::ServiceCtrlHandler(DWORD ctrlCode)
+{
+ DWORD dwState;
+
+ if (!pService)
+ return;
+
+ dwState=pService->dwState; // get current state
+
+ switch(ctrlCode) {
+ case SERVICE_CONTROL_SHUTDOWN:
+ case SERVICE_CONTROL_STOP:
+ dwState = SERVICE_STOP_PENDING;
+ pService->SetStatus(SERVICE_STOP_PENDING,NO_ERROR, 0, 1,
+ pService->nStopTimeOut);
+ pService->StopService();
+ break;
+
+ default:
+ pService->SetStatus(dwState, NO_ERROR,0, 0, 0);
+ break;
+ }
+ //pService->SetStatus(dwState, NO_ERROR,0, 0, 0);
+}
+
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+
+void NTService::Exit(DWORD error)
+{
+ if (hExitEvent)
+ CloseHandle(hExitEvent);
+
+ // Send a message to the scm to tell that we stop
+ if (hServiceStatusHandle)
+ SetStatus(SERVICE_STOPPED, error,0, 0, 0);
+
+ // If the thread has started kill it ???
+ // if (hThreadHandle) CloseHandle(hThreadHandle);
+
+}
+
+/* ------------------------------------------------------------------------
+
+ -------------------------------------------------------------------------- */
+
+BOOL NTService::SeekStatus(LPCSTR szInternName, int OperationType)
+{
+ BOOL ret_value=FALSE;
+ SC_HANDLE service, scm;
+
+ // open a connection to the SCM
+ if (!(scm = OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE)))
+ {
+ DWORD ret_error=GetLastError();
+ if (ret_error == ERROR_ACCESS_DENIED)
+ {
+ printf("Install/Remove of the Service Denied!\n");
+ if (!is_super_user())
+ printf("That operation should be made by an user with Administrator privileges!\n");
+ }
+ else
+ printf("There is a problem for to open the Service Control Manager!\n");
+ }
+ else
+ {
+ if (OperationType == 1)
+ {
+ /* an install operation */
+ if ((service = OpenService(scm,szInternName, SERVICE_ALL_ACCESS )))
+ {
+ LPQUERY_SERVICE_CONFIG ConfigBuf;
+ DWORD dwSize;
+
+ ConfigBuf = (LPQUERY_SERVICE_CONFIG) LocalAlloc(LPTR, 4096);
+ printf("The service already exists!\n");
+ if (QueryServiceConfig(service,ConfigBuf,4096,&dwSize))
+ printf("The current server installed: %s\n",
+ ConfigBuf->lpBinaryPathName);
+ LocalFree(ConfigBuf);
+ CloseServiceHandle(service);
+ }
+ else
+ ret_value=TRUE;
+ }
+ else
+ {
+ /* a remove operation */
+ if (!(service = OpenService(scm,szInternName, SERVICE_ALL_ACCESS )))
+ printf("The service doesn't exist!\n");
+ else
+ {
+ SERVICE_STATUS ss;
+
+ memset(&ss, 0, sizeof(ss));
+ if (QueryServiceStatus(service,&ss))
+ {
+ DWORD dwState = ss.dwCurrentState;
+ if (dwState == SERVICE_RUNNING)
+ printf("Failed to remove the service because the service is running\nStop the service and try again\n");
+ else if (dwState == SERVICE_STOP_PENDING)
+ printf("\
+Failed to remove the service because the service is in stop pending state!\n\
+Wait 30 seconds and try again.\n\
+If this condition persist, reboot the machine and try again\n");
+ else
+ ret_value= TRUE;
+ }
+ CloseServiceHandle(service);
+ }
+ }
+ CloseServiceHandle(scm);
+ }
+
+ return ret_value;
+}
+/* ------------------------------------------------------------------------
+ -------------------------------------------------------------------------- */
+BOOL NTService::IsService(LPCSTR ServiceName)
+{
+ BOOL ret_value=FALSE;
+ SC_HANDLE service, scm;
+
+ if ((scm= OpenSCManager(0, 0,SC_MANAGER_ENUMERATE_SERVICE)))
+ {
+ if ((service = OpenService(scm,ServiceName, SERVICE_QUERY_STATUS)))
+ {
+ ret_value=TRUE;
+ CloseServiceHandle(service);
+ }
+ CloseServiceHandle(scm);
+ }
+ return ret_value;
+}
+/* ------------------------------------------------------------------------
+ -------------------------------------------------------------------------- */
+BOOL NTService::got_service_option(char **argv, char *service_option)
+{
+ char *option;
+ for (option= argv[1]; *option; option++)
+ if (!strcmp(option, service_option))
+ return TRUE;
+ return FALSE;
+}
+/* ------------------------------------------------------------------------
+ -------------------------------------------------------------------------- */
+BOOL NTService::is_super_user()
+{
+ HANDLE hAccessToken;
+ UCHAR InfoBuffer[1024];
+ PTOKEN_GROUPS ptgGroups=(PTOKEN_GROUPS)InfoBuffer;
+ DWORD dwInfoBufferSize;
+ PSID psidAdministrators;
+ SID_IDENTIFIER_AUTHORITY siaNtAuthority = SECURITY_NT_AUTHORITY;
+ UINT x;
+ BOOL ret_value=FALSE;
+
+ if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE,&hAccessToken ))
+ {
+ if (GetLastError() != ERROR_NO_TOKEN)
+ return FALSE;
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hAccessToken))
+ return FALSE;
+ }
+
+ ret_value= GetTokenInformation(hAccessToken,TokenGroups,InfoBuffer,
+ 1024, &dwInfoBufferSize);
+
+ CloseHandle(hAccessToken);
+
+ if (!ret_value )
+ return FALSE;
+
+ if (!AllocateAndInitializeSid(&siaNtAuthority, 2,
+ SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS,
+ 0, 0, 0, 0, 0, 0,
+ &psidAdministrators))
+ return FALSE;
+
+ ret_value = FALSE;
+
+ for (x=0;x<ptgGroups->GroupCount;x++)
+ {
+ if ( EqualSid(psidAdministrators, ptgGroups->Groups[x].Sid) )
+ {
+ ret_value = TRUE;
+ break;
+ }
+
+ }
+ FreeSid(psidAdministrators);
+ return ret_value;
+}
diff --git a/sql/nt_servc.h b/sql/nt_servc.h
new file mode 100644
index 00000000000..949499d8d7f
--- /dev/null
+++ b/sql/nt_servc.h
@@ -0,0 +1,115 @@
+#ifndef NT_SERVC_INCLUDED
+#define NT_SERVC_INCLUDED
+
+/**
+ @file
+
+ @brief
+ Windows NT Service class library
+
+ Copyright Abandoned 1998 Irena Pancirov - Irnet Snc
+ This file is public domain and comes with NO WARRANTY of any kind
+*/
+
+// main application thread
+typedef void (*THREAD_FC)(void *);
+
+class NTService
+{
+ public:
+ NTService();
+ ~NTService();
+
+ BOOL bOsNT; ///< true if OS is NT, false for Win95
+ //install optinos
+ DWORD dwDesiredAccess;
+ DWORD dwServiceType;
+ DWORD dwStartType;
+ DWORD dwErrorControl;
+
+ LPSTR szLoadOrderGroup;
+ LPDWORD lpdwTagID;
+ LPSTR szDependencies;
+ OSVERSIONINFO osVer;
+
+ // time-out (in milisec)
+ int nStartTimeOut;
+ int nStopTimeOut;
+ int nPauseTimeOut;
+ int nResumeTimeOut;
+
+ //
+ DWORD my_argc;
+ LPTSTR *my_argv;
+ HANDLE hShutdownEvent;
+ int nError;
+ DWORD dwState;
+
+ BOOL GetOS(); // returns TRUE if WinNT
+ BOOL IsNT() { return bOsNT;}
+ //init service entry point
+ long Init(LPCSTR szInternName,void *ServiceThread);
+
+ //application shutdown event
+ void SetShutdownEvent(HANDLE hEvent){ hShutdownEvent=hEvent; }
+
+
+ //service install / un-install
+ BOOL Install(int startType,LPCSTR szInternName,LPCSTR szDisplayName,
+ LPCSTR szFullPath, LPCSTR szAccountName=NULL,
+ LPCSTR szPassword=NULL);
+ BOOL SeekStatus(LPCSTR szInternName, int OperationType);
+ BOOL Remove(LPCSTR szInternName);
+ BOOL IsService(LPCSTR ServiceName);
+ BOOL got_service_option(char **argv, char *service_option);
+ BOOL is_super_user();
+
+ /*
+ SetRunning() is to be called by the application
+ when initialization completes and it can accept
+ stop request
+ */
+ void SetRunning(void);
+
+ /**
+ Sets a timeout after which SCM will abort service startup if SetRunning()
+ was not called or the timeout was not extended with another call to
+ SetSlowStarting(). Should be called when static initialization completes,
+ and the variable initialization part begins
+
+ @arg timeout the timeout to pass to the SCM (in milliseconds)
+ */
+ void SetSlowStarting(unsigned long timeout);
+
+ /*
+ Stop() is to be called by the application to stop
+ the service
+ */
+ void Stop(void);
+
+ protected:
+ LPSTR ServiceName;
+ HANDLE hExitEvent;
+ SERVICE_STATUS_HANDLE hServiceStatusHandle;
+ BOOL bPause;
+ BOOL bRunning;
+ HANDLE hThreadHandle;
+ THREAD_FC fpServiceThread;
+
+ void PauseService();
+ void ResumeService();
+ void StopService();
+ BOOL StartService();
+
+ static void ServiceMain(DWORD argc, LPTSTR *argv);
+ static void ServiceCtrlHandler (DWORD ctrlCode);
+
+ void Exit(DWORD error);
+ BOOL SetStatus (DWORD dwCurrentState,DWORD dwWin32ExitCode,
+ DWORD dwServiceSpecificExitCode,
+ DWORD dwCheckPoint,DWORD dwWaitHint);
+
+};
+/* ------------------------- the end -------------------------------------- */
+
+#endif /* NT_SERVC_INCLUDED */
diff --git a/sql/opt_index_cond_pushdown.cc b/sql/opt_index_cond_pushdown.cc
new file mode 100644
index 00000000000..be33e46bf94
--- /dev/null
+++ b/sql/opt_index_cond_pushdown.cc
@@ -0,0 +1,446 @@
+/*
+ Copyright (c) 2009, 2012, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include "sql_select.h"
+#include "sql_test.h"
+
+/****************************************************************************
+ * Index Condition Pushdown code starts
+ ***************************************************************************/
+/*
+ Check if given expression uses only table fields covered by the given index
+
+ SYNOPSIS
+ uses_index_fields_only()
+ item Expression to check
+ tbl The table having the index
+ keyno The index number
+ other_tbls_ok TRUE <=> Fields of other non-const tables are allowed
+
+ DESCRIPTION
+ Check if given expression only uses fields covered by index #keyno in the
+ table tbl. The expression can use any fields in any other tables.
+
+ The expression is guaranteed not to be AND or OR - those constructs are
+ handled outside of this function.
+
+ RETURN
+ TRUE Yes
+ FALSE No
+*/
+
+bool uses_index_fields_only(Item *item, TABLE *tbl, uint keyno,
+ bool other_tbls_ok)
+{
+ if (item->walk(&Item::limit_index_condition_pushdown_processor, FALSE, NULL))
+ {
+ return FALSE;
+ }
+
+ if (item->const_item())
+ return TRUE;
+
+ /*
+ Don't push down the triggered conditions. Nested outer joins execution
+ code may need to evaluate a condition several times (both triggered and
+ untriggered), and there is no way to put thi
+ TODO: Consider cloning the triggered condition and using the copies for:
+ 1. push the first copy down, to have most restrictive index condition
+ possible
+ 2. Put the second copy into tab->select_cond.
+ */
+ if (item->type() == Item::FUNC_ITEM &&
+ ((Item_func*)item)->functype() == Item_func::TRIG_COND_FUNC)
+ return FALSE;
+
+ if (!(item->used_tables() & tbl->map))
+ return other_tbls_ok;
+
+ Item::Type item_type= item->type();
+ switch (item_type) {
+ case Item::FUNC_ITEM:
+ {
+ /* This is a function, apply condition recursively to arguments */
+ Item_func *item_func= (Item_func*)item;
+ Item **child;
+ Item **item_end= (item_func->arguments()) + item_func->argument_count();
+ for (child= item_func->arguments(); child != item_end; child++)
+ {
+ if (!uses_index_fields_only(*child, tbl, keyno, other_tbls_ok))
+ return FALSE;
+ }
+ return TRUE;
+ }
+ case Item::COND_ITEM:
+ {
+ /*
+ This is a AND/OR condition. Regular AND/OR clauses are handled by
+ make_cond_for_index() which will chop off the part that can be
+ checked with index. This code is for handling non-top-level AND/ORs,
+ e.g. func(x AND y).
+ */
+ List_iterator<Item> li(*((Item_cond*)item)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ if (!uses_index_fields_only(item, tbl, keyno, other_tbls_ok))
+ return FALSE;
+ }
+ return TRUE;
+ }
+ case Item::FIELD_ITEM:
+ {
+ Item_field *item_field= (Item_field*)item;
+ Field *field= item_field->field;
+ if (field->table != tbl)
+ return TRUE;
+ /*
+ The below is probably a repetition - the first part checks the
+ other two, but let's play it safe:
+ */
+ if(!field->part_of_key.is_set(keyno) ||
+ field->type() == MYSQL_TYPE_GEOMETRY ||
+ field->type() == MYSQL_TYPE_BLOB)
+ return FALSE;
+ KEY *key_info= tbl->key_info + keyno;
+ KEY_PART_INFO *key_part= key_info->key_part;
+ KEY_PART_INFO *key_part_end= key_part + key_info->user_defined_key_parts;
+ for ( ; key_part < key_part_end; key_part++)
+ {
+ if (field->eq(key_part->field))
+ return !(key_part->key_part_flag & HA_PART_KEY_SEG);
+ }
+ if ((tbl->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
+ tbl->s->primary_key != MAX_KEY &&
+ tbl->s->primary_key != keyno)
+ {
+ key_info= tbl->key_info + tbl->s->primary_key;
+ key_part= key_info->key_part;
+ key_part_end= key_part + key_info->user_defined_key_parts;
+ for ( ; key_part < key_part_end; key_part++)
+ {
+ /*
+ It does not make sense to use the fact that the engine can read in
+ a full field if the key if the index is built only over a part
+ of this field.
+ */
+ if (field->eq(key_part->field))
+ return !(key_part->key_part_flag & HA_PART_KEY_SEG);
+ }
+ }
+ return FALSE;
+ }
+ case Item::REF_ITEM:
+ return uses_index_fields_only(item->real_item(), tbl, keyno,
+ other_tbls_ok);
+ default:
+ return FALSE; /* Play it safe, don't push unknown non-const items */
+ }
+}
+
+#define ICP_COND_USES_INDEX_ONLY 10
+
+/*
+ Get a part of the condition that can be checked using only index fields
+
+ SYNOPSIS
+ make_cond_for_index()
+ cond The source condition
+ table The table that is partially available
+ keyno The index in the above table. Only fields covered by the index
+ are available
+ other_tbls_ok TRUE <=> Fields of other non-const tables are allowed
+
+ DESCRIPTION
+ Get a part of the condition that can be checked when for the given table
+ we have values only of fields covered by some index. The condition may
+ refer to other tables, it is assumed that we have values of all of their
+ fields.
+
+ Example:
+ make_cond_for_index(
+ "cond(t1.field) AND cond(t2.key1) AND cond(t2.non_key) AND cond(t2.key2)",
+ t2, keyno(t2.key1))
+ will return
+ "cond(t1.field) AND cond(t2.key2)"
+
+ RETURN
+ Index condition, or NULL if no condition could be inferred.
+*/
+
+Item *make_cond_for_index(Item *cond, TABLE *table, uint keyno,
+ bool other_tbls_ok)
+{
+ if (!cond)
+ return NULL;
+ if (cond->type() == Item::COND_ITEM)
+ {
+ uint n_marked= 0;
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ table_map used_tables= 0;
+ Item_cond_and *new_cond=new Item_cond_and;
+ if (!new_cond)
+ return (COND*) 0;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix= make_cond_for_index(item, table, keyno, other_tbls_ok);
+ if (fix)
+ {
+ new_cond->argument_list()->push_back(fix);
+ used_tables|= fix->used_tables();
+ }
+ if (MY_TEST(item->marker == ICP_COND_USES_INDEX_ONLY))
+ {
+ n_marked++;
+ item->marker= 0;
+ }
+ }
+ if (n_marked ==((Item_cond*)cond)->argument_list()->elements)
+ cond->marker= ICP_COND_USES_INDEX_ONLY;
+ switch (new_cond->argument_list()->elements) {
+ case 0:
+ return (COND*) 0;
+ case 1:
+ new_cond->used_tables_cache= used_tables;
+ return new_cond->argument_list()->head();
+ default:
+ new_cond->quick_fix_field();
+ new_cond->used_tables_cache= used_tables;
+ return new_cond;
+ }
+ }
+ else /* It's OR */
+ {
+ Item_cond_or *new_cond=new Item_cond_or;
+ if (!new_cond)
+ return (COND*) 0;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix= make_cond_for_index(item, table, keyno, other_tbls_ok);
+ if (!fix)
+ return (COND*) 0;
+ new_cond->argument_list()->push_back(fix);
+ if (MY_TEST(item->marker == ICP_COND_USES_INDEX_ONLY))
+ {
+ n_marked++;
+ item->marker= 0;
+ }
+ }
+ if (n_marked ==((Item_cond*)cond)->argument_list()->elements)
+ cond->marker= ICP_COND_USES_INDEX_ONLY;
+ new_cond->quick_fix_field();
+ new_cond->used_tables_cache= ((Item_cond_or*) cond)->used_tables_cache;
+ new_cond->top_level_item();
+ return new_cond;
+ }
+ }
+
+ if (!uses_index_fields_only(cond, table, keyno, other_tbls_ok))
+ return (COND*) 0;
+ cond->marker= ICP_COND_USES_INDEX_ONLY;
+ return cond;
+}
+
+
+Item *make_cond_remainder(Item *cond, TABLE *table, uint keyno,
+ bool other_tbls_ok, bool exclude_index)
+{
+ if (cond->type() == Item::COND_ITEM)
+ {
+ table_map tbl_map= 0;
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ /* Create new top level AND item */
+ Item_cond_and *new_cond=new Item_cond_and;
+ if (!new_cond)
+ return (COND*) 0;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix= make_cond_remainder(item, table, keyno,
+ other_tbls_ok, exclude_index);
+ if (fix)
+ {
+ new_cond->argument_list()->push_back(fix);
+ tbl_map |= fix->used_tables();
+ }
+ }
+ switch (new_cond->argument_list()->elements) {
+ case 0:
+ return (COND*) 0;
+ case 1:
+ return new_cond->argument_list()->head();
+ default:
+ new_cond->quick_fix_field();
+ ((Item_cond*)new_cond)->used_tables_cache= tbl_map;
+ return new_cond;
+ }
+ }
+ else /* It's OR */
+ {
+ Item_cond_or *new_cond=new Item_cond_or;
+ if (!new_cond)
+ return (COND*) 0;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix= make_cond_remainder(item, table, keyno,
+ other_tbls_ok, FALSE);
+ if (!fix)
+ return (COND*) 0;
+ new_cond->argument_list()->push_back(fix);
+ tbl_map |= fix->used_tables();
+ }
+ new_cond->quick_fix_field();
+ ((Item_cond*)new_cond)->used_tables_cache= tbl_map;
+ new_cond->top_level_item();
+ return new_cond;
+ }
+ }
+ else
+ {
+ if (exclude_index &&
+ uses_index_fields_only(cond, table, keyno, other_tbls_ok))
+ return 0;
+ else
+ return cond;
+ }
+}
+
+
+/*
+ Try to extract and push the index condition
+
+ SYNOPSIS
+ push_index_cond()
+ tab A join tab that has tab->table->file and its condition
+ in tab->select_cond
+ keyno Index for which extract and push the condition
+
+ DESCRIPTION
+ Try to extract and push the index condition down to table handler
+*/
+
+void push_index_cond(JOIN_TAB *tab, uint keyno)
+{
+ DBUG_ENTER("push_index_cond");
+ Item *idx_cond;
+
+ /*
+ Backported the following from MySQL 5.6:
+ 6. The index is not a clustered index. The performance improvement
+ of pushing an index condition on a clustered key is much lower
+ than on a non-clustered key. This restriction should be
+ re-evaluated when WL#6061 is implemented.
+ */
+ if ((tab->table->file->index_flags(keyno, 0, 1) &
+ HA_DO_INDEX_COND_PUSHDOWN) &&
+ optimizer_flag(tab->join->thd, OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN) &&
+ tab->join->thd->lex->sql_command != SQLCOM_UPDATE_MULTI &&
+ tab->join->thd->lex->sql_command != SQLCOM_DELETE_MULTI &&
+ tab->type != JT_CONST && tab->type != JT_SYSTEM &&
+ !(keyno == tab->table->s->primary_key && // (6)
+ tab->table->file->primary_key_is_clustered())) // (6)
+
+ {
+ DBUG_EXECUTE("where",
+ print_where(tab->select_cond, "full cond", QT_ORDINARY););
+
+ idx_cond= make_cond_for_index(tab->select_cond, tab->table, keyno,
+ tab->icp_other_tables_ok);
+
+ DBUG_EXECUTE("where",
+ print_where(idx_cond, "idx cond", QT_ORDINARY););
+
+ if (idx_cond)
+ {
+ Item *idx_remainder_cond= 0;
+ tab->pre_idx_push_select_cond= tab->select_cond;
+ /*
+ For BKA cache we store condition to special BKA cache field
+ because evaluation of the condition requires additional operations
+ before the evaluation. This condition is used in
+ JOIN_CACHE_BKA[_UNIQUE]::skip_index_tuple() functions.
+ */
+ if (tab->use_join_cache &&
+ /*
+ if cache is used then the value is TRUE only
+ for BKA[_UNIQUE] cache (see check_join_cache_usage func).
+ */
+ tab->icp_other_tables_ok &&
+ (idx_cond->used_tables() &
+ ~(tab->table->map | tab->join->const_table_map)))
+ tab->cache_idx_cond= idx_cond;
+ else
+ idx_remainder_cond= tab->table->file->idx_cond_push(keyno, idx_cond);
+
+ /*
+ Disable eq_ref's "lookup cache" if we've pushed down an index
+ condition.
+ TODO: This check happens to work on current ICP implementations, but
+ there may exist a compliant implementation that will not work
+ correctly with it. Sort this out when we stabilize the condition
+ pushdown APIs.
+ */
+ if (idx_remainder_cond != idx_cond)
+ tab->ref.disable_cache= TRUE;
+
+ Item *row_cond= tab->idx_cond_fact_out ?
+ make_cond_remainder(tab->select_cond, tab->table, keyno,
+ tab->icp_other_tables_ok, TRUE) :
+ tab->pre_idx_push_select_cond;
+
+ DBUG_EXECUTE("where",
+ print_where(row_cond, "remainder cond", QT_ORDINARY););
+
+ if (row_cond)
+ {
+ if (!idx_remainder_cond)
+ tab->select_cond= row_cond;
+ else
+ {
+ COND *new_cond= new Item_cond_and(row_cond, idx_remainder_cond);
+ tab->select_cond= new_cond;
+ tab->select_cond->quick_fix_field();
+ ((Item_cond_and*)tab->select_cond)->used_tables_cache=
+ row_cond->used_tables() | idx_remainder_cond->used_tables();
+ }
+ }
+ else
+ tab->select_cond= idx_remainder_cond;
+ if (tab->select)
+ {
+ DBUG_EXECUTE("where",
+ print_where(tab->select->cond,
+ "select_cond",
+ QT_ORDINARY););
+
+ tab->select->cond= tab->select_cond;
+ tab->select->pre_idx_push_select_cond= tab->pre_idx_push_select_cond;
+ }
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
diff --git a/sql/opt_range.cc b/sql/opt_range.cc
new file mode 100644
index 00000000000..3597ade2cba
--- /dev/null
+++ b/sql/opt_range.cc
@@ -0,0 +1,15248 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2014, Monty Program Ab.
+
+ 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 */
+
+/*
+ TODO:
+ Fix that MAYBE_KEY are stored in the tree so that we can detect use
+ of full hash keys for queries like:
+
+ select s.id, kws.keyword_id from sites as s,kws where s.id=kws.site_id and kws.keyword_id in (204,205);
+
+*/
+
+/*
+ This file contains:
+
+ RangeAnalysisModule
+ A module that accepts a condition, index (or partitioning) description,
+ and builds lists of intervals (in index/partitioning space), such that
+ all possible records that match the condition are contained within the
+ intervals.
+ The entry point for the range analysis module is get_mm_tree() function.
+
+ The lists are returned in form of complicated structure of interlinked
+ SEL_TREE/SEL_IMERGE/SEL_ARG objects.
+ See quick_range_seq_next, find_used_partitions for examples of how to walk
+ this structure.
+ All direct "users" of this module are located within this file, too.
+
+
+ PartitionPruningModule
+ A module that accepts a partitioned table, condition, and finds which
+ partitions we will need to use in query execution. Search down for
+ "PartitionPruningModule" for description.
+ The module has single entry point - prune_partitions() function.
+
+
+ Range/index_merge/groupby-minmax optimizer module
+ A module that accepts a table, condition, and returns
+ - a QUICK_*_SELECT object that can be used to retrieve rows that match
+ the specified condition, or a "no records will match the condition"
+ statement.
+
+ The module entry points are
+ test_quick_select()
+ get_quick_select_for_ref()
+
+
+ Record retrieval code for range/index_merge/groupby-min-max.
+ Implementations of QUICK_*_SELECT classes.
+
+ KeyTupleFormat
+ ~~~~~~~~~~~~~~
+ The code in this file (and elsewhere) makes operations on key value tuples.
+ Those tuples are stored in the following format:
+
+ The tuple is a sequence of key part values. The length of key part value
+ depends only on its type (and not depends on the what value is stored)
+
+ KeyTuple: keypart1-data, keypart2-data, ...
+
+ The value of each keypart is stored in the following format:
+
+ keypart_data: [isnull_byte] keypart-value-bytes
+
+ If a keypart may have a NULL value (key_part->field->real_maybe_null() can
+ be used to check this), then the first byte is a NULL indicator with the
+ following valid values:
+ 1 - keypart has NULL value.
+ 0 - keypart has non-NULL value.
+
+ <questionable-statement> If isnull_byte==1 (NULL value), then the following
+ keypart->length bytes must be 0.
+ </questionable-statement>
+
+ keypart-value-bytes holds the value. Its format depends on the field type.
+ The length of keypart-value-bytes may or may not depend on the value being
+ stored. The default is that length is static and equal to
+ KEY_PART_INFO::length.
+
+ Key parts with (key_part_flag & HA_BLOB_PART) have length depending of the
+ value:
+
+ keypart-value-bytes: value_length value_bytes
+
+ The value_length part itself occupies HA_KEY_BLOB_LENGTH=2 bytes.
+
+ See key_copy() and key_restore() for code to move data between index tuple
+ and table record
+
+ CAUTION: the above description is only sergefp's understanding of the
+ subject and may omit some details.
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "key.h" // is_key_used, key_copy, key_cmp, key_restore
+#include "sql_parse.h" // check_stack_overrun
+#include "sql_partition.h" // get_part_id_func, PARTITION_ITERATOR,
+ // struct partition_info, NOT_A_PARTITION_ID
+#include "sql_base.h" // free_io_cache
+#include "records.h" // init_read_record, end_read_record
+#include <m_ctype.h>
+#include "sql_select.h"
+#include "sql_statistics.h"
+#include "filesort.h" // filesort_free_buffers
+
+#ifndef EXTRA_DEBUG
+#define test_rb_tree(A,B) {}
+#define test_use_count(A) {}
+#endif
+
+/*
+ Convert double value to #rows. Currently this does floor(), and we
+ might consider using round() instead.
+*/
+#define double2rows(x) ((ha_rows)(x))
+
+static int sel_cmp(Field *f,uchar *a,uchar *b,uint8 a_flag,uint8 b_flag);
+
+/*
+ this should be long enough so that any memcmp with a string that
+ starts from '\0' won't cross is_null_string boundaries, even
+ if the memcmp is optimized to compare 4- 8- or 16- bytes at once
+*/
+static uchar is_null_string[20]= {1,0};
+
+class RANGE_OPT_PARAM;
+/*
+ A construction block of the SEL_ARG-graph.
+
+ The following description only covers graphs of SEL_ARG objects with
+ sel_arg->type==KEY_RANGE:
+
+ One SEL_ARG object represents an "elementary interval" in form
+
+ min_value <=? table.keypartX <=? max_value
+
+ The interval is a non-empty interval of any kind: with[out] minimum/maximum
+ bound, [half]open/closed, single-point interval, etc.
+
+ 1. SEL_ARG GRAPH STRUCTURE
+
+ SEL_ARG objects are linked together in a graph. The meaning of the graph
+ is better demostrated by an example:
+
+ tree->keys[i]
+ |
+ | $ $
+ | part=1 $ part=2 $ part=3
+ | $ $
+ | +-------+ $ +-------+ $ +--------+
+ | | kp1<1 |--$-->| kp2=5 |--$-->| kp3=10 |
+ | +-------+ $ +-------+ $ +--------+
+ | | $ $ |
+ | | $ $ +--------+
+ | | $ $ | kp3=12 |
+ | | $ $ +--------+
+ | +-------+ $ $
+ \->| kp1=2 |--$--------------$-+
+ +-------+ $ $ | +--------+
+ | $ $ ==>| kp3=11 |
+ +-------+ $ $ | +--------+
+ | kp1=3 |--$--------------$-+ |
+ +-------+ $ $ +--------+
+ | $ $ | kp3=14 |
+ ... $ $ +--------+
+
+ The entire graph is partitioned into "interval lists".
+
+ An interval list is a sequence of ordered disjoint intervals over the same
+ key part. SEL_ARG are linked via "next" and "prev" pointers. Additionally,
+ all intervals in the list form an RB-tree, linked via left/right/parent
+ pointers. The RB-tree root SEL_ARG object will be further called "root of the
+ interval list".
+
+ In the example pic, there are 4 interval lists:
+ "kp<1 OR kp1=2 OR kp1=3", "kp2=5", "kp3=10 OR kp3=12", "kp3=11 OR kp3=13".
+ The vertical lines represent SEL_ARG::next/prev pointers.
+
+ In an interval list, each member X may have SEL_ARG::next_key_part pointer
+ pointing to the root of another interval list Y. The pointed interval list
+ must cover a key part with greater number (i.e. Y->part > X->part).
+
+ In the example pic, the next_key_part pointers are represented by
+ horisontal lines.
+
+ 2. SEL_ARG GRAPH SEMANTICS
+
+ It represents a condition in a special form (we don't have a name for it ATM)
+ The SEL_ARG::next/prev is "OR", and next_key_part is "AND".
+
+ For example, the picture represents the condition in form:
+ (kp1 < 1 AND kp2=5 AND (kp3=10 OR kp3=12)) OR
+ (kp1=2 AND (kp3=11 OR kp3=14)) OR
+ (kp1=3 AND (kp3=11 OR kp3=14))
+
+
+ 3. SEL_ARG GRAPH USE
+
+ Use get_mm_tree() to construct SEL_ARG graph from WHERE condition.
+ Then walk the SEL_ARG graph and get a list of dijsoint ordered key
+ intervals (i.e. intervals in form
+
+ (constA1, .., const1_K) < (keypart1,.., keypartK) < (constB1, .., constB_K)
+
+ Those intervals can be used to access the index. The uses are in:
+ - check_quick_select() - Walk the SEL_ARG graph and find an estimate of
+ how many table records are contained within all
+ intervals.
+ - get_quick_select() - Walk the SEL_ARG, materialize the key intervals,
+ and create QUICK_RANGE_SELECT object that will
+ read records within these intervals.
+
+ 4. SPACE COMPLEXITY NOTES
+
+ SEL_ARG graph is a representation of an ordered disjoint sequence of
+ intervals over the ordered set of index tuple values.
+
+ For multi-part keys, one can construct a WHERE expression such that its
+ list of intervals will be of combinatorial size. Here is an example:
+
+ (keypart1 IN (1,2, ..., n1)) AND
+ (keypart2 IN (1,2, ..., n2)) AND
+ (keypart3 IN (1,2, ..., n3))
+
+ For this WHERE clause the list of intervals will have n1*n2*n3 intervals
+ of form
+
+ (keypart1, keypart2, keypart3) = (k1, k2, k3), where 1 <= k{i} <= n{i}
+
+ SEL_ARG graph structure aims to reduce the amount of required space by
+ "sharing" the elementary intervals when possible (the pic at the
+ beginning of this comment has examples of such sharing). The sharing may
+ prevent combinatorial blowup:
+
+ There are WHERE clauses that have combinatorial-size interval lists but
+ will be represented by a compact SEL_ARG graph.
+ Example:
+ (keypartN IN (1,2, ..., n1)) AND
+ ...
+ (keypart2 IN (1,2, ..., n2)) AND
+ (keypart1 IN (1,2, ..., n3))
+
+ but not in all cases:
+
+ - There are WHERE clauses that do have a compact SEL_ARG-graph
+ representation but get_mm_tree() and its callees will construct a
+ graph of combinatorial size.
+ Example:
+ (keypart1 IN (1,2, ..., n1)) AND
+ (keypart2 IN (1,2, ..., n2)) AND
+ ...
+ (keypartN IN (1,2, ..., n3))
+
+ - There are WHERE clauses for which the minimal possible SEL_ARG graph
+ representation will have combinatorial size.
+ Example:
+ By induction: Let's take any interval on some keypart in the middle:
+
+ kp15=c0
+
+ Then let's AND it with this interval 'structure' from preceding and
+ following keyparts:
+
+ (kp14=c1 AND kp16=c3) OR keypart14=c2) (*)
+
+ We will obtain this SEL_ARG graph:
+
+ kp14 $ kp15 $ kp16
+ $ $
+ +---------+ $ +---------+ $ +---------+
+ | kp14=c1 |--$-->| kp15=c0 |--$-->| kp16=c3 |
+ +---------+ $ +---------+ $ +---------+
+ | $ $
+ +---------+ $ +---------+ $
+ | kp14=c2 |--$-->| kp15=c0 | $
+ +---------+ $ +---------+ $
+ $ $
+
+ Note that we had to duplicate "kp15=c0" and there was no way to avoid
+ that.
+ The induction step: AND the obtained expression with another "wrapping"
+ expression like (*).
+ When the process ends because of the limit on max. number of keyparts
+ we'll have:
+
+ WHERE clause length is O(3*#max_keyparts)
+ SEL_ARG graph size is O(2^(#max_keyparts/2))
+
+ (it is also possible to construct a case where instead of 2 in 2^n we
+ have a bigger constant, e.g. 4, and get a graph with 4^(31/2)= 2^31
+ nodes)
+
+ We avoid consuming too much memory by setting a limit on the number of
+ SEL_ARG object we can construct during one range analysis invocation.
+*/
+
+class SEL_ARG :public Sql_alloc
+{
+public:
+ uint8 min_flag,max_flag,maybe_flag;
+ uint8 part; // Which key part
+ uint8 maybe_null;
+ /*
+ The ordinal number the least significant component encountered in
+ the ranges of the SEL_ARG tree (the first component has number 1)
+ */
+ uint16 max_part_no;
+ /*
+ Number of children of this element in the RB-tree, plus 1 for this
+ element itself.
+ */
+ uint16 elements;
+ /*
+ Valid only for elements which are RB-tree roots: Number of times this
+ RB-tree is referred to (it is referred by SEL_ARG::next_key_part or by
+ SEL_TREE::keys[i] or by a temporary SEL_ARG* variable)
+ */
+ ulong use_count;
+
+ Field *field;
+ uchar *min_value,*max_value; // Pointer to range
+
+ /*
+ eq_tree() requires that left == right == 0 if the type is MAYBE_KEY.
+ */
+ SEL_ARG *left,*right; /* R-B tree children */
+ SEL_ARG *next,*prev; /* Links for bi-directional interval list */
+ SEL_ARG *parent; /* R-B tree parent */
+ SEL_ARG *next_key_part;
+ enum leaf_color { BLACK,RED } color;
+ enum Type { IMPOSSIBLE, MAYBE, MAYBE_KEY, KEY_RANGE } type;
+
+ enum { MAX_SEL_ARGS = 16000 };
+
+ SEL_ARG() {}
+ SEL_ARG(SEL_ARG &);
+ SEL_ARG(Field *,const uchar *, const uchar *);
+ SEL_ARG(Field *field, uint8 part, uchar *min_value, uchar *max_value,
+ uint8 min_flag, uint8 max_flag, uint8 maybe_flag);
+ SEL_ARG(enum Type type_arg)
+ :min_flag(0), max_part_no(0) /* first key part means 1. 0 mean 'no parts'*/,
+ elements(1),use_count(1),left(0),right(0),
+ next_key_part(0), color(BLACK), type(type_arg)
+ {}
+ /**
+ returns true if a range predicate is equal. Use all_same()
+ to check for equality of all the predicates on this keypart.
+ */
+ inline bool is_same(const SEL_ARG *arg) const
+ {
+ if (type != arg->type || part != arg->part)
+ return false;
+ if (type != KEY_RANGE)
+ return true;
+ return cmp_min_to_min(arg) == 0 && cmp_max_to_max(arg) == 0;
+ }
+ /**
+ returns true if all the predicates in the keypart tree are equal
+ */
+ bool all_same(const SEL_ARG *arg) const
+ {
+ if (type != arg->type || part != arg->part)
+ return false;
+ if (type != KEY_RANGE)
+ return true;
+ if (arg == this)
+ return true;
+ const SEL_ARG *cmp_arg= arg->first();
+ const SEL_ARG *cur_arg= first();
+ for (; cur_arg && cmp_arg && cur_arg->is_same(cmp_arg);
+ cur_arg= cur_arg->next, cmp_arg= cmp_arg->next) ;
+ if (cur_arg || cmp_arg)
+ return false;
+ return true;
+ }
+ inline void merge_flags(SEL_ARG *arg) { maybe_flag|=arg->maybe_flag; }
+ inline void maybe_smaller() { maybe_flag=1; }
+ /* Return true iff it's a single-point null interval */
+ inline bool is_null_interval() { return maybe_null && max_value[0] == 1; }
+ inline int cmp_min_to_min(const SEL_ARG* arg) const
+ {
+ return sel_cmp(field,min_value, arg->min_value, min_flag, arg->min_flag);
+ }
+ inline int cmp_min_to_max(const SEL_ARG* arg) const
+ {
+ return sel_cmp(field,min_value, arg->max_value, min_flag, arg->max_flag);
+ }
+ inline int cmp_max_to_max(const SEL_ARG* arg) const
+ {
+ return sel_cmp(field,max_value, arg->max_value, max_flag, arg->max_flag);
+ }
+ inline int cmp_max_to_min(const SEL_ARG* arg) const
+ {
+ return sel_cmp(field,max_value, arg->min_value, max_flag, arg->min_flag);
+ }
+ SEL_ARG *clone_and(SEL_ARG* arg)
+ { // Get overlapping range
+ uchar *new_min,*new_max;
+ uint8 flag_min,flag_max;
+ if (cmp_min_to_min(arg) >= 0)
+ {
+ new_min=min_value; flag_min=min_flag;
+ }
+ else
+ {
+ new_min=arg->min_value; flag_min=arg->min_flag; /* purecov: deadcode */
+ }
+ if (cmp_max_to_max(arg) <= 0)
+ {
+ new_max=max_value; flag_max=max_flag;
+ }
+ else
+ {
+ new_max=arg->max_value; flag_max=arg->max_flag;
+ }
+ return new SEL_ARG(field, part, new_min, new_max, flag_min, flag_max,
+ MY_TEST(maybe_flag && arg->maybe_flag));
+ }
+ SEL_ARG *clone_first(SEL_ARG *arg)
+ { // min <= X < arg->min
+ return new SEL_ARG(field,part, min_value, arg->min_value,
+ min_flag, arg->min_flag & NEAR_MIN ? 0 : NEAR_MAX,
+ maybe_flag | arg->maybe_flag);
+ }
+ SEL_ARG *clone_last(SEL_ARG *arg)
+ { // min <= X <= key_max
+ return new SEL_ARG(field, part, min_value, arg->max_value,
+ min_flag, arg->max_flag, maybe_flag | arg->maybe_flag);
+ }
+ SEL_ARG *clone(RANGE_OPT_PARAM *param, SEL_ARG *new_parent, SEL_ARG **next);
+
+ bool copy_min(SEL_ARG* arg)
+ { // Get overlapping range
+ if (cmp_min_to_min(arg) > 0)
+ {
+ min_value=arg->min_value; min_flag=arg->min_flag;
+ if ((max_flag & (NO_MAX_RANGE | NO_MIN_RANGE)) ==
+ (NO_MAX_RANGE | NO_MIN_RANGE))
+ return 1; // Full range
+ }
+ maybe_flag|=arg->maybe_flag;
+ return 0;
+ }
+ bool copy_max(SEL_ARG* arg)
+ { // Get overlapping range
+ if (cmp_max_to_max(arg) <= 0)
+ {
+ max_value=arg->max_value; max_flag=arg->max_flag;
+ if ((max_flag & (NO_MAX_RANGE | NO_MIN_RANGE)) ==
+ (NO_MAX_RANGE | NO_MIN_RANGE))
+ return 1; // Full range
+ }
+ maybe_flag|=arg->maybe_flag;
+ return 0;
+ }
+
+ void copy_min_to_min(SEL_ARG *arg)
+ {
+ min_value=arg->min_value; min_flag=arg->min_flag;
+ }
+ void copy_min_to_max(SEL_ARG *arg)
+ {
+ max_value=arg->min_value;
+ max_flag=arg->min_flag & NEAR_MIN ? 0 : NEAR_MAX;
+ }
+ void copy_max_to_min(SEL_ARG *arg)
+ {
+ min_value=arg->max_value;
+ min_flag=arg->max_flag & NEAR_MAX ? 0 : NEAR_MIN;
+ }
+ /* returns a number of keypart values (0 or 1) appended to the key buffer */
+ int store_min(uint length, uchar **min_key,uint min_key_flag)
+ {
+ /* "(kp1 > c1) AND (kp2 OP c2) AND ..." -> (kp1 > c1) */
+ if ((min_flag & GEOM_FLAG) ||
+ (!(min_flag & NO_MIN_RANGE) &&
+ !(min_key_flag & (NO_MIN_RANGE | NEAR_MIN))))
+ {
+ if (maybe_null && *min_value)
+ {
+ **min_key=1;
+ bzero(*min_key+1,length-1);
+ }
+ else
+ memcpy(*min_key,min_value,length);
+ (*min_key)+= length;
+ return 1;
+ }
+ return 0;
+ }
+ /* returns a number of keypart values (0 or 1) appended to the key buffer */
+ int store_max(uint length, uchar **max_key, uint max_key_flag)
+ {
+ if (!(max_flag & NO_MAX_RANGE) &&
+ !(max_key_flag & (NO_MAX_RANGE | NEAR_MAX)))
+ {
+ if (maybe_null && *max_value)
+ {
+ **max_key=1;
+ bzero(*max_key+1,length-1);
+ }
+ else
+ memcpy(*max_key,max_value,length);
+ (*max_key)+= length;
+ return 1;
+ }
+ return 0;
+ }
+
+ /*
+ Returns a number of keypart values appended to the key buffer
+ for min key and max key. This function is used by both Range
+ Analysis and Partition pruning. For partition pruning we have
+ to ensure that we don't store also subpartition fields. Thus
+ we have to stop at the last partition part and not step into
+ the subpartition fields. For Range Analysis we set last_part
+ to MAX_KEY which we should never reach.
+ */
+ int store_min_key(KEY_PART *key,
+ uchar **range_key,
+ uint *range_key_flag,
+ uint last_part)
+ {
+ SEL_ARG *key_tree= first();
+ uint res= key_tree->store_min(key[key_tree->part].store_length,
+ range_key, *range_key_flag);
+ *range_key_flag|= key_tree->min_flag;
+ if (key_tree->next_key_part &&
+ key_tree->next_key_part->type == SEL_ARG::KEY_RANGE &&
+ key_tree->part != last_part &&
+ key_tree->next_key_part->part == key_tree->part+1 &&
+ !(*range_key_flag & (NO_MIN_RANGE | NEAR_MIN)))
+ res+= key_tree->next_key_part->store_min_key(key,
+ range_key,
+ range_key_flag,
+ last_part);
+ return res;
+ }
+
+ /* returns a number of keypart values appended to the key buffer */
+ int store_max_key(KEY_PART *key,
+ uchar **range_key,
+ uint *range_key_flag,
+ uint last_part)
+ {
+ SEL_ARG *key_tree= last();
+ uint res=key_tree->store_max(key[key_tree->part].store_length,
+ range_key, *range_key_flag);
+ (*range_key_flag)|= key_tree->max_flag;
+ if (key_tree->next_key_part &&
+ key_tree->next_key_part->type == SEL_ARG::KEY_RANGE &&
+ key_tree->part != last_part &&
+ key_tree->next_key_part->part == key_tree->part+1 &&
+ !(*range_key_flag & (NO_MAX_RANGE | NEAR_MAX)))
+ res+= key_tree->next_key_part->store_max_key(key,
+ range_key,
+ range_key_flag,
+ last_part);
+ return res;
+ }
+
+ SEL_ARG *insert(SEL_ARG *key);
+ SEL_ARG *tree_delete(SEL_ARG *key);
+ SEL_ARG *find_range(SEL_ARG *key);
+ SEL_ARG *rb_insert(SEL_ARG *leaf);
+ friend SEL_ARG *rb_delete_fixup(SEL_ARG *root,SEL_ARG *key, SEL_ARG *par);
+#ifdef EXTRA_DEBUG
+ friend int test_rb_tree(SEL_ARG *element,SEL_ARG *parent);
+ void test_use_count(SEL_ARG *root);
+#endif
+ SEL_ARG *first();
+ const SEL_ARG *first() const;
+ SEL_ARG *last();
+ void make_root();
+ inline bool simple_key()
+ {
+ return !next_key_part && elements == 1;
+ }
+ void increment_use_count(long count)
+ {
+ if (next_key_part)
+ {
+ next_key_part->use_count+=count;
+ count*= (next_key_part->use_count-count);
+ for (SEL_ARG *pos=next_key_part->first(); pos ; pos=pos->next)
+ if (pos->next_key_part)
+ pos->increment_use_count(count);
+ }
+ }
+ void incr_refs()
+ {
+ increment_use_count(1);
+ use_count++;
+ }
+ void incr_refs_all()
+ {
+ for (SEL_ARG *pos=first(); pos ; pos=pos->next)
+ {
+ pos->increment_use_count(1);
+ }
+ use_count++;
+ }
+ void free_tree()
+ {
+ for (SEL_ARG *pos=first(); pos ; pos=pos->next)
+ if (pos->next_key_part)
+ {
+ pos->next_key_part->use_count--;
+ pos->next_key_part->free_tree();
+ }
+ }
+
+ inline SEL_ARG **parent_ptr()
+ {
+ return parent->left == this ? &parent->left : &parent->right;
+ }
+
+
+ /*
+ Check if this SEL_ARG object represents a single-point interval
+
+ SYNOPSIS
+ is_singlepoint()
+
+ DESCRIPTION
+ Check if this SEL_ARG object (not tree) represents a single-point
+ interval, i.e. if it represents a "keypart = const" or
+ "keypart IS NULL".
+
+ RETURN
+ TRUE This SEL_ARG object represents a singlepoint interval
+ FALSE Otherwise
+ */
+
+ bool is_singlepoint()
+ {
+ /*
+ Check for NEAR_MIN ("strictly less") and NO_MIN_RANGE (-inf < field)
+ flags, and the same for right edge.
+ */
+ if (min_flag || max_flag)
+ return FALSE;
+ uchar *min_val= min_value;
+ uchar *max_val= max_value;
+
+ if (maybe_null)
+ {
+ /* First byte is a NULL value indicator */
+ if (*min_val != *max_val)
+ return FALSE;
+
+ if (*min_val)
+ return TRUE; /* This "x IS NULL" */
+ min_val++;
+ max_val++;
+ }
+ return !field->key_cmp(min_val, max_val);
+ }
+ SEL_ARG *clone_tree(RANGE_OPT_PARAM *param);
+};
+
+/**
+ Helper function to compare two SEL_ARG's.
+*/
+static bool all_same(const SEL_ARG *sa1, const SEL_ARG *sa2)
+{
+ if (sa1 == NULL && sa2 == NULL)
+ return true;
+ if ((sa1 != NULL && sa2 == NULL) || (sa1 == NULL && sa2 != NULL))
+ return false;
+ return sa1->all_same(sa2);
+}
+
+class SEL_IMERGE;
+
+#define CLONE_KEY1_MAYBE 1
+#define CLONE_KEY2_MAYBE 2
+#define swap_clone_flag(A) ((A & 1) << 1) | ((A & 2) >> 1)
+
+
+/*
+ While objects of the class SEL_ARG represent ranges for indexes or
+ index infixes (including ranges for index prefixes and index suffixes),
+ objects of the class SEL_TREE represent AND/OR formulas of such ranges.
+ Currently an AND/OR formula represented by a SEL_TREE object can have
+ at most three levels:
+
+ <SEL_TREE formula> ::=
+ [ <SEL_RANGE_TREE formula> AND ]
+ [ <SEL_IMERGE formula> [ AND <SEL_IMERGE formula> ...] ]
+
+ <SEL_RANGE_TREE formula> ::=
+ <SEL_ARG formula> [ AND <SEL_ARG_formula> ... ]
+
+ <SEL_IMERGE formula> ::=
+ <SEL_RANGE_TREE formula> [ OR <SEL_RANGE_TREE formula> ]
+
+ As we can see from the above definitions:
+ - SEL_RANGE_TREE formula is a conjunction of SEL_ARG formulas
+ - SEL_IMERGE formula is a disjunction of SEL_RANGE_TREE formulas
+ - SEL_TREE formula is a conjunction of a SEL_RANGE_TREE formula
+ and SEL_IMERGE formulas.
+ It's required above that a SEL_TREE formula has at least one conjunct.
+
+ Usually we will consider normalized SEL_RANGE_TREE formulas where we use
+ TRUE as conjunct members for those indexes whose SEL_ARG trees are empty.
+
+ We will call an SEL_TREE object simply 'tree'.
+ The part of a tree that represents SEL_RANGE_TREE formula is called
+ 'range part' of the tree while the remaining part is called 'imerge part'.
+ If a tree contains only a range part then we call such a tree 'range tree'.
+ Components of a range tree that represent SEL_ARG formulas are called ranges.
+ If a tree does not contain any range part we call such a tree 'imerge tree'.
+ Components of the imerge part of a tree that represent SEL_IMERGE formula
+ are called imerges.
+
+ Usually we'll designate:
+ SEL_TREE formulas by T_1,...,T_k
+ SEL_ARG formulas by R_1,...,R_k
+ SEL_RANGE_TREE formulas by RT_1,...,RT_k
+ SEL_IMERGE formulas by M_1,...,M_k
+ Accordingly we'll use:
+ t_1,...,t_k - to designate trees representing T_1,...,T_k
+ r_1,...,r_k - to designate ranges representing R_1,...,R_k
+ rt_1,...,r_tk - to designate range trees representing RT_1,...,RT_k
+ m_1,...,m_k - to designate imerges representing M_1,...,M_k
+
+ SEL_TREE objects are usually built from WHERE conditions or
+ ON expressions.
+ A SEL_TREE object always represents an inference of the condition it is
+ built from. Therefore, if a row satisfies a SEL_TREE formula it also
+ satisfies the condition it is built from.
+
+ The following transformations of tree t representing SEL_TREE formula T
+ yield a new tree t1 thar represents an inference of T: T=>T1.
+ (1) remove any of SEL_ARG tree from the range part of t
+ (2) remove any imerge from the tree t
+ (3) remove any of SEL_ARG tree from any range tree contained
+ in any imerge of tree
+
+ Since the basic blocks of any SEL_TREE objects are ranges, SEL_TREE
+ objects in many cases can be effectively used to filter out a big part
+ of table rows that do not satisfy WHERE/IN conditions utilizing
+ only single or multiple range index scans.
+
+ A single range index scan is constructed for a range tree that contains
+ only one SEL_ARG object for an index or an index prefix.
+ An index intersection scan can be constructed for a range tree
+ that contains several SEL_ARG objects. Currently index intersection
+ scans are constructed only for single-point ranges.
+ An index merge scan is constructed for a imerge tree that contains only
+ one imerge. If range trees of this imerge contain only single-point merges
+ than a union of index intersections can be built.
+
+ Usually the tree built by the range optimizer for a query table contains
+ more than one range in the range part, and additionally may contain some
+ imerges in the imerge part. The range optimizer evaluates all of them one
+ by one and chooses the range or the imerge that provides the cheapest
+ single or multiple range index scan of the table. According to rules
+ (1)-(3) this scan always filter out only those rows that do not satisfy
+ the query conditions.
+
+ For any condition the SEL_TREE object for it is built in a bottom up
+ manner starting from the range trees for the predicates. The tree_and
+ function builds a tree for any conjunction of formulas from the trees
+ for its conjuncts. The tree_or function builds a tree for any disjunction
+ of formulas from the trees for its disjuncts.
+*/
+
+class SEL_TREE :public Sql_alloc
+{
+public:
+ /*
+ Starting an effort to document this field:
+ (for some i, keys[i]->type == SEL_ARG::IMPOSSIBLE) =>
+ (type == SEL_TREE::IMPOSSIBLE)
+ */
+ enum Type { IMPOSSIBLE, ALWAYS, MAYBE, KEY, KEY_SMALLER } type;
+ SEL_TREE(enum Type type_arg) :type(type_arg) {}
+ SEL_TREE() :type(KEY)
+ {
+ keys_map.clear_all();
+ bzero((char*) keys,sizeof(keys));
+ }
+ SEL_TREE(SEL_TREE *arg, bool without_merges, RANGE_OPT_PARAM *param);
+ /*
+ Note: there may exist SEL_TREE objects with sel_tree->type=KEY and
+ keys[i]=0 for all i. (SergeyP: it is not clear whether there is any
+ merit in range analyzer functions (e.g. get_mm_parts) returning a
+ pointer to such SEL_TREE instead of NULL)
+ */
+ SEL_ARG *keys[MAX_KEY];
+ key_map keys_map; /* bitmask of non-NULL elements in keys */
+
+ /*
+ Possible ways to read rows using index_merge. The list is non-empty only
+ if type==KEY. Currently can be non empty only if keys_map.is_clear_all().
+ */
+ List<SEL_IMERGE> merges;
+
+ /* The members below are filled/used only after get_mm_tree is done */
+ key_map ror_scans_map; /* bitmask of ROR scan-able elements in keys */
+ uint n_ror_scans; /* number of set bits in ror_scans_map */
+
+ struct st_index_scan_info **index_scans; /* list of index scans */
+ struct st_index_scan_info **index_scans_end; /* last index scan */
+
+ struct st_ror_scan_info **ror_scans; /* list of ROR key scans */
+ struct st_ror_scan_info **ror_scans_end; /* last ROR scan */
+ /* Note that #records for each key scan is stored in table->quick_rows */
+
+ bool without_ranges() { return keys_map.is_clear_all(); }
+ bool without_imerges() { return merges.is_empty(); }
+};
+
+class RANGE_OPT_PARAM
+{
+public:
+ THD *thd; /* Current thread handle */
+ TABLE *table; /* Table being analyzed */
+ COND *cond; /* Used inside get_mm_tree(). */
+ table_map prev_tables;
+ table_map read_tables;
+ table_map current_table; /* Bit of the table being analyzed */
+
+ /* Array of parts of all keys for which range analysis is performed */
+ KEY_PART *key_parts;
+ KEY_PART *key_parts_end;
+ MEM_ROOT *mem_root; /* Memory that will be freed when range analysis completes */
+ MEM_ROOT *old_root; /* Memory that will last until the query end */
+ /*
+ Number of indexes used in range analysis (In SEL_TREE::keys only first
+ #keys elements are not empty)
+ */
+ uint keys;
+
+ /*
+ If true, the index descriptions describe real indexes (and it is ok to
+ call field->optimize_range(real_keynr[...], ...).
+ Otherwise index description describes fake indexes.
+ */
+ bool using_real_indexes;
+
+ /*
+ Aggressively remove "scans" that do not have conditions on first
+ keyparts. Such scans are usable when doing partition pruning but not
+ regular range optimization.
+ */
+ bool remove_jump_scans;
+
+ /*
+ used_key_no -> table_key_no translation table. Only makes sense if
+ using_real_indexes==TRUE
+ */
+ uint real_keynr[MAX_KEY];
+
+ /*
+ Used to store 'current key tuples', in both range analysis and
+ partitioning (list) analysis
+ */
+ uchar min_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH],
+ max_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH];
+
+ /* Number of SEL_ARG objects allocated by SEL_ARG::clone_tree operations */
+ uint alloced_sel_args;
+
+ bool force_default_mrr;
+ KEY_PART *key[MAX_KEY]; /* First key parts of keys used in the query */
+
+ bool statement_should_be_aborted() const
+ {
+ return
+ thd->is_fatal_error ||
+ thd->is_error() ||
+ alloced_sel_args > SEL_ARG::MAX_SEL_ARGS;
+ }
+};
+
+class PARAM : public RANGE_OPT_PARAM
+{
+public:
+ ha_rows quick_rows[MAX_KEY];
+
+ /*
+ This will collect 'possible keys' based on the range optimization.
+
+ Queries with a JOIN object actually use ref optimizer (see add_key_field)
+ to collect possible_keys. This is used by single table UPDATE/DELETE.
+ */
+ key_map possible_keys;
+ longlong baseflag;
+ uint max_key_part, range_count;
+
+ bool quick; // Don't calulate possible keys
+
+ uint fields_bitmap_size;
+ MY_BITMAP needed_fields; /* bitmask of fields needed by the query */
+ MY_BITMAP tmp_covered_fields;
+
+ key_map *needed_reg; /* ptr to SQL_SELECT::needed_reg */
+
+ uint *imerge_cost_buff; /* buffer for index_merge cost estimates */
+ uint imerge_cost_buff_size; /* size of the buffer */
+
+ /* TRUE if last checked tree->key can be used for ROR-scan */
+ bool is_ror_scan;
+ /* Number of ranges in the last checked tree->key */
+ uint n_ranges;
+ uint8 first_null_comp; /* first null component if any, 0 - otherwise */
+};
+
+
+class TABLE_READ_PLAN;
+ class TRP_RANGE;
+ class TRP_ROR_INTERSECT;
+ class TRP_ROR_UNION;
+ class TRP_INDEX_INTERSECT;
+ class TRP_INDEX_MERGE;
+ class TRP_GROUP_MIN_MAX;
+
+struct st_index_scan_info;
+struct st_ror_scan_info;
+
+static SEL_TREE * get_mm_parts(RANGE_OPT_PARAM *param,COND *cond_func,Field *field,
+ Item_func::Functype type,Item *value,
+ Item_result cmp_type);
+static SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param,COND *cond_func,Field *field,
+ KEY_PART *key_part,
+ Item_func::Functype type,Item *value);
+static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond);
+
+static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts);
+static ha_rows check_quick_select(PARAM *param, uint idx, bool index_only,
+ SEL_ARG *tree, bool update_tbl_stats,
+ uint *mrr_flags, uint *bufsize,
+ Cost_estimate *cost);
+
+QUICK_RANGE_SELECT *get_quick_select(PARAM *param,uint index,
+ SEL_ARG *key_tree, uint mrr_flags,
+ uint mrr_buf_size, MEM_ROOT *alloc);
+static TRP_RANGE *get_key_scans_params(PARAM *param, SEL_TREE *tree,
+ bool index_read_must_be_used,
+ bool update_tbl_stats,
+ double read_time);
+static
+TRP_INDEX_INTERSECT *get_best_index_intersect(PARAM *param, SEL_TREE *tree,
+ double read_time);
+static
+TRP_ROR_INTERSECT *get_best_ror_intersect(const PARAM *param, SEL_TREE *tree,
+ double read_time,
+ bool *are_all_covering);
+static
+TRP_ROR_INTERSECT *get_best_covering_ror_intersect(PARAM *param,
+ SEL_TREE *tree,
+ double read_time);
+static
+TABLE_READ_PLAN *get_best_disjunct_quick(PARAM *param, SEL_IMERGE *imerge,
+ double read_time);
+static
+TABLE_READ_PLAN *merge_same_index_scans(PARAM *param, SEL_IMERGE *imerge,
+ TRP_INDEX_MERGE *imerge_trp,
+ double read_time);
+static
+TRP_GROUP_MIN_MAX *get_best_group_min_max(PARAM *param, SEL_TREE *tree,
+ double read_time);
+
+#ifndef DBUG_OFF
+static void print_sel_tree(PARAM *param, SEL_TREE *tree, key_map *tree_map,
+ const char *msg);
+static void print_ror_scans_arr(TABLE *table, const char *msg,
+ struct st_ror_scan_info **start,
+ struct st_ror_scan_info **end);
+static void print_quick(QUICK_SELECT_I *quick, const key_map *needed_reg);
+#endif
+
+static SEL_TREE *tree_and(RANGE_OPT_PARAM *param,
+ SEL_TREE *tree1, SEL_TREE *tree2);
+static SEL_TREE *tree_or(RANGE_OPT_PARAM *param,
+ SEL_TREE *tree1,SEL_TREE *tree2);
+static SEL_ARG *sel_add(SEL_ARG *key1,SEL_ARG *key2);
+static SEL_ARG *key_or(RANGE_OPT_PARAM *param,
+ SEL_ARG *key1, SEL_ARG *key2);
+static SEL_ARG *key_and(RANGE_OPT_PARAM *param,
+ SEL_ARG *key1, SEL_ARG *key2,
+ uint clone_flag);
+static bool get_range(SEL_ARG **e1,SEL_ARG **e2,SEL_ARG *root1);
+bool get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key,
+ SEL_ARG *key_tree, uchar *min_key,uint min_key_flag,
+ uchar *max_key,uint max_key_flag);
+static bool eq_tree(SEL_ARG* a,SEL_ARG *b);
+
+static SEL_ARG null_element(SEL_ARG::IMPOSSIBLE);
+static bool null_part_in_key(KEY_PART *key_part, const uchar *key,
+ uint length);
+static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts);
+
+#include "opt_range_mrr.cc"
+
+static bool sel_trees_have_common_keys(SEL_TREE *tree1, SEL_TREE *tree2,
+ key_map *common_keys);
+static void eliminate_single_tree_imerges(RANGE_OPT_PARAM *param,
+ SEL_TREE *tree);
+
+static bool sel_trees_can_be_ored(RANGE_OPT_PARAM* param,
+ SEL_TREE *tree1, SEL_TREE *tree2,
+ key_map *common_keys);
+static bool sel_trees_must_be_ored(RANGE_OPT_PARAM* param,
+ SEL_TREE *tree1, SEL_TREE *tree2,
+ key_map common_keys);
+static int and_range_trees(RANGE_OPT_PARAM *param,
+ SEL_TREE *tree1, SEL_TREE *tree2,
+ SEL_TREE *result);
+static bool remove_nonrange_trees(RANGE_OPT_PARAM *param, SEL_TREE *tree);
+
+
+/*
+ SEL_IMERGE is a list of possible ways to do index merge, i.e. it is
+ a condition in the following form:
+ (t_1||t_2||...||t_N) && (next)
+
+ where all t_i are SEL_TREEs, next is another SEL_IMERGE and no pair
+ (t_i,t_j) contains SEL_ARGS for the same index.
+
+ SEL_TREE contained in SEL_IMERGE always has merges=NULL.
+
+ This class relies on memory manager to do the cleanup.
+*/
+
+class SEL_IMERGE : public Sql_alloc
+{
+ enum { PREALLOCED_TREES= 10};
+public:
+ SEL_TREE *trees_prealloced[PREALLOCED_TREES];
+ SEL_TREE **trees; /* trees used to do index_merge */
+ SEL_TREE **trees_next; /* last of these trees */
+ SEL_TREE **trees_end; /* end of allocated space */
+
+ SEL_ARG ***best_keys; /* best keys to read in SEL_TREEs */
+
+ SEL_IMERGE() :
+ trees(&trees_prealloced[0]),
+ trees_next(trees),
+ trees_end(trees + PREALLOCED_TREES)
+ {}
+ SEL_IMERGE (SEL_IMERGE *arg, uint cnt, RANGE_OPT_PARAM *param);
+ int or_sel_tree(RANGE_OPT_PARAM *param, SEL_TREE *tree);
+ bool have_common_keys(RANGE_OPT_PARAM *param, SEL_TREE *tree);
+ int and_sel_tree(RANGE_OPT_PARAM *param, SEL_TREE *tree,
+ SEL_IMERGE *new_imerge);
+ int or_sel_tree_with_checks(RANGE_OPT_PARAM *param,
+ uint n_init_trees,
+ SEL_TREE *new_tree,
+ bool is_first_check_pass,
+ bool *is_last_check_pass);
+ int or_sel_imerge_with_checks(RANGE_OPT_PARAM *param,
+ uint n_init_trees,
+ SEL_IMERGE* imerge,
+ bool is_first_check_pass,
+ bool *is_last_check_pass);
+};
+
+
+/*
+ Add a range tree to the range trees of this imerge
+
+ SYNOPSIS
+ or_sel_tree()
+ param Context info for the operation
+ tree SEL_TREE to add to this imerge
+
+ DESCRIPTION
+ The function just adds the range tree 'tree' to the range trees
+ of this imerge.
+
+ RETURN
+ 0 if the operation is success
+ -1 if the function runs out memory
+*/
+
+int SEL_IMERGE::or_sel_tree(RANGE_OPT_PARAM *param, SEL_TREE *tree)
+{
+ if (trees_next == trees_end)
+ {
+ const int realloc_ratio= 2; /* Double size for next round */
+ uint old_elements= (trees_end - trees);
+ uint old_size= sizeof(SEL_TREE**) * old_elements;
+ uint new_size= old_size * realloc_ratio;
+ SEL_TREE **new_trees;
+ if (!(new_trees= (SEL_TREE**)alloc_root(param->mem_root, new_size)))
+ return -1;
+ memcpy(new_trees, trees, old_size);
+ trees= new_trees;
+ trees_next= trees + old_elements;
+ trees_end= trees + old_elements * realloc_ratio;
+ }
+ *(trees_next++)= tree;
+ return 0;
+}
+
+
+/*
+ Check if any of the range trees of this imerge intersects with a given tree
+
+ SYNOPSIS
+ have_common_keys()
+ param Context info for the function
+ tree SEL_TREE intersection with the imerge range trees is checked for
+
+ DESCRIPTION
+ The function checks whether there is any range tree rt_i in this imerge
+ such that there are some indexes for which ranges are defined in both
+ rt_i and the range part of the SEL_TREE tree.
+ To check this the function calls the function sel_trees_have_common_keys.
+
+ RETURN
+ TRUE if there are such range trees in this imerge
+ FALSE otherwise
+*/
+
+bool SEL_IMERGE::have_common_keys(RANGE_OPT_PARAM *param, SEL_TREE *tree)
+{
+ for (SEL_TREE** or_tree= trees, **bound= trees_next;
+ or_tree != bound; or_tree++)
+ {
+ key_map common_keys;
+ if (sel_trees_have_common_keys(*or_tree, tree, &common_keys))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Perform AND operation for this imerge and the range part of a tree
+
+ SYNOPSIS
+ and_sel_tree()
+ param Context info for the operation
+ tree SEL_TREE for the second operand of the operation
+ new_imerge OUT imerge for the result of the operation
+
+ DESCRIPTION
+ This function performs AND operation for this imerge m and the
+ range part of the SEL_TREE tree rt. In other words the function
+ pushes rt into this imerge. The resulting imerge is returned in
+ the parameter new_imerge.
+ If this imerge m represent the formula
+ RT_1 OR ... OR RT_k
+ then the resulting imerge of the function represents the formula
+ (RT_1 AND RT) OR ... OR (RT_k AND RT)
+ The function calls the function and_range_trees to construct the
+ range tree representing (RT_i AND RT).
+
+ NOTE
+ The function may return an empty imerge without any range trees.
+ This happens when each call of and_range_trees returns an
+ impossible range tree (SEL_TREE::IMPOSSIBLE).
+ Example: (key1 < 2 AND key2 > 10) AND (key1 > 4 OR key2 < 6).
+
+ RETURN
+ 0 if the operation is a success
+ -1 otherwise: there is not enough memory to perform the operation
+*/
+
+int SEL_IMERGE::and_sel_tree(RANGE_OPT_PARAM *param, SEL_TREE *tree,
+ SEL_IMERGE *new_imerge)
+{
+ for (SEL_TREE** or_tree= trees; or_tree != trees_next; or_tree++)
+ {
+ SEL_TREE *res_or_tree= 0;
+ SEL_TREE *and_tree= 0;
+ if (!(res_or_tree= new SEL_TREE()) ||
+ !(and_tree= new SEL_TREE(tree, TRUE, param)))
+ return (-1);
+ if (!and_range_trees(param, *or_tree, and_tree, res_or_tree))
+ {
+ if (new_imerge->or_sel_tree(param, res_or_tree))
+ return (-1);
+ }
+ }
+ return 0;
+}
+
+
+/*
+ Perform OR operation on this imerge and the range part of a tree
+
+ SYNOPSIS
+ or_sel_tree_with_checks()
+ param Context info for the operation
+ n_trees Number of trees in this imerge to check for oring
+ tree SEL_TREE whose range part is to be ored
+ is_first_check_pass <=> the first call of the function for this imerge
+ is_last_check_pass OUT <=> no more calls of the function for this imerge
+
+ DESCRIPTION
+ The function performs OR operation on this imerge m and the range part
+ of the SEL_TREE tree rt. It always replaces this imerge with the result
+ of the operation.
+
+ The operation can be performed in two different modes: with
+ is_first_check_pass==TRUE and is_first_check_pass==FALSE, transforming
+ this imerge differently.
+
+ Given this imerge represents the formula
+ RT_1 OR ... OR RT_k:
+
+ 1. In the first mode, when is_first_check_pass==TRUE :
+ 1.1. If rt must be ored(see the function sel_trees_must_be_ored) with
+ some rt_j (there may be only one such range tree in the imerge)
+ then the function produces an imerge representing the formula
+ RT_1 OR ... OR (RT_j OR RT) OR ... OR RT_k,
+ where the tree for (RT_j OR RT) is built by oring the pairs
+ of SEL_ARG trees for the corresponding indexes
+ 1.2. Otherwise the function produces the imerge representing the formula:
+ RT_1 OR ... OR RT_k OR RT.
+
+ 2. In the second mode, when is_first_check_pass==FALSE :
+ 2.1. For each rt_j in the imerge that can be ored (see the function
+ sel_trees_can_be_ored) with rt the function replaces rt_j for a
+ range tree such that for each index for which ranges are defined
+ in both in rt_j and rt the tree contains the result of oring of
+ these ranges.
+ 2.2. In other cases the function does not produce any imerge.
+
+ When is_first_check==TRUE the function returns FALSE in the parameter
+ is_last_check_pass if there is no rt_j such that rt_j can be ored with rt,
+ but, at the same time, it's not true that rt_j must be ored with rt.
+ When is_first_check==FALSE the function always returns FALSE in the
+ parameter is_last_check_pass.
+
+ RETURN
+ 1 The result of oring of rt_j and rt that must be ored returns the
+ the range tree with type==SEL_TREE::ALWAYS
+ (in this case the imerge m should be discarded)
+ -1 The function runs out of memory
+ 0 in all other cases
+*/
+
+int SEL_IMERGE::or_sel_tree_with_checks(RANGE_OPT_PARAM *param,
+ uint n_trees,
+ SEL_TREE *tree,
+ bool is_first_check_pass,
+ bool *is_last_check_pass)
+{
+ bool was_ored= FALSE;
+ *is_last_check_pass= is_first_check_pass;
+ SEL_TREE** or_tree = trees;
+ for (uint i= 0; i < n_trees; i++, or_tree++)
+ {
+ SEL_TREE *result= 0;
+ key_map result_keys;
+ key_map ored_keys;
+ if (sel_trees_can_be_ored(param, *or_tree, tree, &ored_keys))
+ {
+ bool must_be_ored= sel_trees_must_be_ored(param, *or_tree, tree,
+ ored_keys);
+ if (must_be_ored || !is_first_check_pass)
+ {
+ result_keys.clear_all();
+ result= *or_tree;
+ for (uint key_no= 0; key_no < param->keys; key_no++)
+ {
+ if (!ored_keys.is_set(key_no))
+ {
+ result->keys[key_no]= 0;
+ continue;
+ }
+ SEL_ARG *key1= (*or_tree)->keys[key_no];
+ SEL_ARG *key2= tree->keys[key_no];
+ key2->incr_refs();
+ if ((result->keys[key_no]= key_or(param, key1, key2)))
+ {
+
+ result_keys.set_bit(key_no);
+#ifdef EXTRA_DEBUG
+ if (param->alloced_sel_args < SEL_ARG::MAX_SEL_ARGS)
+ {
+ key1= result->keys[key_no];
+ (key1)->test_use_count(key1);
+ }
+#endif
+ }
+ }
+ }
+ else if(is_first_check_pass)
+ *is_last_check_pass= FALSE;
+ }
+
+ if (result)
+ {
+ result->keys_map= result_keys;
+ if (result_keys.is_clear_all())
+ result->type= SEL_TREE::ALWAYS;
+ if ((result->type == SEL_TREE::MAYBE) ||
+ (result->type == SEL_TREE::ALWAYS))
+ return 1;
+ /* SEL_TREE::IMPOSSIBLE is impossible here */
+ *or_tree= result;
+ was_ored= TRUE;
+ }
+ }
+ if (was_ored)
+ return 0;
+
+ if (is_first_check_pass && !*is_last_check_pass &&
+ !(tree= new SEL_TREE(tree, FALSE, param)))
+ return (-1);
+ return or_sel_tree(param, tree);
+}
+
+
+/*
+ Perform OR operation on this imerge and and another imerge
+
+ SYNOPSIS
+ or_sel_imerge_with_checks()
+ param Context info for the operation
+ n_trees Number of trees in this imerge to check for oring
+ imerge The second operand of the operation
+ is_first_check_pass <=> the first call of the function for this imerge
+ is_last_check_pass OUT <=> no more calls of the function for this imerge
+
+ DESCRIPTION
+ For each range tree rt from 'imerge' the function calls the method
+ SEL_IMERGE::or_sel_tree_with_checks that performs OR operation on this
+ SEL_IMERGE object m and the tree rt. The mode of the operation is
+ specified by the parameter is_first_check_pass. Each call of
+ SEL_IMERGE::or_sel_tree_with_checks transforms this SEL_IMERGE object m.
+ The function returns FALSE in the prameter is_last_check_pass if
+ at least one of the calls of SEL_IMERGE::or_sel_tree_with_checks
+ returns FALSE as the value of its last parameter.
+
+ RETURN
+ 1 One of the calls of SEL_IMERGE::or_sel_tree_with_checks returns 1.
+ (in this case the imerge m should be discarded)
+ -1 The function runs out of memory
+ 0 in all other cases
+*/
+
+int SEL_IMERGE::or_sel_imerge_with_checks(RANGE_OPT_PARAM *param,
+ uint n_trees,
+ SEL_IMERGE* imerge,
+ bool is_first_check_pass,
+ bool *is_last_check_pass)
+{
+ *is_last_check_pass= TRUE;
+ SEL_TREE** tree= imerge->trees;
+ SEL_TREE** tree_end= imerge->trees_next;
+ for ( ; tree < tree_end; tree++)
+ {
+ uint rc;
+ bool is_last= TRUE;
+ rc= or_sel_tree_with_checks(param, n_trees, *tree,
+ is_first_check_pass, &is_last);
+ if (!is_last)
+ *is_last_check_pass= FALSE;
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+
+/*
+ Copy constructor for SEL_TREE objects
+
+ SYNOPSIS
+ SEL_TREE
+ arg The source tree for the constructor
+ without_merges <=> only the range part of the tree arg is copied
+ param Context info for the operation
+
+ DESCRIPTION
+ The constructor creates a full copy of the SEL_TREE arg if
+ the prameter without_merges==FALSE. Otherwise a tree is created
+ that contains the copy only of the range part of the tree arg.
+*/
+
+SEL_TREE::SEL_TREE(SEL_TREE *arg, bool without_merges,
+ RANGE_OPT_PARAM *param): Sql_alloc()
+{
+ keys_map= arg->keys_map;
+ type= arg->type;
+ for (uint idx= 0; idx < param->keys; idx++)
+ {
+ if ((keys[idx]= arg->keys[idx]))
+ keys[idx]->incr_refs_all();
+ }
+
+ if (without_merges)
+ return;
+
+ List_iterator<SEL_IMERGE> it(arg->merges);
+ for (SEL_IMERGE *el= it++; el; el= it++)
+ {
+ SEL_IMERGE *merge= new SEL_IMERGE(el, 0, param);
+ if (!merge || merge->trees == merge->trees_next)
+ {
+ merges.empty();
+ return;
+ }
+ merges.push_back (merge);
+ }
+}
+
+
+/*
+ Copy constructor for SEL_IMERGE objects
+
+ SYNOPSIS
+ SEL_IMERGE
+ arg The source imerge for the constructor
+ cnt How many trees from arg are to be copied
+ param Context info for the operation
+
+ DESCRIPTION
+ The cnt==0 then the constructor creates a full copy of the
+ imerge arg. Otherwise only the first cnt trees of the imerge
+ are copied.
+*/
+
+SEL_IMERGE::SEL_IMERGE(SEL_IMERGE *arg, uint cnt,
+ RANGE_OPT_PARAM *param) : Sql_alloc()
+{
+ uint elements= (arg->trees_end - arg->trees);
+ if (elements > PREALLOCED_TREES)
+ {
+ uint size= elements * sizeof (SEL_TREE **);
+ if (!(trees= (SEL_TREE **)alloc_root(param->mem_root, size)))
+ goto mem_err;
+ }
+ else
+ trees= &trees_prealloced[0];
+
+ trees_next= trees + (cnt ? cnt : arg->trees_next-arg->trees);
+ trees_end= trees + elements;
+
+ for (SEL_TREE **tree = trees, **arg_tree= arg->trees; tree < trees_next;
+ tree++, arg_tree++)
+ {
+ if (!(*tree= new SEL_TREE(*arg_tree, TRUE, param)))
+ goto mem_err;
+ }
+
+ return;
+
+mem_err:
+ trees= &trees_prealloced[0];
+ trees_next= trees;
+ trees_end= trees;
+}
+
+
+/*
+ Perform AND operation on two imerge lists
+
+ SYNOPSIS
+ imerge_list_and_list()
+ param Context info for the operation
+ im1 The first imerge list for the operation
+ im2 The second imerge list for the operation
+
+ DESCRIPTION
+ The function just appends the imerge list im2 to the imerge list im1
+
+ RETURN VALUE
+ none
+*/
+
+inline void imerge_list_and_list(List<SEL_IMERGE> *im1, List<SEL_IMERGE> *im2)
+{
+ im1->concat(im2);
+}
+
+
+/*
+ Perform OR operation on two imerge lists
+
+ SYNOPSIS
+ imerge_list_or_list()
+ param Context info for the operation
+ im1 The first imerge list for the operation
+ im2 The second imerge list for the operation
+
+ DESCRIPTION
+ Assuming that the first imerge list represents the formula
+ F1= M1_1 AND ... AND M1_k1
+ while the second imerge list represents the formula
+ F2= M2_1 AND ... AND M2_k2,
+ where M1_i= RT1_i_1 OR ... OR RT1_i_l1i (i in [1..k1])
+ and M2_i = RT2_i_1 OR ... OR RT2_i_l2i (i in [1..k2]),
+ the function builds a list of imerges for some formula that can be
+ inferred from the formula (F1 OR F2).
+
+ More exactly the function builds imerges for the formula (M1_1 OR M2_1).
+ Note that
+ (F1 OR F2) = (M1_1 AND ... AND M1_k1) OR (M2_1 AND ... AND M2_k2) =
+ AND (M1_i OR M2_j) (i in [1..k1], j in [1..k2]) =>
+ M1_1 OR M2_1.
+ So (M1_1 OR M2_1) is indeed an inference formula for (F1 OR F2).
+
+ To build imerges for the formula (M1_1 OR M2_1) the function invokes,
+ possibly twice, the method SEL_IMERGE::or_sel_imerge_with_checks
+ for the imerge m1_1.
+ At its first invocation the method SEL_IMERGE::or_sel_imerge_with_checks
+ performs OR operation on the imerge m1_1 and the range tree rt2_1_1 by
+ calling SEL_IMERGE::or_sel_tree_with_checks with is_first_pass_check==TRUE.
+ The resulting imerge of the operation is ored with the next range tree of
+ the imerge m2_1. This oring continues until the last range tree from
+ m2_1 has been ored.
+ At its second invocation the method SEL_IMERGE::or_sel_imerge_with_checks
+ performs the same sequence of OR operations, but now calling
+ SEL_IMERGE::or_sel_tree_with_checks with is_first_pass_check==FALSE.
+
+ The imerges that the operation produces replace those in the list im1
+
+ RETURN
+ 0 if the operation is a success
+ -1 if the function has run out of memory
+*/
+
+int imerge_list_or_list(RANGE_OPT_PARAM *param,
+ List<SEL_IMERGE> *im1,
+ List<SEL_IMERGE> *im2)
+{
+
+ uint rc;
+ bool is_last_check_pass= FALSE;
+
+ SEL_IMERGE *imerge= im1->head();
+ uint elems= imerge->trees_next-imerge->trees;
+ im1->empty();
+ im1->push_back(imerge);
+
+ rc= imerge->or_sel_imerge_with_checks(param, elems, im2->head(),
+ TRUE, &is_last_check_pass);
+ if (rc)
+ {
+ if (rc == 1)
+ {
+ im1->empty();
+ rc= 0;
+ }
+ return rc;
+ }
+
+ if (!is_last_check_pass)
+ {
+ SEL_IMERGE* new_imerge= new SEL_IMERGE(imerge, elems, param);
+ if (new_imerge)
+ {
+ is_last_check_pass= TRUE;
+ rc= new_imerge->or_sel_imerge_with_checks(param, elems, im2->head(),
+ FALSE, &is_last_check_pass);
+ if (!rc)
+ im1->push_back(new_imerge);
+ }
+ }
+ return rc;
+}
+
+
+/*
+ Perform OR operation for each imerge from a list and the range part of a tree
+
+ SYNOPSIS
+ imerge_list_or_tree()
+ param Context info for the operation
+ merges The list of imerges to be ored with the range part of tree
+ tree SEL_TREE whose range part is to be ored with the imerges
+
+ DESCRIPTION
+ For each imerge mi from the list 'merges' the function performes OR
+ operation with mi and the range part of 'tree' rt, producing one or
+ two imerges.
+
+ Given the merge mi represent the formula RTi_1 OR ... OR RTi_k,
+ the function forms the merges by the following rules:
+
+ 1. If rt cannot be ored with any of the trees rti the function just
+ produces an imerge that represents the formula
+ RTi_1 OR ... RTi_k OR RT.
+ 2. If there exist a tree rtj that must be ored with rt the function
+ produces an imerge the represents the formula
+ RTi_1 OR ... OR (RTi_j OR RT) OR ... OR RTi_k,
+ where the range tree for (RTi_j OR RT) is constructed by oring the
+ SEL_ARG trees that must be ored.
+ 3. For each rti_j that can be ored with rt the function produces
+ the new tree rti_j' and substitutes rti_j for this new range tree.
+
+ In any case the function removes mi from the list and then adds all
+ produced imerges.
+
+ To build imerges by rules 1-3 the function calls the method
+ SEL_IMERGE::or_sel_tree_with_checks, possibly twice. With the first
+ call it passes TRUE for the third parameter of the function.
+ At this first call imerges by rules 1-2 are built. If the call
+ returns FALSE as the return value of its fourth parameter then the
+ function are called for the second time. At this call the imerge
+ of rule 3 is produced.
+
+ If a call of SEL_IMERGE::or_sel_tree_with_checks returns 1 then
+ then it means that the produced tree contains an always true
+ range tree and the whole imerge can be discarded.
+
+ RETURN
+ 1 if no imerges are produced
+ 0 otherwise
+*/
+
+static
+int imerge_list_or_tree(RANGE_OPT_PARAM *param,
+ List<SEL_IMERGE> *merges,
+ SEL_TREE *tree)
+{
+
+ SEL_IMERGE *imerge;
+ List<SEL_IMERGE> additional_merges;
+ List_iterator<SEL_IMERGE> it(*merges);
+
+ while ((imerge= it++))
+ {
+ bool is_last_check_pass;
+ int rc= 0;
+ int rc1= 0;
+ SEL_TREE *or_tree= new SEL_TREE (tree, FALSE, param);
+ if (or_tree)
+ {
+ uint elems= imerge->trees_next-imerge->trees;
+ rc= imerge->or_sel_tree_with_checks(param, elems, or_tree,
+ TRUE, &is_last_check_pass);
+ if (!is_last_check_pass)
+ {
+ SEL_IMERGE *new_imerge= new SEL_IMERGE(imerge, elems, param);
+ if (new_imerge)
+ {
+ rc1= new_imerge->or_sel_tree_with_checks(param, elems, or_tree,
+ FALSE, &is_last_check_pass);
+ if (!rc1)
+ additional_merges.push_back(new_imerge);
+ }
+ }
+ }
+ if (rc || rc1 || !or_tree)
+ it.remove();
+ }
+
+ merges->concat(&additional_merges);
+ return merges->is_empty();
+}
+
+
+/*
+ Perform pushdown operation of the range part of a tree into given imerges
+
+ SYNOPSIS
+ imerge_list_and_tree()
+ param Context info for the operation
+ merges IN/OUT List of imerges to push the range part of 'tree' into
+ tree SEL_TREE whose range part is to be pushed into imerges
+ replace if the pushdow operation for a imerge is a success
+ then the original imerge is replaced for the result
+ of the pushdown
+
+ DESCRIPTION
+ For each imerge from the list merges the function pushes the range part
+ rt of 'tree' into the imerge.
+ More exactly if the imerge mi from the list represents the formula
+ RTi_1 OR ... OR RTi_k
+ the function bulds a new imerge that represents the formula
+ (RTi_1 AND RT) OR ... OR (RTi_k AND RT)
+ and adds this imerge to the list merges.
+ To perform this pushdown operation the function calls the method
+ SEL_IMERGE::and_sel_tree.
+ For any imerge mi the new imerge is not created if for each pair of
+ trees rti_j and rt the intersection of the indexes with defined ranges
+ is empty.
+ If the result of the pushdown operation for the imerge mi returns an
+ imerge with no trees then then not only nothing is added to the list
+ merges but mi itself is removed from the list.
+
+ TODO
+ Optimize the code in order to not create new SEL_IMERGE and new SER_TREE
+ objects when 'replace' is TRUE. (Currently this function is called always
+ with this parameter equal to TRUE.)
+
+ RETURN
+ 1 if no imerges are left in the list merges
+ 0 otherwise
+*/
+
+static
+int imerge_list_and_tree(RANGE_OPT_PARAM *param,
+ List<SEL_IMERGE> *merges,
+ SEL_TREE *tree,
+ bool replace)
+{
+ SEL_IMERGE *imerge;
+ SEL_IMERGE *new_imerge= NULL;
+ List<SEL_IMERGE> new_merges;
+ List_iterator<SEL_IMERGE> it(*merges);
+
+ while ((imerge= it++))
+ {
+ if (!new_imerge)
+ new_imerge= new SEL_IMERGE();
+ if (imerge->have_common_keys(param, tree) &&
+ new_imerge && !imerge->and_sel_tree(param, tree, new_imerge))
+ {
+ if (new_imerge->trees == new_imerge->trees_next)
+ it.remove();
+ else
+ {
+ if (replace)
+ it.replace(new_imerge);
+ else
+ new_merges.push_back(new_imerge);
+ new_imerge= NULL;
+ }
+ }
+ }
+ imerge_list_and_list(&new_merges, merges);
+ *merges= new_merges;
+ return merges->is_empty();
+}
+
+
+/***************************************************************************
+** Basic functions for SQL_SELECT and QUICK_RANGE_SELECT
+***************************************************************************/
+
+ /* make a select from mysql info
+ Error is set as following:
+ 0 = ok
+ 1 = Got some error (out of memory?)
+ */
+
+SQL_SELECT *make_select(TABLE *head, table_map const_tables,
+ table_map read_tables, COND *conds,
+ bool allow_null_cond,
+ int *error)
+{
+ SQL_SELECT *select;
+ DBUG_ENTER("make_select");
+
+ *error=0;
+
+ if (!conds && !allow_null_cond)
+ DBUG_RETURN(0);
+ if (!(select= new SQL_SELECT))
+ {
+ *error= 1; // out of memory
+ DBUG_RETURN(0); /* purecov: inspected */
+ }
+ select->read_tables=read_tables;
+ select->const_tables=const_tables;
+ select->head=head;
+ select->cond= conds;
+
+ if (head->sort.io_cache)
+ {
+ select->file= *head->sort.io_cache;
+ select->records=(ha_rows) (select->file.end_of_file/
+ head->file->ref_length);
+ my_free(head->sort.io_cache);
+ head->sort.io_cache=0;
+ }
+ DBUG_RETURN(select);
+}
+
+
+SQL_SELECT::SQL_SELECT() :quick(0),cond(0),pre_idx_push_select_cond(NULL),free_cond(0)
+{
+ quick_keys.clear_all(); needed_reg.clear_all();
+ my_b_clear(&file);
+}
+
+
+void SQL_SELECT::cleanup()
+{
+ delete quick;
+ quick= 0;
+ if (free_cond)
+ {
+ free_cond=0;
+ delete cond;
+ cond= 0;
+ }
+ close_cached_file(&file);
+}
+
+
+SQL_SELECT::~SQL_SELECT()
+{
+ cleanup();
+}
+
+#undef index // Fix for Unixware 7
+
+QUICK_SELECT_I::QUICK_SELECT_I()
+ :max_used_key_length(0),
+ used_key_parts(0)
+{}
+
+QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(THD *thd, TABLE *table, uint key_nr,
+ bool no_alloc, MEM_ROOT *parent_alloc,
+ bool *create_error)
+ :doing_key_read(0),free_file(0),cur_range(NULL),last_range(0),dont_free(0)
+{
+ my_bitmap_map *bitmap;
+ DBUG_ENTER("QUICK_RANGE_SELECT::QUICK_RANGE_SELECT");
+
+ in_ror_merged_scan= 0;
+ index= key_nr;
+ head= table;
+ key_part_info= head->key_info[index].key_part;
+ my_init_dynamic_array(&ranges, sizeof(QUICK_RANGE*), 16, 16,
+ MYF(MY_THREAD_SPECIFIC));
+
+ /* 'thd' is not accessible in QUICK_RANGE_SELECT::reset(). */
+ mrr_buf_size= thd->variables.mrr_buff_size;
+ mrr_buf_desc= NULL;
+
+ if (!no_alloc && !parent_alloc)
+ {
+ // Allocates everything through the internal memroot
+ init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ thd->mem_root= &alloc;
+ }
+ else
+ bzero((char*) &alloc,sizeof(alloc));
+ file= head->file;
+ record= head->record[0];
+
+ /* Allocate a bitmap for used columns (Q: why not on MEM_ROOT?) */
+ if (!(bitmap= (my_bitmap_map*) my_malloc(head->s->column_bitmap_size,
+ MYF(MY_WME | MY_THREAD_SPECIFIC))))
+ {
+ column_bitmap.bitmap= 0;
+ *create_error= 1;
+ }
+ else
+ my_bitmap_init(&column_bitmap, bitmap, head->s->fields, FALSE);
+ DBUG_VOID_RETURN;
+}
+
+
+void QUICK_RANGE_SELECT::need_sorted_output()
+{
+ if (!(mrr_flags & HA_MRR_SORTED))
+ {
+ /*
+ Native implementation can't produce sorted output. We'll have to
+ switch to default
+ */
+ mrr_flags |= HA_MRR_USE_DEFAULT_IMPL;
+ }
+ mrr_flags |= HA_MRR_SORTED;
+}
+
+
+int QUICK_RANGE_SELECT::init()
+{
+ DBUG_ENTER("QUICK_RANGE_SELECT::init");
+
+ if (file->inited != handler::NONE)
+ file->ha_index_or_rnd_end();
+ DBUG_RETURN(FALSE);
+}
+
+
+void QUICK_RANGE_SELECT::range_end()
+{
+ if (file->inited != handler::NONE)
+ file->ha_index_or_rnd_end();
+}
+
+
+QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT()
+{
+ DBUG_ENTER("QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT");
+ if (!dont_free)
+ {
+ /* file is NULL for CPK scan on covering ROR-intersection */
+ if (file)
+ {
+ range_end();
+ if (doing_key_read)
+ file->extra(HA_EXTRA_NO_KEYREAD);
+ if (free_file)
+ {
+ DBUG_PRINT("info", ("Freeing separate handler 0x%lx (free: %d)", (long) file,
+ free_file));
+ file->ha_external_lock(current_thd, F_UNLCK);
+ file->ha_close();
+ delete file;
+ }
+ }
+ delete_dynamic(&ranges); /* ranges are allocated in alloc */
+ free_root(&alloc,MYF(0));
+ my_free(column_bitmap.bitmap);
+ }
+ my_free(mrr_buf_desc);
+ DBUG_VOID_RETURN;
+}
+
+/*
+ QUICK_INDEX_SORT_SELECT works as follows:
+ - Do index scans, accumulate rowids in the Unique object
+ (Unique will also sort and de-duplicate rowids)
+ - Use rowids from unique to run a disk-ordered sweep
+*/
+
+QUICK_INDEX_SORT_SELECT::QUICK_INDEX_SORT_SELECT(THD *thd_param,
+ TABLE *table)
+ :unique(NULL), pk_quick_select(NULL), thd(thd_param)
+{
+ DBUG_ENTER("QUICK_INDEX_SORT_SELECT::QUICK_INDEX_SORT_SELECT");
+ index= MAX_KEY;
+ head= table;
+ bzero(&read_record, sizeof(read_record));
+ init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ DBUG_VOID_RETURN;
+}
+
+int QUICK_INDEX_SORT_SELECT::init()
+{
+ DBUG_ENTER("QUICK_INDEX_SORT_SELECT::init");
+ DBUG_RETURN(0);
+}
+
+int QUICK_INDEX_SORT_SELECT::reset()
+{
+ DBUG_ENTER("QUICK_INDEX_SORT_SELECT::reset");
+ const int retval= read_keys_and_merge();
+ DBUG_RETURN(retval);
+}
+
+bool
+QUICK_INDEX_SORT_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range)
+{
+ DBUG_ENTER("QUICK_INDEX_SORT_SELECT::push_quick_back");
+ if (head->file->primary_key_is_clustered() &&
+ quick_sel_range->index == head->s->primary_key)
+ {
+ /*
+ A quick_select over a clustered primary key is handled specifically
+ Here we assume:
+ - PK columns are included in any other merged index
+ - Scan on the PK is disk-ordered.
+ (not meeting #2 will only cause performance degradation)
+
+ We could treat clustered PK as any other index, but that would
+ be inefficient. There is no point in doing scan on
+ CPK, remembering the rowid, then making rnd_pos() call with
+ that rowid.
+ */
+ pk_quick_select= quick_sel_range;
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(quick_selects.push_back(quick_sel_range));
+}
+
+QUICK_INDEX_SORT_SELECT::~QUICK_INDEX_SORT_SELECT()
+{
+ List_iterator_fast<QUICK_RANGE_SELECT> quick_it(quick_selects);
+ QUICK_RANGE_SELECT* quick;
+ DBUG_ENTER("QUICK_INDEX_SORT_SELECT::~QUICK_INDEX_SORT_SELECT");
+ delete unique;
+ quick_it.rewind();
+ while ((quick= quick_it++))
+ quick->file= NULL;
+ quick_selects.delete_elements();
+ delete pk_quick_select;
+ /* It's ok to call the next two even if they are already deinitialized */
+ end_read_record(&read_record);
+ free_io_cache(head);
+ free_root(&alloc,MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+QUICK_ROR_INTERSECT_SELECT::QUICK_ROR_INTERSECT_SELECT(THD *thd_param,
+ TABLE *table,
+ bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc)
+ : cpk_quick(NULL), thd(thd_param), need_to_fetch_row(retrieve_full_rows),
+ scans_inited(FALSE)
+{
+ index= MAX_KEY;
+ head= table;
+ record= head->record[0];
+ if (!parent_alloc)
+ init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ else
+ bzero(&alloc, sizeof(MEM_ROOT));
+ last_rowid= (uchar*) alloc_root(parent_alloc? parent_alloc : &alloc,
+ head->file->ref_length);
+}
+
+
+/*
+ Do post-constructor initialization.
+ SYNOPSIS
+ QUICK_ROR_INTERSECT_SELECT::init()
+
+ RETURN
+ 0 OK
+ other Error code
+*/
+
+int QUICK_ROR_INTERSECT_SELECT::init()
+{
+ DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::init");
+ /* Check if last_rowid was successfully allocated in ctor */
+ DBUG_RETURN(!last_rowid);
+}
+
+
+/*
+ Initialize this quick select to be a ROR-merged scan.
+
+ SYNOPSIS
+ QUICK_RANGE_SELECT::init_ror_merged_scan()
+ reuse_handler If TRUE, use head->file, otherwise create a separate
+ handler object
+
+ NOTES
+ This function creates and prepares for subsequent use a separate handler
+ object if it can't reuse head->file. The reason for this is that during
+ ROR-merge several key scans are performed simultaneously, and a single
+ handler is only capable of preserving context of a single key scan.
+
+ In ROR-merge the quick select doing merge does full records retrieval,
+ merged quick selects read only keys.
+
+ RETURN
+ 0 ROR child scan initialized, ok to use.
+ 1 error
+*/
+
+int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler, MEM_ROOT *alloc)
+{
+ handler *save_file= file, *org_file;
+ my_bool org_key_read;
+ THD *thd= head->in_use;
+ MY_BITMAP * const save_read_set= head->read_set;
+ MY_BITMAP * const save_write_set= head->write_set;
+ DBUG_ENTER("QUICK_RANGE_SELECT::init_ror_merged_scan");
+
+ in_ror_merged_scan= 1;
+ if (reuse_handler)
+ {
+ DBUG_PRINT("info", ("Reusing handler 0x%lx", (long) file));
+ if (init())
+ {
+ DBUG_RETURN(1);
+ }
+ head->column_bitmaps_set(&column_bitmap, &column_bitmap);
+ goto end;
+ }
+
+ /* Create a separate handler object for this quick select */
+ if (free_file)
+ {
+ /* already have own 'handler' object. */
+ DBUG_RETURN(0);
+ }
+
+ if (!(file= head->file->clone(head->s->normalized_path.str, alloc)))
+ {
+ /*
+ Manually set the error flag. Note: there seems to be quite a few
+ places where a failure could cause the server to "hang" the client by
+ sending no response to a query. ATM those are not real errors because
+ the storage engine calls in question happen to never fail with the
+ existing storage engines.
+ */
+ my_error(ER_OUT_OF_RESOURCES, MYF(0)); /* purecov: inspected */
+ /* Caller will free the memory */
+ goto failure; /* purecov: inspected */
+ }
+
+ head->column_bitmaps_set(&column_bitmap, &column_bitmap);
+
+ if (file->ha_external_lock(thd, F_RDLCK))
+ goto failure;
+
+ if (init())
+ {
+ file->ha_external_lock(thd, F_UNLCK);
+ file->ha_close();
+ goto failure;
+ }
+ free_file= TRUE;
+ last_rowid= file->ref;
+
+end:
+ DBUG_ASSERT(head->read_set == &column_bitmap);
+ /*
+ We are only going to read key fields and call position() on 'file'
+ The following sets head->tmp_set to only use this key and then updates
+ head->read_set and head->write_set to use this bitmap.
+ The now bitmap is stored in 'column_bitmap' which is used in ::get_next()
+ */
+ org_file= head->file;
+ org_key_read= head->key_read;
+ head->file= file;
+ head->key_read= 0;
+ head->mark_columns_used_by_index_no_reset(index, head->read_set);
+
+ if (!head->no_keyread)
+ {
+ doing_key_read= 1;
+ head->enable_keyread();
+ }
+
+ head->prepare_for_position();
+
+ head->file= org_file;
+ head->key_read= org_key_read;
+
+ /* Restore head->read_set (and write_set) to what they had before the call */
+ head->column_bitmaps_set(save_read_set, save_write_set);
+
+ if (reset())
+ {
+ if (!reuse_handler)
+ {
+ file->ha_external_lock(thd, F_UNLCK);
+ file->ha_close();
+ goto failure;
+ }
+ else
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+
+failure:
+ head->column_bitmaps_set(save_read_set, save_write_set);
+ delete file;
+ file= save_file;
+ DBUG_RETURN(1);
+}
+
+
+/*
+ Initialize this quick select to be a part of a ROR-merged scan.
+ SYNOPSIS
+ QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan()
+ reuse_handler If TRUE, use head->file, otherwise create separate
+ handler object.
+ RETURN
+ 0 OK
+ other error code
+*/
+int QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan(bool reuse_handler,
+ MEM_ROOT *alloc)
+{
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> quick_it(quick_selects);
+ QUICK_SELECT_WITH_RECORD *cur;
+ QUICK_RANGE_SELECT *quick;
+ DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan");
+
+ /* Initialize all merged "children" quick selects */
+ DBUG_ASSERT(!need_to_fetch_row || reuse_handler);
+ if (!need_to_fetch_row && reuse_handler)
+ {
+ cur= quick_it++;
+ quick= cur->quick;
+ /*
+ There is no use of this->file. Use it for the first of merged range
+ selects.
+ */
+ int error= quick->init_ror_merged_scan(TRUE, alloc);
+ if (error)
+ DBUG_RETURN(error);
+ quick->file->extra(HA_EXTRA_KEYREAD_PRESERVE_FIELDS);
+ }
+ while ((cur= quick_it++))
+ {
+ quick= cur->quick;
+#ifndef DBUG_OFF
+ const MY_BITMAP * const save_read_set= quick->head->read_set;
+ const MY_BITMAP * const save_write_set= quick->head->write_set;
+#endif
+ if (quick->init_ror_merged_scan(FALSE, alloc))
+ DBUG_RETURN(1);
+ quick->file->extra(HA_EXTRA_KEYREAD_PRESERVE_FIELDS);
+
+ // Sets are shared by all members of "quick_selects" so must not change
+#ifndef DBUG_OFF
+ DBUG_ASSERT(quick->head->read_set == save_read_set);
+ DBUG_ASSERT(quick->head->write_set == save_write_set);
+#endif
+ /* All merged scans share the same record buffer in intersection. */
+ quick->record= head->record[0];
+ }
+
+ if (need_to_fetch_row && head->file->ha_rnd_init_with_error(false))
+ {
+ DBUG_PRINT("error", ("ROR index_merge rnd_init call failed"));
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Initialize quick select for row retrieval.
+ SYNOPSIS
+ reset()
+ RETURN
+ 0 OK
+ other Error code
+*/
+
+int QUICK_ROR_INTERSECT_SELECT::reset()
+{
+ DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::reset");
+ if (!scans_inited && init_ror_merged_scan(TRUE, &alloc))
+ DBUG_RETURN(1);
+ scans_inited= TRUE;
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
+ QUICK_SELECT_WITH_RECORD *qr;
+ while ((qr= it++))
+ qr->quick->reset();
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Add a merged quick select to this ROR-intersection quick select.
+
+ SYNOPSIS
+ QUICK_ROR_INTERSECT_SELECT::push_quick_back()
+ alloc Mem root to create auxiliary structures on
+ quick Quick select to be added. The quick select must return
+ rows in rowid order.
+ NOTES
+ This call can only be made before init() is called.
+
+ RETURN
+ FALSE OK
+ TRUE Out of memory.
+*/
+
+bool
+QUICK_ROR_INTERSECT_SELECT::push_quick_back(MEM_ROOT *alloc, QUICK_RANGE_SELECT *quick)
+{
+ QUICK_SELECT_WITH_RECORD *qr;
+ if (!(qr= new QUICK_SELECT_WITH_RECORD) ||
+ !(qr->key_tuple= (uchar*)alloc_root(alloc, quick->max_used_key_length)))
+ return TRUE;
+ qr->quick= quick;
+ return quick_selects.push_back(qr);
+}
+
+
+QUICK_ROR_INTERSECT_SELECT::~QUICK_ROR_INTERSECT_SELECT()
+{
+ DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::~QUICK_ROR_INTERSECT_SELECT");
+ quick_selects.delete_elements();
+ delete cpk_quick;
+ free_root(&alloc,MYF(0));
+ if (need_to_fetch_row && head->file->inited != handler::NONE)
+ head->file->ha_rnd_end();
+ DBUG_VOID_RETURN;
+}
+
+
+QUICK_ROR_UNION_SELECT::QUICK_ROR_UNION_SELECT(THD *thd_param,
+ TABLE *table)
+ : thd(thd_param), scans_inited(FALSE)
+{
+ index= MAX_KEY;
+ head= table;
+ rowid_length= table->file->ref_length;
+ record= head->record[0];
+ init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ thd_param->mem_root= &alloc;
+}
+
+
+/*
+ Comparison function to be used QUICK_ROR_UNION_SELECT::queue priority
+ queue.
+
+ SYNPOSIS
+ QUICK_ROR_UNION_SELECT_queue_cmp()
+ arg Pointer to QUICK_ROR_UNION_SELECT
+ val1 First merged select
+ val2 Second merged select
+*/
+
+C_MODE_START
+
+static int QUICK_ROR_UNION_SELECT_queue_cmp(void *arg, uchar *val1, uchar *val2)
+{
+ QUICK_ROR_UNION_SELECT *self= (QUICK_ROR_UNION_SELECT*)arg;
+ return self->head->file->cmp_ref(((QUICK_SELECT_I*)val1)->last_rowid,
+ ((QUICK_SELECT_I*)val2)->last_rowid);
+}
+
+C_MODE_END
+
+
+/*
+ Do post-constructor initialization.
+ SYNOPSIS
+ QUICK_ROR_UNION_SELECT::init()
+
+ RETURN
+ 0 OK
+ other Error code
+*/
+
+int QUICK_ROR_UNION_SELECT::init()
+{
+ DBUG_ENTER("QUICK_ROR_UNION_SELECT::init");
+ if (init_queue(&queue, quick_selects.elements, 0,
+ FALSE , QUICK_ROR_UNION_SELECT_queue_cmp,
+ (void*) this, 0, 0))
+ {
+ bzero(&queue, sizeof(QUEUE));
+ DBUG_RETURN(1);
+ }
+
+ if (!(cur_rowid= (uchar*) alloc_root(&alloc, 2*head->file->ref_length)))
+ DBUG_RETURN(1);
+ prev_rowid= cur_rowid + head->file->ref_length;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Initialize quick select for row retrieval.
+ SYNOPSIS
+ reset()
+
+ RETURN
+ 0 OK
+ other Error code
+*/
+
+int QUICK_ROR_UNION_SELECT::reset()
+{
+ QUICK_SELECT_I *quick;
+ int error;
+ DBUG_ENTER("QUICK_ROR_UNION_SELECT::reset");
+ have_prev_rowid= FALSE;
+ if (!scans_inited)
+ {
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+ while ((quick= it++))
+ {
+ if (quick->init_ror_merged_scan(FALSE, &alloc))
+ DBUG_RETURN(1);
+ }
+ scans_inited= TRUE;
+ }
+ queue_remove_all(&queue);
+ /*
+ Initialize scans for merged quick selects and put all merged quick
+ selects into the queue.
+ */
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+ while ((quick= it++))
+ {
+ if ((error= quick->reset()))
+ DBUG_RETURN(error);
+ if ((error= quick->get_next()))
+ {
+ if (error == HA_ERR_END_OF_FILE)
+ continue;
+ DBUG_RETURN(error);
+ }
+ quick->save_last_pos();
+ queue_insert(&queue, (uchar*)quick);
+ }
+ /* Prepare for ha_rnd_pos calls. */
+ if (head->file->inited && (error= head->file->ha_rnd_end()))
+ {
+ DBUG_PRINT("error", ("ROR index_merge rnd_end call failed"));
+ DBUG_RETURN(error);
+ }
+ if ((error= head->file->ha_rnd_init(false)))
+ {
+ DBUG_PRINT("error", ("ROR index_merge rnd_init call failed"));
+ DBUG_RETURN(error);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+bool
+QUICK_ROR_UNION_SELECT::push_quick_back(QUICK_SELECT_I *quick_sel_range)
+{
+ return quick_selects.push_back(quick_sel_range);
+}
+
+QUICK_ROR_UNION_SELECT::~QUICK_ROR_UNION_SELECT()
+{
+ DBUG_ENTER("QUICK_ROR_UNION_SELECT::~QUICK_ROR_UNION_SELECT");
+ delete_queue(&queue);
+ quick_selects.delete_elements();
+ if (head->file->inited != handler::NONE)
+ head->file->ha_rnd_end();
+ free_root(&alloc,MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+
+QUICK_RANGE::QUICK_RANGE()
+ :min_key(0),max_key(0),min_length(0),max_length(0),
+ flag(NO_MIN_RANGE | NO_MAX_RANGE),
+ min_keypart_map(0), max_keypart_map(0)
+{}
+
+SEL_ARG::SEL_ARG(SEL_ARG &arg) :Sql_alloc()
+{
+ type=arg.type;
+ min_flag=arg.min_flag;
+ max_flag=arg.max_flag;
+ maybe_flag=arg.maybe_flag;
+ maybe_null=arg.maybe_null;
+ part=arg.part;
+ field=arg.field;
+ min_value=arg.min_value;
+ max_value=arg.max_value;
+ next_key_part=arg.next_key_part;
+ max_part_no= arg.max_part_no;
+ use_count=1; elements=1;
+}
+
+
+inline void SEL_ARG::make_root()
+{
+ left=right= &null_element;
+ color=BLACK;
+ next=prev=0;
+ use_count=0; elements=1;
+}
+
+SEL_ARG::SEL_ARG(Field *f,const uchar *min_value_arg,
+ const uchar *max_value_arg)
+ :min_flag(0), max_flag(0), maybe_flag(0), maybe_null(f->real_maybe_null()),
+ elements(1), use_count(1), field(f), min_value((uchar*) min_value_arg),
+ max_value((uchar*) max_value_arg), next(0),prev(0),
+ next_key_part(0), color(BLACK), type(KEY_RANGE)
+{
+ left=right= &null_element;
+ max_part_no= 1;
+}
+
+SEL_ARG::SEL_ARG(Field *field_,uint8 part_,
+ uchar *min_value_, uchar *max_value_,
+ uint8 min_flag_,uint8 max_flag_,uint8 maybe_flag_)
+ :min_flag(min_flag_),max_flag(max_flag_),maybe_flag(maybe_flag_),
+ part(part_),maybe_null(field_->real_maybe_null()), elements(1),use_count(1),
+ field(field_), min_value(min_value_), max_value(max_value_),
+ next(0),prev(0),next_key_part(0),color(BLACK),type(KEY_RANGE)
+{
+ max_part_no= part+1;
+ left=right= &null_element;
+}
+
+SEL_ARG *SEL_ARG::clone(RANGE_OPT_PARAM *param, SEL_ARG *new_parent,
+ SEL_ARG **next_arg)
+{
+ SEL_ARG *tmp;
+
+ /* Bail out if we have already generated too many SEL_ARGs */
+ if (++param->alloced_sel_args > MAX_SEL_ARGS)
+ return 0;
+
+ if (type != KEY_RANGE)
+ {
+ if (!(tmp= new (param->mem_root) SEL_ARG(type)))
+ return 0; // out of memory
+ tmp->prev= *next_arg; // Link into next/prev chain
+ (*next_arg)->next=tmp;
+ (*next_arg)= tmp;
+ tmp->part= this->part;
+ }
+ else
+ {
+ if (!(tmp= new (param->mem_root) SEL_ARG(field,part, min_value,max_value,
+ min_flag, max_flag, maybe_flag)))
+ return 0; // OOM
+ tmp->parent=new_parent;
+ tmp->next_key_part=next_key_part;
+ if (left != &null_element)
+ if (!(tmp->left=left->clone(param, tmp, next_arg)))
+ return 0; // OOM
+
+ tmp->prev= *next_arg; // Link into next/prev chain
+ (*next_arg)->next=tmp;
+ (*next_arg)= tmp;
+
+ if (right != &null_element)
+ if (!(tmp->right= right->clone(param, tmp, next_arg)))
+ return 0; // OOM
+ }
+ increment_use_count(1);
+ tmp->color= color;
+ tmp->elements= this->elements;
+ tmp->max_part_no= max_part_no;
+ return tmp;
+}
+
+/**
+ This gives the first SEL_ARG in the interval list, and the minimal element
+ in the red-black tree
+
+ @return
+ SEL_ARG first SEL_ARG in the interval list
+*/
+SEL_ARG *SEL_ARG::first()
+{
+ SEL_ARG *next_arg=this;
+ if (!next_arg->left)
+ return 0; // MAYBE_KEY
+ while (next_arg->left != &null_element)
+ next_arg=next_arg->left;
+ return next_arg;
+}
+
+const SEL_ARG *SEL_ARG::first() const
+{
+ return const_cast<SEL_ARG*>(this)->first();
+}
+
+SEL_ARG *SEL_ARG::last()
+{
+ SEL_ARG *next_arg=this;
+ if (!next_arg->right)
+ return 0; // MAYBE_KEY
+ while (next_arg->right != &null_element)
+ next_arg=next_arg->right;
+ return next_arg;
+}
+
+
+/*
+ Check if a compare is ok, when one takes ranges in account
+ Returns -2 or 2 if the ranges where 'joined' like < 2 and >= 2
+*/
+
+static int sel_cmp(Field *field, uchar *a, uchar *b, uint8 a_flag,
+ uint8 b_flag)
+{
+ int cmp;
+ /* First check if there was a compare to a min or max element */
+ if (a_flag & (NO_MIN_RANGE | NO_MAX_RANGE))
+ {
+ if ((a_flag & (NO_MIN_RANGE | NO_MAX_RANGE)) ==
+ (b_flag & (NO_MIN_RANGE | NO_MAX_RANGE)))
+ return 0;
+ return (a_flag & NO_MIN_RANGE) ? -1 : 1;
+ }
+ if (b_flag & (NO_MIN_RANGE | NO_MAX_RANGE))
+ return (b_flag & NO_MIN_RANGE) ? 1 : -1;
+
+ if (field->real_maybe_null()) // If null is part of key
+ {
+ if (*a != *b)
+ {
+ return *a ? -1 : 1;
+ }
+ if (*a)
+ goto end; // NULL where equal
+ a++; b++; // Skip NULL marker
+ }
+ cmp=field->key_cmp(a , b);
+ if (cmp) return cmp < 0 ? -1 : 1; // The values differed
+
+ // Check if the compared equal arguments was defined with open/closed range
+ end:
+ if (a_flag & (NEAR_MIN | NEAR_MAX))
+ {
+ if ((a_flag & (NEAR_MIN | NEAR_MAX)) == (b_flag & (NEAR_MIN | NEAR_MAX)))
+ return 0;
+ if (!(b_flag & (NEAR_MIN | NEAR_MAX)))
+ return (a_flag & NEAR_MIN) ? 2 : -2;
+ return (a_flag & NEAR_MIN) ? 1 : -1;
+ }
+ if (b_flag & (NEAR_MIN | NEAR_MAX))
+ return (b_flag & NEAR_MIN) ? -2 : 2;
+ return 0; // The elements where equal
+}
+
+
+SEL_ARG *SEL_ARG::clone_tree(RANGE_OPT_PARAM *param)
+{
+ SEL_ARG tmp_link,*next_arg,*root;
+ next_arg= &tmp_link;
+ if (!(root= clone(param, (SEL_ARG *) 0, &next_arg)))
+ return 0;
+ next_arg->next=0; // Fix last link
+ tmp_link.next->prev=0; // Fix first link
+ if (root) // If not OOM
+ root->use_count= 0;
+ return root;
+}
+
+
+/*
+ Table rows retrieval plan. Range optimizer creates QUICK_SELECT_I-derived
+ objects from table read plans.
+*/
+class TABLE_READ_PLAN
+{
+public:
+ /*
+ Plan read cost, with or without cost of full row retrieval, depending
+ on plan creation parameters.
+ */
+ double read_cost;
+ ha_rows records; /* estimate of #rows to be examined */
+
+ /*
+ If TRUE, the scan returns rows in rowid order. This is used only for
+ scans that can be both ROR and non-ROR.
+ */
+ bool is_ror;
+
+ /*
+ Create quick select for this plan.
+ SYNOPSIS
+ make_quick()
+ param Parameter from test_quick_select
+ retrieve_full_rows If TRUE, created quick select will do full record
+ retrieval.
+ parent_alloc Memory pool to use, if any.
+
+ NOTES
+ retrieve_full_rows is ignored by some implementations.
+
+ RETURN
+ created quick select
+ NULL on any error.
+ */
+ virtual QUICK_SELECT_I *make_quick(PARAM *param,
+ bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc=NULL) = 0;
+
+ /* Table read plans are allocated on MEM_ROOT and are never deleted */
+ static void *operator new(size_t size, MEM_ROOT *mem_root)
+ { return (void*) alloc_root(mem_root, (uint) size); }
+ static void operator delete(void *ptr,size_t size) { TRASH(ptr, size); }
+ static void operator delete(void *ptr, MEM_ROOT *mem_root) { /* Never called */ }
+ virtual ~TABLE_READ_PLAN() {} /* Remove gcc warning */
+
+};
+
+class TRP_ROR_INTERSECT;
+class TRP_ROR_UNION;
+class TRP_INDEX_MERGE;
+
+
+/*
+ Plan for a QUICK_RANGE_SELECT scan.
+ TRP_RANGE::make_quick ignores retrieve_full_rows parameter because
+ QUICK_RANGE_SELECT doesn't distinguish between 'index only' scans and full
+ record retrieval scans.
+*/
+
+class TRP_RANGE : public TABLE_READ_PLAN
+{
+public:
+ SEL_ARG *key; /* set of intervals to be used in "range" method retrieval */
+ uint key_idx; /* key number in PARAM::key */
+ uint mrr_flags;
+ uint mrr_buf_size;
+
+ TRP_RANGE(SEL_ARG *key_arg, uint idx_arg, uint mrr_flags_arg)
+ : key(key_arg), key_idx(idx_arg), mrr_flags(mrr_flags_arg)
+ {}
+ virtual ~TRP_RANGE() {} /* Remove gcc warning */
+
+ QUICK_SELECT_I *make_quick(PARAM *param, bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc)
+ {
+ DBUG_ENTER("TRP_RANGE::make_quick");
+ QUICK_RANGE_SELECT *quick;
+ if ((quick= get_quick_select(param, key_idx, key, mrr_flags,
+ mrr_buf_size, parent_alloc)))
+ {
+ quick->records= records;
+ quick->read_time= read_cost;
+ }
+ DBUG_RETURN(quick);
+ }
+};
+
+
+/* Plan for QUICK_ROR_INTERSECT_SELECT scan. */
+
+class TRP_ROR_INTERSECT : public TABLE_READ_PLAN
+{
+public:
+ TRP_ROR_INTERSECT() {} /* Remove gcc warning */
+ virtual ~TRP_ROR_INTERSECT() {} /* Remove gcc warning */
+ QUICK_SELECT_I *make_quick(PARAM *param, bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc);
+
+ /* Array of pointers to ROR range scans used in this intersection */
+ struct st_ror_scan_info **first_scan;
+ struct st_ror_scan_info **last_scan; /* End of the above array */
+ struct st_ror_scan_info *cpk_scan; /* Clustered PK scan, if there is one */
+ bool is_covering; /* TRUE if no row retrieval phase is necessary */
+ double index_scan_costs; /* SUM(cost(index_scan)) */
+};
+
+
+/*
+ Plan for QUICK_ROR_UNION_SELECT scan.
+ QUICK_ROR_UNION_SELECT always retrieves full rows, so retrieve_full_rows
+ is ignored by make_quick.
+*/
+
+class TRP_ROR_UNION : public TABLE_READ_PLAN
+{
+public:
+ TRP_ROR_UNION() {} /* Remove gcc warning */
+ virtual ~TRP_ROR_UNION() {} /* Remove gcc warning */
+ QUICK_SELECT_I *make_quick(PARAM *param, bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc);
+ TABLE_READ_PLAN **first_ror; /* array of ptrs to plans for merged scans */
+ TABLE_READ_PLAN **last_ror; /* end of the above array */
+};
+
+
+/*
+ Plan for QUICK_INDEX_INTERSECT_SELECT scan.
+ QUICK_INDEX_INTERSECT_SELECT always retrieves full rows, so retrieve_full_rows
+ is ignored by make_quick.
+*/
+
+class TRP_INDEX_INTERSECT : public TABLE_READ_PLAN
+{
+public:
+ TRP_INDEX_INTERSECT() {} /* Remove gcc warning */
+ virtual ~TRP_INDEX_INTERSECT() {} /* Remove gcc warning */
+ QUICK_SELECT_I *make_quick(PARAM *param, bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc);
+ TRP_RANGE **range_scans; /* array of ptrs to plans of intersected scans */
+ TRP_RANGE **range_scans_end; /* end of the array */
+ /* keys whose scans are to be filtered by cpk conditions */
+ key_map filtered_scans;
+};
+
+
+/*
+ Plan for QUICK_INDEX_MERGE_SELECT scan.
+ QUICK_ROR_INTERSECT_SELECT always retrieves full rows, so retrieve_full_rows
+ is ignored by make_quick.
+*/
+
+class TRP_INDEX_MERGE : public TABLE_READ_PLAN
+{
+public:
+ TRP_INDEX_MERGE() {} /* Remove gcc warning */
+ virtual ~TRP_INDEX_MERGE() {} /* Remove gcc warning */
+ QUICK_SELECT_I *make_quick(PARAM *param, bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc);
+ TRP_RANGE **range_scans; /* array of ptrs to plans of merged scans */
+ TRP_RANGE **range_scans_end; /* end of the array */
+};
+
+
+/*
+ Plan for a QUICK_GROUP_MIN_MAX_SELECT scan.
+*/
+
+class TRP_GROUP_MIN_MAX : public TABLE_READ_PLAN
+{
+private:
+ bool have_min, have_max, have_agg_distinct;
+ KEY_PART_INFO *min_max_arg_part;
+ uint group_prefix_len;
+ uint used_key_parts;
+ uint group_key_parts;
+ KEY *index_info;
+ uint index;
+ uint key_infix_len;
+ uchar key_infix[MAX_KEY_LENGTH];
+ SEL_TREE *range_tree; /* Represents all range predicates in the query. */
+ SEL_ARG *index_tree; /* The SEL_ARG sub-tree corresponding to index_info. */
+ uint param_idx; /* Index of used key in param->key. */
+ bool is_index_scan; /* Use index_next() instead of random read */
+public:
+ /* Number of records selected by the ranges in index_tree. */
+ ha_rows quick_prefix_records;
+public:
+ TRP_GROUP_MIN_MAX(bool have_min_arg, bool have_max_arg,
+ bool have_agg_distinct_arg,
+ KEY_PART_INFO *min_max_arg_part_arg,
+ uint group_prefix_len_arg, uint used_key_parts_arg,
+ uint group_key_parts_arg, KEY *index_info_arg,
+ uint index_arg, uint key_infix_len_arg,
+ uchar *key_infix_arg,
+ SEL_TREE *tree_arg, SEL_ARG *index_tree_arg,
+ uint param_idx_arg, ha_rows quick_prefix_records_arg)
+ : have_min(have_min_arg), have_max(have_max_arg),
+ have_agg_distinct(have_agg_distinct_arg),
+ min_max_arg_part(min_max_arg_part_arg),
+ group_prefix_len(group_prefix_len_arg), used_key_parts(used_key_parts_arg),
+ group_key_parts(group_key_parts_arg), index_info(index_info_arg),
+ index(index_arg), key_infix_len(key_infix_len_arg), range_tree(tree_arg),
+ index_tree(index_tree_arg), param_idx(param_idx_arg), is_index_scan(FALSE),
+ quick_prefix_records(quick_prefix_records_arg)
+ {
+ if (key_infix_len)
+ memcpy(this->key_infix, key_infix_arg, key_infix_len);
+ }
+ virtual ~TRP_GROUP_MIN_MAX() {} /* Remove gcc warning */
+
+ QUICK_SELECT_I *make_quick(PARAM *param, bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc);
+ void use_index_scan() { is_index_scan= TRUE; }
+};
+
+
+typedef struct st_index_scan_info
+{
+ uint idx; /* # of used key in param->keys */
+ uint keynr; /* # of used key in table */
+ uint range_count;
+ ha_rows records; /* estimate of # records this scan will return */
+
+ /* Set of intervals over key fields that will be used for row retrieval. */
+ SEL_ARG *sel_arg;
+
+ KEY *key_info;
+ uint used_key_parts;
+
+ /* Estimate of # records filtered out by intersection with cpk */
+ ha_rows filtered_out;
+ /* Bitmap of fields used in index intersection */
+ MY_BITMAP used_fields;
+
+ /* Fields used in the query and covered by ROR scan. */
+ MY_BITMAP covered_fields;
+ uint used_fields_covered; /* # of set bits in covered_fields */
+ int key_rec_length; /* length of key record (including rowid) */
+
+ /*
+ Cost of reading all index records with values in sel_arg intervals set
+ (assuming there is no need to access full table records)
+ */
+ double index_read_cost;
+ uint first_uncovered_field; /* first unused bit in covered_fields */
+ uint key_components; /* # of parts in the key */
+} INDEX_SCAN_INFO;
+
+/*
+ Fill param->needed_fields with bitmap of fields used in the query.
+ SYNOPSIS
+ fill_used_fields_bitmap()
+ param Parameter from test_quick_select function.
+
+ NOTES
+ Clustered PK members are not put into the bitmap as they are implicitly
+ present in all keys (and it is impossible to avoid reading them).
+ RETURN
+ 0 Ok
+ 1 Out of memory.
+*/
+
+static int fill_used_fields_bitmap(PARAM *param)
+{
+ TABLE *table= param->table;
+ my_bitmap_map *tmp;
+ uint pk;
+ param->tmp_covered_fields.bitmap= 0;
+ param->fields_bitmap_size= table->s->column_bitmap_size;
+ if (!(tmp= (my_bitmap_map*) alloc_root(param->mem_root,
+ param->fields_bitmap_size)) ||
+ my_bitmap_init(&param->needed_fields, tmp, table->s->fields, FALSE))
+ return 1;
+
+ bitmap_copy(&param->needed_fields, table->read_set);
+ bitmap_union(&param->needed_fields, table->write_set);
+
+ pk= param->table->s->primary_key;
+ if (pk != MAX_KEY && param->table->file->primary_key_is_clustered())
+ {
+ /* The table uses clustered PK and it is not internally generated */
+ KEY_PART_INFO *key_part= param->table->key_info[pk].key_part;
+ KEY_PART_INFO *key_part_end= key_part +
+ param->table->key_info[pk].user_defined_key_parts;
+ for (;key_part != key_part_end; ++key_part)
+ bitmap_clear_bit(&param->needed_fields, key_part->fieldnr-1);
+ }
+ return 0;
+}
+
+
+/*
+ Test if a key can be used in different ranges
+
+ SYNOPSIS
+ SQL_SELECT::test_quick_select()
+ thd Current thread
+ keys_to_use Keys to use for range retrieval
+ prev_tables Tables assumed to be already read when the scan is
+ performed (but not read at the moment of this call)
+ limit Query limit
+ force_quick_range Prefer to use range (instead of full table scan) even
+ if it is more expensive.
+
+ NOTES
+ Updates the following in the select parameter:
+ needed_reg - Bits for keys with may be used if all prev regs are read
+ quick - Parameter to use when reading records.
+
+ In the table struct the following information is updated:
+ quick_keys - Which keys can be used
+ quick_rows - How many rows the key matches
+ quick_condition_rows - E(# rows that will satisfy the table condition)
+
+ IMPLEMENTATION
+ quick_condition_rows value is obtained as follows:
+
+ It is a minimum of E(#output rows) for all considered table access
+ methods (range and index_merge accesses over various indexes).
+
+ The obtained value is not a true E(#rows that satisfy table condition)
+ but rather a pessimistic estimate. To obtain a true E(#...) one would
+ need to combine estimates of various access methods, taking into account
+ correlations between sets of rows they will return.
+
+ For example, if values of tbl.key1 and tbl.key2 are independent (a right
+ assumption if we have no information about their correlation) then the
+ correct estimate will be:
+
+ E(#rows("tbl.key1 < c1 AND tbl.key2 < c2")) =
+ = E(#rows(tbl.key1 < c1)) / total_rows(tbl) * E(#rows(tbl.key2 < c2)
+
+ which is smaller than
+
+ MIN(E(#rows(tbl.key1 < c1), E(#rows(tbl.key2 < c2)))
+
+ which is currently produced.
+
+ TODO
+ * Change the value returned in quick_condition_rows from a pessimistic
+ estimate to true E(#rows that satisfy table condition).
+ (we can re-use some of E(#rows) calcuation code from index_merge/intersection
+ for this)
+
+ * Check if this function really needs to modify keys_to_use, and change the
+ code to pass it by reference if it doesn't.
+
+ * In addition to force_quick_range other means can be (an usually are) used
+ to make this function prefer range over full table scan. Figure out if
+ force_quick_range is really needed.
+
+ RETURN
+ -1 if impossible select (i.e. certainly no rows will be selected)
+ 0 if can't use quick_select
+ 1 if found usable ranges and quick select has been successfully created.
+*/
+
+int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
+ table_map prev_tables,
+ ha_rows limit, bool force_quick_range,
+ bool ordered_output)
+{
+ uint idx;
+ double scan_time;
+ DBUG_ENTER("SQL_SELECT::test_quick_select");
+ DBUG_PRINT("enter",("keys_to_use: %lu prev_tables: %lu const_tables: %lu",
+ (ulong) keys_to_use.to_ulonglong(), (ulong) prev_tables,
+ (ulong) const_tables));
+ DBUG_PRINT("info", ("records: %lu", (ulong) head->stat_records()));
+ delete quick;
+ quick=0;
+ needed_reg.clear_all();
+ quick_keys.clear_all();
+ DBUG_ASSERT(!head->is_filled_at_execution());
+ if (keys_to_use.is_clear_all() || head->is_filled_at_execution())
+ DBUG_RETURN(0);
+ records= head->stat_records();
+ if (!records)
+ records++; /* purecov: inspected */
+ scan_time= (double) records / TIME_FOR_COMPARE + 1;
+ read_time= (double) head->file->scan_time() + scan_time + 1.1;
+ if (head->force_index)
+ scan_time= read_time= DBL_MAX;
+ if (limit < records)
+ read_time= (double) records + scan_time + 1; // Force to use index
+ else if (read_time <= 2.0 && !force_quick_range)
+ DBUG_RETURN(0); /* No need for quick select */
+
+ possible_keys.clear_all();
+
+ DBUG_PRINT("info",("Time to scan table: %g", read_time));
+
+ keys_to_use.intersect(head->keys_in_use_for_query);
+ if (!keys_to_use.is_clear_all())
+ {
+ uchar buff[STACK_BUFF_ALLOC];
+ MEM_ROOT alloc;
+ SEL_TREE *tree= NULL;
+ KEY_PART *key_parts;
+ KEY *key_info;
+ PARAM param;
+
+ if (check_stack_overrun(thd, 2*STACK_MIN_SIZE + sizeof(PARAM), buff))
+ DBUG_RETURN(0); // Fatal error flag is set
+
+ /* set up parameter that is passed to all functions */
+ param.thd= thd;
+ param.baseflag= head->file->ha_table_flags();
+ param.prev_tables=prev_tables | const_tables;
+ param.read_tables=read_tables;
+ param.current_table= head->map;
+ param.table=head;
+ param.keys=0;
+ param.mem_root= &alloc;
+ param.old_root= thd->mem_root;
+ param.needed_reg= &needed_reg;
+ param.imerge_cost_buff_size= 0;
+ param.using_real_indexes= TRUE;
+ param.remove_jump_scans= TRUE;
+ param.force_default_mrr= ordered_output;
+ param.possible_keys.clear_all();
+
+ thd->no_errors=1; // Don't warn about NULL
+ init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ if (!(param.key_parts=
+ (KEY_PART*) alloc_root(&alloc,
+ sizeof(KEY_PART) *
+ head->s->actual_n_key_parts(thd))) ||
+ fill_used_fields_bitmap(&param))
+ {
+ thd->no_errors=0;
+ free_root(&alloc,MYF(0)); // Return memory & allocator
+ DBUG_RETURN(0); // Can't use range
+ }
+ key_parts= param.key_parts;
+ thd->mem_root= &alloc;
+
+ /*
+ Make an array with description of all key parts of all table keys.
+ This is used in get_mm_parts function.
+ */
+ key_info= head->key_info;
+ for (idx=0 ; idx < head->s->keys ; idx++, key_info++)
+ {
+ KEY_PART_INFO *key_part_info;
+ uint n_key_parts= head->actual_n_key_parts(key_info);
+
+ if (!keys_to_use.is_set(idx))
+ continue;
+ if (key_info->flags & HA_FULLTEXT)
+ continue; // ToDo: ft-keys in non-ft ranges, if possible SerG
+
+ param.key[param.keys]=key_parts;
+ key_part_info= key_info->key_part;
+ for (uint part= 0 ; part < n_key_parts ;
+ part++, key_parts++, key_part_info++)
+ {
+ key_parts->key= param.keys;
+ key_parts->part= part;
+ key_parts->length= key_part_info->length;
+ key_parts->store_length= key_part_info->store_length;
+ key_parts->field= key_part_info->field;
+ key_parts->null_bit= key_part_info->null_bit;
+ key_parts->image_type =
+ (key_info->flags & HA_SPATIAL) ? Field::itMBR : Field::itRAW;
+ /* Only HA_PART_KEY_SEG is used */
+ key_parts->flag= (uint8) key_part_info->key_part_flag;
+ }
+ param.real_keynr[param.keys++]=idx;
+ }
+ param.key_parts_end=key_parts;
+ param.alloced_sel_args= 0;
+
+ /* Calculate cost of full index read for the shortest covering index */
+ if (!head->covering_keys.is_clear_all())
+ {
+ int key_for_use= find_shortest_key(head, &head->covering_keys);
+ double key_read_time= head->file->keyread_time(key_for_use, 1, records) +
+ (double) records / TIME_FOR_COMPARE;
+ DBUG_PRINT("info", ("'all'+'using index' scan will be using key %d, "
+ "read time %g", key_for_use, key_read_time));
+ if (key_read_time < read_time)
+ read_time= key_read_time;
+ }
+
+ TABLE_READ_PLAN *best_trp= NULL;
+ TRP_GROUP_MIN_MAX *group_trp;
+ double best_read_time= read_time;
+
+ if (cond)
+ {
+ if ((tree= get_mm_tree(&param,cond)))
+ {
+ if (tree->type == SEL_TREE::IMPOSSIBLE)
+ {
+ records=0L; /* Return -1 from this function. */
+ read_time= (double) HA_POS_ERROR;
+ goto free_mem;
+ }
+ /*
+ If the tree can't be used for range scans, proceed anyway, as we
+ can construct a group-min-max quick select
+ */
+ if (tree->type != SEL_TREE::KEY && tree->type != SEL_TREE::KEY_SMALLER)
+ tree= NULL;
+ }
+ }
+
+ /*
+ Try to construct a QUICK_GROUP_MIN_MAX_SELECT.
+ Notice that it can be constructed no matter if there is a range tree.
+ */
+ group_trp= get_best_group_min_max(&param, tree, best_read_time);
+ if (group_trp)
+ {
+ param.table->quick_condition_rows= MY_MIN(group_trp->records,
+ head->stat_records());
+ if (group_trp->read_cost < best_read_time)
+ {
+ best_trp= group_trp;
+ best_read_time= best_trp->read_cost;
+ }
+ }
+
+ if (tree)
+ {
+ /*
+ It is possible to use a range-based quick select (but it might be
+ slower than 'all' table scan).
+ */
+ TRP_RANGE *range_trp;
+ TRP_ROR_INTERSECT *rori_trp;
+ TRP_INDEX_INTERSECT *intersect_trp;
+ bool can_build_covering= FALSE;
+
+ remove_nonrange_trees(&param, tree);
+
+ /* Get best 'range' plan and prepare data for making other plans */
+ if ((range_trp= get_key_scans_params(&param, tree, FALSE, TRUE,
+ best_read_time)))
+ {
+ best_trp= range_trp;
+ best_read_time= best_trp->read_cost;
+ }
+
+ /*
+ Simultaneous key scans and row deletes on several handler
+ objects are not allowed so don't use ROR-intersection for
+ table deletes.
+ */
+ if ((thd->lex->sql_command != SQLCOM_DELETE) &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE))
+ {
+ /*
+ Get best non-covering ROR-intersection plan and prepare data for
+ building covering ROR-intersection.
+ */
+ if ((rori_trp= get_best_ror_intersect(&param, tree, best_read_time,
+ &can_build_covering)))
+ {
+ best_trp= rori_trp;
+ best_read_time= best_trp->read_cost;
+ /*
+ Try constructing covering ROR-intersect only if it looks possible
+ and worth doing.
+ */
+ if (!rori_trp->is_covering && can_build_covering &&
+ (rori_trp= get_best_covering_ror_intersect(&param, tree,
+ best_read_time)))
+ best_trp= rori_trp;
+ }
+ }
+ /*
+ Do not look for an index intersection plan if there is a covering
+ index. The scan by this covering index will be always cheaper than
+ any index intersection.
+ */
+ if (param.table->covering_keys.is_clear_all() &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE) &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE_SORT_INTERSECT))
+ {
+ if ((intersect_trp= get_best_index_intersect(&param, tree,
+ best_read_time)))
+ {
+ best_trp= intersect_trp;
+ best_read_time= best_trp->read_cost;
+ set_if_smaller(param.table->quick_condition_rows,
+ intersect_trp->records);
+ }
+ }
+
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE) &&
+ head->stat_records() != 0)
+ {
+ /* Try creating index_merge/ROR-union scan. */
+ SEL_IMERGE *imerge;
+ TABLE_READ_PLAN *best_conj_trp= NULL, *new_conj_trp;
+ LINT_INIT(new_conj_trp); /* no empty index_merge lists possible */
+ DBUG_PRINT("info",("No range reads possible,"
+ " trying to construct index_merge"));
+ List_iterator_fast<SEL_IMERGE> it(tree->merges);
+ while ((imerge= it++))
+ {
+ new_conj_trp= get_best_disjunct_quick(&param, imerge, best_read_time);
+ if (new_conj_trp)
+ set_if_smaller(param.table->quick_condition_rows,
+ new_conj_trp->records);
+ if (new_conj_trp &&
+ (!best_conj_trp ||
+ new_conj_trp->read_cost < best_conj_trp->read_cost))
+ {
+ best_conj_trp= new_conj_trp;
+ best_read_time= best_conj_trp->read_cost;
+ }
+ }
+ if (best_conj_trp)
+ best_trp= best_conj_trp;
+ }
+ }
+
+ thd->mem_root= param.old_root;
+
+ /* If we got a read plan, create a quick select from it. */
+ if (best_trp)
+ {
+ records= best_trp->records;
+ if (!(quick= best_trp->make_quick(&param, TRUE)) || quick->init())
+ {
+ delete quick;
+ quick= NULL;
+ }
+ }
+ possible_keys= param.possible_keys;
+
+ free_mem:
+ free_root(&alloc,MYF(0)); // Return memory & allocator
+ thd->mem_root= param.old_root;
+ thd->no_errors=0;
+ }
+
+
+ DBUG_EXECUTE("info", print_quick(quick, &needed_reg););
+
+ /*
+ Assume that if the user is using 'limit' we will only need to scan
+ limit rows if we are using a key
+ */
+ DBUG_RETURN(records ? MY_TEST(quick) : -1);
+}
+
+/****************************************************************************
+ * Condition selectivity module
+ ****************************************************************************/
+
+
+/*
+ Build descriptors of pseudo-indexes over columns to perform range analysis
+
+ SYNOPSIS
+ create_key_parts_for_pseudo_indexes()
+ param IN/OUT data structure for the descriptors to be built
+ used_fields bitmap of columns for which the descriptors are to be built
+
+ DESCRIPTION
+ For each column marked in the bitmap used_fields the function builds
+ a descriptor of a single-component pseudo-index over this column that
+ can be used for the range analysis of the predicates over this columns.
+ The descriptors are created in the memory of param->mem_root.
+
+ RETURN
+ FALSE in the case of success
+ TRUE otherwise
+*/
+
+static
+bool create_key_parts_for_pseudo_indexes(RANGE_OPT_PARAM *param,
+ MY_BITMAP *used_fields)
+{
+ Field **field_ptr;
+ TABLE *table= param->table;
+ uint parts= 0;
+
+ for (field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ if (bitmap_is_set(used_fields, (*field_ptr)->field_index))
+ parts++;
+ }
+
+ KEY_PART *key_part;
+ uint keys= 0;
+
+ if (!(key_part= (KEY_PART *) alloc_root(param->mem_root,
+ sizeof(KEY_PART) * parts)))
+ return TRUE;
+
+ param->key_parts= key_part;
+
+ for (field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ if (bitmap_is_set(used_fields, (*field_ptr)->field_index))
+ {
+ Field *field= *field_ptr;
+ uint16 store_length;
+ key_part->key= keys;
+ key_part->part= 0;
+ key_part->length= (uint16) field->key_length();
+ store_length= key_part->length;
+ if (field->real_maybe_null())
+ store_length+= HA_KEY_NULL_LENGTH;
+ if (field->real_type() == MYSQL_TYPE_VARCHAR)
+ store_length+= HA_KEY_BLOB_LENGTH;
+ key_part->store_length= store_length;
+ key_part->field= field;
+ key_part->image_type= Field::itRAW;
+ key_part->flag= 0;
+ param->key[keys]= key_part;
+ keys++;
+ key_part++;
+ }
+ }
+ param->keys= keys;
+ param->key_parts_end= key_part;
+
+ return FALSE;
+}
+
+
+/*
+ Estimate the number of rows in all ranges built for a column
+ by the range optimizer
+
+ SYNOPSIS
+ records_in_column_ranges()
+ param the data structure to access descriptors of pseudo indexes
+ built over columns used in the condition of the processed query
+ idx the index of the descriptor of interest in param
+ tree the tree representing ranges built for the interesting column
+
+ DESCRIPTION
+ This function retrieves the ranges represented by the SEL_ARG 'tree' and
+ for each of them r it calls the function get_column_range_cardinality()
+ that estimates the number of expected rows in r. It is assumed that param
+ is the data structure containing the descriptors of pseudo-indexes that
+ has been built to perform range analysis of the range conditions imposed
+ on the columns used in the processed query, while idx is the index of the
+ descriptor created in 'param' exactly for the column for which 'tree'
+ has been built by the range optimizer.
+
+ RETURN
+ the number of rows in the retrieved ranges
+*/
+
+static
+double records_in_column_ranges(PARAM *param, uint idx,
+ SEL_ARG *tree)
+{
+ SEL_ARG_RANGE_SEQ seq;
+ KEY_MULTI_RANGE range;
+ range_seq_t seq_it;
+ double rows;
+ Field *field;
+ uint flags= 0;
+ double total_rows= 0;
+ RANGE_SEQ_IF seq_if = {NULL, sel_arg_range_seq_init,
+ sel_arg_range_seq_next, 0, 0};
+
+ /* Handle cases when we don't have a valid non-empty list of range */
+ if (!tree)
+ return HA_POS_ERROR;
+ if (tree->type == SEL_ARG::IMPOSSIBLE)
+ return (0L);
+
+ field= tree->field;
+
+ seq.keyno= idx;
+ seq.real_keyno= MAX_KEY;
+ seq.param= param;
+ seq.start= tree;
+
+ seq_it= seq_if.init((void *) &seq, 0, flags);
+
+ while (!seq_if.next(seq_it, &range))
+ {
+ key_range *min_endp, *max_endp;
+ min_endp= range.start_key.length? &range.start_key : NULL;
+ max_endp= range.end_key.length? &range.end_key : NULL;
+ rows= get_column_range_cardinality(field, min_endp, max_endp,
+ range.range_flag);
+ if (HA_POS_ERROR == rows)
+ {
+ total_rows= HA_POS_ERROR;
+ break;
+ }
+ total_rows += rows;
+ }
+ return total_rows;
+}
+
+
+/*
+ Calculate the selectivity of the condition imposed on the rows of a table
+
+ SYNOPSIS
+ calculate_cond_selectivity_for_table()
+ thd the context handle
+ table the table of interest
+ cond conditions imposed on the rows of the table
+
+ DESCRIPTION
+ This function calculates the selectivity of range conditions cond imposed
+ on the rows of 'table' in the processed query.
+ The calculated selectivity is assigned to the field table->cond_selectivity.
+
+ Selectivity is calculated as a product of selectivities imposed by:
+
+ 1. possible range accesses. (if multiple range accesses use the same
+ restrictions on the same field, we make adjustments for that)
+ 2. Sargable conditions on fields for which we have column statistics (if
+ a field is used in a possible range access, we assume that selectivity
+ is already provided by the range access' estimates)
+ 3. Reading a few records from the table pages and checking the condition
+ selectivity (this is used for conditions like "column LIKE '%val%'"
+ where approaches #1 and #2 do not provide selectivity data).
+
+ NOTE
+ Currently the selectivities of range conditions over different columns are
+ considered independent.
+
+ RETURN
+ FALSE on success
+ TRUE otherwise
+*/
+
+bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond)
+{
+ uint keynr;
+ uint max_quick_key_parts= 0;
+ MY_BITMAP *used_fields= &table->cond_set;
+ double table_records= table->stat_records();
+ DBUG_ENTER("calculate_cond_selectivity_for_table");
+
+ table->cond_selectivity= 1.0;
+
+ if (!cond || table_records == 0)
+ DBUG_RETURN(FALSE);
+
+ if (table->pos_in_table_list->schema_table)
+ DBUG_RETURN(FALSE);
+
+ MY_BITMAP handled_columns;
+ my_bitmap_map* buf;
+ if (!(buf= (my_bitmap_map*)thd->alloc(table->s->column_bitmap_size)))
+ DBUG_RETURN(TRUE);
+ my_bitmap_init(&handled_columns, buf, table->s->fields, FALSE);
+
+ /*
+ Calculate the selectivity of the range conditions supported by indexes.
+
+ First, take into account possible range accesses.
+ range access estimates are the most precise, we prefer them to any other
+ estimate sources.
+ */
+
+ for (keynr= 0; keynr < table->s->keys; keynr++)
+ {
+ if (table->quick_keys.is_set(keynr))
+ set_if_bigger(max_quick_key_parts, table->quick_key_parts[keynr]);
+ }
+
+ /*
+ Walk through all indexes, indexes where range access uses more keyparts
+ go first.
+ */
+ for (uint quick_key_parts= max_quick_key_parts;
+ quick_key_parts; quick_key_parts--)
+ {
+ for (keynr= 0; keynr < table->s->keys; keynr++)
+ {
+ if (table->quick_keys.is_set(keynr) &&
+ table->quick_key_parts[keynr] == quick_key_parts)
+ {
+ uint i;
+ uint used_key_parts= table->quick_key_parts[keynr];
+ double quick_cond_selectivity= table->quick_rows[keynr] /
+ table_records;
+ KEY *key_info= table->key_info + keynr;
+ KEY_PART_INFO* key_part= key_info->key_part;
+ /*
+ Suppose, there are range conditions on two keys
+ KEY1 (col1, col2)
+ KEY2 (col3, col2)
+
+ we don't want to count selectivity of condition on col2 twice.
+
+ First, find the longest key prefix that's made of columns whose
+ selectivity wasn't already accounted for.
+ */
+ for (i= 0; i < used_key_parts; i++, key_part++)
+ {
+ if (bitmap_is_set(&handled_columns, key_part->fieldnr-1))
+ break;
+ bitmap_set_bit(&handled_columns, key_part->fieldnr-1);
+ }
+ double selectivity_mult;
+ if (i)
+ {
+ /*
+ There is at least 1-column prefix of columns whose selectivity has
+ not yet been accounted for.
+ */
+ table->cond_selectivity*= quick_cond_selectivity;
+ if (i != used_key_parts)
+ {
+ /*
+ Range access got us estimate for #used_key_parts.
+ We need estimate for #(i-1) key parts.
+ */
+ double f1= key_info->actual_rec_per_key(i-1);
+ double f2= key_info->actual_rec_per_key(i);
+ if (f1 > 0 && f2 > 0)
+ selectivity_mult= f1 / f2;
+ else
+ {
+ /*
+ No statistics available, assume the selectivity is proportional
+ to the number of key parts.
+ (i=0 means 1 keypart, i=1 means 2 keyparts, so use i+1)
+ */
+ selectivity_mult= ((double)(i+1)) / i;
+ }
+ table->cond_selectivity*= selectivity_mult;
+ }
+ /*
+ We need to set selectivity for fields supported by indexes.
+ For single-component indexes and for some first components
+ of other indexes we do it here. For the remaining fields
+ we do it later in this function, in the same way as for the
+ fields not used in any indexes.
+ */
+ if (i == 1)
+ {
+ uint fieldnr= key_info->key_part[0].fieldnr;
+ table->field[fieldnr-1]->cond_selectivity= quick_cond_selectivity;
+ if (i != used_key_parts)
+ table->field[fieldnr-1]->cond_selectivity*= selectivity_mult;
+ bitmap_clear_bit(used_fields, fieldnr-1);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ Second step: calculate the selectivity of the range conditions not
+ supported by any index and selectivity of the range condition
+ over the fields whose selectivity has not been set yet.
+ */
+
+ if (thd->variables.optimizer_use_condition_selectivity > 2 &&
+ !bitmap_is_clear_all(used_fields))
+ {
+ PARAM param;
+ MEM_ROOT alloc;
+ SEL_TREE *tree;
+ SEL_ARG **key, **end;
+ double rows;
+ uint idx= 0;
+
+ init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ param.thd= thd;
+ param.mem_root= &alloc;
+ param.old_root= thd->mem_root;
+ param.table= table;
+ param.is_ror_scan= FALSE;
+
+ if (create_key_parts_for_pseudo_indexes(&param, used_fields))
+ goto free_alloc;
+
+ param.prev_tables= param.read_tables= 0;
+ param.current_table= table->map;
+ param.using_real_indexes= FALSE;
+ param.real_keynr[0]= 0;
+ param.alloced_sel_args= 0;
+
+ thd->no_errors=1;
+
+ tree= get_mm_tree(&param, cond);
+
+ if (!tree)
+ goto free_alloc;
+
+ table->reginfo.impossible_range= 0;
+ if (tree->type == SEL_TREE::IMPOSSIBLE)
+ {
+ rows= 0;
+ table->reginfo.impossible_range= 1;
+ goto free_alloc;
+ }
+ else if (tree->type == SEL_TREE::ALWAYS)
+ {
+ rows= table_records;
+ goto free_alloc;
+ }
+ else if (tree->type == SEL_TREE::MAYBE)
+ {
+ rows= table_records;
+ goto free_alloc;
+ }
+
+ for (key= tree->keys, end= key + param.keys; key != end; key++, idx++)
+ {
+ if (*key)
+ {
+ if ((*key)->type == SEL_ARG::IMPOSSIBLE)
+ {
+ rows= 0;
+ table->reginfo.impossible_range= 1;
+ goto free_alloc;
+ }
+ else
+ {
+ rows= records_in_column_ranges(&param, idx, *key);
+ if (rows != HA_POS_ERROR)
+ (*key)->field->cond_selectivity= rows/table_records;
+ }
+ }
+ }
+
+ for (Field **field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ Field *table_field= *field_ptr;
+ if (bitmap_is_set(used_fields, table_field->field_index) &&
+ table_field->cond_selectivity < 1.0)
+ {
+ if (!bitmap_is_set(&handled_columns, table_field->field_index))
+ table->cond_selectivity*= table_field->cond_selectivity;
+ }
+ }
+
+ free_alloc:
+ thd->mem_root= param.old_root;
+ free_root(&alloc, MYF(0));
+
+ }
+
+ bitmap_union(used_fields, &handled_columns);
+
+ /* Check if we can improve selectivity estimates by using sampling */
+ ulong check_rows=
+ MY_MIN(thd->variables.optimizer_selectivity_sampling_limit,
+ (ulong) (table_records * SELECTIVITY_SAMPLING_SHARE));
+ if (cond && check_rows > SELECTIVITY_SAMPLING_THRESHOLD &&
+ thd->variables.optimizer_use_condition_selectivity > 4)
+ {
+ find_selective_predicates_list_processor_data *dt=
+ (find_selective_predicates_list_processor_data *)
+ alloc_root(thd->mem_root,
+ sizeof(find_selective_predicates_list_processor_data));
+ if (!dt)
+ DBUG_RETURN(TRUE);
+ dt->list.empty();
+ dt->table= table;
+ if (cond->walk(&Item::find_selective_predicates_list_processor, 0,
+ (uchar*) dt))
+ DBUG_RETURN(TRUE);
+ if (dt->list.elements > 0)
+ {
+ check_rows= check_selectivity(thd, check_rows, table, &dt->list);
+ if (check_rows > SELECTIVITY_SAMPLING_THRESHOLD)
+ {
+ COND_STATISTIC *stat;
+ List_iterator_fast<COND_STATISTIC> it(dt->list);
+ double examined_rows= check_rows;
+ while ((stat= it++))
+ {
+ if (!stat->positive)
+ {
+ DBUG_PRINT("info", ("To avoid 0 assigned 1 to the counter"));
+ stat->positive= 1; // avoid 0
+ }
+ DBUG_PRINT("info", ("The predicate selectivity : %g",
+ (double)stat->positive / examined_rows));
+ double selectivity= ((double)stat->positive) / examined_rows;
+ table->cond_selectivity*= selectivity;
+ /*
+ If a field is involved then we register its selectivity in case
+ there in an equality with the field.
+ For example in case
+ t1.a LIKE "%bla%" and t1.a = t2.b
+ the selectivity we have found could be used also for t2.
+ */
+ if (stat->field_arg)
+ {
+ stat->field_arg->cond_selectivity*= selectivity;
+
+ if (stat->field_arg->next_equal_field)
+ {
+ for (Field *next_field= stat->field_arg->next_equal_field;
+ next_field != stat->field_arg;
+ next_field= next_field->next_equal_field)
+ {
+ next_field->cond_selectivity*= selectivity;
+ next_field->table->cond_selectivity*= selectivity;
+ }
+ }
+ }
+ }
+
+ }
+ /* This list and its elements put to mem_root so should not be freed */
+ table->cond_selectivity_sampling_explain= &dt->list;
+ }
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+/****************************************************************************
+ * Condition selectivity code ends
+ ****************************************************************************/
+
+/****************************************************************************
+ * Partition pruning module
+ ****************************************************************************/
+
+/*
+ Store field key image to table record
+
+ SYNOPSIS
+ store_key_image_to_rec()
+ field Field which key image should be stored
+ ptr Field value in key format
+ len Length of the value, in bytes
+
+ ATTENTION
+ len is the length of the value not counting the NULL-byte (at the same
+ time, ptr points to the key image, which starts with NULL-byte for
+ nullable columns)
+
+ DESCRIPTION
+ Copy the field value from its key image to the table record. The source
+ is the value in key image format, occupying len bytes in buffer pointed
+ by ptr. The destination is table record, in "field value in table record"
+ format.
+*/
+
+void store_key_image_to_rec(Field *field, uchar *ptr, uint len)
+{
+ /* Do the same as print_key() does */
+ my_bitmap_map *old_map;
+
+ if (field->real_maybe_null())
+ {
+ if (*ptr)
+ {
+ field->set_null();
+ return;
+ }
+ field->set_notnull();
+ ptr++;
+ }
+ old_map= dbug_tmp_use_all_columns(field->table,
+ field->table->write_set);
+ field->set_key_image(ptr, len);
+ dbug_tmp_restore_column_map(field->table->write_set, old_map);
+}
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+
+/*
+ PartitionPruningModule
+
+ This part of the code does partition pruning. Partition pruning solves the
+ following problem: given a query over partitioned tables, find partitions
+ that we will not need to access (i.e. partitions that we can assume to be
+ empty) when executing the query.
+ The set of partitions to prune doesn't depend on which query execution
+ plan will be used to execute the query.
+
+ HOW IT WORKS
+
+ Partition pruning module makes use of RangeAnalysisModule. The following
+ examples show how the problem of partition pruning can be reduced to the
+ range analysis problem:
+
+ EXAMPLE 1
+ Consider a query:
+
+ SELECT * FROM t1 WHERE (t1.a < 5 OR t1.a = 10) AND t1.a > 3 AND t1.b='z'
+
+ where table t1 is partitioned using PARTITION BY RANGE(t1.a). An apparent
+ way to find the used (i.e. not pruned away) partitions is as follows:
+
+ 1. analyze the WHERE clause and extract the list of intervals over t1.a
+ for the above query we will get this list: {(3 < t1.a < 5), (t1.a=10)}
+
+ 2. for each interval I
+ {
+ find partitions that have non-empty intersection with I;
+ mark them as used;
+ }
+
+ EXAMPLE 2
+ Suppose the table is partitioned by HASH(part_func(t1.a, t1.b)). Then
+ we need to:
+
+ 1. Analyze the WHERE clause and get a list of intervals over (t1.a, t1.b).
+ The list of intervals we'll obtain will look like this:
+ ((t1.a, t1.b) = (1,'foo')),
+ ((t1.a, t1.b) = (2,'bar')),
+ ((t1,a, t1.b) > (10,'zz'))
+
+ 2. for each interval I
+ {
+ if (the interval has form "(t1.a, t1.b) = (const1, const2)" )
+ {
+ calculate HASH(part_func(t1.a, t1.b));
+ find which partition has records with this hash value and mark
+ it as used;
+ }
+ else
+ {
+ mark all partitions as used;
+ break;
+ }
+ }
+
+ For both examples the step #1 is exactly what RangeAnalysisModule could
+ be used to do, if it was provided with appropriate index description
+ (array of KEY_PART structures).
+ In example #1, we need to provide it with description of index(t1.a),
+ in example #2, we need to provide it with description of index(t1.a, t1.b).
+
+ These index descriptions are further called "partitioning index
+ descriptions". Note that it doesn't matter if such indexes really exist,
+ as range analysis module only uses the description.
+
+ Putting it all together, partitioning module works as follows:
+
+ prune_partitions() {
+ call create_partition_index_description();
+
+ call get_mm_tree(); // invoke the RangeAnalysisModule
+
+ // analyze the obtained interval list and get used partitions
+ call find_used_partitions();
+ }
+
+*/
+
+struct st_part_prune_param;
+struct st_part_opt_info;
+
+typedef void (*mark_full_part_func)(partition_info*, uint32);
+
+/*
+ Partition pruning operation context
+*/
+typedef struct st_part_prune_param
+{
+ RANGE_OPT_PARAM range_param; /* Range analyzer parameters */
+
+ /***************************************************************
+ Following fields are filled in based solely on partitioning
+ definition and not modified after that:
+ **************************************************************/
+ partition_info *part_info; /* Copy of table->part_info */
+ /* Function to get partition id from partitioning fields only */
+ get_part_id_func get_top_partition_id_func;
+ /* Function to mark a partition as used (w/all subpartitions if they exist)*/
+ mark_full_part_func mark_full_partition_used;
+
+ /* Partitioning 'index' description, array of key parts */
+ KEY_PART *key;
+
+ /*
+ Number of fields in partitioning 'index' definition created for
+ partitioning (0 if partitioning 'index' doesn't include partitioning
+ fields)
+ */
+ uint part_fields;
+ uint subpart_fields; /* Same as above for subpartitioning */
+
+ /*
+ Number of the last partitioning field keypart in the index, or -1 if
+ partitioning index definition doesn't include partitioning fields.
+ */
+ int last_part_partno;
+ int last_subpart_partno; /* Same as above for supartitioning */
+
+ /*
+ is_part_keypart[i] == MY_TEST(keypart #i in partitioning index is a member
+ used in partitioning)
+ Used to maintain current values of cur_part_fields and cur_subpart_fields
+ */
+ my_bool *is_part_keypart;
+ /* Same as above for subpartitioning */
+ my_bool *is_subpart_keypart;
+
+ my_bool ignore_part_fields; /* Ignore rest of partioning fields */
+
+ /***************************************************************
+ Following fields form find_used_partitions() recursion context:
+ **************************************************************/
+ SEL_ARG **arg_stack; /* "Stack" of SEL_ARGs */
+ SEL_ARG **arg_stack_end; /* Top of the stack */
+ /* Number of partitioning fields for which we have a SEL_ARG* in arg_stack */
+ uint cur_part_fields;
+ /* Same as cur_part_fields, but for subpartitioning */
+ uint cur_subpart_fields;
+
+ /* Iterator to be used to obtain the "current" set of used partitions */
+ PARTITION_ITERATOR part_iter;
+
+ /* Initialized bitmap of num_subparts size */
+ MY_BITMAP subparts_bitmap;
+
+ uchar *cur_min_key;
+ uchar *cur_max_key;
+
+ uint cur_min_flag, cur_max_flag;
+} PART_PRUNE_PARAM;
+
+static bool create_partition_index_description(PART_PRUNE_PARAM *prune_par);
+static int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree);
+static int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar,
+ SEL_IMERGE *imerge);
+static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar,
+ List<SEL_IMERGE> &merges);
+static void mark_all_partitions_as_used(partition_info *part_info);
+
+#ifndef DBUG_OFF
+static void print_partitioning_index(KEY_PART *parts, KEY_PART *parts_end);
+static void dbug_print_field(Field *field);
+static void dbug_print_segment_range(SEL_ARG *arg, KEY_PART *part);
+static void dbug_print_singlepoint_range(SEL_ARG **start, uint num);
+#endif
+
+
+/**
+ Perform partition pruning for a given table and condition.
+
+ @param thd Thread handle
+ @param table Table to perform partition pruning for
+ @param pprune_cond Condition to use for partition pruning
+
+ @note This function assumes that lock_partitions are setup when it
+ is invoked. The function analyzes the condition, finds partitions that
+ need to be used to retrieve the records that match the condition, and
+ marks them as used by setting appropriate bit in part_info->read_partitions
+ In the worst case all partitions are marked as used. If the table is not
+ yet locked, it will also unset bits in part_info->lock_partitions that is
+ not set in read_partitions.
+
+ This function returns promptly if called for non-partitioned table.
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond)
+{
+ bool retval= FALSE;
+ partition_info *part_info = table->part_info;
+ DBUG_ENTER("prune_partitions");
+
+ if (!part_info)
+ DBUG_RETURN(FALSE); /* not a partitioned table */
+
+ if (!pprune_cond)
+ {
+ mark_all_partitions_as_used(part_info);
+ DBUG_RETURN(FALSE);
+ }
+
+ PART_PRUNE_PARAM prune_param;
+ MEM_ROOT alloc;
+ RANGE_OPT_PARAM *range_par= &prune_param.range_param;
+ my_bitmap_map *old_sets[2];
+
+ prune_param.part_info= part_info;
+ init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ range_par->mem_root= &alloc;
+ range_par->old_root= thd->mem_root;
+
+ if (create_partition_index_description(&prune_param))
+ {
+ mark_all_partitions_as_used(part_info);
+ free_root(&alloc,MYF(0)); // Return memory & allocator
+ DBUG_RETURN(FALSE);
+ }
+
+ dbug_tmp_use_all_columns(table, old_sets,
+ table->read_set, table->write_set);
+ range_par->thd= thd;
+ range_par->table= table;
+ /* range_par->cond doesn't need initialization */
+ range_par->prev_tables= range_par->read_tables= 0;
+ range_par->current_table= table->map;
+
+ range_par->keys= 1; // one index
+ range_par->using_real_indexes= FALSE;
+ range_par->remove_jump_scans= FALSE;
+ range_par->real_keynr[0]= 0;
+ range_par->alloced_sel_args= 0;
+
+ thd->no_errors=1; // Don't warn about NULL
+ thd->mem_root=&alloc;
+
+ bitmap_clear_all(&part_info->read_partitions);
+
+ prune_param.key= prune_param.range_param.key_parts;
+ SEL_TREE *tree;
+ int res;
+
+ tree= get_mm_tree(range_par, pprune_cond);
+ if (!tree)
+ goto all_used;
+
+ if (tree->type == SEL_TREE::IMPOSSIBLE)
+ {
+ retval= TRUE;
+ goto end;
+ }
+
+ if (tree->type != SEL_TREE::KEY && tree->type != SEL_TREE::KEY_SMALLER)
+ goto all_used;
+
+ if (tree->merges.is_empty())
+ {
+ /* Range analysis has produced a single list of intervals. */
+ prune_param.arg_stack_end= prune_param.arg_stack;
+ prune_param.cur_part_fields= 0;
+ prune_param.cur_subpart_fields= 0;
+
+ prune_param.cur_min_key= prune_param.range_param.min_key;
+ prune_param.cur_max_key= prune_param.range_param.max_key;
+ prune_param.cur_min_flag= prune_param.cur_max_flag= 0;
+
+ init_all_partitions_iterator(part_info, &prune_param.part_iter);
+ if (!tree->keys[0] || (-1 == (res= find_used_partitions(&prune_param,
+ tree->keys[0]))))
+ goto all_used;
+ }
+ else
+ {
+ if (tree->merges.elements == 1)
+ {
+ /*
+ Range analysis has produced a "merge" of several intervals lists, a
+ SEL_TREE that represents an expression in form
+ sel_imerge = (tree1 OR tree2 OR ... OR treeN)
+ that cannot be reduced to one tree. This can only happen when
+ partitioning index has several keyparts and the condition is OR of
+ conditions that refer to different key parts. For example, we'll get
+ here for "partitioning_field=const1 OR subpartitioning_field=const2"
+ */
+ if (-1 == (res= find_used_partitions_imerge(&prune_param,
+ tree->merges.head())))
+ goto all_used;
+ }
+ else
+ {
+ /*
+ Range analysis has produced a list of several imerges, i.e. a
+ structure that represents a condition in form
+ imerge_list= (sel_imerge1 AND sel_imerge2 AND ... AND sel_imergeN)
+ This is produced for complicated WHERE clauses that range analyzer
+ can't really analyze properly.
+ */
+ if (-1 == (res= find_used_partitions_imerge_list(&prune_param,
+ tree->merges)))
+ goto all_used;
+ }
+ }
+
+ /*
+ res == 0 => no used partitions => retval=TRUE
+ res == 1 => some used partitions => retval=FALSE
+ res == -1 - we jump over this line to all_used:
+ */
+ retval= MY_TEST(!res);
+ goto end;
+
+all_used:
+ retval= FALSE; // some partitions are used
+ mark_all_partitions_as_used(prune_param.part_info);
+end:
+ dbug_tmp_restore_column_maps(table->read_set, table->write_set, old_sets);
+ thd->no_errors=0;
+ thd->mem_root= range_par->old_root;
+ free_root(&alloc,MYF(0)); // Return memory & allocator
+ /*
+ Must be a subset of the locked partitions.
+ lock_partitions contains the partitions marked by explicit partition
+ selection (... t PARTITION (pX) ...) and we must only use partitions
+ within that set.
+ */
+ bitmap_intersect(&prune_param.part_info->read_partitions,
+ &prune_param.part_info->lock_partitions);
+ /*
+ If not yet locked, also prune partitions to lock if not UPDATEing
+ partition key fields. This will also prune lock_partitions if we are under
+ LOCK TABLES, so prune away calls to start_stmt().
+ TODO: enhance this prune locking to also allow pruning of
+ 'UPDATE t SET part_key = const WHERE cond_is_prunable' so it adds
+ a lock for part_key partition.
+ */
+ if (table->file->get_lock_type() == F_UNLCK &&
+ !partition_key_modified(table, table->write_set))
+ {
+ bitmap_copy(&prune_param.part_info->lock_partitions,
+ &prune_param.part_info->read_partitions);
+ }
+ if (bitmap_is_clear_all(&(prune_param.part_info->read_partitions)))
+ {
+ table->all_partitions_pruned_away= true;
+ retval= TRUE;
+ }
+ DBUG_RETURN(retval);
+}
+
+
+/*
+ For SEL_ARG* array, store sel_arg->min values into table record buffer
+
+ SYNOPSIS
+ store_selargs_to_rec()
+ ppar Partition pruning context
+ start Array of SEL_ARG* for which the minimum values should be stored
+ num Number of elements in the array
+
+ DESCRIPTION
+ For each SEL_ARG* interval in the specified array, store the left edge
+ field value (sel_arg->min, key image format) into the table record.
+*/
+
+static void store_selargs_to_rec(PART_PRUNE_PARAM *ppar, SEL_ARG **start,
+ int num)
+{
+ KEY_PART *parts= ppar->range_param.key_parts;
+ for (SEL_ARG **end= start + num; start != end; start++)
+ {
+ SEL_ARG *sel_arg= (*start);
+ store_key_image_to_rec(sel_arg->field, sel_arg->min_value,
+ parts[sel_arg->part].length);
+ }
+}
+
+
+/* Mark a partition as used in the case when there are no subpartitions */
+static void mark_full_partition_used_no_parts(partition_info* part_info,
+ uint32 part_id)
+{
+ DBUG_ENTER("mark_full_partition_used_no_parts");
+ DBUG_PRINT("enter", ("Mark partition %u as used", part_id));
+ bitmap_set_bit(&part_info->read_partitions, part_id);
+ DBUG_VOID_RETURN;
+}
+
+
+/* Mark a partition as used in the case when there are subpartitions */
+static void mark_full_partition_used_with_parts(partition_info *part_info,
+ uint32 part_id)
+{
+ uint32 start= part_id * part_info->num_subparts;
+ uint32 end= start + part_info->num_subparts;
+ DBUG_ENTER("mark_full_partition_used_with_parts");
+
+ for (; start != end; start++)
+ {
+ DBUG_PRINT("info", ("1:Mark subpartition %u as used", start));
+ bitmap_set_bit(&part_info->read_partitions, start);
+ }
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Find the set of used partitions for List<SEL_IMERGE>
+ SYNOPSIS
+ find_used_partitions_imerge_list
+ ppar Partition pruning context.
+ key_tree Intervals tree to perform pruning for.
+
+ DESCRIPTION
+ List<SEL_IMERGE> represents "imerge1 AND imerge2 AND ...".
+ The set of used partitions is an intersection of used partitions sets
+ for imerge_{i}.
+ We accumulate this intersection in a separate bitmap.
+
+ RETURN
+ See find_used_partitions()
+*/
+
+static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar,
+ List<SEL_IMERGE> &merges)
+{
+ MY_BITMAP all_merges;
+ uint bitmap_bytes;
+ my_bitmap_map *bitmap_buf;
+ uint n_bits= ppar->part_info->read_partitions.n_bits;
+ bitmap_bytes= bitmap_buffer_size(n_bits);
+ if (!(bitmap_buf= (my_bitmap_map*) alloc_root(ppar->range_param.mem_root,
+ bitmap_bytes)))
+ {
+ /*
+ Fallback, process just the first SEL_IMERGE. This can leave us with more
+ partitions marked as used then actually needed.
+ */
+ return find_used_partitions_imerge(ppar, merges.head());
+ }
+ my_bitmap_init(&all_merges, bitmap_buf, n_bits, FALSE);
+ bitmap_set_prefix(&all_merges, n_bits);
+
+ List_iterator<SEL_IMERGE> it(merges);
+ SEL_IMERGE *imerge;
+ while ((imerge=it++))
+ {
+ int res= find_used_partitions_imerge(ppar, imerge);
+ if (!res)
+ {
+ /* no used partitions on one ANDed imerge => no used partitions at all */
+ return 0;
+ }
+
+ if (res != -1)
+ bitmap_intersect(&all_merges, &ppar->part_info->read_partitions);
+
+
+ if (bitmap_is_clear_all(&all_merges))
+ return 0;
+
+ bitmap_clear_all(&ppar->part_info->read_partitions);
+ }
+ memcpy(ppar->part_info->read_partitions.bitmap, all_merges.bitmap,
+ bitmap_bytes);
+ return 1;
+}
+
+
+/*
+ Find the set of used partitions for SEL_IMERGE structure
+ SYNOPSIS
+ find_used_partitions_imerge()
+ ppar Partition pruning context.
+ key_tree Intervals tree to perform pruning for.
+
+ DESCRIPTION
+ SEL_IMERGE represents "tree1 OR tree2 OR ...". The implementation is
+ trivial - just use mark used partitions for each tree and bail out early
+ if for some tree_{i} all partitions are used.
+
+ RETURN
+ See find_used_partitions().
+*/
+
+static
+int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge)
+{
+ int res= 0;
+ for (SEL_TREE **ptree= imerge->trees; ptree < imerge->trees_next; ptree++)
+ {
+ ppar->arg_stack_end= ppar->arg_stack;
+ ppar->cur_part_fields= 0;
+ ppar->cur_subpart_fields= 0;
+
+ ppar->cur_min_key= ppar->range_param.min_key;
+ ppar->cur_max_key= ppar->range_param.max_key;
+ ppar->cur_min_flag= ppar->cur_max_flag= 0;
+
+ init_all_partitions_iterator(ppar->part_info, &ppar->part_iter);
+ SEL_ARG *key_tree= (*ptree)->keys[0];
+ if (!key_tree || (-1 == (res |= find_used_partitions(ppar, key_tree))))
+ return -1;
+ }
+ return res;
+}
+
+
+/*
+ Collect partitioning ranges for the SEL_ARG tree and mark partitions as used
+
+ SYNOPSIS
+ find_used_partitions()
+ ppar Partition pruning context.
+ key_tree SEL_ARG range tree to perform pruning for
+
+ DESCRIPTION
+ This function
+ * recursively walks the SEL_ARG* tree collecting partitioning "intervals"
+ * finds the partitions one needs to use to get rows in these intervals
+ * marks these partitions as used.
+ The next session desribes the process in greater detail.
+
+ IMPLEMENTATION
+ TYPES OF RESTRICTIONS THAT WE CAN OBTAIN PARTITIONS FOR
+ We can find out which [sub]partitions to use if we obtain restrictions on
+ [sub]partitioning fields in the following form:
+ 1. "partition_field1=const1 AND ... AND partition_fieldN=constN"
+ 1.1 Same as (1) but for subpartition fields
+
+ If partitioning supports interval analysis (i.e. partitioning is a
+ function of a single table field, and partition_info::
+ get_part_iter_for_interval != NULL), then we can also use condition in
+ this form:
+ 2. "const1 <=? partition_field <=? const2"
+ 2.1 Same as (2) but for subpartition_field
+
+ INFERRING THE RESTRICTIONS FROM SEL_ARG TREE
+
+ The below is an example of what SEL_ARG tree may represent:
+
+ (start)
+ | $
+ | Partitioning keyparts $ subpartitioning keyparts
+ | $
+ | ... ... $
+ | | | $
+ | +---------+ +---------+ $ +-----------+ +-----------+
+ \-| par1=c1 |--| par2=c2 |-----| subpar1=c3|--| subpar2=c5|
+ +---------+ +---------+ $ +-----------+ +-----------+
+ | $ | |
+ | $ | +-----------+
+ | $ | | subpar2=c6|
+ | $ | +-----------+
+ | $ |
+ | $ +-----------+ +-----------+
+ | $ | subpar1=c4|--| subpar2=c8|
+ | $ +-----------+ +-----------+
+ | $
+ | $
+ +---------+ $ +------------+ +------------+
+ | par1=c2 |------------------| subpar1=c10|--| subpar2=c12|
+ +---------+ $ +------------+ +------------+
+ | $
+ ... $
+
+ The up-down connections are connections via SEL_ARG::left and
+ SEL_ARG::right. A horizontal connection to the right is the
+ SEL_ARG::next_key_part connection.
+
+ find_used_partitions() traverses the entire tree via recursion on
+ * SEL_ARG::next_key_part (from left to right on the picture)
+ * SEL_ARG::left|right (up/down on the pic). Left-right recursion is
+ performed for each depth level.
+
+ Recursion descent on SEL_ARG::next_key_part is used to accumulate (in
+ ppar->arg_stack) constraints on partitioning and subpartitioning fields.
+ For the example in the above picture, one of stack states is:
+ in find_used_partitions(key_tree = "subpar2=c5") (***)
+ in find_used_partitions(key_tree = "subpar1=c3")
+ in find_used_partitions(key_tree = "par2=c2") (**)
+ in find_used_partitions(key_tree = "par1=c1")
+ in prune_partitions(...)
+ We apply partitioning limits as soon as possible, e.g. when we reach the
+ depth (**), we find which partition(s) correspond to "par1=c1 AND par2=c2",
+ and save them in ppar->part_iter.
+ When we reach the depth (***), we find which subpartition(s) correspond to
+ "subpar1=c3 AND subpar2=c5", and then mark appropriate subpartitions in
+ appropriate subpartitions as used.
+
+ It is possible that constraints on some partitioning fields are missing.
+ For the above example, consider this stack state:
+ in find_used_partitions(key_tree = "subpar2=c12") (***)
+ in find_used_partitions(key_tree = "subpar1=c10")
+ in find_used_partitions(key_tree = "par1=c2")
+ in prune_partitions(...)
+ Here we don't have constraints for all partitioning fields. Since we've
+ never set the ppar->part_iter to contain used set of partitions, we use
+ its default "all partitions" value. We get subpartition id for
+ "subpar1=c3 AND subpar2=c5", and mark that subpartition as used in every
+ partition.
+
+ The inverse is also possible: we may get constraints on partitioning
+ fields, but not constraints on subpartitioning fields. In that case,
+ calls to find_used_partitions() with depth below (**) will return -1,
+ and we will mark entire partition as used.
+
+ TODO
+ Replace recursion on SEL_ARG::left and SEL_ARG::right with a loop
+
+ RETURN
+ 1 OK, one or more [sub]partitions are marked as used.
+ 0 The passed condition doesn't match any partitions
+ -1 Couldn't infer any partition pruning "intervals" from the passed
+ SEL_ARG* tree (which means that all partitions should be marked as
+ used) Marking partitions as used is the responsibility of the caller.
+*/
+
+static
+int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree)
+{
+ int res, left_res=0, right_res=0;
+ int key_tree_part= (int)key_tree->part;
+ bool set_full_part_if_bad_ret= FALSE;
+ bool ignore_part_fields= ppar->ignore_part_fields;
+ bool did_set_ignore_part_fields= FALSE;
+ RANGE_OPT_PARAM *range_par= &(ppar->range_param);
+
+ if (check_stack_overrun(range_par->thd, 3*STACK_MIN_SIZE, NULL))
+ return -1;
+
+ if (key_tree->left != &null_element)
+ {
+ if (-1 == (left_res= find_used_partitions(ppar,key_tree->left)))
+ return -1;
+ }
+
+ /* Push SEL_ARG's to stack to enable looking backwards as well */
+ ppar->cur_part_fields+= ppar->is_part_keypart[key_tree_part];
+ ppar->cur_subpart_fields+= ppar->is_subpart_keypart[key_tree_part];
+ *(ppar->arg_stack_end++)= key_tree;
+
+ if (ignore_part_fields)
+ {
+ /*
+ We come here when a condition on the first partitioning
+ fields led to evaluating the partitioning condition
+ (due to finding a condition of the type a < const or
+ b > const). Thus we must ignore the rest of the
+ partitioning fields but we still want to analyse the
+ subpartitioning fields.
+ */
+ if (key_tree->next_key_part)
+ res= find_used_partitions(ppar, key_tree->next_key_part);
+ else
+ res= -1;
+ goto pop_and_go_right;
+ }
+
+ if (key_tree->type == SEL_ARG::KEY_RANGE)
+ {
+ if (ppar->part_info->get_part_iter_for_interval &&
+ key_tree->part <= ppar->last_part_partno)
+ {
+ /* Collect left and right bound, their lengths and flags */
+ uchar *min_key= ppar->cur_min_key;
+ uchar *max_key= ppar->cur_max_key;
+ uchar *tmp_min_key= min_key;
+ uchar *tmp_max_key= max_key;
+ key_tree->store_min(ppar->key[key_tree->part].store_length,
+ &tmp_min_key, ppar->cur_min_flag);
+ key_tree->store_max(ppar->key[key_tree->part].store_length,
+ &tmp_max_key, ppar->cur_max_flag);
+ uint flag;
+ if (key_tree->next_key_part &&
+ key_tree->next_key_part->part == key_tree->part+1 &&
+ key_tree->next_key_part->part <= ppar->last_part_partno &&
+ key_tree->next_key_part->type == SEL_ARG::KEY_RANGE)
+ {
+ /*
+ There are more key parts for partition pruning to handle
+ This mainly happens when the condition is an equality
+ condition.
+ */
+ if ((tmp_min_key - min_key) == (tmp_max_key - max_key) &&
+ (memcmp(min_key, max_key, (uint)(tmp_max_key - max_key)) == 0) &&
+ !key_tree->min_flag && !key_tree->max_flag)
+ {
+ /* Set 'parameters' */
+ ppar->cur_min_key= tmp_min_key;
+ ppar->cur_max_key= tmp_max_key;
+ uint save_min_flag= ppar->cur_min_flag;
+ uint save_max_flag= ppar->cur_max_flag;
+
+ ppar->cur_min_flag|= key_tree->min_flag;
+ ppar->cur_max_flag|= key_tree->max_flag;
+
+ res= find_used_partitions(ppar, key_tree->next_key_part);
+
+ /* Restore 'parameters' back */
+ ppar->cur_min_key= min_key;
+ ppar->cur_max_key= max_key;
+
+ ppar->cur_min_flag= save_min_flag;
+ ppar->cur_max_flag= save_max_flag;
+ goto pop_and_go_right;
+ }
+ /* We have arrived at the last field in the partition pruning */
+ uint tmp_min_flag= key_tree->min_flag,
+ tmp_max_flag= key_tree->max_flag;
+ if (!tmp_min_flag)
+ key_tree->next_key_part->store_min_key(ppar->key,
+ &tmp_min_key,
+ &tmp_min_flag,
+ ppar->last_part_partno);
+ if (!tmp_max_flag)
+ key_tree->next_key_part->store_max_key(ppar->key,
+ &tmp_max_key,
+ &tmp_max_flag,
+ ppar->last_part_partno);
+ flag= tmp_min_flag | tmp_max_flag;
+ }
+ else
+ flag= key_tree->min_flag | key_tree->max_flag;
+
+ if (tmp_min_key != range_par->min_key)
+ flag&= ~NO_MIN_RANGE;
+ else
+ flag|= NO_MIN_RANGE;
+ if (tmp_max_key != range_par->max_key)
+ flag&= ~NO_MAX_RANGE;
+ else
+ flag|= NO_MAX_RANGE;
+
+ /*
+ We need to call the interval mapper if we have a condition which
+ makes sense to prune on. In the example of COLUMNS on a and
+ b it makes sense if we have a condition on a, or conditions on
+ both a and b. If we only have conditions on b it might make sense
+ but this is a harder case we will solve later. For the harder case
+ this clause then turns into use of all partitions and thus we
+ simply set res= -1 as if the mapper had returned that.
+ TODO: What to do here is defined in WL#4065.
+ */
+ if (ppar->arg_stack[0]->part == 0)
+ {
+ uint32 i;
+ uint32 store_length_array[MAX_KEY];
+ uint32 num_keys= ppar->part_fields;
+
+ for (i= 0; i < num_keys; i++)
+ store_length_array[i]= ppar->key[i].store_length;
+ res= ppar->part_info->
+ get_part_iter_for_interval(ppar->part_info,
+ FALSE,
+ store_length_array,
+ range_par->min_key,
+ range_par->max_key,
+ tmp_min_key - range_par->min_key,
+ tmp_max_key - range_par->max_key,
+ flag,
+ &ppar->part_iter);
+ if (!res)
+ goto pop_and_go_right; /* res==0 --> no satisfying partitions */
+ }
+ else
+ res= -1;
+
+ if (res == -1)
+ {
+ /* get a full range iterator */
+ init_all_partitions_iterator(ppar->part_info, &ppar->part_iter);
+ }
+ /*
+ Save our intent to mark full partition as used if we will not be able
+ to obtain further limits on subpartitions
+ */
+ if (key_tree_part < ppar->last_part_partno)
+ {
+ /*
+ We need to ignore the rest of the partitioning fields in all
+ evaluations after this
+ */
+ did_set_ignore_part_fields= TRUE;
+ ppar->ignore_part_fields= TRUE;
+ }
+ set_full_part_if_bad_ret= TRUE;
+ goto process_next_key_part;
+ }
+
+ if (key_tree_part == ppar->last_subpart_partno &&
+ (NULL != ppar->part_info->get_subpart_iter_for_interval))
+ {
+ PARTITION_ITERATOR subpart_iter;
+ DBUG_EXECUTE("info", dbug_print_segment_range(key_tree,
+ range_par->key_parts););
+ res= ppar->part_info->
+ get_subpart_iter_for_interval(ppar->part_info,
+ TRUE,
+ NULL, /* Currently not used here */
+ key_tree->min_value,
+ key_tree->max_value,
+ 0, 0, /* Those are ignored here */
+ key_tree->min_flag |
+ key_tree->max_flag,
+ &subpart_iter);
+ DBUG_ASSERT(res); /* We can't get "no satisfying subpartitions" */
+ if (res == -1)
+ goto pop_and_go_right; /* all subpartitions satisfy */
+
+ uint32 subpart_id;
+ bitmap_clear_all(&ppar->subparts_bitmap);
+ while ((subpart_id= subpart_iter.get_next(&subpart_iter)) !=
+ NOT_A_PARTITION_ID)
+ bitmap_set_bit(&ppar->subparts_bitmap, subpart_id);
+
+ /* Mark each partition as used in each subpartition. */
+ uint32 part_id;
+ while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) !=
+ NOT_A_PARTITION_ID)
+ {
+ for (uint i= 0; i < ppar->part_info->num_subparts; i++)
+ if (bitmap_is_set(&ppar->subparts_bitmap, i))
+ bitmap_set_bit(&ppar->part_info->read_partitions,
+ part_id * ppar->part_info->num_subparts + i);
+ }
+ goto pop_and_go_right;
+ }
+
+ if (key_tree->is_singlepoint())
+ {
+ if (key_tree_part == ppar->last_part_partno &&
+ ppar->cur_part_fields == ppar->part_fields &&
+ ppar->part_info->get_part_iter_for_interval == NULL)
+ {
+ /*
+ Ok, we've got "fieldN<=>constN"-type SEL_ARGs for all partitioning
+ fields. Save all constN constants into table record buffer.
+ */
+ store_selargs_to_rec(ppar, ppar->arg_stack, ppar->part_fields);
+ DBUG_EXECUTE("info", dbug_print_singlepoint_range(ppar->arg_stack,
+ ppar->part_fields););
+ uint32 part_id;
+ longlong func_value;
+ /* Find in which partition the {const1, ...,constN} tuple goes */
+ if (ppar->get_top_partition_id_func(ppar->part_info, &part_id,
+ &func_value))
+ {
+ res= 0; /* No satisfying partitions */
+ goto pop_and_go_right;
+ }
+ /* Rembember the limit we got - single partition #part_id */
+ init_single_partition_iterator(part_id, &ppar->part_iter);
+
+ /*
+ If there are no subpartitions/we fail to get any limit for them,
+ then we'll mark full partition as used.
+ */
+ set_full_part_if_bad_ret= TRUE;
+ goto process_next_key_part;
+ }
+
+ if (key_tree_part == ppar->last_subpart_partno &&
+ ppar->cur_subpart_fields == ppar->subpart_fields)
+ {
+ /*
+ Ok, we've got "fieldN<=>constN"-type SEL_ARGs for all subpartitioning
+ fields. Save all constN constants into table record buffer.
+ */
+ store_selargs_to_rec(ppar, ppar->arg_stack_end - ppar->subpart_fields,
+ ppar->subpart_fields);
+ DBUG_EXECUTE("info", dbug_print_singlepoint_range(ppar->arg_stack_end-
+ ppar->subpart_fields,
+ ppar->subpart_fields););
+ /* Find the subpartition (it's HASH/KEY so we always have one) */
+ partition_info *part_info= ppar->part_info;
+ uint32 part_id, subpart_id;
+
+ if (part_info->get_subpartition_id(part_info, &subpart_id))
+ return 0;
+
+ /* Mark this partition as used in each subpartition. */
+ while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) !=
+ NOT_A_PARTITION_ID)
+ {
+ bitmap_set_bit(&part_info->read_partitions,
+ part_id * part_info->num_subparts + subpart_id);
+ }
+ res= 1; /* Some partitions were marked as used */
+ goto pop_and_go_right;
+ }
+ }
+ else
+ {
+ /*
+ Can't handle condition on current key part. If we're that deep that
+ we're processing subpartititoning's key parts, this means we'll not be
+ able to infer any suitable condition, so bail out.
+ */
+ if (key_tree_part >= ppar->last_part_partno)
+ {
+ res= -1;
+ goto pop_and_go_right;
+ }
+ /*
+ No meaning in continuing with rest of partitioning key parts.
+ Will try to continue with subpartitioning key parts.
+ */
+ ppar->ignore_part_fields= true;
+ did_set_ignore_part_fields= true;
+ goto process_next_key_part;
+ }
+ }
+
+process_next_key_part:
+ if (key_tree->next_key_part)
+ res= find_used_partitions(ppar, key_tree->next_key_part);
+ else
+ res= -1;
+
+ if (did_set_ignore_part_fields)
+ {
+ /*
+ We have returned from processing all key trees linked to our next
+ key part. We are ready to be moving down (using right pointers) and
+ this tree is a new evaluation requiring its own decision on whether
+ to ignore partitioning fields.
+ */
+ ppar->ignore_part_fields= FALSE;
+ }
+ if (set_full_part_if_bad_ret)
+ {
+ if (res == -1)
+ {
+ /* Got "full range" for subpartitioning fields */
+ uint32 part_id;
+ bool found= FALSE;
+ while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) !=
+ NOT_A_PARTITION_ID)
+ {
+ ppar->mark_full_partition_used(ppar->part_info, part_id);
+ found= TRUE;
+ }
+ res= MY_TEST(found);
+ }
+ /*
+ Restore the "used partitions iterator" to the default setting that
+ specifies iteration over all partitions.
+ */
+ init_all_partitions_iterator(ppar->part_info, &ppar->part_iter);
+ }
+
+pop_and_go_right:
+ /* Pop this key part info off the "stack" */
+ ppar->arg_stack_end--;
+ ppar->cur_part_fields-= ppar->is_part_keypart[key_tree_part];
+ ppar->cur_subpart_fields-= ppar->is_subpart_keypart[key_tree_part];
+
+ if (res == -1)
+ return -1;
+ if (key_tree->right != &null_element)
+ {
+ if (-1 == (right_res= find_used_partitions(ppar,key_tree->right)))
+ return -1;
+ }
+ return (left_res || right_res || res);
+}
+
+
+static void mark_all_partitions_as_used(partition_info *part_info)
+{
+ bitmap_copy(&(part_info->read_partitions),
+ &(part_info->lock_partitions));
+}
+
+
+/*
+ Check if field types allow to construct partitioning index description
+
+ SYNOPSIS
+ fields_ok_for_partition_index()
+ pfield NULL-terminated array of pointers to fields.
+
+ DESCRIPTION
+ For an array of fields, check if we can use all of the fields to create
+ partitioning index description.
+
+ We can't process GEOMETRY fields - for these fields singlepoint intervals
+ cant be generated, and non-singlepoint are "special" kinds of intervals
+ to which our processing logic can't be applied.
+
+ It is not known if we could process ENUM fields, so they are disabled to be
+ on the safe side.
+
+ RETURN
+ TRUE Yes, fields can be used in partitioning index
+ FALSE Otherwise
+*/
+
+static bool fields_ok_for_partition_index(Field **pfield)
+{
+ if (!pfield)
+ return FALSE;
+ for (; (*pfield); pfield++)
+ {
+ enum_field_types ftype= (*pfield)->real_type();
+ if (ftype == MYSQL_TYPE_ENUM || ftype == MYSQL_TYPE_GEOMETRY)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/*
+ Create partition index description and fill related info in the context
+ struct
+
+ SYNOPSIS
+ create_partition_index_description()
+ prune_par INOUT Partition pruning context
+
+ DESCRIPTION
+ Create partition index description. Partition index description is:
+
+ part_index(used_fields_list(part_expr), used_fields_list(subpart_expr))
+
+ If partitioning/sub-partitioning uses BLOB or Geometry fields, then
+ corresponding fields_list(...) is not included into index description
+ and we don't perform partition pruning for partitions/subpartitions.
+
+ RETURN
+ TRUE Out of memory or can't do partition pruning at all
+ FALSE OK
+*/
+
+static bool create_partition_index_description(PART_PRUNE_PARAM *ppar)
+{
+ RANGE_OPT_PARAM *range_par= &(ppar->range_param);
+ partition_info *part_info= ppar->part_info;
+ uint used_part_fields, used_subpart_fields;
+
+ used_part_fields= fields_ok_for_partition_index(part_info->part_field_array) ?
+ part_info->num_part_fields : 0;
+ used_subpart_fields=
+ fields_ok_for_partition_index(part_info->subpart_field_array)?
+ part_info->num_subpart_fields : 0;
+
+ uint total_parts= used_part_fields + used_subpart_fields;
+
+ ppar->ignore_part_fields= FALSE;
+ ppar->part_fields= used_part_fields;
+ ppar->last_part_partno= (int)used_part_fields - 1;
+
+ ppar->subpart_fields= used_subpart_fields;
+ ppar->last_subpart_partno=
+ used_subpart_fields?(int)(used_part_fields + used_subpart_fields - 1): -1;
+
+ if (part_info->is_sub_partitioned())
+ {
+ ppar->mark_full_partition_used= mark_full_partition_used_with_parts;
+ ppar->get_top_partition_id_func= part_info->get_part_partition_id;
+ }
+ else
+ {
+ ppar->mark_full_partition_used= mark_full_partition_used_no_parts;
+ ppar->get_top_partition_id_func= part_info->get_partition_id;
+ }
+
+ KEY_PART *key_part;
+ MEM_ROOT *alloc= range_par->mem_root;
+ if (!total_parts ||
+ !(key_part= (KEY_PART*)alloc_root(alloc, sizeof(KEY_PART)*
+ total_parts)) ||
+ !(ppar->arg_stack= (SEL_ARG**)alloc_root(alloc, sizeof(SEL_ARG*)*
+ total_parts)) ||
+ !(ppar->is_part_keypart= (my_bool*)alloc_root(alloc, sizeof(my_bool)*
+ total_parts)) ||
+ !(ppar->is_subpart_keypart= (my_bool*)alloc_root(alloc, sizeof(my_bool)*
+ total_parts)))
+ return TRUE;
+
+ if (ppar->subpart_fields)
+ {
+ my_bitmap_map *buf;
+ uint32 bufsize= bitmap_buffer_size(ppar->part_info->num_subparts);
+ if (!(buf= (my_bitmap_map*) alloc_root(alloc, bufsize)))
+ return TRUE;
+ my_bitmap_init(&ppar->subparts_bitmap, buf, ppar->part_info->num_subparts,
+ FALSE);
+ }
+ range_par->key_parts= key_part;
+ Field **field= (ppar->part_fields)? part_info->part_field_array :
+ part_info->subpart_field_array;
+ bool in_subpart_fields= FALSE;
+ for (uint part= 0; part < total_parts; part++, key_part++)
+ {
+ key_part->key= 0;
+ key_part->part= part;
+ key_part->length= (uint16)(*field)->key_length();
+ key_part->store_length= (uint16)get_partition_field_store_length(*field);
+
+ DBUG_PRINT("info", ("part %u length %u store_length %u", part,
+ key_part->length, key_part->store_length));
+
+ key_part->field= (*field);
+ key_part->image_type = Field::itRAW;
+ /*
+ We set keypart flag to 0 here as the only HA_PART_KEY_SEG is checked
+ in the RangeAnalysisModule.
+ */
+ key_part->flag= 0;
+ /* We don't set key_parts->null_bit as it will not be used */
+
+ ppar->is_part_keypart[part]= !in_subpart_fields;
+ ppar->is_subpart_keypart[part]= in_subpart_fields;
+
+ /*
+ Check if this was last field in this array, in this case we
+ switch to subpartitioning fields. (This will only happens if
+ there are subpartitioning fields to cater for).
+ */
+ if (!*(++field))
+ {
+ field= part_info->subpart_field_array;
+ in_subpart_fields= TRUE;
+ }
+ }
+ range_par->key_parts_end= key_part;
+
+ DBUG_EXECUTE("info", print_partitioning_index(range_par->key_parts,
+ range_par->key_parts_end););
+ return FALSE;
+}
+
+
+#ifndef DBUG_OFF
+
+static void print_partitioning_index(KEY_PART *parts, KEY_PART *parts_end)
+{
+ DBUG_ENTER("print_partitioning_index");
+ DBUG_LOCK_FILE;
+ fprintf(DBUG_FILE, "partitioning INDEX(");
+ for (KEY_PART *p=parts; p != parts_end; p++)
+ {
+ fprintf(DBUG_FILE, "%s%s", p==parts?"":" ,", p->field->field_name);
+ }
+ fputs(");\n", DBUG_FILE);
+ DBUG_UNLOCK_FILE;
+ DBUG_VOID_RETURN;
+}
+
+/* Print field value into debug trace, in NULL-aware way. */
+static void dbug_print_field(Field *field)
+{
+ if (field->is_real_null())
+ fprintf(DBUG_FILE, "NULL");
+ else
+ {
+ char buf[256];
+ String str(buf, sizeof(buf), &my_charset_bin);
+ str.length(0);
+ String *pstr;
+ pstr= field->val_str(&str);
+ fprintf(DBUG_FILE, "'%s'", pstr->c_ptr_safe());
+ }
+}
+
+
+/* Print a "c1 < keypartX < c2" - type interval into debug trace. */
+static void dbug_print_segment_range(SEL_ARG *arg, KEY_PART *part)
+{
+ DBUG_ENTER("dbug_print_segment_range");
+ DBUG_LOCK_FILE;
+ if (!(arg->min_flag & NO_MIN_RANGE))
+ {
+ store_key_image_to_rec(part->field, arg->min_value, part->length);
+ dbug_print_field(part->field);
+ if (arg->min_flag & NEAR_MIN)
+ fputs(" < ", DBUG_FILE);
+ else
+ fputs(" <= ", DBUG_FILE);
+ }
+
+ fprintf(DBUG_FILE, "%s", part->field->field_name);
+
+ if (!(arg->max_flag & NO_MAX_RANGE))
+ {
+ if (arg->max_flag & NEAR_MAX)
+ fputs(" < ", DBUG_FILE);
+ else
+ fputs(" <= ", DBUG_FILE);
+ store_key_image_to_rec(part->field, arg->max_value, part->length);
+ dbug_print_field(part->field);
+ }
+ fputs("\n", DBUG_FILE);
+ DBUG_UNLOCK_FILE;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Print a singlepoint multi-keypart range interval to debug trace
+
+ SYNOPSIS
+ dbug_print_singlepoint_range()
+ start Array of SEL_ARG* ptrs representing conditions on key parts
+ num Number of elements in the array.
+
+ DESCRIPTION
+ This function prints a "keypartN=constN AND ... AND keypartK=constK"-type
+ interval to debug trace.
+*/
+
+static void dbug_print_singlepoint_range(SEL_ARG **start, uint num)
+{
+ DBUG_ENTER("dbug_print_singlepoint_range");
+ DBUG_LOCK_FILE;
+ SEL_ARG **end= start + num;
+
+ for (SEL_ARG **arg= start; arg != end; arg++)
+ {
+ Field *field= (*arg)->field;
+ fprintf(DBUG_FILE, "%s%s=", (arg==start)?"":", ", field->field_name);
+ dbug_print_field(field);
+ }
+ fputs("\n", DBUG_FILE);
+ DBUG_UNLOCK_FILE;
+ DBUG_VOID_RETURN;
+}
+#endif
+
+/****************************************************************************
+ * Partition pruning code ends
+ ****************************************************************************/
+#endif
+
+
+/*
+ Get cost of 'sweep' full records retrieval.
+ SYNOPSIS
+ get_sweep_read_cost()
+ param Parameter from test_quick_select
+ records # of records to be retrieved
+ RETURN
+ cost of sweep
+*/
+
+double get_sweep_read_cost(const PARAM *param, ha_rows records)
+{
+ double result;
+ DBUG_ENTER("get_sweep_read_cost");
+ if (param->table->file->primary_key_is_clustered())
+ {
+ /*
+ We are using the primary key to find the rows.
+ Calculate the cost for this.
+ */
+ result= param->table->file->read_time(param->table->s->primary_key,
+ (uint)records, records);
+ }
+ else
+ {
+ /*
+ Rows will be retreived with rnd_pos(). Caluclate the expected
+ cost for this.
+ */
+ double n_blocks=
+ ceil(ulonglong2double(param->table->file->stats.data_file_length) /
+ IO_SIZE);
+ double busy_blocks=
+ n_blocks * (1.0 - pow(1.0 - 1.0/n_blocks, rows2double(records)));
+ if (busy_blocks < 1.0)
+ busy_blocks= 1.0;
+ DBUG_PRINT("info",("sweep: nblocks: %g, busy_blocks: %g", n_blocks,
+ busy_blocks));
+ /*
+ Disabled: Bail out if # of blocks to read is bigger than # of blocks in
+ table data file.
+ if (max_cost != DBL_MAX && (busy_blocks+index_reads_cost) >= n_blocks)
+ return 1;
+ */
+ JOIN *join= param->thd->lex->select_lex.join;
+ if (!join || join->table_count == 1)
+ {
+ /* No join, assume reading is done in one 'sweep' */
+ result= busy_blocks*(DISK_SEEK_BASE_COST +
+ DISK_SEEK_PROP_COST*n_blocks/busy_blocks);
+ }
+ else
+ {
+ /*
+ Possibly this is a join with source table being non-last table, so
+ assume that disk seeks are random here.
+ */
+ result= busy_blocks;
+ }
+ }
+ DBUG_PRINT("return",("cost: %g", result));
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Get best plan for a SEL_IMERGE disjunctive expression.
+ SYNOPSIS
+ get_best_disjunct_quick()
+ param Parameter from check_quick_select function
+ imerge Expression to use
+ read_time Don't create scans with cost > read_time
+
+ NOTES
+ index_merge cost is calculated as follows:
+ index_merge_cost =
+ cost(index_reads) + (see #1)
+ cost(rowid_to_row_scan) + (see #2)
+ cost(unique_use) (see #3)
+
+ 1. cost(index_reads) =SUM_i(cost(index_read_i))
+ For non-CPK scans,
+ cost(index_read_i) = {cost of ordinary 'index only' scan}
+ For CPK scan,
+ cost(index_read_i) = {cost of non-'index only' scan}
+
+ 2. cost(rowid_to_row_scan)
+ If table PK is clustered then
+ cost(rowid_to_row_scan) =
+ {cost of ordinary clustered PK scan with n_ranges=n_rows}
+
+ Otherwise, we use the following model to calculate costs:
+ We need to retrieve n_rows rows from file that occupies n_blocks blocks.
+ We assume that offsets of rows we need are independent variates with
+ uniform distribution in [0..max_file_offset] range.
+
+ We'll denote block as "busy" if it contains row(s) we need to retrieve
+ and "empty" if doesn't contain rows we need.
+
+ Probability that a block is empty is (1 - 1/n_blocks)^n_rows (this
+ applies to any block in file). Let x_i be a variate taking value 1 if
+ block #i is empty and 0 otherwise.
+
+ Then E(x_i) = (1 - 1/n_blocks)^n_rows;
+
+ E(n_empty_blocks) = E(sum(x_i)) = sum(E(x_i)) =
+ = n_blocks * ((1 - 1/n_blocks)^n_rows) =
+ ~= n_blocks * exp(-n_rows/n_blocks).
+
+ E(n_busy_blocks) = n_blocks*(1 - (1 - 1/n_blocks)^n_rows) =
+ ~= n_blocks * (1 - exp(-n_rows/n_blocks)).
+
+ Average size of "hole" between neighbor non-empty blocks is
+ E(hole_size) = n_blocks/E(n_busy_blocks).
+
+ The total cost of reading all needed blocks in one "sweep" is:
+
+ E(n_busy_blocks)*
+ (DISK_SEEK_BASE_COST + DISK_SEEK_PROP_COST*n_blocks/E(n_busy_blocks)).
+
+ 3. Cost of Unique use is calculated in Unique::get_use_cost function.
+
+ ROR-union cost is calculated in the same way index_merge, but instead of
+ Unique a priority queue is used.
+
+ RETURN
+ Created read plan
+ NULL - Out of memory or no read scan could be built.
+*/
+
+static
+TABLE_READ_PLAN *get_best_disjunct_quick(PARAM *param, SEL_IMERGE *imerge,
+ double read_time)
+{
+ SEL_TREE **ptree;
+ TRP_INDEX_MERGE *imerge_trp= NULL;
+ TRP_RANGE **range_scans;
+ TRP_RANGE **cur_child;
+ TRP_RANGE **cpk_scan= NULL;
+ bool imerge_too_expensive= FALSE;
+ double imerge_cost= 0.0;
+ ha_rows cpk_scan_records= 0;
+ ha_rows non_cpk_scan_records= 0;
+ bool pk_is_clustered= param->table->file->primary_key_is_clustered();
+ bool all_scans_ror_able= TRUE;
+ bool all_scans_rors= TRUE;
+ uint unique_calc_buff_size;
+ TABLE_READ_PLAN **roru_read_plans;
+ TABLE_READ_PLAN **cur_roru_plan;
+ double roru_index_costs;
+ ha_rows roru_total_records;
+ double roru_intersect_part= 1.0;
+ DBUG_ENTER("get_best_disjunct_quick");
+ DBUG_PRINT("info", ("Full table scan cost: %g", read_time));
+
+ /*
+ In every tree of imerge remove SEL_ARG trees that do not make ranges.
+ If after this removal some SEL_ARG tree becomes empty discard imerge.
+ */
+ for (ptree= imerge->trees; ptree != imerge->trees_next; ptree++)
+ {
+ if (remove_nonrange_trees(param, *ptree))
+ {
+ imerge->trees_next= imerge->trees;
+ break;
+ }
+ }
+
+ uint n_child_scans= imerge->trees_next - imerge->trees;
+
+ if (!n_child_scans)
+ DBUG_RETURN(NULL);
+
+ if (!(range_scans= (TRP_RANGE**)alloc_root(param->mem_root,
+ sizeof(TRP_RANGE*)*
+ n_child_scans)))
+ DBUG_RETURN(NULL);
+ /*
+ Collect best 'range' scan for each of disjuncts, and, while doing so,
+ analyze possibility of ROR scans. Also calculate some values needed by
+ other parts of the code.
+ */
+ for (ptree= imerge->trees, cur_child= range_scans;
+ ptree != imerge->trees_next;
+ ptree++, cur_child++)
+ {
+ DBUG_EXECUTE("info", print_sel_tree(param, *ptree, &(*ptree)->keys_map,
+ "tree in SEL_IMERGE"););
+ if (!(*cur_child= get_key_scans_params(param, *ptree, TRUE, FALSE, read_time)))
+ {
+ /*
+ One of index scans in this index_merge is more expensive than entire
+ table read for another available option. The entire index_merge (and
+ any possible ROR-union) will be more expensive then, too. We continue
+ here only to update SQL_SELECT members.
+ */
+ imerge_too_expensive= TRUE;
+ }
+ if (imerge_too_expensive)
+ continue;
+
+ imerge_cost += (*cur_child)->read_cost;
+ all_scans_ror_able &= ((*ptree)->n_ror_scans > 0);
+ all_scans_rors &= (*cur_child)->is_ror;
+ if (pk_is_clustered &&
+ param->real_keynr[(*cur_child)->key_idx] ==
+ param->table->s->primary_key)
+ {
+ cpk_scan= cur_child;
+ cpk_scan_records= (*cur_child)->records;
+ }
+ else
+ non_cpk_scan_records += (*cur_child)->records;
+ }
+
+ DBUG_PRINT("info", ("index_merge scans cost %g", imerge_cost));
+ if (imerge_too_expensive || (imerge_cost > read_time) ||
+ ((non_cpk_scan_records+cpk_scan_records >=
+ param->table->stat_records()) &&
+ read_time != DBL_MAX))
+ {
+ /*
+ Bail out if it is obvious that both index_merge and ROR-union will be
+ more expensive
+ */
+ DBUG_PRINT("info", ("Sum of index_merge scans is more expensive than "
+ "full table scan, bailing out"));
+ DBUG_RETURN(NULL);
+ }
+
+ /*
+ If all scans happen to be ROR, proceed to generate a ROR-union plan (it's
+ guaranteed to be cheaper than non-ROR union), unless ROR-unions are
+ disabled in @@optimizer_switch
+ */
+ if (all_scans_rors &&
+ optimizer_flag(param->thd, OPTIMIZER_SWITCH_INDEX_MERGE_UNION))
+ {
+ roru_read_plans= (TABLE_READ_PLAN**)range_scans;
+ goto skip_to_ror_scan;
+ }
+
+ if (cpk_scan)
+ {
+ /*
+ Add one ROWID comparison for each row retrieved on non-CPK scan. (it
+ is done in QUICK_RANGE_SELECT::row_in_ranges)
+ */
+ imerge_cost += non_cpk_scan_records / TIME_FOR_COMPARE_ROWID;
+ }
+
+ /* Calculate cost(rowid_to_row_scan) */
+ imerge_cost += get_sweep_read_cost(param, non_cpk_scan_records);
+ DBUG_PRINT("info",("index_merge cost with rowid-to-row scan: %g",
+ imerge_cost));
+ if (imerge_cost > read_time ||
+ !optimizer_flag(param->thd, OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION))
+ {
+ goto build_ror_index_merge;
+ }
+
+ /* Add Unique operations cost */
+ unique_calc_buff_size=
+ Unique::get_cost_calc_buff_size((ulong)non_cpk_scan_records,
+ param->table->file->ref_length,
+ param->thd->variables.sortbuff_size);
+ if (param->imerge_cost_buff_size < unique_calc_buff_size)
+ {
+ if (!(param->imerge_cost_buff= (uint*)alloc_root(param->mem_root,
+ unique_calc_buff_size)))
+ DBUG_RETURN(NULL);
+ param->imerge_cost_buff_size= unique_calc_buff_size;
+ }
+
+ imerge_cost +=
+ Unique::get_use_cost(param->imerge_cost_buff, (uint)non_cpk_scan_records,
+ param->table->file->ref_length,
+ param->thd->variables.sortbuff_size,
+ TIME_FOR_COMPARE_ROWID,
+ FALSE, NULL);
+ DBUG_PRINT("info",("index_merge total cost: %g (wanted: less then %g)",
+ imerge_cost, read_time));
+ if (imerge_cost < read_time)
+ {
+ if ((imerge_trp= new (param->mem_root)TRP_INDEX_MERGE))
+ {
+ imerge_trp->read_cost= imerge_cost;
+ imerge_trp->records= non_cpk_scan_records + cpk_scan_records;
+ imerge_trp->records= MY_MIN(imerge_trp->records,
+ param->table->stat_records());
+ imerge_trp->range_scans= range_scans;
+ imerge_trp->range_scans_end= range_scans + n_child_scans;
+ read_time= imerge_cost;
+ }
+ if (imerge_trp)
+ {
+ TABLE_READ_PLAN *trp= merge_same_index_scans(param, imerge, imerge_trp,
+ read_time);
+ if (trp != imerge_trp)
+ DBUG_RETURN(trp);
+ }
+ }
+
+build_ror_index_merge:
+ if (!all_scans_ror_able ||
+ param->thd->lex->sql_command == SQLCOM_DELETE ||
+ !optimizer_flag(param->thd, OPTIMIZER_SWITCH_INDEX_MERGE_UNION))
+ DBUG_RETURN(imerge_trp);
+
+ /* Ok, it is possible to build a ROR-union, try it. */
+ bool dummy;
+ if (!(roru_read_plans=
+ (TABLE_READ_PLAN**)alloc_root(param->mem_root,
+ sizeof(TABLE_READ_PLAN*)*
+ n_child_scans)))
+ DBUG_RETURN(imerge_trp);
+
+skip_to_ror_scan:
+ roru_index_costs= 0.0;
+ roru_total_records= 0;
+ cur_roru_plan= roru_read_plans;
+
+ /* Find 'best' ROR scan for each of trees in disjunction */
+ for (ptree= imerge->trees, cur_child= range_scans;
+ ptree != imerge->trees_next;
+ ptree++, cur_child++, cur_roru_plan++)
+ {
+ /*
+ Assume the best ROR scan is the one that has cheapest full-row-retrieval
+ scan cost.
+ Also accumulate index_only scan costs as we'll need them to calculate
+ overall index_intersection cost.
+ */
+ double cost;
+ if ((*cur_child)->is_ror)
+ {
+ /* Ok, we have index_only cost, now get full rows scan cost */
+ cost= param->table->file->
+ read_time(param->real_keynr[(*cur_child)->key_idx], 1,
+ (*cur_child)->records) +
+ rows2double((*cur_child)->records) / TIME_FOR_COMPARE;
+ }
+ else
+ cost= read_time;
+
+ TABLE_READ_PLAN *prev_plan= *cur_child;
+ if (!(*cur_roru_plan= get_best_ror_intersect(param, *ptree, cost,
+ &dummy)))
+ {
+ if (prev_plan->is_ror)
+ *cur_roru_plan= prev_plan;
+ else
+ DBUG_RETURN(imerge_trp);
+ roru_index_costs += (*cur_roru_plan)->read_cost;
+ }
+ else
+ roru_index_costs +=
+ ((TRP_ROR_INTERSECT*)(*cur_roru_plan))->index_scan_costs;
+ roru_total_records += (*cur_roru_plan)->records;
+ roru_intersect_part *= (*cur_roru_plan)->records /
+ param->table->stat_records();
+ }
+
+ /*
+ rows to retrieve=
+ SUM(rows_in_scan_i) - table_rows * PROD(rows_in_scan_i / table_rows).
+ This is valid because index_merge construction guarantees that conditions
+ in disjunction do not share key parts.
+ */
+ roru_total_records -= (ha_rows)(roru_intersect_part*
+ param->table->stat_records());
+ /* ok, got a ROR read plan for each of the disjuncts
+ Calculate cost:
+ cost(index_union_scan(scan_1, ... scan_n)) =
+ SUM_i(cost_of_index_only_scan(scan_i)) +
+ queue_use_cost(rowid_len, n) +
+ cost_of_row_retrieval
+ See get_merge_buffers_cost function for queue_use_cost formula derivation.
+ */
+
+ double roru_total_cost;
+ roru_total_cost= roru_index_costs +
+ rows2double(roru_total_records)*log((double)n_child_scans) /
+ (TIME_FOR_COMPARE_ROWID * M_LN2) +
+ get_sweep_read_cost(param, roru_total_records);
+
+ DBUG_PRINT("info", ("ROR-union: cost %g, %d members", roru_total_cost,
+ n_child_scans));
+ TRP_ROR_UNION* roru;
+ if (roru_total_cost < read_time)
+ {
+ if ((roru= new (param->mem_root) TRP_ROR_UNION))
+ {
+ roru->first_ror= roru_read_plans;
+ roru->last_ror= roru_read_plans + n_child_scans;
+ roru->read_cost= roru_total_cost;
+ roru->records= roru_total_records;
+ DBUG_RETURN(roru);
+ }
+ }
+ DBUG_RETURN(imerge_trp);
+}
+
+
+/*
+ Merge index scans for the same indexes in an index merge plan
+
+ SYNOPSIS
+ merge_same_index_scans()
+ param Context info for the operation
+ imerge IN/OUT SEL_IMERGE from which imerge_trp has been extracted
+ imerge_trp The index merge plan where index scans for the same
+ indexes are to be merges
+ read_time The upper bound for the cost of the plan to be evaluated
+
+ DESRIPTION
+ For the given index merge plan imerge_trp extracted from the SEL_MERGE
+ imerge the function looks for range scans with the same indexes and merges
+ them into SEL_ARG trees. Then for each such SEL_ARG tree r_i the function
+ creates a range tree rt_i that contains only r_i. All rt_i are joined
+ into one index merge that replaces the original index merge imerge.
+ The function calls get_best_disjunct_quick for the new index merge to
+ get a new index merge plan that contains index scans only for different
+ indexes.
+ If there are no index scans for the same index in the original index
+ merge plan the function does not change the original imerge and returns
+ imerge_trp as its result.
+
+ RETURN
+ The original or or improved index merge plan
+*/
+
+static
+TABLE_READ_PLAN *merge_same_index_scans(PARAM *param, SEL_IMERGE *imerge,
+ TRP_INDEX_MERGE *imerge_trp,
+ double read_time)
+{
+ uint16 first_scan_tree_idx[MAX_KEY];
+ SEL_TREE **tree;
+ TRP_RANGE **cur_child;
+ uint removed_cnt= 0;
+
+ DBUG_ENTER("merge_same_index_scans");
+
+ bzero(first_scan_tree_idx, sizeof(first_scan_tree_idx[0])*param->keys);
+
+ for (tree= imerge->trees, cur_child= imerge_trp->range_scans;
+ tree != imerge->trees_next;
+ tree++, cur_child++)
+ {
+ DBUG_ASSERT(tree);
+ uint key_idx= (*cur_child)->key_idx;
+ uint16 *tree_idx_ptr= &first_scan_tree_idx[key_idx];
+ if (!*tree_idx_ptr)
+ *tree_idx_ptr= (uint16) (tree-imerge->trees+1);
+ else
+ {
+ SEL_TREE **changed_tree= imerge->trees+(*tree_idx_ptr-1);
+ SEL_ARG *key= (*changed_tree)->keys[key_idx];
+ bzero((*changed_tree)->keys,
+ sizeof((*changed_tree)->keys[0])*param->keys);
+ (*changed_tree)->keys_map.clear_all();
+ if (key)
+ key->incr_refs();
+ if ((*tree)->keys[key_idx])
+ (*tree)->keys[key_idx]->incr_refs();
+ if (((*changed_tree)->keys[key_idx]=
+ key_or(param, key, (*tree)->keys[key_idx])))
+ (*changed_tree)->keys_map.set_bit(key_idx);
+ *tree= NULL;
+ removed_cnt++;
+ }
+ }
+ if (!removed_cnt)
+ DBUG_RETURN(imerge_trp);
+
+ TABLE_READ_PLAN *trp= NULL;
+ SEL_TREE **new_trees_next= imerge->trees;
+ for (tree= new_trees_next; tree != imerge->trees_next; tree++)
+ {
+ if (!*tree)
+ continue;
+ if (tree > new_trees_next)
+ *new_trees_next= *tree;
+ new_trees_next++;
+ }
+ imerge->trees_next= new_trees_next;
+
+ DBUG_ASSERT(imerge->trees_next>imerge->trees);
+
+ if (imerge->trees_next-imerge->trees > 1)
+ trp= get_best_disjunct_quick(param, imerge, read_time);
+ else
+ {
+ /*
+ This alternative theoretically can be reached when the cost
+ of the index merge for such a formula as
+ (key1 BETWEEN c1_1 AND c1_2) AND key2 > c2 OR
+ (key1 BETWEEN c1_3 AND c1_4) AND key3 > c3
+ is estimated as being cheaper than the cost of index scan for
+ the formula
+ (key1 BETWEEN c1_1 AND c1_2) OR (key1 BETWEEN c1_3 AND c1_4)
+
+ In the current code this may happen for two reasons:
+ 1. for a single index range scan data records are accessed in
+ a random order
+ 2. the functions that estimate the cost of a range scan and an
+ index merge retrievals are not well calibrated
+ */
+ trp= get_key_scans_params(param, *imerge->trees, FALSE, TRUE,
+ read_time);
+ }
+
+ DBUG_RETURN(trp);
+}
+
+
+/*
+ This structure contains the info common for all steps of a partial
+ index intersection plan. Morever it contains also the info common
+ for index intersect plans. This info is filled in by the function
+ prepare_search_best just before searching for the best index
+ intersection plan.
+*/
+
+typedef struct st_common_index_intersect_info
+{
+ PARAM *param; /* context info for range optimizations */
+ uint key_size; /* size of a ROWID element stored in Unique object */
+ uint compare_factor; /* 1/compare - cost to compare two ROWIDs */
+ ulonglong max_memory_size; /* maximum space allowed for Unique objects */
+ ha_rows table_cardinality; /* estimate of the number of records in table */
+ double cutoff_cost; /* discard index intersects with greater costs */
+ INDEX_SCAN_INFO *cpk_scan; /* clustered primary key used in intersection */
+
+ bool in_memory; /* unique object for intersection is completely in memory */
+
+ INDEX_SCAN_INFO **search_scans; /* scans possibly included in intersect */
+ uint n_search_scans; /* number of elements in search_scans */
+
+ bool best_uses_cpk; /* current best intersect uses clustered primary key */
+ double best_cost; /* cost of the current best index intersection */
+ /* estimate of the number of records in the current best intersection */
+ ha_rows best_records;
+ uint best_length; /* number of indexes in the current best intersection */
+ INDEX_SCAN_INFO **best_intersect; /* the current best index intersection */
+ /* scans from the best intersect to be filtrered by cpk conditions */
+ key_map filtered_scans;
+
+ uint *buff_elems; /* buffer to calculate cost of index intersection */
+
+} COMMON_INDEX_INTERSECT_INFO;
+
+
+/*
+ This structure contains the info specific for one step of an index
+ intersection plan. The structure is filled in by the function
+ check_index_intersect_extension.
+*/
+
+typedef struct st_partial_index_intersect_info
+{
+ COMMON_INDEX_INTERSECT_INFO *common_info; /* shared by index intersects */
+ uint length; /* number of index scans in the partial intersection */
+ ha_rows records; /* estimate of the number of records in intersection */
+ double cost; /* cost of the partial index intersection */
+
+ /* estimate of total number of records of all scans of the partial index
+ intersect sent to the Unique object used for the intersection */
+ ha_rows records_sent_to_unique;
+
+ /* total cost of the scans of indexes from the partial index intersection */
+ double index_read_cost;
+
+ bool use_cpk_filter; /* cpk filter is to be used for this scan */
+ bool in_memory; /* uses unique object in memory */
+ double in_memory_cost; /* cost of using unique object in memory */
+
+ key_map filtered_scans; /* scans to be filtered by cpk conditions */
+
+ MY_BITMAP *intersect_fields; /* bitmap of fields used in intersection */
+} PARTIAL_INDEX_INTERSECT_INFO;
+
+
+/* Check whether two indexes have the same first n components */
+
+static
+bool same_index_prefix(KEY *key1, KEY *key2, uint used_parts)
+{
+ KEY_PART_INFO *part1= key1->key_part;
+ KEY_PART_INFO *part2= key2->key_part;
+ for(uint i= 0; i < used_parts; i++, part1++, part2++)
+ {
+ if (part1->fieldnr != part2->fieldnr)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Create a bitmap for all fields of a table */
+
+static
+bool create_fields_bitmap(PARAM *param, MY_BITMAP *fields_bitmap)
+{
+ my_bitmap_map *bitmap_buf;
+
+ if (!(bitmap_buf= (my_bitmap_map *) alloc_root(param->mem_root,
+ param->fields_bitmap_size)))
+ return TRUE;
+ if (my_bitmap_init(fields_bitmap, bitmap_buf, param->table->s->fields, FALSE))
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Compare two indexes scans for sort before search for the best intersection */
+
+static
+int cmp_intersect_index_scan(INDEX_SCAN_INFO **a, INDEX_SCAN_INFO **b)
+{
+ return (*a)->records < (*b)->records ?
+ -1 : (*a)->records == (*b)->records ? 0 : 1;
+}
+
+
+static inline
+void set_field_bitmap_for_index_prefix(MY_BITMAP *field_bitmap,
+ KEY_PART_INFO *key_part,
+ uint used_key_parts)
+{
+ bitmap_clear_all(field_bitmap);
+ for (KEY_PART_INFO *key_part_end= key_part+used_key_parts;
+ key_part < key_part_end; key_part++)
+ {
+ bitmap_set_bit(field_bitmap, key_part->fieldnr-1);
+ }
+}
+
+
+/*
+ Round up table cardinality read from statistics provided by engine.
+ This function should go away when mysql test will allow to handle
+ more or less easily in the test suites deviations of InnoDB
+ statistical data.
+*/
+
+static inline
+ha_rows get_table_cardinality_for_index_intersect(TABLE *table)
+{
+ if (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)
+ return table->stat_records();
+ else
+ {
+ ha_rows d;
+ double q;
+ for (q= (double)table->stat_records(), d= 1 ; q >= 10; q/= 10, d*= 10 ) ;
+ return (ha_rows) (floor(q+0.5) * d);
+ }
+}
+
+
+static
+ha_rows records_in_index_intersect_extension(PARTIAL_INDEX_INTERSECT_INFO *curr,
+ INDEX_SCAN_INFO *ext_index_scan);
+
+/*
+ Prepare to search for the best index intersection
+
+ SYNOPSIS
+ prepare_search_best_index_intersect()
+ param common info about index ranges
+ tree tree of ranges for indexes than can be intersected
+ common OUT info needed for search to be filled by the function
+ init OUT info for an initial pseudo step of the intersection plans
+ cutoff_cost cut off cost of the interesting index intersection
+
+ DESCRIPTION
+ The function initializes all fields of the structure 'common' to be used
+ when searching for the best intersection plan. It also allocates
+ memory to store the most cheap index intersection.
+
+ NOTES
+ When selecting candidates for index intersection we always take only
+ one representative out of any set of indexes that share the same range
+ conditions. These indexes always have the same prefixes and the
+ components of this prefixes are exactly those used in these range
+ conditions.
+ Range conditions over clustered primary key (cpk) is always used only
+ as the condition that filters out some rowids retrieved by the scans
+ for secondary indexes. The cpk index will be handled in special way by
+ the function that search for the best index intersection.
+
+ RETURN
+ FALSE in the case of success
+ TRUE otherwise
+*/
+
+static
+bool prepare_search_best_index_intersect(PARAM *param,
+ SEL_TREE *tree,
+ COMMON_INDEX_INTERSECT_INFO *common,
+ PARTIAL_INDEX_INTERSECT_INFO *init,
+ double cutoff_cost)
+{
+ uint i;
+ uint n_search_scans;
+ double cost;
+ INDEX_SCAN_INFO **index_scan;
+ INDEX_SCAN_INFO **scan_ptr;
+ INDEX_SCAN_INFO *cpk_scan= NULL;
+ TABLE *table= param->table;
+ uint n_index_scans= tree->index_scans_end - tree->index_scans;
+
+ if (!n_index_scans)
+ return 1;
+
+ bzero(init, sizeof(*init));
+ init->common_info= common;
+ init->cost= cutoff_cost;
+
+ common->param= param;
+ common->key_size= table->file->ref_length;
+ common->compare_factor= TIME_FOR_COMPARE_ROWID;
+ common->max_memory_size= param->thd->variables.sortbuff_size;
+ common->cutoff_cost= cutoff_cost;
+ common->cpk_scan= NULL;
+ common->table_cardinality=
+ get_table_cardinality_for_index_intersect(table);
+
+ if (n_index_scans <= 1)
+ return TRUE;
+
+ if (table->file->primary_key_is_clustered())
+ {
+ INDEX_SCAN_INFO **index_scan_end;
+ index_scan= tree->index_scans;
+ index_scan_end= index_scan+n_index_scans;
+ for ( ; index_scan < index_scan_end; index_scan++)
+ {
+ if ((*index_scan)->keynr == table->s->primary_key)
+ {
+ common->cpk_scan= cpk_scan= *index_scan;
+ break;
+ }
+ }
+ }
+
+ i= n_index_scans - MY_TEST(cpk_scan != NULL) + 1;
+
+ if (!(common->search_scans =
+ (INDEX_SCAN_INFO **) alloc_root (param->mem_root,
+ sizeof(INDEX_SCAN_INFO *) * i)))
+ return TRUE;
+ bzero(common->search_scans, sizeof(INDEX_SCAN_INFO *) * i);
+
+ INDEX_SCAN_INFO **selected_index_scans= common->search_scans;
+
+ for (i=0, index_scan= tree->index_scans; i < n_index_scans; i++, index_scan++)
+ {
+ uint used_key_parts= (*index_scan)->used_key_parts;
+ KEY *key_info= (*index_scan)->key_info;
+
+ if (*index_scan == cpk_scan)
+ continue;
+ if (cpk_scan && cpk_scan->used_key_parts >= used_key_parts &&
+ same_index_prefix(cpk_scan->key_info, key_info, used_key_parts))
+ continue;
+
+ cost= table->file->keyread_time((*index_scan)->keynr,
+ (*index_scan)->range_count,
+ (*index_scan)->records);
+ if (cost >= cutoff_cost)
+ continue;
+
+ for (scan_ptr= selected_index_scans; *scan_ptr ; scan_ptr++)
+ {
+ /*
+ When we have range conditions for two different indexes with the same
+ beginning it does not make sense to consider both of them for index
+ intersection if the range conditions are covered by common initial
+ components of the indexes. Actually in this case the indexes are
+ guaranteed to have the same range conditions.
+ */
+ if ((*scan_ptr)->used_key_parts == used_key_parts &&
+ same_index_prefix((*scan_ptr)->key_info, key_info, used_key_parts))
+ break;
+ }
+ if (!*scan_ptr || cost < (*scan_ptr)->index_read_cost)
+ {
+ *scan_ptr= *index_scan;
+ (*scan_ptr)->index_read_cost= cost;
+ }
+ }
+
+ ha_rows records_in_scans= 0;
+
+ for (scan_ptr=selected_index_scans, i= 0; *scan_ptr; scan_ptr++, i++)
+ {
+ if (create_fields_bitmap(param, &(*scan_ptr)->used_fields))
+ return TRUE;
+ records_in_scans+= (*scan_ptr)->records;
+ }
+ n_search_scans= i;
+
+ if (cpk_scan && create_fields_bitmap(param, &cpk_scan->used_fields))
+ return TRUE;
+
+ if (!(common->n_search_scans= n_search_scans))
+ return TRUE;
+
+ common->best_uses_cpk= FALSE;
+ common->best_cost= cutoff_cost + COST_EPS;
+ common->best_length= 0;
+
+ if (!(common->best_intersect=
+ (INDEX_SCAN_INFO **) alloc_root (param->mem_root,
+ sizeof(INDEX_SCAN_INFO *) *
+ (i + MY_TEST(cpk_scan != NULL)))))
+ return TRUE;
+
+ size_t calc_cost_buff_size=
+ Unique::get_cost_calc_buff_size((size_t)records_in_scans,
+ common->key_size,
+ common->max_memory_size);
+ if (!(common->buff_elems= (uint *) alloc_root(param->mem_root,
+ calc_cost_buff_size)))
+ return TRUE;
+
+ my_qsort(selected_index_scans, n_search_scans, sizeof(INDEX_SCAN_INFO *),
+ (qsort_cmp) cmp_intersect_index_scan);
+
+ if (cpk_scan)
+ {
+ PARTIAL_INDEX_INTERSECT_INFO curr;
+ set_field_bitmap_for_index_prefix(&cpk_scan->used_fields,
+ cpk_scan->key_info->key_part,
+ cpk_scan->used_key_parts);
+ curr.common_info= common;
+ curr.intersect_fields= &cpk_scan->used_fields;
+ curr.records= cpk_scan->records;
+ curr.length= 1;
+ for (scan_ptr=selected_index_scans; *scan_ptr; scan_ptr++)
+ {
+ ha_rows scan_records= (*scan_ptr)->records;
+ ha_rows records= records_in_index_intersect_extension(&curr, *scan_ptr);
+ (*scan_ptr)->filtered_out= records >= scan_records ?
+ 0 : scan_records-records;
+ }
+ }
+ else
+ {
+ for (scan_ptr=selected_index_scans; *scan_ptr; scan_ptr++)
+ (*scan_ptr)->filtered_out= 0;
+ }
+
+ return FALSE;
+}
+
+
+/*
+ On Estimation of the Number of Records in an Index Intersection
+ ===============================================================
+
+ Consider query Q over table t. Let C be the WHERE condition of this query,
+ and, idx1(a1_1,...,a1_k1) and idx2(a2_1,...,a2_k2) be some indexes defined
+ on table t.
+ Let rt1 and rt2 be the range trees extracted by the range optimizer from C
+ for idx1 and idx2 respectively.
+ Let #t be the estimate of the number of records in table t provided for the
+ optimizer.
+ Let #r1 and #r2 be the estimates of the number of records in the range trees
+ rt1 and rt2, respectively, obtained by the range optimizer.
+
+ We need to get an estimate for the number of records in the index
+ intersection of rt1 and rt2. In other words, we need to estimate the
+ cardinality of the set of records that are in both trees. Let's designate
+ this number by #r.
+
+ If we do not make any assumptions then we can only state that
+ #r<=MY_MIN(#r1,#r2).
+ With this estimate we can't say that the index intersection scan will be
+ cheaper than the cheapest index scan.
+
+ Let Rt1 and Rt2 be AND/OR conditions representing rt and rt2 respectively.
+ The probability that a record belongs to rt1 is sel(Rt1)=#r1/#t.
+ The probability that a record belongs to rt2 is sel(Rt2)=#r2/#t.
+
+ If we assume that the values in columns of idx1 and idx2 are independent
+ then #r/#t=sel(Rt1&Rt2)=sel(Rt1)*sel(Rt2)=(#r1/#t)*(#r2/#t).
+ So in this case we have: #r=#r1*#r2/#t.
+
+ The above assumption of independence of the columns in idx1 and idx2 means
+ that:
+ - all columns are different
+ - values from one column do not correlate with values from any other column.
+
+ We can't help with the case when column correlate with each other.
+ Yet, if they are assumed to be uncorrelated the value of #r theoretically can
+ be evaluated . Unfortunately this evaluation, in general, is rather complex.
+
+ Let's consider two indexes idx1:(dept, manager), idx2:(dept, building)
+ over table 'employee' and two range conditions over these indexes:
+ Rt1: dept=10 AND manager LIKE 'S%'
+ Rt2: dept=10 AND building LIKE 'L%'.
+ We can state that:
+ sel(Rt1&Rt2)=sel(dept=10)*sel(manager LIKE 'S%')*sel(building LIKE 'L%')
+ =sel(Rt1)*sel(Rt2)/sel(dept=10).
+ sel(Rt1/2_0:dept=10) can be estimated if we know the cardinality #r1_0 of
+ the range for sub-index idx1_0 (dept) of the index idx1 or the cardinality
+ #rt2_0 of the same range for sub-index idx2_0(dept) of the index idx2.
+ The current code does not make an estimate either for #rt1_0, or for #rt2_0,
+ but it can be adjusted to provide those numbers.
+ Alternatively, MY_MIN(rec_per_key) for (dept) could be used to get an upper
+ bound for the value of sel(Rt1&Rt2). Yet this statistics is not provided
+ now.
+
+ Let's consider two other indexes idx1:(dept, last_name),
+ idx2:(first_name, last_name) and two range conditions over these indexes:
+ Rt1: dept=5 AND last_name='Sm%'
+ Rt2: first_name='Robert' AND last_name='Sm%'.
+
+ sel(Rt1&Rt2)=sel(dept=5)*sel(last_name='Sm5')*sel(first_name='Robert')
+ =sel(Rt2)*sel(dept=5)
+ Here MY_MAX(rec_per_key) for (dept) could be used to get an upper bound for
+ the value of sel(Rt1&Rt2).
+
+ When the intersected indexes have different major columns, but some
+ minor column are common the picture may be more complicated.
+
+ Let's consider the following range conditions for the same indexes as in
+ the previous example:
+ Rt1: (Rt11: dept=5 AND last_name='So%')
+ OR
+ (Rt12: dept=7 AND last_name='Saw%')
+ Rt2: (Rt21: first_name='Robert' AND last_name='Saw%')
+ OR
+ (Rt22: first_name='Bob' AND last_name='So%')
+ Here we have:
+ sel(Rt1&Rt2)= sel(Rt11)*sel(Rt21)+sel(Rt22)*sel(dept=5) +
+ sel(Rt21)*sel(dept=7)+sel(Rt12)*sel(Rt22)
+ Now consider the range condition:
+ Rt1_0: (dept=5 OR dept=7)
+ For this condition we can state that:
+ sel(Rt1_0&Rt2)=(sel(dept=5)+sel(dept=7))*(sel(Rt21)+sel(Rt22))=
+ sel(dept=5)*sel(Rt21)+sel(dept=7)*sel(Rt21)+
+ sel(dept=5)*sel(Rt22)+sel(dept=7)*sel(Rt22)=
+ sel(dept=5)*sel(Rt21)+sel(Rt21)*sel(dept=7)+
+ sel(Rt22)*sel(dept=5)+sel(dept=7)*sel(Rt22) >
+ sel(Rt11)*sel(Rt21)+sel(Rt22)*sel(dept=5)+
+ sel(Rt21)*sel(dept=7)+sel(Rt12)*sel(Rt22) >
+ sel(Rt1 & Rt2)
+
+ We've just demonstrated for an example what is intuitively almost obvious
+ in general. We can remove the ending parts fromrange trees getting less
+ selective range conditions for sub-indexes.
+ So if not a most major component with the number k of an index idx is
+ encountered in the index with which we intersect we can use the sub-index
+ idx_k-1 that includes the components of idx up to the i-th component and
+ the range tree for idx_k-1 to make an upper bound estimate for the number
+ of records in the index intersection.
+ The range tree for idx_k-1 we use here is the subtree of the original range
+ tree for idx that contains only parts from the first k-1 components.
+
+ As it was mentioned above the range optimizer currently does not provide
+ an estimate for the number of records in the ranges for sub-indexes.
+ However, some reasonable upper bound estimate can be obtained.
+
+ Let's consider the following range tree:
+ Rt: (first_name='Robert' AND last_name='Saw%')
+ OR
+ (first_name='Bob' AND last_name='So%')
+ Let #r be the number of records in Rt. Let f_1 be the fan-out of column
+ last_name:
+ f_1 = rec_per_key[first_name]/rec_per_key[last_name].
+ The the number of records in the range tree:
+ Rt_0: (first_name='Robert' OR first_name='Bob')
+ for the sub-index (first_name) is not greater than MY_MAX(#r*f_1, #t).
+ Strictly speaking, we can state only that it's not greater than
+ MY_MAX(#r*max_f_1, #t), where
+ max_f_1= max_rec_per_key[first_name]/min_rec_per_key[last_name].
+ Yet, if #r/#t is big enough (and this is the case of an index intersection,
+ because using this index range with a single index scan is cheaper than
+ the cost of the intersection when #r/#t is small) then almost safely we
+ can use here f_1 instead of max_f_1.
+
+ The above considerations can be used in future development. Now, they are
+ used partly in the function that provides a rough upper bound estimate for
+ the number of records in an index intersection that follow below.
+*/
+
+/*
+ Estimate the number of records selected by an extension a partial intersection
+
+ SYNOPSIS
+ records_in_index_intersect_extension()
+ curr partial intersection plan to be extended
+ ext_index_scan the evaluated extension of this partial plan
+
+ DESCRIPTION
+ The function provides an estimate for the number of records in the
+ intersection of the partial index intersection curr with the index
+ ext_index_scan. If all intersected indexes does not have common columns
+ then the function returns an exact estimate (assuming there are no
+ correlations between values in the columns). If the intersected indexes
+ have common columns the function returns an upper bound for the number
+ of records in the intersection provided that the intersection of curr
+ with ext_index_scan can is expected to have less records than the expected
+ number of records in the partial intersection curr. In this case the
+ function also assigns the bitmap of the columns in the extended
+ intersection to ext_index_scan->used_fields.
+ If the function cannot expect that the number of records in the extended
+ intersection is less that the expected number of records #r in curr then
+ the function returns a number bigger than #r.
+
+ NOTES
+ See the comment before the desription of the function that explains the
+ reasoning used by this function.
+
+ RETURN
+ The expected number of rows in the extended index intersection
+*/
+
+static
+ha_rows records_in_index_intersect_extension(PARTIAL_INDEX_INTERSECT_INFO *curr,
+ INDEX_SCAN_INFO *ext_index_scan)
+{
+ KEY *key_info= ext_index_scan->key_info;
+ KEY_PART_INFO* key_part= key_info->key_part;
+ uint used_key_parts= ext_index_scan->used_key_parts;
+ MY_BITMAP *used_fields= &ext_index_scan->used_fields;
+
+ if (!curr->length)
+ {
+ /*
+ If this the first index in the intersection just mark the
+ fields in the used_fields bitmap and return the expected
+ number of records in the range scan for the index provided
+ by the range optimizer.
+ */
+ set_field_bitmap_for_index_prefix(used_fields, key_part, used_key_parts);
+ return ext_index_scan->records;
+ }
+
+ uint i;
+ bool better_selectivity= FALSE;
+ ha_rows records= curr->records;
+ MY_BITMAP *curr_intersect_fields= curr->intersect_fields;
+ for (i= 0; i < used_key_parts; i++, key_part++)
+ {
+ if (bitmap_is_set(curr_intersect_fields, key_part->fieldnr-1))
+ break;
+ }
+ if (i)
+ {
+ ha_rows table_cardinality= curr->common_info->table_cardinality;
+ ha_rows ext_records= ext_index_scan->records;
+ if (i < used_key_parts)
+ {
+ double f1= key_info->actual_rec_per_key(i-1);
+ double f2= key_info->actual_rec_per_key(i);
+ ext_records= (ha_rows) ((double) ext_records / f2 * f1);
+ }
+ if (ext_records < table_cardinality)
+ {
+ better_selectivity= TRUE;
+ records= (ha_rows) ((double) records / table_cardinality *
+ ext_records);
+ bitmap_copy(used_fields, curr_intersect_fields);
+ key_part= key_info->key_part;
+ for (uint j= 0; j < used_key_parts; j++, key_part++)
+ bitmap_set_bit(used_fields, key_part->fieldnr-1);
+ }
+ }
+ return !better_selectivity ? records+1 :
+ !records ? 1 : records;
+}
+
+
+/*
+ Estimate the cost a binary search within disjoint cpk range intervals
+
+ Number of comparisons to check whether a cpk value satisfies
+ the cpk range condition = log2(cpk_scan->range_count).
+*/
+
+static inline
+double get_cpk_filter_cost(ha_rows filtered_records,
+ INDEX_SCAN_INFO *cpk_scan,
+ double compare_factor)
+{
+ return log((double) (cpk_scan->range_count+1)) / (compare_factor * M_LN2) *
+ filtered_records;
+}
+
+
+/*
+ Check whether a patial index intersection plan can be extended
+
+ SYNOPSIS
+ check_index_intersect_extension()
+ curr partial intersection plan to be extended
+ ext_index_scan a possible extension of this plan to be checked
+ next OUT the structure to be filled for the extended plan
+
+ DESCRIPTION
+ The function checks whether it makes sense to extend the index
+ intersection plan adding the index ext_index_scan, and, if this
+ the case, the function fills in the structure for the extended plan.
+
+ RETURN
+ TRUE if it makes sense to extend the given plan
+ FALSE otherwise
+*/
+
+static
+bool check_index_intersect_extension(PARTIAL_INDEX_INTERSECT_INFO *curr,
+ INDEX_SCAN_INFO *ext_index_scan,
+ PARTIAL_INDEX_INTERSECT_INFO *next)
+{
+ ha_rows records;
+ ha_rows records_sent_to_unique;
+ double cost;
+ ha_rows ext_index_scan_records= ext_index_scan->records;
+ ha_rows records_filtered_out_by_cpk= ext_index_scan->filtered_out;
+ COMMON_INDEX_INTERSECT_INFO *common_info= curr->common_info;
+ double cutoff_cost= common_info->cutoff_cost;
+ uint idx= curr->length;
+ next->index_read_cost= curr->index_read_cost+ext_index_scan->index_read_cost;
+ if (next->index_read_cost > cutoff_cost)
+ return FALSE;
+
+ if ((next->in_memory= curr->in_memory))
+ next->in_memory_cost= curr->in_memory_cost;
+
+ next->intersect_fields= &ext_index_scan->used_fields;
+ next->filtered_scans= curr->filtered_scans;
+
+ records_sent_to_unique= curr->records_sent_to_unique;
+
+ next->use_cpk_filter= FALSE;
+
+ /* Calculate the cost of using a Unique object for index intersection */
+ if (idx && next->in_memory)
+ {
+ /*
+ All rowids received from the first scan are expected in one unique tree
+ */
+ ha_rows elems_in_tree= common_info->search_scans[0]->records-
+ common_info->search_scans[0]->filtered_out ;
+ next->in_memory_cost+= Unique::get_search_cost(elems_in_tree,
+ common_info->compare_factor)*
+ ext_index_scan_records;
+ cost= next->in_memory_cost;
+ }
+ else
+ {
+ uint *buff_elems= common_info->buff_elems;
+ uint key_size= common_info->key_size;
+ uint compare_factor= common_info->compare_factor;
+ ulonglong max_memory_size= common_info->max_memory_size;
+
+ records_sent_to_unique+= ext_index_scan_records;
+ cost= Unique::get_use_cost(buff_elems, (size_t) records_sent_to_unique, key_size,
+ max_memory_size, compare_factor, TRUE,
+ &next->in_memory);
+ if (records_filtered_out_by_cpk)
+ {
+ /* Check whether using cpk filter for this scan is beneficial */
+
+ double cost2;
+ bool in_memory2;
+ ha_rows records2= records_sent_to_unique-records_filtered_out_by_cpk;
+ cost2= Unique::get_use_cost(buff_elems, (size_t) records2, key_size,
+ max_memory_size, compare_factor, TRUE,
+ &in_memory2);
+ cost2+= get_cpk_filter_cost(ext_index_scan_records, common_info->cpk_scan,
+ compare_factor);
+ if (cost > cost2 + COST_EPS)
+ {
+ cost= cost2;
+ next->in_memory= in_memory2;
+ next->use_cpk_filter= TRUE;
+ records_sent_to_unique= records2;
+ }
+
+ }
+ if (next->in_memory)
+ next->in_memory_cost= cost;
+ }
+
+ if (next->use_cpk_filter)
+ {
+ next->filtered_scans.set_bit(ext_index_scan->keynr);
+ bitmap_union(&ext_index_scan->used_fields,
+ &common_info->cpk_scan->used_fields);
+ }
+ next->records_sent_to_unique= records_sent_to_unique;
+
+ records= records_in_index_intersect_extension(curr, ext_index_scan);
+ if (idx && records > curr->records)
+ return FALSE;
+ if (next->use_cpk_filter && curr->filtered_scans.is_clear_all())
+ records-= records_filtered_out_by_cpk;
+ next->records= records;
+
+ cost+= next->index_read_cost;
+ if (cost >= cutoff_cost)
+ return FALSE;
+
+ cost+= get_sweep_read_cost(common_info->param, records);
+
+ next->cost= cost;
+ next->length= curr->length+1;
+
+ return TRUE;
+}
+
+
+/*
+ Search for the cheapest extensions of range scans used to access a table
+
+ SYNOPSIS
+ find_index_intersect_best_extension()
+ curr partial intersection to evaluate all possible extension for
+
+ DESCRIPTION
+ The function tries to extend the partial plan curr in all possible ways
+ to look for a cheapest index intersection whose cost less than the
+ cut off value set in curr->common_info.cutoff_cost.
+*/
+
+static
+void find_index_intersect_best_extension(PARTIAL_INDEX_INTERSECT_INFO *curr)
+{
+ PARTIAL_INDEX_INTERSECT_INFO next;
+ COMMON_INDEX_INTERSECT_INFO *common_info= curr->common_info;
+ INDEX_SCAN_INFO **index_scans= common_info->search_scans;
+ uint idx= curr->length;
+ INDEX_SCAN_INFO **rem_first_index_scan_ptr= &index_scans[idx];
+ double cost= curr->cost;
+
+ if (cost + COST_EPS < common_info->best_cost)
+ {
+ common_info->best_cost= cost;
+ common_info->best_length= curr->length;
+ common_info->best_records= curr->records;
+ common_info->filtered_scans= curr->filtered_scans;
+ /* common_info->best_uses_cpk <=> at least one scan uses a cpk filter */
+ common_info->best_uses_cpk= !curr->filtered_scans.is_clear_all();
+ uint sz= sizeof(INDEX_SCAN_INFO *) * curr->length;
+ memcpy(common_info->best_intersect, common_info->search_scans, sz);
+ common_info->cutoff_cost= cost;
+ }
+
+ if (!(*rem_first_index_scan_ptr))
+ return;
+
+ next.common_info= common_info;
+
+ INDEX_SCAN_INFO *rem_first_index_scan= *rem_first_index_scan_ptr;
+ for (INDEX_SCAN_INFO **index_scan_ptr= rem_first_index_scan_ptr;
+ *index_scan_ptr; index_scan_ptr++)
+ {
+ *rem_first_index_scan_ptr= *index_scan_ptr;
+ *index_scan_ptr= rem_first_index_scan;
+ if (check_index_intersect_extension(curr, *rem_first_index_scan_ptr, &next))
+ find_index_intersect_best_extension(&next);
+ *index_scan_ptr= *rem_first_index_scan_ptr;
+ *rem_first_index_scan_ptr= rem_first_index_scan;
+ }
+}
+
+
+/*
+ Get the plan of the best intersection of range scans used to access a table
+
+ SYNOPSIS
+ get_best_index_intersect()
+ param common info about index ranges
+ tree tree of ranges for indexes than can be intersected
+ read_time cut off value for the evaluated plans
+
+ DESCRIPTION
+ The function looks for the cheapest index intersection of the range
+ scans to access a table. The info about the ranges for all indexes
+ is provided by the range optimizer and is passed through the
+ parameters param and tree. Any plan whose cost is greater than read_time
+ is rejected.
+ After the best index intersection is found the function constructs
+ the structure that manages the execution by the chosen plan.
+
+ RETURN
+ Pointer to the generated execution structure if a success,
+ 0 - otherwise.
+*/
+
+static
+TRP_INDEX_INTERSECT *get_best_index_intersect(PARAM *param, SEL_TREE *tree,
+ double read_time)
+{
+ uint i;
+ uint count;
+ TRP_RANGE **cur_range;
+ TRP_RANGE **range_scans;
+ INDEX_SCAN_INFO *index_scan;
+ COMMON_INDEX_INTERSECT_INFO common;
+ PARTIAL_INDEX_INTERSECT_INFO init;
+ TRP_INDEX_INTERSECT *intersect_trp= NULL;
+ TABLE *table= param->table;
+
+
+ DBUG_ENTER("get_best_index_intersect");
+
+ if (prepare_search_best_index_intersect(param, tree, &common, &init,
+ read_time))
+ DBUG_RETURN(NULL);
+
+ find_index_intersect_best_extension(&init);
+
+ if (common.best_length <= 1 && !common.best_uses_cpk)
+ DBUG_RETURN(NULL);
+
+ if (common.best_uses_cpk)
+ {
+ memmove((char *) (common.best_intersect+1), (char *) common.best_intersect,
+ sizeof(INDEX_SCAN_INFO *) * common.best_length);
+ common.best_intersect[0]= common.cpk_scan;
+ common.best_length++;
+ }
+
+ count= common.best_length;
+
+ if (!(range_scans= (TRP_RANGE**)alloc_root(param->mem_root,
+ sizeof(TRP_RANGE *)*
+ count)))
+ DBUG_RETURN(NULL);
+
+ for (i= 0, cur_range= range_scans; i < count; i++)
+ {
+ index_scan= common.best_intersect[i];
+ if ((*cur_range= new (param->mem_root) TRP_RANGE(index_scan->sel_arg,
+ index_scan->idx, 0)))
+ {
+ TRP_RANGE *trp= *cur_range;
+ trp->read_cost= index_scan->index_read_cost;
+ trp->records= index_scan->records;
+ trp->is_ror= FALSE;
+ trp->mrr_buf_size= 0;
+ table->intersect_keys.set_bit(index_scan->keynr);
+ cur_range++;
+ }
+ }
+
+ count= tree->index_scans_end - tree->index_scans;
+ for (i= 0; i < count; i++)
+ {
+ index_scan= tree->index_scans[i];
+ if (!table->intersect_keys.is_set(index_scan->keynr))
+ {
+ for (uint j= 0; j < common.best_length; j++)
+ {
+ INDEX_SCAN_INFO *scan= common.best_intersect[j];
+ if (same_index_prefix(index_scan->key_info, scan->key_info,
+ scan->used_key_parts))
+ {
+ table->intersect_keys.set_bit(index_scan->keynr);
+ break;
+ }
+ }
+ }
+ }
+
+ if ((intersect_trp= new (param->mem_root)TRP_INDEX_INTERSECT))
+ {
+ intersect_trp->read_cost= common.best_cost;
+ intersect_trp->records= common.best_records;
+ intersect_trp->range_scans= range_scans;
+ intersect_trp->range_scans_end= cur_range;
+ intersect_trp->filtered_scans= common.filtered_scans;
+ }
+ DBUG_RETURN(intersect_trp);
+}
+
+
+typedef struct st_ror_scan_info : INDEX_SCAN_INFO
+{
+} ROR_SCAN_INFO;
+
+
+/*
+ Create ROR_SCAN_INFO* structure with a single ROR scan on index idx using
+ sel_arg set of intervals.
+
+ SYNOPSIS
+ make_ror_scan()
+ param Parameter from test_quick_select function
+ idx Index of key in param->keys
+ sel_arg Set of intervals for a given key
+
+ RETURN
+ NULL - out of memory
+ ROR scan structure containing a scan for {idx, sel_arg}
+*/
+
+static
+ROR_SCAN_INFO *make_ror_scan(const PARAM *param, int idx, SEL_ARG *sel_arg)
+{
+ ROR_SCAN_INFO *ror_scan;
+ my_bitmap_map *bitmap_buf;
+ uint keynr;
+ DBUG_ENTER("make_ror_scan");
+
+ if (!(ror_scan= (ROR_SCAN_INFO*)alloc_root(param->mem_root,
+ sizeof(ROR_SCAN_INFO))))
+ DBUG_RETURN(NULL);
+
+ ror_scan->idx= idx;
+ ror_scan->keynr= keynr= param->real_keynr[idx];
+ ror_scan->key_rec_length= (param->table->key_info[keynr].key_length +
+ param->table->file->ref_length);
+ ror_scan->sel_arg= sel_arg;
+ ror_scan->records= param->quick_rows[keynr];
+
+ if (!(bitmap_buf= (my_bitmap_map*) alloc_root(param->mem_root,
+ param->fields_bitmap_size)))
+ DBUG_RETURN(NULL);
+
+ if (my_bitmap_init(&ror_scan->covered_fields, bitmap_buf,
+ param->table->s->fields, FALSE))
+ DBUG_RETURN(NULL);
+ bitmap_clear_all(&ror_scan->covered_fields);
+
+ KEY_PART_INFO *key_part= param->table->key_info[keynr].key_part;
+ KEY_PART_INFO *key_part_end= key_part +
+ param->table->key_info[keynr].user_defined_key_parts;
+ for (;key_part != key_part_end; ++key_part)
+ {
+ if (bitmap_is_set(&param->needed_fields, key_part->fieldnr-1))
+ bitmap_set_bit(&ror_scan->covered_fields, key_part->fieldnr-1);
+ }
+ ror_scan->index_read_cost=
+ param->table->file->keyread_time(ror_scan->keynr, 1, ror_scan->records);
+ DBUG_RETURN(ror_scan);
+}
+
+
+/*
+ Compare two ROR_SCAN_INFO** by E(#records_matched) * key_record_length.
+ SYNOPSIS
+ cmp_ror_scan_info()
+ a ptr to first compared value
+ b ptr to second compared value
+
+ RETURN
+ -1 a < b
+ 0 a = b
+ 1 a > b
+*/
+
+static int cmp_ror_scan_info(ROR_SCAN_INFO** a, ROR_SCAN_INFO** b)
+{
+ double val1= rows2double((*a)->records) * (*a)->key_rec_length;
+ double val2= rows2double((*b)->records) * (*b)->key_rec_length;
+ return (val1 < val2)? -1: (val1 == val2)? 0 : 1;
+}
+
+/*
+ Compare two ROR_SCAN_INFO** by
+ (#covered fields in F desc,
+ #components asc,
+ number of first not covered component asc)
+
+ SYNOPSIS
+ cmp_ror_scan_info_covering()
+ a ptr to first compared value
+ b ptr to second compared value
+
+ RETURN
+ -1 a < b
+ 0 a = b
+ 1 a > b
+*/
+
+static int cmp_ror_scan_info_covering(ROR_SCAN_INFO** a, ROR_SCAN_INFO** b)
+{
+ if ((*a)->used_fields_covered > (*b)->used_fields_covered)
+ return -1;
+ if ((*a)->used_fields_covered < (*b)->used_fields_covered)
+ return 1;
+ if ((*a)->key_components < (*b)->key_components)
+ return -1;
+ if ((*a)->key_components > (*b)->key_components)
+ return 1;
+ if ((*a)->first_uncovered_field < (*b)->first_uncovered_field)
+ return -1;
+ if ((*a)->first_uncovered_field > (*b)->first_uncovered_field)
+ return 1;
+ return 0;
+}
+
+
+/* Auxiliary structure for incremental ROR-intersection creation */
+typedef struct
+{
+ const PARAM *param;
+ MY_BITMAP covered_fields; /* union of fields covered by all scans */
+ /*
+ Fraction of table records that satisfies conditions of all scans.
+ This is the number of full records that will be retrieved if a
+ non-index_only index intersection will be employed.
+ */
+ double out_rows;
+ /* TRUE if covered_fields is a superset of needed_fields */
+ bool is_covering;
+
+ ha_rows index_records; /* sum(#records to look in indexes) */
+ double index_scan_costs; /* SUM(cost of 'index-only' scans) */
+ double total_cost;
+} ROR_INTERSECT_INFO;
+
+
+/*
+ Allocate a ROR_INTERSECT_INFO and initialize it to contain zero scans.
+
+ SYNOPSIS
+ ror_intersect_init()
+ param Parameter from test_quick_select
+
+ RETURN
+ allocated structure
+ NULL on error
+*/
+
+static
+ROR_INTERSECT_INFO* ror_intersect_init(const PARAM *param)
+{
+ ROR_INTERSECT_INFO *info;
+ my_bitmap_map* buf;
+ if (!(info= (ROR_INTERSECT_INFO*)alloc_root(param->mem_root,
+ sizeof(ROR_INTERSECT_INFO))))
+ return NULL;
+ info->param= param;
+ if (!(buf= (my_bitmap_map*) alloc_root(param->mem_root,
+ param->fields_bitmap_size)))
+ return NULL;
+ if (my_bitmap_init(&info->covered_fields, buf, param->table->s->fields,
+ FALSE))
+ return NULL;
+ info->is_covering= FALSE;
+ info->index_scan_costs= 0.0;
+ info->index_records= 0;
+ info->out_rows= (double) param->table->stat_records();
+ bitmap_clear_all(&info->covered_fields);
+ return info;
+}
+
+void ror_intersect_cpy(ROR_INTERSECT_INFO *dst, const ROR_INTERSECT_INFO *src)
+{
+ dst->param= src->param;
+ memcpy(dst->covered_fields.bitmap, src->covered_fields.bitmap,
+ no_bytes_in_map(&src->covered_fields));
+ dst->out_rows= src->out_rows;
+ dst->is_covering= src->is_covering;
+ dst->index_records= src->index_records;
+ dst->index_scan_costs= src->index_scan_costs;
+ dst->total_cost= src->total_cost;
+}
+
+
+/*
+ Get selectivity of a ROR scan wrt ROR-intersection.
+
+ SYNOPSIS
+ ror_scan_selectivity()
+ info ROR-interection
+ scan ROR scan
+
+ NOTES
+ Suppose we have a condition on several keys
+ cond=k_11=c_11 AND k_12=c_12 AND ... // parts of first key
+ k_21=c_21 AND k_22=c_22 AND ... // parts of second key
+ ...
+ k_n1=c_n1 AND k_n3=c_n3 AND ... (1) //parts of the key used by *scan
+
+ where k_ij may be the same as any k_pq (i.e. keys may have common parts).
+
+ A full row is retrieved if entire condition holds.
+
+ The recursive procedure for finding P(cond) is as follows:
+
+ First step:
+ Pick 1st part of 1st key and break conjunction (1) into two parts:
+ cond= (k_11=c_11 AND R)
+
+ Here R may still contain condition(s) equivalent to k_11=c_11.
+ Nevertheless, the following holds:
+
+ P(k_11=c_11 AND R) = P(k_11=c_11) * P(R | k_11=c_11).
+
+ Mark k_11 as fixed field (and satisfied condition) F, save P(F),
+ save R to be cond and proceed to recursion step.
+
+ Recursion step:
+ We have a set of fixed fields/satisfied conditions) F, probability P(F),
+ and remaining conjunction R
+ Pick next key part on current key and its condition "k_ij=c_ij".
+ We will add "k_ij=c_ij" into F and update P(F).
+ Lets denote k_ij as t, R = t AND R1, where R1 may still contain t. Then
+
+ P((t AND R1)|F) = P(t|F) * P(R1|t|F) = P(t|F) * P(R1|(t AND F)) (2)
+
+ (where '|' mean conditional probability, not "or")
+
+ Consider the first multiplier in (2). One of the following holds:
+ a) F contains condition on field used in t (i.e. t AND F = F).
+ Then P(t|F) = 1
+
+ b) F doesn't contain condition on field used in t. Then F and t are
+ considered independent.
+
+ P(t|F) = P(t|(fields_before_t_in_key AND other_fields)) =
+ = P(t|fields_before_t_in_key).
+
+ P(t|fields_before_t_in_key) = #records(fields_before_t_in_key) /
+ #records(fields_before_t_in_key, t)
+
+ The second multiplier is calculated by applying this step recursively.
+
+ IMPLEMENTATION
+ This function calculates the result of application of the "recursion step"
+ described above for all fixed key members of a single key, accumulating set
+ of covered fields, selectivity, etc.
+
+ The calculation is conducted as follows:
+ Lets denote #records(keypart1, ... keypartK) as n_k. We need to calculate
+
+ n_{k1} n_{k2}
+ --------- * --------- * .... (3)
+ n_{k1-1} n_{k2-1}
+
+ where k1,k2,... are key parts which fields were not yet marked as fixed
+ ( this is result of application of option b) of the recursion step for
+ parts of a single key).
+ Since it is reasonable to expect that most of the fields are not marked
+ as fixed, we calculate (3) as
+
+ n_{i1} n_{i2}
+ (3) = n_{max_key_part} / ( --------- * --------- * .... )
+ n_{i1-1} n_{i2-1}
+
+ where i1,i2, .. are key parts that were already marked as fixed.
+
+ In order to minimize number of expensive records_in_range calls we group
+ and reduce adjacent fractions.
+
+ RETURN
+ Selectivity of given ROR scan.
+*/
+
+static double ror_scan_selectivity(const ROR_INTERSECT_INFO *info,
+ const ROR_SCAN_INFO *scan)
+{
+ double selectivity_mult= 1.0;
+ KEY_PART_INFO *key_part= info->param->table->key_info[scan->keynr].key_part;
+ uchar key_val[MAX_KEY_LENGTH+MAX_FIELD_WIDTH]; /* key values tuple */
+ uchar *key_ptr= key_val;
+ SEL_ARG *sel_arg, *tuple_arg= NULL;
+ key_part_map keypart_map= 0;
+ bool cur_covered;
+ bool prev_covered= MY_TEST(bitmap_is_set(&info->covered_fields,
+ key_part->fieldnr - 1));
+ key_range min_range;
+ key_range max_range;
+ min_range.key= key_val;
+ min_range.flag= HA_READ_KEY_EXACT;
+ max_range.key= key_val;
+ max_range.flag= HA_READ_AFTER_KEY;
+ ha_rows prev_records= info->param->table->stat_records();
+ DBUG_ENTER("ror_scan_selectivity");
+
+ for (sel_arg= scan->sel_arg; sel_arg;
+ sel_arg= sel_arg->next_key_part)
+ {
+ DBUG_PRINT("info",("sel_arg step"));
+ cur_covered= MY_TEST(bitmap_is_set(&info->covered_fields,
+ key_part[sel_arg->part].fieldnr - 1));
+ if (cur_covered != prev_covered)
+ {
+ /* create (part1val, ..., part{n-1}val) tuple. */
+ ha_rows records;
+ if (!tuple_arg)
+ {
+ tuple_arg= scan->sel_arg;
+ /* Here we use the length of the first key part */
+ tuple_arg->store_min(key_part->store_length, &key_ptr, 0);
+ keypart_map= 1;
+ }
+ while (tuple_arg->next_key_part != sel_arg)
+ {
+ tuple_arg= tuple_arg->next_key_part;
+ tuple_arg->store_min(key_part[tuple_arg->part].store_length,
+ &key_ptr, 0);
+ keypart_map= (keypart_map << 1) | 1;
+ }
+ min_range.length= max_range.length= (size_t) (key_ptr - key_val);
+ min_range.keypart_map= max_range.keypart_map= keypart_map;
+ records= (info->param->table->file->
+ records_in_range(scan->keynr, &min_range, &max_range));
+ if (cur_covered)
+ {
+ /* uncovered -> covered */
+ double tmp= rows2double(records)/rows2double(prev_records);
+ DBUG_PRINT("info", ("Selectivity multiplier: %g", tmp));
+ selectivity_mult *= tmp;
+ prev_records= HA_POS_ERROR;
+ }
+ else
+ {
+ /* covered -> uncovered */
+ prev_records= records;
+ }
+ }
+ prev_covered= cur_covered;
+ }
+ if (!prev_covered)
+ {
+ double tmp= rows2double(info->param->quick_rows[scan->keynr]) /
+ rows2double(prev_records);
+ DBUG_PRINT("info", ("Selectivity multiplier: %g", tmp));
+ selectivity_mult *= tmp;
+ }
+ DBUG_PRINT("info", ("Returning multiplier: %g", selectivity_mult));
+ DBUG_RETURN(selectivity_mult);
+}
+
+
+/*
+ Check if adding a ROR scan to a ROR-intersection reduces its cost of
+ ROR-intersection and if yes, update parameters of ROR-intersection,
+ including its cost.
+
+ SYNOPSIS
+ ror_intersect_add()
+ param Parameter from test_quick_select
+ info ROR-intersection structure to add the scan to.
+ ror_scan ROR scan info to add.
+ is_cpk_scan If TRUE, add the scan as CPK scan (this can be inferred
+ from other parameters and is passed separately only to
+ avoid duplicating the inference code)
+
+ NOTES
+ Adding a ROR scan to ROR-intersect "makes sense" iff the cost of ROR-
+ intersection decreases. The cost of ROR-intersection is calculated as
+ follows:
+
+ cost= SUM_i(key_scan_cost_i) + cost_of_full_rows_retrieval
+
+ When we add a scan the first increases and the second decreases.
+
+ cost_of_full_rows_retrieval=
+ (union of indexes used covers all needed fields) ?
+ cost_of_sweep_read(E(rows_to_retrieve), rows_in_table) :
+ 0
+
+ E(rows_to_retrieve) = #rows_in_table * ror_scan_selectivity(null, scan1) *
+ ror_scan_selectivity({scan1}, scan2) * ... *
+ ror_scan_selectivity({scan1,...}, scanN).
+ RETURN
+ TRUE ROR scan added to ROR-intersection, cost updated.
+ FALSE It doesn't make sense to add this ROR scan to this ROR-intersection.
+*/
+
+static bool ror_intersect_add(ROR_INTERSECT_INFO *info,
+ ROR_SCAN_INFO* ror_scan, bool is_cpk_scan)
+{
+ double selectivity_mult= 1.0;
+
+ DBUG_ENTER("ror_intersect_add");
+ DBUG_PRINT("info", ("Current out_rows= %g", info->out_rows));
+ DBUG_PRINT("info", ("Adding scan on %s",
+ info->param->table->key_info[ror_scan->keynr].name));
+ DBUG_PRINT("info", ("is_cpk_scan: %d",is_cpk_scan));
+
+ selectivity_mult = ror_scan_selectivity(info, ror_scan);
+ if (selectivity_mult == 1.0)
+ {
+ /* Don't add this scan if it doesn't improve selectivity. */
+ DBUG_PRINT("info", ("The scan doesn't improve selectivity."));
+ DBUG_RETURN(FALSE);
+ }
+
+ info->out_rows *= selectivity_mult;
+
+ if (is_cpk_scan)
+ {
+ /*
+ CPK scan is used to filter out rows. We apply filtering for
+ each record of every scan. Assuming 1/TIME_FOR_COMPARE_ROWID
+ per check this gives us:
+ */
+ info->index_scan_costs += rows2double(info->index_records) /
+ TIME_FOR_COMPARE_ROWID;
+ }
+ else
+ {
+ info->index_records += info->param->quick_rows[ror_scan->keynr];
+ info->index_scan_costs += ror_scan->index_read_cost;
+ bitmap_union(&info->covered_fields, &ror_scan->covered_fields);
+ if (!info->is_covering && bitmap_is_subset(&info->param->needed_fields,
+ &info->covered_fields))
+ {
+ DBUG_PRINT("info", ("ROR-intersect is covering now"));
+ info->is_covering= TRUE;
+ }
+ }
+
+ info->total_cost= info->index_scan_costs;
+ DBUG_PRINT("info", ("info->total_cost: %g", info->total_cost));
+ if (!info->is_covering)
+ {
+ info->total_cost +=
+ get_sweep_read_cost(info->param, double2rows(info->out_rows));
+ DBUG_PRINT("info", ("info->total_cost= %g", info->total_cost));
+ }
+ DBUG_PRINT("info", ("New out_rows: %g", info->out_rows));
+ DBUG_PRINT("info", ("New cost: %g, %scovering", info->total_cost,
+ info->is_covering?"" : "non-"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Get best ROR-intersection plan using non-covering ROR-intersection search
+ algorithm. The returned plan may be covering.
+
+ SYNOPSIS
+ get_best_ror_intersect()
+ param Parameter from test_quick_select function.
+ tree Transformed restriction condition to be used to look
+ for ROR scans.
+ read_time Do not return read plans with cost > read_time.
+ are_all_covering [out] set to TRUE if union of all scans covers all
+ fields needed by the query (and it is possible to build
+ a covering ROR-intersection)
+
+ NOTES
+ get_key_scans_params must be called before this function can be called.
+
+ When this function is called by ROR-union construction algorithm it
+ assumes it is building an uncovered ROR-intersection (and thus # of full
+ records to be retrieved is wrong here). This is a hack.
+
+ IMPLEMENTATION
+ The approximate best non-covering plan search algorithm is as follows:
+
+ find_min_ror_intersection_scan()
+ {
+ R= select all ROR scans;
+ order R by (E(#records_matched) * key_record_length).
+
+ S= first(R); -- set of scans that will be used for ROR-intersection
+ R= R-first(S);
+ min_cost= cost(S);
+ min_scan= make_scan(S);
+ while (R is not empty)
+ {
+ firstR= R - first(R);
+ if (!selectivity(S + firstR < selectivity(S)))
+ continue;
+
+ S= S + first(R);
+ if (cost(S) < min_cost)
+ {
+ min_cost= cost(S);
+ min_scan= make_scan(S);
+ }
+ }
+ return min_scan;
+ }
+
+ See ror_intersect_add function for ROR intersection costs.
+
+ Special handling for Clustered PK scans
+ Clustered PK contains all table fields, so using it as a regular scan in
+ index intersection doesn't make sense: a range scan on CPK will be less
+ expensive in this case.
+ Clustered PK scan has special handling in ROR-intersection: it is not used
+ to retrieve rows, instead its condition is used to filter row references
+ we get from scans on other keys.
+
+ RETURN
+ ROR-intersection table read plan
+ NULL if out of memory or no suitable plan found.
+*/
+
+static
+TRP_ROR_INTERSECT *get_best_ror_intersect(const PARAM *param, SEL_TREE *tree,
+ double read_time,
+ bool *are_all_covering)
+{
+ uint idx;
+ double min_cost= DBL_MAX;
+ DBUG_ENTER("get_best_ror_intersect");
+
+ if ((tree->n_ror_scans < 2) || !param->table->stat_records() ||
+ !optimizer_flag(param->thd, OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT))
+ DBUG_RETURN(NULL);
+
+ /*
+ Step1: Collect ROR-able SEL_ARGs and create ROR_SCAN_INFO for each of
+ them. Also find and save clustered PK scan if there is one.
+ */
+ ROR_SCAN_INFO **cur_ror_scan;
+ ROR_SCAN_INFO *cpk_scan= NULL;
+ uint cpk_no;
+
+ if (!(tree->ror_scans= (ROR_SCAN_INFO**)alloc_root(param->mem_root,
+ sizeof(ROR_SCAN_INFO*)*
+ param->keys)))
+ return NULL;
+ cpk_no= ((param->table->file->primary_key_is_clustered()) ?
+ param->table->s->primary_key : MAX_KEY);
+
+ for (idx= 0, cur_ror_scan= tree->ror_scans; idx < param->keys; idx++)
+ {
+ ROR_SCAN_INFO *scan;
+ uint key_no;
+ if (!tree->ror_scans_map.is_set(idx))
+ continue;
+ key_no= param->real_keynr[idx];
+ if (key_no != cpk_no &&
+ param->table->file->index_flags(key_no,0,0) & HA_CLUSTERED_INDEX)
+ {
+ /* Ignore clustering keys */
+ tree->n_ror_scans--;
+ continue;
+ }
+ if (!(scan= make_ror_scan(param, idx, tree->keys[idx])))
+ return NULL;
+ if (key_no == cpk_no)
+ {
+ cpk_scan= scan;
+ tree->n_ror_scans--;
+ }
+ else
+ *(cur_ror_scan++)= scan;
+ }
+
+ tree->ror_scans_end= cur_ror_scan;
+ DBUG_EXECUTE("info",print_ror_scans_arr(param->table, "original",
+ tree->ror_scans,
+ tree->ror_scans_end););
+ /*
+ Ok, [ror_scans, ror_scans_end) is array of ptrs to initialized
+ ROR_SCAN_INFO's.
+ Step 2: Get best ROR-intersection using an approximate algorithm.
+ */
+ my_qsort(tree->ror_scans, tree->n_ror_scans, sizeof(ROR_SCAN_INFO*),
+ (qsort_cmp)cmp_ror_scan_info);
+ DBUG_EXECUTE("info",print_ror_scans_arr(param->table, "ordered",
+ tree->ror_scans,
+ tree->ror_scans_end););
+
+ ROR_SCAN_INFO **intersect_scans; /* ROR scans used in index intersection */
+ ROR_SCAN_INFO **intersect_scans_end;
+ if (!(intersect_scans= (ROR_SCAN_INFO**)alloc_root(param->mem_root,
+ sizeof(ROR_SCAN_INFO*)*
+ tree->n_ror_scans)))
+ return NULL;
+ intersect_scans_end= intersect_scans;
+
+ /* Create and incrementally update ROR intersection. */
+ ROR_INTERSECT_INFO *intersect, *intersect_best;
+ if (!(intersect= ror_intersect_init(param)) ||
+ !(intersect_best= ror_intersect_init(param)))
+ return NULL;
+
+ /* [intersect_scans,intersect_scans_best) will hold the best intersection */
+ ROR_SCAN_INFO **intersect_scans_best;
+ cur_ror_scan= tree->ror_scans;
+ intersect_scans_best= intersect_scans;
+ while (cur_ror_scan != tree->ror_scans_end && !intersect->is_covering)
+ {
+ /* S= S + first(R); R= R - first(R); */
+ if (!ror_intersect_add(intersect, *cur_ror_scan, FALSE))
+ {
+ cur_ror_scan++;
+ continue;
+ }
+
+ *(intersect_scans_end++)= *(cur_ror_scan++);
+
+ if (intersect->total_cost < min_cost)
+ {
+ /* Local minimum found, save it */
+ ror_intersect_cpy(intersect_best, intersect);
+ intersect_scans_best= intersect_scans_end;
+ min_cost = intersect->total_cost;
+ }
+ }
+
+ if (intersect_scans_best == intersect_scans)
+ {
+ DBUG_PRINT("info", ("None of scans increase selectivity"));
+ DBUG_RETURN(NULL);
+ }
+
+ DBUG_EXECUTE("info",print_ror_scans_arr(param->table,
+ "best ROR-intersection",
+ intersect_scans,
+ intersect_scans_best););
+
+ *are_all_covering= intersect->is_covering;
+ uint best_num= intersect_scans_best - intersect_scans;
+ ror_intersect_cpy(intersect, intersect_best);
+
+ /*
+ Ok, found the best ROR-intersection of non-CPK key scans.
+ Check if we should add a CPK scan. If the obtained ROR-intersection is
+ covering, it doesn't make sense to add CPK scan.
+ */
+ if (cpk_scan && !intersect->is_covering)
+ {
+ if (ror_intersect_add(intersect, cpk_scan, TRUE) &&
+ (intersect->total_cost < min_cost))
+ intersect_best= intersect; //just set pointer here
+ }
+ else
+ cpk_scan= 0; // Don't use cpk_scan
+
+ /* Ok, return ROR-intersect plan if we have found one */
+ TRP_ROR_INTERSECT *trp= NULL;
+ if (min_cost < read_time && (cpk_scan || best_num > 1))
+ {
+ if (!(trp= new (param->mem_root) TRP_ROR_INTERSECT))
+ DBUG_RETURN(trp);
+ if (!(trp->first_scan=
+ (ROR_SCAN_INFO**)alloc_root(param->mem_root,
+ sizeof(ROR_SCAN_INFO*)*best_num)))
+ DBUG_RETURN(NULL);
+ memcpy(trp->first_scan, intersect_scans, best_num*sizeof(ROR_SCAN_INFO*));
+ trp->last_scan= trp->first_scan + best_num;
+ trp->is_covering= intersect_best->is_covering;
+ trp->read_cost= intersect_best->total_cost;
+ /* Prevent divisons by zero */
+ ha_rows best_rows = double2rows(intersect_best->out_rows);
+ if (!best_rows)
+ best_rows= 1;
+ set_if_smaller(param->table->quick_condition_rows, best_rows);
+ trp->records= best_rows;
+ trp->index_scan_costs= intersect_best->index_scan_costs;
+ trp->cpk_scan= cpk_scan;
+ DBUG_PRINT("info", ("Returning non-covering ROR-intersect plan:"
+ "cost %g, records %lu",
+ trp->read_cost, (ulong) trp->records));
+ }
+ DBUG_RETURN(trp);
+}
+
+
+/*
+ Get best covering ROR-intersection.
+ SYNOPSIS
+ get_best_ntersectcovering_ror_intersect()
+ param Parameter from test_quick_select function.
+ tree SEL_TREE with sets of intervals for different keys.
+ read_time Don't return table read plans with cost > read_time.
+
+ RETURN
+ Best covering ROR-intersection plan
+ NULL if no plan found.
+
+ NOTES
+ get_best_ror_intersect must be called for a tree before calling this
+ function for it.
+ This function invalidates tree->ror_scans member values.
+
+ The following approximate algorithm is used:
+ I=set of all covering indexes
+ F=set of all fields to cover
+ S={}
+
+ do
+ {
+ Order I by (#covered fields in F desc,
+ #components asc,
+ number of first not covered component asc);
+ F=F-covered by first(I);
+ S=S+first(I);
+ I=I-first(I);
+ } while F is not empty.
+*/
+
+static
+TRP_ROR_INTERSECT *get_best_covering_ror_intersect(PARAM *param,
+ SEL_TREE *tree,
+ double read_time)
+{
+ ROR_SCAN_INFO **ror_scan_mark;
+ ROR_SCAN_INFO **ror_scans_end= tree->ror_scans_end;
+ DBUG_ENTER("get_best_covering_ror_intersect");
+
+ if (!optimizer_flag(param->thd, OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT))
+ DBUG_RETURN(NULL);
+
+ for (ROR_SCAN_INFO **scan= tree->ror_scans; scan != ror_scans_end; ++scan)
+ (*scan)->key_components=
+ param->table->key_info[(*scan)->keynr].user_defined_key_parts;
+
+ /*
+ Run covering-ROR-search algorithm.
+ Assume set I is [ror_scan .. ror_scans_end)
+ */
+
+ /*I=set of all covering indexes */
+ ror_scan_mark= tree->ror_scans;
+
+ MY_BITMAP *covered_fields= &param->tmp_covered_fields;
+ if (!covered_fields->bitmap)
+ covered_fields->bitmap= (my_bitmap_map*)alloc_root(param->mem_root,
+ param->fields_bitmap_size);
+ if (!covered_fields->bitmap ||
+ my_bitmap_init(covered_fields, covered_fields->bitmap,
+ param->table->s->fields, FALSE))
+ DBUG_RETURN(0);
+ bitmap_clear_all(covered_fields);
+
+ double total_cost= 0.0f;
+ ha_rows records=0;
+ bool all_covered;
+
+ DBUG_PRINT("info", ("Building covering ROR-intersection"));
+ DBUG_EXECUTE("info", print_ror_scans_arr(param->table,
+ "building covering ROR-I",
+ ror_scan_mark, ror_scans_end););
+ do
+ {
+ /*
+ Update changed sorting info:
+ #covered fields,
+ number of first not covered component
+ Calculate and save these values for each of remaining scans.
+ */
+ for (ROR_SCAN_INFO **scan= ror_scan_mark; scan != ror_scans_end; ++scan)
+ {
+ bitmap_subtract(&(*scan)->covered_fields, covered_fields);
+ (*scan)->used_fields_covered=
+ bitmap_bits_set(&(*scan)->covered_fields);
+ (*scan)->first_uncovered_field=
+ bitmap_get_first(&(*scan)->covered_fields);
+ }
+
+ my_qsort(ror_scan_mark, ror_scans_end-ror_scan_mark, sizeof(ROR_SCAN_INFO*),
+ (qsort_cmp)cmp_ror_scan_info_covering);
+
+ DBUG_EXECUTE("info", print_ror_scans_arr(param->table,
+ "remaining scans",
+ ror_scan_mark, ror_scans_end););
+
+ /* I=I-first(I) */
+ total_cost += (*ror_scan_mark)->index_read_cost;
+ records += (*ror_scan_mark)->records;
+ DBUG_PRINT("info", ("Adding scan on %s",
+ param->table->key_info[(*ror_scan_mark)->keynr].name));
+ if (total_cost > read_time)
+ DBUG_RETURN(NULL);
+ /* F=F-covered by first(I) */
+ bitmap_union(covered_fields, &(*ror_scan_mark)->covered_fields);
+ all_covered= bitmap_is_subset(&param->needed_fields, covered_fields);
+ } while ((++ror_scan_mark < ror_scans_end) && !all_covered);
+
+ if (!all_covered || (ror_scan_mark - tree->ror_scans) == 1)
+ DBUG_RETURN(NULL);
+
+ /*
+ Ok, [tree->ror_scans .. ror_scan) holds covering index_intersection with
+ cost total_cost.
+ */
+ DBUG_PRINT("info", ("Covering ROR-intersect scans cost: %g", total_cost));
+ DBUG_EXECUTE("info", print_ror_scans_arr(param->table,
+ "creating covering ROR-intersect",
+ tree->ror_scans, ror_scan_mark););
+
+ /* Add priority queue use cost. */
+ total_cost += rows2double(records)*
+ log((double)(ror_scan_mark - tree->ror_scans)) /
+ (TIME_FOR_COMPARE_ROWID * M_LN2);
+ DBUG_PRINT("info", ("Covering ROR-intersect full cost: %g", total_cost));
+
+ if (total_cost > read_time)
+ DBUG_RETURN(NULL);
+
+ TRP_ROR_INTERSECT *trp;
+ if (!(trp= new (param->mem_root) TRP_ROR_INTERSECT))
+ DBUG_RETURN(trp);
+ uint best_num= (ror_scan_mark - tree->ror_scans);
+ if (!(trp->first_scan= (ROR_SCAN_INFO**)alloc_root(param->mem_root,
+ sizeof(ROR_SCAN_INFO*)*
+ best_num)))
+ DBUG_RETURN(NULL);
+ memcpy(trp->first_scan, tree->ror_scans, best_num*sizeof(ROR_SCAN_INFO*));
+ trp->last_scan= trp->first_scan + best_num;
+ trp->is_covering= TRUE;
+ trp->read_cost= total_cost;
+ trp->records= records;
+ trp->cpk_scan= NULL;
+ set_if_smaller(param->table->quick_condition_rows, records);
+
+ DBUG_PRINT("info",
+ ("Returning covering ROR-intersect plan: cost %g, records %lu",
+ trp->read_cost, (ulong) trp->records));
+ DBUG_RETURN(trp);
+}
+
+
+/*
+ Get best "range" table read plan for given SEL_TREE.
+ Also update PARAM members and store ROR scans info in the SEL_TREE.
+ SYNOPSIS
+ get_key_scans_params
+ param parameters from test_quick_select
+ tree make range select for this SEL_TREE
+ index_read_must_be_used if TRUE, assume 'index only' option will be set
+ (except for clustered PK indexes)
+ read_time don't create read plans with cost > read_time.
+ RETURN
+ Best range read plan
+ NULL if no plan found or error occurred
+*/
+
+static TRP_RANGE *get_key_scans_params(PARAM *param, SEL_TREE *tree,
+ bool index_read_must_be_used,
+ bool update_tbl_stats,
+ double read_time)
+{
+ uint idx;
+ SEL_ARG **key,**end, **key_to_read= NULL;
+ ha_rows UNINIT_VAR(best_records); /* protected by key_to_read */
+ uint UNINIT_VAR(best_mrr_flags), /* protected by key_to_read */
+ UNINIT_VAR(best_buf_size); /* protected by key_to_read */
+ TRP_RANGE* read_plan= NULL;
+ DBUG_ENTER("get_key_scans_params");
+ /*
+ Note that there may be trees that have type SEL_TREE::KEY but contain no
+ key reads at all, e.g. tree for expression "key1 is not null" where key1
+ is defined as "not null".
+ */
+ DBUG_EXECUTE("info", print_sel_tree(param, tree, &tree->keys_map,
+ "tree scans"););
+ tree->ror_scans_map.clear_all();
+ tree->n_ror_scans= 0;
+ tree->index_scans= 0;
+ if (!tree->keys_map.is_clear_all())
+ {
+ tree->index_scans=
+ (INDEX_SCAN_INFO **) alloc_root(param->mem_root,
+ sizeof(INDEX_SCAN_INFO *) * param->keys);
+ }
+ tree->index_scans_end= tree->index_scans;
+ for (idx= 0,key=tree->keys, end=key+param->keys; key != end; key++,idx++)
+ {
+ if (*key)
+ {
+ ha_rows found_records;
+ Cost_estimate cost;
+ double found_read_time;
+ uint mrr_flags, buf_size;
+ INDEX_SCAN_INFO *index_scan;
+ uint keynr= param->real_keynr[idx];
+ if ((*key)->type == SEL_ARG::MAYBE_KEY ||
+ (*key)->maybe_flag)
+ param->needed_reg->set_bit(keynr);
+
+ bool read_index_only= index_read_must_be_used ? TRUE :
+ (bool) param->table->covering_keys.is_set(keynr);
+
+ found_records= check_quick_select(param, idx, read_index_only, *key,
+ update_tbl_stats, &mrr_flags,
+ &buf_size, &cost);
+
+ if (found_records != HA_POS_ERROR && tree->index_scans &&
+ (index_scan= (INDEX_SCAN_INFO *)alloc_root(param->mem_root,
+ sizeof(INDEX_SCAN_INFO))))
+ {
+ index_scan->idx= idx;
+ index_scan->keynr= keynr;
+ index_scan->key_info= &param->table->key_info[keynr];
+ index_scan->used_key_parts= param->max_key_part+1;
+ index_scan->range_count= param->range_count;
+ index_scan->records= found_records;
+ index_scan->sel_arg= *key;
+ *tree->index_scans_end++= index_scan;
+ }
+ if ((found_records != HA_POS_ERROR) && param->is_ror_scan)
+ {
+ tree->n_ror_scans++;
+ tree->ror_scans_map.set_bit(idx);
+ }
+ if (found_records != HA_POS_ERROR &&
+ read_time > (found_read_time= cost.total_cost()))
+ {
+ read_time= found_read_time;
+ best_records= found_records;
+ key_to_read= key;
+ best_mrr_flags= mrr_flags;
+ best_buf_size= buf_size;
+ }
+ }
+ }
+
+ DBUG_EXECUTE("info", print_sel_tree(param, tree, &tree->ror_scans_map,
+ "ROR scans"););
+ if (key_to_read)
+ {
+ idx= key_to_read - tree->keys;
+ if ((read_plan= new (param->mem_root) TRP_RANGE(*key_to_read, idx,
+ best_mrr_flags)))
+ {
+ read_plan->records= best_records;
+ read_plan->is_ror= tree->ror_scans_map.is_set(idx);
+ read_plan->read_cost= read_time;
+ read_plan->mrr_buf_size= best_buf_size;
+ DBUG_PRINT("info",
+ ("Returning range plan for key %s, cost %g, records %lu",
+ param->table->key_info[param->real_keynr[idx]].name,
+ read_plan->read_cost, (ulong) read_plan->records));
+ }
+ }
+ else
+ DBUG_PRINT("info", ("No 'range' table read plan found"));
+
+ DBUG_RETURN(read_plan);
+}
+
+
+QUICK_SELECT_I *TRP_INDEX_MERGE::make_quick(PARAM *param,
+ bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc)
+{
+ QUICK_INDEX_MERGE_SELECT *quick_imerge;
+ QUICK_RANGE_SELECT *quick;
+ /* index_merge always retrieves full rows, ignore retrieve_full_rows */
+ if (!(quick_imerge= new QUICK_INDEX_MERGE_SELECT(param->thd, param->table)))
+ return NULL;
+
+ quick_imerge->records= records;
+ quick_imerge->read_time= read_cost;
+ for (TRP_RANGE **range_scan= range_scans; range_scan != range_scans_end;
+ range_scan++)
+ {
+ if (!(quick= (QUICK_RANGE_SELECT*)
+ ((*range_scan)->make_quick(param, FALSE, &quick_imerge->alloc)))||
+ quick_imerge->push_quick_back(quick))
+ {
+ delete quick;
+ delete quick_imerge;
+ return NULL;
+ }
+ }
+ return quick_imerge;
+}
+
+
+QUICK_SELECT_I *TRP_INDEX_INTERSECT::make_quick(PARAM *param,
+ bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc)
+{
+ QUICK_INDEX_INTERSECT_SELECT *quick_intersect;
+ QUICK_RANGE_SELECT *quick;
+ /* index_merge always retrieves full rows, ignore retrieve_full_rows */
+ if (!(quick_intersect= new QUICK_INDEX_INTERSECT_SELECT(param->thd, param->table)))
+ return NULL;
+
+ quick_intersect->records= records;
+ quick_intersect->read_time= read_cost;
+ quick_intersect->filtered_scans= filtered_scans;
+ for (TRP_RANGE **range_scan= range_scans; range_scan != range_scans_end;
+ range_scan++)
+ {
+ if (!(quick= (QUICK_RANGE_SELECT*)
+ ((*range_scan)->make_quick(param, FALSE, &quick_intersect->alloc)))||
+ quick_intersect->push_quick_back(quick))
+ {
+ delete quick;
+ delete quick_intersect;
+ return NULL;
+ }
+ }
+ return quick_intersect;
+}
+
+
+QUICK_SELECT_I *TRP_ROR_INTERSECT::make_quick(PARAM *param,
+ bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc)
+{
+ QUICK_ROR_INTERSECT_SELECT *quick_intrsect;
+ QUICK_RANGE_SELECT *quick;
+ DBUG_ENTER("TRP_ROR_INTERSECT::make_quick");
+ MEM_ROOT *alloc;
+
+ if ((quick_intrsect=
+ new QUICK_ROR_INTERSECT_SELECT(param->thd, param->table,
+ (retrieve_full_rows? (!is_covering) :
+ FALSE),
+ parent_alloc)))
+ {
+ DBUG_EXECUTE("info", print_ror_scans_arr(param->table,
+ "creating ROR-intersect",
+ first_scan, last_scan););
+ alloc= parent_alloc? parent_alloc: &quick_intrsect->alloc;
+ for (; first_scan != last_scan;++first_scan)
+ {
+ if (!(quick= get_quick_select(param, (*first_scan)->idx,
+ (*first_scan)->sel_arg,
+ HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED,
+ 0, alloc)) ||
+ quick_intrsect->push_quick_back(alloc, quick))
+ {
+ delete quick_intrsect;
+ DBUG_RETURN(NULL);
+ }
+ }
+ if (cpk_scan)
+ {
+ if (!(quick= get_quick_select(param, cpk_scan->idx,
+ cpk_scan->sel_arg,
+ HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED,
+ 0, alloc)))
+ {
+ delete quick_intrsect;
+ DBUG_RETURN(NULL);
+ }
+ quick->file= NULL;
+ quick_intrsect->cpk_quick= quick;
+ }
+ quick_intrsect->records= records;
+ quick_intrsect->read_time= read_cost;
+ }
+ DBUG_RETURN(quick_intrsect);
+}
+
+
+QUICK_SELECT_I *TRP_ROR_UNION::make_quick(PARAM *param,
+ bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc)
+{
+ QUICK_ROR_UNION_SELECT *quick_roru;
+ TABLE_READ_PLAN **scan;
+ QUICK_SELECT_I *quick;
+ DBUG_ENTER("TRP_ROR_UNION::make_quick");
+ /*
+ It is impossible to construct a ROR-union that will not retrieve full
+ rows, ignore retrieve_full_rows parameter.
+ */
+ if ((quick_roru= new QUICK_ROR_UNION_SELECT(param->thd, param->table)))
+ {
+ for (scan= first_ror; scan != last_ror; scan++)
+ {
+ if (!(quick= (*scan)->make_quick(param, FALSE, &quick_roru->alloc)) ||
+ quick_roru->push_quick_back(quick))
+ DBUG_RETURN(NULL);
+ }
+ quick_roru->records= records;
+ quick_roru->read_time= read_cost;
+ }
+ DBUG_RETURN(quick_roru);
+}
+
+
+/*
+ Build a SEL_TREE for <> or NOT BETWEEN predicate
+
+ SYNOPSIS
+ get_ne_mm_tree()
+ param PARAM from SQL_SELECT::test_quick_select
+ cond_func item for the predicate
+ field field in the predicate
+ lt_value constant that field should be smaller
+ gt_value constant that field should be greaterr
+ cmp_type compare type for the field
+
+ RETURN
+ # Pointer to tree built tree
+ 0 on error
+*/
+
+static SEL_TREE *get_ne_mm_tree(RANGE_OPT_PARAM *param, Item_func *cond_func,
+ Field *field,
+ Item *lt_value, Item *gt_value,
+ Item_result cmp_type)
+{
+ SEL_TREE *tree;
+ tree= get_mm_parts(param, cond_func, field, Item_func::LT_FUNC,
+ lt_value, cmp_type);
+ if (tree)
+ {
+ tree= tree_or(param, tree, get_mm_parts(param, cond_func, field,
+ Item_func::GT_FUNC,
+ gt_value, cmp_type));
+ }
+ return tree;
+}
+
+
+/*
+ Build a SEL_TREE for a simple predicate
+
+ SYNOPSIS
+ get_func_mm_tree()
+ param PARAM from SQL_SELECT::test_quick_select
+ cond_func item for the predicate
+ field field in the predicate
+ value constant in the predicate
+ cmp_type compare type for the field
+ inv TRUE <> NOT cond_func is considered
+ (makes sense only when cond_func is BETWEEN or IN)
+
+ RETURN
+ Pointer to the tree built tree
+*/
+
+static SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, Item_func *cond_func,
+ Field *field, Item *value,
+ Item_result cmp_type, bool inv)
+{
+ SEL_TREE *tree= 0;
+ DBUG_ENTER("get_func_mm_tree");
+
+ switch (cond_func->functype()) {
+
+ case Item_func::NE_FUNC:
+ tree= get_ne_mm_tree(param, cond_func, field, value, value, cmp_type);
+ break;
+
+ case Item_func::BETWEEN:
+ {
+ if (!value)
+ {
+ if (inv)
+ {
+ tree= get_ne_mm_tree(param, cond_func, field, cond_func->arguments()[1],
+ cond_func->arguments()[2], cmp_type);
+ }
+ else
+ {
+ tree= get_mm_parts(param, cond_func, field, Item_func::GE_FUNC,
+ cond_func->arguments()[1],cmp_type);
+ if (tree)
+ {
+ tree= tree_and(param, tree, get_mm_parts(param, cond_func, field,
+ Item_func::LE_FUNC,
+ cond_func->arguments()[2],
+ cmp_type));
+ }
+ }
+ }
+ else
+ tree= get_mm_parts(param, cond_func, field,
+ (inv ?
+ (value == (Item*)1 ? Item_func::GT_FUNC :
+ Item_func::LT_FUNC):
+ (value == (Item*)1 ? Item_func::LE_FUNC :
+ Item_func::GE_FUNC)),
+ cond_func->arguments()[0], cmp_type);
+ break;
+ }
+ case Item_func::IN_FUNC:
+ {
+ Item_func_in *func=(Item_func_in*) cond_func;
+
+ /*
+ Array for IN() is constructed when all values have the same result
+ type. Tree won't be built for values with different result types,
+ so we check it here to avoid unnecessary work.
+ */
+ if (!func->arg_types_compatible)
+ break;
+
+ if (inv)
+ {
+ if (func->array && func->array->result_type() != ROW_RESULT)
+ {
+ /*
+ We get here for conditions in form "t.key NOT IN (c1, c2, ...)",
+ where c{i} are constants. Our goal is to produce a SEL_TREE that
+ represents intervals:
+
+ ($MIN<t.key<c1) OR (c1<t.key<c2) OR (c2<t.key<c3) OR ... (*)
+
+ where $MIN is either "-inf" or NULL.
+
+ The most straightforward way to produce it is to convert NOT IN
+ into "(t.key != c1) AND (t.key != c2) AND ... " and let the range
+ analyzer to build SEL_TREE from that. The problem is that the
+ range analyzer will use O(N^2) memory (which is probably a bug),
+ and people do use big NOT IN lists (e.g. see BUG#15872, BUG#21282),
+ will run out of memory.
+
+ Another problem with big lists like (*) is that a big list is
+ unlikely to produce a good "range" access, while considering that
+ range access will require expensive CPU calculations (and for
+ MyISAM even index accesses). In short, big NOT IN lists are rarely
+ worth analyzing.
+
+ Considering the above, we'll handle NOT IN as follows:
+ * if the number of entries in the NOT IN list is less than
+ NOT_IN_IGNORE_THRESHOLD, construct the SEL_TREE (*) manually.
+ * Otherwise, don't produce a SEL_TREE.
+ */
+#define NOT_IN_IGNORE_THRESHOLD 1000
+ MEM_ROOT *tmp_root= param->mem_root;
+ param->thd->mem_root= param->old_root;
+ /*
+ Create one Item_type constant object. We'll need it as
+ get_mm_parts only accepts constant values wrapped in Item_Type
+ objects.
+ We create the Item on param->mem_root which points to
+ per-statement mem_root (while thd->mem_root is currently pointing
+ to mem_root local to range optimizer).
+ */
+ Item *value_item= func->array->create_item();
+ param->thd->mem_root= tmp_root;
+
+ if (func->array->count > NOT_IN_IGNORE_THRESHOLD || !value_item)
+ break;
+
+ /* Get a SEL_TREE for "(-inf|NULL) < X < c_0" interval. */
+ uint i=0;
+ do
+ {
+ func->array->value_to_item(i, value_item);
+ tree= get_mm_parts(param, cond_func, field, Item_func::LT_FUNC,
+ value_item, cmp_type);
+ if (!tree)
+ break;
+ i++;
+ } while (i < func->array->count && tree->type == SEL_TREE::IMPOSSIBLE);
+
+ if (!tree || tree->type == SEL_TREE::IMPOSSIBLE)
+ {
+ /* We get here in cases like "t.unsigned NOT IN (-1,-2,-3) */
+ tree= NULL;
+ break;
+ }
+ SEL_TREE *tree2;
+ for (; i < func->array->count; i++)
+ {
+ if (func->array->compare_elems(i, i-1))
+ {
+ /* Get a SEL_TREE for "-inf < X < c_i" interval */
+ func->array->value_to_item(i, value_item);
+ tree2= get_mm_parts(param, cond_func, field, Item_func::LT_FUNC,
+ value_item, cmp_type);
+ if (!tree2)
+ {
+ tree= NULL;
+ break;
+ }
+
+ /* Change all intervals to be "c_{i-1} < X < c_i" */
+ for (uint idx= 0; idx < param->keys; idx++)
+ {
+ SEL_ARG *new_interval, *last_val;
+ if (((new_interval= tree2->keys[idx])) &&
+ (tree->keys[idx]) &&
+ ((last_val= tree->keys[idx]->last())))
+ {
+ new_interval->min_value= last_val->max_value;
+ new_interval->min_flag= NEAR_MIN;
+
+ /*
+ If the interval is over a partial keypart, the
+ interval must be "c_{i-1} <= X < c_i" instead of
+ "c_{i-1} < X < c_i". Reason:
+
+ Consider a table with a column "my_col VARCHAR(3)",
+ and an index with definition
+ "INDEX my_idx my_col(1)". If the table contains rows
+ with my_col values "f" and "foo", the index will not
+ distinguish the two rows.
+
+ Note that tree_or() below will effectively merge
+ this range with the range created for c_{i-1} and
+ we'll eventually end up with only one range:
+ "NULL < X".
+
+ Partitioning indexes are never partial.
+ */
+ if (param->using_real_indexes)
+ {
+ const KEY key=
+ param->table->key_info[param->real_keynr[idx]];
+ const KEY_PART_INFO *kpi= key.key_part + new_interval->part;
+
+ if (kpi->key_part_flag & HA_PART_KEY_SEG)
+ new_interval->min_flag= 0;
+ }
+ }
+ }
+ /*
+ The following doesn't try to allocate memory so no need to
+ check for NULL.
+ */
+ tree= tree_or(param, tree, tree2);
+ }
+ }
+
+ if (tree && tree->type != SEL_TREE::IMPOSSIBLE)
+ {
+ /*
+ Get the SEL_TREE for the last "c_last < X < +inf" interval
+ (value_item cotains c_last already)
+ */
+ tree2= get_mm_parts(param, cond_func, field, Item_func::GT_FUNC,
+ value_item, cmp_type);
+ tree= tree_or(param, tree, tree2);
+ }
+ }
+ else
+ {
+ tree= get_ne_mm_tree(param, cond_func, field,
+ func->arguments()[1], func->arguments()[1],
+ cmp_type);
+ if (tree)
+ {
+ Item **arg, **end;
+ for (arg= func->arguments()+2, end= arg+func->argument_count()-2;
+ arg < end ; arg++)
+ {
+ tree= tree_and(param, tree, get_ne_mm_tree(param, cond_func, field,
+ *arg, *arg, cmp_type));
+ }
+ }
+ }
+ }
+ else
+ {
+ tree= get_mm_parts(param, cond_func, field, Item_func::EQ_FUNC,
+ func->arguments()[1], cmp_type);
+ if (tree)
+ {
+ Item **arg, **end;
+ for (arg= func->arguments()+2, end= arg+func->argument_count()-2;
+ arg < end ; arg++)
+ {
+ tree= tree_or(param, tree, get_mm_parts(param, cond_func, field,
+ Item_func::EQ_FUNC,
+ *arg, cmp_type));
+ }
+ }
+ }
+ break;
+ }
+ default:
+ {
+ /*
+ Here the function for the following predicates are processed:
+ <, <=, =, >=, >, LIKE, IS NULL, IS NOT NULL.
+ If the predicate is of the form (value op field) it is handled
+ as the equivalent predicate (field rev_op value), e.g.
+ 2 <= a is handled as a >= 2.
+ */
+ Item_func::Functype func_type=
+ (value != cond_func->arguments()[0]) ? cond_func->functype() :
+ ((Item_bool_func2*) cond_func)->rev_functype();
+ tree= get_mm_parts(param, cond_func, field, func_type, value, cmp_type);
+ }
+ }
+
+ DBUG_RETURN(tree);
+}
+
+
+/*
+ Build conjunction of all SEL_TREEs for a simple predicate applying equalities
+
+ SYNOPSIS
+ get_full_func_mm_tree()
+ param PARAM from SQL_SELECT::test_quick_select
+ cond_func item for the predicate
+ field_item field in the predicate
+ value constant in the predicate (or a field already read from
+ a table in the case of dynamic range access)
+ (for BETWEEN it contains the number of the field argument,
+ for IN it's always 0)
+ inv TRUE <> NOT cond_func is considered
+ (makes sense only when cond_func is BETWEEN or IN)
+
+ DESCRIPTION
+ For a simple SARGable predicate of the form (f op c), where f is a field and
+ c is a constant, the function builds a conjunction of all SEL_TREES that can
+ be obtained by the substitution of f for all different fields equal to f.
+
+ NOTES
+ If the WHERE condition contains a predicate (fi op c),
+ then not only SELL_TREE for this predicate is built, but
+ the trees for the results of substitution of fi for
+ each fj belonging to the same multiple equality as fi
+ are built as well.
+ E.g. for WHERE t1.a=t2.a AND t2.a > 10
+ a SEL_TREE for t2.a > 10 will be built for quick select from t2
+ and
+ a SEL_TREE for t1.a > 10 will be built for quick select from t1.
+
+ A BETWEEN predicate of the form (fi [NOT] BETWEEN c1 AND c2) is treated
+ in a similar way: we build a conjuction of trees for the results
+ of all substitutions of fi for equal fj.
+ Yet a predicate of the form (c BETWEEN f1i AND f2i) is processed
+ differently. It is considered as a conjuction of two SARGable
+ predicates (f1i <= c) and (f2i <=c) and the function get_full_func_mm_tree
+ is called for each of them separately producing trees for
+ AND j (f1j <=c ) and AND j (f2j <= c)
+ After this these two trees are united in one conjunctive tree.
+ It's easy to see that the same tree is obtained for
+ AND j,k (f1j <=c AND f2k<=c)
+ which is equivalent to
+ AND j,k (c BETWEEN f1j AND f2k).
+ The validity of the processing of the predicate (c NOT BETWEEN f1i AND f2i)
+ which equivalent to (f1i > c OR f2i < c) is not so obvious. Here the
+ function get_full_func_mm_tree is called for (f1i > c) and (f2i < c)
+ producing trees for AND j (f1j > c) and AND j (f2j < c). Then this two
+ trees are united in one OR-tree. The expression
+ (AND j (f1j > c) OR AND j (f2j < c)
+ is equivalent to the expression
+ AND j,k (f1j > c OR f2k < c)
+ which is just a translation of
+ AND j,k (c NOT BETWEEN f1j AND f2k)
+
+ In the cases when one of the items f1, f2 is a constant c1 we do not create
+ a tree for it at all. It works for BETWEEN predicates but does not
+ work for NOT BETWEEN predicates as we have to evaluate the expression
+ with it. If it is TRUE then the other tree can be completely ignored.
+ We do not do it now and no trees are built in these cases for
+ NOT BETWEEN predicates.
+
+ As to IN predicates only ones of the form (f IN (c1,...,cn)),
+ where f1 is a field and c1,...,cn are constant, are considered as
+ SARGable. We never try to narrow the index scan using predicates of
+ the form (c IN (c1,...,f,...,cn)).
+
+ RETURN
+ Pointer to the tree representing the built conjunction of SEL_TREEs
+*/
+
+static SEL_TREE *get_full_func_mm_tree(RANGE_OPT_PARAM *param,
+ Item_func *cond_func,
+ Item_field *field_item, Item *value,
+ bool inv)
+{
+ SEL_TREE *tree= 0;
+ SEL_TREE *ftree= 0;
+ table_map ref_tables= 0;
+ table_map param_comp= ~(param->prev_tables | param->read_tables |
+ param->current_table);
+ DBUG_ENTER("get_full_func_mm_tree");
+
+#ifdef HAVE_SPATIAL
+ if (field_item->field->type() == MYSQL_TYPE_GEOMETRY)
+ {
+ /* We have to be able to store all sorts of spatial features here */
+ ((Field_geom*) field_item->field)->geom_type= Field::GEOM_GEOMETRY;
+ }
+#endif /*HAVE_SPATIAL*/
+
+ for (uint i= 0; i < cond_func->arg_count; i++)
+ {
+ Item *arg= cond_func->arguments()[i]->real_item();
+ if (arg != field_item)
+ ref_tables|= arg->used_tables();
+ }
+ Field *field= field_item->field;
+ Item_result cmp_type= field->cmp_type();
+ if (!((ref_tables | field->table->map) & param_comp))
+ ftree= get_func_mm_tree(param, cond_func, field, value, cmp_type, inv);
+ Item_equal *item_equal= field_item->item_equal;
+ if (item_equal)
+ {
+ Item_equal_fields_iterator it(*item_equal);
+ while (it++)
+ {
+ Field *f= it.get_curr_field();
+ if (field->eq(f))
+ continue;
+ if (!((ref_tables | f->table->map) & param_comp))
+ {
+ tree= get_func_mm_tree(param, cond_func, f, value, cmp_type, inv);
+ ftree= !ftree ? tree : tree_and(param, ftree, tree);
+ }
+ }
+ }
+ DBUG_RETURN(ftree);
+}
+
+ /* make a select tree of all keys in condition */
+
+static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond)
+{
+ SEL_TREE *tree=0;
+ SEL_TREE *ftree= 0;
+ Item_field *field_item= 0;
+ bool inv= FALSE;
+ Item *value= 0;
+ DBUG_ENTER("get_mm_tree");
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ tree= NULL;
+ Item *item;
+ while ((item=li++))
+ {
+ SEL_TREE *new_tree= get_mm_tree(param,item);
+ if (param->statement_should_be_aborted())
+ DBUG_RETURN(NULL);
+ tree= tree_and(param,tree,new_tree);
+ if (tree && tree->type == SEL_TREE::IMPOSSIBLE)
+ break;
+ }
+ }
+ else
+ { // COND OR
+ tree= get_mm_tree(param,li++);
+ if (param->statement_should_be_aborted())
+ DBUG_RETURN(NULL);
+ if (tree)
+ {
+ Item *item;
+ while ((item=li++))
+ {
+ SEL_TREE *new_tree=get_mm_tree(param,item);
+ if (new_tree == NULL || param->statement_should_be_aborted())
+ DBUG_RETURN(NULL);
+ tree= tree_or(param,tree,new_tree);
+ if (tree == NULL || tree->type == SEL_TREE::ALWAYS)
+ break;
+ }
+ }
+ }
+ DBUG_RETURN(tree);
+ }
+ /* Here when simple cond */
+ if (cond->const_item())
+ {
+ if (cond->is_expensive())
+ DBUG_RETURN(0);
+ /*
+ During the cond->val_int() evaluation we can come across a subselect
+ item which may allocate memory on the thd->mem_root and assumes
+ all the memory allocated has the same life span as the subselect
+ item itself. So we have to restore the thread's mem_root here.
+ */
+ MEM_ROOT *tmp_root= param->mem_root;
+ param->thd->mem_root= param->old_root;
+ tree= cond->val_int() ? new(tmp_root) SEL_TREE(SEL_TREE::ALWAYS) :
+ new(tmp_root) SEL_TREE(SEL_TREE::IMPOSSIBLE);
+ param->thd->mem_root= tmp_root;
+ DBUG_RETURN(tree);
+ }
+
+ table_map ref_tables= 0;
+ table_map param_comp= ~(param->prev_tables | param->read_tables |
+ param->current_table);
+ if (cond->type() != Item::FUNC_ITEM)
+ { // Should be a field
+ ref_tables= cond->used_tables();
+ if ((ref_tables & param->current_table) ||
+ (ref_tables & ~(param->prev_tables | param->read_tables)))
+ DBUG_RETURN(0);
+ DBUG_RETURN(new SEL_TREE(SEL_TREE::MAYBE));
+ }
+
+ Item_func *cond_func= (Item_func*) cond;
+ if (cond_func->functype() == Item_func::BETWEEN ||
+ cond_func->functype() == Item_func::IN_FUNC)
+ inv= ((Item_func_opt_neg *) cond_func)->negated;
+ else if (cond_func->select_optimize() == Item_func::OPTIMIZE_NONE)
+ DBUG_RETURN(0);
+
+ param->cond= cond;
+
+ switch (cond_func->functype()) {
+ case Item_func::BETWEEN:
+ if (cond_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM)
+ {
+ field_item= (Item_field*) (cond_func->arguments()[0]->real_item());
+ ftree= get_full_func_mm_tree(param, cond_func, field_item, NULL, inv);
+ }
+
+ /*
+ Concerning the code below see the NOTES section in
+ the comments for the function get_full_func_mm_tree()
+ */
+ for (uint i= 1 ; i < cond_func->arg_count ; i++)
+ {
+ if (cond_func->arguments()[i]->real_item()->type() == Item::FIELD_ITEM)
+ {
+ field_item= (Item_field*) (cond_func->arguments()[i]->real_item());
+ SEL_TREE *tmp= get_full_func_mm_tree(param, cond_func,
+ field_item, (Item*)(intptr)i, inv);
+ if (inv)
+ {
+ tree= !tree ? tmp : tree_or(param, tree, tmp);
+ if (tree == NULL)
+ break;
+ }
+ else
+ tree= tree_and(param, tree, tmp);
+ }
+ else if (inv)
+ {
+ tree= 0;
+ break;
+ }
+ }
+
+ ftree = tree_and(param, ftree, tree);
+ break;
+ case Item_func::IN_FUNC:
+ {
+ Item_func_in *func=(Item_func_in*) cond_func;
+ if (func->key_item()->real_item()->type() != Item::FIELD_ITEM)
+ DBUG_RETURN(0);
+ field_item= (Item_field*) (func->key_item()->real_item());
+ ftree= get_full_func_mm_tree(param, cond_func, field_item, NULL, inv);
+ break;
+ }
+ case Item_func::MULT_EQUAL_FUNC:
+ {
+ Item_equal *item_equal= (Item_equal *) cond;
+ if (!(value= item_equal->get_const()) || value->is_expensive())
+ DBUG_RETURN(0);
+ Item_equal_fields_iterator it(*item_equal);
+ ref_tables= value->used_tables();
+ while (it++)
+ {
+ Field *field= it.get_curr_field();
+ Item_result cmp_type= field->cmp_type();
+ if (!((ref_tables | field->table->map) & param_comp))
+ {
+ tree= get_mm_parts(param, cond, field, Item_func::EQ_FUNC,
+ value,cmp_type);
+ ftree= !ftree ? tree : tree_and(param, ftree, tree);
+ }
+ }
+
+ DBUG_RETURN(ftree);
+ }
+ default:
+
+ DBUG_ASSERT (!ftree);
+ if (cond_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM)
+ {
+ field_item= (Item_field*) (cond_func->arguments()[0]->real_item());
+ value= cond_func->arg_count > 1 ? cond_func->arguments()[1] : NULL;
+ if (value && value->is_expensive())
+ DBUG_RETURN(0);
+ if (!cond_func->arguments()[0]->real_item()->const_item())
+ ftree= get_full_func_mm_tree(param, cond_func, field_item, value, inv);
+ }
+ /*
+ Even if get_full_func_mm_tree() was executed above and did not
+ return a range predicate it may still be possible to create one
+ by reversing the order of the operands. Note that this only
+ applies to predicates where both operands are fields. Example: A
+ query of the form
+
+ WHERE t1.a OP t2.b
+
+ In this case, arguments()[0] == t1.a and arguments()[1] == t2.b.
+ When creating range predicates for t2, get_full_func_mm_tree()
+ above will return NULL because 'field' belongs to t1 and only
+ predicates that applies to t2 are of interest. In this case a
+ call to get_full_func_mm_tree() with reversed operands (see
+ below) may succeed.
+ */
+ if (!ftree && cond_func->have_rev_func() &&
+ cond_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM)
+ {
+ field_item= (Item_field*) (cond_func->arguments()[1]->real_item());
+ value= cond_func->arguments()[0];
+ if (value && value->is_expensive())
+ DBUG_RETURN(0);
+ if (!cond_func->arguments()[1]->real_item()->const_item())
+ ftree= get_full_func_mm_tree(param, cond_func, field_item, value, inv);
+ }
+ }
+
+ DBUG_RETURN(ftree);
+}
+
+
+static SEL_TREE *
+get_mm_parts(RANGE_OPT_PARAM *param, COND *cond_func, Field *field,
+ Item_func::Functype type,
+ Item *value, Item_result cmp_type)
+{
+ DBUG_ENTER("get_mm_parts");
+ if (field->table != param->table)
+ DBUG_RETURN(0);
+
+ KEY_PART *key_part = param->key_parts;
+ KEY_PART *end = param->key_parts_end;
+ SEL_TREE *tree=0;
+ if (value &&
+ value->used_tables() & ~(param->prev_tables | param->read_tables))
+ DBUG_RETURN(0);
+ for (; key_part != end ; key_part++)
+ {
+ if (field->eq(key_part->field))
+ {
+ SEL_ARG *sel_arg=0;
+ if (!tree && !(tree=new SEL_TREE()))
+ DBUG_RETURN(0); // OOM
+ if (!value || !(value->used_tables() & ~param->read_tables))
+ {
+ sel_arg=get_mm_leaf(param,cond_func,
+ key_part->field,key_part,type,value);
+ if (!sel_arg)
+ continue;
+ if (sel_arg->type == SEL_ARG::IMPOSSIBLE)
+ {
+ tree->type=SEL_TREE::IMPOSSIBLE;
+ DBUG_RETURN(tree);
+ }
+ }
+ else
+ {
+ // This key may be used later
+ if (!(sel_arg= new SEL_ARG(SEL_ARG::MAYBE_KEY)))
+ DBUG_RETURN(0); // OOM
+ }
+ sel_arg->part=(uchar) key_part->part;
+ sel_arg->max_part_no= sel_arg->part+1;
+ tree->keys[key_part->key]=sel_add(tree->keys[key_part->key],sel_arg);
+ tree->keys_map.set_bit(key_part->key);
+ }
+ }
+
+ if (tree && tree->merges.is_empty() && tree->keys_map.is_clear_all())
+ tree= NULL;
+ DBUG_RETURN(tree);
+}
+
+
+static SEL_ARG *
+get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field,
+ KEY_PART *key_part, Item_func::Functype type,Item *value)
+{
+ uint maybe_null=(uint) field->real_maybe_null();
+ bool optimize_range;
+ SEL_ARG *tree= 0;
+ MEM_ROOT *alloc= param->mem_root;
+ uchar *str;
+ int err;
+ DBUG_ENTER("get_mm_leaf");
+
+ /*
+ We need to restore the runtime mem_root of the thread in this
+ function because it evaluates the value of its argument, while
+ the argument can be any, e.g. a subselect. The subselect
+ items, in turn, assume that all the memory allocated during
+ the evaluation has the same life span as the item itself.
+ TODO: opt_range.cc should not reset thd->mem_root at all.
+ */
+ param->thd->mem_root= param->old_root;
+ if (!value) // IS NULL or IS NOT NULL
+ {
+ if (field->table->maybe_null) // Can't use a key on this
+ goto end;
+ if (!maybe_null) // Not null field
+ {
+ if (type == Item_func::ISNULL_FUNC)
+ tree= &null_element;
+ goto end;
+ }
+ if (!(tree= new (alloc) SEL_ARG(field,is_null_string,is_null_string)))
+ goto end; // out of memory
+ if (type == Item_func::ISNOTNULL_FUNC)
+ {
+ tree->min_flag=NEAR_MIN; /* IS NOT NULL -> X > NULL */
+ tree->max_flag=NO_MAX_RANGE;
+ }
+ goto end;
+ }
+
+ /*
+ 1. Usually we can't use an index if the column collation
+ differ from the operation collation.
+
+ 2. However, we can reuse a case insensitive index for
+ the binary searches:
+
+ WHERE latin1_swedish_ci_column = 'a' COLLATE lati1_bin;
+
+ WHERE latin1_swedish_ci_colimn = BINARY 'a '
+
+ */
+ if (field->result_type() == STRING_RESULT &&
+ field->match_collation_to_optimize_range() &&
+ value->result_type() == STRING_RESULT &&
+ key_part->image_type == Field::itRAW &&
+ field->charset() != conf_func->compare_collation() &&
+ !(conf_func->compare_collation()->state & MY_CS_BINSORT &&
+ (type == Item_func::EQUAL_FUNC || type == Item_func::EQ_FUNC)))
+ goto end;
+ if (value->cmp_type() == TIME_RESULT && field->cmp_type() != TIME_RESULT)
+ goto end;
+
+ if (key_part->image_type == Field::itMBR)
+ {
+ // @todo: use is_spatial_operator() instead?
+ switch (type) {
+ case Item_func::SP_EQUALS_FUNC:
+ case Item_func::SP_DISJOINT_FUNC:
+ case Item_func::SP_INTERSECTS_FUNC:
+ case Item_func::SP_TOUCHES_FUNC:
+ case Item_func::SP_CROSSES_FUNC:
+ case Item_func::SP_WITHIN_FUNC:
+ case Item_func::SP_CONTAINS_FUNC:
+ case Item_func::SP_OVERLAPS_FUNC:
+ break;
+ default:
+ /*
+ We cannot involve spatial indexes for queries that
+ don't use MBREQUALS(), MBRDISJOINT(), etc. functions.
+ */
+ goto end;
+ }
+ }
+
+ if (param->using_real_indexes)
+ optimize_range= field->optimize_range(param->real_keynr[key_part->key],
+ key_part->part);
+ else
+ optimize_range= TRUE;
+
+ if (type == Item_func::LIKE_FUNC)
+ {
+ bool like_error;
+ char buff1[MAX_FIELD_WIDTH];
+ uchar *min_str,*max_str;
+ String tmp(buff1,sizeof(buff1),value->collation.collation),*res;
+ size_t length, offset, min_length, max_length;
+ uint field_length= field->pack_length()+maybe_null;
+
+ if (!optimize_range)
+ goto end;
+ if (!(res= value->val_str(&tmp)))
+ {
+ tree= &null_element;
+ goto end;
+ }
+
+ /*
+ TODO:
+ Check if this was a function. This should have be optimized away
+ in the sql_select.cc
+ */
+ if (res != &tmp)
+ {
+ tmp.copy(*res); // Get own copy
+ res= &tmp;
+ }
+ if (field->cmp_type() != STRING_RESULT)
+ goto end; // Can only optimize strings
+
+ offset=maybe_null;
+ length=key_part->store_length;
+
+ if (length != key_part->length + maybe_null)
+ {
+ /* key packed with length prefix */
+ offset+= HA_KEY_BLOB_LENGTH;
+ field_length= length - HA_KEY_BLOB_LENGTH;
+ }
+ else
+ {
+ if (unlikely(length < field_length))
+ {
+ /*
+ This can only happen in a table created with UNIREG where one key
+ overlaps many fields
+ */
+ length= field_length;
+ }
+ else
+ field_length= length;
+ }
+ length+=offset;
+ if (!(min_str= (uchar*) alloc_root(alloc, length*2)))
+ goto end;
+
+ max_str=min_str+length;
+ if (maybe_null)
+ max_str[0]= min_str[0]=0;
+
+ field_length-= maybe_null;
+ like_error= my_like_range(field->charset(),
+ res->ptr(), res->length(),
+ ((Item_func_like*)(param->cond))->escape,
+ wild_one, wild_many,
+ field_length,
+ (char*) min_str+offset, (char*) max_str+offset,
+ &min_length, &max_length);
+ if (like_error) // Can't optimize with LIKE
+ goto end;
+
+ if (offset != maybe_null) // BLOB or VARCHAR
+ {
+ int2store(min_str+maybe_null,min_length);
+ int2store(max_str+maybe_null,max_length);
+ }
+ tree= new (alloc) SEL_ARG(field, min_str, max_str);
+ goto end;
+ }
+
+ if (!optimize_range &&
+ type != Item_func::EQ_FUNC &&
+ type != Item_func::EQUAL_FUNC)
+ goto end; // Can't optimize this
+
+ /*
+ We can't always use indexes when comparing a string index to a number
+ cmp_type() is checked to allow compare of dates to numbers
+ */
+ if (field->cmp_type() == STRING_RESULT && value->cmp_type() != STRING_RESULT)
+ goto end;
+ err= value->save_in_field_no_warnings(field, 1);
+ if (err == 2 && field->cmp_type() == STRING_RESULT)
+ {
+ if (type == Item_func::EQ_FUNC)
+ {
+ tree= new (alloc) SEL_ARG(field, 0, 0);
+ tree->type= SEL_ARG::IMPOSSIBLE;
+ }
+ else
+ tree= NULL; /* Cannot infer anything */
+ goto end;
+ }
+ if (err > 0)
+ {
+ if (field->cmp_type() != value->result_type())
+ {
+ if ((type == Item_func::EQ_FUNC || type == Item_func::EQUAL_FUNC) &&
+ value->result_type() == item_cmp_type(field->result_type(),
+ value->result_type()))
+ {
+ tree= new (alloc) SEL_ARG(field, 0, 0);
+ tree->type= SEL_ARG::IMPOSSIBLE;
+ goto end;
+ }
+ else
+ {
+ /*
+ TODO: We should return trees of the type SEL_ARG::IMPOSSIBLE
+ for the cases like int_field > 999999999999999999999999 as well.
+ */
+ tree= 0;
+ if (err == 3 && field->type() == FIELD_TYPE_DATE &&
+ (type == Item_func::GT_FUNC || type == Item_func::GE_FUNC ||
+ type == Item_func::LT_FUNC || type == Item_func::LE_FUNC) )
+ {
+ /*
+ We were saving DATETIME into a DATE column, the conversion went ok
+ but a non-zero time part was cut off.
+
+ In MySQL's SQL dialect, DATE and DATETIME are compared as datetime
+ values. Index over a DATE column uses DATE comparison. Changing
+ from one comparison to the other is possible:
+
+ datetime(date_col)< '2007-12-10 12:34:55' -> date_col<='2007-12-10'
+ datetime(date_col)<='2007-12-10 12:34:55' -> date_col<='2007-12-10'
+
+ datetime(date_col)> '2007-12-10 12:34:55' -> date_col>='2007-12-10'
+ datetime(date_col)>='2007-12-10 12:34:55' -> date_col>='2007-12-10'
+
+ but we'll need to convert '>' to '>=' and '<' to '<='. This will
+ be done together with other types at the end of this function
+ (grep for stored_field_cmp_to_item)
+ */
+ }
+ else
+ goto end;
+ }
+ }
+
+ /*
+ guaranteed at this point: err > 0; field and const of same type
+ If an integer got bounded (e.g. to within 0..255 / -128..127)
+ for < or >, set flags as for <= or >= (no NEAR_MAX / NEAR_MIN)
+ */
+ else if (err == 1 && field->result_type() == INT_RESULT)
+ {
+ if (type == Item_func::LT_FUNC && (value->val_int() > 0))
+ type = Item_func::LE_FUNC;
+ else if (type == Item_func::GT_FUNC &&
+ (field->type() != FIELD_TYPE_BIT) &&
+ !((Field_num*)field)->unsigned_flag &&
+ !((Item_int*)value)->unsigned_flag &&
+ (value->val_int() < 0))
+ type = Item_func::GE_FUNC;
+ }
+ }
+ else if (err < 0)
+ {
+ /* This happens when we try to insert a NULL field in a not null column */
+ tree= &null_element; // cmp with NULL is never TRUE
+ goto end;
+ }
+
+ /*
+ Any sargable predicate except "<=>" involving NULL as a constant is always
+ FALSE
+ */
+ if (type != Item_func::EQUAL_FUNC && field->is_real_null())
+ {
+ tree= &null_element;
+ goto end;
+ }
+
+ str= (uchar*) alloc_root(alloc, key_part->store_length+1);
+ if (!str)
+ goto end;
+ if (maybe_null)
+ *str= (uchar) field->is_real_null(); // Set to 1 if null
+ field->get_key_image(str+maybe_null, key_part->length,
+ key_part->image_type);
+ if (!(tree= new (alloc) SEL_ARG(field, str, str)))
+ goto end; // out of memory
+
+ /*
+ Check if we are comparing an UNSIGNED integer with a negative constant.
+ In this case we know that:
+ (a) (unsigned_int [< | <=] negative_constant) == FALSE
+ (b) (unsigned_int [> | >=] negative_constant) == TRUE
+ In case (a) the condition is false for all values, and in case (b) it
+ is true for all values, so we can avoid unnecessary retrieval and condition
+ testing, and we also get correct comparison of unsinged integers with
+ negative integers (which otherwise fails because at query execution time
+ negative integers are cast to unsigned if compared with unsigned).
+ */
+ if (field->result_type() == INT_RESULT &&
+ value->result_type() == INT_RESULT &&
+ ((field->type() == FIELD_TYPE_BIT ||
+ ((Field_num *) field)->unsigned_flag) &&
+ !((Item_int*) value)->unsigned_flag))
+ {
+ longlong item_val= value->val_int();
+ if (item_val < 0)
+ {
+ if (type == Item_func::LT_FUNC || type == Item_func::LE_FUNC)
+ {
+ tree->type= SEL_ARG::IMPOSSIBLE;
+ goto end;
+ }
+ if (type == Item_func::GT_FUNC || type == Item_func::GE_FUNC)
+ {
+ tree= 0;
+ goto end;
+ }
+ }
+ }
+
+ switch (type) {
+ case Item_func::LT_FUNC:
+ if (stored_field_cmp_to_item(param->thd, field, value) == 0)
+ tree->max_flag=NEAR_MAX;
+ /* fall through */
+ case Item_func::LE_FUNC:
+ if (!maybe_null)
+ tree->min_flag=NO_MIN_RANGE; /* From start */
+ else
+ { // > NULL
+ tree->min_value=is_null_string;
+ tree->min_flag=NEAR_MIN;
+ }
+ break;
+ case Item_func::GT_FUNC:
+ /* Don't use open ranges for partial key_segments */
+ if ((!(key_part->flag & HA_PART_KEY_SEG)) &&
+ (stored_field_cmp_to_item(param->thd, field, value) <= 0))
+ tree->min_flag=NEAR_MIN;
+ tree->max_flag= NO_MAX_RANGE;
+ break;
+ case Item_func::GE_FUNC:
+ /* Don't use open ranges for partial key_segments */
+ if ((!(key_part->flag & HA_PART_KEY_SEG)) &&
+ (stored_field_cmp_to_item(param->thd, field, value) < 0))
+ tree->min_flag= NEAR_MIN;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+ case Item_func::SP_EQUALS_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_EQUAL;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+ case Item_func::SP_DISJOINT_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_DISJOINT;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+ case Item_func::SP_INTERSECTS_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+ case Item_func::SP_TOUCHES_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+
+ case Item_func::SP_CROSSES_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+ case Item_func::SP_WITHIN_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_WITHIN;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+
+ case Item_func::SP_CONTAINS_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_CONTAIN;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+ case Item_func::SP_OVERLAPS_FUNC:
+ tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512;
+ tree->max_flag=NO_MAX_RANGE;
+ break;
+
+ default:
+ break;
+ }
+
+end:
+ param->thd->mem_root= alloc;
+ DBUG_RETURN(tree);
+}
+
+
+/******************************************************************************
+** Tree manipulation functions
+** If tree is 0 it means that the condition can't be tested. It refers
+** to a non existent table or to a field in current table with isn't a key.
+** The different tree flags:
+** IMPOSSIBLE: Condition is never TRUE
+** ALWAYS: Condition is always TRUE
+** MAYBE: Condition may exists when tables are read
+** MAYBE_KEY: Condition refers to a key that may be used in join loop
+** KEY_RANGE: Condition uses a key
+******************************************************************************/
+
+/*
+ Add a new key test to a key when scanning through all keys
+ This will never be called for same key parts.
+*/
+
+static SEL_ARG *
+sel_add(SEL_ARG *key1,SEL_ARG *key2)
+{
+ SEL_ARG *root,**key_link;
+
+ if (!key1)
+ return key2;
+ if (!key2)
+ return key1;
+
+ key_link= &root;
+ while (key1 && key2)
+ {
+ if (key1->part < key2->part)
+ {
+ *key_link= key1;
+ key_link= &key1->next_key_part;
+ key1=key1->next_key_part;
+ }
+ else
+ {
+ *key_link= key2;
+ key_link= &key2->next_key_part;
+ key2=key2->next_key_part;
+ }
+ }
+ *key_link=key1 ? key1 : key2;
+ return root;
+}
+
+
+/*
+ Build a range tree for the conjunction of the range parts of two trees
+
+ SYNOPSIS
+ and_range_trees()
+ param Context info for the operation
+ tree1 SEL_TREE for the first conjunct
+ tree2 SEL_TREE for the second conjunct
+ result SEL_TREE for the result
+
+ DESCRIPTION
+ This function takes range parts of two trees tree1 and tree2 and builds
+ a range tree for the conjunction of the formulas that these two range parts
+ represent.
+ More exactly:
+ if the range part of tree1 represents the normalized formula
+ R1_1 AND ... AND R1_k,
+ and the range part of tree2 represents the normalized formula
+ R2_1 AND ... AND R2_k,
+ then the range part of the result represents the formula:
+ RT = R_1 AND ... AND R_k, where R_i=(R1_i AND R2_i) for each i from [1..k]
+
+ The function assumes that tree1 is never equal to tree2. At the same
+ time the tree result can be the same as tree1 (but never as tree2).
+ If result==tree1 then rt replaces the range part of tree1 leaving
+ imerges as they are.
+ if result!=tree1 than it is assumed that the SEL_ARG trees in tree1 and
+ tree2 should be preserved. Otherwise they can be destroyed.
+
+ RETURN
+ 1 if the type the result tree is SEL_TREE::IMPOSSIBLE
+ 0 otherwise
+*/
+
+static
+int and_range_trees(RANGE_OPT_PARAM *param, SEL_TREE *tree1, SEL_TREE *tree2,
+ SEL_TREE *result)
+{
+ DBUG_ENTER("and_ranges");
+ key_map result_keys;
+ result_keys.clear_all();
+ key_map anded_keys= tree1->keys_map;
+ anded_keys.merge(tree2->keys_map);
+ int key_no;
+ key_map::Iterator it(anded_keys);
+ while ((key_no= it++) != key_map::Iterator::BITMAP_END)
+ {
+ uint flag=0;
+ SEL_ARG *key1= tree1->keys[key_no];
+ SEL_ARG *key2= tree2->keys[key_no];
+ if (key1 && !key1->simple_key())
+ flag|= CLONE_KEY1_MAYBE;
+ if (key2 && !key2->simple_key())
+ flag|=CLONE_KEY2_MAYBE;
+ if (result != tree1)
+ {
+ if (key1)
+ key1->incr_refs();
+ if (key2)
+ key2->incr_refs();
+ }
+ SEL_ARG *key;
+ if ((result->keys[key_no]= key =key_and(param, key1, key2, flag)))
+ {
+ if (key && key->type == SEL_ARG::IMPOSSIBLE)
+ {
+ result->type= SEL_TREE::IMPOSSIBLE;
+ DBUG_RETURN(1);
+ }
+ result_keys.set_bit(key_no);
+#ifdef EXTRA_DEBUG
+ if (param->alloced_sel_args < SEL_ARG::MAX_SEL_ARGS)
+ key->test_use_count(key);
+#endif
+ }
+ }
+ result->keys_map= result_keys;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Build a SEL_TREE for a conjunction out of such trees for the conjuncts
+
+ SYNOPSIS
+ tree_and()
+ param Context info for the operation
+ tree1 SEL_TREE for the first conjunct
+ tree2 SEL_TREE for the second conjunct
+
+ DESCRIPTION
+ This function builds a tree for the formula (A AND B) out of the trees
+ tree1 and tree2 that has been built for the formulas A and B respectively.
+
+ In a general case
+ tree1 represents the formula RT1 AND MT1,
+ where RT1 = R1_1 AND ... AND R1_k1, MT1=M1_1 AND ... AND M1_l1;
+ tree2 represents the formula RT2 AND MT2
+ where RT2 = R2_1 AND ... AND R2_k2, MT2=M2_1 AND ... AND M2_l2.
+
+ The result tree will represent the formula of the the following structure:
+ RT AND RT1MT2 AND RT2MT1, such that
+ rt is a tree obtained by range intersection of trees tree1 and tree2,
+ RT1MT2 = RT1M2_1 AND ... AND RT1M2_l2,
+ RT2MT1 = RT2M1_1 AND ... AND RT2M1_l1,
+ where rt1m2_i (i=1,...,l2) is the result of the pushdown operation
+ of range tree rt1 into imerge m2_i, while rt2m1_j (j=1,...,l1) is the
+ result of the pushdown operation of range tree rt2 into imerge m1_j.
+
+ RT1MT2/RT2MT is empty if MT2/MT1 is empty.
+
+ The range intersection of two range trees is produced by the function
+ and_range_trees. The pushdown of a range tree to a imerge is performed
+ by the function imerge_list_and_tree. This function may produce imerges
+ containing only one range tree. Such trees are intersected with rt and
+ the result of intersection is returned as the range part of the result
+ tree, while the corresponding imerges are removed altogether from its
+ imerge part.
+
+ NOTE
+ The pushdown operation of range trees into imerges is needed to be able
+ to construct valid imerges for the condition like this:
+ key1_p1=c1 AND (key1_p2 BETWEEN c21 AND c22 OR key2 < c2)
+
+ NOTE
+ Currently we do not support intersection between indexes and index merges.
+ When this will be supported the list of imerges for the result tree
+ should include also imerges from M1 and M2. That's why an extra parameter
+ is added to the function imerge_list_and_tree. If we call the function
+ with the last parameter equal to FALSE then MT1 and MT2 will be preserved
+ in the imerge list of the result tree. This can lead to the exponential
+ growth of the imerge list though.
+ Currently the last parameter of imerge_list_and_tree calls is always
+ TRUE.
+
+ RETURN
+ The result tree, if a success
+ 0 - otherwise.
+*/
+
+static
+SEL_TREE *tree_and(RANGE_OPT_PARAM *param, SEL_TREE *tree1, SEL_TREE *tree2)
+{
+ DBUG_ENTER("tree_and");
+ if (!tree1)
+ DBUG_RETURN(tree2);
+ if (!tree2)
+ DBUG_RETURN(tree1);
+ if (tree1->type == SEL_TREE::IMPOSSIBLE || tree2->type == SEL_TREE::ALWAYS)
+ DBUG_RETURN(tree1);
+ if (tree2->type == SEL_TREE::IMPOSSIBLE || tree1->type == SEL_TREE::ALWAYS)
+ DBUG_RETURN(tree2);
+ if (tree1->type == SEL_TREE::MAYBE)
+ {
+ if (tree2->type == SEL_TREE::KEY)
+ tree2->type=SEL_TREE::KEY_SMALLER;
+ DBUG_RETURN(tree2);
+ }
+ if (tree2->type == SEL_TREE::MAYBE)
+ {
+ tree1->type=SEL_TREE::KEY_SMALLER;
+ DBUG_RETURN(tree1);
+ }
+
+ if (!tree1->merges.is_empty())
+ imerge_list_and_tree(param, &tree1->merges, tree2, TRUE);
+ if (!tree2->merges.is_empty())
+ imerge_list_and_tree(param, &tree2->merges, tree1, TRUE);
+ if (and_range_trees(param, tree1, tree2, tree1))
+ DBUG_RETURN(tree1);
+ imerge_list_and_list(&tree1->merges, &tree2->merges);
+ eliminate_single_tree_imerges(param, tree1);
+ DBUG_RETURN(tree1);
+}
+
+
+/*
+ Eliminate single tree imerges in a SEL_TREE objects
+
+ SYNOPSIS
+ eliminate_single_tree_imerges()
+ param Context info for the function
+ tree SEL_TREE where single tree imerges are to be eliminated
+
+ DESCRIPTION
+ For each imerge in 'tree' that contains only one disjunct tree, i.e.
+ for any imerge of the form m=rt, the function performs and operation
+ the range part of tree, replaces rt the with the result of anding and
+ removes imerge m from the the merge part of 'tree'.
+
+ RETURN VALUE
+ none
+*/
+
+static
+void eliminate_single_tree_imerges(RANGE_OPT_PARAM *param, SEL_TREE *tree)
+{
+ SEL_IMERGE *imerge;
+ List<SEL_IMERGE> merges= tree->merges;
+ List_iterator<SEL_IMERGE> it(merges);
+ tree->merges.empty();
+ while ((imerge= it++))
+ {
+ if (imerge->trees+1 == imerge->trees_next)
+ {
+ tree= tree_and(param, tree, *imerge->trees);
+ it.remove();
+ }
+ }
+ tree->merges= merges;
+}
+
+
+/*
+ For two trees check that there are indexes with ranges in both of them
+
+ SYNOPSIS
+ sel_trees_have_common_keys()
+ tree1 SEL_TREE for the first tree
+ tree2 SEL_TREE for the second tree
+ common_keys OUT bitmap of all indexes with ranges in both trees
+
+ DESCRIPTION
+ For two trees tree1 and tree1 the function checks if there are indexes
+ in their range parts such that SEL_ARG trees are defined for them in the
+ range parts of both trees. The function returns the bitmap of such
+ indexes in the parameter common_keys.
+
+ RETURN
+ TRUE if there are such indexes (common_keys is nor empty)
+ FALSE otherwise
+*/
+
+static
+bool sel_trees_have_common_keys(SEL_TREE *tree1, SEL_TREE *tree2,
+ key_map *common_keys)
+{
+ *common_keys= tree1->keys_map;
+ common_keys->intersect(tree2->keys_map);
+ return !common_keys->is_clear_all();
+}
+
+
+/*
+ Check whether range parts of two trees can be ored for some indexes
+
+ SYNOPSIS
+ sel_trees_can_be_ored()
+ param Context info for the function
+ tree1 SEL_TREE for the first tree
+ tree2 SEL_TREE for the second tree
+ common_keys IN/OUT IN: bitmap of all indexes with SEL_ARG in both trees
+ OUT: bitmap of all indexes that can be ored
+
+ DESCRIPTION
+ For two trees tree1 and tree2 and the bitmap common_keys containing
+ bits for indexes that have SEL_ARG trees in range parts of both trees
+ the function checks if there are indexes for which SEL_ARG trees can
+ be ored. Two SEL_ARG trees for the same index can be ored if the most
+ major components of the index used in these trees coincide. If the
+ SEL_ARG trees for an index cannot be ored the function clears the bit
+ for this index in the bitmap common_keys.
+
+ The function does not verify that indexes marked in common_keys really
+ have SEL_ARG trees in both tree1 and tree2. It assumes that this is true.
+
+ NOTE
+ The function sel_trees_can_be_ored is usually used in pair with the
+ function sel_trees_have_common_keys.
+
+ RETURN
+ TRUE if there are indexes for which SEL_ARG trees can be ored
+ FALSE otherwise
+*/
+
+static
+bool sel_trees_can_be_ored(RANGE_OPT_PARAM* param,
+ SEL_TREE *tree1, SEL_TREE *tree2,
+ key_map *common_keys)
+{
+ DBUG_ENTER("sel_trees_can_be_ored");
+ if (!sel_trees_have_common_keys(tree1, tree2, common_keys))
+ DBUG_RETURN(FALSE);
+ int key_no;
+ key_map::Iterator it(*common_keys);
+ while ((key_no= it++) != key_map::Iterator::BITMAP_END)
+ {
+ DBUG_ASSERT(tree1->keys[key_no] && tree2->keys[key_no]);
+ /* Trees have a common key, check if they refer to the same key part */
+ if (tree1->keys[key_no]->part != tree2->keys[key_no]->part)
+ common_keys->clear_bit(key_no);
+ }
+ DBUG_RETURN(!common_keys->is_clear_all());
+}
+
+/*
+ Check whether range parts of two trees must be ored for some indexes
+
+ SYNOPSIS
+ sel_trees_must_be_ored()
+ param Context info for the function
+ tree1 SEL_TREE for the first tree
+ tree2 SEL_TREE for the second tree
+ ordable_keys bitmap of SEL_ARG trees that can be ored
+
+ DESCRIPTION
+ For two trees tree1 and tree2 the function checks whether they must be
+ ored. The function assumes that the bitmap ordable_keys contains bits for
+ those corresponding pairs of SEL_ARG trees from tree1 and tree2 that can
+ be ored.
+ We believe that tree1 and tree2 must be ored if any pair of SEL_ARG trees
+ r1 and r2, such that r1 is from tree1 and r2 is from tree2 and both
+ of them are marked in ordable_keys, can be merged.
+
+ NOTE
+ The function sel_trees_must_be_ored as a rule is used in pair with the
+ function sel_trees_can_be_ored.
+
+ RETURN
+ TRUE if there are indexes for which SEL_ARG trees must be ored
+ FALSE otherwise
+*/
+
+static
+bool sel_trees_must_be_ored(RANGE_OPT_PARAM* param,
+ SEL_TREE *tree1, SEL_TREE *tree2,
+ key_map oredable_keys)
+{
+ key_map tmp;
+ DBUG_ENTER("sel_trees_must_be_ored");
+
+ tmp= tree1->keys_map;
+ tmp.merge(tree2->keys_map);
+ tmp.subtract(oredable_keys);
+ if (!tmp.is_clear_all())
+ DBUG_RETURN(FALSE);
+
+ int idx1, idx2;
+ key_map::Iterator it1(oredable_keys);
+ while ((idx1= it1++) != key_map::Iterator::BITMAP_END)
+ {
+ KEY_PART *key1_init= param->key[idx1]+tree1->keys[idx1]->part;
+ KEY_PART *key1_end= param->key[idx1]+tree1->keys[idx1]->max_part_no;
+ key_map::Iterator it2(oredable_keys);
+ while ((idx2= it2++) != key_map::Iterator::BITMAP_END)
+ {
+ if (idx2 <= idx1)
+ continue;
+
+ KEY_PART *key2_init= param->key[idx2]+tree2->keys[idx2]->part;
+ KEY_PART *key2_end= param->key[idx2]+tree2->keys[idx2]->max_part_no;
+ KEY_PART *part1, *part2;
+ for (part1= key1_init, part2= key2_init;
+ part1 < key1_end && part2 < key2_end;
+ part1++, part2++)
+ {
+ if (!part1->field->eq(part2->field))
+ DBUG_RETURN(FALSE);
+ }
+ }
+ }
+
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Remove the trees that are not suitable for record retrieval
+
+ SYNOPSIS
+ remove_nonrange_trees()
+ param Context info for the function
+ tree Tree to be processed, tree->type is KEY or KEY_SMALLER
+
+ DESCRIPTION
+ This function walks through tree->keys[] and removes the SEL_ARG* trees
+ that are not "maybe" trees (*) and cannot be used to construct quick range
+ selects.
+ (*) - have type MAYBE or MAYBE_KEY. Perhaps we should remove trees of
+ these types here as well.
+
+ A SEL_ARG* tree cannot be used to construct quick select if it has
+ tree->part != 0. (e.g. it could represent "keypart2 < const").
+
+ Normally we allow construction of SEL_TREE objects that have SEL_ARG
+ trees that do not allow quick range select construction.
+ For example:
+ for " keypart1=1 AND keypart2=2 " the execution will proceed as follows:
+ tree1= SEL_TREE { SEL_ARG{keypart1=1} }
+ tree2= SEL_TREE { SEL_ARG{keypart2=2} } -- can't make quick range select
+ from this
+ call tree_and(tree1, tree2) -- this joins SEL_ARGs into a usable SEL_ARG
+ tree.
+
+ Another example:
+ tree3= SEL_TREE { SEL_ARG{key1part1 = 1} }
+ tree4= SEL_TREE { SEL_ARG{key2part2 = 2} } -- can't make quick range select
+ from this
+ call tree_or(tree3, tree4) -- creates a SEL_MERGE ot of which no index
+ merge can be constructed, but it is potentially useful, as anding it with
+ tree5= SEL_TREE { SEL_ARG{key2part1 = 3} } creates an index merge that
+ represents the formula
+ key1part1=1 AND key2part1=3 OR key2part1=3 AND key2part2=2
+ for which an index merge can be built.
+
+ Any final SEL_TREE may contain SEL_ARG trees for which no quick select
+ can be built. Such SEL_ARG trees should be removed from the range part
+ before different range scans are evaluated. Such SEL_ARG trees also should
+ be removed from all range trees of each index merge before different
+ possible index merge plans are evaluated. If after this removal one
+ of the range trees in the index merge becomes empty the whole index merge
+ must be discarded.
+
+ RETURN
+ 0 Ok, some suitable trees left
+ 1 No tree->keys[] left.
+*/
+
+static bool remove_nonrange_trees(RANGE_OPT_PARAM *param, SEL_TREE *tree)
+{
+ bool res= FALSE;
+ for (uint i=0; i < param->keys; i++)
+ {
+ if (tree->keys[i])
+ {
+ if (tree->keys[i]->part)
+ {
+ tree->keys[i]= NULL;
+ tree->keys_map.clear_bit(i);
+ }
+ else
+ res= TRUE;
+ }
+ }
+ return !res;
+}
+
+
+/*
+ Build a SEL_TREE for a disjunction out of such trees for the disjuncts
+
+ SYNOPSIS
+ tree_or()
+ param Context info for the operation
+ tree1 SEL_TREE for the first disjunct
+ tree2 SEL_TREE for the second disjunct
+
+ DESCRIPTION
+ This function builds a tree for the formula (A OR B) out of the trees
+ tree1 and tree2 that has been built for the formulas A and B respectively.
+
+ In a general case
+ tree1 represents the formula RT1 AND MT1,
+ where RT1=R1_1 AND ... AND R1_k1, MT1=M1_1 AND ... AND M1_l1;
+ tree2 represents the formula RT2 AND MT2
+ where RT2=R2_1 AND ... AND R2_k2, MT2=M2_1 and ... and M2_l2.
+
+ The function constructs the result tree according the formula
+ (RT1 OR RT2) AND (MT1 OR RT1) AND (MT2 OR RT2) AND (MT1 OR MT2)
+ that is equivalent to the formula (RT1 AND MT1) OR (RT2 AND MT2).
+
+ To limit the number of produced imerges the function considers
+ a weaker formula than the original one:
+ (RT1 AND M1_1) OR (RT2 AND M2_1)
+ that is equivalent to:
+ (RT1 OR RT2) (1)
+ AND
+ (M1_1 OR M2_1) (2)
+ AND
+ (M1_1 OR RT2) (3)
+ AND
+ (M2_1 OR RT1) (4)
+
+ For the first conjunct (1) the function builds a tree with a range part
+ and, possibly, one imerge. For the other conjuncts (2-4)the function
+ produces sets of imerges. All constructed imerges are included into the
+ result tree.
+
+ For the formula (1) the function produces the tree representing a formula
+ of the structure RT [AND M], such that:
+ - the range tree rt contains the result of oring SEL_ARG trees from rt1
+ and rt2
+ - the imerge m consists of two range trees rt1 and rt2.
+ The imerge m is added if it's not true that rt1 and rt2 must be ored
+ If rt1 and rt2 can't be ored rt is empty and only m is produced for (1).
+
+ To produce imerges for the formula (2) the function calls the function
+ imerge_list_or_list passing it the merge parts of tree1 and tree2 as
+ parameters.
+
+ To produce imerges for the formula (3) the function calls the function
+ imerge_list_or_tree passing it the imerge m1_1 and the range tree rt2 as
+ parameters. Similarly, to produce imerges for the formula (4) the function
+ calls the function imerge_list_or_tree passing it the imerge m2_1 and the
+ range tree rt1.
+
+ If rt1 is empty then the trees for (1) and (4) are empty.
+ If rt2 is empty then the trees for (1) and (3) are empty.
+ If mt1 is empty then the trees for (2) and (3) are empty.
+ If mt2 is empty then the trees for (2) and (4) are empty.
+
+ RETURN
+ The result tree for the operation if a success
+ 0 - otherwise
+*/
+
+static SEL_TREE *
+tree_or(RANGE_OPT_PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2)
+{
+ DBUG_ENTER("tree_or");
+ if (!tree1 || !tree2)
+ DBUG_RETURN(0);
+ if (tree1->type == SEL_TREE::IMPOSSIBLE || tree2->type == SEL_TREE::ALWAYS)
+ DBUG_RETURN(tree2);
+ if (tree2->type == SEL_TREE::IMPOSSIBLE || tree1->type == SEL_TREE::ALWAYS)
+ DBUG_RETURN(tree1);
+ if (tree1->type == SEL_TREE::MAYBE)
+ DBUG_RETURN(tree1); // Can't use this
+ if (tree2->type == SEL_TREE::MAYBE)
+ DBUG_RETURN(tree2);
+
+ SEL_TREE *result= NULL;
+ key_map result_keys;
+ key_map ored_keys;
+ SEL_TREE *rtree[2]= {NULL,NULL};
+ SEL_IMERGE *imerge[2]= {NULL, NULL};
+ bool no_ranges1= tree1->without_ranges();
+ bool no_ranges2= tree2->without_ranges();
+ bool no_merges1= tree1->without_imerges();
+ bool no_merges2= tree2->without_imerges();
+ if (!no_ranges1 && !no_merges2)
+ {
+ rtree[0]= new SEL_TREE(tree1, TRUE, param);
+ imerge[1]= new SEL_IMERGE(tree2->merges.head(), 0, param);
+ }
+ if (!no_ranges2 && !no_merges1)
+ {
+ rtree[1]= new SEL_TREE(tree2, TRUE, param);
+ imerge[0]= new SEL_IMERGE(tree1->merges.head(), 0, param);
+ }
+ bool no_imerge_from_ranges= FALSE;
+ if (!(result= new SEL_TREE()))
+ DBUG_RETURN(result);
+
+ /* Build the range part of the tree for the formula (1) */
+ if (sel_trees_can_be_ored(param, tree1, tree2, &ored_keys))
+ {
+ bool must_be_ored= sel_trees_must_be_ored(param, tree1, tree2, ored_keys);
+ no_imerge_from_ranges= must_be_ored;
+ key_map::Iterator it(ored_keys);
+ int key_no;
+ while ((key_no= it++) != key_map::Iterator::BITMAP_END)
+ {
+ SEL_ARG *key1= tree1->keys[key_no];
+ SEL_ARG *key2= tree2->keys[key_no];
+ if (!must_be_ored)
+ {
+ key1->incr_refs();
+ key2->incr_refs();
+ }
+ if ((result->keys[key_no]= key_or(param, key1, key2)))
+ result->keys_map.set_bit(key_no);
+ }
+ result->type= tree1->type;
+ }
+
+ if (no_imerge_from_ranges && no_merges1 && no_merges2)
+ {
+ if (result->keys_map.is_clear_all())
+ result->type= SEL_TREE::ALWAYS;
+ DBUG_RETURN(result);
+ }
+
+ SEL_IMERGE *imerge_from_ranges;
+ if (!(imerge_from_ranges= new SEL_IMERGE()))
+ result= NULL;
+ else if (!no_ranges1 && !no_ranges2 && !no_imerge_from_ranges)
+ {
+ /* Build the imerge part of the tree for the formula (1) */
+ SEL_TREE *rt1= tree1;
+ SEL_TREE *rt2= tree2;
+ if (no_merges1)
+ rt1= new SEL_TREE(tree1, TRUE, param);
+ if (no_merges2)
+ rt2= new SEL_TREE(tree2, TRUE, param);
+ if (!rt1 || !rt2 ||
+ result->merges.push_back(imerge_from_ranges) ||
+ imerge_from_ranges->or_sel_tree(param, rt1) ||
+ imerge_from_ranges->or_sel_tree(param, rt2))
+ result= NULL;
+ }
+ if (!result)
+ DBUG_RETURN(result);
+
+ result->type= tree1->type;
+
+ if (!no_merges1 && !no_merges2 &&
+ !imerge_list_or_list(param, &tree1->merges, &tree2->merges))
+ {
+ /* Build the imerges for the formula (2) */
+ imerge_list_and_list(&result->merges, &tree1->merges);
+ }
+
+ /* Build the imerges for the formulas (3) and (4) */
+ for (uint i=0; i < 2; i++)
+ {
+ List<SEL_IMERGE> merges;
+ SEL_TREE *rt= rtree[i];
+ SEL_IMERGE *im= imerge[1-i];
+
+ if (rt && im && !merges.push_back(im) &&
+ !imerge_list_or_tree(param, &merges, rt))
+ imerge_list_and_list(&result->merges, &merges);
+ }
+
+ DBUG_RETURN(result);
+}
+
+
+/* And key trees where key1->part < key2 -> part */
+
+static SEL_ARG *
+and_all_keys(RANGE_OPT_PARAM *param, SEL_ARG *key1, SEL_ARG *key2,
+ uint clone_flag)
+{
+ SEL_ARG *next;
+ ulong use_count=key1->use_count;
+
+ if (key1->elements != 1)
+ {
+ key2->use_count+=key1->elements-1; //psergey: why we don't count that key1 has n-k-p?
+ key2->increment_use_count((int) key1->elements-1);
+ }
+ if (key1->type == SEL_ARG::MAYBE_KEY)
+ {
+ key1->right= key1->left= &null_element;
+ key1->next= key1->prev= 0;
+ }
+ for (next=key1->first(); next ; next=next->next)
+ {
+ if (next->next_key_part)
+ {
+ SEL_ARG *tmp= key_and(param, next->next_key_part, key2, clone_flag);
+ if (tmp && tmp->type == SEL_ARG::IMPOSSIBLE)
+ {
+ key1=key1->tree_delete(next);
+ continue;
+ }
+ next->next_key_part=tmp;
+ if (use_count)
+ next->increment_use_count(use_count);
+ if (param->alloced_sel_args > SEL_ARG::MAX_SEL_ARGS)
+ break;
+ }
+ else
+ next->next_key_part=key2;
+ }
+ if (!key1)
+ return &null_element; // Impossible ranges
+ key1->use_count++;
+ key1->max_part_no= MY_MAX(key2->max_part_no, key2->part+1);
+ return key1;
+}
+
+
+/*
+ Produce a SEL_ARG graph that represents "key1 AND key2"
+
+ SYNOPSIS
+ key_and()
+ param Range analysis context (needed to track if we have allocated
+ too many SEL_ARGs)
+ key1 First argument, root of its RB-tree
+ key2 Second argument, root of its RB-tree
+
+ RETURN
+ RB-tree root of the resulting SEL_ARG graph.
+ NULL if the result of AND operation is an empty interval {0}.
+*/
+
+static SEL_ARG *
+key_and(RANGE_OPT_PARAM *param, SEL_ARG *key1, SEL_ARG *key2, uint clone_flag)
+{
+ if (!key1)
+ return key2;
+ if (!key2)
+ return key1;
+ if (key1->part != key2->part)
+ {
+ if (key1->part > key2->part)
+ {
+ swap_variables(SEL_ARG *, key1, key2);
+ clone_flag=swap_clone_flag(clone_flag);
+ }
+ // key1->part < key2->part
+ key1->use_count--;
+ if (key1->use_count > 0)
+ if (!(key1= key1->clone_tree(param)))
+ return 0; // OOM
+ return and_all_keys(param, key1, key2, clone_flag);
+ }
+
+ if (((clone_flag & CLONE_KEY2_MAYBE) &&
+ !(clone_flag & CLONE_KEY1_MAYBE) &&
+ key2->type != SEL_ARG::MAYBE_KEY) ||
+ key1->type == SEL_ARG::MAYBE_KEY)
+ { // Put simple key in key2
+ swap_variables(SEL_ARG *, key1, key2);
+ clone_flag=swap_clone_flag(clone_flag);
+ }
+
+ /* If one of the key is MAYBE_KEY then the found region may be smaller */
+ if (key2->type == SEL_ARG::MAYBE_KEY)
+ {
+ if (key1->use_count > 1)
+ {
+ key1->use_count--;
+ if (!(key1=key1->clone_tree(param)))
+ return 0; // OOM
+ key1->use_count++;
+ }
+ if (key1->type == SEL_ARG::MAYBE_KEY)
+ { // Both are maybe key
+ key1->next_key_part=key_and(param, key1->next_key_part,
+ key2->next_key_part, clone_flag);
+ if (key1->next_key_part &&
+ key1->next_key_part->type == SEL_ARG::IMPOSSIBLE)
+ return key1;
+ }
+ else
+ {
+ key1->maybe_smaller();
+ if (key2->next_key_part)
+ {
+ key1->use_count--; // Incremented in and_all_keys
+ return and_all_keys(param, key1, key2, clone_flag);
+ }
+ key2->use_count--; // Key2 doesn't have a tree
+ }
+ return key1;
+ }
+
+ if ((key1->min_flag | key2->min_flag) & GEOM_FLAG)
+ {
+ /* TODO: why not leave one of the trees? */
+ key1->free_tree();
+ key2->free_tree();
+ return 0; // Can't optimize this
+ }
+
+ key1->use_count--;
+ key2->use_count--;
+ SEL_ARG *e1=key1->first(), *e2=key2->first(), *new_tree=0;
+ uint max_part_no= MY_MAX(key1->max_part_no, key2->max_part_no);
+
+ while (e1 && e2)
+ {
+ int cmp=e1->cmp_min_to_min(e2);
+ if (cmp < 0)
+ {
+ if (get_range(&e1,&e2,key1))
+ continue;
+ }
+ else if (get_range(&e2,&e1,key2))
+ continue;
+ SEL_ARG *next=key_and(param, e1->next_key_part, e2->next_key_part,
+ clone_flag);
+ e1->incr_refs();
+ e2->incr_refs();
+ if (!next || next->type != SEL_ARG::IMPOSSIBLE)
+ {
+ SEL_ARG *new_arg= e1->clone_and(e2);
+ if (!new_arg)
+ return &null_element; // End of memory
+ new_arg->next_key_part=next;
+ if (!new_tree)
+ {
+ new_tree=new_arg;
+ }
+ else
+ new_tree=new_tree->insert(new_arg);
+ }
+ if (e1->cmp_max_to_max(e2) < 0)
+ e1=e1->next; // e1 can't overlapp next e2
+ else
+ e2=e2->next;
+ }
+ key1->free_tree();
+ key2->free_tree();
+ if (!new_tree)
+ return &null_element; // Impossible range
+ new_tree->max_part_no= max_part_no;
+ return new_tree;
+}
+
+
+static bool
+get_range(SEL_ARG **e1,SEL_ARG **e2,SEL_ARG *root1)
+{
+ (*e1)=root1->find_range(*e2); // first e1->min < e2->min
+ if ((*e1)->cmp_max_to_min(*e2) < 0)
+ {
+ if (!((*e1)=(*e1)->next))
+ return 1;
+ if ((*e1)->cmp_min_to_max(*e2) > 0)
+ {
+ (*e2)=(*e2)->next;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ Combine two range expression under a common OR. On a logical level, the
+ transformation is key_or( expr1, expr2 ) => expr1 OR expr2.
+
+ Both expressions are assumed to be in the SEL_ARG format. In a logic sense,
+ theformat is reminiscent of DNF, since an expression such as the following
+
+ ( 1 < kp1 < 10 AND p1 ) OR ( 10 <= kp2 < 20 AND p2 )
+
+ where there is a key consisting of keyparts ( kp1, kp2, ..., kpn ) and p1
+ and p2 are valid SEL_ARG expressions over keyparts kp2 ... kpn, is a valid
+ SEL_ARG condition. The disjuncts appear ordered by the minimum endpoint of
+ the first range and ranges must not overlap. It follows that they are also
+ ordered by maximum endpoints. Thus
+
+ ( 1 < kp1 <= 2 AND ( kp2 = 2 OR kp2 = 3 ) ) OR kp1 = 3
+
+ Is a a valid SER_ARG expression for a key of at least 2 keyparts.
+
+ For simplicity, we will assume that expr2 is a single range predicate,
+ i.e. on the form ( a < x < b AND ... ). It is easy to generalize to a
+ disjunction of several predicates by subsequently call key_or for each
+ disjunct.
+
+ The algorithm iterates over each disjunct of expr1, and for each disjunct
+ where the first keypart's range overlaps with the first keypart's range in
+ expr2:
+
+ If the predicates are equal for the rest of the keyparts, or if there are
+ no more, the range in expr2 has its endpoints copied in, and the SEL_ARG
+ node in expr2 is deallocated. If more ranges became connected in expr1, the
+ surplus is also dealocated. If they differ, two ranges are created.
+
+ - The range leading up to the overlap. Empty if endpoints are equal.
+
+ - The overlapping sub-range. May be the entire range if they are equal.
+
+ Finally, there may be one more range if expr2's first keypart's range has a
+ greater maximum endpoint than the last range in expr1.
+
+ For the overlapping sub-range, we recursively call key_or. Thus in order to
+ compute key_or of
+
+ (1) ( 1 < kp1 < 10 AND 1 < kp2 < 10 )
+
+ (2) ( 2 < kp1 < 20 AND 4 < kp2 < 20 )
+
+ We create the ranges 1 < kp <= 2, 2 < kp1 < 10, 10 <= kp1 < 20. For the
+ first one, we simply hook on the condition for the second keypart from (1)
+ : 1 < kp2 < 10. For the second range 2 < kp1 < 10, key_or( 1 < kp2 < 10, 4
+ < kp2 < 20 ) is called, yielding 1 < kp2 < 20. For the last range, we reuse
+ the range 4 < kp2 < 20 from (2) for the second keypart. The result is thus
+
+ ( 1 < kp1 <= 2 AND 1 < kp2 < 10 ) OR
+ ( 2 < kp1 < 10 AND 1 < kp2 < 20 ) OR
+ ( 10 <= kp1 < 20 AND 4 < kp2 < 20 )
+*/
+static SEL_ARG *
+key_or(RANGE_OPT_PARAM *param, SEL_ARG *key1,SEL_ARG *key2)
+{
+ if (!key1)
+ {
+ if (key2)
+ {
+ key2->use_count--;
+ key2->free_tree();
+ }
+ return 0;
+ }
+ if (!key2)
+ {
+ key1->use_count--;
+ key1->free_tree();
+ return 0;
+ }
+ key1->use_count--;
+ key2->use_count--;
+
+ if (key1->part != key2->part ||
+ (key1->min_flag | key2->min_flag) & GEOM_FLAG)
+ {
+ key1->free_tree();
+ key2->free_tree();
+ return 0; // Can't optimize this
+ }
+
+ // If one of the key is MAYBE_KEY then the found region may be bigger
+ if (key1->type == SEL_ARG::MAYBE_KEY)
+ {
+ key2->free_tree();
+ key1->use_count++;
+ return key1;
+ }
+ if (key2->type == SEL_ARG::MAYBE_KEY)
+ {
+ key1->free_tree();
+ key2->use_count++;
+ return key2;
+ }
+
+ if (key1->use_count > 0)
+ {
+ if (key2->use_count == 0 || key1->elements > key2->elements)
+ {
+ swap_variables(SEL_ARG *,key1,key2);
+ }
+ if (key1->use_count > 0 && !(key1=key1->clone_tree(param)))
+ return 0; // OOM
+ }
+
+ // Add tree at key2 to tree at key1
+ bool key2_shared=key2->use_count != 0;
+ key1->maybe_flag|=key2->maybe_flag;
+
+ /*
+ Notation for illustrations used in the rest of this function:
+
+ Range: [--------]
+ ^ ^
+ start stop
+
+ Two overlapping ranges:
+ [-----] [----] [--]
+ [---] or [---] or [-------]
+
+ Ambiguity: ***
+ The range starts or stops somewhere in the "***" range.
+ Example: a starts before b and may end before/the same plase/after b
+ a: [----***]
+ b: [---]
+
+ Adjacent ranges:
+ Ranges that meet but do not overlap. Example: a = "x < 3", b = "x >= 3"
+ a: ----]
+ b: [----
+ */
+
+ uint max_part_no= MY_MAX(key1->max_part_no, key2->max_part_no);
+
+ for (key2=key2->first(); key2; )
+ {
+ /*
+ key1 consists of one or more ranges. tmp is the range currently
+ being handled.
+
+ initialize tmp to the latest range in key1 that starts the same
+ place or before the range in key2 starts
+
+ key2: [------]
+ key1: [---] [-----] [----]
+ ^
+ tmp
+ */
+ SEL_ARG *tmp=key1->find_range(key2);
+
+ /*
+ Used to describe how two key values are positioned compared to
+ each other. Consider key_value_a.<cmp_func>(key_value_b):
+
+ -2: key_value_a is smaller than key_value_b, and they are adjacent
+ -1: key_value_a is smaller than key_value_b (not adjacent)
+ 0: the key values are equal
+ 1: key_value_a is bigger than key_value_b (not adjacent)
+ -2: key_value_a is bigger than key_value_b, and they are adjacent
+
+ Example: "cmp= tmp->cmp_max_to_min(key2)"
+
+ key2: [-------- (10 <= x ...)
+ tmp: -----] (... x < 10) => cmp==-2
+ tmp: ----] (... x <= 9) => cmp==-1
+ tmp: ------] (... x = 10) => cmp== 0
+ tmp: --------] (... x <= 12) => cmp== 1
+ (cmp == 2 does not make sense for cmp_max_to_min())
+ */
+ int cmp= 0;
+
+ if (!tmp)
+ {
+ /*
+ The range in key2 starts before the first range in key1. Use
+ the first range in key1 as tmp.
+
+ key2: [--------]
+ key1: [****--] [----] [-------]
+ ^
+ tmp
+ */
+ tmp=key1->first();
+ cmp= -1;
+ }
+ else if ((cmp= tmp->cmp_max_to_min(key2)) < 0)
+ {
+ /*
+ This is the case:
+ key2: [-------]
+ tmp: [----**]
+ */
+ SEL_ARG *next=tmp->next;
+ if (cmp == -2 && eq_tree(tmp->next_key_part,key2->next_key_part))
+ {
+ /*
+ Adjacent (cmp==-2) and equal next_key_parts => ranges can be merged
+
+ This is the case:
+ key2: [-------]
+ tmp: [----]
+
+ Result:
+ key2: [-------------] => inserted into key1 below
+ tmp: => deleted
+ */
+ SEL_ARG *key2_next=key2->next;
+ if (key2_shared)
+ {
+ if (!(key2=new SEL_ARG(*key2)))
+ return 0; // out of memory
+ key2->increment_use_count(key1->use_count+1);
+ key2->next=key2_next; // New copy of key2
+ }
+
+ key2->copy_min(tmp);
+ if (!(key1=key1->tree_delete(tmp)))
+ { // Only one key in tree
+ key1=key2;
+ key1->make_root();
+ key2=key2_next;
+ break;
+ }
+ }
+ if (!(tmp=next)) // Move to next range in key1. Now tmp.min > key2.min
+ break; // No more ranges in key1. Copy rest of key2
+ }
+
+ if (cmp < 0)
+ {
+ /*
+ This is the case:
+ key2: [--***]
+ tmp: [----]
+ */
+ int tmp_cmp;
+ if ((tmp_cmp=tmp->cmp_min_to_max(key2)) > 0)
+ {
+ /*
+ This is the case:
+ key2: [------**]
+ tmp: [----]
+ */
+ if (tmp_cmp == 2 && eq_tree(tmp->next_key_part,key2->next_key_part))
+ {
+ /*
+ Adjacent ranges with equal next_key_part. Merge like this:
+
+ This is the case:
+ key2: [------]
+ tmp: [-----]
+
+ Result:
+ key2: [------]
+ tmp: [-------------]
+
+ Then move on to next key2 range.
+ */
+ tmp->copy_min_to_min(key2);
+ key1->merge_flags(key2);
+ if (tmp->min_flag & NO_MIN_RANGE &&
+ tmp->max_flag & NO_MAX_RANGE)
+ {
+ if (key1->maybe_flag)
+ return new SEL_ARG(SEL_ARG::MAYBE_KEY);
+ return 0;
+ }
+ key2->increment_use_count(-1); // Free not used tree
+ key2=key2->next;
+ continue;
+ }
+ else
+ {
+ /*
+ key2 not adjacent to tmp or has different next_key_part.
+ Insert into key1 and move to next range in key2
+
+ This is the case:
+ key2: [------**]
+ tmp: [----]
+
+ Result:
+ key1_ [------**][----]
+ ^ ^
+ insert tmp
+ */
+ SEL_ARG *next=key2->next;
+ if (key2_shared)
+ {
+ SEL_ARG *cpy= new SEL_ARG(*key2); // Must make copy
+ if (!cpy)
+ return 0; // OOM
+ key1=key1->insert(cpy);
+ key2->increment_use_count(key1->use_count+1);
+ }
+ else
+ key1=key1->insert(key2); // Will destroy key2_root
+ key2=next;
+ continue;
+ }
+ }
+ }
+
+ /*
+ The ranges in tmp and key2 are overlapping:
+
+ key2: [----------]
+ tmp: [*****-----*****]
+
+ Corollary: tmp.min <= key2.max
+ */
+ if (eq_tree(tmp->next_key_part,key2->next_key_part))
+ {
+ // Merge overlapping ranges with equal next_key_part
+ if (tmp->is_same(key2))
+ {
+ /*
+ Found exact match of key2 inside key1.
+ Use the relevant range in key1.
+ */
+ tmp->merge_flags(key2); // Copy maybe flags
+ key2->increment_use_count(-1); // Free not used tree
+ }
+ else
+ {
+ SEL_ARG *last= tmp;
+ SEL_ARG *first= tmp;
+
+ /*
+ Find the last range in key1 that overlaps key2 and
+ where all ranges first...last have the same next_key_part as
+ key2.
+
+ key2: [****----------------------*******]
+ key1: [--] [----] [---] [-----] [xxxx]
+ ^ ^ ^
+ first last different next_key_part
+
+ Since key2 covers them, the ranges between first and last
+ are merged into one range by deleting first...last-1 from
+ the key1 tree. In the figure, this applies to first and the
+ two consecutive ranges. The range of last is then extended:
+ * last.min: Set to MY_MIN(key2.min, first.min)
+ * last.max: If there is a last->next that overlaps key2 (i.e.,
+ last->next has a different next_key_part):
+ Set adjacent to last->next.min
+ Otherwise: Set to MY_MAX(key2.max, last.max)
+
+ Result:
+ key2: [****----------------------*******]
+ [--] [----] [---] => deleted from key1
+ key1: [**------------------------***][xxxx]
+ ^ ^
+ tmp=last different next_key_part
+ */
+ while (last->next && last->next->cmp_min_to_max(key2) <= 0 &&
+ eq_tree(last->next->next_key_part,key2->next_key_part))
+ {
+ /*
+ last->next is covered by key2 and has same next_key_part.
+ last can be deleted
+ */
+ SEL_ARG *save=last;
+ last=last->next;
+ key1=key1->tree_delete(save);
+ }
+ // Redirect tmp to last which will cover the entire range
+ tmp= last;
+
+ /*
+ We need the minimum endpoint of first so we can compare it
+ with the minimum endpoint of the enclosing key2 range.
+ */
+ last->copy_min(first);
+ bool full_range= last->copy_min(key2);
+ if (!full_range)
+ {
+ if (last->next && key2->cmp_max_to_min(last->next) >= 0)
+ {
+ /*
+ This is the case:
+ key2: [-------------]
+ key1: [***------] [xxxx]
+ ^ ^
+ last different next_key_part
+
+ Extend range of last up to last->next:
+ key2: [-------------]
+ key1: [***--------][xxxx]
+ */
+ last->copy_min_to_max(last->next);
+ }
+ else
+ /*
+ This is the case:
+ key2: [--------*****]
+ key1: [***---------] [xxxx]
+ ^ ^
+ last different next_key_part
+
+ Extend range of last up to MY_MAX(last.max, key2.max):
+ key2: [--------*****]
+ key1: [***----------**] [xxxx]
+ */
+ full_range= last->copy_max(key2);
+ }
+ if (full_range)
+ { // Full range
+ key1->free_tree();
+ for (; key2 ; key2=key2->next)
+ key2->increment_use_count(-1); // Free not used tree
+ if (key1->maybe_flag)
+ return new SEL_ARG(SEL_ARG::MAYBE_KEY);
+ return 0;
+ }
+ }
+ }
+
+ if (cmp >= 0 && tmp->cmp_min_to_min(key2) < 0)
+ {
+ /*
+ This is the case ("cmp>=0" means that tmp.max >= key2.min):
+ key2: [----]
+ tmp: [------------*****]
+ */
+
+ if (!tmp->next_key_part)
+ {
+ /*
+ tmp->next_key_part is empty: cut the range that is covered
+ by tmp from key2.
+ Reason: (key2->next_key_part OR tmp->next_key_part) will be
+ empty and therefore equal to tmp->next_key_part. Thus, this
+ part of the key2 range is completely covered by tmp.
+ */
+ if (tmp->cmp_max_to_max(key2) >= 0)
+ {
+ /*
+ tmp covers the entire range in key2.
+ key2: [----]
+ tmp: [-----------------]
+
+ Move on to next range in key2
+ */
+ key2->increment_use_count(-1); // Free not used tree
+ key2=key2->next;
+ continue;
+ }
+ else
+ {
+ /*
+ This is the case:
+ key2: [-------]
+ tmp: [---------]
+
+ Result:
+ key2: [---]
+ tmp: [---------]
+ */
+ if (key2->use_count)
+ {
+ SEL_ARG *key2_cpy= new SEL_ARG(*key2);
+ if (key2_cpy)
+ return 0;
+ key2= key2_cpy;
+ }
+ key2->copy_max_to_min(tmp);
+ continue;
+ }
+ }
+
+ /*
+ The ranges are overlapping but have not been merged because
+ next_key_part of tmp and key2 differ.
+ key2: [----]
+ tmp: [------------*****]
+
+ Split tmp in two where key2 starts:
+ key2: [----]
+ key1: [--------][--*****]
+ ^ ^
+ insert tmp
+ */
+ SEL_ARG *new_arg=tmp->clone_first(key2);
+ if (!new_arg)
+ return 0; // OOM
+ if ((new_arg->next_key_part= tmp->next_key_part))
+ new_arg->increment_use_count(key1->use_count+1);
+ tmp->copy_min_to_min(key2);
+ key1=key1->insert(new_arg);
+ } // tmp.min >= key2.min due to this if()
+
+ /*
+ Now key2.min <= tmp.min <= key2.max:
+ key2: [---------]
+ tmp: [****---*****]
+ */
+ SEL_ARG key2_cpy(*key2); // Get copy we can modify
+ for (;;)
+ {
+ if (tmp->cmp_min_to_min(&key2_cpy) > 0)
+ {
+ /*
+ This is the case:
+ key2_cpy: [------------]
+ key1: [-*****]
+ ^
+ tmp
+
+ Result:
+ key2_cpy: [---]
+ key1: [-------][-*****]
+ ^ ^
+ insert tmp
+ */
+ SEL_ARG *new_arg=key2_cpy.clone_first(tmp);
+ if (!new_arg)
+ return 0; // OOM
+ if ((new_arg->next_key_part=key2_cpy.next_key_part))
+ new_arg->increment_use_count(key1->use_count+1);
+ key1=key1->insert(new_arg);
+ key2_cpy.copy_min_to_min(tmp);
+ }
+ // Now key2_cpy.min == tmp.min
+
+ if ((cmp= tmp->cmp_max_to_max(&key2_cpy)) <= 0)
+ {
+ /*
+ tmp.max <= key2_cpy.max:
+ key2_cpy: a) [-------] or b) [----]
+ tmp: [----] [----]
+
+ Steps:
+ 1) Update next_key_part of tmp: OR it with key2_cpy->next_key_part.
+ 2) If case a: Insert range [tmp.max, key2_cpy.max] into key1 using
+ next_key_part of key2_cpy
+
+ Result:
+ key1: a) [----][-] or b) [----]
+ */
+ tmp->maybe_flag|= key2_cpy.maybe_flag;
+ key2_cpy.increment_use_count(key1->use_count+1);
+ tmp->next_key_part= key_or(param, tmp->next_key_part,
+ key2_cpy.next_key_part);
+
+ if (!cmp)
+ break; // case b: done with this key2 range
+
+ // Make key2_cpy the range [tmp.max, key2_cpy.max]
+ key2_cpy.copy_max_to_min(tmp);
+ if (!(tmp=tmp->next))
+ {
+ /*
+ No more ranges in key1. Insert key2_cpy and go to "end"
+ label to insert remaining ranges in key2 if any.
+ */
+ SEL_ARG *tmp2= new SEL_ARG(key2_cpy);
+ if (!tmp2)
+ return 0; // OOM
+ key1=key1->insert(tmp2);
+ key2=key2->next;
+ goto end;
+ }
+ if (tmp->cmp_min_to_max(&key2_cpy) > 0)
+ {
+ /*
+ The next range in key1 does not overlap with key2_cpy.
+ Insert this range into key1 and move on to the next range
+ in key2.
+ */
+ SEL_ARG *tmp2= new SEL_ARG(key2_cpy);
+ if (!tmp2)
+ return 0; // OOM
+ key1=key1->insert(tmp2);
+ break;
+ }
+ /*
+ key2_cpy overlaps with the next range in key1 and the case
+ is now "key2.min <= tmp.min <= key2.max". Go back to for(;;)
+ to handle this situation.
+ */
+ continue;
+ }
+ else
+ {
+ /*
+ This is the case:
+ key2_cpy: [-------]
+ tmp: [------------]
+
+ Result:
+ key1: [-------][---]
+ ^ ^
+ new_arg tmp
+ Steps:
+ 0) If tmp->next_key_part is empty: do nothing. Reason:
+ (key2_cpy->next_key_part OR tmp->next_key_part) will be
+ empty and therefore equal to tmp->next_key_part. Thus,
+ the range in key2_cpy is completely covered by tmp
+ 1) Make new_arg with range [tmp.min, key2_cpy.max].
+ new_arg->next_key_part is OR between next_key_part
+ of tmp and key2_cpy
+ 2) Make tmp the range [key2.max, tmp.max]
+ 3) Insert new_arg into key1
+ */
+ if (!tmp->next_key_part) // Step 0
+ {
+ key2_cpy.increment_use_count(-1); // Free not used tree
+ break;
+ }
+ SEL_ARG *new_arg=tmp->clone_last(&key2_cpy);
+ if (!new_arg)
+ return 0; // OOM
+ tmp->copy_max_to_min(&key2_cpy);
+ tmp->increment_use_count(key1->use_count+1);
+ /* Increment key count as it may be used for next loop */
+ key2_cpy.increment_use_count(1);
+ new_arg->next_key_part= key_or(param, tmp->next_key_part,
+ key2_cpy.next_key_part);
+ key1=key1->insert(new_arg);
+ break;
+ }
+ }
+ // Move on to next range in key2
+ key2=key2->next;
+ }
+
+end:
+ /*
+ Add key2 ranges that are non-overlapping with and higher than the
+ highest range in key1.
+ */
+ while (key2)
+ {
+ SEL_ARG *next=key2->next;
+ if (key2_shared)
+ {
+ SEL_ARG *tmp=new SEL_ARG(*key2); // Must make copy
+ if (!tmp)
+ return 0;
+ key2->increment_use_count(key1->use_count+1);
+ key1=key1->insert(tmp);
+ }
+ else
+ key1=key1->insert(key2); // Will destroy key2_root
+ key2=next;
+ }
+ key1->use_count++;
+
+ key1->max_part_no= max_part_no;
+ return key1;
+}
+
+
+/* Compare if two trees are equal */
+
+static bool eq_tree(SEL_ARG* a,SEL_ARG *b)
+{
+ if (a == b)
+ return 1;
+ if (!a || !b || !a->is_same(b))
+ return 0;
+ if (a->left != &null_element && b->left != &null_element)
+ {
+ if (!eq_tree(a->left,b->left))
+ return 0;
+ }
+ else if (a->left != &null_element || b->left != &null_element)
+ return 0;
+ if (a->right != &null_element && b->right != &null_element)
+ {
+ if (!eq_tree(a->right,b->right))
+ return 0;
+ }
+ else if (a->right != &null_element || b->right != &null_element)
+ return 0;
+ if (a->next_key_part != b->next_key_part)
+ { // Sub range
+ if (!a->next_key_part != !b->next_key_part ||
+ !eq_tree(a->next_key_part, b->next_key_part))
+ return 0;
+ }
+ return 1;
+}
+
+
+SEL_ARG *
+SEL_ARG::insert(SEL_ARG *key)
+{
+ SEL_ARG *element,**UNINIT_VAR(par),*UNINIT_VAR(last_element);
+
+ for (element= this; element != &null_element ; )
+ {
+ last_element=element;
+ if (key->cmp_min_to_min(element) > 0)
+ {
+ par= &element->right; element= element->right;
+ }
+ else
+ {
+ par = &element->left; element= element->left;
+ }
+ }
+ *par=key;
+ key->parent=last_element;
+ /* Link in list */
+ if (par == &last_element->left)
+ {
+ key->next=last_element;
+ if ((key->prev=last_element->prev))
+ key->prev->next=key;
+ last_element->prev=key;
+ }
+ else
+ {
+ if ((key->next=last_element->next))
+ key->next->prev=key;
+ key->prev=last_element;
+ last_element->next=key;
+ }
+ key->left=key->right= &null_element;
+ SEL_ARG *root=rb_insert(key); // rebalance tree
+ root->use_count=this->use_count; // copy root info
+ root->elements= this->elements+1;
+ root->maybe_flag=this->maybe_flag;
+ return root;
+}
+
+
+/*
+** Find best key with min <= given key
+** Because the call context this should never return 0 to get_range
+*/
+
+SEL_ARG *
+SEL_ARG::find_range(SEL_ARG *key)
+{
+ SEL_ARG *element=this,*found=0;
+
+ for (;;)
+ {
+ if (element == &null_element)
+ return found;
+ int cmp=element->cmp_min_to_min(key);
+ if (cmp == 0)
+ return element;
+ if (cmp < 0)
+ {
+ found=element;
+ element=element->right;
+ }
+ else
+ element=element->left;
+ }
+}
+
+
+/*
+ Remove a element from the tree
+
+ SYNOPSIS
+ tree_delete()
+ key Key that is to be deleted from tree (this)
+
+ NOTE
+ This also frees all sub trees that is used by the element
+
+ RETURN
+ root of new tree (with key deleted)
+*/
+
+SEL_ARG *
+SEL_ARG::tree_delete(SEL_ARG *key)
+{
+ enum leaf_color remove_color;
+ SEL_ARG *root,*nod,**par,*fix_par;
+ DBUG_ENTER("tree_delete");
+
+ root=this;
+ this->parent= 0;
+
+ /* Unlink from list */
+ if (key->prev)
+ key->prev->next=key->next;
+ if (key->next)
+ key->next->prev=key->prev;
+ key->increment_use_count(-1);
+ if (!key->parent)
+ par= &root;
+ else
+ par=key->parent_ptr();
+
+ if (key->left == &null_element)
+ {
+ *par=nod=key->right;
+ fix_par=key->parent;
+ if (nod != &null_element)
+ nod->parent=fix_par;
+ remove_color= key->color;
+ }
+ else if (key->right == &null_element)
+ {
+ *par= nod=key->left;
+ nod->parent=fix_par=key->parent;
+ remove_color= key->color;
+ }
+ else
+ {
+ SEL_ARG *tmp=key->next; // next bigger key (exist!)
+ nod= *tmp->parent_ptr()= tmp->right; // unlink tmp from tree
+ fix_par=tmp->parent;
+ if (nod != &null_element)
+ nod->parent=fix_par;
+ remove_color= tmp->color;
+
+ tmp->parent=key->parent; // Move node in place of key
+ (tmp->left=key->left)->parent=tmp;
+ if ((tmp->right=key->right) != &null_element)
+ tmp->right->parent=tmp;
+ tmp->color=key->color;
+ *par=tmp;
+ if (fix_par == key) // key->right == key->next
+ fix_par=tmp; // new parent of nod
+ }
+
+ if (root == &null_element)
+ DBUG_RETURN(0); // Maybe root later
+ if (remove_color == BLACK)
+ root=rb_delete_fixup(root,nod,fix_par);
+ test_rb_tree(root,root->parent);
+
+ root->use_count=this->use_count; // Fix root counters
+ root->elements=this->elements-1;
+ root->maybe_flag=this->maybe_flag;
+ DBUG_RETURN(root);
+}
+
+
+ /* Functions to fix up the tree after insert and delete */
+
+static void left_rotate(SEL_ARG **root,SEL_ARG *leaf)
+{
+ SEL_ARG *y=leaf->right;
+ leaf->right=y->left;
+ if (y->left != &null_element)
+ y->left->parent=leaf;
+ if (!(y->parent=leaf->parent))
+ *root=y;
+ else
+ *leaf->parent_ptr()=y;
+ y->left=leaf;
+ leaf->parent=y;
+}
+
+static void right_rotate(SEL_ARG **root,SEL_ARG *leaf)
+{
+ SEL_ARG *y=leaf->left;
+ leaf->left=y->right;
+ if (y->right != &null_element)
+ y->right->parent=leaf;
+ if (!(y->parent=leaf->parent))
+ *root=y;
+ else
+ *leaf->parent_ptr()=y;
+ y->right=leaf;
+ leaf->parent=y;
+}
+
+
+SEL_ARG *
+SEL_ARG::rb_insert(SEL_ARG *leaf)
+{
+ SEL_ARG *y,*par,*par2,*root;
+ root= this; root->parent= 0;
+
+ leaf->color=RED;
+ while (leaf != root && (par= leaf->parent)->color == RED)
+ { // This can't be root or 1 level under
+ if (par == (par2= leaf->parent->parent)->left)
+ {
+ y= par2->right;
+ if (y->color == RED)
+ {
+ par->color=BLACK;
+ y->color=BLACK;
+ leaf=par2;
+ leaf->color=RED; /* And the loop continues */
+ }
+ else
+ {
+ if (leaf == par->right)
+ {
+ left_rotate(&root,leaf->parent);
+ par=leaf; /* leaf is now parent to old leaf */
+ }
+ par->color=BLACK;
+ par2->color=RED;
+ right_rotate(&root,par2);
+ break;
+ }
+ }
+ else
+ {
+ y= par2->left;
+ if (y->color == RED)
+ {
+ par->color=BLACK;
+ y->color=BLACK;
+ leaf=par2;
+ leaf->color=RED; /* And the loop continues */
+ }
+ else
+ {
+ if (leaf == par->left)
+ {
+ right_rotate(&root,par);
+ par=leaf;
+ }
+ par->color=BLACK;
+ par2->color=RED;
+ left_rotate(&root,par2);
+ break;
+ }
+ }
+ }
+ root->color=BLACK;
+ test_rb_tree(root,root->parent);
+ return root;
+}
+
+
+SEL_ARG *rb_delete_fixup(SEL_ARG *root,SEL_ARG *key,SEL_ARG *par)
+{
+ SEL_ARG *x,*w;
+ root->parent=0;
+
+ x= key;
+ while (x != root && x->color == SEL_ARG::BLACK)
+ {
+ if (x == par->left)
+ {
+ w=par->right;
+ if (w->color == SEL_ARG::RED)
+ {
+ w->color=SEL_ARG::BLACK;
+ par->color=SEL_ARG::RED;
+ left_rotate(&root,par);
+ w=par->right;
+ }
+ if (w->left->color == SEL_ARG::BLACK && w->right->color == SEL_ARG::BLACK)
+ {
+ w->color=SEL_ARG::RED;
+ x=par;
+ }
+ else
+ {
+ if (w->right->color == SEL_ARG::BLACK)
+ {
+ w->left->color=SEL_ARG::BLACK;
+ w->color=SEL_ARG::RED;
+ right_rotate(&root,w);
+ w=par->right;
+ }
+ w->color=par->color;
+ par->color=SEL_ARG::BLACK;
+ w->right->color=SEL_ARG::BLACK;
+ left_rotate(&root,par);
+ x=root;
+ break;
+ }
+ }
+ else
+ {
+ w=par->left;
+ if (w->color == SEL_ARG::RED)
+ {
+ w->color=SEL_ARG::BLACK;
+ par->color=SEL_ARG::RED;
+ right_rotate(&root,par);
+ w=par->left;
+ }
+ if (w->right->color == SEL_ARG::BLACK && w->left->color == SEL_ARG::BLACK)
+ {
+ w->color=SEL_ARG::RED;
+ x=par;
+ }
+ else
+ {
+ if (w->left->color == SEL_ARG::BLACK)
+ {
+ w->right->color=SEL_ARG::BLACK;
+ w->color=SEL_ARG::RED;
+ left_rotate(&root,w);
+ w=par->left;
+ }
+ w->color=par->color;
+ par->color=SEL_ARG::BLACK;
+ w->left->color=SEL_ARG::BLACK;
+ right_rotate(&root,par);
+ x=root;
+ break;
+ }
+ }
+ par=x->parent;
+ }
+ x->color=SEL_ARG::BLACK;
+ return root;
+}
+
+
+ /* Test that the properties for a red-black tree hold */
+
+#ifdef EXTRA_DEBUG
+int test_rb_tree(SEL_ARG *element,SEL_ARG *parent)
+{
+ int count_l,count_r;
+
+ if (element == &null_element)
+ return 0; // Found end of tree
+ if (element->parent != parent)
+ {
+ sql_print_error("Wrong tree: Parent doesn't point at parent");
+ return -1;
+ }
+ if (element->color == SEL_ARG::RED &&
+ (element->left->color == SEL_ARG::RED ||
+ element->right->color == SEL_ARG::RED))
+ {
+ sql_print_error("Wrong tree: Found two red in a row");
+ return -1;
+ }
+ if (element->left == element->right && element->left != &null_element)
+ { // Dummy test
+ sql_print_error("Wrong tree: Found right == left");
+ return -1;
+ }
+ count_l=test_rb_tree(element->left,element);
+ count_r=test_rb_tree(element->right,element);
+ if (count_l >= 0 && count_r >= 0)
+ {
+ if (count_l == count_r)
+ return count_l+(element->color == SEL_ARG::BLACK);
+ sql_print_error("Wrong tree: Incorrect black-count: %d - %d",
+ count_l,count_r);
+ }
+ return -1; // Error, no more warnings
+}
+
+
+/**
+ Count how many times SEL_ARG graph "root" refers to its part "key" via
+ transitive closure.
+
+ @param root An RB-Root node in a SEL_ARG graph.
+ @param key Another RB-Root node in that SEL_ARG graph.
+
+ The passed "root" node may refer to "key" node via root->next_key_part,
+ root->next->n
+
+ This function counts how many times the node "key" is referred (via
+ SEL_ARG::next_key_part) by
+ - intervals of RB-tree pointed by "root",
+ - intervals of RB-trees that are pointed by SEL_ARG::next_key_part from
+ intervals of RB-tree pointed by "root",
+ - and so on.
+
+ Here is an example (horizontal links represent next_key_part pointers,
+ vertical links - next/prev prev pointers):
+
+ +----+ $
+ |root|-----------------+
+ +----+ $ |
+ | $ |
+ | $ |
+ +----+ +---+ $ | +---+ Here the return value
+ | |- ... -| |---$-+--+->|key| will be 4.
+ +----+ +---+ $ | | +---+
+ | $ | |
+ ... $ | |
+ | $ | |
+ +----+ +---+ $ | |
+ | |---| |---------+ |
+ +----+ +---+ $ |
+ | | $ |
+ ... +---+ $ |
+ | |------------+
+ +---+ $
+ @return
+ Number of links to "key" from nodes reachable from "root".
+*/
+
+static ulong count_key_part_usage(SEL_ARG *root, SEL_ARG *key)
+{
+ ulong count= 0;
+ for (root=root->first(); root ; root=root->next)
+ {
+ if (root->next_key_part)
+ {
+ if (root->next_key_part == key)
+ count++;
+ if (root->next_key_part->part < key->part)
+ count+=count_key_part_usage(root->next_key_part,key);
+ }
+ }
+ return count;
+}
+
+
+/*
+ Check if SEL_ARG::use_count value is correct
+
+ SYNOPSIS
+ SEL_ARG::test_use_count()
+ root The root node of the SEL_ARG graph (an RB-tree root node that
+ has the least value of sel_arg->part in the entire graph, and
+ thus is the "origin" of the graph)
+
+ DESCRIPTION
+ Check if SEL_ARG::use_count value is correct. See the definition of
+ use_count for what is "correct".
+*/
+
+void SEL_ARG::test_use_count(SEL_ARG *root)
+{
+ uint e_count=0;
+
+ if (this->type != SEL_ARG::KEY_RANGE)
+ return;
+ for (SEL_ARG *pos=first(); pos ; pos=pos->next)
+ {
+ e_count++;
+ if (pos->next_key_part)
+ {
+ ulong count=count_key_part_usage(root,pos->next_key_part);
+ if (count > pos->next_key_part->use_count)
+ {
+ sql_print_information("Use_count: Wrong count for key at 0x%lx, %lu "
+ "should be %lu", (long unsigned int)pos,
+ pos->next_key_part->use_count, count);
+ return;
+ }
+ pos->next_key_part->test_use_count(root);
+ }
+ }
+ if (e_count != elements)
+ sql_print_warning("Wrong use count: %u (should be %u) for tree at 0x%lx",
+ e_count, elements, (long unsigned int) this);
+}
+#endif
+
+/*
+ Calculate cost and E(#rows) for a given index and intervals tree
+
+ SYNOPSIS
+ check_quick_select()
+ param Parameter from test_quick_select
+ idx Number of index to use in PARAM::key SEL_TREE::key
+ index_only TRUE - assume only index tuples will be accessed
+ FALSE - assume full table rows will be read
+ tree Transformed selection condition, tree->key[idx] holds
+ the intervals for the given index.
+ update_tbl_stats TRUE <=> update table->quick_* with information
+ about range scan we've evaluated.
+ mrr_flags INOUT MRR access flags
+ cost OUT Scan cost
+
+ NOTES
+ param->is_ror_scan is set to reflect if the key scan is a ROR (see
+ is_key_scan_ror function for more info)
+ param->table->quick_*, param->range_count (and maybe others) are
+ updated with data of given key scan, see quick_range_seq_next for details.
+
+ RETURN
+ Estimate # of records to be retrieved.
+ HA_POS_ERROR if estimate calculation failed due to table handler problems.
+*/
+
+static
+ha_rows check_quick_select(PARAM *param, uint idx, bool index_only,
+ SEL_ARG *tree, bool update_tbl_stats,
+ uint *mrr_flags, uint *bufsize, Cost_estimate *cost)
+{
+ SEL_ARG_RANGE_SEQ seq;
+ RANGE_SEQ_IF seq_if = {NULL, sel_arg_range_seq_init, sel_arg_range_seq_next, 0, 0};
+ handler *file= param->table->file;
+ ha_rows rows= HA_POS_ERROR;
+ uint keynr= param->real_keynr[idx];
+ DBUG_ENTER("check_quick_select");
+
+ /* Handle cases when we don't have a valid non-empty list of range */
+ if (!tree)
+ DBUG_RETURN(HA_POS_ERROR);
+ if (tree->type == SEL_ARG::IMPOSSIBLE)
+ DBUG_RETURN(0L);
+ if (tree->type != SEL_ARG::KEY_RANGE || tree->part != 0)
+ DBUG_RETURN(HA_POS_ERROR);
+
+ seq.keyno= idx;
+ seq.real_keyno= keynr;
+ seq.param= param;
+ seq.start= tree;
+
+ param->range_count=0;
+ param->max_key_part=0;
+
+ param->is_ror_scan= TRUE;
+ if (file->index_flags(keynr, 0, TRUE) & HA_KEY_SCAN_NOT_ROR)
+ param->is_ror_scan= FALSE;
+
+ *mrr_flags= param->force_default_mrr? HA_MRR_USE_DEFAULT_IMPL: 0;
+ /*
+ Pass HA_MRR_SORTED to see if MRR implementation can handle sorting.
+ */
+ *mrr_flags|= HA_MRR_NO_ASSOCIATION | HA_MRR_SORTED;
+
+ bool pk_is_clustered= file->primary_key_is_clustered();
+ if (index_only &&
+ (file->index_flags(keynr, param->max_key_part, 1) & HA_KEYREAD_ONLY) &&
+ !(file->index_flags(keynr, param->max_key_part, 1) & HA_CLUSTERED_INDEX))
+ *mrr_flags |= HA_MRR_INDEX_ONLY;
+
+ if (param->thd->lex->sql_command != SQLCOM_SELECT)
+ *mrr_flags |= HA_MRR_USE_DEFAULT_IMPL;
+
+ *bufsize= param->thd->variables.mrr_buff_size;
+ /*
+ Skip materialized derived table/view result table from MRR check as
+ they aren't contain any data yet.
+ */
+ if (param->table->pos_in_table_list->is_non_derived())
+ rows= file->multi_range_read_info_const(keynr, &seq_if, (void*)&seq, 0,
+ bufsize, mrr_flags, cost);
+ if (rows != HA_POS_ERROR)
+ {
+ param->quick_rows[keynr]= rows;
+ param->possible_keys.set_bit(keynr);
+ if (update_tbl_stats)
+ {
+ param->table->quick_keys.set_bit(keynr);
+ param->table->quick_key_parts[keynr]= param->max_key_part+1;
+ param->table->quick_n_ranges[keynr]= param->range_count;
+ param->table->quick_condition_rows=
+ MY_MIN(param->table->quick_condition_rows, rows);
+ param->table->quick_rows[keynr]= rows;
+ }
+ }
+ /* Figure out if the key scan is ROR (returns rows in ROWID order) or not */
+ enum ha_key_alg key_alg= param->table->key_info[seq.real_keyno].algorithm;
+ if ((key_alg != HA_KEY_ALG_BTREE) && (key_alg!= HA_KEY_ALG_UNDEF))
+ {
+ /*
+ All scans are non-ROR scans for those index types.
+ TODO: Don't have this logic here, make table engines return
+ appropriate flags instead.
+ */
+ param->is_ror_scan= FALSE;
+ }
+ else if (param->table->s->primary_key == keynr && pk_is_clustered)
+ {
+ /* Clustered PK scan is always a ROR scan (TODO: same as above) */
+ param->is_ror_scan= TRUE;
+ }
+ else if (param->range_count > 1)
+ {
+ /*
+ Scaning multiple key values in the index: the records are ROR
+ for each value, but not between values. E.g, "SELECT ... x IN
+ (1,3)" returns ROR order for all records with x=1, then ROR
+ order for records with x=3
+ */
+ param->is_ror_scan= FALSE;
+ }
+
+ DBUG_PRINT("exit", ("Records: %lu", (ulong) rows));
+ DBUG_RETURN(rows); //psergey-merge:todo: maintain first_null_comp.
+}
+
+
+/*
+ Check if key scan on given index with equality conditions on first n key
+ parts is a ROR scan.
+
+ SYNOPSIS
+ is_key_scan_ror()
+ param Parameter from test_quick_select
+ keynr Number of key in the table. The key must not be a clustered
+ primary key.
+ nparts Number of first key parts for which equality conditions
+ are present.
+
+ NOTES
+ ROR (Rowid Ordered Retrieval) key scan is a key scan that produces
+ ordered sequence of rowids (ha_xxx::cmp_ref is the comparison function)
+
+ This function is needed to handle a practically-important special case:
+ an index scan is a ROR scan if it is done using a condition in form
+
+ "key1_1=c_1 AND ... AND key1_n=c_n"
+
+ where the index is defined on (key1_1, ..., key1_N [,a_1, ..., a_n])
+
+ and the table has a clustered Primary Key defined as
+ PRIMARY KEY(a_1, ..., a_n, b1, ..., b_k)
+
+ i.e. the first key parts of it are identical to uncovered parts ot the
+ key being scanned. This function assumes that the index flags do not
+ include HA_KEY_SCAN_NOT_ROR flag (that is checked elsewhere).
+
+ Check (1) is made in quick_range_seq_next()
+
+ RETURN
+ TRUE The scan is ROR-scan
+ FALSE Otherwise
+*/
+
+static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts)
+{
+ KEY *table_key= param->table->key_info + keynr;
+ KEY_PART_INFO *key_part= table_key->key_part + nparts;
+ KEY_PART_INFO *key_part_end= (table_key->key_part +
+ table_key->user_defined_key_parts);
+ uint pk_number;
+
+ for (KEY_PART_INFO *kp= table_key->key_part; kp < key_part; kp++)
+ {
+ uint16 fieldnr= param->table->key_info[keynr].
+ key_part[kp - table_key->key_part].fieldnr - 1;
+ if (param->table->field[fieldnr]->key_length() != kp->length)
+ return FALSE;
+ }
+
+ /*
+ If there are equalities for all key parts, it is a ROR scan. If there are
+ equalities all keyparts and even some of key parts from "Extended Key"
+ index suffix, it is a ROR-scan, too.
+ */
+ if (key_part >= key_part_end)
+ return TRUE;
+
+ key_part= table_key->key_part + nparts;
+ pk_number= param->table->s->primary_key;
+ if (!param->table->file->primary_key_is_clustered() || pk_number == MAX_KEY)
+ return FALSE;
+
+ KEY_PART_INFO *pk_part= param->table->key_info[pk_number].key_part;
+ KEY_PART_INFO *pk_part_end= pk_part +
+ param->table->key_info[pk_number].user_defined_key_parts;
+ for (;(key_part!=key_part_end) && (pk_part != pk_part_end);
+ ++key_part, ++pk_part)
+ {
+ if ((key_part->field != pk_part->field) ||
+ (key_part->length != pk_part->length))
+ return FALSE;
+ }
+ return (key_part == key_part_end);
+}
+
+
+/*
+ Create a QUICK_RANGE_SELECT from given key and SEL_ARG tree for that key.
+
+ SYNOPSIS
+ get_quick_select()
+ param
+ idx Index of used key in param->key.
+ key_tree SEL_ARG tree for the used key
+ mrr_flags MRR parameter for quick select
+ mrr_buf_size MRR parameter for quick select
+ parent_alloc If not NULL, use it to allocate memory for
+ quick select data. Otherwise use quick->alloc.
+ NOTES
+ The caller must call QUICK_SELECT::init for returned quick select.
+
+ CAUTION! This function may change thd->mem_root to a MEM_ROOT which will be
+ deallocated when the returned quick select is deleted.
+
+ RETURN
+ NULL on error
+ otherwise created quick select
+*/
+
+QUICK_RANGE_SELECT *
+get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree, uint mrr_flags,
+ uint mrr_buf_size, MEM_ROOT *parent_alloc)
+{
+ QUICK_RANGE_SELECT *quick;
+ bool create_err= FALSE;
+ DBUG_ENTER("get_quick_select");
+
+ if (param->table->key_info[param->real_keynr[idx]].flags & HA_SPATIAL)
+ quick=new QUICK_RANGE_SELECT_GEOM(param->thd, param->table,
+ param->real_keynr[idx],
+ MY_TEST(parent_alloc),
+ parent_alloc, &create_err);
+ else
+ quick=new QUICK_RANGE_SELECT(param->thd, param->table,
+ param->real_keynr[idx],
+ MY_TEST(parent_alloc), NULL, &create_err);
+
+ if (quick)
+ {
+ if (create_err ||
+ get_quick_keys(param,quick,param->key[idx],key_tree,param->min_key,0,
+ param->max_key,0))
+ {
+ delete quick;
+ quick=0;
+ }
+ else
+ {
+ KEY *keyinfo= param->table->key_info+param->real_keynr[idx];
+ quick->mrr_flags= mrr_flags;
+ quick->mrr_buf_size= mrr_buf_size;
+ quick->key_parts=(KEY_PART*)
+ memdup_root(parent_alloc? parent_alloc : &quick->alloc,
+ (char*) param->key[idx],
+ sizeof(KEY_PART)*
+ param->table->actual_n_key_parts(keyinfo));
+ }
+ }
+ DBUG_RETURN(quick);
+}
+
+
+/*
+** Fix this to get all possible sub_ranges
+*/
+bool
+get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key,
+ SEL_ARG *key_tree, uchar *min_key,uint min_key_flag,
+ uchar *max_key, uint max_key_flag)
+{
+ QUICK_RANGE *range;
+ uint flag;
+ int min_part= key_tree->part-1, // # of keypart values in min_key buffer
+ max_part= key_tree->part-1; // # of keypart values in max_key buffer
+
+ if (key_tree->left != &null_element)
+ {
+ if (get_quick_keys(param,quick,key,key_tree->left,
+ min_key,min_key_flag, max_key, max_key_flag))
+ return 1;
+ }
+ uchar *tmp_min_key=min_key,*tmp_max_key=max_key;
+ min_part+= key_tree->store_min(key[key_tree->part].store_length,
+ &tmp_min_key,min_key_flag);
+ max_part+= key_tree->store_max(key[key_tree->part].store_length,
+ &tmp_max_key,max_key_flag);
+
+ if (key_tree->next_key_part &&
+ key_tree->next_key_part->type == SEL_ARG::KEY_RANGE &&
+ key_tree->next_key_part->part == key_tree->part+1)
+ { // const key as prefix
+ if ((tmp_min_key - min_key) == (tmp_max_key - max_key) &&
+ memcmp(min_key, max_key, (uint)(tmp_max_key - max_key))==0 &&
+ key_tree->min_flag==0 && key_tree->max_flag==0)
+ {
+ if (get_quick_keys(param,quick,key,key_tree->next_key_part,
+ tmp_min_key, min_key_flag | key_tree->min_flag,
+ tmp_max_key, max_key_flag | key_tree->max_flag))
+ return 1;
+ goto end; // Ugly, but efficient
+ }
+ {
+ uint tmp_min_flag=key_tree->min_flag,tmp_max_flag=key_tree->max_flag;
+ if (!tmp_min_flag)
+ min_part+= key_tree->next_key_part->store_min_key(key,
+ &tmp_min_key,
+ &tmp_min_flag,
+ MAX_KEY);
+ if (!tmp_max_flag)
+ max_part+= key_tree->next_key_part->store_max_key(key,
+ &tmp_max_key,
+ &tmp_max_flag,
+ MAX_KEY);
+ flag=tmp_min_flag | tmp_max_flag;
+ }
+ }
+ else
+ {
+ flag = (key_tree->min_flag & GEOM_FLAG) ?
+ key_tree->min_flag : key_tree->min_flag | key_tree->max_flag;
+ }
+
+ /*
+ Ensure that some part of min_key and max_key are used. If not,
+ regard this as no lower/upper range
+ */
+ if ((flag & GEOM_FLAG) == 0)
+ {
+ if (tmp_min_key != param->min_key)
+ flag&= ~NO_MIN_RANGE;
+ else
+ flag|= NO_MIN_RANGE;
+ if (tmp_max_key != param->max_key)
+ flag&= ~NO_MAX_RANGE;
+ else
+ flag|= NO_MAX_RANGE;
+ }
+ if (flag == 0)
+ {
+ uint length= (uint) (tmp_min_key - param->min_key);
+ if (length == (uint) (tmp_max_key - param->max_key) &&
+ !memcmp(param->min_key,param->max_key,length))
+ {
+ KEY *table_key=quick->head->key_info+quick->index;
+ flag=EQ_RANGE;
+ if ((table_key->flags & HA_NOSAME) &&
+ key_tree->part == table_key->user_defined_key_parts-1)
+ {
+ if ((table_key->flags & HA_NULL_PART_KEY) &&
+ null_part_in_key(key,
+ param->min_key,
+ (uint) (tmp_min_key - param->min_key)))
+ flag|= NULL_RANGE;
+ else
+ flag|= UNIQUE_RANGE;
+ }
+ }
+ }
+
+ /* Get range for retrieving rows in QUICK_SELECT::get_next */
+ if (!(range= new QUICK_RANGE(param->min_key,
+ (uint) (tmp_min_key - param->min_key),
+ min_part >=0 ? make_keypart_map(min_part) : 0,
+ param->max_key,
+ (uint) (tmp_max_key - param->max_key),
+ max_part >=0 ? make_keypart_map(max_part) : 0,
+ flag)))
+ return 1; // out of memory
+
+ set_if_bigger(quick->max_used_key_length, range->min_length);
+ set_if_bigger(quick->max_used_key_length, range->max_length);
+ set_if_bigger(quick->used_key_parts, (uint) key_tree->part+1);
+ if (insert_dynamic(&quick->ranges, (uchar*) &range))
+ return 1;
+
+ end:
+ if (key_tree->right != &null_element)
+ return get_quick_keys(param,quick,key,key_tree->right,
+ min_key,min_key_flag,
+ max_key,max_key_flag);
+ return 0;
+}
+
+/*
+ Return 1 if there is only one range and this uses the whole unique key
+*/
+
+bool QUICK_RANGE_SELECT::unique_key_range()
+{
+ if (ranges.elements == 1)
+ {
+ QUICK_RANGE *tmp= *((QUICK_RANGE**)ranges.buffer);
+ if ((tmp->flag & (EQ_RANGE | NULL_RANGE)) == EQ_RANGE)
+ {
+ KEY *key=head->key_info+index;
+ return (key->flags & HA_NOSAME) && key->key_length == tmp->min_length;
+ }
+ }
+ return 0;
+}
+
+
+
+/*
+ Return TRUE if any part of the key is NULL
+
+ SYNOPSIS
+ null_part_in_key()
+ key_part Array of key parts (index description)
+ key Key values tuple
+ length Length of key values tuple in bytes.
+
+ RETURN
+ TRUE The tuple has at least one "keypartX is NULL"
+ FALSE Otherwise
+*/
+
+static bool null_part_in_key(KEY_PART *key_part, const uchar *key, uint length)
+{
+ for (const uchar *end=key+length ;
+ key < end;
+ key+= key_part++->store_length)
+ {
+ if (key_part->null_bit && *key)
+ return 1;
+ }
+ return 0;
+}
+
+
+bool QUICK_SELECT_I::is_keys_used(const MY_BITMAP *fields)
+{
+ return is_key_used(head, index, fields);
+}
+
+bool QUICK_INDEX_SORT_SELECT::is_keys_used(const MY_BITMAP *fields)
+{
+ QUICK_RANGE_SELECT *quick;
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+ while ((quick= it++))
+ {
+ if (is_key_used(head, quick->index, fields))
+ return 1;
+ }
+ return 0;
+}
+
+bool QUICK_ROR_INTERSECT_SELECT::is_keys_used(const MY_BITMAP *fields)
+{
+ QUICK_SELECT_WITH_RECORD *qr;
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
+ while ((qr= it++))
+ {
+ if (is_key_used(head, qr->quick->index, fields))
+ return 1;
+ }
+ return 0;
+}
+
+bool QUICK_ROR_UNION_SELECT::is_keys_used(const MY_BITMAP *fields)
+{
+ QUICK_SELECT_I *quick;
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+ while ((quick= it++))
+ {
+ if (quick->is_keys_used(fields))
+ return 1;
+ }
+ return 0;
+}
+
+
+FT_SELECT *get_ft_select(THD *thd, TABLE *table, uint key)
+{
+ bool create_err= FALSE;
+ FT_SELECT *fts= new FT_SELECT(thd, table, key, &create_err);
+ if (create_err)
+ {
+ delete fts;
+ return NULL;
+ }
+ else
+ return fts;
+}
+
+/*
+ Create quick select from ref/ref_or_null scan.
+
+ SYNOPSIS
+ get_quick_select_for_ref()
+ thd Thread handle
+ table Table to access
+ ref ref[_or_null] scan parameters
+ records Estimate of number of records (needed only to construct
+ quick select)
+ NOTES
+ This allocates things in a new memory root, as this may be called many
+ times during a query.
+
+ RETURN
+ Quick select that retrieves the same rows as passed ref scan
+ NULL on error.
+*/
+
+QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
+ TABLE_REF *ref, ha_rows records)
+{
+ MEM_ROOT *old_root, *alloc;
+ QUICK_RANGE_SELECT *quick;
+ KEY *key_info = &table->key_info[ref->key];
+ KEY_PART *key_part;
+ QUICK_RANGE *range;
+ uint part;
+ bool create_err= FALSE;
+ Cost_estimate cost;
+ uint max_used_key_len;
+
+ old_root= thd->mem_root;
+ /* The following call may change thd->mem_root */
+ quick= new QUICK_RANGE_SELECT(thd, table, ref->key, 0, 0, &create_err);
+ /* save mem_root set by QUICK_RANGE_SELECT constructor */
+ alloc= thd->mem_root;
+ /*
+ return back default mem_root (thd->mem_root) changed by
+ QUICK_RANGE_SELECT constructor
+ */
+ thd->mem_root= old_root;
+
+ if (!quick || create_err)
+ return 0; /* no ranges found */
+ if (quick->init())
+ goto err;
+ quick->records= records;
+
+ if ((cp_buffer_from_ref(thd, table, ref) && thd->is_fatal_error) ||
+ !(range= new(alloc) QUICK_RANGE()))
+ goto err; // out of memory
+
+ range->min_key= range->max_key= ref->key_buff;
+ range->min_length= range->max_length= ref->key_length;
+ range->min_keypart_map= range->max_keypart_map=
+ make_prev_keypart_map(ref->key_parts);
+ range->flag= EQ_RANGE;
+
+ if (!(quick->key_parts=key_part=(KEY_PART *)
+ alloc_root(&quick->alloc,sizeof(KEY_PART)*ref->key_parts)))
+ goto err;
+
+ max_used_key_len=0;
+ for (part=0 ; part < ref->key_parts ;part++,key_part++)
+ {
+ key_part->part=part;
+ key_part->field= key_info->key_part[part].field;
+ key_part->length= key_info->key_part[part].length;
+ key_part->store_length= key_info->key_part[part].store_length;
+ key_part->null_bit= key_info->key_part[part].null_bit;
+ key_part->flag= (uint8) key_info->key_part[part].key_part_flag;
+
+ max_used_key_len +=key_info->key_part[part].store_length;
+ }
+
+ quick->max_used_key_length= max_used_key_len;
+
+ if (insert_dynamic(&quick->ranges,(uchar*)&range))
+ goto err;
+
+ /*
+ Add a NULL range if REF_OR_NULL optimization is used.
+ For example:
+ if we have "WHERE A=2 OR A IS NULL" we created the (A=2) range above
+ and have ref->null_ref_key set. Will create a new NULL range here.
+ */
+ if (ref->null_ref_key)
+ {
+ QUICK_RANGE *null_range;
+
+ *ref->null_ref_key= 1; // Set null byte then create a range
+ if (!(null_range= new (alloc)
+ QUICK_RANGE(ref->key_buff, ref->key_length,
+ make_prev_keypart_map(ref->key_parts),
+ ref->key_buff, ref->key_length,
+ make_prev_keypart_map(ref->key_parts), EQ_RANGE)))
+ goto err;
+ *ref->null_ref_key= 0; // Clear null byte
+ if (insert_dynamic(&quick->ranges,(uchar*)&null_range))
+ goto err;
+ }
+
+ /* Call multi_range_read_info() to get the MRR flags and buffer size */
+ quick->mrr_flags= HA_MRR_NO_ASSOCIATION |
+ (table->key_read ? HA_MRR_INDEX_ONLY : 0);
+ if (thd->lex->sql_command != SQLCOM_SELECT)
+ quick->mrr_flags |= HA_MRR_USE_DEFAULT_IMPL;
+
+ quick->mrr_buf_size= thd->variables.mrr_buff_size;
+ if (table->file->multi_range_read_info(quick->index, 1, (uint)records,
+ ~0,
+ &quick->mrr_buf_size,
+ &quick->mrr_flags, &cost))
+ goto err;
+
+ return quick;
+err:
+ delete quick;
+ return 0;
+}
+
+
+/*
+ Perform key scans for all used indexes (except CPK), get rowids and merge
+ them into an ordered non-recurrent sequence of rowids.
+
+ The merge/duplicate removal is performed using Unique class. We put all
+ rowids into Unique, get the sorted sequence and destroy the Unique.
+
+ If table has a clustered primary key that covers all rows (TRUE for bdb
+ and innodb currently) and one of the index_merge scans is a scan on PK,
+ then rows that will be retrieved by PK scan are not put into Unique and
+ primary key scan is not performed here, it is performed later separately.
+
+ RETURN
+ 0 OK
+ other error
+*/
+
+int read_keys_and_merge_scans(THD *thd,
+ TABLE *head,
+ List<QUICK_RANGE_SELECT> quick_selects,
+ QUICK_RANGE_SELECT *pk_quick_select,
+ READ_RECORD *read_record,
+ bool intersection,
+ key_map *filtered_scans,
+ Unique **unique_ptr)
+{
+ List_iterator_fast<QUICK_RANGE_SELECT> cur_quick_it(quick_selects);
+ QUICK_RANGE_SELECT* cur_quick;
+ int result;
+ Unique *unique= *unique_ptr;
+ handler *file= head->file;
+ bool with_cpk_filter= pk_quick_select != NULL;
+ bool enabled_keyread= 0;
+ DBUG_ENTER("read_keys_and_merge");
+
+ /* We're going to just read rowids. */
+ if (!head->key_read)
+ {
+ enabled_keyread= 1;
+ head->enable_keyread();
+ }
+ head->prepare_for_position();
+
+ cur_quick_it.rewind();
+ cur_quick= cur_quick_it++;
+ bool first_quick= TRUE;
+ DBUG_ASSERT(cur_quick != 0);
+
+ /*
+ We reuse the same instance of handler so we need to call both init and
+ reset here.
+ */
+ if (cur_quick->init() || cur_quick->reset())
+ goto err;
+
+ if (unique == NULL)
+ {
+ DBUG_EXECUTE_IF("index_merge_may_not_create_a_Unique", DBUG_ABORT(); );
+ DBUG_EXECUTE_IF("only_one_Unique_may_be_created",
+ DBUG_SET("+d,index_merge_may_not_create_a_Unique"); );
+
+ unique= new Unique(refpos_order_cmp, (void *)file,
+ file->ref_length,
+ thd->variables.sortbuff_size,
+ intersection ? quick_selects.elements : 0);
+ if (!unique)
+ goto err;
+ *unique_ptr= unique;
+ }
+ else
+ {
+ unique->reset();
+ filesort_free_buffers(head, false);
+ }
+
+ DBUG_ASSERT(file->ref_length == unique->get_size());
+ DBUG_ASSERT(thd->variables.sortbuff_size == unique->get_max_in_memory_size());
+
+ for (;;)
+ {
+ while ((result= cur_quick->get_next()) == HA_ERR_END_OF_FILE)
+ {
+ if (intersection)
+ with_cpk_filter= filtered_scans->is_set(cur_quick->index);
+ if (first_quick)
+ {
+ first_quick= FALSE;
+ if (intersection && unique->is_in_memory())
+ unique->close_for_expansion();
+ }
+ cur_quick->range_end();
+ cur_quick= cur_quick_it++;
+ if (!cur_quick)
+ break;
+
+ if (cur_quick->file->inited != handler::NONE)
+ cur_quick->file->ha_index_end();
+ if (cur_quick->init() || cur_quick->reset())
+ goto err;
+ }
+
+ if (result)
+ {
+ if (result != HA_ERR_END_OF_FILE)
+ {
+ cur_quick->range_end();
+ goto err;
+ }
+ break;
+ }
+
+ if (thd->killed)
+ goto err;
+
+ if (with_cpk_filter &&
+ pk_quick_select->row_in_ranges() != intersection )
+ continue;
+
+ cur_quick->file->position(cur_quick->record);
+ if (unique->unique_add((char*)cur_quick->file->ref))
+ goto err;
+ }
+
+ /*
+ Ok all rowids are in the Unique now. The next call will initialize
+ head->sort structure so it can be used to iterate through the rowids
+ sequence.
+ */
+ result= unique->get(head);
+ /*
+ index merge currently doesn't support "using index" at all
+ */
+ if (enabled_keyread)
+ head->disable_keyread();
+ if (init_read_record(read_record, thd, head, (SQL_SELECT*) 0, 1 , 1, TRUE))
+ result= 1;
+ DBUG_RETURN(result);
+
+err:
+ if (enabled_keyread)
+ head->disable_keyread();
+ DBUG_RETURN(1);
+}
+
+
+int QUICK_INDEX_MERGE_SELECT::read_keys_and_merge()
+
+{
+ int result;
+ DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::read_keys_and_merge");
+ result= read_keys_and_merge_scans(thd, head, quick_selects, pk_quick_select,
+ &read_record, FALSE, NULL, &unique);
+ doing_pk_scan= FALSE;
+ DBUG_RETURN(result);
+}
+
+/*
+ Get next row for index_merge.
+ NOTES
+ The rows are read from
+ 1. rowids stored in Unique.
+ 2. QUICK_RANGE_SELECT with clustered primary key (if any).
+ The sets of rows retrieved in 1) and 2) are guaranteed to be disjoint.
+*/
+
+int QUICK_INDEX_MERGE_SELECT::get_next()
+{
+ int result;
+ DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::get_next");
+
+ if (doing_pk_scan)
+ DBUG_RETURN(pk_quick_select->get_next());
+
+ if ((result= read_record.read_record(&read_record)) == -1)
+ {
+ result= HA_ERR_END_OF_FILE;
+ end_read_record(&read_record);
+ free_io_cache(head);
+ /* All rows from Unique have been retrieved, do a clustered PK scan */
+ if (pk_quick_select)
+ {
+ doing_pk_scan= TRUE;
+ if ((result= pk_quick_select->init()) ||
+ (result= pk_quick_select->reset()))
+ DBUG_RETURN(result);
+ DBUG_RETURN(pk_quick_select->get_next());
+ }
+ }
+
+ DBUG_RETURN(result);
+}
+
+int QUICK_INDEX_INTERSECT_SELECT::read_keys_and_merge()
+
+{
+ int result;
+ DBUG_ENTER("QUICK_INDEX_INTERSECT_SELECT::read_keys_and_merge");
+ result= read_keys_and_merge_scans(thd, head, quick_selects, pk_quick_select,
+ &read_record, TRUE, &filtered_scans,
+ &unique);
+ DBUG_RETURN(result);
+}
+
+int QUICK_INDEX_INTERSECT_SELECT::get_next()
+{
+ int result;
+ DBUG_ENTER("QUICK_INDEX_INTERSECT_SELECT::get_next");
+
+ if ((result= read_record.read_record(&read_record)) == -1)
+ {
+ result= HA_ERR_END_OF_FILE;
+ end_read_record(&read_record);
+ free_io_cache(head);
+ }
+
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Retrieve next record.
+ SYNOPSIS
+ QUICK_ROR_INTERSECT_SELECT::get_next()
+
+ NOTES
+ Invariant on enter/exit: all intersected selects have retrieved all index
+ records with rowid <= some_rowid_val and no intersected select has
+ retrieved any index records with rowid > some_rowid_val.
+ We start fresh and loop until we have retrieved the same rowid in each of
+ the key scans or we got an error.
+
+ If a Clustered PK scan is present, it is used only to check if row
+ satisfies its condition (and never used for row retrieval).
+
+ Locking: to ensure that exclusive locks are only set on records that
+ are included in the final result we must release the lock
+ on all rows we read but do not include in the final result. This
+ must be done on each index that reads the record and the lock
+ must be released using the same handler (the same quick object) as
+ used when reading the record.
+
+ RETURN
+ 0 - Ok
+ other - Error code if any error occurred.
+*/
+
+int QUICK_ROR_INTERSECT_SELECT::get_next()
+{
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> quick_it(quick_selects);
+ QUICK_SELECT_WITH_RECORD *qr;
+ QUICK_RANGE_SELECT* quick;
+
+ /* quick that reads the given rowid first. This is needed in order
+ to be able to unlock the row using the same handler object that locked
+ it */
+ QUICK_RANGE_SELECT* quick_with_last_rowid;
+
+ int error, cmp;
+ uint last_rowid_count=0;
+ DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::get_next");
+
+ do
+ {
+ /* Get a rowid for first quick and save it as a 'candidate' */
+ qr= quick_it++;
+ quick= qr->quick;
+ error= quick->get_next();
+ if (cpk_quick)
+ {
+ while (!error && !cpk_quick->row_in_ranges())
+ {
+ quick->file->unlock_row(); /* row not in range; unlock */
+ error= quick->get_next();
+ }
+ }
+ if (error)
+ DBUG_RETURN(error);
+
+ /* Save the read key tuple */
+ key_copy(qr->key_tuple, record, head->key_info + quick->index,
+ quick->max_used_key_length);
+
+ quick->file->position(quick->record);
+ memcpy(last_rowid, quick->file->ref, head->file->ref_length);
+ last_rowid_count= 1;
+ quick_with_last_rowid= quick;
+
+ while (last_rowid_count < quick_selects.elements)
+ {
+ if (!(qr= quick_it++))
+ {
+ quick_it.rewind();
+ qr= quick_it++;
+ }
+ quick= qr->quick;
+
+ do
+ {
+ DBUG_EXECUTE_IF("innodb_quick_report_deadlock",
+ DBUG_SET("+d,innodb_report_deadlock"););
+ if ((error= quick->get_next()))
+ {
+ /* On certain errors like deadlock, trx might be rolled back.*/
+ if (!current_thd->transaction_rollback_request)
+ quick_with_last_rowid->file->unlock_row();
+ DBUG_RETURN(error);
+ }
+ quick->file->position(quick->record);
+ cmp= head->file->cmp_ref(quick->file->ref, last_rowid);
+ if (cmp < 0)
+ {
+ /* This row is being skipped. Release lock on it. */
+ quick->file->unlock_row();
+ }
+ } while (cmp < 0);
+
+ key_copy(qr->key_tuple, record, head->key_info + quick->index,
+ quick->max_used_key_length);
+
+ /* Ok, current select 'caught up' and returned ref >= cur_ref */
+ if (cmp > 0)
+ {
+ /* Found a row with ref > cur_ref. Make it a new 'candidate' */
+ if (cpk_quick)
+ {
+ while (!cpk_quick->row_in_ranges())
+ {
+ quick->file->unlock_row(); /* row not in range; unlock */
+ if ((error= quick->get_next()))
+ {
+ /* On certain errors like deadlock, trx might be rolled back.*/
+ if (!current_thd->transaction_rollback_request)
+ quick_with_last_rowid->file->unlock_row();
+ DBUG_RETURN(error);
+ }
+ }
+ quick->file->position(quick->record);
+ }
+ memcpy(last_rowid, quick->file->ref, head->file->ref_length);
+ quick_with_last_rowid->file->unlock_row();
+ last_rowid_count= 1;
+ quick_with_last_rowid= quick;
+
+ //save the fields here
+ key_copy(qr->key_tuple, record, head->key_info + quick->index,
+ quick->max_used_key_length);
+ }
+ else
+ {
+ /* current 'candidate' row confirmed by this select */
+ last_rowid_count++;
+ }
+ }
+
+ /* We get here if we got the same row ref in all scans. */
+ if (need_to_fetch_row)
+ error= head->file->ha_rnd_pos(head->record[0], last_rowid);
+ } while (error == HA_ERR_RECORD_DELETED);
+
+ if (!need_to_fetch_row)
+ {
+ /* Restore the columns we've read/saved with other quick selects */
+ quick_it.rewind();
+ while ((qr= quick_it++))
+ {
+ if (qr->quick != quick)
+ {
+ key_restore(record, qr->key_tuple, head->key_info + qr->quick->index,
+ qr->quick->max_used_key_length);
+ }
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Retrieve next record.
+ SYNOPSIS
+ QUICK_ROR_UNION_SELECT::get_next()
+
+ NOTES
+ Enter/exit invariant:
+ For each quick select in the queue a {key,rowid} tuple has been
+ retrieved but the corresponding row hasn't been passed to output.
+
+ RETURN
+ 0 - Ok
+ other - Error code if any error occurred.
+*/
+
+int QUICK_ROR_UNION_SELECT::get_next()
+{
+ int error, dup_row;
+ QUICK_SELECT_I *quick;
+ uchar *tmp;
+ DBUG_ENTER("QUICK_ROR_UNION_SELECT::get_next");
+
+ do
+ {
+ do
+ {
+ if (!queue.elements)
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ /* Ok, we have a queue with >= 1 scans */
+
+ quick= (QUICK_SELECT_I*)queue_top(&queue);
+ memcpy(cur_rowid, quick->last_rowid, rowid_length);
+
+ /* put into queue rowid from the same stream as top element */
+ if ((error= quick->get_next()))
+ {
+ if (error != HA_ERR_END_OF_FILE)
+ DBUG_RETURN(error);
+ queue_remove_top(&queue);
+ }
+ else
+ {
+ quick->save_last_pos();
+ queue_replace_top(&queue);
+ }
+
+ if (!have_prev_rowid)
+ {
+ /* No rows have been returned yet */
+ dup_row= FALSE;
+ have_prev_rowid= TRUE;
+ }
+ else
+ dup_row= !head->file->cmp_ref(cur_rowid, prev_rowid);
+ } while (dup_row);
+
+ tmp= cur_rowid;
+ cur_rowid= prev_rowid;
+ prev_rowid= tmp;
+
+ error= head->file->ha_rnd_pos(quick->record, prev_rowid);
+ } while (error == HA_ERR_RECORD_DELETED);
+ DBUG_RETURN(error);
+}
+
+
+int QUICK_RANGE_SELECT::reset()
+{
+ uint buf_size;
+ uchar *mrange_buff;
+ int error;
+ HANDLER_BUFFER empty_buf;
+ MY_BITMAP * const save_read_set= head->read_set;
+ MY_BITMAP * const save_write_set= head->write_set;
+ DBUG_ENTER("QUICK_RANGE_SELECT::reset");
+ last_range= NULL;
+ cur_range= (QUICK_RANGE**) ranges.buffer;
+ RANGE_SEQ_IF seq_funcs= {NULL, quick_range_seq_init, quick_range_seq_next, 0, 0};
+
+ if (file->inited == handler::RND)
+ {
+ /* Handler could be left in this state by MRR */
+ if ((error= file->ha_rnd_end()))
+ DBUG_RETURN(error);
+ }
+
+ if (in_ror_merged_scan)
+ head->column_bitmaps_set_no_signal(&column_bitmap, &column_bitmap);
+
+ if (file->inited == handler::NONE)
+ {
+ DBUG_EXECUTE_IF("bug14365043_2",
+ DBUG_SET("+d,ha_index_init_fail"););
+ if ((error= file->ha_index_init(index,1)))
+ {
+ file->print_error(error, MYF(0));
+ goto err;
+ }
+ }
+
+ /* Allocate buffer if we need one but haven't allocated it yet */
+ if (mrr_buf_size && !mrr_buf_desc)
+ {
+ buf_size= mrr_buf_size;
+ while (buf_size && !my_multi_malloc(MYF(MY_WME),
+ &mrr_buf_desc, sizeof(*mrr_buf_desc),
+ &mrange_buff, buf_size,
+ NullS))
+ {
+ /* Try to shrink the buffers until both are 0. */
+ buf_size/= 2;
+ }
+ if (!mrr_buf_desc)
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+ /* Initialize the handler buffer. */
+ mrr_buf_desc->buffer= mrange_buff;
+ mrr_buf_desc->buffer_end= mrange_buff + buf_size;
+ mrr_buf_desc->end_of_used_area= mrange_buff;
+#ifdef HAVE_valgrind
+ /*
+ We need this until ndb will use the buffer efficiently
+ (Now ndb stores complete row in here, instead of only the used fields
+ which gives us valgrind warnings in compare_record[])
+ */
+ bzero((char*) mrange_buff, buf_size);
+#endif
+ }
+
+ if (!mrr_buf_desc)
+ empty_buf.buffer= empty_buf.buffer_end= empty_buf.end_of_used_area= NULL;
+
+ error= file->multi_range_read_init(&seq_funcs, (void*)this, ranges.elements,
+ mrr_flags, mrr_buf_desc? mrr_buf_desc:
+ &empty_buf);
+err:
+ /* Restore bitmaps set on entry */
+ if (in_ror_merged_scan)
+ head->column_bitmaps_set_no_signal(save_read_set, save_write_set);
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Get next possible record using quick-struct.
+
+ SYNOPSIS
+ QUICK_RANGE_SELECT::get_next()
+
+ NOTES
+ Record is read into table->record[0]
+
+ RETURN
+ 0 Found row
+ HA_ERR_END_OF_FILE No (more) rows in range
+ # Error code
+*/
+
+int QUICK_RANGE_SELECT::get_next()
+{
+ range_id_t dummy;
+ MY_BITMAP * const save_read_set= head->read_set;
+ MY_BITMAP * const save_write_set= head->write_set;
+
+ DBUG_ENTER("QUICK_RANGE_SELECT::get_next");
+ if (in_ror_merged_scan)
+ {
+ /*
+ We don't need to signal the bitmap change as the bitmap is always the
+ same for this head->file
+ */
+ head->column_bitmaps_set_no_signal(&column_bitmap, &column_bitmap);
+ }
+
+ int result= file->multi_range_read_next(&dummy);
+
+ if (in_ror_merged_scan)
+ {
+ /* Restore bitmaps set on entry */
+ head->column_bitmaps_set_no_signal(save_read_set, save_write_set);
+ }
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Get the next record with a different prefix.
+
+ @param prefix_length length of cur_prefix
+ @param group_key_parts The number of key parts in the group prefix
+ @param cur_prefix prefix of a key to be searched for
+
+ Each subsequent call to the method retrieves the first record that has a
+ prefix with length prefix_length and which is different from cur_prefix,
+ such that the record with the new prefix is within the ranges described by
+ this->ranges. The record found is stored into the buffer pointed by
+ this->record. The method is useful for GROUP-BY queries with range
+ conditions to discover the prefix of the next group that satisfies the range
+ conditions.
+
+ @todo
+
+ This method is a modified copy of QUICK_RANGE_SELECT::get_next(), so both
+ methods should be unified into a more general one to reduce code
+ duplication.
+
+ @retval 0 on success
+ @retval HA_ERR_END_OF_FILE if returned all keys
+ @retval other if some error occurred
+*/
+
+int QUICK_RANGE_SELECT::get_next_prefix(uint prefix_length,
+ uint group_key_parts,
+ uchar *cur_prefix)
+{
+ DBUG_ENTER("QUICK_RANGE_SELECT::get_next_prefix");
+ const key_part_map keypart_map= make_prev_keypart_map(group_key_parts);
+
+ for (;;)
+ {
+ int result;
+ if (last_range)
+ {
+ /* Read the next record in the same range with prefix after cur_prefix. */
+ DBUG_ASSERT(cur_prefix != NULL);
+ result= file->ha_index_read_map(record, cur_prefix, keypart_map,
+ HA_READ_AFTER_KEY);
+ if (result || last_range->max_keypart_map == 0)
+ DBUG_RETURN(result);
+
+ key_range previous_endpoint;
+ last_range->make_max_endpoint(&previous_endpoint, prefix_length, keypart_map);
+ if (file->compare_key(&previous_endpoint) <= 0)
+ DBUG_RETURN(0);
+ }
+
+ uint count= ranges.elements - (cur_range - (QUICK_RANGE**) ranges.buffer);
+ if (count == 0)
+ {
+ /* Ranges have already been used up before. None is left for read. */
+ last_range= 0;
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ last_range= *(cur_range++);
+
+ key_range start_key, end_key;
+ last_range->make_min_endpoint(&start_key, prefix_length, keypart_map);
+ last_range->make_max_endpoint(&end_key, prefix_length, keypart_map);
+
+ result= file->read_range_first(last_range->min_keypart_map ? &start_key : 0,
+ last_range->max_keypart_map ? &end_key : 0,
+ MY_TEST(last_range->flag & EQ_RANGE),
+ TRUE);
+ if (last_range->flag == (UNIQUE_RANGE | EQ_RANGE))
+ last_range= 0; // Stop searching
+
+ if (result != HA_ERR_END_OF_FILE)
+ DBUG_RETURN(result);
+ last_range= 0; // No matching rows; go to next range
+ }
+}
+
+
+/* Get next for geometrical indexes */
+
+int QUICK_RANGE_SELECT_GEOM::get_next()
+{
+ DBUG_ENTER("QUICK_RANGE_SELECT_GEOM::get_next");
+
+ for (;;)
+ {
+ int result;
+ if (last_range)
+ {
+ // Already read through key
+ result= file->ha_index_next_same(record, last_range->min_key,
+ last_range->min_length);
+ if (result != HA_ERR_END_OF_FILE)
+ DBUG_RETURN(result);
+ }
+
+ uint count= ranges.elements - (cur_range - (QUICK_RANGE**) ranges.buffer);
+ if (count == 0)
+ {
+ /* Ranges have already been used up before. None is left for read. */
+ last_range= 0;
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+ last_range= *(cur_range++);
+
+ result= file->ha_index_read_map(record, last_range->min_key,
+ last_range->min_keypart_map,
+ (ha_rkey_function)(last_range->flag ^
+ GEOM_FLAG));
+ if (result != HA_ERR_KEY_NOT_FOUND && result != HA_ERR_END_OF_FILE)
+ DBUG_RETURN(result);
+ last_range= 0; // Not found, to next range
+ }
+}
+
+
+/*
+ Check if current row will be retrieved by this QUICK_RANGE_SELECT
+
+ NOTES
+ It is assumed that currently a scan is being done on another index
+ which reads all necessary parts of the index that is scanned by this
+ quick select.
+ The implementation does a binary search on sorted array of disjoint
+ ranges, without taking size of range into account.
+
+ This function is used to filter out clustered PK scan rows in
+ index_merge quick select.
+
+ RETURN
+ TRUE if current row will be retrieved by this quick select
+ FALSE if not
+*/
+
+bool QUICK_RANGE_SELECT::row_in_ranges()
+{
+ QUICK_RANGE *res;
+ uint min= 0;
+ uint max= ranges.elements - 1;
+ uint mid= (max + min)/2;
+
+ while (min != max)
+ {
+ if (cmp_next(*(QUICK_RANGE**)dynamic_array_ptr(&ranges, mid)))
+ {
+ /* current row value > mid->max */
+ min= mid + 1;
+ }
+ else
+ max= mid;
+ mid= (min + max) / 2;
+ }
+ res= *(QUICK_RANGE**)dynamic_array_ptr(&ranges, mid);
+ return (!cmp_next(res) && !cmp_prev(res));
+}
+
+/*
+ This is a hack: we inherit from QUICK_RANGE_SELECT so that we can use the
+ get_next() interface, but we have to hold a pointer to the original
+ QUICK_RANGE_SELECT because its data are used all over the place. What
+ should be done is to factor out the data that is needed into a base
+ class (QUICK_SELECT), and then have two subclasses (_ASC and _DESC)
+ which handle the ranges and implement the get_next() function. But
+ for now, this seems to work right at least.
+ */
+
+QUICK_SELECT_DESC::QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q,
+ uint used_key_parts_arg)
+ :QUICK_RANGE_SELECT(*q), rev_it(rev_ranges),
+ used_key_parts (used_key_parts_arg)
+{
+ QUICK_RANGE *r;
+ /*
+ Use default MRR implementation for reverse scans. No table engine
+ currently can do an MRR scan with output in reverse index order.
+ */
+ mrr_buf_desc= NULL;
+ mrr_flags |= HA_MRR_USE_DEFAULT_IMPL;
+ mrr_buf_size= 0;
+
+ QUICK_RANGE **pr= (QUICK_RANGE**)ranges.buffer;
+ QUICK_RANGE **end_range= pr + ranges.elements;
+ for (; pr!=end_range; pr++)
+ rev_ranges.push_front(*pr);
+
+ /* Remove EQ_RANGE flag for keys that are not using the full key */
+ for (r = rev_it++; r; r = rev_it++)
+ {
+ if ((r->flag & EQ_RANGE) &&
+ head->key_info[index].key_length != r->max_length)
+ r->flag&= ~EQ_RANGE;
+ }
+ rev_it.rewind();
+ q->dont_free=1; // Don't free shared mem
+}
+
+
+int QUICK_SELECT_DESC::get_next()
+{
+ DBUG_ENTER("QUICK_SELECT_DESC::get_next");
+
+ /* The max key is handled as follows:
+ * - if there is NO_MAX_RANGE, start at the end and move backwards
+ * - if it is an EQ_RANGE, which means that max key covers the entire
+ * key, go directly to the key and read through it (sorting backwards is
+ * same as sorting forwards)
+ * - if it is NEAR_MAX, go to the key or next, step back once, and
+ * move backwards
+ * - otherwise (not NEAR_MAX == include the key), go after the key,
+ * step back once, and move backwards
+ */
+
+ for (;;)
+ {
+ int result;
+ if (last_range)
+ { // Already read through key
+ result = ((last_range->flag & EQ_RANGE &&
+ used_key_parts <= head->key_info[index].user_defined_key_parts) ?
+ file->ha_index_next_same(record, last_range->min_key,
+ last_range->min_length) :
+ file->ha_index_prev(record));
+ if (!result)
+ {
+ if (cmp_prev(*rev_it.ref()) == 0)
+ DBUG_RETURN(0);
+ }
+ else if (result != HA_ERR_END_OF_FILE)
+ DBUG_RETURN(result);
+ }
+
+ if (!(last_range= rev_it++))
+ DBUG_RETURN(HA_ERR_END_OF_FILE); // All ranges used
+
+ key_range start_key;
+ start_key.key= (const uchar*) last_range->min_key;
+ start_key.length= last_range->min_length;
+ start_key.flag= ((last_range->flag & NEAR_MIN) ? HA_READ_AFTER_KEY :
+ (last_range->flag & EQ_RANGE) ?
+ HA_READ_KEY_EXACT : HA_READ_KEY_OR_NEXT);
+ start_key.keypart_map= last_range->min_keypart_map;
+ key_range end_key;
+ end_key.key= (const uchar*) last_range->max_key;
+ end_key.length= last_range->max_length;
+ end_key.flag= (last_range->flag & NEAR_MAX ? HA_READ_BEFORE_KEY :
+ HA_READ_AFTER_KEY);
+ end_key.keypart_map= last_range->max_keypart_map;
+ result= file->prepare_range_scan((last_range->flag & NO_MIN_RANGE) ? NULL : &start_key,
+ (last_range->flag & NO_MAX_RANGE) ? NULL : &end_key);
+ if (result)
+ {
+ DBUG_RETURN(result);
+ }
+
+ if (last_range->flag & NO_MAX_RANGE) // Read last record
+ {
+ int local_error;
+ if ((local_error= file->ha_index_last(record)))
+ DBUG_RETURN(local_error); // Empty table
+ if (cmp_prev(last_range) == 0)
+ DBUG_RETURN(0);
+ last_range= 0; // No match; go to next range
+ continue;
+ }
+
+ if (last_range->flag & EQ_RANGE &&
+ used_key_parts <= head->key_info[index].user_defined_key_parts)
+
+ {
+ result= file->ha_index_read_map(record, last_range->max_key,
+ last_range->max_keypart_map,
+ HA_READ_KEY_EXACT);
+ }
+ else
+ {
+ DBUG_ASSERT(last_range->flag & NEAR_MAX ||
+ (last_range->flag & EQ_RANGE &&
+ used_key_parts > head->key_info[index].user_defined_key_parts) ||
+ range_reads_after_key(last_range));
+ result= file->ha_index_read_map(record, last_range->max_key,
+ last_range->max_keypart_map,
+ ((last_range->flag & NEAR_MAX) ?
+ HA_READ_BEFORE_KEY :
+ HA_READ_PREFIX_LAST_OR_PREV));
+ }
+ if (result)
+ {
+ if (result != HA_ERR_KEY_NOT_FOUND && result != HA_ERR_END_OF_FILE)
+ DBUG_RETURN(result);
+ last_range= 0; // Not found, to next range
+ continue;
+ }
+ if (cmp_prev(last_range) == 0)
+ {
+ if (last_range->flag == (UNIQUE_RANGE | EQ_RANGE))
+ last_range= 0; // Stop searching
+ DBUG_RETURN(0); // Found key is in range
+ }
+ last_range= 0; // To next range
+ }
+}
+
+
+/**
+ Create a compatible quick select with the result ordered in an opposite way
+
+ @param used_key_parts_arg Number of used key parts
+
+ @retval NULL in case of errors (OOM etc)
+ @retval pointer to a newly created QUICK_SELECT_DESC if success
+*/
+
+QUICK_SELECT_I *QUICK_RANGE_SELECT::make_reverse(uint used_key_parts_arg)
+{
+ QUICK_SELECT_DESC *new_quick= new QUICK_SELECT_DESC(this, used_key_parts_arg);
+ if (new_quick == NULL)
+ {
+ delete new_quick;
+ return NULL;
+ }
+ return new_quick;
+}
+
+
+/*
+ Compare if found key is over max-value
+ Returns 0 if key <= range->max_key
+ TODO: Figure out why can't this function be as simple as cmp_prev().
+*/
+
+int QUICK_RANGE_SELECT::cmp_next(QUICK_RANGE *range_arg)
+{
+ if (range_arg->flag & NO_MAX_RANGE)
+ return 0; /* key can't be to large */
+
+ KEY_PART *key_part=key_parts;
+ uint store_length;
+
+ for (uchar *key=range_arg->max_key, *end=key+range_arg->max_length;
+ key < end;
+ key+= store_length, key_part++)
+ {
+ int cmp;
+ store_length= key_part->store_length;
+ if (key_part->null_bit)
+ {
+ if (*key)
+ {
+ if (!key_part->field->is_null())
+ return 1;
+ continue;
+ }
+ else if (key_part->field->is_null())
+ return 0;
+ key++; // Skip null byte
+ store_length--;
+ }
+ if ((cmp=key_part->field->key_cmp(key, key_part->length)) < 0)
+ return 0;
+ if (cmp > 0)
+ return 1;
+ }
+ return (range_arg->flag & NEAR_MAX) ? 1 : 0; // Exact match
+}
+
+
+/*
+ Returns 0 if found key is inside range (found key >= range->min_key).
+*/
+
+int QUICK_RANGE_SELECT::cmp_prev(QUICK_RANGE *range_arg)
+{
+ int cmp;
+ if (range_arg->flag & NO_MIN_RANGE)
+ return 0; /* key can't be to small */
+
+ cmp= key_cmp(key_part_info, range_arg->min_key,
+ range_arg->min_length);
+ if (cmp > 0 || (cmp == 0 && !(range_arg->flag & NEAR_MIN)))
+ return 0;
+ return 1; // outside of range
+}
+
+
+/*
+ * TRUE if this range will require using HA_READ_AFTER_KEY
+ See comment in get_next() about this
+ */
+
+bool QUICK_SELECT_DESC::range_reads_after_key(QUICK_RANGE *range_arg)
+{
+ return ((range_arg->flag & (NO_MAX_RANGE | NEAR_MAX)) ||
+ !(range_arg->flag & EQ_RANGE) ||
+ head->key_info[index].key_length != range_arg->max_length) ? 1 : 0;
+}
+
+
+void QUICK_SELECT_I::add_key_name(String *str, bool *first)
+{
+ KEY *key_info= head->key_info + index;
+
+ if (*first)
+ *first= FALSE;
+ else
+ str->append(',');
+ str->append(key_info->name);
+}
+
+
+Explain_quick_select* QUICK_RANGE_SELECT::get_explain(MEM_ROOT *alloc)
+{
+ Explain_quick_select *res;
+ if ((res= new (alloc) Explain_quick_select(QS_TYPE_RANGE)))
+ res->range.set(alloc, head->key_info[index].name, max_used_key_length);
+ return res;
+}
+
+
+Explain_quick_select* QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *alloc)
+{
+ Explain_quick_select *res;
+ if ((res= new (alloc) Explain_quick_select(QS_TYPE_GROUP_MIN_MAX)))
+ res->range.set(alloc, head->key_info[index].name, max_used_key_length);
+ return res;
+}
+
+
+Explain_quick_select* QUICK_INDEX_SORT_SELECT::get_explain(MEM_ROOT *alloc)
+{
+ Explain_quick_select *res;
+ if (!(res= new (alloc) Explain_quick_select(get_type())))
+ return NULL;
+
+ QUICK_RANGE_SELECT *quick;
+ Explain_quick_select *child_explain;
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+ while ((quick= it++))
+ {
+ if ((child_explain= quick->get_explain(alloc)))
+ res->children.push_back(child_explain);
+ else
+ return NULL;
+ }
+
+ if (pk_quick_select)
+ {
+ if ((child_explain= pk_quick_select->get_explain(alloc)))
+ res->children.push_back(child_explain);
+ else
+ return NULL;
+ }
+ return res;
+}
+
+
+/*
+ Same as QUICK_INDEX_SORT_SELECT::get_explain(), but primary key is printed
+ first
+*/
+
+Explain_quick_select* QUICK_INDEX_INTERSECT_SELECT::get_explain(MEM_ROOT *alloc)
+{
+ Explain_quick_select *res;
+ Explain_quick_select *child_explain;
+
+ if (!(res= new (alloc) Explain_quick_select(get_type())))
+ return NULL;
+
+ if (pk_quick_select)
+ {
+ if ((child_explain= pk_quick_select->get_explain(alloc)))
+ res->children.push_back(child_explain);
+ else
+ return NULL;
+ }
+
+ QUICK_RANGE_SELECT *quick;
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+ while ((quick= it++))
+ {
+ if ((child_explain= quick->get_explain(alloc)))
+ res->children.push_back(child_explain);
+ else
+ return NULL;
+ }
+ return res;
+}
+
+
+Explain_quick_select* QUICK_ROR_INTERSECT_SELECT::get_explain(MEM_ROOT *alloc)
+{
+ Explain_quick_select *res;
+ Explain_quick_select *child_explain;
+
+ if (!(res= new (alloc) Explain_quick_select(get_type())))
+ return NULL;
+
+ QUICK_SELECT_WITH_RECORD *qr;
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
+ while ((qr= it++))
+ {
+ if ((child_explain= qr->quick->get_explain(alloc)))
+ res->children.push_back(child_explain);
+ else
+ return NULL;
+ }
+
+ if (cpk_quick)
+ {
+ if ((child_explain= cpk_quick->get_explain(alloc)))
+ res->children.push_back(child_explain);
+ else
+ return NULL;
+ }
+ return res;
+}
+
+
+Explain_quick_select* QUICK_ROR_UNION_SELECT::get_explain(MEM_ROOT *alloc)
+{
+ Explain_quick_select *res;
+ Explain_quick_select *child_explain;
+
+ if (!(res= new (alloc) Explain_quick_select(get_type())))
+ return NULL;
+
+ QUICK_SELECT_I *quick;
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+ while ((quick= it++))
+ {
+ if ((child_explain= quick->get_explain(alloc)))
+ res->children.push_back(child_explain);
+ else
+ return NULL;
+ }
+
+ return res;
+}
+
+
+void QUICK_SELECT_I::add_key_and_length(String *key_names,
+ String *used_lengths,
+ bool *first)
+{
+ char buf[64];
+ uint length;
+ KEY *key_info= head->key_info + index;
+
+ if (*first)
+ *first= FALSE;
+ else
+ {
+ key_names->append(',');
+ used_lengths->append(',');
+ }
+ key_names->append(key_info->name);
+ length= longlong10_to_str(max_used_key_length, buf, 10) - buf;
+ used_lengths->append(buf, length);
+}
+
+
+void QUICK_RANGE_SELECT::add_keys_and_lengths(String *key_names,
+ String *used_lengths)
+{
+ bool first= TRUE;
+
+ add_key_and_length(key_names, used_lengths, &first);
+}
+
+void QUICK_INDEX_MERGE_SELECT::add_keys_and_lengths(String *key_names,
+ String *used_lengths)
+{
+ QUICK_RANGE_SELECT *quick;
+ bool first= TRUE;
+
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+
+ while ((quick= it++))
+ {
+ quick->add_key_and_length(key_names, used_lengths, &first);
+ }
+
+ if (pk_quick_select)
+ pk_quick_select->add_key_and_length(key_names, used_lengths, &first);
+}
+
+
+void QUICK_INDEX_INTERSECT_SELECT::add_keys_and_lengths(String *key_names,
+ String *used_lengths)
+{
+ QUICK_RANGE_SELECT *quick;
+ bool first= TRUE;
+
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+
+ if (pk_quick_select)
+ pk_quick_select->add_key_and_length(key_names, used_lengths, &first);
+
+ while ((quick= it++))
+ {
+ quick->add_key_and_length(key_names, used_lengths, &first);
+ }
+}
+
+void QUICK_ROR_INTERSECT_SELECT::add_keys_and_lengths(String *key_names,
+ String *used_lengths)
+{
+ QUICK_SELECT_WITH_RECORD *qr;
+ bool first= TRUE;
+
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
+
+ while ((qr= it++))
+ {
+ qr->quick->add_key_and_length(key_names, used_lengths, &first);
+ }
+ if (cpk_quick)
+ cpk_quick->add_key_and_length(key_names, used_lengths, &first);
+}
+
+void QUICK_ROR_UNION_SELECT::add_keys_and_lengths(String *key_names,
+ String *used_lengths)
+{
+ QUICK_SELECT_I *quick;
+ bool first= TRUE;
+
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+
+ while ((quick= it++))
+ {
+ if (first)
+ first= FALSE;
+ else
+ {
+ used_lengths->append(',');
+ key_names->append(',');
+ }
+ quick->add_keys_and_lengths(key_names, used_lengths);
+ }
+}
+
+
+void QUICK_RANGE_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set)
+{
+ uint key_len;
+ KEY_PART *part= key_parts;
+ for (key_len=0; key_len < max_used_key_length;
+ key_len += (part++)->store_length)
+ {
+ bitmap_set_bit(col_set, part->field->field_index);
+ }
+}
+
+
+void QUICK_GROUP_MIN_MAX_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set)
+{
+ uint key_len;
+ KEY_PART_INFO *part= index_info->key_part;
+ for (key_len=0; key_len < max_used_key_length;
+ key_len += (part++)->store_length)
+ {
+ bitmap_set_bit(col_set, part->field->field_index);
+ }
+}
+
+
+void QUICK_ROR_INTERSECT_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set)
+{
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
+ QUICK_SELECT_WITH_RECORD *quick;
+ while ((quick= it++))
+ {
+ quick->quick->add_used_key_part_to_set(col_set);
+ }
+}
+
+
+void QUICK_INDEX_SORT_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set)
+{
+ QUICK_RANGE_SELECT *quick;
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+ while ((quick= it++))
+ {
+ quick->add_used_key_part_to_set(col_set);
+ }
+ if (pk_quick_select)
+ pk_quick_select->add_used_key_part_to_set(col_set);
+}
+
+
+void QUICK_ROR_UNION_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set)
+{
+ QUICK_SELECT_I *quick;
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+
+ while ((quick= it++))
+ {
+ quick->add_used_key_part_to_set(col_set);
+ }
+}
+
+
+/*******************************************************************************
+* Implementation of QUICK_GROUP_MIN_MAX_SELECT
+*******************************************************************************/
+
+static inline uint get_field_keypart(KEY *index, Field *field);
+static inline SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree,
+ PARAM *param, uint *param_idx);
+static bool get_sel_arg_for_keypart(Field *field, SEL_ARG *index_range_tree,
+ SEL_ARG **cur_range);
+static bool get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree,
+ KEY_PART_INFO *first_non_group_part,
+ KEY_PART_INFO *min_max_arg_part,
+ KEY_PART_INFO *last_part, THD *thd,
+ uchar *key_infix, uint *key_infix_len,
+ KEY_PART_INFO **first_non_infix_part);
+static bool
+check_group_min_max_predicates(Item *cond, Item_field *min_max_arg_item,
+ Field::imagetype image_type,
+ bool *has_min_max_fld, bool *has_other_fld);
+
+static void
+cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts,
+ uint group_key_parts, SEL_TREE *range_tree,
+ SEL_ARG *index_tree, ha_rows quick_prefix_records,
+ bool have_min, bool have_max,
+ double *read_cost, ha_rows *records);
+
+
+/**
+ Test if this access method is applicable to a GROUP query with MIN/MAX
+ functions, and if so, construct a new TRP object.
+
+ DESCRIPTION
+ Test whether a query can be computed via a QUICK_GROUP_MIN_MAX_SELECT.
+ Queries computable via a QUICK_GROUP_MIN_MAX_SELECT must satisfy the
+ following conditions:
+ A) Table T has at least one compound index I of the form:
+ I = <A_1, ...,A_k, [B_1,..., B_m], C, [D_1,...,D_n]>
+ B) Query conditions:
+ B0. Q is over a single table T.
+ B1. The attributes referenced by Q are a subset of the attributes of I.
+ B2. All attributes QA in Q can be divided into 3 overlapping groups:
+ - SA = {S_1, ..., S_l, [C]} - from the SELECT clause, where C is
+ referenced by any number of MIN and/or MAX functions if present.
+ - WA = {W_1, ..., W_p} - from the WHERE clause
+ - GA = <G_1, ..., G_k> - from the GROUP BY clause (if any)
+ = SA - if Q is a DISTINCT query (based on the
+ equivalence of DISTINCT and GROUP queries.
+ - NGA = QA - (GA union C) = {NG_1, ..., NG_m} - the ones not in
+ GROUP BY and not referenced by MIN/MAX functions.
+ with the following properties specified below.
+ B3. If Q has a GROUP BY WITH ROLLUP clause the access method is not
+ applicable.
+
+ SA1. There is at most one attribute in SA referenced by any number of
+ MIN and/or MAX functions which, which if present, is denoted as C.
+ SA2. The position of the C attribute in the index is after the last A_k.
+ SA3. The attribute C can be referenced in the WHERE clause only in
+ predicates of the forms:
+ - (C {< | <= | > | >= | =} const)
+ - (const {< | <= | > | >= | =} C)
+ - (C between const_i and const_j)
+ - C IS NULL
+ - C IS NOT NULL
+ - C != const
+ SA4. If Q has a GROUP BY clause, there are no other aggregate functions
+ except MIN and MAX. For queries with DISTINCT, aggregate functions
+ are allowed.
+ SA5. The select list in DISTINCT queries should not contain expressions.
+ SA6. Clustered index can not be used by GROUP_MIN_MAX quick select
+ for AGG_FUNC(DISTINCT ...) optimization because cursor position is
+ never stored after a unique key lookup in the clustered index and
+ furhter index_next/prev calls can not be used. So loose index scan
+ optimization can not be used in this case.
+ SA7. If Q has both AGG_FUNC(DISTINCT ...) and MIN/MAX() functions then this
+ access method is not used.
+ For above queries MIN/MAX() aggregation has to be done at
+ nested_loops_join (end_send_group). But with current design MIN/MAX()
+ is always set as part of loose index scan. Because of this mismatch
+ MIN() and MAX() values will be set incorrectly. For such queries to
+ work we need a new interface for loose index scan. This new interface
+ should only fetch records with min and max values and let
+ end_send_group to do aggregation. Until then do not use
+ loose_index_scan.
+ GA1. If Q has a GROUP BY clause, then GA is a prefix of I. That is, if
+ G_i = A_j => i = j.
+ GA2. If Q has a DISTINCT clause, then there is a permutation of SA that
+ forms a prefix of I. This permutation is used as the GROUP clause
+ when the DISTINCT query is converted to a GROUP query.
+ GA3. The attributes in GA may participate in arbitrary predicates, divided
+ into two groups:
+ - RNG(G_1,...,G_q ; where q <= k) is a range condition over the
+ attributes of a prefix of GA
+ - PA(G_i1,...G_iq) is an arbitrary predicate over an arbitrary subset
+ of GA. Since P is applied to only GROUP attributes it filters some
+ groups, and thus can be applied after the grouping.
+ GA4. There are no expressions among G_i, just direct column references.
+ NGA1.If in the index I there is a gap between the last GROUP attribute G_k,
+ and the MIN/MAX attribute C, then NGA must consist of exactly the
+ index attributes that constitute the gap. As a result there is a
+ permutation of NGA, BA=<B_1,...,B_m>, that coincides with the gap
+ in the index.
+ NGA2.If BA <> {}, then the WHERE clause must contain a conjunction EQ of
+ equality conditions for all NG_i of the form (NG_i = const) or
+ (const = NG_i), such that each NG_i is referenced in exactly one
+ conjunct. Informally, the predicates provide constants to fill the
+ gap in the index.
+ NGA3.If BA <> {}, there can only be one range. TODO: This is a code
+ limitation and is not strictly needed. See BUG#15947433
+ WA1. There are no other attributes in the WHERE clause except the ones
+ referenced in predicates RNG, PA, PC, EQ defined above. Therefore
+ WA is subset of (GA union NGA union C) for GA,NGA,C that pass the
+ above tests. By transitivity then it also follows that each WA_i
+ participates in the index I (if this was already tested for GA, NGA
+ and C).
+ WA2. If there is a predicate on C, then it must be in conjunction
+ to all predicates on all earlier keyparts in I.
+
+ C) Overall query form:
+ SELECT EXPR([A_1,...,A_k], [B_1,...,B_m], [MIN(C)], [MAX(C)])
+ FROM T
+ WHERE [RNG(A_1,...,A_p ; where p <= k)]
+ [AND EQ(B_1,...,B_m)]
+ [AND PC(C)]
+ [AND PA(A_i1,...,A_iq)]
+ GROUP BY A_1,...,A_k
+ [HAVING PH(A_1, ..., B_1,..., C)]
+ where EXPR(...) is an arbitrary expression over some or all SELECT fields,
+ or:
+ SELECT DISTINCT A_i1,...,A_ik
+ FROM T
+ WHERE [RNG(A_1,...,A_p ; where p <= k)]
+ [AND PA(A_i1,...,A_iq)];
+
+ NOTES
+ If the current query satisfies the conditions above, and if
+ (mem_root! = NULL), then the function constructs and returns a new TRP
+ object, that is later used to construct a new QUICK_GROUP_MIN_MAX_SELECT.
+ If (mem_root == NULL), then the function only tests whether the current
+ query satisfies the conditions above, and, if so, sets
+ is_applicable = TRUE.
+
+ Queries with DISTINCT for which index access can be used are transformed
+ into equivalent group-by queries of the form:
+
+ SELECT A_1,...,A_k FROM T
+ WHERE [RNG(A_1,...,A_p ; where p <= k)]
+ [AND PA(A_i1,...,A_iq)]
+ GROUP BY A_1,...,A_k;
+
+ The group-by list is a permutation of the select attributes, according
+ to their order in the index.
+
+ TODO
+ - What happens if the query groups by the MIN/MAX field, and there is no
+ other field as in: "select MY_MIN(a) from t1 group by a" ?
+ - We assume that the general correctness of the GROUP-BY query was checked
+ before this point. Is this correct, or do we have to check it completely?
+ - Lift the limitation in condition (B3), that is, make this access method
+ applicable to ROLLUP queries.
+
+ @param param Parameter from test_quick_select
+ @param sel_tree Range tree generated by get_mm_tree
+ @param read_time Best read time so far (=table/index scan time)
+ @return table read plan
+ @retval NULL Loose index scan not applicable or mem_root == NULL
+ @retval !NULL Loose index scan table read plan
+*/
+
+static TRP_GROUP_MIN_MAX *
+get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
+{
+ THD *thd= param->thd;
+ JOIN *join= thd->lex->current_select->join;
+ TABLE *table= param->table;
+ bool have_min= FALSE; /* TRUE if there is a MIN function. */
+ bool have_max= FALSE; /* TRUE if there is a MAX function. */
+ Item_field *min_max_arg_item= NULL; // The argument of all MIN/MAX functions
+ KEY_PART_INFO *min_max_arg_part= NULL; /* The corresponding keypart. */
+ uint group_prefix_len= 0; /* Length (in bytes) of the key prefix. */
+ KEY *index_info= NULL; /* The index chosen for data access. */
+ uint index= 0; /* The id of the chosen index. */
+ uint group_key_parts= 0; // Number of index key parts in the group prefix.
+ uint used_key_parts= 0; /* Number of index key parts used for access. */
+ uchar key_infix[MAX_KEY_LENGTH]; /* Constants from equality predicates.*/
+ uint key_infix_len= 0; /* Length of key_infix. */
+ TRP_GROUP_MIN_MAX *read_plan= NULL; /* The eventually constructed TRP. */
+ uint key_part_nr;
+ ORDER *tmp_group;
+ Item *item;
+ Item_field *item_field;
+ bool is_agg_distinct;
+ List<Item_field> agg_distinct_flds;
+
+ DBUG_ENTER("get_best_group_min_max");
+
+ /* Perform few 'cheap' tests whether this access method is applicable. */
+ if (!join)
+ DBUG_RETURN(NULL); /* This is not a select statement. */
+ if ((join->table_count != 1) || /* The query must reference one table. */
+ (join->select_lex->olap == ROLLUP_TYPE)) /* Check (B3) for ROLLUP */
+ DBUG_RETURN(NULL);
+ if (table->s->keys == 0) /* There are no indexes to use. */
+ DBUG_RETURN(NULL);
+ if (join->conds && join->conds->used_tables() & OUTER_REF_TABLE_BIT)
+ DBUG_RETURN(NULL); /* Cannot execute with correlated conditions. */
+
+ /* Check (SA1,SA4) and store the only MIN/MAX argument - the C attribute.*/
+ if (join->make_sum_func_list(join->all_fields, join->fields_list, 1))
+ DBUG_RETURN(NULL);
+
+ List_iterator<Item> select_items_it(join->fields_list);
+ is_agg_distinct = is_indexed_agg_distinct(join, &agg_distinct_flds);
+
+ if ((!join->group_list) && /* Neither GROUP BY nor a DISTINCT query. */
+ (!join->select_distinct) &&
+ !is_agg_distinct)
+ DBUG_RETURN(NULL);
+ /* Analyze the query in more detail. */
+
+ if (join->sum_funcs[0])
+ {
+ Item_sum *min_max_item;
+ Item_sum **func_ptr= join->sum_funcs;
+ while ((min_max_item= *(func_ptr++)))
+ {
+ if (min_max_item->sum_func() == Item_sum::MIN_FUNC)
+ have_min= TRUE;
+ else if (min_max_item->sum_func() == Item_sum::MAX_FUNC)
+ have_max= TRUE;
+ else if (is_agg_distinct &&
+ (min_max_item->sum_func() == Item_sum::COUNT_DISTINCT_FUNC ||
+ min_max_item->sum_func() == Item_sum::SUM_DISTINCT_FUNC ||
+ min_max_item->sum_func() == Item_sum::AVG_DISTINCT_FUNC))
+ continue;
+ else
+ DBUG_RETURN(NULL);
+
+ /* The argument of MIN/MAX. */
+ Item *expr= min_max_item->get_arg(0)->real_item();
+ if (expr->type() == Item::FIELD_ITEM) /* Is it an attribute? */
+ {
+ if (! min_max_arg_item)
+ min_max_arg_item= (Item_field*) expr;
+ else if (! min_max_arg_item->eq(expr, 1))
+ DBUG_RETURN(NULL);
+ }
+ else
+ DBUG_RETURN(NULL);
+ }
+ }
+
+ /* Check (SA7). */
+ if (is_agg_distinct && (have_max || have_min))
+ {
+ DBUG_RETURN(NULL);
+ }
+
+ /* Check (SA5). */
+ if (join->select_distinct)
+ {
+ while ((item= select_items_it++))
+ {
+ if (item->real_item()->type() != Item::FIELD_ITEM)
+ DBUG_RETURN(NULL);
+ }
+ }
+
+ /* Check (GA4) - that there are no expressions among the group attributes. */
+ for (tmp_group= join->group_list; tmp_group; tmp_group= tmp_group->next)
+ {
+ if ((*tmp_group->item)->real_item()->type() != Item::FIELD_ITEM)
+ DBUG_RETURN(NULL);
+ }
+
+ /*
+ Check that table has at least one compound index such that the conditions
+ (GA1,GA2) are all TRUE. If there is more than one such index, select the
+ first one. Here we set the variables: group_prefix_len and index_info.
+ */
+ KEY *cur_index_info= table->key_info;
+ KEY *cur_index_info_end= cur_index_info + table->s->keys;
+ /* Cost-related variables for the best index so far. */
+ double best_read_cost= DBL_MAX;
+ ha_rows best_records= 0;
+ SEL_ARG *best_index_tree= NULL;
+ ha_rows best_quick_prefix_records= 0;
+ uint best_param_idx= 0;
+
+ const uint pk= param->table->s->primary_key;
+ uint max_key_part;
+ SEL_ARG *cur_index_tree= NULL;
+ ha_rows cur_quick_prefix_records= 0;
+ uint cur_param_idx=MAX_KEY;
+
+ for (uint cur_index= 0 ; cur_index_info != cur_index_info_end ;
+ cur_index_info++, cur_index++)
+ {
+ KEY_PART_INFO *cur_part;
+ KEY_PART_INFO *end_part; /* Last part for loops. */
+ /* Last index part. */
+ KEY_PART_INFO *last_part;
+ KEY_PART_INFO *first_non_group_part;
+ KEY_PART_INFO *first_non_infix_part;
+ uint key_parts;
+ uint key_infix_parts;
+ uint cur_group_key_parts= 0;
+ uint cur_group_prefix_len= 0;
+ double cur_read_cost;
+ ha_rows cur_records;
+ key_map used_key_parts_map;
+ uint cur_key_infix_len= 0;
+ uchar cur_key_infix[MAX_KEY_LENGTH];
+ uint cur_used_key_parts;
+
+ /*
+ Check (B1) - if current index is covering.
+ (was also: "Exclude UNIQUE indexes ..." but this was removed because
+ there are cases Loose Scan over a multi-part index is useful).
+ */
+ if (!table->covering_keys.is_set(cur_index))
+ goto next_index;
+
+ /*
+ Unless extended keys can be used for cur_index:
+ If the current storage manager is such that it appends the primary key to
+ each index, then the above condition is insufficient to check if the
+ index is covering. In such cases it may happen that some fields are
+ covered by the PK index, but not by the current index. Since we can't
+ use the concatenation of both indexes for index lookup, such an index
+ does not qualify as covering in our case. If this is the case, below
+ we check that all query fields are indeed covered by 'cur_index'.
+ */
+ if (cur_index_info->user_defined_key_parts == table->actual_n_key_parts(cur_index_info)
+ && pk < MAX_KEY && cur_index != pk &&
+ (table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX))
+ {
+ /* For each table field */
+ for (uint i= 0; i < table->s->fields; i++)
+ {
+ Field *cur_field= table->field[i];
+ /*
+ If the field is used in the current query ensure that it's
+ part of 'cur_index'
+ */
+ if (bitmap_is_set(table->read_set, cur_field->field_index) &&
+ !cur_field->part_of_key_not_clustered.is_set(cur_index))
+ goto next_index; // Field was not part of key
+ }
+ }
+
+ max_key_part= 0;
+ used_key_parts_map.clear_all();
+
+ /*
+ Check (GA1) for GROUP BY queries.
+ */
+ if (join->group_list)
+ {
+ cur_part= cur_index_info->key_part;
+ end_part= cur_part + table->actual_n_key_parts(cur_index_info);
+ /* Iterate in parallel over the GROUP list and the index parts. */
+ for (tmp_group= join->group_list; tmp_group && (cur_part != end_part);
+ tmp_group= tmp_group->next, cur_part++)
+ {
+ /*
+ TODO:
+ tmp_group::item is an array of Item, is it OK to consider only the
+ first Item? If so, then why? What is the array for?
+ */
+ /* Above we already checked that all group items are fields. */
+ DBUG_ASSERT((*tmp_group->item)->real_item()->type() == Item::FIELD_ITEM);
+ Item_field *group_field= (Item_field *) (*tmp_group->item)->real_item();
+ if (group_field->field->eq(cur_part->field))
+ {
+ cur_group_prefix_len+= cur_part->store_length;
+ ++cur_group_key_parts;
+ max_key_part= cur_part - cur_index_info->key_part + 1;
+ used_key_parts_map.set_bit(max_key_part);
+ }
+ else
+ goto next_index;
+ }
+ /*
+ This function is called on the precondition that the index is covering.
+ Therefore if the GROUP BY list contains more elements than the index,
+ these are duplicates. The GROUP BY list cannot be a prefix of the index.
+ */
+ if (cur_part == end_part && tmp_group)
+ goto next_index;
+ }
+ /*
+ Check (GA2) if this is a DISTINCT query.
+ If GA2, then Store a new ORDER object in group_fields_array at the
+ position of the key part of item_field->field. Thus we get the ORDER
+ objects for each field ordered as the corresponding key parts.
+ Later group_fields_array of ORDER objects is used to convert the query
+ to a GROUP query.
+ */
+ if ((!join->group_list && join->select_distinct) ||
+ is_agg_distinct)
+ {
+ if (!is_agg_distinct)
+ {
+ select_items_it.rewind();
+ }
+
+ List_iterator<Item_field> agg_distinct_flds_it (agg_distinct_flds);
+ while (NULL != (item = (is_agg_distinct ?
+ (Item *) agg_distinct_flds_it++ : select_items_it++)))
+ {
+ /* (SA5) already checked above. */
+ item_field= (Item_field*) item->real_item();
+ DBUG_ASSERT(item->real_item()->type() == Item::FIELD_ITEM);
+
+ /* not doing loose index scan for derived tables */
+ if (!item_field->field)
+ goto next_index;
+
+ /* Find the order of the key part in the index. */
+ key_part_nr= get_field_keypart(cur_index_info, item_field->field);
+ /*
+ Check if this attribute was already present in the select list.
+ If it was present, then its corresponding key part was alredy used.
+ */
+ if (used_key_parts_map.is_set(key_part_nr))
+ continue;
+ if (key_part_nr < 1 ||
+ (!is_agg_distinct && key_part_nr > join->fields_list.elements))
+ goto next_index;
+ cur_part= cur_index_info->key_part + key_part_nr - 1;
+ cur_group_prefix_len+= cur_part->store_length;
+ used_key_parts_map.set_bit(key_part_nr);
+ ++cur_group_key_parts;
+ max_key_part= MY_MAX(max_key_part,key_part_nr);
+ }
+ /*
+ Check that used key parts forms a prefix of the index.
+ To check this we compare bits in all_parts and cur_parts.
+ all_parts have all bits set from 0 to (max_key_part-1).
+ cur_parts have bits set for only used keyparts.
+ */
+ ulonglong all_parts, cur_parts;
+ all_parts= (1ULL << max_key_part) - 1;
+ cur_parts= used_key_parts_map.to_ulonglong() >> 1;
+ if (all_parts != cur_parts)
+ goto next_index;
+ }
+
+ /* Check (SA2). */
+ if (min_max_arg_item)
+ {
+ key_part_nr= get_field_keypart(cur_index_info, min_max_arg_item->field);
+ if (key_part_nr <= cur_group_key_parts)
+ goto next_index;
+ min_max_arg_part= cur_index_info->key_part + key_part_nr - 1;
+ }
+
+ /*
+ Aplly a heuristic: there is no point to use loose index scan when we're
+ using the whole unique index.
+ */
+ if (cur_index_info->flags & HA_NOSAME &&
+ cur_group_key_parts == cur_index_info->user_defined_key_parts)
+ {
+ goto next_index;
+ }
+
+ /*
+ Check (NGA1, NGA2) and extract a sequence of constants to be used as part
+ of all search keys.
+ */
+
+ /*
+ If there is MIN/MAX, each keypart between the last group part and the
+ MIN/MAX part must participate in one equality with constants, and all
+ keyparts after the MIN/MAX part must not be referenced in the query.
+
+ If there is no MIN/MAX, the keyparts after the last group part can be
+ referenced only in equalities with constants, and the referenced keyparts
+ must form a sequence without any gaps that starts immediately after the
+ last group keypart.
+ */
+ key_parts= table->actual_n_key_parts(cur_index_info);
+ last_part= cur_index_info->key_part + key_parts;
+ first_non_group_part= (cur_group_key_parts < key_parts) ?
+ cur_index_info->key_part + cur_group_key_parts :
+ NULL;
+ first_non_infix_part= min_max_arg_part ?
+ (min_max_arg_part < last_part) ?
+ min_max_arg_part :
+ NULL :
+ NULL;
+ if (first_non_group_part &&
+ (!min_max_arg_part || (min_max_arg_part - first_non_group_part > 0)))
+ {
+ if (tree)
+ {
+ uint dummy;
+ SEL_ARG *index_range_tree= get_index_range_tree(cur_index, tree, param,
+ &dummy);
+ if (!get_constant_key_infix(cur_index_info, index_range_tree,
+ first_non_group_part, min_max_arg_part,
+ last_part, thd, cur_key_infix,
+ &cur_key_infix_len,
+ &first_non_infix_part))
+ goto next_index;
+ }
+ else if (min_max_arg_part &&
+ (min_max_arg_part - first_non_group_part > 0))
+ {
+ /*
+ There is a gap but no range tree, thus no predicates at all for the
+ non-group keyparts.
+ */
+ goto next_index;
+ }
+ else if (first_non_group_part && join->conds)
+ {
+ /*
+ If there is no MIN/MAX function in the query, but some index
+ key part is referenced in the WHERE clause, then this index
+ cannot be used because the WHERE condition over the keypart's
+ field cannot be 'pushed' to the index (because there is no
+ range 'tree'), and the WHERE clause must be evaluated before
+ GROUP BY/DISTINCT.
+ */
+ /*
+ Store the first and last keyparts that need to be analyzed
+ into one array that can be passed as parameter.
+ */
+ KEY_PART_INFO *key_part_range[2];
+ key_part_range[0]= first_non_group_part;
+ key_part_range[1]= last_part;
+
+ /* Check if cur_part is referenced in the WHERE clause. */
+ if (join->conds->walk(&Item::find_item_in_field_list_processor, 0,
+ (uchar*) key_part_range))
+ goto next_index;
+ }
+ }
+
+ /*
+ Test (WA1) partially - that no other keypart after the last infix part is
+ referenced in the query.
+ */
+ if (first_non_infix_part)
+ {
+ cur_part= first_non_infix_part +
+ (min_max_arg_part && (min_max_arg_part < last_part));
+ for (; cur_part != last_part; cur_part++)
+ {
+ if (bitmap_is_set(table->read_set, cur_part->field->field_index))
+ goto next_index;
+ }
+ }
+
+ /**
+ Test WA2:If there are conditions on a column C participating in
+ MIN/MAX, those conditions must be conjunctions to all earlier
+ keyparts. Otherwise, Loose Index Scan cannot be used.
+ */
+ if (tree && min_max_arg_item)
+ {
+ uint dummy;
+ SEL_ARG *index_range_tree= get_index_range_tree(cur_index, tree, param,
+ &dummy);
+ SEL_ARG *cur_range= NULL;
+ if (get_sel_arg_for_keypart(min_max_arg_part->field,
+ index_range_tree, &cur_range) ||
+ (cur_range && cur_range->type != SEL_ARG::KEY_RANGE))
+ {
+ goto next_index;
+ }
+ }
+
+ /* If we got to this point, cur_index_info passes the test. */
+ key_infix_parts= cur_key_infix_len ? (uint)
+ (first_non_infix_part - first_non_group_part) : 0;
+ cur_used_key_parts= cur_group_key_parts + key_infix_parts;
+
+ /* Compute the cost of using this index. */
+ if (tree)
+ {
+ /* Find the SEL_ARG sub-tree that corresponds to the chosen index. */
+ cur_index_tree= get_index_range_tree(cur_index, tree, param,
+ &cur_param_idx);
+ /* Check if this range tree can be used for prefix retrieval. */
+ Cost_estimate dummy_cost;
+ uint mrr_flags= HA_MRR_USE_DEFAULT_IMPL;
+ uint mrr_bufsize=0;
+ cur_quick_prefix_records= check_quick_select(param, cur_param_idx,
+ FALSE /*don't care*/,
+ cur_index_tree, TRUE,
+ &mrr_flags, &mrr_bufsize,
+ &dummy_cost);
+ }
+ cost_group_min_max(table, cur_index_info, cur_used_key_parts,
+ cur_group_key_parts, tree, cur_index_tree,
+ cur_quick_prefix_records, have_min, have_max,
+ &cur_read_cost, &cur_records);
+ /*
+ If cur_read_cost is lower than best_read_cost use cur_index.
+ Do not compare doubles directly because they may have different
+ representations (64 vs. 80 bits).
+ */
+ if (cur_read_cost < best_read_cost - (DBL_EPSILON * cur_read_cost))
+ {
+ index_info= cur_index_info;
+ index= cur_index;
+ best_read_cost= cur_read_cost;
+ best_records= cur_records;
+ best_index_tree= cur_index_tree;
+ best_quick_prefix_records= cur_quick_prefix_records;
+ best_param_idx= cur_param_idx;
+ group_key_parts= cur_group_key_parts;
+ group_prefix_len= cur_group_prefix_len;
+ key_infix_len= cur_key_infix_len;
+ if (key_infix_len)
+ memcpy (key_infix, cur_key_infix, sizeof (key_infix));
+ used_key_parts= cur_used_key_parts;
+ }
+
+ next_index:;
+ }
+ if (!index_info) /* No usable index found. */
+ DBUG_RETURN(NULL);
+
+ /* Check (SA3) for the where clause. */
+ bool has_min_max_fld= false, has_other_fld= false;
+ if (join->conds && min_max_arg_item &&
+ !check_group_min_max_predicates(join->conds, min_max_arg_item,
+ (index_info->flags & HA_SPATIAL) ?
+ Field::itMBR : Field::itRAW,
+ &has_min_max_fld, &has_other_fld))
+ DBUG_RETURN(NULL);
+
+ /*
+ Check (SA6) if clustered key is used
+ */
+ if (is_agg_distinct && index == table->s->primary_key &&
+ table->file->primary_key_is_clustered())
+ DBUG_RETURN(NULL);
+
+ /* The query passes all tests, so construct a new TRP object. */
+ read_plan= new (param->mem_root)
+ TRP_GROUP_MIN_MAX(have_min, have_max, is_agg_distinct,
+ min_max_arg_part,
+ group_prefix_len, used_key_parts,
+ group_key_parts, index_info, index,
+ key_infix_len,
+ (key_infix_len > 0) ? key_infix : NULL,
+ tree, best_index_tree, best_param_idx,
+ best_quick_prefix_records);
+ if (read_plan)
+ {
+ if (tree && read_plan->quick_prefix_records == 0)
+ DBUG_RETURN(NULL);
+
+ read_plan->read_cost= best_read_cost;
+ read_plan->records= best_records;
+ if (read_time < best_read_cost && is_agg_distinct)
+ {
+ read_plan->read_cost= 0;
+ read_plan->use_index_scan();
+ }
+
+ DBUG_PRINT("info",
+ ("Returning group min/max plan: cost: %g, records: %lu",
+ read_plan->read_cost, (ulong) read_plan->records));
+ }
+
+ DBUG_RETURN(read_plan);
+}
+
+
+/*
+ Check that the MIN/MAX attribute participates only in range predicates
+ with constants.
+
+ SYNOPSIS
+ check_group_min_max_predicates()
+ cond [in] the expression tree being analyzed
+ min_max_arg [in] the field referenced by the MIN/MAX function(s)
+ image_type [in]
+ has_min_max_arg [out] true if the subtree being analyzed references
+ min_max_arg
+ has_other_arg [out] true if the subtree being analyzed references a
+ column other min_max_arg
+
+ DESCRIPTION
+ The function walks recursively over the cond tree representing a WHERE
+ clause, and checks condition (SA3) - if a field is referenced by a MIN/MAX
+ aggregate function, it is referenced only by one of the following
+ predicates $FUNC$:
+ {=, !=, <, <=, >, >=, between, is [not] null, multiple equal}.
+ In addition the function checks that the WHERE condition is equivalent to
+ "cond1 AND cond2" where :
+ cond1 - does not use min_max_column at all.
+ cond2 - is an AND/OR tree with leaves in form
+ "$FUNC$(min_max_column[, const])".
+
+ RETURN
+ TRUE if cond passes the test
+ FALSE o/w
+*/
+
+static bool
+check_group_min_max_predicates(Item *cond, Item_field *min_max_arg_item,
+ Field::imagetype image_type,
+ bool *has_min_max_arg, bool *has_other_arg)
+{
+ DBUG_ENTER("check_group_min_max_predicates");
+ DBUG_ASSERT(cond && min_max_arg_item);
+
+ cond= cond->real_item();
+ Item::Type cond_type= cond->real_type();
+ if (cond_type == Item::COND_ITEM) /* 'AND' or 'OR' */
+ {
+ DBUG_PRINT("info", ("Analyzing: %s", ((Item_func*) cond)->func_name()));
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *and_or_arg;
+ Item_func::Functype func_type= ((Item_cond*) cond)->functype();
+ bool has_min_max= false, has_other= false;
+ while ((and_or_arg= li++))
+ {
+ /*
+ The WHERE clause doesn't pass the condition if:
+ (1) any subtree doesn't pass the condition or
+ (2) the subtree passes the test, but it is an OR and it references both
+ the min/max argument and other columns.
+ */
+ if (!check_group_min_max_predicates(and_or_arg, min_max_arg_item, //1
+ image_type,
+ &has_min_max, &has_other) ||
+ (func_type == Item_func::COND_OR_FUNC && has_min_max && has_other))//2
+ DBUG_RETURN(FALSE);
+ }
+ *has_min_max_arg= has_min_max || *has_min_max_arg;
+ *has_other_arg= has_other || *has_other_arg;
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Disallow loose index scan if the MIN/MAX argument field is referenced by
+ a subquery in the WHERE clause.
+ */
+
+ if (unlikely(cond_type == Item::SUBSELECT_ITEM))
+ {
+ Item_subselect *subs_cond= (Item_subselect*) cond;
+ if (subs_cond->is_correlated)
+ {
+ DBUG_ASSERT(subs_cond->upper_refs.elements > 0);
+ List_iterator_fast<Item_subselect::Ref_to_outside>
+ li(subs_cond->upper_refs);
+ Item_subselect::Ref_to_outside *dep;
+ while ((dep= li++))
+ {
+ if (dep->item->eq(min_max_arg_item, FALSE))
+ DBUG_RETURN(FALSE);
+ }
+ }
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Subquery with IS [NOT] NULL
+ TODO: Look into the cache_item and optimize it like we do for
+ subselect's above
+ */
+ if (unlikely(cond_type == Item::CACHE_ITEM))
+ DBUG_RETURN(cond->const_item());
+
+ /*
+ Condition of the form 'field' is equivalent to 'field <> 0' and thus
+ satisfies the SA3 condition.
+ */
+ if (cond_type == Item::FIELD_ITEM)
+ {
+ DBUG_PRINT("info", ("Analyzing: %s", cond->full_name()));
+ if (min_max_arg_item->eq((Item_field*)cond, 1))
+ *has_min_max_arg= true;
+ else
+ *has_other_arg= true;
+ DBUG_RETURN(TRUE);
+ }
+
+ /* We presume that at this point there are no other Items than functions. */
+ DBUG_ASSERT(cond_type == Item::FUNC_ITEM);
+ if (unlikely(cond_type != Item::FUNC_ITEM)) /* Safety */
+ DBUG_RETURN(FALSE);
+
+ /* Test if cond references only group-by or non-group fields. */
+ Item_func *pred= (Item_func*) cond;
+ Item_func::Functype pred_type= pred->functype();
+ DBUG_PRINT("info", ("Analyzing: %s", pred->func_name()));
+ if (pred_type == Item_func::MULT_EQUAL_FUNC)
+ {
+ /*
+ Check that each field in a multiple equality is either a constant or
+ it is a reference to the min/max argument, or it doesn't contain the
+ min/max argument at all.
+ */
+ Item_equal_fields_iterator eq_it(*((Item_equal*)pred));
+ Item *eq_item;
+ bool has_min_max= false, has_other= false;
+ while ((eq_item= eq_it++))
+ {
+ if (min_max_arg_item->eq(eq_item->real_item(), 1))
+ has_min_max= true;
+ else
+ has_other= true;
+ }
+ *has_min_max_arg= has_min_max || *has_min_max_arg;
+ *has_other_arg= has_other || *has_other_arg;
+ DBUG_RETURN(!(has_min_max && has_other));
+ }
+
+ Item **arguments= pred->arguments();
+ Item *cur_arg;
+ bool has_min_max= false, has_other= false;
+ for (uint arg_idx= 0; arg_idx < pred->argument_count (); arg_idx++)
+ {
+ cur_arg= arguments[arg_idx]->real_item();
+ DBUG_PRINT("info", ("cur_arg: %s", cur_arg->full_name()));
+ if (cur_arg->type() == Item::FIELD_ITEM)
+ {
+ if (min_max_arg_item->eq(cur_arg, 1))
+ {
+ has_min_max= true;
+ /*
+ If pred references the MIN/MAX argument, check whether pred is a range
+ condition that compares the MIN/MAX argument with a constant.
+ */
+ if (pred_type != Item_func::EQUAL_FUNC &&
+ pred_type != Item_func::LT_FUNC &&
+ pred_type != Item_func::LE_FUNC &&
+ pred_type != Item_func::GT_FUNC &&
+ pred_type != Item_func::GE_FUNC &&
+ pred_type != Item_func::BETWEEN &&
+ pred_type != Item_func::ISNULL_FUNC &&
+ pred_type != Item_func::ISNOTNULL_FUNC &&
+ pred_type != Item_func::EQ_FUNC &&
+ pred_type != Item_func::NE_FUNC)
+ DBUG_RETURN(FALSE);
+
+ /* Check that pred compares min_max_arg_item with a constant. */
+ Item *args[3];
+ bzero(args, 3 * sizeof(Item*));
+ bool inv;
+ /* Test if this is a comparison of a field and a constant. */
+ if (!simple_pred(pred, args, &inv))
+ DBUG_RETURN(FALSE);
+
+ /* Check for compatible string comparisons - similar to get_mm_leaf. */
+ if (args[0] && args[1] && !args[2]) // this is a binary function
+ {
+ if (args[1]->cmp_type() == TIME_RESULT &&
+ min_max_arg_item->field->cmp_type() != TIME_RESULT)
+ DBUG_RETURN(FALSE);
+
+ /*
+ Can't use GROUP_MIN_MAX optimization for ENUM and SET,
+ because the values are stored as numbers in index,
+ while MIN() and MAX() work as strings.
+ It would return the records with min and max enum numeric indexes.
+ "Bug#45300 MAX() and ENUM type" should be fixed first.
+ */
+ if (min_max_arg_item->field->real_type() == MYSQL_TYPE_ENUM ||
+ min_max_arg_item->field->real_type() == MYSQL_TYPE_SET)
+ DBUG_RETURN(FALSE);
+
+ if (min_max_arg_item->result_type() == STRING_RESULT &&
+ /*
+ Don't use an index when comparing strings of different collations.
+ */
+ ((args[1]->result_type() == STRING_RESULT &&
+ image_type == Field::itRAW &&
+ min_max_arg_item->field->charset() !=
+ pred->compare_collation()) ||
+ /*
+ We can't always use indexes when comparing a string index to a
+ number.
+ */
+ (args[1]->result_type() != STRING_RESULT &&
+ min_max_arg_item->field->cmp_type() != args[1]->result_type())))
+ DBUG_RETURN(FALSE);
+ }
+ }
+ else
+ has_other= true;
+ }
+ else if (cur_arg->type() == Item::FUNC_ITEM)
+ {
+ if (!check_group_min_max_predicates(cur_arg, min_max_arg_item, image_type,
+ &has_min_max, &has_other))
+ DBUG_RETURN(FALSE);
+ }
+ else if (cur_arg->const_item() && !cur_arg->is_expensive())
+ {
+ /*
+ For predicates of the form "const OP expr" we also have to check 'expr'
+ to make a decision.
+ */
+ continue;
+ }
+ else
+ DBUG_RETURN(FALSE);
+ if(has_min_max && has_other)
+ DBUG_RETURN(FALSE);
+ }
+ *has_min_max_arg= has_min_max || *has_min_max_arg;
+ *has_other_arg= has_other || *has_other_arg;
+
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Get the SEL_ARG tree 'tree' for the keypart covering 'field', if
+ any. 'tree' must be a unique conjunction to ALL predicates in earlier
+ keyparts of 'keypart_tree'.
+
+ E.g., if 'keypart_tree' is for a composite index (kp1,kp2) and kp2
+ covers 'field', all these conditions satisfies the requirement:
+
+ 1. "(kp1=2 OR kp1=3) AND kp2=10" => returns "kp2=10"
+ 2. "(kp1=2 AND kp2=10) OR (kp1=3 AND kp2=10)" => returns "kp2=10"
+ 3. "(kp1=2 AND (kp2=10 OR kp2=11)) OR (kp1=3 AND (kp2=10 OR kp2=11))"
+ => returns "kp2=10 OR kp2=11"
+
+ whereas these do not
+ 1. "(kp1=2 AND kp2=10) OR kp1=3"
+ 2. "(kp1=2 AND kp2=10) OR (kp1=3 AND kp2=11)"
+ 3. "(kp1=2 AND kp2=10) OR (kp1=3 AND (kp2=10 OR kp2=11))"
+
+ This function effectively tests requirement WA2. In combination with
+ a test that the returned tree has no more than one range it is also
+ a test of NGA3.
+
+ @param[in] field The field we want the SEL_ARG tree for
+ @param[in] keypart_tree Root node of the SEL_ARG* tree for the index
+ @param[out] cur_range The SEL_ARG tree, if any, for the keypart
+ covering field 'keypart_field'
+ @retval true 'keypart_tree' contained a predicate for 'field' that
+ is not conjunction to all predicates on earlier keyparts
+ @retval false otherwise
+*/
+
+static bool
+get_sel_arg_for_keypart(Field *field,
+ SEL_ARG *keypart_tree,
+ SEL_ARG **cur_range)
+{
+ if (keypart_tree == NULL)
+ return false;
+ if (keypart_tree->field->eq(field))
+ {
+ *cur_range= keypart_tree;
+ return false;
+ }
+
+ SEL_ARG *tree_first_range= NULL;
+ SEL_ARG *first_kp= keypart_tree->first();
+
+ for (SEL_ARG *cur_kp= first_kp; cur_kp; cur_kp= cur_kp->next)
+ {
+ SEL_ARG *curr_tree= NULL;
+ if (cur_kp->next_key_part)
+ {
+ if (get_sel_arg_for_keypart(field,
+ cur_kp->next_key_part,
+ &curr_tree))
+ return true;
+ }
+ /*
+ Check if the SEL_ARG tree for 'field' is identical for all ranges in
+ 'keypart_tree
+ */
+ if (cur_kp == first_kp)
+ tree_first_range= curr_tree;
+ else if (!all_same(tree_first_range, curr_tree))
+ return true;
+ }
+ *cur_range= tree_first_range;
+ return false;
+}
+
+/*
+ Extract a sequence of constants from a conjunction of equality predicates.
+
+ SYNOPSIS
+ get_constant_key_infix()
+ index_info [in] Descriptor of the chosen index.
+ index_range_tree [in] Range tree for the chosen index
+ first_non_group_part [in] First index part after group attribute parts
+ min_max_arg_part [in] The keypart of the MIN/MAX argument if any
+ last_part [in] Last keypart of the index
+ thd [in] Current thread
+ key_infix [out] Infix of constants to be used for index lookup
+ key_infix_len [out] Lenghth of the infix
+ first_non_infix_part [out] The first keypart after the infix (if any)
+
+ DESCRIPTION
+ Test conditions (NGA1, NGA2, NGA3) from get_best_group_min_max(). Namely,
+ for each keypart field NG_i not in GROUP-BY, check that there is exactly one
+ constant equality predicate among conds with the form (NG_i = const_ci) or
+ (const_ci = NG_i).. In addition, there can only be one range when there is
+ such a gap.
+ Thus all the NGF_i attributes must fill the 'gap' between the last group-by
+ attribute and the MIN/MAX attribute in the index (if present). Also ensure
+ that there is only a single range on NGF_i (NGA3). If these
+ conditions hold, copy each constant from its corresponding predicate into
+ key_infix, in the order its NG_i attribute appears in the index, and update
+ key_infix_len with the total length of the key parts in key_infix.
+
+ RETURN
+ TRUE if the index passes the test
+ FALSE o/w
+*/
+static bool
+get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree,
+ KEY_PART_INFO *first_non_group_part,
+ KEY_PART_INFO *min_max_arg_part,
+ KEY_PART_INFO *last_part, THD *thd,
+ uchar *key_infix, uint *key_infix_len,
+ KEY_PART_INFO **first_non_infix_part)
+{
+ SEL_ARG *cur_range;
+ KEY_PART_INFO *cur_part;
+ /* End part for the first loop below. */
+ KEY_PART_INFO *end_part= min_max_arg_part ? min_max_arg_part : last_part;
+
+ *key_infix_len= 0;
+ uchar *key_ptr= key_infix;
+ for (cur_part= first_non_group_part; cur_part != end_part; cur_part++)
+ {
+ cur_range= NULL;
+ /*
+ Check NGA3:
+ 1. get_sel_arg_for_keypart gets the range tree for the 'field' and also
+ checks for a unique conjunction of this tree with all the predicates
+ on the earlier keyparts in the index.
+ 2. Check for multiple ranges on the found keypart tree.
+
+ We assume that index_range_tree points to the leftmost keypart in
+ the index.
+ */
+ if (get_sel_arg_for_keypart(cur_part->field, index_range_tree,
+ &cur_range))
+ return false;
+
+ if (cur_range && cur_range->elements > 1)
+ return false;
+
+ if (!cur_range || cur_range->type != SEL_ARG::KEY_RANGE)
+ {
+ if (min_max_arg_part)
+ return false; /* The current keypart has no range predicates at all. */
+ else
+ {
+ *first_non_infix_part= cur_part;
+ return true;
+ }
+ }
+
+ if ((cur_range->min_flag & NO_MIN_RANGE) ||
+ (cur_range->max_flag & NO_MAX_RANGE) ||
+ (cur_range->min_flag & NEAR_MIN) || (cur_range->max_flag & NEAR_MAX))
+ return false;
+
+ uint field_length= cur_part->store_length;
+ if (cur_range->maybe_null &&
+ cur_range->min_value[0] && cur_range->max_value[0])
+ {
+ /*
+ cur_range specifies 'IS NULL'. In this case the argument points
+ to a "null value" (is_null_string) that may not always be long
+ enough for a direct memcpy to a field.
+ */
+ DBUG_ASSERT (field_length > 0);
+ *key_ptr= 1;
+ bzero(key_ptr+1,field_length-1);
+ key_ptr+= field_length;
+ *key_infix_len+= field_length;
+ }
+ else if (memcmp(cur_range->min_value, cur_range->max_value, field_length) == 0)
+ { /* cur_range specifies an equality condition. */
+ memcpy(key_ptr, cur_range->min_value, field_length);
+ key_ptr+= field_length;
+ *key_infix_len+= field_length;
+ }
+ else
+ return false;
+ }
+
+ if (!min_max_arg_part && (cur_part == last_part))
+ *first_non_infix_part= last_part;
+
+ return TRUE;
+}
+
+
+/*
+ Find the key part referenced by a field.
+
+ SYNOPSIS
+ get_field_keypart()
+ index descriptor of an index
+ field field that possibly references some key part in index
+
+ NOTES
+ The return value can be used to get a KEY_PART_INFO pointer by
+ part= index->key_part + get_field_keypart(...) - 1;
+
+ RETURN
+ Positive number which is the consecutive number of the key part, or
+ 0 if field does not reference any index field.
+*/
+
+static inline uint
+get_field_keypart(KEY *index, Field *field)
+{
+ KEY_PART_INFO *part, *end;
+
+ for (part= index->key_part,
+ end= part + field->table->actual_n_key_parts(index);
+ part < end; part++)
+ {
+ if (field->eq(part->field))
+ return part - index->key_part + 1;
+ }
+ return 0;
+}
+
+
+/*
+ Find the SEL_ARG sub-tree that corresponds to the chosen index.
+
+ SYNOPSIS
+ get_index_range_tree()
+ index [in] The ID of the index being looked for
+ range_tree[in] Tree of ranges being searched
+ param [in] PARAM from SQL_SELECT::test_quick_select
+ param_idx [out] Index in the array PARAM::key that corresponds to 'index'
+
+ DESCRIPTION
+
+ A SEL_TREE contains range trees for all usable indexes. This procedure
+ finds the SEL_ARG sub-tree for 'index'. The members of a SEL_TREE are
+ ordered in the same way as the members of PARAM::key, thus we first find
+ the corresponding index in the array PARAM::key. This index is returned
+ through the variable param_idx, to be used later as argument of
+ check_quick_select().
+
+ RETURN
+ Pointer to the SEL_ARG subtree that corresponds to index.
+*/
+
+SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree, PARAM *param,
+ uint *param_idx)
+{
+ uint idx= 0; /* Index nr in param->key_parts */
+ while (idx < param->keys)
+ {
+ if (index == param->real_keynr[idx])
+ break;
+ idx++;
+ }
+ *param_idx= idx;
+ return(range_tree->keys[idx]);
+}
+
+
+/*
+ Compute the cost of a quick_group_min_max_select for a particular index.
+
+ SYNOPSIS
+ cost_group_min_max()
+ table [in] The table being accessed
+ index_info [in] The index used to access the table
+ used_key_parts [in] Number of key parts used to access the index
+ group_key_parts [in] Number of index key parts in the group prefix
+ range_tree [in] Tree of ranges for all indexes
+ index_tree [in] The range tree for the current index
+ quick_prefix_records [in] Number of records retrieved by the internally
+ used quick range select if any
+ have_min [in] True if there is a MIN function
+ have_max [in] True if there is a MAX function
+ read_cost [out] The cost to retrieve rows via this quick select
+ records [out] The number of rows retrieved
+
+ DESCRIPTION
+ This method computes the access cost of a TRP_GROUP_MIN_MAX instance and
+ the number of rows returned.
+
+ NOTES
+ The cost computation distinguishes several cases:
+ 1) No equality predicates over non-group attributes (thus no key_infix).
+ If groups are bigger than blocks on the average, then we assume that it
+ is very unlikely that block ends are aligned with group ends, thus even
+ if we look for both MIN and MAX keys, all pairs of neighbor MIN/MAX
+ keys, except for the first MIN and the last MAX keys, will be in the
+ same block. If groups are smaller than blocks, then we are going to
+ read all blocks.
+ 2) There are equality predicates over non-group attributes.
+ In this case the group prefix is extended by additional constants, and
+ as a result the min/max values are inside sub-groups of the original
+ groups. The number of blocks that will be read depends on whether the
+ ends of these sub-groups will be contained in the same or in different
+ blocks. We compute the probability for the two ends of a subgroup to be
+ in two different blocks as the ratio of:
+ - the number of positions of the left-end of a subgroup inside a group,
+ such that the right end of the subgroup is past the end of the buffer
+ containing the left-end, and
+ - the total number of possible positions for the left-end of the
+ subgroup, which is the number of keys in the containing group.
+ We assume it is very unlikely that two ends of subsequent subgroups are
+ in the same block.
+ 3) The are range predicates over the group attributes.
+ Then some groups may be filtered by the range predicates. We use the
+ selectivity of the range predicates to decide how many groups will be
+ filtered.
+
+ TODO
+ - Take into account the optional range predicates over the MIN/MAX
+ argument.
+ - Check if we have a PK index and we use all cols - then each key is a
+ group, and it will be better to use an index scan.
+
+ RETURN
+ None
+*/
+
+void cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts,
+ uint group_key_parts, SEL_TREE *range_tree,
+ SEL_ARG *index_tree, ha_rows quick_prefix_records,
+ bool have_min, bool have_max,
+ double *read_cost, ha_rows *records)
+{
+ ha_rows table_records;
+ ha_rows num_groups;
+ ha_rows num_blocks;
+ uint keys_per_block;
+ ha_rows keys_per_group;
+ ha_rows keys_per_subgroup; /* Average number of keys in sub-groups */
+ /* formed by a key infix. */
+ double p_overlap; /* Probability that a sub-group overlaps two blocks. */
+ double quick_prefix_selectivity;
+ double io_cost;
+ DBUG_ENTER("cost_group_min_max");
+
+ table_records= table->stat_records();
+ keys_per_block= (uint) (table->file->stats.block_size / 2 /
+ (index_info->key_length + table->file->ref_length)
+ + 1);
+ num_blocks= (ha_rows)(table_records / keys_per_block) + 1;
+
+ /* Compute the number of keys in a group. */
+ keys_per_group= (ha_rows) index_info->actual_rec_per_key(group_key_parts - 1);
+ if (keys_per_group == 0) /* If there is no statistics try to guess */
+ /* each group contains 10% of all records */
+ keys_per_group= (table_records / 10) + 1;
+ num_groups= (table_records / keys_per_group) + 1;
+
+ /* Apply the selectivity of the quick select for group prefixes. */
+ if (range_tree && (quick_prefix_records != HA_POS_ERROR))
+ {
+ quick_prefix_selectivity= (double) quick_prefix_records /
+ (double) table_records;
+ num_groups= (ha_rows) rint(num_groups * quick_prefix_selectivity);
+ set_if_bigger(num_groups, 1);
+ }
+
+ if (used_key_parts > group_key_parts)
+ { /*
+ Compute the probability that two ends of a subgroup are inside
+ different blocks.
+ */
+ keys_per_subgroup= (ha_rows) index_info->actual_rec_per_key(used_key_parts - 1);
+ if (keys_per_subgroup >= keys_per_block) /* If a subgroup is bigger than */
+ p_overlap= 1.0; /* a block, it will overlap at least two blocks. */
+ else
+ {
+ double blocks_per_group= (double) num_blocks / (double) num_groups;
+ p_overlap= (blocks_per_group * (keys_per_subgroup - 1)) / keys_per_group;
+ p_overlap= MY_MIN(p_overlap, 1.0);
+ }
+ io_cost= (double) MY_MIN(num_groups * (1 + p_overlap), num_blocks);
+ }
+ else
+ io_cost= (keys_per_group > keys_per_block) ?
+ (have_min && have_max) ? (double) (num_groups + 1) :
+ (double) num_groups :
+ (double) num_blocks;
+
+ /*
+ CPU cost must be comparable to that of an index scan as computed
+ in SQL_SELECT::test_quick_select(). When the groups are small,
+ e.g. for a unique index, using index scan will be cheaper since it
+ reads the next record without having to re-position to it on every
+ group. To make the CPU cost reflect this, we estimate the CPU cost
+ as the sum of:
+ 1. Cost for evaluating the condition (similarly as for index scan).
+ 2. Cost for navigating the index structure (assuming a b-tree).
+ Note: We only add the cost for one comparision per block. For a
+ b-tree the number of comparisons will be larger.
+ TODO: This cost should be provided by the storage engine.
+ */
+ const double tree_traversal_cost=
+ ceil(log(static_cast<double>(table_records))/
+ log(static_cast<double>(keys_per_block))) *
+ 1/double(2*TIME_FOR_COMPARE);
+
+ const double cpu_cost= num_groups *
+ (tree_traversal_cost + 1/double(TIME_FOR_COMPARE));
+
+ *read_cost= io_cost + cpu_cost;
+ *records= num_groups;
+
+ DBUG_PRINT("info",
+ ("table rows: %lu keys/block: %u keys/group: %lu result rows: %lu blocks: %lu",
+ (ulong)table_records, keys_per_block, (ulong) keys_per_group,
+ (ulong) *records, (ulong) num_blocks));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Construct a new quick select object for queries with group by with min/max.
+
+ SYNOPSIS
+ TRP_GROUP_MIN_MAX::make_quick()
+ param Parameter from test_quick_select
+ retrieve_full_rows ignored
+ parent_alloc Memory pool to use, if any.
+
+ NOTES
+ Make_quick ignores the retrieve_full_rows parameter because
+ QUICK_GROUP_MIN_MAX_SELECT always performs 'index only' scans.
+ The other parameter are ignored as well because all necessary
+ data to create the QUICK object is computed at this TRP creation
+ time.
+
+ RETURN
+ New QUICK_GROUP_MIN_MAX_SELECT object if successfully created,
+ NULL otherwise.
+*/
+
+QUICK_SELECT_I *
+TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc)
+{
+ QUICK_GROUP_MIN_MAX_SELECT *quick;
+ DBUG_ENTER("TRP_GROUP_MIN_MAX::make_quick");
+
+ quick= new QUICK_GROUP_MIN_MAX_SELECT(param->table,
+ param->thd->lex->current_select->join,
+ have_min, have_max,
+ have_agg_distinct, min_max_arg_part,
+ group_prefix_len, group_key_parts,
+ used_key_parts, index_info, index,
+ read_cost, records, key_infix_len,
+ key_infix, parent_alloc, is_index_scan);
+ if (!quick)
+ DBUG_RETURN(NULL);
+
+ if (quick->init())
+ {
+ delete quick;
+ DBUG_RETURN(NULL);
+ }
+
+ if (range_tree)
+ {
+ DBUG_ASSERT(quick_prefix_records > 0);
+ if (quick_prefix_records == HA_POS_ERROR)
+ quick->quick_prefix_select= NULL; /* Can't construct a quick select. */
+ else
+ /* Make a QUICK_RANGE_SELECT to be used for group prefix retrieval. */
+ quick->quick_prefix_select= get_quick_select(param, param_idx,
+ index_tree,
+ HA_MRR_USE_DEFAULT_IMPL, 0,
+ &quick->alloc);
+
+ /*
+ Extract the SEL_ARG subtree that contains only ranges for the MIN/MAX
+ attribute, and create an array of QUICK_RANGES to be used by the
+ new quick select.
+ */
+ if (min_max_arg_part)
+ {
+ SEL_ARG *min_max_range= index_tree;
+ while (min_max_range) /* Find the tree for the MIN/MAX key part. */
+ {
+ if (min_max_range->field->eq(min_max_arg_part->field))
+ break;
+ min_max_range= min_max_range->next_key_part;
+ }
+ /* Scroll to the leftmost interval for the MIN/MAX argument. */
+ while (min_max_range && min_max_range->prev)
+ min_max_range= min_max_range->prev;
+ /* Create an array of QUICK_RANGEs for the MIN/MAX argument. */
+ while (min_max_range)
+ {
+ if (quick->add_range(min_max_range))
+ {
+ delete quick;
+ quick= NULL;
+ DBUG_RETURN(NULL);
+ }
+ min_max_range= min_max_range->next;
+ }
+ }
+ }
+ else
+ quick->quick_prefix_select= NULL;
+
+ quick->update_key_stat();
+ quick->adjust_prefix_ranges();
+
+ DBUG_RETURN(quick);
+}
+
+
+/*
+ Construct new quick select for group queries with min/max.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::QUICK_GROUP_MIN_MAX_SELECT()
+ table The table being accessed
+ join Descriptor of the current query
+ have_min TRUE if the query selects a MIN function
+ have_max TRUE if the query selects a MAX function
+ min_max_arg_part The only argument field of all MIN/MAX functions
+ group_prefix_len Length of all key parts in the group prefix
+ prefix_key_parts All key parts in the group prefix
+ index_info The index chosen for data access
+ use_index The id of index_info
+ read_cost Cost of this access method
+ records Number of records returned
+ key_infix_len Length of the key infix appended to the group prefix
+ key_infix Infix of constants from equality predicates
+ parent_alloc Memory pool for this and quick_prefix_select data
+ is_index_scan get the next different key not by jumping on it via
+ index read, but by scanning until the end of the
+ rows with equal key value.
+
+ RETURN
+ None
+*/
+
+QUICK_GROUP_MIN_MAX_SELECT::
+QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join_arg, bool have_min_arg,
+ bool have_max_arg, bool have_agg_distinct_arg,
+ KEY_PART_INFO *min_max_arg_part_arg,
+ uint group_prefix_len_arg, uint group_key_parts_arg,
+ uint used_key_parts_arg, KEY *index_info_arg,
+ uint use_index, double read_cost_arg,
+ ha_rows records_arg, uint key_infix_len_arg,
+ uchar *key_infix_arg, MEM_ROOT *parent_alloc,
+ bool is_index_scan_arg)
+ :file(table->file), join(join_arg), index_info(index_info_arg),
+ group_prefix_len(group_prefix_len_arg),
+ group_key_parts(group_key_parts_arg), have_min(have_min_arg),
+ have_max(have_max_arg), have_agg_distinct(have_agg_distinct_arg),
+ seen_first_key(FALSE), doing_key_read(FALSE), min_max_arg_part(min_max_arg_part_arg),
+ key_infix(key_infix_arg), key_infix_len(key_infix_len_arg),
+ min_functions_it(NULL), max_functions_it(NULL),
+ is_index_scan(is_index_scan_arg)
+{
+ head= table;
+ index= use_index;
+ record= head->record[0];
+ tmp_record= head->record[1];
+ read_time= read_cost_arg;
+ records= records_arg;
+ used_key_parts= used_key_parts_arg;
+ real_key_parts= used_key_parts_arg;
+ real_prefix_len= group_prefix_len + key_infix_len;
+ group_prefix= NULL;
+ min_max_arg_len= min_max_arg_part ? min_max_arg_part->store_length : 0;
+
+ /*
+ We can't have parent_alloc set as the init function can't handle this case
+ yet.
+ */
+ DBUG_ASSERT(!parent_alloc);
+ if (!parent_alloc)
+ {
+ init_sql_alloc(&alloc, join->thd->variables.range_alloc_block_size, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ join->thd->mem_root= &alloc;
+ }
+ else
+ bzero(&alloc, sizeof(MEM_ROOT)); // ensure that it's not used
+}
+
+
+/*
+ Do post-constructor initialization.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::init()
+
+ DESCRIPTION
+ The method performs initialization that cannot be done in the constructor
+ such as memory allocations that may fail. It allocates memory for the
+ group prefix and inifix buffers, and for the lists of MIN/MAX item to be
+ updated during execution.
+
+ RETURN
+ 0 OK
+ other Error code
+*/
+
+int QUICK_GROUP_MIN_MAX_SELECT::init()
+{
+ if (group_prefix) /* Already initialized. */
+ return 0;
+
+ /*
+ We allocate one byte more to serve the case when the last field in
+ the buffer is compared using uint3korr (e.g. a Field_newdate field)
+ */
+ if (!(last_prefix= (uchar*) alloc_root(&alloc, group_prefix_len+1)))
+ return 1;
+ /*
+ We may use group_prefix to store keys with all select fields, so allocate
+ enough space for it.
+ We allocate one byte more to serve the case when the last field in
+ the buffer is compared using uint3korr (e.g. a Field_newdate field)
+ */
+ if (!(group_prefix= (uchar*) alloc_root(&alloc,
+ real_prefix_len+min_max_arg_len+1)))
+ return 1;
+
+ if (key_infix_len > 0)
+ {
+ /*
+ The memory location pointed to by key_infix will be deleted soon, so
+ allocate a new buffer and copy the key_infix into it.
+ */
+ uchar *tmp_key_infix= (uchar*) alloc_root(&alloc, key_infix_len);
+ if (!tmp_key_infix)
+ return 1;
+ memcpy(tmp_key_infix, this->key_infix, key_infix_len);
+ this->key_infix= tmp_key_infix;
+ }
+
+ if (min_max_arg_part)
+ {
+ if (my_init_dynamic_array(&min_max_ranges, sizeof(QUICK_RANGE*), 16, 16,
+ MYF(MY_THREAD_SPECIFIC)))
+ return 1;
+
+ if (have_min)
+ {
+ if (!(min_functions= new List<Item_sum>))
+ return 1;
+ }
+ else
+ min_functions= NULL;
+ if (have_max)
+ {
+ if (!(max_functions= new List<Item_sum>))
+ return 1;
+ }
+ else
+ max_functions= NULL;
+
+ Item_sum *min_max_item;
+ Item_sum **func_ptr= join->sum_funcs;
+ while ((min_max_item= *(func_ptr++)))
+ {
+ if (have_min && (min_max_item->sum_func() == Item_sum::MIN_FUNC))
+ min_functions->push_back(min_max_item);
+ else if (have_max && (min_max_item->sum_func() == Item_sum::MAX_FUNC))
+ max_functions->push_back(min_max_item);
+ }
+
+ if (have_min)
+ {
+ if (!(min_functions_it= new List_iterator<Item_sum>(*min_functions)))
+ return 1;
+ }
+
+ if (have_max)
+ {
+ if (!(max_functions_it= new List_iterator<Item_sum>(*max_functions)))
+ return 1;
+ }
+ }
+ else
+ min_max_ranges.elements= 0;
+
+ return 0;
+}
+
+
+QUICK_GROUP_MIN_MAX_SELECT::~QUICK_GROUP_MIN_MAX_SELECT()
+{
+ DBUG_ENTER("QUICK_GROUP_MIN_MAX_SELECT::~QUICK_GROUP_MIN_MAX_SELECT");
+ if (file->inited != handler::NONE)
+ {
+ DBUG_ASSERT(file == head->file);
+ if (doing_key_read)
+ head->disable_keyread();
+ /*
+ There may be a code path when the same table was first accessed by index,
+ then the index is closed, and the table is scanned (order by + loose scan).
+ */
+ file->ha_index_or_rnd_end();
+ }
+ if (min_max_arg_part)
+ delete_dynamic(&min_max_ranges);
+ free_root(&alloc,MYF(0));
+ delete min_functions_it;
+ delete max_functions_it;
+ delete quick_prefix_select;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Eventually create and add a new quick range object.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::add_range()
+ sel_range Range object from which a
+
+ NOTES
+ Construct a new QUICK_RANGE object from a SEL_ARG object, and
+ add it to the array min_max_ranges. If sel_arg is an infinite
+ range, e.g. (x < 5 or x > 4), then skip it and do not construct
+ a quick range.
+
+ RETURN
+ FALSE on success
+ TRUE otherwise
+*/
+
+bool QUICK_GROUP_MIN_MAX_SELECT::add_range(SEL_ARG *sel_range)
+{
+ QUICK_RANGE *range;
+ uint range_flag= sel_range->min_flag | sel_range->max_flag;
+
+ /* Skip (-inf,+inf) ranges, e.g. (x < 5 or x > 4). */
+ if ((range_flag & NO_MIN_RANGE) && (range_flag & NO_MAX_RANGE))
+ return FALSE;
+
+ if (!(sel_range->min_flag & NO_MIN_RANGE) &&
+ !(sel_range->max_flag & NO_MAX_RANGE))
+ {
+ if (sel_range->maybe_null &&
+ sel_range->min_value[0] && sel_range->max_value[0])
+ range_flag|= NULL_RANGE; /* IS NULL condition */
+ else if (memcmp(sel_range->min_value, sel_range->max_value,
+ min_max_arg_len) == 0)
+ range_flag|= EQ_RANGE; /* equality condition */
+ }
+ range= new QUICK_RANGE(sel_range->min_value, min_max_arg_len,
+ make_keypart_map(sel_range->part),
+ sel_range->max_value, min_max_arg_len,
+ make_keypart_map(sel_range->part),
+ range_flag);
+ if (!range)
+ return TRUE;
+ if (insert_dynamic(&min_max_ranges, (uchar*)&range))
+ return TRUE;
+ return FALSE;
+}
+
+
+/*
+ Opens the ranges if there are more conditions in quick_prefix_select than
+ the ones used for jumping through the prefixes.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::adjust_prefix_ranges()
+
+ NOTES
+ quick_prefix_select is made over the conditions on the whole key.
+ It defines a number of ranges of length x.
+ However when jumping through the prefixes we use only the the first
+ few most significant keyparts in the range key. However if there
+ are more keyparts to follow the ones we are using we must make the
+ condition on the key inclusive (because x < "ab" means
+ x[0] < 'a' OR (x[0] == 'a' AND x[1] < 'b').
+ To achive the above we must turn off the NEAR_MIN/NEAR_MAX
+*/
+void QUICK_GROUP_MIN_MAX_SELECT::adjust_prefix_ranges ()
+{
+ if (quick_prefix_select &&
+ group_prefix_len < quick_prefix_select->max_used_key_length)
+ {
+ DYNAMIC_ARRAY *arr;
+ uint inx;
+
+ for (inx= 0, arr= &quick_prefix_select->ranges; inx < arr->elements; inx++)
+ {
+ QUICK_RANGE *range;
+
+ get_dynamic(arr, (uchar*)&range, inx);
+ range->flag &= ~(NEAR_MIN | NEAR_MAX);
+ }
+ }
+}
+
+
+/*
+ Determine the total number and length of the keys that will be used for
+ index lookup.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::update_key_stat()
+
+ DESCRIPTION
+ The total length of the keys used for index lookup depends on whether
+ there are any predicates referencing the min/max argument, and/or if
+ the min/max argument field can be NULL.
+ This function does an optimistic analysis whether the search key might
+ be extended by a constant for the min/max keypart. It is 'optimistic'
+ because during actual execution it may happen that a particular range
+ is skipped, and then a shorter key will be used. However this is data
+ dependent and can't be easily estimated here.
+
+ RETURN
+ None
+*/
+
+void QUICK_GROUP_MIN_MAX_SELECT::update_key_stat()
+{
+ max_used_key_length= real_prefix_len;
+ if (min_max_ranges.elements > 0)
+ {
+ QUICK_RANGE *cur_range;
+ if (have_min)
+ { /* Check if the right-most range has a lower boundary. */
+ get_dynamic(&min_max_ranges, (uchar*)&cur_range,
+ min_max_ranges.elements - 1);
+ if (!(cur_range->flag & NO_MIN_RANGE))
+ {
+ max_used_key_length+= min_max_arg_len;
+ used_key_parts++;
+ return;
+ }
+ }
+ if (have_max)
+ { /* Check if the left-most range has an upper boundary. */
+ get_dynamic(&min_max_ranges, (uchar*)&cur_range, 0);
+ if (!(cur_range->flag & NO_MAX_RANGE))
+ {
+ max_used_key_length+= min_max_arg_len;
+ used_key_parts++;
+ return;
+ }
+ }
+ }
+ else if (have_min && min_max_arg_part &&
+ min_max_arg_part->field->real_maybe_null())
+ {
+ /*
+ If a MIN/MAX argument value is NULL, we can quickly determine
+ that we're in the beginning of the next group, because NULLs
+ are always < any other value. This allows us to quickly
+ determine the end of the current group and jump to the next
+ group (see next_min()) and thus effectively increases the
+ usable key length.
+ */
+ max_used_key_length+= min_max_arg_len;
+ used_key_parts++;
+ }
+}
+
+
+/*
+ Initialize a quick group min/max select for key retrieval.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::reset()
+
+ DESCRIPTION
+ Initialize the index chosen for access and find and store the prefix
+ of the last group. The method is expensive since it performs disk access.
+
+ RETURN
+ 0 OK
+ other Error code
+*/
+
+int QUICK_GROUP_MIN_MAX_SELECT::reset(void)
+{
+ int result;
+ DBUG_ENTER("QUICK_GROUP_MIN_MAX_SELECT::reset");
+
+ seen_first_key= FALSE;
+ if (!head->key_read)
+ {
+ doing_key_read= 1;
+ head->enable_keyread(); /* We need only the key attributes */
+ }
+ if ((result= file->ha_index_init(index,1)))
+ {
+ head->file->print_error(result, MYF(0));
+ DBUG_RETURN(result);
+ }
+ if (quick_prefix_select && quick_prefix_select->reset())
+ DBUG_RETURN(1);
+ result= file->ha_index_last(record);
+ if (result == HA_ERR_END_OF_FILE)
+ DBUG_RETURN(0);
+ /* Save the prefix of the last group. */
+ key_copy(last_prefix, record, index_info, group_prefix_len);
+
+ DBUG_RETURN(0);
+}
+
+
+
+/*
+ Get the next key containing the MIN and/or MAX key for the next group.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::get_next()
+
+ DESCRIPTION
+ The method finds the next subsequent group of records that satisfies the
+ query conditions and finds the keys that contain the MIN/MAX values for
+ the key part referenced by the MIN/MAX function(s). Once a group and its
+ MIN/MAX values are found, store these values in the Item_sum objects for
+ the MIN/MAX functions. The rest of the values in the result row are stored
+ in the Item_field::result_field of each select field. If the query does
+ not contain MIN and/or MAX functions, then the function only finds the
+ group prefix, which is a query answer itself.
+
+ NOTES
+ If both MIN and MAX are computed, then we use the fact that if there is
+ no MIN key, there can't be a MAX key as well, so we can skip looking
+ for a MAX key in this case.
+
+ RETURN
+ 0 on success
+ HA_ERR_END_OF_FILE if returned all keys
+ other if some error occurred
+*/
+
+int QUICK_GROUP_MIN_MAX_SELECT::get_next()
+{
+ int min_res= 0;
+ int max_res= 0;
+#ifdef HPUX11
+ /*
+ volatile is required by a bug in the HP compiler due to which the
+ last test of result fails.
+ */
+ volatile int result;
+#else
+ int result;
+#endif
+ int is_last_prefix= 0;
+
+ DBUG_ENTER("QUICK_GROUP_MIN_MAX_SELECT::get_next");
+
+ /*
+ Loop until a group is found that satisfies all query conditions or the last
+ group is reached.
+ */
+ do
+ {
+ result= next_prefix();
+ /*
+ Check if this is the last group prefix. Notice that at this point
+ this->record contains the current prefix in record format.
+ */
+ if (!result)
+ {
+ is_last_prefix= key_cmp(index_info->key_part, last_prefix,
+ group_prefix_len);
+ DBUG_ASSERT(is_last_prefix <= 0);
+ }
+ else
+ {
+ if (result == HA_ERR_KEY_NOT_FOUND)
+ continue;
+ break;
+ }
+
+ if (have_min)
+ {
+ min_res= next_min();
+ if (min_res == 0)
+ update_min_result();
+ }
+ /* If there is no MIN in the group, there is no MAX either. */
+ if ((have_max && !have_min) ||
+ (have_max && have_min && (min_res == 0)))
+ {
+ max_res= next_max();
+ if (max_res == 0)
+ update_max_result();
+ /* If a MIN was found, a MAX must have been found as well. */
+ DBUG_ASSERT((have_max && !have_min) ||
+ (have_max && have_min && (max_res == 0)));
+ }
+ /*
+ If this is just a GROUP BY or DISTINCT without MIN or MAX and there
+ are equality predicates for the key parts after the group, find the
+ first sub-group with the extended prefix.
+ */
+ if (!have_min && !have_max && key_infix_len > 0)
+ result= file->ha_index_read_map(record, group_prefix,
+ make_prev_keypart_map(real_key_parts),
+ HA_READ_KEY_EXACT);
+
+ result= have_min ? min_res : have_max ? max_res : result;
+ } while ((result == HA_ERR_KEY_NOT_FOUND || result == HA_ERR_END_OF_FILE) &&
+ is_last_prefix != 0);
+
+ if (result == HA_ERR_KEY_NOT_FOUND)
+ result= HA_ERR_END_OF_FILE;
+
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Retrieve the minimal key in the next group.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::next_min()
+
+ DESCRIPTION
+ Find the minimal key within this group such that the key satisfies the query
+ conditions and NULL semantics. The found key is loaded into this->record.
+
+ IMPLEMENTATION
+ Depending on the values of min_max_ranges.elements, key_infix_len, and
+ whether there is a NULL in the MIN field, this function may directly
+ return without any data access. In this case we use the key loaded into
+ this->record by the call to this->next_prefix() just before this call.
+
+ RETURN
+ 0 on success
+ HA_ERR_KEY_NOT_FOUND if no MIN key was found that fulfills all conditions.
+ HA_ERR_END_OF_FILE - "" -
+ other if some error occurred
+*/
+
+int QUICK_GROUP_MIN_MAX_SELECT::next_min()
+{
+ int result= 0;
+ DBUG_ENTER("QUICK_GROUP_MIN_MAX_SELECT::next_min");
+
+ /* Find the MIN key using the eventually extended group prefix. */
+ if (min_max_ranges.elements > 0)
+ {
+ if ((result= next_min_in_range()))
+ DBUG_RETURN(result);
+ }
+ else
+ {
+ /* Apply the constant equality conditions to the non-group select fields */
+ if (key_infix_len > 0)
+ {
+ if ((result=
+ file->ha_index_read_map(record, group_prefix,
+ make_prev_keypart_map(real_key_parts),
+ HA_READ_KEY_EXACT)))
+ DBUG_RETURN(result);
+ }
+
+ /*
+ If the min/max argument field is NULL, skip subsequent rows in the same
+ group with NULL in it. Notice that:
+ - if the first row in a group doesn't have a NULL in the field, no row
+ in the same group has (because NULL < any other value),
+ - min_max_arg_part->field->ptr points to some place in 'record'.
+ */
+ if (min_max_arg_part && min_max_arg_part->field->is_null())
+ {
+ uchar *tmp_key_buff= (uchar*)my_alloca(max_used_key_length);
+ /* Find the first subsequent record without NULL in the MIN/MAX field. */
+ key_copy(tmp_key_buff, record, index_info, max_used_key_length);
+ result= file->ha_index_read_map(record, tmp_key_buff,
+ make_keypart_map(real_key_parts),
+ HA_READ_AFTER_KEY);
+ /*
+ Check if the new record belongs to the current group by comparing its
+ prefix with the group's prefix. If it is from the next group, then the
+ whole group has NULLs in the MIN/MAX field, so use the first record in
+ the group as a result.
+ TODO:
+ It is possible to reuse this new record as the result candidate for the
+ next call to next_min(), and to save one lookup in the next call. For
+ this add a new member 'this->next_group_prefix'.
+ */
+ if (!result)
+ {
+ if (key_cmp(index_info->key_part, group_prefix, real_prefix_len))
+ key_restore(record, tmp_key_buff, index_info, 0);
+ }
+ else if (result == HA_ERR_KEY_NOT_FOUND || result == HA_ERR_END_OF_FILE)
+ result= 0; /* There is a result in any case. */
+ my_afree(tmp_key_buff);
+ }
+ }
+
+ /*
+ If the MIN attribute is non-nullable, this->record already contains the
+ MIN key in the group, so just return.
+ */
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Retrieve the maximal key in the next group.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::next_max()
+
+ DESCRIPTION
+ Lookup the maximal key of the group, and store it into this->record.
+
+ RETURN
+ 0 on success
+ HA_ERR_KEY_NOT_FOUND if no MAX key was found that fulfills all conditions.
+ HA_ERR_END_OF_FILE - "" -
+ other if some error occurred
+*/
+
+int QUICK_GROUP_MIN_MAX_SELECT::next_max()
+{
+ int result;
+
+ DBUG_ENTER("QUICK_GROUP_MIN_MAX_SELECT::next_max");
+
+ /* Get the last key in the (possibly extended) group. */
+ if (min_max_ranges.elements > 0)
+ result= next_max_in_range();
+ else
+ result= file->ha_index_read_map(record, group_prefix,
+ make_prev_keypart_map(real_key_parts),
+ HA_READ_PREFIX_LAST);
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Find the next different key value by skiping all the rows with the same key
+ value.
+
+ Implements a specialized loose index access method for queries
+ containing aggregate functions with distinct of the form:
+ SELECT [SUM|COUNT|AVG](DISTINCT a,...) FROM t
+ This method comes to replace the index scan + Unique class
+ (distinct selection) for loose index scan that visits all the rows of a
+ covering index instead of jumping in the begining of each group.
+ TODO: Placeholder function. To be replaced by a handler API call
+
+ @param is_index_scan hint to use index scan instead of random index read
+ to find the next different value.
+ @param file table handler
+ @param key_part group key to compare
+ @param record row data
+ @param group_prefix current key prefix data
+ @param group_prefix_len length of the current key prefix data
+ @param group_key_parts number of the current key prefix columns
+ @return status
+ @retval 0 success
+ @retval !0 failure
+*/
+
+static int index_next_different (bool is_index_scan, handler *file,
+ KEY_PART_INFO *key_part, uchar * record,
+ const uchar * group_prefix,
+ uint group_prefix_len,
+ uint group_key_parts)
+{
+ if (is_index_scan)
+ {
+ int result= 0;
+
+ while (!key_cmp (key_part, group_prefix, group_prefix_len))
+ {
+ result= file->ha_index_next(record);
+ if (result)
+ return(result);
+ }
+ return result;
+ }
+ else
+ return file->ha_index_read_map(record, group_prefix,
+ make_prev_keypart_map(group_key_parts),
+ HA_READ_AFTER_KEY);
+}
+
+
+/*
+ Determine the prefix of the next group.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::next_prefix()
+
+ DESCRIPTION
+ Determine the prefix of the next group that satisfies the query conditions.
+ If there is a range condition referencing the group attributes, use a
+ QUICK_RANGE_SELECT object to retrieve the *first* key that satisfies the
+ condition. If there is a key infix of constants, append this infix
+ immediately after the group attributes. The possibly extended prefix is
+ stored in this->group_prefix. The first key of the found group is stored in
+ this->record, on which relies this->next_min().
+
+ RETURN
+ 0 on success
+ HA_ERR_KEY_NOT_FOUND if there is no key with the formed prefix
+ HA_ERR_END_OF_FILE if there are no more keys
+ other if some error occurred
+*/
+int QUICK_GROUP_MIN_MAX_SELECT::next_prefix()
+{
+ int result;
+ DBUG_ENTER("QUICK_GROUP_MIN_MAX_SELECT::next_prefix");
+
+ if (quick_prefix_select)
+ {
+ uchar *cur_prefix= seen_first_key ? group_prefix : NULL;
+ if ((result= quick_prefix_select->get_next_prefix(group_prefix_len,
+ group_key_parts,
+ cur_prefix)))
+ DBUG_RETURN(result);
+ seen_first_key= TRUE;
+ }
+ else
+ {
+ if (!seen_first_key)
+ {
+ result= file->ha_index_first(record);
+ if (result)
+ DBUG_RETURN(result);
+ seen_first_key= TRUE;
+ }
+ else
+ {
+ /* Load the first key in this group into record. */
+ result= index_next_different (is_index_scan, file, index_info->key_part,
+ record, group_prefix, group_prefix_len,
+ group_key_parts);
+ if (result)
+ DBUG_RETURN(result);
+ }
+ }
+
+ /* Save the prefix of this group for subsequent calls. */
+ key_copy(group_prefix, record, index_info, group_prefix_len);
+ /* Append key_infix to group_prefix. */
+ if (key_infix_len > 0)
+ memcpy(group_prefix + group_prefix_len,
+ key_infix, key_infix_len);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Find the minimal key in a group that satisfies some range conditions for the
+ min/max argument field.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::next_min_in_range()
+
+ DESCRIPTION
+ Given the sequence of ranges min_max_ranges, find the minimal key that is
+ in the left-most possible range. If there is no such key, then the current
+ group does not have a MIN key that satisfies the WHERE clause. If a key is
+ found, its value is stored in this->record.
+
+ RETURN
+ 0 on success
+ HA_ERR_KEY_NOT_FOUND if there is no key with the given prefix in any of
+ the ranges
+ HA_ERR_END_OF_FILE - "" -
+ other if some error
+*/
+
+int QUICK_GROUP_MIN_MAX_SELECT::next_min_in_range()
+{
+ ha_rkey_function find_flag;
+ key_part_map keypart_map;
+ QUICK_RANGE *cur_range;
+ bool found_null= FALSE;
+ int result= HA_ERR_KEY_NOT_FOUND;
+
+ DBUG_ASSERT(min_max_ranges.elements > 0);
+
+ for (uint range_idx= 0; range_idx < min_max_ranges.elements; range_idx++)
+ { /* Search from the left-most range to the right. */
+ get_dynamic(&min_max_ranges, (uchar*)&cur_range, range_idx);
+
+ /*
+ If the current value for the min/max argument is bigger than the right
+ boundary of cur_range, there is no need to check this range.
+ */
+ if (range_idx != 0 && !(cur_range->flag & NO_MAX_RANGE) &&
+ (key_cmp(min_max_arg_part, (const uchar*) cur_range->max_key,
+ min_max_arg_len) == 1))
+ continue;
+
+ if (cur_range->flag & NO_MIN_RANGE)
+ {
+ keypart_map= make_prev_keypart_map(real_key_parts);
+ find_flag= HA_READ_KEY_EXACT;
+ }
+ else
+ {
+ /* Extend the search key with the lower boundary for this range. */
+ memcpy(group_prefix + real_prefix_len, cur_range->min_key,
+ cur_range->min_length);
+ keypart_map= make_keypart_map(real_key_parts);
+ find_flag= (cur_range->flag & (EQ_RANGE | NULL_RANGE)) ?
+ HA_READ_KEY_EXACT : (cur_range->flag & NEAR_MIN) ?
+ HA_READ_AFTER_KEY : HA_READ_KEY_OR_NEXT;
+ }
+
+ result= file->ha_index_read_map(record, group_prefix, keypart_map,
+ find_flag);
+ if (result)
+ {
+ if ((result == HA_ERR_KEY_NOT_FOUND || result == HA_ERR_END_OF_FILE) &&
+ (cur_range->flag & (EQ_RANGE | NULL_RANGE)))
+ continue; /* Check the next range. */
+
+ /*
+ In all other cases (HA_ERR_*, HA_READ_KEY_EXACT with NO_MIN_RANGE,
+ HA_READ_AFTER_KEY, HA_READ_KEY_OR_NEXT) if the lookup failed for this
+ range, it can't succeed for any other subsequent range.
+ */
+ break;
+ }
+
+ /* A key was found. */
+ if (cur_range->flag & EQ_RANGE)
+ break; /* No need to perform the checks below for equal keys. */
+
+ if (cur_range->flag & NULL_RANGE)
+ {
+ /*
+ Remember this key, and continue looking for a non-NULL key that
+ satisfies some other condition.
+ */
+ memcpy(tmp_record, record, head->s->rec_buff_length);
+ found_null= TRUE;
+ continue;
+ }
+
+ /* Check if record belongs to the current group. */
+ if (key_cmp(index_info->key_part, group_prefix, real_prefix_len))
+ {
+ result= HA_ERR_KEY_NOT_FOUND;
+ continue;
+ }
+
+ /* If there is an upper limit, check if the found key is in the range. */
+ if ( !(cur_range->flag & NO_MAX_RANGE) )
+ {
+ /* Compose the MAX key for the range. */
+ uchar *max_key= (uchar*) my_alloca(real_prefix_len + min_max_arg_len);
+ memcpy(max_key, group_prefix, real_prefix_len);
+ memcpy(max_key + real_prefix_len, cur_range->max_key,
+ cur_range->max_length);
+ /* Compare the found key with max_key. */
+ int cmp_res= key_cmp(index_info->key_part, max_key,
+ real_prefix_len + min_max_arg_len);
+ my_afree(max_key);
+ /*
+ The key is outside of the range if:
+ the interval is open and the key is equal to the maximum boundry
+ or
+ the key is greater than the maximum
+ */
+ if (((cur_range->flag & NEAR_MAX) && cmp_res == 0) ||
+ cmp_res > 0)
+ {
+ result= HA_ERR_KEY_NOT_FOUND;
+ continue;
+ }
+ }
+ /* If we got to this point, the current key qualifies as MIN. */
+ DBUG_ASSERT(result == 0);
+ break;
+ }
+ /*
+ If there was a key with NULL in the MIN/MAX field, and there was no other
+ key without NULL from the same group that satisfies some other condition,
+ then use the key with the NULL.
+ */
+ if (found_null && result)
+ {
+ memcpy(record, tmp_record, head->s->rec_buff_length);
+ result= 0;
+ }
+ return result;
+}
+
+
+/*
+ Find the maximal key in a group that satisfies some range conditions for the
+ min/max argument field.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::next_max_in_range()
+
+ DESCRIPTION
+ Given the sequence of ranges min_max_ranges, find the maximal key that is
+ in the right-most possible range. If there is no such key, then the current
+ group does not have a MAX key that satisfies the WHERE clause. If a key is
+ found, its value is stored in this->record.
+
+ RETURN
+ 0 on success
+ HA_ERR_KEY_NOT_FOUND if there is no key with the given prefix in any of
+ the ranges
+ HA_ERR_END_OF_FILE - "" -
+ other if some error
+*/
+
+int QUICK_GROUP_MIN_MAX_SELECT::next_max_in_range()
+{
+ ha_rkey_function find_flag;
+ key_part_map keypart_map;
+ QUICK_RANGE *cur_range;
+ int result;
+
+ DBUG_ASSERT(min_max_ranges.elements > 0);
+
+ for (uint range_idx= min_max_ranges.elements; range_idx > 0; range_idx--)
+ { /* Search from the right-most range to the left. */
+ get_dynamic(&min_max_ranges, (uchar*)&cur_range, range_idx - 1);
+
+ /*
+ If the current value for the min/max argument is smaller than the left
+ boundary of cur_range, there is no need to check this range.
+ */
+ if (range_idx != min_max_ranges.elements &&
+ !(cur_range->flag & NO_MIN_RANGE) &&
+ (key_cmp(min_max_arg_part, (const uchar*) cur_range->min_key,
+ min_max_arg_len) == -1))
+ continue;
+
+ if (cur_range->flag & NO_MAX_RANGE)
+ {
+ keypart_map= make_prev_keypart_map(real_key_parts);
+ find_flag= HA_READ_PREFIX_LAST;
+ }
+ else
+ {
+ /* Extend the search key with the upper boundary for this range. */
+ memcpy(group_prefix + real_prefix_len, cur_range->max_key,
+ cur_range->max_length);
+ keypart_map= make_keypart_map(real_key_parts);
+ find_flag= (cur_range->flag & EQ_RANGE) ?
+ HA_READ_KEY_EXACT : (cur_range->flag & NEAR_MAX) ?
+ HA_READ_BEFORE_KEY : HA_READ_PREFIX_LAST_OR_PREV;
+ }
+
+ result= file->ha_index_read_map(record, group_prefix, keypart_map,
+ find_flag);
+
+ if (result)
+ {
+ if ((result == HA_ERR_KEY_NOT_FOUND || result == HA_ERR_END_OF_FILE) &&
+ (cur_range->flag & EQ_RANGE))
+ continue; /* Check the next range. */
+
+ /*
+ In no key was found with this upper bound, there certainly are no keys
+ in the ranges to the left.
+ */
+ return result;
+ }
+ /* A key was found. */
+ if (cur_range->flag & EQ_RANGE)
+ return 0; /* No need to perform the checks below for equal keys. */
+
+ /* Check if record belongs to the current group. */
+ if (key_cmp(index_info->key_part, group_prefix, real_prefix_len))
+ continue; // Row not found
+
+ /* If there is a lower limit, check if the found key is in the range. */
+ if ( !(cur_range->flag & NO_MIN_RANGE) )
+ {
+ /* Compose the MIN key for the range. */
+ uchar *min_key= (uchar*) my_alloca(real_prefix_len + min_max_arg_len);
+ memcpy(min_key, group_prefix, real_prefix_len);
+ memcpy(min_key + real_prefix_len, cur_range->min_key,
+ cur_range->min_length);
+ /* Compare the found key with min_key. */
+ int cmp_res= key_cmp(index_info->key_part, min_key,
+ real_prefix_len + min_max_arg_len);
+ my_afree(min_key);
+ /*
+ The key is outside of the range if:
+ the interval is open and the key is equal to the minimum boundry
+ or
+ the key is less than the minimum
+ */
+ if (((cur_range->flag & NEAR_MIN) && cmp_res == 0) ||
+ cmp_res < 0)
+ continue;
+ }
+ /* If we got to this point, the current key qualifies as MAX. */
+ return result;
+ }
+ return HA_ERR_KEY_NOT_FOUND;
+}
+
+
+/*
+ Update all MIN function results with the newly found value.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::update_min_result()
+
+ DESCRIPTION
+ The method iterates through all MIN functions and updates the result value
+ of each function by calling Item_sum::reset(), which in turn picks the new
+ result value from this->head->record[0], previously updated by
+ next_min(). The updated value is stored in a member variable of each of the
+ Item_sum objects, depending on the value type.
+
+ IMPLEMENTATION
+ The update must be done separately for MIN and MAX, immediately after
+ next_min() was called and before next_max() is called, because both MIN and
+ MAX take their result value from the same buffer this->head->record[0]
+ (i.e. this->record).
+
+ RETURN
+ None
+*/
+
+void QUICK_GROUP_MIN_MAX_SELECT::update_min_result()
+{
+ Item_sum *min_func;
+
+ min_functions_it->rewind();
+ while ((min_func= (*min_functions_it)++))
+ min_func->reset_and_add();
+}
+
+
+/*
+ Update all MAX function results with the newly found value.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::update_max_result()
+
+ DESCRIPTION
+ The method iterates through all MAX functions and updates the result value
+ of each function by calling Item_sum::reset(), which in turn picks the new
+ result value from this->head->record[0], previously updated by
+ next_max(). The updated value is stored in a member variable of each of the
+ Item_sum objects, depending on the value type.
+
+ IMPLEMENTATION
+ The update must be done separately for MIN and MAX, immediately after
+ next_max() was called, because both MIN and MAX take their result value
+ from the same buffer this->head->record[0] (i.e. this->record).
+
+ RETURN
+ None
+*/
+
+void QUICK_GROUP_MIN_MAX_SELECT::update_max_result()
+{
+ Item_sum *max_func;
+
+ max_functions_it->rewind();
+ while ((max_func= (*max_functions_it)++))
+ max_func->reset_and_add();
+}
+
+
+/*
+ Append comma-separated list of keys this quick select uses to key_names;
+ append comma-separated list of corresponding used lengths to used_lengths.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::add_keys_and_lengths()
+ key_names [out] Names of used indexes
+ used_lengths [out] Corresponding lengths of the index names
+
+ DESCRIPTION
+ This method is used by select_describe to extract the names of the
+ indexes used by a quick select.
+
+*/
+
+void QUICK_GROUP_MIN_MAX_SELECT::add_keys_and_lengths(String *key_names,
+ String *used_lengths)
+{
+ bool first= TRUE;
+
+ add_key_and_length(key_names, used_lengths, &first);
+}
+
+
+#ifndef DBUG_OFF
+
+static void print_sel_tree(PARAM *param, SEL_TREE *tree, key_map *tree_map,
+ const char *msg)
+{
+ SEL_ARG **key,**end;
+ int idx;
+ char buff[1024];
+ DBUG_ENTER("print_sel_tree");
+
+ String tmp(buff,sizeof(buff),&my_charset_bin);
+ tmp.length(0);
+ for (idx= 0,key=tree->keys, end=key+param->keys ;
+ key != end ;
+ key++,idx++)
+ {
+ if (tree_map->is_set(idx))
+ {
+ uint keynr= param->real_keynr[idx];
+ if (tmp.length())
+ tmp.append(',');
+ tmp.append(param->table->key_info[keynr].name);
+ }
+ }
+ if (!tmp.length())
+ tmp.append(STRING_WITH_LEN("(empty)"));
+
+ DBUG_PRINT("info", ("SEL_TREE: 0x%lx (%s) scans: %s", (long) tree, msg,
+ tmp.c_ptr_safe()));
+
+ DBUG_VOID_RETURN;
+}
+
+
+static void print_ror_scans_arr(TABLE *table, const char *msg,
+ struct st_ror_scan_info **start,
+ struct st_ror_scan_info **end)
+{
+ DBUG_ENTER("print_ror_scans_arr");
+
+ char buff[1024];
+ String tmp(buff,sizeof(buff),&my_charset_bin);
+ tmp.length(0);
+ for (;start != end; start++)
+ {
+ if (tmp.length())
+ tmp.append(',');
+ tmp.append(table->key_info[(*start)->keynr].name);
+ }
+ if (!tmp.length())
+ tmp.append(STRING_WITH_LEN("(empty)"));
+ DBUG_PRINT("info", ("ROR key scans (%s): %s", msg, tmp.c_ptr()));
+ DBUG_VOID_RETURN;
+}
+
+
+/*****************************************************************************
+** Print a quick range for debugging
+** TODO:
+** This should be changed to use a String to store each row instead
+** of locking the DEBUG stream !
+*****************************************************************************/
+
+static void
+print_key(KEY_PART *key_part, const uchar *key, uint used_length)
+{
+ char buff[1024];
+ const uchar *key_end= key+used_length;
+ uint store_length;
+ TABLE *table= key_part->field->table;
+ my_bitmap_map *old_sets[2];
+
+ dbug_tmp_use_all_columns(table, old_sets, table->read_set, table->write_set);
+
+ for (; key < key_end; key+=store_length, key_part++)
+ {
+ String tmp(buff,sizeof(buff),&my_charset_bin);
+ Field *field= key_part->field;
+ store_length= key_part->store_length;
+
+ if (field->real_maybe_null())
+ {
+ if (*key)
+ {
+ fwrite("NULL",sizeof(char),4,DBUG_FILE);
+ continue;
+ }
+ key++; // Skip null byte
+ store_length--;
+ }
+ field->set_key_image(key, key_part->length);
+ if (field->type() == MYSQL_TYPE_BIT)
+ (void) field->val_int_as_str(&tmp, 1);
+ else
+ field->val_str(&tmp);
+ fwrite(tmp.ptr(),sizeof(char),tmp.length(),DBUG_FILE);
+ if (key+store_length < key_end)
+ fputc('/',DBUG_FILE);
+ }
+ dbug_tmp_restore_column_maps(table->read_set, table->write_set, old_sets);
+}
+
+
+static void print_quick(QUICK_SELECT_I *quick, const key_map *needed_reg)
+{
+ char buf[MAX_KEY/8+1];
+ TABLE *table;
+ my_bitmap_map *old_sets[2];
+ DBUG_ENTER("print_quick");
+ if (!quick)
+ DBUG_VOID_RETURN;
+ DBUG_LOCK_FILE;
+
+ table= quick->head;
+ dbug_tmp_use_all_columns(table, old_sets, table->read_set, table->write_set);
+ quick->dbug_dump(0, TRUE);
+ dbug_tmp_restore_column_maps(table->read_set, table->write_set, old_sets);
+
+ fprintf(DBUG_FILE,"other_keys: 0x%s:\n", needed_reg->print(buf));
+
+ DBUG_UNLOCK_FILE;
+ DBUG_VOID_RETURN;
+}
+
+
+void QUICK_RANGE_SELECT::dbug_dump(int indent, bool verbose)
+{
+ /* purecov: begin inspected */
+ fprintf(DBUG_FILE, "%*squick range select, key %s, length: %d\n",
+ indent, "", head->key_info[index].name, max_used_key_length);
+
+ if (verbose)
+ {
+ QUICK_RANGE *range;
+ QUICK_RANGE **pr= (QUICK_RANGE**)ranges.buffer;
+ QUICK_RANGE **end_range= pr + ranges.elements;
+ for (; pr != end_range; ++pr)
+ {
+ fprintf(DBUG_FILE, "%*s", indent + 2, "");
+ range= *pr;
+ if (!(range->flag & NO_MIN_RANGE))
+ {
+ print_key(key_parts, range->min_key, range->min_length);
+ if (range->flag & NEAR_MIN)
+ fputs(" < ",DBUG_FILE);
+ else
+ fputs(" <= ",DBUG_FILE);
+ }
+ fputs("X",DBUG_FILE);
+
+ if (!(range->flag & NO_MAX_RANGE))
+ {
+ if (range->flag & NEAR_MAX)
+ fputs(" < ",DBUG_FILE);
+ else
+ fputs(" <= ",DBUG_FILE);
+ print_key(key_parts, range->max_key, range->max_length);
+ }
+ fputs("\n",DBUG_FILE);
+ }
+ }
+ /* purecov: end */
+}
+
+void QUICK_INDEX_SORT_SELECT::dbug_dump(int indent, bool verbose)
+{
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+ QUICK_RANGE_SELECT *quick;
+ fprintf(DBUG_FILE, "%*squick index_merge select\n", indent, "");
+ fprintf(DBUG_FILE, "%*smerged scans {\n", indent, "");
+ while ((quick= it++))
+ quick->dbug_dump(indent+2, verbose);
+ if (pk_quick_select)
+ {
+ fprintf(DBUG_FILE, "%*sclustered PK quick:\n", indent, "");
+ pk_quick_select->dbug_dump(indent+2, verbose);
+ }
+ fprintf(DBUG_FILE, "%*s}\n", indent, "");
+}
+
+void QUICK_ROR_INTERSECT_SELECT::dbug_dump(int indent, bool verbose)
+{
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
+ QUICK_SELECT_WITH_RECORD *qr;
+ fprintf(DBUG_FILE, "%*squick ROR-intersect select, %scovering\n",
+ indent, "", need_to_fetch_row? "":"non-");
+ fprintf(DBUG_FILE, "%*smerged scans {\n", indent, "");
+ while ((qr= it++))
+ qr->quick->dbug_dump(indent+2, verbose);
+ if (cpk_quick)
+ {
+ fprintf(DBUG_FILE, "%*sclustered PK quick:\n", indent, "");
+ cpk_quick->dbug_dump(indent+2, verbose);
+ }
+ fprintf(DBUG_FILE, "%*s}\n", indent, "");
+}
+
+void QUICK_ROR_UNION_SELECT::dbug_dump(int indent, bool verbose)
+{
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+ QUICK_SELECT_I *quick;
+ fprintf(DBUG_FILE, "%*squick ROR-union select\n", indent, "");
+ fprintf(DBUG_FILE, "%*smerged scans {\n", indent, "");
+ while ((quick= it++))
+ quick->dbug_dump(indent+2, verbose);
+ fprintf(DBUG_FILE, "%*s}\n", indent, "");
+}
+
+
+/*
+ Print quick select information to DBUG_FILE.
+
+ SYNOPSIS
+ QUICK_GROUP_MIN_MAX_SELECT::dbug_dump()
+ indent Indentation offset
+ verbose If TRUE show more detailed output.
+
+ DESCRIPTION
+ Print the contents of this quick select to DBUG_FILE. The method also
+ calls dbug_dump() for the used quick select if any.
+
+ IMPLEMENTATION
+ Caller is responsible for locking DBUG_FILE before this call and unlocking
+ it afterwards.
+
+ RETURN
+ None
+*/
+
+void QUICK_GROUP_MIN_MAX_SELECT::dbug_dump(int indent, bool verbose)
+{
+ fprintf(DBUG_FILE,
+ "%*squick_group_min_max_select: index %s (%d), length: %d\n",
+ indent, "", index_info->name, index, max_used_key_length);
+ if (key_infix_len > 0)
+ {
+ fprintf(DBUG_FILE, "%*susing key_infix with length %d:\n",
+ indent, "", key_infix_len);
+ }
+ if (quick_prefix_select)
+ {
+ fprintf(DBUG_FILE, "%*susing quick_range_select:\n", indent, "");
+ quick_prefix_select->dbug_dump(indent + 2, verbose);
+ }
+ if (min_max_ranges.elements > 0)
+ {
+ fprintf(DBUG_FILE, "%*susing %d quick_ranges for MIN/MAX:\n",
+ indent, "", min_max_ranges.elements);
+ }
+}
+
+
+#endif /* !DBUG_OFF */
+
diff --git a/sql/opt_range.h b/sql/opt_range.h
new file mode 100644
index 00000000000..54b15826d1b
--- /dev/null
+++ b/sql/opt_range.h
@@ -0,0 +1,1070 @@
+/*
+ Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+
+/* classes to use when handling where clause */
+
+#ifndef _opt_range_h
+#define _opt_range_h
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "thr_malloc.h" /* sql_memdup */
+#include "records.h" /* READ_RECORD */
+#include "queues.h" /* QUEUE */
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h" /* Item */
+
+class JOIN;
+class Item_sum;
+
+typedef struct st_key_part {
+ uint16 key,part;
+ /* See KEY_PART_INFO for meaning of the next two: */
+ uint16 store_length, length;
+ uint8 null_bit;
+ /*
+ Keypart flags (0 when this structure is used by partition pruning code
+ for fake partitioning index description)
+ */
+ uint8 flag;
+ Field *field;
+ Field::imagetype image_type;
+} KEY_PART;
+
+class Explain_quick_select;
+/*
+ A "MIN_TUPLE < tbl.key_tuple < MAX_TUPLE" interval.
+
+ One of endpoints may be absent. 'flags' member has flags which tell whether
+ the endpoints are '<' or '<='.
+*/
+class QUICK_RANGE :public Sql_alloc {
+ public:
+ uchar *min_key,*max_key;
+ uint16 min_length,max_length,flag;
+ key_part_map min_keypart_map, // bitmap of used keyparts in min_key
+ max_keypart_map; // bitmap of used keyparts in max_key
+#ifdef HAVE_valgrind
+ uint16 dummy; /* Avoid warnings on 'flag' */
+#endif
+ QUICK_RANGE(); /* Full range */
+ QUICK_RANGE(const uchar *min_key_arg, uint min_length_arg,
+ key_part_map min_keypart_map_arg,
+ const uchar *max_key_arg, uint max_length_arg,
+ key_part_map max_keypart_map_arg,
+ uint flag_arg)
+ : min_key((uchar*) sql_memdup(min_key_arg,min_length_arg+1)),
+ max_key((uchar*) sql_memdup(max_key_arg,max_length_arg+1)),
+ min_length((uint16) min_length_arg),
+ max_length((uint16) max_length_arg),
+ flag((uint16) flag_arg),
+ min_keypart_map(min_keypart_map_arg),
+ max_keypart_map(max_keypart_map_arg)
+ {
+#ifdef HAVE_valgrind
+ dummy=0;
+#endif
+ }
+
+ /**
+ Initalizes a key_range object for communication with storage engine.
+
+ This function facilitates communication with the Storage Engine API by
+ translating the minimum endpoint of the interval represented by this
+ QUICK_RANGE into an index range endpoint specifier for the engine.
+
+ @param Pointer to an uninitialized key_range C struct.
+
+ @param prefix_length The length of the search key prefix to be used for
+ lookup.
+
+ @param keypart_map A set (bitmap) of keyparts to be used.
+ */
+ void make_min_endpoint(key_range *kr, uint prefix_length,
+ key_part_map keypart_map) {
+ make_min_endpoint(kr);
+ kr->length= MY_MIN(kr->length, prefix_length);
+ kr->keypart_map&= keypart_map;
+ }
+
+ /**
+ Initalizes a key_range object for communication with storage engine.
+
+ This function facilitates communication with the Storage Engine API by
+ translating the minimum endpoint of the interval represented by this
+ QUICK_RANGE into an index range endpoint specifier for the engine.
+
+ @param Pointer to an uninitialized key_range C struct.
+ */
+ void make_min_endpoint(key_range *kr) {
+ kr->key= (const uchar*)min_key;
+ kr->length= min_length;
+ kr->keypart_map= min_keypart_map;
+ kr->flag= ((flag & NEAR_MIN) ? HA_READ_AFTER_KEY :
+ (flag & EQ_RANGE) ? HA_READ_KEY_EXACT : HA_READ_KEY_OR_NEXT);
+ }
+
+ /**
+ Initalizes a key_range object for communication with storage engine.
+
+ This function facilitates communication with the Storage Engine API by
+ translating the maximum endpoint of the interval represented by this
+ QUICK_RANGE into an index range endpoint specifier for the engine.
+
+ @param Pointer to an uninitialized key_range C struct.
+
+ @param prefix_length The length of the search key prefix to be used for
+ lookup.
+
+ @param keypart_map A set (bitmap) of keyparts to be used.
+ */
+ void make_max_endpoint(key_range *kr, uint prefix_length,
+ key_part_map keypart_map) {
+ make_max_endpoint(kr);
+ kr->length= MY_MIN(kr->length, prefix_length);
+ kr->keypart_map&= keypart_map;
+ }
+
+ /**
+ Initalizes a key_range object for communication with storage engine.
+
+ This function facilitates communication with the Storage Engine API by
+ translating the maximum endpoint of the interval represented by this
+ QUICK_RANGE into an index range endpoint specifier for the engine.
+
+ @param Pointer to an uninitialized key_range C struct.
+ */
+ void make_max_endpoint(key_range *kr) {
+ kr->key= (const uchar*)max_key;
+ kr->length= max_length;
+ kr->keypart_map= max_keypart_map;
+ /*
+ We use READ_AFTER_KEY here because if we are reading on a key
+ prefix we want to find all keys with this prefix
+ */
+ kr->flag= (flag & NEAR_MAX ? HA_READ_BEFORE_KEY : HA_READ_AFTER_KEY);
+ }
+};
+
+
+/*
+ Quick select interface.
+ This class is a parent for all QUICK_*_SELECT and FT_SELECT classes.
+
+ The usage scenario is as follows:
+ 1. Create quick select
+ quick= new QUICK_XXX_SELECT(...);
+
+ 2. Perform lightweight initialization. This can be done in 2 ways:
+ 2.a: Regular initialization
+ if (quick->init())
+ {
+ //the only valid action after failed init() call is delete
+ delete quick;
+ }
+ 2.b: Special initialization for quick selects merged by QUICK_ROR_*_SELECT
+ if (quick->init_ror_merged_scan())
+ delete quick;
+
+ 3. Perform zero, one, or more scans.
+ while (...)
+ {
+ // initialize quick select for scan. This may allocate
+ // buffers and/or prefetch rows.
+ if (quick->reset())
+ {
+ //the only valid action after failed reset() call is delete
+ delete quick;
+ //abort query
+ }
+
+ // perform the scan
+ do
+ {
+ res= quick->get_next();
+ } while (res && ...)
+ }
+
+ 4. Delete the select:
+ delete quick;
+
+ NOTE
+ quick select doesn't use Sql_alloc/MEM_ROOT allocation because "range
+ checked for each record" functionality may create/destroy
+ O(#records_in_some_table) quick selects during query execution.
+*/
+
+class QUICK_SELECT_I
+{
+public:
+ ha_rows records; /* estimate of # of records to be retrieved */
+ double read_time; /* time to perform this retrieval */
+ TABLE *head;
+ /*
+ Index this quick select uses, or MAX_KEY for quick selects
+ that use several indexes
+ */
+ uint index;
+
+ /*
+ Total length of first used_key_parts parts of the key.
+ Applicable if index!= MAX_KEY.
+ */
+ uint max_used_key_length;
+
+ /*
+ Max. number of (first) key parts this quick select uses for retrieval.
+ eg. for "(key1p1=c1 AND key1p2=c2) OR key1p1=c2" used_key_parts == 2.
+ Applicable if index!= MAX_KEY.
+
+ For QUICK_GROUP_MIN_MAX_SELECT it includes MIN/MAX argument keyparts.
+ */
+ uint used_key_parts;
+
+ QUICK_SELECT_I();
+ virtual ~QUICK_SELECT_I(){};
+
+ /*
+ Do post-constructor initialization.
+ SYNOPSIS
+ init()
+
+ init() performs initializations that should have been in constructor if
+ it was possible to return errors from constructors. The join optimizer may
+ create and then delete quick selects without retrieving any rows so init()
+ must not contain any IO or CPU intensive code.
+
+ If init() call fails the only valid action is to delete this quick select,
+ reset() and get_next() must not be called.
+
+ RETURN
+ 0 OK
+ other Error code
+ */
+ virtual int init() = 0;
+
+ /*
+ Initialize quick select for row retrieval.
+ SYNOPSIS
+ reset()
+
+ reset() should be called when it is certain that row retrieval will be
+ necessary. This call may do heavyweight initialization like buffering first
+ N records etc. If reset() call fails get_next() must not be called.
+ Note that reset() may be called several times if
+ * the quick select is executed in a subselect
+ * a JOIN buffer is used
+
+ RETURN
+ 0 OK
+ other Error code
+ */
+ virtual int reset(void) = 0;
+
+ virtual int get_next() = 0; /* get next record to retrieve */
+
+ /* Range end should be called when we have looped over the whole index */
+ virtual void range_end() {}
+
+ virtual bool reverse_sorted() = 0;
+ virtual bool unique_key_range() { return false; }
+
+ /*
+ Request that this quick select produces sorted output. Not all quick
+ selects can do it, the caller is responsible for calling this function
+ only for those quick selects that can.
+ */
+ virtual void need_sorted_output() = 0;
+ enum {
+ QS_TYPE_RANGE = 0,
+ QS_TYPE_INDEX_INTERSECT = 1,
+ QS_TYPE_INDEX_MERGE = 2,
+ QS_TYPE_RANGE_DESC = 3,
+ QS_TYPE_FULLTEXT = 4,
+ QS_TYPE_ROR_INTERSECT = 5,
+ QS_TYPE_ROR_UNION = 6,
+ QS_TYPE_GROUP_MIN_MAX = 7
+ };
+
+ /* Get type of this quick select - one of the QS_TYPE_* values */
+ virtual int get_type() = 0;
+
+ /*
+ Initialize this quick select as a merged scan inside a ROR-union or a ROR-
+ intersection scan. The caller must not additionally call init() if this
+ function is called.
+ SYNOPSIS
+ init_ror_merged_scan()
+ reuse_handler If true, the quick select may use table->handler,
+ otherwise it must create and use a separate handler
+ object.
+ RETURN
+ 0 Ok
+ other Error
+ */
+ virtual int init_ror_merged_scan(bool reuse_handler, MEM_ROOT *alloc)
+ { DBUG_ASSERT(0); return 1; }
+
+ /*
+ Save ROWID of last retrieved row in file->ref. This used in ROR-merging.
+ */
+ virtual void save_last_pos(){};
+
+ void add_key_and_length(String *key_names,
+ String *used_lengths,
+ bool *first);
+
+ /*
+ Append comma-separated list of keys this quick select uses to key_names;
+ append comma-separated list of corresponding used lengths to used_lengths.
+ This is used by select_describe.
+ */
+ virtual void add_keys_and_lengths(String *key_names,
+ String *used_lengths)=0;
+
+ void add_key_name(String *str, bool *first);
+
+ /* Save information about quick select's query plan */
+ virtual Explain_quick_select* get_explain(MEM_ROOT *alloc)= 0;
+
+ /*
+ Return 1 if any index used by this quick select
+ uses field which is marked in passed bitmap.
+ */
+ virtual bool is_keys_used(const MY_BITMAP *fields);
+
+ /**
+ Simple sanity check that the quick select has been set up
+ correctly. Function is overridden by quick selects that merge
+ indices.
+ */
+ virtual bool is_valid() { return index != MAX_KEY; };
+
+ /*
+ rowid of last row retrieved by this quick select. This is used only when
+ doing ROR-index_merge selects
+ */
+ uchar *last_rowid;
+
+ /*
+ Table record buffer used by this quick select.
+ */
+ uchar *record;
+
+ virtual void replace_handler(handler *new_file)
+ {
+ DBUG_ASSERT(0); /* Only supported in QUICK_RANGE_SELECT */
+ }
+
+#ifndef DBUG_OFF
+ /*
+ Print quick select information to DBUG_FILE. Caller is responsible
+ for locking DBUG_FILE before this call and unlocking it afterwards.
+ */
+ virtual void dbug_dump(int indent, bool verbose)= 0;
+#endif
+
+ /*
+ Returns a QUICK_SELECT with reverse order of to the index.
+ */
+ virtual QUICK_SELECT_I *make_reverse(uint used_key_parts_arg) { return NULL; }
+
+ /*
+ Add the key columns used by the quick select into table's read set.
+
+ This is used by an optimization in filesort.
+ */
+ virtual void add_used_key_part_to_set(MY_BITMAP *col_set)=0;
+};
+
+
+struct st_qsel_param;
+class PARAM;
+class SEL_ARG;
+
+
+/*
+ MRR range sequence, array<QUICK_RANGE> implementation: sequence traversal
+ context.
+*/
+typedef struct st_quick_range_seq_ctx
+{
+ QUICK_RANGE **first;
+ QUICK_RANGE **cur;
+ QUICK_RANGE **last;
+} QUICK_RANGE_SEQ_CTX;
+
+range_seq_t quick_range_seq_init(void *init_param, uint n_ranges, uint flags);
+bool quick_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range);
+
+
+/*
+ Quick select that does a range scan on a single key. The records are
+ returned in key order.
+*/
+class QUICK_RANGE_SELECT : public QUICK_SELECT_I
+{
+protected:
+ /* true if we enabled key only reads */
+ bool doing_key_read;
+ handler *file;
+
+ /* Members to deal with case when this quick select is a ROR-merged scan */
+ bool in_ror_merged_scan;
+ MY_BITMAP column_bitmap;
+ bool free_file; /* TRUE <=> this->file is "owned" by this quick select */
+
+ /* Range pointers to be used when not using MRR interface */
+ /* Members needed to use the MRR interface */
+ QUICK_RANGE_SEQ_CTX qr_traversal_ctx;
+public:
+ uint mrr_flags; /* Flags to be used with MRR interface */
+protected:
+ uint mrr_buf_size; /* copy from thd->variables.mrr_buff_size */
+ HANDLER_BUFFER *mrr_buf_desc; /* the handler buffer */
+
+ /* Info about index we're scanning */
+
+ DYNAMIC_ARRAY ranges; /* ordered array of range ptrs */
+ QUICK_RANGE **cur_range; /* current element in ranges */
+
+ QUICK_RANGE *last_range;
+
+ KEY_PART *key_parts;
+ KEY_PART_INFO *key_part_info;
+
+ bool dont_free; /* Used by QUICK_SELECT_DESC */
+
+ int cmp_next(QUICK_RANGE *range);
+ int cmp_prev(QUICK_RANGE *range);
+ bool row_in_ranges();
+public:
+ MEM_ROOT alloc;
+
+ QUICK_RANGE_SELECT(THD *thd, TABLE *table,uint index_arg,bool no_alloc,
+ MEM_ROOT *parent_alloc, bool *create_err);
+ ~QUICK_RANGE_SELECT();
+
+ void need_sorted_output();
+ int init();
+ int reset(void);
+ int get_next();
+ void range_end();
+ int get_next_prefix(uint prefix_length, uint group_key_parts,
+ uchar *cur_prefix);
+ bool reverse_sorted() { return 0; }
+ bool unique_key_range();
+ int init_ror_merged_scan(bool reuse_handler, MEM_ROOT *alloc);
+ void save_last_pos()
+ { file->position(record); }
+ int get_type() { return QS_TYPE_RANGE; }
+ void add_keys_and_lengths(String *key_names, String *used_lengths);
+ Explain_quick_select *get_explain(MEM_ROOT *alloc);
+#ifndef DBUG_OFF
+ void dbug_dump(int indent, bool verbose);
+#endif
+ virtual void replace_handler(handler *new_file) { file= new_file; }
+ QUICK_SELECT_I *make_reverse(uint used_key_parts_arg);
+
+ virtual void add_used_key_part_to_set(MY_BITMAP *col_set);
+
+private:
+ /* Default copy ctor used by QUICK_SELECT_DESC */
+ friend class TRP_ROR_INTERSECT;
+ friend
+ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
+ struct st_table_ref *ref,
+ ha_rows records);
+ friend bool get_quick_keys(PARAM *param, QUICK_RANGE_SELECT *quick,
+ KEY_PART *key, SEL_ARG *key_tree,
+ uchar *min_key, uint min_key_flag,
+ uchar *max_key, uint max_key_flag);
+ friend QUICK_RANGE_SELECT *get_quick_select(PARAM*,uint idx,
+ SEL_ARG *key_tree,
+ uint mrr_flags,
+ uint mrr_buf_size,
+ MEM_ROOT *alloc);
+ friend class QUICK_SELECT_DESC;
+ friend class QUICK_INDEX_SORT_SELECT;
+ friend class QUICK_INDEX_MERGE_SELECT;
+ friend class QUICK_ROR_INTERSECT_SELECT;
+ friend class QUICK_INDEX_INTERSECT_SELECT;
+ friend class QUICK_GROUP_MIN_MAX_SELECT;
+ friend bool quick_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range);
+ friend range_seq_t quick_range_seq_init(void *init_param,
+ uint n_ranges, uint flags);
+ friend
+ int read_keys_and_merge_scans(THD *thd, TABLE *head,
+ List<QUICK_RANGE_SELECT> quick_selects,
+ QUICK_RANGE_SELECT *pk_quick_select,
+ READ_RECORD *read_record,
+ bool intersection,
+ key_map *filtered_scans,
+ Unique **unique_ptr);
+
+};
+
+
+class QUICK_RANGE_SELECT_GEOM: public QUICK_RANGE_SELECT
+{
+public:
+ QUICK_RANGE_SELECT_GEOM(THD *thd, TABLE *table, uint index_arg,
+ bool no_alloc, MEM_ROOT *parent_alloc,
+ bool *create_err)
+ :QUICK_RANGE_SELECT(thd, table, index_arg, no_alloc, parent_alloc,
+ create_err)
+ {};
+ virtual int get_next();
+};
+
+
+/*
+ QUICK_INDEX_SORT_SELECT is the base class for the common functionality of:
+ - QUICK_INDEX_MERGE_SELECT, access based on multi-index merge/union
+ - QUICK_INDEX_INTERSECT_SELECT, access based on multi-index intersection
+
+
+ QUICK_INDEX_SORT_SELECT uses
+ * QUICK_RANGE_SELECTs to get rows
+ * Unique class
+ - to remove duplicate rows for QUICK_INDEX_MERGE_SELECT
+ - to intersect rows for QUICK_INDEX_INTERSECT_SELECT
+
+ INDEX MERGE OPTIMIZER
+ Current implementation doesn't detect all cases where index merge could
+ be used, in particular:
+
+ * index_merge+'using index' is not supported
+
+ * If WHERE part contains complex nested AND and OR conditions, some ways
+ to retrieve rows using index merge will not be considered. The choice
+ of read plan may depend on the order of conjuncts/disjuncts in WHERE
+ part of the query, see comments near imerge_list_or_list and
+ SEL_IMERGE::or_sel_tree_with_checks functions for details.
+
+ * There is no "index_merge_ref" method (but index merge on non-first
+ table in join is possible with 'range checked for each record').
+
+
+ ROW RETRIEVAL ALGORITHM
+
+ index merge/intersection uses Unique class for duplicates removal.
+ index merge/intersection takes advantage of Clustered Primary Key (CPK)
+ if the table has one.
+ The index merge/intersection algorithm consists of two phases:
+
+ Phase 1
+ (implemented by a QUICK_INDEX_MERGE_SELECT::read_keys_and_merge call):
+
+ prepare()
+ {
+ activate 'index only';
+ while(retrieve next row for non-CPK scan)
+ {
+ if (there is a CPK scan and row will be retrieved by it)
+ skip this row;
+ else
+ put its rowid into Unique;
+ }
+ deactivate 'index only';
+ }
+
+ Phase 2
+ (implemented as sequence of QUICK_INDEX_MERGE_SELECT::get_next calls):
+
+ fetch()
+ {
+ retrieve all rows from row pointers stored in Unique
+ (merging/intersecting them);
+ free Unique;
+ if (! intersection)
+ retrieve all rows for CPK scan;
+ }
+*/
+
+class QUICK_INDEX_SORT_SELECT : public QUICK_SELECT_I
+{
+protected:
+ Unique *unique;
+public:
+ QUICK_INDEX_SORT_SELECT(THD *thd, TABLE *table);
+ ~QUICK_INDEX_SORT_SELECT();
+
+ int init();
+ void need_sorted_output() { DBUG_ASSERT(0); /* Can't do it */ }
+ int reset(void);
+ bool reverse_sorted() { return false; }
+ bool unique_key_range() { return false; }
+ bool is_keys_used(const MY_BITMAP *fields);
+#ifndef DBUG_OFF
+ void dbug_dump(int indent, bool verbose);
+#endif
+ Explain_quick_select *get_explain(MEM_ROOT *alloc);
+
+ bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range);
+
+ /* range quick selects this index merge/intersect consists of */
+ List<QUICK_RANGE_SELECT> quick_selects;
+
+ /* quick select that uses clustered primary key (NULL if none) */
+ QUICK_RANGE_SELECT* pk_quick_select;
+
+ MEM_ROOT alloc;
+ THD *thd;
+ virtual bool is_valid()
+ {
+ List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
+ QUICK_RANGE_SELECT *quick;
+ bool valid= true;
+ while ((quick= it++))
+ {
+ if (!quick->is_valid())
+ {
+ valid= false;
+ break;
+ }
+ }
+ return valid;
+ }
+ virtual int read_keys_and_merge()= 0;
+ /* used to get rows collected in Unique */
+ READ_RECORD read_record;
+
+ virtual void add_used_key_part_to_set(MY_BITMAP *col_set);
+};
+
+
+
+class QUICK_INDEX_MERGE_SELECT : public QUICK_INDEX_SORT_SELECT
+{
+private:
+ /* true if this select is currently doing a clustered PK scan */
+ bool doing_pk_scan;
+protected:
+ int read_keys_and_merge();
+
+public:
+ QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table)
+ :QUICK_INDEX_SORT_SELECT(thd, table) {}
+
+ int get_next();
+ int get_type() { return QS_TYPE_INDEX_MERGE; }
+ void add_keys_and_lengths(String *key_names, String *used_lengths);
+};
+
+class QUICK_INDEX_INTERSECT_SELECT : public QUICK_INDEX_SORT_SELECT
+{
+protected:
+ int read_keys_and_merge();
+
+public:
+ QUICK_INDEX_INTERSECT_SELECT(THD *thd, TABLE *table)
+ :QUICK_INDEX_SORT_SELECT(thd, table) {}
+
+ key_map filtered_scans;
+ int get_next();
+ int get_type() { return QS_TYPE_INDEX_INTERSECT; }
+ void add_keys_and_lengths(String *key_names, String *used_lengths);
+ Explain_quick_select *get_explain(MEM_ROOT *alloc);
+};
+
+
+/*
+ Rowid-Ordered Retrieval (ROR) index intersection quick select.
+ This quick select produces intersection of row sequences returned
+ by several QUICK_RANGE_SELECTs it "merges".
+
+ All merged QUICK_RANGE_SELECTs must return rowids in rowid order.
+ QUICK_ROR_INTERSECT_SELECT will return rows in rowid order, too.
+
+ All merged quick selects retrieve {rowid, covered_fields} tuples (not full
+ table records).
+ QUICK_ROR_INTERSECT_SELECT retrieves full records if it is not being used
+ by QUICK_ROR_INTERSECT_SELECT and all merged quick selects together don't
+ cover needed all fields.
+
+ If one of the merged quick selects is a Clustered PK range scan, it is
+ used only to filter rowid sequence produced by other merged quick selects.
+*/
+
+class QUICK_ROR_INTERSECT_SELECT : public QUICK_SELECT_I
+{
+public:
+ QUICK_ROR_INTERSECT_SELECT(THD *thd, TABLE *table,
+ bool retrieve_full_rows,
+ MEM_ROOT *parent_alloc);
+ ~QUICK_ROR_INTERSECT_SELECT();
+
+ int init();
+ void need_sorted_output() { DBUG_ASSERT(0); /* Can't do it */ }
+ int reset(void);
+ int get_next();
+ bool reverse_sorted() { return false; }
+ bool unique_key_range() { return false; }
+ int get_type() { return QS_TYPE_ROR_INTERSECT; }
+ void add_keys_and_lengths(String *key_names, String *used_lengths);
+ Explain_quick_select *get_explain(MEM_ROOT *alloc);
+ bool is_keys_used(const MY_BITMAP *fields);
+ void add_used_key_part_to_set(MY_BITMAP *col_set);
+#ifndef DBUG_OFF
+ void dbug_dump(int indent, bool verbose);
+#endif
+ int init_ror_merged_scan(bool reuse_handler, MEM_ROOT *alloc);
+ bool push_quick_back(MEM_ROOT *alloc, QUICK_RANGE_SELECT *quick_sel_range);
+
+ class QUICK_SELECT_WITH_RECORD : public Sql_alloc
+ {
+ public:
+ QUICK_RANGE_SELECT *quick;
+ uchar *key_tuple;
+ ~QUICK_SELECT_WITH_RECORD() { delete quick; }
+ };
+
+ /*
+ Range quick selects this intersection consists of, not including
+ cpk_quick.
+ */
+ List<QUICK_SELECT_WITH_RECORD> quick_selects;
+
+ virtual bool is_valid()
+ {
+ List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
+ QUICK_SELECT_WITH_RECORD *quick;
+ bool valid= true;
+ while ((quick= it++))
+ {
+ if (!quick->quick->is_valid())
+ {
+ valid= false;
+ break;
+ }
+ }
+ return valid;
+ }
+
+ /*
+ Merged quick select that uses Clustered PK, if there is one. This quick
+ select is not used for row retrieval, it is used for row retrieval.
+ */
+ QUICK_RANGE_SELECT *cpk_quick;
+
+ MEM_ROOT alloc; /* Memory pool for this and merged quick selects data. */
+ THD *thd; /* current thread */
+ bool need_to_fetch_row; /* if true, do retrieve full table records. */
+ /* in top-level quick select, true if merged scans where initialized */
+ bool scans_inited;
+};
+
+
+/*
+ Rowid-Ordered Retrieval index union select.
+ This quick select produces union of row sequences returned by several
+ quick select it "merges".
+
+ All merged quick selects must return rowids in rowid order.
+ QUICK_ROR_UNION_SELECT will return rows in rowid order, too.
+
+ All merged quick selects are set not to retrieve full table records.
+ ROR-union quick select always retrieves full records.
+
+*/
+
+class QUICK_ROR_UNION_SELECT : public QUICK_SELECT_I
+{
+public:
+ QUICK_ROR_UNION_SELECT(THD *thd, TABLE *table);
+ ~QUICK_ROR_UNION_SELECT();
+
+ int init();
+ void need_sorted_output() { DBUG_ASSERT(0); /* Can't do it */ }
+ int reset(void);
+ int get_next();
+ bool reverse_sorted() { return false; }
+ bool unique_key_range() { return false; }
+ int get_type() { return QS_TYPE_ROR_UNION; }
+ void add_keys_and_lengths(String *key_names, String *used_lengths);
+ Explain_quick_select *get_explain(MEM_ROOT *alloc);
+ bool is_keys_used(const MY_BITMAP *fields);
+ void add_used_key_part_to_set(MY_BITMAP *col_set);
+#ifndef DBUG_OFF
+ void dbug_dump(int indent, bool verbose);
+#endif
+
+ bool push_quick_back(QUICK_SELECT_I *quick_sel_range);
+
+ List<QUICK_SELECT_I> quick_selects; /* Merged quick selects */
+
+ virtual bool is_valid()
+ {
+ List_iterator_fast<QUICK_SELECT_I> it(quick_selects);
+ QUICK_SELECT_I *quick;
+ bool valid= true;
+ while ((quick= it++))
+ {
+ if (!quick->is_valid())
+ {
+ valid= false;
+ break;
+ }
+ }
+ return valid;
+ }
+
+ QUEUE queue; /* Priority queue for merge operation */
+ MEM_ROOT alloc; /* Memory pool for this and merged quick selects data. */
+
+ THD *thd; /* current thread */
+ uchar *cur_rowid; /* buffer used in get_next() */
+ uchar *prev_rowid; /* rowid of last row returned by get_next() */
+ bool have_prev_rowid; /* true if prev_rowid has valid data */
+ uint rowid_length; /* table rowid length */
+private:
+ bool scans_inited;
+};
+
+
+/*
+ Index scan for GROUP-BY queries with MIN/MAX aggregate functions.
+
+ This class provides a specialized index access method for GROUP-BY queries
+ of the forms:
+
+ SELECT A_1,...,A_k, [B_1,...,B_m], [MIN(C)], [MAX(C)]
+ FROM T
+ WHERE [RNG(A_1,...,A_p ; where p <= k)]
+ [AND EQ(B_1,...,B_m)]
+ [AND PC(C)]
+ [AND PA(A_i1,...,A_iq)]
+ GROUP BY A_1,...,A_k;
+
+ or
+
+ SELECT DISTINCT A_i1,...,A_ik
+ FROM T
+ WHERE [RNG(A_1,...,A_p ; where p <= k)]
+ [AND PA(A_i1,...,A_iq)];
+
+ where all selected fields are parts of the same index.
+ The class of queries that can be processed by this quick select is fully
+ specified in the description of get_best_trp_group_min_max() in opt_range.cc.
+
+ The get_next() method directly produces result tuples, thus obviating the
+ need to call end_send_group() because all grouping is already done inside
+ get_next().
+
+ Since one of the requirements is that all select fields are part of the same
+ index, this class produces only index keys, and not complete records.
+*/
+
+class QUICK_GROUP_MIN_MAX_SELECT : public QUICK_SELECT_I
+{
+private:
+ handler * const file; /* The handler used to get data. */
+ JOIN *join; /* Descriptor of the current query */
+ KEY *index_info; /* The index chosen for data access */
+ uchar *record; /* Buffer where the next record is returned. */
+ uchar *tmp_record; /* Temporary storage for next_min(), next_max(). */
+ uchar *group_prefix; /* Key prefix consisting of the GROUP fields. */
+ const uint group_prefix_len; /* Length of the group prefix. */
+ uint group_key_parts; /* A number of keyparts in the group prefix */
+ uchar *last_prefix; /* Prefix of the last group for detecting EOF. */
+ bool have_min; /* Specify whether we are computing */
+ bool have_max; /* a MIN, a MAX, or both. */
+ bool have_agg_distinct;/* aggregate_function(DISTINCT ...). */
+ bool seen_first_key; /* Denotes whether the first key was retrieved.*/
+ bool doing_key_read; /* true if we enabled key only reads */
+
+ KEY_PART_INFO *min_max_arg_part; /* The keypart of the only argument field */
+ /* of all MIN/MAX functions. */
+ uint min_max_arg_len; /* The length of the MIN/MAX argument field */
+ uchar *key_infix; /* Infix of constants from equality predicates. */
+ uint key_infix_len;
+ DYNAMIC_ARRAY min_max_ranges; /* Array of range ptrs for the MIN/MAX field. */
+ uint real_prefix_len; /* Length of key prefix extended with key_infix. */
+ uint real_key_parts; /* A number of keyparts in the above value. */
+ List<Item_sum> *min_functions;
+ List<Item_sum> *max_functions;
+ List_iterator<Item_sum> *min_functions_it;
+ List_iterator<Item_sum> *max_functions_it;
+ /*
+ Use index scan to get the next different key instead of jumping into it
+ through index read
+ */
+ bool is_index_scan;
+public:
+ /*
+ The following two members are public to allow easy access from
+ TRP_GROUP_MIN_MAX::make_quick()
+ */
+ MEM_ROOT alloc; /* Memory pool for this and quick_prefix_select data. */
+ QUICK_RANGE_SELECT *quick_prefix_select;/* For retrieval of group prefixes. */
+private:
+ int next_prefix();
+ int next_min_in_range();
+ int next_max_in_range();
+ int next_min();
+ int next_max();
+ void update_min_result();
+ void update_max_result();
+public:
+ QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join, bool have_min,
+ bool have_max, bool have_agg_distinct,
+ KEY_PART_INFO *min_max_arg_part,
+ uint group_prefix_len, uint group_key_parts,
+ uint used_key_parts, KEY *index_info, uint
+ use_index, double read_cost, ha_rows records, uint
+ key_infix_len, uchar *key_infix, MEM_ROOT
+ *parent_alloc, bool is_index_scan);
+ ~QUICK_GROUP_MIN_MAX_SELECT();
+ bool add_range(SEL_ARG *sel_range);
+ void update_key_stat();
+ void adjust_prefix_ranges();
+ bool alloc_buffers();
+ int init();
+ void need_sorted_output() { /* always do it */ }
+ int reset();
+ int get_next();
+ bool reverse_sorted() { return false; }
+ bool unique_key_range() { return false; }
+ int get_type() { return QS_TYPE_GROUP_MIN_MAX; }
+ void add_keys_and_lengths(String *key_names, String *used_lengths);
+ void add_used_key_part_to_set(MY_BITMAP *col_set);
+#ifndef DBUG_OFF
+ void dbug_dump(int indent, bool verbose);
+#endif
+ bool is_agg_distinct() { return have_agg_distinct; }
+ bool loose_scan_is_scanning() { return is_index_scan; }
+ Explain_quick_select *get_explain(MEM_ROOT *alloc);
+};
+
+
+class QUICK_SELECT_DESC: public QUICK_RANGE_SELECT
+{
+public:
+ QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q, uint used_key_parts);
+ int get_next();
+ bool reverse_sorted() { return 1; }
+ int get_type() { return QS_TYPE_RANGE_DESC; }
+ QUICK_SELECT_I *make_reverse(uint used_key_parts_arg)
+ {
+ return this; // is already reverse sorted
+ }
+private:
+ bool range_reads_after_key(QUICK_RANGE *range);
+ int reset(void) { rev_it.rewind(); return QUICK_RANGE_SELECT::reset(); }
+ List<QUICK_RANGE> rev_ranges;
+ List_iterator<QUICK_RANGE> rev_it;
+ uint used_key_parts;
+};
+
+
+class SQL_SELECT :public Sql_alloc {
+ public:
+ QUICK_SELECT_I *quick; // If quick-select used
+ COND *cond; // where condition
+
+ /*
+ When using Index Condition Pushdown: condition that we've had before
+ extracting and pushing index condition.
+ In other cases, NULL.
+ */
+ Item *pre_idx_push_select_cond;
+ TABLE *head;
+ IO_CACHE file; // Positions to used records
+ ha_rows records; // Records in use if read from file
+ double read_time; // Time to read rows
+ key_map quick_keys; // Possible quick keys
+ key_map needed_reg; // Possible quick keys after prev tables.
+ table_map const_tables,read_tables;
+ /* See PARAM::possible_keys */
+ key_map possible_keys;
+ bool free_cond; /* Currently not used and always FALSE */
+
+ SQL_SELECT();
+ ~SQL_SELECT();
+ void cleanup();
+ void set_quick(QUICK_SELECT_I *new_quick) { delete quick; quick= new_quick; }
+ bool check_quick(THD *thd, bool force_quick_range, ha_rows limit)
+ {
+ key_map tmp;
+ tmp.set_all();
+ return test_quick_select(thd, tmp, 0, limit, force_quick_range, FALSE) < 0;
+ }
+ /*
+ RETURN
+ 0 if record must be skipped <-> (cond && cond->val_int() == 0)
+ -1 if error
+ 1 otherwise
+ */
+ inline int skip_record(THD *thd)
+ {
+ int rc= MY_TEST(!cond || cond->val_int());
+ if (thd->is_error())
+ rc= -1;
+ return rc;
+ }
+ int test_quick_select(THD *thd, key_map keys, table_map prev_tables,
+ ha_rows limit, bool force_quick_range,
+ bool ordered_output);
+};
+
+
+class FT_SELECT: public QUICK_RANGE_SELECT
+{
+public:
+ FT_SELECT(THD *thd, TABLE *table, uint key, bool *create_err) :
+ QUICK_RANGE_SELECT (thd, table, key, 1, NULL, create_err)
+ { (void) init(); }
+ ~FT_SELECT() { file->ft_end(); }
+ int init() { return file->ft_init(); }
+ int reset() { return 0; }
+ int get_next() { return file->ha_ft_read(record); }
+ int get_type() { return QS_TYPE_FULLTEXT; }
+};
+
+FT_SELECT *get_ft_select(THD *thd, TABLE *table, uint key);
+QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
+ struct st_table_ref *ref,
+ ha_rows records);
+SQL_SELECT *make_select(TABLE *head, table_map const_tables,
+ table_map read_tables, COND *conds,
+ bool allow_null_cond, int *error);
+
+bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond);
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond);
+#endif
+void store_key_image_to_rec(Field *field, uchar *ptr, uint len);
+
+extern String null_string;
+
+/* check this number of rows (default value) */
+#define SELECTIVITY_SAMPLING_LIMIT 100
+/* but no more then this part of table (10%) */
+#define SELECTIVITY_SAMPLING_SHARE 0.10
+/* do not check if we are going check less then this number of records */
+#define SELECTIVITY_SAMPLING_THRESHOLD 10
+
+#endif
diff --git a/sql/opt_range_mrr.cc b/sql/opt_range_mrr.cc
new file mode 100644
index 00000000000..bff96c7d4cb
--- /dev/null
+++ b/sql/opt_range_mrr.cc
@@ -0,0 +1,364 @@
+/*
+ Copyright (c) 2009, 2011, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/****************************************************************************
+ MRR Range Sequence Interface implementation that walks a SEL_ARG* tree.
+ ****************************************************************************/
+
+/* MRR range sequence, SEL_ARG* implementation: stack entry */
+typedef struct st_range_seq_entry
+{
+ /*
+ Pointers in min and max keys. They point to right-after-end of key
+ images. The 0-th entry has these pointing to key tuple start.
+ */
+ uchar *min_key, *max_key;
+
+ /*
+ Flags, for {keypart0, keypart1, ... this_keypart} subtuple.
+ min_key_flag may have NULL_RANGE set.
+ */
+ uint min_key_flag, max_key_flag;
+
+ /* Number of key parts */
+ uint min_key_parts, max_key_parts;
+ SEL_ARG *key_tree;
+} RANGE_SEQ_ENTRY;
+
+
+/*
+ MRR range sequence, SEL_ARG* implementation: SEL_ARG graph traversal context
+*/
+typedef struct st_sel_arg_range_seq
+{
+ uint keyno; /* index of used tree in SEL_TREE structure */
+ uint real_keyno; /* Number of the index in tables */
+ PARAM *param;
+ SEL_ARG *start; /* Root node of the traversed SEL_ARG* graph */
+
+ RANGE_SEQ_ENTRY stack[MAX_REF_PARTS];
+ int i; /* Index of last used element in the above array */
+
+ bool at_start; /* TRUE <=> The traversal has just started */
+} SEL_ARG_RANGE_SEQ;
+
+
+/*
+ Range sequence interface, SEL_ARG* implementation: Initialize the traversal
+
+ SYNOPSIS
+ init()
+ init_params SEL_ARG tree traversal context
+ n_ranges [ignored] The number of ranges obtained
+ flags [ignored] HA_MRR_SINGLE_POINT, HA_MRR_FIXED_KEY
+
+ RETURN
+ Value of init_param
+*/
+
+range_seq_t sel_arg_range_seq_init(void *init_param, uint n_ranges, uint flags)
+{
+ SEL_ARG_RANGE_SEQ *seq= (SEL_ARG_RANGE_SEQ*)init_param;
+ seq->at_start= TRUE;
+ seq->stack[0].key_tree= NULL;
+ seq->stack[0].min_key= seq->param->min_key;
+ seq->stack[0].min_key_flag= 0;
+ seq->stack[0].min_key_parts= 0;
+
+ seq->stack[0].max_key= seq->param->max_key;
+ seq->stack[0].max_key_flag= 0;
+ seq->stack[0].max_key_parts= 0;
+ seq->i= 0;
+ return init_param;
+}
+
+
+static void step_down_to(SEL_ARG_RANGE_SEQ *arg, SEL_ARG *key_tree)
+{
+ RANGE_SEQ_ENTRY *cur= &arg->stack[arg->i+1];
+ RANGE_SEQ_ENTRY *prev= &arg->stack[arg->i];
+
+ cur->key_tree= key_tree;
+ cur->min_key= prev->min_key;
+ cur->max_key= prev->max_key;
+ cur->min_key_parts= prev->min_key_parts;
+ cur->max_key_parts= prev->max_key_parts;
+
+ uint16 stor_length= arg->param->key[arg->keyno][key_tree->part].store_length;
+ cur->min_key_parts += key_tree->store_min(stor_length, &cur->min_key,
+ prev->min_key_flag);
+ cur->max_key_parts += key_tree->store_max(stor_length, &cur->max_key,
+ prev->max_key_flag);
+
+ cur->min_key_flag= prev->min_key_flag | key_tree->min_flag;
+ cur->max_key_flag= prev->max_key_flag | key_tree->max_flag;
+
+ if (key_tree->is_null_interval())
+ cur->min_key_flag |= NULL_RANGE;
+ (arg->i)++;
+}
+
+
+/*
+ Range sequence interface, SEL_ARG* implementation: get the next interval
+
+ SYNOPSIS
+ sel_arg_range_seq_next()
+ rseq Value returned from sel_arg_range_seq_init
+ range OUT Store information about the range here
+
+ DESCRIPTION
+ This is "get_next" function for Range sequence interface implementation
+ for SEL_ARG* tree.
+
+ IMPLEMENTATION
+ The traversal also updates those param members:
+ - is_ror_scan
+ - range_count
+ - max_key_part
+
+ RETURN
+ FALSE Ok
+ TRUE No more ranges in the sequence
+*/
+
+#if (_MSC_FULL_VER == 160030319)
+/*
+ Workaround Visual Studio 2010 RTM compiler backend bug, the function enters
+ infinite loop.
+ */
+#pragma optimize("g", off)
+#endif
+
+bool sel_arg_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range)
+{
+ SEL_ARG *key_tree;
+ SEL_ARG_RANGE_SEQ *seq= (SEL_ARG_RANGE_SEQ*)rseq;
+ if (seq->at_start)
+ {
+ key_tree= seq->start;
+ seq->at_start= FALSE;
+ goto walk_up_n_right;
+ }
+
+ key_tree= seq->stack[seq->i].key_tree;
+ /* Ok, we're at some "full tuple" position in the tree */
+
+ /* Step down if we can */
+ if (key_tree->next && key_tree->next != &null_element)
+ {
+ //step down; (update the tuple, we'll step right and stay there)
+ seq->i--;
+ step_down_to(seq, key_tree->next);
+ key_tree= key_tree->next;
+ seq->param->is_ror_scan= FALSE;
+ goto walk_right_n_up;
+ }
+
+ /* Ok, can't step down, walk left until we can step down */
+ while (1)
+ {
+ if (seq->i == 1) // can't step left
+ return 1;
+ /* Step left */
+ seq->i--;
+ key_tree= seq->stack[seq->i].key_tree;
+
+ /* Step down if we can */
+ if (key_tree->next && key_tree->next != &null_element)
+ {
+ // Step down; update the tuple
+ seq->i--;
+ step_down_to(seq, key_tree->next);
+ key_tree= key_tree->next;
+ break;
+ }
+ }
+
+ /*
+ Ok, we've stepped down from the path to previous tuple.
+ Walk right-up while we can
+ */
+walk_right_n_up:
+ while (key_tree->next_key_part && key_tree->next_key_part != &null_element &&
+ key_tree->next_key_part->part == key_tree->part + 1 &&
+ key_tree->next_key_part->type == SEL_ARG::KEY_RANGE)
+ {
+ {
+ RANGE_SEQ_ENTRY *cur= &seq->stack[seq->i];
+ uint min_key_length= cur->min_key - seq->param->min_key;
+ uint max_key_length= cur->max_key - seq->param->max_key;
+ uint len= cur->min_key - cur[-1].min_key;
+ if (!(min_key_length == max_key_length &&
+ !memcmp(cur[-1].min_key, cur[-1].max_key, len) &&
+ !key_tree->min_flag && !key_tree->max_flag))
+ {
+ seq->param->is_ror_scan= FALSE;
+ if (!key_tree->min_flag)
+ cur->min_key_parts +=
+ key_tree->next_key_part->store_min_key(seq->param->key[seq->keyno],
+ &cur->min_key,
+ &cur->min_key_flag, MAX_KEY);
+ if (!key_tree->max_flag)
+ cur->max_key_parts +=
+ key_tree->next_key_part->store_max_key(seq->param->key[seq->keyno],
+ &cur->max_key,
+ &cur->max_key_flag, MAX_KEY);
+ break;
+ }
+ }
+
+ /*
+ Ok, current atomic interval is in form "t.field=const" and there is
+ next_key_part interval. Step right, and walk up from there.
+ */
+ key_tree= key_tree->next_key_part;
+
+walk_up_n_right:
+ while (key_tree->prev && key_tree->prev != &null_element)
+ {
+ /* Step up */
+ key_tree= key_tree->prev;
+ }
+ step_down_to(seq, key_tree);
+ }
+
+ /* Ok got a tuple */
+ RANGE_SEQ_ENTRY *cur= &seq->stack[seq->i];
+ uint min_key_length= cur->min_key - seq->param->min_key;
+
+ range->ptr= (char*)(intptr)(key_tree->part);
+ if (cur->min_key_flag & GEOM_FLAG)
+ {
+ range->range_flag= cur->min_key_flag;
+
+ /* Here minimum contains also function code bits, and maximum is +inf */
+ range->start_key.key= seq->param->min_key;
+ range->start_key.length= min_key_length;
+ range->start_key.keypart_map= make_prev_keypart_map(cur->min_key_parts);
+ range->start_key.flag= (ha_rkey_function) (cur->min_key_flag ^ GEOM_FLAG);
+ }
+ else
+ {
+ range->range_flag= cur->min_key_flag | cur->max_key_flag;
+
+ range->start_key.key= seq->param->min_key;
+ range->start_key.length= cur->min_key - seq->param->min_key;
+ range->start_key.keypart_map= make_prev_keypart_map(cur->min_key_parts);
+ range->start_key.flag= (cur->min_key_flag & NEAR_MIN ? HA_READ_AFTER_KEY :
+ HA_READ_KEY_EXACT);
+
+ range->end_key.key= seq->param->max_key;
+ range->end_key.length= cur->max_key - seq->param->max_key;
+ range->end_key.flag= (cur->max_key_flag & NEAR_MAX ? HA_READ_BEFORE_KEY :
+ HA_READ_AFTER_KEY);
+ range->end_key.keypart_map= make_prev_keypart_map(cur->max_key_parts);
+
+ if (!(cur->min_key_flag & ~NULL_RANGE) && !cur->max_key_flag &&
+ (seq->real_keyno == MAX_KEY ||
+ ((uint)key_tree->part+1 ==
+ seq->param->table->key_info[seq->real_keyno].user_defined_key_parts &&
+ (seq->param->table->key_info[seq->real_keyno].flags & HA_NOSAME))) &&
+ range->start_key.length == range->end_key.length &&
+ !memcmp(seq->param->min_key,seq->param->max_key,range->start_key.length))
+ range->range_flag= UNIQUE_RANGE | (cur->min_key_flag & NULL_RANGE);
+
+ if (seq->param->is_ror_scan)
+ {
+ /*
+ If we get here, the condition on the key was converted to form
+ "(keyXpart1 = c1) AND ... AND (keyXpart{key_tree->part - 1} = cN) AND
+ somecond(keyXpart{key_tree->part})"
+ Check if
+ somecond is "keyXpart{key_tree->part} = const" and
+ uncovered "tail" of KeyX parts is either empty or is identical to
+ first members of clustered primary key.
+ */
+ if (!(!(cur->min_key_flag & ~NULL_RANGE) && !cur->max_key_flag &&
+ (range->start_key.length == range->end_key.length) &&
+ !memcmp(range->start_key.key, range->end_key.key, range->start_key.length) &&
+ is_key_scan_ror(seq->param, seq->real_keyno, key_tree->part + 1)))
+ seq->param->is_ror_scan= FALSE;
+ }
+ }
+ seq->param->range_count++;
+ seq->param->max_key_part=MY_MAX(seq->param->max_key_part,key_tree->part);
+ return 0;
+}
+
+#if (_MSC_FULL_VER == 160030319)
+/* VS2010 compiler bug workaround */
+#pragma optimize("g", on)
+#endif
+
+
+/****************************************************************************
+ MRR Range Sequence Interface implementation that walks array<QUICK_RANGE>
+ ****************************************************************************/
+
+/*
+ Range sequence interface implementation for array<QUICK_RANGE>: initialize
+
+ SYNOPSIS
+ quick_range_seq_init()
+ init_param Caller-opaque paramenter: QUICK_RANGE_SELECT* pointer
+ n_ranges Number of ranges in the sequence (ignored)
+ flags MRR flags (currently not used)
+
+ RETURN
+ Opaque value to be passed to quick_range_seq_next
+*/
+
+range_seq_t quick_range_seq_init(void *init_param, uint n_ranges, uint flags)
+{
+ QUICK_RANGE_SELECT *quick= (QUICK_RANGE_SELECT*)init_param;
+ quick->qr_traversal_ctx.first= (QUICK_RANGE**)quick->ranges.buffer;
+ quick->qr_traversal_ctx.cur= (QUICK_RANGE**)quick->ranges.buffer;
+ quick->qr_traversal_ctx.last= quick->qr_traversal_ctx.cur +
+ quick->ranges.elements;
+ return &quick->qr_traversal_ctx;
+}
+
+
+/*
+ Range sequence interface implementation for array<QUICK_RANGE>: get next
+
+ SYNOPSIS
+ quick_range_seq_next()
+ rseq Value returned from quick_range_seq_init
+ range OUT Store information about the range here
+
+ RETURN
+ 0 Ok
+ 1 No more ranges in the sequence
+*/
+
+bool quick_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range)
+{
+ QUICK_RANGE_SEQ_CTX *ctx= (QUICK_RANGE_SEQ_CTX*)rseq;
+
+ if (ctx->cur == ctx->last)
+ return 1; /* no more ranges */
+
+ QUICK_RANGE *cur= *(ctx->cur);
+ cur->make_min_endpoint(&range->start_key);
+ cur->make_max_endpoint(&range->end_key);
+ range->range_flag= cur->flag;
+ ctx->cur++;
+ return 0;
+}
+
+
diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc
new file mode 100644
index 00000000000..0ad90e2ef3d
--- /dev/null
+++ b/sql/opt_subselect.cc
@@ -0,0 +1,5708 @@
+/*
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @file
+
+ @brief
+ Semi-join subquery optimizations code
+
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_base.h"
+#include "sql_select.h"
+#include "filesort.h"
+#include "opt_subselect.h"
+#include "sql_test.h"
+#include <my_bit.h>
+
+/*
+ This file contains optimizations for semi-join subqueries.
+
+ Contents
+ --------
+ 1. What is a semi-join subquery
+ 2. General idea about semi-join execution
+ 2.1 Correlated vs uncorrelated semi-joins
+ 2.2 Mergeable vs non-mergeable semi-joins
+ 3. Code-level view of semi-join processing
+ 3.1 Conversion
+ 3.1.1 Merged semi-join TABLE_LIST object
+ 3.1.2 Non-merged semi-join data structure
+ 3.2 Semi-joins and query optimization
+ 3.2.1 Non-merged semi-joins and join optimization
+ 3.2.2 Merged semi-joins and join optimization
+ 3.3 Semi-joins and query execution
+
+ 1. What is a semi-join subquery
+ -------------------------------
+ We use this definition of semi-join:
+
+ outer_tbl SEMI JOIN inner_tbl ON cond = {set of outer_tbl.row such that
+ exist inner_tbl.row, for which
+ cond(outer_tbl.row,inner_tbl.row)
+ is satisfied}
+
+ That is, semi-join operation is similar to inner join operation, with
+ exception that we don't care how many matches a row from outer_tbl has in
+ inner_tbl.
+
+ In SQL terms: a semi-join subquery is an IN subquery that is an AND-part of
+ the WHERE/ON clause.
+
+ 2. General idea about semi-join execution
+ -----------------------------------------
+ We can execute semi-join in a way similar to inner join, with exception that
+ we need to somehow ensure that we do not generate record combinations that
+ differ only in rows of inner tables.
+ There is a number of different ways to achieve this property, implemented by
+ a number of semi-join execution strategies.
+ Some strategies can handle any semi-joins, other can be applied only to
+ semi-joins that have certain properties that are described below:
+
+ 2.1 Correlated vs uncorrelated semi-joins
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Uncorrelated semi-joins are special in the respect that they allow to
+ - execute the subquery (possible as it's uncorrelated)
+ - somehow make sure that generated set does not have duplicates
+ - perform an inner join with outer tables.
+
+ or, rephrasing in SQL form:
+
+ SELECT ... FROM ot WHERE ot.col IN (SELECT it.col FROM it WHERE uncorr_cond)
+ ->
+ SELECT ... FROM ot JOIN (SELECT DISTINCT it.col FROM it WHERE uncorr_cond)
+
+ 2.2 Mergeable vs non-mergeable semi-joins
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Semi-join operation has some degree of commutability with inner join
+ operation: we can join subquery's tables with ouside table(s) and eliminate
+ duplicate record combination after that:
+
+ ot1 JOIN ot2 SEMI_JOIN{it1,it2} (it1 JOIN it2) ON sjcond(ot2,it*) ->
+ |
+ +-------------------------------+
+ v
+ ot1 SEMI_JOIN{it1,it2} (it1 JOIN it2 JOIN ot2) ON sjcond(ot2,it*)
+
+ In order for this to work, subquery's top-level operation must be join, and
+ grouping or ordering with limit (grouping or ordering with limit are not
+ commutative with duplicate removal). In other words, the conversion is
+ possible when the subquery doesn't have GROUP BY clause, any aggregate
+ functions*, or ORDER BY ... LIMIT clause.
+
+ Definitions:
+ - Subquery whose top-level operation is a join is called *mergeable semi-join*
+ - All other kinds of semi-join subqueries are considered non-mergeable.
+
+ *- this requirement is actually too strong, but its exceptions are too
+ complicated to be considered here.
+
+ 3. Code-level view of semi-join processing
+ ------------------------------------------
+
+ 3.1 Conversion and pre-optimization data structures
+ ---------------------------------------------------
+ * When doing JOIN::prepare for the subquery, we detect that it can be
+ converted into a semi-join and register it in parent_join->sj_subselects
+
+ * At the start of parent_join->optimize(), the predicate is converted into
+ a semi-join node. A semi-join node is a TABLE_LIST object that is linked
+ somewhere in parent_join->join_list (either it is just present there, or
+ it is a descendant of some of its members).
+
+ There are two kinds of semi-joins:
+ - Merged semi-joins
+ - Non-merged semi-joins
+
+ 3.1.1 Merged semi-join TABLE_LIST object
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Merged semi-join object is a TABLE_LIST that contains a sub-join of
+ subquery tables and the semi-join ON expression (in this respect it is
+ very similar to nested outer join representation)
+ Merged semi-join represents this SQL:
+
+ ... SEMI JOIN (inner_tbl1 JOIN ... JOIN inner_tbl_n) ON sj_on_expr
+
+ Semi-join objects of this kind have TABLE_LIST::sj_subq_pred set.
+
+ 3.1.2 Non-merged semi-join data structure
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Non-merged semi-join object is a leaf TABLE_LIST object that has a subquery
+ that produces rows. It is similar to a base table and represents this SQL:
+
+ ... SEMI_JOIN (SELECT non_mergeable_select) ON sj_on_expr
+
+ Subquery items that were converted into semi-joins are removed from the WHERE
+ clause. (They do remain in PS-saved WHERE clause, and they replace themselves
+ with Item_int(1) on subsequent re-executions).
+
+ 3.2 Semi-joins and join optimization
+ ------------------------------------
+
+ 3.2.1 Non-merged semi-joins and join optimization
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ For join optimization purposes, non-merged semi-join nests are similar to
+ base tables. Each such nest is represented by one one JOIN_TAB, which has
+ two possible access strategies:
+ - full table scan (representing SJ-Materialization-Scan strategy)
+ - eq_ref-like table lookup (representing SJ-Materialization-Lookup)
+
+ Unlike regular base tables, non-merged semi-joins have:
+ - non-zero JOIN_TAB::startup_cost, and
+ - join_tab->table->is_filled_at_execution()==TRUE, which means one
+ cannot do const table detection, range analysis or other dataset-dependent
+ optimizations.
+ Instead, get_delayed_table_estimates() will run optimization for the
+ subquery and produce an E(materialized table size).
+
+ 3.2.2 Merged semi-joins and join optimization
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ - optimize_semijoin_nests() does pre-optimization
+ - during join optimization, the join has one JOIN_TAB (or is it POSITION?)
+ array, and suffix-based detection is used, see advance_sj_state()
+ - after join optimization is done, get_best_combination() switches
+ the data-structure to prefix-based, multiple JOIN_TAB ranges format.
+
+ 3.3 Semi-joins and query execution
+ ----------------------------------
+ * Join executor has hooks for all semi-join strategies.
+ TODO elaborate.
+
+*/
+
+/*
+EqualityPropagationAndSjmNests
+******************************
+
+Equalities are used for:
+P1. Equality propagation
+P2. Equality substitution [for a certain join order]
+
+The equality propagation is not affected by SJM nests. In fact, it is done
+before we determine the execution plan, i.e. before we even know we will use
+SJM-nests for execution.
+
+The equality substitution is affected.
+
+Substitution without SJMs
+=========================
+When one doesn't have SJM nests, tables have a strict join order:
+
+ --------------------------------->
+ t1 -- t2 -- t3 -- t4 --- t5
+
+
+ ? ^
+ \
+ --(part-of-WHERE)
+
+
+parts WHERE/ON and ref. expressions are attached at some point along the axis.
+Expression is allowed to refer to a table column if the table is to the left of
+the attachment point. For any given expression, we have a goal:
+
+ "Move leftmost allowed attachment point as much as possible to the left"
+
+Substitution with SJMs - task setting
+=====================================
+
+When SJM nests are present, there is no global strict table ordering anymore:
+
+
+ --------------------------------->
+
+ ot1 -- ot2 --- sjm -- ot4 --- ot5
+ |
+ | Main execution
+ - - - - - - - - - - - - - - - - - - - - - - - -
+ | Materialization
+ it1 -- it2 --/
+
+
+Besides that, we must take into account that
+ - values for outer table columns, otN.col, are inaccessible at
+ materialization step (SJM-RULE)
+ - values for inner table columns, itN.col, are inaccessible at Main execution
+ step, except for SJ-Materialization-Scan and columns that are in the
+ subquery's select list. (SJM-RULE)
+
+Substitution with SJMs - solution
+=================================
+
+First, we introduce global strict table ordering like this:
+
+ ot1 - ot2 --\ /--- ot3 -- ot5
+ \--- it1 --- it2 --/
+
+Now, let's see how to meet (SJM-RULE).
+
+SJ-Materialization is only applicable for uncorrelated subqueries. From this, it
+follows that any multiple equality will either
+1. include only columns of outer tables, or
+2. include only columns of inner tables, or
+3. include columns of inner and outer tables, joined together through one
+ of IN-equalities.
+
+Cases #1 and #2 can be handled in the same way as with regular inner joins.
+
+Case #3 requires special handling, so that we don't construct violations of
+(SJM-RULE). Let's consider possible ways to build violations.
+
+Equality propagation starts with the clause in this form
+
+ top_query_where AND subquery_where AND in_equalities
+
+First, it builds multi-equalities. It can also build a mixed multi-equality
+
+ multiple-equal(ot1.col, ot2.col, ... it1.col, itN.col)
+
+Multi-equalities are pushed down the OR-clauses in top_query_where and in
+subquery_where, so it's possible that clauses like this one are built:
+
+ subquery_cond OR (multiple-equal(it1.col, ot1.col,...) AND ...)
+ ^^^^^^^^^^^^^ \
+ | this must be evaluated
+ \- can only be evaluated at the main phase.
+ at the materialization phase
+
+Finally, equality substitution is started. It does two operations:
+
+
+1. Field reference substitution
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(In the code, this is Item_field::replace_equal_field)
+
+This is a process of replacing each reference to "tblX.col"
+with the first element of the multi-equality. (REF-SUBST-ORIG)
+
+This behaviour can cause problems with Semi-join nests. Suppose, we have a
+condition:
+
+ func(it1.col, it2.col)
+
+and a multi-equality(ot1.col, it1.col). Then, reference to "it1.col" will be
+replaced with "ot1.col", constructing a condition
+
+ func(ot1.col, it2.col)
+
+which will be a violation of (SJM-RULE).
+
+In order to avoid this, (REF-SUBST-ORIG) is amended as follows:
+
+- references to tables "itX.col" that are inner wrt some SJM nest, are
+ replaced with references to the first inner table from the same SJM nest.
+
+- references to top-level tables "otX.col" are replaced with references to
+ the first element of the multi-equality, no matter if that first element is
+ a column of a top-level table or of table from some SJM nest.
+ (REF-SUBST-SJM)
+
+ The case where the first element is a table from an SJM nest $SJM is ok,
+ because it can be proven that $SJM uses SJ-Materialization-Scan, and
+ "unpacks" correct column values to the first element during the main
+ execution phase.
+
+2. Item_equal elimination
+~~~~~~~~~~~~~~~~~~~~~~~~~
+(In the code: eliminate_item_equal) This is a process of taking
+
+ multiple-equal(a,b,c,d,e)
+
+and replacing it with an equivalent expression which is an AND of pair-wise
+equalities:
+
+ a=b AND a=c AND ...
+
+The equalities are picked such that for any given join prefix (t1,t2...) the
+subset of equalities that can be evaluated gives the most restrictive
+filtering.
+
+Without SJM nests, it is sufficient to compare every multi-equality member
+with the first one:
+
+ elem1=elem2 AND elem1=elem3 AND elem1=elem4 ...
+
+When SJM nests are present, we should take care not to construct equalities
+that violate the (SJM-RULE). This is achieved by generating separate sets of
+equalites for top-level tables and for inner tables. That is, for the join
+order
+
+ ot1 - ot2 --\ /--- ot3 -- ot5
+ \--- it1 --- it2 --/
+
+we will generate
+ ot1.col=ot2.col
+ ot1.col=ot3.col
+ ot1.col=ot5.col
+ it2.col=it1.col
+
+
+2.1 The problem with Item_equals and ORs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+As has been mentioned above, multiple equalities are pushed down into OR
+clauses, possibly building clauses like this:
+
+ func(it.col2) OR multiple-equal(it1.col1, it1.col2, ot1.col) (1)
+
+where the first part of the clause has references to inner tables, while the
+second has references to the top-level tables, which is a violation of
+(SJM-RULE).
+
+AND-clauses of this kind do not create problems, because make_cond_for_table()
+will take them apart. OR-clauses will not be split. It is possible to
+split-out the part that's dependent on the inner table:
+
+ func(it.col2) OR it1.col1=it1.col2
+
+but this is a less-restrictive condition than condition (1). Current execution
+scheme will still try to generate the "remainder" condition:
+
+ func(it.col2) OR it1.col1=ot1.col
+
+which is a violation of (SJM-RULE).
+
+QQ: "ot1.col=it1.col" is checked at the upper level. Why was it not removed
+here?
+AA: because has a proper subset of conditions that are found on this level.
+ consider a join order of ot, sjm(it)
+ and a condition
+ ot.col=it.col AND ( ot.col=it.col='foo' OR it.col2='bar')
+
+ we will produce:
+ table ot: nothing
+ table it: ot.col=it.col AND (ot.col='foo' OR it.col2='bar')
+ ^^^^ ^^^^^^^^^^^^^^^^
+ | \ the problem is that
+ | this part condition didnt
+ | receive a substitution
+ |
+ +--- it was correct to subst, 'ot' is
+ the left-most.
+
+
+Does it make sense to push "inner=outer" down into ORs?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Yes. Consider the query:
+
+ select * from ot
+ where ot.col in (select it.col from it where (it.col='foo' OR it.col='bar'))
+
+here, it may be useful to infer that
+
+ (ot.col='foo' OR ot.col='bar') (CASE-FOR-SUBST)
+
+and attach that condition to the table 'ot'.
+
+Possible solutions for Item_equals and ORs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Solution #1
+~~~~~~~~~~~
+Let make_cond_for_table() chop analyze the OR clauses it has produced and
+discard them if they violate (SJM-RULE). This solution would allow to handle
+cases like (CASE-FOR-SUBST) at the expense of making semantics of
+make_cond_for_table() complicated.
+
+Solution #2
+~~~~~~~~~~~
+Before the equality propagation phase, none of the OR clauses violate the
+(SJM-RULE). This way, if we remember which tables the original equality
+referred to, we can only generate equalities that refer to the outer (or inner)
+tables. Note that this will disallow handling of cases like (CASE-FOR-SUBST).
+
+Currently, solution #2 is implemented.
+
+*/
+
+
+static
+bool subquery_types_allow_materialization(Item_in_subselect *in_subs);
+static bool replace_where_subcondition(JOIN *, Item **, Item *, Item *, bool);
+static int subq_sj_candidate_cmp(Item_in_subselect* el1, Item_in_subselect* el2,
+ void *arg);
+static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred);
+static bool convert_subq_to_jtbm(JOIN *parent_join,
+ Item_in_subselect *subq_pred, bool *remove);
+static TABLE_LIST *alloc_join_nest(THD *thd);
+static uint get_tmp_table_rec_length(Item **p_list, uint elements);
+static double get_tmp_table_lookup_cost(THD *thd, double row_count,
+ uint row_size);
+static double get_tmp_table_write_cost(THD *thd, double row_count,
+ uint row_size);
+bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables);
+static SJ_MATERIALIZATION_INFO *
+at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab,
+ uint idx, bool *loose_scan);
+void best_access_path(JOIN *join, JOIN_TAB *s,
+ table_map remaining_tables, uint idx,
+ bool disable_jbuf, double record_count,
+ POSITION *pos, POSITION *loose_scan_pos);
+
+static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm,
+ Item_in_subselect *subq_pred);
+static void remove_sj_conds(Item **tree);
+static bool is_cond_sj_in_equality(Item *item);
+static bool sj_table_is_included(JOIN *join, JOIN_TAB *join_tab);
+static Item *remove_additional_cond(Item* conds);
+static void remove_subq_pushed_predicates(JOIN *join, Item **where);
+
+enum_nested_loop_state
+end_sj_materialize(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
+
+
+/*
+ Check if Materialization strategy is allowed for given subquery predicate.
+
+ @param thd Thread handle
+ @param in_subs The subquery predicate
+ @param child_select The select inside predicate (the function will
+ check it is the only one)
+
+ @return TRUE - Materialization is applicable
+ FALSE - Otherwise
+*/
+
+bool is_materialization_applicable(THD *thd, Item_in_subselect *in_subs,
+ st_select_lex *child_select)
+{
+ st_select_lex_unit* parent_unit= child_select->master_unit();
+ /*
+ Check if the subquery predicate can be executed via materialization.
+ The required conditions are:
+ 0. The materialization optimizer switch was set.
+ 1. Subquery is a single SELECT (not a UNION).
+ TODO: this is a limitation that can be fixed
+ 2. Subquery is not a table-less query. In this case there is no
+ point in materializing.
+ 2A The upper query is not a table-less SELECT ... FROM DUAL. We
+ can't do materialization for SELECT .. FROM DUAL because it
+ does not call setup_subquery_materialization(). We could make
+ SELECT ... FROM DUAL call that function but that doesn't seem
+ to be the case that is worth handling.
+ 3. Either the subquery predicate is a top-level predicate, or at
+ least one partial match strategy is enabled. If no partial match
+ strategy is enabled, then materialization cannot be used for
+ non-top-level queries because it cannot handle NULLs correctly.
+ 4. Subquery is non-correlated
+ TODO:
+ This condition is too restrictive (limitation). It can be extended to:
+ (Subquery is non-correlated ||
+ Subquery is correlated to any query outer to IN predicate ||
+ (Subquery is correlated to the immediate outer query &&
+ Subquery !contains {GROUP BY, ORDER BY [LIMIT],
+ aggregate functions}) && subquery predicate is not under "NOT IN"))
+
+ A note about prepared statements: we want the if-branch to be taken on
+ PREPARE and each EXECUTE. The rewrites are only done once, but we need
+ select_lex->sj_subselects list to be populated for every EXECUTE.
+
+ */
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION) && // 0
+ !child_select->is_part_of_union() && // 1
+ parent_unit->first_select()->leaf_tables.elements && // 2
+ child_select->outer_select()->leaf_tables.elements && // 2A
+ subquery_types_allow_materialization(in_subs) &&
+ (in_subs->is_top_level_item() || //3
+ optimizer_flag(thd,
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) || //3
+ optimizer_flag(thd,
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN)) && //3
+ !in_subs->is_correlated) //4
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Check if we need JOIN::prepare()-phase subquery rewrites and if yes, do them
+
+ SYNOPSIS
+ check_and_do_in_subquery_rewrites()
+ join Subquery's join
+
+ DESCRIPTION
+ Check if we need to do
+ - subquery -> mergeable semi-join rewrite
+ - if the subquery can be handled with materialization
+ - 'substitution' rewrite for table-less subqueries like "(select 1)"
+ - IN->EXISTS rewrite
+ and, depending on the rewrite, either do it, or record it to be done at a
+ later phase.
+
+ RETURN
+ 0 - OK
+ Other - Some sort of query error
+*/
+
+int check_and_do_in_subquery_rewrites(JOIN *join)
+{
+ THD *thd=join->thd;
+ st_select_lex *select_lex= join->select_lex;
+ st_select_lex_unit* parent_unit= select_lex->master_unit();
+ DBUG_ENTER("check_and_do_in_subquery_rewrites");
+
+ /*
+ IN/ALL/ANY rewrites are not applicable for so called fake select
+ (this select exists only to filter results of union if it is needed).
+ */
+ if (select_lex == select_lex->master_unit()->fake_select_lex)
+ DBUG_RETURN(0);
+
+ /*
+ If
+ 1) this join is inside a subquery (of any type except FROM-clause
+ subquery) and
+ 2) we aren't just normalizing a VIEW
+
+ Then perform early unconditional subquery transformations:
+ - Convert subquery predicate into semi-join, or
+ - Mark the subquery for execution using materialization, or
+ - Perform IN->EXISTS transformation, or
+ - Perform more/less ALL/ANY -> MIN/MAX rewrite
+ - Substitute trivial scalar-context subquery with its value
+
+ TODO: for PS, make the whole block execute only on the first execution
+ */
+ Item_subselect *subselect;
+ if (!thd->lex->is_view_context_analysis() && // (1)
+ (subselect= parent_unit->item)) // (2)
+ {
+ Item_in_subselect *in_subs= NULL;
+ Item_allany_subselect *allany_subs= NULL;
+ switch (subselect->substype()) {
+ case Item_subselect::IN_SUBS:
+ in_subs= (Item_in_subselect *)subselect;
+ break;
+ case Item_subselect::ALL_SUBS:
+ case Item_subselect::ANY_SUBS:
+ allany_subs= (Item_allany_subselect *)subselect;
+ break;
+ default:
+ break;
+ }
+
+
+ /* Resolve expressions and perform semantic analysis for IN query */
+ if (in_subs != NULL)
+ /*
+ TODO: Add the condition below to this if statement when we have proper
+ support for is_correlated handling for materialized semijoins.
+ If we were to add this condition now, the fix_fields() call in
+ convert_subq_to_sj() would force the flag is_correlated to be set
+ erroneously for prepared queries.
+
+ thd->stmt_arena->state != Query_arena::PREPARED)
+ */
+ {
+ /*
+ Check if the left and right expressions have the same # of
+ columns, i.e. we don't have a case like
+ (oe1, oe2) IN (SELECT ie1, ie2, ie3 ...)
+
+ TODO why do we have this duplicated in IN->EXISTS transformers?
+ psergey-todo: fix these: grep for duplicated_subselect_card_check
+ */
+ if (select_lex->item_list.elements != in_subs->left_expr->cols())
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), in_subs->left_expr->cols());
+ DBUG_RETURN(-1);
+ }
+
+ SELECT_LEX *current= thd->lex->current_select;
+ thd->lex->current_select= current->return_after_parsing();
+ char const *save_where= thd->where;
+ thd->where= "IN/ALL/ANY subquery";
+
+ bool failure= !in_subs->left_expr->fixed &&
+ in_subs->left_expr->fix_fields(thd, &in_subs->left_expr);
+ thd->lex->current_select= current;
+ thd->where= save_where;
+ if (failure)
+ DBUG_RETURN(-1); /* purecov: deadcode */
+ }
+
+ DBUG_PRINT("info", ("Checking if subq can be converted to semi-join"));
+ /*
+ Check if we're in subquery that is a candidate for flattening into a
+ semi-join (which is done in flatten_subqueries()). The
+ requirements are:
+ 1. Subquery predicate is an IN/=ANY subq predicate
+ 2. Subquery is a single SELECT (not a UNION)
+ 3. Subquery does not have GROUP BY or ORDER BY
+ 4. Subquery does not use aggregate functions or HAVING
+ 5. Subquery predicate is at the AND-top-level of ON/WHERE clause
+ 6. We are not in a subquery of a single table UPDATE/DELETE that
+ doesn't have a JOIN (TODO: We should handle this at some
+ point by switching to multi-table UPDATE/DELETE)
+ 7. We're not in a table-less subquery like "SELECT 1"
+ 8. No execution method was already chosen (by a prepared statement)
+ 9. Parent select is not a table-less select
+ 10. Neither parent nor child select have STRAIGHT_JOIN option.
+ 11. It is first optimisation (the subquery could be moved from ON
+ clause during first optimisation and then be considered for SJ
+ on the second when it is too late)
+ */
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_SEMIJOIN) &&
+ in_subs && // 1
+ !select_lex->is_part_of_union() && // 2
+ !select_lex->group_list.elements && !join->order && // 3
+ !join->having && !select_lex->with_sum_func && // 4
+ in_subs->emb_on_expr_nest && // 5
+ select_lex->outer_select()->join && // 6
+ parent_unit->first_select()->leaf_tables.elements && // 7
+ !in_subs->has_strategy() && // 8
+ select_lex->outer_select()->leaf_tables.elements && // 9
+ !((join->select_options | // 10
+ select_lex->outer_select()->join->select_options) // 10
+ & SELECT_STRAIGHT_JOIN) && // 10
+ select_lex->first_cond_optimization) // 11
+ {
+ DBUG_PRINT("info", ("Subquery is semi-join conversion candidate"));
+
+ (void)subquery_types_allow_materialization(in_subs);
+
+ in_subs->is_flattenable_semijoin= TRUE;
+
+ /* Register the subquery for further processing in flatten_subqueries() */
+ if (!in_subs->is_registered_semijoin)
+ {
+ Query_arena *arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ select_lex->outer_select()->sj_subselects.push_back(in_subs);
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ in_subs->is_registered_semijoin= TRUE;
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Subquery can't be converted to merged semi-join"));
+ /* Test if the user has set a legal combination of optimizer switches. */
+ if (!optimizer_flag(thd, OPTIMIZER_SWITCH_IN_TO_EXISTS) &&
+ !optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION))
+ my_error(ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES, MYF(0));
+
+ /*
+ If the subquery predicate is IN/=ANY, analyse and set all possible
+ subquery execution strategies based on optimizer switches and syntactic
+ properties.
+ */
+ if (in_subs && !in_subs->has_strategy())
+ {
+ if (is_materialization_applicable(thd, in_subs, select_lex))
+ {
+ in_subs->add_strategy(SUBS_MATERIALIZATION);
+
+ /*
+ If the subquery is an AND-part of WHERE register for being processed
+ with jtbm strategy
+ */
+ if (in_subs->emb_on_expr_nest == NO_JOIN_NEST &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_SEMIJOIN))
+ {
+ in_subs->is_flattenable_semijoin= FALSE;
+ if (!in_subs->is_registered_semijoin)
+ {
+ Query_arena *arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ select_lex->outer_select()->sj_subselects.push_back(in_subs);
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ in_subs->is_registered_semijoin= TRUE;
+ }
+ }
+ }
+
+ /*
+ IN-TO-EXISTS is the only universal strategy. Choose it if the user
+ allowed it via an optimizer switch, or if materialization is not
+ possible.
+ */
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_IN_TO_EXISTS) ||
+ !in_subs->has_strategy())
+ in_subs->add_strategy(SUBS_IN_TO_EXISTS);
+ }
+
+ /* Check if max/min optimization applicable */
+ if (allany_subs && !allany_subs->is_set_strategy())
+ {
+ uchar strategy= (allany_subs->is_maxmin_applicable(join) ?
+ (SUBS_MAXMIN_INJECTED | SUBS_MAXMIN_ENGINE) :
+ SUBS_IN_TO_EXISTS);
+ allany_subs->add_strategy(strategy);
+ }
+
+ /*
+ Transform each subquery predicate according to its overloaded
+ transformer.
+ */
+ if (subselect->select_transformer(join))
+ DBUG_RETURN(-1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Check if subquery's compared types allow materialization.
+
+ @param in_subs Subquery predicate, updated as follows:
+ types_allow_materialization TRUE if subquery materialization is allowed.
+ sjm_scan_allowed If types_allow_materialization is TRUE,
+ indicates whether it is possible to use subquery
+ materialization and scan the materialized table.
+
+ @retval TRUE If subquery types allow materialization.
+ @retval FALSE Otherwise.
+
+ @details
+ This is a temporary fix for BUG#36752.
+
+ There are two subquery materialization strategies:
+
+ 1. Materialize and do index lookups in the materialized table. See
+ BUG#36752 for description of restrictions we need to put on the
+ compared expressions.
+
+ 2. Materialize and then do a full scan of the materialized table. At the
+ moment, this strategy's applicability criteria are even stricter than
+ in #1.
+
+ This is so because of the following: consider an uncorrelated subquery
+
+ ...WHERE (ot1.col1, ot2.col2 ...) IN (SELECT ie1,ie2,... FROM it1 ...)
+
+ and a join order that could be used to do sjm-materialization:
+
+ SJM-Scan(it1, it1), ot1, ot2
+
+ IN-equalities will be parts of conditions attached to the outer tables:
+
+ ot1: ot1.col1 = ie1 AND ... (C1)
+ ot2: ot1.col2 = ie2 AND ... (C2)
+
+ besides those there may be additional references to ie1 and ie2
+ generated by equality propagation. The problem with evaluating C1 and
+ C2 is that ie{1,2} refer to subquery tables' columns, while we only have
+ current value of materialization temptable. Our solution is to
+ * require that all ie{N} are table column references. This allows
+ to copy the values of materialization temptable columns to the
+ original table's columns (see setup_sj_materialization for more
+ details)
+ * require that compared columns have exactly the same type. This is
+ a temporary measure to avoid BUG#36752-type problems.
+*/
+
+static
+bool subquery_types_allow_materialization(Item_in_subselect *in_subs)
+{
+ DBUG_ENTER("subquery_types_allow_materialization");
+
+ DBUG_ASSERT(in_subs->left_expr->fixed);
+
+ List_iterator<Item> it(in_subs->unit->first_select()->item_list);
+ uint elements= in_subs->unit->first_select()->item_list.elements;
+
+ in_subs->types_allow_materialization= FALSE; // Assign default values
+ in_subs->sjm_scan_allowed= FALSE;
+
+ bool all_are_fields= TRUE;
+ for (uint i= 0; i < elements; i++)
+ {
+ Item *outer= in_subs->left_expr->element_index(i);
+ Item *inner= it++;
+ all_are_fields &= (outer->real_item()->type() == Item::FIELD_ITEM &&
+ inner->real_item()->type() == Item::FIELD_ITEM);
+ if (outer->cmp_type() != inner->cmp_type())
+ DBUG_RETURN(FALSE);
+ switch (outer->cmp_type()) {
+ case STRING_RESULT:
+ if (!(outer->collation.collation == inner->collation.collation))
+ DBUG_RETURN(FALSE);
+ // Materialization does not work with BLOB columns
+ if (inner->field_type() == MYSQL_TYPE_BLOB ||
+ inner->field_type() == MYSQL_TYPE_GEOMETRY)
+ DBUG_RETURN(FALSE);
+ /*
+ Materialization also is unable to work when create_tmp_table() will
+ create a blob column because item->max_length is too big.
+ The following check is copied from Item::make_string_field():
+ */
+ if (inner->too_big_for_varchar())
+ {
+ DBUG_RETURN(FALSE);
+ }
+ break;
+ case TIME_RESULT:
+ if (mysql_type_to_time_type(outer->field_type()) !=
+ mysql_type_to_time_type(inner->field_type()))
+ DBUG_RETURN(FALSE);
+ default:
+ /* suitable for materialization */
+ break;
+ }
+ }
+
+ in_subs->types_allow_materialization= TRUE;
+ in_subs->sjm_scan_allowed= all_are_fields;
+ DBUG_PRINT("info",("subquery_types_allow_materialization: ok, allowed"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Apply max min optimization of all/any subselect
+*/
+
+bool JOIN::transform_max_min_subquery()
+{
+ DBUG_ENTER("JOIN::transform_max_min_subquery");
+ Item_subselect *subselect= unit->item;
+ if (!subselect || (subselect->substype() != Item_subselect::ALL_SUBS &&
+ subselect->substype() != Item_subselect::ANY_SUBS))
+ DBUG_RETURN(0);
+ DBUG_RETURN(((Item_allany_subselect *) subselect)->
+ transform_into_max_min(this));
+}
+
+
+/*
+ Finalize IN->EXISTS conversion in case we couldn't use materialization.
+
+ DESCRIPTION Invoke the IN->EXISTS converter
+ Replace the Item_in_subselect with its wrapper Item_in_optimizer in WHERE.
+
+ RETURN
+ FALSE - Ok
+ TRUE - Fatal error
+*/
+
+bool make_in_exists_conversion(THD *thd, JOIN *join, Item_in_subselect *item)
+{
+ DBUG_ENTER("make_in_exists_conversion");
+ JOIN *child_join= item->unit->first_select()->join;
+ bool res;
+
+ /*
+ We're going to finalize IN->EXISTS conversion.
+ Normally, IN->EXISTS conversion takes place inside the
+ Item_subselect::fix_fields() call, where item_subselect->fixed==FALSE (as
+ fix_fields() haven't finished yet) and item_subselect->changed==FALSE (as
+ the conversion haven't been finalized)
+
+ At the end of Item_subselect::fix_fields() we had to set fixed=TRUE,
+ changed=TRUE (the only other option would have been to return error).
+
+ So, now we have to set these back for the duration of select_transformer()
+ call.
+ */
+ item->changed= 0;
+ item->fixed= 0;
+
+ SELECT_LEX *save_select_lex= thd->lex->current_select;
+ thd->lex->current_select= item->unit->first_select();
+
+ res= item->select_transformer(child_join);
+
+ thd->lex->current_select= save_select_lex;
+
+ if (res)
+ DBUG_RETURN(TRUE);
+
+ item->changed= 1;
+ item->fixed= 1;
+
+ Item *substitute= item->substitution;
+ bool do_fix_fields= !item->substitution->fixed;
+ /*
+ The Item_subselect has already been wrapped with Item_in_optimizer, so we
+ should search for item->optimizer, not 'item'.
+ */
+ Item *replace_me= item->optimizer;
+ DBUG_ASSERT(replace_me==substitute);
+
+ Item **tree= (item->emb_on_expr_nest == NO_JOIN_NEST)?
+ &join->conds : &(item->emb_on_expr_nest->on_expr);
+ if (replace_where_subcondition(join, tree, replace_me, substitute,
+ do_fix_fields))
+ DBUG_RETURN(TRUE);
+ item->substitution= NULL;
+
+ /*
+ If this is a prepared statement, repeat the above operation for
+ prep_where (or prep_on_expr).
+ */
+ if (!thd->stmt_arena->is_conventional())
+ {
+ tree= (item->emb_on_expr_nest == (TABLE_LIST*)NO_JOIN_NEST)?
+ &join->select_lex->prep_where :
+ &(item->emb_on_expr_nest->prep_on_expr);
+
+ if (replace_where_subcondition(join, tree, replace_me, substitute,
+ FALSE))
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+bool check_for_outer_joins(List<TABLE_LIST> *join_list)
+{
+ TABLE_LIST *table;
+ NESTED_JOIN *nested_join;
+ List_iterator<TABLE_LIST> li(*join_list);
+ while ((table= li++))
+ {
+ if ((nested_join= table->nested_join))
+ {
+ if (check_for_outer_joins(&nested_join->join_list))
+ return TRUE;
+ }
+
+ if (table->outer_join)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Convert semi-join subquery predicates into semi-join join nests
+
+ SYNOPSIS
+ convert_join_subqueries_to_semijoins()
+
+ DESCRIPTION
+
+ Convert candidate subquery predicates into semi-join join nests. This
+ transformation is performed once in query lifetime and is irreversible.
+
+ Conversion of one subquery predicate
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ We start with a join that has a semi-join subquery:
+
+ SELECT ...
+ FROM ot, ...
+ WHERE oe IN (SELECT ie FROM it1 ... itN WHERE subq_where) AND outer_where
+
+ and convert it into a semi-join nest:
+
+ SELECT ...
+ FROM ot SEMI JOIN (it1 ... itN), ...
+ WHERE outer_where AND subq_where AND oe=ie
+
+ that is, in order to do the conversion, we need to
+
+ * Create the "SEMI JOIN (it1 .. itN)" part and add it into the parent
+ query's FROM structure.
+ * Add "AND subq_where AND oe=ie" into parent query's WHERE (or ON if
+ the subquery predicate was in an ON expression)
+ * Remove the subquery predicate from the parent query's WHERE
+
+ Considerations when converting many predicates
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ A join may have at most MAX_TABLES tables. This may prevent us from
+ flattening all subqueries when the total number of tables in parent and
+ child selects exceeds MAX_TABLES.
+ We deal with this problem by flattening children's subqueries first and
+ then using a heuristic rule to determine each subquery predicate's
+ "priority".
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+bool convert_join_subqueries_to_semijoins(JOIN *join)
+{
+ Query_arena *arena, backup;
+ Item_in_subselect *in_subq;
+ THD *thd= join->thd;
+ List_iterator<TABLE_LIST> ti(join->select_lex->leaf_tables);
+ DBUG_ENTER("convert_join_subqueries_to_semijoins");
+
+ if (join->select_lex->sj_subselects.is_empty())
+ DBUG_RETURN(FALSE);
+
+ List_iterator_fast<Item_in_subselect> li(join->select_lex->sj_subselects);
+
+ while ((in_subq= li++))
+ {
+ SELECT_LEX *subq_sel= in_subq->get_select_lex();
+ if (subq_sel->handle_derived(thd->lex, DT_OPTIMIZE))
+ DBUG_RETURN(1);
+ if (subq_sel->handle_derived(thd->lex, DT_MERGE))
+ DBUG_RETURN(TRUE);
+ subq_sel->update_used_tables();
+ }
+
+ li.rewind();
+ /* First, convert child join's subqueries. We proceed bottom-up here */
+ while ((in_subq= li++))
+ {
+ st_select_lex *child_select= in_subq->get_select_lex();
+ JOIN *child_join= child_select->join;
+ child_join->outer_tables = child_join->table_count;
+
+ /*
+ child_select->where contains only the WHERE predicate of the
+ subquery itself here. We may be selecting from a VIEW, which has its
+ own predicate. The combined predicates are available in child_join->conds,
+ which was built by setup_conds() doing prepare_where() for all views.
+ */
+ child_select->where= child_join->conds;
+
+ if (convert_join_subqueries_to_semijoins(child_join))
+ DBUG_RETURN(TRUE);
+ in_subq->sj_convert_priority=
+ MY_TEST(in_subq->emb_on_expr_nest != NO_JOIN_NEST) * MAX_TABLES * 2 +
+ in_subq->is_correlated * MAX_TABLES + child_join->outer_tables;
+ }
+
+ // Temporary measure: disable semi-joins when they are together with outer
+ // joins.
+#if 0
+ if (check_for_outer_joins(join->join_list))
+ {
+ in_subq= join->select_lex->sj_subselects.head();
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ goto skip_conversion;
+ }
+#endif
+ //dump_TABLE_LIST_struct(select_lex, select_lex->leaf_tables);
+ /*
+ 2. Pick which subqueries to convert:
+ sort the subquery array
+ - prefer correlated subqueries over uncorrelated;
+ - prefer subqueries that have greater number of outer tables;
+ */
+ bubble_sort<Item_in_subselect>(&join->select_lex->sj_subselects,
+ subq_sj_candidate_cmp, NULL);
+ // #tables-in-parent-query + #tables-in-subquery < MAX_TABLES
+ /* Replace all subqueries to be flattened with Item_int(1) */
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ li.rewind();
+ while ((in_subq= li++))
+ {
+ bool remove_item= TRUE;
+
+ /* Stop processing if we've reached a subquery that's attached to the ON clause */
+ if (in_subq->emb_on_expr_nest != NO_JOIN_NEST)
+ break;
+
+ if (in_subq->is_flattenable_semijoin)
+ {
+ if (join->table_count +
+ in_subq->unit->first_select()->join->table_count >= MAX_TABLES)
+ break;
+ if (convert_subq_to_sj(join, in_subq))
+ goto restore_arena_and_fail;
+ }
+ else
+ {
+ if (join->table_count + 1 >= MAX_TABLES)
+ break;
+ if (convert_subq_to_jtbm(join, in_subq, &remove_item))
+ goto restore_arena_and_fail;
+ }
+ if (remove_item)
+ {
+ Item **tree= (in_subq->emb_on_expr_nest == NO_JOIN_NEST)?
+ &join->conds : &(in_subq->emb_on_expr_nest->on_expr);
+ Item *replace_me= in_subq->original_item();
+ if (replace_where_subcondition(join, tree, replace_me, new Item_int(1),
+ FALSE))
+ goto restore_arena_and_fail;
+ }
+ }
+//skip_conversion:
+ /*
+ 3. Finalize (perform IN->EXISTS rewrite) the subqueries that we didn't
+ convert:
+ */
+ while (in_subq)
+ {
+ JOIN *child_join= in_subq->unit->first_select()->join;
+ in_subq->changed= 0;
+ in_subq->fixed= 0;
+
+ SELECT_LEX *save_select_lex= thd->lex->current_select;
+ thd->lex->current_select= in_subq->unit->first_select();
+
+ bool res= in_subq->select_transformer(child_join);
+
+ thd->lex->current_select= save_select_lex;
+
+ if (res)
+ DBUG_RETURN(TRUE);
+
+ in_subq->changed= 1;
+ in_subq->fixed= 1;
+
+ Item *substitute= in_subq->substitution;
+ bool do_fix_fields= !in_subq->substitution->fixed;
+ Item **tree= (in_subq->emb_on_expr_nest == NO_JOIN_NEST)?
+ &join->conds : &(in_subq->emb_on_expr_nest->on_expr);
+ Item *replace_me= in_subq->original_item();
+ if (replace_where_subcondition(join, tree, replace_me, substitute,
+ do_fix_fields))
+ DBUG_RETURN(TRUE);
+ in_subq->substitution= NULL;
+ /*
+ If this is a prepared statement, repeat the above operation for
+ prep_where (or prep_on_expr). Subquery-to-semijoin conversion is
+ done once for prepared statement.
+ */
+ if (!thd->stmt_arena->is_conventional())
+ {
+ tree= (in_subq->emb_on_expr_nest == NO_JOIN_NEST)?
+ &join->select_lex->prep_where :
+ &(in_subq->emb_on_expr_nest->prep_on_expr);
+ /*
+ prep_on_expr/ prep_where may be NULL in some cases.
+ If that is the case, do nothing - simplify_joins() will copy
+ ON/WHERE expression into prep_on_expr/prep_where.
+ */
+ if (*tree && replace_where_subcondition(join, tree, replace_me, substitute,
+ FALSE))
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Revert to the IN->EXISTS strategy in the rare case when the subquery could
+ not be flattened.
+ */
+ in_subq->reset_strategy(SUBS_IN_TO_EXISTS);
+ if (is_materialization_applicable(thd, in_subq,
+ in_subq->unit->first_select()))
+ {
+ in_subq->add_strategy(SUBS_MATERIALIZATION);
+ }
+
+ in_subq= li++;
+ }
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ join->select_lex->sj_subselects.empty();
+ DBUG_RETURN(FALSE);
+
+restore_arena_and_fail:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Get #output_rows and scan_time estimates for a "delayed" table.
+
+ SYNOPSIS
+ get_delayed_table_estimates()
+ table IN Table to get estimates for
+ out_rows OUT E(#rows in the table)
+ scan_time OUT E(scan_time).
+ startup_cost OUT cost to populate the table.
+
+ DESCRIPTION
+ Get #output_rows and scan_time estimates for a "delayed" table. By
+ "delayed" here we mean that the table is filled at the start of query
+ execution. This means that the optimizer can't use table statistics to
+ get #rows estimate for it, it has to call this function instead.
+
+ This function is expected to make different actions depending on the nature
+ of the table. At the moment there is only one kind of delayed tables,
+ non-flattenable semi-joins.
+*/
+
+void get_delayed_table_estimates(TABLE *table,
+ ha_rows *out_rows,
+ double *scan_time,
+ double *startup_cost)
+{
+ Item_in_subselect *item= table->pos_in_table_list->jtbm_subselect;
+
+ DBUG_ASSERT(item->engine->engine_type() ==
+ subselect_engine::HASH_SJ_ENGINE);
+
+ subselect_hash_sj_engine *hash_sj_engine=
+ ((subselect_hash_sj_engine*)item->engine);
+
+ *out_rows= (ha_rows)item->jtbm_record_count;
+ *startup_cost= item->jtbm_read_time;
+
+ /* Calculate cost of scanning the temptable */
+ double data_size= item->jtbm_record_count *
+ hash_sj_engine->tmp_table->s->reclength;
+ /* Do like in handler::read_time */
+ *scan_time= data_size/IO_SIZE + 2;
+}
+
+
+/**
+ @brief Replaces an expression destructively inside the expression tree of
+ the WHERE clase.
+
+ @note We substitute AND/OR structure because it was copied by
+ copy_andor_structure and some changes could be done in the copy but
+ should be left permanent, also there could be several layers of AND over
+ AND and OR over OR because ::fix_field() possibly is not called.
+
+ @param join The top-level query.
+ @param old_cond The expression to be replaced.
+ @param new_cond The expression to be substituted.
+ @param do_fix_fields If true, Item::fix_fields(THD*, Item**) is called for
+ the new expression.
+ @return <code>true</code> if there was an error, <code>false</code> if
+ successful.
+*/
+
+static bool replace_where_subcondition(JOIN *join, Item **expr,
+ Item *old_cond, Item *new_cond,
+ bool do_fix_fields)
+{
+ if (*expr == old_cond)
+ {
+ *expr= new_cond;
+ if (do_fix_fields)
+ new_cond->fix_fields(join->thd, expr);
+ return FALSE;
+ }
+
+ if ((*expr)->type() == Item::COND_ITEM)
+ {
+ List_iterator<Item> li(*((Item_cond*)(*expr))->argument_list());
+ Item *item;
+ while ((item= li++))
+ {
+ if (item == old_cond)
+ {
+ li.replace(new_cond);
+ if (do_fix_fields)
+ new_cond->fix_fields(join->thd, li.ref());
+ return FALSE;
+ }
+ else if (item->type() == Item::COND_ITEM)
+ {
+ replace_where_subcondition(join, li.ref(),
+ old_cond, new_cond,
+ do_fix_fields);
+ }
+ }
+ }
+ /*
+ We can come to here when
+ - we're doing replace operations on both on_expr and prep_on_expr
+ - on_expr is the same as prep_on_expr, or they share a sub-tree
+ (so, when we do replace in on_expr, we replace in prep_on_expr, too,
+ and when we try doing a replace in prep_on_expr, the item we wanted
+ to replace there has already been replaced)
+ */
+ return FALSE;
+}
+
+static int subq_sj_candidate_cmp(Item_in_subselect* el1, Item_in_subselect* el2,
+ void *arg)
+{
+ return (el1->sj_convert_priority > el2->sj_convert_priority) ? 1 :
+ ( (el1->sj_convert_priority == el2->sj_convert_priority)? 0 : -1);
+}
+
+
+/*
+ Convert a subquery predicate into a TABLE_LIST semi-join nest
+
+ SYNOPSIS
+ convert_subq_to_sj()
+ parent_join Parent join, the one that has subq_pred in its WHERE/ON
+ clause
+ subq_pred Subquery predicate to be converted
+
+ DESCRIPTION
+ Convert a subquery predicate into a TABLE_LIST semi-join nest. All the
+ prerequisites are already checked, so the conversion is always successfull.
+
+ Prepared Statements: the transformation is permanent:
+ - Changes in TABLE_LIST structures are naturally permanent
+ - Item tree changes are performed on statement MEM_ROOT:
+ = we activate statement MEM_ROOT
+ = this function is called before the first fix_prepare_information
+ call.
+
+ This is intended because the criteria for subquery-to-sj conversion remain
+ constant for the lifetime of the Prepared Statement.
+
+ RETURN
+ FALSE OK
+ TRUE Out of memory error
+*/
+
+static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred)
+{
+ SELECT_LEX *parent_lex= parent_join->select_lex;
+ TABLE_LIST *emb_tbl_nest= NULL;
+ List<TABLE_LIST> *emb_join_list= &parent_lex->top_join_list;
+ THD *thd= parent_join->thd;
+ DBUG_ENTER("convert_subq_to_sj");
+
+ /*
+ 1. Find out where to put the predicate into.
+ Note: for "t1 LEFT JOIN t2" this will be t2, a leaf.
+ */
+ if ((void*)subq_pred->emb_on_expr_nest != (void*)NO_JOIN_NEST)
+ {
+ if (subq_pred->emb_on_expr_nest->nested_join)
+ {
+ /*
+ We're dealing with
+
+ ... [LEFT] JOIN ( ... ) ON (subquery AND whatever) ...
+
+ The sj-nest will be inserted into the brackets nest.
+ */
+ emb_tbl_nest= subq_pred->emb_on_expr_nest;
+ emb_join_list= &emb_tbl_nest->nested_join->join_list;
+ }
+ else if (!subq_pred->emb_on_expr_nest->outer_join)
+ {
+ /*
+ We're dealing with
+
+ ... INNER JOIN tblX ON (subquery AND whatever) ...
+
+ The sj-nest will be tblX's "sibling", i.e. another child of its
+ parent. This is ok because tblX is joined as an inner join.
+ */
+ emb_tbl_nest= subq_pred->emb_on_expr_nest->embedding;
+ if (emb_tbl_nest)
+ emb_join_list= &emb_tbl_nest->nested_join->join_list;
+ }
+ else if (!subq_pred->emb_on_expr_nest->nested_join)
+ {
+ TABLE_LIST *outer_tbl= subq_pred->emb_on_expr_nest;
+ TABLE_LIST *wrap_nest;
+ /*
+ We're dealing with
+
+ ... LEFT JOIN tbl ON (on_expr AND subq_pred) ...
+
+ we'll need to convert it into:
+
+ ... LEFT JOIN ( tbl SJ (subq_tables) ) ON (on_expr AND subq_pred) ...
+ | |
+ |<----- wrap_nest ---->|
+
+ Q: other subqueries may be pointing to this element. What to do?
+ A1: simple solution: copy *subq_pred->expr_join_nest= *parent_nest.
+ But we'll need to fix other pointers.
+ A2: Another way: have TABLE_LIST::next_ptr so the following
+ subqueries know the table has been nested.
+ A3: changes in the TABLE_LIST::outer_join will make everything work
+ automatically.
+ */
+ if (!(wrap_nest= alloc_join_nest(parent_join->thd)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ wrap_nest->embedding= outer_tbl->embedding;
+ wrap_nest->join_list= outer_tbl->join_list;
+ wrap_nest->alias= (char*) "(sj-wrap)";
+
+ wrap_nest->nested_join->join_list.empty();
+ wrap_nest->nested_join->join_list.push_back(outer_tbl);
+
+ outer_tbl->embedding= wrap_nest;
+ outer_tbl->join_list= &wrap_nest->nested_join->join_list;
+
+ /*
+ wrap_nest will take place of outer_tbl, so move the outer join flag
+ and on_expr
+ */
+ wrap_nest->outer_join= outer_tbl->outer_join;
+ outer_tbl->outer_join= 0;
+
+ wrap_nest->on_expr= outer_tbl->on_expr;
+ outer_tbl->on_expr= NULL;
+
+ List_iterator<TABLE_LIST> li(*wrap_nest->join_list);
+ TABLE_LIST *tbl;
+ while ((tbl= li++))
+ {
+ if (tbl == outer_tbl)
+ {
+ li.replace(wrap_nest);
+ break;
+ }
+ }
+ /*
+ Ok now wrap_nest 'contains' outer_tbl and we're ready to add the
+ semi-join nest into it
+ */
+ emb_join_list= &wrap_nest->nested_join->join_list;
+ emb_tbl_nest= wrap_nest;
+ }
+ }
+
+ TABLE_LIST *sj_nest;
+ NESTED_JOIN *nested_join;
+ if (!(sj_nest= alloc_join_nest(parent_join->thd)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ nested_join= sj_nest->nested_join;
+
+ sj_nest->join_list= emb_join_list;
+ sj_nest->embedding= emb_tbl_nest;
+ sj_nest->alias= (char*) "(sj-nest)";
+ sj_nest->sj_subq_pred= subq_pred;
+ sj_nest->original_subq_pred_used_tables= subq_pred->used_tables() |
+ subq_pred->left_expr->used_tables();
+ /* Nests do not participate in those 'chains', so: */
+ /* sj_nest->next_leaf= sj_nest->next_local= sj_nest->next_global == NULL*/
+ emb_join_list->push_back(sj_nest);
+
+ /*
+ nested_join->used_tables and nested_join->not_null_tables are
+ initialized in simplify_joins().
+ */
+
+ /*
+ 2. Walk through subquery's top list and set 'embedding' to point to the
+ sj-nest.
+ */
+ st_select_lex *subq_lex= subq_pred->unit->first_select();
+ nested_join->join_list.empty();
+ List_iterator_fast<TABLE_LIST> li(subq_lex->top_join_list);
+ TABLE_LIST *tl;
+ while ((tl= li++))
+ {
+ tl->embedding= sj_nest;
+ tl->join_list= &nested_join->join_list;
+ nested_join->join_list.push_back(tl);
+ }
+
+ /*
+ Reconnect the next_leaf chain.
+ TODO: Do we have to put subquery's tables at the end of the chain?
+ Inserting them at the beginning would be a bit faster.
+ NOTE: We actually insert them at the front! That's because the order is
+ reversed in this list.
+ */
+ parent_lex->leaf_tables.concat(&subq_lex->leaf_tables);
+
+ if (subq_lex->options & OPTION_SCHEMA_TABLE)
+ parent_lex->options |= OPTION_SCHEMA_TABLE;
+
+ /*
+ Same as above for next_local chain
+ (a theory: a next_local chain always starts with ::leaf_tables
+ because view's tables are inserted after the view)
+ */
+
+ for (tl= (TABLE_LIST*)(parent_lex->table_list.first); tl->next_local; tl= tl->next_local)
+ {}
+
+ tl->next_local= subq_lex->join->tables_list;
+
+ /* A theory: no need to re-connect the next_global chain */
+
+ /* 3. Remove the original subquery predicate from the WHERE/ON */
+
+ // The subqueries were replaced for Item_int(1) earlier
+ subq_pred->reset_strategy(SUBS_SEMI_JOIN); // for subsequent executions
+ /*TODO: also reset the 'with_subselect' there. */
+
+ /* n. Adjust the parent_join->table_count counter */
+ uint table_no= parent_join->table_count;
+ /* n. Walk through child's tables and adjust table->map */
+ List_iterator_fast<TABLE_LIST> si(subq_lex->leaf_tables);
+ while ((tl= si++))
+ {
+ tl->set_tablenr(table_no);
+ if (tl->is_jtbm())
+ tl->jtbm_table_no= table_no;
+ SELECT_LEX *old_sl= tl->select_lex;
+ tl->select_lex= parent_join->select_lex;
+ for (TABLE_LIST *emb= tl->embedding;
+ emb && emb->select_lex == old_sl;
+ emb= emb->embedding)
+ emb->select_lex= parent_join->select_lex;
+ table_no++;
+ }
+ parent_join->table_count += subq_lex->join->table_count;
+ //parent_join->table_count += subq_lex->leaf_tables.elements;
+
+ /*
+ Put the subquery's WHERE into semi-join's sj_on_expr
+ Add the subquery-induced equalities too.
+ */
+ SELECT_LEX *save_lex= thd->lex->current_select;
+ thd->lex->current_select=subq_lex;
+ if (!subq_pred->left_expr->fixed &&
+ subq_pred->left_expr->fix_fields(thd, &subq_pred->left_expr))
+ DBUG_RETURN(TRUE);
+ thd->lex->current_select=save_lex;
+
+ sj_nest->nested_join->sj_corr_tables= subq_pred->used_tables();
+ sj_nest->nested_join->sj_depends_on= subq_pred->used_tables() |
+ subq_pred->left_expr->used_tables();
+ sj_nest->sj_on_expr= subq_lex->join->conds;
+
+ /*
+ Create the IN-equalities and inject them into semi-join's ON expression.
+ Additionally, for LooseScan strategy
+ - Record the number of IN-equalities.
+ - Create list of pointers to (oe1, ..., ieN). We'll need the list to
+ see which of the expressions are bound and which are not (for those
+ we'll produce a distinct stream of (ie_i1,...ie_ik).
+
+ (TODO: can we just create a list of pointers and hope the expressions
+ will not substitute themselves on fix_fields()? or we need to wrap
+ them into Item_direct_view_refs and store pointers to those. The
+ pointers to Item_direct_view_refs are guaranteed to be stable as
+ Item_direct_view_refs doesn't substitute itself with anything in
+ Item_direct_view_ref::fix_fields.
+ */
+ sj_nest->sj_in_exprs= subq_pred->left_expr->cols();
+ sj_nest->nested_join->sj_outer_expr_list.empty();
+
+ if (subq_pred->left_expr->cols() == 1)
+ {
+ nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr);
+ Item_func_eq *item_eq=
+ new Item_func_eq(subq_pred->left_expr, subq_lex->ref_pointer_array[0]);
+ item_eq->in_equality_no= 0;
+ sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
+ }
+ else
+ {
+ for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
+ {
+ nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr->
+ element_index(i));
+ Item_func_eq *item_eq=
+ new Item_func_eq(subq_pred->left_expr->element_index(i),
+ subq_lex->ref_pointer_array[i]);
+ item_eq->in_equality_no= i;
+ sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
+ }
+ }
+ /*
+ Fix the created equality and AND
+
+ Note that fix_fields() can actually fail in a meaningful way here. One
+ example is when the IN-equality is not valid, because it compares columns
+ with incompatible collations. (One can argue it would be more appropriate
+ to check for this at name resolution stage, but as a legacy of IN->EXISTS
+ we have in here).
+ */
+ if (!sj_nest->sj_on_expr->fixed &&
+ sj_nest->sj_on_expr->fix_fields(parent_join->thd, &sj_nest->sj_on_expr))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Walk through sj nest's WHERE and ON expressions and call
+ item->fix_table_changes() for all items.
+ */
+ sj_nest->sj_on_expr->fix_after_pullout(parent_lex, &sj_nest->sj_on_expr);
+ fix_list_after_tbl_changes(parent_lex, &sj_nest->nested_join->join_list);
+
+
+ /* Unlink the child select_lex so it doesn't show up in EXPLAIN: */
+ subq_lex->master_unit()->exclude_level();
+
+ DBUG_EXECUTE("where",
+ print_where(sj_nest->sj_on_expr,"SJ-EXPR", QT_ORDINARY););
+
+ /* Inject sj_on_expr into the parent's WHERE or ON */
+ if (emb_tbl_nest)
+ {
+ emb_tbl_nest->on_expr= and_items(emb_tbl_nest->on_expr,
+ sj_nest->sj_on_expr);
+ emb_tbl_nest->on_expr->top_level_item();
+ if (!emb_tbl_nest->on_expr->fixed &&
+ emb_tbl_nest->on_expr->fix_fields(parent_join->thd,
+ &emb_tbl_nest->on_expr))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else
+ {
+ /* Inject into the WHERE */
+ parent_join->conds= and_items(parent_join->conds, sj_nest->sj_on_expr);
+ parent_join->conds->top_level_item();
+ /*
+ fix_fields must update the properties (e.g. st_select_lex::cond_count of
+ the correct select_lex.
+ */
+ save_lex= thd->lex->current_select;
+ thd->lex->current_select=parent_join->select_lex;
+ if (!parent_join->conds->fixed &&
+ parent_join->conds->fix_fields(parent_join->thd,
+ &parent_join->conds))
+ {
+ DBUG_RETURN(1);
+ }
+ thd->lex->current_select=save_lex;
+ parent_join->select_lex->where= parent_join->conds;
+ }
+
+ if (subq_lex->ftfunc_list->elements)
+ {
+ Item_func_match *ifm;
+ List_iterator_fast<Item_func_match> li(*(subq_lex->ftfunc_list));
+ while ((ifm= li++))
+ parent_lex->ftfunc_list->push_front(ifm);
+ }
+
+ parent_lex->have_merged_subqueries= TRUE;
+ DBUG_RETURN(FALSE);
+}
+
+
+const int SUBQERY_TEMPTABLE_NAME_MAX_LEN= 20;
+
+static void create_subquery_temptable_name(char *to, uint number)
+{
+ DBUG_ASSERT(number < 10000);
+ to= strmov(to, "<subquery");
+ to= int10_to_str((int) number, to, 10);
+ to[0]= '>';
+ to[1]= 0;
+}
+
+
+/*
+ Convert subquery predicate into non-mergeable semi-join nest.
+
+ TODO:
+ why does this do IN-EXISTS conversion? Can't we unify it with mergeable
+ semi-joins? currently, convert_subq_to_sj() cannot fail to convert (unless
+ fatal errors)
+
+
+ RETURN
+ FALSE - Ok
+ TRUE - Fatal error
+*/
+
+static bool convert_subq_to_jtbm(JOIN *parent_join,
+ Item_in_subselect *subq_pred,
+ bool *remove_item)
+{
+ SELECT_LEX *parent_lex= parent_join->select_lex;
+ List<TABLE_LIST> *emb_join_list= &parent_lex->top_join_list;
+ TABLE_LIST *emb_tbl_nest= NULL; // will change when we learn to handle outer joins
+ TABLE_LIST *tl;
+ DBUG_ENTER("convert_subq_to_jtbm");
+ bool optimization_delayed= TRUE;
+ subq_pred->set_strategy(SUBS_MATERIALIZATION);
+
+ subq_pred->is_jtbm_merged= TRUE;
+
+ *remove_item= TRUE;
+
+ TABLE_LIST *jtbm;
+ char *tbl_alias;
+ if (!(tbl_alias= (char*)parent_join->thd->calloc(SUBQERY_TEMPTABLE_NAME_MAX_LEN)) ||
+ !(jtbm= alloc_join_nest(parent_join->thd))) //todo: this is not a join nest!
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ jtbm->join_list= emb_join_list;
+ jtbm->embedding= emb_tbl_nest;
+ jtbm->jtbm_subselect= subq_pred;
+ jtbm->nested_join= NULL;
+
+ /* Nests do not participate in those 'chains', so: */
+ /* jtbm->next_leaf= jtbm->next_local= jtbm->next_global == NULL*/
+ emb_join_list->push_back(jtbm);
+
+ /*
+ Inject the jtbm table into TABLE_LIST::next_leaf list, so that
+ make_join_statistics() and co. can find it.
+ */
+ parent_lex->leaf_tables.push_back(jtbm);
+
+ if (subq_pred->unit->first_select()->options & OPTION_SCHEMA_TABLE)
+ parent_lex->options |= OPTION_SCHEMA_TABLE;
+
+ /*
+ Same as above for TABLE_LIST::next_local chain
+ (a theory: a next_local chain always starts with ::leaf_tables
+ because view's tables are inserted after the view)
+ */
+ for (tl= (TABLE_LIST*)(parent_lex->table_list.first); tl->next_local; tl= tl->next_local)
+ {}
+ tl->next_local= jtbm;
+
+ /* A theory: no need to re-connect the next_global chain */
+ if (optimization_delayed)
+ {
+ DBUG_ASSERT(parent_join->table_count < MAX_TABLES);
+
+ jtbm->jtbm_table_no= parent_join->table_count;
+
+ create_subquery_temptable_name(tbl_alias,
+ subq_pred->unit->first_select()->select_number);
+ jtbm->alias= tbl_alias;
+ parent_join->table_count++;
+ DBUG_RETURN(FALSE);
+ }
+ subselect_hash_sj_engine *hash_sj_engine=
+ ((subselect_hash_sj_engine*)subq_pred->engine);
+ jtbm->table= hash_sj_engine->tmp_table;
+
+ jtbm->table->tablenr= parent_join->table_count;
+ jtbm->table->map= table_map(1) << (parent_join->table_count);
+ jtbm->jtbm_table_no= jtbm->table->tablenr;
+
+ parent_join->table_count++;
+ DBUG_ASSERT(parent_join->table_count < MAX_TABLES);
+
+ Item *conds= hash_sj_engine->semi_join_conds;
+ conds->fix_after_pullout(parent_lex, &conds);
+
+ DBUG_EXECUTE("where", print_where(conds,"SJ-EXPR", QT_ORDINARY););
+
+ create_subquery_temptable_name(tbl_alias, hash_sj_engine->materialize_join->
+ select_lex->select_number);
+ jtbm->alias= tbl_alias;
+
+ parent_lex->have_merged_subqueries= TRUE;
+#if 0
+ /* Inject sj_on_expr into the parent's WHERE or ON */
+ if (emb_tbl_nest)
+ {
+ DBUG_ASSERT(0);
+ /*emb_tbl_nest->on_expr= and_items(emb_tbl_nest->on_expr,
+ sj_nest->sj_on_expr);
+ emb_tbl_nest->on_expr->fix_fields(parent_join->thd, &emb_tbl_nest->on_expr);
+ */
+ }
+ else
+ {
+ /* Inject into the WHERE */
+ parent_join->conds= and_items(parent_join->conds, conds);
+ parent_join->conds->fix_fields(parent_join->thd, &parent_join->conds);
+ parent_join->select_lex->where= parent_join->conds;
+ }
+#endif
+ /* Don't unlink the child subselect, as the subquery will be used. */
+
+ DBUG_RETURN(FALSE);
+}
+
+
+static TABLE_LIST *alloc_join_nest(THD *thd)
+{
+ TABLE_LIST *tbl;
+ if (!(tbl= (TABLE_LIST*) thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST))+
+ sizeof(NESTED_JOIN))))
+ return NULL;
+ tbl->nested_join= (NESTED_JOIN*) ((uchar*)tbl +
+ ALIGN_SIZE(sizeof(TABLE_LIST)));
+ return tbl;
+}
+
+
+void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist)
+{
+ List_iterator<TABLE_LIST> it(*tlist);
+ TABLE_LIST *table;
+ while ((table= it++))
+ {
+ if (table->on_expr)
+ table->on_expr->fix_after_pullout(new_parent, &table->on_expr);
+ if (table->nested_join)
+ fix_list_after_tbl_changes(new_parent, &table->nested_join->join_list);
+ }
+}
+
+
+static void set_emb_join_nest(List<TABLE_LIST> *tables, TABLE_LIST *emb_sj_nest)
+{
+ List_iterator<TABLE_LIST> it(*tables);
+ TABLE_LIST *tbl;
+ while ((tbl= it++))
+ {
+ /*
+ Note: check for nested_join first.
+ derived-merged tables have tbl->table!=NULL &&
+ tbl->table->reginfo==NULL.
+ */
+ if (tbl->nested_join)
+ set_emb_join_nest(&tbl->nested_join->join_list, emb_sj_nest);
+ else if (tbl->table)
+ tbl->table->reginfo.join_tab->emb_sj_nest= emb_sj_nest;
+
+ }
+}
+
+/*
+ Pull tables out of semi-join nests, if possible
+
+ SYNOPSIS
+ pull_out_semijoin_tables()
+ join The join where to do the semi-join flattening
+
+ DESCRIPTION
+ Try to pull tables out of semi-join nests.
+
+ PRECONDITIONS
+ When this function is called, the join may have several semi-join nests
+ but it is guaranteed that one semi-join nest does not contain another.
+
+ ACTION
+ A table can be pulled out of the semi-join nest if
+ - It is a constant table, or
+ - It is accessed via eq_ref(outer_tables)
+
+ POSTCONDITIONS
+ * Tables that were pulled out have JOIN_TAB::emb_sj_nest == NULL
+ * Tables that were not pulled out have JOIN_TAB::emb_sj_nest pointing
+ to semi-join nest they are in.
+ * Semi-join nests' TABLE_LIST::sj_inner_tables is updated accordingly
+
+ This operation is (and should be) performed at each PS execution since
+ tables may become/cease to be constant across PS reexecutions.
+
+ NOTE
+ Table pullout may make uncorrelated subquery correlated. Consider this
+ example:
+
+ ... WHERE oe IN (SELECT it1.primary_key WHERE p(it1, it2) ... )
+
+ here table it1 can be pulled out (we have it1.primary_key=oe which gives
+ us functional dependency). Once it1 is pulled out, all references to it1
+ from p(it1, it2) become references to outside of the subquery and thus
+ make the subquery (i.e. its semi-join nest) correlated.
+ Making the subquery (i.e. its semi-join nest) correlated prevents us from
+ using Materialization or LooseScan to execute it.
+
+ RETURN
+ 0 - OK
+ 1 - Out of memory error
+*/
+
+int pull_out_semijoin_tables(JOIN *join)
+{
+ TABLE_LIST *sj_nest;
+ DBUG_ENTER("pull_out_semijoin_tables");
+ List_iterator<TABLE_LIST> sj_list_it(join->select_lex->sj_nests);
+
+ /* Try pulling out of the each of the semi-joins */
+ while ((sj_nest= sj_list_it++))
+ {
+ List_iterator<TABLE_LIST> child_li(sj_nest->nested_join->join_list);
+ TABLE_LIST *tbl;
+
+ /*
+ Don't do table pull-out for nested joins (if we get nested joins here, it
+ means these are outer joins. It is theoretically possible to do pull-out
+ for some of the outer tables but we dont support this currently.
+ */
+ bool have_join_nest_children= FALSE;
+
+ set_emb_join_nest(&sj_nest->nested_join->join_list, sj_nest);
+
+ while ((tbl= child_li++))
+ {
+ if (tbl->nested_join)
+ {
+ have_join_nest_children= TRUE;
+ break;
+ }
+ }
+
+ table_map pulled_tables= 0;
+ table_map dep_tables= 0;
+ if (have_join_nest_children)
+ goto skip;
+
+ /*
+ Calculate set of tables within this semi-join nest that have
+ other dependent tables
+ */
+ child_li.rewind();
+ while ((tbl= child_li++))
+ {
+ TABLE *const table= tbl->table;
+ if (table &&
+ (table->reginfo.join_tab->dependent &
+ sj_nest->nested_join->used_tables))
+ dep_tables|= table->reginfo.join_tab->dependent;
+ }
+
+ /* Action #1: Mark the constant tables to be pulled out */
+ child_li.rewind();
+ while ((tbl= child_li++))
+ {
+ if (tbl->table)
+ {
+ tbl->table->reginfo.join_tab->emb_sj_nest= sj_nest;
+#if 0
+ /*
+ Do not pull out tables because they are constant. This operation has
+ a problem:
+ - Some constant tables may become/cease to be constant across PS
+ re-executions
+ - Contrary to our initial assumption, it turned out that table pullout
+ operation is not easily undoable.
+
+ The solution is to leave constant tables where they are. This will
+ affect only constant tables that are 1-row or empty, tables that are
+ constant because they are accessed via eq_ref(const) access will
+ still be pulled out as functionally-dependent.
+
+ This will cause us to miss the chance to flatten some of the
+ subqueries, but since const tables do not generate many duplicates,
+ it really doesn't matter that much whether they were pulled out or
+ not.
+
+ All of this was done as fix for BUG#43768.
+ */
+ if (tbl->table->map & join->const_table_map)
+ {
+ pulled_tables |= tbl->table->map;
+ DBUG_PRINT("info", ("Table %s pulled out (reason: constant)",
+ tbl->table->alias));
+ }
+#endif
+ }
+ }
+
+ /*
+ Action #2: Find which tables we can pull out based on
+ update_ref_and_keys() data. Note that pulling one table out can allow
+ us to pull out some other tables too.
+ */
+ bool pulled_a_table;
+ do
+ {
+ pulled_a_table= FALSE;
+ child_li.rewind();
+ while ((tbl= child_li++))
+ {
+ if (tbl->table && !(pulled_tables & tbl->table->map) &&
+ !(dep_tables & tbl->table->map))
+ {
+ if (find_eq_ref_candidate(tbl->table,
+ sj_nest->nested_join->used_tables &
+ ~pulled_tables))
+ {
+ pulled_a_table= TRUE;
+ pulled_tables |= tbl->table->map;
+ DBUG_PRINT("info", ("Table %s pulled out (reason: func dep)",
+ tbl->table->alias.c_ptr()));
+ /*
+ Pulling a table out of uncorrelated subquery in general makes
+ makes it correlated. See the NOTE to this funtion.
+ */
+ sj_nest->sj_subq_pred->is_correlated= TRUE;
+ sj_nest->nested_join->sj_corr_tables|= tbl->table->map;
+ sj_nest->nested_join->sj_depends_on|= tbl->table->map;
+ }
+ }
+ }
+ } while (pulled_a_table);
+
+ child_li.rewind();
+ skip:
+ /*
+ Action #3: Move the pulled out TABLE_LIST elements to the parents.
+ */
+ table_map inner_tables= sj_nest->nested_join->used_tables &
+ ~pulled_tables;
+ /* Record the bitmap of inner tables */
+ sj_nest->sj_inner_tables= inner_tables;
+ if (pulled_tables)
+ {
+ List<TABLE_LIST> *upper_join_list= (sj_nest->embedding != NULL)?
+ (&sj_nest->embedding->nested_join->join_list):
+ (&join->select_lex->top_join_list);
+ Query_arena *arena, backup;
+ arena= join->thd->activate_stmt_arena_if_needed(&backup);
+ while ((tbl= child_li++))
+ {
+ if (tbl->table)
+ {
+ if (inner_tables & tbl->table->map)
+ {
+ /* This table is not pulled out */
+ tbl->table->reginfo.join_tab->emb_sj_nest= sj_nest;
+ }
+ else
+ {
+ /* This table has been pulled out of the semi-join nest */
+ tbl->table->reginfo.join_tab->emb_sj_nest= NULL;
+ /*
+ Pull the table up in the same way as simplify_joins() does:
+ update join_list and embedding pointers but keep next[_local]
+ pointers.
+ */
+ child_li.remove();
+ sj_nest->nested_join->used_tables &= ~tbl->table->map;
+ upper_join_list->push_back(tbl);
+ tbl->join_list= upper_join_list;
+ tbl->embedding= sj_nest->embedding;
+ }
+ }
+ }
+
+ /* Remove the sj-nest itself if we've removed everything from it */
+ if (!inner_tables)
+ {
+ List_iterator<TABLE_LIST> li(*upper_join_list);
+ /* Find the sj_nest in the list. */
+ while (sj_nest != li++) ;
+ li.remove();
+ /* Also remove it from the list of SJ-nests: */
+ sj_list_it.remove();
+ }
+
+ if (arena)
+ join->thd->restore_active_arena(arena, &backup);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Optimize semi-join nests that could be run with sj-materialization
+
+ SYNOPSIS
+ optimize_semijoin_nests()
+ join The join to optimize semi-join nests for
+ all_table_map Bitmap of all tables in the join
+
+ DESCRIPTION
+ Optimize each of the semi-join nests that can be run with
+ materialization. For each of the nests, we
+ - Generate the best join order for this "sub-join" and remember it;
+ - Remember the sub-join execution cost (it's part of materialization
+ cost);
+ - Calculate other costs that will be incurred if we decide
+ to use materialization strategy for this semi-join nest.
+
+ All obtained information is saved and will be used by the main join
+ optimization pass.
+
+ NOTES
+ Because of Join::reoptimize(), this function may be called multiple times.
+
+ RETURN
+ FALSE Ok
+ TRUE Out of memory error
+*/
+
+bool optimize_semijoin_nests(JOIN *join, table_map all_table_map)
+{
+ DBUG_ENTER("optimize_semijoin_nests");
+ List_iterator<TABLE_LIST> sj_list_it(join->select_lex->sj_nests);
+ TABLE_LIST *sj_nest;
+ while ((sj_nest= sj_list_it++))
+ {
+ /* semi-join nests with only constant tables are not valid */
+ /// DBUG_ASSERT(sj_nest->sj_inner_tables & ~join->const_table_map);
+
+ sj_nest->sj_mat_info= NULL;
+ /*
+ The statement may have been executed with 'semijoin=on' earlier.
+ We need to verify that 'semijoin=on' still holds.
+ */
+ if (optimizer_flag(join->thd, OPTIMIZER_SWITCH_SEMIJOIN) &&
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_MATERIALIZATION))
+ {
+ if ((sj_nest->sj_inner_tables & ~join->const_table_map) && /* not everything was pulled out */
+ !sj_nest->sj_subq_pred->is_correlated &&
+ sj_nest->sj_subq_pred->types_allow_materialization)
+ {
+ join->emb_sjm_nest= sj_nest;
+ if (choose_plan(join, all_table_map &~join->const_table_map))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ /*
+ The best plan to run the subquery is now in join->best_positions,
+ save it.
+ */
+ uint n_tables= my_count_bits(sj_nest->sj_inner_tables & ~join->const_table_map);
+ SJ_MATERIALIZATION_INFO* sjm;
+ if (!(sjm= new SJ_MATERIALIZATION_INFO) ||
+ !(sjm->positions= (POSITION*)join->thd->alloc(sizeof(POSITION)*
+ n_tables)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ sjm->tables= n_tables;
+ sjm->is_used= FALSE;
+ double subjoin_out_rows, subjoin_read_time;
+
+ /*
+ join->get_partial_cost_and_fanout(n_tables + join->const_tables,
+ table_map(-1),
+ &subjoin_read_time,
+ &subjoin_out_rows);
+ */
+ join->get_prefix_cost_and_fanout(n_tables,
+ &subjoin_read_time,
+ &subjoin_out_rows);
+
+ sjm->materialization_cost.convert_from_cost(subjoin_read_time);
+ sjm->rows= subjoin_out_rows;
+
+ // Don't use the following list because it has "stale" items. use
+ // ref_pointer_array instead:
+ //
+ //List<Item> &right_expr_list=
+ // sj_nest->sj_subq_pred->unit->first_select()->item_list;
+ /*
+ Adjust output cardinality estimates. If the subquery has form
+
+ ... oe IN (SELECT t1.colX, t2.colY, func(X,Y,Z) )
+
+ then the number of distinct output record combinations has an
+ upper bound of product of number of records matching the tables
+ that are used by the SELECT clause.
+ TODO:
+ We can get a more precise estimate if we
+ - use rec_per_key cardinality estimates. For simple cases like
+ "oe IN (SELECT t.key ...)" it is trivial.
+ - Functional dependencies between the tables in the semi-join
+ nest (the payoff is probably less here?)
+
+ See also get_post_group_estimate().
+ */
+ SELECT_LEX *subq_select= sj_nest->sj_subq_pred->unit->first_select();
+ {
+ for (uint i=0 ; i < join->const_tables + sjm->tables ; i++)
+ {
+ JOIN_TAB *tab= join->best_positions[i].table;
+ join->map2table[tab->table->tablenr]= tab;
+ }
+ //List_iterator<Item> it(right_expr_list);
+ Item **ref_array= subq_select->ref_pointer_array;
+ Item **ref_array_end= ref_array + subq_select->item_list.elements;
+ table_map map= 0;
+ //while ((item= it++))
+ for (;ref_array < ref_array_end; ref_array++)
+ map |= (*ref_array)->used_tables();
+ map= map & ~PSEUDO_TABLE_BITS;
+ Table_map_iterator tm_it(map);
+ int tableno;
+ double rows= 1.0;
+ while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
+ rows *= join->map2table[tableno]->table->quick_condition_rows;
+ sjm->rows= MY_MIN(sjm->rows, rows);
+ }
+ memcpy(sjm->positions, join->best_positions + join->const_tables,
+ sizeof(POSITION) * n_tables);
+
+ /*
+ Calculate temporary table parameters and usage costs
+ */
+ uint rowlen= get_tmp_table_rec_length(subq_select->ref_pointer_array,
+ subq_select->item_list.elements);
+ double lookup_cost= get_tmp_table_lookup_cost(join->thd,
+ subjoin_out_rows, rowlen);
+ double write_cost= get_tmp_table_write_cost(join->thd,
+ subjoin_out_rows, rowlen);
+
+ /*
+ Let materialization cost include the cost to write the data into the
+ temporary table:
+ */
+ sjm->materialization_cost.add_io(subjoin_out_rows, write_cost);
+
+ /*
+ Set the cost to do a full scan of the temptable (will need this to
+ consider doing sjm-scan):
+ */
+ sjm->scan_cost.reset();
+ sjm->scan_cost.add_io(sjm->rows, lookup_cost);
+
+ sjm->lookup_cost.convert_from_cost(lookup_cost);
+ sj_nest->sj_mat_info= sjm;
+ DBUG_EXECUTE("opt", print_sjm(sjm););
+ }
+ }
+ }
+ join->emb_sjm_nest= NULL;
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Get estimated record length for semi-join materialization temptable
+
+ SYNOPSIS
+ get_tmp_table_rec_length()
+ items IN subquery's select list.
+
+ DESCRIPTION
+ Calculate estimated record length for semi-join materialization
+ temptable. It's an estimate because we don't follow every bit of
+ create_tmp_table()'s logic. This isn't necessary as the return value of
+ this function is used only for cost calculations.
+
+ RETURN
+ Length of the temptable record, in bytes
+*/
+
+static uint get_tmp_table_rec_length(Item **p_items, uint elements)
+{
+ uint len= 0;
+ Item *item;
+ //List_iterator<Item> it(items);
+ Item **p_item;
+ for (p_item= p_items; p_item < p_items + elements ; p_item++)
+ {
+ item = *p_item;
+ switch (item->result_type()) {
+ case REAL_RESULT:
+ len += sizeof(double);
+ break;
+ case INT_RESULT:
+ if (item->max_length >= (MY_INT32_NUM_DECIMAL_DIGITS - 1))
+ len += 8;
+ else
+ len += 4;
+ break;
+ case STRING_RESULT:
+ enum enum_field_types type;
+ /* DATE/TIME and GEOMETRY fields have STRING_RESULT result type. */
+ if ((type= item->field_type()) == MYSQL_TYPE_DATETIME ||
+ type == MYSQL_TYPE_TIME || type == MYSQL_TYPE_DATE ||
+ type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_GEOMETRY)
+ len += 8;
+ else
+ len += item->max_length;
+ break;
+ case DECIMAL_RESULT:
+ len += 10;
+ break;
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0); /* purecov: deadcode */
+ break;
+ }
+ }
+ return len;
+}
+
+
+/**
+ The cost of a lookup into a unique hash/btree index on a temporary table
+ with 'row_count' rows each of size 'row_size'.
+
+ @param thd current query context
+ @param row_count number of rows in the temp table
+ @param row_size average size in bytes of the rows
+
+ @return the cost of one lookup
+*/
+
+static double
+get_tmp_table_lookup_cost(THD *thd, double row_count, uint row_size)
+{
+ if (row_count * row_size > thd->variables.max_heap_table_size)
+ return (double) DISK_TEMPTABLE_LOOKUP_COST;
+ else
+ return (double) HEAP_TEMPTABLE_LOOKUP_COST;
+}
+
+/**
+ The cost of writing a row into a temporary table with 'row_count' unique
+ rows each of size 'row_size'.
+
+ @param thd current query context
+ @param row_count number of rows in the temp table
+ @param row_size average size in bytes of the rows
+
+ @return the cost of writing one row
+*/
+
+static double
+get_tmp_table_write_cost(THD *thd, double row_count, uint row_size)
+{
+ double lookup_cost= get_tmp_table_lookup_cost(thd, row_count, row_size);
+ /*
+ TODO:
+ This is an optimistic estimate. Add additional costs resulting from
+ actually writing the row to memory/disk and possible index reorganization.
+ */
+ return lookup_cost;
+}
+
+
+/*
+ Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
+
+ SYNOPSIS
+ find_eq_ref_candidate()
+ table Table to be checked
+ sj_inner_tables Bitmap of inner tables. eq_ref(inner_table) doesn't
+ count.
+
+ DESCRIPTION
+ Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
+
+ TODO
+ Check again if it is feasible to factor common parts with constant table
+ search
+
+ Also check if it's feasible to factor common parts with table elimination
+
+ RETURN
+ TRUE - There exists an eq_ref(outer-tables) candidate
+ FALSE - Otherwise
+*/
+
+bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables)
+{
+ KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
+
+ if (keyuse)
+ {
+ do
+ {
+ uint key= keyuse->key;
+ KEY *keyinfo;
+ key_part_map bound_parts= 0;
+ bool is_excluded_key= keyuse->is_for_hash_join();
+ if (!is_excluded_key)
+ {
+ keyinfo= table->key_info + key;
+ is_excluded_key= !MY_TEST(keyinfo->flags & HA_NOSAME);
+ }
+ if (!is_excluded_key)
+ {
+ do /* For all equalities on all key parts */
+ {
+ /* Check if this is "t.keypart = expr(outer_tables) */
+ if (!(keyuse->used_tables & sj_inner_tables) &&
+ !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
+ {
+ bound_parts |= 1 << keyuse->keypart;
+ }
+ keyuse++;
+ } while (keyuse->key == key && keyuse->table == table);
+
+ if (bound_parts == PREV_BITS(uint, keyinfo->user_defined_key_parts))
+ return TRUE;
+ }
+ else
+ {
+ do
+ {
+ keyuse++;
+ } while (keyuse->key == key && keyuse->table == table);
+ }
+ } while (keyuse->table == table);
+ }
+ return FALSE;
+}
+
+
+/*
+ Do semi-join optimization step after we've added a new tab to join prefix
+
+ SYNOPSIS
+ advance_sj_state()
+ join The join we're optimizing
+ remaining_tables Tables not in the join prefix
+ new_join_tab Join tab we've just added to the join prefix
+ idx Index of this join tab (i.e. number of tables
+ in the prefix minus one)
+ current_record_count INOUT Estimate of #records in join prefix's output
+ current_read_time INOUT Cost to execute the join prefix
+ loose_scan_pos IN A POSITION with LooseScan plan to access
+ table new_join_tab
+ (produced by the last best_access_path call)
+
+ DESCRIPTION
+ Update semi-join optimization state after we've added another tab (table
+ and access method) to the join prefix.
+
+ The state is maintained in join->positions[#prefix_size]. Each of the
+ available strategies has its own state variables.
+
+ for each semi-join strategy
+ {
+ update strategy's state variables;
+
+ if (join prefix has all the tables that are needed to consider
+ using this strategy for the semi-join(s))
+ {
+ calculate cost of using the strategy
+ if ((this is the first strategy to handle the semi-join nest(s) ||
+ the cost is less than other strategies))
+ {
+ // Pick this strategy
+ pos->sj_strategy= ..
+ ..
+ }
+ }
+
+ Most of the new state is saved join->positions[idx] (and hence no undo
+ is necessary). Several members of class JOIN are updated also, these
+ changes can be rolled back with restore_prev_sj_state().
+
+ See setup_semijoin_dups_elimination() for a description of what kinds of
+ join prefixes each strategy can handle.
+*/
+
+bool is_multiple_semi_joins(JOIN *join, POSITION *prefix, uint idx, table_map inner_tables)
+{
+ for (int i= (int)idx; i >= 0; i--)
+ {
+ TABLE_LIST *emb_sj_nest;
+ if ((emb_sj_nest= prefix[i].table->emb_sj_nest))
+ {
+ if (inner_tables & emb_sj_nest->sj_inner_tables)
+ return !MY_TEST(inner_tables == (emb_sj_nest->sj_inner_tables &
+ ~join->const_table_map));
+ }
+ }
+ return FALSE;
+}
+
+
+void advance_sj_state(JOIN *join, table_map remaining_tables, uint idx,
+ double *current_record_count, double *current_read_time,
+ POSITION *loose_scan_pos)
+{
+ POSITION *pos= join->positions + idx;
+ const JOIN_TAB *new_join_tab= pos->table;
+ Semi_join_strategy_picker *pickers[]=
+ {
+ &pos->firstmatch_picker,
+ &pos->loosescan_picker,
+ &pos->sjmat_picker,
+ &pos->dups_weedout_picker,
+ NULL,
+ };
+
+ if (join->emb_sjm_nest)
+ {
+ /*
+ We're performing optimization inside SJ-Materialization nest:
+ - there are no other semi-joins inside semi-join nests
+ - attempts to build semi-join strategies here will confuse
+ the optimizer, so bail out.
+ */
+ pos->sj_strategy= SJ_OPT_NONE;
+ return;
+ }
+
+ /*
+ Update join->cur_sj_inner_tables (Used by FirstMatch in this function and
+ LooseScan detector in best_access_path)
+ */
+ remaining_tables &= ~new_join_tab->table->map;
+ table_map dups_producing_tables;
+
+ if (idx == join->const_tables)
+ dups_producing_tables= 0;
+ else
+ dups_producing_tables= pos[-1].dups_producing_tables;
+
+ TABLE_LIST *emb_sj_nest;
+ if ((emb_sj_nest= new_join_tab->emb_sj_nest))
+ dups_producing_tables |= emb_sj_nest->sj_inner_tables;
+
+ Semi_join_strategy_picker **strategy;
+ if (idx == join->const_tables)
+ {
+ /* First table, initialize pickers */
+ for (strategy= pickers; *strategy != NULL; strategy++)
+ (*strategy)->set_empty();
+ pos->inner_tables_handled_with_other_sjs= 0;
+ }
+ else
+ {
+ for (strategy= pickers; *strategy != NULL; strategy++)
+ {
+ (*strategy)->set_from_prev(pos - 1);
+ }
+ pos->inner_tables_handled_with_other_sjs=
+ pos[-1].inner_tables_handled_with_other_sjs;
+ }
+
+ pos->prefix_cost.convert_from_cost(*current_read_time);
+ pos->prefix_record_count= *current_record_count;
+
+ {
+ pos->sj_strategy= SJ_OPT_NONE;
+
+ for (strategy= pickers; *strategy != NULL; strategy++)
+ {
+ table_map handled_fanout;
+ sj_strategy_enum sj_strategy;
+ double rec_count= *current_record_count;
+ double read_time= *current_read_time;
+ if ((*strategy)->check_qep(join, idx, remaining_tables,
+ new_join_tab,
+ &rec_count,
+ &read_time,
+ &handled_fanout,
+ &sj_strategy,
+ loose_scan_pos))
+ {
+ /*
+ It's possible to use the strategy. Use it, if
+ - it removes semi-join fanout that was not removed before
+ - using it is cheaper than using something else,
+ and {if some other strategy has removed fanout
+ that this strategy is trying to remove, then it
+ did remove the fanout only for one semi-join}
+ This is to avoid a situation when
+ 1. strategy X removes fanout for semijoin X,Y
+ 2. using strategy Z is cheaper, but it only removes
+ fanout from semijoin X.
+ 3. We have no clue what to do about fanount of semi-join Y.
+ */
+ if ((dups_producing_tables & handled_fanout) ||
+ (read_time < *current_read_time &&
+ !(handled_fanout & pos->inner_tables_handled_with_other_sjs)))
+ {
+ /* Mark strategy as used */
+ (*strategy)->mark_used();
+ pos->sj_strategy= sj_strategy;
+ if (sj_strategy == SJ_OPT_MATERIALIZE)
+ join->sjm_lookup_tables |= handled_fanout;
+ else
+ join->sjm_lookup_tables &= ~handled_fanout;
+ *current_read_time= read_time;
+ *current_record_count= rec_count;
+ dups_producing_tables &= ~handled_fanout;
+ //TODO: update bitmap of semi-joins that were handled together with
+ // others.
+ if (is_multiple_semi_joins(join, join->positions, idx, handled_fanout))
+ pos->inner_tables_handled_with_other_sjs |= handled_fanout;
+ }
+ else
+ {
+ /* We decided not to apply the strategy. */
+ (*strategy)->set_empty();
+ }
+ }
+ }
+ }
+
+ if ((emb_sj_nest= new_join_tab->emb_sj_nest))
+ {
+ join->cur_sj_inner_tables |= emb_sj_nest->sj_inner_tables;
+
+ /* Remove the sj_nest if all of its SJ-inner tables are in cur_table_map */
+ if (!(remaining_tables &
+ emb_sj_nest->sj_inner_tables & ~new_join_tab->table->map))
+ join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
+ }
+
+ pos->prefix_cost.convert_from_cost(*current_read_time);
+ pos->prefix_record_count= *current_record_count;
+ pos->dups_producing_tables= dups_producing_tables;
+}
+
+
+void Sj_materialization_picker::set_from_prev(struct st_position *prev)
+{
+ if (prev->sjmat_picker.is_used)
+ set_empty();
+ else
+ {
+ sjm_scan_need_tables= prev->sjmat_picker.sjm_scan_need_tables;
+ sjm_scan_last_inner= prev->sjmat_picker.sjm_scan_last_inner;
+ }
+ is_used= FALSE;
+}
+
+
+bool Sj_materialization_picker::check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ POSITION *loose_scan_pos)
+{
+ bool sjm_scan;
+ SJ_MATERIALIZATION_INFO *mat_info;
+ if ((mat_info= at_sjmat_pos(join, remaining_tables,
+ new_join_tab, idx, &sjm_scan)))
+ {
+ if (sjm_scan)
+ {
+ /*
+ We can't yet evaluate this option yet. This is because we can't
+ accout for fanout of sj-inner tables yet:
+
+ ntX SJM-SCAN(it1 ... itN) | ot1 ... otN |
+ ^(1) ^(2)
+
+ we're now at position (1). SJM temptable in general has multiple
+ records, so at point (1) we'll get the fanout from sj-inner tables (ie
+ there will be multiple record combinations).
+
+ The final join result will not contain any semi-join produced
+ fanout, i.e. tables within SJM-SCAN(...) will not contribute to
+ the cardinality of the join output. Extra fanout produced by
+ SJM-SCAN(...) will be 'absorbed' into fanout produced by ot1 ... otN.
+
+ The simple way to model this is to remove SJM-SCAN(...) fanout once
+ we reach the point #2.
+ */
+ sjm_scan_need_tables=
+ new_join_tab->emb_sj_nest->sj_inner_tables |
+ new_join_tab->emb_sj_nest->nested_join->sj_depends_on |
+ new_join_tab->emb_sj_nest->nested_join->sj_corr_tables;
+ sjm_scan_last_inner= idx;
+ }
+ else
+ {
+ /* This is SJ-Materialization with lookups */
+ Cost_estimate prefix_cost;
+ signed int first_tab= (int)idx - mat_info->tables;
+ double prefix_rec_count;
+ if (first_tab < (int)join->const_tables)
+ {
+ prefix_cost.reset();
+ prefix_rec_count= 1.0;
+ }
+ else
+ {
+ prefix_cost= join->positions[first_tab].prefix_cost;
+ prefix_rec_count= join->positions[first_tab].prefix_record_count;
+ }
+
+ double mat_read_time= prefix_cost.total_cost();
+ mat_read_time += mat_info->materialization_cost.total_cost() +
+ prefix_rec_count * mat_info->lookup_cost.total_cost();
+
+ /*
+ NOTE: When we pick to use SJM[-Scan] we don't memcpy its POSITION
+ elements to join->positions as that makes it hard to return things
+ back when making one step back in join optimization. That's done
+ after the QEP has been chosen.
+ */
+ *read_time= mat_read_time;
+ *record_count= prefix_rec_count;
+ *handled_fanout= new_join_tab->emb_sj_nest->sj_inner_tables;
+ *strategy= SJ_OPT_MATERIALIZE;
+ return TRUE;
+ }
+ }
+
+ /* 4.A SJM-Scan second phase check */
+ if (sjm_scan_need_tables && /* Have SJM-Scan prefix */
+ !(sjm_scan_need_tables & remaining_tables))
+ {
+ TABLE_LIST *mat_nest=
+ join->positions[sjm_scan_last_inner].table->emb_sj_nest;
+ SJ_MATERIALIZATION_INFO *mat_info= mat_nest->sj_mat_info;
+
+ double prefix_cost;
+ double prefix_rec_count;
+ int first_tab= sjm_scan_last_inner + 1 - mat_info->tables;
+ /* Get the prefix cost */
+ if (first_tab == (int)join->const_tables)
+ {
+ prefix_rec_count= 1.0;
+ prefix_cost= 0.0;
+ }
+ else
+ {
+ prefix_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
+ prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
+ }
+
+ /* Add materialization cost */
+ prefix_cost += mat_info->materialization_cost.total_cost() +
+ prefix_rec_count * mat_info->scan_cost.total_cost();
+ prefix_rec_count *= mat_info->rows;
+
+ uint i;
+ table_map rem_tables= remaining_tables;
+ for (i= idx; i != (first_tab + mat_info->tables - 1); i--)
+ rem_tables |= join->positions[i].table->table->map;
+
+ POSITION curpos, dummy;
+ /* Need to re-run best-access-path as we prefix_rec_count has changed */
+ bool disable_jbuf= (join->thd->variables.join_cache_level == 0);
+ for (i= first_tab + mat_info->tables; i <= idx; i++)
+ {
+ best_access_path(join, join->positions[i].table, rem_tables, i,
+ disable_jbuf, prefix_rec_count, &curpos, &dummy);
+ prefix_rec_count *= curpos.records_read;
+ prefix_cost += curpos.read_time;
+ }
+
+ *strategy= SJ_OPT_MATERIALIZE_SCAN;
+ *read_time= prefix_cost;
+ *record_count= prefix_rec_count;
+ *handled_fanout= mat_nest->sj_inner_tables;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+void LooseScan_picker::set_from_prev(struct st_position *prev)
+{
+ if (prev->loosescan_picker.is_used)
+ set_empty();
+ else
+ {
+ first_loosescan_table= prev->loosescan_picker.first_loosescan_table;
+ loosescan_need_tables= prev->loosescan_picker.loosescan_need_tables;
+ }
+ is_used= FALSE;
+}
+
+
+bool LooseScan_picker::check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ struct st_position *loose_scan_pos)
+{
+ POSITION *first= join->positions + first_loosescan_table;
+ /*
+ LooseScan strategy can't handle interleaving between tables from the
+ semi-join that LooseScan is handling and any other tables.
+
+ If we were considering LooseScan for the join prefix (1)
+ and the table we're adding creates an interleaving (2)
+ then
+ stop considering loose scan
+ */
+ if ((first_loosescan_table != MAX_TABLES) && // (1)
+ (first->table->emb_sj_nest->sj_inner_tables & remaining_tables) && //(2)
+ new_join_tab->emb_sj_nest != first->table->emb_sj_nest) //(2)
+ {
+ first_loosescan_table= MAX_TABLES;
+ }
+
+ /*
+ If we got an option to use LooseScan for the current table, start
+ considering using LooseScan strategy
+ */
+ if (loose_scan_pos->read_time != DBL_MAX && !join->outer_join)
+ {
+ first_loosescan_table= idx;
+ loosescan_need_tables=
+ new_join_tab->emb_sj_nest->sj_inner_tables |
+ new_join_tab->emb_sj_nest->nested_join->sj_depends_on |
+ new_join_tab->emb_sj_nest->nested_join->sj_corr_tables;
+ }
+
+ if ((first_loosescan_table != MAX_TABLES) &&
+ !(remaining_tables & loosescan_need_tables) &&
+ (new_join_tab->table->map & loosescan_need_tables))
+ {
+ /*
+ Ok we have LooseScan plan and also have all LooseScan sj-nest's
+ inner tables and outer correlated tables into the prefix.
+ */
+
+ first= join->positions + first_loosescan_table;
+ uint n_tables= my_count_bits(first->table->emb_sj_nest->sj_inner_tables);
+ /* Got a complete LooseScan range. Calculate its cost */
+ /*
+ The same problem as with FirstMatch - we need to save POSITIONs
+ somewhere but reserving space for all cases would require too
+ much space. We will re-calculate POSITION structures later on.
+ */
+ bool disable_jbuf= (join->thd->variables.join_cache_level == 0);
+ optimize_wo_join_buffering(join, first_loosescan_table, idx,
+ remaining_tables,
+ TRUE, //first_alt
+ disable_jbuf ? join->table_count :
+ first_loosescan_table + n_tables,
+ record_count,
+ read_time);
+ /*
+ We don't yet have any other strategies that could handle this
+ semi-join nest (the other options are Duplicate Elimination or
+ Materialization, which need at least the same set of tables in
+ the join prefix to be considered) so unconditionally pick the
+ LooseScan.
+ */
+ *strategy= SJ_OPT_LOOSE_SCAN;
+ *handled_fanout= first->table->emb_sj_nest->sj_inner_tables;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void Firstmatch_picker::set_from_prev(struct st_position *prev)
+{
+ if (prev->firstmatch_picker.is_used)
+ invalidate_firstmatch_prefix();
+ else
+ {
+ first_firstmatch_table= prev->firstmatch_picker.first_firstmatch_table;
+ first_firstmatch_rtbl= prev->firstmatch_picker.first_firstmatch_rtbl;
+ firstmatch_need_tables= prev->firstmatch_picker.firstmatch_need_tables;
+ }
+ is_used= FALSE;
+}
+
+bool Firstmatch_picker::check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ POSITION *loose_scan_pos)
+{
+ if (new_join_tab->emb_sj_nest &&
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_FIRSTMATCH) &&
+ !join->outer_join)
+ {
+ const table_map outer_corr_tables=
+ new_join_tab->emb_sj_nest->nested_join->sj_corr_tables |
+ new_join_tab->emb_sj_nest->nested_join->sj_depends_on;
+ const table_map sj_inner_tables=
+ new_join_tab->emb_sj_nest->sj_inner_tables & ~join->const_table_map;
+
+ /*
+ Enter condition:
+ 1. The next join tab belongs to semi-join nest
+ (verified for the encompassing code block above).
+ 2. We're not in a duplicate producer range yet
+ 3. All outer tables that
+ - the subquery is correlated with, or
+ - referred to from the outer_expr
+ are in the join prefix
+ 4. All inner tables are still part of remaining_tables.
+ */
+ if (!join->cur_sj_inner_tables && // (2)
+ !(remaining_tables & outer_corr_tables) && // (3)
+ (sj_inner_tables == // (4)
+ ((remaining_tables | new_join_tab->table->map) & sj_inner_tables)))
+ {
+ /* Start tracking potential FirstMatch range */
+ first_firstmatch_table= idx;
+ firstmatch_need_tables= sj_inner_tables;
+ first_firstmatch_rtbl= remaining_tables;
+ }
+
+ if (in_firstmatch_prefix())
+ {
+ if (outer_corr_tables & first_firstmatch_rtbl)
+ {
+ /*
+ Trying to add an sj-inner table whose sj-nest has an outer correlated
+ table that was not in the prefix. This means FirstMatch can't be used.
+ */
+ invalidate_firstmatch_prefix();
+ }
+ else
+ {
+ /* Record that we need all of this semi-join's inner tables, too */
+ firstmatch_need_tables|= sj_inner_tables;
+ }
+
+ if (in_firstmatch_prefix() &&
+ !(firstmatch_need_tables & remaining_tables))
+ {
+ /*
+ Got a complete FirstMatch range. Calculate correct costs and fanout
+ */
+
+ if (idx == first_firstmatch_table &&
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_SEMIJOIN_WITH_CACHE))
+ {
+ /*
+ An important special case: only one inner table, and @@optimizer_switch
+ allows join buffering.
+ - read_time is the same (i.e. FirstMatch doesn't add any cost
+ - remove fanout added by the last table
+ */
+ if (*record_count)
+ *record_count /= join->positions[idx].records_read;
+ }
+ else
+ {
+ optimize_wo_join_buffering(join, first_firstmatch_table, idx,
+ remaining_tables, FALSE, idx,
+ record_count,
+ read_time);
+ }
+ /*
+ We ought to save the alternate POSITIONs produced by
+ optimize_wo_join_buffering but the problem is that providing save
+ space uses too much space. Instead, we will re-calculate the
+ alternate POSITIONs after we've picked the best QEP.
+ */
+ *handled_fanout= firstmatch_need_tables;
+ /* *record_count and *read_time were set by the above call */
+ *strategy= SJ_OPT_FIRST_MATCH;
+ return TRUE;
+ }
+ }
+ }
+ else
+ invalidate_firstmatch_prefix();
+ return FALSE;
+}
+
+
+void Duplicate_weedout_picker::set_from_prev(POSITION *prev)
+{
+ if (prev->dups_weedout_picker.is_used)
+ set_empty();
+ else
+ {
+ dupsweedout_tables= prev->dups_weedout_picker.dupsweedout_tables;
+ first_dupsweedout_table= prev->dups_weedout_picker.first_dupsweedout_table;
+ }
+ is_used= FALSE;
+}
+
+
+bool Duplicate_weedout_picker::check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ POSITION *loose_scan_pos
+ )
+{
+ TABLE_LIST *nest;
+ if ((nest= new_join_tab->emb_sj_nest))
+ {
+ if (!dupsweedout_tables)
+ first_dupsweedout_table= idx;
+
+ dupsweedout_tables |= nest->sj_inner_tables |
+ nest->nested_join->sj_depends_on |
+ nest->nested_join->sj_corr_tables;
+ }
+
+ if (dupsweedout_tables)
+ {
+ /* we're in the process of constructing a DuplicateWeedout range */
+ TABLE_LIST *emb= new_join_tab->table->pos_in_table_list->embedding;
+ /* and we've entered an inner side of an outer join*/
+ if (emb && emb->on_expr)
+ dupsweedout_tables |= emb->nested_join->used_tables;
+ }
+
+ /* If this is the last table that we need for DuplicateWeedout range */
+ if (dupsweedout_tables && !(remaining_tables & ~new_join_tab->table->map &
+ dupsweedout_tables))
+ {
+ /*
+ Ok, reached a state where we could put a dups weedout point.
+ Walk back and calculate
+ - the join cost (this is needed as the accumulated cost may assume
+ some other duplicate elimination method)
+ - extra fanout that will be removed by duplicate elimination
+ - duplicate elimination cost
+ There are two cases:
+ 1. We have other strategy/ies to remove all of the duplicates.
+ 2. We don't.
+
+ We need to calculate the cost in case #2 also because we need to make
+ choice between this join order and others.
+ */
+ uint first_tab= first_dupsweedout_table;
+ double dups_cost;
+ double prefix_rec_count;
+ double sj_inner_fanout= 1.0;
+ double sj_outer_fanout= 1.0;
+ uint temptable_rec_size;
+ if (first_tab == join->const_tables)
+ {
+ prefix_rec_count= 1.0;
+ temptable_rec_size= 0;
+ dups_cost= 0.0;
+ }
+ else
+ {
+ dups_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
+ prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
+ temptable_rec_size= 8; /* This is not true but we'll make it so */
+ }
+
+ table_map dups_removed_fanout= 0;
+ double current_fanout= prefix_rec_count;
+ for (uint j= first_dupsweedout_table; j <= idx; j++)
+ {
+ POSITION *p= join->positions + j;
+ current_fanout *= p->records_read;
+ dups_cost += p->read_time + current_fanout / TIME_FOR_COMPARE;
+ if (p->table->emb_sj_nest)
+ {
+ sj_inner_fanout *= p->records_read;
+ dups_removed_fanout |= p->table->table->map;
+ }
+ else
+ {
+ sj_outer_fanout *= p->records_read;
+ temptable_rec_size += p->table->table->file->ref_length;
+ }
+ }
+
+ /*
+ Add the cost of temptable use. The table will have sj_outer_fanout
+ records, and we will make
+ - sj_outer_fanout table writes
+ - sj_inner_fanout*sj_outer_fanout lookups.
+
+ */
+ double one_lookup_cost= get_tmp_table_lookup_cost(join->thd,
+ sj_outer_fanout,
+ temptable_rec_size);
+ double one_write_cost= get_tmp_table_write_cost(join->thd,
+ sj_outer_fanout,
+ temptable_rec_size);
+
+ double write_cost= join->positions[first_tab].prefix_record_count*
+ sj_outer_fanout * one_write_cost;
+ double full_lookup_cost= join->positions[first_tab].prefix_record_count*
+ sj_outer_fanout* sj_inner_fanout *
+ one_lookup_cost;
+ dups_cost += write_cost + full_lookup_cost;
+
+ *read_time= dups_cost;
+ *record_count= prefix_rec_count * sj_outer_fanout;
+ *handled_fanout= dups_removed_fanout;
+ *strategy= SJ_OPT_DUPS_WEEDOUT;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Remove the last join tab from from join->cur_sj_inner_tables bitmap
+ we assume remaining_tables doesnt contain @tab.
+*/
+
+void restore_prev_sj_state(const table_map remaining_tables,
+ const JOIN_TAB *tab, uint idx)
+{
+ TABLE_LIST *emb_sj_nest;
+
+ if (tab->emb_sj_nest)
+ {
+ table_map subq_tables= tab->emb_sj_nest->sj_inner_tables;
+ tab->join->sjm_lookup_tables &= ~subq_tables;
+ }
+
+ if ((emb_sj_nest= tab->emb_sj_nest))
+ {
+ /* If we're removing the last SJ-inner table, remove the sj-nest */
+ if ((remaining_tables & emb_sj_nest->sj_inner_tables) ==
+ (emb_sj_nest->sj_inner_tables & ~tab->table->map))
+ {
+ tab->join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
+ }
+ }
+}
+
+
+/*
+ Given a semi-join nest, find out which of the IN-equalities are bound
+
+ SYNOPSIS
+ get_bound_sj_equalities()
+ sj_nest Semi-join nest
+ remaining_tables Tables that are not yet bound
+
+ DESCRIPTION
+ Given a semi-join nest, find out which of the IN-equalities have their
+ left part expression bound (i.e. the said expression doesn't refer to
+ any of remaining_tables and can be evaluated).
+
+ RETURN
+ Bitmap of bound IN-equalities.
+*/
+
+ulonglong get_bound_sj_equalities(TABLE_LIST *sj_nest,
+ table_map remaining_tables)
+{
+ List_iterator<Item> li(sj_nest->nested_join->sj_outer_expr_list);
+ Item *item;
+ uint i= 0;
+ ulonglong res= 0;
+ while ((item= li++))
+ {
+ /*
+ Q: should this take into account equality propagation and how?
+ A: If e->outer_side is an Item_field, walk over the equality
+ class and see if there is an element that is bound?
+ (this is an optional feature)
+ */
+ if (!(item->used_tables() & remaining_tables))
+ {
+ res |= 1ULL << i;
+ }
+ i++;
+ }
+ return res;
+}
+
+
+/*
+ Check if the last tables of the partial join order allow to use
+ sj-materialization strategy for them
+
+ SYNOPSIS
+ at_sjmat_pos()
+ join
+ remaining_tables
+ tab the last table's join tab
+ idx last table's index
+ loose_scan OUT TRUE <=> use LooseScan
+
+ RETURN
+ TRUE Yes, can apply sj-materialization
+ FALSE No, some of the requirements are not met
+*/
+
+static SJ_MATERIALIZATION_INFO *
+at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab,
+ uint idx, bool *loose_scan)
+{
+ /*
+ Check if
+ 1. We're in a semi-join nest that can be run with SJ-materialization
+ 2. All the tables correlated through the IN subquery are in the prefix
+ */
+ TABLE_LIST *emb_sj_nest= tab->emb_sj_nest;
+ table_map suffix= remaining_tables & ~tab->table->map;
+ if (emb_sj_nest && emb_sj_nest->sj_mat_info &&
+ !(suffix & emb_sj_nest->sj_inner_tables))
+ {
+ /*
+ Walk back and check if all immediately preceding tables are from
+ this semi-join.
+ */
+ uint n_tables= my_count_bits(tab->emb_sj_nest->sj_inner_tables);
+ for (uint i= 1; i < n_tables ; i++)
+ {
+ if (join->positions[idx - i].table->emb_sj_nest != tab->emb_sj_nest)
+ return NULL;
+ }
+ *loose_scan= MY_TEST(remaining_tables & ~tab->table->map &
+ (emb_sj_nest->sj_inner_tables |
+ emb_sj_nest->nested_join->sj_depends_on));
+ if (*loose_scan && !emb_sj_nest->sj_subq_pred->sjm_scan_allowed)
+ return NULL;
+ else
+ return emb_sj_nest->sj_mat_info;
+ }
+ return NULL;
+}
+
+
+/*
+ Re-calculate values of join->best_positions[start..end].prefix_record_count
+*/
+
+static void recalculate_prefix_record_count(JOIN *join, uint start, uint end)
+{
+ for (uint j= start; j < end ;j++)
+ {
+ double prefix_count;
+ if (j == join->const_tables)
+ prefix_count= 1.0;
+ else
+ prefix_count= join->best_positions[j-1].prefix_record_count *
+ join->best_positions[j-1].records_read;
+
+ join->best_positions[j].prefix_record_count= prefix_count;
+ }
+}
+
+
+/*
+ Fix semi-join strategies for the picked join order
+
+ SYNOPSIS
+ fix_semijoin_strategies_for_picked_join_order()
+ join The join with the picked join order
+
+ DESCRIPTION
+ Fix semi-join strategies for the picked join order. This is a step that
+ needs to be done right after we have fixed the join order. What we do
+ here is switch join's semi-join strategy description from backward-based
+ to forwards based.
+
+ When join optimization is in progress, we re-consider semi-join
+ strategies after we've added another table. Here's an illustration.
+ Suppose the join optimization is underway:
+
+ 1) ot1 it1 it2
+ sjX -- looking at (ot1, it1, it2) join prefix, we decide
+ to use semi-join strategy sjX.
+
+ 2) ot1 it1 it2 ot2
+ sjX sjY -- Having added table ot2, we now may consider
+ another semi-join strategy and decide to use a
+ different strategy sjY. Note that the record
+ of sjX has remained under it2. That is
+ necessary because we need to be able to get
+ back to (ot1, it1, it2) join prefix.
+ what makes things even worse is that there are cases where the choice
+ of sjY changes the way we should access it2.
+
+ 3) [ot1 it1 it2 ot2 ot3]
+ sjX sjY -- This means that after join optimization is
+ finished, semi-join info should be read
+ right-to-left (while nearly all plan refinement
+ functions, EXPLAIN, etc proceed from left to
+ right)
+
+ This function does the needed reversal, making it possible to read the
+ join and semi-join order from left to right.
+*/
+
+void fix_semijoin_strategies_for_picked_join_order(JOIN *join)
+{
+ uint table_count=join->table_count;
+ uint tablenr;
+ table_map remaining_tables= 0;
+ table_map handled_tabs= 0;
+ join->sjm_lookup_tables= 0;
+ for (tablenr= table_count - 1 ; tablenr != join->const_tables - 1; tablenr--)
+ {
+ POSITION *pos= join->best_positions + tablenr;
+ JOIN_TAB *s= pos->table;
+ uint first;
+ LINT_INIT(first); // Set by every branch except SJ_OPT_NONE which doesn't use it
+
+ if ((handled_tabs & s->table->map) || pos->sj_strategy == SJ_OPT_NONE)
+ {
+ remaining_tables |= s->table->map;
+ continue;
+ }
+
+ if (pos->sj_strategy == SJ_OPT_MATERIALIZE)
+ {
+ SJ_MATERIALIZATION_INFO *sjm= s->emb_sj_nest->sj_mat_info;
+ sjm->is_used= TRUE;
+ sjm->is_sj_scan= FALSE;
+ memcpy(pos - sjm->tables + 1, sjm->positions,
+ sizeof(POSITION) * sjm->tables);
+ recalculate_prefix_record_count(join, tablenr - sjm->tables + 1,
+ tablenr);
+ first= tablenr - sjm->tables + 1;
+ join->best_positions[first].n_sj_tables= sjm->tables;
+ join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE;
+ join->sjm_lookup_tables|= s->table->map;
+ }
+ else if (pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
+ {
+ POSITION *first_inner= join->best_positions + pos->sjmat_picker.sjm_scan_last_inner;
+ SJ_MATERIALIZATION_INFO *sjm= first_inner->table->emb_sj_nest->sj_mat_info;
+ sjm->is_used= TRUE;
+ sjm->is_sj_scan= TRUE;
+ first= pos->sjmat_picker.sjm_scan_last_inner - sjm->tables + 1;
+ memcpy(join->best_positions + first,
+ sjm->positions, sizeof(POSITION) * sjm->tables);
+ recalculate_prefix_record_count(join, first, first + sjm->tables);
+ join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
+ join->best_positions[first].n_sj_tables= sjm->tables;
+ /*
+ Do what advance_sj_state did: re-run best_access_path for every table
+ in the [last_inner_table + 1; pos..) range
+ */
+ double prefix_rec_count;
+ /* Get the prefix record count */
+ if (first == join->const_tables)
+ prefix_rec_count= 1.0;
+ else
+ prefix_rec_count= join->best_positions[first-1].prefix_record_count;
+
+ /* Add materialization record count*/
+ prefix_rec_count *= sjm->rows;
+
+ uint i;
+ table_map rem_tables= remaining_tables;
+ for (i= tablenr; i != (first + sjm->tables - 1); i--)
+ rem_tables |= join->best_positions[i].table->table->map;
+
+ POSITION dummy;
+ join->cur_sj_inner_tables= 0;
+ for (i= first + sjm->tables; i <= tablenr; i++)
+ {
+ best_access_path(join, join->best_positions[i].table, rem_tables, i,
+ FALSE, prefix_rec_count,
+ join->best_positions + i, &dummy);
+ prefix_rec_count *= join->best_positions[i].records_read;
+ rem_tables &= ~join->best_positions[i].table->table->map;
+ }
+ }
+
+ if (pos->sj_strategy == SJ_OPT_FIRST_MATCH)
+ {
+ first= pos->firstmatch_picker.first_firstmatch_table;
+ join->best_positions[first].sj_strategy= SJ_OPT_FIRST_MATCH;
+ join->best_positions[first].n_sj_tables= tablenr - first + 1;
+ POSITION dummy; // For loose scan paths
+ double record_count= (first== join->const_tables)? 1.0:
+ join->best_positions[tablenr - 1].prefix_record_count;
+
+ table_map rem_tables= remaining_tables;
+ uint idx;
+ for (idx= first; idx <= tablenr; idx++)
+ {
+ rem_tables |= join->best_positions[idx].table->table->map;
+ }
+ /*
+ Re-run best_access_path to produce best access methods that do not use
+ join buffering
+ */
+ join->cur_sj_inner_tables= 0;
+ for (idx= first; idx <= tablenr; idx++)
+ {
+ if (join->best_positions[idx].use_join_buffer)
+ {
+ best_access_path(join, join->best_positions[idx].table,
+ rem_tables, idx, TRUE /* no jbuf */,
+ record_count, join->best_positions + idx, &dummy);
+ }
+ record_count *= join->best_positions[idx].records_read;
+ rem_tables &= ~join->best_positions[idx].table->table->map;
+ }
+ }
+
+ if (pos->sj_strategy == SJ_OPT_LOOSE_SCAN)
+ {
+ first= pos->loosescan_picker.first_loosescan_table;
+ POSITION *first_pos= join->best_positions + first;
+ POSITION loose_scan_pos; // For loose scan paths
+ double record_count= (first== join->const_tables)? 1.0:
+ join->best_positions[tablenr - 1].prefix_record_count;
+
+ table_map rem_tables= remaining_tables;
+ uint idx;
+ for (idx= first; idx <= tablenr; idx++)
+ rem_tables |= join->best_positions[idx].table->table->map;
+ /*
+ Re-run best_access_path to produce best access methods that do not use
+ join buffering
+ */
+ join->cur_sj_inner_tables= 0;
+ for (idx= first; idx <= tablenr; idx++)
+ {
+ if (join->best_positions[idx].use_join_buffer || (idx == first))
+ {
+ best_access_path(join, join->best_positions[idx].table,
+ rem_tables, idx, TRUE /* no jbuf */,
+ record_count, join->best_positions + idx,
+ &loose_scan_pos);
+ if (idx==first)
+ {
+ join->best_positions[idx]= loose_scan_pos;
+ /*
+ If LooseScan is based on ref access (including the "degenerate"
+ one with 0 key parts), we should use full index scan.
+
+ Unfortunately, lots of code assumes that if tab->type==JT_ALL &&
+ tab->quick!=NULL, then quick select should be used. The only
+ simple way to fix this is to remove the quick select:
+ */
+ if (join->best_positions[idx].key)
+ {
+ delete join->best_positions[idx].table->quick;
+ join->best_positions[idx].table->quick= NULL;
+ }
+ }
+ }
+ rem_tables &= ~join->best_positions[idx].table->table->map;
+ record_count *= join->best_positions[idx].records_read;
+ }
+ first_pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
+ first_pos->n_sj_tables= my_count_bits(first_pos->table->emb_sj_nest->sj_inner_tables);
+ }
+
+ if (pos->sj_strategy == SJ_OPT_DUPS_WEEDOUT)
+ {
+ /*
+ Duplicate Weedout starting at pos->first_dupsweedout_table, ending at
+ this table.
+ */
+ first= pos->dups_weedout_picker.first_dupsweedout_table;
+ join->best_positions[first].sj_strategy= SJ_OPT_DUPS_WEEDOUT;
+ join->best_positions[first].n_sj_tables= tablenr - first + 1;
+ }
+
+ uint i_end= first + join->best_positions[first].n_sj_tables;
+ for (uint i= first; i < i_end; i++)
+ {
+ if (i != first)
+ join->best_positions[i].sj_strategy= SJ_OPT_NONE;
+ handled_tabs |= join->best_positions[i].table->table->map;
+ }
+
+ if (tablenr != first)
+ pos->sj_strategy= SJ_OPT_NONE;
+ remaining_tables |= s->table->map;
+ join->join_tab[first].sj_strategy= join->best_positions[first].sj_strategy;
+ join->join_tab[first].n_sj_tables= join->best_positions[first].n_sj_tables;
+ }
+}
+
+
+/*
+ Setup semi-join materialization strategy for one semi-join nest
+
+ SYNOPSIS
+
+ setup_sj_materialization()
+ tab The first tab in the semi-join
+
+ DESCRIPTION
+ Setup execution structures for one semi-join materialization nest:
+ - Create the materialization temporary table
+ - If we're going to do index lookups
+ create TABLE_REF structure to make the lookus
+ - else (if we're going to do a full scan of the temptable)
+ create Copy_field structures to do copying.
+
+ RETURN
+ FALSE Ok
+ TRUE Error
+*/
+
+bool setup_sj_materialization_part1(JOIN_TAB *sjm_tab)
+{
+ DBUG_ENTER("setup_sj_materialization");
+ JOIN_TAB *tab= sjm_tab->bush_children->start;
+ TABLE_LIST *emb_sj_nest= tab->table->pos_in_table_list->embedding;
+
+ /* Walk out of outer join nests until we reach the semi-join nest we're in */
+ while (!emb_sj_nest->sj_mat_info)
+ emb_sj_nest= emb_sj_nest->embedding;
+
+ SJ_MATERIALIZATION_INFO *sjm= emb_sj_nest->sj_mat_info;
+ THD *thd= tab->join->thd;
+ /* First the calls come to the materialization function */
+ //List<Item> &item_list= emb_sj_nest->sj_subq_pred->unit->first_select()->item_list;
+
+ DBUG_ASSERT(sjm->is_used);
+ /*
+ Set up the table to write to, do as select_union::create_result_table does
+ */
+ sjm->sjm_table_param.init();
+ sjm->sjm_table_param.bit_fields_as_long= TRUE;
+ //List_iterator<Item> it(item_list);
+ SELECT_LEX *subq_select= emb_sj_nest->sj_subq_pred->unit->first_select();
+ Item **p_item= subq_select->ref_pointer_array;
+ Item **p_end= p_item + subq_select->item_list.elements;
+ //while((right_expr= it++))
+ for(;p_item != p_end; p_item++)
+ sjm->sjm_table_cols.push_back(*p_item);
+
+ sjm->sjm_table_param.field_count= subq_select->item_list.elements;
+ sjm->sjm_table_param.force_not_null_cols= TRUE;
+
+ if (!(sjm->table= create_tmp_table(thd, &sjm->sjm_table_param,
+ sjm->sjm_table_cols, (ORDER*) 0,
+ TRUE /* distinct */,
+ 1, /*save_sum_fields*/
+ thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS,
+ HA_POS_ERROR /*rows_limit */,
+ (char*)"sj-materialize")))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ sjm->table->map= emb_sj_nest->nested_join->used_tables;
+ sjm->table->file->extra(HA_EXTRA_WRITE_CACHE);
+ sjm->table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+
+ tab->join->sj_tmp_tables.push_back(sjm->table);
+ tab->join->sjm_info_list.push_back(sjm);
+
+ sjm->materialized= FALSE;
+ sjm_tab->table= sjm->table;
+ sjm->table->pos_in_table_list= emb_sj_nest;
+
+ DBUG_RETURN(FALSE);
+}
+
+
+bool setup_sj_materialization_part2(JOIN_TAB *sjm_tab)
+{
+ DBUG_ENTER("setup_sj_materialization_part2");
+ JOIN_TAB *tab= sjm_tab->bush_children->start;
+ TABLE_LIST *emb_sj_nest= tab->table->pos_in_table_list->embedding;
+ /* Walk out of outer join nests until we reach the semi-join nest we're in */
+ while (!emb_sj_nest->sj_mat_info)
+ emb_sj_nest= emb_sj_nest->embedding;
+ SJ_MATERIALIZATION_INFO *sjm= emb_sj_nest->sj_mat_info;
+ THD *thd= tab->join->thd;
+ uint i;
+ //List<Item> &item_list= emb_sj_nest->sj_subq_pred->unit->first_select()->item_list;
+ //List_iterator<Item> it(item_list);
+
+ if (!sjm->is_sj_scan)
+ {
+ KEY *tmp_key; /* The only index on the temporary table. */
+ uint tmp_key_parts; /* Number of keyparts in tmp_key. */
+ tmp_key= sjm->table->key_info;
+ tmp_key_parts= tmp_key->user_defined_key_parts;
+
+ /*
+ Create/initialize everything we will need to index lookups into the
+ temptable.
+ */
+ TABLE_REF *tab_ref;
+ tab_ref= &sjm_tab->ref;
+ tab_ref->key= 0; /* The only temp table index. */
+ tab_ref->key_length= tmp_key->key_length;
+ if (!(tab_ref->key_buff=
+ (uchar*) thd->calloc(ALIGN_SIZE(tmp_key->key_length) * 2)) ||
+ !(tab_ref->key_copy=
+ (store_key**) thd->alloc((sizeof(store_key*) *
+ (tmp_key_parts + 1)))) ||
+ !(tab_ref->items=
+ (Item**) thd->alloc(sizeof(Item*) * tmp_key_parts)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ tab_ref->key_buff2=tab_ref->key_buff+ALIGN_SIZE(tmp_key->key_length);
+ tab_ref->key_err=1;
+ tab_ref->null_rejecting= 1;
+ tab_ref->disable_cache= FALSE;
+
+ KEY_PART_INFO *cur_key_part= tmp_key->key_part;
+ store_key **ref_key= tab_ref->key_copy;
+ uchar *cur_ref_buff= tab_ref->key_buff;
+
+ for (i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++)
+ {
+ tab_ref->items[i]= emb_sj_nest->sj_subq_pred->left_expr->element_index(i);
+ int null_count= MY_TEST(cur_key_part->field->real_maybe_null());
+ *ref_key= new store_key_item(thd, cur_key_part->field,
+ /* TODO:
+ the NULL byte is taken into account in
+ cur_key_part->store_length, so instead of
+ cur_ref_buff + MY_TEST(maybe_null), we could
+ use that information instead.
+ */
+ cur_ref_buff + null_count,
+ null_count ? cur_ref_buff : 0,
+ cur_key_part->length, tab_ref->items[i],
+ FALSE);
+ cur_ref_buff+= cur_key_part->store_length;
+ }
+ *ref_key= NULL; /* End marker. */
+
+ /*
+ We don't ever have guarded conditions for SJM tables, but code at SQL
+ layer depends on cond_guards array being alloced.
+ */
+ if (!(tab_ref->cond_guards= (bool**) thd->calloc(sizeof(uint*)*tmp_key_parts)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ tab_ref->key_err= 1;
+ tab_ref->key_parts= tmp_key_parts;
+ sjm->tab_ref= tab_ref;
+
+ /*
+ Remove the injected semi-join IN-equalities from join_tab conds. This
+ needs to be done because the IN-equalities refer to columns of
+ sj-inner tables which are not available after the materialization
+ has been finished.
+ */
+ for (i= 0; i < sjm->tables; i++)
+ {
+ remove_sj_conds(&tab[i].select_cond);
+ if (tab[i].select)
+ remove_sj_conds(&tab[i].select->cond);
+ }
+ if (!(sjm->in_equality= create_subq_in_equalities(thd, sjm,
+ emb_sj_nest->sj_subq_pred)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ sjm_tab->type= JT_EQ_REF;
+ sjm_tab->select_cond= sjm->in_equality;
+ }
+ else
+ {
+ /*
+ We'll be doing full scan of the temptable.
+ Setup copying of temptable columns back to the record buffers
+ for their source tables. We need this because IN-equalities
+ refer to the original tables.
+
+ EXAMPLE
+
+ Consider the query:
+ SELECT * FROM ot WHERE ot.col1 IN (SELECT it.col2 FROM it)
+
+ Suppose it's executed with SJ-Materialization-scan. We choose to do scan
+ if we can't do the lookup, i.e. the join order is (it, ot). The plan
+ would look as follows:
+
+ table access method condition
+ it materialize+scan -
+ ot (whatever) ot1.col1=it.col2 (C2)
+
+ The condition C2 refers to current row of table it. The problem is
+ that by the time we evaluate C2, we would have finished with scanning
+ it itself and will be scanning the temptable.
+
+ At the moment, our solution is to copy back: when we get the next
+ temptable record, we copy its columns to their corresponding columns
+ in the record buffers for the source tables.
+ */
+ sjm->copy_field= new Copy_field[sjm->sjm_table_cols.elements];
+ //it.rewind();
+ Item **p_item= emb_sj_nest->sj_subq_pred->unit->first_select()->ref_pointer_array;
+ for (uint i=0; i < sjm->sjm_table_cols.elements; i++)
+ {
+ bool dummy;
+ Item_equal *item_eq;
+ //Item *item= (it++)->real_item();
+ Item *item= (*(p_item++))->real_item();
+ DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
+ Field *copy_to= ((Item_field*)item)->field;
+ /*
+ Tricks with Item_equal are due to the following: suppose we have a
+ query:
+
+ ... WHERE cond(ot.col) AND ot.col IN (SELECT it2.col FROM it1,it2
+ WHERE it1.col= it2.col)
+ then equality propagation will create an
+
+ Item_equal(it1.col, it2.col, ot.col)
+
+ then substitute_for_best_equal_field() will change the conditions
+ according to the join order:
+
+ table | attached condition
+ ------+--------------------
+ it1 |
+ it2 | it1.col=it2.col
+ ot | cond(it1.col)
+
+ although we've originally had "SELECT it2.col", conditions attached
+ to subsequent outer tables will refer to it1.col, so SJM-Scan will
+ need to unpack data to there.
+ That is, if an element from subquery's select list participates in
+ equality propagation, then we need to unpack it to the first
+ element equality propagation member that refers to table that is
+ within the subquery.
+ */
+ item_eq= find_item_equal(tab->join->cond_equal, copy_to, &dummy);
+
+ if (item_eq)
+ {
+ List_iterator<Item> it(item_eq->equal_items);
+ /* We're interested in field items only */
+ if (item_eq->get_const())
+ it++;
+ Item *item;
+ while ((item= it++))
+ {
+ if (!(item->used_tables() & ~emb_sj_nest->sj_inner_tables))
+ {
+ DBUG_ASSERT(item->real_item()->type() == Item::FIELD_ITEM);
+ copy_to= ((Item_field *) (item->real_item()))->field;
+ break;
+ }
+ }
+ }
+ sjm->copy_field[i].set(copy_to, sjm->table->field[i], FALSE);
+ /* The write_set for source tables must be set up to allow the copying */
+ bitmap_set_bit(copy_to->table->write_set, copy_to->field_index);
+ }
+ sjm_tab->type= JT_ALL;
+
+ /* Initialize full scan */
+ sjm_tab->read_first_record= join_read_record_no_init;
+ sjm_tab->read_record.copy_field= sjm->copy_field;
+ sjm_tab->read_record.copy_field_end= sjm->copy_field +
+ sjm->sjm_table_cols.elements;
+ sjm_tab->read_record.read_record= rr_sequential_and_unpack;
+ }
+
+ sjm_tab->bush_children->end[-1].next_select= end_sj_materialize;
+
+ DBUG_RETURN(FALSE);
+}
+
+
+
+/*
+ Create subquery IN-equalities assuming use of materialization strategy
+
+ SYNOPSIS
+ create_subq_in_equalities()
+ thd Thread handle
+ sjm Semi-join materialization structure
+ subq_pred The subquery predicate
+
+ DESCRIPTION
+ Create subquery IN-equality predicates. That is, for a subquery
+
+ (oe1, oe2, ...) IN (SELECT ie1, ie2, ... FROM ...)
+
+ create "oe1=ie1 AND ie1=ie2 AND ..." expression, such that ie1, ie2, ..
+ refer to the columns of the table that's used to materialize the
+ subquery.
+
+ RETURN
+ Created condition
+*/
+
+static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm,
+ Item_in_subselect *subq_pred)
+{
+ Item *res= NULL;
+ if (subq_pred->left_expr->cols() == 1)
+ {
+ if (!(res= new Item_func_eq(subq_pred->left_expr,
+ new Item_field(sjm->table->field[0]))))
+ return NULL; /* purecov: inspected */
+ }
+ else
+ {
+ Item *conj;
+ for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
+ {
+ if (!(conj= new Item_func_eq(subq_pred->left_expr->element_index(i),
+ new Item_field(sjm->table->field[i]))) ||
+ !(res= and_items(res, conj)))
+ return NULL; /* purecov: inspected */
+ }
+ }
+ if (res->fix_fields(thd, &res))
+ return NULL; /* purecov: inspected */
+ return res;
+}
+
+
+
+
+static void remove_sj_conds(Item **tree)
+{
+ if (*tree)
+ {
+ if (is_cond_sj_in_equality(*tree))
+ {
+ *tree= NULL;
+ return;
+ }
+ else if ((*tree)->type() == Item::COND_ITEM)
+ {
+ Item *item;
+ List_iterator<Item> li(*(((Item_cond*)*tree)->argument_list()));
+ while ((item= li++))
+ {
+ if (is_cond_sj_in_equality(item))
+ li.replace(new Item_int(1));
+ }
+ }
+ }
+}
+
+/* Check if given Item was injected by semi-join equality */
+static bool is_cond_sj_in_equality(Item *item)
+{
+ if (item->type() == Item::FUNC_ITEM &&
+ ((Item_func*)item)->functype()== Item_func::EQ_FUNC)
+ {
+ Item_func_eq *item_eq= (Item_func_eq*)item;
+ return MY_TEST(item_eq->in_equality_no != UINT_MAX);
+ }
+ return FALSE;
+}
+
+
+/*
+ Create a temporary table to weed out duplicate rowid combinations
+
+ SYNOPSIS
+
+ create_sj_weedout_tmp_table()
+ thd Thread handle
+
+ DESCRIPTION
+ Create a temporary table to weed out duplicate rowid combinations. The
+ table has a single column that is a concatenation of all rowids in the
+ combination.
+
+ Depending on the needed length, there are two cases:
+
+ 1. When the length of the column < max_key_length:
+
+ CREATE TABLE tmp (col VARBINARY(n) NOT NULL, UNIQUE KEY(col));
+
+ 2. Otherwise (not a valid SQL syntax but internally supported):
+
+ CREATE TABLE tmp (col VARBINARY NOT NULL, UNIQUE CONSTRAINT(col));
+
+ The code in this function was produced by extraction of relevant parts
+ from create_tmp_table().
+
+ RETURN
+ created table
+ NULL on error
+*/
+
+bool
+SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd)
+{
+ MEM_ROOT *mem_root_save, own_root;
+ TABLE *table;
+ TABLE_SHARE *share;
+ uint temp_pool_slot=MY_BIT_NONE;
+ char *tmpname,path[FN_REFLEN];
+ Field **reg_field;
+ KEY_PART_INFO *key_part_info;
+ KEY *keyinfo;
+ uchar *group_buff;
+ uchar *bitmaps;
+ uint *blob_field;
+ bool using_unique_constraint=FALSE;
+ bool use_packed_rows= FALSE;
+ Field *field, *key_field;
+ uint null_pack_length, null_count;
+ uchar *null_flags;
+ uchar *pos;
+ DBUG_ENTER("create_sj_weedout_tmp_table");
+ DBUG_ASSERT(!is_degenerate);
+
+ tmp_table= NULL;
+ uint uniq_tuple_length_arg= rowid_len + null_bytes;
+ /*
+ STEP 1: Get temporary table name
+ */
+ if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES))
+ temp_pool_slot = bitmap_lock_set_next(&temp_pool);
+
+ if (temp_pool_slot != MY_BIT_NONE) // we got a slot
+ sprintf(path, "%s_%lx_%i", tmp_file_prefix,
+ current_pid, temp_pool_slot);
+ else
+ {
+ /* if we run out of slots or we are not using tempool */
+ sprintf(path,"%s%lx_%lx_%x", tmp_file_prefix,current_pid,
+ thd->thread_id, thd->tmp_table++);
+ }
+ fn_format(path, path, mysql_tmpdir, "", MY_REPLACE_EXT|MY_UNPACK_FILENAME);
+
+ /* STEP 2: Figure if we'll be using a key or blob+constraint */
+ /* it always has my_charset_bin, so mbmaxlen==1 */
+ if (uniq_tuple_length_arg >= CONVERT_IF_BIGGER_TO_BLOB)
+ using_unique_constraint= TRUE;
+
+ /* STEP 3: Allocate memory for temptable description */
+ init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC));
+ if (!multi_alloc_root(&own_root,
+ &table, sizeof(*table),
+ &share, sizeof(*share),
+ &reg_field, sizeof(Field*) * (1+1),
+ &blob_field, sizeof(uint)*2,
+ &keyinfo, sizeof(*keyinfo),
+ &key_part_info, sizeof(*key_part_info) * 2,
+ &start_recinfo,
+ sizeof(*recinfo)*(1*2+4),
+ &tmpname, (uint) strlen(path)+1,
+ &group_buff, (!using_unique_constraint ?
+ uniq_tuple_length_arg : 0),
+ &bitmaps, bitmap_buffer_size(1)*5,
+ NullS))
+ {
+ if (temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+ DBUG_RETURN(TRUE);
+ }
+ strmov(tmpname,path);
+
+
+ /* STEP 4: Create TABLE description */
+ bzero((char*) table,sizeof(*table));
+ bzero((char*) reg_field,sizeof(Field*)*2);
+
+ table->mem_root= own_root;
+ mem_root_save= thd->mem_root;
+ thd->mem_root= &table->mem_root;
+
+ table->field=reg_field;
+ table->alias.set("weedout-tmp", sizeof("weedout-tmp")-1,
+ table_alias_charset);
+ table->reginfo.lock_type=TL_WRITE; /* Will be updated */
+ table->db_stat=HA_OPEN_KEYFILE+HA_OPEN_RNDFILE;
+ table->map=1;
+ table->temp_pool_slot = temp_pool_slot;
+ table->copy_blobs= 1;
+ table->in_use= thd;
+ table->quick_keys.init();
+ table->covering_keys.init();
+ table->keys_in_use_for_query.init();
+
+ table->s= share;
+ init_tmp_table_share(thd, share, "", 0, tmpname, tmpname);
+ share->blob_field= blob_field;
+ share->table_charset= NULL;
+ share->primary_key= MAX_KEY; // Indicate no primary key
+ share->keys_for_keyread.init();
+ share->keys_in_use.init();
+
+ /* Create the field */
+ {
+ /*
+ For the sake of uniformity, always use Field_varstring (altough we could
+ use Field_string for shorter keys)
+ */
+ field= new Field_varstring(uniq_tuple_length_arg, FALSE, "rowids", share,
+ &my_charset_bin);
+ if (!field)
+ DBUG_RETURN(0);
+ field->table= table;
+ field->key_start.init(0);
+ field->part_of_key.init(0);
+ field->part_of_sortkey.init(0);
+ field->unireg_check= Field::NONE;
+ field->flags= (NOT_NULL_FLAG | BINARY_FLAG | NO_DEFAULT_VALUE_FLAG);
+ field->reset_fields();
+ field->init(table);
+ field->orig_table= NULL;
+
+ field->field_index= 0;
+
+ *(reg_field++)= field;
+ *blob_field= 0;
+ *reg_field= 0;
+
+ share->fields= 1;
+ share->blob_fields= 0;
+ }
+
+ uint reclength= field->pack_length();
+ if (using_unique_constraint)
+ {
+ share->db_plugin= ha_lock_engine(0, TMP_ENGINE_HTON);
+ table->file= get_new_handler(share, &table->mem_root,
+ share->db_type());
+ DBUG_ASSERT(uniq_tuple_length_arg <= table->file->max_key_length());
+ }
+ else
+ {
+ share->db_plugin= ha_lock_engine(0, heap_hton);
+ table->file= get_new_handler(share, &table->mem_root,
+ share->db_type());
+ }
+ if (!table->file)
+ goto err;
+
+ if (table->file->set_ha_share_ref(&share->ha_share))
+ {
+ delete table->file;
+ goto err;
+ }
+
+ null_count=1;
+
+ null_pack_length= 1;
+ reclength += null_pack_length;
+
+ share->reclength= reclength;
+ {
+ uint alloc_length=ALIGN_SIZE(share->reclength + MI_UNIQUE_HASH_LENGTH+1);
+ share->rec_buff_length= alloc_length;
+ if (!(table->record[0]= (uchar*)
+ alloc_root(&table->mem_root, alloc_length*3)))
+ goto err;
+ table->record[1]= table->record[0]+alloc_length;
+ share->default_values= table->record[1]+alloc_length;
+ }
+ setup_tmp_table_column_bitmaps(table, bitmaps);
+
+ recinfo= start_recinfo;
+ null_flags=(uchar*) table->record[0];
+ pos=table->record[0]+ null_pack_length;
+ if (null_pack_length)
+ {
+ bzero((uchar*) recinfo,sizeof(*recinfo));
+ recinfo->type=FIELD_NORMAL;
+ recinfo->length=null_pack_length;
+ recinfo++;
+ bfill(null_flags,null_pack_length,255); // Set null fields
+
+ table->null_flags= (uchar*) table->record[0];
+ share->null_fields= null_count;
+ share->null_bytes= null_pack_length;
+ }
+ null_count=1;
+
+ {
+ //Field *field= *reg_field;
+ uint length;
+ bzero((uchar*) recinfo,sizeof(*recinfo));
+ field->move_field(pos,(uchar*) 0,0);
+
+ field->reset();
+ /*
+ Test if there is a default field value. The test for ->ptr is to skip
+ 'offset' fields generated by initalize_tables
+ */
+ // Initialize the table field:
+ bzero(field->ptr, field->pack_length());
+
+ length=field->pack_length();
+ pos+= length;
+
+ /* Make entry for create table */
+ recinfo->length=length;
+ if (field->flags & BLOB_FLAG)
+ recinfo->type= FIELD_BLOB;
+ else if (use_packed_rows &&
+ field->real_type() == MYSQL_TYPE_STRING &&
+ length >= MIN_STRING_LENGTH_TO_PACK_ROWS)
+ recinfo->type=FIELD_SKIP_ENDSPACE;
+ else
+ recinfo->type=FIELD_NORMAL;
+
+ field->set_table_name(&table->alias);
+ }
+
+ if (thd->variables.tmp_table_size == ~ (ulonglong) 0) // No limit
+ share->max_rows= ~(ha_rows) 0;
+ else
+ share->max_rows= (ha_rows) (((share->db_type() == heap_hton) ?
+ MY_MIN(thd->variables.tmp_table_size,
+ thd->variables.max_heap_table_size) :
+ thd->variables.tmp_table_size) /
+ share->reclength);
+ set_if_bigger(share->max_rows,1); // For dummy start options
+
+
+ //// keyinfo= param->keyinfo;
+ if (TRUE)
+ {
+ DBUG_PRINT("info",("Creating group key in temporary table"));
+ share->keys=1;
+ share->uniques= MY_TEST(using_unique_constraint);
+ table->key_info=keyinfo;
+ keyinfo->key_part=key_part_info;
+ keyinfo->flags=HA_NOSAME;
+ keyinfo->usable_key_parts= keyinfo->user_defined_key_parts= 1;
+ keyinfo->key_length=0;
+ keyinfo->rec_per_key=0;
+ keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+ keyinfo->name= (char*) "weedout_key";
+ {
+ key_part_info->null_bit=0;
+ key_part_info->field= field;
+ key_part_info->offset= field->offset(table->record[0]);
+ key_part_info->length= (uint16) field->key_length();
+ key_part_info->type= (uint8) field->key_type();
+ key_part_info->key_type = FIELDFLAG_BINARY;
+ if (!using_unique_constraint)
+ {
+ if (!(key_field= field->new_key_field(thd->mem_root, table,
+ group_buff,
+ key_part_info->length,
+ field->null_ptr,
+ field->null_bit)))
+ goto err;
+ key_part_info->key_part_flag|= HA_END_SPACE_ARE_EQUAL; //todo need this?
+ }
+ keyinfo->key_length+= key_part_info->length;
+ }
+ }
+
+ if (thd->is_fatal_error) // If end of memory
+ goto err;
+ share->db_record_offset= 1;
+ table->no_rows= 1; // We don't need the data
+
+ // recinfo must point after last field
+ recinfo++;
+ if (share->db_type() == TMP_ENGINE_HTON)
+ {
+ if (create_internal_tmp_table(table, keyinfo, start_recinfo, &recinfo, 0))
+ goto err;
+ }
+ if (open_tmp_table(table))
+ goto err;
+
+ thd->mem_root= mem_root_save;
+ tmp_table= table;
+ DBUG_RETURN(FALSE);
+
+err:
+ thd->mem_root= mem_root_save;
+ free_tmp_table(thd,table); /* purecov: inspected */
+ if (temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+}
+
+
+/*
+ SemiJoinDuplicateElimination: Reset the temporary table
+*/
+
+int SJ_TMP_TABLE::sj_weedout_delete_rows()
+{
+ DBUG_ENTER("SJ_TMP_TABLE::sj_weedout_delete_rows");
+ if (tmp_table)
+ {
+ int rc= tmp_table->file->ha_delete_all_rows();
+ DBUG_RETURN(rc);
+ }
+ have_degenerate_row= FALSE;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ SemiJoinDuplicateElimination: Weed out duplicate row combinations
+
+ SYNPOSIS
+ sj_weedout_check_row()
+ thd Thread handle
+
+ DESCRIPTION
+ Try storing current record combination of outer tables (i.e. their
+ rowids) in the temporary table. This records the fact that we've seen
+ this record combination and also tells us if we've seen it before.
+
+ RETURN
+ -1 Error
+ 1 The row combination is a duplicate (discard it)
+ 0 The row combination is not a duplicate (continue)
+*/
+
+int SJ_TMP_TABLE::sj_weedout_check_row(THD *thd)
+{
+ int error;
+ SJ_TMP_TABLE::TAB *tab= tabs;
+ SJ_TMP_TABLE::TAB *tab_end= tabs_end;
+ uchar *ptr;
+ uchar *nulls_ptr;
+
+ DBUG_ENTER("SJ_TMP_TABLE::sj_weedout_check_row");
+
+ if (is_degenerate)
+ {
+ if (have_degenerate_row)
+ DBUG_RETURN(1);
+
+ have_degenerate_row= TRUE;
+ DBUG_RETURN(0);
+ }
+
+ ptr= tmp_table->record[0] + 1;
+
+ /* Put the the rowids tuple into table->record[0]: */
+
+ // 1. Store the length
+ if (((Field_varstring*)(tmp_table->field[0]))->length_bytes == 1)
+ {
+ *ptr= (uchar)(rowid_len + null_bytes);
+ ptr++;
+ }
+ else
+ {
+ int2store(ptr, rowid_len + null_bytes);
+ ptr += 2;
+ }
+
+ nulls_ptr= ptr;
+ // 2. Zero the null bytes
+ if (null_bytes)
+ {
+ bzero(ptr, null_bytes);
+ ptr += null_bytes;
+ }
+
+ // 3. Put the rowids
+ for (uint i=0; tab != tab_end; tab++, i++)
+ {
+ handler *h= tab->join_tab->table->file;
+ if (tab->join_tab->table->maybe_null && tab->join_tab->table->null_row)
+ {
+ /* It's a NULL-complemented row */
+ *(nulls_ptr + tab->null_byte) |= tab->null_bit;
+ bzero(ptr + tab->rowid_offset, h->ref_length);
+ }
+ else
+ {
+ /* Copy the rowid value */
+ memcpy(ptr + tab->rowid_offset, h->ref, h->ref_length);
+ }
+ }
+
+ error= tmp_table->file->ha_write_tmp_row(tmp_table->record[0]);
+ if (error)
+ {
+ /* create_internal_tmp_table_from_heap will generate error if needed */
+ if (!tmp_table->file->is_fatal_error(error, HA_CHECK_DUP))
+ DBUG_RETURN(1); /* Duplicate */
+
+ bool is_duplicate;
+ if (create_internal_tmp_table_from_heap(thd, tmp_table, start_recinfo,
+ &recinfo, error, 1, &is_duplicate))
+ DBUG_RETURN(-1);
+ if (is_duplicate)
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+int init_dups_weedout(JOIN *join, uint first_table, int first_fanout_table, uint n_tables)
+{
+ THD *thd= join->thd;
+ DBUG_ENTER("init_dups_weedout");
+ SJ_TMP_TABLE::TAB sjtabs[MAX_TABLES];
+ SJ_TMP_TABLE::TAB *last_tab= sjtabs;
+ uint jt_rowid_offset= 0; // # tuple bytes are already occupied (w/o NULL bytes)
+ uint jt_null_bits= 0; // # null bits in tuple bytes
+ /*
+ Walk through the range and remember
+ - tables that need their rowids to be put into temptable
+ - the last outer table
+ */
+ for (JOIN_TAB *j=join->join_tab + first_table;
+ j < join->join_tab + first_table + n_tables; j++)
+ {
+ if (sj_table_is_included(join, j))
+ {
+ last_tab->join_tab= j;
+ last_tab->rowid_offset= jt_rowid_offset;
+ jt_rowid_offset += j->table->file->ref_length;
+ if (j->table->maybe_null)
+ {
+ last_tab->null_byte= jt_null_bits / 8;
+ last_tab->null_bit= jt_null_bits++;
+ }
+ last_tab++;
+ j->table->prepare_for_position();
+ j->keep_current_rowid= TRUE;
+ }
+ }
+
+ SJ_TMP_TABLE *sjtbl;
+ if (jt_rowid_offset) /* Temptable has at least one rowid */
+ {
+ size_t tabs_size= (last_tab - sjtabs) * sizeof(SJ_TMP_TABLE::TAB);
+ if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))) ||
+ !(sjtbl->tabs= (SJ_TMP_TABLE::TAB*) thd->alloc(tabs_size)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ memcpy(sjtbl->tabs, sjtabs, tabs_size);
+ sjtbl->is_degenerate= FALSE;
+ sjtbl->tabs_end= sjtbl->tabs + (last_tab - sjtabs);
+ sjtbl->rowid_len= jt_rowid_offset;
+ sjtbl->null_bits= jt_null_bits;
+ sjtbl->null_bytes= (jt_null_bits + 7)/8;
+ if (sjtbl->create_sj_weedout_tmp_table(thd))
+ DBUG_RETURN(TRUE);
+ join->sj_tmp_tables.push_back(sjtbl->tmp_table);
+ }
+ else
+ {
+ /*
+ This is a special case where the entire subquery predicate does
+ not depend on anything at all, ie this is
+ WHERE const IN (uncorrelated select)
+ */
+ if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ sjtbl->tmp_table= NULL;
+ sjtbl->is_degenerate= TRUE;
+ sjtbl->have_degenerate_row= FALSE;
+ }
+
+ sjtbl->next_flush_table= join->join_tab[first_table].flush_weedout_table;
+ join->join_tab[first_table].flush_weedout_table= sjtbl;
+ join->join_tab[first_fanout_table].first_weedout_table= sjtbl;
+ join->join_tab[first_table + n_tables - 1].check_weed_out_table= sjtbl;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Setup the strategies to eliminate semi-join duplicates.
+
+ SYNOPSIS
+ setup_semijoin_dups_elimination()
+ join Join to process
+ options Join options (needed to see if join buffering will be
+ used or not)
+ no_jbuf_after Another bit of information re where join buffering will
+ be used.
+
+ DESCRIPTION
+ Setup the strategies to eliminate semi-join duplicates. ATM there are 4
+ strategies:
+
+ 1. DuplicateWeedout (use of temptable to remove duplicates based on rowids
+ of row combinations)
+ 2. FirstMatch (pick only the 1st matching row combination of inner tables)
+ 3. LooseScan (scanning the sj-inner table in a way that groups duplicates
+ together and picking the 1st one)
+ 4. SJ-Materialization.
+
+ The join order has "duplicate-generating ranges", and every range is
+ served by one strategy or a combination of FirstMatch with with some
+ other strategy.
+
+ "Duplicate-generating range" is defined as a range within the join order
+ that contains all of the inner tables of a semi-join. All ranges must be
+ disjoint, if tables of several semi-joins are interleaved, then the ranges
+ are joined together, which is equivalent to converting
+ SELECT ... WHERE oe1 IN (SELECT ie1 ...) AND oe2 IN (SELECT ie2 )
+ to
+ SELECT ... WHERE (oe1, oe2) IN (SELECT ie1, ie2 ... ...)
+ .
+
+ Applicability conditions are as follows:
+
+ DuplicateWeedout strategy
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ (ot|nt)* [ it ((it|ot|nt)* (it|ot))] (nt)*
+ +------+ +=========================+ +---+
+ (1) (2) (3)
+
+ (1) - Prefix of OuterTables (those that participate in
+ IN-equality and/or are correlated with subquery) and outer
+ Non-correlated tables.
+ (2) - The handled range. The range starts with the first sj-inner
+ table, and covers all sj-inner and outer tables
+ Within the range, Inner, Outer, outer non-correlated tables
+ may follow in any order.
+ (3) - The suffix of outer non-correlated tables.
+
+ FirstMatch strategy
+ ~~~~~~~~~~~~~~~~~~~
+
+ (ot|nt)* [ it ((it|nt)* it) ] (nt)*
+ +------+ +==================+ +---+
+ (1) (2) (3)
+
+ (1) - Prefix of outer and non-correlated tables
+ (2) - The handled range, which may contain only inner and
+ non-correlated tables.
+ (3) - The suffix of outer non-correlated tables.
+
+ LooseScan strategy
+ ~~~~~~~~~~~~~~~~~~
+
+ (ot|ct|nt) [ loosescan_tbl (ot|nt|it)* it ] (ot|nt)*
+ +--------+ +===========+ +=============+ +------+
+ (1) (2) (3) (4)
+
+ (1) - Prefix that may contain any outer tables. The prefix must contain
+ all the non-trivially correlated outer tables. (non-trivially means
+ that the correlation is not just through the IN-equality).
+
+ (2) - Inner table for which the LooseScan scan is performed.
+
+ (3) - The remainder of the duplicate-generating range. It is served by
+ application of FirstMatch strategy, with the exception that
+ outer IN-correlated tables are considered to be non-correlated.
+
+ (4) - THe suffix of outer and outer non-correlated tables.
+
+
+ The choice between the strategies is made by the join optimizer (see
+ advance_sj_state() and fix_semijoin_strategies_for_picked_join_order()).
+ This function sets up all fields/structures/etc needed for execution except
+ for setup/initialization of semi-join materialization which is done in
+ setup_sj_materialization() (todo: can't we move that to here also?)
+
+ RETURN
+ FALSE OK
+ TRUE Out of memory error
+*/
+
+int setup_semijoin_dups_elimination(JOIN *join, ulonglong options,
+ uint no_jbuf_after)
+{
+ uint i;
+ DBUG_ENTER("setup_semijoin_dups_elimination");
+
+ join->complex_firstmatch_tables= table_map(0);
+
+ POSITION *pos= join->best_positions + join->const_tables;
+ for (i= join->const_tables ; i < join->top_join_tab_count; )
+ {
+ JOIN_TAB *tab=join->join_tab + i;
+ //POSITION *pos= join->best_positions + i;
+ uint keylen, keyno;
+ switch (pos->sj_strategy) {
+ case SJ_OPT_MATERIALIZE:
+ case SJ_OPT_MATERIALIZE_SCAN:
+ /* Do nothing */
+ i+= 1;// It used to be pos->n_sj_tables, but now they are embedded in a nest
+ pos += pos->n_sj_tables;
+ break;
+ case SJ_OPT_LOOSE_SCAN:
+ {
+ /* We jump from the last table to the first one */
+ tab->loosescan_match_tab= tab + pos->n_sj_tables - 1;
+
+ /* LooseScan requires records to be produced in order */
+ if (tab->select && tab->select->quick)
+ tab->select->quick->need_sorted_output();
+
+ for (uint j= i; j < i + pos->n_sj_tables; j++)
+ join->join_tab[j].inside_loosescan_range= TRUE;
+
+ /* Calculate key length */
+ keylen= 0;
+ keyno= pos->loosescan_picker.loosescan_key;
+ for (uint kp=0; kp < pos->loosescan_picker.loosescan_parts; kp++)
+ keylen += tab->table->key_info[keyno].key_part[kp].store_length;
+
+ tab->loosescan_key= keyno;
+ tab->loosescan_key_len= keylen;
+ if (pos->n_sj_tables > 1)
+ tab[pos->n_sj_tables - 1].do_firstmatch= tab;
+ i+= pos->n_sj_tables;
+ pos+= pos->n_sj_tables;
+ break;
+ }
+ case SJ_OPT_DUPS_WEEDOUT:
+ {
+ /*
+ Check for join buffering. If there is one, move the first table
+ forwards, but do not destroy other duplicate elimination methods.
+ */
+ uint first_table= i;
+
+ uint join_cache_level= join->thd->variables.join_cache_level;
+ for (uint j= i; j < i + pos->n_sj_tables; j++)
+ {
+ /*
+ When we'll properly take join buffering into account during
+ join optimization, the below check should be changed to
+ "if (join->best_positions[j].use_join_buffer &&
+ j <= no_jbuf_after)".
+ For now, use a rough criteria:
+ */
+ JOIN_TAB *js_tab=join->join_tab + j;
+ if (j != join->const_tables && js_tab->use_quick != 2 &&
+ j <= no_jbuf_after &&
+ ((js_tab->type == JT_ALL && join_cache_level != 0) ||
+ (join_cache_level > 2 && (js_tab->type == JT_REF ||
+ js_tab->type == JT_EQ_REF))))
+ {
+ /* Looks like we'll be using join buffer */
+ first_table= join->const_tables;
+ /*
+ Make sure that possible sorting of rows from the head table
+ is not to be employed.
+ */
+ if (join->get_sort_by_join_tab())
+ {
+ join->simple_order= 0;
+ join->simple_group= 0;
+ join->need_tmp= join->test_if_need_tmp_table();
+ }
+ break;
+ }
+ }
+
+ init_dups_weedout(join, first_table, i, i + pos->n_sj_tables - first_table);
+ i+= pos->n_sj_tables;
+ pos+= pos->n_sj_tables;
+ break;
+ }
+ case SJ_OPT_FIRST_MATCH:
+ {
+ JOIN_TAB *j;
+ JOIN_TAB *jump_to= tab-1;
+
+ bool complex_range= FALSE;
+ table_map tables_in_range= table_map(0);
+
+ for (j= tab; j != tab + pos->n_sj_tables; j++)
+ {
+ tables_in_range |= j->table->map;
+ if (!j->emb_sj_nest)
+ {
+ /*
+ Got a table that's not within any semi-join nest. This is a case
+ like this:
+
+ SELECT * FROM ot1, nt1 WHERE ot1.col IN (SELECT expr FROM it1, it2)
+
+ with a join order of
+
+ +----- FirstMatch range ----+
+ | |
+ ot1 it1 nt1 nt2 it2 it3 ...
+ | ^
+ | +-------- 'j' points here
+ +------------- SJ_OPT_FIRST_MATCH was set for this table as
+ it's the first one that produces duplicates
+
+ */
+ DBUG_ASSERT(j != tab); /* table ntX must have an itX before it */
+
+ /*
+ If the table right before us is an inner table (like it1 in the
+ picture), it should be set to jump back to previous outer-table
+ */
+ if (j[-1].emb_sj_nest)
+ j[-1].do_firstmatch= jump_to;
+
+ jump_to= j; /* Jump back to us */
+ complex_range= TRUE;
+ }
+ else
+ {
+ j->first_sj_inner_tab= tab;
+ j->last_sj_inner_tab= tab + pos->n_sj_tables - 1;
+ }
+ }
+ j[-1].do_firstmatch= jump_to;
+ i+= pos->n_sj_tables;
+ pos+= pos->n_sj_tables;
+
+ if (complex_range)
+ join->complex_firstmatch_tables|= tables_in_range;
+ break;
+ }
+ case SJ_OPT_NONE:
+ i++;
+ pos++;
+ break;
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Destroy all temporary tables created by NL-semijoin runtime
+*/
+
+void destroy_sj_tmp_tables(JOIN *join)
+{
+ List_iterator<TABLE> it(join->sj_tmp_tables);
+ TABLE *table;
+ while ((table= it++))
+ {
+ /*
+ SJ-Materialization tables are initialized for either sequential reading
+ or index lookup, DuplicateWeedout tables are not initialized for read
+ (we only write to them), so need to call ha_index_or_rnd_end.
+ */
+ table->file->ha_index_or_rnd_end();
+ free_tmp_table(join->thd, table);
+ }
+ join->sj_tmp_tables.empty();
+ join->sjm_info_list.empty();
+}
+
+
+/*
+ Remove all records from all temp tables used by NL-semijoin runtime
+
+ SYNOPSIS
+ clear_sj_tmp_tables()
+ join The join to remove tables for
+
+ DESCRIPTION
+ Remove all records from all temp tables used by NL-semijoin runtime. This
+ must be done before every join re-execution.
+*/
+
+int clear_sj_tmp_tables(JOIN *join)
+{
+ int res;
+ List_iterator<TABLE> it(join->sj_tmp_tables);
+ TABLE *table;
+ while ((table= it++))
+ {
+ if ((res= table->file->ha_delete_all_rows()))
+ return res; /* purecov: inspected */
+ free_io_cache(table);
+ filesort_free_buffers(table,0);
+ }
+
+ SJ_MATERIALIZATION_INFO *sjm;
+ List_iterator<SJ_MATERIALIZATION_INFO> it2(join->sjm_info_list);
+ while ((sjm= it2++))
+ {
+ sjm->materialized= FALSE;
+ }
+ return 0;
+}
+
+
+/*
+ Check if the table's rowid is included in the temptable
+
+ SYNOPSIS
+ sj_table_is_included()
+ join The join
+ join_tab The table to be checked
+
+ DESCRIPTION
+ SemiJoinDuplicateElimination: check the table's rowid should be included
+ in the temptable. This is so if
+
+ 1. The table is not embedded within some semi-join nest
+ 2. The has been pulled out of a semi-join nest, or
+
+ 3. The table is functionally dependent on some previous table
+
+ [4. This is also true for constant tables that can't be
+ NULL-complemented but this function is not called for such tables]
+
+ RETURN
+ TRUE - Include table's rowid
+ FALSE - Don't
+*/
+
+static bool sj_table_is_included(JOIN *join, JOIN_TAB *join_tab)
+{
+ if (join_tab->emb_sj_nest)
+ return FALSE;
+
+ /* Check if this table is functionally dependent on the tables that
+ are within the same outer join nest
+ */
+ TABLE_LIST *embedding= join_tab->table->pos_in_table_list->embedding;
+ if (join_tab->type == JT_EQ_REF)
+ {
+ table_map depends_on= 0;
+ uint idx;
+
+ for (uint kp= 0; kp < join_tab->ref.key_parts; kp++)
+ depends_on |= join_tab->ref.items[kp]->used_tables();
+
+ Table_map_iterator it(depends_on & ~PSEUDO_TABLE_BITS);
+ while ((idx= it.next_bit())!=Table_map_iterator::BITMAP_END)
+ {
+ JOIN_TAB *ref_tab= join->map2table[idx];
+ if (embedding != ref_tab->table->pos_in_table_list->embedding)
+ return TRUE;
+ }
+ /* Ok, functionally dependent */
+ return FALSE;
+ }
+ /* Not functionally dependent => need to include*/
+ return TRUE;
+}
+
+
+/*
+ Index lookup-based subquery: save some flags for EXPLAIN output
+
+ SYNOPSIS
+ save_index_subquery_explain_info()
+ join_tab Subquery's join tab (there is only one as index lookup is
+ only used for subqueries that are single-table SELECTs)
+ where Subquery's WHERE clause
+
+ DESCRIPTION
+ For index lookup-based subquery (i.e. one executed with
+ subselect_uniquesubquery_engine or subselect_indexsubquery_engine),
+ check its EXPLAIN output row should contain
+ "Using index" (TAB_INFO_FULL_SCAN_ON_NULL)
+ "Using Where" (TAB_INFO_USING_WHERE)
+ "Full scan on NULL key" (TAB_INFO_FULL_SCAN_ON_NULL)
+ and set appropriate flags in join_tab->packed_info.
+*/
+
+static void save_index_subquery_explain_info(JOIN_TAB *join_tab, Item* where)
+{
+ join_tab->packed_info= TAB_INFO_HAVE_VALUE;
+ if (join_tab->table->covering_keys.is_set(join_tab->ref.key))
+ join_tab->packed_info |= TAB_INFO_USING_INDEX;
+ if (where)
+ join_tab->packed_info |= TAB_INFO_USING_WHERE;
+ for (uint i = 0; i < join_tab->ref.key_parts; i++)
+ {
+ if (join_tab->ref.cond_guards[i])
+ {
+ join_tab->packed_info |= TAB_INFO_FULL_SCAN_ON_NULL;
+ break;
+ }
+ }
+}
+
+
+/*
+ Check if the join can be rewritten to [unique_]indexsubquery_engine
+
+ DESCRIPTION
+ Check if the join can be changed into [unique_]indexsubquery_engine.
+
+ The check is done after join optimization, the idea is that if the join
+ has only one table and uses a [eq_]ref access generated from subselect's
+ IN-equality then we replace it with a subselect_indexsubquery_engine or a
+ subselect_uniquesubquery_engine.
+
+ RETURN
+ 0 - Ok, rewrite done (stop join optimization and return)
+ 1 - Fatal error (stop join optimization and return)
+ -1 - No rewrite performed, continue with join optimization
+*/
+
+int rewrite_to_index_subquery_engine(JOIN *join)
+{
+ THD *thd= join->thd;
+ JOIN_TAB* join_tab=join->join_tab;
+ SELECT_LEX_UNIT *unit= join->unit;
+ DBUG_ENTER("rewrite_to_index_subquery_engine");
+
+ /*
+ is this simple IN subquery?
+ */
+ /* TODO: In order to use these more efficient subquery engines in more cases,
+ the following problems need to be solved:
+ - the code that removes GROUP BY (group_list), also adds an ORDER BY
+ (order), thus GROUP BY queries (almost?) never pass through this branch.
+ Solution: remove the test below '!join->order', because we remove the
+ ORDER clase for subqueries anyway.
+ - in order to set a more efficient engine, the optimizer needs to both
+ decide to remove GROUP BY, *and* select one of the JT_[EQ_]REF[_OR_NULL]
+ access methods, *and* loose scan should be more expensive or
+ inapliccable. When is that possible?
+ - Consider expanding the applicability of this rewrite for loose scan
+ for group by queries.
+ */
+ if (!join->group_list && !join->order &&
+ join->unit->item &&
+ join->unit->item->substype() == Item_subselect::IN_SUBS &&
+ join->table_count == 1 && join->conds &&
+ !join->unit->is_union())
+ {
+ if (!join->having)
+ {
+ Item *where= join->conds;
+ if (join_tab[0].type == JT_EQ_REF &&
+ join_tab[0].ref.items[0]->name == in_left_expr_name)
+ {
+ remove_subq_pushed_predicates(join, &where);
+ save_index_subquery_explain_info(join_tab, where);
+ join_tab[0].type= JT_UNIQUE_SUBQUERY;
+ join->error= 0;
+ DBUG_RETURN(unit->item->
+ change_engine(new
+ subselect_uniquesubquery_engine(thd,
+ join_tab,
+ unit->item,
+ where)));
+ }
+ else if (join_tab[0].type == JT_REF &&
+ join_tab[0].ref.items[0]->name == in_left_expr_name)
+ {
+ remove_subq_pushed_predicates(join, &where);
+ save_index_subquery_explain_info(join_tab, where);
+ join_tab[0].type= JT_INDEX_SUBQUERY;
+ join->error= 0;
+ DBUG_RETURN(unit->item->
+ change_engine(new
+ subselect_indexsubquery_engine(thd,
+ join_tab,
+ unit->item,
+ where,
+ NULL,
+ 0)));
+ }
+ } else if (join_tab[0].type == JT_REF_OR_NULL &&
+ join_tab[0].ref.items[0]->name == in_left_expr_name &&
+ join->having->name == in_having_cond)
+ {
+ join_tab[0].type= JT_INDEX_SUBQUERY;
+ join->error= 0;
+ join->conds= remove_additional_cond(join->conds);
+ save_index_subquery_explain_info(join_tab, join->conds);
+ DBUG_RETURN(unit->item->
+ change_engine(new subselect_indexsubquery_engine(thd,
+ join_tab,
+ unit->item,
+ join->conds,
+ join->having,
+ 1)));
+ }
+ }
+
+ DBUG_RETURN(-1); /* Haven't done the rewrite */
+}
+
+
+/**
+ Remove additional condition inserted by IN/ALL/ANY transformation.
+
+ @param conds condition for processing
+
+ @return
+ new conditions
+*/
+
+static Item *remove_additional_cond(Item* conds)
+{
+ if (conds->name == in_additional_cond)
+ return 0;
+ if (conds->type() == Item::COND_ITEM)
+ {
+ Item_cond *cnd= (Item_cond*) conds;
+ List_iterator<Item> li(*(cnd->argument_list()));
+ Item *item;
+ while ((item= li++))
+ {
+ if (item->name == in_additional_cond)
+ {
+ li.remove();
+ if (cnd->argument_list()->elements == 1)
+ return cnd->argument_list()->head();
+ return conds;
+ }
+ }
+ }
+ return conds;
+}
+
+
+/*
+ Remove the predicates pushed down into the subquery
+
+ SYNOPSIS
+ remove_subq_pushed_predicates()
+ where IN Must be NULL
+ OUT The remaining WHERE condition, or NULL
+
+ DESCRIPTION
+ Given that this join will be executed using (unique|index)_subquery,
+ without "checking NULL", remove the predicates that were pushed down
+ into the subquery.
+
+ If the subquery compares scalar values, we can remove the condition that
+ was wrapped into trig_cond (it will be checked when needed by the subquery
+ engine)
+
+ If the subquery compares row values, we need to keep the wrapped
+ equalities in the WHERE clause: when the left (outer) tuple has both NULL
+ and non-NULL values, we'll do a full table scan and will rely on the
+ equalities corresponding to non-NULL parts of left tuple to filter out
+ non-matching records.
+
+ TODO: We can remove the equalities that will be guaranteed to be true by the
+ fact that subquery engine will be using index lookup. This must be done only
+ for cases where there are no conversion errors of significance, e.g. 257
+ that is searched in a byte. But this requires homogenization of the return
+ codes of all Field*::store() methods.
+*/
+
+static void remove_subq_pushed_predicates(JOIN *join, Item **where)
+{
+ if (join->conds->type() == Item::FUNC_ITEM &&
+ ((Item_func *)join->conds)->functype() == Item_func::EQ_FUNC &&
+ ((Item_func *)join->conds)->arguments()[0]->type() == Item::REF_ITEM &&
+ ((Item_func *)join->conds)->arguments()[1]->type() == Item::FIELD_ITEM &&
+ test_if_ref (join->conds,
+ (Item_field *)((Item_func *)join->conds)->arguments()[1],
+ ((Item_func *)join->conds)->arguments()[0]))
+ {
+ *where= 0;
+ return;
+ }
+}
+
+
+
+
+/**
+ Optimize all subqueries of a query that were not flattened into a semijoin.
+
+ @details
+ Optimize all immediate children subqueries of a query.
+
+ This phase must be called after substitute_for_best_equal_field() because
+ that function may replace items with other items from a multiple equality,
+ and we need to reference the correct items in the index access method of the
+ IN predicate.
+
+ @return Operation status
+ @retval FALSE success.
+ @retval TRUE error occurred.
+*/
+
+bool JOIN::optimize_unflattened_subqueries()
+{
+ return select_lex->optimize_unflattened_subqueries(false);
+}
+
+/**
+ Optimize all constant subqueries of a query that were not flattened into
+ a semijoin.
+
+ @details
+ Similar to other constant conditions, constant subqueries can be used in
+ various constant optimizations. Having optimized constant subqueries before
+ these constant optimizations, makes it possible to estimate if a subquery
+ is "cheap" enough to be executed during the optimization phase.
+
+ Constant subqueries can be optimized and evaluated independent of the outer
+ query, therefore if const_only = true, this method can be called early in
+ the optimization phase of the outer query.
+
+ @return Operation status
+ @retval FALSE success.
+ @retval TRUE error occurred.
+*/
+
+bool JOIN::optimize_constant_subqueries()
+{
+ ulonglong save_options= select_lex->options;
+ bool res;
+ /*
+ Constant subqueries may be executed during the optimization phase.
+ In EXPLAIN mode the optimizer doesn't initialize many of the data structures
+ needed for execution. In order to make it possible to execute subqueries
+ during optimization, constant subqueries must be optimized for execution,
+ not for EXPLAIN.
+ */
+ select_lex->options&= ~SELECT_DESCRIBE;
+ res= select_lex->optimize_unflattened_subqueries(true);
+ select_lex->options= save_options;
+ return res;
+}
+
+
+/*
+ Join tab execution startup function.
+
+ SYNOPSIS
+ join_tab_execution_startup()
+ tab Join tab to perform startup actions for
+
+ DESCRIPTION
+ Join tab execution startup function. This is different from
+ tab->read_first_record in the regard that this has actions that are to be
+ done once per join execution.
+
+ Currently there are only two possible startup functions, so we have them
+ both here inside if (...) branches. In future we could switch to function
+ pointers.
+
+ TODO: consider moving this together with JOIN_TAB::preread_init
+
+ RETURN
+ NESTED_LOOP_OK - OK
+ NESTED_LOOP_ERROR| NESTED_LOOP_KILLED - Error, abort the join execution
+*/
+
+enum_nested_loop_state join_tab_execution_startup(JOIN_TAB *tab)
+{
+ Item_in_subselect *in_subs;
+ DBUG_ENTER("join_tab_execution_startup");
+
+ if (tab->table->pos_in_table_list &&
+ (in_subs= tab->table->pos_in_table_list->jtbm_subselect))
+ {
+ /* It's a non-merged SJM nest */
+ DBUG_ASSERT(in_subs->engine->engine_type() ==
+ subselect_engine::HASH_SJ_ENGINE);
+ subselect_hash_sj_engine *hash_sj_engine=
+ ((subselect_hash_sj_engine*)in_subs->engine);
+ if (!hash_sj_engine->is_materialized)
+ {
+ hash_sj_engine->materialize_join->exec();
+ hash_sj_engine->is_materialized= TRUE;
+
+ if (hash_sj_engine->materialize_join->error || tab->join->thd->is_fatal_error)
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ }
+ }
+ else if (tab->bush_children)
+ {
+ /* It's a merged SJM nest */
+ enum_nested_loop_state rc;
+ SJ_MATERIALIZATION_INFO *sjm= tab->bush_children->start->emb_sj_nest->sj_mat_info;
+
+ if (!sjm->materialized)
+ {
+ JOIN *join= tab->join;
+ JOIN_TAB *join_tab= tab->bush_children->start;
+ JOIN_TAB *save_return_tab= join->return_tab;
+ /*
+ Now run the join for the inner tables. The first call is to run the
+ join, the second one is to signal EOF (this is essential for some
+ join strategies, e.g. it will make join buffering flush the records)
+ */
+ if ((rc= sub_select(join, join_tab, FALSE/* no EOF */)) < 0 ||
+ (rc= sub_select(join, join_tab, TRUE/* now EOF */)) < 0)
+ {
+ join->return_tab= save_return_tab;
+ DBUG_RETURN(rc); /* it's NESTED_LOOP_(ERROR|KILLED)*/
+ }
+ join->return_tab= save_return_tab;
+ sjm->materialized= TRUE;
+ }
+ }
+
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+
+/*
+ Create a dummy temporary table, useful only for the sake of having a
+ TABLE* object with map,tablenr and maybe_null properties.
+
+ This is used by non-mergeable semi-join materilization code to handle
+ degenerate cases where materialized subquery produced "Impossible WHERE"
+ and thus wasn't materialized.
+*/
+
+TABLE *create_dummy_tmp_table(THD *thd)
+{
+ DBUG_ENTER("create_dummy_tmp_table");
+ TABLE *table;
+ TMP_TABLE_PARAM sjm_table_param;
+ sjm_table_param.init();
+ sjm_table_param.field_count= 1;
+ List<Item> sjm_table_cols;
+ Item *column_item= new Item_int(1);
+ sjm_table_cols.push_back(column_item);
+ if (!(table= create_tmp_table(thd, &sjm_table_param,
+ sjm_table_cols, (ORDER*) 0,
+ TRUE /* distinct */,
+ 1, /*save_sum_fields*/
+ thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS,
+ HA_POS_ERROR /*rows_limit */,
+ (char*)"dummy", TRUE /* Do not open */)))
+ {
+ DBUG_RETURN(NULL);
+ }
+ DBUG_RETURN(table);
+}
+
+
+/*
+ A class that is used to catch one single tuple that is sent to the join
+ output, and save it in Item_cache element(s).
+
+ It is very similar to select_singlerow_subselect but doesn't require a
+ Item_singlerow_subselect item.
+*/
+
+class select_value_catcher :public select_subselect
+{
+public:
+ select_value_catcher(Item_subselect *item_arg)
+ :select_subselect(item_arg)
+ {}
+ int send_data(List<Item> &items);
+ int setup(List<Item> *items);
+ bool assigned; /* TRUE <=> we've caught a value */
+ uint n_elements; /* How many elements we get */
+ Item_cache **row; /* Array of cache elements */
+};
+
+
+int select_value_catcher::setup(List<Item> *items)
+{
+ assigned= FALSE;
+ n_elements= items->elements;
+
+ if (!(row= (Item_cache**) sql_alloc(sizeof(Item_cache*)*n_elements)))
+ return TRUE;
+
+ Item *sel_item;
+ List_iterator<Item> li(*items);
+ for (uint i= 0; (sel_item= li++); i++)
+ {
+ if (!(row[i]= Item_cache::get_cache(sel_item)))
+ return TRUE;
+ row[i]->setup(sel_item);
+ }
+ return FALSE;
+}
+
+
+int select_value_catcher::send_data(List<Item> &items)
+{
+ DBUG_ENTER("select_value_catcher::send_data");
+ DBUG_ASSERT(!assigned);
+ DBUG_ASSERT(items.elements == n_elements);
+
+ if (unit->offset_limit_cnt)
+ { // Using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(0);
+ }
+
+ Item *val_item;
+ List_iterator_fast<Item> li(items);
+ for (uint i= 0; (val_item= li++); i++)
+ {
+ row[i]->store(val_item);
+ row[i]->cache_value();
+ }
+ assigned= TRUE;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Setup JTBM join tabs for execution
+*/
+
+bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list,
+ Item **join_where)
+{
+ TABLE_LIST *table;
+ NESTED_JOIN *nested_join;
+ List_iterator<TABLE_LIST> li(*join_list);
+ DBUG_ENTER("setup_jtbm_semi_joins");
+
+ while ((table= li++))
+ {
+ Item_in_subselect *item;
+
+ if ((item= table->jtbm_subselect))
+ {
+ Item_in_subselect *subq_pred= item;
+ double rows;
+ double read_time;
+
+ /*
+ Perform optimization of the subquery, so that we know estmated
+ - cost of materialization process
+ - how many records will be in the materialized temp.table
+ */
+ if (subq_pred->optimize(&rows, &read_time))
+ DBUG_RETURN(TRUE);
+
+ subq_pred->jtbm_read_time= read_time;
+ subq_pred->jtbm_record_count=rows;
+ JOIN *subq_join= subq_pred->unit->first_select()->join;
+
+ if (!subq_join->tables_list || !subq_join->table_count)
+ {
+ /*
+ A special case; subquery's join is degenerate, and it either produces
+ 0 or 1 record. Examples of both cases:
+
+ select * from ot where col in (select ... from it where 2>3)
+ select * from ot where col in (select MY_MIN(it.key) from it)
+
+ in this case, the subquery predicate has not been setup for
+ materialization. In particular, there is no materialized temp.table.
+ We'll now need to
+ 1. Check whether 1 or 0 records are produced, setup this as a
+ constant join tab.
+ 2. Create a dummy temporary table, because all of the join
+ optimization code relies on TABLE object being present (here we
+ follow a bad tradition started by derived tables)
+ */
+ DBUG_ASSERT(subq_pred->engine->engine_type() ==
+ subselect_engine::SINGLE_SELECT_ENGINE);
+ subselect_single_select_engine *engine=
+ (subselect_single_select_engine*)subq_pred->engine;
+ select_value_catcher *new_sink;
+ if (!(new_sink= new select_value_catcher(subq_pred)))
+ DBUG_RETURN(TRUE);
+ if (new_sink->setup(&engine->select_lex->join->fields_list) ||
+ engine->select_lex->join->change_result(new_sink) ||
+ engine->exec())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ subq_pred->is_jtbm_const_tab= TRUE;
+
+ if (new_sink->assigned)
+ {
+ subq_pred->jtbm_const_row_found= TRUE;
+ /*
+ Subselect produced one row, which is saved in new_sink->row.
+ Inject "left_expr[i] == row[i] equalities into parent's WHERE.
+ */
+ Item *eq_cond;
+ for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
+ {
+ eq_cond= new Item_func_eq(subq_pred->left_expr->element_index(i),
+ new_sink->row[i]);
+ if (!eq_cond)
+ DBUG_RETURN(1);
+
+ if (!((*join_where)= and_items(*join_where, eq_cond)) ||
+ (*join_where)->fix_fields(join->thd, join_where))
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ /* Subselect produced no rows. Just set the flag, */
+ subq_pred->jtbm_const_row_found= FALSE;
+ }
+
+ /* Set up a dummy TABLE*, optimizer code needs JOIN_TABs to have TABLE */
+ TABLE *dummy_table;
+ if (!(dummy_table= create_dummy_tmp_table(join->thd)))
+ DBUG_RETURN(1);
+ table->table= dummy_table;
+ table->table->pos_in_table_list= table;
+ /*
+ Note: the table created above may be freed by:
+ 1. JOIN_TAB::cleanup(), when the parent join is a regular join.
+ 2. cleanup_empty_jtbm_semi_joins(), when the parent join is a
+ degenerate join (e.g. one with "Impossible where").
+ */
+ setup_table_map(table->table, table, table->jtbm_table_no);
+ }
+ else
+ {
+ DBUG_ASSERT(subq_pred->test_set_strategy(SUBS_MATERIALIZATION));
+ subq_pred->is_jtbm_const_tab= FALSE;
+ subselect_hash_sj_engine *hash_sj_engine=
+ ((subselect_hash_sj_engine*)item->engine);
+
+ table->table= hash_sj_engine->tmp_table;
+ table->table->pos_in_table_list= table;
+
+ setup_table_map(table->table, table, table->jtbm_table_no);
+
+ Item *sj_conds= hash_sj_engine->semi_join_conds;
+
+ (*join_where)= and_items(*join_where, sj_conds);
+ if (!(*join_where)->fixed)
+ (*join_where)->fix_fields(join->thd, join_where);
+ }
+ table->table->maybe_null= MY_TEST(join->mixed_implicit_grouping);
+ }
+
+ if ((nested_join= table->nested_join))
+ {
+ if (setup_jtbm_semi_joins(join, &nested_join->join_list, join_where))
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Cleanup non-merged semi-joins (JBMs) that have empty.
+
+ This function is to cleanups for a special case:
+ Consider a query like
+
+ select * from t1 where 1=2 AND t1.col IN (select max(..) ... having 1=2)
+
+ For this query, optimization of subquery will short-circuit, and
+ setup_jtbm_semi_joins() will call create_dummy_tmp_table() so that we have
+ empty, constant temp.table to stand in as materialized temp. table.
+
+ Now, suppose that the upper join is also found to be degenerate. In that
+ case, no JOIN_TAB array will be produced, and hence, JOIN::cleanup() will
+ have a problem with cleaning up empty JTBMs (non-empty ones are cleaned up
+ through Item::cleanup() calls).
+*/
+
+void cleanup_empty_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list)
+{
+ List_iterator<TABLE_LIST> li(*join_list);
+ TABLE_LIST *table;
+ while ((table= li++))
+ {
+ if ((table->jtbm_subselect && table->jtbm_subselect->is_jtbm_const_tab))
+ {
+ if (table->table)
+ {
+ free_tmp_table(join->thd, table->table);
+ table->table= NULL;
+ }
+ }
+ else if (table->nested_join && table->sj_subq_pred)
+ {
+ cleanup_empty_jtbm_semi_joins(join, &table->nested_join->join_list);
+ }
+ }
+}
+
+
+/**
+ Choose an optimal strategy to execute an IN/ALL/ANY subquery predicate
+ based on cost.
+
+ @param join_tables the set of tables joined in the subquery
+
+ @notes
+ The method chooses between the materialization and IN=>EXISTS rewrite
+ strategies for the execution of a non-flattened subquery IN predicate.
+ The cost-based decision is made as follows:
+
+ 1. compute materialize_strategy_cost based on the unmodified subquery
+ 2. reoptimize the subquery taking into account the IN-EXISTS predicates
+ 3. compute in_exists_strategy_cost based on the reoptimized plan
+ 4. compare and set the cheaper strategy
+ if (materialize_strategy_cost >= in_exists_strategy_cost)
+ in_strategy = MATERIALIZATION
+ else
+ in_strategy = IN_TO_EXISTS
+ 5. if in_strategy = MATERIALIZATION and it is not possible to initialize it
+ revert to IN_TO_EXISTS
+ 6. if (in_strategy == MATERIALIZATION)
+ revert the subquery plan to the original one before reoptimizing
+ else
+ inject the IN=>EXISTS predicates into the new EXISTS subquery plan
+
+ The implementation itself is a bit more complicated because it takes into
+ account two more factors:
+ - whether the user allowed both strategies through an optimizer_switch, and
+ - if materialization was the cheaper strategy, whether it can be executed
+ or not.
+
+ @retval FALSE success.
+ @retval TRUE error occurred.
+*/
+
+bool JOIN::choose_subquery_plan(table_map join_tables)
+{
+ enum_reopt_result reopt_result= REOPT_NONE;
+ Item_in_subselect *in_subs;
+
+ /*
+ IN/ALL/ANY optimizations are not applicable for so called fake select
+ (this select exists only to filter results of union if it is needed).
+ */
+ if (select_lex == select_lex->master_unit()->fake_select_lex)
+ return 0;
+
+ if (is_in_subquery())
+ {
+ in_subs= (Item_in_subselect*) unit->item;
+ if (in_subs->create_in_to_exists_cond(this))
+ return true;
+ }
+ else
+ return false;
+
+ /* A strategy must be chosen earlier. */
+ DBUG_ASSERT(in_subs->has_strategy());
+ DBUG_ASSERT(in_to_exists_where || in_to_exists_having);
+ DBUG_ASSERT(!in_to_exists_where || in_to_exists_where->fixed);
+ DBUG_ASSERT(!in_to_exists_having || in_to_exists_having->fixed);
+
+ /* The original QEP of the subquery. */
+ Join_plan_state save_qep(table_count);
+
+ /*
+ Compute and compare the costs of materialization and in-exists if both
+ strategies are possible and allowed by the user (checked during the prepare
+ phase.
+ */
+ if (in_subs->test_strategy(SUBS_MATERIALIZATION) &&
+ in_subs->test_strategy(SUBS_IN_TO_EXISTS))
+ {
+ JOIN *outer_join;
+ JOIN *inner_join= this;
+ /* Number of unique value combinations filtered by the IN predicate. */
+ double outer_lookup_keys;
+ /* Cost and row count of the unmodified subquery. */
+ double inner_read_time_1, inner_record_count_1;
+ /* Cost of the subquery with injected IN-EXISTS predicates. */
+ double inner_read_time_2;
+ /* The cost to compute IN via materialization. */
+ double materialize_strategy_cost;
+ /* The cost of the IN->EXISTS strategy. */
+ double in_exists_strategy_cost;
+ double dummy;
+
+ /*
+ A. Estimate the number of rows of the outer table that will be filtered
+ by the IN predicate.
+ */
+ outer_join= unit->outer_select() ? unit->outer_select()->join : NULL;
+ /*
+ Get the cost of the outer join if:
+ (1) It has at least one table, and
+ (2) It has been already optimized (if there is no join_tab, then the
+ outer join has not been optimized yet).
+ */
+ if (outer_join && outer_join->table_count > 0 && // (1)
+ outer_join->join_tab) // (2)
+ {
+ /*
+ TODO:
+ Currently outer_lookup_keys is computed as the number of rows in
+ the partial join including the JOIN_TAB where the IN predicate is
+ pushed to. In the general case this is a gross overestimate because
+ due to caching we are interested only in the number of unique keys.
+ The search key may be formed by columns from much fewer than all
+ tables in the partial join. Example:
+ select * from t1, t2 where t1.c1 = t2.key AND t2.c2 IN (select ...);
+ If the join order: t1, t2, the number of unique lookup keys is ~ to
+ the number of unique values t2.c2 in the partial join t1 join t2.
+ */
+ outer_join->get_partial_cost_and_fanout(in_subs->get_join_tab_idx(),
+ table_map(-1),
+ &dummy,
+ &outer_lookup_keys);
+ }
+ else
+ {
+ /*
+ TODO: outer_join can be NULL for DELETE statements.
+ How to compute its cost?
+ */
+ outer_lookup_keys= 1;
+ }
+
+ /*
+ B. Estimate the cost and number of records of the subquery both
+ unmodified, and with injected IN->EXISTS predicates.
+ */
+ inner_read_time_1= inner_join->best_read;
+ inner_record_count_1= inner_join->record_count;
+
+ if (in_to_exists_where && const_tables != table_count)
+ {
+ /*
+ Re-optimize and cost the subquery taking into account the IN-EXISTS
+ conditions.
+ */
+ reopt_result= reoptimize(in_to_exists_where, join_tables, &save_qep);
+ if (reopt_result == REOPT_ERROR)
+ return TRUE;
+
+ /* Get the cost of the modified IN-EXISTS plan. */
+ inner_read_time_2= inner_join->best_read;
+
+ }
+ else
+ {
+ /* Reoptimization would not produce any better plan. */
+ inner_read_time_2= inner_read_time_1;
+ }
+
+ /*
+ C. Compute execution costs.
+ */
+ /* C.1 Compute the cost of the materialization strategy. */
+ //uint rowlen= get_tmp_table_rec_length(unit->first_select()->item_list);
+ uint rowlen= get_tmp_table_rec_length(ref_pointer_array,
+ select_lex->item_list.elements);
+ /* The cost of writing one row into the temporary table. */
+ double write_cost= get_tmp_table_write_cost(thd, inner_record_count_1,
+ rowlen);
+ /* The cost of a lookup into the unique index of the materialized table. */
+ double lookup_cost= get_tmp_table_lookup_cost(thd, inner_record_count_1,
+ rowlen);
+ /*
+ The cost of executing the subquery and storing its result in an indexed
+ temporary table.
+ */
+ double materialization_cost= inner_read_time_1 +
+ write_cost * inner_record_count_1;
+
+ materialize_strategy_cost= materialization_cost +
+ outer_lookup_keys * lookup_cost;
+
+ /* C.2 Compute the cost of the IN=>EXISTS strategy. */
+ in_exists_strategy_cost= outer_lookup_keys * inner_read_time_2;
+
+ /* C.3 Compare the costs and choose the cheaper strategy. */
+ if (materialize_strategy_cost >= in_exists_strategy_cost)
+ in_subs->set_strategy(SUBS_IN_TO_EXISTS);
+ else
+ in_subs->set_strategy(SUBS_MATERIALIZATION);
+
+ DBUG_PRINT("info",
+ ("mat_strategy_cost: %.2f, mat_cost: %.2f, write_cost: %.2f, lookup_cost: %.2f",
+ materialize_strategy_cost, materialization_cost, write_cost, lookup_cost));
+ DBUG_PRINT("info",
+ ("inx_strategy_cost: %.2f, inner_read_time_2: %.2f",
+ in_exists_strategy_cost, inner_read_time_2));
+ DBUG_PRINT("info",("outer_lookup_keys: %.2f", outer_lookup_keys));
+ }
+
+ /*
+ If (1) materialization is a possible strategy based on semantic analysis
+ during the prepare phase, then if
+ (2) it is more expensive than the IN->EXISTS transformation, and
+ (3) it is not possible to create usable indexes for the materialization
+ strategy,
+ fall back to IN->EXISTS.
+ otherwise
+ use materialization.
+ */
+ if (in_subs->test_strategy(SUBS_MATERIALIZATION) &&
+ in_subs->setup_mat_engine())
+ {
+ /*
+ If materialization was the cheaper or the only user-selected strategy,
+ but it is not possible to execute it due to limitations in the
+ implementation, fall back to IN-TO-EXISTS.
+ */
+ in_subs->set_strategy(SUBS_IN_TO_EXISTS);
+ }
+
+ if (in_subs->test_strategy(SUBS_MATERIALIZATION))
+ {
+ /* Restore the original query plan used for materialization. */
+ if (reopt_result == REOPT_NEW_PLAN)
+ restore_query_plan(&save_qep);
+
+ in_subs->unit->uncacheable&= ~UNCACHEABLE_DEPENDENT_INJECTED;
+ select_lex->uncacheable&= ~UNCACHEABLE_DEPENDENT_INJECTED;
+
+ /*
+ Reset the "LIMIT 1" set in Item_exists_subselect::fix_length_and_dec.
+ TODO:
+ Currently we set the subquery LIMIT to infinity, and this is correct
+ because we forbid at parse time LIMIT inside IN subqueries (see
+ Item_in_subselect::test_limit). However, once we allow this, here
+ we should set the correct limit if given in the query.
+ */
+ in_subs->unit->global_parameters->select_limit= NULL;
+ in_subs->unit->set_limit(unit->global_parameters);
+ /*
+ Set the limit of this JOIN object as well, because normally its being
+ set in the beginning of JOIN::optimize, which was already done.
+ */
+ select_limit= in_subs->unit->select_limit_cnt;
+ }
+ else if (in_subs->test_strategy(SUBS_IN_TO_EXISTS))
+ {
+ if (reopt_result == REOPT_NONE && in_to_exists_where &&
+ const_tables != table_count)
+ {
+ /*
+ The subquery was not reoptimized with the newly injected IN-EXISTS
+ conditions either because the user allowed only the IN-EXISTS strategy,
+ or because materialization was not possible based on semantic analysis.
+ */
+ reopt_result= reoptimize(in_to_exists_where, join_tables, NULL);
+ if (reopt_result == REOPT_ERROR)
+ return TRUE;
+ }
+
+ if (in_subs->inject_in_to_exists_cond(this))
+ return TRUE;
+ /*
+ If the injected predicate is correlated the IN->EXISTS transformation
+ make the subquery dependent.
+ */
+ if ((in_to_exists_where &&
+ in_to_exists_where->used_tables() & OUTER_REF_TABLE_BIT) ||
+ (in_to_exists_having &&
+ in_to_exists_having->used_tables() & OUTER_REF_TABLE_BIT))
+ {
+ in_subs->unit->uncacheable|= UNCACHEABLE_DEPENDENT_INJECTED;
+ select_lex->uncacheable|= UNCACHEABLE_DEPENDENT_INJECTED;
+ }
+ select_limit= 1;
+ }
+ else
+ DBUG_ASSERT(FALSE);
+
+ return FALSE;
+}
+
+
+/**
+ Choose a query plan for a table-less subquery.
+
+ @notes
+
+ @retval FALSE success.
+ @retval TRUE error occurred.
+*/
+
+bool JOIN::choose_tableless_subquery_plan()
+{
+ DBUG_ASSERT(!tables_list || !table_count);
+ if (unit->item)
+ {
+ DBUG_ASSERT(unit->item->type() == Item::SUBSELECT_ITEM);
+ Item_subselect *subs_predicate= unit->item;
+
+ /*
+ If the optimizer determined that his query has an empty result,
+ in most cases the subquery predicate is a known constant value -
+ either of TRUE, FALSE or NULL. The implementation of
+ Item_subselect::no_rows_in_result() determines which one.
+ */
+ if (zero_result_cause)
+ {
+ if (!implicit_grouping)
+ {
+ /*
+ Both group by queries and non-group by queries without aggregate
+ functions produce empty subquery result. There is no need to further
+ rewrite the subquery because it will not be executed at all.
+ */
+ return FALSE;
+ }
+
+ /* @todo
+ A further optimization is possible when a non-group query with
+ MIN/MAX/COUNT is optimized by opt_sum_query. Then, if there are
+ only MIN/MAX functions over an empty result set, the subquery
+ result is a NULL value/row, thus the value of subs_predicate is
+ NULL.
+ */
+ }
+
+ /*
+ For IN subqueries, use IN->EXISTS transfomation, unless the subquery
+ has been converted to a JTBM semi-join. In that case, just leave
+ everything as-is, setup_jtbm_semi_joins() has special handling for cases
+ like this.
+ */
+ if (subs_predicate->is_in_predicate() &&
+ !(subs_predicate->substype() == Item_subselect::IN_SUBS &&
+ ((Item_in_subselect*)subs_predicate)->is_jtbm_merged))
+ {
+ Item_in_subselect *in_subs;
+ in_subs= (Item_in_subselect*) subs_predicate;
+ in_subs->set_strategy(SUBS_IN_TO_EXISTS);
+ if (in_subs->create_in_to_exists_cond(this) ||
+ in_subs->inject_in_to_exists_cond(this))
+ return TRUE;
+ tmp_having= having;
+ }
+ }
+ return FALSE;
+}
diff --git a/sql/opt_subselect.h b/sql/opt_subselect.h
new file mode 100644
index 00000000000..3da94d05521
--- /dev/null
+++ b/sql/opt_subselect.h
@@ -0,0 +1,399 @@
+/*
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/*
+ Semi-join subquery optimization code definitions
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+int check_and_do_in_subquery_rewrites(JOIN *join);
+bool convert_join_subqueries_to_semijoins(JOIN *join);
+int pull_out_semijoin_tables(JOIN *join);
+bool optimize_semijoin_nests(JOIN *join, table_map all_table_map);
+bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list,
+ Item **join_where);
+void cleanup_empty_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list);
+
+// used by Loose_scan_opt
+ulonglong get_bound_sj_equalities(TABLE_LIST *sj_nest,
+ table_map remaining_tables);
+
+/*
+ This is a class for considering possible loose index scan optimizations.
+ It's usage pattern is as follows:
+ best_access_path()
+ {
+ Loose_scan_opt opt;
+
+ opt.init()
+ for each index we can do ref access with
+ {
+ opt.next_ref_key();
+ for each keyuse
+ opt.add_keyuse();
+ opt.check_ref_access();
+ }
+
+ if (some criteria for range scans)
+ opt.check_range_access();
+
+ opt.get_best_option();
+ }
+*/
+
+class Loose_scan_opt
+{
+ /* All methods must check this before doing anything else */
+ bool try_loosescan;
+
+ /*
+ If we consider (oe1, .. oeN) IN (SELECT ie1, .. ieN) then ieK=oeK is
+ called sj-equality. If oeK depends only on preceding tables then such
+ equality is called 'bound'.
+ */
+ ulonglong bound_sj_equalities;
+
+ /* Accumulated properties of ref access we're now considering: */
+ ulonglong handled_sj_equalities;
+ key_part_map loose_scan_keyparts;
+ uint max_loose_keypart;
+ bool part1_conds_met;
+
+ /*
+ Use of quick select is a special case. Some of its properties:
+ */
+ uint quick_uses_applicable_index;
+ uint quick_max_loose_keypart;
+
+ /* Best loose scan method so far */
+ uint best_loose_scan_key;
+ double best_loose_scan_cost;
+ double best_loose_scan_records;
+ KEYUSE *best_loose_scan_start_key;
+
+ uint best_max_loose_keypart;
+
+public:
+ Loose_scan_opt():
+ try_loosescan(FALSE),
+ bound_sj_equalities(0),
+ quick_uses_applicable_index(FALSE)
+ {
+ UNINIT_VAR(quick_max_loose_keypart); /* Protected by quick_uses_applicable_index */
+ /* The following are protected by best_loose_scan_cost!= DBL_MAX */
+ UNINIT_VAR(best_loose_scan_key);
+ UNINIT_VAR(best_loose_scan_records);
+ UNINIT_VAR(best_max_loose_keypart);
+ UNINIT_VAR(best_loose_scan_start_key);
+ }
+
+ void init(JOIN *join, JOIN_TAB *s, table_map remaining_tables)
+ {
+ /*
+ Discover the bound equalities. We need to do this if
+ 1. The next table is an SJ-inner table, and
+ 2. It is the first table from that semijoin, and
+ 3. We're not within a semi-join range (i.e. all semi-joins either have
+ all or none of their tables in join_table_map), except
+ s->emb_sj_nest (which we've just entered, see #2).
+ 4. All non-IN-equality correlation references from this sj-nest are
+ bound
+ 5. But some of the IN-equalities aren't (so this can't be handled by
+ FirstMatch strategy)
+ */
+ best_loose_scan_cost= DBL_MAX;
+ if (!join->emb_sjm_nest && s->emb_sj_nest && // (1)
+ s->emb_sj_nest->sj_in_exprs < 64 &&
+ ((remaining_tables & s->emb_sj_nest->sj_inner_tables) == // (2)
+ s->emb_sj_nest->sj_inner_tables) && // (2)
+ join->cur_sj_inner_tables == 0 && // (3)
+ !(remaining_tables &
+ s->emb_sj_nest->nested_join->sj_corr_tables) && // (4)
+ remaining_tables & s->emb_sj_nest->nested_join->sj_depends_on &&// (5)
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_LOOSE_SCAN))
+ {
+ /* This table is an LooseScan scan candidate */
+ bound_sj_equalities= get_bound_sj_equalities(s->emb_sj_nest,
+ remaining_tables);
+ try_loosescan= TRUE;
+ DBUG_PRINT("info", ("Will try LooseScan scan, bound_map=%llx",
+ (longlong)bound_sj_equalities));
+ }
+ }
+
+ void next_ref_key()
+ {
+ handled_sj_equalities=0;
+ loose_scan_keyparts= 0;
+ max_loose_keypart= 0;
+ part1_conds_met= FALSE;
+ }
+
+ void add_keyuse(table_map remaining_tables, KEYUSE *keyuse)
+ {
+ if (try_loosescan && keyuse->sj_pred_no != UINT_MAX &&
+ (keyuse->table->file->index_flags(keyuse->key, 0, 1 ) & HA_READ_ORDER))
+
+ {
+ if (!(remaining_tables & keyuse->used_tables))
+ {
+ /*
+ This allows to use equality propagation to infer that some
+ sj-equalities are bound.
+ */
+ bound_sj_equalities |= 1ULL << keyuse->sj_pred_no;
+ }
+ else
+ {
+ handled_sj_equalities |= 1ULL << keyuse->sj_pred_no;
+ loose_scan_keyparts |= ((key_part_map)1) << keyuse->keypart;
+ set_if_bigger(max_loose_keypart, keyuse->keypart);
+ }
+ }
+ }
+
+ bool have_a_case() { return MY_TEST(handled_sj_equalities); }
+
+ void check_ref_access_part1(JOIN_TAB *s, uint key, KEYUSE *start_key,
+ table_map found_part)
+ {
+ /*
+ Check if we can use LooseScan semi-join strategy. We can if
+ 1. This is the right table at right location
+ 2. All IN-equalities are either
+ - "bound", ie. the outer_expr part refers to the preceding tables
+ - "handled", ie. covered by the index we're considering
+ 3. Index order allows to enumerate subquery's duplicate groups in
+ order. This happens when the index definition matches this
+ pattern:
+
+ (handled_col|bound_col)* (other_col|bound_col)
+
+ */
+ if (try_loosescan && // (1)
+ (handled_sj_equalities | bound_sj_equalities) == // (2)
+ PREV_BITS(ulonglong, s->emb_sj_nest->sj_in_exprs) && // (2)
+ (PREV_BITS(key_part_map, max_loose_keypart+1) & // (3)
+ (found_part | loose_scan_keyparts)) == // (3)
+ PREV_BITS(key_part_map, max_loose_keypart+1) && // (3)
+ !key_uses_partial_cols(s->table->s, key))
+ {
+ /* Ok, can use the strategy */
+ part1_conds_met= TRUE;
+ if (s->quick && s->quick->index == key &&
+ s->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
+ {
+ quick_uses_applicable_index= TRUE;
+ quick_max_loose_keypart= max_loose_keypart;
+ }
+ DBUG_PRINT("info", ("Can use LooseScan scan"));
+
+ /*
+ Check if this is a special case where there are no usable bound
+ IN-equalities, i.e. we have
+
+ outer_expr IN (SELECT innertbl.key FROM ...)
+
+ and outer_expr cannot be evaluated yet, so it's actually full
+ index scan and not a ref access
+ */
+ if (!(found_part & 1 ) && /* no usable ref access for 1st key part */
+ s->table->covering_keys.is_set(key))
+ {
+ DBUG_PRINT("info", ("Can use full index scan for LooseScan"));
+
+ /* Calculate the cost of complete loose index scan. */
+ double records= rows2double(s->table->file->stats.records);
+
+ /* The cost is entire index scan cost (divided by 2) */
+ double read_time= s->table->file->keyread_time(key, 1,
+ (ha_rows) records);
+
+ /*
+ Now find out how many different keys we will get (for now we
+ ignore the fact that we have "keypart_i=const" restriction for
+ some key components, that may make us think think that loose
+ scan will produce more distinct records than it actually will)
+ */
+ ulong rpc;
+ if ((rpc= s->table->key_info[key].rec_per_key[max_loose_keypart]))
+ records= records / rpc;
+
+ // TODO: previous version also did /2
+ if (read_time < best_loose_scan_cost)
+ {
+ best_loose_scan_key= key;
+ best_loose_scan_cost= read_time;
+ best_loose_scan_records= records;
+ best_max_loose_keypart= max_loose_keypart;
+ best_loose_scan_start_key= start_key;
+ }
+ }
+ }
+ }
+
+ void check_ref_access_part2(uint key, KEYUSE *start_key, double records,
+ double read_time)
+ {
+ if (part1_conds_met && read_time < best_loose_scan_cost)
+ {
+ /* TODO use rec-per-key-based fanout calculations */
+ best_loose_scan_key= key;
+ best_loose_scan_cost= read_time;
+ best_loose_scan_records= records;
+ best_max_loose_keypart= max_loose_keypart;
+ best_loose_scan_start_key= start_key;
+ }
+ }
+
+ void check_range_access(JOIN *join, uint idx, QUICK_SELECT_I *quick)
+ {
+ /* TODO: this the right part restriction: */
+ if (quick_uses_applicable_index && idx == join->const_tables &&
+ quick->read_time < best_loose_scan_cost)
+ {
+ best_loose_scan_key= quick->index;
+ best_loose_scan_cost= quick->read_time;
+ /* this is ok because idx == join->const_tables */
+ best_loose_scan_records= rows2double(quick->records);
+ best_max_loose_keypart= quick_max_loose_keypart;
+ best_loose_scan_start_key= NULL;
+ }
+ }
+
+ void save_to_position(JOIN_TAB *tab, POSITION *pos)
+ {
+ pos->read_time= best_loose_scan_cost;
+ if (best_loose_scan_cost != DBL_MAX)
+ {
+ pos->records_read= best_loose_scan_records;
+ pos->key= best_loose_scan_start_key;
+ pos->cond_selectivity= 1.0;
+ pos->loosescan_picker.loosescan_key= best_loose_scan_key;
+ pos->loosescan_picker.loosescan_parts= best_max_loose_keypart + 1;
+ pos->use_join_buffer= FALSE;
+ pos->table= tab;
+ // todo need ref_depend_map ?
+ DBUG_PRINT("info", ("Produced a LooseScan plan, key %s, %s",
+ tab->table->key_info[best_loose_scan_key].name,
+ best_loose_scan_start_key? "(ref access)":
+ "(range/index access)"));
+ }
+ }
+};
+
+
+void advance_sj_state(JOIN *join, table_map remaining_tables, uint idx,
+ double *current_record_count, double *current_read_time,
+ POSITION *loose_scan_pos);
+void restore_prev_sj_state(const table_map remaining_tables,
+ const JOIN_TAB *tab, uint idx);
+
+void fix_semijoin_strategies_for_picked_join_order(JOIN *join);
+
+bool setup_sj_materialization_part1(JOIN_TAB *sjm_tab);
+bool setup_sj_materialization_part2(JOIN_TAB *sjm_tab);
+
+
+/*
+ Temporary table used by semi-join DuplicateElimination strategy
+
+ This consists of the temptable itself and data needed to put records
+ into it. The table's DDL is as follows:
+
+ CREATE TABLE tmptable (col VARCHAR(n) BINARY, PRIMARY KEY(col));
+
+ where the primary key can be replaced with unique constraint if n exceeds
+ the limit (as it is always done for query execution-time temptables).
+
+ The record value is a concatenation of rowids of tables from the join we're
+ executing. If a join table is on the inner side of the outer join, we
+ assume that its rowid can be NULL and provide means to store this rowid in
+ the tuple.
+*/
+
+class SJ_TMP_TABLE : public Sql_alloc
+{
+public:
+ /*
+ Array of pointers to tables whose rowids compose the temporary table
+ record.
+ */
+ class TAB
+ {
+ public:
+ JOIN_TAB *join_tab;
+ uint rowid_offset;
+ ushort null_byte;
+ uchar null_bit;
+ };
+ TAB *tabs;
+ TAB *tabs_end;
+
+ /*
+ is_degenerate==TRUE means this is a special case where the temptable record
+ has zero length (and presence of a unique key means that the temptable can
+ have either 0 or 1 records).
+ In this case we don't create the physical temptable but instead record
+ its state in SJ_TMP_TABLE::have_degenerate_row.
+ */
+ bool is_degenerate;
+
+ /*
+ When is_degenerate==TRUE: the contents of the table (whether it has the
+ record or not).
+ */
+ bool have_degenerate_row;
+
+ /* table record parameters */
+ uint null_bits;
+ uint null_bytes;
+ uint rowid_len;
+
+ /* The temporary table itself (NULL means not created yet) */
+ TABLE *tmp_table;
+
+ /*
+ These are the members we got from temptable creation code. We'll need
+ them if we'll need to convert table from HEAP to MyISAM/Maria.
+ */
+ TMP_ENGINE_COLUMNDEF *start_recinfo;
+ TMP_ENGINE_COLUMNDEF *recinfo;
+
+ SJ_TMP_TABLE *next_flush_table;
+
+ int sj_weedout_delete_rows();
+ int sj_weedout_check_row(THD *thd);
+ bool create_sj_weedout_tmp_table(THD *thd);
+};
+
+int setup_semijoin_dups_elimination(JOIN *join, ulonglong options,
+ uint no_jbuf_after);
+void destroy_sj_tmp_tables(JOIN *join);
+int clear_sj_tmp_tables(JOIN *join);
+int rewrite_to_index_subquery_engine(JOIN *join);
+
+
+void get_delayed_table_estimates(TABLE *table,
+ ha_rows *out_rows,
+ double *scan_time,
+ double *startup_cost);
+
+enum_nested_loop_state join_tab_execution_startup(JOIN_TAB *tab);
+
diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc
new file mode 100644
index 00000000000..fc3ce09dd8e
--- /dev/null
+++ b/sql/opt_sum.cc
@@ -0,0 +1,1064 @@
+/* Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2008-2011 Monty Program Ab
+
+ 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 */
+
+
+/**
+ @file
+
+ Optimising of MIN(), MAX() and COUNT(*) queries without 'group by' clause
+ by replacing the aggregate expression with a constant.
+
+ Given a table with a compound key on columns (a,b,c), the following
+ types of queries are optimised (assuming the table handler supports
+ the required methods)
+
+ @verbatim
+ SELECT COUNT(*) FROM t1[,t2,t3,...]
+ SELECT MIN(b) FROM t1 WHERE a=const
+ SELECT MAX(c) FROM t1 WHERE a=const AND b=const
+ SELECT MAX(b) FROM t1 WHERE a=const AND b<const
+ SELECT MIN(b) FROM t1 WHERE a=const AND b>const
+ SELECT MIN(b) FROM t1 WHERE a=const AND b BETWEEN const AND const
+ SELECT MAX(b) FROM t1 WHERE a=const AND b BETWEEN const AND const
+ @endverbatim
+
+ Instead of '<' one can use '<=', '>', '>=' and '=' as well.
+ Instead of 'a=const' the condition 'a IS NULL' can be used.
+
+ If all selected fields are replaced then we will also remove all
+ involved tables and return the answer without any join. Thus, the
+ following query will be replaced with a row of two constants:
+ @verbatim
+ SELECT MAX(b), MIN(d) FROM t1,t2
+ WHERE a=const AND b<const AND d>const
+ @endverbatim
+ (assuming a index for column d of table t2 is defined)
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "key.h" // key_cmp_if_same
+#include "sql_select.h"
+
+static bool find_key_for_maxmin(bool max_fl, TABLE_REF *ref, Field* field,
+ COND *cond, uint *range_fl,
+ uint *key_prefix_length);
+static int reckey_in_range(bool max_fl, TABLE_REF *ref, Field* field,
+ COND *cond, uint range_fl, uint prefix_len);
+static int maxmin_in_range(bool max_fl, Field* field, COND *cond);
+
+
+/*
+ Get exact count of rows in all tables
+
+ SYNOPSIS
+ get_exact_records()
+ tables List of tables
+
+ NOTES
+ When this is called, we know all table handlers supports HA_HAS_RECORDS
+ or HA_STATS_RECORDS_IS_EXACT
+
+ RETURN
+ ULONGLONG_MAX Error: Could not calculate number of rows
+ # Multiplication of number of rows in all tables
+*/
+
+static ulonglong get_exact_record_count(List<TABLE_LIST> &tables)
+{
+ ulonglong count= 1;
+ TABLE_LIST *tl;
+ List_iterator<TABLE_LIST> ti(tables);
+ while ((tl= ti++))
+ {
+ ha_rows tmp= tl->table->file->records();
+ if (tmp == HA_POS_ERROR)
+ return ULONGLONG_MAX;
+ count*= tmp;
+ }
+ return count;
+}
+
+
+/**
+ Use index to read MIN(field) value.
+
+ @param table Table object
+ @param ref Reference to the structure where we store the key value
+ @item_field Field used in MIN()
+ @range_fl Whether range endpoint is strict less than
+ @prefix_len Length of common key part for the range
+
+ @retval
+ 0 No errors
+ HA_ERR_... Otherwise
+*/
+
+static int get_index_min_value(TABLE *table, TABLE_REF *ref,
+ Item_field *item_field, uint range_fl,
+ uint prefix_len)
+{
+ int error;
+
+ if (!ref->key_length)
+ error= table->file->ha_index_first(table->record[0]);
+ else
+ {
+ /*
+ Use index to replace MIN/MAX functions with their values
+ according to the following rules:
+
+ 1) Insert the minimum non-null values where the WHERE clause still
+ matches, or
+ 2) a NULL value if there are only NULL values for key_part_k.
+ 3) Fail, producing a row of nulls
+
+ Implementation: Read the smallest value using the search key. If
+ the interval is open, read the next value after the search
+ key. If read fails, and we're looking for a MIN() value for a
+ nullable column, test if there is an exact match for the key.
+ */
+ if (!(range_fl & NEAR_MIN))
+ /*
+ Closed interval: Either The MIN argument is non-nullable, or
+ we have a >= predicate for the MIN argument.
+ */
+ error= table->file->ha_index_read_map(table->record[0],
+ ref->key_buff,
+ make_prev_keypart_map(ref->key_parts),
+ HA_READ_KEY_OR_NEXT);
+ else
+ {
+ /*
+ Open interval: There are two cases:
+ 1) We have only MIN() and the argument column is nullable, or
+ 2) there is a > predicate on it, nullability is irrelevant.
+ We need to scan the next bigger record first.
+ Open interval is not used if the search key involves the last keypart,
+ and it would not work.
+ */
+ DBUG_ASSERT(prefix_len < ref->key_length);
+ error= table->file->ha_index_read_map(table->record[0],
+ ref->key_buff,
+ make_prev_keypart_map(ref->key_parts),
+ HA_READ_AFTER_KEY);
+ /*
+ If the found record is outside the group formed by the search
+ prefix, or there is no such record at all, check if all
+ records in that group have NULL in the MIN argument
+ column. If that is the case return that NULL.
+
+ Check if case 1 from above holds. If it does, we should read
+ the skipped tuple.
+ */
+ if (item_field->field->real_maybe_null() &&
+ ref->key_buff[prefix_len] == 1 &&
+ /*
+ Last keypart (i.e. the argument to MIN) is set to NULL by
+ find_key_for_maxmin only if all other keyparts are bound
+ to constants in a conjunction of equalities. Hence, we
+ can detect this by checking only if the last keypart is
+ NULL.
+ */
+ (error == HA_ERR_KEY_NOT_FOUND ||
+ key_cmp_if_same(table, ref->key_buff, ref->key, prefix_len)))
+ {
+ DBUG_ASSERT(item_field->field->real_maybe_null());
+ error= table->file->ha_index_read_map(table->record[0],
+ ref->key_buff,
+ make_prev_keypart_map(ref->key_parts),
+ HA_READ_KEY_EXACT);
+ }
+ }
+ }
+ return error;
+}
+
+
+/**
+ Use index to read MAX(field) value.
+
+ @param table Table object
+ @param ref Reference to the structure where we store the key value
+ @range_fl Whether range endpoint is strict greater than
+
+ @retval
+ 0 No errors
+ HA_ERR_... Otherwise
+*/
+
+static int get_index_max_value(TABLE *table, TABLE_REF *ref, uint range_fl)
+{
+ return (ref->key_length ?
+ table->file->ha_index_read_map(table->record[0], ref->key_buff,
+ make_prev_keypart_map(ref->key_parts),
+ range_fl & NEAR_MAX ?
+ HA_READ_BEFORE_KEY :
+ HA_READ_PREFIX_LAST_OR_PREV) :
+ table->file->ha_index_last(table->record[0]));
+}
+
+
+
+/**
+ Substitutes constants for some COUNT(), MIN() and MAX() functions.
+
+ @param thd thread handler
+ @param tables list of leaves of join table tree
+ @param all_fields All fields to be returned
+ @param conds WHERE clause
+
+ @note
+ This function is only called for queries with aggregate functions and no
+ GROUP BY part. This means that the result set shall contain a single
+ row only
+
+ @retval
+ 0 no errors
+ @retval
+ 1 if all items were resolved
+ @retval
+ HA_ERR_KEY_NOT_FOUND on impossible conditions
+ @retval
+ HA_ERR_... if a deadlock or a lock wait timeout happens, for example
+ @retval
+ ER_... e.g. ER_SUBQUERY_NO_1_ROW
+*/
+
+int opt_sum_query(THD *thd,
+ List<TABLE_LIST> &tables, List<Item> &all_fields, COND *conds)
+{
+ List_iterator_fast<Item> it(all_fields);
+ List_iterator<TABLE_LIST> ti(tables);
+ TABLE_LIST *tl;
+ int const_result= 1;
+ bool recalc_const_item= 0;
+ ulonglong count= 1;
+ bool is_exact_count= TRUE, maybe_exact_count= TRUE;
+ table_map removed_tables= 0, outer_tables= 0, used_tables= 0;
+ table_map where_tables= 0;
+ Item *item;
+ int error= 0;
+ DBUG_ENTER("opt_sum_query");
+
+ if (conds)
+ where_tables= conds->used_tables();
+
+ /*
+ Analyze outer join dependencies, and, if possible, compute the number
+ of returned rows.
+ */
+ while ((tl= ti++))
+ {
+ TABLE_LIST *embedded;
+ for (embedded= tl ; embedded; embedded= embedded->embedding)
+ {
+ if (embedded->on_expr)
+ break;
+ }
+ if (embedded)
+ /* Don't replace expression on a table that is part of an outer join */
+ {
+ outer_tables|= tl->table->map;
+
+ /*
+ We can't optimise LEFT JOIN in cases where the WHERE condition
+ restricts the table that is used, like in:
+ SELECT MAX(t1.a) FROM t1 LEFT JOIN t2 join-condition
+ WHERE t2.field IS NULL;
+ */
+ if (tl->table->map & where_tables)
+ DBUG_RETURN(0);
+ }
+ else
+ used_tables|= tl->table->map;
+
+ /*
+ If the storage manager of 'tl' gives exact row count as part of
+ statistics (cheap), compute the total number of rows. If there are
+ no outer table dependencies, this count may be used as the real count.
+ Schema tables are filled after this function is invoked, so we can't
+ get row count
+ */
+ if (!(tl->table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT) ||
+ tl->schema_table)
+ {
+ maybe_exact_count&= MY_TEST(!tl->schema_table &&
+ (tl->table->file->ha_table_flags() &
+ HA_HAS_RECORDS));
+ is_exact_count= FALSE;
+ count= 1; // ensure count != 0
+ }
+ else if (tl->is_materialized_derived() ||
+ tl->jtbm_subselect)
+ {
+ /*
+ Can't remove a derived table as it's number of rows is just an
+ estimate.
+ */
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ error= tl->table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
+ if(error)
+ {
+ tl->table->file->print_error(error, MYF(ME_FATALERROR));
+ DBUG_RETURN(error);
+ }
+ count*= tl->table->file->stats.records;
+ }
+ }
+
+ /*
+ Iterate through all items in the SELECT clause and replace
+ COUNT(), MIN() and MAX() with constants (if possible).
+ */
+
+ while ((item= it++))
+ {
+ if (item->type() == Item::SUM_FUNC_ITEM)
+ {
+ Item_sum *item_sum= (((Item_sum*) item));
+ switch (item_sum->sum_func()) {
+ case Item_sum::COUNT_FUNC:
+ /*
+ If the expr in COUNT(expr) can never be null we can change this
+ to the number of rows in the tables if this number is exact and
+ there are no outer joins.
+ */
+ if (!conds && !((Item_sum_count*) item)->get_arg(0)->maybe_null &&
+ !outer_tables && maybe_exact_count)
+ {
+ if (!is_exact_count)
+ {
+ if ((count= get_exact_record_count(tables)) == ULONGLONG_MAX)
+ {
+ /* Error from handler in counting rows. Don't optimize count() */
+ const_result= 0;
+ continue;
+ }
+ is_exact_count= 1; // count is now exact
+ }
+ ((Item_sum_count*) item)->make_const((longlong) count);
+ recalc_const_item= 1;
+ }
+ else
+ const_result= 0;
+ break;
+ case Item_sum::MIN_FUNC:
+ case Item_sum::MAX_FUNC:
+ {
+ int is_max= MY_TEST(item_sum->sum_func() == Item_sum::MAX_FUNC);
+ /*
+ If MIN/MAX(expr) is the first part of a key or if all previous
+ parts of the key is found in the COND, then we can use
+ indexes to find the key.
+ */
+ Item *expr=item_sum->get_arg(0);
+ if (expr->real_item()->type() == Item::FIELD_ITEM)
+ {
+ uchar key_buff[MAX_KEY_LENGTH];
+ TABLE_REF ref;
+ uint range_fl, prefix_len;
+
+ ref.key_buff= key_buff;
+ Item_field *item_field= (Item_field*) (expr->real_item());
+ TABLE *table= item_field->field->table;
+
+ /*
+ Look for a partial key that can be used for optimization.
+ If we succeed, ref.key_length will contain the length of
+ this key, while prefix_len will contain the length of
+ the beginning of this key without field used in MIN/MAX().
+ Type of range for the key part for this field will be
+ returned in range_fl.
+ */
+ if (table->file->inited || (outer_tables & table->map) ||
+ !find_key_for_maxmin(is_max, &ref, item_field->field, conds,
+ &range_fl, &prefix_len))
+ {
+ const_result= 0;
+ break;
+ }
+ if (!(error= table->file->ha_index_init((uint) ref.key, 1)))
+ error= (is_max ?
+ get_index_max_value(table, &ref, range_fl) :
+ get_index_min_value(table, &ref, item_field, range_fl,
+ prefix_len));
+
+ /* Verify that the read tuple indeed matches the search key */
+ if (!error && reckey_in_range(is_max, &ref, item_field->field,
+ conds, range_fl, prefix_len))
+ error= HA_ERR_KEY_NOT_FOUND;
+ table->disable_keyread();
+ table->file->ha_index_end();
+ if (error)
+ {
+ if (error == HA_ERR_KEY_NOT_FOUND || error == HA_ERR_END_OF_FILE)
+ DBUG_RETURN(HA_ERR_KEY_NOT_FOUND); // No rows matching WHERE
+ /* HA_ERR_LOCK_DEADLOCK or some other error */
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ removed_tables|= table->map;
+ }
+ else if (!expr->const_item() || !is_exact_count || conds)
+ {
+ /*
+ The optimization is not applicable in both cases:
+ (a) 'expr' is a non-constant expression. Then we can't
+ replace 'expr' by a constant.
+ (b) 'expr' is a costant. According to ANSI, MIN/MAX must return
+ NULL if the query does not return any rows. Thus, if we are not
+ able to determine if the query returns any rows, we can't apply
+ the optimization and replace MIN/MAX with a constant.
+ (c) there is a WHERE clause. The WHERE conditions may result in
+ an empty result, but the clause cannot be taken into account here.
+ */
+ const_result= 0;
+ break;
+ }
+ item_sum->set_aggregator(item_sum->has_with_distinct() ?
+ Aggregator::DISTINCT_AGGREGATOR :
+ Aggregator::SIMPLE_AGGREGATOR);
+ /*
+ If count == 0 (so is_exact_count == TRUE) and
+ there're no outer joins, set to NULL,
+ otherwise set to the constant value.
+ */
+ if (!count && !outer_tables)
+ {
+ item_sum->aggregator_clear();
+ }
+ else
+ item_sum->reset_and_add();
+ item_sum->make_const();
+ recalc_const_item= 1;
+ break;
+ }
+ default:
+ const_result= 0;
+ break;
+ }
+ }
+ else if (const_result)
+ {
+ if (recalc_const_item)
+ item->update_used_tables();
+ if (!item->const_item())
+ const_result= 0;
+ }
+ }
+
+ if (thd->is_error())
+ DBUG_RETURN(thd->get_stmt_da()->sql_errno());
+
+ /*
+ If we have a where clause, we can only ignore searching in the
+ tables if MIN/MAX optimisation replaced all used tables
+ We do not use replaced values in case of:
+ SELECT MIN(key) FROM table_1, empty_table
+ removed_tables is != 0 if we have used MIN() or MAX().
+ */
+ if (removed_tables && used_tables != removed_tables)
+ const_result= 0; // We didn't remove all tables
+ DBUG_RETURN(const_result);
+}
+
+
+/*
+ Check if both item1 and item2 are strings, and item1 has fewer characters
+ than item2.
+*/
+
+static bool check_item1_shorter_item2(Item *item1, Item *item2)
+{
+ if (item1->cmp_type() == STRING_RESULT &&
+ item2->cmp_type() == STRING_RESULT)
+ {
+ int len1= item1->max_length / item1->collation.collation->mbmaxlen;
+ int len2= item2->max_length / item2->collation.collation->mbmaxlen;
+ return len1 < len2;
+ }
+ return false; /* When the check is not applicable, it means "not bigger" */
+}
+
+
+/**
+ Test if the predicate compares a field with constants.
+
+ @param func_item Predicate item
+ @param[out] args Here we store the field followed by constants
+ @param[out] inv_order Is set to 1 if the predicate is of the form
+ 'const op field'
+
+ @retval
+ 0 func_item is a simple predicate: a field is compared with a constant
+ whose length does not exceed the max length of the field values
+ @retval
+ 1 Otherwise
+*/
+
+bool simple_pred(Item_func *func_item, Item **args, bool *inv_order)
+{
+ Item *item;
+ *inv_order= 0;
+ switch (func_item->argument_count()) {
+ case 0:
+ /* MULT_EQUAL_FUNC */
+ {
+ Item_equal *item_equal= (Item_equal *) func_item;
+ if (!(args[1]= item_equal->get_const()))
+ return 0;
+ Item_equal_fields_iterator it(*item_equal);
+ if (!(item= it++))
+ return 0;
+ args[0]= item->real_item();
+ if (check_item1_shorter_item2(args[0], args[1]))
+ return 0;
+ if (it++)
+ return 0;
+ }
+ break;
+ case 1:
+ /* field IS NULL */
+ item= func_item->arguments()[0]->real_item();
+ if (item->type() != Item::FIELD_ITEM)
+ return 0;
+ args[0]= item;
+ break;
+ case 2:
+ /* 'field op const' or 'const op field' */
+ item= func_item->arguments()[0]->real_item();
+ if (item->type() == Item::FIELD_ITEM)
+ {
+ args[0]= item;
+ item= func_item->arguments()[1]->real_item();
+ if (!item->const_item())
+ return 0;
+ args[1]= item;
+ }
+ else if (item->const_item())
+ {
+ args[1]= item;
+ item= func_item->arguments()[1]->real_item();
+ if (item->type() != Item::FIELD_ITEM)
+ return 0;
+ args[0]= item;
+ *inv_order= 1;
+ }
+ else
+ return 0;
+ if (check_item1_shorter_item2(args[0], args[1]))
+ return 0;
+ break;
+ case 3:
+ /* field BETWEEN const AND const */
+ item= func_item->arguments()[0]->real_item();
+ if (item->type() == Item::FIELD_ITEM)
+ {
+ args[0]= item;
+ for (int i= 1 ; i <= 2; i++)
+ {
+ item= func_item->arguments()[i]->real_item();
+ if (!item->const_item())
+ return 0;
+ args[i]= item;
+ if (check_item1_shorter_item2(args[0], args[1]))
+ return 0;
+ }
+ }
+ else
+ return 0;
+ }
+ return 1;
+}
+
+
+/**
+ Check whether a condition matches a key to get {MAX|MIN}(field):.
+
+ For the index specified by the keyinfo parameter and an index that
+ contains the field as its component (field_part), the function
+ checks whether
+
+ - the condition cond is a conjunction,
+ - all of its conjuncts refer to columns of the same table, and
+ - each conjunct is on one of the following forms:
+ - f_i = const_i or const_i = f_i or f_i IS NULL,
+ where f_i is part of the index
+ - field {<|<=|>=|>|=} const
+ - const {<|<=|>=|>|=} field
+ - field BETWEEN const_1 AND const_2
+
+ As a side-effect, the key value to be used for looking up the MIN/MAX value
+ is actually stored inside the Field object. An interesting feature is that
+ the function will find the most restrictive endpoint by over-eager
+ evaluation of the @c WHERE condition. It continually stores the current
+ endpoint inside the Field object. For a query such as
+
+ @code
+ SELECT MIN(a) FROM t1 WHERE a > 3 AND a > 5;
+ @endcode
+
+ the algorithm will recurse over the conjuction, storing first a 3 in the
+ field. In the next recursive invocation the expression a > 5 is evaluated
+ as 3 > 5 (Due to the dual nature of Field objects as value carriers and
+ field identifiers), which will obviously fail, leading to 5 being stored in
+ the Field object.
+
+ @param[in] max_fl Set to true if we are optimizing MAX(),
+ false means we are optimizing %MIN()
+ @param[in, out] ref Reference to the structure where the function
+ stores the key value
+ @param[in] keyinfo Reference to the key info
+ @param[in] field_part Pointer to the key part for the field
+ @param[in] cond WHERE condition
+ @param[in,out] key_part_used Map of matchings parts. The function will output
+ the set of key parts actually being matched in
+ this set, yet it relies on the caller to
+ initialize the value to zero. This is due
+ to the fact that this value is passed
+ recursively.
+ @param[in,out] range_fl Says whether endpoints use strict greater/less
+ than.
+ @param[out] prefix_len Length of common key part for the range
+ where MAX/MIN is searched for
+
+ @retval
+ false Index can't be used.
+ @retval
+ true We can use the index to get MIN/MAX value
+*/
+
+static bool matching_cond(bool max_fl, TABLE_REF *ref, KEY *keyinfo,
+ KEY_PART_INFO *field_part, COND *cond,
+ key_part_map *key_part_used, uint *range_fl,
+ uint *prefix_len)
+{
+ DBUG_ENTER("matching_cond");
+ if (!cond)
+ DBUG_RETURN(TRUE);
+ Field *field= field_part->field;
+ if (cond->used_tables() & OUTER_REF_TABLE_BIT)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ if (!(cond->used_tables() & field->table->map) &&
+ MY_TEST(cond->used_tables() & ~PSEUDO_TABLE_BITS))
+ {
+ /* Condition doesn't restrict the used table */
+ DBUG_RETURN(!cond->const_item());
+ }
+ else if (cond->is_expensive())
+ DBUG_RETURN(FALSE);
+ if (cond->type() == Item::COND_ITEM)
+ {
+ if (((Item_cond*) cond)->functype() == Item_func::COND_OR_FUNC)
+ DBUG_RETURN(FALSE);
+
+ /* AND */
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item= li++))
+ {
+ if (!matching_cond(max_fl, ref, keyinfo, field_part, item,
+ key_part_used, range_fl, prefix_len))
+ DBUG_RETURN(FALSE);
+ }
+ DBUG_RETURN(TRUE);
+ }
+
+ if (cond->type() != Item::FUNC_ITEM)
+ DBUG_RETURN(FALSE); // Not operator, can't optimize
+
+ bool eq_type= 0; // =, <=> or IS NULL
+ bool is_null_safe_eq= FALSE; // The operator is NULL safe, e.g. <=>
+ bool noeq_type= 0; // < or >
+ bool less_fl= 0; // < or <=
+ bool is_null= 0; // IS NULL
+ bool between= 0; // BETWEEN ... AND ...
+
+ switch (((Item_func*) cond)->functype()) {
+ case Item_func::ISNULL_FUNC:
+ is_null= 1; /* fall through */
+ case Item_func::EQ_FUNC:
+ eq_type= TRUE;
+ break;
+ case Item_func::EQUAL_FUNC:
+ eq_type= is_null_safe_eq= TRUE;
+ break;
+ case Item_func::LT_FUNC:
+ noeq_type= 1; /* fall through */
+ case Item_func::LE_FUNC:
+ less_fl= 1;
+ break;
+ case Item_func::GT_FUNC:
+ noeq_type= 1; /* fall through */
+ case Item_func::GE_FUNC:
+ break;
+ case Item_func::BETWEEN:
+ if (((Item_func_between*) cond)->negated)
+ DBUG_RETURN(FALSE);
+ between= 1;
+ break;
+ case Item_func::MULT_EQUAL_FUNC:
+ eq_type= 1;
+ break;
+ default:
+ DBUG_RETURN(FALSE); // Can't optimize function
+ }
+
+ Item *args[3];
+ bool inv;
+
+ /* Test if this is a comparison of a field and constant */
+ if (!simple_pred((Item_func*) cond, args, &inv))
+ DBUG_RETURN(FALSE);
+
+ if (!is_null_safe_eq && !is_null &&
+ (args[1]->is_null() || (between && args[2]->is_null())))
+ DBUG_RETURN(FALSE);
+
+ if (inv && !eq_type)
+ less_fl= 1-less_fl; // Convert '<' -> '>' (etc)
+
+ /* Check if field is part of the tested partial key */
+ uchar *key_ptr= ref->key_buff;
+ KEY_PART_INFO *part;
+ for (part= keyinfo->key_part; ; key_ptr+= part++->store_length)
+
+ {
+ if (part > field_part)
+ DBUG_RETURN(FALSE); // Field is beyond the tested parts
+ if (part->field->eq(((Item_field*) args[0])->field))
+ break; // Found a part of the key for the field
+ }
+
+ bool is_field_part= part == field_part;
+ if (!(is_field_part || eq_type))
+ DBUG_RETURN(FALSE);
+
+ key_part_map org_key_part_used= *key_part_used;
+ if (eq_type || between || max_fl == less_fl)
+ {
+ uint length= (key_ptr-ref->key_buff)+part->store_length;
+ if (ref->key_length < length)
+ {
+ /* Ultimately ref->key_length will contain the length of the search key */
+ ref->key_length= length;
+ ref->key_parts= (part - keyinfo->key_part) + 1;
+ }
+ if (!*prefix_len && part+1 == field_part)
+ *prefix_len= length;
+ if (is_field_part && eq_type)
+ *prefix_len= ref->key_length;
+
+ *key_part_used|= (key_part_map) 1 << (part - keyinfo->key_part);
+ }
+
+ if (org_key_part_used == *key_part_used &&
+ /*
+ The current search key is not being extended with a new key part. This
+ means that the a condition is added a key part for which there was a
+ previous condition. We can only overwrite such key parts in some special
+ cases, e.g. a > 2 AND a > 1 (here range_fl must be set to something). In
+ all other cases the WHERE condition is always false anyway.
+ */
+ (eq_type || *range_fl == 0))
+ DBUG_RETURN(FALSE);
+
+ if (org_key_part_used != *key_part_used ||
+ (is_field_part &&
+ (between || eq_type || max_fl == less_fl) && !cond->val_int()))
+ {
+ /*
+ It's the first predicate for this part or a predicate of the
+ following form that moves upper/lower bounds for max/min values:
+ - field BETWEEN const AND const
+ - field = const
+ - field {<|<=} const, when searching for MAX
+ - field {>|>=} const, when searching for MIN
+ */
+
+ if (is_null || (is_null_safe_eq && args[1]->is_null()))
+ {
+ /*
+ If we have a non-nullable index, we cannot use it,
+ since set_null will be ignored, and we will compare uninitialized data.
+ */
+ if (!part->field->real_maybe_null())
+ DBUG_RETURN(FALSE);
+ part->field->set_null();
+ *key_ptr= (uchar) 1;
+ }
+ else
+ {
+ /* Update endpoints for MAX/MIN, see function comment. */
+ Item *value= args[between && max_fl ? 2 : 1];
+ value->save_in_field_no_warnings(part->field, 1);
+ if (part->null_bit)
+ *key_ptr++= (uchar) MY_TEST(part->field->is_null());
+ part->field->get_key_image(key_ptr, part->length, Field::itRAW);
+ }
+ if (is_field_part)
+ {
+ if (between || eq_type)
+ *range_fl&= ~(NO_MAX_RANGE | NO_MIN_RANGE);
+ else
+ {
+ *range_fl&= ~(max_fl ? NO_MAX_RANGE : NO_MIN_RANGE);
+ if (noeq_type)
+ *range_fl|= (max_fl ? NEAR_MAX : NEAR_MIN);
+ else
+ *range_fl&= ~(max_fl ? NEAR_MAX : NEAR_MIN);
+ }
+ }
+ }
+ else if (eq_type)
+ {
+ if ((!is_null && !cond->val_int()) ||
+ (is_null && !MY_TEST(part->field->is_null())))
+ DBUG_RETURN(FALSE); // Impossible test
+ }
+ else if (is_field_part)
+ *range_fl&= ~(max_fl ? NO_MIN_RANGE : NO_MAX_RANGE);
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Check whether we can get value for {max|min}(field) by using a key.
+
+ If where-condition is not a conjunction of 0 or more conjuct the
+ function returns false, otherwise it checks whether there is an
+ index including field as its k-th component/part such that:
+
+ -# for each previous component f_i there is one and only one conjunct
+ of the form: f_i= const_i or const_i= f_i or f_i is null
+ -# references to field occur only in conjucts of the form:
+ field {<|<=|>=|>|=} const or const {<|<=|>=|>|=} field or
+ field BETWEEN const1 AND const2
+ -# all references to the columns from the same table as column field
+ occur only in conjucts mentioned above.
+ -# each of k first components the index is not partial, i.e. is not
+ defined on a fixed length proper prefix of the field.
+
+ If such an index exists the function through the ref parameter
+ returns the key value to find max/min for the field using the index,
+ the length of first (k-1) components of the key and flags saying
+ how to apply the key for the search max/min value.
+ (if we have a condition field = const, prefix_len contains the length
+ of the whole search key)
+
+ @param[in] max_fl 0 for MIN(field) / 1 for MAX(field)
+ @param[in,out] ref Reference to the structure we store the key value
+ @param[in] field Field used inside MIN() / MAX()
+ @param[in] cond WHERE condition
+ @param[out] range_fl Bit flags for how to search if key is ok
+ @param[out] prefix_len Length of prefix for the search range
+
+ @note
+ This function may set field->table->key_read to true,
+ which must be reset after index is used!
+ (This can only happen when function returns 1)
+
+ @retval
+ 0 Index can not be used to optimize MIN(field)/MAX(field)
+ @retval
+ 1 Can use key to optimize MIN()/MAX().
+ In this case ref, range_fl and prefix_len are updated
+*/
+
+static bool find_key_for_maxmin(bool max_fl, TABLE_REF *ref,
+ Field* field, COND *cond,
+ uint *range_fl, uint *prefix_len)
+{
+ if (!(field->flags & PART_KEY_FLAG))
+ return FALSE; // Not key field
+
+ DBUG_ENTER("find_key_for_maxmin");
+
+ TABLE *table= field->table;
+ uint idx= 0;
+
+ KEY *keyinfo,*keyinfo_end;
+ for (keyinfo= table->key_info, keyinfo_end= keyinfo+table->s->keys ;
+ keyinfo != keyinfo_end;
+ keyinfo++,idx++)
+ {
+ KEY_PART_INFO *part,*part_end;
+ key_part_map key_part_to_use= 0;
+ /*
+ Perform a check if index is not disabled by ALTER TABLE
+ or IGNORE INDEX.
+ */
+ if (!table->keys_in_use_for_query.is_set(idx))
+ continue;
+ uint jdx= 0;
+ *prefix_len= 0;
+ part_end= keyinfo->key_part+table->actual_n_key_parts(keyinfo);
+ for (part= keyinfo->key_part ;
+ part != part_end ;
+ part++, jdx++, key_part_to_use= (key_part_to_use << 1) | 1)
+ {
+ if (!(table->file->index_flags(idx, jdx, 0) & HA_READ_ORDER))
+ DBUG_RETURN(FALSE);
+
+ /* Check whether the index component is partial */
+ Field *part_field= table->field[part->fieldnr-1];
+ if ((part_field->flags & BLOB_FLAG) ||
+ part->length < part_field->key_length())
+ break;
+
+ if (field->eq(part->field))
+ {
+ ref->key= idx;
+ ref->key_length= 0;
+ ref->key_parts= 0;
+ key_part_map key_part_used= 0;
+ *range_fl= NO_MIN_RANGE | NO_MAX_RANGE;
+ if (matching_cond(max_fl, ref, keyinfo, part, cond,
+ &key_part_used, range_fl, prefix_len) &&
+ !(key_part_to_use & ~key_part_used))
+ {
+ if (!max_fl && key_part_used == key_part_to_use && part->null_bit)
+ {
+ /*
+ The query is on this form:
+
+ SELECT MIN(key_part_k)
+ FROM t1
+ WHERE key_part_1 = const and ... and key_part_k-1 = const
+
+ If key_part_k is nullable, we want to find the first matching row
+ where key_part_k is not null. The key buffer is now {const, ...,
+ NULL}. This will be passed to the handler along with a flag
+ indicating open interval. If a tuple is read that does not match
+ these search criteria, an attempt will be made to read an exact
+ match for the key buffer.
+ */
+ /* Set the first byte of key_part_k to 1, that means NULL */
+ ref->key_buff[ref->key_length]= 1;
+ ref->key_length+= part->store_length;
+ ref->key_parts++;
+ DBUG_ASSERT(ref->key_parts == jdx+1);
+ *range_fl&= ~NO_MIN_RANGE;
+ *range_fl|= NEAR_MIN; // Open interval
+ }
+ /*
+ The following test is false when the key in the key tree is
+ converted (for example to upper case)
+ */
+ if (field->part_of_key.is_set(idx))
+ table->enable_keyread();
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Check whether found key is in range specified by conditions.
+
+ @param[in] max_fl 0 for MIN(field) / 1 for MAX(field)
+ @param[in] ref Reference to the key value and info
+ @param[in] field Field used the MIN/MAX expression
+ @param[in] cond WHERE condition
+ @param[in] range_fl Says whether there is a condition to to be checked
+ @param[in] prefix_len Length of the constant part of the key
+
+ @retval
+ 0 ok
+ @retval
+ 1 WHERE was not true for the found row
+*/
+
+static int reckey_in_range(bool max_fl, TABLE_REF *ref, Field* field,
+ COND *cond, uint range_fl, uint prefix_len)
+{
+ if (key_cmp_if_same(field->table, ref->key_buff, ref->key, prefix_len))
+ return 1;
+ if (!cond || (range_fl & (max_fl ? NO_MIN_RANGE : NO_MAX_RANGE)))
+ return 0;
+ return maxmin_in_range(max_fl, field, cond);
+}
+
+
+/**
+ Check whether {MAX|MIN}(field) is in range specified by conditions.
+
+ @param[in] max_fl 0 for MIN(field) / 1 for MAX(field)
+ @param[in] field Field used the MIN/MAX expression
+ @param[in] cond WHERE condition
+
+ @retval
+ 0 ok
+ @retval
+ 1 WHERE was not true for the found row
+*/
+
+static int maxmin_in_range(bool max_fl, Field* field, COND *cond)
+{
+ /* If AND/OR condition */
+ if (cond->type() == Item::COND_ITEM)
+ {
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item= li++))
+ {
+ if (maxmin_in_range(max_fl, field, item))
+ return 1;
+ }
+ return 0;
+ }
+
+ if (cond->used_tables() != field->table->map)
+ return 0;
+ bool less_fl= 0;
+ switch (((Item_func*) cond)->functype()) {
+ case Item_func::BETWEEN:
+ return cond->val_int() == 0; // Return 1 if WHERE is false
+ case Item_func::LT_FUNC:
+ case Item_func::LE_FUNC:
+ less_fl= 1;
+ case Item_func::GT_FUNC:
+ case Item_func::GE_FUNC:
+ {
+ Item *item= ((Item_func*) cond)->arguments()[1];
+ /* In case of 'const op item' we have to swap the operator */
+ if (!item->const_item())
+ less_fl= 1-less_fl;
+ /*
+ We only have to check the expression if we are using an expression like
+ SELECT MAX(b) FROM t1 WHERE a=const AND b>const
+ not for
+ SELECT MAX(b) FROM t1 WHERE a=const AND b<const
+ */
+ if (max_fl != less_fl)
+ return cond->val_int() == 0; // Return 1 if WHERE is false
+ return 0;
+ }
+ default:
+ break; // Ignore
+ }
+ return 0;
+}
+
diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc
new file mode 100644
index 00000000000..6434c36aaf2
--- /dev/null
+++ b/sql/opt_table_elimination.cc
@@ -0,0 +1,1905 @@
+/*
+ Copyright (c) 2009, 2011, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @file
+
+ @brief
+ Table Elimination Module
+
+ @defgroup Table_Elimination Table Elimination Module
+ @{
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include "my_bit.h"
+#include "sql_select.h"
+
+/*
+ OVERVIEW
+ ========
+
+ This file contains table elimination module. The idea behind table
+ elimination is as follows: suppose we have a left join
+
+ SELECT * FROM t1 LEFT JOIN
+ (t2 JOIN t3) ON t2.primary_key=t1.col AND
+ t2.primary_key=t2.col
+ WHERE ...
+
+ such that
+ * columns of the inner tables are not used anywhere ouside the outer join
+ (not in WHERE, not in GROUP/ORDER BY clause, not in select list etc etc),
+ * inner side of the outer join is guaranteed to produce at most one matching
+ record combination for each record combination of outer tables.
+
+ then the inner side of the outer join can be removed from the query, as it
+ will always produce only one record combination (either real or
+ null-complemented one) and we don't care about what that record combination
+ is.
+
+
+ MODULE INTERFACE
+ ================
+
+ The module has one entry point - the eliminate_tables() function, which one
+ needs to call (once) at some point before join optimization.
+ eliminate_tables() operates over the JOIN structures. Logically, it
+ removes the inner tables of an outer join operation together with the
+ operation itself. Physically, it changes the following members:
+
+ * Eliminated tables are marked as constant and moved to the front of the
+ join order.
+
+ * In addition to this, they are recorded in JOIN::eliminated_tables bitmap.
+
+ * Items that became disused because they were in the ON expression of an
+ eliminated outer join are notified by means of the Item tree walk which
+ calls Item::mark_as_eliminated_processor for every item
+ - At the moment the only Item that cares whether it was eliminated is
+ Item_subselect with its Item_subselect::eliminated flag which is used
+ by EXPLAIN code to check if the subquery should be shown in EXPLAIN.
+
+ Table elimination is redone on every PS re-execution.
+
+
+ TABLE ELIMINATION ALGORITHM FOR ONE OUTER JOIN
+ ==============================================
+
+ As described above, we can remove inner side of an outer join if it is
+
+ 1. not referred to from any other parts of the query
+ 2. always produces one matching record combination.
+
+ We check #1 by doing a recursive descent down the join->join_list while
+ maintaining a union of used_tables() attribute of all Item expressions in
+ other parts of the query. When we encounter an outer join, we check if the
+ bitmap of tables on its inner side has intersection with tables that are used
+ elsewhere. No intersection means that inner side of the outer join could
+ potentially be eliminated.
+
+ In order to check #2, one needs to prove that inner side of an outer join
+ is functionally dependent on the outside. The proof is constructed from
+ functional dependencies of intermediate objects:
+
+ - Inner side of outer join is functionally dependent when each of its tables
+ are functionally dependent. (We assume a table is functionally dependent
+ when its dependencies allow to uniquely identify one table record, or no
+ records).
+
+ - Table is functionally dependent when it has got a unique key whose columns
+ are functionally dependent.
+
+ - A column is functionally dependent when we could locate an AND-part of a
+ certain ON clause in form
+
+ tblX.columnY= expr
+
+ where expr is functionally depdendent. expr is functionally dependent when
+ all columns that it refers to are functionally dependent.
+
+ These relationships are modeled as a bipartite directed graph that has
+ dependencies as edges and two kinds of nodes:
+
+ Value nodes:
+ - Table column values (each is a value of tblX.columnY)
+ - Table values (each node represents a table inside the join nest we're
+ trying to eliminate).
+ A value has one attribute, it is either bound (i.e. functionally dependent)
+ or not.
+
+ Module nodes:
+ - Modules representing tblX.colY=expr equalities. Equality module has
+ = incoming edges from columns used in expr
+ = outgoing edge to tblX.colY column.
+ - Nodes representing unique keys. Unique key has
+ = incoming edges from key component value modules
+ = outgoing edge to key's table module
+ - Inner side of outer join module. Outer join module has
+ = incoming edges from table value modules
+ = No outgoing edges. Once we reach it, we know we can eliminate the
+ outer join.
+ A module may depend on multiple values, and hence its primary attribute is
+ the number of its arguments that are not bound.
+
+ The algorithm starts with equality nodes that don't have any incoming edges
+ (their expressions are either constant or depend only on tables that are
+ outside of the outer join in question) and performns a breadth-first
+ traversal. If we reach the outer join nest node, it means outer join is
+ functionally dependent and can be eliminated. Otherwise it cannot be
+ eliminated.
+
+ HANDLING MULTIPLE NESTED OUTER JOINS
+ ====================================
+
+ Outer joins that are not nested one within another are eliminated
+ independently. For nested outer joins we have the following considerations:
+
+ 1. ON expressions from children outer joins must be taken into account
+
+ Consider this example:
+
+ SELECT t0.*
+ FROM
+ t0
+ LEFT JOIN
+ (t1 LEFT JOIN t2 ON t2.primary_key=t1.col1)
+ ON
+ t1.primary_key=t0.col AND t2.col1=t1.col2
+
+ Here we cannot eliminate the "... LEFT JOIN t2 ON ..." part alone because the
+ ON clause of top level outer join has references to table t2.
+ We can eliminate the entire "... LEFT JOIN (t1 LEFT JOIN t2) ON .." part,
+ but in order to do that, we must look at both ON expressions.
+
+ 2. ON expressions of parent outer joins are useless.
+ Consider an example:
+
+ SELECT t0.*
+ FROM
+ t0
+ LEFT JOIN
+ (t1 LEFT JOIN t2 ON some_expr)
+ ON
+ t2.primary_key=t1.col -- (*)
+
+ Here the uppermost ON expression has a clause that gives us functional
+ dependency of table t2 on t1 and hence could be used to eliminate the
+ "... LEFT JOIN t2 ON..." part.
+ However, we would not actually encounter this situation, because before the
+ table elimination we run simplify_joins(), which, among other things, upon
+ seeing a functional dependency condition like (*) will convert the outer join
+ of
+
+ "... LEFT JOIN t2 ON ..."
+
+ into inner join and thus make table elimination not to consider eliminating
+ table t2.
+*/
+
+class Dep_value;
+ class Dep_value_field;
+ class Dep_value_table;
+
+
+class Dep_module;
+ class Dep_module_expr;
+ class Dep_module_goal;
+ class Dep_module_key;
+
+class Dep_analysis_context;
+
+
+/*
+ A value, something that can be bound or not bound. One can also iterate over
+ unbound modules that depend on this value
+*/
+
+class Dep_value : public Sql_alloc
+{
+public:
+ Dep_value(): bound(FALSE) {}
+ virtual ~Dep_value(){} /* purecov: inspected */ /* stop compiler warnings */
+
+ bool is_bound() { return bound; }
+ void make_bound() { bound= TRUE; }
+
+ /* Iteration over unbound modules that depend on this value */
+ typedef char *Iterator;
+ virtual Iterator init_unbound_modules_iter(char *buf)=0;
+ virtual Dep_module* get_next_unbound_module(Dep_analysis_context *dac,
+ Iterator iter) = 0;
+ static const size_t iterator_size;
+protected:
+ bool bound;
+};
+
+
+/*
+ A table field value. There is exactly only one such object for any tblX.fieldY
+ - the field depends on its table and equalities
+ - expressions that use the field are its dependencies
+*/
+
+class Dep_value_field : public Dep_value
+{
+public:
+ Dep_value_field(Dep_value_table *table_arg, Field *field_arg) :
+ table(table_arg), field(field_arg)
+ {}
+
+ Dep_value_table *table; /* Table this field is from */
+ Field *field; /* Field this object is representing */
+
+ /* Iteration over unbound modules that are our dependencies */
+ Iterator init_unbound_modules_iter(char *buf);
+ Dep_module* get_next_unbound_module(Dep_analysis_context *dac,
+ Iterator iter);
+
+ void make_unbound_modules_iter_skip_keys(Iterator iter);
+
+ static const size_t iterator_size;
+private:
+ /*
+ Field_deps that belong to one table form a linked list, ordered by
+ field_index
+ */
+ Dep_value_field *next_table_field;
+
+ /*
+ Offset to bits in Dep_analysis_context::expr_deps (see comment to that
+ member for semantics of the bits).
+ */
+ uint bitmap_offset;
+
+ class Module_iter
+ {
+ public:
+ /* if not null, return this and advance */
+ Dep_module_key *key_dep;
+ /* Otherwise, this and advance */
+ uint equality_no;
+ };
+ friend class Dep_analysis_context;
+ friend class Field_dependency_recorder;
+ friend class Dep_value_table;
+};
+
+const size_t Dep_value_field::iterator_size=
+ ALIGN_SIZE(sizeof(Dep_value_field::Module_iter));
+
+
+/*
+ A table value. There is one Dep_value_table object for every table that can
+ potentially be eliminated.
+
+ Table becomes bound as soon as some of its unique keys becomes bound
+ Once the table is bound:
+ - all of its fields are bound
+ - its embedding outer join has one less unknown argument
+*/
+
+class Dep_value_table : public Dep_value
+{
+public:
+ Dep_value_table(TABLE *table_arg) :
+ table(table_arg), fields(NULL), keys(NULL)
+ {}
+ TABLE *table; /* Table this object is representing */
+ /* Ordered list of fields that belong to this table */
+ Dep_value_field *fields;
+ Dep_module_key *keys; /* Ordered list of Unique keys in this table */
+
+ /* Iteration over unbound modules that are our dependencies */
+ Iterator init_unbound_modules_iter(char *buf);
+ Dep_module* get_next_unbound_module(Dep_analysis_context *dac,
+ Iterator iter);
+ static const size_t iterator_size;
+private:
+ class Module_iter
+ {
+ public:
+ /* Space for field iterator */
+ char buf[Dep_value_field::iterator_size];
+ /* !NULL <=> iterating over depdenent modules of this field */
+ Dep_value_field *field_dep;
+ bool returned_goal;
+ };
+};
+
+
+const size_t Dep_value_table::iterator_size=
+ ALIGN_SIZE(sizeof(Dep_value_table::Module_iter));
+
+const size_t Dep_value::iterator_size=
+ MY_MAX(Dep_value_table::iterator_size, Dep_value_field::iterator_size);
+
+
+/*
+ A 'module'. Module has unsatisfied dependencies, number of whose is stored in
+ unbound_args. Modules also can be linked together in a list.
+*/
+
+class Dep_module : public Sql_alloc
+{
+public:
+ virtual ~Dep_module(){} /* purecov: inspected */ /* stop compiler warnings */
+
+ /* Mark as bound. Currently is non-virtual and does nothing */
+ void make_bound() {};
+
+ /*
+ The final module will return TRUE here. When we see that TRUE was returned,
+ that will mean that functional dependency check succeeded.
+ */
+ virtual bool is_final () { return FALSE; }
+
+ /*
+ Increment number of bound arguments. this is expected to change
+ is_applicable() from false to true after sufficient set of arguments is
+ bound.
+ */
+ void touch() { unbound_args--; }
+ bool is_applicable() { return !MY_TEST(unbound_args); }
+
+ /* Iteration over values that */
+ typedef char *Iterator;
+ virtual Iterator init_unbound_values_iter(char *buf)=0;
+ virtual Dep_value* get_next_unbound_value(Dep_analysis_context *dac,
+ Iterator iter)=0;
+ static const size_t iterator_size;
+protected:
+ uint unbound_args;
+
+ Dep_module() : unbound_args(0) {}
+ /* to bump unbound_args when constructing depedendencies */
+ friend class Field_dependency_recorder;
+ friend class Dep_analysis_context;
+};
+
+
+/*
+ This represents either
+ - "tbl.column= expr" equality dependency, i.e. tbl.column depends on fields
+ used in the expression, or
+ - tbl1.col1=tbl2.col2=... multi-equality.
+*/
+
+class Dep_module_expr : public Dep_module
+{
+public:
+ Dep_value_field *field;
+ Item *expr;
+
+ List<Dep_value_field> *mult_equal_fields;
+ /* Used during condition analysis only, similar to KEYUSE::level */
+ uint level;
+
+ Iterator init_unbound_values_iter(char *buf);
+ Dep_value* get_next_unbound_value(Dep_analysis_context *dac, Iterator iter);
+ static const size_t iterator_size;
+private:
+ class Value_iter
+ {
+ public:
+ Dep_value_field *field;
+ List_iterator<Dep_value_field> it;
+ };
+};
+
+const size_t Dep_module_expr::iterator_size=
+ ALIGN_SIZE(sizeof(Dep_module_expr::Value_iter));
+
+
+/*
+ A Unique key module
+ - Unique key has all of its components as arguments
+ - Once unique key is bound, its table value is known
+*/
+
+class Dep_module_key: public Dep_module
+{
+public:
+ Dep_module_key(Dep_value_table *table_arg, uint keyno_arg, uint n_parts_arg) :
+ table(table_arg), keyno(keyno_arg), next_table_key(NULL)
+ {
+ unbound_args= n_parts_arg;
+ }
+ Dep_value_table *table; /* Table this key is from */
+ uint keyno; /* The index we're representing */
+ /* Unique keys form a linked list, ordered by keyno */
+ Dep_module_key *next_table_key;
+
+ Iterator init_unbound_values_iter(char *buf);
+ Dep_value* get_next_unbound_value(Dep_analysis_context *dac, Iterator iter);
+ static const size_t iterator_size;
+private:
+ class Value_iter
+ {
+ public:
+ Dep_value_table *table;
+ };
+};
+
+const size_t Dep_module_key::iterator_size=
+ ALIGN_SIZE(sizeof(Dep_module_key::Value_iter));
+
+const size_t Dep_module::iterator_size=
+ MY_MAX(Dep_module_expr::iterator_size, Dep_module_key::iterator_size);
+
+
+/*
+ A module that represents outer join that we're trying to eliminate. If we
+ manage to declare this module to be bound, then outer join can be eliminated.
+*/
+
+class Dep_module_goal: public Dep_module
+{
+public:
+ Dep_module_goal(uint n_children)
+ {
+ unbound_args= n_children;
+ }
+ bool is_final() { return TRUE; }
+ /*
+ This is the goal module, so the running wave algorithm should terminate
+ once it sees that this module is applicable and should never try to apply
+ it, hence no use for unbound value iterator implementation.
+ */
+ Iterator init_unbound_values_iter(char *buf)
+ {
+ DBUG_ASSERT(0);
+ return NULL;
+ }
+ Dep_value* get_next_unbound_value(Dep_analysis_context *dac, Iterator iter)
+ {
+ DBUG_ASSERT(0);
+ return NULL;
+ }
+};
+
+
+/*
+ Functional dependency analyzer context
+*/
+class Dep_analysis_context
+{
+public:
+ bool setup_equality_modules_deps(List<Dep_module> *bound_modules);
+ bool run_wave(List<Dep_module> *new_bound_modules);
+
+ /* Tables that we're looking at eliminating */
+ table_map usable_tables;
+
+ /* Array of equality dependencies */
+ Dep_module_expr *equality_mods;
+ uint n_equality_mods; /* Number of elements in the array */
+ uint n_equality_mods_alloced;
+
+ /* tablenr -> Dep_value_table* mapping. */
+ Dep_value_table *table_deps[MAX_KEY];
+
+ /* Element for the outer join we're attempting to eliminate */
+ Dep_module_goal *outer_join_dep;
+
+ /*
+ Bitmap of how expressions depend on bits. Given a Dep_value_field object,
+ one can check bitmap_is_set(expr_deps, field_val->bitmap_offset + expr_no)
+ to see if expression equality_mods[expr_no] depends on the given field.
+ */
+ MY_BITMAP expr_deps;
+
+ Dep_value_table *create_table_value(TABLE *table);
+ Dep_value_field *get_field_value(Field *field);
+
+#ifndef DBUG_OFF
+ void dbug_print_deps();
+#endif
+};
+
+
+void eliminate_tables(JOIN *join);
+
+static bool
+eliminate_tables_for_list(JOIN *join,
+ List<TABLE_LIST> *join_list,
+ table_map tables_in_list,
+ Item *on_expr,
+ table_map tables_used_elsewhere);
+static
+bool check_func_dependency(JOIN *join,
+ table_map dep_tables,
+ List_iterator<TABLE_LIST> *it,
+ TABLE_LIST *oj_tbl,
+ Item* cond);
+static
+void build_eq_mods_for_cond(Dep_analysis_context *dac,
+ Dep_module_expr **eq_mod, uint *and_level,
+ Item *cond);
+static
+void check_equality(Dep_analysis_context *dac, Dep_module_expr **eq_mod,
+ uint and_level, Item_func *cond, Item *left, Item *right);
+static
+Dep_module_expr *merge_eq_mods(Dep_module_expr *start,
+ Dep_module_expr *new_fields,
+ Dep_module_expr *end, uint and_level);
+static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl);
+static
+void add_module_expr(Dep_analysis_context *dac, Dep_module_expr **eq_mod,
+ uint and_level, Dep_value_field *field_val, Item *right,
+ List<Dep_value_field>* mult_equal_fields);
+
+
+/*****************************************************************************/
+
+/*
+ Perform table elimination
+
+ SYNOPSIS
+ eliminate_tables()
+ join Join to work on
+
+ DESCRIPTION
+ This is the entry point for table elimination. Grep for MODULE INTERFACE
+ section in this file for calling convention.
+
+ The idea behind table elimination is that if we have an outer join:
+
+ SELECT * FROM t1 LEFT JOIN
+ (t2 JOIN t3) ON t2.primary_key=t1.col AND
+ t3.primary_key=t2.col
+ such that
+
+ 1. columns of the inner tables are not used anywhere ouside the outer
+ join (not in WHERE, not in GROUP/ORDER BY clause, not in select list
+ etc etc), and
+ 2. inner side of the outer join is guaranteed to produce at most one
+ record combination for each record combination of outer tables.
+
+ then the inner side of the outer join can be removed from the query.
+ This is because it will always produce one matching record (either a
+ real match or a NULL-complemented record combination), and since there
+ are no references to columns of the inner tables anywhere, it doesn't
+ matter which record combination it was.
+
+ This function primary handles checking #1. It collects a bitmap of
+ tables that are not used in select list/GROUP BY/ORDER BY/HAVING/etc and
+ thus can possibly be eliminated.
+
+ After this, if #1 is met, the function calls eliminate_tables_for_list()
+ that checks #2.
+
+ SIDE EFFECTS
+ See the OVERVIEW section at the top of this file.
+
+*/
+
+void eliminate_tables(JOIN *join)
+{
+ THD* thd= join->thd;
+ Item *item;
+ table_map used_tables;
+ DBUG_ENTER("eliminate_tables");
+
+ DBUG_ASSERT(join->eliminated_tables == 0);
+
+ /* If there are no outer joins, we have nothing to eliminate: */
+ if (!join->outer_join)
+ DBUG_VOID_RETURN;
+
+ if (!optimizer_flag(thd, OPTIMIZER_SWITCH_TABLE_ELIMINATION))
+ DBUG_VOID_RETURN; /* purecov: inspected */
+
+ /* Find the tables that are referred to from WHERE/HAVING */
+ used_tables= (join->conds? join->conds->used_tables() : 0) |
+ (join->having? join->having->used_tables() : 0);
+
+ /*
+ For "INSERT ... SELECT ... ON DUPLICATE KEY UPDATE column = val"
+ we should also take into account tables mentioned in "val".
+ */
+ if (join->thd->lex->sql_command == SQLCOM_INSERT_SELECT &&
+ join->select_lex == &thd->lex->select_lex)
+ {
+ List_iterator<Item> val_it(thd->lex->value_list);
+ while ((item= val_it++))
+ {
+ DBUG_ASSERT(item->fixed);
+ used_tables |= item->used_tables();
+ }
+ }
+
+ /* Add tables referred to from the select list */
+ List_iterator<Item> it(join->fields_list);
+ while ((item= it++))
+ used_tables |= item->used_tables();
+
+ /* Add tables referred to from ORDER BY and GROUP BY lists */
+ ORDER *all_lists[]= { join->order, join->group_list};
+ for (int i=0; i < 2; i++)
+ {
+ for (ORDER *cur_list= all_lists[i]; cur_list; cur_list= cur_list->next)
+ used_tables |= (*(cur_list->item))->used_tables();
+ }
+
+ if (join->select_lex == &thd->lex->select_lex)
+ {
+
+ /* Multi-table UPDATE: don't eliminate tables referred from SET statement */
+ if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI)
+ {
+ /* Multi-table UPDATE and DELETE: don't eliminate the tables we modify: */
+ used_tables |= thd->table_map_for_update;
+ List_iterator<Item> it2(thd->lex->value_list);
+ while ((item= it2++))
+ used_tables |= item->used_tables();
+ }
+
+ if (thd->lex->sql_command == SQLCOM_DELETE_MULTI)
+ {
+ TABLE_LIST *tbl;
+ for (tbl= (TABLE_LIST*)thd->lex->auxiliary_table_list.first;
+ tbl; tbl= tbl->next_local)
+ {
+ used_tables |= tbl->table->map;
+ }
+ }
+ }
+
+ table_map all_tables= join->all_tables_map();
+ if (all_tables & ~used_tables)
+ {
+ /* There are some tables that we probably could eliminate. Try it. */
+ eliminate_tables_for_list(join, join->join_list, all_tables, NULL,
+ used_tables);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Perform table elimination in a given join list
+
+ SYNOPSIS
+ eliminate_tables_for_list()
+ join The join we're working on
+ join_list Join list to eliminate tables from (and if
+ on_expr !=NULL, then try eliminating join_list
+ itself)
+ list_tables Bitmap of tables embedded in the join_list.
+ on_expr ON expression, if the join list is the inner side
+ of an outer join.
+ NULL means it's not an outer join but rather a
+ top-level join list.
+ tables_used_elsewhere Bitmap of tables that are referred to from
+ somewhere outside of the join list (e.g.
+ select list, HAVING, other ON expressions, etc).
+
+ DESCRIPTION
+ Perform table elimination in a given join list:
+ - First, walk through join list members and try doing table elimination for
+ them.
+ - Then, if the join list itself is an inner side of outer join
+ (on_expr!=NULL), then try to eliminate the entire join list.
+
+ See "HANDLING MULTIPLE NESTED OUTER JOINS" section at the top of this file
+ for more detailed description and justification.
+
+ RETURN
+ TRUE The entire join list eliminated
+ FALSE Join list wasn't eliminated (but some of its child outer joins
+ possibly were)
+*/
+
+static bool
+eliminate_tables_for_list(JOIN *join, List<TABLE_LIST> *join_list,
+ table_map list_tables, Item *on_expr,
+ table_map tables_used_elsewhere)
+{
+ TABLE_LIST *tbl;
+ List_iterator<TABLE_LIST> it(*join_list);
+ table_map tables_used_on_left= 0;
+ bool all_eliminated= TRUE;
+
+ while ((tbl= it++))
+ {
+ if (tbl->on_expr)
+ {
+ table_map outside_used_tables= tables_used_elsewhere |
+ tables_used_on_left;
+ if (on_expr)
+ outside_used_tables |= on_expr->used_tables();
+ if (tbl->nested_join)
+ {
+ /* This is "... LEFT JOIN (join_nest) ON cond" */
+ if (eliminate_tables_for_list(join,
+ &tbl->nested_join->join_list,
+ tbl->nested_join->used_tables,
+ tbl->on_expr,
+ outside_used_tables))
+ {
+ mark_as_eliminated(join, tbl);
+ }
+ else
+ all_eliminated= FALSE;
+ }
+ else
+ {
+ /* This is "... LEFT JOIN tbl ON cond" */
+ if (!(tbl->table->map & outside_used_tables) &&
+ check_func_dependency(join, tbl->table->map, NULL, tbl,
+ tbl->on_expr))
+ {
+ mark_as_eliminated(join, tbl);
+ }
+ else
+ all_eliminated= FALSE;
+ }
+ tables_used_on_left |= tbl->on_expr->used_tables();
+ }
+ else
+ {
+ DBUG_ASSERT(!tbl->nested_join || tbl->sj_on_expr);
+ //psergey-todo: is the following really correct or we'll need to descend
+ //down all ON clauses: ?
+ if (tbl->sj_on_expr)
+ tables_used_on_left |= tbl->sj_on_expr->used_tables();
+ }
+ }
+
+ /* Try eliminating the nest we're called for */
+ if (all_eliminated && on_expr && !(list_tables & tables_used_elsewhere))
+ {
+ it.rewind();
+ return check_func_dependency(join, list_tables & ~join->eliminated_tables,
+ &it, NULL, on_expr);
+ }
+ return FALSE; /* not eliminated */
+}
+
+
+/*
+ Check if given condition makes given set of tables functionally dependent
+
+ SYNOPSIS
+ check_func_dependency()
+ join Join we're procesing
+ dep_tables Tables that we check to be functionally dependent (on
+ everything else)
+ it Iterator that enumerates these tables, or NULL if we're
+ checking one single table and it is specified in oj_tbl
+ parameter.
+ oj_tbl NULL, or one single table that we're checking
+ cond Condition to use to prove functional dependency
+
+ DESCRIPTION
+ Check if we can use given condition to infer that the set of given tables
+ is functionally dependent on everything else.
+
+ RETURN
+ TRUE - Yes, functionally dependent
+ FALSE - No, or error
+*/
+
+static
+bool check_func_dependency(JOIN *join,
+ table_map dep_tables,
+ List_iterator<TABLE_LIST> *it,
+ TABLE_LIST *oj_tbl,
+ Item* cond)
+{
+ Dep_analysis_context dac;
+
+ /*
+ Pre-alloc some Dep_module_expr structures. We don't need this to be
+ guaranteed upper bound.
+ */
+ dac.n_equality_mods_alloced=
+ join->thd->lex->current_select->max_equal_elems +
+ (join->thd->lex->current_select->cond_count+1)*2 +
+ join->thd->lex->current_select->between_count;
+
+ bzero(dac.table_deps, sizeof(dac.table_deps));
+ if (!(dac.equality_mods= new Dep_module_expr[dac.n_equality_mods_alloced]))
+ return FALSE; /* purecov: inspected */
+
+ Dep_module_expr* last_eq_mod= dac.equality_mods;
+
+ /* Create Dep_value_table objects for all tables we're trying to eliminate */
+ if (oj_tbl)
+ {
+ if (!dac.create_table_value(oj_tbl->table))
+ return FALSE; /* purecov: inspected */
+ }
+ else
+ {
+ TABLE_LIST *tbl;
+ while ((tbl= (*it)++))
+ {
+ if (tbl->table && (tbl->table->map & dep_tables))
+ {
+ if (!dac.create_table_value(tbl->table))
+ return FALSE; /* purecov: inspected */
+ }
+ }
+ }
+ dac.usable_tables= dep_tables;
+
+ /*
+ Analyze the the ON expression and create Dep_module_expr objects and
+ Dep_value_field objects for the used fields.
+ */
+ uint and_level=0;
+ build_eq_mods_for_cond(&dac, &last_eq_mod, &and_level, cond);
+ if (!(dac.n_equality_mods= last_eq_mod - dac.equality_mods))
+ return FALSE; /* No useful conditions */
+
+ List<Dep_module> bound_modules;
+
+ if (!(dac.outer_join_dep= new Dep_module_goal(my_count_bits(dep_tables))) ||
+ dac.setup_equality_modules_deps(&bound_modules))
+ {
+ return FALSE; /* OOM, default to non-dependent */ /* purecov: inspected */
+ }
+
+ DBUG_EXECUTE("test", dac.dbug_print_deps(); );
+
+ return dac.run_wave(&bound_modules);
+}
+
+
+/*
+ Running wave functional dependency check algorithm
+
+ SYNOPSIS
+ Dep_analysis_context::run_wave()
+ new_bound_modules List of bound modules to start the running wave from.
+ The list is destroyed during execution
+
+ DESCRIPTION
+ This function uses running wave algorithm to check if the join nest is
+ functionally-dependent.
+ We start from provided list of bound modules, and then run the wave across
+ dependency edges, trying the reach the Dep_module_goal module. If we manage
+ to reach it, then the join nest is functionally-dependent, otherwise it is
+ not.
+
+ RETURN
+ TRUE Yes, functionally dependent
+ FALSE No.
+*/
+
+bool Dep_analysis_context::run_wave(List<Dep_module> *new_bound_modules)
+{
+ List<Dep_value> new_bound_values;
+
+ Dep_value *value;
+ Dep_module *module;
+
+ while (!new_bound_modules->is_empty())
+ {
+ /*
+ The "wave" is in new_bound_modules list. Iterate over values that can be
+ reached from these modules but are not yet bound, and collect the next
+ wave generation in new_bound_values list.
+ */
+ List_iterator<Dep_module> modules_it(*new_bound_modules);
+ while ((module= modules_it++))
+ {
+ char iter_buf[Dep_module::iterator_size + ALIGN_MAX_UNIT];
+ Dep_module::Iterator iter;
+ iter= module->init_unbound_values_iter(iter_buf);
+ while ((value= module->get_next_unbound_value(this, iter)))
+ {
+ if (!value->is_bound())
+ {
+ value->make_bound();
+ new_bound_values.push_back(value);
+ }
+ }
+ }
+ new_bound_modules->empty();
+
+ /*
+ Now walk over list of values we've just found to be bound and check which
+ unbound modules can be reached from them. If there are some modules that
+ became bound, collect them in new_bound_modules list.
+ */
+ List_iterator<Dep_value> value_it(new_bound_values);
+ while ((value= value_it++))
+ {
+ char iter_buf[Dep_value::iterator_size + ALIGN_MAX_UNIT];
+ Dep_value::Iterator iter;
+ iter= value->init_unbound_modules_iter(iter_buf);
+ while ((module= value->get_next_unbound_module(this, iter)))
+ {
+ module->touch();
+ if (!module->is_applicable())
+ continue;
+ if (module->is_final())
+ return TRUE; /* Functionally dependent */
+ module->make_bound();
+ new_bound_modules->push_back(module);
+ }
+ }
+ new_bound_values.empty();
+ }
+ return FALSE;
+}
+
+
+/*
+ This is used to analyze expressions in "tbl.col=expr" dependencies so
+ that we can figure out which fields the expression depends on.
+*/
+
+class Field_dependency_recorder : public Field_enumerator
+{
+public:
+ Field_dependency_recorder(Dep_analysis_context *ctx_arg): ctx(ctx_arg)
+ {}
+
+ void visit_field(Item_field *item)
+ {
+ Field *field= item->field;
+ Dep_value_table *tbl_dep;
+ if ((tbl_dep= ctx->table_deps[field->table->tablenr]))
+ {
+ for (Dep_value_field *field_dep= tbl_dep->fields; field_dep;
+ field_dep= field_dep->next_table_field)
+ {
+ if (field->field_index == field_dep->field->field_index)
+ {
+ uint offs= field_dep->bitmap_offset + expr_offset;
+ if (!bitmap_is_set(&ctx->expr_deps, offs))
+ ctx->equality_mods[expr_offset].unbound_args++;
+ bitmap_set_bit(&ctx->expr_deps, offs);
+ return;
+ }
+ }
+ /*
+ We got here if didn't find this field. It's not a part of
+ a unique key, and/or there is no field=expr element for it.
+ Bump the dependency anyway, this will signal that this dependency
+ cannot be satisfied.
+ */
+ ctx->equality_mods[expr_offset].unbound_args++;
+ }
+ else
+ visited_other_tables= TRUE;
+ }
+
+ Dep_analysis_context *ctx;
+ /* Offset of the expression we're processing in the dependency bitmap */
+ uint expr_offset;
+
+ bool visited_other_tables;
+};
+
+
+
+
+/*
+ Setup inbound dependency relationships for tbl.col=expr equalities
+
+ SYNOPSIS
+ setup_equality_modules_deps()
+ bound_deps_list Put here modules that were found not to depend on
+ any non-bound columns.
+
+ DESCRIPTION
+ Setup inbound dependency relationships for tbl.col=expr equalities:
+ - allocate a bitmap where we store such dependencies
+ - for each "tbl.col=expr" equality, analyze the expr part and find out
+ which fields it refers to and set appropriate dependencies.
+
+ RETURN
+ FALSE OK
+ TRUE Out of memory
+*/
+
+bool Dep_analysis_context::setup_equality_modules_deps(List<Dep_module>
+ *bound_modules)
+{
+ DBUG_ENTER("setup_equality_modules_deps");
+
+ /*
+ Count Dep_value_field objects and assign each of them a unique
+ bitmap_offset value.
+ */
+ uint offset= 0;
+ for (Dep_value_table **tbl_dep= table_deps;
+ tbl_dep < table_deps + MAX_TABLES;
+ tbl_dep++)
+ {
+ if (*tbl_dep)
+ {
+ for (Dep_value_field *field_dep= (*tbl_dep)->fields;
+ field_dep;
+ field_dep= field_dep->next_table_field)
+ {
+ field_dep->bitmap_offset= offset;
+ offset += n_equality_mods;
+ }
+ }
+ }
+
+ void *buf;
+ if (!(buf= current_thd->alloc(bitmap_buffer_size(offset))) ||
+ my_bitmap_init(&expr_deps, (my_bitmap_map*)buf, offset, FALSE))
+ {
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ }
+ bitmap_clear_all(&expr_deps);
+
+ /*
+ Analyze all "field=expr" dependencies, and have expr_deps encode
+ dependencies of expressions from fields.
+
+ Also collect a linked list of equalities that are bound.
+ */
+ Field_dependency_recorder deps_recorder(this);
+ for (Dep_module_expr *eq_mod= equality_mods;
+ eq_mod < equality_mods + n_equality_mods;
+ eq_mod++)
+ {
+ deps_recorder.expr_offset= eq_mod - equality_mods;
+ deps_recorder.visited_other_tables= FALSE;
+ eq_mod->unbound_args= 0;
+
+ if (eq_mod->field)
+ {
+ /* Regular tbl.col=expr(tblX1.col1, tblY1.col2, ...) */
+ eq_mod->expr->walk(&Item::enumerate_field_refs_processor, FALSE,
+ (uchar*)&deps_recorder);
+ }
+ else
+ {
+ /* It's a multi-equality */
+ eq_mod->unbound_args= !MY_TEST(eq_mod->expr);
+ List_iterator<Dep_value_field> it(*eq_mod->mult_equal_fields);
+ Dep_value_field* field_val;
+ while ((field_val= it++))
+ {
+ uint offs= field_val->bitmap_offset + eq_mod - equality_mods;
+ bitmap_set_bit(&expr_deps, offs);
+ }
+ }
+
+ if (!eq_mod->unbound_args)
+ bound_modules->push_back(eq_mod);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Ordering that we're using whenever we need to maintain a no-duplicates list
+ of field value objects.
+*/
+
+static
+int compare_field_values(Dep_value_field *a, Dep_value_field *b, void *unused)
+{
+ uint a_ratio= a->field->table->tablenr*MAX_FIELDS +
+ a->field->field_index;
+
+ uint b_ratio= b->field->table->tablenr*MAX_FIELDS +
+ b->field->field_index;
+ return (a_ratio < b_ratio)? -1 : ((a_ratio == b_ratio)? 0 : 1);
+}
+
+
+/*
+ Produce Dep_module_expr elements for given condition.
+
+ SYNOPSIS
+ build_eq_mods_for_cond()
+ ctx Table elimination context
+ eq_mod INOUT Put produced equality conditions here
+ and_level INOUT AND-level (like in add_key_fields)
+ cond Condition to process
+
+ DESCRIPTION
+ Analyze the given condition and produce an array of Dep_module_expr
+ dependencies from it. The idea of analysis is as follows:
+ There are useful equalities that have form
+
+ eliminable_tbl.field = expr (denote as useful_equality)
+
+ The condition is composed of useful equalities and other conditions that
+ are combined together with AND and OR operators. We process the condition
+ in recursive fashion according to these basic rules:
+
+ useful_equality1 AND useful_equality2 -> make array of two
+ Dep_module_expr objects
+
+ useful_equality AND other_cond -> discard other_cond
+
+ useful_equality OR other_cond -> discard everything
+
+ useful_equality1 OR useful_equality2 -> check if both sides of OR are the
+ same equality. If yes, that's the
+ result, otherwise discard
+ everything.
+
+ The rules are used to map the condition into an array Dep_module_expr
+ elements. The array will specify functional dependencies that logically
+ follow from the condition.
+
+ SEE ALSO
+ This function is modeled after add_key_fields()
+*/
+
+static
+void build_eq_mods_for_cond(Dep_analysis_context *ctx,
+ Dep_module_expr **eq_mod,
+ uint *and_level, Item *cond)
+{
+ if (cond->type() == Item_func::COND_ITEM)
+ {
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+ uint orig_offset= *eq_mod - ctx->equality_mods;
+
+ /* AND/OR */
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ Item *item;
+ while ((item=li++))
+ build_eq_mods_for_cond(ctx, eq_mod, and_level, item);
+
+ for (Dep_module_expr *mod_exp= ctx->equality_mods + orig_offset;
+ mod_exp != *eq_mod ; mod_exp++)
+ {
+ mod_exp->level= *and_level;
+ }
+ }
+ else
+ {
+ Item *item;
+ (*and_level)++;
+ build_eq_mods_for_cond(ctx, eq_mod, and_level, li++);
+ while ((item=li++))
+ {
+ Dep_module_expr *start_key_fields= *eq_mod;
+ (*and_level)++;
+ build_eq_mods_for_cond(ctx, eq_mod, and_level, item);
+ *eq_mod= merge_eq_mods(ctx->equality_mods + orig_offset,
+ start_key_fields, *eq_mod,
+ ++(*and_level));
+ }
+ }
+ return;
+ }
+
+ if (cond->type() != Item::FUNC_ITEM)
+ return;
+
+ Item_func *cond_func= (Item_func*) cond;
+ Item **args= cond_func->arguments();
+
+ switch (cond_func->functype()) {
+ case Item_func::BETWEEN:
+ {
+ Item *fld;
+ if (!((Item_func_between*)cond)->negated &&
+ (fld= args[0]->real_item())->type() == Item::FIELD_ITEM &&
+ args[1]->eq(args[2], ((Item_field*)fld)->field->binary()))
+ {
+ check_equality(ctx, eq_mod, *and_level, cond_func, args[0], args[1]);
+ check_equality(ctx, eq_mod, *and_level, cond_func, args[1], args[0]);
+ }
+ break;
+ }
+ case Item_func::EQ_FUNC:
+ case Item_func::EQUAL_FUNC:
+ {
+ check_equality(ctx, eq_mod, *and_level, cond_func, args[0], args[1]);
+ check_equality(ctx, eq_mod, *and_level, cond_func, args[1], args[0]);
+ break;
+ }
+ case Item_func::ISNULL_FUNC:
+ {
+ Item *tmp=new Item_null;
+ if (tmp)
+ check_equality(ctx, eq_mod, *and_level, cond_func, args[0], tmp);
+ break;
+ }
+ case Item_func::MULT_EQUAL_FUNC:
+ {
+ /*
+ The condition is a
+
+ tbl1.field1 = tbl2.field2 = tbl3.field3 [= const_expr]
+
+ multiple-equality. Do two things:
+ - Collect List<Dep_value_field> of tblX.colY where tblX is one of the
+ tables we're trying to eliminate.
+ - rembember if there was a bound value, either const_expr or tblY.colZ
+ swher tblY is not a table that we're trying to eliminate.
+ Store all collected information in a Dep_module_expr object.
+ */
+ Item_equal *item_equal= (Item_equal*)cond;
+ List<Dep_value_field> *fvl;
+ if (!(fvl= new List<Dep_value_field>))
+ break; /* purecov: inspected */
+
+ Item_equal_fields_iterator it(*item_equal);
+ Item *item;
+ Item *bound_item= item_equal->get_const();
+ while ((item= it++))
+ {
+ Field *equal_field= it.get_curr_field();
+ if ((item->used_tables() & ctx->usable_tables))
+ {
+ Dep_value_field *field_val;
+ if ((field_val= ctx->get_field_value(equal_field)))
+ fvl->push_back(field_val);
+ }
+ else
+ {
+ if (!bound_item)
+ bound_item= item;
+ }
+ }
+ /*
+ Multiple equality is only useful if it includes at least one field from
+ the table that we could potentially eliminate:
+ */
+ if (fvl->elements)
+ {
+
+ bubble_sort<Dep_value_field>(fvl, compare_field_values, NULL);
+ add_module_expr(ctx, eq_mod, *and_level, NULL, bound_item, fvl);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+
+/*
+ Perform an OR operation on two (adjacent) Dep_module_expr arrays.
+
+ SYNOPSIS
+ merge_eq_mods()
+ start Start of left OR-part
+ new_fields Start of right OR-part
+ end End of right OR-part
+ and_level AND-level (like in add_key_fields)
+
+ DESCRIPTION
+ This function is invoked for two adjacent arrays of Dep_module_expr elements:
+
+ $LEFT_PART $RIGHT_PART
+ +-----------------------+-----------------------+
+ start new_fields end
+
+ The goal is to produce an array which would correspond to the combined
+
+ $LEFT_PART OR $RIGHT_PART
+
+ condition. This is achieved as follows: First, we apply distrubutive law:
+
+ (fdep_A_1 AND fdep_A_2 AND ...) OR (fdep_B_1 AND fdep_B_2 AND ...) =
+
+ = AND_ij (fdep_A_[i] OR fdep_B_[j])
+
+ Then we walk over the obtained "fdep_A_[i] OR fdep_B_[j]" pairs, and
+ - Discard those that that have left and right part referring to different
+ columns. We can't infer anything useful from "col1=expr1 OR col2=expr2".
+ - When left and right parts refer to the same column, we check if they are
+ essentially the same.
+ = If they are the same, we keep one copy
+ "t.col=expr OR t.col=expr" -> "t.col=expr
+ = if they are different , then we discard both
+ "t.col=expr1 OR t.col=expr2" -> (nothing useful)
+
+ (no per-table or for-index FUNC_DEPS exist yet at this phase).
+
+ See also merge_key_fields().
+
+ RETURN
+ End of the result array
+*/
+
+static
+Dep_module_expr *merge_eq_mods(Dep_module_expr *start,
+ Dep_module_expr *new_fields,
+ Dep_module_expr *end, uint and_level)
+{
+ if (start == new_fields)
+ return start; /* (nothing) OR (...) -> (nothing) */
+ if (new_fields == end)
+ return start; /* (...) OR (nothing) -> (nothing) */
+
+ Dep_module_expr *first_free= new_fields;
+
+ for (; new_fields != end ; new_fields++)
+ {
+ for (Dep_module_expr *old=start ; old != first_free ; old++)
+ {
+ if (old->field == new_fields->field)
+ {
+ if (!old->field)
+ {
+ /*
+ OR-ing two multiple equalities. We must compute an intersection of
+ used fields, and check the constants according to these rules:
+
+ a=b=c=d OR a=c=e=f -> a=c (compute intersection)
+ a=const1 OR a=b -> (nothing)
+ a=const1 OR a=const1 -> a=const1
+ a=const1 OR a=const2 -> (nothing)
+
+ If we're performing an OR operation over multiple equalities, e.g.
+
+ (a=b=c AND p=q) OR (a=b AND v=z)
+
+ then we'll need to try combining each equality with each. ANDed
+ equalities are guaranteed to be disjoint, so we'll only get one
+ hit.
+ */
+ Field *eq_field= old->mult_equal_fields->head()->field;
+ if (old->expr && new_fields->expr &&
+ old->expr->eq_by_collation(new_fields->expr, eq_field->binary(),
+ eq_field->charset()))
+ {
+ /* Ok, keep */
+ }
+ else
+ {
+ /* no single constant/bound item. */
+ old->expr= NULL;
+ }
+
+ List <Dep_value_field> *fv;
+ if (!(fv= new List<Dep_value_field>))
+ break; /* purecov: inspected */
+
+ List_iterator<Dep_value_field> it1(*old->mult_equal_fields);
+ List_iterator<Dep_value_field> it2(*new_fields->mult_equal_fields);
+ Dep_value_field *lfield= it1++;
+ Dep_value_field *rfield= it2++;
+ /* Intersect two ordered lists */
+ while (lfield && rfield)
+ {
+ if (lfield == rfield)
+ {
+ fv->push_back(lfield);
+ lfield=it1++;
+ rfield=it2++;
+ }
+ else
+ {
+ if (compare_field_values(lfield, rfield, NULL) < 0)
+ lfield= it1++;
+ else
+ rfield= it2++;
+ }
+ }
+
+ if (fv->elements + MY_TEST(old->expr) > 1)
+ {
+ old->mult_equal_fields= fv;
+ old->level= and_level;
+ }
+ }
+ else if (!new_fields->expr->const_item())
+ {
+ /*
+ If the value matches, we can use the key reference.
+ If not, we keep it until we have examined all new values
+ */
+ if (old->expr->eq(new_fields->expr,
+ old->field->field->binary()))
+ {
+ old->level= and_level;
+ }
+ }
+ else if (old->expr->eq_by_collation(new_fields->expr,
+ old->field->field->binary(),
+ old->field->field->charset()))
+ {
+ old->level= and_level;
+ }
+ else
+ {
+ /* The expressions are different. */
+ if (old == --first_free) // If last item
+ break;
+ *old= *first_free; // Remove old value
+ old--; // Retry this value
+ }
+ }
+ }
+ }
+
+ /*
+ Ok, the results are within the [start, first_free) range, and the useful
+ elements have level==and_level. Now, remove all unusable elements:
+ */
+ for (Dep_module_expr *old=start ; old != first_free ;)
+ {
+ if (old->level != and_level)
+ { // Not used in all levels
+ if (old == --first_free)
+ break;
+ *old= *first_free; // Remove old value
+ continue;
+ }
+ old++;
+ }
+ return first_free;
+}
+
+
+/*
+ Add an Dep_module_expr element for left=right condition
+
+ SYNOPSIS
+ check_equality()
+ fda Table elimination context
+ eq_mod INOUT Store created Dep_module_expr here and increment ptr if
+ you do so
+ and_level AND-level (like in add_key_fields)
+ cond Condition we've inferred the left=right equality from.
+ left Left expression
+ right Right expression
+ usable_tables Create Dep_module_expr only if Left_expression's table
+ belongs to this set.
+
+ DESCRIPTION
+ Check if the passed left=right equality is such that
+ - 'left' is an Item_field referring to a field in a table we're checking
+ to be functionally depdendent,
+ - the equality allows to conclude that 'left' expression is functionally
+ dependent on the 'right',
+ and if so, create an Dep_module_expr object.
+*/
+
+static
+void check_equality(Dep_analysis_context *ctx, Dep_module_expr **eq_mod,
+ uint and_level, Item_func *cond, Item *left, Item *right)
+{
+ if ((left->used_tables() & ctx->usable_tables) &&
+ !(right->used_tables() & RAND_TABLE_BIT) &&
+ left->real_item()->type() == Item::FIELD_ITEM)
+ {
+ Field *field= ((Item_field*)left->real_item())->field;
+ if (right->cmp_type() == TIME_RESULT && field->cmp_type() != TIME_RESULT)
+ return;
+ if (field->result_type() == STRING_RESULT)
+ {
+ if (right->result_type() != STRING_RESULT)
+ {
+ if (field->cmp_type() != right->result_type())
+ return;
+ }
+ else
+ {
+ /*
+ We can't assume there's a functional dependency if the effective
+ collation of the operation differ from the field collation.
+ */
+ if ((field->cmp_type() == STRING_RESULT ||
+ field->real_type() == MYSQL_TYPE_ENUM ||
+ field->real_type() == MYSQL_TYPE_SET) &&
+ field->charset() != cond->compare_collation())
+ return;
+ }
+ }
+ Dep_value_field *field_val;
+ if ((field_val= ctx->get_field_value(field)))
+ add_module_expr(ctx, eq_mod, and_level, field_val, right, NULL);
+ }
+}
+
+
+/*
+ Add a Dep_module_expr object with the specified parameters.
+
+ DESCRIPTION
+ Add a Dep_module_expr object with the specified parameters. Re-allocate
+ the ctx->equality_mods array if it has no space left.
+*/
+
+static
+void add_module_expr(Dep_analysis_context *ctx, Dep_module_expr **eq_mod,
+ uint and_level, Dep_value_field *field_val,
+ Item *right, List<Dep_value_field>* mult_equal_fields)
+{
+ if (*eq_mod == ctx->equality_mods + ctx->n_equality_mods_alloced)
+ {
+ /*
+ We've filled the entire equality_mods array. Replace it with a bigger
+ one. We do it somewhat inefficiently but it doesn't matter.
+ */
+ /* purecov: begin inspected */
+ Dep_module_expr *new_arr;
+ if (!(new_arr= new Dep_module_expr[ctx->n_equality_mods_alloced *2]))
+ return;
+ ctx->n_equality_mods_alloced *= 2;
+ for (int i= 0; i < *eq_mod - ctx->equality_mods; i++)
+ new_arr[i]= ctx->equality_mods[i];
+
+ ctx->equality_mods= new_arr;
+ *eq_mod= new_arr + (*eq_mod - ctx->equality_mods);
+ /* purecov: end */
+ }
+
+ (*eq_mod)->field= field_val;
+ (*eq_mod)->expr= right;
+ (*eq_mod)->level= and_level;
+ (*eq_mod)->mult_equal_fields= mult_equal_fields;
+ (*eq_mod)++;
+}
+
+
+/*
+ Create a Dep_value_table object for the given table
+
+ SYNOPSIS
+ Dep_analysis_context::create_table_value()
+ table Table to create object for
+
+ DESCRIPTION
+ Create a Dep_value_table object for the given table. Also create
+ Dep_module_key objects for all unique keys in the table.
+
+ RETURN
+ Created table value object
+ NULL if out of memory
+*/
+
+Dep_value_table *Dep_analysis_context::create_table_value(TABLE *table)
+{
+ Dep_value_table *tbl_dep;
+ if (!(tbl_dep= new Dep_value_table(table)))
+ return NULL; /* purecov: inspected */
+
+ Dep_module_key **key_list= &(tbl_dep->keys);
+ /* Add dependencies for unique keys */
+ for (uint i=0; i < table->s->keys; i++)
+ {
+ KEY *key= table->key_info + i;
+ if (key->flags & HA_NOSAME)
+ {
+ Dep_module_key *key_dep;
+ if (!(key_dep= new Dep_module_key(tbl_dep, i, key->user_defined_key_parts)))
+ return NULL;
+ *key_list= key_dep;
+ key_list= &(key_dep->next_table_key);
+ }
+ }
+ return table_deps[table->tablenr]= tbl_dep;
+}
+
+
+/*
+ Get a Dep_value_field object for the given field, creating it if necessary
+
+ SYNOPSIS
+ Dep_analysis_context::get_field_value()
+ field Field to create object for
+
+ DESCRIPTION
+ Get a Dep_value_field object for the given field. First, we search for it
+ in the list of Dep_value_field objects we have already created. If we don't
+ find it, we create a new Dep_value_field and put it into the list of field
+ objects we have for the table.
+
+ RETURN
+ Created field value object
+ NULL if out of memory
+*/
+
+Dep_value_field *Dep_analysis_context::get_field_value(Field *field)
+{
+ TABLE *table= field->table;
+ Dep_value_table *tbl_dep= table_deps[table->tablenr];
+
+ /* Try finding the field in field list */
+ Dep_value_field **pfield= &(tbl_dep->fields);
+ while (*pfield && (*pfield)->field->field_index < field->field_index)
+ {
+ pfield= &((*pfield)->next_table_field);
+ }
+ if (*pfield && (*pfield)->field->field_index == field->field_index)
+ return *pfield;
+
+ /* Create the field and insert it in the list */
+ Dep_value_field *new_field= new Dep_value_field(tbl_dep, field);
+ new_field->next_table_field= *pfield;
+ *pfield= new_field;
+
+ return new_field;
+}
+
+
+/*
+ Iteration over unbound modules that are our dependencies.
+ for those we have:
+ - dependendencies of our fields
+ - outer join we're in
+*/
+char *Dep_value_table::init_unbound_modules_iter(char *buf)
+{
+ Module_iter *iter= ALIGN_PTR(my_ptrdiff_t(buf), Module_iter);
+ iter->field_dep= fields;
+ if (fields)
+ {
+ fields->init_unbound_modules_iter(iter->buf);
+ fields->make_unbound_modules_iter_skip_keys(iter->buf);
+ }
+ iter->returned_goal= FALSE;
+ return (char*)iter;
+}
+
+
+Dep_module*
+Dep_value_table::get_next_unbound_module(Dep_analysis_context *dac,
+ char *iter)
+{
+ Module_iter *di= (Module_iter*)iter;
+ while (di->field_dep)
+ {
+ Dep_module *res;
+ if ((res= di->field_dep->get_next_unbound_module(dac, di->buf)))
+ return res;
+ if ((di->field_dep= di->field_dep->next_table_field))
+ {
+ char *field_iter= ((Module_iter*)iter)->buf;
+ di->field_dep->init_unbound_modules_iter(field_iter);
+ di->field_dep->make_unbound_modules_iter_skip_keys(field_iter);
+ }
+ }
+
+ if (!di->returned_goal)
+ {
+ di->returned_goal= TRUE;
+ return dac->outer_join_dep;
+ }
+ return NULL;
+}
+
+
+char *Dep_module_expr::init_unbound_values_iter(char *buf)
+{
+ Value_iter *iter= ALIGN_PTR(my_ptrdiff_t(buf), Value_iter);
+ iter->field= field;
+ if (!field)
+ {
+ new (&iter->it) List_iterator<Dep_value_field>(*mult_equal_fields);
+ }
+ return (char*)iter;
+}
+
+
+Dep_value* Dep_module_expr::get_next_unbound_value(Dep_analysis_context *dac,
+ char *buf)
+{
+ Dep_value *res;
+ if (field)
+ {
+ res= ((Value_iter*)buf)->field;
+ ((Value_iter*)buf)->field= NULL;
+ return (!res || res->is_bound())? NULL : res;
+ }
+ else
+ {
+ while ((res= ((Value_iter*)buf)->it++))
+ {
+ if (!res->is_bound())
+ return res;
+ }
+ return NULL;
+ }
+}
+
+
+char *Dep_module_key::init_unbound_values_iter(char *buf)
+{
+ Value_iter *iter= ALIGN_PTR(my_ptrdiff_t(buf), Value_iter);
+ iter->table= table;
+ return (char*)iter;
+}
+
+
+Dep_value* Dep_module_key::get_next_unbound_value(Dep_analysis_context *dac,
+ Dep_module::Iterator iter)
+{
+ Dep_value* res= ((Value_iter*)iter)->table;
+ ((Value_iter*)iter)->table= NULL;
+ return res;
+}
+
+
+Dep_value::Iterator Dep_value_field::init_unbound_modules_iter(char *buf)
+{
+ Module_iter *iter= ALIGN_PTR(my_ptrdiff_t(buf), Module_iter);
+ iter->key_dep= table->keys;
+ iter->equality_no= 0;
+ return (char*)iter;
+}
+
+
+void
+Dep_value_field::make_unbound_modules_iter_skip_keys(Dep_value::Iterator iter)
+{
+ ((Module_iter*)iter)->key_dep= NULL;
+}
+
+
+Dep_module* Dep_value_field::get_next_unbound_module(Dep_analysis_context *dac,
+ Dep_value::Iterator iter)
+{
+ Module_iter *di= (Module_iter*)iter;
+ Dep_module_key *key_dep= di->key_dep;
+
+ /*
+ First, enumerate all unique keys that are
+ - not yet applicable
+ - have this field as a part of them
+ */
+ while (key_dep && (key_dep->is_applicable() ||
+ !field->part_of_key_not_clustered.is_set(key_dep->keyno)))
+ {
+ key_dep= key_dep->next_table_key;
+ }
+
+ if (key_dep)
+ {
+ di->key_dep= key_dep->next_table_key;
+ return key_dep;
+ }
+ else
+ di->key_dep= NULL;
+
+ /*
+ Then walk through [multi]equalities and find those that
+ - depend on this field
+ - and are not bound yet.
+ */
+ uint eq_no= di->equality_no;
+ while (eq_no < dac->n_equality_mods &&
+ (!bitmap_is_set(&dac->expr_deps, bitmap_offset + eq_no) ||
+ dac->equality_mods[eq_no].is_applicable()))
+ {
+ eq_no++;
+ }
+
+ if (eq_no < dac->n_equality_mods)
+ {
+ di->equality_no= eq_no+1;
+ return &dac->equality_mods[eq_no];
+ }
+ return NULL;
+}
+
+
+/*
+ Mark one table or the whole join nest as eliminated.
+*/
+
+static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl)
+{
+ TABLE *table;
+ /*
+ NOTE: there are TABLE_LIST object that have
+ tbl->table!= NULL && tbl->nested_join!=NULL and
+ tbl->table == tbl->nested_join->join_list->element(..)->table
+ */
+ if (tbl->nested_join)
+ {
+ TABLE_LIST *child;
+ List_iterator<TABLE_LIST> it(tbl->nested_join->join_list);
+ while ((child= it++))
+ mark_as_eliminated(join, child);
+ }
+ else if ((table= tbl->table))
+ {
+ JOIN_TAB *tab= tbl->table->reginfo.join_tab;
+ if (!(join->const_table_map & tab->table->map))
+ {
+ DBUG_PRINT("info", ("Eliminated table %s", table->alias.c_ptr()));
+ tab->type= JT_CONST;
+ join->eliminated_tables |= table->map;
+ join->const_table_map|= table->map;
+ set_position(join, join->const_tables++, tab, (KEYUSE*)0);
+ }
+ }
+
+ if (tbl->on_expr)
+ tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL);
+}
+
+
+#ifndef DBUG_OFF
+/* purecov: begin inspected */
+void Dep_analysis_context::dbug_print_deps()
+{
+ DBUG_ENTER("dbug_print_deps");
+ DBUG_LOCK_FILE;
+
+ fprintf(DBUG_FILE,"deps {\n");
+
+ /* Start with printing equalities */
+ for (Dep_module_expr *eq_mod= equality_mods;
+ eq_mod != equality_mods + n_equality_mods; eq_mod++)
+ {
+ char buf[128];
+ String str(buf, sizeof(buf), &my_charset_bin);
+ str.length(0);
+ eq_mod->expr->print(&str, QT_ORDINARY);
+ if (eq_mod->field)
+ {
+ fprintf(DBUG_FILE, " equality%ld: %s -> %s.%s\n",
+ (long)(eq_mod - equality_mods),
+ str.c_ptr(),
+ eq_mod->field->table->table->alias.c_ptr(),
+ eq_mod->field->field->field_name);
+ }
+ else
+ {
+ fprintf(DBUG_FILE, " equality%ld: multi-equality",
+ (long)(eq_mod - equality_mods));
+ }
+ }
+ fprintf(DBUG_FILE,"\n");
+
+ /* Then tables and their fields */
+ for (uint i=0; i < MAX_TABLES; i++)
+ {
+ Dep_value_table *table_dep;
+ if ((table_dep= table_deps[i]))
+ {
+ /* Print table */
+ fprintf(DBUG_FILE, " table %s\n", table_dep->table->alias.c_ptr());
+ /* Print fields */
+ for (Dep_value_field *field_dep= table_dep->fields; field_dep;
+ field_dep= field_dep->next_table_field)
+ {
+ fprintf(DBUG_FILE, " field %s.%s ->",
+ table_dep->table->alias.c_ptr(),
+ field_dep->field->field_name);
+ uint ofs= field_dep->bitmap_offset;
+ for (uint bit= ofs; bit < ofs + n_equality_mods; bit++)
+ {
+ if (bitmap_is_set(&expr_deps, bit))
+ fprintf(DBUG_FILE, " equality%d ", bit - ofs);
+ }
+ fprintf(DBUG_FILE, "\n");
+ }
+ }
+ }
+ fprintf(DBUG_FILE,"\n}\n");
+ DBUG_UNLOCK_FILE;
+ DBUG_VOID_RETURN;
+}
+/* purecov: end */
+
+#endif
+/**
+ @} (end of group Table_Elimination)
+*/
+
diff --git a/sql/parse_file.cc b/sql/parse_file.cc
new file mode 100644
index 00000000000..197f7c97fda
--- /dev/null
+++ b/sql/parse_file.cc
@@ -0,0 +1,950 @@
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/**
+ @file
+
+ @brief
+ Text .frm files management routines
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "parse_file.h"
+#include "unireg.h" // CREATE_MODE
+#include "sql_table.h" // build_table_filename
+#include <m_ctype.h>
+#include <my_sys.h>
+#include <my_dir.h>
+
+/* from sql_db.cc */
+extern long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path);
+
+
+/**
+ Write string with escaping.
+
+ @param file IO_CACHE for record
+ @param val_s string for writing
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+static my_bool
+write_escaped_string(IO_CACHE *file, LEX_STRING *val_s)
+{
+ char *eos= val_s->str + val_s->length;
+ char *ptr= val_s->str;
+
+ for (; ptr < eos; ptr++)
+ {
+ /*
+ Should be in sync with read_escaped_string() and
+ parse_quoted_escaped_string()
+ */
+ switch(*ptr) {
+ case '\\': // escape character
+ if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\\\")))
+ return TRUE;
+ break;
+ case '\n': // parameter value delimiter
+ if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\n")))
+ return TRUE;
+ break;
+ case '\0': // problem for some string processing utilities
+ if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\0")))
+ return TRUE;
+ break;
+ case 26: // problem for windows utilities (Ctrl-Z)
+ if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\z")))
+ return TRUE;
+ break;
+ case '\'': // list of string delimiter
+ if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\\'")))
+ return TRUE;
+ break;
+ default:
+ if (my_b_append(file, (const uchar *)ptr, 1))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static ulonglong view_algo_to_frm(ulonglong val)
+{
+ switch(val)
+ {
+ case VIEW_ALGORITHM_UNDEFINED:
+ return VIEW_ALGORITHM_UNDEFINED_FRM;
+ case VIEW_ALGORITHM_MERGE:
+ return VIEW_ALGORITHM_MERGE_FRM;
+ case VIEW_ALGORITHM_TMPTABLE:
+ return VIEW_ALGORITHM_TMPTABLE_FRM;
+ }
+ DBUG_ASSERT(0); /* Should never happen */
+ return VIEW_ALGORITHM_UNDEFINED;
+}
+
+static ulonglong view_algo_from_frm(ulonglong val)
+{
+ switch(val)
+ {
+ case VIEW_ALGORITHM_UNDEFINED_FRM:
+ return VIEW_ALGORITHM_UNDEFINED;
+ case VIEW_ALGORITHM_MERGE_FRM:
+ return VIEW_ALGORITHM_MERGE;
+ case VIEW_ALGORITHM_TMPTABLE_FRM:
+ return VIEW_ALGORITHM_TMPTABLE;
+ }
+
+ /*
+ Early versions of MariaDB 5.2/5.3 had identical in-memory and frm values
+ Return input value.
+ */
+ return val;
+}
+
+
+/**
+ Write parameter value to IO_CACHE.
+
+ @param file pointer to IO_CACHE structure for writing
+ @param base pointer to data structure
+ @param parameter pointer to parameter descriptor
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+
+static my_bool
+write_parameter(IO_CACHE *file, uchar* base, File_option *parameter)
+{
+ char num_buf[20]; // buffer for numeric operations
+ // string for numeric operations
+ String num(num_buf, sizeof(num_buf), &my_charset_bin);
+ DBUG_ENTER("write_parameter");
+
+ switch (parameter->type) {
+ case FILE_OPTIONS_STRING:
+ {
+ LEX_STRING *val_s= (LEX_STRING *)(base + parameter->offset);
+ if (my_b_append(file, (const uchar *)val_s->str, val_s->length))
+ DBUG_RETURN(TRUE);
+ break;
+ }
+ case FILE_OPTIONS_ESTRING:
+ {
+ if (write_escaped_string(file, (LEX_STRING *)(base + parameter->offset)))
+ DBUG_RETURN(TRUE);
+ break;
+ }
+ case FILE_OPTIONS_ULONGLONG:
+ case FILE_OPTIONS_VIEW_ALGO:
+ {
+ ulonglong val= *(ulonglong *)(base + parameter->offset);
+
+ if (parameter->type == FILE_OPTIONS_VIEW_ALGO)
+ val= view_algo_to_frm(val);
+
+ num.set(val, &my_charset_bin);
+ if (my_b_append(file, (const uchar *)num.ptr(), num.length()))
+ DBUG_RETURN(TRUE);
+ break;
+ }
+ case FILE_OPTIONS_TIMESTAMP:
+ {
+ /* string have to be allocated already */
+ LEX_STRING *val_s= (LEX_STRING *)(base + parameter->offset);
+ time_t tm= my_time(0);
+
+ get_date(val_s->str, GETDATE_DATE_TIME|GETDATE_GMT|GETDATE_FIXEDLENGTH,
+ tm);
+ val_s->length= PARSE_FILE_TIMESTAMPLENGTH;
+ if (my_b_append(file, (const uchar *)val_s->str,
+ PARSE_FILE_TIMESTAMPLENGTH))
+ DBUG_RETURN(TRUE);
+ break;
+ }
+ case FILE_OPTIONS_STRLIST:
+ {
+ List_iterator_fast<LEX_STRING> it(*((List<LEX_STRING>*)
+ (base + parameter->offset)));
+ bool first= 1;
+ LEX_STRING *str;
+ while ((str= it++))
+ {
+ // We need ' ' after string to detect list continuation
+ if ((!first && my_b_append(file, (const uchar *)STRING_WITH_LEN(" "))) ||
+ my_b_append(file, (const uchar *)STRING_WITH_LEN("\'")) ||
+ write_escaped_string(file, str) ||
+ my_b_append(file, (const uchar *)STRING_WITH_LEN("\'")))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ first= 0;
+ }
+ break;
+ }
+ case FILE_OPTIONS_ULLLIST:
+ {
+ List_iterator_fast<ulonglong> it(*((List<ulonglong>*)
+ (base + parameter->offset)));
+ bool first= 1;
+ ulonglong *val;
+ while ((val= it++))
+ {
+ num.set(*val, &my_charset_bin);
+ // We need ' ' after string to detect list continuation
+ if ((!first && my_b_append(file, (const uchar *)STRING_WITH_LEN(" "))) ||
+ my_b_append(file, (const uchar *)num.ptr(), num.length()))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ first= 0;
+ }
+ break;
+ }
+ default:
+ DBUG_ASSERT(0); // never should happened
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Write new .frm.
+
+ @param dir directory where put .frm
+ @param file_name .frm file name
+ @param type .frm type string (VIEW, TABLE)
+ @param base base address for parameter reading (structure like
+ TABLE)
+ @param parameters parameters description
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+
+my_bool
+sql_create_definition_file(const LEX_STRING *dir, const LEX_STRING *file_name,
+ const LEX_STRING *type,
+ uchar* base, File_option *parameters)
+{
+ File handler;
+ IO_CACHE file;
+ char path[FN_REFLEN+1]; // +1 to put temporary file name for sure
+ int path_end;
+ File_option *param;
+ DBUG_ENTER("sql_create_definition_file");
+ DBUG_PRINT("enter", ("Dir: %s, file: %s, base 0x%lx",
+ dir ? dir->str : "",
+ file_name->str, (ulong) base));
+
+ if (dir)
+ {
+ fn_format(path, file_name->str, dir->str, "", MY_UNPACK_FILENAME);
+ path_end= strlen(path);
+ }
+ else
+ {
+ /*
+ if not dir is passed, it means file_name is a full path,
+ including dir name, file name itself, and an extension,
+ and with unpack_filename() executed over it.
+ */
+ path_end= strxnmov(path, sizeof(path) - 1, file_name->str, NullS) - path;
+ }
+
+ // temporary file name
+ path[path_end]='~';
+ path[path_end+1]= '\0';
+ if ((handler= mysql_file_create(key_file_fileparser,
+ path, CREATE_MODE, O_RDWR | O_TRUNC,
+ MYF(MY_WME))) < 0)
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ if (init_io_cache(&file, handler, 0, SEQ_READ_APPEND, 0L, 0, MYF(MY_WME)))
+ goto err_w_file;
+
+ // write header (file signature)
+ if (my_b_append(&file, (const uchar *)STRING_WITH_LEN("TYPE=")) ||
+ my_b_append(&file, (const uchar *)type->str, type->length) ||
+ my_b_append(&file, (const uchar *)STRING_WITH_LEN("\n")))
+ goto err_w_file;
+
+ // write parameters to temporary file
+ for (param= parameters; param->name.str; param++)
+ {
+ if (my_b_append(&file, (const uchar *)param->name.str,
+ param->name.length) ||
+ my_b_append(&file, (const uchar *)STRING_WITH_LEN("=")) ||
+ write_parameter(&file, base, param) ||
+ my_b_append(&file, (const uchar *)STRING_WITH_LEN("\n")))
+ goto err_w_cache;
+ }
+
+ if (end_io_cache(&file))
+ goto err_w_file;
+
+ if (opt_sync_frm) {
+ if (mysql_file_sync(handler, MYF(MY_WME)))
+ goto err_w_file;
+ }
+
+ if (mysql_file_close(handler, MYF(MY_WME)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ path[path_end]='\0';
+
+ {
+ // rename temporary file
+ char path_to[FN_REFLEN];
+ memcpy(path_to, path, path_end+1);
+ path[path_end]='~';
+ if (mysql_file_rename(key_file_fileparser, path, path_to, MYF(MY_WME)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+err_w_cache:
+ end_io_cache(&file);
+err_w_file:
+ mysql_file_close(handler, MYF(MY_WME));
+ DBUG_RETURN(TRUE);
+}
+
+/**
+ Renames a frm file (including backups) in same schema.
+
+ @thd thread handler
+ @param schema name of given schema
+ @param old_name original file name
+ @param new_db new schema
+ @param new_name new file name
+
+ @retval
+ 0 OK
+ @retval
+ 1 Error (only if renaming of frm failed)
+*/
+my_bool rename_in_schema_file(THD *thd,
+ const char *schema, const char *old_name,
+ const char *new_db, const char *new_name)
+{
+ char old_path[FN_REFLEN + 1], new_path[FN_REFLEN + 1], arc_path[FN_REFLEN + 1];
+
+ build_table_filename(old_path, sizeof(old_path) - 1,
+ schema, old_name, reg_ext, 0);
+ build_table_filename(new_path, sizeof(new_path) - 1,
+ new_db, new_name, reg_ext, 0);
+
+ if (mysql_file_rename(key_file_frm, old_path, new_path, MYF(MY_WME)))
+ return 1;
+
+ /* check if arc_dir exists: disabled unused feature (see bug #17823). */
+ build_table_filename(arc_path, sizeof(arc_path) - 1, schema, "arc", "", 0);
+
+ { // remove obsolete 'arc' directory and files if any
+ MY_DIR *new_dirp;
+ if ((new_dirp = my_dir(arc_path, MYF(MY_DONT_SORT))))
+ {
+ DBUG_PRINT("my",("Archive subdir found: %s", arc_path));
+ (void) mysql_rm_arc_files(thd, new_dirp, arc_path);
+ }
+ }
+ return 0;
+}
+
+/**
+ Prepare frm to parse (read to memory).
+
+ @param file_name path & filename to .frm file
+ @param mem_root MEM_ROOT for buffer allocation
+ @param bad_format_errors send errors on bad content
+
+ @note
+ returned pointer + 1 will be type of .frm
+
+ @return
+ 0 - error
+ @return
+ parser object
+*/
+
+File_parser *
+sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root,
+ bool bad_format_errors)
+{
+ MY_STAT stat_info;
+ size_t len;
+ char *end, *sign;
+ File_parser *parser;
+ File file;
+ DBUG_ENTER("sql_parse_prepare");
+
+ if (!mysql_file_stat(key_file_fileparser,
+ file_name->str, &stat_info, MYF(MY_WME)))
+ {
+ DBUG_RETURN(0);
+ }
+
+ if (stat_info.st_size > INT_MAX-1)
+ {
+ my_error(ER_FPARSER_TOO_BIG_FILE, MYF(0), file_name->str);
+ DBUG_RETURN(0);
+ }
+
+ if (!(parser= new(mem_root) File_parser))
+ {
+ DBUG_RETURN(0);
+ }
+
+ if (!(parser->buff= (char*) alloc_root(mem_root, (size_t)(stat_info.st_size+1))))
+ {
+ DBUG_RETURN(0);
+ }
+
+ if ((file= mysql_file_open(key_file_fileparser, file_name->str,
+ O_RDONLY | O_SHARE, MYF(MY_WME))) < 0)
+ {
+ DBUG_RETURN(0);
+ }
+
+ if ((len= mysql_file_read(file, (uchar *)parser->buff,
+ stat_info.st_size, MYF(MY_WME))) ==
+ MY_FILE_ERROR)
+ {
+ mysql_file_close(file, MYF(MY_WME));
+ DBUG_RETURN(0);
+ }
+
+ if (mysql_file_close(file, MYF(MY_WME)))
+ {
+ DBUG_RETURN(0);
+ }
+
+ end= parser->end= parser->buff + len;
+ *end= '\0'; // barrier for more simple parsing
+
+ // 7 = 5 (TYPE=) + 1 (letter at least of type name) + 1 ('\n')
+ if (len < 7 ||
+ parser->buff[0] != 'T' ||
+ parser->buff[1] != 'Y' ||
+ parser->buff[2] != 'P' ||
+ parser->buff[3] != 'E' ||
+ parser->buff[4] != '=')
+ goto frm_error;
+
+ // skip signature;
+ parser->file_type.str= sign= parser->buff + 5;
+ while (*sign >= 'A' && *sign <= 'Z' && sign < end)
+ sign++;
+ if (*sign != '\n')
+ goto frm_error;
+ parser->file_type.length= sign - parser->file_type.str;
+ // EOS for file signature just for safety
+ *sign= '\0';
+
+ parser->start= sign + 1;
+ parser->content_ok= 1;
+
+ DBUG_RETURN(parser);
+
+frm_error:
+ if (bad_format_errors)
+ {
+ my_error(ER_FPARSER_BAD_HEADER, MYF(0), file_name->str);
+ DBUG_RETURN(0);
+ }
+ else
+ DBUG_RETURN(parser); // upper level have to check parser->ok()
+}
+
+
+/**
+ parse LEX_STRING.
+
+ @param ptr pointer on string beginning
+ @param end pointer on symbol after parsed string end (still owned
+ by buffer and can be accessed
+ @param mem_root MEM_ROOT for parameter allocation
+ @param str pointer on string, where results should be stored
+
+ @retval
+ 0 error
+ @retval
+ \# pointer on symbol after string
+*/
+
+
+static char *
+parse_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str)
+{
+ // get string length
+ char *eol= strchr(ptr, '\n');
+
+ if (eol >= end)
+ return 0;
+
+ str->length= eol - ptr;
+
+ if (!(str->str= strmake_root(mem_root, ptr, str->length)))
+ return 0;
+ return eol+1;
+}
+
+
+/**
+ read escaped string from ptr to eol in already allocated str.
+
+ @param ptr pointer on string beginning
+ @param eol pointer on character after end of string
+ @param str target string
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+my_bool
+read_escaped_string(char *ptr, char *eol, LEX_STRING *str)
+{
+ char *write_pos= str->str;
+
+ for (; ptr < eol; ptr++, write_pos++)
+ {
+ char c= *ptr;
+ if (c == '\\')
+ {
+ ptr++;
+ if (ptr >= eol)
+ return TRUE;
+ /*
+ Should be in sync with write_escaped_string() and
+ parse_quoted_escaped_string()
+ */
+ switch(*ptr) {
+ case '\\':
+ *write_pos= '\\';
+ break;
+ case 'n':
+ *write_pos= '\n';
+ break;
+ case '0':
+ *write_pos= '\0';
+ break;
+ case 'z':
+ *write_pos= 26;
+ break;
+ case '\'':
+ *write_pos= '\'';
+ break;
+ default:
+ return TRUE;
+ }
+ }
+ else
+ *write_pos= c;
+ }
+ str->str[str->length= write_pos-str->str]= '\0'; // just for safety
+ return FALSE;
+}
+
+
+/**
+ parse \\n delimited escaped string.
+
+ @param ptr pointer on string beginning
+ @param end pointer on symbol after parsed string end (still owned
+ by buffer and can be accessed
+ @param mem_root MEM_ROOT for parameter allocation
+ @param str pointer on string, where results should be stored
+
+ @retval
+ 0 error
+ @retval
+ \# pointer on symbol after string
+*/
+
+
+char *
+parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str)
+{
+ char *eol= strchr(ptr, '\n');
+
+ if (eol == 0 || eol >= end ||
+ !(str->str= (char*) alloc_root(mem_root, (eol - ptr) + 1)) ||
+ read_escaped_string(ptr, eol, str))
+ return 0;
+
+ return eol+1;
+}
+
+
+/**
+ parse '' delimited escaped string.
+
+ @param ptr pointer on string beginning
+ @param end pointer on symbol after parsed string end (still owned
+ by buffer and can be accessed
+ @param mem_root MEM_ROOT for parameter allocation
+ @param str pointer on string, where results should be stored
+
+ @retval
+ 0 error
+ @retval
+ \# pointer on symbol after string
+*/
+
+static char *
+parse_quoted_escaped_string(char *ptr, char *end,
+ MEM_ROOT *mem_root, LEX_STRING *str)
+{
+ char *eol;
+ uint result_len= 0;
+ bool escaped= 0;
+
+ // starting '
+ if (*(ptr++) != '\'')
+ return 0;
+
+ // find ending '
+ for (eol= ptr; (*eol != '\'' || escaped) && eol < end; eol++)
+ {
+ if (!(escaped= (*eol == '\\' && !escaped)))
+ result_len++;
+ }
+
+ // process string
+ if (eol >= end ||
+ !(str->str= (char*) alloc_root(mem_root, result_len + 1)) ||
+ read_escaped_string(ptr, eol, str))
+ return 0;
+
+ return eol+1;
+}
+
+
+/**
+ Parser for FILE_OPTIONS_ULLLIST type value.
+
+ @param[in,out] ptr pointer to parameter
+ @param[in] end end of the configuration
+ @param[in] line pointer to the line begining
+ @param[in] base base address for parameter writing (structure
+ like TABLE)
+ @param[in] parameter description
+ @param[in] mem_root MEM_ROOT for parameters allocation
+*/
+
+bool get_file_options_ulllist(char *&ptr, char *end, char *line,
+ uchar* base, File_option *parameter,
+ MEM_ROOT *mem_root)
+{
+ List<ulonglong> *nlist= (List<ulonglong>*)(base + parameter->offset);
+ ulonglong *num;
+ nlist->empty();
+ // list parsing
+ while (ptr < end)
+ {
+ int not_used;
+ char *num_end= end;
+ if (!(num= (ulonglong*)alloc_root(mem_root, sizeof(ulonglong))) ||
+ nlist->push_back(num, mem_root))
+ goto nlist_err;
+ *num= my_strtoll10(ptr, &num_end, &not_used);
+ ptr= num_end;
+ switch (*ptr) {
+ case '\n':
+ goto end_of_nlist;
+ case ' ':
+ // we cant go over buffer bounds, because we have \0 at the end
+ ptr++;
+ break;
+ default:
+ goto nlist_err_w_message;
+ }
+ }
+
+end_of_nlist:
+ if (*(ptr++) != '\n')
+ goto nlist_err;
+ return FALSE;
+
+nlist_err_w_message:
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), parameter->name.str, line);
+nlist_err:
+ return TRUE;
+}
+
+
+/**
+ parse parameters.
+
+ @param base base address for parameter writing (structure like
+ TABLE)
+ @param mem_root MEM_ROOT for parameters allocation
+ @param parameters parameters description
+ @param required number of required parameters in above list. If the file
+ contains more parameters than "required", they will
+ be ignored. If the file contains less parameters
+ then "required", non-existing parameters will
+ remain their values.
+ @param hook hook called for unknown keys
+ @param hook_data some data specific for the hook
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+
+my_bool
+File_parser::parse(uchar* base, MEM_ROOT *mem_root,
+ struct File_option *parameters, uint required,
+ Unknown_key_hook *hook)
+{
+ uint first_param= 0, found= 0;
+ char *ptr= start;
+ char *eol;
+ LEX_STRING *str;
+ List<LEX_STRING> *list;
+ DBUG_ENTER("File_parser::parse");
+
+ while (ptr < end && found < required)
+ {
+ char *line= ptr;
+ if (*ptr == '#')
+ {
+ // it is comment
+ if (!(ptr= strchr(ptr, '\n')))
+ {
+ my_error(ER_FPARSER_EOF_IN_COMMENT, MYF(0), line);
+ DBUG_RETURN(TRUE);
+ }
+ ptr++;
+ }
+ else
+ {
+ File_option *parameter= parameters+first_param,
+ *parameters_end= parameters+required;
+ int len= 0;
+ for (; parameter < parameters_end; parameter++)
+ {
+ len= parameter->name.length;
+ // check length
+ if (len < (end-ptr) && ptr[len] != '=')
+ continue;
+ // check keyword
+ if (memcmp(parameter->name.str, ptr, len) == 0)
+ break;
+ }
+
+ if (parameter < parameters_end)
+ {
+ found++;
+ /*
+ if we found first parameter, start search from next parameter
+ next time.
+ (this small optimisation should work, because they should be
+ written in same order)
+ */
+ if (parameter == parameters+first_param)
+ first_param++;
+
+ // get value
+ ptr+= (len+1);
+ switch (parameter->type) {
+ case FILE_OPTIONS_STRING:
+ {
+ if (!(ptr= parse_string(ptr, end, mem_root,
+ (LEX_STRING *)(base +
+ parameter->offset))))
+ {
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
+ parameter->name.str, line);
+ DBUG_RETURN(TRUE);
+ }
+ break;
+ }
+ case FILE_OPTIONS_ESTRING:
+ {
+ if (!(ptr= parse_escaped_string(ptr, end, mem_root,
+ (LEX_STRING *)
+ (base + parameter->offset))))
+ {
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
+ parameter->name.str, line);
+ DBUG_RETURN(TRUE);
+ }
+ break;
+ }
+ case FILE_OPTIONS_ULONGLONG:
+ case FILE_OPTIONS_VIEW_ALGO:
+ if (!(eol= strchr(ptr, '\n')))
+ {
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
+ parameter->name.str, line);
+ DBUG_RETURN(TRUE);
+ }
+ {
+ int not_used;
+ ulonglong val= (ulonglong)my_strtoll10(ptr, 0, &not_used);
+
+ if (parameter->type == FILE_OPTIONS_VIEW_ALGO)
+ val= view_algo_from_frm(val);
+
+ *((ulonglong*)(base + parameter->offset))= val;
+ }
+ ptr= eol+1;
+ break;
+ case FILE_OPTIONS_TIMESTAMP:
+ {
+ /* string have to be allocated already */
+ LEX_STRING *val= (LEX_STRING *)(base + parameter->offset);
+ /* yyyy-mm-dd HH:MM:SS = 19(PARSE_FILE_TIMESTAMPLENGTH) characters */
+ if (ptr[PARSE_FILE_TIMESTAMPLENGTH] != '\n')
+ {
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
+ parameter->name.str, line);
+ DBUG_RETURN(TRUE);
+ }
+ memcpy(val->str, ptr, PARSE_FILE_TIMESTAMPLENGTH);
+ val->str[val->length= PARSE_FILE_TIMESTAMPLENGTH]= '\0';
+ ptr+= (PARSE_FILE_TIMESTAMPLENGTH+1);
+ break;
+ }
+ case FILE_OPTIONS_STRLIST:
+ {
+ list= (List<LEX_STRING>*)(base + parameter->offset);
+
+ list->empty();
+ // list parsing
+ while (ptr < end)
+ {
+ if (!(str= (LEX_STRING*)alloc_root(mem_root,
+ sizeof(LEX_STRING))) ||
+ list->push_back(str, mem_root))
+ goto list_err;
+ if (!(ptr= parse_quoted_escaped_string(ptr, end, mem_root, str)))
+ goto list_err_w_message;
+ switch (*ptr) {
+ case '\n':
+ goto end_of_list;
+ case ' ':
+ // we cant go over buffer bounds, because we have \0 at the end
+ ptr++;
+ break;
+ default:
+ goto list_err_w_message;
+ }
+ }
+
+end_of_list:
+ if (*(ptr++) != '\n')
+ goto list_err;
+ break;
+
+list_err_w_message:
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
+ parameter->name.str, line);
+list_err:
+ DBUG_RETURN(TRUE);
+ }
+ case FILE_OPTIONS_ULLLIST:
+ if (get_file_options_ulllist(ptr, end, line, base,
+ parameter, mem_root))
+ DBUG_RETURN(TRUE);
+ break;
+ default:
+ DBUG_ASSERT(0); // never should happened
+ }
+ }
+ else
+ {
+ ptr= line;
+ if (hook->process_unknown_string(ptr, base, mem_root, end))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ // skip unknown parameter
+ if (!(ptr= strchr(ptr, '\n')))
+ {
+ my_error(ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER, MYF(0), line);
+ DBUG_RETURN(TRUE);
+ }
+ ptr++;
+ }
+ }
+ }
+
+ /*
+ NOTE: if we read less than "required" parameters, it is still Ok.
+ Probably, we've just read the file of the previous version, which
+ contains less parameters.
+ */
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Dummy unknown key hook.
+
+ @param[in,out] unknown_key reference on the line with unknown
+ parameter and the parsing point
+ @param[in] base base address for parameter writing
+ (structure like TABLE)
+ @param[in] mem_root MEM_ROOT for parameters allocation
+ @param[in] end the end of the configuration
+
+ @note
+ This hook used to catch no longer supported keys and process them for
+ backward compatibility, but it will not slow down processing of modern
+ format files.
+ This hook does nothing except debug output.
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+bool
+File_parser_dummy_hook::process_unknown_string(char *&unknown_key,
+ uchar* base, MEM_ROOT *mem_root,
+ char *end)
+{
+ DBUG_ENTER("file_parser_dummy_hook::process_unknown_string");
+ DBUG_PRINT("info", ("Unknown key: '%60s'", unknown_key));
+ DBUG_RETURN(FALSE);
+}
diff --git a/sql/parse_file.h b/sql/parse_file.h
new file mode 100644
index 00000000000..2a0266e98b7
--- /dev/null
+++ b/sql/parse_file.h
@@ -0,0 +1,116 @@
+/* -*- C++ -*- */
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _PARSE_FILE_H_
+#define _PARSE_FILE_H_
+
+#include "my_global.h" // uchar
+#include "sql_string.h" // LEX_STRING
+#include "sql_list.h" // Sql_alloc
+
+class THD;
+
+typedef struct st_mem_root MEM_ROOT;
+
+#define PARSE_FILE_TIMESTAMPLENGTH 19
+
+enum file_opt_type {
+ FILE_OPTIONS_STRING, /**< String (LEX_STRING) */
+ FILE_OPTIONS_ESTRING, /**< Escaped string (LEX_STRING) */
+ FILE_OPTIONS_ULONGLONG, /**< ulonglong parameter (ulonglong) */
+ FILE_OPTIONS_VIEW_ALGO, /**< Similar to longlong, but needs conversion */
+ FILE_OPTIONS_TIMESTAMP, /**< timestamp (LEX_STRING have to be
+ allocated with length 20 (19+1) */
+ FILE_OPTIONS_STRLIST, /**< list of escaped strings
+ (List<LEX_STRING>) */
+ FILE_OPTIONS_ULLLIST /**< list of ulonglong values
+ (List<ulonglong>) */
+};
+
+struct File_option
+{
+ LEX_STRING name; /**< Name of the option */
+ int offset; /**< offset to base address of value */
+ file_opt_type type; /**< Option type */
+};
+
+
+/**
+ This hook used to catch no longer supported keys and process them for
+ backward compatibility.
+*/
+
+class Unknown_key_hook
+{
+public:
+ Unknown_key_hook() {} /* Remove gcc warning */
+ virtual ~Unknown_key_hook() {} /* Remove gcc warning */
+ virtual bool process_unknown_string(char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, char *end)= 0;
+};
+
+
+/** Dummy hook for parsers which do not need hook for unknown keys. */
+
+class File_parser_dummy_hook: public Unknown_key_hook
+{
+public:
+ File_parser_dummy_hook() {} /* Remove gcc warning */
+ virtual bool process_unknown_string(char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, char *end);
+};
+
+extern File_parser_dummy_hook file_parser_dummy_hook;
+
+bool get_file_options_ulllist(char *&ptr, char *end, char *line,
+ uchar* base, File_option *parameter,
+ MEM_ROOT *mem_root);
+
+char *
+parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str);
+
+class File_parser;
+File_parser *sql_parse_prepare(const LEX_STRING *file_name,
+ MEM_ROOT *mem_root, bool bad_format_errors);
+
+my_bool
+sql_create_definition_file(const LEX_STRING *dir, const LEX_STRING *file_name,
+ const LEX_STRING *type,
+ uchar* base, File_option *parameters);
+my_bool rename_in_schema_file(THD *thd,
+ const char *schema, const char *old_name,
+ const char *new_db, const char *new_name);
+
+class File_parser: public Sql_alloc
+{
+ char *buff, *start, *end;
+ LEX_STRING file_type;
+ bool content_ok;
+public:
+ File_parser() :buff(0), start(0), end(0), content_ok(0)
+ { file_type.str= 0; file_type.length= 0; }
+
+ bool ok() { return content_ok; }
+ LEX_STRING *type() { return &file_type; }
+ my_bool parse(uchar* base, MEM_ROOT *mem_root,
+ struct File_option *parameters, uint required,
+ Unknown_key_hook *hook);
+
+ friend File_parser *sql_parse_prepare(const LEX_STRING *file_name,
+ MEM_ROOT *mem_root,
+ bool bad_format_errors);
+};
+#endif /* _PARSE_FILE_H_ */
diff --git a/sql/partition_element.h b/sql/partition_element.h
new file mode 100644
index 00000000000..308a4d6ddd2
--- /dev/null
+++ b/sql/partition_element.h
@@ -0,0 +1,141 @@
+#ifndef PARTITION_ELEMENT_INCLUDED
+#define PARTITION_ELEMENT_INCLUDED
+
+/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "my_base.h" /* ha_rows */
+#include "handler.h" /* UNDEF_NODEGROUP */
+
+/**
+ * An enum and a struct to handle partitioning and subpartitioning.
+ */
+enum partition_type {
+ NOT_A_PARTITION= 0,
+ RANGE_PARTITION,
+ HASH_PARTITION,
+ LIST_PARTITION
+};
+
+enum partition_state {
+ PART_NORMAL= 0,
+ PART_IS_DROPPED= 1,
+ PART_TO_BE_DROPPED= 2,
+ PART_TO_BE_ADDED= 3,
+ PART_TO_BE_REORGED= 4,
+ PART_REORGED_DROPPED= 5,
+ PART_CHANGED= 6,
+ PART_IS_CHANGED= 7,
+ PART_IS_ADDED= 8,
+ PART_ADMIN= 9
+};
+
+/*
+ This struct is used to keep track of column expressions as part
+ of the COLUMNS concept in conjunction with RANGE and LIST partitioning.
+ The value can be either of MINVALUE, MAXVALUE and an expression that
+ must be constant and evaluate to the same type as the column it
+ represents.
+
+ The data in this fixed in two steps. The parser will only fill in whether
+ it is a max_value or provide an expression. Filling in
+ column_value, part_info, partition_id, null_value is done by the
+ function fix_column_value_function. However the item tree needs
+ fixed also before writing it into the frm file (in add_column_list_values).
+ To distinguish between those two variants, fixed= 1 after the
+ fixing in add_column_list_values and fixed= 2 otherwise. This is
+ since the fixing in add_column_list_values isn't a complete fixing.
+*/
+
+typedef struct p_column_list_val
+{
+ void* column_value;
+ Item* item_expression;
+ partition_info *part_info;
+ uint partition_id;
+ bool max_value;
+ bool null_value;
+ char fixed;
+} part_column_list_val;
+
+
+/*
+ This struct is used to contain the value of an element
+ in the VALUES IN struct. It needs to keep knowledge of
+ whether it is a signed/unsigned value and whether it is
+ NULL or not.
+*/
+
+typedef struct p_elem_val
+{
+ longlong value;
+ uint added_items;
+ bool null_value;
+ bool unsigned_flag;
+ part_column_list_val *col_val_array;
+} part_elem_value;
+
+struct st_ddl_log_memory_entry;
+
+class partition_element :public Sql_alloc {
+public:
+ List<partition_element> subpartitions;
+ List<part_elem_value> list_val_list;
+ ha_rows part_max_rows;
+ ha_rows part_min_rows;
+ longlong range_value;
+ char *partition_name;
+ char *tablespace_name;
+ struct st_ddl_log_memory_entry *log_entry;
+ char* part_comment;
+ char* data_file_name;
+ char* index_file_name;
+ handlerton *engine_type;
+ LEX_STRING connect_string;
+ enum partition_state part_state;
+ uint16 nodegroup_id;
+ bool has_null_value;
+ bool signed_flag; // Range value signed
+ bool max_value; // MAXVALUE range
+
+ partition_element()
+ : part_max_rows(0), part_min_rows(0), range_value(0),
+ partition_name(NULL), tablespace_name(NULL),
+ log_entry(NULL), part_comment(NULL),
+ data_file_name(NULL), index_file_name(NULL),
+ engine_type(NULL), connect_string(null_lex_str), part_state(PART_NORMAL),
+ nodegroup_id(UNDEF_NODEGROUP), has_null_value(FALSE),
+ signed_flag(FALSE), max_value(FALSE)
+ {
+ }
+ partition_element(partition_element *part_elem)
+ : part_max_rows(part_elem->part_max_rows),
+ part_min_rows(part_elem->part_min_rows),
+ range_value(0), partition_name(NULL),
+ tablespace_name(part_elem->tablespace_name),
+ part_comment(part_elem->part_comment),
+ data_file_name(part_elem->data_file_name),
+ index_file_name(part_elem->index_file_name),
+ engine_type(part_elem->engine_type),
+ connect_string(null_lex_str),
+ part_state(part_elem->part_state),
+ nodegroup_id(part_elem->nodegroup_id),
+ has_null_value(FALSE)
+ {
+ }
+ ~partition_element() {}
+};
+
+#endif /* PARTITION_ELEMENT_INCLUDED */
diff --git a/sql/partition_info.cc b/sql/partition_info.cc
new file mode 100644
index 00000000000..73b88d64224
--- /dev/null
+++ b/sql/partition_info.cc
@@ -0,0 +1,3154 @@
+/* Copyright (c) 2006, 2013, Oracle and/or its affiliates.
+
+ 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 */
+
+/* Some general useful functions */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+// Required to get server definitions for mysql/plugin.h right
+#include "sql_plugin.h"
+#include "sql_partition.h" // partition_info.h: LIST_PART_ENTRY
+ // NOT_A_PARTITION_ID
+#include "partition_info.h"
+#include "sql_parse.h" // test_if_data_home_dir
+#include "sql_acl.h" // *_ACL
+#include "sql_base.h" // fill_record
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+#include "ha_partition.h"
+
+
+partition_info *partition_info::get_clone()
+{
+ DBUG_ENTER("partition_info::get_clone");
+ if (!this)
+ DBUG_RETURN(NULL);
+ List_iterator<partition_element> part_it(partitions);
+ partition_element *part;
+ partition_info *clone= new partition_info();
+ if (!clone)
+ {
+ mem_alloc_error(sizeof(partition_info));
+ DBUG_RETURN(NULL);
+ }
+ memcpy(clone, this, sizeof(partition_info));
+ memset(&(clone->read_partitions), 0, sizeof(clone->read_partitions));
+ memset(&(clone->lock_partitions), 0, sizeof(clone->lock_partitions));
+ clone->bitmaps_are_initialized= FALSE;
+ clone->partitions.empty();
+
+ while ((part= (part_it++)))
+ {
+ List_iterator<partition_element> subpart_it(part->subpartitions);
+ partition_element *subpart;
+ partition_element *part_clone= new partition_element();
+ if (!part_clone)
+ {
+ mem_alloc_error(sizeof(partition_element));
+ DBUG_RETURN(NULL);
+ }
+ memcpy(part_clone, part, sizeof(partition_element));
+ part_clone->subpartitions.empty();
+ while ((subpart= (subpart_it++)))
+ {
+ partition_element *subpart_clone= new partition_element();
+ if (!subpart_clone)
+ {
+ mem_alloc_error(sizeof(partition_element));
+ DBUG_RETURN(NULL);
+ }
+ memcpy(subpart_clone, subpart, sizeof(partition_element));
+ part_clone->subpartitions.push_back(subpart_clone);
+ }
+ clone->partitions.push_back(part_clone);
+ }
+ DBUG_RETURN(clone);
+}
+
+/**
+ Mark named [sub]partition to be used/locked.
+
+ @param part_name Partition name to match.
+ @param length Partition name length.
+
+ @return Success if partition found
+ @retval true Partition found
+ @retval false Partition not found
+*/
+
+bool partition_info::add_named_partition(const char *part_name,
+ uint length)
+{
+ HASH *part_name_hash;
+ PART_NAME_DEF *part_def;
+ Partition_share *part_share;
+ DBUG_ENTER("partition_info::add_named_partition");
+ DBUG_ASSERT(table && table->s && table->s->ha_share);
+ part_share= static_cast<Partition_share*>((table->s->ha_share));
+ DBUG_ASSERT(part_share->partition_name_hash_initialized);
+ part_name_hash= &part_share->partition_name_hash;
+ DBUG_ASSERT(part_name_hash->records);
+
+ part_def= (PART_NAME_DEF*) my_hash_search(part_name_hash,
+ (const uchar*) part_name,
+ length);
+ if (!part_def)
+ {
+ my_error(ER_UNKNOWN_PARTITION, MYF(0), part_name, table->alias.c_ptr());
+ DBUG_RETURN(true);
+ }
+
+ if (part_def->is_subpart)
+ {
+ bitmap_set_bit(&read_partitions, part_def->part_id);
+ }
+ else
+ {
+ if (is_sub_partitioned())
+ {
+ /* Mark all subpartitions in the partition */
+ uint j, start= part_def->part_id;
+ uint end= start + num_subparts;
+ for (j= start; j < end; j++)
+ bitmap_set_bit(&read_partitions, j);
+ }
+ else
+ bitmap_set_bit(&read_partitions, part_def->part_id);
+ }
+ DBUG_PRINT("info", ("Found partition %u is_subpart %d for name %s",
+ part_def->part_id, part_def->is_subpart,
+ part_name));
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Mark named [sub]partition to be used/locked.
+
+ @param part_elem Partition element that matched.
+*/
+
+bool partition_info::set_named_partition_bitmap(const char *part_name,
+ uint length)
+{
+ DBUG_ENTER("partition_info::set_named_partition_bitmap");
+ bitmap_clear_all(&read_partitions);
+ if (add_named_partition(part_name, length))
+ DBUG_RETURN(true);
+ bitmap_copy(&lock_partitions, &read_partitions);
+ DBUG_RETURN(false);
+}
+
+
+
+/**
+ Prune away partitions not mentioned in the PARTITION () clause,
+ if used.
+
+ @param table_list Table list pointing to table to prune.
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+bool partition_info::prune_partition_bitmaps(TABLE_LIST *table_list)
+{
+ List_iterator<String> partition_names_it(*(table_list->partition_names));
+ uint num_names= table_list->partition_names->elements;
+ uint i= 0;
+ DBUG_ENTER("partition_info::prune_partition_bitmaps");
+
+ if (num_names < 1)
+ DBUG_RETURN(true);
+
+ /*
+ TODO: When adding support for FK in partitioned tables, the referenced
+ table must probably lock all partitions for read, and also write depending
+ of ON DELETE/UPDATE.
+ */
+ bitmap_clear_all(&read_partitions);
+
+ /* No check for duplicate names or overlapping partitions/subpartitions. */
+
+ DBUG_PRINT("info", ("Searching through partition_name_hash"));
+ do
+ {
+ String *part_name_str= partition_names_it++;
+ if (add_named_partition(part_name_str->c_ptr(), part_name_str->length()))
+ DBUG_RETURN(true);
+ } while (++i < num_names);
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Set read/lock_partitions bitmap over non pruned partitions
+
+ @param table_list Possible TABLE_LIST which can contain
+ list of partition names to query
+
+ @return Operation status
+ @retval FALSE OK
+ @retval TRUE Failed to allocate memory for bitmap or list of partitions
+ did not match
+
+ @note OK to call multiple times without the need for free_bitmaps.
+*/
+
+bool partition_info::set_partition_bitmaps(TABLE_LIST *table_list)
+{
+ DBUG_ENTER("partition_info::set_partition_bitmaps");
+
+ DBUG_ASSERT(bitmaps_are_initialized);
+ DBUG_ASSERT(table);
+ is_pruning_completed= false;
+ if (!bitmaps_are_initialized)
+ DBUG_RETURN(TRUE);
+
+ if (table_list &&
+ table_list->partition_names &&
+ table_list->partition_names->elements)
+ {
+ if (table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION)
+ {
+ /*
+ Don't allow PARTITION () clause on a NDB tables yet.
+ TODO: Add partition name handling to NDB/partition_info.
+ which is currently ha_partition specific.
+ */
+ my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(true);
+ }
+ if (prune_partition_bitmaps(table_list))
+ DBUG_RETURN(TRUE);
+ }
+ else
+ {
+ bitmap_set_all(&read_partitions);
+ DBUG_PRINT("info", ("Set all partitions"));
+ }
+ bitmap_copy(&lock_partitions, &read_partitions);
+ DBUG_ASSERT(bitmap_get_first_set(&lock_partitions) != MY_BIT_NONE);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Checks if possible to do prune partitions on insert.
+
+ @param thd Thread context
+ @param duplic How to handle duplicates
+ @param update In case of ON DUPLICATE UPDATE, default function fields
+ @param update_fields In case of ON DUPLICATE UPDATE, which fields to update
+ @param fields Listed fields
+ @param empty_values True if values is empty (only defaults)
+ @param[out] prune_needs_default_values Set on return if copying of default
+ values is needed
+ @param[out] can_prune_partitions Enum showing if possible to prune
+ @param[inout] used_partitions If possible to prune the bitmap
+ is initialized and cleared
+
+ @return Operation status
+ @retval false Success
+ @retval true Failure
+*/
+
+bool partition_info::can_prune_insert(THD* thd,
+ enum_duplicates duplic,
+ COPY_INFO &update,
+ List<Item> &update_fields,
+ List<Item> &fields,
+ bool empty_values,
+ enum_can_prune *can_prune_partitions,
+ bool *prune_needs_default_values,
+ MY_BITMAP *used_partitions)
+{
+ uint32 *bitmap_buf;
+ uint bitmap_bytes;
+ uint num_partitions= 0;
+ *can_prune_partitions= PRUNE_NO;
+ DBUG_ASSERT(bitmaps_are_initialized);
+ DBUG_ENTER("partition_info::can_prune_insert");
+
+ if (table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION)
+ DBUG_RETURN(false); /* Should not insert prune NDB tables */
+
+ /*
+ If under LOCK TABLES pruning will skip start_stmt instead of external_lock
+ for unused partitions.
+
+ Cannot prune if there are BEFORE INSERT triggers that changes any
+ partitioning column, since they may change the row to be in another
+ partition.
+ */
+ if (table->triggers &&
+ table->triggers->has_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE) &&
+ table->triggers->is_fields_updated_in_trigger(&full_part_field_set,
+ TRG_EVENT_INSERT,
+ TRG_ACTION_BEFORE))
+ DBUG_RETURN(false);
+
+ if (table->found_next_number_field)
+ {
+ /*
+ If the field is used in the partitioning expression, we cannot prune.
+ TODO: If all rows have not null values and
+ is not 0 (with NO_AUTO_VALUE_ON_ZERO sql_mode), then pruning is possible!
+ */
+ if (bitmap_is_set(&full_part_field_set,
+ table->found_next_number_field->field_index))
+ DBUG_RETURN(false);
+ }
+
+ /*
+ If updating a field in the partitioning expression, we cannot prune.
+
+ Note: TIMESTAMP_AUTO_SET_ON_INSERT is handled by converting Item_null
+ to the start time of the statement. Which will be the same as in
+ write_row(). So pruning of TIMESTAMP DEFAULT CURRENT_TIME will work.
+ But TIMESTAMP_AUTO_SET_ON_UPDATE cannot be pruned if the timestamp
+ column is a part of any part/subpart expression.
+ */
+ if (duplic == DUP_UPDATE)
+ {
+ /*
+ TODO: add check for static update values, which can be pruned.
+ */
+ if (is_field_in_part_expr(update_fields))
+ DBUG_RETURN(false);
+
+ /*
+ Cannot prune if there are BEFORE UPDATE triggers that changes any
+ partitioning column, since they may change the row to be in another
+ partition.
+ */
+ if (table->triggers &&
+ table->triggers->has_triggers(TRG_EVENT_UPDATE,
+ TRG_ACTION_BEFORE) &&
+ table->triggers->is_fields_updated_in_trigger(&full_part_field_set,
+ TRG_EVENT_UPDATE,
+ TRG_ACTION_BEFORE))
+ {
+ DBUG_RETURN(false);
+ }
+ }
+
+ /*
+ If not all partitioning fields are given,
+ we also must set all non given partitioning fields
+ to get correct defaults.
+ TODO: If any gain, we could enhance this by only copy the needed default
+ fields by
+ 1) check which fields needs to be set.
+ 2) only copy those fields from the default record.
+ */
+ *prune_needs_default_values= false;
+ if (fields.elements)
+ {
+ if (!is_full_part_expr_in_fields(fields))
+ *prune_needs_default_values= true;
+ }
+ else if (empty_values)
+ {
+ *prune_needs_default_values= true; // like 'INSERT INTO t () VALUES ()'
+ }
+ else
+ {
+ /*
+ In case of INSERT INTO t VALUES (...) we must get values for
+ all fields in table from VALUES (...) part, so no defaults
+ are needed.
+ */
+ }
+
+ /* Pruning possible, have to initialize the used_partitions bitmap. */
+ num_partitions= lock_partitions.n_bits;
+ bitmap_bytes= bitmap_buffer_size(num_partitions);
+ if (!(bitmap_buf= (uint32*) thd->alloc(bitmap_bytes)))
+ {
+ mem_alloc_error(bitmap_bytes);
+ DBUG_RETURN(true);
+ }
+ /* Also clears all bits. */
+ if (my_bitmap_init(used_partitions, bitmap_buf, num_partitions, false))
+ {
+ /* purecov: begin deadcode */
+ /* Cannot happen, due to pre-alloc. */
+ mem_alloc_error(bitmap_bytes);
+ DBUG_RETURN(true);
+ /* purecov: end */
+ }
+ /*
+ If no partitioning field in set (e.g. defaults) check pruning only once.
+ */
+ if (fields.elements &&
+ !is_field_in_part_expr(fields))
+ *can_prune_partitions= PRUNE_DEFAULTS;
+ else
+ *can_prune_partitions= PRUNE_YES;
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Mark the partition, the record belongs to, as used.
+
+ @param fields Fields to set
+ @param values Values to use
+ @param info COPY_INFO used for default values handling
+ @param copy_default_values True if we should copy default values
+ @param used_partitions Bitmap to set
+
+ @returns Operational status
+ @retval false Success
+ @retval true Failure
+*/
+
+bool partition_info::set_used_partition(List<Item> &fields,
+ List<Item> &values,
+ COPY_INFO &info,
+ bool copy_default_values,
+ MY_BITMAP *used_partitions)
+{
+ THD *thd= table->in_use;
+ uint32 part_id;
+ longlong func_value;
+ Dummy_error_handler error_handler;
+ bool ret= true;
+ DBUG_ENTER("set_partition");
+ DBUG_ASSERT(thd);
+
+ /* Only allow checking of constant values */
+ List_iterator_fast<Item> v(values);
+ Item *item;
+ thd->push_internal_handler(&error_handler);
+ while ((item= v++))
+ {
+ if (!item->const_item())
+ goto err;
+ }
+
+ if (copy_default_values)
+ restore_record(table,s->default_values);
+
+ if (fields.elements || !values.elements)
+ {
+ if (fill_record(thd, table, fields, values, false))
+ goto err;
+ }
+ else
+ {
+ if (fill_record(thd, table, table->field, values, false, false))
+ goto err;
+ }
+ DBUG_ASSERT(!table->auto_increment_field_not_null);
+
+ /*
+ Evaluate DEFAULT functions like CURRENT_TIMESTAMP.
+ TODO: avoid setting non partitioning fields default value, to avoid
+ overhead. Not yet done, since mostly only one DEFAULT function per
+ table, or at least very few such columns.
+ */
+// if (info.function_defaults_apply_on_columns(&full_part_field_set))
+// info.set_function_defaults(table);
+
+ {
+ /*
+ This function is used in INSERT; 'values' are supplied by user,
+ or are default values, not values read from a table, so read_set is
+ irrelevant.
+ */
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set);
+ const int rc= get_partition_id(this, &part_id, &func_value);
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+ if (rc)
+ goto err;
+ }
+
+ DBUG_PRINT("info", ("Insert into partition %u", part_id));
+ bitmap_set_bit(used_partitions, part_id);
+ ret= false;
+
+err:
+ thd->pop_internal_handler();
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Create a memory area where default partition names are stored and fill it
+ up with the names.
+
+ SYNOPSIS
+ create_default_partition_names()
+ part_no Partition number for subparts
+ num_parts Number of partitions
+ start_no Starting partition number
+ subpart Is it subpartitions
+
+ RETURN VALUE
+ A pointer to the memory area of the default partition names
+
+ DESCRIPTION
+ A support routine for the partition code where default values are
+ generated.
+ The external routine needing this code is check_partition_info
+*/
+
+#define MAX_PART_NAME_SIZE 8
+
+char *partition_info::create_default_partition_names(uint part_no,
+ uint num_parts_arg,
+ uint start_no)
+{
+ char *ptr= (char*) sql_calloc(num_parts_arg*MAX_PART_NAME_SIZE);
+ char *move_ptr= ptr;
+ uint i= 0;
+ DBUG_ENTER("create_default_partition_names");
+
+ if (likely(ptr != 0))
+ {
+ do
+ {
+ sprintf(move_ptr, "p%u", (start_no + i));
+ move_ptr+= MAX_PART_NAME_SIZE;
+ } while (++i < num_parts_arg);
+ }
+ else
+ {
+ mem_alloc_error(num_parts_arg*MAX_PART_NAME_SIZE);
+ }
+ DBUG_RETURN(ptr);
+}
+
+
+/*
+ Generate a version string for partition expression
+ This function must be updated every time there is a possibility for
+ a new function of a higher version number than 5.5.0.
+
+ SYNOPSIS
+ set_show_version_string()
+ RETURN VALUES
+ None
+*/
+void partition_info::set_show_version_string(String *packet)
+{
+ int version= 0;
+ if (column_list)
+ packet->append(STRING_WITH_LEN("\n/*!50500"));
+ else
+ {
+ if (part_expr)
+ part_expr->walk(&Item::intro_version, 0, (uchar*)&version);
+ if (subpart_expr)
+ subpart_expr->walk(&Item::intro_version, 0, (uchar*)&version);
+ if (version == 0)
+ {
+ /* No new functions in partition function */
+ packet->append(STRING_WITH_LEN("\n/*!50100"));
+ }
+ else
+ {
+ char buf[65];
+ char *buf_ptr= longlong10_to_str((longlong)version, buf, 10);
+ packet->append(STRING_WITH_LEN("\n/*!"));
+ packet->append(buf, (size_t)(buf_ptr - buf));
+ }
+ }
+}
+
+/*
+ Create a unique name for the subpartition as part_name'sp''subpart_no'
+
+ SYNOPSIS
+ create_default_subpartition_name()
+ subpart_no Number of subpartition
+ part_name Name of partition
+ RETURN VALUES
+ >0 A reference to the created name string
+ 0 Memory allocation error
+*/
+
+char *partition_info::create_default_subpartition_name(uint subpart_no,
+ const char *part_name)
+{
+ uint size_alloc= strlen(part_name) + MAX_PART_NAME_SIZE;
+ char *ptr= (char*) sql_calloc(size_alloc);
+ DBUG_ENTER("create_default_subpartition_name");
+
+ if (likely(ptr != NULL))
+ {
+ my_snprintf(ptr, size_alloc, "%ssp%u", part_name, subpart_no);
+ }
+ else
+ {
+ mem_alloc_error(size_alloc);
+ }
+ DBUG_RETURN(ptr);
+}
+
+
+/*
+ Set up all the default partitions not set-up by the user in the SQL
+ statement. Also perform a number of checks that the user hasn't tried
+ to use default values where no defaults exists.
+
+ SYNOPSIS
+ set_up_default_partitions()
+ file A reference to a handler of the table
+ info Create info
+ start_no Starting partition number
+
+ RETURN VALUE
+ TRUE Error, attempted default values not possible
+ FALSE Ok, default partitions set-up
+
+ DESCRIPTION
+ The routine uses the underlying handler of the partitioning to define
+ the default number of partitions. For some handlers this requires
+ knowledge of the maximum number of rows to be stored in the table.
+ This routine only accepts HASH and KEY partitioning and thus there is
+ no subpartitioning if this routine is successful.
+ The external routine needing this code is check_partition_info
+*/
+
+bool partition_info::set_up_default_partitions(handler *file,
+ HA_CREATE_INFO *info,
+ uint start_no)
+{
+ uint i;
+ char *default_name;
+ bool result= TRUE;
+ DBUG_ENTER("partition_info::set_up_default_partitions");
+
+ if (part_type != HASH_PARTITION)
+ {
+ const char *error_string;
+ if (part_type == RANGE_PARTITION)
+ error_string= partition_keywords[PKW_RANGE].str;
+ else
+ error_string= partition_keywords[PKW_LIST].str;
+ my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), error_string);
+ goto end;
+ }
+
+ if ((num_parts == 0) &&
+ ((num_parts= file->get_default_no_partitions(info)) == 0))
+ {
+ my_error(ER_PARTITION_NOT_DEFINED_ERROR, MYF(0), "partitions");
+ goto end;
+ }
+
+ if (unlikely(num_parts > MAX_PARTITIONS))
+ {
+ my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
+ goto end;
+ }
+ if (unlikely((!(default_name= create_default_partition_names(0, num_parts,
+ start_no)))))
+ goto end;
+ i= 0;
+ do
+ {
+ partition_element *part_elem= new partition_element();
+ if (likely(part_elem != 0 &&
+ (!partitions.push_back(part_elem))))
+ {
+ part_elem->engine_type= default_engine_type;
+ part_elem->partition_name= default_name;
+ default_name+=MAX_PART_NAME_SIZE;
+ }
+ else
+ {
+ mem_alloc_error(sizeof(partition_element));
+ goto end;
+ }
+ } while (++i < num_parts);
+ result= FALSE;
+end:
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Set up all the default subpartitions not set-up by the user in the SQL
+ statement. Also perform a number of checks that the default partitioning
+ becomes an allowed partitioning scheme.
+
+ SYNOPSIS
+ set_up_default_subpartitions()
+ file A reference to a handler of the table
+ info Create info
+
+ RETURN VALUE
+ TRUE Error, attempted default values not possible
+ FALSE Ok, default partitions set-up
+
+ DESCRIPTION
+ The routine uses the underlying handler of the partitioning to define
+ the default number of partitions. For some handlers this requires
+ knowledge of the maximum number of rows to be stored in the table.
+ This routine is only called for RANGE or LIST partitioning and those
+ need to be specified so only subpartitions are specified.
+ The external routine needing this code is check_partition_info
+*/
+
+bool partition_info::set_up_default_subpartitions(handler *file,
+ HA_CREATE_INFO *info)
+{
+ uint i, j;
+ bool result= TRUE;
+ partition_element *part_elem;
+ List_iterator<partition_element> part_it(partitions);
+ DBUG_ENTER("partition_info::set_up_default_subpartitions");
+
+ if (num_subparts == 0)
+ num_subparts= file->get_default_no_partitions(info);
+ if (unlikely((num_parts * num_subparts) > MAX_PARTITIONS))
+ {
+ my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
+ goto end;
+ }
+ i= 0;
+ do
+ {
+ part_elem= part_it++;
+ j= 0;
+ do
+ {
+ partition_element *subpart_elem= new partition_element(part_elem);
+ if (likely(subpart_elem != 0 &&
+ (!part_elem->subpartitions.push_back(subpart_elem))))
+ {
+ char *ptr= create_default_subpartition_name(j,
+ part_elem->partition_name);
+ if (!ptr)
+ goto end;
+ subpart_elem->engine_type= default_engine_type;
+ subpart_elem->partition_name= ptr;
+ }
+ else
+ {
+ mem_alloc_error(sizeof(partition_element));
+ goto end;
+ }
+ } while (++j < num_subparts);
+ } while (++i < num_parts);
+ result= FALSE;
+end:
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Support routine for check_partition_info
+
+ SYNOPSIS
+ set_up_defaults_for_partitioning()
+ file A reference to a handler of the table
+ info Create info
+ start_no Starting partition number
+
+ RETURN VALUE
+ TRUE Error, attempted default values not possible
+ FALSE Ok, default partitions set-up
+
+ DESCRIPTION
+ Set up defaults for partition or subpartition (cannot set-up for both,
+ this will return an error.
+*/
+
+bool partition_info::set_up_defaults_for_partitioning(handler *file,
+ HA_CREATE_INFO *info,
+ uint start_no)
+{
+ DBUG_ENTER("partition_info::set_up_defaults_for_partitioning");
+
+ if (!default_partitions_setup)
+ {
+ default_partitions_setup= TRUE;
+ if (use_default_partitions)
+ DBUG_RETURN(set_up_default_partitions(file, info, start_no));
+ if (is_sub_partitioned() &&
+ use_default_subpartitions)
+ DBUG_RETURN(set_up_default_subpartitions(file, info));
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Support routine for check_partition_info
+
+ SYNOPSIS
+ find_duplicate_field
+ no parameters
+
+ RETURN VALUE
+ Erroneus field name Error, there are two fields with same name
+ NULL Ok, no field defined twice
+
+ DESCRIPTION
+ Check that the user haven't defined the same field twice in
+ key or column list partitioning.
+*/
+char* partition_info::find_duplicate_field()
+{
+ char *field_name_outer, *field_name_inner;
+ List_iterator<char> it_outer(part_field_list);
+ uint num_fields= part_field_list.elements;
+ uint i,j;
+ DBUG_ENTER("partition_info::find_duplicate_field");
+
+ for (i= 0; i < num_fields; i++)
+ {
+ field_name_outer= it_outer++;
+ List_iterator<char> it_inner(part_field_list);
+ for (j= 0; j < num_fields; j++)
+ {
+ field_name_inner= it_inner++;
+ if (i >= j)
+ continue;
+ if (!(my_strcasecmp(system_charset_info,
+ field_name_outer,
+ field_name_inner)))
+ {
+ DBUG_RETURN(field_name_outer);
+ }
+ }
+ }
+ DBUG_RETURN(NULL);
+}
+
+
+/**
+ @brief Get part_elem and part_id from partition name
+
+ @param partition_name Name of partition to search for.
+ @param file_name[out] Partition file name (part after table name,
+ #P#<part>[#SP#<subpart>]), skipped if NULL.
+ @param part_id[out] Id of found partition or NOT_A_PARTITION_ID.
+
+ @retval Pointer to part_elem of [sub]partition, if not found NULL
+
+ @note Since names of partitions AND subpartitions must be unique,
+ this function searches both partitions and subpartitions and if name of
+ a partition is given for a subpartitioned table, part_elem will be
+ the partition, but part_id will be NOT_A_PARTITION_ID and file_name not set.
+*/
+partition_element *partition_info::get_part_elem(const char *partition_name,
+ char *file_name,
+ uint32 *part_id)
+{
+ List_iterator<partition_element> part_it(partitions);
+ uint i= 0;
+ DBUG_ENTER("partition_info::get_part_elem");
+ DBUG_ASSERT(part_id);
+ *part_id= NOT_A_PARTITION_ID;
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (is_sub_partitioned())
+ {
+ List_iterator<partition_element> sub_part_it(part_elem->subpartitions);
+ uint j= 0;
+ do
+ {
+ partition_element *sub_part_elem= sub_part_it++;
+ if (!my_strcasecmp(system_charset_info,
+ sub_part_elem->partition_name, partition_name))
+ {
+ if (file_name)
+ create_subpartition_name(file_name, "",
+ part_elem->partition_name,
+ partition_name,
+ NORMAL_PART_NAME);
+ *part_id= j + (i * num_subparts);
+ DBUG_RETURN(sub_part_elem);
+ }
+ } while (++j < num_subparts);
+
+ /* Naming a partition (first level) on a subpartitioned table. */
+ if (!my_strcasecmp(system_charset_info,
+ part_elem->partition_name, partition_name))
+ DBUG_RETURN(part_elem);
+ }
+ else if (!my_strcasecmp(system_charset_info,
+ part_elem->partition_name, partition_name))
+ {
+ if (file_name)
+ create_partition_name(file_name, "", partition_name,
+ NORMAL_PART_NAME, TRUE);
+ *part_id= i;
+ DBUG_RETURN(part_elem);
+ }
+ } while (++i < num_parts);
+ DBUG_RETURN(NULL);
+}
+
+
+/**
+ Helper function to find_duplicate_name.
+*/
+
+static const char *get_part_name_from_elem(const char *name, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= strlen(name);
+ return name;
+}
+
+/*
+ A support function to check partition names for duplication in a
+ partitioned table
+
+ SYNOPSIS
+ find_duplicate_name()
+
+ RETURN VALUES
+ NULL Has unique part and subpart names
+ !NULL Pointer to duplicated name
+
+ DESCRIPTION
+ Checks that the list of names in the partitions doesn't contain any
+ duplicated names.
+*/
+
+char *partition_info::find_duplicate_name()
+{
+ HASH partition_names;
+ uint max_names;
+ const uchar *curr_name= NULL;
+ List_iterator<partition_element> parts_it(partitions);
+ partition_element *p_elem;
+
+ DBUG_ENTER("partition_info::find_duplicate_name");
+
+ /*
+ TODO: If table->s->ha_part_data->partition_name_hash.elements is > 0,
+ then we could just return NULL, but that has not been verified.
+ And this only happens when in ALTER TABLE with full table copy.
+ */
+
+ max_names= num_parts;
+ if (is_sub_partitioned())
+ max_names+= num_parts * num_subparts;
+ if (my_hash_init(&partition_names, system_charset_info, max_names, 0, 0,
+ (my_hash_get_key) get_part_name_from_elem, 0, HASH_UNIQUE))
+ {
+ DBUG_ASSERT(0);
+ curr_name= (const uchar*) "Internal failure";
+ goto error;
+ }
+ while ((p_elem= (parts_it++)))
+ {
+ curr_name= (const uchar*) p_elem->partition_name;
+ if (my_hash_insert(&partition_names, curr_name))
+ goto error;
+
+ if (!p_elem->subpartitions.is_empty())
+ {
+ List_iterator<partition_element> subparts_it(p_elem->subpartitions);
+ partition_element *subp_elem;
+ while ((subp_elem= (subparts_it++)))
+ {
+ curr_name= (const uchar*) subp_elem->partition_name;
+ if (my_hash_insert(&partition_names, curr_name))
+ goto error;
+ }
+ }
+ }
+ my_hash_free(&partition_names);
+ DBUG_RETURN(NULL);
+error:
+ my_hash_free(&partition_names);
+ DBUG_RETURN((char*) curr_name);
+}
+
+
+/*
+ A support function to check if a partition element's name is unique
+
+ SYNOPSIS
+ has_unique_name()
+ partition_element element to check
+
+ RETURN VALUES
+ TRUE Has unique name
+ FALSE Doesn't
+*/
+
+bool partition_info::has_unique_name(partition_element *element)
+{
+ DBUG_ENTER("partition_info::has_unique_name");
+
+ const char *name_to_check= element->partition_name;
+ List_iterator<partition_element> parts_it(partitions);
+
+ partition_element *el;
+ while ((el= (parts_it++)))
+ {
+ if (!(my_strcasecmp(system_charset_info, el->partition_name,
+ name_to_check)) && el != element)
+ DBUG_RETURN(FALSE);
+
+ if (!el->subpartitions.is_empty())
+ {
+ partition_element *sub_el;
+ List_iterator<partition_element> subparts_it(el->subpartitions);
+ while ((sub_el= (subparts_it++)))
+ {
+ if (!(my_strcasecmp(system_charset_info, sub_el->partition_name,
+ name_to_check)) && sub_el != element)
+ DBUG_RETURN(FALSE);
+ }
+ }
+ }
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Check that the partition/subpartition is setup to use the correct
+ storage engine
+ SYNOPSIS
+ check_engine_condition()
+ p_elem Partition element
+ table_engine_set Have user specified engine on table level
+ inout::engine_type Current engine used
+ inout::first Is it first partition
+ RETURN VALUE
+ TRUE Failed check
+ FALSE Ok
+ DESCRIPTION
+ Specified engine for table and partitions p0 and pn
+ Must be correct both on CREATE and ALTER commands
+ table p0 pn res (0 - OK, 1 - FAIL)
+ - - - 0
+ - - x 1
+ - x - 1
+ - x x 0
+ x - - 0
+ x - x 0
+ x x - 0
+ x x x 0
+ i.e:
+ - All subpartitions must use the same engine
+ AND it must be the same as the partition.
+ - All partitions must use the same engine
+ AND it must be the same as the table.
+ - if one does NOT specify an engine on the table level
+ then one must either NOT specify any engine on any
+ partition/subpartition OR for ALL partitions/subpartitions
+ Note:
+ When ALTER a table, the engines are already set for all levels
+ (table, all partitions and subpartitions). So if one want to
+ change the storage engine, one must specify it on the table level
+
+*/
+
+static bool check_engine_condition(partition_element *p_elem,
+ bool table_engine_set,
+ handlerton **engine_type,
+ bool *first)
+{
+ DBUG_ENTER("check_engine_condition");
+
+ DBUG_PRINT("enter", ("p_eng %s t_eng %s t_eng_set %u first %u state %u",
+ ha_resolve_storage_engine_name(p_elem->engine_type),
+ ha_resolve_storage_engine_name(*engine_type),
+ table_engine_set, *first, p_elem->part_state));
+ if (*first && !table_engine_set)
+ {
+ *engine_type= p_elem->engine_type;
+ DBUG_PRINT("info", ("setting table_engine = %s",
+ ha_resolve_storage_engine_name(*engine_type)));
+ }
+ *first= FALSE;
+ if ((table_engine_set &&
+ (p_elem->engine_type != (*engine_type) &&
+ p_elem->engine_type)) ||
+ (!table_engine_set &&
+ p_elem->engine_type != (*engine_type)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Check engine mix that it is correct
+ Current limitation is that all partitions and subpartitions
+ must use the same storage engine.
+ SYNOPSIS
+ check_engine_mix()
+ inout::engine_type Current engine used
+ table_engine_set Have user specified engine on table level
+ RETURN VALUE
+ TRUE Error, mixed engines
+ FALSE Ok, no mixed engines
+ DESCRIPTION
+ Current check verifies only that all handlers are the same.
+ Later this check will be more sophisticated.
+ (specified partition handler ) specified table handler
+ (NDB, NDB) NDB OK
+ (MYISAM, MYISAM) - OK
+ (MYISAM, -) - NOT OK
+ (MYISAM, -) MYISAM OK
+ (- , MYISAM) - NOT OK
+ (- , -) MYISAM OK
+ (-,-) - OK
+ (NDB, MYISAM) * NOT OK
+*/
+
+bool partition_info::check_engine_mix(handlerton *engine_type,
+ bool table_engine_set)
+{
+ handlerton *old_engine_type= engine_type;
+ bool first= TRUE;
+ uint n_parts= partitions.elements;
+ DBUG_ENTER("partition_info::check_engine_mix");
+ DBUG_PRINT("info", ("in: engine_type = %s, table_engine_set = %u",
+ ha_resolve_storage_engine_name(engine_type),
+ table_engine_set));
+ if (n_parts)
+ {
+ List_iterator<partition_element> part_it(partitions);
+ uint i= 0;
+ do
+ {
+ partition_element *part_elem= part_it++;
+ DBUG_PRINT("info", ("part = %d engine = %s table_engine_set %u",
+ i, ha_resolve_storage_engine_name(part_elem->engine_type),
+ table_engine_set));
+ if (is_sub_partitioned() &&
+ part_elem->subpartitions.elements)
+ {
+ uint n_subparts= part_elem->subpartitions.elements;
+ uint j= 0;
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ do
+ {
+ partition_element *sub_elem= sub_it++;
+ DBUG_PRINT("info", ("sub = %d engine = %s table_engie_set %u",
+ j, ha_resolve_storage_engine_name(sub_elem->engine_type),
+ table_engine_set));
+ if (check_engine_condition(sub_elem, table_engine_set,
+ &engine_type, &first))
+ goto error;
+ } while (++j < n_subparts);
+ /* ensure that the partition also has correct engine */
+ if (check_engine_condition(part_elem, table_engine_set,
+ &engine_type, &first))
+ goto error;
+ }
+ else if (check_engine_condition(part_elem, table_engine_set,
+ &engine_type, &first))
+ goto error;
+ } while (++i < n_parts);
+ }
+ DBUG_PRINT("info", ("engine_type = %s",
+ ha_resolve_storage_engine_name(engine_type)));
+ if (!engine_type)
+ engine_type= old_engine_type;
+ if (engine_type->flags & HTON_NO_PARTITION)
+ {
+ my_error(ER_PARTITION_MERGE_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_PRINT("info", ("out: engine_type = %s",
+ ha_resolve_storage_engine_name(engine_type)));
+ DBUG_ASSERT(engine_type != partition_hton);
+ DBUG_RETURN(FALSE);
+error:
+ /*
+ Mixed engines not yet supported but when supported it will need
+ the partition handler
+ */
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ This routine allocates an array for all range constants to achieve a fast
+ check what partition a certain value belongs to. At the same time it does
+ also check that the range constants are defined in increasing order and
+ that the expressions are constant integer expressions.
+
+ SYNOPSIS
+ check_range_constants()
+ thd Thread object
+
+ RETURN VALUE
+ TRUE An error occurred during creation of range constants
+ FALSE Successful creation of range constant mapping
+
+ DESCRIPTION
+ This routine is called from check_partition_info to get a quick error
+ before we came too far into the CREATE TABLE process. It is also called
+ from fix_partition_func every time we open the .frm file. It is only
+ called for RANGE PARTITIONed tables.
+*/
+
+bool partition_info::check_range_constants(THD *thd)
+{
+ partition_element* part_def;
+ bool first= TRUE;
+ uint i;
+ List_iterator<partition_element> it(partitions);
+ int result= TRUE;
+ DBUG_ENTER("partition_info::check_range_constants");
+ DBUG_PRINT("enter", ("RANGE with %d parts, column_list = %u", num_parts,
+ column_list));
+
+ if (column_list)
+ {
+ part_column_list_val *loc_range_col_array;
+ part_column_list_val *UNINIT_VAR(current_largest_col_val);
+ uint num_column_values= part_field_list.elements;
+ uint size_entries= sizeof(part_column_list_val) * num_column_values;
+ range_col_array= (part_column_list_val*)sql_calloc(num_parts *
+ size_entries);
+ if (unlikely(range_col_array == NULL))
+ {
+ mem_alloc_error(num_parts * size_entries);
+ goto end;
+ }
+ loc_range_col_array= range_col_array;
+ i= 0;
+ do
+ {
+ part_def= it++;
+ {
+ List_iterator<part_elem_value> list_val_it(part_def->list_val_list);
+ part_elem_value *range_val= list_val_it++;
+ part_column_list_val *col_val= range_val->col_val_array;
+
+ if (fix_column_value_functions(thd, range_val, i))
+ goto end;
+ memcpy(loc_range_col_array, (const void*)col_val, size_entries);
+ loc_range_col_array+= num_column_values;
+ if (!first)
+ {
+ if (compare_column_values((const void*)current_largest_col_val,
+ (const void*)col_val) >= 0)
+ goto range_not_increasing_error;
+ }
+ current_largest_col_val= col_val;
+ }
+ first= FALSE;
+ } while (++i < num_parts);
+ }
+ else
+ {
+ longlong UNINIT_VAR(current_largest);
+ longlong part_range_value;
+ bool signed_flag= !part_expr->unsigned_flag;
+
+ range_int_array= (longlong*)sql_alloc(num_parts * sizeof(longlong));
+ if (unlikely(range_int_array == NULL))
+ {
+ mem_alloc_error(num_parts * sizeof(longlong));
+ goto end;
+ }
+ i= 0;
+ do
+ {
+ part_def= it++;
+ if ((i != (num_parts - 1)) || !defined_max_value)
+ {
+ part_range_value= part_def->range_value;
+ if (!signed_flag)
+ part_range_value-= 0x8000000000000000ULL;
+ }
+ else
+ part_range_value= LONGLONG_MAX;
+
+ if (!first)
+ {
+ if (unlikely(current_largest > part_range_value) ||
+ (unlikely(current_largest == part_range_value) &&
+ (part_range_value < LONGLONG_MAX ||
+ i != (num_parts - 1) ||
+ !defined_max_value)))
+ goto range_not_increasing_error;
+ }
+ range_int_array[i]= part_range_value;
+ current_largest= part_range_value;
+ first= FALSE;
+ } while (++i < num_parts);
+ }
+ result= FALSE;
+end:
+ DBUG_RETURN(result);
+
+range_not_increasing_error:
+ my_error(ER_RANGE_NOT_INCREASING_ERROR, MYF(0));
+ goto end;
+}
+
+
+/*
+ Support routines for check_list_constants used by qsort to sort the
+ constant list expressions. One routine for integers and one for
+ column lists.
+
+ SYNOPSIS
+ list_part_cmp()
+ a First list constant to compare with
+ b Second list constant to compare with
+
+ RETURN VALUE
+ +1 a > b
+ 0 a == b
+ -1 a < b
+*/
+
+extern "C"
+int partition_info_list_part_cmp(const void* a, const void* b)
+{
+ longlong a1= ((LIST_PART_ENTRY*)a)->list_value;
+ longlong b1= ((LIST_PART_ENTRY*)b)->list_value;
+ if (a1 < b1)
+ return -1;
+ else if (a1 > b1)
+ return +1;
+ else
+ return 0;
+}
+
+
+int partition_info::list_part_cmp(const void* a, const void* b)
+{
+ return partition_info_list_part_cmp(a, b);
+}
+
+
+/*
+ Compare two lists of column values in RANGE/LIST partitioning
+ SYNOPSIS
+ compare_column_values()
+ first First column list argument
+ second Second column list argument
+ RETURN VALUES
+ 0 Equal
+ -1 First argument is smaller
+ +1 First argument is larger
+*/
+
+extern "C"
+int partition_info_compare_column_values(const void *first_arg,
+ const void *second_arg)
+{
+ const part_column_list_val *first= (part_column_list_val*)first_arg;
+ const part_column_list_val *second= (part_column_list_val*)second_arg;
+ partition_info *part_info= first->part_info;
+ Field **field;
+
+ for (field= part_info->part_field_array; *field;
+ field++, first++, second++)
+ {
+ if (first->max_value || second->max_value)
+ {
+ if (first->max_value && second->max_value)
+ return 0;
+ if (second->max_value)
+ return -1;
+ else
+ return +1;
+ }
+ if (first->null_value || second->null_value)
+ {
+ if (first->null_value && second->null_value)
+ continue;
+ if (second->null_value)
+ return +1;
+ else
+ return -1;
+ }
+ int res= (*field)->cmp((const uchar*)first->column_value,
+ (const uchar*)second->column_value);
+ if (res)
+ return res;
+ }
+ return 0;
+}
+
+
+int partition_info::compare_column_values(const void *first_arg,
+ const void *second_arg)
+{
+ return partition_info_compare_column_values(first_arg, second_arg);
+}
+
+
+/*
+ This routine allocates an array for all list constants to achieve a fast
+ check what partition a certain value belongs to. At the same time it does
+ also check that there are no duplicates among the list constants and that
+ that the list expressions are constant integer expressions.
+
+ SYNOPSIS
+ check_list_constants()
+ thd Thread object
+
+ RETURN VALUE
+ TRUE An error occurred during creation of list constants
+ FALSE Successful creation of list constant mapping
+
+ DESCRIPTION
+ This routine is called from check_partition_info to get a quick error
+ before we came too far into the CREATE TABLE process. It is also called
+ from fix_partition_func every time we open the .frm file. It is only
+ called for LIST PARTITIONed tables.
+*/
+
+bool partition_info::check_list_constants(THD *thd)
+{
+ uint i, size_entries, num_column_values;
+ uint list_index= 0;
+ part_elem_value *list_value;
+ bool result= TRUE;
+ longlong type_add, calc_value;
+ void *curr_value;
+ void *UNINIT_VAR(prev_value);
+ partition_element* part_def;
+ bool found_null= FALSE;
+ qsort_cmp compare_func;
+ void *ptr;
+ List_iterator<partition_element> list_func_it(partitions);
+ DBUG_ENTER("partition_info::check_list_constants");
+
+ num_list_values= 0;
+ /*
+ We begin by calculating the number of list values that have been
+ defined in the first step.
+
+ We use this number to allocate a properly sized array of structs
+ to keep the partition id and the value to use in that partition.
+ In the second traversal we assign them values in the struct array.
+
+ Finally we sort the array of structs in order of values to enable
+ a quick binary search for the proper value to discover the
+ partition id.
+ After sorting the array we check that there are no duplicates in the
+ list.
+ */
+
+ i= 0;
+ do
+ {
+ part_def= list_func_it++;
+ if (part_def->has_null_value)
+ {
+ if (found_null)
+ {
+ my_error(ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR, MYF(0));
+ goto end;
+ }
+ has_null_value= TRUE;
+ has_null_part_id= i;
+ found_null= TRUE;
+ }
+ List_iterator<part_elem_value> list_val_it1(part_def->list_val_list);
+ while (list_val_it1++)
+ num_list_values++;
+ } while (++i < num_parts);
+ list_func_it.rewind();
+ num_column_values= part_field_list.elements;
+ size_entries= column_list ?
+ (num_column_values * sizeof(part_column_list_val)) :
+ sizeof(LIST_PART_ENTRY);
+ ptr= sql_calloc((num_list_values+1) * size_entries);
+ if (unlikely(ptr == NULL))
+ {
+ mem_alloc_error(num_list_values * size_entries);
+ goto end;
+ }
+ if (column_list)
+ {
+ part_column_list_val *loc_list_col_array;
+ loc_list_col_array= (part_column_list_val*)ptr;
+ list_col_array= (part_column_list_val*)ptr;
+ compare_func= partition_info_compare_column_values;
+ i= 0;
+ do
+ {
+ part_def= list_func_it++;
+ List_iterator<part_elem_value> list_val_it2(part_def->list_val_list);
+ while ((list_value= list_val_it2++))
+ {
+ part_column_list_val *col_val= list_value->col_val_array;
+ if (unlikely(fix_column_value_functions(thd, list_value, i)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ memcpy(loc_list_col_array, (const void*)col_val, size_entries);
+ loc_list_col_array+= num_column_values;
+ }
+ } while (++i < num_parts);
+ }
+ else
+ {
+ compare_func= partition_info_list_part_cmp;
+ list_array= (LIST_PART_ENTRY*)ptr;
+ i= 0;
+ /*
+ Fix to be able to reuse signed sort functions also for unsigned
+ partition functions.
+ */
+ type_add= (longlong)(part_expr->unsigned_flag ?
+ 0x8000000000000000ULL :
+ 0ULL);
+
+ do
+ {
+ part_def= list_func_it++;
+ List_iterator<part_elem_value> list_val_it2(part_def->list_val_list);
+ while ((list_value= list_val_it2++))
+ {
+ calc_value= list_value->value - type_add;
+ list_array[list_index].list_value= calc_value;
+ list_array[list_index++].partition_id= i;
+ }
+ } while (++i < num_parts);
+ }
+ DBUG_ASSERT(fixed);
+ if (num_list_values)
+ {
+ bool first= TRUE;
+ /*
+ list_array and list_col_array are unions, so this works for both
+ variants of LIST partitioning.
+ */
+ my_qsort((void*)list_array, num_list_values, size_entries,
+ compare_func);
+
+ i= 0;
+ do
+ {
+ DBUG_ASSERT(i < num_list_values);
+ curr_value= column_list ? (void*)&list_col_array[num_column_values * i] :
+ (void*)&list_array[i];
+ if (likely(first || compare_func(curr_value, prev_value)))
+ {
+ prev_value= curr_value;
+ first= FALSE;
+ }
+ else
+ {
+ my_error(ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR, MYF(0));
+ goto end;
+ }
+ } while (++i < num_list_values);
+ }
+ result= FALSE;
+end:
+ DBUG_RETURN(result);
+}
+
+/**
+ Check if we allow DATA/INDEX DIRECTORY, if not warn and set them to NULL.
+
+ @param thd THD also containing sql_mode (looks from MODE_NO_DIR_IN_CREATE).
+ @param part_elem partition_element to check.
+*/
+static void warn_if_dir_in_part_elem(THD *thd, partition_element *part_elem)
+{
+ if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)
+ {
+ if (part_elem->data_file_name)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
+ "DATA DIRECTORY");
+ if (part_elem->index_file_name)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
+ "INDEX DIRECTORY");
+ part_elem->data_file_name= part_elem->index_file_name= NULL;
+ }
+}
+
+
+/*
+ This code is used early in the CREATE TABLE and ALTER TABLE process.
+
+ SYNOPSIS
+ check_partition_info()
+ thd Thread object
+ eng_type Return value for used engine in partitions
+ file A reference to a handler of the table
+ info Create info
+ add_or_reorg_part Is it ALTER TABLE ADD/REORGANIZE command
+
+ RETURN VALUE
+ TRUE Error, something went wrong
+ FALSE Ok, full partition data structures are now generated
+
+ DESCRIPTION
+ We will check that the partition info requested is possible to set-up in
+ this version. This routine is an extension of the parser one could say.
+ If defaults were used we will generate default data structures for all
+ partitions.
+
+*/
+
+bool partition_info::check_partition_info(THD *thd, handlerton **eng_type,
+ handler *file, HA_CREATE_INFO *info,
+ bool add_or_reorg_part)
+{
+ handlerton *table_engine= default_engine_type;
+ uint i, tot_partitions;
+ bool result= TRUE, table_engine_set;
+ char *same_name;
+ DBUG_ENTER("partition_info::check_partition_info");
+ DBUG_ASSERT(default_engine_type != partition_hton);
+
+ DBUG_PRINT("info", ("default table_engine = %s",
+ ha_resolve_storage_engine_name(table_engine)));
+ if (!add_or_reorg_part)
+ {
+ int err= 0;
+
+ if (!list_of_part_fields)
+ {
+ DBUG_ASSERT(part_expr);
+ err= part_expr->walk(&Item::check_partition_func_processor, 0,
+ NULL);
+ if (!err && is_sub_partitioned() && !list_of_subpart_fields)
+ err= subpart_expr->walk(&Item::check_partition_func_processor, 0,
+ NULL);
+ }
+ if (err)
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ goto end;
+ }
+ if (thd->lex->sql_command == SQLCOM_CREATE_TABLE &&
+ fix_parser_data(thd))
+ goto end;
+ }
+ if (unlikely(!is_sub_partitioned() &&
+ !(use_default_subpartitions && use_default_num_subpartitions)))
+ {
+ my_error(ER_SUBPARTITION_ERROR, MYF(0));
+ goto end;
+ }
+ if (unlikely(is_sub_partitioned() &&
+ (!(part_type == RANGE_PARTITION ||
+ part_type == LIST_PARTITION))))
+ {
+ /* Only RANGE and LIST partitioning can be subpartitioned */
+ my_error(ER_SUBPARTITION_ERROR, MYF(0));
+ goto end;
+ }
+ if (unlikely(set_up_defaults_for_partitioning(file, info, (uint)0)))
+ goto end;
+ if (!(tot_partitions= get_tot_partitions()))
+ {
+ my_error(ER_PARTITION_NOT_DEFINED_ERROR, MYF(0), "partitions");
+ goto end;
+ }
+ if (unlikely(tot_partitions > MAX_PARTITIONS))
+ {
+ my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
+ goto end;
+ }
+ /*
+ if NOT specified ENGINE = <engine>:
+ If Create, always use create_info->db_type
+ else, use previous tables db_type
+ either ALL or NONE partition should be set to
+ default_engine_type when not table_engine_set
+ Note: after a table is created its storage engines for
+ the table and all partitions/subpartitions are set.
+ So when ALTER it is already set on table level
+ */
+ if (info && info->used_fields & HA_CREATE_USED_ENGINE)
+ {
+ table_engine_set= TRUE;
+ table_engine= info->db_type;
+ /* if partition_hton, use thd->lex->create_info */
+ if (table_engine == partition_hton)
+ table_engine= thd->lex->create_info.db_type;
+ DBUG_ASSERT(table_engine != partition_hton);
+ DBUG_PRINT("info", ("Using table_engine = %s",
+ ha_resolve_storage_engine_name(table_engine)));
+ }
+ else
+ {
+ table_engine_set= FALSE;
+ if (thd->lex->sql_command != SQLCOM_CREATE_TABLE)
+ {
+ table_engine_set= TRUE;
+ DBUG_PRINT("info", ("No create, table_engine = %s",
+ ha_resolve_storage_engine_name(table_engine)));
+ DBUG_ASSERT(table_engine && table_engine != partition_hton);
+ }
+ }
+
+ if (part_field_list.elements > 0 &&
+ (same_name= find_duplicate_field()))
+ {
+ my_error(ER_SAME_NAME_PARTITION_FIELD, MYF(0), same_name);
+ goto end;
+ }
+ if ((same_name= find_duplicate_name()))
+ {
+ my_error(ER_SAME_NAME_PARTITION, MYF(0), same_name);
+ goto end;
+ }
+ i= 0;
+ {
+ List_iterator<partition_element> part_it(partitions);
+ uint num_parts_not_set= 0;
+ uint prev_num_subparts_not_set= num_subparts + 1;
+ do
+ {
+ partition_element *part_elem= part_it++;
+ warn_if_dir_in_part_elem(thd, part_elem);
+ if (!is_sub_partitioned())
+ {
+ if (part_elem->engine_type == NULL)
+ {
+ num_parts_not_set++;
+ part_elem->engine_type= default_engine_type;
+ }
+ if (check_table_name(part_elem->partition_name,
+ strlen(part_elem->partition_name), FALSE))
+ {
+ my_error(ER_WRONG_PARTITION_NAME, MYF(0));
+ goto end;
+ }
+ DBUG_PRINT("info", ("part = %d engine = %s",
+ i, ha_resolve_storage_engine_name(part_elem->engine_type)));
+ }
+ else
+ {
+ uint j= 0;
+ uint num_subparts_not_set= 0;
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ partition_element *sub_elem;
+ do
+ {
+ sub_elem= sub_it++;
+ warn_if_dir_in_part_elem(thd, sub_elem);
+ if (check_table_name(sub_elem->partition_name,
+ strlen(sub_elem->partition_name), FALSE))
+ {
+ my_error(ER_WRONG_PARTITION_NAME, MYF(0));
+ goto end;
+ }
+ if (sub_elem->engine_type == NULL)
+ {
+ if (part_elem->engine_type != NULL)
+ sub_elem->engine_type= part_elem->engine_type;
+ else
+ {
+ sub_elem->engine_type= default_engine_type;
+ num_subparts_not_set++;
+ }
+ }
+ DBUG_PRINT("info", ("part = %d sub = %d engine = %s", i, j,
+ ha_resolve_storage_engine_name(sub_elem->engine_type)));
+ } while (++j < num_subparts);
+
+ if (prev_num_subparts_not_set == (num_subparts + 1) &&
+ (num_subparts_not_set == 0 ||
+ num_subparts_not_set == num_subparts))
+ prev_num_subparts_not_set= num_subparts_not_set;
+
+ if (!table_engine_set &&
+ prev_num_subparts_not_set != num_subparts_not_set)
+ {
+ DBUG_PRINT("info", ("num_subparts_not_set = %u num_subparts = %u",
+ num_subparts_not_set, num_subparts));
+ my_error(ER_MIX_HANDLER_ERROR, MYF(0));
+ goto end;
+ }
+
+ if (part_elem->engine_type == NULL)
+ {
+ if (num_subparts_not_set == 0)
+ part_elem->engine_type= sub_elem->engine_type;
+ else
+ {
+ num_parts_not_set++;
+ part_elem->engine_type= default_engine_type;
+ }
+ }
+ }
+ } while (++i < num_parts);
+ if (!table_engine_set &&
+ num_parts_not_set != 0 &&
+ num_parts_not_set != num_parts)
+ {
+ DBUG_PRINT("info", ("num_parts_not_set = %u num_parts = %u",
+ num_parts_not_set, num_subparts));
+ my_error(ER_MIX_HANDLER_ERROR, MYF(0));
+ goto end;
+ }
+ }
+ if (unlikely(check_engine_mix(table_engine, table_engine_set)))
+ {
+ my_error(ER_MIX_HANDLER_ERROR, MYF(0));
+ goto end;
+ }
+
+ DBUG_ASSERT(table_engine != partition_hton &&
+ default_engine_type == table_engine);
+ if (eng_type)
+ *eng_type= table_engine;
+
+
+ /*
+ We need to check all constant expressions that they are of the correct
+ type and that they are increasing for ranges and not overlapping for
+ list constants.
+ */
+
+ if (add_or_reorg_part)
+ {
+ if (unlikely((part_type == RANGE_PARTITION &&
+ check_range_constants(thd)) ||
+ (part_type == LIST_PARTITION &&
+ check_list_constants(thd))))
+ goto end;
+ }
+ result= FALSE;
+end:
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Print error for no partition found
+
+ SYNOPSIS
+ print_no_partition_found()
+ table Table object
+
+ RETURN VALUES
+*/
+
+void partition_info::print_no_partition_found(TABLE *table_arg, myf errflag)
+{
+ char buf[100];
+ char *buf_ptr= (char*)&buf;
+ TABLE_LIST table_list;
+
+ bzero(&table_list, sizeof(table_list));
+ table_list.db= table_arg->s->db.str;
+ table_list.table_name= table_arg->s->table_name.str;
+
+ if (check_single_table_access(current_thd,
+ SELECT_ACL, &table_list, TRUE))
+ {
+ my_message(ER_NO_PARTITION_FOR_GIVEN_VALUE,
+ ER(ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT), errflag);
+ }
+ else
+ {
+ if (column_list)
+ buf_ptr= (char*)"from column_list";
+ else
+ {
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table_arg, table_arg->read_set);
+ if (part_expr->null_value)
+ buf_ptr= (char*)"NULL";
+ else
+ longlong10_to_str(err_value, buf,
+ part_expr->unsigned_flag ? 10 : -10);
+ dbug_tmp_restore_column_map(table_arg->read_set, old_map);
+ }
+ my_error(ER_NO_PARTITION_FOR_GIVEN_VALUE, errflag, buf_ptr);
+ }
+}
+
+
+/*
+ Set fields related to partition expression
+ SYNOPSIS
+ set_part_expr()
+ start_token Start of partition function string
+ item_ptr Pointer to item tree
+ end_token End of partition function string
+ is_subpart Subpartition indicator
+ RETURN VALUES
+ TRUE Memory allocation error
+ FALSE Success
+*/
+
+bool partition_info::set_part_expr(char *start_token, Item *item_ptr,
+ char *end_token, bool is_subpart)
+{
+ uint expr_len= end_token - start_token;
+ char *func_string= (char*) sql_memdup(start_token, expr_len);
+
+ if (!func_string)
+ {
+ mem_alloc_error(expr_len);
+ return TRUE;
+ }
+ if (is_subpart)
+ {
+ list_of_subpart_fields= FALSE;
+ subpart_expr= item_ptr;
+ subpart_func_string= func_string;
+ subpart_func_len= expr_len;
+ }
+ else
+ {
+ list_of_part_fields= FALSE;
+ part_expr= item_ptr;
+ part_func_string= func_string;
+ part_func_len= expr_len;
+ }
+ return FALSE;
+}
+
+
+/*
+ Check that partition fields and subpartition fields are not too long
+
+ SYNOPSIS
+ check_partition_field_length()
+
+ RETURN VALUES
+ TRUE Total length was too big
+ FALSE Length is ok
+*/
+
+bool partition_info::check_partition_field_length()
+{
+ uint store_length= 0;
+ uint i;
+ DBUG_ENTER("partition_info::check_partition_field_length");
+
+ for (i= 0; i < num_part_fields; i++)
+ store_length+= get_partition_field_store_length(part_field_array[i]);
+ if (store_length > MAX_KEY_LENGTH)
+ DBUG_RETURN(TRUE);
+ store_length= 0;
+ for (i= 0; i < num_subpart_fields; i++)
+ store_length+= get_partition_field_store_length(subpart_field_array[i]);
+ if (store_length > MAX_KEY_LENGTH)
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Set up buffers and arrays for fields requiring preparation
+ SYNOPSIS
+ set_up_charset_field_preps()
+
+ RETURN VALUES
+ TRUE Memory Allocation error
+ FALSE Success
+
+ DESCRIPTION
+ Set up arrays and buffers for fields that require special care for
+ calculation of partition id. This is used for string fields with
+ variable length or string fields with fixed length that isn't using
+ the binary collation.
+*/
+
+bool partition_info::set_up_charset_field_preps()
+{
+ Field *field, **ptr;
+ uchar **char_ptrs;
+ unsigned i;
+ size_t size;
+ uint tot_fields= 0;
+ uint tot_part_fields= 0;
+ uint tot_subpart_fields= 0;
+ DBUG_ENTER("set_up_charset_field_preps");
+
+ if (!(part_type == HASH_PARTITION &&
+ list_of_part_fields) &&
+ check_part_func_fields(part_field_array, FALSE))
+ {
+ ptr= part_field_array;
+ /* Set up arrays and buffers for those fields */
+ while ((field= *(ptr++)))
+ {
+ if (field_is_partition_charset(field))
+ {
+ tot_part_fields++;
+ tot_fields++;
+ }
+ }
+ size= tot_part_fields * sizeof(char*);
+ if (!(char_ptrs= (uchar**)sql_calloc(size)))
+ goto error;
+ part_field_buffers= char_ptrs;
+ if (!(char_ptrs= (uchar**)sql_calloc(size)))
+ goto error;
+ restore_part_field_ptrs= char_ptrs;
+ size= (tot_part_fields + 1) * sizeof(Field*);
+ if (!(char_ptrs= (uchar**)sql_alloc(size)))
+ goto error;
+ part_charset_field_array= (Field**)char_ptrs;
+ ptr= part_field_array;
+ i= 0;
+ while ((field= *(ptr++)))
+ {
+ if (field_is_partition_charset(field))
+ {
+ uchar *field_buf;
+ size= field->pack_length();
+ if (!(field_buf= (uchar*) sql_calloc(size)))
+ goto error;
+ part_charset_field_array[i]= field;
+ part_field_buffers[i++]= field_buf;
+ }
+ }
+ part_charset_field_array[i]= NULL;
+ }
+ if (is_sub_partitioned() && !list_of_subpart_fields &&
+ check_part_func_fields(subpart_field_array, FALSE))
+ {
+ /* Set up arrays and buffers for those fields */
+ ptr= subpart_field_array;
+ while ((field= *(ptr++)))
+ {
+ if (field_is_partition_charset(field))
+ {
+ tot_subpart_fields++;
+ tot_fields++;
+ }
+ }
+ size= tot_subpart_fields * sizeof(char*);
+ if (!(char_ptrs= (uchar**) sql_calloc(size)))
+ goto error;
+ subpart_field_buffers= char_ptrs;
+ if (!(char_ptrs= (uchar**) sql_calloc(size)))
+ goto error;
+ restore_subpart_field_ptrs= char_ptrs;
+ size= (tot_subpart_fields + 1) * sizeof(Field*);
+ if (!(char_ptrs= (uchar**) sql_alloc(size)))
+ goto error;
+ subpart_charset_field_array= (Field**)char_ptrs;
+ ptr= subpart_field_array;
+ i= 0;
+ while ((field= *(ptr++)))
+ {
+ uchar *field_buf;
+ LINT_INIT(field_buf);
+
+ if (!field_is_partition_charset(field))
+ continue;
+ size= field->pack_length();
+ if (!(field_buf= (uchar*) sql_calloc(size)))
+ goto error;
+ subpart_charset_field_array[i]= field;
+ subpart_field_buffers[i++]= field_buf;
+ }
+ subpart_charset_field_array[i]= NULL;
+ }
+ DBUG_RETURN(FALSE);
+error:
+ mem_alloc_error(size);
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Check if path does not contain mysql data home directory
+ for partition elements with data directory and index directory
+
+ SYNOPSIS
+ check_partition_dirs()
+ part_info partition_info struct
+
+ RETURN VALUES
+ 0 ok
+ 1 error
+*/
+
+bool check_partition_dirs(partition_info *part_info)
+{
+ if (!part_info)
+ return 0;
+
+ partition_element *part_elem;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ while ((part_elem= part_it++))
+ {
+ if (part_elem->subpartitions.elements)
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ partition_element *subpart_elem;
+ while ((subpart_elem= sub_it++))
+ {
+ if (error_if_data_home_dir(subpart_elem->data_file_name,
+ "DATA DIRECTORY") ||
+ error_if_data_home_dir(subpart_elem->index_file_name,
+ "INDEX DIRECTORY"))
+ return 1;
+ }
+ }
+ else
+ {
+ if (error_if_data_home_dir(part_elem->data_file_name, "DATA DIRECTORY") ||
+ error_if_data_home_dir(part_elem->index_file_name, "INDEX DIRECTORY"))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ Check what kind of error to report
+
+ @param use_subpart_expr Use the subpart_expr instead of part_expr
+ @param part_str Name of partition to report error (or NULL)
+*/
+void partition_info::report_part_expr_error(bool use_subpart_expr)
+{
+ Item *expr= part_expr;
+ DBUG_ENTER("partition_info::report_part_expr_error");
+ if (use_subpart_expr)
+ expr= subpart_expr;
+
+ if (expr->type() == Item::FIELD_ITEM)
+ {
+ partition_type type= part_type;
+ bool list_of_fields= list_of_part_fields;
+ Item_field *item_field= (Item_field*) expr;
+ /*
+ The expression consists of a single field.
+ It must be of integer type unless KEY or COLUMNS partitioning.
+ */
+ if (use_subpart_expr)
+ {
+ type= subpart_type;
+ list_of_fields= list_of_subpart_fields;
+ }
+ if (!column_list &&
+ item_field->field &&
+ item_field->field->result_type() != INT_RESULT &&
+ !(type == HASH_PARTITION && list_of_fields))
+ {
+ my_error(ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD, MYF(0),
+ item_field->name);
+ DBUG_VOID_RETURN;
+ }
+ }
+ if (use_subpart_expr)
+ my_error(ER_PARTITION_FUNC_NOT_ALLOWED_ERROR, MYF(0), "SUBPARTITION");
+ else
+ my_error(ER_PARTITION_FUNC_NOT_ALLOWED_ERROR, MYF(0), "PARTITION");
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Check if fields are in the partitioning expression.
+
+ @param fields List of Items (fields)
+
+ @return True if any field in the fields list is used by a partitioning expr.
+ @retval true At least one field in the field list is found.
+ @retval false No field is within any partitioning expression.
+*/
+
+bool partition_info::is_field_in_part_expr(List<Item> &fields)
+{
+ List_iterator<Item> it(fields);
+ Item *item;
+ Item_field *field;
+ DBUG_ENTER("is_fields_in_part_expr");
+ while ((item= it++))
+ {
+ field= item->field_for_view_update();
+ DBUG_ASSERT(field->field->table == table);
+ if (bitmap_is_set(&full_part_field_set, field->field->field_index))
+ DBUG_RETURN(true);
+ }
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Check if all partitioning fields are included.
+*/
+
+bool partition_info::is_full_part_expr_in_fields(List<Item> &fields)
+{
+ Field **part_field= full_part_field_array;
+ DBUG_ASSERT(*part_field);
+ DBUG_ENTER("is_full_part_expr_in_fields");
+ /*
+ It is very seldom many fields in full_part_field_array, so it is OK
+ to loop over all of them instead of creating a bitmap fields argument
+ to compare with.
+ */
+ do
+ {
+ List_iterator<Item> it(fields);
+ Item *item;
+ Item_field *field;
+ bool found= false;
+
+ while ((item= it++))
+ {
+ field= item->field_for_view_update();
+ DBUG_ASSERT(field->field->table == table);
+ if (*part_field == field->field)
+ {
+ found= true;
+ break;
+ }
+ }
+ if (!found)
+ DBUG_RETURN(false);
+ } while (*(++part_field));
+ DBUG_RETURN(true);
+}
+
+
+/*
+ Create a new column value in current list with maxvalue
+ Called from parser
+
+ SYNOPSIS
+ add_max_value()
+ RETURN
+ TRUE Error
+ FALSE Success
+*/
+
+int partition_info::add_max_value()
+{
+ DBUG_ENTER("partition_info::add_max_value");
+
+ part_column_list_val *col_val;
+ if (!(col_val= add_column_value()))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ col_val->max_value= TRUE;
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ Create a new column value in current list
+ Called from parser
+
+ SYNOPSIS
+ add_column_value()
+ RETURN
+ >0 A part_column_list_val object which have been
+ inserted into its list
+ 0 Memory allocation failure
+*/
+
+part_column_list_val *partition_info::add_column_value()
+{
+ uint max_val= num_columns ? num_columns : MAX_REF_PARTS;
+ DBUG_ENTER("add_column_value");
+ DBUG_PRINT("enter", ("num_columns = %u, curr_list_object %u, max_val = %u",
+ num_columns, curr_list_object, max_val));
+ if (curr_list_object < max_val)
+ {
+ curr_list_val->added_items++;
+ DBUG_RETURN(&curr_list_val->col_val_array[curr_list_object++]);
+ }
+ if (!num_columns && part_type == LIST_PARTITION)
+ {
+ /*
+ We're trying to add more than MAX_REF_PARTS, this can happen
+ in ALTER TABLE using List partitions where the first partition
+ uses VALUES IN (1,2,3...,17) where the number of fields in
+ the list is more than MAX_REF_PARTS, in this case we know
+ that the number of columns must be 1 and we thus reorganize
+ into the structure used for 1 column. After this we call
+ ourselves recursively which should always succeed.
+ */
+ if (!reorganize_into_single_field_col_val())
+ {
+ DBUG_RETURN(add_column_value());
+ }
+ DBUG_RETURN(NULL);
+ }
+ if (column_list)
+ {
+ my_error(ER_PARTITION_COLUMN_LIST_ERROR, MYF(0));
+ }
+ else
+ {
+ if (part_type == RANGE_PARTITION)
+ my_error(ER_TOO_MANY_VALUES_ERROR, MYF(0), "RANGE");
+ else
+ my_error(ER_TOO_MANY_VALUES_ERROR, MYF(0), "LIST");
+ }
+ DBUG_RETURN(NULL);
+}
+
+
+/*
+ Initialise part_elem_value object at setting of a new object
+ (Helper functions to functions called by parser)
+
+ SYNOPSIS
+ init_col_val
+ col_val Column value object to be initialised
+ item Item object representing column value
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+*/
+void partition_info::init_col_val(part_column_list_val *col_val, Item *item)
+{
+ DBUG_ENTER("partition_info::init_col_val");
+
+ col_val->item_expression= item;
+ col_val->null_value= item->null_value;
+ if (item->result_type() == INT_RESULT)
+ {
+ /*
+ This could be both column_list partitioning and function
+ partitioning, but it doesn't hurt to set the function
+ partitioning flags about unsignedness.
+ */
+ curr_list_val->value= item->val_int();
+ curr_list_val->unsigned_flag= TRUE;
+ if (!item->unsigned_flag &&
+ curr_list_val->value < 0)
+ curr_list_val->unsigned_flag= FALSE;
+ if (!curr_list_val->unsigned_flag)
+ curr_part_elem->signed_flag= TRUE;
+ }
+ col_val->part_info= NULL;
+ DBUG_VOID_RETURN;
+}
+/*
+ Add a column value in VALUES LESS THAN or VALUES IN
+ (Called from parser)
+
+ SYNOPSIS
+ add_column_list_value()
+ lex Parser's lex object
+ thd Thread object
+ item Item object representing column value
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+*/
+bool partition_info::add_column_list_value(THD *thd, Item *item)
+{
+ part_column_list_val *col_val;
+ Name_resolution_context *context= &thd->lex->current_select->context;
+ TABLE_LIST *save_list= context->table_list;
+ const char *save_where= thd->where;
+ DBUG_ENTER("partition_info::add_column_list_value");
+
+ if (part_type == LIST_PARTITION &&
+ num_columns == 1U)
+ {
+ if (init_column_part())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ context->table_list= 0;
+ if (column_list)
+ thd->where= "field list";
+ else
+ thd->where= "partition function";
+
+ if (item->walk(&Item::check_partition_func_processor, 0,
+ NULL))
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (item->fix_fields(thd, (Item**)0) ||
+ ((context->table_list= save_list), FALSE) ||
+ (!item->const_item()))
+ {
+ context->table_list= save_list;
+ thd->where= save_where;
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ thd->where= save_where;
+
+ if (!(col_val= add_column_value()))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ init_col_val(col_val, item);
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ Initialise part_info object for receiving a set of column values
+ for a partition, called when parser reaches VALUES LESS THAN or
+ VALUES IN.
+
+ SYNOPSIS
+ init_column_part()
+ lex Parser's lex object
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+*/
+bool partition_info::init_column_part()
+{
+ partition_element *p_elem= curr_part_elem;
+ part_column_list_val *col_val_array;
+ part_elem_value *list_val;
+ uint loc_num_columns;
+ DBUG_ENTER("partition_info::init_column_part");
+
+ if (!(list_val=
+ (part_elem_value*)sql_calloc(sizeof(part_elem_value))) ||
+ p_elem->list_val_list.push_back(list_val))
+ {
+ mem_alloc_error(sizeof(part_elem_value));
+ DBUG_RETURN(TRUE);
+ }
+ if (num_columns)
+ loc_num_columns= num_columns;
+ else
+ loc_num_columns= MAX_REF_PARTS;
+ if (!(col_val_array=
+ (part_column_list_val*)sql_calloc(loc_num_columns *
+ sizeof(part_column_list_val))))
+ {
+ mem_alloc_error(loc_num_columns * sizeof(part_elem_value));
+ DBUG_RETURN(TRUE);
+ }
+ list_val->col_val_array= col_val_array;
+ list_val->added_items= 0;
+ curr_list_val= list_val;
+ curr_list_object= 0;
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ In the case of ALTER TABLE ADD/REORGANIZE PARTITION for LIST
+ partitions we can specify list values as:
+ VALUES IN (v1, v2,,,, v17) if we're using the first partitioning
+ variant with a function or a column list partitioned table with
+ one partition field. In this case the parser knows not the
+ number of columns start with and allocates MAX_REF_PARTS in the
+ array. If we try to allocate something beyond MAX_REF_PARTS we
+ will call this function to reorganize into a structure with
+ num_columns = 1. Also when the parser knows that we used LIST
+ partitioning and we used a VALUES IN like above where number of
+ values was smaller than MAX_REF_PARTS or equal, then we will
+ reorganize after discovering this in the parser.
+
+ SYNOPSIS
+ reorganize_into_single_field_col_val()
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+*/
+int partition_info::reorganize_into_single_field_col_val()
+{
+ part_column_list_val *col_val, *new_col_val;
+ part_elem_value *val= curr_list_val;
+ uint loc_num_columns= num_columns;
+ uint i;
+ DBUG_ENTER("partition_info::reorganize_into_single_field_col_val");
+
+ num_columns= 1;
+ val->added_items= 1U;
+ col_val= &val->col_val_array[0];
+ init_col_val(col_val, col_val->item_expression);
+ for (i= 1; i < loc_num_columns; i++)
+ {
+ col_val= &val->col_val_array[i];
+ DBUG_ASSERT(part_type == LIST_PARTITION);
+ if (init_column_part())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ if (!(new_col_val= add_column_value()))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ memcpy(new_col_val, col_val, sizeof(*col_val));
+ init_col_val(new_col_val, col_val->item_expression);
+ }
+ curr_list_val= val;
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ This function handles the case of function-based partitioning.
+ It fixes some data structures created in the parser and puts
+ them in the format required by the rest of the partitioning
+ code.
+
+ SYNOPSIS
+ fix_partition_values()
+ thd Thread object
+ col_val Array of one value
+ part_elem The partition instance
+ part_id Id of partition instance
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+*/
+int partition_info::fix_partition_values(THD *thd,
+ part_elem_value *val,
+ partition_element *part_elem,
+ uint part_id)
+{
+ part_column_list_val *col_val= val->col_val_array;
+ DBUG_ENTER("partition_info::fix_partition_values");
+
+ if (col_val->fixed)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ if (val->added_items != 1)
+ {
+ my_error(ER_PARTITION_COLUMN_LIST_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (col_val->max_value)
+ {
+ /* The parser ensures we're not LIST partitioned here */
+ DBUG_ASSERT(part_type == RANGE_PARTITION);
+ if (defined_max_value)
+ {
+ my_error(ER_PARTITION_MAXVALUE_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (part_id == (num_parts - 1))
+ {
+ defined_max_value= TRUE;
+ part_elem->max_value= TRUE;
+ part_elem->range_value= LONGLONG_MAX;
+ }
+ else
+ {
+ my_error(ER_PARTITION_MAXVALUE_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else
+ {
+ Item *item_expr= col_val->item_expression;
+ if ((val->null_value= item_expr->null_value))
+ {
+ if (part_elem->has_null_value)
+ {
+ my_error(ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ part_elem->has_null_value= TRUE;
+ }
+ else if (item_expr->result_type() != INT_RESULT)
+ {
+ my_error(ER_VALUES_IS_NOT_INT_TYPE_ERROR, MYF(0),
+ part_elem->partition_name);
+ DBUG_RETURN(TRUE);
+ }
+ if (part_type == RANGE_PARTITION)
+ {
+ if (part_elem->has_null_value)
+ {
+ my_error(ER_NULL_IN_VALUES_LESS_THAN, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ part_elem->range_value= val->value;
+ }
+ }
+ col_val->fixed= 2;
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ Get column item with a proper character set according to the field
+
+ SYNOPSIS
+ get_column_item()
+ item Item object to start with
+ field Field for which the item will be compared to
+
+ RETURN VALUES
+ NULL Error
+ item Returned item
+*/
+
+Item* partition_info::get_column_item(Item *item, Field *field)
+{
+ if (field->result_type() == STRING_RESULT &&
+ item->collation.collation != field->charset())
+ {
+ if (!(item= convert_charset_partition_constant(item,
+ field->charset())))
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ return NULL;
+ }
+ }
+ return item;
+}
+
+
+/*
+ Evaluate VALUES functions for column list values
+ SYNOPSIS
+ fix_column_value_functions()
+ thd Thread object
+ col_val List of column values
+ part_id Partition id we are fixing
+
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ Fix column VALUES and store in memory array adapted to the data type
+*/
+
+bool partition_info::fix_column_value_functions(THD *thd,
+ part_elem_value *val,
+ uint part_id)
+{
+ uint n_columns= part_field_list.elements;
+ bool result= FALSE;
+ uint i;
+ part_column_list_val *col_val= val->col_val_array;
+ DBUG_ENTER("partition_info::fix_column_value_functions");
+
+ if (col_val->fixed > 1)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ for (i= 0; i < n_columns; col_val++, i++)
+ {
+ Item *column_item= col_val->item_expression;
+ Field *field= part_field_array[i];
+ col_val->part_info= this;
+ col_val->partition_id= part_id;
+ if (col_val->max_value)
+ col_val->column_value= NULL;
+ else
+ {
+ col_val->column_value= NULL;
+ if (!col_val->null_value)
+ {
+ uchar *val_ptr;
+ uint len= field->pack_length();
+ ulonglong save_sql_mode;
+ bool save_got_warning;
+
+ if (!(column_item= get_column_item(column_item,
+ field)))
+ {
+ result= TRUE;
+ goto end;
+ }
+ save_sql_mode= thd->variables.sql_mode;
+ thd->variables.sql_mode= 0;
+ save_got_warning= thd->got_warning;
+ thd->got_warning= 0;
+ if (column_item->save_in_field(field, TRUE) ||
+ thd->got_warning)
+ {
+ my_error(ER_WRONG_TYPE_COLUMN_VALUE_ERROR, MYF(0));
+ result= TRUE;
+ goto end;
+ }
+ thd->got_warning= save_got_warning;
+ thd->variables.sql_mode= save_sql_mode;
+ if (!(val_ptr= (uchar*) sql_calloc(len)))
+ {
+ mem_alloc_error(len);
+ result= TRUE;
+ goto end;
+ }
+ col_val->column_value= val_ptr;
+ memcpy(val_ptr, field->ptr, len);
+ }
+ }
+ col_val->fixed= 2;
+ }
+end:
+ DBUG_RETURN(result);
+}
+
+/**
+ Fix partition data from parser.
+
+ @details The parser generates generic data structures, we need to set them
+ up as the rest of the code expects to find them. This is in reality part
+ of the syntax check of the parser code.
+
+ It is necessary to call this function in the case of a CREATE TABLE
+ statement, in this case we do it early in the check_partition_info
+ function.
+
+ It is necessary to call this function for ALTER TABLE where we
+ assign a completely new partition structure, in this case we do it
+ in prep_alter_part_table after discovering that the partition
+ structure is entirely redefined.
+
+ It's necessary to call this method also for ALTER TABLE ADD/REORGANIZE
+ of partitions, in this we call it in prep_alter_part_table after
+ making some initial checks but before going deep to check the partition
+ info, we also assign the column_list variable before calling this function
+ here.
+
+ Finally we also call it immediately after returning from parsing the
+ partitioning text found in the frm file.
+
+ This function mainly fixes the VALUES parts, these are handled differently
+ whether or not we use column list partitioning. Since the parser doesn't
+ know which we are using we need to set-up the old data structures after
+ the parser is complete when we know if what type of partitioning the
+ base table is using.
+
+ For column lists we will handle this in the fix_column_value_function.
+ For column lists it is sufficient to verify that the number of columns
+ and number of elements are in synch with each other. So only partitioning
+ using functions need to be set-up to their data structures.
+
+ @param thd Thread object
+
+ @return Operation status
+ @retval TRUE Failure
+ @retval FALSE Success
+*/
+
+bool partition_info::fix_parser_data(THD *thd)
+{
+ List_iterator<partition_element> it(partitions);
+ partition_element *part_elem;
+ uint num_elements;
+ uint i= 0, j, k;
+ DBUG_ENTER("partition_info::fix_parser_data");
+
+ if (!(part_type == RANGE_PARTITION ||
+ part_type == LIST_PARTITION))
+ {
+ if (part_type == HASH_PARTITION && list_of_part_fields)
+ {
+ /* KEY partitioning, check ALGORITHM = N. Should not pass the parser! */
+ if (key_algorithm > KEY_ALGORITHM_55)
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ DBUG_RETURN(true);
+ }
+ /* If not set, use DEFAULT = 2 for CREATE and ALTER! */
+ if ((thd_sql_command(thd) == SQLCOM_CREATE_TABLE ||
+ thd_sql_command(thd) == SQLCOM_ALTER_TABLE) &&
+ key_algorithm == KEY_ALGORITHM_NONE)
+ key_algorithm= KEY_ALGORITHM_55;
+ }
+ DBUG_RETURN(FALSE);
+ }
+ if (is_sub_partitioned() && list_of_subpart_fields)
+ {
+ /* KEY subpartitioning, check ALGORITHM = N. Should not pass the parser! */
+ if (key_algorithm > KEY_ALGORITHM_55)
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ DBUG_RETURN(true);
+ }
+ /* If not set, use DEFAULT = 2 for CREATE and ALTER! */
+ if ((thd_sql_command(thd) == SQLCOM_CREATE_TABLE ||
+ thd_sql_command(thd) == SQLCOM_ALTER_TABLE) &&
+ key_algorithm == KEY_ALGORITHM_NONE)
+ key_algorithm= KEY_ALGORITHM_55;
+ }
+ do
+ {
+ part_elem= it++;
+ List_iterator<part_elem_value> list_val_it(part_elem->list_val_list);
+ num_elements= part_elem->list_val_list.elements;
+ DBUG_ASSERT(part_type == RANGE_PARTITION ?
+ num_elements == 1U : TRUE);
+ for (j= 0; j < num_elements; j++)
+ {
+ part_elem_value *val= list_val_it++;
+ if (column_list)
+ {
+ if (val->added_items != num_columns)
+ {
+ my_error(ER_PARTITION_COLUMN_LIST_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ for (k= 0; k < num_columns; k++)
+ {
+ part_column_list_val *col_val= &val->col_val_array[k];
+ if (col_val->null_value && part_type == RANGE_PARTITION)
+ {
+ my_error(ER_NULL_IN_VALUES_LESS_THAN, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ else
+ {
+ if (fix_partition_values(thd, val, part_elem, i))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ if (val->null_value)
+ {
+ /*
+ Null values aren't required in the value part, they are kept per
+ partition instance, only LIST partitions have NULL values.
+ */
+ list_val_it.remove();
+ }
+ }
+ }
+ } while (++i < num_parts);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ helper function to compare strings that can also be
+ a NULL pointer.
+
+ @param a char pointer (can be NULL).
+ @param b char pointer (can be NULL).
+
+ @return false if equal
+ @retval true strings differs
+ @retval false strings is equal
+*/
+
+static bool strcmp_null(const char *a, const char *b)
+{
+ if (!a && !b)
+ return false;
+ if (a && b && !strcmp(a, b))
+ return false;
+ return true;
+}
+
+
+/**
+ Check if the new part_info has the same partitioning.
+
+ @param new_part_info New partition definition to compare with.
+
+ @return True if not considered to have changed the partitioning.
+ @retval true Allowed change (only .frm change, compatible distribution).
+ @retval false Different partitioning, will need redistribution of rows.
+
+ @note Currently only used to allow changing from non-set key_algorithm
+ to a specified key_algorithm, to avoid rebuild when upgrading from 5.1 of
+ such partitioned tables using numeric colums in the partitioning expression.
+ For more info see bug#14521864.
+ Does not check if columns etc has changed, i.e. only for
+ alter_info->flags == ALTER_PARTITION.
+*/
+
+bool partition_info::has_same_partitioning(partition_info *new_part_info)
+{
+ DBUG_ENTER("partition_info::has_same_partitioning");
+
+ DBUG_ASSERT(part_field_array && part_field_array[0]);
+
+ /*
+ Only consider pre 5.5.3 .frm's to have same partitioning as
+ a new one with KEY ALGORITHM = 1 ().
+ */
+
+ if (part_field_array[0]->table->s->mysql_version >= 50503)
+ DBUG_RETURN(false);
+
+ if (!new_part_info ||
+ part_type != new_part_info->part_type ||
+ num_parts != new_part_info->num_parts ||
+ use_default_partitions != new_part_info->use_default_partitions ||
+ new_part_info->is_sub_partitioned() != is_sub_partitioned())
+ DBUG_RETURN(false);
+
+ if (part_type != HASH_PARTITION)
+ {
+ /*
+ RANGE or LIST partitioning, check if KEY subpartitioned.
+ Also COLUMNS partitioning was added in 5.5, so treat that as different.
+ */
+ if (!is_sub_partitioned() ||
+ !new_part_info->is_sub_partitioned() ||
+ column_list ||
+ new_part_info->column_list ||
+ !list_of_subpart_fields ||
+ !new_part_info->list_of_subpart_fields ||
+ new_part_info->num_subparts != num_subparts ||
+ new_part_info->subpart_field_list.elements !=
+ subpart_field_list.elements ||
+ new_part_info->use_default_subpartitions !=
+ use_default_subpartitions)
+ DBUG_RETURN(false);
+ }
+ else
+ {
+ /* Check if KEY partitioned. */
+ if (!new_part_info->list_of_part_fields ||
+ !list_of_part_fields ||
+ new_part_info->part_field_list.elements != part_field_list.elements)
+ DBUG_RETURN(false);
+ }
+
+ /* Check that it will use the same fields in KEY (fields) list. */
+ List_iterator<char> old_field_name_it(part_field_list);
+ List_iterator<char> new_field_name_it(new_part_info->part_field_list);
+ char *old_name, *new_name;
+ while ((old_name= old_field_name_it++))
+ {
+ new_name= new_field_name_it++;
+ if (!new_name || my_strcasecmp(system_charset_info,
+ new_name,
+ old_name))
+ DBUG_RETURN(false);
+ }
+
+ if (is_sub_partitioned())
+ {
+ /* Check that it will use the same fields in KEY subpart fields list. */
+ List_iterator<char> old_field_name_it(subpart_field_list);
+ List_iterator<char> new_field_name_it(new_part_info->subpart_field_list);
+ char *old_name, *new_name;
+ while ((old_name= old_field_name_it++))
+ {
+ new_name= new_field_name_it++;
+ if (!new_name || my_strcasecmp(system_charset_info,
+ new_name,
+ old_name))
+ DBUG_RETURN(false);
+ }
+ }
+
+ if (!use_default_partitions)
+ {
+ /*
+ Loop over partitions/subpartition to verify that they are
+ the same, including state and name.
+ */
+ List_iterator<partition_element> part_it(partitions);
+ List_iterator<partition_element> new_part_it(new_part_info->partitions);
+ uint i= 0;
+ do
+ {
+ partition_element *part_elem= part_it++;
+ partition_element *new_part_elem= new_part_it++;
+ /*
+ The following must match:
+ partition_name, tablespace_name, data_file_name, index_file_name,
+ engine_type, part_max_rows, part_min_rows, nodegroup_id.
+ (max_value, signed_flag, has_null_value only on partition level,
+ RANGE/LIST)
+ The following can differ:
+ - part_comment
+ part_state must be PART_NORMAL!
+ */
+ if (!part_elem || !new_part_elem ||
+ strcmp(part_elem->partition_name,
+ new_part_elem->partition_name) ||
+ part_elem->part_state != PART_NORMAL ||
+ new_part_elem->part_state != PART_NORMAL ||
+ part_elem->max_value != new_part_elem->max_value ||
+ part_elem->signed_flag != new_part_elem->signed_flag ||
+ part_elem->has_null_value != new_part_elem->has_null_value)
+ DBUG_RETURN(false);
+
+ /* new_part_elem may not have engine_type set! */
+ if (new_part_elem->engine_type &&
+ part_elem->engine_type != new_part_elem->engine_type)
+ DBUG_RETURN(false);
+
+ if (is_sub_partitioned())
+ {
+ /*
+ Check that both old and new partition has the same definition
+ (VALUES IN/VALUES LESS THAN) (No COLUMNS partitioning, see above)
+ */
+ if (part_type == LIST_PARTITION)
+ {
+ List_iterator<part_elem_value> list_vals(part_elem->list_val_list);
+ List_iterator<part_elem_value>
+ new_list_vals(new_part_elem->list_val_list);
+ part_elem_value *val;
+ part_elem_value *new_val;
+ while ((val= list_vals++))
+ {
+ new_val= new_list_vals++;
+ if (!new_val)
+ DBUG_RETURN(false);
+ if ((!val->null_value && !new_val->null_value) &&
+ val->value != new_val->value)
+ DBUG_RETURN(false);
+ }
+ if (new_list_vals++)
+ DBUG_RETURN(false);
+ }
+ else
+ {
+ DBUG_ASSERT(part_type == RANGE_PARTITION);
+ if (new_part_elem->range_value != part_elem->range_value)
+ DBUG_RETURN(false);
+ }
+
+ if (!use_default_subpartitions)
+ {
+ List_iterator<partition_element>
+ sub_part_it(part_elem->subpartitions);
+ List_iterator<partition_element>
+ new_sub_part_it(new_part_elem->subpartitions);
+ uint j= 0;
+ do
+ {
+ partition_element *sub_part_elem= sub_part_it++;
+ partition_element *new_sub_part_elem= new_sub_part_it++;
+ /* new_part_elem may not have engine_type set! */
+ if (new_sub_part_elem->engine_type &&
+ sub_part_elem->engine_type != new_sub_part_elem->engine_type)
+ DBUG_RETURN(false);
+
+ if (strcmp(sub_part_elem->partition_name,
+ new_sub_part_elem->partition_name) ||
+ sub_part_elem->part_state != PART_NORMAL ||
+ new_sub_part_elem->part_state != PART_NORMAL ||
+ sub_part_elem->part_min_rows !=
+ new_sub_part_elem->part_min_rows ||
+ sub_part_elem->part_max_rows !=
+ new_sub_part_elem->part_max_rows ||
+ sub_part_elem->nodegroup_id !=
+ new_sub_part_elem->nodegroup_id)
+ DBUG_RETURN(false);
+
+ if (strcmp_null(sub_part_elem->data_file_name,
+ new_sub_part_elem->data_file_name) ||
+ strcmp_null(sub_part_elem->index_file_name,
+ new_sub_part_elem->index_file_name) ||
+ strcmp_null(sub_part_elem->tablespace_name,
+ new_sub_part_elem->tablespace_name))
+ DBUG_RETURN(false);
+
+ } while (++j < num_subparts);
+ }
+ }
+ else
+ {
+ if (part_elem->part_min_rows != new_part_elem->part_min_rows ||
+ part_elem->part_max_rows != new_part_elem->part_max_rows ||
+ part_elem->nodegroup_id != new_part_elem->nodegroup_id)
+ DBUG_RETURN(false);
+
+ if (strcmp_null(part_elem->data_file_name,
+ new_part_elem->data_file_name) ||
+ strcmp_null(part_elem->index_file_name,
+ new_part_elem->index_file_name) ||
+ strcmp_null(part_elem->tablespace_name,
+ new_part_elem->tablespace_name))
+ DBUG_RETURN(false);
+ }
+ } while (++i < num_parts);
+ }
+
+ /*
+ Only if key_algorithm was not specified before and it is now set,
+ consider this as nothing was changed, and allow change without rebuild!
+ */
+ if (key_algorithm != partition_info::KEY_ALGORITHM_NONE ||
+ new_part_info->key_algorithm == partition_info::KEY_ALGORITHM_NONE)
+ DBUG_RETURN(false);
+
+ DBUG_RETURN(true);
+}
+
+
+void partition_info::print_debug(const char *str, uint *value)
+{
+ DBUG_ENTER("print_debug");
+ if (value)
+ DBUG_PRINT("info", ("parser: %s, val = %u", str, *value));
+ else
+ DBUG_PRINT("info", ("parser: %s", str));
+ DBUG_VOID_RETURN;
+}
+#else /* WITH_PARTITION_STORAGE_ENGINE */
+ /*
+ For builds without partitioning we need to define these functions
+ since we they are called from the parser. The parser cannot
+ remove code parts using ifdef, but the code parts cannot be called
+ so we simply need to add empty functions to make the linker happy.
+ */
+part_column_list_val *partition_info::add_column_value()
+{
+ return NULL;
+}
+
+bool partition_info::set_part_expr(char *start_token, Item *item_ptr,
+ char *end_token, bool is_subpart)
+{
+ (void)start_token;
+ (void)item_ptr;
+ (void)end_token;
+ (void)is_subpart;
+ return FALSE;
+}
+
+int partition_info::reorganize_into_single_field_col_val()
+{
+ return 0;
+}
+
+bool partition_info::init_column_part()
+{
+ return FALSE;
+}
+
+bool partition_info::add_column_list_value(THD *thd, Item *item)
+{
+ return FALSE;
+}
+int partition_info::add_max_value()
+{
+ return 0;
+}
+
+void partition_info::print_debug(const char *str, uint *value)
+{
+}
+
+bool check_partition_dirs(partition_info *part_info)
+{
+ return 0;
+}
+
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
diff --git a/sql/partition_info.h b/sql/partition_info.h
new file mode 100644
index 00000000000..8ad7b1fd1fd
--- /dev/null
+++ b/sql/partition_info.h
@@ -0,0 +1,414 @@
+#ifndef PARTITION_INFO_INCLUDED
+#define PARTITION_INFO_INCLUDED
+
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_class.h"
+#include "partition_element.h"
+
+class partition_info;
+struct TABLE_LIST;
+/* Some function typedefs */
+typedef int (*get_part_id_func)(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+typedef int (*get_subpart_id_func)(partition_info *part_info,
+ uint32 *part_id);
+
+struct st_ddl_log_memory_entry;
+
+class partition_info : public Sql_alloc
+{
+public:
+ /*
+ * Here comes a set of definitions needed for partitioned table handlers.
+ */
+ List<partition_element> partitions;
+ List<partition_element> temp_partitions;
+
+ List<char> part_field_list;
+ List<char> subpart_field_list;
+
+ /*
+ If there is no subpartitioning, use only this func to get partition ids.
+ If there is subpartitioning, use the this func to get partition id when
+ you have both partition and subpartition fields.
+ */
+ get_part_id_func get_partition_id;
+
+ /* Get partition id when we don't have subpartition fields */
+ get_part_id_func get_part_partition_id;
+
+ /*
+ Get subpartition id when we have don't have partition fields by we do
+ have subpartition ids.
+ Mikael said that for given constant tuple
+ {subpart_field1, ..., subpart_fieldN} the subpartition id will be the
+ same in all subpartitions
+ */
+ get_subpart_id_func get_subpartition_id;
+
+ /*
+ When we have various string fields we might need some preparation
+ before and clean-up after calling the get_part_id_func's. We need
+ one such method for get_part_partition_id and one for
+ get_subpartition_id.
+ */
+ get_part_id_func get_part_partition_id_charset;
+ get_subpart_id_func get_subpartition_id_charset;
+
+ /* NULL-terminated array of fields used in partitioned expression */
+ Field **part_field_array;
+ Field **subpart_field_array;
+ Field **part_charset_field_array;
+ Field **subpart_charset_field_array;
+ /*
+ Array of all fields used in partition and subpartition expression,
+ without duplicates, NULL-terminated.
+ */
+ Field **full_part_field_array;
+ /*
+ Set of all fields used in partition and subpartition expression.
+ Required for testing of partition fields in write_set when
+ updating. We need to set all bits in read_set because the row may
+ need to be inserted in a different [sub]partition.
+ */
+ MY_BITMAP full_part_field_set;
+
+ /*
+ When we have a field that requires transformation before calling the
+ partition functions we must allocate field buffers for the field of
+ the fields in the partition function.
+ */
+ uchar **part_field_buffers;
+ uchar **subpart_field_buffers;
+ uchar **restore_part_field_ptrs;
+ uchar **restore_subpart_field_ptrs;
+
+ Item *part_expr;
+ Item *subpart_expr;
+
+ Item *item_free_list;
+
+ struct st_ddl_log_memory_entry *first_log_entry;
+ struct st_ddl_log_memory_entry *exec_log_entry;
+ struct st_ddl_log_memory_entry *frm_log_entry;
+
+ /*
+ Bitmaps of partitions used by the current query.
+ * read_partitions - partitions to be used for reading.
+ * lock_partitions - partitions that must be locked (read or write).
+ Usually read_partitions is the same set as lock_partitions, but
+ in case of UPDATE the WHERE clause can limit the read_partitions set,
+ but not neccesarily the lock_partitions set.
+ Usage pattern:
+ * Initialized in ha_partition::open().
+ * read+lock_partitions is set according to explicit PARTITION,
+ WL#5217, in open_and_lock_tables().
+ * Bits in read_partitions can be cleared in prune_partitions()
+ in the optimizing step.
+ (WL#4443 is about allowing prune_partitions() to affect lock_partitions
+ and be done before locking too).
+ * When the partition enabled handler get an external_lock call it locks
+ all partitions in lock_partitions (and remembers which partitions it
+ locked, so that it can unlock them later). In case of LOCK TABLES it will
+ lock all partitions, and keep them locked while lock_partitions can
+ change for each statement under LOCK TABLES.
+ * Freed at the same time item_free_list is freed.
+ */
+ MY_BITMAP read_partitions;
+ MY_BITMAP lock_partitions;
+ bool bitmaps_are_initialized;
+
+ union {
+ longlong *range_int_array;
+ LIST_PART_ENTRY *list_array;
+ part_column_list_val *range_col_array;
+ part_column_list_val *list_col_array;
+ };
+
+ /********************************************
+ * INTERVAL ANALYSIS
+ ********************************************/
+ /*
+ Partitioning interval analysis function for partitioning, or NULL if
+ interval analysis is not supported for this kind of partitioning.
+ */
+ get_partitions_in_range_iter get_part_iter_for_interval;
+ /*
+ Partitioning interval analysis function for subpartitioning, or NULL if
+ interval analysis is not supported for this kind of partitioning.
+ */
+ get_partitions_in_range_iter get_subpart_iter_for_interval;
+
+ /********************************************
+ * INTERVAL ANALYSIS ENDS
+ ********************************************/
+
+ longlong err_value;
+ char* part_info_string;
+
+ char *part_func_string;
+ char *subpart_func_string;
+
+ partition_element *curr_part_elem; // part or sub part
+ partition_element *current_partition; // partition
+ part_elem_value *curr_list_val;
+ uint curr_list_object;
+ uint num_columns;
+
+ TABLE *table;
+ /*
+ These key_map's are used for Partitioning to enable quick decisions
+ on whether we can derive more information about which partition to
+ scan just by looking at what index is used.
+ */
+ key_map all_fields_in_PF, all_fields_in_PPF, all_fields_in_SPF;
+ key_map some_fields_in_PF;
+
+ handlerton *default_engine_type;
+ partition_type part_type;
+ partition_type subpart_type;
+
+ uint part_info_len;
+ uint part_func_len;
+ uint subpart_func_len;
+
+ uint num_parts;
+ uint num_subparts;
+ uint count_curr_subparts; // used during parsing
+
+ uint num_list_values;
+
+ uint num_part_fields;
+ uint num_subpart_fields;
+ uint num_full_part_fields;
+
+ uint has_null_part_id;
+ /*
+ This variable is used to calculate the partition id when using
+ LINEAR KEY/HASH. This functionality is kept in the MySQL Server
+ but mainly of use to handlers supporting partitioning.
+ */
+ uint16 linear_hash_mask;
+ /*
+ PARTITION BY KEY ALGORITHM=N
+ Which algorithm to use for hashing the fields.
+ N = 1 - Use 5.1 hashing (numeric fields are hashed as binary)
+ N = 2 - Use 5.5 hashing (numeric fields are hashed like latin1 bytes)
+ */
+ enum enum_key_algorithm
+ {
+ KEY_ALGORITHM_NONE= 0,
+ KEY_ALGORITHM_51= 1,
+ KEY_ALGORITHM_55= 2
+ };
+ enum_key_algorithm key_algorithm;
+
+ /* Only the number of partitions defined (uses default names and options). */
+ bool use_default_partitions;
+ bool use_default_num_partitions;
+ /* Only the number of subpartitions defined (uses default names etc.). */
+ bool use_default_subpartitions;
+ bool use_default_num_subpartitions;
+ bool default_partitions_setup;
+ bool defined_max_value;
+ bool list_of_part_fields; // KEY or COLUMNS PARTITIONING
+ bool list_of_subpart_fields; // KEY SUBPARTITIONING
+ bool linear_hash_ind; // LINEAR HASH/KEY
+ bool fixed;
+ bool is_auto_partitioned;
+ bool has_null_value;
+ bool column_list; // COLUMNS PARTITIONING, 5.5+
+ /**
+ True if pruning has been completed and can not be pruned any further,
+ even if there are subqueries or stored programs in the condition.
+
+ Some times it is needed to run prune_partitions() a second time to prune
+ read partitions after tables are locked, when subquery and
+ stored functions might have been evaluated.
+ */
+ bool is_pruning_completed;
+
+ partition_info()
+ : get_partition_id(NULL), get_part_partition_id(NULL),
+ get_subpartition_id(NULL),
+ part_field_array(NULL), subpart_field_array(NULL),
+ part_charset_field_array(NULL),
+ subpart_charset_field_array(NULL),
+ full_part_field_array(NULL),
+ part_field_buffers(NULL), subpart_field_buffers(NULL),
+ restore_part_field_ptrs(NULL), restore_subpart_field_ptrs(NULL),
+ part_expr(NULL), subpart_expr(NULL), item_free_list(NULL),
+ first_log_entry(NULL), exec_log_entry(NULL), frm_log_entry(NULL),
+ bitmaps_are_initialized(FALSE),
+ list_array(NULL), err_value(0),
+ part_info_string(NULL),
+ part_func_string(NULL), subpart_func_string(NULL),
+ curr_part_elem(NULL), current_partition(NULL),
+ curr_list_object(0), num_columns(0), table(NULL),
+ default_engine_type(NULL),
+ part_type(NOT_A_PARTITION), subpart_type(NOT_A_PARTITION),
+ part_info_len(0),
+ part_func_len(0), subpart_func_len(0),
+ num_parts(0), num_subparts(0),
+ count_curr_subparts(0),
+ num_list_values(0), num_part_fields(0), num_subpart_fields(0),
+ num_full_part_fields(0), has_null_part_id(0), linear_hash_mask(0),
+ key_algorithm(KEY_ALGORITHM_NONE),
+ use_default_partitions(TRUE), use_default_num_partitions(TRUE),
+ use_default_subpartitions(TRUE), use_default_num_subpartitions(TRUE),
+ default_partitions_setup(FALSE), defined_max_value(FALSE),
+ list_of_part_fields(FALSE), list_of_subpart_fields(FALSE),
+ linear_hash_ind(FALSE), fixed(FALSE),
+ is_auto_partitioned(FALSE),
+ has_null_value(FALSE), column_list(FALSE), is_pruning_completed(false)
+ {
+ all_fields_in_PF.clear_all();
+ all_fields_in_PPF.clear_all();
+ all_fields_in_SPF.clear_all();
+ some_fields_in_PF.clear_all();
+ partitions.empty();
+ temp_partitions.empty();
+ part_field_list.empty();
+ subpart_field_list.empty();
+ }
+ ~partition_info() {}
+
+ partition_info *get_clone();
+ bool set_named_partition_bitmap(const char *part_name, uint length);
+ bool set_partition_bitmaps(TABLE_LIST *table_list);
+ /* Answers the question if subpartitioning is used for a certain table */
+ bool is_sub_partitioned()
+ {
+ return (subpart_type == NOT_A_PARTITION ? FALSE : TRUE);
+ }
+
+ /* Returns the total number of partitions on the leaf level */
+ uint get_tot_partitions()
+ {
+ return num_parts * (is_sub_partitioned() ? num_subparts : 1);
+ }
+
+ bool set_up_defaults_for_partitioning(handler *file, HA_CREATE_INFO *info,
+ uint start_no);
+ char *find_duplicate_field();
+ char *find_duplicate_name();
+ bool check_engine_mix(handlerton *engine_type, bool default_engine);
+ bool check_range_constants(THD *thd);
+ bool check_list_constants(THD *thd);
+ bool check_partition_info(THD *thd, handlerton **eng_type,
+ handler *file, HA_CREATE_INFO *info,
+ bool check_partition_function);
+ void print_no_partition_found(TABLE *table, myf errflag);
+ void print_debug(const char *str, uint*);
+ Item* get_column_item(Item *item, Field *field);
+ int fix_partition_values(THD *thd,
+ part_elem_value *val,
+ partition_element *part_elem,
+ uint part_id);
+ bool fix_column_value_functions(THD *thd,
+ part_elem_value *val,
+ uint part_id);
+ bool fix_parser_data(THD *thd);
+ int add_max_value();
+ void init_col_val(part_column_list_val *col_val, Item *item);
+ int reorganize_into_single_field_col_val();
+ part_column_list_val *add_column_value();
+ bool set_part_expr(char *start_token, Item *item_ptr,
+ char *end_token, bool is_subpart);
+ static int compare_column_values(const void *a, const void *b);
+ bool set_up_charset_field_preps();
+ bool check_partition_field_length();
+ bool init_column_part();
+ bool add_column_list_value(THD *thd, Item *item);
+ void set_show_version_string(String *packet);
+ partition_element *get_part_elem(const char *partition_name,
+ char *file_name,
+ uint32 *part_id);
+ void report_part_expr_error(bool use_subpart_expr);
+ bool set_used_partition(List<Item> &fields,
+ List<Item> &values,
+ COPY_INFO &info,
+ bool copy_default_values,
+ MY_BITMAP *used_partitions);
+ /**
+ PRUNE_NO - Unable to prune.
+ PRUNE_DEFAULTS - Partitioning field is only set to
+ DEFAULT values, only need to check
+ pruning for one row where the DEFAULTS
+ values are set.
+ PRUNE_YES - Pruning is possible, calculate the used partition set
+ by evaluate the partition_id on row by row basis.
+ */
+ enum enum_can_prune {PRUNE_NO=0, PRUNE_DEFAULTS, PRUNE_YES};
+ bool can_prune_insert(THD *thd,
+ enum_duplicates duplic,
+ COPY_INFO &update,
+ List<Item> &update_fields,
+ List<Item> &fields,
+ bool empty_values,
+ enum_can_prune *can_prune_partitions,
+ bool *prune_needs_default_values,
+ MY_BITMAP *used_partitions);
+ bool has_same_partitioning(partition_info *new_part_info);
+private:
+ static int list_part_cmp(const void* a, const void* b);
+ bool set_up_default_partitions(handler *file, HA_CREATE_INFO *info,
+ uint start_no);
+ bool set_up_default_subpartitions(handler *file, HA_CREATE_INFO *info);
+ char *create_default_partition_names(uint part_no, uint num_parts,
+ uint start_no);
+ char *create_default_subpartition_name(uint subpart_no,
+ const char *part_name);
+ bool prune_partition_bitmaps(TABLE_LIST *table_list);
+ bool add_named_partition(const char *part_name, uint length);
+ bool is_field_in_part_expr(List<Item> &fields);
+ bool is_full_part_expr_in_fields(List<Item> &fields);
+public:
+ bool has_unique_name(partition_element *element);
+};
+
+uint32 get_next_partition_id_range(struct st_partition_iter* part_iter);
+bool check_partition_dirs(partition_info *part_info);
+
+/* Initialize the iterator to return a single partition with given part_id */
+
+static inline void init_single_partition_iterator(uint32 part_id,
+ PARTITION_ITERATOR *part_iter)
+{
+ part_iter->part_nums.start= part_iter->part_nums.cur= part_id;
+ part_iter->part_nums.end= part_id+1;
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= FALSE;
+ part_iter->get_next= get_next_partition_id_range;
+}
+
+/* Initialize the iterator to enumerate all partitions */
+static inline
+void init_all_partitions_iterator(partition_info *part_info,
+ PARTITION_ITERATOR *part_iter)
+{
+ part_iter->part_nums.start= part_iter->part_nums.cur= 0;
+ part_iter->part_nums.end= part_info->num_parts;
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= FALSE;
+ part_iter->get_next= get_next_partition_id_range;
+}
+
+#endif /* PARTITION_INFO_INCLUDED */
diff --git a/sql/password.c b/sql/password.c
new file mode 100644
index 00000000000..37d06136d80
--- /dev/null
+++ b/sql/password.c
@@ -0,0 +1,544 @@
+/*
+ Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2012, Monty Program Ab
+
+ 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 */
+
+/* password checking routines */
+/*****************************************************************************
+ The main idea is that no password are sent between client & server on
+ connection and that no password are saved in mysql in a decodable form.
+
+ On connection a random string is generated and sent to the client.
+ The client generates a new string with a random generator inited with
+ the hash values from the password and the sent string.
+ This 'check' string is sent to the server where it is compared with
+ a string generated from the stored hash_value of the password and the
+ random string.
+
+ The password is saved (in user.password) by using the PASSWORD() function in
+ mysql.
+
+ This is .c file because it's used in libmysqlclient, which is entirely in C.
+ (we need it to be portable to a variety of systems).
+ Example:
+ update user set password=PASSWORD("hello") where user="test"
+ This saves a hashed number as a string in the password field.
+
+ The new authentication is performed in following manner:
+
+ SERVER: public_seed=create_random_string()
+ send(public_seed)
+
+ CLIENT: recv(public_seed)
+ hash_stage1=sha1("password")
+ hash_stage2=sha1(hash_stage1)
+ reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
+
+ // this three steps are done in scramble()
+
+ send(reply)
+
+
+ SERVER: recv(reply)
+ hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
+ candidate_hash2=sha1(hash_stage1)
+ check(candidate_hash2==hash_stage2)
+
+ // this three steps are done in check_scramble()
+
+*****************************************************************************/
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <m_string.h>
+#include <password.h>
+#include <mysql.h>
+#include <my_rnd.h>
+#include <sha1.h>
+
+/************ MySQL 3.23-4.0 authentication routines: untouched ***********/
+
+/*
+ New (MySQL 3.21+) random generation structure initialization
+ SYNOPSIS
+ my_rnd_init()
+ rand_st OUT Structure to initialize
+ seed1 IN First initialization parameter
+ seed2 IN Second initialization parameter
+*/
+
+/*
+ Generate binary hash from raw text string
+ Used for Pre-4.1 password handling
+ SYNOPSIS
+ hash_password()
+ result OUT store hash in this location
+ password IN plain text password to build hash
+ password_len IN password length (password may be not null-terminated)
+*/
+
+void hash_password(ulong *result, const char *password, uint password_len)
+{
+ register ulong nr=1345345333L, add=7, nr2=0x12345671L;
+ ulong tmp;
+ const char *password_end= password + password_len;
+ for (; password < password_end; password++)
+ {
+ if (*password == ' ' || *password == '\t')
+ continue; /* skip space in password */
+ tmp= (ulong) (uchar) *password;
+ nr^= (((nr & 63)+add)*tmp)+ (nr << 8);
+ nr2+=(nr2 << 8) ^ nr;
+ add+=tmp;
+ }
+ result[0]=nr & (((ulong) 1L << 31) -1L); /* Don't use sign bit (str2int) */;
+ result[1]=nr2 & (((ulong) 1L << 31) -1L);
+}
+
+
+/*
+ Create password to be stored in user database from raw string
+ Used for pre-4.1 password handling
+ SYNOPSIS
+ my_make_scrambled_password_323()
+ to OUT store scrambled password here
+ password IN user-supplied password
+ pass_len IN length of password string
+*/
+
+void my_make_scrambled_password_323(char *to, const char *password,
+ size_t pass_len)
+{
+ ulong hash_res[2];
+ hash_password(hash_res, password, (uint) pass_len);
+ sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]);
+}
+
+
+/*
+ Wrapper around my_make_scrambled_password_323() to maintain client lib ABI
+ compatibility.
+ In server code usage of my_make_scrambled_password_323() is preferred to
+ avoid strlen().
+ SYNOPSIS
+ make_scrambled_password_323()
+ to OUT store scrambled password here
+ password IN NULL-terminated string with user-supplied password
+*/
+
+void make_scrambled_password_323(char *to, const char *password)
+{
+ my_make_scrambled_password_323(to, password, strlen(password));
+}
+
+
+/*
+ Scramble string with password.
+ Used in pre 4.1 authentication phase.
+ SYNOPSIS
+ scramble_323()
+ to OUT Store scrambled message here. Buffer must be at least
+ SCRAMBLE_LENGTH_323+1 bytes long
+ message IN Message to scramble. Message must be at least
+ SRAMBLE_LENGTH_323 bytes long.
+ password IN Password to use while scrambling
+*/
+
+void scramble_323(char *to, const char *message, const char *password)
+{
+ struct my_rnd_struct rand_st;
+ ulong hash_pass[2], hash_message[2];
+
+ if (password && password[0])
+ {
+ char extra, *to_start=to;
+ const char *message_end= message + SCRAMBLE_LENGTH_323;
+ hash_password(hash_pass,password, (uint) strlen(password));
+ hash_password(hash_message, message, SCRAMBLE_LENGTH_323);
+ my_rnd_init(&rand_st,hash_pass[0] ^ hash_message[0],
+ hash_pass[1] ^ hash_message[1]);
+ for (; message < message_end; message++)
+ *to++= (char) (floor(my_rnd(&rand_st)*31)+64);
+ extra=(char) (floor(my_rnd(&rand_st)*31));
+ while (to_start != to)
+ *(to_start++)^=extra;
+ }
+ *to= 0;
+}
+
+
+/**
+ Check scrambled message. Used in pre 4.1 password handling.
+
+ @param scrambled Scrambled message to check.
+ @param message Original random message which was used for scrambling.
+ @param hash_pass Password which should be used for scrambling.
+
+ @remark scrambled and message must be SCRAMBLED_LENGTH_323 bytes long.
+
+ @return FALSE if password is correct, TRUE otherwise.
+*/
+
+my_bool
+check_scramble_323(const unsigned char *scrambled, const char *message,
+ ulong *hash_pass)
+{
+ struct my_rnd_struct rand_st;
+ ulong hash_message[2];
+ /* Big enough for checks. */
+ uchar buff[16], scrambled_buff[SCRAMBLE_LENGTH_323 + 1];
+ uchar *to, extra;
+ const uchar *pos;
+
+ /* Ensure that the scrambled message is null-terminated. */
+ memcpy(scrambled_buff, scrambled, SCRAMBLE_LENGTH_323);
+ scrambled_buff[SCRAMBLE_LENGTH_323]= '\0';
+ scrambled= scrambled_buff;
+
+ hash_password(hash_message, message, SCRAMBLE_LENGTH_323);
+ my_rnd_init(&rand_st,hash_pass[0] ^ hash_message[0],
+ hash_pass[1] ^ hash_message[1]);
+ to=buff;
+ DBUG_ASSERT(sizeof(buff) > SCRAMBLE_LENGTH_323);
+ for (pos=scrambled ; *pos && to < buff+sizeof(buff) ; pos++)
+ *to++=(char) (floor(my_rnd(&rand_st)*31)+64);
+ if (pos-scrambled != SCRAMBLE_LENGTH_323)
+ return 1;
+ extra=(char) (floor(my_rnd(&rand_st)*31));
+ to=buff;
+ while (*scrambled)
+ {
+ if (*scrambled++ != (uchar) (*to++ ^ extra))
+ return 1; /* Wrong password */
+ }
+ return 0;
+}
+
+static inline uint8 char_val(uint8 X)
+{
+ return (uint) (X >= '0' && X <= '9' ? X-'0' :
+ X >= 'A' && X <= 'Z' ? X-'A'+10 : X-'a'+10);
+}
+
+
+/*
+ Convert password from hex string (as stored in mysql.user) to binary form.
+ SYNOPSIS
+ get_salt_from_password_323()
+ res OUT store salt here
+ password IN password string as stored in mysql.user
+ NOTE
+ This function does not have length check for passwords. It will just crash
+ Password hashes in old format must have length divisible by 8
+*/
+
+void get_salt_from_password_323(ulong *res, const char *password)
+{
+ res[0]= res[1]= 0;
+ if (password)
+ {
+ while (*password)
+ {
+ ulong val=0;
+ uint i;
+ for (i=0 ; i < 8 ; i++)
+ val=(val << 4)+char_val(*password++);
+ *res++=val;
+ }
+ }
+}
+
+
+/*
+ Convert scrambled password from binary form to asciiz hex string.
+ SYNOPSIS
+ make_password_from_salt_323()
+ to OUT store resulting string password here, at least 17 bytes
+ salt IN password in salt format, 2 ulongs
+*/
+
+void make_password_from_salt_323(char *to, const ulong *salt)
+{
+ sprintf(to,"%08lx%08lx", salt[0], salt[1]);
+}
+
+
+/*
+ **************** MySQL 4.1.1 authentication routines *************
+*/
+
+/**
+ Generate string of printable random characters of requested length.
+
+ @param to[out] Buffer for generation; must be at least length+1 bytes
+ long; result string is always null-terminated
+ length[in] How many random characters to put in buffer
+ rand_st Structure used for number generation
+*/
+
+void create_random_string(char *to, uint length,
+ struct my_rnd_struct *rand_st)
+{
+ char *end= to + length;
+ /* Use pointer arithmetics as it is faster way to do so. */
+ for (; to < end; to++)
+ *to= (char) (my_rnd(rand_st)*94+33);
+ *to= '\0';
+}
+
+
+/* Character to use as version identifier for version 4.1 */
+
+#define PVERSION41_CHAR '*'
+
+
+/*
+ Convert given octet sequence to asciiz string of hex characters;
+ str..str+len and 'to' may not overlap.
+ SYNOPSIS
+ octet2hex()
+ buf OUT output buffer. Must be at least 2*len+1 bytes
+ str, len IN the beginning and the length of the input string
+
+ RETURN
+ buf+len*2
+*/
+
+char *octet2hex(char *to, const char *str, uint len)
+{
+ const char *str_end= str + len;
+ for (; str != str_end; ++str)
+ {
+ *to++= _dig_vec_upper[((uchar) *str) >> 4];
+ *to++= _dig_vec_upper[((uchar) *str) & 0x0F];
+ }
+ *to= '\0';
+ return to;
+}
+
+
+/*
+ Convert given asciiz string of hex (0..9 a..f) characters to octet
+ sequence.
+ SYNOPSIS
+ hex2octet()
+ to OUT buffer to place result; must be at least len/2 bytes
+ str, len IN begin, length for character string; str and to may not
+ overlap; len % 2 == 0
+*/
+
+static void
+hex2octet(uint8 *to, const char *str, uint len)
+{
+ const char *str_end= str + len;
+ while (str < str_end)
+ {
+ register char tmp= char_val(*str++);
+ *to++= (tmp << 4) | char_val(*str++);
+ }
+}
+
+
+/*
+ Encrypt/Decrypt function used for password encryption in authentication.
+ Simple XOR is used here but it is OK as we crypt random strings. Note,
+ that XOR(s1, XOR(s1, s2)) == s2, XOR(s1, s2) == XOR(s2, s1)
+ SYNOPSIS
+ my_crypt()
+ to OUT buffer to hold crypted string; must be at least len bytes
+ long; to and s1 (or s2) may be the same.
+ s1, s2 IN input strings (of equal length)
+ len IN length of s1 and s2
+*/
+
+static void
+my_crypt(char *to, const uchar *s1, const uchar *s2, uint len)
+{
+ const uint8 *s1_end= s1 + len;
+ while (s1 < s1_end)
+ *to++= *s1++ ^ *s2++;
+}
+
+
+/**
+ Compute two stage SHA1 hash of the password :
+
+ hash_stage1=sha1("password")
+ hash_stage2=sha1(hash_stage1)
+
+ @param password [IN] Password string.
+ @param pass_len [IN] Length of the password.
+ @param hash_stage1 [OUT] sha1(password)
+ @param hash_stage2 [OUT] sha1(hash_stage1)
+*/
+
+inline static
+void compute_two_stage_sha1_hash(const char *password, size_t pass_len,
+ uint8 *hash_stage1, uint8 *hash_stage2)
+{
+ /* Stage 1: hash password */
+ compute_sha1_hash(hash_stage1, password, pass_len);
+
+ /* Stage 2 : hash first stage's output. */
+ compute_sha1_hash(hash_stage2, (const char *) hash_stage1, SHA1_HASH_SIZE);
+}
+
+
+/*
+ MySQL 4.1.1 password hashing: SHA conversion (see RFC 2289, 3174) twice
+ applied to the password string, and then produced octet sequence is
+ converted to hex string.
+ The result of this function is used as return value from PASSWORD() and
+ is stored in the database.
+ SYNOPSIS
+ my_make_scrambled_password()
+ buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string
+ password IN password string
+ pass_len IN length of password string
+*/
+
+void my_make_scrambled_password(char *to, const char *password,
+ size_t pass_len)
+{
+ uint8 hash_stage2[SHA1_HASH_SIZE];
+
+ /* Two stage SHA1 hash of the password. */
+ compute_two_stage_sha1_hash(password, pass_len, (uint8 *) to, hash_stage2);
+
+ /* convert hash_stage2 to hex string */
+ *to++= PVERSION41_CHAR;
+ octet2hex(to, (const char*) hash_stage2, SHA1_HASH_SIZE);
+}
+
+
+/*
+ Wrapper around my_make_scrambled_password() to maintain client lib ABI
+ compatibility.
+ In server code usage of my_make_scrambled_password() is preferred to
+ avoid strlen().
+ SYNOPSIS
+ make_scrambled_password()
+ buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string
+ password IN NULL-terminated password string
+*/
+
+void make_scrambled_password(char *to, const char *password)
+{
+ my_make_scrambled_password(to, password, strlen(password));
+}
+
+
+/*
+ Produce an obscure octet sequence from password and random
+ string, received from the server. This sequence corresponds to the
+ password, but password can not be easily restored from it. The sequence
+ is then sent to the server for validation. Trailing zero is not stored
+ in the buf as it is not needed.
+ This function is used by client to create authenticated reply to the
+ server's greeting.
+ SYNOPSIS
+ scramble()
+ buf OUT store scrambled string here. The buf must be at least
+ SHA1_HASH_SIZE bytes long.
+ message IN random message, must be exactly SCRAMBLE_LENGTH long and
+ NULL-terminated.
+ password IN users' password
+*/
+
+void
+scramble(char *to, const char *message, const char *password)
+{
+ uint8 hash_stage1[SHA1_HASH_SIZE];
+ uint8 hash_stage2[SHA1_HASH_SIZE];
+
+ /* Two stage SHA1 hash of the password. */
+ compute_two_stage_sha1_hash(password, strlen(password), hash_stage1,
+ hash_stage2);
+
+ /* create crypt string as sha1(message, hash_stage2) */;
+ compute_sha1_hash_multi((uint8 *) to, message, SCRAMBLE_LENGTH,
+ (const char *) hash_stage2, SHA1_HASH_SIZE);
+ my_crypt(to, (const uchar *) to, hash_stage1, SCRAMBLE_LENGTH);
+}
+
+
+/*
+ Check that scrambled message corresponds to the password; the function
+ is used by server to check that received reply is authentic.
+ This function does not check lengths of given strings: message must be
+ null-terminated, reply and hash_stage2 must be at least SHA1_HASH_SIZE
+ long (if not, something fishy is going on).
+ SYNOPSIS
+ check_scramble()
+ scramble clients' reply, presumably produced by scramble()
+ message original random string, previously sent to client
+ (presumably second argument of scramble()), must be
+ exactly SCRAMBLE_LENGTH long and NULL-terminated.
+ hash_stage2 hex2octet-decoded database entry
+ All params are IN.
+
+ RETURN VALUE
+ 0 password is correct
+ !0 password is invalid
+*/
+
+my_bool
+check_scramble(const uchar *scramble_arg, const char *message,
+ const uint8 *hash_stage2)
+{
+ uint8 buf[SHA1_HASH_SIZE];
+ uint8 hash_stage2_reassured[SHA1_HASH_SIZE];
+
+ /* create key to encrypt scramble */
+ compute_sha1_hash_multi(buf, message, SCRAMBLE_LENGTH,
+ (const char *) hash_stage2, SHA1_HASH_SIZE);
+ /* encrypt scramble */
+ my_crypt((char *) buf, buf, scramble_arg, SCRAMBLE_LENGTH);
+
+ /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */
+ compute_sha1_hash(hash_stage2_reassured, (const char *) buf, SHA1_HASH_SIZE);
+
+ return MY_TEST(memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE));
+}
+
+/*
+ Convert scrambled password from asciiz hex string to binary form.
+
+ SYNOPSIS
+ get_salt_from_password()
+ res OUT buf to hold password. Must be at least SHA1_HASH_SIZE
+ bytes long.
+ password IN 4.1.1 version value of user.password
+*/
+
+void get_salt_from_password(uint8 *hash_stage2, const char *password)
+{
+ hex2octet(hash_stage2, password+1 /* skip '*' */, SHA1_HASH_SIZE * 2);
+}
+
+/*
+ Convert scrambled password from binary form to asciiz hex string.
+ SYNOPSIS
+ make_password_from_salt()
+ to OUT store resulting string here, 2*SHA1_HASH_SIZE+2 bytes
+ salt IN password in salt format
+*/
+
+void make_password_from_salt(char *to, const uint8 *hash_stage2)
+{
+ *to++= PVERSION41_CHAR;
+ octet2hex(to, (const char*) hash_stage2, SHA1_HASH_SIZE);
+}
+
diff --git a/sql/plistsort.c b/sql/plistsort.c
new file mode 100644
index 00000000000..71d287e7b45
--- /dev/null
+++ b/sql/plistsort.c
@@ -0,0 +1,166 @@
+/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+/*
+things to define before including the file:
+
+#define LS_LIST_ITEM ListItem
+#define LS_COMPARE_FUNC_DECL compare_func var_name,
+#define LS_COMPARE_FUNC_CALL(list_el1, list_el2) (*var_name)(list_el1, list_el2)
+#define LS_NEXT(A) (A)->next
+#define LS_SET_NEXT(A,val) (A)->next= val
+#define LS_P_NEXT(A) &(A)->next
+#define LS_NAME plistsort
+#define LS_SCOPE static
+#define LS_STRUCT_NAME ls_struct_name
+*/
+
+typedef struct LS_STRUCT_NAME
+{
+ LS_LIST_ITEM *list1;
+ int list_len;
+ int return_point;
+} LS_STRUCT_NAME;
+
+LS_SCOPE LS_LIST_ITEM* LS_NAME(LS_COMPARE_FUNC_DECL LS_LIST_ITEM *list, int list_len)
+{
+ LS_LIST_ITEM *list_end;
+ LS_LIST_ITEM *sorted_list;
+
+ struct LS_STRUCT_NAME stack[63], *sp= stack;
+
+ if (list_len < 2)
+ return list;
+
+ sp->list_len= list_len;
+ sp->return_point= 2;
+
+recursion_point:
+
+ if (sp->list_len < 4)
+ {
+ LS_LIST_ITEM *e1, *e2;
+ sorted_list= list;
+ e1= LS_NEXT(sorted_list);
+ list_end= LS_NEXT(e1);
+ if (LS_COMPARE_FUNC_CALL(sorted_list, e1))
+ {
+ sorted_list= e1;
+ e1= list;
+ }
+ if (sp->list_len == 2)
+ {
+ LS_SET_NEXT(sorted_list, e1);
+ LS_SET_NEXT(e1, NULL);
+ goto exit_point;
+ }
+ e2= list_end;
+ list_end= LS_NEXT(e2);
+ if (LS_COMPARE_FUNC_CALL(e1, e2))
+ {
+ {
+ LS_LIST_ITEM *tmp_e= e1;
+ e1= e2;
+ e2= tmp_e;
+ }
+ if (LS_COMPARE_FUNC_CALL(sorted_list, e1))
+ {
+ LS_LIST_ITEM *tmp_e= sorted_list;
+ sorted_list= e1;
+ e1= tmp_e;
+ }
+ }
+
+ LS_SET_NEXT(sorted_list, e1);
+ LS_SET_NEXT(e1, e2);
+ LS_SET_NEXT(e2, NULL);
+ goto exit_point;
+ }
+
+ {
+ register struct LS_STRUCT_NAME *sp0= sp++;
+ sp->list_len= sp0->list_len >> 1;
+ sp0->list_len-= sp->list_len;
+ sp->return_point= 0;
+ }
+ goto recursion_point;
+return_point0:
+ sp->list1= sorted_list;
+ {
+ register struct LS_STRUCT_NAME *sp0= sp++;
+ list= list_end;
+ sp->list_len= sp0->list_len;
+ sp->return_point= 1;
+ }
+ goto recursion_point;
+return_point1:
+ {
+ register LS_LIST_ITEM **hook= &sorted_list;
+ register LS_LIST_ITEM *list1= sp->list1;
+ register LS_LIST_ITEM *list2= sorted_list;
+
+ if (LS_COMPARE_FUNC_CALL(list1, list2))
+ {
+ LS_LIST_ITEM *tmp_e= list2;
+ list2= list1;
+ list1= tmp_e;
+ }
+ for (;;)
+ {
+ *hook= list1;
+ do
+ {
+ if (!(list1= *(hook= LS_P_NEXT(list1))))
+ {
+ *hook= list2;
+ goto exit_point;
+ }
+ } while (LS_COMPARE_FUNC_CALL(list2, list1));
+
+ *hook= list2;
+ do
+ {
+ if (!(list2= *(hook= LS_P_NEXT(list2))))
+ {
+ *hook= list1;
+ goto exit_point;
+ }
+ } while (LS_COMPARE_FUNC_CALL(list1, list2));
+ }
+ }
+
+exit_point:
+ switch ((sp--)->return_point)
+ {
+ case 0: goto return_point0;
+ case 1: goto return_point1;
+ default:;
+ }
+
+ return sorted_list;
+}
+
+
+#undef LS_LIST_ITEM
+#undef LS_NEXT
+#undef LS_SET_NEXT
+#undef LS_P_NEXT
+#undef LS_NAME
+#undef LS_STRUCT_NAME
+#undef LS_SCOPE
+#undef LS_COMPARE_FUNC_DECL
+#undef LS_COMPARE_FUNC_CALL
+
diff --git a/sql/procedure.cc b/sql/procedure.cc
new file mode 100644
index 00000000000..8f9d6c0a7f3
--- /dev/null
+++ b/sql/procedure.cc
@@ -0,0 +1,102 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/* Procedures (functions with changes output of select) */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "procedure.h"
+#include "sql_analyse.h" // Includes procedure
+#ifdef USE_PROC_RANGE
+#include "proc_range.h"
+#endif
+
+static struct st_procedure_def {
+ const char *name;
+ Procedure *(*init)(THD *thd,ORDER *param,select_result *result,
+ List<Item> &field_list);
+} sql_procs[] = {
+#ifdef USE_PROC_RANGE
+ { "split_sum",proc_sum_range_init }, // Internal procedure at TCX
+ { "split_count",proc_count_range_init }, // Internal procedure at TCX
+ { "matris_ranges",proc_matris_range_init }, // Internal procedure at TCX
+#endif
+ { "analyse",proc_analyse_init } // Analyse a result
+};
+
+
+my_decimal *Item_proc_string::val_decimal(my_decimal *decimal_value)
+{
+ if (null_value)
+ return 0;
+ string2my_decimal(E_DEC_FATAL_ERROR, &str_value, decimal_value);
+ return (decimal_value);
+}
+
+
+my_decimal *Item_proc_int::val_decimal(my_decimal *decimal_value)
+{
+ if (null_value)
+ return 0;
+ int2my_decimal(E_DEC_FATAL_ERROR, value, unsigned_flag, decimal_value);
+ return (decimal_value);
+}
+
+
+my_decimal *Item_proc_real::val_decimal(my_decimal *decimal_value)
+{
+ if (null_value)
+ return 0;
+ double2my_decimal(E_DEC_FATAL_ERROR, value, decimal_value);
+ return (decimal_value);
+}
+
+
+/**
+ Setup handling of procedure.
+
+ @return
+ Return 0 if everything is ok
+*/
+
+
+Procedure *
+setup_procedure(THD *thd,ORDER *param,select_result *result,
+ List<Item> &field_list,int *error)
+{
+ uint i;
+ DBUG_ENTER("setup_procedure");
+ *error=0;
+ if (!param)
+ DBUG_RETURN(0);
+ for (i=0 ; i < array_elements(sql_procs) ; i++)
+ {
+ if (!my_strcasecmp(system_charset_info,
+ (*param->item)->name,sql_procs[i].name))
+ {
+ Procedure *proc=(*sql_procs[i].init)(thd,param,result,field_list);
+ *error= !proc;
+ DBUG_RETURN(proc);
+ }
+ }
+ my_error(ER_UNKNOWN_PROCEDURE, MYF(0), (*param->item)->name);
+ *error=1;
+ DBUG_RETURN(0);
+}
diff --git a/sql/procedure.h b/sql/procedure.h
new file mode 100644
index 00000000000..6870b97de57
--- /dev/null
+++ b/sql/procedure.h
@@ -0,0 +1,167 @@
+#ifndef PROCEDURE_INCLUDED
+#define PROCEDURE_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/* When using sql procedures */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "sql_class.h" /* select_result, set_var.h: THD */
+#include "set_var.h" /* Item */
+
+#define PROC_NO_SORT 1 /**< Bits in flags */
+#define PROC_GROUP 2 /**< proc must have group */
+
+/* Procedure items used by procedures to store values for send_result_set_metadata */
+
+class Item_proc :public Item
+{
+public:
+ Item_proc(const char *name_par): Item()
+ {
+ this->name=(char*) name_par;
+ }
+ enum Type type() const { return Item::PROC_ITEM; }
+ virtual void set(double nr)=0;
+ virtual void set(const char *str,uint length,CHARSET_INFO *cs)=0;
+ virtual void set(longlong nr)=0;
+ virtual enum_field_types field_type() const=0;
+ void set(const char *str) { set(str,(uint) strlen(str), default_charset()); }
+ void make_field(Send_field *tmp_field)
+ {
+ init_make_field(tmp_field,field_type());
+ }
+ unsigned int size_of() { return sizeof(*this);}
+ bool check_vcol_func_processor(uchar *int_arg)
+ {
+ return trace_unsupported_by_check_vcol_func_processor("proc");
+ }
+};
+
+class Item_proc_real :public Item_proc
+{
+ double value;
+public:
+ Item_proc_real(const char *name_par,uint dec) : Item_proc(name_par)
+ {
+ decimals=dec; max_length=float_length(dec);
+ }
+ enum Item_result result_type () const { return REAL_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; }
+ void set(double nr) { value=nr; }
+ void set(longlong nr) { value=(double) nr; }
+ void set(const char *str,uint length,CHARSET_INFO *cs)
+ {
+ int err_not_used;
+ char *end_not_used;
+ value= my_strntod(cs,(char*) str,length, &end_not_used, &err_not_used);
+ }
+ double val_real() { return value; }
+ longlong val_int() { return (longlong) value; }
+ String *val_str(String *s)
+ {
+ s->set_real(value,decimals,default_charset());
+ return s;
+ }
+ my_decimal *val_decimal(my_decimal *);
+ unsigned int size_of() { return sizeof(*this);}
+};
+
+class Item_proc_int :public Item_proc
+{
+ longlong value;
+public:
+ Item_proc_int(const char *name_par) :Item_proc(name_par)
+ { max_length=11; }
+ enum Item_result result_type () const { return INT_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; }
+ void set(double nr) { value=(longlong) nr; }
+ void set(longlong nr) { value=nr; }
+ void set(const char *str,uint length, CHARSET_INFO *cs)
+ { int err; value=my_strntoll(cs,str,length,10,NULL,&err); }
+ double val_real() { return (double) value; }
+ longlong val_int() { return value; }
+ String *val_str(String *s) { s->set(value, default_charset()); return s; }
+ my_decimal *val_decimal(my_decimal *);
+ unsigned int size_of() { return sizeof(*this);}
+};
+
+
+class Item_proc_string :public Item_proc
+{
+public:
+ Item_proc_string(const char *name_par,uint length) :Item_proc(name_par)
+ { this->max_length=length; }
+ enum Item_result result_type () const { return STRING_RESULT; }
+ enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; }
+ void set(double nr) { str_value.set_real(nr, 2, default_charset()); }
+ void set(longlong nr) { str_value.set(nr, default_charset()); }
+ void set(const char *str, uint length, CHARSET_INFO *cs)
+ { str_value.copy(str,length,cs); }
+ double val_real()
+ {
+ int err_not_used;
+ char *end_not_used;
+ CHARSET_INFO *cs= str_value.charset();
+ return my_strntod(cs, (char*) str_value.ptr(), str_value.length(),
+ &end_not_used, &err_not_used);
+ }
+ longlong val_int()
+ {
+ int err;
+ CHARSET_INFO *cs=str_value.charset();
+ return my_strntoll(cs,str_value.ptr(),str_value.length(),10,NULL,&err);
+ }
+ String *val_str(String*)
+ {
+ return null_value ? (String*) 0 : (String*) &str_value;
+ }
+ my_decimal *val_decimal(my_decimal *);
+ unsigned int size_of() { return sizeof(*this);}
+};
+
+/* The procedure class definitions */
+
+class Procedure {
+protected:
+ List<Item> *fields;
+ select_result *result;
+public:
+ const uint flags;
+ ORDER *group,*param_fields;
+ Procedure(select_result *res,uint flags_par) :result(res),flags(flags_par),
+ group(0),param_fields(0) {}
+ virtual ~Procedure() {group=param_fields=0; fields=0; }
+ virtual void add(void)=0;
+ virtual void end_group(void)=0;
+ virtual int send_row(List<Item> &fields)=0;
+ virtual bool change_columns(List<Item> &fields)=0;
+ virtual void update_refs(void) {}
+ virtual int end_of_records() { return 0; }
+};
+
+Procedure *setup_procedure(THD *thd,ORDER *proc_param,select_result *result,
+ List<Item> &field_list,int *error);
+
+#endif /* PROCEDURE_INCLUDED */
diff --git a/sql/protocol.cc b/sql/protocol.cc
new file mode 100644
index 00000000000..a6d67fd4a91
--- /dev/null
+++ b/sql/protocol.cc
@@ -0,0 +1,1563 @@
+/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2012, Monty Program Ab
+
+ 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 */
+
+/**
+ @file
+
+ Low level functions for storing data to be send to the MySQL client.
+ The actual communction is handled by the net_xxx functions in net_serv.cc
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "protocol.h"
+#include "sql_class.h" // THD
+#include <stdarg.h>
+
+static const unsigned int PACKET_BUFFER_EXTRA_ALLOC= 1024;
+/* Declared non-static only because of the embedded library. */
+bool net_send_error_packet(THD *, uint, const char *, const char *);
+/* Declared non-static only because of the embedded library. */
+bool net_send_ok(THD *, uint, uint, ulonglong, ulonglong, const char *);
+/* Declared non-static only because of the embedded library. */
+bool net_send_eof(THD *thd, uint server_status, uint statement_warn_count);
+#ifndef EMBEDDED_LIBRARY
+static bool write_eof_packet(THD *, NET *, uint, uint);
+#endif
+
+#ifndef EMBEDDED_LIBRARY
+bool Protocol::net_store_data(const uchar *from, size_t length)
+#else
+bool Protocol_binary::net_store_data(const uchar *from, size_t length)
+#endif
+{
+ ulong packet_length=packet->length();
+ /*
+ The +9 comes from that strings of length longer than 16M require
+ 9 bytes to be stored (see net_store_length).
+ */
+ if (packet_length+9+length > packet->alloced_length() &&
+ packet->realloc(packet_length+9+length))
+ return 1;
+ uchar *to= net_store_length((uchar*) packet->ptr()+packet_length, length);
+ memcpy(to,from,length);
+ packet->length((uint) (to+length-(uchar*) packet->ptr()));
+ return 0;
+}
+
+
+/*
+ net_store_data_cs() - extended version with character set conversion.
+
+ It is optimized for short strings whose length after
+ conversion is garanteed to be less than 251, which accupies
+ exactly one byte to store length. It allows not to use
+ the "convert" member as a temporary buffer, conversion
+ is done directly to the "packet" member.
+ The limit 251 is good enough to optimize send_result_set_metadata()
+ because column, table, database names fit into this limit.
+*/
+
+#ifndef EMBEDDED_LIBRARY
+bool Protocol::net_store_data_cs(const uchar *from, size_t length,
+ CHARSET_INFO *from_cs, CHARSET_INFO *to_cs)
+#else
+bool Protocol_binary::net_store_data_cs(const uchar *from, size_t length,
+ CHARSET_INFO *from_cs, CHARSET_INFO *to_cs)
+#endif
+{
+ uint dummy_errors;
+ /* Calculate maxumum possible result length */
+ uint conv_length= to_cs->mbmaxlen * length / from_cs->mbminlen;
+
+ if (conv_length > 250)
+ {
+ /*
+ For strings with conv_length greater than 250 bytes
+ we don't know how many bytes we will need to store length: one or two,
+ because we don't know result length until conversion is done.
+ For example, when converting from utf8 (mbmaxlen=3) to latin1,
+ conv_length=300 means that the result length can vary between 100 to 300.
+ length=100 needs one byte, length=300 needs to bytes.
+
+ Thus conversion directly to "packet" is not worthy.
+ Let's use "convert" as a temporary buffer.
+ */
+ return (convert->copy((const char*) from, length, from_cs,
+ to_cs, &dummy_errors) ||
+ net_store_data((const uchar*) convert->ptr(), convert->length()));
+ }
+
+ ulong packet_length= packet->length();
+ ulong new_length= packet_length + conv_length + 1;
+
+ if (new_length > packet->alloced_length() && packet->realloc(new_length))
+ return 1;
+
+ char *length_pos= (char*) packet->ptr() + packet_length;
+ char *to= length_pos + 1;
+
+ to+= copy_and_convert(to, conv_length, to_cs,
+ (const char*) from, length, from_cs, &dummy_errors);
+
+ net_store_length((uchar*) length_pos, to - length_pos - 1);
+ packet->length((uint) (to - packet->ptr()));
+ return 0;
+}
+
+
+/**
+ Send a error string to client.
+
+ Design note:
+
+ net_printf_error and net_send_error are low-level functions
+ that shall be used only when a new connection is being
+ established or at server startup.
+
+ For SIGNAL/RESIGNAL and GET DIAGNOSTICS functionality it's
+ critical that every error that can be intercepted is issued in one
+ place only, my_message_sql.
+
+ @param thd Thread handler
+ @param sql_errno The error code to send
+ @param err A pointer to the error message
+
+ @return
+ @retval FALSE The message was sent to the client
+ @retval TRUE An error occurred and the message wasn't sent properly
+*/
+
+bool net_send_error(THD *thd, uint sql_errno, const char *err,
+ const char* sqlstate)
+{
+ bool error;
+ DBUG_ENTER("net_send_error");
+
+ DBUG_ASSERT(!thd->spcont);
+ DBUG_ASSERT(sql_errno);
+ DBUG_ASSERT(err);
+
+ DBUG_PRINT("enter",("sql_errno: %d err: %s", sql_errno, err));
+
+ if (sqlstate == NULL)
+ sqlstate= mysql_errno_to_sqlstate(sql_errno);
+
+ /*
+ It's one case when we can push an error even though there
+ is an OK or EOF already.
+ */
+ thd->get_stmt_da()->set_overwrite_status(true);
+
+ /* Abort multi-result sets */
+ thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS;
+
+ error= net_send_error_packet(thd, sql_errno, err, sqlstate);
+
+ thd->get_stmt_da()->set_overwrite_status(false);
+
+ DBUG_RETURN(error);
+}
+
+/**
+ Return ok to the client.
+
+ The ok packet has the following structure:
+
+ - 0 : Marker (1 byte)
+ - affected_rows : Stored in 1-9 bytes
+ - id : Stored in 1-9 bytes
+ - server_status : Copy of thd->server_status; Can be used by client
+ to check if we are inside an transaction.
+ New in 4.0 protocol
+ - warning_count : Stored in 2 bytes; New in 4.1 protocol
+ - message : Stored as packed length (1-9 bytes) + message.
+ Is not stored if no message.
+
+ @param thd Thread handler
+ @param server_status The server status
+ @param statement_warn_count Total number of warnings
+ @param affected_rows Number of rows changed by statement
+ @param id Auto_increment id for first row (if used)
+ @param message Message to send to the client (Used by mysql_status)
+
+ @return
+ @retval FALSE The message was successfully sent
+ @retval TRUE An error occurred and the messages wasn't sent properly
+
+*/
+
+#ifndef EMBEDDED_LIBRARY
+bool
+net_send_ok(THD *thd,
+ uint server_status, uint statement_warn_count,
+ ulonglong affected_rows, ulonglong id, const char *message)
+{
+ NET *net= &thd->net;
+ uchar buff[MYSQL_ERRMSG_SIZE+10],*pos;
+ bool error= FALSE;
+ DBUG_ENTER("net_send_ok");
+
+ if (! net->vio) // hack for re-parsing queries
+ {
+ DBUG_PRINT("info", ("vio present: NO"));
+ DBUG_RETURN(FALSE);
+ }
+
+ buff[0]=0; // No fields
+ pos=net_store_length(buff+1,affected_rows);
+ pos=net_store_length(pos, id);
+ if (thd->client_capabilities & CLIENT_PROTOCOL_41)
+ {
+ DBUG_PRINT("info",
+ ("affected_rows: %lu id: %lu status: %u warning_count: %u",
+ (ulong) affected_rows,
+ (ulong) id,
+ (uint) (server_status & 0xffff),
+ (uint) statement_warn_count));
+ int2store(pos, server_status);
+ pos+=2;
+
+ /* We can only return up to 65535 warnings in two bytes */
+ uint tmp= MY_MIN(statement_warn_count, 65535);
+ int2store(pos, tmp);
+ pos+= 2;
+ }
+ else if (net->return_status) // For 4.0 protocol
+ {
+ int2store(pos, server_status);
+ pos+=2;
+ }
+ thd->get_stmt_da()->set_overwrite_status(true);
+
+ if (message && message[0])
+ pos= net_store_data(pos, (uchar*) message, strlen(message));
+ error= my_net_write(net, buff, (size_t) (pos-buff));
+ if (!error)
+ error= net_flush(net);
+
+
+ thd->get_stmt_da()->set_overwrite_status(false);
+ DBUG_PRINT("info", ("OK sent, so no more error sending allowed"));
+
+ DBUG_RETURN(error);
+}
+
+static uchar eof_buff[1]= { (uchar) 254 }; /* Marker for end of fields */
+
+/**
+ Send eof (= end of result set) to the client.
+
+ The eof packet has the following structure:
+
+ - 254 : Marker (1 byte)
+ - warning_count : Stored in 2 bytes; New in 4.1 protocol
+ - status_flag : Stored in 2 bytes;
+ For flags like SERVER_MORE_RESULTS_EXISTS.
+
+ Note that the warning count will not be sent if 'no_flush' is set as
+ we don't want to report the warning count until all data is sent to the
+ client.
+
+ @param thd Thread handler
+ @param server_status The server status
+ @param statement_warn_count Total number of warnings
+
+ @return
+ @retval FALSE The message was successfully sent
+ @retval TRUE An error occurred and the message wasn't sent properly
+*/
+
+bool
+net_send_eof(THD *thd, uint server_status, uint statement_warn_count)
+{
+ NET *net= &thd->net;
+ bool error= FALSE;
+ DBUG_ENTER("net_send_eof");
+ /* Set to TRUE if no active vio, to work well in case of --init-file */
+ if (net->vio != 0)
+ {
+ thd->get_stmt_da()->set_overwrite_status(true);
+ error= write_eof_packet(thd, net, server_status, statement_warn_count);
+ if (!error)
+ error= net_flush(net);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ DBUG_PRINT("info", ("EOF sent, so no more error sending allowed"));
+ }
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Format EOF packet according to the current protocol and
+ write it to the network output buffer.
+
+ @param thd The thread handler
+ @param net The network handler
+ @param server_status The server status
+ @param statement_warn_count The number of warnings
+
+
+ @return
+ @retval FALSE The message was sent successfully
+ @retval TRUE An error occurred and the messages wasn't sent properly
+*/
+
+static bool write_eof_packet(THD *thd, NET *net,
+ uint server_status,
+ uint statement_warn_count)
+{
+ bool error;
+ if (thd->client_capabilities & CLIENT_PROTOCOL_41)
+ {
+ uchar buff[5];
+ /*
+ Don't send warn count during SP execution, as the warn_list
+ is cleared between substatements, and mysqltest gets confused
+ */
+ uint tmp= MY_MIN(statement_warn_count, 65535);
+ buff[0]= 254;
+ int2store(buff+1, tmp);
+ /*
+ The following test should never be true, but it's better to do it
+ because if 'is_fatal_error' is set the server is not going to execute
+ other queries (see the if test in dispatch_command / COM_QUERY)
+ */
+ if (thd->is_fatal_error)
+ server_status&= ~SERVER_MORE_RESULTS_EXISTS;
+ int2store(buff + 3, server_status);
+ error= my_net_write(net, buff, 5);
+ }
+ else
+ error= my_net_write(net, eof_buff, 1);
+
+ return error;
+}
+
+/**
+ @param thd Thread handler
+ @param sql_errno The error code to send
+ @param err A pointer to the error message
+
+ @return
+ @retval FALSE The message was successfully sent
+ @retval TRUE An error occurred and the messages wasn't sent properly
+*/
+
+bool net_send_error_packet(THD *thd, uint sql_errno, const char *err,
+ const char* sqlstate)
+
+{
+ NET *net= &thd->net;
+ uint length;
+ /*
+ buff[]: sql_errno:2 + ('#':1 + SQLSTATE_LENGTH:5) + MYSQL_ERRMSG_SIZE:512
+ */
+ uint error;
+ char converted_err[MYSQL_ERRMSG_SIZE];
+ char buff[2+1+SQLSTATE_LENGTH+MYSQL_ERRMSG_SIZE], *pos;
+
+ DBUG_ENTER("send_error_packet");
+
+ if (net->vio == 0)
+ {
+ if (thd->bootstrap)
+ {
+ /* In bootstrap it's ok to print on stderr */
+ fprintf(stderr,"ERROR: %d %s\n",sql_errno,err);
+ }
+ DBUG_RETURN(FALSE);
+ }
+
+ int2store(buff,sql_errno);
+ pos= buff+2;
+ if (thd->client_capabilities & CLIENT_PROTOCOL_41)
+ {
+ /* The first # is to make the protocol backward compatible */
+ buff[2]= '#';
+ pos= strmov(buff+3, sqlstate);
+ }
+
+ convert_error_message(converted_err, sizeof(converted_err),
+ thd->variables.character_set_results,
+ err, strlen(err), system_charset_info, &error);
+ /* Converted error message is always null-terminated. */
+ length= (uint) (strmake(pos, converted_err, MYSQL_ERRMSG_SIZE - 1) - buff);
+
+ DBUG_RETURN(net_write_command(net,(uchar) 255, (uchar*) "", 0, (uchar*) buff,
+ length));
+}
+
+#endif /* EMBEDDED_LIBRARY */
+
+/**
+ Faster net_store_length when we know that length is less than 65536.
+ We keep a separate version for that range because it's widely used in
+ libmysql.
+
+ uint is used as agrument type because of MySQL type conventions:
+ - uint for 0..65536
+ - ulong for 0..4294967296
+ - ulonglong for bigger numbers.
+*/
+
+static uchar *net_store_length_fast(uchar *packet, uint length)
+{
+ if (length < 251)
+ {
+ *packet=(uchar) length;
+ return packet+1;
+ }
+ *packet++=252;
+ int2store(packet,(uint) length);
+ return packet+2;
+}
+
+/**
+ Send the status of the current statement execution over network.
+
+ @param thd in fact, carries two parameters, NET for the transport and
+ Diagnostics_area as the source of status information.
+
+ In MySQL, there are two types of SQL statements: those that return
+ a result set and those that return status information only.
+
+ If a statement returns a result set, it consists of 3 parts:
+ - result set meta-data
+ - variable number of result set rows (can be 0)
+ - followed and terminated by EOF or ERROR packet
+
+ Once the client has seen the meta-data information, it always
+ expects an EOF or ERROR to terminate the result set. If ERROR is
+ received, the result set rows are normally discarded (this is up
+ to the client implementation, libmysql at least does discard them).
+ EOF, on the contrary, means "successfully evaluated the entire
+ result set". Since we don't know how many rows belong to a result
+ set until it's evaluated, EOF/ERROR is the indicator of the end
+ of the row stream. Note, that we can not buffer result set rows
+ on the server -- there may be an arbitrary number of rows. But
+ we do buffer the last packet (EOF/ERROR) in the Diagnostics_area and
+ delay sending it till the very end of execution (here), to be able to
+ change EOF to an ERROR if commit failed or some other error occurred
+ during the last cleanup steps taken after execution.
+
+ A statement that does not return a result set doesn't send result
+ set meta-data either. Instead it returns one of:
+ - OK packet
+ - ERROR packet.
+ Similarly to the EOF/ERROR of the previous statement type, OK/ERROR
+ packet is "buffered" in the diagnostics area and sent to the client
+ in the end of statement.
+
+ @note This method defines a template, but delegates actual
+ sending of data to virtual Protocol::send_{ok,eof,error}. This
+ allows for implementation of protocols that "intercept" ok/eof/error
+ messages, and store them in memory, etc, instead of sending to
+ the client.
+
+ @pre The diagnostics area is assigned or disabled. It can not be empty
+ -- we assume that every SQL statement or COM_* command
+ generates OK, ERROR, or EOF status.
+
+ @post The status information is encoded to protocol format and sent to the
+ client.
+
+ @return We conventionally return void, since the only type of error
+ that can happen here is a NET (transport) error, and that one
+ will become visible when we attempt to read from the NET the
+ next command.
+ Diagnostics_area::is_sent is set for debugging purposes only.
+*/
+
+void Protocol::end_statement()
+{
+ DBUG_ENTER("Protocol::end_statement");
+ DBUG_ASSERT(! thd->get_stmt_da()->is_sent());
+ bool error= FALSE;
+
+ /* Can not be true, but do not take chances in production. */
+ if (thd->get_stmt_da()->is_sent())
+ DBUG_VOID_RETURN;
+
+ switch (thd->get_stmt_da()->status()) {
+ case Diagnostics_area::DA_ERROR:
+ /* The query failed, send error to log and abort bootstrap. */
+ error= send_error(thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message(),
+ thd->get_stmt_da()->get_sqlstate());
+ break;
+ case Diagnostics_area::DA_EOF:
+ error= send_eof(thd->server_status,
+ thd->get_stmt_da()->statement_warn_count());
+ break;
+ case Diagnostics_area::DA_OK:
+ error= send_ok(thd->server_status,
+ thd->get_stmt_da()->statement_warn_count(),
+ thd->get_stmt_da()->affected_rows(),
+ thd->get_stmt_da()->last_insert_id(),
+ thd->get_stmt_da()->message());
+ break;
+ case Diagnostics_area::DA_DISABLED:
+ break;
+ case Diagnostics_area::DA_EMPTY:
+ default:
+ DBUG_ASSERT(0);
+ error= send_ok(thd->server_status, 0, 0, 0, NULL);
+ break;
+ }
+ if (!error)
+ thd->get_stmt_da()->set_is_sent(true);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ A default implementation of "OK" packet response to the client.
+
+ Currently this implementation is re-used by both network-oriented
+ protocols -- the binary and text one. They do not differ
+ in their OK packet format, which allows for a significant simplification
+ on client side.
+*/
+
+bool Protocol::send_ok(uint server_status, uint statement_warn_count,
+ ulonglong affected_rows, ulonglong last_insert_id,
+ const char *message)
+{
+ DBUG_ENTER("Protocol::send_ok");
+ const bool retval=
+ net_send_ok(thd, server_status, statement_warn_count,
+ affected_rows, last_insert_id, message);
+ DBUG_RETURN(retval);
+}
+
+
+/**
+ A default implementation of "EOF" packet response to the client.
+
+ Binary and text protocol do not differ in their EOF packet format.
+*/
+
+bool Protocol::send_eof(uint server_status, uint statement_warn_count)
+{
+ DBUG_ENTER("Protocol::send_eof");
+ const bool retval= net_send_eof(thd, server_status, statement_warn_count);
+ DBUG_RETURN(retval);
+}
+
+
+/**
+ A default implementation of "ERROR" packet response to the client.
+
+ Binary and text protocol do not differ in ERROR packet format.
+*/
+
+bool Protocol::send_error(uint sql_errno, const char *err_msg,
+ const char *sql_state)
+{
+ DBUG_ENTER("Protocol::send_error");
+ const bool retval= net_send_error_packet(thd, sql_errno, err_msg, sql_state);
+ DBUG_RETURN(retval);
+}
+
+
+/**
+ Send a progress report to the client
+
+ What we send is:
+ header (255,255,255,1)
+ stage, max_stage as on byte integers
+ percentage withing the stage as percentage*1000
+ (that is, ratio*100000) as a 3 byte integer
+ proc_info as a string
+*/
+
+const uchar progress_header[2]= {(uchar) 255, (uchar) 255 };
+
+void net_send_progress_packet(THD *thd)
+{
+ uchar buff[200], *pos;
+ const char *proc_info= thd->proc_info ? thd->proc_info : "";
+ uint length= strlen(proc_info);
+ ulonglong progress;
+ DBUG_ENTER("net_send_progress_packet");
+
+ if (unlikely(!thd->net.vio))
+ DBUG_VOID_RETURN; // Socket is closed
+
+ pos= buff;
+ /*
+ Store number of strings first. This allows us to later expand the
+ progress indicator if needed.
+ */
+ *pos++= (uchar) 1; // Number of strings
+ *pos++= (uchar) thd->progress.stage + 1;
+ /*
+ We have the MY_MAX() here to avoid problems if max_stage is not set,
+ which may happen during automatic repair of table
+ */
+ *pos++= (uchar) MY_MAX(thd->progress.max_stage, thd->progress.stage + 1);
+ progress= 0;
+ if (thd->progress.max_counter)
+ progress= 100000ULL * thd->progress.counter / thd->progress.max_counter;
+ int3store(pos, progress); // Between 0 & 100000
+ pos+= 3;
+ pos= net_store_data(pos, (const uchar*) proc_info,
+ MY_MIN(length, sizeof(buff)-7));
+ net_write_command(&thd->net, (uchar) 255, progress_header,
+ sizeof(progress_header), (uchar*) buff,
+ (uint) (pos - buff));
+ DBUG_VOID_RETURN;
+}
+
+
+/****************************************************************************
+ Functions used by the protocol functions (like net_send_ok) to store
+ strings and numbers in the header result packet.
+****************************************************************************/
+
+/* The following will only be used for short strings < 65K */
+
+uchar *net_store_data(uchar *to, const uchar *from, size_t length)
+{
+ to=net_store_length_fast(to,length);
+ memcpy(to,from,length);
+ return to+length;
+}
+
+uchar *net_store_data(uchar *to,int32 from)
+{
+ char buff[20];
+ uint length=(uint) (int10_to_str(from,buff,10)-buff);
+ to=net_store_length_fast(to,length);
+ memcpy(to,buff,length);
+ return to+length;
+}
+
+uchar *net_store_data(uchar *to,longlong from)
+{
+ char buff[22];
+ uint length=(uint) (longlong10_to_str(from,buff,10)-buff);
+ to=net_store_length_fast(to,length);
+ memcpy(to,buff,length);
+ return to+length;
+}
+
+
+/*****************************************************************************
+ Default Protocol functions
+*****************************************************************************/
+
+void Protocol::init(THD *thd_arg)
+{
+ thd=thd_arg;
+ packet= &thd->packet;
+ convert= &thd->convert_buffer;
+#ifndef DBUG_OFF
+ field_types= 0;
+#endif
+}
+
+/**
+ Finish the result set with EOF packet, as is expected by the client,
+ if there is an error evaluating the next row and a continue handler
+ for the error.
+*/
+
+void Protocol::end_partial_result_set(THD *thd_arg)
+{
+ net_send_eof(thd_arg, thd_arg->server_status,
+ 0 /* no warnings, we're inside SP */);
+}
+
+
+bool Protocol::flush()
+{
+#ifndef EMBEDDED_LIBRARY
+ bool error;
+ thd->get_stmt_da()->set_overwrite_status(true);
+ error= net_flush(&thd->net);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ return error;
+#else
+ return 0;
+#endif
+}
+
+#ifndef EMBEDDED_LIBRARY
+
+/**
+ Send name and type of result to client.
+
+ Sum fields has table name empty and field_name.
+
+ @param THD Thread data object
+ @param list List of items to send to client
+ @param flag Bit mask with the following functions:
+ - 1 send number of rows
+ - 2 send default values
+ - 4 don't write eof packet
+
+ @retval
+ 0 ok
+ @retval
+ 1 Error (Note that in this case the error is not sent to the
+ client)
+*/
+bool Protocol::send_result_set_metadata(List<Item> *list, uint flags)
+{
+ List_iterator_fast<Item> it(*list);
+ Item *item;
+ uchar buff[MAX_FIELD_WIDTH];
+ String tmp((char*) buff,sizeof(buff),&my_charset_bin);
+ Protocol_text prot(thd);
+ String *local_packet= prot.storage_packet();
+ CHARSET_INFO *thd_charset= thd->variables.character_set_results;
+ DBUG_ENTER("Protocol::send_result_set_metadata");
+
+ if (flags & SEND_NUM_ROWS)
+ { // Packet with number of elements
+ uchar *pos= net_store_length(buff, list->elements);
+ if (my_net_write(&thd->net, buff, (size_t) (pos-buff)))
+ DBUG_RETURN(1);
+ }
+
+#ifndef DBUG_OFF
+ field_types= (enum_field_types*) thd->alloc(sizeof(field_types) *
+ list->elements);
+ uint count= 0;
+#endif
+
+ /* We have to reallocate it here as a stored procedure may have reset it */
+ (void) local_packet->alloc(thd->variables.net_buffer_length);
+
+ while ((item=it++))
+ {
+ char *pos;
+ CHARSET_INFO *cs= system_charset_info;
+ Send_field field;
+ item->make_field(&field);
+
+ /* Keep things compatible for old clients */
+ if (field.type == MYSQL_TYPE_VARCHAR)
+ field.type= MYSQL_TYPE_VAR_STRING;
+
+ prot.prepare_for_resend();
+
+ if (thd->client_capabilities & CLIENT_PROTOCOL_41)
+ {
+ if (prot.store(STRING_WITH_LEN("def"), cs, thd_charset) ||
+ prot.store(field.db_name, (uint) strlen(field.db_name),
+ cs, thd_charset) ||
+ prot.store(field.table_name, (uint) strlen(field.table_name),
+ cs, thd_charset) ||
+ prot.store(field.org_table_name, (uint) strlen(field.org_table_name),
+ cs, thd_charset) ||
+ prot.store(field.col_name, (uint) strlen(field.col_name),
+ cs, thd_charset) ||
+ prot.store(field.org_col_name, (uint) strlen(field.org_col_name),
+ cs, thd_charset) ||
+ local_packet->realloc(local_packet->length()+12))
+ goto err;
+ /* Store fixed length fields */
+ pos= (char*) local_packet->ptr()+local_packet->length();
+ *pos++= 12; // Length of packed fields
+ /* inject a NULL to test the client */
+ DBUG_EXECUTE_IF("poison_rs_fields", pos[-1]= (char) 0xfb;);
+ if (item->charset_for_protocol() == &my_charset_bin || thd_charset == NULL)
+ {
+ /* No conversion */
+ int2store(pos, item->charset_for_protocol()->number);
+ int4store(pos+2, field.length);
+ }
+ else
+ {
+ /* With conversion */
+ uint32 field_length, max_length;
+ int2store(pos, thd_charset->number);
+ /*
+ For TEXT/BLOB columns, field_length describes the maximum data
+ length in bytes. There is no limit to the number of characters
+ that a TEXT column can store, as long as the data fits into
+ the designated space.
+ For the rest of textual columns, field_length is evaluated as
+ char_count * mbmaxlen, where character count is taken from the
+ definition of the column. In other words, the maximum number
+ of characters here is limited by the column definition.
+
+ When one has a LONG TEXT column with a single-byte
+ character set, and the connection character set is multi-byte, the
+ client may get fields longer than UINT_MAX32, due to
+ <character set column> -> <character set connection> conversion.
+ In that case column max length does not fit into the 4 bytes
+ reserved for it in the protocol.
+ */
+ max_length= (field.type >= MYSQL_TYPE_TINY_BLOB &&
+ field.type <= MYSQL_TYPE_BLOB) ?
+ field.length / item->collation.collation->mbminlen :
+ field.length / item->collation.collation->mbmaxlen;
+ field_length= char_to_byte_length_safe(max_length,
+ thd_charset->mbmaxlen);
+ int4store(pos + 2, field_length);
+ }
+ pos[6]= field.type;
+ int2store(pos+7,field.flags);
+ pos[9]= (char) field.decimals;
+ pos[10]= 0; // For the future
+ pos[11]= 0; // For the future
+ pos+= 12;
+ }
+ else
+ {
+ if (prot.store(field.table_name, (uint) strlen(field.table_name),
+ cs, thd_charset) ||
+ prot.store(field.col_name, (uint) strlen(field.col_name),
+ cs, thd_charset) ||
+ local_packet->realloc(local_packet->length()+10))
+ goto err;
+ pos= (char*) local_packet->ptr()+local_packet->length();
+ pos[0]=3;
+ int3store(pos+1,field.length);
+ pos[4]=1;
+ pos[5]=field.type;
+ pos[6]=3;
+ int2store(pos+7,field.flags);
+ pos[9]= (char) field.decimals;
+ pos+= 10;
+ }
+ local_packet->length((uint) (pos - local_packet->ptr()));
+ if (flags & SEND_DEFAULTS)
+ item->send(&prot, &tmp); // Send default value
+ if (prot.write())
+ DBUG_RETURN(1);
+#ifndef DBUG_OFF
+ field_types[count++]= field.type;
+#endif
+ }
+
+ if (flags & SEND_EOF)
+ {
+ /*
+ Mark the end of meta-data result set, and store thd->server_status,
+ to show that there is no cursor.
+ Send no warning information, as it will be sent at statement end.
+ */
+ if (write_eof_packet(thd, &thd->net, thd->server_status,
+ thd->get_stmt_da()->current_statement_warn_count()))
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(prepare_for_send(list->elements));
+
+err:
+ my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES),
+ MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(1); /* purecov: inspected */
+}
+
+
+bool Protocol::write()
+{
+ DBUG_ENTER("Protocol::write");
+ DBUG_RETURN(my_net_write(&thd->net, (uchar*) packet->ptr(),
+ packet->length()));
+}
+#endif /* EMBEDDED_LIBRARY */
+
+
+/**
+ Send one result set row.
+
+ @param row_items a collection of column values for that row
+
+ @return Error status.
+ @retval TRUE Error.
+ @retval FALSE Success.
+*/
+
+bool Protocol::send_result_set_row(List<Item> *row_items)
+{
+ char buffer[MAX_FIELD_WIDTH];
+ String str_buffer(buffer, sizeof (buffer), &my_charset_bin);
+ List_iterator_fast<Item> it(*row_items);
+
+ DBUG_ENTER("Protocol::send_result_set_row");
+
+ for (Item *item= it++; item; item= it++)
+ {
+ if (item->send(this, &str_buffer))
+ {
+ // If we're out of memory, reclaim some, to help us recover.
+ this->free();
+ DBUG_RETURN(TRUE);
+ }
+ /* Item::send() may generate an error. If so, abort the loop. */
+ if (thd->is_error())
+ DBUG_RETURN(TRUE);
+
+ /*
+ Reset str_buffer to its original state, as it may have been altered in
+ Item::send().
+ */
+ str_buffer.set(buffer, sizeof(buffer), &my_charset_bin);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Send \\0 end terminated string.
+
+ @param from NullS or \\0 terminated string
+
+ @note
+ In most cases one should use store(from, length) instead of this function
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool Protocol::store(const char *from, CHARSET_INFO *cs)
+{
+ if (!from)
+ return store_null();
+ uint length= strlen(from);
+ return store(from, length, cs);
+}
+
+
+/**
+ Send a set of strings as one long string with ',' in between.
+*/
+
+bool Protocol::store(I_List<i_string>* str_list)
+{
+ char buf[256];
+ String tmp(buf, sizeof(buf), &my_charset_bin);
+ uint32 len;
+ I_List_iterator<i_string> it(*str_list);
+ i_string* s;
+
+ tmp.length(0);
+ while ((s=it++))
+ {
+ tmp.append(s->ptr);
+ tmp.append(',');
+ }
+ if ((len= tmp.length()))
+ len--; // Remove last ','
+ return store((char*) tmp.ptr(), len, tmp.charset());
+}
+
+/****************************************************************************
+ Functions to handle the simple (default) protocol where everything is
+ This protocol is the one that is used by default between the MySQL server
+ and client when you are not using prepared statements.
+
+ All data are sent as 'packed-string-length' followed by 'string-data'
+****************************************************************************/
+
+#ifndef EMBEDDED_LIBRARY
+void Protocol_text::prepare_for_resend()
+{
+ packet->length(0);
+#ifndef DBUG_OFF
+ field_pos= 0;
+#endif
+}
+
+bool Protocol_text::store_null()
+{
+#ifndef DBUG_OFF
+ field_pos++;
+#endif
+ char buff[1];
+ buff[0]= (char)251;
+ return packet->append(buff, sizeof(buff), PACKET_BUFFER_EXTRA_ALLOC);
+}
+#endif
+
+
+/**
+ Auxilary function to convert string to the given character set
+ and store in network buffer.
+*/
+
+bool Protocol::store_string_aux(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs)
+{
+ /* 'tocs' is set 0 when client issues SET character_set_results=NULL */
+ if (tocs && !my_charset_same(fromcs, tocs) &&
+ fromcs != &my_charset_bin &&
+ tocs != &my_charset_bin)
+ {
+ /* Store with conversion */
+ return net_store_data_cs((uchar*) from, length, fromcs, tocs);
+ }
+ /* Store without conversion */
+ return net_store_data((uchar*) from, length);
+}
+
+
+bool Protocol_text::store(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_DECIMAL ||
+ field_types[field_pos] == MYSQL_TYPE_BIT ||
+ field_types[field_pos] == MYSQL_TYPE_NEWDECIMAL ||
+ (field_types[field_pos] >= MYSQL_TYPE_ENUM &&
+ field_types[field_pos] <= MYSQL_TYPE_GEOMETRY));
+ field_pos++;
+#endif
+ return store_string_aux(from, length, fromcs, tocs);
+}
+
+
+bool Protocol_text::store(const char *from, size_t length,
+ CHARSET_INFO *fromcs)
+{
+ CHARSET_INFO *tocs= this->thd->variables.character_set_results;
+#ifndef DBUG_OFF
+ DBUG_PRINT("info", ("Protocol_text::store field %u (%u): %.*s", field_pos,
+ field_count, (int) length, (length == 0 ? "" : from)));
+ DBUG_ASSERT(field_pos < field_count);
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_DECIMAL ||
+ field_types[field_pos] == MYSQL_TYPE_BIT ||
+ field_types[field_pos] == MYSQL_TYPE_NEWDECIMAL ||
+ field_types[field_pos] == MYSQL_TYPE_NEWDATE ||
+ (field_types[field_pos] >= MYSQL_TYPE_ENUM &&
+ field_types[field_pos] <= MYSQL_TYPE_GEOMETRY));
+ field_pos++;
+#endif
+ return store_string_aux(from, length, fromcs, tocs);
+}
+
+
+bool Protocol_text::store_tiny(longlong from)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 || field_types[field_pos] == MYSQL_TYPE_TINY);
+ field_pos++;
+#endif
+ char buff[20];
+ return net_store_data((uchar*) buff,
+ (size_t) (int10_to_str((int) from, buff, -10) - buff));
+}
+
+
+bool Protocol_text::store_short(longlong from)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_YEAR ||
+ field_types[field_pos] == MYSQL_TYPE_SHORT);
+ field_pos++;
+#endif
+ char buff[20];
+ return net_store_data((uchar*) buff,
+ (size_t) (int10_to_str((int) from, buff, -10) -
+ buff));
+}
+
+
+bool Protocol_text::store_long(longlong from)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_INT24 ||
+ field_types[field_pos] == MYSQL_TYPE_LONG);
+ field_pos++;
+#endif
+ char buff[20];
+ return net_store_data((uchar*) buff,
+ (size_t) (int10_to_str((long int)from, buff,
+ (from <0)?-10:10)-buff));
+}
+
+
+bool Protocol_text::store_longlong(longlong from, bool unsigned_flag)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_LONGLONG);
+ field_pos++;
+#endif
+ char buff[22];
+ return net_store_data((uchar*) buff,
+ (size_t) (longlong10_to_str(from,buff,
+ unsigned_flag ? 10 : -10)-
+ buff));
+}
+
+
+bool Protocol_text::store_decimal(const my_decimal *d)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_NEWDECIMAL);
+ field_pos++;
+#endif
+ char buff[DECIMAL_MAX_STR_LENGTH];
+ String str(buff, sizeof(buff), &my_charset_bin);
+ (void) my_decimal2string(E_DEC_FATAL_ERROR, d, 0, 0, 0, &str);
+ return net_store_data((uchar*) str.ptr(), str.length());
+}
+
+
+bool Protocol_text::store(float from, uint32 decimals, String *buffer)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_FLOAT);
+ field_pos++;
+#endif
+ buffer->set_real((double) from, decimals, thd->charset());
+ return net_store_data((uchar*) buffer->ptr(), buffer->length());
+}
+
+
+bool Protocol_text::store(double from, uint32 decimals, String *buffer)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_DOUBLE);
+ field_pos++;
+#endif
+ buffer->set_real(from, decimals, thd->charset());
+ return net_store_data((uchar*) buffer->ptr(), buffer->length());
+}
+
+
+bool Protocol_text::store(Field *field)
+{
+ if (field->is_null())
+ return store_null();
+#ifndef DBUG_OFF
+ field_pos++;
+#endif
+ char buff[MAX_FIELD_WIDTH];
+ String str(buff,sizeof(buff), &my_charset_bin);
+ CHARSET_INFO *tocs= this->thd->variables.character_set_results;
+#ifndef DBUG_OFF
+ TABLE *table= field->table;
+ my_bitmap_map *old_map= 0;
+ if (table->file)
+ old_map= dbug_tmp_use_all_columns(table, table->read_set);
+#endif
+
+ field->val_str(&str);
+#ifndef DBUG_OFF
+ if (old_map)
+ dbug_tmp_restore_column_map(table->read_set, old_map);
+#endif
+
+ return store_string_aux(str.ptr(), str.length(), str.charset(), tocs);
+}
+
+
+bool Protocol_text::store(MYSQL_TIME *tm, int decimals)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_DATETIME ||
+ field_types[field_pos] == MYSQL_TYPE_TIMESTAMP);
+ field_pos++;
+#endif
+ char buff[MAX_DATE_STRING_REP_LENGTH];
+ uint length= my_datetime_to_str(tm, buff, decimals);
+ return net_store_data((uchar*) buff, length);
+}
+
+
+bool Protocol_text::store_date(MYSQL_TIME *tm)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_DATE);
+ field_pos++;
+#endif
+ char buff[MAX_DATE_STRING_REP_LENGTH];
+ size_t length= my_date_to_str(tm, buff);
+ return net_store_data((uchar*) buff, length);
+}
+
+
+bool Protocol_text::store_time(MYSQL_TIME *tm, int decimals)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_TIME);
+ field_pos++;
+#endif
+ char buff[MAX_DATE_STRING_REP_LENGTH];
+ uint length= my_time_to_str(tm, buff, decimals);
+ return net_store_data((uchar*) buff, length);
+}
+
+/**
+ Assign OUT-parameters to user variables.
+
+ @param sp_params List of PS/SP parameters (both input and output).
+
+ @return Error status.
+ @retval FALSE Success.
+ @retval TRUE Error.
+*/
+
+bool Protocol_text::send_out_parameters(List<Item_param> *sp_params)
+{
+ DBUG_ASSERT(sp_params->elements ==
+ thd->lex->prepared_stmt_params.elements);
+
+ List_iterator_fast<Item_param> item_param_it(*sp_params);
+ List_iterator_fast<LEX_STRING> user_var_name_it(thd->lex->prepared_stmt_params);
+
+ while (true)
+ {
+ Item_param *item_param= item_param_it++;
+ LEX_STRING *user_var_name= user_var_name_it++;
+
+ if (!item_param || !user_var_name)
+ break;
+
+ if (!item_param->get_out_param_info())
+ continue; // It's an IN-parameter.
+
+ Item_func_set_user_var *suv=
+ new Item_func_set_user_var(*user_var_name, item_param);
+ /*
+ Item_func_set_user_var is not fixed after construction, call
+ fix_fields().
+ */
+ if (suv->fix_fields(thd, NULL))
+ return TRUE;
+
+ if (suv->check(FALSE))
+ return TRUE;
+
+ if (suv->update())
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/****************************************************************************
+ Functions to handle the binary protocol used with prepared statements
+
+ Data format:
+
+ [ok:1] reserved ok packet
+ [null_field:(field_count+7+2)/8] reserved to send null data. The size is
+ calculated using:
+ bit_fields= (field_count+7+2)/8;
+ 2 bits are reserved for identifying type
+ of package.
+ [[length]data] data field (the length applies only for
+ string/binary/time/timestamp fields and
+ rest of them are not sent as they have
+ the default length that client understands
+ based on the field type
+ [..]..[[length]data] data
+****************************************************************************/
+
+bool Protocol_binary::prepare_for_send(uint num_columns)
+{
+ Protocol::prepare_for_send(num_columns);
+ bit_fields= (field_count+9)/8;
+ return packet->alloc(bit_fields+1);
+
+ /* prepare_for_resend will be called after this one */
+}
+
+
+void Protocol_binary::prepare_for_resend()
+{
+ packet->length(bit_fields+1);
+ bzero((uchar*) packet->ptr(), 1+bit_fields);
+ field_pos=0;
+}
+
+
+bool Protocol_binary::store(const char *from, size_t length,
+ CHARSET_INFO *fromcs)
+{
+ CHARSET_INFO *tocs= thd->variables.character_set_results;
+ field_pos++;
+ return store_string_aux(from, length, fromcs, tocs);
+}
+
+bool Protocol_binary::store(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs)
+{
+ field_pos++;
+ return store_string_aux(from, length, fromcs, tocs);
+}
+
+bool Protocol_binary::store_null()
+{
+ uint offset= (field_pos+2)/8+1, bit= (1 << ((field_pos+2) & 7));
+ /* Room for this as it's allocated in prepare_for_send */
+ char *to= (char*) packet->ptr()+offset;
+ *to= (char) ((uchar) *to | (uchar) bit);
+ field_pos++;
+ return 0;
+}
+
+
+bool Protocol_binary::store_tiny(longlong from)
+{
+ char buff[1];
+ field_pos++;
+ buff[0]= (uchar) from;
+ return packet->append(buff, sizeof(buff), PACKET_BUFFER_EXTRA_ALLOC);
+}
+
+
+bool Protocol_binary::store_short(longlong from)
+{
+ field_pos++;
+ char *to= packet->prep_append(2, PACKET_BUFFER_EXTRA_ALLOC);
+ if (!to)
+ return 1;
+ int2store(to, (int) from);
+ return 0;
+}
+
+
+bool Protocol_binary::store_long(longlong from)
+{
+ field_pos++;
+ char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC);
+ if (!to)
+ return 1;
+ int4store(to, from);
+ return 0;
+}
+
+
+bool Protocol_binary::store_longlong(longlong from, bool unsigned_flag)
+{
+ field_pos++;
+ char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC);
+ if (!to)
+ return 1;
+ int8store(to, from);
+ return 0;
+}
+
+bool Protocol_binary::store_decimal(const my_decimal *d)
+{
+#ifndef DBUG_OFF
+ DBUG_ASSERT(field_types == 0 ||
+ field_types[field_pos] == MYSQL_TYPE_NEWDECIMAL);
+ field_pos++;
+#endif
+ char buff[DECIMAL_MAX_STR_LENGTH];
+ String str(buff, sizeof(buff), &my_charset_bin);
+ (void) my_decimal2string(E_DEC_FATAL_ERROR, d, 0, 0, 0, &str);
+ return store(str.ptr(), str.length(), str.charset());
+}
+
+bool Protocol_binary::store(float from, uint32 decimals, String *buffer)
+{
+ field_pos++;
+ char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC);
+ if (!to)
+ return 1;
+ float4store(to, from);
+ return 0;
+}
+
+
+bool Protocol_binary::store(double from, uint32 decimals, String *buffer)
+{
+ field_pos++;
+ char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC);
+ if (!to)
+ return 1;
+ float8store(to, from);
+ return 0;
+}
+
+
+bool Protocol_binary::store(Field *field)
+{
+ /*
+ We should not increment field_pos here as send_binary() will call another
+ protocol function to do this for us
+ */
+ if (field->is_null())
+ return store_null();
+ return field->send_binary(this);
+}
+
+
+bool Protocol_binary::store(MYSQL_TIME *tm, int decimals)
+{
+ char buff[12],*pos;
+ uint length;
+ field_pos++;
+ pos= buff+1;
+
+ int2store(pos, tm->year);
+ pos[2]= (uchar) tm->month;
+ pos[3]= (uchar) tm->day;
+ pos[4]= (uchar) tm->hour;
+ pos[5]= (uchar) tm->minute;
+ pos[6]= (uchar) tm->second;
+ DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS ||
+ (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS));
+ if (decimals != AUTO_SEC_PART_DIGITS)
+ my_time_trunc(tm, decimals);
+ int4store(pos+7, tm->second_part);
+ if (tm->second_part)
+ length=11;
+ else if (tm->hour || tm->minute || tm->second)
+ length=7;
+ else if (tm->year || tm->month || tm->day)
+ length=4;
+ else
+ length=0;
+ buff[0]=(char) length; // Length is stored first
+ return packet->append(buff, length+1, PACKET_BUFFER_EXTRA_ALLOC);
+}
+
+bool Protocol_binary::store_date(MYSQL_TIME *tm)
+{
+ tm->hour= tm->minute= tm->second=0;
+ tm->second_part= 0;
+ return Protocol_binary::store(tm, 0);
+}
+
+
+bool Protocol_binary::store_time(MYSQL_TIME *tm, int decimals)
+{
+ char buff[13], *pos;
+ uint length;
+ field_pos++;
+ pos= buff+1;
+ pos[0]= tm->neg ? 1 : 0;
+ if (tm->hour >= 24)
+ {
+ uint days= tm->hour/24;
+ tm->hour-= days*24;
+ tm->day+= days;
+ }
+ int4store(pos+1, tm->day);
+ pos[5]= (uchar) tm->hour;
+ pos[6]= (uchar) tm->minute;
+ pos[7]= (uchar) tm->second;
+ DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS ||
+ (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS));
+ if (decimals != AUTO_SEC_PART_DIGITS)
+ my_time_trunc(tm, decimals);
+ int4store(pos+8, tm->second_part);
+ if (tm->second_part)
+ length=12;
+ else if (tm->hour || tm->minute || tm->second || tm->day)
+ length=8;
+ else
+ length=0;
+ buff[0]=(char) length; // Length is stored first
+ return packet->append(buff, length+1, PACKET_BUFFER_EXTRA_ALLOC);
+}
+
+/**
+ Send a result set with OUT-parameter values by means of PS-protocol.
+
+ @param sp_params List of PS/SP parameters (both input and output).
+
+ @return Error status.
+ @retval FALSE Success.
+ @retval TRUE Error.
+*/
+
+bool Protocol_binary::send_out_parameters(List<Item_param> *sp_params)
+{
+ if (!(thd->client_capabilities & CLIENT_PS_MULTI_RESULTS))
+ {
+ /* The client does not support OUT-parameters. */
+ return FALSE;
+ }
+
+ List<Item> out_param_lst;
+
+ {
+ List_iterator_fast<Item_param> item_param_it(*sp_params);
+
+ while (true)
+ {
+ Item_param *item_param= item_param_it++;
+
+ if (!item_param)
+ break;
+
+ if (!item_param->get_out_param_info())
+ continue; // It's an IN-parameter.
+
+ if (out_param_lst.push_back(item_param))
+ return TRUE;
+ }
+ }
+
+ if (!out_param_lst.elements)
+ return FALSE;
+
+ /*
+ We have to set SERVER_PS_OUT_PARAMS in THD::server_status, because it
+ is used in send_result_set_metadata().
+ */
+
+ thd->server_status|= SERVER_PS_OUT_PARAMS | SERVER_MORE_RESULTS_EXISTS;
+
+ /* Send meta-data. */
+ if (send_result_set_metadata(&out_param_lst, SEND_NUM_ROWS | SEND_EOF))
+ return TRUE;
+
+ /* Send data. */
+
+ prepare_for_resend();
+
+ if (send_result_set_row(&out_param_lst))
+ return TRUE;
+
+ if (write())
+ return TRUE;
+
+ /* Restore THD::server_status. */
+ thd->server_status&= ~SERVER_PS_OUT_PARAMS;
+
+ /* Send EOF-packet. */
+ net_send_eof(thd, thd->server_status, 0);
+
+ /*
+ Reset SERVER_MORE_RESULTS_EXISTS bit, because this is the last packet
+ for sure.
+ */
+ thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS;
+
+ return FALSE;
+}
diff --git a/sql/protocol.h b/sql/protocol.h
new file mode 100644
index 00000000000..c58de68289f
--- /dev/null
+++ b/sql/protocol.h
@@ -0,0 +1,221 @@
+#ifndef PROTOCOL_INCLUDED
+#define PROTOCOL_INCLUDED
+
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_error.h"
+#include "my_decimal.h" /* my_decimal */
+
+class i_string;
+class Field;
+class THD;
+class Item_param;
+typedef struct st_mysql_field MYSQL_FIELD;
+typedef struct st_mysql_rows MYSQL_ROWS;
+
+class Protocol
+{
+protected:
+ THD *thd;
+ String *packet;
+ /* Used by net_store_data() for charset conversions */
+ String *convert;
+ uint field_pos;
+#ifndef DBUG_OFF
+ enum enum_field_types *field_types;
+#endif
+ uint field_count;
+#ifndef EMBEDDED_LIBRARY
+ bool net_store_data(const uchar *from, size_t length);
+ bool net_store_data_cs(const uchar *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
+#else
+ virtual bool net_store_data(const uchar *from, size_t length);
+ virtual bool net_store_data_cs(const uchar *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
+ char **next_field;
+ MYSQL_FIELD *next_mysql_field;
+ MEM_ROOT *alloc;
+#endif
+ /*
+ The following two are low-level functions that are invoked from
+ higher-level store_xxx() funcs. The data is stored into this->packet.
+ */
+ bool store_string_aux(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
+
+ virtual bool send_ok(uint server_status, uint statement_warn_count,
+ ulonglong affected_rows, ulonglong last_insert_id,
+ const char *message);
+
+ virtual bool send_eof(uint server_status, uint statement_warn_count);
+
+ virtual bool send_error(uint sql_errno, const char *err_msg,
+ const char *sql_state);
+
+public:
+ Protocol() {}
+ Protocol(THD *thd_arg) { init(thd_arg); }
+ virtual ~Protocol() {}
+ void init(THD* thd_arg);
+
+ enum { SEND_NUM_ROWS= 1, SEND_DEFAULTS= 2, SEND_EOF= 4 };
+ virtual bool send_result_set_metadata(List<Item> *list, uint flags);
+ bool send_result_set_row(List<Item> *row_items);
+
+ bool store(I_List<i_string> *str_list);
+ bool store(const char *from, CHARSET_INFO *cs);
+ String *storage_packet() { return packet; }
+ inline void free() { packet->free(); }
+ virtual bool write();
+ inline bool store(int from)
+ { return store_long((longlong) from); }
+ inline bool store(uint32 from)
+ { return store_long((longlong) from); }
+ inline bool store(longlong from)
+ { return store_longlong((longlong) from, 0); }
+ inline bool store(ulonglong from)
+ { return store_longlong((longlong) from, 1); }
+ inline bool store(String *str)
+ { return store((char*) str->ptr(), str->length(), str->charset()); }
+
+ virtual bool prepare_for_send(uint num_columns)
+ {
+ field_count= num_columns;
+ return 0;
+ }
+ virtual bool flush();
+ virtual void end_partial_result_set(THD *thd);
+ virtual void prepare_for_resend()=0;
+
+ virtual bool store_null()=0;
+ virtual bool store_tiny(longlong from)=0;
+ virtual bool store_short(longlong from)=0;
+ virtual bool store_long(longlong from)=0;
+ virtual bool store_longlong(longlong from, bool unsigned_flag)=0;
+ virtual bool store_decimal(const my_decimal *)=0;
+ virtual bool store(const char *from, size_t length, CHARSET_INFO *cs)=0;
+ virtual bool store(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs)=0;
+ virtual bool store(float from, uint32 decimals, String *buffer)=0;
+ virtual bool store(double from, uint32 decimals, String *buffer)=0;
+ virtual bool store(MYSQL_TIME *time, int decimals)=0;
+ virtual bool store_date(MYSQL_TIME *time)=0;
+ virtual bool store_time(MYSQL_TIME *time, int decimals)=0;
+ virtual bool store(Field *field)=0;
+
+ virtual bool send_out_parameters(List<Item_param> *sp_params)=0;
+#ifdef EMBEDDED_LIBRARY
+ int begin_dataset();
+ virtual void remove_last_row() {}
+#else
+ void remove_last_row() {}
+#endif
+ enum enum_protocol_type
+ {
+ /*
+ Before adding a new type, please make sure
+ there is enough storage for it in Query_cache_query_flags.
+ */
+ PROTOCOL_TEXT= 0, PROTOCOL_BINARY= 1, PROTOCOL_LOCAL= 2
+ };
+ virtual enum enum_protocol_type type()= 0;
+
+ void end_statement();
+};
+
+
+/** Class used for the old (MySQL 4.0 protocol). */
+
+class Protocol_text :public Protocol
+{
+public:
+ Protocol_text() {}
+ Protocol_text(THD *thd_arg) :Protocol(thd_arg) {}
+ virtual void prepare_for_resend();
+ virtual bool store_null();
+ virtual bool store_tiny(longlong from);
+ virtual bool store_short(longlong from);
+ virtual bool store_long(longlong from);
+ virtual bool store_longlong(longlong from, bool unsigned_flag);
+ virtual bool store_decimal(const my_decimal *);
+ virtual bool store(const char *from, size_t length, CHARSET_INFO *cs);
+ virtual bool store(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
+ virtual bool store(MYSQL_TIME *time, int decimals);
+ virtual bool store_date(MYSQL_TIME *time);
+ virtual bool store_time(MYSQL_TIME *time, int decimals);
+ virtual bool store(float nr, uint32 decimals, String *buffer);
+ virtual bool store(double from, uint32 decimals, String *buffer);
+ virtual bool store(Field *field);
+
+ virtual bool send_out_parameters(List<Item_param> *sp_params);
+#ifdef EMBEDDED_LIBRARY
+ void remove_last_row();
+#endif
+ virtual enum enum_protocol_type type() { return PROTOCOL_TEXT; };
+};
+
+
+class Protocol_binary :public Protocol
+{
+private:
+ uint bit_fields;
+public:
+ Protocol_binary() {}
+ Protocol_binary(THD *thd_arg) :Protocol(thd_arg) {}
+ virtual bool prepare_for_send(uint num_columns);
+ virtual void prepare_for_resend();
+#ifdef EMBEDDED_LIBRARY
+ virtual bool write();
+ bool net_store_data(const uchar *from, size_t length);
+ bool net_store_data_cs(const uchar *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
+#endif
+ virtual bool store_null();
+ virtual bool store_tiny(longlong from);
+ virtual bool store_short(longlong from);
+ virtual bool store_long(longlong from);
+ virtual bool store_longlong(longlong from, bool unsigned_flag);
+ virtual bool store_decimal(const my_decimal *);
+ virtual bool store(const char *from, size_t length, CHARSET_INFO *cs);
+ virtual bool store(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
+ virtual bool store(MYSQL_TIME *time, int decimals);
+ virtual bool store_date(MYSQL_TIME *time);
+ virtual bool store_time(MYSQL_TIME *time, int decimals);
+ virtual bool store(float nr, uint32 decimals, String *buffer);
+ virtual bool store(double from, uint32 decimals, String *buffer);
+ virtual bool store(Field *field);
+
+ virtual bool send_out_parameters(List<Item_param> *sp_params);
+
+ virtual enum enum_protocol_type type() { return PROTOCOL_BINARY; };
+};
+
+void send_warning(THD *thd, uint sql_errno, const char *err=0);
+bool net_send_error(THD *thd, uint sql_errno, const char *err,
+ const char* sqlstate);
+void net_send_progress_packet(THD *thd);
+uchar *net_store_data(uchar *to,const uchar *from, size_t length);
+uchar *net_store_data(uchar *to,int32 from);
+uchar *net_store_data(uchar *to,longlong from);
+
+#endif /* PROTOCOL_INCLUDED */
diff --git a/sql/records.cc b/sql/records.cc
new file mode 100644
index 00000000000..bfce2f83967
--- /dev/null
+++ b/sql/records.cc
@@ -0,0 +1,713 @@
+/*
+ Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma implementation /* gcc class implementation */
+#endif
+
+/**
+ @file
+
+ @brief
+ Functions for easy reading of records, possible through a cache
+*/
+
+#include <my_global.h>
+#include "records.h"
+#include "sql_priv.h"
+#include "records.h"
+#include "filesort.h" // filesort_free_buffers
+#include "opt_range.h" // SQL_SELECT
+#include "sql_class.h" // THD
+#include "sql_base.h"
+
+static int rr_quick(READ_RECORD *info);
+int rr_sequential(READ_RECORD *info);
+static int rr_from_tempfile(READ_RECORD *info);
+static int rr_unpack_from_tempfile(READ_RECORD *info);
+static int rr_unpack_from_buffer(READ_RECORD *info);
+static int rr_from_pointers(READ_RECORD *info);
+static int rr_from_cache(READ_RECORD *info);
+static int init_rr_cache(THD *thd, READ_RECORD *info);
+static int rr_cmp(uchar *a,uchar *b);
+static int rr_index_first(READ_RECORD *info);
+static int rr_index_last(READ_RECORD *info);
+static int rr_index(READ_RECORD *info);
+static int rr_index_desc(READ_RECORD *info);
+
+
+/**
+ Initialize READ_RECORD structure to perform full index scan in desired
+ direction using read_record.read_record() interface
+
+ This function has been added at late stage and is used only by
+ UPDATE/DELETE. Other statements perform index scans using
+ join_read_first/next functions.
+
+ @param info READ_RECORD structure to initialize.
+ @param thd Thread handle
+ @param table Table to be accessed
+ @param print_error If true, call table->file->print_error() if an error
+ occurs (except for end-of-records error)
+ @param idx index to scan
+ @param reverse Scan in the reverse direction
+*/
+
+void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table,
+ bool print_error, uint idx, bool reverse)
+{
+ int error;
+ empty_record(table);
+ bzero((char*) info,sizeof(*info));
+ info->thd= thd;
+ info->table= table;
+ info->record= table->record[0];
+ info->print_error= print_error;
+ info->unlock_row= rr_unlock_row;
+
+ table->status=0; /* And it's always found */
+ if (!table->file->inited &&
+ (error= table->file->ha_index_init(idx, 1)))
+ {
+ if (print_error)
+ table->file->print_error(error, MYF(0));
+ }
+
+ /* read_record will be changed to rr_index in rr_index_first */
+ info->read_record= reverse ? rr_index_last : rr_index_first;
+}
+
+
+/*
+ init_read_record is used to scan by using a number of different methods.
+ Which method to use is set-up in this call so that later calls to
+ the info->read_record will call the appropriate method using a function
+ pointer.
+
+ There are five methods that relate completely to the sort function
+ filesort. The result of a filesort is retrieved using read_record
+ calls. The other two methods are used for normal table access.
+
+ The filesort will produce references to the records sorted, these
+ references can be stored in memory or in a temporary file.
+
+ The temporary file is normally used when the references doesn't fit into
+ a properly sized memory buffer. For most small queries the references
+ are stored in the memory buffer.
+ SYNOPSIS
+ init_read_record()
+ info OUT read structure
+ thd Thread handle
+ table Table the data [originally] comes from.
+ select SQL_SELECT structure. We may select->quick or
+ select->file as data source
+ use_record_cache Call file->extra_opt(HA_EXTRA_CACHE,...)
+ if we're going to do sequential read and some
+ additional conditions are satisfied.
+ print_error Copy this to info->print_error
+ disable_rr_cache Don't use rr_from_cache (used by sort-union
+ index-merge which produces rowid sequences that
+ are already ordered)
+
+ DESCRIPTION
+ This function sets up reading data via one of the methods:
+
+ The temporary file is also used when performing an update where a key is
+ modified.
+
+ Methods used when ref's are in memory (using rr_from_pointers):
+ rr_unpack_from_buffer:
+ ----------------------
+ This method is used when table->sort.addon_field is allocated.
+ This is allocated for most SELECT queries not involving any BLOB's.
+ In this case the records are fetched from a memory buffer.
+ rr_from_pointers:
+ -----------------
+ Used when the above is not true, UPDATE, DELETE and so forth and
+ SELECT's involving BLOB's. It is also used when the addon_field
+ buffer is not allocated due to that its size was bigger than the
+ session variable max_length_for_sort_data.
+ In this case the record data is fetched from the handler using the
+ saved reference using the rnd_pos handler call.
+
+ Methods used when ref's are in a temporary file (using rr_from_tempfile)
+ rr_unpack_from_tempfile:
+ ------------------------
+ Same as rr_unpack_from_buffer except that references are fetched from
+ temporary file. Should obviously not really happen other than in
+ strange configurations.
+
+ rr_from_tempfile:
+ -----------------
+ Same as rr_from_pointers except that references are fetched from
+ temporary file instead of from
+ rr_from_cache:
+ --------------
+ This is a special variant of rr_from_tempfile that can be used for
+ handlers that is not using the HA_FAST_KEY_READ table flag. Instead
+ of reading the references one by one from the temporary file it reads
+ a set of them, sorts them and reads all of them into a buffer which
+ is then used for a number of subsequent calls to rr_from_cache.
+ It is only used for SELECT queries and a number of other conditions
+ on table size.
+
+ All other accesses use either index access methods (rr_quick) or a full
+ table scan (rr_sequential).
+ rr_quick:
+ ---------
+ rr_quick uses one of the QUICK_SELECT classes in opt_range.cc to
+ perform an index scan. There are loads of functionality hidden
+ in these quick classes. It handles all index scans of various kinds.
+ rr_sequential:
+ --------------
+ This is the most basic access method of a table using rnd_init,
+ rnd_next and rnd_end. No indexes are used.
+*/
+
+bool init_read_record(READ_RECORD *info,THD *thd, TABLE *table,
+ SQL_SELECT *select,
+ int use_record_cache, bool print_error,
+ bool disable_rr_cache)
+{
+ IO_CACHE *tempfile;
+ DBUG_ENTER("init_read_record");
+
+ bzero((char*) info,sizeof(*info));
+ info->thd=thd;
+ info->table=table;
+ info->forms= &info->table; /* Only one table */
+
+ if ((table->s->tmp_table == INTERNAL_TMP_TABLE ||
+ table->s->tmp_table == NON_TRANSACTIONAL_TMP_TABLE) &&
+ !table->sort.addon_field)
+ (void) table->file->extra(HA_EXTRA_MMAP);
+
+ if (table->sort.addon_field)
+ {
+ info->rec_buf= table->sort.addon_buf;
+ info->ref_length= table->sort.addon_length;
+ }
+ else
+ {
+ empty_record(table);
+ info->record= table->record[0];
+ info->ref_length= table->file->ref_length;
+ }
+ info->select=select;
+ info->print_error=print_error;
+ info->unlock_row= rr_unlock_row;
+ info->ignore_not_found_rows= 0;
+ table->status=0; /* And it's always found */
+
+ if (select && my_b_inited(&select->file))
+ tempfile= &select->file;
+ else
+ tempfile= table->sort.io_cache;
+ if (tempfile && my_b_inited(tempfile) &&
+ !(select && select->quick))
+ {
+ DBUG_PRINT("info",("using rr_from_tempfile"));
+ info->read_record= (table->sort.addon_field ?
+ rr_unpack_from_tempfile : rr_from_tempfile);
+ info->io_cache=tempfile;
+ reinit_io_cache(info->io_cache,READ_CACHE,0L,0,0);
+ info->ref_pos=table->file->ref;
+ if (!table->file->inited)
+ if (table->file->ha_rnd_init_with_error(0))
+ DBUG_RETURN(1);
+
+ /*
+ table->sort.addon_field is checked because if we use addon fields,
+ it doesn't make sense to use cache - we don't read from the table
+ and table->sort.io_cache is read sequentially
+ */
+ if (!disable_rr_cache &&
+ !table->sort.addon_field &&
+ thd->variables.read_rnd_buff_size &&
+ !(table->file->ha_table_flags() & HA_FAST_KEY_READ) &&
+ (table->db_stat & HA_READ_ONLY ||
+ table->reginfo.lock_type <= TL_READ_NO_INSERT) &&
+ (ulonglong) table->s->reclength* (table->file->stats.records+
+ table->file->stats.deleted) >
+ (ulonglong) MIN_FILE_LENGTH_TO_USE_ROW_CACHE &&
+ info->io_cache->end_of_file/info->ref_length * table->s->reclength >
+ (my_off_t) MIN_ROWS_TO_USE_TABLE_CACHE &&
+ !table->s->blob_fields &&
+ info->ref_length <= MAX_REFLENGTH)
+ {
+ if (! init_rr_cache(thd, info))
+ {
+ DBUG_PRINT("info",("using rr_from_cache"));
+ info->read_record=rr_from_cache;
+ }
+ }
+ }
+ else if (select && select->quick)
+ {
+ DBUG_PRINT("info",("using rr_quick"));
+ info->read_record=rr_quick;
+ }
+ else if (table->sort.record_pointers)
+ {
+ DBUG_PRINT("info",("using record_pointers"));
+ if (table->file->ha_rnd_init_with_error(0))
+ DBUG_RETURN(1);
+ info->cache_pos=table->sort.record_pointers;
+ info->cache_end=info->cache_pos+
+ table->sort.found_records*info->ref_length;
+ info->read_record= (table->sort.addon_field ?
+ rr_unpack_from_buffer : rr_from_pointers);
+ }
+ else
+ {
+ DBUG_PRINT("info",("using rr_sequential"));
+ info->read_record=rr_sequential;
+ if (table->file->ha_rnd_init_with_error(1))
+ DBUG_RETURN(1);
+ /* We can use record cache if we don't update dynamic length tables */
+ if (!table->no_cache &&
+ (use_record_cache > 0 ||
+ (int) table->reginfo.lock_type <= (int) TL_READ_HIGH_PRIORITY ||
+ !(table->s->db_options_in_use & HA_OPTION_PACK_RECORD) ||
+ (use_record_cache < 0 &&
+ !(table->file->ha_table_flags() & HA_NOT_DELETE_WITH_CACHE))))
+ (void) table->file->extra_opt(HA_EXTRA_CACHE,
+ thd->variables.read_buff_size);
+ }
+ /* Condition pushdown to storage engine */
+ if (thd->use_cond_push(table->file) && select && select->cond &&
+ (select->cond->used_tables() & table->map) &&
+ !table->file->pushed_cond)
+ table->file->cond_push(select->cond);
+
+ DBUG_RETURN(0);
+} /* init_read_record */
+
+
+
+void end_read_record(READ_RECORD *info)
+{ /* free cache if used */
+ if (info->cache)
+ {
+ my_free_lock(info->cache);
+ info->cache=0;
+ }
+ if (info->table)
+ {
+ filesort_free_buffers(info->table,0);
+ if (info->table->created)
+ (void) info->table->file->extra(HA_EXTRA_NO_CACHE);
+ if (info->read_record != rr_quick) // otherwise quick_range does it
+ (void) info->table->file->ha_index_or_rnd_end();
+ info->table=0;
+ }
+}
+
+static int rr_handle_error(READ_RECORD *info, int error)
+{
+ if (info->thd->killed)
+ {
+ info->thd->send_kill_message();
+ return 1;
+ }
+
+ if (error == HA_ERR_END_OF_FILE)
+ error= -1;
+ else
+ {
+ if (info->print_error)
+ info->table->file->print_error(error, MYF(0));
+ if (error < 0) // Fix negative BDB errno
+ error= 1;
+ }
+ return error;
+}
+
+
+/** Read a record from head-database. */
+
+static int rr_quick(READ_RECORD *info)
+{
+ int tmp;
+ while ((tmp= info->select->quick->get_next()))
+ {
+ if (info->thd->killed || (tmp != HA_ERR_RECORD_DELETED))
+ {
+ tmp= rr_handle_error(info, tmp);
+ break;
+ }
+ }
+ if (info->table->vfield)
+ update_virtual_fields(info->thd, info->table);
+ return tmp;
+}
+
+
+/**
+ Reads first row in an index scan.
+
+ @param info Scan info
+
+ @retval
+ 0 Ok
+ @retval
+ -1 End of records
+ @retval
+ 1 Error
+*/
+
+static int rr_index_first(READ_RECORD *info)
+{
+ int tmp;
+ // tell handler that we are doing an index scan
+ if ((tmp = info->table->file->prepare_index_scan()))
+ {
+ tmp= rr_handle_error(info, tmp);
+ return tmp;
+ }
+
+ tmp= info->table->file->ha_index_first(info->record);
+ info->read_record= rr_index;
+ if (tmp)
+ tmp= rr_handle_error(info, tmp);
+ return tmp;
+}
+
+
+/**
+ Reads last row in an index scan.
+
+ @param info Scan info
+
+ @retval
+ 0 Ok
+ @retval
+ -1 End of records
+ @retval
+ 1 Error
+*/
+
+static int rr_index_last(READ_RECORD *info)
+{
+ int tmp= info->table->file->ha_index_last(info->record);
+ info->read_record= rr_index_desc;
+ if (tmp)
+ tmp= rr_handle_error(info, tmp);
+ return tmp;
+}
+
+
+/**
+ Reads index sequentially after first row.
+
+ Read the next index record (in forward direction) and translate return
+ value.
+
+ @param info Scan info
+
+ @retval
+ 0 Ok
+ @retval
+ -1 End of records
+ @retval
+ 1 Error
+*/
+
+static int rr_index(READ_RECORD *info)
+{
+ int tmp= info->table->file->ha_index_next(info->record);
+ if (tmp)
+ tmp= rr_handle_error(info, tmp);
+ return tmp;
+}
+
+
+/**
+ Reads index sequentially from the last row to the first.
+
+ Read the prev index record (in backward direction) and translate return
+ value.
+
+ @param info Scan info
+
+ @retval
+ 0 Ok
+ @retval
+ -1 End of records
+ @retval
+ 1 Error
+*/
+
+static int rr_index_desc(READ_RECORD *info)
+{
+ int tmp= info->table->file->ha_index_prev(info->record);
+ if (tmp)
+ tmp= rr_handle_error(info, tmp);
+ return tmp;
+}
+
+
+int rr_sequential(READ_RECORD *info)
+{
+ int tmp;
+ while ((tmp= info->table->file->ha_rnd_next(info->record)))
+ {
+ /*
+ rnd_next can return RECORD_DELETED for MyISAM when one thread is
+ reading and another deleting without locks.
+ */
+ if (info->thd->killed || (tmp != HA_ERR_RECORD_DELETED))
+ {
+ tmp= rr_handle_error(info, tmp);
+ break;
+ }
+ }
+ if (!tmp && info->table->vfield)
+ update_virtual_fields(info->thd, info->table);
+ return tmp;
+}
+
+
+static int rr_from_tempfile(READ_RECORD *info)
+{
+ int tmp;
+ for (;;)
+ {
+ if (my_b_read(info->io_cache,info->ref_pos,info->ref_length))
+ return -1; /* End of file */
+ if (!(tmp= info->table->file->ha_rnd_pos(info->record,info->ref_pos)))
+ break;
+ /* The following is extremely unlikely to happen */
+ if (tmp == HA_ERR_RECORD_DELETED ||
+ (tmp == HA_ERR_KEY_NOT_FOUND && info->ignore_not_found_rows))
+ continue;
+ tmp= rr_handle_error(info, tmp);
+ break;
+ }
+ return tmp;
+} /* rr_from_tempfile */
+
+
+/**
+ Read a result set record from a temporary file after sorting.
+
+ The function first reads the next sorted record from the temporary file.
+ into a buffer. If a success it calls a callback function that unpacks
+ the fields values use in the result set from this buffer into their
+ positions in the regular record buffer.
+
+ @param info Reference to the context including record descriptors
+
+ @retval
+ 0 Record successfully read.
+ @retval
+ -1 There is no record to be read anymore.
+*/
+
+static int rr_unpack_from_tempfile(READ_RECORD *info)
+{
+ if (my_b_read(info->io_cache, info->rec_buf, info->ref_length))
+ return -1;
+ TABLE *table= info->table;
+ (*table->sort.unpack)(table->sort.addon_field, info->rec_buf,
+ info->rec_buf + info->ref_length);
+
+ return 0;
+}
+
+static int rr_from_pointers(READ_RECORD *info)
+{
+ int tmp;
+ uchar *cache_pos;
+
+ for (;;)
+ {
+ if (info->cache_pos == info->cache_end)
+ return -1; /* End of file */
+ cache_pos= info->cache_pos;
+ info->cache_pos+= info->ref_length;
+
+ if (!(tmp= info->table->file->ha_rnd_pos(info->record,cache_pos)))
+ break;
+
+ /* The following is extremely unlikely to happen */
+ if (tmp == HA_ERR_RECORD_DELETED ||
+ (tmp == HA_ERR_KEY_NOT_FOUND && info->ignore_not_found_rows))
+ continue;
+ tmp= rr_handle_error(info, tmp);
+ break;
+ }
+ return tmp;
+}
+
+/**
+ Read a result set record from a buffer after sorting.
+
+ The function first reads the next sorted record from the sort buffer.
+ If a success it calls a callback function that unpacks
+ the fields values use in the result set from this buffer into their
+ positions in the regular record buffer.
+
+ @param info Reference to the context including record descriptors
+
+ @retval
+ 0 Record successfully read.
+ @retval
+ -1 There is no record to be read anymore.
+*/
+
+static int rr_unpack_from_buffer(READ_RECORD *info)
+{
+ if (info->cache_pos == info->cache_end)
+ return -1; /* End of buffer */
+ TABLE *table= info->table;
+ (*table->sort.unpack)(table->sort.addon_field, info->cache_pos,
+ info->cache_end);
+ info->cache_pos+= info->ref_length;
+
+ return 0;
+}
+ /* cacheing of records from a database */
+
+static int init_rr_cache(THD *thd, READ_RECORD *info)
+{
+ uint rec_cache_size;
+ DBUG_ENTER("init_rr_cache");
+
+ info->struct_length= 3+MAX_REFLENGTH;
+ info->reclength= ALIGN_SIZE(info->table->s->reclength+1);
+ if (info->reclength < info->struct_length)
+ info->reclength= ALIGN_SIZE(info->struct_length);
+
+ info->error_offset= info->table->s->reclength;
+ info->cache_records= (thd->variables.read_rnd_buff_size /
+ (info->reclength+info->struct_length));
+ rec_cache_size= info->cache_records*info->reclength;
+ info->rec_cache_size= info->cache_records*info->ref_length;
+
+ // We have to allocate one more byte to use uint3korr (see comments for it)
+ if (info->cache_records <= 2 ||
+ !(info->cache=(uchar*) my_malloc_lock(rec_cache_size+info->cache_records*
+ info->struct_length+1,
+ MYF(MY_THREAD_SPECIFIC))))
+ DBUG_RETURN(1);
+#ifdef HAVE_valgrind
+ // Avoid warnings in qsort
+ bzero(info->cache,rec_cache_size+info->cache_records* info->struct_length+1);
+#endif
+ DBUG_PRINT("info",("Allocated buffert for %d records",info->cache_records));
+ info->read_positions=info->cache+rec_cache_size;
+ info->cache_pos=info->cache_end=info->cache;
+ DBUG_RETURN(0);
+} /* init_rr_cache */
+
+
+static int rr_from_cache(READ_RECORD *info)
+{
+ reg1 uint i;
+ ulong length;
+ my_off_t rest_of_file;
+ int16 error;
+ uchar *position,*ref_position,*record_pos;
+ ulong record;
+
+ for (;;)
+ {
+ if (info->cache_pos != info->cache_end)
+ {
+ if (info->cache_pos[info->error_offset])
+ {
+ shortget(error,info->cache_pos);
+ if (info->print_error)
+ info->table->file->print_error(error,MYF(0));
+ }
+ else
+ {
+ error=0;
+ memcpy(info->record,info->cache_pos,
+ (size_t) info->table->s->reclength);
+ }
+ info->cache_pos+=info->reclength;
+ return ((int) error);
+ }
+ length=info->rec_cache_size;
+ rest_of_file=info->io_cache->end_of_file - my_b_tell(info->io_cache);
+ if ((my_off_t) length > rest_of_file)
+ length= (ulong) rest_of_file;
+ if (!length || my_b_read(info->io_cache,info->cache,length))
+ {
+ DBUG_PRINT("info",("Found end of file"));
+ return -1; /* End of file */
+ }
+
+ length/=info->ref_length;
+ position=info->cache;
+ ref_position=info->read_positions;
+ for (i=0 ; i < length ; i++,position+=info->ref_length)
+ {
+ memcpy(ref_position,position,(size_t) info->ref_length);
+ ref_position+=MAX_REFLENGTH;
+ int3store(ref_position,(long) i);
+ ref_position+=3;
+ }
+ my_qsort(info->read_positions, length, info->struct_length,
+ (qsort_cmp) rr_cmp);
+
+ position=info->read_positions;
+ for (i=0 ; i < length ; i++)
+ {
+ memcpy(info->ref_pos,position,(size_t) info->ref_length);
+ position+=MAX_REFLENGTH;
+ record=uint3korr(position);
+ position+=3;
+ record_pos=info->cache+record*info->reclength;
+ if ((error=(int16) info->table->file->ha_rnd_pos(record_pos,info->ref_pos)))
+ {
+ record_pos[info->error_offset]=1;
+ shortstore(record_pos,error);
+ DBUG_PRINT("error",("Got error: %d:%d when reading row",
+ my_errno, error));
+ }
+ else
+ record_pos[info->error_offset]=0;
+ }
+ info->cache_end=(info->cache_pos=info->cache)+length*info->reclength;
+ }
+} /* rr_from_cache */
+
+
+static int rr_cmp(uchar *a,uchar *b)
+{
+ if (a[0] != b[0])
+ return (int) a[0] - (int) b[0];
+ if (a[1] != b[1])
+ return (int) a[1] - (int) b[1];
+ if (a[2] != b[2])
+ return (int) a[2] - (int) b[2];
+#if MAX_REFLENGTH == 4
+ return (int) a[3] - (int) b[3];
+#else
+ if (a[3] != b[3])
+ return (int) a[3] - (int) b[3];
+ if (a[4] != b[4])
+ return (int) a[4] - (int) b[4];
+ if (a[5] != b[5])
+ return (int) a[5] - (int) b[5];
+ if (a[6] != b[6])
+ return (int) a[6] - (int) b[6];
+ return (int) a[7] - (int) b[7];
+#endif
+}
diff --git a/sql/records.h b/sql/records.h
new file mode 100644
index 00000000000..21477d4a30b
--- /dev/null
+++ b/sql/records.h
@@ -0,0 +1,85 @@
+#ifndef SQL_RECORDS_H
+#define SQL_RECORDS_H
+/* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+struct st_join_table;
+class handler;
+struct TABLE;
+class THD;
+class SQL_SELECT;
+class Copy_field;
+
+/**
+ A context for reading through a single table using a chosen access method:
+ index read, scan, etc, use of cache, etc.
+
+ Use by:
+ READ_RECORD read_record;
+ init_read_record(&read_record, ...);
+ while (read_record.read_record())
+ {
+ ...
+ }
+ end_read_record();
+*/
+
+struct READ_RECORD
+{
+ typedef int (*Read_func)(READ_RECORD*);
+ typedef void (*Unlock_row_func)(st_join_table *);
+ typedef int (*Setup_func)(struct st_join_table*);
+
+ TABLE *table; /* Head-form */
+ //handler *file;
+ TABLE **forms; /* head and ref forms */
+ Unlock_row_func unlock_row;
+ Read_func read_record;
+ THD *thd;
+ SQL_SELECT *select;
+ uint cache_records;
+ uint ref_length,struct_length,reclength,rec_cache_size,error_offset;
+ uint index;
+ uchar *ref_pos; /* pointer to form->refpos */
+ uchar *record;
+ uchar *rec_buf; /* to read field values after filesort */
+ uchar *cache,*cache_pos,*cache_end,*read_positions;
+ struct st_io_cache *io_cache;
+ bool print_error, ignore_not_found_rows;
+
+ /*
+ SJ-Materialization runtime may need to read fields from the materialized
+ table and unpack them into original table fields:
+ */
+ Copy_field *copy_field;
+ Copy_field *copy_field_end;
+public:
+ READ_RECORD() {}
+};
+
+bool init_read_record(READ_RECORD *info, THD *thd, TABLE *reg_form,
+ SQL_SELECT *select, int use_record_cache,
+ bool print_errors, bool disable_rr_cache);
+void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table,
+ bool print_error, uint idx, bool reverse);
+void end_read_record(READ_RECORD *info);
+
+void rr_unlock_row(st_join_table *tab);
+
+#endif /* SQL_RECORDS_H */
diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc
new file mode 100644
index 00000000000..3c99becf304
--- /dev/null
+++ b/sql/repl_failsafe.cc
@@ -0,0 +1,278 @@
+/*
+ Copyright (c) 2001, 2011, Oracle and/or its affiliates.
+
+ 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 */
+
+/**
+ @file
+
+ All of the functions defined in this file which are not used (the ones to
+ handle failsafe) are not used; their code has not been updated for more
+ than one year now so should be considered as BADLY BROKEN. Do not enable
+ it. The used functions (to handle LOAD DATA FROM MASTER, plus some small
+ functions like register_slave()) are working.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_parse.h" // check_access
+#ifdef HAVE_REPLICATION
+
+#include "repl_failsafe.h"
+#include "sql_acl.h" // REPL_SLAVE_ACL
+#include "sql_repl.h"
+#include "slave.h"
+#include "rpl_mi.h"
+#include "rpl_filter.h"
+#include "log_event.h"
+#include <mysql.h>
+
+#define SLAVE_LIST_CHUNK 128
+#define SLAVE_ERRMSG_SIZE (FN_REFLEN+64)
+
+
+ulong rpl_status=RPL_NULL;
+mysql_mutex_t LOCK_rpl_status;
+HASH slave_list;
+
+const char *rpl_role_type[] = {"MASTER","SLAVE",NullS};
+TYPELIB rpl_role_typelib = {array_elements(rpl_role_type)-1,"",
+ rpl_role_type, NULL};
+
+const char* rpl_status_type[]=
+{
+ "AUTH_MASTER","IDLE_SLAVE","ACTIVE_SLAVE","LOST_SOLDIER","TROOP_SOLDIER",
+ "RECOVERY_CAPTAIN","NULL",NullS
+};
+
+/*
+ All of the functions defined in this file which are not used (the ones to
+ handle failsafe) are not used; their code has not been updated for more than
+ one year now so should be considered as BADLY BROKEN. Do not enable it.
+ The used functions (to handle LOAD DATA FROM MASTER, plus some small
+ functions like register_slave()) are working.
+*/
+
+void change_rpl_status(ulong from_status, ulong to_status)
+{
+ mysql_mutex_lock(&LOCK_rpl_status);
+ if (rpl_status == from_status || rpl_status == RPL_ANY)
+ rpl_status = to_status;
+ mysql_mutex_unlock(&LOCK_rpl_status);
+}
+
+
+#define get_object(p, obj, msg) \
+{\
+ uint len = (uint)*p++; \
+ if (p + len > p_end || len >= sizeof(obj)) \
+ {\
+ errmsg= msg;\
+ goto err; \
+ }\
+ strmake(obj,(char*) p,len); \
+ p+= len; \
+}\
+
+
+void unregister_slave(THD* thd, bool only_mine, bool need_mutex)
+{
+ uint32 thd_server_id= thd->variables.server_id;
+ if (thd_server_id)
+ {
+ if (need_mutex)
+ mysql_mutex_lock(&LOCK_slave_list);
+
+ SLAVE_INFO* old_si;
+ if ((old_si = (SLAVE_INFO*)my_hash_search(&slave_list,
+ (uchar*)&thd_server_id, 4)) &&
+ (!only_mine || old_si->thd == thd))
+ my_hash_delete(&slave_list, (uchar*)old_si);
+
+ if (need_mutex)
+ mysql_mutex_unlock(&LOCK_slave_list);
+ }
+}
+
+
+/**
+ Register slave in 'slave_list' hash table.
+
+ @return
+ 0 ok
+ @return
+ 1 Error. Error message sent to client
+*/
+
+int register_slave(THD* thd, uchar* packet, uint packet_length)
+{
+ int res;
+ SLAVE_INFO *si;
+ uchar *p= packet, *p_end= packet + packet_length;
+ const char *errmsg= "Wrong parameters to function register_slave";
+
+ if (check_access(thd, REPL_SLAVE_ACL, any_db, NULL, NULL, 0, 0))
+ return 1;
+ if (!(si = (SLAVE_INFO*)my_malloc(sizeof(SLAVE_INFO), MYF(MY_WME))))
+ goto err2;
+
+ thd->variables.server_id= si->server_id= uint4korr(p);
+ p+= 4;
+ get_object(p,si->host, "Failed to register slave: too long 'report-host'");
+ get_object(p,si->user, "Failed to register slave: too long 'report-user'");
+ get_object(p,si->password, "Failed to register slave; too long 'report-password'");
+ if (p+10 > p_end)
+ goto err;
+ si->port= uint2korr(p);
+ p += 2;
+ /*
+ We need to by pass the bytes used in the fake rpl_recovery_rank
+ variable. It was removed in patch for BUG#13963. But this would
+ make a server with that patch unable to connect to an old master.
+ See: BUG#49259
+ */
+ // si->rpl_recovery_rank= uint4korr(p);
+ p += 4;
+ if (!(si->master_id= uint4korr(p)))
+ si->master_id= global_system_variables.server_id;
+ si->thd= thd;
+
+ mysql_mutex_lock(&LOCK_slave_list);
+ unregister_slave(thd,0,0);
+ res= my_hash_insert(&slave_list, (uchar*) si);
+ mysql_mutex_unlock(&LOCK_slave_list);
+ return res;
+
+err:
+ my_free(si);
+ my_message(ER_UNKNOWN_ERROR, errmsg, MYF(0)); /* purecov: inspected */
+err2:
+ return 1;
+}
+
+extern "C" uint32
+*slave_list_key(SLAVE_INFO* si, size_t *len,
+ my_bool not_used __attribute__((unused)))
+{
+ *len = 4;
+ return &si->server_id;
+}
+
+extern "C" void slave_info_free(void *s)
+{
+ my_free(s);
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_LOCK_slave_list;
+
+static PSI_mutex_info all_slave_list_mutexes[]=
+{
+ { &key_LOCK_slave_list, "LOCK_slave_list", PSI_FLAG_GLOBAL}
+};
+
+static void init_all_slave_list_mutexes(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_slave_list_mutexes);
+ PSI_server->register_mutex(category, all_slave_list_mutexes, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+void init_slave_list()
+{
+#ifdef HAVE_PSI_INTERFACE
+ init_all_slave_list_mutexes();
+#endif
+
+ my_hash_init(&slave_list, system_charset_info, SLAVE_LIST_CHUNK, 0, 0,
+ (my_hash_get_key) slave_list_key,
+ (my_hash_free_key) slave_info_free, 0);
+ mysql_mutex_init(key_LOCK_slave_list, &LOCK_slave_list, MY_MUTEX_INIT_FAST);
+}
+
+void end_slave_list()
+{
+ /* No protection by a mutex needed as we are only called at shutdown */
+ if (my_hash_inited(&slave_list))
+ {
+ my_hash_free(&slave_list);
+ mysql_mutex_destroy(&LOCK_slave_list);
+ }
+}
+
+/**
+ Execute a SHOW SLAVE HOSTS statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+bool show_slave_hosts(THD* thd)
+{
+ List<Item> field_list;
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("show_slave_hosts");
+
+ field_list.push_back(new Item_return_int("Server_id", 10,
+ MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_empty_string("Host", 20));
+ if (opt_show_slave_auth_info)
+ {
+ field_list.push_back(new Item_empty_string("User",20));
+ field_list.push_back(new Item_empty_string("Password",20));
+ }
+ field_list.push_back(new Item_return_int("Port", 7, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_return_int("Master_id", 10,
+ MYSQL_TYPE_LONG));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ mysql_mutex_lock(&LOCK_slave_list);
+
+ for (uint i = 0; i < slave_list.records; ++i)
+ {
+ SLAVE_INFO* si = (SLAVE_INFO*) my_hash_element(&slave_list, i);
+ protocol->prepare_for_resend();
+ protocol->store((uint32) si->server_id);
+ protocol->store(si->host, &my_charset_bin);
+ if (opt_show_slave_auth_info)
+ {
+ protocol->store(si->user, &my_charset_bin);
+ protocol->store(si->password, &my_charset_bin);
+ }
+ protocol->store((uint32) si->port);
+ protocol->store((uint32) si->master_id);
+ if (protocol->write())
+ {
+ mysql_mutex_unlock(&LOCK_slave_list);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_slave_list);
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+#endif /* HAVE_REPLICATION */
+
diff --git a/sql/repl_failsafe.h b/sql/repl_failsafe.h
new file mode 100644
index 00000000000..2cc031a462d
--- /dev/null
+++ b/sql/repl_failsafe.h
@@ -0,0 +1,48 @@
+#ifndef REPL_FAILSAFE_INCLUDED
+#define REPL_FAILSAFE_INCLUDED
+
+/* Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifdef HAVE_REPLICATION
+
+#include "mysql.h"
+#include "my_sys.h"
+#include "slave.h"
+
+typedef enum {RPL_AUTH_MASTER=0,RPL_IDLE_SLAVE,RPL_ACTIVE_SLAVE,
+ RPL_LOST_SOLDIER,RPL_TROOP_SOLDIER,
+ RPL_RECOVERY_CAPTAIN,RPL_NULL /* inactive */,
+ RPL_ANY /* wild card used by change_rpl_status */ } RPL_STATUS;
+extern ulong rpl_status;
+
+extern mysql_mutex_t LOCK_rpl_status;
+extern mysql_cond_t COND_rpl_status;
+extern TYPELIB rpl_role_typelib;
+extern const char* rpl_role_type[], *rpl_status_type[];
+
+void change_rpl_status(ulong from_status, ulong to_status);
+int find_recovery_captain(THD* thd, MYSQL* mysql);
+
+extern HASH slave_list;
+
+bool show_slave_hosts(THD* thd);
+void init_slave_list();
+void end_slave_list();
+int register_slave(THD* thd, uchar* packet, uint packet_length);
+void unregister_slave(THD* thd, bool only_mine, bool need_mutex);
+
+#endif /* HAVE_REPLICATION */
+#endif /* REPL_FAILSAFE_INCLUDED */
diff --git a/sql/replication.h b/sql/replication.h
new file mode 100644
index 00000000000..fc48ecd9ffc
--- /dev/null
+++ b/sql/replication.h
@@ -0,0 +1,538 @@
+/* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#ifndef REPLICATION_H
+#define REPLICATION_H
+
+/***************************************************************************
+ NOTE: plugin locking.
+ This API was created specifically for the semisync plugin and its locking
+ logic is also matches semisync plugin usage pattern. In particular, a plugin
+ is locked on Binlog_transmit_observer::transmit_start and is unlocked after
+ Binlog_transmit_observer::transmit_stop. All other master observable events
+ happen between these two and don't lock the plugin at all. This works well
+ for the semisync_master plugin.
+
+ Also a plugin is locked on Binlog_relay_IO_observer::thread_start
+ and unlocked after Binlog_relay_IO_observer::thread_stop. This works well for
+ the semisync_slave plugin.
+***************************************************************************/
+
+#include <mysql.h>
+
+typedef struct st_mysql MYSQL;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ Transaction observer flags.
+*/
+enum Trans_flags {
+ /** Transaction is a real transaction */
+ TRANS_IS_REAL_TRANS = 1
+};
+
+/**
+ Transaction observer parameter
+*/
+typedef struct Trans_param {
+ uint32 server_id;
+ uint32 flags;
+
+ /*
+ The latest binary log file name and position written by current
+ transaction, if binary log is disabled or no log event has been
+ written into binary log file by current transaction (events
+ written into transaction log cache are not counted), these two
+ member will be zero.
+ */
+ const char *log_file;
+ my_off_t log_pos;
+} Trans_param;
+
+/**
+ Observes and extends transaction execution
+*/
+typedef struct Trans_observer {
+ uint32 len;
+
+ /**
+ This callback is called after transaction commit
+
+ This callback is called right after commit to storage engines for
+ transactional tables.
+
+ For non-transactional tables, this is called at the end of the
+ statement, before sending statement status, if the statement
+ succeeded.
+
+ @note The return value is currently ignored by the server.
+
+ @param param The parameter for transaction observers
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_commit)(Trans_param *param);
+
+ /**
+ This callback is called after transaction rollback
+
+ This callback is called right after rollback to storage engines
+ for transactional tables.
+
+ For non-transactional tables, this is called at the end of the
+ statement, before sending statement status, if the statement
+ failed.
+
+ @note The return value is currently ignored by the server.
+
+ @param param The parameter for transaction observers
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_rollback)(Trans_param *param);
+} Trans_observer;
+
+/**
+ Binlog storage flags
+*/
+enum Binlog_storage_flags {
+ /** Binary log was sync:ed */
+ BINLOG_STORAGE_IS_SYNCED = 1
+};
+
+/**
+ Binlog storage observer parameters
+ */
+typedef struct Binlog_storage_param {
+ uint32 server_id;
+} Binlog_storage_param;
+
+/**
+ Observe binlog logging storage
+*/
+typedef struct Binlog_storage_observer {
+ uint32 len;
+
+ /**
+ This callback is called after binlog has been flushed
+
+ This callback is called after cached events have been flushed to
+ binary log file. Whether the binary log file is synchronized to
+ disk is indicated by the bit BINLOG_STORAGE_IS_SYNCED in @a flags.
+
+ @param param Observer common parameter
+ @param log_file Binlog file name been updated
+ @param log_pos Binlog position after update
+ @param flags flags for binlog storage
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_flush)(Binlog_storage_param *param,
+ const char *log_file, my_off_t log_pos,
+ uint32 flags);
+} Binlog_storage_observer;
+
+/**
+ Replication binlog transmitter (binlog dump) observer parameter.
+*/
+typedef struct Binlog_transmit_param {
+ uint32 server_id;
+ uint32 flags;
+} Binlog_transmit_param;
+
+/**
+ Observe and extends the binlog dumping thread.
+*/
+typedef struct Binlog_transmit_observer {
+ uint32 len;
+
+ /**
+ This callback is called when binlog dumping starts
+
+
+ @param param Observer common parameter
+ @param log_file Binlog file name to transmit from
+ @param log_pos Binlog position to transmit from
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*transmit_start)(Binlog_transmit_param *param,
+ const char *log_file, my_off_t log_pos);
+
+ /**
+ This callback is called when binlog dumping stops
+
+ @param param Observer common parameter
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*transmit_stop)(Binlog_transmit_param *param);
+
+ /**
+ This callback is called to reserve bytes in packet header for event transmission
+
+ This callback is called when resetting transmit packet header to
+ reserve bytes for this observer in packet header.
+
+ The @a header buffer is allocated by the server code, and @a size
+ is the size of the header buffer. Each observer can only reserve
+ a maximum size of @a size in the header.
+
+ @param param Observer common parameter
+ @param header Pointer of the header buffer
+ @param size Size of the header buffer
+ @param len Header length reserved by this observer
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*reserve_header)(Binlog_transmit_param *param,
+ unsigned char *header,
+ unsigned long size,
+ unsigned long *len);
+
+ /**
+ This callback is called before sending an event packet to slave
+
+ @param param Observer common parameter
+ @param packet Binlog event packet to send
+ @param len Length of the event packet
+ @param log_file Binlog file name of the event packet to send
+ @param log_pos Binlog position of the event packet to send
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*before_send_event)(Binlog_transmit_param *param,
+ unsigned char *packet, unsigned long len,
+ const char *log_file, my_off_t log_pos );
+
+ /**
+ This callback is called after sending an event packet to slave
+
+ @param param Observer common parameter
+ @param event_buf Binlog event packet buffer sent
+ @param len length of the event packet buffer
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_send_event)(Binlog_transmit_param *param,
+ const char *event_buf, unsigned long len);
+
+ /**
+ This callback is called after resetting master status
+
+ This is called when executing the command RESET MASTER, and is
+ used to reset status variables added by observers.
+
+ @param param Observer common parameter
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_reset_master)(Binlog_transmit_param *param);
+} Binlog_transmit_observer;
+
+/**
+ Binlog relay IO flags
+*/
+enum Binlog_relay_IO_flags {
+ /** Binary relay log was sync:ed */
+ BINLOG_RELAY_IS_SYNCED = 1
+};
+
+
+/**
+ Replication binlog relay IO observer parameter
+*/
+typedef struct Binlog_relay_IO_param {
+ uint32 server_id;
+
+ /* Master host, user and port */
+ char *host;
+ char *user;
+ unsigned int port;
+
+ char *master_log_name;
+ my_off_t master_log_pos;
+
+ MYSQL *mysql; /* the connection to master */
+} Binlog_relay_IO_param;
+
+/**
+ Observes and extends the service of slave IO thread.
+*/
+typedef struct Binlog_relay_IO_observer {
+ uint32 len;
+
+ /**
+ This callback is called when slave IO thread starts
+
+ @param param Observer common parameter
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*thread_start)(Binlog_relay_IO_param *param);
+
+ /**
+ This callback is called when slave IO thread stops
+
+ @param param Observer common parameter
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*thread_stop)(Binlog_relay_IO_param *param);
+
+ /**
+ This callback is called before slave requesting binlog transmission from master
+
+ This is called before slave issuing BINLOG_DUMP command to master
+ to request binlog.
+
+ @param param Observer common parameter
+ @param flags binlog dump flags
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*before_request_transmit)(Binlog_relay_IO_param *param, uint32 flags);
+
+ /**
+ This callback is called after read an event packet from master
+
+ @param param Observer common parameter
+ @param packet The event packet read from master
+ @param len Length of the event packet read from master
+ @param event_buf The event packet return after process
+ @param event_len The length of event packet return after process
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_read_event)(Binlog_relay_IO_param *param,
+ const char *packet, unsigned long len,
+ const char **event_buf, unsigned long *event_len);
+
+ /**
+ This callback is called after written an event packet to relay log
+
+ @param param Observer common parameter
+ @param event_buf Event packet written to relay log
+ @param event_len Length of the event packet written to relay log
+ @param flags flags for relay log
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_queue_event)(Binlog_relay_IO_param *param,
+ const char *event_buf, unsigned long event_len,
+ uint32 flags);
+
+ /**
+ This callback is called after reset slave relay log IO status
+
+ @param param Observer common parameter
+
+ @retval 0 Sucess
+ @retval 1 Failure
+ */
+ int (*after_reset_slave)(Binlog_relay_IO_param *param);
+} Binlog_relay_IO_observer;
+
+
+/**
+ Register a transaction observer
+
+ @param observer The transaction observer to register
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer already exists
+*/
+int register_trans_observer(Trans_observer *observer, void *p);
+
+/**
+ Unregister a transaction observer
+
+ @param observer The transaction observer to unregister
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer not exists
+*/
+int unregister_trans_observer(Trans_observer *observer, void *p);
+
+/**
+ Register a binlog storage observer
+
+ @param observer The binlog storage observer to register
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer already exists
+*/
+int register_binlog_storage_observer(Binlog_storage_observer *observer, void *p);
+
+/**
+ Unregister a binlog storage observer
+
+ @param observer The binlog storage observer to unregister
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer not exists
+*/
+int unregister_binlog_storage_observer(Binlog_storage_observer *observer, void *p);
+
+/**
+ Register a binlog transmit observer
+
+ @param observer The binlog transmit observer to register
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer already exists
+*/
+int register_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p);
+
+/**
+ Unregister a binlog transmit observer
+
+ @param observer The binlog transmit observer to unregister
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer not exists
+*/
+int unregister_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p);
+
+/**
+ Register a binlog relay IO (slave IO thread) observer
+
+ @param observer The binlog relay IO observer to register
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer already exists
+*/
+int register_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p);
+
+/**
+ Unregister a binlog relay IO (slave IO thread) observer
+
+ @param observer The binlog relay IO observer to unregister
+ @param p pointer to the internal plugin structure
+
+ @retval 0 Sucess
+ @retval 1 Observer not exists
+*/
+int unregister_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p);
+
+/**
+ Connect to master
+
+ This function can only used in the slave I/O thread context, and
+ will use the same master information to do the connection.
+
+ @code
+ MYSQL *mysql = mysql_init(NULL);
+ if (rpl_connect_master(mysql))
+ {
+ // do stuff with the connection
+ }
+ mysql_close(mysql); // close the connection
+ @endcode
+
+ @param mysql address of MYSQL structure to use, pass NULL will
+ create a new one
+
+ @return address of MYSQL structure on success, NULL on failure
+*/
+MYSQL *rpl_connect_master(MYSQL *mysql);
+
+/**
+ Get the value of user variable as an integer.
+
+ This function will return the value of variable @a name as an
+ integer. If the original value of the variable is not an integer,
+ the value will be converted into an integer.
+
+ @param name user variable name
+ @param value pointer to return the value
+ @param null_value if not NULL, the function will set it to true if
+ the value of variable is null, set to false if not
+
+ @retval 0 Success
+ @retval 1 Variable not found
+*/
+int get_user_var_int(const char *name,
+ long long int *value, int *null_value);
+
+/**
+ Get the value of user variable as a double precision float number.
+
+ This function will return the value of variable @a name as real
+ number. If the original value of the variable is not a real number,
+ the value will be converted into a real number.
+
+ @param name user variable name
+ @param value pointer to return the value
+ @param null_value if not NULL, the function will set it to true if
+ the value of variable is null, set to false if not
+
+ @retval 0 Success
+ @retval 1 Variable not found
+*/
+int get_user_var_real(const char *name,
+ double *value, int *null_value);
+
+/**
+ Get the value of user variable as a string.
+
+ This function will return the value of variable @a name as
+ string. If the original value of the variable is not a string,
+ the value will be converted into a string.
+
+ @param name user variable name
+ @param value pointer to the value buffer
+ @param len length of the value buffer
+ @param precision precision of the value if it is a float number
+ @param null_value if not NULL, the function will set it to true if
+ the value of variable is null, set to false if not
+
+ @retval 0 Success
+ @retval 1 Variable not found
+*/
+int get_user_var_str(const char *name,
+ char *value, unsigned long len,
+ unsigned int precision, int *null_value);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* REPLICATION_H */
diff --git a/sql/rpl_constants.h b/sql/rpl_constants.h
new file mode 100644
index 00000000000..f83588ce321
--- /dev/null
+++ b/sql/rpl_constants.h
@@ -0,0 +1,74 @@
+/* Copyright (c) 2007 MySQL AB, 2008 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+#ifndef RPL_CONSTANTS_H
+#define RPL_CONSTANTS_H
+
+/**
+ Enumeration of the incidents that can occur for the server.
+ */
+enum Incident {
+ /** No incident */
+ INCIDENT_NONE = 0,
+
+ /** There are possibly lost events in the replication stream */
+ INCIDENT_LOST_EVENTS = 1,
+
+ /** Shall be last event of the enumeration */
+ INCIDENT_COUNT
+};
+
+
+/**
+ Enumeration of the reserved formats of Binlog extra row information
+*/
+enum ExtraRowInfoFormat {
+ /** Ndb format */
+ ERIF_NDB = 0,
+
+ /** Reserved formats 0 -> 63 inclusive */
+ ERIF_LASTRESERVED = 63,
+
+ /**
+ Available / uncontrolled formats
+ 64 -> 254 inclusive
+ */
+ ERIF_OPEN1 = 64,
+ ERIF_OPEN2 = 65,
+
+ ERIF_LASTOPEN = 254,
+
+ /**
+ Multi-payload format 255
+
+ Length is total length, payload is sequence of
+ sub-payloads with their own headers containing
+ length + format.
+ */
+ ERIF_MULTI = 255
+};
+
+/*
+ 1 byte length, 1 byte format
+ Length is total length in bytes, including 2 byte header
+ Length values 0 and 1 are currently invalid and reserved.
+*/
+#define EXTRA_ROW_INFO_LEN_OFFSET 0
+#define EXTRA_ROW_INFO_FORMAT_OFFSET 1
+#define EXTRA_ROW_INFO_HDR_BYTES 2
+#define EXTRA_ROW_INFO_MAX_PAYLOAD (255 - EXTRA_ROW_INFO_HDR_BYTES)
+
+#endif /* RPL_CONSTANTS_H */
diff --git a/sql/rpl_filter.cc b/sql/rpl_filter.cc
new file mode 100644
index 00000000000..28859c2eb85
--- /dev/null
+++ b/sql/rpl_filter.cc
@@ -0,0 +1,796 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "mysqld.h" // system_charset_info
+#include "rpl_filter.h"
+#include "hash.h" // my_hash_free
+#include "table.h" // TABLE_LIST
+
+#define TABLE_RULE_HASH_SIZE 16
+#define TABLE_RULE_ARR_SIZE 16
+
+Rpl_filter::Rpl_filter() :
+ table_rules_on(0), do_table_inited(0), ignore_table_inited(0),
+ wild_do_table_inited(0), wild_ignore_table_inited(0)
+{
+ do_db.empty();
+ ignore_db.empty();
+ rewrite_db.empty();
+}
+
+
+Rpl_filter::~Rpl_filter()
+{
+ if (do_table_inited)
+ my_hash_free(&do_table);
+ if (ignore_table_inited)
+ my_hash_free(&ignore_table);
+ if (wild_do_table_inited)
+ free_string_array(&wild_do_table);
+ if (wild_ignore_table_inited)
+ free_string_array(&wild_ignore_table);
+ free_string_list(&do_db);
+ free_string_list(&ignore_db);
+ free_list(&rewrite_db);
+}
+
+
+#ifndef MYSQL_CLIENT
+/*
+ Returns true if table should be logged/replicated
+
+ SYNOPSIS
+ tables_ok()
+ db db to use if db in TABLE_LIST is undefined for a table
+ tables list of tables to check
+
+ NOTES
+ Changing table order in the list can lead to different results.
+
+ Note also order of precedence of do/ignore rules (see code). For
+ that reason, users should not set conflicting rules because they
+ may get unpredicted results (precedence order is explained in the
+ manual).
+
+ If no table in the list is marked "updating", then we always
+ return 0, because there is no reason to execute this statement on
+ slave if it updates nothing. (Currently, this can only happen if
+ statement is a multi-delete (SQLCOM_DELETE_MULTI) and "tables" are
+ the tables in the FROM):
+
+ In the case of SQLCOM_DELETE_MULTI, there will be a second call to
+ tables_ok(), with tables having "updating==TRUE" (those after the
+ DELETE), so this second call will make the decision (because
+ all_tables_not_ok() = !tables_ok(1st_list) &&
+ !tables_ok(2nd_list)).
+
+ TODO
+ "Include all tables like "abc.%" except "%.EFG"". (Can't be done now.)
+ If we supported Perl regexps, we could do it with pattern: /^abc\.(?!EFG)/
+ (I could not find an equivalent in the regex library MySQL uses).
+
+ RETURN VALUES
+ 0 should not be logged/replicated
+ 1 should be logged/replicated
+*/
+
+bool
+Rpl_filter::tables_ok(const char* db, TABLE_LIST* tables)
+{
+ bool some_tables_updating= 0;
+ DBUG_ENTER("Rpl_filter::tables_ok");
+
+ for (; tables; tables= tables->next_global)
+ {
+ char hash_key[SAFE_NAME_LEN*2+2];
+ char *end;
+ uint len;
+
+ if (!tables->updating)
+ continue;
+ some_tables_updating= 1;
+ end= strmov(hash_key, tables->db ? tables->db : db);
+ *end++= '.';
+ len= (uint) (strmov(end, tables->table_name) - hash_key);
+ if (do_table_inited) // if there are any do's
+ {
+ if (my_hash_search(&do_table, (uchar*) hash_key, len))
+ DBUG_RETURN(1);
+ }
+ if (ignore_table_inited) // if there are any ignores
+ {
+ if (my_hash_search(&ignore_table, (uchar*) hash_key, len))
+ DBUG_RETURN(0);
+ }
+ if (wild_do_table_inited &&
+ find_wild(&wild_do_table, hash_key, len))
+ DBUG_RETURN(1);
+ if (wild_ignore_table_inited &&
+ find_wild(&wild_ignore_table, hash_key, len))
+ DBUG_RETURN(0);
+ }
+
+ /*
+ If no table was to be updated, ignore statement (no reason we play it on
+ slave, slave is supposed to replicate _changes_ only).
+ If no explicit rule found and there was a do list, do not replicate.
+ If there was no do list, go ahead
+ */
+ DBUG_RETURN(some_tables_updating &&
+ !do_table_inited && !wild_do_table_inited);
+}
+
+#endif
+
+/*
+ Checks whether a db matches some do_db and ignore_db rules
+
+ SYNOPSIS
+ db_ok()
+ db name of the db to check
+
+ RETURN VALUES
+ 0 should not be logged/replicated
+ 1 should be logged/replicated
+*/
+
+bool
+Rpl_filter::db_ok(const char* db)
+{
+ DBUG_ENTER("Rpl_filter::db_ok");
+
+ if (do_db.is_empty() && ignore_db.is_empty())
+ DBUG_RETURN(1); // Ok to replicate if the user puts no constraints
+
+ /*
+ Previous behaviour "if the user has specified restrictions on which
+ databases to replicate and db was not selected, do not replicate" has
+ been replaced with "do replicate".
+ Since the filtering criteria is not equal to "NULL" the statement should
+ be logged into binlog.
+ */
+ if (!db)
+ DBUG_RETURN(1);
+
+ if (!do_db.is_empty()) // if the do's are not empty
+ {
+ I_List_iterator<i_string> it(do_db);
+ i_string* tmp;
+
+ while ((tmp=it++))
+ {
+ if (!strcmp(tmp->ptr, db))
+ DBUG_RETURN(1); // match
+ }
+ DBUG_PRINT("exit", ("Don't replicate"));
+ DBUG_RETURN(0);
+ }
+ else // there are some elements in the don't, otherwise we cannot get here
+ {
+ I_List_iterator<i_string> it(ignore_db);
+ i_string* tmp;
+
+ while ((tmp=it++))
+ {
+ if (!strcmp(tmp->ptr, db))
+ {
+ DBUG_PRINT("exit", ("Don't replicate"));
+ DBUG_RETURN(0); // match
+ }
+ }
+ DBUG_RETURN(1);
+ }
+}
+
+
+/*
+ Checks whether a db matches wild_do_table and wild_ignore_table
+ rules (for replication)
+
+ SYNOPSIS
+ db_ok_with_wild_table()
+ db name of the db to check.
+ Is tested with check_db_name() before calling this function.
+
+ NOTES
+ Here is the reason for this function.
+ We advise users who want to exclude a database 'db1' safely to do it
+ with replicate_wild_ignore_table='db1.%' instead of binlog_ignore_db or
+ replicate_ignore_db because the two lasts only check for the selected db,
+ which won't work in that case:
+ USE db2;
+ UPDATE db1.t SET ... #this will be replicated and should not
+ whereas replicate_wild_ignore_table will work in all cases.
+ With replicate_wild_ignore_table, we only check tables. When
+ one does 'DROP DATABASE db1', tables are not involved and the
+ statement will be replicated, while users could expect it would not (as it
+ rougly means 'DROP db1.first_table, DROP db1.second_table...').
+ In other words, we want to interpret 'db1.%' as "everything touching db1".
+ That is why we want to match 'db1' against 'db1.%' wild table rules.
+
+ RETURN VALUES
+ 0 should not be logged/replicated
+ 1 should be logged/replicated
+*/
+
+bool
+Rpl_filter::db_ok_with_wild_table(const char *db)
+{
+ DBUG_ENTER("Rpl_filter::db_ok_with_wild_table");
+
+ char hash_key[SAFE_NAME_LEN+2];
+ char *end;
+ int len;
+ end= strmov(hash_key, db);
+ *end++= '.';
+ len= end - hash_key ;
+ if (wild_do_table_inited && find_wild(&wild_do_table, hash_key, len))
+ {
+ DBUG_PRINT("return",("1"));
+ DBUG_RETURN(1);
+ }
+ if (wild_ignore_table_inited && find_wild(&wild_ignore_table, hash_key, len))
+ {
+ DBUG_PRINT("return",("0"));
+ DBUG_RETURN(0);
+ }
+
+ /*
+ If no explicit rule found and there was a do list, do not replicate.
+ If there was no do list, go ahead
+ */
+ DBUG_PRINT("return",("db=%s,retval=%d", db, !wild_do_table_inited));
+ DBUG_RETURN(!wild_do_table_inited);
+}
+
+
+bool
+Rpl_filter::is_on()
+{
+ return table_rules_on;
+}
+
+
+/**
+ Parse and add the given comma-separated sequence of filter rules.
+
+ @param spec Comma-separated sequence of filter rules.
+ @param add Callback member function to add a filter rule.
+
+ @return true if error, false otherwise.
+*/
+
+int
+Rpl_filter::parse_filter_rule(const char* spec, Add_filter add)
+{
+ int status= 0;
+ char *arg, *ptr, *pstr;
+
+ if (! (ptr= my_strdup(spec, MYF(MY_WME))))
+ return true;
+
+ pstr= ptr;
+
+ while (pstr)
+ {
+ arg= pstr;
+
+ /* Parse token string. */
+ pstr= strpbrk(arg, ",");
+
+ /* NUL terminate the token string. */
+ if (pstr)
+ *pstr++= '\0';
+
+ /* Skip an empty token string. */
+ if (arg[0] == '\0')
+ continue;
+
+ /* Skip leading spaces. */
+ while (my_isspace(system_charset_info, *arg))
+ arg++;
+
+ status= (this->*add)(arg);
+
+ if (status)
+ break;
+ }
+
+ my_free(ptr);
+
+ return status;
+}
+
+
+int
+Rpl_filter::add_do_table(const char* table_spec)
+{
+ DBUG_ENTER("Rpl_filter::add_do_table");
+ if (!do_table_inited)
+ init_table_rule_hash(&do_table, &do_table_inited);
+ table_rules_on= 1;
+ DBUG_RETURN(add_table_rule(&do_table, table_spec));
+}
+
+
+int
+Rpl_filter::add_ignore_table(const char* table_spec)
+{
+ DBUG_ENTER("Rpl_filter::add_ignore_table");
+ if (!ignore_table_inited)
+ init_table_rule_hash(&ignore_table, &ignore_table_inited);
+ table_rules_on= 1;
+ DBUG_RETURN(add_table_rule(&ignore_table, table_spec));
+}
+
+
+int
+Rpl_filter::set_do_table(const char* table_spec)
+{
+ int status;
+
+ if (do_table_inited)
+ my_hash_reset(&do_table);
+
+ status= parse_filter_rule(table_spec, &Rpl_filter::add_do_table);
+
+ if (!do_table.records)
+ {
+ my_hash_free(&do_table);
+ do_table_inited= 0;
+ }
+
+ return status;
+}
+
+
+int
+Rpl_filter::set_ignore_table(const char* table_spec)
+{
+ int status;
+
+ if (ignore_table_inited)
+ my_hash_reset(&ignore_table);
+
+ status= parse_filter_rule(table_spec, &Rpl_filter::add_ignore_table);
+
+ if (!ignore_table.records)
+ {
+ my_hash_free(&ignore_table);
+ ignore_table_inited= 0;
+ }
+
+ return status;
+}
+
+
+int
+Rpl_filter::add_wild_do_table(const char* table_spec)
+{
+ DBUG_ENTER("Rpl_filter::add_wild_do_table");
+ if (!wild_do_table_inited)
+ init_table_rule_array(&wild_do_table, &wild_do_table_inited);
+ table_rules_on= 1;
+ DBUG_RETURN(add_wild_table_rule(&wild_do_table, table_spec));
+}
+
+
+int
+Rpl_filter::add_wild_ignore_table(const char* table_spec)
+{
+ DBUG_ENTER("Rpl_filter::add_wild_ignore_table");
+ if (!wild_ignore_table_inited)
+ init_table_rule_array(&wild_ignore_table, &wild_ignore_table_inited);
+ table_rules_on= 1;
+ DBUG_RETURN(add_wild_table_rule(&wild_ignore_table, table_spec));
+}
+
+
+int
+Rpl_filter::set_wild_do_table(const char* table_spec)
+{
+ int status;
+
+ if (wild_do_table_inited)
+ free_string_array(&wild_do_table);
+
+ status= parse_filter_rule(table_spec, &Rpl_filter::add_wild_do_table);
+
+ if (!wild_do_table.elements)
+ {
+ delete_dynamic(&wild_do_table);
+ wild_do_table_inited= 0;
+ }
+
+ return status;
+}
+
+
+int
+Rpl_filter::set_wild_ignore_table(const char* table_spec)
+{
+ int status;
+
+ if (wild_ignore_table_inited)
+ free_string_array(&wild_ignore_table);
+
+ status= parse_filter_rule(table_spec, &Rpl_filter::add_wild_ignore_table);
+
+ if (!wild_ignore_table.elements)
+ {
+ delete_dynamic(&wild_ignore_table);
+ wild_ignore_table_inited= 0;
+ }
+
+ return status;
+}
+
+
+void
+Rpl_filter::add_db_rewrite(const char* from_db, const char* to_db)
+{
+ i_string_pair *db_pair = new i_string_pair(from_db, to_db);
+ rewrite_db.push_back(db_pair);
+}
+
+
+int
+Rpl_filter::add_table_rule(HASH* h, const char* table_spec)
+{
+ const char* dot = strchr(table_spec, '.');
+ if (!dot) return 1;
+ // len is always > 0 because we know the there exists a '.'
+ uint len = (uint)strlen(table_spec);
+ TABLE_RULE_ENT* e = (TABLE_RULE_ENT*)my_malloc(sizeof(TABLE_RULE_ENT)
+ + len, MYF(MY_WME));
+ if (!e) return 1;
+ e->db= (char*)e + sizeof(TABLE_RULE_ENT);
+ e->tbl_name= e->db + (dot - table_spec) + 1;
+ e->key_len= len;
+ memcpy(e->db, table_spec, len);
+
+ return my_hash_insert(h, (uchar*)e);
+}
+
+
+/*
+ Add table expression with wildcards to dynamic array
+*/
+
+int
+Rpl_filter::add_wild_table_rule(DYNAMIC_ARRAY* a, const char* table_spec)
+{
+ const char* dot = strchr(table_spec, '.');
+ if (!dot) return 1;
+ uint len = (uint)strlen(table_spec);
+ TABLE_RULE_ENT* e = (TABLE_RULE_ENT*)my_malloc(sizeof(TABLE_RULE_ENT)
+ + len, MYF(MY_WME));
+ if (!e) return 1;
+ e->db= (char*)e + sizeof(TABLE_RULE_ENT);
+ e->tbl_name= e->db + (dot - table_spec) + 1;
+ e->key_len= len;
+ memcpy(e->db, table_spec, len);
+ return insert_dynamic(a, (uchar*)&e);
+}
+
+
+int
+Rpl_filter::add_string_list(I_List<i_string> *list, const char* spec)
+{
+ char *str;
+ i_string *node;
+
+ if (! (str= my_strdup(spec, MYF(MY_WME))))
+ return true;
+
+ if (! (node= new i_string(str)))
+ {
+ my_free(str);
+ return true;
+ }
+
+ list->push_back(node);
+
+ return false;
+}
+
+
+int
+Rpl_filter::add_do_db(const char* table_spec)
+{
+ DBUG_ENTER("Rpl_filter::add_do_db");
+ DBUG_RETURN(add_string_list(&do_db, table_spec));
+}
+
+
+int
+Rpl_filter::add_ignore_db(const char* table_spec)
+{
+ DBUG_ENTER("Rpl_filter::add_ignore_db");
+ DBUG_RETURN(add_string_list(&ignore_db, table_spec));
+}
+
+
+int
+Rpl_filter::set_do_db(const char* db_spec)
+{
+ free_string_list(&do_db);
+ return parse_filter_rule(db_spec, &Rpl_filter::add_do_db);
+}
+
+
+int
+Rpl_filter::set_ignore_db(const char* db_spec)
+{
+ free_string_list(&ignore_db);
+ return parse_filter_rule(db_spec, &Rpl_filter::add_ignore_db);
+}
+
+
+extern "C" uchar *get_table_key(const uchar *, size_t *, my_bool);
+extern "C" void free_table_ent(void* a);
+
+uchar *get_table_key(const uchar* a, size_t *len,
+ my_bool __attribute__((unused)))
+{
+ TABLE_RULE_ENT *e= (TABLE_RULE_ENT *) a;
+
+ *len= e->key_len;
+ return (uchar*)e->db;
+}
+
+
+void free_table_ent(void* a)
+{
+ TABLE_RULE_ENT *e= (TABLE_RULE_ENT *) a;
+
+ my_free(e);
+}
+
+
+void
+Rpl_filter::init_table_rule_hash(HASH* h, bool* h_inited)
+{
+ my_hash_init(h, system_charset_info,TABLE_RULE_HASH_SIZE,0,0,
+ get_table_key, free_table_ent, 0);
+ *h_inited = 1;
+}
+
+
+void
+Rpl_filter::init_table_rule_array(DYNAMIC_ARRAY* a, bool* a_inited)
+{
+ my_init_dynamic_array(a, sizeof(TABLE_RULE_ENT*), TABLE_RULE_ARR_SIZE,
+ TABLE_RULE_ARR_SIZE, MYF(0));
+ *a_inited = 1;
+}
+
+
+TABLE_RULE_ENT*
+Rpl_filter::find_wild(DYNAMIC_ARRAY *a, const char* key, int len)
+{
+ uint i;
+ const char* key_end= key + len;
+
+ for (i= 0; i < a->elements; i++)
+ {
+ TABLE_RULE_ENT* e ;
+ get_dynamic(a, (uchar*)&e, i);
+ if (!my_wildcmp(system_charset_info, key, key_end,
+ (const char*)e->db,
+ (const char*)(e->db + e->key_len),
+ '\\',wild_one,wild_many))
+ return e;
+ }
+
+ return 0;
+}
+
+
+void
+Rpl_filter::free_string_array(DYNAMIC_ARRAY *a)
+{
+ uint i;
+ for (i= 0; i < a->elements; i++)
+ {
+ char* p;
+ get_dynamic(a, (uchar*) &p, i);
+ my_free(p);
+ }
+ delete_dynamic(a);
+}
+
+
+void
+Rpl_filter::free_string_list(I_List<i_string> *l)
+{
+ void *ptr;
+ i_string *tmp;
+
+ while ((tmp= l->get()))
+ {
+ ptr= (void *) tmp->ptr;
+ my_free(ptr);
+ delete tmp;
+ }
+
+ l->empty();
+}
+
+
+/*
+ Builds a String from a HASH of TABLE_RULE_ENT. Cannot be used for any other
+ hash, as it assumes that the hash entries are TABLE_RULE_ENT.
+
+ SYNOPSIS
+ table_rule_ent_hash_to_str()
+ s pointer to the String to fill
+ h pointer to the HASH to read
+
+ RETURN VALUES
+ none
+*/
+
+void
+Rpl_filter::table_rule_ent_hash_to_str(String* s, HASH* h, bool inited)
+{
+ s->length(0);
+ if (inited)
+ {
+ for (uint i= 0; i < h->records; i++)
+ {
+ TABLE_RULE_ENT* e= (TABLE_RULE_ENT*) my_hash_element(h, i);
+ if (s->length())
+ s->append(',');
+ s->append(e->db,e->key_len);
+ }
+ }
+}
+
+
+void
+Rpl_filter::table_rule_ent_dynamic_array_to_str(String* s, DYNAMIC_ARRAY* a,
+ bool inited)
+{
+ s->length(0);
+ if (inited)
+ {
+ for (uint i= 0; i < a->elements; i++)
+ {
+ TABLE_RULE_ENT* e;
+ get_dynamic(a, (uchar*)&e, i);
+ if (s->length())
+ s->append(',');
+ s->append(e->db,e->key_len);
+ }
+ }
+}
+
+
+void
+Rpl_filter::get_do_table(String* str)
+{
+ table_rule_ent_hash_to_str(str, &do_table, do_table_inited);
+}
+
+
+void
+Rpl_filter::get_ignore_table(String* str)
+{
+ table_rule_ent_hash_to_str(str, &ignore_table, ignore_table_inited);
+}
+
+
+void
+Rpl_filter::get_wild_do_table(String* str)
+{
+ table_rule_ent_dynamic_array_to_str(str, &wild_do_table, wild_do_table_inited);
+}
+
+
+void
+Rpl_filter::get_wild_ignore_table(String* str)
+{
+ table_rule_ent_dynamic_array_to_str(str, &wild_ignore_table, wild_ignore_table_inited);
+}
+
+
+bool
+Rpl_filter::rewrite_db_is_empty()
+{
+ return rewrite_db.is_empty();
+}
+
+
+const char*
+Rpl_filter::get_rewrite_db(const char* db, size_t *new_len)
+{
+ if (rewrite_db.is_empty() || !db)
+ return db;
+ I_List_iterator<i_string_pair> it(rewrite_db);
+ i_string_pair* tmp;
+
+ while ((tmp=it++))
+ {
+ if (!strcmp(tmp->key, db))
+ {
+ *new_len= strlen(tmp->val);
+ return tmp->val;
+ }
+ }
+ return db;
+}
+
+
+void
+Rpl_filter::copy_rewrite_db(Rpl_filter *from)
+{
+ I_List_iterator<i_string_pair> it(from->rewrite_db);
+ i_string_pair* tmp;
+ DBUG_ASSERT(rewrite_db.is_empty());
+
+ /* TODO: Add memory checking here and in all add_xxxx functions ! */
+ while ((tmp=it++))
+ add_db_rewrite(tmp->key, tmp->val);
+}
+
+I_List<i_string>*
+Rpl_filter::get_do_db()
+{
+ return &do_db;
+}
+
+
+I_List<i_string>*
+Rpl_filter::get_ignore_db()
+{
+ return &ignore_db;
+}
+
+
+void
+Rpl_filter::db_rule_ent_list_to_str(String* str, I_List<i_string>* list)
+{
+ I_List_iterator<i_string> it(*list);
+ i_string* s;
+
+ str->length(0);
+
+ while ((s= it++))
+ {
+ str->append(s->ptr);
+ str->append(',');
+ }
+
+ // Remove last ','
+ if (!str->is_empty())
+ str->chop();
+}
+
+
+void
+Rpl_filter::get_do_db(String* str)
+{
+ db_rule_ent_list_to_str(str, get_do_db());
+}
+
+
+void
+Rpl_filter::get_ignore_db(String* str)
+{
+ db_rule_ent_list_to_str(str, get_ignore_db());
+}
diff --git a/sql/rpl_filter.h b/sql/rpl_filter.h
new file mode 100644
index 00000000000..65d11cfb6e6
--- /dev/null
+++ b/sql/rpl_filter.h
@@ -0,0 +1,146 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef RPL_FILTER_H
+#define RPL_FILTER_H
+
+#include "mysql.h"
+#include "sql_list.h" /* I_List */
+#include "hash.h" /* HASH */
+
+class String;
+struct TABLE_LIST;
+typedef struct st_dynamic_array DYNAMIC_ARRAY;
+
+typedef struct st_table_rule_ent
+{
+ char* db;
+ char* tbl_name;
+ uint key_len;
+} TABLE_RULE_ENT;
+
+/*
+ Rpl_filter
+
+ Inclusion and exclusion rules of tables and databases.
+ Also handles rewrites of db.
+ Used for replication and binlogging.
+ */
+class Rpl_filter
+{
+public:
+ Rpl_filter();
+ ~Rpl_filter();
+ Rpl_filter(Rpl_filter const&);
+ Rpl_filter& operator=(Rpl_filter const&);
+
+ /* Checks - returns true if ok to replicate/log */
+
+#ifndef MYSQL_CLIENT
+ bool tables_ok(const char* db, TABLE_LIST *tables);
+#endif
+ bool db_ok(const char* db);
+ bool db_ok_with_wild_table(const char *db);
+
+ bool is_on();
+
+ /* Setters - add filtering rules */
+
+ int add_do_table(const char* table_spec);
+ int add_ignore_table(const char* table_spec);
+
+ int set_do_table(const char* table_spec);
+ int set_ignore_table(const char* table_spec);
+
+ int add_wild_do_table(const char* table_spec);
+ int add_wild_ignore_table(const char* table_spec);
+
+ int set_wild_do_table(const char* table_spec);
+ int set_wild_ignore_table(const char* table_spec);
+
+ int add_do_db(const char* db_spec);
+ int add_ignore_db(const char* db_spec);
+
+ int set_do_db(const char* db_spec);
+ int set_ignore_db(const char* db_spec);
+
+ void add_db_rewrite(const char* from_db, const char* to_db);
+
+ /* Getters - to get information about current rules */
+
+ void get_do_table(String* str);
+ void get_ignore_table(String* str);
+
+ void get_wild_do_table(String* str);
+ void get_wild_ignore_table(String* str);
+
+ bool rewrite_db_is_empty();
+ const char* get_rewrite_db(const char* db, size_t *new_len);
+ void copy_rewrite_db(Rpl_filter *from);
+
+ I_List<i_string>* get_do_db();
+ I_List<i_string>* get_ignore_db();
+
+ void get_do_db(String* str);
+ void get_ignore_db(String* str);
+
+private:
+ bool table_rules_on;
+
+ void init_table_rule_hash(HASH* h, bool* h_inited);
+ void init_table_rule_array(DYNAMIC_ARRAY* a, bool* a_inited);
+
+ int add_table_rule(HASH* h, const char* table_spec);
+ int add_wild_table_rule(DYNAMIC_ARRAY* a, const char* table_spec);
+
+ typedef int (Rpl_filter::*Add_filter)(char const*);
+
+ int parse_filter_rule(const char* spec, Add_filter func);
+
+ void free_string_array(DYNAMIC_ARRAY *a);
+ void free_string_list(I_List<i_string> *l);
+
+ void table_rule_ent_hash_to_str(String* s, HASH* h, bool inited);
+ void table_rule_ent_dynamic_array_to_str(String* s, DYNAMIC_ARRAY* a,
+ bool inited);
+ void db_rule_ent_list_to_str(String* s, I_List<i_string>* l);
+ TABLE_RULE_ENT* find_wild(DYNAMIC_ARRAY *a, const char* key, int len);
+
+ int add_string_list(I_List<i_string> *list, const char* spec);
+
+ /*
+ Those 4 structures below are uninitialized memory unless the
+ corresponding *_inited variables are "true".
+ */
+ HASH do_table;
+ HASH ignore_table;
+ DYNAMIC_ARRAY wild_do_table;
+ DYNAMIC_ARRAY wild_ignore_table;
+
+ bool do_table_inited;
+ bool ignore_table_inited;
+ bool wild_do_table_inited;
+ bool wild_ignore_table_inited;
+
+ I_List<i_string> do_db;
+ I_List<i_string> ignore_db;
+
+ I_List<i_string_pair> rewrite_db;
+};
+
+extern Rpl_filter *global_rpl_filter;
+extern Rpl_filter *binlog_filter;
+
+#endif // RPL_FILTER_H
diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc
new file mode 100644
index 00000000000..6e67a75b989
--- /dev/null
+++ b/sql/rpl_gtid.cc
@@ -0,0 +1,2364 @@
+/* Copyright (c) 2013, Kristian Nielsen and MariaDB Services Ab.
+
+ 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 */
+
+
+/* Definitions for MariaDB global transaction ID (GTID). */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "my_sys.h"
+#include "unireg.h"
+#include "my_global.h"
+#include "sql_base.h"
+#include "sql_parse.h"
+#include "key.h"
+#include "rpl_gtid.h"
+#include "rpl_rli.h"
+
+
+const LEX_STRING rpl_gtid_slave_state_table_name=
+ { C_STRING_WITH_LEN("gtid_slave_pos") };
+
+
+void
+rpl_slave_state::update_state_hash(uint64 sub_id, rpl_gtid *gtid,
+ rpl_group_info *rgi)
+{
+ int err;
+ /*
+ Add the gtid to the HASH in the replication slave state.
+
+ We must do this only _after_ commit, so that for parallel replication,
+ there will not be an attempt to delete the corresponding table row before
+ it is even committed.
+ */
+ mysql_mutex_lock(&LOCK_slave_state);
+ err= update(gtid->domain_id, gtid->server_id, sub_id, gtid->seq_no, rgi);
+ mysql_mutex_unlock(&LOCK_slave_state);
+ if (err)
+ {
+ sql_print_warning("Slave: Out of memory during slave state maintenance. "
+ "Some no longer necessary rows in table "
+ "mysql.%s may be left undeleted.",
+ rpl_gtid_slave_state_table_name.str);
+ /*
+ Such failure is not fatal. We will fail to delete the row for this
+ GTID, but it will do no harm and will be removed automatically on next
+ server restart.
+ */
+ }
+}
+
+
+int
+rpl_slave_state::record_and_update_gtid(THD *thd, rpl_group_info *rgi)
+{
+ DBUG_ENTER("rpl_slave_state::record_and_update_gtid");
+
+ /*
+ Update the GTID position, if we have it and did not already update
+ it in a GTID transaction.
+ */
+ if (rgi->gtid_pending)
+ {
+ uint64 sub_id= rgi->gtid_sub_id;
+ rgi->gtid_pending= false;
+ if (rgi->gtid_ignore_duplicate_state!=rpl_group_info::GTID_DUPLICATE_IGNORE)
+ {
+ if (record_gtid(thd, &rgi->current_gtid, sub_id, false, false))
+ DBUG_RETURN(1);
+ update_state_hash(sub_id, &rgi->current_gtid, rgi);
+ }
+ rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL;
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Check GTID event execution when --gtid-ignore-duplicates.
+
+ The idea with --gtid-ignore-duplicates is that we allow multiple master
+ connections (in multi-source replication) to all receive the same GTIDs and
+ event groups. Only one instance of each is applied; we use the sequence
+ number in the GTID to decide whether a GTID has already been applied.
+
+ So if the seq_no of a GTID (or a higher sequence number) has already been
+ applied, then the event should be skipped. If not then the event should be
+ applied.
+
+ To avoid two master connections tring to apply the same event
+ simultaneously, only one is allowed to work in any given domain at any point
+ in time. The associated Relay_log_info object is called the owner of the
+ domain (and there can be multiple parallel worker threads working in that
+ domain for that Relay_log_info). Any other Relay_log_info/master connection
+ must wait for the domain to become free, or for their GTID to have been
+ applied, before being allowed to proceed.
+
+ Returns:
+ 0 This GTID is already applied, it should be skipped.
+ 1 The GTID is not yet applied; this rli is now the owner, and must apply
+ the event and release the domain afterwards.
+ -1 Error (out of memory to allocate a new element for the domain).
+*/
+int
+rpl_slave_state::check_duplicate_gtid(rpl_gtid *gtid, rpl_group_info *rgi)
+{
+ uint32 domain_id= gtid->domain_id;
+ uint32 seq_no= gtid->seq_no;
+ rpl_slave_state::element *elem;
+ int res;
+ bool did_enter_cond= false;
+ PSI_stage_info old_stage;
+ THD *thd;
+ Relay_log_info *rli= rgi->rli;
+
+ mysql_mutex_lock(&LOCK_slave_state);
+ if (!(elem= get_element(domain_id)))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ res= -1;
+ goto err;
+ }
+ /*
+ Note that the elem pointer does not change once inserted in the hash. So
+ we can re-use the pointer without looking it up again in the hash after
+ each lock release and re-take.
+ */
+
+ for (;;)
+ {
+ if (elem->highest_seq_no >= seq_no)
+ {
+ /* This sequence number is already applied, ignore it. */
+ res= 0;
+ rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_IGNORE;
+ break;
+ }
+ if (!elem->owner_rli)
+ {
+ /* The domain became free, grab it and apply the event. */
+ elem->owner_rli= rli;
+ elem->owner_count= 1;
+ rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_OWNER;
+ res= 1;
+ break;
+ }
+ if (elem->owner_rli == rli)
+ {
+ /* Already own this domain, increment reference count and apply event. */
+ ++elem->owner_count;
+ rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_OWNER;
+ res= 1;
+ break;
+ }
+ thd= rgi->thd;
+ if (thd->check_killed())
+ {
+ thd->send_kill_message();
+ res= -1;
+ break;
+ }
+ /*
+ Someone else is currently processing this GTID (or an earlier one).
+ Wait for them to complete (or fail), and then check again.
+ */
+ if (!did_enter_cond)
+ {
+ thd->ENTER_COND(&elem->COND_gtid_ignore_duplicates, &LOCK_slave_state,
+ &stage_gtid_wait_other_connection, &old_stage);
+ did_enter_cond= true;
+ }
+ mysql_cond_wait(&elem->COND_gtid_ignore_duplicates,
+ &LOCK_slave_state);
+ }
+
+err:
+ if (did_enter_cond)
+ thd->EXIT_COND(&old_stage);
+ else
+ mysql_mutex_unlock(&LOCK_slave_state);
+ return res;
+}
+
+
+void
+rpl_slave_state::release_domain_owner(rpl_group_info *rgi)
+{
+ element *elem= NULL;
+
+ mysql_mutex_lock(&LOCK_slave_state);
+ if (!(elem= get_element(rgi->current_gtid.domain_id)))
+ {
+ /*
+ We cannot really deal with error here, as we are already called in an
+ error handling case (transaction failure and rollback).
+
+ However, get_element() only fails if the element did not exist already
+ and could not be allocated due to out-of-memory - and if it did not
+ exist, then we would not get here in the first place.
+ */
+ mysql_mutex_unlock(&LOCK_slave_state);
+ return;
+ }
+
+ if (rgi->gtid_ignore_duplicate_state == rpl_group_info::GTID_DUPLICATE_OWNER)
+ {
+ uint32 count= elem->owner_count;
+ DBUG_ASSERT(count > 0);
+ DBUG_ASSERT(elem->owner_rli == rgi->rli);
+ --count;
+ elem->owner_count= count;
+ if (count == 0)
+ {
+ elem->owner_rli= NULL;
+ mysql_cond_broadcast(&elem->COND_gtid_ignore_duplicates);
+ }
+ }
+ rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL;
+ mysql_mutex_unlock(&LOCK_slave_state);
+}
+
+
+static void
+rpl_slave_state_free_element(void *arg)
+{
+ struct rpl_slave_state::element *elem= (struct rpl_slave_state::element *)arg;
+ mysql_cond_destroy(&elem->COND_wait_gtid);
+ mysql_cond_destroy(&elem->COND_gtid_ignore_duplicates);
+ my_free(elem);
+}
+
+
+rpl_slave_state::rpl_slave_state()
+ : last_sub_id(0), inited(false), loaded(false)
+{
+ my_hash_init(&hash, &my_charset_bin, 32, offsetof(element, domain_id),
+ sizeof(uint32), NULL, rpl_slave_state_free_element, HASH_UNIQUE);
+}
+
+
+rpl_slave_state::~rpl_slave_state()
+{
+}
+
+
+void
+rpl_slave_state::init()
+{
+ DBUG_ASSERT(!inited);
+ mysql_mutex_init(key_LOCK_slave_state, &LOCK_slave_state, MY_MUTEX_INIT_SLOW);
+ inited= true;
+}
+
+
+void
+rpl_slave_state::truncate_hash()
+{
+ uint32 i;
+
+ for (i= 0; i < hash.records; ++i)
+ {
+ element *e= (element *)my_hash_element(&hash, i);
+ list_element *l= e->list;
+ list_element *next;
+ while (l)
+ {
+ next= l->next;
+ my_free(l);
+ l= next;
+ }
+ /* The element itself is freed by the hash element free function. */
+ }
+ my_hash_reset(&hash);
+}
+
+void
+rpl_slave_state::deinit()
+{
+ if (!inited)
+ return;
+ truncate_hash();
+ my_hash_free(&hash);
+ mysql_mutex_destroy(&LOCK_slave_state);
+}
+
+
+int
+rpl_slave_state::update(uint32 domain_id, uint32 server_id, uint64 sub_id,
+ uint64 seq_no, rpl_group_info *rgi)
+{
+ element *elem= NULL;
+ list_element *list_elem= NULL;
+
+ if (!(elem= get_element(domain_id)))
+ return 1;
+
+ if (seq_no > elem->highest_seq_no)
+ elem->highest_seq_no= seq_no;
+ if (elem->gtid_waiter && elem->min_wait_seq_no <= seq_no)
+ {
+ /*
+ Someone was waiting in MASTER_GTID_WAIT() for this GTID to appear.
+ Signal (and remove) them. The waiter will handle all the processing
+ of all pending MASTER_GTID_WAIT(), so we do not slow down the
+ replication SQL thread.
+ */
+ mysql_mutex_assert_owner(&LOCK_slave_state);
+ elem->gtid_waiter= NULL;
+ mysql_cond_broadcast(&elem->COND_wait_gtid);
+ }
+
+ if (rgi)
+ {
+ if (rgi->gtid_ignore_duplicate_state==rpl_group_info::GTID_DUPLICATE_OWNER)
+ {
+#ifndef DBUG_OFF
+ Relay_log_info *rli= rgi->rli;
+#endif
+ uint32 count= elem->owner_count;
+ DBUG_ASSERT(count > 0);
+ DBUG_ASSERT(elem->owner_rli == rli);
+ --count;
+ elem->owner_count= count;
+ if (count == 0)
+ {
+ elem->owner_rli= NULL;
+ mysql_cond_broadcast(&elem->COND_gtid_ignore_duplicates);
+ }
+ }
+ rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL;
+ }
+
+ if (!(list_elem= (list_element *)my_malloc(sizeof(*list_elem), MYF(MY_WME))))
+ return 1;
+ list_elem->server_id= server_id;
+ list_elem->sub_id= sub_id;
+ list_elem->seq_no= seq_no;
+
+ elem->add(list_elem);
+ if (last_sub_id < sub_id)
+ last_sub_id= sub_id;
+
+ return 0;
+}
+
+
+struct rpl_slave_state::element *
+rpl_slave_state::get_element(uint32 domain_id)
+{
+ struct element *elem;
+
+ elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0);
+ if (elem)
+ return elem;
+
+ if (!(elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME))))
+ return NULL;
+ elem->list= NULL;
+ elem->domain_id= domain_id;
+ elem->highest_seq_no= 0;
+ elem->gtid_waiter= NULL;
+ elem->owner_rli= NULL;
+ elem->owner_count= 0;
+ mysql_cond_init(key_COND_wait_gtid, &elem->COND_wait_gtid, 0);
+ mysql_cond_init(key_COND_gtid_ignore_duplicates,
+ &elem->COND_gtid_ignore_duplicates, 0);
+ if (my_hash_insert(&hash, (uchar *)elem))
+ {
+ my_free(elem);
+ return NULL;
+ }
+ return elem;
+}
+
+
+int
+rpl_slave_state::put_back_list(uint32 domain_id, list_element *list)
+{
+ element *e;
+ if (!(e= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0)))
+ return 1;
+ while (list)
+ {
+ list_element *next= list->next;
+ e->add(list);
+ list= next;
+ }
+ return 0;
+}
+
+
+int
+rpl_slave_state::truncate_state_table(THD *thd)
+{
+ TABLE_LIST tlist;
+ int err= 0;
+
+ tmp_disable_binlog(thd);
+ tlist.init_one_table(STRING_WITH_LEN("mysql"),
+ rpl_gtid_slave_state_table_name.str,
+ rpl_gtid_slave_state_table_name.length,
+ NULL, TL_WRITE);
+ if (!(err= open_and_lock_tables(thd, &tlist, FALSE, 0)))
+ {
+ err= tlist.table->file->ha_truncate();
+
+ if (err)
+ {
+ ha_rollback_trans(thd, FALSE);
+ close_thread_tables(thd);
+ ha_rollback_trans(thd, TRUE);
+ }
+ else
+ {
+ ha_commit_trans(thd, FALSE);
+ close_thread_tables(thd);
+ ha_commit_trans(thd, TRUE);
+ }
+ thd->mdl_context.release_transactional_locks();
+ }
+
+ reenable_binlog(thd);
+ return err;
+}
+
+
+static const TABLE_FIELD_TYPE mysql_rpl_slave_state_coltypes[4]= {
+ { { C_STRING_WITH_LEN("domain_id") },
+ { C_STRING_WITH_LEN("int(10) unsigned") },
+ {NULL, 0} },
+ { { C_STRING_WITH_LEN("sub_id") },
+ { C_STRING_WITH_LEN("bigint(20) unsigned") },
+ {NULL, 0} },
+ { { C_STRING_WITH_LEN("server_id") },
+ { C_STRING_WITH_LEN("int(10) unsigned") },
+ {NULL, 0} },
+ { { C_STRING_WITH_LEN("seq_no") },
+ { C_STRING_WITH_LEN("bigint(20) unsigned") },
+ {NULL, 0} },
+};
+
+static const uint mysql_rpl_slave_state_pk_parts[]= {0, 1};
+
+static const TABLE_FIELD_DEF mysql_gtid_slave_pos_tabledef= {
+ array_elements(mysql_rpl_slave_state_coltypes),
+ mysql_rpl_slave_state_coltypes,
+ array_elements(mysql_rpl_slave_state_pk_parts),
+ mysql_rpl_slave_state_pk_parts
+};
+
+class Gtid_db_intact : public Table_check_intact
+{
+protected:
+ void report_error(uint, const char *fmt, ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+ error_log_print(ERROR_LEVEL, fmt, args);
+ va_end(args);
+ }
+};
+
+static Gtid_db_intact gtid_table_intact;
+
+/*
+ Check that the mysql.gtid_slave_pos table has the correct definition.
+*/
+int
+gtid_check_rpl_slave_state_table(TABLE *table)
+{
+ int err;
+
+ if ((err= gtid_table_intact.check(table, &mysql_gtid_slave_pos_tabledef)))
+ my_error(ER_GTID_OPEN_TABLE_FAILED, MYF(0), "mysql",
+ rpl_gtid_slave_state_table_name.str);
+ return err;
+}
+
+
+/*
+ Write a gtid to the replication slave state table.
+
+ Do it as part of the transaction, to get slave crash safety, or as a separate
+ transaction if !in_transaction (eg. MyISAM or DDL).
+
+ gtid The global transaction id for this event group.
+ sub_id Value allocated within the sub_id when the event group was
+ read (sub_id must be consistent with commit order in master binlog).
+
+ Note that caller must later ensure that the new gtid and sub_id is inserted
+ into the appropriate HASH element with rpl_slave_state.add(), so that it can
+ be deleted later. But this must only be done after COMMIT if in transaction.
+*/
+int
+rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
+ bool in_transaction, bool in_statement)
+{
+ TABLE_LIST tlist;
+ int err= 0;
+ bool table_opened= false;
+ TABLE *table;
+ list_element *elist= 0, *next;
+ element *elem;
+ ulonglong thd_saved_option= thd->variables.option_bits;
+ Query_tables_list lex_backup;
+ wait_for_commit* suspended_wfc;
+ DBUG_ENTER("record_gtid");
+
+ if (unlikely(!loaded))
+ {
+ /*
+ Probably the mysql.gtid_slave_pos table is missing (eg. upgrade) or
+ corrupt.
+
+ We already complained loudly about this, but we can try to continue
+ until the DBA fixes it.
+ */
+ DBUG_RETURN(0);
+ }
+
+ if (!in_statement)
+ mysql_reset_thd_for_next_command(thd);
+
+ DBUG_EXECUTE_IF("gtid_inject_record_gtid",
+ {
+ my_error(ER_CANNOT_UPDATE_GTID_STATE, MYF(0));
+ DBUG_RETURN(1);
+ } );
+
+ /*
+ If we are applying a non-transactional event group, we will be committing
+ here a transaction, but that does not imply that the event group has
+ completed or has been binlogged. So we should not trigger
+ wakeup_subsequent_commits() here.
+
+ Note: An alternative here could be to put a call to mark_start_commit() in
+ stmt_done() before the call to record_and_update_gtid(). This would
+ prevent later calling mark_start_commit() after we have run
+ wakeup_subsequent_commits() from committing the GTID update transaction
+ (which must be avoided to avoid accessing freed group_commit_orderer
+ object). It would also allow following event groups to start slightly
+ earlier. And in the cases where record_gtid() is called without an active
+ transaction, the current statement should have been binlogged already, so
+ binlog order is preserved.
+
+ But this is rather subtle, and potentially fragile. And it does not really
+ seem worth it; non-transactional loads are unlikely to benefit much from
+ parallel replication in any case. So for now, we go with the simple
+ suspend/resume of wakeup_subsequent_commits() here in record_gtid().
+ */
+ suspended_wfc= thd->suspend_subsequent_commits();
+ thd->lex->reset_n_backup_query_tables_list(&lex_backup);
+ tlist.init_one_table(STRING_WITH_LEN("mysql"),
+ rpl_gtid_slave_state_table_name.str,
+ rpl_gtid_slave_state_table_name.length,
+ NULL, TL_WRITE);
+ if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0)))
+ goto end;
+ table_opened= true;
+ table= tlist.table;
+
+ if ((err= gtid_check_rpl_slave_state_table(table)))
+ goto end;
+
+ if (!in_transaction)
+ {
+ DBUG_PRINT("info", ("resetting OPTION_BEGIN"));
+ thd->variables.option_bits&=
+ ~(ulonglong)(OPTION_NOT_AUTOCOMMIT |OPTION_BEGIN |OPTION_BIN_LOG |
+ OPTION_GTID_BEGIN);
+ }
+ else
+ thd->variables.option_bits&= ~(ulonglong)OPTION_BIN_LOG;
+
+ bitmap_set_all(table->write_set);
+
+ table->field[0]->store((ulonglong)gtid->domain_id, true);
+ table->field[1]->store(sub_id, true);
+ table->field[2]->store((ulonglong)gtid->server_id, true);
+ table->field[3]->store(gtid->seq_no, true);
+ DBUG_EXECUTE_IF("inject_crash_before_write_rpl_slave_state", DBUG_SUICIDE(););
+ if ((err= table->file->ha_write_row(table->record[0])))
+ {
+ table->file->print_error(err, MYF(0));
+ goto end;
+ }
+
+ if(opt_bin_log &&
+ (err= mysql_bin_log.bump_seq_no_counter_if_needed(gtid->domain_id,
+ gtid->seq_no)))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto end;
+ }
+
+ mysql_mutex_lock(&LOCK_slave_state);
+ if ((elem= get_element(gtid->domain_id)) == NULL)
+ {
+ mysql_mutex_unlock(&LOCK_slave_state);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ err= 1;
+ goto end;
+ }
+ if ((elist= elem->grab_list()) != NULL)
+ {
+ /* Delete any old stuff, but keep around the most recent one. */
+ list_element *cur= elist;
+ uint64 best_sub_id= cur->sub_id;
+ list_element **best_ptr_ptr= &elist;
+ while ((next= cur->next))
+ {
+ if (next->sub_id > best_sub_id)
+ {
+ best_sub_id= next->sub_id;
+ best_ptr_ptr= &cur->next;
+ }
+ cur= next;
+ }
+ /*
+ Delete the highest sub_id element from the old list, and put it back as
+ the single-element new list.
+ */
+ cur= *best_ptr_ptr;
+ *best_ptr_ptr= cur->next;
+ cur->next= NULL;
+ elem->list= cur;
+ }
+ mysql_mutex_unlock(&LOCK_slave_state);
+
+ if (!elist)
+ goto end;
+
+ /* Now delete any already committed rows. */
+ bitmap_set_bit(table->read_set, table->field[0]->field_index);
+ bitmap_set_bit(table->read_set, table->field[1]->field_index);
+
+ if ((err= table->file->ha_index_init(0, 0)))
+ {
+ table->file->print_error(err, MYF(0));
+ goto end;
+ }
+ while (elist)
+ {
+ uchar key_buffer[4+8];
+
+ DBUG_EXECUTE_IF("gtid_slave_pos_simulate_failed_delete",
+ { err= ENOENT;
+ table->file->print_error(err, MYF(0));
+ /* `break' does not work inside DBUG_EXECUTE_IF */
+ goto dbug_break; });
+
+ next= elist->next;
+
+ table->field[1]->store(elist->sub_id, true);
+ /* domain_id is already set in table->record[0] from write_row() above. */
+ key_copy(key_buffer, table->record[0], &table->key_info[0], 0, false);
+ if (table->file->ha_index_read_map(table->record[1], key_buffer,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT))
+ /* We cannot find the row, assume it is already deleted. */
+ ;
+ else if ((err= table->file->ha_delete_row(table->record[1])))
+ table->file->print_error(err, MYF(0));
+ /*
+ In case of error, we still discard the element from the list. We do
+ not want to endlessly error on the same element in case of table
+ corruption or such.
+ */
+ my_free(elist);
+ elist= next;
+ if (err)
+ break;
+ }
+IF_DBUG(dbug_break:, )
+ table->file->ha_index_end();
+
+end:
+
+ if (table_opened)
+ {
+ if (err || (err= ha_commit_trans(thd, FALSE)))
+ {
+ /*
+ If error, we need to put any remaining elist back into the HASH so we
+ can do another delete attempt later.
+ */
+ if (elist)
+ {
+ mysql_mutex_lock(&LOCK_slave_state);
+ put_back_list(gtid->domain_id, elist);
+ mysql_mutex_unlock(&LOCK_slave_state);
+ }
+
+ ha_rollback_trans(thd, FALSE);
+ }
+ close_thread_tables(thd);
+ if (in_transaction)
+ thd->mdl_context.release_statement_locks();
+ else
+ thd->mdl_context.release_transactional_locks();
+ }
+ thd->lex->restore_backup_query_tables_list(&lex_backup);
+ thd->variables.option_bits= thd_saved_option;
+ thd->resume_subsequent_commits(suspended_wfc);
+ DBUG_EXECUTE_IF("inject_record_gtid_serverid_100_sleep",
+ {
+ if (gtid->server_id == 100)
+ my_sleep(500000);
+ });
+ DBUG_RETURN(err);
+}
+
+
+uint64
+rpl_slave_state::next_sub_id(uint32 domain_id)
+{
+ uint64 sub_id= 0;
+
+ mysql_mutex_lock(&LOCK_slave_state);
+ sub_id= ++last_sub_id;
+ mysql_mutex_unlock(&LOCK_slave_state);
+
+ return sub_id;
+}
+
+
+bool
+rpl_slave_state_tostring_helper(String *dest, const rpl_gtid *gtid, bool *first)
+{
+ if (*first)
+ *first= false;
+ else
+ if (dest->append(",",1))
+ return true;
+ return
+ dest->append_ulonglong(gtid->domain_id) ||
+ dest->append("-",1) ||
+ dest->append_ulonglong(gtid->server_id) ||
+ dest->append("-",1) ||
+ dest->append_ulonglong(gtid->seq_no);
+}
+
+
+int
+rpl_slave_state::iterate(int (*cb)(rpl_gtid *, void *), void *data,
+ rpl_gtid *extra_gtids, uint32 num_extra)
+{
+ uint32 i;
+ HASH gtid_hash;
+ uchar *rec;
+ rpl_gtid *gtid;
+ int res= 1;
+
+ my_hash_init(&gtid_hash, &my_charset_bin, 32, offsetof(rpl_gtid, domain_id),
+ sizeof(uint32), NULL, NULL, HASH_UNIQUE);
+ for (i= 0; i < num_extra; ++i)
+ if (extra_gtids[i].server_id == global_system_variables.server_id &&
+ my_hash_insert(&gtid_hash, (uchar *)(&extra_gtids[i])))
+ goto err;
+
+ mysql_mutex_lock(&LOCK_slave_state);
+
+ for (i= 0; i < hash.records; ++i)
+ {
+ uint64 best_sub_id;
+ rpl_gtid best_gtid;
+ element *e= (element *)my_hash_element(&hash, i);
+ list_element *l= e->list;
+
+ if (!l)
+ continue; /* Nothing here */
+
+ best_gtid.domain_id= e->domain_id;
+ best_gtid.server_id= l->server_id;
+ best_gtid.seq_no= l->seq_no;
+ best_sub_id= l->sub_id;
+ while ((l= l->next))
+ {
+ if (l->sub_id > best_sub_id)
+ {
+ best_sub_id= l->sub_id;
+ best_gtid.server_id= l->server_id;
+ best_gtid.seq_no= l->seq_no;
+ }
+ }
+
+ /* Check if we have something newer in the extra list. */
+ rec= my_hash_search(&gtid_hash, (const uchar *)&best_gtid.domain_id, 0);
+ if (rec)
+ {
+ gtid= (rpl_gtid *)rec;
+ if (gtid->seq_no > best_gtid.seq_no)
+ memcpy(&best_gtid, gtid, sizeof(best_gtid));
+ if (my_hash_delete(&gtid_hash, rec))
+ {
+ mysql_mutex_unlock(&LOCK_slave_state);
+ goto err;
+ }
+ }
+
+ if ((res= (*cb)(&best_gtid, data)))
+ {
+ mysql_mutex_unlock(&LOCK_slave_state);
+ goto err;
+ }
+ }
+
+ mysql_mutex_unlock(&LOCK_slave_state);
+
+ /* Also add any remaining extra domain_ids. */
+ for (i= 0; i < gtid_hash.records; ++i)
+ {
+ gtid= (rpl_gtid *)my_hash_element(&gtid_hash, i);
+ if ((res= (*cb)(gtid, data)))
+ goto err;
+ }
+
+ res= 0;
+
+err:
+ my_hash_free(&gtid_hash);
+
+ return res;
+}
+
+
+struct rpl_slave_state_tostring_data {
+ String *dest;
+ bool first;
+};
+static int
+rpl_slave_state_tostring_cb(rpl_gtid *gtid, void *data)
+{
+ rpl_slave_state_tostring_data *p= (rpl_slave_state_tostring_data *)data;
+ return rpl_slave_state_tostring_helper(p->dest, gtid, &p->first);
+}
+
+
+/*
+ Prepare the current slave state as a string, suitable for sending to the
+ master to request to receive binlog events starting from that GTID state.
+
+ The state consists of the most recently applied GTID for each domain_id,
+ ie. the one with the highest sub_id within each domain_id.
+
+ Optinally, extra_gtids is a list of GTIDs from the binlog. This is used when
+ a server was previously a master and now needs to connect to a new master as
+ a slave. For each domain_id, if the GTID in the binlog was logged with our
+ own server_id _and_ has a higher seq_no than what is in the slave state,
+ then this should be used as the position to start replicating at. This
+ allows to promote a slave as new master, and connect the old master as a
+ slave with MASTER_GTID_POS=AUTO.
+*/
+int
+rpl_slave_state::tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra)
+{
+ struct rpl_slave_state_tostring_data data;
+ data.first= true;
+ data.dest= dest;
+
+ return iterate(rpl_slave_state_tostring_cb, &data, extra_gtids, num_extra);
+}
+
+
+/*
+ Lookup a domain_id in the current replication slave state.
+
+ Returns false if the domain_id has no entries in the slave state.
+ Otherwise returns true, and fills in out_gtid with the corresponding
+ GTID.
+*/
+bool
+rpl_slave_state::domain_to_gtid(uint32 domain_id, rpl_gtid *out_gtid)
+{
+ element *elem;
+ list_element *list;
+ uint64 best_sub_id;
+
+ mysql_mutex_lock(&LOCK_slave_state);
+ elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0);
+ if (!elem || !(list= elem->list))
+ {
+ mysql_mutex_unlock(&LOCK_slave_state);
+ return false;
+ }
+
+ out_gtid->domain_id= domain_id;
+ out_gtid->server_id= list->server_id;
+ out_gtid->seq_no= list->seq_no;
+ best_sub_id= list->sub_id;
+
+ while ((list= list->next))
+ {
+ if (best_sub_id > list->sub_id)
+ continue;
+ best_sub_id= list->sub_id;
+ out_gtid->server_id= list->server_id;
+ out_gtid->seq_no= list->seq_no;
+ }
+
+ mysql_mutex_unlock(&LOCK_slave_state);
+ return true;
+}
+
+
+/*
+ Parse a GTID at the start of a string, and update the pointer to point
+ at the first character after the parsed GTID.
+
+ Returns 0 on ok, non-zero on parse error.
+*/
+static int
+gtid_parser_helper(char **ptr, char *end, rpl_gtid *out_gtid)
+{
+ char *q;
+ char *p= *ptr;
+ uint64 v1, v2, v3;
+ int err= 0;
+
+ q= end;
+ v1= (uint64)my_strtoll10(p, &q, &err);
+ if (err != 0 || v1 > (uint32)0xffffffff || q == end || *q != '-')
+ return 1;
+ p= q+1;
+ q= end;
+ v2= (uint64)my_strtoll10(p, &q, &err);
+ if (err != 0 || v2 > (uint32)0xffffffff || q == end || *q != '-')
+ return 1;
+ p= q+1;
+ q= end;
+ v3= (uint64)my_strtoll10(p, &q, &err);
+ if (err != 0)
+ return 1;
+
+ out_gtid->domain_id= v1;
+ out_gtid->server_id= v2;
+ out_gtid->seq_no= v3;
+ *ptr= q;
+ return 0;
+}
+
+
+rpl_gtid *
+gtid_parse_string_to_list(const char *str, size_t str_len, uint32 *out_len)
+{
+ char *p= const_cast<char *>(str);
+ char *end= p + str_len;
+ uint32 len= 0, alloc_len= 5;
+ rpl_gtid *list= NULL;
+
+ for (;;)
+ {
+ rpl_gtid gtid;
+
+ if (len >= (((uint32)1 << 28)-1) || gtid_parser_helper(&p, end, &gtid))
+ {
+ my_free(list);
+ return NULL;
+ }
+ if ((!list || len >= alloc_len) &&
+ !(list=
+ (rpl_gtid *)my_realloc(list,
+ (alloc_len= alloc_len*2) * sizeof(rpl_gtid),
+ MYF(MY_FREE_ON_ERROR|MY_ALLOW_ZERO_PTR))))
+ return NULL;
+ list[len++]= gtid;
+
+ if (p == end)
+ break;
+ if (*p != ',')
+ {
+ my_free(list);
+ return NULL;
+ }
+ ++p;
+ }
+ *out_len= len;
+ return list;
+}
+
+
+/*
+ Update the slave replication state with the GTID position obtained from
+ master when connecting with old-style (filename,offset) position.
+
+ If RESET is true then all existing entries are removed. Otherwise only
+ domain_ids mentioned in the STATE_FROM_MASTER are changed.
+
+ Returns 0 if ok, non-zero if error.
+*/
+int
+rpl_slave_state::load(THD *thd, char *state_from_master, size_t len,
+ bool reset, bool in_statement)
+{
+ char *end= state_from_master + len;
+
+ if (reset)
+ {
+ if (truncate_state_table(thd))
+ return 1;
+ truncate_hash();
+ }
+ if (state_from_master == end)
+ return 0;
+ for (;;)
+ {
+ rpl_gtid gtid;
+ uint64 sub_id;
+
+ if (gtid_parser_helper(&state_from_master, end, &gtid) ||
+ !(sub_id= next_sub_id(gtid.domain_id)) ||
+ record_gtid(thd, &gtid, sub_id, false, in_statement) ||
+ update(gtid.domain_id, gtid.server_id, sub_id, gtid.seq_no, NULL))
+ return 1;
+ if (state_from_master == end)
+ break;
+ if (*state_from_master != ',')
+ return 1;
+ ++state_from_master;
+ }
+ return 0;
+}
+
+
+bool
+rpl_slave_state::is_empty()
+{
+ uint32 i;
+ bool result= true;
+
+ mysql_mutex_lock(&LOCK_slave_state);
+ for (i= 0; i < hash.records; ++i)
+ {
+ element *e= (element *)my_hash_element(&hash, i);
+ if (e->list)
+ {
+ result= false;
+ break;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_slave_state);
+
+ return result;
+}
+
+
+rpl_binlog_state::rpl_binlog_state()
+{
+ my_hash_init(&hash, &my_charset_bin, 32, offsetof(element, domain_id),
+ sizeof(uint32), NULL, my_free, HASH_UNIQUE);
+ mysql_mutex_init(key_LOCK_binlog_state, &LOCK_binlog_state,
+ MY_MUTEX_INIT_SLOW);
+ initialized= 1;
+}
+
+
+void
+rpl_binlog_state::reset_nolock()
+{
+ uint32 i;
+
+ for (i= 0; i < hash.records; ++i)
+ my_hash_free(&((element *)my_hash_element(&hash, i))->hash);
+ my_hash_reset(&hash);
+}
+
+
+void
+rpl_binlog_state::reset()
+{
+ mysql_mutex_lock(&LOCK_binlog_state);
+ reset_nolock();
+ mysql_mutex_unlock(&LOCK_binlog_state);
+}
+
+
+void rpl_binlog_state::free()
+{
+ if (initialized)
+ {
+ initialized= 0;
+ reset_nolock();
+ my_hash_free(&hash);
+ mysql_mutex_destroy(&LOCK_binlog_state);
+ }
+}
+
+
+bool
+rpl_binlog_state::load(struct rpl_gtid *list, uint32 count)
+{
+ uint32 i;
+ bool res= false;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ reset_nolock();
+ for (i= 0; i < count; ++i)
+ {
+ if (update_nolock(&(list[i]), false))
+ {
+ res= true;
+ break;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+static int rpl_binlog_state_load_cb(rpl_gtid *gtid, void *data)
+{
+ rpl_binlog_state *self= (rpl_binlog_state *)data;
+ return self->update_nolock(gtid, false);
+}
+
+
+bool
+rpl_binlog_state::load(rpl_slave_state *slave_pos)
+{
+ bool res= false;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ reset_nolock();
+ if (slave_pos->iterate(rpl_binlog_state_load_cb, this, NULL, 0))
+ res= true;
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+rpl_binlog_state::~rpl_binlog_state()
+{
+ free();
+}
+
+
+/*
+ Update replication state with a new GTID.
+
+ If the (domain_id, server_id) pair already exists, then the new GTID replaces
+ the old one for that domain id. Else a new entry is inserted.
+
+ Returns 0 for ok, 1 for error.
+*/
+int
+rpl_binlog_state::update_nolock(const struct rpl_gtid *gtid, bool strict)
+{
+ element *elem;
+
+ if ((elem= (element *)my_hash_search(&hash,
+ (const uchar *)(&gtid->domain_id), 0)))
+ {
+ if (strict && elem->last_gtid && elem->last_gtid->seq_no >= gtid->seq_no)
+ {
+ my_error(ER_GTID_STRICT_OUT_OF_ORDER, MYF(0), gtid->domain_id,
+ gtid->server_id, gtid->seq_no, elem->last_gtid->domain_id,
+ elem->last_gtid->server_id, elem->last_gtid->seq_no);
+ return 1;
+ }
+ if (elem->seq_no_counter < gtid->seq_no)
+ elem->seq_no_counter= gtid->seq_no;
+ if (!elem->update_element(gtid))
+ return 0;
+ }
+ else if (!alloc_element_nolock(gtid))
+ return 0;
+
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return 1;
+}
+
+
+int
+rpl_binlog_state::update(const struct rpl_gtid *gtid, bool strict)
+{
+ int res;
+ mysql_mutex_lock(&LOCK_binlog_state);
+ res= update_nolock(gtid, strict);
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+/*
+ Fill in a new GTID, allocating next sequence number, and update state
+ accordingly.
+*/
+int
+rpl_binlog_state::update_with_next_gtid(uint32 domain_id, uint32 server_id,
+ rpl_gtid *gtid)
+{
+ element *elem;
+ int res= 0;
+
+ gtid->domain_id= domain_id;
+ gtid->server_id= server_id;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ if ((elem= (element *)my_hash_search(&hash, (const uchar *)(&domain_id), 0)))
+ {
+ gtid->seq_no= ++elem->seq_no_counter;
+ if (!elem->update_element(gtid))
+ goto end;
+ }
+ else
+ {
+ gtid->seq_no= 1;
+ if (!alloc_element_nolock(gtid))
+ goto end;
+ }
+
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ res= 1;
+end:
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+/* Helper functions for update. */
+int
+rpl_binlog_state::element::update_element(const rpl_gtid *gtid)
+{
+ rpl_gtid *lookup_gtid;
+
+ /*
+ By far the most common case is that successive events within same
+ replication domain have the same server id (it changes only when
+ switching to a new master). So save a hash lookup in this case.
+ */
+ if (likely(last_gtid && last_gtid->server_id == gtid->server_id))
+ {
+ last_gtid->seq_no= gtid->seq_no;
+ return 0;
+ }
+
+ lookup_gtid= (rpl_gtid *)
+ my_hash_search(&hash, (const uchar *)&gtid->server_id, 0);
+ if (lookup_gtid)
+ {
+ lookup_gtid->seq_no= gtid->seq_no;
+ last_gtid= lookup_gtid;
+ return 0;
+ }
+
+ /* Allocate a new GTID and insert it. */
+ lookup_gtid= (rpl_gtid *)my_malloc(sizeof(*lookup_gtid), MYF(MY_WME));
+ if (!lookup_gtid)
+ return 1;
+ memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid));
+ if (my_hash_insert(&hash, (const uchar *)lookup_gtid))
+ {
+ my_free(lookup_gtid);
+ return 1;
+ }
+ last_gtid= lookup_gtid;
+ return 0;
+}
+
+
+int
+rpl_binlog_state::alloc_element_nolock(const rpl_gtid *gtid)
+{
+ element *elem;
+ rpl_gtid *lookup_gtid;
+
+ /* First time we see this domain_id; allocate a new element. */
+ elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME));
+ lookup_gtid= (rpl_gtid *)my_malloc(sizeof(*lookup_gtid), MYF(MY_WME));
+ if (elem && lookup_gtid)
+ {
+ elem->domain_id= gtid->domain_id;
+ my_hash_init(&elem->hash, &my_charset_bin, 32,
+ offsetof(rpl_gtid, server_id), sizeof(uint32), NULL, my_free,
+ HASH_UNIQUE);
+ elem->last_gtid= lookup_gtid;
+ elem->seq_no_counter= gtid->seq_no;
+ memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid));
+ if (0 == my_hash_insert(&elem->hash, (const uchar *)lookup_gtid))
+ {
+ lookup_gtid= NULL; /* Do not free. */
+ if (0 == my_hash_insert(&hash, (const uchar *)elem))
+ return 0;
+ }
+ my_hash_free(&elem->hash);
+ }
+
+ /* An error. */
+ if (elem)
+ my_free(elem);
+ if (lookup_gtid)
+ my_free(lookup_gtid);
+ return 1;
+}
+
+
+/*
+ Check that a new GTID can be logged without creating an out-of-order
+ sequence number with existing GTIDs.
+*/
+bool
+rpl_binlog_state::check_strict_sequence(uint32 domain_id, uint32 server_id,
+ uint64 seq_no)
+{
+ element *elem;
+ bool res= 0;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ if ((elem= (element *)my_hash_search(&hash,
+ (const uchar *)(&domain_id), 0)) &&
+ elem->last_gtid && elem->last_gtid->seq_no >= seq_no)
+ {
+ my_error(ER_GTID_STRICT_OUT_OF_ORDER, MYF(0), domain_id, server_id, seq_no,
+ elem->last_gtid->domain_id, elem->last_gtid->server_id,
+ elem->last_gtid->seq_no);
+ res= 1;
+ }
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+/*
+ When we see a new GTID that will not be binlogged (eg. slave thread
+ with --log-slave-updates=0), then we need to remember to allocate any
+ GTID seq_no of our own within that domain starting from there.
+
+ Returns 0 if ok, non-zero if out-of-memory.
+*/
+int
+rpl_binlog_state::bump_seq_no_if_needed(uint32 domain_id, uint64 seq_no)
+{
+ element *elem;
+ int res;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ if ((elem= (element *)my_hash_search(&hash, (const uchar *)(&domain_id), 0)))
+ {
+ if (elem->seq_no_counter < seq_no)
+ elem->seq_no_counter= seq_no;
+ res= 0;
+ goto end;
+ }
+
+ /* We need to allocate a new, empty element to remember the next seq_no. */
+ if (!(elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME))))
+ {
+ res= 1;
+ goto end;
+ }
+
+ elem->domain_id= domain_id;
+ my_hash_init(&elem->hash, &my_charset_bin, 32,
+ offsetof(rpl_gtid, server_id), sizeof(uint32), NULL, my_free,
+ HASH_UNIQUE);
+ elem->last_gtid= NULL;
+ elem->seq_no_counter= seq_no;
+ if (0 == my_hash_insert(&hash, (const uchar *)elem))
+ {
+ res= 0;
+ goto end;
+ }
+
+ my_hash_free(&elem->hash);
+ my_free(elem);
+ res= 1;
+
+end:
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+/*
+ Write binlog state to text file, so we can read it in again without having
+ to scan last binlog file (normal shutdown/startup, not crash recovery).
+
+ The most recent GTID within each domain_id is written after any other GTID
+ within this domain.
+*/
+int
+rpl_binlog_state::write_to_iocache(IO_CACHE *dest)
+{
+ ulong i, j;
+ char buf[21];
+ int res= 0;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ for (i= 0; i < hash.records; ++i)
+ {
+ size_t res;
+ element *e= (element *)my_hash_element(&hash, i);
+ if (!e->last_gtid)
+ {
+ DBUG_ASSERT(e->hash.records == 0);
+ continue;
+ }
+ for (j= 0; j <= e->hash.records; ++j)
+ {
+ const rpl_gtid *gtid;
+ if (j < e->hash.records)
+ {
+ gtid= (const rpl_gtid *)my_hash_element(&e->hash, j);
+ if (gtid == e->last_gtid)
+ continue;
+ }
+ else
+ gtid= e->last_gtid;
+
+ longlong10_to_str(gtid->seq_no, buf, 10);
+ res= my_b_printf(dest, "%u-%u-%s\n", gtid->domain_id, gtid->server_id, buf);
+ if (res == (size_t) -1)
+ {
+ res= 1;
+ goto end;
+ }
+ }
+ }
+
+end:
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+int
+rpl_binlog_state::read_from_iocache(IO_CACHE *src)
+{
+ /* 10-digit - 10-digit - 20-digit \n \0 */
+ char buf[10+1+10+1+20+1+1];
+ char *p, *end;
+ rpl_gtid gtid;
+ int res= 0;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ reset_nolock();
+ for (;;)
+ {
+ size_t len= my_b_gets(src, buf, sizeof(buf));
+ if (!len)
+ break;
+ p= buf;
+ end= buf + len;
+ if (gtid_parser_helper(&p, end, &gtid) ||
+ update_nolock(&gtid, false))
+ {
+ res= 1;
+ break;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+rpl_gtid *
+rpl_binlog_state::find_nolock(uint32 domain_id, uint32 server_id)
+{
+ element *elem;
+ if (!(elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0)))
+ return NULL;
+ return (rpl_gtid *)my_hash_search(&elem->hash, (const uchar *)&server_id, 0);
+}
+
+rpl_gtid *
+rpl_binlog_state::find(uint32 domain_id, uint32 server_id)
+{
+ rpl_gtid *p;
+ mysql_mutex_lock(&LOCK_binlog_state);
+ p= find_nolock(domain_id, server_id);
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return p;
+}
+
+rpl_gtid *
+rpl_binlog_state::find_most_recent(uint32 domain_id)
+{
+ element *elem;
+ rpl_gtid *gtid= NULL;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0);
+ if (elem && elem->last_gtid)
+ gtid= elem->last_gtid;
+ mysql_mutex_unlock(&LOCK_binlog_state);
+
+ return gtid;
+}
+
+
+uint32
+rpl_binlog_state::count()
+{
+ uint32 c= 0;
+ uint32 i;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ for (i= 0; i < hash.records; ++i)
+ c+= ((element *)my_hash_element(&hash, i))->hash.records;
+ mysql_mutex_unlock(&LOCK_binlog_state);
+
+ return c;
+}
+
+
+int
+rpl_binlog_state::get_gtid_list(rpl_gtid *gtid_list, uint32 list_size)
+{
+ uint32 i, j, pos;
+ int res= 0;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ pos= 0;
+ for (i= 0; i < hash.records; ++i)
+ {
+ element *e= (element *)my_hash_element(&hash, i);
+ if (!e->last_gtid)
+ {
+ DBUG_ASSERT(e->hash.records==0);
+ continue;
+ }
+ for (j= 0; j <= e->hash.records; ++j)
+ {
+ const rpl_gtid *gtid;
+ if (j < e->hash.records)
+ {
+ gtid= (rpl_gtid *)my_hash_element(&e->hash, j);
+ if (gtid == e->last_gtid)
+ continue;
+ }
+ else
+ gtid= e->last_gtid;
+
+ if (pos >= list_size)
+ {
+ res= 1;
+ goto end;
+ }
+ memcpy(&gtid_list[pos++], gtid, sizeof(*gtid));
+ }
+ }
+
+end:
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+/*
+ Get a list of the most recently binlogged GTID, for each domain_id.
+
+ This can be used when switching from being a master to being a slave,
+ to know where to start replicating from the new master.
+
+ The returned list must be de-allocated with my_free().
+
+ Returns 0 for ok, non-zero for out-of-memory.
+*/
+int
+rpl_binlog_state::get_most_recent_gtid_list(rpl_gtid **list, uint32 *size)
+{
+ uint32 i;
+ uint32 alloc_size, out_size;
+ int res= 0;
+
+ out_size= 0;
+ mysql_mutex_lock(&LOCK_binlog_state);
+ alloc_size= hash.records;
+ if (!(*list= (rpl_gtid *)my_malloc(alloc_size * sizeof(rpl_gtid),
+ MYF(MY_WME))))
+ {
+ res= 1;
+ goto end;
+ }
+ for (i= 0; i < alloc_size; ++i)
+ {
+ element *e= (element *)my_hash_element(&hash, i);
+ if (!e->last_gtid)
+ continue;
+ memcpy(&((*list)[out_size++]), e->last_gtid, sizeof(rpl_gtid));
+ }
+
+end:
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ *size= out_size;
+ return res;
+}
+
+
+bool
+rpl_binlog_state::append_pos(String *str)
+{
+ uint32 i;
+ bool first= true;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ for (i= 0; i < hash.records; ++i)
+ {
+ element *e= (element *)my_hash_element(&hash, i);
+ if (e->last_gtid &&
+ rpl_slave_state_tostring_helper(str, e->last_gtid, &first))
+ return true;
+ }
+ mysql_mutex_unlock(&LOCK_binlog_state);
+
+ return false;
+}
+
+
+bool
+rpl_binlog_state::append_state(String *str)
+{
+ uint32 i, j;
+ bool first= true;
+ bool res= false;
+
+ mysql_mutex_lock(&LOCK_binlog_state);
+ for (i= 0; i < hash.records; ++i)
+ {
+ element *e= (element *)my_hash_element(&hash, i);
+ if (!e->last_gtid)
+ {
+ DBUG_ASSERT(e->hash.records==0);
+ continue;
+ }
+ for (j= 0; j <= e->hash.records; ++j)
+ {
+ const rpl_gtid *gtid;
+ if (j < e->hash.records)
+ {
+ gtid= (rpl_gtid *)my_hash_element(&e->hash, j);
+ if (gtid == e->last_gtid)
+ continue;
+ }
+ else
+ gtid= e->last_gtid;
+
+ if (rpl_slave_state_tostring_helper(str, gtid, &first))
+ {
+ res= true;
+ goto end;
+ }
+ }
+ }
+
+end:
+ mysql_mutex_unlock(&LOCK_binlog_state);
+ return res;
+}
+
+
+slave_connection_state::slave_connection_state()
+{
+ my_hash_init(&hash, &my_charset_bin, 32,
+ offsetof(entry, gtid) + offsetof(rpl_gtid, domain_id),
+ sizeof(uint32), NULL, my_free, HASH_UNIQUE);
+}
+
+
+slave_connection_state::~slave_connection_state()
+{
+ my_hash_free(&hash);
+}
+
+
+/*
+ Create a hash from the slave GTID state that is sent to master when slave
+ connects to start replication.
+
+ The state is sent as <GTID>,<GTID>,...,<GTID>, for example:
+
+ 0-2-112,1-4-1022
+
+ The state gives for each domain_id the GTID to start replication from for
+ the corresponding replication stream. So domain_id must be unique.
+
+ Returns 0 if ok, non-zero if error due to malformed input.
+
+ Note that input string is built by slave server, so it will not be incorrect
+ unless bug/corruption/malicious server. So we just need basic sanity check,
+ not fancy user-friendly error message.
+*/
+
+int
+slave_connection_state::load(char *slave_request, size_t len)
+{
+ char *p, *end;
+ uchar *rec;
+ rpl_gtid *gtid;
+ const entry *e;
+
+ reset();
+ p= slave_request;
+ end= slave_request + len;
+ if (p == end)
+ return 0;
+ for (;;)
+ {
+ if (!(rec= (uchar *)my_malloc(sizeof(entry), MYF(MY_WME))))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), sizeof(*gtid));
+ return 1;
+ }
+ gtid= &((entry *)rec)->gtid;
+ if (gtid_parser_helper(&p, end, gtid))
+ {
+ my_free(rec);
+ my_error(ER_INCORRECT_GTID_STATE, MYF(0));
+ return 1;
+ }
+ if ((e= (const entry *)
+ my_hash_search(&hash, (const uchar *)(&gtid->domain_id), 0)))
+ {
+ my_error(ER_DUPLICATE_GTID_DOMAIN, MYF(0), gtid->domain_id,
+ gtid->server_id, (ulonglong)gtid->seq_no, e->gtid.domain_id,
+ e->gtid.server_id, (ulonglong)e->gtid.seq_no, gtid->domain_id);
+ my_free(rec);
+ return 1;
+ }
+ ((entry *)rec)->flags= 0;
+ if (my_hash_insert(&hash, rec))
+ {
+ my_free(rec);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return 1;
+ }
+ if (p == end)
+ break; /* Finished. */
+ if (*p != ',')
+ {
+ my_error(ER_INCORRECT_GTID_STATE, MYF(0));
+ return 1;
+ }
+ ++p;
+ }
+
+ return 0;
+}
+
+
+int
+slave_connection_state::load(const rpl_gtid *gtid_list, uint32 count)
+{
+ uint32 i;
+
+ reset();
+ for (i= 0; i < count; ++i)
+ if (update(&gtid_list[i]))
+ return 1;
+ return 0;
+}
+
+
+static int
+slave_connection_state_load_cb(rpl_gtid *gtid, void *data)
+{
+ slave_connection_state *state= (slave_connection_state *)data;
+ return state->update(gtid);
+}
+
+
+/*
+ Same as rpl_slave_state::tostring(), but populates a slave_connection_state
+ instead.
+*/
+int
+slave_connection_state::load(rpl_slave_state *state,
+ rpl_gtid *extra_gtids, uint32 num_extra)
+{
+ reset();
+ return state->iterate(slave_connection_state_load_cb, this,
+ extra_gtids, num_extra);
+}
+
+
+slave_connection_state::entry *
+slave_connection_state::find_entry(uint32 domain_id)
+{
+ return (entry *) my_hash_search(&hash, (const uchar *)(&domain_id), 0);
+}
+
+
+rpl_gtid *
+slave_connection_state::find(uint32 domain_id)
+{
+ entry *e= find_entry(domain_id);
+ if (!e)
+ return NULL;
+ return &e->gtid;
+}
+
+
+int
+slave_connection_state::update(const rpl_gtid *in_gtid)
+{
+ entry *e;
+ uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0);
+ if (rec)
+ {
+ e= (entry *)rec;
+ e->gtid= *in_gtid;
+ return 0;
+ }
+
+ if (!(e= (entry *)my_malloc(sizeof(*e), MYF(MY_WME))))
+ return 1;
+ e->gtid= *in_gtid;
+ e->flags= 0;
+ if (my_hash_insert(&hash, (uchar *)e))
+ {
+ my_free(e);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+void
+slave_connection_state::remove(const rpl_gtid *in_gtid)
+{
+ uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0);
+#ifndef DBUG_OFF
+ bool err;
+ rpl_gtid *slave_gtid= &((entry *)rec)->gtid;
+ DBUG_ASSERT(rec /* We should never try to remove not present domain_id. */);
+ DBUG_ASSERT(slave_gtid->server_id == in_gtid->server_id);
+ DBUG_ASSERT(slave_gtid->seq_no == in_gtid->seq_no);
+#endif
+
+ IF_DBUG(err=, )
+ my_hash_delete(&hash, rec);
+ DBUG_ASSERT(!err);
+}
+
+
+void
+slave_connection_state::remove_if_present(const rpl_gtid *in_gtid)
+{
+ uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0);
+ if (rec)
+ my_hash_delete(&hash, rec);
+}
+
+
+int
+slave_connection_state::to_string(String *out_str)
+{
+ out_str->length(0);
+ return append_to_string(out_str);
+}
+
+
+int
+slave_connection_state::append_to_string(String *out_str)
+{
+ uint32 i;
+ bool first;
+
+ first= true;
+ for (i= 0; i < hash.records; ++i)
+ {
+ const entry *e= (const entry *)my_hash_element(&hash, i);
+ if (rpl_slave_state_tostring_helper(out_str, &e->gtid, &first))
+ return 1;
+ }
+ return 0;
+}
+
+
+int
+slave_connection_state::get_gtid_list(rpl_gtid *gtid_list, uint32 list_size)
+{
+ uint32 i, pos;
+
+ pos= 0;
+ for (i= 0; i < hash.records; ++i)
+ {
+ entry *e;
+ if (pos >= list_size)
+ return 1;
+ e= (entry *)my_hash_element(&hash, i);
+ memcpy(&gtid_list[pos++], &e->gtid, sizeof(e->gtid));
+ }
+
+ return 0;
+}
+
+
+/*
+ Check if the GTID position has been reached, for mysql_binlog_send().
+
+ The position has not been reached if we have anything in the state, unless
+ it has either the START_ON_EMPTY_DOMAIN flag set (which means it does not
+ belong to this master at all), or the START_OWN_SLAVE_POS (which means that
+ we start on an old position from when the server was a slave with
+ --log-slave-updates=0).
+*/
+bool
+slave_connection_state::is_pos_reached()
+{
+ uint32 i;
+
+ for (i= 0; i < hash.records; ++i)
+ {
+ entry *e= (entry *)my_hash_element(&hash, i);
+ if (!(e->flags & (START_OWN_SLAVE_POS|START_ON_EMPTY_DOMAIN)))
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+ Execute a MASTER_GTID_WAIT().
+ The position to wait for is in gtid_str in string form.
+ The timeout in microseconds is in timeout_us, zero means no timeout.
+
+ Returns:
+ 1 for error.
+ 0 for wait completed.
+ -1 for wait timed out.
+*/
+int
+gtid_waiting::wait_for_pos(THD *thd, String *gtid_str, longlong timeout_us)
+{
+ int err;
+ rpl_gtid *wait_pos;
+ uint32 count, i;
+ struct timespec wait_until, *wait_until_ptr;
+
+ /* Wait for the empty position returns immediately. */
+ if (gtid_str->length() == 0)
+ return 0;
+
+ if (!(wait_pos= gtid_parse_string_to_list(gtid_str->ptr(), gtid_str->length(),
+ &count)))
+ {
+ my_error(ER_INCORRECT_GTID_STATE, MYF(0));
+ return 1;
+ }
+
+ if (timeout_us >= 0)
+ {
+ set_timespec_nsec(wait_until, (ulonglong)1000*timeout_us);
+ wait_until_ptr= &wait_until;
+ }
+ else
+ wait_until_ptr= NULL;
+ err= 0;
+ for (i= 0; i < count; ++i)
+ {
+ if ((err= wait_for_gtid(thd, &wait_pos[i], wait_until_ptr)))
+ break;
+ }
+ my_free(wait_pos);
+ return err;
+}
+
+
+void
+gtid_waiting::promote_new_waiter(gtid_waiting::hash_element *he)
+{
+ queue_element *qe;
+
+ mysql_mutex_assert_owner(&LOCK_gtid_waiting);
+ if (queue_empty(&he->queue))
+ return;
+ qe= (queue_element *)queue_top(&he->queue);
+ qe->do_small_wait= true;
+ mysql_cond_signal(&qe->thd->COND_wakeup_ready);
+}
+
+void
+gtid_waiting::process_wait_hash(uint64 wakeup_seq_no,
+ gtid_waiting::hash_element *he)
+{
+ mysql_mutex_assert_owner(&LOCK_gtid_waiting);
+
+ for (;;)
+ {
+ queue_element *qe;
+
+ if (queue_empty(&he->queue))
+ break;
+ qe= (queue_element *)queue_top(&he->queue);
+ if (qe->wait_seq_no > wakeup_seq_no)
+ break;
+ DBUG_ASSERT(!qe->done);
+ queue_remove_top(&he->queue);
+ qe->done= true;;
+ mysql_cond_signal(&qe->thd->COND_wakeup_ready);
+ }
+}
+
+
+/*
+ Execute a MASTER_GTID_WAIT() for one specific domain.
+
+ The implementation is optimised primarily for (1) minimal performance impact
+ on the slave replication threads, and secondarily for (2) quick performance
+ of MASTER_GTID_WAIT() on a single GTID, which can be useful for consistent
+ read to clients in an async replication read-scaleout scenario.
+
+ To achieve (1), we have a "small" wait and a "large" wait. The small wait
+ contends with the replication threads on the lock on the gtid_slave_pos, so
+ only minimal processing is done under that lock, and only a single waiter at
+ a time does the small wait.
+
+ If there is already a small waiter, a new thread will either replace the
+ small waiter (if it needs to wait for an earlier sequence number), or
+ instead do a "large" wait.
+
+ Once awoken on the small wait, the waiting thread releases the lock shared
+ with the SQL threads quickly, and then processes all waiters currently doing
+ the large wait using a different lock that does not impact replication.
+
+ This way, the SQL threads only need to do a single check + possibly a
+ pthread_cond_signal() when updating the gtid_slave_state, and the time that
+ non-SQL threads contend for the lock on gtid_slave_state is minimized.
+
+ There is always at least one thread that has the responsibility to ensure
+ that there is a small waiter; this thread has queue_element::do_small_wait
+ set to true. This thread will do the small wait until it is done, at which
+ point it will make sure to pass on the responsibility to another thread.
+ Normally only one thread has do_small_wait==true, but it can occasionally
+ happen that there is more than one, when threads race one another for the
+ lock on the small wait (this results in slightly increased activity on the
+ small lock but is otherwise harmless).
+
+ Returns:
+ 0 Wait completed normally
+ -1 Wait completed due to timeout
+ 1 An error (my_error() will have been called to set the error in the da)
+*/
+int
+gtid_waiting::wait_for_gtid(THD *thd, rpl_gtid *wait_gtid,
+ struct timespec *wait_until)
+{
+ bool timed_out= false;
+#ifdef HAVE_REPLICATION
+ queue_element elem;
+ uint32 domain_id= wait_gtid->domain_id;
+ uint64 seq_no= wait_gtid->seq_no;
+ hash_element *he;
+ rpl_slave_state::element *slave_state_elem= NULL;
+ PSI_stage_info old_stage;
+ bool did_enter_cond= false;
+
+ elem.wait_seq_no= seq_no;
+ elem.thd= thd;
+ elem.done= false;
+
+ mysql_mutex_lock(&LOCK_gtid_waiting);
+ if (!(he= get_entry(wait_gtid->domain_id)))
+ {
+ mysql_mutex_unlock(&LOCK_gtid_waiting);
+ return 1;
+ }
+ /*
+ If there is already another waiter with seq_no no larger than our own,
+ we are sure that there is already a small waiter that will wake us up
+ (or later pass the small wait responsibility to us). So in this case, we
+ do not need to touch the small wait lock at all.
+ */
+ elem.do_small_wait=
+ (queue_empty(&he->queue) ||
+ ((queue_element *)queue_top(&he->queue))->wait_seq_no > seq_no);
+
+ if (register_in_wait_queue(thd, wait_gtid, he, &elem))
+ {
+ mysql_mutex_unlock(&LOCK_gtid_waiting);
+ return 1;
+ }
+ /*
+ Loop, doing either the small or large wait as appropriate, until either
+ the position waited for is reached, or we get a kill or timeout.
+ */
+ for (;;)
+ {
+ mysql_mutex_assert_owner(&LOCK_gtid_waiting);
+
+ if (elem.do_small_wait)
+ {
+ uint64 wakeup_seq_no;
+ queue_element *cur_waiter;
+
+ mysql_mutex_lock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ /*
+ The elements in the gtid_slave_state_hash are never re-allocated once
+ they enter the hash, so we do not need to re-do the lookup after releasing
+ and re-aquiring the lock.
+ */
+ if (!slave_state_elem &&
+ !(slave_state_elem= rpl_global_gtid_slave_state.get_element(domain_id)))
+ {
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ remove_from_wait_queue(he, &elem);
+ promote_new_waiter(he);
+ if (did_enter_cond)
+ thd->EXIT_COND(&old_stage);
+ else
+ mysql_mutex_unlock(&LOCK_gtid_waiting);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return 1;
+ }
+
+ if ((wakeup_seq_no= slave_state_elem->highest_seq_no) >= seq_no)
+ {
+ /*
+ We do not have to wait. (We will be removed from the wait queue when
+ we call process_wait_hash() below.
+ */
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ }
+ else if ((cur_waiter= slave_state_elem->gtid_waiter) &&
+ slave_state_elem->min_wait_seq_no <= seq_no)
+ {
+ /*
+ There is already a suitable small waiter, go do the large wait.
+ (Normally we would not have needed to check the small wait in this
+ case, but it can happen if we race with another thread for the small
+ lock).
+ */
+ elem.do_small_wait= false;
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ }
+ else
+ {
+ /*
+ We have to do the small wait ourselves (stealing it from any thread
+ that might already be waiting for a later seq_no).
+ */
+ slave_state_elem->gtid_waiter= &elem;
+ slave_state_elem->min_wait_seq_no= seq_no;
+ if (cur_waiter)
+ {
+ /* We stole the wait, so wake up the old waiting thread. */
+ mysql_cond_signal(&slave_state_elem->COND_wait_gtid);
+ }
+
+ /* Release the large lock, and do the small wait. */
+ if (did_enter_cond)
+ {
+ thd->EXIT_COND(&old_stage);
+ did_enter_cond= false;
+ }
+ else
+ mysql_mutex_unlock(&LOCK_gtid_waiting);
+ thd->ENTER_COND(&slave_state_elem->COND_wait_gtid,
+ &rpl_global_gtid_slave_state.LOCK_slave_state,
+ &stage_master_gtid_wait_primary, &old_stage);
+ do
+ {
+ if (thd->check_killed())
+ break;
+ else if (wait_until)
+ {
+ int err=
+ mysql_cond_timedwait(&slave_state_elem->COND_wait_gtid,
+ &rpl_global_gtid_slave_state.LOCK_slave_state,
+ wait_until);
+ if (err == ETIMEDOUT || err == ETIME)
+ {
+ timed_out= true;
+ break;
+ }
+ }
+ else
+ mysql_cond_wait(&slave_state_elem->COND_wait_gtid,
+ &rpl_global_gtid_slave_state.LOCK_slave_state);
+ } while (slave_state_elem->gtid_waiter == &elem);
+ wakeup_seq_no= slave_state_elem->highest_seq_no;
+ /*
+ If we aborted due to timeout or kill, remove us as waiter.
+
+ If we were replaced by another waiter with a smaller seq_no, then we
+ no longer have responsibility for the small wait.
+ */
+ if ((cur_waiter= slave_state_elem->gtid_waiter))
+ {
+ if (cur_waiter == &elem)
+ slave_state_elem->gtid_waiter= NULL;
+ else if (slave_state_elem->min_wait_seq_no <= seq_no)
+ elem.do_small_wait= false;
+ }
+ thd->EXIT_COND(&old_stage);
+
+ mysql_mutex_lock(&LOCK_gtid_waiting);
+ }
+
+ /*
+ Note that hash_entry pointers do not change once allocated, so we do
+ not need to lookup `he' again after re-aquiring LOCK_gtid_waiting.
+ */
+ process_wait_hash(wakeup_seq_no, he);
+ }
+ else
+ {
+ /* Do the large wait. */
+ if (!did_enter_cond)
+ {
+ thd->ENTER_COND(&thd->COND_wakeup_ready, &LOCK_gtid_waiting,
+ &stage_master_gtid_wait, &old_stage);
+ did_enter_cond= true;
+ }
+ while (!elem.done && !thd->check_killed())
+ {
+ thd_wait_begin(thd, THD_WAIT_BINLOG);
+ if (wait_until)
+ {
+ int err= mysql_cond_timedwait(&thd->COND_wakeup_ready,
+ &LOCK_gtid_waiting, wait_until);
+ if (err == ETIMEDOUT || err == ETIME)
+ timed_out= true;
+ }
+ else
+ mysql_cond_wait(&thd->COND_wakeup_ready, &LOCK_gtid_waiting);
+ thd_wait_end(thd);
+ if (elem.do_small_wait || timed_out)
+ break;
+ }
+ }
+
+ if ((thd->killed || timed_out) && !elem.done)
+ {
+ /* Aborted, so remove ourselves from the hash. */
+ remove_from_wait_queue(he, &elem);
+ elem.done= true;
+ }
+ if (elem.done)
+ {
+ /*
+ If our wait is done, but we have (or were passed) responsibility for
+ the small wait, then we need to pass on that task to someone else.
+ */
+ if (elem.do_small_wait)
+ promote_new_waiter(he);
+ break;
+ }
+ }
+
+ if (did_enter_cond)
+ thd->EXIT_COND(&old_stage);
+ else
+ mysql_mutex_unlock(&LOCK_gtid_waiting);
+ if (thd->killed)
+ thd->send_kill_message();
+#endif /* HAVE_REPLICATION */
+ return timed_out ? -1 : 0;
+}
+
+
+static void
+free_hash_element(void *p)
+{
+ gtid_waiting::hash_element *e= (gtid_waiting::hash_element *)p;
+ delete_queue(&e->queue);
+ my_free(e);
+}
+
+
+void
+gtid_waiting::init()
+{
+ my_hash_init(&hash, &my_charset_bin, 32,
+ offsetof(hash_element, domain_id), sizeof(uint32), NULL,
+ free_hash_element, HASH_UNIQUE);
+ mysql_mutex_init(key_LOCK_gtid_waiting, &LOCK_gtid_waiting, 0);
+}
+
+
+void
+gtid_waiting::destroy()
+{
+ mysql_mutex_destroy(&LOCK_gtid_waiting);
+ my_hash_free(&hash);
+}
+
+
+static int
+cmp_queue_elem(void *, uchar *a, uchar *b)
+{
+ uint64 seq_no_a= *(uint64 *)a;
+ uint64 seq_no_b= *(uint64 *)b;
+ if (seq_no_a < seq_no_b)
+ return -1;
+ else if (seq_no_a == seq_no_b)
+ return 0;
+ else
+ return 1;
+}
+
+
+gtid_waiting::hash_element *
+gtid_waiting::get_entry(uint32 domain_id)
+{
+ hash_element *e;
+
+ if ((e= (hash_element *)my_hash_search(&hash, (const uchar *)&domain_id, 0)))
+ return e;
+
+ if (!(e= (hash_element *)my_malloc(sizeof(*e), MYF(MY_WME))))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), sizeof(*e));
+ return NULL;
+ }
+
+ if (init_queue(&e->queue, 8, offsetof(queue_element, wait_seq_no), 0,
+ cmp_queue_elem, NULL, 1+offsetof(queue_element, queue_idx), 1))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ my_free(e);
+ return NULL;
+ }
+ e->domain_id= domain_id;
+ if (my_hash_insert(&hash, (uchar *)e))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ delete_queue(&e->queue);
+ my_free(e);
+ return NULL;
+ }
+ return e;
+}
+
+
+int
+gtid_waiting::register_in_wait_queue(THD *thd, rpl_gtid *wait_gtid,
+ gtid_waiting::hash_element *he,
+ gtid_waiting::queue_element *elem)
+{
+ mysql_mutex_assert_owner(&LOCK_gtid_waiting);
+
+ if (queue_insert_safe(&he->queue, (uchar *)elem))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return 1;
+ }
+
+ return 0;
+}
+
+
+void
+gtid_waiting::remove_from_wait_queue(gtid_waiting::hash_element *he,
+ gtid_waiting::queue_element *elem)
+{
+ mysql_mutex_assert_owner(&LOCK_gtid_waiting);
+
+ queue_remove(&he->queue, elem->queue_idx);
+}
diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h
new file mode 100644
index 00000000000..997540728a5
--- /dev/null
+++ b/sql/rpl_gtid.h
@@ -0,0 +1,301 @@
+/* Copyright (c) 2013, Kristian Nielsen and MariaDB Services Ab.
+
+ 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 */
+
+#ifndef RPL_GTID_H
+#define RPL_GTID_H
+
+#include "hash.h"
+#include "queues.h"
+
+
+/* Definitions for MariaDB global transaction ID (GTID). */
+
+
+extern const LEX_STRING rpl_gtid_slave_state_table_name;
+
+class String;
+
+struct rpl_gtid
+{
+ uint32 domain_id;
+ uint32 server_id;
+ uint64 seq_no;
+};
+
+
+enum enum_gtid_skip_type {
+ GTID_SKIP_NOT, GTID_SKIP_STANDALONE, GTID_SKIP_TRANSACTION
+};
+
+
+/*
+ Structure to keep track of threads waiting in MASTER_GTID_WAIT().
+
+ Since replication is (mostly) single-threaded, we want to minimise the
+ performance impact on that from MASTER_GTID_WAIT(). To achieve this, we
+ are careful to keep the common lock between replication threads and
+ MASTER_GTID_WAIT threads held for as short as possible. We keep only
+ a single thread waiting to be notified by the replication threads; this
+ thread then handles all the (potentially heavy) lifting of dealing with
+ all current waiting threads.
+*/
+struct gtid_waiting {
+ /* Elements in the hash, basically a priority queue for each domain. */
+ struct hash_element {
+ QUEUE queue;
+ uint32 domain_id;
+ };
+ /* A priority queue to handle waiters in one domain in seq_no order. */
+ struct queue_element {
+ uint64 wait_seq_no;
+ THD *thd;
+ int queue_idx;
+ /*
+ do_small_wait is true if we have responsibility for ensuring that there
+ is a small waiter.
+ */
+ bool do_small_wait;
+ /*
+ The flag `done' is set when the wait is completed (either due to reaching
+ the position waited for, or due to timeout or kill). The queue_element
+ is in the queue if and only if `done' is true.
+ */
+ bool done;
+ };
+
+ mysql_mutex_t LOCK_gtid_waiting;
+ HASH hash;
+
+ void init();
+ void destroy();
+ hash_element *get_entry(uint32 domain_id);
+ int wait_for_pos(THD *thd, String *gtid_str, longlong timeout_us);
+ void promote_new_waiter(gtid_waiting::hash_element *he);
+ int wait_for_gtid(THD *thd, rpl_gtid *wait_gtid, struct timespec *wait_until);
+ void process_wait_hash(uint64 wakeup_seq_no, gtid_waiting::hash_element *he);
+ int register_in_wait_queue(THD *thd, rpl_gtid *wait_gtid, hash_element *he,
+ queue_element *elem);
+ void remove_from_wait_queue(hash_element *he, queue_element *elem);
+};
+
+
+class Relay_log_info;
+struct rpl_group_info;
+
+/*
+ Replication slave state.
+
+ For every independent replication stream (identified by domain_id), this
+ remembers the last gtid applied on the slave within this domain.
+
+ Since events are always committed in-order within a single domain, this is
+ sufficient to maintain the state of the replication slave.
+*/
+struct rpl_slave_state
+{
+ /* Elements in the list of GTIDs kept for each domain_id. */
+ struct list_element
+ {
+ struct list_element *next;
+ uint64 sub_id;
+ uint64 seq_no;
+ uint32 server_id;
+ };
+
+ /* Elements in the HASH that hold the state for one domain_id. */
+ struct element
+ {
+ struct list_element *list;
+ uint32 domain_id;
+ /* Highest seq_no seen so far in this domain. */
+ uint64 highest_seq_no;
+ /*
+ If this is non-NULL, then it is the waiter responsible for the small
+ wait in MASTER_GTID_WAIT().
+ */
+ gtid_waiting::queue_element *gtid_waiter;
+ /*
+ If gtid_waiter is non-NULL, then this is the seq_no that its
+ MASTER_GTID_WAIT() is waiting on. When we reach this seq_no, we need to
+ signal the waiter on COND_wait_gtid.
+ */
+ uint64 min_wait_seq_no;
+ mysql_cond_t COND_wait_gtid;
+
+ /*
+ For --gtid-ignore-duplicates. The Relay_log_info that currently owns
+ this domain, and the number of worker threads that are active in it.
+
+ The idea is that only one of multiple master connections is allowed to
+ actively apply events for a given domain. Other connections must either
+ discard the events (if the seq_no in GTID shows they have already been
+ applied), or wait to see if the current owner will apply it.
+ */
+ const Relay_log_info *owner_rli;
+ uint32 owner_count;
+ mysql_cond_t COND_gtid_ignore_duplicates;
+
+ list_element *grab_list() { list_element *l= list; list= NULL; return l; }
+ void add(list_element *l)
+ {
+ l->next= list;
+ list= l;
+ }
+ };
+
+ /* Mapping from domain_id to its element. */
+ HASH hash;
+ /* Mutex protecting access to the state. */
+ mysql_mutex_t LOCK_slave_state;
+
+ uint64 last_sub_id;
+ bool inited;
+ bool loaded;
+
+ rpl_slave_state();
+ ~rpl_slave_state();
+
+ void init();
+ void deinit();
+ void truncate_hash();
+ ulong count() const { return hash.records; }
+ int update(uint32 domain_id, uint32 server_id, uint64 sub_id,
+ uint64 seq_no, rpl_group_info *rgi);
+ int truncate_state_table(THD *thd);
+ int record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
+ bool in_transaction, bool in_statement);
+ uint64 next_sub_id(uint32 domain_id);
+ int iterate(int (*cb)(rpl_gtid *, void *), void *data,
+ rpl_gtid *extra_gtids, uint32 num_extra);
+ int tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra);
+ bool domain_to_gtid(uint32 domain_id, rpl_gtid *out_gtid);
+ int load(THD *thd, char *state_from_master, size_t len, bool reset,
+ bool in_statement);
+ bool is_empty();
+
+ element *get_element(uint32 domain_id);
+ int put_back_list(uint32 domain_id, list_element *list);
+
+ void update_state_hash(uint64 sub_id, rpl_gtid *gtid, rpl_group_info *rgi);
+ int record_and_update_gtid(THD *thd, struct rpl_group_info *rgi);
+ int check_duplicate_gtid(rpl_gtid *gtid, rpl_group_info *rgi);
+ void release_domain_owner(rpl_group_info *rgi);
+};
+
+
+/*
+ Binlog state.
+ This keeps the last GTID written to the binlog for every distinct
+ (domain_id, server_id) pair.
+ This will be logged at the start of the next binlog file as a
+ Gtid_list_log_event; this way, it is easy to find the binlog file
+ containing a gigen GTID, by simply scanning backwards from the newest
+ one until a lower seq_no is found in the Gtid_list_log_event at the
+ start of a binlog for the given domain_id and server_id.
+
+ We also remember the last logged GTID for every domain_id. This is used
+ to know where to start when a master is changed to a slave. As a side
+ effect, it also allows to skip a hash lookup in the very common case of
+ logging a new GTID with same server id as last GTID.
+*/
+struct rpl_binlog_state
+{
+ struct element {
+ uint32 domain_id;
+ HASH hash; /* Containing all server_id for one domain_id */
+ /* The most recent entry in the hash. */
+ rpl_gtid *last_gtid;
+ /* Counter to allocate next seq_no for this domain. */
+ uint64 seq_no_counter;
+
+ int update_element(const rpl_gtid *gtid);
+ };
+ /* Mapping from domain_id to collection of elements. */
+ HASH hash;
+ /* Mutex protecting access to the state. */
+ mysql_mutex_t LOCK_binlog_state;
+ my_bool initialized;
+
+ rpl_binlog_state();
+ ~rpl_binlog_state();
+
+ void reset_nolock();
+ void reset();
+ void free();
+ bool load(struct rpl_gtid *list, uint32 count);
+ bool load(rpl_slave_state *slave_pos);
+ int update_nolock(const struct rpl_gtid *gtid, bool strict);
+ int update(const struct rpl_gtid *gtid, bool strict);
+ int update_with_next_gtid(uint32 domain_id, uint32 server_id,
+ rpl_gtid *gtid);
+ int alloc_element_nolock(const rpl_gtid *gtid);
+ bool check_strict_sequence(uint32 domain_id, uint32 server_id, uint64 seq_no);
+ int bump_seq_no_if_needed(uint32 domain_id, uint64 seq_no);
+ int write_to_iocache(IO_CACHE *dest);
+ int read_from_iocache(IO_CACHE *src);
+ uint32 count();
+ int get_gtid_list(rpl_gtid *gtid_list, uint32 list_size);
+ int get_most_recent_gtid_list(rpl_gtid **list, uint32 *size);
+ bool append_pos(String *str);
+ bool append_state(String *str);
+ rpl_gtid *find_nolock(uint32 domain_id, uint32 server_id);
+ rpl_gtid *find(uint32 domain_id, uint32 server_id);
+ rpl_gtid *find_most_recent(uint32 domain_id);
+};
+
+
+/*
+ Represent the GTID state that a slave connection to a master requests
+ the master to start sending binlog events from.
+*/
+struct slave_connection_state
+{
+ struct entry {
+ rpl_gtid gtid;
+ uint32 flags;
+ };
+ static const uint32 START_OWN_SLAVE_POS= 0x1;
+ static const uint32 START_ON_EMPTY_DOMAIN= 0x2;
+
+ /* Mapping from domain_id to the entry with GTID requested for that domain. */
+ HASH hash;
+
+ slave_connection_state();
+ ~slave_connection_state();
+
+ void reset() { my_hash_reset(&hash); }
+ int load(char *slave_request, size_t len);
+ int load(const rpl_gtid *gtid_list, uint32 count);
+ int load(rpl_slave_state *state, rpl_gtid *extra_gtids, uint32 num_extra);
+ rpl_gtid *find(uint32 domain_id);
+ entry *find_entry(uint32 domain_id);
+ int update(const rpl_gtid *in_gtid);
+ void remove(const rpl_gtid *gtid);
+ void remove_if_present(const rpl_gtid *in_gtid);
+ ulong count() const { return hash.records; }
+ int to_string(String *out_str);
+ int append_to_string(String *out_str);
+ int get_gtid_list(rpl_gtid *gtid_list, uint32 list_size);
+ bool is_pos_reached();
+};
+
+
+extern bool rpl_slave_state_tostring_helper(String *dest, const rpl_gtid *gtid,
+ bool *first);
+extern int gtid_check_rpl_slave_state_table(TABLE *table);
+extern rpl_gtid *gtid_parse_string_to_list(const char *p, size_t len,
+ uint32 *out_len);
+
+#endif /* RPL_GTID_H */
diff --git a/sql/rpl_handler.cc b/sql/rpl_handler.cc
new file mode 100644
index 00000000000..09e221e9bd5
--- /dev/null
+++ b/sql/rpl_handler.cc
@@ -0,0 +1,536 @@
+/* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+
+#include "rpl_mi.h"
+#include "sql_repl.h"
+#include "log_event.h"
+#include "rpl_filter.h"
+#include <my_dir.h>
+#include "rpl_handler.h"
+
+Trans_delegate *transaction_delegate;
+Binlog_storage_delegate *binlog_storage_delegate;
+#ifdef HAVE_REPLICATION
+Binlog_transmit_delegate *binlog_transmit_delegate;
+Binlog_relay_IO_delegate *binlog_relay_io_delegate;
+#endif /* HAVE_REPLICATION */
+
+/*
+ structure to save transaction log filename and position
+*/
+typedef struct Trans_binlog_info {
+ my_off_t log_pos;
+ char log_file[FN_REFLEN];
+} Trans_binlog_info;
+
+static pthread_key(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO);
+
+int get_user_var_int(const char *name,
+ long long int *value, int *null_value)
+{
+ bool null_val;
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&current_thd->user_vars,
+ (uchar*) name, strlen(name));
+ if (!entry)
+ return 1;
+ *value= entry->val_int(&null_val);
+ if (null_value)
+ *null_value= null_val;
+ return 0;
+}
+
+int get_user_var_real(const char *name,
+ double *value, int *null_value)
+{
+ bool null_val;
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&current_thd->user_vars,
+ (uchar*) name, strlen(name));
+ if (!entry)
+ return 1;
+ *value= entry->val_real(&null_val);
+ if (null_value)
+ *null_value= null_val;
+ return 0;
+}
+
+int get_user_var_str(const char *name, char *value,
+ size_t len, unsigned int precision, int *null_value)
+{
+ String str;
+ bool null_val;
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&current_thd->user_vars,
+ (uchar*) name, strlen(name));
+ if (!entry)
+ return 1;
+ entry->val_str(&null_val, &str, precision);
+ strncpy(value, str.c_ptr(), len);
+ if (null_value)
+ *null_value= null_val;
+ return 0;
+}
+
+int delegates_init()
+{
+ static my_aligned_storage<sizeof(Trans_delegate), MY_ALIGNOF(long)> trans_mem;
+ static my_aligned_storage<sizeof(Binlog_storage_delegate),
+ MY_ALIGNOF(long)> storage_mem;
+#ifdef HAVE_REPLICATION
+ static my_aligned_storage<sizeof(Binlog_transmit_delegate),
+ MY_ALIGNOF(long)> transmit_mem;
+ static my_aligned_storage<sizeof(Binlog_relay_IO_delegate),
+ MY_ALIGNOF(long)> relay_io_mem;
+#endif
+
+ void *place_trans_mem= trans_mem.data;
+ void *place_storage_mem= storage_mem.data;
+
+ transaction_delegate= new (place_trans_mem) Trans_delegate;
+
+ if (!transaction_delegate->is_inited())
+ {
+ sql_print_error("Initialization of transaction delegates failed. "
+ "Please report a bug.");
+ return 1;
+ }
+
+ binlog_storage_delegate= new (place_storage_mem) Binlog_storage_delegate;
+
+ if (!binlog_storage_delegate->is_inited())
+ {
+ sql_print_error("Initialization binlog storage delegates failed. "
+ "Please report a bug.");
+ return 1;
+ }
+
+#ifdef HAVE_REPLICATION
+ void *place_transmit_mem= transmit_mem.data;
+ void *place_relay_io_mem= relay_io_mem.data;
+
+ binlog_transmit_delegate= new (place_transmit_mem) Binlog_transmit_delegate;
+
+ if (!binlog_transmit_delegate->is_inited())
+ {
+ sql_print_error("Initialization of binlog transmit delegates failed. "
+ "Please report a bug.");
+ return 1;
+ }
+
+ binlog_relay_io_delegate= new (place_relay_io_mem) Binlog_relay_IO_delegate;
+
+ if (!binlog_relay_io_delegate->is_inited())
+ {
+ sql_print_error("Initialization binlog relay IO delegates failed. "
+ "Please report a bug.");
+ return 1;
+ }
+#endif
+
+ if (pthread_key_create(&RPL_TRANS_BINLOG_INFO, NULL))
+ {
+ sql_print_error("Error while creating pthread specific data key for replication. "
+ "Please report a bug.");
+ return 1;
+ }
+
+ return 0;
+}
+
+void delegates_destroy()
+{
+ if (transaction_delegate)
+ transaction_delegate->~Trans_delegate();
+ if (binlog_storage_delegate)
+ binlog_storage_delegate->~Binlog_storage_delegate();
+#ifdef HAVE_REPLICATION
+ if (binlog_transmit_delegate)
+ binlog_transmit_delegate->~Binlog_transmit_delegate();
+ if (binlog_relay_io_delegate)
+ binlog_relay_io_delegate->~Binlog_relay_IO_delegate();
+#endif /* HAVE_REPLICATION */
+}
+
+/*
+ This macro is used by almost all the Delegate methods to iterate
+ over all the observers running given callback function of the
+ delegate.
+ */
+#define FOREACH_OBSERVER(r, f, do_lock, args) \
+ param.server_id= thd->variables.server_id; \
+ read_lock(); \
+ Observer_info_iterator iter= observer_info_iter(); \
+ Observer_info *info= iter++; \
+ for (; info; info= iter++) \
+ { \
+ if (do_lock) plugin_lock(thd, plugin_int_to_ref(info->plugin_int)); \
+ if (((Observer *)info->observer)->f \
+ && ((Observer *)info->observer)->f args) \
+ { \
+ r= 1; \
+ sql_print_error("Run function '" #f "' in plugin '%s' failed", \
+ info->plugin_int->name.str); \
+ break; \
+ } \
+ } \
+ unlock();
+
+
+int Trans_delegate::after_commit(THD *thd, bool all)
+{
+ Trans_param param;
+ bool is_real_trans= (all || thd->transaction.all.ha_list == 0);
+
+ param.flags = is_real_trans ? TRANS_IS_REAL_TRANS : 0;
+
+ Trans_binlog_info *log_info=
+ my_pthread_getspecific_ptr(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO);
+
+ param.log_file= log_info ? log_info->log_file : 0;
+ param.log_pos= log_info ? log_info->log_pos : 0;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_commit, false, (&param));
+
+ /*
+ This is the end of a real transaction or autocommit statement, we
+ can free the memory allocated for binlog file and position.
+ */
+ if (is_real_trans && log_info)
+ {
+ my_pthread_setspecific_ptr(RPL_TRANS_BINLOG_INFO, NULL);
+ my_free(log_info);
+ }
+ return ret;
+}
+
+int Trans_delegate::after_rollback(THD *thd, bool all)
+{
+ Trans_param param;
+ bool is_real_trans= (all || thd->transaction.all.ha_list == 0);
+
+ param.flags = is_real_trans ? TRANS_IS_REAL_TRANS : 0;
+
+ Trans_binlog_info *log_info=
+ my_pthread_getspecific_ptr(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO);
+
+ param.log_file= log_info ? log_info->log_file : 0;
+ param.log_pos= log_info ? log_info->log_pos : 0;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_rollback, false, (&param));
+
+ /*
+ This is the end of a real transaction or autocommit statement, we
+ can free the memory allocated for binlog file and position.
+ */
+ if (is_real_trans && log_info)
+ {
+ my_pthread_setspecific_ptr(RPL_TRANS_BINLOG_INFO, NULL);
+ my_free(log_info);
+ }
+ return ret;
+}
+
+int Binlog_storage_delegate::after_flush(THD *thd,
+ const char *log_file,
+ my_off_t log_pos,
+ bool synced)
+{
+ Binlog_storage_param param;
+ uint32 flags=0;
+ if (synced)
+ flags |= BINLOG_STORAGE_IS_SYNCED;
+
+ Trans_binlog_info *log_info=
+ my_pthread_getspecific_ptr(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO);
+
+ if (!log_info)
+ {
+ if(!(log_info=
+ (Trans_binlog_info *)my_malloc(sizeof(Trans_binlog_info), MYF(0))))
+ return 1;
+ my_pthread_setspecific_ptr(RPL_TRANS_BINLOG_INFO, log_info);
+ }
+
+ strcpy(log_info->log_file, log_file+dirname_length(log_file));
+ log_info->log_pos = log_pos;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_flush, false,
+ (&param, log_info->log_file, log_info->log_pos, flags));
+ return ret;
+}
+
+#ifdef HAVE_REPLICATION
+int Binlog_transmit_delegate::transmit_start(THD *thd, ushort flags,
+ const char *log_file,
+ my_off_t log_pos)
+{
+ Binlog_transmit_param param;
+ param.flags= flags;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, transmit_start, true, (&param, log_file, log_pos));
+ return ret;
+}
+
+int Binlog_transmit_delegate::transmit_stop(THD *thd, ushort flags)
+{
+ Binlog_transmit_param param;
+ param.flags= flags;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, transmit_stop, false, (&param));
+ return ret;
+}
+
+int Binlog_transmit_delegate::reserve_header(THD *thd, ushort flags,
+ String *packet)
+{
+ /* NOTE2ME: Maximum extra header size for each observer, I hope 32
+ bytes should be enough for each Observer to reserve their extra
+ header. If later found this is not enough, we can increase this
+ /HEZX
+ */
+#define RESERVE_HEADER_SIZE 32
+ unsigned char header[RESERVE_HEADER_SIZE];
+ ulong hlen;
+ Binlog_transmit_param param;
+ param.flags= flags;
+ param.server_id= thd->variables.server_id;
+
+ int ret= 0;
+ read_lock();
+ Observer_info_iterator iter= observer_info_iter();
+ Observer_info *info= iter++;
+ for (; info; info= iter++)
+ {
+ hlen= 0;
+ if (((Observer *)info->observer)->reserve_header
+ && ((Observer *)info->observer)->reserve_header(&param,
+ header,
+ RESERVE_HEADER_SIZE,
+ &hlen))
+ {
+ ret= 1;
+ break;
+ }
+ if (hlen == 0)
+ continue;
+ if (hlen > RESERVE_HEADER_SIZE || packet->append((char *)header, hlen))
+ {
+ ret= 1;
+ break;
+ }
+ }
+ unlock();
+ return ret;
+}
+
+int Binlog_transmit_delegate::before_send_event(THD *thd, ushort flags,
+ String *packet,
+ const char *log_file,
+ my_off_t log_pos)
+{
+ Binlog_transmit_param param;
+ param.flags= flags;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, before_send_event, false,
+ (&param, (uchar *)packet->c_ptr(),
+ packet->length(),
+ log_file+dirname_length(log_file), log_pos));
+ return ret;
+}
+
+int Binlog_transmit_delegate::after_send_event(THD *thd, ushort flags,
+ String *packet)
+{
+ Binlog_transmit_param param;
+ param.flags= flags;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_send_event, false,
+ (&param, packet->c_ptr(), packet->length()));
+ return ret;
+}
+
+int Binlog_transmit_delegate::after_reset_master(THD *thd, ushort flags)
+
+{
+ Binlog_transmit_param param;
+ param.flags= flags;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_reset_master, false, (&param));
+ return ret;
+}
+
+void Binlog_relay_IO_delegate::init_param(Binlog_relay_IO_param *param,
+ Master_info *mi)
+{
+ param->mysql= mi->mysql;
+ param->user= mi->user;
+ param->host= mi->host;
+ param->port= mi->port;
+ param->master_log_name= mi->master_log_name;
+ param->master_log_pos= mi->master_log_pos;
+}
+
+int Binlog_relay_IO_delegate::thread_start(THD *thd, Master_info *mi)
+{
+ Binlog_relay_IO_param param;
+ init_param(&param, mi);
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, thread_start, true, (&param));
+ return ret;
+}
+
+
+int Binlog_relay_IO_delegate::thread_stop(THD *thd, Master_info *mi)
+{
+
+ Binlog_relay_IO_param param;
+ init_param(&param, mi);
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, thread_stop, false, (&param));
+ return ret;
+}
+
+int Binlog_relay_IO_delegate::before_request_transmit(THD *thd,
+ Master_info *mi,
+ ushort flags)
+{
+ Binlog_relay_IO_param param;
+ init_param(&param, mi);
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, before_request_transmit, false, (&param, (uint32)flags));
+ return ret;
+}
+
+int Binlog_relay_IO_delegate::after_read_event(THD *thd, Master_info *mi,
+ const char *packet, ulong len,
+ const char **event_buf,
+ ulong *event_len)
+{
+ Binlog_relay_IO_param param;
+ init_param(&param, mi);
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_read_event, false,
+ (&param, packet, len, event_buf, event_len));
+ return ret;
+}
+
+int Binlog_relay_IO_delegate::after_queue_event(THD *thd, Master_info *mi,
+ const char *event_buf,
+ ulong event_len,
+ bool synced)
+{
+ Binlog_relay_IO_param param;
+ init_param(&param, mi);
+
+ uint32 flags=0;
+ if (synced)
+ flags |= BINLOG_STORAGE_IS_SYNCED;
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_queue_event, false,
+ (&param, event_buf, event_len, flags));
+ return ret;
+}
+
+int Binlog_relay_IO_delegate::after_reset_slave(THD *thd, Master_info *mi)
+
+{
+ Binlog_relay_IO_param param;
+ init_param(&param, mi);
+
+ int ret= 0;
+ FOREACH_OBSERVER(ret, after_reset_slave, false, (&param));
+ return ret;
+}
+#endif /* HAVE_REPLICATION */
+
+int register_trans_observer(Trans_observer *observer, void *p)
+{
+ return transaction_delegate->add_observer(observer, (st_plugin_int *)p);
+}
+
+int unregister_trans_observer(Trans_observer *observer, void *p)
+{
+ return transaction_delegate->remove_observer(observer, (st_plugin_int *)p);
+}
+
+int register_binlog_storage_observer(Binlog_storage_observer *observer, void *p)
+{
+ return binlog_storage_delegate->add_observer(observer, (st_plugin_int *)p);
+}
+
+int unregister_binlog_storage_observer(Binlog_storage_observer *observer, void *p)
+{
+ return binlog_storage_delegate->remove_observer(observer, (st_plugin_int *)p);
+}
+
+#ifdef HAVE_REPLICATION
+int register_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p)
+{
+ return binlog_transmit_delegate->add_observer(observer, (st_plugin_int *)p);
+}
+
+int unregister_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p)
+{
+ return binlog_transmit_delegate->remove_observer(observer, (st_plugin_int *)p);
+}
+
+int register_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p)
+{
+ return binlog_relay_io_delegate->add_observer(observer, (st_plugin_int *)p);
+}
+
+int unregister_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p)
+{
+ return binlog_relay_io_delegate->remove_observer(observer, (st_plugin_int *)p);
+}
+#else
+int register_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p)
+{
+ return 0;
+}
+
+int unregister_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p)
+{
+ return 0;
+}
+
+int register_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p)
+{
+ return 0;
+}
+
+int unregister_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p)
+{
+ return 0;
+}
+#endif /* HAVE_REPLICATION */
diff --git a/sql/rpl_handler.h b/sql/rpl_handler.h
new file mode 100644
index 00000000000..e262ebdbd6b
--- /dev/null
+++ b/sql/rpl_handler.h
@@ -0,0 +1,213 @@
+/* Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef RPL_HANDLER_H
+#define RPL_HANDLER_H
+
+#include "sql_priv.h"
+#include "rpl_mi.h"
+#include "rpl_rli.h"
+#include "sql_plugin.h"
+#include "replication.h"
+
+class Observer_info {
+public:
+ void *observer;
+ st_plugin_int *plugin_int;
+
+ Observer_info(void *ob, st_plugin_int *p)
+ :observer(ob), plugin_int(p)
+ { }
+};
+
+class Delegate {
+public:
+ typedef List<Observer_info> Observer_info_list;
+ typedef List_iterator<Observer_info> Observer_info_iterator;
+
+ int add_observer(void *observer, st_plugin_int *plugin)
+ {
+ int ret= FALSE;
+ if (!inited)
+ return TRUE;
+ write_lock();
+ Observer_info_iterator iter(observer_info_list);
+ Observer_info *info= iter++;
+ while (info && info->observer != observer)
+ info= iter++;
+ if (!info)
+ {
+ info= new Observer_info(observer, plugin);
+ if (!info || observer_info_list.push_back(info, &memroot))
+ ret= TRUE;
+ }
+ else
+ ret= TRUE;
+ unlock();
+ return ret;
+ }
+
+ int remove_observer(void *observer, st_plugin_int *plugin)
+ {
+ int ret= FALSE;
+ if (!inited)
+ return TRUE;
+ write_lock();
+ Observer_info_iterator iter(observer_info_list);
+ Observer_info *info= iter++;
+ while (info && info->observer != observer)
+ info= iter++;
+ if (info)
+ {
+ iter.remove();
+ delete info;
+ }
+ else
+ ret= TRUE;
+ unlock();
+ return ret;
+ }
+
+ inline Observer_info_iterator observer_info_iter()
+ {
+ return Observer_info_iterator(observer_info_list);
+ }
+
+ inline bool is_empty()
+ {
+ return observer_info_list.is_empty();
+ }
+
+ inline int read_lock()
+ {
+ if (!inited)
+ return TRUE;
+ return rw_rdlock(&lock);
+ }
+
+ inline int write_lock()
+ {
+ if (!inited)
+ return TRUE;
+ return rw_wrlock(&lock);
+ }
+
+ inline int unlock()
+ {
+ if (!inited)
+ return TRUE;
+ return rw_unlock(&lock);
+ }
+
+ inline bool is_inited()
+ {
+ return inited;
+ }
+
+ Delegate()
+ {
+ inited= FALSE;
+ if (my_rwlock_init(&lock, NULL))
+ return;
+ init_sql_alloc(&memroot, 1024, 0, MYF(0));
+ inited= TRUE;
+ }
+ ~Delegate()
+ {
+ inited= FALSE;
+ rwlock_destroy(&lock);
+ free_root(&memroot, MYF(0));
+ }
+
+private:
+ Observer_info_list observer_info_list;
+ rw_lock_t lock;
+ MEM_ROOT memroot;
+ bool inited;
+};
+
+class Trans_delegate
+ :public Delegate {
+public:
+ typedef Trans_observer Observer;
+ int before_commit(THD *thd, bool all);
+ int before_rollback(THD *thd, bool all);
+ int after_commit(THD *thd, bool all);
+ int after_rollback(THD *thd, bool all);
+};
+
+class Binlog_storage_delegate
+ :public Delegate {
+public:
+ typedef Binlog_storage_observer Observer;
+ int after_flush(THD *thd, const char *log_file,
+ my_off_t log_pos, bool synced);
+};
+
+#ifdef HAVE_REPLICATION
+class Binlog_transmit_delegate
+ :public Delegate {
+public:
+ typedef Binlog_transmit_observer Observer;
+ int transmit_start(THD *thd, ushort flags,
+ const char *log_file, my_off_t log_pos);
+ int transmit_stop(THD *thd, ushort flags);
+ int reserve_header(THD *thd, ushort flags, String *packet);
+ int before_send_event(THD *thd, ushort flags,
+ String *packet, const
+ char *log_file, my_off_t log_pos );
+ int after_send_event(THD *thd, ushort flags,
+ String *packet);
+ int after_reset_master(THD *thd, ushort flags);
+};
+
+class Binlog_relay_IO_delegate
+ :public Delegate {
+public:
+ typedef Binlog_relay_IO_observer Observer;
+ int thread_start(THD *thd, Master_info *mi);
+ int thread_stop(THD *thd, Master_info *mi);
+ int before_request_transmit(THD *thd, Master_info *mi, ushort flags);
+ int after_read_event(THD *thd, Master_info *mi,
+ const char *packet, ulong len,
+ const char **event_buf, ulong *event_len);
+ int after_queue_event(THD *thd, Master_info *mi,
+ const char *event_buf, ulong event_len,
+ bool synced);
+ int after_reset_slave(THD *thd, Master_info *mi);
+private:
+ void init_param(Binlog_relay_IO_param *param, Master_info *mi);
+};
+#endif /* HAVE_REPLICATION */
+
+int delegates_init();
+void delegates_destroy();
+
+extern Trans_delegate *transaction_delegate;
+extern Binlog_storage_delegate *binlog_storage_delegate;
+#ifdef HAVE_REPLICATION
+extern Binlog_transmit_delegate *binlog_transmit_delegate;
+extern Binlog_relay_IO_delegate *binlog_relay_io_delegate;
+#endif /* HAVE_REPLICATION */
+
+/*
+ if there is no observers in the delegate, we can return 0
+ immediately.
+*/
+#define RUN_HOOK(group, hook, args) \
+ (group ##_delegate->is_empty() ? \
+ 0 : group ##_delegate->hook args)
+
+#endif /* RPL_HANDLER_H */
diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc
new file mode 100644
index 00000000000..19b193729fd
--- /dev/null
+++ b/sql/rpl_injector.cc
@@ -0,0 +1,251 @@
+/* Copyright (c) 2006, 2011, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED by later includes
+#include "rpl_injector.h"
+#include "transaction.h"
+#include "sql_parse.h" // begin_trans, end_trans, COMMIT
+#include "sql_base.h" // close_thread_tables
+#include "log_event.h" // Incident_log_event
+
+/*
+ injector::transaction - member definitions
+*/
+
+/* inline since it's called below */
+inline
+injector::transaction::transaction(MYSQL_BIN_LOG *log, THD *thd)
+ : m_state(START_STATE), m_thd(thd)
+{
+ /*
+ Default initialization of m_start_pos (which initializes it to garbage).
+ We need to fill it in using the code below.
+ */
+ LOG_INFO log_info;
+ log->get_current_log(&log_info);
+ /* !!! binlog_pos does not follow RAII !!! */
+ m_start_pos.m_file_name= my_strdup(log_info.log_file_name, MYF(0));
+ m_start_pos.m_file_pos= log_info.pos;
+
+ m_thd->lex->start_transaction_opt= 0; /* for begin_trans() */
+ trans_begin(m_thd);
+}
+
+injector::transaction::~transaction()
+{
+ if (!good())
+ return;
+
+ /* Needed since my_free expects a 'char*' (instead of 'void*'). */
+ char* const the_memory= const_cast<char*>(m_start_pos.m_file_name);
+
+ /*
+ We set the first character to null just to give all the copies of the
+ start position a (minimal) chance of seening that the memory is lost.
+ All assuming the my_free does not step over the memory, of course.
+ */
+ *the_memory= '\0';
+
+ my_free(the_memory);
+}
+
+/**
+ @retval 0 transaction committed
+ @retval 1 transaction rolled back
+ */
+int injector::transaction::commit()
+{
+ DBUG_ENTER("injector::transaction::commit()");
+ int error= m_thd->binlog_flush_pending_rows_event(true);
+ /*
+ Cluster replication does not preserve statement or
+ transaction boundaries of the master. Instead, a new
+ transaction on replication slave is started when a new GCI
+ (global checkpoint identifier) is issued, and is committed
+ when the last event of the check point has been received and
+ processed. This ensures consistency of each cluster in
+ cluster replication, and there is no requirement for stronger
+ consistency: MySQL replication is asynchronous with other
+ engines as well.
+
+ A practical consequence of that is that row level replication
+ stream passed through the injector thread never contains
+ COMMIT events.
+ Here we should preserve the server invariant that there is no
+ outstanding statement transaction when the normal transaction
+ is committed by committing the statement transaction
+ explicitly.
+ */
+ trans_commit_stmt(m_thd);
+ if (!trans_commit(m_thd))
+ {
+ close_thread_tables(m_thd);
+ m_thd->mdl_context.release_transactional_locks();
+ }
+ DBUG_RETURN(error);
+}
+
+
+int injector::transaction::use_table(server_id_type sid, table tbl)
+{
+ DBUG_ENTER("injector::transaction::use_table");
+
+ int error;
+
+ if ((error= check_state(TABLE_STATE)))
+ DBUG_RETURN(error);
+
+ server_id_type save_id= m_thd->variables.server_id;
+ m_thd->set_server_id(sid);
+ error= m_thd->binlog_write_table_map(tbl.get_table(),
+ tbl.is_transactional());
+ m_thd->set_server_id(save_id);
+ DBUG_RETURN(error);
+}
+
+
+int injector::transaction::write_row (server_id_type sid, table tbl,
+ MY_BITMAP const* cols, size_t colcnt,
+ record_type record)
+{
+ DBUG_ENTER("injector::transaction::write_row(...)");
+
+ int error= check_state(ROW_STATE);
+ if (error)
+ DBUG_RETURN(error);
+
+ server_id_type save_id= m_thd->variables.server_id;
+ m_thd->set_server_id(sid);
+ error= m_thd->binlog_write_row(tbl.get_table(), tbl.is_transactional(),
+ cols, colcnt, record);
+ m_thd->set_server_id(save_id);
+ DBUG_RETURN(error);
+}
+
+
+int injector::transaction::delete_row(server_id_type sid, table tbl,
+ MY_BITMAP const* cols, size_t colcnt,
+ record_type record)
+{
+ DBUG_ENTER("injector::transaction::delete_row(...)");
+
+ int error= check_state(ROW_STATE);
+ if (error)
+ DBUG_RETURN(error);
+
+ server_id_type save_id= m_thd->variables.server_id;
+ m_thd->set_server_id(sid);
+ error= m_thd->binlog_delete_row(tbl.get_table(), tbl.is_transactional(),
+ cols, colcnt, record);
+ m_thd->set_server_id(save_id);
+ DBUG_RETURN(error);
+}
+
+
+int injector::transaction::update_row(server_id_type sid, table tbl,
+ MY_BITMAP const* cols, size_t colcnt,
+ record_type before, record_type after)
+{
+ DBUG_ENTER("injector::transaction::update_row(...)");
+
+ int error= check_state(ROW_STATE);
+ if (error)
+ DBUG_RETURN(error);
+
+ server_id_type save_id= m_thd->variables.server_id;
+ m_thd->set_server_id(sid);
+ error= m_thd->binlog_update_row(tbl.get_table(), tbl.is_transactional(),
+ cols, colcnt, before, after);
+ m_thd->set_server_id(save_id);
+ DBUG_RETURN(error);
+}
+
+
+injector::transaction::binlog_pos injector::transaction::start_pos() const
+{
+ return m_start_pos;
+}
+
+
+/*
+ injector - member definitions
+*/
+
+/* This constructor is called below */
+inline injector::injector()
+{
+}
+
+static injector *s_injector= 0;
+injector *injector::instance()
+{
+ if (s_injector == 0)
+ s_injector= new injector;
+ /* "There can be only one [instance]" */
+ return s_injector;
+}
+
+void injector::free_instance()
+{
+ injector *inj = s_injector;
+
+ if (inj != 0)
+ {
+ s_injector= 0;
+ delete inj;
+ }
+}
+
+
+injector::transaction injector::new_trans(THD *thd)
+{
+ DBUG_ENTER("injector::new_trans(THD*)");
+ /*
+ Currently, there is no alternative to using 'mysql_bin_log' since that
+ is hardcoded into the way the handler is using the binary log.
+ */
+ DBUG_RETURN(transaction(&mysql_bin_log, thd));
+}
+
+void injector::new_trans(THD *thd, injector::transaction *ptr)
+{
+ DBUG_ENTER("injector::new_trans(THD *, transaction *)");
+ /*
+ Currently, there is no alternative to using 'mysql_bin_log' since that
+ is hardcoded into the way the handler is using the binary log.
+ */
+ transaction trans(&mysql_bin_log, thd);
+ ptr->swap(trans);
+
+ DBUG_VOID_RETURN;
+}
+
+int injector::record_incident(THD *thd, Incident incident)
+{
+ Incident_log_event ev(thd, incident);
+ if (int error= mysql_bin_log.write(&ev))
+ return error;
+ return mysql_bin_log.rotate_and_purge(true);
+}
+
+int injector::record_incident(THD *thd, Incident incident, LEX_STRING const message)
+{
+ Incident_log_event ev(thd, incident, message);
+ if (int error= mysql_bin_log.write(&ev))
+ return error;
+ return mysql_bin_log.rotate_and_purge(true);
+}
diff --git a/sql/rpl_injector.h b/sql/rpl_injector.h
new file mode 100644
index 00000000000..98788955e24
--- /dev/null
+++ b/sql/rpl_injector.h
@@ -0,0 +1,337 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef INJECTOR_H
+#define INJECTOR_H
+
+/* Pull in 'byte', 'my_off_t', and 'uint32' */
+#include <my_global.h>
+#include <my_bitmap.h>
+
+#include "rpl_constants.h"
+#include "table.h" /* TABLE */
+
+/* Forward declarations */
+class handler;
+class MYSQL_BIN_LOG;
+struct TABLE;
+
+
+/*
+ Injector to inject rows into the MySQL server.
+
+ The injector class is used to notify the MySQL server of new rows that have
+ appeared outside of MySQL control.
+
+ The original purpose of this is to allow clusters---which handle replication
+ inside the cluster through other means---to insert new rows into binary log.
+ Note, however, that the injector should be used whenever rows are altered in
+ any manner that is outside of MySQL server visibility and which therefore
+ are not seen by the MySQL server.
+ */
+class injector
+{
+public:
+
+ /*
+ Get an instance of the injector.
+
+ DESCRIPTION
+ The injector is a Singleton, so this static function return the
+ available instance of the injector.
+
+ RETURN VALUE
+ A pointer to the available injector object.
+ */
+ static injector *instance();
+
+ /*
+ Delete the singleton instance (if allocated). Used during server shutdown.
+ */
+ static void free_instance();
+
+ /*
+ A transaction where rows can be added.
+
+ DESCRIPTION
+ The transaction class satisfy the **CopyConstructible** and
+ **Assignable** requirements. Note that the transaction is *not*
+ default constructible.
+ */
+ class transaction {
+ friend class injector;
+ public:
+ /* Convenience definitions */
+ typedef uchar* record_type;
+ typedef uint32 server_id_type;
+
+ /*
+ Table reference.
+
+ RESPONSIBILITY
+
+ The class contains constructors to handle several forms of
+ references to tables. The constructors can implicitly be used to
+ construct references from, e.g., strings containing table names.
+
+ EXAMPLE
+
+ The class is intended to be used *by value*. Please, do not try to
+ construct objects of this type using 'new'; instead construct an
+ object, possibly a temporary object. For example:
+
+ injector::transaction::table tbl(share->table, true);
+ MY_BITMAP cols;
+ my_bitmap_init(&cols, NULL, (i + 7) / 8, false);
+ inj->write_row(::server_id, tbl, &cols, row_data);
+
+ or
+
+ MY_BITMAP cols;
+ my_bitmap_init(&cols, NULL, (i + 7) / 8, false);
+ inj->write_row(::server_id,
+ injector::transaction::table(share->table, true),
+ &cols, row_data);
+
+ This will work, be more efficient, and have greater chance of
+ inlining, not run the risk of losing pointers.
+
+ COLLABORATION
+
+ injector::transaction
+ Provide a flexible interface to the representation of tables.
+
+ */
+ class table
+ {
+ public:
+ table(TABLE *table, bool is_transactional)
+ : m_table(table), m_is_transactional(is_transactional)
+ {
+ }
+
+ char const *db_name() const { return m_table->s->db.str; }
+ char const *table_name() const { return m_table->s->table_name.str; }
+ TABLE *get_table() const { return m_table; }
+ bool is_transactional() const { return m_is_transactional; }
+
+ private:
+ TABLE *m_table;
+ bool m_is_transactional;
+ };
+
+ /*
+ Binlog position as a structure.
+ */
+ class binlog_pos {
+ friend class transaction;
+ public:
+ char const *file_name() const { return m_file_name; }
+ my_off_t file_pos() const { return m_file_pos; }
+
+ private:
+ char const *m_file_name;
+ my_off_t m_file_pos;
+ };
+
+ transaction() : m_thd(NULL) { }
+ transaction(transaction const&);
+ ~transaction();
+
+ /* Clear transaction, i.e., make calls to 'good()' return false. */
+ void clear() { m_thd= NULL; }
+
+ /* Is the transaction in a good state? */
+ bool good() const { return m_thd != NULL; }
+
+ /* Default assignment operator: standard implementation */
+ transaction& operator=(transaction t) {
+ swap(t);
+ return *this;
+ }
+
+ /*
+
+ DESCRIPTION
+
+ Register table for use within the transaction. All tables
+ that are going to be used need to be registered before being
+ used below. The member function will fail with an error if
+ use_table() is called after any *_row() function has been
+ called for the transaction.
+
+ RETURN VALUE
+
+ 0 All OK
+ >0 Failure
+
+ */
+ int use_table(server_id_type sid, table tbl);
+
+ /*
+ Add a 'write row' entry to the transaction.
+ */
+ int write_row (server_id_type sid, table tbl,
+ MY_BITMAP const *cols, size_t colcnt,
+ record_type record);
+
+ /*
+ Add a 'delete row' entry to the transaction.
+ */
+ int delete_row(server_id_type sid, table tbl,
+ MY_BITMAP const *cols, size_t colcnt,
+ record_type record);
+
+ /*
+ Add an 'update row' entry to the transaction.
+ */
+ int update_row(server_id_type sid, table tbl,
+ MY_BITMAP const *cols, size_t colcnt,
+ record_type before, record_type after);
+
+ /*
+ Commit a transaction.
+
+ This member function will clean up after a sequence of *_row calls by,
+ for example, releasing resource and unlocking files.
+ */
+ int commit();
+
+ /*
+ Get the position for the start of the transaction.
+
+ Returns the position in the binary log of the first event in this
+ transaction. If no event is yet written, the position where the event
+ *will* be written is returned. This position is known, since a
+ new_transaction() will lock the binary log and prevent any other
+ writes to the binary log.
+ */
+ binlog_pos start_pos() const;
+
+ private:
+ /* Only the injector may construct these object */
+ transaction(MYSQL_BIN_LOG *, THD *);
+
+ void swap(transaction& o) {
+ /* std::swap(m_start_pos, o.m_start_pos); */
+ {
+ binlog_pos const tmp= m_start_pos;
+ m_start_pos= o.m_start_pos;
+ o.m_start_pos= tmp;
+ }
+
+ /* std::swap(m_thd, o.m_thd); */
+ {
+ THD* const tmp= m_thd;
+ m_thd= o.m_thd;
+ o.m_thd= tmp;
+ }
+ {
+ enum_state const tmp= m_state;
+ m_state= o.m_state;
+ o.m_state= tmp;
+ }
+ }
+
+ enum enum_state
+ {
+ START_STATE, /* Start state */
+ TABLE_STATE, /* At least one table has been registered */
+ ROW_STATE, /* At least one row has been registered */
+ STATE_COUNT /* State count and sink state */
+ } m_state;
+
+ /*
+ Check and update the state.
+
+ PARAMETER(S)
+
+ target_state
+ The state we are moving to: TABLE_STATE if we are
+ writing a table and ROW_STATE if we are writing a row.
+
+ DESCRIPTION
+
+ The internal state will be updated to the target state if
+ and only if it is a legal move. The only legal moves are:
+
+ START_STATE -> START_STATE
+ START_STATE -> TABLE_STATE
+ TABLE_STATE -> TABLE_STATE
+ TABLE_STATE -> ROW_STATE
+
+ That is:
+ - It is not possible to write any row before having written at
+ least one table
+ - It is not possible to write a table after at least one row
+ has been written
+
+ RETURN VALUE
+
+ 0 All OK
+ -1 Incorrect call sequence
+ */
+ int check_state(enum_state const target_state)
+ {
+#ifndef DBUG_OFF
+ static char const *state_name[] = {
+ "START_STATE", "TABLE_STATE", "ROW_STATE", "STATE_COUNT"
+ };
+
+ DBUG_ASSERT(target_state <= STATE_COUNT);
+ DBUG_PRINT("info", ("In state %s", state_name[m_state]));
+#endif
+
+ if (m_state <= target_state && target_state <= m_state + 1 &&
+ m_state < STATE_COUNT)
+ m_state= target_state;
+ else
+ m_state= STATE_COUNT;
+ return m_state == STATE_COUNT ? 1 : 0;
+ }
+
+
+ binlog_pos m_start_pos;
+ THD *m_thd;
+ };
+
+ /*
+ Create a new transaction. This member function will prepare for a
+ sequence of *_row calls by, for example, reserving resources and
+ locking files. There are two overloaded alternatives: one returning a
+ transaction by value and one using placement semantics. The following
+ two calls are equivalent, with the exception that the latter will
+ overwrite the transaction.
+
+ injector::transaction trans1= inj->new_trans(thd);
+
+ injector::transaction trans2;
+ inj->new_trans(thd, &trans);
+ */
+ transaction new_trans(THD *);
+ void new_trans(THD *, transaction *);
+
+ int record_incident(THD*, Incident incident);
+ int record_incident(THD*, Incident incident, LEX_STRING const message);
+
+private:
+ explicit injector();
+ ~injector() { } /* Nothing needs to be done */
+ injector(injector const&); /* You're not allowed to copy injector
+ instances.
+ */
+};
+
+#endif /* INJECTOR_H */
diff --git a/sql/rpl_mi.cc b/sql/rpl_mi.cc
new file mode 100644
index 00000000000..ddc502210ce
--- /dev/null
+++ b/sql/rpl_mi.cc
@@ -0,0 +1,1391 @@
+/* Copyright (c) 2006, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2011, Monty Program Ab
+
+ 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 <my_global.h> // For HAVE_REPLICATION
+#include "sql_priv.h"
+#include <my_dir.h>
+#include "unireg.h" // REQUIRED by other includes
+#include "rpl_mi.h"
+#include "slave.h" // SLAVE_MAX_HEARTBEAT_PERIOD
+#include "strfunc.h"
+#include "sql_repl.h"
+
+#ifdef HAVE_REPLICATION
+
+#define DEFAULT_CONNECT_RETRY 60
+
+static void init_master_log_pos(Master_info* mi);
+
+Master_info::Master_info(LEX_STRING *connection_name_arg,
+ bool is_slave_recovery)
+ :Slave_reporting_capability("I/O"),
+ ssl(0), ssl_verify_server_cert(1), fd(-1), io_thd(0),
+ rli(is_slave_recovery), port(MYSQL_PORT),
+ checksum_alg_before_fd(BINLOG_CHECKSUM_ALG_UNDEF),
+ connect_retry(DEFAULT_CONNECT_RETRY), inited(0), abort_slave(0),
+ slave_running(0), slave_run_id(0), clock_diff_with_master(0),
+ sync_counter(0), heartbeat_period(0), received_heartbeats(0),
+ master_id(0), prev_master_id(0),
+ using_gtid(USE_GTID_NO), events_queued_since_last_gtid(0),
+ gtid_reconnect_event_skip_count(0), gtid_event_seen(false)
+{
+ host[0] = 0; user[0] = 0; password[0] = 0;
+ ssl_ca[0]= 0; ssl_capath[0]= 0; ssl_cert[0]= 0;
+ ssl_cipher[0]= 0; ssl_key[0]= 0;
+ ssl_crl[0]= 0; ssl_crlpath[0]= 0;
+
+ /*
+ Store connection name and lower case connection name
+ It's safe to ignore any OMM errors as this is checked by error()
+ */
+ connection_name.length= cmp_connection_name.length=
+ connection_name_arg->length;
+ if ((connection_name.str= (char*) my_malloc(connection_name_arg->length*2+2,
+ MYF(MY_WME))))
+ {
+ cmp_connection_name.str= (connection_name.str +
+ connection_name_arg->length+1);
+ strmake(connection_name.str, connection_name_arg->str,
+ connection_name.length);
+ memcpy(cmp_connection_name.str, connection_name_arg->str,
+ connection_name.length+1);
+ my_casedn_str(system_charset_info, cmp_connection_name.str);
+ }
+ /* When MySQL restarted, all Rpl_filter settings which aren't in the my.cnf
+ * will lose. So if you want a setting will not lose after restarting, you
+ * should add them into my.cnf
+ * */
+ rpl_filter= get_or_create_rpl_filter(connection_name.str,
+ connection_name.length);
+ copy_filter_setting(rpl_filter, global_rpl_filter);
+
+ my_init_dynamic_array(&ignore_server_ids,
+ sizeof(global_system_variables.server_id), 16, 16,
+ MYF(0));
+ bzero((char*) &file, sizeof(file));
+ mysql_mutex_init(key_master_info_run_lock, &run_lock, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_master_info_data_lock, &data_lock, MY_MUTEX_INIT_FAST);
+ mysql_mutex_setflags(&run_lock, MYF_NO_DEADLOCK_DETECTION);
+ mysql_mutex_setflags(&data_lock, MYF_NO_DEADLOCK_DETECTION);
+ mysql_mutex_init(key_master_info_sleep_lock, &sleep_lock, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_master_info_data_cond, &data_cond, NULL);
+ mysql_cond_init(key_master_info_start_cond, &start_cond, NULL);
+ mysql_cond_init(key_master_info_stop_cond, &stop_cond, NULL);
+ mysql_cond_init(key_master_info_sleep_cond, &sleep_cond, NULL);
+}
+
+Master_info::~Master_info()
+{
+ rpl_filters.delete_element(connection_name.str, connection_name.length,
+ (void (*)(const char*, uchar*)) free_rpl_filter);
+ my_free(connection_name.str);
+ delete_dynamic(&ignore_server_ids);
+ mysql_mutex_destroy(&run_lock);
+ mysql_mutex_destroy(&data_lock);
+ mysql_mutex_destroy(&sleep_lock);
+ mysql_cond_destroy(&data_cond);
+ mysql_cond_destroy(&start_cond);
+ mysql_cond_destroy(&stop_cond);
+ mysql_cond_destroy(&sleep_cond);
+}
+
+/**
+ A comparison function to be supplied as argument to @c sort_dynamic()
+ and @c bsearch()
+
+ @return -1 if first argument is less, 0 if it equal to, 1 if it is greater
+ than the second
+*/
+int change_master_server_id_cmp(ulong *id1, ulong *id2)
+{
+ return *id1 < *id2? -1 : (*id1 > *id2? 1 : 0);
+}
+
+
+/**
+ Reports if the s_id server has been configured to ignore events
+ it generates with
+
+ CHANGE MASTER IGNORE_SERVER_IDS= ( list of server ids )
+
+ Method is called from the io thread event receiver filtering.
+
+ @param s_id the master server identifier
+
+ @retval TRUE if s_id is in the list of ignored master servers,
+ @retval FALSE otherwise.
+ */
+bool Master_info::shall_ignore_server_id(ulong s_id)
+{
+ if (likely(ignore_server_ids.elements == 1))
+ return (* (ulong*) dynamic_array_ptr(&ignore_server_ids, 0)) == s_id;
+ else
+ return bsearch((const ulong *) &s_id,
+ ignore_server_ids.buffer,
+ ignore_server_ids.elements, sizeof(ulong),
+ (int (*) (const void*, const void*)) change_master_server_id_cmp)
+ != NULL;
+}
+
+void Master_info::clear_in_memory_info(bool all)
+{
+ init_master_log_pos(this);
+ if (all)
+ {
+ port= MYSQL_PORT;
+ host[0] = 0; user[0] = 0; password[0] = 0;
+ }
+}
+
+
+const char *
+Master_info::using_gtid_astext(enum enum_using_gtid arg)
+{
+ switch (arg)
+ {
+ case USE_GTID_NO:
+ return "No";
+ case USE_GTID_SLAVE_POS:
+ return "Slave_Pos";
+ default:
+ DBUG_ASSERT(arg == USE_GTID_CURRENT_POS);
+ return "Current_Pos";
+ }
+}
+
+
+void init_master_log_pos(Master_info* mi)
+{
+ DBUG_ENTER("init_master_log_pos");
+
+ mi->master_log_name[0] = 0;
+ mi->master_log_pos = BIN_LOG_HEADER_SIZE; // skip magic number
+ mi->using_gtid= Master_info::USE_GTID_NO;
+ mi->gtid_current_pos.reset();
+ mi->events_queued_since_last_gtid= 0;
+ mi->gtid_reconnect_event_skip_count= 0;
+ mi->gtid_event_seen= false;
+
+ /* Intentionally init ssl_verify_server_cert to 0, no option available */
+ mi->ssl_verify_server_cert= 0;
+ /*
+ always request heartbeat unless master_heartbeat_period is set
+ explicitly zero. Here is the default value for heartbeat period
+ if CHANGE MASTER did not specify it. (no data loss in conversion
+ as hb period has a max)
+ */
+ mi->heartbeat_period= (float) MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD,
+ (slave_net_timeout/2.0));
+ DBUG_ASSERT(mi->heartbeat_period > (float) 0.001
+ || mi->heartbeat_period == 0);
+
+ DBUG_VOID_RETURN;
+}
+
+
+enum {
+ LINES_IN_MASTER_INFO_WITH_SSL= 14,
+
+ /* 5.1.16 added value of master_ssl_verify_server_cert */
+ LINE_FOR_MASTER_SSL_VERIFY_SERVER_CERT= 15,
+
+ /* 5.5 added value of master_heartbeat_period */
+ LINE_FOR_MASTER_HEARTBEAT_PERIOD= 16,
+
+ /* MySQL Cluster 6.3 added master_bind */
+ LINE_FOR_MASTER_BIND = 17,
+
+ /* 6.0 added value of master_ignore_server_id */
+ LINE_FOR_REPLICATE_IGNORE_SERVER_IDS= 18,
+
+ /* 6.0 added value of master_uuid */
+ LINE_FOR_MASTER_UUID= 19,
+
+ /* line for master_retry_count */
+ LINE_FOR_MASTER_RETRY_COUNT= 20,
+
+ /* line for ssl_crl */
+ LINE_FOR_SSL_CRL= 21,
+
+ /* line for ssl_crl */
+ LINE_FOR_SSL_CRLPATH= 22,
+
+ /* MySQL 5.6 fixed-position lines. */
+ LINE_FOR_FIRST_MYSQL_5_6=23,
+ LINE_FOR_LAST_MYSQL_5_6=23,
+ /* Reserved lines for MySQL future versions. */
+ LINE_FOR_LAST_MYSQL_FUTURE=33,
+ /* Number of (fixed-position) lines used when saving master info file */
+ LINES_IN_MASTER_INFO= LINE_FOR_LAST_MYSQL_FUTURE
+};
+
+int init_master_info(Master_info* mi, const char* master_info_fname,
+ const char* slave_info_fname,
+ bool abort_if_no_master_info_file,
+ int thread_mask)
+{
+ int fd,error;
+ char fname[FN_REFLEN+128];
+ DBUG_ENTER("init_master_info");
+
+ if (mi->inited)
+ {
+ /*
+ We have to reset read position of relay-log-bin as we may have
+ already been reading from 'hotlog' when the slave was stopped
+ last time. If this case pos_in_file would be set and we would
+ get a crash when trying to read the signature for the binary
+ relay log.
+
+ We only rewind the read position if we are starting the SQL
+ thread. The handle_slave_sql thread assumes that the read
+ position is at the beginning of the file, and will read the
+ "signature" and then fast-forward to the last position read.
+ */
+ if (thread_mask & SLAVE_SQL)
+ {
+ bool hot_log= FALSE;
+ /*
+ my_b_seek does an implicit flush_io_cache, so we need to:
+
+ 1. check if this log is active (hot)
+ 2. if it is we keep log_lock until the seek ends, otherwise
+ release it right away.
+
+ If we did not take log_lock, SQL thread might race with IO
+ thread for the IO_CACHE mutex.
+
+ */
+ mysql_mutex_t *log_lock= mi->rli.relay_log.get_log_lock();
+ mysql_mutex_lock(log_lock);
+ hot_log= mi->rli.relay_log.is_active(mi->rli.linfo.log_file_name);
+
+ if (!hot_log)
+ mysql_mutex_unlock(log_lock);
+
+ my_b_seek(mi->rli.cur_log, (my_off_t) 0);
+
+ if (hot_log)
+ mysql_mutex_unlock(log_lock);
+ }
+ DBUG_RETURN(0);
+ }
+
+ mi->mysql=0;
+ mi->file_id=1;
+ fn_format(fname, master_info_fname, mysql_data_home, "", 4+32);
+
+ /*
+ We need a mutex while we are changing master info parameters to
+ keep other threads from reading bogus info
+ */
+
+ mysql_mutex_lock(&mi->data_lock);
+ fd = mi->fd;
+
+ /* does master.info exist ? */
+
+ if (access(fname,F_OK))
+ {
+ if (abort_if_no_master_info_file)
+ {
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(0);
+ }
+ /*
+ if someone removed the file from underneath our feet, just close
+ the old descriptor and re-create the old file
+ */
+ if (fd >= 0)
+ mysql_file_close(fd, MYF(MY_WME));
+ if ((fd= mysql_file_open(key_file_master_info,
+ fname, O_CREAT|O_RDWR|O_BINARY, MYF(MY_WME))) < 0 )
+ {
+ sql_print_error("Failed to create a new master info file (\
+file '%s', errno %d)", fname, my_errno);
+ goto err;
+ }
+ if (init_io_cache(&mi->file, fd, IO_SIZE*2, READ_CACHE, 0L,0,
+ MYF(MY_WME)))
+ {
+ sql_print_error("Failed to create a cache on master info file (\
+file '%s')", fname);
+ goto err;
+ }
+
+ mi->fd = fd;
+ mi->clear_in_memory_info(false);
+
+ }
+ else // file exists
+ {
+ if (fd >= 0)
+ reinit_io_cache(&mi->file, READ_CACHE, 0L,0,0);
+ else
+ {
+ if ((fd= mysql_file_open(key_file_master_info,
+ fname, O_RDWR|O_BINARY, MYF(MY_WME))) < 0 )
+ {
+ sql_print_error("Failed to open the existing master info file (\
+file '%s', errno %d)", fname, my_errno);
+ goto err;
+ }
+ if (init_io_cache(&mi->file, fd, IO_SIZE*2, READ_CACHE, 0L,
+ 0, MYF(MY_WME)))
+ {
+ sql_print_error("Failed to create a cache on master info file (\
+file '%s')", fname);
+ goto err;
+ }
+ }
+
+ mi->fd = fd;
+ int port, connect_retry, master_log_pos, lines;
+ int ssl= 0, ssl_verify_server_cert= 0;
+ float master_heartbeat_period= 0.0;
+ char *first_non_digit;
+ char buf[HOSTNAME_LENGTH+1];
+
+ /*
+ Starting from 4.1.x master.info has new format. Now its
+ first line contains number of lines in file. By reading this
+ number we will be always distinguish to which version our
+ master.info corresponds to. We can't simply count lines in
+ file since versions before 4.1.x could generate files with more
+ lines than needed.
+ If first line doesn't contain a number or contain number less than
+ LINES_IN_MASTER_INFO_WITH_SSL then such file is treated like file
+ from pre 4.1.1 version.
+ There is no ambiguity when reading an old master.info, as before
+ 4.1.1, the first line contained the binlog's name, which is either
+ empty or has an extension (contains a '.'), so can't be confused
+ with an integer.
+
+ So we're just reading first line and trying to figure which version
+ is this.
+ */
+
+ /*
+ The first row is temporarily stored in mi->master_log_name,
+ if it is line count and not binlog name (new format) it will be
+ overwritten by the second row later.
+ */
+ if (init_strvar_from_file(mi->master_log_name,
+ sizeof(mi->master_log_name), &mi->file,
+ ""))
+ goto errwithmsg;
+
+ lines= strtoul(mi->master_log_name, &first_non_digit, 10);
+
+ if (mi->master_log_name[0]!='\0' &&
+ *first_non_digit=='\0' && lines >= LINES_IN_MASTER_INFO_WITH_SSL)
+ {
+ /* Seems to be new format => read master log name from next line */
+ if (init_strvar_from_file(mi->master_log_name,
+ sizeof(mi->master_log_name), &mi->file, ""))
+ goto errwithmsg;
+ }
+ else
+ lines= 7;
+
+ if (init_intvar_from_file(&master_log_pos, &mi->file, 4) ||
+ init_strvar_from_file(mi->host, sizeof(mi->host), &mi->file, 0) ||
+ init_strvar_from_file(mi->user, sizeof(mi->user), &mi->file, "test") ||
+ init_strvar_from_file(mi->password, SCRAMBLED_PASSWORD_CHAR_LENGTH+1,
+ &mi->file, 0) ||
+ init_intvar_from_file(&port, &mi->file, MYSQL_PORT) ||
+ init_intvar_from_file(&connect_retry, &mi->file,
+ DEFAULT_CONNECT_RETRY))
+ goto errwithmsg;
+
+ /*
+ If file has ssl part use it even if we have server without
+ SSL support. But these options will be ignored later when
+ slave will try connect to master, so in this case warning
+ is printed.
+ */
+ if (lines >= LINES_IN_MASTER_INFO_WITH_SSL)
+ {
+ if (init_intvar_from_file(&ssl, &mi->file, 0) ||
+ init_strvar_from_file(mi->ssl_ca, sizeof(mi->ssl_ca),
+ &mi->file, 0) ||
+ init_strvar_from_file(mi->ssl_capath, sizeof(mi->ssl_capath),
+ &mi->file, 0) ||
+ init_strvar_from_file(mi->ssl_cert, sizeof(mi->ssl_cert),
+ &mi->file, 0) ||
+ init_strvar_from_file(mi->ssl_cipher, sizeof(mi->ssl_cipher),
+ &mi->file, 0) ||
+ init_strvar_from_file(mi->ssl_key, sizeof(mi->ssl_key),
+ &mi->file, 0))
+ goto errwithmsg;
+
+ /*
+ Starting from 5.1.16 ssl_verify_server_cert might be
+ in the file
+ */
+ if (lines >= LINE_FOR_MASTER_SSL_VERIFY_SERVER_CERT &&
+ init_intvar_from_file(&ssl_verify_server_cert, &mi->file, 0))
+ goto errwithmsg;
+ /*
+ Starting from 6.0 master_heartbeat_period might be
+ in the file
+ */
+ if (lines >= LINE_FOR_MASTER_HEARTBEAT_PERIOD &&
+ init_floatvar_from_file(&master_heartbeat_period, &mi->file, 0.0))
+ goto errwithmsg;
+ /*
+ Starting from MySQL Cluster 6.3 master_bind might be in the file
+ (this is just a reservation to avoid future upgrade problems)
+ */
+ if (lines >= LINE_FOR_MASTER_BIND &&
+ init_strvar_from_file(buf, sizeof(buf), &mi->file, ""))
+ goto errwithmsg;
+ /*
+ Starting from 6.0 list of server_id of ignorable servers might be
+ in the file
+ */
+ if (lines >= LINE_FOR_REPLICATE_IGNORE_SERVER_IDS &&
+ init_dynarray_intvar_from_file(&mi->ignore_server_ids, &mi->file))
+ {
+ sql_print_error("Failed to initialize master info ignore_server_ids");
+ goto errwithmsg;
+ }
+
+ /* reserved */
+ if (lines >= LINE_FOR_MASTER_UUID &&
+ init_strvar_from_file(buf, sizeof(buf), &mi->file, ""))
+ goto errwithmsg;
+
+ /* Starting from 5.5 the master_retry_count may be in the repository. */
+ if (lines >= LINE_FOR_MASTER_RETRY_COUNT &&
+ init_strvar_from_file(buf, sizeof(buf), &mi->file, ""))
+ goto errwithmsg;
+
+ if (lines >= LINE_FOR_SSL_CRLPATH &&
+ (init_strvar_from_file(mi->ssl_crl, sizeof(mi->ssl_crl),
+ &mi->file, "") ||
+ init_strvar_from_file(mi->ssl_crlpath, sizeof(mi->ssl_crlpath),
+ &mi->file, "")))
+ goto errwithmsg;
+
+ /*
+ Starting with MariaDB 10.0, we use a key=value syntax, which is nicer
+ in several ways. But we leave a bunch of empty lines to accomodate
+ any future old-style additions in MySQL (this will make it easier for
+ users moving from MariaDB to MySQL, to not have MySQL try to
+ interpret a MariaDB key=value line.)
+ */
+ if (lines >= LINE_FOR_LAST_MYSQL_FUTURE)
+ {
+ uint i;
+ /* Skip lines used by / reserved for MySQL >= 5.6. */
+ for (i= LINE_FOR_FIRST_MYSQL_5_6; i <= LINE_FOR_LAST_MYSQL_FUTURE; ++i)
+ {
+ if (init_strvar_from_file(buf, sizeof(buf), &mi->file, ""))
+ goto errwithmsg;
+ }
+
+ /*
+ Parse any extra key=value lines.
+ Ignore unknown lines, to facilitate downgrades.
+ */
+ while (!init_strvar_from_file(buf, sizeof(buf), &mi->file, 0))
+ {
+ if (0 == strncmp(buf, STRING_WITH_LEN("using_gtid=")))
+ {
+ int val= atoi(buf + sizeof("using_gtid"));
+ if (val == Master_info::USE_GTID_CURRENT_POS)
+ mi->using_gtid= Master_info::USE_GTID_CURRENT_POS;
+ else if (val == Master_info::USE_GTID_SLAVE_POS)
+ mi->using_gtid= Master_info::USE_GTID_SLAVE_POS;
+ else
+ mi->using_gtid= Master_info::USE_GTID_NO;
+ }
+ }
+ }
+ }
+
+#ifndef HAVE_OPENSSL
+ if (ssl)
+ sql_print_warning("SSL information in the master info file "
+ "('%s') are ignored because this MySQL slave was "
+ "compiled without SSL support.", fname);
+#endif /* HAVE_OPENSSL */
+
+ /*
+ This has to be handled here as init_intvar_from_file can't handle
+ my_off_t types
+ */
+ mi->master_log_pos= (my_off_t) master_log_pos;
+ mi->port= (uint) port;
+ mi->connect_retry= (uint) connect_retry;
+ mi->ssl= (my_bool) ssl;
+ mi->ssl_verify_server_cert= ssl_verify_server_cert;
+ mi->heartbeat_period= master_heartbeat_period;
+ }
+ DBUG_PRINT("master_info",("log_file_name: %s position: %ld",
+ mi->master_log_name,
+ (ulong) mi->master_log_pos));
+
+ mi->rli.mi= mi;
+ if (init_relay_log_info(&mi->rli, slave_info_fname))
+ goto err;
+
+ mi->inited = 1;
+ mi->rli.is_relay_log_recovery= FALSE;
+ // now change cache READ -> WRITE - must do this before flush_master_info
+ reinit_io_cache(&mi->file, WRITE_CACHE, 0L, 0, 1);
+ if ((error= MY_TEST(flush_master_info(mi, TRUE, TRUE))))
+ sql_print_error("Failed to flush master info file");
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(error);
+
+errwithmsg:
+ sql_print_error("Error reading master configuration");
+
+err:
+ if (fd >= 0)
+ {
+ mysql_file_close(fd, MYF(0));
+ end_io_cache(&mi->file);
+ }
+ mi->fd= -1;
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(1);
+}
+
+
+/*
+ RETURN
+ 2 - flush relay log failed
+ 1 - flush master info failed
+ 0 - all ok
+*/
+int flush_master_info(Master_info* mi,
+ bool flush_relay_log_cache,
+ bool need_lock_relay_log)
+{
+ IO_CACHE* file = &mi->file;
+ char lbuf[22];
+ int err= 0;
+
+ DBUG_ENTER("flush_master_info");
+ DBUG_PRINT("enter",("master_pos: %ld", (long) mi->master_log_pos));
+
+ /*
+ Flush the relay log to disk. If we don't do it, then the relay log while
+ have some part (its last kilobytes) in memory only, so if the slave server
+ dies now, with, say, from master's position 100 to 150 in memory only (not
+ on disk), and with position 150 in master.info, then when the slave
+ restarts, the I/O thread will fetch binlogs from 150, so in the relay log
+ we will have "[0, 100] U [150, infinity[" and nobody will notice it, so the
+ SQL thread will jump from 100 to 150, and replication will silently break.
+
+ When we come to this place in code, relay log may or not be initialized;
+ the caller is responsible for setting 'flush_relay_log_cache' accordingly.
+ */
+ if (flush_relay_log_cache)
+ {
+ mysql_mutex_t *log_lock= mi->rli.relay_log.get_log_lock();
+ IO_CACHE *log_file= mi->rli.relay_log.get_log_file();
+
+ if (need_lock_relay_log)
+ mysql_mutex_lock(log_lock);
+
+ mysql_mutex_assert_owner(log_lock);
+ err= flush_io_cache(log_file);
+
+ if (need_lock_relay_log)
+ mysql_mutex_unlock(log_lock);
+
+ if (err)
+ DBUG_RETURN(2);
+ }
+
+ /*
+ produce a line listing the total number and all the ignored server_id:s
+ */
+ char* ignore_server_ids_buf;
+ {
+ ignore_server_ids_buf=
+ (char *) my_malloc((sizeof(global_system_variables.server_id) * 3 + 1) *
+ (1 + mi->ignore_server_ids.elements), MYF(MY_WME));
+ if (!ignore_server_ids_buf)
+ DBUG_RETURN(1);
+ ulong cur_len= sprintf(ignore_server_ids_buf, "%u",
+ mi->ignore_server_ids.elements);
+ for (ulong i= 0; i < mi->ignore_server_ids.elements; i++)
+ {
+ ulong s_id;
+ get_dynamic(&mi->ignore_server_ids, (uchar*) &s_id, i);
+ cur_len+= sprintf(ignore_server_ids_buf + cur_len, " %lu", s_id);
+ }
+ }
+
+ /*
+ We flushed the relay log BEFORE the master.info file, because if we crash
+ now, we will get a duplicate event in the relay log at restart. If we
+ flushed in the other order, we would get a hole in the relay log.
+ And duplicate is better than hole (with a duplicate, in later versions we
+ can add detection and scrap one event; with a hole there's nothing we can
+ do).
+ */
+
+ /*
+ In certain cases this code may create master.info files that seems
+ corrupted, because of extra lines filled with garbage in the end
+ file (this happens if new contents take less space than previous
+ contents of file). But because of number of lines in the first line
+ of file we don't care about this garbage.
+ */
+ char heartbeat_buf[sizeof(mi->heartbeat_period) * 4]; // buffer to suffice always
+ sprintf(heartbeat_buf, "%.3f", mi->heartbeat_period);
+ my_b_seek(file, 0L);
+ my_b_printf(file,
+ "%u\n%s\n%s\n%s\n%s\n%s\n%d\n%d\n%d\n%s\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n"
+ "\n\n\n\n\n\n\n\n\n\n\n"
+ "using_gtid=%d\n",
+ LINES_IN_MASTER_INFO,
+ mi->master_log_name, llstr(mi->master_log_pos, lbuf),
+ mi->host, mi->user,
+ mi->password, mi->port, mi->connect_retry,
+ (int)(mi->ssl), mi->ssl_ca, mi->ssl_capath, mi->ssl_cert,
+ mi->ssl_cipher, mi->ssl_key, mi->ssl_verify_server_cert,
+ heartbeat_buf, "", ignore_server_ids_buf,
+ "", 0,
+ mi->ssl_crl, mi->ssl_crlpath, mi->using_gtid);
+ my_free(ignore_server_ids_buf);
+ err= flush_io_cache(file);
+ if (sync_masterinfo_period && !err &&
+ ++(mi->sync_counter) >= sync_masterinfo_period)
+ {
+ err= my_sync(mi->fd, MYF(MY_WME));
+ mi->sync_counter= 0;
+ }
+ DBUG_RETURN(-err);
+}
+
+
+void end_master_info(Master_info* mi)
+{
+ DBUG_ENTER("end_master_info");
+
+ if (!mi->inited)
+ DBUG_VOID_RETURN;
+ end_relay_log_info(&mi->rli);
+ if (mi->fd >= 0)
+ {
+ end_io_cache(&mi->file);
+ mysql_file_close(mi->fd, MYF(MY_WME));
+ mi->fd = -1;
+ }
+ mi->inited = 0;
+
+ DBUG_VOID_RETURN;
+}
+
+/* Multi-Master By P.Linux */
+uchar *get_key_master_info(Master_info *mi, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ /* Return lower case name */
+ *length= mi->cmp_connection_name.length;
+ return (uchar*) mi->cmp_connection_name.str;
+}
+
+void free_key_master_info(Master_info *mi)
+{
+ DBUG_ENTER("free_key_master_info");
+ terminate_slave_threads(mi,SLAVE_FORCE_ALL);
+ end_master_info(mi);
+ delete mi;
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Check if connection name for master_info is valid.
+
+ It's valid if it's a valid system name of length less than
+ MAX_CONNECTION_NAME.
+
+ @return
+ 0 ok
+ 1 error
+*/
+
+bool check_master_connection_name(LEX_STRING *name)
+{
+ if (name->length >= MAX_CONNECTION_NAME)
+ return 1;
+ return 0;
+}
+
+
+/**
+ Create a log file with a given suffix.
+
+ @param
+ res_file_name Store result here
+ length Length of res_file_name buffer
+ info_file Original file name (prefix)
+ append 1 if we should add suffix last (not before ext)
+ suffix Suffix
+
+ @note
+ The suffix is added before the extension of the file name prefixed with '-'.
+ The suffix is also converted to lower case and we transform
+ all not safe character, as we do with MySQL table names.
+
+ If suffix is an empty string, then we don't add any suffix.
+ This is to allow one to use this function also to generate old
+ file names without a prefix.
+*/
+
+void create_logfile_name_with_suffix(char *res_file_name, size_t length,
+ const char *info_file, bool append,
+ LEX_STRING *suffix)
+{
+ char buff[MAX_CONNECTION_NAME+1],
+ res[MAX_CONNECTION_NAME * MAX_FILENAME_MBWIDTH+1], *p;
+
+ p= strmake(res_file_name, info_file, length);
+ /* If not empty suffix and there is place left for some part of the suffix */
+ if (suffix->length != 0 && p <= res_file_name + length -1)
+ {
+ const char *info_file_end= info_file + (p - res_file_name);
+ const char *ext= append ? info_file_end : fn_ext2(info_file);
+ size_t res_length, ext_pos, from_length;
+ uint errors;
+
+ /* Create null terminated string */
+ from_length= strmake(buff, suffix->str, suffix->length) - buff;
+ /* Convert to characters usable in a file name */
+ res_length= strconvert(system_charset_info, buff, from_length,
+ &my_charset_filename, res, sizeof(res), &errors);
+
+ ext_pos= (size_t) (ext - info_file);
+ length-= (suffix->length - ext_pos); /* Leave place for extension */
+ p= res_file_name + ext_pos;
+ *p++= '-'; /* Add separator */
+ p= strmake(p, res, MY_MIN((size_t) (length - (p - res_file_name)),
+ res_length));
+ /* Add back extension. We have checked above that there is space for it */
+ strmov(p, ext);
+ }
+}
+
+void copy_filter_setting(Rpl_filter* dst_filter, Rpl_filter* src_filter)
+{
+ char buf[256];
+ String tmp(buf, sizeof(buf), &my_charset_bin);
+
+ dst_filter->get_do_db(&tmp);
+ if (tmp.is_empty())
+ {
+ src_filter->get_do_db(&tmp);
+ if (!tmp.is_empty())
+ dst_filter->set_do_db(tmp.ptr());
+ }
+
+ dst_filter->get_do_table(&tmp);
+ if (tmp.is_empty())
+ {
+ src_filter->get_do_table(&tmp);
+ if (!tmp.is_empty())
+ dst_filter->set_do_table(tmp.ptr());
+ }
+
+ dst_filter->get_ignore_db(&tmp);
+ if (tmp.is_empty())
+ {
+ src_filter->get_ignore_db(&tmp);
+ if (!tmp.is_empty())
+ dst_filter->set_ignore_db(tmp.ptr());
+ }
+
+ dst_filter->get_ignore_table(&tmp);
+ if (tmp.is_empty())
+ {
+ src_filter->get_ignore_table(&tmp);
+ if (!tmp.is_empty())
+ dst_filter->set_ignore_table(tmp.ptr());
+ }
+
+ dst_filter->get_wild_do_table(&tmp);
+ if (tmp.is_empty())
+ {
+ src_filter->get_wild_do_table(&tmp);
+ if (!tmp.is_empty())
+ dst_filter->set_wild_do_table(tmp.ptr());
+ }
+
+ dst_filter->get_wild_ignore_table(&tmp);
+ if (tmp.is_empty())
+ {
+ src_filter->get_wild_ignore_table(&tmp);
+ if (!tmp.is_empty())
+ dst_filter->set_wild_ignore_table(tmp.ptr());
+ }
+
+ if (dst_filter->rewrite_db_is_empty())
+ {
+ if (!src_filter->rewrite_db_is_empty())
+ dst_filter->copy_rewrite_db(src_filter);
+ }
+}
+
+Master_info_index::Master_info_index()
+{
+ size_t filename_length, dir_length;
+ /*
+ Create the Master_info index file by prepending 'multi-' before
+ the master_info_file file name.
+ */
+ fn_format(index_file_name, master_info_file, mysql_data_home,
+ "", MY_UNPACK_FILENAME);
+ filename_length= strlen(index_file_name) + 1; /* Count 0 byte */
+ dir_length= dirname_length(index_file_name);
+ bmove_upp((uchar*) index_file_name + filename_length + 6,
+ (uchar*) index_file_name + filename_length,
+ filename_length - dir_length);
+ memcpy(index_file_name + dir_length, "multi-", 6);
+
+ bzero((char*) &index_file, sizeof(index_file));
+ index_file.file= -1;
+}
+
+Master_info_index::~Master_info_index()
+{
+ /* This will close connection for all objects in the cache */
+ my_hash_free(&master_info_hash);
+ end_io_cache(&index_file);
+ if (index_file.file >= 0)
+ my_close(index_file.file, MYF(MY_WME));
+}
+
+
+/* Load All Master_info from master.info.index File
+ * RETURN:
+ * 0 - All Success
+ * 1 - All Fail
+ * 2 - Some Success, Some Fail
+ */
+
+bool Master_info_index::init_all_master_info()
+{
+ int thread_mask;
+ int err_num= 0, succ_num= 0; // The number of success read Master_info
+ char sign[MAX_CONNECTION_NAME+1];
+ File index_file_nr;
+ DBUG_ENTER("init_all_master_info");
+
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+ DBUG_ASSERT(master_info_index);
+
+ if ((index_file_nr= my_open(index_file_name,
+ O_RDWR | O_CREAT | O_BINARY ,
+ MYF(MY_WME | ME_NOREFRESH))) < 0 ||
+ my_sync(index_file_nr, MYF(MY_WME)) ||
+ init_io_cache(&index_file, index_file_nr,
+ IO_SIZE, READ_CACHE,
+ my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)),
+ 0, MYF(MY_WME | MY_WAIT_IF_FULL)))
+ {
+ if (index_file_nr >= 0)
+ my_close(index_file_nr,MYF(0));
+
+ sql_print_error("Creation of Master_info index file '%s' failed",
+ index_file_name);
+ DBUG_RETURN(1);
+ }
+
+ /* Initialize Master_info Hash Table */
+ if (my_hash_init(&master_info_hash, system_charset_info,
+ MAX_REPLICATION_THREAD, 0, 0,
+ (my_hash_get_key) get_key_master_info,
+ (my_hash_free_key)free_key_master_info, HASH_UNIQUE))
+ {
+ sql_print_error("Initializing Master_info hash table failed");
+ DBUG_RETURN(1);
+ }
+
+ reinit_io_cache(&index_file, READ_CACHE, 0L,0,0);
+ while (!init_strvar_from_file(sign, sizeof(sign),
+ &index_file, NULL))
+ {
+ LEX_STRING connection_name;
+ Master_info *mi;
+ char buf_master_info_file[FN_REFLEN];
+ char buf_relay_log_info_file[FN_REFLEN];
+
+ connection_name.str= sign;
+ connection_name.length= strlen(sign);
+ if (!(mi= new Master_info(&connection_name, relay_log_recovery)) ||
+ mi->error())
+ {
+ delete mi;
+ DBUG_RETURN(1);
+ }
+
+ lock_slave_threads(mi);
+ init_thread_mask(&thread_mask,mi,0 /*not inverse*/);
+
+ create_logfile_name_with_suffix(buf_master_info_file,
+ sizeof(buf_master_info_file),
+ master_info_file, 0,
+ &mi->cmp_connection_name);
+ create_logfile_name_with_suffix(buf_relay_log_info_file,
+ sizeof(buf_relay_log_info_file),
+ relay_log_info_file, 0,
+ &mi->cmp_connection_name);
+ if (global_system_variables.log_warnings > 1)
+ sql_print_information("Reading Master_info: '%s' Relay_info:'%s'",
+ buf_master_info_file, buf_relay_log_info_file);
+
+ if (init_master_info(mi, buf_master_info_file, buf_relay_log_info_file,
+ 0, thread_mask))
+ {
+ err_num++;
+ sql_print_error("Initialized Master_info from '%s' failed",
+ buf_master_info_file);
+ if (!master_info_index->get_master_info(&connection_name,
+ Sql_condition::WARN_LEVEL_NOTE))
+ {
+ /* Master_info is not in HASH; Add it */
+ if (master_info_index->add_master_info(mi, FALSE))
+ return 1;
+ succ_num++;
+ unlock_slave_threads(mi);
+ }
+ else
+ {
+ /* Master_info already in HASH */
+ sql_print_error(ER(ER_CONNECTION_ALREADY_EXISTS),
+ (int) connection_name.length, connection_name.str);
+ unlock_slave_threads(mi);
+ delete mi;
+ }
+ continue;
+ }
+ else
+ {
+ /* Initialization of Master_info succeded. Add it to HASH */
+ if (global_system_variables.log_warnings > 1)
+ sql_print_information("Initialized Master_info from '%s'",
+ buf_master_info_file);
+ if (master_info_index->get_master_info(&connection_name,
+ Sql_condition::WARN_LEVEL_NOTE))
+ {
+ /* Master_info was already registered */
+ sql_print_error(ER(ER_CONNECTION_ALREADY_EXISTS),
+ (int) connection_name.length, connection_name.str);
+ unlock_slave_threads(mi);
+ delete mi;
+ continue;
+ }
+
+ /* Master_info was not registered; add it */
+ if (master_info_index->add_master_info(mi, FALSE))
+ return 1;
+ succ_num++;
+ unlock_slave_threads(mi);
+
+ if (!opt_skip_slave_start)
+ {
+ if (start_slave_threads(1 /* need mutex */,
+ 0 /* no wait for start*/,
+ mi,
+ buf_master_info_file,
+ buf_relay_log_info_file,
+ SLAVE_IO | SLAVE_SQL))
+ {
+ sql_print_error("Failed to create slave threads for connection '%.*s'",
+ (int) connection_name.length,
+ connection_name.str);
+ continue;
+ }
+ if (global_system_variables.log_warnings)
+ sql_print_information("Started replication for '%.*s'",
+ (int) connection_name.length,
+ connection_name.str);
+ }
+ }
+ }
+
+ if (!err_num) // No Error on read Master_info
+ {
+ if (global_system_variables.log_warnings > 1)
+ sql_print_information("Reading of all Master_info entries succeded");
+ DBUG_RETURN(0);
+ }
+ else if (succ_num) // Have some Error and some Success
+ {
+ sql_print_warning("Reading of some Master_info entries failed");
+ DBUG_RETURN(2);
+ }
+ else // All failed
+ {
+ sql_print_error("Reading of all Master_info entries failed!");
+ DBUG_RETURN(1);
+ }
+}
+
+
+/* Write new master.info to master.info.index File */
+bool Master_info_index::write_master_name_to_index_file(LEX_STRING *name,
+ bool do_sync)
+{
+ DBUG_ASSERT(my_b_inited(&index_file) != 0);
+ DBUG_ENTER("write_master_name_to_index_file");
+
+ /* Don't write default slave to master_info.index */
+ if (name->length == 0)
+ DBUG_RETURN(0);
+
+ reinit_io_cache(&index_file, WRITE_CACHE,
+ my_b_filelength(&index_file), 0, 0);
+
+ if (my_b_write(&index_file, (uchar*) name->str, name->length) ||
+ my_b_write(&index_file, (uchar*) "\n", 1) ||
+ flush_io_cache(&index_file) ||
+ (do_sync && my_sync(index_file.file, MYF(MY_WME))))
+ {
+ sql_print_error("Write of new Master_info for '%.*s' to index file failed",
+ (int) name->length, name->str);
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Get Master_info for a connection
+
+ @param
+ connection_name Connection name
+ warning WARN_LEVEL_NOTE -> Don't print anything
+ WARN_LEVEL_WARN -> Issue warning if not exists
+ WARN_LEVEL_ERROR-> Issue error if not exists
+*/
+
+Master_info *
+Master_info_index::get_master_info(LEX_STRING *connection_name,
+ Sql_condition::enum_warning_level warning)
+{
+ Master_info *mi;
+ char buff[MAX_CONNECTION_NAME+1], *res;
+ uint buff_length;
+ DBUG_ENTER("get_master_info");
+ DBUG_PRINT("enter",
+ ("connection_name: '%.*s'", (int) connection_name->length,
+ connection_name->str));
+
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+ if (!this) // master_info_index is set to NULL on server shutdown
+ return NULL;
+
+ /* Make name lower case for comparison */
+ res= strmake(buff, connection_name->str, connection_name->length);
+ my_casedn_str(system_charset_info, buff);
+ buff_length= (size_t) (res-buff);
+
+ mi= (Master_info*) my_hash_search(&master_info_hash,
+ (uchar*) buff, buff_length);
+ if (!mi && warning != Sql_condition::WARN_LEVEL_NOTE)
+ {
+ my_error(WARN_NO_MASTER_INFO,
+ MYF(warning == Sql_condition::WARN_LEVEL_WARN ? ME_JUST_WARNING :
+ 0),
+ (int) connection_name->length,
+ connection_name->str);
+ }
+ DBUG_RETURN(mi);
+}
+
+
+/* Check Master_host & Master_port is duplicated or not */
+bool Master_info_index::check_duplicate_master_info(LEX_STRING *name_arg,
+ const char *host,
+ uint port)
+{
+ Master_info *mi;
+ DBUG_ENTER("check_duplicate_master_info");
+
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+ DBUG_ASSERT(master_info_index);
+
+ /* Get full host and port name */
+ if ((mi= master_info_index->get_master_info(name_arg,
+ Sql_condition::WARN_LEVEL_NOTE)))
+ {
+ if (!host)
+ host= mi->host;
+ if (!port)
+ port= mi->port;
+ }
+ if (!host || !port)
+ DBUG_RETURN(FALSE); // Not comparable yet
+
+ for (uint i= 0; i < master_info_hash.records; ++i)
+ {
+ Master_info *tmp_mi;
+ tmp_mi= (Master_info *) my_hash_element(&master_info_hash, i);
+ if (tmp_mi == mi)
+ continue; // Current connection
+ if (!strcasecmp(host, tmp_mi->host) && port == tmp_mi->port)
+ {
+ my_error(ER_CONNECTION_ALREADY_EXISTS, MYF(0),
+ (int) name_arg->length,
+ name_arg->str,
+ (int) tmp_mi->connection_name.length,
+ tmp_mi->connection_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/* Add a Master_info class to Hash Table */
+bool Master_info_index::add_master_info(Master_info *mi, bool write_to_file)
+{
+ if (!my_hash_insert(&master_info_hash, (uchar*) mi))
+ {
+ if (global_system_variables.log_warnings > 1)
+ sql_print_information("Added new Master_info '%.*s' to hash table",
+ (int) mi->connection_name.length,
+ mi->connection_name.str);
+ if (write_to_file)
+ return write_master_name_to_index_file(&mi->connection_name, 1);
+ return FALSE;
+ }
+
+ /* Impossible error (EOM) ? */
+ sql_print_error("Adding new entry '%.*s' to master_info failed",
+ (int) mi->connection_name.length,
+ mi->connection_name.str);
+ return TRUE;
+}
+
+
+/**
+ Remove a Master_info class From Hash Table
+
+ TODO: Change this to use my_rename() to make the file name creation
+ atomic
+*/
+
+bool Master_info_index::remove_master_info(LEX_STRING *name)
+{
+ Master_info* mi;
+ DBUG_ENTER("remove_master_info");
+
+ if ((mi= get_master_info(name, Sql_condition::WARN_LEVEL_WARN)))
+ {
+ // Delete Master_info and rewrite others to file
+ if (!my_hash_delete(&master_info_hash, (uchar*) mi))
+ {
+ File index_file_nr;
+
+ // Close IO_CACHE and FILE handler fisrt
+ end_io_cache(&index_file);
+ my_close(index_file.file, MYF(MY_WME));
+
+ // Reopen File and truncate it
+ if ((index_file_nr= my_open(index_file_name,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY ,
+ MYF(MY_WME))) < 0 ||
+ init_io_cache(&index_file, index_file_nr,
+ IO_SIZE, WRITE_CACHE,
+ my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)),
+ 0, MYF(MY_WME | MY_WAIT_IF_FULL)))
+ {
+ int error= my_errno;
+ if (index_file_nr >= 0)
+ my_close(index_file_nr,MYF(0));
+
+ sql_print_error("Create of Master Info Index file '%s' failed with "
+ "error: %M",
+ index_file_name, error);
+ DBUG_RETURN(TRUE);
+ }
+
+ // Rewrite Master_info.index
+ for (uint i= 0; i< master_info_hash.records; ++i)
+ {
+ Master_info *tmp_mi;
+ tmp_mi= (Master_info *) my_hash_element(&master_info_hash, i);
+ write_master_name_to_index_file(&tmp_mi->connection_name, 0);
+ }
+ my_sync(index_file_nr, MYF(MY_WME));
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Master_info_index::give_error_if_slave_running()
+
+ @return
+ TRUE If some slave is running. An error is printed
+ FALSE No slave is running
+*/
+
+bool Master_info_index::give_error_if_slave_running()
+{
+ DBUG_ENTER("give_error_if_slave_running");
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+ if (!this) // master_info_index is set to NULL on server shutdown
+ return TRUE;
+
+ for (uint i= 0; i< master_info_hash.records; ++i)
+ {
+ Master_info *mi;
+ mi= (Master_info *) my_hash_element(&master_info_hash, i);
+ if (mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN)
+ {
+ my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length,
+ mi->connection_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Master_info_index::any_slave_sql_running()
+
+ The LOCK_active_mi must be held while calling this function.
+
+ @return
+ TRUE If some slave SQL thread is running.
+ FALSE No slave SQL thread is running
+*/
+
+bool Master_info_index::any_slave_sql_running()
+{
+ DBUG_ENTER("any_slave_sql_running");
+ if (!this) // master_info_index is set to NULL on server shutdown
+ return TRUE;
+
+ for (uint i= 0; i< master_info_hash.records; ++i)
+ {
+ Master_info *mi= (Master_info *)my_hash_element(&master_info_hash, i);
+ if (mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN)
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Master_info_index::start_all_slaves()
+
+ Start all slaves that was not running.
+
+ @return
+ TRUE Error
+ FALSE Everything ok.
+*/
+
+bool Master_info_index::start_all_slaves(THD *thd)
+{
+ bool result= FALSE;
+ DBUG_ENTER("warn_if_slave_running");
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+
+ for (uint i= 0; i< master_info_hash.records; ++i)
+ {
+ int error;
+ Master_info *mi;
+ mi= (Master_info *) my_hash_element(&master_info_hash, i);
+
+ /*
+ Try to start all slaves that are configured (host is defined)
+ and are not already running
+ */
+ if ((mi->slave_running != MYSQL_SLAVE_RUN_CONNECT ||
+ !mi->rli.slave_running) && *mi->host)
+ {
+ if ((error= start_slave(thd, mi, 1)))
+ {
+ my_error(ER_CANT_START_STOP_SLAVE, MYF(0),
+ "START",
+ (int) mi->connection_name.length,
+ mi->connection_name.str);
+ result= 1;
+ if (error < 0) // fatal error
+ break;
+ }
+ else
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SLAVE_STARTED, ER(ER_SLAVE_STARTED),
+ (int) mi->connection_name.length,
+ mi->connection_name.str);
+ }
+ }
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Master_info_index::stop_all_slaves()
+
+ Start all slaves that was not running.
+
+ @return
+ TRUE Error
+ FALSE Everything ok.
+*/
+
+bool Master_info_index::stop_all_slaves(THD *thd)
+{
+ bool result= FALSE;
+ DBUG_ENTER("warn_if_slave_running");
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+
+ for (uint i= 0; i< master_info_hash.records; ++i)
+ {
+ int error;
+ Master_info *mi;
+ mi= (Master_info *) my_hash_element(&master_info_hash, i);
+ if ((mi->slave_running != MYSQL_SLAVE_NOT_RUN ||
+ mi->rli.slave_running))
+ {
+ if ((error= stop_slave(thd, mi, 1)))
+ {
+ my_error(ER_CANT_START_STOP_SLAVE, MYF(0),
+ "STOP",
+ (int) mi->connection_name.length,
+ mi->connection_name.str);
+ result= 1;
+ if (error < 0) // Fatal error
+ break;
+ }
+ else
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SLAVE_STOPPED, ER(ER_SLAVE_STOPPED),
+ (int) mi->connection_name.length,
+ mi->connection_name.str);
+ }
+ }
+ DBUG_RETURN(result);
+}
+
+#endif /* HAVE_REPLICATION */
diff --git a/sql/rpl_mi.h b/sql/rpl_mi.h
new file mode 100644
index 00000000000..2b0b40feb3d
--- /dev/null
+++ b/sql/rpl_mi.h
@@ -0,0 +1,248 @@
+/* Copyright (c) 2006, 2012, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifndef RPL_MI_H
+#define RPL_MI_H
+
+#ifdef HAVE_REPLICATION
+
+#include "rpl_rli.h"
+#include "rpl_reporting.h"
+#include "my_sys.h"
+#include "rpl_filter.h"
+#include "keycaches.h"
+
+typedef struct st_mysql MYSQL;
+
+/*****************************************************************************
+ Replication IO Thread
+
+ Master_info contains:
+ - information about how to connect to a master
+ - current master log name
+ - current master log offset
+ - misc control variables
+
+ Master_info is initialized once from the master.info file if such
+ exists. Otherwise, data members corresponding to master.info fields
+ are initialized with defaults specified by master-* options. The
+ initialization is done through init_master_info() call.
+
+ The format of master.info file:
+
+ log_name
+ log_pos
+ master_host
+ master_user
+ master_pass
+ master_port
+ master_connect_retry
+
+ To write out the contents of master.info file to disk ( needed every
+ time we read and queue data from the master ), a call to
+ flush_master_info() is required.
+
+ To clean up, call end_master_info()
+
+*****************************************************************************/
+
+class Master_info : public Slave_reporting_capability
+{
+ public:
+ enum enum_using_gtid {
+ USE_GTID_NO= 0, USE_GTID_CURRENT_POS= 1, USE_GTID_SLAVE_POS= 2
+ };
+
+ Master_info(LEX_STRING *connection_name, bool is_slave_recovery);
+ ~Master_info();
+ bool shall_ignore_server_id(ulong s_id);
+ void clear_in_memory_info(bool all);
+ bool error()
+ {
+ /* If malloc() in initialization failed */
+ return connection_name.str == 0;
+ }
+ static const char *using_gtid_astext(enum enum_using_gtid arg);
+
+ /* the variables below are needed because we can change masters on the fly */
+ char master_log_name[FN_REFLEN+6]; /* Room for multi-*/
+ char host[HOSTNAME_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1];
+ char user[USERNAME_LENGTH+1];
+ char password[MAX_PASSWORD_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1];
+ LEX_STRING connection_name; /* User supplied connection name */
+ LEX_STRING cmp_connection_name; /* Connection name in lower case */
+ bool ssl; // enables use of SSL connection if true
+ char ssl_ca[FN_REFLEN], ssl_capath[FN_REFLEN], ssl_cert[FN_REFLEN];
+ char ssl_cipher[FN_REFLEN], ssl_key[FN_REFLEN];
+ char ssl_crl[FN_REFLEN], ssl_crlpath[FN_REFLEN];
+ bool ssl_verify_server_cert;
+
+ my_off_t master_log_pos;
+ File fd; // we keep the file open, so we need to remember the file pointer
+ IO_CACHE file;
+
+ mysql_mutex_t data_lock, run_lock, sleep_lock;
+ mysql_cond_t data_cond, start_cond, stop_cond, sleep_cond;
+ THD *io_thd;
+ MYSQL* mysql;
+ uint32 file_id; /* for 3.23 load data infile */
+ Relay_log_info rli;
+ uint port;
+ Rpl_filter* rpl_filter; /* Each replication can set its filter rule*/
+ /*
+ to hold checksum alg in use until IO thread has received FD.
+ Initialized to novalue, then set to the queried from master
+ @@global.binlog_checksum and deactivated once FD has been received.
+ */
+ uint8 checksum_alg_before_fd;
+ uint connect_retry;
+#ifndef DBUG_OFF
+ int events_till_disconnect;
+#endif
+ bool inited;
+ volatile bool abort_slave;
+ volatile uint slave_running;
+ volatile ulong slave_run_id;
+ /*
+ The difference in seconds between the clock of the master and the clock of
+ the slave (second - first). It must be signed as it may be <0 or >0.
+ clock_diff_with_master is computed when the I/O thread starts; for this the
+ I/O thread does a SELECT UNIX_TIMESTAMP() on the master.
+ "how late the slave is compared to the master" is computed like this:
+ clock_of_slave - last_timestamp_executed_by_SQL_thread - clock_diff_with_master
+
+ */
+ long clock_diff_with_master;
+ /*
+ Keeps track of the number of events before fsyncing.
+ The option --sync-master-info determines how many
+ events should happen before fsyncing.
+ */
+ uint sync_counter;
+ float heartbeat_period; // interface with CHANGE MASTER or master.info
+ ulonglong received_heartbeats; // counter of received heartbeat events
+ DYNAMIC_ARRAY ignore_server_ids;
+ ulong master_id;
+ /*
+ At reconnect and until the first rotate event is seen, prev_master_id is
+ the value of master_id during the previous connection, used to detect
+ silent change of master server during reconnects.
+ */
+ ulong prev_master_id;
+ /*
+ Which kind of GTID position (if any) is used when connecting to master.
+
+ Note that you can not change the numeric values of these, they are used
+ in master.info.
+ */
+ enum enum_using_gtid using_gtid;
+
+ /*
+ This GTID position records how far we have fetched into the relay logs.
+ This is used to continue fetching when the IO thread reconnects to the
+ master.
+
+ (Full slave stop/start does not use it, as it resets the relay logs).
+ */
+ slave_connection_state gtid_current_pos;
+ /*
+ If events_queued_since_last_gtid is non-zero, it is the number of events
+ queued so far in the relaylog of a GTID-prefixed event group.
+ It is zero when no partial event group has been queued at the moment.
+ */
+ uint64 events_queued_since_last_gtid;
+ /*
+ The GTID of the partially-queued event group, when
+ events_queued_since_last_gtid is non-zero.
+ */
+ rpl_gtid last_queued_gtid;
+ /* Whether last_queued_gtid had the FL_STANDALONE flag set. */
+ bool last_queued_gtid_standalone;
+ /*
+ When slave IO thread needs to reconnect, gtid_reconnect_event_skip_count
+ counts number of events to skip from the first GTID-prefixed event group,
+ to avoid duplicating events in the relay log.
+ */
+ uint64 gtid_reconnect_event_skip_count;
+ /* gtid_event_seen is false until we receive first GTID event from master. */
+ bool gtid_event_seen;
+};
+int init_master_info(Master_info* mi, const char* master_info_fname,
+ const char* slave_info_fname,
+ bool abort_if_no_master_info_file,
+ int thread_mask);
+void end_master_info(Master_info* mi);
+int flush_master_info(Master_info* mi,
+ bool flush_relay_log_cache,
+ bool need_lock_relay_log);
+int change_master_server_id_cmp(ulong *id1, ulong *id2);
+void copy_filter_setting(Rpl_filter* dst_filter, Rpl_filter* src_filter);
+
+/*
+ Multi master are handled trough this struct.
+ Changes to this needs to be protected by LOCK_active_mi;
+*/
+
+class Master_info_index
+{
+private:
+ IO_CACHE index_file;
+ char index_file_name[FN_REFLEN];
+
+public:
+ Master_info_index();
+ ~Master_info_index();
+
+ HASH master_info_hash;
+
+ bool init_all_master_info();
+ bool write_master_name_to_index_file(LEX_STRING *connection_name,
+ bool do_sync);
+
+ bool check_duplicate_master_info(LEX_STRING *connection_name,
+ const char *host, uint port);
+ bool add_master_info(Master_info *mi, bool write_to_file);
+ bool remove_master_info(LEX_STRING *connection_name);
+ Master_info *get_master_info(LEX_STRING *connection_name,
+ Sql_condition::enum_warning_level warning);
+ bool give_error_if_slave_running();
+ bool any_slave_sql_running();
+ bool start_all_slaves(THD *thd);
+ bool stop_all_slaves(THD *thd);
+};
+
+
+/*
+ The class rpl_io_thread_info is the THD::system_thread_info for the IO thread.
+*/
+class rpl_io_thread_info
+{
+public:
+};
+
+
+bool check_master_connection_name(LEX_STRING *name);
+void create_logfile_name_with_suffix(char *res_file_name, size_t length,
+ const char *info_file,
+ bool append,
+ LEX_STRING *suffix);
+
+uchar *get_key_master_info(Master_info *mi, size_t *length,
+ my_bool not_used __attribute__((unused)));
+void free_key_master_info(Master_info *mi);
+
+
+#endif /* HAVE_REPLICATION */
+#endif /* RPL_MI_H */
diff --git a/sql/rpl_parallel.cc b/sql/rpl_parallel.cc
new file mode 100644
index 00000000000..99ddde95689
--- /dev/null
+++ b/sql/rpl_parallel.cc
@@ -0,0 +1,2294 @@
+#include "my_global.h"
+#include "rpl_parallel.h"
+#include "slave.h"
+#include "rpl_mi.h"
+#include "sql_parse.h"
+#include "debug_sync.h"
+
+/*
+ Code for optional parallel execution of replicated events on the slave.
+*/
+
+
+/*
+ Maximum number of queued events to accumulate in a local free list, before
+ moving them to the global free list. There is additional a limit of how much
+ to accumulate based on opt_slave_parallel_max_queued.
+*/
+#define QEV_BATCH_FREE 200
+
+
+struct rpl_parallel_thread_pool global_rpl_thread_pool;
+
+static void signal_error_to_sql_driver_thread(THD *thd, rpl_group_info *rgi,
+ int err);
+
+static int
+rpt_handle_event(rpl_parallel_thread::queued_event *qev,
+ struct rpl_parallel_thread *rpt)
+{
+ int err;
+ rpl_group_info *rgi= qev->rgi;
+ Relay_log_info *rli= rgi->rli;
+ THD *thd= rgi->thd;
+ Log_event *ev;
+
+ DBUG_ASSERT(qev->typ == rpl_parallel_thread::queued_event::QUEUED_EVENT);
+ ev= qev->ev;
+
+ thd->system_thread_info.rpl_sql_info->rpl_filter = rli->mi->rpl_filter;
+ ev->thd= thd;
+
+ strcpy(rgi->event_relay_log_name_buf, qev->event_relay_log_name);
+ rgi->event_relay_log_name= rgi->event_relay_log_name_buf;
+ rgi->event_relay_log_pos= qev->event_relay_log_pos;
+ rgi->future_event_relay_log_pos= qev->future_event_relay_log_pos;
+ strcpy(rgi->future_event_master_log_name, qev->future_event_master_log_name);
+ mysql_mutex_lock(&rli->data_lock);
+ /* Mutex will be released in apply_event_and_update_pos(). */
+ err= apply_event_and_update_pos(ev, thd, rgi, rpt);
+
+ thread_safe_increment64(&rli->executed_entries,
+ &slave_executed_entries_lock);
+ /* ToDo: error handling. */
+ return err;
+}
+
+
+static void
+handle_queued_pos_update(THD *thd, rpl_parallel_thread::queued_event *qev)
+{
+ int cmp;
+ Relay_log_info *rli;
+ rpl_parallel_entry *e;
+
+ /*
+ Events that are not part of an event group, such as Format Description,
+ Stop, GTID List and such, are executed directly in the driver SQL thread,
+ to keep the relay log state up-to-date. But the associated position update
+ is done here, in sync with other normal events as they are queued to
+ worker threads.
+ */
+ if ((thd->variables.option_bits & OPTION_BEGIN) &&
+ opt_using_transactions)
+ return;
+
+ /* Do not update position if an earlier event group caused an error abort. */
+ DBUG_ASSERT(qev->typ == rpl_parallel_thread::queued_event::QUEUED_POS_UPDATE);
+ e= qev->entry_for_queued;
+ if (e->stop_on_error_sub_id < (uint64)ULONGLONG_MAX || e->force_abort)
+ return;
+
+ rli= qev->rgi->rli;
+ mysql_mutex_lock(&rli->data_lock);
+ cmp= strcmp(rli->group_relay_log_name, qev->event_relay_log_name);
+ if (cmp < 0)
+ {
+ rli->group_relay_log_pos= qev->future_event_relay_log_pos;
+ strmake_buf(rli->group_relay_log_name, qev->event_relay_log_name);
+ rli->notify_group_relay_log_name_update();
+ } else if (cmp == 0 &&
+ rli->group_relay_log_pos < qev->future_event_relay_log_pos)
+ rli->group_relay_log_pos= qev->future_event_relay_log_pos;
+
+ cmp= strcmp(rli->group_master_log_name, qev->future_event_master_log_name);
+ if (cmp < 0)
+ {
+ strcpy(rli->group_master_log_name, qev->future_event_master_log_name);
+ rli->notify_group_master_log_name_update();
+ rli->group_master_log_pos= qev->future_event_master_log_pos;
+ }
+ else if (cmp == 0
+ && rli->group_master_log_pos < qev->future_event_master_log_pos)
+ rli->group_master_log_pos= qev->future_event_master_log_pos;
+ mysql_mutex_unlock(&rli->data_lock);
+ mysql_cond_broadcast(&rli->data_cond);
+}
+
+
+static void
+finish_event_group(rpl_parallel_thread *rpt, uint64 sub_id,
+ rpl_parallel_entry *entry, rpl_group_info *rgi)
+{
+ THD *thd= rpt->thd;
+ wait_for_commit *wfc= &rgi->commit_orderer;
+ int err;
+
+ thd->get_stmt_da()->set_overwrite_status(true);
+ /*
+ Remove any left-over registration to wait for a prior commit to
+ complete. Normally, such wait would already have been removed at
+ this point by wait_for_prior_commit() called from within COMMIT
+ processing. However, in case of MyISAM and no binlog, we might not
+ have any commit processing, and so we need to do the wait here,
+ before waking up any subsequent commits, to preserve correct
+ order of event execution. Also, in the error case we might have
+ skipped waiting and thus need to remove it explicitly.
+
+ It is important in the non-error case to do a wait, not just an
+ unregister. Because we might be last in a group-commit that is
+ replicated in parallel, and the following event will then wait
+ for us to complete and rely on this also ensuring that any other
+ event in the group has completed.
+
+ And in the error case, correct GCO lifetime relies on the fact that once
+ the last event group in the GCO has executed wait_for_prior_commit(),
+ all earlier event groups have also committed; this way no more
+ mark_start_commit() calls can be made and it is safe to de-allocate
+ the GCO.
+ */
+ err= wfc->wait_for_prior_commit(thd);
+ if (unlikely(err) && !rgi->worker_error)
+ signal_error_to_sql_driver_thread(thd, rgi, err);
+ thd->wait_for_commit_ptr= NULL;
+
+ mysql_mutex_lock(&entry->LOCK_parallel_entry);
+ /*
+ We need to mark that this event group started its commit phase, in case we
+ missed it before (otherwise we would deadlock the next event group that is
+ waiting for this). In most cases (normal DML), it will be a no-op.
+ */
+ rgi->mark_start_commit_no_lock();
+
+ if (entry->last_committed_sub_id < sub_id)
+ {
+ /*
+ Record that this event group has finished (eg. transaction is
+ committed, if transactional), so other event groups will no longer
+ attempt to wait for us to commit. Once we have increased
+ entry->last_committed_sub_id, no other threads will execute
+ register_wait_for_prior_commit() against us. Thus, by doing one
+ extra (usually redundant) wakeup_subsequent_commits() we can ensure
+ that no register_wait_for_prior_commit() can ever happen without a
+ subsequent wakeup_subsequent_commits() to wake it up.
+
+ We can race here with the next transactions, but that is fine, as
+ long as we check that we do not decrease last_committed_sub_id. If
+ this commit is done, then any prior commits will also have been
+ done and also no longer need waiting for.
+ */
+ entry->last_committed_sub_id= sub_id;
+
+ /* Now free any GCOs in which all transactions have committed. */
+ group_commit_orderer *tmp_gco= rgi->gco;
+ while (tmp_gco &&
+ (!tmp_gco->next_gco || tmp_gco->last_sub_id > sub_id ||
+ tmp_gco->next_gco->wait_count > entry->count_committing_event_groups))
+ {
+ /*
+ We must not free a GCO before the wait_count of the following GCO has
+ been reached and wakeup has been sent. Otherwise we will lose the
+ wakeup and hang (there were several such bugs in the past).
+
+ The intention is that this is ensured already since we only free when
+ the last event group in the GCO has committed
+ (tmp_gco->last_sub_id <= sub_id). However, if we have a bug, we have
+ extra check on next_gco->wait_count to hopefully avoid hanging; we
+ have here an assertion in debug builds that this check does not in
+ fact trigger.
+ */
+ DBUG_ASSERT(!tmp_gco->next_gco || tmp_gco->last_sub_id > sub_id);
+ tmp_gco= tmp_gco->prev_gco;
+ }
+ while (tmp_gco)
+ {
+ group_commit_orderer *prev_gco= tmp_gco->prev_gco;
+ tmp_gco->next_gco->prev_gco= NULL;
+ rpt->loc_free_gco(tmp_gco);
+ tmp_gco= prev_gco;
+ }
+ }
+
+ /*
+ If this event group got error, then any following event groups that have
+ not yet started should just skip their group, preparing for stop of the
+ SQL driver thread.
+ */
+ if (unlikely(rgi->worker_error) &&
+ entry->stop_on_error_sub_id == (uint64)ULONGLONG_MAX)
+ entry->stop_on_error_sub_id= sub_id;
+ mysql_mutex_unlock(&entry->LOCK_parallel_entry);
+
+ thd->clear_error();
+ thd->reset_killed();
+ /*
+ Would do thd->get_stmt_da()->set_overwrite_status(false) here, but
+ reset_diagnostics_area() already does that.
+ */
+ thd->get_stmt_da()->reset_diagnostics_area();
+ wfc->wakeup_subsequent_commits(rgi->worker_error);
+}
+
+
+static void
+signal_error_to_sql_driver_thread(THD *thd, rpl_group_info *rgi, int err)
+{
+ rgi->worker_error= err;
+ rgi->cleanup_context(thd, true);
+ rgi->rli->abort_slave= true;
+ rgi->rli->stop_for_until= false;
+ mysql_mutex_lock(rgi->rli->relay_log.get_log_lock());
+ mysql_mutex_unlock(rgi->rli->relay_log.get_log_lock());
+ rgi->rli->relay_log.signal_update();
+}
+
+
+static void
+unlock_or_exit_cond(THD *thd, mysql_mutex_t *lock, bool *did_enter_cond,
+ PSI_stage_info *old_stage)
+{
+ if (*did_enter_cond)
+ {
+ thd->EXIT_COND(old_stage);
+ *did_enter_cond= false;
+ }
+ else
+ mysql_mutex_unlock(lock);
+}
+
+
+static void
+register_wait_for_prior_event_group_commit(rpl_group_info *rgi,
+ rpl_parallel_entry *entry)
+{
+ mysql_mutex_assert_owner(&entry->LOCK_parallel_entry);
+ if (rgi->wait_commit_sub_id > entry->last_committed_sub_id)
+ {
+ /*
+ Register that the commit of this event group must wait for the
+ commit of the previous event group to complete before it may
+ complete itself, so that we preserve commit order.
+ */
+ wait_for_commit *waitee=
+ &rgi->wait_commit_group_info->commit_orderer;
+ rgi->commit_orderer.register_wait_for_prior_commit(waitee);
+ }
+}
+
+
+#ifndef DBUG_OFF
+static int
+dbug_simulate_tmp_error(rpl_group_info *rgi, THD *thd)
+{
+ if (rgi->current_gtid.domain_id == 0 && rgi->current_gtid.seq_no == 100 &&
+ rgi->retry_event_count == 4)
+ {
+ thd->clear_error();
+ thd->get_stmt_da()->reset_diagnostics_area();
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+
+/*
+ If we detect a deadlock due to eg. storage engine locks that conflict with
+ the fixed commit order, then the later transaction will be killed
+ asynchroneously to allow the former to complete its commit.
+
+ In this case, we convert the 'killed' error into a deadlock error, and retry
+ the later transaction. */
+static void
+convert_kill_to_deadlock_error(rpl_group_info *rgi)
+{
+ THD *thd= rgi->thd;
+ int err_code;
+
+ if (!thd->get_stmt_da()->is_error())
+ return;
+ err_code= thd->get_stmt_da()->sql_errno();
+ if ((err_code == ER_QUERY_INTERRUPTED || err_code == ER_CONNECTION_KILLED) &&
+ rgi->killed_for_retry)
+ {
+ thd->clear_error();
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ rgi->killed_for_retry= false;
+ thd->reset_killed();
+ }
+}
+
+
+static bool
+is_group_ending(Log_event *ev, Log_event_type event_type)
+{
+ return event_type == XID_EVENT ||
+ (event_type == QUERY_EVENT &&
+ (((Query_log_event *)ev)->is_commit() ||
+ ((Query_log_event *)ev)->is_rollback()));
+}
+
+
+static int
+retry_event_group(rpl_group_info *rgi, rpl_parallel_thread *rpt,
+ rpl_parallel_thread::queued_event *orig_qev)
+{
+ IO_CACHE rlog;
+ LOG_INFO linfo;
+ File fd= (File)-1;
+ const char *errmsg;
+ inuse_relaylog *ir= rgi->relay_log;
+ uint64 event_count;
+ uint64 events_to_execute= rgi->retry_event_count;
+ Relay_log_info *rli= rgi->rli;
+ int err;
+ ulonglong cur_offset, old_offset;
+ char log_name[FN_REFLEN];
+ THD *thd= rgi->thd;
+ rpl_parallel_entry *entry= rgi->parallel_entry;
+ ulong retries= 0;
+ Format_description_log_event *description_event= NULL;
+
+do_retry:
+ event_count= 0;
+ err= 0;
+ errmsg= NULL;
+
+ /*
+ If we already started committing before getting the deadlock (or other
+ error) that caused us to need to retry, we have already signalled
+ subsequent transactions that we have started committing. This is
+ potentially a problem, as now we will rollback, and if subsequent
+ transactions would start to execute now, they could see an unexpected
+ state of the database and get eg. key not found or duplicate key error.
+
+ However, to get a deadlock in the first place, there must have been
+ another earlier transaction that is waiting for us. Thus that other
+ transaction has _not_ yet started to commit, and any subsequent
+ transactions will still be waiting at this point.
+
+ So here, we decrement back the count of transactions that started
+ committing (if we already incremented it), undoing the effect of an
+ earlier mark_start_commit(). Then later, when the retry succeeds and we
+ commit again, we can do a new mark_start_commit() and eventually wake up
+ subsequent transactions at the proper time.
+
+ We need to do the unmark before the rollback, to be sure that the
+ transaction we deadlocked with will not signal that it started to commit
+ until after the unmark.
+ */
+ rgi->unmark_start_commit();
+ DEBUG_SYNC(thd, "rpl_parallel_retry_after_unmark");
+
+ /*
+ We might get the deadlock error that causes the retry during commit, while
+ sitting in wait_for_prior_commit(). If this happens, we will have a
+ pending error in the wait_for_commit object. So clear this by
+ unregistering (and later re-registering) the wait.
+ */
+ if(thd->wait_for_commit_ptr)
+ thd->wait_for_commit_ptr->unregister_wait_for_prior_commit();
+ DBUG_EXECUTE_IF("inject_mdev8031", {
+ /* Simulate that we get deadlock killed at this exact point. */
+ rgi->killed_for_retry= true;
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+ thd->killed= KILL_CONNECTION;
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+ });
+ rgi->cleanup_context(thd, 1);
+ thd->reset_killed();
+ thd->clear_error();
+
+ /*
+ If we retry due to a deadlock kill that occured during the commit step, we
+ might have already updated (but not committed) an update of table
+ mysql.gtid_slave_pos, and cleared the gtid_pending flag. Now we have
+ rolled back any such update, so we must set the gtid_pending flag back to
+ true so that we will do a new update when/if we succeed with the retry.
+ */
+ rgi->gtid_pending= true;
+
+ mysql_mutex_lock(&rli->data_lock);
+ ++rli->retried_trans;
+ statistic_increment(slave_retried_transactions, LOCK_status);
+ mysql_mutex_unlock(&rli->data_lock);
+
+ for (;;)
+ {
+ mysql_mutex_lock(&entry->LOCK_parallel_entry);
+ register_wait_for_prior_event_group_commit(rgi, entry);
+ mysql_mutex_unlock(&entry->LOCK_parallel_entry);
+
+ /*
+ Let us wait for all prior transactions to complete before trying again.
+ This way, we avoid repeatedly conflicting with and getting deadlock
+ killed by the same earlier transaction.
+ */
+ if (!(err= thd->wait_for_prior_commit()))
+ break;
+
+ convert_kill_to_deadlock_error(rgi);
+ if (!has_temporary_error(thd))
+ goto err;
+ /*
+ If we get a temporary error such as a deadlock kill, we can safely
+ ignore it, as we already rolled back.
+
+ But we still want to retry the wait for the prior transaction to
+ complete its commit.
+ */
+ thd->clear_error();
+ thd->reset_killed();
+ if(thd->wait_for_commit_ptr)
+ thd->wait_for_commit_ptr->unregister_wait_for_prior_commit();
+ DBUG_EXECUTE_IF("inject_mdev8031", {
+ /* Inject a small sleep to give prior transaction a chance to commit. */
+ my_sleep(100000);
+ });
+ }
+
+ /*
+ Let us clear any lingering deadlock kill one more time, here after
+ wait_for_prior_commit() has completed. This should rule out any
+ possibility of an old deadlock kill lingering on beyond this point.
+ */
+ thd->reset_killed();
+
+ strmake_buf(log_name, ir->name);
+ if ((fd= open_binlog(&rlog, log_name, &errmsg)) <0)
+ {
+ err= 1;
+ goto err;
+ }
+ cur_offset= rgi->retry_start_offset;
+ delete description_event;
+ description_event=
+ read_relay_log_description_event(&rlog, cur_offset, &errmsg);
+ if (!description_event)
+ {
+ err= 1;
+ goto err;
+ }
+ DBUG_EXECUTE_IF("inject_mdev8031", {
+ /* Simulate pending KILL caught in read_relay_log_description_event(). */
+ if (thd->check_killed()) {
+ thd->send_kill_message();
+ err= 1;
+ goto err;
+ }
+ });
+ my_b_seek(&rlog, cur_offset);
+
+ do
+ {
+ Log_event_type event_type;
+ Log_event *ev;
+ rpl_parallel_thread::queued_event *qev;
+
+ /* The loop is here so we can try again the next relay log file on EOF. */
+ for (;;)
+ {
+ old_offset= cur_offset;
+ ev= Log_event::read_log_event(&rlog, 0, description_event,
+ opt_slave_sql_verify_checksum);
+ cur_offset= my_b_tell(&rlog);
+
+ if (ev)
+ break;
+ if (rlog.error < 0)
+ {
+ errmsg= "slave SQL thread aborted because of I/O error";
+ err= 1;
+ goto check_retry;
+ }
+ if (rlog.error > 0)
+ {
+ sql_print_error("Slave SQL thread: I/O error reading "
+ "event(errno: %d cur_log->error: %d)",
+ my_errno, rlog.error);
+ errmsg= "Aborting slave SQL thread because of partial event read";
+ err= 1;
+ goto err;
+ }
+ /* EOF. Move to the next relay log. */
+ end_io_cache(&rlog);
+ mysql_file_close(fd, MYF(MY_WME));
+ fd= (File)-1;
+
+ /* Find the next relay log file. */
+ if((err= rli->relay_log.find_log_pos(&linfo, log_name, 1)) ||
+ (err= rli->relay_log.find_next_log(&linfo, 1)))
+ {
+ char buff[22];
+ sql_print_error("next log error: %d offset: %s log: %s",
+ err,
+ llstr(linfo.index_file_offset, buff),
+ log_name);
+ goto err;
+ }
+ strmake_buf(log_name ,linfo.log_file_name);
+
+ DBUG_EXECUTE_IF("inject_retry_event_group_open_binlog_kill", {
+ if (retries < 2)
+ {
+ /* Simulate that we get deadlock killed during open_binlog(). */
+ mysql_reset_thd_for_next_command(thd);
+ rgi->killed_for_retry= true;
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+ thd->killed= KILL_CONNECTION;
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+ thd->send_kill_message();
+ fd= (File)-1;
+ err= 1;
+ goto check_retry;
+ }
+ });
+ if ((fd= open_binlog(&rlog, log_name, &errmsg)) <0)
+ {
+ err= 1;
+ goto check_retry;
+ }
+ /* Loop to try again on the new log file. */
+ }
+
+ event_type= ev->get_type_code();
+ if (event_type == FORMAT_DESCRIPTION_EVENT)
+ {
+ delete description_event;
+ description_event= (Format_description_log_event *)ev;
+ continue;
+ } else if (!Log_event::is_group_event(event_type))
+ {
+ delete ev;
+ continue;
+ }
+ ev->thd= thd;
+
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ qev= rpt->retry_get_qev(ev, orig_qev, log_name, old_offset,
+ cur_offset - old_offset);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+ if (!qev)
+ {
+ delete ev;
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ err= 1;
+ goto err;
+ }
+ if (is_group_ending(ev, event_type))
+ rgi->mark_start_commit();
+
+ err= rpt_handle_event(qev, rpt);
+ ++event_count;
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ rpt->free_qev(qev);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+
+ delete_or_keep_event_post_apply(rgi, event_type, ev);
+ DBUG_EXECUTE_IF("rpl_parallel_simulate_double_temp_err_gtid_0_x_100",
+ if (retries == 0) err= dbug_simulate_tmp_error(rgi, thd););
+ DBUG_EXECUTE_IF("rpl_parallel_simulate_infinite_temp_err_gtid_0_x_100",
+ err= dbug_simulate_tmp_error(rgi, thd););
+ if (!err)
+ continue;
+
+check_retry:
+ convert_kill_to_deadlock_error(rgi);
+ if (has_temporary_error(thd))
+ {
+ ++retries;
+ if (retries < slave_trans_retries)
+ {
+ if (fd >= 0)
+ {
+ end_io_cache(&rlog);
+ mysql_file_close(fd, MYF(MY_WME));
+ fd= (File)-1;
+ }
+ goto do_retry;
+ }
+ sql_print_error("Slave worker thread retried transaction %lu time(s) "
+ "in vain, giving up. Consider raising the value of "
+ "the slave_transaction_retries variable.",
+ slave_trans_retries);
+ }
+ goto err;
+
+ } while (event_count < events_to_execute);
+
+err:
+
+ if (description_event)
+ delete description_event;
+ if (fd >= 0)
+ {
+ end_io_cache(&rlog);
+ mysql_file_close(fd, MYF(MY_WME));
+ }
+ if (errmsg)
+ sql_print_error("Error reading relay log event: %s", errmsg);
+ return err;
+}
+
+
+pthread_handler_t
+handle_rpl_parallel_thread(void *arg)
+{
+ THD *thd;
+ PSI_stage_info old_stage;
+ struct rpl_parallel_thread::queued_event *events;
+ bool group_standalone= true;
+ bool in_event_group= false;
+ bool skip_event_group= false;
+ rpl_group_info *group_rgi= NULL;
+ group_commit_orderer *gco;
+ uint64 event_gtid_sub_id= 0;
+ rpl_sql_thread_info sql_info(NULL);
+ int err;
+
+ struct rpl_parallel_thread *rpt= (struct rpl_parallel_thread *)arg;
+
+ my_thread_init();
+ thd = new THD;
+ thd->thread_stack = (char*)&thd;
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ set_current_thd(thd);
+ pthread_detach_this_thread();
+ thd->init_for_queries();
+ thd->variables.binlog_annotate_row_events= 0;
+ init_thr_lock();
+ thd->store_globals();
+ thd->system_thread= SYSTEM_THREAD_SLAVE_SQL;
+ thd->security_ctx->skip_grants();
+ thd->variables.max_allowed_packet= slave_max_allowed_packet;
+ thd->slave_thread= 1;
+ thd->enable_slow_log= opt_log_slow_slave_statements;
+ thd->variables.log_slow_filter= global_system_variables.log_slow_filter;
+ set_slave_thread_options(thd);
+ thd->client_capabilities = CLIENT_LOCAL_FILES;
+ thd->net.reading_or_writing= 0;
+ thd_proc_info(thd, "Waiting for work from main SQL threads");
+ thd->set_time();
+ thd->variables.lock_wait_timeout= LONG_TIMEOUT;
+ thd->system_thread_info.rpl_sql_info= &sql_info;
+
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ rpt->thd= thd;
+
+ while (rpt->delay_start)
+ mysql_cond_wait(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread);
+
+ rpt->running= true;
+ mysql_cond_signal(&rpt->COND_rpl_thread);
+
+ while (!rpt->stop)
+ {
+ rpl_parallel_thread::queued_event *qev, *next_qev;
+
+ thd->ENTER_COND(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread,
+ &stage_waiting_for_work_from_sql_thread, &old_stage);
+ /*
+ There are 4 cases that should cause us to wake up:
+ - Events have been queued for us to handle.
+ - We have an owner, but no events and not inside event group -> we need
+ to release ourself to the thread pool
+ - SQL thread is stopping, and we have an owner but no events, and we are
+ inside an event group; no more events will be queued to us, so we need
+ to abort the group (force_abort==1).
+ - Thread pool shutdown (rpt->stop==1).
+ */
+ while (!( (events= rpt->event_queue) ||
+ (rpt->current_owner && !in_event_group) ||
+ (rpt->current_owner && group_rgi->parallel_entry->force_abort) ||
+ rpt->stop))
+ mysql_cond_wait(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread);
+ rpt->dequeue1(events);
+ thd->EXIT_COND(&old_stage);
+
+ more_events:
+ for (qev= events; qev; qev= next_qev)
+ {
+ Log_event_type event_type;
+ rpl_group_info *rgi= qev->rgi;
+ rpl_parallel_entry *entry= rgi->parallel_entry;
+ bool end_of_group, group_ending;
+
+ next_qev= qev->next;
+ if (qev->typ == rpl_parallel_thread::queued_event::QUEUED_POS_UPDATE)
+ {
+ handle_queued_pos_update(thd, qev);
+ rpt->loc_free_qev(qev);
+ continue;
+ }
+ else if (qev->typ ==
+ rpl_parallel_thread::queued_event::QUEUED_MASTER_RESTART)
+ {
+ if (in_event_group)
+ {
+ /*
+ Master restarted (crashed) in the middle of an event group.
+ So we need to roll back and discard that event group.
+ */
+ group_rgi->cleanup_context(thd, 1);
+ in_event_group= false;
+ finish_event_group(rpt, group_rgi->gtid_sub_id,
+ qev->entry_for_queued, group_rgi);
+
+ rpt->loc_free_rgi(group_rgi);
+ thd->rgi_slave= group_rgi= NULL;
+ }
+
+ rpt->loc_free_qev(qev);
+ continue;
+ }
+ DBUG_ASSERT(qev->typ==rpl_parallel_thread::queued_event::QUEUED_EVENT);
+
+ thd->rgi_slave= rgi;
+ gco= rgi->gco;
+ /* Handle a new event group, which will be initiated by a GTID event. */
+ if ((event_type= qev->ev->get_type_code()) == GTID_EVENT)
+ {
+ bool did_enter_cond= false;
+ PSI_stage_info old_stage;
+ uint64 wait_count;
+
+ DBUG_EXECUTE_IF("rpl_parallel_scheduled_gtid_0_x_100", {
+ if (rgi->current_gtid.domain_id == 0 &&
+ rgi->current_gtid.seq_no == 100) {
+ debug_sync_set_action(thd,
+ STRING_WITH_LEN("now SIGNAL scheduled_gtid_0_x_100"));
+ }
+ });
+
+ if(unlikely(thd->wait_for_commit_ptr) && group_rgi != NULL)
+ {
+ /*
+ This indicates that we get a new GTID event in the middle of
+ a not completed event group. This is corrupt binlog (the master
+ will never write such binlog), so it does not happen unless
+ someone tries to inject wrong crafted binlog, but let us still
+ try to handle it somewhat nicely.
+ */
+ group_rgi->cleanup_context(thd, true);
+ finish_event_group(rpt, group_rgi->gtid_sub_id,
+ group_rgi->parallel_entry, group_rgi);
+ rpt->loc_free_rgi(group_rgi);
+ }
+
+ in_event_group= true;
+ /*
+ If the standalone flag is set, then this event group consists of a
+ single statement (possibly preceeded by some Intvar_log_event and
+ similar), without any terminating COMMIT/ROLLBACK/XID.
+ */
+ group_standalone=
+ (0 != (static_cast<Gtid_log_event *>(qev->ev)->flags2 &
+ Gtid_log_event::FL_STANDALONE));
+
+ event_gtid_sub_id= rgi->gtid_sub_id;
+ rgi->thd= thd;
+
+ /*
+ Register ourself to wait for the previous commit, if we need to do
+ such registration _and_ that previous commit has not already
+ occured.
+
+ Also do not start parallel execution of this event group until all
+ prior groups have reached the commit phase that are not safe to run
+ in parallel with.
+ */
+ mysql_mutex_lock(&entry->LOCK_parallel_entry);
+ if (!gco->installed)
+ {
+ group_commit_orderer *prev_gco= gco->prev_gco;
+ if (prev_gco)
+ {
+ prev_gco->last_sub_id= gco->prior_sub_id;
+ prev_gco->next_gco= gco;
+ }
+ gco->installed= true;
+ }
+ wait_count= gco->wait_count;
+ if (wait_count > entry->count_committing_event_groups)
+ {
+ DEBUG_SYNC(thd, "rpl_parallel_start_waiting_for_prior");
+ thd->ENTER_COND(&gco->COND_group_commit_orderer,
+ &entry->LOCK_parallel_entry,
+ &stage_waiting_for_prior_transaction_to_start_commit,
+ &old_stage);
+ did_enter_cond= true;
+ do
+ {
+ if (thd->check_killed() && !rgi->worker_error)
+ {
+ DEBUG_SYNC(thd, "rpl_parallel_start_waiting_for_prior_killed");
+ thd->clear_error();
+ thd->get_stmt_da()->reset_diagnostics_area();
+ thd->send_kill_message();
+ slave_output_error_info(rgi, thd);
+ signal_error_to_sql_driver_thread(thd, rgi, 1);
+ /*
+ Even though we were killed, we need to continue waiting for the
+ prior event groups to signal that we can continue. Otherwise we
+ mess up the accounting for ordering. However, now that we have
+ marked the error, events will just be skipped rather than
+ executed, and things will progress quickly towards stop.
+ */
+ }
+ mysql_cond_wait(&gco->COND_group_commit_orderer,
+ &entry->LOCK_parallel_entry);
+ } while (wait_count > entry->count_committing_event_groups);
+ }
+
+ if (entry->force_abort && wait_count > entry->stop_count)
+ {
+ /*
+ We are stopping (STOP SLAVE), and this event group is beyond the
+ point where we can safely stop. So set a flag that will cause us
+ to skip, rather than execute, the following events.
+ */
+ skip_event_group= true;
+ }
+ else
+ skip_event_group= false;
+
+ if (unlikely(entry->stop_on_error_sub_id <= rgi->wait_commit_sub_id))
+ skip_event_group= true;
+ register_wait_for_prior_event_group_commit(rgi, entry);
+
+ unlock_or_exit_cond(thd, &entry->LOCK_parallel_entry,
+ &did_enter_cond, &old_stage);
+
+ thd->wait_for_commit_ptr= &rgi->commit_orderer;
+
+ if (opt_gtid_ignore_duplicates)
+ {
+ int res=
+ rpl_global_gtid_slave_state.check_duplicate_gtid(&rgi->current_gtid,
+ rgi);
+ if (res < 0)
+ {
+ /* Error. */
+ slave_output_error_info(rgi, thd);
+ signal_error_to_sql_driver_thread(thd, rgi, 1);
+ }
+ else if (!res)
+ {
+ /* GTID already applied by another master connection, skip. */
+ skip_event_group= true;
+ }
+ else
+ {
+ /* We have to apply the event. */
+ }
+ }
+ }
+
+ group_rgi= rgi;
+ group_ending= is_group_ending(qev->ev, event_type);
+ if (group_ending && likely(!rgi->worker_error))
+ {
+ DEBUG_SYNC(thd, "rpl_parallel_before_mark_start_commit");
+ rgi->mark_start_commit();
+ DEBUG_SYNC(thd, "rpl_parallel_after_mark_start_commit");
+ }
+
+ /*
+ If the SQL thread is stopping, we just skip execution of all the
+ following event groups. We still do all the normal waiting and wakeup
+ processing between the event groups as a simple way to ensure that
+ everything is stopped and cleaned up correctly.
+ */
+ if (likely(!rgi->worker_error) && !skip_event_group)
+ {
+ ++rgi->retry_event_count;
+#ifndef DBUG_OFF
+ err= 0;
+ DBUG_EXECUTE_IF("rpl_parallel_simulate_temp_err_xid",
+ if (event_type == XID_EVENT)
+ {
+ thd->clear_error();
+ thd->get_stmt_da()->reset_diagnostics_area();
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ err= 1;
+ DEBUG_SYNC(thd, "rpl_parallel_simulate_temp_err_xid");
+ });
+ if (!err)
+#endif
+ err= rpt_handle_event(qev, rpt);
+ delete_or_keep_event_post_apply(rgi, event_type, qev->ev);
+ DBUG_EXECUTE_IF("rpl_parallel_simulate_temp_err_gtid_0_x_100",
+ err= dbug_simulate_tmp_error(rgi, thd););
+ if (err)
+ {
+ convert_kill_to_deadlock_error(rgi);
+ if (has_temporary_error(thd) && slave_trans_retries > 0)
+ err= retry_event_group(rgi, rpt, qev);
+ }
+ }
+ else
+ {
+ delete qev->ev;
+ thd->get_stmt_da()->set_overwrite_status(true);
+ err= thd->wait_for_prior_commit();
+ thd->get_stmt_da()->set_overwrite_status(false);
+ }
+
+ end_of_group=
+ in_event_group &&
+ ((group_standalone && !Log_event::is_part_of_group(event_type)) ||
+ group_ending);
+
+ rpt->loc_free_qev(qev);
+
+ if (unlikely(err))
+ {
+ if (!rgi->worker_error)
+ {
+ slave_output_error_info(rgi, thd);
+ signal_error_to_sql_driver_thread(thd, rgi, err);
+ }
+ thd->reset_killed();
+ }
+ if (end_of_group)
+ {
+ in_event_group= false;
+ finish_event_group(rpt, event_gtid_sub_id, entry, rgi);
+ rpt->loc_free_rgi(rgi);
+ thd->rgi_slave= group_rgi= rgi= NULL;
+ skip_event_group= false;
+ DEBUG_SYNC(thd, "rpl_parallel_end_of_group");
+ }
+ }
+
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ /*
+ Now that we have the lock, we can move everything from our local free
+ lists to the real free lists that are also accessible from the SQL
+ driver thread.
+ */
+ rpt->batch_free();
+
+ if ((events= rpt->event_queue) != NULL)
+ {
+ /*
+ Take next group of events from the replication pool.
+ This is faster than having to wakeup the pool manager thread to give us
+ a new event.
+ */
+ rpt->dequeue1(events);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+ goto more_events;
+ }
+ rpt->inuse_relaylog_refcount_update();
+
+ if (in_event_group && group_rgi->parallel_entry->force_abort)
+ {
+ /*
+ We are asked to abort, without getting the remaining events in the
+ current event group.
+
+ We have to rollback the current transaction and update the last
+ sub_id value so that SQL thread will know we are done with the
+ half-processed event group.
+ */
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+ signal_error_to_sql_driver_thread(thd, group_rgi, 1);
+ finish_event_group(rpt, group_rgi->gtid_sub_id,
+ group_rgi->parallel_entry, group_rgi);
+ in_event_group= false;
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ rpt->free_rgi(group_rgi);
+ thd->rgi_slave= group_rgi= NULL;
+ skip_event_group= false;
+ }
+ if (!in_event_group)
+ {
+ rpt->current_owner= NULL;
+ /* Tell wait_for_done() that we are done, if it is waiting. */
+ if (likely(rpt->current_entry) &&
+ unlikely(rpt->current_entry->force_abort))
+ mysql_cond_broadcast(&rpt->current_entry->COND_parallel_entry);
+ rpt->current_entry= NULL;
+ if (!rpt->stop)
+ rpt->pool->release_thread(rpt);
+ }
+ }
+
+ rpt->thd= NULL;
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+
+ thd->clear_error();
+ thd->catalog= 0;
+ thd->reset_query();
+ thd->reset_db(NULL, 0);
+ thd_proc_info(thd, "Slave worker thread exiting");
+ thd->temporary_tables= 0;
+ mysql_mutex_lock(&LOCK_thread_count);
+ THD_CHECK_SENTRY(thd);
+ delete thd;
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ rpt->running= false;
+ mysql_cond_signal(&rpt->COND_rpl_thread);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+
+ my_thread_end();
+
+ return NULL;
+}
+
+
+static void
+dealloc_gco(group_commit_orderer *gco)
+{
+ mysql_cond_destroy(&gco->COND_group_commit_orderer);
+ my_free(gco);
+}
+
+
+static int
+rpl_parallel_change_thread_count(rpl_parallel_thread_pool *pool,
+ uint32 new_count)
+{
+ uint32 i;
+ rpl_parallel_thread **new_list= NULL;
+ rpl_parallel_thread *new_free_list= NULL;
+ rpl_parallel_thread *rpt_array= NULL;
+
+ /*
+ Allocate the new list of threads up-front.
+ That way, if we fail half-way, we only need to free whatever we managed
+ to allocate, and will not be left with a half-functional thread pool.
+ */
+ if (new_count &&
+ !my_multi_malloc(MYF(MY_WME|MY_ZEROFILL),
+ &new_list, new_count*sizeof(*new_list),
+ &rpt_array, new_count*sizeof(*rpt_array),
+ NULL))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), (int(new_count*sizeof(*new_list) +
+ new_count*sizeof(*rpt_array))));
+ goto err;;
+ }
+
+ for (i= 0; i < new_count; ++i)
+ {
+ pthread_t th;
+
+ new_list[i]= &rpt_array[i];
+ new_list[i]->delay_start= true;
+ mysql_mutex_init(key_LOCK_rpl_thread, &new_list[i]->LOCK_rpl_thread,
+ MY_MUTEX_INIT_SLOW);
+ mysql_cond_init(key_COND_rpl_thread, &new_list[i]->COND_rpl_thread, NULL);
+ mysql_cond_init(key_COND_rpl_thread_queue,
+ &new_list[i]->COND_rpl_thread_queue, NULL);
+ new_list[i]->pool= pool;
+ if (mysql_thread_create(key_rpl_parallel_thread, &th, &connection_attrib,
+ handle_rpl_parallel_thread, new_list[i]))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto err;
+ }
+ new_list[i]->next= new_free_list;
+ new_free_list= new_list[i];
+ }
+
+ /*
+ Grab each old thread in turn, and signal it to stop.
+
+ Note that since we require all replication threads to be stopped before
+ changing the parallel replication worker thread pool, all the threads will
+ be already idle and will terminate immediately.
+ */
+ for (i= 0; i < pool->count; ++i)
+ {
+ rpl_parallel_thread *rpt= pool->get_thread(NULL, NULL);
+ rpt->stop= true;
+ mysql_cond_signal(&rpt->COND_rpl_thread);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+ }
+
+ for (i= 0; i < pool->count; ++i)
+ {
+ rpl_parallel_thread *rpt= pool->threads[i];
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ while (rpt->running)
+ mysql_cond_wait(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+ mysql_mutex_destroy(&rpt->LOCK_rpl_thread);
+ mysql_cond_destroy(&rpt->COND_rpl_thread);
+ while (rpt->qev_free_list)
+ {
+ rpl_parallel_thread::queued_event *next= rpt->qev_free_list->next;
+ my_free(rpt->qev_free_list);
+ rpt->qev_free_list= next;
+ }
+ while (rpt->rgi_free_list)
+ {
+ rpl_group_info *next= rpt->rgi_free_list->next;
+ delete rpt->rgi_free_list;
+ rpt->rgi_free_list= next;
+ }
+ while (rpt->gco_free_list)
+ {
+ group_commit_orderer *next= rpt->gco_free_list->next_gco;
+ dealloc_gco(rpt->gco_free_list);
+ rpt->gco_free_list= next;
+ }
+ }
+
+ my_free(pool->threads);
+ pool->threads= new_list;
+ pool->free_list= new_free_list;
+ pool->count= new_count;
+ for (i= 0; i < pool->count; ++i)
+ {
+ mysql_mutex_lock(&pool->threads[i]->LOCK_rpl_thread);
+ pool->threads[i]->delay_start= false;
+ mysql_cond_signal(&pool->threads[i]->COND_rpl_thread);
+ while (!pool->threads[i]->running)
+ mysql_cond_wait(&pool->threads[i]->COND_rpl_thread,
+ &pool->threads[i]->LOCK_rpl_thread);
+ mysql_mutex_unlock(&pool->threads[i]->LOCK_rpl_thread);
+ }
+
+ mysql_mutex_lock(&pool->LOCK_rpl_thread_pool);
+ mysql_cond_broadcast(&pool->COND_rpl_thread_pool);
+ mysql_mutex_unlock(&pool->LOCK_rpl_thread_pool);
+
+ return 0;
+
+err:
+ if (new_list)
+ {
+ while (new_free_list)
+ {
+ mysql_mutex_lock(&new_free_list->LOCK_rpl_thread);
+ new_free_list->delay_start= false;
+ new_free_list->stop= true;
+ mysql_cond_signal(&new_free_list->COND_rpl_thread);
+ while (!new_free_list->running)
+ mysql_cond_wait(&new_free_list->COND_rpl_thread,
+ &new_free_list->LOCK_rpl_thread);
+ while (new_free_list->running)
+ mysql_cond_wait(&new_free_list->COND_rpl_thread,
+ &new_free_list->LOCK_rpl_thread);
+ mysql_mutex_unlock(&new_free_list->LOCK_rpl_thread);
+ new_free_list= new_free_list->next;
+ }
+ my_free(new_list);
+ }
+ return 1;
+}
+
+
+int
+rpl_parallel_activate_pool(rpl_parallel_thread_pool *pool)
+{
+ if (!pool->count)
+ return rpl_parallel_change_thread_count(pool, opt_slave_parallel_threads);
+ return 0;
+}
+
+
+int
+rpl_parallel_inactivate_pool(rpl_parallel_thread_pool *pool)
+{
+ return rpl_parallel_change_thread_count(pool, 0);
+}
+
+
+void
+rpl_parallel_thread::batch_free()
+{
+ mysql_mutex_assert_owner(&LOCK_rpl_thread);
+ if (loc_qev_list)
+ {
+ *loc_qev_last_ptr_ptr= qev_free_list;
+ qev_free_list= loc_qev_list;
+ loc_qev_list= NULL;
+ dequeue2(loc_qev_size);
+ /* Signal that our queue can now accept more events. */
+ mysql_cond_signal(&COND_rpl_thread_queue);
+ loc_qev_size= 0;
+ qev_free_pending= 0;
+ }
+ if (loc_rgi_list)
+ {
+ *loc_rgi_last_ptr_ptr= rgi_free_list;
+ rgi_free_list= loc_rgi_list;
+ loc_rgi_list= NULL;
+ }
+ if (loc_gco_list)
+ {
+ *loc_gco_last_ptr_ptr= gco_free_list;
+ gco_free_list= loc_gco_list;
+ loc_gco_list= NULL;
+ }
+}
+
+
+void
+rpl_parallel_thread::inuse_relaylog_refcount_update()
+{
+ inuse_relaylog *ir= accumulated_ir_last;
+ if (ir)
+ {
+ my_atomic_rwlock_wrlock(&ir->inuse_relaylog_atomic_lock);
+ my_atomic_add64(&ir->dequeued_count, accumulated_ir_count);
+ my_atomic_rwlock_wrunlock(&ir->inuse_relaylog_atomic_lock);
+ accumulated_ir_count= 0;
+ accumulated_ir_last= NULL;
+ }
+}
+
+
+rpl_parallel_thread::queued_event *
+rpl_parallel_thread::get_qev_common(Log_event *ev, ulonglong event_size)
+{
+ queued_event *qev;
+ mysql_mutex_assert_owner(&LOCK_rpl_thread);
+ if ((qev= qev_free_list))
+ qev_free_list= qev->next;
+ else if(!(qev= (queued_event *)my_malloc(sizeof(*qev), MYF(0))))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*qev));
+ return NULL;
+ }
+ qev->typ= rpl_parallel_thread::queued_event::QUEUED_EVENT;
+ qev->ev= ev;
+ qev->event_size= event_size;
+ qev->next= NULL;
+ return qev;
+}
+
+
+rpl_parallel_thread::queued_event *
+rpl_parallel_thread::get_qev(Log_event *ev, ulonglong event_size,
+ Relay_log_info *rli)
+{
+ queued_event *qev= get_qev_common(ev, event_size);
+ if (!qev)
+ return NULL;
+ strcpy(qev->event_relay_log_name, rli->event_relay_log_name);
+ qev->event_relay_log_pos= rli->event_relay_log_pos;
+ qev->future_event_relay_log_pos= rli->future_event_relay_log_pos;
+ strcpy(qev->future_event_master_log_name, rli->future_event_master_log_name);
+ return qev;
+}
+
+
+rpl_parallel_thread::queued_event *
+rpl_parallel_thread::retry_get_qev(Log_event *ev, queued_event *orig_qev,
+ const char *relay_log_name,
+ ulonglong event_pos, ulonglong event_size)
+{
+ queued_event *qev= get_qev_common(ev, event_size);
+ if (!qev)
+ return NULL;
+ qev->rgi= orig_qev->rgi;
+ strcpy(qev->event_relay_log_name, relay_log_name);
+ qev->event_relay_log_pos= event_pos;
+ qev->future_event_relay_log_pos= event_pos+event_size;
+ strcpy(qev->future_event_master_log_name,
+ orig_qev->future_event_master_log_name);
+ return qev;
+}
+
+
+void
+rpl_parallel_thread::loc_free_qev(rpl_parallel_thread::queued_event *qev)
+{
+ inuse_relaylog *ir= qev->ir;
+ inuse_relaylog *last_ir= accumulated_ir_last;
+ if (ir != last_ir)
+ {
+ if (last_ir)
+ inuse_relaylog_refcount_update();
+ accumulated_ir_last= ir;
+ }
+ ++accumulated_ir_count;
+ if (!loc_qev_list)
+ loc_qev_last_ptr_ptr= &qev->next;
+ else
+ qev->next= loc_qev_list;
+ loc_qev_list= qev;
+ loc_qev_size+= qev->event_size;
+ /*
+ We want to release to the global free list only occasionally, to avoid
+ having to take the LOCK_rpl_thread muted too many times.
+
+ However, we do need to release regularly. If we let the unreleased part
+ grow too large, then the SQL driver thread may go to sleep waiting for
+ the queue to drop below opt_slave_parallel_max_queued, and this in turn
+ can stall all other worker threads for more stuff to do.
+ */
+ if (++qev_free_pending >= QEV_BATCH_FREE ||
+ loc_qev_size >= opt_slave_parallel_max_queued/3)
+ {
+ mysql_mutex_lock(&LOCK_rpl_thread);
+ batch_free();
+ mysql_mutex_unlock(&LOCK_rpl_thread);
+ }
+}
+
+
+void
+rpl_parallel_thread::free_qev(rpl_parallel_thread::queued_event *qev)
+{
+ mysql_mutex_assert_owner(&LOCK_rpl_thread);
+ qev->next= qev_free_list;
+ qev_free_list= qev;
+}
+
+
+rpl_group_info*
+rpl_parallel_thread::get_rgi(Relay_log_info *rli, Gtid_log_event *gtid_ev,
+ rpl_parallel_entry *e, ulonglong event_size)
+{
+ rpl_group_info *rgi;
+ mysql_mutex_assert_owner(&LOCK_rpl_thread);
+ if ((rgi= rgi_free_list))
+ {
+ rgi_free_list= rgi->next;
+ rgi->reinit(rli);
+ }
+ else
+ {
+ if(!(rgi= new rpl_group_info(rli)))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*rgi));
+ return NULL;
+ }
+ rgi->is_parallel_exec = true;
+ }
+ if ((rgi->deferred_events_collecting= rli->mi->rpl_filter->is_on()) &&
+ !rgi->deferred_events)
+ rgi->deferred_events= new Deferred_log_events(rli);
+ if (event_group_new_gtid(rgi, gtid_ev))
+ {
+ free_rgi(rgi);
+ my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME));
+ return NULL;
+ }
+ rgi->parallel_entry= e;
+ rgi->relay_log= rli->last_inuse_relaylog;
+ rgi->retry_start_offset= rli->future_event_relay_log_pos-event_size;
+ rgi->retry_event_count= 0;
+ rgi->killed_for_retry= false;
+
+ return rgi;
+}
+
+
+void
+rpl_parallel_thread::loc_free_rgi(rpl_group_info *rgi)
+{
+ DBUG_ASSERT(rgi->commit_orderer.waitee == NULL);
+ rgi->free_annotate_event();
+ if (!loc_rgi_list)
+ loc_rgi_last_ptr_ptr= &rgi->next;
+ else
+ rgi->next= loc_rgi_list;
+ loc_rgi_list= rgi;
+}
+
+
+void
+rpl_parallel_thread::free_rgi(rpl_group_info *rgi)
+{
+ mysql_mutex_assert_owner(&LOCK_rpl_thread);
+ DBUG_ASSERT(rgi->commit_orderer.waitee == NULL);
+ rgi->free_annotate_event();
+ rgi->next= rgi_free_list;
+ rgi_free_list= rgi;
+}
+
+
+group_commit_orderer *
+rpl_parallel_thread::get_gco(uint64 wait_count, group_commit_orderer *prev,
+ uint64 prior_sub_id)
+{
+ group_commit_orderer *gco;
+ mysql_mutex_assert_owner(&LOCK_rpl_thread);
+ if ((gco= gco_free_list))
+ gco_free_list= gco->next_gco;
+ else if(!(gco= (group_commit_orderer *)my_malloc(sizeof(*gco), MYF(0))))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*gco));
+ return NULL;
+ }
+ mysql_cond_init(key_COND_group_commit_orderer,
+ &gco->COND_group_commit_orderer, NULL);
+ gco->wait_count= wait_count;
+ gco->prev_gco= prev;
+ gco->next_gco= NULL;
+ gco->prior_sub_id= prior_sub_id;
+ gco->installed= false;
+ return gco;
+}
+
+
+void
+rpl_parallel_thread::loc_free_gco(group_commit_orderer *gco)
+{
+ if (!loc_gco_list)
+ loc_gco_last_ptr_ptr= &gco->next_gco;
+ else
+ gco->next_gco= loc_gco_list;
+ loc_gco_list= gco;
+}
+
+
+rpl_parallel_thread_pool::rpl_parallel_thread_pool()
+ : count(0), threads(0), free_list(0), inited(false)
+{
+}
+
+
+int
+rpl_parallel_thread_pool::init(uint32 size)
+{
+ count= 0;
+ threads= NULL;
+ free_list= NULL;
+
+ mysql_mutex_init(key_LOCK_rpl_thread_pool, &LOCK_rpl_thread_pool,
+ MY_MUTEX_INIT_SLOW);
+ mysql_cond_init(key_COND_rpl_thread_pool, &COND_rpl_thread_pool, NULL);
+ inited= true;
+
+ /*
+ The pool is initially empty. Threads will be spawned when a slave SQL
+ thread is started.
+ */
+
+ return 0;
+}
+
+
+void
+rpl_parallel_thread_pool::destroy()
+{
+ if (!inited)
+ return;
+ rpl_parallel_change_thread_count(this, 0);
+ mysql_mutex_destroy(&LOCK_rpl_thread_pool);
+ mysql_cond_destroy(&COND_rpl_thread_pool);
+ inited= false;
+}
+
+
+/*
+ Wait for a worker thread to become idle. When one does, grab the thread for
+ our use and return it.
+
+ Note that we return with the worker threads's LOCK_rpl_thread mutex locked.
+*/
+struct rpl_parallel_thread *
+rpl_parallel_thread_pool::get_thread(rpl_parallel_thread **owner,
+ rpl_parallel_entry *entry)
+{
+ rpl_parallel_thread *rpt;
+
+ mysql_mutex_lock(&LOCK_rpl_thread_pool);
+ while ((rpt= free_list) == NULL)
+ mysql_cond_wait(&COND_rpl_thread_pool, &LOCK_rpl_thread_pool);
+ free_list= rpt->next;
+ mysql_mutex_unlock(&LOCK_rpl_thread_pool);
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ rpt->current_owner= owner;
+ rpt->current_entry= entry;
+
+ return rpt;
+}
+
+
+/*
+ Release a thread to the thread pool.
+ The thread should be locked, and should not have any work queued for it.
+*/
+void
+rpl_parallel_thread_pool::release_thread(rpl_parallel_thread *rpt)
+{
+ rpl_parallel_thread *list;
+
+ mysql_mutex_assert_owner(&rpt->LOCK_rpl_thread);
+ DBUG_ASSERT(rpt->current_owner == NULL);
+ mysql_mutex_lock(&LOCK_rpl_thread_pool);
+ list= free_list;
+ rpt->next= list;
+ free_list= rpt;
+ if (!list)
+ mysql_cond_broadcast(&COND_rpl_thread_pool);
+ mysql_mutex_unlock(&LOCK_rpl_thread_pool);
+}
+
+
+/*
+ Obtain a worker thread that we can queue an event to.
+
+ Each invocation allocates a new worker thread, to maximise
+ parallelism. However, only up to a maximum of
+ --slave-domain-parallel-threads workers can be occupied by a single
+ replication domain; after that point, we start re-using worker threads that
+ are still executing events that were queued earlier for this thread.
+
+ We never queue more than --rpl-parallel-wait-queue_max amount of events
+ for one worker, to avoid the SQL driver thread using up all memory with
+ queued events while worker threads are stalling.
+
+ Note that this function returns with rpl_parallel_thread::LOCK_rpl_thread
+ locked. Exception is if we were killed, in which case NULL is returned.
+
+ The *did_enter_cond flag is set true if we had to wait for a worker thread
+ to become free (with mysql_cond_wait()). If so, old_stage will also be set,
+ and the LOCK_rpl_thread must be released with THD::EXIT_COND() instead
+ of mysql_mutex_unlock.
+
+ If the flag `reuse' is set, the last worker thread will be returned again,
+ if it is still available. Otherwise a new worker thread is allocated.
+*/
+rpl_parallel_thread *
+rpl_parallel_entry::choose_thread(rpl_group_info *rgi, bool *did_enter_cond,
+ PSI_stage_info *old_stage, bool reuse)
+{
+ uint32 idx;
+ Relay_log_info *rli= rgi->rli;
+ rpl_parallel_thread *thr;
+
+ idx= rpl_thread_idx;
+ if (!reuse)
+ {
+ ++idx;
+ if (idx >= rpl_thread_max)
+ idx= 0;
+ rpl_thread_idx= idx;
+ }
+ thr= rpl_threads[idx];
+ if (thr)
+ {
+ *did_enter_cond= false;
+ mysql_mutex_lock(&thr->LOCK_rpl_thread);
+ for (;;)
+ {
+ if (thr->current_owner != &rpl_threads[idx])
+ {
+ /*
+ The worker thread became idle, and returned to the free list and
+ possibly was allocated to a different request. So we should allocate
+ a new worker thread.
+ */
+ unlock_or_exit_cond(rli->sql_driver_thd, &thr->LOCK_rpl_thread,
+ did_enter_cond, old_stage);
+ thr= NULL;
+ break;
+ }
+ else if (thr->queued_size <= opt_slave_parallel_max_queued)
+ {
+ /* The thread is ready to queue into. */
+ break;
+ }
+ else if (rli->sql_driver_thd->check_killed())
+ {
+ unlock_or_exit_cond(rli->sql_driver_thd, &thr->LOCK_rpl_thread,
+ did_enter_cond, old_stage);
+ my_error(ER_CONNECTION_KILLED, MYF(0));
+ DBUG_EXECUTE_IF("rpl_parallel_wait_queue_max",
+ {
+ debug_sync_set_action(rli->sql_driver_thd,
+ STRING_WITH_LEN("now SIGNAL wait_queue_killed"));
+ };);
+ slave_output_error_info(rgi, rli->sql_driver_thd);
+ return NULL;
+ }
+ else
+ {
+ /*
+ We have reached the limit of how much memory we are allowed to use
+ for queuing events, so wait for the thread to consume some of its
+ queue.
+ */
+ if (!*did_enter_cond)
+ {
+ /*
+ We need to do the debug_sync before ENTER_COND().
+ Because debug_sync changes the thd->mysys_var->current_mutex,
+ and this can cause THD::awake to use the wrong mutex.
+ */
+ DBUG_EXECUTE_IF("rpl_parallel_wait_queue_max",
+ {
+ debug_sync_set_action(rli->sql_driver_thd,
+ STRING_WITH_LEN("now SIGNAL wait_queue_ready"));
+ };);
+ rli->sql_driver_thd->ENTER_COND(&thr->COND_rpl_thread_queue,
+ &thr->LOCK_rpl_thread,
+ &stage_waiting_for_room_in_worker_thread,
+ old_stage);
+ *did_enter_cond= true;
+ }
+ mysql_cond_wait(&thr->COND_rpl_thread_queue, &thr->LOCK_rpl_thread);
+ }
+ }
+ }
+ if (!thr)
+ rpl_threads[idx]= thr= global_rpl_thread_pool.get_thread(&rpl_threads[idx],
+ this);
+
+ return thr;
+}
+
+static void
+free_rpl_parallel_entry(void *element)
+{
+ rpl_parallel_entry *e= (rpl_parallel_entry *)element;
+ while (e->current_gco)
+ {
+ group_commit_orderer *prev_gco= e->current_gco->prev_gco;
+ dealloc_gco(e->current_gco);
+ e->current_gco= prev_gco;
+ }
+ mysql_cond_destroy(&e->COND_parallel_entry);
+ mysql_mutex_destroy(&e->LOCK_parallel_entry);
+ my_free(e);
+}
+
+
+rpl_parallel::rpl_parallel() :
+ current(NULL), sql_thread_stopping(false)
+{
+ my_hash_init(&domain_hash, &my_charset_bin, 32,
+ offsetof(rpl_parallel_entry, domain_id), sizeof(uint32),
+ NULL, free_rpl_parallel_entry, HASH_UNIQUE);
+}
+
+
+void
+rpl_parallel::reset()
+{
+ my_hash_reset(&domain_hash);
+ current= NULL;
+ sql_thread_stopping= false;
+}
+
+
+rpl_parallel::~rpl_parallel()
+{
+ my_hash_free(&domain_hash);
+}
+
+
+rpl_parallel_entry *
+rpl_parallel::find(uint32 domain_id)
+{
+ struct rpl_parallel_entry *e;
+
+ if (!(e= (rpl_parallel_entry *)my_hash_search(&domain_hash,
+ (const uchar *)&domain_id, 0)))
+ {
+ /* Allocate a new, empty one. */
+ ulong count= opt_slave_domain_parallel_threads;
+ if (count == 0 || count > opt_slave_parallel_threads)
+ count= opt_slave_parallel_threads;
+ rpl_parallel_thread **p;
+ if (!my_multi_malloc(MYF(MY_WME|MY_ZEROFILL),
+ &e, sizeof(*e),
+ &p, count*sizeof(*p),
+ NULL))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), (int)(sizeof(*e)+count*sizeof(*p)));
+ return NULL;
+ }
+ e->rpl_threads= p;
+ e->rpl_thread_max= count;
+ e->domain_id= domain_id;
+ e->stop_on_error_sub_id= (uint64)ULONGLONG_MAX;
+ if (my_hash_insert(&domain_hash, (uchar *)e))
+ {
+ my_free(e);
+ return NULL;
+ }
+ mysql_mutex_init(key_LOCK_parallel_entry, &e->LOCK_parallel_entry,
+ MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_parallel_entry, &e->COND_parallel_entry, NULL);
+ }
+ else
+ e->force_abort= false;
+
+ return e;
+}
+
+
+void
+rpl_parallel::wait_for_done(THD *thd, Relay_log_info *rli)
+{
+ struct rpl_parallel_entry *e;
+ rpl_parallel_thread *rpt;
+ uint32 i, j;
+
+ /*
+ First signal all workers that they must force quit; no more events will
+ be queued to complete any partial event groups executed.
+ */
+ for (i= 0; i < domain_hash.records; ++i)
+ {
+ e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i);
+ mysql_mutex_lock(&e->LOCK_parallel_entry);
+ /*
+ We want the worker threads to stop as quickly as is safe. If the slave
+ SQL threads are behind, we could have significant amount of events
+ queued for the workers, and we want to stop without waiting for them
+ all to be applied first. But if any event group has already started
+ executing in a worker, we want to be sure that all prior event groups
+ are also executed, so that we stop at a consistent point in the binlog
+ stream (per replication domain).
+
+ All event groups wait for e->count_committing_event_groups to reach
+ the value of group_commit_orderer::wait_count before starting to
+ execute. Thus, at this point we know that any event group with a
+ strictly larger wait_count are safe to skip, none of them can have
+ started executing yet. So we set e->stop_count here and use it to
+ decide in the worker threads whether to continue executing an event
+ group or whether to skip it, when force_abort is set.
+
+ If we stop due to reaching the START SLAVE UNTIL condition, then we
+ need to continue executing any queued events up to that point.
+ */
+ e->force_abort= true;
+ e->stop_count= rli->stop_for_until ?
+ e->count_queued_event_groups : e->count_committing_event_groups;
+ mysql_mutex_unlock(&e->LOCK_parallel_entry);
+ for (j= 0; j < e->rpl_thread_max; ++j)
+ {
+ if ((rpt= e->rpl_threads[j]))
+ {
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ if (rpt->current_owner == &e->rpl_threads[j])
+ mysql_cond_signal(&rpt->COND_rpl_thread);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+ }
+ }
+ }
+ DBUG_EXECUTE_IF("rpl_parallel_wait_for_done_trigger",
+ {
+ debug_sync_set_action(thd,
+ STRING_WITH_LEN("now SIGNAL wait_for_done_waiting"));
+ };);
+
+ for (i= 0; i < domain_hash.records; ++i)
+ {
+ e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i);
+ for (j= 0; j < e->rpl_thread_max; ++j)
+ {
+ if ((rpt= e->rpl_threads[j]))
+ {
+ mysql_mutex_lock(&rpt->LOCK_rpl_thread);
+ while (rpt->current_owner == &e->rpl_threads[j])
+ mysql_cond_wait(&e->COND_parallel_entry, &rpt->LOCK_rpl_thread);
+ mysql_mutex_unlock(&rpt->LOCK_rpl_thread);
+ }
+ }
+ }
+}
+
+
+/*
+ This function handles the case where the SQL driver thread reached the
+ START SLAVE UNTIL position; we stop queueing more events but continue
+ processing remaining, already queued events; then use executes manual
+ STOP SLAVE; then this function signals to worker threads that they
+ should stop the processing of any remaining queued events.
+*/
+void
+rpl_parallel::stop_during_until()
+{
+ struct rpl_parallel_entry *e;
+ uint32 i;
+
+ for (i= 0; i < domain_hash.records; ++i)
+ {
+ e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i);
+ mysql_mutex_lock(&e->LOCK_parallel_entry);
+ if (e->force_abort)
+ e->stop_count= e->count_committing_event_groups;
+ mysql_mutex_unlock(&e->LOCK_parallel_entry);
+ }
+}
+
+
+bool
+rpl_parallel::workers_idle()
+{
+ struct rpl_parallel_entry *e;
+ uint32 i, max_i;
+
+ max_i= domain_hash.records;
+ for (i= 0; i < max_i; ++i)
+ {
+ bool active;
+ e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i);
+ mysql_mutex_lock(&e->LOCK_parallel_entry);
+ active= e->current_sub_id > e->last_committed_sub_id;
+ mysql_mutex_unlock(&e->LOCK_parallel_entry);
+ if (active)
+ break;
+ }
+ return (i == max_i);
+}
+
+
+int
+rpl_parallel_entry::queue_master_restart(rpl_group_info *rgi,
+ Format_description_log_event *fdev)
+{
+ uint32 idx;
+ rpl_parallel_thread *thr;
+ rpl_parallel_thread::queued_event *qev;
+ Relay_log_info *rli= rgi->rli;
+
+ /*
+ We only need to queue the server restart if we still have a thread working
+ on a (potentially partial) event group.
+
+ If the last thread we queued for has finished, then it cannot have any
+ partial event group that needs aborting.
+
+ Thus there is no need for the full complexity of choose_thread(). We only
+ need to check if we have a current worker thread, and queue for it if so.
+ */
+ idx= rpl_thread_idx;
+ thr= rpl_threads[idx];
+ if (!thr)
+ return 0;
+ mysql_mutex_lock(&thr->LOCK_rpl_thread);
+ if (thr->current_owner != &rpl_threads[idx])
+ {
+ /* No active worker thread, so no need to queue the master restart. */
+ mysql_mutex_unlock(&thr->LOCK_rpl_thread);
+ return 0;
+ }
+
+ if (!(qev= thr->get_qev(fdev, 0, rli)))
+ {
+ mysql_mutex_unlock(&thr->LOCK_rpl_thread);
+ return 1;
+ }
+
+ qev->rgi= rgi;
+ qev->typ= rpl_parallel_thread::queued_event::QUEUED_MASTER_RESTART;
+ qev->entry_for_queued= this;
+ qev->ir= rli->last_inuse_relaylog;
+ ++qev->ir->queued_count;
+ thr->enqueue(qev);
+ mysql_cond_signal(&thr->COND_rpl_thread);
+ mysql_mutex_unlock(&thr->LOCK_rpl_thread);
+ return 0;
+}
+
+
+int
+rpl_parallel::wait_for_workers_idle(THD *thd)
+{
+ uint32 i, max_i;
+
+ /*
+ The domain_hash is only accessed by the SQL driver thread, so it is safe
+ to iterate over without a lock.
+ */
+ max_i= domain_hash.records;
+ for (i= 0; i < max_i; ++i)
+ {
+ bool active;
+ wait_for_commit my_orderer;
+ struct rpl_parallel_entry *e;
+
+ e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i);
+ mysql_mutex_lock(&e->LOCK_parallel_entry);
+ if ((active= (e->current_sub_id > e->last_committed_sub_id)))
+ {
+ wait_for_commit *waitee= &e->current_group_info->commit_orderer;
+ my_orderer.register_wait_for_prior_commit(waitee);
+ thd->wait_for_commit_ptr= &my_orderer;
+ }
+ mysql_mutex_unlock(&e->LOCK_parallel_entry);
+ if (active)
+ {
+ int err= my_orderer.wait_for_prior_commit(thd);
+ thd->wait_for_commit_ptr= NULL;
+ if (err)
+ return err;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ Handle seeing a GTID during slave restart in GTID mode. If we stopped with
+ different replication domains having reached different positions in the relay
+ log, we need to skip event groups in domains that are further progressed.
+
+ Updates the state with the seen GTID, and returns true if this GTID should
+ be skipped, false otherwise.
+*/
+bool
+process_gtid_for_restart_pos(Relay_log_info *rli, rpl_gtid *gtid)
+{
+ slave_connection_state::entry *gtid_entry;
+ slave_connection_state *state= &rli->restart_gtid_pos;
+
+ if (likely(state->count() == 0) ||
+ !(gtid_entry= state->find_entry(gtid->domain_id)))
+ return false;
+ if (gtid->server_id == gtid_entry->gtid.server_id)
+ {
+ uint64 seq_no= gtid_entry->gtid.seq_no;
+ if (gtid->seq_no >= seq_no)
+ {
+ /*
+ This domain has reached its start position. So remove it, so that
+ further events will be processed normally.
+ */
+ state->remove(&gtid_entry->gtid);
+ }
+ return gtid->seq_no <= seq_no;
+ }
+ else
+ return true;
+}
+
+
+/*
+ This is used when we get an error during processing in do_event();
+ We will not queue any event to the thread, but we still need to wake it up
+ to be sure that it will be returned to the pool.
+*/
+static void
+abandon_worker_thread(THD *thd, rpl_parallel_thread *cur_thread,
+ bool *did_enter_cond, PSI_stage_info *old_stage)
+{
+ unlock_or_exit_cond(thd, &cur_thread->LOCK_rpl_thread,
+ did_enter_cond, old_stage);
+ mysql_cond_signal(&cur_thread->COND_rpl_thread);
+}
+
+
+/*
+ do_event() is executed by the sql_driver_thd thread.
+ It's main purpose is to find a thread that can execute the query.
+
+ @retval 0 ok, event was accepted
+ @retval 1 error
+ @retval -1 event should be executed serially, in the sql driver thread
+*/
+
+int
+rpl_parallel::do_event(rpl_group_info *serial_rgi, Log_event *ev,
+ ulonglong event_size)
+{
+ rpl_parallel_entry *e;
+ rpl_parallel_thread *cur_thread;
+ rpl_parallel_thread::queued_event *qev;
+ rpl_group_info *rgi= NULL;
+ Relay_log_info *rli= serial_rgi->rli;
+ enum Log_event_type typ;
+ bool is_group_event;
+ bool did_enter_cond= false;
+ PSI_stage_info old_stage;
+
+ /* Handle master log name change, seen in Rotate_log_event. */
+ typ= ev->get_type_code();
+ if (unlikely(typ == ROTATE_EVENT))
+ {
+ Rotate_log_event *rev= static_cast<Rotate_log_event *>(ev);
+ if ((rev->server_id != global_system_variables.server_id ||
+ rli->replicate_same_server_id) &&
+ !rev->is_relay_log_event() &&
+ !rli->is_in_group())
+ {
+ memcpy(rli->future_event_master_log_name,
+ rev->new_log_ident, rev->ident_len+1);
+ }
+ }
+
+ /*
+ Execute queries non-parallel if slave_skip_counter is set, as it's is
+ easier to skip queries in single threaded mode.
+ */
+ if (rli->slave_skip_counter)
+ return -1;
+
+ /* Execute pre-10.0 event, which have no GTID, in single-threaded mode. */
+ is_group_event= Log_event::is_group_event(typ);
+ if (unlikely(!current) && typ != GTID_EVENT &&
+ !(unlikely(rli->gtid_skip_flag != GTID_SKIP_NOT) && is_group_event))
+ return -1;
+
+ /* ToDo: what to do with this lock?!? */
+ mysql_mutex_unlock(&rli->data_lock);
+
+ if (unlikely(typ == FORMAT_DESCRIPTION_EVENT))
+ {
+ Format_description_log_event *fdev=
+ static_cast<Format_description_log_event *>(ev);
+ if (fdev->created)
+ {
+ /*
+ This format description event marks a new binlog after a master server
+ restart. We are going to close all temporary tables to clean up any
+ possible left-overs after a prior master crash.
+
+ Thus we need to wait for all prior events to execute to completion,
+ in case they need access to any of the temporary tables.
+
+ We also need to notify the worker thread running the prior incomplete
+ event group (if any), as such event group signifies an incompletely
+ written group cut short by a master crash, and must be rolled back.
+ */
+ if (current->queue_master_restart(serial_rgi, fdev) ||
+ wait_for_workers_idle(rli->sql_driver_thd))
+ {
+ delete ev;
+ return 1;
+ }
+ }
+ }
+ else if (unlikely(typ == GTID_LIST_EVENT))
+ {
+ Gtid_list_log_event *glev= static_cast<Gtid_list_log_event *>(ev);
+ rpl_gtid *list= glev->list;
+ uint32 count= glev->count;
+ rli->update_relay_log_state(list, count);
+ while (count)
+ {
+ process_gtid_for_restart_pos(rli, list);
+ ++list;
+ --count;
+ }
+ }
+
+ /*
+ Stop queueing additional event groups once the SQL thread is requested to
+ stop.
+
+ We have to queue any remaining events of any event group that has already
+ been partially queued, but after that we will just ignore any further
+ events the SQL driver thread may try to queue, and eventually it will stop.
+ */
+ if ((typ == GTID_EVENT || !is_group_event) && rli->abort_slave)
+ sql_thread_stopping= true;
+ if (sql_thread_stopping)
+ {
+ delete ev;
+ /*
+ Return "no error"; normal stop is not an error, and otherwise the error
+ has already been recorded.
+ */
+ return 0;
+ }
+
+ if (unlikely(rli->gtid_skip_flag != GTID_SKIP_NOT) && is_group_event)
+ {
+ if (typ == GTID_EVENT)
+ rli->gtid_skip_flag= GTID_SKIP_NOT;
+ else
+ {
+ if (rli->gtid_skip_flag == GTID_SKIP_STANDALONE)
+ {
+ if (!Log_event::is_part_of_group(typ))
+ rli->gtid_skip_flag= GTID_SKIP_NOT;
+ }
+ else
+ {
+ DBUG_ASSERT(rli->gtid_skip_flag == GTID_SKIP_TRANSACTION);
+ if (typ == XID_EVENT ||
+ (typ == QUERY_EVENT &&
+ (((Query_log_event *)ev)->is_commit() ||
+ ((Query_log_event *)ev)->is_rollback())))
+ rli->gtid_skip_flag= GTID_SKIP_NOT;
+ }
+ delete_or_keep_event_post_apply(serial_rgi, typ, ev);
+ return 0;
+ }
+ }
+
+ if (typ == GTID_EVENT)
+ {
+ rpl_gtid gtid;
+ Gtid_log_event *gtid_ev= static_cast<Gtid_log_event *>(ev);
+ uint32 domain_id= (rli->mi->using_gtid == Master_info::USE_GTID_NO ?
+ 0 : gtid_ev->domain_id);
+ if (!(e= find(domain_id)))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME));
+ delete ev;
+ return 1;
+ }
+ current= e;
+
+ gtid.domain_id= gtid_ev->domain_id;
+ gtid.server_id= gtid_ev->server_id;
+ gtid.seq_no= gtid_ev->seq_no;
+ rli->update_relay_log_state(&gtid, 1);
+ if (process_gtid_for_restart_pos(rli, &gtid))
+ {
+ /*
+ This domain has progressed further into the relay log before the last
+ SQL thread restart. So we need to skip this event group to not doubly
+ apply it.
+ */
+ rli->gtid_skip_flag= ((gtid_ev->flags2 & Gtid_log_event::FL_STANDALONE) ?
+ GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION);
+ delete_or_keep_event_post_apply(serial_rgi, typ, ev);
+ return 0;
+ }
+ }
+ else
+ e= current;
+
+ /*
+ Find a worker thread to queue the event for.
+ Prefer a new thread, so we maximise parallelism (at least for the group
+ commit). But do not exceed a limit of --slave-domain-parallel-threads;
+ instead re-use a thread that we queued for previously.
+ */
+ cur_thread=
+ e->choose_thread(serial_rgi, &did_enter_cond, &old_stage,
+ typ != GTID_EVENT);
+ if (!cur_thread)
+ {
+ /* This means we were killed. The error is already signalled. */
+ delete ev;
+ return 1;
+ }
+
+ if (!(qev= cur_thread->get_qev(ev, event_size, rli)))
+ {
+ abandon_worker_thread(rli->sql_driver_thd, cur_thread,
+ &did_enter_cond, &old_stage);
+ delete ev;
+ return 1;
+ }
+
+ if (typ == GTID_EVENT)
+ {
+ Gtid_log_event *gtid_ev= static_cast<Gtid_log_event *>(ev);
+
+ if (!(rgi= cur_thread->get_rgi(rli, gtid_ev, e, event_size)))
+ {
+ cur_thread->free_qev(qev);
+ abandon_worker_thread(rli->sql_driver_thd, cur_thread,
+ &did_enter_cond, &old_stage);
+ delete ev;
+ return 1;
+ }
+
+ /*
+ We queue the event group in a new worker thread, to run in parallel
+ with previous groups.
+
+ To preserve commit order within the replication domain, we set up
+ rgi->wait_commit_sub_id to make the new group commit only after the
+ previous group has committed.
+
+ Event groups that group-committed together on the master can be run
+ in parallel with each other without restrictions. But one batch of
+ group-commits may not start before all groups in the previous batch
+ have initiated their commit phase; we set up rgi->gco to ensure that.
+ */
+ rgi->wait_commit_sub_id= e->current_sub_id;
+ rgi->wait_commit_group_info= e->current_group_info;
+
+ if (!((gtid_ev->flags2 & Gtid_log_event::FL_GROUP_COMMIT_ID) &&
+ e->last_commit_id == gtid_ev->commit_id))
+ {
+ /*
+ A new batch of transactions that group-committed together on the master.
+
+ Remember the count that marks the end of the previous group committed
+ batch, and allocate a new gco.
+ */
+ uint64 count= e->count_queued_event_groups;
+ group_commit_orderer *gco;
+
+ if (!(gco= cur_thread->get_gco(count, e->current_gco, e->current_sub_id)))
+ {
+ cur_thread->free_rgi(rgi);
+ cur_thread->free_qev(qev);
+ abandon_worker_thread(rli->sql_driver_thd, cur_thread,
+ &did_enter_cond, &old_stage);
+ delete ev;
+ return 1;
+ }
+ e->current_gco= rgi->gco= gco;
+ }
+ else
+ rgi->gco= e->current_gco;
+ if (gtid_ev->flags2 & Gtid_log_event::FL_GROUP_COMMIT_ID)
+ e->last_commit_id= gtid_ev->commit_id;
+ else
+ e->last_commit_id= 0;
+ qev->rgi= e->current_group_info= rgi;
+ e->current_sub_id= rgi->gtid_sub_id;
+ ++e->count_queued_event_groups;
+ }
+ else if (!is_group_event)
+ {
+ int err;
+ bool tmp;
+ /*
+ Events like ROTATE and FORMAT_DESCRIPTION. Do not run in worker thread.
+ Same for events not preceeded by GTID (we should not see those normally,
+ but they might be from an old master).
+ */
+ qev->rgi= serial_rgi;
+
+ tmp= serial_rgi->is_parallel_exec;
+ serial_rgi->is_parallel_exec= true;
+ err= rpt_handle_event(qev, NULL);
+ serial_rgi->is_parallel_exec= tmp;
+ if (ev->is_relay_log_event())
+ qev->future_event_master_log_pos= 0;
+ else if (typ == ROTATE_EVENT)
+ qev->future_event_master_log_pos=
+ (static_cast<Rotate_log_event *>(ev))->pos;
+ else
+ qev->future_event_master_log_pos= ev->log_pos;
+ delete_or_keep_event_post_apply(serial_rgi, typ, ev);
+
+ if (err)
+ {
+ cur_thread->free_qev(qev);
+ abandon_worker_thread(rli->sql_driver_thd, cur_thread,
+ &did_enter_cond, &old_stage);
+ return 1;
+ }
+ /*
+ Queue a position update, so that the position will be updated in a
+ reasonable way relative to other events:
+
+ - If the currently executing events are queued serially for a single
+ thread, the position will only be updated when everything before has
+ completed.
+
+ - If we are executing multiple independent events in parallel, then at
+ least the position will not be updated until one of them has reached
+ the current point.
+ */
+ qev->typ= rpl_parallel_thread::queued_event::QUEUED_POS_UPDATE;
+ qev->entry_for_queued= e;
+ }
+ else
+ {
+ qev->rgi= e->current_group_info;
+ }
+
+ /*
+ Queue the event for processing.
+ */
+ rli->event_relay_log_pos= rli->future_event_relay_log_pos;
+ qev->ir= rli->last_inuse_relaylog;
+ ++qev->ir->queued_count;
+ cur_thread->enqueue(qev);
+ unlock_or_exit_cond(rli->sql_driver_thd, &cur_thread->LOCK_rpl_thread,
+ &did_enter_cond, &old_stage);
+ mysql_cond_signal(&cur_thread->COND_rpl_thread);
+
+ return 0;
+}
diff --git a/sql/rpl_parallel.h b/sql/rpl_parallel.h
new file mode 100644
index 00000000000..09e0f39c0cd
--- /dev/null
+++ b/sql/rpl_parallel.h
@@ -0,0 +1,320 @@
+#ifndef RPL_PARALLEL_H
+#define RPL_PARALLEL_H
+
+#include "log_event.h"
+
+
+struct rpl_parallel;
+struct rpl_parallel_entry;
+struct rpl_parallel_thread_pool;
+
+class Relay_log_info;
+struct inuse_relaylog;
+
+
+/*
+ Structure used to keep track of the parallel replication of a batch of
+ event-groups that group-committed together on the master.
+
+ It is used to ensure that every event group in one batch has reached the
+ commit stage before the next batch starts executing.
+
+ Note the lifetime of this structure:
+
+ - It is allocated when the first event in a new batch of group commits
+ is queued, from the free list rpl_parallel_entry::gco_free_list.
+
+ - The gco for the batch currently being queued is owned by
+ rpl_parallel_entry::current_gco. The gco for a previous batch that has
+ been fully queued is owned by the gco->prev_gco pointer of the gco for
+ the following batch.
+
+ - The worker thread waits on gco->COND_group_commit_orderer for
+ rpl_parallel_entry::count_committing_event_groups to reach wait_count
+ before starting; the first waiter links the gco into the next_gco
+ pointer of the gco of the previous batch for signalling.
+
+ - When an event group reaches the commit stage, it signals the
+ COND_group_commit_orderer if its gco->next_gco pointer is non-NULL and
+ rpl_parallel_entry::count_committing_event_groups has reached
+ gco->next_gco->wait_count.
+
+ - The gco lives until all its event groups have completed their commit.
+ This is detected by rpl_parallel_entry::last_committed_sub_id being
+ greater than or equal gco->last_sub_id. Once this happens, the gco is
+ freed. Note that since update of last_committed_sub_id can happen
+ out-of-order, the thread that frees a given gco can be for any later
+ event group, not necessarily an event group from the gco being freed.
+*/
+struct group_commit_orderer {
+ /* Wakeup condition, used with rpl_parallel_entry::LOCK_parallel_entry. */
+ mysql_cond_t COND_group_commit_orderer;
+ uint64 wait_count;
+ group_commit_orderer *prev_gco;
+ group_commit_orderer *next_gco;
+ /*
+ The sub_id of last event group in the previous GCO.
+ Only valid if prev_gco != NULL.
+ */
+ uint64 prior_sub_id;
+ /*
+ The sub_id of the last event group in this GCO. Only valid when next_gco
+ is non-NULL.
+ */
+ uint64 last_sub_id;
+ bool installed;
+};
+
+
+struct rpl_parallel_thread {
+ bool delay_start;
+ bool running;
+ bool stop;
+ mysql_mutex_t LOCK_rpl_thread;
+ mysql_cond_t COND_rpl_thread;
+ mysql_cond_t COND_rpl_thread_queue;
+ struct rpl_parallel_thread *next; /* For free list. */
+ struct rpl_parallel_thread_pool *pool;
+ THD *thd;
+ /*
+ Who owns the thread, if any (it's a pointer into the
+ rpl_parallel_entry::rpl_threads array.
+ */
+ struct rpl_parallel_thread **current_owner;
+ /* The rpl_parallel_entry of the owner. */
+ rpl_parallel_entry *current_entry;
+ struct queued_event {
+ queued_event *next;
+ /*
+ queued_event can hold either an event to be executed, or just a binlog
+ position to be updated without any associated event.
+ */
+ enum queued_event_t {
+ QUEUED_EVENT,
+ QUEUED_POS_UPDATE,
+ QUEUED_MASTER_RESTART
+ } typ;
+ union {
+ Log_event *ev; /* QUEUED_EVENT */
+ rpl_parallel_entry *entry_for_queued; /* QUEUED_POS_UPDATE and
+ QUEUED_MASTER_RESTART */
+ };
+ rpl_group_info *rgi;
+ inuse_relaylog *ir;
+ ulonglong future_event_relay_log_pos;
+ char event_relay_log_name[FN_REFLEN];
+ char future_event_master_log_name[FN_REFLEN];
+ ulonglong event_relay_log_pos;
+ my_off_t future_event_master_log_pos;
+ size_t event_size;
+ } *event_queue, *last_in_queue;
+ uint64 queued_size;
+ /* These free lists are protected by LOCK_rpl_thread. */
+ queued_event *qev_free_list;
+ rpl_group_info *rgi_free_list;
+ group_commit_orderer *gco_free_list;
+ /*
+ These free lists are local to the thread, so need not be protected by any
+ lock. They are moved to the global free lists in batches in the function
+ batch_free(), to reduce LOCK_rpl_thread contention.
+
+ The lists are not NULL-terminated (as we do not need to traverse them).
+ Instead, if they are non-NULL, the loc_XXX_last_ptr_ptr points to the
+ `next' pointer of the last element, which is used to link into the front
+ of the global freelists.
+ */
+ queued_event *loc_qev_list, **loc_qev_last_ptr_ptr;
+ size_t loc_qev_size;
+ uint64 qev_free_pending;
+ rpl_group_info *loc_rgi_list, **loc_rgi_last_ptr_ptr;
+ group_commit_orderer *loc_gco_list, **loc_gco_last_ptr_ptr;
+ /* These keep track of batch update of inuse_relaylog refcounts. */
+ inuse_relaylog *accumulated_ir_last;
+ uint64 accumulated_ir_count;
+
+ void enqueue(queued_event *qev)
+ {
+ if (last_in_queue)
+ last_in_queue->next= qev;
+ else
+ event_queue= qev;
+ last_in_queue= qev;
+ queued_size+= qev->event_size;
+ }
+
+ void dequeue1(queued_event *list)
+ {
+ DBUG_ASSERT(list == event_queue);
+ event_queue= last_in_queue= NULL;
+ }
+
+ void dequeue2(size_t dequeue_size)
+ {
+ queued_size-= dequeue_size;
+ }
+
+ queued_event *get_qev_common(Log_event *ev, ulonglong event_size);
+ queued_event *get_qev(Log_event *ev, ulonglong event_size,
+ Relay_log_info *rli);
+ queued_event *retry_get_qev(Log_event *ev, queued_event *orig_qev,
+ const char *relay_log_name,
+ ulonglong event_pos, ulonglong event_size);
+ /*
+ Put a qev on the local free list, to be later released to the global free
+ list by batch_free().
+ */
+ void loc_free_qev(queued_event *qev);
+ /*
+ Release an rgi immediately to the global free list. Requires holding the
+ LOCK_rpl_thread mutex.
+ */
+ void free_qev(queued_event *qev);
+ rpl_group_info *get_rgi(Relay_log_info *rli, Gtid_log_event *gtid_ev,
+ rpl_parallel_entry *e, ulonglong event_size);
+ /*
+ Put an gco on the local free list, to be later released to the global free
+ list by batch_free().
+ */
+ void loc_free_rgi(rpl_group_info *rgi);
+ /*
+ Release an rgi immediately to the global free list. Requires holding the
+ LOCK_rpl_thread mutex.
+ */
+ void free_rgi(rpl_group_info *rgi);
+ group_commit_orderer *get_gco(uint64 wait_count, group_commit_orderer *prev,
+ uint64 first_sub_id);
+ /*
+ Put a gco on the local free list, to be later released to the global free
+ list by batch_free().
+ */
+ void loc_free_gco(group_commit_orderer *gco);
+ /*
+ Move all local free lists to the global ones. Requires holding
+ LOCK_rpl_thread.
+ */
+ void batch_free();
+ /* Update inuse_relaylog refcounts with what we have accumulated so far. */
+ void inuse_relaylog_refcount_update();
+};
+
+
+struct rpl_parallel_thread_pool {
+ uint32 count;
+ struct rpl_parallel_thread **threads;
+ struct rpl_parallel_thread *free_list;
+ mysql_mutex_t LOCK_rpl_thread_pool;
+ mysql_cond_t COND_rpl_thread_pool;
+ bool inited;
+
+ rpl_parallel_thread_pool();
+ int init(uint32 size);
+ void destroy();
+ struct rpl_parallel_thread *get_thread(rpl_parallel_thread **owner,
+ rpl_parallel_entry *entry);
+ void release_thread(rpl_parallel_thread *rpt);
+};
+
+
+struct rpl_parallel_entry {
+ mysql_mutex_t LOCK_parallel_entry;
+ mysql_cond_t COND_parallel_entry;
+ uint32 domain_id;
+ uint64 last_commit_id;
+ bool active;
+ /*
+ Set when SQL thread is shutting down, and no more events can be processed,
+ so worker threads must force abort any current transactions without
+ waiting for event groups to complete.
+ */
+ bool force_abort;
+ /*
+ At STOP SLAVE (force_abort=true), we do not want to process all events in
+ the queue (which could unnecessarily delay stop, if a lot of events happen
+ to be queued). The stop_count provides a safe point at which to stop, so
+ that everything before becomes committed and nothing after does. The value
+ corresponds to group_commit_orderer::wait_count; if wait_count is less than
+ or equal to stop_count, we execute the associated event group, else we
+ skip it (and all following) and stop.
+ */
+ uint64 stop_count;
+
+ /*
+ Cyclic array recording the last rpl_thread_max worker threads that we
+ queued event for. This is used to limit how many workers a single domain
+ can occupy (--slave-domain-parallel-threads).
+
+ Note that workers are never explicitly deleted from the array. Instead,
+ we need to check (under LOCK_rpl_thread) that the thread still belongs
+ to us before re-using (rpl_thread::current_owner).
+ */
+ rpl_parallel_thread **rpl_threads;
+ uint32 rpl_thread_max;
+ uint32 rpl_thread_idx;
+ /*
+ The sub_id of the last transaction to commit within this domain_id.
+ Must be accessed under LOCK_parallel_entry protection.
+
+ Event groups commit in order, so the rpl_group_info for an event group
+ will be alive (at least) as long as
+ rpl_group_info::gtid_sub_id > last_committed_sub_id. This can be used to
+ safely refer back to previous event groups if they are still executing,
+ and ignore them if they completed, without requiring explicit
+ synchronisation between the threads.
+ */
+ uint64 last_committed_sub_id;
+ /*
+ The sub_id of the last event group in this replication domain that was
+ queued for execution by a worker thread.
+ */
+ uint64 current_sub_id;
+ rpl_group_info *current_group_info;
+ /*
+ If we get an error in some event group, we set the sub_id of that event
+ group here. Then later event groups (with higher sub_id) can know not to
+ try to start (event groups that already started will be rolled back when
+ wait_for_prior_commit() returns error).
+ The value is ULONGLONG_MAX when no error occured.
+ */
+ uint64 stop_on_error_sub_id;
+ /* Total count of event groups queued so far. */
+ uint64 count_queued_event_groups;
+ /*
+ Count of event groups that have started (but not necessarily completed)
+ the commit phase. We use this to know when every event group in a previous
+ batch of master group commits have started committing on the slave, so
+ that it is safe to start executing the events in the following batch.
+ */
+ uint64 count_committing_event_groups;
+ /* The group_commit_orderer object for the events currently being queued. */
+ group_commit_orderer *current_gco;
+
+ rpl_parallel_thread * choose_thread(rpl_group_info *rgi, bool *did_enter_cond,
+ PSI_stage_info *old_stage, bool reuse);
+ int queue_master_restart(rpl_group_info *rgi,
+ Format_description_log_event *fdev);
+};
+struct rpl_parallel {
+ HASH domain_hash;
+ rpl_parallel_entry *current;
+ bool sql_thread_stopping;
+
+ rpl_parallel();
+ ~rpl_parallel();
+ void reset();
+ rpl_parallel_entry *find(uint32 domain_id);
+ void wait_for_done(THD *thd, Relay_log_info *rli);
+ void stop_during_until();
+ bool workers_idle();
+ int wait_for_workers_idle(THD *thd);
+ int do_event(rpl_group_info *serial_rgi, Log_event *ev, ulonglong event_size);
+};
+
+
+extern struct rpl_parallel_thread_pool global_rpl_thread_pool;
+
+
+extern int rpl_parallel_activate_pool(rpl_parallel_thread_pool *pool);
+extern int rpl_parallel_inactivate_pool(rpl_parallel_thread_pool *pool);
+extern bool process_gtid_for_restart_pos(Relay_log_info *rli, rpl_gtid *gtid);
+
+#endif /* RPL_PARALLEL_H */
diff --git a/sql/rpl_record.cc b/sql/rpl_record.cc
new file mode 100644
index 00000000000..f0308308fea
--- /dev/null
+++ b/sql/rpl_record.cc
@@ -0,0 +1,465 @@
+/* Copyright (c) 2007, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2014, SkySQL Ab.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "rpl_rli.h"
+#include "rpl_record.h"
+#include "slave.h" // Need to pull in slave_print_msg
+#include "rpl_utility.h"
+#include "rpl_rli.h"
+
+/**
+ Pack a record of data for a table into a format suitable for
+ transfer via the binary log.
+
+ The format for a row in transfer with N fields is the following:
+
+ ceil(N/8) null bytes:
+ One null bit for every column *regardless of whether it can be
+ null or not*. This simplifies the decoding. Observe that the
+ number of null bits is equal to the number of set bits in the
+ @c cols bitmap. The number of null bytes is the smallest number
+ of bytes necessary to store the null bits.
+
+ Padding bits are 1.
+
+ N packets:
+ Each field is stored in packed format.
+
+
+ @param table Table describing the format of the record
+
+ @param cols Bitmap with a set bit for each column that should
+ be stored in the row
+
+ @param row_data Pointer to memory where row will be written
+
+ @param record Pointer to record that should be packed. It is
+ assumed that the pointer refers to either @c
+ record[0] or @c record[1], but no such check is
+ made since the code does not rely on that.
+
+ @return The number of bytes written at @c row_data.
+ */
+#if !defined(MYSQL_CLIENT)
+size_t
+pack_row(TABLE *table, MY_BITMAP const* cols,
+ uchar *row_data, const uchar *record)
+{
+ Field **p_field= table->field, *field;
+ int const null_byte_count= (bitmap_bits_set(cols) + 7) / 8;
+ uchar *pack_ptr = row_data + null_byte_count;
+ uchar *null_ptr = row_data;
+ my_ptrdiff_t const rec_offset= record - table->record[0];
+ my_ptrdiff_t const def_offset= table->s->default_values - table->record[0];
+
+ DBUG_ENTER("pack_row");
+
+ /*
+ We write the null bits and the packed records using one pass
+ through all the fields. The null bytes are written little-endian,
+ i.e., the first fields are in the first byte.
+ */
+ unsigned int null_bits= (1U << 8) - 1;
+ // Mask to mask out the correct but among the null bits
+ unsigned int null_mask= 1U;
+ for ( ; (field= *p_field) ; p_field++)
+ {
+ if (bitmap_is_set(cols, p_field - table->field))
+ {
+ my_ptrdiff_t offset;
+ if (field->is_null(rec_offset))
+ {
+ offset= def_offset;
+ null_bits |= null_mask;
+ }
+ else
+ {
+ offset= rec_offset;
+ null_bits &= ~null_mask;
+
+ /*
+ We only store the data of the field if it is non-null
+
+ For big-endian machines, we have to make sure that the
+ length is stored in little-endian format, since this is the
+ format used for the binlog.
+ */
+#ifndef DBUG_OFF
+ const uchar *old_pack_ptr= pack_ptr;
+#endif
+ pack_ptr= field->pack(pack_ptr, field->ptr + offset,
+ field->max_data_length());
+ DBUG_PRINT("debug", ("field: %s; real_type: %d, pack_ptr: 0x%lx;"
+ " pack_ptr':0x%lx; bytes: %d",
+ field->field_name, field->real_type(),
+ (ulong) old_pack_ptr, (ulong) pack_ptr,
+ (int) (pack_ptr - old_pack_ptr)));
+ DBUG_DUMP("packed_data", old_pack_ptr, pack_ptr - old_pack_ptr);
+ }
+
+ null_mask <<= 1;
+ if ((null_mask & 0xFF) == 0)
+ {
+ DBUG_ASSERT(null_ptr < row_data + null_byte_count);
+ null_mask = 1U;
+ *null_ptr++ = null_bits;
+ null_bits= (1U << 8) - 1;
+ }
+ }
+ }
+
+ /*
+ Write the last (partial) byte, if there is one
+ */
+ if ((null_mask & 0xFF) > 1)
+ {
+ DBUG_ASSERT(null_ptr < row_data + null_byte_count);
+ *null_ptr++ = null_bits;
+ }
+
+ /*
+ The null pointer should now point to the first byte of the
+ packed data. If it doesn't, something is very wrong.
+ */
+ DBUG_ASSERT(null_ptr == row_data + null_byte_count);
+ DBUG_DUMP("row_data", row_data, pack_ptr - row_data);
+ DBUG_RETURN(static_cast<size_t>(pack_ptr - row_data));
+}
+#endif
+
+
+/**
+ Unpack a row into @c table->record[0].
+
+ The function will always unpack into the @c table->record[0]
+ record. This is because there are too many dependencies on where
+ the various member functions of Field and subclasses expect to
+ write.
+
+ The row is assumed to only consist of the fields for which the
+ corresponding bit in bitset @c cols is set; the other parts of the
+ record are left alone.
+
+ At most @c colcnt columns are read: if the table is larger than
+ that, the remaining fields are not filled in.
+
+ @note The relay log information can be NULL, which means that no
+ checking or comparison with the source table is done, simply
+ because it is not used. This feature is used by MySQL Backup to
+ unpack a row from from the backup image, but can be used for other
+ purposes as well.
+
+ @param rli Relay log info, which can be NULL
+ @param table Table to unpack into
+ @param colcnt Number of columns to read from record
+ @param row_data
+ Packed row data
+ @param cols Pointer to bitset describing columns to fill in
+ @param curr_row_end
+ Pointer to variable that will hold the value of the
+ one-after-end position for the current row
+ @param master_reclength
+ Pointer to variable that will be set to the length of the
+ record on the master side
+ @param row_end
+ Pointer to variable that will hold the value of the
+ end position for the data in the row event
+
+ @retval 0 No error
+
+ @retval HA_ERR_GENERIC
+ A generic, internal, error caused the unpacking to fail.
+ @retval ER_SLAVE_CORRUPT_EVENT
+ Found error when trying to unpack fields.
+ */
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int
+unpack_row(rpl_group_info *rgi,
+ TABLE *table, uint const colcnt,
+ uchar const *const row_data, MY_BITMAP const *cols,
+ uchar const **const current_row_end, ulong *const master_reclength,
+ uchar const *const row_end)
+{
+ DBUG_ENTER("unpack_row");
+ DBUG_ASSERT(row_data);
+ DBUG_ASSERT(table);
+ size_t const master_null_byte_count= (bitmap_bits_set(cols) + 7) / 8;
+ int error= 0;
+
+ uchar const *null_ptr= row_data;
+ uchar const *pack_ptr= row_data + master_null_byte_count;
+
+ Field **const begin_ptr = table->field;
+ Field **field_ptr;
+ Field **const end_ptr= begin_ptr + colcnt;
+
+ DBUG_ASSERT(null_ptr < row_data + master_null_byte_count);
+
+ // Mask to mask out the correct bit among the null bits
+ unsigned int null_mask= 1U;
+ // The "current" null bits
+ unsigned int null_bits= *null_ptr++;
+ uint i= 0;
+ table_def *tabledef= NULL;
+ TABLE *conv_table= NULL;
+ bool table_found= rgi && rgi->get_table_data(table, &tabledef, &conv_table);
+ DBUG_PRINT("debug", ("Table data: table_found: %d, tabldef: %p, conv_table: %p",
+ table_found, tabledef, conv_table));
+ DBUG_ASSERT(table_found);
+
+ /*
+ If rgi is NULL it means that there is no source table and that the
+ row shall just be unpacked without doing any checks. This feature
+ is used by MySQL Backup, but can be used for other purposes as
+ well.
+ */
+ if (rgi && !table_found)
+ DBUG_RETURN(HA_ERR_GENERIC);
+
+ for (field_ptr= begin_ptr ; field_ptr < end_ptr && *field_ptr ; ++field_ptr)
+ {
+ /*
+ If there is a conversion table, we pick up the field pointer to
+ the conversion table. If the conversion table or the field
+ pointer is NULL, no conversions are necessary.
+ */
+ Field *conv_field=
+ conv_table ? conv_table->field[field_ptr - begin_ptr] : NULL;
+ Field *const f=
+ conv_field ? conv_field : *field_ptr;
+ DBUG_PRINT("debug", ("Conversion %srequired for field '%s' (#%ld)",
+ conv_field ? "" : "not ",
+ (*field_ptr)->field_name,
+ (long) (field_ptr - begin_ptr)));
+ DBUG_ASSERT(f != NULL);
+
+ /*
+ No need to bother about columns that does not exist: they have
+ gotten default values when being emptied above.
+ */
+ if (bitmap_is_set(cols, field_ptr - begin_ptr))
+ {
+ if ((null_mask & 0xFF) == 0)
+ {
+ DBUG_ASSERT(null_ptr < row_data + master_null_byte_count);
+ null_mask= 1U;
+ null_bits= *null_ptr++;
+ }
+
+ DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set
+
+ if (null_bits & null_mask)
+ {
+ if (f->maybe_null())
+ {
+ DBUG_PRINT("debug", ("Was NULL; null mask: 0x%x; null bits: 0x%x",
+ null_mask, null_bits));
+ /**
+ Calling reset just in case one is unpacking on top a
+ record with data.
+
+ This could probably go into set_null() but doing so,
+ (i) triggers assertion in other parts of the code at
+ the moment; (ii) it would make us reset the field,
+ always when setting null, which right now doesn't seem
+ needed anywhere else except here.
+
+ TODO: maybe in the future we should consider moving
+ the reset to make it part of set_null. But then
+ the assertions triggered need to be
+ addressed/revisited.
+ */
+ f->reset();
+ f->set_null();
+ }
+ else
+ {
+ f->set_default();
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_BAD_NULL_ERROR, ER(ER_BAD_NULL_ERROR),
+ f->field_name);
+ }
+ }
+ else
+ {
+ f->set_notnull();
+
+ /*
+ We only unpack the field if it was non-null.
+ Use the master's size information if available else call
+ normal unpack operation.
+ */
+ uint16 const metadata= tabledef->field_metadata(i);
+#ifndef DBUG_OFF
+ uchar const *const old_pack_ptr= pack_ptr;
+#endif
+ pack_ptr= f->unpack(f->ptr, pack_ptr, row_end, metadata);
+ DBUG_PRINT("debug", ("field: %s; metadata: 0x%x;"
+ " pack_ptr: 0x%lx; pack_ptr': 0x%lx; bytes: %d",
+ f->field_name, metadata,
+ (ulong) old_pack_ptr, (ulong) pack_ptr,
+ (int) (pack_ptr - old_pack_ptr)));
+ if (!pack_ptr)
+ {
+ rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT,
+ rgi->gtid_info(),
+ "Could not read field '%s' of table '%s.%s'",
+ f->field_name, table->s->db.str,
+ table->s->table_name.str);
+ DBUG_RETURN(ER_SLAVE_CORRUPT_EVENT);
+ }
+ }
+
+ /*
+ If conv_field is set, then we are doing a conversion. In this
+ case, we have unpacked the master data to the conversion
+ table, so we need to copy the value stored in the conversion
+ table into the final table and do the conversion at the same time.
+ */
+ if (conv_field)
+ {
+ Copy_field copy;
+#ifndef DBUG_OFF
+ char source_buf[MAX_FIELD_WIDTH];
+ char value_buf[MAX_FIELD_WIDTH];
+ String source_type(source_buf, sizeof(source_buf), system_charset_info);
+ String value_string(value_buf, sizeof(value_buf), system_charset_info);
+ conv_field->sql_type(source_type);
+ conv_field->val_str(&value_string);
+ DBUG_PRINT("debug", ("Copying field '%s' of type '%s' with value '%s'",
+ (*field_ptr)->field_name,
+ source_type.c_ptr_safe(), value_string.c_ptr_safe()));
+#endif
+ copy.set(*field_ptr, f, TRUE);
+ (*copy.do_copy)(&copy);
+#ifndef DBUG_OFF
+ char target_buf[MAX_FIELD_WIDTH];
+ String target_type(target_buf, sizeof(target_buf), system_charset_info);
+ (*field_ptr)->sql_type(target_type);
+ (*field_ptr)->val_str(&value_string);
+ DBUG_PRINT("debug", ("Value of field '%s' of type '%s' is now '%s'",
+ (*field_ptr)->field_name,
+ target_type.c_ptr_safe(), value_string.c_ptr_safe()));
+#endif
+ }
+
+ null_mask <<= 1;
+ }
+ i++;
+ }
+
+ /*
+ throw away master's extra fields
+ */
+ uint max_cols= MY_MIN(tabledef->size(), cols->n_bits);
+ for (; i < max_cols; i++)
+ {
+ if (bitmap_is_set(cols, i))
+ {
+ if ((null_mask & 0xFF) == 0)
+ {
+ DBUG_ASSERT(null_ptr < row_data + master_null_byte_count);
+ null_mask= 1U;
+ null_bits= *null_ptr++;
+ }
+ DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set
+
+ if (!((null_bits & null_mask) && tabledef->maybe_null(i))) {
+ uint32 len= tabledef->calc_field_size(i, (uchar *) pack_ptr);
+ DBUG_DUMP("field_data", pack_ptr, len);
+ pack_ptr+= len;
+ }
+ null_mask <<= 1;
+ }
+ }
+
+ /*
+ We should now have read all the null bytes, otherwise something is
+ really wrong.
+ */
+ DBUG_ASSERT(null_ptr == row_data + master_null_byte_count);
+
+ DBUG_DUMP("row_data", row_data, pack_ptr - row_data);
+
+ *current_row_end = pack_ptr;
+ if (master_reclength)
+ {
+ if (*field_ptr)
+ *master_reclength = (*field_ptr)->ptr - table->record[0];
+ else
+ *master_reclength = table->s->reclength;
+ }
+
+ DBUG_RETURN(error);
+}
+
+/**
+ Fills @c table->record[0] with default values.
+
+ First @c restore_record() is called to restore the default values for
+ record concerning the given table. Then, if @c check is true,
+ a check is performed to see if fields are have default value or can
+ be NULL. Otherwise error is reported.
+
+ @param table Table whose record[0] buffer is prepared.
+ @param skip Number of columns for which default/nullable check
+ should be skipped.
+ @param check Specifies if lack of default error needs checking.
+
+ @returns 0 on success or a handler level error code
+ */
+int prepare_record(TABLE *const table, const uint skip, const bool check)
+{
+ DBUG_ENTER("prepare_record");
+
+ restore_record(table, s->default_values);
+
+ /*
+ This skip should be revisited in 6.0, because in 6.0 RBR one
+ can have holes in the row (as the grain of the writeset is
+ the column and not the entire row).
+ */
+ if (skip >= table->s->fields || !check)
+ DBUG_RETURN(0);
+
+ /*
+ For fields the extra fields on the slave, we check if they have a default.
+ The check follows the same rules as the INSERT query without specifying an
+ explicit value for a field not having the explicit default
+ (@c check_that_all_fields_are_given_values()).
+ */
+ for (Field **field_ptr= table->field+skip; *field_ptr; ++field_ptr)
+ {
+ Field *const f= *field_ptr;
+ if ((f->flags & NO_DEFAULT_VALUE_FLAG) &&
+ (f->real_type() != MYSQL_TYPE_ENUM))
+ {
+ f->set_default();
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_NO_DEFAULT_FOR_FIELD,
+ ER(ER_NO_DEFAULT_FOR_FIELD),
+ f->field_name);
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+#endif // HAVE_REPLICATION
diff --git a/sql/rpl_record.h b/sql/rpl_record.h
new file mode 100644
index 00000000000..c10eb8225b0
--- /dev/null
+++ b/sql/rpl_record.h
@@ -0,0 +1,43 @@
+/* Copyright (c) 2007, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2013, SkySQL Ab.
+
+ 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 */
+
+#ifndef RPL_RECORD_H
+#define RPL_RECORD_H
+
+#include <rpl_reporting.h>
+#include "my_global.h" /* uchar */
+
+struct rpl_group_info;
+struct TABLE;
+typedef struct st_bitmap MY_BITMAP;
+
+#if !defined(MYSQL_CLIENT)
+size_t pack_row(TABLE* table, MY_BITMAP const* cols,
+ uchar *row_data, const uchar *data);
+#endif
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int unpack_row(rpl_group_info *rgi,
+ TABLE *table, uint const colcnt,
+ uchar const *const row_data, MY_BITMAP const *cols,
+ uchar const **const curr_row_end, ulong *const master_reclength,
+ uchar const *const row_end);
+
+// Fill table's record[0] with default values.
+int prepare_record(TABLE *const table, const uint skip, const bool check);
+#endif
+
+#endif
diff --git a/sql/rpl_record_old.cc b/sql/rpl_record_old.cc
new file mode 100644
index 00000000000..061fab78dbd
--- /dev/null
+++ b/sql/rpl_record_old.cc
@@ -0,0 +1,200 @@
+/* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED by other includes
+#include "rpl_rli.h"
+#include "rpl_record_old.h"
+#include "log_event.h" // Log_event_type
+
+size_t
+pack_row_old(TABLE *table, MY_BITMAP const* cols,
+ uchar *row_data, const uchar *record)
+{
+ Field **p_field= table->field, *field;
+ int n_null_bytes= table->s->null_bytes;
+ uchar *ptr;
+ uint i;
+ my_ptrdiff_t const rec_offset= record - table->record[0];
+ my_ptrdiff_t const def_offset= table->s->default_values - table->record[0];
+ memcpy(row_data, record, n_null_bytes);
+ ptr= row_data+n_null_bytes;
+
+ for (i= 0 ; (field= *p_field) ; i++, p_field++)
+ {
+ if (bitmap_is_set(cols,i))
+ {
+ my_ptrdiff_t const offset=
+ field->is_null(rec_offset) ? def_offset : rec_offset;
+ field->move_field_offset(offset);
+ ptr= field->pack(ptr, field->ptr);
+ field->move_field_offset(-offset);
+ }
+ }
+ return (static_cast<size_t>(ptr - row_data));
+}
+
+
+/*
+ Unpack a row into a record.
+
+ SYNOPSIS
+ unpack_row()
+ rli Relay log info
+ table Table to unpack into
+ colcnt Number of columns to read from record
+ record Record where the data should be unpacked
+ row Packed row data
+ cols Pointer to columns data to fill in
+ row_end Pointer to variable that will hold the value of the
+ one-after-end position for the row
+ master_reclength
+ Pointer to variable that will be set to the length of the
+ record on the master side
+ rw_set Pointer to bitmap that holds either the read_set or the
+ write_set of the table
+
+ DESCRIPTION
+
+ The row is assumed to only consist of the fields for which the
+ bitset represented by 'arr' and 'bits'; the other parts of the
+ record are left alone.
+
+ At most 'colcnt' columns are read: if the table is larger than
+ that, the remaining fields are not filled in.
+
+ RETURN VALUE
+
+ Error code, or zero if no error. The following error codes can
+ be returned:
+
+ ER_NO_DEFAULT_FOR_FIELD
+ Returned if one of the fields existing on the slave but not on
+ the master does not have a default value (and isn't nullable)
+ ER_SLAVE_CORRUPT_EVENT
+ Wrong data for field found.
+ */
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int
+unpack_row_old(rpl_group_info *rgi,
+ TABLE *table, uint const colcnt, uchar *record,
+ uchar const *row, const uchar *row_buffer_end,
+ MY_BITMAP const *cols,
+ uchar const **row_end, ulong *master_reclength,
+ MY_BITMAP* const rw_set, Log_event_type const event_type)
+{
+ DBUG_ASSERT(record && row);
+ my_ptrdiff_t const offset= record - (uchar*) table->record[0];
+ size_t master_null_bytes= table->s->null_bytes;
+
+ if (colcnt != table->s->fields)
+ {
+ Field **fptr= &table->field[colcnt-1];
+ do
+ master_null_bytes= (*fptr)->last_null_byte();
+ while (master_null_bytes == Field::LAST_NULL_BYTE_UNDEF &&
+ fptr-- > table->field);
+
+ /*
+ If master_null_bytes is LAST_NULL_BYTE_UNDEF (0) at this time,
+ there were no nullable fields nor BIT fields at all in the
+ columns that are common to the master and the slave. In that
+ case, there is only one null byte holding the X bit.
+
+ OBSERVE! There might still be nullable columns following the
+ common columns, so table->s->null_bytes might be greater than 1.
+ */
+ if (master_null_bytes == Field::LAST_NULL_BYTE_UNDEF)
+ master_null_bytes= 1;
+ }
+
+ DBUG_ASSERT(master_null_bytes <= table->s->null_bytes);
+ memcpy(record, row, master_null_bytes); // [1]
+ int error= 0;
+
+ bitmap_set_all(rw_set);
+
+ Field **const begin_ptr = table->field;
+ Field **field_ptr;
+ uchar const *ptr= row + master_null_bytes;
+ Field **const end_ptr= begin_ptr + colcnt;
+ for (field_ptr= begin_ptr ; field_ptr < end_ptr ; ++field_ptr)
+ {
+ Field *const f= *field_ptr;
+
+ if (bitmap_is_set(cols, field_ptr - begin_ptr))
+ {
+ f->move_field_offset(offset);
+ ptr= f->unpack(f->ptr, ptr, row_buffer_end, 0);
+ f->move_field_offset(-offset);
+ if (!ptr)
+ {
+ rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, NULL,
+ "Could not read field `%s` of table `%s`.`%s`",
+ f->field_name, table->s->db.str,
+ table->s->table_name.str);
+ return(ER_SLAVE_CORRUPT_EVENT);
+ }
+ }
+ else
+ bitmap_clear_bit(rw_set, field_ptr - begin_ptr);
+ }
+
+ *row_end = ptr;
+ if (master_reclength)
+ {
+ if (*field_ptr)
+ *master_reclength = (*field_ptr)->ptr - table->record[0];
+ else
+ *master_reclength = table->s->reclength;
+ }
+
+ /*
+ Set properties for remaining columns, if there are any. We let the
+ corresponding bit in the write_set be set, to write the value if
+ it was not there already. We iterate over all remaining columns,
+ even if there were an error, to get as many error messages as
+ possible. We are still able to return a pointer to the next row,
+ so redo that.
+
+ This generation of error messages is only relevant when inserting
+ new rows.
+ */
+ for ( ; *field_ptr ; ++field_ptr)
+ {
+ uint32 const mask= NOT_NULL_FLAG | NO_DEFAULT_VALUE_FLAG;
+
+ DBUG_PRINT("debug", ("flags = 0x%x, mask = 0x%x, flags & mask = 0x%x",
+ (*field_ptr)->flags, mask,
+ (*field_ptr)->flags & mask));
+
+ if (event_type == WRITE_ROWS_EVENT &&
+ ((*field_ptr)->flags & mask) == mask)
+ {
+ rgi->rli->report(ERROR_LEVEL, ER_NO_DEFAULT_FOR_FIELD, NULL,
+ "Field `%s` of table `%s`.`%s` "
+ "has no default value and cannot be NULL",
+ (*field_ptr)->field_name, table->s->db.str,
+ table->s->table_name.str);
+ error = ER_NO_DEFAULT_FOR_FIELD;
+ }
+ else
+ (*field_ptr)->set_default();
+ }
+
+ return error;
+}
+#endif
diff --git a/sql/rpl_record_old.h b/sql/rpl_record_old.h
new file mode 100644
index 00000000000..34ef9f11c47
--- /dev/null
+++ b/sql/rpl_record_old.h
@@ -0,0 +1,35 @@
+/* Copyright (c) 2007, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifndef RPL_RECORD_OLD_H
+#define RPL_RECORD_OLD_H
+
+#include "log_event.h" /* Log_event_type */
+
+#ifndef MYSQL_CLIENT
+size_t pack_row_old(TABLE *table, MY_BITMAP const* cols,
+ uchar *row_data, const uchar *record);
+
+#ifdef HAVE_REPLICATION
+int unpack_row_old(rpl_group_info *rgi,
+ TABLE *table, uint const colcnt, uchar *record,
+ uchar const *row, uchar const *row_buffer_end,
+ MY_BITMAP const *cols,
+ uchar const **row_end, ulong *master_reclength,
+ MY_BITMAP* const rw_set,
+ Log_event_type const event_type);
+#endif
+#endif
+#endif
diff --git a/sql/rpl_reporting.cc b/sql/rpl_reporting.cc
new file mode 100644
index 00000000000..49708df40f7
--- /dev/null
+++ b/sql/rpl_reporting.cc
@@ -0,0 +1,82 @@
+
+/* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "rpl_reporting.h"
+#include "log.h" // sql_print_error, sql_print_warning,
+ // sql_print_information
+
+Slave_reporting_capability::Slave_reporting_capability(char const *thread_name)
+ : m_thread_name(thread_name)
+{
+ mysql_mutex_init(key_mutex_slave_reporting_capability_err_lock,
+ &err_lock, MY_MUTEX_INIT_FAST);
+}
+
+void
+Slave_reporting_capability::report(loglevel level, int err_code,
+ const char *extra_info,
+ const char *msg, ...) const
+{
+ void (*report_function)(const char *, ...);
+ char buff[MAX_SLAVE_ERRMSG];
+ char *pbuff= buff;
+ uint pbuffsize= sizeof(buff);
+ va_list args;
+ va_start(args, msg);
+
+ mysql_mutex_lock(&err_lock);
+ switch (level)
+ {
+ case ERROR_LEVEL:
+ /*
+ It's an error, it must be reported in Last_error and Last_errno in SHOW
+ SLAVE STATUS.
+ */
+ pbuff= m_last_error.message;
+ pbuffsize= sizeof(m_last_error.message);
+ m_last_error.number = err_code;
+ report_function= sql_print_error;
+ break;
+ case WARNING_LEVEL:
+ report_function= sql_print_warning;
+ break;
+ case INFORMATION_LEVEL:
+ report_function= sql_print_information;
+ break;
+ default:
+ DBUG_ASSERT(0); // should not come here
+ return; // don't crash production builds, just do nothing
+ }
+
+ my_vsnprintf(pbuff, pbuffsize, msg, args);
+
+ mysql_mutex_unlock(&err_lock);
+ va_end(args);
+
+ /* If the msg string ends with '.', do not add a ',' it would be ugly */
+ report_function("Slave %s: %s%s %s%sInternal MariaDB error code: %d",
+ m_thread_name, pbuff,
+ (pbuff[0] && *(strend(pbuff)-1) == '.') ? "" : ",",
+ (extra_info ? extra_info : ""), (extra_info ? ", " : ""),
+ err_code);
+}
+
+Slave_reporting_capability::~Slave_reporting_capability()
+{
+ mysql_mutex_destroy(&err_lock);
+}
diff --git a/sql/rpl_reporting.h b/sql/rpl_reporting.h
new file mode 100644
index 00000000000..d90b7ad6650
--- /dev/null
+++ b/sql/rpl_reporting.h
@@ -0,0 +1,109 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef RPL_REPORTING_H
+#define RPL_REPORTING_H
+
+#include "my_sys.h" /* loglevel */
+
+/**
+ Maximum size of an error message from a slave thread.
+ */
+#define MAX_SLAVE_ERRMSG 1024
+
+/**
+ Mix-in to handle the message logging and reporting for relay log
+ info and master log info structures.
+
+ By inheriting from this class, the class is imbued with
+ capabilities to do slave reporting.
+ */
+class Slave_reporting_capability
+{
+public:
+ /** lock used to synchronize m_last_error on 'SHOW SLAVE STATUS' **/
+ mutable mysql_mutex_t err_lock;
+ /**
+ Constructor.
+
+ @param thread_name Printable name of the slave thread that is reporting.
+ */
+ Slave_reporting_capability(char const *thread_name);
+
+ /**
+ Writes a message and, if it's an error message, to Last_Error
+ (which will be displayed by SHOW SLAVE STATUS).
+
+ @param level The severity level
+ @param err_code The error code
+ @param msg The message (usually related to the error
+ code, but can contain more information), in
+ printf() format.
+ */
+ void report(loglevel level, int err_code, const char *extra_info,
+ const char *msg, ...) const
+ ATTRIBUTE_FORMAT(printf, 5, 6);
+
+ /**
+ Clear errors. They will not show up under <code>SHOW SLAVE
+ STATUS</code>.
+ */
+ void clear_error() {
+ mysql_mutex_lock(&err_lock);
+ m_last_error.clear();
+ mysql_mutex_unlock(&err_lock);
+ }
+
+ /**
+ Error information structure.
+ */
+ class Error {
+ friend class Slave_reporting_capability;
+ public:
+ Error()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ number= 0;
+ message[0]= '\0';
+ }
+
+ /** Error code */
+ uint32 number;
+ /** Error message */
+ char message[MAX_SLAVE_ERRMSG];
+ };
+
+ Error const& last_error() const { return m_last_error; }
+
+ virtual ~Slave_reporting_capability()= 0;
+private:
+ /**
+ Last error produced by the I/O or SQL thread respectively.
+ */
+ mutable Error m_last_error;
+
+ char const *const m_thread_name;
+
+ // not implemented
+ Slave_reporting_capability(const Slave_reporting_capability& rhs);
+ Slave_reporting_capability& operator=(const Slave_reporting_capability& rhs);
+};
+
+#endif // RPL_REPORTING_H
+
diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc
new file mode 100644
index 00000000000..9bd0ca55b01
--- /dev/null
+++ b/sql/rpl_rli.cc
@@ -0,0 +1,2002 @@
+/* Copyright (c) 2006, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2013, Monty Program Ab
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // HAVE_*
+#include "rpl_mi.h"
+#include "rpl_rli.h"
+#include "sql_base.h" // close_thread_tables
+#include <my_dir.h> // For MY_STAT
+#include "sql_repl.h" // For check_binlog_magic
+#include "log_event.h" // Format_description_log_event, Log_event,
+ // FORMAT_DESCRIPTION_LOG_EVENT, ROTATE_EVENT,
+ // PREFIX_SQL_LOAD
+#include "rpl_utility.h"
+#include "transaction.h"
+#include "sql_parse.h" // end_trans, ROLLBACK
+#include <mysql/plugin.h>
+#include <mysql/service_thd_wait.h>
+
+static int count_relay_log_space(Relay_log_info* rli);
+
+/**
+ Current replication state (hash of last GTID executed, per replication
+ domain).
+*/
+rpl_slave_state rpl_global_gtid_slave_state;
+/* Object used for MASTER_GTID_WAIT(). */
+gtid_waiting rpl_global_gtid_waiting;
+
+
+// Defined in slave.cc
+int init_intvar_from_file(int* var, IO_CACHE* f, int default_val);
+int init_strvar_from_file(char *var, int max_size, IO_CACHE *f,
+ const char *default_val);
+
+Relay_log_info::Relay_log_info(bool is_slave_recovery)
+ :Slave_reporting_capability("SQL"),
+ no_storage(FALSE), replicate_same_server_id(::replicate_same_server_id),
+ info_fd(-1), cur_log_fd(-1), relay_log(&sync_relaylog_period),
+ sync_counter(0), is_relay_log_recovery(is_slave_recovery),
+ save_temporary_tables(0), mi(0),
+ inuse_relaylog_list(0), last_inuse_relaylog(0),
+ cur_log_old_open_count(0), group_relay_log_pos(0),
+ event_relay_log_pos(0),
+#if HAVE_valgrind
+ is_fake(FALSE),
+#endif
+ group_master_log_pos(0), log_space_total(0), ignore_log_space_limit(0),
+ last_master_timestamp(0), sql_thread_caught_up(true), slave_skip_counter(0),
+ abort_pos_wait(0), slave_run_id(0), sql_driver_thd(),
+ gtid_skip_flag(GTID_SKIP_NOT), inited(0), abort_slave(0), stop_for_until(0),
+ slave_running(0), until_condition(UNTIL_NONE),
+ until_log_pos(0), retried_trans(0), executed_entries(0),
+ m_flags(0)
+{
+ DBUG_ENTER("Relay_log_info::Relay_log_info");
+
+ relay_log.is_relay_log= TRUE;
+#ifdef HAVE_PSI_INTERFACE
+ relay_log.set_psi_keys(key_RELAYLOG_LOCK_index,
+ key_RELAYLOG_update_cond,
+ key_file_relaylog,
+ key_file_relaylog_index,
+ key_RELAYLOG_COND_queue_busy);
+#endif
+
+ group_relay_log_name[0]= event_relay_log_name[0]=
+ group_master_log_name[0]= 0;
+ until_log_name[0]= ign_master_log_name_end[0]= 0;
+ max_relay_log_size= global_system_variables.max_relay_log_size;
+ bzero((char*) &info_file, sizeof(info_file));
+ bzero((char*) &cache_buf, sizeof(cache_buf));
+ mysql_mutex_init(key_relay_log_info_run_lock, &run_lock, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_relay_log_info_data_lock,
+ &data_lock, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_relay_log_info_log_space_lock,
+ &log_space_lock, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_relay_log_info_data_cond, &data_cond, NULL);
+ mysql_cond_init(key_relay_log_info_start_cond, &start_cond, NULL);
+ mysql_cond_init(key_relay_log_info_stop_cond, &stop_cond, NULL);
+ mysql_cond_init(key_relay_log_info_log_space_cond, &log_space_cond, NULL);
+ relay_log.init_pthread_objects();
+ DBUG_VOID_RETURN;
+}
+
+
+Relay_log_info::~Relay_log_info()
+{
+ DBUG_ENTER("Relay_log_info::~Relay_log_info");
+
+ reset_inuse_relaylog();
+ mysql_mutex_destroy(&run_lock);
+ mysql_mutex_destroy(&data_lock);
+ mysql_mutex_destroy(&log_space_lock);
+ mysql_cond_destroy(&data_cond);
+ mysql_cond_destroy(&start_cond);
+ mysql_cond_destroy(&stop_cond);
+ mysql_cond_destroy(&log_space_cond);
+ relay_log.cleanup();
+ DBUG_VOID_RETURN;
+}
+
+
+int init_relay_log_info(Relay_log_info* rli,
+ const char* info_fname)
+{
+ char fname[FN_REFLEN+128];
+ int info_fd;
+ const char* msg = 0;
+ int error = 0;
+ DBUG_ENTER("init_relay_log_info");
+ DBUG_ASSERT(!rli->no_storage); // Don't init if there is no storage
+
+ if (rli->inited) // Set if this function called
+ DBUG_RETURN(0);
+ fn_format(fname, info_fname, mysql_data_home, "", 4+32);
+ mysql_mutex_lock(&rli->data_lock);
+ info_fd = rli->info_fd;
+ rli->cur_log_fd = -1;
+ rli->slave_skip_counter=0;
+ rli->abort_pos_wait=0;
+ rli->log_space_limit= relay_log_space_limit;
+ rli->log_space_total= 0;
+
+ char pattern[FN_REFLEN];
+ (void) my_realpath(pattern, slave_load_tmpdir, 0);
+ if (fn_format(pattern, PREFIX_SQL_LOAD, pattern, "",
+ MY_SAFE_PATH | MY_RETURN_REAL_PATH) == NullS)
+ {
+ mysql_mutex_unlock(&rli->data_lock);
+ sql_print_error("Unable to use slave's temporary directory %s",
+ slave_load_tmpdir);
+ DBUG_RETURN(1);
+ }
+ unpack_filename(rli->slave_patternload_file, pattern);
+ rli->slave_patternload_file_size= strlen(rli->slave_patternload_file);
+
+ /*
+ The relay log will now be opened, as a SEQ_READ_APPEND IO_CACHE.
+ Note that the I/O thread flushes it to disk after writing every
+ event, in flush_master_info(mi, 1, ?).
+ */
+
+ {
+ /* Reports an error and returns, if the --relay-log's path
+ is a directory.*/
+ if (opt_relay_logname &&
+ opt_relay_logname[strlen(opt_relay_logname) - 1] == FN_LIBCHAR)
+ {
+ mysql_mutex_unlock(&rli->data_lock);
+ sql_print_error("Path '%s' is a directory name, please specify \
+a file name for --relay-log option", opt_relay_logname);
+ DBUG_RETURN(1);
+ }
+
+ /* Reports an error and returns, if the --relay-log-index's path
+ is a directory.*/
+ if (opt_relaylog_index_name &&
+ opt_relaylog_index_name[strlen(opt_relaylog_index_name) - 1]
+ == FN_LIBCHAR)
+ {
+ mysql_mutex_unlock(&rli->data_lock);
+ sql_print_error("Path '%s' is a directory name, please specify \
+a file name for --relay-log-index option", opt_relaylog_index_name);
+ DBUG_RETURN(1);
+ }
+
+ char buf[FN_REFLEN];
+ const char *ln;
+ static bool name_warning_sent= 0;
+ ln= rli->relay_log.generate_name(opt_relay_logname, "-relay-bin",
+ 1, buf);
+ /* We send the warning only at startup, not after every RESET SLAVE */
+ if (!opt_relay_logname && !opt_relaylog_index_name && !name_warning_sent &&
+ !opt_bootstrap)
+ {
+ /*
+ User didn't give us info to name the relay log index file.
+ Picking `hostname`-relay-bin.index like we do, causes replication to
+ fail if this slave's hostname is changed later. So, we would like to
+ instead require a name. But as we don't want to break many existing
+ setups, we only give warning, not error.
+ */
+ sql_print_warning("Neither --relay-log nor --relay-log-index were used;"
+ " so replication "
+ "may break when this MySQL server acts as a "
+ "slave and has his hostname changed!! Please "
+ "use '--log-basename=#' or '--relay-log=%s' to avoid "
+ "this problem.", ln);
+ name_warning_sent= 1;
+ }
+
+ /* For multimaster, add connection name to relay log filenames */
+ Master_info* mi= rli->mi;
+ char buf_relay_logname[FN_REFLEN], buf_relaylog_index_name_buff[FN_REFLEN];
+ char *buf_relaylog_index_name= opt_relaylog_index_name;
+
+ create_logfile_name_with_suffix(buf_relay_logname,
+ sizeof(buf_relay_logname),
+ ln, 1, &mi->cmp_connection_name);
+ ln= buf_relay_logname;
+
+ if (opt_relaylog_index_name)
+ {
+ buf_relaylog_index_name= buf_relaylog_index_name_buff;
+ create_logfile_name_with_suffix(buf_relaylog_index_name_buff,
+ sizeof(buf_relaylog_index_name_buff),
+ opt_relaylog_index_name, 0,
+ &mi->cmp_connection_name);
+ }
+
+ /*
+ note, that if open() fails, we'll still have index file open
+ but a destructor will take care of that
+ */
+ if (rli->relay_log.open_index_file(buf_relaylog_index_name, ln, TRUE) ||
+ rli->relay_log.open(ln, LOG_BIN, 0, SEQ_READ_APPEND,
+ mi->rli.max_relay_log_size, 1, TRUE))
+ {
+ mysql_mutex_unlock(&rli->data_lock);
+ sql_print_error("Failed when trying to open logs for '%s' in init_relay_log_info(). Error: %M", ln, my_errno);
+ DBUG_RETURN(1);
+ }
+ }
+
+ /* if file does not exist */
+ if (access(fname,F_OK))
+ {
+ /*
+ If someone removed the file from underneath our feet, just close
+ the old descriptor and re-create the old file
+ */
+ if (info_fd >= 0)
+ mysql_file_close(info_fd, MYF(MY_WME));
+ if ((info_fd= mysql_file_open(key_file_relay_log_info,
+ fname, O_CREAT|O_RDWR|O_BINARY, MYF(MY_WME))) < 0)
+ {
+ sql_print_error("Failed to create a new relay log info file (\
+file '%s', errno %d)", fname, my_errno);
+ msg= current_thd->get_stmt_da()->message();
+ goto err;
+ }
+ if (init_io_cache(&rli->info_file, info_fd, IO_SIZE*2, READ_CACHE, 0L,0,
+ MYF(MY_WME)))
+ {
+ sql_print_error("Failed to create a cache on relay log info file '%s'",
+ fname);
+ msg= current_thd->get_stmt_da()->message();
+ goto err;
+ }
+
+ /* Init relay log with first entry in the relay index file */
+ if (init_relay_log_pos(rli,NullS,BIN_LOG_HEADER_SIZE,0 /* no data lock */,
+ &msg, 0))
+ {
+ sql_print_error("Failed to open the relay log 'FIRST' (relay_log_pos 4)");
+ goto err;
+ }
+ rli->group_master_log_name[0]= 0;
+ rli->group_master_log_pos= 0;
+ rli->info_fd= info_fd;
+ }
+ else // file exists
+ {
+ if (info_fd >= 0)
+ reinit_io_cache(&rli->info_file, READ_CACHE, 0L,0,0);
+ else
+ {
+ int error=0;
+ if ((info_fd= mysql_file_open(key_file_relay_log_info,
+ fname, O_RDWR|O_BINARY, MYF(MY_WME))) < 0)
+ {
+ sql_print_error("\
+Failed to open the existing relay log info file '%s' (errno %d)",
+ fname, my_errno);
+ error= 1;
+ }
+ else if (init_io_cache(&rli->info_file, info_fd,
+ IO_SIZE*2, READ_CACHE, 0L, 0, MYF(MY_WME)))
+ {
+ sql_print_error("Failed to create a cache on relay log info file '%s'",
+ fname);
+ error= 1;
+ }
+ if (error)
+ {
+ if (info_fd >= 0)
+ mysql_file_close(info_fd, MYF(0));
+ rli->info_fd= -1;
+ rli->relay_log.close(LOG_CLOSE_INDEX | LOG_CLOSE_STOP_EVENT);
+ mysql_mutex_unlock(&rli->data_lock);
+ DBUG_RETURN(1);
+ }
+ }
+
+ rli->info_fd = info_fd;
+ int relay_log_pos, master_log_pos, lines;
+ char *first_non_digit;
+ /*
+ In MySQL 5.6, there is a MASTER_DELAY option to CHANGE MASTER. This is
+ not yet merged into MariaDB (as of 10.0.13). However, we detect the
+ presense of the new option in relay-log.info, as a placeholder for
+ possible later merge of the feature, and to maintain file format
+ compatibility with MySQL 5.6+.
+ */
+ int dummy_sql_delay;
+
+ /*
+ Starting from MySQL 5.6.x, relay-log.info has a new format.
+ Now, its first line contains the number of lines in the file.
+ By reading this number we can determine which version our master.info
+ comes from. We can't simply count the lines in the file, since
+ versions before 5.6.x could generate files with more lines than
+ needed. If first line doesn't contain a number, or if it
+ contains a number less than LINES_IN_RELAY_LOG_INFO_WITH_DELAY,
+ then the file is treated like a file from pre-5.6.x version.
+ There is no ambiguity when reading an old master.info: before
+ 5.6.x, the first line contained the binlog's name, which is
+ either empty or has an extension (contains a '.'), so can't be
+ confused with an integer.
+
+ So we're just reading first line and trying to figure which
+ version is this.
+ */
+
+ /*
+ The first row is temporarily stored in mi->master_log_name, if
+ it is line count and not binlog name (new format) it will be
+ overwritten by the second row later.
+ */
+ if (init_strvar_from_file(rli->group_relay_log_name,
+ sizeof(rli->group_relay_log_name),
+ &rli->info_file, ""))
+ {
+ msg="Error reading slave log configuration";
+ goto err;
+ }
+
+ lines= strtoul(rli->group_relay_log_name, &first_non_digit, 10);
+
+ if (rli->group_relay_log_name[0] != '\0' &&
+ *first_non_digit == '\0' &&
+ lines >= LINES_IN_RELAY_LOG_INFO_WITH_DELAY)
+ {
+ DBUG_PRINT("info", ("relay_log_info file is in new format."));
+ /* Seems to be new format => read relay log name from next line */
+ if (init_strvar_from_file(rli->group_relay_log_name,
+ sizeof(rli->group_relay_log_name),
+ &rli->info_file, ""))
+ {
+ msg="Error reading slave log configuration";
+ goto err;
+ }
+ }
+ else
+ DBUG_PRINT("info", ("relay_log_info file is in old format."));
+
+ if (init_intvar_from_file(&relay_log_pos,
+ &rli->info_file, BIN_LOG_HEADER_SIZE) ||
+ init_strvar_from_file(rli->group_master_log_name,
+ sizeof(rli->group_master_log_name),
+ &rli->info_file, "") ||
+ init_intvar_from_file(&master_log_pos, &rli->info_file, 0) ||
+ (lines >= LINES_IN_RELAY_LOG_INFO_WITH_DELAY &&
+ init_intvar_from_file(&dummy_sql_delay, &rli->info_file, 0)))
+ {
+ msg="Error reading slave log configuration";
+ goto err;
+ }
+
+ strmake_buf(rli->event_relay_log_name,rli->group_relay_log_name);
+ rli->group_relay_log_pos= rli->event_relay_log_pos= relay_log_pos;
+ rli->group_master_log_pos= master_log_pos;
+
+ if (rli->is_relay_log_recovery && init_recovery(rli->mi, &msg))
+ goto err;
+
+ if (init_relay_log_pos(rli,
+ rli->group_relay_log_name,
+ rli->group_relay_log_pos,
+ 0 /* no data lock*/,
+ &msg, 0))
+ {
+ char llbuf[22];
+ sql_print_error("Failed to open the relay log '%s' (relay_log_pos %s)",
+ rli->group_relay_log_name,
+ llstr(rli->group_relay_log_pos, llbuf));
+ goto err;
+ }
+ }
+
+#ifndef DBUG_OFF
+ {
+ char llbuf1[22], llbuf2[22];
+ DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%s rli->event_relay_log_pos=%s",
+ llstr(my_b_tell(rli->cur_log),llbuf1),
+ llstr(rli->event_relay_log_pos,llbuf2)));
+ DBUG_ASSERT(rli->event_relay_log_pos >= BIN_LOG_HEADER_SIZE);
+ DBUG_ASSERT(my_b_tell(rli->cur_log) == rli->event_relay_log_pos);
+ }
+#endif
+
+ /*
+ Now change the cache from READ to WRITE - must do this
+ before flush_relay_log_info
+ */
+ reinit_io_cache(&rli->info_file, WRITE_CACHE,0L,0,1);
+ if ((error= flush_relay_log_info(rli)))
+ {
+ msg= "Failed to flush relay log info file";
+ goto err;
+ }
+ if (count_relay_log_space(rli))
+ {
+ msg="Error counting relay log space";
+ goto err;
+ }
+ rli->inited= 1;
+ mysql_mutex_unlock(&rli->data_lock);
+ DBUG_RETURN(error);
+
+err:
+ sql_print_error("%s", msg);
+ end_io_cache(&rli->info_file);
+ if (info_fd >= 0)
+ mysql_file_close(info_fd, MYF(0));
+ rli->info_fd= -1;
+ rli->relay_log.close(LOG_CLOSE_INDEX | LOG_CLOSE_STOP_EVENT);
+ mysql_mutex_unlock(&rli->data_lock);
+ DBUG_RETURN(1);
+}
+
+
+static inline int add_relay_log(Relay_log_info* rli,LOG_INFO* linfo)
+{
+ MY_STAT s;
+ DBUG_ENTER("add_relay_log");
+ if (!mysql_file_stat(key_file_relaylog,
+ linfo->log_file_name, &s, MYF(0)))
+ {
+ sql_print_error("log %s listed in the index, but failed to stat",
+ linfo->log_file_name);
+ DBUG_RETURN(1);
+ }
+ rli->log_space_total += s.st_size;
+#ifndef DBUG_OFF
+ char buf[22];
+ DBUG_PRINT("info",("log_space_total: %s", llstr(rli->log_space_total,buf)));
+#endif
+ DBUG_RETURN(0);
+}
+
+
+static int count_relay_log_space(Relay_log_info* rli)
+{
+ LOG_INFO linfo;
+ DBUG_ENTER("count_relay_log_space");
+ rli->log_space_total= 0;
+ if (rli->relay_log.find_log_pos(&linfo, NullS, 1))
+ {
+ sql_print_error("Could not find first log while counting relay log space");
+ DBUG_RETURN(1);
+ }
+ do
+ {
+ if (add_relay_log(rli,&linfo))
+ DBUG_RETURN(1);
+ } while (!rli->relay_log.find_next_log(&linfo, 1));
+ /*
+ As we have counted everything, including what may have written in a
+ preceding write, we must reset bytes_written, or we may count some space
+ twice.
+ */
+ rli->relay_log.reset_bytes_written();
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Reset UNTIL condition for Relay_log_info
+
+ SYNOPSYS
+ clear_until_condition()
+ rli - Relay_log_info structure where UNTIL condition should be reset
+ */
+
+void Relay_log_info::clear_until_condition()
+{
+ DBUG_ENTER("clear_until_condition");
+
+ until_condition= Relay_log_info::UNTIL_NONE;
+ until_log_name[0]= 0;
+ until_log_pos= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Read the correct format description event for starting to replicate from
+ a given position in a relay log file.
+*/
+Format_description_log_event *
+read_relay_log_description_event(IO_CACHE *cur_log, ulonglong start_pos,
+ const char **errmsg)
+{
+ Log_event *ev;
+ Format_description_log_event *fdev;
+ bool found= false;
+
+ /*
+ By default the relay log is in binlog format 3 (4.0).
+ Even if format is 4, this will work enough to read the first event
+ (Format_desc) (remember that format 4 is just lenghtened compared to format
+ 3; format 3 is a prefix of format 4).
+ */
+ fdev= new Format_description_log_event(3);
+
+ while (!found)
+ {
+ Log_event_type typ;
+
+ /*
+ Read the possible Format_description_log_event; if position
+ was 4, no need, it will be read naturally.
+ */
+ DBUG_PRINT("info",("looking for a Format_description_log_event"));
+
+ if (my_b_tell(cur_log) >= start_pos)
+ break;
+
+ if (!(ev= Log_event::read_log_event(cur_log, 0, fdev,
+ opt_slave_sql_verify_checksum)))
+ {
+ DBUG_PRINT("info",("could not read event, cur_log->error=%d",
+ cur_log->error));
+ if (cur_log->error) /* not EOF */
+ {
+ *errmsg= "I/O error reading event at position 4";
+ delete fdev;
+ return NULL;
+ }
+ break;
+ }
+ typ= ev->get_type_code();
+ if (typ == FORMAT_DESCRIPTION_EVENT)
+ {
+ DBUG_PRINT("info",("found Format_description_log_event"));
+ delete fdev;
+ fdev= (Format_description_log_event*) ev;
+ /*
+ As ev was returned by read_log_event, it has passed is_valid(), so
+ my_malloc() in ctor worked, no need to check again.
+ */
+ /*
+ Ok, we found a Format_description event. But it is not sure that this
+ describes the whole relay log; indeed, one can have this sequence
+ (starting from position 4):
+ Format_desc (of slave)
+ Rotate (of master)
+ Format_desc (of master)
+ So the Format_desc which really describes the rest of the relay log
+ is the 3rd event (it can't be further than that, because we rotate
+ the relay log when we queue a Rotate event from the master).
+ But what describes the Rotate is the first Format_desc.
+ So what we do is:
+ go on searching for Format_description events, until you exceed the
+ position (argument 'pos') or until you find another event than Rotate
+ or Format_desc.
+ */
+ }
+ else
+ {
+ DBUG_PRINT("info",("found event of another type=%d", typ));
+ found= (typ != ROTATE_EVENT);
+ delete ev;
+ }
+ }
+ return fdev;
+}
+
+
+/*
+ Open the given relay log
+
+ SYNOPSIS
+ init_relay_log_pos()
+ rli Relay information (will be initialized)
+ log Name of relay log file to read from. NULL = First log
+ pos Position in relay log file
+ need_data_lock Set to 1 if this functions should do mutex locks
+ errmsg Store pointer to error message here
+ look_for_description_event
+ 1 if we should look for such an event. We only need
+ this when the SQL thread starts and opens an existing
+ relay log and has to execute it (possibly from an
+ offset >4); then we need to read the first event of
+ the relay log to be able to parse the events we have
+ to execute.
+
+ DESCRIPTION
+ - Close old open relay log files.
+ - If we are using the same relay log as the running IO-thread, then set
+ rli->cur_log to point to the same IO_CACHE entry.
+ - If not, open the 'log' binary file.
+
+ TODO
+ - check proper initialization of group_master_log_name/group_master_log_pos
+
+ RETURN VALUES
+ 0 ok
+ 1 error. errmsg is set to point to the error message
+*/
+
+int init_relay_log_pos(Relay_log_info* rli,const char* log,
+ ulonglong pos, bool need_data_lock,
+ const char** errmsg,
+ bool look_for_description_event)
+{
+ DBUG_ENTER("init_relay_log_pos");
+ DBUG_PRINT("info", ("pos: %lu", (ulong) pos));
+
+ *errmsg=0;
+ mysql_mutex_t *log_lock= rli->relay_log.get_log_lock();
+
+ if (need_data_lock)
+ mysql_mutex_lock(&rli->data_lock);
+
+ /*
+ Slave threads are not the only users of init_relay_log_pos(). CHANGE MASTER
+ is, too, and init_slave() too; these 2 functions allocate a description
+ event in init_relay_log_pos, which is not freed by the terminating SQL slave
+ thread as that thread is not started by these functions. So we have to free
+ the description_event here, in case, so that there is no memory leak in
+ running, say, CHANGE MASTER.
+ */
+ delete rli->relay_log.description_event_for_exec;
+ /*
+ By default the relay log is in binlog format 3 (4.0).
+ Even if format is 4, this will work enough to read the first event
+ (Format_desc) (remember that format 4 is just lenghtened compared to format
+ 3; format 3 is a prefix of format 4).
+ */
+ rli->relay_log.description_event_for_exec= new
+ Format_description_log_event(3);
+
+ mysql_mutex_lock(log_lock);
+
+ /* Close log file and free buffers if it's already open */
+ if (rli->cur_log_fd >= 0)
+ {
+ end_io_cache(&rli->cache_buf);
+ mysql_file_close(rli->cur_log_fd, MYF(MY_WME));
+ rli->cur_log_fd = -1;
+ }
+
+ rli->group_relay_log_pos = rli->event_relay_log_pos = pos;
+ rli->clear_flag(Relay_log_info::IN_STMT);
+ rli->clear_flag(Relay_log_info::IN_TRANSACTION);
+
+ /*
+ Test to see if the previous run was with the skip of purging
+ If yes, we do not purge when we restart
+ */
+ if (rli->relay_log.find_log_pos(&rli->linfo, NullS, 1))
+ {
+ *errmsg="Could not find first log during relay log initialization";
+ goto err;
+ }
+
+ if (log && rli->relay_log.find_log_pos(&rli->linfo, log, 1))
+ {
+ *errmsg="Could not find target log during relay log initialization";
+ goto err;
+ }
+ strmake_buf(rli->group_relay_log_name,rli->linfo.log_file_name);
+ strmake_buf(rli->event_relay_log_name,rli->linfo.log_file_name);
+ if (rli->relay_log.is_active(rli->linfo.log_file_name))
+ {
+ /*
+ The IO thread is using this log file.
+ In this case, we will use the same IO_CACHE pointer to
+ read data as the IO thread is using to write data.
+ */
+ my_b_seek((rli->cur_log=rli->relay_log.get_log_file()), (off_t)0);
+ if (check_binlog_magic(rli->cur_log,errmsg))
+ goto err;
+ rli->cur_log_old_open_count=rli->relay_log.get_open_count();
+ }
+ else
+ {
+ /*
+ Open the relay log and set rli->cur_log to point at this one
+ */
+ if ((rli->cur_log_fd=open_binlog(&rli->cache_buf,
+ rli->linfo.log_file_name,errmsg)) < 0)
+ goto err;
+ rli->cur_log = &rli->cache_buf;
+ }
+ /*
+ In all cases, check_binlog_magic() has been called so we're at offset 4 for
+ sure.
+ */
+ if (pos > BIN_LOG_HEADER_SIZE) /* If pos<=4, we stay at 4 */
+ {
+ if (look_for_description_event)
+ {
+ Format_description_log_event *fdev;
+ if (!(fdev= read_relay_log_description_event(rli->cur_log, pos, errmsg)))
+ goto err;
+ delete rli->relay_log.description_event_for_exec;
+ rli->relay_log.description_event_for_exec= fdev;
+ }
+ my_b_seek(rli->cur_log,(off_t)pos);
+#ifndef DBUG_OFF
+ {
+ char llbuf1[22], llbuf2[22];
+ DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%s rli->event_relay_log_pos=%s",
+ llstr(my_b_tell(rli->cur_log),llbuf1),
+ llstr(rli->event_relay_log_pos,llbuf2)));
+ }
+#endif
+
+ }
+
+err:
+ /*
+ If we don't purge, we can't honour relay_log_space_limit ;
+ silently discard it
+ */
+ if (!relay_log_purge)
+ rli->log_space_limit= 0;
+ mysql_cond_broadcast(&rli->data_cond);
+
+ mysql_mutex_unlock(log_lock);
+
+ if (need_data_lock)
+ mysql_mutex_unlock(&rli->data_lock);
+ if (!rli->relay_log.description_event_for_exec->is_valid() && !*errmsg)
+ *errmsg= "Invalid Format_description log event; could be out of memory";
+
+ DBUG_RETURN ((*errmsg) ? 1 : 0);
+}
+
+
+/*
+ Waits until the SQL thread reaches (has executed up to) the
+ log/position or timed out.
+
+ SYNOPSIS
+ wait_for_pos()
+ thd client thread that sent SELECT MASTER_POS_WAIT
+ log_name log name to wait for
+ log_pos position to wait for
+ timeout timeout in seconds before giving up waiting
+
+ NOTES
+ timeout is longlong whereas it should be ulong ; but this is
+ to catch if the user submitted a negative timeout.
+
+ RETURN VALUES
+ -2 improper arguments (log_pos<0)
+ or slave not running, or master info changed
+ during the function's execution,
+ or client thread killed. -2 is translated to NULL by caller
+ -1 timed out
+ >=0 number of log events the function had to wait
+ before reaching the desired log/position
+ */
+
+int Relay_log_info::wait_for_pos(THD* thd, String* log_name,
+ longlong log_pos,
+ longlong timeout)
+{
+ int event_count = 0;
+ ulong init_abort_pos_wait;
+ int error=0;
+ struct timespec abstime; // for timeout checking
+ PSI_stage_info old_stage;
+ DBUG_ENTER("Relay_log_info::wait_for_pos");
+
+ if (!inited)
+ DBUG_RETURN(-2);
+
+ DBUG_PRINT("enter",("log_name: '%s' log_pos: %lu timeout: %lu",
+ log_name->c_ptr(), (ulong) log_pos, (ulong) timeout));
+
+ set_timespec(abstime,timeout);
+ mysql_mutex_lock(&data_lock);
+ thd->ENTER_COND(&data_cond, &data_lock,
+ &stage_waiting_for_the_slave_thread_to_advance_position,
+ &old_stage);
+ /*
+ This function will abort when it notices that some CHANGE MASTER or
+ RESET MASTER has changed the master info.
+ To catch this, these commands modify abort_pos_wait ; We just monitor
+ abort_pos_wait and see if it has changed.
+ Why do we have this mechanism instead of simply monitoring slave_running
+ in the loop (we do this too), as CHANGE MASTER/RESET SLAVE require that
+ the SQL thread be stopped?
+ This is becasue if someones does:
+ STOP SLAVE;CHANGE MASTER/RESET SLAVE; START SLAVE;
+ the change may happen very quickly and we may not notice that
+ slave_running briefly switches between 1/0/1.
+ */
+ init_abort_pos_wait= abort_pos_wait;
+
+ /*
+ We'll need to
+ handle all possible log names comparisons (e.g. 999 vs 1000).
+ We use ulong for string->number conversion ; this is no
+ stronger limitation than in find_uniq_filename in sql/log.cc
+ */
+ ulong log_name_extension;
+ char log_name_tmp[FN_REFLEN]; //make a char[] from String
+
+ strmake(log_name_tmp, log_name->ptr(), MY_MIN(log_name->length(), FN_REFLEN-1));
+
+ char *p= fn_ext(log_name_tmp);
+ char *p_end;
+ if (!*p || log_pos<0)
+ {
+ error= -2; //means improper arguments
+ goto err;
+ }
+ // Convert 0-3 to 4
+ log_pos= MY_MAX(log_pos, BIN_LOG_HEADER_SIZE);
+ /* p points to '.' */
+ log_name_extension= strtoul(++p, &p_end, 10);
+ /*
+ p_end points to the first invalid character.
+ If it equals to p, no digits were found, error.
+ If it contains '\0' it means conversion went ok.
+ */
+ if (p_end==p || *p_end)
+ {
+ error= -2;
+ goto err;
+ }
+
+ /* The "compare and wait" main loop */
+ while (!thd->killed &&
+ init_abort_pos_wait == abort_pos_wait &&
+ slave_running)
+ {
+ bool pos_reached;
+ int cmp_result= 0;
+
+ DBUG_PRINT("info",
+ ("init_abort_pos_wait: %ld abort_pos_wait: %ld",
+ init_abort_pos_wait, abort_pos_wait));
+ DBUG_PRINT("info",("group_master_log_name: '%s' pos: %lu",
+ group_master_log_name, (ulong) group_master_log_pos));
+
+ /*
+ group_master_log_name can be "", if we are just after a fresh
+ replication start or after a CHANGE MASTER TO MASTER_HOST/PORT
+ (before we have executed one Rotate event from the master) or
+ (rare) if the user is doing a weird slave setup (see next
+ paragraph). If group_master_log_name is "", we assume we don't
+ have enough info to do the comparison yet, so we just wait until
+ more data. In this case master_log_pos is always 0 except if
+ somebody (wrongly) sets this slave to be a slave of itself
+ without using --replicate-same-server-id (an unsupported
+ configuration which does nothing), then group_master_log_pos
+ will grow and group_master_log_name will stay "".
+ */
+ if (*group_master_log_name)
+ {
+ char *basename= (group_master_log_name +
+ dirname_length(group_master_log_name));
+ /*
+ First compare the parts before the extension.
+ Find the dot in the master's log basename,
+ and protect against user's input error :
+ if the names do not match up to '.' included, return error
+ */
+ char *q= (char*)(fn_ext(basename)+1);
+ if (strncmp(basename, log_name_tmp, (int)(q-basename)))
+ {
+ error= -2;
+ break;
+ }
+ // Now compare extensions.
+ char *q_end;
+ ulong group_master_log_name_extension= strtoul(q, &q_end, 10);
+ if (group_master_log_name_extension < log_name_extension)
+ cmp_result= -1 ;
+ else
+ cmp_result= (group_master_log_name_extension > log_name_extension) ? 1 : 0 ;
+
+ pos_reached= ((!cmp_result && group_master_log_pos >= (ulonglong)log_pos) ||
+ cmp_result > 0);
+ if (pos_reached || thd->killed)
+ break;
+ }
+
+ //wait for master update, with optional timeout.
+
+ DBUG_PRINT("info",("Waiting for master update"));
+ /*
+ We are going to mysql_cond_(timed)wait(); if the SQL thread stops it
+ will wake us up.
+ */
+ thd_wait_begin(thd, THD_WAIT_BINLOG);
+ if (timeout > 0)
+ {
+ /*
+ Note that mysql_cond_timedwait checks for the timeout
+ before for the condition ; i.e. it returns ETIMEDOUT
+ if the system time equals or exceeds the time specified by abstime
+ before the condition variable is signaled or broadcast, _or_ if
+ the absolute time specified by abstime has already passed at the time
+ of the call.
+ For that reason, mysql_cond_timedwait will do the "timeoutting" job
+ even if its condition is always immediately signaled (case of a loaded
+ master).
+ */
+ error= mysql_cond_timedwait(&data_cond, &data_lock, &abstime);
+ }
+ else
+ mysql_cond_wait(&data_cond, &data_lock);
+ thd_wait_end(thd);
+ DBUG_PRINT("info",("Got signal of master update or timed out"));
+ if (error == ETIMEDOUT || error == ETIME)
+ {
+ error= -1;
+ break;
+ }
+ error=0;
+ event_count++;
+ DBUG_PRINT("info",("Testing if killed or SQL thread not running"));
+ }
+
+err:
+ thd->EXIT_COND(&old_stage);
+ DBUG_PRINT("exit",("killed: %d abort: %d slave_running: %d \
+improper_arguments: %d timed_out: %d",
+ thd->killed_errno(),
+ (int) (init_abort_pos_wait != abort_pos_wait),
+ (int) slave_running,
+ (int) (error == -2),
+ (int) (error == -1)));
+ if (thd->killed || init_abort_pos_wait != abort_pos_wait ||
+ !slave_running)
+ {
+ error= -2;
+ }
+ DBUG_RETURN( error ? error : event_count );
+}
+
+
+void Relay_log_info::inc_group_relay_log_pos(ulonglong log_pos,
+ rpl_group_info *rgi,
+ bool skip_lock)
+{
+ DBUG_ENTER("Relay_log_info::inc_group_relay_log_pos");
+
+ if (!skip_lock)
+ mysql_mutex_lock(&data_lock);
+ rgi->inc_event_relay_log_pos();
+ DBUG_PRINT("info", ("log_pos: %lu group_master_log_pos: %lu",
+ (long) log_pos, (long) group_master_log_pos));
+ if (rgi->is_parallel_exec)
+ {
+ /* In case of parallel replication, do not update the position backwards. */
+ int cmp= strcmp(group_relay_log_name, rgi->event_relay_log_name);
+ if (cmp < 0)
+ {
+ group_relay_log_pos= rgi->future_event_relay_log_pos;
+ strmake_buf(group_relay_log_name, rgi->event_relay_log_name);
+ notify_group_relay_log_name_update();
+ } else if (cmp == 0 && group_relay_log_pos < rgi->future_event_relay_log_pos)
+ group_relay_log_pos= rgi->future_event_relay_log_pos;
+
+ /*
+ In the parallel case we need to update the master_log_name here, rather
+ than in Rotate_log_event::do_update_pos().
+ */
+ cmp= strcmp(group_master_log_name, rgi->future_event_master_log_name);
+ if (cmp <= 0)
+ {
+ if (cmp < 0)
+ {
+ strcpy(group_master_log_name, rgi->future_event_master_log_name);
+ notify_group_master_log_name_update();
+ group_master_log_pos= log_pos;
+ }
+ else if (group_master_log_pos < log_pos)
+ group_master_log_pos= log_pos;
+ }
+ }
+ else
+ {
+ /* Non-parallel case. */
+ group_relay_log_pos= event_relay_log_pos;
+ strmake_buf(group_relay_log_name, event_relay_log_name);
+ notify_group_relay_log_name_update();
+ if (log_pos) // 3.23 binlogs don't have log_posx
+ group_master_log_pos= log_pos;
+ }
+
+ /*
+ If the slave does not support transactions and replicates a transaction,
+ users should not trust group_master_log_pos (which they can display with
+ SHOW SLAVE STATUS or read from relay-log.info), because to compute
+ group_master_log_pos the slave relies on log_pos stored in the master's
+ binlog, but if we are in a master's transaction these positions are always
+ the BEGIN's one (excepted for the COMMIT), so group_master_log_pos does
+ not advance as it should on the non-transactional slave (it advances by
+ big leaps, whereas it should advance by small leaps).
+ */
+ /*
+ In 4.x we used the event's len to compute the positions here. This is
+ wrong if the event was 3.23/4.0 and has been converted to 5.0, because
+ then the event's len is not what is was in the master's binlog, so this
+ will make a wrong group_master_log_pos (yes it's a bug in 3.23->4.0
+ replication: Exec_master_log_pos is wrong). Only way to solve this is to
+ have the original offset of the end of the event the relay log. This is
+ what we do in 5.0: log_pos has become "end_log_pos" (because the real use
+ of log_pos in 4.0 was to compute the end_log_pos; so better to store
+ end_log_pos instead of begin_log_pos.
+ If we had not done this fix here, the problem would also have appeared
+ when the slave and master are 5.0 but with different event length (for
+ example the slave is more recent than the master and features the event
+ UID). It would give false MASTER_POS_WAIT, false Exec_master_log_pos in
+ SHOW SLAVE STATUS, and so the user would do some CHANGE MASTER using this
+ value which would lead to badly broken replication.
+ Even the relay_log_pos will be corrupted in this case, because the len is
+ the relay log is not "val".
+ With the end_log_pos solution, we avoid computations involving lengthes.
+ */
+ mysql_cond_broadcast(&data_cond);
+ if (!skip_lock)
+ mysql_mutex_unlock(&data_lock);
+ DBUG_VOID_RETURN;
+}
+
+
+void Relay_log_info::close_temporary_tables()
+{
+ TABLE *table,*next;
+ DBUG_ENTER("Relay_log_info::close_temporary_tables");
+
+ for (table=save_temporary_tables ; table ; table=next)
+ {
+ next=table->next;
+
+ /* Reset in_use as the table may have been created by another thd */
+ table->in_use=0;
+ /*
+ Don't ask for disk deletion. For now, anyway they will be deleted when
+ slave restarts, but it is a better intention to not delete them.
+ */
+ DBUG_PRINT("info", ("table: 0x%lx", (long) table));
+ close_temporary(table, 1, 0);
+ }
+ save_temporary_tables= 0;
+ slave_open_temp_tables= 0;
+ DBUG_VOID_RETURN;
+}
+
+/*
+ purge_relay_logs()
+
+ NOTES
+ Assumes to have a run lock on rli and that no slave thread are running.
+*/
+
+int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset,
+ const char** errmsg)
+{
+ int error=0;
+ DBUG_ENTER("purge_relay_logs");
+
+ /*
+ Even if rli->inited==0, we still try to empty rli->master_log_* variables.
+ Indeed, rli->inited==0 does not imply that they already are empty.
+ It could be that slave's info initialization partly succeeded :
+ for example if relay-log.info existed but *relay-bin*.*
+ have been manually removed, init_relay_log_info reads the old
+ relay-log.info and fills rli->master_log_*, then init_relay_log_info
+ checks for the existence of the relay log, this fails and
+ init_relay_log_info leaves rli->inited to 0.
+ In that pathological case, rli->master_log_pos* will be properly reinited
+ at the next START SLAVE (as RESET SLAVE or CHANGE
+ MASTER, the callers of purge_relay_logs, will delete bogus *.info files
+ or replace them with correct files), however if the user does SHOW SLAVE
+ STATUS before START SLAVE, he will see old, confusing rli->master_log_*.
+ In other words, we reinit rli->master_log_* for SHOW SLAVE STATUS
+ to display fine in any case.
+ */
+
+ rli->group_master_log_name[0]= 0;
+ rli->group_master_log_pos= 0;
+
+ if (!rli->inited)
+ {
+ DBUG_PRINT("info", ("rli->inited == 0"));
+ DBUG_RETURN(0);
+ }
+
+ DBUG_ASSERT(rli->slave_running == 0);
+ DBUG_ASSERT(rli->mi->slave_running == 0);
+
+ mysql_mutex_lock(&rli->data_lock);
+
+ /*
+ we close the relay log fd possibly left open by the slave SQL thread,
+ to be able to delete it; the relay log fd possibly left open by the slave
+ I/O thread will be closed naturally in reset_logs() by the
+ close(LOG_CLOSE_TO_BE_OPENED) call
+ */
+ if (rli->cur_log_fd >= 0)
+ {
+ end_io_cache(&rli->cache_buf);
+ mysql_file_close(rli->cur_log_fd, MYF(MY_WME));
+ rli->cur_log_fd= -1;
+ }
+
+ if (rli->relay_log.reset_logs(thd, !just_reset, NULL, 0))
+ {
+ *errmsg = "Failed during log reset";
+ error=1;
+ goto err;
+ }
+ if (!just_reset)
+ {
+ /* Save name of used relay log file */
+ strmake_buf(rli->group_relay_log_name, rli->relay_log.get_log_fname());
+ strmake_buf(rli->event_relay_log_name, rli->relay_log.get_log_fname());
+ rli->group_relay_log_pos= rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE;
+ rli->log_space_total= 0;
+
+ if (count_relay_log_space(rli))
+ {
+ *errmsg= "Error counting relay log space";
+ error=1;
+ goto err;
+ }
+ error= init_relay_log_pos(rli, rli->group_relay_log_name,
+ rli->group_relay_log_pos,
+ 0 /* do not need data lock */, errmsg, 0);
+ }
+ else
+ {
+ /* Ensure relay log names are not used */
+ rli->group_relay_log_name[0]= rli->event_relay_log_name[0]= 0;
+ }
+
+err:
+#ifndef DBUG_OFF
+ char buf[22];
+#endif
+ DBUG_PRINT("info",("log_space_total: %s",llstr(rli->log_space_total,buf)));
+ mysql_mutex_unlock(&rli->data_lock);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Check if condition stated in UNTIL clause of START SLAVE is reached.
+ SYNOPSYS
+ Relay_log_info::is_until_satisfied()
+ master_beg_pos position of the beginning of to be executed event
+ (not log_pos member of the event that points to the
+ beginning of the following event)
+
+
+ DESCRIPTION
+ Checks if UNTIL condition is reached. Uses caching result of last
+ comparison of current log file name and target log file name. So cached
+ value should be invalidated if current log file name changes
+ (see Relay_log_info::notify_... functions).
+
+ This caching is needed to avoid of expensive string comparisons and
+ strtol() conversions needed for log names comparison. We don't need to
+ compare them each time this function is called, we only need to do this
+ when current log name changes. If we have UNTIL_MASTER_POS condition we
+ need to do this only after Rotate_log_event::do_apply_event() (which is
+ rare, so caching gives real benifit), and if we have UNTIL_RELAY_POS
+ condition then we should invalidate cached comarison value after
+ inc_group_relay_log_pos() which called for each group of events (so we
+ have some benefit if we have something like queries that use
+ autoincrement or if we have transactions).
+
+ Should be called ONLY if until_condition != UNTIL_NONE !
+ RETURN VALUE
+ true - condition met or error happened (condition seems to have
+ bad log file name)
+ false - condition not met
+*/
+
+bool Relay_log_info::is_until_satisfied(THD *thd, Log_event *ev)
+{
+ const char *log_name;
+ ulonglong log_pos;
+ DBUG_ENTER("Relay_log_info::is_until_satisfied");
+
+ DBUG_ASSERT(until_condition == UNTIL_MASTER_POS ||
+ until_condition == UNTIL_RELAY_POS);
+
+ if (until_condition == UNTIL_MASTER_POS)
+ {
+ if (ev && ev->server_id == (uint32) global_system_variables.server_id &&
+ !replicate_same_server_id)
+ DBUG_RETURN(FALSE);
+ log_name= group_master_log_name;
+ log_pos= ((!ev)? group_master_log_pos :
+ (get_flag(IN_TRANSACTION) || !ev->log_pos) ?
+ group_master_log_pos : ev->log_pos - ev->data_written);
+ }
+ else
+ { /* until_condition == UNTIL_RELAY_POS */
+ log_name= group_relay_log_name;
+ log_pos= group_relay_log_pos;
+ }
+
+#ifndef DBUG_OFF
+ {
+ char buf[32];
+ DBUG_PRINT("info", ("group_master_log_name='%s', group_master_log_pos=%s",
+ group_master_log_name, llstr(group_master_log_pos, buf)));
+ DBUG_PRINT("info", ("group_relay_log_name='%s', group_relay_log_pos=%s",
+ group_relay_log_name, llstr(group_relay_log_pos, buf)));
+ DBUG_PRINT("info", ("(%s) log_name='%s', log_pos=%s",
+ until_condition == UNTIL_MASTER_POS ? "master" : "relay",
+ log_name, llstr(log_pos, buf)));
+ DBUG_PRINT("info", ("(%s) until_log_name='%s', until_log_pos=%s",
+ until_condition == UNTIL_MASTER_POS ? "master" : "relay",
+ until_log_name, llstr(until_log_pos, buf)));
+ }
+#endif
+
+ if (until_log_names_cmp_result == UNTIL_LOG_NAMES_CMP_UNKNOWN)
+ {
+ /*
+ We have no cached comparison results so we should compare log names
+ and cache result.
+ If we are after RESET SLAVE, and the SQL slave thread has not processed
+ any event yet, it could be that group_master_log_name is "". In that case,
+ just wait for more events (as there is no sensible comparison to do).
+ */
+
+ if (*log_name)
+ {
+ const char *basename= log_name + dirname_length(log_name);
+
+ const char *q= (const char*)(fn_ext(basename)+1);
+ if (strncmp(basename, until_log_name, (int)(q-basename)) == 0)
+ {
+ /* Now compare extensions. */
+ char *q_end;
+ ulong log_name_extension= strtoul(q, &q_end, 10);
+ if (log_name_extension < until_log_name_extension)
+ until_log_names_cmp_result= UNTIL_LOG_NAMES_CMP_LESS;
+ else
+ until_log_names_cmp_result=
+ (log_name_extension > until_log_name_extension) ?
+ UNTIL_LOG_NAMES_CMP_GREATER : UNTIL_LOG_NAMES_CMP_EQUAL ;
+ }
+ else
+ {
+ /* Probably error so we aborting */
+ sql_print_error("Slave SQL thread is stopped because UNTIL "
+ "condition is bad.");
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else
+ DBUG_RETURN(until_log_pos == 0);
+ }
+
+ DBUG_RETURN(((until_log_names_cmp_result == UNTIL_LOG_NAMES_CMP_EQUAL &&
+ log_pos >= until_log_pos) ||
+ until_log_names_cmp_result == UNTIL_LOG_NAMES_CMP_GREATER));
+}
+
+
+void Relay_log_info::stmt_done(my_off_t event_master_log_pos, THD *thd,
+ rpl_group_info *rgi)
+{
+ DBUG_ENTER("Relay_log_info::stmt_done");
+
+ DBUG_ASSERT(rgi->rli == this);
+ /*
+ If in a transaction, and if the slave supports transactions, just
+ inc_event_relay_log_pos(). We only have to check for OPTION_BEGIN
+ (not OPTION_NOT_AUTOCOMMIT) as transactions are logged with
+ BEGIN/COMMIT, not with SET AUTOCOMMIT= .
+
+ We can't use rgi->rli->get_flag(IN_TRANSACTION) here as OPTION_BEGIN
+ is also used for single row transactions.
+
+ CAUTION: opt_using_transactions means innodb || bdb ; suppose the
+ master supports InnoDB and BDB, but the slave supports only BDB,
+ problems will arise: - suppose an InnoDB table is created on the
+ master, - then it will be MyISAM on the slave - but as
+ opt_using_transactions is true, the slave will believe he is
+ transactional with the MyISAM table. And problems will come when
+ one does START SLAVE; STOP SLAVE; START SLAVE; (the slave will
+ resume at BEGIN whereas there has not been any rollback). This is
+ the problem of using opt_using_transactions instead of a finer
+ "does the slave support _transactional handler used on the
+ master_".
+
+ More generally, we'll have problems when a query mixes a
+ transactional handler and MyISAM and STOP SLAVE is issued in the
+ middle of the "transaction". START SLAVE will resume at BEGIN
+ while the MyISAM table has already been updated.
+ */
+ if ((rgi->thd->variables.option_bits & OPTION_BEGIN) &&
+ opt_using_transactions)
+ rgi->inc_event_relay_log_pos();
+ else
+ {
+ inc_group_relay_log_pos(event_master_log_pos, rgi);
+ if (rpl_global_gtid_slave_state.record_and_update_gtid(thd, rgi))
+ {
+ report(WARNING_LEVEL, ER_CANNOT_UPDATE_GTID_STATE, rgi->gtid_info(),
+ "Failed to update GTID state in %s.%s, slave state may become "
+ "inconsistent: %d: %s",
+ "mysql", rpl_gtid_slave_state_table_name.str,
+ thd->get_stmt_da()->sql_errno(), thd->get_stmt_da()->message());
+ /*
+ At this point we are not in a transaction (for example after DDL),
+ so we can not roll back. Anyway, normally updates to the slave
+ state table should not fail, and if they do, at least we made the
+ DBA aware of the problem in the error log.
+ */
+ }
+ DBUG_EXECUTE_IF("inject_crash_before_flush_rli", DBUG_SUICIDE(););
+ if (mi->using_gtid == Master_info::USE_GTID_NO)
+ flush_relay_log_info(this);
+ DBUG_EXECUTE_IF("inject_crash_after_flush_rli", DBUG_SUICIDE(););
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+int
+Relay_log_info::alloc_inuse_relaylog(const char *name)
+{
+ inuse_relaylog *ir;
+ uint32 gtid_count;
+ rpl_gtid *gtid_list;
+
+ if (!(ir= (inuse_relaylog *)my_malloc(sizeof(*ir), MYF(MY_WME|MY_ZEROFILL))))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*ir));
+ return 1;
+ }
+ gtid_count= relay_log_state.count();
+ if (!(gtid_list= (rpl_gtid *)my_malloc(sizeof(*gtid_list)*gtid_count,
+ MYF(MY_WME))))
+ {
+ my_free(ir);
+ my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*gtid_list)*gtid_count);
+ return 1;
+ }
+ if (relay_log_state.get_gtid_list(gtid_list, gtid_count))
+ {
+ my_free(gtid_list);
+ my_free(ir);
+ DBUG_ASSERT(0 /* Should not be possible as we allocated correct length */);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return 1;
+ }
+ ir->rli= this;
+ strmake_buf(ir->name, name);
+ ir->relay_log_state= gtid_list;
+ ir->relay_log_state_count= gtid_count;
+
+ if (!inuse_relaylog_list)
+ inuse_relaylog_list= ir;
+ else
+ {
+ last_inuse_relaylog->completed= true;
+ last_inuse_relaylog->next= ir;
+ }
+ last_inuse_relaylog= ir;
+ my_atomic_rwlock_init(&ir->inuse_relaylog_atomic_lock);
+
+ return 0;
+}
+
+
+void
+Relay_log_info::free_inuse_relaylog(inuse_relaylog *ir)
+{
+ my_free(ir->relay_log_state);
+ my_atomic_rwlock_destroy(&ir->inuse_relaylog_atomic_lock);
+ my_free(ir);
+}
+
+
+void
+Relay_log_info::reset_inuse_relaylog()
+{
+ inuse_relaylog *cur= inuse_relaylog_list;
+ while (cur)
+ {
+ DBUG_ASSERT(cur->queued_count == cur->dequeued_count);
+ inuse_relaylog *next= cur->next;
+ free_inuse_relaylog(cur);
+ cur= next;
+ }
+ inuse_relaylog_list= last_inuse_relaylog= NULL;
+}
+
+
+int
+Relay_log_info::update_relay_log_state(rpl_gtid *gtid_list, uint32 count)
+{
+ int res= 0;
+ while (count)
+ {
+ if (relay_log_state.update_nolock(gtid_list, false))
+ res= 1;
+ ++gtid_list;
+ --count;
+ }
+ return res;
+}
+
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+int
+rpl_load_gtid_slave_state(THD *thd)
+{
+ TABLE_LIST tlist;
+ TABLE *table;
+ bool table_opened= false;
+ bool table_scanned= false;
+ bool array_inited= false;
+ struct local_element { uint64 sub_id; rpl_gtid gtid; };
+ struct local_element tmp_entry, *entry;
+ HASH hash;
+ DYNAMIC_ARRAY array;
+ int err= 0;
+ uint32 i;
+ DBUG_ENTER("rpl_load_gtid_slave_state");
+
+ mysql_mutex_lock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ bool loaded= rpl_global_gtid_slave_state.loaded;
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ if (loaded)
+ DBUG_RETURN(0);
+
+ my_hash_init(&hash, &my_charset_bin, 32,
+ offsetof(local_element, gtid) + offsetof(rpl_gtid, domain_id),
+ sizeof(uint32), NULL, my_free, HASH_UNIQUE);
+ if ((err= my_init_dynamic_array(&array, sizeof(local_element), 0, 0, MYF(0))))
+ goto end;
+ array_inited= true;
+
+ mysql_reset_thd_for_next_command(thd);
+
+ tlist.init_one_table(STRING_WITH_LEN("mysql"),
+ rpl_gtid_slave_state_table_name.str,
+ rpl_gtid_slave_state_table_name.length,
+ NULL, TL_READ);
+ if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0)))
+ goto end;
+ table_opened= true;
+ table= tlist.table;
+
+ if ((err= gtid_check_rpl_slave_state_table(table)))
+ goto end;
+
+ bitmap_set_all(table->read_set);
+ if ((err= table->file->ha_rnd_init_with_error(1)))
+ {
+ table->file->print_error(err, MYF(0));
+ goto end;
+ }
+ table_scanned= true;
+ for (;;)
+ {
+ uint32 domain_id, server_id;
+ uint64 sub_id, seq_no;
+ uchar *rec;
+
+ if ((err= table->file->ha_rnd_next(table->record[0])))
+ {
+ if (err == HA_ERR_RECORD_DELETED)
+ continue;
+ else if (err == HA_ERR_END_OF_FILE)
+ break;
+ else
+ {
+ table->file->print_error(err, MYF(0));
+ goto end;
+ }
+ }
+ domain_id= (ulonglong)table->field[0]->val_int();
+ sub_id= (ulonglong)table->field[1]->val_int();
+ server_id= (ulonglong)table->field[2]->val_int();
+ seq_no= (ulonglong)table->field[3]->val_int();
+ DBUG_PRINT("info", ("Read slave state row: %u-%u-%lu sub_id=%lu\n",
+ (unsigned)domain_id, (unsigned)server_id,
+ (ulong)seq_no, (ulong)sub_id));
+
+ tmp_entry.sub_id= sub_id;
+ tmp_entry.gtid.domain_id= domain_id;
+ tmp_entry.gtid.server_id= server_id;
+ tmp_entry.gtid.seq_no= seq_no;
+ if ((err= insert_dynamic(&array, (uchar *)&tmp_entry)))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto end;
+ }
+
+ if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0)))
+ {
+ entry= (struct local_element *)rec;
+ if (entry->sub_id >= sub_id)
+ continue;
+ entry->sub_id= sub_id;
+ DBUG_ASSERT(entry->gtid.domain_id == domain_id);
+ entry->gtid.server_id= server_id;
+ entry->gtid.seq_no= seq_no;
+ }
+ else
+ {
+ if (!(entry= (struct local_element *)my_malloc(sizeof(*entry),
+ MYF(MY_WME))))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*entry));
+ err= 1;
+ goto end;
+ }
+ entry->sub_id= sub_id;
+ entry->gtid.domain_id= domain_id;
+ entry->gtid.server_id= server_id;
+ entry->gtid.seq_no= seq_no;
+ if ((err= my_hash_insert(&hash, (uchar *)entry)))
+ {
+ my_free(entry);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto end;
+ }
+ }
+ }
+
+ mysql_mutex_lock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ if (rpl_global_gtid_slave_state.loaded)
+ {
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ goto end;
+ }
+
+ for (i= 0; i < array.elements; ++i)
+ {
+ get_dynamic(&array, (uchar *)&tmp_entry, i);
+ if ((err= rpl_global_gtid_slave_state.update(tmp_entry.gtid.domain_id,
+ tmp_entry.gtid.server_id,
+ tmp_entry.sub_id,
+ tmp_entry.gtid.seq_no,
+ NULL)))
+ {
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto end;
+ }
+ }
+
+ for (i= 0; i < hash.records; ++i)
+ {
+ entry= (struct local_element *)my_hash_element(&hash, i);
+ if (opt_bin_log &&
+ mysql_bin_log.bump_seq_no_counter_if_needed(entry->gtid.domain_id,
+ entry->gtid.seq_no))
+ {
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto end;
+ }
+ }
+
+ rpl_global_gtid_slave_state.loaded= true;
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+
+ err= 0; /* Clear HA_ERR_END_OF_FILE */
+
+end:
+ if (table_scanned)
+ {
+ table->file->ha_index_or_rnd_end();
+ ha_commit_trans(thd, FALSE);
+ ha_commit_trans(thd, TRUE);
+ }
+ if (table_opened)
+ {
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+ if (array_inited)
+ delete_dynamic(&array);
+ my_hash_free(&hash);
+ DBUG_RETURN(err);
+}
+
+
+void
+rpl_group_info::reinit(Relay_log_info *rli)
+{
+ this->rli= rli;
+ tables_to_lock= NULL;
+ tables_to_lock_count= 0;
+ trans_retries= 0;
+ last_event_start_time= 0;
+ gtid_sub_id= 0;
+ commit_id= 0;
+ gtid_pending= false;
+ worker_error= 0;
+ row_stmt_start_timestamp= 0;
+ long_find_row_note_printed= false;
+ did_mark_start_commit= false;
+ gtid_ignore_duplicate_state= GTID_DUPLICATE_NULL;
+ commit_orderer.reinit();
+}
+
+rpl_group_info::rpl_group_info(Relay_log_info *rli)
+ : thd(0), wait_commit_sub_id(0),
+ wait_commit_group_info(0), parallel_entry(0),
+ deferred_events(NULL), m_annotate_event(0), is_parallel_exec(false)
+{
+ reinit(rli);
+ bzero(&current_gtid, sizeof(current_gtid));
+ mysql_mutex_init(key_rpl_group_info_sleep_lock, &sleep_lock,
+ MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_rpl_group_info_sleep_cond, &sleep_cond, NULL);
+}
+
+
+rpl_group_info::~rpl_group_info()
+{
+ free_annotate_event();
+ delete deferred_events;
+ mysql_mutex_destroy(&sleep_lock);
+ mysql_cond_destroy(&sleep_cond);
+}
+
+
+int
+event_group_new_gtid(rpl_group_info *rgi, Gtid_log_event *gev)
+{
+ uint64 sub_id= rpl_global_gtid_slave_state.next_sub_id(gev->domain_id);
+ if (!sub_id)
+ {
+ /* Out of memory caused hash insertion to fail. */
+ return 1;
+ }
+ rgi->gtid_sub_id= sub_id;
+ rgi->current_gtid.domain_id= gev->domain_id;
+ rgi->current_gtid.server_id= gev->server_id;
+ rgi->current_gtid.seq_no= gev->seq_no;
+ rgi->commit_id= gev->commit_id;
+ rgi->gtid_pending= true;
+ return 0;
+}
+
+
+void
+delete_or_keep_event_post_apply(rpl_group_info *rgi,
+ Log_event_type typ, Log_event *ev)
+{
+ /*
+ ToDo: This needs to work on rpl_group_info, not Relay_log_info, to be
+ thread-safe for parallel replication.
+ */
+
+ switch (typ) {
+ case FORMAT_DESCRIPTION_EVENT:
+ /*
+ Format_description_log_event should not be deleted because it
+ will be used to read info about the relay log's format;
+ it will be deleted when the SQL thread does not need it,
+ i.e. when this thread terminates.
+ */
+ break;
+ case ANNOTATE_ROWS_EVENT:
+ /*
+ Annotate_rows event should not be deleted because after it has
+ been applied, thd->query points to the string inside this event.
+ The thd->query will be used to generate new Annotate_rows event
+ during applying the subsequent Rows events.
+ */
+ rgi->set_annotate_event((Annotate_rows_log_event*) ev);
+ break;
+ case DELETE_ROWS_EVENT_V1:
+ case UPDATE_ROWS_EVENT_V1:
+ case WRITE_ROWS_EVENT_V1:
+ case DELETE_ROWS_EVENT:
+ case UPDATE_ROWS_EVENT:
+ case WRITE_ROWS_EVENT:
+ /*
+ After the last Rows event has been applied, the saved Annotate_rows
+ event (if any) is not needed anymore and can be deleted.
+ */
+ if (((Rows_log_event*)ev)->get_flags(Rows_log_event::STMT_END_F))
+ rgi->free_annotate_event();
+ /* fall through */
+ default:
+ DBUG_PRINT("info", ("Deleting the event after it has been executed"));
+ if (!rgi->is_deferred_event(ev))
+ delete ev;
+ break;
+ }
+}
+
+
+void rpl_group_info::cleanup_context(THD *thd, bool error)
+{
+ DBUG_ENTER("rpl_group_info::cleanup_context");
+ DBUG_PRINT("enter", ("error: %d", (int) error));
+
+ DBUG_ASSERT(this->thd == thd);
+ /*
+ 1) Instances of Table_map_log_event, if ::do_apply_event() was called on them,
+ may have opened tables, which we cannot be sure have been closed (because
+ maybe the Rows_log_event have not been found or will not be, because slave
+ SQL thread is stopping, or relay log has a missing tail etc). So we close
+ all thread's tables. And so the table mappings have to be cancelled.
+ 2) Rows_log_event::do_apply_event() may even have started statements or
+ transactions on them, which we need to rollback in case of error.
+ 3) If finding a Format_description_log_event after a BEGIN, we also need
+ to rollback before continuing with the next events.
+ 4) so we need this "context cleanup" function.
+ */
+ if (error)
+ {
+ trans_rollback_stmt(thd); // if a "statement transaction"
+ /* trans_rollback() also resets OPTION_GTID_BEGIN */
+ trans_rollback(thd); // if a "real transaction"
+ /*
+ Now that we have rolled back the transaction, make sure we do not
+ erroneously update the GTID position.
+ */
+ gtid_pending= false;
+ }
+ m_table_map.clear_tables();
+ slave_close_thread_tables(thd);
+ if (error)
+ {
+ thd->mdl_context.release_transactional_locks();
+
+ if (thd == rli->sql_driver_thd)
+ {
+ /*
+ Reset flags. This is needed to handle incident events and errors in
+ the relay log noticed by the sql driver thread.
+ */
+ rli->clear_flag(Relay_log_info::IN_STMT);
+ rli->clear_flag(Relay_log_info::IN_TRANSACTION);
+ }
+ }
+
+ /*
+ Cleanup for the flags that have been set at do_apply_event.
+ */
+ thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS;
+ thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS;
+
+ /*
+ Ensure we always release the domain for others to process, when using
+ --gtid-ignore-duplicates.
+ */
+ if (gtid_ignore_duplicate_state != GTID_DUPLICATE_NULL)
+ rpl_global_gtid_slave_state.release_domain_owner(this);
+
+ /*
+ Reset state related to long_find_row notes in the error log:
+ - timestamp
+ - flag that decides whether the slave prints or not
+ */
+ reset_row_stmt_start_timestamp();
+ unset_long_find_row_note_printed();
+
+ DBUG_VOID_RETURN;
+}
+
+
+void rpl_group_info::clear_tables_to_lock()
+{
+ DBUG_ENTER("rpl_group_info::clear_tables_to_lock()");
+#ifndef DBUG_OFF
+ /**
+ When replicating in RBR and MyISAM Merge tables are involved
+ open_and_lock_tables (called in do_apply_event) appends the
+ base tables to the list of tables_to_lock. Then these are
+ removed from the list in close_thread_tables (which is called
+ before we reach this point).
+
+ This assertion just confirms that we get no surprises at this
+ point.
+ */
+ uint i=0;
+ for (TABLE_LIST *ptr= tables_to_lock ; ptr ; ptr= ptr->next_global, i++) ;
+ DBUG_ASSERT(i == tables_to_lock_count);
+#endif
+
+ while (tables_to_lock)
+ {
+ uchar* to_free= reinterpret_cast<uchar*>(tables_to_lock);
+ if (tables_to_lock->m_tabledef_valid)
+ {
+ tables_to_lock->m_tabledef.table_def::~table_def();
+ tables_to_lock->m_tabledef_valid= FALSE;
+ }
+
+ /*
+ If blob fields were used during conversion of field values
+ from the master table into the slave table, then we need to
+ free the memory used temporarily to store their values before
+ copying into the slave's table.
+ */
+ if (tables_to_lock->m_conv_table)
+ free_blobs(tables_to_lock->m_conv_table);
+
+ tables_to_lock=
+ static_cast<RPL_TABLE_LIST*>(tables_to_lock->next_global);
+ tables_to_lock_count--;
+ my_free(to_free);
+ }
+ DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0);
+ DBUG_VOID_RETURN;
+}
+
+
+void rpl_group_info::slave_close_thread_tables(THD *thd)
+{
+ DBUG_ENTER("rpl_group_info::slave_close_thread_tables(THD *thd)");
+ thd->get_stmt_da()->set_overwrite_status(true);
+ thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+ thd->get_stmt_da()->set_overwrite_status(false);
+
+ close_thread_tables(thd);
+ /*
+ - If transaction rollback was requested due to deadlock
+ perform it and release metadata locks.
+ - If inside a multi-statement transaction,
+ defer the release of metadata locks until the current
+ transaction is either committed or rolled back. This prevents
+ other statements from modifying the table for the entire
+ duration of this transaction. This provides commit ordering
+ and guarantees serializability across multiple transactions.
+ - If in autocommit mode, or outside a transactional context,
+ automatically release metadata locks of the current statement.
+ */
+ if (thd->transaction_rollback_request)
+ {
+ trans_rollback_implicit(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+ else if (! thd->in_multi_stmt_transaction_mode())
+ thd->mdl_context.release_transactional_locks();
+ else
+ thd->mdl_context.release_statement_locks();
+
+ clear_tables_to_lock();
+ DBUG_VOID_RETURN;
+}
+
+
+
+static void
+mark_start_commit_inner(rpl_parallel_entry *e, group_commit_orderer *gco,
+ rpl_group_info *rgi)
+{
+ group_commit_orderer *tmp;
+ uint64 count= ++e->count_committing_event_groups;
+ /* Signal any following GCO whose wait_count has been reached now. */
+ tmp= gco;
+ while ((tmp= tmp->next_gco))
+ {
+ uint64 wait_count= tmp->wait_count;
+ if (wait_count > count)
+ break;
+ mysql_cond_broadcast(&tmp->COND_group_commit_orderer);
+ }
+}
+
+
+void
+rpl_group_info::mark_start_commit_no_lock()
+{
+ if (did_mark_start_commit)
+ return;
+ mark_start_commit_inner(parallel_entry, gco, this);
+ did_mark_start_commit= true;
+}
+
+
+void
+rpl_group_info::mark_start_commit()
+{
+ rpl_parallel_entry *e;
+
+ if (did_mark_start_commit)
+ return;
+
+ e= this->parallel_entry;
+ mysql_mutex_lock(&e->LOCK_parallel_entry);
+ mark_start_commit_inner(e, gco, this);
+ mysql_mutex_unlock(&e->LOCK_parallel_entry);
+ did_mark_start_commit= true;
+}
+
+
+/*
+ Format the current GTID as a string suitable for printing in error messages.
+
+ The string is stored in a buffer inside rpl_group_info, so remains valid
+ until next call to gtid_info() or until destruction of rpl_group_info.
+
+ If no GTID is available, then NULL is returned.
+*/
+char *
+rpl_group_info::gtid_info()
+{
+ if (!gtid_sub_id || !current_gtid.seq_no)
+ return NULL;
+ my_snprintf(gtid_info_buf, sizeof(gtid_info_buf), "Gtid %u-%u-%llu",
+ current_gtid.domain_id, current_gtid.server_id,
+ current_gtid.seq_no);
+ return gtid_info_buf;
+}
+
+
+/*
+ Undo the effect of a prior mark_start_commit().
+
+ This is only used for retrying a transaction in parallel replication, after
+ we have encountered a deadlock or other temporary error.
+
+ When we get such a deadlock, it means that the current group of transactions
+ did not yet all start committing (else they would not have deadlocked). So
+ we will not yet have woken up anything in the next group, our rgi->gco is
+ still live, and we can simply decrement the counter (to be incremented again
+ later, when the retry succeeds and reaches the commit step).
+*/
+void
+rpl_group_info::unmark_start_commit()
+{
+ rpl_parallel_entry *e;
+
+ if (!did_mark_start_commit)
+ return;
+
+ e= this->parallel_entry;
+ mysql_mutex_lock(&e->LOCK_parallel_entry);
+ --e->count_committing_event_groups;
+ mysql_mutex_unlock(&e->LOCK_parallel_entry);
+ did_mark_start_commit= false;
+}
+
+
+rpl_sql_thread_info::rpl_sql_thread_info(Rpl_filter *filter)
+ : rpl_filter(filter)
+{
+ cached_charset_invalidate();
+}
+
+
+void rpl_sql_thread_info::cached_charset_invalidate()
+{
+ DBUG_ENTER("rpl_group_info::cached_charset_invalidate");
+
+ /* Full of zeroes means uninitialized. */
+ bzero(cached_charset, sizeof(cached_charset));
+ DBUG_VOID_RETURN;
+}
+
+
+bool rpl_sql_thread_info::cached_charset_compare(char *charset) const
+{
+ DBUG_ENTER("rpl_group_info::cached_charset_compare");
+
+ if (memcmp(cached_charset, charset, sizeof(cached_charset)))
+ {
+ memcpy(const_cast<char*>(cached_charset), charset, sizeof(cached_charset));
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+#endif
diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h
new file mode 100644
index 00000000000..2d92f384ef3
--- /dev/null
+++ b/sql/rpl_rli.h
@@ -0,0 +1,847 @@
+/* Copyright (c) 2005, 2012, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifndef RPL_RLI_H
+#define RPL_RLI_H
+
+#include "rpl_tblmap.h"
+#include "rpl_reporting.h"
+#include "rpl_utility.h"
+#include "log.h" /* LOG_INFO, MYSQL_BIN_LOG */
+#include "sql_class.h" /* THD */
+#include "log_event.h"
+#include "rpl_parallel.h"
+
+struct RPL_TABLE_LIST;
+class Master_info;
+class Rpl_filter;
+
+
+enum {
+ LINES_IN_RELAY_LOG_INFO_WITH_DELAY= 5
+};
+
+
+/****************************************************************************
+
+ Replication SQL Thread
+
+ Relay_log_info contains:
+ - the current relay log
+ - the current relay log offset
+ - master log name
+ - master log sequence corresponding to the last update
+ - misc information specific to the SQL thread
+
+ Relay_log_info is initialized from the slave.info file if such
+ exists. Otherwise, data members are intialized with defaults. The
+ initialization is done with init_relay_log_info() call.
+
+ The format of slave.info file:
+
+ relay_log_name
+ relay_log_pos
+ master_log_name
+ master_log_pos
+
+ To clean up, call end_relay_log_info()
+
+*****************************************************************************/
+
+struct rpl_group_info;
+struct inuse_relaylog;
+
+class Relay_log_info : public Slave_reporting_capability
+{
+public:
+ /**
+ Flags for the state of reading the relay log. Note that these are
+ bit masks.
+ */
+ enum enum_state_flag {
+ /** We are inside a group of events forming a statement */
+ IN_STMT=1,
+ /** We have inside a transaction */
+ IN_TRANSACTION=2
+ };
+
+ /*
+ If flag set, then rli does not store its state in any info file.
+ This is the case only when we execute BINLOG SQL commands inside
+ a client, non-replication thread.
+ */
+ bool no_storage;
+
+ /*
+ If true, events with the same server id should be replicated. This
+ field is set on creation of a relay log info structure by copying
+ the value of ::replicate_same_server_id and can be overridden if
+ necessary. For example of when this is done, check sql_binlog.cc,
+ where the BINLOG statement can be used to execute "raw" events.
+ */
+ bool replicate_same_server_id;
+
+ /*** The following variables can only be read when protect by data lock ****/
+
+ /*
+ info_fd - file descriptor of the info file. set only during
+ initialization or clean up - safe to read anytime
+ cur_log_fd - file descriptor of the current read relay log
+ */
+ File info_fd,cur_log_fd;
+
+ /*
+ Protected with internal locks.
+ Must get data_lock when resetting the logs.
+ */
+ MYSQL_BIN_LOG relay_log;
+ LOG_INFO linfo;
+
+ /*
+ cur_log
+ Pointer that either points at relay_log.get_log_file() or
+ &rli->cache_buf, depending on whether the log is hot or there was
+ the need to open a cold relay_log.
+
+ cache_buf
+ IO_CACHE used when opening cold relay logs.
+ */
+ IO_CACHE cache_buf,*cur_log;
+
+ /*
+ Keeps track of the number of transactions that commits
+ before fsyncing. The option --sync-relay-log-info determines
+ how many transactions should commit before fsyncing.
+ */
+ uint sync_counter;
+
+ /*
+ Identifies when the recovery process is going on.
+ See sql/slave.cc:init_recovery for further details.
+ */
+ bool is_relay_log_recovery;
+
+ /* The following variables are safe to read any time */
+
+ /* IO_CACHE of the info file - set only during init or end */
+ IO_CACHE info_file;
+
+ /*
+ List of temporary tables used by this connection.
+ This is updated when a temporary table is created or dropped by
+ a replication thread.
+
+ Not reset when replication ends, to allow one to access the tables
+ when replication restarts.
+
+ Protected by data_lock.
+ */
+ TABLE *save_temporary_tables;
+
+ /*
+ standard lock acquisition order to avoid deadlocks:
+ run_lock, data_lock, relay_log.LOCK_log, relay_log.LOCK_index
+ */
+ mysql_mutex_t data_lock, run_lock;
+ /*
+ start_cond is broadcast when SQL thread is started
+ stop_cond - when stopped
+ data_cond - when data protected by data_lock changes
+ */
+ mysql_cond_t start_cond, stop_cond, data_cond;
+ /* parent Master_info structure */
+ Master_info *mi;
+
+ /*
+ List of active relay log files.
+ (This can be more than one in case of parallel replication).
+ */
+ inuse_relaylog *inuse_relaylog_list;
+ inuse_relaylog *last_inuse_relaylog;
+
+ /*
+ Needed to deal properly with cur_log getting closed and re-opened with
+ a different log under our feet
+ */
+ uint32 cur_log_old_open_count;
+
+ /*
+ Let's call a group (of events) :
+ - a transaction
+ or
+ - an autocommiting query + its associated events (INSERT_ID,
+ TIMESTAMP...)
+ We need these rli coordinates :
+ - relay log name and position of the beginning of the group we currently
+ are executing. Needed to know where we have to restart when replication has
+ stopped in the middle of a group (which has been rolled back by the slave).
+ - relay log name and position just after the event we have just
+ executed. This event is part of the current group.
+ Formerly we only had the immediately above coordinates, plus a 'pending'
+ variable, but this dealt wrong with the case of a transaction starting on a
+ relay log and finishing (commiting) on another relay log. Case which can
+ happen when, for example, the relay log gets rotated because of
+ max_binlog_size.
+ */
+ char group_relay_log_name[FN_REFLEN];
+ ulonglong group_relay_log_pos;
+ char event_relay_log_name[FN_REFLEN];
+ ulonglong event_relay_log_pos;
+ ulonglong future_event_relay_log_pos;
+ /*
+ The master log name for current event. Only used in parallel replication.
+ */
+ char future_event_master_log_name[FN_REFLEN];
+
+#ifdef HAVE_valgrind
+ bool is_fake; /* Mark that this is a fake relay log info structure */
+#endif
+
+ /*
+ Original log name and position of the group we're currently executing
+ (whose coordinates are group_relay_log_name/pos in the relay log)
+ in the master's binlog. These concern the *group*, because in the master's
+ binlog the log_pos that comes with each event is the position of the
+ beginning of the group.
+ */
+ char group_master_log_name[FN_REFLEN];
+ volatile my_off_t group_master_log_pos;
+
+ /*
+ Handling of the relay_log_space_limit optional constraint.
+ ignore_log_space_limit is used to resolve a deadlock between I/O and SQL
+ threads, the SQL thread sets it to unblock the I/O thread and make it
+ temporarily forget about the constraint.
+ */
+ ulonglong log_space_limit,log_space_total;
+ bool ignore_log_space_limit;
+
+ /*
+ Used by the SQL thread to instructs the IO thread to rotate
+ the logs when the SQL thread needs to purge to release some
+ disk space.
+ */
+ bool sql_force_rotate_relay;
+
+ time_t last_master_timestamp;
+ /*
+ The SQL driver thread sets this true while it is waiting at the end of the
+ relay log for more events to arrive. SHOW SLAVE STATUS uses this to report
+ Seconds_Behind_Master as zero while the SQL thread is so waiting.
+ */
+ bool sql_thread_caught_up;
+
+ void clear_until_condition();
+
+ /*
+ Needed for problems when slave stops and we want to restart it
+ skipping one or more events in the master log that have caused
+ errors, and have been manually applied by DBA already.
+ Must be ulong as it's refered to from set_var.cc
+ */
+ volatile ulonglong slave_skip_counter;
+ ulonglong max_relay_log_size;
+
+ volatile ulong abort_pos_wait; /* Incremented on change master */
+ volatile ulong slave_run_id; /* Incremented on slave start */
+ mysql_mutex_t log_space_lock;
+ mysql_cond_t log_space_cond;
+ /*
+ THD for the main sql thread, the one that starts threads to process
+ slave requests. If there is only one thread, then this THD is also
+ used for SQL processing.
+ A kill sent to this THD will kill the replication.
+ */
+ THD *sql_driver_thd;
+#ifndef DBUG_OFF
+ int events_till_abort;
+#endif
+
+ enum_gtid_skip_type gtid_skip_flag;
+
+ /*
+ inited changes its value within LOCK_active_mi-guarded critical
+ sections at times of start_slave_threads() (0->1) and end_slave() (1->0).
+ Readers may not acquire the mutex while they realize potential concurrency
+ issue.
+ If not set, the value of other members of the structure are undefined.
+ */
+ volatile bool inited;
+ volatile bool abort_slave;
+ volatile bool stop_for_until;
+ volatile uint slave_running;
+
+ /*
+ Condition and its parameters from START SLAVE UNTIL clause.
+
+ UNTIL condition is tested with is_until_satisfied() method that is
+ called by exec_relay_log_event(). is_until_satisfied() caches the result
+ of the comparison of log names because log names don't change very often;
+ this cache is invalidated by parts of code which change log names with
+ notify_*_log_name_updated() methods. (They need to be called only if SQL
+ thread is running).
+ */
+
+ enum {
+ UNTIL_NONE= 0, UNTIL_MASTER_POS, UNTIL_RELAY_POS, UNTIL_GTID
+ } until_condition;
+ char until_log_name[FN_REFLEN];
+ ulonglong until_log_pos;
+ /* extension extracted from log_name and converted to int */
+ ulong until_log_name_extension;
+ /*
+ Cached result of comparison of until_log_name and current log name
+ -2 means unitialised, -1,0,1 are comarison results
+ */
+ enum
+ {
+ UNTIL_LOG_NAMES_CMP_UNKNOWN= -2, UNTIL_LOG_NAMES_CMP_LESS= -1,
+ UNTIL_LOG_NAMES_CMP_EQUAL= 0, UNTIL_LOG_NAMES_CMP_GREATER= 1
+ } until_log_names_cmp_result;
+ /* Condition for UNTIL master_gtid_pos. */
+ slave_connection_state until_gtid_pos;
+
+ /*
+ retried_trans is a cumulative counter: how many times the slave
+ has retried a transaction (any) since slave started.
+ Protected by data_lock.
+ */
+ ulong retried_trans;
+ /*
+ Number of executed events for SLAVE STATUS.
+ Protected by slave_executed_entries_lock
+ */
+ int64 executed_entries;
+
+ /*
+ If the end of the hot relay log is made of master's events ignored by the
+ slave I/O thread, these two keep track of the coords (in the master's
+ binlog) of the last of these events seen by the slave I/O thread. If not,
+ ign_master_log_name_end[0] == 0.
+ As they are like a Rotate event read/written from/to the relay log, they
+ are both protected by rli->relay_log.LOCK_log.
+ */
+ char ign_master_log_name_end[FN_REFLEN];
+ ulonglong ign_master_log_pos_end;
+ /* Similar for ignored GTID events. */
+ slave_connection_state ign_gtids;
+
+ /*
+ Indentifies where the SQL Thread should create temporary files for the
+ LOAD DATA INFILE. This is used for security reasons.
+ */
+ char slave_patternload_file[FN_REFLEN];
+ size_t slave_patternload_file_size;
+
+ rpl_parallel parallel;
+ /*
+ The relay_log_state keeps track of the current binlog state of the execution
+ of the relay log. This is used to know where to resume current GTID position
+ if the slave thread is stopped and restarted.
+ It is only accessed from the SQL thread, so it does not need any locking.
+ */
+ rpl_binlog_state relay_log_state;
+ /*
+ The restart_gtid_state is used when the SQL thread restarts on a relay log
+ in GTID mode. In multi-domain parallel replication, each domain may have a
+ separat position, so some events in more progressed domains may need to be
+ skipped. This keeps track of the domains that have not yet reached their
+ starting event.
+ */
+ slave_connection_state restart_gtid_pos;
+
+ Relay_log_info(bool is_slave_recovery);
+ ~Relay_log_info();
+
+ /*
+ Invalidate cached until_log_name and group_relay_log_name comparison
+ result. Should be called after any update of group_realy_log_name if
+ there chances that sql_thread is running.
+ */
+ inline void notify_group_relay_log_name_update()
+ {
+ if (until_condition==UNTIL_RELAY_POS)
+ until_log_names_cmp_result= UNTIL_LOG_NAMES_CMP_UNKNOWN;
+ }
+
+ /*
+ The same as previous but for group_master_log_name.
+ */
+ inline void notify_group_master_log_name_update()
+ {
+ if (until_condition==UNTIL_MASTER_POS)
+ until_log_names_cmp_result= UNTIL_LOG_NAMES_CMP_UNKNOWN;
+ }
+
+ void inc_group_relay_log_pos(ulonglong log_pos,
+ rpl_group_info *rgi,
+ bool skip_lock=0);
+
+ int wait_for_pos(THD* thd, String* log_name, longlong log_pos,
+ longlong timeout);
+ void close_temporary_tables();
+
+ /* Check if UNTIL condition is satisfied. See slave.cc for more. */
+ bool is_until_satisfied(THD *thd, Log_event *ev);
+ inline ulonglong until_pos()
+ {
+ DBUG_ASSERT(until_condition == UNTIL_MASTER_POS ||
+ until_condition == UNTIL_RELAY_POS);
+ return ((until_condition == UNTIL_MASTER_POS) ? group_master_log_pos :
+ group_relay_log_pos);
+ }
+
+ /**
+ Helper function to do after statement completion.
+
+ This function is called from an event to complete the group by
+ either stepping the group position, if the "statement" is not
+ inside a transaction; or increase the event position, if the
+ "statement" is inside a transaction.
+
+ @param event_log_pos
+ Master log position of the event. The position is recorded in the
+ relay log info and used to produce information for <code>SHOW
+ SLAVE STATUS</code>.
+ */
+ void stmt_done(my_off_t event_log_pos, THD *thd, rpl_group_info *rgi);
+ int alloc_inuse_relaylog(const char *name);
+ void free_inuse_relaylog(inuse_relaylog *ir);
+ void reset_inuse_relaylog();
+ int update_relay_log_state(rpl_gtid *gtid_list, uint32 count);
+
+ /**
+ Is the replication inside a group?
+
+ The reader of the relay log is inside a group if either:
+ - The IN_TRANSACTION flag is set, meaning we're inside a transaction
+ - The IN_STMT flag is set, meaning we have read at least one row from
+ a multi-event entry.
+
+ This flag reflects the state of the log 'just now', ie after the last
+ read event would be executed.
+ This allow us to test if we can stop replication before reading
+ the next entry.
+
+ @retval true Replication thread is currently inside a group
+ @retval false Replication thread is currently not inside a group
+ */
+ bool is_in_group() const {
+ return (m_flags & (IN_STMT | IN_TRANSACTION));
+ }
+
+ /**
+ Set the value of a replication state flag.
+
+ @param flag Flag to set
+ */
+ void set_flag(enum_state_flag flag)
+ {
+ m_flags|= flag;
+ }
+
+ /**
+ Get the value of a replication state flag.
+
+ @param flag Flag to get value of
+
+ @return @c true if the flag was set, @c false otherwise.
+ */
+ bool get_flag(enum_state_flag flag)
+ {
+ return m_flags & flag;
+ }
+
+ /**
+ Clear the value of a replication state flag.
+
+ @param flag Flag to clear
+ */
+ void clear_flag(enum_state_flag flag)
+ {
+ m_flags&= ~flag;
+ }
+
+private:
+
+ /*
+ Holds the state of the data in the relay log.
+ We need this to ensure that we are not in the middle of a
+ statement or inside BEGIN ... COMMIT when should rotate the
+ relay log.
+ */
+ uint32 m_flags;
+};
+
+
+/*
+ In parallel replication, if we need to re-try a transaction due to a
+ deadlock or other temporary error, we may need to go back and re-read events
+ out of an earlier relay log.
+
+ This structure keeps track of the relaylogs that are potentially in use.
+ Each rpl_group_info has a pointer to one of those, corresponding to the
+ first GTID event.
+
+ A pair of reference count keeps track of how long a relay log is potentially
+ in use. When the `completed' flag is set, all events have been read out of
+ the relay log, but the log might still be needed for retry in worker
+ threads. As worker threads complete an event group, they increment
+ atomically the `dequeued_count' with number of events queued. Thus, when
+ completed is set and dequeued_count equals queued_count, the relay log file
+ is finally done with and can be purged.
+
+ By separating the queued and dequeued count, only the dequeued_count needs
+ multi-thread synchronisation; the completed flag and queued_count fields
+ are only accessed by the SQL driver thread and need no synchronisation.
+*/
+struct inuse_relaylog {
+ inuse_relaylog *next;
+ Relay_log_info *rli;
+ /*
+ relay_log_state holds the binlog state corresponding to the start of this
+ relay log file. It is an array with relay_log_state_count elements.
+ */
+ rpl_gtid *relay_log_state;
+ uint32 relay_log_state_count;
+ /* Number of events in this relay log queued for worker threads. */
+ int64 queued_count;
+ /* Number of events completed by worker threads. */
+ volatile int64 dequeued_count;
+ /* Set when all events have been read from a relaylog. */
+ bool completed;
+ char name[FN_REFLEN];
+ /* Lock used to protect inuse_relaylog::dequeued_count */
+ my_atomic_rwlock_t inuse_relaylog_atomic_lock;
+};
+
+
+/*
+ This is data for various state needed to be kept for the processing of
+ one event group (transaction) during replication.
+
+ In single-threaded replication, there will be one global rpl_group_info and
+ one global Relay_log_info per master connection. They will be linked
+ together.
+
+ In parallel replication, there will be one rpl_group_info object for
+ each running sql thread, each having their own thd.
+
+ All rpl_group_info will share the same Relay_log_info.
+*/
+
+struct rpl_group_info
+{
+ rpl_group_info *next; /* For free list in rpl_parallel_thread */
+ Relay_log_info *rli;
+ THD *thd;
+ /*
+ Current GTID being processed.
+ The sub_id gives the binlog order within one domain_id. A zero sub_id
+ means that there is no active GTID.
+ */
+ uint64 gtid_sub_id;
+ rpl_gtid current_gtid;
+ uint64 commit_id;
+ /*
+ This is used to keep transaction commit order.
+ We will signal this when we commit, and can register it to wait for the
+ commit_orderer of the previous commit to signal us.
+ */
+ wait_for_commit commit_orderer;
+ /*
+ If non-zero, the sub_id of a prior event group whose commit we have to wait
+ for before committing ourselves. Then wait_commit_group_info points to the
+ event group to wait for.
+
+ Before using this, rpl_parallel_entry::last_committed_sub_id should be
+ compared against wait_commit_sub_id. Only if last_committed_sub_id is
+ smaller than wait_commit_sub_id must the wait be done (otherwise the
+ waited-for transaction is already committed, so we would otherwise wait
+ for the wrong commit).
+ */
+ uint64 wait_commit_sub_id;
+ rpl_group_info *wait_commit_group_info;
+ /*
+ This holds a pointer to a struct that keeps track of the need to wait
+ for the previous batch of event groups to reach the commit stage, before
+ this batch can start to execute.
+
+ (When we execute in parallel the transactions that group committed
+ together on the master, we still need to wait for any prior transactions
+ to have reached the commit stage).
+
+ The pointed-to gco is only valid for as long as
+ gtid_sub_id < parallel_entry->last_committed_sub_id. After that, it can
+ be freed by another thread.
+ */
+ group_commit_orderer *gco;
+
+ struct rpl_parallel_entry *parallel_entry;
+
+ /*
+ A container to hold on Intvar-, Rand-, Uservar- log-events in case
+ the slave is configured with table filtering rules.
+ The withhold events are executed when their parent Query destiny is
+ determined for execution as well.
+ */
+ Deferred_log_events *deferred_events;
+
+ /*
+ State of the container: true stands for IRU events gathering,
+ false does for execution, either deferred or direct.
+ */
+ bool deferred_events_collecting;
+
+ Annotate_rows_log_event *m_annotate_event;
+
+ RPL_TABLE_LIST *tables_to_lock; /* RBR: Tables to lock */
+ uint tables_to_lock_count; /* RBR: Count of tables to lock */
+ table_mapping m_table_map; /* RBR: Mapping table-id to table */
+ mysql_mutex_t sleep_lock;
+ mysql_cond_t sleep_cond;
+
+ /*
+ trans_retries varies between 0 to slave_transaction_retries and counts how
+ many times the slave has retried the present transaction; gets reset to 0
+ when the transaction finally succeeds.
+ */
+ ulong trans_retries;
+
+ /*
+ Used to defer stopping the SQL thread to give it a chance
+ to finish up the current group of events.
+ The timestamp is set and reset in @c sql_slave_killed().
+ */
+ time_t last_event_start_time;
+
+ char *event_relay_log_name;
+ char event_relay_log_name_buf[FN_REFLEN];
+ ulonglong event_relay_log_pos;
+ ulonglong future_event_relay_log_pos;
+ /*
+ The master log name for current event. Only used in parallel replication.
+ */
+ char future_event_master_log_name[FN_REFLEN];
+ bool is_parallel_exec;
+ /* When gtid_pending is true, we have not yet done record_gtid(). */
+ bool gtid_pending;
+ int worker_error;
+ /*
+ Set true when we signalled that we reach the commit phase. Used to avoid
+ counting one event group twice.
+ */
+ bool did_mark_start_commit;
+ enum {
+ GTID_DUPLICATE_NULL=0,
+ GTID_DUPLICATE_IGNORE=1,
+ GTID_DUPLICATE_OWNER=2
+ };
+ /*
+ When --gtid-ignore-duplicates, this is set to one of the above three
+ values:
+ GTID_DUPLICATE_NULL - Not using --gtid-ignore-duplicates.
+ GTID_DUPLICATE_IGNORE - This gtid already applied, skip the event group.
+ GTID_DUPLICATE_OWNER - We are the current owner of the domain, and must
+ apply the event group and then release the domain.
+ */
+ uint8 gtid_ignore_duplicate_state;
+
+ /*
+ Runtime state for printing a note when slave is taking
+ too long while processing a row event.
+ */
+ time_t row_stmt_start_timestamp;
+ bool long_find_row_note_printed;
+ /* Needs room for "Gtid D-S-N\x00". */
+ char gtid_info_buf[5+10+1+10+1+20+1];
+
+ /*
+ Information to be able to re-try an event group in case of a deadlock or
+ other temporary error.
+ */
+ inuse_relaylog *relay_log;
+ uint64 retry_start_offset;
+ uint64 retry_event_count;
+ bool killed_for_retry;
+
+ rpl_group_info(Relay_log_info *rli_);
+ ~rpl_group_info();
+ void reinit(Relay_log_info *rli);
+
+ /*
+ Returns true if the argument event resides in the containter;
+ more specifically, the checking is done against the last added event.
+ */
+ bool is_deferred_event(Log_event * ev)
+ {
+ return deferred_events_collecting ? deferred_events->is_last(ev) : false;
+ };
+ /* The general cleanup that slave applier may need at the end of query. */
+ inline void cleanup_after_query()
+ {
+ if (deferred_events)
+ deferred_events->rewind();
+ };
+ /* The general cleanup that slave applier may need at the end of session. */
+ void cleanup_after_session()
+ {
+ if (deferred_events)
+ {
+ delete deferred_events;
+ deferred_events= NULL;
+ }
+ };
+
+ /**
+ Save pointer to Annotate_rows event and switch on the
+ binlog_annotate_row_events for this sql thread.
+ To be called when sql thread recieves an Annotate_rows event.
+ */
+ inline void set_annotate_event(Annotate_rows_log_event *event)
+ {
+ free_annotate_event();
+ m_annotate_event= event;
+ this->thd->variables.binlog_annotate_row_events= 1;
+ }
+
+ /**
+ Returns pointer to the saved Annotate_rows event or NULL if there is
+ no saved event.
+ */
+ inline Annotate_rows_log_event* get_annotate_event()
+ {
+ return m_annotate_event;
+ }
+
+ /**
+ Delete saved Annotate_rows event (if any) and switch off the
+ binlog_annotate_row_events for this sql thread.
+ To be called when sql thread has applied the last (i.e. with
+ STMT_END_F flag) rbr event.
+ */
+ inline void free_annotate_event()
+ {
+ if (m_annotate_event)
+ {
+ this->thd->variables.binlog_annotate_row_events= 0;
+ delete m_annotate_event;
+ m_annotate_event= 0;
+ }
+ }
+
+ bool get_table_data(TABLE *table_arg, table_def **tabledef_var, TABLE **conv_table_var) const
+ {
+ DBUG_ASSERT(tabledef_var && conv_table_var);
+ for (TABLE_LIST *ptr= tables_to_lock ; ptr != NULL ; ptr= ptr->next_global)
+ if (ptr->table == table_arg)
+ {
+ *tabledef_var= &static_cast<RPL_TABLE_LIST*>(ptr)->m_tabledef;
+ *conv_table_var= static_cast<RPL_TABLE_LIST*>(ptr)->m_conv_table;
+ DBUG_PRINT("debug", ("Fetching table data for table %s.%s:"
+ " tabledef: %p, conv_table: %p",
+ table_arg->s->db.str, table_arg->s->table_name.str,
+ *tabledef_var, *conv_table_var));
+ return true;
+ }
+ return false;
+ }
+
+ void clear_tables_to_lock();
+ void cleanup_context(THD *, bool);
+ void slave_close_thread_tables(THD *);
+ void mark_start_commit_no_lock();
+ void mark_start_commit();
+ char *gtid_info();
+ void unmark_start_commit();
+
+ time_t get_row_stmt_start_timestamp()
+ {
+ return row_stmt_start_timestamp;
+ }
+
+ time_t set_row_stmt_start_timestamp()
+ {
+ if (row_stmt_start_timestamp == 0)
+ row_stmt_start_timestamp= my_time(0);
+
+ return row_stmt_start_timestamp;
+ }
+
+ void reset_row_stmt_start_timestamp()
+ {
+ row_stmt_start_timestamp= 0;
+ }
+
+ void set_long_find_row_note_printed()
+ {
+ long_find_row_note_printed= true;
+ }
+
+ void unset_long_find_row_note_printed()
+ {
+ long_find_row_note_printed= false;
+ }
+
+ bool is_long_find_row_note_printed()
+ {
+ return long_find_row_note_printed;
+ }
+
+ inline void inc_event_relay_log_pos()
+ {
+ if (!is_parallel_exec)
+ rli->event_relay_log_pos= future_event_relay_log_pos;
+ }
+};
+
+
+/*
+ The class rpl_sql_thread_info is the THD::system_thread_info for an SQL
+ thread; this is either the driver SQL thread or a worker thread for parallel
+ replication.
+*/
+class rpl_sql_thread_info
+{
+public:
+ char cached_charset[6];
+ Rpl_filter* rpl_filter;
+
+ rpl_sql_thread_info(Rpl_filter *filter);
+
+ /*
+ Last charset (6 bytes) seen by slave SQL thread is cached here; it helps
+ the thread save 3 get_charset() per Query_log_event if the charset is not
+ changing from event to event (common situation).
+ When the 6 bytes are equal to 0 is used to mean "cache is invalidated".
+ */
+ void cached_charset_invalidate();
+ bool cached_charset_compare(char *charset) const;
+};
+
+
+// Defined in rpl_rli.cc
+int init_relay_log_info(Relay_log_info* rli, const char* info_fname);
+
+
+extern struct rpl_slave_state rpl_global_gtid_slave_state;
+extern gtid_waiting rpl_global_gtid_waiting;
+
+int rpl_load_gtid_slave_state(THD *thd);
+int event_group_new_gtid(rpl_group_info *rgi, Gtid_log_event *gev);
+void delete_or_keep_event_post_apply(rpl_group_info *rgi,
+ Log_event_type typ, Log_event *ev);
+
+#endif /* RPL_RLI_H */
diff --git a/sql/rpl_tblmap.cc b/sql/rpl_tblmap.cc
new file mode 100644
index 00000000000..4c521cf0c16
--- /dev/null
+++ b/sql/rpl_tblmap.cc
@@ -0,0 +1,176 @@
+/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+
+#ifdef HAVE_REPLICATION
+
+#include "rpl_tblmap.h"
+#ifndef MYSQL_CLIENT
+#include "table.h"
+#endif
+
+#ifdef MYSQL_CLIENT
+#define MAYBE_TABLE_NAME(T) ("")
+#else
+#define MAYBE_TABLE_NAME(T) ((T) ? (T)->s->table_name.str : "<>")
+#endif
+#define TABLE_ID_HASH_SIZE 32
+#define TABLE_ID_CHUNK 256
+
+table_mapping::table_mapping()
+ : m_free(0)
+{
+ DBUG_ENTER("table_mapping::table_mapping");
+ /*
+ No "free_element" function for entries passed here, as the entries are
+ allocated in a MEM_ROOT (freed as a whole in the destructor), they cannot
+ be freed one by one.
+ Note that below we don't test if my_hash_init() succeeded. This
+ constructor is called at startup only.
+ */
+ (void) my_hash_init(&m_table_ids,&my_charset_bin,TABLE_ID_HASH_SIZE,
+ offsetof(entry,table_id),sizeof(ulong),
+ 0,0,0);
+ /* We don't preallocate any block, this is consistent with m_free=0 above */
+ init_alloc_root(&m_mem_root, TABLE_ID_HASH_SIZE*sizeof(entry), 0, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+table_mapping::~table_mapping()
+{
+#ifdef MYSQL_CLIENT
+ clear_tables();
+#endif
+ my_hash_free(&m_table_ids);
+ free_root(&m_mem_root, MYF(0));
+}
+
+TABLE* table_mapping::get_table(ulong table_id)
+{
+ DBUG_ENTER("table_mapping::get_table(ulong)");
+ DBUG_PRINT("enter", ("table_id: %lu", table_id));
+ entry *e= find_entry(table_id);
+ if (e)
+ {
+ DBUG_PRINT("info", ("tid %lu -> table 0x%lx (%s)",
+ table_id, (long) e->table,
+ MAYBE_TABLE_NAME(e->table)));
+ DBUG_RETURN(e->table);
+ }
+
+ DBUG_PRINT("info", ("tid %lu is not mapped!", table_id));
+ DBUG_RETURN(NULL);
+}
+
+/*
+ Called when we are out of table id entries. Creates TABLE_ID_CHUNK
+ new entries, chain them and attach them at the head of the list of free
+ (free for use) entries.
+*/
+int table_mapping::expand()
+{
+ /*
+ If we wanted to use "tmp= new (&m_mem_root) entry[TABLE_ID_CHUNK]",
+ we would have to make "entry" derive from Sql_alloc but then it would not
+ be a POD anymore and we want it to be (see rpl_tblmap.h). So we allocate
+ in C.
+ */
+ entry *tmp= (entry *)alloc_root(&m_mem_root, TABLE_ID_CHUNK*sizeof(entry));
+ if (tmp == NULL)
+ return ERR_MEMORY_ALLOCATION; // Memory allocation failed
+
+ /* Find the end of this fresh new array of free entries */
+ entry *e_end= tmp+TABLE_ID_CHUNK-1;
+ for (entry *e= tmp; e < e_end; e++)
+ e->next= e+1;
+ e_end->next= m_free;
+ m_free= tmp;
+ return 0;
+}
+
+int table_mapping::set_table(ulong table_id, TABLE* table)
+{
+ DBUG_ENTER("table_mapping::set_table(ulong,TABLE*)");
+ DBUG_PRINT("enter", ("table_id: %lu table: 0x%lx (%s)",
+ table_id,
+ (long) table, MAYBE_TABLE_NAME(table)));
+ entry *e= find_entry(table_id);
+ if (e == 0)
+ {
+ if (m_free == 0 && expand())
+ DBUG_RETURN(ERR_MEMORY_ALLOCATION); // Memory allocation failed
+ e= m_free;
+ m_free= m_free->next;
+ }
+ else
+ {
+#ifdef MYSQL_CLIENT
+ free_table_map_log_event(e->table);
+#endif
+ my_hash_delete(&m_table_ids,(uchar *)e);
+ }
+ e->table_id= table_id;
+ e->table= table;
+ if (my_hash_insert(&m_table_ids,(uchar *)e))
+ {
+ /* we add this entry to the chain of free (free for use) entries */
+ e->next= m_free;
+ m_free= e;
+ DBUG_RETURN(ERR_MEMORY_ALLOCATION);
+ }
+
+ DBUG_PRINT("info", ("tid %lu -> table 0x%lx (%s)",
+ table_id, (long) e->table,
+ MAYBE_TABLE_NAME(e->table)));
+ DBUG_RETURN(0); // All OK
+}
+
+int table_mapping::remove_table(ulong table_id)
+{
+ entry *e= find_entry(table_id);
+ if (e)
+ {
+ my_hash_delete(&m_table_ids,(uchar *)e);
+ /* we add this entry to the chain of free (free for use) entries */
+ e->next= m_free;
+ m_free= e;
+ return 0; // All OK
+ }
+ return 1; // No table to remove
+}
+
+/*
+ Puts all entries into the list of free-for-use entries (does not free any
+ memory), and empties the hash.
+*/
+void table_mapping::clear_tables()
+{
+ DBUG_ENTER("table_mapping::clear_tables()");
+ for (uint i= 0; i < m_table_ids.records; i++)
+ {
+ entry *e= (entry *)my_hash_element(&m_table_ids, i);
+#ifdef MYSQL_CLIENT
+ free_table_map_log_event(e->table);
+#endif
+ e->next= m_free;
+ m_free= e;
+ }
+ my_hash_reset(&m_table_ids);
+ DBUG_VOID_RETURN;
+}
+
+#endif
diff --git a/sql/rpl_tblmap.h b/sql/rpl_tblmap.h
new file mode 100644
index 00000000000..9fb1c4afbd7
--- /dev/null
+++ b/sql/rpl_tblmap.h
@@ -0,0 +1,112 @@
+/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef TABLE_MAPPING_H
+#define TABLE_MAPPING_H
+
+/* Forward declarations */
+#ifndef MYSQL_CLIENT
+struct TABLE;
+#else
+class Table_map_log_event;
+typedef Table_map_log_event TABLE;
+void free_table_map_log_event(TABLE *table);
+#endif
+
+
+/*
+ CLASS table_mapping
+
+ RESPONSIBILITIES
+ The table mapping is used to map table id's to table pointers
+
+ COLLABORATION
+ RELAY_LOG For mapping table id:s to tables when receiving events.
+ */
+
+/*
+ Guilhem to Mats:
+ in the table_mapping class, the memory is allocated and never freed (until
+ destruction). So this is a good candidate for allocating inside a MEM_ROOT:
+ it gives the efficient allocation in chunks (like in expand()). So I have
+ introduced a MEM_ROOT.
+
+ Note that inheriting from Sql_alloc had no effect: it has effects only when
+ "ptr= new table_mapping" is called, and this is never called. And it would
+ then allocate from thd->mem_root which is a highly volatile object (reset
+ from example after executing each query, see dispatch_command(), it has a
+ free_root() at end); as the table_mapping object is supposed to live longer
+ than a query, it was dangerous.
+ A dedicated MEM_ROOT needs to be used, see below.
+*/
+
+#include "hash.h" /* HASH */
+
+class table_mapping {
+
+private:
+ MEM_ROOT m_mem_root;
+
+public:
+
+ enum enum_error {
+ ERR_NO_ERROR = 0,
+ ERR_LIMIT_EXCEEDED,
+ ERR_MEMORY_ALLOCATION
+ };
+
+ table_mapping();
+ ~table_mapping();
+
+ TABLE* get_table(ulong table_id);
+
+ int set_table(ulong table_id, TABLE* table);
+ int remove_table(ulong table_id);
+ void clear_tables();
+ ulong count() const { return m_table_ids.records; }
+
+private:
+ /*
+ This is a POD (Plain Old Data). Keep it that way (we apply offsetof() to
+ it, which only works for PODs)
+ */
+ struct entry {
+ ulong table_id;
+ union {
+ TABLE *table;
+ entry *next;
+ };
+ };
+
+ entry *find_entry(ulong table_id)
+ {
+ return (entry *) my_hash_search(&m_table_ids,
+ (uchar*)&table_id,
+ sizeof(table_id));
+ }
+ int expand();
+
+ /*
+ Head of the list of free entries; "free" in the sense that it's an
+ allocated entry free for use, NOT in the sense that it's freed
+ memory.
+ */
+ entry *m_free;
+
+ /* Correspondance between an id (a number) and a TABLE object */
+ HASH m_table_ids;
+};
+
+#endif
diff --git a/sql/rpl_utility.cc b/sql/rpl_utility.cc
new file mode 100644
index 00000000000..9067f1e4253
--- /dev/null
+++ b/sql/rpl_utility.cc
@@ -0,0 +1,1255 @@
+/* Copyright (c) 2006, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2013, Monty Program Ab
+
+ 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 <my_global.h>
+#include "rpl_utility.h"
+#include "log_event.h"
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+#include "rpl_rli.h"
+#include "sql_select.h"
+
+/**
+ Function to compare two size_t integers for their relative
+ order. Used below.
+ */
+int compare(size_t a, size_t b)
+{
+ if (a < b)
+ return -1;
+ if (b < a)
+ return 1;
+ return 0;
+}
+
+
+/**
+ Max value for an unsigned integer of 'bits' bits.
+
+ The somewhat contorted expression is to avoid overflow.
+ */
+uint32 uint_max(int bits) {
+ return (((1UL << (bits - 1)) - 1) << 1) | 1;
+}
+
+
+/**
+ Calculate display length for MySQL56 temporal data types from their metadata.
+ It contains fractional precision in the low 16-bit word.
+*/
+static uint32
+max_display_length_for_temporal2_field(uint32 int_display_length,
+ unsigned int metadata)
+{
+ metadata&= 0x00ff;
+ return int_display_length + metadata + (metadata ? 1 : 0);
+}
+
+
+/**
+ Compute the maximum display length of a field.
+
+ @param sql_type Type of the field
+ @param metadata The metadata from the master for the field.
+ @return Maximum length of the field in bytes.
+ */
+static uint32
+max_display_length_for_field(enum_field_types sql_type, unsigned int metadata)
+{
+ DBUG_PRINT("debug", ("sql_type: %d, metadata: 0x%x", sql_type, metadata));
+ DBUG_ASSERT(metadata >> 16 == 0);
+
+ switch (sql_type) {
+ case MYSQL_TYPE_NEWDECIMAL:
+ return metadata >> 8;
+
+ case MYSQL_TYPE_FLOAT:
+ return 12;
+
+ case MYSQL_TYPE_DOUBLE:
+ return 22;
+
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_ENUM:
+ return metadata & 0x00ff;
+
+ case MYSQL_TYPE_STRING:
+ {
+ uchar type= metadata >> 8;
+ if (type == MYSQL_TYPE_SET || type == MYSQL_TYPE_ENUM)
+ return metadata & 0xff;
+ else
+ /* This is taken from Field_string::unpack. */
+ return (((metadata >> 4) & 0x300) ^ 0x300) + (metadata & 0x00ff);
+ }
+
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_TINY:
+ return 4;
+
+ case MYSQL_TYPE_SHORT:
+ return 6;
+
+ case MYSQL_TYPE_INT24:
+ return 9;
+
+ case MYSQL_TYPE_LONG:
+ return 11;
+
+#ifdef HAVE_LONG_LONG
+ case MYSQL_TYPE_LONGLONG:
+ return 20;
+
+#endif
+ case MYSQL_TYPE_NULL:
+ return 0;
+
+ case MYSQL_TYPE_NEWDATE:
+ return 3;
+
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TIME:
+ return 3;
+
+ case MYSQL_TYPE_TIME2:
+ return max_display_length_for_temporal2_field(MIN_TIME_WIDTH, metadata);
+
+ case MYSQL_TYPE_TIMESTAMP:
+ return 4;
+
+ case MYSQL_TYPE_TIMESTAMP2:
+ return max_display_length_for_temporal2_field(MAX_DATETIME_WIDTH, metadata);
+
+ case MYSQL_TYPE_DATETIME:
+ return 8;
+
+ case MYSQL_TYPE_DATETIME2:
+ return max_display_length_for_temporal2_field(MAX_DATETIME_WIDTH, metadata);
+
+ case MYSQL_TYPE_BIT:
+ /*
+ Decode the size of the bit field from the master.
+ */
+ DBUG_ASSERT((metadata & 0xff) <= 7);
+ return 8 * (metadata >> 8U) + (metadata & 0x00ff);
+
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ return metadata;
+
+ /*
+ The actual length for these types does not really matter since
+ they are used to calc_pack_length, which ignores the given
+ length for these types.
+
+ Since we want this to be accurate for other uses, we return the
+ maximum size in bytes of these BLOBs.
+ */
+
+ case MYSQL_TYPE_TINY_BLOB:
+ return uint_max(1 * 8);
+
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ return uint_max(3 * 8);
+
+ case MYSQL_TYPE_BLOB:
+ /*
+ For the blob type, Field::real_type() lies and say that all
+ blobs are of type MYSQL_TYPE_BLOB. In that case, we have to look
+ at the length instead to decide what the max display size is.
+ */
+ return uint_max(metadata * 8);
+
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ return uint_max(4 * 8);
+
+ default:
+ return ~(uint32) 0;
+ }
+}
+
+
+/*
+ Compare the pack lengths of a source field (on the master) and a
+ target field (on the slave).
+
+ @param field Target field.
+ @param type Source field type.
+ @param metadata Source field metadata.
+
+ @retval -1 The length of the source field is smaller than the target field.
+ @retval 0 The length of the source and target fields are the same.
+ @retval 1 The length of the source field is greater than the target field.
+ */
+int compare_lengths(Field *field, enum_field_types source_type, uint16 metadata)
+{
+ DBUG_ENTER("compare_lengths");
+ size_t const source_length=
+ max_display_length_for_field(source_type, metadata);
+ size_t const target_length= field->max_display_length();
+ DBUG_PRINT("debug", ("source_length: %lu, source_type: %u,"
+ " target_length: %lu, target_type: %u",
+ (unsigned long) source_length, source_type,
+ (unsigned long) target_length, field->real_type()));
+ int result= compare(source_length, target_length);
+ DBUG_PRINT("result", ("%d", result));
+ DBUG_RETURN(result);
+}
+#endif //MYSQL_CLIENT
+/*********************************************************************
+ * table_def member definitions *
+ *********************************************************************/
+
+/*
+ This function returns the field size in raw bytes based on the type
+ and the encoded field data from the master's raw data.
+*/
+uint32 table_def::calc_field_size(uint col, uchar *master_data) const
+{
+ uint32 length= 0;
+
+ switch (type(col)) {
+ case MYSQL_TYPE_NEWDECIMAL:
+ length= my_decimal_get_binary_size(m_field_metadata[col] >> 8,
+ m_field_metadata[col] & 0xff);
+ break;
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ length= m_field_metadata[col];
+ break;
+ /*
+ The cases for SET and ENUM are include for completeness, however
+ both are mapped to type MYSQL_TYPE_STRING and their real types
+ are encoded in the field metadata.
+ */
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_STRING:
+ {
+ uchar type= m_field_metadata[col] >> 8U;
+ if ((type == MYSQL_TYPE_SET) || (type == MYSQL_TYPE_ENUM))
+ length= m_field_metadata[col] & 0x00ff;
+ else
+ {
+ /*
+ We are reading the actual size from the master_data record
+ because this field has the actual lengh stored in the first
+ byte.
+ */
+ length= (uint) *master_data + 1;
+ DBUG_ASSERT(length != 0);
+ }
+ break;
+ }
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_TINY:
+ length= 1;
+ break;
+ case MYSQL_TYPE_SHORT:
+ length= 2;
+ break;
+ case MYSQL_TYPE_INT24:
+ length= 3;
+ break;
+ case MYSQL_TYPE_LONG:
+ length= 4;
+ break;
+#ifdef HAVE_LONG_LONG
+ case MYSQL_TYPE_LONGLONG:
+ length= 8;
+ break;
+#endif
+ case MYSQL_TYPE_NULL:
+ length= 0;
+ break;
+ case MYSQL_TYPE_NEWDATE:
+ length= 3;
+ break;
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TIME:
+ length= 3;
+ break;
+ case MYSQL_TYPE_TIME2:
+ length= my_time_binary_length(m_field_metadata[col]);
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ length= 4;
+ break;
+ case MYSQL_TYPE_TIMESTAMP2:
+ length= my_timestamp_binary_length(m_field_metadata[col]);
+ break;
+ case MYSQL_TYPE_DATETIME:
+ length= 8;
+ break;
+ case MYSQL_TYPE_DATETIME2:
+ length= my_datetime_binary_length(m_field_metadata[col]);
+ break;
+ case MYSQL_TYPE_BIT:
+ {
+ /*
+ Decode the size of the bit field from the master.
+ from_len is the length in bytes from the master
+ from_bit_len is the number of extra bits stored in the master record
+ If from_bit_len is not 0, add 1 to the length to account for accurate
+ number of bytes needed.
+ */
+ uint from_len= (m_field_metadata[col] >> 8U) & 0x00ff;
+ uint from_bit_len= m_field_metadata[col] & 0x00ff;
+ DBUG_ASSERT(from_bit_len <= 7);
+ length= from_len + ((from_bit_len > 0) ? 1 : 0);
+ break;
+ }
+ case MYSQL_TYPE_VARCHAR:
+ {
+ length= m_field_metadata[col] > 255 ? 2 : 1; // c&p of Field_varstring::data_length()
+ length+= length == 1 ? (uint32) *master_data : uint2korr(master_data);
+ break;
+ }
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ {
+ /*
+ Compute the length of the data. We cannot use get_length() here
+ since it is dependent on the specific table (and also checks the
+ packlength using the internal 'table' pointer) and replication
+ is using a fixed format for storing data in the binlog.
+ */
+ switch (m_field_metadata[col]) {
+ case 1:
+ length= *master_data;
+ break;
+ case 2:
+ length= uint2korr(master_data);
+ break;
+ case 3:
+ length= uint3korr(master_data);
+ break;
+ case 4:
+ length= uint4korr(master_data);
+ break;
+ default:
+ DBUG_ASSERT(0); // Should not come here
+ break;
+ }
+
+ length+= m_field_metadata[col];
+ break;
+ }
+ default:
+ length= ~(uint32) 0;
+ }
+ return length;
+}
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+/**
+ */
+void show_sql_type(enum_field_types type, uint16 metadata, String *str, CHARSET_INFO *field_cs)
+{
+ DBUG_ENTER("show_sql_type");
+ DBUG_PRINT("enter", ("type: %d, metadata: 0x%x", type, metadata));
+
+ switch (type)
+ {
+ case MYSQL_TYPE_TINY:
+ str->set_ascii(STRING_WITH_LEN("tinyint"));
+ break;
+
+ case MYSQL_TYPE_SHORT:
+ str->set_ascii(STRING_WITH_LEN("smallint"));
+ break;
+
+ case MYSQL_TYPE_LONG:
+ str->set_ascii(STRING_WITH_LEN("int"));
+ break;
+
+ case MYSQL_TYPE_FLOAT:
+ str->set_ascii(STRING_WITH_LEN("float"));
+ break;
+
+ case MYSQL_TYPE_DOUBLE:
+ str->set_ascii(STRING_WITH_LEN("double"));
+ break;
+
+ case MYSQL_TYPE_NULL:
+ str->set_ascii(STRING_WITH_LEN("null"));
+ break;
+
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_TIMESTAMP2:
+ str->set_ascii(STRING_WITH_LEN("timestamp"));
+ break;
+
+ case MYSQL_TYPE_LONGLONG:
+ str->set_ascii(STRING_WITH_LEN("bigint"));
+ break;
+
+ case MYSQL_TYPE_INT24:
+ str->set_ascii(STRING_WITH_LEN("mediumint"));
+ break;
+
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_DATE:
+ str->set_ascii(STRING_WITH_LEN("date"));
+ break;
+
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_TIME2:
+ str->set_ascii(STRING_WITH_LEN("time"));
+ break;
+
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_DATETIME2:
+ str->set_ascii(STRING_WITH_LEN("datetime"));
+ break;
+
+ case MYSQL_TYPE_YEAR:
+ str->set_ascii(STRING_WITH_LEN("year"));
+ break;
+
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ {
+ CHARSET_INFO *cs= str->charset();
+ uint32 length=
+ cs->cset->snprintf(cs, (char*) str->ptr(), str->alloced_length(),
+ "varchar(%u)", metadata);
+ str->length(length);
+ }
+ break;
+
+ case MYSQL_TYPE_BIT:
+ {
+ CHARSET_INFO *cs= str->charset();
+ int bit_length= 8 * (metadata >> 8) + (metadata & 0xFF);
+ uint32 length=
+ cs->cset->snprintf(cs, (char*) str->ptr(), str->alloced_length(),
+ "bit(%d)", bit_length);
+ str->length(length);
+ }
+ break;
+
+ case MYSQL_TYPE_DECIMAL:
+ {
+ CHARSET_INFO *cs= str->charset();
+ uint32 length=
+ cs->cset->snprintf(cs, (char*) str->ptr(), str->alloced_length(),
+ "decimal(%d,?)", metadata);
+ str->length(length);
+ }
+ break;
+
+ case MYSQL_TYPE_NEWDECIMAL:
+ {
+ CHARSET_INFO *cs= str->charset();
+ uint32 length=
+ cs->cset->snprintf(cs, (char*) str->ptr(), str->alloced_length(),
+ "decimal(%d,%d)", metadata >> 8, metadata & 0xff);
+ str->length(length);
+ }
+ break;
+
+ case MYSQL_TYPE_ENUM:
+ str->set_ascii(STRING_WITH_LEN("enum"));
+ break;
+
+ case MYSQL_TYPE_SET:
+ str->set_ascii(STRING_WITH_LEN("set"));
+ break;
+
+ case MYSQL_TYPE_BLOB:
+ /*
+ Field::real_type() lies regarding the actual type of a BLOB, so
+ it is necessary to check the pack length to figure out what kind
+ of blob it really is.
+ */
+ switch (get_blob_type_from_length(metadata))
+ {
+ case MYSQL_TYPE_TINY_BLOB:
+ str->set_ascii(STRING_WITH_LEN("tinyblob"));
+ break;
+
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ str->set_ascii(STRING_WITH_LEN("mediumblob"));
+ break;
+
+ case MYSQL_TYPE_LONG_BLOB:
+ str->set_ascii(STRING_WITH_LEN("longblob"));
+ break;
+
+ case MYSQL_TYPE_BLOB:
+ str->set_ascii(STRING_WITH_LEN("blob"));
+ break;
+
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ break;
+
+ case MYSQL_TYPE_STRING:
+ {
+ /*
+ This is taken from Field_string::unpack.
+ */
+ CHARSET_INFO *cs= str->charset();
+ uint bytes= (((metadata >> 4) & 0x300) ^ 0x300) + (metadata & 0x00ff);
+ uint32 length=
+ cs->cset->snprintf(cs, (char*) str->ptr(), str->alloced_length(),
+ "char(%d)", bytes / field_cs->mbmaxlen);
+ str->length(length);
+ }
+ break;
+
+ case MYSQL_TYPE_GEOMETRY:
+ str->set_ascii(STRING_WITH_LEN("geometry"));
+ break;
+
+ default:
+ str->set_ascii(STRING_WITH_LEN("<unknown type>"));
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Check the order variable and print errors if the order is not
+ acceptable according to the current settings.
+
+ @param order The computed order of the conversion needed.
+ @param rli The relay log info data structure: for error reporting.
+ */
+bool is_conversion_ok(int order, Relay_log_info *rli)
+{
+ DBUG_ENTER("is_conversion_ok");
+ bool allow_non_lossy, allow_lossy;
+
+ allow_non_lossy = slave_type_conversions_options &
+ (1ULL << SLAVE_TYPE_CONVERSIONS_ALL_NON_LOSSY);
+ allow_lossy= slave_type_conversions_options &
+ (1ULL << SLAVE_TYPE_CONVERSIONS_ALL_LOSSY);
+
+ DBUG_PRINT("enter", ("order: %d, flags:%s%s", order,
+ allow_non_lossy ? " ALL_NON_LOSSY" : "",
+ allow_lossy ? " ALL_LOSSY" : ""));
+ if (order < 0 && !allow_non_lossy)
+ {
+ /* !!! Add error message saying that non-lossy conversions need to be allowed. */
+ DBUG_RETURN(false);
+ }
+
+ if (order > 0 && !allow_lossy)
+ {
+ /* !!! Add error message saying that lossy conversions need to be allowed. */
+ DBUG_RETURN(false);
+ }
+
+ DBUG_RETURN(true);
+}
+
+
+/**
+ Can a type potentially be converted to another type?
+
+ This function check if the types are convertible and what
+ conversion is required.
+
+ If conversion is not possible, and error is printed.
+
+ If conversion is possible:
+
+ - *order will be set to -1 if source type is smaller than target
+ type and a non-lossy conversion can be required. This includes
+ the case where the field types are different but types could
+ actually be converted in either direction.
+
+ - *order will be set to 0 if no conversion is required.
+
+ - *order will be set to 1 if the source type is strictly larger
+ than the target type and that conversion is potentially lossy.
+
+ @param[in] field Target field
+ @param[in] type Source field type
+ @param[in] metadata Source field metadata
+ @param[in] rli Relay log info (for error reporting)
+ @param[in] mflags Flags from the table map event
+ @param[out] order Order between source field and target field
+
+ @return @c true if conversion is possible according to the current
+ settings, @c false if conversion is not possible according to the
+ current setting.
+ */
+static bool
+can_convert_field_to(Field *field,
+ enum_field_types source_type, uint16 metadata,
+ Relay_log_info *rli, uint16 mflags,
+ int *order_var)
+{
+ DBUG_ENTER("can_convert_field_to");
+#ifndef DBUG_OFF
+ char field_type_buf[MAX_FIELD_WIDTH];
+ String field_type(field_type_buf, sizeof(field_type_buf), &my_charset_latin1);
+ field->sql_type(field_type);
+ DBUG_PRINT("enter", ("field_type: %s, target_type: %d, source_type: %d, source_metadata: 0x%x",
+ field_type.c_ptr_safe(), field->real_type(), source_type, metadata));
+#endif
+ /*
+ If the real type is the same, we need to check the metadata to
+ decide if conversions are allowed.
+ */
+ if (field->real_type() == source_type)
+ {
+ if (metadata == 0) // Metadata can only be zero if no metadata was provided
+ {
+ /*
+ If there is no metadata, we either have an old event where no
+ metadata were supplied, or a type that does not require any
+ metadata. In either case, conversion can be done but no
+ conversion table is necessary.
+ */
+ DBUG_PRINT("debug", ("Base types are identical, but there is no metadata"));
+ *order_var= 0;
+ DBUG_RETURN(true);
+ }
+
+ DBUG_PRINT("debug", ("Base types are identical, doing field size comparison"));
+ if (field->compatible_field_size(metadata, rli, mflags, order_var))
+ DBUG_RETURN(is_conversion_ok(*order_var, rli));
+ else
+ DBUG_RETURN(false);
+ }
+ else if (
+ /*
+ Conversion from MariaDB TIMESTAMP(0), TIME(0), DATETIME(0)
+ to the corresponding MySQL56 types is non-lossy.
+ */
+ (metadata == 0 &&
+ ((field->real_type() == MYSQL_TYPE_TIMESTAMP2 &&
+ source_type == MYSQL_TYPE_TIMESTAMP) ||
+ (field->real_type() == MYSQL_TYPE_TIME2 &&
+ source_type == MYSQL_TYPE_TIME) ||
+ (field->real_type() == MYSQL_TYPE_DATETIME2 &&
+ source_type == MYSQL_TYPE_DATETIME))) ||
+ /*
+ Conversion from MySQL56 TIMESTAMP(N), TIME(N), DATETIME(N)
+ to the corresponding MariaDB or MySQL55 types is non-lossy.
+ */
+ (metadata == field->decimals() &&
+ ((field->real_type() == MYSQL_TYPE_TIMESTAMP &&
+ source_type == MYSQL_TYPE_TIMESTAMP2) ||
+ (field->real_type() == MYSQL_TYPE_TIME &&
+ source_type == MYSQL_TYPE_TIME2) ||
+ (field->real_type() == MYSQL_TYPE_DATETIME &&
+ source_type == MYSQL_TYPE_DATETIME2))))
+ {
+ /*
+ TS-TODO: conversion from FSP1>FSP2.
+ */
+ *order_var= -1;
+ DBUG_RETURN(true);
+ }
+ else if (!slave_type_conversions_options)
+ DBUG_RETURN(false);
+
+ /*
+ Here, from and to will always be different. Since the types are
+ different, we cannot use the compatible_field_size() function, but
+ have to rely on hard-coded max-sizes for fields.
+ */
+
+ DBUG_PRINT("debug", ("Base types are different, checking conversion"));
+ switch (source_type) // Source type (on master)
+ {
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ switch (field->real_type())
+ {
+ case MYSQL_TYPE_NEWDECIMAL:
+ /*
+ Then the other type is either FLOAT, DOUBLE, or old style
+ DECIMAL, so we require lossy conversion.
+ */
+ *order_var= 1;
+ DBUG_RETURN(is_conversion_ok(*order_var, rli));
+
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ {
+ if (source_type == MYSQL_TYPE_NEWDECIMAL ||
+ source_type == MYSQL_TYPE_DECIMAL)
+ *order_var = 1; // Always require lossy conversions
+ else
+ *order_var= compare_lengths(field, source_type, metadata);
+ DBUG_ASSERT(*order_var != 0);
+ DBUG_RETURN(is_conversion_ok(*order_var, rli));
+ }
+
+ default:
+ DBUG_RETURN(false);
+ }
+ break;
+
+ /*
+ The length comparison check will do the correct job of comparing
+ the field lengths (in bytes) of two integer types.
+ */
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ switch (field->real_type())
+ {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ *order_var= compare_lengths(field, source_type, metadata);
+ DBUG_ASSERT(*order_var != 0);
+ DBUG_RETURN(is_conversion_ok(*order_var, rli));
+
+ default:
+ DBUG_RETURN(false);
+ }
+ break;
+
+ /*
+ Since source and target type is different, and it is not possible
+ to convert bit types to anything else, this will return false.
+ */
+ case MYSQL_TYPE_BIT:
+ DBUG_RETURN(false);
+
+ /*
+ If all conversions are disabled, it is not allowed to convert
+ between these types. Since the TEXT vs. BINARY is distinguished by
+ the charset, and the charset is not replicated, we cannot
+ currently distinguish between , e.g., TEXT and BLOB.
+ */
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ switch (field->real_type())
+ {
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ *order_var= compare_lengths(field, source_type, metadata);
+ /*
+ Here we know that the types are different, so if the order
+ gives that they do not require any conversion, we still need
+ to have non-lossy conversion enabled to allow conversion
+ between different (string) types of the same length.
+ */
+ if (*order_var == 0)
+ *order_var= -1;
+ DBUG_RETURN(is_conversion_ok(*order_var, rli));
+
+ default:
+ DBUG_RETURN(false);
+ }
+ break;
+
+ case MYSQL_TYPE_GEOMETRY:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_YEAR:
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_NULL:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_TIMESTAMP2:
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_TIME2:
+ DBUG_RETURN(false);
+ }
+ DBUG_RETURN(false); // To keep GCC happy
+}
+
+
+/**
+ Is the definition compatible with a table?
+
+ This function will compare the master table with an existing table
+ on the slave and see if they are compatible with respect to the
+ current settings of @c SLAVE_TYPE_CONVERSIONS.
+
+ If the tables are compatible and conversions are required, @c
+ *tmp_table_var will be set to a virtual temporary table with field
+ pointers for the fields that require conversions. This allow simple
+ checking of whether a conversion are to be applied or not.
+
+ If tables are compatible, but no conversions are necessary, @c
+ *tmp_table_var will be set to NULL.
+
+ @param rli_arg[in]
+ Relay log info, for error reporting.
+
+ @param table[in]
+ Table to compare with
+
+ @param tmp_table_var[out]
+ Virtual temporary table for performing conversions, if necessary.
+
+ @retval true Master table is compatible with slave table.
+ @retval false Master table is not compatible with slave table.
+*/
+bool
+table_def::compatible_with(THD *thd, rpl_group_info *rgi,
+ TABLE *table, TABLE **conv_table_var)
+ const
+{
+ /*
+ We only check the initial columns for the tables.
+ */
+ uint const cols_to_check= MY_MIN(table->s->fields, size());
+ Relay_log_info *rli= rgi->rli;
+ TABLE *tmp_table= NULL;
+
+ for (uint col= 0 ; col < cols_to_check ; ++col)
+ {
+ Field *const field= table->field[col];
+ int order;
+ if (can_convert_field_to(field, type(col), field_metadata(col), rli, m_flags, &order))
+ {
+ DBUG_PRINT("debug", ("Checking column %d -"
+ " field '%s' can be converted - order: %d",
+ col, field->field_name, order));
+ DBUG_ASSERT(order >= -1 && order <= 1);
+
+ /*
+ If order is not 0, a conversion is required, so we need to set
+ up the conversion table.
+ */
+ if (order != 0 && tmp_table == NULL)
+ {
+ /*
+ This will create the full table with all fields. This is
+ necessary to ge the correct field lengths for the record.
+ */
+ tmp_table= create_conversion_table(thd, rgi, table);
+ if (tmp_table == NULL)
+ return false;
+ /*
+ Clear all fields up to, but not including, this column.
+ */
+ for (unsigned int i= 0; i < col; ++i)
+ tmp_table->field[i]= NULL;
+ }
+
+ if (order == 0 && tmp_table != NULL)
+ tmp_table->field[col]= NULL;
+ }
+ else
+ {
+ DBUG_PRINT("debug", ("Checking column %d -"
+ " field '%s' can not be converted",
+ col, field->field_name));
+ DBUG_ASSERT(col < size() && col < table->s->fields);
+ DBUG_ASSERT(table->s->db.str && table->s->table_name.str);
+ const char *db_name= table->s->db.str;
+ const char *tbl_name= table->s->table_name.str;
+ char source_buf[MAX_FIELD_WIDTH];
+ char target_buf[MAX_FIELD_WIDTH];
+ String source_type(source_buf, sizeof(source_buf), &my_charset_latin1);
+ String target_type(target_buf, sizeof(target_buf), &my_charset_latin1);
+ show_sql_type(type(col), field_metadata(col), &source_type, field->charset());
+ field->sql_type(target_type);
+ rli->report(ERROR_LEVEL, ER_SLAVE_CONVERSION_FAILED, rgi->gtid_info(),
+ ER(ER_SLAVE_CONVERSION_FAILED),
+ col, db_name, tbl_name,
+ source_type.c_ptr_safe(), target_type.c_ptr_safe());
+ return false;
+ }
+ }
+
+#ifndef DBUG_OFF
+ if (tmp_table)
+ {
+ for (unsigned int col= 0; col < tmp_table->s->fields; ++col)
+ if (tmp_table->field[col])
+ {
+ char source_buf[MAX_FIELD_WIDTH];
+ char target_buf[MAX_FIELD_WIDTH];
+ String source_type(source_buf, sizeof(source_buf), &my_charset_latin1);
+ String target_type(target_buf, sizeof(target_buf), &my_charset_latin1);
+ tmp_table->field[col]->sql_type(source_type);
+ table->field[col]->sql_type(target_type);
+ DBUG_PRINT("debug", ("Field %s - conversion required."
+ " Source type: '%s', Target type: '%s'",
+ tmp_table->field[col]->field_name,
+ source_type.c_ptr_safe(), target_type.c_ptr_safe()));
+ }
+ }
+#endif
+
+ *conv_table_var= tmp_table;
+ return true;
+}
+
+/**
+ Create a conversion table.
+
+ If the function is unable to create the conversion table, an error
+ will be printed and NULL will be returned.
+
+ @return Pointer to conversion table, or NULL if unable to create
+ conversion table.
+ */
+
+TABLE *table_def::create_conversion_table(THD *thd, rpl_group_info *rgi,
+ TABLE *target_table) const
+{
+ DBUG_ENTER("table_def::create_conversion_table");
+
+ List<Create_field> field_list;
+ TABLE *conv_table= NULL;
+ Relay_log_info *rli= rgi->rli;
+ /*
+ At slave, columns may differ. So we should create
+ MY_MIN(columns@master, columns@slave) columns in the
+ conversion table.
+ */
+ uint const cols_to_create= MY_MIN(target_table->s->fields, size());
+ for (uint col= 0 ; col < cols_to_create; ++col)
+ {
+ Create_field *field_def=
+ (Create_field*) alloc_root(thd->mem_root, sizeof(Create_field));
+ if (field_list.push_back(field_def))
+ DBUG_RETURN(NULL);
+
+ uint decimals= 0;
+ TYPELIB* interval= NULL;
+ uint pack_length= 0;
+ uint32 max_length=
+ max_display_length_for_field(type(col), field_metadata(col));
+
+ switch(type(col))
+ {
+ int precision;
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ interval= static_cast<Field_enum*>(target_table->field[col])->typelib;
+ pack_length= field_metadata(col) & 0x00ff;
+ break;
+
+ case MYSQL_TYPE_NEWDECIMAL:
+ /*
+ The display length of a DECIMAL type is not the same as the
+ length that should be supplied to make_field, so we correct
+ the length here.
+ */
+ precision= field_metadata(col) >> 8;
+ decimals= field_metadata(col) & 0x00ff;
+ max_length=
+ my_decimal_precision_to_length(precision, decimals, FALSE);
+ break;
+
+ case MYSQL_TYPE_DECIMAL:
+ sql_print_error("In RBR mode, Slave received incompatible DECIMAL field "
+ "(old-style decimal field) from Master while creating "
+ "conversion table. Please consider changing datatype on "
+ "Master to new style decimal by executing ALTER command for"
+ " column Name: %s.%s.%s.",
+ target_table->s->db.str,
+ target_table->s->table_name.str,
+ target_table->field[col]->field_name);
+ goto err;
+
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ pack_length= field_metadata(col) & 0x00ff;
+ break;
+
+ default:
+ break;
+ }
+
+ DBUG_PRINT("debug", ("sql_type: %d, target_field: '%s', max_length: %d, decimals: %d,"
+ " maybe_null: %d, unsigned_flag: %d, pack_length: %u",
+ binlog_type(col), target_table->field[col]->field_name,
+ max_length, decimals, TRUE, FALSE, pack_length));
+ field_def->init_for_tmp_table(type(col),
+ max_length,
+ decimals,
+ TRUE, // maybe_null
+ FALSE, // unsigned_flag
+ pack_length);
+ field_def->charset= target_table->field[col]->charset();
+ field_def->interval= interval;
+ }
+
+ conv_table= create_virtual_tmp_table(thd, field_list);
+
+err:
+ if (conv_table == NULL)
+ rli->report(ERROR_LEVEL, ER_SLAVE_CANT_CREATE_CONVERSION, rgi->gtid_info(),
+ ER(ER_SLAVE_CANT_CREATE_CONVERSION),
+ target_table->s->db.str,
+ target_table->s->table_name.str);
+ DBUG_RETURN(conv_table);
+}
+#endif /* MYSQL_CLIENT */
+
+table_def::table_def(unsigned char *types, ulong size,
+ uchar *field_metadata, int metadata_size,
+ uchar *null_bitmap, uint16 flags)
+ : m_size(size), m_type(0), m_field_metadata_size(metadata_size),
+ m_field_metadata(0), m_null_bits(0), m_flags(flags),
+ m_memory(NULL)
+{
+ m_memory= (uchar *)my_multi_malloc(MYF(MY_WME),
+ &m_type, size,
+ &m_field_metadata,
+ size * sizeof(uint16),
+ &m_null_bits, (size + 7) / 8,
+ NULL);
+
+ bzero(m_field_metadata, size * sizeof(uint16));
+
+ if (m_type)
+ memcpy(m_type, types, size);
+ else
+ m_size= 0;
+ /*
+ Extract the data from the table map into the field metadata array
+ iff there is field metadata. The variable metadata_size will be
+ 0 if we are replicating from an older version server since no field
+ metadata was written to the table map. This can also happen if
+ there were no fields in the master that needed extra metadata.
+ */
+ if (m_size && metadata_size)
+ {
+ int index= 0;
+ for (unsigned int i= 0; i < m_size; i++)
+ {
+ switch (binlog_type(i)) {
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_DOUBLE:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_GEOMETRY:
+ {
+ /*
+ These types store a single byte.
+ */
+ m_field_metadata[i]= field_metadata[index];
+ index++;
+ break;
+ }
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_STRING:
+ {
+ uint16 x= field_metadata[index++] << 8U; // real_type
+ x+= field_metadata[index++]; // pack or field length
+ m_field_metadata[i]= x;
+ break;
+ }
+ case MYSQL_TYPE_BIT:
+ {
+ uint16 x= field_metadata[index++];
+ x = x + (field_metadata[index++] << 8U);
+ m_field_metadata[i]= x;
+ break;
+ }
+ case MYSQL_TYPE_VARCHAR:
+ {
+ /*
+ These types store two bytes.
+ */
+ char *ptr= (char *)&field_metadata[index];
+ m_field_metadata[i]= uint2korr(ptr);
+ index= index + 2;
+ break;
+ }
+ case MYSQL_TYPE_NEWDECIMAL:
+ {
+ uint16 x= field_metadata[index++] << 8U; // precision
+ x+= field_metadata[index++]; // decimals
+ m_field_metadata[i]= x;
+ break;
+ }
+ case MYSQL_TYPE_TIME2:
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_TIMESTAMP2:
+ m_field_metadata[i]= field_metadata[index++];
+ break;
+ default:
+ m_field_metadata[i]= 0;
+ break;
+ }
+ }
+ }
+ if (m_size && null_bitmap)
+ memcpy(m_null_bits, null_bitmap, (m_size + 7) / 8);
+}
+
+
+table_def::~table_def()
+{
+ my_free(m_memory);
+#ifndef DBUG_OFF
+ m_type= 0;
+ m_size= 0;
+#endif
+}
+
+
+/**
+ @param even_buf point to the buffer containing serialized event
+ @param event_len length of the event accounting possible checksum alg
+
+ @return TRUE if test fails
+ FALSE as success
+*/
+bool event_checksum_test(uchar *event_buf, ulong event_len, uint8 alg)
+{
+ bool res= FALSE;
+ uint16 flags= 0; // to store in FD's buffer flags orig value
+
+ if (alg != BINLOG_CHECKSUM_ALG_OFF && alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ {
+ ha_checksum incoming;
+ ha_checksum computed;
+
+ if (event_buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT)
+ {
+#ifndef DBUG_OFF
+ int8 fd_alg= event_buf[event_len - BINLOG_CHECKSUM_LEN -
+ BINLOG_CHECKSUM_ALG_DESC_LEN];
+#endif
+ /*
+ FD event is checksummed and therefore verified w/o the binlog-in-use flag
+ */
+ flags= uint2korr(event_buf + FLAGS_OFFSET);
+ if (flags & LOG_EVENT_BINLOG_IN_USE_F)
+ event_buf[FLAGS_OFFSET] &= ~LOG_EVENT_BINLOG_IN_USE_F;
+ /*
+ The only algorithm currently is CRC32. Zero indicates
+ the binlog file is checksum-free *except* the FD-event.
+ */
+ DBUG_ASSERT(fd_alg == BINLOG_CHECKSUM_ALG_CRC32 || fd_alg == 0);
+ DBUG_ASSERT(alg == BINLOG_CHECKSUM_ALG_CRC32);
+ /*
+ Complile time guard to watch over the max number of alg
+ */
+ compile_time_assert(BINLOG_CHECKSUM_ALG_ENUM_END <= 0x80);
+ }
+ incoming= uint4korr(event_buf + event_len - BINLOG_CHECKSUM_LEN);
+ computed= my_checksum(0L, NULL, 0);
+ /* checksum the event content but the checksum part itself */
+ computed= my_checksum(computed, (const uchar*) event_buf,
+ event_len - BINLOG_CHECKSUM_LEN);
+ if (flags != 0)
+ {
+ /* restoring the orig value of flags of FD */
+ DBUG_ASSERT(event_buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT);
+ event_buf[FLAGS_OFFSET]= (uchar) flags;
+ }
+ res= !(computed == incoming);
+ }
+ return DBUG_EVALUATE_IF("simulate_checksum_test_failure", TRUE, res);
+}
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+
+Deferred_log_events::Deferred_log_events(Relay_log_info *rli) : last_added(NULL)
+{
+ my_init_dynamic_array(&array, sizeof(Log_event *), 32, 16, MYF(0));
+}
+
+Deferred_log_events::~Deferred_log_events()
+{
+ delete_dynamic(&array);
+}
+
+int Deferred_log_events::add(Log_event *ev)
+{
+ last_added= ev;
+ insert_dynamic(&array, (uchar*) &ev);
+ return 0;
+}
+
+bool Deferred_log_events::is_empty()
+{
+ return array.elements == 0;
+}
+
+bool Deferred_log_events::execute(rpl_group_info *rgi)
+{
+ bool res= false;
+ DBUG_ENTER("Deferred_log_events::execute");
+ DBUG_ASSERT(rgi->deferred_events_collecting);
+
+ rgi->deferred_events_collecting= false;
+ for (uint i= 0; !res && i < array.elements; i++)
+ {
+ Log_event *ev= (* (Log_event **)
+ dynamic_array_ptr(&array, i));
+ res= ev->apply_event(rgi);
+ }
+ rgi->deferred_events_collecting= true;
+ DBUG_RETURN(res);
+}
+
+void Deferred_log_events::rewind()
+{
+ /*
+ Reset preceding Query log event events which execution was
+ deferred because of slave side filtering.
+ */
+ if (!is_empty())
+ {
+ for (uint i= 0; i < array.elements; i++)
+ {
+ Log_event *ev= *(Log_event **) dynamic_array_ptr(&array, i);
+ delete ev;
+ }
+ last_added= NULL;
+ if (array.elements > array.max_element)
+ freeze_size(&array);
+ reset_dynamic(&array);
+ }
+ last_added= NULL;
+}
+
+#endif
+
diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h
new file mode 100644
index 00000000000..ed0ce16363b
--- /dev/null
+++ b/sql/rpl_utility.h
@@ -0,0 +1,307 @@
+/*
+ Copyright (c) 2006, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifndef RPL_UTILITY_H
+#define RPL_UTILITY_H
+
+#ifndef __cplusplus
+#error "Don't include this C++ header file from a non-C++ file!"
+#endif
+
+#include "sql_priv.h"
+#include "m_string.h" /* bzero, memcpy */
+#ifdef MYSQL_SERVER
+#include "table.h" /* TABLE_LIST */
+#endif
+#include "mysql_com.h"
+
+class Relay_log_info;
+class Log_event;
+struct rpl_group_info;
+
+/**
+ A table definition from the master.
+
+ The responsibilities of this class is:
+ - Extract and decode table definition data from the table map event
+ - Check if table definition in table map is compatible with table
+ definition on slave
+ */
+
+class table_def
+{
+public:
+ /**
+ Constructor.
+
+ @param types Array of types, each stored as a byte
+ @param size Number of elements in array 'types'
+ @param field_metadata Array of extra information about fields
+ @param metadata_size Size of the field_metadata array
+ @param null_bitmap The bitmap of fields that can be null
+ */
+ table_def(unsigned char *types, ulong size, uchar *field_metadata,
+ int metadata_size, uchar *null_bitmap, uint16 flags);
+
+ ~table_def();
+
+ /**
+ Return the number of fields there is type data for.
+
+ @return The number of fields that there is type data for.
+ */
+ ulong size() const { return m_size; }
+
+
+ /**
+ Returns internal binlog type code for one field,
+ without translation to real types.
+ */
+ enum_field_types binlog_type(ulong index) const
+ {
+ return static_cast<enum_field_types>(m_type[index]);
+ }
+ /*
+ Return a representation of the type data for one field.
+
+ @param index Field index to return data for
+
+ @return Will return a representation of the type data for field
+ <code>index</code>. Currently, only the type identifier is
+ returned.
+ */
+ enum_field_types type(ulong index) const
+ {
+ DBUG_ASSERT(index < m_size);
+ /*
+ If the source type is MYSQL_TYPE_STRING, it can in reality be
+ either MYSQL_TYPE_STRING, MYSQL_TYPE_ENUM, or MYSQL_TYPE_SET, so
+ we might need to modify the type to get the real type.
+ */
+ enum_field_types source_type= binlog_type(index);
+ uint16 source_metadata= m_field_metadata[index];
+ switch (source_type)
+ {
+ case MYSQL_TYPE_STRING:
+ {
+ int real_type= source_metadata >> 8;
+ if (real_type == MYSQL_TYPE_ENUM || real_type == MYSQL_TYPE_SET)
+ source_type= static_cast<enum_field_types>(real_type);
+ break;
+ }
+
+ /*
+ This type has not been used since before row-based replication,
+ so we can safely assume that it really is MYSQL_TYPE_NEWDATE.
+ */
+ case MYSQL_TYPE_DATE:
+ source_type= MYSQL_TYPE_NEWDATE;
+ break;
+
+ default:
+ /* Do nothing */
+ break;
+ }
+
+ return source_type;
+ }
+
+
+ /*
+ This function allows callers to get the extra field data from the
+ table map for a given field. If there is no metadata for that field
+ or there is no extra metadata at all, the function returns 0.
+
+ The function returns the value for the field metadata for column at
+ position indicated by index. As mentioned, if the field was a type
+ that stores field metadata, that value is returned else zero (0) is
+ returned. This method is used in the unpack() methods of the
+ corresponding fields to properly extract the data from the binary log
+ in the event that the master's field is smaller than the slave.
+ */
+ uint16 field_metadata(uint index) const
+ {
+ DBUG_ASSERT(index < m_size);
+ if (m_field_metadata_size)
+ return m_field_metadata[index];
+ else
+ return 0;
+ }
+
+ /*
+ This function returns whether the field on the master can be null.
+ This value is derived from field->maybe_null().
+ */
+ my_bool maybe_null(uint index) const
+ {
+ DBUG_ASSERT(index < m_size);
+ return ((m_null_bits[(index / 8)] &
+ (1 << (index % 8))) == (1 << (index %8)));
+ }
+
+ /*
+ This function returns the field size in raw bytes based on the type
+ and the encoded field data from the master's raw data. This method can
+ be used for situations where the slave needs to skip a column (e.g.,
+ WL#3915) or needs to advance the pointer for the fields in the raw
+ data from the master to a specific column.
+ */
+ uint32 calc_field_size(uint col, uchar *master_data) const;
+
+ /**
+ Decide if the table definition is compatible with a table.
+
+ Compare the definition with a table to see if it is compatible
+ with it.
+
+ A table definition is compatible with a table if:
+ - The columns types of the table definition is a (not
+ necessarily proper) prefix of the column type of the table.
+
+ - The other way around.
+
+ - Each column on the master that also exists on the slave can be
+ converted according to the current settings of @c
+ SLAVE_TYPE_CONVERSIONS.
+
+ @param thd
+ @param rli Pointer to relay log info
+ @param table Pointer to table to compare with.
+
+ @param[out] tmp_table_var Pointer to temporary table for holding
+ conversion table.
+
+ @retval 1 if the table definition is not compatible with @c table
+ @retval 0 if the table definition is compatible with @c table
+ */
+#ifndef MYSQL_CLIENT
+ bool compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table,
+ TABLE **conv_table_var) const;
+
+ /**
+ Create a virtual in-memory temporary table structure.
+
+ The table structure has records and field array so that a row can
+ be unpacked into the record for further processing.
+
+ In the virtual table, each field that requires conversion will
+ have a non-NULL value, while fields that do not require
+ conversion will have a NULL value.
+
+ Some information that is missing in the events, such as the
+ character set for string types, are taken from the table that the
+ field is going to be pushed into, so the target table that the data
+ eventually need to be pushed into need to be supplied.
+
+ @param thd Thread to allocate memory from.
+ @param rli Relay log info structure, for error reporting.
+ @param target_table Target table for fields.
+
+ @return A pointer to a temporary table with memory allocated in the
+ thread's memroot, NULL if the table could not be created
+ */
+ TABLE *create_conversion_table(THD *thd, rpl_group_info *rgi,
+ TABLE *target_table) const;
+#endif
+
+
+private:
+ ulong m_size; // Number of elements in the types array
+ unsigned char *m_type; // Array of type descriptors
+ uint m_field_metadata_size;
+ uint16 *m_field_metadata;
+ uchar *m_null_bits;
+ uint16 m_flags; // Table flags
+ uchar *m_memory;
+};
+
+
+#ifndef MYSQL_CLIENT
+/**
+ Extend the normal table list with a few new fields needed by the
+ slave thread, but nowhere else.
+ */
+struct RPL_TABLE_LIST
+ : public TABLE_LIST
+{
+ bool m_tabledef_valid;
+ table_def m_tabledef;
+ TABLE *m_conv_table;
+ bool master_had_triggers;
+};
+
+
+/* Anonymous namespace for template functions/classes */
+CPP_UNNAMED_NS_START
+
+ /*
+ Smart pointer that will automatically call my_afree (a macro) when
+ the pointer goes out of scope. This is used so that I do not have
+ to remember to call my_afree() before each return. There is no
+ overhead associated with this, since all functions are inline.
+
+ I (Matz) would prefer to use the free function as a template
+ parameter, but that is not possible when the "function" is a
+ macro.
+ */
+ template <class Obj>
+ class auto_afree_ptr
+ {
+ Obj* m_ptr;
+ public:
+ auto_afree_ptr(Obj* ptr) : m_ptr(ptr) { }
+ ~auto_afree_ptr() { if (m_ptr) my_afree(m_ptr); }
+ void assign(Obj* ptr) {
+ /* Only to be called if it hasn't been given a value before. */
+ DBUG_ASSERT(m_ptr == NULL);
+ m_ptr= ptr;
+ }
+ Obj* get() { return m_ptr; }
+ };
+
+CPP_UNNAMED_NS_END
+
+class Deferred_log_events
+{
+private:
+ DYNAMIC_ARRAY array;
+ Log_event *last_added;
+
+public:
+ Deferred_log_events(Relay_log_info *rli);
+ ~Deferred_log_events();
+ /* queue for exection at Query-log-event time prior the Query */
+ int add(Log_event *ev);
+ bool is_empty();
+ bool execute(struct rpl_group_info *rgi);
+ void rewind();
+ bool is_last(Log_event *ev) { return ev == last_added; };
+};
+
+#endif
+
+// NB. number of printed bit values is limited to sizeof(buf) - 1
+#define DBUG_PRINT_BITSET(N,FRM,BS) \
+ do { \
+ char buf[256]; \
+ uint i; \
+ for (i = 0 ; i < MY_MIN(sizeof(buf) - 1, (BS)->n_bits) ; i++) \
+ buf[i] = bitmap_is_set((BS), i) ? '1' : '0'; \
+ buf[i] = '\0'; \
+ DBUG_PRINT((N), ((FRM), buf)); \
+ } while (0)
+
+#endif /* RPL_UTILITY_H */
diff --git a/sql/scheduler.cc b/sql/scheduler.cc
new file mode 100644
index 00000000000..a9b253e478a
--- /dev/null
+++ b/sql/scheduler.cc
@@ -0,0 +1,153 @@
+/* Copyright (c) 2007, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2012, 2014, SkySQL Ab.
+
+ 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 */
+
+/*
+ Implementation for the thread scheduler
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma implementation
+#endif
+
+#include "sql_connect.h" // init_new_connection_handler_thread
+#include "scheduler.h"
+#include "mysqld.h"
+#include "sql_class.h"
+#include "sql_callback.h"
+#include <violite.h>
+
+/*
+ End connection, in case when we are using 'no-threads'
+*/
+
+static bool no_threads_end(THD *thd, bool put_in_cache)
+{
+ unlink_thd(thd);
+ return 1; // Abort handle_one_connection
+}
+
+/** @internal
+ Helper functions to allow mysys to call the thread scheduler when
+ waiting for locks.
+*/
+
+/**@{*/
+extern "C"
+{
+static void scheduler_wait_lock_begin(void) {
+ thd_wait_begin(NULL, THD_WAIT_TABLE_LOCK);
+}
+
+static void scheduler_wait_lock_end(void) {
+ thd_wait_end(NULL);
+}
+
+static void scheduler_wait_sync_begin(void) {
+ thd_wait_begin(NULL, THD_WAIT_SYNC);
+}
+
+static void scheduler_wait_sync_end(void) {
+ thd_wait_end(NULL);
+}
+
+static void scheduler_wait_net_begin(void) {
+ thd_wait_begin(NULL, THD_WAIT_NET);
+}
+
+static void scheduler_wait_net_end(void) {
+ thd_wait_end(NULL);
+}
+
+};
+/**@}*/
+
+/**
+ Common scheduler init function.
+
+ The scheduler is either initialized by calling
+ one_thread_scheduler() or one_thread_per_connection_scheduler() in
+ mysqld.cc, so this init function will always be called.
+ */
+void scheduler_init() {
+ thr_set_lock_wait_callback(scheduler_wait_lock_begin,
+ scheduler_wait_lock_end);
+ thr_set_sync_wait_callback(scheduler_wait_sync_begin,
+ scheduler_wait_sync_end);
+
+ vio_set_wait_callback(scheduler_wait_net_begin,
+ scheduler_wait_net_end);
+}
+
+
+/**
+ Kill notification callback, used by one-thread-per-connection
+ and threadpool scheduler.
+
+ Wakes up a thread that is stuck in read/poll/epoll/event-poll
+ routines used by threadpool, such that subsequent attempt to
+ read from client connection will result in IO error.
+*/
+
+void post_kill_notification(THD *thd)
+{
+ DBUG_ENTER("post_kill_notification");
+ if (current_thd == thd || thd->system_thread)
+ DBUG_VOID_RETURN;
+
+ if (thd->net.vio)
+ vio_shutdown(thd->net.vio, SHUT_RD);
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Initialize scheduler for --thread-handling=one-thread-per-connection
+*/
+
+#ifndef EMBEDDED_LIBRARY
+
+
+void one_thread_per_connection_scheduler(scheduler_functions *func,
+ ulong *arg_max_connections,
+ uint *arg_connection_count)
+{
+ scheduler_init();
+ func->max_threads= *arg_max_connections + 1;
+ func->max_connections= arg_max_connections;
+ func->connection_count= arg_connection_count;
+ func->init_new_connection_thread= init_new_connection_handler_thread;
+ func->add_connection= create_thread_to_handle_connection;
+ func->end_thread= one_thread_per_connection_end;
+ func->post_kill_notification= post_kill_notification;
+}
+#endif
+
+/*
+ Initailize scheduler for --thread-handling=no-threads
+*/
+
+void one_thread_scheduler(scheduler_functions *func)
+{
+ scheduler_init();
+ func->max_threads= 1;
+ func->max_connections= &max_connections;
+ func->connection_count= &connection_count;
+#ifndef EMBEDDED_LIBRARY
+ func->init_new_connection_thread= init_new_connection_handler_thread;
+ func->add_connection= handle_connection_in_main_thread;
+#endif
+ func->end_thread= no_threads_end;
+}
+
diff --git a/sql/scheduler.h b/sql/scheduler.h
new file mode 100644
index 00000000000..f7aff377eac
--- /dev/null
+++ b/sql/scheduler.h
@@ -0,0 +1,111 @@
+#ifndef SCHEDULER_INCLUDED
+#define SCHEDULER_INCLUDED
+
+/* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2012, Monty Program Ab
+
+ 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 */
+
+/*
+ Classes for the thread scheduler
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface
+#endif
+
+#include <my_global.h>
+
+class THD;
+
+/* Functions used when manipulating threads */
+
+struct scheduler_functions
+{
+ uint max_threads, *connection_count;
+ ulong *max_connections;
+ bool (*init)(void);
+ bool (*init_new_connection_thread)(void);
+ void (*add_connection)(THD *thd);
+ void (*thd_wait_begin)(THD *thd, int wait_type);
+ void (*thd_wait_end)(THD *thd);
+ void (*post_kill_notification)(THD *thd);
+ bool (*end_thread)(THD *thd, bool cache_thread);
+ void (*end)(void);
+};
+
+
+/**
+ Scheduler types enumeration.
+
+ The default of --thread-handling is the first one in the
+ thread_handling_names array, this array has to be consistent with
+ the order in this array, so to change default one has to change the
+ first entry in this enum and the first entry in the
+ thread_handling_names array.
+
+ @note The last entry of the enumeration is also used to mark the
+ thread handling as dynamic. In this case the name of the thread
+ handling is fetched from the name of the plugin that implements it.
+*/
+enum scheduler_types
+{
+ /*
+ The default of --thread-handling is the first one in the
+ thread_handling_names array, this array has to be consistent with
+ the order in this array, so to change default one has to change
+ the first entry in this enum and the first entry in the
+ thread_handling_names array.
+ */
+ SCHEDULER_ONE_THREAD_PER_CONNECTION=0,
+ SCHEDULER_NO_THREADS,
+ SCHEDULER_TYPES_COUNT
+};
+
+void one_thread_per_connection_scheduler(scheduler_functions *func,
+ ulong *arg_max_connections, uint *arg_connection_count);
+void one_thread_scheduler(scheduler_functions *func);
+
+extern void scheduler_init();
+extern void post_kill_notification(THD *);
+/*
+ To be used for pool-of-threads (implemeneted differently on various OSs)
+*/
+struct thd_scheduler
+{
+public:
+ /*
+ Thread instrumentation for the user job.
+ This member holds the instrumentation while the user job is not run
+ by a thread.
+
+ Note that this member is not conditionally declared
+ (ifdef HAVE_PSI_INTERFACE), because doing so will change the binary
+ layout of THD, which is exposed to plugin code that may be compiled
+ differently.
+ */
+ PSI_thread *m_psi;
+ void *data; /* scheduler-specific data structure */
+};
+
+#ifdef HAVE_POOL_OF_THREADS
+void pool_of_threads_scheduler(scheduler_functions* func,
+ ulong *arg_max_connections,
+ uint *arg_connection_count);
+#else
+#define pool_of_threads_scheduler(A,B,C) \
+ one_thread_per_connection_scheduler(A, B, C)
+#endif /*HAVE_POOL_OF_THREADS*/
+
+#endif /* SCHEDULER_INCLUDED */
diff --git a/sql/set_var.cc b/sql/set_var.cc
new file mode 100644
index 00000000000..0b9699e39f7
--- /dev/null
+++ b/sql/set_var.cc
@@ -0,0 +1,901 @@
+/* Copyright (c) 2002, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2014, SkySQL Ab.
+
+ 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 */
+
+/* variable declarations are in sys_vars.cc now !!! */
+
+#include "sql_plugin.h" // Includes my_global.h
+#include "sql_class.h" // set_var.h: session_var_ptr
+#include "set_var.h"
+#include "sql_priv.h"
+#include "unireg.h"
+#include "mysqld.h" // lc_messages_dir
+#include "sys_vars_shared.h"
+#include "transaction.h"
+#include "sql_locale.h" // my_locale_by_number,
+ // my_locale_by_name
+#include "strfunc.h" // find_set_from_flags, find_set
+#include "sql_parse.h" // check_global_access
+#include "sql_table.h" // reassign_keycache_tables
+#include "sql_time.h" // date_time_format_copy,
+ // date_time_format_make
+#include "derror.h"
+#include "tztime.h" // my_tz_find, my_tz_SYSTEM, struct Time_zone
+#include "sql_acl.h" // SUPER_ACL
+#include "sql_select.h" // free_underlaid_joins
+#include "sql_view.h" // updatable_views_with_limit_typelib
+#include "lock.h" // lock_global_read_lock,
+ // make_global_read_lock_block_commit,
+ // unlock_global_read_lock
+
+static HASH system_variable_hash;
+static PolyLock_mutex PLock_global_system_variables(&LOCK_global_system_variables);
+
+/**
+ Return variable name and length for hashing of variables.
+*/
+
+static uchar *get_sys_var_length(const sys_var *var, size_t *length,
+ my_bool first)
+{
+ *length= var->name.length;
+ return (uchar*) var->name.str;
+}
+
+sys_var_chain all_sys_vars = { NULL, NULL };
+
+int sys_var_init()
+{
+ DBUG_ENTER("sys_var_init");
+
+ /* Must be already initialized. */
+ DBUG_ASSERT(system_charset_info != NULL);
+
+ if (my_hash_init(&system_variable_hash, system_charset_info, 100, 0,
+ 0, (my_hash_get_key) get_sys_var_length, 0, HASH_UNIQUE))
+ goto error;
+
+ if (mysql_add_sys_var_chain(all_sys_vars.first))
+ goto error;
+
+ DBUG_RETURN(0);
+
+error:
+ fprintf(stderr, "failed to initialize System variables");
+ DBUG_RETURN(1);
+}
+
+int sys_var_add_options(DYNAMIC_ARRAY *long_options, int parse_flags)
+{
+ uint saved_elements= long_options->elements;
+
+ DBUG_ENTER("sys_var_add_options");
+
+ for (sys_var *var=all_sys_vars.first; var; var= var->next)
+ {
+ if (var->register_option(long_options, parse_flags))
+ goto error;
+ }
+
+ DBUG_RETURN(0);
+
+error:
+ fprintf(stderr, "failed to initialize System variables");
+ long_options->elements= saved_elements;
+ DBUG_RETURN(1);
+}
+
+void sys_var_end()
+{
+ DBUG_ENTER("sys_var_end");
+
+ my_hash_free(&system_variable_hash);
+
+ for (sys_var *var=all_sys_vars.first; var; var= var->next)
+ var->cleanup();
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ sys_var constructor
+
+ @param chain variables are linked into chain for mysql_add_sys_var_chain()
+ @param name_arg the name of the variable. Must be 0-terminated and exist
+ for the liftime of the sys_var object. @sa my_option::name
+ @param comment shown in mysqld --help, @sa my_option::comment
+ @param flags_arg or'ed flag_enum values
+ @param off offset of the global variable value from the
+ &global_system_variables.
+ @param getopt_id -1 for no command-line option, otherwise @sa my_option::id
+ @param getopt_arg_type @sa my_option::arg_type
+ @param show_val_type_arg what value_ptr() returns for sql_show.cc
+ @param def_val default value, @sa my_option::def_value
+ @param lock mutex or rw_lock that protects the global variable
+ *in addition* to LOCK_global_system_variables.
+ @param binlog_status_enum @sa binlog_status_enum
+ @param on_check_func a function to be called at the end of sys_var::check,
+ put your additional checks here
+ @param on_update_func a function to be called at the end of sys_var::update,
+ any post-update activity should happen here
+ @param substitute If non-NULL, this variable is deprecated and the
+ string describes what one should use instead. If an empty string,
+ the variable is deprecated but no replacement is offered.
+*/
+sys_var::sys_var(sys_var_chain *chain, const char *name_arg,
+ const char *comment, int flags_arg, ptrdiff_t off,
+ int getopt_id, enum get_opt_arg_type getopt_arg_type,
+ SHOW_TYPE show_val_type_arg, longlong def_val,
+ PolyLock *lock, enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func,
+ on_update_function on_update_func,
+ const char *substitute) :
+ next(0),
+ binlog_status(binlog_status_arg),
+ flags(flags_arg), show_val_type(show_val_type_arg),
+ guard(lock), offset(off), on_check(on_check_func), on_update(on_update_func),
+ deprecation_substitute(substitute),
+ is_os_charset(FALSE)
+{
+ /*
+ There is a limitation in handle_options() related to short options:
+ - either all short options should be declared when parsing in multiple stages,
+ - or none should be declared.
+ Because a lot of short options are used in the normal parsing phase
+ for mysqld, we enforce here that no short option is present
+ in the first (PARSE_EARLY) stage.
+ See handle_options() for details.
+ */
+ DBUG_ASSERT(!(flags & PARSE_EARLY) || getopt_id <= 0 || getopt_id >= 255);
+
+ name.str= name_arg; // ER_NO_DEFAULT relies on 0-termination of name_arg
+ name.length= strlen(name_arg); // and so does this.
+ DBUG_ASSERT(name.length <= NAME_CHAR_LEN);
+
+ bzero(&option, sizeof(option));
+ option.name= name_arg;
+ option.id= getopt_id;
+ option.comment= comment;
+ option.arg_type= getopt_arg_type;
+ option.value= (uchar **)global_var_ptr();
+ option.def_value= def_val;
+
+ if (chain->last)
+ chain->last->next= this;
+ else
+ chain->first= this;
+ chain->last= this;
+}
+
+bool sys_var::update(THD *thd, set_var *var)
+{
+ enum_var_type type= var->type;
+ if (type == OPT_GLOBAL || scope() == GLOBAL)
+ {
+ /*
+ Yes, both locks need to be taken before an update, just as
+ both are taken to get a value. If we'll take only 'guard' here,
+ then value_ptr() for strings won't be safe in SHOW VARIABLES anymore,
+ to make it safe we'll need value_ptr_unlock().
+ */
+ AutoWLock lock1(&PLock_global_system_variables);
+ AutoWLock lock2(guard);
+ return global_update(thd, var) ||
+ (on_update && on_update(this, thd, OPT_GLOBAL));
+ }
+ else
+ return session_update(thd, var) ||
+ (on_update && on_update(this, thd, OPT_SESSION));
+}
+
+uchar *sys_var::session_value_ptr(THD *thd, LEX_STRING *base)
+{
+ return session_var_ptr(thd);
+}
+
+uchar *sys_var::global_value_ptr(THD *thd, LEX_STRING *base)
+{
+ return global_var_ptr();
+}
+
+bool sys_var::check(THD *thd, set_var *var)
+{
+ if ((var->value && do_check(thd, var))
+ || (on_check && on_check(this, thd, var)))
+ {
+ if (!thd->is_error())
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), system_charset_info), *res;
+
+ if (!var->value)
+ {
+ str.set(STRING_WITH_LEN("DEFAULT"), &my_charset_latin1);
+ res= &str;
+ }
+ else if (!(res=var->value->val_str(&str)))
+ {
+ str.set(STRING_WITH_LEN("NULL"), &my_charset_latin1);
+ res= &str;
+ }
+ ErrConvString err(res);
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name.str, err.ptr());
+ }
+ return true;
+ }
+ return false;
+}
+
+uchar *sys_var::value_ptr(THD *thd, enum_var_type type, LEX_STRING *base)
+{
+ if (type == OPT_GLOBAL || scope() == GLOBAL)
+ {
+ mysql_mutex_assert_owner(&LOCK_global_system_variables);
+ AutoRLock lock(guard);
+ return global_value_ptr(thd, base);
+ }
+ else
+ return session_value_ptr(thd, base);
+}
+
+bool sys_var::set_default(THD *thd, set_var* var)
+{
+ if (var->type == OPT_GLOBAL || scope() == GLOBAL)
+ global_save_default(thd, var);
+ else
+ session_save_default(thd, var);
+
+ return check(thd, var) || update(thd, var);
+}
+
+
+#define do_num_val(T,CMD) \
+do { \
+ mysql_mutex_lock(&LOCK_global_system_variables); \
+ T val= *(T*) value_ptr(thd, type, base); \
+ mysql_mutex_unlock(&LOCK_global_system_variables); \
+ CMD; \
+} while (0)
+
+#define case_for_integers(CMD) \
+ case SHOW_SINT: do_num_val (int,CMD); \
+ case SHOW_SLONG: do_num_val (long,CMD); \
+ case SHOW_SLONGLONG:do_num_val (longlong,CMD); \
+ case SHOW_UINT: do_num_val (uint,CMD); \
+ case SHOW_ULONG: do_num_val (ulong,CMD); \
+ case SHOW_ULONGLONG:do_num_val (ulonglong,CMD); \
+ case SHOW_HA_ROWS: do_num_val (ha_rows,CMD); \
+ case SHOW_BOOL: do_num_val (bool,CMD); \
+ case SHOW_MY_BOOL: do_num_val (my_bool,CMD)
+
+#define case_for_double(CMD) \
+ case SHOW_DOUBLE: do_num_val (double,CMD)
+
+#define case_get_string_as_lex_string \
+ case SHOW_CHAR: \
+ mysql_mutex_lock(&LOCK_global_system_variables); \
+ sval.str= (char*) value_ptr(thd, type, base); \
+ sval.length= sval.str ? strlen(sval.str) : 0; \
+ break; \
+ case SHOW_CHAR_PTR: \
+ mysql_mutex_lock(&LOCK_global_system_variables); \
+ sval.str= *(char**) value_ptr(thd, type, base); \
+ sval.length= sval.str ? strlen(sval.str) : 0; \
+ break; \
+ case SHOW_LEX_STRING: \
+ mysql_mutex_lock(&LOCK_global_system_variables); \
+ sval= *(LEX_STRING *) value_ptr(thd, type, base); \
+ break
+
+longlong sys_var::val_int(bool *is_null,
+ THD *thd, enum_var_type type, LEX_STRING *base)
+{
+ LEX_STRING sval;
+ *is_null= false;
+ switch (show_type())
+ {
+ case_get_string_as_lex_string;
+ case_for_integers(return val);
+ case_for_double(return (longlong) val);
+ default:
+ my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str);
+ return 0;
+ }
+
+ longlong ret= 0;
+ if (!(*is_null= !sval.str))
+ ret= longlong_from_string_with_check(system_charset_info,
+ sval.str, sval.str + sval.length);
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ return ret;
+}
+
+
+String *sys_var::val_str(String *str,
+ THD *thd, enum_var_type type, LEX_STRING *base)
+{
+ LEX_STRING sval;
+ switch (show_type())
+ {
+ case_get_string_as_lex_string;
+ case_for_integers(return str->set((ulonglong)val, system_charset_info) ? 0 : str);
+ case_for_double(return str->set_real(val, 6, system_charset_info) ? 0 : str);
+ default:
+ my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str);
+ return 0;
+ }
+
+ if (!sval.str || str->copy(sval.str, sval.length, system_charset_info))
+ str= NULL;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ return str;
+}
+
+
+double sys_var::val_real(bool *is_null,
+ THD *thd, enum_var_type type, LEX_STRING *base)
+{
+ LEX_STRING sval;
+ *is_null= false;
+ switch (show_type())
+ {
+ case_get_string_as_lex_string;
+ case_for_integers(return val);
+ case_for_double(return val);
+ default:
+ my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str);
+ return 0;
+ }
+
+ double ret= 0;
+ if (!(*is_null= !sval.str))
+ ret= double_from_string_with_check(system_charset_info,
+ sval.str, sval.str + sval.length);
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ return ret;
+}
+
+
+void sys_var::do_deprecated_warning(THD *thd)
+{
+ if (deprecation_substitute != NULL)
+ {
+ char buf1[NAME_CHAR_LEN + 3];
+ strxnmov(buf1, sizeof(buf1)-1, "@@", name.str, 0);
+
+ /*
+ if deprecation_substitute is an empty string,
+ there is no replacement for the syntax
+ */
+ uint errmsg= deprecation_substitute[0] == '\0'
+ ? ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT
+ : ER_WARN_DEPRECATED_SYNTAX;
+ if (thd)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_DEPRECATED_SYNTAX, ER(errmsg),
+ buf1, deprecation_substitute);
+ else
+ sql_print_warning(ER_DEFAULT(errmsg), buf1, deprecation_substitute);
+ }
+}
+
+/**
+ Throw warning (error in STRICT mode) if value for variable needed bounding.
+ Plug-in interface also uses this.
+
+ @param thd thread handle
+ @param name variable's name
+ @param fixed did we have to correct the value? (throw warn/err if so)
+ @param is_unsigned is value's type unsigned?
+ @param v variable's value
+
+ @retval true on error, false otherwise (warning or ok)
+ */
+bool throw_bounds_warning(THD *thd, const char *name,
+ bool fixed, bool is_unsigned, longlong v)
+{
+ if (fixed)
+ {
+ char buf[22];
+
+ if (is_unsigned)
+ ullstr((ulonglong) v, buf);
+ else
+ llstr(v, buf);
+
+ if (thd->is_strict_mode())
+ {
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf);
+ return true;
+ }
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), name, buf);
+ }
+ return false;
+}
+
+bool throw_bounds_warning(THD *thd, const char *name, bool fixed, double v)
+{
+ if (fixed)
+ {
+ char buf[64];
+
+ my_gcvt(v, MY_GCVT_ARG_DOUBLE, sizeof(buf) - 1, buf, NULL);
+
+ if (thd->is_strict_mode())
+ {
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf);
+ return true;
+ }
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE,
+ ER(ER_TRUNCATED_WRONG_VALUE), name, buf);
+ }
+ return false;
+}
+
+CHARSET_INFO *sys_var::charset(THD *thd)
+{
+ return is_os_charset ? thd->variables.character_set_filesystem :
+ system_charset_info;
+}
+
+typedef struct old_names_map_st
+{
+ const char *old_name;
+ const char *new_name;
+} my_old_conv;
+
+static my_old_conv old_conv[]=
+{
+ { "cp1251_koi8" , "cp1251" },
+ { "cp1250_latin2" , "cp1250" },
+ { "kam_latin2" , "keybcs2" },
+ { "mac_latin2" , "MacRoman" },
+ { "macce_latin2" , "MacCE" },
+ { "pc2_latin2" , "pclatin2" },
+ { "vga_latin2" , "pclatin1" },
+ { "koi8_cp1251" , "koi8r" },
+ { "win1251ukr_koi8_ukr" , "win1251ukr" },
+ { "koi8_ukr_win1251ukr" , "koi8u" },
+ { NULL , NULL }
+};
+
+CHARSET_INFO *get_old_charset_by_name(const char *name)
+{
+ my_old_conv *conv;
+
+ for (conv= old_conv; conv->old_name; conv++)
+ {
+ if (!my_strcasecmp(&my_charset_latin1, name, conv->old_name))
+ return get_charset_by_csname(conv->new_name, MY_CS_PRIMARY, MYF(0));
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Main handling of variables:
+ - Initialisation
+ - Searching during parsing
+ - Update loop
+****************************************************************************/
+
+/**
+ Add variables to the dynamic hash of system variables
+
+ @param first Pointer to first system variable to add
+
+ @retval
+ 0 SUCCESS
+ @retval
+ otherwise FAILURE
+*/
+
+
+int mysql_add_sys_var_chain(sys_var *first)
+{
+ sys_var *var;
+
+ /* A write lock should be held on LOCK_system_variables_hash */
+
+ for (var= first; var; var= var->next)
+ {
+ /* this fails if there is a conflicting variable name. see HASH_UNIQUE */
+ if (my_hash_insert(&system_variable_hash, (uchar*) var))
+ {
+ fprintf(stderr, "*** duplicate variable name '%s' ?\n", var->name.str);
+ goto error;
+ }
+ }
+ return 0;
+
+error:
+ for (; first != var; first= first->next)
+ my_hash_delete(&system_variable_hash, (uchar*) first);
+ return 1;
+}
+
+
+/*
+ Remove variables to the dynamic hash of system variables
+
+ SYNOPSIS
+ mysql_del_sys_var_chain()
+ first Pointer to first system variable to remove
+
+ RETURN VALUES
+ 0 SUCCESS
+ otherwise FAILURE
+*/
+
+int mysql_del_sys_var_chain(sys_var *first)
+{
+ int result= 0;
+
+ mysql_rwlock_wrlock(&LOCK_system_variables_hash);
+ for (sys_var *var= first; var; var= var->next)
+ result|= my_hash_delete(&system_variable_hash, (uchar*) var);
+ mysql_rwlock_unlock(&LOCK_system_variables_hash);
+
+ return result;
+}
+
+
+static int show_cmp(SHOW_VAR *a, SHOW_VAR *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+
+/**
+ Constructs an array of system variables for display to the user.
+
+ @param thd current thread
+ @param sorted If TRUE, the system variables should be sorted
+ @param type OPT_GLOBAL or OPT_SESSION for SHOW GLOBAL|SESSION VARIABLES
+
+ @retval
+ pointer Array of SHOW_VAR elements for display
+ @retval
+ NULL FAILURE
+*/
+
+SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type)
+{
+ int count= system_variable_hash.records, i;
+ int size= sizeof(SHOW_VAR) * (count + 1);
+ SHOW_VAR *result= (SHOW_VAR*) thd->alloc(size);
+
+ if (result)
+ {
+ SHOW_VAR *show= result;
+
+ for (i= 0; i < count; i++)
+ {
+ sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i);
+
+ // don't show session-only variables in SHOW GLOBAL VARIABLES
+ if (type == OPT_GLOBAL && var->check_type(type))
+ continue;
+
+ show->name= var->name.str;
+ show->value= (char*) var;
+ show->type= SHOW_SYS;
+ show++;
+ }
+
+ /* sort into order */
+ if (sorted)
+ my_qsort(result, show-result, sizeof(SHOW_VAR),
+ (qsort_cmp) show_cmp);
+
+ /* make last element empty */
+ bzero(show, sizeof(SHOW_VAR));
+ }
+ return result;
+}
+
+/**
+ Find a user set-table variable.
+
+ @param str Name of system variable to find
+ @param length Length of variable. zero means that we should use strlen()
+ on the variable
+
+ @retval
+ pointer pointer to variable definitions
+ @retval
+ 0 Unknown variable (error message is given)
+*/
+
+sys_var *intern_find_sys_var(const char *str, uint length)
+{
+ sys_var *var;
+
+ /*
+ This function is only called from the sql_plugin.cc.
+ A lock on LOCK_system_variable_hash should be held
+ */
+ var= (sys_var*) my_hash_search(&system_variable_hash,
+ (uchar*) str, length ? length : strlen(str));
+
+ return var;
+}
+
+
+/**
+ Execute update of all variables.
+
+ First run a check of all variables that all updates will go ok.
+ If yes, then execute all updates, returning an error if any one failed.
+
+ This should ensure that in all normal cases none all or variables are
+ updated.
+
+ @param THD Thread id
+ @param var_list List of variables to update
+
+ @retval
+ 0 ok
+ @retval
+ 1 ERROR, message sent (normally no variables was updated)
+ @retval
+ -1 ERROR, message not sent
+*/
+
+int sql_set_variables(THD *thd, List<set_var_base> *var_list)
+{
+ int error;
+ List_iterator_fast<set_var_base> it(*var_list);
+ DBUG_ENTER("sql_set_variables");
+
+ set_var_base *var;
+ while ((var=it++))
+ {
+ if ((error= var->check(thd)))
+ goto err;
+ }
+ if (!(error= MY_TEST(thd->is_error())))
+ {
+ it.rewind();
+ while ((var= it++))
+ error|= var->update(thd); // Returns 0, -1 or 1
+ }
+
+err:
+ free_underlaid_joins(thd, &thd->lex->select_lex);
+ DBUG_RETURN(error);
+}
+
+/*****************************************************************************
+ Functions to handle SET mysql_internal_variable=const_expr
+*****************************************************************************/
+
+/**
+ Verify that the supplied value is correct.
+
+ @param thd Thread handler
+
+ @return status code
+ @retval -1 Failure
+ @retval 0 Success
+ */
+
+int set_var::check(THD *thd)
+{
+ var->do_deprecated_warning(thd);
+ if (var->is_readonly())
+ {
+ my_error(ER_INCORRECT_GLOBAL_LOCAL_VAR, MYF(0), var->name.str, "read only");
+ return -1;
+ }
+ if (var->check_type(type))
+ {
+ int err= type == OPT_GLOBAL ? ER_LOCAL_VARIABLE : ER_GLOBAL_VARIABLE;
+ my_error(err, MYF(0), var->name.str);
+ return -1;
+ }
+ if ((type == OPT_GLOBAL && check_global_access(thd, SUPER_ACL)))
+ return 1;
+ /* value is a NULL pointer if we are using SET ... = DEFAULT */
+ if (!value)
+ return 0;
+
+ if ((!value->fixed &&
+ value->fix_fields(thd, &value)) || value->check_cols(1))
+ return -1;
+ if (var->check_update_type(value->result_type()))
+ {
+ my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), var->name.str);
+ return -1;
+ }
+ return var->check(thd, this) ? -1 : 0;
+}
+
+
+/**
+ Check variable, but without assigning value (used by PS).
+
+ @param thd thread handler
+
+ @retval
+ 0 ok
+ @retval
+ 1 ERROR, message sent (normally no variables was updated)
+ @retval
+ -1 ERROR, message not sent
+*/
+int set_var::light_check(THD *thd)
+{
+ if (var->check_type(type))
+ {
+ int err= type == OPT_GLOBAL ? ER_LOCAL_VARIABLE : ER_GLOBAL_VARIABLE;
+ my_error(err, MYF(0), var->name);
+ return -1;
+ }
+ if (type == OPT_GLOBAL && check_global_access(thd, SUPER_ACL))
+ return 1;
+
+ if (value && ((!value->fixed && value->fix_fields(thd, &value)) ||
+ value->check_cols(1)))
+ return -1;
+ return 0;
+}
+
+/**
+ Update variable
+
+ @param thd thread handler
+ @returns 0|1 ok or ERROR
+
+ @note ERROR can be only due to abnormal operations involving
+ the server's execution evironment such as
+ out of memory, hard disk failure or the computer blows up.
+ Consider set_var::check() method if there is a need to return
+ an error due to logics.
+*/
+int set_var::update(THD *thd)
+{
+ return value ? var->update(thd, this) : var->set_default(thd, this);
+}
+
+
+/*****************************************************************************
+ Functions to handle SET @user_variable=const_expr
+*****************************************************************************/
+
+int set_var_user::check(THD *thd)
+{
+ /*
+ Item_func_set_user_var can't substitute something else on its place =>
+ 0 can be passed as last argument (reference on item)
+ */
+ return (user_var_item->fix_fields(thd, (Item**) 0) ||
+ user_var_item->check(0)) ? -1 : 0;
+}
+
+
+/**
+ Check variable, but without assigning value (used by PS).
+
+ @param thd thread handler
+
+ @retval
+ 0 ok
+ @retval
+ 1 ERROR, message sent (normally no variables was updated)
+ @retval
+ -1 ERROR, message not sent
+*/
+int set_var_user::light_check(THD *thd)
+{
+ /*
+ Item_func_set_user_var can't substitute something else on its place =>
+ 0 can be passed as last argument (reference on item)
+ */
+ return (user_var_item->fix_fields(thd, (Item**) 0));
+}
+
+
+int set_var_user::update(THD *thd)
+{
+ if (user_var_item->update())
+ {
+ /* Give an error if it's not given already */
+ my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), MYF(0));
+ return -1;
+ }
+ return 0;
+}
+
+
+/*****************************************************************************
+ Functions to handle SET PASSWORD
+*****************************************************************************/
+
+int set_var_password::check(THD *thd)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ user= get_current_user(thd, user);
+ /* Returns 1 as the function sends error to client */
+ return check_change_password(thd, user->host.str, user->user.str,
+ password, strlen(password)) ? 1 : 0;
+#else
+ return 0;
+#endif
+}
+
+int set_var_password::update(THD *thd)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* Returns 1 as the function sends error to client */
+ return change_password(thd, user->host.str, user->user.str, password) ?
+ 1 : 0;
+#else
+ return 0;
+#endif
+}
+
+/*****************************************************************************
+ Functions to handle SET ROLE
+*****************************************************************************/
+int set_var_role::check(THD *thd)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ int status= acl_check_setrole(thd, role.str, &access);
+ return status;
+#else
+ return 0;
+#endif
+}
+
+int set_var_role::update(THD *thd)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ return acl_setrole(thd, role.str, access);
+#else
+ return 0;
+#endif
+}
+
+
+/*****************************************************************************
+ Functions to handle SET NAMES and SET CHARACTER SET
+*****************************************************************************/
+
+int set_var_collation_client::check(THD *thd)
+{
+ /* Currently, UCS-2 cannot be used as a client character set */
+ if (!is_supported_parser_charset(character_set_client))
+ {
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "character_set_client",
+ character_set_client->csname);
+ return 1;
+ }
+ return 0;
+}
+
+int set_var_collation_client::update(THD *thd)
+{
+ thd->variables.character_set_client= character_set_client;
+ thd->variables.character_set_results= character_set_results;
+ thd->variables.collation_connection= collation_connection;
+ thd->update_charset();
+ thd->protocol_text.init(thd);
+ thd->protocol_binary.init(thd);
+ return 0;
+}
+
diff --git a/sql/set_var.h b/sql/set_var.h
new file mode 100644
index 00000000000..83ba662b76c
--- /dev/null
+++ b/sql/set_var.h
@@ -0,0 +1,349 @@
+#ifndef SET_VAR_INCLUDED
+#define SET_VAR_INCLUDED
+/* Copyright (c) 2002, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, SkySQL Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/**
+ @file
+ "public" interface to sys_var - server configuration variables.
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include <my_getopt.h>
+
+class sys_var;
+class set_var;
+class sys_var_pluginvar;
+class PolyLock;
+class Item_func_set_user_var;
+
+// This include needs to be here since item.h requires enum_var_type :-P
+#include "item.h" /* Item */
+#include "sql_class.h" /* THD */
+
+extern TYPELIB bool_typelib;
+
+struct sys_var_chain
+{
+ sys_var *first;
+ sys_var *last;
+};
+
+int mysql_add_sys_var_chain(sys_var *chain);
+int mysql_del_sys_var_chain(sys_var *chain);
+
+/**
+ A class representing one system variable - that is something
+ that can be accessed as @@global.variable_name or @@session.variable_name,
+ visible in SHOW xxx VARIABLES and in INFORMATION_SCHEMA.xxx_VARIABLES,
+ optionally it can be assigned to, optionally it can have a command-line
+ counterpart with the same name.
+*/
+class sys_var
+{
+public:
+ sys_var *next;
+ LEX_CSTRING name;
+ enum flag_enum { GLOBAL, SESSION, ONLY_SESSION, SCOPE_MASK=1023,
+ READONLY=1024, ALLOCATED=2048, PARSE_EARLY=4096, SHOW_VALUE_IN_HELP=8192 };
+ /**
+ Enumeration type to indicate for a system variable whether
+ it will be written to the binlog or not.
+ */
+ enum binlog_status_enum { VARIABLE_NOT_IN_BINLOG,
+ SESSION_VARIABLE_IN_BINLOG } binlog_status;
+
+ my_option option; ///< min, max, default values are stored here
+
+protected:
+ typedef bool (*on_check_function)(sys_var *self, THD *thd, set_var *var);
+ typedef bool (*on_update_function)(sys_var *self, THD *thd, enum_var_type type);
+
+ int flags; ///< or'ed flag_enum values
+ const SHOW_TYPE show_val_type; ///< what value_ptr() returns for sql_show.cc
+ PolyLock *guard; ///< *second* lock that protects the variable
+ ptrdiff_t offset; ///< offset to the value from global_system_variables
+ on_check_function on_check;
+ on_update_function on_update;
+ const char *const deprecation_substitute;
+ bool is_os_charset; ///< true if the value is in character_set_filesystem
+
+public:
+ sys_var(sys_var_chain *chain, const char *name_arg, const char *comment,
+ int flag_args, ptrdiff_t off, int getopt_id,
+ enum get_opt_arg_type getopt_arg_type, SHOW_TYPE show_val_type_arg,
+ longlong def_val, PolyLock *lock, enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func, on_update_function on_update_func,
+ const char *substitute);
+
+ virtual ~sys_var() {}
+
+ /**
+ All the cleanup procedures should be performed here
+ */
+ virtual void cleanup() {}
+ /**
+ downcast for sys_var_pluginvar. Returns this if it's an instance
+ of sys_var_pluginvar, and 0 otherwise.
+ */
+ virtual sys_var_pluginvar *cast_pluginvar() { return 0; }
+
+ bool check(THD *thd, set_var *var);
+ uchar *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base);
+
+ /**
+ Update the system variable with the default value from either
+ session or global scope. The default value is stored in the
+ 'var' argument. Return false when successful.
+ */
+ bool set_default(THD *thd, set_var *var);
+ bool update(THD *thd, set_var *var);
+
+ longlong val_int(bool *is_null, THD *thd, enum_var_type type, LEX_STRING *base);
+ String *val_str(String *str, THD *thd, enum_var_type type, LEX_STRING *base);
+ double val_real(bool *is_null, THD *thd, enum_var_type type, LEX_STRING *base);
+
+ SHOW_TYPE show_type() { return show_val_type; }
+ int scope() const { return flags & SCOPE_MASK; }
+ CHARSET_INFO *charset(THD *thd);
+ bool is_readonly() const { return flags & READONLY; }
+ /**
+ the following is only true for keycache variables,
+ that support the syntax @@keycache_name.variable_name
+ */
+ bool is_struct() { return option.var_type & GET_ASK_ADDR; }
+ bool is_written_to_binlog(enum_var_type type)
+ { return type != OPT_GLOBAL && binlog_status == SESSION_VARIABLE_IN_BINLOG; }
+ virtual bool check_update_type(Item_result type) = 0;
+ bool check_type(enum_var_type type)
+ {
+ switch (scope())
+ {
+ case GLOBAL: return type != OPT_GLOBAL;
+ case SESSION: return false; // always ok
+ case ONLY_SESSION: return type == OPT_GLOBAL;
+ }
+ return true; // keep gcc happy
+ }
+ bool register_option(DYNAMIC_ARRAY *array, int parse_flags)
+ {
+ return ((((option.id != -1) && ((flags & PARSE_EARLY) == parse_flags)) ||
+ (flags & parse_flags)) &&
+ insert_dynamic(array, (uchar*)&option));
+ }
+ void do_deprecated_warning(THD *thd);
+
+private:
+ virtual bool do_check(THD *thd, set_var *var) = 0;
+ /**
+ save the session default value of the variable in var
+ */
+ virtual void session_save_default(THD *thd, set_var *var) = 0;
+ /**
+ save the global default value of the variable in var
+ */
+ virtual void global_save_default(THD *thd, set_var *var) = 0;
+ virtual bool session_update(THD *thd, set_var *var) = 0;
+ virtual bool global_update(THD *thd, set_var *var) = 0;
+
+protected:
+ /**
+ A pointer to a value of the variable for SHOW.
+ It must be of show_val_type type (bool for SHOW_BOOL, int for SHOW_INT,
+ longlong for SHOW_LONGLONG, etc).
+ */
+ virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base);
+ virtual uchar *global_value_ptr(THD *thd, LEX_STRING *base);
+
+ /**
+ A pointer to a storage area of the variable, to the raw data.
+ Typically it's the same as session_value_ptr(), but it's different,
+ for example, for ENUM, that is printed as a string, but stored as a number.
+ */
+ uchar *session_var_ptr(THD *thd)
+ { return ((uchar*)&(thd->variables)) + offset; }
+
+ uchar *global_var_ptr()
+ { return ((uchar*)&global_system_variables) + offset; }
+};
+
+#include "sql_plugin.h" /* SHOW_HA_ROWS, SHOW_MY_BOOL */
+
+
+/****************************************************************************
+ Classes for parsing of the SET command
+****************************************************************************/
+
+/**
+ A base class for everything that can be set with SET command.
+ It's similar to Items, an instance of this is created by the parser
+ for every assigmnent in SET (or elsewhere, e.g. in SELECT).
+*/
+class set_var_base :public Sql_alloc
+{
+public:
+ set_var_base() {}
+ virtual ~set_var_base() {}
+ virtual int check(THD *thd)=0; /* To check privileges etc. */
+ virtual int update(THD *thd)=0; /* To set the value */
+ virtual int light_check(THD *thd) { return check(thd); } /* for PS */
+};
+
+
+/**
+ set_var_base descendant for assignments to the system variables.
+*/
+class set_var :public set_var_base
+{
+public:
+ sys_var *var; ///< system variable to be updated
+ Item *value; ///< the expression that provides the new value of the variable
+ enum_var_type type;
+ union ///< temp storage to hold a value between sys_var::check and ::update
+ {
+ ulonglong ulonglong_value; ///< for unsigned integer, set, enum sysvars
+ longlong longlong_value; ///< for signed integer
+ double double_value; ///< for Sys_var_double
+ plugin_ref plugin; ///< for Sys_var_plugin
+ Time_zone *time_zone; ///< for Sys_var_tz
+ LEX_STRING string_value; ///< for Sys_var_charptr and others
+ const void *ptr; ///< for Sys_var_struct
+ } save_result;
+ LEX_STRING base; /**< for structured variables, like keycache_name.variable_name */
+
+ set_var(enum_var_type type_arg, sys_var *var_arg,
+ const LEX_STRING *base_name_arg, Item *value_arg)
+ :var(var_arg), type(type_arg), base(*base_name_arg)
+ {
+ /*
+ If the set value is a field, change it to a string to allow things like
+ SET table_type=MYISAM;
+ */
+ if (value_arg && value_arg->type() == Item::FIELD_ITEM)
+ {
+ Item_field *item= (Item_field*) value_arg;
+ if (!(value=new Item_string_sys(item->field_name))) // names are utf8
+ value=value_arg; /* Give error message later */
+ }
+ else
+ value=value_arg;
+ }
+ int check(THD *thd);
+ int update(THD *thd);
+ int light_check(THD *thd);
+};
+
+
+/* User variables like @my_own_variable */
+class set_var_user: public set_var_base
+{
+ Item_func_set_user_var *user_var_item;
+public:
+ set_var_user(Item_func_set_user_var *item)
+ :user_var_item(item)
+ {}
+ int check(THD *thd);
+ int update(THD *thd);
+ int light_check(THD *thd);
+};
+
+/* For SET PASSWORD */
+
+class set_var_password: public set_var_base
+{
+ LEX_USER *user;
+ char *password;
+public:
+ set_var_password(LEX_USER *user_arg,char *password_arg)
+ :user(user_arg), password(password_arg)
+ {}
+ int check(THD *thd);
+ int update(THD *thd);
+};
+
+/* For SET ROLE */
+
+class set_var_role: public set_var_base
+{
+ LEX_STRING role;
+ ulonglong access;
+public:
+ set_var_role(LEX_STRING role_arg) : role(role_arg) {}
+ int check(THD *thd);
+ int update(THD *thd);
+};
+
+
+/* For SET NAMES and SET CHARACTER SET */
+
+class set_var_collation_client: public set_var_base
+{
+ CHARSET_INFO *character_set_client;
+ CHARSET_INFO *character_set_results;
+ CHARSET_INFO *collation_connection;
+public:
+ set_var_collation_client(CHARSET_INFO *client_coll_arg,
+ CHARSET_INFO *connection_coll_arg,
+ CHARSET_INFO *result_coll_arg)
+ :character_set_client(client_coll_arg),
+ character_set_results(result_coll_arg),
+ collation_connection(connection_coll_arg)
+ {}
+ int check(THD *thd);
+ int update(THD *thd);
+};
+
+
+/* optional things, have_* variables */
+extern SHOW_COMP_OPTION have_csv, have_innodb;
+extern SHOW_COMP_OPTION have_ndbcluster, have_partitioning;
+extern SHOW_COMP_OPTION have_profiling;
+
+extern SHOW_COMP_OPTION have_ssl, have_symlink, have_dlopen;
+extern SHOW_COMP_OPTION have_query_cache;
+extern SHOW_COMP_OPTION have_geometry, have_rtree_keys;
+extern SHOW_COMP_OPTION have_crypt;
+extern SHOW_COMP_OPTION have_compress;
+extern SHOW_COMP_OPTION have_openssl;
+
+/*
+ Prototypes for helper functions
+*/
+
+SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type);
+
+sys_var *find_sys_var(THD *thd, const char *str, uint length=0);
+int sql_set_variables(THD *thd, List<set_var_base> *var_list);
+
+bool fix_delay_key_write(sys_var *self, THD *thd, enum_var_type type);
+
+ulonglong expand_sql_mode(ulonglong sql_mode);
+bool sql_mode_string_representation(THD *thd, ulonglong sql_mode, LEX_STRING *ls);
+int default_regex_flags_pcre(const THD *thd);
+
+extern sys_var *Sys_autocommit_ptr;
+
+CHARSET_INFO *get_old_charset_by_name(const char *old_name);
+
+int sys_var_init();
+int sys_var_add_options(DYNAMIC_ARRAY *long_options, int parse_flags);
+void sys_var_end(void);
+
+#endif
+
diff --git a/sql/sha2.cc b/sql/sha2.cc
new file mode 100644
index 00000000000..f2201974172
--- /dev/null
+++ b/sql/sha2.cc
@@ -0,0 +1,68 @@
+/* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/**
+ @file
+ A compatibility layer to our built-in SSL implementation, to mimic the
+ oft-used external library, OpenSSL.
+*/
+
+#include <my_global.h>
+#include <sha2.h>
+
+#ifdef HAVE_YASSL
+
+/*
+ If TaoCrypt::SHA512 or ::SHA384 are not defined (but ::SHA256 is), it's
+ probably that neither of config.h's SIZEOF_LONG or SIZEOF_LONG_LONG are
+ 64 bits long. At present, both OpenSSL and YaSSL require 64-bit integers
+ for SHA-512. (The SIZEOF_* definitions come from autoconf's config.h .)
+*/
+
+# define GEN_YASSL_SHA2_BRIDGE(size) \
+unsigned char* SHA##size(const unsigned char *input_ptr, size_t input_length, \
+ char unsigned *output_ptr) { \
+ TaoCrypt::SHA##size hasher; \
+ \
+ hasher.Update(input_ptr, input_length); \
+ hasher.Final(output_ptr); \
+ return(output_ptr); \
+}
+
+
+/**
+ @fn SHA512
+ @fn SHA384
+ @fn SHA256
+ @fn SHA224
+
+ Instantiate an hash object, fill in the cleartext value, compute the digest,
+ and extract the result from the object.
+
+ (Generate the functions. See similar .h code for the prototypes.)
+*/
+# ifndef OPENSSL_NO_SHA512
+GEN_YASSL_SHA2_BRIDGE(512);
+GEN_YASSL_SHA2_BRIDGE(384);
+# else
+# warning Some SHA2 functionality is missing. See OPENSSL_NO_SHA512.
+# endif
+GEN_YASSL_SHA2_BRIDGE(256);
+GEN_YASSL_SHA2_BRIDGE(224);
+
+# undef GEN_YASSL_SHA2_BRIDGE
+
+#endif /* HAVE_YASSL */
diff --git a/sql/share/CMakeLists.txt b/sql/share/CMakeLists.txt
new file mode 100644
index 00000000000..e0d5fb6c1a7
--- /dev/null
+++ b/sql/share/CMakeLists.txt
@@ -0,0 +1,55 @@
+# Copyright (c) 2006 MySQL AB, 2009, 2010 Sun Microsystems, Inc.
+# Use is subject to license terms.
+#
+# 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
+
+SET (dirs
+danish
+german
+slovak
+dutch
+greek
+norwegian
+spanish
+english
+hungarian
+norwegian-ny
+swedish
+italian
+polish
+ukrainian
+japanese
+portuguese
+romanian
+estonian
+korean
+russian
+czech
+french
+serbian
+)
+
+SET(files
+ errmsg-utf8.txt
+)
+
+FOREACH (dir ${dirs})
+ INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${dir}
+ DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT Server)
+ENDFOREACH()
+INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/charsets DESTINATION ${INSTALL_MYSQLSHAREDIR}
+ COMPONENT Common PATTERN "languages.html" EXCLUDE
+)
+
+INSTALL(FILES ${files} DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT Server)
diff --git a/sql/share/charsets/Index.xml b/sql/share/charsets/Index.xml
new file mode 100644
index 00000000000..9764d629625
--- /dev/null
+++ b/sql/share/charsets/Index.xml
@@ -0,0 +1,599 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets max-id="99">
+
+<copyright>
+ Copyright (c) 2003-2005 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<description>
+This file lists all of the available character sets.
+To make maintaining easier please:
+ - keep records sorted by collation number.
+ - change charsets.max-id when adding a new collation.
+</description>
+
+<charset name="big5">
+ <family>Traditional Chinese</family>
+ <description>Big5 Traditional Chinese</description>
+ <alias>big-5</alias>
+ <alias>bigfive</alias>
+ <alias>big-five</alias>
+ <alias>cn-big5</alias>
+ <alias>csbig5</alias>
+ <collation name="big5_chinese_ci" id="1" order="Chinese">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="big5_bin" id="84" order="Binary">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="latin2">
+ <family>Central European</family>
+ <description>ISO 8859-2 Central European</description>
+ <alias>csisolatin2</alias>
+ <alias>iso-8859-2</alias>
+ <alias>iso-ir-101</alias>
+ <alias>iso_8859-2</alias>
+ <alias>iso_8859-2:1987</alias>
+ <alias>l2</alias>
+ <collation name="latin2_czech_cs" id="2" order="Czech" flag="compiled"/>
+ <collation name="latin2_general_ci" id="9" flag="primary">
+ <order>Hungarian</order>
+ <order>Polish</order>
+ <order>Romanian</order>
+ <order>Croatian</order>
+ <order>Slovak</order>
+ <order>Slovenian</order>
+ <order>Sorbian</order>
+ </collation>
+ <collation name="latin2_hungarian_ci" id="21" order="Hungarian"/>
+ <collation name="latin2_croatian_ci" id="27" order="Croatian"/>
+ <collation name="latin2_bin" id="77" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="dec8">
+ <family>Western</family>
+ <description>DEC West European</description>
+ <collation name="dec8_bin" id="69" order="Binary" flag="binary"/>
+ <collation name="dec8_swedish_ci" id="3" flag="primary">
+ <order>Dutch</order>
+ <order>English</order>
+ <order>French</order>
+ <order>German Duden</order>
+ <order>Italian</order>
+ <order>Latin</order>
+ <order>Portuguese</order>
+ <order>Spanish</order>
+ </collation>
+</charset>
+
+<charset name="cp850">
+ <family>Western</family>
+ <description>DOS West European</description>
+ <alias>850</alias>
+ <alias>cspc850multilingual</alias>
+ <alias>ibm850</alias>
+ <collation name="cp850_general_ci" id="4" flag="primary">
+ <order>Dutch</order>
+ <order>English</order>
+ <order>French</order>
+ <order>German Duden</order>
+ <order>Italian</order>
+ <order>Latin</order>
+ <order>Portuguese</order>
+ <order>Spanish</order>
+ </collation>
+ <collation name="cp850_bin" id="80" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="latin1">
+ <family>Western</family>
+ <description>cp1252 West European</description>
+ <alias>csisolatin1</alias>
+ <alias>iso-8859-1</alias>
+ <alias>iso-ir-100</alias>
+ <alias>iso_8859-1</alias>
+ <alias>iso_8859-1:1987</alias>
+ <alias>l1</alias>
+ <alias>latin1</alias>
+ <collation name="latin1_german1_ci" id="5" order="German Duden"/>
+ <collation name="latin1_swedish_ci" id="8" order="Finnish, Swedish">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="latin1_danish_ci" id="15" order="Danish"/>
+ <collation name="latin1_german2_ci" id="31" order="German Phonebook" flag="compiled"/>
+ <collation name="latin1_spanish_ci" id="94" order="Spanish"/>
+ <collation name="latin1_bin" id="47" order="Binary">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="latin1_general_ci" id="48">
+ <order>Dutch</order>
+ <order>English</order>
+ <order>French</order>
+ <order>German Duden</order>
+ <order>Italian</order>
+ <order>Latin</order>
+ <order>Portuguese</order>
+ <order>Spanish</order>
+ </collation>
+ <collation name="latin1_general_cs" id="49">
+ <order>Dutch</order>
+ <order>English</order>
+ <order>French</order>
+ <order>German Duden</order>
+ <order>Italian</order>
+ <order>Latin</order>
+ <order>Portuguese</order>
+ <order>Spanish</order>
+ </collation>
+</charset>
+
+<charset name="hp8">
+ <family>Western</family>
+ <description>HP West European</description>
+ <alias>hproman8</alias>
+ <collation name="hp8_bin" id="72" order="Binary" flag="binary"/>
+ <collation name="hp8_english_ci" id="6" flag="primary">
+ <order>Dutch</order>
+ <order>English</order>
+ <order>French</order>
+ <order>German Duden</order>
+ <order>Italian</order>
+ <order>Latin</order>
+ <order>Portuguese</order>
+ <order>Spanish</order>
+ </collation>
+</charset>
+
+<charset name="koi8r">
+ <family>Cyrillic</family>
+ <description>KOI8-R Relcom Russian</description>
+ <alias>koi8-r</alias>
+ <alias>cskoi8r</alias>
+ <collation name="koi8r_general_ci" id="7" order="Russian" flag="primary"/>
+ <collation name="koi8r_bin" id="74" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="swe7">
+ <family>Western</family>
+ <description>7bit Swedish</description>
+ <alias>iso-646-se</alias>
+ <collation name="swe7_swedish_ci" id="10" order="Swedish" flag="primary"/>
+ <collation name="swe7_bin" id="82" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="ascii">
+ <family>Western</family>
+ <description>US ASCII</description>
+ <alias>us</alias>
+ <alias>us-ascii</alias>
+ <alias>csascii</alias>
+ <alias>iso-ir-6</alias>
+ <alias>iso646-us</alias>
+ <collation name="ascii_general_ci" id="11" order="English" flag="primary"/>
+ <collation name="ascii_bin" id="65" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="ujis">
+ <family>Japanese</family>
+ <description>EUC-JP Japanese</description>
+ <alias>euc-jp</alias>
+ <collation name="ujis_japanese_ci" id="12" order="Japanese">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="ujis_bin" id="91" order="Japanese">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="sjis">
+ <family>Japanese</family>
+ <description>Shift-JIS Japanese</description>
+ <alias>s-jis</alias>
+ <alias>shift-jis</alias>
+ <alias>x-sjis</alias>
+ <collation name="sjis_japanese_ci" id="13" order="Japanese">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="sjis_bin" id="88" order="Binary">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="cp1251">
+ <family>Cyrillic</family>
+ <description>Windows Cyrillic</description>
+ <alias>windows-1251</alias>
+ <alias>ms-cyr</alias>
+ <alias>ms-cyrillic</alias>
+ <collation name="cp1251_bulgarian_ci" id="14">
+ <order>Belarusian</order>
+ <order>Bulgarian</order>
+ <order>Macedonian</order>
+ <order>Russian</order>
+ <order>Serbian</order>
+ <order>Mongolian</order>
+ <order>Ukrainian</order>
+ </collation>
+ <collation name="cp1251_ukrainian_ci" id="23" order="Ukrainian"/>
+ <collation name="cp1251_bin" id="50" order="Binary" flag="binary"/>
+ <collation name="cp1251_general_ci" id="51" flag="primary">
+ <order>Belarusian</order>
+ <order>Bulgarian</order>
+ <order>Macedonian</order>
+ <order>Russian</order>
+ <order>Serbian</order>
+ <order>Mongolian</order>
+ <order>Ukrainian</order>
+ </collation>
+ <collation name="cp1251_general_cs" id="52">
+ <order>Belarusian</order>
+ <order>Bulgarian</order>
+ <order>Macedonian</order>
+ <order>Russian</order>
+ <order>Serbian</order>
+ <order>Mongolian</order>
+ <order>Ukrainian</order>
+ </collation>
+</charset>
+
+<charset name="hebrew">
+ <family>Hebrew</family>
+ <description>ISO 8859-8 Hebrew</description>
+ <alias>csisolatinhebrew</alias>
+ <alias>iso-8859-8</alias>
+ <alias>iso-ir-138</alias>
+ <collation name="hebrew_general_ci" id="16" order="Hebrew" flag="primary"/>
+ <collation name="hebrew_bin" id="71" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="tis620">
+ <family>Thai</family>
+ <description>TIS620 Thai</description>
+ <alias>tis-620</alias>
+ <collation name="tis620_thai_ci" id="18" order="Thai">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="tis620_bin" id="89" order="Binary">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="euckr">
+ <family>Korean</family>
+ <description>EUC-KR Korean</description>
+ <alias>euc_kr</alias>
+ <alias>euc-kr</alias>
+ <collation name="euckr_korean_ci" id="19" order="Korean">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="euckr_bin" id="85">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="latin7">
+ <family>Baltic</family>
+ <description>ISO 8859-13 Baltic</description>
+ <alias>BalticRim</alias>
+ <alias>iso-8859-13</alias>
+ <alias>l7</alias>
+ <collation name="latin7_estonian_cs" id="20">
+ <order>Estonian</order>
+ </collation>
+ <collation name="latin7_general_ci" id="41">
+ <order>Latvian</order>
+ <order>Lithuanian</order>
+ <flag>primary</flag>
+ </collation>
+ <collation name="latin7_general_cs" id="42">
+ <order>Latvian</order>
+ <order>Lithuanian</order>
+ </collation>
+ <collation name="latin7_bin" id="79" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="koi8u">
+ <family>Cyrillic</family>
+ <description>KOI8-U Ukrainian</description>
+ <alias>koi8-u</alias>
+ <collation name="koi8u_general_ci" id="22" order="Ukranian" flag="primary"/>
+ <collation name="koi8u_bin" id="75" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="gb2312">
+ <family>Simplified Chinese</family>
+ <description>GB2312 Simplified Chinese</description>
+ <alias>chinese</alias>
+ <alias>iso-ir-58</alias>
+ <collation name="gb2312_chinese_ci" id="24" order="Chinese">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="gb2312_bin" id="86">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="greek">
+ <family>Greek</family>
+ <description>ISO 8859-7 Greek</description>
+ <alias>csisolatingreek</alias>
+ <alias>ecma-118</alias>
+ <alias>greek8</alias>
+ <alias>iso-8859-7</alias>
+ <alias>iso-ir-126</alias>
+ <collation name="greek_general_ci" id="25" order="Greek" flag="primary"/>
+ <collation name="greek_bin" id="70" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="cp1250">
+ <family>Central European</family>
+ <description>Windows Central European</description>
+ <alias>ms-ce</alias>
+ <alias>windows-1250</alias>
+ <collation name="cp1250_general_ci" id="26" flag="primary">
+ <order>Hungarian</order>
+ <order>Polish</order>
+ <order>Romanian</order>
+ <order>Croatian</order>
+ <order>Slovak</order>
+ <order>Slovenian</order>
+ <order>Sorbian</order>
+ </collation>
+ <collation name="cp1250_croatian_ci" id="44">
+ <order>Croatian</order>
+ </collation>
+ <collation name="cp1250_polish_ci" id="99">
+ <order>Polish</order>
+ </collation>
+ <collation name="cp1250_czech_cs" id="34" order="Czech">
+ <flag>compiled</flag>
+ </collation>
+ <collation name="cp1250_bin" id="66" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="gbk">
+ <family>East Asian</family>
+ <description>GBK Simplified Chinese</description>
+ <alias>cp936</alias>
+ <collation name="gbk_chinese_ci" id="28" order="Chinese">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="gbk_bin" id="87" order="Binary">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="cp1257">
+ <family>Baltic</family>
+ <description>Windows Baltic</description>
+ <alias>WinBaltRim</alias>
+ <alias>windows-1257</alias>
+ <collation name="cp1257_lithuanian_ci" id="29" order="Lithuanian"/>
+ <collation name="cp1257_bin" id="58" order="Binary" flag="binary"/>
+ <collation name="cp1257_general_ci" id="59" flag="primary">
+ <order>Latvian</order>
+ <order>Lithuanian</order>
+ </collation>
+ <!--collation name="cp1257_ci" id="60"/-->
+ <!--collation name="cp1257_cs" id="61"/-->
+</charset>
+
+<charset name="latin5">
+ <family>South Asian</family>
+ <description>ISO 8859-9 Turkish</description>
+ <alias>csisolatin5</alias>
+ <alias>iso-8859-9</alias>
+ <alias>iso-ir-148</alias>
+ <alias>l5</alias>
+ <alias>latin5</alias>
+ <alias>turkish</alias>
+ <collation name="latin5_turkish_ci" id="30" order="Turkish" flag="primary"/>
+ <collation name="latin5_bin" id="78" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="armscii8">
+ <family>South Asian</family>
+ <description>ARMSCII-8 Armenian</description>
+ <alias>armscii-8</alias>
+ <collation name="armscii8_general_ci" id="32" order="Armenian" flag="primary"/>
+ <collation name="armscii8_bin" id="64" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="utf8">
+ <family>Unicode</family>
+ <description>UTF-8 Unicode</description>
+ <alias>utf-8</alias>
+ <collation name="utf8_general_ci" id="33">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="utf8_bin" id="83">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="ucs2">
+ <family>Unicode</family>
+ <description>UCS-2 Unicode</description>
+ <collation name="ucs2_general_ci" id="35">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="ucs2_bin" id="90">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="cp866">
+ <family>Cyrillic</family>
+ <description>DOS Russian</description>
+ <alias>866</alias>
+ <alias>csibm866</alias>
+ <alias>ibm866</alias>
+ <alias>DOSCyrillicRussian</alias>
+ <collation name="cp866_general_ci" id="36" order="Russian" flag="primary"/>
+ <collation name="cp866_bin" id="68" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="keybcs2">
+ <family>Central European</family>
+ <description>DOS Kamenicky Czech-Slovak</description>
+ <collation name="keybcs2_general_ci" id="37" order="Czech" flag="primary"/>
+ <collation name="keybcs2_bin" id="73" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="macce">
+ <family>Central European</family>
+ <description>Mac Central European</description>
+ <alias>MacCentralEurope</alias>
+ <collation name="macce_general_ci" id="38" flag="primary">
+ <order>Hungarian</order>
+ <order>Polish</order>
+ <order>Romanian</order>
+ <order>Croatian</order>
+ <order>Slovak</order>
+ <order>Slovenian</order>
+ <order>Sorbian</order>
+ </collation>
+ <collation name="macce_bin" id="43" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="macroman">
+ <family>Western</family>
+ <description>Mac West European</description>
+ <alias>Mac</alias>
+ <alias>Macintosh</alias>
+ <alias>csmacintosh</alias>
+ <collation name="macroman_general_ci" id="39" flag="primary">
+ <order>Dutch</order>
+ <order>English</order>
+ <order>French</order>
+ <order>German Duden</order>
+ <order>Italian</order>
+ <order>Latin</order>
+ <order>Portuguese</order>
+ <order>Spanish</order>
+ </collation>
+ <collation name="macroman_bin" id="53" order="Binary" flag="binary"/>
+ <!--collation name="macroman_ci" id="54"/-->
+ <!--collation name="macroman_ci_ai" id="55"/-->
+ <!--collation name="macroman_cs" id="56"/-->
+</charset>
+
+<charset name="cp852">
+ <family>Central European</family>
+ <description>DOS Central European</description>
+ <alias>852</alias>
+ <alias>cp852</alias>
+ <alias>ibm852</alias>
+ <collation name="cp852_general_ci" id="40" flag="primary">
+ <order>Hungarian</order>
+ <order>Polish</order>
+ <order>Romanian</order>
+ <order>Croatian</order>
+ <order>Slovak</order>
+ <order>Slovenian</order>
+ <order>Sorbian</order>
+ </collation>
+ <collation name="cp852_bin" id="81" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="cp1256">
+ <family>Arabic</family>
+ <description>Windows Arabic</description>
+ <alias>ms-arab</alias>
+ <alias>windows-1256</alias>
+ <collation name="cp1256_bin" id="67" order="Binary" flag="binary"/>
+ <collation name="cp1256_general_ci" id="57" order="Arabic" flag="primary">
+ <order>Arabic</order>
+ <order>Persian</order>
+ <order>Pakistani</order>
+ <order>Urdu</order>
+ </collation>
+</charset>
+
+<charset name="geostd8">
+ <family>South Asian</family>
+ <description>GEOSTD8 Georgian</description>
+ <collation name="geostd8_general_ci" id="92" order="Georgian" flag="primary"/>
+ <collation name="geostd8_bin" id="93" order="Binary" flag="binary"/>
+</charset>
+
+<charset name="binary">
+ <description>Binary pseudo charset</description>
+ <collation name="binary" id="63" order="Binary">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="cp932">
+ <family>Japanese</family>
+ <description>SJIS for Windows Japanese</description>
+ <alias>ms_cp932</alias>
+ <alias>sjis_cp932</alias>
+ <alias>sjis_ms</alias>
+ <collation name="cp932_japanese_ci" id="95" order="Japanese">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="cp932_bin" id="96" order="Binary">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+<charset name="eucjpms">
+ <family>Japanese</family>
+ <description>UJIS for Windows Japanese</description>
+ <alias>eucjpms</alias>
+ <alias>eucJP_ms</alias>
+ <alias>ujis_ms</alias>
+ <alias>ujis_cp932</alias>
+ <collation name="eucjpms_japanese_ci" id="97" order="Japanese">
+ <flag>primary</flag>
+ <flag>compiled</flag>
+ </collation>
+ <collation name="eucjpms_bin" id="98" order="Japanese">
+ <flag>binary</flag>
+ <flag>compiled</flag>
+ </collation>
+</charset>
+
+</charsets>
+
diff --git a/sql/share/charsets/README b/sql/share/charsets/README
new file mode 100644
index 00000000000..3c5b3206faa
--- /dev/null
+++ b/sql/share/charsets/README
@@ -0,0 +1,39 @@
+This directory holds configuration files that enable MySQL to work with
+different character sets. It contains:
+
+charset_name.xml
+ Each charset_name.xml file contains information for a simple character
+ set. The information in the file describes character types,
+ lower- and upper-case equivalencies and sorting orders for the
+ character values in the set.
+
+Index.xml
+ The Index.xml file lists all of the available charset configurations,
+ including collations.
+
+ Each collation must have a unique number. The number is stored
+ IN THE DATABASE TABLE FILES and must not be changed.
+
+ The max-id attribute of the <charsets> element must be set to
+ the largest collation number.
+
+Compiled in or configuration file?
+ When should a character set be compiled in to MySQL's string library
+ (libmystrings), and when should it be placed in a charset_name.xml
+ configuration file?
+
+ If the character set requires the strcoll functions or is a
+ multi-byte character set, it MUST be compiled in to the string
+ library. If it does not require these functions, it should be
+ placed in a charset_name.xml configuration file.
+
+ If the character set uses any one of the strcoll functions, it
+ must define all of them. Likewise, if the set uses one of the
+ multi-byte functions, it must define them all. See the manual for
+ more information on how to add a complex character set to MySQL.
+
+Syntax of configuration files
+ The syntax is very simple. Words in <map> array elements are
+ separated by arbitrary amounts of whitespace. Each word must be a
+ number in hexadecimal format. The ctype array has 257 words; the
+ other arrays (lower, upper, etc.) take up 256 words each after that.
diff --git a/sql/share/charsets/armscii8.xml b/sql/share/charsets/armscii8.xml
new file mode 100644
index 00000000000..c1eb93b1f91
--- /dev/null
+++ b/sql/share/charsets/armscii8.xml
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2004 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="armscii8">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 01 02 01 02 01 02 01 02 01 02 01 02 01 02
+ 01 02 01 02 01 02 01 02 01 02 01 02 01 02 01 02
+ 01 02 01 02 01 02 01 02 01 02 01 02 01 02 01 02
+ 01 02 01 02 01 02 01 02 01 02 01 02 01 02 01 02
+ 01 02 01 02 01 02 01 02 01 02 01 02 01 02 10 10
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 B8 A9 AA AB AC AD AE AF
+ B0 B1 B3 B3 B5 B5 B7 B7 B9 B9 BB BB BD BD BF BF
+ C1 C1 C3 C3 C5 C5 C7 C7 C9 C9 CB CB CD CD CF CF
+ D1 D1 D3 D3 D5 D5 D7 D7 D9 D9 DB DB DD DD DF DF
+ E1 E1 E3 E3 E5 E5 E7 E7 E9 E9 EB EB ED ED EF EF
+ F1 F1 F3 F3 F5 F5 F7 F7 F9 F9 FB FB FD FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B2 B4 B4 B6 B6 B8 B8 BA BA BC BC BE BE
+ C0 C0 C2 C2 C4 C4 C6 C6 C8 C8 CA CA CC CC CE CE
+ D0 D0 D2 D2 D4 D4 D6 D6 D8 D8 DA DA DC DC DE DE
+ E0 E0 E2 E2 E4 E4 E6 E6 E8 E8 EA EA EC EC EE EE
+ F0 F0 F2 F2 F4 F4 F6 F6 F8 F8 FA FA FC FC FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 2741 00A7 0589 0029 0028 00BB 00AB 2014 002E 055D 002C 002D 055F 2026 055C
+055B 055E 0531 0561 0532 0562 0533 0563 0534 0564 0535 0565 0536 0566 0537 0567
+0538 0568 0539 0569 053A 056A 053B 056B 053C 056C 053D 056D 053E 056E 053F 056F
+0540 0570 0541 0571 0542 0572 0543 0573 0544 0574 0545 0575 0546 0576 0547 0577
+0548 0578 0549 0579 054A 057A 054B 057B 054C 057C 054D 057D 054E 057E 054F 057F
+0550 0580 0551 0581 0552 0582 0553 0583 0554 0584 0555 0585 0556 0586 2019 0027
+</map>
+</unicode>
+
+
+<collation name="armscii8_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="armscii8_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/ascii.xml b/sql/share/charsets/ascii.xml
new file mode 100644
index 00000000000..c516a68516c
--- /dev/null
+++ b/sql/share/charsets/ascii.xml
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2007 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="ascii">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</map>
+</unicode>
+
+
+<collation name="ascii_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="ascii_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/cp1250.xml b/sql/share/charsets/cp1250.xml
new file mode 100644
index 00000000000..e6681a625a2
--- /dev/null
+++ b/sql/share/charsets/cp1250.xml
@@ -0,0 +1,183 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2005 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="cp1250">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 20 20 10 20 10 10 10 10 20 10 01 10 01 01 01 01
+ 20 10 10 10 10 10 10 10 20 10 02 10 02 02 02 02
+ 48 10 10 01 10 01 10 01 10 10 01 10 10 10 10 01
+ 10 10 10 02 10 10 10 10 10 02 02 10 01 10 02 02
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 10 01 01 01 01 01 01 01 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 10 02 02 02 02 02 02 02 10
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 9A 8B 9C 9D 9E 9F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 B3 A4 B9 A6 A7 A8 A9 BA AB AC AD AE BF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BE BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 D7 F8 F9 FA FB FC FD FE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 8A 9B 8C 8D 8E 8F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 A3 B4 B5 B6 B7 B8 A5 AA BB BC BD BC AF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC DD DE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+20AC 0000 201A 0000 201E 2026 2020 2021 0000 2030 0160 2039 015A 0164 017D 0179
+0000 2018 2019 201C 201D 2022 2013 2014 0000 2122 0161 203A 015B 0165 017E 017A
+00A0 02C7 02D8 0141 00A4 0104 00A6 00A7 00A8 00A9 015E 00AB 00AC 00AD 00AE 017B
+00B0 00B1 02DB 0142 00B4 00B5 00B6 00B7 00B8 0105 015F 00BB 013D 02DD 013E 017C
+0154 00C1 00C2 0102 00C4 0139 0106 00C7 010C 00C9 0118 00CB 011A 00CD 00CE 010E
+0110 0143 0147 00D3 00D4 0150 00D6 00D7 0158 016E 00DA 0170 00DC 00DD 0162 00DF
+0155 00E1 00E2 0103 00E4 013A 0107 00E7 010D 00E9 0119 00EB 011B 00ED 00EE 010F
+0111 0144 0148 00F3 00F4 0151 00F6 00F7 0159 016F 00FA 0171 00FC 00FD 0163 02D9
+</map>
+</unicode>
+
+
+<collation name="cp1250_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 46 49 4A 4B 4C 4D 4E 4F 50 52 53 55
+ 56 57 58 59 5B 5C 5D 5E 5F 60 61 63 64 65 66 67
+ 68 41 42 43 46 49 4A 4B 4C 4D 4E 4F 50 52 53 55
+ 56 57 58 59 5B 5C 5D 5E 5F 60 61 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 5A 8B 5A 5B 62 62
+ 90 91 92 93 94 95 96 97 98 99 5A 9B 5A 5B 62 62
+ 20 A1 A2 50 A4 41 A6 59 A8 A9 59 AB AC AD AE 62
+ B0 B1 B2 50 B4 B5 B6 B7 B8 41 59 BB 50 BD 50 62
+ 58 41 41 41 41 50 45 43 44 49 49 49 49 4D 4D 46
+ 47 53 53 55 55 55 55 D7 58 5C 5C 5C 5C 60 5B 59
+ 58 41 41 41 41 50 45 43 44 49 49 49 49 4D 4D 46
+ 47 53 53 55 55 55 55 F7 58 5C 5C 5C 5C 60 5B FF
+</map>
+</collation>
+
+<collation name="cp1250_croatian_ci">
+<map>
+00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+40 41 43 44 48 4B 4D 4E 4F 50 52 53 54 56 57 59
+5B 5C 5D 5F 62 64 66 67 68 69 6B 90 91 92 93 94
+95 41 43 44 48 4B 4D 4E 4F 50 52 53 54 56 57 59
+5B 5C 5D 5F 62 64 66 67 68 69 6B 96 97 98 99 9A
+9B 9C 9E 9F A0 A1 A2 A3 A4 A5 60 A6 5F 62 6C 6B
+A7 A8 A9 AA AB AC AD AE AF B0 60 B1 5F 62 6C 6B
+B2 B3 B4 54 B5 41 B6 B7 B8 B9 5F BA BB BC BD 6B
+BE BF C0 54 C1 C2 C3 C4 C5 41 5F C6 54 C7 54 6B
+5D 41 41 41 41 54 47 44 46 4B 4B 4B 4B 50 50 48
+4A 57 57 59 59 59 59 C8 5D 64 64 64 64 69 62 5F
+5D 41 41 41 41 54 47 44 46 4B 4B 4B 4B 50 50 48
+4A 57 57 59 59 59 59 C9 5D 64 64 64 64 69 62 FF
+</map>
+</collation>
+
+<collation name="cp1250_polish_ci">
+<map>
+00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+40 41 43 44 48 4B 4D 4E 4F 50 52 53 54 56 57 59
+5B 5C 5D 5F 62 64 66 67 68 69 6B 90 91 92 93 94
+95 41 43 44 48 4B 4D 4E 4F 50 52 53 54 56 57 59
+5B 5C 5D 5F 62 64 66 67 68 69 6B 96 97 98 99 9A
+9B 9C 9E 9F A0 A1 A2 A3 A4 A5 5F A6 60 62 6B 6C
+A7 A8 A9 AA AB AC AD AE AF B0 5F B1 60 62 6B 6C
+B2 B3 B4 55 B5 42 B6 B7 B8 B9 5F BA BB BC BD 6D
+BE BF C0 55 C1 C2 C3 C4 C5 42 5F C6 54 C7 54 6D
+5D 41 41 41 41 54 47 44 44 4B 4C 4B 4B 50 50 48
+48 58 57 5A 59 59 59 C8 5D 64 64 64 64 69 62 5F
+5D 41 41 41 41 54 47 44 44 4B 4C 4B 4B 50 50 48
+48 58 57 5A 59 59 59 C9 5D 64 64 64 64 69 62 FF
+</map>
+</collation>
+
+<collation name="cp1250_czech_ci"/>
+
+<collation name="cp1250_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/cp1251.xml b/sql/share/charsets/cp1251.xml
new file mode 100644
index 00000000000..4cd584c0bf5
--- /dev/null
+++ b/sql/share/charsets/cp1251.xml
@@ -0,0 +1,214 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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
+</copyright>
+
+<charset name="cp1251">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 00
+ 01 01 00 02 00 00 00 00 00 00 01 00 01 01 01 01
+ 02 00 00 00 00 00 00 00 00 00 02 00 02 02 02 02
+ 00 01 02 01 00 01 00 00 01 00 01 00 00 00 00 01
+ 00 00 01 02 02 00 00 00 02 00 02 00 02 01 02 02
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 90 83 82 83 84 85 86 87 88 89 9A 8B 9C 9D 9E 9F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A2 A2 BC A4 B4 A6 A7 B8 A9 BA AB AC AD AE BF
+ B0 B1 B3 B3 B4 B5 B6 B7 B8 B9 BA BB BC BE BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 81 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 80 91 92 93 94 95 96 97 98 99 8A 9B 8C 9D 8E 8F
+ A0 A1 A1 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B2 A5 B5 B6 B7 A8 B9 AA BB A3 BD BD AF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 0402 0403 201A 0453 201E 2026 2020 2021 20AC 2030 0409 2039 040A 040C 040B 040F
+ 0452 2018 2019 201C 201D 2022 2013 2014 0000 2122 0459 203A 045A 045C 045B 045F
+ 00A0 040E 045E 0408 00A4 0490 00A6 00A7 0401 00A9 0404 00AB 00AC 00AD 00AE 0407
+ 00B0 00B1 0406 0456 0491 00B5 00B6 00B7 0451 2116 0454 00BB 0458 0405 0455 0457
+ 0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041A 041B 041C 041D 041E 041F
+ 0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042A 042B 042C 042D 042E 042F
+ 0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043A 043B 043C 043D 043E 043F
+ 0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044A 044B 044C 044D 044E 044F
+
+</map>
+</unicode>
+
+
+<collation name="cp1251_bulgarian_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7C 7D 7E 7F 80
+ 81 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 82 83 84 85 FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF 61 FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF 61 FF FF FF FF FF FF FF
+ 5B 5C 5D 5E 5F 60 62 63 64 65 66 67 68 69 6A 6B
+ 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B
+ 5B 5C 5D 5E 5F 60 62 63 64 65 66 67 68 69 6A 6B
+ 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B
+</map>
+</collation>
+
+
+<collation name="cp1251_bin" flag="binary"/>
+
+
+<collation name="cp1251_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 43 45 47 49 4B 4D 4F 51 53 55 57 59 5B 5D
+ 5F 61 63 65 67 69 6B 6D 6F 71 73 D3 D4 D5 D6 D7
+ D8 41 43 45 47 49 4B 4D 4F 51 53 55 57 59 5B 5D
+ 5F 61 63 65 67 69 6B 6D 6F 71 73 D9 DA DB DC DD
+ 81 83 DE 83 DF E0 E1 E2 E3 E4 A1 E5 A7 9D B3 C1
+ 81 E6 E7 E8 E9 EA EB EC ED EE A1 EF A7 9D B3 C1
+ F0 B7 B7 99 F1 7D F2 F3 87 F4 89 F5 F6 F7 F8 95
+ F9 FA 93 93 7D FB FC FD 87 FE 89 FF 99 8F 8F 95
+ 75 77 79 7B 7F 85 8B 8D 91 97 9B 9F A3 A5 A9 AB
+ AD AF B1 B5 B9 BB BD BF C3 C5 C7 C9 CB CD CF D1
+ 75 77 79 7B 7F 85 8B 8D 91 97 9B 9F A3 A5 A9 AB
+ AD AF B1 B5 B9 BB BD BF C3 C5 C7 C9 CB CD CF D1
+</map>
+</collation>
+
+
+<collation name="cp1251_general_cs">
+<!--
+# Case insensitive, accent sensitive
+# Sort order is correct for Belarusian, Bulgarian, Macedonian,
+# Russian, Serbian, Mongolian languages. Almost good for Ukrainian,
+# except that "CYRILLIC LETTER SOFT SIGN" is not in the end of alphabet,
+# but between YERU and E.
+-->
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 43 45 47 49 4B 4D 4F 51 53 55 57 59 5B 5D
+ 5F 61 63 65 67 69 6B 6D 6F 71 73 D3 D4 D5 D6 D7
+ D8 42 44 46 48 4A 4C 4E 50 52 54 56 58 5A 5C 5E
+ 60 62 64 66 68 6A 6C 6E 70 72 74 D9 DA DB DC DD
+ 81 83 DE 84 DF E0 E1 E2 E3 E4 A1 E5 A7 9D B3 C1
+ 82 E6 E7 E8 E9 EA EB EC ED EE A2 EF A8 9E B4 C2
+ F0 B7 B8 99 F1 7D F2 F3 87 F4 89 F5 F6 F7 F8 95
+ F9 FA 93 94 7E FB FC FD 88 FE 8A FF 9A 8F 90 96
+ 75 77 79 7B 7F 85 8B 8D 91 97 9B 9F A3 A5 A9 AB
+ AD AF B1 B5 B9 BB BD BF C3 C5 C7 C9 CB CD CF D1
+ 76 78 7A 7C 80 86 8C 8E 92 98 9C A0 A4 A6 AA AC
+ AE B0 B2 B6 BA BC BE C0 C4 C6 C8 CA CC CE D0 D2
+</map>
+</collation>
+
+
+<collation name="cp1251_ukrainian_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 20 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4
+ B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4
+ C5 C6 C7 C8 C9 84 CA CB 88 CC 87 CD CE CF D0 8D
+ D1 D2 8C 8C 84 D3 D4 D5 88 D6 87 D7 D8 D9 DA 8D
+ 80 81 82 83 85 86 89 8A 8B 8E 8F 90 91 92 93 94
+ 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4
+ 80 81 82 83 85 86 89 8A 8B 8E 8F 90 91 92 93 94
+ 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4
+</map>
+</collation>
+
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/cp1256.xml b/sql/share/charsets/cp1256.xml
new file mode 100644
index 00000000000..ab0ba855f3b
--- /dev/null
+++ b/sql/share/charsets/cp1256.xml
@@ -0,0 +1,142 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<!-- Arabic, Persian, Pakistani, Urdu -->
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="cp1256">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 00 00
+ 00 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 03 00 02 00 00 00 00 00 00 00 00 01 03 03 00
+ 03 10 10 10 10 00 00 00 00 00 00 00 02 00 00 00
+ 00 10 00 00 00 00 00 00 00 00 00 10 10 10 00 00
+ 10 10 00 00 00 00 00 00 00 00 10 10 00 00 00 10
+ 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
+ 03 03 03 03 03 03 03 00 03 03 03 03 03 03 03 03
+ 02 03 02 03 03 03 03 02 02 02 02 02 03 03 02 02
+ 03 03 03 03 02 03 03 00 03 02 03 02 02 00 00 00
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 54 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 54 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 9C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 74 55 56 57 58 59 5A 5B 5C 5F 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 74 55 56 57 58 59 5A 7B 7C 7F 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 8C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 20AC 067E 201A 0192 201E 2026 2020 2021 02C6 2030 0000 2039 0152 0686 0698 0000
+ 06AF 2018 2019 201C 201D 2022 2013 2014 0000 2122 0000 203A 0153 200C 200D 0000
+ 00A0 060C 00A2 00A3 00A4 00A5 00A6 00A7 00A8 00A9 0000 00AB 00AC 00AD 00AE 00AF
+ 00B0 00B1 00B2 00B3 00B4 00B5 00B6 00B7 00B8 00B9 061B 00BB 00BC 00BD 00BE 061F
+ 0000 0621 0622 0623 0624 0625 0626 0627 0628 0629 062A 062B 062C 062D 062E 062F
+ 0630 0631 0632 0633 0634 0635 0636 00D7 0637 0638 0639 063A 0640 0641 0642 0643
+ 00E0 0644 00E2 0645 0646 0647 0648 00E7 00E8 00E9 00EA 00EB 0649 064A 00EE 00EF
+ 064B 064C 064D 064E 00F4 064F 0650 00F7 0651 00F9 0652 00FB 00FC 200E 200F 0000
+</map>
+</unicode>
+
+
+<collation name="cp1256_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 45 47 4A 4C 52 55 57 59 5D 5F 61 63 65 67
+ 6C 6E 70 72 74 76 7B 7D 7F 81 83 B9 BA BB BC BD
+ BE 41 45 47 4A 4C 52 55 57 59 5D 5F 61 63 65 67
+ 6C 6E 70 72 74 76 7B 7D 7F 81 83 BF C0 C1 C2 C3
+ C4 8E C5 54 C6 C7 C8 C9 CA CB CC CD 6A 92 99 CE
+ A5 CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 6A DA DB DC
+ DD B6 DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB
+ EC ED EE EF F0 F1 F2 F3 F4 F5 B7 F6 F7 F8 F9 B8
+ FA 85 86 87 88 89 8A 8B 8C 8D 9F 90 91 93 94 95
+ 96 97 98 9A 9B 9C 9D FB 9E 9F A0 A1 AD A2 A3 A4
+ 43 A6 44 A7 A8 A9 AA 49 4E 4F 50 51 AB AC 5B 5C
+ AE AF B0 B1 69 B2 B3 FC B4 78 B5 79 7A FD FE FF
+</map>
+</collation>
+
+<collation name="cp1256_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
+
diff --git a/sql/share/charsets/cp1257.xml b/sql/share/charsets/cp1257.xml
new file mode 100644
index 00000000000..61d1d276b0a
--- /dev/null
+++ b/sql/share/charsets/cp1257.xml
@@ -0,0 +1,228 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="cp1257">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 01 00 01 00 00 00 00 01
+ 00 00 00 00 00 00 00 00 02 00 02 00 00 00 00 02
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 00 02 02 02 02 02 02 02 00
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 B8 A9 BA AB AC AD AE BF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 D7 F8 F9 FA FB FC FD FE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 BA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 A8 B9 BA BB BC BD BE AF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC DD DE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 20AC 0000 201A 0000 201E 2026 2020 2021 0000 2030 0000 2039 0000 00A8 02C7 00B8
+ 0000 2018 2019 201C 201D 2022 2013 2014 0000 2122 0000 203A 0000 00AF 02DB 0000
+ 00A0 0000 00A2 00A3 00A4 0000 00A6 00A7 00D8 00A9 0156 00AB 00AC 00AD 00AE 00C6
+ 00B0 00B1 00B2 00B3 00B4 00B5 00B6 00B7 00F8 00B9 0157 00BB 00BC 00BD 00BE 00E6
+ 0104 012E 0100 0106 00C4 00C5 0118 0112 010C 00C9 0179 0116 0122 0136 012A 013B
+ 0160 0143 0145 00D3 014C 00D5 00D6 00D7 0172 0141 015A 016A 00DC 017B 017D 00DF
+ 0105 012F 0101 0107 00E4 00E5 0119 0113 010D 00E9 017A 0117 0123 0137 012B 013C
+ 0161 0144 0146 00F3 014D 00F5 00F6 00F7 0173 0142 015B 016B 00FC 017C 017E 02D9
+</map>
+</unicode>
+
+
+<collation name="cp1257_lithuanian_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 43 44 46 47 4A 4B 4C 4D 50 51 52 53 54 55
+ 56 57 58 59 5B 5C 5F 60 61 4E FF 62 63 64 65 66
+ 67 41 43 44 46 47 4A 4B 4C 4D 50 51 52 53 54 55
+ 56 57 58 59 5B 5C 5F 60 61 4E FF 68 69 6A 6B FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ 42 4F FF FF FF FF 48 FF 45 FF FF 49 FF FF FF FF
+ 5A FF FF FF FF FF FF FF 5E FF FF 5D FF FF FF FF
+ FF 4F FF FF FF FF 48 FF 45 FF FF 49 FF FF FF FF
+ 5A FF FF FF FF FF FF FF 5E FF FF 5D FF FF FF FF
+</map>
+</collation>
+
+
+<collation name="cp1257_bin" flag="binary"/>
+
+
+<collation name="cp1257_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 4D 4F 55 57 61 63 67 69 6F 71 75 7B 7D 83
+ 8F 91 93 97 9E A0 A8 AA AC AE B0 B8 B9 BA BB BC
+ BD 41 4D 4F 55 57 61 63 67 69 6F 71 75 7B 7D 83
+ 8F 91 93 97 9E A0 A8 AA AC AE B0 BE BF C0 C1 C4
+ C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4
+ D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4
+ E5 E6 E7 E8 E9 EA EB EC 83 ED 93 EE EF F0 F1 41
+ F2 F3 F4 F5 F6 F7 F8 F9 83 FA 93 FB FC FD FE 41
+ 41 69 41 4F 41 41 57 57 4F 57 B0 57 63 71 69 75
+ 97 7D 7D 83 83 83 83 C2 A0 75 97 A0 A0 B0 B0 97
+ 41 69 41 4F 41 41 57 57 4F 57 B0 57 63 71 69 75
+ 97 7D 7D 83 83 83 83 C3 A0 75 97 A0 A0 B0 B0 FF
+</map>
+</collation>
+
+
+<collation name="cp1257_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 4D 4F 55 57 61 63 67 69 6F 71 75 7B 7D 83
+ 8F 91 93 97 9E A0 A8 AA AC AE B0 B8 B9 BA BB BC
+ BD 41 4D 4F 55 57 61 63 67 69 6F 71 75 7B 7D 83
+ 8F 91 93 97 9E A0 A8 AA AC AE B0 BE BF C0 C1 C4
+ C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4
+ D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4
+ E5 E6 E7 E8 E9 EA EB EC 85 ED 95 EE EF F0 F1 4B
+ F2 F3 F4 F5 F6 F7 F8 F9 85 FA 95 FB FC FD FE 4B
+ 43 6B 45 51 47 49 59 5B 53 5D B2 5F 65 73 6D 77
+ 99 7F 81 87 89 8B 8D C2 A2 79 9B A4 A6 B4 B6 9D
+ 43 6B 45 51 47 49 59 5B 53 5D B2 5F 65 73 6D 77
+ 99 7F 81 87 89 8B 8D C3 A2 79 9B A4 A6 B4 B6 FF
+</map>
+</collation>
+
+
+<collation name="cp1257_cs">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 4D 4F 55 57 61 63 67 69 6F 71 75 7B 7D 83
+ 8F 91 93 97 9E A0 A8 AA AC AE B0 B8 B9 BA BB BC
+ BD 42 4E 50 56 58 62 64 68 6A 70 72 76 7C 7E 84
+ 90 92 94 98 9F A1 A9 AB AD AF B1 BE BF C0 C1 C4
+ C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4
+ D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4
+ E5 E6 E7 E8 E9 EA EB EC 85 ED 95 EE EF F0 F1 4B
+ F2 F3 F4 F5 F6 F7 F8 F9 86 FA 96 FB FC FD FE 4C
+ 43 6B 45 51 47 49 59 5B 53 5D B2 5F 65 73 6D 77
+ 99 7F 81 87 89 8B 8D C2 A2 79 9B A4 A6 B4 B6 9D
+ 44 6C 46 52 48 4A 5A 5C 54 5E B3 60 66 74 6E 78
+ 9A 80 82 88 8A 8C 8E C3 A3 7A 9C A5 A7 B5 B7 FF
+</map>
+</collation>
+
+
+<collation name="cp1257ltlv">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 47 49 4D 4F 57 59 5D 5F 65 67 6B 6F 71 75
+ 79 7B 7D 81 85 87 8D 8F 91 93 95 FF FF FF FF FF
+ FF 42 48 4A 4E 50 58 5A 5E 60 66 68 6C 70 72 76
+ 7A 7C 7E 82 86 88 8E 90 92 94 96 FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF 7F FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF 80 FF FF FF FF FF
+ 45 63 43 FF FF FF 53 51 4B FF FF 55 5B 69 61 6D
+ 83 FF 73 FF 77 FF FF FF 8B FF FF 89 FF 99 97 FF
+ 46 64 44 FF FF FF 54 52 4C FF FF 56 5C 6A 62 6E
+ 84 FF 74 FF 78 FF FF FF 8C FF FF 8A FF 9A 98 FF
+</map>
+</collation>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/cp850.xml b/sql/share/charsets/cp850.xml
new file mode 100644
index 00000000000..06465540a75
--- /dev/null
+++ b/sql/share/charsets/cp850.xml
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="cp850">
+
+<ctype>
+<map>
+ 00
+ 20 30 30 30 30 30 30 20 20 28 28 28 28 28 30 30
+ 30 30 30 30 30 30 30 30 30 30 20 30 30 30 30 30
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 30
+ 01 02 02 02 02 02 02 02 02 02 02 02 02 02 01 01
+ 01 02 01 02 02 02 02 02 02 01 01 02 10 01 10 10
+ 02 02 02 02 02 01 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 01 01 01 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 10 02 01 10 10 10 10 10 10 10 10
+ 02 01 01 01 01 02 01 01 01 10 10 10 10 10 01 10
+ 01 02 01 01 02 01 10 02 01 01 01 01 02 01 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 20
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 87 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 84 86
+ 82 91 91 93 94 95 96 97 98 94 81 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A4 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 9A 90 41 8E 41 8F 80 45 45 45 49 49 49 8E 8F
+ 90 92 92 4F 99 4F 55 55 59 99 9A 9B 9C 9D 9E 9F
+ 41 49 4F 55 A5 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f
+00c7 00fc 00e9 00e2 00e4 00e0 00e5 00e7 00ea 00eb 00e8 00ef 00ee 00ec 00c4 00c5
+00c9 00e6 00c6 00f4 00f6 00f2 00fb 00f9 00ff 00d6 00dc 00f8 00a3 00d8 00d7 0192
+00e1 00ed 00f3 00fa 00f1 00d1 00aa 00ba 00bf 00ae 00ac 00bd 00bc 00a1 00ab 00bb
+2591 2592 2593 2502 2524 00c1 00c2 00c0 00a9 2563 2551 2557 255d 00a2 00a5 2510
+2514 2534 252c 251c 2500 253c 00e3 00c3 255a 2554 2569 2566 2560 2550 256c 00a4
+00f0 00d0 00ca 00cb 00c8 0131 00cd 00ce 00cf 2518 250c 2588 2584 00a6 00cc 2580
+00d3 00df 00d4 00d2 00f5 00d5 00b5 00fe 00de 00da 00db 00d9 00fd 00dd 00af 00b4
+00ad 00b1 2017 00be 00b6 00a7 00f7 00b8 00b0 00a8 00b7 00b9 00b3 00b2 25a0 00a0
+</map>
+</unicode>
+
+
+<collation name="cp850_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 51 53 55 59 63 65 67 69 74 76 78 7A 7C 80
+ 8E 90 92 94 97 99 A3 A5 A7 A9 AE B1 B2 B3 B4 B5
+ B6 41 51 53 55 59 63 65 67 69 74 76 78 7A 7C 80
+ 8E 90 92 94 97 99 A3 A5 A7 A9 AE B7 B8 B9 BA BB
+ 54 A1 5D 47 4B 43 4D 54 5F 61 5B 71 6F 6B 4B 4D
+ 5D 4F 4F 86 8A 82 9F 9B AD 8A A1 8C E3 8C BD BE
+ 45 6D 84 9D 7E 7E EA FA FF EE EC FD FC CE EB FB
+ DC DD DE C3 C9 45 47 43 E9 D5 CF D1 D3 E2 E5 C5
+ C6 CB CA C8 C2 CC 49 49 D2 D0 D7 D6 D4 CD D8 E4
+ 57 57 5F 61 5B 73 6D 6F 71 C7 C4 DB DA E6 6B D9
+ 84 96 86 82 88 88 F5 B0 B0 9D 9F 9B AB AB EF F4
+ ED F1 C1 FE F6 E7 BF BC F0 E8 F7 F9 F3 F2 DF E0
+</map>
+</collation>
+
+<collation name="cp850_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/cp852.xml b/sql/share/charsets/cp852.xml
new file mode 100644
index 00000000000..e0c574d2ea1
--- /dev/null
+++ b/sql/share/charsets/cp852.xml
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2004 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="cp852">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 00
+ 01 02 02 02 02 02 02 02 02 02 01 02 02 01 01 01
+ 01 01 02 02 02 01 02 01 02 01 01 01 02 01 00 02
+ 02 02 02 02 01 02 01 02 01 02 00 02 01 01 00 00
+ 00 00 00 00 00 01 01 01 02 00 00 00 00 01 02 00
+ 00 00 00 00 00 00 01 02 00 00 00 00 00 00 00 00
+ 02 01 01 01 02 01 01 01 02 00 00 00 00 01 01 00
+ 01 02 01 01 02 02 01 02 01 01 02 01 02 01 02 00
+ 00 00 00 00 00 00 00 00 00 00 00 02 01 02 00 48
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 87 81 82 83 84 85 86 87 88 89 8B 8B 8C AB 84 86
+ 82 92 92 93 94 96 96 98 98 94 81 9C 9C 88 9E 9F
+ A0 A1 A2 A3 A5 A5 A7 A7 A9 A9 AA AB 9F B8 AE AF
+ B0 B1 B2 B3 B4 A0 83 D8 B8 B9 BA BB BC BE BE BF
+ C0 C1 C2 C3 C4 C5 C7 C7 C8 C9 CA CB CC CD CE CF
+ D0 D0 D4 89 D4 E5 A1 8C D8 D9 DA DB DC EE 85 DF
+ A2 E1 93 E4 E4 E5 E7 E7 EA A3 E8 FB EC EC EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 9A 90 B6 8E DE 8F 80 9D D3 8A 8A D7 8D 8E 8F
+ 90 91 91 E2 99 95 95 97 97 99 9A 9B 9B 9D 9E AC
+ B5 D6 E0 E9 A4 A4 A6 A6 A8 A8 AA 8D AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 AD B9 BA BB BC BE BD BF
+ C0 C1 C2 C3 C4 C5 C6 C6 C8 C9 CA CB CC CD CE CF
+ D1 D1 D2 D3 D2 D5 D6 D7 B7 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E3 D5 E6 E6 E8 E9 E8 EB ED ED DD EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA EB FC FC FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 00C7 00FC 00E9 00E2 00E4 016F 0107 00E7 0142 00EB 0150 0151 00EE 0179 00C4 0106
+ 00C9 0139 013A 00F4 00F6 013D 013E 015A 015B 00D6 00DC 0164 0165 0141 00D7 010D
+ 00E1 00ED 00F3 00FA 0104 0105 017D 017E 0118 0119 00AC 017A 010C 015F 00AB 00BB
+ 2591 2592 2593 2502 2524 00C1 00C2 011A 015E 2563 2551 2557 255D 017B 017C 2510
+ 2514 2534 252C 251C 2500 253C 0102 0103 255A 2554 2569 2566 2560 2550 256C 00A4
+ 0111 0110 010E 00CB 010F 0147 00CD 00CE 011B 2518 250C 2588 2584 0162 016E 2580
+ 00D3 00DF 00D4 0143 0144 0148 0160 0161 0154 00DA 0155 0170 00FD 00DD 0163 00B4
+ 00AD 02DD 02DB 02C7 02D8 00A7 00F7 00B8 00B0 00A8 02D9 0171 0158 0159 25A0 00A0
+</map>
+</unicode>
+
+
+<collation name="cp852_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 47 48 4C 4F 54 55 56 57 5A 5B 5C 5E 5F 62
+ 67 68 69 6C 71 74 75 76 77 78 7B 90 91 92 93 94
+ 95 41 47 48 4C 4F 54 55 56 57 5A 5B 5C 5E 5F 62
+ 67 68 69 6C 71 74 75 76 77 78 7B 96 97 98 99 9A
+ 48 74 4F 41 41 74 48 48 5C 4F 62 62 57 7B 41 48
+ 4F 5C 5C 62 62 5C 5C 6C 6C 62 74 71 71 5C 9E 48
+ 41 57 62 74 41 41 7B 7B 4F 4F AA 7B 48 6C AE AF
+ B0 B1 B2 B3 B4 41 41 4F 6C B5 BA BB BC 7B 7B BF
+ C0 C1 C2 C3 C4 C5 41 41 C8 C9 CA CB CC CD CE CF
+ 4C 4C 4C 4F 4C 60 57 57 4F D9 DA DB DC 71 74 DF
+ 62 70 62 60 60 60 6C 6C 69 74 69 74 78 78 71 EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA 74 69 69 FE FF
+</map>
+</collation>
+
+<collation name="cp852_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/cp866.xml b/sql/share/charsets/cp866.xml
new file mode 100644
index 00000000000..9cd8c8c504b
--- /dev/null
+++ b/sql/share/charsets/cp866.xml
@@ -0,0 +1,142 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="cp866">
+<!-- cp866_DOSCyrillicRussian -->
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 00
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 01 02 01 02 01 02 01 02 00 00 00 00 00 00 00 48
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ A0 A1 A2 A3 A4 A5 86 87 88 89 AA AB AC AD AE AF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ A0 A1 A2 A3 A4 A5 86 87 88 89 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F1 F1 F3 F3 F5 F5 F7 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ F0 F0 F2 F2 F4 F4 F6 F6 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041A 041B 041C 041D 041E 041F
+ 0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042A 042B 042C 042D 042E 042F
+ 0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043A 043B 043C 043D 043E 043F
+ 2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255D 255C 255B 2510
+ 2514 2534 252C 251C 2500 253C 255E 255F 255A 2554 2569 2566 2560 2550 256C 2567
+ 2568 2564 2565 2559 2558 2552 2553 256B 256A 2518 250C 2588 2584 258C 2590 2580
+ 0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044A 044B 044C 044D 044E 044F
+ 0401 0451 0404 0454 0407 0457 040E 045E 00B0 2219 00B7 221A 207F 00B2 25A0 00A0
+</map>
+</unicode>
+
+
+<collation name="cp866_general_ci">
+<!-- Case insensitive, accent sensitive -->
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 43 45 47 49 4B 4D 4F 51 53 55 57 59 5B 5D
+ 5F 61 63 65 67 69 6B 6D 6F 71 73 BD BE BF C0 C1
+ C2 41 43 45 47 49 4B 4D 4F 51 54 55 57 59 5B 5D
+ 5F 61 63 65 67 69 6B 6D 6F 71 73 C3 C4 C5 C6 C7
+ 75 77 79 7B 7D 7F 85 87 89 8D 8F 91 93 95 97 99
+ 9B 9D 9F A1 A5 A7 A9 AB AD AF B1 B3 B5 B7 B9 BB
+ 75 77 79 7B 7D 7F 85 87 89 8D 8F 91 93 95 97 99
+ C8 C9 CA D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+ 9B 9D 9F A1 A5 A7 A9 AB AD AF B1 B3 B5 B7 B9 BB
+ 81 81 83 83 8B 8B A3 A3 CB CC CD CE CF D0 D1 D2
+</map>
+</collation>
+
+<collation name="cp866_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
+
diff --git a/sql/share/charsets/dec8.xml b/sql/share/charsets/dec8.xml
new file mode 100644
index 00000000000..68949309ced
--- /dev/null
+++ b/sql/share/charsets/dec8.xml
@@ -0,0 +1,140 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="dec8">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 10 01 01 01 01 01 01 01 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 10 02 02 02 02 02 02 02 02
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 D7 F8 F9 FA FB FC FD FE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC DD DE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 00A1 00A2 00A3 0000 00A5 0000 00A7 00A4 00A9 00AA 00AB 0000 0000 0000 0000
+00B0 00B1 00B2 00B3 0000 00B5 00B6 00B7 0000 00B9 00BA 00BB 00BC 00BD 0000 00BF
+00C0 00C1 00C2 00C3 00C4 00C5 00C6 00C7 00C8 00C9 00CA 00CB 00CC 00CD 00CE 00CF
+0000 00D1 00D2 00D3 00D4 00D5 00D6 0152 00D8 00D9 00DA 00DB 00DC 0178 0000 00DF
+00E0 00E1 00E2 00E3 00E4 00E5 00E6 00E7 00E8 00E9 00EA 00EB 00EC 00ED 00EE 00EF
+0000 00F1 00F2 00F3 00F4 00F5 00F6 0153 00F8 00F9 00FA 00FB 00FC 00FF 0000 0000
+</map>
+</unicode>
+
+
+<collation name="dec8_swedish_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ 41 41 41 41 5C 5B 5C 43 45 45 45 45 49 49 49 49
+ 44 4E 4F 4F 4F 4F 5D D7 D8 55 55 55 59 59 DE DF
+ 41 41 41 41 5C 5B 5C 43 45 45 45 45 49 49 49 49
+ 44 4E 4F 4F 4F 4F 5D F7 D8 55 55 55 59 59 DE FF
+</map>
+</collation>
+
+<collation name="dec8_bin" flag="binary"/>
+
+</charset>
+
+
+</charsets>
diff --git a/sql/share/charsets/geostd8.xml b/sql/share/charsets/geostd8.xml
new file mode 100644
index 00000000000..822cc083724
--- /dev/null
+++ b/sql/share/charsets/geostd8.xml
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="geostd8">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 00 10 00 10 10 10 10 00 10 00 10 00 00 00 00
+ 00 10 10 10 10 10 10 10 00 00 00 10 00 00 00 00
+ 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
+ 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
+ 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+20AC 0000 201A 0000 201E 2026 2020 2021 0000 2030 0000 2039 0000 0000 0000 0000
+0000 2018 2019 201C 201D 2022 2013 2014 0000 0000 0000 203A 0000 0000 0000 0000
+00A0 00A1 00A2 00A3 00A4 00A5 00A6 00A7 00A8 00A9 00AA 00AB 00AC 00AD 00AE 00AF
+00B0 00B1 00B2 00B3 00B4 00B5 00B6 00B7 00B8 00B9 00BA 00BB 00BC 00BD 00BE 00BF
+10D0 10D1 10D2 10D3 10D4 10D5 10D6 10F1 10D7 10D8 10D9 10DA 10DB 10DC 10F2 10DD
+10DE 10DF 10E0 10E1 10E2 10F3 10E3 10E4 10E5 10E6 10E7 10E8 10E9 10EA 10EB 10EC
+10ED 10EE 10F4 10EF 10F0 10F5 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 2116 0000 0000
+</map>
+</unicode>
+
+
+<collation name="geostd8_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="geostd8_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/greek.xml b/sql/share/charsets/greek.xml
new file mode 100644
index 00000000000..cbbe22e675a
--- /dev/null
+++ b/sql/share/charsets/greek.xml
@@ -0,0 +1,144 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="greek">
+
+<!-- It's ISO Greek rahter than WIN Greek because -->
+<!-- 0xB6 is marked as upper letter, it's true for ISO Greek version -->
+<!-- In Windows version this character is PILCROW SIGN -->
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 48 10 10 10 00 00 10 10 10 10 00 10 10 10 00 10
+ 10 10 10 10 10 10 01 10 01 01 01 10 01 10 01 01
+ 02 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 00 01 01 01 01 01 01 01 01 01 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 00
+ </map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 DC B7 DD DE DF BB FC BD FD FE
+ C0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 D2 F3 F4 F5 F6 F7 F8 F9 FA FB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ DA C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB C1 C5 C7 C9
+ DB C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D3 D3 D4 D5 D6 D7 D8 D9 DA DB CF D5 D9 FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 02BD 02BC 00A3 0000 0000 00A6 00A7 00A8 00A9 0000 00AB 00AC 00AD 0000 2015
+00B0 00B1 00B2 00B3 0384 0385 0386 00B7 0388 0389 038A 00BB 038C 00BD 038E 038F
+0390 0391 0392 0393 0394 0395 0396 0397 0398 0399 039A 039B 039C 039D 039E 039F
+03A0 03A1 0000 03A3 03A4 03A5 03A6 03A7 03A8 03A9 03AA 03AB 03AC 03AD 03AE 03AF
+03B0 03B1 03B2 03B3 03B4 03B5 03B6 03B7 03B8 03B9 03BA 03BB 03BC 03BD 03BE 03BF
+03C0 03C1 03C2 03C3 03C4 03C5 03C6 03C7 03C8 03C9 03CA 03CB 03CC 03CD 03CE 0000
+</map>
+</unicode>
+
+
+<collation name="greek_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 C1 B7 C5 C7 C9 BB CF BD D5 D9
+ C9 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 C9 D5 C1 C5 C7 C9
+ D5 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D3 D3 D4 D5 D6 D7 D8 D9 C9 D5 CF D5 D9 FF
+</map>
+</collation>
+
+<collation name="greek_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
+
diff --git a/sql/share/charsets/hebrew.xml b/sql/share/charsets/hebrew.xml
new file mode 100644
index 00000000000..562fa4f4748
--- /dev/null
+++ b/sql/share/charsets/hebrew.xml
@@ -0,0 +1,140 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2006 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="hebrew">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 00 00 20 20 00
+ </map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 0000 00A2 00A3 00A4 00A5 00A6 00A7 00A8 00A9 00D7 00AB 00AC 00AD 00AE 203E
+00B0 00B1 00B2 00B3 00B4 00B5 00B6 00B7 00B8 00B9 00F7 00BB 00BC 00BD 00BE 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 2017
+05D0 05D1 05D2 05D3 05D4 05D5 05D6 05D7 05D8 05D9 05DA 05DB 05DC 05DD 05DE 05DF
+05E0 05E1 05E2 05E3 05E4 05E5 05E6 05E7 05E8 05E9 05EA 0000 0000 200E 200F 0000
+</map>
+</unicode>
+
+
+<collation name="hebrew_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC DD DE FF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="hebrew_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
+
diff --git a/sql/share/charsets/hp8.xml b/sql/share/charsets/hp8.xml
new file mode 100644
index 00000000000..b17f75ed73e
--- /dev/null
+++ b/sql/share/charsets/hp8.xml
@@ -0,0 +1,140 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="hp8">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 20 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 20 20 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 02 10 10 10 10 10 10 02 10 02 02
+ 01 10 10 01 02 10 10 02 01 10 01 01 01 10 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 20 20 20 20 10 10 10 10 10 10 10 10 10 20
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 C8 C0 C9 C1 CD D1 DD A8 A9 AA AB AC CB C3 AF
+ B0 B2 B2 B3 B5 B5 B7 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D4 D1 D6 D7 D4 D5 D6 D7 CC D9 CE CF C5 DD DE C2
+ C4 E2 E2 E4 E4 D5 D9 C6 CA EA EA EC EC C7 EF EF
+ F1 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B1 B3 B4 B4 B6 B6 B8 B9 BA BB BC BD BE BF
+ A2 A4 DF AE E0 DC E7 ED A1 A3 E8 AD D8 A5 DA DB
+ D0 A6 D2 D3 D0 E5 D2 D3 D8 E6 DA DB DC A7 DE DF
+ E0 E1 E1 E3 E3 E5 E6 E7 E8 E9 E9 EB EB ED EE EE
+ F0 F0 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 00C0 00C2 00C8 00CA 00CB 00CE 00CF 00B4 02CB 02C6 00A8 02DC 00D9 00DB 20A4
+00AF 00DD 00FD 00B0 00C7 00E7 00D1 00F1 00A1 00BF 00A4 00A3 00A5 00A7 0192 00A2
+00E2 00EA 00F4 00FB 00E1 00E9 00F3 00FA 00E0 00E8 00F2 00F9 00E4 00EB 00F6 00FC
+00C5 00EE 00D8 00C6 00E5 00ED 00F8 00E6 00C4 00EC 00D6 00DC 00C9 00EF 00DF 00D4
+00C1 00C3 00E3 00D0 00F0 00CD 00CC 00D3 00D2 00D5 00F5 0160 0161 00DA 0178 00FF
+00DE 00FE 00B7 00B5 00B6 00BE 2014 00BC 00BD 00AA 00BA 00AB 25A0 00BB 00B1 0000
+
+</map>
+</unicode>
+
+
+<collation name="hp8_english_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5C 5D 5B 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="hp8_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/keybcs2.xml b/sql/share/charsets/keybcs2.xml
new file mode 100644
index 00000000000..7c2775ba5c3
--- /dev/null
+++ b/sql/share/charsets/keybcs2.xml
@@ -0,0 +1,140 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="keybcs2">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 00
+ 01 02 82 02 02 01 01 02 82 81 01 01 02 02 01 01
+ 81 02 01 02 02 01 02 01 02 01 01 01 01 01 01 02
+ 02 02 02 02 02 01 01 01 02 02 02 01 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 02 02 01 02 01 02 00 02 01 01 01 02 00 02 02 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 48
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 87 81 82 83 84 83 86 87 88 88 8D A1 8C 8D 84 A0
+ 82 91 91 93 94 A2 96 A3 98 94 81 9B 8C 98 A9 9F
+ A0 A1 A2 A3 A4 A4 96 93 9B A9 AA AA AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 ED E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 68 59 5A 7B 7C 7D 7E 7F
+ 87 9A 90 85 8E 85 86 80 89 89 8A 8B 9C 8A 8E 8F
+ 90 92 92 A7 99 95 A6 97 9D 99 9A A8 9C 9D 9E 9F
+ 8F 8B 95 97 A5 A5 A6 A7 A8 9E AB AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC E8 EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 010C 00FC 00E9 010F 00E4 010E 0164 010D 011B 011A 0139 00CD 013E 013A 00C4 00C1
+ 00C9 017E 017D 00F4 00F6 00D3 016F 00DA 00FD 00D6 00DC 0160 013D 00DD 0158 0165
+ 00E1 00ED 00F3 00FA 0148 0147 016E 00D4 0161 0159 0155 0154 00BC 00A1 00AB 00BB
+ 2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255D 255C 255B 2510
+ 2514 2534 252C 251C 2500 253C 255E 255F 255A 2554 2569 2566 2560 2550 256C 2567
+ 2568 2564 2565 2559 2558 2552 2553 256B 256A 2518 250C 2588 2584 258C 2590 2580
+ 03B1 00DF 0393 03C0 03A3 03C3 00B5 03C4 03A6 0398 03A9 03B4 221E 03C6 03B5 2229
+ 2261 00B1 2265 2264 2320 2321 00F7 2248 00B0 2219 00B7 221A 207F 00B2 25A0 00A0
+</map>
+</unicode>
+
+
+<collation name="keybcs2_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 44 45 47 49 50 51 52 53 54 55 56 57 58 5A
+ 5E 5F 60 63 66 68 6C 6D 6E 6F 72 90 91 92 93 94
+ 95 41 44 45 47 49 50 51 52 53 54 55 56 57 58 5A
+ 5E 5F 60 63 66 68 6C 6D 6E 6F 72 96 97 98 99 9A
+ 45 68 49 47 41 47 66 45 49 49 56 53 56 56 41 41
+ 49 72 72 5A 5A 5A 68 68 6F 5A 68 63 56 6F 60 66
+ 41 53 5A 68 58 58 68 5A 63 60 60 60 A0 A1 A2 A3
+ A4 A5 A6 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC
+ BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC
+ CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC
+ 80 65 83 87 88 89 DD 8A 85 8B 84 81 DE 85 82 DF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="keybcs2_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
+
diff --git a/sql/share/charsets/koi8r.xml b/sql/share/charsets/koi8r.xml
new file mode 100644
index 00000000000..25264d4f9ce
--- /dev/null
+++ b/sql/share/charsets/koi8r.xml
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="koi8r">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 02 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 01 10 10 10 10 10 10 10 10 10 10 10 10
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 A3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 B3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f
+2500 2502 250c 2510 2514 2518 251c 2524 252c 2534 253c 2580 2584 2588 258c 2590
+2591 2592 2593 2320 25a0 2219 221a 2248 2264 2265 00a0 2321 00b0 00b2 00b7 00f7
+2550 2551 2552 0451 2553 2554 2555 2556 2557 2558 2559 255a 255b 255c 255d 255e
+255f 2560 2561 0401 2562 2563 2564 2565 2566 2567 2568 2569 256a 256b 256c 00a9
+044e 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043a 043b 043c 043d 043e
+043f 044f 0440 0441 0442 0443 0436 0432 044c 044b 0437 0448 044d 0449 0447 044a
+042e 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041a 041b 041c 041d 041e
+041f 042f 0420 0421 0422 0423 0416 0412 042c 042b 0417 0428 042d 0429 0427 042a
+</map>
+</unicode>
+
+
+<collation name="koi8r_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 E5 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE
+ AF B0 B1 E5 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD
+ FE DF E0 F6 E3 E4 F4 E2 F5 E8 E9 EA EB EC ED EE
+ EF FF F0 F1 F2 F3 E6 E1 FC FB E7 F8 FD F9 F7 FA
+ FE DF E0 F6 E3 E4 F4 E2 F5 E8 E9 EA EB EC ED EE
+ EF FF F0 F1 F2 F3 E6 E1 FC FB E7 F8 FD F9 F7 FA
+</map>
+</collation>
+
+<collation name="koi8r_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/koi8u.xml b/sql/share/charsets/koi8u.xml
new file mode 100644
index 00000000000..a2f5de9feb2
--- /dev/null
+++ b/sql/share/charsets/koi8u.xml
@@ -0,0 +1,140 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="koi8u">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 02 02 10 02 02 10 10 10 10 10 02 10 10
+ 10 10 10 01 01 10 01 01 10 10 10 10 10 01 10 10
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 20 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 20 20 20 A3 A4 20 A6 A7 20 20 20 20 20 AD 20 20
+ 20 20 20 A3 A4 20 A6 A7 20 20 20 20 20 AD 20 20
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 20 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 20 20 20 B3 B4 20 B6 B7 20 20 20 20 20 BD 20 20
+ 20 20 20 B3 B4 20 B6 B7 20 20 20 20 20 BD 20 20
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+2500 2502 250C 2510 2514 2518 251C 2524 252C 2534 253C 2580 2584 2588 258C 2590
+2591 2592 2593 2320 25A0 2022 221A 2248 2264 2265 00A0 2321 00B0 00B2 00B7 00F7
+2550 2551 2552 0451 0454 2554 0456 0457 2557 2558 2559 255A 255B 0491 255D 255E
+255F 2560 2561 0401 0404 2563 0406 0407 2566 2567 2568 2569 256A 0490 256C 00A9
+044E 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043A 043B 043C 043D 043E
+043F 044F 0440 0441 0442 0443 0436 0432 044C 044B 0437 0448 044D 0449 0447 044A
+042E 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041A 041B 041C 041D 041E
+041F 042F 0420 0421 0422 0423 0416 0412 042C 042B 0417 0428 042D 0429 0427 042A
+</map>
+</unicode>
+
+
+<collation name="koi8u_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 20 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4
+ B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4
+ C5 C6 C7 88 87 C8 8C 8D C9 CA CB CC CD 84 CE CF
+ D0 D1 D2 88 87 D3 8C 8D D4 D5 D6 D7 D8 84 D9 DA
+ A3 80 81 9B 85 86 99 83 9A 8B 8E 8F 90 91 92 93
+ 94 A4 95 96 97 98 89 82 A1 A0 8A 9D A2 9E 9C 9F
+ A3 80 81 9B 85 86 99 83 9A 8B 8E 8F 90 91 92 93
+ 94 A4 95 96 97 98 89 82 A1 A0 8A 9D A2 9E 9C 9F
+</map>
+</collation>
+
+<collation name="koi8u_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
+
diff --git a/sql/share/charsets/languages.html b/sql/share/charsets/languages.html
new file mode 100644
index 00000000000..3263d6a2ae2
--- /dev/null
+++ b/sql/share/charsets/languages.html
@@ -0,0 +1,274 @@
+#!/bin/sh
+
+# Copyright (C) 2003 MySQL AB
+# Use is subject to license terms
+#
+# 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+#<pre>
+(
+echo "DROP TABLE lang;"
+echo "CREATE TABLE lang (lang varchar(128), letters text character set utf8);"
+(
+grep -v "^#" << END
+#
+Greenlandic &#x00C1;&#x00C2;&#x00C3;&#x00CA;&#x00CD;&#x00CE;&#x00D4;&#x00DA;&#x00DB;&#x00E1;&#x00E2;&#x00E3;&#x00EA;&#x00ED;&#x00EE;&#x00F4;&#x00FA;&#x00FB;&#x0128;&#x0129;&#x0138;&#x0168;&#x0169;
+#Use of these letters was abolished in a spelling reform in 1973:
+#Greenlandic &#x00C5;&#x00C6;&#x00D8;&#x00E5;&#x00E6;&#x00F8;
+#Characters not found in the UCS:
+# K LATIN CAPITAL LETTER KRA
+#############################################################
+#Basque &#x00D1;&#x00DC;&#x00F1;&#x00FC;&#x0154;&#x0155;
+#Characters not found in the UCS:
+# D LATIN CAPITAL LETTER D WITH MACRON
+# d LATIN SMALL LETTER D WITH MACRON
+# L LATIN CAPITAL LETTER L WITH MACRON
+# l LATIN SMALL LETTER L WITH MACRON
+# T LATIN CAPITAL LETTER T WITH MACRON
+# t LATIN SMALL LETTER T WITH MACRON
+#############################################################
+#Maltese #&#x00C0;&#x00C1;&#x00C2;&#x00C8;&#x00C9;&#x00CA;&#x00CC;&#x00CD;&#x00CE;&#x00D2;&#x00D3;&#x00D4;&#x00D9;&#x00DA;&#x00DB;#&#x00E0;&#x00E1;&#x00E2;&#x00E8;&#x00E9;&#x00EA;&#x00EC;&#x00ED;&#x00EE;&#x00F2;&#x00D3;&#x00F4;&#x00F9;&#x00FA;&#x00FB;#&#x010A;&#x010B;&#x0120;&#x0121;&#x0126;&#x0127;&#x017B;&#x017C;&#x02BC;
+#BosnianCyr &#x0402;&#x0408;&#x0409;&#x040A;&#x040B;&#x040F;&#x0410;&#x0411;&#x0412;&#x0413;&#x0414;&#x0415;&#x0416;&#x0417;&#x0418;&#x041A;&#x041B;&#x041C;&#x041D;&#x041E;&#x041F;&#x0420;&#x0421;&#x0422;&#x0423;&#x0424;&#x0425;&#x0426;&#x0427;&#x0428;&#x0430;&#x0431;&#x0432;&#x0433;&#x0434;&#x0435;&#x0436;&#x0437;&#x0438;&#x043A;&#x043B;&#x043C;&#x043D;&#x043E;&#x043F;&#x0440;&#x0441;&#x0442;&#x0443;&#x0444;&#x0445;&#x0446;&#x0447;&#x0448;&#x0452;&#x0458;&#x0459;&#x045A;&#x045B;&#x045F;
+#Scots &#x0041;
+#Scots1 &#x01B7;&#x021C;&#x021D;&#x0292;
+###########################################
+#### Hiragana 3040-309F
+Hiragana &#x3041;&#x3042;&#x3043;&#x3044;&#x3045;&#x3046;&#x3047;&#x3048;&#x3049;&#x304A;&#x304B;&#x304C;&#x304D;&#x304E;&#x304F;&#x3050;&#x3051;&#x3052;&#x3053;&#x3054;&#x3055;&#x3056;&#x3057;&#x3058;&#x3059;&#x305A;&#x305B;&#x305C;&#x305D;&#x305E;&#x305F;&#x3060;&#x3061;&#x3062;&#x3063;&#x3064;&#x3065;&#x3066;&#x3067;&#x3068;&#x3069;&#x306A;&#x306B;&#x306C;&#x306D;&#x306E;&#x306F;&#x3070;&#x3071;&#x3072;&#x3073;&#x3074;&#x3075;&#x3076;&#x3077;&#x3078;&#x3079;&#x307A;&#x307B;&#x307C;&#x307D;&#x307E;&#x307F;&#x3080;&#x3081;&#x3082;&#x3083;&#x3084;&#x3085;&#x3086;&#x3087;&#x3088;&#x3089;&#x308A;&#x308B;&#x308C;&#x308D;&#x308E;&#x308F;&#x3090;&#x3091;&#x3092;&#x3093;
+Hiragana1 &#x3094;&#x3099;&#x309A;
+Hiragana2 &#x309B;&#x309C;&#x309D;&#x309E;
+#### Katakana 30A0-30FF
+Katakana &#x30A1;&#x30A2;&#x30A3;&#x30A4;&#x30A5;&#x30A6;&#x30A7;&#x30A8;&#x30A9;&#x30AA;&#x30AB;&#x30AC;&#x30AD;&#x30AE;&#x30AF;&#x30B0;&#x30B1;&#x30B2;&#x30B3;&#x30B4;&#x30B5;&#x30B6;&#x30B7;&#x30B8;&#x30B9;&#x30BA;&#x30BB;&#x30BC;&#x30BD;&#x30BE;&#x30BF;&#x30C0;&#x30C1;&#x30C2;&#x30C3;&#x30C4;&#x30C5;&#x30C6;&#x30C7;&#x30C8;&#x30C9;&#x30CA;&#x30CB;&#x30CC;&#x30CD;&#x30CE;&#x30CF;&#x30D0;&#x30D1;&#x30D2;&#x30D3;&#x30D4;&#x30D5;&#x30D6;&#x30D7;&#x30D8;&#x30D9;&#x30DA;&#x30DB;&#x30DC;&#x30DD;&#x30DE;&#x30DF;&#x30E0;&#x30E1;&#x30E2;&#x30E3;&#x30E4;&#x30E5;&#x30E6;&#x30E7;&#x30E8;&#x30E9;&#x30EA;&#x30EB;&#x30EC;&#x30ED;&#x30EE;&#x30EF;&#x30F0;&#x30F1;&#x30F2;&#x30F3;&#x30F4;&#x30F5;&#x30F6;
+Katakana1 &#x30F7;&#x30F8;&#x30F9;&#x30FA;
+Katakana2 &#x30FB;&#x30FC;&#x30FD;&#x30FE;
+############################################
+Albanian &#x00C2;&#x00C7;&#x00CB;&#x00E2;&#x00E7;&#x00EB;
+Bosnian &#x0106;&#x0107;&#x010C;&#x010D;&#x0110;&#x0111;&#x0160;&#x0161;&#x017D;&#x017E;
+Breton &#x00C2;&#x00CA;&#x00D1;&#x00D9;&#x00DC;&#x00E2;&#x00EA;&#x00F1;&#x00F9;&#x00FC;
+Catalan &#x00C0;&#x00C7;&#x00C8;&#x00C9;&#x00CD;&#x00CF;&#x00D2;&#x00D3;&#x00DA;&#x00DC;&#x00E0;&#x00E7;&#x00E8;&#x00E9;&#x00ED;&#x00EF;&#x00F2;&#x00F3;&#x00FA;&#x00FC;
+#Catalan1 &#x00B7;&#x013F;&#x0140;
+Croatian &#x0106;&#x0107;&#x010C;&#x010D;&#x0110;&#x0111;&#x0160;&#x0161;&#x017D;&#x017E;
+CroatianLig &#x01F1;&#x01F2;&#x01F3;&#x01C4;&#x01C5;&#x01C6;&#x01C7;&#x01C8;&#x01C9;&#x01CA;&#x01CB;&#x01CC;
+Czech &#x00C1;&#x00C9;&#x00CD;&#x00D3;&#x00DA;&#x00DD;&#x00E1;&#x00E9;&#x00ED;&#x00F3;&#x00FA;&#x00FD;&#x010C;&#x010D;&#x010E;&#x010F;&#x011A;&#x011B;&#x0147;&#x0148;&#x0158;&#x0159;&#x0160;&#x0161;&#x0164;&#x0165;&#x016E;&#x016F;&#x017D;&#x017E;
+Danish &#x00C1;&#x00C4;&#x00C5;&#x00C6;&#x00C9;&#x00D3;&#x00D6;&#x00D8;&#x00DC;&#x00E1;&#x00E4;&#x00E5;&#x00E6;&#x00E9;&#x00F3;&#x00F6;&#x00F8;&#x00FC;
+Dutch &#x00C0;&#x00C2;&#x00C4;&#x00C6;&#x00C7;&#x00C8;&#x00C9;&#x00CA;&#x00CB;&#x00CE;&#x00CF;&#x00D1;&#x00D2;&#x00D3;&#x00D4;&#x00D6;&#x00D9;&#x00DA;&#x00DB;&#x00DC;&#x00E0;&#x00E2;&#x00E4;&#x00E6;&#x00E7;&#x00E8;&#x00E9;&#x00EA;&#x00EB;&#x00EE;&#x00EF;&#x00F1;&#x00F2;&#x00F3;&#x00F4;&#x00F6;&#x00F9;&#x00FA;&#x00FB;&#x00FC;
+Esperanto &#x0108;&#x0109;&#x011C;&#x011D;&#x0124;&#x0125;&#x0134;&#x0135;&#x015C;&#x015D;&#x016C;&#x016D;
+Estonian &#x00C4;&#x00D5;&#x00D6;&#x00DC;&#x00E4;&#x00F5;&#x00F6;&#x00FC;&#x0160;&#x0161;&#x017D;&#x017E;
+Faroese &#x00C5;&#x00C6;&#x00D0;&#x00D3;&#x00D6;&#x00D8;&#x00DA;&#x00DD;&#x00E5;&#x00E6;&#x00F0;&#x00F3;&#x00F6;&#x00F8;&#x00FA;&#x00FD;
+Finnish &#x00C4;&#x00C5;&#x00D6;&#x00DC;&#x00E4;&#x00E5;&#x00F6;&#x00FC;
+#Finnish1 &#x0160;&#x0161;&#x017D;&#x017E;
+French(limited) &#x00C0;&#x00C2;&#x00C6;&#x00C7;&#x00C8;&#x00C9;&#x00CA;&#x00CB;&#x00CE;&#x00CF;&#x00D1;&#x00D4;&#x00D9;&#x00DB;&#x00E0;&#x00E2;&#x00E6;&#x00E7;&#x00E8;&#x00E9;&#x00EA;&#x00EB;&#x00EE;&#x00EF;&#x00F1;&#x00F4;&#x00F9;&#x00FB;&#x00FF;
+French &#x0152;&#x0153;&#x0178;
+German &#x00C4;&#x00D6;&#x00DC;&#x00DF;&#x00E4;&#x00F6;&#x00FC;
+Hungarian &#x00C1;&#x00C9;&#x00CD;&#x00D3;&#x00D6;&#x00DA;&#x00DC;&#x00E1;&#x00E9;&#x00ED;&#x00F3;&#x00F6;&#x00FA;&#x00FC;&#x0150;&#x0151;&#x0170;&#x0171;
+Icelandic &#x00C1;&#x00C6;&#x00C9;&#x00CD;&#x00D0;&#x00D3;&#x00D6;&#x00DA;&#x00DD;&#x00DE;&#x00E1;&#x00E6;&#x00E9;&#x00ED;&#x00F0;&#x00F3;&#x00F6;&#x00FA;&#x00FD;&#x00FE;
+Italian &#x00C0;&#x00C8;&#x00C9;&#x00CC;&#x00CD;&#x00CF;&#x00D2;&#x00D3;&#x00D9;&#x00DA;&#x00E0;&#x00E8;&#x00E9;&#x00EC;&#x00ED;&#x00EF;&#x00F2;&#x00F3;&#x00F9;&#x00FA;
+#Latin &#x0041;
+Latvian &#x0100;&#x0101;&#x010C;&#x010D;&#x0112;&#x0113;&#x0122;&#x0123;&#x012A;&#x012B;&#x0136;&#x0137;&#x013B;&#x013C;&#x0145;&#x0146;&#x0160;&#x0161;&#x016A;&#x016B;&#x017D;&#x017E;
+Lithuanian &#x0104;&#x0105;&#x010C;&#x010D;&#x0116;&#x0117;&#x0118;&#x0119;&#x012E;&#x012F;&#x0160;&#x0161;&#x016A;&#x016B;&#x0172;&#x0173;&#x017D;&#x017E;
+Norwegian &#x00C5;&#x00C6;&#x00D8;&#x00E5;&#x00E6;&#x00F8;
+Polish &#x00D3;&#x00F3;&#x0104;&#x0105;&#x0106;&#x0107;&#x0118;&#x0119;&#x0141;&#x0142;&#x0143;&#x0144;&#x015A;&#x015B;&#x0179;&#x017A;&#x017B;&#x017C;
+Portuguese &#x00C0;&#x00C1;&#x00C2;&#x00C3;&#x00C7;&#x00C9;&#x00CA;&#x00CD;&#x00D3;&#x00D4;&#x00D5;&#x00DA;&#x00DC;&#x00E0;&#x00E1;&#x00E2;&#x00E3;&#x00E7;&#x00E9;&#x00EA;&#x00ED;&#x00F3;&#x00F4;&#x00F5;&#x00FA;&#x00FC;
+#http://en.wikipedia.org/wiki/Special_Romanian_Unicode_characters
+Romanian &#x00C2;&#x00CE;&#x00E2;&#x00EE;&#x0102;&#x0103;&#x0218;&#x0219;&#x021A;&#x021B;
+Romanian(ErrorST) &#x00C2;&#x00CE;&#x00E2;&#x00EE;&#x0102;&#x0103;&#x015E;&#x015F;&#x0162;&#x0163;
+Slovak &#x00C1;&#x00C4;&#x00C9;&#x00CD;&#x00D3;&#x00D4;&#x00DA;&#x00DD;&#x00E1;&#x00E4;&#x00E9;&#x00ED;&#x00F3;&#x00F4;&#x00FA;&#x00FD;&#x010C;&#x010D;&#x010E;&#x010F;&#x0139;&#x013A;&#x013D;&#x013E;&#x0147;&#x0148;&#x0154;&#x0155;&#x0160;&#x0161;&#x0164;&#x0165;&#x017D;&#x017E;
+Slovene &#x010C;&#x010D;&#x0160;&#x0161;&#x017D;&#x017E
+Sorbian-Lower &#x0106;&#x0107;&#x010C;&#x010D;&#x011A;&#x011B;&#x0141;&#x0142;&#x0143;&#x0144;&#x0158;&#x0159;&#x015A;&#x015B;&#x0160;&#x0161;&#x0179;&#x017A;&#x017D;&#x017E;
+Sorbian-Upper &#x00D3;&#x00F3;&#x0106;&#x0107;&#x010C;&#x010D;&#x011A;&#x011B;&#x0141;&#x0142;&#x0143;&#x0144;&#x0158;&#x0159;&#x0160;&#x0161;&#x017D;&#x017E;
+Spanish &#x00C1;&#x00C9;&#x00CD;&#x00D1;&#x00D3;&#x00DA;&#x00DC;&#x00E1;&#x00E9;&#x00ED;&#x00F1;&#x00F3;&#x00FA;&#x00FC;
+Swedish &#x00C4;&#x00C5;&#x00D6;&#x00E4;&#x00E5;&#x00F6;
+Turkish &#x00C2;&#x00C7;&#x00D6;&#x00DB;&#x00DC;&#x00E2;&#x00E7;&#x00F6;&#x00FB;&#x00FC;&#x011E;&#x011F;&#x0130;&#x0131;
+Welsh &#x00C0;&#x00C1;&#x00C2;&#x00C4;&#x00C8;&#x00C9;&#x00CA;&#x00CB;&#x00CC;&#x00CD;&#x00CE;&#x00CF;&#x00D2;&#x00D3;&#x00D4;&#x00D6;&#x00D9;&#x00DA;&#x00DB;&#x00DC;&#x00DD;&#x00E0;&#x00E1;&#x00E2;&#x00E4;&#x00E8;&#x00E9;&#x00EA;&#x00EB;&#x00EC;&#x00ED;&#x00EE;&#x00EF;&#x00F2;&#x00F3;&#x00F4;&#x00F6;&#x00F9;&#x00FA;&#x00FB;&#x00FC;&#x00FD;&#x00FF;&#x0174;&#x0175;&#x0176;&#x0177;&#x0178;&#x1E80;&#x1E81;&#x1E82;&#x1E83;&#x1E84;&#x1E85;&#x1EF2;&#x1EF3;
+##################################
+Belarusian &#x0401;&#x0406;&#x040E;&#x0410;&#x0411;&#x0412;&#x0413;&#x0414;&#x0415;&#x0416;&#x0417;&#x0418;&#x0419;&#x041A;&#x041B;&#x041C;&#x041D;&#x041E;&#x041F;&#x0420;&#x0421;&#x0422;&#x0423;&#x0424;&#x0425;&#x0426;&#x0427;&#x0428;&#x0429;&#x042A;&#x042B;&#x042C;&#x042D;&#x042E;&#x042F;&#x0430;&#x0431;&#x0432;&#x0433;&#x0434;&#x0435;&#x0436;&#x0437;&#x0438;&#x0439;&#x043A;&#x043B;&#x043C;&#x043D;&#x043E;&#x043F;&#x0440;&#x0441;&#x0442;&#x0443;&#x0444;&#x0445;&#x0446;&#x0447;&#x0448;&#x0449;&#x044A;&#x044B;&#x044C;&#x044D;&#x044E;&#x044F;&#x0451;&#x0456;&#x045E;
+Bulgarian &#x0410;&#x0411;&#x0412;&#x0413;&#x0414;&#x0415;&#x0416;&#x0417;&#x0418;&#x0419;&#x041A;&#x041B;&#x041C;&#x041D;&#x041E;&#x041F;&#x0420;&#x0421;&#x0422;&#x0423;&#x0424;&#x0425;&#x0426;&#x0427;&#x0428;&#x0429;&#x042A;&#x042C;&#x042E;&#x042F;&#x0430;&#x0431;&#x0432;&#x0433;&#x0434;&#x0435;&#x0436;&#x0437;&#x0438;&#x0439;&#x043A;&#x043B;&#x043C;&#x043D;&#x043E;&#x043F;&#x0440;&#x0441;&#x0442;&#x0443;&#x0444;&#x0445;&#x0446;&#x0447;&#x0448;&#x0449;&#x044A;&#x044C;&#x044E;&#x044F;
+Bulgarian1 &#x0400;&#x04AD;&#x0450;&#x045D;&#x0462;&#x0463;&#x046A;&#x046B;
+Macedonian &#x0403;&#x0405;&#x0408;&#x0409;&#x040A;&#x040C;&#x040F;&#x0410;&#x0411;&#x0412;&#x0413;&#x0414;&#x0415;&#x0416;&#x0417;&#x0418;&#x041A;&#x041B;&#x041C;&#x041D;&#x041E;&#x041F;&#x0420;&#x0421;&#x0422;&#x0423;&#x0424;&#x0425;&#x0426;&#x0427;&#x0428;&#x0430;&#x0431;&#x0432;&#x0433;&#x0434;&#x0435;&#x0436;&#x0437;&#x0438;&#x043A;&#x043B;&#x043C;&#x043D;&#x043E;&#x043F;&#x0440;&#x0441;&#x0442;&#x0443;&#x0444;&#x0445;&#x0446;&#x0447;&#x0448;&#x0453;&#x0455;&#x0458;&#x0459;&#x045A;&#x045C;&#x045F;
+Russian &#x0401;&#x0410;&#x0411;&#x0412;&#x0413;&#x0414;&#x0415;&#x0416;&#x0417;&#x0418;&#x0419;&#x041A;&#x041B;&#x041C;&#x041D;&#x041E;&#x041F;&#x0420;&#x0421;&#x0422;&#x0423;&#x0424;&#x0425;&#x0426;&#x0427;&#x0428;&#x0429;&#x042A;&#x042B;&#x042C;&#x042D;&#x042E;&#x042F;&#x0430;&#x0431;&#x0432;&#x0433;&#x0434;&#x0435;&#x0436;&#x0437;&#x0438;&#x0439;&#x043A;&#x043B;&#x043C;&#x043D;&#x043E;&#x043F;&#x0440;&#x0441;&#x0442;&#x0443;&#x0444;&#x0445;&#x0446;&#x0447;&#x0448;&#x0449;&#x044A;&#x044B;&#x044C;&#x044D;&#x044E;&#x044F;&#x0451;
+RussianOLD &#x0406;&#x0456;&#x0462;&#x0463;&#x0472;&#x0473;&#x0474;&#x0475;
+Serbian &#x0402;&#x0408;&#x0409;&#x040A;&#x040B;&#x040F;&#x0410;&#x0411;&#x0412;&#x0413;&#x0414;&#x0415;&#x0416;&#x0417;&#x0418;&#x041A;&#x041B;&#x041C;&#x041D;&#x041E;&#x041F;&#x0420;&#x0421;&#x0422;&#x0423;&#x0424;&#x0425;&#x0426;&#x0427;&#x0428;&#x0430;&#x0431;&#x0432;&#x0433;&#x0434;&#x0435;&#x0436;&#x0437;&#x0438;&#x043A;&#x043B;&#x043C;&#x043D;&#x043E;&#x043F;&#x0440;&#x0441;&#x0442;&#x0443;&#x0444;&#x0445;&#x0446;&#x0447;&#x0448;&#x0452;&#x0458;&#x0459;&#x045A;&#x045B;&#x045F;
+Ukrainian &#x0404;&#x0406;&#x0407;&#x0410;&#x0411;&#x0412;&#x0413;&#x0414;&#x0415;&#x0416;&#x0417;&#x0418;&#x0419;&#x041A;&#x041B;&#x041C;&#x041D;&#x041E;&#x041F;&#x0420;&#x0421;&#x0422;&#x0423;&#x0424;&#x0425;&#x0426;&#x0427;&#x0428;&#x0429;&#x042C;&#x042D;&#x042E;&#x042F;&#x0430;&#x0431;&#x0432;&#x0433;&#x0434;&#x0435;&#x0436;&#x0437;&#x0438;&#x0439;&#x043A;&#x043B;&#x043C;&#x043D;&#x043E;&#x043F;&#x0440;&#x0441;&#x0442;&#x0443;&#x0444;&#x0445;&#x0446;&#x0447;&#x0448;&#x0449;&#x044C;&#x044D;&#x044E;&#x044F;&#x0454;&#x0456;&#x0457;&#x0490;&#x0491;
+##################################
+Armenian &#x0531;&#x0532;&#x0533;&#x0534;&#x0535;&#x0536;&#x0537;&#x0538;&#x0539;&#x053A;&#x053B;&#x053C;&#x053D;&#x053E;&#x053F;&#x0541;&#x0542;&#x0543;&#x0544;&#x0545;&#x0546;&#x0547;&#x0548;&#x0549;&#x054A;&#x054B;&#x054C;&#x054D;&#x054E;&#x054F;&#x0551;&#x0552;&#x0553;&#x0554;&#x0555;&#x0556;&#x055B;&#x055C;&#x055D;&#x055E;&#x055F;&#x0561;&#x0562;&#x0563;&#x0564;&#x0565;&#x0586;&#x0589;
+#Armenian1 &#x055A;&#x058A;
+#Characters not found in the UCS:
+# ARMENIAN ETERNITY SIGN
+#
+GeorgianOld &#x10A0;&#x10A1;&#x10A2;&#x10A3;&#x10A4;&#x10A5;&#x10A6;&#x10A7;&#x10A8;&#x10A9;&#x10AA;&#x10AB;&#x10AC;&#x10AD;&#x10AE;&#x10AF;&#x10B0;&#x10B1;&#x10B2;&#x10B3;&#x10B4;&#x10B5;&#x10B6;&#x10B7;&#x10B8;&#x10B9;&#x10BA;&#x10BB;&#x10BC;&#x10BD;&#x10BE;&#x10BF;&#x10C0;&#x10C1;&#x10C2;&#x10C3;&#x10C4;&#x10C5;
+Georgian &#x10D0;&#x10D1;&#x10D2;&#x10D3;&#x10D4;&#x10D5;&#x10D6;&#x10D7;&#x10D8;&#x10D9;&#x10DA;&#x10DB;&#x10DC;&#x10DD;&#x10DE;&#x10DF;&#x10E0;&#x10E1;&#x10E2;&#x10E3;&#x10E4;&#x10E5;&#x10E6;&#x10E7;&#x10E8;&#x10E9;&#x10EA;&#x10EB;&#x10EC;&#x10ED;&#x10EE;&#x10EF;&#x10F0;
+GeorgianArc &#x10F1;&#x10F2;&#x10F3;&#x10F4;&#x10F5;&#x10F6;
+GeorgianPunc &#x10FB;
+#
+GreekExt1 &#x0384;&#x0385;&#x0386;&#x00B7;&#x0388;&#x0389;&#x038A;&#x00BB;&#x038C;&#x00BD;&#x038E;&#x038F;&#x0390;
+Greek &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;&#x0397;&#x0398;&#x0399;&#x039A;&#x039B;&#x039C;&#x039D;&#x039E;&#x039F;&#x03A0;&#x03A1;&#x03A3;&#x03A4;&#x03A5;&#x03A6;&#x03A7;&#x03A8;&#x03A9;&#x03B1;&#x03B2;&#x03B3;&#x03B4;&#x03B5;&#x03B6;&#x03B7;&#x03B8;&#x03B9;&#x03BA;&#x03BB;&#x03BC;&#x03BD;&#x03BE;&#x03BF;&#x03C0;&#x03C1;&#x03C3;&#x03C4;&#x03C5;&#x03C6;&#x03C7;&#x03C8;&#x03C9;
+GreekExt2 &#x03AA;&#x03AB;&#x03AC;&#x03AD;&#x03AE;&#x03AF;&#x03B0;&#x03CA;&#x03CB;&#x03CC;&#x03CD;&#x03CE;
+GreekExt4 &#x03C2;
+#
+Hebrew &#x05D0;&#x05D1;&#x05D2;&#x05D3;&#x05D4;&#x05D5;&#x05D6;&#x05D7;&#x05D8;&#x05D9;&#x05DA;&#x05DB;&#x05DC;&#x05DD;&#x05DE;&#x05DF;&#x05E0;&#x05E1;&#x05E2;&#x05E3;&#x05E4;&#x05E5;&#x05E6;&#x05E7;&#x05E8;&#x05E9;&#x05EA;
+##################################
+#Abaza
+#Abkhaz
+#Adyghe
+#Agul *
+#(Aisor)
+#Akhvakh *
+#(?lvdalska)
+#(Andi) *
+#(Aragonese)
+#Archi *
+#Arumanian
+#(Arvanite)
+#Asturian
+#Avar
+#Azerbaijani
+#(Bagulal) *
+#Balkar
+#Bashkir
+#Basque !
+#Bats *
+#Bezhta *
+#(Botlikh) *
+#Budukh *
+#(Chamalal)
+#Chechen
+#Chuvash
+#Cornish !
+#(Corsican)
+#Dargwa
+#Erzya
+#(Franco-Proven?al)
+#(Frisian, East)
+#(Frisian, North)
+#Frisian, West
+#Friulian
+#Gagauz
+#Gaelic, Irish !
+#Gaelic, Manx !
+#Gaelic, Scottish !
+#Galician !
+#(German, Low) !
+#(German, Swiss) !
+#Godoberi *
+#(Hinukh) *
+#(Hunzib) *
+#Ingrian
+#Ingush
+#Istro-Romanian
+#(Judeo-Georgian)
+#(Judeo-Kurdish)
+#(Judeo-Tati)
+#Kabardian
+#Kalmyk
+#Karachay
+#(Karaim)
+#(Karata) *
+#Karelian
+#Kashubian
+#Kazakh
+#Khinalug
+#(Khvarshi) *
+#(Kirmanji)
+#Komi
+#Komi-Permyak
+#(Kryts)
+#Kumyk
+#(Kurdish)
+#(Ladin)
+#(Ladino)
+#Lak
+#Laz
+#Lezgian
+#Livonian
+#(Ludian)
+#Luxemburgish !
+#Mari, Hill
+#Mari, Meadow
+#Megleno-Romanian
+#(Mingrelian)
+#Moksha
+#Moldavian
+#Nenets, Tundra
+#Nogai
+#Occitan
+#Old Church Slavonic
+#(Olonets)
+#Ossetian
+#(Romani)
+#Romansch
+#(Rusyn)
+#Rutul
+#Sami, Inari
+#Sami, Kildin
+#Sami, Lule
+#Sami, Northern
+#Sami, Skolt
+#Sami, Southern
+#(Sami, Ter) *
+#(Sami, Ume) *
+#(Sardinian) *
+#Scots !
+#Svan
+#Tabasaran
+#(Talysh)
+#Tatar, Crimean
+#Tatar, Kazan
+#Tati
+#(Tindi) *
+#(Tsakonian) *
+#Tsakhur *
+#(Tsez) *
+#(Turkish, Crimean)
+#Ubykh *
+#Udi
+#Udmurt
+#(V?mhusm?l)
+#Vepsian
+#Votic
+#(Walloon)
+#(Yiddish)
+################################
+# 4 Gaelic-new-orthography
+# 4 Frisian
+# 3 Rhaeto-Romanic
+# 2 S&AACUTEmi-with-restrictions
+# 1 Rhjaeto-Romanic
+# 1 Gaelic-old-and-new-orthographies
+END
+) |
+
+while read a b
+do
+ c=`echo $b | replace "&#x" "" ";" ""`
+ printf "INSERT INTO lang VALUES ('$a',_ucs2 X'$c');\n"
+done
+) | mysql -f test
+
+#mysql test << END
+#SELECT * FROM lang WHERE CONVERT(letters USING latin1) NOT LIKE _binary'%?%';
+#SELECT * FROM lang WHERE CONVERT(letters USING latin2) NOT LIKE _binary'%?%';
+#END
+
+
+
+list="big5 dec8 cp850 hp8 koi8r latin1 latin2 swe7 ascii ujis sjis hebrew euckr koi8u gb2312 greek cp1250 gbk latin5 armscii8 cp866 keybcs2 macce macroman cp852 latin7 cp1251 cp1256 cp1257 geostd8"
+
+for p in $list
+do
+echo "-----------------"
+echo $p:
+mysql -sss test << END
+SELECT lang FROM lang WHERE CONVERT(letters USING $p) NOT LIKE _binary'%?%' ORDER BY lang;
+END
+done
+
diff --git a/sql/share/charsets/latin1.xml b/sql/share/charsets/latin1.xml
new file mode 100644
index 00000000000..68307847d91
--- /dev/null
+++ b/sql/share/charsets/latin1.xml
@@ -0,0 +1,253 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2005 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="latin1">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 10 00 10 02 10 10 10 10 10 10 01 10 01 00 01 00
+ 00 10 10 10 10 10 10 10 10 10 02 10 02 00 02 01
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 10 01 01 01 01 01 01 01 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 10 02 02 02 02 02 02 02 02
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 D7 F8 F9 FA FB FC FD FE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC DD DE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 20AC 0081 201A 0192 201E 2026 2020 2021 02C6 2030 0160 2039 0152 008D 017D 008F
+ 0090 2018 2019 201C 201D 2022 2013 2014 02DC 2122 0161 203A 0153 009D 017E 0178
+ 00A0 00A1 00A2 00A3 00A4 00A5 00A6 00A7 00A8 00A9 00AA 00AB 00AC 00AD 00AE 00AF
+ 00B0 00B1 00B2 00B3 00B4 00B5 00B6 00B7 00B8 00B9 00BA 00BB 00BC 00BD 00BE 00BF
+ 00C0 00C1 00C2 00C3 00C4 00C5 00C6 00C7 00C8 00C9 00CA 00CB 00CC 00CD 00CE 00CF
+ 00D0 00D1 00D2 00D3 00D4 00D5 00D6 00D7 00D8 00D9 00DA 00DB 00DC 00DD 00DE 00DF
+ 00E0 00E1 00E2 00E3 00E4 00E5 00E6 00E7 00E8 00E9 00EA 00EB 00EC 00ED 00EE 00EF
+ 00F0 00F1 00F2 00F3 00F4 00F5 00F6 00F7 00F8 00F9 00FA 00FB 00FC 00FD 00FE 00FF
+</map>
+</unicode>
+
+
+<collation name="latin1_swedish_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ 41 41 41 41 5C 5B 5C 43 45 45 45 45 49 49 49 49
+ 44 4E 4F 4F 4F 4F 5D D7 D8 55 55 55 59 59 DE DF
+ 41 41 41 41 5C 5B 5C 43 45 45 45 45 49 49 49 49
+ 44 4E 4F 4F 4F 4F 5D F7 D8 55 55 55 59 59 DE FF
+</map>
+</collation>
+
+
+<collation name="latin1_german1_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ 41 41 41 41 41 41 41 43 45 45 45 45 49 49 49 49
+ D0 4E 4F 4F 4F 4F 4F D7 4F 55 55 55 55 59 DE 53
+ 41 41 41 41 41 41 41 43 45 45 45 45 49 49 49 49
+ D0 4E 4F 4F 4F 4F 4F F7 4F 55 55 55 55 59 DE FF
+</map>
+</collation>
+
+
+<collation name="latin1_danish_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ 41 41 41 41 5B 5D 5B 43 45 45 45 45 49 49 49 49
+ 44 4E 4F 4F 4F 4F 5C D7 5C 55 55 55 59 59 DE DF
+ 41 41 41 41 5B 5D 5B 43 45 45 45 45 49 49 49 49
+ 44 4E 4F 4F 4F 4F 5C F7 5C 55 55 55 59 59 DE FF
+</map>
+</collation>
+
+
+<collation name="latin1_german2_ci"/>
+
+
+<collation name="latin1_bin" flag="binary"/>
+
+
+<collation name="latin1_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 51 53 57 5B 65 67 69 6B 75 77 79 7B 7D 81
+ 8F 91 93 95 98 9A A4 A6 A8 AA AF B3 B4 B5 B6 B7
+ B8 41 51 53 57 5B 65 67 69 6B 75 77 79 7B 7D 81
+ 8F 91 93 95 98 9A A4 A6 A8 AA AF B9 BA BB BC BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+ 43 45 47 49 4B 4D 4F 55 5D 5F 61 63 6D 6F 71 73
+ 59 7F 83 85 87 89 8B BD 8D 9C 9E A0 A2 AC B1 97
+ 43 45 47 49 4B 4D 4F 55 5D 5F 61 63 6D 6F 71 73
+ 59 7F 83 85 87 89 8B BE 8D 9C 9E A0 A2 AC B1 AE
+</map>
+</collation>
+
+
+<collation name="latin1_general_cs">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 51 53 57 5B 65 67 69 6B 75 77 79 7B 7D 81
+ 8F 91 93 95 98 9A A4 A6 A8 AA AF B3 B4 B5 B6 B7
+ B8 42 52 54 58 5C 66 68 6A 6C 76 78 7A 7C 7E 82
+ 90 92 94 96 99 9B A5 A7 A9 AB B0 B9 BA BB BC BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+ 43 45 47 49 4B 4D 4F 55 5D 5F 61 63 6D 6F 71 73
+ 59 7F 83 85 87 89 8B BD 8D 9C 9E A0 A2 AC B1 97
+ 44 46 48 4A 4C 4E 50 56 5E 60 62 64 6E 70 72 74
+ 5A 80 84 86 88 8A 8C BE 8E 9D 9F A1 A3 AD B2 AE
+</map>
+</collation>
+
+
+<collation name="latin1_spanish_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 51 53 57 5B 65 67 69 6B 75 77 79 7B 7D 81
+ 8F 91 93 95 98 9A A4 A6 A8 AA AF B3 B4 B5 B6 B7
+ B8 41 51 53 57 5B 65 67 69 6B 75 77 79 7B 7D 81
+ 8F 91 93 95 98 9A A4 A6 A8 AA AF B9 BA BB BC BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+ 41 41 41 41 41 41 41 53 5B 5B 5B 5B 6B 6B 6B 6B
+ 57 7F 81 81 81 81 81 BD 81 9A 9A 9A 9A AA B1 97
+ 41 41 41 41 41 41 41 53 5B 5B 5B 5B 6B 6B 6B 6B
+ 57 7F 81 81 81 81 81 BE 81 9A 9A 9A 9A AA B1 AA
+</map>
+</collation>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/latin2.xml b/sql/share/charsets/latin2.xml
new file mode 100644
index 00000000000..29ff4cb974b
--- /dev/null
+++ b/sql/share/charsets/latin2.xml
@@ -0,0 +1,186 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2005 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="latin2">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 48 01 10 01 10 01 01 10 10 01 01 01 01 10 01 01
+ 10 02 10 02 10 02 02 10 10 02 02 02 02 10 02 02
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 10 01 01 01 01 01 01 10 01 01 01 01 01 01 01 10
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 10 02 02 02 02 02 02 02 10
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 B1 A2 B3 A4 B5 B6 A7 A8 B9 BA BB BC AD BE BF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 D7 F8 F9 FA FB FC FD FE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 A1 B2 A3 B4 A5 A6 B7 B8 A9 AA AB AC BD AE AF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC DD DE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 0104 02D8 0141 00A4 013D 015A 00A7 00A8 0160 015E 0164 0179 00AD 017D 017B
+00B0 0105 02DB 0142 00B4 013E 015B 02C7 00B8 0161 015F 0165 017A 02DD 017E 017C
+0154 00C1 00C2 0102 00C4 0139 0106 00C7 010C 00C9 0118 00CB 011A 00CD 00CE 010E
+0110 0143 0147 00D3 00D4 0150 00D6 00D7 0158 016E 00DA 0170 00DC 00DD 0162 00DF
+0155 00E1 00E2 0103 00E4 013A 0107 00E7 010D 00E9 0119 00EB 011B 00ED 00EE 010F
+0111 0144 0148 00F3 00F4 0151 00F6 00F7 0159 016F 00FA 0171 00FC 00FD 0163 02D9
+</map>
+</unicode>
+
+
+<collation name="latin2_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 44 45 48 49 4B 4C 4D 4E 4F 50 51 53 54 56
+ 58 59 5A 5B 5E 5F 60 61 62 63 64 68 69 6A 6B 6C
+ 6D 41 44 45 48 49 4B 4C 4D 4E 4F 50 51 53 54 56
+ 58 59 5A 5B 5E 5F 60 61 62 63 64 6E 6F 70 71 FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
+ FF 42 FF 52 FF 51 5C FF FF 5D 5B 5E 65 FF 67 66
+ FF 42 FF 52 FF 51 5C FF FF 5D 5B 5E 65 FF 67 66
+ 5A 43 43 43 43 51 46 45 47 49 4A 49 49 4E 4E 48
+ FF 55 54 57 56 56 56 FF 5A 5F 5F 5F 5F 63 5E FF
+ 5A 43 43 43 43 51 46 45 47 49 4A 49 49 4E 4E 48
+ FF 55 54 57 56 56 56 FF 5A 5F 5F 5F 5F 63 5E FF
+</map>
+</collation>
+
+
+<collation name="latin2_croatian_ci">
+<map>
+00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+40 41 43 44 48 4B 4D 4E 4F 50 52 53 54 56 57 59
+5B 5C 5D 5F 62 64 66 67 68 69 6B C6 C7 C8 C9 CA
+CB 41 43 44 48 4B 4D 4E 4F 50 52 53 54 56 57 59
+5B 5C 5D 5F 62 64 66 67 68 69 6B CC CD CE CF D0
+D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0
+E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0
+F1 41 F2 54 F3 54 5F F4 F5 61 5F 62 6B F6 8E 6B
+F7 41 F8 54 F9 54 5F FA FB 61 5F 62 6B FC 8E 6B
+5D 41 41 41 41 54 47 44 46 4B 4B 4B 4B 50 50 48
+4A 57 57 59 59 59 59 FD 5D 64 64 64 64 69 62 5F
+5D 41 41 41 41 54 47 44 46 4B 4B 4B 4B 50 50 48
+4A 57 57 59 59 59 59 FE 5D 64 64 64 64 69 62 FF
+</map>
+</collation>
+
+
+<collation name="latin2_czech_ci"/>
+
+
+<collation name="latin2_hungarian_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 47 48 4C 4E 53 54 55 56 5A 5B 5C 60 61 64
+ 69 6A 6B 6E 72 75 7A 7B 7C 7D 7F 83 84 85 86 87
+ 88 41 47 48 4C 4E 53 54 55 56 5A 5B 5C 60 61 64
+ 69 6A 6B 6E 72 75 7A 7B 7C 7D 7F 89 8A 8B 8C 00
+ 01 78 4E 04 05 06 07 08 09 0A 67 67 56 56 0F 41
+ 4E 12 13 67 67 64 78 75 78 67 78 1C 1D 1E 1F FF
+ 41 56 64 75 5E 6F FF 67 FF 70 71 73 80 FF 81 82
+ FF 42 FF 5D FF 41 6F FF FF 70 71 73 80 FF 81 82
+ 6C 41 44 45 46 5F 49 4B 4A 4E 51 52 50 56 57 4D
+ FF 62 63 64 66 67 67 FF 6D 77 75 78 78 7E 74 FF
+ 64 41 44 45 46 5F 49 4B 4A 4E 51 78 50 56 58 4D
+ FF 62 63 64 66 67 67 FF 6D 77 75 78 78 7E 74 FF
+</map>
+</collation>
+
+<collation name="latin2_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/latin5.xml b/sql/share/charsets/latin5.xml
new file mode 100644
index 00000000000..ca7dd106de5
--- /dev/null
+++ b/sql/share/charsets/latin5.xml
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (c) 2003, 2005 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="latin5">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 10 01 01 01 01 01 01 01 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 10 02 02 02 02 02 02 02 02
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 FD 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 D7 F8 F9 FA FB FC 69 FE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 DD 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC 49 DE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 00A1 00A2 00A3 00A4 00A5 00A6 00A7 00A8 00A9 00AA 00AB 00AC 00AD 00AE 00AF
+00B0 00B1 00B2 00B3 00B4 00B5 00B6 00B7 00B8 00B9 00BA 00BB 00BC 00BD 00BE 00BF
+00C0 00C1 00C2 00C3 00C4 00C5 00C6 00C7 00C8 00C9 00CA 00CB 00CC 00CD 00CE 00CF
+011E 00D1 00D2 00D3 00D4 00D5 00D6 00D7 00D8 00D9 00DA 00DB 00DC 0130 015E 00DF
+00E0 00E1 00E2 00E3 00E4 00E5 00E6 00E7 00E8 00E9 00EA 00EB 00EC 00ED 00EE 00EF
+011F 00F1 00F2 00F3 00F4 00F5 00F6 00F7 00F8 00F9 00FA 00FB 00FC 0131 015F 00FF
+</map>
+</unicode>
+
+
+<collation name="latin5_turkish_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 45 46 47 48 4A 4B 4D 4E 4F 50 51 52
+ 54 55 56 57 59 5A 5C 5D 5E 5F 60 61 62 63 64 65
+ 66 41 42 43 45 46 47 48 4A 4C 4D 4E 4F 50 51 52
+ 54 55 56 57 59 5A 5C 5D 5E 5F 60 87 88 89 8A 8B
+ 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B
+ 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB
+ AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB
+ BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB
+ 41 41 41 41 41 41 41 44 46 46 46 46 4C 4C 4C 4C
+ 49 51 52 52 52 52 53 E0 52 5A 5A 5A 5B 4C 58 57
+ 41 41 41 41 41 41 41 44 46 46 46 46 4C 4C 4C 4C
+ 49 51 52 52 52 52 53 FA 52 5A 5A 5A 5B 4B 58 5F
+</map>
+</collation>
+
+<collation name="latin5_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/latin7.xml b/sql/share/charsets/latin7.xml
new file mode 100644
index 00000000000..81866c23bbd
--- /dev/null
+++ b/sql/share/charsets/latin7.xml
@@ -0,0 +1,187 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="latin7">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 20
+ 01 20 10 20 10 10 00 00 20 10 20 10 20 10 10 10
+ 20 10 10 10 10 10 10 10 20 00 20 10 20 10 10 20
+ 48 20 10 10 10 20 10 10 10 10 01 10 10 10 10 01
+ 10 10 10 10 10 10 10 10 10 10 02 10 10 10 10 02
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 10 01 01 01 01 01 01 01 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 10 02 02 02 02 02 02 02 10
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 B8 A9 BA AB AC AD AE BF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 D7 F8 F9 FA FB FC FD FE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 A8 B9 AA BB BC BD BE AF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 F7 D8 D9 DA DB DC DD DE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008A 008B 008C 008D 008E 008F
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009A 009B 009C 009D 009E 009F
+00A0 201D 00A2 00A3 00A4 201E 00A6 00A7 00D8 00A9 0156 00AB 00AC 00AD 00AE 00C6
+00B0 00B1 00B2 00B3 201C 00B5 00B6 00B7 00F8 00B9 0157 00BB 00BC 00BD 00BE 00E6
+0104 012E 0100 0106 00C4 00C5 0118 0112 010C 00C9 0179 0116 0122 0136 012A 013B
+0160 0143 0145 00D3 014C 00D5 00D6 00D7 0172 0141 015A 016A 00DC 017B 017D 00DF
+0105 012F 0101 0107 00E4 00E5 0119 0113 010D 00E9 017A 0117 0123 0137 012B 013C
+0161 0144 0146 00F3 014D 00F5 00F6 00F7 0173 0142 015B 016B 00FC 017C 017E 2019
+</map>
+</unicode>
+
+
+<collation name="latin7_estonian_cs">
+<map>
+ 00 02 03 04 05 06 07 08 09 2E 2F 30 31 32 0A 0B
+ 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B
+ 2C 33 34 35 36 37 38 27 39 3A 3B 5D 3C 28 3D 3E
+ 76 7A 7C 7E 80 81 82 83 84 85 3F 40 5E 5F 60 41
+ 42 86 90 92 98 9A A4 A6 AA AC B2 B4 B8 BE C0 C6
+ CE D0 D2 D6 E5 E8 EE F0 FA FC DD 43 44 45 46 47
+ 48 87 91 93 99 9B A5 A7 AB AD B3 B5 B9 BF C1 C7
+ CF D1 D3 D7 E6 E9 EF F1 FB FD DE 49 4A 4B 4C 1C
+ 01 1D 57 1E 5A 74 71 72 1F 75 20 5B 21 4E 52 51
+ 22 55 56 58 59 73 2A 2B 23 E7 24 5C 25 4F 54 26
+ 2D FE 66 67 68 FF 4D 69 CC 6A D4 62 6B 29 6C 8E
+ 6D 61 7D 7F 50 6E 6F 70 CD 7B D5 63 77 78 79 8F
+ 8C B0 88 94 F4 8A A2 A0 96 9C DF 9E A8 B6 AE BA
+ DB C2 C4 C8 CA F2 F6 64 EC BC D8 EA F8 E1 E3 DA
+ 8D B1 89 95 F5 8B A3 A1 97 9D E0 9F A9 B7 AF BB
+ DC C3 C5 C9 CB F3 F7 65 ED BD D9 EB F9 E2 E4 53
+</map>
+</collation>
+
+
+<collation name="latin7_general_cs">
+<!-- Created for case-sensitive record search -->
+<!-- by Andis Grasis & Rihards Grasis e-mail:andis@cata.lv -->
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 30 32 33 34 35 36 37 2B 38 39 3A 5C 3B 2C 3C 3D
+ 76 7A 7C 7E 80 81 82 83 84 85 3E 3F 5D 5E 5F 40
+ 41 86 92 94 9A 9C A6 A8 AC AE B4 B6 BA C0 C2 C8
+ D4 D6 D8 DC E3 E6 EE F0 F2 F4 F6 42 43 44 45 46
+ 47 87 93 95 9B 9D A7 A9 AD AF B5 B7 BB C1 C3 C9
+ D5 D7 D9 DD E4 E7 EF F1 F3 F5 F7 48 49 4A 4B 20
+ 75 21 56 22 59 73 70 71 23 74 24 5A 25 4D 51 50
+ 26 54 55 57 58 72 2E 2F 27 E5 28 5B 29 4E 53 2A
+ 31 FE 65 66 67 FF 4C 68 D3 69 DA 61 6A 2D 6B 90
+ 6C 60 7D 7F 4F 6D 6E 6F D2 7B DB 62 77 78 79 91
+ 8E B2 8A 96 88 8C A4 A2 98 9E F8 A0 AA B8 B0 BE
+ E1 C4 C6 CA CE D0 CC 63 EC BC DE EA E8 FA FC E0
+ 8F B3 8B 97 89 8D A5 A3 99 9F F9 A1 AB B9 B1 BF
+ E2 C5 C7 CB CF D1 CD 64 ED BD DF EB E9 FB FD 52
+</map>
+</collation>
+
+
+<collation name="latin7_general_ci">
+<!-- Created for case-insensitive record search -->
+<!-- Created by Andis & Rihards -->
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 30 32 33 34 35 36 37 2B 38 39 3A 5C 3B 2C 3C 3D
+ 76 7A 7C 7E 80 81 82 83 84 85 3E 3F 5D 5E 5F 40
+ 41 86 92 94 9A 9C A6 A8 AC AE B4 B6 BA C0 C2 C8
+ D4 D6 D8 DC E3 E6 EE F0 F2 F4 F6 42 43 44 45 46
+ 47 86 92 94 9A 9C A6 A8 AC AE B4 B6 BA C0 C2 C8
+ D4 D6 D8 DC E2 E6 EE F0 F2 F4 F6 48 49 4A 4B 20
+ 75 21 56 22 59 73 70 71 23 74 24 5A 25 4D 51 50
+ 26 54 55 57 58 72 2E 2F 27 E5 28 5B 29 4E 53 2A
+ 31 FE 65 66 67 FF 4C 68 2D 69 DA 61 6A 2D 6B 90
+ 6C 60 7D 7F 4F 6D 6E 6F D3 7B DB 62 77 78 79 90
+ 8E B2 8A 96 88 8C A4 A2 98 9E F8 A0 AA B8 B0 BE
+ E1 C4 C6 CA CE D0 CC 63 EC BC DE EA E8 FA FC E0
+ 8E B2 8A 96 88 8C A4 A2 98 9E F8 A0 AA B8 B0 BE
+ E1 C4 C6 CA CE D0 CC 64 EC BC DE EA E8 FA FC 52
+</map>
+</collation>
+
+<collation name="latin7_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/macce.xml b/sql/share/charsets/macce.xml
new file mode 100644
index 00000000000..4fa46301d2e
--- /dev/null
+++ b/sql/share/charsets/macce.xml
@@ -0,0 +1,207 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="macce">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 00
+ 01 01 02 01 01 01 01 02 02 01 02 02 01 02 02 01
+ 02 01 02 02 01 02 01 02 02 02 02 02 02 01 02 02
+ 00 00 01 00 00 00 00 02 00 00 00 02 00 00 02 01
+ 02 01 00 00 02 01 00 00 02 01 02 01 02 01 02 01
+ 02 01 00 00 02 01 00 00 00 00 00 02 01 01 02 01
+ 00 00 00 00 00 00 00 00 02 01 02 01 00 00 02 01
+ 02 01 00 00 02 01 02 01 01 02 01 01 02 01 01 01
+ 02 01 01 02 01 02 01 02 01 02 02 01 01 02 01 00
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 54 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 54 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 8A 82 82 8E 88 9A 9F 87 88 8B 8A 8B 8D 8D 8E 90
+ 90 93 92 93 95 95 98 97 98 99 9A 9B 9C 9E 9E 9F
+ A0 A1 AB A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE B0
+ B0 B4 B2 B3 B4 FA B6 B7 B8 BA BA BC BC BE BE C0
+ C0 C4 C2 C3 C4 CB C6 C7 C8 C9 CA CB CE 9B CE D8
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 DA DA DE DC DD DE E0
+ E0 E4 E2 E3 E4 E6 E6 87 E9 E9 92 EC EC F0 97 99
+ F0 F3 9C F3 F5 F5 F7 F7 F9 F9 FA FD B8 FD AE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 74 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 74 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 81 83 84 85 86 E7 84 89 80 89 8C 8C 83 8F
+ 8F 91 EA 91 94 94 96 EE 96 EF 85 CD F2 9D 9D 86
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA A2 AC AD FE AF
+ AF B1 B2 B3 B1 B5 B6 B7 FC B9 B9 BB BB BD BD BF
+ BF C1 C2 C3 C1 C5 C6 C7 C8 C9 CA C5 CC CD CC CF
+ D0 D1 D2 D3 D4 D5 D6 D7 CF D9 D9 DB DC DD DB DF
+ DF E1 E2 E3 E1 E5 E5 E7 E8 E8 EA EB EB ED EE EF
+ ED F1 F2 F1 F4 F4 F6 F6 F8 F8 B5 FB FC FB FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 00C4 0100 0101 00C9 0104 00D6 00DC 00E1 0105 010C 00E4 010D 0106 0107 00E9 0179
+ 017A 010E 00ED 010F 0112 0113 0116 00F3 0117 00F4 00F6 00F5 00FA 011A 011B 00FC
+ 2020 00B0 0118 00A3 00A7 2022 00B6 00DF 00AE 00A9 2122 0119 00A8 2260 0123 012E
+ 012F 012A 2264 2265 012B 0136 2202 2211 0142 013B 013C 013D 013E 0139 013A 0145
+ 0146 0143 00AC 221A 0144 0147 2206 00AB 00BB 2026 00A0 0148 0150 00D5 0151 014C
+ 2013 2014 201C 201D 2018 2019 00F7 25CA 014D 0154 0155 0158 2039 203A 0159 0156
+ 0157 0160 201A 201E 0161 015A 015B 00C1 0164 0165 00CD 017D 017E 016A 00D3 00D4
+ 016B 016E 00DA 016F 0170 0171 0172 0173 00DD 00FD 0137 017B 0141 017C 0122 02C7
+</map>
+</unicode>
+
+
+<collation name="macce_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 46 47 4A 4C 52 53 55 56 5A 5B 5D 62 62 67
+ 6F 70 71 75 79 81 88 89 8A 8B 8D 90 91 92 93 94
+ 95 41 46 47 4A 4C 52 53 55 56 5A 5B 5D 62 62 67
+ 6F 70 71 75 79 81 88 89 8A 8B 8D 96 97 98 99 9A
+ 41 41 41 4C 41 67 81 41 41 47 41 47 47 47 4C 8D
+ 8D 4A 56 4A 4C 4C 4C 67 4C 67 67 67 81 4C 4C 81
+ A0 A1 4C A3 A4 A5 A6 75 A8 A9 AA 4C AC AD 53 56
+ 56 56 B2 B3 56 5B B6 B7 5D 5D 5D 5D 5D 5D 5D 62
+ 62 62 C2 C3 62 62 C6 C7 C8 C9 CA 62 67 67 67 67
+ D0 D1 D2 D3 D4 D5 D6 D7 67 71 71 71 DC DD 71 71
+ 71 75 E2 E3 75 75 75 41 79 79 56 8D 8D 81 67 67
+ 81 81 81 81 81 81 81 81 8B 8B 5B 8D 5D 8D 53 FF
+</map>
+</collation>
+
+
+<collation name="macce_bin" flag="binary"/>
+
+<collation name="macce_ci_ai">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 4B 4D 53 57 63 65 69 6B 73 75 79 83 85 8D
+ 9B 9D 9F A7 AE B2 C0 C2 C4 C6 CA D2 D3 D4 D5 D6
+ D7 41 4B 4D 53 57 63 65 69 6B 73 75 79 83 85 8D
+ 9B 9D 9F A7 AE B2 C0 C2 C4 C6 CA D8 D9 DA DB DC
+ 41 41 41 57 41 8D B2 41 41 4D 41 4D 4D 4D 57 CA
+ CA 53 6B 53 57 57 57 8D 57 8D 8D 8D B2 57 57 B2
+ DD DE 57 DF E0 E1 E2 A7 E3 E4 E5 57 E6 E7 65 6B
+ 6B 6B E8 E9 6B 75 EA EB 79 79 79 79 79 79 79 85
+ 85 85 EC ED 85 85 EE EF F0 F1 F2 85 8D 8D 8D 8D
+ F3 F4 F5 F6 F7 F8 F9 FA 8D 9F 9F 9F FB FC 9F 9F
+ 9F A7 FD FE A7 A7 A7 41 BE BE 6B CA CA B2 8D 8D
+ B2 B2 B2 B2 B2 B2 B2 B2 C6 C6 75 CA 79 CA 65 FF
+</map>
+</collation>
+
+
+<collation name="macce_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 4B 4D 53 57 63 65 69 6B 73 75 79 83 85 8D
+ 9B 9D 9F A7 AE B2 C0 C2 C4 C6 CA D2 D3 D4 D5 D6
+ D7 41 4B 4D 53 57 63 65 69 6B 73 75 79 83 85 8D
+ 9B 9D 9F A7 AE B2 C0 C2 C4 C6 CA D8 D9 DA DB DC
+ 45 47 47 59 49 91 B6 43 49 4F 45 4F 51 51 59 CE
+ CE 55 71 55 5B 5B 5D 8F 5D 99 91 97 B8 5F 5F B6
+ DD DE 61 DF E0 E1 E2 AD E3 E4 E5 61 E6 E7 67 6F
+ 6F 6D E8 E9 6D 77 EA EB 7B 81 82 7F 7F 7D 7D 8B
+ 8B 87 EC ED 87 89 EE EF F0 F1 F2 89 93 97 93 95
+ F3 F4 F5 F6 F7 F8 F9 FA 95 A1 A1 A3 FB FC A3 A5
+ A5 A9 FD FE A9 AB AB 43 B0 B0 71 CC CC BC 8F 99
+ BC B4 B8 B4 BA BA BE BE C8 C8 77 D0 7B D0 67 FF
+
+</map>
+</collation>
+
+
+<collation name="macce_cs">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 4B 4D 53 57 63 65 69 6B 73 75 79 83 85 8D
+ 9B 9D 9F A7 AE B2 C0 C2 C4 C6 CA D2 D3 D4 D5 D6
+ D7 42 4C 4E 54 58 64 66 6A 6C 74 76 7A 84 86 8E
+ 9C 9E A0 A8 AF B3 C1 C3 C5 C7 CB D8 D9 DA DB DC
+ 45 47 48 59 49 91 B6 44 4A 4F 46 50 51 52 5A CE
+ CF 55 72 56 5B 5C 5D 90 5E 9A 92 98 B8 5F 60 B7
+ DD DE 61 DF E0 E1 E2 AD E3 E4 E5 62 E6 E7 68 6F
+ 70 6D E8 E9 6E 77 EA EB 7C 81 82 7F 80 7D 7E 8B
+ 8C 87 EC ED 88 89 EE EF F0 F1 F2 8A 93 97 94 95
+ F3 F4 F5 F6 F7 F8 F9 FA 96 A1 A2 A3 FB FC A4 A5
+ A6 A9 FD FE AA AB AC 43 B0 B1 71 CC CD BC 8F 99
+ BD B4 B9 B5 BA BB BE BF C8 C9 78 D0 7B D1 67 FF
+</map>
+</collation>
+
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/macroman.xml b/sql/share/charsets/macroman.xml
new file mode 100644
index 00000000000..4ee8dc1f952
--- /dev/null
+++ b/sql/share/charsets/macroman.xml
@@ -0,0 +1,200 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="macroman">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 10 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 10 10 10 10 10
+ 10 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 10 10 10 10 10
+ 20 01 01 01 01 01 01 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
+ 00 00 00 00 00 00 00 02 00 00 00 00 00 00 01 01
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 02
+ 00 00 00 00 02 00 00 00 00 00 00 20 01 01 00 00
+ 00 00 00 00 00 00 00 00 02 01 00 00 00 00 00 00
+ 00 00 00 00 00 20 01 01 01 01 01 01 01 01 01 01
+ 00 01 01 01 01 02 00 00 00 00 00 00 00 00 00 00
+</map>
+</ctype>
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 5B 5C 5D 5E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 8A 8C 8D 8E 96 9A 9F 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD BE BF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA 88 8B 9B CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D8 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 89 90 87 91 8F 92 94 95 93 97 99
+ F0 98 9C 9E 9D F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 E7 CB E5 80 CC 81 82 83 E9
+ E6 E8 EA ED EB EC 84 EE F1 EF 85 CD F2 F4 F3 86
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD AE AF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D9 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+ 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+ 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+ 0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+ 0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+ 0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+ 0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 005B 005C 005D 005E 005F
+ 0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+ 0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 007B 007C 007D 007E 007F
+ 00C4 00C5 00C7 00C9 00D1 00D6 00DC 00E1 00E0 00E2 00E4 00E3 00E5 00E7 00E9 00E8
+ 00EA 00EB 00ED 00EC 00EE 00EF 00F1 00F3 00F2 00F4 00F6 00F5 00FA 00F9 00FB 00FC
+ 2020 00B0 00A2 00A3 00A7 2022 00B6 00DF 00AE 00A9 2122 00B4 00A8 2260 00C6 00D8
+ 221E 00B1 2264 2265 00A5 00B5 2202 2211 220F 03C0 222B 00AA 00BA 03A9 00E6 00F8
+ 00BF 00A1 00AC 221A 0192 2248 2206 00AB 00BB 2026 00A0 00C0 00C3 00D5 0152 0153
+ 2013 2014 201C 201D 2018 2019 00F7 25CA 00FF 0178 2044 20AC 2039 203A FB01 FB02
+ 2021 00B7 201A 201E 2030 00C2 00CA 00C1 00CB 00C8 00CD 00CE 00CF 00CC 00D3 00D4
+ F8FF 00D2 00DA 00DB 00D9 0131 02C6 02DC 00AF 02D8 02D9 02DA 00B8 02DD 02DB 02C7
+</map>
+</unicode>
+
+<collation name="macroman_general_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 49 50 52 53 57 59 60 61 67 68 69 70 71 72
+ 79 80 81 82 84 85 90 91 92 93 95 A0 A1 A2 A3 A4
+ A5 41 49 50 52 53 57 59 60 61 67 68 69 70 71 72
+ 79 80 81 82 84 85 90 91 92 93 95 A6 A7 A8 A9 AA
+ 41 41 50 53 71 72 85 41 41 41 41 41 41 50 53 53
+ 53 53 61 61 61 61 71 72 72 72 72 72 85 85 85 85
+ AB AC AD AE AF B0 B1 82 B2 B3 B4 B5 B6 B7 48 72
+ B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 48 72
+ C6 C7 C8 C9 57 CA CB CC CD CE CF 41 41 72 D0 D1
+ D2 D3 D4 D5 D6 D7 D8 D9 93 93 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 41 53 41 53 53 61 61 61 61 72 72
+ F0 72 85 85 85 61 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="macroman_bin" flag="binary"/>
+
+<collation name="macroman_ci_ai">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 51 53 57 59 63 66 68 6A 75 77 79 7B 7D 81
+ 91 93 95 97 9A 9C A6 A8 AA AC B0 B2 B3 B4 B5 B6
+ B7 41 51 53 57 59 63 66 68 6A 75 77 79 7B 7D 81
+ 91 93 95 97 9A 9C A6 A8 AA AC B0 B8 B9 BA BB BC
+ 41 41 53 59 7D 81 9C 41 41 41 41 41 41 53 59 59
+ 59 59 6A 6A 6A 6A 7D 81 81 81 81 81 9C 9C 9C 9C
+ BD BE BF C0 C1 C2 C3 97 C4 C5 C6 C7 C8 C9 41 81
+ CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 41 81
+ D8 D9 DA DB 63 DC DD DE DF E0 E1 41 41 81 81 81
+ E2 E3 E4 E5 E6 E7 E8 E9 AC AC EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 41 59 41 59 59 6A 6A 6A 6A 81 81
+ F0 81 9C 9C 9C 6A F6 F7 F8 F9 FA FB FC FD FE FF
+
+</map>
+</collation>
+
+<collation name="macroman_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 51 53 57 59 63 66 68 6A 75 77 79 7B 7D 81
+ 91 93 95 97 9A 9C A6 A8 AA AC B0 B2 B3 B4 B5 B6
+ B7 41 51 53 57 59 63 66 68 6A 75 77 79 7B 7D 81
+ 91 93 95 97 9A 9C A6 A8 AA AC B0 B8 B9 BA BB BC
+ 4B 4D 55 5D 7F 8B A4 45 43 47 4B 49 4D 55 5D 5B
+ 5F 61 6E 6C 70 72 7F 85 83 87 8B 89 A0 9E A2 A4
+ BD BE BF C0 C1 C2 C3 99 C4 C5 C6 C7 C8 C9 4F 8D
+ CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 4F 8D
+ D8 D9 DA DB 65 DC DD DE DF E0 E1 43 49 89 8F 8F
+ E2 E3 E4 E5 E6 E7 E8 E9 AE AE EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 47 5F 45 61 5B 6E 70 70 6C 85 87
+ F0 83 A0 A2 9E 72 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="macroman_cs">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 51 53 57 59 63 66 68 6A 75 77 79 7B 7D 81
+ 91 93 95 97 9A 9C A6 A8 AA AC B0 B2 B3 B4 B5 B6
+ B7 42 52 54 58 5A 64 67 69 6B 76 78 7A 7C 7E 82
+ 92 94 96 98 9B 9D A7 A9 AB AD B1 B8 B9 BA BB BC
+ 4B 4D 55 5D 7F 8B A4 46 44 48 4C 4A 4E 56 5E 5C
+ 60 62 6F 6D 71 73 80 86 84 88 8C 8A A1 9F A3 A5
+ BD BE BF C0 C1 C2 C3 99 C4 C5 C6 C7 C8 C9 4F 8D
+ CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 50 8E
+ D8 D9 DA DB 65 DC DD DE DF E0 E1 43 49 89 8F 90
+ E2 E3 E4 E5 E6 E7 E8 E9 AF AE EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 47 5F 45 61 5B 6E 70 72 6C 85 87
+ F0 83 A0 A2 9E 74 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+</charset>
+
+</charsets>
diff --git a/sql/share/charsets/swe7.xml b/sql/share/charsets/swe7.xml
new file mode 100644
index 00000000000..d881f1e7d62
--- /dev/null
+++ b/sql/share/charsets/swe7.xml
@@ -0,0 +1,141 @@
+<?xml version='1.0' encoding="utf-8"?>
+
+<charsets>
+
+<copyright>
+ Copyright (C) 2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+</copyright>
+
+<charset name="swe7">
+
+<ctype>
+<map>
+ 00
+ 20 20 20 20 20 20 20 20 20 28 28 28 28 28 20 20
+ 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+ 48 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
+ 84 84 84 84 84 84 84 84 84 84 10 10 10 10 10 10
+ 01 81 81 81 81 81 81 01 01 01 01 01 01 01 01 01
+ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 10
+ 01 82 82 82 82 82 82 02 02 02 02 02 02 02 02 02
+ 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 20
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+</map>
+</ctype>
+
+
+<lower>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 5F
+ 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
+ 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</lower>
+
+
+<upper>
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
+ 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</upper>
+
+
+<unicode>
+<map>
+0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F
+0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001A 001B 001C 001D 001E 001F
+0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002A 002B 002C 002D 002E 002F
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003A 003B 003C 003D 003E 003F
+00C9 0041 0042 0043 0044 0045 0046 0047 0048 0049 004A 004B 004C 004D 004E 004F
+0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005A 00C4 00D6 00C5 00DC 005F
+00E9 0061 0062 0063 0064 0065 0066 0067 0068 0069 006A 006B 006C 006D 006E 006F
+0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007A 00E4 00F6 00E5 00FC 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</map>
+</unicode>
+
+
+<collation name="swe7_swedish_ci">
+<map>
+ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+ 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
+ 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
+ 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
+ 45 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5C 5D 5B 59 5F
+ 45 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
+ 50 51 52 53 54 55 56 57 58 59 5A 5C 5D 5B 59 7F
+ 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
+ 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
+ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
+ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
+ C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
+ D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
+ E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
+ F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
+</map>
+</collation>
+
+<collation name="swe7_bin" flag="binary"/>
+
+</charset>
+
+</charsets>
+
+
diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt
new file mode 100644
index 00000000000..6954170e86c
--- /dev/null
+++ b/sql/share/errmsg-utf8.txt
@@ -0,0 +1,7113 @@
+languages czech=cze latin2, danish=dan latin1, dutch=nla latin1, english=eng latin1, estonian=est latin7, french=fre latin1, german=ger latin1, greek=greek greek, hungarian=hun latin2, italian=ita latin1, japanese=jpn ujis, korean=kor euckr, norwegian-ny=norwegian-ny latin1, norwegian=nor latin1, polish=pol latin2, portuguese=por latin1, romanian=rum latin2, russian=rus koi8r, serbian=serbian cp1250, slovak=slo latin2, spanish=spa latin1, swedish=swe latin1, ukrainian=ukr koi8u, bulgarian=bgn cp1251;
+
+default-language eng
+
+start-error-number 1000
+
+ER_HASHCHK
+ eng "hashchk"
+ER_NISAMCHK
+ eng "isamchk"
+ER_NO
+ cze "NE"
+ dan "NEJ"
+ nla "NEE"
+ eng "NO"
+ est "EI"
+ fre "NON"
+ ger "Nein"
+ greek "ΟΧΙ"
+ hun "NEM"
+ kor "아니오"
+ nor "NEI"
+ norwegian-ny "NEI"
+ pol "NIE"
+ por "NÃO"
+ rum "NU"
+ rus "ÐЕТ"
+ serbian "NE"
+ slo "NIE"
+ ukr "ÐІ"
+ER_YES
+ cze "ANO"
+ dan "JA"
+ nla "JA"
+ eng "YES"
+ est "JAH"
+ fre "OUI"
+ ger "Ja"
+ greek "ÎΑΙ"
+ hun "IGEN"
+ ita "SI"
+ kor "예"
+ nor "JA"
+ norwegian-ny "JA"
+ pol "TAK"
+ por "SIM"
+ rum "DA"
+ rus "ДÐ"
+ serbian "DA"
+ slo "Ãno"
+ spa "SI"
+ ukr "ТÐК"
+ER_CANT_CREATE_FILE
+ cze "Nemohu vytvořit soubor '%-.200s' (chybový kód: %M)"
+ dan "Kan ikke oprette filen '%-.200s' (Fejlkode: %M)"
+ nla "Kan file '%-.200s' niet aanmaken (Errcode: %M)"
+ eng "Can't create file '%-.200s' (errno: %M)"
+ est "Ei suuda luua faili '%-.200s' (veakood: %M)"
+ fre "Ne peut créer le fichier '%-.200s' (Errcode: %M)"
+ ger "Kann Datei '%-.200s' nicht erzeugen (Fehler: %M)"
+ greek "ΑδÏνατη η δημιουÏγία του αÏχείου '%-.200s' (κωδικός λάθους: %M)"
+ hun "A '%-.200s' file nem hozhato letre (hibakod: %M)"
+ ita "Impossibile creare il file '%-.200s' (errno: %M)"
+ jpn "ファイル '%-.200s' を作æˆã§ãã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "í™”ì¼ '%-.200s'를 만들지 못했습니다. (ì—러번호: %M)"
+ nor "Kan ikke opprette fila '%-.200s' (Feilkode: %M)"
+ norwegian-ny "Kan ikkje opprette fila '%-.200s' (Feilkode: %M)"
+ pol "Nie można stworzyć pliku '%-.200s' (Kod błędu: %M)"
+ por "Não pode criar o arquivo '%-.200s' (erro no. %M)"
+ rum "Nu pot sa creez fisierul '%-.200s' (Eroare: %M)"
+ rus "Ðевозможно Ñоздать файл '%-.200s' (ошибка: %M)"
+ serbian "Ne mogu da kreiram file '%-.200s' (errno: %M)"
+ slo "Nemôžem vytvoriť súbor '%-.200s' (chybový kód: %M)"
+ spa "No puedo crear archivo '%-.200s' (Error: %M)"
+ swe "Kan inte skapa filen '%-.200s' (Felkod: %M)"
+ ukr "Ðе можу Ñтворити файл '%-.200s' (помилка: %M)"
+ER_CANT_CREATE_TABLE
+ cze "Nemohu vytvořit tabulku %`s.%`s (chybový kód: %M)"
+ dan "Kan ikke oprette tabellen %`s.%`s (Fejlkode: %M)"
+ nla "Kan tabel %`s.%`s niet aanmaken (Errcode: %M)"
+ eng "Can't create table %`s.%`s (errno: %M)"
+ jps "%`s.%`s テーブルãŒä½œã‚Œã¾ã›ã‚“.(errno: %M)",
+ est "Ei suuda luua tabelit %`s.%`s (veakood: %M)"
+ fre "Ne peut créer la table %`s.%`s (Errcode: %M)"
+ ger "Kann Tabelle %`s.%`s nicht erzeugen (Fehler: %M)"
+ greek "ΑδÏνατη η δημιουÏγία του πίνακα %`s.%`s (κωδικός λάθους: %M)"
+ hun "A %`s.%`s tabla nem hozhato letre (hibakod: %M)"
+ ita "Impossibile creare la tabella %`s.%`s (errno: %M)"
+ jpn "%`s.%`s テーブルãŒä½œã‚Œã¾ã›ã‚“.(errno: %M)"
+ kor "í…Œì´ë¸” %`s.%`s를 만들지 못했습니다. (ì—러번호: %M)"
+ nor "Kan ikke opprette tabellen %`s.%`s (Feilkode: %M)"
+ norwegian-ny "Kan ikkje opprette tabellen %`s.%`s (Feilkode: %M)"
+ pol "Nie można stworzyć tabeli %`s.%`s (Kod błędu: %M)"
+ por "Não pode criar a tabela %`s.%`s (erro no. %M)"
+ rum "Nu pot sa creez tabla %`s.%`s (Eroare: %M)"
+ rus "Ðевозможно Ñоздать таблицу %`s.%`s (ошибка: %M)"
+ serbian "Ne mogu da kreiram tabelu %`s.%`s (errno: %M)"
+ slo "Nemôžem vytvoriť tabuľku %`s.%`s (chybový kód: %M)"
+ spa "No puedo crear tabla %`s.%`s (Error: %M)"
+ swe "Kan inte skapa tabellen %`s.%`s (Felkod: %M)"
+ ukr "Ðе можу Ñтворити таблицю %`s.%`s (помилка: %M)"
+ER_CANT_CREATE_DB
+ cze "Nemohu vytvořit databázi '%-.192s' (chybový kód: %M)"
+ dan "Kan ikke oprette databasen '%-.192s' (Fejlkode: %M)"
+ nla "Kan database '%-.192s' niet aanmaken (Errcode: %M)"
+ eng "Can't create database '%-.192s' (errno: %M)"
+ est "Ei suuda luua andmebaasi '%-.192s' (veakood: %M)"
+ fre "Ne peut créer la base '%-.192s' (Erreur %M)"
+ ger "Kann Datenbank '%-.192s' nicht erzeugen (Fehler: %M)"
+ greek "ΑδÏνατη η δημιουÏγία της βάσης δεδομένων '%-.192s' (κωδικός λάθους: %M)"
+ hun "Az '%-.192s' adatbazis nem hozhato letre (hibakod: %M)"
+ ita "Impossibile creare il database '%-.192s' (errno: %M)"
+ jpn "データベース '%-.192s' を作æˆã§ãã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'를 만들지 못했습니다.. (ì—러번호: %M)"
+ nor "Kan ikke opprette databasen '%-.192s' (Feilkode: %M)"
+ norwegian-ny "Kan ikkje opprette databasen '%-.192s' (Feilkode: %M)"
+ pol "Nie można stworzyć bazy danych '%-.192s' (Kod błędu: %M)"
+ por "Não pode criar o banco de dados '%-.192s' (erro no. %M)"
+ rum "Nu pot sa creez baza de date '%-.192s' (Eroare: %M)"
+ rus "Ðевозможно Ñоздать базу данных '%-.192s' (ошибка: %M)"
+ serbian "Ne mogu da kreiram bazu '%-.192s' (errno: %M)"
+ slo "Nemôžem vytvoriť databázu '%-.192s' (chybový kód: %M)"
+ spa "No puedo crear base de datos '%-.192s' (Error: %M)"
+ swe "Kan inte skapa databasen '%-.192s' (Felkod: %M)"
+ ukr "Ðе можу Ñтворити базу данних '%-.192s' (помилка: %M)"
+ER_DB_CREATE_EXISTS
+ cze "Nemohu vytvořit databázi '%-.192s'; databáze již existuje"
+ dan "Kan ikke oprette databasen '%-.192s'; databasen eksisterer"
+ nla "Kan database '%-.192s' niet aanmaken; database bestaat reeds"
+ eng "Can't create database '%-.192s'; database exists"
+ est "Ei suuda luua andmebaasi '%-.192s': andmebaas juba eksisteerib"
+ fre "Ne peut créer la base '%-.192s'; elle existe déjà"
+ ger "Kann Datenbank '%-.192s' nicht erzeugen. Datenbank existiert bereits"
+ greek "ΑδÏνατη η δημιουÏγία της βάσης δεδομένων '%-.192s'; Η βάση δεδομένων υπάÏχει ήδη"
+ hun "Az '%-.192s' adatbazis nem hozhato letre Az adatbazis mar letezik"
+ ita "Impossibile creare il database '%-.192s'; il database esiste"
+ jpn "データベース '%-.192s' を作æˆã§ãã¾ã›ã‚“。データベースã¯ã™ã§ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+ kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'를 만들지 못했습니다.. ë°ì´íƒ€ë² ì´ìŠ¤ê°€ 존재함"
+ nor "Kan ikke opprette databasen '%-.192s'; databasen eksisterer"
+ norwegian-ny "Kan ikkje opprette databasen '%-.192s'; databasen eksisterer"
+ pol "Nie można stworzyć bazy danych '%-.192s'; baza danych już istnieje"
+ por "Não pode criar o banco de dados '%-.192s'; este banco de dados já existe"
+ rum "Nu pot sa creez baza de date '%-.192s'; baza de date exista deja"
+ rus "Ðевозможно Ñоздать базу данных '%-.192s'. База данных уже ÑущеÑтвует"
+ serbian "Ne mogu da kreiram bazu '%-.192s'; baza već postoji."
+ slo "Nemôžem vytvoriť databázu '%-.192s'; databáza existuje"
+ spa "No puedo crear base de datos '%-.192s'; la base de datos ya existe"
+ swe "Databasen '%-.192s' existerar redan"
+ ukr "Ðе можу Ñтворити базу данних '%-.192s'. База данних Ñ–Ñнує"
+ER_DB_DROP_EXISTS
+ cze "Nemohu zrušit databázi '%-.192s', databáze neexistuje"
+ dan "Kan ikke slette (droppe) '%-.192s'; databasen eksisterer ikke"
+ nla "Kan database '%-.192s' niet verwijderen; database bestaat niet"
+ eng "Can't drop database '%-.192s'; database doesn't exist"
+ est "Ei suuda kustutada andmebaasi '%-.192s': andmebaasi ei eksisteeri"
+ fre "Ne peut effacer la base '%-.192s'; elle n'existe pas"
+ ger "Kann Datenbank '%-.192s' nicht löschen; Datenbank nicht vorhanden"
+ greek "ΑδÏνατη η διαγÏαφή της βάσης δεδομένων '%-.192s'. Η βάση δεδομένων δεν υπάÏχει"
+ hun "A(z) '%-.192s' adatbazis nem szuntetheto meg. Az adatbazis nem letezik"
+ ita "Impossibile cancellare '%-.192s'; il database non esiste"
+ jpn "データベース '%-.192s' を削除ã§ãã¾ã›ã‚“。データベースã¯å­˜åœ¨ã—ã¾ã›ã‚“。"
+ kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'를 제거하지 못했습니다. ë°ì´íƒ€ë² ì´ìŠ¤ê°€ 존재하지 ì•ŠìŒ "
+ nor "Kan ikke fjerne (drop) '%-.192s'; databasen eksisterer ikke"
+ norwegian-ny "Kan ikkje fjerne (drop) '%-.192s'; databasen eksisterer ikkje"
+ pol "Nie można usun?ć bazy danych '%-.192s'; baza danych nie istnieje"
+ por "Não pode eliminar o banco de dados '%-.192s'; este banco de dados não existe"
+ rum "Nu pot sa drop baza de date '%-.192s'; baza da date este inexistenta"
+ rus "Ðевозможно удалить базу данных '%-.192s'. Такой базы данных нет"
+ serbian "Ne mogu da izbrišem bazu '%-.192s'; baza ne postoji."
+ slo "Nemôžem zmazať databázu '%-.192s'; databáza neexistuje"
+ spa "No puedo eliminar base de datos '%-.192s'; la base de datos no existe"
+ swe "Kan inte radera databasen '%-.192s'; databasen finns inte"
+ ukr "Ðе можу видалити базу данних '%-.192s'. База данних не Ñ–Ñнує"
+ER_DB_DROP_DELETE
+ cze "Chyba při rušení databáze (nemohu vymazat '%-.192s', chyba %M)"
+ dan "Fejl ved sletning (drop) af databasen (kan ikke slette '%-.192s', Fejlkode %M)"
+ nla "Fout bij verwijderen database (kan '%-.192s' niet verwijderen, Errcode: %M)"
+ eng "Error dropping database (can't delete '%-.192s', errno: %M)"
+ est "Viga andmebaasi kustutamisel (ei suuda kustutada faili '%-.192s', veakood: %M)"
+ fre "Ne peut effacer la base '%-.192s' (erreur %M)"
+ ger "Fehler beim Löschen der Datenbank ('%-.192s' kann nicht gelöscht werden, Fehler: %M)"
+ greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή της βάσης δεδομένων (αδÏνατη η διαγÏαφή '%-.192s', κωδικός λάθους: %M)"
+ hun "Adatbazis megszuntetesi hiba ('%-.192s' nem torolheto, hibakod: %M)"
+ ita "Errore durante la cancellazione del database (impossibile cancellare '%-.192s', errno: %M)"
+ jpn "データベース削除エラー ('%-.192s' を削除ã§ãã¾ã›ã‚“。エラー番å·: %M)"
+ kor "ë°ì´íƒ€ë² ì´ìФ 제거 ì—러('%-.192s'를 삭제할 수 ì—†ì니다, ì—러번호: %M)"
+ nor "Feil ved fjerning (drop) av databasen (kan ikke slette '%-.192s', feil %M)"
+ norwegian-ny "Feil ved fjerning (drop) av databasen (kan ikkje slette '%-.192s', feil %M)"
+ pol "Bł?d podczas usuwania bazy danych (nie można usun?ć '%-.192s', bł?d %M)"
+ por "Erro ao eliminar banco de dados (não pode eliminar '%-.192s' - erro no. %M)"
+ rum "Eroare dropuind baza de date (nu pot sa sterg '%-.192s', Eroare: %M)"
+ rus "Ошибка при удалении базы данных (невозможно удалить '%-.192s', ошибка: %M)"
+ serbian "Ne mogu da izbrišem bazu (ne mogu da izbrišem '%-.192s', errno: %M)"
+ slo "Chyba pri mazaní databázy (nemôžem zmazať '%-.192s', chybový kód: %M)"
+ spa "Error eliminando la base de datos(no puedo borrar '%-.192s', error %M)"
+ swe "Fel vid radering av databasen (Kan inte radera '%-.192s'. Felkod: %M)"
+ ukr "Ðе можу видалити базу данних (Ðе можу видалити '%-.192s', помилка: %M)"
+ER_DB_DROP_RMDIR
+ cze "Chyba při rušení databáze (nemohu vymazat adresář '%-.192s', chyba %M)"
+ dan "Fejl ved sletting af database (kan ikke slette folderen '%-.192s', Fejlkode %M)"
+ nla "Fout bij verwijderen database (kan rmdir '%-.192s' niet uitvoeren, Errcode: %M)"
+ eng "Error dropping database (can't rmdir '%-.192s', errno: %M)"
+ est "Viga andmebaasi kustutamisel (ei suuda kustutada kataloogi '%-.192s', veakood: %M)"
+ fre "Erreur en effaçant la base (rmdir '%-.192s', erreur %M)"
+ ger "Fehler beim Löschen der Datenbank (Verzeichnis '%-.192s' kann nicht gelöscht werden, Fehler: %M)"
+ greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή της βάσης δεδομένων (αδÏνατη η διαγÏαφή του φακέλλου '%-.192s', κωδικός λάθους: %M)"
+ hun "Adatbazis megszuntetesi hiba ('%-.192s' nem szuntetheto meg, hibakod: %M)"
+ ita "Errore durante la cancellazione del database (impossibile rmdir '%-.192s', errno: %M)"
+ jpn "データベース削除エラー (ディレクトリ '%-.192s' を削除ã§ãã¾ã›ã‚“。エラー番å·: %M)"
+ kor "ë°ì´íƒ€ë² ì´ìФ 제거 ì—러(rmdir '%-.192s'를 í•  수 ì—†ì니다, ì—러번호: %M)"
+ nor "Feil ved sletting av database (kan ikke slette katalogen '%-.192s', feil %M)"
+ norwegian-ny "Feil ved sletting av database (kan ikkje slette katalogen '%-.192s', feil %M)"
+ pol "Bł?d podczas usuwania bazy danych (nie można wykonać rmdir '%-.192s', bł?d %M)"
+ por "Erro ao eliminar banco de dados (não pode remover diretório '%-.192s' - erro no. %M)"
+ rum "Eroare dropuind baza de date (nu pot sa rmdir '%-.192s', Eroare: %M)"
+ rus "Ðевозможно удалить базу данных (невозможно удалить каталог '%-.192s', ошибка: %M)"
+ serbian "Ne mogu da izbrišem bazu (ne mogu da izbrišem direktorijum '%-.192s', errno: %M)"
+ slo "Chyba pri mazaní databázy (nemôžem vymazať adresár '%-.192s', chybový kód: %M)"
+ spa "Error eliminando la base de datos (No puedo borrar directorio '%-.192s', error %M)"
+ swe "Fel vid radering av databasen (Kan inte radera biblioteket '%-.192s'. Felkod: %M)"
+ ukr "Ðе можу видалити базу данних (Ðе можу видалити теку '%-.192s', помилка: %M)"
+ER_CANT_DELETE_FILE
+ cze "Chyba při výmazu '%-.192s' (chybový kód: %M)"
+ dan "Fejl ved sletning af '%-.192s' (Fejlkode: %M)"
+ nla "Fout bij het verwijderen van '%-.192s' (Errcode: %M)"
+ eng "Error on delete of '%-.192s' (errno: %M)"
+ est "Viga '%-.192s' kustutamisel (veakood: %M)"
+ fre "Erreur en effaçant '%-.192s' (Errcode: %M)"
+ ger "Fehler beim Löschen von '%-.192s' (Fehler: %M)"
+ greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή '%-.192s' (κωδικός λάθους: %M)"
+ hun "Torlesi hiba: '%-.192s' (hibakod: %M)"
+ ita "Errore durante la cancellazione di '%-.192s' (errno: %M)"
+ jpn "ファイル '%-.192s' ã®å‰Šé™¤ã‚¨ãƒ©ãƒ¼ (エラー番å·: %M)"
+ kor "'%-.192s' ì‚­ì œ 중 ì—러 (ì—러번호: %M)"
+ nor "Feil ved sletting av '%-.192s' (Feilkode: %M)"
+ norwegian-ny "Feil ved sletting av '%-.192s' (Feilkode: %M)"
+ pol "Bł?d podczas usuwania '%-.192s' (Kod błędu: %M)"
+ por "Erro na remoção de '%-.192s' (erro no. %M)"
+ rum "Eroare incercind sa delete '%-.192s' (Eroare: %M)"
+ rus "Ошибка при удалении '%-.192s' (ошибка: %M)"
+ serbian "Greška pri brisanju '%-.192s' (errno: %M)"
+ slo "Chyba pri mazaní '%-.192s' (chybový kód: %M)"
+ spa "Error en el borrado de '%-.192s' (Error: %M)"
+ swe "Kan inte radera filen '%-.192s' (Felkod: %M)"
+ ukr "Ðе можу видалити '%-.192s' (помилка: %M)"
+ER_CANT_FIND_SYSTEM_REC
+ cze "Nemohu Äíst záznam v systémové tabulce"
+ dan "Kan ikke læse posten i systemfolderen"
+ nla "Kan record niet lezen in de systeem tabel"
+ eng "Can't read record in system table"
+ est "Ei suuda lugeda kirjet süsteemsest tabelist"
+ fre "Ne peut lire un enregistrement de la table 'system'"
+ ger "Datensatz in der Systemtabelle nicht lesbar"
+ greek "ΑδÏνατη η ανάγνωση εγγÏαφής από πίνακα του συστήματος"
+ hun "Nem olvashato rekord a rendszertablaban"
+ ita "Impossibile leggere il record dalla tabella di sistema"
+ jpn "システム表ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’読ã¿è¾¼ã‚ã¾ã›ã‚“。"
+ kor "system í…Œì´ë¸”ì—서 레코드를 ì½ì„ 수 없습니다."
+ nor "Kan ikke lese posten i systemkatalogen"
+ norwegian-ny "Kan ikkje lese posten i systemkatalogen"
+ pol "Nie można odczytać rekordu z tabeli systemowej"
+ por "Não pode ler um registro numa tabela do sistema"
+ rum "Nu pot sa citesc cimpurile in tabla de system (system table)"
+ rus "Ðевозможно прочитать запиÑÑŒ в ÑиÑтемной таблице"
+ serbian "Ne mogu da proÄitam slog iz sistemske tabele"
+ slo "Nemôžem ÄítaÅ¥ záznam v systémovej tabuľke"
+ spa "No puedo leer el registro en la tabla del sistema"
+ swe "Hittar inte posten i systemregistret"
+ ukr "Ðе можу зчитати Ð·Ð°Ð¿Ð¸Ñ Ð· ÑиÑтемної таблиці"
+ER_CANT_GET_STAT
+ cze "Nemohu získat stav '%-.200s' (chybový kód: %M)"
+ dan "Kan ikke læse status af '%-.200s' (Fejlkode: %M)"
+ nla "Kan de status niet krijgen van '%-.200s' (Errcode: %M)"
+ eng "Can't get status of '%-.200s' (errno: %M)"
+ est "Ei suuda lugeda '%-.200s' olekut (veakood: %M)"
+ fre "Ne peut obtenir le status de '%-.200s' (Errcode: %M)"
+ ger "Kann Status von '%-.200s' nicht ermitteln (Fehler: %M)"
+ greek "ΑδÏνατη η λήψη πληÏοφοÏιών για την κατάσταση του '%-.200s' (κωδικός λάθους: %M)"
+ hun "A(z) '%-.200s' statusza nem allapithato meg (hibakod: %M)"
+ ita "Impossibile leggere lo stato di '%-.200s' (errno: %M)"
+ jpn "'%-.200s' ã®çŠ¶æ…‹ã‚’å–å¾—ã§ãã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "'%-.200s'ì˜ ìƒíƒœë¥¼ 얻지 못했습니다. (ì—러번호: %M)"
+ nor "Kan ikke lese statusen til '%-.200s' (Feilkode: %M)"
+ norwegian-ny "Kan ikkje lese statusen til '%-.200s' (Feilkode: %M)"
+ pol "Nie można otrzymać statusu '%-.200s' (Kod błędu: %M)"
+ por "Não pode obter o status de '%-.200s' (erro no. %M)"
+ rum "Nu pot sa obtin statusul lui '%-.200s' (Eroare: %M)"
+ rus "Ðевозможно получить ÑтатуÑную информацию о '%-.200s' (ошибка: %M)"
+ serbian "Ne mogu da dobijem stanje file-a '%-.200s' (errno: %M)"
+ slo "Nemôžem zistiť stav '%-.200s' (chybový kód: %M)"
+ spa "No puedo obtener el estado de '%-.200s' (Error: %M)"
+ swe "Kan inte läsa filinformationen (stat) från '%-.200s' (Felkod: %M)"
+ ukr "Ðе можу отримати ÑÑ‚Ð°Ñ‚ÑƒÑ '%-.200s' (помилка: %M)"
+ER_CANT_GET_WD
+ cze "Chyba při zjišťování pracovní adresář (chybový kód: %M)"
+ dan "Kan ikke læse aktive folder (Fejlkode: %M)"
+ nla "Kan de werkdirectory niet krijgen (Errcode: %M)"
+ eng "Can't get working directory (errno: %M)"
+ est "Ei suuda identifitseerida jooksvat kataloogi (veakood: %M)"
+ fre "Ne peut obtenir le répertoire de travail (Errcode: %M)"
+ ger "Kann Arbeitsverzeichnis nicht ermitteln (Fehler: %M)"
+ greek "Ο φάκελλος εÏγασίας δεν βÏέθηκε (κωδικός λάθους: %M)"
+ hun "A munkakonyvtar nem allapithato meg (hibakod: %M)"
+ ita "Impossibile leggere la directory di lavoro (errno: %M)"
+ jpn "作業ディレクトリをå–å¾—ã§ãã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "수행 디렉토리를 찾지 못했습니다. (ì—러번호: %M)"
+ nor "Kan ikke lese aktiv katalog(Feilkode: %M)"
+ norwegian-ny "Kan ikkje lese aktiv katalog(Feilkode: %M)"
+ pol "Nie można rozpoznać aktualnego katalogu (Kod błędu: %M)"
+ por "Não pode obter o diretório corrente (erro no. %M)"
+ rum "Nu pot sa obtin directorul current (working directory) (Eroare: %M)"
+ rus "Ðевозможно определить рабочий каталог (ошибка: %M)"
+ serbian "Ne mogu da dobijem trenutni direktorijum (errno: %M)"
+ slo "Nemôžem zistiť pracovný adresár (chybový kód: %M)"
+ spa "No puedo acceder al directorio (Error: %M)"
+ swe "Kan inte inte läsa aktivt bibliotek. (Felkod: %M)"
+ ukr "Ðе можу визначити робочу теку (помилка: %M)"
+ER_CANT_LOCK
+ cze "Nemohu uzamknout soubor (chybový kód: %M)"
+ dan "Kan ikke låse fil (Fejlkode: %M)"
+ nla "Kan de file niet blokeren (Errcode: %M)"
+ eng "Can't lock file (errno: %M)"
+ est "Ei suuda lukustada faili (veakood: %M)"
+ fre "Ne peut verrouiller le fichier (Errcode: %M)"
+ ger "Datei kann nicht gesperrt werden (Fehler: %M)"
+ greek "Το αÏχείο δεν μποÏεί να κλειδωθεί (κωδικός λάθους: %M)"
+ hun "A file nem zarolhato. (hibakod: %M)"
+ ita "Impossibile il locking il file (errno: %M)"
+ jpn "ファイルをロックã§ãã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "í™”ì¼ì„ 잠그지(lock) 못했습니다. (ì—러번호: %M)"
+ nor "Kan ikke låse fila (Feilkode: %M)"
+ norwegian-ny "Kan ikkje låse fila (Feilkode: %M)"
+ pol "Nie można zablokować pliku (Kod błędu: %M)"
+ por "Não pode travar o arquivo (erro no. %M)"
+ rum "Nu pot sa lock fisierul (Eroare: %M)"
+ rus "Ðевозможно поÑтавить блокировку на файле (ошибка: %M)"
+ serbian "Ne mogu da zakljuÄam file (errno: %M)"
+ slo "Nemôžem zamknúť súbor (chybový kód: %M)"
+ spa "No puedo bloquear archivo: (Error: %M)"
+ swe "Kan inte låsa filen. (Felkod: %M)"
+ ukr "Ðе можу заблокувати файл (помилка: %M)"
+ER_CANT_OPEN_FILE
+ cze "Nemohu otevřít soubor '%-.200s' (chybový kód: %M)"
+ dan "Kan ikke åbne fil: '%-.200s' (Fejlkode: %M)"
+ nla "Kan de file '%-.200s' niet openen (Errcode: %M)"
+ eng "Can't open file: '%-.200s' (errno: %M)"
+ est "Ei suuda avada faili '%-.200s' (veakood: %M)"
+ fre "Ne peut ouvrir le fichier: '%-.200s' (Errcode: %M)"
+ ger "Kann Datei '%-.200s' nicht öffnen (Fehler: %M)"
+ greek "Δεν είναι δυνατό να ανοιχτεί το αÏχείο: '%-.200s' (κωδικός λάθους: %M)"
+ hun "A '%-.200s' file nem nyithato meg (hibakod: %M)"
+ ita "Impossibile aprire il file: '%-.200s' (errno: %M)"
+ jpn "ファイル '%-.200s' をオープンã§ãã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "í™”ì¼ì„ ì—´ì§€ 못했습니다.: '%-.200s' (ì—러번호: %M)"
+ nor "Kan ikke åpne fila: '%-.200s' (Feilkode: %M)"
+ norwegian-ny "Kan ikkje åpne fila: '%-.200s' (Feilkode: %M)"
+ pol "Nie można otworzyć pliku: '%-.200s' (Kod błędu: %M)"
+ por "Não pode abrir o arquivo '%-.200s' (erro no. %M)"
+ rum "Nu pot sa deschid fisierul: '%-.200s' (Eroare: %M)"
+ rus "Ðевозможно открыть файл: '%-.200s' (ошибка: %M)"
+ serbian "Ne mogu da otvorim file: '%-.200s' (errno: %M)"
+ slo "Nemôžem otvoriť súbor: '%-.200s' (chybový kód: %M)"
+ spa "No puedo abrir archivo: '%-.200s' (Error: %M)"
+ swe "Kan inte använda '%-.200s' (Felkod: %M)"
+ ukr "Ðе можу відкрити файл: '%-.200s' (помилка: %M)"
+ER_FILE_NOT_FOUND
+ cze "Nemohu najít soubor '%-.200s' (chybový kód: %M)"
+ dan "Kan ikke finde fila: '%-.200s' (Fejlkode: %M)"
+ nla "Kan de file: '%-.200s' niet vinden (Errcode: %M)"
+ eng "Can't find file: '%-.200s' (errno: %M)"
+ est "Ei suuda leida faili '%-.200s' (veakood: %M)"
+ fre "Ne peut trouver le fichier: '%-.200s' (Errcode: %M)"
+ ger "Kann Datei '%-.200s' nicht finden (Fehler: %M)"
+ greek "Δεν βÏέθηκε το αÏχείο: '%-.200s' (κωδικός λάθους: %M)"
+ hun "A(z) '%-.200s' file nem talalhato (hibakod: %M)"
+ ita "Impossibile trovare il file: '%-.200s' (errno: %M)"
+ jpn "ファイル '%-.200s' ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "í™”ì¼ì„ 찾지 못했습니다.: '%-.200s' (ì—러번호: %M)"
+ nor "Kan ikke finne fila: '%-.200s' (Feilkode: %M)"
+ norwegian-ny "Kan ikkje finne fila: '%-.200s' (Feilkode: %M)"
+ pol "Nie można znaleĽć pliku: '%-.200s' (Kod błędu: %M)"
+ por "Não pode encontrar o arquivo '%-.200s' (erro no. %M)"
+ rum "Nu pot sa gasesc fisierul: '%-.200s' (Eroare: %M)"
+ rus "Ðевозможно найти файл: '%-.200s' (ошибка: %M)"
+ serbian "Ne mogu da pronađem file: '%-.200s' (errno: %M)"
+ slo "Nemôžem nájsť súbor: '%-.200s' (chybový kód: %M)"
+ spa "No puedo encontrar archivo: '%-.200s' (Error: %M)"
+ swe "Hittar inte filen '%-.200s' (Felkod: %M)"
+ ukr "Ðе можу знайти файл: '%-.200s' (помилка: %M)"
+ER_CANT_READ_DIR
+ cze "Nemohu Äíst adresář '%-.192s' (chybový kód: %M)"
+ dan "Kan ikke læse folder '%-.192s' (Fejlkode: %M)"
+ nla "Kan de directory niet lezen van '%-.192s' (Errcode: %M)"
+ eng "Can't read dir of '%-.192s' (errno: %M)"
+ est "Ei suuda lugeda kataloogi '%-.192s' (veakood: %M)"
+ fre "Ne peut lire le répertoire de '%-.192s' (Errcode: %M)"
+ ger "Verzeichnis von '%-.192s' nicht lesbar (Fehler: %M)"
+ greek "Δεν είναι δυνατό να διαβαστεί ο φάκελλος του '%-.192s' (κωδικός λάθους: %M)"
+ hun "A(z) '%-.192s' konyvtar nem olvashato. (hibakod: %M)"
+ ita "Impossibile leggere la directory di '%-.192s' (errno: %M)"
+ jpn "ディレクトリ '%-.192s' を読ã¿è¾¼ã‚ã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "'%-.192s'디렉토리를 ì½ì§€ 못했습니다. (ì—러번호: %M)"
+ nor "Kan ikke lese katalogen '%-.192s' (Feilkode: %M)"
+ norwegian-ny "Kan ikkje lese katalogen '%-.192s' (Feilkode: %M)"
+ pol "Nie można odczytać katalogu '%-.192s' (Kod błędu: %M)"
+ por "Não pode ler o diretório de '%-.192s' (erro no. %M)"
+ rum "Nu pot sa citesc directorul '%-.192s' (Eroare: %M)"
+ rus "Ðевозможно прочитать каталог '%-.192s' (ошибка: %M)"
+ serbian "Ne mogu da proÄitam direktorijum '%-.192s' (errno: %M)"
+ slo "Nemôžem ÄítaÅ¥ adresár '%-.192s' (chybový kód: %M)"
+ spa "No puedo leer el directorio de '%-.192s' (Error: %M)"
+ swe "Kan inte läsa från bibliotek '%-.192s' (Felkod: %M)"
+ ukr "Ðе можу прочитати теку '%-.192s' (помилка: %M)"
+ER_CANT_SET_WD
+ cze "Nemohu změnit adresář na '%-.192s' (chybový kód: %M)"
+ dan "Kan ikke skifte folder til '%-.192s' (Fejlkode: %M)"
+ nla "Kan de directory niet veranderen naar '%-.192s' (Errcode: %M)"
+ eng "Can't change dir to '%-.192s' (errno: %M)"
+ est "Ei suuda siseneda kataloogi '%-.192s' (veakood: %M)"
+ fre "Ne peut changer le répertoire pour '%-.192s' (Errcode: %M)"
+ ger "Kann nicht in das Verzeichnis '%-.192s' wechseln (Fehler: %M)"
+ greek "ΑδÏνατη η αλλαγή του Ï„Ïέχοντος καταλόγου σε '%-.192s' (κωδικός λάθους: %M)"
+ hun "Konyvtarvaltas nem lehetseges a(z) '%-.192s'-ba. (hibakod: %M)"
+ ita "Impossibile cambiare la directory in '%-.192s' (errno: %M)"
+ jpn "ディレクトリ '%-.192s' ã«ç§»å‹•ã§ãã¾ã›ã‚“。(エラー番å·: %M)"
+ kor "'%-.192s'디렉토리로 ì´ë™í•  수 없었습니다. (ì—러번호: %M)"
+ nor "Kan ikke skifte katalog til '%-.192s' (Feilkode: %M)"
+ norwegian-ny "Kan ikkje skifte katalog til '%-.192s' (Feilkode: %M)"
+ pol "Nie można zmienić katalogu na '%-.192s' (Kod błędu: %M)"
+ por "Não pode mudar para o diretório '%-.192s' (erro no. %M)"
+ rum "Nu pot sa schimb directorul '%-.192s' (Eroare: %M)"
+ rus "Ðевозможно перейти в каталог '%-.192s' (ошибка: %M)"
+ serbian "Ne mogu da promenim direktorijum na '%-.192s' (errno: %M)"
+ slo "Nemôžem vojsť do adresára '%-.192s' (chybový kód: %M)"
+ spa "No puedo cambiar al directorio de '%-.192s' (Error: %M)"
+ swe "Kan inte byta till '%-.192s' (Felkod: %M)"
+ ukr "Ðе можу перейти у теку '%-.192s' (помилка: %M)"
+ER_CHECKREAD
+ cze "Záznam byl zmÄ›nÄ›n od posledního Ätení v tabulce '%-.192s'"
+ dan "Posten er ændret siden sidste læsning '%-.192s'"
+ nla "Record is veranderd sinds de laatste lees activiteit in de tabel '%-.192s'"
+ eng "Record has changed since last read in table '%-.192s'"
+ est "Kirje tabelis '%-.192s' on muutunud viimasest lugemisest saadik"
+ fre "Enregistrement modifié depuis sa dernière lecture dans la table '%-.192s'"
+ ger "Datensatz hat sich seit dem letzten Zugriff auf Tabelle '%-.192s' geändert"
+ greek "Η εγγÏαφή έχει αλλάξει από την τελευταία φοÏά που ανασÏÏθηκε από τον πίνακα '%-.192s'"
+ hun "A(z) '%-.192s' tablaban talalhato rekord megvaltozott az utolso olvasas ota"
+ ita "Il record e` cambiato dall'ultima lettura della tabella '%-.192s'"
+ jpn "表 '%-.192s' ã®æœ€å¾Œã®èª­ã¿è¾¼ã¿æ™‚点ã‹ã‚‰ã€ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒå¤‰åŒ–ã—ã¾ã—ãŸã€‚"
+ kor "í…Œì´ë¸” '%-.192s'ì—서 마지막으로 ì½ì€ 후 Recordê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤."
+ nor "Posten har blitt endret siden den ble lest '%-.192s'"
+ norwegian-ny "Posten har vorte endra sidan den sist vart lesen '%-.192s'"
+ pol "Rekord został zmieniony od ostaniego odczytania z tabeli '%-.192s'"
+ por "Registro alterado desde a última leitura da tabela '%-.192s'"
+ rum "Cimpul a fost schimbat de la ultima citire a tabelei '%-.192s'"
+ rus "ЗапиÑÑŒ изменилаÑÑŒ Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° поÑледней выборки в таблице '%-.192s'"
+ serbian "Slog je promenjen od zadnjeg Äitanja tabele '%-.192s'"
+ slo "Záznam bol zmenený od posledného Äítania v tabuľke '%-.192s'"
+ spa "El registro ha cambiado desde la ultima lectura de la tabla '%-.192s'"
+ swe "Posten har förändrats sedan den lästes i register '%-.192s'"
+ ukr "Ð—Ð°Ð¿Ð¸Ñ Ð±ÑƒÐ»Ð¾ змінено з чаÑу оÑтаннього Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð· таблиці '%-.192s'"
+ER_DISK_FULL
+ cze "Disk je plný (%s), Äekám na uvolnÄ›ní nÄ›jakého místa ... (chybový kód: %M)"
+ dan "Ikke mere diskplads (%s). Venter på at få frigjort plads... (Fejlkode: %M)"
+ nla "Schijf vol (%s). Aan het wachten totdat er ruimte vrij wordt gemaakt... (Errcode: %M)"
+ eng "Disk full (%s); waiting for someone to free some space... (errno: %M)"
+ est "Ketas täis (%s). Ootame kuni tekib vaba ruumi... (veakood: %M)"
+ fre "Disque plein (%s). J'attend que quelqu'un libère de l'espace... (Errcode: %M)"
+ ger "Festplatte voll (%s). Warte, bis jemand Platz schafft ... (Fehler: %M)"
+ greek "Δεν υπάÏχει χώÏος στο δίσκο (%s). ΠαÏακαλώ, πεÏιμένετε να ελευθεÏωθεί χώÏος... (κωδικός λάθους: %M)"
+ hun "A lemez megtelt (%s). (hibakod: %M)"
+ ita "Disco pieno (%s). In attesa che qualcuno liberi un po' di spazio... (errno: %M)"
+ jpn "ディスク領域ä¸è¶³ã§ã™(%s)。(エラー番å·: %M)"
+ kor "Disk full (%s). 다른 ì‚¬ëžŒì´ ì§€ìš¸ë•Œê¹Œì§€ 기다립니다... (ì—러번호: %M)"
+ nor "Ikke mer diskplass (%s). Venter på å få frigjort plass... (Feilkode: %M)"
+ norwegian-ny "Ikkje meir diskplass (%s). Ventar på å få frigjort plass... (Feilkode: %M)"
+ pol "Dysk pełny (%s). Oczekiwanie na zwolnienie miejsca... (Kod błędu: %M)"
+ por "Disco cheio (%s). Aguardando alguém liberar algum espaço... (erro no. %M)"
+ rum "Hard-disk-ul este plin (%s). Astept sa se elibereze ceva spatiu... (Eroare: %M)"
+ rus "ДиÑк заполнен. (%s). Ожидаем, пока кто-то не уберет поÑле ÑÐµÐ±Ñ Ð¼ÑƒÑор... (ошибка: %M)"
+ serbian "Disk je pun (%s). Čekam nekoga da dođe i oslobodi nešto mesta... (errno: %M)"
+ slo "Disk je plný (%s), Äakám na uvoľnenie miesta... (chybový kód: %M)"
+ spa "Disco lleno (%s). Esperando para que se libere algo de espacio... (Error: %M)"
+ swe "Disken är full (%s). Väntar tills det finns ledigt utrymme... (Felkod: %M)"
+ ukr "ДиÑк заповнений (%s). Вичикую, доки звільнитьÑÑ Ñ‚Ñ€Ð¾Ñ…Ð¸ міÑцÑ... (помилка: %M)"
+ER_DUP_KEY 23000
+ cze "Nemohu zapsat, zdvojený klÃ­Ä v tabulce '%-.192s'"
+ dan "Kan ikke skrive, flere ens nøgler i tabellen '%-.192s'"
+ nla "Kan niet schrijven, dubbele zoeksleutel in tabel '%-.192s'"
+ eng "Can't write; duplicate key in table '%-.192s'"
+ est "Ei saa kirjutada, korduv võti tabelis '%-.192s'"
+ fre "Ecriture impossible, doublon dans une clé de la table '%-.192s'"
+ ger "Kann nicht speichern, Grund: doppelter Schlüssel in Tabelle '%-.192s'"
+ greek "Δεν είναι δυνατή η καταχώÏηση, η τιμή υπάÏχει ήδη στον πίνακα '%-.192s'"
+ hun "Irasi hiba, duplikalt kulcs a '%-.192s' tablaban."
+ ita "Scrittura impossibile: chiave duplicata nella tabella '%-.192s'"
+ jpn "書ãè¾¼ã‚ã¾ã›ã‚“。表 '%-.192s' ã«é‡è¤‡ã™ã‚‹ã‚­ãƒ¼ãŒã‚りã¾ã™ã€‚"
+ kor "기ë¡í•  수 ì—†ì니다., í…Œì´ë¸” '%-.192s'ì—서 중복 키"
+ nor "Kan ikke skrive, flere like nøkler i tabellen '%-.192s'"
+ norwegian-ny "Kan ikkje skrive, flere like nyklar i tabellen '%-.192s'"
+ pol "Nie można zapisać, powtórzone klucze w tabeli '%-.192s'"
+ por "Não pode gravar. Chave duplicada na tabela '%-.192s'"
+ rum "Nu pot sa scriu (can't write), cheie duplicata in tabela '%-.192s'"
+ rus "Ðевозможно произвеÑти запиÑÑŒ, дублирующийÑÑ ÐºÐ»ÑŽÑ‡ в таблице '%-.192s'"
+ serbian "Ne mogu da piÅ¡em poÅ¡to postoji duplirani kljuÄ u tabeli '%-.192s'"
+ slo "Nemôžem zapísaÅ¥, duplikát kľúÄa v tabuľke '%-.192s'"
+ spa "No puedo escribir, clave duplicada en la tabla '%-.192s'"
+ swe "Kan inte skriva, dubbel söknyckel i register '%-.192s'"
+ ukr "Ðе можу запиÑати, дублюючийÑÑ ÐºÐ»ÑŽÑ‡ в таблиці '%-.192s'"
+ER_ERROR_ON_CLOSE
+ cze "Chyba při zavírání '%-.192s' (chybový kód: %M)"
+ dan "Fejl ved lukning af '%-.192s' (Fejlkode: %M)"
+ nla "Fout bij het sluiten van '%-.192s' (Errcode: %M)"
+ eng "Error on close of '%-.192s' (errno: %M)"
+ est "Viga faili '%-.192s' sulgemisel (veakood: %M)"
+ fre "Erreur a la fermeture de '%-.192s' (Errcode: %M)"
+ ger "Fehler beim Schließen von '%-.192s' (Fehler: %M)"
+ greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κλείνοντας το '%-.192s' (κωδικός λάθους: %M)"
+ hun "Hiba a(z) '%-.192s' zarasakor. (hibakod: %M)"
+ ita "Errore durante la chiusura di '%-.192s' (errno: %M)"
+ jpn "'%-.192s' ã®ã‚¯ãƒ­ãƒ¼ã‚ºæ™‚エラー (エラー番å·: %M)"
+ kor "'%-.192s'닫는 중 ì—러 (ì—러번호: %M)"
+ nor "Feil ved lukking av '%-.192s' (Feilkode: %M)"
+ norwegian-ny "Feil ved lukking av '%-.192s' (Feilkode: %M)"
+ pol "Bł?d podczas zamykania '%-.192s' (Kod błędu: %M)"
+ por "Erro ao fechar '%-.192s' (erro no. %M)"
+ rum "Eroare inchizind '%-.192s' (errno: %M)"
+ rus "Ошибка при закрытии '%-.192s' (ошибка: %M)"
+ serbian "Greška pri zatvaranju '%-.192s' (errno: %M)"
+ slo "Chyba pri zatváraní '%-.192s' (chybový kód: %M)"
+ spa "Error en el cierre de '%-.192s' (Error: %M)"
+ swe "Fick fel vid stängning av '%-.192s' (Felkod: %M)"
+ ukr "Ðе можу закрити '%-.192s' (помилка: %M)"
+ER_ERROR_ON_READ
+ cze "Chyba pÅ™i Ätení souboru '%-.200s' (chybový kód: %M)"
+ dan "Fejl ved læsning af '%-.200s' (Fejlkode: %M)"
+ nla "Fout bij het lezen van file '%-.200s' (Errcode: %M)"
+ eng "Error reading file '%-.200s' (errno: %M)"
+ est "Viga faili '%-.200s' lugemisel (veakood: %M)"
+ fre "Erreur en lecture du fichier '%-.200s' (Errcode: %M)"
+ ger "Fehler beim Lesen der Datei '%-.200s' (Fehler: %M)"
+ greek "ΠÏόβλημα κατά την ανάγνωση του αÏχείου '%-.200s' (κωδικός λάθους: %M)"
+ hun "Hiba a '%-.200s'file olvasasakor. (hibakod: %M)"
+ ita "Errore durante la lettura del file '%-.200s' (errno: %M)"
+ jpn "ファイル '%-.200s' ã®èª­ã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼ (エラー番å·: %M)"
+ kor "'%-.200s'í™”ì¼ ì½ê¸° ì—러 (ì—러번호: %M)"
+ nor "Feil ved lesing av '%-.200s' (Feilkode: %M)"
+ norwegian-ny "Feil ved lesing av '%-.200s' (Feilkode: %M)"
+ pol "Bł?d podczas odczytu pliku '%-.200s' (Kod błędu: %M)"
+ por "Erro ao ler arquivo '%-.200s' (erro no. %M)"
+ rum "Eroare citind fisierul '%-.200s' (errno: %M)"
+ rus "Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° '%-.200s' (ошибка: %M)"
+ serbian "GreÅ¡ka pri Äitanju file-a '%-.200s' (errno: %M)"
+ slo "Chyba pri Äítaní súboru '%-.200s' (chybový kód: %M)"
+ spa "Error leyendo el fichero '%-.200s' (Error: %M)"
+ swe "Fick fel vid läsning av '%-.200s' (Felkod %M)"
+ ukr "Ðе можу прочитати файл '%-.200s' (помилка: %M)"
+ER_ERROR_ON_RENAME
+ cze "Chyba při přejmenování '%-.210s' na '%-.210s' (chybový kód: %M)"
+ dan "Fejl ved omdøbning af '%-.210s' til '%-.210s' (Fejlkode: %M)"
+ nla "Fout bij het hernoemen van '%-.210s' naar '%-.210s' (Errcode: %M)"
+ eng "Error on rename of '%-.210s' to '%-.210s' (errno: %M)"
+ est "Viga faili '%-.210s' ümbernimetamisel '%-.210s'-ks (veakood: %M)"
+ fre "Erreur en renommant '%-.210s' en '%-.210s' (Errcode: %M)"
+ ger "Fehler beim Umbenennen von '%-.210s' in '%-.210s' (Fehler: %M)"
+ greek "ΠÏόβλημα κατά την μετονομασία του αÏχείου '%-.210s' to '%-.210s' (κωδικός λάθους: %M)"
+ hun "Hiba a '%-.210s' file atnevezesekor '%-.210s'. (hibakod: %M)"
+ ita "Errore durante la rinominazione da '%-.210s' a '%-.210s' (errno: %M)"
+ jpn "'%-.210s' ã®åå‰ã‚’ '%-.210s' ã«å¤‰æ›´ã§ãã¾ã›ã‚“ (エラー番å·: %M)"
+ kor "'%-.210s'를 '%-.210s'로 ì´ë¦„ 변경중 ì—러 (ì—러번호: %M)"
+ nor "Feil ved omdøping av '%-.210s' til '%-.210s' (Feilkode: %M)"
+ norwegian-ny "Feil ved omdøyping av '%-.210s' til '%-.210s' (Feilkode: %M)"
+ pol "Bł?d podczas zmieniania nazwy '%-.210s' na '%-.210s' (Kod błędu: %M)"
+ por "Erro ao renomear '%-.210s' para '%-.210s' (erro no. %M)"
+ rum "Eroare incercind sa renumesc '%-.210s' in '%-.210s' (errno: %M)"
+ rus "Ошибка при переименовании '%-.210s' в '%-.210s' (ошибка: %M)"
+ serbian "Greška pri promeni imena '%-.210s' na '%-.210s' (errno: %M)"
+ slo "Chyba pri premenovávaní '%-.210s' na '%-.210s' (chybový kód: %M)"
+ spa "Error en el renombrado de '%-.210s' a '%-.210s' (Error: %M)"
+ swe "Kan inte byta namn från '%-.210s' till '%-.210s' (Felkod: %M)"
+ ukr "Ðе можу перейменувати '%-.210s' у '%-.210s' (помилка: %M)"
+ER_ERROR_ON_WRITE
+ cze "Chyba při zápisu do souboru '%-.200s' (chybový kód: %M)"
+ dan "Fejl ved skriving av filen '%-.200s' (Fejlkode: %M)"
+ nla "Fout bij het wegschrijven van file '%-.200s' (Errcode: %M)"
+ eng "Error writing file '%-.200s' (errno: %M)"
+ est "Viga faili '%-.200s' kirjutamisel (veakood: %M)"
+ fre "Erreur d'écriture du fichier '%-.200s' (Errcode: %M)"
+ ger "Fehler beim Speichern der Datei '%-.200s' (Fehler: %M)"
+ greek "ΠÏόβλημα κατά την αποθήκευση του αÏχείου '%-.200s' (κωδικός λάθους: %M)"
+ hun "Hiba a '%-.200s' file irasakor. (hibakod: %M)"
+ ita "Errore durante la scrittura del file '%-.200s' (errno: %M)"
+ jpn "ファイル '%-.200s' ã®æ›¸ãè¾¼ã¿ã‚¨ãƒ©ãƒ¼ (エラー番å·: %M)"
+ kor "'%-.200s'í™”ì¼ ê¸°ë¡ ì¤‘ ì—러 (ì—러번호: %M)"
+ nor "Feil ved skriving av fila '%-.200s' (Feilkode: %M)"
+ norwegian-ny "Feil ved skriving av fila '%-.200s' (Feilkode: %M)"
+ pol "Bł?d podczas zapisywania pliku '%-.200s' (Kod błędu: %M)"
+ por "Erro ao gravar arquivo '%-.200s' (erro no. %M)"
+ rum "Eroare scriind fisierul '%-.200s' (errno: %M)"
+ rus "Ошибка запиÑи в файл '%-.200s' (ошибка: %M)"
+ serbian "Greška pri upisu '%-.200s' (errno: %M)"
+ slo "Chyba pri zápise do súboru '%-.200s' (chybový kód: %M)"
+ spa "Error escribiendo el archivo '%-.200s' (Error: %M)"
+ swe "Fick fel vid skrivning till '%-.200s' (Felkod %M)"
+ ukr "Ðе можу запиÑати файл '%-.200s' (помилка: %M)"
+ER_FILE_USED
+ cze "'%-.192s' je zamÄen proti zmÄ›nám"
+ dan "'%-.192s' er låst mod opdateringer"
+ nla "'%-.192s' is geblokeerd tegen veranderingen"
+ eng "'%-.192s' is locked against change"
+ est "'%-.192s' on lukustatud muudatuste vastu"
+ fre "'%-.192s' est verrouillé contre les modifications"
+ ger "'%-.192s' ist für Änderungen gesperrt"
+ greek "'%-.192s' δεν επιτÏέπονται αλλαγές"
+ hun "'%-.192s' a valtoztatas ellen zarolva"
+ ita "'%-.192s' e` soggetto a lock contro i cambiamenti"
+ jpn "'%-.192s' ã¯ãƒ­ãƒƒã‚¯ã•れã¦ã„ã¾ã™ã€‚"
+ kor "'%-.192s'ê°€ 변경할 수 ì—†ë„ë¡ ìž ê²¨ìžˆì니다."
+ nor "'%-.192s' er låst mot oppdateringer"
+ norwegian-ny "'%-.192s' er låst mot oppdateringar"
+ pol "'%-.192s' jest zablokowany na wypadek zmian"
+ por "'%-.192s' está com travamento contra alterações"
+ rum "'%-.192s' este blocat pentry schimbari (loccked against change)"
+ rus "'%-.192s' заблокирован Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹"
+ serbian "'%-.192s' je zakljuÄan za upis"
+ slo "'%-.192s' je zamknutý proti zmenám"
+ spa "'%-.192s' esta bloqueado contra cambios"
+ swe "'%-.192s' är låst mot användning"
+ ukr "'%-.192s' заблокований на внеÑÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½"
+ER_FILSORT_ABORT
+ cze "Třídění přerušeno"
+ dan "Sortering afbrudt"
+ nla "Sorteren afgebroken"
+ eng "Sort aborted"
+ est "Sorteerimine katkestatud"
+ fre "Tri alphabétique abandonné"
+ ger "Sortiervorgang abgebrochen"
+ greek "Η διαδικασία ταξινόμισης ακυÏώθηκε"
+ hun "Sikertelen rendezes"
+ ita "Operazione di ordinamento abbandonata"
+ jpn "ソート処ç†ã‚’中断ã—ã¾ã—ãŸã€‚"
+ kor "소트가 중단ë˜ì—ˆìŠµë‹ˆë‹¤."
+ nor "Sortering avbrutt"
+ norwegian-ny "Sortering avbrote"
+ pol "Sortowanie przerwane"
+ por "Ordenação abortada"
+ rum "Sortare intrerupta"
+ rus "Сортировка прервана"
+ serbian "Sortiranje je prekinuto"
+ slo "Triedenie prerušené"
+ spa "Ordeancion cancelada"
+ swe "Sorteringen avbruten"
+ ukr "Ð¡Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÑ€Ð²Ð°Ð½Ð¾"
+ER_FORM_NOT_FOUND
+ cze "Pohled '%-.192s' pro '%-.192s' neexistuje"
+ dan "View '%-.192s' eksisterer ikke for '%-.192s'"
+ nla "View '%-.192s' bestaat niet voor '%-.192s'"
+ eng "View '%-.192s' doesn't exist for '%-.192s'"
+ est "Vaade '%-.192s' ei eksisteeri '%-.192s' jaoks"
+ fre "La vue (View) '%-.192s' n'existe pas pour '%-.192s'"
+ ger "View '%-.192s' existiert für '%-.192s' nicht"
+ greek "Το View '%-.192s' δεν υπάÏχει για '%-.192s'"
+ hun "A(z) '%-.192s' nezet nem letezik a(z) '%-.192s'-hoz"
+ ita "La view '%-.192s' non esiste per '%-.192s'"
+ jpn "ビュー '%-.192s' 㯠'%-.192s' ã«å­˜åœ¨ã—ã¾ã›ã‚“。"
+ kor "ë·° '%-.192s'ê°€ '%-.192s'ì—서는 존재하지 않ì니다."
+ nor "View '%-.192s' eksisterer ikke for '%-.192s'"
+ norwegian-ny "View '%-.192s' eksisterar ikkje for '%-.192s'"
+ pol "Widok '%-.192s' nie istnieje dla '%-.192s'"
+ por "Visão '%-.192s' não existe para '%-.192s'"
+ rum "View '%-.192s' nu exista pentru '%-.192s'"
+ rus "ПредÑтавление '%-.192s' не ÑущеÑтвует Ð´Ð»Ñ '%-.192s'"
+ serbian "View '%-.192s' ne postoji za '%-.192s'"
+ slo "Pohľad '%-.192s' neexistuje pre '%-.192s'"
+ spa "La vista '%-.192s' no existe para '%-.192s'"
+ swe "Formulär '%-.192s' finns inte i '%-.192s'"
+ ukr "ВиглÑд '%-.192s' не Ñ–Ñнує Ð´Ð»Ñ '%-.192s'"
+ER_GET_ERRNO
+ nla "Fout %M van tabel handler %s"
+ eng "Got error %M from storage engine %s"
+ fre "Reçu l'erreur %M du handler de la table %s"
+ ger "Fehler %M von Speicher-Engine %s"
+ greek "Ελήφθη μήνυμα λάθους %M από τον χειÏιστή πίνακα (table handler) %s"
+ ita "Rilevato l'errore %M dal gestore delle tabelle %s"
+ nor "Mottok feil %M fra tabell håndterer %s"
+ norwegian-ny "Mottok feil %M fra tabell handterar %s"
+ pol "Otrzymano bł?d %M z obsługi tabeli %s"
+ por "Obteve erro %M no manipulador de tabelas %s"
+ rum "Eroarea %M obtinuta din handlerul tabelei %s"
+ rus "Получена ошибка %M от обработчика таблиц %s"
+ spa "Error %M desde el manejador de la tabla %s"
+ swe "Fick felkod %M från databashanteraren %s"
+ ukr "Отримано помилку %M від деÑкриптора таблиці %s"
+ER_ILLEGAL_HA
+ eng "Storage engine %s of the table %`s.%`s doesn't have this option"
+ ger "Diese Option gibt es nicht in Speicher-Engine %s für %`s.%`s"
+ rus "Обработчик %s таблицы %`s.%`s не поддерживает Ñту возможноÑть"
+ ukr "ДеÑкриптор %s таблиці %`s.%`s не має цієї влаÑтивоÑті"
+ER_KEY_NOT_FOUND
+ cze "Nemohu najít záznam v '%-.192s'"
+ dan "Kan ikke finde posten i '%-.192s'"
+ nla "Kan record niet vinden in '%-.192s'"
+ eng "Can't find record in '%-.192s'"
+ est "Ei suuda leida kirjet '%-.192s'-s"
+ fre "Ne peut trouver l'enregistrement dans '%-.192s'"
+ ger "Kann Datensatz in '%-.192s' nicht finden"
+ greek "ΑδÏνατη η ανεÏÏεση εγγÏαφής στο '%-.192s'"
+ hun "Nem talalhato a rekord '%-.192s'-ben"
+ ita "Impossibile trovare il record in '%-.192s'"
+ jpn "'%-.192s' ã«ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
+ kor "'%-.192s'ì—서 레코드를 ì°¾ì„ ìˆ˜ ì—†ì니다."
+ nor "Kan ikke finne posten i '%-.192s'"
+ norwegian-ny "Kan ikkje finne posten i '%-.192s'"
+ pol "Nie można znaleĽć rekordu w '%-.192s'"
+ por "Não pode encontrar registro em '%-.192s'"
+ rum "Nu pot sa gasesc recordul in '%-.192s'"
+ rus "Ðевозможно найти запиÑÑŒ в '%-.192s'"
+ serbian "Ne mogu da pronađem slog u '%-.192s'"
+ slo "Nemôžem nájsť záznam v '%-.192s'"
+ spa "No puedo encontrar el registro en '%-.192s'"
+ swe "Hittar inte posten '%-.192s'"
+ ukr "Ðе можу запиÑати у '%-.192s'"
+ER_NOT_FORM_FILE
+ cze "Nesprávná informace v souboru '%-.200s'"
+ dan "Forkert indhold i: '%-.200s'"
+ nla "Verkeerde info in file: '%-.200s'"
+ eng "Incorrect information in file: '%-.200s'"
+ est "Vigane informatsioon failis '%-.200s'"
+ fre "Information erronnée dans le fichier: '%-.200s'"
+ ger "Falsche Information in Datei '%-.200s'"
+ greek "Λάθος πληÏοφοÏίες στο αÏχείο: '%-.200s'"
+ hun "Ervenytelen info a file-ban: '%-.200s'"
+ ita "Informazione errata nel file: '%-.200s'"
+ jpn "ファイル '%-.200s' å†…ã®æƒ…å ±ãŒä¸æ­£ã§ã™ã€‚"
+ kor "í™”ì¼ì˜ 부정확한 ì •ë³´: '%-.200s'"
+ nor "Feil informasjon i filen: '%-.200s'"
+ norwegian-ny "Feil informasjon i fila: '%-.200s'"
+ pol "Niewła?ciwa informacja w pliku: '%-.200s'"
+ por "Informação incorreta no arquivo '%-.200s'"
+ rum "Informatie incorecta in fisierul: '%-.200s'"
+ rus "ÐÐµÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð² файле '%-.200s'"
+ serbian "Pogrešna informacija u file-u: '%-.200s'"
+ slo "Nesprávna informácia v súbore: '%-.200s'"
+ spa "Informacion erronea en el archivo: '%-.200s'"
+ swe "Felaktig fil: '%-.200s'"
+ ukr "Хибна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ñƒ файлі: '%-.200s'"
+ER_NOT_KEYFILE
+ cze "Nesprávný klÃ­Ä pro tabulku '%-.200s'; pokuste se ho opravit"
+ dan "Fejl i indeksfilen til tabellen '%-.200s'; prøv at reparere den"
+ nla "Verkeerde zoeksleutel file voor tabel: '%-.200s'; probeer het te repareren"
+ eng "Incorrect key file for table '%-.200s'; try to repair it"
+ est "Tabeli '%-.200s' võtmefail on vigane; proovi seda parandada"
+ fre "Index corrompu dans la table: '%-.200s'; essayez de le réparer"
+ ger "Fehlerhafte Index-Datei für Tabelle '%-.200s'; versuche zu reparieren"
+ greek "Λάθος αÏχείο ταξινόμισης (key file) για τον πίνακα: '%-.200s'; ΠαÏακαλώ, διοÏθώστε το!"
+ hun "Ervenytelen kulcsfile a tablahoz: '%-.200s'; probalja kijavitani!"
+ ita "File chiave errato per la tabella : '%-.200s'; prova a riparalo"
+ jpn "表 '%-.200s' ã®ç´¢å¼•ファイル(key file)ã®å†…容ãŒä¸æ­£ã§ã™ã€‚修復を試行ã—ã¦ãã ã•ã„。"
+ kor "'%-.200s' í…Œì´ë¸”ì˜ ë¶€ì •í™•í•œ 키 존재. 수정하시오!"
+ nor "Tabellen '%-.200s' har feil i nøkkelfilen; forsøk å reparer den"
+ norwegian-ny "Tabellen '%-.200s' har feil i nykkelfila; prøv å reparere den"
+ pol "Niewła?ciwy plik kluczy dla tabeli: '%-.200s'; spróbuj go naprawić"
+ por "Arquivo de índice incorreto para tabela '%-.200s'; tente repará-lo"
+ rum "Cheia fisierului incorecta pentru tabela: '%-.200s'; incearca s-o repari"
+ rus "Ðекорректный индекÑный файл Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹: '%-.200s'. Попробуйте воÑÑтановить его"
+ serbian "Pogrešan key file za tabelu: '%-.200s'; probajte da ga ispravite"
+ slo "Nesprávny kÄ¾ÃºÄ pre tabuľku '%-.200s'; pokúste sa ho opraviÅ¥"
+ spa "Clave de archivo erronea para la tabla: '%-.200s'; intente repararlo"
+ swe "Fatalt fel vid hantering av register '%-.200s'; kör en reparation"
+ ukr "Хибний файл ключей Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ–: '%-.200s'; Спробуйте його відновити"
+ER_OLD_KEYFILE
+ cze "Starý klíÄový soubor pro '%-.192s'; opravte ho."
+ dan "Gammel indeksfil for tabellen '%-.192s'; reparer den"
+ nla "Oude zoeksleutel file voor tabel '%-.192s'; repareer het!"
+ eng "Old key file for table '%-.192s'; repair it!"
+ est "Tabeli '%-.192s' võtmefail on aegunud; paranda see!"
+ fre "Vieux fichier d'index pour la table '%-.192s'; réparez le!"
+ ger "Alte Index-Datei für Tabelle '%-.192s'. Bitte reparieren"
+ greek "Παλαιό αÏχείο ταξινόμισης (key file) για τον πίνακα '%-.192s'; ΠαÏακαλώ, διοÏθώστε το!"
+ hun "Regi kulcsfile a '%-.192s'tablahoz; probalja kijavitani!"
+ ita "File chiave vecchio per la tabella '%-.192s'; riparalo!"
+ jpn "表 '%-.192s' ã®ç´¢å¼•ファイル(key file)ã¯å¤ã„å½¢å¼ã§ã™ã€‚修復ã—ã¦ãã ã•ã„。"
+ kor "'%-.192s' í…Œì´ë¸”ì˜ ì´ì „ë²„ì ¼ì˜ í‚¤ 존재. 수정하시오!"
+ nor "Gammel nøkkelfil for tabellen '%-.192s'; reparer den!"
+ norwegian-ny "Gammel nykkelfil for tabellen '%-.192s'; reparer den!"
+ pol "Plik kluczy dla tabeli '%-.192s' jest starego typu; napraw go!"
+ por "Arquivo de índice desatualizado para tabela '%-.192s'; repare-o!"
+ rum "Cheia fisierului e veche pentru tabela '%-.192s'; repar-o!"
+ rus "Старый индекÑный файл Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹ '%-.192s'; отремонтируйте его!"
+ serbian "Zastareo key file za tabelu '%-.192s'; ispravite ga"
+ slo "Starý kľúÄový súbor pre '%-.192s'; opravte ho!"
+ spa "Clave de archivo antigua para la tabla '%-.192s'; reparelo!"
+ swe "Gammal nyckelfil '%-.192s'; reparera registret"
+ ukr "Старий файл ключей Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– '%-.192s'; Відновіть його!"
+ER_OPEN_AS_READONLY
+ cze "'%-.192s' je jen pro Ätení"
+ dan "'%-.192s' er skrivebeskyttet"
+ nla "'%-.192s' is alleen leesbaar"
+ eng "Table '%-.192s' is read only"
+ est "Tabel '%-.192s' on ainult lugemiseks"
+ fre "'%-.192s' est en lecture seulement"
+ ger "Tabelle '%-.192s' ist nur lesbar"
+ greek "'%-.192s' επιτÏέπεται μόνο η ανάγνωση"
+ hun "'%-.192s' irasvedett"
+ ita "'%-.192s' e` di sola lettura"
+ jpn "表 '%-.192s' ã¯èª­ã¿è¾¼ã¿å°‚用ã§ã™ã€‚"
+ kor "í…Œì´ë¸” '%-.192s'는 ì½ê¸°ì „ìš© 입니다."
+ nor "'%-.192s' er skrivebeskyttet"
+ norwegian-ny "'%-.192s' er skrivetryggja"
+ pol "'%-.192s' jest tylko do odczytu"
+ por "Tabela '%-.192s' é somente para leitura"
+ rum "Tabela '%-.192s' e read-only"
+ rus "Таблица '%-.192s' предназначена только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ"
+ serbian "Tabelu '%-.192s' je dozvoljeno samo Äitati"
+ slo "'%-.192s' is ÄítaÅ¥ only"
+ spa "'%-.192s' es de solo lectura"
+ swe "'%-.192s' är skyddad mot förändring"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ"
+ER_OUTOFMEMORY HY001 S1001
+ cze "Málo paměti. Přestartujte daemona a zkuste znovu (je potřeba %d bytů)"
+ dan "Ikke mere hukommelse. Genstart serveren og prøv igen (mangler %d bytes)"
+ nla "Geen geheugen meer. Herstart server en probeer opnieuw (%d bytes nodig)"
+ eng "Out of memory; restart server and try again (needed %d bytes)"
+ est "Mälu sai otsa. Proovi MariaDB uuesti käivitada (puudu jäi %d baiti)"
+ fre "Manque de mémoire. Redémarrez le démon et ré-essayez (%d octets nécessaires)"
+ ger "Kein Speicher vorhanden (%d Bytes benötigt). Bitte Server neu starten"
+ greek "Δεν υπάÏχει διαθέσιμη μνήμη. ΠÏοσπαθήστε πάλι, επανεκινώντας τη διαδικασία (demon) (χÏειάζονται %d bytes)"
+ hun "Nincs eleg memoria. Inditsa ujra a demont, es probalja ismet. (%d byte szukseges.)"
+ ita "Memoria esaurita. Fai ripartire il demone e riprova (richiesti %d bytes)"
+ jpn "メモリãŒä¸è¶³ã—ã¦ã„ã¾ã™ã€‚サーãƒãƒ¼ã‚’å†èµ·å‹•ã—ã¦ã¿ã¦ãã ã•ã„。(%d ãƒã‚¤ãƒˆã®å‰²ã‚Šå½“ã¦ã«å¤±æ•—)"
+ kor "Out of memory. ë°ëª¬ì„ 재 실행 후 다시 시작하시오 (needed %d bytes)"
+ nor "Ikke mer minne. Star på nytt tjenesten og prøv igjen (trengte %d byter)"
+ norwegian-ny "Ikkje meir minne. Start på nytt tenesten og prøv igjen (trengte %d bytar)"
+ pol "Zbyt mało pamięci. Uruchom ponownie demona i spróbuj ponownie (potrzeba %d bajtów)"
+ por "Sem memória. Reinicie o programa e tente novamente (necessita de %d bytes)"
+ rum "Out of memory. Porneste daemon-ul din nou si incearca inca o data (e nevoie de %d bytes)"
+ rus "ÐедоÑтаточно памÑти. ПерезапуÑтите Ñервер и попробуйте еще раз (нужно %d байт)"
+ serbian "Nema memorije. Restartujte MariaDB server i probajte ponovo (potrebno je %d byte-ova)"
+ slo "Málo pamäti. Reštartujte daemona a skúste znova (je potrebných %d bytov)"
+ spa "Memoria insuficiente. Reinicie el demonio e intentelo otra vez (necesita %d bytes)"
+ swe "Oväntat slut på minnet, starta om programmet och försök på nytt (Behövde %d bytes)"
+ ukr "Брак пам'Ñті. РеÑтартуйте Ñервер та Ñпробуйте знову (потрібно %d байтів)"
+ER_OUT_OF_SORTMEMORY HY001 S1001
+ cze "Málo paměti pro třídění. Zvyšte velikost třídícího bufferu"
+ dan "Ikke mere sorteringshukommelse. Øg sorteringshukommelse (sort buffer size) for serveren"
+ nla "Geen geheugen om te sorteren. Verhoog de server sort buffer size"
+ eng "Out of sort memory, consider increasing server sort buffer size"
+ est "Mälu sai sorteerimisel otsa. Suurenda MariaDB-i sorteerimispuhvrit"
+ fre "Manque de mémoire pour le tri. Augmentez-la."
+ ger "Kein Speicher zum Sortieren vorhanden. sort_buffer_size sollte im Server erhöht werden"
+ greek "Δεν υπάÏχει διαθέσιμη μνήμη για ταξινόμιση. Αυξήστε το sort buffer size για τη διαδικασία (demon)"
+ hun "Nincs eleg memoria a rendezeshez. Novelje a rendezo demon puffermeretet"
+ ita "Memoria per gli ordinamenti esaurita. Incrementare il 'sort_buffer' al demone"
+ jpn "ソートメモリãŒä¸è¶³ã—ã¦ã„ã¾ã™ã€‚ソートãƒãƒƒãƒ•ァサイズ(sort buffer size)ã®å¢—加を検討ã—ã¦ãã ã•ã„。"
+ kor "Out of sort memory. daemon sort bufferì˜ í¬ê¸°ë¥¼ ì¦ê°€ì‹œí‚¤ì„¸ìš”"
+ nor "Ikke mer sorteringsminne. Vurder å øke sorteringsminnet (sort buffer size) for tjenesten"
+ norwegian-ny "Ikkje meir sorteringsminne. Vurder å auke sorteringsminnet (sorteringsbuffer storleik) for tenesten"
+ pol "Zbyt mało pamięci dla sortowania. Zwiększ wielko?ć bufora demona dla sortowania"
+ por "Não há memória suficiente para ordenação. Considere aumentar o tamanho do retentor (buffer) de ordenação."
+ rum "Out of memory pentru sortare. Largeste marimea buffer-ului pentru sortare in daemon (sort buffer size)"
+ rus "ÐедоÑтаточно памÑти Ð´Ð»Ñ Ñортировки. Увеличьте размер буфера Ñортировки на Ñервере"
+ serbian "Nema memorije za sortiranje. Povećajte veliÄinu sort buffer-a MariaDB server-u"
+ slo "Málo pamäti pre triedenie, zvýšte veľkosť triediaceho bufferu"
+ spa "Memoria de ordenacion insuficiente. Incremente el tamano del buffer de ordenacion"
+ swe "Sorteringsbufferten räcker inte till. Kontrollera startparametrarna"
+ ukr "Брак пам'Ñті Ð´Ð»Ñ ÑортуваннÑ. Треба збільшити розмір буфера ÑÐ¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñƒ Ñервера"
+ER_UNEXPECTED_EOF
+ cze "NeoÄekávaný konec souboru pÅ™i Ätení '%-.192s' (chybový kód: %M)"
+ dan "Uventet afslutning på fil (eof) ved læsning af filen '%-.192s' (Fejlkode: %M)"
+ nla "Onverwachte eof gevonden tijdens het lezen van file '%-.192s' (Errcode: %M)"
+ eng "Unexpected EOF found when reading file '%-.192s' (errno: %M)"
+ est "Ootamatu faililõpumärgend faili '%-.192s' lugemisel (veakood: %M)"
+ fre "Fin de fichier inattendue en lisant '%-.192s' (Errcode: %M)"
+ ger "Unerwartetes Ende beim Lesen der Datei '%-.192s' (Fehler: %M)"
+ greek "Κατά τη διάÏκεια της ανάγνωσης, βÏέθηκε απÏοσδόκητα το τέλος του αÏχείου '%-.192s' (κωδικός λάθους: %M)"
+ hun "Varatlan filevege-jel a '%-.192s'olvasasakor. (hibakod: %M)"
+ ita "Fine del file inaspettata durante la lettura del file '%-.192s' (errno: %M)"
+ jpn "ファイル '%-.192s' を読ã¿è¾¼ã¿ä¸­ã«äºˆæœŸã›ãšãƒ•ァイルã®çµ‚端ã«é”ã—ã¾ã—ãŸã€‚(エラー番å·: %M)"
+ kor "'%-.192s' í™”ì¼ì„ ì½ëŠ” ë„중 ìž˜ëª»ëœ eofì„ ë°œê²¬ (ì—러번호: %M)"
+ nor "Uventet slutt på fil (eof) ved lesing av filen '%-.192s' (Feilkode: %M)"
+ norwegian-ny "Uventa slutt på fil (eof) ved lesing av fila '%-.192s' (Feilkode: %M)"
+ pol "Nieoczekiwany 'eof' napotkany podczas czytania z pliku '%-.192s' (Kod błędu: %M)"
+ por "Encontrado fim de arquivo inesperado ao ler arquivo '%-.192s' (erro no. %M)"
+ rum "Sfirsit de fisier neasteptat in citirea fisierului '%-.192s' (errno: %M)"
+ rus "Ðеожиданный конец файла '%-.192s' (ошибка: %M)"
+ serbian "NeoÄekivani kraj pri Äitanju file-a '%-.192s' (errno: %M)"
+ slo "NeoÄakávaný koniec súboru pri Äítaní '%-.192s' (chybový kód: %M)"
+ spa "Inesperado fin de ficheroU mientras leiamos el archivo '%-.192s' (Error: %M)"
+ swe "Oväntat filslut vid läsning från '%-.192s' (Felkod: %M)"
+ ukr "Хибний кінець файлу '%-.192s' (помилка: %M)"
+ER_CON_COUNT_ERROR 08004
+ cze "Příliš mnoho spojení"
+ dan "For mange forbindelser (connections)"
+ nla "Te veel verbindingen"
+ eng "Too many connections"
+ est "Liiga palju samaaegseid ühendusi"
+ fre "Trop de connexions"
+ ger "Zu viele Verbindungen"
+ greek "ΥπάÏχουν πολλές συνδέσεις..."
+ hun "Tul sok kapcsolat"
+ ita "Troppe connessioni"
+ jpn "接続ãŒå¤šã™ãŽã¾ã™ã€‚"
+ kor "너무 ë§Žì€ ì—°ê²°... max_connectionì„ ì¦ê°€ 시키시오..."
+ nor "For mange tilkoblinger (connections)"
+ norwegian-ny "For mange tilkoplingar (connections)"
+ pol "Zbyt wiele poł?czeń"
+ por "Excesso de conexões"
+ rum "Prea multe conectiuni"
+ rus "Слишком много Ñоединений"
+ serbian "Previše konekcija"
+ slo "Príliš mnoho spojení"
+ spa "Demasiadas conexiones"
+ swe "För många anslutningar"
+ ukr "Забагато з'єднань"
+ER_OUT_OF_RESOURCES
+ cze "Málo prostoru/paměti pro thread"
+ dan "Udgået for tråde/hukommelse"
+ nla "Geen thread geheugen meer; controleer of mysqld of andere processen al het beschikbare geheugen gebruikt. Zo niet, dan moet u wellicht 'ulimit' gebruiken om mysqld toe te laten meer geheugen te benutten, of u kunt extra swap ruimte toevoegen"
+ eng "Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use 'ulimit' to allow mysqld to use more memory or you can add more swap space"
+ est "Mälu sai otsa. Võimalik, et aitab swap-i lisamine või käsu 'ulimit' abil MariaDB-le rohkema mälu kasutamise lubamine"
+ fre "Manque de 'threads'/mémoire"
+ ger "Kein Speicher mehr vorhanden. Prüfen Sie, ob mysqld oder ein anderer Prozess den gesamten Speicher verbraucht. Wenn nicht, sollten Sie mit 'ulimit' dafür sorgen, dass mysqld mehr Speicher benutzen darf, oder mehr Swap-Speicher einrichten"
+ greek "ΠÏόβλημα με τη διαθέσιμη μνήμη (Out of thread space/memory)"
+ hun "Elfogyott a thread-memoria"
+ ita "Fine dello spazio/memoria per i thread"
+ jpn "メモリãŒä¸è¶³ã—ã¦ã„ã¾ã™ã€‚mysqld ã‚„ãã®ä»–ã®ãƒ—ロセスãŒãƒ¡ãƒ¢ãƒªãƒ¼ã‚’使ã„切ã£ã¦ã„ãªã„ã‹ç¢ºèªã—ã¦ä¸‹ã•ã„。メモリーを使ã„切ã£ã¦ã„ãªã„å ´åˆã€'ulimit'ã®è¨­å®šç­‰ã§ mysqld ã®ãƒ¡ãƒ¢ãƒªãƒ¼ä½¿ç”¨æœ€å¤§é‡ã‚’多ãã™ã‚‹ã‹ã€ã‚¹ãƒ¯ãƒƒãƒ—領域を増やã™å¿…è¦ãŒã‚ã‚‹ã‹ã‚‚ã—れã¾ã›ã‚“。"
+# This message failed to convert from euc-kr, skipped
+ nor "Tomt for tråd plass/minne"
+ norwegian-ny "Tomt for tråd plass/minne"
+ pol "Zbyt mało miejsca/pamięci dla w?tku"
+ por "Sem memória. Verifique se o mysqld ou algum outro processo está usando toda memória disponível. Se não, você pode ter que usar 'ulimit' para permitir ao mysqld usar mais memória ou você pode adicionar mais área de 'swap'"
+ rum "Out of memory; Verifica daca mysqld sau vreun alt proces foloseste toate memoria disponbila. Altfel, trebuie sa folosesi 'ulimit' ca sa permiti lui memoria disponbila. Altfel, trebuie sa folosesi 'ulimit' ca sa permiti lui mysqld sa foloseasca mai multa memorie ori adauga mai mult spatiu pentru swap (swap space)"
+ rus "ÐедоÑтаточно памÑти; удоÑтоверьтеÑÑŒ, что mysqld или какой-либо другой процеÑÑ Ð½Ðµ занимает вÑÑŽ доÑтупную памÑть. ЕÑли нет, то вы можете иÑпользовать ulimit, чтобы выделить Ð´Ð»Ñ mysqld больше памÑти, или увеличить объем файла подкачки"
+ serbian "Nema memorije; Proverite da li MariaDB server ili neki drugi proces koristi svu slobodnu memoriju. (UNIX: Ako ne, probajte da upotrebite 'ulimit' komandu da biste dozvolili daemon-u da koristi više memorije ili probajte da dodate više swap memorije)"
+ slo "Málo miesta-pamäti pre vlákno"
+ spa "Memoria/espacio de tranpaso insuficiente"
+ swe "Fick slut på minnet. Kontrollera om mysqld eller någon annan process använder allt tillgängligt minne. Om inte, försök använda 'ulimit' eller allokera mera swap"
+ ukr "Брак пам'Ñті; Перевірте чи mysqld або ÑкіÑÑŒ інші процеÑи викориÑтовують уÑÑŽ доÑтупну пам'Ñть. Як ні, то ви можете ÑкориÑтатиÑÑ 'ulimit', аби дозволити mysqld викориÑтовувати більше пам'Ñті або ви можете додати більше міÑÑ†Ñ Ð¿Ñ–Ð´ Ñвап"
+ER_BAD_HOST_ERROR 08S01
+ cze "Nemohu zjistit jméno stroje pro Vaši adresu"
+ dan "Kan ikke få værtsnavn for din adresse"
+ nla "Kan de hostname niet krijgen van uw adres"
+ eng "Can't get hostname for your address"
+ est "Ei suuda lahendada IP aadressi masina nimeks"
+ fre "Ne peut obtenir de hostname pour votre adresse"
+ ger "Kann Hostnamen für diese Adresse nicht erhalten"
+ greek "Δεν έγινε γνωστό το hostname για την address σας"
+ hun "A gepnev nem allapithato meg a cimbol"
+ ita "Impossibile risalire al nome dell'host dall'indirizzo (risoluzione inversa)"
+ jpn "IPアドレスã‹ã‚‰ãƒ›ã‚¹ãƒˆåを解決ã§ãã¾ã›ã‚“。"
+ kor "ë‹¹ì‹ ì˜ ì»´í“¨í„°ì˜ í˜¸ìŠ¤íŠ¸ì´ë¦„ì„ ì–»ì„ ìˆ˜ ì—†ì니다."
+ nor "Kan ikke få tak i vertsnavn for din adresse"
+ norwegian-ny "Kan ikkje få tak i vertsnavn for di adresse"
+ pol "Nie można otrzymać nazwy hosta dla twojego adresu"
+ por "Não pode obter nome do 'host' para seu endereço"
+ rum "Nu pot sa obtin hostname-ul adresei tale"
+ rus "Ðевозможно получить Ð¸Ð¼Ñ Ñ…Ð¾Ñта Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ адреÑа"
+ serbian "Ne mogu da dobijem ime host-a za vašu IP adresu"
+ slo "Nemôžem zistiť meno hostiteľa pre vašu adresu"
+ spa "No puedo obtener el nombre de maquina de tu direccion"
+ swe "Kan inte hitta 'hostname' för din adress"
+ ukr "Ðе можу визначити ім'Ñ Ñ…Ð¾Ñту Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— адреÑи"
+ER_HANDSHAKE_ERROR 08S01
+ cze "Chyba při ustavování spojení"
+ dan "Forkert håndtryk (handshake)"
+ nla "Verkeerde handshake"
+ eng "Bad handshake"
+ est "Väär handshake"
+ fre "Mauvais 'handshake'"
+ ger "Ungültiger Handshake"
+ greek "Η αναγνώÏιση (handshake) δεν έγινε σωστά"
+ hun "A kapcsolatfelvetel nem sikerult (Bad handshake)"
+ ita "Negoziazione impossibile"
+ jpn "ãƒãƒ³ãƒ‰ã‚·ã‚§ã‚¤ã‚¯ã‚¨ãƒ©ãƒ¼"
+ nor "Feil håndtrykk (handshake)"
+ norwegian-ny "Feil handtrykk (handshake)"
+ pol "Zły uchwyt(handshake)"
+ por "Negociação de acesso falhou"
+ rum "Prost inceput de conectie (bad handshake)"
+ rus "Ðекорректное приветÑтвие"
+ serbian "LoÅ¡ poÄetak komunikacije (handshake)"
+ slo "Chyba pri nadväzovaní spojenia"
+ spa "Protocolo erroneo"
+ swe "Fel vid initiering av kommunikationen med klienten"
+ ukr "Ðевірна уÑтановка зв'Ñзку"
+ER_DBACCESS_DENIED_ERROR 42000
+ cze "Přístup pro uživatele '%s'@'%s' k databázi '%-.192s' není povolen"
+ dan "Adgang nægtet bruger: '%s'@'%s' til databasen '%-.192s'"
+ nla "Toegang geweigerd voor gebruiker: '%s'@'%s' naar database '%-.192s'"
+ eng "Access denied for user '%s'@'%s' to database '%-.192s'"
+ jps "ユーザー '%s'@'%s' ã® '%-.192s' データベースã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’æ‹’å¦ã—ã¾ã™",
+ est "Ligipääs keelatud kasutajale '%s'@'%s' andmebaasile '%-.192s'"
+ fre "Accès refusé pour l'utilisateur: '%s'@'%s'. Base '%-.192s'"
+ ger "Benutzer '%s'@'%s' hat keine Zugriffsberechtigung für Datenbank '%-.192s'"
+ greek "Δεν επιτέÏεται η Ï€Ïόσβαση στο χÏήστη: '%s'@'%s' στη βάση δεδομένων '%-.192s'"
+ hun "A(z) '%s'@'%s' felhasznalo szamara tiltott eleres az '%-.192s' adabazishoz."
+ ita "Accesso non consentito per l'utente: '%s'@'%s' al database '%-.192s'"
+ jpn "ユーザー '%s'@'%s' ã® '%-.192s' データベースã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’æ‹’å¦ã—ã¾ã™"
+ kor "'%s'@'%s' 사용ìžëŠ” '%-.192s' ë°ì´íƒ€ë² ì´ìŠ¤ì— ì ‘ê·¼ì´ ê±°ë¶€ ë˜ì—ˆìŠµë‹ˆë‹¤."
+ nor "Tilgang nektet for bruker: '%s'@'%s' til databasen '%-.192s' nektet"
+ norwegian-ny "Tilgang ikkje tillate for brukar: '%s'@'%s' til databasen '%-.192s' nekta"
+ por "Acesso negado para o usuário '%s'@'%s' ao banco de dados '%-.192s'"
+ rum "Acces interzis pentru utilizatorul: '%s'@'%s' la baza de date '%-.192s'"
+ rus "Ð”Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%s'@'%s' доÑтуп к базе данных '%-.192s' закрыт"
+ serbian "Pristup je zabranjen korisniku '%s'@'%s' za bazu '%-.192s'"
+ slo "Zakázaný prístup pre užívateľa: '%s'@'%s' k databázi '%-.192s'"
+ spa "Acceso negado para usuario: '%s'@'%s' para la base de datos '%-.192s'"
+ swe "Användare '%s'@'%s' är ej berättigad att använda databasen %-.192s"
+ ukr "ДоÑтуп заборонено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача: '%s'@'%s' до бази данних '%-.192s'"
+ER_ACCESS_DENIED_ERROR 28000
+ cze "Přístup pro uživatele '%s'@'%s' (s heslem %s)"
+ dan "Adgang nægtet bruger: '%s'@'%s' (Bruger adgangskode: %s)"
+ nla "Toegang geweigerd voor gebruiker: '%s'@'%s' (Wachtwoord gebruikt: %s)"
+ eng "Access denied for user '%s'@'%s' (using password: %s)"
+ jps "ユーザー '%s'@'%s' ã‚’æ‹’å¦ã—ã¾ã™.uUsing password: %s)",
+ est "Ligipääs keelatud kasutajale '%s'@'%s' (kasutab parooli: %s)"
+ fre "Accès refusé pour l'utilisateur: '%s'@'%s' (mot de passe: %s)"
+ ger "Benutzer '%s'@'%s' hat keine Zugriffsberechtigung (verwendetes Passwort: %s)"
+ greek "Δεν επιτέÏεται η Ï€Ïόσβαση στο χÏήστη: '%s'@'%s' (χÏήση password: %s)"
+ hun "A(z) '%s'@'%s' felhasznalo szamara tiltott eleres. (Hasznalja a jelszot: %s)"
+ ita "Accesso non consentito per l'utente: '%s'@'%s' (Password: %s)"
+ jpn "ユーザー '%s'@'%s' ã‚’æ‹’å¦ã—ã¾ã™.uUsing password: %s)"
+ kor "'%s'@'%s' 사용ìžëŠ” ì ‘ê·¼ì´ ê±°ë¶€ ë˜ì—ˆìŠµë‹ˆë‹¤. (using password: %s)"
+ nor "Tilgang nektet for bruker: '%s'@'%s' (Bruker passord: %s)"
+ norwegian-ny "Tilgang ikke tillate for brukar: '%s'@'%s' (Brukar passord: %s)"
+ por "Acesso negado para o usuário '%s'@'%s' (senha usada: %s)"
+ rum "Acces interzis pentru utilizatorul: '%s'@'%s' (Folosind parola: %s)"
+ rus "ДоÑтуп закрыт Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%s'@'%s' (был иÑпользован пароль: %s)"
+ serbian "Pristup je zabranjen korisniku '%s'@'%s' (koristi lozinku: '%s')"
+ slo "Zakázaný prístup pre užívateľa: '%s'@'%s' (použitie hesla: %s)"
+ spa "Acceso negado para usuario: '%s'@'%s' (Usando clave: %s)"
+ swe "Användare '%s'@'%s' är ej berättigad att logga in (Använder lösen: %s)"
+ ukr "ДоÑтуп заборонено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача: '%s'@'%s' (ВикориÑтано пароль: %s)"
+ER_NO_DB_ERROR 3D000
+ cze "Nebyla vybrána žádná databáze"
+ dan "Ingen database valgt"
+ nla "Geen database geselecteerd"
+ eng "No database selected"
+ est "Andmebaasi ei ole valitud"
+ fre "Aucune base n'a été sélectionnée"
+ ger "Keine Datenbank ausgewählt"
+ greek "Δεν επιλέχθηκε βάση δεδομένων"
+ hun "Nincs kivalasztott adatbazis"
+ ita "Nessun database selezionato"
+ jpn "データベースãŒé¸æŠžã•れã¦ã„ã¾ã›ã‚“。"
+ kor "ì„ íƒëœ ë°ì´íƒ€ë² ì´ìŠ¤ê°€ 없습니다."
+ nor "Ingen database valgt"
+ norwegian-ny "Ingen database vald"
+ pol "Nie wybrano żadnej bazy danych"
+ por "Nenhum banco de dados foi selecionado"
+ rum "Nici o baza de data nu a fost selectata inca"
+ rus "База данных не выбрана"
+ serbian "Ni jedna baza nije selektovana"
+ slo "Nebola vybraná databáza"
+ spa "Base de datos no seleccionada"
+ swe "Ingen databas i användning"
+ ukr "Базу данних не вибрано"
+ER_UNKNOWN_COM_ERROR 08S01
+ cze "Neznámý příkaz"
+ dan "Ukendt kommando"
+ nla "Onbekend commando"
+ eng "Unknown command"
+ est "Tundmatu käsk"
+ fre "Commande inconnue"
+ ger "Unbekannter Befehl"
+ greek "Αγνωστη εντολή"
+ hun "Ervenytelen parancs"
+ ita "Comando sconosciuto"
+ jpn "䏿˜Žãªã‚³ãƒžãƒ³ãƒ‰ã§ã™ã€‚"
+ kor "명령어가 뭔지 모르겠어요..."
+ nor "Ukjent kommando"
+ norwegian-ny "Ukjent kommando"
+ pol "Nieznana komenda"
+ por "Comando desconhecido"
+ rum "Comanda invalida"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° коммуникационного протокола"
+ serbian "Nepoznata komanda"
+ slo "Neznámy príkaz"
+ spa "Comando desconocido"
+ swe "Okänt kommando"
+ ukr "Ðевідома команда"
+ER_BAD_NULL_ERROR 23000
+ cze "Sloupec '%-.192s' nemůže být null"
+ dan "Kolonne '%-.192s' kan ikke være NULL"
+ nla "Kolom '%-.192s' kan niet null zijn"
+ eng "Column '%-.192s' cannot be null"
+ est "Tulp '%-.192s' ei saa omada nullväärtust"
+ fre "Le champ '%-.192s' ne peut être vide (null)"
+ ger "Feld '%-.192s' darf nicht NULL sein"
+ greek "Το πεδίο '%-.192s' δεν μποÏεί να είναι κενό (null)"
+ hun "A(z) '%-.192s' oszlop erteke nem lehet nulla"
+ ita "La colonna '%-.192s' non puo` essere nulla"
+ jpn "列 '%-.192s' 㯠null ã«ã§ãã¾ã›ã‚“。"
+ kor "칼럼 '%-.192s'는 ë„(Null)ì´ ë˜ë©´ 안ë©ë‹ˆë‹¤. "
+ nor "Kolonne '%-.192s' kan ikke vere null"
+ norwegian-ny "Kolonne '%-.192s' kan ikkje vere null"
+ pol "Kolumna '%-.192s' nie może być null"
+ por "Coluna '%-.192s' não pode ser vazia"
+ rum "Coloana '%-.192s' nu poate sa fie null"
+ rus "Столбец '%-.192s' не может принимать величину NULL"
+ serbian "Kolona '%-.192s' ne može biti NULL"
+ slo "Pole '%-.192s' nemôže byť null"
+ spa "La columna '%-.192s' no puede ser nula"
+ swe "Kolumn '%-.192s' får inte vara NULL"
+ ukr "Стовбець '%-.192s' не може бути нульовим"
+ER_BAD_DB_ERROR 42000
+ cze "Neznámá databáze '%-.192s'"
+ dan "Ukendt database '%-.192s'"
+ nla "Onbekende database '%-.192s'"
+ eng "Unknown database '%-.192s'"
+ est "Tundmatu andmebaas '%-.192s'"
+ fre "Base '%-.192s' inconnue"
+ ger "Unbekannte Datenbank '%-.192s'"
+ greek "Αγνωστη βάση δεδομένων '%-.192s'"
+ hun "Ervenytelen adatbazis: '%-.192s'"
+ ita "Database '%-.192s' sconosciuto"
+ jpn "'%-.192s' ã¯ä¸æ˜Žãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã§ã™ã€‚"
+ kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'는 알수 ì—†ìŒ"
+ nor "Ukjent database '%-.192s'"
+ norwegian-ny "Ukjent database '%-.192s'"
+ pol "Nieznana baza danych '%-.192s'"
+ por "Banco de dados '%-.192s' desconhecido"
+ rum "Baza de data invalida '%-.192s'"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð±Ð°Ð·Ð° данных '%-.192s'"
+ serbian "Nepoznata baza '%-.192s'"
+ slo "Neznáma databáza '%-.192s'"
+ spa "Base de datos desconocida '%-.192s'"
+ swe "Okänd databas: '%-.192s'"
+ ukr "Ðевідома база данних '%-.192s'"
+ER_TABLE_EXISTS_ERROR 42S01
+ cze "Tabulka '%-.192s' již existuje"
+ dan "Tabellen '%-.192s' findes allerede"
+ nla "Tabel '%-.192s' bestaat al"
+ eng "Table '%-.192s' already exists"
+ est "Tabel '%-.192s' juba eksisteerib"
+ fre "La table '%-.192s' existe déjà"
+ ger "Tabelle '%-.192s' bereits vorhanden"
+ greek "Ο πίνακας '%-.192s' υπάÏχει ήδη"
+ hun "A(z) '%-.192s' tabla mar letezik"
+ ita "La tabella '%-.192s' esiste gia`"
+ jpn "表 '%-.192s' ã¯ã™ã§ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+ kor "í…Œì´ë¸” '%-.192s'는 ì´ë¯¸ 존재함"
+ nor "Tabellen '%-.192s' eksisterer allerede"
+ norwegian-ny "Tabellen '%-.192s' eksisterar allereide"
+ pol "Tabela '%-.192s' już istnieje"
+ por "Tabela '%-.192s' já existe"
+ rum "Tabela '%-.192s' exista deja"
+ rus "Таблица '%-.192s' уже ÑущеÑтвует"
+ serbian "Tabela '%-.192s' već postoji"
+ slo "Tabuľka '%-.192s' už existuje"
+ spa "La tabla '%-.192s' ya existe"
+ swe "Tabellen '%-.192s' finns redan"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' вже Ñ–Ñнує"
+ER_BAD_TABLE_ERROR 42S02
+ cze "Neznámá tabulka '%-.100s'"
+ dan "Ukendt tabel '%-.100s'"
+ nla "Onbekende tabel '%-.100s'"
+ eng "Unknown table '%-.100s'"
+ est "Tundmatu tabel '%-.100s'"
+ fre "Table '%-.100s' inconnue"
+ ger "Unbekannte Tabelle '%-.100s'"
+ greek "Αγνωστος πίνακας '%-.100s'"
+ hun "Ervenytelen tabla: '%-.100s'"
+ ita "Tabella '%-.100s' sconosciuta"
+ jpn "'%-.100s' ã¯ä¸æ˜Žãªè¡¨ã§ã™ã€‚"
+ kor "í…Œì´ë¸” '%-.100s'는 알수 ì—†ìŒ"
+ nor "Ukjent tabell '%-.100s'"
+ norwegian-ny "Ukjent tabell '%-.100s'"
+ pol "Nieznana tabela '%-.100s'"
+ por "Tabela '%-.100s' desconhecida"
+ rum "Tabela '%-.100s' este invalida"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° '%-.100s'"
+ serbian "Nepoznata tabela '%-.100s'"
+ slo "Neznáma tabuľka '%-.100s'"
+ spa "Tabla '%-.100s' desconocida"
+ swe "Okänd tabell '%-.100s'"
+ ukr "Ðевідома Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.100s'"
+ER_NON_UNIQ_ERROR 23000
+ cze "Sloupec '%-.192s' v %-.192s není zcela jasný"
+ dan "Felt: '%-.192s' i tabel %-.192s er ikke entydigt"
+ nla "Kolom: '%-.192s' in %-.192s is niet eenduidig"
+ eng "Column '%-.192s' in %-.192s is ambiguous"
+ est "Väli '%-.192s' %-.192s-s ei ole ühene"
+ fre "Champ: '%-.192s' dans %-.192s est ambigu"
+ ger "Feld '%-.192s' in %-.192s ist nicht eindeutig"
+ greek "Το πεδίο: '%-.192s' σε %-.192s δεν έχει καθοÏιστεί"
+ hun "A(z) '%-.192s' oszlop %-.192s-ben ketertelmu"
+ ita "Colonna: '%-.192s' di %-.192s e` ambigua"
+ jpn "列 '%-.192s' 㯠%-.192s å†…ã§æ›–昧ã§ã™ã€‚"
+ kor "칼럼: '%-.192s' in '%-.192s' ì´ ëª¨í˜¸í•¨"
+ nor "Felt: '%-.192s' i tabell %-.192s er ikke entydig"
+ norwegian-ny "Kolonne: '%-.192s' i tabell %-.192s er ikkje eintydig"
+ pol "Kolumna: '%-.192s' w %-.192s jest dwuznaczna"
+ por "Coluna '%-.192s' em '%-.192s' é ambígua"
+ rum "Coloana: '%-.192s' in %-.192s este ambigua"
+ rus "Столбец '%-.192s' в %-.192s задан неоднозначно"
+ serbian "Kolona '%-.192s' u %-.192s nije jedinstvena u kontekstu"
+ slo "Pole: '%-.192s' v %-.192s je nejasné"
+ spa "La columna: '%-.192s' en %-.192s es ambigua"
+ swe "Kolumn '%-.192s' i %-.192s är inte unik"
+ ukr "Стовбець '%-.192s' у %-.192s визначений неоднозначно"
+ER_SERVER_SHUTDOWN 08S01
+ cze "Probíhá ukonÄování práce serveru"
+ dan "Database nedlukning er i gang"
+ nla "Bezig met het stoppen van de server"
+ eng "Server shutdown in progress"
+ est "Serveri seiskamine käib"
+ fre "Arrêt du serveur en cours"
+ ger "Der Server wird heruntergefahren"
+ greek "ΕναÏξη διαδικασίας αποσÏνδεσης του εξυπηÏετητή (server shutdown)"
+ hun "A szerver leallitasa folyamatban"
+ ita "Shutdown del server in corso"
+ jpn "サーãƒãƒ¼ã‚’シャットダウン中ã§ã™ã€‚"
+ kor "Server가 셧다운 중입니다."
+ nor "Database nedkobling er i gang"
+ norwegian-ny "Tenar nedkopling er i gang"
+ pol "Trwa kończenie działania serwera"
+ por "'Shutdown' do servidor em andamento"
+ rum "Terminarea serverului este in desfasurare"
+ rus "Сервер находитÑÑ Ð² процеÑÑе оÑтановки"
+ serbian "Gašenje servera je u toku"
+ slo "Prebieha ukonÄovanie práce servera"
+ spa "Desconexion de servidor en proceso"
+ swe "Servern går nu ned"
+ ukr "ЗавершуєтьÑÑ Ñ€Ð°Ð±Ð¾Ñ‚Ð° Ñервера"
+ER_BAD_FIELD_ERROR 42S22 S0022
+ cze "Neznámý sloupec '%-.192s' v %-.192s"
+ dan "Ukendt kolonne '%-.192s' i tabel %-.192s"
+ nla "Onbekende kolom '%-.192s' in %-.192s"
+ eng "Unknown column '%-.192s' in '%-.192s'"
+ est "Tundmatu tulp '%-.192s' '%-.192s'-s"
+ fre "Champ '%-.192s' inconnu dans %-.192s"
+ ger "Unbekanntes Tabellenfeld '%-.192s' in %-.192s"
+ greek "Αγνωστο πεδίο '%-.192s' σε '%-.192s'"
+ hun "A(z) '%-.192s' oszlop ervenytelen '%-.192s'-ben"
+ ita "Colonna sconosciuta '%-.192s' in '%-.192s'"
+ jpn "列 '%-.192s' 㯠'%-.192s' ã«ã¯ã‚りã¾ã›ã‚“。"
+ kor "Unknown 칼럼 '%-.192s' in '%-.192s'"
+ nor "Ukjent kolonne '%-.192s' i tabell %-.192s"
+ norwegian-ny "Ukjent felt '%-.192s' i tabell %-.192s"
+ pol "Nieznana kolumna '%-.192s' w %-.192s"
+ por "Coluna '%-.192s' desconhecida em '%-.192s'"
+ rum "Coloana invalida '%-.192s' in '%-.192s'"
+ rus "ÐеизвеÑтный Ñтолбец '%-.192s' в '%-.192s'"
+ serbian "Nepoznata kolona '%-.192s' u '%-.192s'"
+ slo "Neznáme pole '%-.192s' v '%-.192s'"
+ spa "La columna '%-.192s' en %-.192s es desconocida"
+ swe "Okänd kolumn '%-.192s' i %-.192s"
+ ukr "Ðевідомий Ñтовбець '%-.192s' у '%-.192s'"
+ER_WRONG_FIELD_WITH_GROUP 42000 S1009
+ cze "Použité '%-.192s' nebylo v group by"
+ dan "Brugte '%-.192s' som ikke var i group by"
+ nla "Opdracht gebruikt '%-.192s' dat niet in de GROUP BY voorkomt"
+ eng "'%-.192s' isn't in GROUP BY"
+ est "'%-.192s' puudub GROUP BY klauslis"
+ fre "'%-.192s' n'est pas dans 'group by'"
+ ger "'%-.192s' ist nicht in GROUP BY vorhanden"
+ greek "ΧÏησιμοποιήθηκε '%-.192s' που δεν υπήÏχε στο group by"
+ hun "Used '%-.192s' with wasn't in group by"
+ ita "Usato '%-.192s' che non e` nel GROUP BY"
+ jpn "'%-.192s' ã¯GROUP BYå¥ã§æŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。"
+ kor "'%-.192s'ì€ GROUP BYì†ì— ì—†ìŒ"
+ nor "Brukte '%-.192s' som ikke var i group by"
+ norwegian-ny "Brukte '%-.192s' som ikkje var i group by"
+ pol "Użyto '%-.192s' bez umieszczenia w group by"
+ por "'%-.192s' não está em 'GROUP BY'"
+ rum "'%-.192s' nu exista in clauza GROUP BY"
+ rus "'%-.192s' не приÑутÑтвует в GROUP BY"
+ serbian "Entitet '%-.192s' nije naveden u komandi 'GROUP BY'"
+ slo "Použité '%-.192s' nebolo v 'group by'"
+ spa "Usado '%-.192s' el cual no esta group by"
+ swe "'%-.192s' finns inte i GROUP BY"
+ ukr "'%-.192s' не є у GROUP BY"
+ER_WRONG_GROUP_FIELD 42000 S1009
+ cze "Nemohu použít group na '%-.192s'"
+ dan "Kan ikke gruppere på '%-.192s'"
+ nla "Kan '%-.192s' niet groeperen"
+ eng "Can't group on '%-.192s'"
+ est "Ei saa grupeerida '%-.192s' järgi"
+ fre "Ne peut regrouper '%-.192s'"
+ ger "Gruppierung über '%-.192s' nicht möglich"
+ greek "ΑδÏνατη η ομαδοποίηση (group on) '%-.192s'"
+ hun "A group nem hasznalhato: '%-.192s'"
+ ita "Impossibile raggruppare per '%-.192s'"
+ jpn "'%-.192s' ã§ã®ã‚°ãƒ«ãƒ¼ãƒ—化ã¯ã§ãã¾ã›ã‚“。"
+ kor "'%-.192s'를 그룹할 수 ì—†ìŒ"
+ nor "Kan ikke gruppere på '%-.192s'"
+ norwegian-ny "Kan ikkje gruppere på '%-.192s'"
+ pol "Nie można grupować po '%-.192s'"
+ por "Não pode agrupar em '%-.192s'"
+ rum "Nu pot sa grupez pe (group on) '%-.192s'"
+ rus "Ðевозможно произвеÑти группировку по '%-.192s'"
+ serbian "Ne mogu da grupišem po '%-.192s'"
+ slo "Nemôžem použiť 'group' na '%-.192s'"
+ spa "No puedo agrupar por '%-.192s'"
+ swe "Kan inte använda GROUP BY med '%-.192s'"
+ ukr "Ðе можу групувати по '%-.192s'"
+ER_WRONG_SUM_SELECT 42000 S1009
+ cze "Příkaz obsahuje zároveň funkci sum a sloupce"
+ dan "Udtrykket har summer (sum) funktioner og kolonner i samme udtryk"
+ nla "Opdracht heeft totaliseer functies en kolommen in dezelfde opdracht"
+ eng "Statement has sum functions and columns in same statement"
+ est "Lauses on korraga nii tulbad kui summeerimisfunktsioonid"
+ fre "Vous demandez la fonction sum() et des champs dans la même commande"
+ ger "Die Verwendung von Summierungsfunktionen und Spalten im selben Befehl ist nicht erlaubt"
+ greek "Η διατÏπωση πεÏιέχει sum functions και columns στην ίδια διατÏπωση"
+ ita "Il comando ha una funzione SUM e una colonna non specificata nella GROUP BY"
+ jpn "集計関数ã¨é€šå¸¸ã®åˆ—ãŒåŒæ™‚ã«æŒ‡å®šã•れã¦ã„ã¾ã™ã€‚"
+ kor "Statement ê°€ sumê¸°ëŠ¥ì„ ë™ìž‘중ì´ê³  ì¹¼ëŸ¼ë„ ë™ì¼í•œ statement입니다."
+ nor "Uttrykket har summer (sum) funksjoner og kolonner i samme uttrykk"
+ norwegian-ny "Uttrykket har summer (sum) funksjoner og kolonner i same uttrykk"
+ pol "Zapytanie ma funkcje sumuj?ce i kolumny w tym samym zapytaniu"
+ por "Cláusula contém funções de soma e colunas juntas"
+ rum "Comanda are functii suma si coloane in aceeasi comanda"
+ rus "Выражение Ñодержит групповые функции и Ñтолбцы, но не включает GROUP BY. Ркак вы умудрилиÑÑŒ получить Ñто Ñообщение об ошибке?"
+ serbian "Izraz ima 'SUM' agregatnu funkciju i kolone u isto vreme"
+ slo "Príkaz obsahuje zároveň funkciu 'sum' a poľa"
+ spa "El estamento tiene funciones de suma y columnas en el mismo estamento"
+ swe "Kommandot har både sum functions och enkla funktioner"
+ ukr "У виразі викориÑтано підÑумовуючі функції порÑд з іменами Ñтовбців"
+ER_WRONG_VALUE_COUNT 21S01
+ cze "PoÄet sloupců neodpovídá zadané hodnotÄ›"
+ dan "Kolonne tæller stemmer ikke med antallet af værdier"
+ nla "Het aantal kolommen komt niet overeen met het aantal opgegeven waardes"
+ eng "Column count doesn't match value count"
+ est "Tulpade arv erineb väärtuste arvust"
+ ger "Die Anzahl der Spalten entspricht nicht der Anzahl der Werte"
+ greek "Το Column count δεν ταιÏιάζει με το value count"
+ hun "Az oszlopban levo ertek nem egyezik meg a szamitott ertekkel"
+ ita "Il numero delle colonne non e` uguale al numero dei valori"
+ jpn "列数ãŒå€¤ã®å€‹æ•°ã¨ä¸€è‡´ã—ã¾ã›ã‚“。"
+ kor "ì¹¼ëŸ¼ì˜ ì¹´ìš´íŠ¸ê°€ ê°’ì˜ ì¹´ìš´íŠ¸ì™€ ì¼ì¹˜í•˜ì§€ 않습니다."
+ nor "Felt telling stemmer verdi telling"
+ norwegian-ny "Kolonne telling stemmer verdi telling"
+ pol "Liczba kolumn nie odpowiada liczbie warto?ci"
+ por "Contagem de colunas não confere com a contagem de valores"
+ rum "Numarul de coloane nu este acelasi cu numarul valoarei"
+ rus "КоличеÑтво Ñтолбцов не Ñовпадает Ñ ÐºÐ¾Ð»Ð¸Ñ‡ÐµÑтвом значений"
+ serbian "Broj kolona ne odgovara broju vrednosti"
+ slo "PoÄet polí nezodpovedá zadanej hodnote"
+ spa "La columna con count no tiene valores para contar"
+ swe "Antalet kolumner motsvarar inte antalet värden"
+ ukr "КількіÑть Ñтовбців не Ñпівпадає з кількіÑтю значень"
+ER_TOO_LONG_IDENT 42000 S1009
+ cze "Jméno identifikátoru '%-.100s' je příliš dlouhé"
+ dan "Navnet '%-.100s' er for langt"
+ nla "Naam voor herkenning '%-.100s' is te lang"
+ eng "Identifier name '%-.100s' is too long"
+ est "Identifikaatori '%-.100s' nimi on liiga pikk"
+ fre "Le nom de l'identificateur '%-.100s' est trop long"
+ ger "Name des Bezeichners '%-.100s' ist zu lang"
+ greek "Το identifier name '%-.100s' είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿"
+ hun "A(z) '%-.100s' azonositonev tul hosszu."
+ ita "Il nome dell'identificatore '%-.100s' e` troppo lungo"
+ jpn "識別å­å '%-.100s' ã¯é•·ã™ãŽã¾ã™ã€‚"
+ kor "Identifier '%-.100s'는 너무 길군요."
+ nor "Identifikator '%-.100s' er for lang"
+ norwegian-ny "Identifikator '%-.100s' er for lang"
+ pol "Nazwa identyfikatora '%-.100s' jest zbyt długa"
+ por "Nome identificador '%-.100s' é longo demais"
+ rum "Numele indentificatorului '%-.100s' este prea lung"
+ rus "Слишком длинный идентификатор '%-.100s'"
+ serbian "Ime '%-.100s' je predugaÄko"
+ slo "Meno identifikátora '%-.100s' je príliš dlhé"
+ spa "El nombre del identificador '%-.100s' es demasiado grande"
+ swe "Kolumnnamn '%-.100s' är för långt"
+ ukr "Ім'Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ‚Ð¾Ñ€Ð° '%-.100s' задовге"
+ER_DUP_FIELDNAME 42S21 S1009
+ cze "Zdvojené jméno sloupce '%-.192s'"
+ dan "Feltnavnet '%-.192s' findes allerede"
+ nla "Dubbele kolom naam '%-.192s'"
+ eng "Duplicate column name '%-.192s'"
+ est "Kattuv tulba nimi '%-.192s'"
+ fre "Nom du champ '%-.192s' déjà utilisé"
+ ger "Doppelter Spaltenname: '%-.192s'"
+ greek "Επανάληψη column name '%-.192s'"
+ hun "Duplikalt oszlopazonosito: '%-.192s'"
+ ita "Nome colonna duplicato '%-.192s'"
+ jpn "列å '%-.192s' ã¯é‡è¤‡ã—ã¦ã¾ã™ã€‚"
+ kor "ì¤‘ë³µëœ ì¹¼ëŸ¼ ì´ë¦„: '%-.192s'"
+ nor "Feltnavnet '%-.192s' eksisterte fra før"
+ norwegian-ny "Feltnamnet '%-.192s' eksisterte frå før"
+ pol "Powtórzona nazwa kolumny '%-.192s'"
+ por "Nome da coluna '%-.192s' duplicado"
+ rum "Numele coloanei '%-.192s' e duplicat"
+ rus "ДублирующееÑÑ Ð¸Ð¼Ñ Ñтолбца '%-.192s'"
+ serbian "Duplirano ime kolone '%-.192s'"
+ slo "Opakované meno poľa '%-.192s'"
+ spa "Nombre de columna duplicado '%-.192s'"
+ swe "Kolumnnamn '%-.192s finns flera gånger"
+ ukr "Дублююче ім'Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s'"
+ER_DUP_KEYNAME 42000 S1009
+ cze "Zdvojené jméno klíÄe '%-.192s'"
+ dan "Indeksnavnet '%-.192s' findes allerede"
+ nla "Dubbele zoeksleutel naam '%-.192s'"
+ eng "Duplicate key name '%-.192s'"
+ est "Kattuv võtme nimi '%-.192s'"
+ fre "Nom de clef '%-.192s' déjà utilisé"
+ ger "Doppelter Name für Schlüssel vorhanden: '%-.192s'"
+ greek "Επανάληψη key name '%-.192s'"
+ hun "Duplikalt kulcsazonosito: '%-.192s'"
+ ita "Nome chiave duplicato '%-.192s'"
+ jpn "索引å '%-.192s' ã¯é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚"
+ kor "ì¤‘ë³µëœ í‚¤ ì´ë¦„ : '%-.192s'"
+ nor "Nøkkelnavnet '%-.192s' eksisterte fra før"
+ norwegian-ny "Nøkkelnamnet '%-.192s' eksisterte frå før"
+ pol "Powtórzony nazwa klucza '%-.192s'"
+ por "Nome da chave '%-.192s' duplicado"
+ rum "Numele cheiei '%-.192s' e duplicat"
+ rus "ДублирующееÑÑ Ð¸Ð¼Ñ ÐºÐ»ÑŽÑ‡Ð° '%-.192s'"
+ serbian "Duplirano ime kljuÄa '%-.192s'"
+ slo "Opakované meno kľúÄa '%-.192s'"
+ spa "Nombre de clave duplicado '%-.192s'"
+ swe "Nyckelnamn '%-.192s' finns flera gånger"
+ ukr "Дублююче ім'Ñ ÐºÐ»ÑŽÑ‡Ð° '%-.192s'"
+# When using this error code, please use ER(ER_DUP_ENTRY_WITH_KEY_NAME)
+# for the message string. See, for example, code in handler.cc.
+ER_DUP_ENTRY 23000 S1009
+ cze "Zdvojený klÃ­Ä '%-.192s' (Äíslo klíÄe %d)"
+ dan "Ens værdier '%-.192s' for indeks %d"
+ nla "Dubbele ingang '%-.192s' voor zoeksleutel %d"
+ eng "Duplicate entry '%-.192s' for key %d"
+ est "Kattuv väärtus '%-.192s' võtmele %d"
+ fre "Duplicata du champ '%-.192s' pour la clef %d"
+ ger "Doppelter Eintrag '%-.192s' für Schlüssel %d"
+ greek "Διπλή εγγÏαφή '%-.192s' για το κλειδί %d"
+ hun "Duplikalt bejegyzes '%-.192s' a %d kulcs szerint."
+ ita "Valore duplicato '%-.192s' per la chiave %d"
+ jpn "'%-.192s' ã¯ç´¢å¼• %d ã§é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚"
+ kor "ì¤‘ë³µëœ ìž…ë ¥ ê°’ '%-.192s': key %d"
+ nor "Like verdier '%-.192s' for nøkkel %d"
+ norwegian-ny "Like verdiar '%-.192s' for nykkel %d"
+ pol "Powtórzone wystąpienie '%-.192s' dla klucza %d"
+ por "Entrada '%-.192s' duplicada para a chave %d"
+ rum "Cimpul '%-.192s' e duplicat pentru cheia %d"
+ rus "ДублирующаÑÑÑ Ð·Ð°Ð¿Ð¸ÑÑŒ '%-.192s' по ключу %d"
+ serbian "Dupliran unos '%-.192s' za kljuÄ '%d'"
+ slo "Opakovaný kÄ¾ÃºÄ '%-.192s' (Äíslo kľúÄa %d)"
+ spa "Entrada duplicada '%-.192s' para la clave %d"
+ swe "Dublett '%-.192s' för nyckel %d"
+ ukr "Дублюючий Ð·Ð°Ð¿Ð¸Ñ '%-.192s' Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° %d"
+ER_WRONG_FIELD_SPEC 42000 S1009
+ cze "Chybná specifikace sloupce '%-.192s'"
+ dan "Forkert kolonnespecifikaton for felt '%-.192s'"
+ nla "Verkeerde kolom specificatie voor kolom '%-.192s'"
+ eng "Incorrect column specifier for column '%-.192s'"
+ est "Vigane tulba kirjeldus tulbale '%-.192s'"
+ fre "Mauvais paramètre de champ pour le champ '%-.192s'"
+ ger "Falsche Spezifikation für Feld '%-.192s'"
+ greek "Εσφαλμένο column specifier για το πεδίο '%-.192s'"
+ hun "Rossz oszlopazonosito: '%-.192s'"
+ ita "Specifica errata per la colonna '%-.192s'"
+ jpn "列 '%-.192s' ã®å®šç¾©ãŒä¸æ­£ã§ã™ã€‚"
+ kor "칼럼 '%-.192s'ì˜ ë¶€ì •í™•í•œ 칼럼 ì •ì˜ìž"
+ nor "Feil kolonne spesifikator for felt '%-.192s'"
+ norwegian-ny "Feil kolonne spesifikator for kolonne '%-.192s'"
+ pol "Błędna specyfikacja kolumny dla kolumny '%-.192s'"
+ por "Especificador de coluna incorreto para a coluna '%-.192s'"
+ rum "Specificandul coloanei '%-.192s' este incorect"
+ rus "Ðекорректный определитель Ñтолбца Ð´Ð»Ñ Ñтолбца '%-.192s'"
+ serbian "Pogrešan naziv kolone za kolonu '%-.192s'"
+ slo "Chyba v špecifikácii poľa '%-.192s'"
+ spa "Especificador de columna erroneo para la columna '%-.192s'"
+ swe "Felaktigt kolumntyp för kolumn '%-.192s'"
+ ukr "Ðевірний Ñпецифікатор ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s'"
+ER_PARSE_ERROR 42000 s1009
+ cze "%s blízko '%-.80s' na řádku %d"
+ dan "%s nær '%-.80s' på linje %d"
+ nla "%s bij '%-.80s' in regel %d"
+ eng "%s near '%-.80s' at line %d"
+ est "%s '%-.80s' ligidal real %d"
+ fre "%s près de '%-.80s' à la ligne %d"
+ ger "%s bei '%-.80s' in Zeile %d"
+ greek "%s πλησίον '%-.80s' στη γÏαμμή %d"
+ hun "A %s a '%-.80s'-hez kozeli a %d sorban"
+ ita "%s vicino a '%-.80s' linea %d"
+ jpn "%s : '%-.80s' 付近 %d 行目"
+ kor "'%s' ì—러 ê°™ì니다. ('%-.80s' 명령어 ë¼ì¸ %d)"
+ nor "%s nær '%-.80s' på linje %d"
+ norwegian-ny "%s attmed '%-.80s' på line %d"
+ pol "%s obok '%-.80s' w linii %d"
+ por "%s próximo a '%-.80s' na linha %d"
+ rum "%s linga '%-.80s' pe linia %d"
+ rus "%s около '%-.80s' на Ñтроке %d"
+ serbian "'%s' u iskazu '%-.80s' na liniji %d"
+ slo "%s blízko '%-.80s' na riadku %d"
+ spa "%s cerca '%-.80s' en la linea %d"
+ swe "%s nära '%-.80s' på rad %d"
+ ukr "%s Ð±Ñ–Ð»Ñ '%-.80s' в Ñтроці %d"
+ER_EMPTY_QUERY 42000
+ cze "Výsledek dotazu je prázdný"
+ dan "Forespørgsel var tom"
+ nla "Query was leeg"
+ eng "Query was empty"
+ est "Tühi päring"
+ fre "Query est vide"
+ ger "Leere Abfrage"
+ greek "Το εÏώτημα (query) που θέσατε ήταν κενό"
+ hun "Ures lekerdezes."
+ ita "La query e` vuota"
+ jpn "クエリãŒç©ºã§ã™ã€‚"
+ kor "쿼리결과가 없습니다."
+ nor "Forespørsel var tom"
+ norwegian-ny "Førespurnad var tom"
+ pol "Zapytanie było puste"
+ por "Consulta (query) estava vazia"
+ rum "Query-ul a fost gol"
+ rus "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð¾ÐºÐ°Ð·Ð°Ð»ÑÑ Ð¿ÑƒÑтым"
+ serbian "Upit je bio prazan"
+ slo "Výsledok požiadavky bol prázdny"
+ spa "La query estaba vacia"
+ swe "Frågan var tom"
+ ukr "ПуÑтий запит"
+ER_NONUNIQ_TABLE 42000 S1009
+ cze "NejednoznaÄná tabulka/alias: '%-.192s'"
+ dan "Tabellen/aliaset: '%-.192s' er ikke unikt"
+ nla "Niet unieke waarde tabel/alias: '%-.192s'"
+ eng "Not unique table/alias: '%-.192s'"
+ est "Ei ole unikaalne tabel/alias '%-.192s'"
+ fre "Table/alias: '%-.192s' non unique"
+ ger "Tabellenname/Alias '%-.192s' nicht eindeutig"
+ greek "ΑδÏνατη η ανεÏÏεση unique table/alias: '%-.192s'"
+ hun "Nem egyedi tabla/alias: '%-.192s'"
+ ita "Tabella/alias non unico: '%-.192s'"
+ jpn "表åï¼åˆ¥å '%-.192s' ã¯ä¸€æ„ã§ã¯ã‚りã¾ã›ã‚“。"
+ kor "Unique 하지 ì•Šì€ í…Œì´ë¸”/alias: '%-.192s'"
+ nor "Ikke unikt tabell/alias: '%-.192s'"
+ norwegian-ny "Ikkje unikt tabell/alias: '%-.192s'"
+ pol "Tabela/alias nie s? unikalne: '%-.192s'"
+ por "Tabela/alias '%-.192s' não única"
+ rum "Tabela/alias: '%-.192s' nu este unic"
+ rus "ПовторÑющаÑÑÑ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð°/пÑевдоним '%-.192s'"
+ serbian "Tabela ili alias nisu bili jedinstveni: '%-.192s'"
+ slo "Nie jednoznaÄná tabuľka/alias: '%-.192s'"
+ spa "Tabla/alias: '%-.192s' es no unica"
+ swe "Icke unikt tabell/alias: '%-.192s'"
+ ukr "Ðеунікальна таблицÑ/пÑевдонім: '%-.192s'"
+ER_INVALID_DEFAULT 42000 S1009
+ cze "Chybná defaultní hodnota pro '%-.192s'"
+ dan "Ugyldig standardværdi for '%-.192s'"
+ nla "Foutieve standaard waarde voor '%-.192s'"
+ eng "Invalid default value for '%-.192s'"
+ est "Vigane vaikeväärtus '%-.192s' jaoks"
+ fre "Valeur par défaut invalide pour '%-.192s'"
+ ger "Fehlerhafter Vorgabewert (DEFAULT) für '%-.192s'"
+ greek "Εσφαλμένη Ï€ÏοκαθοÏισμένη τιμή (default value) για '%-.192s'"
+ hun "Ervenytelen ertek: '%-.192s'"
+ ita "Valore di default non valido per '%-.192s'"
+ jpn "'%-.192s' ã¸ã®ãƒ‡ãƒ•ォルト値ãŒç„¡åйã§ã™ã€‚"
+ kor "'%-.192s'ì˜ ìœ íš¨í•˜ì§€ 못한 ë””í´íЏ ê°’ì„ ì‚¬ìš©í•˜ì…¨ìŠµë‹ˆë‹¤."
+ nor "Ugyldig standardverdi for '%-.192s'"
+ norwegian-ny "Ugyldig standardverdi for '%-.192s'"
+ pol "Niewła?ciwa warto?ć domy?lna dla '%-.192s'"
+ por "Valor padrão (default) inválido para '%-.192s'"
+ rum "Valoarea de default este invalida pentru '%-.192s'"
+ rus "Ðекорректное значение по умолчанию Ð´Ð»Ñ '%-.192s'"
+ serbian "Loša default vrednost za '%-.192s'"
+ slo "Chybná implicitná hodnota pre '%-.192s'"
+ spa "Valor por defecto invalido para '%-.192s'"
+ swe "Ogiltigt DEFAULT värde för '%-.192s'"
+ ukr "Ðевірне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾ замовчуванню Ð´Ð»Ñ '%-.192s'"
+ER_MULTIPLE_PRI_KEY 42000 S1009
+ cze "Definováno více primárních klíÄů"
+ dan "Flere primærnøgler specificeret"
+ nla "Meerdere primaire zoeksleutels gedefinieerd"
+ eng "Multiple primary key defined"
+ est "Mitut primaarset võtit ei saa olla"
+ fre "Plusieurs clefs primaires définies"
+ ger "Mehrere Primärschlüssel (PRIMARY KEY) definiert"
+ greek "ΠεÏισσότεÏα από ένα primary key οÏίστηκαν"
+ hun "Tobbszoros elsodleges kulcs definialas."
+ ita "Definite piu` chiave primarie"
+ jpn "PRIMARY KEY ãŒè¤‡æ•°å®šç¾©ã•れã¦ã„ã¾ã™ã€‚"
+ kor "Multiple primary keyê°€ ì •ì˜ë˜ì–´ 있슴"
+ nor "Fleire primærnøkle spesifisert"
+ norwegian-ny "Fleire primærnyklar spesifisert"
+ pol "Zdefiniowano wiele kluczy podstawowych"
+ por "Definida mais de uma chave primária"
+ rum "Chei primare definite de mai multe ori"
+ rus "Указано неÑколько первичных ключей"
+ serbian "Definisani viÅ¡estruki primarni kljuÄevi"
+ slo "Zadefinovaných viac primárnych kľúÄov"
+ spa "Multiples claves primarias definidas"
+ swe "Flera PRIMARY KEY använda"
+ ukr "Первинного ключа визначено неодноразово"
+ER_TOO_MANY_KEYS 42000 S1009
+ cze "Zadáno příliÅ¡ mnoho klíÄů, je povoleno nejvíce %d klíÄů"
+ dan "For mange nøgler specificeret. Kun %d nøgler må bruges"
+ nla "Teveel zoeksleutels gedefinieerd. Maximaal zijn %d zoeksleutels toegestaan"
+ eng "Too many keys specified; max %d keys allowed"
+ est "Liiga palju võtmeid. Maksimaalselt võib olla %d võtit"
+ fre "Trop de clefs sont définies. Maximum de %d clefs alloué"
+ ger "Zu viele Schlüssel definiert. Maximal %d Schlüssel erlaubt"
+ greek "ΠάÏα πολλά key οÏίσθηκαν. Το Ï€Î¿Î»Ï %d επιτÏέπονται"
+ hun "Tul sok kulcs. Maximum %d kulcs engedelyezett."
+ ita "Troppe chiavi. Sono ammesse max %d chiavi"
+ jpn "ç´¢å¼•ã®æ•°ãŒå¤šã™ãŽã¾ã™ã€‚最大 %d 個ã¾ã§ã§ã™ã€‚"
+ kor "너무 ë§Žì€ í‚¤ê°€ ì •ì˜ë˜ì–´ 있ì니다.. 최대 %dì˜ í‚¤ê°€ 가능함"
+ nor "For mange nøkler spesifisert. Maks %d nøkler tillatt"
+ norwegian-ny "For mange nykler spesifisert. Maks %d nyklar tillatt"
+ pol "Okre?lono zbyt wiele kluczy. Dostępnych jest maksymalnie %d kluczy"
+ por "Especificadas chaves demais. O máximo permitido são %d chaves"
+ rum "Prea multe chei. Numarul de chei maxim este %d"
+ rus "Указано Ñлишком много ключей. РазрешаетÑÑ ÑƒÐºÐ°Ð·Ñ‹Ð²Ð°Ñ‚ÑŒ не более %d ключей"
+ serbian "Navedeno je previÅ¡e kljuÄeva. Maksimum %d kljuÄeva je dozvoljeno"
+ slo "Zadaných ríliÅ¡ veľa kľúÄov. Najviac %d kľúÄov je povolených"
+ spa "Demasiadas claves primarias declaradas. Un maximo de %d claves son permitidas"
+ swe "För många nycklar använda. Man får ha högst %d nycklar"
+ ukr "Забагато ключів зазначено. Дозволено не більше %d ключів"
+ER_TOO_MANY_KEY_PARTS 42000 S1009
+ cze "Zadáno příliÅ¡ mnoho Äást klíÄů, je povoleno nejvíce %d Äástí"
+ dan "For mange nøgledele specificeret. Kun %d dele må bruges"
+ nla "Teveel zoeksleutel onderdelen gespecificeerd. Maximaal %d onderdelen toegestaan"
+ eng "Too many key parts specified; max %d parts allowed"
+ est "Võti koosneb liiga paljudest osadest. Maksimaalselt võib olla %d osa"
+ fre "Trop de parties specifiées dans la clef. Maximum de %d parties"
+ ger "Zu viele Teilschlüssel definiert. Maximal %d Teilschlüssel erlaubt"
+ greek "ΠάÏα πολλά key parts οÏίσθηκαν. Το Ï€Î¿Î»Ï %d επιτÏέπονται"
+ hun "Tul sok kulcsdarabot definialt. Maximum %d resz engedelyezett"
+ ita "Troppe parti di chiave specificate. Sono ammesse max %d parti"
+ jpn "索引ã®ã‚­ãƒ¼åˆ—指定ãŒå¤šã™ãŽã¾ã™ã€‚最大 %d 個ã¾ã§ã§ã™ã€‚"
+ kor "너무 ë§Žì€ í‚¤ 부분(parts)ë“¤ì´ ì •ì˜ë˜ì–´ 있ì니다.. 최대 %d ë¶€ë¶„ì´ ê°€ëŠ¥í•¨"
+ nor "For mange nøkkeldeler spesifisert. Maks %d deler tillatt"
+ norwegian-ny "For mange nykkeldelar spesifisert. Maks %d delar tillatt"
+ pol "Okre?lono zbyt wiele czę?ci klucza. Dostępnych jest maksymalnie %d czę?ci"
+ por "Especificadas partes de chave demais. O máximo permitido são %d partes"
+ rum "Prea multe chei. Numarul de chei maxim este %d"
+ rus "Указано Ñлишком много чаÑтей ÑоÑтавного ключа. РазрешаетÑÑ ÑƒÐºÐ°Ð·Ñ‹Ð²Ð°Ñ‚ÑŒ не более %d чаÑтей"
+ serbian "Navedeno je previÅ¡e delova kljuÄa. Maksimum %d delova je dozvoljeno"
+ slo "Zadaných ríliÅ¡ veľa Äastí kľúÄov. Je povolených najviac %d Äastí"
+ spa "Demasiadas partes de clave declaradas. Un maximo de %d partes son permitidas"
+ swe "För många nyckeldelar använda. Man får ha högst %d nyckeldelar"
+ ukr "Забагато чаÑтин ключа зазначено. Дозволено не більше %d чаÑтин"
+ER_TOO_LONG_KEY 42000 S1009
+ cze "Zadaný klÃ­Ä byl příliÅ¡ dlouhý, nejvÄ›tší délka klíÄe je %d"
+ dan "Specificeret nøgle var for lang. Maksimal nøglelængde er %d"
+ nla "Gespecificeerde zoeksleutel was te lang. De maximale lengte is %d"
+ eng "Specified key was too long; max key length is %d bytes"
+ est "Võti on liiga pikk. Maksimaalne võtmepikkus on %d"
+ fre "La clé est trop longue. Longueur maximale: %d"
+ ger "Schlüssel ist zu lang. Die maximale Schlüssellänge beträgt %d"
+ greek "Το κλειδί που οÏίσθηκε είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿. Το μέγιστο μήκος είναι %d"
+ hun "A megadott kulcs tul hosszu. Maximalis kulcshosszusag: %d"
+ ita "La chiave specificata e` troppo lunga. La max lunghezza della chiave e` %d"
+ jpn "索引ã®ã‚­ãƒ¼ãŒé•·ã™ãŽã¾ã™ã€‚最大 %d ãƒã‚¤ãƒˆã¾ã§ã§ã™ã€‚"
+ kor "ì •ì˜ëœ 키가 너무 ê¹ë‹ˆë‹¤. 최대 í‚¤ì˜ ê¸¸ì´ëŠ” %d입니다."
+ nor "Spesifisert nøkkel var for lang. Maks nøkkellengde er is %d"
+ norwegian-ny "Spesifisert nykkel var for lang. Maks nykkellengde er %d"
+ pol "Zdefinowany klucz jest zbyt długi. Maksymaln? długo?ci? klucza jest %d"
+ por "Chave especificada longa demais. O comprimento de chave máximo permitido é %d"
+ rum "Cheia specificata este prea lunga. Marimea maxima a unei chei este de %d"
+ rus "Указан Ñлишком длинный ключ. МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° ключа ÑоÑтавлÑет %d байт"
+ serbian "Navedeni kljuÄ je predug. Maksimalna dužina kljuÄa je %d"
+ slo "Zadaný kÄ¾ÃºÄ je príliÅ¡ dlhý, najväÄÅ¡ia dĺžka kľúÄa je %d"
+ spa "Declaracion de clave demasiado larga. La maxima longitud de clave es %d"
+ swe "För lång nyckel. Högsta tillåtna nyckellängd är %d"
+ ukr "Зазначений ключ задовгий. Ðайбільша довжина ключа %d байтів"
+ER_KEY_COLUMN_DOES_NOT_EXITS 42000 S1009
+ cze "KlíÄový sloupec '%-.192s' v tabulce neexistuje"
+ dan "Nøglefeltet '%-.192s' eksisterer ikke i tabellen"
+ nla "Zoeksleutel kolom '%-.192s' bestaat niet in tabel"
+ eng "Key column '%-.192s' doesn't exist in table"
+ est "Võtme tulp '%-.192s' puudub tabelis"
+ fre "La clé '%-.192s' n'existe pas dans la table"
+ ger "In der Tabelle gibt es kein Schlüsselfeld '%-.192s'"
+ greek "Το πεδίο κλειδί '%-.192s' δεν υπάÏχει στον πίνακα"
+ hun "A(z) '%-.192s'kulcsoszlop nem letezik a tablaban"
+ ita "La colonna chiave '%-.192s' non esiste nella tabella"
+ jpn "キー列 '%-.192s' ã¯è¡¨ã«ã‚りã¾ã›ã‚“。"
+ kor "Key 칼럼 '%-.192s'는 í…Œì´ë¸”ì— ì¡´ìž¬í•˜ì§€ 않습니다."
+ nor "Nøkkel felt '%-.192s' eksiterer ikke i tabellen"
+ norwegian-ny "Nykkel kolonne '%-.192s' eksiterar ikkje i tabellen"
+ pol "Kolumna '%-.192s' zdefiniowana w kluczu nie istnieje w tabeli"
+ por "Coluna chave '%-.192s' não existe na tabela"
+ rum "Coloana cheie '%-.192s' nu exista in tabela"
+ rus "Ключевой Ñтолбец '%-.192s' в таблице не ÑущеÑтвует"
+ serbian "KljuÄna kolona '%-.192s' ne postoji u tabeli"
+ slo "KľúÄový stĺpec '%-.192s' v tabuľke neexistuje"
+ spa "La columna clave '%-.192s' no existe en la tabla"
+ swe "Nyckelkolumn '%-.192s' finns inte"
+ ukr "Ключовий Ñтовбець '%-.192s' не Ñ–Ñнує у таблиці"
+ER_BLOB_USED_AS_KEY 42000 S1009
+ eng "BLOB column %`s can't be used in key specification in the %s table"
+ ger "BLOB-Feld %`s kann beim %s Tabellen nicht als Schlüssel verwendet werden"
+ rus "Столбец типа BLOB %`s не может быть иÑпользован как значение ключа в %s таблице"
+ ukr "BLOB Ñтовбець %`s не може бути викориÑтаний у визначенні ключа в %s таблиці"
+ER_TOO_BIG_FIELDLENGTH 42000 S1009
+ cze "Příliš velká délka sloupce '%-.192s' (nejvíce %lu). Použijte BLOB"
+ dan "For stor feltlængde for kolonne '%-.192s' (maks = %lu). Brug BLOB i stedet"
+ nla "Te grote kolomlengte voor '%-.192s' (max = %lu). Maak hiervoor gebruik van het type BLOB"
+ eng "Column length too big for column '%-.192s' (max = %lu); use BLOB or TEXT instead"
+ est "Tulba '%-.192s' pikkus on liiga pikk (maksimaalne pikkus: %lu). Kasuta BLOB väljatüüpi"
+ fre "Champ '%-.192s' trop long (max = %lu). Utilisez un BLOB"
+ ger "Feldlänge für Feld '%-.192s' zu groß (maximal %lu). BLOB- oder TEXT-Spaltentyp verwenden!"
+ greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿ μήκος για το πεδίο '%-.192s' (max = %lu). ΠαÏακαλώ χÏησιμοποιείστε τον Ï„Ïπο BLOB"
+ hun "A(z) '%-.192s' oszlop tul hosszu. (maximum = %lu). Hasznaljon BLOB tipust inkabb."
+ ita "La colonna '%-.192s' e` troppo grande (max=%lu). Utilizza un BLOB."
+ jpn "列 '%-.192s' ã®ã‚µã‚¤ã‚ºå®šç¾©ãŒå¤§ãã™ãŽã¾ã™ (最大 %lu ã¾ã§)。代ã‚り㫠BLOB ã¾ãŸã¯ TEXT を使用ã—ã¦ãã ã•ã„。"
+ kor "칼럼 '%-.192s'ì˜ ì¹¼ëŸ¼ 길ì´ê°€ 너무 ê¹ë‹ˆë‹¤ (최대 = %lu). ëŒ€ì‹ ì— BLOB를 사용하세요."
+ nor "For stor nøkkellengde for kolonne '%-.192s' (maks = %lu). Bruk BLOB istedenfor"
+ norwegian-ny "For stor nykkellengde for felt '%-.192s' (maks = %lu). Bruk BLOB istadenfor"
+ pol "Zbyt duża długo?ć kolumny '%-.192s' (maks. = %lu). W zamian użyj typu BLOB"
+ por "Comprimento da coluna '%-.192s' grande demais (max = %lu); use BLOB em seu lugar"
+ rum "Lungimea coloanei '%-.192s' este prea lunga (maximum = %lu). Foloseste BLOB mai bine"
+ rus "Слишком Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ð´Ð»Ð¸Ð½Ð° Ñтолбца '%-.192s' (макÑимум = %lu). ИÑпользуйте тип BLOB или TEXT вмеÑто текущего"
+ serbian "Previše podataka za kolonu '%-.192s' (maksimum je %lu). Upotrebite BLOB polje"
+ slo "Príliš veľká dĺžka pre pole '%-.192s' (maximum = %lu). Použite BLOB"
+ spa "Longitud de columna demasiado grande para la columna '%-.192s' (maximo = %lu).Usar BLOB en su lugar"
+ swe "För stor kolumnlängd angiven för '%-.192s' (max= %lu). Använd en BLOB instället"
+ ukr "Задовга довжина ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s' (max = %lu). ВикориÑтайте тип BLOB"
+ER_WRONG_AUTO_KEY 42000 S1009
+ cze "Můžete mít pouze jedno AUTO pole a to musí být definováno jako klíÄ"
+ dan "Der kan kun specificeres eet AUTO_INCREMENT-felt, og det skal være indekseret"
+ nla "Er kan slechts 1 autofield zijn en deze moet als zoeksleutel worden gedefinieerd."
+ eng "Incorrect table definition; there can be only one auto column and it must be defined as a key"
+ est "Vigane tabelikirjeldus; Tabelis tohib olla üks auto_increment tüüpi tulp ning see peab olema defineeritud võtmena"
+ fre "Un seul champ automatique est permis et il doit être indexé"
+ ger "Falsche Tabellendefinition. Es darf nur eine AUTO_INCREMENT-Spalte geben, und diese muss als Schlüssel definiert werden"
+ greek "ΜποÏεί να υπάÏχει μόνο ένα auto field και Ï€Ïέπει να έχει οÏισθεί σαν key"
+ hun "Csak egy auto mezo lehetseges, es azt kulcskent kell definialni."
+ ita "Puo` esserci solo un campo AUTO e deve essere definito come chiave"
+ jpn "䏿­£ãªè¡¨å®šç¾©ã§ã™ã€‚AUTO_INCREMENT列ã¯ï¼‘個ã¾ã§ã§ã€ç´¢å¼•を定義ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚"
+ kor "부정확한 í…Œì´ë¸” ì •ì˜; í…Œì´ë¸”ì€ í•˜ë‚˜ì˜ auto ì¹¼ëŸ¼ì´ ì¡´ìž¬í•˜ê³  키로 ì •ì˜ë˜ì–´ì ¸ì•¼ 합니다."
+ nor "Bare ett auto felt kan være definert som nøkkel."
+ norwegian-ny "Bare eitt auto felt kan være definert som nøkkel."
+ pol "W tabeli może być tylko jedno pole auto i musi ono być zdefiniowane jako klucz"
+ por "Definição incorreta de tabela. Somente é permitido um único campo auto-incrementado e ele tem que ser definido como chave"
+ rum "Definitia tabelei este incorecta; Nu pot fi mai mult de o singura coloana de tip auto si aceasta trebuie definita ca cheie"
+ rus "Ðекорректное определение таблицы: может ÑущеÑтвовать только один автоинкрементный Ñтолбец, и он должен быть определен как ключ"
+ serbian "PogreÅ¡na definicija tabele; U tabeli može postojati samo jedna 'AUTO' kolona i ona mora biti istovremeno definisana kao kolona kljuÄa"
+ slo "Môžete maÅ¥ iba jedno AUTO pole a to musí byÅ¥ definované ako kľúÄ"
+ spa "Puede ser solamente un campo automatico y este debe ser definido como una clave"
+ swe "Det får finnas endast ett AUTO_INCREMENT-fält och detta måste vara en nyckel"
+ ukr "Ðевірне Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ–; Може бути лише один автоматичний Ñтовбець, що повинен бути визначений Ñк ключ"
+ER_UNUSED_9
+ eng "You should never see it"
+ER_NORMAL_SHUTDOWN
+ cze "%s: normální ukonÄení\n"
+ dan "%s: Normal nedlukning\n"
+ nla "%s: Normaal afgesloten \n"
+ eng "%s: Normal shutdown\n"
+ est "%s: MariaDB lõpetas\n"
+ fre "%s: Arrêt normal du serveur\n"
+ ger "%s: Normal heruntergefahren\n"
+ greek "%s: Φυσιολογική διαδικασία shutdown\n"
+ hun "%s: Normal leallitas\n"
+ ita "%s: Shutdown normale\n"
+ jpn "%s: 通常シャットダウン\n"
+ kor "%s: ì •ìƒì ì¸ shutdown\n"
+ nor "%s: Normal avslutning\n"
+ norwegian-ny "%s: Normal nedkopling\n"
+ pol "%s: Standardowe zakończenie działania\n"
+ por "%s: 'Shutdown' normal\n"
+ rum "%s: Terminare normala\n"
+ rus "%s: ÐšÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð°Ñ Ð¾Ñтановка\n"
+ serbian "%s: Normalno gašenje\n"
+ slo "%s: normálne ukonÄenie\n"
+ spa "%s: Apagado normal\n"
+ swe "%s: Normal avslutning\n"
+ ukr "%s: Ðормальне завершеннÑ\n"
+ER_GOT_SIGNAL
+ cze "%s: pÅ™ijat signal %d, konÄím\n"
+ dan "%s: Fangede signal %d. Afslutter!!\n"
+ nla "%s: Signaal %d. Systeem breekt af!\n"
+ eng "%s: Got signal %d. Aborting!\n"
+ est "%s: sain signaali %d. Lõpetan!\n"
+ fre "%s: Reçu le signal %d. Abandonne!\n"
+ ger "%s: Signal %d erhalten. Abbruch!\n"
+ greek "%s: Ελήφθη το μήνυμα %d. Η διαδικασία εγκαταλείπεται!\n"
+ hun "%s: %d jelzes. Megszakitva!\n"
+ ita "%s: Ricevuto segnale %d. Interruzione!\n"
+ jpn "%s: シグナル %d ã‚’å—ä¿¡ã—ã¾ã—ãŸã€‚強制終了ã—ã¾ã™ï¼\n"
+ kor "%s: %d 신호가 들어왔ìŒ. 중지!\n"
+ nor "%s: Oppdaget signal %d. Avslutter!\n"
+ norwegian-ny "%s: Oppdaga signal %d. Avsluttar!\n"
+ pol "%s: Otrzymano sygnał %d. Kończenie działania!\n"
+ por "%s: Obteve sinal %d. Abortando!\n"
+ rum "%s: Semnal %d obtinut. Aborting!\n"
+ rus "%s: Получен Ñигнал %d. Прекращаем!\n"
+ serbian "%s: Dobio signal %d. Prekidam!\n"
+ slo "%s: prijatý signál %d, ukonÄenie (Abort)!\n"
+ spa "%s: Recibiendo signal %d. Abortando!\n"
+ swe "%s: Fick signal %d. Avslutar!\n"
+ ukr "%s: Отримано Ñигнал %d. ПерериваюÑÑŒ!\n"
+ER_SHUTDOWN_COMPLETE
+ cze "%s: ukonÄení práce hotovo\n"
+ dan "%s: Server lukket\n"
+ nla "%s: Afsluiten afgerond\n"
+ eng "%s: Shutdown complete\n"
+ est "%s: Lõpp\n"
+ fre "%s: Arrêt du serveur terminé\n"
+ ger "%s: Herunterfahren beendet\n"
+ greek "%s: Η διαδικασία Shutdown ολοκληÏώθηκε\n"
+ hun "%s: A leallitas kesz\n"
+ ita "%s: Shutdown completato\n"
+ jpn "%s: シャットダウン完了\n"
+ kor "%s: Shutdown ì´ ì™„ë£Œë¨!\n"
+ nor "%s: Avslutning komplett\n"
+ norwegian-ny "%s: Nedkopling komplett\n"
+ pol "%s: Zakończenie działania wykonane\n"
+ por "%s: 'Shutdown' completo\n"
+ rum "%s: Terminare completa\n"
+ rus "%s: ОÑтановка завершена\n"
+ serbian "%s: Gašenje završeno\n"
+ slo "%s: práca ukonÄená\n"
+ spa "%s: Apagado completado\n"
+ swe "%s: Avslutning klar\n"
+ ukr "%s: Роботу завершено\n"
+ER_FORCING_CLOSE 08S01
+ cze "%s: násilné uzavření threadu %ld uživatele '%-.48s'\n"
+ dan "%s: Forceret nedlukning af tråd: %ld bruger: '%-.48s'\n"
+ nla "%s: Afsluiten afgedwongen van thread %ld gebruiker: '%-.48s'\n"
+ eng "%s: Forcing close of thread %ld user: '%-.48s'\n"
+ est "%s: Sulgen jõuga lõime %ld kasutaja: '%-.48s'\n"
+ fre "%s: Arrêt forcé de la tâche (thread) %ld utilisateur: '%-.48s'\n"
+ ger "%s: Thread %ld zwangsweise beendet. Benutzer: '%-.48s'\n"
+ greek "%s: Το thread θα κλείσει %ld user: '%-.48s'\n"
+ hun "%s: A(z) %ld thread kenyszeritett zarasa. Felhasznalo: '%-.48s'\n"
+ ita "%s: Forzata la chiusura del thread %ld utente: '%-.48s'\n"
+ jpn "%s: スレッド %ld を強制終了ã—ã¾ã™ (ユーザー: '%-.48s')\n"
+ kor "%s: thread %ldì˜ ê°•ì œ 종료 user: '%-.48s'\n"
+ nor "%s: Påtvinget avslutning av tråd %ld bruker: '%-.48s'\n"
+ norwegian-ny "%s: Påtvinga avslutning av tråd %ld brukar: '%-.48s'\n"
+ pol "%s: Wymuszenie zamknięcia w?tku %ld użytkownik: '%-.48s'\n"
+ por "%s: Forçando finalização da 'thread' %ld - usuário '%-.48s'\n"
+ rum "%s: Terminare fortata a thread-ului %ld utilizatorului: '%-.48s'\n"
+ rus "%s: Принудительно закрываем поток %ld пользователÑ: '%-.48s'\n"
+ serbian "%s: Usiljeno gašenje thread-a %ld koji pripada korisniku: '%-.48s'\n"
+ slo "%s: násilné ukonÄenie vlákna %ld užívateľa '%-.48s'\n"
+ spa "%s: Forzando a cerrar el thread %ld usuario: '%-.48s'\n"
+ swe "%s: Stänger av tråd %ld; användare: '%-.48s'\n"
+ ukr "%s: ПриÑкорюю Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð³Ñ–Ð»ÐºÐ¸ %ld кориÑтувача: '%-.48s'\n"
+ER_IPSOCK_ERROR 08S01
+ cze "Nemohu vytvořit IP socket"
+ dan "Kan ikke oprette IP socket"
+ nla "Kan IP-socket niet openen"
+ eng "Can't create IP socket"
+ est "Ei suuda luua IP socketit"
+ fre "Ne peut créer la connexion IP (socket)"
+ ger "Kann IP-Socket nicht erzeugen"
+ greek "Δεν είναι δυνατή η δημιουÏγία IP socket"
+ hun "Az IP socket nem hozhato letre"
+ ita "Impossibile creare il socket IP"
+ jpn "IPソケットを作æˆã§ãã¾ã›ã‚“。"
+ kor "IP ì†Œì¼“ì„ ë§Œë“¤ì§€ 못했습니다."
+ nor "Kan ikke opprette IP socket"
+ norwegian-ny "Kan ikkje opprette IP socket"
+ pol "Nie można stworzyć socket'u IP"
+ por "Não pode criar o soquete IP"
+ rum "Nu pot crea IP socket"
+ rus "Ðевозможно Ñоздать IP-Ñокет"
+ serbian "Ne mogu da kreiram IP socket"
+ slo "Nemôžem vytvoriť IP socket"
+ spa "No puedo crear IP socket"
+ swe "Kan inte skapa IP-socket"
+ ukr "Ðе можу Ñтворити IP роз'єм"
+ER_NO_SUCH_INDEX 42S12 S1009
+ cze "Tabulka '%-.192s' nemá index odpovídající CREATE INDEX. Vytvořte tabulku znovu"
+ dan "Tabellen '%-.192s' har ikke den nøgle, som blev brugt i CREATE INDEX. Genopret tabellen"
+ nla "Tabel '%-.192s' heeft geen INDEX zoals deze gemaakt worden met CREATE INDEX. Maak de tabel opnieuw"
+ eng "Table '%-.192s' has no index like the one used in CREATE INDEX; recreate the table"
+ est "Tabelil '%-.192s' puuduvad võtmed. Loo tabel uuesti"
+ fre "La table '%-.192s' n'a pas d'index comme celle utilisée dans CREATE INDEX. Recréez la table"
+ ger "Tabelle '%-.192s' besitzt keinen wie den in CREATE INDEX verwendeten Index. Tabelle neu anlegen"
+ greek "Ο πίνακας '%-.192s' δεν έχει ευÏετήÏιο (index) σαν αυτό που χÏησιμοποιείτε στην CREATE INDEX. ΠαÏακαλώ, ξαναδημιουÏγήστε τον πίνακα"
+ hun "A(z) '%-.192s' tablahoz nincs meg a CREATE INDEX altal hasznalt index. Alakitsa at a tablat"
+ ita "La tabella '%-.192s' non ha nessun indice come quello specificatato dalla CREATE INDEX. Ricrea la tabella"
+ jpn "表 '%-.192s' ã«ä»¥å‰CREATE INDEXã§ä½œæˆã•れãŸç´¢å¼•ãŒã‚りã¾ã›ã‚“。表を作り直ã—ã¦ãã ã•ã„。"
+ kor "í…Œì´ë¸” '%-.192s'는 ì¸ë±ìŠ¤ë¥¼ 만들지 않았습니다. alter í…Œì´ë¸”ëª…ë ¹ì„ ì´ìš©í•˜ì—¬ í…Œì´ë¸”ì„ ìˆ˜ì •í•˜ì„¸ìš”..."
+ nor "Tabellen '%-.192s' har ingen index som den som er brukt i CREATE INDEX. Gjenopprett tabellen"
+ norwegian-ny "Tabellen '%-.192s' har ingen index som den som er brukt i CREATE INDEX. Oprett tabellen på nytt"
+ pol "Tabela '%-.192s' nie ma indeksu takiego jak w CREATE INDEX. Stwórz tabelę"
+ por "Tabela '%-.192s' não possui um índice como o usado em CREATE INDEX. Recrie a tabela"
+ rum "Tabela '%-.192s' nu are un index ca acela folosit in CREATE INDEX. Re-creeaza tabela"
+ rus "Ð’ таблице '%-.192s' нет такого индекÑа, как в CREATE INDEX. Создайте таблицу заново"
+ serbian "Tabela '%-.192s' nema isti indeks kao onaj upotrebljen pri komandi 'CREATE INDEX'. Napravite tabelu ponovo"
+ slo "Tabuľka '%-.192s' nemá index zodpovedajúci CREATE INDEX. Vytvorte tabulku znova"
+ spa "La tabla '%-.192s' no tiene indice como el usado en CREATE INDEX. Crea de nuevo la tabla"
+ swe "Tabellen '%-.192s' har inget index som motsvarar det angivna i CREATE INDEX. Skapa om tabellen"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' має індекÑ, що не Ñпівпадає з вказанним у CREATE INDEX. Створіть таблицю знову"
+ER_WRONG_FIELD_TERMINATORS 42000 S1009
+ cze "Argument separátoru položek nebyl oÄekáván. PÅ™eÄtÄ›te si manuál"
+ dan "Felt adskiller er ikke som forventet, se dokumentationen"
+ nla "De argumenten om velden te scheiden zijn anders dan verwacht. Raadpleeg de handleiding"
+ eng "Field separator argument is not what is expected; check the manual"
+ est "Väljade eraldaja erineb oodatust. Tutvu kasutajajuhendiga"
+ fre "Séparateur de champs inconnu. Vérifiez dans le manuel"
+ ger "Feldbegrenzer-Argument ist nicht in der erwarteten Form. Bitte im Handbuch nachlesen"
+ greek "Ο διαχωÏιστής πεδίων δεν είναι αυτός που αναμενόταν. ΠαÏακαλώ ανατÏέξτε στο manual"
+ hun "A mezoelvalaszto argumentumok nem egyeznek meg a varttal. Nezze meg a kezikonyvben!"
+ ita "L'argomento 'Field separator' non e` quello atteso. Controlla il manuale"
+ jpn "フィールド区切り文字ãŒäºˆæœŸã›ã¬ä½¿ã‚れ方をã—ã¦ã„ã¾ã™ã€‚マニュアルを確èªã—ã¦ä¸‹ã•ã„。"
+ kor "필드 êµ¬ë¶„ìž ì¸ìˆ˜ë“¤ì´ 완전하지 않습니다. ë©”ë‰´ì–¼ì„ ì°¾ì•„ 보세요."
+ nor "Felt skiller argumentene er ikke som forventet, se dokumentasjonen"
+ norwegian-ny "Felt skiljer argumenta er ikkje som venta, sjå dokumentasjonen"
+ pol "Nie oczekiwano separatora. SprawdĽ podręcznik"
+ por "Argumento separador de campos não é o esperado. Cheque o manual"
+ rum "Argumentul pentru separatorul de cimpuri este diferit de ce ma asteptam. Verifica manualul"
+ rus "Ðргумент Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸Ñ‚ÐµÐ»Ñ Ð¿Ð¾Ð»ÐµÐ¹ - не тот, который ожидалÑÑ. ОбращайтеÑÑŒ к документации"
+ serbian "Argument separatora polja nije ono Å¡to se oÄekivalo. Proverite uputstvo MariaDB server-a"
+ slo "Argument oddeľovaÄ polí nezodpovedá požiadavkám. Skontrolujte v manuáli"
+ spa "Los separadores de argumentos del campo no son los especificados. Comprueba el manual"
+ swe "Fältseparatorerna är vad som förväntades. Kontrollera mot manualen"
+ ukr "Хибний розділювач полів. Почитайте документацію"
+ER_BLOBS_AND_NO_TERMINATED 42000 S1009
+ cze "Není možné použít pevný rowlength s BLOBem. Použijte 'fields terminated by'."
+ dan "Man kan ikke bruge faste feltlængder med BLOB. Brug i stedet 'fields terminated by'."
+ nla "Bij het gebruik van BLOBs is het niet mogelijk om vaste rijlengte te gebruiken. Maak s.v.p. gebruik van 'fields terminated by'."
+ eng "You can't use fixed rowlength with BLOBs; please use 'fields terminated by'"
+ est "BLOB-tüüpi väljade olemasolul ei saa kasutada fikseeritud väljapikkust. Vajalik 'fields terminated by' määrang."
+ fre "Vous ne pouvez utiliser des lignes de longueur fixe avec des BLOBs. Utiliser 'fields terminated by'."
+ ger "Eine feste Zeilenlänge kann für BLOB-Felder nicht verwendet werden. Bitte 'fields terminated by' verwenden"
+ greek "Δεν μποÏείτε να χÏησιμοποιήσετε fixed rowlength σε BLOBs. ΠαÏακαλώ χÏησιμοποιείστε 'fields terminated by'."
+ hun "Fix hosszusagu BLOB-ok nem hasznalhatok. Hasznalja a 'mezoelvalaszto jelet' ."
+ ita "Non possono essere usate righe a lunghezza fissa con i BLOB. Usa 'FIELDS TERMINATED BY'."
+ jpn "BLOBã«ã¯å›ºå®šé•·ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒä½¿ç”¨ã§ãã¾ã›ã‚“。'FIELDS TERMINATED BY'å¥ã‚’使用ã—ã¦ä¸‹ã•ã„。"
+ kor "BLOB로는 고정길ì´ì˜ lowlength를 사용할 수 없습니다. 'fields terminated by'를 사용하세요."
+ nor "En kan ikke bruke faste feltlengder med BLOB. Vennlisgt bruk 'fields terminated by'."
+ norwegian-ny "Ein kan ikkje bruke faste feltlengder med BLOB. Vennlisgt bruk 'fields terminated by'."
+ pol "Nie można użyć stałej długo?ci wiersza z polami typu BLOB. Użyj 'fields terminated by'."
+ por "Você não pode usar comprimento de linha fixo com BLOBs. Por favor, use campos com comprimento limitado."
+ rum "Nu poti folosi lungime de cimp fix pentru BLOB-uri. Foloseste 'fields terminated by'."
+ rus "ФикÑированный размер запиÑи Ñ Ð¿Ð¾Ð»Ñми типа BLOB иÑпользовать нельзÑ, применÑйте 'fields terminated by'"
+ serbian "Ne možete koristiti fiksnu veliÄinu sloga kada imate BLOB polja. Molim koristite 'fields terminated by' opciju."
+ slo "Nie je možné použiť fixnú dĺžku s BLOBom. Použite 'fields terminated by'."
+ spa "No puedes usar longitudes de filas fijos con BLOBs. Por favor usa 'campos terminados por '."
+ swe "Man kan inte använda fast radlängd med blobs. Använd 'fields terminated by'"
+ ukr "Ðе можна викориÑтовувати Ñталу довжину Ñтроки з BLOB. ЗкориÑтайтеÑÑ 'fields terminated by'"
+ER_TEXTFILE_NOT_READABLE
+ cze "Soubor '%-.128s' musí být v adresáři databáze nebo Äitelný pro vÅ¡echny"
+ dan "Filen '%-.128s' skal være i database-folderen, eller kunne læses af alle"
+ nla "Het bestand '%-.128s' dient in de database directory voor the komen of leesbaar voor iedereen te zijn."
+ eng "The file '%-.128s' must be in the database directory or be readable by all"
+ est "Fail '%-.128s' peab asuma andmebaasi kataloogis või olema kõigile loetav"
+ fre "Le fichier '%-.128s' doit être dans le répertoire de la base et lisible par tous"
+ ger "Datei '%-.128s' muss im Datenbank-Verzeichnis vorhanden oder lesbar für alle sein"
+ greek "Το αÏχείο '%-.128s' Ï€Ïέπει να υπάÏχει στο database directory ή να μποÏεί να διαβαστεί από όλους"
+ hun "A(z) '%-.128s'-nak az adatbazis konyvtarban kell lennie, vagy mindenki szamara olvashatonak"
+ ita "Il file '%-.128s' deve essere nella directory del database e deve essere leggibile da tutti"
+ jpn "ファイル '%-.128s' ã¯ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ã‚ã‚‹ã‹ã€å…¨ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‹ã‚‰èª­ã‚ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚"
+ kor "'%-.128s' í™”ì¼ëŠ” ë°ì´íƒ€ë² ì´ìФ ë””ë ‰í† ë¦¬ì— ì¡´ìž¬í•˜ê±°ë‚˜ 모ë‘ì—게 ì½ê¸° 가능하여야 합니다."
+ nor "Filen '%-.128s' må være i database-katalogen for å være lesbar for alle"
+ norwegian-ny "Filen '%-.128s' må være i database-katalogen for å være lesbar for alle"
+ pol "Plik '%-.128s' musi znajdować sie w katalogu bazy danych lub mieć prawa czytania przez wszystkich"
+ por "Arquivo '%-.128s' tem que estar no diretório do banco de dados ou ter leitura possível para todos"
+ rum "Fisierul '%-.128s' trebuie sa fie in directorul bazei de data sau trebuie sa poata sa fie citit de catre toata lumea (verifica permisiile)"
+ rus "Файл '%-.128s' должен находитьÑÑ Ð² том же каталоге, что и база данных, или быть общедоÑтупным Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ"
+ serbian "File '%-.128s' mora biti u direktorijumu gde su file-ovi baze i mora imati odgovarajuća prava pristupa"
+ slo "Súbor '%-.128s' musí byÅ¥ v adresári databázy, alebo Äitateľný pre vÅ¡etkých"
+ spa "El archivo '%-.128s' debe estar en el directorio de la base de datos o ser de lectura por todos"
+ swe "Textfilen '%-.128s' måste finnas i databasbiblioteket eller vara läsbar för alla"
+ ukr "Файл '%-.128s' повинен бути у теці бази данних або мати вÑтановлене право на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð´Ð»Ñ ÑƒÑÑ–Ñ…"
+ER_FILE_EXISTS_ERROR
+ cze "Soubor '%-.200s' již existuje"
+ dan "Filen '%-.200s' eksisterer allerede"
+ nla "Het bestand '%-.200s' bestaat reeds"
+ eng "File '%-.200s' already exists"
+ est "Fail '%-.200s' juba eksisteerib"
+ fre "Le fichier '%-.200s' existe déjà"
+ ger "Datei '%-.200s' bereits vorhanden"
+ greek "Το αÏχείο '%-.200s' υπάÏχει ήδη"
+ hun "A '%-.200s' file mar letezik."
+ ita "Il file '%-.200s' esiste gia`"
+ jpn "ファイル '%-.200s' ã¯ã™ã§ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+ kor "'%-.200s' í™”ì¼ì€ ì´ë¯¸ 존재합니다."
+ nor "Filen '%-.200s' eksisterte allerede"
+ norwegian-ny "Filen '%-.200s' eksisterte allereide"
+ pol "Plik '%-.200s' już istnieje"
+ por "Arquivo '%-.200s' já existe"
+ rum "Fisierul '%-.200s' exista deja"
+ rus "Файл '%-.200s' уже ÑущеÑтвует"
+ serbian "File '%-.200s' već postoji"
+ slo "Súbor '%-.200s' už existuje"
+ spa "El archivo '%-.200s' ya existe"
+ swe "Filen '%-.200s' existerar redan"
+ ukr "Файл '%-.200s' вже Ñ–Ñнує"
+ER_LOAD_INFO
+ cze "Záznamů: %ld Vymazáno: %ld PÅ™eskoÄeno: %ld Varování: %ld"
+ dan "Poster: %ld Fjernet: %ld Sprunget over: %ld Advarsler: %ld"
+ nla "Records: %ld Verwijderd: %ld Overgeslagen: %ld Waarschuwingen: %ld"
+ eng "Records: %ld Deleted: %ld Skipped: %ld Warnings: %ld"
+ est "Kirjeid: %ld Kustutatud: %ld Vahele jäetud: %ld Hoiatusi: %ld"
+ fre "Enregistrements: %ld Effacés: %ld Non traités: %ld Avertissements: %ld"
+ ger "Datensätze: %ld Gelöscht: %ld Ausgelassen: %ld Warnungen: %ld"
+ greek "ΕγγÏαφές: %ld ΔιαγÏαφές: %ld ΠαÏεκάμφθησαν: %ld ΠÏοειδοποιήσεις: %ld"
+ hun "Rekordok: %ld Torolve: %ld Skipped: %ld Warnings: %ld"
+ ita "Records: %ld Cancellati: %ld Saltati: %ld Avvertimenti: %ld"
+ jpn "レコード数: %ld 削除: %ld スキップ: %ld 警告: %ld"
+ kor "레코드: %ld개 삭제: %ld개 스킵: %ld개 경고: %ld개"
+ nor "Poster: %ld Fjernet: %ld Hoppet over: %ld Advarsler: %ld"
+ norwegian-ny "Poster: %ld Fjerna: %ld Hoppa over: %ld Ã…tvaringar: %ld"
+ pol "Recordów: %ld Usuniętych: %ld Pominiętych: %ld Ostrzeżeń: %ld"
+ por "Registros: %ld - Deletados: %ld - Ignorados: %ld - Avisos: %ld"
+ rum "Recorduri: %ld Sterse: %ld Sarite (skipped): %ld Atentionari (warnings): %ld"
+ rus "ЗапиÑей: %ld Удалено: %ld Пропущено: %ld Предупреждений: %ld"
+ serbian "Slogova: %ld Izbrisano: %ld PreskoÄeno: %ld Upozorenja: %ld"
+ slo "Záznamov: %ld Zmazaných: %ld PreskoÄených: %ld Varovania: %ld"
+ spa "Registros: %ld Borrados: %ld Saltados: %ld Peligros: %ld"
+ swe "Rader: %ld Bortagna: %ld Dubletter: %ld Varningar: %ld"
+ ukr "ЗапиÑів: %ld Видалено: %ld Пропущено: %ld ЗаÑтережень: %ld"
+ER_ALTER_INFO
+ cze "Záznamů: %ld Zdvojených: %ld"
+ dan "Poster: %ld Ens: %ld"
+ nla "Records: %ld Dubbel: %ld"
+ eng "Records: %ld Duplicates: %ld"
+ est "Kirjeid: %ld Kattuvaid: %ld"
+ fre "Enregistrements: %ld Doublons: %ld"
+ ger "Datensätze: %ld Duplikate: %ld"
+ greek "ΕγγÏαφές: %ld Επαναλήψεις: %ld"
+ hun "Rekordok: %ld Duplikalva: %ld"
+ ita "Records: %ld Duplicati: %ld"
+ jpn "レコード数: %ld é‡è¤‡: %ld"
+ kor "레코드: %ld개 중복: %ld개"
+ nor "Poster: %ld Like: %ld"
+ norwegian-ny "Poster: %ld Like: %ld"
+ pol "Rekordów: %ld Duplikatów: %ld"
+ por "Registros: %ld - Duplicados: %ld"
+ rum "Recorduri: %ld Duplicate: %ld"
+ rus "ЗапиÑей: %ld Дубликатов: %ld"
+ serbian "Slogova: %ld Duplikata: %ld"
+ slo "Záznamov: %ld Opakovaných: %ld"
+ spa "Registros: %ld Duplicados: %ld"
+ swe "Rader: %ld Dubletter: %ld"
+ ukr "ЗапиÑів: %ld Дублікатів: %ld"
+ER_WRONG_SUB_KEY
+ cze "Chybná podÄást klíÄe -- není to Å™etÄ›zec nebo je delší než délka Äásti klíÄe"
+ dan "Forkert indeksdel. Den anvendte nøgledel er ikke en streng eller længden er større end nøglelængden"
+ nla "Foutief sub-gedeelte van de zoeksleutel. De gebruikte zoeksleutel is geen onderdeel van een string of of de gebruikte lengte is langer dan de zoeksleutel"
+ eng "Incorrect prefix key; the used key part isn't a string, the used length is longer than the key part, or the storage engine doesn't support unique prefix keys"
+ est "Vigane võtme osa. Kasutatud võtmeosa ei ole string tüüpi, määratud pikkus on pikem kui võtmeosa või tabelihandler ei toeta seda tüüpi võtmeid"
+ fre "Mauvaise sous-clef. Ce n'est pas un 'string' ou la longueur dépasse celle définie dans la clef"
+ ger "Falscher Unterteilschlüssel. Der verwendete Schlüsselteil ist entweder kein String, die verwendete Länge ist länger als der Teilschlüssel oder die Speicher-Engine unterstützt keine Unterteilschlüssel"
+ greek "Εσφαλμένο sub part key. Το χÏησιμοποιοÏμενο key part δεν είναι string ή το μήκος του είναι μεγαλÏτεÏο"
+ hun "Rossz alkulcs. A hasznalt kulcsresz nem karaktersorozat vagy hosszabb, mint a kulcsresz"
+ ita "Sotto-parte della chiave errata. La parte di chiave utilizzata non e` una stringa o la lunghezza e` maggiore della parte di chiave."
+ jpn "キーã®ãƒ—レフィックスãŒä¸æ­£ã§ã™ã€‚ã‚­ãƒ¼ãŒæ–‡å­—列ã§ã¯ãªã„ã‹ã€ãƒ—レフィックス長ãŒã‚­ãƒ¼ã‚ˆã‚Šã‚‚é•·ã„ã‹ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ãŒä¸€æ„索引ã®ãƒ—レフィックス指定をサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“。"
+ kor "부정확한 서버 파트 키. ì‚¬ìš©ëœ í‚¤ 파트가 스트ë§ì´ 아니거나 키 íŒŒíŠ¸ì˜ ê¸¸ì´ê°€ 너무 ê¹ë‹ˆë‹¤."
+ nor "Feil delnøkkel. Den brukte delnøkkelen er ikke en streng eller den oppgitte lengde er lengre enn nøkkel lengden"
+ norwegian-ny "Feil delnykkel. Den brukte delnykkelen er ikkje ein streng eller den oppgitte lengda er lengre enn nykkellengden"
+ pol "Błędna podczę?ć klucza. Użyta czę?ć klucza nie jest łańcuchem lub użyta długo?ć jest większa niż czę?ć klucza"
+ por "Sub parte da chave incorreta. A parte da chave usada não é uma 'string' ou o comprimento usado é maior que parte da chave ou o manipulador de tabelas não suporta sub chaves únicas"
+ rum "Componentul cheii este incorrect. Componentul folosit al cheii nu este un sir sau lungimea folosita este mai lunga decit lungimea cheii"
+ rus "ÐÐµÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð°Ñ Ñ‡Ð°Ñть ключа. ИÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ñ‡Ð°Ñть ключа не ÑвлÑетÑÑ Ñтрокой, ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° больше, чем длина чаÑти ключа, или обработчик таблицы не поддерживает уникальные чаÑти ключа"
+ serbian "PogreÅ¡an pod-kljuÄ dela kljuÄa. Upotrebljeni deo kljuÄa nije string, upotrebljena dužina je veća od dela kljuÄa ili handler tabela ne podržava jedinstvene pod-kljuÄeve"
+ slo "Incorrect prefix key; the used key part isn't a string or the used length is longer than the key part"
+ spa "Parte de la clave es erronea. Una parte de la clave no es una cadena o la longitud usada es tan grande como la parte de la clave"
+ swe "Felaktig delnyckel. Nyckeldelen är inte en sträng eller den angivna längden är längre än kolumnlängden"
+ ukr "Ðевірна чаÑтина ключа. ВикориÑтана чаÑтина ключа не Ñ” Ñтрокою, задовга або вказівник таблиці не підтримує унікальних чаÑтин ключей"
+ER_CANT_REMOVE_ALL_FIELDS 42000
+ cze "Není možné vymazat všechny položky s ALTER TABLE. Použijte DROP TABLE"
+ dan "Man kan ikke slette alle felter med ALTER TABLE. Brug DROP TABLE i stedet."
+ nla "Het is niet mogelijk alle velden te verwijderen met ALTER TABLE. Gebruik a.u.b. DROP TABLE hiervoor!"
+ eng "You can't delete all columns with ALTER TABLE; use DROP TABLE instead"
+ est "ALTER TABLE kasutades ei saa kustutada kõiki tulpasid. Kustuta tabel DROP TABLE abil"
+ fre "Vous ne pouvez effacer tous les champs avec ALTER TABLE. Utilisez DROP TABLE"
+ ger "Mit ALTER TABLE können nicht alle Felder auf einmal gelöscht werden. Dafür DROP TABLE verwenden"
+ greek "Δεν είναι δυνατή η διαγÏαφή όλων των πεδίων με ALTER TABLE. ΠαÏακαλώ χÏησιμοποιείστε DROP TABLE"
+ hun "Az osszes mezo nem torolheto az ALTER TABLE-lel. Hasznalja a DROP TABLE-t helyette"
+ ita "Non si possono cancellare tutti i campi con una ALTER TABLE. Utilizzare DROP TABLE"
+ jpn "ALTER TABLE ã§ã¯å…¨ã¦ã®åˆ—ã®å‰Šé™¤ã¯ã§ãã¾ã›ã‚“。DROP TABLE を使用ã—ã¦ãã ã•ã„。"
+ kor "ALTER TABLE 명령으로는 모든 ì¹¼ëŸ¼ì„ ì§€ìš¸ 수 없습니다. DROP TABLE ëª…ë ¹ì„ ì´ìš©í•˜ì„¸ìš”."
+ nor "En kan ikke slette alle felt med ALTER TABLE. Bruk DROP TABLE isteden."
+ norwegian-ny "Ein kan ikkje slette alle felt med ALTER TABLE. Bruk DROP TABLE istadenfor."
+ pol "Nie można usun?ć wszystkich pól wykorzystuj?c ALTER TABLE. W zamian użyj DROP TABLE"
+ por "Você não pode deletar todas as colunas com ALTER TABLE; use DROP TABLE em seu lugar"
+ rum "Nu poti sterge toate coloanele cu ALTER TABLE. Foloseste DROP TABLE in schimb"
+ rus "ÐÐµÐ»ÑŒÐ·Ñ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ вÑе Ñтолбцы Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ALTER TABLE. ИÑпользуйте DROP TABLE"
+ serbian "Ne možete da izbrišete sve kolone pomoću komande 'ALTER TABLE'. Upotrebite komandu 'DROP TABLE' ako želite to da uradite"
+ slo "One nemôžem zmazať all fields with ALTER TABLE; use DROP TABLE instead"
+ spa "No puede borrar todos los campos con ALTER TABLE. Usa DROP TABLE para hacerlo"
+ swe "Man kan inte radera alla fält med ALTER TABLE. Använd DROP TABLE istället"
+ ukr "Ðе можливо видалити вÑÑ– Ñтовбці за допомогою ALTER TABLE. Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ÑкориÑтайтеÑÑ DROP TABLE"
+ER_CANT_DROP_FIELD_OR_KEY 42000
+ cze "Nemohu zruÅ¡it '%-.192s' (provést DROP). Zkontrolujte, zda neexistují záznamy/klíÄe"
+ dan "Kan ikke udføre DROP '%-.192s'. Undersøg om feltet/nøglen eksisterer."
+ nla "Kan '%-.192s' niet weggooien. Controleer of het veld of de zoeksleutel daadwerkelijk bestaat."
+ eng "Can't DROP '%-.192s'; check that column/key exists"
+ est "Ei suuda kustutada '%-.192s'. Kontrolli kas tulp/võti eksisteerib"
+ fre "Ne peut effacer (DROP) '%-.192s'. Vérifiez s'il existe"
+ ger "Kann '%-.192s' nicht löschen. Existiert die Spalte oder der Schlüssel?"
+ greek "ΑδÏνατη η διαγÏαφή (DROP) '%-.192s'. ΠαÏακαλώ ελέγξτε αν το πεδίο/κλειδί υπάÏχει"
+ hun "A DROP '%-.192s' nem lehetseges. Ellenorizze, hogy a mezo/kulcs letezik-e"
+ ita "Impossibile cancellare '%-.192s'. Controllare che il campo chiave esista"
+ jpn "'%-.192s' を削除ã§ãã¾ã›ã‚“。列ï¼ç´¢å¼•ã®å­˜åœ¨ã‚’確èªã—ã¦ä¸‹ã•ã„。"
+ kor "'%-.192s'를 DROPí•  수 없습니다. 칼럼ì´ë‚˜ 키가 존재하는지 채í¬í•˜ì„¸ìš”."
+ nor "Kan ikke DROP '%-.192s'. Undersøk om felt/nøkkel eksisterer."
+ norwegian-ny "Kan ikkje DROP '%-.192s'. Undersøk om felt/nøkkel eksisterar."
+ pol "Nie można wykonać operacji DROP '%-.192s'. SprawdĽ, czy to pole/klucz istnieje"
+ por "Não se pode fazer DROP '%-.192s'. Confira se esta coluna/chave existe"
+ rum "Nu pot sa DROP '%-.192s'. Verifica daca coloana/cheia exista"
+ rus "Ðевозможно удалить (DROP) '%-.192s'. УбедитеÑÑŒ что Ñтолбец/ключ дейÑтвительно ÑущеÑтвует"
+ serbian "Ne mogu da izvrÅ¡im komandu drop 'DROP' na '%-.192s'. Proverite da li ta kolona (odnosno kljuÄ) postoji"
+ slo "Nemôžem zruÅ¡iÅ¥ (DROP) '%-.192s'. Skontrolujte, Äi neexistujú záznamy/kľúÄe"
+ spa "No puedo ELIMINAR '%-.192s'. compuebe que el campo/clave existe"
+ swe "Kan inte ta bort '%-.192s'. Kontrollera att fältet/nyckel finns"
+ ukr "Ðе можу DROP '%-.192s'. Перевірте, чи цей Ñтовбець/ключ Ñ–Ñнує"
+ER_INSERT_INFO
+ cze "Záznamů: %ld Zdvojených: %ld Varování: %ld"
+ dan "Poster: %ld Ens: %ld Advarsler: %ld"
+ nla "Records: %ld Dubbel: %ld Waarschuwing: %ld"
+ eng "Records: %ld Duplicates: %ld Warnings: %ld"
+ est "Kirjeid: %ld Kattuvaid: %ld Hoiatusi: %ld"
+ fre "Enregistrements: %ld Doublons: %ld Avertissements: %ld"
+ ger "Datensätze: %ld Duplikate: %ld Warnungen: %ld"
+ greek "ΕγγÏαφές: %ld Επαναλήψεις: %ld ΠÏοειδοποιήσεις: %ld"
+ hun "Rekordok: %ld Duplikalva: %ld Warnings: %ld"
+ ita "Records: %ld Duplicati: %ld Avvertimenti: %ld"
+ jpn "レコード数: %ld é‡è¤‡æ•°: %ld 警告: %ld"
+ kor "레코드: %ld개 중복: %ld개 경고: %ld개"
+ nor "Poster: %ld Like: %ld Advarsler: %ld"
+ norwegian-ny "Postar: %ld Like: %ld Ã…tvaringar: %ld"
+ pol "Rekordów: %ld Duplikatów: %ld Ostrzeżeń: %ld"
+ por "Registros: %ld - Duplicados: %ld - Avisos: %ld"
+ rum "Recorduri: %ld Duplicate: %ld Atentionari (warnings): %ld"
+ rus "ЗапиÑей: %ld Дубликатов: %ld Предупреждений: %ld"
+ serbian "Slogova: %ld Duplikata: %ld Upozorenja: %ld"
+ slo "Záznamov: %ld Opakovaných: %ld Varovania: %ld"
+ spa "Registros: %ld Duplicados: %ld Peligros: %ld"
+ swe "Rader: %ld Dubletter: %ld Varningar: %ld"
+ ukr "ЗапиÑів: %ld Дублікатів: %ld ЗаÑтережень: %ld"
+ER_UPDATE_TABLE_USED
+ eng "Table '%-.192s' is specified twice, both as a target for '%s' and as a separate source for data"
+ swe "Table '%-.192s' är använd två gånger. Både för '%s' och för att hämta data"
+ER_NO_SUCH_THREAD
+ cze "Neznámá identifikace threadu: %lu"
+ dan "Ukendt tråd id: %lu"
+ nla "Onbekend thread id: %lu"
+ eng "Unknown thread id: %lu"
+ est "Tundmatu lõim: %lu"
+ fre "Numéro de tâche inconnu: %lu"
+ ger "Unbekannte Thread-ID: %lu"
+ greek "Αγνωστο thread id: %lu"
+ hun "Ervenytelen szal (thread) id: %lu"
+ ita "Thread id: %lu sconosciuto"
+ jpn "䏿˜Žãªã‚¹ãƒ¬ãƒƒãƒ‰IDã§ã™: %lu"
+ kor "알수 없는 쓰레드 id: %lu"
+ nor "Ukjent tråd id: %lu"
+ norwegian-ny "Ukjent tråd id: %lu"
+ pol "Nieznany identyfikator w?tku: %lu"
+ por "'Id' de 'thread' %lu desconhecido"
+ rum "Id-ul: %lu thread-ului este necunoscut"
+ rus "ÐеизвеÑтный номер потока: %lu"
+ serbian "Nepoznat thread identifikator: %lu"
+ slo "Neznáma identifikácia vlákna: %lu"
+ spa "Identificador del thread: %lu desconocido"
+ swe "Finns ingen tråd med id %lu"
+ ukr "Ðевідомий ідентифікатор гілки: %lu"
+ER_KILL_DENIED_ERROR
+ cze "Nejste vlastníkem threadu %lu"
+ dan "Du er ikke ejer af tråden %lu"
+ nla "U bent geen bezitter van thread %lu"
+ eng "You are not owner of thread %lu"
+ est "Ei ole lõime %lu omanik"
+ fre "Vous n'êtes pas propriétaire de la tâche no: %lu"
+ ger "Sie sind nicht Eigentümer von Thread %lu"
+ greek "Δεν είσθε owner του thread %lu"
+ hun "A %lu thread-nek mas a tulajdonosa"
+ ita "Utente non proprietario del thread %lu"
+ jpn "スレッド %lu ã®ã‚ªãƒ¼ãƒŠãƒ¼ã§ã¯ã‚りã¾ã›ã‚“。"
+ kor "쓰레드(Thread) %luì˜ ì†Œìœ ìžê°€ 아닙니다."
+ nor "Du er ikke eier av tråden %lu"
+ norwegian-ny "Du er ikkje eigar av tråd %lu"
+ pol "Nie jeste? wła?cicielem w?tku %lu"
+ por "Você não é proprietário da 'thread' %lu"
+ rum "Nu sinteti proprietarul threadului %lu"
+ rus "Ð’Ñ‹ не ÑвлÑетеÑÑŒ владельцем потока %lu"
+ serbian "Vi niste vlasnik thread-a %lu"
+ slo "Nie ste vlastníkom vlákna %lu"
+ spa "Tu no eres el propietario del thread%lu"
+ swe "Du är inte ägare till tråd %lu"
+ ukr "Ви не володар гілки %lu"
+ER_NO_TABLES_USED
+ cze "Nejsou použity žádné tabulky"
+ dan "Ingen tabeller i brug"
+ nla "Geen tabellen gebruikt."
+ eng "No tables used"
+ est "Ühtegi tabelit pole kasutusel"
+ fre "Aucune table utilisée"
+ ger "Keine Tabellen verwendet"
+ greek "Δεν χÏησιμοποιήθηκαν πίνακες"
+ hun "Nincs hasznalt tabla"
+ ita "Nessuna tabella usata"
+ jpn "è¡¨ãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。"
+ kor "ì–´ë–¤ í…Œì´ë¸”ë„ ì‚¬ìš©ë˜ì§€ 않았습니다."
+ nor "Ingen tabeller i bruk"
+ norwegian-ny "Ingen tabellar i bruk"
+ pol "Nie ma żadej użytej tabeli"
+ por "Nenhuma tabela usada"
+ rum "Nici o tabela folosita"
+ rus "Ðикакие таблицы не иÑпользованы"
+ serbian "Nema upotrebljenih tabela"
+ slo "Nie je použitá žiadna tabuľka"
+ spa "No ha tablas usadas"
+ swe "Inga tabeller angivna"
+ ukr "Ðе викориÑтано таблиць"
+ER_TOO_BIG_SET
+ cze "Příliš mnoho řetězců pro sloupec %-.192s a SET"
+ dan "For mange tekststrenge til specifikationen af SET i kolonne %-.192s"
+ nla "Teveel strings voor kolom %-.192s en SET"
+ eng "Too many strings for column %-.192s and SET"
+ est "Liiga palju string tulbale %-.192s tüübile SET"
+ fre "Trop de chaînes dans la colonne %-.192s avec SET"
+ ger "Zu viele Strings für Feld %-.192s und SET angegeben"
+ greek "ΠάÏα πολλά strings για το πεδίο %-.192s και SET"
+ hun "Tul sok karakter: %-.192s es SET"
+ ita "Troppe stringhe per la colonna %-.192s e la SET"
+ jpn "SETåž‹ã®åˆ— '%-.192s' ã®ãƒ¡ãƒ³ãƒãƒ¼ã®æ•°ãŒå¤šã™ãŽã¾ã™ã€‚"
+ kor "칼럼 %-.192s와 SETì—서 스트ë§ì´ 너무 많습니다."
+ nor "For mange tekststrenger kolonne %-.192s og SET"
+ norwegian-ny "For mange tekststrengar felt %-.192s og SET"
+ pol "Zbyt wiele łańcuchów dla kolumny %-.192s i polecenia SET"
+ por "'Strings' demais para coluna '%-.192s' e SET"
+ rum "Prea multe siruri pentru coloana %-.192s si SET"
+ rus "Слишком много значений Ð´Ð»Ñ Ñтолбца %-.192s в SET"
+ serbian "Previše string-ova za kolonu '%-.192s' i komandu 'SET'"
+ slo "Príliš mnoho reťazcov pre pole %-.192s a SET"
+ spa "Muchas strings para columna %-.192s y SET"
+ swe "För många alternativ till kolumn %-.192s för SET"
+ ukr "Забагато Ñтрок Ð´Ð»Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ %-.192s та SET"
+ER_NO_UNIQUE_LOGFILE
+ cze "Nemohu vytvoÅ™it jednoznaÄné jméno logovacího souboru %-.200s.(1-999)\n"
+ dan "Kan ikke lave unikt log-filnavn %-.200s.(1-999)\n"
+ nla "Het is niet mogelijk een unieke naam te maken voor de logfile %-.200s.(1-999)\n"
+ eng "Can't generate a unique log-filename %-.200s.(1-999)\n"
+ est "Ei suuda luua unikaalset logifaili nime %-.200s.(1-999)\n"
+ fre "Ne peut générer un unique nom de journal %-.200s.(1-999)\n"
+ ger "Kann keinen eindeutigen Dateinamen für die Logdatei %-.200s(1-999) erzeugen\n"
+ greek "ΑδÏνατη η δημιουÏγία unique log-filename %-.200s.(1-999)\n"
+ hun "Egyedi log-filenev nem generalhato: %-.200s.(1-999)\n"
+ ita "Impossibile generare un nome del file log unico %-.200s.(1-999)\n"
+ jpn "一æ„ãªãƒ­ã‚°ãƒ•ァイルå %-.200s.(1-999) を生æˆã§ãã¾ã›ã‚“。\n"
+ kor "Unique ë¡œê·¸í™”ì¼ '%-.200s'를 만들수 없습니다.(1-999)\n"
+ nor "Kan ikke lage unikt loggfilnavn %-.200s.(1-999)\n"
+ norwegian-ny "Kan ikkje lage unikt loggfilnavn %-.200s.(1-999)\n"
+ pol "Nie można stworzyć unikalnej nazwy pliku z logiem %-.200s.(1-999)\n"
+ por "Não pode gerar um nome de arquivo de 'log' único '%-.200s'.(1-999)\n"
+ rum "Nu pot sa generez un nume de log unic %-.200s.(1-999)\n"
+ rus "Ðевозможно Ñоздать уникальное Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° журнала %-.200s.(1-999)\n"
+ serbian "Ne mogu da generišem jedinstveno ime log-file-a: '%-.200s.(1-999)'\n"
+ slo "Nemôžem vytvoriť unikátne meno log-súboru %-.200s.(1-999)\n"
+ spa "No puede crear un unico archivo log %-.200s.(1-999)\n"
+ swe "Kan inte generera ett unikt filnamn %-.200s.(1-999)\n"
+ ukr "Ðе можу згенерувати унікальне ім'Ñ log-файлу %-.200s.(1-999)\n"
+ER_TABLE_NOT_LOCKED_FOR_WRITE
+ cze "Tabulka '%-.192s' byla zamÄena s READ a nemůže být zmÄ›nÄ›na"
+ dan "Tabellen '%-.192s' var låst med READ lås og kan ikke opdateres"
+ nla "Tabel '%-.192s' was gelocked met een lock om te lezen. Derhalve kunnen geen wijzigingen worden opgeslagen."
+ eng "Table '%-.192s' was locked with a READ lock and can't be updated"
+ est "Tabel '%-.192s' on lukustatud READ lukuga ning ei ole muudetav"
+ fre "Table '%-.192s' verrouillée lecture (READ): modification impossible"
+ ger "Tabelle '%-.192s' ist mit Lesesperre versehen und kann nicht aktualisiert werden"
+ greek "Ο πίνακας '%-.192s' έχει κλειδωθεί με READ lock και δεν επιτÏέπονται αλλαγές"
+ hun "A(z) '%-.192s' tabla zarolva lett (READ lock) es nem lehet frissiteni"
+ ita "La tabella '%-.192s' e` soggetta a lock in lettura e non puo` essere aggiornata"
+ jpn "表 '%-.192s' ã¯READロックã•れã¦ã„ã¦ã€æ›´æ–°ã§ãã¾ã›ã‚“。"
+ kor "í…Œì´ë¸” '%-.192s'는 READ ë½ì´ 잠겨있어서 갱신할 수 없습니다."
+ nor "Tabellen '%-.192s' var låst med READ lås og kan ikke oppdateres"
+ norwegian-ny "Tabellen '%-.192s' var låst med READ lås og kan ikkje oppdaterast"
+ pol "Tabela '%-.192s' została zablokowana przez READ i nie może zostać zaktualizowana"
+ por "Tabela '%-.192s' foi travada com trava de leitura e não pode ser atualizada"
+ rum "Tabela '%-.192s' a fost locked cu un READ lock si nu poate fi actualizata"
+ rus "Таблица '%-.192s' заблокирована уровнем READ lock и не может быть изменена"
+ serbian "Tabela '%-.192s' je zakljuÄana READ lock-om; iz nje se može samo Äitati ali u nju se ne može pisati"
+ slo "Tabuľka '%-.192s' bola zamknutá s READ a nemôže byť zmenená"
+ spa "Tabla '%-.192s' fue trabada con un READ lock y no puede ser actualizada"
+ swe "Tabell '%-.192s' kan inte uppdateras emedan den är låst för läsning"
+ ukr "Таблицю '%-.192s' заблоковано тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ, тому Ñ—Ñ— не можна оновити"
+ER_TABLE_NOT_LOCKED
+ cze "Tabulka '%-.192s' nebyla zamÄena s LOCK TABLES"
+ dan "Tabellen '%-.192s' var ikke låst med LOCK TABLES"
+ nla "Tabel '%-.192s' was niet gelocked met LOCK TABLES"
+ eng "Table '%-.192s' was not locked with LOCK TABLES"
+ est "Tabel '%-.192s' ei ole lukustatud käsuga LOCK TABLES"
+ fre "Table '%-.192s' non verrouillée: utilisez LOCK TABLES"
+ ger "Tabelle '%-.192s' wurde nicht mit LOCK TABLES gesperrt"
+ greek "Ο πίνακας '%-.192s' δεν έχει κλειδωθεί με LOCK TABLES"
+ hun "A(z) '%-.192s' tabla nincs zarolva a LOCK TABLES-szel"
+ ita "Non e` stato impostato il lock per la tabella '%-.192s' con LOCK TABLES"
+ jpn "表 '%-.192s' 㯠LOCK TABLES ã§ãƒ­ãƒƒã‚¯ã•れã¦ã„ã¾ã›ã‚“。"
+ kor "í…Œì´ë¸” '%-.192s'는 LOCK TABLES 명령으로 잠기지 않았습니다."
+ nor "Tabellen '%-.192s' var ikke låst med LOCK TABLES"
+ norwegian-ny "Tabellen '%-.192s' var ikkje låst med LOCK TABLES"
+ pol "Tabela '%-.192s' nie została zablokowana poleceniem LOCK TABLES"
+ por "Tabela '%-.192s' não foi travada com LOCK TABLES"
+ rum "Tabela '%-.192s' nu a fost locked cu LOCK TABLES"
+ rus "Таблица '%-.192s' не была заблокирована Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ LOCK TABLES"
+ serbian "Tabela '%-.192s' nije bila zakljuÄana komandom 'LOCK TABLES'"
+ slo "Tabuľka '%-.192s' nebola zamknutá s LOCK TABLES"
+ spa "Tabla '%-.192s' no fue trabada con LOCK TABLES"
+ swe "Tabell '%-.192s' är inte låst med LOCK TABLES"
+ ukr "Таблицю '%-.192s' не було блоковано з LOCK TABLES"
+ER_BLOB_CANT_HAVE_DEFAULT 42000
+ cze "Blob položka '%-.192s' nemůže mít defaultní hodnotu"
+ dan "BLOB feltet '%-.192s' kan ikke have en standard værdi"
+ nla "Blob veld '%-.192s' can geen standaardwaarde bevatten"
+ eng "BLOB/TEXT column '%-.192s' can't have a default value"
+ est "BLOB-tüüpi tulp '%-.192s' ei saa omada vaikeväärtust"
+ fre "BLOB '%-.192s' ne peut avoir de valeur par défaut"
+ ger "BLOB/TEXT-Feld '%-.192s' darf keinen Vorgabewert (DEFAULT) haben"
+ greek "Τα Blob πεδία '%-.192s' δεν μποÏοÏν να έχουν Ï€ÏοκαθοÏισμένες τιμές (default value)"
+ hun "A(z) '%-.192s' blob objektumnak nem lehet alapertelmezett erteke"
+ ita "Il campo BLOB '%-.192s' non puo` avere un valore di default"
+ jpn "BLOB/TEXT 列 '%-.192s' ã«ã¯ãƒ‡ãƒ•ォルト値を指定ã§ãã¾ã›ã‚“。"
+ kor "BLOB 칼럼 '%-.192s' 는 ë””í´íЏ ê°’ì„ ê°€ì§ˆ 수 없습니다."
+ nor "Blob feltet '%-.192s' kan ikke ha en standard verdi"
+ norwegian-ny "Blob feltet '%-.192s' kan ikkje ha ein standard verdi"
+ pol "Pole typu blob '%-.192s' nie może mieć domy?lnej warto?ci"
+ por "Coluna BLOB '%-.192s' não pode ter um valor padrão (default)"
+ rum "Coloana BLOB '%-.192s' nu poate avea o valoare default"
+ rus "Ðевозможно указывать значение по умолчанию Ð´Ð»Ñ Ñтолбца BLOB '%-.192s'"
+ serbian "BLOB kolona '%-.192s' ne može imati default vrednost"
+ slo "Pole BLOB '%-.192s' nemôže mať implicitnú hodnotu"
+ spa "Campo Blob '%-.192s' no puede tener valores patron"
+ swe "BLOB fält '%-.192s' kan inte ha ett DEFAULT-värde"
+ ukr "Стовбець BLOB '%-.192s' не може мати Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾ замовчуванню"
+ER_WRONG_DB_NAME 42000
+ cze "Nepřípustné jméno databáze '%-.100s'"
+ dan "Ugyldigt database navn '%-.100s'"
+ nla "Databasenaam '%-.100s' is niet getoegestaan"
+ eng "Incorrect database name '%-.100s'"
+ est "Vigane andmebaasi nimi '%-.100s'"
+ fre "Nom de base de donnée illégal: '%-.100s'"
+ ger "Unerlaubter Datenbankname '%-.100s'"
+ greek "Λάθος όνομα βάσης δεδομένων '%-.100s'"
+ hun "Hibas adatbazisnev: '%-.100s'"
+ ita "Nome database errato '%-.100s'"
+ jpn "データベースå '%-.100s' ã¯ä¸æ­£ã§ã™ã€‚"
+ kor "'%-.100s' ë°ì´íƒ€ë² ì´ìŠ¤ì˜ ì´ë¦„ì´ ë¶€ì •í™•í•©ë‹ˆë‹¤."
+ nor "Ugyldig database navn '%-.100s'"
+ norwegian-ny "Ugyldig database namn '%-.100s'"
+ pol "Niedozwolona nazwa bazy danych '%-.100s'"
+ por "Nome de banco de dados '%-.100s' incorreto"
+ rum "Numele bazei de date este incorect '%-.100s'"
+ rus "Ðекорректное Ð¸Ð¼Ñ Ð±Ð°Ð·Ñ‹ данных '%-.100s'"
+ serbian "Pogrešno ime baze '%-.100s'"
+ slo "Neprípustné meno databázy '%-.100s'"
+ spa "Nombre de base de datos ilegal '%-.100s'"
+ swe "Felaktigt databasnamn '%-.100s'"
+ ukr "Ðевірне ім'Ñ Ð±Ð°Ð·Ð¸ данних '%-.100s'"
+ER_WRONG_TABLE_NAME 42000
+ cze "Nepřípustné jméno tabulky '%-.100s'"
+ dan "Ugyldigt tabel navn '%-.100s'"
+ nla "Niet toegestane tabelnaam '%-.100s'"
+ eng "Incorrect table name '%-.100s'"
+ est "Vigane tabeli nimi '%-.100s'"
+ fre "Nom de table illégal: '%-.100s'"
+ ger "Unerlaubter Tabellenname '%-.100s'"
+ greek "Λάθος όνομα πίνακα '%-.100s'"
+ hun "Hibas tablanev: '%-.100s'"
+ ita "Nome tabella errato '%-.100s'"
+ jpn "表å '%-.100s' ã¯ä¸æ­£ã§ã™ã€‚"
+ kor "'%-.100s' í…Œì´ë¸” ì´ë¦„ì´ ë¶€ì •í™•í•©ë‹ˆë‹¤."
+ nor "Ugyldig tabell navn '%-.100s'"
+ norwegian-ny "Ugyldig tabell namn '%-.100s'"
+ pol "Niedozwolona nazwa tabeli '%-.100s'..."
+ por "Nome de tabela '%-.100s' incorreto"
+ rum "Numele tabelei este incorect '%-.100s'"
+ rus "Ðекорректное Ð¸Ð¼Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹ '%-.100s'"
+ serbian "Pogrešno ime tabele '%-.100s'"
+ slo "Neprípustné meno tabuľky '%-.100s'"
+ spa "Nombre de tabla ilegal '%-.100s'"
+ swe "Felaktigt tabellnamn '%-.100s'"
+ ukr "Ðевірне ім'Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– '%-.100s'"
+ER_TOO_BIG_SELECT 42000
+ cze "Zadaný SELECT by procházel příliš mnoho záznamů a trval velmi dlouho. Zkontrolujte tvar WHERE a je-li SELECT v pořádku, použijte SET SQL_BIG_SELECTS=1"
+ dan "SELECT ville undersøge for mange poster og ville sandsynligvis tage meget lang tid. Undersøg WHERE delen og brug SET SQL_BIG_SELECTS=1 hvis udtrykket er korrekt"
+ nla "Het SELECT-statement zou te veel records analyseren en dus veel tijd in beslagnemen. Kijk het WHERE-gedeelte van de query na en kies SET SQL_BIG_SELECTS=1 als het stament in orde is."
+ eng "The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay"
+ est "SELECT lause peab läbi vaatama suure hulga kirjeid ja võtaks tõenäoliselt liiga kaua aega. Tasub kontrollida WHERE klauslit ja vajadusel kasutada käsku SET SQL_BIG_SELECTS=1"
+ fre "SELECT va devoir examiner beaucoup d'enregistrements ce qui va prendre du temps. Vérifiez la clause WHERE et utilisez SET SQL_BIG_SELECTS=1 si SELECT se passe bien"
+ ger "Die Ausführung des SELECT würde zu viele Datensätze untersuchen und wahrscheinlich sehr lange dauern. Bitte WHERE-Klausel überprüfen und gegebenenfalls SET SQL_BIG_SELECTS=1 oder SET MAX_JOIN_SIZE=# verwenden"
+ greek "Το SELECT θα εξετάσει μεγάλο αÏιθμό εγγÏαφών και πιθανώς θα καθυστεÏήσει. ΠαÏακαλώ εξετάστε τις παÏαμέτÏους του WHERE και χÏησιμοποιείστε SET SQL_BIG_SELECTS=1 αν το SELECT είναι σωστό"
+ hun "A SELECT tul sok rekordot fog megvizsgalni es nagyon sokaig fog tartani. Ellenorizze a WHERE-t es hasznalja a SET SQL_BIG_SELECTS=1 beallitast, ha a SELECT okay"
+ ita "La SELECT dovrebbe esaminare troppi record e usare troppo tempo. Controllare la WHERE e usa SET SQL_BIG_SELECTS=1 se e` tutto a posto."
+ jpn "SELECTãŒMAX_JOIN_SIZEã‚’è¶…ãˆã‚‹è¡Œæ•°ã‚’処ç†ã—ã¾ã—ãŸã€‚WHEREå¥ã‚’確èªã—ã€SELECTæ–‡ã«å•題ãŒãªã‘れã°ã€ SET SQL_BIG_SELECTS=1 ã¾ãŸã¯ SET MAX_JOIN_SIZE=# を使用ã—ã¦ä¸‹ã•ã„。"
+ kor "SELECT 명령ì—서 너무 ë§Žì€ ë ˆì½”ë“œë¥¼ 찾기 ë•Œë¬¸ì— ë§Žì€ ì‹œê°„ì´ ì†Œìš”ë©ë‹ˆë‹¤. ë”°ë¼ì„œ WHERE ë¬¸ì„ ì ê²€í•˜ê±°ë‚˜, 만약 SELECTê°€ okë˜ë©´ SET SQL_BIG_SELECTS=1 ì˜µì…˜ì„ ì‚¬ìš©í•˜ì„¸ìš”."
+ nor "SELECT ville undersøke for mange poster og ville sannsynligvis ta veldig lang tid. Undersøk WHERE klausulen og bruk SET SQL_BIG_SELECTS=1 om SELECTen er korrekt"
+ norwegian-ny "SELECT ville undersøkje for mange postar og ville sannsynligvis ta veldig lang tid. Undersøk WHERE klausulen og bruk SET SQL_BIG_SELECTS=1 om SELECTen er korrekt"
+ pol "Operacja SELECT będzie dotyczyła zbyt wielu rekordów i prawdopodobnie zajmie bardzo dużo czasu. SprawdĽ warunek WHERE i użyj SQL_OPTION BIG_SELECTS=1 je?li operacja SELECT jest poprawna"
+ por "O SELECT examinaria registros demais e provavelmente levaria muito tempo. Cheque sua cláusula WHERE e use SET SQL_BIG_SELECTS=1, se o SELECT estiver correto"
+ rum "SELECT-ul ar examina prea multe cimpuri si probabil ar lua prea mult timp; verifica clauza WHERE si foloseste SET SQL_BIG_SELECTS=1 daca SELECT-ul e okay"
+ rus "Ð”Ð»Ñ Ñ‚Ð°ÐºÐ¾Ð¹ выборки SELECT должен будет проÑмотреть Ñлишком много запиÑей и, видимо, Ñто займет очень много времени. Проверьте ваше указание WHERE, и, еÑли в нем вÑе в порÑдке, укажите SET SQL_BIG_SELECTS=1"
+ serbian "Komanda 'SELECT' će ispitati previše slogova i potrošiti previše vremena. Proverite vaš 'WHERE' filter i upotrebite 'SET OPTION SQL_BIG_SELECTS=1' ako želite baš ovakvu komandu"
+ slo "Zadaná požiadavka SELECT by prechádzala príliš mnoho záznamov a trvala by príliš dlho. Skontrolujte tvar WHERE a ak je v poriadku, použite SET SQL_BIG_SELECTS=1"
+ spa "El SELECT puede examinar muchos registros y probablemente con mucho tiempo. Verifique tu WHERE y usa SET SQL_BIG_SELECTS=1 si el SELECT esta correcto"
+ swe "Den angivna frågan skulle läsa mer än MAX_JOIN_SIZE rader. Kontrollera din WHERE och använd SET SQL_BIG_SELECTS=1 eller SET MAX_JOIN_SIZE=# ifall du vill hantera stora joins"
+ ukr "Запиту SELECT потрібно обробити багато запиÑів, що, певне, займе дуже багато чаÑу. Перевірте ваше WHERE та викориÑтовуйте SET SQL_BIG_SELECTS=1, Ñкщо цей запит SELECT Ñ” вірним"
+ER_UNKNOWN_ERROR
+ cze "Neznámá chyba"
+ dan "Ukendt fejl"
+ nla "Onbekende Fout"
+ eng "Unknown error"
+ est "Tundmatu viga"
+ fre "Erreur inconnue"
+ ger "Unbekannter Fehler"
+ greek "ΠÏοέκυψε άγνωστο λάθος"
+ hun "Ismeretlen hiba"
+ ita "Errore sconosciuto"
+ jpn "䏿˜Žãªã‚¨ãƒ©ãƒ¼"
+ kor "알수 없는 ì—러입니다."
+ nor "Ukjent feil"
+ norwegian-ny "Ukjend feil"
+ por "Erro desconhecido"
+ rum "Eroare unknown"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°"
+ serbian "Nepoznata greška"
+ slo "Neznámá chyba"
+ spa "Error desconocido"
+ swe "Okänt fel"
+ ukr "Ðевідома помилка"
+ER_UNKNOWN_PROCEDURE 42000
+ cze "Neznámá procedura %-.192s"
+ dan "Ukendt procedure %-.192s"
+ nla "Onbekende procedure %-.192s"
+ eng "Unknown procedure '%-.192s'"
+ est "Tundmatu protseduur '%-.192s'"
+ fre "Procédure %-.192s inconnue"
+ ger "Unbekannte Prozedur '%-.192s'"
+ greek "Αγνωστη διαδικασία '%-.192s'"
+ hun "Ismeretlen eljaras: '%-.192s'"
+ ita "Procedura '%-.192s' sconosciuta"
+ jpn "'%-.192s' ã¯ä¸æ˜Žãªãƒ—ロシージャã§ã™ã€‚"
+ kor "알수 없는 수행문 : '%-.192s'"
+ nor "Ukjent prosedyre %-.192s"
+ norwegian-ny "Ukjend prosedyre %-.192s"
+ pol "Unkown procedure %-.192s"
+ por "'Procedure' '%-.192s' desconhecida"
+ rum "Procedura unknown '%-.192s'"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ñ†ÐµÐ´ÑƒÑ€Ð° '%-.192s'"
+ serbian "Nepoznata procedura '%-.192s'"
+ slo "Neznámá procedúra '%-.192s'"
+ spa "Procedimiento desconocido %-.192s"
+ swe "Okänd procedur: %-.192s"
+ ukr "Ðевідома процедура '%-.192s'"
+ER_WRONG_PARAMCOUNT_TO_PROCEDURE 42000
+ cze "Chybný poÄet parametrů procedury %-.192s"
+ dan "Forkert antal parametre til proceduren %-.192s"
+ nla "Foutief aantal parameters doorgegeven aan procedure %-.192s"
+ eng "Incorrect parameter count to procedure '%-.192s'"
+ est "Vale parameetrite hulk protseduurile '%-.192s'"
+ fre "Mauvais nombre de paramètres pour la procedure %-.192s"
+ ger "Falsche Parameterzahl für Prozedur '%-.192s'"
+ greek "Λάθος αÏιθμός παÏαμέτÏων στη διαδικασία '%-.192s'"
+ hun "Rossz parameter a(z) '%-.192s'eljaras szamitasanal"
+ ita "Numero di parametri errato per la procedura '%-.192s'"
+ jpn "プロシージャ '%-.192s' ã¸ã®ãƒ‘ラメータ数ãŒä¸æ­£ã§ã™ã€‚"
+ kor "'%-.192s' ìˆ˜í–‰ë¬¸ì— ëŒ€í•œ 부정확한 파ë¼ë©”í„°"
+ nor "Feil parameter antall til prosedyren %-.192s"
+ norwegian-ny "Feil parameter tal til prosedyra %-.192s"
+ pol "Incorrect parameter count to procedure %-.192s"
+ por "Número de parâmetros incorreto para a 'procedure' '%-.192s'"
+ rum "Procedura '%-.192s' are un numar incorect de parametri"
+ rus "Ðекорректное количеÑтво параметров Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ†ÐµÐ´ÑƒÑ€Ñ‹ '%-.192s'"
+ serbian "Pogrešan broj parametara za proceduru '%-.192s'"
+ slo "Chybný poÄet parametrov procedúry '%-.192s'"
+ spa "Equivocado parametro count para procedimiento %-.192s"
+ swe "Felaktigt antal parametrar till procedur %-.192s"
+ ukr "Хибна кількіÑть параметрів процедури '%-.192s'"
+ER_WRONG_PARAMETERS_TO_PROCEDURE
+ cze "Chybné parametry procedury %-.192s"
+ dan "Forkert(e) parametre til proceduren %-.192s"
+ nla "Foutieve parameters voor procedure %-.192s"
+ eng "Incorrect parameters to procedure '%-.192s'"
+ est "Vigased parameetrid protseduurile '%-.192s'"
+ fre "Paramètre erroné pour la procedure %-.192s"
+ ger "Falsche Parameter für Prozedur '%-.192s'"
+ greek "Λάθος παÏάμετÏοι στην διαδικασία '%-.192s'"
+ hun "Rossz parameter a(z) '%-.192s' eljarasban"
+ ita "Parametri errati per la procedura '%-.192s'"
+ jpn "プロシージャ '%-.192s' ã¸ã®ãƒ‘ラメータãŒä¸æ­£ã§ã™ã€‚"
+ kor "'%-.192s' ìˆ˜í–‰ë¬¸ì— ëŒ€í•œ 부정확한 파ë¼ë©”í„°"
+ nor "Feil parametre til prosedyren %-.192s"
+ norwegian-ny "Feil parameter til prosedyra %-.192s"
+ pol "Incorrect parameters to procedure %-.192s"
+ por "Parâmetros incorretos para a 'procedure' '%-.192s'"
+ rum "Procedura '%-.192s' are parametrii incorecti"
+ rus "Ðекорректные параметры Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ†ÐµÐ´ÑƒÑ€Ñ‹ '%-.192s'"
+ serbian "Pogrešni parametri prosleđeni proceduri '%-.192s'"
+ slo "Chybné parametre procedúry '%-.192s'"
+ spa "Equivocados parametros para procedimiento %-.192s"
+ swe "Felaktiga parametrar till procedur %-.192s"
+ ukr "Хибний параметер процедури '%-.192s'"
+ER_UNKNOWN_TABLE 42S02
+ cze "Neznámá tabulka '%-.192s' v %-.32s"
+ dan "Ukendt tabel '%-.192s' i %-.32s"
+ nla "Onbekende tabel '%-.192s' in %-.32s"
+ eng "Unknown table '%-.192s' in %-.32s"
+ est "Tundmatu tabel '%-.192s' %-.32s-s"
+ fre "Table inconnue '%-.192s' dans %-.32s"
+ ger "Unbekannte Tabelle '%-.192s' in '%-.32s'"
+ greek "Αγνωστος πίνακας '%-.192s' σε %-.32s"
+ hun "Ismeretlen tabla: '%-.192s' %-.32s-ban"
+ ita "Tabella '%-.192s' sconosciuta in %-.32s"
+ jpn "'%-.192s' 㯠%-.32s ã§ã¯ä¸æ˜Žãªè¡¨ã§ã™ã€‚"
+ kor "알수 없는 í…Œì´ë¸” '%-.192s' (ë°ì´íƒ€ë² ì´ìФ %-.32s)"
+ nor "Ukjent tabell '%-.192s' i %-.32s"
+ norwegian-ny "Ukjend tabell '%-.192s' i %-.32s"
+ pol "Unknown table '%-.192s' in %-.32s"
+ por "Tabela '%-.192s' desconhecida em '%-.32s'"
+ rum "Tabla '%-.192s' invalida in %-.32s"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° '%-.192s' в %-.32s"
+ serbian "Nepoznata tabela '%-.192s' u '%-.32s'"
+ slo "Neznáma tabuľka '%-.192s' v %-.32s"
+ spa "Tabla desconocida '%-.192s' in %-.32s"
+ swe "Okänd tabell '%-.192s' i '%-.32s'"
+ ukr "Ðевідома Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' у %-.32s"
+ER_FIELD_SPECIFIED_TWICE 42000
+ cze "Položka '%-.192s' je zadána dvakrát"
+ dan "Feltet '%-.192s' er anvendt to gange"
+ nla "Veld '%-.192s' is dubbel gespecificeerd"
+ eng "Column '%-.192s' specified twice"
+ est "Tulp '%-.192s' on määratletud topelt"
+ fre "Champ '%-.192s' spécifié deux fois"
+ ger "Feld '%-.192s' wurde zweimal angegeben"
+ greek "Το πεδίο '%-.192s' έχει οÏισθεί δÏο φοÏές"
+ hun "A(z) '%-.192s' mezot ketszer definialta"
+ ita "Campo '%-.192s' specificato 2 volte"
+ jpn "列 '%-.192s' ã¯2回指定ã•れã¦ã„ã¾ã™ã€‚"
+ kor "칼럼 '%-.192s'는 ë‘번 ì •ì˜ë˜ì–´ 있ì니다."
+ nor "Feltet '%-.192s' er spesifisert to ganger"
+ norwegian-ny "Feltet '%-.192s' er spesifisert to gangar"
+ pol "Field '%-.192s' specified twice"
+ por "Coluna '%-.192s' especificada duas vezes"
+ rum "Coloana '%-.192s' specificata de doua ori"
+ rus "Столбец '%-.192s' указан дважды"
+ serbian "Kolona '%-.192s' je navedena dva puta"
+ slo "Pole '%-.192s' je zadané dvakrát"
+ spa "Campo '%-.192s' especificado dos veces"
+ swe "Fält '%-.192s' är redan använt"
+ ukr "Стовбець '%-.192s' зазначено двічі"
+ER_INVALID_GROUP_FUNC_USE
+ cze "Nesprávné použití funkce group"
+ dan "Forkert brug af grupperings-funktion"
+ nla "Ongeldig gebruik van GROUP-functie"
+ eng "Invalid use of group function"
+ est "Vigane grupeerimisfunktsiooni kasutus"
+ fre "Utilisation invalide de la clause GROUP"
+ ger "Falsche Verwendung einer Gruppierungsfunktion"
+ greek "Εσφαλμένη χÏήση της group function"
+ hun "A group funkcio ervenytelen hasznalata"
+ ita "Uso non valido di una funzione di raggruppamento"
+ jpn "集計関数ã®ä½¿ç”¨æ–¹æ³•ãŒä¸æ­£ã§ã™ã€‚"
+ kor "ìž˜ëª»ëœ ê·¸ë£¹ 함수를 사용하였습니다."
+ por "Uso inválido de função de agrupamento (GROUP)"
+ rum "Folosire incorecta a functiei group"
+ rus "Ðеправильное иÑпользование групповых функций"
+ serbian "Pogrešna upotreba 'GROUP' funkcije"
+ slo "Nesprávne použitie funkcie GROUP"
+ spa "Invalido uso de función en grupo"
+ swe "Felaktig användning av SQL grupp function"
+ ukr "Хибне викориÑÑ‚Ð°Ð½Ð½Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ— групуваннÑ"
+ER_UNSUPPORTED_EXTENSION 42000
+ cze "Tabulka '%-.192s' používá rozšíření, které v této verzi MySQL není"
+ dan "Tabellen '%-.192s' bruger et filtypenavn som ikke findes i denne MariaDB version"
+ nla "Tabel '%-.192s' gebruikt een extensie, die niet in deze MariaDB-versie voorkomt."
+ eng "Table '%-.192s' uses an extension that doesn't exist in this MariaDB version"
+ est "Tabel '%-.192s' kasutab laiendust, mis ei eksisteeri antud MariaDB versioonis"
+ fre "Table '%-.192s' : utilise une extension invalide pour cette version de MariaDB"
+ ger "Tabelle '%-.192s' verwendet eine Erweiterung, die in dieser MariaDB-Version nicht verfügbar ist"
+ greek "Ο πίνακς '%-.192s' χÏησιμοποιεί κάποιο extension που δεν υπάÏχει στην έκδοση αυτή της MariaDB"
+ hun "A(z) '%-.192s' tabla olyan bovitest hasznal, amely nem letezik ebben a MariaDB versioban."
+ ita "La tabella '%-.192s' usa un'estensione che non esiste in questa versione di MariaDB"
+ jpn "表 '%-.192s' ã¯ã€ã“ã®MySQLãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã¯ç„¡ã„機能を使用ã—ã¦ã„ã¾ã™ã€‚"
+ kor "í…Œì´ë¸” '%-.192s'는 í™•ìž¥ëª…ë ¹ì„ ì´ìš©í•˜ì§€ë§Œ í˜„ìž¬ì˜ MariaDB 버젼ì—서는 존재하지 않습니다."
+ nor "Table '%-.192s' uses a extension that doesn't exist in this MariaDB version"
+ norwegian-ny "Table '%-.192s' uses a extension that doesn't exist in this MariaDB version"
+ pol "Table '%-.192s' uses a extension that doesn't exist in this MariaDB version"
+ por "Tabela '%-.192s' usa uma extensão que não existe nesta versão do MariaDB"
+ rum "Tabela '%-.192s' foloseste o extensire inexistenta in versiunea curenta de MariaDB"
+ rus "Ð’ таблице '%-.192s' иÑпользуютÑÑ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñти, не поддерживаемые в Ñтой верÑии MariaDB"
+ serbian "Tabela '%-.192s' koristi ekstenziju koje ne postoji u ovoj verziji MariaDB-a"
+ slo "Tabuľka '%-.192s' používa rozšírenie, ktoré v tejto verzii MariaDB nie je"
+ spa "Tabla '%-.192s' usa una extensión que no existe en esta MariaDB versión"
+ swe "Tabell '%-.192s' har en extension som inte finns i denna version av MariaDB"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' викориÑтовує розширеннÑ, що не Ñ–Ñнує у цій верÑÑ–Ñ— MariaDB"
+ER_TABLE_MUST_HAVE_COLUMNS 42000
+ cze "Tabulka musí mít alespoň jeden sloupec"
+ dan "En tabel skal have mindst een kolonne"
+ nla "Een tabel moet minstens 1 kolom bevatten"
+ eng "A table must have at least 1 column"
+ est "Tabelis peab olema vähemalt üks tulp"
+ fre "Une table doit comporter au moins une colonne"
+ ger "Eine Tabelle muss mindestens eine Spalte besitzen"
+ greek "Ενας πίνακας Ï€Ïέπει να έχει τουλάχιστον ένα πεδίο"
+ hun "A tablanak legalabb egy oszlopot tartalmazni kell"
+ ita "Una tabella deve avere almeno 1 colonna"
+ jpn "表ã«ã¯æœ€ä½Žã§ã‚‚1個ã®åˆ—ãŒå¿…è¦ã§ã™ã€‚"
+ kor "í•˜ë‚˜ì˜ í…Œì´ë¸”ì—서는 ì ì–´ë„ í•˜ë‚˜ì˜ ì¹¼ëŸ¼ì´ ì¡´ìž¬í•˜ì—¬ì•¼ 합니다."
+ por "Uma tabela tem que ter pelo menos uma (1) coluna"
+ rum "O tabela trebuie sa aiba cel putin o coloana"
+ rus "Ð’ таблице должен быть как минимум один Ñтолбец"
+ serbian "Tabela mora imati najmanje jednu kolonu"
+ slo "Tabuľka musí mať aspoň 1 pole"
+ spa "Una tabla debe tener al menos 1 columna"
+ swe "Tabeller måste ha minst 1 kolumn"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ Ð¿Ð¾Ð²Ð¸Ð½Ð½Ð° мати хочаб один Ñтовбець"
+ER_RECORD_FILE_FULL
+ cze "Tabulka '%-.192s' je plná"
+ dan "Tabellen '%-.192s' er fuld"
+ nla "De tabel '%-.192s' is vol"
+ eng "The table '%-.192s' is full"
+ est "Tabel '%-.192s' on täis"
+ fre "La table '%-.192s' est pleine"
+ ger "Tabelle '%-.192s' ist voll"
+ greek "Ο πίνακας '%-.192s' είναι γεμάτος"
+ hun "A '%-.192s' tabla megtelt"
+ ita "La tabella '%-.192s' e` piena"
+ jpn "表 '%-.192s' ã¯æº€æ¯ã§ã™ã€‚"
+ kor "í…Œì´ë¸” '%-.192s'ê°€ full났습니다. "
+ por "Tabela '%-.192s' está cheia"
+ rum "Tabela '%-.192s' e plina"
+ rus "Таблица '%-.192s' переполнена"
+ serbian "Tabela '%-.192s' je popunjena do kraja"
+ slo "Tabuľka '%-.192s' je plná"
+ spa "La tabla '%-.192s' está llena"
+ swe "Tabellen '%-.192s' är full"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' заповнена"
+ER_UNKNOWN_CHARACTER_SET 42000
+ cze "Neznámá znaková sada: '%-.64s'"
+ dan "Ukendt tegnsæt: '%-.64s'"
+ nla "Onbekende character set: '%-.64s'"
+ eng "Unknown character set: '%-.64s'"
+ est "Vigane kooditabel '%-.64s'"
+ fre "Jeu de caractères inconnu: '%-.64s'"
+ ger "Unbekannter Zeichensatz: '%-.64s'"
+ greek "Αγνωστο character set: '%-.64s'"
+ hun "Ervenytelen karakterkeszlet: '%-.64s'"
+ ita "Set di caratteri '%-.64s' sconosciuto"
+ jpn "䏿˜Žãªæ–‡å­—コードセット: '%-.64s'"
+ kor "알수없는 언어 Set: '%-.64s'"
+ por "Conjunto de caracteres '%-.64s' desconhecido"
+ rum "Set de caractere invalid: '%-.64s'"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° '%-.64s'"
+ serbian "Nepoznati karakter-set: '%-.64s'"
+ slo "Neznáma znaková sada: '%-.64s'"
+ spa "Juego de caracteres desconocido: '%-.64s'"
+ swe "Okänd teckenuppsättning: '%-.64s'"
+ ukr "Ðевідома кодова таблицÑ: '%-.64s'"
+ER_TOO_MANY_TABLES
+ cze "Příliš mnoho tabulek, MySQL jich může mít v joinu jen %d"
+ dan "For mange tabeller. MariaDB kan kun bruge %d tabeller i et join"
+ nla "Teveel tabellen. MariaDB kan slechts %d tabellen in een join bevatten"
+ eng "Too many tables; MariaDB can only use %d tables in a join"
+ est "Liiga palju tabeleid. MariaDB suudab JOINiga ühendada kuni %d tabelit"
+ fre "Trop de tables. MariaDB ne peut utiliser que %d tables dans un JOIN"
+ ger "Zu viele Tabellen. MariaDB kann in einem Join maximal %d Tabellen verwenden"
+ greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿Ï‚ αÏιθμός πινάκων. Η MariaDB μποÏεί να χÏησιμοποιήσει %d πίνακες σε διαδικασία join"
+ hun "Tul sok tabla. A MariaDB csak %d tablat tud kezelni osszefuzeskor"
+ ita "Troppe tabelle. MariaDB puo` usare solo %d tabelle in una join"
+ jpn "表ãŒå¤šã™ãŽã¾ã™ã€‚MySQLãŒJOINã§ãる表㯠%d 個ã¾ã§ã§ã™ã€‚"
+ kor "너무 ë§Žì€ í…Œì´ë¸”ì´ Joinë˜ì—ˆìŠµë‹ˆë‹¤. MariaDBì—서는 JOIN시 %dê°œì˜ í…Œì´ë¸”ë§Œ 사용할 수 있습니다."
+ por "Tabelas demais. O MariaDB pode usar somente %d tabelas em uma junção (JOIN)"
+ rum "Prea multe tabele. MariaDB nu poate folosi mai mult de %d tabele intr-un join"
+ rus "Слишком много таблиц. MariaDB может иÑпользовать только %d таблиц в Ñоединении"
+ serbian "Previše tabela. MariaDB može upotrebiti maksimum %d tabela pri 'JOIN' operaciji"
+ slo "Príliš mnoho tabuliek. MariaDB môže použiť len %d v JOIN-e"
+ spa "Muchas tablas. MariaDB solamente puede usar %d tablas en un join"
+ swe "För många tabeller. MariaDB can ha högst %d tabeller i en och samma join"
+ ukr "Забагато таблиць. MariaDB може викориÑтовувати лише %d таблиць у об'єднанні"
+ER_TOO_MANY_FIELDS
+ cze "Příliš mnoho položek"
+ dan "For mange felter"
+ nla "Te veel velden"
+ eng "Too many columns"
+ est "Liiga palju tulpasid"
+ fre "Trop de champs"
+ ger "Zu viele Felder"
+ greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿Ï‚ αÏιθμός πεδίων"
+ hun "Tul sok mezo"
+ ita "Troppi campi"
+ jpn "列ãŒå¤šã™ãŽã¾ã™ã€‚"
+ kor "ì¹¼ëŸ¼ì´ ë„ˆë¬´ 많습니다."
+ por "Colunas demais"
+ rum "Prea multe coloane"
+ rus "Слишком много Ñтолбцов"
+ serbian "Previše kolona"
+ slo "Príliš mnoho polí"
+ spa "Muchos campos"
+ swe "För många fält"
+ ukr "Забагато Ñтовбців"
+ER_TOO_BIG_ROWSIZE 42000
+ cze "Řádek je příliÅ¡ velký. Maximální velikost řádku, nepoÄítaje položky blob, je %ld. Musíte zmÄ›nit nÄ›které položky na blob"
+ dan "For store poster. Max post størrelse, uden BLOB's, er %ld. Du må lave nogle felter til BLOB's"
+ nla "Rij-grootte is groter dan toegestaan. Maximale rij grootte, blobs niet meegeteld, is %ld. U dient sommige velden in blobs te veranderen."
+ eng "Row size too large. The maximum row size for the used table type, not counting BLOBs, is %ld. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs"
+ est "Liiga pikk kirje. Kirje maksimumpikkus arvestamata BLOB-tüüpi välju on %ld. Muuda mõned väljad BLOB-tüüpi väljadeks"
+ fre "Ligne trop grande. Le taille maximale d'une ligne, sauf les BLOBs, est %ld. Changez le type de quelques colonnes en BLOB"
+ ger "Zeilenlänge zu groß. Die maximale Zeilenlänge für den verwendeten Tabellentyp (ohne BLOB-Felder) beträgt %ld. Einige Felder müssen in BLOB oder TEXT umgewandelt werden"
+ greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿ μέγεθος εγγÏαφής. Το μέγιστο μέγεθος εγγÏαφής, χωÏίς να υπολογίζονται τα blobs, είναι %ld. ΠÏέπει να οÏίσετε κάποια πεδία σαν blobs"
+ hun "Tul nagy sormeret. A maximalis sormeret (nem szamolva a blob objektumokat) %ld. Nehany mezot meg kell valtoztatnia"
+ ita "Riga troppo grande. La massima grandezza di una riga, non contando i BLOB, e` %ld. Devi cambiare alcuni campi in BLOB"
+ jpn "行サイズãŒå¤§ãã™ãŽã¾ã™ã€‚ã“ã®è¡¨ã®æœ€å¤§è¡Œã‚µã‚¤ã‚ºã¯ BLOB ã‚’å«ã¾ãšã« %ld ã§ã™ã€‚æ ¼ç´æ™‚ã®ã‚ªãƒ¼ãƒãƒ¼ãƒ˜ãƒƒãƒ‰ã‚‚å«ã¾ã‚Œã¾ã™(マニュアルを確èªã—ã¦ãã ã•ã„)。列をTEXTã¾ãŸã¯BLOBã«å¤‰æ›´ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚"
+ kor "너무 í° row 사ì´ì¦ˆìž…니다. BLOB를 계산하지 않고 최대 row 사ì´ì¦ˆëŠ” %ld입니다. ì–¼ë§ˆê°„ì˜ í•„ë“œë“¤ì„ BLOB로 바꾸셔야 ê² êµ°ìš”.."
+ por "Tamanho de linha grande demais. O máximo tamanho de linha, não contando BLOBs, é %ld. Você tem que mudar alguns campos para BLOBs"
+ rum "Marimea liniei (row) prea mare. Marimea maxima a liniei, excluzind BLOB-urile este de %ld. Trebuie sa schimbati unele cimpuri in BLOB-uri"
+ rus "Слишком большой размер запиÑи. МакÑимальный размер Ñтроки, иÑÐºÐ»ÑŽÑ‡Ð°Ñ Ð¿Ð¾Ð»Ñ BLOB, - %ld. Возможно, вам Ñледует изменить тип некоторых полей на BLOB"
+ serbian "Prevelik slog. Maksimalna veliÄina sloga, ne raÄunajući BLOB polja, je %ld. Trebali bi da promenite tip nekih polja u BLOB"
+ slo "Riadok je príliš veľký. Maximálna veľkosť riadku, okrem 'BLOB', je %ld. Musíte zmeniť niektoré položky na BLOB"
+ spa "Tamaño de línea muy grande. Máximo tamaño de línea, no contando blob, es %ld. Tu tienes que cambiar algunos campos para blob"
+ swe "För stor total radlängd. Den högst tillåtna radlängden, förutom BLOBs, är %ld. Ändra några av dina fält till BLOB"
+ ukr "Задовга Ñтрока. Ðайбільшою довжиною Ñтроки, не рахуючи BLOB, Ñ” %ld. Вам потрібно привеÑти деÑкі Ñтовбці до типу BLOB"
+ER_STACK_OVERRUN
+ cze "PÅ™eteÄení zásobníku threadu: použito %ld z %ld. Použijte 'mysqld --thread_stack=#' k zadání vÄ›tšího zásobníku"
+ dan "Thread stack brugt: Brugt: %ld af en %ld stak. Brug 'mysqld --thread_stack=#' for at allokere en større stak om nødvendigt"
+ nla "Thread stapel overrun: Gebruikte: %ld van een %ld stack. Gebruik 'mysqld --thread_stack=#' om een grotere stapel te definieren (indien noodzakelijk)."
+ eng "Thread stack overrun: Used: %ld of a %ld stack. Use 'mysqld --thread_stack=#' to specify a bigger stack if needed"
+ fre "Débordement de la pile des tâches (Thread stack). Utilisées: %ld pour une pile de %ld. Essayez 'mysqld --thread_stack=#' pour indiquer une plus grande valeur"
+ ger "Thread-Stack-Überlauf. Benutzt: %ld von %ld Stack. 'mysqld --thread_stack=#' verwenden, um bei Bedarf einen größeren Stack anzulegen"
+ greek "Stack overrun στο thread: Used: %ld of a %ld stack. ΠαÏακαλώ χÏησιμοποιείστε 'mysqld --thread_stack=#' για να οÏίσετε ένα μεγαλÏτεÏο stack αν χÏειάζεται"
+ hun "Thread verem tullepes: Used: %ld of a %ld stack. Hasznalja a 'mysqld --thread_stack=#' nagyobb verem definialasahoz"
+ ita "Thread stack overrun: Usati: %ld di uno stack di %ld. Usa 'mysqld --thread_stack=#' per specificare uno stack piu` grande."
+ jpn "スレッドスタックä¸è¶³ã§ã™(使用: %ld ; サイズ: %ld)。必è¦ã«å¿œã˜ã¦ã€ã‚ˆã‚Šå¤§ãã„値㧠'mysqld --thread_stack=#' ã®æŒ‡å®šã‚’ã—ã¦ãã ã•ã„。"
+ kor "쓰레드 스íƒì´ 넘쳤습니다. 사용: %ldê°œ 스íƒ: %ldê°œ. 만약 필요시 ë”í° ìŠ¤íƒì„ ì›í• ë•Œì—는 'mysqld --thread_stack=#' 를 ì •ì˜í•˜ì„¸ìš”"
+ por "Estouro da pilha do 'thread'. Usados %ld de uma pilha de %ld. Use 'mysqld --thread_stack=#' para especificar uma pilha maior, se necessário"
+ rum "Stack-ul thread-ului a fost depasit (prea mic): Folositi: %ld intr-un stack de %ld. Folositi 'mysqld --thread_stack=#' ca sa specifici un stack mai mare"
+ rus "Стек потоков переполнен: иÑпользовано: %ld из %ld Ñтека. ПрименÑйте 'mysqld --thread_stack=#' Ð´Ð»Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐµÐ³Ð¾ размера Ñтека, еÑли необходимо"
+ serbian "Prepisivanje thread stack-a: Upotrebljeno: %ld od %ld stack memorije. Upotrebite 'mysqld --thread_stack=#' da navedete veći stack ako je potrebno"
+ slo "PreteÄenie zásobníku vlákna: použité: %ld z %ld. Použite 'mysqld --thread_stack=#' k zadaniu väÄÅ¡ieho zásobníka"
+ spa "Sobrecarga de la pila de thread: Usada: %ld de una %ld pila. Use 'mysqld --thread_stack=#' para especificar una mayor pila si necesario"
+ swe "Trådstacken tog slut: Har använt %ld av %ld bytes. Använd 'mysqld --thread_stack=#' ifall du behöver en större stack"
+ ukr "Стек гілок переповнено: ВикориÑтано: %ld з %ld. ВикориÑтовуйте 'mysqld --thread_stack=#' аби зазначити більший Ñтек, Ñкщо необхідно"
+ER_WRONG_OUTER_JOIN 42000
+ cze "V OUTER JOIN byl nalezen křížový odkaz. Prověřte ON podmínky"
+ dan "Krydsreferencer fundet i OUTER JOIN; check dine ON conditions"
+ nla "Gekruiste afhankelijkheid gevonden in OUTER JOIN. Controleer uw ON-conditions"
+ eng "Cross dependency found in OUTER JOIN; examine your ON conditions"
+ est "Ristsõltuvus OUTER JOIN klauslis. Kontrolli oma ON tingimusi"
+ fre "Dépendance croisée dans une clause OUTER JOIN. Vérifiez la condition ON"
+ ger "OUTER JOIN enthält fehlerhafte Abhängigkeiten. In ON verwendete Bedingungen überprüfen"
+ greek "Cross dependency βÏέθηκε σε OUTER JOIN. ΠαÏακαλώ εξετάστε τις συνθήκες που θέσατε στο ON"
+ hun "Keresztfuggoseg van az OUTER JOIN-ban. Ellenorizze az ON felteteleket"
+ ita "Trovata una dipendenza incrociata nella OUTER JOIN. Controlla le condizioni ON"
+ jpn "OUTER JOINã«ç›¸äº’ä¾å­˜ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸã€‚ONå¥ã®æ¡ä»¶ã‚’確èªã—ã¦ä¸‹ã•ã„。"
+ por "Dependência cruzada encontrada em junção externa (OUTER JOIN); examine as condições utilizadas nas cláusulas 'ON'"
+ rum "Dependinta incrucisata (cross dependency) gasita in OUTER JOIN. Examinati conditiile ON"
+ rus "Ð’ OUTER JOIN обнаружена перекреÑÑ‚Ð½Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑть. Внимательно проанализируйте Ñвои уÑÐ»Ð¾Ð²Ð¸Ñ ON"
+ serbian "Unakrsna zavisnost pronađena u komandi 'OUTER JOIN'. Istražite vaše 'ON' uslove"
+ slo "V OUTER JOIN bol nájdený krížový odkaz. Skontrolujte podmienky ON"
+ spa "Dependencia cruzada encontrada en OUTER JOIN. Examine su condición ON"
+ swe "Felaktigt referens i OUTER JOIN. Kontrollera ON-uttrycket"
+ ukr "ПерехреÑна залежніÑть у OUTER JOIN. Перевірте умову ON"
+ER_NULL_COLUMN_IN_INDEX 42000
+ eng "Table handler doesn't support NULL in given index. Please change column '%-.192s' to be NOT NULL or use another handler"
+ swe "Tabell hanteraren kan inte indexera NULL kolumner för den givna index typen. Ändra '%-.192s' till NOT NULL eller använd en annan hanterare"
+ER_CANT_FIND_UDF
+ cze "Nemohu naÄíst funkci '%-.192s'"
+ dan "Kan ikke læse funktionen '%-.192s'"
+ nla "Kan functie '%-.192s' niet laden"
+ eng "Can't load function '%-.192s'"
+ est "Ei suuda avada funktsiooni '%-.192s'"
+ fre "Imposible de charger la fonction '%-.192s'"
+ ger "Kann Funktion '%-.192s' nicht laden"
+ greek "Δεν είναι δυνατή η διαδικασία load για τη συνάÏτηση '%-.192s'"
+ hun "A(z) '%-.192s' fuggveny nem toltheto be"
+ ita "Impossibile caricare la funzione '%-.192s'"
+ jpn "関数 '%-.192s' をロードã§ãã¾ã›ã‚“。"
+ kor "'%-.192s' 함수를 로드하지 못했습니다."
+ por "Não pode carregar a função '%-.192s'"
+ rum "Nu pot incarca functia '%-.192s'"
+ rus "Ðевозможно загрузить функцию '%-.192s'"
+ serbian "Ne mogu da uÄitam funkciju '%-.192s'"
+ slo "Nemôžem naÄítaÅ¥ funkciu '%-.192s'"
+ spa "No puedo cargar función '%-.192s'"
+ swe "Kan inte ladda funktionen '%-.192s'"
+ ukr "Ðе можу завантажити функцію '%-.192s'"
+ER_CANT_INITIALIZE_UDF
+ cze "Nemohu inicializovat funkci '%-.192s'; %-.80s"
+ dan "Kan ikke starte funktionen '%-.192s'; %-.80s"
+ nla "Kan functie '%-.192s' niet initialiseren; %-.80s"
+ eng "Can't initialize function '%-.192s'; %-.80s"
+ est "Ei suuda algväärtustada funktsiooni '%-.192s'; %-.80s"
+ fre "Impossible d'initialiser la fonction '%-.192s'; %-.80s"
+ ger "Kann Funktion '%-.192s' nicht initialisieren: %-.80s"
+ greek "Δεν είναι δυνατή η έναÏξη της συνάÏτησης '%-.192s'; %-.80s"
+ hun "A(z) '%-.192s' fuggveny nem inicializalhato; %-.80s"
+ ita "Impossibile inizializzare la funzione '%-.192s'; %-.80s"
+ jpn "関数 '%-.192s' ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“。; %-.80s"
+ kor "'%-.192s' 함수를 초기화 하지 못했습니다.; %-.80s"
+ por "Não pode inicializar a função '%-.192s' - '%-.80s'"
+ rum "Nu pot initializa functia '%-.192s'; %-.80s"
+ rus "Ðевозможно инициализировать функцию '%-.192s'; %-.80s"
+ serbian "Ne mogu da inicijalizujem funkciju '%-.192s'; %-.80s"
+ slo "Nemôžem inicializovať funkciu '%-.192s'; %-.80s"
+ spa "No puedo inicializar función '%-.192s'; %-.80s"
+ swe "Kan inte initialisera funktionen '%-.192s'; '%-.80s'"
+ ukr "Ðе можу ініціалізувати функцію '%-.192s'; %-.80s"
+ER_UDF_NO_PATHS
+ cze "Pro sdílenou knihovnu nejsou povoleny cesty"
+ dan "Angivelse af sti ikke tilladt for delt bibliotek"
+ nla "Geen pad toegestaan voor shared library"
+ eng "No paths allowed for shared library"
+ est "Teegi nimes ei tohi olla kataloogi"
+ fre "Chemin interdit pour les bibliothèques partagées"
+ ger "Keine Pfade gestattet für Shared Library"
+ greek "Δεν βÏέθηκαν paths για την shared library"
+ hun "Nincs ut a megosztott konyvtarakhoz (shared library)"
+ ita "Non sono ammessi path per le librerie condivisa"
+ jpn "共有ライブラリã«ã¯ãƒ‘スを指定ã§ãã¾ã›ã‚“。"
+ kor "공유 ë¼ì´ë²„러리를 위한 패스가 ì •ì˜ë˜ì–´ 있지 않습니다."
+ por "Não há caminhos (paths) permitidos para biblioteca compartilhada"
+ rum "Nici un paths nu e permis pentru o librarie shared"
+ rus "ÐедопуÑтимо указывать пути Ð´Ð»Ñ Ð´Ð¸Ð½Ð°Ð¼Ð¸Ñ‡ÐµÑких библиотек"
+ serbian "Ne postoje dozvoljene putanje do share-ovane biblioteke"
+ slo "Neprípustné žiadne cesty k zdieľanej knižnici"
+ spa "No pasos permitidos para librarias conjugadas"
+ swe "Man får inte ange sökväg för dynamiska bibliotek"
+ ukr "Ðе дозволено викориÑтовувати путі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð´Ñ–Ð»ÑŽÐ²Ð°Ð½Ð¸Ñ… бібліотек"
+ER_UDF_EXISTS
+ cze "Funkce '%-.192s' již existuje"
+ dan "Funktionen '%-.192s' findes allerede"
+ nla "Functie '%-.192s' bestaat reeds"
+ eng "Function '%-.192s' already exists"
+ est "Funktsioon '%-.192s' juba eksisteerib"
+ fre "La fonction '%-.192s' existe déjà"
+ ger "Funktion '%-.192s' existiert schon"
+ greek "Η συνάÏτηση '%-.192s' υπάÏχει ήδη"
+ hun "A '%-.192s' fuggveny mar letezik"
+ ita "La funzione '%-.192s' esiste gia`"
+ jpn "関数 '%-.192s' ã¯ã™ã§ã«å®šç¾©ã•れã¦ã„ã¾ã™ã€‚"
+ kor "'%-.192s' 함수는 ì´ë¯¸ 존재합니다."
+ por "Função '%-.192s' já existe"
+ rum "Functia '%-.192s' exista deja"
+ rus "Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ '%-.192s' уже ÑущеÑтвует"
+ serbian "Funkcija '%-.192s' već postoji"
+ slo "Funkcia '%-.192s' už existuje"
+ spa "Función '%-.192s' ya existe"
+ swe "Funktionen '%-.192s' finns redan"
+ ukr "Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ '%-.192s' вже Ñ–Ñнує"
+ER_CANT_OPEN_LIBRARY
+ cze "Nemohu otevřít sdílenou knihovnu '%-.192s' (errno: %d, %-.128s)"
+ dan "Kan ikke åbne delt bibliotek '%-.192s' (errno: %d, %-.128s)"
+ nla "Kan shared library '%-.192s' niet openen (Errcode: %d, %-.128s)"
+ eng "Can't open shared library '%-.192s' (errno: %d, %-.128s)"
+ est "Ei suuda avada jagatud teeki '%-.192s' (veakood: %d, %-.128s)"
+ fre "Impossible d'ouvrir la bibliothèque partagée '%-.192s' (errno: %d, %-.128s)"
+ ger "Kann Shared Library '%-.192s' nicht öffnen (Fehler: %d, %-.128s)"
+ greek "Δεν είναι δυνατή η ανάγνωση της shared library '%-.192s' (κωδικός λάθους: %d, %-.128s)"
+ hun "A(z) '%-.192s' megosztott konyvtar nem hasznalhato (hibakod: %d, %-.128s)"
+ ita "Impossibile aprire la libreria condivisa '%-.192s' (errno: %d, %-.128s)"
+ jpn "共有ライブラリ '%-.192s' ã‚’é–‹ã事ãŒã§ãã¾ã›ã‚“。(エラー番å·: %d, %-.128s)"
+ kor "'%-.192s' 공유 ë¼ì´ë²„러리를 열수 없습니다.(ì—러번호: %d, %-.128s)"
+ nor "Can't open shared library '%-.192s' (errno: %d, %-.128s)"
+ norwegian-ny "Can't open shared library '%-.192s' (errno: %d, %-.128s)"
+ pol "Can't open shared library '%-.192s' (errno: %d, %-.128s)"
+ por "Não pode abrir biblioteca compartilhada '%-.192s' (erro no. %d, %-.128s)"
+ rum "Nu pot deschide libraria shared '%-.192s' (Eroare: %d, %-.128s)"
+ rus "Ðевозможно открыть динамичеÑкую библиотеку '%-.192s' (ошибка: %d, %-.128s)"
+ serbian "Ne mogu da otvorim share-ovanu biblioteku '%-.192s' (errno: %d, %-.128s)"
+ slo "Nemôžem otvoriť zdieľanú knižnicu '%-.192s' (chybový kód: %d, %-.128s)"
+ spa "No puedo abrir libraria conjugada '%-.192s' (errno: %d, %-.128s)"
+ swe "Kan inte öppna det dynamiska biblioteket '%-.192s' (Felkod: %d, %-.128s)"
+ ukr "Ðе можу відкрити розділювану бібліотеку '%-.192s' (помилка: %d, %-.128s)"
+ER_CANT_FIND_DL_ENTRY
+ cze "Nemohu najít funkci '%-.128s' v knihovně"
+ dan "Kan ikke finde funktionen '%-.128s' i bibliotek"
+ nla "Kan functie '%-.128s' niet in library vinden"
+ eng "Can't find symbol '%-.128s' in library"
+ est "Ei leia funktsiooni '%-.128s' antud teegis"
+ fre "Impossible de trouver la fonction '%-.128s' dans la bibliothèque"
+ ger "Kann Funktion '%-.128s' in der Library nicht finden"
+ greek "Δεν είναι δυνατή η ανεÏÏεση της συνάÏτησης '%-.128s' στην βιβλιοθήκη"
+ hun "A(z) '%-.128s' fuggveny nem talalhato a konyvtarban"
+ ita "Impossibile trovare la funzione '%-.128s' nella libreria"
+ jpn "関数 '%-.128s' ã¯å…±æœ‰ãƒ©ã‚¤ãƒ–ラリー中ã«ã‚りã¾ã›ã‚“。"
+ kor "ë¼ì´ë²„러리ì—서 '%-.128s' 함수를 ì°¾ì„ ìˆ˜ 없습니다."
+ por "Não pode encontrar a função '%-.128s' na biblioteca"
+ rum "Nu pot gasi functia '%-.128s' in libraria"
+ rus "Ðевозможно отыÑкать Ñимвол '%-.128s' в библиотеке"
+ serbian "Ne mogu da pronadjem funkciju '%-.128s' u biblioteci"
+ slo "Nemôžem nájsť funkciu '%-.128s' v knižnici"
+ spa "No puedo encontrar función '%-.128s' en libraria"
+ swe "Hittar inte funktionen '%-.128s' in det dynamiska biblioteket"
+ ukr "Ðе можу знайти функцію '%-.128s' у бібліотеці"
+ER_FUNCTION_NOT_DEFINED
+ cze "Funkce '%-.192s' není definována"
+ dan "Funktionen '%-.192s' er ikke defineret"
+ nla "Functie '%-.192s' is niet gedefinieerd"
+ eng "Function '%-.192s' is not defined"
+ est "Funktsioon '%-.192s' ei ole defineeritud"
+ fre "La fonction '%-.192s' n'est pas définie"
+ ger "Funktion '%-.192s' ist nicht definiert"
+ greek "Η συνάÏτηση '%-.192s' δεν έχει οÏισθεί"
+ hun "A '%-.192s' fuggveny nem definialt"
+ ita "La funzione '%-.192s' non e` definita"
+ jpn "関数 '%-.192s' ã¯å®šç¾©ã•れã¦ã„ã¾ã›ã‚“。"
+ kor "'%-.192s' 함수가 ì •ì˜ë˜ì–´ 있지 않습니다."
+ por "Função '%-.192s' não está definida"
+ rum "Functia '%-.192s' nu e definita"
+ rus "Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ '%-.192s' не определена"
+ serbian "Funkcija '%-.192s' nije definisana"
+ slo "Funkcia '%-.192s' nie je definovaná"
+ spa "Función '%-.192s' no está definida"
+ swe "Funktionen '%-.192s' är inte definierad"
+ ukr "Функцію '%-.192s' не визначено"
+ER_HOST_IS_BLOCKED
+ cze "Stroj '%-.64s' je zablokován kvůli mnoha chybám při připojování. Odblokujete použitím 'mysqladmin flush-hosts'"
+ dan "Værten '%-.64s' er blokeret på grund af mange fejlforespørgsler. Lås op med 'mysqladmin flush-hosts'"
+ nla "Host '%-.64s' is geblokkeeerd vanwege te veel verbindings fouten. Deblokkeer met 'mysqladmin flush-hosts'"
+ eng "Host '%-.64s' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'"
+ est "Masin '%-.64s' on blokeeritud hulgaliste ühendusvigade tõttu. Blokeeringu saab tühistada 'mysqladmin flush-hosts' käsuga"
+ fre "L'hôte '%-.64s' est bloqué à cause d'un trop grand nombre d'erreur de connexion. Débloquer le par 'mysqladmin flush-hosts'"
+ ger "Host '%-.64s' blockiert wegen zu vieler Verbindungsfehler. Aufheben der Blockierung mit 'mysqladmin flush-hosts'"
+ greek "Ο υπολογιστής '%-.64s' έχει αποκλεισθεί λόγω πολλαπλών λαθών σÏνδεσης. ΠÏοσπαθήστε να διοÏώσετε με 'mysqladmin flush-hosts'"
+ hun "A '%-.64s' host blokkolodott, tul sok kapcsolodasi hiba miatt. Hasznalja a 'mysqladmin flush-hosts' parancsot"
+ ita "Sistema '%-.64s' bloccato a causa di troppi errori di connessione. Per sbloccarlo: 'mysqladmin flush-hosts'"
+ jpn "接続エラーãŒå¤šã„ãŸã‚ã€ãƒ›ã‚¹ãƒˆ '%-.64s' ã¯æ‹’å¦ã•れã¾ã—ãŸã€‚'mysqladmin flush-hosts' ã§è§£é™¤ã§ãã¾ã™ã€‚"
+ kor "너무 ë§Žì€ ì—°ê²°ì˜¤ë¥˜ë¡œ ì¸í•˜ì—¬ 호스트 '%-.64s'는 블ë½ë˜ì—ˆìŠµë‹ˆë‹¤. 'mysqladmin flush-hosts'를 ì´ìš©í•˜ì—¬ 블ë½ì„ 해제하세요"
+ por "'Host' '%-.64s' está bloqueado devido a muitos erros de conexão. Desbloqueie com 'mysqladmin flush-hosts'"
+ rum "Host-ul '%-.64s' e blocat din cauza multelor erori de conectie. Poti deploca folosind 'mysqladmin flush-hosts'"
+ rus "ХоÑÑ‚ '%-.64s' заблокирован из-за Ñлишком большого количеÑтва ошибок ÑоединениÑ. Разблокировать его можно Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ 'mysqladmin flush-hosts'"
+ serbian "Host '%-.64s' je blokiran zbog previše grešaka u konekciji. Možete ga odblokirati pomoću komande 'mysqladmin flush-hosts'"
+ spa "Servidor '%-.64s' está bloqueado por muchos errores de conexión. Desbloquear con 'mysqladmin flush-hosts'"
+ swe "Denna dator, '%-.64s', är blockerad pga många felaktig paket. Gör 'mysqladmin flush-hosts' för att ta bort alla blockeringarna"
+ ukr "ХоÑÑ‚ '%-.64s' заблоковано з причини великої кількоÑті помилок з'єднаннÑ. Ð”Ð»Ñ Ñ€Ð¾Ð·Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸Ñтовуйте 'mysqladmin flush-hosts'"
+ER_HOST_NOT_PRIVILEGED
+ cze "Stroj '%-.64s' nemá povoleno se k tomuto MySQL serveru připojit"
+ dan "Værten '%-.64s' kan ikke tilkoble denne MariaDB-server"
+ nla "Het is host '%-.64s' is niet toegestaan verbinding te maken met deze MariaDB server"
+ eng "Host '%-.64s' is not allowed to connect to this MariaDB server"
+ est "Masinal '%-.64s' puudub ligipääs sellele MariaDB serverile"
+ fre "Le hôte '%-.64s' n'est pas authorisé à se connecter à ce serveur MariaDB"
+ ger "Host '%-.64s' hat keine Berechtigung, sich mit diesem MariaDB-Server zu verbinden"
+ greek "Ο υπολογιστής '%-.64s' δεν έχει δικαίωμα σÏνδεσης με τον MariaDB server"
+ hun "A '%-.64s' host szamara nem engedelyezett a kapcsolodas ehhez a MariaDB szerverhez"
+ ita "Al sistema '%-.64s' non e` consentita la connessione a questo server MariaDB"
+ jpn "ホスト '%-.64s' ã‹ã‚‰ã®ã“ã® MySQL server ã¸ã®æŽ¥ç¶šã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。"
+ kor "'%-.64s' 호스트는 ì´ MariaDBì„œë²„ì— ì ‘ì†í•  허가를 받지 못했습니다."
+ por "'Host' '%-.64s' não tem permissão para se conectar com este servidor MariaDB"
+ rum "Host-ul '%-.64s' nu este permis a se conecta la aceste server MariaDB"
+ rus "ХоÑту '%-.64s' не разрешаетÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ð°Ñ‚ÑŒÑÑ Ðº Ñтому Ñерверу MariaDB"
+ serbian "Host-u '%-.64s' nije dozvoljeno da se konektuje na ovaj MariaDB server"
+ spa "Servidor '%-.64s' no está permitido para conectar con este servidor MariaDB"
+ swe "Denna dator, '%-.64s', har inte privileger att använda denna MariaDB server"
+ ukr "ХоÑту '%-.64s' не доволено зв'ÑзуватиÑÑŒ з цим Ñервером MariaDB"
+ER_PASSWORD_ANONYMOUS_USER 42000
+ cze "Používáte MySQL jako anonymní uživatel a anonymní uživatelé nemají povoleno měnit hesla"
+ dan "Du bruger MariaDB som anonym bruger. Anonyme brugere må ikke ændre adgangskoder"
+ nla "U gebruikt MariaDB als anonieme gebruiker en deze mogen geen wachtwoorden wijzigen"
+ eng "You are using MariaDB as an anonymous user and anonymous users are not allowed to change passwords"
+ est "Te kasutate MariaDB-i anonüümse kasutajana, kelledel pole parooli muutmise õigust"
+ fre "Vous utilisez un utilisateur anonyme et les utilisateurs anonymes ne sont pas autorisés à changer les mots de passe"
+ ger "Sie benutzen MariaDB als anonymer Benutzer und dürfen daher keine Passwörter ändern"
+ greek "ΧÏησιμοποιείτε την MariaDB σαν anonymous user και έτσι δεν μποÏείτε να αλλάξετε τα passwords άλλων χÏηστών"
+ hun "Nevtelen (anonymous) felhasznalokent nem negedelyezett a jelszovaltoztatas"
+ ita "Impossibile cambiare la password usando MariaDB come utente anonimo"
+ jpn "MySQL を匿åユーザーã§ä½¿ç”¨ã—ã¦ã„ã‚‹ã®ã§ã€ãƒ‘スワードã®å¤‰æ›´ã¯ã§ãã¾ã›ã‚“。"
+ kor "ë‹¹ì‹ ì€ MariaDBì„œë²„ì— ìµëª…ì˜ ì‚¬ìš©ìžë¡œ ì ‘ì†ì„ 하셨습니다.ìµëª…ì˜ ì‚¬ìš©ìžëŠ” 암호를 변경할 수 없습니다."
+ por "Você está usando o MariaDB como usuário anônimo e usuários anônimos não têm permissão para mudar senhas"
+ rum "Dumneavoastra folositi MariaDB ca un utilizator anonim si utilizatorii anonimi nu au voie sa schime parolele"
+ rus "Ð’Ñ‹ иÑпользуете MariaDB от имени анонимного пользователÑ, а анонимным пользователÑм не разрешаетÑÑ Ð¼ÐµÐ½Ñть пароли"
+ serbian "Vi koristite MariaDB kao anonimni korisnik a anonimnim korisnicima nije dozvoljeno da menjaju lozinke"
+ spa "Tu estás usando MariaDB como un usuario anonimo y usuarios anonimos no tienen permiso para cambiar las claves"
+ swe "Du använder MariaDB som en anonym användare och som sådan får du inte ändra ditt lösenord"
+ ukr "Ви викориÑтовуєте MariaDB Ñк анонімний кориÑтувач, тому вам не дозволено змінювати паролі"
+ER_PASSWORD_NOT_ALLOWED 42000
+ cze "Na změnu hesel ostatním musíte mít právo provést update tabulek v databázi mysql"
+ dan "Du skal have tilladelse til at opdatere tabeller i MariaDB databasen for at ændre andres adgangskoder"
+ nla "U moet tabel update priveleges hebben in de mysql database om wachtwoorden voor anderen te mogen wijzigen"
+ eng "You must have privileges to update tables in the mysql database to be able to change passwords for others"
+ est "Teiste paroolide muutmiseks on nõutav tabelite muutmisõigus 'mysql' andmebaasis"
+ fre "Vous devez avoir le privilège update sur les tables de la base de donnée mysql pour pouvoir changer les mots de passe des autres"
+ ger "Sie benötigen die Berechtigung zum Aktualisieren von Tabellen in der Datenbank 'mysql', um die Passwörter anderer Benutzer ändern zu können"
+ greek "ΠÏέπει να έχετε δικαίωμα διόÏθωσης πινάκων (update) στη βάση δεδομένων mysql για να μποÏείτε να αλλάξετε τα passwords άλλων χÏηστών"
+ hun "Onnek tabla-update joggal kell rendelkeznie a mysql adatbazisban masok jelszavanak megvaltoztatasahoz"
+ ita "E` necessario il privilegio di update sulle tabelle del database mysql per cambiare le password per gli altri utenti"
+ jpn "ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ‘スワードを変更ã™ã‚‹ãŸã‚ã«ã¯ã€mysqlデータベースã®è¡¨ã‚’æ›´æ–°ã™ã‚‹æ¨©é™ãŒå¿…è¦ã§ã™ã€‚"
+ kor "ë‹¹ì‹ ì€ ë‹¤ë¥¸ì‚¬ìš©ìžë“¤ì˜ 암호를 변경할 수 있ë„ë¡ ë°ì´íƒ€ë² ì´ìФ ë³€ê²½ê¶Œí•œì„ ê°€ì ¸ì•¼ 합니다."
+ por "Você deve ter privilégios para atualizar tabelas no banco de dados mysql para ser capaz de mudar a senha de outros"
+ rum "Trebuie sa aveti privilegii sa actualizati tabelele in bazele de date mysql ca sa puteti sa schimati parolele altora"
+ rus "Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾ чтобы изменÑть пароли других пользователей, у Ð²Ð°Ñ Ð´Ð¾Ð»Ð¶Ð½Ñ‹ быть привилегии на изменение таблиц в базе данных mysql"
+ serbian "Morate imati privilegije da možete da update-ujete određene tabele ako želite da menjate lozinke za druge korisnike"
+ spa "Tu debes de tener permiso para actualizar tablas en la base de datos mysql para cambiar las claves para otros"
+ swe "För att ändra lösenord för andra måste du ha rättigheter att uppdatera mysql-databasen"
+ ukr "Ви повині мати право на Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†ÑŒ у базі данних mysql, аби мати можливіÑть змінювати пароль іншим"
+ER_PASSWORD_NO_MATCH 28000
+ cze "V tabulce user není žádný odpovídající řádek"
+ dan "Kan ikke finde nogen tilsvarende poster i bruger tabellen"
+ nla "Kan geen enkele passende rij vinden in de gebruikers tabel"
+ eng "Can't find any matching row in the user table"
+ est "Ei leia vastavat kirjet kasutajate tabelis"
+ fre "Impossible de trouver un enregistrement correspondant dans la table user"
+ ger "Kann keinen passenden Datensatz in Tabelle 'user' finden"
+ greek "Δεν είναι δυνατή η ανεÏÏεση της αντίστοιχης εγγÏαφής στον πίνακα των χÏηστών"
+ hun "Nincs megegyezo sor a user tablaban"
+ ita "Impossibile trovare la riga corrispondente nella tabella user"
+ jpn "ユーザーテーブルã«è©²å½“ã™ã‚‹ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
+ kor "ì‚¬ìš©ìž í…Œì´ë¸”ì—서 ì¼ì¹˜í•˜ëŠ” ê²ƒì„ ì°¾ì„ ìˆ˜ ì—†ì니다."
+ por "Não pode encontrar nenhuma linha que combine na tabela usuário (user table)"
+ rum "Nu pot gasi nici o linie corespunzatoare in tabela utilizatorului"
+ rus "Ðевозможно отыÑкать подходÑщую запиÑÑŒ в таблице пользователей"
+ serbian "Ne mogu da pronađem odgovarajući slog u 'user' tabeli"
+ spa "No puedo encontrar una línea correponsdiente en la tabla user"
+ swe "Hittade inte användaren i 'user'-tabellen"
+ ukr "Ðе можу знайти відповідних запиÑів у таблиці кориÑтувача"
+ER_UPDATE_INFO
+ cze "Nalezených řádků: %ld Změněno: %ld Varování: %ld"
+ dan "Poster fundet: %ld Ændret: %ld Advarsler: %ld"
+ nla "Passende rijen: %ld Gewijzigd: %ld Waarschuwingen: %ld"
+ eng "Rows matched: %ld Changed: %ld Warnings: %ld"
+ est "Sobinud kirjeid: %ld Muudetud: %ld Hoiatusi: %ld"
+ fre "Enregistrements correspondants: %ld Modifiés: %ld Warnings: %ld"
+ ger "Datensätze gefunden: %ld Geändert: %ld Warnungen: %ld"
+ hun "Megegyezo sorok szama: %ld Valtozott: %ld Warnings: %ld"
+ ita "Rows riconosciute: %ld Cambiate: %ld Warnings: %ld"
+ jpn "該当ã—ãŸè¡Œ: %ld 変更: %ld 警告: %ld"
+ kor "ì¼ì¹˜í•˜ëŠ” Rows : %ldê°œ 변경ë¨: %ldê°œ 경고: %ldê°œ"
+ por "Linhas que combinaram: %ld - Alteradas: %ld - Avisos: %ld"
+ rum "Linii identificate (matched): %ld Schimbate: %ld Atentionari (warnings): %ld"
+ rus "Совпало запиÑей: %ld Изменено: %ld Предупреждений: %ld"
+ serbian "Odgovarajućih slogova: %ld Promenjeno: %ld Upozorenja: %ld"
+ spa "Líneas correspondientes: %ld Cambiadas: %ld Avisos: %ld"
+ swe "Rader: %ld Uppdaterade: %ld Varningar: %ld"
+ ukr "ЗапиÑів відповідає: %ld Змінено: %ld ЗаÑтережень: %ld"
+ER_CANT_CREATE_THREAD
+ cze "Nemohu vytvoÅ™it nový thread (errno %M). Pokud je jeÅ¡tÄ› nÄ›jaká volná paměť, podívejte se do manuálu na Äást o chybách specifických pro jednotlivé operaÄní systémy"
+ dan "Kan ikke danne en ny tråd (fejl nr. %M). Hvis computeren ikke er løbet tør for hukommelse, kan du se i brugervejledningen for en mulig operativ-system - afhængig fejl"
+ nla "Kan geen nieuwe thread aanmaken (Errcode: %M). Indien er geen tekort aan geheugen is kunt u de handleiding consulteren over een mogelijke OS afhankelijke fout"
+ eng "Can't create a new thread (errno %M); if you are not out of available memory, you can consult the manual for a possible OS-dependent bug"
+ est "Ei suuda luua uut lõime (veakood %M). Kui mälu ei ole otsas, on tõenäoliselt tegemist operatsioonisüsteemispetsiifilise veaga"
+ fre "Impossible de créer une nouvelle tâche (errno %M). S'il reste de la mémoire libre, consultez le manual pour trouver un éventuel bug dépendant de l'OS"
+ ger "Kann keinen neuen Thread erzeugen (Fehler: %M). Sollte noch Speicher verfügbar sein, bitte im Handbuch wegen möglicher Fehler im Betriebssystem nachschlagen"
+ hun "Uj thread letrehozasa nem lehetseges (Hibakod: %M). Amenyiben van meg szabad memoria, olvassa el a kezikonyv operacios rendszerfuggo hibalehetosegekrol szolo reszet"
+ ita "Impossibile creare un nuovo thread (errno %M). Se non ci sono problemi di memoria disponibile puoi consultare il manuale per controllare possibili problemi dipendenti dal SO"
+ jpn "æ–°è¦ã«ã‚¹ãƒ¬ãƒƒãƒ‰ã‚’作æˆã§ãã¾ã›ã‚“。(ã‚¨ãƒ©ãƒ¼ç•ªå· %M) ã‚‚ã—も使用å¯èƒ½ãƒ¡ãƒ¢ãƒªãƒ¼ã®ä¸è¶³ã§ãªã‘れã°ã€OSä¾å­˜ã®ãƒã‚°ã§ã‚ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚"
+ kor "새로운 쓰레드를 만들 수 없습니다.(ì—러번호 %M). 만약 여유메모리가 있다면 OS-dependent버그 ì˜ ë©”ë‰´ì–¼ ë¶€ë¶„ì„ ì°¾ì•„ë³´ì‹œì˜¤."
+ nor "Can't create a new thread (errno %M); if you are not out of available memory you can consult the manual for any possible OS dependent bug"
+ norwegian-ny "Can't create a new thread (errno %M); if you are not out of available memory you can consult the manual for any possible OS dependent bug"
+ pol "Can't create a new thread (errno %M); if you are not out of available memory you can consult the manual for any possible OS dependent bug"
+ por "Não pode criar uma nova 'thread' (erro no. %M). Se você não estiver sem memória disponível, você pode consultar o manual sobre um possível 'bug' dependente do sistema operacional"
+ rum "Nu pot crea un thread nou (Eroare %M). Daca mai aveti memorie disponibila in sistem, puteti consulta manualul - ar putea exista un potential bug in legatura cu sistemul de operare"
+ rus "Ðевозможно Ñоздать новый поток (ошибка %M). ЕÑли Ñто не ÑитуациÑ, ÑвÑÐ·Ð°Ð½Ð½Ð°Ñ Ñ Ð½ÐµÑ…Ð²Ð°Ñ‚ÐºÐ¾Ð¹ памÑти, то вам Ñледует изучить документацию на предмет опиÑÐ°Ð½Ð¸Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ð¹ ошибки работы в конкретной ОС"
+ serbian "Ne mogu da kreiram novi thread (errno %M). Ako imate joÅ¡ slobodne memorije, trebali biste da pogledate u priruÄniku da li je ovo specifiÄna greÅ¡ka vaÅ¡eg operativnog sistema"
+ spa "No puedo crear un nuevo thread (errno %M). Si tu está con falta de memoria disponible, tu puedes consultar el Manual para posibles problemas con SO"
+ swe "Kan inte skapa en ny tråd (errno %M)"
+ ukr "Ðе можу Ñтворити нову гілку (помилка %M). Якщо ви не викориÑтали уÑÑŽ пам'Ñть, то прочитайте документацію до вашої ОС - можливо це помилка ОС"
+ER_WRONG_VALUE_COUNT_ON_ROW 21S01
+ cze "PoÄet sloupců neodpovídá poÄtu hodnot na řádku %lu"
+ dan "Kolonne antallet stemmer ikke overens med antallet af værdier i post %lu"
+ nla "Kolom aantal komt niet overeen met waarde aantal in rij %lu"
+ eng "Column count doesn't match value count at row %lu"
+ est "Tulpade hulk erineb väärtuste hulgast real %lu"
+ ger "Anzahl der Felder stimmt nicht mit der Anzahl der Werte in Zeile %lu überein"
+ hun "Az oszlopban talalhato ertek nem egyezik meg a %lu sorban szamitott ertekkel"
+ ita "Il numero delle colonne non corrisponde al conteggio alla riga %lu"
+ jpn "%lu 行目ã§ã€åˆ—ã®æ•°ãŒå€¤ã®æ•°ã¨ä¸€è‡´ã—ã¾ã›ã‚“。"
+ kor "Row %luì—서 칼럼 카운트와 value 카운터와 ì¼ì¹˜í•˜ì§€ 않습니다."
+ por "Contagem de colunas não confere com a contagem de valores na linha %lu"
+ rum "Numarul de coloane nu corespunde cu numarul de valori la linia %lu"
+ rus "КоличеÑтво Ñтолбцов не Ñовпадает Ñ ÐºÐ¾Ð»Ð¸Ñ‡ÐµÑтвом значений в запиÑи %lu"
+ serbian "Broj kolona ne odgovara broju vrednosti u slogu %lu"
+ spa "El número de columnas no corresponde al número en la línea %lu"
+ swe "Antalet kolumner motsvarar inte antalet värden på rad: %lu"
+ ukr "КількіÑть Ñтовбців не Ñпівпадає з кількіÑтю значень у Ñтроці %lu"
+ER_CANT_REOPEN_TABLE
+ cze "Nemohu znovuotevřít tabulku: '%-.192s"
+ dan "Kan ikke genåbne tabel '%-.192s"
+ nla "Kan tabel niet opnieuw openen: '%-.192s"
+ eng "Can't reopen table: '%-.192s'"
+ est "Ei suuda taasavada tabelit '%-.192s'"
+ fre "Impossible de réouvrir la table: '%-.192s"
+ ger "Kann Tabelle'%-.192s' nicht erneut öffnen"
+ hun "Nem lehet ujra-megnyitni a tablat: '%-.192s"
+ ita "Impossibile riaprire la tabella: '%-.192s'"
+ jpn "表をå†ã‚ªãƒ¼ãƒ—ンã§ãã¾ã›ã‚“。: '%-.192s'"
+ kor "í…Œì´ë¸”ì„ ë‹¤ì‹œ 열수 없군요: '%-.192s"
+ nor "Can't reopen table: '%-.192s"
+ norwegian-ny "Can't reopen table: '%-.192s"
+ pol "Can't reopen table: '%-.192s"
+ por "Não pode reabrir a tabela '%-.192s"
+ rum "Nu pot redeschide tabela: '%-.192s'"
+ rus "Ðевозможно заново открыть таблицу '%-.192s'"
+ serbian "Ne mogu da ponovo otvorim tabelu '%-.192s'"
+ slo "Can't reopen table: '%-.192s"
+ spa "No puedo reabrir tabla: '%-.192s"
+ swe "Kunde inte stänga och öppna tabell '%-.192s"
+ ukr "Ðе можу перевідкрити таблицю: '%-.192s'"
+ER_INVALID_USE_OF_NULL 22004
+ cze "Neplatné užití hodnoty NULL"
+ dan "Forkert brug af nulværdi (NULL)"
+ nla "Foutief gebruik van de NULL waarde"
+ eng "Invalid use of NULL value"
+ est "NULL väärtuse väärkasutus"
+ fre "Utilisation incorrecte de la valeur NULL"
+ ger "Unerlaubte Verwendung eines NULL-Werts"
+ hun "A NULL ervenytelen hasznalata"
+ ita "Uso scorretto del valore NULL"
+ jpn "NULL 値ã®ä½¿ç”¨æ–¹æ³•ãŒä¸é©åˆ‡ã§ã™ã€‚"
+ kor "NULL ê°’ì„ ìž˜ëª» 사용하셨군요..."
+ por "Uso inválido do valor NULL"
+ rum "Folosirea unei value NULL e invalida"
+ rus "Ðеправильное иÑпользование величины NULL"
+ serbian "Pogrešna upotreba vrednosti NULL"
+ spa "Invalido uso de valor NULL"
+ swe "Felaktig använding av NULL"
+ ukr "Хибне викориÑÑ‚Ð°Ð½Ð½Ñ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ NULL"
+ER_REGEXP_ERROR 42000
+ cze "Regulární výraz vrátil chybu '%-.64s'"
+ dan "Fik fejl '%-.64s' fra regexp"
+ nla "Fout '%-.64s' ontvangen van regexp"
+ eng "Got error '%-.64s' from regexp"
+ est "regexp tagastas vea '%-.64s'"
+ fre "Erreur '%-.64s' provenant de regexp"
+ ger "regexp lieferte Fehler '%-.64s'"
+ hun "'%-.64s' hiba a regularis kifejezes hasznalata soran (regexp)"
+ ita "Errore '%-.64s' da regexp"
+ jpn "regexp ãŒã‚¨ãƒ©ãƒ¼ '%-.64s' ã‚’è¿”ã—ã¾ã—ãŸã€‚"
+ kor "regexpì—서 '%-.64s'ê°€ 났습니다."
+ por "Obteve erro '%-.64s' em regexp"
+ rum "Eroarea '%-.64s' obtinuta din expresia regulara (regexp)"
+ rus "Получена ошибка '%-.64s' от регулÑрного выражениÑ"
+ serbian "Funkcija regexp je vratila grešku '%-.64s'"
+ spa "Obtenido error '%-.64s' de regexp"
+ swe "Fick fel '%-.64s' från REGEXP"
+ ukr "Отримано помилку '%-.64s' від регулÑрного виразу"
+ER_MIX_OF_GROUP_FUNC_AND_FIELDS 42000
+ cze "Pokud není žádná GROUP BY klauzule, není dovoleno souÄasné použití GROUP položek (MIN(),MAX(),COUNT()...) s ne GROUP položkami"
+ dan "Sammenblanding af GROUP kolonner (MIN(),MAX(),COUNT()...) uden GROUP kolonner er ikke tilladt, hvis der ikke er noget GROUP BY prædikat"
+ nla "Het mixen van GROUP kolommen (MIN(),MAX(),COUNT()...) met no-GROUP kolommen is foutief indien er geen GROUP BY clausule is"
+ eng "Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause"
+ est "GROUP tulpade (MIN(),MAX(),COUNT()...) kooskasutamine tavaliste tulpadega ilma GROUP BY klauslita ei ole lubatud"
+ fre "Mélanger les colonnes GROUP (MIN(),MAX(),COUNT()...) avec des colonnes normales est interdit s'il n'y a pas de clause GROUP BY"
+ ger "Das Vermischen von GROUP-Feldern (MIN(),MAX(),COUNT()...) mit Nicht-GROUP-Feldern ist nicht zulässig, wenn keine GROUP-BY-Klausel vorhanden ist"
+ hun "A GROUP mezok (MIN(),MAX(),COUNT()...) kevert hasznalata nem lehetseges GROUP BY hivatkozas nelkul"
+ ita "Il mescolare funzioni di aggregazione (MIN(),MAX(),COUNT()...) e non e` illegale se non c'e` una clausula GROUP BY"
+ jpn "GROUP BYå¥ãŒç„¡ã„å ´åˆã€é›†è¨ˆé–¢æ•°(MIN(),MAX(),COUNT(),...)ã¨é€šå¸¸ã®åˆ—ã‚’åŒæ™‚ã«ä½¿ç”¨ã§ãã¾ã›ã‚“。"
+ kor "Mixing of GROUP 칼럼s (MIN(),MAX(),COUNT(),...) with no GROUP 칼럼s is illegal if there is no GROUP BY clause"
+ por "Mistura de colunas agrupadas (com MIN(), MAX(), COUNT(), ...) com colunas não agrupadas é ilegal, se não existir uma cláusula de agrupamento (cláusula GROUP BY)"
+ rum "Amestecarea de coloane GROUP (MIN(),MAX(),COUNT()...) fara coloane GROUP este ilegala daca nu exista o clauza GROUP BY"
+ rus "Одновременное иÑпользование Ñгруппированных (GROUP) Ñтолбцов (MIN(),MAX(),COUNT(),...) Ñ Ð½ÐµÑгруппированными Ñтолбцами ÑвлÑетÑÑ Ð½ÐµÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ñ‹Ð¼, еÑли в выражении еÑть GROUP BY"
+ serbian "Upotreba agregatnih funkcija (MIN(),MAX(),COUNT()...) bez 'GROUP' kolona je pogrešna ako ne postoji 'GROUP BY' iskaz"
+ spa "Mezcla de columnas GROUP (MIN(),MAX(),COUNT()...) con no GROUP columnas es ilegal si no hat la clausula GROUP BY"
+ swe "Man får ha både GROUP-kolumner (MIN(),MAX(),COUNT()...) och fält i en fråga om man inte har en GROUP BY-del"
+ ukr "Ð—Ð¼Ñ–ÑˆÑƒÐ²Ð°Ð½Ð½Ñ GROUP Ñтовбців (MIN(),MAX(),COUNT()...) з не GROUP ÑтовбцÑми Ñ” забороненим, Ñкщо не має GROUP BY"
+ER_NONEXISTING_GRANT 42000
+ cze "Neexistuje odpovídající grant pro uživatele '%-.48s' na stroji '%-.64s'"
+ dan "Denne tilladelse findes ikke for brugeren '%-.48s' på vært '%-.64s'"
+ nla "Deze toegang (GRANT) is niet toegekend voor gebruiker '%-.48s' op host '%-.64s'"
+ eng "There is no such grant defined for user '%-.48s' on host '%-.64s'"
+ est "Sellist õigust ei ole defineeritud kasutajale '%-.48s' masinast '%-.64s'"
+ fre "Un tel droit n'est pas défini pour l'utilisateur '%-.48s' sur l'hôte '%-.64s'"
+ ger "Für Benutzer '%-.48s' auf Host '%-.64s' gibt es keine solche Berechtigung"
+ hun "A '%-.48s' felhasznalonak nincs ilyen joga a '%-.64s' host-on"
+ ita "GRANT non definita per l'utente '%-.48s' dalla macchina '%-.64s'"
+ jpn "ユーザー '%-.48s' (ホスト '%-.64s' 上) ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。"
+ kor "ì‚¬ìš©ìž '%-.48s' (호스트 '%-.64s')를 위하여 ì •ì˜ëœ 그런 승ì¸ì€ 없습니다."
+ por "Não existe tal permissão (grant) definida para o usuário '%-.48s' no 'host' '%-.64s'"
+ rum "Nu exista un astfel de grant definit pentru utilzatorul '%-.48s' de pe host-ul '%-.64s'"
+ rus "Такие права не определены Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%-.48s' на хоÑте '%-.64s'"
+ serbian "Ne postoji odobrenje za pristup korisniku '%-.48s' na host-u '%-.64s'"
+ spa "No existe permiso definido para usuario '%-.48s' en el servidor '%-.64s'"
+ swe "Det finns inget privilegium definierat för användare '%-.48s' på '%-.64s'"
+ ukr "Повноважень не визначено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача '%-.48s' з хоÑту '%-.64s'"
+ER_TABLEACCESS_DENIED_ERROR 42000
+ cze "%-.32s příkaz nepřístupný pro uživatele: '%s'@'%s' pro tabulku '%-.192s'"
+ dan "%-.32s-kommandoen er ikke tilladt for brugeren '%s'@'%s' for tabellen '%-.192s'"
+ nla "%-.32s commando geweigerd voor gebruiker: '%s'@'%s' voor tabel '%-.192s'"
+ eng "%-.32s command denied to user '%s'@'%s' for table '%-.192s'"
+ jps "コマンド %-.32s 㯠ユーザー '%s'@'%s' ,テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“",
+ est "%-.32s käsk ei ole lubatud kasutajale '%s'@'%s' tabelis '%-.192s'"
+ fre "La commande '%-.32s' est interdite à l'utilisateur: '%s'@'%s' sur la table '%-.192s'"
+ ger "%-.32s Befehl nicht erlaubt für Benutzer '%s'@'%s' auf Tabelle '%-.192s'"
+ hun "%-.32s parancs a '%s'@'%s' felhasznalo szamara nem engedelyezett a '%-.192s' tablaban"
+ ita "Comando %-.32s negato per l'utente: '%s'@'%s' sulla tabella '%-.192s'"
+ jpn "コマンド %-.32s 㯠ユーザー '%s'@'%s' ,テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“"
+ kor "'%-.32s' ëª…ë ¹ì€ ë‹¤ìŒ ì‚¬ìš©ìžì—게 ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤. : '%s'@'%s' for í…Œì´ë¸” '%-.192s'"
+ por "Comando '%-.32s' negado para o usuário '%s'@'%s' na tabela '%-.192s'"
+ rum "Comanda %-.32s interzisa utilizatorului: '%s'@'%s' pentru tabela '%-.192s'"
+ rus "Команда %-.32s запрещена пользователю '%s'@'%s' Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹ '%-.192s'"
+ serbian "%-.32s komanda zabranjena za korisnika '%s'@'%s' za tabelu '%-.192s'"
+ spa "%-.32s comando negado para usuario: '%s'@'%s' para tabla '%-.192s'"
+ swe "%-.32s ej tillåtet för '%s'@'%s' för tabell '%-.192s'"
+ ukr "%-.32s команда заборонена кориÑтувачу: '%s'@'%s' у таблиці '%-.192s'"
+ER_COLUMNACCESS_DENIED_ERROR 42000
+ cze "%-.32s příkaz nepřístupný pro uživatele: '%s'@'%s' pro sloupec '%-.192s' v tabulce '%-.192s'"
+ dan "%-.32s-kommandoen er ikke tilladt for brugeren '%s'@'%s' for kolonne '%-.192s' in tabellen '%-.192s'"
+ nla "%-.32s commando geweigerd voor gebruiker: '%s'@'%s' voor kolom '%-.192s' in tabel '%-.192s'"
+ eng "%-.32s command denied to user '%s'@'%s' for column '%-.192s' in table '%-.192s'"
+ jps "コマンド %-.32s 㯠ユーザー '%s'@'%s'Â¥n カラム '%-.192s' テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“",
+ est "%-.32s käsk ei ole lubatud kasutajale '%s'@'%s' tulbale '%-.192s' tabelis '%-.192s'"
+ fre "La commande '%-.32s' est interdite à l'utilisateur: '%s'@'%s' sur la colonne '%-.192s' de la table '%-.192s'"
+ ger "%-.32s Befehl nicht erlaubt für Benutzer '%s'@'%s' und Feld '%-.192s' in Tabelle '%-.192s'"
+ hun "%-.32s parancs a '%s'@'%s' felhasznalo szamara nem engedelyezett a '%-.192s' mezo eseten a '%-.192s' tablaban"
+ ita "Comando %-.32s negato per l'utente: '%s'@'%s' sulla colonna '%-.192s' della tabella '%-.192s'"
+ jpn "コマンド %-.32s 㯠ユーザー '%s'@'%s'\n カラム '%-.192s' テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“"
+ kor "'%-.32s' ëª…ë ¹ì€ ë‹¤ìŒ ì‚¬ìš©ìžì—게 ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤. : '%s'@'%s' for 칼럼 '%-.192s' in í…Œì´ë¸” '%-.192s'"
+ por "Comando '%-.32s' negado para o usuário '%s'@'%s' na coluna '%-.192s', na tabela '%-.192s'"
+ rum "Comanda %-.32s interzisa utilizatorului: '%s'@'%s' pentru coloana '%-.192s' in tabela '%-.192s'"
+ rus "Команда %-.32s запрещена пользователю '%s'@'%s' Ð´Ð»Ñ Ñтолбца '%-.192s' в таблице '%-.192s'"
+ serbian "%-.32s komanda zabranjena za korisnika '%s'@'%s' za kolonu '%-.192s' iz tabele '%-.192s'"
+ spa "%-.32s comando negado para usuario: '%s'@'%s' para columna '%-.192s' en la tabla '%-.192s'"
+ swe "%-.32s ej tillåtet för '%s'@'%s' för kolumn '%-.192s' i tabell '%-.192s'"
+ ukr "%-.32s команда заборонена кориÑтувачу: '%s'@'%s' Ð´Ð»Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s' у таблиці '%-.192s'"
+ER_ILLEGAL_GRANT_FOR_TABLE 42000
+ cze "Neplatný příkaz GRANT/REVOKE. Prosím, pÅ™eÄtÄ›te si v manuálu, jaká privilegia je možné použít."
+ dan "Forkert GRANT/REVOKE kommando. Se i brugervejledningen hvilke privilegier der kan specificeres."
+ nla "Foutief GRANT/REVOKE commando. Raadpleeg de handleiding welke priveleges gebruikt kunnen worden."
+ eng "Illegal GRANT/REVOKE command; please consult the manual to see which privileges can be used"
+ est "Vigane GRANT/REVOKE käsk. Tutvu kasutajajuhendiga"
+ fre "Commande GRANT/REVOKE incorrecte. Consultez le manuel."
+ ger "Unzulässiger GRANT- oder REVOKE-Befehl. Verfügbare Berechtigungen sind im Handbuch aufgeführt"
+ greek "Illegal GRANT/REVOKE command; please consult the manual to see which privileges can be used."
+ hun "Ervenytelen GRANT/REVOKE parancs. Kerem, nezze meg a kezikonyvben, milyen jogok lehetsegesek"
+ ita "Comando GRANT/REVOKE illegale. Prego consultare il manuale per sapere quali privilegi possono essere usati."
+ jpn "䏿­£ãª GRANT/REVOKE コマンドã§ã™ã€‚ã©ã®æ¨©é™ã§åˆ©ç”¨å¯èƒ½ã‹ã¯ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«ã‚’å‚ç…§ã—ã¦ä¸‹ã•ã„。"
+ kor "ìž˜ëª»ëœ GRANT/REVOKE 명령. ì–´ë–¤ 권리와 승ì¸ì´ 사용ë˜ì–´ 질 수 있는지 ë©”ë‰´ì–¼ì„ ë³´ì‹œì˜¤."
+ nor "Illegal GRANT/REVOKE command; please consult the manual to see which privleges can be used."
+ norwegian-ny "Illegal GRANT/REVOKE command; please consult the manual to see which privleges can be used."
+ pol "Illegal GRANT/REVOKE command; please consult the manual to see which privleges can be used."
+ por "Comando GRANT/REVOKE ilegal. Por favor consulte no manual quais privilégios podem ser usados."
+ rum "Comanda GRANT/REVOKE ilegala. Consultati manualul in privinta privilegiilor ce pot fi folosite."
+ rus "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° GRANT или REVOKE. ОбратитеÑÑŒ к документации, чтобы выÑÑнить, какие привилегии можно иÑпользовать"
+ serbian "PogreÅ¡na 'GRANT' odnosno 'REVOKE' komanda. Molim Vas pogledajte u priruÄniku koje vrednosti mogu biti upotrebljene."
+ slo "Illegal GRANT/REVOKE command; please consult the manual to see which privleges can be used."
+ spa "Ilegal comando GRANT/REVOKE. Por favor consulte el manual para cuales permisos pueden ser usados."
+ swe "Felaktigt GRANT-privilegium använt"
+ ukr "Хибна GRANT/REVOKE команда; прочитайте документацію ÑтоÑовно того, Ñкі права можна викориÑтовувати"
+ER_GRANT_WRONG_HOST_OR_USER 42000
+ cze "Argument příkazu GRANT uživatel nebo stroj je příliš dlouhý"
+ dan "Værts- eller brugernavn for langt til GRANT"
+ nla "De host of gebruiker parameter voor GRANT is te lang"
+ eng "The host or user argument to GRANT is too long"
+ est "Masina või kasutaja nimi GRANT lauses on liiga pikk"
+ fre "L'hôte ou l'utilisateur donné en argument à GRANT est trop long"
+ ger "Das Host- oder User-Argument für GRANT ist zu lang"
+ hun "A host vagy felhasznalo argumentuma tul hosszu a GRANT parancsban"
+ ita "L'argomento host o utente per la GRANT e` troppo lungo"
+ jpn "GRANTコマンドã¸ã®ã€ãƒ›ã‚¹ãƒˆåやユーザーåãŒé•·ã™ãŽã¾ã™ã€‚"
+ kor "승ì¸(GRANT)ì„ ìœ„í•˜ì—¬ 사용한 사용ìžë‚˜ í˜¸ìŠ¤íŠ¸ì˜ ê°’ë“¤ì´ ë„ˆë¬´ ê¹ë‹ˆë‹¤."
+ por "Argumento de 'host' ou de usuário para o GRANT é longo demais"
+ rum "Argumentul host-ului sau utilizatorului pentru GRANT e prea lung"
+ rus "Слишком длинное Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ/хоÑта Ð´Ð»Ñ GRANT"
+ serbian "Argument 'host' ili 'korisnik' prosleÄ‘en komandi 'GRANT' je predugaÄak"
+ spa "El argumento para servidor o usuario para GRANT es demasiado grande"
+ swe "Felaktigt maskinnamn eller användarnamn använt med GRANT"
+ ukr "Ðргумент host або user Ð´Ð»Ñ GRANT задовгий"
+ER_NO_SUCH_TABLE 42S02
+ cze "Tabulka '%-.192s.%-.192s' neexistuje"
+ dan "Tabellen '%-.192s.%-.192s' eksisterer ikke"
+ nla "Tabel '%-.192s.%-.192s' bestaat niet"
+ eng "Table '%-.192s.%-.192s' doesn't exist"
+ est "Tabelit '%-.192s.%-.192s' ei eksisteeri"
+ fre "La table '%-.192s.%-.192s' n'existe pas"
+ ger "Tabelle '%-.192s.%-.192s' existiert nicht"
+ hun "A '%-.192s.%-.192s' tabla nem letezik"
+ ita "La tabella '%-.192s.%-.192s' non esiste"
+ jpn "表 '%-.192s.%-.192s' ã¯å­˜åœ¨ã—ã¾ã›ã‚“。"
+ kor "í…Œì´ë¸” '%-.192s.%-.192s' 는 존재하지 않습니다."
+ nor "Table '%-.192s.%-.192s' doesn't exist"
+ norwegian-ny "Table '%-.192s.%-.192s' doesn't exist"
+ pol "Table '%-.192s.%-.192s' doesn't exist"
+ por "Tabela '%-.192s.%-.192s' não existe"
+ rum "Tabela '%-.192s.%-.192s' nu exista"
+ rus "Таблица '%-.192s.%-.192s' не ÑущеÑтвует"
+ serbian "Tabela '%-.192s.%-.192s' ne postoji"
+ slo "Table '%-.192s.%-.192s' doesn't exist"
+ spa "Tabla '%-.192s.%-.192s' no existe"
+ swe "Det finns ingen tabell som heter '%-.192s.%-.192s'"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s.%-.192s' не Ñ–Ñнує"
+ER_NONEXISTING_TABLE_GRANT 42000
+ cze "Neexistuje odpovídající grant pro uživatele '%-.48s' na stroji '%-.64s' pro tabulku '%-.192s'"
+ dan "Denne tilladelse eksisterer ikke for brugeren '%-.48s' på vært '%-.64s' for tabellen '%-.192s'"
+ nla "Deze toegang (GRANT) is niet toegekend voor gebruiker '%-.48s' op host '%-.64s' op tabel '%-.192s'"
+ eng "There is no such grant defined for user '%-.48s' on host '%-.64s' on table '%-.192s'"
+ est "Sellist õigust ei ole defineeritud kasutajale '%-.48s' masinast '%-.64s' tabelile '%-.192s'"
+ fre "Un tel droit n'est pas défini pour l'utilisateur '%-.48s' sur l'hôte '%-.64s' sur la table '%-.192s'"
+ ger "Eine solche Berechtigung ist für User '%-.48s' auf Host '%-.64s' an Tabelle '%-.192s' nicht definiert"
+ hun "A '%-.48s' felhasznalo szamara a '%-.64s' host '%-.192s' tablajaban ez a parancs nem engedelyezett"
+ ita "GRANT non definita per l'utente '%-.48s' dalla macchina '%-.64s' sulla tabella '%-.192s'"
+ jpn "ユーザー '%-.48s' (ホスト '%-.64s' 上) ã®è¡¨ '%-.192s' ã¸ã®æ¨©é™ã¯å®šç¾©ã•れã¦ã„ã¾ã›ã‚“。"
+ kor "ì‚¬ìš©ìž '%-.48s'(호스트 '%-.64s')는 í…Œì´ë¸” '%-.192s'를 사용하기 위하여 ì •ì˜ëœ 승ì¸ì€ 없습니다. "
+ por "Não existe tal permissão (grant) definido para o usuário '%-.48s' no 'host' '%-.64s', na tabela '%-.192s'"
+ rum "Nu exista un astfel de privilegiu (grant) definit pentru utilizatorul '%-.48s' de pe host-ul '%-.64s' pentru tabela '%-.192s'"
+ rus "Такие права не определены Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%-.48s' на компьютере '%-.64s' Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹ '%-.192s'"
+ serbian "Ne postoji odobrenje za pristup korisniku '%-.48s' na host-u '%-.64s' tabeli '%-.192s'"
+ spa "No existe tal permiso definido para usuario '%-.48s' en el servidor '%-.64s' en la tabla '%-.192s'"
+ swe "Det finns inget privilegium definierat för användare '%-.48s' på '%-.64s' för tabell '%-.192s'"
+ ukr "Повноважень не визначено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача '%-.48s' з хоÑту '%-.64s' Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– '%-.192s'"
+ER_NOT_ALLOWED_COMMAND 42000
+ cze "Použitý příkaz není v této verzi MySQL povolen"
+ dan "Den brugte kommando er ikke tilladt med denne udgave af MariaDB"
+ nla "Het used commando is niet toegestaan in deze MariaDB versie"
+ eng "The used command is not allowed with this MariaDB version"
+ est "Antud käsk ei ole lubatud käesolevas MariaDB versioonis"
+ fre "Cette commande n'existe pas dans cette version de MariaDB"
+ ger "Der verwendete Befehl ist in dieser MariaDB-Version nicht zulässig"
+ hun "A hasznalt parancs nem engedelyezett ebben a MariaDB verzioban"
+ ita "Il comando utilizzato non e` supportato in questa versione di MariaDB"
+ jpn "ã“ã®MySQLãƒãƒ¼ã‚¸ãƒ§ãƒ³ã§ã¯åˆ©ç”¨ã§ããªã„コマンドã§ã™ã€‚"
+ kor "ì‚¬ìš©ëœ ëª…ë ¹ì€ í˜„ìž¬ì˜ MariaDB 버젼ì—서는 ì´ìš©ë˜ì§€ 않습니다."
+ por "Comando usado não é permitido para esta versão do MariaDB"
+ rum "Comanda folosita nu este permisa pentru aceasta versiune de MariaDB"
+ rus "Эта команда не допуÑкаетÑÑ Ð² данной верÑии MariaDB"
+ serbian "Upotrebljena komanda nije dozvoljena sa ovom verzijom MariaDB servera"
+ spa "El comando usado no es permitido con esta versión de MariaDB"
+ swe "Du kan inte använda detta kommando med denna MariaDB version"
+ ukr "ВикориÑтовувана команда не дозволена у цій верÑÑ–Ñ— MariaDB"
+ER_SYNTAX_ERROR 42000
+ cze "Vaše syntaxe je nějaká divná"
+ dan "Der er en fejl i SQL syntaksen"
+ nla "Er is iets fout in de gebruikte syntax"
+ eng "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use"
+ est "Viga SQL süntaksis"
+ fre "Erreur de syntaxe"
+ ger "Fehler in der SQL-Syntax. Bitte die korrekte Syntax im Handbuch nachschlagen"
+ greek "You have an error in your SQL syntax"
+ hun "Szintaktikai hiba"
+ ita "Errore di sintassi nella query SQL"
+ jpn "SQL構文エラーã§ã™ã€‚ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«å¯¾å¿œã™ã‚‹ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«ã‚’å‚ç…§ã—ã¦æ­£ã—ã„æ§‹æ–‡ã‚’確èªã—ã¦ãã ã•ã„。"
+ kor "SQL êµ¬ë¬¸ì— ì˜¤ë¥˜ê°€ 있습니다."
+ nor "Something is wrong in your syntax"
+ norwegian-ny "Something is wrong in your syntax"
+ pol "Something is wrong in your syntax"
+ por "Você tem um erro de sintaxe no seu SQL"
+ rum "Aveti o eroare in sintaxa RSQL"
+ rus "У Ð²Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° в запроÑе. Изучите документацию по иÑпользуемой верÑии MariaDB на предмет корректного ÑинтакÑиÑа"
+ serbian "Imate grešku u vašoj SQL sintaksi"
+ slo "Something is wrong in your syntax"
+ spa "Algo está equivocado en su sintax"
+ swe "Du har något fel i din syntax"
+ ukr "У Ð²Ð°Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° у ÑинтакÑиÑÑ– SQL"
+ER_DELAYED_CANT_CHANGE_LOCK
+ cze "Zpožděný insert threadu nebyl schopen získat požadovaný zámek pro tabulku %-.192s"
+ dan "Forsinket indsættelse tråden (delayed insert thread) kunne ikke opnå lås på tabellen %-.192s"
+ nla "'Delayed insert' thread kon de aangevraagde 'lock' niet krijgen voor tabel %-.192s"
+ eng "Delayed insert thread couldn't get requested lock for table %-.192s"
+ est "INSERT DELAYED lõim ei suutnud saada soovitud lukku tabelile %-.192s"
+ fre "La tâche 'delayed insert' n'a pas pu obtenir le verrou démandé sur la table %-.192s"
+ ger "Verzögerter (DELAYED) Einfüge-Thread konnte die angeforderte Sperre für Tabelle '%-.192s' nicht erhalten"
+ hun "A kesleltetett beillesztes (delayed insert) thread nem kapott zatolast a %-.192s tablahoz"
+ ita "Il thread di inserimento ritardato non riesce ad ottenere il lock per la tabella %-.192s"
+ jpn "'Delayed insert'スレッドãŒè¡¨ '%-.192s' ã®ãƒ­ãƒƒã‚¯ã‚’å–å¾—ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+ kor "ì§€ì—°ëœ insert 쓰레드가 í…Œì´ë¸” %-.192sì˜ ìš”êµ¬ëœ ë½í‚¹ì„ 처리할 수 없었습니다."
+ por "'Thread' de inserção retardada (atrasada) pois não conseguiu obter a trava solicitada para tabela '%-.192s'"
+ rum "Thread-ul pentru inserarea aminata nu a putut obtine lacatul (lock) pentru tabela %-.192s"
+ rus "Поток, обÑлуживающий отложенную вÑтавку (delayed insert), не Ñмог получить запрашиваемую блокировку на таблицу %-.192s"
+ serbian "Prolongirani 'INSERT' thread nije mogao da dobije traženo zakljuÄavanje tabele '%-.192s'"
+ spa "Thread de inserción retarda no pudiendo bloquear para la tabla %-.192s"
+ swe "DELAYED INSERT-tråden kunde inte låsa tabell '%-.192s'"
+ ukr "Гілка Ð´Ð»Ñ INSERT DELAYED не може отримати Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– %-.192s"
+ER_TOO_MANY_DELAYED_THREADS
+ cze "Příliš mnoho zpožděných threadů"
+ dan "For mange slettede tråde (threads) i brug"
+ nla "Te veel 'delayed' threads in gebruik"
+ eng "Too many delayed threads in use"
+ est "Liiga palju DELAYED lõimesid kasutusel"
+ fre "Trop de tâche 'delayed' en cours"
+ ger "Zu viele verzögerte (DELAYED) Threads in Verwendung"
+ hun "Tul sok kesletetett thread (delayed)"
+ ita "Troppi threads ritardati in uso"
+ jpn "'Delayed insert'スレッドãŒå¤šã™ãŽã¾ã™ã€‚"
+ kor "너무 ë§Žì€ ì§€ì—° 쓰레드를 사용하고 있습니다."
+ por "Excesso de 'threads' retardadas (atrasadas) em uso"
+ rum "Prea multe threaduri aminate care sint in uz"
+ rus "Слишком много потоков, обÑлуживающих отложенную вÑтавку (delayed insert)"
+ serbian "Previše prolongiranih thread-ova je u upotrebi"
+ spa "Muchos threads retardados en uso"
+ swe "Det finns redan 'max_delayed_threads' trådar i använding"
+ ukr "Забагато затриманих гілок викориÑтовуєтьÑÑ"
+ER_ABORTING_CONNECTION 08S01
+ cze "Zrušeno spojení %ld do databáze: '%-.192s' uživatel: '%-.48s' (%-.64s)"
+ dan "Afbrudt forbindelse %ld til database: '%-.192s' bruger: '%-.48s' (%-.64s)"
+ nla "Afgebroken verbinding %ld naar db: '%-.192s' gebruiker: '%-.48s' (%-.64s)"
+ eng "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)"
+ est "Ühendus katkestatud %ld andmebaasile: '%-.192s' kasutajale: '%-.48s' (%-.64s)"
+ fre "Connection %ld avortée vers la bd: '%-.192s' utilisateur: '%-.48s' (%-.64s)"
+ ger "Abbruch der Verbindung %ld zur Datenbank '%-.192s'. Benutzer: '%-.48s' (%-.64s)"
+ hun "Megszakitott kapcsolat %ld db: '%-.192s' adatbazishoz, felhasznalo: '%-.48s' (%-.64s)"
+ ita "Interrotta la connessione %ld al db: '%-.192s' utente: '%-.48s' (%-.64s)"
+ jpn "接続 %ld ãŒä¸­æ–­ã•れã¾ã—ãŸã€‚データベース: '%-.192s' ユーザー: '%-.48s' (%-.64s)"
+ kor "ë°ì´íƒ€ë² ì´ìФ ì ‘ì†ì„ 위한 ì—°ê²° %ldê°€ ì¤‘ë‹¨ë¨ : '%-.192s' 사용ìž: '%-.48s' (%-.64s)"
+ nor "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)"
+ norwegian-ny "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)"
+ pol "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)"
+ por "Conexão %ld abortou para o banco de dados '%-.192s' - usuário '%-.48s' (%-.64s)"
+ rum "Conectie terminata %ld la baza de date: '%-.192s' utilizator: '%-.48s' (%-.64s)"
+ rus "Прервано Ñоединение %ld к базе данных '%-.192s' Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%-.48s' (%-.64s)"
+ serbian "Prekinuta konekcija broj %ld ka bazi: '%-.192s' korisnik je bio: '%-.48s' (%-.64s)"
+ slo "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)"
+ spa "Conexión abortada %ld para db: '%-.192s' usuario: '%-.48s' (%-.64s)"
+ swe "Avbröt länken för tråd %ld till db '%-.192s', användare '%-.48s' (%-.64s)"
+ ukr "Перервано з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ %ld до бази данних: '%-.192s' кориÑтувача: '%-.48s' (%-.64s)"
+ER_NET_PACKET_TOO_LARGE 08S01
+ cze "Zjištěn příchozí packet delší než 'max_allowed_packet'"
+ dan "Modtog en datapakke som var større end 'max_allowed_packet'"
+ nla "Groter pakket ontvangen dan 'max_allowed_packet'"
+ eng "Got a packet bigger than 'max_allowed_packet' bytes"
+ est "Saabus suurem pakett kui lubatud 'max_allowed_packet' muutujaga"
+ fre "Paquet plus grand que 'max_allowed_packet' reçu"
+ ger "Empfangenes Paket ist größer als 'max_allowed_packet' Bytes"
+ hun "A kapott csomag nagyobb, mint a maximalisan engedelyezett: 'max_allowed_packet'"
+ ita "Ricevuto un pacchetto piu` grande di 'max_allowed_packet'"
+ jpn "'max_allowed_packet'よりも大ããªãƒ‘ケットをå—ä¿¡ã—ã¾ã—ãŸã€‚"
+ kor "'max_allowed_packet'보다 ë”í° íŒ¨í‚·ì„ ë°›ì•˜ìŠµë‹ˆë‹¤."
+ por "Obteve um pacote maior do que a taxa máxima de pacotes definida (max_allowed_packet)"
+ rum "Un packet mai mare decit 'max_allowed_packet' a fost primit"
+ rus "Полученный пакет больше, чем 'max_allowed_packet'"
+ serbian "Primio sam mrežni paket veći od definisane vrednosti 'max_allowed_packet'"
+ spa "Obtenido un paquete mayor que 'max_allowed_packet'"
+ swe "Kommunkationspaketet är större än 'max_allowed_packet'"
+ ukr "Отримано пакет більший ніж max_allowed_packet"
+ER_NET_READ_ERROR_FROM_PIPE 08S01
+ cze "ZjiÅ¡tÄ›na chyba pÅ™i Ätení z roury spojení"
+ dan "Fik læsefejl fra forbindelse (connection pipe)"
+ nla "Kreeg leesfout van de verbindings pipe"
+ eng "Got a read error from the connection pipe"
+ est "Viga ühendustoru lugemisel"
+ fre "Erreur de lecture reçue du pipe de connexion"
+ ger "Lese-Fehler bei einer Verbindungs-Pipe"
+ hun "Olvasasi hiba a kapcsolat soran"
+ ita "Rilevato un errore di lettura dalla pipe di connessione"
+ jpn "接続パイプã®èª­ã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼ã§ã™ã€‚"
+ kor "ì—°ê²° 파ì´í”„로부터 ì—러가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+ por "Obteve um erro de leitura no 'pipe' da conexão"
+ rum "Eroare la citire din cauza lui 'connection pipe'"
+ rus "Получена ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¾Ñ‚ потока ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ (connection pipe)"
+ serbian "GreÅ¡ka pri Äitanju podataka sa pipe-a"
+ spa "Obtenido un error de lectura de la conexión pipe"
+ swe "Fick läsfel från klienten vid läsning från 'PIPE'"
+ ukr "Отримано помилку Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð· комунікаційного каналу"
+ER_NET_FCNTL_ERROR 08S01
+ cze "Zjištěna chyba fcntl()"
+ dan "Fik fejlmeddelelse fra fcntl()"
+ nla "Kreeg fout van fcntl()"
+ eng "Got an error from fcntl()"
+ est "fcntl() tagastas vea"
+ fre "Erreur reçue de fcntl() "
+ ger "fcntl() lieferte einen Fehler"
+ hun "Hiba a fcntl() fuggvenyben"
+ ita "Rilevato un errore da fcntl()"
+ jpn "fcntl()ãŒã‚¨ãƒ©ãƒ¼ã‚’è¿”ã—ã¾ã—ãŸã€‚"
+ kor "fcntl() 함수로부터 ì—러가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+ por "Obteve um erro em fcntl()"
+ rum "Eroare obtinuta de la fcntl()"
+ rus "Получена ошибка от fcntl()"
+ serbian "Greška pri izvršavanju funkcije fcntl()"
+ spa "Obtenido un error de fcntl()"
+ swe "Fick fatalt fel från 'fcntl()'"
+ ukr "Отримано помилкку від fcntl()"
+ER_NET_PACKETS_OUT_OF_ORDER 08S01
+ cze "Příchozí packety v chybném pořadí"
+ dan "Modtog ikke datapakker i korrekt rækkefølge"
+ nla "Pakketten in verkeerde volgorde ontvangen"
+ eng "Got packets out of order"
+ est "Paketid saabusid vales järjekorras"
+ fre "Paquets reçus dans le désordre"
+ ger "Pakete nicht in der richtigen Reihenfolge empfangen"
+ hun "Helytelen sorrendben erkezett adatcsomagok"
+ ita "Ricevuti pacchetti non in ordine"
+ jpn "䏿­£ãªé †åºã®ãƒ‘ケットをå—ä¿¡ã—ã¾ã—ãŸã€‚"
+ kor "순서가 맞지않는 íŒ¨í‚·ì„ ë°›ì•˜ìŠµë‹ˆë‹¤."
+ por "Obteve pacotes fora de ordem"
+ rum "Packets care nu sint ordonati au fost gasiti"
+ rus "Пакеты получены в неверном порÑдке"
+ serbian "Primio sam mrežne pakete van reda"
+ spa "Obtenido paquetes desordenados"
+ swe "Kommunikationspaketen kom i fel ordning"
+ ukr "Отримано пакети у неналежному порÑдку"
+ER_NET_UNCOMPRESS_ERROR 08S01
+ cze "Nemohu rozkomprimovat komunikaÄní packet"
+ dan "Kunne ikke dekomprimere kommunikations-pakke (communication packet)"
+ nla "Communicatiepakket kon niet worden gedecomprimeerd"
+ eng "Couldn't uncompress communication packet"
+ est "Viga andmepaketi lahtipakkimisel"
+ fre "Impossible de décompresser le paquet reçu"
+ ger "Kommunikationspaket lässt sich nicht entpacken"
+ hun "A kommunikacios adatcsomagok nem tomorithetok ki"
+ ita "Impossibile scompattare i pacchetti di comunicazione"
+ jpn "圧縮パケットã®å±•é–‹ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+ kor "통신 íŒ¨í‚·ì˜ ì••ì¶•í•´ì œë¥¼ í•  수 없었습니다."
+ por "Não conseguiu descomprimir pacote de comunicação"
+ rum "Nu s-a putut decompresa pachetul de comunicatie (communication packet)"
+ rus "Ðевозможно раÑпаковать пакет, полученный через коммуникационный протокол"
+ serbian "Ne mogu da dekompresujem mrežne pakete"
+ spa "No puedo descomprimir paquetes de comunicación"
+ swe "Kunde inte packa up kommunikationspaketet"
+ ukr "Ðе можу декомпреÑувати комунікаційний пакет"
+ER_NET_READ_ERROR 08S01
+ cze "ZjiÅ¡tÄ›na chyba pÅ™i Ätení komunikaÄního packetu"
+ dan "Fik fejlmeddelelse ved læsning af kommunikations-pakker (communication packets)"
+ nla "Fout bij het lezen van communicatiepakketten"
+ eng "Got an error reading communication packets"
+ est "Viga andmepaketi lugemisel"
+ fre "Erreur de lecture des paquets reçus"
+ ger "Fehler beim Lesen eines Kommunikationspakets"
+ hun "HIba a kommunikacios adatcsomagok olvasasa soran"
+ ita "Rilevato un errore ricevendo i pacchetti di comunicazione"
+ jpn "パケットã®å—ä¿¡ã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ kor "통신 íŒ¨í‚·ì„ ì½ëŠ” 중 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+ por "Obteve um erro na leitura de pacotes de comunicação"
+ rum "Eroare obtinuta citind pachetele de comunicatie (communication packets)"
+ rus "Получена ошибка в процеÑÑе Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð°ÐºÐµÑ‚Ð° через коммуникационный протокол "
+ serbian "Greška pri primanju mrežnih paketa"
+ spa "Obtenido un error leyendo paquetes de comunicación"
+ swe "Fick ett fel vid läsning från klienten"
+ ukr "Отримано помилку Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼ÑƒÐ½Ñ–ÐºÐ°Ñ†Ñ–Ð¹Ð½Ð¸Ñ… пакетів"
+ER_NET_READ_INTERRUPTED 08S01
+ cze "ZjiÅ¡tÄ›n timeout pÅ™i Ätení komunikaÄního packetu"
+ dan "Timeout-fejl ved læsning af kommunukations-pakker (communication packets)"
+ nla "Timeout bij het lezen van communicatiepakketten"
+ eng "Got timeout reading communication packets"
+ est "Kontrollaja ületamine andmepakettide lugemisel"
+ fre "Timeout en lecture des paquets reçus"
+ ger "Zeitüberschreitung beim Lesen eines Kommunikationspakets"
+ hun "Idotullepes a kommunikacios adatcsomagok olvasasa soran"
+ ita "Rilevato un timeout ricevendo i pacchetti di comunicazione"
+ jpn "パケットã®å—ä¿¡ã§ã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ kor "통신 íŒ¨í‚·ì„ ì½ëŠ” 중 timeoutì´ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+ por "Obteve expiração de tempo (timeout) na leitura de pacotes de comunicação"
+ rum "Timeout obtinut citind pachetele de comunicatie (communication packets)"
+ rus "Получен таймаут Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð¿Ð°ÐºÐµÑ‚Ð° через коммуникационный протокол "
+ serbian "Vremenski limit za Äitanje mrežnih paketa je istekao"
+ spa "Obtenido timeout leyendo paquetes de comunicación"
+ swe "Fick 'timeout' vid läsning från klienten"
+ ukr "Отримано затримку Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼ÑƒÐ½Ñ–ÐºÐ°Ñ†Ñ–Ð¹Ð½Ð¸Ñ… пакетів"
+ER_NET_ERROR_ON_WRITE 08S01
+ cze "ZjiÅ¡tÄ›na chyba pÅ™i zápisu komunikaÄního packetu"
+ dan "Fik fejlmeddelelse ved skrivning af kommunukations-pakker (communication packets)"
+ nla "Fout bij het schrijven van communicatiepakketten"
+ eng "Got an error writing communication packets"
+ est "Viga andmepaketi kirjutamisel"
+ fre "Erreur d'écriture des paquets envoyés"
+ ger "Fehler beim Schreiben eines Kommunikationspakets"
+ hun "Hiba a kommunikacios csomagok irasa soran"
+ ita "Rilevato un errore inviando i pacchetti di comunicazione"
+ jpn "パケットã®é€ä¿¡ã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ kor "통신 íŒ¨í‚·ì„ ê¸°ë¡í•˜ëŠ” 중 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+ por "Obteve um erro na escrita de pacotes de comunicação"
+ rum "Eroare in scrierea pachetelor de comunicatie (communication packets)"
+ rus "Получена ошибка при передаче пакета через коммуникационный протокол "
+ serbian "Greška pri slanju mrežnih paketa"
+ spa "Obtenido un error de escribiendo paquetes de comunicación"
+ swe "Fick ett fel vid skrivning till klienten"
+ ukr "Отримано помилку запиÑу комунікаційних пакетів"
+ER_NET_WRITE_INTERRUPTED 08S01
+ cze "ZjiÅ¡tÄ›n timeout pÅ™i zápisu komunikaÄního packetu"
+ dan "Timeout-fejl ved skrivning af kommunukations-pakker (communication packets)"
+ nla "Timeout bij het schrijven van communicatiepakketten"
+ eng "Got timeout writing communication packets"
+ est "Kontrollaja ületamine andmepakettide kirjutamisel"
+ fre "Timeout d'écriture des paquets envoyés"
+ ger "Zeitüberschreitung beim Schreiben eines Kommunikationspakets"
+ hun "Idotullepes a kommunikacios csomagok irasa soran"
+ ita "Rilevato un timeout inviando i pacchetti di comunicazione"
+ jpn "パケットã®é€ä¿¡ã§ã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ kor "통신 íŒ¨íŒƒì„ ê¸°ë¡í•˜ëŠ” 중 timeoutì´ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+ por "Obteve expiração de tempo ('timeout') na escrita de pacotes de comunicação"
+ rum "Timeout obtinut scriind pachetele de comunicatie (communication packets)"
+ rus "Получен таймаут в процеÑÑе передачи пакета через коммуникационный протокол "
+ serbian "Vremenski limit za slanje mrežnih paketa je istekao"
+ spa "Obtenido timeout escribiendo paquetes de comunicación"
+ swe "Fick 'timeout' vid skrivning till klienten"
+ ukr "Отримано затримку запиÑу комунікаційних пакетів"
+ER_TOO_LONG_STRING 42000
+ cze "Výsledný řetězec je delší než 'max_allowed_packet'"
+ dan "Strengen med resultater er større end 'max_allowed_packet'"
+ nla "Resultaat string is langer dan 'max_allowed_packet'"
+ eng "Result string is longer than 'max_allowed_packet' bytes"
+ est "Tulemus on pikem kui lubatud 'max_allowed_packet' muutujaga"
+ fre "La chaîne résultat est plus grande que 'max_allowed_packet'"
+ ger "Ergebnis-String ist länger als 'max_allowed_packet' Bytes"
+ hun "Ez eredmeny sztring nagyobb, mint a lehetseges maximum: 'max_allowed_packet'"
+ ita "La stringa di risposta e` piu` lunga di 'max_allowed_packet'"
+ jpn "çµæžœã®æ–‡å­—列㌠'max_allowed_packet' よりも大ãã„ã§ã™ã€‚"
+ por "'String' resultante é mais longa do que 'max_allowed_packet'"
+ rum "Sirul rezultat este mai lung decit 'max_allowed_packet'"
+ rus "Ð ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð¸Ñ€ÑƒÑŽÑ‰Ð°Ñ Ñтрока больше, чем 'max_allowed_packet'"
+ serbian "RezultujuÄi string je duži nego Å¡to to dozvoljava parametar servera 'max_allowed_packet'"
+ spa "La string resultante es mayor que max_allowed_packet"
+ swe "Resultatsträngen är längre än max_allowed_packet"
+ ukr "Строка результату довша ніж max_allowed_packet"
+ER_TABLE_CANT_HANDLE_BLOB 42000
+ cze "Typ použité tabulky (%s) nepodporuje BLOB/TEXT sloupce"
+ dan "Denne tabeltype (%s) understøtter ikke brug af BLOB og TEXT kolonner"
+ nla "Het gebruikte tabel type (%s) ondersteunt geen BLOB/TEXT kolommen"
+ eng "Storage engine %s doesn't support BLOB/TEXT columns"
+ est "Valitud tabelitüüp (%s) ei toeta BLOB/TEXT tüüpi välju"
+ fre "Ce type de table (%s) ne supporte pas les colonnes BLOB/TEXT"
+ ger "Der verwendete Tabellentyp (%s) unterstützt keine BLOB- und TEXT-Felder"
+ hun "A hasznalt tabla tipus (%s) nem tamogatja a BLOB/TEXT mezoket"
+ ita "Il tipo di tabella usata (%s) non supporta colonne di tipo BLOB/TEXT"
+ por "Tipo de tabela usado (%s) não permite colunas BLOB/TEXT"
+ rum "Tipul de tabela folosit (%s) nu suporta coloane de tip BLOB/TEXT"
+ rus "%s таблицы не поддерживают типы BLOB/TEXT"
+ serbian "Iskorišteni tip tabele (%s) ne podržava kolone tipa 'BLOB' odnosno 'TEXT'"
+ spa "El tipo de tabla usada (%s) no permite soporte para columnas BLOB/TEXT"
+ swe "Den använda tabelltypen (%s) kan inte hantera BLOB/TEXT-kolumner"
+ ukr "%s таблиці не підтримують BLOB/TEXT Ñтовбці"
+ER_TABLE_CANT_HANDLE_AUTO_INCREMENT 42000
+ cze "Typ použité tabulky (%s) nepodporuje AUTO_INCREMENT sloupce"
+ dan "Denne tabeltype understøtter (%s) ikke brug af AUTO_INCREMENT kolonner"
+ nla "Het gebruikte tabel type (%s) ondersteunt geen AUTO_INCREMENT kolommen"
+ eng "Storage engine %s doesn't support AUTO_INCREMENT columns"
+ est "Valitud tabelitüüp (%s) ei toeta AUTO_INCREMENT tüüpi välju"
+ fre "Ce type de table (%s) ne supporte pas les colonnes AUTO_INCREMENT"
+ ger "Der verwendete Tabellentyp (%s) unterstützt keine AUTO_INCREMENT-Felder"
+ hun "A hasznalt tabla tipus (%s) nem tamogatja az AUTO_INCREMENT tipusu mezoket"
+ ita "Il tipo di tabella usata (%s) non supporta colonne di tipo AUTO_INCREMENT"
+ por "Tipo de tabela usado (%s) não permite colunas AUTO_INCREMENT"
+ rum "Tipul de tabela folosit (%s) nu suporta coloane de tip AUTO_INCREMENT"
+ rus "%s таблицы не поддерживают автоинкрементные Ñтолбцы"
+ serbian "Iskorišteni tip tabele (%s) ne podržava kolone tipa 'AUTO_INCREMENT'"
+ spa "El tipo de tabla usada (%s) no permite soporte para columnas AUTO_INCREMENT"
+ swe "Den använda tabelltypen (%s) kan inte hantera AUTO_INCREMENT-kolumner"
+ ukr "%s таблиці не підтримують AUTO_INCREMENT Ñтовбці"
+ER_DELAYED_INSERT_TABLE_LOCKED
+ cze "INSERT DELAYED není možno s tabulkou '%-.192s' použít, protože je zamÄená pomocí LOCK TABLES"
+ dan "INSERT DELAYED kan ikke bruges med tabellen '%-.192s', fordi tabellen er låst med LOCK TABLES"
+ nla "INSERT DELAYED kan niet worden gebruikt bij table '%-.192s', vanwege een 'lock met LOCK TABLES"
+ eng "INSERT DELAYED can't be used with table '%-.192s' because it is locked with LOCK TABLES"
+ est "INSERT DELAYED ei saa kasutada tabeli '%-.192s' peal, kuna see on lukustatud LOCK TABLES käsuga"
+ fre "INSERT DELAYED ne peut être utilisé avec la table '%-.192s', car elle est verrouée avec LOCK TABLES"
+ ger "INSERT DELAYED kann für Tabelle '%-.192s' nicht verwendet werden, da sie mit LOCK TABLES gesperrt ist"
+ greek "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES"
+ hun "Az INSERT DELAYED nem hasznalhato a '%-.192s' tablahoz, mert a tabla zarolt (LOCK TABLES)"
+ ita "L'inserimento ritardato (INSERT DELAYED) non puo` essere usato con la tabella '%-.192s', perche` soggetta a lock da 'LOCK TABLES'"
+ jpn "表 '%-.192s' ã¯LOCK TABLESã§ãƒ­ãƒƒã‚¯ã•れã¦ã„ã‚‹ãŸã‚ã€INSERT DELAYEDを使用ã§ãã¾ã›ã‚“。"
+ kor "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES"
+ nor "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES"
+ norwegian-ny "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES"
+ pol "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES"
+ por "INSERT DELAYED não pode ser usado com a tabela '%-.192s', porque ela está travada com LOCK TABLES"
+ rum "INSERT DELAYED nu poate fi folosit cu tabela '%-.192s', deoarece este locked folosing LOCK TABLES"
+ rus "ÐÐµÐ»ÑŒÐ·Ñ Ð¸Ñпользовать INSERT DELAYED Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹ '%-.192s', потому что она заблокирована Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ LOCK TABLES"
+ serbian "Komanda 'INSERT DELAYED' ne može biti iskoriÅ¡tena u tabeli '%-.192s', zbog toga Å¡to je zakljuÄana komandom 'LOCK TABLES'"
+ slo "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES"
+ spa "INSERT DELAYED no puede ser usado con tablas '%-.192s', porque esta bloqueada con LOCK TABLES"
+ swe "INSERT DELAYED kan inte användas med tabell '%-.192s', emedan den är låst med LOCK TABLES"
+ ukr "INSERT DELAYED не може бути викориÑтано з таблицею '%-.192s', тому що Ñ—Ñ— заблоковано з LOCK TABLES"
+ER_WRONG_COLUMN_NAME 42000
+ cze "Nesprávné jméno sloupce '%-.100s'"
+ dan "Forkert kolonnenavn '%-.100s'"
+ nla "Incorrecte kolom naam '%-.100s'"
+ eng "Incorrect column name '%-.100s'"
+ est "Vigane tulba nimi '%-.100s'"
+ fre "Nom de colonne '%-.100s' incorrect"
+ ger "Falscher Spaltenname '%-.100s'"
+ hun "Ervenytelen mezonev: '%-.100s'"
+ ita "Nome colonna '%-.100s' non corretto"
+ jpn "列å '%-.100s' ã¯ä¸æ­£ã§ã™ã€‚"
+ por "Nome de coluna '%-.100s' incorreto"
+ rum "Nume increct de coloana '%-.100s'"
+ rus "Ðеверное Ð¸Ð¼Ñ Ñтолбца '%-.100s'"
+ serbian "Pogrešno ime kolone '%-.100s'"
+ spa "Incorrecto nombre de columna '%-.100s'"
+ swe "Felaktigt kolumnnamn '%-.100s'"
+ ukr "Ðевірне ім'Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.100s'"
+ER_WRONG_KEY_COLUMN 42000
+ eng "The storage engine %s can't index column %`s"
+ ger "Die Speicher-Engine %s kann die Spalte %`s nicht indizieren"
+ rus "Обработчик таблиц %s не может проиндекÑировать Ñтолбец %`s"
+ ukr "Вказівник таблиц %s не може індекÑувати Ñтовбець %`s"
+ER_WRONG_MRG_TABLE
+ cze "Všechny tabulky v MERGE tabulce nejsou definovány stejně"
+ dan "Tabellerne i MERGE er ikke defineret ens"
+ nla "Niet alle tabellen in de MERGE tabel hebben identieke gedefinities"
+ eng "Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist"
+ est "Kõik tabelid MERGE tabeli määratluses ei ole identsed"
+ fre "Toutes les tables de la table de type MERGE n'ont pas la même définition"
+ ger "Nicht alle Tabellen in der MERGE-Tabelle sind gleich definiert"
+ hun "A MERGE tablaban talalhato tablak definicioja nem azonos"
+ ita "Non tutte le tabelle nella tabella di MERGE sono definite in maniera identica"
+ jpn "MERGEè¡¨ã®æ§‹æˆè¡¨ãŒã‚ªãƒ¼ãƒ—ンã§ãã¾ã›ã‚“。列定義ãŒç•°ãªã‚‹ã‹ã€MyISAM表ã§ã¯ãªã„ã‹ã€å­˜åœ¨ã—ã¾ã›ã‚“。"
+ kor "All tables in the MERGE table are not defined identically"
+ nor "All tables in the MERGE table are not defined identically"
+ norwegian-ny "All tables in the MERGE table are not defined identically"
+ pol "All tables in the MERGE table are not defined identically"
+ por "Todas as tabelas contidas na tabela fundida (MERGE) não estão definidas identicamente"
+ rum "Toate tabelele din tabela MERGE nu sint definite identic"
+ rus "Ðе вÑе таблицы в MERGE определены одинаково"
+ serbian "Tabele iskoriÅ¡tene u 'MERGE' tabeli nisu definisane na isti naÄin"
+ slo "All tables in the MERGE table are not defined identically"
+ spa "Todas las tablas en la MERGE tabla no estan definidas identicamente"
+ swe "Tabellerna i MERGE-tabellen är inte identiskt definierade"
+ ukr "Таблиці у MERGE TABLE мають різну Ñтруктуру"
+ER_DUP_UNIQUE 23000
+ cze "Kvůli unique constraintu nemozu zapsat do tabulky '%-.192s'"
+ dan "Kan ikke skrive til tabellen '%-.192s' fordi det vil bryde CONSTRAINT regler"
+ nla "Kan niet opslaan naar table '%-.192s' vanwege 'unique' beperking"
+ eng "Can't write, because of unique constraint, to table '%-.192s'"
+ est "Ei suuda kirjutada tabelisse '%-.192s', kuna see rikub ühesuse kitsendust"
+ fre "Écriture impossible à cause d'un index UNIQUE sur la table '%-.192s'"
+ ger "Schreiben in Tabelle '%-.192s' nicht möglich wegen einer Eindeutigkeitsbeschränkung (unique constraint)"
+ hun "A '%-.192s' nem irhato, az egyedi mezok miatt"
+ jpn "ä¸€æ„æ€§åˆ¶ç´„é•åã®ãŸã‚ã€è¡¨ '%-.192s' ã«æ›¸ãè¾¼ã‚ã¾ã›ã‚“。"
+ ita "Impossibile scrivere nella tabella '%-.192s' per limitazione di unicita`"
+ por "Não pode gravar, devido à restrição UNIQUE, na tabela '%-.192s'"
+ rum "Nu pot scrie pe hard-drive, din cauza constraintului unic (unique constraint) pentru tabela '%-.192s'"
+ rus "Ðевозможно запиÑать в таблицу '%-.192s' из-за ограничений уникального ключа"
+ serbian "Zbog provere jedinstvenosti ne mogu da upišem podatke u tabelu '%-.192s'"
+ spa "No puedo escribir, debido al único constraint, para tabla '%-.192s'"
+ swe "Kan inte skriva till tabell '%-.192s'; UNIQUE-test"
+ ukr "Ðе можу запиÑати до таблиці '%-.192s', з причини вимог унікальноÑті"
+ER_BLOB_KEY_WITHOUT_LENGTH 42000
+ cze "BLOB sloupec '%-.192s' je použit ve specifikaci klíÄe bez délky"
+ dan "BLOB kolonnen '%-.192s' brugt i nøglespecifikation uden nøglelængde"
+ nla "BLOB kolom '%-.192s' gebruikt in zoeksleutel specificatie zonder zoeksleutel lengte"
+ eng "BLOB/TEXT column '%-.192s' used in key specification without a key length"
+ est "BLOB-tüüpi tulp '%-.192s' on kasutusel võtmes ilma pikkust määratlemata"
+ fre "La colonne '%-.192s' de type BLOB est utilisée dans une définition d'index sans longueur d'index"
+ ger "BLOB- oder TEXT-Spalte '%-.192s' wird in der Schlüsseldefinition ohne Schlüssellängenangabe verwendet"
+ greek "BLOB column '%-.192s' used in key specification without a key length"
+ hun "BLOB mezo '%-.192s' hasznalt a mezo specifikacioban, a mezohossz megadasa nelkul"
+ ita "La colonna '%-.192s' di tipo BLOB e` usata in una chiave senza specificarne la lunghezza"
+ jpn "BLOB列 '%-.192s' をキーã«ä½¿ç”¨ã™ã‚‹ã«ã¯é•·ã•指定ãŒå¿…è¦ã§ã™ã€‚"
+ kor "BLOB column '%-.192s' used in key specification without a key length"
+ nor "BLOB column '%-.192s' used in key specification without a key length"
+ norwegian-ny "BLOB column '%-.192s' used in key specification without a key length"
+ pol "BLOB column '%-.192s' used in key specification without a key length"
+ por "Coluna BLOB '%-.192s' usada na especificação de chave sem o comprimento da chave"
+ rum "Coloana BLOB '%-.192s' este folosita in specificarea unei chei fara ca o lungime de cheie sa fie folosita"
+ rus "Столбец типа BLOB '%-.192s' был указан в определении ключа без ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð´Ð»Ð¸Ð½Ñ‹ ключа"
+ serbian "BLOB kolona '%-.192s' je upotrebljena u specifikaciji kljuÄa bez navoÄ‘enja dužine kljuÄa"
+ slo "BLOB column '%-.192s' used in key specification without a key length"
+ spa "Columna BLOB column '%-.192s' usada en especificación de clave sin tamaño de la clave"
+ swe "Du har inte angett någon nyckellängd för BLOB '%-.192s'"
+ ukr "Стовбець BLOB '%-.192s' викориÑтано у визначенні ключа без Ð²ÐºÐ°Ð·Ð°Ð½Ð½Ñ Ð´Ð¾Ð²Ð¶Ð¸Ð½Ð¸ ключа"
+ER_PRIMARY_CANT_HAVE_NULL 42000
+ cze "VÅ¡echny Äásti primárního klíÄe musejí být NOT NULL; pokud potÅ™ebujete NULL, použijte UNIQUE"
+ dan "Alle dele af en PRIMARY KEY skal være NOT NULL; Hvis du skal bruge NULL i nøglen, brug UNIQUE istedet"
+ nla "Alle delen van een PRIMARY KEY moeten NOT NULL zijn; Indien u NULL in een zoeksleutel nodig heeft kunt u UNIQUE gebruiken"
+ eng "All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead"
+ est "Kõik PRIMARY KEY peavad olema määratletud NOT NULL piiranguga; vajadusel kasuta UNIQUE tüüpi võtit"
+ fre "Toutes les parties d'un index PRIMARY KEY doivent être NOT NULL; Si vous avez besoin d'un NULL dans l'index, utilisez un index UNIQUE"
+ ger "Alle Teile eines PRIMARY KEY müssen als NOT NULL definiert sein. Wenn NULL in einem Schlüssel benötigt wird, muss ein UNIQUE-Schlüssel verwendet werden"
+ hun "Az elsodleges kulcs teljes egeszeben csak NOT NULL tipusu lehet; Ha NULL mezot szeretne a kulcskent, hasznalja inkabb a UNIQUE-ot"
+ ita "Tutte le parti di una chiave primaria devono essere dichiarate NOT NULL; se necessitano valori NULL nelle chiavi utilizzare UNIQUE"
+ jpn "PRIMARY KEYã®åˆ—ã¯å…¨ã¦NOT NULLã§ãªã‘れã°ã„ã‘ã¾ã›ã‚“。UNIQUE索引ã§ã‚れã°NULLã‚’å«ã‚€ã“ã¨ãŒå¯èƒ½ã§ã™ã€‚"
+ por "Todas as partes de uma chave primária devem ser não-nulas. Se você precisou usar um valor nulo (NULL) em uma chave, use a cláusula UNIQUE em seu lugar"
+ rum "Toate partile unei chei primare (PRIMARY KEY) trebuie sa fie NOT NULL; Daca aveti nevoie de NULL in vreo cheie, folositi UNIQUE in schimb"
+ rus "Ð’Ñе чаÑти первичного ключа (PRIMARY KEY) должны быть определены как NOT NULL; ЕÑли вам нужна поддержка величин NULL в ключе, воÑпользуйтеÑÑŒ индекÑом UNIQUE"
+ serbian "Svi delovi primarnog kljuÄa moraju biti razliÄiti od NULL; Ako Vam ipak treba NULL vrednost u kljuÄu, upotrebite 'UNIQUE'"
+ spa "Todas las partes de un PRIMARY KEY deben ser NOT NULL; Si necesitas NULL en una clave, use UNIQUE"
+ swe "Alla delar av en PRIMARY KEY måste vara NOT NULL; Om du vill ha en nyckel med NULL, använd UNIQUE istället"
+ ukr "УÑÑ– чаÑтини PRIMARY KEY повинні бути NOT NULL; Якщо ви потребуєте NULL у ключі, ÑкориÑтайтеÑÑ UNIQUE"
+ER_TOO_MANY_ROWS 42000
+ cze "Výsledek obsahuje více než jeden řádek"
+ dan "Resultatet bestod af mere end een række"
+ nla "Resultaat bevatte meer dan een rij"
+ eng "Result consisted of more than one row"
+ est "Tulemis oli rohkem kui üks kirje"
+ fre "Le résultat contient plus d'un enregistrement"
+ ger "Ergebnis besteht aus mehr als einer Zeile"
+ hun "Az eredmeny tobb, mint egy sort tartalmaz"
+ ita "Il risultato consiste di piu` di una riga"
+ jpn "çµæžœãŒ2行以上ã§ã™ã€‚"
+ por "O resultado consistiu em mais do que uma linha"
+ rum "Resultatul constista din mai multe linii"
+ rus "Ð’ результате возвращена более чем одна Ñтрока"
+ serbian "Rezultat je saÄinjen od viÅ¡e slogova"
+ spa "Resultado compuesto de mas que una línea"
+ swe "Resultet bestod av mera än en rad"
+ ukr "Результат знаходитьÑÑ Ñƒ більше ніж одній Ñтроці"
+ER_REQUIRES_PRIMARY_KEY 42000
+ cze "Tento typ tabulky vyžaduje primární klíÄ"
+ dan "Denne tabeltype kræver en primærnøgle"
+ nla "Dit tabel type heeft een primaire zoeksleutel nodig"
+ eng "This table type requires a primary key"
+ est "Antud tabelitüüp nõuab primaarset võtit"
+ fre "Ce type de table nécessite une clé primaire (PRIMARY KEY)"
+ ger "Dieser Tabellentyp benötigt einen Primärschlüssel (PRIMARY KEY)"
+ hun "Az adott tablatipushoz elsodleges kulcs hasznalata kotelezo"
+ ita "Questo tipo di tabella richiede una chiave primaria"
+ jpn "使用ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ã§ã¯ã€PRIMARY KEYãŒå¿…è¦ã§ã™ã€‚"
+ por "Este tipo de tabela requer uma chave primária"
+ rum "Aceast tip de tabela are nevoie de o cheie primara"
+ rus "Этот тип таблицы требует Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€Ð²Ð¸Ñ‡Ð½Ð¾Ð³Ð¾ ключа"
+ serbian "Ovaj tip tabele zahteva da imate definisan primarni kljuÄ"
+ spa "Este tipo de tabla necesita de una primary key"
+ swe "Denna tabelltyp kräver en PRIMARY KEY"
+ ukr "Цей тип таблиці потребує первинного ключа"
+ER_NO_RAID_COMPILED
+ cze "Tato verze MySQL není zkompilována s podporou RAID"
+ dan "Denne udgave af MariaDB er ikke oversat med understøttelse af RAID"
+ nla "Deze versie van MariaDB is niet gecompileerd met RAID ondersteuning"
+ eng "This version of MariaDB is not compiled with RAID support"
+ est "Antud MariaDB versioon on kompileeritud ilma RAID toeta"
+ fre "Cette version de MariaDB n'est pas compilée avec le support RAID"
+ ger "Diese MariaDB-Version ist nicht mit RAID-Unterstützung kompiliert"
+ hun "Ezen leforditott MariaDB verzio nem tartalmaz RAID support-ot"
+ ita "Questa versione di MYSQL non e` compilata con il supporto RAID"
+ jpn "ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®MySQLã¯RAIDサãƒãƒ¼ãƒˆã‚’å«ã‚ã¦ã‚³ãƒ³ãƒ‘イルã•れã¦ã„ã¾ã›ã‚“。"
+ por "Esta versão do MariaDB não foi compilada com suporte a RAID"
+ rum "Aceasta versiune de MariaDB, nu a fost compilata cu suport pentru RAID"
+ rus "Эта верÑÐ¸Ñ MariaDB Ñкомпилирована без поддержки RAID"
+ serbian "Ova verzija MariaDB servera nije kompajlirana sa podrškom za RAID uređaje"
+ spa "Esta versión de MariaDB no es compilada con soporte RAID"
+ swe "Denna version av MariaDB är inte kompilerad med RAID"
+ ukr "Ð¦Ñ Ð²ÐµÑ€ÑÑ–Ñ MariaDB не зкомпільована з підтримкою RAID"
+ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE
+ cze "Update tabulky bez WHERE s klíÄem není v módu bezpeÄných update dovoleno"
+ dan "Du bruger sikker opdaterings modus ('safe update mode') og du forsøgte at opdatere en tabel uden en WHERE klausul, der gør brug af et KEY felt"
+ nla "U gebruikt 'safe update mode' en u probeerde een tabel te updaten zonder een WHERE met een KEY kolom"
+ eng "You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column"
+ est "Katse muuta tabelit turvalises rezhiimis ilma WHERE klauslita"
+ fre "Vous êtes en mode 'safe update' et vous essayez de faire un UPDATE sans clause WHERE utilisant un index"
+ ger "MariaDB läuft im sicheren Aktualisierungsmodus (safe update mode). Sie haben versucht, eine Tabelle zu aktualisieren, ohne in der WHERE-Klausel ein KEY-Feld anzugeben"
+ hun "On a biztonsagos update modot hasznalja, es WHERE that uses a KEY column"
+ ita "In modalita` 'safe update' si e` cercato di aggiornare una tabella senza clausola WHERE su una chiave"
+ jpn "'safe update mode'ã§ã€ç´¢å¼•を利用ã™ã‚‹WHEREå¥ã®ç„¡ã„更新処ç†ã‚’実行ã—よã†ã¨ã—ã¾ã—ãŸã€‚"
+ por "Você está usando modo de atualização seguro e tentou atualizar uma tabela sem uma cláusula WHERE que use uma coluna chave"
+ rus "Ð’Ñ‹ работаете в режиме безопаÑных обновлений (safe update mode) и попробовали изменить таблицу без иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ð¾Ð³Ð¾ Ñтолбца в чаÑти WHERE"
+ serbian "Vi koristite safe update mod servera, a probali ste da promenite podatke bez 'WHERE' komande koja koristi kolonu kljuÄa"
+ spa "Tu estás usando modo de actualización segura y tentado actualizar una tabla sin un WHERE que usa una KEY columna"
+ swe "Du använder 'säker uppdateringsmod' och försökte uppdatera en tabell utan en WHERE-sats som använder sig av en nyckel"
+ ukr "Ви у режимі безпечного Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚Ð° намагаєтеÑÑŒ оновити таблицю без оператора WHERE, що викориÑтовує KEY Ñтовбець"
+ER_KEY_DOES_NOT_EXITS 42000 S1009
+ cze "KlÃ­Ä '%-.192s' v tabulce '%-.192s' neexistuje"
+ dan "Nøglen '%-.192s' eksisterer ikke i tabellen '%-.192s'"
+ nla "Zoeksleutel '%-.192s' bestaat niet in tabel '%-.192s'"
+ eng "Key '%-.192s' doesn't exist in table '%-.192s'"
+ est "Võti '%-.192s' ei eksisteeri tabelis '%-.192s'"
+ fre "L'index '%-.192s' n'existe pas sur la table '%-.192s'"
+ ger "Schlüssel '%-.192s' existiert in der Tabelle '%-.192s' nicht"
+ hun "A '%-.192s' kulcs nem letezik a '%-.192s' tablaban"
+ ita "La chiave '%-.192s' non esiste nella tabella '%-.192s'"
+ jpn "索引 '%-.192s' ã¯è¡¨ '%-.192s' ã«ã¯å­˜åœ¨ã—ã¾ã›ã‚“。"
+ por "Chave '%-.192s' não existe na tabela '%-.192s'"
+ rus "Ключ '%-.192s' не ÑущеÑтвует в таблице '%-.192s'"
+ serbian "KljuÄ '%-.192s' ne postoji u tabeli '%-.192s'"
+ spa "Clave '%-.192s' no existe en la tabla '%-.192s'"
+ swe "Nyckel '%-.192s' finns inte in tabell '%-.192s'"
+ ukr "Ключ '%-.192s' не Ñ–Ñнує в таблиці '%-.192s'"
+ER_CHECK_NO_SUCH_TABLE 42000
+ cze "Nemohu otevřít tabulku"
+ dan "Kan ikke åbne tabellen"
+ nla "Kan tabel niet openen"
+ eng "Can't open table"
+ est "Ei suuda avada tabelit"
+ fre "Impossible d'ouvrir la table"
+ ger "Kann Tabelle nicht öffnen"
+ hun "Nem tudom megnyitni a tablat"
+ ita "Impossibile aprire la tabella"
+ jpn "表をオープンã§ãã¾ã›ã‚“。"
+ por "Não pode abrir a tabela"
+ rus "Ðевозможно открыть таблицу"
+ serbian "Ne mogu da otvorim tabelu"
+ spa "No puedo abrir tabla"
+ swe "Kan inte öppna tabellen"
+ ukr "Ðе можу відкрити таблицю"
+ER_CHECK_NOT_IMPLEMENTED 42000
+ cze "Handler tabulky nepodporuje %s"
+ dan "Denne tabeltype understøtter ikke %s"
+ nla "De 'handler' voor de tabel ondersteund geen %s"
+ eng "The storage engine for the table doesn't support %s"
+ est "Antud tabelitüüp ei toeta %s käske"
+ fre "Ce type de table ne supporte pas les %s"
+ ger "Die Speicher-Engine für diese Tabelle unterstützt kein %s"
+ greek "The handler for the table doesn't support %s"
+ hun "A tabla kezeloje (handler) nem tamogatja az %s"
+ ita "Il gestore per la tabella non supporta il %s"
+ jpn "ã“ã®è¡¨ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ã¯ '%s' を利用ã§ãã¾ã›ã‚“。"
+ kor "The handler for the table doesn't support %s"
+ nor "The handler for the table doesn't support %s"
+ norwegian-ny "The handler for the table doesn't support %s"
+ pol "The handler for the table doesn't support %s"
+ por "O manipulador de tabela não suporta %s"
+ rum "The handler for the table doesn't support %s"
+ rus "Обработчик таблицы не поддерживает Ñтого: %s"
+ serbian "Handler za ovu tabelu ne dozvoljava %s komande"
+ slo "The handler for the table doesn't support %s"
+ spa "El manipulador de la tabla no permite soporte para %s"
+ swe "Tabellhanteraren för denna tabell kan inte göra %s"
+ ukr "Вказівник таблиці не підтримуе %s"
+ER_CANT_DO_THIS_DURING_AN_TRANSACTION 25000
+ cze "Provedení tohoto příkazu není v transakci dovoleno"
+ dan "Du må ikke bruge denne kommando i en transaktion"
+ nla "Het is u niet toegestaan dit commando uit te voeren binnen een transactie"
+ eng "You are not allowed to execute this command in a transaction"
+ est "Seda käsku ei saa kasutada transaktsiooni sees"
+ fre "Vous n'êtes pas autorisé à exécute cette commande dans une transaction"
+ ger "Sie dürfen diesen Befehl nicht in einer Transaktion ausführen"
+ hun "Az On szamara nem engedelyezett a parancs vegrehajtasa a tranzakcioban"
+ ita "Non puoi eseguire questo comando in una transazione"
+ jpn "ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³å†…ã§å®Ÿè¡Œã§ãã¾ã›ã‚“。"
+ por "Não lhe é permitido executar este comando em uma transação"
+ rus "Вам не разрешено выполнÑть Ñту команду в транзакции"
+ serbian "Nije Vam dozvoljeno da izvršite ovu komandu u transakciji"
+ spa "No tienes el permiso para ejecutar este comando en una transición"
+ swe "Du får inte utföra detta kommando i en transaktion"
+ ukr "Вам не дозволено виконувати цю команду в транзакції"
+ER_ERROR_DURING_COMMIT
+ cze "Chyba %M při COMMIT"
+ dan "Modtog fejl %M mens kommandoen COMMIT blev udført"
+ nla "Kreeg fout %M tijdens COMMIT"
+ eng "Got error %M during COMMIT"
+ est "Viga %M käsu COMMIT täitmisel"
+ fre "Erreur %M lors du COMMIT"
+ ger "Fehler %M beim COMMIT"
+ hun "%M hiba a COMMIT vegrehajtasa soran"
+ ita "Rilevato l'errore %M durante il COMMIT"
+ jpn "COMMIT中ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ por "Obteve erro %M durante COMMIT"
+ rus "Получена ошибка %M в процеÑÑе COMMIT"
+ serbian "Greška %M za vreme izvršavanja komande 'COMMIT'"
+ spa "Obtenido error %M durante COMMIT"
+ swe "Fick fel %M vid COMMIT"
+ ukr "Отримано помилку %M під Ñ‡Ð°Ñ COMMIT"
+ER_ERROR_DURING_ROLLBACK
+ cze "Chyba %M při ROLLBACK"
+ dan "Modtog fejl %M mens kommandoen ROLLBACK blev udført"
+ nla "Kreeg fout %M tijdens ROLLBACK"
+ eng "Got error %M during ROLLBACK"
+ est "Viga %M käsu ROLLBACK täitmisel"
+ fre "Erreur %M lors du ROLLBACK"
+ ger "Fehler %M beim ROLLBACK"
+ hun "%M hiba a ROLLBACK vegrehajtasa soran"
+ ita "Rilevato l'errore %M durante il ROLLBACK"
+ jpn "ROLLBACK中ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ por "Obteve erro %M durante ROLLBACK"
+ rus "Получена ошибка %M в процеÑÑе ROLLBACK"
+ serbian "Greška %M za vreme izvršavanja komande 'ROLLBACK'"
+ spa "Obtenido error %M durante ROLLBACK"
+ swe "Fick fel %M vid ROLLBACK"
+ ukr "Отримано помилку %M під Ñ‡Ð°Ñ ROLLBACK"
+ER_ERROR_DURING_FLUSH_LOGS
+ cze "Chyba %M při FLUSH_LOGS"
+ dan "Modtog fejl %M mens kommandoen FLUSH_LOGS blev udført"
+ nla "Kreeg fout %M tijdens FLUSH_LOGS"
+ eng "Got error %M during FLUSH_LOGS"
+ est "Viga %M käsu FLUSH_LOGS täitmisel"
+ fre "Erreur %M lors du FLUSH_LOGS"
+ ger "Fehler %M bei FLUSH_LOGS"
+ hun "%M hiba a FLUSH_LOGS vegrehajtasa soran"
+ ita "Rilevato l'errore %M durante il FLUSH_LOGS"
+ jpn "FLUSH_LOGS中ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ por "Obteve erro %M durante FLUSH_LOGS"
+ rus "Получена ошибка %M в процеÑÑе FLUSH_LOGS"
+ serbian "Greška %M za vreme izvršavanja komande 'FLUSH_LOGS'"
+ spa "Obtenido error %M durante FLUSH_LOGS"
+ swe "Fick fel %M vid FLUSH_LOGS"
+ ukr "Отримано помилку %M під Ñ‡Ð°Ñ FLUSH_LOGS"
+ER_ERROR_DURING_CHECKPOINT
+ cze "Chyba %M při CHECKPOINT"
+ dan "Modtog fejl %M mens kommandoen CHECKPOINT blev udført"
+ nla "Kreeg fout %M tijdens CHECKPOINT"
+ eng "Got error %M during CHECKPOINT"
+ est "Viga %M käsu CHECKPOINT täitmisel"
+ fre "Erreur %M lors du CHECKPOINT"
+ ger "Fehler %M bei CHECKPOINT"
+ hun "%M hiba a CHECKPOINT vegrehajtasa soran"
+ ita "Rilevato l'errore %M durante il CHECKPOINT"
+ jpn "CHECKPOINT中ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ por "Obteve erro %M durante CHECKPOINT"
+ rus "Получена ошибка %M в процеÑÑе CHECKPOINT"
+ serbian "Greška %M za vreme izvršavanja komande 'CHECKPOINT'"
+ spa "Obtenido error %M durante CHECKPOINT"
+ swe "Fick fel %M vid CHECKPOINT"
+ ukr "Отримано помилку %M під Ñ‡Ð°Ñ CHECKPOINT"
+ER_NEW_ABORTING_CONNECTION 08S01
+ cze "Spojení %ld do databáze: '%-.192s' uživatel: '%-.48s' stroj: '%-.64s' (%-.64s) bylo přerušeno"
+ dan "Afbrød forbindelsen %ld til databasen '%-.192s' bruger: '%-.48s' vært: '%-.64s' (%-.64s)"
+ nla "Afgebroken verbinding %ld naar db: '%-.192s' gebruiker: '%-.48s' host: '%-.64s' (%-.64s)"
+ eng "Aborted connection %ld to db: '%-.192s' user: '%-.48s' host: '%-.64s' (%-.64s)"
+ est "Ühendus katkestatud %ld andmebaas: '%-.192s' kasutaja: '%-.48s' masin: '%-.64s' (%-.64s)"
+ fre "Connection %ld avortée vers la bd: '%-.192s' utilisateur: '%-.48s' hôte: '%-.64s' (%-.64s)"
+ ger "Abbruch der Verbindung %ld zur Datenbank '%-.192s'. Benutzer: '%-.48s', Host: '%-.64s' (%-.64s)"
+ ita "Interrotta la connessione %ld al db: ''%-.192s' utente: '%-.48s' host: '%-.64s' (%-.64s)"
+ jpn "接続 %ld ãŒä¸­æ–­ã•れã¾ã—ãŸã€‚データベース: '%-.192s' ユーザー: '%-.48s' ホスト: '%-.64s' (%-.64s)"
+ por "Conexão %ld abortada para banco de dados '%-.192s' - usuário '%-.48s' - 'host' '%-.64s' ('%-.64s')"
+ rus "Прервано Ñоединение %ld к базе данных '%-.192s' Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%-.48s' Ñ Ñ…Ð¾Ñта '%-.64s' (%-.64s)"
+ serbian "Prekinuta konekcija broj %ld ka bazi: '%-.192s' korisnik je bio: '%-.48s' a host: '%-.64s' (%-.64s)"
+ spa "Abortada conexión %ld para db: '%-.192s' usuario: '%-.48s' servidor: '%-.64s' (%-.64s)"
+ swe "Avbröt länken för tråd %ld till db '%-.192s', användare '%-.48s', host '%-.64s' (%-.64s)"
+ ukr "Перервано з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ %ld до бази данних: '%-.192s' кориÑтувач: '%-.48s' хоÑÑ‚: '%-.64s' (%-.64s)"
+ER_UNUSED_10
+ eng "You should never see it"
+ER_FLUSH_MASTER_BINLOG_CLOSED
+ eng "Binlog closed, cannot RESET MASTER"
+ ger "Binlog geschlossen. Kann RESET MASTER nicht ausführen"
+ jpn "ãƒã‚¤ãƒŠãƒªãƒ­ã‚°ãŒã‚¯ãƒ­ãƒ¼ã‚ºã•れã¦ã„ã¾ã™ã€‚RESET MASTER を実行ã§ãã¾ã›ã‚“。"
+ por "Binlog fechado. Não pode fazer RESET MASTER"
+ rus "Двоичный журнал Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð·Ð°ÐºÑ€Ñ‹Ñ‚, невозможно выполнить RESET MASTER"
+ serbian "Binarni log file zatvoren, ne mogu da izvršim komandu 'RESET MASTER'"
+ ukr "Реплікаційний лог закрито, не можу виконати RESET MASTER"
+ER_INDEX_REBUILD
+ cze "Přebudování indexu dumpnuté tabulky '%-.192s' nebylo úspěšné"
+ dan "Kunne ikke genopbygge indekset for den dumpede tabel '%-.192s'"
+ nla "Gefaald tijdens heropbouw index van gedumpte tabel '%-.192s'"
+ eng "Failed rebuilding the index of dumped table '%-.192s'"
+ fre "La reconstruction de l'index de la table copiée '%-.192s' a échoué"
+ ger "Neuerstellung des Index der Dump-Tabelle '%-.192s' fehlgeschlagen"
+ greek "Failed rebuilding the index of dumped table '%-.192s'"
+ hun "Failed rebuilding the index of dumped table '%-.192s'"
+ ita "Fallita la ricostruzione dell'indice della tabella copiata '%-.192s'"
+ jpn "ダンプ表 '%-.192s' ã®ç´¢å¼•冿§‹ç¯‰ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+ por "Falhou na reconstrução do índice da tabela 'dumped' '%-.192s'"
+ rus "Ошибка переÑтройки индекÑа Ñохраненной таблицы '%-.192s'"
+ serbian "Izgradnja indeksa dump-ovane tabele '%-.192s' nije uspela"
+ spa "Falla reconstruyendo el indice de la tabla dumped '%-.192s'"
+ ukr "Ðевдале Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ–Ð½Ð´ÐµÐºÑа переданої таблиці '%-.192s'"
+ER_MASTER
+ cze "Chyba masteru: '%-.64s'"
+ dan "Fejl fra master: '%-.64s'"
+ nla "Fout van master: '%-.64s'"
+ eng "Error from master: '%-.64s'"
+ fre "Erreur reçue du maître: '%-.64s'"
+ ger "Fehler vom Master: '%-.64s'"
+ ita "Errore dal master: '%-.64s"
+ jpn "マスターã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿ: '%-.64s'"
+ por "Erro no 'master' '%-.64s'"
+ rus "Ошибка от головного Ñервера: '%-.64s'"
+ serbian "Greška iz glavnog servera '%-.64s' u klasteru"
+ spa "Error del master: '%-.64s'"
+ swe "Fel från master: '%-.64s'"
+ ukr "Помилка від головного: '%-.64s'"
+ER_MASTER_NET_READ 08S01
+ cze "Síťová chyba pÅ™i Ätení z masteru"
+ dan "Netværksfejl ved læsning fra master"
+ nla "Net fout tijdens lezen van master"
+ eng "Net error reading from master"
+ fre "Erreur de lecture réseau reçue du maître"
+ ger "Netzfehler beim Lesen vom Master"
+ ita "Errore di rete durante la ricezione dal master"
+ jpn "マスターã‹ã‚‰ã®ãƒ‡ãƒ¼ã‚¿å—信中ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚¨ãƒ©ãƒ¼"
+ por "Erro de rede lendo do 'master'"
+ rus "Возникла ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð² процеÑÑе коммуникации Ñ Ð³Ð¾Ð»Ð¾Ð²Ð½Ñ‹Ð¼ Ñервером"
+ serbian "Greška u primanju mrežnih paketa sa glavnog servera u klasteru"
+ spa "Error de red leyendo del master"
+ swe "Fick nätverksfel vid läsning från master"
+ ukr "Мережева помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð²Ñ–Ð´ головного"
+ER_MASTER_NET_WRITE 08S01
+ cze "Síťová chyba při zápisu na master"
+ dan "Netværksfejl ved skrivning til master"
+ nla "Net fout tijdens schrijven naar master"
+ eng "Net error writing to master"
+ fre "Erreur d'écriture réseau reçue du maître"
+ ger "Netzfehler beim Schreiben zum Master"
+ ita "Errore di rete durante l'invio al master"
+ jpn "マスターã¸ã®ãƒ‡ãƒ¼ã‚¿é€ä¿¡ä¸­ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚¨ãƒ©ãƒ¼"
+ por "Erro de rede gravando no 'master'"
+ rus "Возникла ошибка запиÑи в процеÑÑе коммуникации Ñ Ð³Ð¾Ð»Ð¾Ð²Ð½Ñ‹Ð¼ Ñервером"
+ serbian "Greška u slanju mrežnih paketa na glavni server u klasteru"
+ spa "Error de red escribiendo para el master"
+ swe "Fick nätverksfel vid skrivning till master"
+ ukr "Мережева помилка запиÑу до головного"
+ER_FT_MATCHING_KEY_NOT_FOUND
+ cze "Žádný sloupec nemá vytvořen fulltextový index"
+ dan "Kan ikke finde en FULLTEXT nøgle som svarer til kolonne listen"
+ nla "Kan geen FULLTEXT index vinden passend bij de kolom lijst"
+ eng "Can't find FULLTEXT index matching the column list"
+ est "Ei suutnud leida FULLTEXT indeksit, mis kattuks kasutatud tulpadega"
+ fre "Impossible de trouver un index FULLTEXT correspondant à cette liste de colonnes"
+ ger "Kann keinen FULLTEXT-Index finden, der der Feldliste entspricht"
+ ita "Impossibile trovare un indice FULLTEXT che corrisponda all'elenco delle colonne"
+ jpn "列リストã«å¯¾å¿œã™ã‚‹å…¨æ–‡ç´¢å¼•(FULLTEXT)ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
+ por "Não pode encontrar um índice para o texto todo que combine com a lista de colunas"
+ rus "Ðевозможно отыÑкать полнотекÑтовый (FULLTEXT) индекÑ, ÑоответÑтвующий ÑпиÑку Ñтолбцов"
+ serbian "Ne mogu da pronađem 'FULLTEXT' indeks koli odgovara listi kolona"
+ spa "No puedo encontrar índice FULLTEXT correspondiendo a la lista de columnas"
+ swe "Hittar inte ett FULLTEXT-index i kolumnlistan"
+ ukr "Ðе можу знайти FULLTEXT індекÑ, що відповідає переліку Ñтовбців"
+ER_LOCK_OR_ACTIVE_TRANSACTION
+ cze "Nemohu provést zadaný příkaz, protože existují aktivní zamÄené tabulky nebo aktivní transakce"
+ dan "Kan ikke udføre den givne kommando fordi der findes aktive, låste tabeller eller fordi der udføres en transaktion"
+ nla "Kan het gegeven commando niet uitvoeren, want u heeft actieve gelockte tabellen of een actieve transactie"
+ eng "Can't execute the given command because you have active locked tables or an active transaction"
+ est "Ei suuda täita antud käsku kuna on aktiivseid lukke või käimasolev transaktsioon"
+ fre "Impossible d'exécuter la commande car vous avez des tables verrouillées ou une transaction active"
+ ger "Kann den angegebenen Befehl wegen einer aktiven Tabellensperre oder einer aktiven Transaktion nicht ausführen"
+ ita "Impossibile eseguire il comando richiesto: tabelle sotto lock o transazione in atto"
+ jpn "ã™ã§ã«ã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªè¡¨ãƒ­ãƒƒã‚¯ã‚„トランザクションãŒã‚ã‚‹ãŸã‚ã€ã‚³ãƒžãƒ³ãƒ‰ã‚’実行ã§ãã¾ã›ã‚“。"
+ por "Não pode executar o comando dado porque você tem tabelas ativas travadas ou uma transação ativa"
+ rus "Ðевозможно выполнить указанную команду, поÑкольку у Ð²Ð°Ñ Ð¿Ñ€Ð¸ÑутÑтвуют активно заблокированные таблица или Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð°Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ"
+ serbian "Ne mogu da izvrÅ¡im datu komandu zbog toga Å¡to su tabele zakljuÄane ili je transakcija u toku"
+ spa "No puedo ejecutar el comando dado porque tienes tablas bloqueadas o una transición activa"
+ swe "Kan inte utföra kommandot emedan du har en låst tabell eller an aktiv transaktion"
+ ukr "Ðе можу виконати подану команду тому, що Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð° або виконуєтьÑÑ Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ñ–Ñ"
+ER_UNKNOWN_SYSTEM_VARIABLE
+ cze "Neznámá systémová proměnná '%-.64s'"
+ dan "Ukendt systemvariabel '%-.64s'"
+ nla "Onbekende systeem variabele '%-.64s'"
+ eng "Unknown system variable '%-.64s'"
+ est "Tundmatu süsteemne muutuja '%-.64s'"
+ fre "Variable système '%-.64s' inconnue"
+ ger "Unbekannte Systemvariable '%-.64s'"
+ ita "Variabile di sistema '%-.64s' sconosciuta"
+ jpn "'%-.64s' ã¯ä¸æ˜Žãªã‚·ã‚¹ãƒ†ãƒ å¤‰æ•°ã§ã™ã€‚"
+ por "Variável de sistema '%-.64s' desconhecida"
+ rus "ÐеизвеÑÑ‚Ð½Ð°Ñ ÑиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s'"
+ serbian "Nepoznata sistemska promenljiva '%-.64s'"
+ spa "Desconocida variable de sistema '%-.64s'"
+ swe "Okänd systemvariabel: '%-.64s'"
+ ukr "Ðевідома ÑиÑтемна змінна '%-.64s'"
+ER_CRASHED_ON_USAGE
+ cze "Tabulka '%-.192s' je oznaÄena jako poruÅ¡ená a mÄ›la by být opravena"
+ dan "Tabellen '%-.192s' er markeret med fejl og bør repareres"
+ nla "Tabel '%-.192s' staat als gecrashed gemarkeerd en dient te worden gerepareerd"
+ eng "Table '%-.192s' is marked as crashed and should be repaired"
+ est "Tabel '%-.192s' on märgitud vigaseks ja tuleb parandada"
+ fre "La table '%-.192s' est marquée 'crashed' et devrait être réparée"
+ ger "Tabelle '%-.192s' ist als defekt markiert und sollte repariert werden"
+ ita "La tabella '%-.192s' e` segnalata come corrotta e deve essere riparata"
+ jpn "表 '%-.192s' ã¯å£Šã‚Œã¦ã„ã¾ã™ã€‚修復ãŒå¿…è¦ã§ã™ã€‚"
+ por "Tabela '%-.192s' está marcada como danificada e deve ser reparada"
+ rus "Таблица '%-.192s' помечена как иÑÐ¿Ð¾Ñ€Ñ‡ÐµÐ½Ð½Ð°Ñ Ð¸ должна пройти проверку и ремонт"
+ serbian "Tabela '%-.192s' je markirana kao oštećena i trebala bi biti popravljena"
+ spa "Tabla '%-.192s' está marcada como crashed y debe ser reparada"
+ swe "Tabell '%-.192s' är trasig och bör repareras med REPAIR TABLE"
+ ukr "Таблицю '%-.192s' марковано Ñк зіпÑовану та Ñ—Ñ— потрібно відновити"
+ER_CRASHED_ON_REPAIR
+ cze "Tabulka '%-.192s' je oznaÄena jako poruÅ¡ená a poslední (automatická?) oprava se nezdaÅ™ila"
+ dan "Tabellen '%-.192s' er markeret med fejl og sidste (automatiske?) REPAIR fejlede"
+ nla "Tabel '%-.192s' staat als gecrashed gemarkeerd en de laatste (automatische?) reparatie poging mislukte"
+ eng "Table '%-.192s' is marked as crashed and last (automatic?) repair failed"
+ est "Tabel '%-.192s' on märgitud vigaseks ja viimane (automaatne?) parandus ebaõnnestus"
+ fre "La table '%-.192s' est marquée 'crashed' et le dernier 'repair' a échoué"
+ ger "Tabelle '%-.192s' ist als defekt markiert und der letzte (automatische?) Reparaturversuch schlug fehl"
+ ita "La tabella '%-.192s' e` segnalata come corrotta e l'ultima ricostruzione (automatica?) e` fallita"
+ jpn "表 '%-.192s' ã¯å£Šã‚Œã¦ã„ã¾ã™ã€‚修復(自動?)ã«ã‚‚失敗ã—ã¦ã„ã¾ã™ã€‚"
+ por "Tabela '%-.192s' está marcada como danificada e a última reparação (automática?) falhou"
+ rus "Таблица '%-.192s' помечена как иÑÐ¿Ð¾Ñ€Ñ‡ÐµÐ½Ð½Ð°Ñ Ð¸ поÑледний (автоматичеÑкий?) ремонт не был уÑпешным"
+ serbian "Tabela '%-.192s' je markirana kao oštećena, a zadnja (automatska?) popravka je bila neuspela"
+ spa "Tabla '%-.192s' está marcada como crashed y la última reparación (automactica?) falló"
+ swe "Tabell '%-.192s' är trasig och senast (automatiska?) reparation misslyckades"
+ ukr "Таблицю '%-.192s' марковано Ñк зіпÑовану та оÑтаннє (автоматичне?) Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ"
+ER_WARNING_NOT_COMPLETE_ROLLBACK
+ dan "Advarsel: Visse data i tabeller der ikke understøtter transaktioner kunne ikke tilbagestilles"
+ nla "Waarschuwing: Roll back mislukt voor sommige buiten transacties gewijzigde tabellen"
+ eng "Some non-transactional changed tables couldn't be rolled back"
+ est "Hoiatus: mõnesid transaktsioone mittetoetavaid tabeleid ei suudetud tagasi kerida"
+ fre "Attention: certaines tables ne supportant pas les transactions ont été changées et elles ne pourront pas être restituées"
+ ger "Änderungen an einigen nicht transaktionalen Tabellen konnten nicht zurückgerollt werden"
+ ita "Attenzione: Alcune delle modifiche alle tabelle non transazionali non possono essere ripristinate (roll back impossibile)"
+ jpn "トランザクション対応ã§ã¯ãªã„表ã¸ã®å¤‰æ›´ã¯ãƒ­ãƒ¼ãƒ«ãƒãƒƒã‚¯ã•れã¾ã›ã‚“。"
+ por "Aviso: Algumas tabelas não-transacionais alteradas não puderam ser reconstituídas (rolled back)"
+ rus "Внимание: по некоторым измененным нетранзакционным таблицам невозможно будет произвеÑти откат транзакции"
+ serbian "Upozorenje: Neke izmenjene tabele ne podržavaju komandu 'ROLLBACK'"
+ spa "Aviso: Algunas tablas no transancionales no pueden tener rolled back"
+ swe "Warning: Några icke transaktionella tabeller kunde inte återställas vid ROLLBACK"
+ ukr "ЗаÑтереженнÑ: ДеÑкі нетранзакційні зміни таблиць не можна буде повернути"
+ER_TRANS_CACHE_FULL
+ dan "Fler-udtryks transaktion krævede mere plads en 'max_binlog_cache_size' bytes. Forhøj værdien af denne variabel og prøv igen"
+ nla "Multi-statement transactie vereist meer dan 'max_binlog_cache_size' bytes opslag. Verhoog deze mysqld variabele en probeer opnieuw"
+ eng "Multi-statement transaction required more than 'max_binlog_cache_size' bytes of storage; increase this mysqld variable and try again"
+ est "Mitme lausendiga transaktsioon nõudis rohkem ruumi kui lubatud 'max_binlog_cache_size' muutujaga. Suurenda muutuja väärtust ja proovi uuesti"
+ fre "Cette transaction à commandes multiples nécessite plus de 'max_binlog_cache_size' octets de stockage, augmentez cette variable de mysqld et réessayez"
+ ger "Transaktionen, die aus mehreren Befehlen bestehen, benötigten mehr als 'max_binlog_cache_size' Bytes an Speicher. Btte vergrössern Sie diese Server-Variable versuchen Sie es noch einmal"
+ ita "La transazione a comandi multipli (multi-statement) ha richiesto piu` di 'max_binlog_cache_size' bytes di disco: aumentare questa variabile di mysqld e riprovare"
+ jpn "複数ステートメントã‹ã‚‰æˆã‚‹ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³ãŒ 'max_binlog_cache_size' 以上ã®å®¹é‡ã‚’å¿…è¦ã¨ã—ã¾ã—ãŸã€‚ã“ã®ã‚·ã‚¹ãƒ†ãƒ å¤‰æ•°ã‚’増加ã—ã¦ã€å†è©¦è¡Œã—ã¦ãã ã•ã„。"
+ por "Transações multi-declaradas (multi-statement transactions) requeriram mais do que o valor limite (max_binlog_cache_size) de bytes para armazenagem. Aumente o valor desta variável do mysqld e tente novamente"
+ rus "Транзакции, включающей большое количеÑтво команд, потребовалоÑÑŒ более чем 'max_binlog_cache_size' байт. Увеличьте Ñту переменную Ñервера mysqld и попробуйте еще раз"
+ spa "Multipla transición necesita mas que 'max_binlog_cache_size' bytes de almacenamiento. Aumente esta variable mysqld y tente de nuevo"
+ swe "Transaktionen krävde mera än 'max_binlog_cache_size' minne. Öka denna mysqld-variabel och försök på nytt"
+ ukr "Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ñ–Ñ Ð· багатьма виразами вимагає більше ніж 'max_binlog_cache_size' байтів Ð´Ð»Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ. Збільште цю змінну mysqld та Ñпробуйте знову"
+ER_SLAVE_MUST_STOP
+ dan "Denne handling kunne ikke udføres med kørende slave '%2$*1$s', brug først kommandoen STOP SLAVE '%2$*1$s'"
+ nla "Deze operatie kan niet worden uitgevoerd met een actieve slave '%2$*1$s', doe eerst STOP SLAVE '%2$*1$s'"
+ eng "This operation cannot be performed as you have a running slave '%2$*1$s'; run STOP SLAVE '%2$*1$s' first"
+ fre "Cette opération ne peut être réalisée avec un esclave '%2$*1$s' actif, faites STOP SLAVE '%2$*1$s' d'abord"
+ ger "Diese Operation kann bei einem aktiven Slave '%2$*1$s' nicht durchgeführt werden. Bitte zuerst STOP SLAVE '%2$*1$s' ausführen"
+ ita "Questa operazione non puo' essere eseguita con un database 'slave' '%2$*1$s' che gira, lanciare prima STOP SLAVE '%2$*1$s'"
+ por "Esta operação não pode ser realizada com um 'slave' '%2$*1$s' em execução. Execute STOP SLAVE '%2$*1$s' primeiro"
+ rus "Эту операцию невозможно выполнить при работающем потоке подчиненного Ñервера %2$*1$s. Сначала выполните STOP SLAVE '%2$*1$s'"
+ serbian "Ova operacija ne može biti izvršena dok je aktivan podređeni '%2$*1$s' server. Zadajte prvo komandu 'STOP SLAVE '%2$*1$s'' da zaustavite podređeni server."
+ spa "Esta operación no puede ser hecha con el esclavo '%2$*1$s' funcionando, primero use STOP SLAVE '%2$*1$s'"
+ swe "Denna operation kan inte göras under replikering; Du har en aktiv förbindelse till '%2$*1$s'. Gör STOP SLAVE '%2$*1$s' först"
+ ukr "ÐžÐ¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ðµ може бути виконана з запущеним підлеглим '%2$*1$s', Ñпочатку виконайте STOP SLAVE '%2$*1$s'"
+ER_SLAVE_NOT_RUNNING
+ dan "Denne handling kræver en kørende slave. Konfigurer en slave og brug kommandoen START SLAVE"
+ nla "Deze operatie vereist een actieve slave, configureer slave en doe dan START SLAVE"
+ eng "This operation requires a running slave; configure slave and do START SLAVE"
+ fre "Cette opération nécessite un esclave actif, configurez les esclaves et faites START SLAVE"
+ ger "Diese Operation benötigt einen aktiven Slave. Bitte Slave konfigurieren und mittels START SLAVE aktivieren"
+ ita "Questa operaione richiede un database 'slave', configurarlo ed eseguire START SLAVE"
+ jpn "ã“ã®å‡¦ç†ã¯ã€ç¨¼åƒä¸­ã®ã‚¹ãƒ¬ãƒ¼ãƒ–ã§ãªã‘れã°å®Ÿè¡Œã§ãã¾ã›ã‚“。スレーブã®è¨­å®šã‚’ã—ã¦START SLAVEコマンドを実行ã—ã¦ãã ã•ã„。"
+ por "Esta operação requer um 'slave' em execução. Configure o 'slave' e execute START SLAVE"
+ rus "Ð”Ð»Ñ Ñтой операции требуетÑÑ Ñ€Ð°Ð±Ð¾Ñ‚Ð°ÑŽÑ‰Ð¸Ð¹ подчиненный Ñервер. Сначала выполните START SLAVE"
+ serbian "Ova operacija zahteva da je aktivan podređeni server. Konfigurišite prvo podređeni server i onda izvršite komandu 'START SLAVE'"
+ spa "Esta operación necesita el esclavo funcionando, configure esclavo y haga el START SLAVE"
+ swe "Denna operation kan endast göras under replikering; Konfigurera slaven och gör START SLAVE"
+ ukr "ÐžÐ¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð²Ð¸Ð¼Ð°Ð³Ð°Ñ” запущеного підлеглого, зконфігуруйте підлеглого та виконайте START SLAVE"
+ER_BAD_SLAVE
+ dan "Denne server er ikke konfigureret som slave. Ret in config-filen eller brug kommandoen CHANGE MASTER TO"
+ nla "De server is niet geconfigureerd als slave, fix in configuratie bestand of met CHANGE MASTER TO"
+ eng "The server is not configured as slave; fix in config file or with CHANGE MASTER TO"
+ fre "Le server n'est pas configuré comme un esclave, changez le fichier de configuration ou utilisez CHANGE MASTER TO"
+ ger "Der Server ist nicht als Slave konfiguriert. Bitte in der Konfigurationsdatei oder mittels CHANGE MASTER TO beheben"
+ ita "Il server non e' configurato come 'slave', correggere il file di configurazione cambiando CHANGE MASTER TO"
+ jpn "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¯ã‚¹ãƒ¬ãƒ¼ãƒ–ã¨ã—ã¦è¨­å®šã•れã¦ã„ã¾ã›ã‚“。コンフィグファイルã‹CHANGE MASTER TOコマンドã§è¨­å®šã—ã¦ä¸‹ã•ã„。"
+ por "O servidor não está configurado como 'slave'. Acerte o arquivo de configuração ou use CHANGE MASTER TO"
+ rus "Этот Ñервер не наÑтроен как подчиненный. ВнеÑите иÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² конфигурационном файле или Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ CHANGE MASTER TO"
+ serbian "Server nije konfigurisan kao podređeni server, ispravite konfiguracioni file ili na njemu izvršite komandu 'CHANGE MASTER TO'"
+ spa "El servidor no está configurado como esclavo, edite el archivo config file o con CHANGE MASTER TO"
+ swe "Servern är inte konfigurerade som en replikationsslav. Ändra konfigurationsfilen eller gör CHANGE MASTER TO"
+ ukr "Сервер не зконфігуровано Ñк підлеглий, виправте це у файлі конфігурації або з CHANGE MASTER TO"
+ER_MASTER_INFO
+ eng "Could not initialize master info structure for '%.*s'; more error messages can be found in the MariaDB error log"
+ fre "Impossible d'initialiser les structures d'information de maître '%.*s', vous trouverez des messages d'erreur supplémentaires dans le journal des erreurs de MariaDB"
+ ger "Konnte Master-Info-Struktur '%.*s' nicht initialisieren. Weitere Fehlermeldungen können im MariaDB-Error-Log eingesehen werden"
+ jpn "'master info '%.*s''構造体ã®åˆæœŸåŒ–ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚MariaDBエラーログã§ã‚¨ãƒ©ãƒ¼ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’確èªã—ã¦ãã ã•ã„。"
+ serbian "Nisam mogao da inicijalizujem informacionu strukturu glavnog servera, proverite da li imam privilegije potrebne za pristup file-u 'master.info' '%.*s'"
+ swe "Kunde inte initialisera replikationsstrukturerna för '%.*s'. See MariaDB fel fil för mera information"
+ER_SLAVE_THREAD
+ dan "Kunne ikke danne en slave-tråd; check systemressourcerne"
+ nla "Kon slave thread niet aanmaken, controleer systeem resources"
+ eng "Could not create slave thread; check system resources"
+ fre "Impossible de créer une tâche esclave, vérifiez les ressources système"
+ ger "Konnte Slave-Thread nicht starten. Bitte System-Ressourcen überprüfen"
+ ita "Impossibile creare il thread 'slave', controllare le risorse di sistema"
+ jpn "スレーブスレッドを作æˆã§ãã¾ã›ã‚“。システムリソースを確èªã—ã¦ãã ã•ã„。"
+ por "Não conseguiu criar 'thread' de 'slave'. Verifique os recursos do sistema"
+ rus "Ðевозможно Ñоздать поток подчиненного Ñервера. Проверьте ÑиÑтемные реÑурÑÑ‹"
+ serbian "Nisam mogao da startujem thread za podređeni server, proverite sistemske resurse"
+ spa "No puedo crear el thread esclavo, verifique recursos del sistema"
+ swe "Kunde inte starta en tråd för replikering"
+ ukr "Ðе можу Ñтворити підлеглу гілку, перевірте ÑиÑтемні реÑурÑи"
+ER_TOO_MANY_USER_CONNECTIONS 42000
+ dan "Brugeren %-.64s har allerede mere end 'max_user_connections' aktive forbindelser"
+ nla "Gebruiker %-.64s heeft reeds meer dan 'max_user_connections' actieve verbindingen"
+ eng "User %-.64s already has more than 'max_user_connections' active connections"
+ est "Kasutajal %-.64s on juba rohkem ühendusi kui lubatud 'max_user_connections' muutujaga"
+ fre "L'utilisateur %-.64s possède déjà plus de 'max_user_connections' connexions actives"
+ ger "Benutzer '%-.64s' hat mehr als 'max_user_connections' aktive Verbindungen"
+ ita "L'utente %-.64s ha gia' piu' di 'max_user_connections' connessioni attive"
+ jpn "ユーザー '%-.64s' ã¯ã™ã§ã« 'max_user_connections' 以上ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªæŽ¥ç¶šã‚’è¡Œã£ã¦ã„ã¾ã™ã€‚"
+ por "Usuário '%-.64s' já possui mais que o valor máximo de conexões (max_user_connections) ativas"
+ rus "У Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %-.64s уже больше чем 'max_user_connections' активных Ñоединений"
+ serbian "Korisnik %-.64s već ima više aktivnih konekcija nego što je to određeno 'max_user_connections' promenljivom"
+ spa "Usario %-.64s ya tiene mas que 'max_user_connections' conexiones activas"
+ swe "Användare '%-.64s' har redan 'max_user_connections' aktiva inloggningar"
+ ukr "КориÑтувач %-.64s вже має більше ніж 'max_user_connections' активних з'єднань"
+ER_SET_CONSTANTS_ONLY
+ dan "Du må kun bruge konstantudtryk med SET"
+ nla "U mag alleen constante expressies gebruiken bij SET"
+ eng "You may only use constant expressions in this statement"
+ est "Ainult konstantsed suurused on lubatud SET klauslis"
+ fre "Seules les expressions constantes sont autorisées avec SET"
+ ger "Bei diesem Befehl dürfen nur konstante Ausdrücke verwendet werden"
+ ita "Si possono usare solo espressioni costanti con SET"
+ jpn "SET処ç†ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚"
+ por "Você pode usar apenas expressões constantes com SET"
+ rus "С Ñтой командой вы можете иÑпользовать только конÑтантные выражениÑ"
+ serbian "Možete upotrebiti samo konstantan iskaz sa komandom 'SET'"
+ spa "Tu solo debes usar expresiones constantes con SET"
+ swe "Man kan endast använda konstantuttryck med SET"
+ ukr "Можна викориÑтовувати лише вирази зі Ñталими у SET"
+ER_LOCK_WAIT_TIMEOUT
+ dan "Lock wait timeout overskredet"
+ nla "Lock wacht tijd overschreden"
+ eng "Lock wait timeout exceeded; try restarting transaction"
+ est "Kontrollaeg ületatud luku järel ootamisel; Proovi transaktsiooni otsast alata"
+ fre "Timeout sur l'obtention du verrou"
+ ger "Beim Warten auf eine Sperre wurde die zulässige Wartezeit überschritten. Bitte versuchen Sie, die Transaktion neu zu starten"
+ ita "E' scaduto il timeout per l'attesa del lock"
+ jpn "ロック待ã¡ãŒã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ã¾ã—ãŸã€‚トランザクションをå†è©¦è¡Œã—ã¦ãã ã•ã„。"
+ por "Tempo de espera (timeout) de travamento excedido. Tente reiniciar a transação."
+ rus "Таймаут Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸ иÑтек; попробуйте перезапуÑтить транзакцию"
+ serbian "Vremenski limit za zakljuÄavanje tabele je istekao; Probajte da ponovo startujete transakciju"
+ spa "Tiempo de bloqueo de espera excedido"
+ swe "Fick inte ett lås i tid ; Försök att starta om transaktionen"
+ ukr "Затримку Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð°Ð½Ð¾"
+ER_LOCK_TABLE_FULL
+ dan "Det totale antal låse overstiger størrelsen på låse-tabellen"
+ nla "Het totale aantal locks overschrijdt de lock tabel grootte"
+ eng "The total number of locks exceeds the lock table size"
+ est "Lukkude koguarv ületab lukutabeli suuruse"
+ fre "Le nombre total de verrou dépasse la taille de la table des verrous"
+ ger "Die Gesamtzahl der Sperren überschreitet die Größe der Sperrtabelle"
+ ita "Il numero totale di lock e' maggiore della grandezza della tabella di lock"
+ jpn "ãƒ­ãƒƒã‚¯ã®æ•°ãŒå¤šã™ãŽã¾ã™ã€‚"
+ por "O número total de travamentos excede o tamanho da tabela de travamentos"
+ rus "Общее количеÑтво блокировок превыÑило размеры таблицы блокировок"
+ serbian "Broj totalnih zakljuÄavanja tabele premaÅ¡uje veliÄinu tabele zakljuÄavanja"
+ spa "El número total de bloqueos excede el tamaño de bloqueo de la tabla"
+ swe "Antal lås överskrider antalet reserverade lås"
+ ukr "Загальна кількіÑть блокувань перевищила розмір блокувань Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ–"
+ER_READ_ONLY_TRANSACTION 25000
+ dan "Update lås kan ikke opnås under en READ UNCOMMITTED transaktion"
+ nla "Update locks kunnen niet worden verkregen tijdens een READ UNCOMMITTED transactie"
+ eng "Update locks cannot be acquired during a READ UNCOMMITTED transaction"
+ est "Uuenduslukke ei saa kasutada READ UNCOMMITTED transaktsiooni käigus"
+ fre "Un verrou en update ne peut être acquit pendant une transaction READ UNCOMMITTED"
+ ger "Während einer READ-UNCOMMITTED-Transaktion können keine UPDATE-Sperren angefordert werden"
+ ita "I lock di aggiornamento non possono essere acquisiti durante una transazione 'READ UNCOMMITTED'"
+ jpn "読ã¿è¾¼ã¿å°‚用トランザクションã§ã™ã€‚"
+ por "Travamentos de atualização não podem ser obtidos durante uma transação de tipo READ UNCOMMITTED"
+ rus "Блокировки обновлений Ð½ÐµÐ»ÑŒÐ·Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ñ‚ÑŒ в процеÑÑе Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð½Ðµ принÑтой (в режиме READ UNCOMMITTED) транзакции"
+ serbian "ZakljuÄavanja izmena ne mogu biti realizovana sve dok traje 'READ UNCOMMITTED' transakcija"
+ spa "Bloqueos de actualización no pueden ser adqueridos durante una transición READ UNCOMMITTED"
+ swe "Updateringslås kan inte göras när man använder READ UNCOMMITTED"
+ ukr "Оновити Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ðµ можливо на протÑзі транзакції READ UNCOMMITTED"
+ER_DROP_DB_WITH_READ_LOCK
+ dan "DROP DATABASE er ikke tilladt mens en tråd holder på globalt read lock"
+ nla "DROP DATABASE niet toegestaan terwijl thread een globale 'read lock' bezit"
+ eng "DROP DATABASE not allowed while thread is holding global read lock"
+ est "DROP DATABASE ei ole lubatud kui lõim omab globaalset READ lukku"
+ fre "DROP DATABASE n'est pas autorisée pendant qu'une tâche possède un verrou global en lecture"
+ ger "DROP DATABASE ist nicht erlaubt, solange der Thread eine globale Lesesperre hält"
+ ita "DROP DATABASE non e' permesso mentre il thread ha un lock globale di lettura"
+ jpn "グローãƒãƒ«ãƒªãƒ¼ãƒ‰ãƒ­ãƒƒã‚¯ã‚’ä¿æŒã—ã¦ã„ã‚‹é–“ã¯ã€DROP DATABASE を実行ã§ãã¾ã›ã‚“。"
+ por "DROP DATABASE não permitido enquanto uma 'thread' está mantendo um travamento global de leitura"
+ rus "Ðе допуÑкаетÑÑ DROP DATABASE, пока поток держит глобальную блокировку чтениÑ"
+ serbian "Komanda 'DROP DATABASE' nije dozvoljena dok thread globalno zakljuÄava Äitanje podataka"
+ spa "DROP DATABASE no permitido mientras un thread está ejerciendo un bloqueo de lectura global"
+ swe "DROP DATABASE är inte tillåtet när man har ett globalt läslås"
+ ukr "DROP DATABASE не дозволено доки гілка перебуває під загальним блокуваннÑм читаннÑ"
+ER_CREATE_DB_WITH_READ_LOCK
+ dan "CREATE DATABASE er ikke tilladt mens en tråd holder på globalt read lock"
+ nla "CREATE DATABASE niet toegestaan terwijl thread een globale 'read lock' bezit"
+ eng "CREATE DATABASE not allowed while thread is holding global read lock"
+ est "CREATE DATABASE ei ole lubatud kui lõim omab globaalset READ lukku"
+ fre "CREATE DATABASE n'est pas autorisée pendant qu'une tâche possède un verrou global en lecture"
+ ger "CREATE DATABASE ist nicht erlaubt, solange der Thread eine globale Lesesperre hält"
+ ita "CREATE DATABASE non e' permesso mentre il thread ha un lock globale di lettura"
+ jpn "グローãƒãƒ«ãƒªãƒ¼ãƒ‰ãƒ­ãƒƒã‚¯ã‚’ä¿æŒã—ã¦ã„ã‚‹é–“ã¯ã€CREATE DATABASE を実行ã§ãã¾ã›ã‚“。"
+ por "CREATE DATABASE não permitido enquanto uma 'thread' está mantendo um travamento global de leitura"
+ rus "Ðе допуÑкаетÑÑ CREATE DATABASE, пока поток держит глобальную блокировку чтениÑ"
+ serbian "Komanda 'CREATE DATABASE' nije dozvoljena dok thread globalno zakljuÄava Äitanje podataka"
+ spa "CREATE DATABASE no permitido mientras un thread está ejerciendo un bloqueo de lectura global"
+ swe "CREATE DATABASE är inte tillåtet när man har ett globalt läslås"
+ ukr "CREATE DATABASE не дозволено доки гілка перебуває під загальним блокуваннÑм читаннÑ"
+ER_WRONG_ARGUMENTS
+ nla "Foutieve parameters voor %s"
+ eng "Incorrect arguments to %s"
+ est "Vigased parameetrid %s-le"
+ fre "Mauvais arguments à %s"
+ ger "Falsche Argumente für %s"
+ ita "Argomenti errati a %s"
+ jpn "%s ã®å¼•æ•°ãŒä¸æ­£ã§ã™"
+ por "Argumentos errados para %s"
+ rus "Ðеверные параметры Ð´Ð»Ñ %s"
+ serbian "Pogrešni argumenti prosleđeni na %s"
+ spa "Argumentos errados para %s"
+ swe "Felaktiga argument till %s"
+ ukr "Хибний аргумент Ð´Ð»Ñ %s"
+ER_NO_PERMISSION_TO_CREATE_USER 42000
+ nla "'%s'@'%s' mag geen nieuwe gebruikers creeren"
+ eng "'%s'@'%s' is not allowed to create new users"
+ est "Kasutajal '%s'@'%s' ei ole lubatud luua uusi kasutajaid"
+ fre "'%s'@'%s' n'est pas autorisé à créer de nouveaux utilisateurs"
+ ger "'%s'@'%s' ist nicht berechtigt, neue Benutzer hinzuzufügen"
+ ita "A '%s'@'%s' non e' permesso creare nuovi utenti"
+ por "Não é permitido a '%s'@'%s' criar novos usuários"
+ rus "'%s'@'%s' не разрешаетÑÑ Ñоздавать новых пользователей"
+ serbian "Korisniku '%s'@'%s' nije dozvoljeno da kreira nove korisnike"
+ spa "'%s'@'%s' no es permitido para crear nuevos usuarios"
+ swe "'%s'@'%s' har inte rättighet att skapa nya användare"
+ ukr "КориÑтувачу '%s'@'%s' не дозволено Ñтворювати нових кориÑтувачів"
+ER_UNION_TABLES_IN_DIFFERENT_DIR
+ nla "Incorrecte tabel definitie; alle MERGE tabellen moeten tot dezelfde database behoren"
+ eng "Incorrect table definition; all MERGE tables must be in the same database"
+ est "Vigane tabelimääratlus; kõik MERGE tabeli liikmed peavad asuma samas andmebaasis"
+ fre "Définition de table incorrecte; toutes les tables MERGE doivent être dans la même base de donnée"
+ ger "Falsche Tabellendefinition. Alle MERGE-Tabellen müssen sich in derselben Datenbank befinden"
+ ita "Definizione della tabella errata; tutte le tabelle di tipo MERGE devono essere nello stesso database"
+ jpn "䏿­£ãªè¡¨å®šç¾©ã§ã™ã€‚MERGEè¡¨ã®æ§‹æˆè¡¨ã¯ã™ã¹ã¦åŒã˜ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹å†…ã«ãªã‘れã°ãªã‚Šã¾ã›ã‚“。"
+ por "Definição incorreta da tabela. Todas as tabelas contidas na junção devem estar no mesmo banco de dados."
+ rus "Ðеверное определение таблицы; Ð’Ñе таблицы в MERGE должны принадлежать одной и той же базе данных"
+ serbian "Pogrešna definicija tabele; sve 'MERGE' tabele moraju biti u istoj bazi podataka"
+ spa "Incorrecta definición de la tabla; Todas las tablas MERGE deben estar en el mismo banco de datos"
+ swe "Felaktig tabelldefinition; alla tabeller i en MERGE-tabell måste vara i samma databas"
+ER_LOCK_DEADLOCK 40001
+ nla "Deadlock gevonden tijdens lock-aanvraag poging; Probeer herstart van de transactie"
+ eng "Deadlock found when trying to get lock; try restarting transaction"
+ est "Lukustamisel tekkis tupik (deadlock); alusta transaktsiooni otsast"
+ fre "Deadlock découvert en essayant d'obtenir les verrous : essayez de redémarrer la transaction"
+ ger "Beim Versuch, eine Sperre anzufordern, ist ein Deadlock aufgetreten. Versuchen Sie, die Transaktion neu zu starten"
+ ita "Trovato deadlock durante il lock; Provare a far ripartire la transazione"
+ jpn "ロックå–得中ã«ãƒ‡ãƒƒãƒ‰ãƒ­ãƒƒã‚¯ãŒæ¤œå‡ºã•れã¾ã—ãŸã€‚トランザクションをå†è©¦è¡Œã—ã¦ãã ã•ã„。"
+ por "Encontrado um travamento fatal (deadlock) quando tentava obter uma trava. Tente reiniciar a transação."
+ rus "Возникла Ñ‚ÑƒÐ¿Ð¸ÐºÐ¾Ð²Ð°Ñ ÑÐ¸Ñ‚ÑƒÐ°Ñ†Ð¸Ñ Ð² процеÑÑе Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸; Попробуйте перезапуÑтить транзакцию"
+ serbian "Unakrsno zakljuÄavanje pronaÄ‘eno kada sam pokuÅ¡ao da dobijem pravo na zakljuÄavanje; Probajte da restartujete transakciju"
+ spa "Encontrado deadlock cuando tentando obtener el bloqueo; Tente recomenzar la transición"
+ swe "Fick 'DEADLOCK' vid låsförsök av block/rad. Försök att starta om transaktionen"
+ER_TABLE_CANT_HANDLE_FT
+ nla "Het gebruikte tabel type (%s) ondersteund geen FULLTEXT indexen"
+ eng "The storage engine %s doesn't support FULLTEXT indexes"
+ est "Antud tabelitüüp (%s) ei toeta FULLTEXT indekseid"
+ fre "Le type de table utilisé (%s) ne supporte pas les index FULLTEXT"
+ ger "Der verwendete Tabellentyp (%s) unterstützt keine FULLTEXT-Indizes"
+ ita "La tabella usata (%s) non supporta gli indici FULLTEXT"
+ por "O tipo de tabela utilizado (%s) não suporta índices de texto completo (fulltext indexes)"
+ rus "ИÑпользуемый тип таблиц (%s) не поддерживает полнотекÑтовых индекÑов"
+ serbian "Upotrebljeni tip tabele (%s) ne podržava 'FULLTEXT' indekse"
+ spa "El tipo de tabla usada (%s) no soporta índices FULLTEXT"
+ swe "Tabelltypen (%s) har inte hantering av FULLTEXT-index"
+ ukr "ВикориÑтаний тип таблиці (%s) не підтримує FULLTEXT індекÑів"
+ER_CANNOT_ADD_FOREIGN
+ nla "Kan foreign key beperking niet toevoegen"
+ eng "Cannot add foreign key constraint"
+ fre "Impossible d'ajouter des contraintes d'index externe"
+ ger "Fremdschlüssel-Beschränkung kann nicht hinzugefügt werden"
+ ita "Impossibile aggiungere il vincolo di integrita' referenziale (foreign key constraint)"
+ jpn "外部キー制約を追加ã§ãã¾ã›ã‚“。"
+ por "Não pode acrescentar uma restrição de chave estrangeira"
+ rus "Ðевозможно добавить Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð²Ð½ÐµÑˆÐ½ÐµÐ³Ð¾ ключа"
+ serbian "Ne mogu da dodam proveru spoljnog kljuÄa"
+ spa "No puede adicionar clave extranjera constraint"
+ swe "Kan inte lägga till 'FOREIGN KEY constraint'"
+ER_NO_REFERENCED_ROW 23000
+ nla "Kan onderliggende rij niet toevoegen: foreign key beperking gefaald"
+ eng "Cannot add or update a child row: a foreign key constraint fails"
+ fre "Impossible d'ajouter un enregistrement fils : une constrainte externe l'empèche"
+ ger "Hinzufügen oder Aktualisieren eines Kind-Datensatzes schlug aufgrund einer Fremdschlüssel-Beschränkung fehl"
+ greek "Cannot add a child row: a foreign key constraint fails"
+ hun "Cannot add a child row: a foreign key constraint fails"
+ ita "Impossibile aggiungere la riga: un vincolo d'integrita' referenziale non e' soddisfatto"
+ jpn "親キーãŒã‚りã¾ã›ã‚“。外部キー制約é•åã§ã™ã€‚"
+ norwegian-ny "Cannot add a child row: a foreign key constraint fails"
+ por "Não pode acrescentar uma linha filha: uma restrição de chave estrangeira falhou"
+ rus "Ðевозможно добавить или обновить дочернюю Ñтроку: проверка ограничений внешнего ключа не выполнÑетÑÑ"
+ spa "No puede adicionar una línea hijo: falla de clave extranjera constraint"
+ swe "FOREIGN KEY-konflikt: Kan inte skriva barn"
+ER_ROW_IS_REFERENCED 23000
+ eng "Cannot delete or update a parent row: a foreign key constraint fails"
+ fre "Impossible de supprimer un enregistrement père : une constrainte externe l'empèche"
+ ger "Löschen oder Aktualisieren eines Eltern-Datensatzes schlug aufgrund einer Fremdschlüssel-Beschränkung fehl"
+ greek "Cannot delete a parent row: a foreign key constraint fails"
+ hun "Cannot delete a parent row: a foreign key constraint fails"
+ ita "Impossibile cancellare la riga: un vincolo d'integrita' referenziale non e' soddisfatto"
+ jpn "å­ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒã‚りã¾ã™ã€‚外部キー制約é•åã§ã™ã€‚"
+ por "Não pode apagar uma linha pai: uma restrição de chave estrangeira falhou"
+ rus "Ðевозможно удалить или обновить родительÑкую Ñтроку: проверка ограничений внешнего ключа не выполнÑетÑÑ"
+ serbian "Ne mogu da izbriÅ¡em roditeljski slog: provera spoljnog kljuÄa je neuspela"
+ spa "No puede deletar una línea padre: falla de clave extranjera constraint"
+ swe "FOREIGN KEY-konflikt: Kan inte radera fader"
+ER_CONNECT_TO_MASTER 08S01
+ nla "Fout bij opbouwen verbinding naar master: %-.128s"
+ eng "Error connecting to master: %-.128s"
+ ger "Fehler bei der Verbindung zum Master: %-.128s"
+ ita "Errore durante la connessione al master: %-.128s"
+ jpn "マスターã¸ã®æŽ¥ç¶šã‚¨ãƒ©ãƒ¼: %-.128s"
+ por "Erro conectando com o master: %-.128s"
+ rus "Ошибка ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð³Ð¾Ð»Ð¾Ð²Ð½Ñ‹Ð¼ Ñервером: %-.128s"
+ spa "Error de coneccion a master: %-.128s"
+ swe "Fick fel vid anslutning till master: %-.128s"
+ER_QUERY_ON_MASTER
+ nla "Fout bij uitvoeren query op master: %-.128s"
+ eng "Error running query on master: %-.128s"
+ ger "Beim Ausführen einer Abfrage auf dem Master trat ein Fehler auf: %-.128s"
+ ita "Errore eseguendo una query sul master: %-.128s"
+ jpn "マスターã§ã®ã‚¯ã‚¨ãƒªå®Ÿè¡Œã‚¨ãƒ©ãƒ¼: %-.128s"
+ por "Erro rodando consulta no master: %-.128s"
+ rus "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на головном Ñервере: %-.128s"
+ spa "Error executando el query en master: %-.128s"
+ swe "Fick fel vid utförande av command på mastern: %-.128s"
+ER_ERROR_WHEN_EXECUTING_COMMAND
+ nla "Fout tijdens uitvoeren van commando %s: %-.128s"
+ eng "Error when executing command %s: %-.128s"
+ est "Viga käsu %s täitmisel: %-.128s"
+ ger "Fehler beim Ausführen des Befehls %s: %-.128s"
+ ita "Errore durante l'esecuzione del comando %s: %-.128s"
+ jpn "%s コマンドã®å®Ÿè¡Œã‚¨ãƒ©ãƒ¼: %-.128s"
+ por "Erro quando executando comando %s: %-.128s"
+ rus "Ошибка при выполнении команды %s: %-.128s"
+ serbian "Greška pri izvršavanju komande %s: %-.128s"
+ spa "Error de %s: %-.128s"
+ swe "Fick fel vid utförande av %s: %-.128s"
+ER_WRONG_USAGE
+ nla "Foutief gebruik van %s en %s"
+ eng "Incorrect usage of %s and %s"
+ est "Vigane %s ja %s kasutus"
+ ger "Falsche Verwendung von %s und %s"
+ ita "Uso errato di %s e %s"
+ jpn "%s ã® %s ã«é–¢ã™ã‚‹ä¸æ­£ãªä½¿ç”¨æ³•ã§ã™ã€‚"
+ por "Uso errado de %s e %s"
+ rus "Ðеверное иÑпользование %s и %s"
+ serbian "Pogrešna upotreba %s i %s"
+ spa "Equivocado uso de %s y %s"
+ swe "Felaktig använding av %s and %s"
+ ukr "Wrong usage of %s and %s"
+ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT 21000
+ nla "De gebruikte SELECT commando's hebben een verschillend aantal kolommen"
+ eng "The used SELECT statements have a different number of columns"
+ est "Tulpade arv kasutatud SELECT lausetes ei kattu"
+ ger "Die verwendeten SELECT-Befehle liefern unterschiedliche Anzahlen von Feldern zurück"
+ ita "La SELECT utilizzata ha un numero di colonne differente"
+ jpn "使用ã®SELECTæ–‡ãŒè¿”ã™åˆ—æ•°ãŒé•ã„ã¾ã™ã€‚"
+ por "Os comandos SELECT usados têm diferente número de colunas"
+ rus "ИÑпользованные операторы выборки (SELECT) дают разное количеÑтво Ñтолбцов"
+ serbian "Upotrebljene 'SELECT' komande adresiraju razliÄit broj kolona"
+ spa "El comando SELECT usado tiene diferente número de columnas"
+ swe "SELECT-kommandona har olika antal kolumner"
+ER_CANT_UPDATE_WITH_READLOCK
+ nla "Kan de query niet uitvoeren vanwege een conflicterende read lock"
+ eng "Can't execute the query because you have a conflicting read lock"
+ est "Ei suuda täita päringut konfliktse luku tõttu"
+ ger "Augrund eines READ-LOCK-Konflikts kann die Abfrage nicht ausgeführt werden"
+ ita "Impossibile eseguire la query perche' c'e' un conflitto con in lock di lettura"
+ jpn "ç«¶åˆã™ã‚‹ãƒªãƒ¼ãƒ‰ãƒ­ãƒƒã‚¯ã‚’ä¿æŒã—ã¦ã„ã‚‹ã®ã§ã€ã‚¯ã‚¨ãƒªã‚’実行ã§ãã¾ã›ã‚“。"
+ por "Não posso executar a consulta porque você tem um conflito de travamento de leitura"
+ rus "Ðевозможно иÑполнить запроÑ, поÑкольку у Ð²Ð°Ñ ÑƒÑтановлены конфликтующие блокировки чтениÑ"
+ serbian "Ne mogu da izvrÅ¡im upit zbog toga Å¡to imate zakljuÄavanja Äitanja podataka u konfliktu"
+ spa "No puedo ejecutar el query porque usted tiene conflicto de traba de lectura"
+ swe "Kan inte utföra kommandot emedan du har ett READ-lås"
+ER_MIXING_NOT_ALLOWED
+ nla "Het combineren van transactionele en niet-transactionele tabellen is uitgeschakeld."
+ eng "Mixing of transactional and non-transactional tables is disabled"
+ est "Transaktsioone toetavate ning mittetoetavate tabelite kooskasutamine ei ole lubatud"
+ ger "Die gleichzeitige Verwendung von Tabellen mit und ohne Transaktionsunterstützung ist deaktiviert"
+ ita "E' disabilitata la possibilita' di mischiare tabelle transazionali e non-transazionali"
+ jpn "トランザクション対応ã®è¡¨ã¨éžå¯¾å¿œã®è¡¨ã®åŒæ™‚使用ã¯ç„¡åŠ¹åŒ–ã•れã¦ã„ã¾ã™ã€‚"
+ por "Mistura de tabelas transacional e não-transacional está desabilitada"
+ rus "ИÑпользование транзакционных таблиц нарÑду Ñ Ð½ÐµÑ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ð¾Ð½Ð½Ñ‹Ð¼Ð¸ запрещено"
+ serbian "MeÅ¡anje tabela koje podržavaju transakcije i onih koje ne podržavaju transakcije je iskljuÄeno"
+ spa "Mezla de transancional y no-transancional tablas está deshabilitada"
+ swe "Blandning av transaktionella och icke-transaktionella tabeller är inaktiverat"
+ER_DUP_ARGUMENT
+ nla "Optie '%s' tweemaal gebruikt in opdracht"
+ eng "Option '%s' used twice in statement"
+ est "Määrangut '%s' on lauses kasutatud topelt"
+ ger "Option '%s' wird im Befehl zweimal verwendet"
+ ita "L'opzione '%s' e' stata usata due volte nel comando"
+ jpn "オプション '%s' ãŒ2度使用ã•れã¦ã„ã¾ã™ã€‚"
+ por "Opção '%s' usada duas vezes no comando"
+ rus "ÐžÐ¿Ñ†Ð¸Ñ '%s' дважды иÑпользована в выражении"
+ spa "Opción '%s' usada dos veces en el comando"
+ swe "Option '%s' användes två gånger"
+ER_USER_LIMIT_REACHED 42000
+ nla "Gebruiker '%-.64s' heeft het maximale gebruik van de '%s' faciliteit overschreden (huidige waarde: %ld)"
+ eng "User '%-.64s' has exceeded the '%s' resource (current value: %ld)"
+ ger "Benutzer '%-.64s' hat die Ressourcenbeschränkung '%s' überschritten (aktueller Wert: %ld)"
+ ita "L'utente '%-.64s' ha ecceduto la risorsa '%s' (valore corrente: %ld)"
+ jpn "ユーザー '%-.64s' ã¯ãƒªã‚½ãƒ¼ã‚¹ã®ä¸Šé™ '%s' ã«é”ã—ã¾ã—ãŸã€‚(ç¾åœ¨å€¤: %ld)"
+ por "Usuário '%-.64s' tem excedido o '%s' recurso (atual valor: %ld)"
+ rus "Пользователь '%-.64s' превыÑил иÑпользование реÑурÑа '%s' (текущее значение: %ld)"
+ spa "Usuario '%-.64s' ha excedido el recurso '%s' (actual valor: %ld)"
+ swe "Användare '%-.64s' har överskridit '%s' (nuvarande värde: %ld)"
+ER_SPECIFIC_ACCESS_DENIED_ERROR 42000
+ nla "Toegang geweigerd. U moet het %-.128s privilege hebben voor deze operatie"
+ eng "Access denied; you need (at least one of) the %-.128s privilege(s) for this operation"
+ ger "Kein Zugriff. Hierfür wird die Berechtigung %-.128s benötigt"
+ ita "Accesso non consentito. Serve il privilegio %-.128s per questa operazione"
+ jpn "ã‚¢ã‚¯ã‚»ã‚¹ã¯æ‹’å¦ã•れã¾ã—ãŸã€‚ã“ã®æ“作ã«ã¯ %-.128s 権é™ãŒ(複数ã®å ´åˆã¯ã©ã‚Œã‹1ã¤)å¿…è¦ã§ã™ã€‚"
+ por "Acesso negado. Você precisa o privilégio %-.128s para essa operação"
+ rus "Ð’ доÑтупе отказано. Вам нужны привилегии %-.128s Ð´Ð»Ñ Ñтой операции"
+ spa "Acceso negado. Usted necesita el privilegio %-.128s para esta operación"
+ swe "Du har inte privlegiet '%-.128s' som behövs för denna operation"
+ ukr "Access denied. You need the %-.128s privilege for this operation"
+ER_LOCAL_VARIABLE
+ nla "Variabele '%-.64s' is SESSION en kan niet worden gebruikt met SET GLOBAL"
+ eng "Variable '%-.64s' is a SESSION variable and can't be used with SET GLOBAL"
+ ger "Variable '%-.64s' ist eine lokale Variable und kann nicht mit SET GLOBAL verändert werden"
+ ita "La variabile '%-.64s' e' una variabile locale ( SESSION ) e non puo' essere cambiata usando SET GLOBAL"
+ jpn "変数 '%-.64s' ã¯ã‚»ãƒƒã‚·ãƒ§ãƒ³å¤‰æ•°ã§ã™ã€‚SET GLOBALã§ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。"
+ por "Variável '%-.64s' é uma SESSION variável e não pode ser usada com SET GLOBAL"
+ rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' ÑвлÑетÑÑ Ð¿Ð¾Ñ‚Ð¾ÐºÐ¾Ð²Ð¾Ð¹ (SESSION) переменной и не может быть изменена Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ SET GLOBAL"
+ spa "Variable '%-.64s' es una SESSION variable y no puede ser usada con SET GLOBAL"
+ swe "Variabel '%-.64s' är en SESSION variabel och kan inte ändrad med SET GLOBAL"
+ER_GLOBAL_VARIABLE
+ nla "Variabele '%-.64s' is GLOBAL en dient te worden gewijzigd met SET GLOBAL"
+ eng "Variable '%-.64s' is a GLOBAL variable and should be set with SET GLOBAL"
+ ger "Variable '%-.64s' ist eine globale Variable und muss mit SET GLOBAL verändert werden"
+ ita "La variabile '%-.64s' e' una variabile globale ( GLOBAL ) e deve essere cambiata usando SET GLOBAL"
+ jpn "変数 '%-.64s' ã¯ã‚°ãƒ­ãƒ¼ãƒãƒ«å¤‰æ•°ã§ã™ã€‚SET GLOBALを使用ã—ã¦ãã ã•ã„。"
+ por "Variável '%-.64s' é uma GLOBAL variável e deve ser configurada com SET GLOBAL"
+ rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' ÑвлÑетÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð¾Ð¹ (GLOBAL) переменной, и ее Ñледует изменÑть Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ SET GLOBAL"
+ spa "Variable '%-.64s' es una GLOBAL variable y no puede ser configurada con SET GLOBAL"
+ swe "Variabel '%-.64s' är en GLOBAL variabel och bör sättas med SET GLOBAL"
+ER_NO_DEFAULT 42000
+ nla "Variabele '%-.64s' heeft geen standaard waarde"
+ eng "Variable '%-.64s' doesn't have a default value"
+ ger "Variable '%-.64s' hat keinen Vorgabewert"
+ ita "La variabile '%-.64s' non ha un valore di default"
+ jpn "変数 '%-.64s' ã«ã¯ãƒ‡ãƒ•ォルト値ãŒã‚りã¾ã›ã‚“。"
+ por "Variável '%-.64s' não tem um valor padrão"
+ rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' не имеет Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾ умолчанию"
+ spa "Variable '%-.64s' no tiene un valor patrón"
+ swe "Variabel '%-.64s' har inte ett DEFAULT-värde"
+ER_WRONG_VALUE_FOR_VAR 42000
+ nla "Variabele '%-.64s' kan niet worden gewijzigd naar de waarde '%-.200s'"
+ eng "Variable '%-.64s' can't be set to the value of '%-.200s'"
+ ger "Variable '%-.64s' kann nicht auf '%-.200s' gesetzt werden"
+ ita "Alla variabile '%-.64s' non puo' essere assegato il valore '%-.200s'"
+ jpn "変数 '%-.64s' ã«å€¤ '%-.200s' を設定ã§ãã¾ã›ã‚“。"
+ por "Variável '%-.64s' não pode ser configurada para o valor de '%-.200s'"
+ rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' не может быть уÑтановлена в значение '%-.200s'"
+ spa "Variable '%-.64s' no puede ser configurada para el valor de '%-.200s'"
+ swe "Variabel '%-.64s' kan inte sättas till '%-.200s'"
+ER_WRONG_TYPE_FOR_VAR 42000
+ nla "Foutief argumenttype voor variabele '%-.64s'"
+ eng "Incorrect argument type to variable '%-.64s'"
+ ger "Falscher Argumenttyp für Variable '%-.64s'"
+ ita "Tipo di valore errato per la variabile '%-.64s'"
+ jpn "変数 '%-.64s' ã¸ã®å€¤ã®åž‹ãŒä¸æ­£ã§ã™ã€‚"
+ por "Tipo errado de argumento para variável '%-.64s'"
+ rus "Ðеверный тип аргумента Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð¹ '%-.64s'"
+ spa "Tipo de argumento equivocado para variable '%-.64s'"
+ swe "Fel typ av argument till variabel '%-.64s'"
+ER_VAR_CANT_BE_READ
+ nla "Variabele '%-.64s' kan alleen worden gewijzigd, niet gelezen"
+ eng "Variable '%-.64s' can only be set, not read"
+ ger "Variable '%-.64s' kann nur verändert, nicht gelesen werden"
+ ita "Alla variabile '%-.64s' e' di sola scrittura quindi puo' essere solo assegnato un valore, non letto"
+ jpn "変数 '%-.64s' ã¯æ›¸ãè¾¼ã¿å°‚用ã§ã™ã€‚読ã¿è¾¼ã¿ã¯ã§ãã¾ã›ã‚“。"
+ por "Variável '%-.64s' somente pode ser configurada, não lida"
+ rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' может быть только уÑтановлена, но не Ñчитана"
+ spa "Variable '%-.64s' solamente puede ser configurada, no leída"
+ swe "Variabeln '%-.64s' kan endast sättas, inte läsas"
+ER_CANT_USE_OPTION_HERE 42000
+ nla "Foutieve toepassing/plaatsing van '%s'"
+ eng "Incorrect usage/placement of '%s'"
+ ger "Falsche Verwendung oder Platzierung von '%s'"
+ ita "Uso/posizione di '%s' sbagliato"
+ jpn "'%s' ã®ä½¿ç”¨æ³•ã¾ãŸã¯å ´æ‰€ãŒä¸æ­£ã§ã™ã€‚"
+ por "Errado uso/colocação de '%s'"
+ rus "Ðеверное иÑпользование или в неверном меÑте указан '%s'"
+ spa "Equivocado uso/colocación de '%s'"
+ swe "Fel använding/placering av '%s'"
+ER_NOT_SUPPORTED_YET 42000
+ nla "Deze versie van MariaDB ondersteunt nog geen '%s'"
+ eng "This version of MariaDB doesn't yet support '%s'"
+ ger "Diese MariaDB-Version unterstützt '%s' nicht"
+ ita "Questa versione di MariaDB non supporta ancora '%s'"
+ jpn "ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®MariaDBã§ã¯ã€ã¾ã  '%s' を利用ã§ãã¾ã›ã‚“。"
+ por "Esta versão de MariaDB não suporta ainda '%s'"
+ rus "Эта верÑÐ¸Ñ MariaDB пока еще не поддерживает '%s'"
+ spa "Esta versión de MariaDB no soporta todavia '%s'"
+ swe "Denna version av MariaDB kan ännu inte utföra '%s'"
+ER_MASTER_FATAL_ERROR_READING_BINLOG
+ nla "Kreeg fatale fout %d: '%-.320s' van master tijdens lezen van data uit binaire log"
+ eng "Got fatal error %d from master when reading data from binary log: '%-.320s'"
+ ger "Schwerer Fehler %d: '%-.320s vom Master beim Lesen des binären Logs"
+ ita "Errore fatale %d: '%-.320s' dal master leggendo i dati dal log binario"
+ jpn "致命的ãªã‚¨ãƒ©ãƒ¼ %d: '%-.320s' ãŒãƒžã‚¹ã‚¿ãƒ¼ã§ãƒã‚¤ãƒŠãƒªãƒ­ã‚°èª­ã¿è¾¼ã¿ä¸­ã«ç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+ por "Obteve fatal erro %d: '%-.320s' do master quando lendo dados do binary log"
+ rus "Получена неиÑÐ¿Ñ€Ð°Ð²Ð¸Ð¼Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° %d: '%-.320s' от головного Ñервера в процеÑÑе выборки данных из двоичного журнала"
+ spa "Recibió fatal error %d: '%-.320s' del master cuando leyendo datos del binary log"
+ swe "Fick fatalt fel %d: '%-.320s' från master vid läsning av binärloggen"
+ER_SLAVE_IGNORED_TABLE
+ eng "Slave SQL thread ignored the query because of replicate-*-table rules"
+ ger "Slave-SQL-Thread hat die Abfrage aufgrund von replicate-*-table-Regeln ignoriert"
+ jpn "replicate-*-table ルールã«å¾“ã£ã¦ã€ã‚¹ãƒ¬ãƒ¼ãƒ–SQLスレッドã¯ã‚¯ã‚¨ãƒªã‚’無視ã—ã¾ã—ãŸã€‚"
+ nla "Slave SQL thread negeerde de query vanwege replicate-*-table opties"
+ por "Slave SQL thread ignorado a consulta devido às normas de replicação-*-tabela"
+ spa "Slave SQL thread ignorado el query debido a las reglas de replicación-*-tabla"
+ swe "Slav SQL tråden ignorerade frågan pga en replicate-*-table regel"
+ER_INCORRECT_GLOBAL_LOCAL_VAR
+ eng "Variable '%-.192s' is a %s variable"
+ serbian "Promenljiva '%-.192s' je %s promenljiva"
+ ger "Variable '%-.192s' ist eine %s-Variable"
+ jpn "変数 '%-.192s' 㯠%s 変数ã§ã™ã€‚"
+ nla "Variabele '%-.192s' is geen %s variabele"
+ spa "Variable '%-.192s' es una %s variable"
+ swe "Variabel '%-.192s' är av typ %s"
+ER_WRONG_FK_DEF 42000
+ eng "Incorrect foreign key definition for '%-.192s': %s"
+ ger "Falsche Fremdschlüssel-Definition für '%-.192s': %s"
+ jpn "外部キー '%-.192s' ã®å®šç¾©ã®ä¸æ­£: %s"
+ nla "Incorrecte foreign key definitie voor '%-.192s': %s"
+ por "Definição errada da chave estrangeira para '%-.192s': %s"
+ spa "Equivocada definición de llave extranjera para '%-.192s': %s"
+ swe "Felaktig FOREIGN KEY-definition för '%-.192s': %s"
+ER_KEY_REF_DO_NOT_MATCH_TABLE_REF
+ eng "Key reference and table reference don't match"
+ ger "Schlüssel- und Tabellenverweis passen nicht zusammen"
+ jpn "外部キーã®å‚照表ã¨å®šç¾©ãŒä¸€è‡´ã—ã¾ã›ã‚“。"
+ nla "Sleutel- en tabelreferentie komen niet overeen"
+ por "Referência da chave e referência da tabela não coincidem"
+ spa "Referencia de llave y referencia de tabla no coinciden"
+ swe "Nyckelreferensen och tabellreferensen stämmer inte överens"
+ER_OPERAND_COLUMNS 21000
+ eng "Operand should contain %d column(s)"
+ ger "Operand sollte %d Spalte(n) enthalten"
+ jpn "オペランド㫠%d 個ã®åˆ—ãŒå¿…è¦ã§ã™ã€‚"
+ nla "Operand behoort %d kolommen te bevatten"
+ rus "Операнд должен Ñодержать %d колонок"
+ spa "Operando debe tener %d columna(s)"
+ ukr "Операнд має ÑкладатиÑÑ Ð· %d Ñтовбців"
+ER_SUBQUERY_NO_1_ROW 21000
+ eng "Subquery returns more than 1 row"
+ ger "Unterabfrage lieferte mehr als einen Datensatz zurück"
+ jpn "サブクエリãŒ2行以上ã®çµæžœã‚’è¿”ã—ã¾ã™ã€‚"
+ nla "Subquery retourneert meer dan 1 rij"
+ por "Subconsulta retorna mais que 1 registro"
+ rus "ÐŸÐ¾Ð´Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‰Ð°ÐµÑ‚ более одной запиÑи"
+ spa "Subconsulta retorna mas que 1 línea"
+ swe "Subquery returnerade mer än 1 rad"
+ ukr "Підзапит повертає більш нiж 1 запиÑ"
+ER_UNKNOWN_STMT_HANDLER
+ dan "Unknown prepared statement handler (%.*s) given to %s"
+ eng "Unknown prepared statement handler (%.*s) given to %s"
+ ger "Unbekannter Prepared-Statement-Handler (%.*s) für %s angegeben"
+ jpn "'%.*s' ã¯ãƒ—リペアードステートメントã®ä¸æ˜Žãªãƒãƒ³ãƒ‰ãƒ«ã§ã™ã€‚(%s ã§æŒ‡å®šã•れã¾ã—ãŸ)"
+ nla "Onebekende prepared statement handler (%.*s) voor %s aangegeven"
+ por "Desconhecido manipulador de declaração preparado (%.*s) determinado para %s"
+ spa "Desconocido preparado comando handler (%.*s) dado para %s"
+ swe "Okänd PREPARED STATEMENT id (%.*s) var given till %s"
+ ukr "Unknown prepared statement handler (%.*s) given to %s"
+ER_CORRUPT_HELP_DB
+ eng "Help database is corrupt or does not exist"
+ ger "Die Hilfe-Datenbank ist beschädigt oder existiert nicht"
+ jpn "ヘルプデータベースã¯å£Šã‚Œã¦ã„ã‚‹ã‹å­˜åœ¨ã—ã¾ã›ã‚“。"
+ nla "Help database is beschadigd of bestaat niet"
+ por "Banco de dado de ajuda corrupto ou não existente"
+ spa "Base de datos Help está corrupto o no existe"
+ swe "Hjälpdatabasen finns inte eller är skadad"
+ER_CYCLIC_REFERENCE
+ eng "Cyclic reference on subqueries"
+ ger "Zyklischer Verweis in Unterabfragen"
+ jpn "サブクエリã®å‚ç…§ãŒãƒ«ãƒ¼ãƒ—ã—ã¦ã„ã¾ã™ã€‚"
+ nla "Cyclische verwijzing in subqueries"
+ por "Referência cíclica em subconsultas"
+ rus "ЦикличеÑÐºÐ°Ñ ÑÑылка на подзапроÑ"
+ spa "Cíclica referencia en subconsultas"
+ swe "Cyklisk referens i subqueries"
+ ukr "Циклічне поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° підзапит"
+ER_AUTO_CONVERT
+ eng "Converting column '%s' from %s to %s"
+ ger "Feld '%s' wird von %s nach %s umgewandelt"
+ jpn "列 '%s' ã‚’ %s ã‹ã‚‰ %s ã¸å¤‰æ›ã—ã¾ã™ã€‚"
+ nla "Veld '%s' wordt van %s naar %s geconverteerd"
+ por "Convertendo coluna '%s' de %s para %s"
+ rus "Преобразование Ð¿Ð¾Ð»Ñ '%s' из %s в %s"
+ spa "Convirtiendo columna '%s' de %s para %s"
+ swe "Konvertar kolumn '%s' från %s till %s"
+ ukr "ÐŸÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñтовбца '%s' з %s у %s"
+ER_ILLEGAL_REFERENCE 42S22
+ eng "Reference '%-.64s' not supported (%s)"
+ ger "Verweis '%-.64s' wird nicht unterstützt (%s)"
+ jpn "'%-.64s' ã®å‚ç…§ã¯ã§ãã¾ã›ã‚“。(%s)"
+ nla "Verwijzing '%-.64s' niet ondersteund (%s)"
+ por "Referência '%-.64s' não suportada (%s)"
+ rus "СÑылка '%-.64s' не поддерживаетÑÑ (%s)"
+ spa "Referencia '%-.64s' no soportada (%s)"
+ swe "Referens '%-.64s' stöds inte (%s)"
+ ukr "ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ '%-.64s' не пiдтримуетÑÑ (%s)"
+ER_DERIVED_MUST_HAVE_ALIAS 42000
+ eng "Every derived table must have its own alias"
+ ger "Für jede abgeleitete Tabelle muss ein eigener Alias angegeben werden"
+ jpn "導出表ã«ã¯åˆ¥åãŒå¿…é ˆã§ã™ã€‚"
+ nla "Voor elke afgeleide tabel moet een unieke alias worden gebruikt"
+ por "Cada tabela derivada deve ter seu próprio alias"
+ spa "Cada tabla derivada debe tener su propio alias"
+ swe "Varje 'derived table' måste ha sitt eget alias"
+ER_SELECT_REDUCED 01000
+ eng "Select %u was reduced during optimization"
+ ger "Select %u wurde während der Optimierung reduziert"
+ jpn "Select %u ã¯æœ€é©åŒ–ã«ã‚ˆã£ã¦æ¸›ã‚‰ã•れã¾ã—ãŸã€‚"
+ nla "Select %u werd geredureerd tijdens optimtalisatie"
+ por "Select %u foi reduzido durante otimização"
+ rus "Select %u был упразднен в процеÑÑе оптимизации"
+ spa "Select %u fué reducido durante optimización"
+ swe "Select %u reducerades vid optimiering"
+ ukr "Select %u was ÑкаÑовано при оптимiзацii"
+ER_TABLENAME_NOT_ALLOWED_HERE 42000
+ eng "Table '%-.192s' from one of the SELECTs cannot be used in %-.32s"
+ ger "Tabelle '%-.192s', die in einem der SELECT-Befehle verwendet wurde, kann nicht in %-.32s verwendet werden"
+ jpn "特定ã®SELECTã®ã¿ã§ä½¿ç”¨ã®è¡¨ '%-.192s' 㯠%-.32s ã§ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。"
+ nla "Tabel '%-.192s' uit een van de SELECTS kan niet in %-.32s gebruikt worden"
+ por "Tabela '%-.192s' de um dos SELECTs não pode ser usada em %-.32s"
+ spa "Tabla '%-.192s' de uno de los SELECT no puede ser usada en %-.32s"
+ swe "Tabell '%-.192s' från en SELECT kan inte användas i %-.32s"
+ER_NOT_SUPPORTED_AUTH_MODE 08004
+ eng "Client does not support authentication protocol requested by server; consider upgrading MariaDB client"
+ ger "Client unterstützt das vom Server erwartete Authentifizierungsprotokoll nicht. Bitte aktualisieren Sie Ihren MariaDB-Client"
+ jpn "クライアントã¯ã‚µãƒ¼ãƒãƒ¼ãŒè¦æ±‚ã™ã‚‹èªè¨¼ãƒ—ロトコルã«å¯¾å¿œã§ãã¾ã›ã‚“。MariaDBクライアントã®ã‚¢ãƒƒãƒ—グレードを検討ã—ã¦ãã ã•ã„。"
+ nla "Client ondersteunt het door de server verwachtte authenticatieprotocol niet. Overweeg een nieuwere MariaDB client te gebruiken"
+ por "Cliente não suporta o protocolo de autenticação exigido pelo servidor; considere a atualização do cliente MariaDB"
+ spa "Cliente no soporta protocolo de autenticación solicitado por el servidor; considere actualizar el cliente MariaDB"
+ swe "Klienten stöder inte autentiseringsprotokollet som begärts av servern; överväg uppgradering av klientprogrammet."
+ER_SPATIAL_CANT_HAVE_NULL 42000
+ eng "All parts of a SPATIAL index must be NOT NULL"
+ ger "Alle Teile eines SPATIAL-Index müssen als NOT NULL deklariert sein"
+ jpn "空間索引ã®ã‚­ãƒ¼åˆ—㯠NOT NULL ã§ãªã‘れã°ã„ã‘ã¾ã›ã‚“。"
+ nla "Alle delete van een SPATIAL index dienen als NOT NULL gedeclareerd te worden"
+ por "Todas as partes de uma SPATIAL index devem ser NOT NULL"
+ spa "Todas las partes de una SPATIAL index deben ser NOT NULL"
+ swe "Alla delar av en SPATIAL index måste vara NOT NULL"
+ER_COLLATION_CHARSET_MISMATCH 42000
+ eng "COLLATION '%s' is not valid for CHARACTER SET '%s'"
+ ger "COLLATION '%s' ist für CHARACTER SET '%s' ungültig"
+ jpn "COLLATION '%s' 㯠CHARACTER SET '%s' ã«é©ç”¨ã§ãã¾ã›ã‚“。"
+ nla "COLLATION '%s' is niet geldig voor CHARACTER SET '%s'"
+ por "COLLATION '%s' não é válida para CHARACTER SET '%s'"
+ spa "COLLATION '%s' no es válido para CHARACTER SET '%s'"
+ swe "COLLATION '%s' är inte tillåtet för CHARACTER SET '%s'"
+ER_SLAVE_WAS_RUNNING
+ eng "Slave is already running"
+ ger "Slave läuft bereits"
+ jpn "スレーブã¯ã™ã§ã«ç¨¼åƒä¸­ã§ã™ã€‚"
+ nla "Slave is reeds actief"
+ por "O slave já está rodando"
+ spa "Slave ya está funcionando"
+ swe "Slaven har redan startat"
+ER_SLAVE_WAS_NOT_RUNNING
+ eng "Slave already has been stopped"
+ ger "Slave wurde bereits angehalten"
+ jpn "スレーブã¯ã™ã§ã«åœæ­¢ã—ã¦ã„ã¾ã™ã€‚"
+ nla "Slave is reeds gestopt"
+ por "O slave já está parado"
+ spa "Slave ya fué parado"
+ swe "Slaven har redan stoppat"
+ER_TOO_BIG_FOR_UNCOMPRESS
+ eng "Uncompressed data size too large; the maximum size is %d (probably, length of uncompressed data was corrupted)"
+ ger "Unkomprimierte Daten sind zu groß. Die maximale Größe beträgt %d (wahrscheinlich wurde die Länge der unkomprimierten Daten beschädigt)"
+ jpn "展開後ã®ãƒ‡ãƒ¼ã‚¿ãŒå¤§ãã™ãŽã¾ã™ã€‚最大サイズ㯠%d ã§ã™ã€‚(展開後データã®é•·ã•情報ãŒå£Šã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ã‚‚ã‚りã¾ã™ã€‚)"
+ nla "Ongecomprimeerder data is te groot; de maximum lengte is %d (waarschijnlijk, de lengte van de gecomprimeerde data was beschadigd)"
+ por "Tamanho muito grande dos dados des comprimidos. O máximo tamanho é %d. (provavelmente, o comprimento dos dados descomprimidos está corrupto)"
+ spa "Tamaño demasiado grande para datos descomprimidos. El máximo tamaño es %d. (probablemente, extensión de datos descomprimidos fué corrompida)"
+ER_ZLIB_Z_MEM_ERROR
+ eng "ZLIB: Not enough memory"
+ ger "ZLIB: Nicht genug Speicher"
+ jpn "ZLIB: メモリä¸è¶³ã§ã™ã€‚"
+ nla "ZLIB: Onvoldoende geheugen"
+ por "ZLIB: Não suficiente memória disponível"
+ spa "Z_MEM_ERROR: No suficiente memoria para zlib"
+ER_ZLIB_Z_BUF_ERROR
+ eng "ZLIB: Not enough room in the output buffer (probably, length of uncompressed data was corrupted)"
+ ger "ZLIB: Im Ausgabepuffer ist nicht genug Platz vorhanden (wahrscheinlich wurde die Länge der unkomprimierten Daten beschädigt)"
+ jpn "ZLIB: 出力ãƒãƒƒãƒ•ã‚¡ã«å分ãªç©ºããŒã‚りã¾ã›ã‚“。(展開後データã®é•·ã•情報ãŒå£Šã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ã‚‚ã‚りã¾ã™ã€‚)"
+ nla "ZLIB: Onvoldoende ruimte in uitgaande buffer (waarschijnlijk, de lengte van de ongecomprimeerde data was beschadigd)"
+ por "ZLIB: Não suficiente espaço no buffer emissor (provavelmente, o comprimento dos dados descomprimidos está corrupto)"
+ spa "Z_BUF_ERROR: No suficiente espacio en el búfer de salida para zlib (probablemente, extensión de datos descomprimidos fué corrompida)"
+ER_ZLIB_Z_DATA_ERROR
+ eng "ZLIB: Input data corrupted"
+ ger "ZLIB: Eingabedaten beschädigt"
+ jpn "ZLIB: 入力データãŒå£Šã‚Œã¦ã„ã¾ã™ã€‚"
+ nla "ZLIB: Invoer data beschadigd"
+ por "ZLIB: Dados de entrada está corrupto"
+ spa "ZLIB: Dato de entrada fué corrompido para zlib"
+ER_CUT_VALUE_GROUP_CONCAT
+ eng "Row %u was cut by GROUP_CONCAT()"
+ER_WARN_TOO_FEW_RECORDS 01000
+ eng "Row %lu doesn't contain data for all columns"
+ ger "Zeile %lu enthält nicht für alle Felder Daten"
+ jpn "行 %lu ã¯ã™ã¹ã¦ã®åˆ—ã¸ã®ãƒ‡ãƒ¼ã‚¿ã‚’å«ã‚“ã§ã„ã¾ã›ã‚“。"
+ nla "Rij %lu bevat niet de data voor alle kolommen"
+ por "Conta de registro é menor que a conta de coluna na linha %lu"
+ spa "Línea %lu no contiene datos para todas las columnas"
+ER_WARN_TOO_MANY_RECORDS 01000
+ eng "Row %lu was truncated; it contained more data than there were input columns"
+ ger "Zeile %lu gekürzt, die Zeile enthielt mehr Daten, als es Eingabefelder gibt"
+ jpn "行 %lu ã¯ãƒ‡ãƒ¼ã‚¿ã‚’切りæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚列よりも多ã„データをå«ã‚“ã§ã„ã¾ã—ãŸã€‚"
+ nla "Regel %lu ingekort, bevatte meer data dan invoer kolommen"
+ por "Conta de registro é maior que a conta de coluna na linha %lu"
+ spa "Línea %lu fué truncada; La misma contine mas datos que las que existen en las columnas de entrada"
+ER_WARN_NULL_TO_NOTNULL 22004
+ eng "Column set to default value; NULL supplied to NOT NULL column '%s' at row %lu"
+ ger "Feld auf Vorgabewert gesetzt, da NULL für NOT-NULL-Feld '%s' in Zeile %lu angegeben"
+ jpn "列ã«ãƒ‡ãƒ•ォルト値ãŒè¨­å®šã•れã¾ã—ãŸã€‚NOT NULLã®åˆ— '%s' 㫠行 %lu ã§ NULL ãŒä¸Žãˆã‚‰ã‚Œã¾ã—ãŸã€‚"
+ por "Dado truncado, NULL fornecido para NOT NULL coluna '%s' na linha %lu"
+ spa "Datos truncado, NULL suministrado para NOT NULL columna '%s' en la línea %lu"
+ER_WARN_DATA_OUT_OF_RANGE 22003
+ eng "Out of range value for column '%s' at row %lu"
+WARN_DATA_TRUNCATED 01000
+ eng "Data truncated for column '%s' at row %lu"
+ ger "Daten abgeschnitten für Feld '%s' in Zeile %lu"
+ jpn "列 '%s' 㮠行 %lu ã§ãƒ‡ãƒ¼ã‚¿ãŒåˆ‡ã‚Šæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚"
+ por "Dado truncado para coluna '%s' na linha %lu"
+ spa "Datos truncados para columna '%s' en la línea %lu"
+ER_WARN_USING_OTHER_HANDLER
+ eng "Using storage engine %s for table '%s'"
+ ger "Für Tabelle '%s' wird Speicher-Engine %s benutzt"
+ jpn "ストレージエンジン %s ãŒè¡¨ '%s' ã«åˆ©ç”¨ã•れã¦ã„ã¾ã™ã€‚"
+ por "Usando engine de armazenamento %s para tabela '%s'"
+ spa "Usando motor de almacenamiento %s para tabla '%s'"
+ swe "Använder handler %s för tabell '%s'"
+ER_CANT_AGGREGATE_2COLLATIONS
+ eng "Illegal mix of collations (%s,%s) and (%s,%s) for operation '%s'"
+ ger "Unerlaubte Mischung von Sortierreihenfolgen (%s, %s) und (%s, %s) für Operation '%s'"
+ jpn "ç…§åˆé †åº (%s,%s) 㨠(%s,%s) ã®æ··åœ¨ã¯æ“作 '%s' ã§ã¯ä¸æ­£ã§ã™ã€‚"
+ por "Combinação ilegal de collations (%s,%s) e (%s,%s) para operação '%s'"
+ spa "Ilegal mezcla de collations (%s,%s) y (%s,%s) para operación '%s'"
+ER_DROP_USER
+ eng "Cannot drop one or more of the requested users"
+ ger "Kann einen oder mehrere der angegebenen Benutzer nicht löschen"
+ER_REVOKE_GRANTS
+ eng "Can't revoke all privileges for one or more of the requested users"
+ ger "Kann nicht alle Berechtigungen widerrufen, die für einen oder mehrere Benutzer gewährt wurden"
+ jpn "指定ã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã‹ã‚‰æŒ‡å®šã•れãŸå…¨ã¦ã®æ¨©é™ã‚’剥奪ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+ por "Não pode revocar todos os privilégios, grant para um ou mais dos usuários pedidos"
+ spa "No puede revocar todos los privilegios, derecho para uno o mas de los usuarios solicitados"
+ER_CANT_AGGREGATE_3COLLATIONS
+ eng "Illegal mix of collations (%s,%s), (%s,%s), (%s,%s) for operation '%s'"
+ ger "Unerlaubte Mischung von Sortierreihenfolgen (%s, %s), (%s, %s), (%s, %s) für Operation '%s'"
+ jpn "ç…§åˆé †åº (%s,%s), (%s,%s), (%s,%s) ã®æ··åœ¨ã¯æ“作 '%s' ã§ã¯ä¸æ­£ã§ã™ã€‚"
+ por "Ilegal combinação de collations (%s,%s), (%s,%s), (%s,%s) para operação '%s'"
+ spa "Ilegal mezcla de collations (%s,%s), (%s,%s), (%s,%s) para operación '%s'"
+ER_CANT_AGGREGATE_NCOLLATIONS
+ eng "Illegal mix of collations for operation '%s'"
+ ger "Unerlaubte Mischung von Sortierreihenfolgen für Operation '%s'"
+ jpn "æ“作 '%s' ã§ã¯ä¸æ­£ãªç…§åˆé †åºã®æ··åœ¨ã§ã™ã€‚"
+ por "Ilegal combinação de collations para operação '%s'"
+ spa "Ilegal mezcla de collations para operación '%s'"
+ER_VARIABLE_IS_NOT_STRUCT
+ eng "Variable '%-.64s' is not a variable component (can't be used as XXXX.variable_name)"
+ ger "Variable '%-.64s' ist keine Variablen-Komponente (kann nicht als XXXX.variablen_name verwendet werden)"
+ jpn "変数 '%-.64s' ã¯æ§‹é€ å¤‰æ•°ã®æ§‹æˆè¦ç´ ã§ã¯ã‚りã¾ã›ã‚“。(XXXX.変数å ã¨ã„ã†æŒ‡å®šã¯ã§ãã¾ã›ã‚“。)"
+ por "Variável '%-.64s' não é uma variável componente (Não pode ser usada como XXXX.variável_nome)"
+ spa "Variable '%-.64s' no es una variable componente (No puede ser usada como XXXX.variable_name)"
+ER_UNKNOWN_COLLATION
+ eng "Unknown collation: '%-.64s'"
+ ger "Unbekannte Sortierreihenfolge: '%-.64s'"
+ jpn "䏿˜Žãªç…§åˆé †åº: '%-.64s'"
+ por "Collation desconhecida: '%-.64s'"
+ spa "Collation desconocida: '%-.64s'"
+ER_SLAVE_IGNORED_SSL_PARAMS
+ eng "SSL parameters in CHANGE MASTER are ignored because this MariaDB slave was compiled without SSL support; they can be used later if MariaDB slave with SSL is started"
+ ger "SSL-Parameter in CHANGE MASTER werden ignoriert, weil dieser MariaDB-Slave ohne SSL-Unterstützung kompiliert wurde. Sie können aber später verwendet werden, wenn ein MariaDB-Slave mit SSL gestartet wird"
+ jpn "ã“ã®MySQLスレーブã¯SSLサãƒãƒ¼ãƒˆã‚’å«ã‚ã¦ã‚³ãƒ³ãƒ‘イルã•れã¦ã„ãªã„ã®ã§ã€CHANGE MASTER ã®SSLパラメータã¯ç„¡è¦–ã•れã¾ã—ãŸã€‚今後SSLサãƒãƒ¼ãƒˆã‚’æŒã¤MySQLスレーブを起動ã™ã‚‹éš›ã«åˆ©ç”¨ã•れã¾ã™ã€‚"
+ por "SSL parâmetros em CHANGE MASTER são ignorados porque este escravo MariaDB foi compilado sem o SSL suporte. Os mesmos podem ser usados mais tarde quando o escravo MariaDB com SSL seja iniciado."
+ spa "Parametros SSL en CHANGE MASTER son ignorados porque este slave MariaDB fue compilado sin soporte SSL; pueden ser usados despues cuando el slave MariaDB con SSL sea inicializado"
+ER_SERVER_IS_IN_SECURE_AUTH_MODE
+ eng "Server is running in --secure-auth mode, but '%s'@'%s' has a password in the old format; please change the password to the new format"
+ ger "Server läuft im Modus --secure-auth, aber '%s'@'%s' hat ein Passwort im alten Format. Bitte Passwort ins neue Format ändern"
+ jpn "サーãƒãƒ¼ã¯ --secure-auth モードã§ç¨¼åƒã—ã¦ã„ã¾ã™ã€‚ã—ã‹ã— '%s'@'%s' ã¯å¤ã„å½¢å¼ã®ãƒ‘スワードを使用ã—ã¦ã„ã¾ã™ã€‚æ–°ã—ã„å½¢å¼ã®ãƒ‘スワードã«å¤‰æ›´ã—ã¦ãã ã•ã„。"
+ por "Servidor está rodando em --secure-auth modo, porêm '%s'@'%s' tem senha no formato antigo; por favor troque a senha para o novo formato"
+ rus "Сервер запущен в режиме --secure-auth (безопаÑной авторизации), но Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%s'@'%s' пароль Ñохранён в Ñтаром формате; необходимо обновить формат паролÑ"
+ spa "Servidor está rodando en modo --secure-auth, pero '%s'@'%s' tiene clave en el antiguo formato; por favor cambie la clave para el nuevo formato"
+ER_WARN_FIELD_RESOLVED
+ eng "Field or reference '%-.192s%s%-.192s%s%-.192s' of SELECT #%d was resolved in SELECT #%d"
+ ger "Feld oder Verweis '%-.192s%s%-.192s%s%-.192s' im SELECT-Befehl Nr. %d wurde im SELECT-Befehl Nr. %d aufgelöst"
+ jpn "フィールドã¾ãŸã¯å‚ç…§ '%-.192s%s%-.192s%s%-.192s' 㯠SELECT #%d ã§ã¯ãªãã€SELECT #%d ã§è§£æ±ºã•れã¾ã—ãŸã€‚"
+ por "Campo ou referência '%-.192s%s%-.192s%s%-.192s' de SELECT #%d foi resolvido em SELECT #%d"
+ rus "Поле или ÑÑылка '%-.192s%s%-.192s%s%-.192s' из SELECTа #%d была найдена в SELECTе #%d"
+ spa "Campo o referencia '%-.192s%s%-.192s%s%-.192s' de SELECT #%d fue resolvido en SELECT #%d"
+ ukr "Стовбець або поÑÐ¸Ð»Ð°Ð½Ð½Ñ '%-.192s%s%-.192s%s%-.192s' із SELECTу #%d було знайдене у SELECTÑ– #%d"
+ER_BAD_SLAVE_UNTIL_COND
+ eng "Incorrect parameter or combination of parameters for START SLAVE UNTIL"
+ ger "Falscher Parameter oder falsche Kombination von Parametern für START SLAVE UNTIL"
+ jpn "START SLAVE UNTIL ã¸ã®ãƒ‘ラメータã¾ãŸã¯ãã®çµ„ã¿åˆã‚ã›ãŒä¸æ­£ã§ã™ã€‚"
+ por "Parâmetro ou combinação de parâmetros errado para START SLAVE UNTIL"
+ spa "Parametro equivocado o combinación de parametros para START SLAVE UNTIL"
+ER_MISSING_SKIP_SLAVE
+ eng "It is recommended to use --skip-slave-start when doing step-by-step replication with START SLAVE UNTIL; otherwise, you will get problems if you get an unexpected slave's mysqld restart"
+ ger "Es wird empfohlen, mit --skip-slave-start zu starten, wenn mit START SLAVE UNTIL eine Schritt-für-Schritt-Replikation ausgeführt wird. Ansonsten gibt es Probleme, wenn ein Slave-Server unerwartet neu startet"
+ jpn "START SLAVE UNTIL ã§æ®µéšŽçš„ã«ãƒ¬ãƒ—リケーションを行ã†éš›ã«ã¯ã€--skip-slave-start オプションを使ã†ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚使ã‚ãªã„å ´åˆã€ã‚¹ãƒ¬ãƒ¼ãƒ–ã®mysqldãŒä¸æ…®ã®å†èµ·å‹•ã‚’ã™ã‚‹ã¨å•題ãŒç™ºç”Ÿã—ã¾ã™ã€‚"
+ por "É recomendado para rodar com --skip-slave-start quando fazendo replicação passo-por-passo com START SLAVE UNTIL, de outra forma você não está seguro em caso de inesperada reinicialição do mysqld escravo"
+ spa "Es recomendado rodar con --skip-slave-start cuando haciendo replicación step-by-step con START SLAVE UNTIL, a menos que usted no esté seguro en caso de inesperada reinicialización del mysqld slave"
+ER_UNTIL_COND_IGNORED
+ eng "SQL thread is not to be started so UNTIL options are ignored"
+ ger "SQL-Thread soll nicht gestartet werden. Daher werden UNTIL-Optionen ignoriert"
+ jpn "スレーブSQLスレッドãŒé–‹å§‹ã•れãªã„ãŸã‚ã€UNTILオプションã¯ç„¡è¦–ã•れã¾ã—ãŸã€‚"
+ por "Thread SQL não pode ser inicializado tal que opções UNTIL são ignoradas"
+ spa "SQL thread no es inicializado tal que opciones UNTIL son ignoradas"
+ER_WRONG_NAME_FOR_INDEX 42000
+ eng "Incorrect index name '%-.100s'"
+ ger "Falscher Indexname '%-.100s'"
+ jpn "索引å '%-.100s' ã¯ä¸æ­£ã§ã™ã€‚"
+ por "Incorreto nome de índice '%-.100s'"
+ spa "Nombre de índice incorrecto '%-.100s'"
+ swe "Felaktigt index namn '%-.100s'"
+ER_WRONG_NAME_FOR_CATALOG 42000
+ eng "Incorrect catalog name '%-.100s'"
+ ger "Falscher Katalogname '%-.100s'"
+ jpn "カタログå '%-.100s' ã¯ä¸æ­£ã§ã™ã€‚"
+ por "Incorreto nome de catálogo '%-.100s'"
+ spa "Nombre de catalog incorrecto '%-.100s'"
+ swe "Felaktigt katalog namn '%-.100s'"
+ER_WARN_QC_RESIZE
+ eng "Query cache failed to set size %llu; new query cache size is %lu"
+ ger "Änderung der Query-Cache-Größe auf %llu fehlgeschlagen; neue Query-Cache-Größe ist %lu"
+ por "Falha em Query cache para configurar tamanho %llu, novo tamanho de query cache é %lu"
+ rus "Кеш запроÑов не может уÑтановить размер %llu, новый размер кеша зпроÑов - %lu"
+ spa "Query cache fallada para configurar tamaño %llu, nuevo tamaño de query cache es %lu"
+ swe "Storleken av "Query cache" kunde inte sättas till %llu, ny storlek är %lu"
+ ukr "Кеш запитів неÑпроможен вÑтановити розмір %llu, новий розмір кеша запитів - %lu"
+ER_BAD_FT_COLUMN
+ eng "Column '%-.192s' cannot be part of FULLTEXT index"
+ ger "Feld '%-.192s' kann nicht Teil eines FULLTEXT-Index sein"
+ jpn "列 '%-.192s' ã¯å…¨æ–‡ç´¢å¼•ã®ã‚­ãƒ¼ã«ã¯ã§ãã¾ã›ã‚“。"
+ por "Coluna '%-.192s' não pode ser parte de índice FULLTEXT"
+ spa "Columna '%-.192s' no puede ser parte de FULLTEXT index"
+ swe "Kolumn '%-.192s' kan inte vara del av ett FULLTEXT index"
+ER_UNKNOWN_KEY_CACHE
+ eng "Unknown key cache '%-.100s'"
+ ger "Unbekannter Schlüssel-Cache '%-.100s'"
+ jpn "'%-.100s' ã¯ä¸æ˜Žãªã‚­ãƒ¼ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã§ã™ã€‚"
+ por "Key cache desconhecida '%-.100s'"
+ spa "Desconocida key cache '%-.100s'"
+ swe "Okänd nyckel cache '%-.100s'"
+ER_WARN_HOSTNAME_WONT_WORK
+ eng "MariaDB is started in --skip-name-resolve mode; you must restart it without this switch for this grant to work"
+ ger "MariaDB wurde mit --skip-name-resolve gestartet. Diese Option darf nicht verwendet werden, damit diese Rechtevergabe möglich ist"
+ jpn "MariaDB㯠--skip-name-resolve モードã§èµ·å‹•ã—ã¦ã„ã¾ã™ã€‚ã“ã®ã‚ªãƒ—ションを外ã—ã¦å†èµ·å‹•ã—ãªã‘れã°ã€ã“ã®æ¨©é™æ“ä½œã¯æ©Ÿèƒ½ã—ã¾ã›ã‚“。"
+ por "MariaDB foi inicializado em modo --skip-name-resolve. Você necesita reincializá-lo sem esta opção para este grant funcionar"
+ spa "MariaDB esta inicializado en modo --skip-name-resolve. Usted necesita reinicializarlo sin esta opción para este derecho funcionar"
+ER_UNKNOWN_STORAGE_ENGINE 42000
+ eng "Unknown storage engine '%s'"
+ ger "Unbekannte Speicher-Engine '%s'"
+ jpn "'%s' ã¯ä¸æ˜Žãªã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ã§ã™ã€‚"
+ por "Motor de tabela desconhecido '%s'"
+ spa "Desconocido motor de tabla '%s'"
+ER_WARN_DEPRECATED_SYNTAX
+ eng "'%s' is deprecated and will be removed in a future release. Please use %s instead"
+ ger "'%s' ist veraltet. Bitte benutzen Sie '%s'"
+ jpn "'%s' ã¯å°†æ¥ã®ãƒªãƒªãƒ¼ã‚¹ã§å»ƒæ­¢äºˆå®šã§ã™ã€‚代ã‚り㫠%s を使用ã—ã¦ãã ã•ã„。"
+ por "'%s' é desatualizado. Use '%s' em seu lugar"
+ spa "'%s' está desaprobado, use '%s' en su lugar"
+ER_NON_UPDATABLE_TABLE
+ eng "The target table %-.100s of the %s is not updatable"
+ ger "Die Zieltabelle %-.100s von %s ist nicht aktualisierbar"
+ jpn "対象表 %-.100s ã¯æ›´æ–°å¯èƒ½ã§ã¯ãªã„ã®ã§ã€%s を行ãˆã¾ã›ã‚“。"
+ por "A tabela destino %-.100s do %s não é atualizável"
+ rus "Таблица %-.100s в %s не может изменÑÑ‚ÑÑ"
+ spa "La tabla destino %-.100s del %s no es actualizable"
+ swe "Tabell %-.100s använd med '%s' är inte uppdateringsbar"
+ ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ %-.100s у %s не може оновлюватиÑÑŒ"
+ER_FEATURE_DISABLED
+ eng "The '%s' feature is disabled; you need MariaDB built with '%s' to have it working"
+ ger "Das Feature '%s' ist ausgeschaltet, Sie müssen MariaDB mit '%s' übersetzen, damit es verfügbar ist"
+ jpn "機能 '%s' ã¯ç„¡åйã§ã™ã€‚利用ã™ã‚‹ãŸã‚ã«ã¯ '%s' ã‚’å«ã‚ã¦ãƒ“ルドã—ãŸMariaDBãŒå¿…è¦ã§ã™ã€‚"
+ por "O recurso '%s' foi desativado; você necessita MariaDB construído com '%s' para ter isto funcionando"
+ spa "El recurso '%s' fue deshabilitado; usted necesita construir MariaDB con '%s' para tener eso funcionando"
+ swe "'%s' är inte aktiverad; För att aktivera detta måste du bygga om MariaDB med '%s' definierad"
+ER_OPTION_PREVENTS_STATEMENT
+ eng "The MariaDB server is running with the %s option so it cannot execute this statement"
+ ger "Der MariaDB-Server läuft mit der Option %s und kann diese Anweisung deswegen nicht ausführen"
+ jpn "MariaDBサーãƒãƒ¼ãŒ %s オプションã§å®Ÿè¡Œã•れã¦ã„ã‚‹ã®ã§ã€ã“ã®ã‚¹ãƒ†ãƒ¼ãƒˆãƒ¡ãƒ³ãƒˆã¯å®Ÿè¡Œã§ãã¾ã›ã‚“。"
+ por "O servidor MariaDB está rodando com a opção %s razão pela qual não pode executar esse commando"
+ spa "El servidor MariaDB está rodando con la opción %s tal que no puede ejecutar este comando"
+ swe "MariaDB är startad med %s. Pga av detta kan du inte använda detta kommando"
+ER_DUPLICATED_VALUE_IN_TYPE
+ eng "Column '%-.100s' has duplicated value '%-.64s' in %s"
+ ger "Feld '%-.100s' hat doppelten Wert '%-.64s' in %s"
+ jpn "列 '%-.100s' ã§ã€é‡è¤‡ã™ã‚‹å€¤ '%-.64s' ㌠%s ã«æŒ‡å®šã•れã¦ã„ã¾ã™ã€‚"
+ por "Coluna '%-.100s' tem valor duplicado '%-.64s' em %s"
+ spa "Columna '%-.100s' tiene valor doblado '%-.64s' en %s"
+ER_TRUNCATED_WRONG_VALUE 22007
+ eng "Truncated incorrect %-.32s value: '%-.128s'"
+ ger "Falscher %-.32s-Wert gekürzt: '%-.128s'"
+ jpn "䏿­£ãª %-.32s ã®å€¤ãŒåˆ‡ã‚Šæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚: '%-.128s'"
+ por "Truncado errado %-.32s valor: '%-.128s'"
+ spa "Equivocado truncado %-.32s valor: '%-.128s'"
+ER_TOO_MUCH_AUTO_TIMESTAMP_COLS
+ eng "Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause"
+ ger "Fehlerhafte Tabellendefinition. Es kann nur eine einzige TIMESTAMP-Spalte mit CURRENT_TIMESTAMP als DEFAULT oder in einer ON-UPDATE-Klausel geben"
+ jpn "䏿­£ãªè¡¨å®šç¾©ã§ã™ã€‚DEFAULTå¥ã¾ãŸã¯ON UPDATEå¥ã« CURRENT_TIMESTAMP ã‚’ã¨ã‚‚ãªã†TIMESTAMPåž‹ã®åˆ—ã¯1ã¤ã¾ã§ã§ã™ã€‚"
+ por "Incorreta definição de tabela; Pode ter somente uma coluna TIMESTAMP com CURRENT_TIMESTAMP em DEFAULT ou ON UPDATE cláusula"
+ spa "Incorrecta definición de tabla; Solamente debe haber una columna TIMESTAMP con CURRENT_TIMESTAMP en DEFAULT o ON UPDATE cláusula"
+ER_INVALID_ON_UPDATE
+ eng "Invalid ON UPDATE clause for '%-.192s' column"
+ ger "Ungültige ON-UPDATE-Klausel für Spalte '%-.192s'"
+ jpn "列 '%-.192s' ã« ON UPDATEå¥ã¯ç„¡åйã§ã™ã€‚"
+ por "Inválida cláusula ON UPDATE para campo '%-.192s'"
+ spa "Inválido ON UPDATE cláusula para campo '%-.192s'"
+ER_UNSUPPORTED_PS
+ eng "This command is not supported in the prepared statement protocol yet"
+ ger "Dieser Befehl wird im Protokoll für vorbereitete Anweisungen noch nicht unterstützt"
+ER_GET_ERRMSG
+ dan "Modtog fejl %d '%-.200s' fra %s"
+ eng "Got error %d '%-.200s' from %s"
+ ger "Fehler %d '%-.200s' von %s"
+ jpn "エラー %d '%-.200s' ㌠%s ã‹ã‚‰è¿”ã•れã¾ã—ãŸã€‚"
+ nor "Mottok feil %d '%-.200s' fa %s"
+ norwegian-ny "Mottok feil %d '%-.200s' fra %s"
+ER_GET_TEMPORARY_ERRMSG
+ dan "Modtog temporary fejl %d '%-.200s' fra %s"
+ eng "Got temporary error %d '%-.200s' from %s"
+ jpn "一時エラー %d '%-.200s' ㌠%s ã‹ã‚‰è¿”ã•れã¾ã—ãŸã€‚"
+ ger "Temporärer Fehler %d '%-.200s' von %s"
+ nor "Mottok temporary feil %d '%-.200s' fra %s"
+ norwegian-ny "Mottok temporary feil %d '%-.200s' fra %s"
+ER_UNKNOWN_TIME_ZONE
+ eng "Unknown or incorrect time zone: '%-.64s'"
+ ger "Unbekannte oder falsche Zeitzone: '%-.64s'"
+ER_WARN_INVALID_TIMESTAMP
+ eng "Invalid TIMESTAMP value in column '%s' at row %lu"
+ ger "Ungültiger TIMESTAMP-Wert in Feld '%s', Zeile %lu"
+ER_INVALID_CHARACTER_STRING
+ eng "Invalid %s character string: '%.64s'"
+ ger "Ungültiger %s-Zeichen-String: '%.64s'"
+ER_WARN_ALLOWED_PACKET_OVERFLOWED
+ eng "Result of %s() was larger than max_allowed_packet (%ld) - truncated"
+ ger "Ergebnis von %s() war größer als max_allowed_packet (%ld) Bytes und wurde deshalb gekürzt"
+ER_CONFLICTING_DECLARATIONS
+ eng "Conflicting declarations: '%s%s' and '%s%s'"
+ ger "Widersprüchliche Deklarationen: '%s%s' und '%s%s'"
+ER_SP_NO_RECURSIVE_CREATE 2F003
+ eng "Can't create a %s from within another stored routine"
+ ger "Kann kein %s innerhalb einer anderen gespeicherten Routine erzeugen"
+ER_SP_ALREADY_EXISTS 42000
+ eng "%s %s already exists"
+ ger "%s %s existiert bereits"
+ER_SP_DOES_NOT_EXIST 42000
+ eng "%s %s does not exist"
+ ger "%s %s existiert nicht"
+ER_SP_DROP_FAILED
+ eng "Failed to DROP %s %s"
+ ger "DROP %s %s ist fehlgeschlagen"
+ER_SP_STORE_FAILED
+ eng "Failed to CREATE %s %s"
+ ger "CREATE %s %s ist fehlgeschlagen"
+ER_SP_LILABEL_MISMATCH 42000
+ eng "%s with no matching label: %s"
+ ger "%s ohne passende Marke: %s"
+ER_SP_LABEL_REDEFINE 42000
+ eng "Redefining label %s"
+ ger "Neudefinition der Marke %s"
+ER_SP_LABEL_MISMATCH 42000
+ eng "End-label %s without match"
+ ger "Ende-Marke %s ohne zugehörigen Anfang"
+ER_SP_UNINIT_VAR 01000
+ eng "Referring to uninitialized variable %s"
+ ger "Zugriff auf nichtinitialisierte Variable %s"
+ER_SP_BADSELECT 0A000
+ eng "PROCEDURE %s can't return a result set in the given context"
+ ger "PROCEDURE %s kann im gegebenen Kontext keine Ergebnismenge zurückgeben"
+ER_SP_BADRETURN 42000
+ eng "RETURN is only allowed in a FUNCTION"
+ ger "RETURN ist nur innerhalb einer FUNCTION erlaubt"
+ER_SP_BADSTATEMENT 0A000
+ eng "%s is not allowed in stored procedures"
+ ger "%s ist in gespeicherten Prozeduren nicht erlaubt"
+ER_UPDATE_LOG_DEPRECATED_IGNORED 42000
+ eng "The update log is deprecated and replaced by the binary log; SET SQL_LOG_UPDATE has been ignored. This option will be removed in MariaDB 5.6."
+ ger "Das Update-Log ist veraltet und wurde durch das Binär-Log ersetzt. SET SQL_LOG_UPDATE wird ignoriert. Diese Option wird in MariaDB 5.6 entfernt."
+ER_UPDATE_LOG_DEPRECATED_TRANSLATED 42000
+ eng "The update log is deprecated and replaced by the binary log; SET SQL_LOG_UPDATE has been translated to SET SQL_LOG_BIN. This option will be removed in MariaDB 5.6."
+ ger "Das Update-Log ist veraltet und wurde durch das Binär-Log ersetzt. SET SQL_LOG_UPDATE wurde in SET SQL_LOG_BIN übersetzt. Diese Option wird in MariaDB 5.6 entfernt."
+ER_QUERY_INTERRUPTED 70100
+ eng "Query execution was interrupted"
+ ger "Ausführung der Abfrage wurde unterbrochen"
+ER_SP_WRONG_NO_OF_ARGS 42000
+ eng "Incorrect number of arguments for %s %s; expected %u, got %u"
+ ger "Falsche Anzahl von Argumenten für %s %s; erwarte %u, erhalte %u"
+ER_SP_COND_MISMATCH 42000
+ eng "Undefined CONDITION: %s"
+ ger "Undefinierte CONDITION: %s"
+ER_SP_NORETURN 42000
+ eng "No RETURN found in FUNCTION %s"
+ ger "Kein RETURN in FUNCTION %s gefunden"
+ER_SP_NORETURNEND 2F005
+ eng "FUNCTION %s ended without RETURN"
+ ger "FUNCTION %s endete ohne RETURN"
+ER_SP_BAD_CURSOR_QUERY 42000
+ eng "Cursor statement must be a SELECT"
+ ger "Cursor-Anweisung muss ein SELECT sein"
+ER_SP_BAD_CURSOR_SELECT 42000
+ eng "Cursor SELECT must not have INTO"
+ ger "Cursor-SELECT darf kein INTO haben"
+ER_SP_CURSOR_MISMATCH 42000
+ eng "Undefined CURSOR: %s"
+ ger "Undefinierter CURSOR: %s"
+ER_SP_CURSOR_ALREADY_OPEN 24000
+ eng "Cursor is already open"
+ ger "Cursor ist schon geöffnet"
+ER_SP_CURSOR_NOT_OPEN 24000
+ eng "Cursor is not open"
+ ger "Cursor ist nicht geöffnet"
+ER_SP_UNDECLARED_VAR 42000
+ eng "Undeclared variable: %s"
+ ger "Nicht deklarierte Variable: %s"
+ER_SP_WRONG_NO_OF_FETCH_ARGS
+ eng "Incorrect number of FETCH variables"
+ ger "Falsche Anzahl von FETCH-Variablen"
+ER_SP_FETCH_NO_DATA 02000
+ eng "No data - zero rows fetched, selected, or processed"
+ ger "Keine Daten - null Zeilen geholt (fetch), ausgewählt oder verarbeitet"
+ER_SP_DUP_PARAM 42000
+ eng "Duplicate parameter: %s"
+ ger "Doppelter Parameter: %s"
+ER_SP_DUP_VAR 42000
+ eng "Duplicate variable: %s"
+ ger "Doppelte Variable: %s"
+ER_SP_DUP_COND 42000
+ eng "Duplicate condition: %s"
+ ger "Doppelte Bedingung: %s"
+ER_SP_DUP_CURS 42000
+ eng "Duplicate cursor: %s"
+ ger "Doppelter Cursor: %s"
+ER_SP_CANT_ALTER
+ eng "Failed to ALTER %s %s"
+ ger "ALTER %s %s fehlgeschlagen"
+ER_SP_SUBSELECT_NYI 0A000
+ eng "Subquery value not supported"
+ ger "Subquery-Wert wird nicht unterstützt"
+ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG 0A000
+ eng "%s is not allowed in stored function or trigger"
+ ger "%s ist in gespeicherten Funktionen und in Triggern nicht erlaubt"
+ER_SP_VARCOND_AFTER_CURSHNDLR 42000
+ eng "Variable or condition declaration after cursor or handler declaration"
+ ger "Deklaration einer Variablen oder einer Bedingung nach der Deklaration eines Cursors oder eines Handlers"
+ER_SP_CURSOR_AFTER_HANDLER 42000
+ eng "Cursor declaration after handler declaration"
+ ger "Deklaration eines Cursors nach der Deklaration eines Handlers"
+ER_SP_CASE_NOT_FOUND 20000
+ eng "Case not found for CASE statement"
+ ger "Fall für CASE-Anweisung nicht gefunden"
+ER_FPARSER_TOO_BIG_FILE
+ eng "Configuration file '%-.192s' is too big"
+ ger "Konfigurationsdatei '%-.192s' ist zu groß"
+ rus "Слишком большой конфигурационный файл '%-.192s'"
+ ukr "Занадто великий конфігураційний файл '%-.192s'"
+ER_FPARSER_BAD_HEADER
+ eng "Malformed file type header in file '%-.192s'"
+ ger "Nicht wohlgeformter Dateityp-Header in Datei '%-.192s'"
+ rus "Ðеверный заголовок типа файла '%-.192s'"
+ ukr "Ðевірний заголовок типу у файлі '%-.192s'"
+ER_FPARSER_EOF_IN_COMMENT
+ eng "Unexpected end of file while parsing comment '%-.200s'"
+ ger "Unerwartetes Dateiende beim Parsen des Kommentars '%-.200s'"
+ rus "Ðеожиданный конец файла в коментарии '%-.200s'"
+ ukr "ÐеÑподіванний кінець файлу у коментарі '%-.200s'"
+ER_FPARSER_ERROR_IN_PARAMETER
+ eng "Error while parsing parameter '%-.192s' (line: '%-.192s')"
+ ger "Fehler beim Parsen des Parameters '%-.192s' (Zeile: '%-.192s')"
+ rus "Ошибка при раÑпознавании параметра '%-.192s' (Ñтрока: '%-.192s')"
+ ukr "Помилка в роÑпізнаванні параметру '%-.192s' (Ñ€Ñдок: '%-.192s')"
+ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER
+ eng "Unexpected end of file while skipping unknown parameter '%-.192s'"
+ ger "Unerwartetes Dateiende beim Überspringen des unbekannten Parameters '%-.192s'"
+ rus "Ðеожиданный конец файла при пропуÑке неизвеÑтного параметра '%-.192s'"
+ ukr "ÐеÑподіванний кінець файлу у Ñпробі проминути невідомий параметр '%-.192s'"
+ER_VIEW_NO_EXPLAIN
+ eng "EXPLAIN/SHOW can not be issued; lacking privileges for underlying table"
+ ger "EXPLAIN/SHOW kann nicht verlangt werden. Rechte für zugrunde liegende Tabelle fehlen"
+ rus "EXPLAIN/SHOW не может быть выполнено; недоÑтаточно прав на таблицы запроÑа"
+ ukr "EXPLAIN/SHOW не може бути виконано; немає прав на таблиці запиту"
+ER_FRM_UNKNOWN_TYPE
+ eng "File '%-.192s' has unknown type '%-.64s' in its header"
+ ger "Datei '%-.192s' hat unbekannten Typ '%-.64s' im Header"
+ rus "Файл '%-.192s' Ñодержит неизвеÑтный тип '%-.64s' в заголовке"
+ ukr "Файл '%-.192s' має невідомий тип '%-.64s' у заголовку"
+ER_WRONG_OBJECT
+ eng "'%-.192s.%-.192s' is not %s"
+ ger "'%-.192s.%-.192s' ist nicht %s"
+ rus "'%-.192s.%-.192s' - не %s"
+ ukr "'%-.192s.%-.192s' не є %s"
+ER_NONUPDATEABLE_COLUMN
+ eng "Column '%-.192s' is not updatable"
+ ger "Feld '%-.192s' ist nicht aktualisierbar"
+ rus "Столбец '%-.192s' не обновлÑемый"
+ ukr "Стовбець '%-.192s' не може бути зминений"
+ER_VIEW_SELECT_DERIVED
+ eng "View's SELECT contains a subquery in the FROM clause"
+ ger "SELECT der View enthält eine Subquery in der FROM-Klausel"
+ rus "View SELECT Ñодержит Ð¿Ð¾Ð´Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð² конÑтрукции FROM"
+ ukr "View SELECT має підзапит у конÑтрукції FROM"
+ER_VIEW_SELECT_CLAUSE
+ eng "View's SELECT contains a '%s' clause"
+ ger "SELECT der View enthält eine '%s'-Klausel"
+ rus "View SELECT Ñодержит конÑтрукцию '%s'"
+ ukr "View SELECT має конÑтрукцію '%s'"
+ER_VIEW_SELECT_VARIABLE
+ eng "View's SELECT contains a variable or parameter"
+ ger "SELECT der View enthält eine Variable oder einen Parameter"
+ rus "View SELECT Ñодержит переменную или параметр"
+ ukr "View SELECT має зминну або параметер"
+ER_VIEW_SELECT_TMPTABLE
+ eng "View's SELECT refers to a temporary table '%-.192s'"
+ ger "SELECT der View verweist auf eine temporäre Tabelle '%-.192s'"
+ rus "View SELECT Ñодержит ÑÑылку на временную таблицу '%-.192s'"
+ ukr "View SELECT викориÑтовує тимчаÑову таблицю '%-.192s'"
+ER_VIEW_WRONG_LIST
+ eng "View's SELECT and view's field list have different column counts"
+ ger "SELECT- und Feldliste der Views haben unterschiedliche Anzahlen von Spalten"
+ rus "View SELECT и ÑпиÑок полей view имеют разное количеÑтво Ñтолбцов"
+ ukr "View SELECT Ñ– перелік Ñтовбців view мають різну кількіÑть Ñковбців"
+ER_WARN_VIEW_MERGE
+ eng "View merge algorithm can't be used here for now (assumed undefined algorithm)"
+ ger "View-Merge-Algorithmus kann hier momentan nicht verwendet werden (undefinierter Algorithmus wird angenommen)"
+ rus "Ðлгоритм ÑлиÑÐ½Ð¸Ñ view не может быть иÑпользован ÑÐµÐ¹Ñ‡Ð°Ñ (алгоритм будет неопеределенным)"
+ ukr "Ðлгоритм Ð·Ð»Ð¸Ð²Ð°Ð½Ð½Ñ view не може бути викориÑтаний зараз (алгоритм буде невизначений)"
+ER_WARN_VIEW_WITHOUT_KEY
+ eng "View being updated does not have complete key of underlying table in it"
+ ger "Die aktualisierte View enthält nicht den vollständigen Schlüssel der zugrunde liegenden Tabelle"
+ rus "ОбновлÑемый view не Ñодержит ключа иÑпользованных(ой) в нем таблиц(Ñ‹)"
+ ukr "View, що оновлюетьÑÑ, не міÑтить повного ключа таблиці(ÑŒ), що викоріÑтана в ньюому"
+ER_VIEW_INVALID
+ eng "View '%-.192s.%-.192s' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them"
+ER_SP_NO_DROP_SP
+ eng "Can't drop or alter a %s from within another stored routine"
+ ger "Kann eine %s nicht von innerhalb einer anderen gespeicherten Routine löschen oder ändern"
+ER_SP_GOTO_IN_HNDLR
+ eng "GOTO is not allowed in a stored procedure handler"
+ ger "GOTO ist im Handler einer gespeicherten Prozedur nicht erlaubt"
+ER_TRG_ALREADY_EXISTS
+ eng "Trigger already exists"
+ ger "Trigger existiert bereits"
+ER_TRG_DOES_NOT_EXIST
+ eng "Trigger does not exist"
+ ger "Trigger existiert nicht"
+ER_TRG_ON_VIEW_OR_TEMP_TABLE
+ eng "Trigger's '%-.192s' is view or temporary table"
+ ger "'%-.192s' des Triggers ist View oder temporäre Tabelle"
+ER_TRG_CANT_CHANGE_ROW
+ eng "Updating of %s row is not allowed in %strigger"
+ ger "Aktualisieren einer %s-Zeile ist in einem %s-Trigger nicht erlaubt"
+ER_TRG_NO_SUCH_ROW_IN_TRG
+ eng "There is no %s row in %s trigger"
+ ger "Es gibt keine %s-Zeile im %s-Trigger"
+ER_NO_DEFAULT_FOR_FIELD
+ eng "Field '%-.192s' doesn't have a default value"
+ ger "Feld '%-.192s' hat keinen Vorgabewert"
+ER_DIVISION_BY_ZERO 22012
+ eng "Division by 0"
+ ger "Division durch 0"
+ER_TRUNCATED_WRONG_VALUE_FOR_FIELD 22007
+ eng "Incorrect %-.32s value: '%-.128s' for column '%.192s' at row %lu"
+ ger "Falscher %-.32s-Wert: '%-.128s' für Feld '%.192s' in Zeile %lu"
+ER_ILLEGAL_VALUE_FOR_TYPE 22007
+ eng "Illegal %s '%-.192s' value found during parsing"
+ ger "Nicht zulässiger %s-Wert '%-.192s' beim Parsen gefunden"
+ER_VIEW_NONUPD_CHECK
+ eng "CHECK OPTION on non-updatable view '%-.192s.%-.192s'"
+ ger "CHECK OPTION auf nicht-aktualisierbarem View '%-.192s.%-.192s'"
+ rus "CHECK OPTION Ð´Ð»Ñ Ð½ÐµÐ¾Ð±Ð½Ð¾Ð²Ð»Ñемого VIEW '%-.192s.%-.192s'"
+ ukr "CHECK OPTION Ð´Ð»Ñ VIEW '%-.192s.%-.192s' що не може бути оновленним"
+ER_VIEW_CHECK_FAILED
+ eng "CHECK OPTION failed '%-.192s.%-.192s'"
+ ger "CHECK OPTION fehlgeschlagen: '%-.192s.%-.192s'"
+ rus "проверка CHECK OPTION Ð´Ð»Ñ VIEW '%-.192s.%-.192s' провалилаÑÑŒ"
+ ukr "Перевірка CHECK OPTION Ð´Ð»Ñ VIEW '%-.192s.%-.192s' не пройшла"
+ER_PROCACCESS_DENIED_ERROR 42000
+ eng "%-.32s command denied to user '%s'@'%s' for routine '%-.192s'"
+ ger "Befehl %-.32s nicht zulässig für Benutzer '%s'@'%s' in Routine '%-.192s'"
+ER_RELAY_LOG_FAIL
+ eng "Failed purging old relay logs: %s"
+ ger "Bereinigen alter Relais-Logs fehlgeschlagen: %s"
+ER_PASSWD_LENGTH
+ eng "Password hash should be a %d-digit hexadecimal number"
+ ger "Passwort-Hash sollte eine Hexdaezimalzahl mit %d Stellen sein"
+ER_UNKNOWN_TARGET_BINLOG
+ eng "Target log not found in binlog index"
+ ger "Ziel-Log im Binlog-Index nicht gefunden"
+ER_IO_ERR_LOG_INDEX_READ
+ eng "I/O error reading log index file"
+ ger "Fehler beim Lesen der Log-Index-Datei"
+ER_BINLOG_PURGE_PROHIBITED
+ eng "Server configuration does not permit binlog purge"
+ ger "Server-Konfiguration erlaubt keine Binlog-Bereinigung"
+ER_FSEEK_FAIL
+ eng "Failed on fseek()"
+ ger "fseek() fehlgeschlagen"
+ER_BINLOG_PURGE_FATAL_ERR
+ eng "Fatal error during log purge"
+ ger "Schwerwiegender Fehler bei der Log-Bereinigung"
+ER_LOG_IN_USE
+ eng "A purgeable log is in use, will not purge"
+ ger "Ein zu bereinigendes Log wird gerade benutzt, daher keine Bereinigung"
+ER_LOG_PURGE_UNKNOWN_ERR
+ eng "Unknown error during log purge"
+ ger "Unbekannter Fehler bei Log-Bereinigung"
+ER_RELAY_LOG_INIT
+ eng "Failed initializing relay log position: %s"
+ ger "Initialisierung der Relais-Log-Position fehlgeschlagen: %s"
+ER_NO_BINARY_LOGGING
+ eng "You are not using binary logging"
+ ger "Sie verwenden keine Binärlogs"
+ER_RESERVED_SYNTAX
+ eng "The '%-.64s' syntax is reserved for purposes internal to the MariaDB server"
+ ger "Die Schreibweise '%-.64s' ist für interne Zwecke des MariaDB-Servers reserviert"
+ER_WSAS_FAILED
+ eng "WSAStartup Failed"
+ ger "WSAStartup fehlgeschlagen"
+ER_DIFF_GROUPS_PROC
+ eng "Can't handle procedures with different groups yet"
+ ger "Kann Prozeduren mit unterschiedlichen Gruppen noch nicht verarbeiten"
+ER_NO_GROUP_FOR_PROC
+ eng "Select must have a group with this procedure"
+ ger "SELECT muss bei dieser Prozedur ein GROUP BY haben"
+ER_ORDER_WITH_PROC
+ eng "Can't use ORDER clause with this procedure"
+ ger "Kann bei dieser Prozedur keine ORDER-BY-Klausel verwenden"
+ER_LOGGING_PROHIBIT_CHANGING_OF
+ eng "Binary logging and replication forbid changing the global server %s"
+ ger "Binärlogs und Replikation verhindern Wechsel des globalen Servers %s"
+ER_NO_FILE_MAPPING
+ eng "Can't map file: %-.200s, errno: %M"
+ ger "Kann Datei nicht abbilden: %-.200s, Fehler: %M"
+ER_WRONG_MAGIC
+ eng "Wrong magic in %-.64s"
+ ger "Falsche magische Zahlen in %-.64s"
+ER_PS_MANY_PARAM
+ eng "Prepared statement contains too many placeholders"
+ ger "Vorbereitete Anweisung enthält zu viele Platzhalter"
+ER_KEY_PART_0
+ eng "Key part '%-.192s' length cannot be 0"
+ ger "Länge des Schlüsselteils '%-.192s' kann nicht 0 sein"
+ER_VIEW_CHECKSUM
+ eng "View text checksum failed"
+ ger "View-Text-Prüfsumme fehlgeschlagen"
+ rus "Проверка контрольной Ñуммы текÑта VIEW провалилаÑÑŒ"
+ ukr "Перевірка контрольної Ñуми текÑту VIEW не пройшла"
+ER_VIEW_MULTIUPDATE
+ eng "Can not modify more than one base table through a join view '%-.192s.%-.192s'"
+ ger "Kann nicht mehr als eine Basistabelle über Join-View '%-.192s.%-.192s' ändern"
+ rus "ÐÐµÐ»ÑŒÐ·Ñ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ больше чем одну базовую таблицу иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð¼Ð½Ð¾Ð³Ð¾Ñ‚Ð°Ð±Ð»Ð¸Ñ‡Ð½Ñ‹Ð¹ VIEW '%-.192s.%-.192s'"
+ ukr "Ðеможливо оновити більш ниж одну базову таблицю выкориÑтовуючи VIEW '%-.192s.%-.192s', що міÑтіть декілька таблиць"
+ER_VIEW_NO_INSERT_FIELD_LIST
+ eng "Can not insert into join view '%-.192s.%-.192s' without fields list"
+ ger "Kann nicht ohne Feldliste in Join-View '%-.192s.%-.192s' einfügen"
+ rus "ÐÐµÐ»ÑŒÐ·Ñ Ð²ÑтавлÑть запиÑи в многотабличный VIEW '%-.192s.%-.192s' без ÑпиÑка полей"
+ ukr "Ðеможливо уÑтавити Ñ€Ñдки у VIEW '%-.192s.%-.192s', що міÑтить декілька таблиць, без ÑпиÑку Ñтовбців"
+ER_VIEW_DELETE_MERGE_VIEW
+ eng "Can not delete from join view '%-.192s.%-.192s'"
+ ger "Kann nicht aus Join-View '%-.192s.%-.192s' löschen"
+ rus "ÐÐµÐ»ÑŒÐ·Ñ ÑƒÐ´Ð°Ð»Ñть из многотабличного VIEW '%-.192s.%-.192s'"
+ ukr "Ðеможливо видалити Ñ€Ñдки у VIEW '%-.192s.%-.192s', що міÑтить декілька таблиць"
+ER_CANNOT_USER
+ eng "Operation %s failed for %.256s"
+ ger "Operation %s schlug fehl für %.256s"
+ norwegian-ny "Operation %s failed for '%.256s'"
+ER_XAER_NOTA XAE04
+ eng "XAER_NOTA: Unknown XID"
+ ger "XAER_NOTA: Unbekannte XID"
+ER_XAER_INVAL XAE05
+ eng "XAER_INVAL: Invalid arguments (or unsupported command)"
+ ger "XAER_INVAL: Ungültige Argumente (oder nicht unterstützter Befehl)"
+ER_XAER_RMFAIL XAE07
+ eng "XAER_RMFAIL: The command cannot be executed when global transaction is in the %.64s state"
+ ger "XAER_RMFAIL: DEr Befehl kann nicht ausgeführt werden, wenn die globale Transaktion im Zustand %.64s ist"
+ rus "XAER_RMFAIL: Ñту команду Ð½ÐµÐ»ÑŒÐ·Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½Ñть когда Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð² ÑоÑтоÑнии '%.64s'"
+ER_XAER_OUTSIDE XAE09
+ eng "XAER_OUTSIDE: Some work is done outside global transaction"
+ ger "XAER_OUTSIDE: Einige Arbeiten werden außerhalb der globalen Transaktion verrichtet"
+ER_XAER_RMERR XAE03
+ eng "XAER_RMERR: Fatal error occurred in the transaction branch - check your data for consistency"
+ ger "XAER_RMERR: Schwerwiegender Fehler im Transaktionszweig - prüfen Sie Ihre Daten auf Konsistenz"
+ER_XA_RBROLLBACK XA100
+ eng "XA_RBROLLBACK: Transaction branch was rolled back"
+ ger "XA_RBROLLBACK: Transaktionszweig wurde zurückgerollt"
+ER_NONEXISTING_PROC_GRANT 42000
+ eng "There is no such grant defined for user '%-.48s' on host '%-.64s' on routine '%-.192s'"
+ ger "Es gibt diese Berechtigung für Benutzer '%-.48s' auf Host '%-.64s' für Routine '%-.192s' nicht"
+ER_PROC_AUTO_GRANT_FAIL
+ eng "Failed to grant EXECUTE and ALTER ROUTINE privileges"
+ ger "Gewährung von EXECUTE- und ALTER-ROUTINE-Rechten fehlgeschlagen"
+ER_PROC_AUTO_REVOKE_FAIL
+ eng "Failed to revoke all privileges to dropped routine"
+ ger "Rücknahme aller Rechte für die gelöschte Routine fehlgeschlagen"
+ER_DATA_TOO_LONG 22001
+ eng "Data too long for column '%s' at row %lu"
+ ger "Daten zu lang für Feld '%s' in Zeile %lu"
+ER_SP_BAD_SQLSTATE 42000
+ eng "Bad SQLSTATE: '%s'"
+ ger "Ungültiger SQLSTATE: '%s'"
+ER_STARTUP
+ eng "%s: ready for connections.\nVersion: '%s' socket: '%s' port: %d %s"
+ ger "%s: bereit für Verbindungen.\nVersion: '%s' Socket: '%s' Port: %d %s"
+ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR
+ eng "Can't load value from file with fixed size rows to variable"
+ ger "Kann Wert aus Datei mit Zeilen fester Größe nicht in Variable laden"
+ER_CANT_CREATE_USER_WITH_GRANT 42000
+ eng "You are not allowed to create a user with GRANT"
+ ger "Sie dürfen keinen Benutzer mit GRANT anlegen"
+ER_WRONG_VALUE_FOR_TYPE
+ eng "Incorrect %-.32s value: '%-.128s' for function %-.32s"
+ ger "Falscher %-.32s-Wert: '%-.128s' für Funktion %-.32s"
+ER_TABLE_DEF_CHANGED
+ eng "Table definition has changed, please retry transaction"
+ ger "Tabellendefinition wurde geändert, bitte starten Sie die Transaktion neu"
+ER_SP_DUP_HANDLER 42000
+ eng "Duplicate handler declared in the same block"
+ ger "Doppelter Handler im selben Block deklariert"
+ER_SP_NOT_VAR_ARG 42000
+ eng "OUT or INOUT argument %d for routine %s is not a variable or NEW pseudo-variable in BEFORE trigger"
+ ger "OUT- oder INOUT-Argument %d für Routine %s ist keine Variable"
+ER_SP_NO_RETSET 0A000
+ eng "Not allowed to return a result set from a %s"
+ ger "Rückgabe einer Ergebnismenge aus einer %s ist nicht erlaubt"
+ER_CANT_CREATE_GEOMETRY_OBJECT 22003
+ eng "Cannot get geometry object from data you send to the GEOMETRY field"
+ ger "Kann kein Geometrieobjekt aus den Daten machen, die Sie dem GEOMETRY-Feld übergeben haben"
+ER_FAILED_ROUTINE_BREAK_BINLOG
+ eng "A routine failed and has neither NO SQL nor READS SQL DATA in its declaration and binary logging is enabled; if non-transactional tables were updated, the binary log will miss their changes"
+ ger "Eine Routine, die weder NO SQL noch READS SQL DATA in der Deklaration hat, schlug fehl und Binärlogging ist aktiv. Wenn Nicht-Transaktions-Tabellen aktualisiert wurden, enthält das Binärlog ihre Änderungen nicht"
+ER_BINLOG_UNSAFE_ROUTINE
+ eng "This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)"
+ ger "Diese Routine hat weder DETERMINISTIC, NO SQL noch READS SQL DATA in der Deklaration und Binärlogging ist aktiv (*vielleicht* sollten Sie die weniger sichere Variable log_bin_trust_function_creators verwenden)"
+ER_BINLOG_CREATE_ROUTINE_NEED_SUPER
+ eng "You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)"
+ ger "Sie haben keine SUPER-Berechtigung und Binärlogging ist aktiv (*vielleicht* sollten Sie die weniger sichere Variable log_bin_trust_function_creators verwenden)"
+ER_EXEC_STMT_WITH_OPEN_CURSOR
+ eng "You can't execute a prepared statement which has an open cursor associated with it. Reset the statement to re-execute it."
+ ger "Sie können keine vorbereitete Anweisung ausführen, die mit einem geöffneten Cursor verknüpft ist. Setzen Sie die Anweisung zurück, um sie neu auszuführen"
+ER_STMT_HAS_NO_OPEN_CURSOR
+ eng "The statement (%lu) has no open cursor."
+ ger "Die Anweisung (%lu) hat keinen geöffneten Cursor"
+ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
+ eng "Explicit or implicit commit is not allowed in stored function or trigger."
+ ger "Explizites oder implizites Commit ist in gespeicherten Funktionen und in Triggern nicht erlaubt"
+ER_NO_DEFAULT_FOR_VIEW_FIELD
+ eng "Field of view '%-.192s.%-.192s' underlying table doesn't have a default value"
+ ger "Ein Feld der dem View '%-.192s.%-.192s' zugrundeliegenden Tabelle hat keinen Vorgabewert"
+ER_SP_NO_RECURSION
+ eng "Recursive stored functions and triggers are not allowed."
+ ger "Rekursive gespeicherte Routinen und Triggers sind nicht erlaubt"
+ER_TOO_BIG_SCALE 42000 S1009
+ eng "Too big scale %u specified for '%-.192s'. Maximum is %lu."
+ ger "Zu großer Skalierungsfaktor %u für '%-.192s' angegeben. Maximum ist %lu"
+ER_TOO_BIG_PRECISION 42000 S1009
+ eng "Too big precision %u specified for '%-.192s'. Maximum is %lu."
+ ger "Zu große Genauigkeit %u für '%-.192s' angegeben. Maximum ist %lu"
+ER_M_BIGGER_THAN_D 42000 S1009
+ eng "For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column '%-.192s')."
+ ger "Für FLOAT(M,D), DOUBLE(M,D) oder DECIMAL(M,D) muss M >= D sein (Feld '%-.192s')"
+ER_WRONG_LOCK_OF_SYSTEM_TABLE
+ eng "You can't combine write-locking of system tables with other tables or lock types"
+ ger "Sie können Schreibsperren auf der Systemtabelle nicht mit anderen Tabellen kombinieren"
+ER_CONNECT_TO_FOREIGN_DATA_SOURCE
+ eng "Unable to connect to foreign data source: %.64s"
+ ger "Kann nicht mit Fremddatenquelle verbinden: %.64s"
+ER_QUERY_ON_FOREIGN_DATA_SOURCE
+ eng "There was a problem processing the query on the foreign data source. Data source error: %-.64s"
+ ger "Bei der Verarbeitung der Abfrage ist in der Fremddatenquelle ein Problem aufgetreten. Datenquellenfehlermeldung: %-.64s"
+ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST
+ eng "The foreign data source you are trying to reference does not exist. Data source error: %-.64s"
+ ger "Die Fremddatenquelle, auf die Sie zugreifen wollen, existiert nicht. Datenquellenfehlermeldung: %-.64s"
+ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE
+ eng "Can't create federated table. The data source connection string '%-.64s' is not in the correct format"
+ ger "Kann föderierte Tabelle nicht erzeugen. Der Datenquellen-Verbindungsstring '%-.64s' hat kein korrektes Format"
+ER_FOREIGN_DATA_STRING_INVALID
+ eng "The data source connection string '%-.64s' is not in the correct format"
+ ger "Der Datenquellen-Verbindungsstring '%-.64s' hat kein korrektes Format"
+ER_CANT_CREATE_FEDERATED_TABLE
+ eng "Can't create federated table. Foreign data src error: %-.64s"
+ ger "Kann föderierte Tabelle nicht erzeugen. Fremddatenquellenfehlermeldung: %-.64s"
+ER_TRG_IN_WRONG_SCHEMA
+ eng "Trigger in wrong schema"
+ ger "Trigger im falschen Schema"
+ER_STACK_OVERRUN_NEED_MORE
+ eng "Thread stack overrun: %ld bytes used of a %ld byte stack, and %ld bytes needed. Use 'mysqld --thread_stack=#' to specify a bigger stack."
+ ger "Thread-Stack-Überlauf: %ld Bytes eines %ld-Byte-Stacks in Verwendung, und %ld Bytes benötigt. Verwenden Sie 'mysqld --thread_stack=#', um einen größeren Stack anzugeben"
+ jpn "スレッドスタックä¸è¶³ã§ã™(使用: %ld ; サイズ: %ld ; è¦æ±‚: %ld)。より大ãã„値㧠'mysqld --thread_stack=#' ã®æŒ‡å®šã‚’ã—ã¦ãã ã•ã„。"
+ER_TOO_LONG_BODY 42000 S1009
+ eng "Routine body for '%-.100s' is too long"
+ ger "Routinen-Body für '%-.100s' ist zu lang"
+ER_WARN_CANT_DROP_DEFAULT_KEYCACHE
+ eng "Cannot drop default keycache"
+ ger "Der vorgabemäßige Schlüssel-Cache kann nicht gelöscht werden"
+ER_TOO_BIG_DISPLAYWIDTH 42000 S1009
+ eng "Display width out of range for '%-.192s' (max = %lu)"
+ ger "Anzeigebreite außerhalb des zulässigen Bereichs für '%-.192s' (Maximum = %lu)"
+ER_XAER_DUPID XAE08
+ eng "XAER_DUPID: The XID already exists"
+ ger "XAER_DUPID: Die XID existiert bereits"
+ER_DATETIME_FUNCTION_OVERFLOW 22008
+ eng "Datetime function: %-.32s field overflow"
+ ger "Datetime-Funktion: %-.32s Feldüberlauf"
+ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
+ eng "Can't update table '%-.192s' in stored function/trigger because it is already used by statement which invoked this stored function/trigger."
+ ger "Kann Tabelle '%-.192s' in gespeicherter Funktion oder Trigger nicht aktualisieren, weil sie bereits von der Anweisung verwendet wird, die diese gespeicherte Funktion oder den Trigger aufrief"
+ER_VIEW_PREVENT_UPDATE
+ eng "The definition of table '%-.192s' prevents operation %.192s on table '%-.192s'."
+ ger "Die Definition der Tabelle '%-.192s' verhindert die Operation %.192s auf Tabelle '%-.192s'"
+ER_PS_NO_RECURSION
+ eng "The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner"
+ ger "Die vorbereitete Anweisung enthält einen Aufruf einer gespeicherten Routine, die auf eben dieselbe Anweisung verweist. Es ist nicht erlaubt, eine vorbereitete Anweisung in solch rekursiver Weise auszuführen"
+ER_SP_CANT_SET_AUTOCOMMIT
+ eng "Not allowed to set autocommit from a stored function or trigger"
+ ger "Es ist nicht erlaubt, innerhalb einer gespeicherten Funktion oder eines Triggers AUTOCOMMIT zu setzen"
+ER_MALFORMED_DEFINER 0L000
+ eng "Invalid definer"
+ER_VIEW_FRM_NO_USER
+ eng "View '%-.192s'.'%-.192s' has no definer information (old table format). Current user is used as definer. Please recreate the view!"
+ ger "View '%-.192s'.'%-.192s' hat keine Definierer-Information (altes Tabellenformat). Der aktuelle Benutzer wird als Definierer verwendet. Bitte erstellen Sie den View neu"
+ER_VIEW_OTHER_USER
+ eng "You need the SUPER privilege for creation view with '%-.192s'@'%-.192s' definer"
+ ger "Sie brauchen die SUPER-Berechtigung, um einen View mit dem Definierer '%-.192s'@'%-.192s' zu erzeugen"
+ER_NO_SUCH_USER
+ eng "The user specified as a definer ('%-.64s'@'%-.64s') does not exist"
+ ger "Der als Definierer angegebene Benutzer ('%-.64s'@'%-.64s') existiert nicht"
+ER_FORBID_SCHEMA_CHANGE
+ eng "Changing schema from '%-.192s' to '%-.192s' is not allowed."
+ ger "Wechsel des Schemas von '%-.192s' auf '%-.192s' ist nicht erlaubt"
+ER_ROW_IS_REFERENCED_2 23000
+ eng "Cannot delete or update a parent row: a foreign key constraint fails (%.192s)"
+ ger "Kann Eltern-Zeile nicht löschen oder aktualisieren: eine Fremdschlüsselbedingung schlägt fehl (%.192s)"
+ER_NO_REFERENCED_ROW_2 23000
+ eng "Cannot add or update a child row: a foreign key constraint fails (%.192s)"
+ ger "Kann Kind-Zeile nicht hinzufügen oder aktualisieren: eine Fremdschlüsselbedingung schlägt fehl (%.192s)"
+ER_SP_BAD_VAR_SHADOW 42000
+ eng "Variable '%-.64s' must be quoted with `...`, or renamed"
+ ger "Variable '%-.64s' muss mit `...` geschützt oder aber umbenannt werden"
+ER_TRG_NO_DEFINER
+ eng "No definer attribute for trigger '%-.192s'.'%-.192s'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger."
+ ger "Kein Definierer-Attribut für Trigger '%-.192s'.'%-.192s'. Der Trigger wird mit der Autorisierung des Aufrufers aktiviert, der möglicherweise keine zureichenden Berechtigungen hat. Bitte legen Sie den Trigger neu an."
+ER_OLD_FILE_FORMAT
+ eng "'%-.192s' has an old format, you should re-create the '%s' object(s)"
+ ger "'%-.192s' hat altes Format, Sie sollten die '%s'-Objekt(e) neu erzeugen"
+ER_SP_RECURSION_LIMIT
+ eng "Recursive limit %d (as set by the max_sp_recursion_depth variable) was exceeded for routine %.192s"
+ ger "Rekursionsgrenze %d (durch Variable max_sp_recursion_depth gegeben) wurde für Routine %.192s überschritten"
+ER_SP_PROC_TABLE_CORRUPT
+ eng "Failed to load routine %-.192s. The table mysql.proc is missing, corrupt, or contains bad data (internal code %d)"
+ ger "Routine %-.192s konnte nicht geladen werden. Die Tabelle mysql.proc fehlt, ist beschädigt, oder enthält fehlerhaften Daten (interner Code: %d)"
+ER_SP_WRONG_NAME 42000
+ eng "Incorrect routine name '%-.192s'"
+ ger "Ungültiger Routinenname '%-.192s'"
+ER_TABLE_NEEDS_UPGRADE
+ eng "Table upgrade required. Please do \"REPAIR TABLE `%-.32s`\" or dump/reload to fix it!"
+ ger "Tabellenaktualisierung erforderlich. Bitte zum Reparieren \"REPAIR TABLE `%-.32s`\" eingeben!"
+ER_SP_NO_AGGREGATE 42000
+ eng "AGGREGATE is not supported for stored functions"
+ ger "AGGREGATE wird bei gespeicherten Funktionen nicht unterstützt"
+ER_MAX_PREPARED_STMT_COUNT_REACHED 42000
+ eng "Can't create more than max_prepared_stmt_count statements (current value: %lu)"
+ ger "Kann nicht mehr Anweisungen als max_prepared_stmt_count erzeugen (aktueller Wert: %lu)"
+ER_VIEW_RECURSIVE
+ eng "`%-.192s`.`%-.192s` contains view recursion"
+ ger "`%-.192s`.`%-.192s` enthält View-Rekursion"
+ER_NON_GROUPING_FIELD_USED 42000
+ eng "Non-grouping field '%-.192s' is used in %-.64s clause"
+ ger "In der %-.192s-Klausel wird das die Nicht-Gruppierungsspalte '%-.64s' verwendet"
+ER_TABLE_CANT_HANDLE_SPKEYS
+ eng "The storage engine %s doesn't support SPATIAL indexes"
+ ger "Der verwendete Tabellentyp (%s) unterstützt keine SPATIAL-Indizes"
+ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA
+ eng "Triggers can not be created on system tables"
+ ger "Trigger können nicht auf Systemtabellen erzeugt werden"
+ER_REMOVED_SPACES
+ eng "Leading spaces are removed from name '%s'"
+ ger "Führende Leerzeichen werden aus dem Namen '%s' entfernt"
+ER_AUTOINC_READ_FAILED
+ eng "Failed to read auto-increment value from storage engine"
+ ger "Lesen des Autoincrement-Werts von der Speicher-Engine fehlgeschlagen"
+ER_USERNAME
+ eng "user name"
+ ger "Benutzername"
+ER_HOSTNAME
+ eng "host name"
+ ger "Hostname"
+ER_WRONG_STRING_LENGTH
+ eng "String '%-.70s' is too long for %s (should be no longer than %d)"
+ ger "String '%-.70s' ist zu lang für %s (sollte nicht länger sein als %d)"
+ER_NON_INSERTABLE_TABLE
+ eng "The target table %-.100s of the %s is not insertable-into"
+ ger "Die Zieltabelle %-.100s von %s ist nicht einfügbar"
+ jpn "対象表 %-.100s ã¯æŒ¿å…¥å¯èƒ½ã§ã¯ãªã„ã®ã§ã€%s を行ãˆã¾ã›ã‚“。"
+ER_ADMIN_WRONG_MRG_TABLE
+ eng "Table '%-.64s' is differently defined or of non-MyISAM type or doesn't exist"
+ ger "Tabelle '%-.64s' ist unterschiedlich definiert, nicht vom Typ MyISAM oder existiert nicht"
+ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT
+ eng "Too high level of nesting for select"
+ ger "Zu tief verschachtelte SELECT-Anweisungen"
+ER_NAME_BECOMES_EMPTY
+ eng "Name '%-.64s' has become ''"
+ ger "Name '%-.64s' wurde zu ''"
+ER_AMBIGUOUS_FIELD_TERM
+ eng "First character of the FIELDS TERMINATED string is ambiguous; please use non-optional and non-empty FIELDS ENCLOSED BY"
+ ger "Das erste Zeichen der Zeichenkette FIELDS TERMINATED ist mehrdeutig; bitte benutzen Sie nicht optionale und nicht leere FIELDS ENCLOSED BY"
+ER_FOREIGN_SERVER_EXISTS
+ eng "The foreign server, %s, you are trying to create already exists."
+ ger "Der entfernte Server %s, den Sie versuchen zu erzeugen, existiert schon."
+ER_FOREIGN_SERVER_DOESNT_EXIST
+ eng "The foreign server name you are trying to reference does not exist. Data source error: %-.64s"
+ ger "Die externe Verbindung, auf die Sie zugreifen wollen, existiert nicht. Datenquellenfehlermeldung: %-.64s"
+ER_ILLEGAL_HA_CREATE_OPTION
+ eng "Table storage engine '%-.64s' does not support the create option '%.64s'"
+ ger "Speicher-Engine '%-.64s' der Tabelle unterstützt die Option '%.64s' nicht"
+ER_PARTITION_REQUIRES_VALUES_ERROR
+ eng "Syntax error: %-.64s PARTITIONING requires definition of VALUES %-.64s for each partition"
+ ger "Fehler in der SQL-Syntax: %-.64s-PARTITIONierung erfordert Definition von VALUES %-.64s für jede Partition"
+ swe "Syntaxfel: %-.64s PARTITIONering kräver definition av VALUES %-.64s för varje partition"
+ER_PARTITION_WRONG_VALUES_ERROR
+ eng "Only %-.64s PARTITIONING can use VALUES %-.64s in partition definition"
+ ger "Nur %-.64s-PARTITIONierung kann VALUES %-.64s in der Partitionsdefinition verwenden"
+ swe "Endast %-.64s partitionering kan använda VALUES %-.64s i definition av partitionen"
+ER_PARTITION_MAXVALUE_ERROR
+ eng "MAXVALUE can only be used in last partition definition"
+ ger "MAXVALUE kann nur für die Definition der letzten Partition verwendet werden"
+ swe "MAXVALUE kan bara användas i definitionen av den sista partitionen"
+ER_PARTITION_SUBPARTITION_ERROR
+ eng "Subpartitions can only be hash partitions and by key"
+ ger "Unterpartitionen dürfen nur HASH- oder KEY-Partitionen sein"
+ swe "Subpartitioner kan bara vara hash och key partitioner"
+ER_PARTITION_SUBPART_MIX_ERROR
+ eng "Must define subpartitions on all partitions if on one partition"
+ ger "Wenn Sie Unterpartitionen auf einer Partition definieren, müssen Sie das für alle Partitionen tun"
+ swe "Subpartitioner måste definieras på alla partitioner om på en"
+ER_PARTITION_WRONG_NO_PART_ERROR
+ eng "Wrong number of partitions defined, mismatch with previous setting"
+ ger "Falsche Anzahl von Partitionen definiert, stimmt nicht mit vorherigen Einstellungen überein"
+ swe "Antal partitioner definierade och antal partitioner är inte lika"
+ER_PARTITION_WRONG_NO_SUBPART_ERROR
+ eng "Wrong number of subpartitions defined, mismatch with previous setting"
+ ger "Falsche Anzahl von Unterpartitionen definiert, stimmt nicht mit vorherigen Einstellungen überein"
+ swe "Antal subpartitioner definierade och antal subpartitioner är inte lika"
+ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR
+ eng "Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed"
+ ger "Konstante oder Random-Ausdrücke in (Unter-)Partitionsfunktionen sind nicht erlaubt"
+ swe "Konstanta uttryck eller slumpmässiga uttryck är inte tillåtna (sub)partitioneringsfunktioner"
+ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR
+ eng "Expression in RANGE/LIST VALUES must be constant"
+ ger "Ausdrücke in RANGE/LIST VALUES müssen konstant sein"
+ swe "Uttryck i RANGE/LIST VALUES måste vara ett konstant uttryck"
+ER_FIELD_NOT_FOUND_PART_ERROR
+ eng "Field in list of fields for partition function not found in table"
+ ger "Felder in der Feldliste der Partitionierungsfunktion wurden in der Tabelle nicht gefunden"
+ swe "Fält i listan av fält för partitionering med key inte funnen i tabellen"
+ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR
+ eng "List of fields is only allowed in KEY partitions"
+ ger "Eine Feldliste ist nur in KEY-Partitionen erlaubt"
+ swe "En lista av fält är endast tillåtet för KEY partitioner"
+ER_INCONSISTENT_PARTITION_INFO_ERROR
+ eng "The partition info in the frm file is not consistent with what can be written into the frm file"
+ ger "Die Partitionierungsinformationen in der frm-Datei stimmen nicht mit dem überein, was in die frm-Datei geschrieben werden kann"
+ swe "Partitioneringsinformationen i frm-filen är inte konsistent med vad som kan skrivas i frm-filen"
+ER_PARTITION_FUNC_NOT_ALLOWED_ERROR
+ eng "The %-.192s function returns the wrong type"
+ ger "Die %-.192s-Funktion gibt einen falschen Typ zurück"
+ swe "%-.192s-funktionen returnerar felaktig typ"
+ER_PARTITIONS_MUST_BE_DEFINED_ERROR
+ eng "For %-.64s partitions each partition must be defined"
+ ger "Für %-.64s-Partitionen muss jede Partition definiert sein"
+ swe "För %-.64s partitionering så måste varje partition definieras"
+ER_RANGE_NOT_INCREASING_ERROR
+ eng "VALUES LESS THAN value must be strictly increasing for each partition"
+ ger "Werte in VALUES LESS THAN müssen für jede Partition strikt aufsteigend sein"
+ swe "Värden i VALUES LESS THAN måste vara strikt växande för varje partition"
+ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR
+ eng "VALUES value must be of same type as partition function"
+ ger "VALUES-Werte müssen vom selben Typ wie die Partitionierungsfunktion sein"
+ swe "Värden i VALUES måste vara av samma typ som partitioneringsfunktionen"
+ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR
+ eng "Multiple definition of same constant in list partitioning"
+ ger "Mehrfachdefinition derselben Konstante bei Listen-Partitionierung"
+ swe "Multipel definition av samma konstant i list partitionering"
+ER_PARTITION_ENTRY_ERROR
+ eng "Partitioning can not be used stand-alone in query"
+ ger "Partitionierung kann in einer Abfrage nicht alleinstehend benutzt werden"
+ swe "Partitioneringssyntax kan inte användas på egen hand i en SQL-fråga"
+ER_MIX_HANDLER_ERROR
+ eng "The mix of handlers in the partitions is not allowed in this version of MariaDB"
+ ger "Das Vermischen von Handlern in Partitionen ist in dieser Version von MariaDB nicht erlaubt"
+ swe "Denna mix av lagringsmotorer är inte tillåten i denna version av MariaDB"
+ER_PARTITION_NOT_DEFINED_ERROR
+ eng "For the partitioned engine it is necessary to define all %-.64s"
+ ger "Für die partitionierte Engine müssen alle %-.64s definiert sein"
+ swe "För partitioneringsmotorn så är det nödvändigt att definiera alla %-.64s"
+ER_TOO_MANY_PARTITIONS_ERROR
+ eng "Too many partitions (including subpartitions) were defined"
+ ger "Es wurden zu vielen Partitionen (einschließlich Unterpartitionen) definiert"
+ swe "För många partitioner (inkluderande subpartitioner) definierades"
+ER_SUBPARTITION_ERROR
+ eng "It is only possible to mix RANGE/LIST partitioning with HASH/KEY partitioning for subpartitioning"
+ ger "RANGE/LIST-Partitionierung kann bei Unterpartitionen nur zusammen mit HASH/KEY-Partitionierung verwendet werden"
+ swe "Det är endast möjligt att blanda RANGE/LIST partitionering med HASH/KEY partitionering för subpartitionering"
+ER_CANT_CREATE_HANDLER_FILE
+ eng "Failed to create specific handler file"
+ ger "Erzeugen einer spezifischen Handler-Datei fehlgeschlagen"
+ swe "Misslyckades med att skapa specifik fil i lagringsmotor"
+ER_BLOB_FIELD_IN_PART_FUNC_ERROR
+ eng "A BLOB field is not allowed in partition function"
+ ger "In der Partitionierungsfunktion sind BLOB-Spalten nicht erlaubt"
+ swe "Ett BLOB-fält är inte tillåtet i partitioneringsfunktioner"
+ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF
+ eng "A %-.192s must include all columns in the table's partitioning function"
+ER_NO_PARTS_ERROR
+ eng "Number of %-.64s = 0 is not an allowed value"
+ ger "Eine Anzahl von %-.64s = 0 ist kein erlaubter Wert"
+ swe "Antal %-.64s = 0 är inte ett tillåten värde"
+ER_PARTITION_MGMT_ON_NONPARTITIONED
+ eng "Partition management on a not partitioned table is not possible"
+ ger "Partitionsverwaltung einer nicht partitionierten Tabelle ist nicht möglich"
+ swe "Partitioneringskommando på en opartitionerad tabell är inte möjligt"
+ER_FOREIGN_KEY_ON_PARTITIONED
+ eng "Foreign key clause is not yet supported in conjunction with partitioning"
+ ger "Fremdschlüssel-Beschränkungen sind im Zusammenhang mit Partitionierung nicht zulässig"
+ swe "Foreign key klausul är inte ännu implementerad i kombination med partitionering"
+ER_DROP_PARTITION_NON_EXISTENT
+ eng "Error in list of partitions to %-.64s"
+ ger "Fehler in der Partitionsliste bei %-.64s"
+ swe "Fel i listan av partitioner att %-.64s"
+ER_DROP_LAST_PARTITION
+ eng "Cannot remove all partitions, use DROP TABLE instead"
+ ger "Es lassen sich nicht sämtliche Partitionen löschen, benutzen Sie statt dessen DROP TABLE"
+ swe "Det är inte tillåtet att ta bort alla partitioner, använd DROP TABLE istället"
+ER_COALESCE_ONLY_ON_HASH_PARTITION
+ eng "COALESCE PARTITION can only be used on HASH/KEY partitions"
+ ger "COALESCE PARTITION kann nur auf HASH- oder KEY-Partitionen benutzt werden"
+ swe "COALESCE PARTITION kan bara användas på HASH/KEY partitioner"
+ER_REORG_HASH_ONLY_ON_SAME_NO
+ eng "REORGANIZE PARTITION can only be used to reorganize partitions not to change their numbers"
+ ger "REORGANIZE PARTITION kann nur zur Reorganisation von Partitionen verwendet werden, nicht, um ihre Nummern zu ändern"
+ swe "REORGANIZE PARTITION kan bara användas för att omorganisera partitioner, inte för att ändra deras antal"
+ER_REORG_NO_PARAM_ERROR
+ eng "REORGANIZE PARTITION without parameters can only be used on auto-partitioned tables using HASH PARTITIONs"
+ ger "REORGANIZE PARTITION ohne Parameter kann nur für auto-partitionierte Tabellen verwendet werden, die HASH-Partitionierung benutzen"
+ swe "REORGANIZE PARTITION utan parametrar kan bara användas på auto-partitionerade tabeller som använder HASH partitionering"
+ER_ONLY_ON_RANGE_LIST_PARTITION
+ eng "%-.64s PARTITION can only be used on RANGE/LIST partitions"
+ ger "%-.64s PARTITION kann nur für RANGE- oder LIST-Partitionen verwendet werden"
+ swe "%-.64s PARTITION kan bara användas på RANGE/LIST-partitioner"
+ER_ADD_PARTITION_SUBPART_ERROR
+ eng "Trying to Add partition(s) with wrong number of subpartitions"
+ ger "Es wurde versucht, eine oder mehrere Partitionen mit der falschen Anzahl von Unterpartitionen hinzuzufügen"
+ swe "ADD PARTITION med fel antal subpartitioner"
+ER_ADD_PARTITION_NO_NEW_PARTITION
+ eng "At least one partition must be added"
+ ger "Es muss zumindest eine Partition hinzugefügt werden"
+ swe "Åtminstone en partition måste läggas till vid ADD PARTITION"
+ER_COALESCE_PARTITION_NO_PARTITION
+ eng "At least one partition must be coalesced"
+ ger "Zumindest eine Partition muss mit COALESCE PARTITION zusammengefügt werden"
+ swe "Åtminstone en partition måste slås ihop vid COALESCE PARTITION"
+ER_REORG_PARTITION_NOT_EXIST
+ eng "More partitions to reorganize than there are partitions"
+ ger "Es wurde versucht, mehr Partitionen als vorhanden zu reorganisieren"
+ swe "Fler partitioner att reorganisera än det finns partitioner"
+ER_SAME_NAME_PARTITION
+ eng "Duplicate partition name %-.192s"
+ ger "Doppelter Partitionsname: %-.192s"
+ swe "Duplicerat partitionsnamn %-.192s"
+ER_NO_BINLOG_ERROR
+ eng "It is not allowed to shut off binlog on this command"
+ ger "Es es nicht erlaubt, bei diesem Befehl binlog abzuschalten"
+ swe "Det är inte tillåtet att stänga av binlog på detta kommando"
+ER_CONSECUTIVE_REORG_PARTITIONS
+ eng "When reorganizing a set of partitions they must be in consecutive order"
+ ger "Bei der Reorganisation eines Satzes von Partitionen müssen diese in geordneter Reihenfolge vorliegen"
+ swe "När ett antal partitioner omorganiseras måste de vara i konsekutiv ordning"
+ER_REORG_OUTSIDE_RANGE
+ eng "Reorganize of range partitions cannot change total ranges except for last partition where it can extend the range"
+ ger "Die Reorganisation von RANGE-Partitionen kann Gesamtbereiche nicht verändern, mit Ausnahme der letzten Partition, die den Bereich erweitern kann"
+ swe "Reorganisering av rangepartitioner kan inte ändra den totala intervallet utom för den sista partitionen där intervallet kan utökas"
+ER_PARTITION_FUNCTION_FAILURE
+ eng "Partition function not supported in this version for this handler"
+ ger "Partitionsfunktion in dieser Version dieses Handlers nicht unterstützt"
+ER_PART_STATE_ERROR
+ eng "Partition state cannot be defined from CREATE/ALTER TABLE"
+ ger "Partitionszustand kann nicht von CREATE oder ALTER TABLE aus definiert werden"
+ swe "Partition state kan inte definieras från CREATE/ALTER TABLE"
+ER_LIMITED_PART_RANGE
+ eng "The %-.64s handler only supports 32 bit integers in VALUES"
+ ger "Der Handler %-.64s unterstützt in VALUES nur 32-Bit-Integers"
+ swe "%-.64s stödjer endast 32 bitar i integers i VALUES"
+ER_PLUGIN_IS_NOT_LOADED
+ eng "Plugin '%-.192s' is not loaded"
+ ger "Plugin '%-.192s' ist nicht geladen"
+ER_WRONG_VALUE
+ eng "Incorrect %-.32s value: '%-.128s'"
+ ger "Falscher %-.32s-Wert: '%-.128s'"
+ER_NO_PARTITION_FOR_GIVEN_VALUE
+ eng "Table has no partition for value %-.64s"
+ ger "Tabelle hat für den Wert %-.64s keine Partition"
+ER_FILEGROUP_OPTION_ONLY_ONCE
+ eng "It is not allowed to specify %s more than once"
+ ger "%s darf nicht mehr als einmal angegegeben werden"
+ER_CREATE_FILEGROUP_FAILED
+ eng "Failed to create %s"
+ ger "Anlegen von %s fehlgeschlagen"
+ER_DROP_FILEGROUP_FAILED
+ eng "Failed to drop %s"
+ ger "Löschen von %s fehlgeschlagen"
+ER_TABLESPACE_AUTO_EXTEND_ERROR
+ eng "The handler doesn't support autoextend of tablespaces"
+ ger "Der Handler unterstützt keine automatische Erweiterung (Autoextend) von Tablespaces"
+ER_WRONG_SIZE_NUMBER
+ eng "A size parameter was incorrectly specified, either number or on the form 10M"
+ ger "Ein Größen-Parameter wurde unkorrekt angegeben, muss entweder Zahl sein oder im Format 10M"
+ER_SIZE_OVERFLOW_ERROR
+ eng "The size number was correct but we don't allow the digit part to be more than 2 billion"
+ ger "Die Zahl für die Größe war korrekt, aber der Zahlanteil darf nicht größer als 2 Milliarden sein"
+ER_ALTER_FILEGROUP_FAILED
+ eng "Failed to alter: %s"
+ ger "Änderung von %s fehlgeschlagen"
+ER_BINLOG_ROW_LOGGING_FAILED
+ eng "Writing one row to the row-based binary log failed"
+ ger "Schreiben einer Zeilen ins zeilenbasierte Binärlog fehlgeschlagen"
+ER_BINLOG_ROW_WRONG_TABLE_DEF
+ eng "Table definition on master and slave does not match: %s"
+ ger "Tabellendefinition auf Master und Slave stimmt nicht überein: %s"
+ER_BINLOG_ROW_RBR_TO_SBR
+ eng "Slave running with --log-slave-updates must use row-based binary logging to be able to replicate row-based binary log events"
+ ger "Slave, die mit --log-slave-updates laufen, müssen zeilenbasiertes Loggen verwenden, um zeilenbasierte Binärlog-Ereignisse loggen zu können"
+ER_EVENT_ALREADY_EXISTS
+ eng "Event '%-.192s' already exists"
+ ger "Event '%-.192s' existiert bereits"
+ER_EVENT_STORE_FAILED
+ eng "Failed to store event %s. Error code %M from storage engine."
+ ger "Speichern von Event %s fehlgeschlagen. Fehlercode der Speicher-Engine: %M"
+ER_EVENT_DOES_NOT_EXIST
+ eng "Unknown event '%-.192s'"
+ ger "Unbekanntes Event '%-.192s'"
+ER_EVENT_CANT_ALTER
+ eng "Failed to alter event '%-.192s'"
+ ger "Ändern des Events '%-.192s' fehlgeschlagen"
+ER_EVENT_DROP_FAILED
+ eng "Failed to drop %s"
+ ger "Löschen von %s fehlgeschlagen"
+ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG
+ eng "INTERVAL is either not positive or too big"
+ ger "INTERVAL ist entweder nicht positiv oder zu groß"
+ER_EVENT_ENDS_BEFORE_STARTS
+ eng "ENDS is either invalid or before STARTS"
+ ger "ENDS ist entweder ungültig oder liegt vor STARTS"
+ER_EVENT_EXEC_TIME_IN_THE_PAST
+ eng "Event execution time is in the past. Event has been disabled"
+ ger "Ausführungszeit des Events liegt in der Vergangenheit. Event wurde deaktiviert"
+ER_EVENT_OPEN_TABLE_FAILED
+ eng "Failed to open mysql.event"
+ ger "Öffnen von mysql.event fehlgeschlagen"
+ER_EVENT_NEITHER_M_EXPR_NOR_M_AT
+ eng "No datetime expression provided"
+ ger "Kein DATETIME-Ausdruck angegeben"
+
+ER_UNUSED_2
+ eng "You should never see it"
+ER_UNUSED_3
+ eng "You should never see it"
+ER_EVENT_CANNOT_DELETE
+ eng "Failed to delete the event from mysql.event"
+ ger "Löschen des Events aus mysql.event fehlgeschlagen"
+ER_EVENT_COMPILE_ERROR
+ eng "Error during compilation of event's body"
+ ger "Fehler beim Kompilieren des Event-Bodys"
+ER_EVENT_SAME_NAME
+ eng "Same old and new event name"
+ ger "Alter und neuer Event-Name sind gleich"
+ER_EVENT_DATA_TOO_LONG
+ eng "Data for column '%s' too long"
+ ger "Daten der Spalte '%s' zu lang"
+ER_DROP_INDEX_FK
+ eng "Cannot drop index '%-.192s': needed in a foreign key constraint"
+ ger "Kann Index '%-.192s' nicht löschen: wird für eine Fremdschlüsselbeschränkung benötigt"
+# When using this error message, use the ER_WARN_DEPRECATED_SYNTAX error
+# code.
+ER_WARN_DEPRECATED_SYNTAX_WITH_VER
+ eng "The syntax '%s' is deprecated and will be removed in MariaDB %s. Please use %s instead"
+ ger "Die Syntax '%s' ist veraltet und wird in MariaDB %s entfernt. Bitte benutzen Sie statt dessen %s"
+ER_CANT_WRITE_LOCK_LOG_TABLE
+ eng "You can't write-lock a log table. Only read access is possible"
+ ger "Eine Log-Tabelle kann nicht schreibgesperrt werden. Es ist ohnehin nur Lesezugriff möglich"
+ER_CANT_LOCK_LOG_TABLE
+ eng "You can't use locks with log tables."
+ ger "Log-Tabellen können nicht gesperrt werden."
+ER_UNUSED_4
+ eng "You should never see it"
+ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE
+ eng "Column count of mysql.%s is wrong. Expected %d, found %d. Created with MariaDB %d, now running %d. Please use mysql_upgrade to fix this error."
+ ger "Spaltenanzahl von mysql.%s falsch. %d erwartet, aber %d erhalten. Erzeugt mit MariaDB %d, jetzt unter %d. Bitte benutzen Sie mysql_upgrade, um den Fehler zu beheben"
+ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR
+ eng "Cannot switch out of the row-based binary log format when the session has open temporary tables"
+ ger "Kann nicht aus dem zeilenbasierten Binärlog-Format herauswechseln, wenn die Sitzung offene temporäre Tabellen hat"
+ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT
+ eng "Cannot change the binary logging format inside a stored function or trigger"
+ ger "Das Binärlog-Format kann innerhalb einer gespeicherten Funktion oder eines Triggers nicht geändert werden"
+ER_NDB_CANT_SWITCH_BINLOG_FORMAT
+ eng "The NDB cluster engine does not support changing the binlog format on the fly yet"
+ ger "Die Speicher-Engine NDB Cluster unterstützt das Ändern des Binärlog-Formats zur Laufzeit noch nicht"
+ER_PARTITION_NO_TEMPORARY
+ eng "Cannot create temporary table with partitions"
+ ger "Anlegen temporärer Tabellen mit Partitionen nicht möglich"
+ER_PARTITION_CONST_DOMAIN_ERROR
+ eng "Partition constant is out of partition function domain"
+ ger "Partitionskonstante liegt außerhalb der Partitionsfunktionsdomäne"
+ swe "Partitionskonstanten är utanför partitioneringsfunktionens domän"
+ER_PARTITION_FUNCTION_IS_NOT_ALLOWED
+ eng "This partition function is not allowed"
+ ger "Diese Partitionierungsfunktion ist nicht erlaubt"
+ swe "Denna partitioneringsfunktion är inte tillåten"
+ER_DDL_LOG_ERROR
+ eng "Error in DDL log"
+ ger "Fehler im DDL-Log"
+ER_NULL_IN_VALUES_LESS_THAN
+ eng "Not allowed to use NULL value in VALUES LESS THAN"
+ ger "In VALUES LESS THAN dürfen keine NULL-Werte verwendet werden"
+ swe "Det är inte tillåtet att använda NULL-värden i VALUES LESS THAN"
+ER_WRONG_PARTITION_NAME
+ eng "Incorrect partition name"
+ ger "Falscher Partitionsname"
+ swe "Felaktigt partitionsnamn"
+ER_CANT_CHANGE_TX_CHARACTERISTICS 25001
+ eng "Transaction characteristics can't be changed while a transaction is in progress"
+ER_DUP_ENTRY_AUTOINCREMENT_CASE
+ eng "ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry '%-.192s' for key '%-.192s'"
+ ger "ALTER TABLE führt zur Neusequenzierung von auto_increment, wodurch der doppelte Eintrag '%-.192s' für Schlüssel '%-.192s' auftritt"
+ER_EVENT_MODIFY_QUEUE_ERROR
+ eng "Internal scheduler error %d"
+ ger "Interner Scheduler-Fehler %d"
+ER_EVENT_SET_VAR_ERROR
+ eng "Error during starting/stopping of the scheduler. Error code %M"
+ ger "Fehler während des Startens oder Anhalten des Schedulers. Fehlercode %M"
+ER_PARTITION_MERGE_ERROR
+ eng "Engine cannot be used in partitioned tables"
+ ger "Engine kann in partitionierten Tabellen nicht verwendet werden"
+ swe "Engine inte användas i en partitionerad tabell"
+ER_CANT_ACTIVATE_LOG
+ eng "Cannot activate '%-.64s' log"
+ ger "Kann Logdatei '%-.64s' nicht aktivieren"
+ER_RBR_NOT_AVAILABLE
+ eng "The server was not built with row-based replication"
+ ger "Der Server wurde nicht mit zeilenbasierter Replikation gebaut"
+ER_BASE64_DECODE_ERROR
+ eng "Decoding of base64 string failed"
+ swe "Avkodning av base64 sträng misslyckades"
+ ger "Der Server hat keine zeilenbasierte Replikation"
+ER_EVENT_RECURSION_FORBIDDEN
+ eng "Recursion of EVENT DDL statements is forbidden when body is present"
+ ger "Rekursivität von EVENT-DDL-Anweisungen ist unzulässig wenn ein Hauptteil (Body) existiert"
+ER_EVENTS_DB_ERROR
+ eng "Cannot proceed because system tables used by Event Scheduler were found damaged at server start"
+ ger "Kann nicht weitermachen, weil die Tabellen, die von Events verwendet werden, beim Serverstart als beschädigt markiert wurden"
+ER_ONLY_INTEGERS_ALLOWED
+ eng "Only integers allowed as number here"
+ ger "An dieser Stelle sind nur Ganzzahlen zulässig"
+ER_UNSUPORTED_LOG_ENGINE
+ eng "Storage engine %s cannot be used for log tables"
+ ger "Speicher-Engine %s kann für Logtabellen nicht verwendet werden"
+ER_BAD_LOG_STATEMENT
+ eng "You cannot '%s' a log table if logging is enabled"
+ ger "Sie können eine Logtabelle nicht '%s', wenn Loggen angeschaltet ist"
+ER_CANT_RENAME_LOG_TABLE
+ eng "Cannot rename '%s'. When logging enabled, rename to/from log table must rename two tables: the log table to an archive table and another table back to '%s'"
+ ger "Kann '%s' nicht umbenennen. Wenn Loggen angeschaltet ist, müssen zwei Tabellen umbenannt werden: die Logtabelle zu einer Archivtabelle, und eine weitere Tabelle zu '%s'"
+ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT 42000
+ eng "Incorrect parameter count in the call to native function '%-.192s'"
+ ger "Falsche Anzahl von Parametern beim Aufruf der nativen Funktion '%-.192s'"
+ER_WRONG_PARAMETERS_TO_NATIVE_FCT 42000
+ eng "Incorrect parameters in the call to native function '%-.192s'"
+ ger "Falscher Parameter beim Aufruf der nativen Funktion '%-.192s'"
+ER_WRONG_PARAMETERS_TO_STORED_FCT 42000
+ eng "Incorrect parameters in the call to stored function '%-.192s'"
+ ger "Falsche Parameter beim Aufruf der gespeicherten Funktion '%-.192s'"
+ER_NATIVE_FCT_NAME_COLLISION
+ eng "This function '%-.192s' has the same name as a native function"
+ ger "Die Funktion '%-.192s' hat denselben Namen wie eine native Funktion"
+# When using this error message, use the ER_DUP_ENTRY error code. See, for
+# example, code in handler.cc.
+ER_DUP_ENTRY_WITH_KEY_NAME 23000 S1009
+ cze "Zvojený klÃ­Ä '%-.64s' (Äíslo klíÄe '%-.192s')"
+ dan "Ens værdier '%-.64s' for indeks '%-.192s'"
+ nla "Dubbele ingang '%-.64s' voor zoeksleutel '%-.192s'"
+ eng "Duplicate entry '%-.64s' for key '%-.192s'"
+ est "Kattuv väärtus '%-.64s' võtmele '%-.192s'"
+ fre "Duplicata du champ '%-.64s' pour la clef '%-.192s'"
+ ger "Doppelter Eintrag '%-.64s' für Schlüssel '%-.192s'"
+ greek "Διπλή εγγÏαφή '%-.64s' για το κλειδί '%-.192s'"
+ hun "Duplikalt bejegyzes '%-.64s' a '%-.192s' kulcs szerint."
+ ita "Valore duplicato '%-.64s' per la chiave '%-.192s'"
+ jpn "'%-.64s' ã¯ç´¢å¼• '%-.192s' ã§é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚"
+ kor "ì¤‘ë³µëœ ìž…ë ¥ ê°’ '%-.64s': key '%-.192s'"
+ nor "Like verdier '%-.64s' for nøkkel '%-.192s'"
+ norwegian-ny "Like verdiar '%-.64s' for nykkel '%-.192s'"
+ pol "Powtórzone wystąpienie '%-.64s' dla klucza '%-.192s'"
+ por "Entrada '%-.64s' duplicada para a chave '%-.192s'"
+ rum "Cimpul '%-.64s' e duplicat pentru cheia '%-.192s'"
+ rus "ДублирующаÑÑÑ Ð·Ð°Ð¿Ð¸ÑÑŒ '%-.64s' по ключу '%-.192s'"
+ serbian "Dupliran unos '%-.64s' za kljuÄ '%-.192s'"
+ slo "Opakovaný kÄ¾ÃºÄ '%-.64s' (Äíslo kľúÄa '%-.192s')"
+ spa "Entrada duplicada '%-.64s' para la clave '%-.192s'"
+ swe "Dublett '%-.64s' för nyckel '%-.192s'"
+ ukr "Дублюючий Ð·Ð°Ð¿Ð¸Ñ '%-.64s' Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° '%-.192s'"
+ER_BINLOG_PURGE_EMFILE
+ eng "Too many files opened, please execute the command again"
+ ger "Zu viele offene Dateien, bitte führen Sie den Befehl noch einmal aus"
+ER_EVENT_CANNOT_CREATE_IN_THE_PAST
+ eng "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation."
+ ger "Ausführungszeit des Events liegt in der Vergangenheit, und es wurde ON COMPLETION NOT PRESERVE gesetzt. Das Event wurde unmittelbar nach Erzeugung gelöscht."
+ER_EVENT_CANNOT_ALTER_IN_THE_PAST
+ eng "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was not changed. Specify a time in the future."
+ ger "Execution Zeitpunkt des Ereignisses in der Vergangenheit liegt, und es war NACH ABSCHLUSS Set nicht erhalten. Die Veranstaltung wurde nicht verändert. Geben Sie einen Zeitpunkt in der Zukunft."
+ER_SLAVE_INCIDENT
+ eng "The incident %s occured on the master. Message: %-.64s"
+ ger "Der Vorfall %s passierte auf dem Master. Meldung: %-.64s"
+ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT
+ eng "Table has no partition for some existing values"
+ ger "Tabelle hat für einige bestehende Werte keine Partition"
+ER_BINLOG_UNSAFE_STATEMENT
+ eng "Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. %s"
+ swe "Detta är inte säkert att logga i statement-format, för BINLOG_FORMAT = STATEMENT. %s"
+ ger "Unsichere Anweisung ins Binärlog geschrieben, weil Anweisungsformat BINLOG_FORMAT = STATEMENT. %s"
+ER_SLAVE_FATAL_ERROR
+ eng "Fatal error: %s"
+ ger "Fataler Fehler: %s"
+ER_SLAVE_RELAY_LOG_READ_FAILURE
+ eng "Relay log read failure: %s"
+ ger "Relaylog-Lesefehler: %s"
+ER_SLAVE_RELAY_LOG_WRITE_FAILURE
+ eng "Relay log write failure: %s"
+ ger "Relaylog-Schreibfehler: %s"
+ER_SLAVE_CREATE_EVENT_FAILURE
+ eng "Failed to create %s"
+ ger "Erzeugen von %s fehlgeschlagen"
+ER_SLAVE_MASTER_COM_FAILURE
+ eng "Master command %s failed: %s"
+ ger "Master-Befehl %s fehlgeschlagen: %s"
+ER_BINLOG_LOGGING_IMPOSSIBLE
+ eng "Binary logging not possible. Message: %s"
+ ger "Binärlogging nicht möglich. Meldung: %s"
+ER_VIEW_NO_CREATION_CTX
+ eng "View `%-.64s`.`%-.64s` has no creation context"
+ ger "View `%-.64s`.`%-.64s` hat keinen Erzeugungskontext"
+ER_VIEW_INVALID_CREATION_CTX
+ eng "Creation context of view `%-.64s`.`%-.64s' is invalid"
+ ger "Erzeugungskontext des Views`%-.64s`.`%-.64s' ist ungültig"
+ER_SR_INVALID_CREATION_CTX
+ eng "Creation context of stored routine `%-.64s`.`%-.64s` is invalid"
+ ger "Erzeugungskontext der gespeicherten Routine`%-.64s`.`%-.64s` ist ungültig"
+ER_TRG_CORRUPTED_FILE
+ eng "Corrupted TRG file for table `%-.64s`.`%-.64s`"
+ ger "Beschädigte TRG-Datei für Tabelle `%-.64s`.`%-.64s`"
+ER_TRG_NO_CREATION_CTX
+ eng "Triggers for table `%-.64s`.`%-.64s` have no creation context"
+ ger "Trigger für Tabelle `%-.64s`.`%-.64s` haben keinen Erzeugungskontext"
+ER_TRG_INVALID_CREATION_CTX
+ eng "Trigger creation context of table `%-.64s`.`%-.64s` is invalid"
+ ger "Trigger-Erzeugungskontext der Tabelle `%-.64s`.`%-.64s` ist ungültig"
+ER_EVENT_INVALID_CREATION_CTX
+ eng "Creation context of event `%-.64s`.`%-.64s` is invalid"
+ ger "Erzeugungskontext des Events `%-.64s`.`%-.64s` ist ungültig"
+ER_TRG_CANT_OPEN_TABLE
+ eng "Cannot open table for trigger `%-.64s`.`%-.64s`"
+ ger "Kann Tabelle für den Trigger `%-.64s`.`%-.64s` nicht öffnen"
+ER_CANT_CREATE_SROUTINE
+ eng "Cannot create stored routine `%-.64s`. Check warnings"
+ ger "Kann gespeicherte Routine `%-.64s` nicht erzeugen. Beachten Sie die Warnungen"
+ER_UNUSED_11
+ eng "You should never see it"
+ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT
+ eng "The BINLOG statement of type `%s` was not preceded by a format description BINLOG statement."
+ ger "Der BINLOG-Anweisung vom Typ `%s` ging keine BINLOG-Anweisung zur Formatbeschreibung voran."
+ER_SLAVE_CORRUPT_EVENT
+ eng "Corrupted replication event was detected"
+ ger "Beschädigtes Replikationsereignis entdeckt"
+ER_LOAD_DATA_INVALID_COLUMN
+ eng "Invalid column reference (%-.64s) in LOAD DATA"
+ ger "Ungültige Spaltenreferenz (%-.64s) bei LOAD DATA"
+ER_LOG_PURGE_NO_FILE
+ eng "Being purged log %s was not found"
+ ger "Zu bereinigende Logdatei %s wurde nicht gefunden"
+ER_XA_RBTIMEOUT XA106
+ eng "XA_RBTIMEOUT: Transaction branch was rolled back: took too long"
+ ger "XA_RBTIMEOUT: Transaktionszweig wurde zurückgerollt: Zeitüberschreitung"
+ER_XA_RBDEADLOCK XA102
+ eng "XA_RBDEADLOCK: Transaction branch was rolled back: deadlock was detected"
+ ger "XA_RBDEADLOCK: Transaktionszweig wurde zurückgerollt: Deadlock entdeckt"
+ER_NEED_REPREPARE
+ eng "Prepared statement needs to be re-prepared"
+ ger "Vorbereitete Anweisungen müssen noch einmal vorbereitet werden"
+ER_DELAYED_NOT_SUPPORTED
+ eng "DELAYED option not supported for table '%-.192s'"
+ ger "Die DELAYED-Option wird für Tabelle '%-.192s' nicht unterstützt"
+WARN_NO_MASTER_INFO
+ eng "There is no master connection '%.*s'"
+ ger "Die Master-Info-Struktur existiert nicht '%.*s'"
+WARN_OPTION_IGNORED
+ eng "<%-.64s> option ignored"
+ ger "Option <%-.64s> ignoriert"
+ER_PLUGIN_DELETE_BUILTIN
+ eng "Built-in plugins cannot be deleted"
+ ger "Eingebaute Plugins können nicht gelöscht werden"
+WARN_PLUGIN_BUSY
+ eng "Plugin is busy and will be uninstalled on shutdown"
+ ger "Plugin wird verwendet und wird erst beim Herunterfahren deinstalliert"
+ER_VARIABLE_IS_READONLY
+ eng "%s variable '%s' is read-only. Use SET %s to assign the value"
+ ger "%s Variable '%s' ist nur lesbar. Benutzen Sie SET %s, um einen Wert zuzuweisen"
+ER_WARN_ENGINE_TRANSACTION_ROLLBACK
+ eng "Storage engine %s does not support rollback for this statement. Transaction rolled back and must be restarted"
+ ger "Speicher-Engine %s unterstützt für diese Anweisung kein Rollback. Transaktion wurde zurückgerollt und muss neu gestartet werden"
+ER_SLAVE_HEARTBEAT_FAILURE
+ eng "Unexpected master's heartbeat data: %s"
+ ger "Unerwartete Daten vom Heartbeat des Masters: %s"
+ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE
+ eng "The requested value for the heartbeat period is either negative or exceeds the maximum allowed (%s seconds)."
+ER_NDB_REPLICATION_SCHEMA_ERROR
+ eng "Bad schema for mysql.ndb_replication table. Message: %-.64s"
+ ger "Fehlerhaftes Schema für mysql.ndb_replication table. Meldung: %-.64s"
+ER_CONFLICT_FN_PARSE_ERROR
+ eng "Error in parsing conflict function. Message: %-.64s"
+ ger "Fehler beim Parsen einer Konflikt-Funktion. Meldung: %-.64s"
+ER_EXCEPTIONS_WRITE_ERROR
+ eng "Write to exceptions table failed. Message: %-.128s""
+ ger "Schreiben in Ausnahme-Tabelle fehlgeschlagen. Meldung: %-.128s""
+ER_TOO_LONG_TABLE_COMMENT
+ eng "Comment for table '%-.64s' is too long (max = %u)"
+ por "Comentário para a tabela '%-.64s' é longo demais (max = %u)"
+ ger "Kommentar für Tabelle '%-.64s' ist zu lang (max = %u)"
+ER_TOO_LONG_FIELD_COMMENT
+ eng "Comment for field '%-.64s' is too long (max = %u)"
+ por "Comentário para o campo '%-.64s' é longo demais (max = %u)"
+ ger "Kommentar für Feld '%-.64s' ist zu lang (max = %u)"
+ER_FUNC_INEXISTENT_NAME_COLLISION 42000
+ eng "FUNCTION %s does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual"
+ ger "FUNCTION %s existiert nicht. Erläuterungen im Abschnitt 'Function Name Parsing and Resolution' im Referenzhandbuch"
+# When updating these, please update EXPLAIN_FILENAME_MAX_EXTRA_LENGTH in
+# sql_table.h with the new maximal additional length for explain_filename.
+ER_DATABASE_NAME
+ eng "Database"
+ swe "Databas"
+ ger "Datenbank"
+ER_TABLE_NAME
+ eng "Table"
+ swe "Tabell"
+ ger "Tabelle"
+ER_PARTITION_NAME
+ eng "Partition"
+ swe "Partition"
+ ger "Partition"
+ER_SUBPARTITION_NAME
+ eng "Subpartition"
+ swe "Subpartition"
+ ger "Unterpartition"
+ER_TEMPORARY_NAME
+ eng "Temporary"
+ swe "Temporär"
+ ger "Temporär"
+ER_RENAMED_NAME
+ eng "Renamed"
+ swe "Namnändrad"
+ ger "Umbenannt"
+ER_TOO_MANY_CONCURRENT_TRXS
+ eng "Too many active concurrent transactions"
+ ger "Zu viele aktive simultane Transaktionen"
+WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED
+ eng "Non-ASCII separator arguments are not fully supported"
+ ger "Nicht-ASCII-Trennargumente werden nicht vollständig unterstützt"
+ER_DEBUG_SYNC_TIMEOUT
+ eng "debug sync point wait timed out"
+ ger "Debug Sync Point Wartezeit überschritten"
+ER_DEBUG_SYNC_HIT_LIMIT
+ eng "debug sync point hit limit reached"
+ ger "Debug Sync Point Hit Limit erreicht"
+ER_DUP_SIGNAL_SET 42000
+ eng "Duplicate condition information item '%s'"
+ ger "Informationselement '%s' für Duplikatbedingung"
+# Note that the SQLSTATE is not 01000, it is provided by SIGNAL/RESIGNAL
+ER_SIGNAL_WARN 01000
+ eng "Unhandled user-defined warning condition"
+ ger "Unbehandelte benutzerdefinierte Warnbedingung"
+# Note that the SQLSTATE is not 02000, it is provided by SIGNAL/RESIGNAL
+ER_SIGNAL_NOT_FOUND 02000
+ eng "Unhandled user-defined not found condition"
+ ger "Unbehandelte benutzerdefinierte Nicht-gefunden-Bedingung"
+# Note that the SQLSTATE is not HY000, it is provided by SIGNAL/RESIGNAL
+ER_SIGNAL_EXCEPTION HY000
+ eng "Unhandled user-defined exception condition"
+ ger "Unbehandelte benutzerdefinierte Ausnahmebedingung"
+ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER 0K000
+ eng "RESIGNAL when handler not active"
+ ger "RESIGNAL bei nicht aktivem Handler"
+ER_SIGNAL_BAD_CONDITION_TYPE
+ eng "SIGNAL/RESIGNAL can only use a CONDITION defined with SQLSTATE"
+ ger "SIGNAL/RESIGNAL kann nur mit einer Bedingung (CONDITION) benutzt werden, die bei SQLSTATE definiert wurde"
+WARN_COND_ITEM_TRUNCATED
+ eng "Data truncated for condition item '%s'"
+ ger "Daten gekürzt für Bedingungselement '%s'"
+ER_COND_ITEM_TOO_LONG
+ eng "Data too long for condition item '%s'"
+ ger "Daten zu lang für Bedingungselement '%s'"
+ER_UNKNOWN_LOCALE
+ eng "Unknown locale: '%-.64s'"
+ ger "Unbekannte Locale: '%-.64s'"
+ER_SLAVE_IGNORE_SERVER_IDS
+ eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id"
+ ger "Die angeforderte Server-ID %d steht im Konflikt mit der Startoption --replicate-same-server-id für den Slave"
+ER_QUERY_CACHE_DISABLED
+ eng "Query cache is disabled; set query_cache_type to ON or DEMAND to enable it"
+ER_SAME_NAME_PARTITION_FIELD
+ eng "Duplicate partition field name '%-.192s'"
+ ger "Partitionsfeld '%-.192s' ist ein Duplikat"
+ER_PARTITION_COLUMN_LIST_ERROR
+ eng "Inconsistency in usage of column lists for partitioning"
+ ger "Inkonsistenz bei der Benutzung von Spaltenlisten für Partitionierung"
+ER_WRONG_TYPE_COLUMN_VALUE_ERROR
+ eng "Partition column values of incorrect type"
+ ger "Partitionsspaltenwerte sind vom falschen Typ"
+ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR
+ eng "Too many fields in '%-.192s'"
+ ger "Zu viele Felder in '%-.192s'"
+ER_MAXVALUE_IN_VALUES_IN
+ eng "Cannot use MAXVALUE as value in VALUES IN"
+ ger "MAXVALUE kann nicht als Wert in VALUES IN verwendet werden"
+ER_TOO_MANY_VALUES_ERROR
+ eng "Cannot have more than one value for this type of %-.64s partitioning"
+ ger "Für den Partionierungstyp %-.64s darf es nicht mehr als einen Wert geben"
+ER_ROW_SINGLE_PARTITION_FIELD_ERROR
+ eng "Row expressions in VALUES IN only allowed for multi-field column partitioning"
+ ger "Zeilenausdrücke in VALUES IN sind nur für Mehrfeld-Spaltenpartionierung erlaubt"
+ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD
+ eng "Field '%-.192s' is of a not allowed type for this type of partitioning"
+ ger "Feld '%-.192s' ist für diese Art von Partitionierung von einem nicht zulässigen Typ"
+ER_PARTITION_FIELDS_TOO_LONG
+ eng "The total length of the partitioning fields is too large"
+ ger "Die Gesamtlänge der Partitionsfelder ist zu groß"
+ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE
+ eng "Cannot execute statement: impossible to write to binary log since both row-incapable engines and statement-incapable engines are involved."
+ER_BINLOG_ROW_MODE_AND_STMT_ENGINE
+ eng "Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = ROW and at least one table uses a storage engine limited to statement-based logging."
+ER_BINLOG_UNSAFE_AND_STMT_ENGINE
+ eng "Cannot execute statement: impossible to write to binary log since statement is unsafe, storage engine is limited to statement-based logging, and BINLOG_FORMAT = MIXED. %s"
+ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE
+ eng "Cannot execute statement: impossible to write to binary log since statement is in row format and at least one table uses a storage engine limited to statement-based logging."
+ER_BINLOG_STMT_MODE_AND_ROW_ENGINE
+ eng "Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging.%s"
+ER_BINLOG_ROW_INJECTION_AND_STMT_MODE
+ eng "Cannot execute statement: impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT."
+ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE
+ eng "Cannot execute statement: impossible to write to binary log since more than one engine is involved and at least one engine is self-logging."
+
+ER_BINLOG_UNSAFE_LIMIT
+ eng "The statement is unsafe because it uses a LIMIT clause. This is unsafe because the set of rows included cannot be predicted."
+ER_BINLOG_UNSAFE_INSERT_DELAYED
+ eng "The statement is unsafe because it uses INSERT DELAYED. This is unsafe because the times when rows are inserted cannot be predicted."
+ER_BINLOG_UNSAFE_SYSTEM_TABLE
+ eng "The statement is unsafe because it uses the general log, slow query log, or performance_schema table(s). This is unsafe because system tables may differ on slaves."
+ER_BINLOG_UNSAFE_AUTOINC_COLUMNS
+ eng "Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column. Inserted values cannot be logged correctly."
+ER_BINLOG_UNSAFE_UDF
+ eng "Statement is unsafe because it uses a UDF which may not return the same value on the slave."
+ER_BINLOG_UNSAFE_SYSTEM_VARIABLE
+ eng "Statement is unsafe because it uses a system variable that may have a different value on the slave."
+ER_BINLOG_UNSAFE_SYSTEM_FUNCTION
+ eng "Statement is unsafe because it uses a system function that may return a different value on the slave."
+ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS
+ eng "Statement is unsafe because it accesses a non-transactional table after accessing a transactional table within the same transaction."
+
+ER_MESSAGE_AND_STATEMENT
+ eng "%s Statement: %s"
+
+ER_SLAVE_CONVERSION_FAILED
+ eng "Column %d of table '%-.192s.%-.192s' cannot be converted from type '%-.32s' to type '%-.32s'"
+ER_SLAVE_CANT_CREATE_CONVERSION
+ eng "Can't create conversion table for table '%-.192s.%-.192s'"
+ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT
+ eng "Cannot modify @@session.binlog_format inside a transaction"
+ER_PATH_LENGTH
+ eng "The path specified for %.64s is too long."
+ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT
+ eng "'%s' is deprecated and will be removed in a future release."
+ ger "'%s' ist veraltet und wird in einer zukünftigen Version entfernt werden."
+
+ER_WRONG_NATIVE_TABLE_STRUCTURE
+ eng "Native table '%-.64s'.'%-.64s' has the wrong structure"
+
+ER_WRONG_PERFSCHEMA_USAGE
+ eng "Invalid performance_schema usage."
+ER_WARN_I_S_SKIPPED_TABLE
+ eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement"
+
+ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT
+ eng "Cannot modify @@session.binlog_direct_non_transactional_updates inside a transaction"
+ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT
+ eng "Cannot change the binlog direct flag inside a stored function or trigger"
+ER_SPATIAL_MUST_HAVE_GEOM_COL 42000
+ eng "A SPATIAL index may only contain a geometrical type column"
+ ger "Ein raumbezogener Index (SPATIAL) darf nur Spalten geometrischen Typs enthalten"
+ER_TOO_LONG_INDEX_COMMENT
+ eng "Comment for index '%-.64s' is too long (max = %lu)"
+
+ER_LOCK_ABORTED
+ eng "Wait on a lock was aborted due to a pending exclusive lock"
+
+ER_DATA_OUT_OF_RANGE 22003
+ eng "%s value is out of range in '%s'"
+
+ER_WRONG_SPVAR_TYPE_IN_LIMIT
+ eng "A variable of a non-integer based type in LIMIT clause"
+
+ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE
+ eng "Mixing self-logging and non-self-logging engines in a statement is unsafe."
+
+ER_BINLOG_UNSAFE_MIXED_STATEMENT
+ eng "Statement accesses nontransactional table as well as transactional or temporary table, and writes to any of them."
+
+ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN
+ eng "Cannot modify @@session.sql_log_bin inside a transaction"
+
+ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN
+ eng "Cannot change the sql_log_bin inside a stored function or trigger"
+
+ER_FAILED_READ_FROM_PAR_FILE
+ eng "Failed to read from the .par file"
+ swe "Misslyckades läsa från .par filen"
+
+ER_VALUES_IS_NOT_INT_TYPE_ERROR
+ eng "VALUES value for partition '%-.64s' must have type INT"
+ swe "Värden i VALUES för partition '%-.64s' måste ha typen INT"
+
+ER_ACCESS_DENIED_NO_PASSWORD_ERROR 28000
+ cze "Přístup pro uživatele '%s'@'%s'"
+ dan "Adgang nægtet bruger: '%s'@'%s'"
+ nla "Toegang geweigerd voor gebruiker: '%s'@'%s'"
+ eng "Access denied for user '%s'@'%s'"
+ est "Ligipääs keelatud kasutajale '%s'@'%s'"
+ fre "Accès refusé pour l'utilisateur: '%s'@'%s'"
+ ger "Benutzer '%s'@'%s' hat keine Zugriffsberechtigung"
+ greek "Δεν επιτέÏεται η Ï€Ïόσβαση στο χÏήστη: '%s'@'%s'"
+ hun "A(z) '%s'@'%s' felhasznalo szamara tiltott eleres."
+ ita "Accesso non consentito per l'utente: '%s'@'%s'"
+ kor "'%s'@'%s' 사용ìžëŠ” ì ‘ê·¼ì´ ê±°ë¶€ ë˜ì—ˆìŠµë‹ˆë‹¤."
+ nor "Tilgang nektet for bruker: '%s'@'%s'"
+ norwegian-ny "Tilgang ikke tillate for brukar: '%s'@'%s'"
+ por "Acesso negado para o usuário '%s'@'%s'"
+ rum "Acces interzis pentru utilizatorul: '%s'@'%s'"
+ rus "ДоÑтуп закрыт Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%s'@'%s'"
+ serbian "Pristup je zabranjen korisniku '%s'@'%s'"
+ slo "Zakázaný prístup pre užívateľa: '%s'@'%s'"
+ spa "Acceso negado para usuario: '%s'@'%s'"
+ swe "Användare '%s'@'%s' är ej berättigad att logga in"
+ ukr "ДоÑтуп заборонено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача: '%s'@'%s'"
+
+ER_SET_PASSWORD_AUTH_PLUGIN
+ eng "SET PASSWORD has no significance for users authenticating via plugins"
+
+ER_GRANT_PLUGIN_USER_EXISTS
+ eng "GRANT with IDENTIFIED WITH is illegal because the user %-.*s already exists"
+
+ER_TRUNCATE_ILLEGAL_FK 42000
+ eng "Cannot truncate a table referenced in a foreign key constraint (%.192s)"
+
+ER_PLUGIN_IS_PERMANENT
+ eng "Plugin '%s' is force_plus_permanent and can not be unloaded"
+
+ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN
+ eng "The requested value for the heartbeat period is less than 1 millisecond. The value is reset to 0, meaning that heartbeating will effectively be disabled."
+
+ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX
+ eng "The requested value for the heartbeat period exceeds the value of `slave_net_timeout' seconds. A sensible value for the period should be less than the timeout."
+
+ER_STMT_CACHE_FULL
+ eng "Multi-row statements required more than 'max_binlog_stmt_cache_size' bytes of storage; increase this mysqld variable and try again"
+
+ER_MULTI_UPDATE_KEY_CONFLICT
+ eng "Primary key/partition key update is not allowed since the table is updated both as '%-.192s' and '%-.192s'."
+
+# When translating this error message make sure to include "ALTER TABLE" in the
+# message as mysqlcheck parses the error message looking for ALTER TABLE.
+ER_TABLE_NEEDS_REBUILD
+ eng "Table rebuild required. Please do \"ALTER TABLE `%-.32s` FORCE\" or dump/reload to fix it!"
+
+WARN_OPTION_BELOW_LIMIT
+ eng "The value of '%s' should be no less than the value of '%s'"
+
+ER_INDEX_COLUMN_TOO_LONG
+ eng "Index column size too large. The maximum column size is %lu bytes."
+
+ER_ERROR_IN_TRIGGER_BODY
+ eng "Trigger '%-.64s' has an error in its body: '%-.256s'"
+
+ER_ERROR_IN_UNKNOWN_TRIGGER_BODY
+ eng "Unknown trigger has an error in its body: '%-.256s'"
+
+ER_INDEX_CORRUPT
+ eng "Index %s is corrupted"
+
+ER_UNDO_RECORD_TOO_BIG
+ eng "Undo log record is too big."
+
+ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT
+ eng "INSERT IGNORE... SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave."
+
+ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE
+ eng "INSERT... SELECT... ON DUPLICATE KEY UPDATE is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are updated. This order cannot be predicted and may differ on master and the slave."
+
+ER_BINLOG_UNSAFE_REPLACE_SELECT
+ eng "REPLACE... SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are replaced. This order cannot be predicted and may differ on master and the slave."
+
+ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT
+ eng "CREATE... IGNORE SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave."
+
+ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT
+ eng "CREATE... REPLACE SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are replaced. This order cannot be predicted and may differ on master and the slave."
+
+ER_BINLOG_UNSAFE_UPDATE_IGNORE
+ eng "UPDATE IGNORE is unsafe because the order in which rows are updated determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave."
+
+ER_UNUSED_13
+ eng "You should never see it"
+
+ER_UNUSED_14
+ eng "You should never see it"
+
+ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT
+ eng "Statements writing to a table with an auto-increment column after selecting from another table are unsafe because the order in which rows are retrieved determines what (if any) rows will be written. This order cannot be predicted and may differ on master and the slave."
+
+ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC
+ eng "CREATE TABLE... SELECT... on a table with an auto-increment column is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are inserted. This order cannot be predicted and may differ on master and the slave."
+
+ER_BINLOG_UNSAFE_INSERT_TWO_KEYS
+ eng "INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe"
+
+ER_TABLE_IN_FK_CHECK
+ eng "Table is being used in foreign key check."
+
+ER_UNUSED_1
+ eng "You should never see it"
+
+ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST
+ eng "INSERT into autoincrement field which is not the first part in the composed primary key is unsafe."
+
+#
+# End of 5.5 error messages.
+#
+
+ER_CANNOT_LOAD_FROM_TABLE_V2
+ eng "Cannot load from %s.%s. The table is probably corrupted"
+ ger "Kann %s.%s nicht einlesen. Tabelle ist wahrscheinlich beschädigt"
+
+ER_MASTER_DELAY_VALUE_OUT_OF_RANGE
+ eng "The requested value %u for the master delay exceeds the maximum %u"
+ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT
+ eng "Only Format_description_log_event and row events are allowed in BINLOG statements (but %s was provided)"
+
+ER_PARTITION_EXCHANGE_DIFFERENT_OPTION
+ eng "Non matching attribute '%-.64s' between partition and table"
+ swe "Attributet '%-.64s' är olika mellan partition och tabell"
+ER_PARTITION_EXCHANGE_PART_TABLE
+ eng "Table to exchange with partition is partitioned: '%-.64s'"
+ swe "Tabellen att byta ut mot partition är partitionerad: '%-.64s'"
+ER_PARTITION_EXCHANGE_TEMP_TABLE
+ eng "Table to exchange with partition is temporary: '%-.64s'"
+ swe "Tabellen att byta ut mot partition är temporär: '%-.64s'"
+ER_PARTITION_INSTEAD_OF_SUBPARTITION
+ eng "Subpartitioned table, use subpartition instead of partition"
+ swe "Subpartitionerad tabell, använd subpartition istället för partition"
+ER_UNKNOWN_PARTITION
+ eng "Unknown partition '%-.64s' in table '%-.64s'"
+ swe "Okänd partition '%-.64s' i tabell '%-.64s'"
+ER_TABLES_DIFFERENT_METADATA
+ eng "Tables have different definitions"
+ swe "Tabellerna har olika definitioner"
+ER_ROW_DOES_NOT_MATCH_PARTITION
+ eng "Found a row that does not match the partition"
+ swe "Hittade en rad som inte passar i partitionen"
+ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX
+ eng "Option binlog_cache_size (%lu) is greater than max_binlog_cache_size (%lu); setting binlog_cache_size equal to max_binlog_cache_size."
+ER_WARN_INDEX_NOT_APPLICABLE
+ eng "Cannot use %-.64s access on index '%-.64s' due to type or collation conversion on field '%-.64s'"
+
+ER_PARTITION_EXCHANGE_FOREIGN_KEY
+ eng "Table to exchange with partition has foreign key references: '%-.64s'"
+ swe "Tabellen att byta ut mot partition har foreign key referenser: '%-.64s'"
+ER_NO_SUCH_KEY_VALUE
+ eng "Key value '%-.192s' was not found in table '%-.192s.%-.192s'"
+ER_RPL_INFO_DATA_TOO_LONG
+ eng "Data for column '%s' too long"
+ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE
+ eng "Replication event checksum verification failed while reading from network."
+ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE
+ eng "Replication event checksum verification failed while reading from a log file."
+
+ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX
+ eng "Option binlog_stmt_cache_size (%lu) is greater than max_binlog_stmt_cache_size (%lu); setting binlog_stmt_cache_size equal to max_binlog_stmt_cache_size."
+ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT
+ eng "Can't update table '%-.192s' while '%-.192s' is being created."
+
+ER_PARTITION_CLAUSE_ON_NONPARTITIONED
+ eng "PARTITION () clause on non partitioned table"
+ swe "PARTITION () klausul för en icke partitionerad tabell"
+ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET
+ eng "Found a row not matching the given partition set"
+ swe "Hittade en rad som inte passar i någon given partition"
+
+ER_UNUSED_5
+ eng "You should never see it"
+
+ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE
+ eng "Failure while changing the type of replication repository: %s."
+
+ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE
+ eng "The creation of some temporary tables could not be rolled back."
+ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE
+ eng "Some temporary tables were dropped, but these operations could not be rolled back."
+
+ER_MTS_FEATURE_IS_NOT_SUPPORTED
+ eng "%s is not supported in multi-threaded slave mode. %s"
+ER_MTS_UPDATED_DBS_GREATER_MAX
+ eng "The number of modified databases exceeds the maximum %d; the database names will not be included in the replication event metadata."
+ER_MTS_CANT_PARALLEL
+ eng "Cannot execute the current event group in the parallel mode. Encountered event %s, relay-log name %s, position %s which prevents execution of this event group in parallel mode. Reason: %s."
+ER_MTS_INCONSISTENT_DATA
+ eng "%s"
+
+ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING
+ eng "FULLTEXT index is not supported for partitioned tables."
+ swe "FULLTEXT index stöds ej för partitionerade tabeller."
+
+ER_DA_INVALID_CONDITION_NUMBER 35000
+ eng "Invalid condition number"
+ por "Número de condição inválido"
+
+ER_INSECURE_PLAIN_TEXT
+ eng "Sending passwords in plain text without SSL/TLS is extremely insecure."
+
+ER_INSECURE_CHANGE_MASTER
+ eng "Storing MySQL user name or password information in the master.info repository is not secure and is therefore not recommended. Please see the MySQL Manual for more about this issue and possible alternatives."
+
+ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO 23000 S1009
+ eng "Foreign key constraint for table '%.192s', record '%-.192s' would lead to a duplicate entry in table '%.192s', key '%.192s'"
+ ger "Fremdschlüssel-Beschränkung für Tabelle '%.192s', Datensatz '%-.192s' würde zu einem doppelten Eintrag in Tabelle '%.192s', Schlüssel '%.192s' führen"
+ swe "FOREIGN KEY constraint för tabell '%.192s', posten '%-.192s' kan inte uppdatera barntabell '%.192s' på grund av nyckel '%.192s'"
+
+ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO 23000 S1009
+ eng "Foreign key constraint for table '%.192s', record '%-.192s' would lead to a duplicate entry in a child table"
+ ger "Fremdschlüssel-Beschränkung für Tabelle '%.192s', Datensatz '%-.192s' würde zu einem doppelten Eintrag in einer Kind-Tabelle führen"
+ swe "FOREIGN KEY constraint för tabell '%.192s', posten '%-.192s' kan inte uppdatera en barntabell på grund av UNIQUE-test"
+
+ER_SQLTHREAD_WITH_SECURE_SLAVE
+ eng "Setting authentication options is not possible when only the Slave SQL Thread is being started."
+
+ER_TABLE_HAS_NO_FT
+ eng "The table does not have FULLTEXT index to support this query"
+
+ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER
+ eng "The system variable %.200s cannot be set in stored functions or triggers."
+
+ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION
+ eng "The system variable %.200s cannot be set when there is an ongoing transaction."
+
+ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST
+ eng "The system variable @@SESSION.GTID_NEXT has the value %.200s, which is not listed in @@SESSION.GTID_NEXT_LIST."
+
+ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION_WHEN_GTID_NEXT_LIST_IS_NULL
+ eng "When @@SESSION.GTID_NEXT_LIST == NULL, the system variable @@SESSION.GTID_NEXT cannot change inside a transaction."
+
+ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION
+ eng "The statement 'SET %.200s' cannot invoke a stored function."
+
+ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL
+ eng "The system variable @@SESSION.GTID_NEXT cannot be 'AUTOMATIC' when @@SESSION.GTID_NEXT_LIST is non-NULL."
+
+ER_SKIPPING_LOGGED_TRANSACTION
+ eng "Skipping transaction %.200s because it has already been executed and logged."
+
+ER_MALFORMED_GTID_SET_SPECIFICATION
+ eng "Malformed GTID set specification '%.200s'."
+
+ER_MALFORMED_GTID_SET_ENCODING
+ eng "Malformed GTID set encoding."
+
+ER_MALFORMED_GTID_SPECIFICATION
+ eng "Malformed GTID specification '%.200s'."
+
+ER_GNO_EXHAUSTED
+ eng "Impossible to generate Global Transaction Identifier: the integer component reached the maximal value. Restart the server with a new server_uuid."
+
+ER_BAD_SLAVE_AUTO_POSITION
+ eng "Parameters MASTER_LOG_FILE, MASTER_LOG_POS, RELAY_LOG_FILE and RELAY_LOG_POS cannot be set when MASTER_AUTO_POSITION is active."
+
+ER_AUTO_POSITION_REQUIRES_GTID_MODE_ON
+ eng "CHANGE MASTER TO MASTER_AUTO_POSITION = 1 can only be executed when GTID_MODE = ON."
+
+ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET
+ eng "Cannot execute statements with implicit commit inside a transaction when GTID_NEXT != AUTOMATIC or GTID_NEXT_LIST != NULL."
+
+ER_GTID_MODE_2_OR_3_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON
+ eng "GTID_MODE = ON or GTID_MODE = UPGRADE_STEP_2 requires ENFORCE_GTID_CONSISTENCY = 1."
+
+ER_GTID_MODE_REQUIRES_BINLOG
+ eng "GTID_MODE = ON or UPGRADE_STEP_1 or UPGRADE_STEP_2 requires --log-bin and --log-slave-updates."
+
+ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF
+ eng "GTID_NEXT cannot be set to UUID:NUMBER when GTID_MODE = OFF."
+
+ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON
+ eng "GTID_NEXT cannot be set to ANONYMOUS when GTID_MODE = ON."
+
+ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF
+ eng "GTID_NEXT_LIST cannot be set to a non-NULL value when GTID_MODE = OFF."
+
+ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF
+ eng "Found a Gtid_log_event or Previous_gtids_log_event when GTID_MODE = OFF."
+
+ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE
+ eng "When ENFORCE_GTID_CONSISTENCY = 1, updates to non-transactional tables can only be done in either autocommitted statements or single-statement transactions, and never in the same statement as updates to transactional tables."
+
+ER_GTID_UNSAFE_CREATE_SELECT
+ eng "CREATE TABLE ... SELECT is forbidden when ENFORCE_GTID_CONSISTENCY = 1."
+
+ER_GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION
+ eng "When ENFORCE_GTID_CONSISTENCY = 1, the statements CREATE TEMPORARY TABLE and DROP TEMPORARY TABLE can be executed in a non-transactional context only, and require that AUTOCOMMIT = 1."
+
+ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME
+ eng "The value of GTID_MODE can only change one step at a time: OFF <-> UPGRADE_STEP_1 <-> UPGRADE_STEP_2 <-> ON. Also note that this value must be stepped up or down simultaneously on all servers; see the Manual for instructions."
+
+ER_MASTER_HAS_PURGED_REQUIRED_GTIDS
+ eng "The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires."
+
+ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID
+ eng "GTID_NEXT cannot be changed by a client that owns a GTID. The client owns %s. Ownership is released on COMMIT or ROLLBACK."
+
+ER_UNKNOWN_EXPLAIN_FORMAT
+ eng "Unknown EXPLAIN format name: '%s'"
+ rus "ÐеизвеÑтное Ð¸Ð¼Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð° команды EXPLAIN: '%s'"
+
+ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION 25006
+ eng "Cannot execute statement in a READ ONLY transaction."
+
+ER_TOO_LONG_TABLE_PARTITION_COMMENT
+ eng "Comment for table partition '%-.64s' is too long (max = %lu)"
+
+ER_SLAVE_CONFIGURATION
+ eng "Slave is not configured or failed to initialize properly. You must at least set --server-id to enable either a master or a slave. Additional error messages can be found in the MySQL error log."
+
+ER_INNODB_FT_LIMIT
+ eng "InnoDB presently supports one FULLTEXT index creation at a time"
+
+ER_INNODB_NO_FT_TEMP_TABLE
+ eng "Cannot create FULLTEXT index on temporary InnoDB table"
+
+ER_INNODB_FT_WRONG_DOCID_COLUMN
+ eng "Column '%-.192s' is of wrong type for an InnoDB FULLTEXT index"
+
+ER_INNODB_FT_WRONG_DOCID_INDEX
+ eng "Index '%-.192s' is of wrong type for an InnoDB FULLTEXT index"
+
+ER_INNODB_ONLINE_LOG_TOO_BIG
+ eng "Creating index '%-.192s' required more than 'innodb_online_alter_log_max_size' bytes of modification log. Please try again."
+
+ER_UNKNOWN_ALTER_ALGORITHM
+ eng "Unknown ALGORITHM '%s'"
+
+ER_UNKNOWN_ALTER_LOCK
+ eng "Unknown LOCK type '%s'"
+
+ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS
+ eng "CHANGE MASTER cannot be executed when the slave was stopped with an error or killed in MTS mode. Consider using RESET SLAVE or START SLAVE UNTIL."
+
+ER_MTS_RECOVERY_FAILURE
+ eng "Cannot recover after SLAVE errored out in parallel execution mode. Additional error messages can be found in the MySQL error log."
+
+ER_MTS_RESET_WORKERS
+ eng "Cannot clean up worker info tables. Additional error messages can be found in the MySQL error log."
+
+ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2
+ eng "Column count of %s.%s is wrong. Expected %d, found %d. The table is probably corrupted"
+ ger "Spaltenanzahl von %s.%s falsch. %d erwartet, aber %d gefunden. Tabelle ist wahrscheinlich beschädigt"
+
+ER_SLAVE_SILENT_RETRY_TRANSACTION
+ eng "Slave must silently retry current transaction"
+
+ER_DISCARD_FK_CHECKS_RUNNING
+ eng "There is a foreign key check running on table '%-.192s'. Cannot discard the table."
+
+ER_TABLE_SCHEMA_MISMATCH
+ eng "Schema mismatch (%s)"
+
+ER_TABLE_IN_SYSTEM_TABLESPACE
+ eng "Table '%-.192s' in system tablespace"
+
+ER_IO_READ_ERROR
+ eng "IO Read error: (%lu, %s) %s"
+
+ER_IO_WRITE_ERROR
+ eng "IO Write error: (%lu, %s) %s"
+
+ER_TABLESPACE_MISSING
+ eng "Tablespace is missing for table '%-.192s'"
+
+ER_TABLESPACE_EXISTS
+ eng "Tablespace for table '%-.192s' exists. Please DISCARD the tablespace before IMPORT."
+
+ER_TABLESPACE_DISCARDED
+ eng "Tablespace has been discarded for table '%-.192s'"
+
+ER_INTERNAL_ERROR
+ eng "Internal error: %-.192s"
+
+ER_INNODB_IMPORT_ERROR
+ eng "ALTER TABLE '%-.192s' IMPORT TABLESPACE failed with error %lu : '%s'"
+
+ER_INNODB_INDEX_CORRUPT
+ eng "Index corrupt: %s"
+
+ER_INVALID_YEAR_COLUMN_LENGTH
+ eng "YEAR(%lu) column type is deprecated. Creating YEAR(4) column instead."
+ rus "Тип YEAR(%lu) более не поддерживаетÑÑ, вмеÑто него будет Ñоздана колонка Ñ Ñ‚Ð¸Ð¿Ð¾Ð¼ YEAR(4)."
+
+ER_NOT_VALID_PASSWORD
+ eng "Your password does not satisfy the current policy requirements"
+
+ER_MUST_CHANGE_PASSWORD
+ eng "You must SET PASSWORD before executing this statement"
+ bgn "ТрÑбва първо да Ñи Ñмените паролата ÑÑŠÑ SET PASSWORD за да можете да изпълните тази команда"
+
+ER_FK_NO_INDEX_CHILD
+ eng "Failed to add the foreign key constaint. Missing index for constraint '%s' in the foreign table '%s'"
+
+ER_FK_NO_INDEX_PARENT
+ eng "Failed to add the foreign key constaint. Missing index for constraint '%s' in the referenced table '%s'"
+
+ER_FK_FAIL_ADD_SYSTEM
+ eng "Failed to add the foreign key constraint '%s' to system tables"
+
+ER_FK_CANNOT_OPEN_PARENT
+ eng "Failed to open the referenced table '%s'"
+
+ER_FK_INCORRECT_OPTION
+ eng "Failed to add the foreign key constraint on table '%s'. Incorrect options in FOREIGN KEY constraint '%s'"
+
+ER_FK_DUP_NAME
+ eng "Duplicate foreign key constraint name '%s'"
+
+ER_PASSWORD_FORMAT
+ eng "The password hash doesn't have the expected format. Check if the correct password algorithm is being used with the PASSWORD() function."
+
+ER_FK_COLUMN_CANNOT_DROP
+ eng "Cannot drop column '%-.192s': needed in a foreign key constraint '%-.192s'"
+ ger "Kann Spalte '%-.192s' nicht löschen: wird für eine Fremdschlüsselbeschränkung '%-.192s' benötigt"
+
+ER_FK_COLUMN_CANNOT_DROP_CHILD
+ eng "Cannot drop column '%-.192s': needed in a foreign key constraint '%-.192s' of table '%-.192s'"
+ ger "Kann Spalte '%-.192s' nicht löschen: wird für eine Fremdschlüsselbeschränkung '%-.192s' der Tabelle '%-.192s' benötigt"
+
+ER_FK_COLUMN_NOT_NULL
+ eng "Column '%-.192s' cannot be NOT NULL: needed in a foreign key constraint '%-.192s' SET NULL"
+ ger "Spalte '%-.192s' kann nicht NOT NULL sein: wird für eine Fremdschlüsselbeschränkung '%-.192s' SET NULL benötigt"
+
+ER_DUP_INDEX
+ eng "Duplicate index '%-.64s' defined on the table '%-.64s.%-.64s'. This is deprecated and will be disallowed in a future release."
+
+ER_FK_COLUMN_CANNOT_CHANGE
+ eng "Cannot change column '%-.192s': used in a foreign key constraint '%-.192s'"
+
+ER_FK_COLUMN_CANNOT_CHANGE_CHILD
+ eng "Cannot change column '%-.192s': used in a foreign key constraint '%-.192s' of table '%-.192s'"
+
+ER_FK_CANNOT_DELETE_PARENT
+ eng "Cannot delete rows from table which is parent in a foreign key constraint '%-.192s' of table '%-.192s'"
+
+ER_MALFORMED_PACKET
+ eng "Malformed communication packet."
+
+ER_READ_ONLY_MODE
+ eng "Running in read-only mode"
+
+ER_GTID_NEXT_TYPE_UNDEFINED_GROUP
+ eng "When GTID_NEXT is set to a GTID, you must explicitly set it again after a COMMIT or ROLLBACK. If you see this error message in the slave SQL thread, it means that a table in the current transaction is transactional on the master and non-transactional on the slave. In a client connection, it means that you executed SET GTID_NEXT before a transaction and forgot to set GTID_NEXT to a different identifier or to 'AUTOMATIC' after COMMIT or ROLLBACK. Current GTID_NEXT is '%s'."
+
+ER_VARIABLE_NOT_SETTABLE_IN_SP
+ eng "The system variable %.200s cannot be set in stored procedures."
+
+ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF
+ eng "GTID_PURGED can only be set when GTID_MODE = ON."
+
+ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY
+ eng "GTID_PURGED can only be set when GTID_EXECUTED is empty."
+
+ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY
+ eng "GTID_PURGED can only be set when there are no ongoing transactions (not even in other clients)."
+
+ER_GTID_PURGED_WAS_CHANGED
+ eng "GTID_PURGED was changed from '%s' to '%s'."
+
+ER_GTID_EXECUTED_WAS_CHANGED
+ eng "GTID_EXECUTED was changed from '%s' to '%s'."
+
+ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES
+ eng "Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT, and both replicated and non replicated tables are written to."
+
+ER_ALTER_OPERATION_NOT_SUPPORTED 0A000
+ eng "%s is not supported for this operation. Try %s."
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON 0A000
+ eng "%s is not supported. Reason: %s. Try %s."
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY
+ eng "COPY algorithm requires a lock"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION
+ eng "Partition specific operations do not yet support LOCK/ALGORITHM"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME
+ eng "Columns participating in a foreign key are renamed"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE
+ eng "Cannot change column type INPLACE"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK
+ eng "Adding foreign keys needs foreign_key_checks=OFF"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE
+ eng "Creating unique indexes with IGNORE requires COPY algorithm to remove duplicate rows"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK
+ eng "Dropping a primary key is not allowed without also adding a new primary key"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC
+ eng "Adding an auto-increment column requires a lock"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS
+ eng "Cannot replace hidden FTS_DOC_ID with a user-visible one"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS
+ eng "Cannot drop or rename FTS_DOC_ID"
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS
+ eng "Fulltext index creation requires a lock"
+
+ER_SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE
+ eng "sql_slave_skip_counter can not be set when the server is running with GTID_MODE = ON. Instead, for each transaction that you want to skip, generate an empty transaction with the same GTID as the transaction"
+
+ER_DUP_UNKNOWN_IN_INDEX 23000
+ cze "Zdvojený klÃ­Ä (Äíslo klíÄe '%-.192s')"
+ dan "Flere ens nøgler for indeks '%-.192s'"
+ nla "Dubbele ingang voor zoeksleutel '%-.192s'"
+ eng "Duplicate entry for key '%-.192s'"
+ est "Kattuv väärtus võtmele '%-.192s'"
+ fre "Duplicata du champ pour la clef '%-.192s'"
+ ger "Doppelter Eintrag für Schlüssel '%-.192s'"
+ greek "Διπλή εγγÏαφή για το κλειδί '%-.192s'"
+ hun "Duplikalt bejegyzes a '%-.192s' kulcs szerint."
+ ita "Valore duplicato per la chiave '%-.192s'"
+ jpn "ã¯ç´¢å¼• '%-.192s' ã§é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚"
+ kor "ì¤‘ë³µëœ ìž…ë ¥ ê°’: key '%-.192s'"
+ nor "Like verdier for nøkkel '%-.192s'"
+ norwegian-ny "Like verdiar for nykkel '%-.192s'"
+ pol "Powtórzone wystąpienie dla klucza '%-.192s'"
+ por "Entrada duplicada para a chave '%-.192s'"
+ rum "Cimpul e duplicat pentru cheia '%-.192s'"
+ rus "ДублирующаÑÑÑ Ð·Ð°Ð¿Ð¸ÑÑŒ по ключу '%-.192s'"
+ serbian "Dupliran unos za kljuÄ '%-.192s'"
+ slo "Opakovaný kÄ¾ÃºÄ (Äíslo kľúÄa '%-.192s')"
+ spa "Entrada duplicada para la clave '%-.192s'"
+ swe "Dublett för nyckel '%-.192s'"
+ ukr "Дублюючий Ð·Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° '%-.192s'"
+
+ER_IDENT_CAUSES_TOO_LONG_PATH
+ eng "Long database name and identifier for object resulted in path length exceeding %d characters. Path: '%s'."
+
+ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL
+ eng "cannot silently convert NULL values, as required in this SQL_MODE"
+
+ER_MUST_CHANGE_PASSWORD_LOGIN
+ eng "Your password has expired. To log in you must change it using a client that supports expired passwords."
+ bgn "Паролата ви е изтекла. За да влезете трÑбва да Ñ Ñмените използвайки клиент който поддрържа такива пароли."
+
+ER_ROW_IN_WRONG_PARTITION
+ eng "Found a row in wrong partition %s"
+ swe "Hittade en rad i fel partition %s"
+
+ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX
+ eng "Cannot schedule event %s, relay-log name %s, position %s to Worker thread because its size %lu exceeds %lu of slave_pending_jobs_size_max."
+
+ER_INNODB_NO_FT_USES_PARSER
+ eng "Cannot CREATE FULLTEXT INDEX WITH PARSER on InnoDB table"
+ER_BINLOG_LOGICAL_CORRUPTION
+ eng "The binary log file '%s' is logically corrupted: %s"
+
+ER_WARN_PURGE_LOG_IN_USE
+ eng "file %s was not purged because it was being read by %d thread(s), purged only %d out of %d files."
+
+ER_WARN_PURGE_LOG_IS_ACTIVE
+ eng "file %s was not purged because it is the active log file."
+
+ER_AUTO_INCREMENT_CONFLICT
+ eng "Auto-increment value in UPDATE conflicts with internally generated values"
+
+WARN_ON_BLOCKHOLE_IN_RBR
+ eng "Row events are not logged for %s statements that modify BLACKHOLE tables in row format. Table(s): '%-.192s'"
+
+ER_SLAVE_MI_INIT_REPOSITORY
+ eng "Slave failed to initialize master info structure from the repository"
+
+ER_SLAVE_RLI_INIT_REPOSITORY
+ eng "Slave failed to initialize relay log info structure from the repository"
+
+ER_ACCESS_DENIED_CHANGE_USER_ERROR 28000
+ eng "Access denied trying to change to user '%-.48s'@'%-.64s' (using password: %s). Disconnecting."
+ bgn "Отказан доÑтъп при опит за ÑмÑна към потребител %-.48s'@'%-.64s' (използвана парола: %s). ЗатварÑне на връзката."
+
+ER_INNODB_READ_ONLY
+ eng "InnoDB is in read only mode."
+
+ER_STOP_SLAVE_SQL_THREAD_TIMEOUT
+ eng "STOP SLAVE command execution is incomplete: Slave SQL thread got the stop signal, thread is busy, SQL thread will stop once the current task is complete."
+
+ER_STOP_SLAVE_IO_THREAD_TIMEOUT
+ eng "STOP SLAVE command execution is incomplete: Slave IO thread got the stop signal, thread is busy, IO thread will stop once the current task is complete."
+
+ER_TABLE_CORRUPT
+ eng "Operation cannot be performed. The table '%-.64s.%-.64s' is missing, corrupt or contains bad data."
+
+ER_TEMP_FILE_WRITE_FAILURE
+ eng "Temporary file write failure."
+
+ER_INNODB_FT_AUX_NOT_HEX_ID
+ eng "Upgrade index name failed, please use create index(alter table) algorithm copy to rebuild index."
+
+
+#
+# MariaDB error messages section starts here
+#
+
+# The following is here to allow us to detect if there was missing
+# error messages in the errmsg.sys file
+
+ER_LAST_MYSQL_ERROR_MESSAGE
+ eng ""
+
+# MariaDB error numbers starts from 1900
+start-error-number 1900
+
+ER_VCOL_BASED_ON_VCOL
+ eng "A computed column cannot be based on a computed column"
+ER_VIRTUAL_COLUMN_FUNCTION_IS_NOT_ALLOWED
+ eng "Function or expression is not allowed for column '%s'"
+ER_DATA_CONVERSION_ERROR_FOR_VIRTUAL_COLUMN
+ eng "Generated value for computed column '%s' cannot be converted to type '%s'"
+ER_PRIMARY_KEY_BASED_ON_VIRTUAL_COLUMN
+ eng "Primary key cannot be defined upon a computed column"
+ER_KEY_BASED_ON_GENERATED_VIRTUAL_COLUMN
+ eng "Key/Index cannot be defined on a non-stored computed column"
+ER_WRONG_FK_OPTION_FOR_VIRTUAL_COLUMN
+ eng "Cannot define foreign key with %s clause on a computed column"
+ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN
+ eng "The value specified for computed column '%s' in table '%s' ignored"
+ER_UNSUPPORTED_ACTION_ON_VIRTUAL_COLUMN
+ eng "This is not yet supported for computed columns"
+ER_CONST_EXPR_IN_VCOL
+ eng "Constant expression in computed column function is not allowed"
+ER_ROW_EXPR_FOR_VCOL
+ eng "Expression for computed column cannot return a row"
+ER_UNSUPPORTED_ENGINE_FOR_VIRTUAL_COLUMNS
+ eng "%s storage engine does not support computed columns"
+ER_UNKNOWN_OPTION
+ eng "Unknown option '%-.64s'"
+ER_BAD_OPTION_VALUE
+ eng "Incorrect value '%-.64s' for option '%-.64s'"
+ER_UNUSED_6
+ eng "You should never see it"
+ER_UNUSED_7
+ eng "You should never see it"
+ER_UNUSED_8
+ eng "You should never see it"
+ER_DATA_OVERFLOW 22003
+ eng "Got overflow when converting '%-.128s' to %-.32s. Value truncated."
+ER_DATA_TRUNCATED 22003
+ eng "Truncated value '%-.128s' when converting to %-.32s"
+ER_BAD_DATA 22007
+ eng "Encountered illegal value '%-.128s' when converting to %-.32s"
+ER_DYN_COL_WRONG_FORMAT
+ eng "Encountered illegal format of dynamic column string"
+ER_DYN_COL_IMPLEMENTATION_LIMIT
+ eng "Dynamic column implementation limit reached"
+ER_DYN_COL_DATA 22007
+ eng "Illegal value used as argument of dynamic column function"
+ER_DYN_COL_WRONG_CHARSET
+ eng "Dynamic column contains unknown character set"
+ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES
+ eng "At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'."
+ER_QUERY_CACHE_IS_DISABLED
+ eng "Query cache is disabled (resize or similar command in progress); repeat this command later"
+ER_QUERY_CACHE_IS_GLOBALY_DISABLED
+ eng "Query cache is globally disabled and you can't enable it only for this session"
+ER_VIEW_ORDERBY_IGNORED
+ eng "View '%-.192s'.'%-.192s' ORDER BY clause ignored because there is other ORDER BY clause already."
+ER_CONNECTION_KILLED 70100
+ eng "Connection was killed"
+ER_UNUSED_12
+ eng "You should never see it"
+ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SKIP_REPLICATION
+ eng "Cannot modify @@session.skip_replication inside a transaction"
+ER_STORED_FUNCTION_PREVENTS_SWITCH_SKIP_REPLICATION
+ eng "Cannot modify @@session.skip_replication inside a stored function or trigger"
+ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT
+ eng "Query execution was interrupted. The query examined at least %llu rows, which exceeds LIMIT ROWS EXAMINED (%llu). The query result may be incomplete."
+ER_NO_SUCH_TABLE_IN_ENGINE 42S02
+ eng "Table '%-.192s.%-.192s' doesn't exist in engine"
+ swe "Det finns ingen tabell som heter '%-.192s.%-.192s' i handlern"
+ER_TARGET_NOT_EXPLAINABLE
+ eng "Target is not running an EXPLAINable command"
+ER_CONNECTION_ALREADY_EXISTS
+ eng "Connection '%.*s' conflicts with existing connection '%.*s'"
+ER_MASTER_LOG_PREFIX
+ eng "Master '%.*s': "
+ER_CANT_START_STOP_SLAVE
+ eng "Can't %s SLAVE '%.*s'"
+ER_SLAVE_STARTED
+ eng "SLAVE '%.*s' started"
+ER_SLAVE_STOPPED
+ eng "SLAVE '%.*s' stopped"
+ER_SQL_DISCOVER_ERROR
+ eng "Engine %s failed to discover table %`-.192s.%`-.192s with '%s'"
+ER_FAILED_GTID_STATE_INIT
+ eng "Failed initializing replication GTID state"
+ER_INCORRECT_GTID_STATE
+ eng "Could not parse GTID list"
+ER_CANNOT_UPDATE_GTID_STATE
+ eng "Could not update replication slave gtid state"
+ER_DUPLICATE_GTID_DOMAIN
+ eng "GTID %u-%u-%llu and %u-%u-%llu conflict (duplicate domain id %u)"
+ER_GTID_OPEN_TABLE_FAILED
+ eng "Failed to open %s.%s"
+ ger "Öffnen von %s.%s fehlgeschlagen"
+ER_GTID_POSITION_NOT_FOUND_IN_BINLOG
+ eng "Connecting slave requested to start from GTID %u-%u-%llu, which is not in the master's binlog"
+ER_CANNOT_LOAD_SLAVE_GTID_STATE
+ eng "Failed to load replication slave GTID position from table %s.%s"
+ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG
+ eng "Specified GTID %u-%u-%llu conflicts with the binary log which contains a more recent GTID %u-%u-%llu. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos."
+ER_MASTER_GTID_POS_MISSING_DOMAIN
+ eng "Specified value for @@gtid_slave_pos contains no value for replication domain %u. This conflicts with the binary log which contains GTID %u-%u-%llu. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos."
+ER_UNTIL_REQUIRES_USING_GTID
+ eng "START SLAVE UNTIL master_gtid_pos requires that slave is using GTID"
+ER_GTID_STRICT_OUT_OF_ORDER
+ eng "An attempt was made to binlog GTID %u-%u-%llu which would create an out-of-order sequence number with existing GTID %u-%u-%llu, and gtid strict mode is enabled."
+ER_GTID_START_FROM_BINLOG_HOLE
+ eng "The binlog on the master is missing the GTID %u-%u-%llu requested by the slave (even though a subsequent sequence number does exist), and GTID strict mode is enabled"
+ER_SLAVE_UNEXPECTED_MASTER_SWITCH
+ eng "Unexpected GTID received from master after reconnect. This normally indicates that the master server was replaced without restarting the slave threads. %s"
+ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO
+ eng "Cannot modify @@session.gtid_domain_id or @@session.gtid_seq_no inside a transaction"
+ER_STORED_FUNCTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO
+ eng "Cannot modify @@session.gtid_domain_id or @@session.gtid_seq_no inside a stored function or trigger"
+ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2
+ eng "Connecting slave requested to start from GTID %u-%u-%llu, which is not in the master's binlog. Since the master's binlog contains GTIDs with higher sequence numbers, it probably means that the slave has diverged due to executing extra erroneous transactions"
+ER_BINLOG_MUST_BE_EMPTY
+ eng "This operation is not allowed if any GTID has been logged to the binary log. Run RESET MASTER first to erase the log"
+ER_NO_SUCH_QUERY
+ eng "Unknown query id: %lld"
+ ger "Unbekannte Abfrage-ID: %lld"
+ rus "ÐеизвеÑтный номер запроÑа: %lld"
+ER_BAD_BASE64_DATA
+ eng "Bad base64 data as position %u"
+ER_INVALID_ROLE OP000
+ eng "Invalid role specification %`s."
+ rum "Rolul %`s este invalid."
+ER_INVALID_CURRENT_USER 0L000
+ eng "The current user is invalid."
+ rum "Utilizatorul curent este invalid."
+ER_CANNOT_GRANT_ROLE
+ eng "Cannot grant role '%s' to: %s."
+ rum "Rolul '%s' nu poate fi acordat catre: %s."
+ER_CANNOT_REVOKE_ROLE
+ eng "Cannot revoke role '%s' from: %s."
+ rum "Rolul '%s' nu poate fi revocat de la: %s."
+ER_CHANGE_SLAVE_PARALLEL_THREADS_ACTIVE
+ eng "Cannot change @@slave_parallel_threads while another change is in progress"
+ER_PRIOR_COMMIT_FAILED
+ eng "Commit failed due to failure of an earlier commit on which this one depends"
+ER_IT_IS_A_VIEW 42S02
+ eng "'%-.192s' is a view"
+ER_SLAVE_SKIP_NOT_IN_GTID
+ eng "When using GTID, @@sql_slave_skip_counter can not be used. Instead, setting @@gtid_slave_pos explicitly can be used to skip to after a given GTID position."
+ER_TABLE_DEFINITION_TOO_BIG
+ eng "The definition for table %`s is too big"
diff --git a/sql/signal_handler.cc b/sql/signal_handler.cc
new file mode 100644
index 00000000000..3fadbcd088f
--- /dev/null
+++ b/sql/signal_handler.cc
@@ -0,0 +1,276 @@
+/* Copyright (c) 2011, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2014, SkySQL Ab.
+
+ 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 "my_global.h"
+#include <signal.h>
+
+//#include "sys_vars.h"
+#include <keycache.h>
+#include "mysqld.h"
+#include "sql_class.h"
+#include "my_stacktrace.h"
+
+#ifdef __WIN__
+#include <crtdbg.h>
+#define SIGNAL_FMT "exception 0x%x"
+#else
+#define SIGNAL_FMT "signal %d"
+#endif
+
+/*
+ We are handling signals/exceptions in this file.
+ Any global variables we read should be 'volatile sig_atomic_t'
+ to guarantee that we read some consistent value.
+ */
+static volatile sig_atomic_t segfaulted= 0;
+extern ulong max_used_connections;
+extern volatile sig_atomic_t calling_initgroups;
+#ifdef HAVE_NPTL
+extern volatile sig_atomic_t ld_assume_kernel_is_set;
+#endif
+
+extern const char *optimizer_switch_names[];
+
+/**
+ * Handler for fatal signals on POSIX, exception handler on Windows.
+ *
+ * Fatal events (seg.fault, bus error etc.) will trigger
+ * this signal handler. The handler will try to dump relevant
+ * debugging information to stderr and dump a core image.
+ *
+ * POSIX : Signal handlers should, if possible, only use a set of 'safe' system
+ * calls and library functions. A list of safe calls in POSIX systems
+ * are available at:
+ * http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html
+ *
+ * @param sig Signal number /Exception code
+*/
+extern "C" sig_handler handle_fatal_signal(int sig)
+{
+ time_t curr_time;
+ struct tm tm;
+#ifdef HAVE_STACKTRACE
+ THD *thd;
+#endif
+
+ if (segfaulted)
+ {
+ my_safe_printf_stderr("Fatal " SIGNAL_FMT " while backtracing\n", sig);
+ _exit(1); /* Quit without running destructors */
+ }
+
+ segfaulted = 1;
+
+ curr_time= my_time(0);
+ localtime_r(&curr_time, &tm);
+
+ my_safe_printf_stderr("%02d%02d%02d %2d:%02d:%02d ",
+ tm.tm_year % 100, tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ if (opt_expect_abort
+#ifdef _WIN32
+ && sig == EXCEPTION_BREAKPOINT /* __debugbreak in my_sigabrt_hander() */
+#else
+ && sig == SIGABRT
+#endif
+ )
+ {
+ fprintf(stderr,"[Note] mysqld did an expected abort\n");
+ goto end;
+ }
+
+ my_safe_printf_stderr("[ERROR] mysqld got " SIGNAL_FMT " ;\n",sig);
+
+ my_safe_printf_stderr("%s",
+ "This could be because you hit a bug. It is also possible that this binary\n"
+ "or one of the libraries it was linked against is corrupt, improperly built,\n"
+ "or misconfigured. This error can also be caused by malfunctioning hardware.\n\n");
+
+ my_safe_printf_stderr("%s",
+ "To report this bug, see http://kb.askmonty.org/en/reporting-bugs\n\n");
+
+ my_safe_printf_stderr("%s",
+ "We will try our best to scrape up some info that will hopefully help\n"
+ "diagnose the problem, but since we have already crashed, \n"
+ "something is definitely wrong and this may fail.\n\n");
+
+ set_server_version();
+ my_safe_printf_stderr("Server version: %s\n", server_version);
+
+ my_safe_printf_stderr("key_buffer_size=%lu\n",
+ (ulong) dflt_key_cache->key_cache_mem_size);
+
+ my_safe_printf_stderr("read_buffer_size=%ld\n",
+ (long) global_system_variables.read_buff_size);
+
+ my_safe_printf_stderr("max_used_connections=%lu\n",
+ (ulong) max_used_connections);
+
+ my_safe_printf_stderr("max_threads=%u\n",
+ (uint) thread_scheduler->max_threads +
+ (uint) extra_max_connections);
+
+ my_safe_printf_stderr("thread_count=%u\n", (uint) thread_count);
+
+ my_safe_printf_stderr("It is possible that mysqld could use up to \n"
+ "key_buffer_size + "
+ "(read_buffer_size + sort_buffer_size)*max_threads = "
+ "%lu K bytes of memory\n",
+ (ulong)(dflt_key_cache->key_cache_mem_size +
+ (global_system_variables.read_buff_size +
+ global_system_variables.sortbuff_size) *
+ (thread_scheduler->max_threads + extra_max_connections) +
+ (max_connections + extra_max_connections)* sizeof(THD)) / 1024);
+
+ my_safe_printf_stderr("%s",
+ "Hope that's ok; if not, decrease some variables in the equation.\n\n");
+
+#ifdef HAVE_STACKTRACE
+ thd= current_thd;
+
+ if (opt_stack_trace)
+ {
+ my_safe_printf_stderr("Thread pointer: 0x%p\n", thd);
+ my_safe_printf_stderr("%s",
+ "Attempting backtrace. You can use the following "
+ "information to find out\n"
+ "where mysqld died. If you see no messages after this, something went\n"
+ "terribly wrong...\n");
+ my_print_stacktrace(thd ? (uchar*) thd->thread_stack : NULL,
+ (ulong)my_thread_stack_size);
+ }
+ if (thd)
+ {
+ const char *kreason= "UNKNOWN";
+ switch (thd->killed) {
+ case NOT_KILLED:
+ case KILL_HARD_BIT:
+ kreason= "NOT_KILLED";
+ break;
+ case KILL_BAD_DATA:
+ case KILL_BAD_DATA_HARD:
+ kreason= "KILL_BAD_DATA";
+ break;
+ case KILL_CONNECTION:
+ case KILL_CONNECTION_HARD:
+ kreason= "KILL_CONNECTION";
+ break;
+ case KILL_QUERY:
+ case KILL_QUERY_HARD:
+ kreason= "KILL_QUERY";
+ break;
+ case KILL_SYSTEM_THREAD:
+ case KILL_SYSTEM_THREAD_HARD:
+ kreason= "KILL_SYSTEM_THREAD";
+ break;
+ case KILL_SERVER:
+ case KILL_SERVER_HARD:
+ kreason= "KILL_SERVER";
+ break;
+ case ABORT_QUERY:
+ case ABORT_QUERY_HARD:
+ kreason= "ABORT_QUERY";
+ break;
+ }
+ my_safe_printf_stderr("%s", "\n"
+ "Trying to get some variables.\n"
+ "Some pointers may be invalid and cause the dump to abort.\n");
+
+ my_safe_printf_stderr("Query (%p): ", thd->query());
+ my_safe_print_str(thd->query(), MY_MIN(65536U, thd->query_length()));
+ my_safe_printf_stderr("\nConnection ID (thread ID): %lu\n",
+ (ulong) thd->thread_id);
+ my_safe_printf_stderr("Status: %s\n\n", kreason);
+ my_safe_printf_stderr("%s", "Optimizer switch: ");
+ ulonglong optsw= thd->variables.optimizer_switch;
+ for (uint i= 0; optimizer_switch_names[i+1]; i++, optsw >>= 1)
+ {
+ if (i)
+ my_safe_printf_stderr("%s", ",");
+ my_safe_printf_stderr("%s=%s",
+ optimizer_switch_names[i], optsw & 1 ? "on" : "off");
+ }
+ my_safe_printf_stderr("%s", "\n\n");
+ }
+ my_safe_printf_stderr("%s",
+ "The manual page at "
+ "http://dev.mysql.com/doc/mysql/en/crashing.html contains\n"
+ "information that should help you find out what is causing the crash.\n");
+
+#endif /* HAVE_STACKTRACE */
+
+#ifdef HAVE_INITGROUPS
+ if (calling_initgroups)
+ {
+ my_safe_printf_stderr("%s", "\n"
+ "This crash occured while the server was calling initgroups(). This is\n"
+ "often due to the use of a mysqld that is statically linked against \n"
+ "glibc and configured to use LDAP in /etc/nsswitch.conf.\n"
+ "You will need to either upgrade to a version of glibc that does not\n"
+ "have this problem (2.3.4 or later when used with nscd),\n"
+ "disable LDAP in your nsswitch.conf, or use a "
+ "mysqld that is not statically linked.\n");
+ }
+#endif
+
+#ifdef HAVE_NPTL
+ if (thd_lib_detected == THD_LIB_LT && !ld_assume_kernel_is_set)
+ {
+ my_safe_printf_stderr("%s",
+ "You are running a statically-linked LinuxThreads binary on an NPTL\n"
+ "system. This can result in crashes on some distributions due to "
+ "LT/NPTL conflicts.\n"
+ "You should either build a dynamically-linked binary, "
+ "or force LinuxThreads\n"
+ "to be used with the LD_ASSUME_KERNEL environment variable.\n"
+ "Please consult the documentation for your distribution "
+ "on how to do that.\n");
+ }
+#endif
+
+ if (locked_in_memory)
+ {
+ my_safe_printf_stderr("%s", "\n"
+ "The \"--memlock\" argument, which was enabled, "
+ "uses system calls that are\n"
+ "unreliable and unstable on some operating systems and "
+ "operating-system versions (notably, some versions of Linux).\n"
+ "This crash could be due to use of those buggy OS calls.\n"
+ "You should consider whether you really need the "
+ "\"--memlock\" parameter and/or consult the OS distributer about "
+ "\"mlockall\" bugs.\n");
+ }
+
+#ifdef HAVE_WRITE_CORE
+ if (test_flags & TEST_CORE_ON_SIGNAL)
+ {
+ my_safe_printf_stderr("%s", "Writing a core file\n");
+ fflush(stderr);
+ my_write_core(sig);
+ }
+#endif
+
+end:
+#ifndef __WIN__
+ /*
+ Quit, without running destructors (etc.)
+ On Windows, do not terminate, but pass control to exception filter.
+ */
+ _exit(1); // Using _exit(), since exit() is not async signal safe
+#else
+ return;
+#endif
+}
diff --git a/sql/slave.cc b/sql/slave.cc
new file mode 100644
index 00000000000..30d55f8bc2a
--- /dev/null
+++ b/sql/slave.cc
@@ -0,0 +1,6979 @@
+/* Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+
+/**
+ @addtogroup Replication
+ @{
+
+ @file
+
+ @brief Code to run the io thread and the sql thread on the
+ replication slave.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "slave.h"
+#include "sql_parse.h" // execute_init_command
+#include "sql_table.h" // mysql_rm_table
+#include "rpl_mi.h"
+#include "rpl_rli.h"
+#include "sql_repl.h"
+#include "rpl_filter.h"
+#include "repl_failsafe.h"
+#include "transaction.h"
+#include <thr_alarm.h>
+#include <my_dir.h>
+#include <sql_common.h>
+#include <errmsg.h>
+#include <mysqld_error.h>
+#include <mysys_err.h>
+#include "rpl_handler.h"
+#include <signal.h>
+#include <mysql.h>
+#include <myisam.h>
+
+#include "sql_base.h" // close_thread_tables
+#include "tztime.h" // struct Time_zone
+#include "log_event.h" // Rotate_log_event,
+ // Create_file_log_event,
+ // Format_description_log_event
+
+#ifdef HAVE_REPLICATION
+
+#include "rpl_tblmap.h"
+#include "debug_sync.h"
+#include "rpl_parallel.h"
+
+
+#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
+
+#define MAX_SLAVE_RETRY_PAUSE 5
+/*
+ a parameter of sql_slave_killed() to defer the killed status
+*/
+#define SLAVE_WAIT_GROUP_DONE 60
+bool use_slave_mask = 0;
+MY_BITMAP slave_error_mask;
+char slave_skip_error_names[SHOW_VAR_FUNC_BUFF_SIZE];
+
+char* slave_load_tmpdir = 0;
+Master_info *active_mi= 0;
+Master_info_index *master_info_index;
+my_bool replicate_same_server_id;
+ulonglong relay_log_space_limit = 0;
+LEX_STRING default_master_connection_name= { (char*) "", 0 };
+
+/*
+ When slave thread exits, we need to remember the temporary tables so we
+ can re-use them on slave start.
+
+ TODO: move the vars below under Master_info
+*/
+
+int disconnect_slave_event_count = 0, abort_slave_event_count = 0;
+
+static pthread_key(Master_info*, RPL_MASTER_INFO);
+
+enum enum_slave_reconnect_actions
+{
+ SLAVE_RECON_ACT_REG= 0,
+ SLAVE_RECON_ACT_DUMP= 1,
+ SLAVE_RECON_ACT_EVENT= 2,
+ SLAVE_RECON_ACT_MAX
+};
+
+enum enum_slave_reconnect_messages
+{
+ SLAVE_RECON_MSG_WAIT= 0,
+ SLAVE_RECON_MSG_KILLED_WAITING= 1,
+ SLAVE_RECON_MSG_AFTER= 2,
+ SLAVE_RECON_MSG_FAILED= 3,
+ SLAVE_RECON_MSG_COMMAND= 4,
+ SLAVE_RECON_MSG_KILLED_AFTER= 5,
+ SLAVE_RECON_MSG_MAX
+};
+
+static const char *reconnect_messages[SLAVE_RECON_ACT_MAX][SLAVE_RECON_MSG_MAX]=
+{
+ {
+ "Waiting to reconnect after a failed registration on master",
+ "Slave I/O thread killed while waitnig to reconnect after a failed \
+registration on master",
+ "Reconnecting after a failed registration on master",
+ "failed registering on master, reconnecting to try again, \
+log '%s' at position %s%s",
+ "COM_REGISTER_SLAVE",
+ "Slave I/O thread killed during or after reconnect"
+ },
+ {
+ "Waiting to reconnect after a failed binlog dump request",
+ "Slave I/O thread killed while retrying master dump",
+ "Reconnecting after a failed binlog dump request",
+ "failed dump request, reconnecting to try again, log '%s' at position %s%s",
+ "COM_BINLOG_DUMP",
+ "Slave I/O thread killed during or after reconnect"
+ },
+ {
+ "Waiting to reconnect after a failed master event read",
+ "Slave I/O thread killed while waiting to reconnect after a failed read",
+ "Reconnecting after a failed master event read",
+ "Slave I/O thread: Failed reading log event, reconnecting to retry, \
+log '%s' at position %s%s",
+ "",
+ "Slave I/O thread killed during or after a reconnect done to recover from \
+failed read"
+ }
+};
+
+
+typedef enum { SLAVE_THD_IO, SLAVE_THD_SQL} SLAVE_THD_TYPE;
+
+static int process_io_rotate(Master_info* mi, Rotate_log_event* rev);
+static int process_io_create_file(Master_info* mi, Create_file_log_event* cev);
+static bool wait_for_relay_log_space(Relay_log_info* rli);
+static bool io_slave_killed(Master_info* mi);
+static bool sql_slave_killed(rpl_group_info *rgi);
+static int init_slave_thread(THD*, Master_info *, SLAVE_THD_TYPE);
+static void print_slave_skip_errors(void);
+static int safe_connect(THD* thd, MYSQL* mysql, Master_info* mi);
+static int safe_reconnect(THD*, MYSQL*, Master_info*, bool);
+static int connect_to_master(THD*, MYSQL*, Master_info*, bool, bool);
+static Log_event* next_event(rpl_group_info* rgi, ulonglong *event_size);
+static int queue_event(Master_info* mi,const char* buf,ulong event_len);
+static int terminate_slave_thread(THD *, mysql_mutex_t *, mysql_cond_t *,
+ volatile uint *, bool);
+static bool check_io_slave_killed(Master_info *mi, const char *info);
+static bool send_show_master_info_header(THD *, bool, size_t);
+static bool send_show_master_info_data(THD *, Master_info *, bool, String *);
+/*
+ Function to set the slave's max_allowed_packet based on the value
+ of slave_max_allowed_packet.
+
+ @in_param thd Thread handler for slave
+ @in_param mysql MySQL connection handle
+*/
+
+static void set_slave_max_allowed_packet(THD *thd, MYSQL *mysql)
+{
+ DBUG_ENTER("set_slave_max_allowed_packet");
+ // thd and mysql must be valid
+ DBUG_ASSERT(thd && mysql);
+
+ thd->variables.max_allowed_packet= slave_max_allowed_packet;
+ thd->net.max_packet_size= slave_max_allowed_packet;
+ /*
+ Adding MAX_LOG_EVENT_HEADER_LEN to the max_packet_size on the I/O
+ thread and the mysql->option max_allowed_packet, since a
+ replication event can become this much larger than
+ the corresponding packet (query) sent from client to master.
+ */
+ thd->net.max_packet_size+= MAX_LOG_EVENT_HEADER;
+ /*
+ Skipping the setting of mysql->net.max_packet size to slave
+ max_allowed_packet since this is done during mysql_real_connect.
+ */
+ mysql->options.max_allowed_packet=
+ slave_max_allowed_packet+MAX_LOG_EVENT_HEADER;
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Find out which replications threads are running
+
+ SYNOPSIS
+ init_thread_mask()
+ mask Return value here
+ mi master_info for slave
+ inverse If set, returns which threads are not running
+
+ IMPLEMENTATION
+ Get a bit mask for which threads are running so that we can later restart
+ these threads.
+
+ RETURN
+ mask If inverse == 0, running threads
+ If inverse == 1, stopped threads
+*/
+
+void init_thread_mask(int* mask,Master_info* mi,bool inverse)
+{
+ bool set_io = mi->slave_running, set_sql = mi->rli.slave_running;
+ register int tmp_mask=0;
+ DBUG_ENTER("init_thread_mask");
+
+ if (set_io)
+ tmp_mask |= SLAVE_IO;
+ if (set_sql)
+ tmp_mask |= SLAVE_SQL;
+ if (inverse)
+ tmp_mask^= (SLAVE_IO | SLAVE_SQL);
+ *mask = tmp_mask;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ lock_slave_threads()
+*/
+
+void lock_slave_threads(Master_info* mi)
+{
+ DBUG_ENTER("lock_slave_threads");
+
+ //TODO: see if we can do this without dual mutex
+ mysql_mutex_lock(&mi->run_lock);
+ mysql_mutex_lock(&mi->rli.run_lock);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ unlock_slave_threads()
+*/
+
+void unlock_slave_threads(Master_info* mi)
+{
+ DBUG_ENTER("unlock_slave_threads");
+
+ //TODO: see if we can do this without dual mutex
+ mysql_mutex_unlock(&mi->rli.run_lock);
+ mysql_mutex_unlock(&mi->run_lock);
+ DBUG_VOID_RETURN;
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_thread_key key_thread_slave_io, key_thread_slave_sql;
+
+static PSI_thread_info all_slave_threads[]=
+{
+ { &key_thread_slave_io, "slave_io", PSI_FLAG_GLOBAL},
+ { &key_thread_slave_sql, "slave_sql", PSI_FLAG_GLOBAL}
+};
+
+static void init_slave_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_slave_threads);
+ PSI_server->register_thread(category, all_slave_threads, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+
+static bool slave_init_thread_running;
+
+
+pthread_handler_t
+handle_slave_init(void *arg __attribute__((unused)))
+{
+ THD *thd;
+
+ my_thread_init();
+ thd= new THD;
+ thd->thread_stack= (char*) &thd; /* Set approximate stack start */
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->thread_id= thread_id++;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ thd->system_thread = SYSTEM_THREAD_SLAVE_INIT;
+ thd->store_globals();
+ thd->security_ctx->skip_grants();
+ thd->set_command(COM_DAEMON);
+
+ thd_proc_info(thd, "Loading slave GTID position from table");
+ if (rpl_load_gtid_slave_state(thd))
+ sql_print_warning("Failed to load slave replication state from table "
+ "%s.%s: %u: %s", "mysql",
+ rpl_gtid_slave_state_table_name.str,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ delete thd;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ my_thread_end();
+
+ mysql_mutex_lock(&LOCK_slave_init);
+ slave_init_thread_running= false;
+ mysql_cond_broadcast(&COND_slave_init);
+ mysql_mutex_unlock(&LOCK_slave_init);
+
+ return 0;
+}
+
+
+/*
+ Start the slave init thread.
+
+ This thread is used to load the GTID state from mysql.gtid_slave_pos at
+ server start; reading from table requires valid THD, which is otherwise not
+ available during server init.
+*/
+static int
+run_slave_init_thread()
+{
+ pthread_t th;
+
+ slave_init_thread_running= true;
+ if (mysql_thread_create(key_thread_slave_init, &th, &connection_attrib,
+ handle_slave_init, NULL))
+ {
+ sql_print_error("Failed to create thread while initialising slave");
+ return 1;
+ }
+
+ mysql_mutex_lock(&LOCK_slave_init);
+ while (slave_init_thread_running)
+ mysql_cond_wait(&COND_slave_init, &LOCK_slave_init);
+ mysql_mutex_unlock(&LOCK_slave_init);
+
+ return 0;
+}
+
+
+/* Initialize slave structures */
+
+int init_slave()
+{
+ DBUG_ENTER("init_slave");
+ int error= 0;
+
+#ifdef HAVE_PSI_INTERFACE
+ init_slave_psi_keys();
+#endif
+
+ if (run_slave_init_thread())
+ return 1;
+
+ if (global_rpl_thread_pool.init(opt_slave_parallel_threads))
+ return 1;
+
+ /*
+ This is called when mysqld starts. Before client connections are
+ accepted. However bootstrap may conflict with us if it does START SLAVE.
+ So it's safer to take the lock.
+ */
+ mysql_mutex_lock(&LOCK_active_mi);
+
+ if (pthread_key_create(&RPL_MASTER_INFO, NULL))
+ goto err;
+
+ master_info_index= new Master_info_index;
+ if (!master_info_index || master_info_index->init_all_master_info())
+ {
+ sql_print_error("Failed to initialize multi master structures");
+ mysql_mutex_unlock(&LOCK_active_mi);
+ DBUG_RETURN(1);
+ }
+ if (!(active_mi= new Master_info(&default_master_connection_name,
+ relay_log_recovery)) ||
+ active_mi->error())
+ {
+ delete active_mi;
+ active_mi= 0;
+ goto err;
+ }
+
+ if (master_info_index->add_master_info(active_mi, FALSE))
+ {
+ delete active_mi;
+ active_mi= 0;
+ goto err;
+ }
+
+ /*
+ If --slave-skip-errors=... was not used, the string value for the
+ system variable has not been set up yet. Do it now.
+ */
+ if (!use_slave_mask)
+ {
+ print_slave_skip_errors();
+ }
+
+ /*
+ If master_host is not specified, try to read it from the master_info file.
+ If master_host is specified, create the master_info file if it doesn't
+ exists.
+ */
+
+ if (init_master_info(active_mi,master_info_file,relay_log_info_file,
+ 1, (SLAVE_IO | SLAVE_SQL)))
+ {
+ sql_print_error("Failed to initialize the master info structure");
+ goto err;
+ }
+
+ /* If server id is not set, start_slave_thread() will say it */
+
+ if (active_mi->host[0] && !opt_skip_slave_start)
+ {
+ if (start_slave_threads(1 /* need mutex */,
+ 0 /* no wait for start*/,
+ active_mi,
+ master_info_file,
+ relay_log_info_file,
+ SLAVE_IO | SLAVE_SQL))
+ {
+ sql_print_error("Failed to create slave threads");
+ goto err;
+ }
+ }
+
+end:
+ mysql_mutex_unlock(&LOCK_active_mi);
+ DBUG_RETURN(error);
+
+err:
+ sql_print_error("Failed to allocate memory for the Master Info structure");
+ error= 1;
+ goto end;
+}
+
+/*
+ Updates the master info based on the information stored in the
+ relay info and ignores relay logs previously retrieved by the IO
+ thread, which thus starts fetching again based on to the
+ group_master_log_pos and group_master_log_name. Eventually, the old
+ relay logs will be purged by the normal purge mechanism.
+
+ In the feature, we should improve this routine in order to avoid throwing
+ away logs that are safely stored in the disk. Note also that this recovery
+ routine relies on the correctness of the relay-log.info and only tolerates
+ coordinate problems in master.info.
+
+ In this function, there is no need for a mutex as the caller
+ (i.e. init_slave) already has one acquired.
+
+ Specifically, the following structures are updated:
+
+ 1 - mi->master_log_pos <-- rli->group_master_log_pos
+ 2 - mi->master_log_name <-- rli->group_master_log_name
+ 3 - It moves the relay log to the new relay log file, by
+ rli->group_relay_log_pos <-- BIN_LOG_HEADER_SIZE;
+ rli->event_relay_log_pos <-- BIN_LOG_HEADER_SIZE;
+ rli->group_relay_log_name <-- rli->relay_log.get_log_fname();
+ rli->event_relay_log_name <-- rli->relay_log.get_log_fname();
+
+ If there is an error, it returns (1), otherwise returns (0).
+ */
+int init_recovery(Master_info* mi, const char** errmsg)
+{
+ DBUG_ENTER("init_recovery");
+
+ Relay_log_info *rli= &mi->rli;
+ if (rli->group_master_log_name[0])
+ {
+ mi->master_log_pos= MY_MAX(BIN_LOG_HEADER_SIZE,
+ rli->group_master_log_pos);
+ strmake_buf(mi->master_log_name, rli->group_master_log_name);
+
+ sql_print_warning("Recovery from master pos %ld and file %s.",
+ (ulong) mi->master_log_pos, mi->master_log_name);
+
+ strmake_buf(rli->group_relay_log_name, rli->relay_log.get_log_fname());
+ strmake_buf(rli->event_relay_log_name, rli->relay_log.get_log_fname());
+
+ rli->group_relay_log_pos= rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE;
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Convert slave skip errors bitmap into a printable string.
+*/
+
+static void print_slave_skip_errors(void)
+{
+ /*
+ To be safe, we want 10 characters of room in the buffer for a number
+ plus terminators. Also, we need some space for constant strings.
+ 10 characters must be sufficient for a number plus {',' | '...'}
+ plus a NUL terminator. That is a max 6 digit number.
+ */
+ const size_t MIN_ROOM= 10;
+ DBUG_ENTER("print_slave_skip_errors");
+ DBUG_ASSERT(sizeof(slave_skip_error_names) > MIN_ROOM);
+ DBUG_ASSERT(MAX_SLAVE_ERROR <= 999999); // 6 digits
+
+ /* Make @@slave_skip_errors show the nice human-readable value. */
+ opt_slave_skip_errors= slave_skip_error_names;
+
+ if (!use_slave_mask || bitmap_is_clear_all(&slave_error_mask))
+ {
+ /* purecov: begin tested */
+ memcpy(slave_skip_error_names, STRING_WITH_LEN("OFF"));
+ /* purecov: end */
+ }
+ else if (bitmap_is_set_all(&slave_error_mask))
+ {
+ /* purecov: begin tested */
+ memcpy(slave_skip_error_names, STRING_WITH_LEN("ALL"));
+ /* purecov: end */
+ }
+ else
+ {
+ char *buff= slave_skip_error_names;
+ char *bend= buff + sizeof(slave_skip_error_names);
+ int errnum;
+
+ for (errnum= 0; errnum < MAX_SLAVE_ERROR; errnum++)
+ {
+ if (bitmap_is_set(&slave_error_mask, errnum))
+ {
+ if (buff + MIN_ROOM >= bend)
+ break; /* purecov: tested */
+ buff= int10_to_str(errnum, buff, 10);
+ *buff++= ',';
+ }
+ }
+ if (buff != slave_skip_error_names)
+ buff--; // Remove last ','
+ if (errnum < MAX_SLAVE_ERROR)
+ {
+ /* Couldn't show all errors */
+ buff= strmov(buff, "..."); /* purecov: tested */
+ }
+ *buff=0;
+ }
+ DBUG_PRINT("init", ("error_names: '%s'", slave_skip_error_names));
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Init function to set up array for errors that should be skipped for slave
+
+ SYNOPSIS
+ init_slave_skip_errors()
+ arg List of errors numbers to skip, separated with ','
+
+ NOTES
+ Called from get_options() in mysqld.cc on start-up
+*/
+
+void init_slave_skip_errors(const char* arg)
+{
+ const char *p;
+ DBUG_ENTER("init_slave_skip_errors");
+
+ if (my_bitmap_init(&slave_error_mask,0,MAX_SLAVE_ERROR,0))
+ {
+ fprintf(stderr, "Badly out of memory, please check your system status\n");
+ exit(1);
+ }
+ use_slave_mask = 1;
+ for (;my_isspace(system_charset_info,*arg);++arg)
+ /* empty */;
+ if (!my_strnncoll(system_charset_info,(uchar*)arg,4,(const uchar*)"all",4))
+ {
+ bitmap_set_all(&slave_error_mask);
+ print_slave_skip_errors();
+ DBUG_VOID_RETURN;
+ }
+ for (p= arg ; *p; )
+ {
+ long err_code;
+ if (!(p= str2int(p, 10, 0, LONG_MAX, &err_code)))
+ break;
+ if (err_code < MAX_SLAVE_ERROR)
+ bitmap_set_bit(&slave_error_mask,(uint)err_code);
+ while (!my_isdigit(system_charset_info,*p) && *p)
+ p++;
+ }
+ /* Convert slave skip errors bitmap into a printable string. */
+ print_slave_skip_errors();
+ DBUG_VOID_RETURN;
+}
+
+int terminate_slave_threads(Master_info* mi,int thread_mask,bool skip_lock)
+{
+ DBUG_ENTER("terminate_slave_threads");
+
+ if (!mi->inited)
+ DBUG_RETURN(0); /* successfully do nothing */
+ int error,force_all = (thread_mask & SLAVE_FORCE_ALL);
+ mysql_mutex_t *sql_lock = &mi->rli.run_lock, *io_lock = &mi->run_lock;
+ mysql_mutex_t *log_lock= mi->rli.relay_log.get_log_lock();
+
+ if (thread_mask & (SLAVE_SQL|SLAVE_FORCE_ALL))
+ {
+ DBUG_PRINT("info",("Terminating SQL thread"));
+ if (opt_slave_parallel_threads > 0 &&
+ mi->rli.abort_slave && mi->rli.stop_for_until)
+ {
+ mi->rli.stop_for_until= false;
+ mi->rli.parallel.stop_during_until();
+ }
+ else
+ mi->rli.abort_slave=1;
+ if ((error=terminate_slave_thread(mi->rli.sql_driver_thd, sql_lock,
+ &mi->rli.stop_cond,
+ &mi->rli.slave_running,
+ skip_lock)) &&
+ !force_all)
+ DBUG_RETURN(error);
+
+ mysql_mutex_lock(log_lock);
+
+ DBUG_PRINT("info",("Flushing relay-log info file."));
+ if (current_thd)
+ THD_STAGE_INFO(current_thd, stage_flushing_relay_log_info_file);
+ if (flush_relay_log_info(&mi->rli))
+ DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS);
+
+ if (my_sync(mi->rli.info_fd, MYF(MY_WME)))
+ DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS);
+
+ mysql_mutex_unlock(log_lock);
+
+ if (opt_slave_parallel_threads > 0 &&
+ !master_info_index->any_slave_sql_running())
+ rpl_parallel_inactivate_pool(&global_rpl_thread_pool);
+ }
+ if (thread_mask & (SLAVE_IO|SLAVE_FORCE_ALL))
+ {
+ DBUG_PRINT("info",("Terminating IO thread"));
+ mi->abort_slave=1;
+ if ((error=terminate_slave_thread(mi->io_thd, io_lock,
+ &mi->stop_cond,
+ &mi->slave_running,
+ skip_lock)) &&
+ !force_all)
+ DBUG_RETURN(error);
+
+ mysql_mutex_lock(log_lock);
+
+ DBUG_PRINT("info",("Flushing relay log and master info file."));
+ if (current_thd)
+ THD_STAGE_INFO(current_thd, stage_flushing_relay_log_and_master_info_repository);
+ if (flush_master_info(mi, TRUE, FALSE))
+ DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS);
+
+ if (mi->rli.relay_log.is_open() &&
+ my_sync(mi->rli.relay_log.get_log_file()->file, MYF(MY_WME)))
+ DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS);
+
+ if (my_sync(mi->fd, MYF(MY_WME)))
+ DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS);
+
+ mysql_mutex_unlock(log_lock);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Wait for a slave thread to terminate.
+
+ This function is called after requesting the thread to terminate
+ (by setting @c abort_slave member of @c Relay_log_info or @c
+ Master_info structure to 1). Termination of the thread is
+ controlled with the the predicate <code>*slave_running</code>.
+
+ Function will acquire @c term_lock before waiting on the condition
+ unless @c skip_lock is true in which case the mutex should be owned
+ by the caller of this function and will remain acquired after
+ return from the function.
+
+ @param term_lock
+ Associated lock to use when waiting for @c term_cond
+
+ @param term_cond
+ Condition that is signalled when the thread has terminated
+
+ @param slave_running
+ Pointer to predicate to check for slave thread termination
+
+ @param skip_lock
+ If @c true the lock will not be acquired before waiting on
+ the condition. In this case, it is assumed that the calling
+ function acquires the lock before calling this function.
+
+ @retval 0 All OK ER_SLAVE_NOT_RUNNING otherwise.
+
+ @note If the executing thread has to acquire term_lock (skip_lock
+ is false), the negative running status does not represent
+ any issue therefore no error is reported.
+
+ */
+static int
+terminate_slave_thread(THD *thd,
+ mysql_mutex_t *term_lock,
+ mysql_cond_t *term_cond,
+ volatile uint *slave_running,
+ bool skip_lock)
+{
+ DBUG_ENTER("terminate_slave_thread");
+ if (!skip_lock)
+ {
+ mysql_mutex_lock(term_lock);
+ }
+ else
+ {
+ mysql_mutex_assert_owner(term_lock);
+ }
+ if (!*slave_running)
+ {
+ if (!skip_lock)
+ {
+ /*
+ if run_lock (term_lock) is acquired locally then either
+ slave_running status is fine
+ */
+ mysql_mutex_unlock(term_lock);
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ DBUG_RETURN(ER_SLAVE_NOT_RUNNING);
+ }
+ }
+ DBUG_ASSERT(thd != 0);
+ THD_CHECK_SENTRY(thd);
+
+ /*
+ Is is critical to test if the slave is running. Otherwise, we might
+ be referening freed memory trying to kick it
+ */
+
+ while (*slave_running) // Should always be true
+ {
+ int error __attribute__((unused));
+ DBUG_PRINT("loop", ("killing slave thread"));
+
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+#ifndef DONT_USE_THR_ALARM
+ /*
+ Error codes from pthread_kill are:
+ EINVAL: invalid signal number (can't happen)
+ ESRCH: thread already killed (can happen, should be ignored)
+ */
+ int err __attribute__((unused))= pthread_kill(thd->real_id, thr_client_alarm);
+ DBUG_ASSERT(err != EINVAL);
+#endif
+ thd->awake(NOT_KILLED);
+
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+
+ /*
+ There is a small chance that slave thread might miss the first
+ alarm. To protect againts it, resend the signal until it reacts
+ */
+ struct timespec abstime;
+ set_timespec(abstime,2);
+ error= mysql_cond_timedwait(term_cond, term_lock, &abstime);
+ DBUG_ASSERT(error == ETIMEDOUT || error == 0);
+ }
+
+ DBUG_ASSERT(*slave_running == 0);
+
+ if (!skip_lock)
+ mysql_mutex_unlock(term_lock);
+ DBUG_RETURN(0);
+}
+
+
+int start_slave_thread(
+#ifdef HAVE_PSI_INTERFACE
+ PSI_thread_key thread_key,
+#endif
+ pthread_handler h_func, mysql_mutex_t *start_lock,
+ mysql_mutex_t *cond_lock,
+ mysql_cond_t *start_cond,
+ volatile uint *slave_running,
+ volatile ulong *slave_run_id,
+ Master_info* mi)
+{
+ pthread_t th;
+ ulong start_id;
+ int error;
+ DBUG_ENTER("start_slave_thread");
+
+ DBUG_ASSERT(mi->inited);
+
+ if (start_lock)
+ mysql_mutex_lock(start_lock);
+ if (!global_system_variables.server_id)
+ {
+ if (start_cond)
+ mysql_cond_broadcast(start_cond);
+ if (start_lock)
+ mysql_mutex_unlock(start_lock);
+ sql_print_error("Server id not set, will not start slave");
+ DBUG_RETURN(ER_BAD_SLAVE);
+ }
+
+ if (*slave_running)
+ {
+ if (start_cond)
+ mysql_cond_broadcast(start_cond);
+ if (start_lock)
+ mysql_mutex_unlock(start_lock);
+ DBUG_RETURN(ER_SLAVE_MUST_STOP);
+ }
+ start_id= *slave_run_id;
+ DBUG_PRINT("info",("Creating new slave thread"));
+ if ((error = mysql_thread_create(thread_key,
+ &th, &connection_attrib, h_func, (void*)mi)))
+ {
+ sql_print_error("Can't create slave thread (errno= %d).", error);
+ if (start_lock)
+ mysql_mutex_unlock(start_lock);
+ DBUG_RETURN(ER_SLAVE_THREAD);
+ }
+ if (start_cond && cond_lock) // caller has cond_lock
+ {
+ THD* thd = current_thd;
+ while (start_id == *slave_run_id)
+ {
+ DBUG_PRINT("sleep",("Waiting for slave thread to start"));
+ PSI_stage_info saved_stage= {0, "", 0};
+ thd->ENTER_COND(start_cond, cond_lock,
+ & stage_waiting_for_slave_thread_to_start,
+ & saved_stage);
+ /*
+ It is not sufficient to test this at loop bottom. We must test
+ it after registering the mutex in enter_cond(). If the kill
+ happens after testing of thd->killed and before the mutex is
+ registered, we could otherwise go waiting though thd->killed is
+ set.
+ */
+ if (!thd->killed)
+ mysql_cond_wait(start_cond, cond_lock);
+ thd->EXIT_COND(& saved_stage);
+ mysql_mutex_lock(cond_lock); // re-acquire it as exit_cond() released
+ if (thd->killed)
+ {
+ if (start_lock)
+ mysql_mutex_unlock(start_lock);
+ DBUG_RETURN(thd->killed_errno());
+ }
+ }
+ }
+ if (start_lock)
+ mysql_mutex_unlock(start_lock);
+ DBUG_RETURN(0);
+}
+
+
+/*
+ start_slave_threads()
+
+ NOTES
+ SLAVE_FORCE_ALL is not implemented here on purpose since it does not make
+ sense to do that for starting a slave--we always care if it actually
+ started the threads that were not previously running
+*/
+
+int start_slave_threads(bool need_slave_mutex, bool wait_for_start,
+ Master_info* mi, const char* master_info_fname,
+ const char* slave_info_fname, int thread_mask)
+{
+ mysql_mutex_t *lock_io=0, *lock_sql=0, *lock_cond_io=0, *lock_cond_sql=0;
+ mysql_cond_t* cond_io=0, *cond_sql=0;
+ int error=0;
+ const char *errmsg;
+ DBUG_ENTER("start_slave_threads");
+
+ if (need_slave_mutex)
+ {
+ lock_io = &mi->run_lock;
+ lock_sql = &mi->rli.run_lock;
+ }
+ if (wait_for_start)
+ {
+ cond_io = &mi->start_cond;
+ cond_sql = &mi->rli.start_cond;
+ lock_cond_io = &mi->run_lock;
+ lock_cond_sql = &mi->rli.run_lock;
+ }
+
+ /*
+ If we are using GTID and both SQL and IO threads are stopped, then get
+ rid of all relay logs.
+
+ Relay logs are not very useful when using GTID, except as a buffer
+ between the fetch in the IO thread and the apply in SQL thread. However
+ while one of the threads is running, they are in use and cannot be
+ removed.
+ */
+ if (mi->using_gtid != Master_info::USE_GTID_NO &&
+ !mi->slave_running && !mi->rli.slave_running)
+ {
+ /*
+ purge_relay_logs() clears the mi->rli.group_master_log_pos.
+ So save and restore them, like we do in CHANGE MASTER.
+ (We are not going to use them for GTID, but it might be worth to
+ keep them in case connection with GTID fails and user wants to go
+ back and continue with previous old-style replication coordinates).
+ */
+ mi->master_log_pos = MY_MAX(BIN_LOG_HEADER_SIZE,
+ mi->rli.group_master_log_pos);
+ strmake(mi->master_log_name, mi->rli.group_master_log_name,
+ sizeof(mi->master_log_name)-1);
+ purge_relay_logs(&mi->rli, NULL, 0, &errmsg);
+ mi->rli.group_master_log_pos= mi->master_log_pos;
+ strmake(mi->rli.group_master_log_name, mi->master_log_name,
+ sizeof(mi->rli.group_master_log_name)-1);
+
+ error= rpl_load_gtid_state(&mi->gtid_current_pos, mi->using_gtid ==
+ Master_info::USE_GTID_CURRENT_POS);
+ mi->events_queued_since_last_gtid= 0;
+ mi->gtid_reconnect_event_skip_count= 0;
+
+ mi->rli.restart_gtid_pos.reset();
+ }
+
+ if (!error && (thread_mask & SLAVE_IO))
+ error= start_slave_thread(
+#ifdef HAVE_PSI_INTERFACE
+ key_thread_slave_io,
+#endif
+ handle_slave_io, lock_io, lock_cond_io,
+ cond_io,
+ &mi->slave_running, &mi->slave_run_id,
+ mi);
+ if (!error && (thread_mask & SLAVE_SQL))
+ {
+ if (opt_slave_parallel_threads > 0)
+ error= rpl_parallel_activate_pool(&global_rpl_thread_pool);
+ if (!error)
+ error= start_slave_thread(
+#ifdef HAVE_PSI_INTERFACE
+ key_thread_slave_sql,
+#endif
+ handle_slave_sql, lock_sql, lock_cond_sql,
+ cond_sql,
+ &mi->rli.slave_running, &mi->rli.slave_run_id,
+ mi);
+ if (error)
+ terminate_slave_threads(mi, thread_mask & SLAVE_IO, !need_slave_mutex);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Release slave threads at time of executing shutdown.
+
+ SYNOPSIS
+ end_slave()
+*/
+
+void end_slave()
+{
+ DBUG_ENTER("end_slave");
+
+ /*
+ This is called when the server terminates, in close_connections().
+ It terminates slave threads. However, some CHANGE MASTER etc may still be
+ running presently. If a START SLAVE was in progress, the mutex lock below
+ will make us wait until slave threads have started, and START SLAVE
+ returns, then we terminate them here.
+
+ We can also be called by cleanup(), which only happens if some
+ startup parameter to the server was wrong.
+ */
+ mysql_mutex_lock(&LOCK_active_mi);
+ /* This will call terminate_slave_threads() on all connections */
+ delete master_info_index;
+ master_info_index= 0;
+ active_mi= 0;
+ mysql_mutex_unlock(&LOCK_active_mi);
+ global_rpl_thread_pool.destroy();
+ free_all_rpl_filters();
+ DBUG_VOID_RETURN;
+}
+
+static bool io_slave_killed(Master_info* mi)
+{
+ DBUG_ENTER("io_slave_killed");
+
+ DBUG_ASSERT(mi->slave_running); // tracking buffer overrun
+ DBUG_RETURN(mi->abort_slave || abort_loop || mi->io_thd->killed);
+}
+
+/**
+ The function analyzes a possible killed status and makes
+ a decision whether to accept it or not.
+ Normally upon accepting the sql thread goes to shutdown.
+ In the event of deffering decision @rli->last_event_start_time waiting
+ timer is set to force the killed status be accepted upon its expiration.
+
+ @param thd pointer to a THD instance
+ @param rli pointer to Relay_log_info instance
+
+ @return TRUE the killed status is recognized, FALSE a possible killed
+ status is deferred.
+*/
+static bool sql_slave_killed(rpl_group_info *rgi)
+{
+ bool ret= FALSE;
+ Relay_log_info *rli= rgi->rli;
+ THD *thd= rgi->thd;
+ DBUG_ENTER("sql_slave_killed");
+
+ DBUG_ASSERT(rli->sql_driver_thd == thd);
+ DBUG_ASSERT(rli->slave_running == 1);// tracking buffer overrun
+ if (abort_loop || rli->sql_driver_thd->killed || rli->abort_slave)
+ {
+ /*
+ The transaction should always be binlogged if OPTION_KEEP_LOG is
+ set (it implies that something can not be rolled back). And such
+ case should be regarded similarly as modifing a
+ non-transactional table because retrying of the transaction will
+ lead to an error or inconsistency as well.
+
+ Example: OPTION_KEEP_LOG is set if a temporary table is created
+ or dropped.
+
+ Note that transaction.all.modified_non_trans_table may be 1
+ if last statement was a single row transaction without begin/end.
+ Testing this flag must always be done in connection with
+ rli->is_in_group().
+ */
+
+ if ((thd->transaction.all.modified_non_trans_table ||
+ (thd->variables.option_bits & OPTION_KEEP_LOG)) &&
+ rli->is_in_group())
+ {
+ char msg_stopped[]=
+ "... Slave SQL Thread stopped with incomplete event group "
+ "having non-transactional changes. "
+ "If the group consists solely of row-based events, you can try "
+ "to restart the slave with --slave-exec-mode=IDEMPOTENT, which "
+ "ignores duplicate key, key not found, and similar errors (see "
+ "documentation for details).";
+
+ DBUG_PRINT("info", ("modified_non_trans_table: %d OPTION_BEGIN: %d "
+ "OPTION_KEEP_LOG: %d is_in_group: %d",
+ thd->transaction.all.modified_non_trans_table,
+ MY_TEST(thd->variables.option_bits & OPTION_BEGIN),
+ MY_TEST(thd->variables.option_bits & OPTION_KEEP_LOG),
+ rli->is_in_group()));
+
+ if (rli->abort_slave)
+ {
+ DBUG_PRINT("info",
+ ("Request to stop slave SQL Thread received while "
+ "applying a group that has non-transactional "
+ "changes; waiting for completion of the group ... "));
+
+ /*
+ Slave sql thread shutdown in face of unfinished group
+ modified Non-trans table is handled via a timer. The slave
+ may eventually give out to complete the current group and in
+ that case there might be issues at consequent slave restart,
+ see the error message. WL#2975 offers a robust solution
+ requiring to store the last exectuted event's coordinates
+ along with the group's coordianates instead of waiting with
+ @c last_event_start_time the timer.
+ */
+
+ if (rgi->last_event_start_time == 0)
+ rgi->last_event_start_time= my_time(0);
+ ret= difftime(my_time(0), rgi->last_event_start_time) <=
+ SLAVE_WAIT_GROUP_DONE ? FALSE : TRUE;
+
+ DBUG_EXECUTE_IF("stop_slave_middle_group",
+ DBUG_EXECUTE_IF("incomplete_group_in_relay_log",
+ ret= TRUE;);); // time is over
+
+ if (ret == 0)
+ {
+ rli->report(WARNING_LEVEL, 0, rgi->gtid_info(),
+ "Request to stop slave SQL Thread received while "
+ "applying a group that has non-transactional "
+ "changes; waiting for completion of the group ... ");
+ }
+ else
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(),
+ ER(ER_SLAVE_FATAL_ERROR), msg_stopped);
+ }
+ }
+ else
+ {
+ ret= TRUE;
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(),
+ ER(ER_SLAVE_FATAL_ERROR),
+ msg_stopped);
+ }
+ }
+ else
+ {
+ ret= TRUE;
+ }
+ }
+ if (ret)
+ rgi->last_event_start_time= 0;
+
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ skip_load_data_infile()
+
+ NOTES
+ This is used to tell a 3.23 master to break send_file()
+*/
+
+void skip_load_data_infile(NET *net)
+{
+ DBUG_ENTER("skip_load_data_infile");
+
+ (void)net_request_file(net, "/dev/null");
+ (void)my_net_read(net); // discard response
+ (void)net_write_command(net, 0, (uchar*) "", 0, (uchar*) "", 0); // ok
+ DBUG_VOID_RETURN;
+}
+
+
+bool net_request_file(NET* net, const char* fname)
+{
+ DBUG_ENTER("net_request_file");
+ DBUG_RETURN(net_write_command(net, 251, (uchar*) fname, strlen(fname),
+ (uchar*) "", 0));
+}
+
+/*
+ From other comments and tests in code, it looks like
+ sometimes Query_log_event and Load_log_event can have db == 0
+ (see rewrite_db() above for example)
+ (cases where this happens are unclear; it may be when the master is 3.23).
+*/
+
+const char *print_slave_db_safe(const char* db)
+{
+ DBUG_ENTER("*print_slave_db_safe");
+
+ DBUG_RETURN((db ? db : ""));
+}
+
+#endif /* HAVE_REPLICATION */
+
+int init_strvar_from_file(char *var, int max_size, IO_CACHE *f,
+ const char *default_val)
+{
+ uint length;
+ DBUG_ENTER("init_strvar_from_file");
+
+ if ((length=my_b_gets(f,var, max_size)))
+ {
+ char* last_p = var + length -1;
+ if (*last_p == '\n')
+ *last_p = 0; // if we stopped on newline, kill it
+ else
+ {
+ /*
+ If we truncated a line or stopped on last char, remove all chars
+ up to and including newline.
+ */
+ int c;
+ while (((c=my_b_get(f)) != '\n' && c != my_b_EOF)) ;
+ }
+ DBUG_RETURN(0);
+ }
+ else if (default_val)
+ {
+ strmake(var, default_val, max_size-1);
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(1);
+}
+
+
+/*
+ when moving these functions to mysys, don't forget to
+ remove slave.cc from libmysqld/CMakeLists.txt
+*/
+int init_intvar_from_file(int* var, IO_CACHE* f, int default_val)
+{
+ char buf[32];
+ DBUG_ENTER("init_intvar_from_file");
+
+
+ if (my_b_gets(f, buf, sizeof(buf)))
+ {
+ *var = atoi(buf);
+ DBUG_RETURN(0);
+ }
+ else if (default_val)
+ {
+ *var = default_val;
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(1);
+}
+
+int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val)
+{
+ char buf[16];
+ DBUG_ENTER("init_floatvar_from_file");
+
+
+ if (my_b_gets(f, buf, sizeof(buf)))
+ {
+ if (sscanf(buf, "%f", var) != 1)
+ DBUG_RETURN(1);
+ else
+ DBUG_RETURN(0);
+ }
+ else if (default_val != 0.0)
+ {
+ *var = default_val;
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(1);
+}
+
+
+/**
+ A master info read method
+
+ This function is called from @c init_master_info() along with
+ relatives to restore some of @c active_mi members.
+ Particularly, this function is responsible for restoring
+ IGNORE_SERVER_IDS list of servers whose events the slave is
+ going to ignore (to not log them in the relay log).
+ Items being read are supposed to be decimal output of values of a
+ type shorter or equal of @c long and separated by the single space.
+
+ @param arr @c DYNAMIC_ARRAY pointer to storage for servers id
+ @param f @c IO_CACHE pointer to the source file
+
+ @retval 0 All OK
+ @retval non-zero An error
+*/
+
+int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f)
+{
+ int ret= 0;
+ char buf[16 * (sizeof(long)*4 + 1)]; // static buffer to use most of times
+ char *buf_act= buf; // actual buffer can be dynamic if static is short
+ char *token, *last;
+ uint num_items; // number of items of `arr'
+ size_t read_size;
+ DBUG_ENTER("init_dynarray_intvar_from_file");
+
+ if ((read_size= my_b_gets(f, buf_act, sizeof(buf))) == 0)
+ {
+ return 0; // no line in master.info
+ }
+ if (read_size + 1 == sizeof(buf) && buf[sizeof(buf) - 2] != '\n')
+ {
+ /*
+ short read happend; allocate sufficient memory and make the 2nd read
+ */
+ char buf_work[(sizeof(long)*3 + 1)*16];
+ memcpy(buf_work, buf, sizeof(buf_work));
+ num_items= atoi(strtok_r(buf_work, " ", &last));
+ size_t snd_size;
+ /*
+ max size lower bound approximate estimation bases on the formula:
+ (the items number + items themselves) *
+ (decimal size + space) - 1 + `\n' + '\0'
+ */
+ size_t max_size= (1 + num_items) * (sizeof(long)*3 + 1) + 1;
+ buf_act= (char*) my_malloc(max_size, MYF(MY_WME));
+ memcpy(buf_act, buf, read_size);
+ snd_size= my_b_gets(f, buf_act + read_size, max_size - read_size);
+ if (snd_size == 0 ||
+ ((snd_size + 1 == max_size - read_size) && buf_act[max_size - 2] != '\n'))
+ {
+ /*
+ failure to make the 2nd read or short read again
+ */
+ ret= 1;
+ goto err;
+ }
+ }
+ token= strtok_r(buf_act, " ", &last);
+ if (token == NULL)
+ {
+ ret= 1;
+ goto err;
+ }
+ num_items= atoi(token);
+ for (uint i=0; i < num_items; i++)
+ {
+ token= strtok_r(NULL, " ", &last);
+ if (token == NULL)
+ {
+ ret= 1;
+ goto err;
+ }
+ else
+ {
+ ulong val= atol(token);
+ insert_dynamic(arr, (uchar *) &val);
+ }
+ }
+err:
+ if (buf_act != buf)
+ my_free(buf_act);
+ DBUG_RETURN(ret);
+}
+
+#ifdef HAVE_REPLICATION
+
+/*
+ Check if the error is caused by network.
+ @param[in] errorno Number of the error.
+ RETURNS:
+ TRUE network error
+ FALSE not network error
+*/
+
+bool is_network_error(uint errorno)
+{
+ if (errorno == CR_CONNECTION_ERROR ||
+ errorno == CR_CONN_HOST_ERROR ||
+ errorno == CR_SERVER_GONE_ERROR ||
+ errorno == CR_SERVER_LOST ||
+ errorno == ER_CON_COUNT_ERROR ||
+ errorno == ER_CONNECTION_KILLED ||
+ errorno == ER_NEW_ABORTING_CONNECTION ||
+ errorno == ER_NET_READ_INTERRUPTED ||
+ errorno == ER_SERVER_SHUTDOWN)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/*
+ Note that we rely on the master's version (3.23, 4.0.14 etc) instead of
+ relying on the binlog's version. This is not perfect: imagine an upgrade
+ of the master without waiting that all slaves are in sync with the master;
+ then a slave could be fooled about the binlog's format. This is what happens
+ when people upgrade a 3.23 master to 4.0 without doing RESET MASTER: 4.0
+ slaves are fooled. So we do this only to distinguish between 3.23 and more
+ recent masters (it's too late to change things for 3.23).
+
+ RETURNS
+ 0 ok
+ 1 error
+ 2 transient network problem, the caller should try to reconnect
+*/
+
+static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi)
+{
+ char err_buff[MAX_SLAVE_ERRMSG], err_buff2[MAX_SLAVE_ERRMSG];
+ const char* errmsg= 0;
+ int err_code= 0;
+ MYSQL_RES *master_res= 0;
+ MYSQL_ROW master_row;
+ uint version= mysql_get_server_version(mysql) / 10000;
+ DBUG_ENTER("get_master_version_and_clock");
+
+ /*
+ Free old description_event_for_queue (that is needed if we are in
+ a reconnection).
+ */
+ delete mi->rli.relay_log.description_event_for_queue;
+ mi->rli.relay_log.description_event_for_queue= 0;
+
+ if (!my_isdigit(&my_charset_bin,*mysql->server_version))
+ {
+ errmsg= err_buff2;
+ snprintf(err_buff2, sizeof(err_buff2),
+ "Master reported unrecognized MySQL version: %s",
+ mysql->server_version);
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, ER(err_code), err_buff2);
+ }
+ else
+ {
+ /*
+ Note the following switch will bug when we have MySQL branch 30 ;)
+ */
+ switch (version) {
+ case 0:
+ case 1:
+ case 2:
+ errmsg= err_buff2;
+ snprintf(err_buff2, sizeof(err_buff2),
+ "Master reported unrecognized MySQL version: %s",
+ mysql->server_version);
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, ER(err_code), err_buff2);
+ break;
+ case 3:
+ mi->rli.relay_log.description_event_for_queue= new
+ Format_description_log_event(1, mysql->server_version);
+ break;
+ case 4:
+ mi->rli.relay_log.description_event_for_queue= new
+ Format_description_log_event(3, mysql->server_version);
+ break;
+ default:
+ /*
+ Master is MySQL >=5.0. Give a default Format_desc event, so that we can
+ take the early steps (like tests for "is this a 3.23 master") which we
+ have to take before we receive the real master's Format_desc which will
+ override this one. Note that the Format_desc we create below is garbage
+ (it has the format of the *slave*); it's only good to help know if the
+ master is 3.23, 4.0, etc.
+ */
+ mi->rli.relay_log.description_event_for_queue= new
+ Format_description_log_event(4, mysql->server_version);
+ break;
+ }
+ }
+
+ /*
+ This does not mean that a 5.0 slave will be able to read a 6.0 master; but
+ as we don't know yet, we don't want to forbid this for now. If a 5.0 slave
+ can't read a 6.0 master, this will show up when the slave can't read some
+ events sent by the master, and there will be error messages.
+ */
+
+ if (errmsg)
+ goto err;
+
+ /* as we are here, we tried to allocate the event */
+ if (!mi->rli.relay_log.description_event_for_queue)
+ {
+ errmsg= "default Format_description_log_event";
+ err_code= ER_SLAVE_CREATE_EVENT_FAILURE;
+ sprintf(err_buff, ER(err_code), errmsg);
+ goto err;
+ }
+
+ /*
+ FD_q's (A) is set initially from RL's (A): FD_q.(A) := RL.(A).
+ It's necessary to adjust FD_q.(A) at this point because in the following
+ course FD_q is going to be dumped to RL.
+ Generally FD_q is derived from a received FD_m (roughly FD_q := FD_m)
+ in queue_event and the master's (A) is installed.
+ At one step with the assignment the Relay-Log's checksum alg is set to
+ a new value: RL.(A) := FD_q.(A). If the slave service is stopped
+ the last time assigned RL.(A) will be passed over to the restarting
+ service (to the current execution point).
+ RL.A is a "codec" to verify checksum in queue_event() almost all the time
+ the first fake Rotate event.
+ Starting from this point IO thread will executes the following checksum
+ warmup sequence of actions:
+
+ FD_q.A := RL.A,
+ A_m^0 := master.@@global.binlog_checksum,
+ {queue_event(R_f): verifies(R_f, A_m^0)},
+ {queue_event(FD_m): verifies(FD_m, FD_m.A), dump(FD_q), rotate(RL),
+ FD_q := FD_m, RL.A := FD_q.A)}
+
+ See legends definition on MYSQL_BIN_LOG::relay_log_checksum_alg
+ docs lines (binlog.h).
+ In above A_m^0 - the value of master's
+ @@binlog_checksum determined in the upcoming handshake (stored in
+ mi->checksum_alg_before_fd).
+
+
+ After the warm-up sequence IO gets to "normal" checksum verification mode
+ to use RL.A in
+
+ {queue_event(E_m): verifies(E_m, RL.A)}
+
+ until it has received a new FD_m.
+ */
+ mi->rli.relay_log.description_event_for_queue->checksum_alg=
+ mi->rli.relay_log.relay_log_checksum_alg;
+
+ DBUG_ASSERT(mi->rli.relay_log.description_event_for_queue->checksum_alg !=
+ BINLOG_CHECKSUM_ALG_UNDEF);
+ DBUG_ASSERT(mi->rli.relay_log.relay_log_checksum_alg !=
+ BINLOG_CHECKSUM_ALG_UNDEF);
+ /*
+ Compare the master and slave's clock. Do not die if master's clock is
+ unavailable (very old master not supporting UNIX_TIMESTAMP()?).
+ */
+
+#ifdef ENABLED_DEBUG_SYNC
+ DBUG_EXECUTE_IF("dbug.before_get_UNIX_TIMESTAMP",
+ {
+ const char act[]=
+ "now "
+ "wait_for signal.get_unix_timestamp";
+ DBUG_ASSERT(debug_sync_service);
+ DBUG_ASSERT(!debug_sync_set_action(current_thd,
+ STRING_WITH_LEN(act)));
+ };);
+#endif
+
+ master_res= NULL;
+ if (!mysql_real_query(mysql, STRING_WITH_LEN("SELECT UNIX_TIMESTAMP()")) &&
+ (master_res= mysql_store_result(mysql)) &&
+ (master_row= mysql_fetch_row(master_res)))
+ {
+ mi->clock_diff_with_master=
+ (long) (time((time_t*) 0) - strtoul(master_row[0], 0, 10));
+ }
+ else if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+ else if (is_network_error(mysql_errno(mysql)))
+ {
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "Get master clock failed with error: %s", mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ mi->clock_diff_with_master= 0; /* The "most sensible" value */
+ sql_print_warning("\"SELECT UNIX_TIMESTAMP()\" failed on master, "
+ "do not trust column Seconds_Behind_Master of SHOW "
+ "SLAVE STATUS. Error: %s (%d)",
+ mysql_error(mysql), mysql_errno(mysql));
+ }
+ if (master_res)
+ {
+ mysql_free_result(master_res);
+ master_res= NULL;
+ }
+
+ /*
+ Check that the master's server id and ours are different. Because if they
+ are equal (which can result from a simple copy of master's datadir to slave,
+ thus copying some my.cnf), replication will work but all events will be
+ skipped.
+ Do not die if SHOW VARIABLES LIKE 'SERVER_ID' fails on master (very old
+ master?).
+ Note: we could have put a @@SERVER_ID in the previous SELECT
+ UNIX_TIMESTAMP() instead, but this would not have worked on 3.23 masters.
+ */
+#ifdef ENABLED_DEBUG_SYNC
+ DBUG_EXECUTE_IF("dbug.before_get_SERVER_ID",
+ {
+ const char act[]=
+ "now "
+ "wait_for signal.get_server_id";
+ DBUG_ASSERT(debug_sync_service);
+ DBUG_ASSERT(!debug_sync_set_action(current_thd,
+ STRING_WITH_LEN(act)));
+ };);
+#endif
+ master_res= NULL;
+ master_row= NULL;
+ if (!mysql_real_query(mysql,
+ STRING_WITH_LEN("SHOW VARIABLES LIKE 'SERVER_ID'")) &&
+ (master_res= mysql_store_result(mysql)) &&
+ (master_row= mysql_fetch_row(master_res)))
+ {
+ if ((global_system_variables.server_id ==
+ (mi->master_id= strtoul(master_row[1], 0, 10))) &&
+ !mi->rli.replicate_same_server_id)
+ {
+ errmsg= "The slave I/O thread stops because master and slave have equal \
+MySQL server ids; these ids must be different for replication to work (or \
+the --replicate-same-server-id option must be used on slave but this does \
+not always make sense; please check the manual before using it).";
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, ER(err_code), errmsg);
+ goto err;
+ }
+ }
+ else if (mysql_errno(mysql))
+ {
+ if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+ else if (is_network_error(mysql_errno(mysql)))
+ {
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "Get master SERVER_ID failed with error: %s", mysql_error(mysql));
+ goto network_err;
+ }
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is encountered \
+when it try to get the value of SERVER_ID variable from master.";
+ err_code= mysql_errno(mysql);
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ else if (!master_row && master_res)
+ {
+ mi->report(WARNING_LEVEL, ER_UNKNOWN_SYSTEM_VARIABLE, NULL,
+ "Unknown system variable 'SERVER_ID' on master, \
+maybe it is a *VERY OLD MASTER*.");
+ }
+ if (master_res)
+ {
+ mysql_free_result(master_res);
+ master_res= NULL;
+ }
+ if (mi->master_id == 0 && mi->ignore_server_ids.elements > 0)
+ {
+ errmsg= "Slave configured with server id filtering could not detect the master server id.";
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, ER(err_code), errmsg);
+ goto err;
+ }
+
+ /*
+ Check that the master's global character_set_server and ours are the same.
+ Not fatal if query fails (old master?).
+ Note that we don't check for equality of global character_set_client and
+ collation_connection (neither do we prevent their setting in
+ set_var.cc). That's because from what I (Guilhem) have tested, the global
+ values of these 2 are never used (new connections don't use them).
+ We don't test equality of global collation_database either as it's is
+ going to be deprecated (made read-only) in 4.1 very soon.
+ The test is only relevant if master < 5.0.3 (we'll test only if it's older
+ than the 5 branch; < 5.0.3 was alpha...), as >= 5.0.3 master stores
+ charset info in each binlog event.
+ We don't do it for 3.23 because masters <3.23.50 hang on
+ SELECT @@unknown_var (BUG#7965 - see changelog of 3.23.50). So finally we
+ test only if master is 4.x.
+ */
+
+ /* redundant with rest of code but safer against later additions */
+ if (version == 3)
+ goto err;
+
+ if (version == 4)
+ {
+ master_res= NULL;
+ if (!mysql_real_query(mysql,
+ STRING_WITH_LEN("SELECT @@GLOBAL.COLLATION_SERVER")) &&
+ (master_res= mysql_store_result(mysql)) &&
+ (master_row= mysql_fetch_row(master_res)))
+ {
+ if (strcmp(master_row[0], global_system_variables.collation_server->name))
+ {
+ errmsg= "The slave I/O thread stops because master and slave have \
+different values for the COLLATION_SERVER global variable. The values must \
+be equal for the Statement-format replication to work";
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, ER(err_code), errmsg);
+ goto err;
+ }
+ }
+ else if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+ else if (is_network_error(mysql_errno(mysql)))
+ {
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "Get master COLLATION_SERVER failed with error: %s", mysql_error(mysql));
+ goto network_err;
+ }
+ else if (mysql_errno(mysql) != ER_UNKNOWN_SYSTEM_VARIABLE)
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is encountered \
+when it try to get the value of COLLATION_SERVER global variable from master.";
+ err_code= mysql_errno(mysql);
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ else
+ mi->report(WARNING_LEVEL, ER_UNKNOWN_SYSTEM_VARIABLE, NULL,
+ "Unknown system variable 'COLLATION_SERVER' on master, \
+maybe it is a *VERY OLD MASTER*. *NOTE*: slave may experience \
+inconsistency if replicated data deals with collation.");
+
+ if (master_res)
+ {
+ mysql_free_result(master_res);
+ master_res= NULL;
+ }
+ }
+
+ /*
+ Perform analogous check for time zone. Theoretically we also should
+ perform check here to verify that SYSTEM time zones are the same on
+ slave and master, but we can't rely on value of @@system_time_zone
+ variable (it is time zone abbreviation) since it determined at start
+ time and so could differ for slave and master even if they are really
+ in the same system time zone. So we are omiting this check and just
+ relying on documentation. Also according to Monty there are many users
+ who are using replication between servers in various time zones. Hence
+ such check will broke everything for them. (And now everything will
+ work for them because by default both their master and slave will have
+ 'SYSTEM' time zone).
+ This check is only necessary for 4.x masters (and < 5.0.4 masters but
+ those were alpha).
+ */
+ if (version == 4)
+ {
+ master_res= NULL;
+ if (!mysql_real_query(mysql, STRING_WITH_LEN("SELECT @@GLOBAL.TIME_ZONE")) &&
+ (master_res= mysql_store_result(mysql)) &&
+ (master_row= mysql_fetch_row(master_res)))
+ {
+ if (strcmp(master_row[0],
+ global_system_variables.time_zone->get_name()->ptr()))
+ {
+ errmsg= "The slave I/O thread stops because master and slave have \
+different values for the TIME_ZONE global variable. The values must \
+be equal for the Statement-format replication to work";
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, ER(err_code), errmsg);
+ goto err;
+ }
+ }
+ else if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+ else if (is_network_error(err_code= mysql_errno(mysql)))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Get master TIME_ZONE failed with error: %s",
+ mysql_error(mysql));
+ goto network_err;
+ }
+ else if (err_code == ER_UNKNOWN_SYSTEM_VARIABLE)
+ {
+ /* We use ERROR_LEVEL to get the error logged to file */
+ mi->report(ERROR_LEVEL, err_code, NULL,
+
+ "MySQL master doesn't have a TIME_ZONE variable. Note that"
+ "if your timezone is not same between master and slave, your "
+ "slave may get wrong data into timestamp columns");
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is encountered \
+when it try to get the value of TIME_ZONE global variable from master.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ if (master_res)
+ {
+ mysql_free_result(master_res);
+ master_res= NULL;
+ }
+ }
+
+ if (mi->heartbeat_period != 0.0)
+ {
+ char llbuf[22];
+ const char query_format[]= "SET @master_heartbeat_period= %s";
+ char query[sizeof(query_format) - 2 + sizeof(llbuf)];
+ /*
+ the period is an ulonglong of nano-secs.
+ */
+ llstr((ulonglong) (mi->heartbeat_period*1000000000UL), llbuf);
+ sprintf(query, query_format, llbuf);
+
+ DBUG_EXECUTE_IF("simulate_slave_heartbeat_network_error",
+ { static ulong dbug_count= 0;
+ if (++dbug_count < 3)
+ goto heartbeat_network_error;
+ });
+ if (mysql_real_query(mysql, query, strlen(query)))
+ {
+ if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+
+ if (is_network_error(mysql_errno(mysql)))
+ {
+ IF_DBUG(heartbeat_network_error: , )
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "SET @master_heartbeat_period to master failed with error: %s",
+ mysql_error(mysql));
+ mysql_free_result(mysql_store_result(mysql));
+ goto network_err;
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is encountered "
+ "when it tries to SET @master_heartbeat_period on master.";
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ mysql_free_result(mysql_store_result(mysql));
+ goto err;
+ }
+ }
+ mysql_free_result(mysql_store_result(mysql));
+ }
+
+ /*
+ Querying if master is capable to checksum and notifying it about own
+ CRC-awareness. The master's side instant value of @@global.binlog_checksum
+ is stored in the dump thread's uservar area as well as cached locally
+ to become known in consensus by master and slave.
+ */
+ DBUG_EXECUTE_IF("simulate_slave_unaware_checksum",
+ mi->checksum_alg_before_fd= BINLOG_CHECKSUM_ALG_OFF;
+ goto past_checksum;);
+ {
+ int rc;
+ const char query[]= "SET @master_binlog_checksum= @@global.binlog_checksum";
+ master_res= NULL;
+ mi->checksum_alg_before_fd= BINLOG_CHECKSUM_ALG_UNDEF; //initially undefined
+ /*
+ @c checksum_alg_before_fd is queried from master in this block.
+ If master is old checksum-unaware the value stays undefined.
+ Once the first FD will be received its alg descriptor will replace
+ the being queried one.
+ */
+ rc= mysql_real_query(mysql, query, strlen(query));
+ if (rc != 0)
+ {
+ if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+
+ if (mysql_errno(mysql) == ER_UNKNOWN_SYSTEM_VARIABLE)
+ {
+ /* Ignore this expected error if not a high error level */
+ if (global_system_variables.log_warnings > 1)
+ {
+ // this is tolerable as OM -> NS is supported
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "Notifying master by %s failed with "
+ "error: %s", query, mysql_error(mysql));
+ }
+ }
+ else
+ {
+ if (is_network_error(mysql_errno(mysql)))
+ {
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "Notifying master by %s failed with "
+ "error: %s", query, mysql_error(mysql));
+ mysql_free_result(mysql_store_result(mysql));
+ goto network_err;
+ }
+ else
+ {
+ errmsg= "The slave I/O thread stops because a fatal error is encountered "
+ "when it tried to SET @master_binlog_checksum on master.";
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ mysql_free_result(mysql_store_result(mysql));
+ goto err;
+ }
+ }
+ }
+ else
+ {
+ mysql_free_result(mysql_store_result(mysql));
+ if (!mysql_real_query(mysql,
+ STRING_WITH_LEN("SELECT @master_binlog_checksum")) &&
+ (master_res= mysql_store_result(mysql)) &&
+ (master_row= mysql_fetch_row(master_res)) &&
+ (master_row[0] != NULL))
+ {
+ mi->checksum_alg_before_fd= (uint8)
+ find_type(master_row[0], &binlog_checksum_typelib, 1) - 1;
+ // valid outcome is either of
+ DBUG_ASSERT(mi->checksum_alg_before_fd == BINLOG_CHECKSUM_ALG_OFF ||
+ mi->checksum_alg_before_fd == BINLOG_CHECKSUM_ALG_CRC32);
+ }
+ else if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+ else if (is_network_error(mysql_errno(mysql)))
+ {
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "Get master BINLOG_CHECKSUM failed with error: %s", mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ errmsg= "The slave I/O thread stops because a fatal error is encountered "
+ "when it tried to SELECT @master_binlog_checksum.";
+ err_code= ER_SLAVE_FATAL_ERROR;
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ mysql_free_result(mysql_store_result(mysql));
+ goto err;
+ }
+ }
+ if (master_res)
+ {
+ mysql_free_result(master_res);
+ master_res= NULL;
+ }
+ }
+
+#ifndef DBUG_OFF
+past_checksum:
+#endif
+
+ /*
+ Request the master to filter away events with the @@skip_replication flag
+ set, if we are running with
+ --replicate-events-marked-for-skip=FILTER_ON_MASTER.
+ */
+ if (opt_replicate_events_marked_for_skip == RPL_SKIP_FILTER_ON_MASTER)
+ {
+ if (mysql_real_query(mysql, STRING_WITH_LEN("SET skip_replication=1")))
+ {
+ err_code= mysql_errno(mysql);
+ if (is_network_error(err_code))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Setting master-side filtering of @@skip_replication failed "
+ "with error: %s", mysql_error(mysql));
+ goto network_err;
+ }
+ else if (err_code == ER_UNKNOWN_SYSTEM_VARIABLE)
+ {
+ /*
+ The master is older than the slave and does not support the
+ @@skip_replication feature.
+ This is not a problem, as such master will not generate events with
+ the @@skip_replication flag set in the first place. We will still
+ do slave-side filtering of such events though, to handle the (rare)
+ case of downgrading a master and receiving old events generated from
+ before the downgrade with the @@skip_replication flag set.
+ */
+ DBUG_PRINT("info", ("Old master does not support master-side filtering "
+ "of @@skip_replication events."));
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is "
+ "encountered when it tries to request filtering of events marked "
+ "with the @@skip_replication flag.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ }
+ }
+
+ /* Announce MariaDB slave capabilities. */
+ DBUG_EXECUTE_IF("simulate_slave_capability_none", goto after_set_capability;);
+ {
+ int rc= DBUG_EVALUATE_IF("simulate_slave_capability_old_53",
+ mysql_real_query(mysql, STRING_WITH_LEN("SET @mariadb_slave_capability="
+ STRINGIFY_ARG(MARIA_SLAVE_CAPABILITY_ANNOTATE))),
+ mysql_real_query(mysql, STRING_WITH_LEN("SET @mariadb_slave_capability="
+ STRINGIFY_ARG(MARIA_SLAVE_CAPABILITY_MINE))));
+ if (rc)
+ {
+ err_code= mysql_errno(mysql);
+ if (is_network_error(err_code))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Setting @mariadb_slave_capability failed with error: %s",
+ mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is "
+ "encountered when it tries to set @mariadb_slave_capability.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ }
+ }
+#ifndef DBUG_OFF
+after_set_capability:
+#endif
+
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ /* Request dump to start from slave replication GTID state. */
+ int rc;
+ char str_buf[256];
+ String query_str(str_buf, sizeof(str_buf), system_charset_info);
+ query_str.length(0);
+
+ /*
+ Read the master @@GLOBAL.gtid_domain_id variable.
+ This is mostly to check that master is GTID aware, but we could later
+ perhaps use it to check that different multi-source masters are correctly
+ configured with distinct domain_id.
+ */
+ if (mysql_real_query(mysql,
+ STRING_WITH_LEN("SELECT @@GLOBAL.gtid_domain_id")) ||
+ !(master_res= mysql_store_result(mysql)) ||
+ !(master_row= mysql_fetch_row(master_res)))
+ {
+ err_code= mysql_errno(mysql);
+ if (is_network_error(err_code))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Get master @@GLOBAL.gtid_domain_id failed with error: %s",
+ mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ errmsg= "The slave I/O thread stops because master does not support "
+ "MariaDB global transaction id. A fatal error is encountered when "
+ "it tries to SELECT @@GLOBAL.gtid_domain_id.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ }
+ mysql_free_result(master_res);
+ master_res= NULL;
+
+ query_str.append(STRING_WITH_LEN("SET @slave_connect_state='"),
+ system_charset_info);
+ if (mi->gtid_current_pos.append_to_string(&query_str))
+ {
+ err_code= ER_OUTOFMEMORY;
+ errmsg= "The slave I/O thread stops because a fatal out-of-memory "
+ "error is encountered when it tries to compute @slave_connect_state.";
+ sprintf(err_buff, "%s Error: Out of memory", errmsg);
+ goto err;
+ }
+ query_str.append(STRING_WITH_LEN("'"), system_charset_info);
+
+ rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
+ if (rc)
+ {
+ err_code= mysql_errno(mysql);
+ if (is_network_error(err_code))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Setting @slave_connect_state failed with error: %s",
+ mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is "
+ "encountered when it tries to set @slave_connect_state.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ }
+
+ query_str.length(0);
+ if (query_str.append(STRING_WITH_LEN("SET @slave_gtid_strict_mode="),
+ system_charset_info) ||
+ query_str.append_ulonglong(opt_gtid_strict_mode != false))
+ {
+ err_code= ER_OUTOFMEMORY;
+ errmsg= "The slave I/O thread stops because a fatal out-of-memory "
+ "error is encountered when it tries to set @slave_gtid_strict_mode.";
+ sprintf(err_buff, "%s Error: Out of memory", errmsg);
+ goto err;
+ }
+
+ rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
+ if (rc)
+ {
+ err_code= mysql_errno(mysql);
+ if (is_network_error(err_code))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Setting @slave_gtid_strict_mode failed with error: %s",
+ mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is "
+ "encountered when it tries to set @slave_gtid_strict_mode.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ }
+
+ query_str.length(0);
+ if (query_str.append(STRING_WITH_LEN("SET @slave_gtid_ignore_duplicates="),
+ system_charset_info) ||
+ query_str.append_ulonglong(opt_gtid_ignore_duplicates != false))
+ {
+ err_code= ER_OUTOFMEMORY;
+ errmsg= "The slave I/O thread stops because a fatal out-of-memory error "
+ "is encountered when it tries to set @slave_gtid_ignore_duplicates.";
+ sprintf(err_buff, "%s Error: Out of memory", errmsg);
+ goto err;
+ }
+
+ rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
+ if (rc)
+ {
+ err_code= mysql_errno(mysql);
+ if (is_network_error(err_code))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Setting @slave_gtid_ignore_duplicates failed with "
+ "error: %s", mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is "
+ "encountered when it tries to set @slave_gtid_ignore_duplicates.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ }
+
+ if (mi->rli.until_condition == Relay_log_info::UNTIL_GTID)
+ {
+ query_str.length(0);
+ query_str.append(STRING_WITH_LEN("SET @slave_until_gtid='"),
+ system_charset_info);
+ if (mi->rli.until_gtid_pos.append_to_string(&query_str))
+ {
+ err_code= ER_OUTOFMEMORY;
+ errmsg= "The slave I/O thread stops because a fatal out-of-memory "
+ "error is encountered when it tries to compute @slave_until_gtid.";
+ sprintf(err_buff, "%s Error: Out of memory", errmsg);
+ goto err;
+ }
+ query_str.append(STRING_WITH_LEN("'"), system_charset_info);
+
+ rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
+ if (rc)
+ {
+ err_code= mysql_errno(mysql);
+ if (is_network_error(err_code))
+ {
+ mi->report(ERROR_LEVEL, err_code, NULL,
+ "Setting @slave_until_gtid failed with error: %s",
+ mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ /* Fatal error */
+ errmsg= "The slave I/O thread stops because a fatal error is "
+ "encountered when it tries to set @slave_until_gtid.";
+ sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
+ goto err;
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ If we are not using GTID to connect this time, then instead request
+ the corresponding GTID position from the master, so that the user
+ can reconnect the next time using MASTER_GTID_POS=AUTO.
+ */
+ char quote_buf[2*sizeof(mi->master_log_name)+1];
+ char str_buf[28+2*sizeof(mi->master_log_name)+10];
+ String query(str_buf, sizeof(str_buf), system_charset_info);
+ query.length(0);
+
+ query.append("SELECT binlog_gtid_pos('");
+ escape_quotes_for_mysql(&my_charset_bin, quote_buf, sizeof(quote_buf),
+ mi->master_log_name, strlen(mi->master_log_name));
+ query.append(quote_buf);
+ query.append("',");
+ query.append_ulonglong(mi->master_log_pos);
+ query.append(")");
+
+ if (!mysql_real_query(mysql, query.c_ptr_safe(), query.length()) &&
+ (master_res= mysql_store_result(mysql)) &&
+ (master_row= mysql_fetch_row(master_res)) &&
+ (master_row[0] != NULL))
+ {
+ rpl_global_gtid_slave_state.load(mi->io_thd, master_row[0],
+ strlen(master_row[0]), false, false);
+ }
+ else if (check_io_slave_killed(mi, NULL))
+ goto slave_killed_err;
+ else if (is_network_error(mysql_errno(mysql)))
+ {
+ mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL,
+ "Get master GTID position failed with error: %s", mysql_error(mysql));
+ goto network_err;
+ }
+ else
+ {
+ /*
+ ToDo: If the master does not have the binlog_gtid_pos() function, it
+ just means that it is an old master with no GTID support, so we should
+ do nothing.
+
+ However, if binlog_gtid_pos() exists, but fails or returns NULL, then
+ it means that the requested position is not valid. We could use this
+ to catch attempts to replicate from within the middle of an event,
+ avoiding strange failures or possible corruption.
+ */
+ }
+ if (master_res)
+ {
+ mysql_free_result(master_res);
+ master_res= NULL;
+ }
+ }
+
+err:
+ if (errmsg)
+ {
+ if (master_res)
+ mysql_free_result(master_res);
+ DBUG_ASSERT(err_code != 0);
+ mi->report(ERROR_LEVEL, err_code, NULL, "%s", err_buff);
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+
+network_err:
+ if (master_res)
+ mysql_free_result(master_res);
+ DBUG_RETURN(2);
+
+slave_killed_err:
+ if (master_res)
+ mysql_free_result(master_res);
+ DBUG_RETURN(2);
+}
+
+
+static bool wait_for_relay_log_space(Relay_log_info* rli)
+{
+ bool slave_killed=0;
+ bool ignore_log_space_limit;
+ Master_info* mi = rli->mi;
+ PSI_stage_info old_stage;
+ THD* thd = mi->io_thd;
+ DBUG_ENTER("wait_for_relay_log_space");
+
+ mysql_mutex_lock(&rli->log_space_lock);
+ thd->ENTER_COND(&rli->log_space_cond,
+ &rli->log_space_lock,
+ &stage_waiting_for_relay_log_space,
+ &old_stage);
+ while (rli->log_space_limit < rli->log_space_total &&
+ !(slave_killed=io_slave_killed(mi)) &&
+ !rli->ignore_log_space_limit)
+ mysql_cond_wait(&rli->log_space_cond, &rli->log_space_lock);
+
+ ignore_log_space_limit= rli->ignore_log_space_limit;
+ rli->ignore_log_space_limit= 0;
+
+ thd->EXIT_COND(&old_stage);
+
+ /*
+ Makes the IO thread read only one event at a time
+ until the SQL thread is able to purge the relay
+ logs, freeing some space.
+
+ Therefore, once the SQL thread processes this next
+ event, it goes to sleep (no more events in the queue),
+ sets ignore_log_space_limit=true and wakes the IO thread.
+ However, this event may have been enough already for
+ the SQL thread to purge some log files, freeing
+ rli->log_space_total .
+
+ This guarantees that the SQL and IO thread move
+ forward only one event at a time (to avoid deadlocks),
+ when the relay space limit is reached. It also
+ guarantees that when the SQL thread is prepared to
+ rotate (to be able to purge some logs), the IO thread
+ will know about it and will rotate.
+
+ NOTE: The ignore_log_space_limit is only set when the SQL
+ thread sleeps waiting for events.
+
+ */
+
+ if (ignore_log_space_limit)
+ {
+#ifndef DBUG_OFF
+ {
+ char llbuf1[22], llbuf2[22];
+ DBUG_PRINT("info", ("log_space_limit=%s "
+ "log_space_total=%s "
+ "ignore_log_space_limit=%d "
+ "sql_force_rotate_relay=%d",
+ llstr(rli->log_space_limit,llbuf1),
+ llstr(rli->log_space_total,llbuf2),
+ (int) rli->ignore_log_space_limit,
+ (int) rli->sql_force_rotate_relay));
+ }
+#endif
+ if (rli->sql_force_rotate_relay)
+ {
+ mysql_mutex_lock(&mi->data_lock);
+ rotate_relay_log(rli->mi);
+ mysql_mutex_unlock(&mi->data_lock);
+ rli->sql_force_rotate_relay= false;
+ }
+ }
+
+ DBUG_RETURN(slave_killed);
+}
+
+
+/*
+ Builds a Rotate from the ignored events' info and writes it to relay log.
+
+ SYNOPSIS
+ write_ignored_events_info_to_relay_log()
+ thd pointer to I/O thread's thd
+ mi
+
+ DESCRIPTION
+ Slave I/O thread, going to die, must leave a durable trace of the
+ ignored events' end position for the use of the slave SQL thread, by
+ calling this function. Only that thread can call it (see assertion).
+ */
+static void write_ignored_events_info_to_relay_log(THD *thd, Master_info *mi)
+{
+ Relay_log_info *rli= &mi->rli;
+ mysql_mutex_t *log_lock= rli->relay_log.get_log_lock();
+ DBUG_ENTER("write_ignored_events_info_to_relay_log");
+
+ DBUG_ASSERT(thd == mi->io_thd);
+ mysql_mutex_lock(log_lock);
+ if (rli->ign_master_log_name_end[0] || rli->ign_gtids.count())
+ {
+ Rotate_log_event *rev= NULL;
+ Gtid_list_log_event *glev= NULL;
+ if (rli->ign_master_log_name_end[0])
+ {
+ rev= new Rotate_log_event(rli->ign_master_log_name_end,
+ 0, rli->ign_master_log_pos_end,
+ Rotate_log_event::DUP_NAME);
+ rli->ign_master_log_name_end[0]= 0;
+ if (unlikely(!(bool)rev))
+ mi->report(ERROR_LEVEL, ER_SLAVE_CREATE_EVENT_FAILURE, NULL,
+ ER(ER_SLAVE_CREATE_EVENT_FAILURE),
+ "Rotate_event (out of memory?),"
+ " SHOW SLAVE STATUS may be inaccurate");
+ }
+ if (rli->ign_gtids.count())
+ {
+ glev= new Gtid_list_log_event(&rli->ign_gtids,
+ Gtid_list_log_event::FLAG_IGN_GTIDS);
+ rli->ign_gtids.reset();
+ if (unlikely(!(bool)glev))
+ mi->report(ERROR_LEVEL, ER_SLAVE_CREATE_EVENT_FAILURE, NULL,
+ ER(ER_SLAVE_CREATE_EVENT_FAILURE),
+ "Gtid_list_event (out of memory?),"
+ " gtid_slave_pos may be inaccurate");
+ }
+
+ /* Can unlock before writing as slave SQL thd will soon see our event. */
+ mysql_mutex_unlock(log_lock);
+ if (rev)
+ {
+ DBUG_PRINT("info",("writing a Rotate event to track down ignored events"));
+ rev->server_id= 0; // don't be ignored by slave SQL thread
+ if (unlikely(rli->relay_log.append(rev)))
+ mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL,
+ ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
+ "failed to write a Rotate event"
+ " to the relay log, SHOW SLAVE STATUS may be"
+ " inaccurate");
+ delete rev;
+ }
+ if (glev)
+ {
+ DBUG_PRINT("info",("writing a Gtid_list event to track down ignored events"));
+ glev->server_id= 0; // don't be ignored by slave SQL thread
+ glev->set_artificial_event(); // Don't mess up Exec_Master_Log_Pos
+ if (unlikely(rli->relay_log.append(glev)))
+ mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL,
+ ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
+ "failed to write a Gtid_list event to the relay log, "
+ "gtid_slave_pos may be inaccurate");
+ delete glev;
+ }
+ if (likely (rev || glev))
+ {
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ if (flush_master_info(mi, TRUE, TRUE))
+ sql_print_error("Failed to flush master info file");
+ }
+ }
+ else
+ mysql_mutex_unlock(log_lock);
+ DBUG_VOID_RETURN;
+}
+
+
+int register_slave_on_master(MYSQL* mysql, Master_info *mi,
+ bool *suppress_warnings)
+{
+ uchar buf[1024], *pos= buf;
+ uint report_host_len=0, report_user_len=0, report_password_len=0;
+ DBUG_ENTER("register_slave_on_master");
+
+ *suppress_warnings= FALSE;
+ if (report_host)
+ report_host_len= strlen(report_host);
+ if (report_host_len > HOSTNAME_LENGTH)
+ {
+ sql_print_warning("The length of report_host is %d. "
+ "It is larger than the max length(%d), so this "
+ "slave cannot be registered to the master.",
+ report_host_len, HOSTNAME_LENGTH);
+ DBUG_RETURN(0);
+ }
+
+ if (report_user)
+ report_user_len= strlen(report_user);
+ if (report_user_len > USERNAME_LENGTH)
+ {
+ sql_print_warning("The length of report_user is %d. "
+ "It is larger than the max length(%d), so this "
+ "slave cannot be registered to the master.",
+ report_user_len, USERNAME_LENGTH);
+ DBUG_RETURN(0);
+ }
+
+ if (report_password)
+ report_password_len= strlen(report_password);
+ if (report_password_len > MAX_PASSWORD_LENGTH)
+ {
+ sql_print_warning("The length of report_password is %d. "
+ "It is larger than the max length(%d), so this "
+ "slave cannot be registered to the master.",
+ report_password_len, MAX_PASSWORD_LENGTH);
+ DBUG_RETURN(0);
+ }
+
+ int4store(pos, global_system_variables.server_id); pos+= 4;
+ pos= net_store_data(pos, (uchar*) report_host, report_host_len);
+ pos= net_store_data(pos, (uchar*) report_user, report_user_len);
+ pos= net_store_data(pos, (uchar*) report_password, report_password_len);
+ int2store(pos, (uint16) report_port); pos+= 2;
+ /*
+ Fake rpl_recovery_rank, which was removed in BUG#13963,
+ so that this server can register itself on old servers,
+ see BUG#49259.
+ */
+ int4store(pos, /* rpl_recovery_rank */ 0); pos+= 4;
+ /* The master will fill in master_id */
+ int4store(pos, 0); pos+= 4;
+
+ if (simple_command(mysql, COM_REGISTER_SLAVE, buf, (size_t) (pos- buf), 0))
+ {
+ if (mysql_errno(mysql) == ER_NET_READ_INTERRUPTED)
+ {
+ *suppress_warnings= TRUE; // Suppress reconnect warning
+ }
+ else if (!check_io_slave_killed(mi, NULL))
+ {
+ char buf[256];
+ my_snprintf(buf, sizeof(buf), "%s (Errno: %d)", mysql_error(mysql),
+ mysql_errno(mysql));
+ mi->report(ERROR_LEVEL, ER_SLAVE_MASTER_COM_FAILURE, NULL,
+ ER(ER_SLAVE_MASTER_COM_FAILURE), "COM_REGISTER_SLAVE", buf);
+ }
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Execute a SHOW SLAVE STATUS statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @param mi Pointer to Master_info object for the IO thread.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+
+bool show_master_info(THD *thd, Master_info *mi, bool full)
+{
+ DBUG_ENTER("show_master_info");
+ String gtid_pos;
+
+ if (full && rpl_global_gtid_slave_state.tostring(&gtid_pos, NULL, 0))
+ DBUG_RETURN(TRUE);
+ if (send_show_master_info_header(thd, full, gtid_pos.length()))
+ DBUG_RETURN(TRUE);
+ if (send_show_master_info_data(thd, mi, full, &gtid_pos))
+ DBUG_RETURN(TRUE);
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+static bool send_show_master_info_header(THD *thd, bool full,
+ size_t gtid_pos_length)
+{
+ List<Item> field_list;
+ Protocol *protocol= thd->protocol;
+ Master_info *mi;
+ DBUG_ENTER("show_master_info_header");
+
+ if (full)
+ {
+ field_list.push_back(new Item_empty_string("Connection_name",
+ MAX_CONNECTION_NAME));
+ field_list.push_back(new Item_empty_string("Slave_SQL_State",
+ 30));
+ }
+
+ field_list.push_back(new Item_empty_string("Slave_IO_State",
+ 30));
+ field_list.push_back(new Item_empty_string("Master_Host",
+ sizeof(mi->host)));
+ field_list.push_back(new Item_empty_string("Master_User",
+ sizeof(mi->user)));
+ field_list.push_back(new Item_return_int("Master_Port", 7,
+ MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_return_int("Connect_Retry", 10,
+ MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_empty_string("Master_Log_File",
+ FN_REFLEN));
+ field_list.push_back(new Item_return_int("Read_Master_Log_Pos", 10,
+ MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_empty_string("Relay_Log_File",
+ FN_REFLEN));
+ field_list.push_back(new Item_return_int("Relay_Log_Pos", 10,
+ MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_empty_string("Relay_Master_Log_File",
+ FN_REFLEN));
+ field_list.push_back(new Item_empty_string("Slave_IO_Running", 3));
+ field_list.push_back(new Item_empty_string("Slave_SQL_Running", 3));
+ field_list.push_back(new Item_empty_string("Replicate_Do_DB", 20));
+ field_list.push_back(new Item_empty_string("Replicate_Ignore_DB", 20));
+ field_list.push_back(new Item_empty_string("Replicate_Do_Table", 20));
+ field_list.push_back(new Item_empty_string("Replicate_Ignore_Table", 23));
+ field_list.push_back(new Item_empty_string("Replicate_Wild_Do_Table", 24));
+ field_list.push_back(new Item_empty_string("Replicate_Wild_Ignore_Table",
+ 28));
+ field_list.push_back(new Item_return_int("Last_Errno", 4, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_empty_string("Last_Error", 20));
+ field_list.push_back(new Item_return_int("Skip_Counter", 10,
+ MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_return_int("Exec_Master_Log_Pos", 10,
+ MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_return_int("Relay_Log_Space", 10,
+ MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_empty_string("Until_Condition", 6));
+ field_list.push_back(new Item_empty_string("Until_Log_File", FN_REFLEN));
+ field_list.push_back(new Item_return_int("Until_Log_Pos", 10,
+ MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_empty_string("Master_SSL_Allowed", 7));
+ field_list.push_back(new Item_empty_string("Master_SSL_CA_File",
+ sizeof(mi->ssl_ca)));
+ field_list.push_back(new Item_empty_string("Master_SSL_CA_Path",
+ sizeof(mi->ssl_capath)));
+ field_list.push_back(new Item_empty_string("Master_SSL_Cert",
+ sizeof(mi->ssl_cert)));
+ field_list.push_back(new Item_empty_string("Master_SSL_Cipher",
+ sizeof(mi->ssl_cipher)));
+ field_list.push_back(new Item_empty_string("Master_SSL_Key",
+ sizeof(mi->ssl_key)));
+ field_list.push_back(new Item_return_int("Seconds_Behind_Master", 10,
+ MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_empty_string("Master_SSL_Verify_Server_Cert",
+ 3));
+ field_list.push_back(new Item_return_int("Last_IO_Errno", 4, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_empty_string("Last_IO_Error", 20));
+ field_list.push_back(new Item_return_int("Last_SQL_Errno", 4, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_empty_string("Last_SQL_Error", 20));
+ field_list.push_back(new Item_empty_string("Replicate_Ignore_Server_Ids",
+ FN_REFLEN));
+ field_list.push_back(new Item_return_int("Master_Server_Id", sizeof(ulong),
+ MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_empty_string("Master_SSL_Crl",
+ sizeof(mi->ssl_crl)));
+ field_list.push_back(new Item_empty_string("Master_SSL_Crlpath",
+ sizeof(mi->ssl_crlpath)));
+ field_list.push_back(new Item_empty_string("Using_Gtid",
+ sizeof("Current_Pos")-1));
+ field_list.push_back(new Item_empty_string("Gtid_IO_Pos", 30));
+ if (full)
+ {
+ field_list.push_back(new Item_return_int("Retried_transactions",
+ 10, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_return_int("Max_relay_log_size",
+ 10, MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_return_int("Executed_log_entries",
+ 10, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_return_int("Slave_received_heartbeats",
+ 10, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_float("Slave_heartbeat_period",
+ 0.0, 3, 10));
+ field_list.push_back(new Item_empty_string("Gtid_Slave_Pos",
+ gtid_pos_length));
+ }
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+
+static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full,
+ String *gtid_pos)
+{
+ DBUG_ENTER("send_show_master_info_data");
+
+ if (mi->host[0])
+ {
+ DBUG_PRINT("info",("host is set: '%s'", mi->host));
+ String *packet= &thd->packet;
+ Protocol *protocol= thd->protocol;
+ Rpl_filter *rpl_filter= mi->rpl_filter;
+ char buf[256];
+ String tmp(buf, sizeof(buf), &my_charset_bin);
+
+ protocol->prepare_for_resend();
+
+ /*
+ slave_running can be accessed without run_lock but not other
+ non-volotile members like mi->io_thd, which is guarded by the mutex.
+ */
+ if (full)
+ protocol->store(mi->connection_name.str, mi->connection_name.length,
+ &my_charset_bin);
+ mysql_mutex_lock(&mi->run_lock);
+ if (full)
+ {
+ /*
+ Show what the sql driver replication thread is doing
+ This is only meaningful if there is only one slave thread.
+ */
+ protocol->store(mi->rli.sql_driver_thd ?
+ mi->rli.sql_driver_thd->get_proc_info() : "",
+ &my_charset_bin);
+ }
+ protocol->store(mi->io_thd ? mi->io_thd->get_proc_info() : "", &my_charset_bin);
+ mysql_mutex_unlock(&mi->run_lock);
+
+ mysql_mutex_lock(&mi->data_lock);
+ mysql_mutex_lock(&mi->rli.data_lock);
+ mysql_mutex_lock(&mi->err_lock);
+ mysql_mutex_lock(&mi->rli.err_lock);
+ protocol->store(mi->host, &my_charset_bin);
+ protocol->store(mi->user, &my_charset_bin);
+ protocol->store((uint32) mi->port);
+ protocol->store((uint32) mi->connect_retry);
+ protocol->store(mi->master_log_name, &my_charset_bin);
+ protocol->store((ulonglong) mi->master_log_pos);
+ protocol->store(mi->rli.group_relay_log_name +
+ dirname_length(mi->rli.group_relay_log_name),
+ &my_charset_bin);
+ protocol->store((ulonglong) mi->rli.group_relay_log_pos);
+ protocol->store(mi->rli.group_master_log_name, &my_charset_bin);
+ protocol->store(mi->slave_running == MYSQL_SLAVE_RUN_CONNECT ?
+ "Yes" : (mi->slave_running == MYSQL_SLAVE_RUN_NOT_CONNECT ?
+ "Connecting" : "No"), &my_charset_bin);
+ protocol->store(mi->rli.slave_running ? "Yes":"No", &my_charset_bin);
+ protocol->store(rpl_filter->get_do_db());
+ protocol->store(rpl_filter->get_ignore_db());
+
+ rpl_filter->get_do_table(&tmp);
+ protocol->store(&tmp);
+ rpl_filter->get_ignore_table(&tmp);
+ protocol->store(&tmp);
+ rpl_filter->get_wild_do_table(&tmp);
+ protocol->store(&tmp);
+ rpl_filter->get_wild_ignore_table(&tmp);
+ protocol->store(&tmp);
+
+ protocol->store(mi->rli.last_error().number);
+ protocol->store(mi->rli.last_error().message, &my_charset_bin);
+ protocol->store((uint32) mi->rli.slave_skip_counter);
+ protocol->store((ulonglong) mi->rli.group_master_log_pos);
+ protocol->store((ulonglong) mi->rli.log_space_total);
+
+ protocol->store(
+ mi->rli.until_condition==Relay_log_info::UNTIL_NONE ? "None":
+ ( mi->rli.until_condition==Relay_log_info::UNTIL_MASTER_POS? "Master":
+ ( mi->rli.until_condition==Relay_log_info::UNTIL_RELAY_POS? "Relay":
+ "Gtid")), &my_charset_bin);
+ protocol->store(mi->rli.until_log_name, &my_charset_bin);
+ protocol->store((ulonglong) mi->rli.until_log_pos);
+
+#ifdef HAVE_OPENSSL
+ protocol->store(mi->ssl? "Yes":"No", &my_charset_bin);
+#else
+ protocol->store(mi->ssl? "Ignored":"No", &my_charset_bin);
+#endif
+ protocol->store(mi->ssl_ca, &my_charset_bin);
+ protocol->store(mi->ssl_capath, &my_charset_bin);
+ protocol->store(mi->ssl_cert, &my_charset_bin);
+ protocol->store(mi->ssl_cipher, &my_charset_bin);
+ protocol->store(mi->ssl_key, &my_charset_bin);
+
+ /*
+ Seconds_Behind_Master: if SQL thread is running and I/O thread is
+ connected, we can compute it otherwise show NULL (i.e. unknown).
+ */
+ if ((mi->slave_running == MYSQL_SLAVE_RUN_CONNECT) &&
+ mi->rli.slave_running)
+ {
+ long time_diff;
+ bool idle;
+ time_t stamp= mi->rli.last_master_timestamp;
+
+ if (!stamp)
+ idle= true;
+ else
+ {
+ idle= mi->rli.sql_thread_caught_up;
+ if (opt_slave_parallel_threads > 0 && idle &&
+ !mi->rli.parallel.workers_idle())
+ idle= false;
+ }
+ if (idle)
+ time_diff= 0;
+ else
+ {
+ time_diff= ((long)(time(0) - stamp) - mi->clock_diff_with_master);
+ /*
+ Apparently on some systems time_diff can be <0. Here are possible
+ reasons related to MySQL:
+ - the master is itself a slave of another master whose time is ahead.
+ - somebody used an explicit SET TIMESTAMP on the master.
+ Possible reason related to granularity-to-second of time functions
+ (nothing to do with MySQL), which can explain a value of -1:
+ assume the master's and slave's time are perfectly synchronized, and
+ that at slave's connection time, when the master's timestamp is read,
+ it is at the very end of second 1, and (a very short time later) when
+ the slave's timestamp is read it is at the very beginning of second
+ 2. Then the recorded value for master is 1 and the recorded value for
+ slave is 2. At SHOW SLAVE STATUS time, assume that the difference
+ between timestamp of slave and rli->last_master_timestamp is 0
+ (i.e. they are in the same second), then we get 0-(2-1)=-1 as a result.
+ This confuses users, so we don't go below 0.
+
+ last_master_timestamp == 0 (an "impossible" timestamp 1970) is a
+ special marker to say "consider we have caught up".
+ */
+ if (time_diff < 0)
+ time_diff= 0;
+ }
+ protocol->store((longlong)time_diff);
+ }
+ else
+ {
+ protocol->store_null();
+ }
+ protocol->store(mi->ssl_verify_server_cert? "Yes":"No", &my_charset_bin);
+
+ // Last_IO_Errno
+ protocol->store(mi->last_error().number);
+ // Last_IO_Error
+ protocol->store(mi->last_error().message, &my_charset_bin);
+ // Last_SQL_Errno
+ protocol->store(mi->rli.last_error().number);
+ // Last_SQL_Error
+ protocol->store(mi->rli.last_error().message, &my_charset_bin);
+ // Replicate_Ignore_Server_Ids
+ {
+ char buff[FN_REFLEN];
+ ulong i, cur_len;
+ for (i= 0, buff[0]= 0, cur_len= 0;
+ i < mi->ignore_server_ids.elements; i++)
+ {
+ ulong s_id, slen;
+ char sbuff[FN_REFLEN];
+ get_dynamic(&mi->ignore_server_ids, (uchar*) &s_id, i);
+ slen= sprintf(sbuff, (i==0? "%lu" : ", %lu"), s_id);
+ if (cur_len + slen + 4 > FN_REFLEN)
+ {
+ /*
+ break the loop whenever remained space could not fit
+ ellipses on the next cycle
+ */
+ sprintf(buff + cur_len, "...");
+ break;
+ }
+ cur_len += sprintf(buff + cur_len, "%s", sbuff);
+ }
+ protocol->store(buff, &my_charset_bin);
+ }
+ // Master_Server_id
+ protocol->store((uint32) mi->master_id);
+ // Master_Ssl_Crl
+ protocol->store(mi->ssl_ca, &my_charset_bin);
+ // Master_Ssl_Crlpath
+ protocol->store(mi->ssl_capath, &my_charset_bin);
+ protocol->store(mi->using_gtid_astext(mi->using_gtid), &my_charset_bin);
+ {
+ char buff[30];
+ String tmp(buff, sizeof(buff), system_charset_info);
+ mi->gtid_current_pos.to_string(&tmp);
+ protocol->store(tmp.ptr(), tmp.length(), &my_charset_bin);
+ }
+ if (full)
+ {
+ protocol->store((uint32) mi->rli.retried_trans);
+ protocol->store((ulonglong) mi->rli.max_relay_log_size);
+ protocol->store((uint32) mi->rli.executed_entries);
+ protocol->store((uint32) mi->received_heartbeats);
+ protocol->store((double) mi->heartbeat_period, 3, &tmp);
+ protocol->store(gtid_pos->ptr(), gtid_pos->length(), &my_charset_bin);
+ }
+
+ mysql_mutex_unlock(&mi->rli.err_lock);
+ mysql_mutex_unlock(&mi->err_lock);
+ mysql_mutex_unlock(&mi->rli.data_lock);
+ mysql_mutex_unlock(&mi->data_lock);
+
+ if (my_net_write(&thd->net, (uchar*) thd->packet.ptr(), packet->length()))
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/* Used to sort connections by name */
+
+static int cmp_mi_by_name(const Master_info **arg1,
+ const Master_info **arg2)
+{
+ return my_strcasecmp(system_charset_info, (*arg1)->connection_name.str,
+ (*arg2)->connection_name.str);
+}
+
+
+/**
+ Execute a SHOW FULL SLAVE STATUS statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ Elements are sorted according to the original connection_name.
+
+ @retval FALSE success
+ @retval TRUE failure
+
+ @note
+ master_info_index is protected by LOCK_active_mi.
+*/
+
+bool show_all_master_info(THD* thd)
+{
+ uint i, elements;
+ String gtid_pos;
+ Master_info **tmp;
+ DBUG_ENTER("show_master_info");
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+
+ gtid_pos.length(0);
+ if (rpl_append_gtid_state(&gtid_pos, true))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (send_show_master_info_header(thd, 1, gtid_pos.length()))
+ DBUG_RETURN(TRUE);
+
+ if (!master_info_index ||
+ !(elements= master_info_index->master_info_hash.records))
+ goto end;
+
+ /*
+ Sort lines to get them into a predicted order
+ (needed for test cases and to not confuse users)
+ */
+ if (!(tmp= (Master_info**) thd->alloc(sizeof(Master_info*) * elements)))
+ DBUG_RETURN(TRUE);
+
+ for (i= 0; i < elements; i++)
+ {
+ tmp[i]= (Master_info *) my_hash_element(&master_info_index->
+ master_info_hash, i);
+ }
+ my_qsort(tmp, elements, sizeof(Master_info*), (qsort_cmp) cmp_mi_by_name);
+
+ for (i= 0; i < elements; i++)
+ {
+ if (send_show_master_info_data(thd, tmp[i], 1, &gtid_pos))
+ DBUG_RETURN(TRUE);
+ }
+
+end:
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+void set_slave_thread_options(THD* thd)
+{
+ DBUG_ENTER("set_slave_thread_options");
+ /*
+ It's nonsense to constrain the slave threads with max_join_size; if a
+ query succeeded on master, we HAVE to execute it. So set
+ OPTION_BIG_SELECTS. Setting max_join_size to HA_POS_ERROR is not enough
+ (and it's not needed if we have OPTION_BIG_SELECTS) because an INSERT
+ SELECT examining more than 4 billion rows would still fail (yes, because
+ when max_join_size is 4G, OPTION_BIG_SELECTS is automatically set, but
+ only for client threads.
+ */
+ ulonglong options= thd->variables.option_bits | OPTION_BIG_SELECTS;
+ if (opt_log_slave_updates)
+ options|= OPTION_BIN_LOG;
+ else
+ options&= ~OPTION_BIN_LOG;
+ thd->variables.option_bits= options;
+ thd->variables.completion_type= 0;
+ DBUG_VOID_RETURN;
+}
+
+void set_slave_thread_default_charset(THD* thd, rpl_group_info *rgi)
+{
+ DBUG_ENTER("set_slave_thread_default_charset");
+
+ thd->variables.character_set_client=
+ global_system_variables.character_set_client;
+ thd->variables.collation_connection=
+ global_system_variables.collation_connection;
+ thd->variables.collation_server=
+ global_system_variables.collation_server;
+ thd->update_charset();
+
+ thd->system_thread_info.rpl_sql_info->cached_charset_invalidate();
+ DBUG_VOID_RETURN;
+}
+
+/*
+ init_slave_thread()
+*/
+
+static int init_slave_thread(THD* thd, Master_info *mi,
+ SLAVE_THD_TYPE thd_type)
+{
+ DBUG_ENTER("init_slave_thread");
+ int simulate_error __attribute__((unused))= 0;
+ DBUG_EXECUTE_IF("simulate_io_slave_error_on_init",
+ simulate_error|= (1 << SLAVE_THD_IO););
+ DBUG_EXECUTE_IF("simulate_sql_slave_error_on_init",
+ simulate_error|= (1 << SLAVE_THD_SQL););
+ /* We must call store_globals() before doing my_net_init() */
+ if (init_thr_lock() || thd->store_globals() ||
+ my_net_init(&thd->net, 0, MYF(MY_THREAD_SPECIFIC)) ||
+ IF_DBUG(simulate_error & (1<< thd_type), 0))
+ {
+ thd->cleanup();
+ DBUG_RETURN(-1);
+ }
+
+ thd->system_thread = (thd_type == SLAVE_THD_SQL) ?
+ SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO;
+ thd->security_ctx->skip_grants();
+ thd->slave_thread= 1;
+ thd->connection_name= mi->connection_name;
+ thd->enable_slow_log= opt_log_slow_slave_statements;
+ thd->variables.log_slow_filter= global_system_variables.log_slow_filter;
+ set_slave_thread_options(thd);
+ thd->client_capabilities = CLIENT_LOCAL_FILES;
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ if (thd_type == SLAVE_THD_SQL)
+ THD_STAGE_INFO(thd, stage_waiting_for_the_next_event_in_relay_log);
+ else
+ THD_STAGE_INFO(thd, stage_waiting_for_master_update);
+ thd->set_time();
+ /* Do not use user-supplied timeout value for system threads. */
+ thd->variables.lock_wait_timeout= LONG_TIMEOUT;
+ DBUG_RETURN(0);
+}
+
+/*
+ Sleep for a given amount of time or until killed.
+
+ @param thd Thread context of the current thread.
+ @param seconds The number of seconds to sleep.
+ @param func Function object to check if the thread has been killed.
+ @param info The Rpl_info object associated with this sleep.
+
+ @retval True if the thread has been killed, false otherwise.
+*/
+template <typename killed_func, typename rpl_info>
+static bool slave_sleep(THD *thd, time_t seconds,
+ killed_func func, rpl_info info)
+{
+
+ bool ret;
+ struct timespec abstime;
+
+ mysql_mutex_t *lock= &info->sleep_lock;
+ mysql_cond_t *cond= &info->sleep_cond;
+
+ /* Absolute system time at which the sleep time expires. */
+ set_timespec(abstime, seconds);
+ mysql_mutex_lock(lock);
+ thd->ENTER_COND(cond, lock, NULL, NULL);
+
+ while (! (ret= func(info)))
+ {
+ int error= mysql_cond_timedwait(cond, lock, &abstime);
+ if (error == ETIMEDOUT || error == ETIME)
+ break;
+ }
+ /* Implicitly unlocks the mutex. */
+ thd->EXIT_COND(NULL);
+ return ret;
+}
+
+
+static int request_dump(THD *thd, MYSQL* mysql, Master_info* mi,
+ bool *suppress_warnings)
+{
+ uchar buf[FN_REFLEN + 10];
+ int len;
+ ushort binlog_flags = 0; // for now
+ char* logname = mi->master_log_name;
+ DBUG_ENTER("request_dump");
+
+ *suppress_warnings= FALSE;
+
+ if (opt_log_slave_updates && opt_replicate_annotate_row_events)
+ binlog_flags|= BINLOG_SEND_ANNOTATE_ROWS_EVENT;
+
+ if (RUN_HOOK(binlog_relay_io,
+ before_request_transmit,
+ (thd, mi, binlog_flags)))
+ DBUG_RETURN(1);
+
+ // TODO if big log files: Change next to int8store()
+ int4store(buf, (ulong) mi->master_log_pos);
+ int2store(buf + 4, binlog_flags);
+ int4store(buf + 6, global_system_variables.server_id);
+ len = (uint) strlen(logname);
+ memcpy(buf + 10, logname,len);
+ if (simple_command(mysql, COM_BINLOG_DUMP, buf, len + 10, 1))
+ {
+ /*
+ Something went wrong, so we will just reconnect and retry later
+ in the future, we should do a better error analysis, but for
+ now we just fill up the error log :-)
+ */
+ if (mysql_errno(mysql) == ER_NET_READ_INTERRUPTED)
+ *suppress_warnings= TRUE; // Suppress reconnect warning
+ else
+ sql_print_error("Error on COM_BINLOG_DUMP: %d %s, will retry in %d secs",
+ mysql_errno(mysql), mysql_error(mysql),
+ mi->connect_retry);
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Read one event from the master
+
+ SYNOPSIS
+ read_event()
+ mysql MySQL connection
+ mi Master connection information
+ suppress_warnings TRUE when a normal net read timeout has caused us to
+ try a reconnect. We do not want to print anything to
+ the error log in this case because this a anormal
+ event in an idle server.
+
+ RETURN VALUES
+ 'packet_error' Error
+ number Length of packet
+*/
+
+static ulong read_event(MYSQL* mysql, Master_info *mi, bool* suppress_warnings)
+{
+ ulong len;
+ DBUG_ENTER("read_event");
+
+ *suppress_warnings= FALSE;
+ /*
+ my_real_read() will time us out
+ We check if we were told to die, and if not, try reading again
+ */
+#ifndef DBUG_OFF
+ if (disconnect_slave_event_count && !(mi->events_till_disconnect--))
+ DBUG_RETURN(packet_error);
+#endif
+
+ len = cli_safe_read(mysql);
+ if (len == packet_error || (long) len < 1)
+ {
+ if (mysql_errno(mysql) == ER_NET_READ_INTERRUPTED)
+ {
+ /*
+ We are trying a normal reconnect after a read timeout;
+ we suppress prints to .err file as long as the reconnect
+ happens without problems
+ */
+ *suppress_warnings= TRUE;
+ }
+ else
+ sql_print_error("Error reading packet from server: %s ( server_errno=%d)",
+ mysql_error(mysql), mysql_errno(mysql));
+ DBUG_RETURN(packet_error);
+ }
+
+ /* Check if eof packet */
+ if (len < 8 && mysql->net.read_pos[0] == 254)
+ {
+ sql_print_information("Slave: received end packet from server, apparent "
+ "master shutdown: %s",
+ mysql_error(mysql));
+ DBUG_RETURN(packet_error);
+ }
+
+ DBUG_PRINT("exit", ("len: %lu net->read_pos[4]: %d",
+ len, mysql->net.read_pos[4]));
+ DBUG_RETURN(len - 1);
+}
+
+/*
+ Check if the current error is of temporary nature of not.
+ Some errors are temporary in nature, such as
+ ER_LOCK_DEADLOCK and ER_LOCK_WAIT_TIMEOUT. Ndb also signals
+ that the error is temporary by pushing a warning with the error code
+ ER_GET_TEMPORARY_ERRMSG, if the originating error is temporary.
+*/
+int
+has_temporary_error(THD *thd)
+{
+ DBUG_ENTER("has_temporary_error");
+
+ DBUG_EXECUTE_IF("all_errors_are_temporary_errors",
+ if (thd->get_stmt_da()->is_error())
+ {
+ thd->clear_error();
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ });
+
+ /*
+ If there is no message in THD, we can't say if it's a temporary
+ error or not. This is currently the case for Incident_log_event,
+ which sets no message. Return FALSE.
+ */
+ if (!thd->is_error())
+ DBUG_RETURN(0);
+
+ /*
+ Temporary error codes:
+ currently, InnoDB deadlock detected by InnoDB or lock
+ wait timeout (innodb_lock_wait_timeout exceeded
+ */
+ if (thd->get_stmt_da()->sql_errno() == ER_LOCK_DEADLOCK ||
+ thd->get_stmt_da()->sql_errno() == ER_LOCK_WAIT_TIMEOUT)
+ DBUG_RETURN(1);
+
+#ifdef HAVE_NDB_BINLOG
+ /*
+ currently temporary error set in ndbcluster
+ */
+ List_iterator_fast<Sql_condition> it(thd->warning_info->warn_list());
+ Sql_condition *err;
+ while ((err= it++))
+ {
+ DBUG_PRINT("info", ("has condition %d %s", err->get_sql_errno(),
+ err->get_message_text()));
+ switch (err->get_sql_errno())
+ {
+ case ER_GET_TEMPORARY_ERRMSG:
+ DBUG_RETURN(1);
+ default:
+ break;
+ }
+ }
+#endif
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Applies the given event and advances the relay log position.
+
+ In essence, this function does:
+
+ @code
+ ev->apply_event(rli);
+ ev->update_pos(rli);
+ @endcode
+
+ But it also does some maintainance, such as skipping events if
+ needed and reporting errors.
+
+ If the @c skip flag is set, then it is tested whether the event
+ should be skipped, by looking at the slave_skip_counter and the
+ server id. The skip flag should be set when calling this from a
+ replication thread but not set when executing an explicit BINLOG
+ statement.
+
+ @retval 0 OK.
+
+ @retval 1 Error calling ev->apply_event().
+
+ @retval 2 No error calling ev->apply_event(), but error calling
+ ev->update_pos().
+*/
+int apply_event_and_update_pos(Log_event* ev, THD* thd,
+ rpl_group_info *rgi,
+ rpl_parallel_thread *rpt)
+{
+ int exec_res= 0;
+ Relay_log_info* rli= rgi->rli;
+ DBUG_ENTER("apply_event_and_update_pos");
+
+ DBUG_PRINT("exec_event",("%s(type_code: %d; server_id: %d)",
+ ev->get_type_str(), ev->get_type_code(),
+ ev->server_id));
+ DBUG_PRINT("info", ("thd->options: '%s%s%s' rgi->last_event_start_time: %lu",
+ FLAGSTR(thd->variables.option_bits, OPTION_NOT_AUTOCOMMIT),
+ FLAGSTR(thd->variables.option_bits, OPTION_BEGIN),
+ FLAGSTR(thd->variables.option_bits, OPTION_GTID_BEGIN),
+ (ulong) rgi->last_event_start_time));
+
+ /*
+ Execute the event to change the database and update the binary
+ log coordinates, but first we set some data that is needed for
+ the thread.
+
+ The event will be executed unless it is supposed to be skipped.
+
+ Queries originating from this server must be skipped. Low-level
+ events (Format_description_log_event, Rotate_log_event,
+ Stop_log_event) from this server must also be skipped. But for
+ those we don't want to modify 'group_master_log_pos', because
+ these events did not exist on the master.
+ Format_description_log_event is not completely skipped.
+
+ Skip queries specified by the user in 'slave_skip_counter'. We
+ can't however skip events that has something to do with the log
+ files themselves.
+
+ Filtering on own server id is extremely important, to ignore
+ execution of events created by the creation/rotation of the relay
+ log (remember that now the relay log starts with its Format_desc,
+ has a Rotate etc).
+ */
+
+ /* Use the original server id for logging. */
+ thd->variables.server_id = ev->server_id;
+ thd->set_time(); // time the query
+ thd->lex->current_select= 0;
+ if (!ev->when)
+ {
+ my_hrtime_t hrtime= my_hrtime();
+ ev->when= hrtime_to_my_time(hrtime);
+ ev->when_sec_part= hrtime_sec_part(hrtime);
+ }
+ thd->variables.option_bits=
+ (thd->variables.option_bits & ~OPTION_SKIP_REPLICATION) |
+ (ev->flags & LOG_EVENT_SKIP_REPLICATION_F ? OPTION_SKIP_REPLICATION : 0);
+ ev->thd = thd; // because up to this point, ev->thd == 0
+
+ int reason= ev->shall_skip(rgi);
+ if (reason == Log_event::EVENT_SKIP_COUNT)
+ {
+ DBUG_ASSERT(rli->slave_skip_counter > 0);
+ rli->slave_skip_counter--;
+ }
+ mysql_mutex_unlock(&rli->data_lock);
+ DBUG_EXECUTE_IF("inject_slave_sql_before_apply_event",
+ {
+ DBUG_ASSERT(!debug_sync_set_action
+ (thd, STRING_WITH_LEN("now WAIT_FOR continue")));
+ DBUG_SET_INITIAL("-d,inject_slave_sql_before_apply_event");
+ };);
+ if (reason == Log_event::EVENT_SKIP_NOT)
+ exec_res= ev->apply_event(rgi);
+
+#ifndef DBUG_OFF
+ /*
+ This only prints information to the debug trace.
+
+ TODO: Print an informational message to the error log?
+ */
+ static const char *const explain[] = {
+ // EVENT_SKIP_NOT,
+ "not skipped",
+ // EVENT_SKIP_IGNORE,
+ "skipped because event should be ignored",
+ // EVENT_SKIP_COUNT
+ "skipped because event skip counter was non-zero"
+ };
+ DBUG_PRINT("info", ("OPTION_BEGIN: %d IN_STMT: %d IN_TRANSACTION: %d",
+ MY_TEST(thd->variables.option_bits & OPTION_BEGIN),
+ rli->get_flag(Relay_log_info::IN_STMT),
+ rli->get_flag(Relay_log_info::IN_TRANSACTION)));
+ DBUG_PRINT("skip_event", ("%s event was %s",
+ ev->get_type_str(), explain[reason]));
+#endif
+
+ DBUG_PRINT("info", ("apply_event error = %d", exec_res));
+ if (exec_res == 0)
+ {
+ int error= ev->update_pos(rgi);
+#ifdef HAVE_valgrind
+ if (!rli->is_fake)
+#endif
+ {
+#ifndef DBUG_OFF
+ char buf[22];
+#endif
+ DBUG_PRINT("info", ("update_pos error = %d", error));
+ DBUG_PRINT("info", ("group %s %s",
+ llstr(rli->group_relay_log_pos, buf),
+ rli->group_relay_log_name));
+ DBUG_PRINT("info", ("event %s %s",
+ llstr(rli->event_relay_log_pos, buf),
+ rli->event_relay_log_name));
+ }
+ /*
+ The update should not fail, so print an error message and
+ return an error code.
+
+ TODO: Replace this with a decent error message when merged
+ with BUG#24954 (which adds several new error message).
+ */
+ if (error)
+ {
+ char buf[22];
+ rli->report(ERROR_LEVEL, ER_UNKNOWN_ERROR, rgi->gtid_info(),
+ "It was not possible to update the positions"
+ " of the relay log information: the slave may"
+ " be in an inconsistent state."
+ " Stopped in %s position %s",
+ rli->group_relay_log_name,
+ llstr(rli->group_relay_log_pos, buf));
+ DBUG_RETURN(2);
+ }
+ }
+ else
+ {
+ /*
+ Make sure we do not erroneously update gtid_slave_pos with a lingering
+ GTID from this failed event group (MDEV-4906).
+ */
+ rgi->gtid_pending= false;
+ }
+
+ DBUG_RETURN(exec_res ? 1 : 0);
+}
+
+
+/**
+ Keep the relay log transaction state up to date.
+
+ The state reflects how things are after the given event, that has just been
+ read from the relay log, is executed.
+
+ This is only needed to ensure we:
+ - Don't abort the sql driver thread in the middle of an event group.
+ - Don't rotate the io thread in the middle of a statement or transaction.
+ The mechanism is that the io thread, when it needs to rotate the relay
+ log, will wait until the sql driver has read all the cached events
+ and then continue reading events one by one from the master until
+ the sql threads signals that log doesn't have an active group anymore.
+
+ There are two possible cases. We keep them as 2 separate flags mainly
+ to make debugging easier.
+
+ - IN_STMT is set when we have read an event that should be used
+ together with the next event. This is for example setting a
+ variable that is used when executing the next statement.
+ - IN_TRANSACTION is set when we are inside a BEGIN...COMMIT group
+
+ To test the state one should use the is_in_group() function.
+*/
+
+inline void update_state_of_relay_log(Relay_log_info *rli, Log_event *ev)
+{
+ Log_event_type typ= ev->get_type_code();
+
+ /* check if we are in a multi part event */
+ if (ev->is_part_of_group())
+ rli->set_flag(Relay_log_info::IN_STMT);
+ else if (Log_event::is_group_event(typ))
+ {
+ /*
+ If it was not a is_part_of_group() and not a group event (like
+ rotate) then we can reset the IN_STMT flag. We have the above
+ if only to allow us to have a rotate element anywhere.
+ */
+ rli->clear_flag(Relay_log_info::IN_STMT);
+ }
+
+ /* Check for an event that starts or stops a transaction */
+ if (typ == QUERY_EVENT)
+ {
+ Query_log_event *qev= (Query_log_event*) ev;
+ /*
+ Trivial optimization to avoid the following somewhat expensive
+ checks.
+ */
+ if (qev->q_len <= sizeof("ROLLBACK"))
+ {
+ if (qev->is_begin())
+ rli->set_flag(Relay_log_info::IN_TRANSACTION);
+ if (qev->is_commit() || qev->is_rollback())
+ rli->clear_flag(Relay_log_info::IN_TRANSACTION);
+ }
+ }
+ if (typ == XID_EVENT)
+ rli->clear_flag(Relay_log_info::IN_TRANSACTION);
+ if (typ == GTID_EVENT &&
+ !(((Gtid_log_event*) ev)->flags2 & Gtid_log_event::FL_STANDALONE))
+ {
+ /* This GTID_EVENT will generate a BEGIN event */
+ rli->set_flag(Relay_log_info::IN_TRANSACTION);
+ }
+
+ DBUG_PRINT("info", ("event: %u IN_STMT: %d IN_TRANSACTION: %d",
+ (uint) typ,
+ rli->get_flag(Relay_log_info::IN_STMT),
+ rli->get_flag(Relay_log_info::IN_TRANSACTION)));
+}
+
+
+/**
+ Top-level function for executing the next event from the relay log.
+
+ This function reads the event from the relay log, executes it, and
+ advances the relay log position. It also handles errors, etc.
+
+ This function may fail to apply the event for the following reasons:
+
+ - The position specfied by the UNTIL condition of the START SLAVE
+ command is reached.
+
+ - It was not possible to read the event from the log.
+
+ - The slave is killed.
+
+ - An error occurred when applying the event, and the event has been
+ tried slave_trans_retries times. If the event has been retried
+ fewer times, 0 is returned.
+
+ - init_master_info or init_relay_log_pos failed. (These are called
+ if a failure occurs when applying the event.)
+
+ - An error occurred when updating the binlog position.
+
+ @retval 0 The event was applied.
+
+ @retval 1 The event was not applied.
+*/
+
+static int exec_relay_log_event(THD* thd, Relay_log_info* rli,
+ rpl_group_info *serial_rgi)
+{
+ ulonglong event_size;
+ DBUG_ENTER("exec_relay_log_event");
+
+ /*
+ We acquire this mutex since we need it for all operations except
+ event execution. But we will release it in places where we will
+ wait for something for example inside of next_event().
+ */
+ mysql_mutex_lock(&rli->data_lock);
+
+ Log_event *ev= next_event(serial_rgi, &event_size);
+
+ if (sql_slave_killed(serial_rgi))
+ {
+ mysql_mutex_unlock(&rli->data_lock);
+ delete ev;
+ DBUG_RETURN(1);
+ }
+ if (ev)
+ {
+ int exec_res;
+ Log_event_type typ= ev->get_type_code();
+
+ /*
+ Even if we don't execute this event, we keep the master timestamp,
+ so that seconds behind master shows correct delta (there are events
+ that are not replayed, so we keep falling behind).
+
+ If it is an artificial event, or a relay log event (IO thread generated
+ event) or ev->when is set to 0, we don't update the
+ last_master_timestamp.
+ */
+ if (!(ev->is_artificial_event() || ev->is_relay_log_event() || (ev->when == 0)))
+ {
+ rli->last_master_timestamp= ev->when + (time_t) ev->exec_time;
+ DBUG_ASSERT(rli->last_master_timestamp >= 0);
+ }
+
+ /*
+ This tests if the position of the beginning of the current event
+ hits the UNTIL barrier.
+ */
+ if ((rli->until_condition == Relay_log_info::UNTIL_MASTER_POS ||
+ rli->until_condition == Relay_log_info::UNTIL_RELAY_POS) &&
+ rli->is_until_satisfied(thd, ev))
+ {
+ char buf[22];
+ sql_print_information("Slave SQL thread stopped because it reached its"
+ " UNTIL position %s", llstr(rli->until_pos(), buf));
+ /*
+ Setting abort_slave flag because we do not want additional
+ message about error in query execution to be printed.
+ */
+ rli->abort_slave= 1;
+ rli->stop_for_until= true;
+ mysql_mutex_unlock(&rli->data_lock);
+ delete ev;
+ DBUG_RETURN(1);
+ }
+
+ { /**
+ The following failure injecion works in cooperation with tests
+ setting @@global.debug= 'd,incomplete_group_in_relay_log'.
+ Xid or Commit events are not executed to force the slave sql
+ read hanging if the realy log does not have any more events.
+ */
+ DBUG_EXECUTE_IF("incomplete_group_in_relay_log",
+ if ((typ == XID_EVENT) ||
+ ((typ == QUERY_EVENT) &&
+ strcmp("COMMIT", ((Query_log_event *) ev)->query) == 0))
+ {
+ DBUG_ASSERT(thd->transaction.all.modified_non_trans_table);
+ rli->abort_slave= 1;
+ mysql_mutex_unlock(&rli->data_lock);
+ delete ev;
+ serial_rgi->inc_event_relay_log_pos();
+ DBUG_RETURN(0);
+ };);
+ }
+
+ update_state_of_relay_log(rli, ev);
+
+ if (opt_slave_parallel_threads > 0)
+ {
+ int res= rli->parallel.do_event(serial_rgi, ev, event_size);
+ if (res >= 0)
+ DBUG_RETURN(res);
+ /*
+ Else we proceed to execute the event non-parallel.
+ This is the case for pre-10.0 events without GTID, and for handling
+ slave_skip_counter.
+ */
+ }
+
+ if (typ == GTID_EVENT)
+ {
+ Gtid_log_event *gev= static_cast<Gtid_log_event *>(ev);
+
+ /*
+ For GTID, allocate a new sub_id for the given domain_id.
+ The sub_id must be allocated in increasing order of binlog order.
+ */
+ if (event_group_new_gtid(serial_rgi, gev))
+ {
+ sql_print_error("Error reading relay log event: %s", "slave SQL thread "
+ "aborted because of out-of-memory error");
+ mysql_mutex_unlock(&rli->data_lock);
+ delete ev;
+ DBUG_RETURN(1);
+ }
+
+ if (opt_gtid_ignore_duplicates)
+ {
+ int res= rpl_global_gtid_slave_state.check_duplicate_gtid
+ (&serial_rgi->current_gtid, serial_rgi);
+ if (res < 0)
+ {
+ sql_print_error("Error processing GTID event: %s", "slave SQL "
+ "thread aborted because of out-of-memory error");
+ mysql_mutex_unlock(&rli->data_lock);
+ delete ev;
+ DBUG_RETURN(1);
+ }
+ /*
+ If we need to skip this event group (because the GTID was already
+ applied), then do it using the code for slave_skip_counter, which
+ is able to handle skipping until the end of the event group.
+ */
+ if (!res)
+ rli->slave_skip_counter= 1;
+ }
+ }
+
+ serial_rgi->future_event_relay_log_pos= rli->future_event_relay_log_pos;
+ serial_rgi->event_relay_log_name= rli->event_relay_log_name;
+ serial_rgi->event_relay_log_pos= rli->event_relay_log_pos;
+ exec_res= apply_event_and_update_pos(ev, thd, serial_rgi, NULL);
+
+ delete_or_keep_event_post_apply(serial_rgi, typ, ev);
+
+ /*
+ update_log_pos failed: this should not happen, so we don't
+ retry.
+ */
+ if (exec_res == 2)
+ DBUG_RETURN(1);
+
+ if (slave_trans_retries)
+ {
+ int temp_err;
+ LINT_INIT(temp_err);
+ if (exec_res && (temp_err= has_temporary_error(thd)))
+ {
+ const char *errmsg;
+ rli->clear_error();
+ /*
+ We were in a transaction which has been rolled back because of a
+ temporary error;
+ let's seek back to BEGIN log event and retry it all again.
+ Note, if lock wait timeout (innodb_lock_wait_timeout exceeded)
+ there is no rollback since 5.0.13 (ref: manual).
+ We have to not only seek but also
+
+ a) init_master_info(), to seek back to hot relay log's start
+ for later (for when we will come back to this hot log after
+ re-processing the possibly existing old logs where BEGIN is:
+ check_binlog_magic() will then need the cache to be at
+ position 0 (see comments at beginning of
+ init_master_info()).
+ b) init_relay_log_pos(), because the BEGIN may be an older relay log.
+ */
+ if (serial_rgi->trans_retries < slave_trans_retries)
+ {
+ if (init_master_info(rli->mi, 0, 0, 0, SLAVE_SQL))
+ sql_print_error("Failed to initialize the master info structure");
+ else if (init_relay_log_pos(rli,
+ rli->group_relay_log_name,
+ rli->group_relay_log_pos,
+ 1, &errmsg, 1))
+ sql_print_error("Error initializing relay log position: %s",
+ errmsg);
+ else
+ {
+ exec_res= 0;
+ serial_rgi->cleanup_context(thd, 1);
+ /* chance for concurrent connection to get more locks */
+ slave_sleep(thd, MY_MIN(serial_rgi->trans_retries,
+ MAX_SLAVE_RETRY_PAUSE),
+ sql_slave_killed, serial_rgi);
+ serial_rgi->trans_retries++;
+ mysql_mutex_lock(&rli->data_lock); // because of SHOW STATUS
+ rli->retried_trans++;
+ statistic_increment(slave_retried_transactions, LOCK_status);
+ mysql_mutex_unlock(&rli->data_lock);
+ DBUG_PRINT("info", ("Slave retries transaction "
+ "rgi->trans_retries: %lu",
+ serial_rgi->trans_retries));
+ }
+ }
+ else
+ sql_print_error("Slave SQL thread retried transaction %lu time(s) "
+ "in vain, giving up. Consider raising the value of "
+ "the slave_transaction_retries variable.",
+ slave_trans_retries);
+ }
+ else if ((exec_res && !temp_err) ||
+ (opt_using_transactions &&
+ rli->group_relay_log_pos == rli->event_relay_log_pos))
+ {
+ /*
+ Only reset the retry counter if the entire group succeeded
+ or failed with a non-transient error. On a successful
+ event, the execution will proceed as usual; in the case of a
+ non-transient error, the slave will stop with an error.
+ */
+ serial_rgi->trans_retries= 0; // restart from fresh
+ DBUG_PRINT("info", ("Resetting retry counter, rgi->trans_retries: %lu",
+ serial_rgi->trans_retries));
+ }
+ }
+ thread_safe_increment64(&rli->executed_entries,
+ &slave_executed_entries_lock);
+ DBUG_RETURN(exec_res);
+ }
+ mysql_mutex_unlock(&rli->data_lock);
+ rli->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_READ_FAILURE, NULL,
+ ER(ER_SLAVE_RELAY_LOG_READ_FAILURE), "\
+Could not parse relay log event entry. The possible reasons are: the master's \
+binary log is corrupted (you can check this by running 'mysqlbinlog' on the \
+binary log), the slave's relay log is corrupted (you can check this by running \
+'mysqlbinlog' on the relay log), a network problem, or a bug in the master's \
+or slave's MySQL code. If you want to check the master's binary log or slave's \
+relay log, you will be able to know their names by issuing 'SHOW SLAVE STATUS' \
+on this slave.\
+");
+ DBUG_RETURN(1);
+}
+
+
+static bool check_io_slave_killed(Master_info *mi, const char *info)
+{
+ if (io_slave_killed(mi))
+ {
+ if (info && global_system_variables.log_warnings)
+ sql_print_information("%s", info);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ @brief Try to reconnect slave IO thread.
+
+ @details Terminates current connection to master, sleeps for
+ @c mi->connect_retry msecs and initiates new connection with
+ @c safe_reconnect(). Variable pointed by @c retry_count is increased -
+ if it exceeds @c master_retry_count then connection is not re-established
+ and function signals error.
+ Unless @c suppres_warnings is TRUE, a warning is put in the server error log
+ when reconnecting. The warning message and messages used to report errors
+ are taken from @c messages array. In case @c master_retry_count is exceeded,
+ no messages are added to the log.
+
+ @param[in] thd Thread context.
+ @param[in] mysql MySQL connection.
+ @param[in] mi Master connection information.
+ @param[in,out] retry_count Number of attempts to reconnect.
+ @param[in] suppress_warnings TRUE when a normal net read timeout
+ has caused to reconnecting.
+ @param[in] messages Messages to print/log, see
+ reconnect_messages[] array.
+
+ @retval 0 OK.
+ @retval 1 There was an error.
+*/
+
+static int try_to_reconnect(THD *thd, MYSQL *mysql, Master_info *mi,
+ uint *retry_count, bool suppress_warnings,
+ const char *messages[SLAVE_RECON_MSG_MAX])
+{
+ mi->slave_running= MYSQL_SLAVE_RUN_NOT_CONNECT;
+ thd->proc_info= messages[SLAVE_RECON_MSG_WAIT];
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ thd->clear_active_vio();
+#endif
+ end_server(mysql);
+ if ((*retry_count)++)
+ {
+ if (*retry_count > master_retry_count)
+ return 1; // Don't retry forever
+ slave_sleep(thd, mi->connect_retry, io_slave_killed, mi);
+ }
+ if (check_io_slave_killed(mi, messages[SLAVE_RECON_MSG_KILLED_WAITING]))
+ return 1;
+ thd->proc_info = messages[SLAVE_RECON_MSG_AFTER];
+ if (!suppress_warnings)
+ {
+ char buf[256], llbuff[22];
+ String tmp;
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ tmp.append(STRING_WITH_LEN("; GTID position '"));
+ mi->gtid_current_pos.append_to_string(&tmp);
+ if (mi->events_queued_since_last_gtid == 0)
+ tmp.append(STRING_WITH_LEN("'"));
+ else
+ {
+ tmp.append(STRING_WITH_LEN("', GTID event skip "));
+ tmp.append_ulonglong((ulonglong)mi->events_queued_since_last_gtid);
+ }
+ }
+ my_snprintf(buf, sizeof(buf), messages[SLAVE_RECON_MSG_FAILED],
+ IO_RPL_LOG_NAME, llstr(mi->master_log_pos, llbuff),
+ tmp.c_ptr_safe());
+ /*
+ Raise a warining during registering on master/requesting dump.
+ Log a message reading event.
+ */
+ if (messages[SLAVE_RECON_MSG_COMMAND][0])
+ {
+ mi->report(WARNING_LEVEL, ER_SLAVE_MASTER_COM_FAILURE, NULL,
+ ER(ER_SLAVE_MASTER_COM_FAILURE),
+ messages[SLAVE_RECON_MSG_COMMAND], buf);
+ }
+ else
+ {
+ sql_print_information("%s", buf);
+ }
+ }
+ if (safe_reconnect(thd, mysql, mi, 1) || io_slave_killed(mi))
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_information("%s", messages[SLAVE_RECON_MSG_KILLED_AFTER]);
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Slave IO thread entry point.
+
+ @param arg Pointer to Master_info struct that holds information for
+ the IO thread.
+
+ @return Always 0.
+*/
+pthread_handler_t handle_slave_io(void *arg)
+{
+ THD *thd; // needs to be first for thread_stack
+ MYSQL *mysql;
+ Master_info *mi = (Master_info*)arg;
+ Relay_log_info *rli= &mi->rli;
+ char llbuff[22];
+ uint retry_count;
+ bool suppress_warnings;
+ int ret;
+ rpl_io_thread_info io_info;
+#ifndef DBUG_OFF
+ uint retry_count_reg= 0, retry_count_dump= 0, retry_count_event= 0;
+#endif
+ // needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff
+ my_thread_init();
+ DBUG_ENTER("handle_slave_io");
+
+ DBUG_ASSERT(mi->inited);
+ mysql= NULL ;
+ retry_count= 0;
+
+ thd= new THD; // note that contructor of THD uses DBUG_ !
+
+ mysql_mutex_lock(&mi->run_lock);
+ /* Inform waiting threads that slave has started */
+ mi->slave_run_id++;
+
+#ifndef DBUG_OFF
+ mi->events_till_disconnect = disconnect_slave_event_count;
+#endif
+
+ THD_CHECK_SENTRY(thd);
+ mi->io_thd = thd;
+
+ pthread_detach_this_thread();
+ thd->thread_stack= (char*) &thd; // remember where our stack is
+ mi->clear_error();
+ if (init_slave_thread(thd, mi, SLAVE_THD_IO))
+ {
+ mysql_cond_broadcast(&mi->start_cond);
+ sql_print_error("Failed during slave I/O thread initialization");
+ goto err_during_init;
+ }
+ thd->system_thread_info.rpl_io_info= &io_info;
+ mysql_mutex_lock(&LOCK_thread_count);
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ mi->slave_running = MYSQL_SLAVE_RUN_NOT_CONNECT;
+ mi->abort_slave = 0;
+ mysql_mutex_unlock(&mi->run_lock);
+ mysql_cond_broadcast(&mi->start_cond);
+
+ DBUG_PRINT("master_info",("log_file_name: '%s' position: %s",
+ mi->master_log_name,
+ llstr(mi->master_log_pos,llbuff)));
+
+ /* This must be called before run any binlog_relay_io hooks */
+ my_pthread_setspecific_ptr(RPL_MASTER_INFO, mi);
+
+ /* Load the set of seen GTIDs, if we did not already. */
+ if (rpl_load_gtid_slave_state(thd))
+ {
+ mi->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL,
+ "Unable to load replication GTID slave state from mysql.%s: %s",
+ rpl_gtid_slave_state_table_name.str,
+ thd->get_stmt_da()->message());
+ /*
+ If we are using old-style replication, we can continue, even though we
+ then will not be able to record the GTIDs we receive. But if using GTID,
+ we must give up.
+ */
+ if (mi->using_gtid != Master_info::USE_GTID_NO || opt_gtid_strict_mode)
+ goto err;
+ }
+
+
+ if (RUN_HOOK(binlog_relay_io, thread_start, (thd, mi)))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ ER(ER_SLAVE_FATAL_ERROR), "Failed to run 'thread_start' hook");
+ goto err;
+ }
+
+ if (!(mi->mysql = mysql = mysql_init(NULL)))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ ER(ER_SLAVE_FATAL_ERROR), "error in mysql_init()");
+ goto err;
+ }
+
+ THD_STAGE_INFO(thd, stage_connecting_to_master);
+ // we can get killed during safe_connect
+ if (!safe_connect(thd, mysql, mi))
+ {
+ if (mi->using_gtid == Master_info::USE_GTID_NO)
+ sql_print_information("Slave I/O thread: connected to master '%s@%s:%d',"
+ "replication started in log '%s' at position %s",
+ mi->user, mi->host, mi->port,
+ IO_RPL_LOG_NAME,
+ llstr(mi->master_log_pos,llbuff));
+ else
+ {
+ String tmp;
+ mi->gtid_current_pos.to_string(&tmp);
+ sql_print_information("Slave I/O thread: connected to master '%s@%s:%d',"
+ "replication starts at GTID position '%s'",
+ mi->user, mi->host, mi->port, tmp.c_ptr_safe());
+ }
+ }
+ else
+ {
+ sql_print_information("Slave I/O thread killed while connecting to master");
+ goto err;
+ }
+
+connected:
+
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ /*
+ When the IO thread (re)connects to the master using GTID, it will
+ connect at the start of an event group. But the IO thread may have
+ previously logged part of the following event group to the relay
+ log.
+
+ When the IO and SQL thread are started together, we erase any previous
+ relay logs, but this is not possible/desirable while the SQL thread is
+ running. To avoid duplicating partial event groups in the relay logs in
+ this case, we remember the count of events in any partially logged event
+ group before the reconnect, and then here at connect we set up a counter
+ to skip the already-logged part of the group.
+ */
+ mi->gtid_reconnect_event_skip_count= mi->events_queued_since_last_gtid;
+ mi->gtid_event_seen= false;
+ }
+
+#ifdef ENABLED_DEBUG_SYNC
+ DBUG_EXECUTE_IF("dbug.before_get_running_status_yes",
+ {
+ const char act[]=
+ "now "
+ "wait_for signal.io_thread_let_running";
+ DBUG_ASSERT(debug_sync_service);
+ DBUG_ASSERT(!debug_sync_set_action(thd,
+ STRING_WITH_LEN(act)));
+ };);
+#endif
+
+ // TODO: the assignment below should be under mutex (5.0)
+ mi->slave_running= MYSQL_SLAVE_RUN_CONNECT;
+ thd->slave_net = &mysql->net;
+ THD_STAGE_INFO(thd, stage_checking_master_version);
+ ret= get_master_version_and_clock(mysql, mi);
+ if (ret == 1)
+ /* Fatal error */
+ goto err;
+
+ if (ret == 2)
+ {
+ if (check_io_slave_killed(mi, "Slave I/O thread killed"
+ "while calling get_master_version_and_clock(...)"))
+ goto err;
+ suppress_warnings= FALSE;
+ /*
+ Try to reconnect because the error was caused by a transient network
+ problem
+ */
+ if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
+ reconnect_messages[SLAVE_RECON_ACT_REG]))
+ goto err;
+ goto connected;
+ }
+
+ if (mi->rli.relay_log.description_event_for_queue->binlog_version > 1)
+ {
+ /*
+ Register ourselves with the master.
+ */
+ THD_STAGE_INFO(thd, stage_registering_slave_on_master);
+ if (register_slave_on_master(mysql, mi, &suppress_warnings))
+ {
+ if (!check_io_slave_killed(mi, "Slave I/O thread killed "
+ "while registering slave on master"))
+ {
+ sql_print_error("Slave I/O thread couldn't register on master");
+ if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
+ reconnect_messages[SLAVE_RECON_ACT_REG]))
+ goto err;
+ }
+ else
+ goto err;
+ goto connected;
+ }
+ DBUG_EXECUTE_IF("FORCE_SLAVE_TO_RECONNECT_REG",
+ if (!retry_count_reg)
+ {
+ retry_count_reg++;
+ sql_print_information("Forcing to reconnect slave I/O thread");
+ if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
+ reconnect_messages[SLAVE_RECON_ACT_REG]))
+ goto err;
+ goto connected;
+ });
+ }
+
+ DBUG_PRINT("info",("Starting reading binary log from master"));
+ while (!io_slave_killed(mi))
+ {
+ THD_STAGE_INFO(thd, stage_requesting_binlog_dump);
+ if (request_dump(thd, mysql, mi, &suppress_warnings))
+ {
+ sql_print_error("Failed on request_dump()");
+ if (check_io_slave_killed(mi, "Slave I/O thread killed while \
+requesting master dump") ||
+ try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
+ reconnect_messages[SLAVE_RECON_ACT_DUMP]))
+ goto err;
+ goto connected;
+ }
+ DBUG_EXECUTE_IF("FORCE_SLAVE_TO_RECONNECT_DUMP",
+ if (!retry_count_dump)
+ {
+ retry_count_dump++;
+ sql_print_information("Forcing to reconnect slave I/O thread");
+ if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
+ reconnect_messages[SLAVE_RECON_ACT_DUMP]))
+ goto err;
+ goto connected;
+ });
+ const char *event_buf;
+
+ DBUG_ASSERT(mi->last_error().number == 0);
+ while (!io_slave_killed(mi))
+ {
+ ulong event_len;
+ /*
+ We say "waiting" because read_event() will wait if there's nothing to
+ read. But if there's something to read, it will not wait. The
+ important thing is to not confuse users by saying "reading" whereas
+ we're in fact receiving nothing.
+ */
+ THD_STAGE_INFO(thd, stage_waiting_for_master_to_send_event);
+ event_len= read_event(mysql, mi, &suppress_warnings);
+ if (check_io_slave_killed(mi, "Slave I/O thread killed while \
+reading event"))
+ goto err;
+ DBUG_EXECUTE_IF("FORCE_SLAVE_TO_RECONNECT_EVENT",
+ if (!retry_count_event)
+ {
+ retry_count_event++;
+ sql_print_information("Forcing to reconnect slave I/O thread");
+ if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
+ reconnect_messages[SLAVE_RECON_ACT_EVENT]))
+ goto err;
+ goto connected;
+ });
+
+ if (event_len == packet_error)
+ {
+ uint mysql_error_number= mysql_errno(mysql);
+ switch (mysql_error_number) {
+ case CR_NET_PACKET_TOO_LARGE:
+ sql_print_error("\
+Log entry on master is longer than slave_max_allowed_packet (%lu) on \
+slave. If the entry is correct, restart the server with a higher value of \
+slave_max_allowed_packet",
+ slave_max_allowed_packet);
+ mi->report(ERROR_LEVEL, ER_NET_PACKET_TOO_LARGE, NULL,
+ "%s", "Got a packet bigger than 'slave_max_allowed_packet' bytes");
+ goto err;
+ case ER_MASTER_FATAL_ERROR_READING_BINLOG:
+ mi->report(ERROR_LEVEL, ER_MASTER_FATAL_ERROR_READING_BINLOG, NULL,
+ ER(ER_MASTER_FATAL_ERROR_READING_BINLOG),
+ mysql_error_number, mysql_error(mysql));
+ goto err;
+ case ER_OUT_OF_RESOURCES:
+ sql_print_error("\
+Stopping slave I/O thread due to out-of-memory error from master");
+ mi->report(ERROR_LEVEL, ER_OUT_OF_RESOURCES, NULL,
+ "%s", ER(ER_OUT_OF_RESOURCES));
+ goto err;
+ }
+ if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
+ reconnect_messages[SLAVE_RECON_ACT_EVENT]))
+ goto err;
+ goto connected;
+ } // if (event_len == packet_error)
+
+ retry_count=0; // ok event, reset retry counter
+ THD_STAGE_INFO(thd, stage_queueing_master_event_to_the_relay_log);
+ event_buf= (const char*)mysql->net.read_pos + 1;
+ if (RUN_HOOK(binlog_relay_io, after_read_event,
+ (thd, mi,(const char*)mysql->net.read_pos + 1,
+ event_len, &event_buf, &event_len)))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ ER(ER_SLAVE_FATAL_ERROR),
+ "Failed to run 'after_read_event' hook");
+ goto err;
+ }
+
+ /* XXX: 'synced' should be updated by queue_event to indicate
+ whether event has been synced to disk */
+ bool synced= 0;
+ if (queue_event(mi, event_buf, event_len))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL,
+ ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
+ "could not queue event from master");
+ goto err;
+ }
+
+ if (RUN_HOOK(binlog_relay_io, after_queue_event,
+ (thd, mi, event_buf, event_len, synced)))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ ER(ER_SLAVE_FATAL_ERROR),
+ "Failed to run 'after_queue_event' hook");
+ goto err;
+ }
+
+ if (mi->using_gtid == Master_info::USE_GTID_NO &&
+ flush_master_info(mi, TRUE, TRUE))
+ {
+ sql_print_error("Failed to flush master info file");
+ goto err;
+ }
+ /*
+ See if the relay logs take too much space.
+ We don't lock mi->rli.log_space_lock here; this dirty read saves time
+ and does not introduce any problem:
+ - if mi->rli.ignore_log_space_limit is 1 but becomes 0 just after (so
+ the clean value is 0), then we are reading only one more event as we
+ should, and we'll block only at the next event. No big deal.
+ - if mi->rli.ignore_log_space_limit is 0 but becomes 1 just
+ after (so the clean value is 1), then we are going into
+ wait_for_relay_log_space() for no reason, but this function
+ will do a clean read, notice the clean value and exit
+ immediately.
+ */
+#ifndef DBUG_OFF
+ {
+ char llbuf1[22], llbuf2[22];
+ DBUG_PRINT("info", ("log_space_limit=%s log_space_total=%s \
+ignore_log_space_limit=%d",
+ llstr(rli->log_space_limit,llbuf1),
+ llstr(rli->log_space_total,llbuf2),
+ (int) rli->ignore_log_space_limit));
+ }
+#endif
+
+ if (rli->log_space_limit && rli->log_space_limit <
+ rli->log_space_total &&
+ !rli->ignore_log_space_limit)
+ if (wait_for_relay_log_space(rli))
+ {
+ sql_print_error("Slave I/O thread aborted while waiting for relay \
+log space");
+ goto err;
+ }
+ }
+ }
+
+ // error = 0;
+err:
+ // print the current replication position
+ if (mi->using_gtid == Master_info::USE_GTID_NO)
+ sql_print_information("Slave I/O thread exiting, read up to log '%s', "
+ "position %s",
+ IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff));
+ else
+ {
+ String tmp;
+ mi->gtid_current_pos.to_string(&tmp);
+ sql_print_information("Slave I/O thread exiting, read up to log '%s', "
+ "position %s; GTID position %s",
+ IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff),
+ tmp.c_ptr_safe());
+ }
+ RUN_HOOK(binlog_relay_io, thread_stop, (thd, mi));
+ thd->reset_query();
+ thd->reset_db(NULL, 0);
+ if (mysql)
+ {
+ /*
+ Here we need to clear the active VIO before closing the
+ connection with the master. The reason is that THD::awake()
+ might be called from terminate_slave_thread() because somebody
+ issued a STOP SLAVE. If that happends, the close_active_vio()
+ can be called in the middle of closing the VIO associated with
+ the 'mysql' object, causing a crash.
+ */
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ thd->clear_active_vio();
+#endif
+ mysql_close(mysql);
+ mi->mysql=0;
+ }
+ write_ignored_events_info_to_relay_log(thd, mi);
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ flush_master_info(mi, TRUE, TRUE);
+ THD_STAGE_INFO(thd, stage_waiting_for_slave_mutex_on_exit);
+ thd->add_status_to_global();
+ mysql_mutex_lock(&mi->run_lock);
+
+err_during_init:
+ /* Forget the relay log's format */
+ delete mi->rli.relay_log.description_event_for_queue;
+ mi->rli.relay_log.description_event_for_queue= 0;
+ // TODO: make rpl_status part of Master_info
+ change_rpl_status(RPL_ACTIVE_SLAVE,RPL_IDLE_SLAVE);
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->unlink();
+ mysql_mutex_unlock(&LOCK_thread_count);
+ THD_CHECK_SENTRY(thd);
+ delete thd;
+ mi->abort_slave= 0;
+ mi->slave_running= MYSQL_SLAVE_NOT_RUN;
+ mi->io_thd= 0;
+ /*
+ Note: the order of the two following calls (first broadcast, then unlock)
+ is important. Otherwise a killer_thread can execute between the calls and
+ delete the mi structure leading to a crash! (see BUG#25306 for details)
+ */
+ mysql_cond_broadcast(&mi->stop_cond); // tell the world we are done
+ DBUG_EXECUTE_IF("simulate_slave_delay_at_terminate_bug38694", sleep(5););
+ mysql_mutex_unlock(&mi->run_lock);
+
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+ my_thread_end();
+#ifdef HAVE_OPENSSL
+ ERR_remove_state(0);
+#endif
+ pthread_exit(0);
+ return 0; // Avoid compiler warnings
+}
+
+/*
+ Check the temporary directory used by commands like
+ LOAD DATA INFILE.
+
+ As the directory never changes during a mysqld run, we only
+ test this once and cache the result. This also resolve a race condition
+ when this can be run by multiple threads at the same time.
+ */
+
+static bool check_temp_dir_run= 0;
+static int check_temp_dir_result= 0;
+
+static
+int check_temp_dir(char* tmp_file)
+{
+ File fd;
+ int result= 1; // Assume failure
+ MY_DIR *dirp;
+ char tmp_dir[FN_REFLEN];
+ size_t tmp_dir_size;
+ DBUG_ENTER("check_temp_dir");
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ if (check_temp_dir_run)
+ {
+ result= check_temp_dir_result;
+ goto end;
+ }
+ check_temp_dir_run= 1;
+
+ /*
+ Get the directory from the temporary file.
+ */
+ dirname_part(tmp_dir, tmp_file, &tmp_dir_size);
+
+ /*
+ Check if the directory exists.
+ */
+ if (!(dirp=my_dir(tmp_dir,MYF(MY_WME))))
+ goto end;
+ my_dirend(dirp);
+
+ /*
+ Check permissions to create a file. We use O_TRUNC to ensure that
+ things works even if we happen to have and old file laying around.
+ */
+ if ((fd= mysql_file_create(key_file_misc,
+ tmp_file, CREATE_MODE,
+ O_WRONLY | O_BINARY | O_TRUNC | O_NOFOLLOW,
+ MYF(MY_WME))) < 0)
+ goto end;
+
+ result= 0; // Directory name ok
+ /*
+ Clean up.
+ */
+ mysql_file_close(fd, MYF(0));
+ mysql_file_delete(key_file_misc, tmp_file, MYF(0));
+
+end:
+ check_temp_dir_result= result;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_RETURN(result);
+}
+
+
+void
+slave_output_error_info(rpl_group_info *rgi, THD *thd)
+{
+ /*
+ retrieve as much info as possible from the thd and, error
+ codes and warnings and print this to the error log as to
+ allow the user to locate the error
+ */
+ Relay_log_info *rli= rgi->rli;
+ uint32 const last_errno= rli->last_error().number;
+ char llbuff[22];
+
+ if (thd->is_error())
+ {
+ char const *const errmsg= thd->get_stmt_da()->message();
+
+ DBUG_PRINT("info",
+ ("thd->get_stmt_da()->sql_errno()=%d; rli->last_error.number=%d",
+ thd->get_stmt_da()->sql_errno(), last_errno));
+ if (last_errno == 0)
+ {
+ /*
+ This function is reporting an error which was not reported
+ while executing exec_relay_log_event().
+ */
+ rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(),
+ rgi->gtid_info(), "%s", errmsg);
+ }
+ else if (last_errno != thd->get_stmt_da()->sql_errno())
+ {
+ /*
+ * An error was reported while executing exec_relay_log_event()
+ * however the error code differs from what is in the thread.
+ * This function prints out more information to help finding
+ * what caused the problem.
+ */
+ sql_print_error("Slave (additional info): %s Error_code: %d",
+ errmsg, thd->get_stmt_da()->sql_errno());
+ }
+ }
+
+ /* Print any warnings issued */
+ Diagnostics_area::Sql_condition_iterator it=
+ thd->get_stmt_da()->sql_conditions();
+ const Sql_condition *err;
+ /*
+ Added controlled slave thread cancel for replication
+ of user-defined variables.
+ */
+ bool udf_error = false;
+ while ((err= it++))
+ {
+ if (err->get_sql_errno() == ER_CANT_OPEN_LIBRARY)
+ udf_error = true;
+ sql_print_warning("Slave: %s Error_code: %d", err->get_message_text(), err->get_sql_errno());
+ }
+ if (udf_error)
+ {
+ String tmp;
+ if (rli->mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ tmp.append(STRING_WITH_LEN("; GTID position '"));
+ rpl_append_gtid_state(&tmp, false);
+ tmp.append(STRING_WITH_LEN("'"));
+ }
+ sql_print_error("Error loading user-defined library, slave SQL "
+ "thread aborted. Install the missing library, and restart the "
+ "slave SQL thread with \"SLAVE START\". We stopped at log '%s' "
+ "position %s%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos,
+ llbuff), tmp.c_ptr_safe());
+ }
+ else
+ {
+ String tmp;
+ if (rli->mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ tmp.append(STRING_WITH_LEN("; GTID position '"));
+ rpl_append_gtid_state(&tmp, false);
+ tmp.append(STRING_WITH_LEN("'"));
+ }
+ sql_print_error("\
+Error running query, slave SQL thread aborted. Fix the problem, and restart \
+the slave SQL thread with \"SLAVE START\". We stopped at log \
+'%s' position %s%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff),
+ tmp.c_ptr_safe());
+ }
+}
+
+
+/**
+ Slave SQL thread entry point.
+
+ @param arg Pointer to Relay_log_info object that holds information
+ for the SQL thread.
+
+ @return Always 0.
+*/
+pthread_handler_t handle_slave_sql(void *arg)
+{
+ THD *thd; /* needs to be first for thread_stack */
+ char llbuff[22],llbuff1[22];
+ char saved_log_name[FN_REFLEN];
+ char saved_master_log_name[FN_REFLEN];
+ my_off_t UNINIT_VAR(saved_log_pos);
+ my_off_t UNINIT_VAR(saved_master_log_pos);
+ String saved_skip_gtid_pos;
+ my_off_t saved_skip= 0;
+ Master_info *mi= ((Master_info*)arg);
+ Relay_log_info* rli = &mi->rli;
+ const char *errmsg;
+ rpl_group_info *serial_rgi;
+ rpl_sql_thread_info sql_info(mi->rpl_filter);
+
+ // needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff
+ my_thread_init();
+ DBUG_ENTER("handle_slave_sql");
+
+ LINT_INIT(saved_master_log_pos);
+ LINT_INIT(saved_log_pos);
+
+ serial_rgi= new rpl_group_info(rli);
+ thd = new THD; // note that contructor of THD uses DBUG_ !
+ thd->thread_stack = (char*)&thd; // remember where our stack is
+ thd->system_thread_info.rpl_sql_info= &sql_info;
+
+ DBUG_ASSERT(rli->inited);
+ DBUG_ASSERT(rli->mi == mi);
+ mysql_mutex_lock(&rli->run_lock);
+ DBUG_ASSERT(!rli->slave_running);
+ errmsg= 0;
+#ifndef DBUG_OFF
+ rli->events_till_abort = abort_slave_event_count;
+#endif
+
+ /*
+ THD for the sql driver thd. In parallel replication this is the thread
+ that reads things from the relay log and calls rpl_parallel::do_event()
+ to execute queries.
+
+ In single thread replication this is the THD for the thread that is
+ executing SQL queries too.
+ */
+ serial_rgi->thd= rli->sql_driver_thd= thd;
+
+ /* Inform waiting threads that slave has started */
+ rli->slave_run_id++;
+ rli->slave_running= MYSQL_SLAVE_RUN_NOT_CONNECT;
+
+ pthread_detach_this_thread();
+ if (init_slave_thread(thd, mi, SLAVE_THD_SQL))
+ {
+ /*
+ TODO: this is currently broken - slave start and change master
+ will be stuck if we fail here
+ */
+ mysql_cond_broadcast(&rli->start_cond);
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ "Failed during slave thread initialization");
+ goto err_during_init;
+ }
+ thd->init_for_queries();
+ thd->rgi_slave= serial_rgi;
+ if ((serial_rgi->deferred_events_collecting= mi->rpl_filter->is_on()))
+ {
+ serial_rgi->deferred_events= new Deferred_log_events(rli);
+ }
+
+ /*
+ binlog_annotate_row_events must be TRUE only after an Annotate_rows event
+ has been received and only till the last corresponding rbr event has been
+ applied. In all other cases it must be FALSE.
+ */
+ thd->variables.binlog_annotate_row_events= 0;
+ mysql_mutex_lock(&LOCK_thread_count);
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ /*
+ We are going to set slave_running to 1. Assuming slave I/O thread is
+ alive and connected, this is going to make Seconds_Behind_Master be 0
+ i.e. "caught up". Even if we're just at start of thread. Well it's ok, at
+ the moment we start we can think we are caught up, and the next second we
+ start receiving data so we realize we are not caught up and
+ Seconds_Behind_Master grows. No big deal.
+ */
+ rli->abort_slave = 0;
+ rli->stop_for_until= false;
+ mysql_mutex_unlock(&rli->run_lock);
+ mysql_cond_broadcast(&rli->start_cond);
+
+ /*
+ Reset errors for a clean start (otherwise, if the master is idle, the SQL
+ thread may execute no Query_log_event, so the error will remain even
+ though there's no problem anymore). Do not reset the master timestamp
+ (imagine the slave has caught everything, the STOP SLAVE and START SLAVE:
+ as we are not sure that we are going to receive a query, we want to
+ remember the last master timestamp (to say how many seconds behind we are
+ now.
+ But the master timestamp is reset by RESET SLAVE & CHANGE MASTER.
+ */
+ rli->clear_error();
+ rli->parallel.reset();
+
+ //tell the I/O thread to take relay_log_space_limit into account from now on
+ mysql_mutex_lock(&rli->log_space_lock);
+ rli->ignore_log_space_limit= 0;
+ mysql_mutex_unlock(&rli->log_space_lock);
+
+ serial_rgi->gtid_sub_id= 0;
+ serial_rgi->gtid_pending= false;
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ /*
+ We initialize the relay log state from the know starting position.
+ It will then be updated as required by GTID and GTID_LIST events found
+ while applying events read from relay logs.
+ */
+ rli->relay_log_state.load(&rpl_global_gtid_slave_state);
+ }
+ rli->gtid_skip_flag = GTID_SKIP_NOT;
+ if (init_relay_log_pos(rli,
+ rli->group_relay_log_name,
+ rli->group_relay_log_pos,
+ 1 /*need data lock*/, &errmsg,
+ 1 /*look for a description_event*/))
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ "Error initializing relay log position: %s", errmsg);
+ goto err;
+ }
+ rli->reset_inuse_relaylog();
+ if (rli->alloc_inuse_relaylog(rli->group_relay_log_name))
+ goto err;
+
+ strcpy(rli->future_event_master_log_name, rli->group_master_log_name);
+ THD_CHECK_SENTRY(thd);
+#ifndef DBUG_OFF
+ {
+ char llbuf1[22], llbuf2[22];
+ DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%s rli->event_relay_log_pos=%s",
+ llstr(my_b_tell(rli->cur_log),llbuf1),
+ llstr(rli->event_relay_log_pos,llbuf2)));
+ DBUG_ASSERT(rli->event_relay_log_pos >= BIN_LOG_HEADER_SIZE);
+ /*
+ Wonder if this is correct. I (Guilhem) wonder if my_b_tell() returns the
+ correct position when it's called just after my_b_seek() (the questionable
+ stuff is those "seek is done on next read" comments in the my_b_seek()
+ source code).
+ The crude reality is that this assertion randomly fails whereas
+ replication seems to work fine. And there is no easy explanation why it
+ fails (as we my_b_seek(rli->event_relay_log_pos) at the very end of
+ init_relay_log_pos() called above). Maybe the assertion would be
+ meaningful if we held rli->data_lock between the my_b_seek() and the
+ DBUG_ASSERT().
+ */
+#ifdef SHOULD_BE_CHECKED
+ DBUG_ASSERT(my_b_tell(rli->cur_log) == rli->event_relay_log_pos);
+#endif
+ }
+#endif
+
+ DBUG_PRINT("master_info",("log_file_name: %s position: %s",
+ rli->group_master_log_name,
+ llstr(rli->group_master_log_pos,llbuff)));
+ if (global_system_variables.log_warnings)
+ {
+ String tmp;
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ tmp.append(STRING_WITH_LEN("; GTID position '"));
+ rpl_append_gtid_state(&tmp,
+ mi->using_gtid==Master_info::USE_GTID_CURRENT_POS);
+ tmp.append(STRING_WITH_LEN("'"));
+ }
+ sql_print_information("Slave SQL thread initialized, starting replication in \
+log '%s' at position %s, relay log '%s' position: %s%s", RPL_LOG_NAME,
+ llstr(rli->group_master_log_pos,llbuff),rli->group_relay_log_name,
+ llstr(rli->group_relay_log_pos,llbuff1), tmp.c_ptr_safe());
+ }
+
+ if (check_temp_dir(rli->slave_patternload_file))
+ {
+ rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL,
+ "Unable to use slave's temporary directory %s - %s",
+ slave_load_tmpdir, thd->get_stmt_da()->message());
+ goto err;
+ }
+
+ /* Load the set of seen GTIDs, if we did not already. */
+ if (rpl_load_gtid_slave_state(thd))
+ {
+ rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL,
+ "Unable to load replication GTID slave state from mysql.%s: %s",
+ rpl_gtid_slave_state_table_name.str,
+ thd->get_stmt_da()->message());
+ /*
+ If we are using old-style replication, we can continue, even though we
+ then will not be able to record the GTIDs we receive. But if using GTID,
+ we must give up.
+ */
+ if (mi->using_gtid != Master_info::USE_GTID_NO || opt_gtid_strict_mode)
+ goto err;
+ }
+
+ /* execute init_slave variable */
+ if (opt_init_slave.length)
+ {
+ execute_init_command(thd, &opt_init_slave, &LOCK_sys_init_slave);
+ if (thd->is_slave_error)
+ {
+ rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL,
+ "Slave SQL thread aborted. Can't execute init_slave query");
+ goto err;
+ }
+ }
+
+ /*
+ First check until condition - probably there is nothing to execute. We
+ do not want to wait for next event in this case.
+ */
+ mysql_mutex_lock(&rli->data_lock);
+ if (rli->slave_skip_counter)
+ {
+ strmake_buf(saved_log_name, rli->group_relay_log_name);
+ strmake_buf(saved_master_log_name, rli->group_master_log_name);
+ saved_log_pos= rli->group_relay_log_pos;
+ saved_master_log_pos= rli->group_master_log_pos;
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ saved_skip_gtid_pos.append(STRING_WITH_LEN(", GTID '"));
+ rpl_append_gtid_state(&saved_skip_gtid_pos, false);
+ saved_skip_gtid_pos.append(STRING_WITH_LEN("'; "));
+ }
+ saved_skip= rli->slave_skip_counter;
+ }
+ if ((rli->until_condition == Relay_log_info::UNTIL_MASTER_POS ||
+ rli->until_condition == Relay_log_info::UNTIL_RELAY_POS) &&
+ rli->is_until_satisfied(thd, NULL))
+ {
+ char buf[22];
+ sql_print_information("Slave SQL thread stopped because it reached its"
+ " UNTIL position %s", llstr(rli->until_pos(), buf));
+ mysql_mutex_unlock(&rli->data_lock);
+ goto err;
+ }
+ mysql_mutex_unlock(&rli->data_lock);
+
+ /* Read queries from the IO/THREAD until this thread is killed */
+
+ while (!sql_slave_killed(serial_rgi))
+ {
+ THD_STAGE_INFO(thd, stage_reading_event_from_the_relay_log);
+ THD_CHECK_SENTRY(thd);
+
+ if (saved_skip && rli->slave_skip_counter == 0)
+ {
+ String tmp;
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ tmp.append(STRING_WITH_LEN(", GTID '"));
+ rpl_append_gtid_state(&tmp, false);
+ tmp.append(STRING_WITH_LEN("'; "));
+ }
+
+ sql_print_information("'SQL_SLAVE_SKIP_COUNTER=%ld' executed at "
+ "relay_log_file='%s', relay_log_pos='%ld', master_log_name='%s', "
+ "master_log_pos='%ld'%s and new position at "
+ "relay_log_file='%s', relay_log_pos='%ld', master_log_name='%s', "
+ "master_log_pos='%ld'%s ",
+ (ulong) saved_skip, saved_log_name, (ulong) saved_log_pos,
+ saved_master_log_name, (ulong) saved_master_log_pos,
+ saved_skip_gtid_pos.c_ptr_safe(),
+ rli->group_relay_log_name, (ulong) rli->group_relay_log_pos,
+ rli->group_master_log_name, (ulong) rli->group_master_log_pos,
+ tmp.c_ptr_safe());
+ saved_skip= 0;
+ saved_skip_gtid_pos.free();
+ }
+
+ if (exec_relay_log_event(thd, rli, serial_rgi))
+ {
+ DBUG_PRINT("info", ("exec_relay_log_event() failed"));
+ // do not scare the user if SQL thread was simply killed or stopped
+ if (!sql_slave_killed(serial_rgi))
+ slave_output_error_info(serial_rgi, thd);
+ goto err;
+ }
+ }
+
+ if (opt_slave_parallel_threads > 0)
+ rli->parallel.wait_for_done(thd, rli);
+
+ /* Thread stopped. Print the current replication position to the log */
+ {
+ String tmp;
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ tmp.append(STRING_WITH_LEN("; GTID position '"));
+ rpl_append_gtid_state(&tmp, false);
+ tmp.append(STRING_WITH_LEN("'"));
+ }
+ sql_print_information("Slave SQL thread exiting, replication stopped in "
+ "log '%s' at position %s%s",
+ RPL_LOG_NAME,
+ llstr(rli->group_master_log_pos,llbuff),
+ tmp.c_ptr_safe());
+ }
+
+ err:
+
+ /*
+ Once again, in case we aborted with an error and skipped the first one.
+ (We want the first one to be before the printout of stop position to
+ get the correct position printed.)
+ */
+ if (opt_slave_parallel_threads > 0)
+ rli->parallel.wait_for_done(thd, rli);
+
+ /*
+ Some events set some playgrounds, which won't be cleared because thread
+ stops. Stopping of this thread may not be known to these events ("stop"
+ request is detected only by the present function, not by events), so we
+ must "proactively" clear playgrounds:
+ */
+ thd->clear_error();
+ serial_rgi->cleanup_context(thd, 1);
+ /*
+ Some extra safety, which should not been needed (normally, event deletion
+ should already have done these assignments (each event which sets these
+ variables is supposed to set them to 0 before terminating)).
+ */
+ thd->catalog= 0;
+ thd->reset_query();
+ thd->reset_db(NULL, 0);
+ if (rli->mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ ulong domain_count;
+
+ flush_relay_log_info(rli);
+ if (opt_slave_parallel_threads > 0)
+ {
+ /*
+ In parallel replication GTID mode, we may stop with different domains
+ at different positions in the relay log.
+
+ To handle this when we restart the SQL thread, mark the current
+ per-domain position in the Relay_log_info.
+ */
+ mysql_mutex_lock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ domain_count= rpl_global_gtid_slave_state.count();
+ mysql_mutex_unlock(&rpl_global_gtid_slave_state.LOCK_slave_state);
+ if (domain_count > 1)
+ {
+ inuse_relaylog *ir;
+
+ /*
+ Load the starting GTID position, so that we can skip already applied
+ GTIDs when we restart the SQL thread. And set the start position in
+ the relay log back to a known safe place to start (prior to any not
+ yet applied transaction in any domain).
+ */
+ rli->restart_gtid_pos.load(&rpl_global_gtid_slave_state, NULL, 0);
+ if ((ir= rli->inuse_relaylog_list))
+ {
+ rpl_gtid *gtid= ir->relay_log_state;
+ uint32 count= ir->relay_log_state_count;
+ while (count > 0)
+ {
+ process_gtid_for_restart_pos(rli, gtid);
+ ++gtid;
+ --count;
+ }
+ strmake_buf(rli->group_relay_log_name, ir->name);
+ rli->group_relay_log_pos= BIN_LOG_HEADER_SIZE;
+ }
+ }
+ }
+ }
+ THD_STAGE_INFO(thd, stage_waiting_for_slave_mutex_on_exit);
+ thd->add_status_to_global();
+ mysql_mutex_lock(&rli->run_lock);
+err_during_init:
+ /* We need data_lock, at least to wake up any waiting master_pos_wait() */
+ mysql_mutex_lock(&rli->data_lock);
+ DBUG_ASSERT(rli->slave_running == MYSQL_SLAVE_RUN_NOT_CONNECT); // tracking buffer overrun
+ /* When master_pos_wait() wakes up it will check this and terminate */
+ rli->slave_running= MYSQL_SLAVE_NOT_RUN;
+ /* Forget the relay log's format */
+ delete rli->relay_log.description_event_for_exec;
+ rli->relay_log.description_event_for_exec= 0;
+ rli->reset_inuse_relaylog();
+ /* Wake up master_pos_wait() */
+ mysql_mutex_unlock(&rli->data_lock);
+ DBUG_PRINT("info",("Signaling possibly waiting master_pos_wait() functions"));
+ mysql_cond_broadcast(&rli->data_cond);
+ rli->ignore_log_space_limit= 0; /* don't need any lock */
+ /* we die so won't remember charset - re-update them on next thread start */
+ thd->system_thread_info.rpl_sql_info->cached_charset_invalidate();
+
+ /*
+ TODO: see if we can do this conditionally in next_event() instead
+ to avoid unneeded position re-init
+ */
+ thd->temporary_tables = 0; // remove tempation from destructor to close them
+ THD_CHECK_SENTRY(thd);
+ rli->sql_driver_thd= 0;
+ mysql_mutex_lock(&LOCK_thread_count);
+ THD_CHECK_SENTRY(thd);
+ thd->rgi_fake= thd->rgi_slave= NULL;
+ delete serial_rgi;
+ delete thd;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ /*
+ Note: the order of the broadcast and unlock calls below (first broadcast, then unlock)
+ is important. Otherwise a killer_thread can execute between the calls and
+ delete the mi structure leading to a crash! (see BUG#25306 for details)
+ */
+ mysql_cond_broadcast(&rli->stop_cond);
+ DBUG_EXECUTE_IF("simulate_slave_delay_at_terminate_bug38694", sleep(5););
+ mysql_mutex_unlock(&rli->run_lock); // tell the world we are done
+
+ DBUG_LEAVE; // Must match DBUG_ENTER()
+ my_thread_end();
+#ifdef HAVE_OPENSSL
+ ERR_remove_state(0);
+#endif
+ pthread_exit(0);
+ return 0; // Avoid compiler warnings
+}
+
+
+/*
+ process_io_create_file()
+*/
+
+static int process_io_create_file(Master_info* mi, Create_file_log_event* cev)
+{
+ int error = 1;
+ ulong num_bytes;
+ bool cev_not_written;
+ THD *thd = mi->io_thd;
+ NET *net = &mi->mysql->net;
+ DBUG_ENTER("process_io_create_file");
+
+ if (unlikely(!cev->is_valid()))
+ DBUG_RETURN(1);
+
+ if (!mi->rpl_filter->db_ok(cev->db))
+ {
+ skip_load_data_infile(net);
+ DBUG_RETURN(0);
+ }
+ DBUG_ASSERT(cev->inited_from_old);
+ thd->file_id = cev->file_id = mi->file_id++;
+ thd->variables.server_id = cev->server_id;
+ cev_not_written = 1;
+
+ if (unlikely(net_request_file(net,cev->fname)))
+ {
+ sql_print_error("Slave I/O: failed requesting download of '%s'",
+ cev->fname);
+ goto err;
+ }
+
+ /*
+ This dummy block is so we could instantiate Append_block_log_event
+ once and then modify it slightly instead of doing it multiple times
+ in the loop
+ */
+ {
+ Append_block_log_event aev(thd,0,0,0,0);
+
+ for (;;)
+ {
+ if (unlikely((num_bytes=my_net_read(net)) == packet_error))
+ {
+ sql_print_error("Network read error downloading '%s' from master",
+ cev->fname);
+ goto err;
+ }
+ if (unlikely(!num_bytes)) /* eof */
+ {
+ /* 3.23 master wants it */
+ net_write_command(net, 0, (uchar*) "", 0, (uchar*) "", 0);
+ /*
+ If we wrote Create_file_log_event, then we need to write
+ Execute_load_log_event. If we did not write Create_file_log_event,
+ then this is an empty file and we can just do as if the LOAD DATA
+ INFILE had not existed, i.e. write nothing.
+ */
+ if (unlikely(cev_not_written))
+ break;
+ Execute_load_log_event xev(thd,0,0);
+ xev.log_pos = cev->log_pos;
+ if (unlikely(mi->rli.relay_log.append(&xev)))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL,
+ ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
+ "error writing Exec_load event to relay log");
+ goto err;
+ }
+ mi->rli.relay_log.harvest_bytes_written(&mi->rli.log_space_total);
+ break;
+ }
+ if (unlikely(cev_not_written))
+ {
+ cev->block = net->read_pos;
+ cev->block_len = num_bytes;
+ if (unlikely(mi->rli.relay_log.append(cev)))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL,
+ ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
+ "error writing Create_file event to relay log");
+ goto err;
+ }
+ cev_not_written=0;
+ mi->rli.relay_log.harvest_bytes_written(&mi->rli.log_space_total);
+ }
+ else
+ {
+ aev.block = net->read_pos;
+ aev.block_len = num_bytes;
+ aev.log_pos = cev->log_pos;
+ if (unlikely(mi->rli.relay_log.append(&aev)))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL,
+ ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
+ "error writing Append_block event to relay log");
+ goto err;
+ }
+ mi->rli.relay_log.harvest_bytes_written(&mi->rli.log_space_total) ;
+ }
+ }
+ }
+ error=0;
+err:
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Start using a new binary log on the master
+
+ SYNOPSIS
+ process_io_rotate()
+ mi master_info for the slave
+ rev The rotate log event read from the binary log
+
+ DESCRIPTION
+ Updates the master info with the place in the next binary
+ log where we should start reading.
+ Rotate the relay log to avoid mixed-format relay logs.
+
+ NOTES
+ We assume we already locked mi->data_lock
+
+ RETURN VALUES
+ 0 ok
+ 1 Log event is illegal
+
+*/
+
+static int process_io_rotate(Master_info *mi, Rotate_log_event *rev)
+{
+ DBUG_ENTER("process_io_rotate");
+ mysql_mutex_assert_owner(&mi->data_lock);
+
+ if (unlikely(!rev->is_valid()))
+ DBUG_RETURN(1);
+
+ /* Safe copy as 'rev' has been "sanitized" in Rotate_log_event's ctor */
+ memcpy(mi->master_log_name, rev->new_log_ident, rev->ident_len+1);
+ mi->master_log_pos= rev->pos;
+ DBUG_PRINT("info", ("master_log_pos: '%s' %lu",
+ mi->master_log_name, (ulong) mi->master_log_pos));
+#ifndef DBUG_OFF
+ /*
+ If we do not do this, we will be getting the first
+ rotate event forever, so we need to not disconnect after one.
+ */
+ if (disconnect_slave_event_count)
+ mi->events_till_disconnect++;
+#endif
+
+ /*
+ If description_event_for_queue is format <4, there is conversion in the
+ relay log to the slave's format (4). And Rotate can mean upgrade or
+ nothing. If upgrade, it's to 5.0 or newer, so we will get a Format_desc, so
+ no need to reset description_event_for_queue now. And if it's nothing (same
+ master version as before), no need (still using the slave's format).
+ */
+ if (mi->rli.relay_log.description_event_for_queue->binlog_version >= 4)
+ {
+ DBUG_ASSERT(mi->rli.relay_log.description_event_for_queue->checksum_alg ==
+ mi->rli.relay_log.relay_log_checksum_alg);
+
+ delete mi->rli.relay_log.description_event_for_queue;
+ /* start from format 3 (MySQL 4.0) again */
+ mi->rli.relay_log.description_event_for_queue= new
+ Format_description_log_event(3);
+ mi->rli.relay_log.description_event_for_queue->checksum_alg=
+ mi->rli.relay_log.relay_log_checksum_alg;
+ }
+ /*
+ Rotate the relay log makes binlog format detection easier (at next slave
+ start or mysqlbinlog)
+ */
+ DBUG_RETURN(rotate_relay_log(mi) /* will take the right mutexes */);
+}
+
+/*
+ Reads a 3.23 event and converts it to the slave's format. This code was
+ copied from MySQL 4.0.
+*/
+static int queue_binlog_ver_1_event(Master_info *mi, const char *buf,
+ ulong event_len)
+{
+ const char *errmsg = 0;
+ ulong inc_pos;
+ bool ignore_event= 0;
+ char *tmp_buf = 0;
+ Relay_log_info *rli= &mi->rli;
+ DBUG_ENTER("queue_binlog_ver_1_event");
+
+ /*
+ If we get Load event, we need to pass a non-reusable buffer
+ to read_log_event, so we do a trick
+ */
+ if ((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT)
+ {
+ if (unlikely(!(tmp_buf=(char*)my_malloc(event_len+1,MYF(MY_WME)))))
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ ER(ER_SLAVE_FATAL_ERROR), "Memory allocation failed");
+ DBUG_RETURN(1);
+ }
+ memcpy(tmp_buf,buf,event_len);
+ /*
+ Create_file constructor wants a 0 as last char of buffer, this 0 will
+ serve as the string-termination char for the file's name (which is at the
+ end of the buffer)
+ We must increment event_len, otherwise the event constructor will not see
+ this end 0, which leads to segfault.
+ */
+ tmp_buf[event_len++]=0;
+ int4store(tmp_buf+EVENT_LEN_OFFSET, event_len);
+ buf = (const char*)tmp_buf;
+ }
+ /*
+ This will transform LOAD_EVENT into CREATE_FILE_EVENT, ask the master to
+ send the loaded file, and write it to the relay log in the form of
+ Append_block/Exec_load (the SQL thread needs the data, as that thread is not
+ connected to the master).
+ */
+ Log_event *ev=
+ Log_event::read_log_event(buf, event_len, &errmsg,
+ mi->rli.relay_log.description_event_for_queue, 0);
+ if (unlikely(!ev))
+ {
+ sql_print_error("Read invalid event from master: '%s',\
+ master could be corrupt but a more likely cause of this is a bug",
+ errmsg);
+ my_free(tmp_buf);
+ DBUG_RETURN(1);
+ }
+
+ mysql_mutex_lock(&mi->data_lock);
+ ev->log_pos= mi->master_log_pos; /* 3.23 events don't contain log_pos */
+ switch (ev->get_type_code()) {
+ case STOP_EVENT:
+ ignore_event= 1;
+ inc_pos= event_len;
+ break;
+ case ROTATE_EVENT:
+ if (unlikely(process_io_rotate(mi,(Rotate_log_event*)ev)))
+ {
+ delete ev;
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(1);
+ }
+ inc_pos= 0;
+ break;
+ case CREATE_FILE_EVENT:
+ /*
+ Yes it's possible to have CREATE_FILE_EVENT here, even if we're in
+ queue_old_event() which is for 3.23 events which don't comprise
+ CREATE_FILE_EVENT. This is because read_log_event() above has just
+ transformed LOAD_EVENT into CREATE_FILE_EVENT.
+ */
+ {
+ /* We come here when and only when tmp_buf != 0 */
+ DBUG_ASSERT(tmp_buf != 0);
+ inc_pos=event_len;
+ ev->log_pos+= inc_pos;
+ int error = process_io_create_file(mi,(Create_file_log_event*)ev);
+ delete ev;
+ mi->master_log_pos += inc_pos;
+ DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
+ mysql_mutex_unlock(&mi->data_lock);
+ my_free(tmp_buf);
+ DBUG_RETURN(error);
+ }
+ default:
+ inc_pos= event_len;
+ break;
+ }
+ if (likely(!ignore_event))
+ {
+ if (ev->log_pos)
+ /*
+ Don't do it for fake Rotate events (see comment in
+ Log_event::Log_event(const char* buf...) in log_event.cc).
+ */
+ ev->log_pos+= event_len; /* make log_pos be the pos of the end of the event */
+ if (unlikely(rli->relay_log.append(ev)))
+ {
+ delete ev;
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(1);
+ }
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ }
+ delete ev;
+ mi->master_log_pos+= inc_pos;
+ DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(0);
+}
+
+/*
+ Reads a 4.0 event and converts it to the slave's format. This code was copied
+ from queue_binlog_ver_1_event(), with some affordable simplifications.
+*/
+static int queue_binlog_ver_3_event(Master_info *mi, const char *buf,
+ ulong event_len)
+{
+ const char *errmsg = 0;
+ ulong inc_pos;
+ char *tmp_buf = 0;
+ Relay_log_info *rli= &mi->rli;
+ DBUG_ENTER("queue_binlog_ver_3_event");
+
+ /* read_log_event() will adjust log_pos to be end_log_pos */
+ Log_event *ev=
+ Log_event::read_log_event(buf,event_len, &errmsg,
+ mi->rli.relay_log.description_event_for_queue, 0);
+ if (unlikely(!ev))
+ {
+ sql_print_error("Read invalid event from master: '%s',\
+ master could be corrupt but a more likely cause of this is a bug",
+ errmsg);
+ my_free(tmp_buf);
+ DBUG_RETURN(1);
+ }
+ mysql_mutex_lock(&mi->data_lock);
+ switch (ev->get_type_code()) {
+ case STOP_EVENT:
+ goto err;
+ case ROTATE_EVENT:
+ if (unlikely(process_io_rotate(mi,(Rotate_log_event*)ev)))
+ {
+ delete ev;
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(1);
+ }
+ inc_pos= 0;
+ break;
+ default:
+ inc_pos= event_len;
+ break;
+ }
+
+ if (unlikely(rli->relay_log.append(ev)))
+ {
+ delete ev;
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(1);
+ }
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ delete ev;
+ mi->master_log_pos+= inc_pos;
+err:
+ DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_RETURN(0);
+}
+
+/*
+ queue_old_event()
+
+ Writes a 3.23 or 4.0 event to the relay log, after converting it to the 5.0
+ (exactly, slave's) format. To do the conversion, we create a 5.0 event from
+ the 3.23/4.0 bytes, then write this event to the relay log.
+
+ TODO:
+ Test this code before release - it has to be tested on a separate
+ setup with 3.23 master or 4.0 master
+*/
+
+static int queue_old_event(Master_info *mi, const char *buf,
+ ulong event_len)
+{
+ DBUG_ENTER("queue_old_event");
+
+ switch (mi->rli.relay_log.description_event_for_queue->binlog_version)
+ {
+ case 1:
+ DBUG_RETURN(queue_binlog_ver_1_event(mi,buf,event_len));
+ case 3:
+ DBUG_RETURN(queue_binlog_ver_3_event(mi,buf,event_len));
+ default: /* unsupported format; eg version 2 */
+ DBUG_PRINT("info",("unsupported binlog format %d in queue_old_event()",
+ mi->rli.relay_log.description_event_for_queue->binlog_version));
+ DBUG_RETURN(1);
+ }
+}
+
+/*
+ queue_event()
+
+ If the event is 3.23/4.0, passes it to queue_old_event() which will convert
+ it. Otherwise, writes a 5.0 (or newer) event to the relay log. Then there is
+ no format conversion, it's pure read/write of bytes.
+ So a 5.0.0 slave's relay log can contain events in the slave's format or in
+ any >=5.0.0 format.
+*/
+
+static int queue_event(Master_info* mi,const char* buf, ulong event_len)
+{
+ int error= 0;
+ String error_msg;
+ ulonglong inc_pos;
+ ulonglong event_pos;
+ Relay_log_info *rli= &mi->rli;
+ mysql_mutex_t *log_lock= rli->relay_log.get_log_lock();
+ ulong s_id;
+ bool unlock_data_lock= TRUE;
+ bool gtid_skip_enqueue= false;
+ bool got_gtid_event= false;
+ rpl_gtid event_gtid;
+
+ /*
+ FD_q must have been prepared for the first R_a event
+ inside get_master_version_and_clock()
+ Show-up of FD:s affects checksum_alg at once because
+ that changes FD_queue.
+ */
+ uint8 checksum_alg= mi->checksum_alg_before_fd != BINLOG_CHECKSUM_ALG_UNDEF ?
+ mi->checksum_alg_before_fd :
+ mi->rli.relay_log.relay_log_checksum_alg;
+
+ char *save_buf= NULL; // needed for checksumming the fake Rotate event
+ char rot_buf[LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN];
+
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_OFF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_CRC32);
+
+ DBUG_ENTER("queue_event");
+ /*
+ FD_queue checksum alg description does not apply in a case of
+ FD itself. The one carries both parts of the checksum data.
+ */
+ if (buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT)
+ {
+ checksum_alg= get_checksum_alg(buf, event_len);
+ }
+ else if (buf[EVENT_TYPE_OFFSET] == START_EVENT_V3)
+ {
+ // checksum behaviour is similar to the pre-checksum FD handling
+ mi->checksum_alg_before_fd= BINLOG_CHECKSUM_ALG_UNDEF;
+ mi->rli.relay_log.description_event_for_queue->checksum_alg=
+ mi->rli.relay_log.relay_log_checksum_alg= checksum_alg=
+ BINLOG_CHECKSUM_ALG_OFF;
+ }
+
+ // does not hold always because of old binlog can work with NM
+ // DBUG_ASSERT(checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
+
+ // should hold unless manipulations with RL. Tests that do that
+ // will have to refine the clause.
+ DBUG_ASSERT(mi->rli.relay_log.relay_log_checksum_alg !=
+ BINLOG_CHECKSUM_ALG_UNDEF);
+
+ // Emulate the network corruption
+ DBUG_EXECUTE_IF("corrupt_queue_event",
+ if (buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT)
+ {
+ char *debug_event_buf_c = (char*) buf;
+ int debug_cor_pos = rand() % (event_len - BINLOG_CHECKSUM_LEN);
+ debug_event_buf_c[debug_cor_pos] =~ debug_event_buf_c[debug_cor_pos];
+ DBUG_PRINT("info", ("Corrupt the event at queue_event: byte on position %d", debug_cor_pos));
+ DBUG_SET("-d,corrupt_queue_event");
+ }
+ );
+
+ if (event_checksum_test((uchar *) buf, event_len, checksum_alg))
+ {
+ error= ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE;
+ unlock_data_lock= FALSE;
+ goto err;
+ }
+
+ if (mi->rli.relay_log.description_event_for_queue->binlog_version<4 &&
+ (uchar)buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT /* a way to escape */)
+ DBUG_RETURN(queue_old_event(mi,buf,event_len));
+
+ mysql_mutex_lock(&mi->data_lock);
+
+ switch ((uchar)buf[EVENT_TYPE_OFFSET]) {
+ case STOP_EVENT:
+ /*
+ We needn't write this event to the relay log. Indeed, it just indicates a
+ master server shutdown. The only thing this does is cleaning. But
+ cleaning is already done on a per-master-thread basis (as the master
+ server is shutting down cleanly, it has written all DROP TEMPORARY TABLE
+ prepared statements' deletion are TODO only when we binlog prep stmts).
+
+ We don't even increment mi->master_log_pos, because we may be just after
+ a Rotate event. Btw, in a few milliseconds we are going to have a Start
+ event from the next binlog (unless the master is presently running
+ without --log-bin).
+ */
+ goto err;
+ case ROTATE_EVENT:
+ {
+ Rotate_log_event rev(buf, checksum_alg != BINLOG_CHECKSUM_ALG_OFF ?
+ event_len - BINLOG_CHECKSUM_LEN : event_len,
+ mi->rli.relay_log.description_event_for_queue);
+
+ if (unlikely(mi->gtid_reconnect_event_skip_count) &&
+ unlikely(!mi->gtid_event_seen) &&
+ rev.is_artificial_event() &&
+ (mi->prev_master_id != mi->master_id ||
+ strcmp(rev.new_log_ident, mi->master_log_name) != 0))
+ {
+ /*
+ Artificial Rotate_log_event is the first event we receive at the start
+ of each master binlog file. It gives the name of the new binlog file.
+
+ Normally, we already have this name from the real rotate event at the
+ end of the previous binlog file (unless we are making a new connection
+ using GTID). But if the master server restarted/crashed, there is no
+ rotate event at the end of the prior binlog file, so the name is new.
+
+ We use this fact to handle a special case of master crashing. If the
+ master crashed while writing the binlog, it might end with a partial
+ event group lacking the COMMIT/XID event, which must be rolled
+ back. If the slave IO thread happens to get a disconnect in the middle
+ of exactly this event group, it will try to reconnect at the same GTID
+ and skip already fetched events. However, that GTID did not commit on
+ the master before the crash, so it does not really exist, and the
+ master will connect the slave at the next following GTID starting in
+ the next binlog. This could confuse the slave and make it mix the
+ start of one event group with the end of another.
+
+ But we detect this case here, by noticing the change of binlog name
+ which detects the missing rotate event at the end of the previous
+ binlog file. In this case, we reset the counters to make us not skip
+ the next event group, and queue an artificial Format Description
+ event. The previously fetched incomplete event group will then be
+ rolled back when the Format Description event is executed by the SQL
+ thread.
+
+ A similar case is if the reconnect somehow connects to a different
+ master server (like due to a network proxy or IP address takeover).
+ We detect this case by noticing a change of server_id and in this
+ case likewise rollback the partially received event group.
+ */
+ Format_description_log_event fdle(4);
+
+ if (mi->prev_master_id != mi->master_id)
+ sql_print_warning("The server_id of master server changed in the "
+ "middle of GTID %u-%u-%llu. Assuming a change of "
+ "master server, so rolling back the previously "
+ "received partial transaction. Expected: %lu, "
+ "received: %lu", mi->last_queued_gtid.domain_id,
+ mi->last_queued_gtid.server_id,
+ mi->last_queued_gtid.seq_no,
+ mi->prev_master_id, mi->master_id);
+ else if (strcmp(rev.new_log_ident, mi->master_log_name) != 0)
+ sql_print_warning("Unexpected change of master binlog file name in the "
+ "middle of GTID %u-%u-%llu, assuming that master has "
+ "crashed and rolling back the transaction. Expected: "
+ "'%s', received: '%s'",
+ mi->last_queued_gtid.domain_id,
+ mi->last_queued_gtid.server_id,
+ mi->last_queued_gtid.seq_no,
+ mi->master_log_name, rev.new_log_ident);
+
+ mysql_mutex_lock(log_lock);
+ if (likely(!fdle.write(rli->relay_log.get_log_file()) &&
+ !rli->relay_log.flush_and_sync(NULL)))
+ {
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ }
+ else
+ {
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ mysql_mutex_unlock(log_lock);
+ goto err;
+ }
+ rli->relay_log.signal_update();
+ mysql_mutex_unlock(log_lock);
+
+ mi->gtid_reconnect_event_skip_count= 0;
+ mi->events_queued_since_last_gtid= 0;
+ }
+ mi->prev_master_id= mi->master_id;
+
+ if (unlikely(process_io_rotate(mi, &rev)))
+ {
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ goto err;
+ }
+ /*
+ Checksum special cases for the fake Rotate (R_f) event caused by the protocol
+ of events generation and serialization in RL where Rotate of master is
+ queued right next to FD of slave.
+ Since it's only FD that carries the alg desc of FD_s has to apply to R_m.
+ Two special rules apply only to the first R_f which comes in before any FD_m.
+ The 2nd R_f should be compatible with the FD_s that must have taken over
+ the last seen FD_m's (A).
+
+ RSC_1: If OM \and fake Rotate \and slave is configured to
+ to compute checksum for its first FD event for RL
+ the fake Rotate gets checksummed here.
+ */
+ if (uint4korr(&buf[0]) == 0 && checksum_alg == BINLOG_CHECKSUM_ALG_OFF &&
+ mi->rli.relay_log.relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_OFF)
+ {
+ ha_checksum rot_crc= my_checksum(0L, NULL, 0);
+ event_len += BINLOG_CHECKSUM_LEN;
+ memcpy(rot_buf, buf, event_len - BINLOG_CHECKSUM_LEN);
+ int4store(&rot_buf[EVENT_LEN_OFFSET],
+ uint4korr(&rot_buf[EVENT_LEN_OFFSET]) + BINLOG_CHECKSUM_LEN);
+ rot_crc= my_checksum(rot_crc, (const uchar *) rot_buf,
+ event_len - BINLOG_CHECKSUM_LEN);
+ int4store(&rot_buf[event_len - BINLOG_CHECKSUM_LEN], rot_crc);
+ DBUG_ASSERT(event_len == uint4korr(&rot_buf[EVENT_LEN_OFFSET]));
+ DBUG_ASSERT(mi->rli.relay_log.description_event_for_queue->checksum_alg ==
+ mi->rli.relay_log.relay_log_checksum_alg);
+ /* the first one */
+ DBUG_ASSERT(mi->checksum_alg_before_fd != BINLOG_CHECKSUM_ALG_UNDEF);
+ save_buf= (char *) buf;
+ buf= rot_buf;
+ }
+ else
+ /*
+ RSC_2: If NM \and fake Rotate \and slave does not compute checksum
+ the fake Rotate's checksum is stripped off before relay-logging.
+ */
+ if (uint4korr(&buf[0]) == 0 && checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ mi->rli.relay_log.relay_log_checksum_alg == BINLOG_CHECKSUM_ALG_OFF)
+ {
+ event_len -= BINLOG_CHECKSUM_LEN;
+ memcpy(rot_buf, buf, event_len);
+ int4store(&rot_buf[EVENT_LEN_OFFSET],
+ uint4korr(&rot_buf[EVENT_LEN_OFFSET]) - BINLOG_CHECKSUM_LEN);
+ DBUG_ASSERT(event_len == uint4korr(&rot_buf[EVENT_LEN_OFFSET]));
+ DBUG_ASSERT(mi->rli.relay_log.description_event_for_queue->checksum_alg ==
+ mi->rli.relay_log.relay_log_checksum_alg);
+ /* the first one */
+ DBUG_ASSERT(mi->checksum_alg_before_fd != BINLOG_CHECKSUM_ALG_UNDEF);
+ save_buf= (char *) buf;
+ buf= rot_buf;
+ }
+ /*
+ Now the I/O thread has just changed its mi->master_log_name, so
+ incrementing mi->master_log_pos is nonsense.
+ */
+ inc_pos= 0;
+ break;
+ }
+ case FORMAT_DESCRIPTION_EVENT:
+ {
+ /*
+ Create an event, and save it (when we rotate the relay log, we will have
+ to write this event again).
+ */
+ /*
+ We are the only thread which reads/writes description_event_for_queue.
+ The relay_log struct does not move (though some members of it can
+ change), so we needn't any lock (no rli->data_lock, no log lock).
+ */
+ Format_description_log_event* tmp;
+ const char* errmsg;
+ // mark it as undefined that is irrelevant anymore
+ mi->checksum_alg_before_fd= BINLOG_CHECKSUM_ALG_UNDEF;
+ if (!(tmp= (Format_description_log_event*)
+ Log_event::read_log_event(buf, event_len, &errmsg,
+ mi->rli.relay_log.description_event_for_queue,
+ 1)))
+ {
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ goto err;
+ }
+ delete mi->rli.relay_log.description_event_for_queue;
+ mi->rli.relay_log.description_event_for_queue= tmp;
+ if (tmp->checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF)
+ tmp->checksum_alg= BINLOG_CHECKSUM_ALG_OFF;
+
+ /* installing new value of checksum Alg for relay log */
+ mi->rli.relay_log.relay_log_checksum_alg= tmp->checksum_alg;
+
+ /*
+ Do not queue any format description event that we receive after a
+ reconnect where we are skipping over a partial event group received
+ before the reconnect.
+
+ (If we queued such an event, and it was the first format_description
+ event after master restart, the slave SQL thread would think that
+ the partial event group before it in the relay log was from a
+ previous master crash and should be rolled back).
+ */
+ if (unlikely(mi->gtid_reconnect_event_skip_count && !mi->gtid_event_seen))
+ gtid_skip_enqueue= true;
+
+ /*
+ Though this does some conversion to the slave's format, this will
+ preserve the master's binlog format version, and number of event types.
+ */
+ /*
+ If the event was not requested by the slave (the slave did not ask for
+ it), i.e. has end_log_pos=0, we do not increment mi->master_log_pos
+ */
+ inc_pos= uint4korr(buf+LOG_POS_OFFSET) ? event_len : 0;
+ DBUG_PRINT("info",("binlog format is now %d",
+ mi->rli.relay_log.description_event_for_queue->binlog_version));
+
+ }
+ break;
+
+ case HEARTBEAT_LOG_EVENT:
+ {
+ /*
+ HB (heartbeat) cannot come before RL (Relay)
+ */
+ char llbuf[22];
+ Heartbeat_log_event hb(buf,
+ mi->rli.relay_log.relay_log_checksum_alg
+ != BINLOG_CHECKSUM_ALG_OFF ?
+ event_len - BINLOG_CHECKSUM_LEN : event_len,
+ mi->rli.relay_log.description_event_for_queue);
+ if (!hb.is_valid())
+ {
+ error= ER_SLAVE_HEARTBEAT_FAILURE;
+ error_msg.append(STRING_WITH_LEN("inconsistent heartbeat event content;"));
+ error_msg.append(STRING_WITH_LEN("the event's data: log_file_name "));
+ error_msg.append(hb.get_log_ident(), (uint) strlen(hb.get_log_ident()));
+ error_msg.append(STRING_WITH_LEN(" log_pos "));
+ llstr(hb.log_pos, llbuf);
+ error_msg.append(llbuf, strlen(llbuf));
+ goto err;
+ }
+ mi->received_heartbeats++;
+ /*
+ compare local and event's versions of log_file, log_pos.
+
+ Heartbeat is sent only after an event corresponding to the corrdinates
+ the heartbeat carries.
+ Slave can not have a higher coordinate except in the only
+ special case when mi->master_log_name, master_log_pos have never
+ been updated by Rotate event i.e when slave does not have any history
+ with the master (and thereafter mi->master_log_pos is NULL).
+
+ Slave can have lower coordinates, if some event from master was omitted.
+
+ TODO: handling `when' for SHOW SLAVE STATUS' snds behind
+ */
+ if ((memcmp(mi->master_log_name, hb.get_log_ident(), hb.get_ident_len())
+ && mi->master_log_name != NULL)
+ || mi->master_log_pos > hb.log_pos)
+ {
+ /* missed events of heartbeat from the past */
+ error= ER_SLAVE_HEARTBEAT_FAILURE;
+ error_msg.append(STRING_WITH_LEN("heartbeat is not compatible with local info;"));
+ error_msg.append(STRING_WITH_LEN("the event's data: log_file_name "));
+ error_msg.append(hb.get_log_ident(), (uint) strlen(hb.get_log_ident()));
+ error_msg.append(STRING_WITH_LEN(" log_pos "));
+ llstr(hb.log_pos, llbuf);
+ error_msg.append(llbuf, strlen(llbuf));
+ goto err;
+ }
+ goto skip_relay_logging;
+ }
+ break;
+
+ case GTID_LIST_EVENT:
+ {
+ const char *errmsg;
+ Gtid_list_log_event *glev;
+ Log_event *tmp;
+ uint32 flags;
+
+ if (!(tmp= Log_event::read_log_event(buf, event_len, &errmsg,
+ mi->rli.relay_log.description_event_for_queue,
+ opt_slave_sql_verify_checksum)))
+ {
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ goto err;
+ }
+ glev= static_cast<Gtid_list_log_event *>(tmp);
+ event_pos= glev->log_pos;
+ flags= glev->gl_flags;
+ delete glev;
+
+ /*
+ We use fake Gtid_list events to update the old-style position (among
+ other things).
+
+ Early code created fake Gtid_list events with zero log_pos, those should
+ not modify old-style position.
+ */
+ if (event_pos == 0 || event_pos <= mi->master_log_pos)
+ inc_pos= 0;
+ else
+ inc_pos= event_pos - mi->master_log_pos;
+
+ if (mi->rli.until_condition == Relay_log_info::UNTIL_GTID &&
+ flags & Gtid_list_log_event::FLAG_UNTIL_REACHED)
+ {
+ char str_buf[128];
+ String str(str_buf, sizeof(str_buf), system_charset_info);
+ mi->rli.until_gtid_pos.to_string(&str);
+ sql_print_information("Slave I/O thread stops because it reached its"
+ " UNTIL master_gtid_pos %s", str.c_ptr_safe());
+ mi->abort_slave= true;
+ }
+ }
+ break;
+
+ case GTID_EVENT:
+ {
+ uchar gtid_flag;
+
+ if (Gtid_log_event::peek(buf, event_len, checksum_alg,
+ &event_gtid.domain_id, &event_gtid.server_id,
+ &event_gtid.seq_no, &gtid_flag,
+ rli->relay_log.description_event_for_queue))
+ {
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ goto err;
+ }
+ got_gtid_event= true;
+ if (mi->using_gtid == Master_info::USE_GTID_NO)
+ goto default_action;
+ if (unlikely(!mi->gtid_event_seen))
+ {
+ mi->gtid_event_seen= true;
+ if (mi->gtid_reconnect_event_skip_count)
+ {
+ /*
+ If we are reconnecting, and we need to skip a partial event group
+ already queued to the relay log before the reconnect, then we check
+ that we actually get the same event group (same GTID) as before, so
+ we do not end up with half of one group and half another.
+
+ The only way we should be able to receive a different GTID than what
+ we expect is if the binlog on the master (or more likely the whole
+ master server) was replaced with a different one, on the same IP
+ address, _and_ the new master happens to have domains in a different
+ order so we get the GTID from a different domain first. Still, it is
+ best to protect against this case.
+ */
+ if (event_gtid.domain_id != mi->last_queued_gtid.domain_id ||
+ event_gtid.server_id != mi->last_queued_gtid.server_id ||
+ event_gtid.seq_no != mi->last_queued_gtid.seq_no)
+ {
+ bool first;
+ error= ER_SLAVE_UNEXPECTED_MASTER_SWITCH;
+ error_msg.append(STRING_WITH_LEN("Expected: "));
+ first= true;
+ rpl_slave_state_tostring_helper(&error_msg, &mi->last_queued_gtid,
+ &first);
+ error_msg.append(STRING_WITH_LEN(", received: "));
+ first= true;
+ rpl_slave_state_tostring_helper(&error_msg, &event_gtid, &first);
+ goto err;
+ }
+ }
+ }
+
+ if (unlikely(mi->gtid_reconnect_event_skip_count))
+ {
+ goto default_action;
+ }
+
+ /*
+ We have successfully queued to relay log everything before this GTID, so
+ in case of reconnect we can start from after any previous GTID.
+ (Normally we would have updated gtid_current_pos earlier at the end of
+ the previous event group, but better leave an extra check here for
+ safety).
+ */
+ if (mi->events_queued_since_last_gtid)
+ {
+ mi->gtid_current_pos.update(&mi->last_queued_gtid);
+ mi->events_queued_since_last_gtid= 0;
+ }
+ mi->last_queued_gtid= event_gtid;
+ mi->last_queued_gtid_standalone=
+ (gtid_flag & Gtid_log_event::FL_STANDALONE) != 0;
+ ++mi->events_queued_since_last_gtid;
+ inc_pos= event_len;
+ }
+ break;
+
+#ifndef DBUG_OFF
+ case XID_EVENT:
+ DBUG_EXECUTE_IF("slave_discard_xid_for_gtid_0_x_1000",
+ {
+ /* Inject an event group that is missing its XID commit event. */
+ if (mi->last_queued_gtid.domain_id == 0 &&
+ mi->last_queued_gtid.seq_no == 1000)
+ goto skip_relay_logging;
+ });
+ /* Fall through to default case ... */
+#endif
+
+ default:
+ default_action:
+ if (mi->using_gtid != Master_info::USE_GTID_NO && mi->gtid_event_seen)
+ {
+ if (unlikely(mi->gtid_reconnect_event_skip_count))
+ {
+ --mi->gtid_reconnect_event_skip_count;
+ gtid_skip_enqueue= true;
+ }
+ else if (mi->events_queued_since_last_gtid)
+ ++mi->events_queued_since_last_gtid;
+ }
+
+ inc_pos= event_len;
+ break;
+ }
+
+ /*
+ If we filter events master-side (eg. @@skip_replication), we will see holes
+ in the event positions from the master. If we see such a hole, adjust
+ mi->master_log_pos accordingly so we maintain the correct position (for
+ reconnect, MASTER_POS_WAIT(), etc.)
+ */
+ if (inc_pos > 0 &&
+ event_len >= LOG_POS_OFFSET+4 &&
+ (event_pos= uint4korr(buf+LOG_POS_OFFSET)) > mi->master_log_pos + inc_pos)
+ {
+ inc_pos= event_pos - mi->master_log_pos;
+ DBUG_PRINT("info", ("Adjust master_log_pos %llu->%llu to account for "
+ "master-side filtering",
+ mi->master_log_pos + inc_pos, event_pos));
+ }
+
+ /*
+ If this event is originating from this server, don't queue it.
+ We don't check this for 3.23 events because it's simpler like this; 3.23
+ will be filtered anyway by the SQL slave thread which also tests the
+ server id (we must also keep this test in the SQL thread, in case somebody
+ upgrades a 4.0 slave which has a not-filtered relay log).
+
+ ANY event coming from ourselves can be ignored: it is obvious for queries;
+ for STOP_EVENT/ROTATE_EVENT/START_EVENT: these cannot come from ourselves
+ (--log-slave-updates would not log that) unless this slave is also its
+ direct master (an unsupported, useless setup!).
+ */
+
+ mysql_mutex_lock(log_lock);
+ s_id= uint4korr(buf + SERVER_ID_OFFSET);
+ /*
+ Write the event to the relay log, unless we reconnected in the middle
+ of an event group and now need to skip the initial part of the group that
+ we already wrote before reconnecting.
+ */
+ if (unlikely(gtid_skip_enqueue))
+ {
+ mi->master_log_pos+= inc_pos;
+ if ((uchar)buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT &&
+ s_id == mi->master_id)
+ {
+ /*
+ If we write this master's description event in the middle of an event
+ group due to GTID reconnect, SQL thread will think that master crashed
+ in the middle of the group and roll back the first half, so we must not.
+
+ But we still have to write an artificial copy of the masters description
+ event, to override the initial slave-version description event so that
+ SQL thread has the right information for parsing the events it reads.
+ */
+ rli->relay_log.description_event_for_queue->created= 0;
+ rli->relay_log.description_event_for_queue->set_artificial_event();
+ if (rli->relay_log.append_no_lock
+ (rli->relay_log.description_event_for_queue))
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ else
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ }
+ else if (mi->gtid_reconnect_event_skip_count == 0)
+ {
+ /*
+ Add a fake rotate event so that SQL thread can see the old-style
+ position where we re-connected in the middle of a GTID event group.
+ */
+ Rotate_log_event fake_rev(mi->master_log_name, 0, mi->master_log_pos, 0);
+ fake_rev.server_id= mi->master_id;
+ if (rli->relay_log.append_no_lock(&fake_rev))
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ else
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ }
+ }
+ else
+ if ((s_id == global_system_variables.server_id &&
+ !mi->rli.replicate_same_server_id) ||
+ /*
+ the following conjunction deals with IGNORE_SERVER_IDS, if set
+ If the master is on the ignore list, execution of
+ format description log events and rotate events is necessary.
+ */
+ (mi->ignore_server_ids.elements > 0 &&
+ mi->shall_ignore_server_id(s_id) &&
+ /* everything is filtered out from non-master */
+ (s_id != mi->master_id ||
+ /* for the master meta information is necessary */
+ (buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT &&
+ buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT))))
+ {
+ /*
+ Do not write it to the relay log.
+ a) We still want to increment mi->master_log_pos, so that we won't
+ re-read this event from the master if the slave IO thread is now
+ stopped/restarted (more efficient if the events we are ignoring are big
+ LOAD DATA INFILE).
+ b) We want to record that we are skipping events, for the information of
+ the slave SQL thread, otherwise that thread may let
+ rli->group_relay_log_pos stay too small if the last binlog's event is
+ ignored.
+ But events which were generated by this slave and which do not exist in
+ the master's binlog (i.e. Format_desc, Rotate & Stop) should not increment
+ mi->master_log_pos.
+ If the event is originated remotely and is being filtered out by
+ IGNORE_SERVER_IDS it increments mi->master_log_pos
+ as well as rli->group_relay_log_pos.
+ */
+ if (!(s_id == global_system_variables.server_id &&
+ !mi->rli.replicate_same_server_id) ||
+ (buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT &&
+ buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT &&
+ buf[EVENT_TYPE_OFFSET] != STOP_EVENT))
+ {
+ mi->master_log_pos+= inc_pos;
+ memcpy(rli->ign_master_log_name_end, mi->master_log_name, FN_REFLEN);
+ DBUG_ASSERT(rli->ign_master_log_name_end[0]);
+ rli->ign_master_log_pos_end= mi->master_log_pos;
+ if (got_gtid_event)
+ rli->ign_gtids.update(&event_gtid);
+ }
+ rli->relay_log.signal_update(); // the slave SQL thread needs to re-check
+ DBUG_PRINT("info", ("master_log_pos: %lu, event originating from %u server, ignored",
+ (ulong) mi->master_log_pos, uint4korr(buf + SERVER_ID_OFFSET)));
+ }
+ else
+ {
+ if (likely(!(rli->relay_log.appendv(buf,event_len,0))))
+ {
+ mi->master_log_pos+= inc_pos;
+ DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ }
+ else
+ {
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ }
+ rli->ign_master_log_name_end[0]= 0; // last event is not ignored
+ if (got_gtid_event)
+ rli->ign_gtids.remove_if_present(&event_gtid);
+ if (save_buf != NULL)
+ buf= save_buf;
+ }
+ mysql_mutex_unlock(log_lock);
+
+ if (!error &&
+ mi->using_gtid != Master_info::USE_GTID_NO &&
+ mi->events_queued_since_last_gtid > 0 &&
+ ( (mi->last_queued_gtid_standalone &&
+ !Log_event::is_part_of_group((Log_event_type)(uchar)
+ buf[EVENT_TYPE_OFFSET])) ||
+ (!mi->last_queued_gtid_standalone &&
+ ((uchar)buf[EVENT_TYPE_OFFSET] == XID_EVENT ||
+ ((uchar)buf[EVENT_TYPE_OFFSET] == QUERY_EVENT &&
+ Query_log_event::peek_is_commit_rollback(buf, event_len,
+ checksum_alg))))))
+ {
+ /*
+ The whole of the current event group is queued. So in case of
+ reconnect we can start from after the current GTID.
+ */
+ mi->gtid_current_pos.update(&mi->last_queued_gtid);
+ mi->events_queued_since_last_gtid= 0;
+ }
+
+skip_relay_logging:
+
+err:
+ if (unlock_data_lock)
+ mysql_mutex_unlock(&mi->data_lock);
+ DBUG_PRINT("info", ("error: %d", error));
+ if (error)
+ mi->report(ERROR_LEVEL, error, NULL, ER(error),
+ (error == ER_SLAVE_RELAY_LOG_WRITE_FAILURE)?
+ "could not queue event from master" :
+ error_msg.ptr());
+ DBUG_RETURN(error);
+}
+
+
+void end_relay_log_info(Relay_log_info* rli)
+{
+ DBUG_ENTER("end_relay_log_info");
+
+ if (!rli->inited)
+ DBUG_VOID_RETURN;
+ if (rli->info_fd >= 0)
+ {
+ end_io_cache(&rli->info_file);
+ mysql_file_close(rli->info_fd, MYF(MY_WME));
+ rli->info_fd = -1;
+ }
+ if (rli->cur_log_fd >= 0)
+ {
+ end_io_cache(&rli->cache_buf);
+ mysql_file_close(rli->cur_log_fd, MYF(MY_WME));
+ rli->cur_log_fd = -1;
+ }
+ rli->inited = 0;
+ rli->relay_log.close(LOG_CLOSE_INDEX | LOG_CLOSE_STOP_EVENT);
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+ /*
+ Delete the slave's temporary tables from memory.
+ In the future there will be other actions than this, to ensure persistance
+ of slave's temp tables after shutdown.
+ */
+ rli->close_temporary_tables();
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Hook to detach the active VIO before closing a connection handle.
+
+ The client API might close the connection (and associated data)
+ in case it encounters a unrecoverable (network) error. This hook
+ is called from the client code before the VIO handle is deleted
+ allows the thread to detach the active vio so it does not point
+ to freed memory.
+
+ Other calls to THD::clear_active_vio throughout this module are
+ redundant due to the hook but are left in place for illustrative
+ purposes.
+*/
+
+extern "C" void slave_io_thread_detach_vio()
+{
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ THD *thd= current_thd;
+ if (thd && thd->slave_thread)
+ thd->clear_active_vio();
+#endif
+}
+
+
+/*
+ Try to connect until successful or slave killed
+
+ SYNPOSIS
+ safe_connect()
+ thd Thread handler for slave
+ mysql MySQL connection handle
+ mi Replication handle
+
+ RETURN
+ 0 ok
+ # Error
+*/
+
+static int safe_connect(THD* thd, MYSQL* mysql, Master_info* mi)
+{
+ DBUG_ENTER("safe_connect");
+
+ DBUG_RETURN(connect_to_master(thd, mysql, mi, 0, 0));
+}
+
+
+/*
+ SYNPOSIS
+ connect_to_master()
+
+ IMPLEMENTATION
+ Try to connect until successful or slave killed or we have retried
+ master_retry_count times
+*/
+
+static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi,
+ bool reconnect, bool suppress_warnings)
+{
+ int slave_was_killed;
+ int last_errno= -2; // impossible error
+ ulong err_count=0;
+ char llbuff[22];
+ my_bool my_true= 1;
+ DBUG_ENTER("connect_to_master");
+ set_slave_max_allowed_packet(thd, mysql);
+#ifndef DBUG_OFF
+ mi->events_till_disconnect = disconnect_slave_event_count;
+#endif
+ ulong client_flag= 0;
+ if (opt_slave_compressed_protocol)
+ client_flag=CLIENT_COMPRESS; /* We will use compression */
+
+ mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *) &slave_net_timeout);
+ mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, (char *) &slave_net_timeout);
+ mysql_options(mysql, MYSQL_OPT_USE_THREAD_SPECIFIC_MEMORY,
+ (char*) &my_true);
+
+#ifdef HAVE_OPENSSL
+ if (mi->ssl)
+ {
+ mysql_ssl_set(mysql,
+ mi->ssl_key[0]?mi->ssl_key:0,
+ mi->ssl_cert[0]?mi->ssl_cert:0,
+ mi->ssl_ca[0]?mi->ssl_ca:0,
+ mi->ssl_capath[0]?mi->ssl_capath:0,
+ mi->ssl_cipher[0]?mi->ssl_cipher:0);
+ mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
+ &mi->ssl_verify_server_cert);
+ mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH,
+ mi->ssl_crlpath[0] ? mi->ssl_crlpath : 0);
+ mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
+ &mi->ssl_verify_server_cert);
+ }
+#endif
+
+ /*
+ If server's default charset is not supported (like utf16, utf32) as client
+ charset, then set client charset to 'latin1' (default client charset).
+ */
+ if (is_supported_parser_charset(default_charset_info))
+ mysql_options(mysql, MYSQL_SET_CHARSET_NAME, default_charset_info->csname);
+ else
+ {
+ sql_print_information("'%s' can not be used as client character set. "
+ "'%s' will be used as default client character set "
+ "while connecting to master.",
+ default_charset_info->csname,
+ default_client_charset_info->csname);
+ mysql_options(mysql, MYSQL_SET_CHARSET_NAME,
+ default_client_charset_info->csname);
+ }
+
+ /* This one is not strictly needed but we have it here for completeness */
+ mysql_options(mysql, MYSQL_SET_CHARSET_DIR, (char *) charsets_dir);
+
+ /* Set MYSQL_PLUGIN_DIR in case master asks for an external authentication plugin */
+ if (opt_plugin_dir_ptr && *opt_plugin_dir_ptr)
+ mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir_ptr);
+
+ /* we disallow empty users */
+ if (mi->user == NULL || mi->user[0] == 0)
+ {
+ mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
+ ER(ER_SLAVE_FATAL_ERROR),
+ "Invalid (empty) username when attempting to "
+ "connect to the master server. Connection attempt "
+ "terminated.");
+ DBUG_RETURN(1);
+ }
+ while (!(slave_was_killed = io_slave_killed(mi)) &&
+ (reconnect ? mysql_reconnect(mysql) != 0 :
+ mysql_real_connect(mysql, mi->host, mi->user, mi->password, 0,
+ mi->port, 0, client_flag) == 0))
+ {
+ /* Don't repeat last error */
+ if ((int)mysql_errno(mysql) != last_errno)
+ {
+ last_errno=mysql_errno(mysql);
+ suppress_warnings= 0;
+ mi->report(ERROR_LEVEL, last_errno, NULL,
+ "error %s to master '%s@%s:%d'"
+ " - retry-time: %d retries: %lu message: %s",
+ (reconnect ? "reconnecting" : "connecting"),
+ mi->user, mi->host, mi->port,
+ mi->connect_retry, master_retry_count,
+ mysql_error(mysql));
+ }
+ /*
+ By default we try forever. The reason is that failure will trigger
+ master election, so if the user did not set master_retry_count we
+ do not want to have election triggered on the first failure to
+ connect
+ */
+ if (++err_count == master_retry_count)
+ {
+ slave_was_killed=1;
+ if (reconnect)
+ change_rpl_status(RPL_ACTIVE_SLAVE,RPL_LOST_SOLDIER);
+ break;
+ }
+ slave_sleep(thd,mi->connect_retry,io_slave_killed, mi);
+ }
+
+ if (!slave_was_killed)
+ {
+ mi->clear_error(); // clear possible left over reconnect error
+ if (reconnect)
+ {
+ if (!suppress_warnings && global_system_variables.log_warnings)
+ sql_print_information("Slave: connected to master '%s@%s:%d',\
+replication resumed in log '%s' at position %s", mi->user,
+ mi->host, mi->port,
+ IO_RPL_LOG_NAME,
+ llstr(mi->master_log_pos,llbuff));
+ }
+ else
+ {
+ change_rpl_status(RPL_IDLE_SLAVE,RPL_ACTIVE_SLAVE);
+ general_log_print(thd, COM_CONNECT_OUT, "%s@%s:%d",
+ mi->user, mi->host, mi->port);
+ }
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ thd->set_active_vio(mysql->net.vio);
+#endif
+ }
+ mysql->reconnect= 1;
+ DBUG_PRINT("exit",("slave_was_killed: %d", slave_was_killed));
+ DBUG_RETURN(slave_was_killed);
+}
+
+
+/*
+ safe_reconnect()
+
+ IMPLEMENTATION
+ Try to connect until successful or slave killed or we have retried
+ master_retry_count times
+*/
+
+static int safe_reconnect(THD* thd, MYSQL* mysql, Master_info* mi,
+ bool suppress_warnings)
+{
+ DBUG_ENTER("safe_reconnect");
+ DBUG_RETURN(connect_to_master(thd, mysql, mi, 1, suppress_warnings));
+}
+
+
+#ifdef NOT_USED
+MYSQL *rpl_connect_master(MYSQL *mysql)
+{
+ Master_info *mi= my_pthread_getspecific_ptr(Master_info*, RPL_MASTER_INFO);
+ bool allocated= false;
+ my_bool my_true= 1;
+ THD *thd;
+
+ if (!mi)
+ {
+ sql_print_error("'rpl_connect_master' must be called in slave I/O thread context.");
+ return NULL;
+ }
+ thd= mi->io_thd;
+ if (!mysql)
+ {
+ if(!(mysql= mysql_init(NULL)))
+ {
+ sql_print_error("rpl_connect_master: failed in mysql_init()");
+ return NULL;
+ }
+ allocated= true;
+ }
+
+ /*
+ XXX: copied from connect_to_master, this function should not
+ change the slave status, so we cannot use connect_to_master
+ directly
+
+ TODO: make this part a seperate function to eliminate duplication
+ */
+ mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *) &slave_net_timeout);
+ mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, (char *) &slave_net_timeout);
+ mysql_options(mysql, MYSQL_OPT_USE_THREAD_SPECIFIC_MEMORY,
+ (char*) &my_true);
+
+#ifdef HAVE_OPENSSL
+ if (mi->ssl)
+ {
+ mysql_ssl_set(mysql,
+ mi->ssl_key[0]?mi->ssl_key:0,
+ mi->ssl_cert[0]?mi->ssl_cert:0,
+ mi->ssl_ca[0]?mi->ssl_ca:0,
+ mi->ssl_capath[0]?mi->ssl_capath:0,
+ mi->ssl_cipher[0]?mi->ssl_cipher:0);
+ mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
+ &mi->ssl_verify_server_cert);
+ }
+#endif
+
+ mysql_options(mysql, MYSQL_SET_CHARSET_NAME, default_charset_info->csname);
+ /* This one is not strictly needed but we have it here for completeness */
+ mysql_options(mysql, MYSQL_SET_CHARSET_DIR, (char *) charsets_dir);
+
+ if (mi->user == NULL
+ || mi->user[0] == 0
+ || io_slave_killed( mi)
+ || !mysql_real_connect(mysql, mi->host, mi->user, mi->password, 0,
+ mi->port, 0, 0))
+ {
+ if (!io_slave_killed( mi))
+ sql_print_error("rpl_connect_master: error connecting to master: %s (server_error: %d)",
+ mysql_error(mysql), mysql_errno(mysql));
+
+ if (allocated)
+ mysql_close(mysql); // this will free the object
+ return NULL;
+ }
+ return mysql;
+}
+#endif
+
+/*
+ Store the file and position where the execute-slave thread are in the
+ relay log.
+
+ SYNOPSIS
+ flush_relay_log_info()
+ rli Relay log information
+
+ NOTES
+ - As this is only called by the slave thread or on STOP SLAVE, with the
+ log_lock grabbed and the slave thread stopped, we don't need to have
+ a lock here.
+ - If there is an active transaction, then we don't update the position
+ in the relay log. This is to ensure that we re-execute statements
+ if we die in the middle of an transaction that was rolled back.
+ - As a transaction never spans binary logs, we don't have to handle the
+ case where we do a relay-log-rotation in the middle of the transaction.
+ If this would not be the case, we would have to ensure that we
+ don't delete the relay log file where the transaction started when
+ we switch to a new relay log file.
+
+ TODO
+ - Change the log file information to a binary format to avoid calling
+ longlong2str.
+
+ RETURN VALUES
+ 0 ok
+ 1 write error
+*/
+
+bool flush_relay_log_info(Relay_log_info* rli)
+{
+ bool error=0;
+ DBUG_ENTER("flush_relay_log_info");
+
+ if (unlikely(rli->no_storage))
+ DBUG_RETURN(0);
+
+ IO_CACHE *file = &rli->info_file;
+ char buff[FN_REFLEN*2+22*2+4], *pos;
+
+ my_b_seek(file, 0L);
+ pos=strmov(buff, rli->group_relay_log_name);
+ *pos++='\n';
+ pos= longlong10_to_str(rli->group_relay_log_pos, pos, 10);
+ *pos++='\n';
+ pos=strmov(pos, rli->group_master_log_name);
+ *pos++='\n';
+ pos=longlong10_to_str(rli->group_master_log_pos, pos, 10);
+ *pos='\n';
+ if (my_b_write(file, (uchar*) buff, (size_t) (pos-buff)+1))
+ error=1;
+ if (flush_io_cache(file))
+ error=1;
+ if (sync_relayloginfo_period &&
+ !error &&
+ ++(rli->sync_counter) >= sync_relayloginfo_period)
+ {
+ if (my_sync(rli->info_fd, MYF(MY_WME)))
+ error=1;
+ rli->sync_counter= 0;
+ }
+ /*
+ Flushing the relay log is done by the slave I/O thread
+ or by the user on STOP SLAVE.
+ */
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Called when we notice that the current "hot" log got rotated under our feet.
+*/
+
+static IO_CACHE *reopen_relay_log(Relay_log_info *rli, const char **errmsg)
+{
+ DBUG_ENTER("reopen_relay_log");
+ DBUG_ASSERT(rli->cur_log != &rli->cache_buf);
+ DBUG_ASSERT(rli->cur_log_fd == -1);
+
+ IO_CACHE *cur_log = rli->cur_log=&rli->cache_buf;
+ if ((rli->cur_log_fd=open_binlog(cur_log,rli->event_relay_log_name,
+ errmsg)) <0)
+ DBUG_RETURN(0);
+ /*
+ We want to start exactly where we was before:
+ relay_log_pos Current log pos
+ pending Number of bytes already processed from the event
+ */
+ rli->event_relay_log_pos= MY_MAX(rli->event_relay_log_pos, BIN_LOG_HEADER_SIZE);
+ my_b_seek(cur_log,rli->event_relay_log_pos);
+ DBUG_RETURN(cur_log);
+}
+
+
+/**
+ Reads next event from the relay log. Should be called from the
+ slave IO thread.
+
+ @param rli Relay_log_info structure for the slave IO thread.
+
+ @return The event read, or NULL on error. If an error occurs, the
+ error is reported through the sql_print_information() or
+ sql_print_error() functions.
+
+ The size of the read event (in bytes) is returned in *event_size.
+*/
+static Log_event* next_event(rpl_group_info *rgi, ulonglong *event_size)
+{
+ Log_event* ev;
+ Relay_log_info *rli= rgi->rli;
+ IO_CACHE* cur_log = rli->cur_log;
+ mysql_mutex_t *log_lock = rli->relay_log.get_log_lock();
+ const char* errmsg=0;
+ DBUG_ENTER("next_event");
+
+ DBUG_ASSERT(rgi->thd != 0 && rgi->thd == rli->sql_driver_thd);
+ *event_size= 0;
+
+#ifndef DBUG_OFF
+ if (abort_slave_event_count && !rli->events_till_abort--)
+ DBUG_RETURN(0);
+#endif
+
+ /*
+ For most operations we need to protect rli members with data_lock,
+ so we assume calling function acquired this mutex for us and we will
+ hold it for the most of the loop below However, we will release it
+ whenever it is worth the hassle, and in the cases when we go into a
+ mysql_cond_wait() with the non-data_lock mutex
+ */
+ mysql_mutex_assert_owner(&rli->data_lock);
+
+ while (!sql_slave_killed(rgi))
+ {
+ /*
+ We can have two kinds of log reading:
+ hot_log:
+ rli->cur_log points at the IO_CACHE of relay_log, which
+ is actively being updated by the I/O thread. We need to be careful
+ in this case and make sure that we are not looking at a stale log that
+ has already been rotated. If it has been, we reopen the log.
+
+ The other case is much simpler:
+ We just have a read only log that nobody else will be updating.
+ */
+ ulonglong old_pos;
+ bool hot_log;
+ if ((hot_log = (cur_log != &rli->cache_buf)))
+ {
+ DBUG_ASSERT(rli->cur_log_fd == -1); // foreign descriptor
+ mysql_mutex_lock(log_lock);
+
+ /*
+ Reading xxx_file_id is safe because the log will only
+ be rotated when we hold relay_log.LOCK_log
+ */
+ if (rli->relay_log.get_open_count() != rli->cur_log_old_open_count)
+ {
+ // The master has switched to a new log file; Reopen the old log file
+ cur_log=reopen_relay_log(rli, &errmsg);
+ mysql_mutex_unlock(log_lock);
+ if (!cur_log) // No more log files
+ goto err;
+ hot_log=0; // Using old binary log
+ }
+ }
+ /*
+ As there is no guarantee that the relay is open (for example, an I/O
+ error during a write by the slave I/O thread may have closed it), we
+ have to test it.
+ */
+ if (!my_b_inited(cur_log))
+ goto err;
+#ifndef DBUG_OFF
+ {
+ /* This is an assertion which sometimes fails, let's try to track it */
+ char llbuf1[22], llbuf2[22];
+ DBUG_PRINT("info", ("my_b_tell(cur_log)=%s rli->event_relay_log_pos=%s",
+ llstr(my_b_tell(cur_log),llbuf1),
+ llstr(rli->event_relay_log_pos,llbuf2)));
+ DBUG_ASSERT(my_b_tell(cur_log) >= BIN_LOG_HEADER_SIZE);
+ DBUG_ASSERT(opt_slave_parallel_threads > 0 ||
+ my_b_tell(cur_log) == rli->event_relay_log_pos);
+ }
+#endif
+ /*
+ Relay log is always in new format - if the master is 3.23, the
+ I/O thread will convert the format for us.
+ A problem: the description event may be in a previous relay log. So if
+ the slave has been shutdown meanwhile, we would have to look in old relay
+ logs, which may even have been deleted. So we need to write this
+ description event at the beginning of the relay log.
+ When the relay log is created when the I/O thread starts, easy: the
+ master will send the description event and we will queue it.
+ But if the relay log is created by new_file(): then the solution is:
+ MYSQL_BIN_LOG::open() will write the buffered description event.
+ */
+ old_pos= rli->event_relay_log_pos;
+ if ((ev= Log_event::read_log_event(cur_log,0,
+ rli->relay_log.description_event_for_exec,
+ opt_slave_sql_verify_checksum)))
+
+ {
+ /*
+ read it while we have a lock, to avoid a mutex lock in
+ inc_event_relay_log_pos()
+ */
+ rli->future_event_relay_log_pos= my_b_tell(cur_log);
+ *event_size= rli->future_event_relay_log_pos - old_pos;
+
+ if (hot_log)
+ mysql_mutex_unlock(log_lock);
+ rli->sql_thread_caught_up= false;
+ DBUG_RETURN(ev);
+ }
+ if (opt_reckless_slave) // For mysql-test
+ cur_log->error = 0;
+ if (cur_log->error < 0)
+ {
+ errmsg = "slave SQL thread aborted because of I/O error";
+ if (hot_log)
+ mysql_mutex_unlock(log_lock);
+ goto err;
+ }
+ if (!cur_log->error) /* EOF */
+ {
+ /*
+ On a hot log, EOF means that there are no more updates to
+ process and we must block until I/O thread adds some and
+ signals us to continue
+ */
+ if (hot_log)
+ {
+ /*
+ We say in Seconds_Behind_Master that we have "caught up". Note that
+ for example if network link is broken but I/O slave thread hasn't
+ noticed it (slave_net_timeout not elapsed), then we'll say "caught
+ up" whereas we're not really caught up. Fixing that would require
+ internally cutting timeout in smaller pieces in network read, no
+ thanks. Another example: SQL has caught up on I/O, now I/O has read
+ a new event and is queuing it; the false "0" will exist until SQL
+ finishes executing the new event; it will be look abnormal only if
+ the events have old timestamps (then you get "many", 0, "many").
+
+ Transient phases like this can be fixed with implemeting
+ Heartbeat event which provides the slave the status of the
+ master at time the master does not have any new update to send.
+ Seconds_Behind_Master would be zero only when master has no
+ more updates in binlog for slave. The heartbeat can be sent
+ in a (small) fraction of slave_net_timeout. Until it's done
+ rli->sql_thread_caught_up is temporarely (for time of waiting for
+ the following event) set whenever EOF is reached.
+ */
+ rli->sql_thread_caught_up= true;
+
+ DBUG_ASSERT(rli->relay_log.get_open_count() ==
+ rli->cur_log_old_open_count);
+
+ if (rli->ign_master_log_name_end[0])
+ {
+ /* We generate and return a Rotate, to make our positions advance */
+ DBUG_PRINT("info",("seeing an ignored end segment"));
+ ev= new Rotate_log_event(rli->ign_master_log_name_end,
+ 0, rli->ign_master_log_pos_end,
+ Rotate_log_event::DUP_NAME);
+ rli->ign_master_log_name_end[0]= 0;
+ mysql_mutex_unlock(log_lock);
+ if (unlikely(!ev))
+ {
+ errmsg= "Slave SQL thread failed to create a Rotate event "
+ "(out of memory?), SHOW SLAVE STATUS may be inaccurate";
+ goto err;
+ }
+ ev->server_id= 0; // don't be ignored by slave SQL thread
+ DBUG_RETURN(ev);
+ }
+
+ if (rli->ign_gtids.count())
+ {
+ /* We generate and return a Gtid_list, to update gtid_slave_pos. */
+ DBUG_PRINT("info",("seeing ignored end gtids"));
+ ev= new Gtid_list_log_event(&rli->ign_gtids,
+ Gtid_list_log_event::FLAG_IGN_GTIDS);
+ rli->ign_gtids.reset();
+ mysql_mutex_unlock(log_lock);
+ if (unlikely(!ev))
+ {
+ errmsg= "Slave SQL thread failed to create a Gtid_list event "
+ "(out of memory?), gtid_slave_pos may be inaccurate";
+ goto err;
+ }
+ ev->server_id= 0; // don't be ignored by slave SQL thread
+ ev->set_artificial_event(); // Don't mess up Exec_Master_Log_Pos
+ DBUG_RETURN(ev);
+ }
+
+ /*
+ We have to check sql_slave_killed() here an extra time.
+ Otherwise we may miss a wakeup, since last check was done
+ without holding LOCK_log.
+ */
+ if (sql_slave_killed(rgi))
+ {
+ mysql_mutex_unlock(log_lock);
+ break;
+ }
+
+ /*
+ We can, and should release data_lock while we are waiting for
+ update. If we do not, show slave status will block
+ */
+ mysql_mutex_unlock(&rli->data_lock);
+
+ /*
+ Possible deadlock :
+ - the I/O thread has reached log_space_limit
+ - the SQL thread has read all relay logs, but cannot purge for some
+ reason:
+ * it has already purged all logs except the current one
+ * there are other logs than the current one but they're involved in
+ a transaction that finishes in the current one (or is not finished)
+ Solution :
+ Wake up the possibly waiting I/O thread, and set a boolean asking
+ the I/O thread to temporarily ignore the log_space_limit
+ constraint, because we do not want the I/O thread to block because of
+ space (it's ok if it blocks for any other reason (e.g. because the
+ master does not send anything). Then the I/O thread stops waiting
+ and reads one more event and starts honoring log_space_limit again.
+
+ If the SQL thread needs more events to be able to rotate the log (it
+ might need to finish the current group first), then it can ask for
+ one more at a time. Thus we don't outgrow the relay log indefinitely,
+ but rather in a controlled manner, until the next rotate.
+
+ When the SQL thread starts it sets ignore_log_space_limit to false.
+ We should also reset ignore_log_space_limit to 0 when the user does
+ RESET SLAVE, but in fact, no need as RESET SLAVE requires that the
+ slave be stopped, and the SQL thread sets ignore_log_space_limit
+ to 0 when
+ it stops.
+ */
+ mysql_mutex_lock(&rli->log_space_lock);
+
+ /*
+ If we have reached the limit of the relay space and we
+ are going to sleep, waiting for more events:
+
+ 1. If outside a group, SQL thread asks the IO thread
+ to force a rotation so that the SQL thread purges
+ logs next time it processes an event (thus space is
+ freed).
+
+ 2. If in a group, SQL thread asks the IO thread to
+ ignore the limit and queues yet one more event
+ so that the SQL thread finishes the group and
+ is are able to rotate and purge sometime soon.
+ */
+ if (rli->log_space_limit &&
+ rli->log_space_limit < rli->log_space_total)
+ {
+ /* force rotation if not in an unfinished group */
+ rli->sql_force_rotate_relay= !rli->is_in_group();
+
+ /* ask for one more event */
+ rli->ignore_log_space_limit= true;
+ }
+
+ /*
+ If the I/O thread is blocked, unblock it. Ok to broadcast
+ after unlock, because the mutex is only destroyed in
+ ~Relay_log_info(), i.e. when rli is destroyed, and rli will
+ not be destroyed before we exit the present function.
+ */
+ mysql_mutex_unlock(&rli->log_space_lock);
+ mysql_cond_broadcast(&rli->log_space_cond);
+ // Note that wait_for_update_relay_log unlocks lock_log !
+ rli->relay_log.wait_for_update_relay_log(rli->sql_driver_thd);
+ // re-acquire data lock since we released it earlier
+ mysql_mutex_lock(&rli->data_lock);
+ rli->sql_thread_caught_up= false;
+ continue;
+ }
+ /*
+ If the log was not hot, we need to move to the next log in
+ sequence. The next log could be hot or cold, we deal with both
+ cases separately after doing some common initialization
+ */
+ end_io_cache(cur_log);
+ DBUG_ASSERT(rli->cur_log_fd >= 0);
+ mysql_file_close(rli->cur_log_fd, MYF(MY_WME));
+ rli->cur_log_fd = -1;
+ rli->last_inuse_relaylog->completed= true;
+
+ if (relay_log_purge)
+ {
+ /*
+ purge_first_log will properly set up relay log coordinates in rli.
+ If the group's coordinates are equal to the event's coordinates
+ (i.e. the relay log was not rotated in the middle of a group),
+ we can purge this relay log too.
+ We do ulonglong and string comparisons, this may be slow but
+ - purging the last relay log is nice (it can save 1GB of disk), so we
+ like to detect the case where we can do it, and given this,
+ - I see no better detection method
+ - purge_first_log is not called that often
+ */
+ if (rli->relay_log.purge_first_log
+ (rli,
+ rli->group_relay_log_pos == rli->event_relay_log_pos
+ && !strcmp(rli->group_relay_log_name,rli->event_relay_log_name)))
+ {
+ errmsg = "Error purging processed logs";
+ goto err;
+ }
+ }
+ else
+ {
+ /*
+ If hot_log is set, then we already have a lock on
+ LOCK_log. If not, we have to get the lock.
+
+ According to Sasha, the only time this code will ever be executed
+ is if we are recovering from a bug.
+ */
+ if (rli->relay_log.find_next_log(&rli->linfo, !hot_log))
+ {
+ errmsg = "error switching to the next log";
+ goto err;
+ }
+ rli->event_relay_log_pos = BIN_LOG_HEADER_SIZE;
+ strmake_buf(rli->event_relay_log_name,rli->linfo.log_file_name);
+ flush_relay_log_info(rli);
+ }
+
+ /*
+ Now we want to open this next log. To know if it's a hot log (the one
+ being written by the I/O thread now) or a cold log, we can use
+ is_active(); if it is hot, we use the I/O cache; if it's cold we open
+ the file normally. But if is_active() reports that the log is hot, this
+ may change between the test and the consequence of the test. So we may
+ open the I/O cache whereas the log is now cold, which is nonsense.
+ To guard against this, we need to have LOCK_log.
+ */
+
+ DBUG_PRINT("info",("hot_log: %d",hot_log));
+ if (!hot_log) /* if hot_log, we already have this mutex */
+ mysql_mutex_lock(log_lock);
+ if (rli->relay_log.is_active(rli->linfo.log_file_name))
+ {
+ rli->cur_log= cur_log= rli->relay_log.get_log_file();
+ rli->cur_log_old_open_count= rli->relay_log.get_open_count();
+ DBUG_ASSERT(rli->cur_log_fd == -1);
+
+ /*
+ When the SQL thread is [stopped and] (re)started the
+ following may happen:
+
+ 1. Log was hot at stop time and remains hot at restart
+
+ SQL thread reads again from hot_log (SQL thread was
+ reading from the active log when it was stopped and the
+ very same log is still active on SQL thread restart).
+
+ In this case, my_b_seek is performed on cur_log, while
+ cur_log points to relay_log.get_log_file();
+
+ 2. Log was hot at stop time but got cold before restart
+
+ The log was hot when SQL thread stopped, but it is not
+ anymore when the SQL thread restarts.
+
+ In this case, the SQL thread reopens the log, using
+ cache_buf, ie, cur_log points to &cache_buf, and thence
+ its coordinates are reset.
+
+ 3. Log was already cold at stop time
+
+ The log was not hot when the SQL thread stopped, and, of
+ course, it will not be hot when it restarts.
+
+ In this case, the SQL thread opens the cold log again,
+ using cache_buf, ie, cur_log points to &cache_buf, and
+ thence its coordinates are reset.
+
+ 4. Log was hot at stop time, DBA changes to previous cold
+ log and restarts SQL thread
+
+ The log was hot when the SQL thread was stopped, but the
+ user changed the coordinates of the SQL thread to
+ restart from a previous cold log.
+
+ In this case, at start time, cur_log points to a cold
+ log, opened using &cache_buf as cache, and coordinates
+ are reset. However, as it moves on to the next logs, it
+ will eventually reach the hot log. If the hot log is the
+ same at the time the SQL thread was stopped, then
+ coordinates were not reset - the cur_log will point to
+ relay_log.get_log_file(), and not a freshly opened
+ IO_CACHE through cache_buf. For this reason we need to
+ deploy a my_b_seek before calling check_binlog_magic at
+ this point of the code (see: BUG#55263 for more
+ details).
+
+ NOTES:
+ - We must keep the LOCK_log to read the 4 first bytes, as
+ this is a hot log (same as when we call read_log_event()
+ above: for a hot log we take the mutex).
+
+ - Because of scenario #4 above, we need to have a
+ my_b_seek here. Otherwise, we might hit the assertion
+ inside check_binlog_magic.
+ */
+
+ my_b_seek(cur_log, (my_off_t) 0);
+ if (check_binlog_magic(cur_log,&errmsg))
+ {
+ if (!hot_log)
+ mysql_mutex_unlock(log_lock);
+ goto err;
+ }
+ if (rli->alloc_inuse_relaylog(rli->linfo.log_file_name))
+ {
+ if (!hot_log)
+ mysql_mutex_unlock(log_lock);
+ goto err;
+ }
+ if (!hot_log)
+ mysql_mutex_unlock(log_lock);
+ continue;
+ }
+ if (!hot_log)
+ mysql_mutex_unlock(log_lock);
+ /*
+ if we get here, the log was not hot, so we will have to open it
+ ourselves. We are sure that the log is still not hot now (a log can get
+ from hot to cold, but not from cold to hot). No need for LOCK_log.
+ */
+ // open_binlog() will check the magic header
+ if ((rli->cur_log_fd=open_binlog(cur_log,rli->linfo.log_file_name,
+ &errmsg)) <0)
+ goto err;
+ if (rli->alloc_inuse_relaylog(rli->linfo.log_file_name))
+ goto err;
+ }
+ else
+ {
+ /*
+ Read failed with a non-EOF error.
+ TODO: come up with something better to handle this error
+ */
+ if (hot_log)
+ mysql_mutex_unlock(log_lock);
+ sql_print_error("Slave SQL thread: I/O error reading \
+event(errno: %d cur_log->error: %d)",
+ my_errno,cur_log->error);
+ // set read position to the beginning of the event
+ my_b_seek(cur_log,rli->event_relay_log_pos);
+ /* otherwise, we have had a partial read */
+ errmsg = "Aborting slave SQL thread because of partial event read";
+ break; // To end of function
+ }
+ }
+ if (!errmsg && global_system_variables.log_warnings)
+ {
+ sql_print_information("Error reading relay log event: %s",
+ "slave SQL thread was killed");
+ DBUG_RETURN(0);
+ }
+
+err:
+ if (errmsg)
+ sql_print_error("Error reading relay log event: %s", errmsg);
+ DBUG_RETURN(0);
+}
+
+/*
+ Rotate a relay log (this is used only by FLUSH LOGS; the automatic rotation
+ because of size is simpler because when we do it we already have all relevant
+ locks; here we don't, so this function is mainly taking locks).
+ Returns nothing as we cannot catch any error (MYSQL_BIN_LOG::new_file()
+ is void).
+*/
+
+int rotate_relay_log(Master_info* mi)
+{
+ DBUG_ENTER("rotate_relay_log");
+ Relay_log_info* rli= &mi->rli;
+ int error= 0;
+
+ DBUG_EXECUTE_IF("crash_before_rotate_relaylog", DBUG_SUICIDE(););
+
+ /*
+ We need to test inited because otherwise, new_file() will attempt to lock
+ LOCK_log, which may not be inited (if we're not a slave).
+ */
+ if (!rli->inited)
+ {
+ DBUG_PRINT("info", ("rli->inited == 0"));
+ goto end;
+ }
+
+ /* If the relay log is closed, new_file() will do nothing. */
+ if ((error= rli->relay_log.new_file()))
+ goto end;
+
+ /*
+ We harvest now, because otherwise BIN_LOG_HEADER_SIZE will not immediately
+ be counted, so imagine a succession of FLUSH LOGS and assume the slave
+ threads are started:
+ relay_log_space decreases by the size of the deleted relay log, but does
+ not increase, so flush-after-flush we may become negative, which is wrong.
+ Even if this will be corrected as soon as a query is replicated on the
+ slave (because the I/O thread will then call harvest_bytes_written() which
+ will harvest all these BIN_LOG_HEADER_SIZE we forgot), it may give strange
+ output in SHOW SLAVE STATUS meanwhile. So we harvest now.
+ If the log is closed, then this will just harvest the last writes, probably
+ 0 as they probably have been harvested.
+
+ Note that it needs to be protected by mi->data_lock.
+ */
+ mysql_mutex_assert_owner(&mi->data_lock);
+ rli->relay_log.harvest_bytes_written(&rli->log_space_total);
+end:
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Detects, based on master's version (as found in the relay log), if master
+ has a certain bug.
+ @param rli Relay_log_info which tells the master's version
+ @param bug_id Number of the bug as found in bugs.mysql.com
+ @param report bool report error message, default TRUE
+
+ @param pred Predicate function that will be called with @c param to
+ check for the bug. If the function return @c true, the bug is present,
+ otherwise, it is not.
+
+ @param param State passed to @c pred function.
+
+ @return TRUE if master has the bug, FALSE if it does not.
+*/
+bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report,
+ bool (*pred)(const void *), const void *param)
+{
+ struct st_version_range_for_one_bug {
+ uint bug_id;
+ const uchar introduced_in[3]; // first version with bug
+ const uchar fixed_in[3]; // first version with fix
+ };
+ static struct st_version_range_for_one_bug versions_for_all_bugs[]=
+ {
+ {24432, { 5, 0, 24 }, { 5, 0, 38 } },
+ {24432, { 5, 1, 12 }, { 5, 1, 17 } },
+ {33029, { 5, 0, 0 }, { 5, 0, 58 } },
+ {33029, { 5, 1, 0 }, { 5, 1, 12 } },
+ {37426, { 5, 1, 0 }, { 5, 1, 26 } },
+ };
+ const uchar *master_ver=
+ rli->relay_log.description_event_for_exec->server_version_split.ver;
+
+ DBUG_ASSERT(sizeof(rli->relay_log.description_event_for_exec->server_version_split.ver) == 3);
+
+ for (uint i= 0;
+ i < sizeof(versions_for_all_bugs)/sizeof(*versions_for_all_bugs);i++)
+ {
+ const uchar *introduced_in= versions_for_all_bugs[i].introduced_in,
+ *fixed_in= versions_for_all_bugs[i].fixed_in;
+ if ((versions_for_all_bugs[i].bug_id == bug_id) &&
+ (memcmp(introduced_in, master_ver, 3) <= 0) &&
+ (memcmp(fixed_in, master_ver, 3) > 0) &&
+ (pred == NULL || (*pred)(param)))
+ {
+ if (!report)
+ return TRUE;
+ // a short message for SHOW SLAVE STATUS (message length constraints)
+ my_printf_error(ER_UNKNOWN_ERROR, "master may suffer from"
+ " http://bugs.mysql.com/bug.php?id=%u"
+ " so slave stops; check error log on slave"
+ " for more info", MYF(0), bug_id);
+ // a verbose message for the error log
+ rli->report(ERROR_LEVEL, ER_UNKNOWN_ERROR, NULL,
+ "According to the master's version ('%s'),"
+ " it is probable that master suffers from this bug:"
+ " http://bugs.mysql.com/bug.php?id=%u"
+ " and thus replicating the current binary log event"
+ " may make the slave's data become different from the"
+ " master's data."
+ " To take no risk, slave refuses to replicate"
+ " this event and stops."
+ " We recommend that all updates be stopped on the"
+ " master and slave, that the data of both be"
+ " manually synchronized,"
+ " that master's binary logs be deleted,"
+ " that master be upgraded to a version at least"
+ " equal to '%d.%d.%d'. Then replication can be"
+ " restarted.",
+ rli->relay_log.description_event_for_exec->server_version,
+ bug_id,
+ fixed_in[0], fixed_in[1], fixed_in[2]);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ BUG#33029, For all 5.0 up to 5.0.58 exclusive, and 5.1 up to 5.1.12
+ exclusive, if one statement in a SP generated AUTO_INCREMENT value
+ by the top statement, all statements after it would be considered
+ generated AUTO_INCREMENT value by the top statement, and a
+ erroneous INSERT_ID value might be associated with these statement,
+ which could cause duplicate entry error and stop the slave.
+
+ Detect buggy master to work around.
+ */
+bool rpl_master_erroneous_autoinc(THD *thd)
+{
+ if (thd->rgi_slave)
+ {
+ DBUG_EXECUTE_IF("simulate_bug33029", return TRUE;);
+ return rpl_master_has_bug(thd->rgi_slave->rli, 33029, FALSE, NULL, NULL);
+ }
+ return FALSE;
+}
+
+/**
+ @} (end of group Replication)
+*/
+
+#endif /* HAVE_REPLICATION */
diff --git a/sql/slave.h b/sql/slave.h
new file mode 100644
index 00000000000..e16f801b577
--- /dev/null
+++ b/sql/slave.h
@@ -0,0 +1,274 @@
+/*
+ Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifndef SLAVE_H
+#define SLAVE_H
+
+/**
+ @defgroup Replication Replication
+ @{
+
+ @file
+*/
+
+/**
+ Some of defines are need in parser even though replication is not
+ compiled in (embedded).
+*/
+
+/**
+ The maximum is defined as (ULONG_MAX/1000) with 4 bytes ulong
+*/
+#define SLAVE_MAX_HEARTBEAT_PERIOD 4294967
+
+#ifdef HAVE_REPLICATION
+
+#include "log.h"
+#include "my_list.h"
+#include "rpl_filter.h"
+#include "rpl_tblmap.h"
+
+#define SLAVE_NET_TIMEOUT 3600
+
+#define MAX_SLAVE_ERROR 2000
+
+#define MAX_REPLICATION_THREAD 64
+
+// Forward declarations
+class Relay_log_info;
+class Master_info;
+class Master_info_index;
+struct rpl_group_info;
+struct rpl_parallel_thread;
+
+int init_intvar_from_file(int* var, IO_CACHE* f, int default_val);
+int init_strvar_from_file(char *var, int max_size, IO_CACHE *f,
+ const char *default_val);
+int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val);
+int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f);
+
+/*****************************************************************************
+
+ MySQL Replication
+
+ Replication is implemented via two types of threads:
+
+ I/O Thread - One of these threads is started for each master server.
+ They maintain a connection to their master server, read log
+ events from the master as they arrive, and queues them into
+ a single, shared relay log file. A Master_info
+ represents each of these threads.
+
+ SQL Thread - One of these threads is started and reads from the relay log
+ file, executing each event. A Relay_log_info
+ represents this thread.
+
+ Buffering in the relay log file makes it unnecessary to reread events from
+ a master server across a slave restart. It also decouples the slave from
+ the master where long-running updates and event logging are concerned--ie
+ it can continue to log new events while a slow query executes on the slave.
+
+*****************************************************************************/
+
+/*
+ MUTEXES in replication:
+
+ LOCK_active_mi: [note: this was originally meant for multimaster, to switch
+ from a master to another, to protect active_mi] It is used to SERIALIZE ALL
+ administrative commands of replication: START SLAVE, STOP SLAVE, CHANGE
+ MASTER, RESET SLAVE, end_slave() (when mysqld stops) [init_slave() does not
+ need it it's called early]. Any of these commands holds the mutex from the
+ start till the end. This thus protects us against a handful of deadlocks
+ (consider start_slave_thread() which, when starting the I/O thread, releases
+ mi->run_lock, keeps rli->run_lock, and tries to re-acquire mi->run_lock).
+
+ Currently active_mi never moves (it's created at startup and deleted at
+ shutdown, and not changed: it always points to the same Master_info struct),
+ because we don't have multimaster. So for the moment, mi does not move, and
+ mi->rli does not either.
+
+ In Master_info: run_lock, data_lock
+ run_lock protects all information about the run state: slave_running, thd
+ and the existence of the I/O thread to stop/start it, you need this mutex).
+ data_lock protects some moving members of the struct: counters (log name,
+ position) and relay log (MYSQL_BIN_LOG object).
+
+ In Relay_log_info: run_lock, data_lock
+ see Master_info
+
+ Order of acquisition: if you want to have LOCK_active_mi and a run_lock, you
+ must acquire LOCK_active_mi first.
+
+ In MYSQL_BIN_LOG: LOCK_log, LOCK_index of the binlog and the relay log
+ LOCK_log: when you write to it. LOCK_index: when you create/delete a binlog
+ (so that you have to update the .index file).
+*/
+
+extern ulong master_retry_count;
+extern MY_BITMAP slave_error_mask;
+extern char slave_skip_error_names[];
+extern bool use_slave_mask;
+extern char *slave_load_tmpdir;
+extern char *master_info_file;
+extern MYSQL_PLUGIN_IMPORT char *relay_log_info_file;
+extern char *opt_relay_logname, *opt_relaylog_index_name;
+extern my_bool opt_skip_slave_start, opt_reckless_slave;
+extern my_bool opt_log_slave_updates;
+extern char *opt_slave_skip_errors;
+extern my_bool opt_replicate_annotate_row_events;
+extern ulonglong relay_log_space_limit;
+
+/*
+ 3 possible values for Master_info::slave_running and
+ Relay_log_info::slave_running.
+ The values 0,1,2 are very important: to keep the diff small, I didn't
+ substitute places where we use 0/1 with the newly defined symbols. So don't change
+ these values.
+ The same way, code is assuming that in Relay_log_info we use only values
+ 0/1.
+ I started with using an enum, but
+ enum_variable=1; is not legal so would have required many line changes.
+*/
+#define MYSQL_SLAVE_NOT_RUN 0
+#define MYSQL_SLAVE_RUN_NOT_CONNECT 1
+#define MYSQL_SLAVE_RUN_CONNECT 2
+
+#define RPL_LOG_NAME (rli->group_master_log_name[0] ? rli->group_master_log_name :\
+ "FIRST")
+#define IO_RPL_LOG_NAME (mi->master_log_name[0] ? mi->master_log_name :\
+ "FIRST")
+
+/*
+ If the following is set, if first gives an error, second will be
+ tried. Otherwise, if first fails, we fail.
+*/
+#define SLAVE_FORCE_ALL 4
+
+/*
+ Values for the option --replicate-events-marked-for-skip.
+ Must match the names in replicate_events_marked_for_skip_names in sys_vars.cc
+*/
+#define RPL_SKIP_REPLICATE 0
+#define RPL_SKIP_FILTER_ON_SLAVE 1
+#define RPL_SKIP_FILTER_ON_MASTER 2
+
+
+int init_slave();
+int init_recovery(Master_info* mi, const char** errmsg);
+void init_slave_skip_errors(const char* arg);
+bool flush_relay_log_info(Relay_log_info* rli);
+int register_slave_on_master(MYSQL* mysql);
+int terminate_slave_threads(Master_info* mi, int thread_mask,
+ bool skip_lock = 0);
+int start_slave_threads(bool need_slave_mutex, bool wait_for_start,
+ Master_info* mi, const char* master_info_fname,
+ const char* slave_info_fname, int thread_mask);
+/*
+ cond_lock is usually same as start_lock. It is needed for the case when
+ start_lock is 0 which happens if start_slave_thread() is called already
+ inside the start_lock section, but at the same time we want a
+ mysql_cond_wait() on start_cond, start_lock
+*/
+int start_slave_thread(
+#ifdef HAVE_PSI_INTERFACE
+ PSI_thread_key thread_key,
+#endif
+ pthread_handler h_func,
+ mysql_mutex_t *start_lock,
+ mysql_mutex_t *cond_lock,
+ mysql_cond_t *start_cond,
+ volatile uint *slave_running,
+ volatile ulong *slave_run_id,
+ Master_info *mi);
+
+/* If fd is -1, dump to NET */
+int mysql_table_dump(THD* thd, const char* db,
+ const char* tbl_name, int fd = -1);
+
+/* retrieve table from master and copy to slave*/
+int fetch_master_table(THD* thd, const char* db_name, const char* table_name,
+ Master_info* mi, MYSQL* mysql, bool overwrite);
+
+bool show_master_info(THD* thd, Master_info* mi, bool full);
+bool show_all_master_info(THD* thd);
+bool show_binlog_info(THD* thd);
+bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report,
+ bool (*pred)(const void *), const void *param);
+bool rpl_master_erroneous_autoinc(THD* thd);
+
+const char *print_slave_db_safe(const char *db);
+void skip_load_data_infile(NET* net);
+
+void end_slave(); /* release slave threads */
+void close_active_mi(); /* clean up slave threads data */
+void clear_until_condition(Relay_log_info* rli);
+void clear_slave_error(Relay_log_info* rli);
+void end_relay_log_info(Relay_log_info* rli);
+void lock_slave_threads(Master_info* mi);
+void unlock_slave_threads(Master_info* mi);
+void init_thread_mask(int* mask,Master_info* mi,bool inverse);
+Format_description_log_event *
+read_relay_log_description_event(IO_CACHE *cur_log, ulonglong start_pos,
+ const char **errmsg);
+
+int init_relay_log_pos(Relay_log_info* rli,const char* log,ulonglong pos,
+ bool need_data_lock, const char** errmsg,
+ bool look_for_description_event);
+
+int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset,
+ const char** errmsg);
+void set_slave_thread_options(THD* thd);
+void set_slave_thread_default_charset(THD *thd, rpl_group_info *rgi);
+int rotate_relay_log(Master_info* mi);
+int has_temporary_error(THD *thd);
+int apply_event_and_update_pos(Log_event* ev, THD* thd,
+ struct rpl_group_info *rgi,
+ rpl_parallel_thread *rpt);
+
+pthread_handler_t handle_slave_io(void *arg);
+void slave_output_error_info(rpl_group_info *rgi, THD *thd);
+pthread_handler_t handle_slave_sql(void *arg);
+bool net_request_file(NET* net, const char* fname);
+
+extern bool volatile abort_loop;
+extern Master_info *active_mi; /* active_mi for multi-master */
+extern Master_info *default_master_info; /* To replace active_mi */
+extern Master_info_index *master_info_index;
+extern LEX_STRING default_master_connection_name;
+extern my_bool replicate_same_server_id;
+
+extern int disconnect_slave_event_count, abort_slave_event_count ;
+
+/* the master variables are defaults read from my.cnf or command line */
+extern uint report_port;
+extern char *master_info_file, *report_user;
+extern char *report_host, *report_password;
+
+extern I_List<THD> threads;
+
+#else
+#define close_active_mi() /* no-op */
+#endif /* HAVE_REPLICATION */
+
+/* masks for start/stop operations on io and sql slave threads */
+#define SLAVE_IO 1
+#define SLAVE_SQL 2
+
+/**
+ @} (end of group Replication)
+*/
+
+#endif
diff --git a/sql/sp.cc b/sql/sp.cc
new file mode 100644
index 00000000000..334b5e12ba3
--- /dev/null
+++ b/sql/sp.cc
@@ -0,0 +1,2278 @@
+/*
+ Copyright (c) 2002, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sp.h"
+#include "sql_base.h" // close_thread_tables
+#include "sql_parse.h" // parse_sql
+#include "key.h" // key_copy
+#include "sql_show.h" // append_definer, append_identifier
+#include "sql_db.h" // get_default_db_collation, mysql_opt_change_db,
+ // mysql_change_db, check_db_dir_existence,
+ // load_db_opt_by_name
+#include "sql_table.h" // write_bin_log
+#include "sql_acl.h" // SUPER_ACL
+#include "sp_head.h"
+#include "sp_cache.h"
+#include "lock.h" // lock_object_name
+
+#include <my_user.h>
+
+static bool
+create_string(THD *thd, String *buf,
+ stored_procedure_type sp_type,
+ const char *db, ulong dblen,
+ const char *name, ulong namelen,
+ const char *params, ulong paramslen,
+ const char *returns, ulong returnslen,
+ const char *body, ulong bodylen,
+ st_sp_chistics *chistics,
+ const LEX_STRING *definer_user,
+ const LEX_STRING *definer_host,
+ ulonglong sql_mode);
+
+static int
+db_load_routine(THD *thd, stored_procedure_type type, sp_name *name,
+ sp_head **sphp,
+ ulonglong sql_mode, const char *params, const char *returns,
+ const char *body, st_sp_chistics &chistics,
+ LEX_STRING *definer_user_name, LEX_STRING *definer_host_name,
+ longlong created, longlong modified,
+ Stored_program_creation_ctx *creation_ctx);
+
+static const
+TABLE_FIELD_TYPE proc_table_fields[MYSQL_PROC_FIELD_COUNT] =
+{
+ {
+ { C_STRING_WITH_LEN("db") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("name") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("type") },
+ { C_STRING_WITH_LEN("enum('FUNCTION','PROCEDURE')") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("specific_name") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("language") },
+ { C_STRING_WITH_LEN("enum('SQL')") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("sql_data_access") },
+ { C_STRING_WITH_LEN("enum('CONTAINS_SQL','NO_SQL','READS_SQL_DATA','MODIFIES_SQL_DATA')") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("is_deterministic") },
+ { C_STRING_WITH_LEN("enum('YES','NO')") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("security_type") },
+ { C_STRING_WITH_LEN("enum('INVOKER','DEFINER')") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("param_list") },
+ { C_STRING_WITH_LEN("blob") },
+ { NULL, 0 }
+ },
+
+ {
+ { C_STRING_WITH_LEN("returns") },
+ { C_STRING_WITH_LEN("longblob") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("body") },
+ { C_STRING_WITH_LEN("longblob") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("definer") },
+ { C_STRING_WITH_LEN("char(") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("created") },
+ { C_STRING_WITH_LEN("timestamp") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("modified") },
+ { C_STRING_WITH_LEN("timestamp") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("sql_mode") },
+ { C_STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES',"
+ "'IGNORE_SPACE','IGNORE_BAD_TABLE_OPTIONS','ONLY_FULL_GROUP_BY',"
+ "'NO_UNSIGNED_SUBTRACTION',"
+ "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB',"
+ "'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40',"
+ "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES',"
+ "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES',"
+ "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER',"
+ "'HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH')") },
+ { NULL, 0 }
+ },
+ {
+ { C_STRING_WITH_LEN("comment") },
+ { C_STRING_WITH_LEN("text") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("character_set_client") },
+ { C_STRING_WITH_LEN("char(32)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("collation_connection") },
+ { C_STRING_WITH_LEN("char(32)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("db_collation") },
+ { C_STRING_WITH_LEN("char(32)") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("body_utf8") },
+ { C_STRING_WITH_LEN("longblob") },
+ { NULL, 0 }
+ }
+};
+
+static const TABLE_FIELD_DEF
+proc_table_def= {MYSQL_PROC_FIELD_COUNT, proc_table_fields, 0, (uint*) 0 };
+
+/*************************************************************************/
+
+/**
+ Stored_routine_creation_ctx -- creation context of stored routines
+ (stored procedures and functions).
+*/
+
+class Stored_routine_creation_ctx : public Stored_program_creation_ctx,
+ public Sql_alloc
+{
+public:
+ static Stored_routine_creation_ctx *
+ load_from_db(THD *thd, const sp_name *name, TABLE *proc_tbl);
+
+public:
+ virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root)
+ {
+ return new (mem_root) Stored_routine_creation_ctx(m_client_cs,
+ m_connection_cl,
+ m_db_cl);
+ }
+
+protected:
+ virtual Object_creation_ctx *create_backup_ctx(THD *thd) const
+ {
+ DBUG_ENTER("Stored_routine_creation_ctx::create_backup_ctx");
+ DBUG_RETURN(new Stored_routine_creation_ctx(thd));
+ }
+
+private:
+ Stored_routine_creation_ctx(THD *thd)
+ : Stored_program_creation_ctx(thd)
+ { }
+
+ Stored_routine_creation_ctx(CHARSET_INFO *client_cs,
+ CHARSET_INFO *connection_cl,
+ CHARSET_INFO *db_cl)
+ : Stored_program_creation_ctx(client_cs, connection_cl, db_cl)
+ { }
+};
+
+/**************************************************************************
+ Stored_routine_creation_ctx implementation.
+**************************************************************************/
+
+bool load_charset(MEM_ROOT *mem_root,
+ Field *field,
+ CHARSET_INFO *dflt_cs,
+ CHARSET_INFO **cs)
+{
+ String cs_name;
+
+ if (get_field(mem_root, field, &cs_name))
+ {
+ *cs= dflt_cs;
+ return TRUE;
+ }
+
+ *cs= get_charset_by_csname(cs_name.c_ptr(), MY_CS_PRIMARY, MYF(0));
+
+ if (*cs == NULL)
+ {
+ *cs= dflt_cs;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*************************************************************************/
+
+bool load_collation(MEM_ROOT *mem_root,
+ Field *field,
+ CHARSET_INFO *dflt_cl,
+ CHARSET_INFO **cl)
+{
+ String cl_name;
+
+ if (get_field(mem_root, field, &cl_name))
+ {
+ *cl= dflt_cl;
+ return TRUE;
+ }
+
+ *cl= get_charset_by_name(cl_name.c_ptr(), MYF(0));
+
+ if (*cl == NULL)
+ {
+ *cl= dflt_cl;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*************************************************************************/
+
+Stored_routine_creation_ctx *
+Stored_routine_creation_ctx::load_from_db(THD *thd,
+ const sp_name *name,
+ TABLE *proc_tbl)
+{
+ /* Load character set/collation attributes. */
+
+ CHARSET_INFO *client_cs;
+ CHARSET_INFO *connection_cl;
+ CHARSET_INFO *db_cl;
+
+ const char *db_name= thd->strmake(name->m_db.str, name->m_db.length);
+ const char *sr_name= thd->strmake(name->m_name.str, name->m_name.length);
+
+ bool invalid_creation_ctx= FALSE;
+
+ if (load_charset(thd->mem_root,
+ proc_tbl->field[MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT],
+ thd->variables.character_set_client,
+ &client_cs))
+ {
+ sql_print_warning("Stored routine '%s'.'%s': invalid value "
+ "in column mysql.proc.character_set_client.",
+ (const char *) db_name,
+ (const char *) sr_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (load_collation(thd->mem_root,
+ proc_tbl->field[MYSQL_PROC_FIELD_COLLATION_CONNECTION],
+ thd->variables.collation_connection,
+ &connection_cl))
+ {
+ sql_print_warning("Stored routine '%s'.'%s': invalid value "
+ "in column mysql.proc.collation_connection.",
+ (const char *) db_name,
+ (const char *) sr_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (load_collation(thd->mem_root,
+ proc_tbl->field[MYSQL_PROC_FIELD_DB_COLLATION],
+ NULL,
+ &db_cl))
+ {
+ sql_print_warning("Stored routine '%s'.'%s': invalid value "
+ "in column mysql.proc.db_collation.",
+ (const char *) db_name,
+ (const char *) sr_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (invalid_creation_ctx)
+ {
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_SR_INVALID_CREATION_CTX,
+ ER(ER_SR_INVALID_CREATION_CTX),
+ (const char *) db_name,
+ (const char *) sr_name);
+ }
+
+ /*
+ If we failed to retrieve the database collation, load the default one
+ from the disk.
+ */
+
+ if (!db_cl)
+ db_cl= get_default_db_collation(thd, name->m_db.str);
+
+ /* Create the context. */
+
+ return new Stored_routine_creation_ctx(client_cs, connection_cl, db_cl);
+}
+
+/*************************************************************************/
+
+class Proc_table_intact : public Table_check_intact
+{
+private:
+ bool m_print_once;
+
+public:
+ Proc_table_intact() : m_print_once(TRUE) {}
+
+protected:
+ void report_error(uint code, const char *fmt, ...);
+};
+
+
+/**
+ Report failure to validate the mysql.proc table definition.
+ Print a message to the error log only once.
+*/
+
+void Proc_table_intact::report_error(uint code, const char *fmt, ...)
+{
+ va_list args;
+ char buf[512];
+
+ va_start(args, fmt);
+ my_vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
+ if (code)
+ my_message(code, buf, MYF(0));
+ else
+ my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "proc");
+
+ if (m_print_once)
+ {
+ m_print_once= FALSE;
+ sql_print_error("%s", buf);
+ }
+};
+
+
+/** Single instance used to control printing to the error log. */
+static Proc_table_intact proc_table_intact;
+
+
+/**
+ Open the mysql.proc table for read.
+
+ @param thd Thread context
+ @param backup Pointer to Open_tables_state instance where information about
+ currently open tables will be saved, and from which will be
+ restored when we will end work with mysql.proc.
+
+ @retval
+ 0 Error
+ @retval
+ \# Pointer to TABLE object of mysql.proc
+*/
+
+TABLE *open_proc_table_for_read(THD *thd, Open_tables_backup *backup)
+{
+ TABLE_LIST table;
+
+ DBUG_ENTER("open_proc_table_for_read");
+
+ table.init_one_table("mysql", 5, "proc", 4, "proc", TL_READ);
+
+ if (open_system_tables_for_read(thd, &table, backup))
+ DBUG_RETURN(NULL);
+
+ if (!proc_table_intact.check(table.table, &proc_table_def))
+ DBUG_RETURN(table.table);
+
+ close_system_tables(thd, backup);
+
+ DBUG_RETURN(NULL);
+}
+
+
+/**
+ Open the mysql.proc table for update.
+
+ @param thd Thread context
+
+ @note
+ Table opened with this call should closed using close_thread_tables().
+
+ @retval
+ 0 Error
+ @retval
+ \# Pointer to TABLE object of mysql.proc
+*/
+
+static TABLE *open_proc_table_for_update(THD *thd)
+{
+ TABLE_LIST table_list;
+ TABLE *table;
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ DBUG_ENTER("open_proc_table_for_update");
+
+ table_list.init_one_table("mysql", 5, "proc", 4, "proc", TL_WRITE);
+
+ if (!(table= open_system_table_for_update(thd, &table_list)))
+ DBUG_RETURN(NULL);
+
+ if (!proc_table_intact.check(table, &proc_table_def))
+ DBUG_RETURN(table);
+
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ DBUG_RETURN(NULL);
+}
+
+
+/**
+ Find row in open mysql.proc table representing stored routine.
+
+ @param thd Thread context
+ @param type Type of routine to find (function or procedure)
+ @param name Name of routine
+ @param table TABLE object for open mysql.proc table.
+
+ @retval
+ SP_OK Routine found
+ @retval
+ SP_KEY_NOT_FOUND No routine with given name
+*/
+
+static int
+db_find_routine_aux(THD *thd, stored_procedure_type type, sp_name *name,
+ TABLE *table)
+{
+ uchar key[MAX_KEY_LENGTH]; // db, name, optional key length type
+ DBUG_ENTER("db_find_routine_aux");
+ DBUG_PRINT("enter", ("type: %d name: %.*s",
+ type, (int) name->m_name.length, name->m_name.str));
+
+ /*
+ Create key to find row. We have to use field->store() to be able to
+ handle VARCHAR and CHAR fields.
+ Assumption here is that the three first fields in the table are
+ 'db', 'name' and 'type' and the first key is the primary key over the
+ same fields.
+ */
+ if (name->m_name.length > table->field[1]->field_length)
+ DBUG_RETURN(SP_KEY_NOT_FOUND);
+ table->field[0]->store(name->m_db.str, name->m_db.length, &my_charset_bin);
+ table->field[1]->store(name->m_name.str, name->m_name.length,
+ &my_charset_bin);
+ table->field[2]->store((longlong) type, TRUE);
+ key_copy(key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ if (table->file->ha_index_read_idx_map(table->record[0], 0, key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ DBUG_RETURN(SP_KEY_NOT_FOUND);
+
+ DBUG_RETURN(SP_OK);
+}
+
+
+/**
+ Find routine definition in mysql.proc table and create corresponding
+ sp_head object for it.
+
+ @param thd Thread context
+ @param type Type of routine (TYPE_ENUM_PROCEDURE/...)
+ @param name Name of routine
+ @param sphp Out parameter in which pointer to created sp_head
+ object is returned (0 in case of error).
+
+ @note
+ This function may damage current LEX during execution, so it is good
+ idea to create temporary LEX and make it active before calling it.
+
+ @retval
+ 0 Success
+ @retval
+ non-0 Error (may be one of special codes like SP_KEY_NOT_FOUND)
+*/
+
+static int
+db_find_routine(THD *thd, stored_procedure_type type, sp_name *name,
+ sp_head **sphp)
+{
+ TABLE *table;
+ const char *params, *returns, *body;
+ int ret;
+ const char *definer;
+ longlong created;
+ longlong modified;
+ st_sp_chistics chistics;
+ char *ptr;
+ uint length;
+ char buff[65];
+ String str(buff, sizeof(buff), &my_charset_bin);
+ bool saved_time_zone_used= thd->time_zone_used;
+ ulonglong sql_mode, saved_mode= thd->variables.sql_mode;
+ Open_tables_backup open_tables_state_backup;
+ Stored_program_creation_ctx *creation_ctx;
+ char definer_user_name_holder[USERNAME_LENGTH + 1];
+ LEX_STRING definer_user_name= { definer_user_name_holder, USERNAME_LENGTH };
+ char definer_host_name_holder[HOSTNAME_LENGTH + 1];
+ LEX_STRING definer_host_name= { definer_host_name_holder, HOSTNAME_LENGTH };
+
+ DBUG_ENTER("db_find_routine");
+ DBUG_PRINT("enter", ("type: %d name: %.*s",
+ type, (int) name->m_name.length, name->m_name.str));
+
+ *sphp= 0; // In case of errors
+ if (!(table= open_proc_table_for_read(thd, &open_tables_state_backup)))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
+ /* Reset sql_mode during data dictionary operations. */
+ thd->variables.sql_mode= 0;
+
+ if ((ret= db_find_routine_aux(thd, type, name, table)) != SP_OK)
+ goto done;
+
+ if (table->s->fields < MYSQL_PROC_FIELD_COUNT)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+
+ bzero((char *)&chistics, sizeof(chistics));
+ if ((ptr= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_ACCESS])) == NULL)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+ switch (ptr[0]) {
+ case 'N':
+ chistics.daccess= SP_NO_SQL;
+ break;
+ case 'C':
+ chistics.daccess= SP_CONTAINS_SQL;
+ break;
+ case 'R':
+ chistics.daccess= SP_READS_SQL_DATA;
+ break;
+ case 'M':
+ chistics.daccess= SP_MODIFIES_SQL_DATA;
+ break;
+ default:
+ chistics.daccess= SP_DEFAULT_ACCESS_MAPPING;
+ }
+
+ if ((ptr= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_DETERMINISTIC])) == NULL)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+ chistics.detistic= (ptr[0] == 'N' ? FALSE : TRUE);
+
+ if ((ptr= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_SECURITY_TYPE])) == NULL)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+ chistics.suid= (ptr[0] == 'I' ? SP_IS_NOT_SUID : SP_IS_SUID);
+
+ if ((params= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_PARAM_LIST])) == NULL)
+ {
+ params= "";
+ }
+
+ if (type == TYPE_ENUM_PROCEDURE)
+ returns= "";
+ else if ((returns= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_RETURNS])) == NULL)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+
+ if ((body= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_BODY])) == NULL)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+
+ // Get additional information
+ if ((definer= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_DEFINER])) == NULL)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+
+ modified= table->field[MYSQL_PROC_FIELD_MODIFIED]->val_int();
+ created= table->field[MYSQL_PROC_FIELD_CREATED]->val_int();
+
+ sql_mode= (ulong) table->field[MYSQL_PROC_FIELD_SQL_MODE]->val_int();
+
+ table->field[MYSQL_PROC_FIELD_COMMENT]->val_str(&str, &str);
+
+ ptr= 0;
+ if ((length= str.length()))
+ ptr= thd->strmake(str.ptr(), length);
+ chistics.comment.str= ptr;
+ chistics.comment.length= length;
+
+ creation_ctx= Stored_routine_creation_ctx::load_from_db(thd, name, table);
+
+ close_system_tables(thd, &open_tables_state_backup);
+ table= 0;
+
+ if (parse_user(definer, strlen(definer),
+ definer_user_name.str, &definer_user_name.length,
+ definer_host_name.str, &definer_host_name.length) &&
+ definer_user_name.length && !definer_host_name.length)
+ {
+ // 'user@' -> 'user@%'
+ definer_host_name= host_not_specified;
+ }
+
+ ret= db_load_routine(thd, type, name, sphp,
+ sql_mode, params, returns, body, chistics,
+ &definer_user_name, &definer_host_name,
+ created, modified, creation_ctx);
+ done:
+ /*
+ Restore the time zone flag as the timezone usage in proc table
+ does not affect replication.
+ */
+ thd->time_zone_used= saved_time_zone_used;
+ if (table)
+ close_system_tables(thd, &open_tables_state_backup);
+ thd->variables.sql_mode= saved_mode;
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Silence DEPRECATED SYNTAX warnings when loading a stored procedure
+ into the cache.
+*/
+struct Silence_deprecated_warning : public Internal_error_handler
+{
+public:
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+};
+
+bool
+Silence_deprecated_warning::handle_condition(
+ THD *,
+ uint sql_errno,
+ const char*,
+ Sql_condition::enum_warning_level level,
+ const char*,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ if (sql_errno == ER_WARN_DEPRECATED_SYNTAX &&
+ level == Sql_condition::WARN_LEVEL_WARN)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/**
+ @brief The function parses input strings and returns SP stucture.
+
+ @param[in] thd Thread handler
+ @param[in] defstr CREATE... string
+ @param[in] sql_mode SQL mode
+ @param[in] creation_ctx Creation context of stored routines
+
+ @return Pointer on sp_head struct
+ @retval # Pointer on sp_head struct
+ @retval 0 error
+*/
+
+static sp_head *sp_compile(THD *thd, String *defstr, ulonglong sql_mode,
+ Stored_program_creation_ctx *creation_ctx)
+{
+ sp_head *sp;
+ ulonglong old_sql_mode= thd->variables.sql_mode;
+ ha_rows old_select_limit= thd->variables.select_limit;
+ sp_rcontext *old_spcont= thd->spcont;
+ Silence_deprecated_warning warning_handler;
+ Parser_state parser_state;
+
+ thd->variables.sql_mode= sql_mode;
+ thd->variables.select_limit= HA_POS_ERROR;
+
+ if (parser_state.init(thd, defstr->c_ptr_safe(), defstr->length()))
+ {
+ thd->variables.sql_mode= old_sql_mode;
+ thd->variables.select_limit= old_select_limit;
+ return NULL;
+ }
+
+ lex_start(thd);
+ thd->push_internal_handler(&warning_handler);
+ thd->spcont= 0;
+
+ if (parse_sql(thd, & parser_state, creation_ctx) || thd->lex == NULL)
+ {
+ sp= thd->lex->sphead;
+ delete sp;
+ sp= 0;
+ }
+ else
+ {
+ sp= thd->lex->sphead;
+ }
+
+ thd->pop_internal_handler();
+ thd->spcont= old_spcont;
+ thd->variables.sql_mode= old_sql_mode;
+ thd->variables.select_limit= old_select_limit;
+ return sp;
+}
+
+
+class Bad_db_error_handler : public Internal_error_handler
+{
+public:
+ Bad_db_error_handler()
+ :m_error_caught(false)
+ {}
+
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* message,
+ Sql_condition ** cond_hdl);
+
+ bool error_caught() const { return m_error_caught; }
+
+private:
+ bool m_error_caught;
+};
+
+bool
+Bad_db_error_handler::handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* message,
+ Sql_condition ** cond_hdl)
+{
+ if (sql_errno == ER_BAD_DB_ERROR)
+ {
+ m_error_caught= true;
+ return true;
+ }
+ return false;
+}
+
+
+static int
+db_load_routine(THD *thd, stored_procedure_type type,
+ sp_name *name, sp_head **sphp,
+ ulonglong sql_mode, const char *params, const char *returns,
+ const char *body, st_sp_chistics &chistics,
+ LEX_STRING *definer_user_name, LEX_STRING *definer_host_name,
+ longlong created, longlong modified,
+ Stored_program_creation_ctx *creation_ctx)
+{
+ LEX *old_lex= thd->lex, newlex;
+ String defstr;
+ char saved_cur_db_name_buf[SAFE_NAME_LEN+1];
+ LEX_STRING saved_cur_db_name=
+ { saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
+ bool cur_db_changed;
+ Bad_db_error_handler db_not_exists_handler;
+
+ int ret= 0;
+
+ thd->lex= &newlex;
+ newlex.current_select= NULL;
+
+ defstr.set_charset(creation_ctx->get_client_cs());
+
+ /*
+ We have to add DEFINER clause and provide proper routine characterstics in
+ routine definition statement that we build here to be able to use this
+ definition for SHOW CREATE PROCEDURE later.
+ */
+
+ if (!create_string(thd, &defstr,
+ type,
+ NULL, 0,
+ name->m_name.str, name->m_name.length,
+ params, strlen(params),
+ returns, strlen(returns),
+ body, strlen(body),
+ &chistics, definer_user_name, definer_host_name,
+ sql_mode))
+ {
+ ret= SP_INTERNAL_ERROR;
+ goto end;
+ }
+
+ thd->push_internal_handler(&db_not_exists_handler);
+ /*
+ Change the current database (if needed).
+
+ TODO: why do we force switch here?
+ */
+
+ if (mysql_opt_change_db(thd, &name->m_db, &saved_cur_db_name, TRUE,
+ &cur_db_changed))
+ {
+ ret= SP_INTERNAL_ERROR;
+ thd->pop_internal_handler();
+ goto end;
+ }
+ thd->pop_internal_handler();
+ if (db_not_exists_handler.error_caught())
+ {
+ ret= SP_INTERNAL_ERROR;
+ my_error(ER_BAD_DB_ERROR, MYF(0), name->m_db.str);
+
+ goto end;
+ }
+
+ {
+ *sphp= sp_compile(thd, &defstr, sql_mode, creation_ctx);
+ /*
+ Force switching back to the saved current database (if changed),
+ because it may be NULL. In this case, mysql_change_db() would
+ generate an error.
+ */
+
+ if (cur_db_changed && mysql_change_db(thd, &saved_cur_db_name, TRUE))
+ {
+ ret= SP_INTERNAL_ERROR;
+ goto end;
+ }
+
+ if (!*sphp)
+ {
+ ret= SP_PARSE_ERROR;
+ goto end;
+ }
+
+ (*sphp)->set_definer(definer_user_name, definer_host_name);
+ (*sphp)->set_info(created, modified, &chistics, sql_mode);
+ (*sphp)->set_creation_ctx(creation_ctx);
+ (*sphp)->optimize();
+ /*
+ Not strictly necessary to invoke this method here, since we know
+ that we've parsed CREATE PROCEDURE/FUNCTION and not an
+ UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
+ maintain the invariant that this method is called for each
+ distinct statement, in case its logic is extended with other
+ types of analyses in future.
+ */
+ newlex.set_trg_event_type_for_tables();
+ }
+
+end:
+ thd->lex->sphead= NULL;
+ lex_end(thd->lex);
+ thd->lex= old_lex;
+ return ret;
+}
+
+
+static void
+sp_returns_type(THD *thd, String &result, sp_head *sp)
+{
+ TABLE table;
+ TABLE_SHARE share;
+ Field *field;
+ bzero((char*) &table, sizeof(table));
+ bzero((char*) &share, sizeof(share));
+ table.in_use= thd;
+ table.s = &share;
+ field= sp->create_result_field(0, 0, &table);
+ field->sql_type(result);
+
+ if (field->has_charset())
+ {
+ result.append(STRING_WITH_LEN(" CHARSET "));
+ result.append(field->charset()->csname);
+ if (!(field->charset()->state & MY_CS_PRIMARY))
+ {
+ result.append(STRING_WITH_LEN(" COLLATE "));
+ result.append(field->charset()->name);
+ }
+ }
+
+ delete field;
+}
+
+
+/**
+ Write stored-routine object into mysql.proc.
+
+ This operation stores attributes of the stored procedure/function into
+ the mysql.proc.
+
+ @param thd Thread context.
+ @param type Stored routine type
+ (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION).
+ @param sp Stored routine object to store.
+
+ @note Opens and closes the thread tables. Therefore assumes
+ that there are no locked tables in this thread at the time of
+ invocation.
+ Unlike some other DDL statements, *does* close the tables
+ in the end, since the call to this function is normally
+ followed by an implicit grant (sp_grant_privileges())
+ and this subsequent call opens and closes mysql.procs_priv.
+
+ @return Error code. SP_OK is returned on success. Other
+ SP_ constants are used to indicate about errors.
+*/
+
+int
+sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp)
+{
+ int ret;
+ TABLE *table;
+ char definer_buf[USER_HOST_BUFF_SIZE];
+ LEX_STRING definer;
+ ulonglong saved_mode= thd->variables.sql_mode;
+ MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ?
+ MDL_key::FUNCTION : MDL_key::PROCEDURE;
+
+ CHARSET_INFO *db_cs= get_default_db_collation(thd, sp->m_db.str);
+
+ enum_check_fields saved_count_cuted_fields;
+
+ bool store_failed= FALSE;
+ DBUG_ENTER("sp_create_routine");
+ DBUG_PRINT("enter", ("type: %d name: %.*s", (int) type,
+ (int) sp->m_name.length,
+ sp->m_name.str));
+ String retstr(64);
+ retstr.set_charset(system_charset_info);
+
+ DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
+ type == TYPE_ENUM_FUNCTION);
+
+ /* Grab an exclusive MDL lock. */
+ if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
+ /* Reset sql_mode during data dictionary operations. */
+ thd->variables.sql_mode= 0;
+
+ saved_count_cuted_fields= thd->count_cuted_fields;
+ thd->count_cuted_fields= CHECK_FIELD_WARN;
+
+ if (!(table= open_proc_table_for_update(thd)))
+ ret= SP_OPEN_TABLE_FAILED;
+ else
+ {
+ restore_record(table, s->default_values); // Get default values for fields
+
+ /* NOTE: all needed privilege checks have been already done. */
+ thd->lex->definer->set_lex_string(&definer, definer_buf);
+
+ if (table->s->fields < MYSQL_PROC_FIELD_COUNT)
+ {
+ ret= SP_GET_FIELD_FAILED;
+ goto done;
+ }
+
+ if (system_charset_info->cset->numchars(system_charset_info,
+ sp->m_name.str,
+ sp->m_name.str+sp->m_name.length) >
+ table->field[MYSQL_PROC_FIELD_NAME]->char_length())
+ {
+ ret= SP_BAD_IDENTIFIER;
+ goto done;
+ }
+ if (sp->m_body.length > table->field[MYSQL_PROC_FIELD_BODY]->field_length)
+ {
+ ret= SP_BODY_TOO_LONG;
+ goto done;
+ }
+
+ store_failed=
+ table->field[MYSQL_PROC_FIELD_DB]->
+ store(sp->m_db.str, sp->m_db.length, system_charset_info);
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_NAME]->
+ store(sp->m_name.str, sp->m_name.length, system_charset_info);
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_MYSQL_TYPE]->
+ store((longlong)type, TRUE);
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_SPECIFIC_NAME]->
+ store(sp->m_name.str, sp->m_name.length, system_charset_info);
+
+ if (sp->m_chistics->daccess != SP_DEFAULT_ACCESS)
+ {
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_ACCESS]->
+ store((longlong)sp->m_chistics->daccess, TRUE);
+ }
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_DETERMINISTIC]->
+ store((longlong)(sp->m_chistics->detistic ? 1 : 2), TRUE);
+
+ if (sp->m_chistics->suid != SP_IS_DEFAULT_SUID)
+ {
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_SECURITY_TYPE]->
+ store((longlong)sp->m_chistics->suid, TRUE);
+ }
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_PARAM_LIST]->
+ store(sp->m_params.str, sp->m_params.length, system_charset_info);
+
+ if (sp->m_type == TYPE_ENUM_FUNCTION)
+ {
+ sp_returns_type(thd, retstr, sp);
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_RETURNS]->
+ store(retstr.ptr(), retstr.length(), system_charset_info);
+ }
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_BODY]->
+ store(sp->m_body.str, sp->m_body.length, system_charset_info);
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_DEFINER]->
+ store(definer.str, definer.length, system_charset_info);
+
+ ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_CREATED])->set_time();
+ ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time();
+
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_SQL_MODE]->
+ store((longlong)saved_mode, TRUE);
+
+ if (sp->m_chistics->comment.str)
+ {
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_COMMENT]->
+ store(sp->m_chistics->comment.str, sp->m_chistics->comment.length,
+ system_charset_info);
+ }
+
+ if ((sp->m_type == TYPE_ENUM_FUNCTION) &&
+ !trust_function_creators && mysql_bin_log.is_open())
+ {
+ if (!sp->m_chistics->detistic)
+ {
+ /*
+ Note that this test is not perfect; one could use
+ a non-deterministic read-only function in an update statement.
+ */
+ enum enum_sp_data_access access=
+ (sp->m_chistics->daccess == SP_DEFAULT_ACCESS) ?
+ SP_DEFAULT_ACCESS_MAPPING : sp->m_chistics->daccess;
+ if (access == SP_CONTAINS_SQL ||
+ access == SP_MODIFIES_SQL_DATA)
+ {
+ my_message(ER_BINLOG_UNSAFE_ROUTINE,
+ ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0));
+ ret= SP_INTERNAL_ERROR;
+ goto done;
+ }
+ }
+ if (!(thd->security_ctx->master_access & SUPER_ACL))
+ {
+ my_message(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER,
+ ER(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER), MYF(0));
+ ret= SP_INTERNAL_ERROR;
+ goto done;
+ }
+ }
+
+ table->field[MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT]->set_notnull();
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT]->store(
+ thd->charset()->csname,
+ strlen(thd->charset()->csname),
+ system_charset_info);
+
+ table->field[MYSQL_PROC_FIELD_COLLATION_CONNECTION]->set_notnull();
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_COLLATION_CONNECTION]->store(
+ thd->variables.collation_connection->name,
+ strlen(thd->variables.collation_connection->name),
+ system_charset_info);
+
+ table->field[MYSQL_PROC_FIELD_DB_COLLATION]->set_notnull();
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_DB_COLLATION]->store(
+ db_cs->name, strlen(db_cs->name), system_charset_info);
+
+ table->field[MYSQL_PROC_FIELD_BODY_UTF8]->set_notnull();
+ store_failed= store_failed ||
+ table->field[MYSQL_PROC_FIELD_BODY_UTF8]->store(
+ sp->m_body_utf8.str, sp->m_body_utf8.length, system_charset_info);
+
+ if (store_failed)
+ {
+ ret= SP_FLD_STORE_FAILED;
+ goto done;
+ }
+
+ ret= SP_OK;
+ if (table->file->ha_write_row(table->record[0]))
+ ret= SP_WRITE_ROW_FAILED;
+ /* Make change permanent and avoid 'table is marked as crashed' errors */
+ table->file->extra(HA_EXTRA_FLUSH);
+
+ if (ret == SP_OK)
+ sp_cache_invalidate();
+
+ if (ret == SP_OK && mysql_bin_log.is_open())
+ {
+ thd->clear_error();
+
+ String log_query;
+ log_query.set_charset(system_charset_info);
+
+ if (!create_string(thd, &log_query,
+ sp->m_type,
+ (sp->m_explicit_name ? sp->m_db.str : NULL),
+ (sp->m_explicit_name ? sp->m_db.length : 0),
+ sp->m_name.str, sp->m_name.length,
+ sp->m_params.str, sp->m_params.length,
+ retstr.ptr(), retstr.length(),
+ sp->m_body.str, sp->m_body.length,
+ sp->m_chistics, &(thd->lex->definer->user),
+ &(thd->lex->definer->host),
+ saved_mode))
+ {
+ ret= SP_INTERNAL_ERROR;
+ goto done;
+ }
+ /* restore sql_mode when binloging */
+ thd->variables.sql_mode= saved_mode;
+ /* Such a statement can always go directly to binlog, no trans cache */
+ if (thd->binlog_query(THD::STMT_QUERY_TYPE,
+ log_query.ptr(), log_query.length(),
+ FALSE, FALSE, FALSE, 0))
+ ret= SP_INTERNAL_ERROR;
+ thd->variables.sql_mode= 0;
+ }
+ }
+
+done:
+ thd->count_cuted_fields= saved_count_cuted_fields;
+ thd->variables.sql_mode= saved_mode;
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Delete the record for the stored routine object from mysql.proc.
+
+ The operation deletes the record for the stored routine specified by name
+ from the mysql.proc table and invalidates the stored-routine cache.
+
+ @param thd Thread context.
+ @param type Stored routine type
+ (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION)
+ @param name Stored routine name.
+
+ @return Error code. SP_OK is returned on success. Other SP_ constants are
+ used to indicate about errors.
+*/
+
+int
+sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name)
+{
+ TABLE *table;
+ int ret;
+ MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ?
+ MDL_key::FUNCTION : MDL_key::PROCEDURE;
+ DBUG_ENTER("sp_drop_routine");
+ DBUG_PRINT("enter", ("type: %d name: %.*s",
+ type, (int) name->m_name.length, name->m_name.str));
+
+ DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
+ type == TYPE_ENUM_FUNCTION);
+
+ /* Grab an exclusive MDL lock. */
+ if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str))
+ DBUG_RETURN(SP_DELETE_ROW_FAILED);
+
+ if (!(table= open_proc_table_for_update(thd)))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
+ /*
+ This statement will be replicated as a statement, even when using
+ row-based replication. The flag will be reset at the end of the
+ statement.
+ */
+ if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK)
+ {
+ if (table->file->ha_delete_row(table->record[0]))
+ ret= SP_DELETE_ROW_FAILED;
+ /* Make change permanent and avoid 'table is marked as crashed' errors */
+ table->file->extra(HA_EXTRA_FLUSH);
+ }
+
+ if (ret == SP_OK)
+ {
+ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length()))
+ ret= SP_INTERNAL_ERROR;
+ sp_cache_invalidate();
+
+ /*
+ A lame workaround for lack of cache flush:
+ make sure the routine is at least gone from the
+ local cache.
+ */
+ {
+ sp_head *sp;
+ sp_cache **spc= (type == TYPE_ENUM_FUNCTION ?
+ &thd->sp_func_cache : &thd->sp_proc_cache);
+ sp= sp_cache_lookup(spc, name);
+ if (sp)
+ sp_cache_flush_obsolete(spc, &sp);
+ }
+ }
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Find and updated the record for the stored routine object in mysql.proc.
+
+ The operation finds the record for the stored routine specified by name
+ in the mysql.proc table and updates it with new attributes. After
+ successful update, the cache is invalidated.
+
+ @param thd Thread context.
+ @param type Stored routine type
+ (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION)
+ @param name Stored routine name.
+ @param chistics New values of stored routine attributes to write.
+
+ @return Error code. SP_OK is returned on success. Other SP_ constants are
+ used to indicate about errors.
+*/
+
+int
+sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name,
+ st_sp_chistics *chistics)
+{
+ TABLE *table;
+ int ret;
+ MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ?
+ MDL_key::FUNCTION : MDL_key::PROCEDURE;
+ DBUG_ENTER("sp_update_routine");
+ DBUG_PRINT("enter", ("type: %d name: %.*s",
+ (int) type,
+ (int) name->m_name.length, name->m_name.str));
+
+ DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
+ type == TYPE_ENUM_FUNCTION);
+
+ /* Grab an exclusive MDL lock. */
+ if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
+ if (!(table= open_proc_table_for_update(thd)))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
+ if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK)
+ {
+ if (type == TYPE_ENUM_FUNCTION && ! trust_function_creators &&
+ mysql_bin_log.is_open() &&
+ (chistics->daccess == SP_CONTAINS_SQL ||
+ chistics->daccess == SP_MODIFIES_SQL_DATA))
+ {
+ char *ptr;
+ bool is_deterministic;
+ ptr= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_DETERMINISTIC]);
+ if (ptr == NULL)
+ {
+ ret= SP_INTERNAL_ERROR;
+ goto err;
+ }
+ is_deterministic= ptr[0] == 'N' ? FALSE : TRUE;
+ if (!is_deterministic)
+ {
+ my_message(ER_BINLOG_UNSAFE_ROUTINE,
+ ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0));
+ ret= SP_INTERNAL_ERROR;
+ goto err;
+ }
+ }
+
+ store_record(table,record[1]);
+ ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time();
+ if (chistics->suid != SP_IS_DEFAULT_SUID)
+ table->field[MYSQL_PROC_FIELD_SECURITY_TYPE]->
+ store((longlong)chistics->suid, TRUE);
+ if (chistics->daccess != SP_DEFAULT_ACCESS)
+ table->field[MYSQL_PROC_FIELD_ACCESS]->
+ store((longlong)chistics->daccess, TRUE);
+ if (chistics->comment.str)
+ table->field[MYSQL_PROC_FIELD_COMMENT]->store(chistics->comment.str,
+ chistics->comment.length,
+ system_charset_info);
+ if ((ret= table->file->ha_update_row(table->record[1],table->record[0])) &&
+ ret != HA_ERR_RECORD_IS_THE_SAME)
+ ret= SP_WRITE_ROW_FAILED;
+ else
+ ret= 0;
+ /* Make change permanent and avoid 'table is marked as crashed' errors */
+ table->file->extra(HA_EXTRA_FLUSH);
+ }
+
+ if (ret == SP_OK)
+ {
+ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length()))
+ ret= SP_INTERNAL_ERROR;
+ sp_cache_invalidate();
+ }
+err:
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ This internal handler is used to trap errors from opening mysql.proc.
+*/
+
+class Lock_db_routines_error_handler : public Internal_error_handler
+{
+public:
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+ {
+ if (sql_errno == ER_NO_SUCH_TABLE ||
+ sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE ||
+ sql_errno == ER_CANNOT_LOAD_FROM_TABLE_V2 ||
+ sql_errno == ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE ||
+ sql_errno == ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2)
+ return true;
+ return false;
+ }
+};
+
+
+/**
+ Acquires exclusive metadata lock on all stored routines in the
+ given database.
+
+ @note Will also return false (=success) if mysql.proc can't be opened
+ or is outdated. This allows DROP DATABASE to continue in these
+ cases.
+ */
+
+bool lock_db_routines(THD *thd, char *db)
+{
+ TABLE *table;
+ uint key_len;
+ Open_tables_backup open_tables_state_backup;
+ MDL_request_list mdl_requests;
+ Lock_db_routines_error_handler err_handler;
+ uchar keybuf[MAX_KEY_LENGTH];
+ DBUG_ENTER("lock_db_routines");
+
+ DBUG_ASSERT(ok_for_lower_case_names(db));
+
+ /*
+ mysql.proc will be re-opened during deletion, so we can ignore
+ errors when opening the table here. The error handler is
+ used to avoid getting the same warning twice.
+ */
+ thd->push_internal_handler(&err_handler);
+ table= open_proc_table_for_read(thd, &open_tables_state_backup);
+ thd->pop_internal_handler();
+ if (!table)
+ {
+ /*
+ DROP DATABASE should not fail even if mysql.proc does not exist
+ or is outdated. We therefore only abort mysql_rm_db() if we
+ have errors not handled by the error handler.
+ */
+ DBUG_RETURN(thd->is_error() || thd->killed);
+ }
+
+ table->field[MYSQL_PROC_FIELD_DB]->store(db, strlen(db), system_charset_info);
+ key_len= table->key_info->key_part[0].store_length;
+ table->field[MYSQL_PROC_FIELD_DB]->get_key_image(keybuf, key_len, Field::itRAW);
+ int nxtres= table->file->ha_index_init(0, 1);
+ if (nxtres)
+ {
+ table->file->print_error(nxtres, MYF(0));
+ close_system_tables(thd, &open_tables_state_backup);
+ DBUG_RETURN(true);
+ }
+
+ if (! table->file->ha_index_read_map(table->record[0], keybuf, (key_part_map)1,
+ HA_READ_KEY_EXACT))
+ {
+ do
+ {
+ char *sp_name= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_NAME]);
+ if (sp_name == NULL) // skip invalid sp names (hand-edited mysql.proc?)
+ continue;
+
+ longlong sp_type= table->field[MYSQL_PROC_MYSQL_TYPE]->val_int();
+ MDL_request *mdl_request= new (thd->mem_root) MDL_request;
+ mdl_request->init(sp_type == TYPE_ENUM_FUNCTION ?
+ MDL_key::FUNCTION : MDL_key::PROCEDURE,
+ db, sp_name, MDL_EXCLUSIVE, MDL_TRANSACTION);
+ mdl_requests.push_front(mdl_request);
+ } while (! (nxtres= table->file->ha_index_next_same(table->record[0], keybuf, key_len)));
+ }
+ table->file->ha_index_end();
+ if (nxtres != 0 && nxtres != HA_ERR_END_OF_FILE)
+ {
+ table->file->print_error(nxtres, MYF(0));
+ close_system_tables(thd, &open_tables_state_backup);
+ DBUG_RETURN(true);
+ }
+ close_system_tables(thd, &open_tables_state_backup);
+
+ /* We should already hold a global IX lock and a schema X lock. */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
+ MDL_INTENTION_EXCLUSIVE) &&
+ thd->mdl_context.is_lock_owner(MDL_key::SCHEMA, db, "",
+ MDL_EXCLUSIVE));
+ DBUG_RETURN(thd->mdl_context.acquire_locks(&mdl_requests,
+ thd->variables.lock_wait_timeout));
+}
+
+
+/**
+ Drop all routines in database 'db'
+
+ @note Close the thread tables, the calling code might want to
+ delete from other system tables afterwards.
+*/
+
+int
+sp_drop_db_routines(THD *thd, char *db)
+{
+ TABLE *table;
+ int ret;
+ uint key_len;
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ uchar keybuf[MAX_KEY_LENGTH];
+ DBUG_ENTER("sp_drop_db_routines");
+ DBUG_PRINT("enter", ("db: %s", db));
+
+ ret= SP_OPEN_TABLE_FAILED;
+ if (!(table= open_proc_table_for_update(thd)))
+ goto err;
+
+ table->field[MYSQL_PROC_FIELD_DB]->store(db, strlen(db), system_charset_info);
+ key_len= table->key_info->key_part[0].store_length;
+ table->field[MYSQL_PROC_FIELD_DB]->get_key_image(keybuf, key_len, Field::itRAW);
+
+ ret= SP_OK;
+ if (table->file->ha_index_init(0, 1))
+ {
+ ret= SP_KEY_NOT_FOUND;
+ goto err_idx_init;
+ }
+ if (!table->file->ha_index_read_map(table->record[0], keybuf, (key_part_map)1,
+ HA_READ_KEY_EXACT))
+ {
+ int nxtres;
+ bool deleted= FALSE;
+
+ do
+ {
+ if (! table->file->ha_delete_row(table->record[0]))
+ deleted= TRUE; /* We deleted something */
+ else
+ {
+ ret= SP_DELETE_ROW_FAILED;
+ nxtres= 0;
+ break;
+ }
+ } while (!(nxtres= table->file->ha_index_next_same(table->record[0],
+ keybuf, key_len)));
+ if (nxtres != HA_ERR_END_OF_FILE)
+ ret= SP_KEY_NOT_FOUND;
+ if (deleted)
+ {
+ sp_cache_invalidate();
+ /* Make change permanent and avoid 'table is marked as crashed' errors */
+ table->file->extra(HA_EXTRA_FLUSH);
+ }
+ }
+ table->file->ha_index_end();
+
+err_idx_init:
+ close_thread_tables(thd);
+ /*
+ Make sure to only release the MDL lock on mysql.proc, not other
+ metadata locks DROP DATABASE might have acquired.
+ */
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+err:
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Implement SHOW CREATE statement for stored routines.
+
+ The operation finds the stored routine object specified by name and then
+ calls sp_head::show_create_routine() for the object.
+
+ @param thd Thread context.
+ @param type Stored routine type
+ (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION)
+ @param name Stored routine name.
+
+ @return Error status.
+ @retval FALSE on success
+ @retval TRUE on error
+*/
+
+bool
+sp_show_create_routine(THD *thd, stored_procedure_type type, sp_name *name)
+{
+ sp_head *sp;
+
+ DBUG_ENTER("sp_show_create_routine");
+ DBUG_PRINT("enter", ("name: %.*s",
+ (int) name->m_name.length,
+ name->m_name.str));
+
+ DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
+ type == TYPE_ENUM_FUNCTION);
+
+ /*
+ @todo: Consider using prelocking for this code as well. Currently
+ SHOW CREATE PROCEDURE/FUNCTION is a dirty read of the data
+ dictionary, i.e. takes no metadata locks.
+ It is "safe" to do as long as it doesn't affect the results
+ of the binary log or the query cache, which currently it does not.
+ */
+ if (sp_cache_routine(thd, type, name, FALSE, &sp))
+ DBUG_RETURN(TRUE);
+
+ if (sp == NULL || sp->show_create_routine(thd, type))
+ {
+ /*
+ If we have insufficient privileges, pretend the routine
+ does not exist.
+ */
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
+ type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE",
+ name->m_name.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Obtain object representing stored procedure/function by its name from
+ stored procedures cache and looking into mysql.proc if needed.
+
+ @param thd thread context
+ @param type type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE)
+ @param name name of procedure
+ @param cp hash to look routine in
+ @param cache_only if true perform cache-only lookup
+ (Don't look in mysql.proc).
+
+ @retval
+ NonNULL pointer to sp_head object for the procedure
+ @retval
+ NULL in case of error.
+*/
+
+sp_head *
+sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name,
+ sp_cache **cp, bool cache_only)
+{
+ sp_head *sp;
+ ulong depth= (type == TYPE_ENUM_PROCEDURE ?
+ thd->variables.max_sp_recursion_depth :
+ 0);
+ DBUG_ENTER("sp_find_routine");
+ DBUG_PRINT("enter", ("name: %.*s.%.*s type: %d cache only %d",
+ (int) name->m_db.length, name->m_db.str,
+ (int) name->m_name.length, name->m_name.str,
+ type, cache_only));
+
+ if ((sp= sp_cache_lookup(cp, name)))
+ {
+ ulong level;
+ sp_head *new_sp;
+ const char *returns= "";
+
+ /*
+ String buffer for RETURNS data type must have system charset;
+ 64 -- size of "returns" column of mysql.proc.
+ */
+ String retstr(64);
+ retstr.set_charset(sp->get_creation_ctx()->get_client_cs());
+
+ DBUG_PRINT("info", ("found: 0x%lx", (ulong)sp));
+ if (sp->m_first_free_instance)
+ {
+ DBUG_PRINT("info", ("first free: 0x%lx level: %lu flags %x",
+ (ulong)sp->m_first_free_instance,
+ sp->m_first_free_instance->m_recursion_level,
+ sp->m_first_free_instance->m_flags));
+ DBUG_ASSERT(!(sp->m_first_free_instance->m_flags & sp_head::IS_INVOKED));
+ if (sp->m_first_free_instance->m_recursion_level > depth)
+ {
+ sp->recursion_level_error(thd);
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(sp->m_first_free_instance);
+ }
+ /*
+ Actually depth could be +1 than the actual value in case a SP calls
+ SHOW CREATE PROCEDURE. Hence, the linked list could hold up to one more
+ instance.
+ */
+
+ level= sp->m_last_cached_sp->m_recursion_level + 1;
+ if (level > depth)
+ {
+ sp->recursion_level_error(thd);
+ DBUG_RETURN(0);
+ }
+
+ if (type == TYPE_ENUM_FUNCTION)
+ {
+ sp_returns_type(thd, retstr, sp);
+ returns= retstr.ptr();
+ }
+ if (db_load_routine(thd, type, name, &new_sp,
+ sp->m_sql_mode, sp->m_params.str, returns,
+ sp->m_body.str, *sp->m_chistics,
+ &sp->m_definer_user, &sp->m_definer_host,
+ sp->m_created, sp->m_modified,
+ sp->get_creation_ctx()) == SP_OK)
+ {
+ sp->m_last_cached_sp->m_next_cached_sp= new_sp;
+ new_sp->m_recursion_level= level;
+ new_sp->m_first_instance= sp;
+ sp->m_last_cached_sp= sp->m_first_free_instance= new_sp;
+ DBUG_PRINT("info", ("added level: 0x%lx, level: %lu, flags %x",
+ (ulong)new_sp, new_sp->m_recursion_level,
+ new_sp->m_flags));
+ DBUG_RETURN(new_sp);
+ }
+ DBUG_RETURN(0);
+ }
+ if (!cache_only)
+ {
+ if (db_find_routine(thd, type, name, &sp) == SP_OK)
+ {
+ sp_cache_insert(cp, sp);
+ DBUG_PRINT("info", ("added new: 0x%lx, level: %lu, flags %x",
+ (ulong)sp, sp->m_recursion_level,
+ sp->m_flags));
+ }
+ }
+ DBUG_RETURN(sp);
+}
+
+
+/**
+ This is used by sql_acl.cc:mysql_routine_grant() and is used to find
+ the routines in 'routines'.
+
+ @param thd Thread handler
+ @param routines List of needles in the hay stack
+ @param any Any of the needles are good enough
+
+ @return
+ @retval FALSE Found.
+ @retval TRUE Not found
+*/
+
+bool
+sp_exist_routines(THD *thd, TABLE_LIST *routines, bool any)
+{
+ TABLE_LIST *routine;
+ bool sp_object_found;
+ DBUG_ENTER("sp_exists_routine");
+ for (routine= routines; routine; routine= routine->next_global)
+ {
+ sp_name *name;
+ LEX_STRING lex_db;
+ LEX_STRING lex_name;
+ lex_db.length= strlen(routine->db);
+ lex_name.length= strlen(routine->table_name);
+ lex_db.str= thd->strmake(routine->db, lex_db.length);
+ lex_name.str= thd->strmake(routine->table_name, lex_name.length);
+ name= new sp_name(lex_db, lex_name, true);
+ name->init_qname(thd);
+ sp_object_found= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, name,
+ &thd->sp_proc_cache, FALSE) != NULL ||
+ sp_find_routine(thd, TYPE_ENUM_FUNCTION, name,
+ &thd->sp_func_cache, FALSE) != NULL;
+ thd->get_stmt_da()->clear_warning_info(thd->query_id);
+ if (sp_object_found)
+ {
+ if (any)
+ break;
+ }
+ else if (!any)
+ {
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "FUNCTION or PROCEDURE",
+ routine->table_name);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
+ my_bool first)
+{
+ Sroutine_hash_entry *rn= (Sroutine_hash_entry *)ptr;
+ *plen= rn->mdl_request.key.length();
+ return (uchar *)rn->mdl_request.key.ptr();
+}
+
+
+/**
+ Auxilary function that adds new element to the set of stored routines
+ used by statement.
+
+ In case when statement uses stored routines but does not need
+ prelocking (i.e. it does not use any tables) we will access the
+ elements of Query_tables_list::sroutines set on prepared statement
+ re-execution. Because of this we have to allocate memory for both
+ hash element and copy of its key in persistent arena.
+
+ @param prelocking_ctx Prelocking context of the statement
+ @param arena Arena in which memory for new element will be
+ allocated
+ @param key Key for the hash representing set
+ @param belong_to_view Uppermost view which uses this routine
+ (0 if routine is not used by view)
+
+ @note
+ Will also add element to end of 'Query_tables_list::sroutines_list' list.
+
+ @todo
+ When we will got rid of these accesses on re-executions we will be
+ able to allocate memory for hash elements in non-persitent arena
+ and directly use key values from sp_head::m_sroutines sets instead
+ of making their copies.
+
+ @retval
+ TRUE new element was added.
+ @retval
+ FALSE element was not added (because it is already present in
+ the set).
+*/
+
+bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
+ const MDL_key *key, TABLE_LIST *belong_to_view)
+{
+ my_hash_init_opt(&prelocking_ctx->sroutines, system_charset_info,
+ Query_tables_list::START_SROUTINES_HASH_SIZE,
+ 0, 0, sp_sroutine_key, 0, 0);
+
+ if (!my_hash_search(&prelocking_ctx->sroutines, key->ptr(), key->length()))
+ {
+ Sroutine_hash_entry *rn=
+ (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry));
+ if (!rn) // OOM. Error will be reported using fatal_error().
+ return FALSE;
+ rn->mdl_request.init(key, MDL_SHARED, MDL_TRANSACTION);
+ if (my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn))
+ return FALSE;
+ prelocking_ctx->sroutines_list.link_in_list(rn, &rn->next);
+ rn->belong_to_view= belong_to_view;
+ rn->m_sp_cache_version= 0;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Add routine which is explicitly used by statement to the set of stored
+ routines used by this statement.
+
+ To be friendly towards prepared statements one should pass
+ persistent arena as second argument.
+
+ @param prelocking_ctx Prelocking context of the statement
+ @param arena Arena in which memory for new element of the set
+ will be allocated
+ @param rt Routine name
+ @param rt_type Routine type (one of TYPE_ENUM_PROCEDURE/...)
+
+ @note
+ Will also add element to end of 'Query_tables_list::sroutines_list' list
+ (and will take into account that this is an explicitly used routine).
+*/
+
+void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
+ sp_name *rt, enum stored_procedure_type rt_type)
+{
+ MDL_key key((rt_type == TYPE_ENUM_FUNCTION) ? MDL_key::FUNCTION :
+ MDL_key::PROCEDURE,
+ rt->m_db.str, rt->m_name.str);
+ (void)sp_add_used_routine(prelocking_ctx, arena, &key, 0);
+ prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next;
+ prelocking_ctx->sroutines_list_own_elements=
+ prelocking_ctx->sroutines_list.elements;
+}
+
+
+/**
+ Remove routines which are only indirectly used by statement from
+ the set of routines used by this statement.
+
+ @param prelocking_ctx Prelocking context of the statement
+*/
+
+void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx)
+{
+ Sroutine_hash_entry *not_own_rt, *next_rt;
+ for (not_own_rt= *prelocking_ctx->sroutines_list_own_last;
+ not_own_rt; not_own_rt= next_rt)
+ {
+ /*
+ It is safe to obtain not_own_rt->next after calling hash_delete() now
+ but we want to be more future-proof.
+ */
+ next_rt= not_own_rt->next;
+ my_hash_delete(&prelocking_ctx->sroutines, (uchar *)not_own_rt);
+ }
+
+ *prelocking_ctx->sroutines_list_own_last= NULL;
+ prelocking_ctx->sroutines_list.next= prelocking_ctx->sroutines_list_own_last;
+ prelocking_ctx->sroutines_list.elements=
+ prelocking_ctx->sroutines_list_own_elements;
+}
+
+
+/**
+ Merge contents of two hashes representing sets of routines used
+ by statements or by other routines.
+
+ @param dst hash to which elements should be added
+ @param src hash from which elements merged
+
+ @note
+ This procedure won't create new Sroutine_hash_entry objects,
+ instead it will simply add elements from source to destination
+ hash. Thus time of life of elements in destination hash becomes
+ dependant on time of life of elements from source hash. It also
+ won't touch lists linking elements in source and destination
+ hashes.
+
+ @returns
+ @return TRUE Failure
+ @return FALSE Success
+*/
+
+bool sp_update_sp_used_routines(HASH *dst, HASH *src)
+{
+ for (uint i=0 ; i < src->records ; i++)
+ {
+ Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i);
+ if (!my_hash_search(dst, (uchar *)rt->mdl_request.key.ptr(),
+ rt->mdl_request.key.length()))
+ {
+ if (my_hash_insert(dst, (uchar *)rt))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ Add contents of hash representing set of routines to the set of
+ routines used by statement.
+
+ @param thd Thread context
+ @param prelocking_ctx Prelocking context of the statement
+ @param src Hash representing set from which routines will
+ be added
+ @param belong_to_view Uppermost view which uses these routines, 0 if none
+
+ @note It will also add elements to end of
+ 'Query_tables_list::sroutines_list' list.
+*/
+
+void
+sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+ HASH *src, TABLE_LIST *belong_to_view)
+{
+ for (uint i=0 ; i < src->records ; i++)
+ {
+ Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i);
+ (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
+ &rt->mdl_request.key, belong_to_view);
+ }
+}
+
+
+/**
+ Add contents of list representing set of routines to the set of
+ routines used by statement.
+
+ @param thd Thread context
+ @param prelocking_ctx Prelocking context of the statement
+ @param src List representing set from which routines will
+ be added
+ @param belong_to_view Uppermost view which uses these routines, 0 if none
+
+ @note It will also add elements to end of
+ 'Query_tables_list::sroutines_list' list.
+*/
+
+void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+ SQL_I_List<Sroutine_hash_entry> *src,
+ TABLE_LIST *belong_to_view)
+{
+ for (Sroutine_hash_entry *rt= src->first; rt; rt= rt->next)
+ (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
+ &rt->mdl_request.key, belong_to_view);
+}
+
+
+/**
+ A helper wrapper around sp_cache_routine() to use from
+ prelocking until 'sp_name' is eradicated as a class.
+*/
+
+int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt,
+ bool lookup_only, sp_head **sp)
+{
+ char qname_buff[NAME_LEN*2+1+1];
+ sp_name name(&rt->mdl_request.key, qname_buff);
+ MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace();
+ stored_procedure_type type= ((mdl_type == MDL_key::FUNCTION) ?
+ TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE);
+
+ /*
+ Check that we have an MDL lock on this routine, unless it's a top-level
+ CALL. The assert below should be unambiguous: the first element
+ in sroutines_list has an MDL lock unless it's a top-level call, or a
+ trigger, but triggers can't occur here (see the preceding assert).
+ */
+ DBUG_ASSERT(rt->mdl_request.ticket || rt == thd->lex->sroutines_list.first);
+
+ return sp_cache_routine(thd, type, &name, lookup_only, sp);
+}
+
+
+/**
+ Ensure that routine is present in cache by loading it from the mysql.proc
+ table if needed. If the routine is present but old, reload it.
+ Emit an appropriate error if there was a problem during
+ loading.
+
+ @param[in] thd Thread context.
+ @param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE).
+ @param[in] name Name of routine.
+ @param[in] lookup_only Only check that the routine is in the cache.
+ If it's not, don't try to load. If it is present,
+ but old, don't try to reload.
+ @param[out] sp Pointer to sp_head object for routine, NULL if routine was
+ not found.
+
+ @retval 0 Either routine is found and was succesfully loaded into cache
+ or it does not exist.
+ @retval non-0 Error while loading routine from mysql,proc table.
+*/
+
+int sp_cache_routine(THD *thd, enum stored_procedure_type type, sp_name *name,
+ bool lookup_only, sp_head **sp)
+{
+ int ret= 0;
+ sp_cache **spc= (type == TYPE_ENUM_FUNCTION ?
+ &thd->sp_func_cache : &thd->sp_proc_cache);
+
+ DBUG_ENTER("sp_cache_routine");
+
+ DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
+
+ *sp= sp_cache_lookup(spc, name);
+
+ if (lookup_only)
+ DBUG_RETURN(SP_OK);
+
+ if (*sp)
+ {
+ sp_cache_flush_obsolete(spc, sp);
+ if (*sp)
+ DBUG_RETURN(SP_OK);
+ }
+
+ switch ((ret= db_find_routine(thd, type, name, sp)))
+ {
+ case SP_OK:
+ sp_cache_insert(spc, *sp);
+ break;
+ case SP_KEY_NOT_FOUND:
+ ret= SP_OK;
+ break;
+ default:
+ /* Query might have been killed, don't set error. */
+ if (thd->killed)
+ break;
+ /*
+ Any error when loading an existing routine is either some problem
+ with the mysql.proc table, or a parse error because the contents
+ has been tampered with (in which case we clear that error).
+ */
+ if (ret == SP_PARSE_ERROR)
+ thd->clear_error();
+ /*
+ If we cleared the parse error, or when db_find_routine() flagged
+ an error with it's return value without calling my_error(), we
+ set the generic "mysql.proc table corrupt" error here.
+ */
+ if (! thd->is_error())
+ {
+ /*
+ SP allows full NAME_LEN chars thus he have to allocate enough
+ size in bytes. Otherwise there is stack overrun could happen
+ if multibyte sequence is `name`. `db` is still safe because the
+ rest of the server checks agains NAME_LEN bytes and not chars.
+ Hence, the overrun happens only if the name is in length > 32 and
+ uses multibyte (cyrillic, greek, etc.)
+ */
+ char n[NAME_LEN*2+2];
+
+ /* m_qname.str is not always \0 terminated */
+ memcpy(n, name->m_qname.str, name->m_qname.length);
+ n[name->m_qname.length]= '\0';
+ my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
+ }
+ break;
+ }
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Generates the CREATE... string from the table information.
+
+ @return
+ Returns TRUE on success, FALSE on (alloc) failure.
+*/
+static bool
+create_string(THD *thd, String *buf,
+ stored_procedure_type type,
+ const char *db, ulong dblen,
+ const char *name, ulong namelen,
+ const char *params, ulong paramslen,
+ const char *returns, ulong returnslen,
+ const char *body, ulong bodylen,
+ st_sp_chistics *chistics,
+ const LEX_STRING *definer_user,
+ const LEX_STRING *definer_host,
+ ulonglong sql_mode)
+{
+ ulonglong old_sql_mode= thd->variables.sql_mode;
+ /* Make some room to begin with */
+ if (buf->alloc(100 + dblen + 1 + namelen + paramslen + returnslen + bodylen +
+ chistics->comment.length + 10 /* length of " DEFINER= "*/ +
+ USER_HOST_BUFF_SIZE))
+ return FALSE;
+
+ thd->variables.sql_mode= sql_mode;
+ buf->append(STRING_WITH_LEN("CREATE "));
+ append_definer(thd, buf, definer_user, definer_host);
+ if (type == TYPE_ENUM_FUNCTION)
+ buf->append(STRING_WITH_LEN("FUNCTION "));
+ else
+ buf->append(STRING_WITH_LEN("PROCEDURE "));
+ if (dblen > 0)
+ {
+ append_identifier(thd, buf, db, dblen);
+ buf->append('.');
+ }
+ append_identifier(thd, buf, name, namelen);
+ buf->append('(');
+ buf->append(params, paramslen);
+ buf->append(')');
+ if (type == TYPE_ENUM_FUNCTION)
+ {
+ buf->append(STRING_WITH_LEN(" RETURNS "));
+ buf->append(returns, returnslen);
+ }
+ buf->append('\n');
+ switch (chistics->daccess) {
+ case SP_NO_SQL:
+ buf->append(STRING_WITH_LEN(" NO SQL\n"));
+ break;
+ case SP_READS_SQL_DATA:
+ buf->append(STRING_WITH_LEN(" READS SQL DATA\n"));
+ break;
+ case SP_MODIFIES_SQL_DATA:
+ buf->append(STRING_WITH_LEN(" MODIFIES SQL DATA\n"));
+ break;
+ case SP_DEFAULT_ACCESS:
+ case SP_CONTAINS_SQL:
+ /* Do nothing */
+ break;
+ }
+ if (chistics->detistic)
+ buf->append(STRING_WITH_LEN(" DETERMINISTIC\n"));
+ if (chistics->suid == SP_IS_NOT_SUID)
+ buf->append(STRING_WITH_LEN(" SQL SECURITY INVOKER\n"));
+ if (chistics->comment.length)
+ {
+ buf->append(STRING_WITH_LEN(" COMMENT "));
+ append_unescaped(buf, chistics->comment.str, chistics->comment.length);
+ buf->append('\n');
+ }
+ buf->append(body, bodylen);
+ thd->variables.sql_mode= old_sql_mode;
+ return TRUE;
+}
+
+
+/**
+ @brief The function loads sp_head struct for information schema purposes
+ (used for I_S ROUTINES & PARAMETERS tables).
+
+ @param[in] thd thread handler
+ @param[in] proc_table mysql.proc table structurte
+ @param[in] db database name
+ @param[in] name sp name
+ @param[in] sql_mode SQL mode
+ @param[in] type Routine type
+ @param[in] returns 'returns' string
+ @param[in] params parameters definition string
+ @param[out] free_sp_head returns 1 if we need to free sp_head struct
+ otherwise returns 0
+
+ @return Pointer on sp_head struct
+ @retval # Pointer on sp_head struct
+ @retval 0 error
+*/
+
+sp_head *
+sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db,
+ String *name, ulong sql_mode, stored_procedure_type type,
+ const char *returns, const char *params,
+ bool *free_sp_head)
+{
+ const char *sp_body;
+ String defstr;
+ struct st_sp_chistics sp_chistics;
+ const LEX_STRING definer_user= {(char*)STRING_WITH_LEN("")};
+ const LEX_STRING definer_host= {(char*)STRING_WITH_LEN("")};
+ LEX_STRING sp_db_str;
+ LEX_STRING sp_name_str;
+ sp_head *sp;
+ sp_cache **spc= ((type == TYPE_ENUM_PROCEDURE) ?
+ &thd->sp_proc_cache : &thd->sp_func_cache);
+ sp_db_str.str= db->c_ptr();
+ sp_db_str.length= db->length();
+ sp_name_str.str= name->c_ptr();
+ sp_name_str.length= name->length();
+ sp_name sp_name_obj(sp_db_str, sp_name_str, true);
+ sp_name_obj.init_qname(thd);
+ *free_sp_head= 0;
+ if ((sp= sp_cache_lookup(spc, &sp_name_obj)))
+ {
+ return sp;
+ }
+
+ LEX *old_lex= thd->lex, newlex;
+ Stored_program_creation_ctx *creation_ctx=
+ Stored_routine_creation_ctx::load_from_db(thd, &sp_name_obj, proc_table);
+ sp_body= (type == TYPE_ENUM_FUNCTION ? "RETURN NULL" : "BEGIN END");
+ bzero((char*) &sp_chistics, sizeof(sp_chistics));
+ defstr.set_charset(creation_ctx->get_client_cs());
+ if (!create_string(thd, &defstr, type,
+ sp_db_str.str, sp_db_str.length,
+ sp_name_obj.m_name.str, sp_name_obj.m_name.length,
+ params, strlen(params),
+ returns, strlen(returns),
+ sp_body, strlen(sp_body),
+ &sp_chistics, &definer_user, &definer_host, sql_mode))
+ return 0;
+
+ thd->lex= &newlex;
+ newlex.current_select= NULL;
+ sp= sp_compile(thd, &defstr, sql_mode, creation_ctx);
+ *free_sp_head= 1;
+ thd->lex->sphead= NULL;
+ lex_end(thd->lex);
+ thd->lex= old_lex;
+ return sp;
+}
diff --git a/sql/sp.h b/sql/sp.h
new file mode 100644
index 00000000000..3353132346b
--- /dev/null
+++ b/sql/sp.h
@@ -0,0 +1,217 @@
+/* -*- C++ -*- */
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _SP_H_
+#define _SP_H_
+
+#include "sql_string.h" // LEX_STRING
+
+class Field;
+class Open_tables_backup;
+class Open_tables_state;
+class Query_arena;
+class Query_tables_list;
+class Sroutine_hash_entry;
+class THD;
+class sp_cache;
+class sp_head;
+class sp_name;
+struct st_sp_chistics;
+struct LEX;
+struct TABLE;
+struct TABLE_LIST;
+typedef struct st_hash HASH;
+template <typename T> class SQL_I_List;
+
+/*
+ Values for the type enum. This reflects the order of the enum declaration
+ in the CREATE TABLE command.
+*/
+enum stored_procedure_type
+{
+ TYPE_ENUM_FUNCTION=1,
+ TYPE_ENUM_PROCEDURE=2,
+ TYPE_ENUM_TRIGGER=3,
+ TYPE_ENUM_PROXY=4
+};
+
+/* Tells what SP_DEFAULT_ACCESS should be mapped to */
+#define SP_DEFAULT_ACCESS_MAPPING SP_CONTAINS_SQL
+
+// Return codes from sp_create_*, sp_drop_*, and sp_show_*:
+#define SP_OK 0
+#define SP_KEY_NOT_FOUND -1
+#define SP_OPEN_TABLE_FAILED -2
+#define SP_WRITE_ROW_FAILED -3
+#define SP_DELETE_ROW_FAILED -4
+#define SP_GET_FIELD_FAILED -5
+#define SP_PARSE_ERROR -6
+#define SP_INTERNAL_ERROR -7
+#define SP_NO_DB_ERROR -8
+#define SP_BAD_IDENTIFIER -9
+#define SP_BODY_TOO_LONG -10
+#define SP_FLD_STORE_FAILED -11
+
+/* DB storage of Stored PROCEDUREs and FUNCTIONs */
+enum
+{
+ MYSQL_PROC_FIELD_DB = 0,
+ MYSQL_PROC_FIELD_NAME,
+ MYSQL_PROC_MYSQL_TYPE,
+ MYSQL_PROC_FIELD_SPECIFIC_NAME,
+ MYSQL_PROC_FIELD_LANGUAGE,
+ MYSQL_PROC_FIELD_ACCESS,
+ MYSQL_PROC_FIELD_DETERMINISTIC,
+ MYSQL_PROC_FIELD_SECURITY_TYPE,
+ MYSQL_PROC_FIELD_PARAM_LIST,
+ MYSQL_PROC_FIELD_RETURNS,
+ MYSQL_PROC_FIELD_BODY,
+ MYSQL_PROC_FIELD_DEFINER,
+ MYSQL_PROC_FIELD_CREATED,
+ MYSQL_PROC_FIELD_MODIFIED,
+ MYSQL_PROC_FIELD_SQL_MODE,
+ MYSQL_PROC_FIELD_COMMENT,
+ MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT,
+ MYSQL_PROC_FIELD_COLLATION_CONNECTION,
+ MYSQL_PROC_FIELD_DB_COLLATION,
+ MYSQL_PROC_FIELD_BODY_UTF8,
+ MYSQL_PROC_FIELD_COUNT
+};
+
+/* Drop all routines in database 'db' */
+int
+sp_drop_db_routines(THD *thd, char *db);
+
+/**
+ Acquires exclusive metadata lock on all stored routines in the
+ given database.
+
+ @param thd Thread handler
+ @param db Database name
+
+ @retval false Success
+ @retval true Failure
+ */
+bool lock_db_routines(THD *thd, char *db);
+
+sp_head *
+sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name,
+ sp_cache **cp, bool cache_only);
+
+int
+sp_cache_routine(THD *thd, Sroutine_hash_entry *rt,
+ bool lookup_only, sp_head **sp);
+
+
+int
+sp_cache_routine(THD *thd, stored_procedure_type type, sp_name *name,
+ bool lookup_only, sp_head **sp);
+
+bool
+sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any);
+
+bool
+sp_show_create_routine(THD *thd, stored_procedure_type type, sp_name *name);
+
+int
+sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp);
+
+int
+sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name,
+ st_sp_chistics *chistics);
+
+int
+sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name);
+
+
+/**
+ Structure that represents element in the set of stored routines
+ used by statement or routine.
+*/
+
+class Sroutine_hash_entry
+{
+public:
+ /**
+ Metadata lock request for routine.
+ MDL_key in this request is also used as a key for set.
+ */
+ MDL_request mdl_request;
+ /**
+ Next element in list linking all routines in set. See also comments
+ for LEX::sroutine/sroutine_list and sp_head::m_sroutines.
+ */
+ Sroutine_hash_entry *next;
+ /**
+ Uppermost view which directly or indirectly uses this routine.
+ 0 if routine is not used in view. Note that it also can be 0 if
+ statement uses routine both via view and directly.
+ */
+ TABLE_LIST *belong_to_view;
+ /**
+ This is for prepared statement validation purposes.
+ A statement looks up and pre-loads all its stored functions
+ at prepare. Later on, if a function is gone from the cache,
+ execute may fail.
+ Remember the version of sp_head at prepare to be able to
+ invalidate the prepared statement at execute if it
+ changes.
+ */
+ ulong m_sp_cache_version;
+};
+
+
+/*
+ Procedures for handling sets of stored routines used by statement or routine.
+*/
+void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
+ sp_name *rt, stored_procedure_type rt_type);
+bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
+ const MDL_key *key, TABLE_LIST *belong_to_view);
+void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx);
+bool sp_update_sp_used_routines(HASH *dst, HASH *src);
+void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+ HASH *src, TABLE_LIST *belong_to_view);
+void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+ SQL_I_List<Sroutine_hash_entry> *src,
+ TABLE_LIST *belong_to_view);
+
+extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
+ my_bool first);
+
+/*
+ Routines which allow open/lock and close mysql.proc table even when
+ we already have some tables open and locked.
+*/
+TABLE *open_proc_table_for_read(THD *thd, Open_tables_backup *backup);
+
+sp_head *
+sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db,
+ String *name, ulong sql_mode, stored_procedure_type type,
+ const char *returns, const char *params,
+ bool *free_sp_head);
+
+bool load_charset(MEM_ROOT *mem_root,
+ Field *field,
+ CHARSET_INFO *dflt_cs,
+ CHARSET_INFO **cs);
+
+bool load_collation(MEM_ROOT *mem_root,
+ Field *field,
+ CHARSET_INFO *dflt_cl,
+ CHARSET_INFO **cl);
+
+#endif /* _SP_H_ */
diff --git a/sql/sp_cache.cc b/sql/sp_cache.cc
new file mode 100644
index 00000000000..bafd0f34ab6
--- /dev/null
+++ b/sql/sp_cache.cc
@@ -0,0 +1,315 @@
+/* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation
+#endif
+#include "sp_cache.h"
+#include "sp_head.h"
+
+static mysql_mutex_t Cversion_lock;
+static ulong volatile Cversion= 0;
+
+
+/*
+ Cache of stored routines.
+*/
+
+class sp_cache
+{
+public:
+ sp_cache();
+ ~sp_cache();
+
+ /**
+ Inserts a sp_head object into a hash table.
+
+ @returns Success status
+ @return TRUE Failure
+ @return FALSE Success
+ */
+ inline bool insert(sp_head *sp)
+ {
+ return my_hash_insert(&m_hashtable, (const uchar *)sp);
+ }
+
+ inline sp_head *lookup(char *name, uint namelen)
+ {
+ return (sp_head *) my_hash_search(&m_hashtable, (const uchar *)name,
+ namelen);
+ }
+
+ inline void remove(sp_head *sp)
+ {
+ my_hash_delete(&m_hashtable, (uchar *)sp);
+ }
+
+ /**
+ Remove all elements from a stored routine cache if the current
+ number of elements exceeds the argument value.
+
+ @param[in] upper_limit_for_elements Soft upper limit of elements that
+ can be stored in the cache.
+ */
+ void enforce_limit(ulong upper_limit_for_elements)
+ {
+ if (m_hashtable.records > upper_limit_for_elements)
+ my_hash_reset(&m_hashtable);
+ }
+
+private:
+ void init();
+ void cleanup();
+
+ /* All routines in this cache */
+ HASH m_hashtable;
+}; // class sp_cache
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_Cversion_lock;
+
+static PSI_mutex_info all_sp_cache_mutexes[]=
+{
+ { &key_Cversion_lock, "Cversion_lock", PSI_FLAG_GLOBAL}
+};
+
+static void init_sp_cache_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_sp_cache_mutexes);
+ PSI_server->register_mutex(category, all_sp_cache_mutexes, count);
+}
+#endif
+
+/* Initialize the SP caching once at startup */
+
+void sp_cache_init()
+{
+#ifdef HAVE_PSI_INTERFACE
+ init_sp_cache_psi_keys();
+#endif
+
+ mysql_mutex_init(key_Cversion_lock, &Cversion_lock, MY_MUTEX_INIT_FAST);
+}
+
+
+/*
+ Clear the cache *cp and set *cp to NULL.
+
+ SYNOPSIS
+ sp_cache_clear()
+ cp Pointer to cache to clear
+
+ NOTE
+ This function doesn't invalidate other caches.
+*/
+
+void sp_cache_clear(sp_cache **cp)
+{
+ sp_cache *c= *cp;
+
+ if (c)
+ {
+ delete c;
+ *cp= NULL;
+ }
+}
+
+
+void sp_cache_end()
+{
+ mysql_mutex_destroy(&Cversion_lock);
+}
+
+
+/*
+ Insert a routine into the cache.
+
+ SYNOPSIS
+ sp_cache_insert()
+ cp The cache to put routine into
+ sp Routine to insert.
+
+ TODO: Perhaps it will be more straightforward if in case we returned an
+ error from this function when we couldn't allocate sp_cache. (right
+ now failure to put routine into cache will cause a 'SP not found'
+ error to be reported at some later time)
+*/
+
+void sp_cache_insert(sp_cache **cp, sp_head *sp)
+{
+ sp_cache *c;
+
+ if (!(c= *cp))
+ {
+ if (!(c= new sp_cache()))
+ return; // End of memory error
+ }
+ /* Reading a ulong variable with no lock. */
+ sp->set_sp_cache_version(Cversion);
+ DBUG_PRINT("info",("sp_cache: inserting: %.*s", (int) sp->m_qname.length,
+ sp->m_qname.str));
+ c->insert(sp);
+ *cp= c; // Update *cp if it was NULL
+}
+
+
+/*
+ Look up a routine in the cache.
+ SYNOPSIS
+ sp_cache_lookup()
+ cp Cache to look into
+ name Name of rutine to find
+
+ NOTE
+ An obsolete (but not more obsolete then since last
+ sp_cache_flush_obsolete call) routine may be returned.
+
+ RETURN
+ The routine or
+ NULL if the routine not found.
+*/
+
+sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name)
+{
+ sp_cache *c= *cp;
+ if (! c)
+ return NULL;
+ return c->lookup(name->m_qname.str, name->m_qname.length);
+}
+
+
+/*
+ Invalidate all routines in all caches.
+
+ SYNOPSIS
+ sp_cache_invalidate()
+
+ NOTE
+ This is called when a VIEW definition is created or modified (and in some
+ other contexts). We can't destroy sp_head objects here as one may modify
+ VIEW definitions from prelocking-free SPs.
+*/
+
+void sp_cache_invalidate()
+{
+ DBUG_PRINT("info",("sp_cache: invalidating"));
+ thread_safe_increment(Cversion, &Cversion_lock);
+}
+
+
+/**
+ Remove an out-of-date SP from the cache.
+
+ @param[in] cp Cache to flush
+ @param[in] sp SP to remove.
+
+ @note This invalidates pointers to sp_head objects this thread
+ uses. In practice that means 'dont call this function when
+ inside SP'.
+*/
+
+void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp)
+{
+ if ((*sp)->sp_cache_version() < Cversion && !(*sp)->is_invoked())
+ {
+ (*cp)->remove(*sp);
+ *sp= NULL;
+ }
+}
+
+
+/**
+ Return the current global version of the cache.
+*/
+
+ulong sp_cache_version()
+{
+ return Cversion;
+}
+
+
+/**
+ Enforce that the current number of elements in the cache don't exceed
+ the argument value by flushing the cache if necessary.
+
+ @param[in] c Cache to check
+ @param[in] upper_limit_for_elements Soft upper limit for number of sp_head
+ objects that can be stored in the cache.
+*/
+void
+sp_cache_enforce_limit(sp_cache *c, ulong upper_limit_for_elements)
+{
+ if (c)
+ c->enforce_limit(upper_limit_for_elements);
+}
+
+/*************************************************************************
+ Internal functions
+ *************************************************************************/
+
+extern "C" uchar *hash_get_key_for_sp_head(const uchar *ptr, size_t *plen,
+ my_bool first);
+extern "C" void hash_free_sp_head(void *p);
+
+uchar *hash_get_key_for_sp_head(const uchar *ptr, size_t *plen,
+ my_bool first)
+{
+ sp_head *sp= (sp_head *)ptr;
+ *plen= sp->m_qname.length;
+ return (uchar*) sp->m_qname.str;
+}
+
+
+void hash_free_sp_head(void *p)
+{
+ sp_head *sp= (sp_head *)p;
+ delete sp;
+}
+
+
+sp_cache::sp_cache()
+{
+ init();
+}
+
+
+sp_cache::~sp_cache()
+{
+ my_hash_free(&m_hashtable);
+}
+
+
+void
+sp_cache::init()
+{
+ my_hash_init(&m_hashtable, system_charset_info, 0, 0, 0,
+ hash_get_key_for_sp_head, hash_free_sp_head, 0);
+}
+
+
+void
+sp_cache::cleanup()
+{
+ my_hash_free(&m_hashtable);
+}
diff --git a/sql/sp_cache.h b/sql/sp_cache.h
new file mode 100644
index 00000000000..b21d4c4bf25
--- /dev/null
+++ b/sql/sp_cache.h
@@ -0,0 +1,68 @@
+/* -*- C++ -*- */
+/* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _SP_CACHE_H_
+#define _SP_CACHE_H_
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "my_global.h" /* ulong */
+
+/*
+ Stored procedures/functions cache. This is used as follows:
+ * Each thread has its own cache.
+ * Each sp_head object is put into its thread cache before it is used, and
+ then remains in the cache until deleted.
+*/
+
+class sp_head;
+class sp_cache;
+class sp_name;
+
+/*
+ Cache usage scenarios:
+ 1. Application-wide init:
+ sp_cache_init();
+
+ 2. SP execution in thread:
+ 2.1 While holding sp_head* pointers:
+
+ // look up a routine in the cache (no checks if it is up to date or not)
+ sp_cache_lookup();
+
+ sp_cache_insert();
+ sp_cache_invalidate();
+
+ 2.2 When not holding any sp_head* pointers:
+ sp_cache_flush_obsolete();
+
+ 3. Before thread exit:
+ sp_cache_clear();
+*/
+
+void sp_cache_init();
+void sp_cache_end();
+void sp_cache_clear(sp_cache **cp);
+void sp_cache_insert(sp_cache **cp, sp_head *sp);
+sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name);
+void sp_cache_invalidate();
+void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp);
+ulong sp_cache_version();
+void sp_cache_enforce_limit(sp_cache *cp, ulong upper_limit_for_elements);
+
+#endif /* _SP_CACHE_H_ */
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
new file mode 100644
index 00000000000..e181e14611b
--- /dev/null
+++ b/sql/sp_head.cc
@@ -0,0 +1,4293 @@
+/*
+ Copyright (c) 2002, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2013, Monty Program Ab
+
+ 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 <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_prepare.h"
+#include "sql_cache.h" // query_cache_*
+#include "probes_mysql.h"
+#include "sql_show.h" // append_identifier
+#include "sql_db.h" // mysql_opt_change_db, mysql_change_db
+#include "sql_table.h" // sp_prepare_create_field,
+ // prepare_create_field
+#include "sql_acl.h" // *_ACL
+#include "sql_array.h" // Dynamic_array
+#include "log_event.h" // Query_log_event
+#include "sql_derived.h" // mysql_handle_derived
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation
+#endif
+#include "sp_head.h"
+#include "sp.h"
+#include "sp_pcontext.h"
+#include "sp_rcontext.h"
+#include "sp_cache.h"
+#include "set_var.h"
+#include "sql_parse.h" // cleanup_items
+#include "sql_base.h" // close_thread_tables
+#include "transaction.h" // trans_commit_stmt
+#include "sql_audit.h"
+#include "debug_sync.h"
+
+/*
+ Sufficient max length of printed destinations and frame offsets (all uints).
+*/
+#define SP_INSTR_UINT_MAXLEN 8
+#define SP_STMT_PRINT_MAXLEN 40
+
+
+#include <my_user.h>
+
+extern "C" uchar *sp_table_key(const uchar *ptr, size_t *plen, my_bool first);
+
+/**
+ Helper function which operates on a THD object to set the query start_time to
+ the current time.
+
+ @param[in, out] thd The session object
+
+*/
+
+static void reset_start_time_for_sp(THD *thd)
+{
+ if (!thd->in_sub_stmt)
+ thd->set_start_time();
+}
+
+Item_result
+sp_map_result_type(enum enum_field_types type)
+{
+ switch (type) {
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ return INT_RESULT;
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ return DECIMAL_RESULT;
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ return REAL_RESULT;
+ default:
+ return STRING_RESULT;
+ }
+}
+
+
+Item::Type
+sp_map_item_type(enum enum_field_types type)
+{
+ switch (type) {
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ return Item::INT_ITEM;
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ return Item::DECIMAL_ITEM;
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ return Item::REAL_ITEM;
+ default:
+ return Item::STRING_ITEM;
+ }
+}
+
+
+/**
+ Return a string representation of the Item value.
+
+ @param thd thread handle
+ @param str string buffer for representation of the value
+
+ @note
+ If the item has a string result type, the string is escaped
+ according to its character set.
+
+ @retval
+ NULL on error
+ @retval
+ non-NULL a pointer to valid a valid string on success
+*/
+
+static String *
+sp_get_item_value(THD *thd, Item *item, String *str)
+{
+ switch (item->result_type()) {
+ case REAL_RESULT:
+ case INT_RESULT:
+ case DECIMAL_RESULT:
+ if (item->field_type() != MYSQL_TYPE_BIT)
+ return item->val_str(str);
+ else {/* Bit type is handled as binary string */}
+ case STRING_RESULT:
+ {
+ String *result= item->val_str(str);
+
+ if (!result)
+ return NULL;
+
+ {
+ char buf_holder[STRING_BUFFER_USUAL_SIZE];
+ String buf(buf_holder, sizeof(buf_holder), result->charset());
+ CHARSET_INFO *cs= thd->variables.character_set_client;
+
+ /* We must reset length of the buffer, because of String specificity. */
+ buf.length(0);
+
+ buf.append('_');
+ buf.append(result->charset()->csname);
+ if (cs->escape_with_backslash_is_dangerous)
+ buf.append(' ');
+ append_query_string(cs, &buf, result->ptr(), result->length(),
+ thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
+ buf.append(" COLLATE '");
+ buf.append(item->collation.collation->name);
+ buf.append('\'');
+ str->copy(buf);
+
+ return str;
+ }
+ }
+
+ case ROW_RESULT:
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ Returns a combination of:
+ - sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might
+ result in multiple result sets being sent back.
+ - sp_head::CONTAINS_DYNAMIC_SQL: added if 'cmd' is one of PREPARE,
+ EXECUTE, DEALLOCATE.
+*/
+
+uint
+sp_get_flags_for_command(LEX *lex)
+{
+ uint flags;
+
+ switch (lex->sql_command) {
+ case SQLCOM_SELECT:
+ if (lex->result)
+ {
+ flags= 0; /* This is a SELECT with INTO clause */
+ break;
+ }
+ /* fallthrough */
+ case SQLCOM_ANALYZE:
+ case SQLCOM_OPTIMIZE:
+ case SQLCOM_PRELOAD_KEYS:
+ case SQLCOM_ASSIGN_TO_KEYCACHE:
+ case SQLCOM_CHECKSUM:
+ case SQLCOM_CHECK:
+ case SQLCOM_HA_READ:
+ case SQLCOM_SHOW_AUTHORS:
+ case SQLCOM_SHOW_BINLOGS:
+ case SQLCOM_SHOW_BINLOG_EVENTS:
+ case SQLCOM_SHOW_RELAYLOG_EVENTS:
+ case SQLCOM_SHOW_CHARSETS:
+ case SQLCOM_SHOW_COLLATIONS:
+ case SQLCOM_SHOW_CONTRIBUTORS:
+ case SQLCOM_SHOW_CREATE:
+ case SQLCOM_SHOW_CREATE_DB:
+ case SQLCOM_SHOW_CREATE_FUNC:
+ case SQLCOM_SHOW_CREATE_PROC:
+ case SQLCOM_SHOW_CREATE_EVENT:
+ case SQLCOM_SHOW_CREATE_TRIGGER:
+ case SQLCOM_SHOW_DATABASES:
+ case SQLCOM_SHOW_ERRORS:
+ case SQLCOM_SHOW_EXPLAIN:
+ case SQLCOM_SHOW_FIELDS:
+ case SQLCOM_SHOW_FUNC_CODE:
+ case SQLCOM_SHOW_GRANTS:
+ case SQLCOM_SHOW_ENGINE_STATUS:
+ case SQLCOM_SHOW_ENGINE_LOGS:
+ case SQLCOM_SHOW_ENGINE_MUTEX:
+ case SQLCOM_SHOW_EVENTS:
+ case SQLCOM_SHOW_KEYS:
+ case SQLCOM_SHOW_MASTER_STAT:
+ case SQLCOM_SHOW_OPEN_TABLES:
+ case SQLCOM_SHOW_PRIVILEGES:
+ case SQLCOM_SHOW_PROCESSLIST:
+ case SQLCOM_SHOW_PROC_CODE:
+ case SQLCOM_SHOW_SLAVE_HOSTS:
+ case SQLCOM_SHOW_SLAVE_STAT:
+ case SQLCOM_SHOW_STATUS:
+ case SQLCOM_SHOW_STATUS_FUNC:
+ case SQLCOM_SHOW_STATUS_PROC:
+ case SQLCOM_SHOW_STORAGE_ENGINES:
+ case SQLCOM_SHOW_TABLES:
+ case SQLCOM_SHOW_TABLE_STATUS:
+ case SQLCOM_SHOW_VARIABLES:
+ case SQLCOM_SHOW_WARNS:
+ case SQLCOM_REPAIR:
+ flags= sp_head::MULTI_RESULTS;
+ break;
+ /*
+ EXECUTE statement may return a result set, but doesn't have to.
+ We can't, however, know it in advance, and therefore must add
+ this statement here. This is ok, as is equivalent to a result-set
+ statement within an IF condition.
+ */
+ case SQLCOM_EXECUTE:
+ flags= sp_head::MULTI_RESULTS | sp_head::CONTAINS_DYNAMIC_SQL;
+ break;
+ case SQLCOM_PREPARE:
+ case SQLCOM_DEALLOCATE_PREPARE:
+ flags= sp_head::CONTAINS_DYNAMIC_SQL;
+ break;
+ case SQLCOM_CREATE_TABLE:
+ if (lex->create_info.tmp_table())
+ flags= 0;
+ else
+ flags= sp_head::HAS_COMMIT_OR_ROLLBACK;
+ break;
+ case SQLCOM_DROP_TABLE:
+ if (lex->drop_temporary)
+ flags= 0;
+ else
+ flags= sp_head::HAS_COMMIT_OR_ROLLBACK;
+ break;
+ case SQLCOM_FLUSH:
+ flags= sp_head::HAS_SQLCOM_FLUSH;
+ break;
+ case SQLCOM_RESET:
+ flags= sp_head::HAS_SQLCOM_RESET;
+ break;
+ case SQLCOM_CREATE_INDEX:
+ case SQLCOM_CREATE_DB:
+ case SQLCOM_CREATE_VIEW:
+ case SQLCOM_CREATE_TRIGGER:
+ case SQLCOM_CREATE_USER:
+ case SQLCOM_CREATE_ROLE:
+ case SQLCOM_ALTER_TABLE:
+ case SQLCOM_GRANT:
+ case SQLCOM_GRANT_ROLE:
+ case SQLCOM_REVOKE:
+ case SQLCOM_REVOKE_ROLE:
+ case SQLCOM_BEGIN:
+ case SQLCOM_RENAME_TABLE:
+ case SQLCOM_RENAME_USER:
+ case SQLCOM_DROP_INDEX:
+ case SQLCOM_DROP_DB:
+ case SQLCOM_REVOKE_ALL:
+ case SQLCOM_DROP_USER:
+ case SQLCOM_DROP_ROLE:
+ case SQLCOM_DROP_VIEW:
+ case SQLCOM_DROP_TRIGGER:
+ case SQLCOM_TRUNCATE:
+ case SQLCOM_COMMIT:
+ case SQLCOM_ROLLBACK:
+ case SQLCOM_LOAD:
+ case SQLCOM_LOCK_TABLES:
+ case SQLCOM_CREATE_PROCEDURE:
+ case SQLCOM_CREATE_SPFUNCTION:
+ case SQLCOM_ALTER_PROCEDURE:
+ case SQLCOM_ALTER_FUNCTION:
+ case SQLCOM_DROP_PROCEDURE:
+ case SQLCOM_DROP_FUNCTION:
+ case SQLCOM_CREATE_EVENT:
+ case SQLCOM_ALTER_EVENT:
+ case SQLCOM_DROP_EVENT:
+ case SQLCOM_INSTALL_PLUGIN:
+ case SQLCOM_UNINSTALL_PLUGIN:
+ flags= sp_head::HAS_COMMIT_OR_ROLLBACK;
+ break;
+ case SQLCOM_DELETE:
+ case SQLCOM_DELETE_MULTI:
+ {
+ /*
+ DELETE normally doesn't return resultset, but there are two exceptions:
+ - DELETE ... RETURNING
+ - EXPLAIN DELETE ...
+ */
+ if (lex->select_lex.item_list.is_empty() && !lex->describe)
+ flags= 0;
+ else
+ flags= sp_head::MULTI_RESULTS;
+ break;
+ }
+ case SQLCOM_UPDATE:
+ case SQLCOM_UPDATE_MULTI:
+ case SQLCOM_INSERT:
+ case SQLCOM_REPLACE:
+ case SQLCOM_REPLACE_SELECT:
+ case SQLCOM_INSERT_SELECT:
+ {
+ if (!lex->describe)
+ flags= 0;
+ else
+ flags= sp_head::MULTI_RESULTS;
+ break;
+ }
+ default:
+ flags= 0;
+ break;
+ }
+ return flags;
+}
+
+/**
+ Prepare an Item for evaluation (call of fix_fields).
+
+ @param thd thread handler
+ @param it_addr pointer on item refernce
+
+ @retval
+ NULL error
+ @retval
+ non-NULL prepared item
+*/
+
+Item *
+sp_prepare_func_item(THD* thd, Item **it_addr)
+{
+ DBUG_ENTER("sp_prepare_func_item");
+ it_addr= (*it_addr)->this_item_addr(thd, it_addr);
+
+ if (!(*it_addr)->fixed &&
+ ((*it_addr)->fix_fields(thd, it_addr) ||
+ (*it_addr)->check_cols(1)))
+ {
+ DBUG_PRINT("info", ("fix_fields() failed"));
+ DBUG_RETURN(NULL);
+ }
+ DBUG_RETURN(*it_addr);
+}
+
+
+/**
+ Evaluate an expression and store the result in the field.
+
+ @param thd current thread object
+ @param result_field the field to store the result
+ @param expr_item_ptr the root item of the expression
+
+ @retval
+ FALSE on success
+ @retval
+ TRUE on error
+*/
+
+bool
+sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr)
+{
+ Item *expr_item;
+ enum_check_fields save_count_cuted_fields= thd->count_cuted_fields;
+ bool save_abort_on_warning= thd->abort_on_warning;
+ bool save_stmt_modified_non_trans_table=
+ thd->transaction.stmt.modified_non_trans_table;
+
+ DBUG_ENTER("sp_eval_expr");
+
+ if (!*expr_item_ptr)
+ goto error;
+
+ if (!(expr_item= sp_prepare_func_item(thd, expr_item_ptr)))
+ goto error;
+
+ /*
+ Set THD flags to emit warnings/errors in case of overflow/type errors
+ during saving the item into the field.
+
+ Save original values and restore them after save.
+ */
+
+ thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
+ thd->abort_on_warning= thd->is_strict_mode();
+ thd->transaction.stmt.modified_non_trans_table= FALSE;
+
+ /* Save the value in the field. Convert the value if needed. */
+
+ expr_item->save_in_field(result_field, 0);
+
+ thd->count_cuted_fields= save_count_cuted_fields;
+ thd->abort_on_warning= save_abort_on_warning;
+ thd->transaction.stmt.modified_non_trans_table= save_stmt_modified_non_trans_table;
+
+ if (!thd->is_error())
+ DBUG_RETURN(FALSE);
+
+error:
+ /*
+ In case of error during evaluation, leave the result field set to NULL.
+ Sic: we can't do it in the beginning of the function because the
+ result field might be needed for its own re-evaluation, e.g. case of
+ set x = x + 1;
+ */
+ result_field->set_null();
+ DBUG_RETURN (TRUE);
+}
+
+
+/**
+ Create temporary sp_name object from MDL key.
+
+ @note The lifetime of this object is bound to the lifetime of the MDL_key.
+ This should be fine as sp_name objects created by this constructor
+ are mainly used for SP-cache lookups.
+
+ @param key MDL key containing database and routine name.
+ @param qname_buff Buffer to be used for storing quoted routine name
+ (should be at least 2*NAME_LEN+1+1 bytes).
+*/
+
+sp_name::sp_name(const MDL_key *key, char *qname_buff)
+{
+ m_db.str= (char*)key->db_name();
+ m_db.length= key->db_name_length();
+ m_name.str= (char*)key->name();
+ m_name.length= key->name_length();
+ m_qname.str= qname_buff;
+ if (m_db.length)
+ {
+ strxmov(qname_buff, m_db.str, ".", m_name.str, NullS);
+ m_qname.length= m_db.length + 1 + m_name.length;
+ }
+ else
+ {
+ strmov(qname_buff, m_name.str);
+ m_qname.length= m_name.length;
+ }
+ m_explicit_name= false;
+}
+
+
+/**
+ Init the qualified name from the db and name.
+*/
+void
+sp_name::init_qname(THD *thd)
+{
+ const uint dot= !!m_db.length;
+ /* m_qname format: [database + dot] + name + '\0' */
+ m_qname.length= m_db.length + dot + m_name.length;
+ if (!(m_qname.str= (char*) thd->alloc(m_qname.length + 1)))
+ return;
+ sprintf(m_qname.str, "%.*s%.*s%.*s",
+ (int) m_db.length, (m_db.length ? m_db.str : ""),
+ dot, ".",
+ (int) m_name.length, m_name.str);
+ DBUG_ASSERT(ok_for_lower_case_names(m_db.str));
+}
+
+
+/**
+ Check that the name 'ident' is ok. It's assumed to be an 'ident'
+ from the parser, so we only have to check length and trailing spaces.
+ The former is a standard requirement (and 'show status' assumes a
+ non-empty name), the latter is a mysql:ism as trailing spaces are
+ removed by get_field().
+
+ @retval
+ TRUE bad name
+ @retval
+ FALSE name is ok
+*/
+
+bool
+check_routine_name(LEX_STRING *ident)
+{
+ if (!ident || !ident->str || !ident->str[0] ||
+ ident->str[ident->length-1] == ' ')
+ {
+ my_error(ER_SP_WRONG_NAME, MYF(0), ident->str);
+ return TRUE;
+ }
+ if (check_string_char_length(ident, "", NAME_CHAR_LEN,
+ system_charset_info, 1))
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), ident->str);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/*
+ *
+ * sp_head
+ *
+ */
+
+void *
+sp_head::operator new(size_t size) throw()
+{
+ DBUG_ENTER("sp_head::operator new");
+ MEM_ROOT own_root;
+ sp_head *sp;
+
+ init_sql_alloc(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC, MYF(0));
+ sp= (sp_head *) alloc_root(&own_root, size);
+ if (sp == NULL)
+ DBUG_RETURN(NULL);
+ sp->main_mem_root= own_root;
+ DBUG_PRINT("info", ("mem_root 0x%lx", (ulong) &sp->mem_root));
+ DBUG_RETURN(sp);
+}
+
+void
+sp_head::operator delete(void *ptr, size_t size) throw()
+{
+ DBUG_ENTER("sp_head::operator delete");
+ MEM_ROOT own_root;
+
+ if (ptr == NULL)
+ DBUG_VOID_RETURN;
+
+ sp_head *sp= (sp_head *) ptr;
+
+ /* Make a copy of main_mem_root as free_root will free the sp */
+ own_root= sp->main_mem_root;
+ DBUG_PRINT("info", ("mem_root 0x%lx moved to 0x%lx",
+ (ulong) &sp->mem_root, (ulong) &own_root));
+ free_root(&own_root, MYF(0));
+
+ DBUG_VOID_RETURN;
+}
+
+
+sp_head::sp_head()
+ :Query_arena(&main_mem_root, STMT_INITIALIZED_FOR_SP),
+ m_flags(0),
+ m_sp_cache_version(0),
+ unsafe_flags(0),
+ m_recursion_level(0),
+ m_next_cached_sp(0),
+ m_cont_level(0)
+{
+ const LEX_STRING str_reset= { NULL, 0 };
+
+ m_first_instance= this;
+ m_first_free_instance= this;
+ m_last_cached_sp= this;
+
+ m_return_field_def.charset = NULL;
+ /*
+ FIXME: the only use case when name is NULL is events, and it should
+ be rewritten soon. Remove the else part and replace 'if' with
+ an assert when this is done.
+ */
+ m_db= m_name= m_qname= str_reset;
+
+ DBUG_ENTER("sp_head::sp_head");
+
+ m_backpatch.empty();
+ m_cont_backpatch.empty();
+ m_lex.empty();
+ my_hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0);
+ my_hash_init(&m_sroutines, system_charset_info, 0, 0, 0, sp_sroutine_key,
+ 0, 0);
+
+ m_body_utf8.str= NULL;
+ m_body_utf8.length= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+
+void
+sp_head::init(LEX *lex)
+{
+ DBUG_ENTER("sp_head::init");
+
+ lex->spcont= m_pcont= new sp_pcontext();
+
+ if (!lex->spcont)
+ DBUG_VOID_RETURN;
+
+ /*
+ Altough trg_table_fields list is used only in triggers we init for all
+ types of stored procedures to simplify reset_lex()/restore_lex() code.
+ */
+ lex->trg_table_fields.empty();
+ my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8, MYF(0));
+
+ m_param_begin= NULL;
+ m_param_end= NULL;
+
+ m_body_begin= NULL ;
+
+ m_qname.str= NULL;
+ m_qname.length= 0;
+
+ m_explicit_name= false;
+
+ m_db.str= NULL;
+ m_db.length= 0;
+
+ m_name.str= NULL;
+ m_name.length= 0;
+
+ m_params.str= NULL;
+ m_params.length= 0;
+
+ m_body.str= NULL;
+ m_body.length= 0;
+
+ m_defstr.str= NULL;
+ m_defstr.length= 0;
+
+ m_return_field_def.charset= NULL;
+
+ DBUG_VOID_RETURN;
+}
+
+
+void
+sp_head::init_sp_name(THD *thd, sp_name *spname)
+{
+ DBUG_ENTER("sp_head::init_sp_name");
+
+ /* Must be initialized in the parser. */
+
+ DBUG_ASSERT(spname && spname->m_db.str && spname->m_db.length);
+
+ /* We have to copy strings to get them into the right memroot. */
+
+ m_db.length= spname->m_db.length;
+ m_db.str= strmake_root(thd->mem_root, spname->m_db.str, spname->m_db.length);
+
+ m_name.length= spname->m_name.length;
+ m_name.str= strmake_root(thd->mem_root, spname->m_name.str,
+ spname->m_name.length);
+
+ m_explicit_name= spname->m_explicit_name;
+
+ if (spname->m_qname.length == 0)
+ spname->init_qname(thd);
+
+ m_qname.length= spname->m_qname.length;
+ m_qname.str= (char*) memdup_root(thd->mem_root,
+ spname->m_qname.str,
+ spname->m_qname.length + 1);
+
+ DBUG_VOID_RETURN;
+}
+
+
+void
+sp_head::set_body_start(THD *thd, const char *begin_ptr)
+{
+ m_body_begin= begin_ptr;
+ thd->m_parser_state->m_lip.body_utf8_start(thd, begin_ptr);
+}
+
+
+void
+sp_head::set_stmt_end(THD *thd)
+{
+ Lex_input_stream *lip= & thd->m_parser_state->m_lip; /* shortcut */
+ const char *end_ptr= lip->get_cpp_ptr(); /* shortcut */
+
+ /* Make the string of parameters. */
+
+ if (m_param_begin && m_param_end)
+ {
+ m_params.length= m_param_end - m_param_begin;
+ m_params.str= thd->strmake(m_param_begin, m_params.length);
+ }
+
+ /* Remember end pointer for further dumping of whole statement. */
+
+ thd->lex->stmt_definition_end= end_ptr;
+
+ /* Make the string of body (in the original character set). */
+
+ m_body.length= end_ptr - m_body_begin;
+ m_body.str= thd->strmake(m_body_begin, m_body.length);
+ trim_whitespace(thd->charset(), & m_body);
+
+ /* Make the string of UTF-body. */
+
+ lip->body_utf8_append(end_ptr);
+
+ m_body_utf8.length= lip->get_body_utf8_length();
+ m_body_utf8.str= thd->strmake(lip->get_body_utf8_str(), m_body_utf8.length);
+ trim_whitespace(thd->charset(), & m_body_utf8);
+
+ /*
+ Make the string of whole stored-program-definition query (in the
+ original character set).
+ */
+
+ m_defstr.length= end_ptr - lip->get_cpp_buf();
+ m_defstr.str= thd->strmake(lip->get_cpp_buf(), m_defstr.length);
+ trim_whitespace(thd->charset(), & m_defstr);
+}
+
+
+static TYPELIB *
+create_typelib(MEM_ROOT *mem_root, Create_field *field_def, List<String> *src)
+{
+ TYPELIB *result= NULL;
+ CHARSET_INFO *cs= field_def->charset;
+ DBUG_ENTER("create_typelib");
+
+ if (src->elements)
+ {
+ result= (TYPELIB*) alloc_root(mem_root, sizeof(TYPELIB));
+ result->count= src->elements;
+ result->name= "";
+ if (!(result->type_names=(const char **)
+ alloc_root(mem_root,(sizeof(char *)+sizeof(int))*(result->count+1))))
+ DBUG_RETURN(0);
+ result->type_lengths= (uint*)(result->type_names + result->count+1);
+ List_iterator<String> it(*src);
+ String conv;
+ for (uint i=0; i < result->count; i++)
+ {
+ uint32 dummy;
+ uint length;
+ String *tmp= it++;
+
+ if (String::needs_conversion(tmp->length(), tmp->charset(),
+ cs, &dummy))
+ {
+ uint cnv_errs;
+ conv.copy(tmp->ptr(), tmp->length(), tmp->charset(), cs, &cnv_errs);
+
+ length= conv.length();
+ result->type_names[i]= (char*) strmake_root(mem_root, conv.ptr(),
+ length);
+ }
+ else
+ {
+ length= tmp->length();
+ result->type_names[i]= strmake_root(mem_root, tmp->ptr(), length);
+ }
+
+ // Strip trailing spaces.
+ length= cs->cset->lengthsp(cs, result->type_names[i], length);
+ result->type_lengths[i]= length;
+ ((uchar *)result->type_names[i])[length]= '\0';
+ }
+ result->type_names[result->count]= 0;
+ result->type_lengths[result->count]= 0;
+ }
+ DBUG_RETURN(result);
+}
+
+
+sp_head::~sp_head()
+{
+ LEX *lex;
+ sp_instr *i;
+ DBUG_ENTER("sp_head::~sp_head");
+
+ /* sp_head::restore_thd_mem_root() must already have been called. */
+ DBUG_ASSERT(m_thd == NULL);
+
+ for (uint ip = 0 ; (i = get_instr(ip)) ; ip++)
+ delete i;
+ delete_dynamic(&m_instr);
+ delete m_pcont;
+ free_items();
+
+ /*
+ If we have non-empty LEX stack then we just came out of parser with
+ error. Now we should delete all auxilary LEXes and restore original
+ THD::lex. It is safe to not update LEX::ptr because further query
+ string parsing and execution will be stopped anyway.
+ */
+ while ((lex= (LEX *)m_lex.pop()))
+ {
+ THD *thd= lex->thd;
+ thd->lex->sphead= NULL;
+ lex_end(thd->lex);
+ delete thd->lex;
+ thd->lex= lex;
+ }
+
+ my_hash_free(&m_sptabs);
+ my_hash_free(&m_sroutines);
+
+ delete m_next_cached_sp;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ This is only used for result fields from functions (both during
+ fix_length_and_dec() and evaluation).
+*/
+
+Field *
+sp_head::create_result_field(uint field_max_length, const char *field_name,
+ TABLE *table)
+{
+ uint field_length;
+ Field *field;
+
+ DBUG_ENTER("sp_head::create_result_field");
+
+ field_length= !m_return_field_def.length ?
+ field_max_length : m_return_field_def.length;
+
+ field= ::make_field(table->s, /* TABLE_SHARE ptr */
+ (uchar*) 0, /* field ptr */
+ field_length, /* field [max] length */
+ (uchar*) "", /* null ptr */
+ 0, /* null bit */
+ m_return_field_def.pack_flag,
+ m_return_field_def.sql_type,
+ m_return_field_def.charset,
+ m_return_field_def.geom_type,
+ Field::NONE, /* unreg check */
+ m_return_field_def.interval,
+ field_name ? field_name : (const char *) m_name.str);
+
+ field->vcol_info= m_return_field_def.vcol_info;
+ field->stored_in_db= m_return_field_def.stored_in_db;
+ if (field)
+ field->init(table);
+
+ DBUG_RETURN(field);
+}
+
+
+int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b)
+{
+ return (int)((*a)->pos_in_query - (*b)->pos_in_query);
+}
+
+
+/*
+ StoredRoutinesBinlogging
+ This paragraph applies only to statement-based binlogging. Row-based
+ binlogging does not need anything special like this.
+
+ Top-down overview:
+
+ 1. Statements
+
+ Statements that have is_update_query(stmt) == TRUE are written into the
+ binary log verbatim.
+ Examples:
+ UPDATE tbl SET tbl.x = spfunc_w_side_effects()
+ UPDATE tbl SET tbl.x=1 WHERE spfunc_w_side_effect_that_returns_false(tbl.y)
+
+ Statements that have is_update_query(stmt) == FALSE (e.g. SELECTs) are not
+ written into binary log. Instead we catch function calls the statement
+ makes and write it into binary log separately (see #3).
+
+ 2. PROCEDURE calls
+
+ CALL statements are not written into binary log. Instead
+ * Any FUNCTION invocation (in SET, IF, WHILE, OPEN CURSOR and other SP
+ instructions) is written into binlog separately.
+
+ * Each statement executed in SP is binlogged separately, according to rules
+ in #1, with the exception that we modify query string: we replace uses
+ of SP local variables with NAME_CONST('spvar_name', <spvar-value>) calls.
+ This substitution is done in subst_spvars().
+
+ 3. FUNCTION calls
+
+ In sp_head::execute_function(), we check
+ * If this function invocation is done from a statement that is written
+ into the binary log.
+ * If there were any attempts to write events to the binary log during
+ function execution (grep for start_union_events and stop_union_events)
+
+ If the answers are No and Yes, we write the function call into the binary
+ log as "SELECT spfunc(<param1value>, <param2value>, ...)"
+
+
+ 4. Miscellaneous issues.
+
+ 4.1 User variables.
+
+ When we call mysql_bin_log.write() for an SP statement, thd->user_var_events
+ must hold set<{var_name, value}> pairs for all user variables used during
+ the statement execution.
+ This set is produced by tracking user variable reads during statement
+ execution.
+
+ For SPs, this has the following implications:
+ 1) thd->user_var_events may contain events from several SP statements and
+ needs to be valid after exection of these statements was finished. In
+ order to achieve that, we
+ * Allocate user_var_events array elements on appropriate mem_root (grep
+ for user_var_events_alloc).
+ * Use is_query_in_union() to determine if user_var_event is created.
+
+ 2) We need to empty thd->user_var_events after we have wrote a function
+ call. This is currently done by making
+ reset_dynamic(&thd->user_var_events);
+ calls in several different places. (TODO cosider moving this into
+ mysql_bin_log.write() function)
+
+ 4.2 Auto_increment storage in binlog
+
+ As we may write two statements to binlog from one single logical statement
+ (case of "SELECT func1(),func2()": it is binlogged as "SELECT func1()" and
+ then "SELECT func2()"), we need to reset auto_increment binlog variables
+ after each binlogged SELECT. Otherwise, the auto_increment value of the
+ first SELECT would be used for the second too.
+*/
+
+
+/**
+ Replace thd->query{_length} with a string that one can write to
+ the binlog.
+
+ The binlog-suitable string is produced by replacing references to SP local
+ variables with NAME_CONST('sp_var_name', value) calls.
+
+ @param thd Current thread.
+ @param instr Instruction (we look for Item_splocal instances in
+ instr->free_list)
+ @param query_str Original query string
+
+ @return
+ - FALSE on success.
+ thd->query{_length} either has been appropriately replaced or there
+ is no need for replacements.
+ - TRUE out of memory error.
+*/
+
+static bool
+subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str)
+{
+ DBUG_ENTER("subst_spvars");
+
+ Dynamic_array<Item_splocal*> sp_vars_uses;
+ char *pbuf, *cur, buffer[512];
+ String qbuf(buffer, sizeof(buffer), &my_charset_bin);
+ int prev_pos, res, buf_len;
+
+ /* Find all instances of Item_splocal used in this statement */
+ for (Item *item= instr->free_list; item; item= item->next)
+ {
+ if (item->is_splocal())
+ {
+ Item_splocal *item_spl= (Item_splocal*)item;
+ if (item_spl->pos_in_query)
+ sp_vars_uses.append(item_spl);
+ }
+ }
+ if (!sp_vars_uses.elements())
+ DBUG_RETURN(FALSE);
+
+ /* Sort SP var refs by their occurences in the query */
+ sp_vars_uses.sort(cmp_splocal_locations);
+
+ /*
+ Construct a statement string where SP local var refs are replaced
+ with "NAME_CONST(name, value)"
+ */
+ qbuf.length(0);
+ cur= query_str->str;
+ prev_pos= res= 0;
+ thd->query_name_consts= 0;
+
+ for (Item_splocal **splocal= sp_vars_uses.front();
+ splocal <= sp_vars_uses.back(); splocal++)
+ {
+ Item *val;
+
+ char str_buffer[STRING_BUFFER_USUAL_SIZE];
+ String str_value_holder(str_buffer, sizeof(str_buffer),
+ &my_charset_latin1);
+ String *str_value;
+
+ /* append the text between sp ref occurences */
+ res|= qbuf.append(cur + prev_pos, (*splocal)->pos_in_query - prev_pos);
+ prev_pos= (*splocal)->pos_in_query + (*splocal)->len_in_query;
+
+ res|= (*splocal)->fix_fields(thd, (Item **) splocal);
+ if (res)
+ break;
+
+ if ((*splocal)->limit_clause_param)
+ {
+ res|= qbuf.append_ulonglong((*splocal)->val_uint());
+ if (res)
+ break;
+ continue;
+ }
+
+ /* append the spvar substitute */
+ res|= qbuf.append(STRING_WITH_LEN(" NAME_CONST('"));
+ res|= qbuf.append((*splocal)->m_name.str, (*splocal)->m_name.length);
+ res|= qbuf.append(STRING_WITH_LEN("',"));
+
+ if (res)
+ break;
+
+ val= (*splocal)->this_item();
+ DBUG_PRINT("info", ("print 0x%lx", (long) val));
+ str_value= sp_get_item_value(thd, val, &str_value_holder);
+ if (str_value)
+ res|= qbuf.append(*str_value);
+ else
+ res|= qbuf.append(STRING_WITH_LEN("NULL"));
+ res|= qbuf.append(')');
+ if (res)
+ break;
+
+ thd->query_name_consts++;
+ }
+ if (res ||
+ qbuf.append(cur + prev_pos, query_str->length - prev_pos))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Allocate additional space at the end of the new query string for the
+ query_cache_send_result_to_client function.
+
+ The query buffer layout is:
+ buffer :==
+ <statement> The input statement(s)
+ '\0' Terminating null char
+ <length> Length of following current database name 2
+ <db_name> Name of current database
+ <flags> Flags struct
+ */
+ buf_len= (qbuf.length() + 1 + QUERY_CACHE_DB_LENGTH_SIZE + thd->db_length +
+ QUERY_CACHE_FLAGS_SIZE + 1);
+ if ((pbuf= (char *) alloc_root(thd->mem_root, buf_len)))
+ {
+ char *ptr= pbuf + qbuf.length();
+ memcpy(pbuf, qbuf.ptr(), qbuf.length());
+ *ptr= 0;
+ int2store(ptr+1, thd->db_length);
+ }
+ else
+ DBUG_RETURN(TRUE);
+
+ thd->set_query(pbuf, qbuf.length());
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Return appropriate error about recursion limit reaching
+
+ @param thd Thread handle
+
+ @remark For functions and triggers we return error about
+ prohibited recursion. For stored procedures we
+ return about reaching recursion limit.
+*/
+
+void sp_head::recursion_level_error(THD *thd)
+{
+ if (m_type == TYPE_ENUM_PROCEDURE)
+ {
+ my_error(ER_SP_RECURSION_LIMIT, MYF(0),
+ static_cast<int>(thd->variables.max_sp_recursion_depth),
+ m_name.str);
+ }
+ else
+ my_error(ER_SP_NO_RECURSION, MYF(0));
+}
+
+
+
+/**
+ Execute the routine. The main instruction jump loop is there.
+ Assume the parameters already set.
+
+ @param thd Thread context.
+ @param merge_da_on_success Flag specifying if Warning Info should be
+ propagated to the caller on Completion
+ Condition or not.
+
+ @todo
+ - Will write this SP statement into binlog separately
+ (TODO: consider changing the condition to "not inside event union")
+
+ @return Error status.
+ @retval
+ FALSE on success
+ @retval
+ TRUE on error
+*/
+
+bool
+sp_head::execute(THD *thd, bool merge_da_on_success)
+{
+ DBUG_ENTER("sp_head::execute");
+ char saved_cur_db_name_buf[SAFE_NAME_LEN+1];
+ LEX_STRING saved_cur_db_name=
+ { saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
+ bool cur_db_changed= FALSE;
+ sp_rcontext *ctx= thd->spcont;
+ bool err_status= FALSE;
+ uint ip= 0;
+ ulonglong save_sql_mode;
+ bool save_abort_on_warning;
+ Query_arena *old_arena;
+ /* per-instruction arena */
+ MEM_ROOT execute_mem_root;
+ Query_arena execute_arena(&execute_mem_root, STMT_INITIALIZED_FOR_SP),
+ backup_arena;
+ query_id_t old_query_id;
+ TABLE *old_derived_tables;
+ LEX *old_lex;
+ Item_change_list old_change_list;
+ String old_packet;
+ uint old_server_status;
+ const uint status_backup_mask= SERVER_STATUS_CURSOR_EXISTS |
+ SERVER_STATUS_LAST_ROW_SENT;
+ Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer;
+ Object_creation_ctx *saved_creation_ctx;
+ Diagnostics_area *da= thd->get_stmt_da();
+ Warning_info sp_wi(da->warning_info_id(), false, true);
+
+ /*
+ Just reporting a stack overrun error
+ (@sa check_stack_overrun()) requires stack memory for error
+ message buffer. Thus, we have to put the below check
+ relatively close to the beginning of the execution stack,
+ where available stack margin is still big. As long as the check
+ has to be fairly high up the call stack, the amount of memory
+ we "book" for has to stay fairly high as well, and hence
+ not very accurate. The number below has been calculated
+ by trial and error, and reflects the amount of memory necessary
+ to execute a single stored procedure instruction, be it either
+ an SQL statement, or, heaviest of all, a CALL, which involves
+ parsing and loading of another stored procedure into the cache
+ (@sa db_load_routine() and Bug#10100).
+ At the time of measuring, a recursive SP invocation required
+ 3232 bytes of stack on 32 bit Linux, 6016 bytes on 64 bit Mac
+ and 11152 on 64 bit Solaris sparc.
+ The same with db_load_routine() required circa 7k bytes and
+ 14k bytes accordingly. Hence, here we book the stack with some
+ reasonable margin.
+
+ Reverting back to 8 * STACK_MIN_SIZE until further fix.
+ 8 * STACK_MIN_SIZE is required on some exotic platforms.
+ */
+ if (check_stack_overrun(thd, 8 * STACK_MIN_SIZE, (uchar*)&old_packet))
+ DBUG_RETURN(TRUE);
+
+ /* init per-instruction memroot */
+ init_sql_alloc(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
+
+ DBUG_ASSERT(!(m_flags & IS_INVOKED));
+ m_flags|= IS_INVOKED;
+ m_first_instance->m_first_free_instance= m_next_cached_sp;
+ if (m_next_cached_sp)
+ {
+ DBUG_PRINT("info",
+ ("first free for 0x%lx ++: 0x%lx->0x%lx level: %lu flags %x",
+ (ulong)m_first_instance, (ulong) this,
+ (ulong) m_next_cached_sp,
+ m_next_cached_sp->m_recursion_level,
+ m_next_cached_sp->m_flags));
+ }
+ /*
+ Check that if there are not any instances after this one then
+ pointer to the last instance points on this instance or if there are
+ some instances after this one then recursion level of next instance
+ greater then recursion level of current instance on 1
+ */
+ DBUG_ASSERT((m_next_cached_sp == 0 &&
+ m_first_instance->m_last_cached_sp == this) ||
+ (m_recursion_level + 1 == m_next_cached_sp->m_recursion_level));
+
+ /*
+ NOTE: The SQL Standard does not specify the context that should be
+ preserved for stored routines. However, at SAP/Walldorf meeting it was
+ decided that current database should be preserved.
+ */
+
+ if (m_db.length &&
+ (err_status= mysql_opt_change_db(thd, &m_db, &saved_cur_db_name, FALSE,
+ &cur_db_changed)))
+ {
+ goto done;
+ }
+
+ thd->is_slave_error= 0;
+ old_arena= thd->stmt_arena;
+
+ /* Push a new warning information area. */
+ da->copy_sql_conditions_to_wi(thd, &sp_wi);
+ da->push_warning_info(&sp_wi);
+
+ /*
+ Switch query context. This has to be done early as this is sometimes
+ allocated trough sql_alloc
+ */
+ saved_creation_ctx= m_creation_ctx->set_n_backup(thd);
+
+ /*
+ We have to save/restore this info when we are changing call level to
+ be able properly do close_thread_tables() in instructions.
+ */
+ old_query_id= thd->query_id;
+ old_derived_tables= thd->derived_tables;
+ thd->derived_tables= 0;
+ save_sql_mode= thd->variables.sql_mode;
+ thd->variables.sql_mode= m_sql_mode;
+ save_abort_on_warning= thd->abort_on_warning;
+ thd->abort_on_warning= 0;
+ /**
+ When inside a substatement (a stored function or trigger
+ statement), clear the metadata observer in THD, if any.
+ Remember the value of the observer here, to be able
+ to restore it when leaving the substatement.
+
+ We reset the observer to suppress errors when a substatement
+ uses temporary tables. If a temporary table does not exist
+ at start of the main statement, it's not prelocked
+ and thus is not validated with other prelocked tables.
+
+ Later on, when the temporary table is opened, metadata
+ versions mismatch, expectedly.
+
+ The proper solution for the problem is to re-validate tables
+ of substatements (Bug#12257, Bug#27011, Bug#32868, Bug#33000),
+ but it's not implemented yet.
+ */
+ thd->m_reprepare_observer= 0;
+
+ /*
+ It is also more efficient to save/restore current thd->lex once when
+ do it in each instruction
+ */
+ old_lex= thd->lex;
+ /*
+ We should also save Item tree change list to avoid rollback something
+ too early in the calling query.
+ */
+ thd->change_list.move_elements_to(&old_change_list);
+ /*
+ Cursors will use thd->packet, so they may corrupt data which was prepared
+ for sending by upper level. OTOH cursors in the same routine can share this
+ buffer safely so let use use routine-local packet instead of having own
+ packet buffer for each cursor.
+
+ It is probably safe to use same thd->convert_buff everywhere.
+ */
+ old_packet.swap(thd->packet);
+ old_server_status= thd->server_status & status_backup_mask;
+
+ /*
+ Switch to per-instruction arena here. We can do it since we cleanup
+ arena after every instruction.
+ */
+ thd->set_n_backup_active_arena(&execute_arena, &backup_arena);
+
+ /*
+ Save callers arena in order to store instruction results and out
+ parameters in it later during sp_eval_func_item()
+ */
+ thd->spcont->callers_arena= &backup_arena;
+
+#if defined(ENABLED_PROFILING)
+ /* Discard the initial part of executing routines. */
+ thd->profiling.discard_current_query();
+#endif
+ DEBUG_SYNC(thd, "sp_head_execute_before_loop");
+ do
+ {
+ sp_instr *i;
+
+#if defined(ENABLED_PROFILING)
+ /*
+ Treat each "instr" of a routine as discrete unit that could be profiled.
+ Profiling only records information for segments of code that set the
+ source of the query, and almost all kinds of instructions in s-p do not.
+ */
+ thd->profiling.finish_current_query();
+ thd->profiling.start_new_query("continuing inside routine");
+#endif
+
+ /* get_instr returns NULL when we're done. */
+ i = get_instr(ip);
+ if (i == NULL)
+ {
+#if defined(ENABLED_PROFILING)
+ thd->profiling.discard_current_query();
+#endif
+ break;
+ }
+
+ /* Reset number of warnings for this query. */
+ thd->get_stmt_da()->reset_for_next_command();
+
+ DBUG_PRINT("execute", ("Instruction %u", ip));
+
+ /*
+ We need to reset start_time to allow for time to flow inside a stored
+ procedure. This is only done for SP since time is suppose to be constant
+ during execution of triggers and functions.
+ */
+ reset_start_time_for_sp(thd);
+
+ /*
+ We have to set thd->stmt_arena before executing the instruction
+ to store in the instruction free_list all new items, created
+ during the first execution (for example expanding of '*' or the
+ items made during other permanent subquery transformations).
+ */
+ thd->stmt_arena= i;
+
+ /*
+ Will write this SP statement into binlog separately.
+ TODO: consider changing the condition to "not inside event union".
+ */
+ MEM_ROOT *user_var_events_alloc_saved= thd->user_var_events_alloc;
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ thd->user_var_events_alloc= thd->mem_root;
+
+ sql_digest_state *parent_digest= thd->m_digest;
+ thd->m_digest= NULL;
+
+ err_status= i->execute(thd, &ip);
+
+ thd->m_digest= parent_digest;
+
+ if (i->free_list)
+ cleanup_items(i->free_list);
+
+ /*
+ If we've set thd->user_var_events_alloc to mem_root of this SP
+ statement, clean all the events allocated in it.
+ */
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ {
+ reset_dynamic(&thd->user_var_events);
+ thd->user_var_events_alloc= user_var_events_alloc_saved;
+ }
+
+ /* we should cleanup free_list and memroot, used by instruction */
+ thd->cleanup_after_query();
+ free_root(&execute_mem_root, MYF(0));
+
+ /*
+ Find and process SQL handlers unless it is a fatal error (fatal
+ errors are not catchable by SQL handlers) or the connection has been
+ killed during execution.
+ */
+ if (!thd->is_fatal_error && !thd->killed_errno() &&
+ ctx->handle_sql_condition(thd, &ip, i))
+ {
+ err_status= FALSE;
+ }
+
+ /* Reset sp_rcontext::end_partial_result_set flag. */
+ ctx->end_partial_result_set= FALSE;
+
+ } while (!err_status && !thd->killed && !thd->is_fatal_error);
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.finish_current_query();
+ thd->profiling.start_new_query("tail end of routine");
+#endif
+
+ /* Restore query context. */
+
+ m_creation_ctx->restore_env(thd, saved_creation_ctx);
+
+ /* Restore arena. */
+
+ thd->restore_active_arena(&execute_arena, &backup_arena);
+
+ thd->spcont->pop_all_cursors(); // To avoid memory leaks after an error
+
+ /* Restore all saved */
+ thd->server_status= (thd->server_status & ~status_backup_mask) | old_server_status;
+ old_packet.swap(thd->packet);
+ DBUG_ASSERT(thd->change_list.is_empty());
+ old_change_list.move_elements_to(&thd->change_list);
+ thd->lex= old_lex;
+ thd->set_query_id(old_query_id);
+ DBUG_ASSERT(!thd->derived_tables);
+ thd->derived_tables= old_derived_tables;
+ thd->variables.sql_mode= save_sql_mode;
+ thd->abort_on_warning= save_abort_on_warning;
+ thd->m_reprepare_observer= save_reprepare_observer;
+
+ thd->stmt_arena= old_arena;
+ state= STMT_EXECUTED;
+
+ /*
+ Restore the caller's original warning information area:
+ - warnings generated during trigger execution should not be
+ propagated to the caller on success;
+ - if there was an exception during execution, warning info should be
+ propagated to the caller in any case.
+ */
+ da->pop_warning_info();
+
+ if (err_status || merge_da_on_success)
+ {
+ /*
+ If a routine body is empty or if a routine did not generate any warnings,
+ do not duplicate our own contents by appending the contents of the called
+ routine. We know that the called routine did not change its warning info.
+
+ On the other hand, if the routine body is not empty and some statement in
+ the routine generates a warning or uses tables, warning info is guaranteed
+ to have changed. In this case we know that the routine warning info
+ contains only new warnings, and thus we perform a copy.
+ */
+ if (da->warning_info_changed(&sp_wi))
+ {
+ /*
+ If the invocation of the routine was a standalone statement,
+ rather than a sub-statement, in other words, if it's a CALL
+ of a procedure, rather than invocation of a function or a
+ trigger, we need to clear the current contents of the caller's
+ warning info.
+
+ This is per MySQL rules: if a statement generates a warning,
+ warnings from the previous statement are flushed. Normally
+ it's done in push_warning(). However, here we don't use
+ push_warning() to avoid invocation of condition handlers or
+ escalation of warnings to errors.
+ */
+ da->opt_clear_warning_info(thd->query_id);
+ da->copy_sql_conditions_from_wi(thd, &sp_wi);
+ da->remove_marked_sql_conditions();
+ }
+ }
+
+ done:
+ DBUG_PRINT("info", ("err_status: %d killed: %d is_slave_error: %d report_error: %d",
+ err_status, thd->killed, thd->is_slave_error,
+ thd->is_error()));
+
+ if (thd->killed)
+ err_status= TRUE;
+ /*
+ If the DB has changed, the pointer has changed too, but the
+ original thd->db will then have been freed
+ */
+ if (cur_db_changed && thd->killed != KILL_CONNECTION)
+ {
+ /*
+ Force switching back to the saved current database, because it may be
+ NULL. In this case, mysql_change_db() would generate an error.
+ */
+
+ err_status|= mysql_change_db(thd, &saved_cur_db_name, TRUE);
+ }
+ m_flags&= ~IS_INVOKED;
+ DBUG_PRINT("info",
+ ("first free for 0x%lx --: 0x%lx->0x%lx, level: %lu, flags %x",
+ (ulong) m_first_instance,
+ (ulong) m_first_instance->m_first_free_instance,
+ (ulong) this, m_recursion_level, m_flags));
+ /*
+ Check that we have one of following:
+
+ 1) there are not free instances which means that this instance is last
+ in the list of instances (pointer to the last instance point on it and
+ ther are not other instances after this one in the list)
+
+ 2) There are some free instances which mean that first free instance
+ should go just after this one and recursion level of that free instance
+ should be on 1 more then recursion level of this instance.
+ */
+ DBUG_ASSERT((m_first_instance->m_first_free_instance == 0 &&
+ this == m_first_instance->m_last_cached_sp &&
+ m_next_cached_sp == 0) ||
+ (m_first_instance->m_first_free_instance != 0 &&
+ m_first_instance->m_first_free_instance == m_next_cached_sp &&
+ m_first_instance->m_first_free_instance->m_recursion_level ==
+ m_recursion_level + 1));
+ m_first_instance->m_first_free_instance= this;
+
+ DBUG_RETURN(err_status);
+}
+
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+/**
+ set_routine_security_ctx() changes routine security context, and
+ checks if there is an EXECUTE privilege in new context. If there is
+ no EXECUTE privilege, it changes the context back and returns a
+ error.
+
+ @param thd thread handle
+ @param sp stored routine to change the context for
+ @param is_proc TRUE is procedure, FALSE if function
+ @param save_ctx pointer to an old security context
+
+ @todo
+ - Cache if the definer has the right to use the object on the
+ first usage and only reset the cache if someone does a GRANT
+ statement that 'may' affect this.
+
+ @retval
+ TRUE if there was a error, and the context wasn't changed.
+ @retval
+ FALSE if the context was changed.
+*/
+
+bool
+set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc,
+ Security_context **save_ctx)
+{
+ *save_ctx= 0;
+ if (sp->m_chistics->suid != SP_IS_NOT_SUID &&
+ sp->m_security_ctx.change_security_context(thd, &sp->m_definer_user,
+ &sp->m_definer_host,
+ &sp->m_db,
+ save_ctx))
+ return TRUE;
+
+ /*
+ If we changed context to run as another user, we need to check the
+ access right for the new context again as someone may have revoked
+ the right to use the procedure from this user.
+
+ TODO:
+ Cache if the definer has the right to use the object on the
+ first usage and only reset the cache if someone does a GRANT
+ statement that 'may' affect this.
+ */
+ if (*save_ctx &&
+ check_routine_access(thd, EXECUTE_ACL,
+ sp->m_db.str, sp->m_name.str, is_proc, FALSE))
+ {
+ sp->m_security_ctx.restore_security_context(thd, *save_ctx);
+ *save_ctx= 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif // ! NO_EMBEDDED_ACCESS_CHECKS
+
+
+/**
+ Execute trigger stored program.
+
+ - changes security context for triggers
+ - switch to new memroot
+ - call sp_head::execute
+ - restore old memroot
+ - restores security context
+
+ @param thd Thread handle
+ @param db database name
+ @param table table name
+ @param grant_info GRANT_INFO structure to be filled with
+ information about definer's privileges
+ on subject table
+
+ @todo
+ - TODO: we should create sp_rcontext once per command and reuse it
+ on subsequent executions of a trigger.
+
+ @retval
+ FALSE on success
+ @retval
+ TRUE on error
+*/
+
+bool
+sp_head::execute_trigger(THD *thd,
+ const LEX_STRING *db_name,
+ const LEX_STRING *table_name,
+ GRANT_INFO *grant_info)
+{
+ sp_rcontext *octx = thd->spcont;
+ sp_rcontext *nctx = NULL;
+ bool err_status= FALSE;
+ MEM_ROOT call_mem_root;
+ Query_arena call_arena(&call_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP);
+ Query_arena backup_arena;
+
+ DBUG_ENTER("sp_head::execute_trigger");
+ DBUG_PRINT("info", ("trigger %s", m_name.str));
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *save_ctx= NULL;
+
+
+ if (m_chistics->suid != SP_IS_NOT_SUID &&
+ m_security_ctx.change_security_context(thd,
+ &m_definer_user,
+ &m_definer_host,
+ &m_db,
+ &save_ctx))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Fetch information about table-level privileges for subject table into
+ GRANT_INFO instance. The access check itself will happen in
+ Item_trigger_field, where this information will be used along with
+ information about column-level privileges.
+ */
+
+ fill_effective_table_privileges(thd,
+ grant_info,
+ db_name->str,
+ table_name->str);
+
+ /* Check that the definer has TRIGGER privilege on the subject table. */
+
+ if (!(grant_info->privilege & TRIGGER_ACL))
+ {
+ char priv_desc[128];
+ get_privilege_desc(priv_desc, sizeof(priv_desc), TRIGGER_ACL);
+
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), priv_desc,
+ thd->security_ctx->priv_user, thd->security_ctx->host_or_ip,
+ table_name->str);
+
+ m_security_ctx.restore_security_context(thd, save_ctx);
+ DBUG_RETURN(TRUE);
+ }
+#endif // NO_EMBEDDED_ACCESS_CHECKS
+
+ /*
+ Prepare arena and memroot for objects which lifetime is whole
+ duration of trigger call (sp_rcontext, it's tables and items,
+ sp_cursor and Item_cache holders for case expressions). We can't
+ use caller's arena/memroot for those objects because in this case
+ some fixed amount of memory will be consumed for each trigger
+ invocation and so statements which involve lot of them will hog
+ memory.
+
+ TODO: we should create sp_rcontext once per command and reuse it
+ on subsequent executions of a trigger.
+ */
+ init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
+ thd->set_n_backup_active_arena(&call_arena, &backup_arena);
+
+ if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL)))
+ {
+ err_status= TRUE;
+ goto err_with_cleanup;
+ }
+
+#ifndef DBUG_OFF
+ nctx->sp= this;
+#endif
+
+ thd->spcont= nctx;
+
+ err_status= execute(thd, FALSE);
+
+err_with_cleanup:
+ thd->restore_active_arena(&call_arena, &backup_arena);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ m_security_ctx.restore_security_context(thd, save_ctx);
+#endif // NO_EMBEDDED_ACCESS_CHECKS
+
+ delete nctx;
+ call_arena.free_items();
+ free_root(&call_mem_root, MYF(0));
+ thd->spcont= octx;
+
+ if (thd->killed)
+ thd->send_kill_message();
+
+ DBUG_RETURN(err_status);
+}
+
+
+/**
+ Execute a function.
+
+ - evaluate parameters
+ - changes security context for SUID routines
+ - switch to new memroot
+ - call sp_head::execute
+ - restore old memroot
+ - evaluate the return value
+ - restores security context
+
+ @param thd Thread handle
+ @param argp Passed arguments (these are items from containing
+ statement?)
+ @param argcount Number of passed arguments. We need to check if
+ this is correct.
+ @param return_value_fld Save result here.
+
+ @todo
+ We should create sp_rcontext once per command and reuse
+ it on subsequent executions of a function/trigger.
+
+ @todo
+ In future we should associate call arena/mem_root with
+ sp_rcontext and allocate all these objects (and sp_rcontext
+ itself) on it directly rather than juggle with arenas.
+
+ @retval
+ FALSE on success
+ @retval
+ TRUE on error
+*/
+
+bool
+sp_head::execute_function(THD *thd, Item **argp, uint argcount,
+ Field *return_value_fld)
+{
+ ulonglong binlog_save_options;
+ bool need_binlog_call= FALSE;
+ uint arg_no;
+ sp_rcontext *octx = thd->spcont;
+ sp_rcontext *nctx = NULL;
+ char buf[STRING_BUFFER_USUAL_SIZE];
+ String binlog_buf(buf, sizeof(buf), &my_charset_bin);
+ bool err_status= FALSE;
+ MEM_ROOT call_mem_root;
+ Query_arena call_arena(&call_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP);
+ Query_arena backup_arena;
+ DBUG_ENTER("sp_head::execute_function");
+ DBUG_PRINT("info", ("function %s", m_name.str));
+
+ LINT_INIT(binlog_save_options);
+
+ /*
+ Check that the function is called with all specified arguments.
+
+ If it is not, use my_error() to report an error, or it will not terminate
+ the invoking query properly.
+ */
+ if (argcount != m_pcont->context_var_count())
+ {
+ /*
+ Need to use my_error here, or it will not terminate the
+ invoking query properly.
+ */
+ my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0),
+ "FUNCTION", m_qname.str, m_pcont->context_var_count(), argcount);
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Prepare arena and memroot for objects which lifetime is whole
+ duration of function call (sp_rcontext, it's tables and items,
+ sp_cursor and Item_cache holders for case expressions).
+ We can't use caller's arena/memroot for those objects because
+ in this case some fixed amount of memory will be consumed for
+ each function/trigger invocation and so statements which involve
+ lot of them will hog memory.
+ TODO: we should create sp_rcontext once per command and reuse
+ it on subsequent executions of a function/trigger.
+ */
+ init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
+ thd->set_n_backup_active_arena(&call_arena, &backup_arena);
+
+ if (!(nctx= sp_rcontext::create(thd, m_pcont, return_value_fld)))
+ {
+ thd->restore_active_arena(&call_arena, &backup_arena);
+ err_status= TRUE;
+ goto err_with_cleanup;
+ }
+
+ /*
+ We have to switch temporarily back to callers arena/memroot.
+ Function arguments belong to the caller and so the may reference
+ memory which they will allocate during calculation long after
+ this function call will be finished (e.g. in Item::cleanup()).
+ */
+ thd->restore_active_arena(&call_arena, &backup_arena);
+
+#ifndef DBUG_OFF
+ nctx->sp= this;
+#endif
+
+ /* Pass arguments. */
+ for (arg_no= 0; arg_no < argcount; arg_no++)
+ {
+ /* Arguments must be fixed in Item_func_sp::fix_fields */
+ DBUG_ASSERT(argp[arg_no]->fixed);
+
+ if ((err_status= nctx->set_variable(thd, arg_no, &(argp[arg_no]))))
+ goto err_with_cleanup;
+ }
+
+ /*
+ If row-based binlogging, we don't need to binlog the function's call, let
+ each substatement be binlogged its way.
+ */
+ need_binlog_call= mysql_bin_log.is_open() &&
+ (thd->variables.option_bits & OPTION_BIN_LOG) &&
+ !thd->is_current_stmt_binlog_format_row();
+
+ /*
+ Remember the original arguments for unrolled replication of functions
+ before they are changed by execution.
+ */
+ if (need_binlog_call)
+ {
+ binlog_buf.length(0);
+ binlog_buf.append(STRING_WITH_LEN("SELECT "));
+ append_identifier(thd, &binlog_buf, m_db.str, m_db.length);
+ binlog_buf.append('.');
+ append_identifier(thd, &binlog_buf, m_name.str, m_name.length);
+ binlog_buf.append('(');
+ for (arg_no= 0; arg_no < argcount; arg_no++)
+ {
+ String str_value_holder;
+ String *str_value;
+
+ if (arg_no)
+ binlog_buf.append(',');
+
+ str_value= sp_get_item_value(thd, nctx->get_item(arg_no),
+ &str_value_holder);
+
+ if (str_value)
+ binlog_buf.append(*str_value);
+ else
+ binlog_buf.append(STRING_WITH_LEN("NULL"));
+ }
+ binlog_buf.append(')');
+ }
+ thd->spcont= nctx;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *save_security_ctx;
+ if (set_routine_security_ctx(thd, this, FALSE, &save_security_ctx))
+ {
+ err_status= TRUE;
+ goto err_with_cleanup;
+ }
+#endif
+
+ if (need_binlog_call)
+ {
+ query_id_t q;
+ reset_dynamic(&thd->user_var_events);
+ /*
+ In case of artificially constructed events for function calls
+ we have separate union for each such event and hence can't use
+ query_id of real calling statement as the start of all these
+ unions (this will break logic of replication of user-defined
+ variables). So we use artifical value which is guaranteed to
+ be greater than all query_id's of all statements belonging
+ to previous events/unions.
+ Possible alternative to this is logging of all function invocations
+ as one select and not resetting THD::user_var_events before
+ each invocation.
+ */
+ q= get_query_id();
+ mysql_bin_log.start_union_events(thd, q + 1);
+ binlog_save_options= thd->variables.option_bits;
+ thd->variables.option_bits&= ~OPTION_BIN_LOG;
+ }
+
+ /*
+ Switch to call arena/mem_root so objects like sp_cursor or
+ Item_cache holders for case expressions can be allocated on it.
+
+ TODO: In future we should associate call arena/mem_root with
+ sp_rcontext and allocate all these objects (and sp_rcontext
+ itself) on it directly rather than juggle with arenas.
+ */
+ thd->set_n_backup_active_arena(&call_arena, &backup_arena);
+
+ err_status= execute(thd, TRUE);
+
+ thd->restore_active_arena(&call_arena, &backup_arena);
+
+ if (need_binlog_call)
+ {
+ mysql_bin_log.stop_union_events(thd);
+ thd->variables.option_bits= binlog_save_options;
+ if (thd->binlog_evt_union.unioned_events)
+ {
+ int errcode = query_error_code(thd, thd->killed == NOT_KILLED);
+ Query_log_event qinfo(thd, binlog_buf.ptr(), binlog_buf.length(),
+ thd->binlog_evt_union.unioned_events_trans, FALSE, FALSE, errcode);
+ if (mysql_bin_log.write(&qinfo) &&
+ thd->binlog_evt_union.unioned_events_trans)
+ {
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
+ "Invoked ROUTINE modified a transactional table but MySQL "
+ "failed to reflect this change in the binary log");
+ err_status= TRUE;
+ }
+ reset_dynamic(&thd->user_var_events);
+ /* Forget those values, in case more function calls are binlogged: */
+ thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0;
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.empty();
+ }
+ }
+
+ if (!err_status)
+ {
+ /* We need result only in function but not in trigger */
+
+ if (!nctx->is_return_value_set())
+ {
+ my_error(ER_SP_NORETURNEND, MYF(0), m_name.str);
+ err_status= TRUE;
+ }
+ }
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ m_security_ctx.restore_security_context(thd, save_security_ctx);
+#endif
+
+err_with_cleanup:
+ delete nctx;
+ call_arena.free_items();
+ free_root(&call_mem_root, MYF(0));
+ thd->spcont= octx;
+
+ /*
+ If not insided a procedure and a function printing warning
+ messsages.
+ */
+ if (need_binlog_call &&
+ thd->spcont == NULL && !thd->binlog_evt_union.do_union)
+ thd->issue_unsafe_warnings();
+
+ DBUG_RETURN(err_status);
+}
+
+
+/**
+ Execute a procedure.
+
+ The function does the following steps:
+ - Set all parameters
+ - changes security context for SUID routines
+ - call sp_head::execute
+ - copy back values of INOUT and OUT parameters
+ - restores security context
+
+ @param thd Thread handle
+ @param args List of values passed as arguments.
+
+ @retval
+ FALSE on success
+ @retval
+ TRUE on error
+*/
+
+bool
+sp_head::execute_procedure(THD *thd, List<Item> *args)
+{
+ bool err_status= FALSE;
+ uint params = m_pcont->context_var_count();
+ /* Query start time may be reset in a multi-stmt SP; keep this for later. */
+ ulonglong utime_before_sp_exec= thd->utime_after_lock;
+ sp_rcontext *save_spcont, *octx;
+ sp_rcontext *nctx = NULL;
+ bool save_enable_slow_log;
+ bool save_log_general= false;
+ DBUG_ENTER("sp_head::execute_procedure");
+ DBUG_PRINT("info", ("procedure %s", m_name.str));
+
+ if (args->elements != params)
+ {
+ my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "PROCEDURE",
+ m_qname.str, params, args->elements);
+ DBUG_RETURN(TRUE);
+ }
+
+ save_spcont= octx= thd->spcont;
+ if (! octx)
+ {
+ /* Create a temporary old context. */
+ if (!(octx= sp_rcontext::create(thd, m_pcont, NULL)))
+ {
+ DBUG_PRINT("error", ("Could not create octx"));
+ DBUG_RETURN(TRUE);
+ }
+
+#ifndef DBUG_OFF
+ octx->sp= 0;
+#endif
+ thd->spcont= octx;
+
+ /* set callers_arena to thd, for upper-level function to work */
+ thd->spcont->callers_arena= thd;
+ }
+
+ if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL)))
+ {
+ delete nctx; /* Delete nctx if it was init() that failed. */
+ thd->spcont= save_spcont;
+ DBUG_RETURN(TRUE);
+ }
+#ifndef DBUG_OFF
+ nctx->sp= this;
+#endif
+
+ if (params > 0)
+ {
+ List_iterator<Item> it_args(*args);
+
+ DBUG_PRINT("info",(" %.*s: eval args", (int) m_name.length, m_name.str));
+
+ for (uint i= 0 ; i < params ; i++)
+ {
+ Item *arg_item= it_args++;
+
+ if (!arg_item)
+ break;
+
+ sp_variable *spvar= m_pcont->find_variable(i);
+
+ if (!spvar)
+ continue;
+
+ if (spvar->mode != sp_variable::MODE_IN)
+ {
+ Settable_routine_parameter *srp=
+ arg_item->get_settable_routine_parameter();
+
+ if (!srp)
+ {
+ my_error(ER_SP_NOT_VAR_ARG, MYF(0), i+1, m_qname.str);
+ err_status= TRUE;
+ break;
+ }
+
+ srp->set_required_privilege(spvar->mode == sp_variable::MODE_INOUT);
+ }
+
+ if (spvar->mode == sp_variable::MODE_OUT)
+ {
+ Item_null *null_item= new Item_null();
+ Item *tmp_item= null_item;
+
+ if (!null_item ||
+ nctx->set_variable(thd, i, &tmp_item))
+ {
+ DBUG_PRINT("error", ("set variable failed"));
+ err_status= TRUE;
+ break;
+ }
+ }
+ else
+ {
+ if (nctx->set_variable(thd, i, it_args.ref()))
+ {
+ DBUG_PRINT("error", ("set variable 2 failed"));
+ err_status= TRUE;
+ break;
+ }
+ }
+ }
+
+ /*
+ Okay, got values for all arguments. Close tables that might be used by
+ arguments evaluation. If arguments evaluation required prelocking mode,
+ we'll leave it here.
+ */
+ thd->lex->unit.cleanup();
+
+ if (!thd->in_sub_stmt)
+ {
+ thd->get_stmt_da()->set_overwrite_status(true);
+ thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ }
+
+ thd_proc_info(thd, "closing tables");
+ close_thread_tables(thd);
+ thd_proc_info(thd, 0);
+
+ if (! thd->in_sub_stmt)
+ {
+ if (thd->transaction_rollback_request)
+ {
+ trans_rollback_implicit(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+ else if (! thd->in_multi_stmt_transaction_mode())
+ thd->mdl_context.release_transactional_locks();
+ else
+ thd->mdl_context.release_statement_locks();
+ }
+
+ thd->rollback_item_tree_changes();
+
+ DBUG_PRINT("info",(" %.*s: eval args done", (int) m_name.length,
+ m_name.str));
+ }
+ save_enable_slow_log= thd->enable_slow_log;
+ if (!(m_flags & LOG_SLOW_STATEMENTS) && save_enable_slow_log)
+ {
+ DBUG_PRINT("info", ("Disabling slow log for the execution"));
+ thd->enable_slow_log= FALSE;
+ }
+ if (!(m_flags & LOG_GENERAL_LOG) && !(thd->variables.option_bits & OPTION_LOG_OFF))
+ {
+ DBUG_PRINT("info", ("Disabling general log for the execution"));
+ save_log_general= true;
+ /* disable this bit */
+ thd->variables.option_bits |= OPTION_LOG_OFF;
+ }
+ thd->spcont= nctx;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *save_security_ctx= 0;
+ if (!err_status)
+ err_status= set_routine_security_ctx(thd, this, TRUE, &save_security_ctx);
+#endif
+
+ if (!err_status)
+ {
+ err_status= execute(thd, TRUE);
+ DBUG_PRINT("info", ("execute returned %d", (int) err_status));
+ }
+
+ if (save_log_general)
+ thd->variables.option_bits &= ~OPTION_LOG_OFF;
+ thd->enable_slow_log= save_enable_slow_log;
+ /*
+ In the case when we weren't able to employ reuse mechanism for
+ OUT/INOUT paranmeters, we should reallocate memory. This
+ allocation should be done on the arena which will live through
+ all execution of calling routine.
+ */
+ thd->spcont->callers_arena= octx->callers_arena;
+
+ if (!err_status && params > 0)
+ {
+ List_iterator<Item> it_args(*args);
+
+ /*
+ Copy back all OUT or INOUT values to the previous frame, or
+ set global user variables
+ */
+ for (uint i= 0 ; i < params ; i++)
+ {
+ Item *arg_item= it_args++;
+
+ if (!arg_item)
+ break;
+
+ sp_variable *spvar= m_pcont->find_variable(i);
+
+ if (spvar->mode == sp_variable::MODE_IN)
+ continue;
+
+ Settable_routine_parameter *srp=
+ arg_item->get_settable_routine_parameter();
+
+ DBUG_ASSERT(srp);
+
+ if (srp->set_value(thd, octx, nctx->get_item_addr(i)))
+ {
+ DBUG_PRINT("error", ("set value failed"));
+ err_status= TRUE;
+ break;
+ }
+
+ Send_field *out_param_info= new (thd->mem_root) Send_field();
+ nctx->get_item(i)->make_field(out_param_info);
+ out_param_info->db_name= m_db.str;
+ out_param_info->table_name= m_name.str;
+ out_param_info->org_table_name= m_name.str;
+ out_param_info->col_name= spvar->name.str;
+ out_param_info->org_col_name= spvar->name.str;
+
+ srp->set_out_param_info(out_param_info);
+ }
+ }
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (save_security_ctx)
+ m_security_ctx.restore_security_context(thd, save_security_ctx);
+#endif
+
+ if (!save_spcont)
+ delete octx;
+
+ delete nctx;
+ thd->spcont= save_spcont;
+ thd->utime_after_lock= utime_before_sp_exec;
+
+ /*
+ If not insided a procedure and a function printing warning
+ messsages.
+ */
+ bool need_binlog_call= mysql_bin_log.is_open() &&
+ (thd->variables.option_bits & OPTION_BIN_LOG) &&
+ !thd->is_current_stmt_binlog_format_row();
+ if (need_binlog_call && thd->spcont == NULL &&
+ !thd->binlog_evt_union.do_union)
+ thd->issue_unsafe_warnings();
+
+ DBUG_RETURN(err_status);
+}
+
+
+/**
+ Reset lex during parsing, before we parse a sub statement.
+
+ @param thd Thread handler.
+
+ @return Error state
+ @retval true An error occurred.
+ @retval false Success.
+*/
+
+bool
+sp_head::reset_lex(THD *thd)
+{
+ DBUG_ENTER("sp_head::reset_lex");
+ LEX *sublex;
+ LEX *oldlex= thd->lex;
+
+ sublex= new (thd->mem_root)st_lex_local;
+ if (sublex == 0)
+ DBUG_RETURN(TRUE);
+
+ thd->lex= sublex;
+ (void)m_lex.push_front(oldlex);
+
+ /* Reset most stuff. */
+ lex_start(thd);
+
+ /* And keep the SP stuff too */
+ sublex->sphead= oldlex->sphead;
+ sublex->spcont= oldlex->spcont;
+ /* And trigger related stuff too */
+ sublex->trg_chistics= oldlex->trg_chistics;
+ sublex->trg_table_fields.empty();
+ sublex->sp_lex_in_use= FALSE;
+
+ /* Reset type info. */
+
+ sublex->charset= NULL;
+ sublex->length= NULL;
+ sublex->dec= NULL;
+ sublex->interval_list.empty();
+ sublex->type= 0;
+ sublex->uint_geom_type= 0;
+ sublex->vcol_info= 0;
+
+ /* Reset part of parser state which needs this. */
+ thd->m_parser_state->m_yacc.reset_before_substatement();
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Restore lex during parsing, after we have parsed a sub statement.
+
+ @param thd Thread handle
+
+ @return
+ @retval TRUE failure
+ @retval FALSE success
+*/
+
+bool
+sp_head::restore_lex(THD *thd)
+{
+ DBUG_ENTER("sp_head::restore_lex");
+ LEX *sublex= thd->lex;
+ LEX *oldlex;
+
+ sublex->set_trg_event_type_for_tables();
+
+ oldlex= (LEX *)m_lex.pop();
+ if (! oldlex)
+ DBUG_RETURN(FALSE); // Nothing to restore
+
+ oldlex->trg_table_fields.push_back(&sublex->trg_table_fields);
+
+ /* If this substatement is unsafe, the entire routine is too. */
+ DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags: 0x%x",
+ thd->lex->get_stmt_unsafe_flags()));
+ unsafe_flags|= sublex->get_stmt_unsafe_flags();
+
+ /*
+ Add routines which are used by statement to respective set for
+ this routine.
+ */
+ if (sp_update_sp_used_routines(&m_sroutines, &sublex->sroutines))
+ DBUG_RETURN(TRUE);
+
+ /* If this substatement is a update query, then mark MODIFIES_DATA */
+ if (is_update_query(sublex->sql_command))
+ m_flags|= MODIFIES_DATA;
+
+ /*
+ Merge tables used by this statement (but not by its functions or
+ procedures) to multiset of tables used by this routine.
+ */
+ merge_table_list(thd, sublex->query_tables, sublex);
+ if (! sublex->sp_lex_in_use)
+ {
+ sublex->sphead= NULL;
+ lex_end(sublex);
+ delete sublex;
+ }
+ thd->lex= oldlex;
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Put the instruction on the backpatch list, associated with the label.
+*/
+int
+sp_head::push_backpatch(sp_instr *i, sp_label *lab)
+{
+ bp_t *bp= (bp_t *)sql_alloc(sizeof(bp_t));
+
+ if (!bp)
+ return 1;
+ bp->lab= lab;
+ bp->instr= i;
+ return m_backpatch.push_front(bp);
+}
+
+/**
+ Update all instruction with this label in the backpatch list to
+ the current position.
+*/
+void
+sp_head::backpatch(sp_label *lab)
+{
+ bp_t *bp;
+ uint dest= instructions();
+ List_iterator_fast<bp_t> li(m_backpatch);
+
+ DBUG_ENTER("sp_head::backpatch");
+ while ((bp= li++))
+ {
+ if (bp->lab == lab)
+ {
+ DBUG_PRINT("info", ("backpatch: (m_ip %d, label 0x%lx <%s>) to dest %d",
+ bp->instr->m_ip, (ulong) lab, lab->name.str, dest));
+ bp->instr->backpatch(dest, lab->ctx);
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Prepare an instance of Create_field for field creation (fill all necessary
+ attributes).
+
+ @param[in] thd Thread handle
+ @param[in] lex Yacc parsing context
+ @param[in] field_type Field type
+ @param[out] field_def An instance of create_field to be filled
+
+ @retval
+ FALSE on success
+ @retval
+ TRUE on error
+*/
+
+bool
+sp_head::fill_field_definition(THD *thd, LEX *lex,
+ enum enum_field_types field_type,
+ Create_field *field_def)
+{
+ LEX_STRING cmt = { 0, 0 };
+ uint unused1= 0;
+
+ if (field_def->init(thd, (char*) "", field_type, lex->length, lex->dec,
+ lex->type, (Item*) 0, (Item*) 0, &cmt, 0,
+ &lex->interval_list,
+ lex->charset ? lex->charset :
+ thd->variables.collation_database,
+ lex->uint_geom_type,
+ lex->vcol_info, NULL, FALSE))
+ return TRUE;
+
+ if (field_def->interval_list.elements)
+ field_def->interval= create_typelib(mem_root, field_def,
+ &field_def->interval_list);
+
+ sp_prepare_create_field(thd, field_def);
+
+ if (prepare_create_field(field_def, &unused1, HA_CAN_GEOMETRY))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+int
+sp_head::new_cont_backpatch(sp_instr_opt_meta *i)
+{
+ m_cont_level+= 1;
+ if (i)
+ {
+ /* Use the cont. destination slot to store the level */
+ i->m_cont_dest= m_cont_level;
+ if (m_cont_backpatch.push_front(i))
+ return 1;
+ }
+ return 0;
+}
+
+int
+sp_head::add_cont_backpatch(sp_instr_opt_meta *i)
+{
+ i->m_cont_dest= m_cont_level;
+ return m_cont_backpatch.push_front(i);
+}
+
+void
+sp_head::do_cont_backpatch()
+{
+ uint dest= instructions();
+ uint lev= m_cont_level--;
+ sp_instr_opt_meta *i;
+
+ while ((i= m_cont_backpatch.head()) && i->m_cont_dest == lev)
+ {
+ i->m_cont_dest= dest;
+ (void)m_cont_backpatch.pop();
+ }
+}
+
+void
+sp_head::set_info(longlong created, longlong modified,
+ st_sp_chistics *chistics, ulonglong sql_mode)
+{
+ m_created= created;
+ m_modified= modified;
+ m_chistics= (st_sp_chistics *) memdup_root(mem_root, (char*) chistics,
+ sizeof(*chistics));
+ if (m_chistics->comment.length == 0)
+ m_chistics->comment.str= 0;
+ else
+ m_chistics->comment.str= strmake_root(mem_root,
+ m_chistics->comment.str,
+ m_chistics->comment.length);
+ m_sql_mode= sql_mode;
+}
+
+
+void
+sp_head::set_definer(const char *definer, uint definerlen)
+{
+ char user_name_holder[USERNAME_LENGTH + 1];
+ LEX_STRING user_name= { user_name_holder, USERNAME_LENGTH };
+
+ char host_name_holder[HOSTNAME_LENGTH + 1];
+ LEX_STRING host_name= { host_name_holder, HOSTNAME_LENGTH };
+
+ if (parse_user(definer, definerlen, user_name.str, &user_name.length,
+ host_name.str, &host_name.length) &&
+ user_name.length && !host_name.length)
+ {
+ // 'user@' -> 'user@%'
+ host_name= host_not_specified;
+ }
+
+ set_definer(&user_name, &host_name);
+}
+
+
+void
+sp_head::set_definer(const LEX_STRING *user_name, const LEX_STRING *host_name)
+{
+ m_definer_user.str= strmake_root(mem_root, user_name->str, user_name->length);
+ m_definer_user.length= user_name->length;
+
+ m_definer_host.str= strmake_root(mem_root, host_name->str, host_name->length);
+ m_definer_host.length= host_name->length;
+}
+
+
+void
+sp_head::reset_thd_mem_root(THD *thd)
+{
+ DBUG_ENTER("sp_head::reset_thd_mem_root");
+ m_thd_root= thd->mem_root;
+ thd->mem_root= &main_mem_root;
+ DBUG_PRINT("info", ("mem_root 0x%lx moved to thd mem root 0x%lx",
+ (ulong) &mem_root, (ulong) &thd->mem_root));
+ free_list= thd->free_list; // Keep the old list
+ thd->free_list= NULL; // Start a new one
+ m_thd= thd;
+ DBUG_VOID_RETURN;
+}
+
+void
+sp_head::restore_thd_mem_root(THD *thd)
+{
+ DBUG_ENTER("sp_head::restore_thd_mem_root");
+
+ /*
+ In some cases our parser detects a syntax error and calls
+ LEX::cleanup_lex_after_parse_error() method only after
+ finishing parsing the whole routine. In such a situation
+ sp_head::restore_thd_mem_root() will be called twice - the
+ first time as part of normal parsing process and the second
+ time by cleanup_lex_after_parse_error().
+ To avoid ruining active arena/mem_root state in this case we
+ skip restoration of old arena/mem_root if this method has been
+ already called for this routine.
+ */
+ if (!m_thd)
+ DBUG_VOID_RETURN;
+
+ Item *flist= free_list; // The old list
+ set_query_arena(thd); // Get new free_list and mem_root
+ state= STMT_INITIALIZED_FOR_SP;
+
+ DBUG_PRINT("info", ("mem_root 0x%lx returned from thd mem root 0x%lx",
+ (ulong) &mem_root, (ulong) &thd->mem_root));
+ thd->free_list= flist; // Restore the old one
+ thd->mem_root= m_thd_root;
+ m_thd= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Check if a user has access right to a routine.
+
+ @param thd Thread handler
+ @param sp SP
+ @param full_access Set to 1 if the user has SELECT right to the
+ 'mysql.proc' able or is the owner of the routine
+ @retval
+ false ok
+ @retval
+ true error
+*/
+
+bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access)
+{
+ TABLE_LIST tables;
+ bzero((char*) &tables,sizeof(tables));
+ tables.db= (char*) "mysql";
+ tables.table_name= tables.alias= (char*) "proc";
+ *full_access= ((!check_table_access(thd, SELECT_ACL, &tables, FALSE,
+ 1, TRUE) &&
+ (tables.grant.privilege & SELECT_ACL) != 0) ||
+ (!strcmp(sp->m_definer_user.str,
+ thd->security_ctx->priv_user) &&
+ !strcmp(sp->m_definer_host.str,
+ thd->security_ctx->priv_host)));
+ if (!*full_access)
+ return check_some_routine_access(thd, sp->m_db.str, sp->m_name.str,
+ sp->m_type == TYPE_ENUM_PROCEDURE);
+ return 0;
+}
+
+
+/**
+ Implement SHOW CREATE statement for stored routines.
+
+ @param thd Thread context.
+ @param type Stored routine type
+ (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION)
+
+ @return Error status.
+ @retval FALSE on success
+ @retval TRUE on error
+*/
+
+bool
+sp_head::show_create_routine(THD *thd, int type)
+{
+ const char *col1_caption= type == TYPE_ENUM_PROCEDURE ?
+ "Procedure" : "Function";
+
+ const char *col3_caption= type == TYPE_ENUM_PROCEDURE ?
+ "Create Procedure" : "Create Function";
+
+ bool err_status;
+
+ Protocol *protocol= thd->protocol;
+ List<Item> fields;
+
+ LEX_STRING sql_mode;
+
+ bool full_access;
+
+ DBUG_ENTER("sp_head::show_create_routine");
+ DBUG_PRINT("info", ("routine %s", m_name.str));
+
+ DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
+ type == TYPE_ENUM_FUNCTION);
+
+ if (check_show_routine_access(thd, this, &full_access))
+ DBUG_RETURN(TRUE);
+
+ sql_mode_string_representation(thd, m_sql_mode, &sql_mode);
+
+ /* Send header. */
+
+ fields.push_back(new Item_empty_string(col1_caption, NAME_CHAR_LEN));
+ fields.push_back(new Item_empty_string("sql_mode", sql_mode.length));
+
+ {
+ /*
+ NOTE: SQL statement field must be not less than 1024 in order not to
+ confuse old clients.
+ */
+
+ Item_empty_string *stmt_fld=
+ new Item_empty_string(col3_caption,
+ MY_MAX(m_defstr.length, 1024));
+
+ stmt_fld->maybe_null= TRUE;
+
+ fields.push_back(stmt_fld);
+ }
+
+ fields.push_back(new Item_empty_string("character_set_client",
+ MY_CS_NAME_SIZE));
+
+ fields.push_back(new Item_empty_string("collation_connection",
+ MY_CS_NAME_SIZE));
+
+ fields.push_back(new Item_empty_string("Database Collation",
+ MY_CS_NAME_SIZE));
+
+ if (protocol->send_result_set_metadata(&fields,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Send data. */
+
+ protocol->prepare_for_resend();
+
+ protocol->store(m_name.str, m_name.length, system_charset_info);
+ protocol->store(sql_mode.str, sql_mode.length, system_charset_info);
+
+ if (full_access)
+ protocol->store(m_defstr.str, m_defstr.length,
+ m_creation_ctx->get_client_cs());
+ else
+ protocol->store_null();
+
+
+ protocol->store(m_creation_ctx->get_client_cs()->csname, system_charset_info);
+ protocol->store(m_creation_ctx->get_connection_cl()->name, system_charset_info);
+ protocol->store(m_creation_ctx->get_db_cl()->name, system_charset_info);
+
+ err_status= protocol->write();
+
+ if (!err_status)
+ my_eof(thd);
+
+ DBUG_RETURN(err_status);
+}
+
+
+/**
+ Add instruction to SP.
+
+ @param instr Instruction
+*/
+
+int sp_head::add_instr(sp_instr *instr)
+{
+ instr->free_list= m_thd->free_list;
+ m_thd->free_list= 0;
+ /*
+ Memory root of every instruction is designated for permanent
+ transformations (optimizations) made on the parsed tree during
+ the first execution. It points to the memory root of the
+ entire stored procedure, as their life span is equal.
+ */
+ instr->mem_root= &main_mem_root;
+ return insert_dynamic(&m_instr, (uchar*)&instr);
+}
+
+
+/**
+ Do some minimal optimization of the code:
+ -# Mark used instructions
+ -# While doing this, shortcut jumps to jump instructions
+ -# Compact the code, removing unused instructions.
+
+ This is the main mark and move loop; it relies on the following methods
+ in sp_instr and its subclasses:
+
+ - opt_mark() : Mark instruction as reachable
+ - opt_shortcut_jump(): Shortcut jumps to the final destination;
+ used by opt_mark().
+ - opt_move() : Update moved instruction
+ - set_destination() : Set the new destination (jump instructions only)
+*/
+
+void sp_head::optimize()
+{
+ List<sp_instr> bp;
+ sp_instr *i;
+ uint src, dst;
+
+ opt_mark();
+
+ bp.empty();
+ src= dst= 0;
+ while ((i= get_instr(src)))
+ {
+ if (! i->marked)
+ {
+ delete i;
+ src+= 1;
+ }
+ else
+ {
+ if (src != dst)
+ {
+ /* Move the instruction and update prev. jumps */
+ sp_instr *ibp;
+ List_iterator_fast<sp_instr> li(bp);
+
+ set_dynamic(&m_instr, (uchar*)&i, dst);
+ while ((ibp= li++))
+ {
+ sp_instr_opt_meta *im= static_cast<sp_instr_opt_meta *>(ibp);
+ im->set_destination(src, dst);
+ }
+ }
+ i->opt_move(dst, &bp);
+ src+= 1;
+ dst+= 1;
+ }
+ }
+ m_instr.elements= dst;
+ bp.empty();
+}
+
+void sp_head::add_mark_lead(uint ip, List<sp_instr> *leads)
+{
+ sp_instr *i= get_instr(ip);
+
+ if (i && ! i->marked)
+ leads->push_front(i);
+}
+
+void
+sp_head::opt_mark()
+{
+ uint ip;
+ sp_instr *i;
+ List<sp_instr> leads;
+
+ /*
+ Forward flow analysis algorithm in the instruction graph:
+ - first, add the entry point in the graph (the first instruction) to the
+ 'leads' list of paths to explore.
+ - while there are still leads to explore:
+ - pick one lead, and follow the path forward. Mark instruction reached.
+ Stop only if the end of the routine is reached, or the path converge
+ to code already explored (marked).
+ - while following a path, collect in the 'leads' list any fork to
+ another path (caused by conditional jumps instructions), so that these
+ paths can be explored as well.
+ */
+
+ /* Add the entry point */
+ i= get_instr(0);
+ leads.push_front(i);
+
+ /* For each path of code ... */
+ while (leads.elements != 0)
+ {
+ i= leads.pop();
+
+ /* Mark the entire path, collecting new leads. */
+ while (i && ! i->marked)
+ {
+ ip= i->opt_mark(this, & leads);
+ i= get_instr(ip);
+ }
+ }
+}
+
+
+#ifndef DBUG_OFF
+/**
+ Return the routine instructions as a result set.
+ @return
+ 0 if ok, !=0 on error.
+*/
+int
+sp_head::show_routine_code(THD *thd)
+{
+ Protocol *protocol= thd->protocol;
+ char buff[2048];
+ String buffer(buff, sizeof(buff), system_charset_info);
+ List<Item> field_list;
+ sp_instr *i;
+ bool full_access;
+ int res= 0;
+ uint ip;
+ DBUG_ENTER("sp_head::show_routine_code");
+ DBUG_PRINT("info", ("procedure: %s", m_name.str));
+
+ if (check_show_routine_access(thd, this, &full_access) || !full_access)
+ DBUG_RETURN(1);
+
+ field_list.push_back(new Item_uint("Pos", 9));
+ // 1024 is for not to confuse old clients
+ field_list.push_back(new Item_empty_string("Instruction",
+ MY_MAX(buffer.length(), 1024)));
+ if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS |
+ Protocol::SEND_EOF))
+ DBUG_RETURN(1);
+
+ for (ip= 0; (i = get_instr(ip)) ; ip++)
+ {
+ /*
+ Consistency check. If these are different something went wrong
+ during optimization.
+ */
+ if (ip != i->m_ip)
+ {
+ const char *format= "Instruction at position %u has m_ip=%u";
+ char tmp[sizeof(format) + 2*SP_INSTR_UINT_MAXLEN + 1];
+
+ sprintf(tmp, format, ip, i->m_ip);
+ /*
+ Since this is for debugging purposes only, we don't bother to
+ introduce a special error code for it.
+ */
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, tmp);
+ }
+ protocol->prepare_for_resend();
+ protocol->store((longlong)ip);
+
+ buffer.set("", 0, system_charset_info);
+ i->print(&buffer);
+ protocol->store(buffer.ptr(), buffer.length(), system_charset_info);
+ if ((res= protocol->write()))
+ break;
+ }
+
+ if (!res)
+ my_eof(thd);
+
+ DBUG_RETURN(res);
+}
+#endif // ifndef DBUG_OFF
+
+
+/**
+ Prepare LEX and thread for execution of instruction, if requested open
+ and lock LEX's tables, execute instruction's core function, perform
+ cleanup afterwards.
+
+ @param thd thread context
+ @param nextp out - next instruction
+ @param open_tables if TRUE then check read access to tables in LEX's table
+ list and open and lock them (used in instructions which
+ need to calculate some expression and don't execute
+ complete statement).
+ @param sp_instr instruction for which we prepare context, and which core
+ function execute by calling its exec_core() method.
+
+ @note
+ We are not saving/restoring some parts of THD which may need this because
+ we do this once for whole routine execution in sp_head::execute().
+
+ @return
+ 0/non-0 - Success/Failure
+*/
+
+int
+sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
+ bool open_tables, sp_instr* instr)
+{
+ int res= 0;
+ DBUG_ENTER("reset_lex_and_exec_core");
+
+ /*
+ The flag is saved at the entry to the following substatement.
+ It's reset further in the common code part.
+ It's merged with the saved parent's value at the exit of this func.
+ */
+ bool parent_modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table;
+ thd->transaction.stmt.modified_non_trans_table= FALSE;
+ DBUG_ASSERT(!thd->derived_tables);
+ DBUG_ASSERT(thd->change_list.is_empty());
+ /*
+ Use our own lex.
+ We should not save old value since it is saved/restored in
+ sp_head::execute() when we are entering/leaving routine.
+ */
+ thd->lex= m_lex;
+
+ thd->set_query_id(next_query_id());
+
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ {
+ /*
+ This statement will enter/leave prelocked mode on its own.
+ Entering prelocked mode changes table list and related members
+ of LEX, so we'll need to restore them.
+ */
+ if (lex_query_tables_own_last)
+ {
+ /*
+ We've already entered/left prelocked mode with this statement.
+ Attach the list of tables that need to be prelocked and mark m_lex
+ as having such list attached.
+ */
+ *lex_query_tables_own_last= prelocking_tables;
+ m_lex->mark_as_requiring_prelocking(lex_query_tables_own_last);
+ }
+ }
+
+ reinit_stmt_before_use(thd, m_lex);
+
+ if (open_tables)
+ res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
+
+ if (!res)
+ {
+ res= instr->exec_core(thd, nextp);
+ DBUG_PRINT("info",("exec_core returned: %d", res));
+ }
+
+ /*
+ Call after unit->cleanup() to close open table
+ key read.
+ */
+ if (open_tables)
+ {
+ m_lex->unit.cleanup();
+ /* Here we also commit or rollback the current statement. */
+ if (! thd->in_sub_stmt)
+ {
+ thd->get_stmt_da()->set_overwrite_status(true);
+ thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ }
+ thd_proc_info(thd, "closing tables");
+ close_thread_tables(thd);
+ thd_proc_info(thd, 0);
+
+ if (! thd->in_sub_stmt)
+ {
+ if (thd->transaction_rollback_request)
+ {
+ trans_rollback_implicit(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+ else if (! thd->in_multi_stmt_transaction_mode())
+ thd->mdl_context.release_transactional_locks();
+ else
+ thd->mdl_context.release_statement_locks();
+ }
+ }
+ //TODO: why is this here if log_slow_query is in sp_instr_stmt_execute?
+ delete_explain_query(m_lex);
+
+ if (m_lex->query_tables_own_last)
+ {
+ /*
+ We've entered and left prelocking mode when executing statement
+ stored in m_lex.
+ m_lex->query_tables(->next_global)* list now has a 'tail' - a list
+ of tables that are added for prelocking. (If this is the first
+ execution, the 'tail' was added by open_tables(), otherwise we've
+ attached it above in this function).
+ Now we'll save the 'tail', and detach it.
+ */
+ lex_query_tables_own_last= m_lex->query_tables_own_last;
+ prelocking_tables= *lex_query_tables_own_last;
+ *lex_query_tables_own_last= NULL;
+ m_lex->mark_as_requiring_prelocking(NULL);
+ }
+ thd->rollback_item_tree_changes();
+ /*
+ Update the state of the active arena if no errors on
+ open_tables stage.
+ */
+ if (!res || !thd->is_error() ||
+ (thd->get_stmt_da()->sql_errno() != ER_CANT_REOPEN_TABLE &&
+ thd->get_stmt_da()->sql_errno() != ER_NO_SUCH_TABLE &&
+ thd->get_stmt_da()->sql_errno() != ER_NO_SUCH_TABLE_IN_ENGINE &&
+ thd->get_stmt_da()->sql_errno() != ER_UPDATE_TABLE_USED))
+ thd->stmt_arena->state= Query_arena::STMT_EXECUTED;
+
+ /*
+ Merge here with the saved parent's values
+ what is needed from the substatement gained
+ */
+ thd->transaction.stmt.modified_non_trans_table |= parent_modified_non_trans_table;
+ /*
+ Unlike for PS we should not call Item's destructors for newly created
+ items after execution of each instruction in stored routine. This is
+ because SP often create Item (like Item_int, Item_string etc...) when
+ they want to store some value in local variable, pass return value and
+ etc... So their life time should be longer than one instruction.
+
+ cleanup_items() is called in sp_head::execute()
+ */
+ DBUG_RETURN(res || thd->is_error());
+}
+
+
+/*
+ sp_instr class functions
+*/
+
+int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables)
+{
+ int result;
+
+ /*
+ Check whenever we have access to tables for this statement
+ and open and lock them before executing instructions core function.
+ */
+ if (open_temporary_tables(thd, tables) ||
+ check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)
+ || open_and_lock_tables(thd, tables, TRUE, 0))
+ result= -1;
+ else
+ result= 0;
+ /* Prepare all derived tables/views to catch possible errors. */
+ if (!result)
+ result= mysql_handle_derived(thd->lex, DT_PREPARE) ? -1 : 0;
+
+ return result;
+}
+
+uint sp_instr::get_cont_dest() const
+{
+ return (m_ip+1);
+}
+
+
+int sp_instr::exec_core(THD *thd, uint *nextp)
+{
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+/*
+ sp_instr_stmt class functions
+*/
+
+int
+sp_instr_stmt::execute(THD *thd, uint *nextp)
+{
+ int res;
+ DBUG_ENTER("sp_instr_stmt::execute");
+ DBUG_PRINT("info", ("command: %d", m_lex_keeper.sql_command()));
+
+ const CSET_STRING query_backup= thd->query_string;
+#if defined(ENABLED_PROFILING)
+ /* This s-p instr is profilable and will be captured. */
+ thd->profiling.set_query_source(m_query.str, m_query.length);
+#endif
+ if (!(res= alloc_query(thd, m_query.str, m_query.length)) &&
+ !(res=subst_spvars(thd, this, &m_query)))
+ {
+ /*
+ (the order of query cache and subst_spvars calls is irrelevant because
+ queries with SP vars can't be cached)
+ */
+ general_log_write(thd, COM_QUERY, thd->query(), thd->query_length());
+
+ if (query_cache_send_result_to_client(thd, thd->query(),
+ thd->query_length()) <= 0)
+ {
+ res= m_lex_keeper.reset_lex_and_exec_core(thd, nextp, FALSE, this);
+
+ if (thd->get_stmt_da()->is_eof())
+ {
+ /* Finalize server status flags after executing a statement. */
+ thd->update_server_status();
+
+ thd->protocol->end_statement();
+ }
+
+ query_cache_end_of_result(thd);
+
+ mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS,
+ thd->get_stmt_da()->is_error() ?
+ thd->get_stmt_da()->sql_errno() : 0,
+ command_name[COM_QUERY].str);
+
+ if (!res && unlikely(thd->enable_slow_log))
+ log_slow_statement(thd);
+ }
+ else
+ {
+ /* change statistics */
+ enum_sql_command save_sql_command= thd->lex->sql_command;
+ thd->lex->sql_command= SQLCOM_SELECT;
+ status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]);
+ thd->update_stats();
+ thd->lex->sql_command= save_sql_command;
+ *nextp= m_ip+1;
+ }
+ thd->set_query(query_backup);
+ thd->query_name_consts= 0;
+
+ if (!thd->is_error())
+ {
+ res= 0;
+ thd->get_stmt_da()->reset_diagnostics_area();
+ }
+ }
+ DBUG_RETURN(res || thd->is_error());
+}
+
+
+void
+sp_instr_stmt::print(String *str)
+{
+ uint i, len;
+
+ /* stmt CMD "..." */
+ if (str->reserve(SP_STMT_PRINT_MAXLEN+SP_INSTR_UINT_MAXLEN+8))
+ return;
+ str->qs_append(STRING_WITH_LEN("stmt "));
+ str->qs_append((uint)m_lex_keeper.sql_command());
+ str->qs_append(STRING_WITH_LEN(" \""));
+ len= m_query.length;
+ /*
+ Print the query string (but not too much of it), just to indicate which
+ statement it is.
+ */
+ if (len > SP_STMT_PRINT_MAXLEN)
+ len= SP_STMT_PRINT_MAXLEN-3;
+ /* Copy the query string and replace '\n' with ' ' in the process */
+ for (i= 0 ; i < len ; i++)
+ {
+ char c= m_query.str[i];
+ if (c == '\n')
+ c= ' ';
+ str->qs_append(c);
+ }
+ if (m_query.length > SP_STMT_PRINT_MAXLEN)
+ str->qs_append(STRING_WITH_LEN("...")); /* Indicate truncated string */
+ str->qs_append('"');
+}
+
+
+int
+sp_instr_stmt::exec_core(THD *thd, uint *nextp)
+{
+ MYSQL_QUERY_EXEC_START(thd->query(),
+ thd->thread_id,
+ (char *) (thd->db ? thd->db : ""),
+ &thd->security_ctx->priv_user[0],
+ (char *)thd->security_ctx->host_or_ip,
+ 3);
+ int res= mysql_execute_command(thd);
+ MYSQL_QUERY_EXEC_DONE(res);
+ *nextp= m_ip+1;
+ return res;
+}
+
+
+/*
+ sp_instr_set class functions
+*/
+
+int
+sp_instr_set::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_set::execute");
+ DBUG_PRINT("info", ("offset: %u", m_offset));
+
+ DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
+}
+
+
+int
+sp_instr_set::exec_core(THD *thd, uint *nextp)
+{
+ int res= thd->spcont->set_variable(thd, m_offset, &m_value);
+
+ if (res)
+ {
+ /* Failed to evaluate the value. Reset the variable to NULL. */
+
+ if (thd->spcont->set_variable(thd, m_offset, 0))
+ {
+ /* If this also failed, let's abort. */
+ my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+ }
+ }
+ delete_explain_query(thd->lex);
+
+ *nextp = m_ip+1;
+ return res;
+}
+
+void
+sp_instr_set::print(String *str)
+{
+ /* set name@offset ... */
+ int rsrv = SP_INSTR_UINT_MAXLEN+6;
+ sp_variable *var = m_ctx->find_variable(m_offset);
+
+ /* 'var' should always be non-null, but just in case... */
+ if (var)
+ rsrv+= var->name.length;
+ if (str->reserve(rsrv))
+ return;
+ str->qs_append(STRING_WITH_LEN("set "));
+ if (var)
+ {
+ str->qs_append(var->name.str, var->name.length);
+ str->qs_append('@');
+ }
+ str->qs_append(m_offset);
+ str->qs_append(' ');
+ m_value->print(str, QT_ORDINARY);
+}
+
+
+/*
+ sp_instr_set_trigger_field class functions
+*/
+
+int
+sp_instr_set_trigger_field::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_set_trigger_field::execute");
+ thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
+ DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
+}
+
+
+int
+sp_instr_set_trigger_field::exec_core(THD *thd, uint *nextp)
+{
+ const int res= (trigger_field->set_value(thd, &value) ? -1 : 0);
+ *nextp = m_ip+1;
+ return res;
+}
+
+void
+sp_instr_set_trigger_field::print(String *str)
+{
+ str->append(STRING_WITH_LEN("set_trigger_field "));
+ trigger_field->print(str, QT_ORDINARY);
+ str->append(STRING_WITH_LEN(":="));
+ value->print(str, QT_ORDINARY);
+}
+
+/*
+ sp_instr_opt_meta
+*/
+
+uint sp_instr_opt_meta::get_cont_dest() const
+{
+ return m_cont_dest;
+}
+
+
+/*
+ sp_instr_jump class functions
+*/
+
+int
+sp_instr_jump::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_jump::execute");
+ DBUG_PRINT("info", ("destination: %u", m_dest));
+
+ *nextp= m_dest;
+ DBUG_RETURN(0);
+}
+
+void
+sp_instr_jump::print(String *str)
+{
+ /* jump dest */
+ if (str->reserve(SP_INSTR_UINT_MAXLEN+5))
+ return;
+ str->qs_append(STRING_WITH_LEN("jump "));
+ str->qs_append(m_dest);
+}
+
+uint
+sp_instr_jump::opt_mark(sp_head *sp, List<sp_instr> *leads)
+{
+ m_dest= opt_shortcut_jump(sp, this);
+ if (m_dest != m_ip+1) /* Jumping to following instruction? */
+ marked= 1;
+ m_optdest= sp->get_instr(m_dest);
+ return m_dest;
+}
+
+uint
+sp_instr_jump::opt_shortcut_jump(sp_head *sp, sp_instr *start)
+{
+ uint dest= m_dest;
+ sp_instr *i;
+
+ while ((i= sp->get_instr(dest)))
+ {
+ uint ndest;
+
+ if (start == i || this == i)
+ break;
+ ndest= i->opt_shortcut_jump(sp, start);
+ if (ndest == dest)
+ break;
+ dest= ndest;
+ }
+ return dest;
+}
+
+void
+sp_instr_jump::opt_move(uint dst, List<sp_instr> *bp)
+{
+ if (m_dest > m_ip)
+ bp->push_back(this); // Forward
+ else if (m_optdest)
+ m_dest= m_optdest->m_ip; // Backward
+ m_ip= dst;
+}
+
+
+/*
+ sp_instr_jump_if_not class functions
+*/
+
+int
+sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_jump_if_not::execute");
+ DBUG_PRINT("info", ("destination: %u", m_dest));
+ DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
+}
+
+
+int
+sp_instr_jump_if_not::exec_core(THD *thd, uint *nextp)
+{
+ Item *it;
+ int res;
+
+ it= sp_prepare_func_item(thd, &m_expr);
+ if (! it)
+ {
+ res= -1;
+ }
+ else
+ {
+ res= 0;
+ if (! it->val_bool())
+ *nextp = m_dest;
+ else
+ *nextp = m_ip+1;
+ }
+
+ return res;
+}
+
+
+void
+sp_instr_jump_if_not::print(String *str)
+{
+ /* jump_if_not dest(cont) ... */
+ if (str->reserve(2*SP_INSTR_UINT_MAXLEN+14+32)) // Add some for the expr. too
+ return;
+ str->qs_append(STRING_WITH_LEN("jump_if_not "));
+ str->qs_append(m_dest);
+ str->qs_append('(');
+ str->qs_append(m_cont_dest);
+ str->qs_append(STRING_WITH_LEN(") "));
+ m_expr->print(str, QT_ORDINARY);
+}
+
+
+uint
+sp_instr_jump_if_not::opt_mark(sp_head *sp, List<sp_instr> *leads)
+{
+ sp_instr *i;
+
+ marked= 1;
+ if ((i= sp->get_instr(m_dest)))
+ {
+ m_dest= i->opt_shortcut_jump(sp, this);
+ m_optdest= sp->get_instr(m_dest);
+ }
+ sp->add_mark_lead(m_dest, leads);
+ if ((i= sp->get_instr(m_cont_dest)))
+ {
+ m_cont_dest= i->opt_shortcut_jump(sp, this);
+ m_cont_optdest= sp->get_instr(m_cont_dest);
+ }
+ sp->add_mark_lead(m_cont_dest, leads);
+ return m_ip+1;
+}
+
+void
+sp_instr_jump_if_not::opt_move(uint dst, List<sp_instr> *bp)
+{
+ /*
+ cont. destinations may point backwards after shortcutting jumps
+ during the mark phase. If it's still pointing forwards, only
+ push this for backpatching if sp_instr_jump::opt_move() will not
+ do it (i.e. if the m_dest points backwards).
+ */
+ if (m_cont_dest > m_ip)
+ { // Forward
+ if (m_dest < m_ip)
+ bp->push_back(this);
+ }
+ else if (m_cont_optdest)
+ m_cont_dest= m_cont_optdest->m_ip; // Backward
+ /* This will take care of m_dest and m_ip */
+ sp_instr_jump::opt_move(dst, bp);
+}
+
+
+/*
+ sp_instr_freturn class functions
+*/
+
+int
+sp_instr_freturn::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_freturn::execute");
+ DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
+}
+
+
+int
+sp_instr_freturn::exec_core(THD *thd, uint *nextp)
+{
+ /*
+ RETURN is a "procedure statement" (in terms of the SQL standard).
+ That means, Diagnostics Area should be clean before its execution.
+ */
+
+ Diagnostics_area *da= thd->get_stmt_da();
+ da->clear_warning_info(da->warning_info_id());
+
+ /*
+ Change <next instruction pointer>, so that this will be the last
+ instruction in the stored function.
+ */
+
+ *nextp= UINT_MAX;
+
+ /*
+ Evaluate the value of return expression and store it in current runtime
+ context.
+
+ NOTE: It's necessary to evaluate result item right here, because we must
+ do it in scope of execution the current context/block.
+ */
+
+ return thd->spcont->set_return_value(thd, &m_value);
+}
+
+void
+sp_instr_freturn::print(String *str)
+{
+ /* freturn type expr... */
+ if (str->reserve(1024+8+32)) // Add some for the expr. too
+ return;
+ str->qs_append(STRING_WITH_LEN("freturn "));
+ str->qs_append((uint)m_type);
+ str->qs_append(' ');
+ m_value->print(str, QT_ORDINARY);
+}
+
+/*
+ sp_instr_hpush_jump class functions
+*/
+
+int
+sp_instr_hpush_jump::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_hpush_jump::execute");
+
+ int ret= thd->spcont->push_handler(m_handler, m_ip + 1);
+
+ *nextp= m_dest;
+
+ DBUG_RETURN(ret);
+}
+
+
+void
+sp_instr_hpush_jump::print(String *str)
+{
+ /* hpush_jump dest fsize type */
+ if (str->reserve(SP_INSTR_UINT_MAXLEN*2 + 21))
+ return;
+
+ str->qs_append(STRING_WITH_LEN("hpush_jump "));
+ str->qs_append(m_dest);
+ str->qs_append(' ');
+ str->qs_append(m_frame);
+
+ switch (m_handler->type) {
+ case sp_handler::EXIT:
+ str->qs_append(STRING_WITH_LEN(" EXIT"));
+ break;
+ case sp_handler::CONTINUE:
+ str->qs_append(STRING_WITH_LEN(" CONTINUE"));
+ break;
+ default:
+ // The handler type must be either CONTINUE or EXIT.
+ DBUG_ASSERT(0);
+ }
+}
+
+
+uint
+sp_instr_hpush_jump::opt_mark(sp_head *sp, List<sp_instr> *leads)
+{
+ sp_instr *i;
+
+ marked= 1;
+ if ((i= sp->get_instr(m_dest)))
+ {
+ m_dest= i->opt_shortcut_jump(sp, this);
+ m_optdest= sp->get_instr(m_dest);
+ }
+ sp->add_mark_lead(m_dest, leads);
+
+ /*
+ For continue handlers, all instructions in the scope of the handler
+ are possible leads. For example, the instruction after freturn might
+ be executed if the freturn triggers the condition handled by the
+ continue handler.
+
+ m_dest marks the start of the handler scope. It's added as a lead
+ above, so we start on m_dest+1 here.
+ m_opt_hpop is the hpop marking the end of the handler scope.
+ */
+ if (m_handler->type == sp_handler::CONTINUE)
+ {
+ for (uint scope_ip= m_dest+1; scope_ip <= m_opt_hpop; scope_ip++)
+ sp->add_mark_lead(scope_ip, leads);
+ }
+
+ return m_ip+1;
+}
+
+
+/*
+ sp_instr_hpop class functions
+*/
+
+int
+sp_instr_hpop::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_hpop::execute");
+ thd->spcont->pop_handlers(m_count);
+ *nextp= m_ip+1;
+ DBUG_RETURN(0);
+}
+
+void
+sp_instr_hpop::print(String *str)
+{
+ /* hpop count */
+ if (str->reserve(SP_INSTR_UINT_MAXLEN+5))
+ return;
+ str->qs_append(STRING_WITH_LEN("hpop "));
+ str->qs_append(m_count);
+}
+
+
+/*
+ sp_instr_hreturn class functions
+*/
+
+int
+sp_instr_hreturn::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_hreturn::execute");
+
+ uint continue_ip= thd->spcont->exit_handler(thd->get_stmt_da());
+
+ *nextp= m_dest ? m_dest : continue_ip;
+
+ DBUG_RETURN(0);
+}
+
+
+void
+sp_instr_hreturn::print(String *str)
+{
+ /* hreturn framesize dest */
+ if (str->reserve(SP_INSTR_UINT_MAXLEN*2 + 9))
+ return;
+ str->qs_append(STRING_WITH_LEN("hreturn "));
+ if (m_dest)
+ {
+ // NOTE: this is legacy: hreturn instruction for EXIT handler
+ // should print out 0 as frame index.
+ str->qs_append(STRING_WITH_LEN("0 "));
+ str->qs_append(m_dest);
+ }
+ else
+ {
+ str->qs_append(m_frame);
+ }
+}
+
+
+uint
+sp_instr_hreturn::opt_mark(sp_head *sp, List<sp_instr> *leads)
+{
+ marked= 1;
+
+ if (m_dest)
+ {
+ /*
+ This is an EXIT handler; next instruction step is in m_dest.
+ */
+ return m_dest;
+ }
+
+ /*
+ This is a CONTINUE handler; next instruction step will come from
+ the handler stack and not from opt_mark.
+ */
+ return UINT_MAX;
+}
+
+
+/*
+ sp_instr_cpush class functions
+*/
+
+int
+sp_instr_cpush::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_cpush::execute");
+
+ int ret= thd->spcont->push_cursor(&m_lex_keeper, this);
+
+ *nextp= m_ip+1;
+
+ DBUG_RETURN(ret);
+}
+
+
+void
+sp_instr_cpush::print(String *str)
+{
+ const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
+
+ /* cpush name@offset */
+ uint rsrv= SP_INSTR_UINT_MAXLEN+7;
+
+ if (cursor_name)
+ rsrv+= cursor_name->length;
+ if (str->reserve(rsrv))
+ return;
+ str->qs_append(STRING_WITH_LEN("cpush "));
+ if (cursor_name)
+ {
+ str->qs_append(cursor_name->str, cursor_name->length);
+ str->qs_append('@');
+ }
+ str->qs_append(m_cursor);
+}
+
+
+/*
+ sp_instr_cpop class functions
+*/
+
+int
+sp_instr_cpop::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_cpop::execute");
+ thd->spcont->pop_cursors(m_count);
+ *nextp= m_ip+1;
+ DBUG_RETURN(0);
+}
+
+
+void
+sp_instr_cpop::print(String *str)
+{
+ /* cpop count */
+ if (str->reserve(SP_INSTR_UINT_MAXLEN+5))
+ return;
+ str->qs_append(STRING_WITH_LEN("cpop "));
+ str->qs_append(m_count);
+}
+
+
+/*
+ sp_instr_copen class functions
+*/
+
+/**
+ @todo
+ Assert that we either have an error or a cursor
+*/
+
+int
+sp_instr_copen::execute(THD *thd, uint *nextp)
+{
+ /*
+ We don't store a pointer to the cursor in the instruction to be
+ able to reuse the same instruction among different threads in future.
+ */
+ sp_cursor *c= thd->spcont->get_cursor(m_cursor);
+ int res;
+ DBUG_ENTER("sp_instr_copen::execute");
+
+ if (! c)
+ res= -1;
+ else
+ {
+ sp_lex_keeper *lex_keeper= c->get_lex_keeper();
+ Query_arena *old_arena= thd->stmt_arena;
+
+ /*
+ Get the Query_arena from the cpush instruction, which contains
+ the free_list of the query, so new items (if any) are stored in
+ the right free_list, and we can cleanup after each open.
+ */
+ thd->stmt_arena= c->get_instr();
+ res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this);
+ /* Cleanup the query's items */
+ if (thd->stmt_arena->free_list)
+ cleanup_items(thd->stmt_arena->free_list);
+ thd->stmt_arena= old_arena;
+ /* TODO: Assert here that we either have an error or a cursor */
+ }
+ DBUG_RETURN(res);
+}
+
+
+int
+sp_instr_copen::exec_core(THD *thd, uint *nextp)
+{
+ sp_cursor *c= thd->spcont->get_cursor(m_cursor);
+ int res= c->open(thd);
+ *nextp= m_ip+1;
+ return res;
+}
+
+void
+sp_instr_copen::print(String *str)
+{
+ const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
+
+ /* copen name@offset */
+ uint rsrv= SP_INSTR_UINT_MAXLEN+7;
+
+ if (cursor_name)
+ rsrv+= cursor_name->length;
+ if (str->reserve(rsrv))
+ return;
+ str->qs_append(STRING_WITH_LEN("copen "));
+ if (cursor_name)
+ {
+ str->qs_append(cursor_name->str, cursor_name->length);
+ str->qs_append('@');
+ }
+ str->qs_append(m_cursor);
+}
+
+
+/*
+ sp_instr_cclose class functions
+*/
+
+int
+sp_instr_cclose::execute(THD *thd, uint *nextp)
+{
+ sp_cursor *c= thd->spcont->get_cursor(m_cursor);
+ int res;
+ DBUG_ENTER("sp_instr_cclose::execute");
+
+ if (! c)
+ res= -1;
+ else
+ res= c->close(thd);
+ *nextp= m_ip+1;
+ DBUG_RETURN(res);
+}
+
+
+void
+sp_instr_cclose::print(String *str)
+{
+ const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
+
+ /* cclose name@offset */
+ uint rsrv= SP_INSTR_UINT_MAXLEN+8;
+
+ if (cursor_name)
+ rsrv+= cursor_name->length;
+ if (str->reserve(rsrv))
+ return;
+ str->qs_append(STRING_WITH_LEN("cclose "));
+ if (cursor_name)
+ {
+ str->qs_append(cursor_name->str, cursor_name->length);
+ str->qs_append('@');
+ }
+ str->qs_append(m_cursor);
+}
+
+
+/*
+ sp_instr_cfetch class functions
+*/
+
+int
+sp_instr_cfetch::execute(THD *thd, uint *nextp)
+{
+ sp_cursor *c= thd->spcont->get_cursor(m_cursor);
+ int res;
+ Query_arena backup_arena;
+ DBUG_ENTER("sp_instr_cfetch::execute");
+
+ res= c ? c->fetch(thd, &m_varlist) : -1;
+
+ *nextp= m_ip+1;
+ DBUG_RETURN(res);
+}
+
+
+void
+sp_instr_cfetch::print(String *str)
+{
+ List_iterator_fast<sp_variable> li(m_varlist);
+ sp_variable *pv;
+ const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
+
+ /* cfetch name@offset vars... */
+ uint rsrv= SP_INSTR_UINT_MAXLEN+8;
+
+ if (cursor_name)
+ rsrv+= cursor_name->length;
+ if (str->reserve(rsrv))
+ return;
+ str->qs_append(STRING_WITH_LEN("cfetch "));
+ if (cursor_name)
+ {
+ str->qs_append(cursor_name->str, cursor_name->length);
+ str->qs_append('@');
+ }
+ str->qs_append(m_cursor);
+ while ((pv= li++))
+ {
+ if (str->reserve(pv->name.length+SP_INSTR_UINT_MAXLEN+2))
+ return;
+ str->qs_append(' ');
+ str->qs_append(pv->name.str, pv->name.length);
+ str->qs_append('@');
+ str->qs_append(pv->offset);
+ }
+}
+
+
+/*
+ sp_instr_error class functions
+*/
+
+int
+sp_instr_error::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_error::execute");
+
+ my_message(m_errcode, ER(m_errcode), MYF(0));
+ *nextp= m_ip+1;
+ DBUG_RETURN(-1);
+}
+
+
+void
+sp_instr_error::print(String *str)
+{
+ /* error code */
+ if (str->reserve(SP_INSTR_UINT_MAXLEN+6))
+ return;
+ str->qs_append(STRING_WITH_LEN("error "));
+ str->qs_append(m_errcode);
+}
+
+
+/**************************************************************************
+ sp_instr_set_case_expr class implementation
+**************************************************************************/
+
+int
+sp_instr_set_case_expr::execute(THD *thd, uint *nextp)
+{
+ DBUG_ENTER("sp_instr_set_case_expr::execute");
+
+ DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
+}
+
+
+int
+sp_instr_set_case_expr::exec_core(THD *thd, uint *nextp)
+{
+ int res= thd->spcont->set_case_expr(thd, m_case_expr_id, &m_case_expr);
+
+ if (res && !thd->spcont->get_case_expr(m_case_expr_id))
+ {
+ /*
+ Failed to evaluate the value, the case expression is still not
+ initialized. Set to NULL so we can continue.
+ */
+
+ Item *null_item= new Item_null();
+
+ if (!null_item ||
+ thd->spcont->set_case_expr(thd, m_case_expr_id, &null_item))
+ {
+ /* If this also failed, we have to abort. */
+ my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+ }
+ }
+ else
+ *nextp= m_ip+1;
+
+ return res;
+}
+
+
+void
+sp_instr_set_case_expr::print(String *str)
+{
+ /* set_case_expr (cont) id ... */
+ str->reserve(2*SP_INSTR_UINT_MAXLEN+18+32); // Add some extra for expr too
+ str->qs_append(STRING_WITH_LEN("set_case_expr ("));
+ str->qs_append(m_cont_dest);
+ str->qs_append(STRING_WITH_LEN(") "));
+ str->qs_append(m_case_expr_id);
+ str->qs_append(' ');
+ m_case_expr->print(str, QT_ORDINARY);
+}
+
+uint
+sp_instr_set_case_expr::opt_mark(sp_head *sp, List<sp_instr> *leads)
+{
+ sp_instr *i;
+
+ marked= 1;
+ if ((i= sp->get_instr(m_cont_dest)))
+ {
+ m_cont_dest= i->opt_shortcut_jump(sp, this);
+ m_cont_optdest= sp->get_instr(m_cont_dest);
+ }
+ sp->add_mark_lead(m_cont_dest, leads);
+ return m_ip+1;
+}
+
+void
+sp_instr_set_case_expr::opt_move(uint dst, List<sp_instr> *bp)
+{
+ if (m_cont_dest > m_ip)
+ bp->push_back(this); // Forward
+ else if (m_cont_optdest)
+ m_cont_dest= m_cont_optdest->m_ip; // Backward
+ m_ip= dst;
+}
+
+
+/* ------------------------------------------------------------------ */
+
+
+/*
+ Structure that represent all instances of one table
+ in optimized multi-set of tables used by routine.
+*/
+
+typedef struct st_sp_table
+{
+ /*
+ Multi-set key:
+ db_name\0table_name\0alias\0 - for normal tables
+ db_name\0table_name\0 - for temporary tables
+ */
+ LEX_STRING qname;
+ uint db_length, table_name_length;
+ bool temp; /* true if corresponds to a temporary table */
+ thr_lock_type lock_type; /* lock type used for prelocking */
+ uint lock_count;
+ uint query_lock_count;
+ uint8 trg_event_map;
+} SP_TABLE;
+
+
+uchar *sp_table_key(const uchar *ptr, size_t *plen, my_bool first)
+{
+ SP_TABLE *tab= (SP_TABLE *)ptr;
+ *plen= tab->qname.length;
+ return (uchar *)tab->qname.str;
+}
+
+
+/**
+ Merge the list of tables used by some query into the multi-set of
+ tables used by routine.
+
+ @param thd thread context
+ @param table table list
+ @param lex_for_tmp_check LEX of the query for which we are merging
+ table list.
+
+ @note
+ This method will use LEX provided to check whenever we are creating
+ temporary table and mark it as such in target multi-set.
+
+ @retval
+ TRUE Success
+ @retval
+ FALSE Error
+*/
+
+bool
+sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check)
+{
+ SP_TABLE *tab;
+
+ if (lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE &&
+ lex_for_tmp_check->drop_temporary)
+ return TRUE;
+
+ for (uint i= 0 ; i < m_sptabs.records ; i++)
+ {
+ tab= (SP_TABLE*) my_hash_element(&m_sptabs, i);
+ tab->query_lock_count= 0;
+ }
+
+ for (; table ; table= table->next_global)
+ if (!table->derived && !table->schema_table)
+ {
+ /*
+ Structure of key for the multi-set is "db\0table\0alias\0".
+ Since "alias" part can have arbitrary length we use String
+ object to construct the key. By default String will use
+ buffer allocated on stack with NAME_LEN bytes reserved for
+ alias, since in most cases it is going to be smaller than
+ NAME_LEN bytes.
+ */
+ char tname_buff[(SAFE_NAME_LEN + 1) * 3];
+ String tname(tname_buff, sizeof(tname_buff), &my_charset_bin);
+ uint temp_table_key_length;
+
+ tname.length(0);
+ tname.append(table->db, table->db_length);
+ tname.append('\0');
+ tname.append(table->table_name, table->table_name_length);
+ tname.append('\0');
+ temp_table_key_length= tname.length();
+ tname.append(table->alias);
+ tname.append('\0');
+
+ /*
+ Upgrade the lock type because this table list will be used
+ only in pre-locked mode, in which DELAYED inserts are always
+ converted to normal inserts.
+ */
+ if (table->lock_type == TL_WRITE_DELAYED)
+ table->lock_type= TL_WRITE;
+
+ /*
+ We ignore alias when we check if table was already marked as temporary
+ (and therefore should not be prelocked). Otherwise we will erroneously
+ treat table with same name but with different alias as non-temporary.
+ */
+ if ((tab= (SP_TABLE*) my_hash_search(&m_sptabs, (uchar *)tname.ptr(),
+ tname.length())) ||
+ ((tab= (SP_TABLE*) my_hash_search(&m_sptabs, (uchar *)tname.ptr(),
+ temp_table_key_length)) &&
+ tab->temp))
+ {
+ if (tab->lock_type < table->lock_type)
+ tab->lock_type= table->lock_type; // Use the table with the highest lock type
+ tab->query_lock_count++;
+ if (tab->query_lock_count > tab->lock_count)
+ tab->lock_count++;
+ tab->trg_event_map|= table->trg_event_map;
+ }
+ else
+ {
+ if (!(tab= (SP_TABLE *)thd->calloc(sizeof(SP_TABLE))))
+ return FALSE;
+ if (lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE &&
+ lex_for_tmp_check->query_tables == table &&
+ lex_for_tmp_check->create_info.tmp_table())
+ {
+ tab->temp= TRUE;
+ tab->qname.length= temp_table_key_length;
+ }
+ else
+ tab->qname.length= tname.length();
+ tab->qname.str= (char*) thd->memdup(tname.ptr(), tab->qname.length);
+ if (!tab->qname.str)
+ return FALSE;
+ tab->table_name_length= table->table_name_length;
+ tab->db_length= table->db_length;
+ tab->lock_type= table->lock_type;
+ tab->lock_count= tab->query_lock_count= 1;
+ tab->trg_event_map= table->trg_event_map;
+ if (my_hash_insert(&m_sptabs, (uchar *)tab))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+/**
+ Add tables used by routine to the table list.
+
+ Converts multi-set of tables used by this routine to table list and adds
+ this list to the end of table list specified by 'query_tables_last_ptr'.
+
+ Elements of list will be allocated in PS memroot, so this list will be
+ persistent between PS executions.
+
+ @param[in] thd Thread context
+ @param[in,out] query_tables_last_ptr Pointer to the next_global member of
+ last element of the list where tables
+ will be added (or to its root).
+ @param[in] belong_to_view Uppermost view which uses this routine,
+ 0 if none.
+
+ @retval
+ TRUE if some elements were added
+ @retval
+ FALSE otherwise.
+*/
+
+bool
+sp_head::add_used_tables_to_table_list(THD *thd,
+ TABLE_LIST ***query_tables_last_ptr,
+ TABLE_LIST *belong_to_view)
+{
+ uint i;
+ Query_arena *arena, backup;
+ bool result= FALSE;
+ DBUG_ENTER("sp_head::add_used_tables_to_table_list");
+
+ /*
+ Use persistent arena for table list allocation to be PS/SP friendly.
+ Note that we also have to copy database/table names and alias to PS/SP
+ memory since current instance of sp_head object can pass away before
+ next execution of PS/SP for which tables are added to prelocking list.
+ This will be fixed by introducing of proper invalidation mechanism
+ once new TDC is ready.
+ */
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ for (i=0 ; i < m_sptabs.records ; i++)
+ {
+ char *tab_buff, *key_buff;
+ TABLE_LIST *table;
+ SP_TABLE *stab= (SP_TABLE*) my_hash_element(&m_sptabs, i);
+ if (stab->temp)
+ continue;
+
+ if (!(tab_buff= (char *)thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST)) *
+ stab->lock_count)) ||
+ !(key_buff= (char*)thd->memdup(stab->qname.str,
+ stab->qname.length)))
+ DBUG_RETURN(FALSE);
+
+ for (uint j= 0; j < stab->lock_count; j++)
+ {
+ table= (TABLE_LIST *)tab_buff;
+
+ table->db= key_buff;
+ table->db_length= stab->db_length;
+ table->table_name= table->db + table->db_length + 1;
+ table->table_name_length= stab->table_name_length;
+ table->alias= table->table_name + table->table_name_length + 1;
+ table->lock_type= stab->lock_type;
+ table->cacheable_table= 1;
+ table->prelocking_placeholder= 1;
+ table->belong_to_view= belong_to_view;
+ table->trg_event_map= stab->trg_event_map;
+ /*
+ Since we don't allow DDL on base tables in prelocked mode it
+ is safe to infer the type of metadata lock from the type of
+ table lock.
+ */
+ table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
+ table->lock_type >= TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ,
+ MDL_TRANSACTION);
+
+ /* Everyting else should be zeroed */
+
+ **query_tables_last_ptr= table;
+ table->prev_global= *query_tables_last_ptr;
+ *query_tables_last_ptr= &table->next_global;
+
+ tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST));
+ result= TRUE;
+ }
+ }
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Simple function for adding an explicitly named (systems) table to
+ the global table list, e.g. "mysql", "proc".
+*/
+
+TABLE_LIST *
+sp_add_to_query_tables(THD *thd, LEX *lex,
+ const char *db, const char *name,
+ thr_lock_type locktype,
+ enum_mdl_type mdl_type)
+{
+ TABLE_LIST *table;
+
+ if (!(table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST))))
+ return NULL;
+ table->db_length= strlen(db);
+ table->db= thd->strmake(db, table->db_length);
+ table->table_name_length= strlen(name);
+ table->table_name= thd->strmake(name, table->table_name_length);
+ table->alias= thd->strdup(name);
+ table->lock_type= locktype;
+ table->select_lex= lex->current_select;
+ table->cacheable_table= 1;
+ table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
+ mdl_type, MDL_TRANSACTION);
+
+ lex->add_to_query_tables(table);
+ return table;
+}
+
diff --git a/sql/sp_head.h b/sql/sp_head.h
new file mode 100644
index 00000000000..dbdb957aa79
--- /dev/null
+++ b/sql/sp_head.h
@@ -0,0 +1,1414 @@
+/* -*- C++ -*- */
+/*
+ Copyright (c) 2002, 2011, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifndef _SP_HEAD_H_
+#define _SP_HEAD_H_
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+/*
+ It is necessary to include set_var.h instead of item.h because there
+ are dependencies on include order for set_var.h and item.h. This
+ will be resolved later.
+*/
+#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_class.h" // THD, set_var.h: THD
+#include "set_var.h" // Item
+#include "sp_pcontext.h" // sp_pcontext
+#include <stddef.h>
+#include "sp.h"
+
+/**
+ @defgroup Stored_Routines Stored Routines
+ @ingroup Runtime_Environment
+ @{
+*/
+
+// Values for the type enum. This reflects the order of the enum declaration
+// in the CREATE TABLE command.
+//#define TYPE_ENUM_FUNCTION 1 #define TYPE_ENUM_PROCEDURE 2 #define
+//TYPE_ENUM_TRIGGER 3 #define TYPE_ENUM_PROXY 4
+
+Item_result
+sp_map_result_type(enum enum_field_types type);
+
+Item::Type
+sp_map_item_type(enum enum_field_types type);
+
+uint
+sp_get_flags_for_command(LEX *lex);
+
+class sp_instr;
+class sp_instr_opt_meta;
+class sp_instr_jump_if_not;
+
+/*************************************************************************/
+
+/**
+ Stored_program_creation_ctx -- base class for creation context of stored
+ programs (stored routines, triggers, events).
+*/
+
+class Stored_program_creation_ctx :public Default_object_creation_ctx
+{
+public:
+ CHARSET_INFO *get_db_cl()
+ {
+ return m_db_cl;
+ }
+
+public:
+ virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root) = 0;
+
+protected:
+ Stored_program_creation_ctx(THD *thd)
+ : Default_object_creation_ctx(thd),
+ m_db_cl(thd->variables.collation_database)
+ { }
+
+ Stored_program_creation_ctx(CHARSET_INFO *client_cs,
+ CHARSET_INFO *connection_cl,
+ CHARSET_INFO *db_cl)
+ : Default_object_creation_ctx(client_cs, connection_cl),
+ m_db_cl(db_cl)
+ { }
+
+protected:
+ virtual void change_env(THD *thd) const
+ {
+ thd->variables.collation_database= m_db_cl;
+
+ Default_object_creation_ctx::change_env(thd);
+ }
+
+protected:
+ /**
+ db_cl stores the value of the database collation. Both character set
+ and collation attributes are used.
+
+ Database collation is included into the context because it defines the
+ default collation for stored-program variables.
+ */
+ CHARSET_INFO *m_db_cl;
+};
+
+/*************************************************************************/
+
+class sp_name : public Sql_alloc
+{
+public:
+
+ LEX_STRING m_db;
+ LEX_STRING m_name;
+ LEX_STRING m_qname;
+ bool m_explicit_name; /**< Prepend the db name? */
+
+ sp_name(LEX_STRING db, LEX_STRING name, bool use_explicit_name)
+ : m_db(db), m_name(name), m_explicit_name(use_explicit_name)
+ {
+ if (lower_case_table_names && m_db.str)
+ m_db.length= my_casedn_str(files_charset_info, m_db.str);
+ m_qname.str= 0;
+ m_qname.length= 0;
+ }
+
+ /** Create temporary sp_name object from MDL key. */
+ sp_name(const MDL_key *key, char *qname_buff);
+
+ // Init. the qualified name from the db and name.
+ void init_qname(THD *thd); // thd for memroot allocation
+
+ ~sp_name()
+ {}
+};
+
+
+bool
+check_routine_name(LEX_STRING *ident);
+
+class sp_head :private Query_arena
+{
+ sp_head(const sp_head &); /**< Prevent use of these */
+ void operator=(sp_head &);
+
+ MEM_ROOT main_mem_root;
+public:
+ /** Possible values of m_flags */
+ enum {
+ HAS_RETURN= 1, // For FUNCTIONs only: is set if has RETURN
+ MULTI_RESULTS= 8, // Is set if a procedure with SELECT(s)
+ CONTAINS_DYNAMIC_SQL= 16, // Is set if a procedure with PREPARE/EXECUTE
+ IS_INVOKED= 32, // Is set if this sp_head is being used
+ HAS_SET_AUTOCOMMIT_STMT= 64,// Is set if a procedure with 'set autocommit'
+ /* Is set if a procedure with COMMIT (implicit or explicit) | ROLLBACK */
+ HAS_COMMIT_OR_ROLLBACK= 128,
+ LOG_SLOW_STATEMENTS= 256, // Used by events
+ LOG_GENERAL_LOG= 512, // Used by events
+ HAS_SQLCOM_RESET= 1024,
+ HAS_SQLCOM_FLUSH= 2048,
+
+ /**
+ Marks routines that directly (i.e. not by calling other routines)
+ change tables. Note that this flag is set automatically based on
+ type of statements used in the stored routine and is different
+ from routine characteristic provided by user in a form of CONTAINS
+ SQL, READS SQL DATA, MODIFIES SQL DATA clauses. The latter are
+ accepted by parser but pretty much ignored after that.
+ We don't rely on them:
+ a) for compatibility reasons.
+ b) because in CONTAINS SQL case they don't provide enough
+ information anyway.
+ */
+ MODIFIES_DATA= 4096
+ };
+
+ stored_procedure_type m_type;
+ uint m_flags; // Boolean attributes of a stored routine
+
+ Create_field m_return_field_def; /**< This is used for FUNCTIONs only. */
+
+ const char *m_tmp_query; ///< Temporary pointer to sub query string
+ st_sp_chistics *m_chistics;
+ ulonglong m_sql_mode; ///< For SHOW CREATE and execution
+ LEX_STRING m_qname; ///< db.name
+ bool m_explicit_name; ///< Prepend the db name? */
+ LEX_STRING m_db;
+ LEX_STRING m_name;
+ LEX_STRING m_params;
+ LEX_STRING m_body;
+ LEX_STRING m_body_utf8;
+ LEX_STRING m_defstr;
+ LEX_STRING m_definer_user;
+ LEX_STRING m_definer_host;
+
+ /**
+ Is this routine being executed?
+ */
+ bool is_invoked() const { return m_flags & IS_INVOKED; }
+
+ /**
+ Get the value of the SP cache version, as remembered
+ when the routine was inserted into the cache.
+ */
+ ulong sp_cache_version() const { return m_sp_cache_version; }
+
+ /** Set the value of the SP cache version. */
+ void set_sp_cache_version(ulong version_arg)
+ {
+ m_sp_cache_version= version_arg;
+ }
+private:
+ /**
+ Version of the stored routine cache at the moment when the
+ routine was added to it. Is used only for functions and
+ procedures, not used for triggers or events. When sp_head is
+ created, its version is 0. When it's added to the cache, the
+ version is assigned the global value 'Cversion'.
+ If later on Cversion is incremented, we know that the routine
+ is obsolete and should not be used --
+ sp_cache_flush_obsolete() will purge it.
+ */
+ ulong m_sp_cache_version;
+ Stored_program_creation_ctx *m_creation_ctx;
+ /**
+ Boolean combination of (1<<flag), where flag is a member of
+ LEX::enum_binlog_stmt_unsafe.
+ */
+ uint32 unsafe_flags;
+
+public:
+ inline Stored_program_creation_ctx *get_creation_ctx()
+ {
+ return m_creation_ctx;
+ }
+
+ inline void set_creation_ctx(Stored_program_creation_ctx *creation_ctx)
+ {
+ m_creation_ctx= creation_ctx->clone(mem_root);
+ }
+
+ longlong m_created;
+ longlong m_modified;
+ /** Recursion level of the current SP instance. The levels are numbered from 0 */
+ ulong m_recursion_level;
+ /**
+ A list of diferent recursion level instances for the same procedure.
+ For every recursion level we have a sp_head instance. This instances
+ connected in the list. The list ordered by increasing recursion level
+ (m_recursion_level).
+ */
+ sp_head *m_next_cached_sp;
+ /**
+ Pointer to the first element of the above list
+ */
+ sp_head *m_first_instance;
+ /**
+ Pointer to the first free (non-INVOKED) routine in the list of
+ cached instances for this SP. This pointer is set only for the first
+ SP in the list of instences (see above m_first_cached_sp pointer).
+ The pointer equal to 0 if we have no free instances.
+ For non-first instance value of this pointer meanless (point to itself);
+ */
+ sp_head *m_first_free_instance;
+ /**
+ Pointer to the last element in the list of instances of the SP.
+ For non-first instance value of this pointer meanless (point to itself);
+ */
+ sp_head *m_last_cached_sp;
+ /**
+ Set containing names of stored routines used by this routine.
+ Note that unlike elements of similar set for statement elements of this
+ set are not linked in one list. Because of this we are able save memory
+ by using for this set same objects that are used in 'sroutines' sets
+ for statements of which this stored routine consists.
+ */
+ HASH m_sroutines;
+ // Pointers set during parsing
+ const char *m_param_begin;
+ const char *m_param_end;
+
+private:
+ const char *m_body_begin;
+
+public:
+ /*
+ Security context for stored routine which should be run under
+ definer privileges.
+ */
+ Security_context m_security_ctx;
+
+ /**
+ List of all items (Item_trigger_field objects) representing fields in
+ old/new version of row in trigger. We use this list for checking whenever
+ all such fields are valid at trigger creation time and for binding these
+ fields to TABLE object at table open (although for latter pointer to table
+ being opened is probably enough).
+ */
+ SQL_I_List<Item_trigger_field> m_trg_table_fields;
+
+ static void *
+ operator new(size_t size) throw ();
+
+ static void
+ operator delete(void *ptr, size_t size) throw ();
+
+ sp_head();
+
+ /// Initialize after we have reset mem_root
+ void
+ init(LEX *lex);
+
+ /** Copy sp name from parser. */
+ void
+ init_sp_name(THD *thd, sp_name *spname);
+
+ /** Set the body-definition start position. */
+ void
+ set_body_start(THD *thd, const char *begin_ptr);
+
+ /** Set the statement-definition (body-definition) end position. */
+ void
+ set_stmt_end(THD *thd);
+
+ virtual ~sp_head();
+
+ bool
+ execute_trigger(THD *thd,
+ const LEX_STRING *db_name,
+ const LEX_STRING *table_name,
+ GRANT_INFO *grant_info);
+
+ bool
+ execute_function(THD *thd, Item **args, uint argcount, Field *return_fld);
+
+ bool
+ execute_procedure(THD *thd, List<Item> *args);
+
+ bool
+ show_create_routine(THD *thd, int type);
+
+ int
+ add_instr(sp_instr *instr);
+
+ /**
+ Returns true if any substatement in the routine directly
+ (not through another routine) modifies data/changes table.
+
+ @sa Comment for MODIFIES_DATA flag.
+ */
+ bool modifies_data() const
+ { return m_flags & MODIFIES_DATA; }
+
+ inline uint instructions()
+ { return m_instr.elements; }
+
+ inline sp_instr *
+ last_instruction()
+ {
+ sp_instr *i;
+
+ get_dynamic(&m_instr, (uchar*)&i, m_instr.elements-1);
+ return i;
+ }
+
+ /*
+ Resets lex in 'thd' and keeps a copy of the old one.
+
+ @todo Conflicting comment in sp_head.cc
+ */
+ bool
+ reset_lex(THD *thd);
+
+ /**
+ Restores lex in 'thd' from our copy, but keeps some status from the
+ one in 'thd', like ptr, tables, fields, etc.
+
+ @todo Conflicting comment in sp_head.cc
+ */
+ bool
+ restore_lex(THD *thd);
+
+ /// Put the instruction on the backpatch list, associated with the label.
+ int
+ push_backpatch(sp_instr *, sp_label *);
+
+ /// Update all instruction with this label in the backpatch list to
+ /// the current position.
+ void
+ backpatch(sp_label *);
+
+ /// Start a new cont. backpatch level. If 'i' is NULL, the level is just incr.
+ int
+ new_cont_backpatch(sp_instr_opt_meta *i);
+
+ /// Add an instruction to the current level
+ int
+ add_cont_backpatch(sp_instr_opt_meta *i);
+
+ /// Backpatch (and pop) the current level to the current position.
+ void
+ do_cont_backpatch();
+
+ char *name(uint *lenp = 0) const
+ {
+ if (lenp)
+ *lenp= (uint) m_name.length;
+ return m_name.str;
+ }
+
+ char *create_string(THD *thd, ulong *lenp);
+
+ Field *create_result_field(uint field_max_length, const char *field_name,
+ TABLE *table);
+
+ bool fill_field_definition(THD *thd, LEX *lex,
+ enum enum_field_types field_type,
+ Create_field *field_def);
+
+ void set_info(longlong created, longlong modified,
+ st_sp_chistics *chistics, ulonglong sql_mode);
+
+ void set_definer(const char *definer, uint definerlen);
+ void set_definer(const LEX_STRING *user_name, const LEX_STRING *host_name);
+
+ void reset_thd_mem_root(THD *thd);
+
+ void restore_thd_mem_root(THD *thd);
+
+ /**
+ Optimize the code.
+ */
+ void optimize();
+
+ /**
+ Helper used during flow analysis during code optimization.
+ See the implementation of <code>opt_mark()</code>.
+ @param ip the instruction to add to the leads list
+ @param leads the list of remaining paths to explore in the graph that
+ represents the code, during flow analysis.
+ */
+ void add_mark_lead(uint ip, List<sp_instr> *leads);
+
+ void recursion_level_error(THD *thd);
+
+ inline sp_instr *
+ get_instr(uint i)
+ {
+ sp_instr *ip;
+
+ if (i < m_instr.elements)
+ get_dynamic(&m_instr, (uchar*)&ip, i);
+ else
+ ip= NULL;
+ return ip;
+ }
+
+ /* Add tables used by routine to the table list. */
+ bool add_used_tables_to_table_list(THD *thd,
+ TABLE_LIST ***query_tables_last_ptr,
+ TABLE_LIST *belong_to_view);
+
+ /**
+ Check if this stored routine contains statements disallowed
+ in a stored function or trigger, and set an appropriate error message
+ if this is the case.
+ */
+ bool is_not_allowed_in_function(const char *where)
+ {
+ if (m_flags & CONTAINS_DYNAMIC_SQL)
+ my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "Dynamic SQL");
+ else if (m_flags & MULTI_RESULTS)
+ my_error(ER_SP_NO_RETSET, MYF(0), where);
+ else if (m_flags & HAS_SET_AUTOCOMMIT_STMT)
+ my_error(ER_SP_CANT_SET_AUTOCOMMIT, MYF(0));
+ else if (m_flags & HAS_COMMIT_OR_ROLLBACK)
+ my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
+ else if (m_flags & HAS_SQLCOM_RESET)
+ my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "RESET");
+ else if (m_flags & HAS_SQLCOM_FLUSH)
+ my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "FLUSH");
+
+ return MY_TEST(m_flags &
+ (CONTAINS_DYNAMIC_SQL | MULTI_RESULTS |
+ HAS_SET_AUTOCOMMIT_STMT | HAS_COMMIT_OR_ROLLBACK |
+ HAS_SQLCOM_RESET | HAS_SQLCOM_FLUSH));
+ }
+
+#ifndef DBUG_OFF
+ int show_routine_code(THD *thd);
+#endif
+
+ /*
+ This method is intended for attributes of a routine which need
+ to propagate upwards to the Query_tables_list of the caller (when
+ a property of a sp_head needs to "taint" the calling statement).
+ */
+ void propagate_attributes(Query_tables_list *prelocking_ctx)
+ {
+ DBUG_ENTER("sp_head::propagate_attributes");
+ /*
+ If this routine needs row-based binary logging, the entire top statement
+ too (we cannot switch from statement-based to row-based only for this
+ routine, as in statement-based the top-statement may be binlogged and
+ the substatements not).
+ */
+ DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags(): 0x%x",
+ prelocking_ctx->get_stmt_unsafe_flags()));
+ DBUG_PRINT("info", ("sp_head(0x%p=%s)->unsafe_flags: 0x%x",
+ this, name(), unsafe_flags));
+ prelocking_ctx->set_stmt_unsafe_flags(unsafe_flags);
+ DBUG_VOID_RETURN;
+ }
+
+ sp_pcontext *get_parse_context() { return m_pcont; }
+
+private:
+
+ MEM_ROOT *m_thd_root; ///< Temp. store for thd's mem_root
+ THD *m_thd; ///< Set if we have reset mem_root
+
+ sp_pcontext *m_pcont; ///< Parse context
+ List<LEX> m_lex; ///< Temp. store for the other lex
+ DYNAMIC_ARRAY m_instr; ///< The "instructions"
+ typedef struct
+ {
+ sp_label *lab;
+ sp_instr *instr;
+ } bp_t;
+ List<bp_t> m_backpatch; ///< Instructions needing backpatching
+ /**
+ We need a special list for backpatching of instructions with a continue
+ destination (in the case of a continue handler catching an error in
+ the test), since it would otherwise interfere with the normal backpatch
+ mechanism - e.g. jump_if_not instructions have two different destinations
+ which are to be patched differently.
+ Since these occur in a more restricted way (always the same "level" in
+ the code), we don't need the label.
+ */
+ List<sp_instr_opt_meta> m_cont_backpatch;
+ uint m_cont_level; // The current cont. backpatch level
+
+ /**
+ Multi-set representing optimized list of tables to be locked by this
+ routine. Does not include tables which are used by invoked routines.
+
+ @note
+ For prelocking-free SPs this multiset is constructed too.
+ We do so because the same instance of sp_head may be called both
+ in prelocked mode and in non-prelocked mode.
+ */
+ HASH m_sptabs;
+
+ bool
+ execute(THD *thd, bool merge_da_on_success);
+
+ /**
+ Perform a forward flow analysis in the generated code.
+ Mark reachable instructions, for the optimizer.
+ */
+ void opt_mark();
+
+ /**
+ Merge the list of tables used by query into the multi-set of tables used
+ by routine.
+ */
+ bool merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check);
+}; // class sp_head : public Sql_alloc
+
+
+//
+// "Instructions"...
+//
+
+class sp_instr :public Query_arena, public Sql_alloc
+{
+ sp_instr(const sp_instr &); /**< Prevent use of these */
+ void operator=(sp_instr &);
+
+public:
+
+ uint marked;
+ uint m_ip; ///< My index
+ sp_pcontext *m_ctx; ///< My parse context
+
+ /// Should give each a name or type code for debugging purposes?
+ sp_instr(uint ip, sp_pcontext *ctx)
+ :Query_arena(0, STMT_INITIALIZED_FOR_SP), marked(0), m_ip(ip), m_ctx(ctx)
+ {}
+
+ virtual ~sp_instr()
+ { free_items(); }
+
+
+ /**
+ Execute this instruction
+
+
+ @param thd Thread handle
+ @param[out] nextp index of the next instruction to execute. (For most
+ instructions this will be the instruction following this
+ one). Note that this parameter is undefined in case of
+ errors, use get_cont_dest() to find the continuation
+ instruction for CONTINUE error handlers.
+
+ @retval 0 on success,
+ @retval other if some error occured
+ */
+
+ virtual int execute(THD *thd, uint *nextp) = 0;
+
+ /**
+ Execute <code>open_and_lock_tables()</code> for this statement.
+ Open and lock the tables used by this statement, as a pre-requisite
+ to execute the core logic of this instruction with
+ <code>exec_core()</code>.
+ @param thd the current thread
+ @param tables the list of tables to open and lock
+ @return zero on success, non zero on failure.
+ */
+ int exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables);
+
+ /**
+ Get the continuation destination of this instruction.
+ @return the continuation destination
+ */
+ virtual uint get_cont_dest() const;
+
+ /*
+ Execute core function of instruction after all preparations (e.g.
+ setting of proper LEX, saving part of the thread context have been
+ done).
+
+ Should be implemented for instructions using expressions or whole
+ statements (thus having to have own LEX). Used in concert with
+ sp_lex_keeper class and its descendants (there are none currently).
+ */
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str) = 0;
+
+ virtual void backpatch(uint dest, sp_pcontext *dst_ctx)
+ {}
+
+ /**
+ Mark this instruction as reachable during optimization and return the
+ index to the next instruction. Jump instruction will add their
+ destination to the leads list.
+ */
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads)
+ {
+ marked= 1;
+ return m_ip+1;
+ }
+
+ /**
+ Short-cut jumps to jumps during optimization. This is used by the
+ jump instructions' opt_mark() methods. 'start' is the starting point,
+ used to prevent the mark sweep from looping for ever. Return the
+ end destination.
+ */
+ virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start)
+ {
+ return m_ip;
+ }
+
+ /**
+ Inform the instruction that it has been moved during optimization.
+ Most instructions will simply update its index, but jump instructions
+ must also take care of their destination pointers. Forward jumps get
+ pushed to the backpatch list 'ibp'.
+ */
+ virtual void opt_move(uint dst, List<sp_instr> *ibp)
+ {
+ m_ip= dst;
+ }
+
+}; // class sp_instr : public Sql_alloc
+
+
+/**
+ Auxilary class to which instructions delegate responsibility
+ for handling LEX and preparations before executing statement
+ or calculating complex expression.
+
+ Exist mainly to avoid having double hierarchy between instruction
+ classes.
+
+ @todo
+ Add ability to not store LEX and do any preparations if
+ expression used is simple.
+*/
+
+class sp_lex_keeper
+{
+ /** Prevent use of these */
+ sp_lex_keeper(const sp_lex_keeper &);
+ void operator=(sp_lex_keeper &);
+public:
+
+ sp_lex_keeper(LEX *lex, bool lex_resp)
+ : m_lex(lex), m_lex_resp(lex_resp),
+ lex_query_tables_own_last(NULL)
+ {
+ lex->sp_lex_in_use= TRUE;
+ }
+ virtual ~sp_lex_keeper()
+ {
+ if (m_lex_resp)
+ {
+ /* Prevent endless recursion. */
+ m_lex->sphead= NULL;
+ lex_end(m_lex);
+ delete m_lex;
+ }
+ }
+
+ /**
+ Prepare execution of instruction using LEX, if requested check whenever
+ we have read access to tables used and open/lock them, call instruction's
+ exec_core() method, perform cleanup afterwards.
+
+ @todo Conflicting comment in sp_head.cc
+ */
+ int reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables,
+ sp_instr* instr);
+
+ inline uint sql_command() const
+ {
+ return (uint)m_lex->sql_command;
+ }
+
+ void disable_query_cache()
+ {
+ m_lex->safe_to_cache_query= 0;
+ }
+private:
+
+ LEX *m_lex;
+ /**
+ Indicates whenever this sp_lex_keeper instance responsible
+ for LEX deletion.
+ */
+ bool m_lex_resp;
+
+ /*
+ Support for being able to execute this statement in two modes:
+ a) inside prelocked mode set by the calling procedure or its ancestor.
+ b) outside of prelocked mode, when this statement enters/leaves
+ prelocked mode itself.
+ */
+
+ /**
+ List of additional tables this statement needs to lock when it
+ enters/leaves prelocked mode on its own.
+ */
+ TABLE_LIST *prelocking_tables;
+
+ /**
+ The value m_lex->query_tables_own_last should be set to this when the
+ statement enters/leaves prelocked mode on its own.
+ */
+ TABLE_LIST **lex_query_tables_own_last;
+};
+
+
+/**
+ Call out to some prepared SQL statement.
+*/
+class sp_instr_stmt : public sp_instr
+{
+ sp_instr_stmt(const sp_instr_stmt &); /**< Prevent use of these */
+ void operator=(sp_instr_stmt &);
+
+public:
+
+ LEX_STRING m_query; ///< For thd->query
+
+ sp_instr_stmt(uint ip, sp_pcontext *ctx, LEX *lex)
+ : sp_instr(ip, ctx), m_lex_keeper(lex, TRUE)
+ {
+ m_query.str= 0;
+ m_query.length= 0;
+ }
+
+ virtual ~sp_instr_stmt()
+ {};
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+private:
+
+ sp_lex_keeper m_lex_keeper;
+
+}; // class sp_instr_stmt : public sp_instr
+
+
+class sp_instr_set : public sp_instr
+{
+ sp_instr_set(const sp_instr_set &); /**< Prevent use of these */
+ void operator=(sp_instr_set &);
+
+public:
+
+ sp_instr_set(uint ip, sp_pcontext *ctx,
+ uint offset, Item *val, enum enum_field_types type_arg,
+ LEX *lex, bool lex_resp)
+ : sp_instr(ip, ctx), m_offset(offset), m_value(val), m_type(type_arg),
+ m_lex_keeper(lex, lex_resp)
+ {}
+
+ virtual ~sp_instr_set()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+private:
+
+ uint m_offset; ///< Frame offset
+ Item *m_value;
+ enum enum_field_types m_type; ///< The declared type
+ sp_lex_keeper m_lex_keeper;
+
+}; // class sp_instr_set : public sp_instr
+
+
+/**
+ Set NEW/OLD row field value instruction. Used in triggers.
+*/
+class sp_instr_set_trigger_field : public sp_instr
+{
+ sp_instr_set_trigger_field(const sp_instr_set_trigger_field &);
+ void operator=(sp_instr_set_trigger_field &);
+
+public:
+
+ sp_instr_set_trigger_field(uint ip, sp_pcontext *ctx,
+ Item_trigger_field *trg_fld,
+ Item *val, LEX *lex)
+ : sp_instr(ip, ctx),
+ trigger_field(trg_fld),
+ value(val), m_lex_keeper(lex, TRUE)
+ {}
+
+ virtual ~sp_instr_set_trigger_field()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+private:
+ Item_trigger_field *trigger_field;
+ Item *value;
+ sp_lex_keeper m_lex_keeper;
+}; // class sp_instr_trigger_field : public sp_instr
+
+
+/**
+ An abstract class for all instructions with destinations that
+ needs to be updated by the optimizer.
+
+ Even if not all subclasses will use both the normal destination and
+ the continuation destination, we put them both here for simplicity.
+*/
+class sp_instr_opt_meta : public sp_instr
+{
+public:
+
+ uint m_dest; ///< Where we will go
+ uint m_cont_dest; ///< Where continue handlers will go
+
+ sp_instr_opt_meta(uint ip, sp_pcontext *ctx)
+ : sp_instr(ip, ctx),
+ m_dest(0), m_cont_dest(0), m_optdest(0), m_cont_optdest(0)
+ {}
+
+ sp_instr_opt_meta(uint ip, sp_pcontext *ctx, uint dest)
+ : sp_instr(ip, ctx),
+ m_dest(dest), m_cont_dest(0), m_optdest(0), m_cont_optdest(0)
+ {}
+
+ virtual ~sp_instr_opt_meta()
+ {}
+
+ virtual void set_destination(uint old_dest, uint new_dest)
+ = 0;
+
+ virtual uint get_cont_dest() const;
+
+protected:
+
+ sp_instr *m_optdest; ///< Used during optimization
+ sp_instr *m_cont_optdest; ///< Used during optimization
+
+}; // class sp_instr_opt_meta : public sp_instr
+
+class sp_instr_jump : public sp_instr_opt_meta
+{
+ sp_instr_jump(const sp_instr_jump &); /**< Prevent use of these */
+ void operator=(sp_instr_jump &);
+
+public:
+
+ sp_instr_jump(uint ip, sp_pcontext *ctx)
+ : sp_instr_opt_meta(ip, ctx)
+ {}
+
+ sp_instr_jump(uint ip, sp_pcontext *ctx, uint dest)
+ : sp_instr_opt_meta(ip, ctx, dest)
+ {}
+
+ virtual ~sp_instr_jump()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads);
+
+ virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start);
+
+ virtual void opt_move(uint dst, List<sp_instr> *ibp);
+
+ virtual void backpatch(uint dest, sp_pcontext *dst_ctx)
+ {
+ /* Calling backpatch twice is a logic flaw in jump resolution. */
+ DBUG_ASSERT(m_dest == 0);
+ m_dest= dest;
+ }
+
+ /**
+ Update the destination; used by the optimizer.
+ */
+ virtual void set_destination(uint old_dest, uint new_dest)
+ {
+ if (m_dest == old_dest)
+ m_dest= new_dest;
+ }
+
+}; // class sp_instr_jump : public sp_instr_opt_meta
+
+
+class sp_instr_jump_if_not : public sp_instr_jump
+{
+ sp_instr_jump_if_not(const sp_instr_jump_if_not &); /**< Prevent use of these */
+ void operator=(sp_instr_jump_if_not &);
+
+public:
+
+ sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, LEX *lex)
+ : sp_instr_jump(ip, ctx), m_expr(i),
+ m_lex_keeper(lex, TRUE)
+ {}
+
+ sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, uint dest, LEX *lex)
+ : sp_instr_jump(ip, ctx, dest), m_expr(i),
+ m_lex_keeper(lex, TRUE)
+ {}
+
+ virtual ~sp_instr_jump_if_not()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads);
+
+ /** Override sp_instr_jump's shortcut; we stop here */
+ virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start)
+ {
+ return m_ip;
+ }
+
+ virtual void opt_move(uint dst, List<sp_instr> *ibp);
+
+ virtual void set_destination(uint old_dest, uint new_dest)
+ {
+ sp_instr_jump::set_destination(old_dest, new_dest);
+ if (m_cont_dest == old_dest)
+ m_cont_dest= new_dest;
+ }
+
+private:
+
+ Item *m_expr; ///< The condition
+ sp_lex_keeper m_lex_keeper;
+
+}; // class sp_instr_jump_if_not : public sp_instr_jump
+
+
+class sp_instr_freturn : public sp_instr
+{
+ sp_instr_freturn(const sp_instr_freturn &); /**< Prevent use of these */
+ void operator=(sp_instr_freturn &);
+
+public:
+
+ sp_instr_freturn(uint ip, sp_pcontext *ctx,
+ Item *val, enum enum_field_types type_arg, LEX *lex)
+ : sp_instr(ip, ctx), m_value(val), m_type(type_arg),
+ m_lex_keeper(lex, TRUE)
+ {}
+
+ virtual ~sp_instr_freturn()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads)
+ {
+ marked= 1;
+ return UINT_MAX;
+ }
+
+protected:
+
+ Item *m_value;
+ enum enum_field_types m_type;
+ sp_lex_keeper m_lex_keeper;
+
+}; // class sp_instr_freturn : public sp_instr
+
+
+class sp_instr_hpush_jump : public sp_instr_jump
+{
+ sp_instr_hpush_jump(const sp_instr_hpush_jump &); /**< Prevent use of these */
+ void operator=(sp_instr_hpush_jump &);
+
+public:
+
+ sp_instr_hpush_jump(uint ip,
+ sp_pcontext *ctx,
+ sp_handler *handler)
+ :sp_instr_jump(ip, ctx),
+ m_handler(handler),
+ m_opt_hpop(0),
+ m_frame(ctx->current_var_count())
+ {
+ DBUG_ASSERT(m_handler->condition_values.elements == 0);
+ }
+
+ virtual ~sp_instr_hpush_jump()
+ {
+ m_handler->condition_values.empty();
+ m_handler= NULL;
+ }
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads);
+
+ /** Override sp_instr_jump's shortcut; we stop here. */
+ virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start)
+ {
+ return m_ip;
+ }
+
+ virtual void backpatch(uint dest, sp_pcontext *dst_ctx)
+ {
+ DBUG_ASSERT(!m_dest || !m_opt_hpop);
+ if (!m_dest)
+ m_dest= dest;
+ else
+ m_opt_hpop= dest;
+ }
+
+ void add_condition(sp_condition_value *condition_value)
+ { m_handler->condition_values.push_back(condition_value); }
+
+ sp_handler *get_handler()
+ { return m_handler; }
+
+private:
+
+private:
+ /// Handler.
+ sp_handler *m_handler;
+
+ /// hpop marking end of handler scope.
+ uint m_opt_hpop;
+
+ // This attribute is needed for SHOW PROCEDURE CODE only (i.e. it's needed in
+ // debug version only). It's used in print().
+ uint m_frame;
+
+}; // class sp_instr_hpush_jump : public sp_instr_jump
+
+
+class sp_instr_hpop : public sp_instr
+{
+ sp_instr_hpop(const sp_instr_hpop &); /**< Prevent use of these */
+ void operator=(sp_instr_hpop &);
+
+public:
+
+ sp_instr_hpop(uint ip, sp_pcontext *ctx, uint count)
+ : sp_instr(ip, ctx), m_count(count)
+ {}
+
+ virtual ~sp_instr_hpop()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+private:
+
+ uint m_count;
+
+}; // class sp_instr_hpop : public sp_instr
+
+
+class sp_instr_hreturn : public sp_instr_jump
+{
+ sp_instr_hreturn(const sp_instr_hreturn &); /**< Prevent use of these */
+ void operator=(sp_instr_hreturn &);
+
+public:
+
+ sp_instr_hreturn(uint ip, sp_pcontext *ctx)
+ :sp_instr_jump(ip, ctx),
+ m_frame(ctx->current_var_count())
+ {}
+
+ virtual ~sp_instr_hreturn()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ /* This instruction will not be short cut optimized. */
+ virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start)
+ {
+ return m_ip;
+ }
+
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads);
+
+private:
+
+ uint m_frame;
+
+}; // class sp_instr_hreturn : public sp_instr_jump
+
+
+/** This is DECLARE CURSOR */
+class sp_instr_cpush : public sp_instr
+{
+ sp_instr_cpush(const sp_instr_cpush &); /**< Prevent use of these */
+ void operator=(sp_instr_cpush &);
+
+public:
+
+ sp_instr_cpush(uint ip, sp_pcontext *ctx, LEX *lex, uint offset)
+ : sp_instr(ip, ctx), m_lex_keeper(lex, TRUE), m_cursor(offset)
+ {}
+
+ virtual ~sp_instr_cpush()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ /**
+ This call is used to cleanup the instruction when a sensitive
+ cursor is closed. For now stored procedures always use materialized
+ cursors and the call is not used.
+ */
+ virtual void cleanup_stmt() { /* no op */ }
+private:
+
+ sp_lex_keeper m_lex_keeper;
+ uint m_cursor; /**< Frame offset (for debugging) */
+
+}; // class sp_instr_cpush : public sp_instr
+
+
+class sp_instr_cpop : public sp_instr
+{
+ sp_instr_cpop(const sp_instr_cpop &); /**< Prevent use of these */
+ void operator=(sp_instr_cpop &);
+
+public:
+
+ sp_instr_cpop(uint ip, sp_pcontext *ctx, uint count)
+ : sp_instr(ip, ctx), m_count(count)
+ {}
+
+ virtual ~sp_instr_cpop()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+private:
+
+ uint m_count;
+
+}; // class sp_instr_cpop : public sp_instr
+
+
+class sp_instr_copen : public sp_instr
+{
+ sp_instr_copen(const sp_instr_copen &); /**< Prevent use of these */
+ void operator=(sp_instr_copen &);
+
+public:
+
+ sp_instr_copen(uint ip, sp_pcontext *ctx, uint c)
+ : sp_instr(ip, ctx), m_cursor(c)
+ {}
+
+ virtual ~sp_instr_copen()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+private:
+
+ uint m_cursor; ///< Stack index
+
+}; // class sp_instr_copen : public sp_instr_stmt
+
+
+class sp_instr_cclose : public sp_instr
+{
+ sp_instr_cclose(const sp_instr_cclose &); /**< Prevent use of these */
+ void operator=(sp_instr_cclose &);
+
+public:
+
+ sp_instr_cclose(uint ip, sp_pcontext *ctx, uint c)
+ : sp_instr(ip, ctx), m_cursor(c)
+ {}
+
+ virtual ~sp_instr_cclose()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+private:
+
+ uint m_cursor;
+
+}; // class sp_instr_cclose : public sp_instr
+
+
+class sp_instr_cfetch : public sp_instr
+{
+ sp_instr_cfetch(const sp_instr_cfetch &); /**< Prevent use of these */
+ void operator=(sp_instr_cfetch &);
+
+public:
+
+ sp_instr_cfetch(uint ip, sp_pcontext *ctx, uint c)
+ : sp_instr(ip, ctx), m_cursor(c)
+ {
+ m_varlist.empty();
+ }
+
+ virtual ~sp_instr_cfetch()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ void add_to_varlist(sp_variable *var)
+ {
+ m_varlist.push_back(var);
+ }
+
+private:
+
+ uint m_cursor;
+ List<sp_variable> m_varlist;
+
+}; // class sp_instr_cfetch : public sp_instr
+
+
+class sp_instr_error : public sp_instr
+{
+ sp_instr_error(const sp_instr_error &); /**< Prevent use of these */
+ void operator=(sp_instr_error &);
+
+public:
+
+ sp_instr_error(uint ip, sp_pcontext *ctx, int errcode)
+ : sp_instr(ip, ctx), m_errcode(errcode)
+ {}
+
+ virtual ~sp_instr_error()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads)
+ {
+ marked= 1;
+ return UINT_MAX;
+ }
+
+private:
+
+ int m_errcode;
+
+}; // class sp_instr_error : public sp_instr
+
+
+class sp_instr_set_case_expr : public sp_instr_opt_meta
+{
+public:
+
+ sp_instr_set_case_expr(uint ip, sp_pcontext *ctx, uint case_expr_id,
+ Item *case_expr, LEX *lex)
+ : sp_instr_opt_meta(ip, ctx),
+ m_case_expr_id(case_expr_id), m_case_expr(case_expr),
+ m_lex_keeper(lex, TRUE)
+ {}
+
+ virtual ~sp_instr_set_case_expr()
+ {}
+
+ virtual int execute(THD *thd, uint *nextp);
+
+ virtual int exec_core(THD *thd, uint *nextp);
+
+ virtual void print(String *str);
+
+ virtual uint opt_mark(sp_head *sp, List<sp_instr> *leads);
+
+ virtual void opt_move(uint dst, List<sp_instr> *ibp);
+
+ virtual void set_destination(uint old_dest, uint new_dest)
+ {
+ if (m_cont_dest == old_dest)
+ m_cont_dest= new_dest;
+ }
+
+private:
+
+ uint m_case_expr_id;
+ Item *m_case_expr;
+ sp_lex_keeper m_lex_keeper;
+
+}; // class sp_instr_set_case_expr : public sp_instr_opt_meta
+
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+bool
+sp_change_security_context(THD *thd, sp_head *sp,
+ Security_context **backup);
+void
+sp_restore_security_context(THD *thd, Security_context *backup);
+
+bool
+set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc,
+ Security_context **save_ctx);
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+
+TABLE_LIST *
+sp_add_to_query_tables(THD *thd, LEX *lex,
+ const char *db, const char *name,
+ thr_lock_type locktype,
+ enum_mdl_type mdl_type);
+
+Item *
+sp_prepare_func_item(THD* thd, Item **it_addr);
+
+bool
+sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr);
+
+/**
+ @} (end of group Stored_Routines)
+*/
+
+#endif /* _SP_HEAD_H_ */
diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc
new file mode 100644
index 00000000000..11954921e06
--- /dev/null
+++ b/sql/sp_pcontext.cc
@@ -0,0 +1,485 @@
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation
+#endif
+
+#include "sp_pcontext.h"
+#include "sp_head.h"
+
+bool sp_condition_value::equals(const sp_condition_value *cv) const
+{
+ DBUG_ASSERT(cv);
+
+ if (this == cv)
+ return true;
+
+ if (type != cv->type)
+ return false;
+
+ switch (type)
+ {
+ case sp_condition_value::ERROR_CODE:
+ return (mysqlerr == cv->mysqlerr);
+
+ case sp_condition_value::SQLSTATE:
+ return (strcmp(sql_state, cv->sql_state) == 0);
+
+ default:
+ return true;
+ }
+}
+
+
+void sp_pcontext::init(uint var_offset,
+ uint cursor_offset,
+ int num_case_expressions)
+{
+ m_var_offset= var_offset;
+ m_cursor_offset= cursor_offset;
+ m_num_case_exprs= num_case_expressions;
+
+ m_labels.empty();
+}
+
+
+sp_pcontext::sp_pcontext()
+ : Sql_alloc(),
+ m_max_var_index(0), m_max_cursor_index(0),
+ m_parent(NULL), m_pboundary(0),
+ m_scope(REGULAR_SCOPE)
+{
+ init(0, 0, 0);
+}
+
+
+sp_pcontext::sp_pcontext(sp_pcontext *prev, sp_pcontext::enum_scope scope)
+ : Sql_alloc(),
+ m_max_var_index(0), m_max_cursor_index(0),
+ m_parent(prev), m_pboundary(0),
+ m_scope(scope)
+{
+ init(prev->m_var_offset + prev->m_max_var_index,
+ prev->current_cursor_count(),
+ prev->get_num_case_exprs());
+}
+
+
+sp_pcontext::~sp_pcontext()
+{
+ for (size_t i= 0; i < m_children.elements(); ++i)
+ delete m_children.at(i);
+}
+
+
+sp_pcontext *sp_pcontext::push_context(THD *thd, sp_pcontext::enum_scope scope)
+{
+ sp_pcontext *child= new (thd->mem_root) sp_pcontext(this, scope);
+
+ if (child)
+ m_children.append(child);
+ return child;
+}
+
+
+sp_pcontext *sp_pcontext::pop_context()
+{
+ m_parent->m_max_var_index+= m_max_var_index;
+
+ uint submax= max_cursor_index();
+ if (submax > m_parent->m_max_cursor_index)
+ m_parent->m_max_cursor_index= submax;
+
+ if (m_num_case_exprs > m_parent->m_num_case_exprs)
+ m_parent->m_num_case_exprs= m_num_case_exprs;
+
+ return m_parent;
+}
+
+
+uint sp_pcontext::diff_handlers(const sp_pcontext *ctx, bool exclusive) const
+{
+ uint n= 0;
+ const sp_pcontext *pctx= this;
+ const sp_pcontext *last_ctx= NULL;
+
+ while (pctx && pctx != ctx)
+ {
+ n+= pctx->m_handlers.elements();
+ last_ctx= pctx;
+ pctx= pctx->parent_context();
+ }
+ if (pctx)
+ return (exclusive && last_ctx ? n - last_ctx->m_handlers.elements() : n);
+ return 0; // Didn't find ctx
+}
+
+
+uint sp_pcontext::diff_cursors(const sp_pcontext *ctx, bool exclusive) const
+{
+ uint n= 0;
+ const sp_pcontext *pctx= this;
+ const sp_pcontext *last_ctx= NULL;
+
+ while (pctx && pctx != ctx)
+ {
+ n+= pctx->m_cursors.elements();
+ last_ctx= pctx;
+ pctx= pctx->parent_context();
+ }
+ if (pctx)
+ return (exclusive && last_ctx ? n - last_ctx->m_cursors.elements() : n);
+ return 0; // Didn't find ctx
+}
+
+
+sp_variable *sp_pcontext::find_variable(LEX_STRING name,
+ bool current_scope_only) const
+{
+ uint i= m_vars.elements() - m_pboundary;
+
+ while (i--)
+ {
+ sp_variable *p= m_vars.at(i);
+
+ if (my_strnncoll(system_charset_info,
+ (const uchar *)name.str, name.length,
+ (const uchar *)p->name.str, p->name.length) == 0)
+ {
+ return p;
+ }
+ }
+
+ return (!current_scope_only && m_parent) ?
+ m_parent->find_variable(name, false) :
+ NULL;
+}
+
+
+sp_variable *sp_pcontext::find_variable(uint offset) const
+{
+ if (m_var_offset <= offset && offset < m_var_offset + m_vars.elements())
+ return m_vars.at(offset - m_var_offset); // This frame
+
+ return m_parent ?
+ m_parent->find_variable(offset) : // Some previous frame
+ NULL; // Index out of bounds
+}
+
+
+sp_variable *sp_pcontext::add_variable(THD *thd,
+ LEX_STRING name,
+ enum enum_field_types type,
+ sp_variable::enum_mode mode)
+{
+ sp_variable *p=
+ new (thd->mem_root) sp_variable(name, type,mode, current_var_count());
+
+ if (!p)
+ return NULL;
+
+ ++m_max_var_index;
+
+ return m_vars.append(p) ? NULL : p;
+}
+
+
+sp_label *sp_pcontext::push_label(THD *thd, LEX_STRING name, uint ip)
+{
+ sp_label *label=
+ new (thd->mem_root) sp_label(name, ip, sp_label::IMPLICIT, this);
+
+ if (!label)
+ return NULL;
+
+ m_labels.push_front(label);
+
+ return label;
+}
+
+
+sp_label *sp_pcontext::find_label(LEX_STRING name)
+{
+ List_iterator_fast<sp_label> li(m_labels);
+ sp_label *lab;
+
+ while ((lab= li++))
+ {
+ if (my_strcasecmp(system_charset_info, name.str, lab->name.str) == 0)
+ return lab;
+ }
+
+ /*
+ Note about exception handlers.
+ See SQL:2003 SQL/PSM (ISO/IEC 9075-4:2003),
+ section 13.1 <compound statement>,
+ syntax rule 4.
+ In short, a DECLARE HANDLER block can not refer
+ to labels from the parent context, as they are out of scope.
+ */
+ return (m_parent && (m_scope == REGULAR_SCOPE)) ?
+ m_parent->find_label(name) :
+ NULL;
+}
+
+
+bool sp_pcontext::add_condition(THD *thd,
+ LEX_STRING name,
+ sp_condition_value *value)
+{
+ sp_condition *p= new (thd->mem_root) sp_condition(name, value);
+
+ if (p == NULL)
+ return true;
+
+ return m_conditions.append(p);
+}
+
+
+sp_condition_value *sp_pcontext::find_condition(LEX_STRING name,
+ bool current_scope_only) const
+{
+ uint i= m_conditions.elements();
+
+ while (i--)
+ {
+ sp_condition *p= m_conditions.at(i);
+
+ if (my_strnncoll(system_charset_info,
+ (const uchar *) name.str, name.length,
+ (const uchar *) p->name.str, p->name.length) == 0)
+ {
+ return p->value;
+ }
+ }
+
+ return (!current_scope_only && m_parent) ?
+ m_parent->find_condition(name, false) :
+ NULL;
+}
+
+
+sp_handler *sp_pcontext::add_handler(THD *thd,
+ sp_handler::enum_type type)
+{
+ sp_handler *h= new (thd->mem_root) sp_handler(type);
+
+ if (!h)
+ return NULL;
+
+ return m_handlers.append(h) ? NULL : h;
+}
+
+
+bool sp_pcontext::check_duplicate_handler(
+ const sp_condition_value *cond_value) const
+{
+ for (size_t i= 0; i < m_handlers.elements(); ++i)
+ {
+ sp_handler *h= m_handlers.at(i);
+
+ List_iterator_fast<sp_condition_value> li(h->condition_values);
+ sp_condition_value *cv;
+
+ while ((cv= li++))
+ {
+ if (cond_value->equals(cv))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+sp_handler*
+sp_pcontext::find_handler(const char *sql_state,
+ uint sql_errno,
+ Sql_condition::enum_warning_level level) const
+{
+ sp_handler *found_handler= NULL;
+ sp_condition_value *found_cv= NULL;
+
+ for (size_t i= 0; i < m_handlers.elements(); ++i)
+ {
+ sp_handler *h= m_handlers.at(i);
+
+ List_iterator_fast<sp_condition_value> li(h->condition_values);
+ sp_condition_value *cv;
+
+ while ((cv= li++))
+ {
+ switch (cv->type)
+ {
+ case sp_condition_value::ERROR_CODE:
+ if (sql_errno == cv->mysqlerr &&
+ (!found_cv ||
+ found_cv->type > sp_condition_value::ERROR_CODE))
+ {
+ found_cv= cv;
+ found_handler= h;
+ }
+ break;
+
+ case sp_condition_value::SQLSTATE:
+ if (strcmp(sql_state, cv->sql_state) == 0 &&
+ (!found_cv ||
+ found_cv->type > sp_condition_value::SQLSTATE))
+ {
+ found_cv= cv;
+ found_handler= h;
+ }
+ break;
+
+ case sp_condition_value::WARNING:
+ if ((is_sqlstate_warning(sql_state) ||
+ level == Sql_condition::WARN_LEVEL_WARN) && !found_cv)
+ {
+ found_cv= cv;
+ found_handler= h;
+ }
+ break;
+
+ case sp_condition_value::NOT_FOUND:
+ if (is_sqlstate_not_found(sql_state) && !found_cv)
+ {
+ found_cv= cv;
+ found_handler= h;
+ }
+ break;
+
+ case sp_condition_value::EXCEPTION:
+ if (is_sqlstate_exception(sql_state) &&
+ level == Sql_condition::WARN_LEVEL_ERROR && !found_cv)
+ {
+ found_cv= cv;
+ found_handler= h;
+ }
+ break;
+ }
+ }
+ }
+
+ if (found_handler)
+ return found_handler;
+
+
+ // There is no appropriate handler in this parsing context. We need to look up
+ // in parent contexts. There might be two cases here:
+ //
+ // 1. The current context has REGULAR_SCOPE. That means, it's a simple
+ // BEGIN..END block:
+ // ...
+ // BEGIN
+ // ... # We're here.
+ // END
+ // ...
+ // In this case we simply call find_handler() on parent's context recursively.
+ //
+ // 2. The current context has HANDLER_SCOPE. That means, we're inside an
+ // SQL-handler block:
+ // ...
+ // DECLARE ... HANDLER FOR ...
+ // BEGIN
+ // ... # We're here.
+ // END
+ // ...
+ // In this case we can not just call parent's find_handler(), because
+ // parent's handler don't catch conditions from this scope. Instead, we should
+ // try to find first parent context (we might have nested handler
+ // declarations), which has REGULAR_SCOPE (i.e. which is regular BEGIN..END
+ // block).
+
+ const sp_pcontext *p= this;
+
+ while (p && p->m_scope == HANDLER_SCOPE)
+ p= p->m_parent;
+
+ if (!p || !p->m_parent)
+ return NULL;
+
+ return p->m_parent->find_handler(sql_state, sql_errno, level);
+}
+
+
+bool sp_pcontext::add_cursor(LEX_STRING name)
+{
+ if (m_cursors.elements() == m_max_cursor_index)
+ ++m_max_cursor_index;
+
+ return m_cursors.append(name);
+}
+
+
+bool sp_pcontext::find_cursor(LEX_STRING name,
+ uint *poff,
+ bool current_scope_only) const
+{
+ uint i= m_cursors.elements();
+
+ while (i--)
+ {
+ LEX_STRING n= m_cursors.at(i);
+
+ if (my_strnncoll(system_charset_info,
+ (const uchar *) name.str, name.length,
+ (const uchar *) n.str, n.length) == 0)
+ {
+ *poff= m_cursor_offset + i;
+ return true;
+ }
+ }
+
+ return (!current_scope_only && m_parent) ?
+ m_parent->find_cursor(name, poff, false) :
+ false;
+}
+
+
+void sp_pcontext::retrieve_field_definitions(
+ List<Create_field> *field_def_lst) const
+{
+ /* Put local/context fields in the result list. */
+
+ for (size_t i= 0; i < m_vars.elements(); ++i)
+ {
+ sp_variable *var_def= m_vars.at(i);
+
+ field_def_lst->push_back(&var_def->field_def);
+ }
+
+ /* Put the fields of the enclosed contexts in the result list. */
+
+ for (size_t i= 0; i < m_children.elements(); ++i)
+ m_children.at(i)->retrieve_field_definitions(field_def_lst);
+}
+
+
+const LEX_STRING *sp_pcontext::find_cursor(uint offset) const
+{
+ if (m_cursor_offset <= offset &&
+ offset < m_cursor_offset + m_cursors.elements())
+ {
+ return &m_cursors.at(offset - m_cursor_offset); // This frame
+ }
+
+ return m_parent ?
+ m_parent->find_cursor(offset) : // Some previous frame
+ NULL; // Index out of bounds
+}
diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h
new file mode 100644
index 00000000000..4d8623108aa
--- /dev/null
+++ b/sql/sp_pcontext.h
@@ -0,0 +1,565 @@
+/* -*- C++ -*- */
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _SP_PCONTEXT_H_
+#define _SP_PCONTEXT_H_
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_string.h" // LEX_STRING
+#include "mysql_com.h" // enum_field_types
+#include "field.h" // Create_field
+#include "sql_array.h" // Dynamic_array
+
+
+/// This class represents a stored program variable or a parameter
+/// (also referenced as 'SP-variable').
+
+class sp_variable : public Sql_alloc
+{
+public:
+ enum enum_mode
+ {
+ MODE_IN,
+ MODE_OUT,
+ MODE_INOUT
+ };
+
+ /// Name of the SP-variable.
+ LEX_STRING name;
+
+ /// Field-type of the SP-variable.
+ enum enum_field_types type;
+
+ /// Mode of the SP-variable.
+ enum_mode mode;
+
+ /// The index to the variable's value in the runtime frame.
+ ///
+ /// It is calculated during parsing and used when creating sp_instr_set
+ /// instructions and Item_splocal items. I.e. values are set/referred by
+ /// array indexing in runtime.
+ uint offset;
+
+ /// Default value of the SP-variable (if any).
+ Item *default_value;
+
+ /// Full type information (field meta-data) of the SP-variable.
+ Create_field field_def;
+
+public:
+ sp_variable(LEX_STRING _name, enum_field_types _type, enum_mode _mode,
+ uint _offset)
+ :Sql_alloc(),
+ name(_name),
+ type(_type),
+ mode(_mode),
+ offset(_offset),
+ default_value(NULL)
+ { }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/// This class represents an SQL/PSM label. Can refer to the identifier
+/// used with the "label_name:" construct which may precede some SQL/PSM
+/// statements, or to an implicit implementation-dependent identifier which
+/// the parser inserts before a high-level flow control statement such as
+/// IF/WHILE/REPEAT/LOOP, when such statement is rewritten into a
+/// combination of low-level jump/jump_if instructions and labels.
+
+class sp_label : public Sql_alloc
+{
+public:
+ enum enum_type
+ {
+ /// Implicit label generated by parser.
+ IMPLICIT,
+
+ /// Label at BEGIN.
+ BEGIN,
+
+ /// Label at iteration control
+ ITERATION
+ };
+
+ /// Name of the label.
+ LEX_STRING name;
+
+ /// Instruction pointer of the label.
+ uint ip;
+
+ /// Type of the label.
+ enum_type type;
+
+ /// Scope of the label.
+ class sp_pcontext *ctx;
+
+public:
+ sp_label(LEX_STRING _name, uint _ip, enum_type _type, sp_pcontext *_ctx)
+ :Sql_alloc(),
+ name(_name),
+ ip(_ip),
+ type(_type),
+ ctx(_ctx)
+ { }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/// This class represents condition-value term in DECLARE CONDITION or
+/// DECLARE HANDLER statements. sp_condition_value has little to do with
+/// SQL-conditions.
+///
+/// In some sense, this class is a union -- a set of filled attributes
+/// depends on the sp_condition_value::type value.
+
+class sp_condition_value : public Sql_alloc
+{
+public:
+ enum enum_type
+ {
+ ERROR_CODE,
+ SQLSTATE,
+ WARNING,
+ NOT_FOUND,
+ EXCEPTION
+ };
+
+ /// Type of the condition value.
+ enum_type type;
+
+ /// SQLSTATE of the condition value.
+ char sql_state[SQLSTATE_LENGTH+1];
+
+ /// MySQL error code of the condition value.
+ uint mysqlerr;
+
+public:
+ sp_condition_value(uint _mysqlerr)
+ :Sql_alloc(),
+ type(ERROR_CODE),
+ mysqlerr(_mysqlerr)
+ { }
+
+ sp_condition_value(const char *_sql_state)
+ :Sql_alloc(),
+ type(SQLSTATE)
+ {
+ memcpy(sql_state, _sql_state, SQLSTATE_LENGTH);
+ sql_state[SQLSTATE_LENGTH]= 0;
+ }
+
+ sp_condition_value(enum_type _type)
+ :Sql_alloc(),
+ type(_type)
+ {
+ DBUG_ASSERT(type != ERROR_CODE && type != SQLSTATE);
+ }
+
+ /// Check if two instances of sp_condition_value are equal or not.
+ ///
+ /// @param cv another instance of sp_condition_value to check.
+ ///
+ /// @return true if the instances are equal, false otherwise.
+ bool equals(const sp_condition_value *cv) const;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/// This class represents 'DECLARE CONDITION' statement.
+/// sp_condition has little to do with SQL-conditions.
+
+class sp_condition : public Sql_alloc
+{
+public:
+ /// Name of the condition.
+ LEX_STRING name;
+
+ /// Value of the condition.
+ sp_condition_value *value;
+
+public:
+ sp_condition(LEX_STRING _name, sp_condition_value *_value)
+ :Sql_alloc(),
+ name(_name),
+ value(_value)
+ { }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/// This class represents 'DECLARE HANDLER' statement.
+
+class sp_handler : public Sql_alloc
+{
+public:
+ /// Enumeration of possible handler types.
+ /// Note: UNDO handlers are not (and have never been) supported.
+ enum enum_type
+ {
+ EXIT,
+ CONTINUE
+ };
+
+ /// Handler type.
+ enum_type type;
+
+ /// Conditions caught by this handler.
+ List<sp_condition_value> condition_values;
+
+public:
+ /// The constructor.
+ ///
+ /// @param _type SQL-handler type.
+ sp_handler(enum_type _type)
+ :Sql_alloc(),
+ type(_type)
+ { }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/// The class represents parse-time context, which keeps track of declared
+/// variables/parameters, conditions, handlers, cursors and labels.
+///
+/// sp_pcontext objects are organized in a tree according to the following
+/// rules:
+/// - one sp_pcontext object corresponds for for each BEGIN..END block;
+/// - one sp_pcontext object corresponds for each exception handler;
+/// - one additional sp_pcontext object is created to contain
+/// Stored Program parameters.
+///
+/// sp_pcontext objects are used both at parse-time and at runtime.
+///
+/// During the parsing stage sp_pcontext objects are used:
+/// - to look up defined names (e.g. declared variables and visible
+/// labels);
+/// - to check for duplicates;
+/// - for error checking;
+/// - to calculate offsets to be used at runtime.
+///
+/// During the runtime phase, a tree of sp_pcontext objects is used:
+/// - for error checking (e.g. to check correct number of parameters);
+/// - to resolve SQL-handlers.
+
+class sp_pcontext : public Sql_alloc
+{
+public:
+ enum enum_scope
+ {
+ /// REGULAR_SCOPE designates regular BEGIN ... END blocks.
+ REGULAR_SCOPE,
+
+ /// HANDLER_SCOPE designates SQL-handler blocks.
+ HANDLER_SCOPE
+ };
+
+public:
+ sp_pcontext();
+ ~sp_pcontext();
+
+
+ /// Create and push a new context in the tree.
+
+ /// @param thd thread context.
+ /// @param scope scope of the new parsing context.
+ /// @return the node created.
+ sp_pcontext *push_context(THD *thd, enum_scope scope);
+
+ /// Pop a node from the parsing context tree.
+ /// @return the parent node.
+ sp_pcontext *pop_context();
+
+ sp_pcontext *parent_context() const
+ { return m_parent; }
+
+ /// Calculate and return the number of handlers to pop between the given
+ /// context and this one.
+ ///
+ /// @param ctx the other parsing context.
+ /// @param exclusive specifies if the last scope should be excluded.
+ ///
+ /// @return the number of handlers to pop between the given context and
+ /// this one. If 'exclusive' is true, don't count the last scope we are
+ /// leaving; this is used for LEAVE where we will jump to the hpop
+ /// instructions.
+ uint diff_handlers(const sp_pcontext *ctx, bool exclusive) const;
+
+ /// Calculate and return the number of cursors to pop between the given
+ /// context and this one.
+ ///
+ /// @param ctx the other parsing context.
+ /// @param exclusive specifies if the last scope should be excluded.
+ ///
+ /// @return the number of cursors to pop between the given context and
+ /// this one. If 'exclusive' is true, don't count the last scope we are
+ /// leaving; this is used for LEAVE where we will jump to the cpop
+ /// instructions.
+ uint diff_cursors(const sp_pcontext *ctx, bool exclusive) const;
+
+ /////////////////////////////////////////////////////////////////////////
+ // SP-variables (parameters and variables).
+ /////////////////////////////////////////////////////////////////////////
+
+ /// @return the maximum number of variables used in this and all child
+ /// contexts. For the root parsing context, this gives us the number of
+ /// slots needed for variables during the runtime phase.
+ uint max_var_index() const
+ { return m_max_var_index; }
+
+ /// @return the current number of variables used in the parent contexts
+ /// (from the root), including this context.
+ uint current_var_count() const
+ { return m_var_offset + m_vars.elements(); }
+
+ /// @return the number of variables in this context alone.
+ uint context_var_count() const
+ { return m_vars.elements(); }
+
+ /// @return map index in this parsing context to runtime offset.
+ uint var_context2runtime(uint i) const
+ { return m_var_offset + i; }
+
+ /// Add SP-variable to the parsing context.
+ ///
+ /// @param thd Thread context.
+ /// @param name Name of the SP-variable.
+ /// @param type Type of the SP-variable.
+ /// @param mode Mode of the SP-variable.
+ ///
+ /// @return instance of newly added SP-variable.
+ sp_variable *add_variable(THD *thd,
+ LEX_STRING name,
+ enum enum_field_types type,
+ sp_variable::enum_mode mode);
+
+ /// Retrieve full type information about SP-variables in this parsing
+ /// context and its children.
+ ///
+ /// @param field_def_lst[out] Container to store type information.
+ void retrieve_field_definitions(List<Create_field> *field_def_lst) const;
+
+ /// Find SP-variable by name.
+ ///
+ /// The function does a linear search (from newer to older variables,
+ /// in case we have shadowed names).
+ ///
+ /// The function is called only at parsing time.
+ ///
+ /// @param name Variable name.
+ /// @param current_scope_only A flag if we search only in current scope.
+ ///
+ /// @return instance of found SP-variable, or NULL if not found.
+ sp_variable *find_variable(LEX_STRING name, bool current_scope_only) const;
+
+ /// Find SP-variable by the offset in the root parsing context.
+ ///
+ /// The function is used for two things:
+ /// - When evaluating parameters at the beginning, and setting out parameters
+ /// at the end, of invocation. (Top frame only, so no recursion then.)
+ /// - For printing of sp_instr_set. (Debug mode only.)
+ ///
+ /// @param offset Variable offset in the root parsing context.
+ ///
+ /// @return instance of found SP-variable, or NULL if not found.
+ sp_variable *find_variable(uint offset) const;
+
+ /// Set the current scope boundary (for default values).
+ ///
+ /// @param n The number of variables to skip.
+ void declare_var_boundary(uint n)
+ { m_pboundary= n; }
+
+ /////////////////////////////////////////////////////////////////////////
+ // CASE expressions.
+ /////////////////////////////////////////////////////////////////////////
+
+ int register_case_expr()
+ { return m_num_case_exprs++; }
+
+ int get_num_case_exprs() const
+ { return m_num_case_exprs; }
+
+ bool push_case_expr_id(int case_expr_id)
+ { return m_case_expr_ids.append(case_expr_id); }
+
+ void pop_case_expr_id()
+ { m_case_expr_ids.pop(); }
+
+ int get_current_case_expr_id() const
+ { return *m_case_expr_ids.back(); }
+
+ /////////////////////////////////////////////////////////////////////////
+ // Labels.
+ /////////////////////////////////////////////////////////////////////////
+
+ sp_label *push_label(THD *thd, LEX_STRING name, uint ip);
+
+ sp_label *find_label(LEX_STRING name);
+
+ sp_label *last_label()
+ {
+ sp_label *label= m_labels.head();
+
+ if (!label && m_parent)
+ label= m_parent->last_label();
+
+ return label;
+ }
+
+ sp_label *pop_label()
+ { return m_labels.pop(); }
+
+ /////////////////////////////////////////////////////////////////////////
+ // Conditions.
+ /////////////////////////////////////////////////////////////////////////
+
+ bool add_condition(THD *thd, LEX_STRING name, sp_condition_value *value);
+
+ /// See comment for find_variable() above.
+ sp_condition_value *find_condition(LEX_STRING name,
+ bool current_scope_only) const;
+
+ /////////////////////////////////////////////////////////////////////////
+ // Handlers.
+ /////////////////////////////////////////////////////////////////////////
+
+ sp_handler *add_handler(THD* thd, sp_handler::enum_type type);
+
+ /// This is an auxilary parsing-time function to check if an SQL-handler
+ /// exists in the current parsing context (current scope) for the given
+ /// SQL-condition. This function is used to check for duplicates during
+ /// the parsing phase.
+ ///
+ /// This function can not be used during the runtime phase to check
+ /// SQL-handler existence because it searches for the SQL-handler in the
+ /// current scope only (during runtime, current and parent scopes
+ /// should be checked according to the SQL-handler resolution rules).
+ ///
+ /// @param condition_value the handler condition value
+ /// (not SQL-condition!).
+ ///
+ /// @retval true if such SQL-handler exists.
+ /// @retval false otherwise.
+ bool check_duplicate_handler(const sp_condition_value *cond_value) const;
+
+ /// Find an SQL handler for the given SQL condition according to the
+ /// SQL-handler resolution rules. This function is used at runtime.
+ ///
+ /// @param sql_state The SQL condition state
+ /// @param sql_errno The error code
+ /// @param level The SQL condition level
+ ///
+ /// @return a pointer to the found SQL-handler or NULL.
+ sp_handler *find_handler(const char *sql_state,
+ uint sql_errno,
+ Sql_condition::enum_warning_level level) const;
+
+ /////////////////////////////////////////////////////////////////////////
+ // Cursors.
+ /////////////////////////////////////////////////////////////////////////
+
+ bool add_cursor(LEX_STRING name);
+
+ /// See comment for find_variable() above.
+ bool find_cursor(LEX_STRING name, uint *poff, bool current_scope_only) const;
+
+ /// Find cursor by offset (for debugging only).
+ const LEX_STRING *find_cursor(uint offset) const;
+
+ uint max_cursor_index() const
+ { return m_max_cursor_index + m_cursors.elements(); }
+
+ uint current_cursor_count() const
+ { return m_cursor_offset + m_cursors.elements(); }
+
+private:
+ /// Constructor for a tree node.
+ /// @param prev the parent parsing context
+ /// @param scope scope of this parsing context
+ sp_pcontext(sp_pcontext *prev, enum_scope scope);
+
+ void init(uint var_offset, uint cursor_offset, int num_case_expressions);
+
+ /* Prevent use of these */
+ sp_pcontext(const sp_pcontext &);
+ void operator=(sp_pcontext &);
+
+private:
+ /// m_max_var_index -- number of variables (including all types of arguments)
+ /// in this context including all children contexts.
+ ///
+ /// m_max_var_index >= m_vars.elements().
+ ///
+ /// m_max_var_index of the root parsing context contains number of all
+ /// variables (including arguments) in all enclosed contexts.
+ uint m_max_var_index;
+
+ /// The maximum sub context's framesizes.
+ uint m_max_cursor_index;
+
+ /// Parent context.
+ sp_pcontext *m_parent;
+
+ /// An index of the first SP-variable in this parsing context. The index
+ /// belongs to a runtime table of SP-variables.
+ ///
+ /// Note:
+ /// - m_var_offset is 0 for root parsing context;
+ /// - m_var_offset is different for all nested parsing contexts.
+ uint m_var_offset;
+
+ /// Cursor offset for this context.
+ uint m_cursor_offset;
+
+ /// Boundary for finding variables in this context. This is the number of
+ /// variables currently "invisible" to default clauses. This is normally 0,
+ /// but will be larger during parsing of DECLARE ... DEFAULT, to get the
+ /// scope right for DEFAULT values.
+ uint m_pboundary;
+
+ int m_num_case_exprs;
+
+ /// SP parameters/variables.
+ Dynamic_array<sp_variable *> m_vars;
+
+ /// Stack of CASE expression ids.
+ Dynamic_array<int> m_case_expr_ids;
+
+ /// Stack of SQL-conditions.
+ Dynamic_array<sp_condition *> m_conditions;
+
+ /// Stack of cursors.
+ Dynamic_array<LEX_STRING> m_cursors;
+
+ /// Stack of SQL-handlers.
+ Dynamic_array<sp_handler *> m_handlers;
+
+ /// List of labels.
+ List<sp_label> m_labels;
+
+ /// Children contexts, used for destruction.
+ Dynamic_array<sp_pcontext *> m_children;
+
+ /// Scope of this parsing context.
+ enum_scope m_scope;
+}; // class sp_pcontext : public Sql_alloc
+
+
+#endif /* _SP_PCONTEXT_H_ */
diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc
new file mode 100644
index 00000000000..a5a6a61f73c
--- /dev/null
+++ b/sql/sp_rcontext.cc
@@ -0,0 +1,558 @@
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation
+#endif
+
+#include "mysql.h"
+#include "sp_head.h"
+#include "sql_cursor.h"
+#include "sp_rcontext.h"
+#include "sp_pcontext.h"
+#include "sql_select.h" // create_virtual_tmp_table
+
+///////////////////////////////////////////////////////////////////////////
+// sp_rcontext implementation.
+///////////////////////////////////////////////////////////////////////////
+
+
+sp_rcontext::sp_rcontext(const sp_pcontext *root_parsing_ctx,
+ Field *return_value_fld,
+ bool in_sub_stmt)
+ :end_partial_result_set(false),
+ m_root_parsing_ctx(root_parsing_ctx),
+ m_var_table(NULL),
+ m_return_value_fld(return_value_fld),
+ m_return_value_set(false),
+ m_in_sub_stmt(in_sub_stmt),
+ m_ccount(0)
+{
+}
+
+
+sp_rcontext::~sp_rcontext()
+{
+ if (m_var_table)
+ free_blobs(m_var_table);
+
+ // Leave m_handlers, m_handler_call_stack, m_var_items, m_cstack
+ // and m_case_expr_holders untouched.
+ // They are allocated in mem roots and will be freed accordingly.
+}
+
+
+sp_rcontext *sp_rcontext::create(THD *thd,
+ const sp_pcontext *root_parsing_ctx,
+ Field *return_value_fld)
+{
+ sp_rcontext *ctx= new (thd->mem_root) sp_rcontext(root_parsing_ctx,
+ return_value_fld,
+ thd->in_sub_stmt);
+
+ if (!ctx)
+ return NULL;
+
+ if (ctx->alloc_arrays(thd) ||
+ ctx->init_var_table(thd) ||
+ ctx->init_var_items(thd))
+ {
+ delete ctx;
+ return NULL;
+ }
+
+ return ctx;
+}
+
+
+bool sp_rcontext::alloc_arrays(THD *thd)
+{
+ {
+ size_t n= m_root_parsing_ctx->max_cursor_index();
+ m_cstack.reset(
+ static_cast<sp_cursor **> (
+ thd->alloc(n * sizeof (sp_cursor*))),
+ n);
+ }
+
+ {
+ size_t n= m_root_parsing_ctx->get_num_case_exprs();
+ m_case_expr_holders.reset(
+ static_cast<Item_cache **> (
+ thd->calloc(n * sizeof (Item_cache*))),
+ n);
+ }
+
+ return !m_cstack.array() || !m_case_expr_holders.array();
+}
+
+
+bool sp_rcontext::init_var_table(THD *thd)
+{
+ List<Create_field> field_def_lst;
+
+ if (!m_root_parsing_ctx->max_var_index())
+ return false;
+
+ m_root_parsing_ctx->retrieve_field_definitions(&field_def_lst);
+
+ DBUG_ASSERT(field_def_lst.elements == m_root_parsing_ctx->max_var_index());
+
+ if (!(m_var_table= create_virtual_tmp_table(thd, field_def_lst)))
+ return true;
+
+ m_var_table->copy_blobs= true;
+ m_var_table->alias.set("", 0, m_var_table->alias.charset());
+
+ return false;
+}
+
+
+bool sp_rcontext::init_var_items(THD *thd)
+{
+ uint num_vars= m_root_parsing_ctx->max_var_index();
+
+ m_var_items.reset(
+ static_cast<Item **> (
+ thd->alloc(num_vars * sizeof (Item *))),
+ num_vars);
+
+ if (!m_var_items.array())
+ return true;
+
+ for (uint idx = 0; idx < num_vars; ++idx)
+ {
+ if (!(m_var_items[idx]= new Item_field(m_var_table->field[idx])))
+ return true;
+ }
+
+ return false;
+}
+
+
+bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item)
+{
+ DBUG_ASSERT(m_return_value_fld);
+
+ m_return_value_set = true;
+
+ return sp_eval_expr(thd, m_return_value_fld, return_value_item);
+}
+
+
+bool sp_rcontext::push_cursor(sp_lex_keeper *lex_keeper,
+ sp_instr_cpush *i)
+{
+ /*
+ We should create cursors in the callers arena, as
+ it could be (and usually is) used in several instructions.
+ */
+ sp_cursor *c= new (callers_arena->mem_root) sp_cursor(lex_keeper, i);
+
+ if (c == NULL)
+ return true;
+
+ m_cstack[m_ccount++]= c;
+ return false;
+}
+
+
+void sp_rcontext::pop_cursors(uint count)
+{
+ DBUG_ASSERT(m_ccount >= count);
+
+ while (count--)
+ delete m_cstack[--m_ccount];
+}
+
+
+bool sp_rcontext::push_handler(sp_handler *handler, uint first_ip)
+{
+ /*
+ We should create handler entries in the callers arena, as
+ they could be (and usually are) used in several instructions.
+ */
+ sp_handler_entry *he=
+ new (callers_arena->mem_root) sp_handler_entry(handler, first_ip);
+
+ if (he == NULL)
+ return true;
+
+ return m_handlers.append(he);
+}
+
+
+void sp_rcontext::pop_handlers(size_t count)
+{
+ DBUG_ASSERT(m_handlers.elements() >= count);
+
+ for (size_t i= 0; i < count; ++i)
+ m_handlers.pop();
+}
+
+
+bool sp_rcontext::handle_sql_condition(THD *thd,
+ uint *ip,
+ const sp_instr *cur_spi)
+{
+ DBUG_ENTER("sp_rcontext::handle_sql_condition");
+
+ /*
+ If this is a fatal sub-statement error, and this runtime
+ context corresponds to a sub-statement, no CONTINUE/EXIT
+ handlers from this context are applicable: try to locate one
+ in the outer scope.
+ */
+ if (thd->is_fatal_sub_stmt_error && m_in_sub_stmt)
+ DBUG_RETURN(false);
+
+ Diagnostics_area *da= thd->get_stmt_da();
+ const sp_handler *found_handler= NULL;
+ const Sql_condition *found_condition= NULL;
+
+ if (thd->is_error())
+ {
+ found_handler=
+ cur_spi->m_ctx->find_handler(da->get_sqlstate(),
+ da->sql_errno(),
+ Sql_condition::WARN_LEVEL_ERROR);
+
+ if (found_handler)
+ found_condition= da->get_error_condition();
+
+ /*
+ Found condition can be NULL if the diagnostics area was full
+ when the error was raised. It can also be NULL if
+ Diagnostics_area::set_error_status(uint sql_error) was used.
+ In these cases, make a temporary Sql_condition here so the
+ error can be handled.
+ */
+ if (!found_condition)
+ {
+ Sql_condition *condition=
+ new (callers_arena->mem_root) Sql_condition(callers_arena->mem_root);
+ condition->set(da->sql_errno(), da->get_sqlstate(),
+ Sql_condition::WARN_LEVEL_ERROR,
+ da->message());
+ found_condition= condition;
+ }
+ }
+ else if (da->current_statement_warn_count())
+ {
+ Diagnostics_area::Sql_condition_iterator it= da->sql_conditions();
+ const Sql_condition *c;
+
+ // Here we need to find the last warning/note from the stack.
+ // In MySQL most substantial warning is the last one.
+ // (We could have used a reverse iterator here if one existed)
+
+ while ((c= it++))
+ {
+ if (c->get_level() == Sql_condition::WARN_LEVEL_WARN ||
+ c->get_level() == Sql_condition::WARN_LEVEL_NOTE)
+ {
+ const sp_handler *handler=
+ cur_spi->m_ctx->find_handler(c->get_sqlstate(),
+ c->get_sql_errno(),
+ c->get_level());
+ if (handler)
+ {
+ found_handler= handler;
+ found_condition= c;
+ }
+ }
+ }
+ }
+
+ if (!found_handler)
+ DBUG_RETURN(false);
+
+ // At this point, we know that:
+ // - there is a pending SQL-condition (error or warning);
+ // - there is an SQL-handler for it.
+
+ DBUG_ASSERT(found_condition);
+
+ sp_handler_entry *handler_entry= NULL;
+ for (size_t i= 0; i < m_handlers.elements(); ++i)
+ {
+ sp_handler_entry *h= m_handlers.at(i);
+
+ if (h->handler == found_handler)
+ {
+ handler_entry= h;
+ break;
+ }
+ }
+
+ /*
+ handler_entry usually should not be NULL here, as that indicates
+ that the parser context thinks a HANDLER should be activated,
+ but the runtime context cannot find it.
+
+ However, this can happen (and this is in line with the Standard)
+ if SQL-condition has been raised before DECLARE HANDLER instruction
+ is processed.
+
+ For example:
+ CREATE PROCEDURE p()
+ BEGIN
+ DECLARE v INT DEFAULT 'get'; -- raises SQL-warning here
+ DECLARE EXIT HANDLER ... -- this handler does not catch the warning
+ END
+ */
+ if (!handler_entry)
+ DBUG_RETURN(false);
+
+ // Mark active conditions so that they can be deleted when the handler exits.
+ da->mark_sql_conditions_for_removal();
+
+ uint continue_ip= handler_entry->handler->type == sp_handler::CONTINUE ?
+ cur_spi->get_cont_dest() : 0;
+
+ /* End aborted result set. */
+ if (end_partial_result_set)
+ thd->protocol->end_partial_result_set(thd);
+
+ /* Reset error state. */
+ thd->clear_error();
+ thd->killed= NOT_KILLED; // Some errors set thd->killed
+ // (e.g. "bad data").
+
+ /* Add a frame to handler-call-stack. */
+ Sql_condition_info *cond_info=
+ new (callers_arena->mem_root) Sql_condition_info(found_condition,
+ callers_arena);
+ Handler_call_frame *frame=
+ new (callers_arena->mem_root) Handler_call_frame(cond_info, continue_ip);
+ m_handler_call_stack.append(frame);
+
+ *ip= handler_entry->first_ip;
+
+ DBUG_RETURN(true);
+}
+
+
+uint sp_rcontext::exit_handler(Diagnostics_area *da)
+{
+ DBUG_ENTER("sp_rcontext::exit_handler");
+ DBUG_ASSERT(m_handler_call_stack.elements() > 0);
+
+ Handler_call_frame *f= m_handler_call_stack.pop();
+
+ /*
+ Remove the SQL conditions that were present in DA when the
+ handler was activated.
+ */
+ da->remove_marked_sql_conditions();
+
+ uint continue_ip= f->continue_ip;
+
+ DBUG_RETURN(continue_ip);
+}
+
+
+int sp_rcontext::set_variable(THD *thd, Field *field, Item **value)
+{
+ if (!value)
+ {
+ field->set_null();
+ return 0;
+ }
+
+ return sp_eval_expr(thd, field, value);
+}
+
+
+Item_cache *sp_rcontext::create_case_expr_holder(THD *thd,
+ const Item *item) const
+{
+ Item_cache *holder;
+ Query_arena current_arena;
+
+ thd->set_n_backup_active_arena(thd->spcont->callers_arena, &current_arena);
+
+ holder= Item_cache::get_cache(item);
+
+ thd->restore_active_arena(thd->spcont->callers_arena, &current_arena);
+
+ return holder;
+}
+
+
+bool sp_rcontext::set_case_expr(THD *thd, int case_expr_id,
+ Item **case_expr_item_ptr)
+{
+ Item *case_expr_item= sp_prepare_func_item(thd, case_expr_item_ptr);
+ if (!case_expr_item)
+ return true;
+
+ if (!m_case_expr_holders[case_expr_id] ||
+ m_case_expr_holders[case_expr_id]->result_type() !=
+ case_expr_item->result_type())
+ {
+ m_case_expr_holders[case_expr_id]=
+ create_case_expr_holder(thd, case_expr_item);
+ }
+
+ m_case_expr_holders[case_expr_id]->store(case_expr_item);
+ m_case_expr_holders[case_expr_id]->cache_value();
+ return false;
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+// sp_cursor implementation.
+///////////////////////////////////////////////////////////////////////////
+
+
+sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i)
+ :m_lex_keeper(lex_keeper),
+ server_side_cursor(NULL),
+ m_i(i)
+{
+ /*
+ currsor can't be stored in QC, so we should prevent opening QC for
+ try to write results which are absent.
+ */
+ lex_keeper->disable_query_cache();
+}
+
+
+/*
+ Open an SP cursor
+
+ SYNOPSIS
+ open()
+ THD Thread handler
+
+
+ RETURN
+ 0 in case of success, -1 otherwise
+*/
+
+int sp_cursor::open(THD *thd)
+{
+ if (server_side_cursor)
+ {
+ my_message(ER_SP_CURSOR_ALREADY_OPEN, ER(ER_SP_CURSOR_ALREADY_OPEN),
+ MYF(0));
+ return -1;
+ }
+ if (mysql_open_cursor(thd, &result, &server_side_cursor))
+ return -1;
+ return 0;
+}
+
+
+int sp_cursor::close(THD *thd)
+{
+ if (! server_side_cursor)
+ {
+ my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0));
+ return -1;
+ }
+ destroy();
+ return 0;
+}
+
+
+void sp_cursor::destroy()
+{
+ delete server_side_cursor;
+ server_side_cursor= NULL;
+}
+
+
+int sp_cursor::fetch(THD *thd, List<sp_variable> *vars)
+{
+ if (! server_side_cursor)
+ {
+ my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0));
+ return -1;
+ }
+ if (vars->elements != result.get_field_count())
+ {
+ my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS,
+ ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0));
+ return -1;
+ }
+
+ DBUG_EXECUTE_IF("bug23032_emit_warning",
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR,
+ ER(ER_UNKNOWN_ERROR)););
+
+ result.set_spvar_list(vars);
+
+ /* Attempt to fetch one row */
+ if (server_side_cursor->is_open())
+ server_side_cursor->fetch(1);
+
+ /*
+ If the cursor was pointing after the last row, the fetch will
+ close it instead of sending any rows.
+ */
+ if (! server_side_cursor->is_open())
+ {
+ my_message(ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA), MYF(0));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+// sp_cursor::Select_fetch_into_spvars implementation.
+///////////////////////////////////////////////////////////////////////////
+
+
+int sp_cursor::Select_fetch_into_spvars::prepare(List<Item> &fields,
+ SELECT_LEX_UNIT *u)
+{
+ /*
+ Cache the number of columns in the result set in order to easily
+ return an error if column count does not match value count.
+ */
+ field_count= fields.elements;
+ return select_result_interceptor::prepare(fields, u);
+}
+
+
+int sp_cursor::Select_fetch_into_spvars::send_data(List<Item> &items)
+{
+ List_iterator_fast<sp_variable> spvar_iter(*spvar_list);
+ List_iterator_fast<Item> item_iter(items);
+ sp_variable *spvar;
+ Item *item;
+
+ /* Must be ensured by the caller */
+ DBUG_ASSERT(spvar_list->elements == items.elements);
+
+ /*
+ Assign the row fetched from a server side cursor to stored
+ procedure variables.
+ */
+ for (; spvar= spvar_iter++, item= item_iter++; )
+ {
+ if (thd->spcont->set_variable(thd, spvar->offset, &item))
+ return true;
+ }
+ return false;
+}
diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h
new file mode 100644
index 00000000000..c48025da93d
--- /dev/null
+++ b/sql/sp_rcontext.h
@@ -0,0 +1,469 @@
+/* -*- C++ -*- */
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _SP_RCONTEXT_H_
+#define _SP_RCONTEXT_H_
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_class.h" // select_result_interceptor
+#include "sp_pcontext.h" // sp_condition_value
+
+///////////////////////////////////////////////////////////////////////////
+// sp_rcontext declaration.
+///////////////////////////////////////////////////////////////////////////
+
+class sp_cursor;
+class sp_lex_keeper;
+class sp_instr_cpush;
+class Query_arena;
+class sp_head;
+class Item_cache;
+
+
+/*
+ This class is a runtime context of a Stored Routine. It is used in an
+ execution and is intended to contain all dynamic objects (i.e. objects, which
+ can be changed during execution), such as:
+ - stored routine variables;
+ - cursors;
+ - handlers;
+
+ Runtime context is used with sp_head class. sp_head class is intended to
+ contain all static things, related to the stored routines (code, for example).
+ sp_head instance creates runtime context for the execution of a stored
+ routine.
+
+ There is a parsing context (an instance of sp_pcontext class), which is used
+ on parsing stage. However, now it contains some necessary for an execution
+ things, such as definition of used stored routine variables. That's why
+ runtime context needs a reference to the parsing context.
+*/
+
+class sp_rcontext : public Sql_alloc
+{
+public:
+ /// Construct and properly initialize a new sp_rcontext instance. The static
+ /// create-function is needed because we need a way to return an error from
+ /// the constructor.
+ ///
+ /// @param thd Thread handle.
+ /// @param root_parsing_ctx Top-level parsing context for this stored program.
+ /// @param return_value_fld Field object to store the return value
+ /// (for stored functions only).
+ ///
+ /// @return valid sp_rcontext object or NULL in case of OOM-error.
+ static sp_rcontext *create(THD *thd,
+ const sp_pcontext *root_parsing_ctx,
+ Field *return_value_fld);
+
+ ~sp_rcontext();
+
+private:
+ sp_rcontext(const sp_pcontext *root_parsing_ctx,
+ Field *return_value_fld,
+ bool in_sub_stmt);
+
+ // Prevent use of copying constructor and operator.
+ sp_rcontext(const sp_rcontext &);
+ void operator=(sp_rcontext &);
+
+private:
+ /// This is an auxillary class to store entering instruction pointer for an
+ /// SQL-handler.
+ class sp_handler_entry : public Sql_alloc
+ {
+ public:
+ /// Handler definition (from parsing context).
+ const sp_handler *handler;
+
+ /// Instruction pointer to the first instruction.
+ uint first_ip;
+
+ /// The constructor.
+ ///
+ /// @param _handler sp_handler object.
+ /// @param _first_ip first instruction pointer.
+ sp_handler_entry(const sp_handler *_handler, uint _first_ip)
+ :handler(_handler), first_ip(_first_ip)
+ { }
+ };
+
+public:
+ /// This class stores basic information about SQL-condition, such as:
+ /// - SQL error code;
+ /// - error level;
+ /// - SQLSTATE;
+ /// - text message.
+ ///
+ /// It's used to organize runtime SQL-handler call stack.
+ ///
+ /// Standard Sql_condition class can not be used, because we don't always have
+ /// an Sql_condition object for an SQL-condition in Diagnostics_area.
+ ///
+ /// Eventually, this class should be moved to sql_error.h, and be a part of
+ /// standard SQL-condition processing (Diagnostics_area should contain an
+ /// object for active SQL-condition, not just information stored in DA's
+ /// fields).
+ class Sql_condition_info : public Sql_alloc
+ {
+ public:
+ /// SQL error code.
+ uint sql_errno;
+
+ /// Error level.
+ Sql_condition::enum_warning_level level;
+
+ /// SQLSTATE.
+ char sql_state[SQLSTATE_LENGTH + 1];
+
+ /// Text message.
+ char *message;
+
+ /// The constructor.
+ ///
+ /// @param _sql_condition The SQL condition.
+ /// @param arena Query arena for SP
+ Sql_condition_info(const Sql_condition *_sql_condition,
+ Query_arena *arena)
+ :sql_errno(_sql_condition->get_sql_errno()),
+ level(_sql_condition->get_level())
+ {
+ memcpy(sql_state, _sql_condition->get_sqlstate(), SQLSTATE_LENGTH);
+ sql_state[SQLSTATE_LENGTH]= '\0';
+
+ message= strdup_root(arena->mem_root, _sql_condition->get_message_text());
+ }
+ };
+
+private:
+ /// This class represents a call frame of SQL-handler (one invocation of a
+ /// handler). Basically, it's needed to store continue instruction pointer for
+ /// CONTINUE SQL-handlers.
+ class Handler_call_frame : public Sql_alloc
+ {
+ public:
+ /// SQL-condition, triggered handler activation.
+ const Sql_condition_info *sql_condition;
+
+ /// Continue-instruction-pointer for CONTINUE-handlers.
+ /// The attribute contains 0 for EXIT-handlers.
+ uint continue_ip;
+
+ /// The constructor.
+ ///
+ /// @param _sql_condition SQL-condition, triggered handler activation.
+ /// @param _continue_ip Continue instruction pointer.
+ Handler_call_frame(const Sql_condition_info *_sql_condition,
+ uint _continue_ip)
+ :sql_condition(_sql_condition),
+ continue_ip(_continue_ip)
+ { }
+ };
+
+public:
+ /// Arena used to (re) allocate items on. E.g. reallocate INOUT/OUT
+ /// SP-variables when they don't fit into prealloced items. This is common
+ /// situation with String items. It is used mainly in sp_eval_func_item().
+ Query_arena *callers_arena;
+
+ /// Flag to end an open result set before start executing an SQL-handler
+ /// (if one is found). Otherwise the client will hang due to a violation
+ /// of the client/server protocol.
+ bool end_partial_result_set;
+
+#ifndef DBUG_OFF
+ /// The stored program for which this runtime context is created. Used for
+ /// checking if correct runtime context is used for variable handling.
+ sp_head *sp;
+#endif
+
+ /////////////////////////////////////////////////////////////////////////
+ // SP-variables.
+ /////////////////////////////////////////////////////////////////////////
+
+ int set_variable(THD *thd, uint var_idx, Item **value)
+ { return set_variable(thd, m_var_table->field[var_idx], value); }
+
+ Item *get_item(uint var_idx) const
+ { return m_var_items[var_idx]; }
+
+ Item **get_item_addr(uint var_idx) const
+ { return m_var_items.array() + var_idx; }
+
+ bool set_return_value(THD *thd, Item **return_value_item);
+
+ bool is_return_value_set() const
+ { return m_return_value_set; }
+
+ /////////////////////////////////////////////////////////////////////////
+ // SQL-handlers.
+ /////////////////////////////////////////////////////////////////////////
+
+ /// Create a new sp_handler_entry instance and push it to the handler call
+ /// stack.
+ ///
+ /// @param handler SQL-handler object.
+ /// @param first_ip First instruction pointer of the handler.
+ ///
+ /// @return error flag.
+ /// @retval false on success.
+ /// @retval true on error.
+ bool push_handler(sp_handler *handler, uint first_ip);
+
+ /// Pop and delete given number of sp_handler_entry instances from the handler
+ /// call stack.
+ ///
+ /// @param count Number of handler entries to pop & delete.
+ void pop_handlers(size_t count);
+
+ const Sql_condition_info *raised_condition() const
+ {
+ return m_handler_call_stack.elements() ?
+ (*m_handler_call_stack.back())->sql_condition : NULL;
+ }
+
+ /// Handle current SQL condition (if any).
+ ///
+ /// This is the public-interface function to handle SQL conditions in
+ /// stored routines.
+ ///
+ /// @param thd Thread handle.
+ /// @param ip[out] Instruction pointer to the first handler
+ /// instruction.
+ /// @param cur_spi Current SP instruction.
+ ///
+ /// @retval true if an SQL-handler has been activated. That means, all of
+ /// the following conditions are satisfied:
+ /// - the SP-instruction raised SQL-condition(s),
+ /// - and there is an SQL-handler to process at least one of those
+ /// SQL-conditions,
+ /// - and that SQL-handler has been activated.
+ /// Note, that the return value has nothing to do with "error flag"
+ /// semantics.
+ ///
+ /// @retval false otherwise.
+ bool handle_sql_condition(THD *thd,
+ uint *ip,
+ const sp_instr *cur_spi);
+
+ /// Remove latest call frame from the handler call stack.
+ ///
+ /// @param da Diagnostics area containing handled conditions.
+ ///
+ /// @return continue instruction pointer of the removed handler.
+ uint exit_handler(Diagnostics_area *da);
+
+ /////////////////////////////////////////////////////////////////////////
+ // Cursors.
+ /////////////////////////////////////////////////////////////////////////
+
+ /// Create a new sp_cursor instance and push it to the cursor stack.
+ ///
+ /// @param lex_keeper SP-instruction execution helper.
+ /// @param i Cursor-push instruction.
+ ///
+ /// @return error flag.
+ /// @retval false on success.
+ /// @retval true on error.
+ bool push_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i);
+
+ /// Pop and delete given number of sp_cursor instance from the cursor stack.
+ ///
+ /// @param count Number of cursors to pop & delete.
+ void pop_cursors(uint count);
+
+ void pop_all_cursors()
+ { pop_cursors(m_ccount); }
+
+ sp_cursor *get_cursor(uint i) const
+ { return m_cstack[i]; }
+
+ /////////////////////////////////////////////////////////////////////////
+ // CASE expressions.
+ /////////////////////////////////////////////////////////////////////////
+
+ /// Set CASE expression to the specified value.
+ ///
+ /// @param thd Thread handler.
+ /// @param case_expr_id The CASE expression identifier.
+ /// @param case_expr_item The CASE expression value
+ ///
+ /// @return error flag.
+ /// @retval false on success.
+ /// @retval true on error.
+ ///
+ /// @note The idea is to reuse Item_cache for the expression of the one
+ /// CASE statement. This optimization takes place when there is CASE
+ /// statement inside of a loop. So, in other words, we will use the same
+ /// object on each iteration instead of creating a new one for each
+ /// iteration.
+ ///
+ /// TODO
+ /// Hypothetically, a type of CASE expression can be different for each
+ /// iteration. For instance, this can happen if the expression contains
+ /// a session variable (something like @@VAR) and its type is changed
+ /// from one iteration to another.
+ ///
+ /// In order to cope with this problem, we check type each time, when we
+ /// use already created object. If the type does not match, we re-create
+ /// Item. This also can (should?) be optimized.
+ bool set_case_expr(THD *thd, int case_expr_id, Item **case_expr_item_ptr);
+
+ Item *get_case_expr(int case_expr_id) const
+ { return m_case_expr_holders[case_expr_id]; }
+
+ Item ** get_case_expr_addr(int case_expr_id) const
+ { return (Item**) m_case_expr_holders.array() + case_expr_id; }
+
+private:
+ /// Internal function to allocate memory for arrays.
+ ///
+ /// @param thd Thread handle.
+ ///
+ /// @return error flag: false on success, true in case of failure.
+ bool alloc_arrays(THD *thd);
+
+ /// Create and initialize a table to store SP-variables.
+ ///
+ /// param thd Thread handle.
+ ///
+ /// @return error flag.
+ /// @retval false on success.
+ /// @retval true on error.
+ bool init_var_table(THD *thd);
+
+ /// Create and initialize an Item-adapter (Item_field) for each SP-var field.
+ ///
+ /// param thd Thread handle.
+ ///
+ /// @return error flag.
+ /// @retval false on success.
+ /// @retval true on error.
+ bool init_var_items(THD *thd);
+
+ /// Create an instance of appropriate Item_cache class depending on the
+ /// specified type in the callers arena.
+ ///
+ /// @note We should create cache items in the callers arena, as they are
+ /// used between in several instructions.
+ ///
+ /// @param thd Thread handler.
+ /// @param item Item to get the expression type.
+ ///
+ /// @return Pointer to valid object on success, or NULL in case of error.
+ Item_cache *create_case_expr_holder(THD *thd, const Item *item) const;
+
+ int set_variable(THD *thd, Field *field, Item **value);
+
+private:
+ /// Top-level (root) parsing context for this runtime context.
+ const sp_pcontext *m_root_parsing_ctx;
+
+ /// Virtual table for storing SP-variables.
+ TABLE *m_var_table;
+
+ /// Collection of Item_field proxies, each of them points to the
+ /// corresponding field in m_var_table.
+ Bounds_checked_array<Item *> m_var_items;
+
+ /// This is a pointer to a field, which should contain return value for
+ /// stored functions (only). For stored procedures, this pointer is NULL.
+ Field *m_return_value_fld;
+
+ /// Indicates whether the return value (in m_return_value_fld) has been
+ /// set during execution.
+ bool m_return_value_set;
+
+ /// Flag to tell if the runtime context is created for a sub-statement.
+ bool m_in_sub_stmt;
+
+ /// Stack of visible handlers.
+ Dynamic_array<sp_handler_entry *> m_handlers;
+
+ /// Stack of caught SQL conditions.
+ Dynamic_array<Handler_call_frame *> m_handler_call_stack;
+
+ /// Stack of cursors.
+ Bounds_checked_array<sp_cursor *> m_cstack;
+
+ /// Current number of cursors in m_cstack.
+ uint m_ccount;
+
+ /// Array of CASE expression holders.
+ Bounds_checked_array<Item_cache *> m_case_expr_holders;
+}; // class sp_rcontext : public Sql_alloc
+
+///////////////////////////////////////////////////////////////////////////
+// sp_cursor declaration.
+///////////////////////////////////////////////////////////////////////////
+
+class Server_side_cursor;
+typedef class st_select_lex_unit SELECT_LEX_UNIT;
+
+/* A mediator between stored procedures and server side cursors */
+
+class sp_cursor : public Sql_alloc
+{
+private:
+ /// An interceptor of cursor result set used to implement
+ /// FETCH <cname> INTO <varlist>.
+ class Select_fetch_into_spvars: public select_result_interceptor
+ {
+ List<sp_variable> *spvar_list;
+ uint field_count;
+ public:
+ Select_fetch_into_spvars() {} /* Remove gcc warning */
+ uint get_field_count() { return field_count; }
+ void set_spvar_list(List<sp_variable> *vars) { spvar_list= vars; }
+
+ virtual bool send_eof() { return FALSE; }
+ virtual int send_data(List<Item> &items);
+ virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+};
+
+public:
+ sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i);
+
+ virtual ~sp_cursor()
+ { destroy(); }
+
+ sp_lex_keeper *get_lex_keeper() { return m_lex_keeper; }
+
+ int open(THD *thd);
+
+ int close(THD *thd);
+
+ my_bool is_open()
+ { return MY_TEST(server_side_cursor); }
+
+ int fetch(THD *, List<sp_variable> *vars);
+
+ sp_instr_cpush *get_instr()
+ { return m_i; }
+
+private:
+ Select_fetch_into_spvars result;
+ sp_lex_keeper *m_lex_keeper;
+ Server_side_cursor *server_side_cursor;
+ sp_instr_cpush *m_i; // My push instruction
+ void destroy();
+
+}; // class sp_cursor : public Sql_alloc
+
+#endif /* _SP_RCONTEXT_H_ */
diff --git a/sql/spatial.cc b/sql/spatial.cc
new file mode 100644
index 00000000000..bfe302f332e
--- /dev/null
+++ b/sql/spatial.cc
@@ -0,0 +1,2607 @@
+/*
+ Copyright (c) 2002, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2013, Monty Program Ab.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "spatial.h"
+#include "gstream.h" // Gis_read_stream
+#include "sql_string.h" // String
+
+#ifdef HAVE_SPATIAL
+
+/*
+ exponential notation :
+ 1 sign
+ 1 number before the decimal point
+ 1 decimal point
+ 14 number of significant digits (see String::qs_append(double))
+ 1 'e' sign
+ 1 exponent sign
+ 3 exponent digits
+ ==
+ 22
+
+ "f" notation :
+ 1 optional 0
+ 1 sign
+ 14 number significant digits (see String::qs_append(double) )
+ 1 decimal point
+ ==
+ 17
+*/
+
+#define MAX_DIGITS_IN_DOUBLE MY_GCVT_MAX_FIELD_WIDTH
+
+/***************************** Gis_class_info *******************************/
+
+String Geometry::bad_geometry_data("Bad object", &my_charset_bin);
+
+Geometry::Class_info *Geometry::ci_collection[Geometry::wkb_last+1]=
+{
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+static Geometry::Class_info **ci_collection_end=
+ Geometry::ci_collection+Geometry::wkb_last + 1;
+
+Geometry::Class_info::Class_info(const char *name, int type_id,
+ create_geom_t create_func):
+ m_type_id(type_id), m_create_func(create_func)
+{
+ m_name.str= (char *) name;
+ m_name.length= strlen(name);
+
+ ci_collection[type_id]= this;
+}
+
+static Geometry *create_point(char *buffer)
+{
+ return new (buffer) Gis_point;
+}
+
+static Geometry *create_linestring(char *buffer)
+{
+ return new (buffer) Gis_line_string;
+}
+
+static Geometry *create_polygon(char *buffer)
+{
+ return new (buffer) Gis_polygon;
+}
+
+static Geometry *create_multipoint(char *buffer)
+{
+ return new (buffer) Gis_multi_point;
+}
+
+static Geometry *create_multipolygon(char *buffer)
+{
+ return new (buffer) Gis_multi_polygon;
+}
+
+static Geometry *create_multilinestring(char *buffer)
+{
+ return new (buffer) Gis_multi_line_string;
+}
+
+static Geometry *create_geometrycollection(char *buffer)
+{
+ return new (buffer) Gis_geometry_collection;
+}
+
+
+
+static Geometry::Class_info point_class("POINT",
+ Geometry::wkb_point, create_point);
+
+static Geometry::Class_info linestring_class("LINESTRING",
+ Geometry::wkb_linestring,
+ create_linestring);
+static Geometry::Class_info polygon_class("POLYGON",
+ Geometry::wkb_polygon,
+ create_polygon);
+static Geometry::Class_info multipoint_class("MULTIPOINT",
+ Geometry::wkb_multipoint,
+ create_multipoint);
+static Geometry::Class_info
+multilinestring_class("MULTILINESTRING",
+ Geometry::wkb_multilinestring, create_multilinestring);
+static Geometry::Class_info multipolygon_class("MULTIPOLYGON",
+ Geometry::wkb_multipolygon,
+ create_multipolygon);
+static Geometry::Class_info
+geometrycollection_class("GEOMETRYCOLLECTION",Geometry::wkb_geometrycollection,
+ create_geometrycollection);
+
+static void get_point(double *x, double *y, const char *data)
+{
+ float8get(*x, data);
+ float8get(*y, data + SIZEOF_STORED_DOUBLE);
+}
+
+/***************************** Geometry *******************************/
+
+Geometry::Class_info *Geometry::find_class(const char *name, uint32 len)
+{
+ for (Class_info **cur_rt= ci_collection;
+ cur_rt < ci_collection_end; cur_rt++)
+ {
+ if (*cur_rt &&
+ ((*cur_rt)->m_name.length == len) &&
+ (my_strnncoll(&my_charset_latin1,
+ (const uchar*) (*cur_rt)->m_name.str, len,
+ (const uchar*) name, len) == 0))
+ return *cur_rt;
+ }
+ return 0;
+}
+
+
+Geometry *Geometry::create_by_typeid(Geometry_buffer *buffer, int type_id)
+{
+ Class_info *ci;
+ if (!(ci= find_class(type_id)))
+ return NULL;
+ return (*ci->m_create_func)(buffer->data);
+}
+
+
+Geometry *Geometry::construct(Geometry_buffer *buffer,
+ const char *data, uint32 data_len)
+{
+ uint32 geom_type;
+ Geometry *result;
+
+ if (data_len < SRID_SIZE + WKB_HEADER_SIZE) // < 4 + (1 + 4)
+ return NULL;
+ /* + 1 to skip the byte order (stored in position SRID_SIZE). */
+ geom_type= uint4korr(data + SRID_SIZE + 1);
+ if (!(result= create_by_typeid(buffer, (int) geom_type)))
+ return NULL;
+ result->m_data= data+ SRID_SIZE + WKB_HEADER_SIZE;
+ result->m_data_end= data + data_len;
+ return result;
+}
+
+
+Geometry *Geometry::create_from_wkt(Geometry_buffer *buffer,
+ Gis_read_stream *trs, String *wkt,
+ bool init_stream)
+{
+ LEX_STRING name;
+ Class_info *ci;
+ char next_sym;
+
+ if (trs->get_next_word(&name))
+ {
+ trs->set_error_msg("Geometry name expected");
+ return NULL;
+ }
+ if (!(ci= find_class(name.str, name.length)) ||
+ wkt->reserve(1 + 4, 512))
+ return NULL;
+ Geometry *result= (*ci->m_create_func)(buffer->data);
+ wkt->q_append((char) wkb_ndr);
+ wkt->q_append((uint32) result->get_class_info()->m_type_id);
+ if (!(next_sym= trs->next_symbol()))
+ return NULL;
+ if (!(next_sym= trs->next_symbol()))
+ return NULL;
+ if ((next_sym == '(' && trs->check_next_symbol('(')) ||
+ result->init_from_wkt(trs, wkt) ||
+ (next_sym == '(' && trs->check_next_symbol(')')))
+ return NULL;
+ if (init_stream)
+ {
+ result->set_data_ptr(wkt->ptr(), wkt->length());
+ result->shift_wkb_header();
+ }
+ return result;
+}
+
+
+int Geometry::as_wkt(String *wkt, const char **end)
+{
+ uint32 len= (uint) get_class_info()->m_name.length;
+ if (wkt->reserve(len + 2, 512))
+ return 1;
+ wkt->qs_append(get_class_info()->m_name.str, len);
+ if (get_class_info() != &geometrycollection_class)
+ wkt->qs_append('(');
+ if (get_data_as_wkt(wkt, end))
+ return 1;
+ if (get_class_info() != &geometrycollection_class)
+ wkt->qs_append(')');
+ return 0;
+}
+
+
+static double wkb_get_double(const char *ptr, Geometry::wkbByteOrder bo)
+{
+ double res;
+ if (bo != Geometry::wkb_xdr)
+ {
+ float8get(res, ptr);
+ }
+ else
+ {
+ char inv_array[8];
+ inv_array[0]= ptr[7];
+ inv_array[1]= ptr[6];
+ inv_array[2]= ptr[5];
+ inv_array[3]= ptr[4];
+ inv_array[4]= ptr[3];
+ inv_array[5]= ptr[2];
+ inv_array[6]= ptr[1];
+ inv_array[7]= ptr[0];
+ float8get(res, inv_array);
+ }
+ return res;
+}
+
+
+static uint32 wkb_get_uint(const char *ptr, Geometry::wkbByteOrder bo)
+{
+ if (bo != Geometry::wkb_xdr)
+ return uint4korr(ptr);
+ /* else */
+ {
+ char inv_array[4];
+ inv_array[0]= ptr[3];
+ inv_array[1]= ptr[2];
+ inv_array[2]= ptr[1];
+ inv_array[3]= ptr[0];
+ return uint4korr(inv_array);
+ }
+}
+
+
+Geometry *Geometry::create_from_wkb(Geometry_buffer *buffer,
+ const char *wkb, uint32 len, String *res)
+{
+ uint32 geom_type;
+ Geometry *geom;
+
+ if (len < WKB_HEADER_SIZE)
+ return NULL;
+ geom_type= wkb_get_uint(wkb+1, (wkbByteOrder)wkb[0]);
+ if (!(geom= create_by_typeid(buffer, (int) geom_type)) ||
+ res->reserve(WKB_HEADER_SIZE, 512))
+ return NULL;
+
+ res->q_append((char) wkb_ndr);
+ res->q_append(geom_type);
+
+ return geom->init_from_wkb(wkb + WKB_HEADER_SIZE, len - WKB_HEADER_SIZE,
+ (wkbByteOrder) wkb[0], res) ? geom : NULL;
+}
+
+
+int Geometry::create_from_opresult(Geometry_buffer *g_buf,
+ String *res, Gcalc_result_receiver &rr)
+{
+ uint32 geom_type= rr.get_result_typeid();
+ Geometry *obj= create_by_typeid(g_buf, geom_type);
+
+ if (!obj || res->reserve(WKB_HEADER_SIZE, 512))
+ return 1;
+
+ res->q_append((char) wkb_ndr);
+ res->q_append(geom_type);
+ return obj->init_from_opresult(res, rr.result(), rr.length()) == 0 &&
+ rr.length();
+}
+
+
+bool Geometry::envelope(String *result) const
+{
+ MBR mbr;
+ const char *end;
+
+ if (get_mbr(&mbr, &end))
+ return 1;
+
+ if (!mbr.valid())
+ {
+ /* Empty geometry */
+ if (result->reserve(1 + 4*2))
+ return 1;
+ result->q_append((char) wkb_ndr);
+ result->q_append((uint32) wkb_geometrycollection);
+ result->q_append((uint32) 0);
+ return 0;
+ }
+ if (result->reserve(1 + 4 * 3 + SIZEOF_STORED_DOUBLE * 10))
+ return 1;
+
+ result->q_append((char) wkb_ndr);
+ result->q_append((uint32) wkb_polygon);
+ result->q_append((uint32) 1);
+ result->q_append((uint32) 5);
+ result->q_append(mbr.xmin);
+ result->q_append(mbr.ymin);
+ result->q_append(mbr.xmax);
+ result->q_append(mbr.ymin);
+ result->q_append(mbr.xmax);
+ result->q_append(mbr.ymax);
+ result->q_append(mbr.xmin);
+ result->q_append(mbr.ymax);
+ result->q_append(mbr.xmin);
+ result->q_append(mbr.ymin);
+
+ return 0;
+}
+
+
+/*
+ Create a point from data.
+
+ SYNPOSIS
+ create_point()
+ result Put result here
+ data Data for point is here.
+
+ RETURN
+ 0 ok
+ 1 Can't reallocate 'result'
+*/
+
+bool Geometry::create_point(String *result, const char *data) const
+{
+ if (no_data(data, POINT_DATA_SIZE) ||
+ result->reserve(1 + 4 + POINT_DATA_SIZE))
+ return 1;
+ result->q_append((char) wkb_ndr);
+ result->q_append((uint32) wkb_point);
+ /* Copy two double in same format */
+ result->q_append(data, POINT_DATA_SIZE);
+ return 0;
+}
+
+/*
+ Create a point from coordinates.
+
+ SYNPOSIS
+ create_point()
+ result Put result here
+ x x coordinate for point
+ y y coordinate for point
+
+ RETURN
+ 0 ok
+ 1 Can't reallocate 'result'
+*/
+
+bool Geometry::create_point(String *result, double x, double y) const
+{
+ if (result->reserve(1 + 4 + POINT_DATA_SIZE))
+ return 1;
+
+ result->q_append((char) wkb_ndr);
+ result->q_append((uint32) wkb_point);
+ result->q_append(x);
+ result->q_append(y);
+ return 0;
+}
+
+/*
+ Append N points from packed format to text
+
+ SYNOPSIS
+ append_points()
+ txt Append points here
+ n_points Number of points
+ data Packed data
+ offset Offset between points
+
+ RETURN
+ # end of data
+*/
+
+const char *Geometry::append_points(String *txt, uint32 n_points,
+ const char *data, uint32 offset) const
+{
+ while (n_points--)
+ {
+ double x,y;
+ data+= offset;
+ get_point(&x, &y, data);
+ data+= POINT_DATA_SIZE;
+ txt->qs_append(x);
+ txt->qs_append(' ');
+ txt->qs_append(y);
+ txt->qs_append(',');
+ }
+ return data;
+}
+
+
+/*
+ Get most bounding rectangle (mbr) for X points
+
+ SYNOPSIS
+ get_mbr_for_points()
+ mbr MBR (store rectangle here)
+ points Number of points
+ data Packed data
+ offset Offset between points
+
+ RETURN
+ 0 Wrong data
+ # end of data
+*/
+
+const char *Geometry::get_mbr_for_points(MBR *mbr, const char *data,
+ uint offset) const
+{
+ uint32 points;
+ /* read number of points */
+ if (no_data(data, 4))
+ return 0;
+ points= uint4korr(data);
+ data+= 4;
+
+ if (not_enough_points(data, points, offset))
+ return 0;
+
+ /* Calculate MBR for points */
+ while (points--)
+ {
+ data+= offset;
+ mbr->add_xy(data, data + SIZEOF_STORED_DOUBLE);
+ data+= POINT_DATA_SIZE;
+ }
+ return data;
+}
+
+
+/***************************** Point *******************************/
+
+uint32 Gis_point::get_data_size() const
+{
+ return POINT_DATA_SIZE;
+}
+
+
+bool Gis_point::init_from_wkt(Gis_read_stream *trs, String *wkb)
+{
+ double x, y;
+ if (trs->get_next_number(&x) || trs->get_next_number(&y) ||
+ wkb->reserve(POINT_DATA_SIZE, 512))
+ return 1;
+ wkb->q_append(x);
+ wkb->q_append(y);
+ return 0;
+}
+
+
+uint Gis_point::init_from_wkb(const char *wkb, uint len,
+ wkbByteOrder bo, String *res)
+{
+ double x, y;
+ if (len < POINT_DATA_SIZE || res->reserve(POINT_DATA_SIZE))
+ return 0;
+ x= wkb_get_double(wkb, bo);
+ y= wkb_get_double(wkb + SIZEOF_STORED_DOUBLE, bo);
+ res->q_append(x);
+ res->q_append(y);
+ return POINT_DATA_SIZE;
+}
+
+
+bool Gis_point::get_data_as_wkt(String *txt, const char **end) const
+{
+ double x, y;
+ if (get_xy(&x, &y))
+ return 1;
+ if (txt->reserve(MAX_DIGITS_IN_DOUBLE * 2 + 1))
+ return 1;
+ txt->qs_append(x);
+ txt->qs_append(' ');
+ txt->qs_append(y);
+ *end= m_data+ POINT_DATA_SIZE;
+ return 0;
+}
+
+
+bool Gis_point::get_mbr(MBR *mbr, const char **end) const
+{
+ double x, y;
+ if (get_xy(&x, &y))
+ return 1;
+ mbr->add_xy(x, y);
+ *end= m_data+ POINT_DATA_SIZE;
+ return 0;
+}
+
+
+int Gis_point::area(double *ar, const char **end) const
+{
+ *ar= 0;
+ *end= m_data+ POINT_DATA_SIZE;
+ return 0;
+}
+
+
+int Gis_point::geom_length(double *len, const char **end) const
+{
+ *len= 0;
+ *end= m_data+ POINT_DATA_SIZE;
+ return 0;
+}
+
+
+int Gis_point::store_shapes(Gcalc_shape_transporter *trn) const
+{
+ double x, y;
+
+ return get_xy(&x, &y) || trn->single_point(x, y);
+}
+
+
+const Geometry::Class_info *Gis_point::get_class_info() const
+{
+ return &point_class;
+}
+
+
+/***************************** LineString *******************************/
+
+uint32 Gis_line_string::get_data_size() const
+{
+ uint32 n_points;
+ if (no_data(m_data, 4))
+ return GET_SIZE_ERROR;
+
+ n_points= uint4korr(m_data);
+
+ if (not_enough_points(m_data + 4, n_points))
+ return GET_SIZE_ERROR;
+
+ return 4 + n_points * POINT_DATA_SIZE;
+}
+
+
+bool Gis_line_string::init_from_wkt(Gis_read_stream *trs, String *wkb)
+{
+ uint32 n_points= 0;
+ uint32 np_pos= wkb->length();
+ Gis_point p;
+
+ if (wkb->reserve(4, 512))
+ return 1;
+ wkb->length(wkb->length()+4); // Reserve space for points
+
+ for (;;)
+ {
+ if (p.init_from_wkt(trs, wkb))
+ return 1;
+ n_points++;
+ if (trs->skip_char(',')) // Didn't find ','
+ break;
+ }
+ if (n_points < 1)
+ {
+ trs->set_error_msg("Too few points in LINESTRING");
+ return 1;
+ }
+ wkb->write_at_position(np_pos, n_points);
+ return 0;
+}
+
+
+uint Gis_line_string::init_from_wkb(const char *wkb, uint len,
+ wkbByteOrder bo, String *res)
+{
+ uint32 n_points, proper_length;
+ const char *wkb_end;
+ Gis_point p;
+
+ if (len < 4 || (n_points= wkb_get_uint(wkb, bo)) < 1 ||
+ ((len - 4) / POINT_DATA_SIZE) < n_points)
+ return 0;
+ proper_length= 4 + n_points * POINT_DATA_SIZE;
+
+ if (len < proper_length || res->reserve(proper_length))
+ return 0;
+
+ res->q_append(n_points);
+ wkb_end= wkb + proper_length;
+ for (wkb+= 4; wkb<wkb_end; wkb+= POINT_DATA_SIZE)
+ {
+ if (!p.init_from_wkb(wkb, POINT_DATA_SIZE, bo, res))
+ return 0;
+ }
+
+ return proper_length;
+}
+
+
+bool Gis_line_string::get_data_as_wkt(String *txt, const char **end) const
+{
+ uint32 n_points;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data += 4;
+
+ if (n_points < 1 ||
+ not_enough_points(data, n_points) ||
+ txt->reserve(((MAX_DIGITS_IN_DOUBLE + 1)*2 + 1) * n_points))
+ return 1;
+
+ while (n_points--)
+ {
+ double x, y;
+ get_point(&x, &y, data);
+ data+= POINT_DATA_SIZE;
+ txt->qs_append(x);
+ txt->qs_append(' ');
+ txt->qs_append(y);
+ txt->qs_append(',');
+ }
+ txt->length(txt->length() - 1); // Remove end ','
+ *end= data;
+ return 0;
+}
+
+
+bool Gis_line_string::get_mbr(MBR *mbr, const char **end) const
+{
+ return (*end=get_mbr_for_points(mbr, m_data, 0)) == 0;
+}
+
+
+int Gis_line_string::geom_length(double *len, const char **end) const
+{
+ uint32 n_points;
+ double prev_x, prev_y;
+ const char *data= m_data;
+
+ *len= 0; // In case of errors
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data+= 4;
+ if (n_points < 1 || not_enough_points(data, n_points))
+ return 1;
+
+ get_point(&prev_x, &prev_y, data);
+ data+= POINT_DATA_SIZE;
+ while (--n_points)
+ {
+ double x, y;
+ get_point(&x, &y, data);
+ data+= POINT_DATA_SIZE;
+ *len+= sqrt(pow(prev_x-x,2)+pow(prev_y-y,2));
+ prev_x= x;
+ prev_y= y;
+ }
+ *end= data;
+ return 0;
+}
+
+
+int Gis_line_string::area(double *ar, const char **end) const
+{
+ uint32 n_points;
+ *ar= 0.0;
+
+ /* read number of points */
+ if (no_data(m_data, 4))
+ return 1;
+ n_points= uint4korr(m_data);
+ *end= m_data + 4 + POINT_DATA_SIZE * n_points;
+ return 0;
+}
+
+
+int Gis_line_string::is_closed(int *closed) const
+{
+ uint32 n_points;
+ double x1, y1, x2, y2;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ if (n_points == 1)
+ {
+ *closed=1;
+ return 0;
+ }
+ data+= 4;
+ if (n_points == 0 || not_enough_points(data, n_points))
+ return 1;
+
+ /* Get first point */
+ get_point(&x1, &y1, data);
+
+ /* get last point */
+ data+= POINT_DATA_SIZE + (n_points-2)*POINT_DATA_SIZE;
+ get_point(&x2, &y2, data);
+
+ *closed= (x1==x2) && (y1==y2);
+ return 0;
+}
+
+
+int Gis_line_string::num_points(uint32 *n_points) const
+{
+ *n_points= uint4korr(m_data);
+ return 0;
+}
+
+
+int Gis_line_string::start_point(String *result) const
+{
+ /* +4 is for skipping over number of points */
+ return create_point(result, m_data + 4);
+}
+
+
+int Gis_line_string::end_point(String *result) const
+{
+ uint32 n_points;
+ if (no_data(m_data, 4))
+ return 1;
+ n_points= uint4korr(m_data);
+ if (n_points == 0 || not_enough_points(m_data+4, n_points))
+ return 1;
+ return create_point(result, m_data + 4 + (n_points - 1) * POINT_DATA_SIZE);
+}
+
+
+int Gis_line_string::point_n(uint32 num, String *result) const
+{
+ uint32 n_points;
+ if (no_data(m_data, 4))
+ return 1;
+ num--;
+ n_points= uint4korr(m_data);
+ if (num >= n_points || not_enough_points(m_data+4, n_points))
+ return 1;
+
+ return create_point(result, m_data + 4 + num*POINT_DATA_SIZE);
+}
+
+
+int Gis_line_string::store_shapes(Gcalc_shape_transporter *trn) const
+{
+ uint32 n_points;
+ double x, y;
+ double UNINIT_VAR(prev_x), UNINIT_VAR(prev_y);
+ int first_point= 1;
+ const char *data= m_data;
+
+ if (no_data(m_data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data+= 4;
+ if (n_points < 1 || not_enough_points(data, n_points))
+ return 1;
+
+ trn->start_line();
+
+ while (n_points--)
+ {
+ get_point(&x, &y, data);
+ data+= POINT_DATA_SIZE;
+ if (!first_point && x == prev_x && y == prev_y)
+ continue;
+ if (trn->add_point(x, y))
+ return 1;
+ first_point= 0;
+ prev_x= x;
+ prev_y= y;
+ }
+
+ return trn->complete_line();
+}
+
+const Geometry::Class_info *Gis_line_string::get_class_info() const
+{
+ return &linestring_class;
+}
+
+
+/***************************** Polygon *******************************/
+
+uint32 Gis_polygon::get_data_size() const
+{
+ uint32 n_linear_rings;
+ uint32 n_points;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return GET_SIZE_ERROR;
+ n_linear_rings= uint4korr(data);
+ data+= 4;
+
+ while (n_linear_rings--)
+ {
+ if (no_data(data, 4) ||
+ not_enough_points(data+4, n_points= uint4korr(data)))
+ return GET_SIZE_ERROR;
+ data+= 4 + n_points*POINT_DATA_SIZE;
+ }
+ if (no_data(data, 0))
+ return GET_SIZE_ERROR;
+ return (uint32) (data - m_data);
+}
+
+
+bool Gis_polygon::init_from_wkt(Gis_read_stream *trs, String *wkb)
+{
+ uint32 n_linear_rings= 0;
+ uint32 lr_pos= wkb->length();
+ int closed;
+
+ if (wkb->reserve(4, 512))
+ return 1;
+ wkb->length(wkb->length()+4); // Reserve space for points
+ for (;;)
+ {
+ Gis_line_string ls;
+ uint32 ls_pos=wkb->length();
+ if (trs->check_next_symbol('(') ||
+ ls.init_from_wkt(trs, wkb) ||
+ trs->check_next_symbol(')'))
+ return 1;
+
+ ls.set_data_ptr(wkb->ptr() + ls_pos, wkb->length() - ls_pos);
+ if (ls.is_closed(&closed) || !closed)
+ {
+ trs->set_error_msg("POLYGON's linear ring isn't closed");
+ return 1;
+ }
+ n_linear_rings++;
+ if (trs->skip_char(',')) // Didn't find ','
+ break;
+ }
+ wkb->write_at_position(lr_pos, n_linear_rings);
+ return 0;
+}
+
+
+uint Gis_polygon::init_from_opresult(String *bin,
+ const char *opres, uint res_len)
+{
+ const char *opres_orig= opres;
+ uint32 position= bin->length();
+ uint32 poly_shapes= 0;
+
+ if (bin->reserve(4, 512))
+ return 0;
+ bin->q_append(poly_shapes);
+
+ while (opres_orig + res_len > opres)
+ {
+ uint32 n_points, proper_length;
+ const char *op_end, *p1_position;
+ Gis_point p;
+ Gcalc_function::shape_type st;
+
+ st= (Gcalc_function::shape_type) uint4korr(opres);
+ if (poly_shapes && st != Gcalc_function::shape_hole)
+ break;
+ poly_shapes++;
+ n_points= uint4korr(opres + 4) + 1; /* skip shape type id */
+ proper_length= 4 + n_points * POINT_DATA_SIZE;
+
+ if (bin->reserve(proper_length, 512))
+ return 0;
+
+ bin->q_append(n_points);
+ op_end= opres + 8 + (n_points-1) * 8 * 2;
+ p1_position= (opres+= 8);
+ for (; opres<op_end; opres+= POINT_DATA_SIZE)
+ {
+ if (!p.init_from_wkb(opres, POINT_DATA_SIZE, wkb_ndr, bin))
+ return 0;
+ }
+ if (!p.init_from_wkb(p1_position, POINT_DATA_SIZE, wkb_ndr, bin))
+ return 0;
+ }
+
+ bin->write_at_position(position, poly_shapes);
+
+ return (uint) (opres - opres_orig);
+}
+
+
+uint Gis_polygon::init_from_wkb(const char *wkb, uint len, wkbByteOrder bo,
+ String *res)
+{
+ uint32 n_linear_rings;
+ const char *wkb_orig= wkb;
+
+ if (len < 4)
+ return 0;
+
+ if (!(n_linear_rings= wkb_get_uint(wkb, bo)))
+ return 0;
+
+ if (res->reserve(4, 512))
+ return 0;
+ wkb+= 4;
+ len-= 4;
+ res->q_append(n_linear_rings);
+
+ while (n_linear_rings--)
+ {
+ Gis_line_string ls;
+ uint32 ls_pos= res->length();
+ int ls_len;
+ int closed;
+
+ if (!(ls_len= ls.init_from_wkb(wkb, len, bo, res)))
+ return 0;
+
+ ls.set_data_ptr(res->ptr() + ls_pos, res->length() - ls_pos);
+
+ if (ls.is_closed(&closed) || !closed)
+ return 0;
+ wkb+= ls_len;
+ }
+
+ return (uint) (wkb - wkb_orig);
+}
+
+
+bool Gis_polygon::get_data_as_wkt(String *txt, const char **end) const
+{
+ uint32 n_linear_rings;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+
+ n_linear_rings= uint4korr(data);
+ data+= 4;
+
+ while (n_linear_rings--)
+ {
+ uint32 n_points;
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data+= 4;
+ if (not_enough_points(data, n_points) ||
+ txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
+ return 1;
+ txt->qs_append('(');
+ data= append_points(txt, n_points, data, 0);
+ (*txt) [txt->length() - 1]= ')'; // Replace end ','
+ txt->qs_append(',');
+ }
+ txt->length(txt->length() - 1); // Remove end ','
+ *end= data;
+ return 0;
+}
+
+
+bool Gis_polygon::get_mbr(MBR *mbr, const char **end) const
+{
+ uint32 n_linear_rings;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_linear_rings= uint4korr(data);
+ data+= 4;
+
+ while (n_linear_rings--)
+ {
+ if (!(data= get_mbr_for_points(mbr, data, 0)))
+ return 1;
+ }
+ *end= data;
+ return 0;
+}
+
+
+int Gis_polygon::area(double *ar, const char **end_of_data) const
+{
+ uint32 n_linear_rings;
+ double result= -1.0;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_linear_rings= uint4korr(data);
+ data+= 4;
+
+ while (n_linear_rings--)
+ {
+ double prev_x, prev_y;
+ double lr_area= 0;
+ uint32 n_points;
+
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ if (n_points == 0 ||
+ not_enough_points(data, n_points))
+ return 1;
+ get_point(&prev_x, &prev_y, data+4);
+ data+= (4+POINT_DATA_SIZE);
+
+ while (--n_points) // One point is already read
+ {
+ double x, y;
+ get_point(&x, &y, data);
+ data+= POINT_DATA_SIZE;
+ lr_area+= (prev_x + x)* (prev_y - y);
+ prev_x= x;
+ prev_y= y;
+ }
+ lr_area= fabs(lr_area)/2;
+ if (result == -1.0)
+ result= lr_area;
+ else
+ result-= lr_area;
+ }
+ *ar= fabs(result);
+ *end_of_data= data;
+ return 0;
+}
+
+
+int Gis_polygon::exterior_ring(String *result) const
+{
+ uint32 n_points, length;
+ const char *data= m_data + 4; // skip n_linerings
+
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data+= 4;
+ length= n_points * POINT_DATA_SIZE;
+ if (not_enough_points(data, n_points) || result->reserve(1+4+4+ length))
+ return 1;
+
+ result->q_append((char) wkb_ndr);
+ result->q_append((uint32) wkb_linestring);
+ result->q_append(n_points);
+ result->q_append(data, n_points * POINT_DATA_SIZE);
+ return 0;
+}
+
+
+int Gis_polygon::num_interior_ring(uint32 *n_int_rings) const
+{
+ if (no_data(m_data, 4))
+ return 1;
+ *n_int_rings= uint4korr(m_data)-1;
+ return 0;
+}
+
+
+int Gis_polygon::interior_ring_n(uint32 num, String *result) const
+{
+ const char *data= m_data;
+ uint32 n_linear_rings;
+ uint32 n_points;
+ uint32 points_size;
+
+ if (no_data(data, 4))
+ return 1;
+ n_linear_rings= uint4korr(data);
+ data+= 4;
+
+ if (num >= n_linear_rings || num < 1)
+ return 1;
+
+ while (num--)
+ {
+ if (no_data(data, 4))
+ return 1;
+ data+= 4 + uint4korr(data) * POINT_DATA_SIZE;
+ }
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ points_size= n_points * POINT_DATA_SIZE;
+ data+= 4;
+ if (not_enough_points(data, n_points) || result->reserve(1+4+4+ points_size))
+ return 1;
+
+ result->q_append((char) wkb_ndr);
+ result->q_append((uint32) wkb_linestring);
+ result->q_append(n_points);
+ result->q_append(data, points_size);
+
+ return 0;
+}
+
+
+int Gis_polygon::centroid_xy(double *x, double *y) const
+{
+ uint32 n_linear_rings;
+ double UNINIT_VAR(res_area);
+ double UNINIT_VAR(res_cx), UNINIT_VAR(res_cy);
+ const char *data= m_data;
+ bool first_loop= 1;
+
+ if (no_data(data, 4) ||
+ (n_linear_rings= uint4korr(data)) == 0)
+ return 1;
+ data+= 4;
+
+ while (n_linear_rings--)
+ {
+ uint32 n_points, org_n_points;
+ double prev_x, prev_y;
+ double cur_area= 0;
+ double cur_cx= 0;
+ double cur_cy= 0;
+
+ if (no_data(data, 4))
+ return 1;
+ org_n_points= n_points= uint4korr(data);
+ data+= 4;
+ if (n_points == 0 || not_enough_points(data, n_points))
+ return 1;
+ get_point(&prev_x, &prev_y, data);
+ data+= POINT_DATA_SIZE;
+
+ while (--n_points) // One point is already read
+ {
+ double tmp_x, tmp_y;
+ get_point(&tmp_x, &tmp_y, data);
+ data+= POINT_DATA_SIZE;
+ cur_area+= (prev_x + tmp_x) * (prev_y - tmp_y);
+ cur_cx+= tmp_x;
+ cur_cy+= tmp_y;
+ prev_x= tmp_x;
+ prev_y= tmp_y;
+ }
+ cur_area= fabs(cur_area) / 2;
+ cur_cx= cur_cx / (org_n_points - 1);
+ cur_cy= cur_cy / (org_n_points - 1);
+
+ if (!first_loop)
+ {
+ double d_area= fabs(res_area - cur_area);
+ res_cx= (res_area * res_cx - cur_area * cur_cx) / d_area;
+ res_cy= (res_area * res_cy - cur_area * cur_cy) / d_area;
+ }
+ else
+ {
+ first_loop= 0;
+ res_area= cur_area;
+ res_cx= cur_cx;
+ res_cy= cur_cy;
+ }
+ }
+
+ *x= res_cx;
+ *y= res_cy;
+ return 0;
+}
+
+
+int Gis_polygon::centroid(String *result) const
+{
+ double x, y;
+ if (centroid_xy(&x, &y))
+ return 1;
+ return create_point(result, x, y);
+}
+
+
+int Gis_polygon::store_shapes(Gcalc_shape_transporter *trn) const
+{
+ uint32 n_linear_rings;
+ const char *data= m_data;
+ double first_x, first_y;
+ double prev_x, prev_y;
+ int was_equal_first= 0;
+
+ if (trn->start_poly())
+ return 1;
+
+ if (no_data(data, 4))
+ return 1;
+ n_linear_rings= uint4korr(data);
+ data+= 4;
+
+ while (n_linear_rings--)
+ {
+ uint32 n_points;
+
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data+= 4;
+ if (!n_points || not_enough_points(data, n_points))
+ return 1;
+
+ trn->start_ring();
+ get_point(&first_x, &first_y, data);
+ data+= POINT_DATA_SIZE;
+
+ prev_x= first_x;
+ prev_y= first_y;
+ if (trn->add_point(first_x, first_y))
+ return 1;
+
+ if (--n_points == 0)
+ goto single_point_ring;
+
+ while (--n_points)
+ {
+ double x, y;
+ get_point(&x, &y, data);
+ data+= POINT_DATA_SIZE;
+ if (x == prev_x && y == prev_y)
+ continue;
+ prev_x= x;
+ prev_y= y;
+ if (was_equal_first)
+ {
+ if (trn->add_point(first_x, first_y))
+ return 1;
+ was_equal_first= 0;
+ }
+ if (x == first_x && y == first_y)
+ {
+ was_equal_first= 1;
+ continue;
+ }
+ if (trn->add_point(x, y))
+ return 1;
+ }
+ data+= POINT_DATA_SIZE;
+
+single_point_ring:
+ trn->complete_ring();
+ }
+
+ trn->complete_poly();
+ return 0;
+}
+
+
+const Geometry::Class_info *Gis_polygon::get_class_info() const
+{
+ return &polygon_class;
+}
+
+
+/***************************** MultiPoint *******************************/
+
+uint32 Gis_multi_point::get_data_size() const
+{
+ uint32 n_points;
+
+ if (no_data(m_data, 4) ||
+ not_enough_points(m_data+4, (n_points= uint4korr(m_data)),
+ WKB_HEADER_SIZE))
+ return GET_SIZE_ERROR;
+ return 4 + n_points * (POINT_DATA_SIZE + WKB_HEADER_SIZE);
+}
+
+
+bool Gis_multi_point::init_from_wkt(Gis_read_stream *trs, String *wkb)
+{
+ uint32 n_points= 0;
+ uint32 np_pos= wkb->length();
+ Gis_point p;
+
+ if (wkb->reserve(4, 512))
+ return 1;
+ wkb->length(wkb->length()+4); // Reserve space for points
+
+ for (;;)
+ {
+ if (wkb->reserve(1 + 4, 512))
+ return 1;
+ wkb->q_append((char) wkb_ndr);
+ wkb->q_append((uint32) wkb_point);
+ if (p.init_from_wkt(trs, wkb))
+ return 1;
+ n_points++;
+ if (trs->skip_char(',')) // Didn't find ','
+ break;
+ }
+ wkb->write_at_position(np_pos, n_points); // Store number of found points
+ return 0;
+}
+
+
+uint Gis_multi_point::init_from_opresult(String *bin,
+ const char *opres, uint res_len)
+{
+ uint bin_size, n_points;
+ Gis_point p;
+ const char *opres_end;
+
+ n_points= res_len/(4+8*2);
+ bin_size= n_points * (WKB_HEADER_SIZE + POINT_DATA_SIZE) + 4;
+
+ if (bin->reserve(bin_size, 512))
+ return 0;
+
+ bin->q_append(n_points);
+ opres_end= opres + res_len;
+ for (; opres < opres_end; opres+= (4 + 8*2))
+ {
+ bin->q_append((char)wkb_ndr);
+ bin->q_append((uint32)wkb_point);
+ if (!p.init_from_wkb(opres + 4, POINT_DATA_SIZE, wkb_ndr, bin))
+ return 0;
+ }
+ return res_len;
+}
+
+
+uint Gis_multi_point::init_from_wkb(const char *wkb, uint len, wkbByteOrder bo,
+ String *res)
+{
+ uint32 n_points;
+ uint proper_size;
+ Gis_point p;
+ const char *wkb_end;
+
+ if (len < 4 ||
+ (n_points= wkb_get_uint(wkb, bo)) > max_n_points)
+ return 0;
+ proper_size= 4 + n_points * (WKB_HEADER_SIZE + POINT_DATA_SIZE);
+
+ if (len < proper_size || res->reserve(proper_size))
+ return 0;
+
+ res->q_append(n_points);
+ wkb_end= wkb + proper_size;
+ for (wkb+=4; wkb < wkb_end; wkb+= (WKB_HEADER_SIZE + POINT_DATA_SIZE))
+ {
+ res->q_append((char)wkb_ndr);
+ res->q_append((uint32)wkb_point);
+ if (!p.init_from_wkb(wkb + WKB_HEADER_SIZE,
+ POINT_DATA_SIZE, (wkbByteOrder) wkb[0], res))
+ return 0;
+ }
+ return proper_size;
+}
+
+
+bool Gis_multi_point::get_data_as_wkt(String *txt, const char **end) const
+{
+ uint32 n_points;
+ if (no_data(m_data, 4))
+ return 1;
+
+ n_points= uint4korr(m_data);
+ if (n_points > max_n_points ||
+ not_enough_points(m_data+4, n_points, WKB_HEADER_SIZE) ||
+ txt->reserve(((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
+ return 1;
+ *end= append_points(txt, n_points, m_data+4, WKB_HEADER_SIZE);
+ txt->length(txt->length()-1); // Remove end ','
+ return 0;
+}
+
+
+bool Gis_multi_point::get_mbr(MBR *mbr, const char **end) const
+{
+ return (*end= get_mbr_for_points(mbr, m_data, WKB_HEADER_SIZE)) == 0;
+}
+
+
+int Gis_multi_point::num_geometries(uint32 *num) const
+{
+ *num= uint4korr(m_data);
+ return 0;
+}
+
+
+int Gis_multi_point::geometry_n(uint32 num, String *result) const
+{
+ const char *data= m_data;
+ uint32 n_points;
+
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data+= 4+ (num - 1) * (WKB_HEADER_SIZE + POINT_DATA_SIZE);
+
+ if (num > n_points || num < 1 ||
+ no_data(data, WKB_HEADER_SIZE + POINT_DATA_SIZE) ||
+ result->reserve(WKB_HEADER_SIZE + POINT_DATA_SIZE))
+ return 1;
+
+ result->q_append(data, WKB_HEADER_SIZE + POINT_DATA_SIZE);
+ return 0;
+}
+
+
+int Gis_multi_point::store_shapes(Gcalc_shape_transporter *trn) const
+{
+ uint32 n_points;
+ Gis_point pt;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ data+= 4;
+
+ if (trn->start_collection(n_points))
+ return 1;
+
+ while (n_points--)
+ {
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ data+= WKB_HEADER_SIZE;
+ pt.set_data_ptr(data, (uint32) (m_data_end - data));
+ if (pt.store_shapes(trn))
+ return 1;
+ data+= pt.get_data_size();
+ }
+ return 0;
+}
+
+
+const Geometry::Class_info *Gis_multi_point::get_class_info() const
+{
+ return &multipoint_class;
+}
+
+
+/***************************** MultiLineString *******************************/
+
+uint32 Gis_multi_line_string::get_data_size() const
+{
+ uint32 n_line_strings;
+ uint32 n_points;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return GET_SIZE_ERROR;
+ n_line_strings= uint4korr(data);
+ data+= 4;
+
+ while (n_line_strings--)
+ {
+ if (no_data(data, WKB_HEADER_SIZE + 4) ||
+ not_enough_points(data + WKB_HEADER_SIZE+4,
+ (n_points= uint4korr(data + WKB_HEADER_SIZE))))
+ return GET_SIZE_ERROR;
+ data+= (WKB_HEADER_SIZE + 4 + n_points*POINT_DATA_SIZE);
+ }
+ if (no_data(data, 0))
+ return GET_SIZE_ERROR;
+ return (uint32) (data - m_data);
+}
+
+
+bool Gis_multi_line_string::init_from_wkt(Gis_read_stream *trs, String *wkb)
+{
+ uint32 n_line_strings= 0;
+ uint32 ls_pos= wkb->length();
+
+ if (wkb->reserve(4, 512))
+ return 1;
+ wkb->length(wkb->length()+4); // Reserve space for points
+
+ for (;;)
+ {
+ Gis_line_string ls;
+
+ if (wkb->reserve(1 + 4, 512))
+ return 1;
+ wkb->q_append((char) wkb_ndr); wkb->q_append((uint32) wkb_linestring);
+
+ if (trs->check_next_symbol('(') ||
+ ls.init_from_wkt(trs, wkb) ||
+ trs->check_next_symbol(')'))
+ return 1;
+ n_line_strings++;
+ if (trs->skip_char(',')) // Didn't find ','
+ break;
+ }
+ wkb->write_at_position(ls_pos, n_line_strings);
+ return 0;
+}
+
+
+uint Gis_multi_line_string::init_from_opresult(String *bin,
+ const char *opres, uint res_len)
+{
+ const char *opres_orig= opres;
+ int ns_pos= bin->length();
+ uint n_linestring= 0;
+
+ if (bin->reserve(4, 512))
+ return 0;
+ bin->q_append(n_linestring);
+
+ while (res_len)
+ {
+ Gis_line_string ls;
+ int ls_len;
+
+ if (bin->reserve(WKB_HEADER_SIZE, 512))
+ return 0;
+
+ bin->q_append((char) wkb_ndr);
+ bin->q_append((uint32) wkb_linestring);
+
+ if (!(ls_len= ls.init_from_opresult(bin, opres, res_len)))
+ return 0;
+ opres+= ls_len;
+ res_len-= ls_len;
+ n_linestring++;
+ }
+ bin->write_at_position(ns_pos, n_linestring);
+ return (uint) (opres - opres_orig);
+}
+
+
+uint Gis_multi_line_string::init_from_wkb(const char *wkb, uint len,
+ wkbByteOrder bo, String *res)
+{
+ uint32 n_line_strings;
+ const char *wkb_orig= wkb;
+
+ if (len < 4 ||
+ (n_line_strings= wkb_get_uint(wkb, bo))< 1)
+ return 0;
+
+ if (res->reserve(4, 512))
+ return 0;
+ res->q_append(n_line_strings);
+
+ wkb+= 4;
+ while (n_line_strings--)
+ {
+ Gis_line_string ls;
+ int ls_len;
+
+ if ((len < WKB_HEADER_SIZE) ||
+ res->reserve(WKB_HEADER_SIZE, 512))
+ return 0;
+
+ res->q_append((char) wkb_ndr);
+ res->q_append((uint32) wkb_linestring);
+
+ if (!(ls_len= ls.init_from_wkb(wkb + WKB_HEADER_SIZE, len,
+ (wkbByteOrder) wkb[0], res)))
+ return 0;
+ ls_len+= WKB_HEADER_SIZE;;
+ wkb+= ls_len;
+ len-= ls_len;
+ }
+ return (uint) (wkb - wkb_orig);
+}
+
+
+bool Gis_multi_line_string::get_data_as_wkt(String *txt,
+ const char **end) const
+{
+ uint32 n_line_strings;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_line_strings= uint4korr(data);
+ data+= 4;
+
+ while (n_line_strings--)
+ {
+ uint32 n_points;
+ if (no_data(data, (WKB_HEADER_SIZE + 4)))
+ return 1;
+ n_points= uint4korr(data + WKB_HEADER_SIZE);
+ data+= WKB_HEADER_SIZE + 4;
+ if (not_enough_points(data, n_points) ||
+ txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
+ return 1;
+ txt->qs_append('(');
+ data= append_points(txt, n_points, data, 0);
+ (*txt) [txt->length() - 1]= ')';
+ txt->qs_append(',');
+ }
+ txt->length(txt->length() - 1);
+ *end= data;
+ return 0;
+}
+
+
+bool Gis_multi_line_string::get_mbr(MBR *mbr, const char **end) const
+{
+ uint32 n_line_strings;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_line_strings= uint4korr(data);
+ data+= 4;
+
+ while (n_line_strings--)
+ {
+ data+= WKB_HEADER_SIZE;
+ if (!(data= get_mbr_for_points(mbr, data, 0)))
+ return 1;
+ }
+ *end= data;
+ return 0;
+}
+
+
+int Gis_multi_line_string::num_geometries(uint32 *num) const
+{
+ *num= uint4korr(m_data);
+ return 0;
+}
+
+
+int Gis_multi_line_string::geometry_n(uint32 num, String *result) const
+{
+ uint32 n_line_strings, n_points, length;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_line_strings= uint4korr(data);
+ data+= 4;
+
+ if ((num > n_line_strings) || (num < 1))
+ return 1;
+
+ for (;;)
+ {
+ if (no_data(data, WKB_HEADER_SIZE + 4))
+ return 1;
+ n_points= uint4korr(data + WKB_HEADER_SIZE);
+ length= WKB_HEADER_SIZE + 4+ POINT_DATA_SIZE * n_points;
+ if (not_enough_points(data+WKB_HEADER_SIZE+4, n_points))
+ return 1;
+ if (!--num)
+ break;
+ data+= length;
+ }
+ return result->append(data, length, (uint32) 0);
+}
+
+
+int Gis_multi_line_string::geom_length(double *len, const char **end) const
+{
+ uint32 n_line_strings;
+ const char *data= m_data;
+ const char *line_end;
+
+ if (no_data(data, 4))
+ return 1;
+ n_line_strings= uint4korr(data);
+ data+= 4;
+
+ *len=0;
+ while (n_line_strings--)
+ {
+ double ls_len;
+ Gis_line_string ls;
+ data+= WKB_HEADER_SIZE;
+ ls.set_data_ptr(data, (uint32) (m_data_end - data));
+ if (ls.geom_length(&ls_len, &line_end))
+ return 1;
+ *len+= ls_len;
+ /*
+ We know here that ls was ok, so we can call the trivial function
+ Gis_line_string::get_data_size without error checking
+ */
+ data+= ls.get_data_size();
+ }
+ *end= data;
+ return 0;
+}
+
+
+int Gis_multi_line_string::is_closed(int *closed) const
+{
+ uint32 n_line_strings;
+ const char *data= m_data;
+
+ if (no_data(data, 4 + WKB_HEADER_SIZE))
+ return 1;
+ n_line_strings= uint4korr(data);
+ data+= 4 + WKB_HEADER_SIZE;
+
+ while (n_line_strings--)
+ {
+ Gis_line_string ls;
+ if (no_data(data, 0))
+ return 1;
+ ls.set_data_ptr(data, (uint32) (m_data_end - data));
+ if (ls.is_closed(closed))
+ return 1;
+ if (!*closed)
+ return 0;
+ /*
+ We know here that ls was ok, so we can call the trivial function
+ Gis_line_string::get_data_size without error checking
+ */
+ data+= ls.get_data_size() + WKB_HEADER_SIZE;
+ }
+ return 0;
+}
+
+
+int Gis_multi_line_string::store_shapes(Gcalc_shape_transporter *trn) const
+{
+ uint32 n_lines;
+ Gis_line_string ls;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_lines= uint4korr(data);
+ data+= 4;
+
+ if (trn->start_collection(n_lines))
+ return 1;
+
+ while (n_lines--)
+ {
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ data+= WKB_HEADER_SIZE;
+ ls.set_data_ptr(data, (uint32) (m_data_end - data));
+ if (ls.store_shapes(trn))
+ return 1;
+ data+= ls.get_data_size();
+ }
+ return 0;
+}
+
+
+const Geometry::Class_info *Gis_multi_line_string::get_class_info() const
+{
+ return &multilinestring_class;
+}
+
+
+/***************************** MultiPolygon *******************************/
+
+uint32 Gis_multi_polygon::get_data_size() const
+{
+ uint32 n_polygons;
+ uint32 n_points;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return GET_SIZE_ERROR;
+ n_polygons= uint4korr(data);
+ data+= 4;
+
+ while (n_polygons--)
+ {
+ uint32 n_linear_rings;
+ if (no_data(data, 4 + WKB_HEADER_SIZE))
+ return GET_SIZE_ERROR;
+
+ n_linear_rings= uint4korr(data + WKB_HEADER_SIZE);
+ data+= 4 + WKB_HEADER_SIZE;
+
+ while (n_linear_rings--)
+ {
+ if (no_data(data, 4) ||
+ not_enough_points(data+4, (n_points= uint4korr(data))))
+ return GET_SIZE_ERROR;
+ data+= 4 + n_points * POINT_DATA_SIZE;
+ }
+ }
+ if (no_data(data, 0))
+ return GET_SIZE_ERROR;
+ return (uint32) (data - m_data);
+}
+
+
+bool Gis_multi_polygon::init_from_wkt(Gis_read_stream *trs, String *wkb)
+{
+ uint32 n_polygons= 0;
+ int np_pos= wkb->length();
+ Gis_polygon p;
+
+ if (wkb->reserve(4, 512))
+ return 1;
+ wkb->length(wkb->length()+4); // Reserve space for points
+
+ for (;;)
+ {
+ if (wkb->reserve(1 + 4, 512))
+ return 1;
+ wkb->q_append((char) wkb_ndr);
+ wkb->q_append((uint32) wkb_polygon);
+
+ if (trs->check_next_symbol('(') ||
+ p.init_from_wkt(trs, wkb) ||
+ trs->check_next_symbol(')'))
+ return 1;
+ n_polygons++;
+ if (trs->skip_char(',')) // Didn't find ','
+ break;
+ }
+ wkb->write_at_position(np_pos, n_polygons);
+ return 0;
+}
+
+
+uint Gis_multi_polygon::init_from_wkb(const char *wkb, uint len,
+ wkbByteOrder bo, String *res)
+{
+ uint32 n_poly;
+ const char *wkb_orig= wkb;
+
+ if (len < 4)
+ return 0;
+ n_poly= wkb_get_uint(wkb, bo);
+
+ if (res->reserve(4, 512))
+ return 0;
+ res->q_append(n_poly);
+
+ wkb+=4;
+ while (n_poly--)
+ {
+ Gis_polygon p;
+ int p_len;
+
+ if (len < WKB_HEADER_SIZE ||
+ res->reserve(WKB_HEADER_SIZE, 512))
+ return 0;
+ res->q_append((char) wkb_ndr);
+ res->q_append((uint32) wkb_polygon);
+
+ if (!(p_len= p.init_from_wkb(wkb + WKB_HEADER_SIZE, len,
+ (wkbByteOrder) wkb[0], res)))
+ return 0;
+ p_len+= WKB_HEADER_SIZE;
+ wkb+= p_len;
+ len-= p_len;
+ }
+ return (uint) (wkb - wkb_orig);
+}
+
+
+uint Gis_multi_polygon::init_from_opresult(String *bin,
+ const char *opres, uint res_len)
+{
+ Gis_polygon p;
+ const char *opres_orig= opres;
+ uint p_len;
+ uint32 n_poly= 0;
+ uint32 np_pos= bin->length();
+
+ if (bin->reserve(4, 512))
+ return 0;
+
+ bin->q_append(n_poly);
+ while (res_len)
+ {
+ if (bin->reserve(1 + 4, 512))
+ return 0;
+ bin->q_append((char)wkb_ndr);
+ bin->q_append((uint32)wkb_polygon);
+ if (!(p_len= p.init_from_opresult(bin, opres, res_len)))
+ return 0;
+ opres+= p_len;
+ res_len-= p_len;
+ n_poly++;
+ }
+ bin->write_at_position(np_pos, n_poly);
+ return opres - opres_orig;
+}
+
+
+bool Gis_multi_polygon::get_data_as_wkt(String *txt, const char **end) const
+{
+ uint32 n_polygons;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_polygons= uint4korr(data);
+ data+= 4;
+
+ while (n_polygons--)
+ {
+ uint32 n_linear_rings;
+ if (no_data(data, 4 + WKB_HEADER_SIZE) ||
+ txt->reserve(1, 512))
+ return 1;
+ n_linear_rings= uint4korr(data+WKB_HEADER_SIZE);
+ data+= 4 + WKB_HEADER_SIZE;
+ txt->q_append('(');
+
+ while (n_linear_rings--)
+ {
+ if (no_data(data, 4))
+ return 1;
+ uint32 n_points= uint4korr(data);
+ data+= 4;
+ if (not_enough_points(data, n_points) ||
+ txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points,
+ 512))
+ return 1;
+ txt->qs_append('(');
+ data= append_points(txt, n_points, data, 0);
+ (*txt) [txt->length() - 1]= ')';
+ txt->qs_append(',');
+ }
+ (*txt) [txt->length() - 1]= ')';
+ txt->qs_append(',');
+ }
+ txt->length(txt->length() - 1);
+ *end= data;
+ return 0;
+}
+
+
+bool Gis_multi_polygon::get_mbr(MBR *mbr, const char **end) const
+{
+ uint32 n_polygons;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_polygons= uint4korr(data);
+ data+= 4;
+
+ while (n_polygons--)
+ {
+ uint32 n_linear_rings;
+ if (no_data(data, 4+WKB_HEADER_SIZE))
+ return 1;
+ n_linear_rings= uint4korr(data + WKB_HEADER_SIZE);
+ data+= WKB_HEADER_SIZE + 4;
+
+ while (n_linear_rings--)
+ {
+ if (!(data= get_mbr_for_points(mbr, data, 0)))
+ return 1;
+ }
+ }
+ *end= data;
+ return 0;
+}
+
+
+int Gis_multi_polygon::num_geometries(uint32 *num) const
+{
+ *num= uint4korr(m_data);
+ return 0;
+}
+
+
+int Gis_multi_polygon::geometry_n(uint32 num, String *result) const
+{
+ uint32 n_polygons;
+ const char *data= m_data, *start_of_polygon;
+
+ if (no_data(data, 4))
+ return 1;
+ n_polygons= uint4korr(data);
+ data+= 4;
+
+ if (num > n_polygons || num < 1)
+ return -1;
+
+ do
+ {
+ uint32 n_linear_rings;
+ start_of_polygon= data;
+
+ if (no_data(data, WKB_HEADER_SIZE + 4))
+ return 1;
+ n_linear_rings= uint4korr(data + WKB_HEADER_SIZE);
+ data+= WKB_HEADER_SIZE + 4;
+
+ while (n_linear_rings--)
+ {
+ uint32 n_points;
+ if (no_data(data, 4))
+ return 1;
+ n_points= uint4korr(data);
+ if (not_enough_points(data + 4, n_points))
+ return 1;
+ data+= 4 + POINT_DATA_SIZE * n_points;
+ }
+ } while (--num);
+ if (no_data(data, 0)) // We must check last segment
+ return 1;
+ return result->append(start_of_polygon, (uint32) (data - start_of_polygon),
+ (uint32) 0);
+}
+
+
+int Gis_multi_polygon::area(double *ar, const char **end_of_data) const
+{
+ uint32 n_polygons;
+ const char *data= m_data;
+ double result= 0;
+
+ if (no_data(data, 4))
+ return 1;
+ n_polygons= uint4korr(data);
+ data+= 4;
+
+ while (n_polygons--)
+ {
+ double p_area;
+ Gis_polygon p;
+
+ data+= WKB_HEADER_SIZE;
+ p.set_data_ptr(data, (uint32) (m_data_end - data));
+ if (p.area(&p_area, &data))
+ return 1;
+ result+= p_area;
+ }
+ *ar= result;
+ *end_of_data= data;
+ return 0;
+}
+
+
+int Gis_multi_polygon::centroid(String *result) const
+{
+ uint32 n_polygons;
+ Gis_polygon p;
+ double res_area= 0.0, res_cx= 0.0, res_cy= 0.0;
+ double cur_area, cur_cx, cur_cy;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_polygons= uint4korr(data);
+ data+= 4;
+
+ while (n_polygons--)
+ {
+ data+= WKB_HEADER_SIZE;
+ p.set_data_ptr(data, (uint32) (m_data_end - data));
+ if (p.area(&cur_area, &data) ||
+ p.centroid_xy(&cur_cx, &cur_cy))
+ return 1;
+
+ res_area+= cur_area;
+ res_cx+= cur_area * cur_cx;
+ res_cy+= cur_area * cur_cy;
+ }
+
+ res_cx/= res_area;
+ res_cy/= res_area;
+
+ return create_point(result, res_cx, res_cy);
+}
+
+
+int Gis_multi_polygon::store_shapes(Gcalc_shape_transporter *trn) const
+{
+ uint32 n_polygons;
+ Gis_polygon p;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_polygons= uint4korr(data);
+ data+= 4;
+
+ if (trn->start_collection(n_polygons))
+ return 1;
+
+ while (n_polygons--)
+ {
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ data+= WKB_HEADER_SIZE;
+ p.set_data_ptr(data, (uint32) (m_data_end - data));
+ if (p.store_shapes(trn))
+ return 1;
+ data+= p.get_data_size();
+ }
+ return 0;
+}
+
+
+const Geometry::Class_info *Gis_multi_polygon::get_class_info() const
+{
+ return &multipolygon_class;
+}
+
+
+/************************* GeometryCollection ****************************/
+
+uint32 Gis_geometry_collection::get_data_size() const
+{
+ uint32 n_objects;
+ const char *data= m_data;
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ if (no_data(data, 4))
+ return GET_SIZE_ERROR;
+ n_objects= uint4korr(data);
+ data+= 4;
+
+ while (n_objects--)
+ {
+ uint32 wkb_type,object_size;
+
+ if (no_data(data, WKB_HEADER_SIZE))
+ return GET_SIZE_ERROR;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return GET_SIZE_ERROR;
+ geom->set_data_ptr(data, (uint) (m_data_end - data));
+ if ((object_size= geom->get_data_size()) == GET_SIZE_ERROR)
+ return GET_SIZE_ERROR;
+ data+= object_size;
+ }
+ return (uint32) (data - m_data);
+}
+
+
+bool Gis_geometry_collection::init_from_wkt(Gis_read_stream *trs, String *wkb)
+{
+ uint32 n_objects= 0;
+ uint32 no_pos= wkb->length();
+ Geometry_buffer buffer;
+ Geometry *g;
+ char next_sym;
+
+ if (wkb->reserve(4, 512))
+ return 1;
+ wkb->length(wkb->length()+4); // Reserve space for points
+
+ if (!(next_sym= trs->next_symbol()))
+ return 1;
+
+ if (next_sym != ')')
+ {
+ LEX_STRING next_word;
+ if (trs->lookup_next_word(&next_word))
+ return 1;
+
+ if (next_word.length != 5 ||
+ (my_strnncoll(&my_charset_latin1,
+ (const uchar*) "empty", 5,
+ (const uchar*) next_word.str, 5) != 0))
+ {
+ for (;;)
+ {
+ if (!(g= create_from_wkt(&buffer, trs, wkb)))
+ return 1;
+
+ if (g->get_class_info()->m_type_id == wkb_geometrycollection)
+ {
+ trs->set_error_msg("Unexpected GEOMETRYCOLLECTION");
+ return 1;
+ }
+ n_objects++;
+ if (trs->skip_char(',')) // Didn't find ','
+ break;
+ }
+ }
+ }
+
+ wkb->write_at_position(no_pos, n_objects);
+ return 0;
+}
+
+
+uint Gis_geometry_collection::init_from_opresult(String *bin,
+ const char *opres,
+ uint res_len)
+{
+ const char *opres_orig= opres;
+ Geometry_buffer buffer;
+ Geometry *geom;
+ int g_len;
+ uint32 wkb_type;
+ int no_pos= bin->length();
+ uint32 n_objects= 0;
+
+ if (bin->reserve(4, 512))
+ return 0;
+ bin->q_append(n_objects);
+
+ while (res_len)
+ {
+ switch ((Gcalc_function::shape_type) uint4korr(opres))
+ {
+ case Gcalc_function::shape_point: wkb_type= wkb_point; break;
+ case Gcalc_function::shape_line: wkb_type= wkb_linestring; break;
+ case Gcalc_function::shape_polygon: wkb_type= wkb_polygon; break;
+ default: wkb_type= 0; DBUG_ASSERT(FALSE);
+ };
+
+ if (bin->reserve(WKB_HEADER_SIZE, 512))
+ return 0;
+
+ bin->q_append((char) wkb_ndr);
+ bin->q_append(wkb_type);
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)) ||
+ !(g_len= geom->init_from_opresult(bin, opres, res_len)))
+ return 0;
+ opres+= g_len;
+ res_len-= g_len;
+ n_objects++;
+ }
+ bin->write_at_position(no_pos, n_objects);
+ return (uint) (opres - opres_orig);
+}
+
+
+uint Gis_geometry_collection::init_from_wkb(const char *wkb, uint len,
+ wkbByteOrder bo, String *res)
+{
+ uint32 n_geom;
+ const char *wkb_orig= wkb;
+
+ if (len < 4)
+ return 0;
+ n_geom= wkb_get_uint(wkb, bo);
+
+ if (res->reserve(4, 512))
+ return 0;
+ res->q_append(n_geom);
+
+ wkb+= 4;
+ while (n_geom--)
+ {
+ Geometry_buffer buffer;
+ Geometry *geom;
+ int g_len;
+ uint32 wkb_type;
+
+ if (len < WKB_HEADER_SIZE ||
+ res->reserve(WKB_HEADER_SIZE, 512))
+ return 0;
+
+ res->q_append((char) wkb_ndr);
+ wkb_type= wkb_get_uint(wkb+1, (wkbByteOrder) wkb[0]);
+ res->q_append(wkb_type);
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)) ||
+ !(g_len= geom->init_from_wkb(wkb + WKB_HEADER_SIZE, len,
+ (wkbByteOrder) wkb[0], res)))
+ return 0;
+ g_len+= WKB_HEADER_SIZE;
+ wkb+= g_len;
+ len-= g_len;
+ }
+ return (uint) (wkb - wkb_orig);
+}
+
+
+bool Gis_geometry_collection::get_data_as_wkt(String *txt,
+ const char **end) const
+{
+ uint32 n_objects;
+ Geometry_buffer buffer;
+ Geometry *geom;
+ const char *data= m_data;
+
+ if (no_data(data, 4))
+ return 1;
+ n_objects= uint4korr(data);
+ data+= 4;
+
+ if (n_objects == 0)
+ {
+ txt->append(STRING_WITH_LEN(" EMPTY"), 512);
+ goto exit;
+ }
+
+ txt->qs_append('(');
+ while (n_objects--)
+ {
+ uint32 wkb_type;
+
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return 1;
+ geom->set_data_ptr(data, (uint) (m_data_end - data));
+ if (geom->as_wkt(txt, &data))
+ return 1;
+ if (n_objects && txt->append(STRING_WITH_LEN(","), 512))
+ return 1;
+ }
+ txt->qs_append(')');
+exit:
+ *end= data;
+ return 0;
+}
+
+
+bool Gis_geometry_collection::get_mbr(MBR *mbr, const char **end) const
+{
+ uint32 n_objects;
+ const char *data= m_data;
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ if (no_data(data, 4))
+ return 1;
+ n_objects= uint4korr(data);
+ data+= 4;
+ if (n_objects == 0)
+ goto exit;
+
+ while (n_objects--)
+ {
+ uint32 wkb_type;
+
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return 1;
+ geom->set_data_ptr(data, (uint32) (m_data_end - data));
+ if (geom->get_mbr(mbr, &data))
+ return 1;
+ }
+exit:
+ *end= data;
+ return 0;
+}
+
+
+int Gis_geometry_collection::area(double *ar, const char **end) const
+{
+ uint32 n_objects;
+ const char *data= m_data;
+ Geometry_buffer buffer;
+ Geometry *geom;
+ double result;
+
+ if (no_data(data, 4))
+ return 1;
+ n_objects= uint4korr(data);
+ data+= 4;
+
+ result= 0.0;
+ if (n_objects == 0)
+ goto exit;
+
+ while (n_objects--)
+ {
+ uint32 wkb_type;
+
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return 1;
+ geom->set_data_ptr(data, (uint32) (m_data_end - data));
+ if (geom->area(ar, &data))
+ return 1;
+ result+= *ar;
+ }
+exit:
+ *end= data;
+ *ar= result;
+ return 0;
+}
+
+
+int Gis_geometry_collection::geom_length(double *len, const char **end) const
+{
+ uint32 n_objects;
+ const char *data= m_data;
+ Geometry_buffer buffer;
+ Geometry *geom;
+ double result;
+
+ if (no_data(data, 4))
+ return 1;
+ n_objects= uint4korr(data);
+ data+= 4;
+ result= 0.0;
+
+ if (n_objects == 0)
+ goto exit;
+
+ while (n_objects--)
+ {
+ uint32 wkb_type;
+
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return 1;
+ geom->set_data_ptr(data, (uint32) (m_data_end - data));
+ if (geom->geom_length(len, &data))
+ return 1;
+ result+= *len;
+ }
+
+exit:
+ *end= data;
+ *len= result;
+ return 0;
+}
+
+
+int Gis_geometry_collection::num_geometries(uint32 *num) const
+{
+ if (no_data(m_data, 4))
+ return 1;
+ *num= uint4korr(m_data);
+ return 0;
+}
+
+
+int Gis_geometry_collection::geometry_n(uint32 num, String *result) const
+{
+ uint32 n_objects, wkb_type, length;
+ const char *data= m_data;
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ if (no_data(data, 4))
+ return 1;
+ n_objects= uint4korr(data);
+ data+= 4;
+ if (num > n_objects || num < 1)
+ return 1;
+
+ do
+ {
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return 1;
+ geom->set_data_ptr(data, (uint) (m_data_end - data));
+ if ((length= geom->get_data_size()) == GET_SIZE_ERROR)
+ return 1;
+ data+= length;
+ } while (--num);
+
+ /* Copy found object to result */
+ if (result->reserve(1 + 4 + length))
+ return 1;
+ result->q_append((char) wkb_ndr);
+ result->q_append((uint32) wkb_type);
+ result->q_append(data-length, length); // data-length = start_of_data
+ return 0;
+}
+
+
+/*
+ Return dimension for object
+
+ SYNOPSIS
+ dimension()
+ res_dim Result dimension
+ end End of object will be stored here. May be 0 for
+ simple objects!
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+bool Gis_geometry_collection::dimension(uint32 *res_dim, const char **end) const
+{
+ uint32 n_objects;
+ const char *data= m_data;
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ if (no_data(data, 4))
+ return 1;
+ n_objects= uint4korr(data);
+ data+= 4;
+
+ *res_dim= 0;
+ while (n_objects--)
+ {
+ uint32 wkb_type, length, dim;
+ const char *end_data;
+
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return 1;
+ geom->set_data_ptr(data, (uint32) (m_data_end - data));
+ if (geom->dimension(&dim, &end_data))
+ return 1;
+ set_if_bigger(*res_dim, dim);
+ if (end_data) // Complex object
+ data= end_data;
+ else if ((length= geom->get_data_size()) == GET_SIZE_ERROR)
+ return 1;
+ else
+ data+= length;
+ }
+ *end= data;
+ return 0;
+}
+
+
+int Gis_geometry_collection::store_shapes(Gcalc_shape_transporter *trn) const
+{
+ uint32 n_objects;
+ const char *data= m_data;
+ Geometry_buffer buffer;
+ Geometry *geom;
+
+ if (no_data(data, 4))
+ return 1;
+ n_objects= uint4korr(data);
+ data+= 4;
+
+ if (!n_objects)
+ {
+ trn->empty_shape();
+ return 0;
+ }
+
+ if (trn->start_collection(n_objects))
+ return 1;
+
+ while (n_objects--)
+ {
+ uint32 wkb_type;
+
+ if (no_data(data, WKB_HEADER_SIZE))
+ return 1;
+ wkb_type= uint4korr(data + 1);
+ data+= WKB_HEADER_SIZE;
+ if (!(geom= create_by_typeid(&buffer, wkb_type)))
+ return 1;
+ geom->set_data_ptr(data, (uint32) (m_data_end - data));
+ if (geom->store_shapes(trn))
+ return 1;
+
+ data+= geom->get_data_size();
+ }
+ return 0;
+}
+
+
+const Geometry::Class_info *Gis_geometry_collection::get_class_info() const
+{
+ return &geometrycollection_class;
+}
+
+#endif /*HAVE_SPATIAL*/
diff --git a/sql/spatial.h b/sql/spatial.h
new file mode 100644
index 00000000000..3a6055add06
--- /dev/null
+++ b/sql/spatial.h
@@ -0,0 +1,592 @@
+/*
+ Copyright (c) 2002, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab.
+
+ 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 */
+
+#ifndef _spatial_h
+#define _spatial_h
+
+#include "sql_string.h" /* String, LEX_STRING */
+#include <my_compiler.h>
+
+#ifdef HAVE_SPATIAL
+
+class Gis_read_stream;
+
+#include "gcalc_tools.h"
+
+const uint SRID_SIZE= 4;
+const uint SIZEOF_STORED_DOUBLE= 8;
+const uint POINT_DATA_SIZE= (SIZEOF_STORED_DOUBLE * 2);
+const uint WKB_HEADER_SIZE= 1+4;
+const uint32 GET_SIZE_ERROR= ((uint32) -1);
+
+struct st_point_2d
+{
+ double x;
+ double y;
+};
+
+struct st_linear_ring
+{
+ uint32 n_points;
+ st_point_2d points;
+};
+
+/***************************** MBR *******************************/
+
+
+/*
+ It's ok that a lot of the functions are inline as these are only used once
+ in MySQL
+*/
+
+struct MBR
+{
+ double xmin, ymin, xmax, ymax;
+
+ MBR()
+ {
+ xmin= ymin= DBL_MAX;
+ xmax= ymax= -DBL_MAX;
+ }
+
+ MBR(const double xmin_arg, const double ymin_arg,
+ const double xmax_arg, const double ymax_arg)
+ :xmin(xmin_arg), ymin(ymin_arg), xmax(xmax_arg), ymax(ymax_arg)
+ {}
+
+ MBR(const st_point_2d &min, const st_point_2d &max)
+ :xmin(min.x), ymin(min.y), xmax(max.x), ymax(max.y)
+ {}
+
+ inline void add_xy(double x, double y)
+ {
+ /* Not using "else" for proper one point MBR calculation */
+ if (x < xmin)
+ xmin= x;
+ if (x > xmax)
+ xmax= x;
+ if (y < ymin)
+ ymin= y;
+ if (y > ymax)
+ ymax= y;
+ }
+ void add_xy(const char *px, const char *py)
+ {
+ double x, y;
+ float8get(x, px);
+ float8get(y, py);
+ add_xy(x,y);
+ }
+ void add_mbr(const MBR *mbr)
+ {
+ if (mbr->xmin < xmin)
+ xmin= mbr->xmin;
+ if (mbr->xmax > xmax)
+ xmax= mbr->xmax;
+ if (mbr->ymin < ymin)
+ ymin= mbr->ymin;
+ if (mbr->ymax > ymax)
+ ymax= mbr->ymax;
+ }
+ void buffer(double d)
+ {
+ xmin-= d;
+ ymin-= d;
+ xmax+= d;
+ ymax+= d;
+ }
+
+ int equals(const MBR *mbr)
+ {
+ /* The following should be safe, even if we compare doubles */
+ return ((mbr->xmin == xmin) && (mbr->ymin == ymin) &&
+ (mbr->xmax == xmax) && (mbr->ymax == ymax));
+ }
+
+ int disjoint(const MBR *mbr)
+ {
+ /* The following should be safe, even if we compare doubles */
+ return ((mbr->xmin > xmax) || (mbr->ymin > ymax) ||
+ (mbr->xmax < xmin) || (mbr->ymax < ymin));
+ }
+
+ int intersects(const MBR *mbr)
+ {
+ return !disjoint(mbr);
+ }
+
+ int touches(const MBR *mbr)
+ {
+ /* The following should be safe, even if we compare doubles */
+ return ((mbr->xmin == xmax || mbr->xmax == xmin) &&
+ ((mbr->ymin >= ymin && mbr->ymin <= ymax) ||
+ (mbr->ymax >= ymin && mbr->ymax <= ymax))) ||
+ ((mbr->ymin == ymax || mbr->ymax == ymin) &&
+ ((mbr->xmin >= xmin && mbr->xmin <= xmax) ||
+ (mbr->xmax >= xmin && mbr->xmax <= xmax)));
+ }
+
+ int within(const MBR *mbr)
+ {
+ /* The following should be safe, even if we compare doubles */
+ return ((mbr->xmin <= xmin) && (mbr->ymin <= ymin) &&
+ (mbr->xmax >= xmax) && (mbr->ymax >= ymax));
+ }
+
+ int contains(const MBR *mbr)
+ {
+ /* The following should be safe, even if we compare doubles */
+ return ((mbr->xmin >= xmin) && (mbr->ymin >= ymin) &&
+ (mbr->xmax <= xmax) && (mbr->ymax <= ymax));
+ }
+
+ bool inner_point(double x, double y) const
+ {
+ /* The following should be safe, even if we compare doubles */
+ return (xmin<x) && (xmax>x) && (ymin<y) && (ymax>y);
+ }
+
+ /**
+ The dimension maps to an integer as:
+ - Polygon -> 2
+ - Horizontal or vertical line -> 1
+ - Point -> 0
+ - Invalid MBR -> -1
+ */
+ int dimension() const
+ {
+ int d= 0;
+
+ if (xmin > xmax)
+ return -1;
+ else if (xmin < xmax)
+ d++;
+
+ if (ymin > ymax)
+ return -1;
+ else if (ymin < ymax)
+ d++;
+
+ return d;
+ }
+
+ int overlaps(const MBR *mbr)
+ {
+ /*
+ overlaps() requires that some point inside *this is also inside
+ *mbr, and that both geometries and their intersection are of the
+ same dimension.
+ */
+ int d = dimension();
+
+ if (d != mbr->dimension() || d <= 0 || contains(mbr) || within(mbr))
+ return 0;
+
+ MBR intersection(MY_MAX(xmin, mbr->xmin), MY_MAX(ymin, mbr->ymin),
+ MY_MIN(xmax, mbr->xmax), MY_MIN(ymax, mbr->ymax));
+
+ return (d == intersection.dimension());
+ }
+
+ int valid() const
+ { return xmin <= xmax && ymin <= ymax; }
+};
+
+
+/***************************** Geometry *******************************/
+
+struct Geometry_buffer;
+
+class Geometry
+{
+public:
+ Geometry() {} /* Remove gcc warning */
+ virtual ~Geometry() {} /* Remove gcc warning */
+ static void *operator new(size_t size, void *buffer)
+ {
+ return buffer;
+ }
+
+ static void operator delete(void *ptr, void *buffer)
+ {}
+
+ static void operator delete(void *buffer)
+ {}
+
+ static String bad_geometry_data;
+
+ enum wkbType
+ {
+ wkb_point= 1,
+ wkb_linestring= 2,
+ wkb_polygon= 3,
+ wkb_multipoint= 4,
+ wkb_multilinestring= 5,
+ wkb_multipolygon= 6,
+ wkb_geometrycollection= 7,
+ wkb_last=7
+ };
+ enum wkbByteOrder
+ {
+ wkb_xdr= 0, /* Big Endian */
+ wkb_ndr= 1 /* Little Endian */
+ };
+
+ /** Callback which creates Geometry objects on top of a given placement. */
+ typedef Geometry *(*create_geom_t)(char *);
+
+ class Class_info
+ {
+ public:
+ LEX_STRING m_name;
+ int m_type_id;
+ create_geom_t m_create_func;
+ Class_info(const char *name, int type_id, create_geom_t create_func);
+ };
+
+ virtual const Class_info *get_class_info() const=0;
+ virtual uint32 get_data_size() const=0;
+ virtual bool init_from_wkt(Gis_read_stream *trs, String *wkb)=0;
+ /* returns the length of the wkb that was read */
+ virtual uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo,
+ String *res)=0;
+ virtual uint init_from_opresult(String *bin,
+ const char *opres, uint res_len)
+ { return init_from_wkb(opres + 4, UINT_MAX32, wkb_ndr, bin) + 4; }
+
+ virtual bool get_data_as_wkt(String *txt, const char **end) const=0;
+ virtual bool get_mbr(MBR *mbr, const char **end) const=0;
+ virtual bool dimension(uint32 *dim, const char **end) const=0;
+ virtual int get_x(double *x) const { return -1; }
+ virtual int get_y(double *y) const { return -1; }
+ virtual int geom_length(double *len, const char **end) const { return -1; }
+ virtual int area(double *ar, const char **end) const { return -1;}
+ virtual int is_closed(int *closed) const { return -1; }
+ virtual int num_interior_ring(uint32 *n_int_rings) const { return -1; }
+ virtual int num_points(uint32 *n_points) const { return -1; }
+ virtual int num_geometries(uint32 *num) const { return -1; }
+ virtual int start_point(String *point) const { return -1; }
+ virtual int end_point(String *point) const { return -1; }
+ virtual int exterior_ring(String *ring) const { return -1; }
+ virtual int centroid(String *point) const { return -1; }
+ virtual int point_n(uint32 num, String *result) const { return -1; }
+ virtual int interior_ring_n(uint32 num, String *result) const { return -1; }
+ virtual int geometry_n(uint32 num, String *result) const { return -1; }
+ virtual int store_shapes(Gcalc_shape_transporter *trn) const=0;
+
+public:
+ static Geometry *create_by_typeid(Geometry_buffer *buffer, int type_id);
+
+ static Geometry *construct(Geometry_buffer *buffer,
+ const char *data, uint32 data_len);
+ static Geometry *create_from_wkt(Geometry_buffer *buffer,
+ Gis_read_stream *trs, String *wkt,
+ bool init_stream=1);
+ static Geometry *create_from_wkb(Geometry_buffer *buffer,
+ const char *wkb, uint32 len, String *res);
+ static int create_from_opresult(Geometry_buffer *g_buf,
+ String *res, Gcalc_result_receiver &rr);
+ int as_wkt(String *wkt, const char **end);
+
+ inline void set_data_ptr(const char *data, uint32 data_len)
+ {
+ m_data= data;
+ m_data_end= data + data_len;
+ }
+
+ inline void shift_wkb_header()
+ {
+ m_data+= WKB_HEADER_SIZE;
+ }
+
+ bool envelope(String *result) const;
+ static Class_info *ci_collection[wkb_last+1];
+
+protected:
+ static Class_info *find_class(int type_id)
+ {
+ return ((type_id < wkb_point) || (type_id > wkb_last)) ?
+ NULL : ci_collection[type_id];
+ }
+ static Class_info *find_class(const char *name, uint32 len);
+ const char *append_points(String *txt, uint32 n_points,
+ const char *data, uint32 offset) const;
+ bool create_point(String *result, const char *data) const;
+ bool create_point(String *result, double x, double y) const;
+ const char *get_mbr_for_points(MBR *mbr, const char *data, uint offset)
+ const;
+
+ /**
+ Check if there're enough data remaining as requested
+
+ @arg cur_data pointer to the position in the binary form
+ @arg data_amount number of points expected
+ @return true if not enough data
+ */
+ inline bool no_data(const char *cur_data, size_t data_amount) const
+ {
+ return (cur_data + data_amount > m_data_end);
+ }
+
+ /**
+ Check if there're enough points remaining as requested
+
+ Need to perform the calculation in logical units, since multiplication
+ can overflow the size data type.
+
+ @arg data pointer to the begining of the points array
+ @arg expected_points number of points expected
+ @arg extra_point_space extra space for each point element in the array
+ @return true if there are not enough points
+ */
+ inline bool not_enough_points(const char *data, uint32 expected_points,
+ uint32 extra_point_space = 0) const
+ {
+ return (m_data_end < data ||
+ (expected_points > ((m_data_end - data) /
+ (POINT_DATA_SIZE + extra_point_space))));
+ }
+ const char *m_data;
+ const char *m_data_end;
+};
+
+
+/***************************** Point *******************************/
+
+class Gis_point: public Geometry
+{
+public:
+ Gis_point() {} /* Remove gcc warning */
+ virtual ~Gis_point() {} /* Remove gcc warning */
+ uint32 get_data_size() const;
+ bool init_from_wkt(Gis_read_stream *trs, String *wkb);
+ uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, String *res);
+ bool get_data_as_wkt(String *txt, const char **end) const;
+ bool get_mbr(MBR *mbr, const char **end) const;
+
+ int get_xy(double *x, double *y) const
+ {
+ const char *data= m_data;
+ if (no_data(data, SIZEOF_STORED_DOUBLE * 2))
+ return 1;
+ float8get(*x, data);
+ float8get(*y, data + SIZEOF_STORED_DOUBLE);
+ return 0;
+ }
+
+ int get_x(double *x) const
+ {
+ if (no_data(m_data, SIZEOF_STORED_DOUBLE))
+ return 1;
+ float8get(*x, m_data);
+ return 0;
+ }
+
+ int get_y(double *y) const
+ {
+ const char *data= m_data;
+ if (no_data(data, SIZEOF_STORED_DOUBLE * 2)) return 1;
+ float8get(*y, data + SIZEOF_STORED_DOUBLE);
+ return 0;
+ }
+
+ int geom_length(double *len, const char **end) const;
+ int area(double *ar, const char **end) const;
+ bool dimension(uint32 *dim, const char **end) const
+ {
+ *dim= 0;
+ *end= 0; /* No default end */
+ return 0;
+ }
+ int store_shapes(Gcalc_shape_transporter *trn) const;
+ const Class_info *get_class_info() const;
+};
+
+
+/***************************** LineString *******************************/
+
+class Gis_line_string: public Geometry
+{
+public:
+ Gis_line_string() {} /* Remove gcc warning */
+ virtual ~Gis_line_string() {} /* Remove gcc warning */
+ uint32 get_data_size() const;
+ bool init_from_wkt(Gis_read_stream *trs, String *wkb);
+ uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, String *res);
+ bool get_data_as_wkt(String *txt, const char **end) const;
+ bool get_mbr(MBR *mbr, const char **end) const;
+ int geom_length(double *len, const char **end) const;
+ int area(double *ar, const char **end) const;
+ int is_closed(int *closed) const;
+ int num_points(uint32 *n_points) const;
+ int start_point(String *point) const;
+ int end_point(String *point) const;
+ int point_n(uint32 n, String *result) const;
+ bool dimension(uint32 *dim, const char **end) const
+ {
+ *dim= 1;
+ *end= 0; /* No default end */
+ return 0;
+ }
+ int store_shapes(Gcalc_shape_transporter *trn) const;
+ const Class_info *get_class_info() const;
+};
+
+
+/***************************** Polygon *******************************/
+
+class Gis_polygon: public Geometry
+{
+public:
+ Gis_polygon() {} /* Remove gcc warning */
+ virtual ~Gis_polygon() {} /* Remove gcc warning */
+ uint32 get_data_size() const;
+ bool init_from_wkt(Gis_read_stream *trs, String *wkb);
+ uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, String *res);
+ uint init_from_opresult(String *bin, const char *opres, uint res_len);
+ bool get_data_as_wkt(String *txt, const char **end) const;
+ bool get_mbr(MBR *mbr, const char **end) const;
+ int area(double *ar, const char **end) const;
+ int exterior_ring(String *result) const;
+ int num_interior_ring(uint32 *n_int_rings) const;
+ int interior_ring_n(uint32 num, String *result) const;
+ int centroid_xy(double *x, double *y) const;
+ int centroid(String *result) const;
+ bool dimension(uint32 *dim, const char **end) const
+ {
+ *dim= 2;
+ *end= 0; /* No default end */
+ return 0;
+ }
+ int store_shapes(Gcalc_shape_transporter *trn) const;
+ const Class_info *get_class_info() const;
+};
+
+
+/***************************** MultiPoint *******************************/
+
+class Gis_multi_point: public Geometry
+{
+ // Maximum number of points in MultiPoint that can fit into String
+ static const uint32 max_n_points=
+ (uint32) (UINT_MAX32 - WKB_HEADER_SIZE - 4 /* n_points */) /
+ (WKB_HEADER_SIZE + POINT_DATA_SIZE);
+public:
+ Gis_multi_point() {} /* Remove gcc warning */
+ virtual ~Gis_multi_point() {} /* Remove gcc warning */
+ uint32 get_data_size() const;
+ bool init_from_wkt(Gis_read_stream *trs, String *wkb);
+ uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, String *res);
+ uint init_from_opresult(String *bin, const char *opres, uint res_len);
+ bool get_data_as_wkt(String *txt, const char **end) const;
+ bool get_mbr(MBR *mbr, const char **end) const;
+ int num_geometries(uint32 *num) const;
+ int geometry_n(uint32 num, String *result) const;
+ bool dimension(uint32 *dim, const char **end) const
+ {
+ *dim= 0;
+ *end= 0; /* No default end */
+ return 0;
+ }
+ int store_shapes(Gcalc_shape_transporter *trn) const;
+ const Class_info *get_class_info() const;
+};
+
+
+/***************************** MultiLineString *******************************/
+
+class Gis_multi_line_string: public Geometry
+{
+public:
+ Gis_multi_line_string() {} /* Remove gcc warning */
+ virtual ~Gis_multi_line_string() {} /* Remove gcc warning */
+ uint32 get_data_size() const;
+ bool init_from_wkt(Gis_read_stream *trs, String *wkb);
+ uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, String *res);
+ uint init_from_opresult(String *bin, const char *opres, uint res_len);
+ bool get_data_as_wkt(String *txt, const char **end) const;
+ bool get_mbr(MBR *mbr, const char **end) const;
+ int num_geometries(uint32 *num) const;
+ int geometry_n(uint32 num, String *result) const;
+ int geom_length(double *len, const char **end) const;
+ int is_closed(int *closed) const;
+ bool dimension(uint32 *dim, const char **end) const
+ {
+ *dim= 1;
+ *end= 0; /* No default end */
+ return 0;
+ }
+ int store_shapes(Gcalc_shape_transporter *trn) const;
+ const Class_info *get_class_info() const;
+};
+
+
+/***************************** MultiPolygon *******************************/
+
+class Gis_multi_polygon: public Geometry
+{
+public:
+ Gis_multi_polygon() {} /* Remove gcc warning */
+ virtual ~Gis_multi_polygon() {} /* Remove gcc warning */
+ uint32 get_data_size() const;
+ bool init_from_wkt(Gis_read_stream *trs, String *wkb);
+ uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, String *res);
+ bool get_data_as_wkt(String *txt, const char **end) const;
+ bool get_mbr(MBR *mbr, const char **end) const;
+ int num_geometries(uint32 *num) const;
+ int geometry_n(uint32 num, String *result) const;
+ int area(double *ar, const char **end) const;
+ int centroid(String *result) const;
+ bool dimension(uint32 *dim, const char **end) const
+ {
+ *dim= 2;
+ *end= 0; /* No default end */
+ return 0;
+ }
+ int store_shapes(Gcalc_shape_transporter *trn) const;
+ const Class_info *get_class_info() const;
+ uint init_from_opresult(String *bin, const char *opres, uint res_len);
+};
+
+
+/*********************** GeometryCollection *******************************/
+
+class Gis_geometry_collection: public Geometry
+{
+public:
+ Gis_geometry_collection() {} /* Remove gcc warning */
+ virtual ~Gis_geometry_collection() {} /* Remove gcc warning */
+ uint32 get_data_size() const;
+ bool init_from_wkt(Gis_read_stream *trs, String *wkb);
+ uint init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, String *res);
+ uint init_from_opresult(String *bin, const char *opres, uint res_len);
+ bool get_data_as_wkt(String *txt, const char **end) const;
+ bool get_mbr(MBR *mbr, const char **end) const;
+ int area(double *ar, const char **end) const;
+ int geom_length(double *len, const char **end) const;
+ int num_geometries(uint32 *num) const;
+ int geometry_n(uint32 num, String *result) const;
+ bool dimension(uint32 *dim, const char **end) const;
+ int store_shapes(Gcalc_shape_transporter *trn) const;
+ const Class_info *get_class_info() const;
+};
+
+struct Geometry_buffer : public
+ my_aligned_storage<sizeof(Gis_point), MY_ALIGNOF(Gis_point)> {};
+
+#endif /*HAVE_SPATIAL*/
+#endif
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc
new file mode 100644
index 00000000000..f46a923bddf
--- /dev/null
+++ b/sql/sql_acl.cc
@@ -0,0 +1,12462 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, SkySQL Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/*
+ The privileges are saved in the following tables:
+ mysql/user ; super user who are allowed to do almost anything
+ mysql/host ; host privileges. This is used if host is empty in mysql/db.
+ mysql/db ; database privileges / user
+
+ data in tables is sorted according to how many not-wild-cards there is
+ in the relevant fields. Empty strings comes last.
+*/
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "sql_acl.h" // MYSQL_DB_FIELD_COUNT, ACL_ACCESS
+#include "sql_base.h" // close_mysql_tables
+#include "key.h" // key_copy, key_cmp_if_same, key_restore
+#include "sql_show.h" // append_identifier
+#include "sql_table.h" // build_table_filename
+#include "hash_filo.h"
+#include "sql_parse.h" // check_access
+#include "sql_view.h" // VIEW_ANY_ACL
+#include "records.h" // READ_RECORD, read_record_info,
+ // init_read_record, end_read_record
+#include "rpl_filter.h" // rpl_filter
+#include "rpl_rli.h"
+#include <m_ctype.h>
+#include <stdarg.h>
+#include "sp_head.h"
+#include "sp.h"
+#include "transaction.h"
+#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
+#include <sql_common.h>
+#include <mysql/plugin_auth.h>
+#include "sql_connect.h"
+#include "hostname.h"
+#include "sql_db.h"
+#include "sql_array.h"
+#include "sql_hset.h"
+
+#include "sql_plugin_compat.h"
+
+bool mysql_user_table_is_in_short_password_format= false;
+
+static const
+TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = {
+ {
+ { C_STRING_WITH_LEN("Host") },
+ { C_STRING_WITH_LEN("char(60)") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("Db") },
+ { C_STRING_WITH_LEN("char(64)") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("User") },
+ { C_STRING_WITH_LEN("char(") },
+ {NULL, 0}
+ },
+ {
+ { C_STRING_WITH_LEN("Select_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Insert_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Update_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Delete_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Create_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Drop_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Grant_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("References_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Index_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Alter_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Create_tmp_table_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Lock_tables_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Create_view_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Show_view_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Create_routine_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Alter_routine_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Execute_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Event_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("Trigger_priv") },
+ { C_STRING_WITH_LEN("enum('N','Y')") },
+ { C_STRING_WITH_LEN("utf8") }
+ }
+};
+
+const TABLE_FIELD_DEF
+mysql_db_table_def= {MYSQL_DB_FIELD_COUNT, mysql_db_table_fields, 0, (uint*) 0 };
+
+static LEX_STRING native_password_plugin_name= {
+ C_STRING_WITH_LEN("mysql_native_password")
+};
+
+static LEX_STRING old_password_plugin_name= {
+ C_STRING_WITH_LEN("mysql_old_password")
+};
+
+/// @todo make it configurable
+LEX_STRING *default_auth_plugin_name= &native_password_plugin_name;
+
+/*
+ Wildcard host, matches any hostname
+*/
+LEX_STRING host_not_specified= { C_STRING_WITH_LEN("%") };
+
+/*
+ Constants, used in the SHOW GRANTS command.
+ Their actual string values are irrelevant, they're always compared
+ as pointers to these string constants.
+*/
+LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") };
+LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") };
+LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") };
+
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+static plugin_ref old_password_plugin;
+#endif
+static plugin_ref native_password_plugin;
+
+static char *safe_str(char *str)
+{ return str ? str : const_cast<char*>(""); }
+
+static const char *safe_str(const char *str)
+{ return str ? str : ""; }
+
+/* Classes */
+
+struct acl_host_and_ip
+{
+ char *hostname;
+ long ip, ip_mask; // Used with masked ip:s
+};
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+static bool compare_hostname(const acl_host_and_ip *, const char *, const char *);
+#else
+#define compare_hostname(X,Y,Z) 0
+#endif
+
+class ACL_ACCESS {
+public:
+ ulong sort;
+ ulong access;
+};
+
+/* ACL_HOST is used if no host is specified */
+
+class ACL_HOST :public ACL_ACCESS
+{
+public:
+ acl_host_and_ip host;
+ char *db;
+};
+
+class ACL_USER_BASE :public ACL_ACCESS
+{
+
+public:
+ static void *operator new(size_t size, MEM_ROOT *mem_root)
+ { return (void*) alloc_root(mem_root, size); }
+
+ uchar flags; // field used to store various state information
+ LEX_STRING user;
+ /* list to hold references to granted roles (ACL_ROLE instances) */
+ DYNAMIC_ARRAY role_grants;
+};
+
+class ACL_USER :public ACL_USER_BASE
+{
+public:
+ acl_host_and_ip host;
+ uint hostname_length;
+ USER_RESOURCES user_resource;
+ uint8 salt[SCRAMBLE_LENGTH + 1]; // scrambled password in binary form
+ uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1
+ enum SSL_type ssl_type;
+ const char *ssl_cipher, *x509_issuer, *x509_subject;
+ LEX_STRING plugin;
+ LEX_STRING auth_string;
+
+ ACL_USER *copy(MEM_ROOT *root)
+ {
+ ACL_USER *dst= (ACL_USER *) alloc_root(root, sizeof(ACL_USER));
+ if (!dst)
+ return 0;
+ *dst= *this;
+ dst->user.str= safe_strdup_root(root, user.str);
+ dst->user.length= user.length;
+ dst->ssl_cipher= safe_strdup_root(root, ssl_cipher);
+ dst->x509_issuer= safe_strdup_root(root, x509_issuer);
+ dst->x509_subject= safe_strdup_root(root, x509_subject);
+ if (plugin.str == native_password_plugin_name.str ||
+ plugin.str == old_password_plugin_name.str)
+ dst->plugin= plugin;
+ else
+ dst->plugin.str= strmake_root(root, plugin.str, plugin.length);
+ dst->auth_string.str= safe_strdup_root(root, auth_string.str);
+ dst->host.hostname= safe_strdup_root(root, host.hostname);
+ bzero(&dst->role_grants, sizeof(role_grants));
+ return dst;
+ }
+
+ int cmp(const char *user2, const char *host2)
+ {
+ CHARSET_INFO *cs= system_charset_info;
+ int res;
+ res= strcmp(safe_str(user.str), safe_str(user2));
+ if (!res)
+ res= my_strcasecmp(cs, host.hostname, host2);
+ return res;
+ }
+
+ bool eq(const char *user2, const char *host2) { return !cmp(user2, host2); }
+
+ bool wild_eq(const char *user2, const char *host2, const char *ip2)
+ {
+ if (strcmp(safe_str(user.str), safe_str(user2)))
+ return false;
+
+ return compare_hostname(&host, host2, ip2 ? ip2 : host2);
+ }
+};
+
+class ACL_ROLE :public ACL_USER_BASE
+{
+public:
+ /*
+ In case of granting a role to a role, the access bits are merged together
+ via a bit OR operation and placed in the ACL_USER::access field.
+
+ When rebuilding role_grants via the rebuild_role_grant function,
+ the ACL_USER::access field needs to be reset first. The field
+ initial_role_access holds initial grants, as granted directly to the role
+ */
+ ulong initial_role_access;
+ /*
+ In subgraph traversal, when we need to traverse only a part of the graph
+ (e.g. all direct and indirect grantees of a role X), the counter holds the
+ number of affected neighbour nodes.
+ See also propagate_role_grants()
+ */
+ uint counter;
+ DYNAMIC_ARRAY parent_grantee; // array of backlinks to elements granted
+
+ ACL_ROLE(ACL_USER * user, MEM_ROOT *mem);
+ ACL_ROLE(const char * rolename, ulong privileges, MEM_ROOT *mem);
+
+};
+
+class ACL_DB :public ACL_ACCESS
+{
+public:
+ acl_host_and_ip host;
+ char *user,*db;
+ ulong initial_access; /* access bits present in the table */
+};
+
+#ifndef DBUG_OFF
+/* status variables, only visible in SHOW STATUS after -#d,role_merge_stats */
+ulong role_global_merges= 0, role_db_merges= 0, role_table_merges= 0,
+ role_column_merges= 0, role_routine_merges= 0;
+#endif
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+static void update_hostname(acl_host_and_ip *host, const char *hostname);
+static ulong get_sort(uint count,...);
+static bool show_proxy_grants (THD *, const char *, const char *,
+ char *, size_t);
+static bool show_role_grants(THD *, const char *, const char *,
+ ACL_USER_BASE *, char *, size_t);
+static bool show_global_privileges(THD *, ACL_USER_BASE *,
+ bool, char *, size_t);
+static bool show_database_privileges(THD *, const char *, const char *,
+ char *, size_t);
+static bool show_table_and_column_privileges(THD *, const char *, const char *,
+ char *, size_t);
+static int show_routine_grants(THD *, const char *, const char *, HASH *,
+ const char *, int, char *, int);
+
+class ACL_PROXY_USER :public ACL_ACCESS
+{
+ acl_host_and_ip host;
+ const char *user;
+ acl_host_and_ip proxied_host;
+ const char *proxied_user;
+ bool with_grant;
+
+ typedef enum {
+ MYSQL_PROXIES_PRIV_HOST,
+ MYSQL_PROXIES_PRIV_USER,
+ MYSQL_PROXIES_PRIV_PROXIED_HOST,
+ MYSQL_PROXIES_PRIV_PROXIED_USER,
+ MYSQL_PROXIES_PRIV_WITH_GRANT,
+ MYSQL_PROXIES_PRIV_GRANTOR,
+ MYSQL_PROXIES_PRIV_TIMESTAMP } old_acl_proxy_users;
+public:
+ ACL_PROXY_USER () {};
+
+ void init(const char *host_arg, const char *user_arg,
+ const char *proxied_host_arg, const char *proxied_user_arg,
+ bool with_grant_arg)
+ {
+ user= (user_arg && *user_arg) ? user_arg : NULL;
+ update_hostname (&host, (host_arg && *host_arg) ? host_arg : NULL);
+ proxied_user= (proxied_user_arg && *proxied_user_arg) ?
+ proxied_user_arg : NULL;
+ update_hostname (&proxied_host,
+ (proxied_host_arg && *proxied_host_arg) ?
+ proxied_host_arg : NULL);
+ with_grant= with_grant_arg;
+ sort= get_sort(4, host.hostname, user, proxied_host.hostname, proxied_user);
+ }
+
+ void init(MEM_ROOT *mem, const char *host_arg, const char *user_arg,
+ const char *proxied_host_arg, const char *proxied_user_arg,
+ bool with_grant_arg)
+ {
+ init ((host_arg && *host_arg) ? strdup_root (mem, host_arg) : NULL,
+ (user_arg && *user_arg) ? strdup_root (mem, user_arg) : NULL,
+ (proxied_host_arg && *proxied_host_arg) ?
+ strdup_root (mem, proxied_host_arg) : NULL,
+ (proxied_user_arg && *proxied_user_arg) ?
+ strdup_root (mem, proxied_user_arg) : NULL,
+ with_grant_arg);
+ }
+
+ void init(TABLE *table, MEM_ROOT *mem)
+ {
+ init (get_field(mem, table->field[MYSQL_PROXIES_PRIV_HOST]),
+ get_field(mem, table->field[MYSQL_PROXIES_PRIV_USER]),
+ get_field(mem, table->field[MYSQL_PROXIES_PRIV_PROXIED_HOST]),
+ get_field(mem, table->field[MYSQL_PROXIES_PRIV_PROXIED_USER]),
+ table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->val_int() != 0);
+ }
+
+ bool get_with_grant() { return with_grant; }
+ const char *get_user() { return user; }
+ const char *get_host() { return host.hostname; }
+ const char *get_proxied_user() { return proxied_user; }
+ const char *get_proxied_host() { return proxied_host.hostname; }
+ void set_user(MEM_ROOT *mem, const char *user_arg)
+ {
+ user= user_arg && *user_arg ? strdup_root(mem, user_arg) : NULL;
+ }
+ void set_host(MEM_ROOT *mem, const char *host_arg)
+ {
+ update_hostname(&host, safe_strdup_root(mem, host_arg));
+ }
+
+ bool check_validity(bool check_no_resolve)
+ {
+ if (check_no_resolve &&
+ (hostname_requires_resolving(host.hostname) ||
+ hostname_requires_resolving(proxied_host.hostname)))
+ {
+ sql_print_warning("'proxies_priv' entry '%s@%s %s@%s' "
+ "ignored in --skip-name-resolve mode.",
+ safe_str(proxied_user),
+ safe_str(proxied_host.hostname),
+ safe_str(user),
+ safe_str(host.hostname));
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ bool matches(const char *host_arg, const char *user_arg, const char *ip_arg,
+ const char *proxied_user_arg)
+ {
+ DBUG_ENTER("ACL_PROXY_USER::matches");
+ DBUG_PRINT("info", ("compare_hostname(%s,%s,%s) &&"
+ "compare_hostname(%s,%s,%s) &&"
+ "wild_compare (%s,%s) &&"
+ "wild_compare (%s,%s)",
+ host.hostname, host_arg, ip_arg, proxied_host.hostname,
+ host_arg, ip_arg, user_arg, user,
+ proxied_user_arg, proxied_user));
+ DBUG_RETURN(compare_hostname(&host, host_arg, ip_arg) &&
+ compare_hostname(&proxied_host, host_arg, ip_arg) &&
+ (!user ||
+ (user_arg && !wild_compare(user_arg, user, TRUE))) &&
+ (!proxied_user ||
+ (proxied_user && !wild_compare(proxied_user_arg,
+ proxied_user, TRUE))));
+ }
+
+
+ inline static bool auth_element_equals(const char *a, const char *b)
+ {
+ return (a == b || (a != NULL && b != NULL && !strcmp(a,b)));
+ }
+
+
+ bool pk_equals(ACL_PROXY_USER *grant)
+ {
+ DBUG_ENTER("pk_equals");
+ DBUG_PRINT("info", ("strcmp(%s,%s) &&"
+ "strcmp(%s,%s) &&"
+ "wild_compare (%s,%s) &&"
+ "wild_compare (%s,%s)",
+ user, grant->user, proxied_user, grant->proxied_user,
+ host.hostname, grant->host.hostname,
+ proxied_host.hostname, grant->proxied_host.hostname));
+
+ bool res= auth_element_equals(user, grant->user) &&
+ auth_element_equals(proxied_user, grant->proxied_user) &&
+ auth_element_equals(host.hostname, grant->host.hostname) &&
+ auth_element_equals(proxied_host.hostname,
+ grant->proxied_host.hostname);
+ DBUG_RETURN(res);
+ }
+
+
+ bool granted_on(const char *host_arg, const char *user_arg)
+ {
+ return (((!user && (!user_arg || !user_arg[0])) ||
+ (user && user_arg && !strcmp(user, user_arg))) &&
+ ((!host.hostname && (!host_arg || !host_arg[0])) ||
+ (host.hostname && host_arg && !strcmp(host.hostname, host_arg))));
+ }
+
+
+ void print_grant(String *str)
+ {
+ str->append(STRING_WITH_LEN("GRANT PROXY ON '"));
+ if (proxied_user)
+ str->append(proxied_user, strlen(proxied_user));
+ str->append(STRING_WITH_LEN("'@'"));
+ if (proxied_host.hostname)
+ str->append(proxied_host.hostname, strlen(proxied_host.hostname));
+ str->append(STRING_WITH_LEN("' TO '"));
+ if (user)
+ str->append(user, strlen(user));
+ str->append(STRING_WITH_LEN("'@'"));
+ if (host.hostname)
+ str->append(host.hostname, strlen(host.hostname));
+ str->append(STRING_WITH_LEN("'"));
+ if (with_grant)
+ str->append(STRING_WITH_LEN(" WITH GRANT OPTION"));
+ }
+
+ void set_data(ACL_PROXY_USER *grant)
+ {
+ with_grant= grant->with_grant;
+ }
+
+ static int store_pk(TABLE *table,
+ const LEX_STRING *host,
+ const LEX_STRING *user,
+ const LEX_STRING *proxied_host,
+ const LEX_STRING *proxied_user)
+ {
+ DBUG_ENTER("ACL_PROXY_USER::store_pk");
+ DBUG_PRINT("info", ("host=%s, user=%s, proxied_host=%s, proxied_user=%s",
+ host->str, user->str,
+ proxied_host->str, proxied_user->str));
+ if (table->field[MYSQL_PROXIES_PRIV_HOST]->store(host->str,
+ host->length,
+ system_charset_info))
+ DBUG_RETURN(TRUE);
+ if (table->field[MYSQL_PROXIES_PRIV_USER]->store(user->str,
+ user->length,
+ system_charset_info))
+ DBUG_RETURN(TRUE);
+ if (table->field[MYSQL_PROXIES_PRIV_PROXIED_HOST]->store(proxied_host->str,
+ proxied_host->length,
+ system_charset_info))
+ DBUG_RETURN(TRUE);
+ if (table->field[MYSQL_PROXIES_PRIV_PROXIED_USER]->store(proxied_user->str,
+ proxied_user->length,
+ system_charset_info))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+ }
+
+ static int store_data_record(TABLE *table,
+ const LEX_STRING *host,
+ const LEX_STRING *user,
+ const LEX_STRING *proxied_host,
+ const LEX_STRING *proxied_user,
+ bool with_grant,
+ const char *grantor)
+ {
+ DBUG_ENTER("ACL_PROXY_USER::store_pk");
+ if (store_pk(table, host, user, proxied_host, proxied_user))
+ DBUG_RETURN(TRUE);
+ DBUG_PRINT("info", ("with_grant=%s", with_grant ? "TRUE" : "FALSE"));
+ if (table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0,
+ TRUE))
+ DBUG_RETURN(TRUE);
+ if (table->field[MYSQL_PROXIES_PRIV_GRANTOR]->store(grantor,
+ strlen(grantor),
+ system_charset_info))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+ }
+};
+
+#define FIRST_NON_YN_FIELD 26
+
+class acl_entry :public hash_filo_element
+{
+public:
+ ulong access;
+ uint16 length;
+ char key[1]; // Key will be stored here
+};
+
+
+static uchar* acl_entry_get_key(acl_entry *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length=(uint) entry->length;
+ return (uchar*) entry->key;
+}
+
+static uchar* acl_role_get_key(ACL_ROLE *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length=(uint) entry->user.length;
+ return (uchar*) entry->user.str;
+}
+
+struct ROLE_GRANT_PAIR : public Sql_alloc
+{
+ char *u_uname;
+ char *u_hname;
+ char *r_uname;
+ LEX_STRING hashkey;
+ bool with_admin;
+
+ bool init(MEM_ROOT *mem, char *username, char *hostname, char *rolename,
+ bool with_admin_option);
+};
+
+static uchar* acl_role_map_get_key(ROLE_GRANT_PAIR *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length=(uint) entry->hashkey.length;
+ return (uchar*) entry->hashkey.str;
+}
+
+bool ROLE_GRANT_PAIR::init(MEM_ROOT *mem, char *username,
+ char *hostname, char *rolename,
+ bool with_admin_option)
+{
+ if (!this)
+ return true;
+
+ size_t uname_l = username ? strlen(username) : 0;
+ size_t hname_l = hostname ? strlen(hostname) : 0;
+ size_t rname_l = rolename ? strlen(rolename) : 0;
+ /*
+ Create a buffer that holds all 3 NULL terminated strings in succession
+ To save memory space, the same buffer is used as the hashkey
+ */
+ size_t bufflen = uname_l + hname_l + rname_l + 3; //add the '\0' aswell
+ char *buff= (char *)alloc_root(mem, bufflen);
+ if (!buff)
+ return true;
+
+ /*
+ Offsets in the buffer for all 3 strings
+ */
+ char *username_pos= buff;
+ char *hostname_pos= buff + uname_l + 1;
+ char *rolename_pos= buff + uname_l + hname_l + 2;
+
+ if (username) //prevent undefined behaviour
+ memcpy(username_pos, username, uname_l);
+ username_pos[uname_l]= '\0'; //#1 string terminator
+ u_uname= username_pos;
+
+ if (hostname) //prevent undefined behaviour
+ memcpy(hostname_pos, hostname, hname_l);
+ hostname_pos[hname_l]= '\0'; //#2 string terminator
+ u_hname= hostname_pos;
+
+ if (rolename) //prevent undefined behaviour
+ memcpy(rolename_pos, rolename, rname_l);
+ rolename_pos[rname_l]= '\0'; //#3 string terminator
+ r_uname= rolename_pos;
+
+ hashkey.str = buff;
+ hashkey.length = bufflen;
+
+ with_admin= with_admin_option;
+
+ return false;
+}
+
+#define IP_ADDR_STRLEN (3 + 1 + 3 + 1 + 3 + 1 + 3)
+#define ACL_KEY_LENGTH (IP_ADDR_STRLEN + 1 + NAME_LEN + \
+ 1 + USERNAME_LENGTH + 1)
+
+#if defined(HAVE_OPENSSL)
+/*
+ Without SSL the handshake consists of one packet. This packet
+ has both client capabilities and scrambled password.
+ With SSL the handshake might consist of two packets. If the first
+ packet (client capabilities) has CLIENT_SSL flag set, we have to
+ switch to SSL and read the second packet. The scrambled password
+ is in the second packet and client_capabilities field will be ignored.
+ Maybe it is better to accept flags other than CLIENT_SSL from the
+ second packet?
+*/
+#define SSL_HANDSHAKE_SIZE 2
+#define MIN_HANDSHAKE_SIZE 2
+#else
+#define MIN_HANDSHAKE_SIZE 6
+#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */
+#define NORMAL_HANDSHAKE_SIZE 6
+
+#define ROLE_ASSIGN_COLUMN_IDX 43
+/* various flags valid for ACL_USER */
+#define IS_ROLE (1L << 0)
+/* Flag to mark that a ROLE is on the recursive DEPTH_FIRST_SEARCH stack */
+#define ROLE_ON_STACK (1L << 1)
+/*
+ Flag to mark that a ROLE and all it's neighbours have
+ been visited
+*/
+#define ROLE_EXPLORED (1L << 2)
+/* Flag to mark that on_node was already called for this role */
+#define ROLE_OPENED (1L << 3)
+
+static DYNAMIC_ARRAY acl_hosts, acl_users, acl_dbs, acl_proxy_users;
+static HASH acl_roles;
+/*
+ An hash containing mappings user <--> role
+
+ A hash is used so as to make updates quickly
+ The hashkey used represents all the entries combined
+*/
+static HASH acl_roles_mappings;
+static MEM_ROOT acl_memroot, grant_memroot;
+static bool initialized=0;
+static bool allow_all_hosts=1;
+static HASH acl_check_hosts, column_priv_hash, proc_priv_hash, func_priv_hash;
+static DYNAMIC_ARRAY acl_wild_hosts;
+static Hash_filo<acl_entry> *acl_cache;
+static uint grant_version=0; /* Version of priv tables. incremented by acl_load */
+static ulong get_access(TABLE *form,uint fieldnr, uint *next_field=0);
+static bool check_is_role(TABLE *form);
+static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b);
+static ulong get_sort(uint count,...);
+static void init_check_host(void);
+static void rebuild_check_host(void);
+static void rebuild_role_grants(void);
+static ACL_USER *find_user_exact(const char *host, const char *user);
+static ACL_USER *find_user_wild(const char *host, const char *user, const char *ip= 0);
+static ACL_ROLE *find_acl_role(const char *user);
+static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_STRING *u, const LEX_STRING *h, const LEX_STRING *r);
+static ACL_USER_BASE *find_acl_user_base(const char *user, const char *host);
+static bool update_user_table(THD *thd, TABLE *table, const char *host,
+ const char *user, const char *new_password,
+ uint new_password_len);
+static my_bool acl_load(THD *thd, TABLE_LIST *tables);
+static my_bool grant_load(THD *thd, TABLE_LIST *tables);
+static inline void get_grantor(THD *thd, char* grantor);
+static bool add_role_user_mapping(const char *uname, const char *hname, const char *rname);
+
+#define ROLE_CYCLE_FOUND 2
+static int traverse_role_graph_up(ACL_ROLE *, void *,
+ int (*) (ACL_ROLE *, void *),
+ int (*) (ACL_ROLE *, ACL_ROLE *, void *));
+
+static int traverse_role_graph_down(ACL_USER_BASE *, void *,
+ int (*) (ACL_USER_BASE *, void *),
+ int (*) (ACL_USER_BASE *, ACL_ROLE *, void *));
+
+/*
+ Enumeration of various ACL's and Hashes used in handle_grant_struct()
+*/
+enum enum_acl_lists
+{
+ USER_ACL= 0,
+ ROLE_ACL,
+ DB_ACL,
+ COLUMN_PRIVILEGES_HASH,
+ PROC_PRIVILEGES_HASH,
+ FUNC_PRIVILEGES_HASH,
+ PROXY_USERS_ACL,
+ ROLES_MAPPINGS_HASH
+};
+
+ACL_ROLE::ACL_ROLE(ACL_USER *user, MEM_ROOT *root) : counter(0)
+{
+
+ access= user->access;
+ /* set initial role access the same as the table row privileges */
+ initial_role_access= user->access;
+ this->user.str= safe_strdup_root(root, user->user.str);
+ this->user.length= user->user.length;
+ bzero(&role_grants, sizeof(role_grants));
+ bzero(&parent_grantee, sizeof(parent_grantee));
+ flags= IS_ROLE;
+}
+
+ACL_ROLE::ACL_ROLE(const char * rolename, ulong privileges, MEM_ROOT *root) :
+ initial_role_access(privileges), counter(0)
+{
+ this->access= initial_role_access;
+ this->user.str= safe_strdup_root(root, rolename);
+ this->user.length= strlen(rolename);
+ bzero(&role_grants, sizeof(role_grants));
+ bzero(&parent_grantee, sizeof(parent_grantee));
+ flags= IS_ROLE;
+}
+
+
+static bool is_invalid_role_name(const char *str)
+{
+ if (*str && strcasecmp(str, "PUBLIC") && strcasecmp(str, "NONE"))
+ return false;
+
+ my_error(ER_INVALID_ROLE, MYF(0), str);
+ return true;
+}
+
+
+static void free_acl_user(ACL_USER *user)
+{
+ delete_dynamic(&(user->role_grants));
+}
+
+static void free_acl_role(ACL_ROLE *role)
+{
+ delete_dynamic(&(role->role_grants));
+ delete_dynamic(&(role->parent_grantee));
+}
+
+/**
+ Convert scrambled password to binary form, according to scramble type,
+ Binary form is stored in user.salt.
+
+ @param acl_user The object where to store the salt
+ @param password The password hash containing the salt
+ @param password_len The length of the password hash
+
+ Despite the name of the function it is used when loading ACLs from disk
+ to store the password hash in the ACL_USER object.
+*/
+
+static void
+set_user_salt(ACL_USER *acl_user, const char *password, uint password_len)
+{
+ if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH)
+ {
+ get_salt_from_password(acl_user->salt, password);
+ acl_user->salt_len= SCRAMBLE_LENGTH;
+ }
+ else if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
+ {
+ get_salt_from_password_323((ulong *) acl_user->salt, password);
+ acl_user->salt_len= SCRAMBLE_LENGTH_323;
+ }
+ else
+ acl_user->salt_len= 0;
+}
+
+static char *fix_plugin_ptr(char *name)
+{
+ if (my_strcasecmp(system_charset_info, name,
+ native_password_plugin_name.str) == 0)
+ return native_password_plugin_name.str;
+ else
+ if (my_strcasecmp(system_charset_info, name,
+ old_password_plugin_name.str) == 0)
+ return old_password_plugin_name.str;
+ else
+ return name;
+}
+
+/**
+ Fix ACL::plugin pointer to point to a hard-coded string, if appropriate
+
+ Make sure that if ACL_USER's plugin is a built-in, then it points
+ to a hard coded string, not to an allocated copy. Run-time, for
+ authentication, we want to be able to detect built-ins by comparing
+ pointers, not strings.
+
+ Additionally - update the salt if the plugin is built-in.
+
+ @retval 0 the pointers were fixed
+ @retval 1 this ACL_USER uses a not built-in plugin
+*/
+static bool fix_user_plugin_ptr(ACL_USER *user)
+{
+ if (my_strcasecmp(system_charset_info, user->plugin.str,
+ native_password_plugin_name.str) == 0)
+ user->plugin= native_password_plugin_name;
+ else
+ if (my_strcasecmp(system_charset_info, user->plugin.str,
+ old_password_plugin_name.str) == 0)
+ user->plugin= old_password_plugin_name;
+ else
+ return true;
+
+ if (user->auth_string.length)
+ set_user_salt(user, user->auth_string.str, user->auth_string.length);
+ return false;
+}
+
+static bool get_YN_as_bool(Field *field)
+{
+ char buff[2];
+ String res(buff,sizeof(buff),&my_charset_latin1);
+ field->val_str(&res);
+ return res[0] == 'Y' || res[0] == 'y';
+}
+
+
+/*
+ Initialize structures responsible for user/db-level privilege checking and
+ load privilege information for them from tables in the 'mysql' database.
+
+ SYNOPSIS
+ acl_init()
+ dont_read_acl_tables TRUE if we want to skip loading data from
+ privilege tables and disable privilege checking.
+
+ NOTES
+ This function is mostly responsible for preparatory steps, main work
+ on initialization and grants loading is done in acl_reload().
+
+ RETURN VALUES
+ 0 ok
+ 1 Could not initialize grant's
+*/
+
+my_bool acl_init(bool dont_read_acl_tables)
+{
+ THD *thd;
+ my_bool return_val;
+ DBUG_ENTER("acl_init");
+
+ acl_cache= new Hash_filo<acl_entry>(ACL_CACHE_SIZE, 0, 0,
+ (my_hash_get_key) acl_entry_get_key,
+ (my_hash_free_key) free,
+ &my_charset_utf8_bin);
+
+ /*
+ cache built-in native authentication plugins,
+ to avoid hash searches and a global mutex lock on every connect
+ */
+ native_password_plugin= my_plugin_lock_by_name(0,
+ &native_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN);
+ old_password_plugin= my_plugin_lock_by_name(0,
+ &old_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN);
+
+ if (!native_password_plugin || !old_password_plugin)
+ DBUG_RETURN(1);
+
+ if (dont_read_acl_tables)
+ {
+ DBUG_RETURN(0); /* purecov: tested */
+ }
+
+ /*
+ To be able to run this from boot, we allocate a temporary THD
+ */
+ if (!(thd=new THD))
+ DBUG_RETURN(1); /* purecov: inspected */
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+ /*
+ It is safe to call acl_reload() since acl_* arrays and hashes which
+ will be freed there are global static objects and thus are initialized
+ by zeros at startup.
+ */
+ return_val= acl_reload(thd);
+ delete thd;
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+ DBUG_RETURN(return_val);
+}
+
+/**
+ Choose from either native or old password plugins when assigning a password
+*/
+
+static bool set_user_plugin (ACL_USER *user, int password_len)
+{
+ switch (password_len)
+ {
+ case 0: /* no password */
+ case SCRAMBLED_PASSWORD_CHAR_LENGTH:
+ user->plugin= native_password_plugin_name;
+ return FALSE;
+ case SCRAMBLED_PASSWORD_CHAR_LENGTH_323:
+ user->plugin= old_password_plugin_name;
+ return FALSE;
+ default:
+ sql_print_warning("Found invalid password for user: '%s@%s'; "
+ "Ignoring user", safe_str(user->user.str),
+ safe_str(user->host.hostname));
+ return TRUE;
+ }
+}
+
+
+/*
+ Initialize structures responsible for user/db-level privilege checking
+ and load information about grants from open privilege tables.
+
+ SYNOPSIS
+ acl_load()
+ thd Current thread
+ tables List containing open "mysql.host", "mysql.user",
+ "mysql.db", "mysql.proxies_priv" and "mysql.roles_mapping"
+ tables.
+
+ RETURN VALUES
+ FALSE Success
+ TRUE Error
+*/
+
+static my_bool acl_load(THD *thd, TABLE_LIST *tables)
+{
+ TABLE *table;
+ READ_RECORD read_record_info;
+ my_bool return_val= TRUE;
+ bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE;
+ char tmp_name[SAFE_NAME_LEN+1];
+ int password_length;
+ ulonglong old_sql_mode= thd->variables.sql_mode;
+ DBUG_ENTER("acl_load");
+
+ thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH;
+
+ grant_version++; /* Privileges updated */
+
+ init_sql_alloc(&acl_memroot, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
+ (void) my_init_dynamic_array(&acl_hosts,sizeof(ACL_HOST), 20, 50, MYF(0));
+ if (tables[0].table) // "host" table may not exist (e.g. in MySQL 5.6.7+)
+ {
+ if (init_read_record(&read_record_info, thd, table= tables[0].table,
+ NULL, 1, 1, FALSE))
+ goto end;
+ table->use_all_columns();
+ while (!(read_record_info.read_record(&read_record_info)))
+ {
+ ACL_HOST host;
+ update_hostname(&host.host,get_field(&acl_memroot, table->field[0]));
+ host.db= get_field(&acl_memroot, table->field[1]);
+ if (lower_case_table_names && host.db)
+ {
+ /*
+ convert db to lower case and give a warning if the db wasn't
+ already in lower case
+ */
+ char *end = strnmov(tmp_name, host.db, sizeof(tmp_name));
+ if (end >= tmp_name + sizeof(tmp_name))
+ {
+ sql_print_warning(ER(ER_WRONG_DB_NAME), host.db);
+ continue;
+ }
+ my_casedn_str(files_charset_info, host.db);
+ if (strcmp(host.db, tmp_name) != 0)
+ sql_print_warning("'host' entry '%s|%s' had database in mixed "
+ "case that has been forced to lowercase because "
+ "lower_case_table_names is set. It will not be "
+ "possible to remove this privilege using REVOKE.",
+ host.host.hostname, host.db);
+ }
+ host.access= get_access(table,2);
+ host.access= fix_rights_for_db(host.access);
+ host.sort= get_sort(2,host.host.hostname,host.db);
+ if (check_no_resolve && hostname_requires_resolving(host.host.hostname))
+ {
+ sql_print_warning("'host' entry '%s|%s' "
+ "ignored in --skip-name-resolve mode.",
+ safe_str(host.host.hostname),
+ safe_str(host.db));
+ continue;
+ }
+#ifndef TO_BE_REMOVED
+ if (table->s->fields == 8)
+ { // Without grant
+ if (host.access & CREATE_ACL)
+ host.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL;
+ }
+#endif
+ (void) push_dynamic(&acl_hosts,(uchar*) &host);
+ }
+ my_qsort((uchar*) dynamic_element(&acl_hosts,0,ACL_HOST*),acl_hosts.elements,
+ sizeof(ACL_HOST),(qsort_cmp) acl_compare);
+ end_read_record(&read_record_info);
+ }
+ freeze_size(&acl_hosts);
+
+ if (init_read_record(&read_record_info, thd, table=tables[1].table,
+ NULL, 1, 1, FALSE))
+ goto end;
+ table->use_all_columns();
+ (void) my_init_dynamic_array(&acl_users,sizeof(ACL_USER), 50, 100, MYF(0));
+ (void) my_hash_init2(&acl_roles,50, &my_charset_utf8_bin,
+ 0, 0, 0, (my_hash_get_key) acl_role_get_key, 0,
+ (void (*)(void *))free_acl_role, 0);
+
+ username_char_length= MY_MIN(table->field[1]->char_length(),
+ USERNAME_CHAR_LENGTH);
+ password_length= table->field[2]->field_length /
+ table->field[2]->charset()->mbmaxlen;
+ if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
+ {
+ sql_print_error("Fatal error: mysql.user table is damaged or in "
+ "unsupported 3.20 format.");
+ goto end;
+ }
+
+ DBUG_PRINT("info",("user table fields: %d, password length: %d",
+ table->s->fields, password_length));
+
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH)
+ {
+ if (opt_secure_auth)
+ {
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ sql_print_error("Fatal error: mysql.user table is in old format, "
+ "but server started with --secure-auth option.");
+ goto end;
+ }
+ mysql_user_table_is_in_short_password_format= true;
+ if (global_system_variables.old_passwords)
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ else
+ {
+ global_system_variables.old_passwords= 1;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ sql_print_warning("mysql.user table is not updated to new password format; "
+ "Disabling new password usage until "
+ "mysql_fix_privilege_tables is run");
+ }
+ thd->variables.old_passwords= 1;
+ }
+ else
+ {
+ mysql_user_table_is_in_short_password_format= false;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ }
+
+ allow_all_hosts=0;
+ while (!(read_record_info.read_record(&read_record_info)))
+ {
+ ACL_USER user;
+ bool is_role= FALSE;
+ bzero(&user, sizeof(user));
+ update_hostname(&user.host, get_field(&acl_memroot, table->field[0]));
+ char *username= get_field(&acl_memroot, table->field[1]);
+ user.user.str= username;
+ user.user.length= username? strlen(username) : 0;
+
+ /*
+ If the user entry is a role, skip password and hostname checks
+ A user can not log in with a role so some checks are not necessary
+ */
+ is_role= check_is_role(table);
+
+ if (is_role && is_invalid_role_name(username))
+ {
+ thd->clear_error(); // the warning is still issued
+ continue;
+ }
+
+ if (!is_role && check_no_resolve &&
+ hostname_requires_resolving(user.host.hostname))
+ {
+ sql_print_warning("'user' entry '%s@%s' "
+ "ignored in --skip-name-resolve mode.",
+ safe_str(user.user.str),
+ safe_str(user.host.hostname));
+ continue;
+ }
+
+ char *password= get_field(&acl_memroot, table->field[2]);
+ uint password_len= password ? strlen(password) : 0;
+ user.auth_string.str= safe_str(password);
+ user.auth_string.length= password_len;
+ set_user_salt(&user, password, password_len);
+
+ if (!is_role && set_user_plugin(&user, password_len))
+ continue;
+
+ {
+ uint next_field;
+ user.access= get_access(table,3,&next_field) & GLOBAL_ACLS;
+ /*
+ if it is pre 5.0.1 privilege table then map CREATE privilege on
+ CREATE VIEW & SHOW VIEW privileges
+ */
+ if (table->s->fields <= 31 && (user.access & CREATE_ACL))
+ user.access|= (CREATE_VIEW_ACL | SHOW_VIEW_ACL);
+
+ /*
+ if it is pre 5.0.2 privilege table then map CREATE/ALTER privilege on
+ CREATE PROCEDURE & ALTER PROCEDURE privileges
+ */
+ if (table->s->fields <= 33 && (user.access & CREATE_ACL))
+ user.access|= CREATE_PROC_ACL;
+ if (table->s->fields <= 33 && (user.access & ALTER_ACL))
+ user.access|= ALTER_PROC_ACL;
+
+ /*
+ pre 5.0.3 did not have CREATE_USER_ACL
+ */
+ if (table->s->fields <= 36 && (user.access & GRANT_ACL))
+ user.access|= CREATE_USER_ACL;
+
+
+ /*
+ if it is pre 5.1.6 privilege table then map CREATE privilege on
+ CREATE|ALTER|DROP|EXECUTE EVENT
+ */
+ if (table->s->fields <= 37 && (user.access & SUPER_ACL))
+ user.access|= EVENT_ACL;
+
+ /*
+ if it is pre 5.1.6 privilege then map TRIGGER privilege on CREATE.
+ */
+ if (table->s->fields <= 38 && (user.access & SUPER_ACL))
+ user.access|= TRIGGER_ACL;
+
+ user.sort= get_sort(2, user.host.hostname, user.user.str);
+ user.hostname_length= (user.host.hostname ?
+ (uint) strlen(user.host.hostname) : 0);
+
+ /* Starting from 4.0.2 we have more fields */
+ if (table->s->fields >= 31)
+ {
+ char *ssl_type=get_field(thd->mem_root, table->field[next_field++]);
+ if (!ssl_type)
+ user.ssl_type=SSL_TYPE_NONE;
+ else if (!strcmp(ssl_type, "ANY"))
+ user.ssl_type=SSL_TYPE_ANY;
+ else if (!strcmp(ssl_type, "X509"))
+ user.ssl_type=SSL_TYPE_X509;
+ else /* !strcmp(ssl_type, "SPECIFIED") */
+ user.ssl_type=SSL_TYPE_SPECIFIED;
+
+ user.ssl_cipher= get_field(&acl_memroot, table->field[next_field++]);
+ user.x509_issuer= get_field(&acl_memroot, table->field[next_field++]);
+ user.x509_subject= get_field(&acl_memroot, table->field[next_field++]);
+
+ char *ptr = get_field(thd->mem_root, table->field[next_field++]);
+ user.user_resource.questions=ptr ? atoi(ptr) : 0;
+ ptr = get_field(thd->mem_root, table->field[next_field++]);
+ user.user_resource.updates=ptr ? atoi(ptr) : 0;
+ ptr = get_field(thd->mem_root, table->field[next_field++]);
+ user.user_resource.conn_per_hour= ptr ? atoi(ptr) : 0;
+ if (user.user_resource.questions || user.user_resource.updates ||
+ user.user_resource.conn_per_hour)
+ mqh_used=1;
+
+ if (table->s->fields >= 36)
+ {
+ /* Starting from 5.0.3 we have max_user_connections field */
+ ptr= get_field(thd->mem_root, table->field[next_field++]);
+ user.user_resource.user_conn= ptr ? atoi(ptr) : 0;
+ }
+
+ if (!is_role && table->s->fields >= 41)
+ {
+ /* We may have plugin & auth_String fields */
+ char *tmpstr= get_field(&acl_memroot, table->field[next_field++]);
+ if (tmpstr)
+ {
+ user.plugin.str= tmpstr;
+ user.plugin.length= strlen(user.plugin.str);
+ user.auth_string.str=
+ safe_str(get_field(&acl_memroot, table->field[next_field++]));
+ user.auth_string.length= strlen(user.auth_string.str);
+
+ if (user.auth_string.length && password_len)
+ {
+ sql_print_warning("'user' entry '%s@%s' has both a password "
+ "and an authentication plugin specified. The "
+ "password will be ignored.",
+ safe_str(user.user.str),
+ safe_str(user.host.hostname));
+ }
+
+ fix_user_plugin_ptr(&user);
+ }
+ }
+ }
+ else
+ {
+ user.ssl_type=SSL_TYPE_NONE;
+#ifndef TO_BE_REMOVED
+ if (table->s->fields <= 13)
+ { // Without grant
+ if (user.access & CREATE_ACL)
+ user.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL;
+ }
+ /* Convert old privileges */
+ user.access|= LOCK_TABLES_ACL | CREATE_TMP_ACL | SHOW_DB_ACL;
+ if (user.access & FILE_ACL)
+ user.access|= REPL_CLIENT_ACL | REPL_SLAVE_ACL;
+ if (user.access & PROCESS_ACL)
+ user.access|= SUPER_ACL | EXECUTE_ACL;
+#endif
+ }
+
+ (void) my_init_dynamic_array(&user.role_grants,sizeof(ACL_ROLE *),
+ 8, 8, MYF(0));
+
+ if (is_role)
+ {
+ DBUG_PRINT("info", ("Found role %s", user.user.str));
+ ACL_ROLE *entry= new (&acl_memroot) ACL_ROLE(&user, &acl_memroot);
+ entry->role_grants = user.role_grants;
+ (void) my_init_dynamic_array(&entry->parent_grantee,
+ sizeof(ACL_USER_BASE *), 8, 8, MYF(0));
+ my_hash_insert(&acl_roles, (uchar *)entry);
+
+ continue;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Found user %s", user.user.str));
+ (void) push_dynamic(&acl_users,(uchar*) &user);
+ }
+ if (!user.host.hostname ||
+ (user.host.hostname[0] == wild_many && !user.host.hostname[1]))
+ allow_all_hosts=1; // Anyone can connect
+ }
+ }
+ my_qsort((uchar*) dynamic_element(&acl_users,0,ACL_USER*),acl_users.elements,
+ sizeof(ACL_USER),(qsort_cmp) acl_compare);
+ end_read_record(&read_record_info);
+ freeze_size(&acl_users);
+
+ if (init_read_record(&read_record_info, thd, table=tables[2].table,
+ NULL, 1, 1, FALSE))
+ goto end;
+ table->use_all_columns();
+ (void) my_init_dynamic_array(&acl_dbs,sizeof(ACL_DB), 50, 100, MYF(0));
+ while (!(read_record_info.read_record(&read_record_info)))
+ {
+ ACL_DB db;
+ db.user=get_field(&acl_memroot, table->field[MYSQL_DB_FIELD_USER]);
+ const char *hostname= get_field(&acl_memroot, table->field[MYSQL_DB_FIELD_HOST]);
+ if (!hostname && find_acl_role(db.user))
+ hostname= "";
+ update_hostname(&db.host, hostname);
+ db.db=get_field(&acl_memroot, table->field[MYSQL_DB_FIELD_DB]);
+ if (!db.db)
+ {
+ sql_print_warning("Found an entry in the 'db' table with empty database name; Skipped");
+ continue;
+ }
+ if (check_no_resolve && hostname_requires_resolving(db.host.hostname))
+ {
+ sql_print_warning("'db' entry '%s %s@%s' "
+ "ignored in --skip-name-resolve mode.",
+ db.db, safe_str(db.user), safe_str(db.host.hostname));
+ continue;
+ }
+ db.access=get_access(table,3);
+ db.access=fix_rights_for_db(db.access);
+ db.initial_access= db.access;
+ if (lower_case_table_names)
+ {
+ /*
+ convert db to lower case and give a warning if the db wasn't
+ already in lower case
+ */
+ char *end = strnmov(tmp_name, db.db, sizeof(tmp_name));
+ if (end >= tmp_name + sizeof(tmp_name))
+ {
+ sql_print_warning(ER(ER_WRONG_DB_NAME), db.db);
+ continue;
+ }
+ my_casedn_str(files_charset_info, db.db);
+ if (strcmp(db.db, tmp_name) != 0)
+ {
+ sql_print_warning("'db' entry '%s %s@%s' had database in mixed "
+ "case that has been forced to lowercase because "
+ "lower_case_table_names is set. It will not be "
+ "possible to remove this privilege using REVOKE.",
+ db.db, safe_str(db.user), safe_str(db.host.hostname));
+ }
+ }
+ db.sort=get_sort(3,db.host.hostname,db.db,db.user);
+#ifndef TO_BE_REMOVED
+ if (table->s->fields <= 9)
+ { // Without grant
+ if (db.access & CREATE_ACL)
+ db.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL;
+ }
+#endif
+ (void) push_dynamic(&acl_dbs,(uchar*) &db);
+ }
+ my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements,
+ sizeof(ACL_DB),(qsort_cmp) acl_compare);
+ end_read_record(&read_record_info);
+ freeze_size(&acl_dbs);
+
+ (void) my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER),
+ 50, 100, MYF(0));
+ if (tables[3].table)
+ {
+ if (init_read_record(&read_record_info, thd, table= tables[3].table,
+ NULL, 1, 1, FALSE))
+ goto end;
+ table->use_all_columns();
+ while (!(read_record_info.read_record(&read_record_info)))
+ {
+ ACL_PROXY_USER proxy;
+ proxy.init(table, &acl_memroot);
+ if (proxy.check_validity(check_no_resolve))
+ continue;
+ if (push_dynamic(&acl_proxy_users, (uchar*) &proxy))
+ {
+ end_read_record(&read_record_info);
+ goto end;
+ }
+ }
+ my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER*),
+ acl_proxy_users.elements,
+ sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare);
+ end_read_record(&read_record_info);
+ }
+ else
+ {
+ sql_print_error("Missing system table mysql.proxies_priv; "
+ "please run mysql_upgrade to create it");
+ }
+ freeze_size(&acl_proxy_users);
+
+ if (tables[4].table)
+ {
+ if (init_read_record(&read_record_info, thd, table= tables[4].table,
+ NULL, 1, 1, FALSE))
+ goto end;
+ table->use_all_columns();
+ /* account for every role mapping */
+
+ (void) my_hash_init2(&acl_roles_mappings, 50, system_charset_info, 0, 0, 0,
+ (my_hash_get_key) acl_role_map_get_key, 0, 0, 0);
+ MEM_ROOT temp_root;
+ init_alloc_root(&temp_root, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
+ while (!(read_record_info.read_record(&read_record_info)))
+ {
+ char *hostname= safe_str(get_field(&temp_root, table->field[0]));
+ char *username= safe_str(get_field(&temp_root, table->field[1]));
+ char *rolename= safe_str(get_field(&temp_root, table->field[2]));
+ bool with_grant_option= get_YN_as_bool(table->field[3]);
+
+ if (add_role_user_mapping(username, hostname, rolename)) {
+ sql_print_error("Invalid roles_mapping table entry user:'%s@%s', rolename:'%s'",
+ username, hostname, rolename);
+ continue;
+ }
+
+ ROLE_GRANT_PAIR *mapping= new (&acl_memroot) ROLE_GRANT_PAIR;
+
+ if (mapping->init(&acl_memroot, username, hostname, rolename, with_grant_option))
+ continue;
+
+ my_hash_insert(&acl_roles_mappings, (uchar*) mapping);
+ }
+
+ free_root(&temp_root, MYF(0));
+ end_read_record(&read_record_info);
+ }
+ else
+ {
+ sql_print_error("Missing system table mysql.roles_mapping; "
+ "please run mysql_upgrade to create it");
+ }
+
+ init_check_host();
+
+ initialized=1;
+ return_val= FALSE;
+
+end:
+ thd->variables.sql_mode= old_sql_mode;
+ DBUG_RETURN(return_val);
+}
+
+
+void acl_free(bool end)
+{
+ my_hash_free(&acl_roles);
+ free_root(&acl_memroot,MYF(0));
+ delete_dynamic(&acl_hosts);
+ delete_dynamic_with_callback(&acl_users, (FREE_FUNC) free_acl_user);
+ delete_dynamic(&acl_dbs);
+ delete_dynamic(&acl_wild_hosts);
+ delete_dynamic(&acl_proxy_users);
+ my_hash_free(&acl_check_hosts);
+ my_hash_free(&acl_roles_mappings);
+ plugin_unlock(0, native_password_plugin);
+ plugin_unlock(0, old_password_plugin);
+ if (!end)
+ acl_cache->clear(1); /* purecov: inspected */
+ else
+ {
+ delete acl_cache;
+ acl_cache=0;
+ }
+}
+
+
+/*
+ Forget current user/db-level privileges and read new privileges
+ from the privilege tables.
+
+ SYNOPSIS
+ acl_reload()
+ thd Current thread
+
+ NOTE
+ All tables of calling thread which were open and locked by LOCK TABLES
+ statement will be unlocked and closed.
+ This function is also used for initialization of structures responsible
+ for user/db-level privilege checking.
+
+ RETURN VALUE
+ FALSE Success
+ TRUE Failure
+*/
+
+my_bool acl_reload(THD *thd)
+{
+ TABLE_LIST tables[5];
+ DYNAMIC_ARRAY old_acl_hosts, old_acl_users, old_acl_dbs, old_acl_proxy_users;
+ HASH old_acl_roles, old_acl_roles_mappings;
+ MEM_ROOT old_mem;
+ my_bool return_val= TRUE;
+ DBUG_ENTER("acl_reload");
+
+ /*
+ To avoid deadlocks we should obtain table locks before
+ obtaining acl_cache->lock mutex.
+ */
+ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("host"), "host", TL_READ);
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("user"), "user", TL_READ);
+ tables[2].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("db"), "db", TL_READ);
+ tables[3].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("proxies_priv"),
+ "proxies_priv", TL_READ);
+ tables[4].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("roles_mapping"),
+ "roles_mapping", TL_READ);
+ tables[0].next_local= tables[0].next_global= tables + 1;
+ tables[1].next_local= tables[1].next_global= tables + 2;
+ tables[2].next_local= tables[2].next_global= tables + 3;
+ tables[3].next_local= tables[3].next_global= tables + 4;
+ tables[0].open_type= tables[1].open_type= tables[2].open_type=
+ tables[3].open_type= tables[4].open_type= OT_BASE_ONLY;
+ tables[0].open_strategy= tables[3].open_strategy=
+ tables[4].open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
+
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ /*
+ Execution might have been interrupted; only print the error message
+ if an error condition has been raised.
+ */
+ if (thd->get_stmt_da()->is_error())
+ sql_print_error("Fatal error: Can't open and lock privilege tables: %s",
+ thd->get_stmt_da()->message());
+ goto end;
+ }
+
+ acl_cache->clear(0);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ old_acl_hosts= acl_hosts;
+ old_acl_users= acl_users;
+ old_acl_roles= acl_roles;
+ old_acl_roles_mappings= acl_roles_mappings;
+ old_acl_proxy_users= acl_proxy_users;
+ old_acl_dbs= acl_dbs;
+ old_mem= acl_memroot;
+ delete_dynamic(&acl_wild_hosts);
+ my_hash_free(&acl_check_hosts);
+
+ if ((return_val= acl_load(thd, tables)))
+ { // Error. Revert to old list
+ DBUG_PRINT("error",("Reverting to old privileges"));
+ acl_free(); /* purecov: inspected */
+ acl_hosts= old_acl_hosts;
+ acl_users= old_acl_users;
+ acl_roles= old_acl_roles;
+ acl_roles_mappings= old_acl_roles_mappings;
+ acl_proxy_users= old_acl_proxy_users;
+ acl_dbs= old_acl_dbs;
+ acl_memroot= old_mem;
+ init_check_host();
+ }
+ else
+ {
+ my_hash_free(&old_acl_roles);
+ free_root(&old_mem,MYF(0));
+ delete_dynamic(&old_acl_hosts);
+ delete_dynamic_with_callback(&old_acl_users, (FREE_FUNC) free_acl_user);
+ delete_dynamic(&old_acl_proxy_users);
+ delete_dynamic(&old_acl_dbs);
+ my_hash_free(&old_acl_roles_mappings);
+ }
+ mysql_mutex_unlock(&acl_cache->lock);
+end:
+ close_mysql_tables(thd);
+ DBUG_RETURN(return_val);
+}
+
+/*
+ Get all access bits from table after fieldnr
+
+ IMPLEMENTATION
+ We know that the access privileges ends when there is no more fields
+ or the field is not an enum with two elements.
+
+ SYNOPSIS
+ get_access()
+ form an open table to read privileges from.
+ The record should be already read in table->record[0]
+ fieldnr number of the first privilege (that is ENUM('N','Y') field
+ next_field on return - number of the field next to the last ENUM
+ (unless next_field == 0)
+
+ RETURN VALUE
+ privilege mask
+*/
+
+static ulong get_access(TABLE *form, uint fieldnr, uint *next_field)
+{
+ ulong access_bits=0,bit;
+ char buff[2];
+ String res(buff,sizeof(buff),&my_charset_latin1);
+ Field **pos;
+
+ for (pos=form->field+fieldnr, bit=1;
+ *pos && (*pos)->real_type() == MYSQL_TYPE_ENUM &&
+ ((Field_enum*) (*pos))->typelib->count == 2 ;
+ pos++, fieldnr++, bit<<=1)
+ {
+ if (get_YN_as_bool(*pos))
+ access_bits|= bit;
+ }
+ if (next_field)
+ *next_field=fieldnr;
+ return access_bits;
+}
+
+/*
+ Check if a user entry in the user table is marked as being a role entry
+
+ IMPLEMENTATION
+ Access the coresponding column and check the coresponding ENUM of the form
+ ENUM('N', 'Y')
+
+ SYNOPSIS
+ check_is_role()
+ form an open table to read the entry from.
+ The record should be already read in table->record[0]
+
+ RETURN VALUE
+ TRUE if the user is marked as a role
+ FALSE otherwise
+*/
+
+static bool check_is_role(TABLE *form)
+{
+ char buff[2];
+ String res(buff, sizeof(buff), &my_charset_latin1);
+ /* Table version does not support roles */
+ if (form->s->fields <= ROLE_ASSIGN_COLUMN_IDX)
+ return FALSE;
+
+ return get_YN_as_bool(form->field[ROLE_ASSIGN_COLUMN_IDX]);
+}
+
+
+/*
+ Return a number which, if sorted 'desc', puts strings in this order:
+ no wildcards
+ wildcards
+ empty string
+*/
+
+static ulong get_sort(uint count,...)
+{
+ va_list args;
+ va_start(args,count);
+ ulong sort=0;
+
+ /* Should not use this function with more than 4 arguments for compare. */
+ DBUG_ASSERT(count <= 4);
+
+ while (count--)
+ {
+ char *start, *str= va_arg(args,char*);
+ uint chars= 0;
+ uint wild_pos= 0; /* first wildcard position */
+
+ if ((start= str))
+ {
+ for (; *str ; str++)
+ {
+ if (*str == wild_prefix && str[1])
+ str++;
+ else if (*str == wild_many || *str == wild_one)
+ {
+ wild_pos= (uint) (str - start) + 1;
+ break;
+ }
+ chars= 128; // Marker that chars existed
+ }
+ }
+ sort= (sort << 8) + (wild_pos ? MY_MIN(wild_pos, 127U) : chars);
+ }
+ va_end(args);
+ return sort;
+}
+
+
+static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b)
+{
+ if (a->sort > b->sort)
+ return -1;
+ if (a->sort < b->sort)
+ return 1;
+ return 0;
+}
+
+
+/*
+ Gets user credentials without authentication and resource limit checks.
+
+ SYNOPSIS
+ acl_getroot()
+ sctx Context which should be initialized
+ user user name
+ host host name
+ ip IP
+ db current data base name
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+bool acl_getroot(Security_context *sctx, char *user, char *host,
+ char *ip, char *db)
+{
+ int res= 1;
+ uint i;
+ ACL_USER *acl_user= 0;
+ DBUG_ENTER("acl_getroot");
+
+ DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', db: '%s'",
+ host, ip, user, db));
+ sctx->user= user;
+ sctx->host= host;
+ sctx->ip= ip;
+ sctx->host_or_ip= host ? host : (safe_str(ip));
+
+ if (!initialized)
+ {
+ /*
+ here if mysqld's been started with --skip-grant-tables option.
+ */
+ sctx->skip_grants();
+ DBUG_RETURN(FALSE);
+ }
+
+ mysql_mutex_lock(&acl_cache->lock);
+
+ sctx->master_access= 0;
+ sctx->db_access= 0;
+ *sctx->priv_user= *sctx->priv_host= *sctx->priv_role= 0;
+
+ if (host[0]) // User, not Role
+ {
+ acl_user= find_user_wild(host, user, ip);
+
+ if (acl_user)
+ {
+ res= 0;
+ for (i=0 ; i < acl_dbs.elements ; i++)
+ {
+ ACL_DB *acl_db= dynamic_element(&acl_dbs, i, ACL_DB*);
+ if (!acl_db->user ||
+ (user && user[0] && !strcmp(user, acl_db->user)))
+ {
+ if (compare_hostname(&acl_db->host, host, ip))
+ {
+ if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0)))
+ {
+ sctx->db_access= acl_db->access;
+ break;
+ }
+ }
+ }
+ }
+ sctx->master_access= acl_user->access;
+
+ if (acl_user->user.str)
+ strmake_buf(sctx->priv_user, user);
+
+ if (acl_user->host.hostname)
+ strmake_buf(sctx->priv_host, acl_user->host.hostname);
+ }
+ }
+ else // Role, not User
+ {
+ ACL_ROLE *acl_role= find_acl_role(user);
+ if (acl_role)
+ {
+ res= 0;
+ for (i=0 ; i < acl_dbs.elements ; i++)
+ {
+ ACL_DB *acl_db= dynamic_element(&acl_dbs, i, ACL_DB*);
+ if (!acl_db->user ||
+ (user && user[0] && !strcmp(user, acl_db->user)))
+ {
+ if (compare_hostname(&acl_db->host, "", ""))
+ {
+ if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0)))
+ {
+ sctx->db_access= acl_db->access;
+ break;
+ }
+ }
+ }
+ }
+ sctx->master_access= acl_role->access;
+
+ if (acl_role->user.str)
+ strmake_buf(sctx->priv_user, user);
+ sctx->priv_host[0]= 0;
+ }
+ }
+
+ mysql_mutex_unlock(&acl_cache->lock);
+ DBUG_RETURN(res);
+}
+
+int acl_check_setrole(THD *thd, char *rolename, ulonglong *access)
+{
+ ACL_ROLE *role;
+ ACL_USER_BASE *acl_user_base;
+ ACL_USER *UNINIT_VAR(acl_user);
+ bool is_granted= FALSE;
+ int result= 0;
+
+ /* clear role privileges */
+ mysql_mutex_lock(&acl_cache->lock);
+
+ if (!strcasecmp(rolename, "NONE"))
+ {
+ /* have to clear the privileges */
+ /* get the current user */
+ acl_user= find_user_exact(thd->security_ctx->priv_host,
+ thd->security_ctx->priv_user);
+ if (acl_user == NULL)
+ {
+ my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename);
+ result= -1;
+ }
+ else if (access)
+ *access= acl_user->access;
+
+ goto end;
+ }
+
+ role= find_acl_role(rolename);
+
+ /* According to SQL standard, the same error message must be presented */
+ if (role == NULL) {
+ my_error(ER_INVALID_ROLE, MYF(0), rolename);
+ result= -1;
+ goto end;
+ }
+
+ for (uint i=0 ; i < role->parent_grantee.elements ; i++)
+ {
+ acl_user_base= *(dynamic_element(&role->parent_grantee, i, ACL_USER_BASE**));
+ if (acl_user_base->flags & IS_ROLE)
+ continue;
+
+ acl_user= (ACL_USER *)acl_user_base;
+ /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */
+ if (acl_user->wild_eq(thd->security_ctx->priv_user,
+ thd->security_ctx->host, thd->security_ctx->ip))
+ {
+ is_granted= TRUE;
+ break;
+ }
+ }
+
+ /* According to SQL standard, the same error message must be presented */
+ if (!is_granted)
+ {
+ my_error(ER_INVALID_ROLE, MYF(0), rolename);
+ result= 1;
+ goto end;
+ }
+
+ if (access)
+ {
+ *access = acl_user->access | role->access;
+ }
+end:
+ mysql_mutex_unlock(&acl_cache->lock);
+ return result;
+}
+
+
+int acl_setrole(THD *thd, char *rolename, ulonglong access)
+{
+ /* merge the privileges */
+ Security_context *sctx= thd->security_ctx;
+ sctx->master_access= static_cast<ulong>(access);
+ if (thd->db)
+ sctx->db_access= acl_get(sctx->host, sctx->ip, sctx->user, thd->db, FALSE);
+
+ if (!strcasecmp(rolename, "NONE"))
+ {
+ thd->security_ctx->priv_role[0]= 0;
+ }
+ else
+ {
+ if (thd->db)
+ sctx->db_access|= acl_get("", "", rolename, thd->db, FALSE);
+ /* mark the current role */
+ strmake_buf(thd->security_ctx->priv_role, rolename);
+ }
+ return 0;
+}
+
+
+static uchar* check_get_key(ACL_USER *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length=buff->hostname_length;
+ return (uchar*) buff->host.hostname;
+}
+
+
+static void acl_update_role(const char *rolename, ulong privileges)
+{
+ ACL_ROLE *role= find_acl_role(rolename);
+ if (role)
+ role->initial_role_access= role->access= privileges;
+}
+
+
+static void acl_update_user(const char *user, const char *host,
+ const char *password, uint password_len,
+ enum SSL_type ssl_type,
+ const char *ssl_cipher,
+ const char *x509_issuer,
+ const char *x509_subject,
+ USER_RESOURCES *mqh,
+ ulong privileges,
+ const LEX_STRING *plugin,
+ const LEX_STRING *auth)
+{
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ for (uint i=0 ; i < acl_users.elements ; i++)
+ {
+ ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*);
+ if (acl_user->eq(user, host))
+ {
+ if (plugin->str[0])
+ {
+ acl_user->plugin= *plugin;
+ acl_user->auth_string.str= auth->str ?
+ strmake_root(&acl_memroot, auth->str, auth->length) : const_cast<char*>("");
+ acl_user->auth_string.length= auth->length;
+ if (fix_user_plugin_ptr(acl_user))
+ acl_user->plugin.str= strmake_root(&acl_memroot, plugin->str, plugin->length);
+ }
+ else
+ if (password[0])
+ {
+ acl_user->auth_string.str= strmake_root(&acl_memroot, password, password_len);
+ acl_user->auth_string.length= password_len;
+ set_user_salt(acl_user, password, password_len);
+ set_user_plugin(acl_user, password_len);
+ }
+ acl_user->access=privileges;
+ if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
+ acl_user->user_resource.questions=mqh->questions;
+ if (mqh->specified_limits & USER_RESOURCES::UPDATES_PER_HOUR)
+ acl_user->user_resource.updates=mqh->updates;
+ if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
+ acl_user->user_resource.conn_per_hour= mqh->conn_per_hour;
+ if (mqh->specified_limits & USER_RESOURCES::USER_CONNECTIONS)
+ acl_user->user_resource.user_conn= mqh->user_conn;
+ if (ssl_type != SSL_TYPE_NOT_SPECIFIED)
+ {
+ acl_user->ssl_type= ssl_type;
+ acl_user->ssl_cipher= (ssl_cipher ? strdup_root(&acl_memroot,ssl_cipher) :
+ 0);
+ acl_user->x509_issuer= (x509_issuer ? strdup_root(&acl_memroot,x509_issuer) :
+ 0);
+ acl_user->x509_subject= (x509_subject ?
+ strdup_root(&acl_memroot,x509_subject) : 0);
+ }
+ /* search complete: */
+ break;
+ }
+ }
+}
+
+
+static void acl_insert_role(const char *rolename, ulong privileges)
+{
+ ACL_ROLE *entry;
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+ entry= new (&acl_memroot) ACL_ROLE(rolename, privileges, &acl_memroot);
+ (void) my_init_dynamic_array(&entry->parent_grantee,
+ sizeof(ACL_USER_BASE *), 8, 8, MYF(0));
+ (void) my_init_dynamic_array(&entry->role_grants,sizeof(ACL_ROLE *),
+ 8, 8, MYF(0));
+
+ my_hash_insert(&acl_roles, (uchar *)entry);
+}
+
+
+static void acl_insert_user(const char *user, const char *host,
+ const char *password, uint password_len,
+ enum SSL_type ssl_type,
+ const char *ssl_cipher,
+ const char *x509_issuer,
+ const char *x509_subject,
+ USER_RESOURCES *mqh,
+ ulong privileges,
+ const LEX_STRING *plugin,
+ const LEX_STRING *auth)
+{
+ ACL_USER acl_user;
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ acl_user.user.str=*user ? strdup_root(&acl_memroot,user) : 0;
+ acl_user.user.length= strlen(user);
+ update_hostname(&acl_user.host, safe_strdup_root(&acl_memroot, host));
+ if (plugin->str[0])
+ {
+ acl_user.plugin= *plugin;
+ acl_user.auth_string.str= auth->str ?
+ strmake_root(&acl_memroot, auth->str, auth->length) : const_cast<char*>("");
+ acl_user.auth_string.length= auth->length;
+ if (fix_user_plugin_ptr(&acl_user))
+ acl_user.plugin.str= strmake_root(&acl_memroot, plugin->str, plugin->length);
+ }
+ else
+ {
+ acl_user.auth_string.str= strmake_root(&acl_memroot, password, password_len);
+ acl_user.auth_string.length= password_len;
+ set_user_salt(&acl_user, password, password_len);
+ set_user_plugin(&acl_user, password_len);
+ }
+
+ acl_user.flags= 0;
+ acl_user.access=privileges;
+ acl_user.user_resource = *mqh;
+ acl_user.sort=get_sort(2, acl_user.host.hostname, acl_user.user.str);
+ acl_user.hostname_length=(uint) strlen(host);
+ acl_user.ssl_type= (ssl_type != SSL_TYPE_NOT_SPECIFIED ?
+ ssl_type : SSL_TYPE_NONE);
+ acl_user.ssl_cipher= ssl_cipher ? strdup_root(&acl_memroot,ssl_cipher) : 0;
+ acl_user.x509_issuer= x509_issuer ? strdup_root(&acl_memroot,x509_issuer) : 0;
+ acl_user.x509_subject=x509_subject ? strdup_root(&acl_memroot,x509_subject) : 0;
+ (void) my_init_dynamic_array(&acl_user.role_grants, sizeof(ACL_USER *),
+ 8, 8, MYF(0));
+
+ (void) push_dynamic(&acl_users,(uchar*) &acl_user);
+ if (!acl_user.host.hostname ||
+ (acl_user.host.hostname[0] == wild_many && !acl_user.host.hostname[1]))
+ allow_all_hosts=1; // Anyone can connect /* purecov: tested */
+ my_qsort((uchar*) dynamic_element(&acl_users,0,ACL_USER*),acl_users.elements,
+ sizeof(ACL_USER),(qsort_cmp) acl_compare);
+
+ /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
+ rebuild_check_host();
+
+ /*
+ Rebuild every user's role_grants since 'acl_users' has been sorted
+ and old pointers to ACL_USER elements are no longer valid
+ */
+ rebuild_role_grants();
+}
+
+
+static void acl_update_db(const char *user, const char *host, const char *db,
+ ulong privileges)
+{
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ for (uint i=0 ; i < acl_dbs.elements ; i++)
+ {
+ ACL_DB *acl_db=dynamic_element(&acl_dbs,i,ACL_DB*);
+ if ((!acl_db->user && !user[0]) ||
+ (acl_db->user &&
+ !strcmp(user,acl_db->user)))
+ {
+ if ((!acl_db->host.hostname && !host[0]) ||
+ (acl_db->host.hostname &&
+ !strcmp(host, acl_db->host.hostname)))
+ {
+ if ((!acl_db->db && !db[0]) ||
+ (acl_db->db && !strcmp(db,acl_db->db)))
+
+ {
+ if (privileges)
+ {
+ acl_db->access= privileges;
+ acl_db->initial_access= acl_db->access;
+ }
+ else
+ delete_dynamic_element(&acl_dbs,i);
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ Insert a user/db/host combination into the global acl_cache
+
+ SYNOPSIS
+ acl_insert_db()
+ user User name
+ host Host name
+ db Database name
+ privileges Bitmap of privileges
+
+ NOTES
+ acl_cache->lock must be locked when calling this
+*/
+
+static void acl_insert_db(const char *user, const char *host, const char *db,
+ ulong privileges)
+{
+ ACL_DB acl_db;
+ mysql_mutex_assert_owner(&acl_cache->lock);
+ acl_db.user=strdup_root(&acl_memroot,user);
+ update_hostname(&acl_db.host, safe_strdup_root(&acl_memroot, host));
+ acl_db.db=strdup_root(&acl_memroot,db);
+ acl_db.initial_access= acl_db.access= privileges;
+ acl_db.sort=get_sort(3,acl_db.host.hostname,acl_db.db,acl_db.user);
+ (void) push_dynamic(&acl_dbs,(uchar*) &acl_db);
+ my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements,
+ sizeof(ACL_DB),(qsort_cmp) acl_compare);
+}
+
+
+/*
+ Get privilege for a host, user and db combination
+
+ as db_is_pattern changes the semantics of comparison,
+ acl_cache is not used if db_is_pattern is set.
+*/
+
+ulong acl_get(const char *host, const char *ip,
+ const char *user, const char *db, my_bool db_is_pattern)
+{
+ ulong host_access= ~(ulong)0, db_access= 0;
+ uint i;
+ size_t key_length;
+ char key[ACL_KEY_LENGTH],*tmp_db,*end;
+ acl_entry *entry;
+ DBUG_ENTER("acl_get");
+
+ tmp_db= strmov(strmov(key, safe_str(ip)) + 1, user) + 1;
+ end= strnmov(tmp_db, db, key + sizeof(key) - tmp_db);
+
+ if (end >= key + sizeof(key)) // db name was truncated
+ DBUG_RETURN(0); // no privileges for an invalid db name
+
+ if (lower_case_table_names)
+ {
+ my_casedn_str(files_charset_info, tmp_db);
+ db=tmp_db;
+ }
+ key_length= (size_t) (end-key);
+
+ mysql_mutex_lock(&acl_cache->lock);
+ if (!db_is_pattern && (entry=acl_cache->search((uchar*) key, key_length)))
+ {
+ db_access=entry->access;
+ mysql_mutex_unlock(&acl_cache->lock);
+ DBUG_PRINT("exit", ("access: 0x%lx", db_access));
+ DBUG_RETURN(db_access);
+ }
+
+ /*
+ Check if there are some access rights for database and user
+ */
+ for (i=0 ; i < acl_dbs.elements ; i++)
+ {
+ ACL_DB *acl_db=dynamic_element(&acl_dbs,i,ACL_DB*);
+ if (!acl_db->user || !strcmp(user,acl_db->user))
+ {
+ if (compare_hostname(&acl_db->host,host,ip))
+ {
+ if (!acl_db->db || !wild_compare(db,acl_db->db,db_is_pattern))
+ {
+ db_access=acl_db->access;
+ if (acl_db->host.hostname)
+ goto exit; // Fully specified. Take it
+ /* the host table is not used for roles */
+ if ((!host || !host[0]) && !acl_db->host.hostname && find_acl_role(user))
+ goto exit;
+ break; /* purecov: tested */
+ }
+ }
+ }
+ }
+ if (!db_access)
+ goto exit; // Can't be better
+
+ /*
+ No host specified for user. Get hostdata from host table
+ */
+ host_access=0; // Host must be found
+ for (i=0 ; i < acl_hosts.elements ; i++)
+ {
+ ACL_HOST *acl_host=dynamic_element(&acl_hosts,i,ACL_HOST*);
+ if (compare_hostname(&acl_host->host,host,ip))
+ {
+ if (!acl_host->db || !wild_compare(db,acl_host->db,db_is_pattern))
+ {
+ host_access=acl_host->access; // Fully specified. Take it
+ break;
+ }
+ }
+ }
+exit:
+ /* Save entry in cache for quick retrieval */
+ if (!db_is_pattern &&
+ (entry= (acl_entry*) malloc(sizeof(acl_entry)+key_length)))
+ {
+ entry->access=(db_access & host_access);
+ entry->length=key_length;
+ memcpy((uchar*) entry->key,key,key_length);
+ acl_cache->add(entry);
+ }
+ mysql_mutex_unlock(&acl_cache->lock);
+ DBUG_PRINT("exit", ("access: 0x%lx", db_access & host_access));
+ DBUG_RETURN(db_access & host_access);
+}
+
+/*
+ Check if there are any possible matching entries for this host
+
+ NOTES
+ All host names without wild cards are stored in a hash table,
+ entries with wildcards are stored in a dynamic array
+*/
+
+static void init_check_host(void)
+{
+ DBUG_ENTER("init_check_host");
+ (void) my_init_dynamic_array(&acl_wild_hosts,sizeof(struct acl_host_and_ip),
+ acl_users.elements, 1, MYF(0));
+ (void) my_hash_init(&acl_check_hosts,system_charset_info,
+ acl_users.elements, 0, 0,
+ (my_hash_get_key) check_get_key, 0, 0);
+ if (!allow_all_hosts)
+ {
+ for (uint i=0 ; i < acl_users.elements ; i++)
+ {
+ ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*);
+ if (strchr(acl_user->host.hostname,wild_many) ||
+ strchr(acl_user->host.hostname,wild_one) ||
+ acl_user->host.ip_mask)
+ { // Has wildcard
+ uint j;
+ for (j=0 ; j < acl_wild_hosts.elements ; j++)
+ { // Check if host already exists
+ acl_host_and_ip *acl=dynamic_element(&acl_wild_hosts,j,
+ acl_host_and_ip *);
+ if (!my_strcasecmp(system_charset_info,
+ acl_user->host.hostname, acl->hostname))
+ break; // already stored
+ }
+ if (j == acl_wild_hosts.elements) // If new
+ (void) push_dynamic(&acl_wild_hosts,(uchar*) &acl_user->host);
+ }
+ else if (!my_hash_search(&acl_check_hosts,(uchar*)
+ acl_user->host.hostname,
+ strlen(acl_user->host.hostname)))
+ {
+ if (my_hash_insert(&acl_check_hosts,(uchar*) acl_user))
+ { // End of memory
+ allow_all_hosts=1; // Should never happen
+ DBUG_VOID_RETURN;
+ }
+ }
+ }
+ }
+ freeze_size(&acl_wild_hosts);
+ freeze_size(&acl_check_hosts.array);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Rebuild lists used for checking of allowed hosts
+
+ We need to rebuild 'acl_check_hosts' and 'acl_wild_hosts' after adding,
+ dropping or renaming user, since they contain pointers to elements of
+ 'acl_user' array, which are invalidated by drop operation, and use
+ ACL_USER::host::hostname as a key, which is changed by rename.
+*/
+void rebuild_check_host(void)
+{
+ delete_dynamic(&acl_wild_hosts);
+ my_hash_free(&acl_check_hosts);
+ init_check_host();
+}
+
+/*
+ Reset a role role_grants dynamic array.
+ Also, the role's access bits are reset to the ones present in the table.
+*/
+static my_bool acl_role_reset_role_arrays(void *ptr,
+ void * not_used __attribute__((unused)))
+{
+ ACL_ROLE *role= (ACL_ROLE *)ptr;
+ reset_dynamic(&role->role_grants);
+ reset_dynamic(&role->parent_grantee);
+ role->counter= 0;
+ return 0;
+}
+
+/*
+ Add a the coresponding pointers present in the mapping to the entries in
+ acl_users and acl_roles
+*/
+static bool add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role)
+{
+ return push_dynamic(&grantee->role_grants, (uchar*) &role)
+ || push_dynamic(&role->parent_grantee, (uchar*) &grantee);
+
+}
+
+/*
+ Revert the last add_role_user_mapping() action
+*/
+static void undo_add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role)
+{
+ void *pop __attribute__((unused));
+
+ pop= pop_dynamic(&grantee->role_grants);
+ DBUG_ASSERT(role == *(ACL_ROLE**)pop);
+
+ pop= pop_dynamic(&role->parent_grantee);
+ DBUG_ASSERT(grantee == *(ACL_USER_BASE**)pop);
+}
+
+/*
+ this helper is used when building role_grants and parent_grantee arrays
+ from scratch.
+
+ this happens either on initial loading of data from tables, in acl_load().
+ or in rebuild_role_grants after acl_role_reset_role_arrays().
+*/
+static bool add_role_user_mapping(const char *uname, const char *hname,
+ const char *rname)
+{
+ ACL_USER_BASE *grantee= find_acl_user_base(uname, hname);
+ ACL_ROLE *role= find_acl_role(rname);
+
+ if (grantee == NULL || role == NULL)
+ return 1;
+
+ /*
+ because all arrays are rebuilt completely, and counters were also reset,
+ we can increment them here, and after the rebuild all counters will
+ have correct values (equal to the number of roles granted).
+ */
+ if (grantee->flags & IS_ROLE)
+ ((ACL_ROLE*)grantee)->counter++;
+ return add_role_user_mapping(grantee, role);
+}
+
+/*
+ This helper function is used to removes roles and grantees
+ from the corresponding cross-reference arrays. see remove_role_user_mapping().
+ as such, it asserts that an element to delete is present in the array,
+ and is present only once.
+*/
+static void remove_ptr_from_dynarray(DYNAMIC_ARRAY *array, void *ptr)
+{
+ bool found __attribute__((unused))= false;
+ for (uint i= 0; i < array->elements; i++)
+ {
+ if (ptr == *dynamic_element(array, i, void**))
+ {
+ DBUG_ASSERT(!found);
+ delete_dynamic_element(array, i);
+ IF_DBUG(found= true, break);
+ }
+ }
+ DBUG_ASSERT(found);
+}
+
+static void remove_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role,
+ int grantee_idx=-1, int role_idx=-1)
+{
+ remove_ptr_from_dynarray(&grantee->role_grants, role);
+ remove_ptr_from_dynarray(&role->parent_grantee, grantee);
+}
+
+
+static my_bool add_role_user_mapping_action(void *ptr, void *unused __attribute__((unused)))
+{
+ ROLE_GRANT_PAIR *pair= (ROLE_GRANT_PAIR*)ptr;
+ bool status __attribute__((unused));
+ status= add_role_user_mapping(pair->u_uname, pair->u_hname, pair->r_uname);
+ /*
+ The invariant chosen is that acl_roles_mappings should _always_
+ only contain valid entries, referencing correct user and role grants.
+ If add_role_user_mapping detects an invalid entry, it will not add
+ the mapping into the ACL_USER::role_grants array.
+ */
+ DBUG_ASSERT(status == 0);
+ return 0;
+}
+
+
+/*
+ Rebuild the role grants every time the acl_users is modified
+
+ The role grants in the ACL_USER class need to be rebuilt, as they contain
+ pointers to elements of the acl_users array.
+*/
+
+static void rebuild_role_grants(void)
+{
+ DBUG_ENTER("rebuild_role_grants");
+ /*
+ Reset every user's and role's role_grants array
+ */
+ for (uint i=0; i < acl_users.elements; i++) {
+ ACL_USER *user= dynamic_element(&acl_users, i, ACL_USER *);
+ reset_dynamic(&user->role_grants);
+ }
+ my_hash_iterate(&acl_roles, acl_role_reset_role_arrays, NULL);
+
+ /* Rebuild the direct links between users and roles in ACL_USER::role_grants */
+ my_hash_iterate(&acl_roles_mappings, add_role_user_mapping_action, NULL);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/* Return true if there is no users that can match the given host */
+bool acl_check_host(const char *host, const char *ip)
+{
+ if (allow_all_hosts)
+ return 0;
+ mysql_mutex_lock(&acl_cache->lock);
+
+ if ((host && my_hash_search(&acl_check_hosts,(uchar*) host,strlen(host))) ||
+ (ip && my_hash_search(&acl_check_hosts,(uchar*) ip, strlen(ip))))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ return 0; // Found host
+ }
+ for (uint i=0 ; i < acl_wild_hosts.elements ; i++)
+ {
+ acl_host_and_ip *acl=dynamic_element(&acl_wild_hosts,i,acl_host_and_ip*);
+ if (compare_hostname(acl, host, ip))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ return 0; // Host ok
+ }
+ }
+ mysql_mutex_unlock(&acl_cache->lock);
+ if (ip != NULL)
+ {
+ /* Increment HOST_CACHE.COUNT_HOST_ACL_ERRORS. */
+ Host_errors errors;
+ errors.m_host_acl= 1;
+ inc_host_errors(ip, &errors);
+ }
+ return 1; // Host is not allowed
+}
+
+
+/**
+ Check if the user is allowed to change password
+
+ @param thd THD
+ @param host Hostname for the user
+ @param user User name
+ @param new_password New password
+ @param new_password_len The length of the new password
+
+ new_password cannot be NULL
+
+ @return Error status
+ @retval 0 OK
+ @retval 1 ERROR; In this case the error is sent to the client.
+*/
+
+int check_change_password(THD *thd, const char *host, const char *user,
+ char *new_password, uint new_password_len)
+{
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
+ return(1);
+ }
+ if (!thd->slave_thread && !thd->security_ctx->priv_user[0])
+ {
+ my_message(ER_PASSWORD_ANONYMOUS_USER, ER(ER_PASSWORD_ANONYMOUS_USER),
+ MYF(0));
+ return(1);
+ }
+ if (!host) // Role
+ {
+ my_error(ER_PASSWORD_NO_MATCH, MYF(0));
+ return 1;
+ }
+ if (!thd->slave_thread &&
+ (strcmp(thd->security_ctx->priv_user, user) ||
+ my_strcasecmp(system_charset_info, host,
+ thd->security_ctx->priv_host)))
+ {
+ if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0))
+ return(1);
+ }
+ size_t len= strlen(new_password);
+ if (len && len != SCRAMBLED_PASSWORD_CHAR_LENGTH &&
+ len != SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
+ {
+ my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH);
+ return -1;
+ }
+ return(0);
+}
+
+
+/**
+ Change a password for a user.
+
+ @param thd THD
+ @param host Hostname
+ @param user User name
+ @param new_password New password hash for host@user
+
+ @return Error code
+ @retval 0 ok
+ @retval 1 ERROR; In this case the error is sent to the client.
+*/
+bool change_password(THD *thd, const char *host, const char *user,
+ char *new_password)
+{
+ TABLE_LIST tables;
+ TABLE *table;
+ Rpl_filter *rpl_filter;
+ /* Buffer should be extended when password length is extended. */
+ char buff[512];
+ ulong query_length;
+ enum_binlog_format save_binlog_format;
+ uint new_password_len= (uint) strlen(new_password);
+ bool result= 1;
+ DBUG_ENTER("change_password");
+ DBUG_PRINT("enter",("host: '%s' user: '%s' new_password: '%s'",
+ host,user,new_password));
+ DBUG_ASSERT(host != 0); // Ensured by parent
+
+ if (check_change_password(thd, host, user, new_password, new_password_len))
+ DBUG_RETURN(1);
+
+ tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE);
+
+#ifdef HAVE_REPLICATION
+ /*
+ GRANT and REVOKE are applied the slave in/exclusion rules as they are
+ some kind of updates to the mysql.% tables.
+ */
+ if (thd->slave_thread &&
+ (rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter)->is_on())
+ {
+ /*
+ The tables must be marked "updating" so that tables_ok() takes them into
+ account in tests. It's ok to leave 'updating' set after tables_ok.
+ */
+ tables.updating= 1;
+ /* Thanks to bzero, tables.next==0 */
+ if (!(thd->spcont || rpl_filter->tables_ok(0, &tables)))
+ DBUG_RETURN(0);
+ }
+#endif
+ if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ DBUG_RETURN(1);
+
+ /*
+ This statement will be replicated as a statement, even when using
+ row-based replication. The flag will be reset at the end of the
+ statement.
+ This has to be handled here as it's called by set_var.cc, which is
+ not automaticly handled by sql_parse.cc
+ */
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ mysql_mutex_lock(&acl_cache->lock);
+ ACL_USER *acl_user;
+ if (!(acl_user= find_user_exact(host, user)))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0));
+ goto end;
+ }
+
+ /* update loaded acl entry: */
+ if (acl_user->plugin.str == native_password_plugin_name.str ||
+ acl_user->plugin.str == old_password_plugin_name.str)
+ {
+ acl_user->auth_string.str= strmake_root(&acl_memroot, new_password, new_password_len);
+ acl_user->auth_string.length= new_password_len;
+ set_user_salt(acl_user, new_password, new_password_len);
+ set_user_plugin(acl_user, new_password_len);
+ }
+ else
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SET_PASSWORD_AUTH_PLUGIN, ER(ER_SET_PASSWORD_AUTH_PLUGIN));
+
+ if (update_user_table(thd, table,
+ safe_str(acl_user->host.hostname),
+ safe_str(acl_user->user.str),
+ new_password, new_password_len))
+ {
+ mysql_mutex_unlock(&acl_cache->lock); /* purecov: deadcode */
+ goto end;
+ }
+
+ acl_cache->clear(1); // Clear locked hostname cache
+ mysql_mutex_unlock(&acl_cache->lock);
+ result= 0;
+ if (mysql_bin_log.is_open())
+ {
+ query_length=
+ sprintf(buff,"SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'",
+ safe_str(acl_user->user.str),
+ safe_str(acl_user->host.hostname),
+ new_password);
+ thd->clear_error();
+ result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length,
+ FALSE, FALSE, FALSE, 0);
+ }
+end:
+ close_mysql_tables(thd);
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Find user in ACL
+
+ SYNOPSIS
+ is_acl_user()
+ host host name
+ user user name
+
+ RETURN
+ FALSE user not fond
+ TRUE there is such user
+*/
+
+bool is_acl_user(const char *host, const char *user)
+{
+ bool res;
+
+ /* --skip-grants */
+ if (!initialized)
+ return TRUE;
+
+ mysql_mutex_lock(&acl_cache->lock);
+
+ if (*host) // User
+ res= find_user_exact(host, user) != NULL;
+ else // Role
+ res= find_acl_role(user) != NULL;
+
+ mysql_mutex_unlock(&acl_cache->lock);
+ return res;
+}
+
+
+/*
+ unlike find_user_exact and find_user_wild,
+ this function finds anonymous users too, it's when a
+ user is not empty, but priv_user (acl_user->user) is empty.
+*/
+static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip)
+{
+ ACL_USER *result= NULL;
+ mysql_mutex_assert_owner(&acl_cache->lock);
+ for (uint i=0; i < acl_users.elements; i++)
+ {
+ ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*);
+ if ((!acl_user_tmp->user.str ||
+ !strcmp(user, acl_user_tmp->user.str)) &&
+ compare_hostname(&acl_user_tmp->host, host, ip))
+ {
+ result= acl_user_tmp;
+ break;
+ }
+ }
+ return result;
+}
+
+
+/*
+ Find first entry that matches the specified user@host pair
+*/
+static ACL_USER * find_user_exact(const char *host, const char *user)
+{
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ for (uint i=0 ; i < acl_users.elements ; i++)
+ {
+ ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*);
+ if (acl_user->eq(user, host))
+ return acl_user;
+ }
+ return 0;
+}
+
+/*
+ Find first entry that matches the specified user@host pair
+*/
+static ACL_USER * find_user_wild(const char *host, const char *user, const char *ip)
+{
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ for (uint i=0 ; i < acl_users.elements ; i++)
+ {
+ ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*);
+ if (acl_user->wild_eq(user, host, ip))
+ return acl_user;
+ }
+ return 0;
+}
+
+/*
+ Find a role with the specified name
+*/
+static ACL_ROLE *find_acl_role(const char *role)
+{
+ DBUG_ENTER("find_acl_role");
+ DBUG_PRINT("enter",("role: '%s'", role));
+ DBUG_PRINT("info", ("Hash elements: %ld", acl_roles.records));
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ ACL_ROLE *r= (ACL_ROLE *)my_hash_search(&acl_roles, (uchar *)role,
+ role ? strlen(role) : 0);
+ DBUG_RETURN(r);
+}
+
+
+static ACL_USER_BASE *find_acl_user_base(const char *user, const char *host)
+{
+ if (*host)
+ return find_user_exact(host, user);
+
+ return find_acl_role(user);
+}
+
+
+/*
+ Comparing of hostnames
+
+ NOTES
+ A hostname may be of type:
+ hostname (May include wildcards); monty.pp.sci.fi
+ ip (May include wildcards); 192.168.0.0
+ ip/netmask 192.168.0.0/255.255.255.0
+
+ A net mask of 0.0.0.0 is not allowed.
+*/
+
+static const char *calc_ip(const char *ip, long *val, char end)
+{
+ long ip_val,tmp;
+ if (!(ip=str2int(ip,10,0,255,&ip_val)) || *ip != '.')
+ return 0;
+ ip_val<<=24;
+ if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != '.')
+ return 0;
+ ip_val+=tmp<<16;
+ if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != '.')
+ return 0;
+ ip_val+=tmp<<8;
+ if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != end)
+ return 0;
+ *val=ip_val+tmp;
+ return ip;
+}
+
+
+static void update_hostname(acl_host_and_ip *host, const char *hostname)
+{
+ // fix historical undocumented convention that empty host is the same as '%'
+ hostname=const_cast<char*>(hostname ? hostname : host_not_specified.str);
+ host->hostname=(char*) hostname; // This will not be modified!
+ if (!(hostname= calc_ip(hostname,&host->ip,'/')) ||
+ !(hostname= calc_ip(hostname+1,&host->ip_mask,'\0')))
+ {
+ host->ip= host->ip_mask=0; // Not a masked ip
+ }
+}
+
+
+static bool compare_hostname(const acl_host_and_ip *host, const char *hostname,
+ const char *ip)
+{
+ long tmp;
+ if (host->ip_mask && ip && calc_ip(ip,&tmp,'\0'))
+ {
+ return (tmp & host->ip_mask) == host->ip;
+ }
+ return (!host->hostname ||
+ (hostname && !wild_case_compare(system_charset_info,
+ hostname, host->hostname)) ||
+ (ip && !wild_compare(ip, host->hostname, 0)));
+}
+
+/**
+ Check if the given host name needs to be resolved or not.
+ Host name has to be resolved if it actually contains *name*.
+
+ For example:
+ 192.168.1.1 --> FALSE
+ 192.168.1.0/255.255.255.0 --> FALSE
+ % --> FALSE
+ 192.168.1.% --> FALSE
+ AB% --> FALSE
+
+ AAAAFFFF --> TRUE (Hostname)
+ AAAA:FFFF:1234:5678 --> FALSE
+ ::1 --> FALSE
+
+ This function does not check if the given string is a valid host name or
+ not. It assumes that the argument is a valid host name.
+
+ @param hostname the string to check.
+
+ @return a flag telling if the argument needs to be resolved or not.
+ @retval TRUE the argument is a host name and needs to be resolved.
+ @retval FALSE the argument is either an IP address, or a patter and
+ should not be resolved.
+*/
+
+bool hostname_requires_resolving(const char *hostname)
+{
+ if (!hostname)
+ return FALSE;
+
+ /* Check if hostname is the localhost. */
+
+ size_t hostname_len= strlen(hostname);
+ size_t localhost_len= strlen(my_localhost);
+
+ if (hostname == my_localhost ||
+ (hostname_len == localhost_len &&
+ !my_strnncoll(system_charset_info,
+ (const uchar *) hostname, hostname_len,
+ (const uchar *) my_localhost, strlen(my_localhost))))
+ {
+ return FALSE;
+ }
+
+ /*
+ If the string contains any of {':', '%', '_', '/'}, it is definitely
+ not a host name:
+ - ':' means that the string is an IPv6 address;
+ - '%' or '_' means that the string is a pattern;
+ - '/' means that the string is an IPv4 network address;
+ */
+
+ for (const char *p= hostname; *p; ++p)
+ {
+ switch (*p) {
+ case ':':
+ case '%':
+ case '_':
+ case '/':
+ return FALSE;
+ }
+ }
+
+ /*
+ Now we have to tell a host name (ab.cd, 12.ab) from an IPv4 address
+ (12.34.56.78). The assumption is that if the string contains only
+ digits and dots, it is an IPv4 address. Otherwise -- a host name.
+ */
+
+ for (const char *p= hostname; *p; ++p)
+ {
+ if (*p != '.' && !my_isdigit(&my_charset_latin1, *p))
+ return TRUE; /* a "letter" has been found. */
+ }
+
+ return FALSE; /* all characters are either dots or digits. */
+}
+
+
+/**
+ Update record for user in mysql.user privilege table with new password.
+
+ @param thd THD
+ @param table Pointer to TABLE object for open mysql.user table
+ @param host Hostname
+ @param user Username
+ @param new_password New password hash
+ @param new_password_len Length of new password hash
+
+ @see change_password
+*/
+
+static bool update_user_table(THD *thd, TABLE *table,
+ const char *host, const char *user,
+ const char *new_password, uint new_password_len)
+{
+ char user_key[MAX_KEY_LENGTH];
+ int error;
+ DBUG_ENTER("update_user_table");
+ DBUG_PRINT("enter",("user: %s host: %s",user,host));
+
+ table->use_all_columns();
+ table->field[0]->store(host,(uint) strlen(host), system_charset_info);
+ table->field[1]->store(user,(uint) strlen(user), system_charset_info);
+ key_copy((uchar *) user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ if (table->file->ha_index_read_idx_map(table->record[0], 0,
+ (uchar *) user_key, HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH),
+ MYF(0)); /* purecov: deadcode */
+ DBUG_RETURN(1); /* purecov: deadcode */
+ }
+ store_record(table,record[1]);
+ table->field[2]->store(new_password, new_password_len, system_charset_info);
+ if ((error=table->file->ha_update_row(table->record[1],table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: deadcode */
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Return 1 if we are allowed to create new users
+ the logic here is: INSERT_ACL is sufficient.
+ It's also a requirement in opt_safe_user_create,
+ otherwise CREATE_USER_ACL is enough.
+*/
+
+static bool test_if_create_new_users(THD *thd)
+{
+ Security_context *sctx= thd->security_ctx;
+ bool create_new_users= MY_TEST(sctx->master_access & INSERT_ACL) ||
+ (!opt_safe_user_create &&
+ MY_TEST(sctx->master_access & CREATE_USER_ACL));
+ if (!create_new_users)
+ {
+ TABLE_LIST tl;
+ ulong db_access;
+ tl.init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("user"), "user", TL_WRITE);
+ create_new_users= 1;
+
+ db_access=acl_get(sctx->host, sctx->ip,
+ sctx->priv_user, tl.db, 0);
+ if (sctx->priv_role[0])
+ db_access|= acl_get("", "", sctx->priv_role, tl.db, 0);
+ if (!(db_access & INSERT_ACL))
+ {
+ if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE))
+ create_new_users=0;
+ }
+ }
+ return create_new_users;
+}
+
+
+/****************************************************************************
+ Handle GRANT commands
+****************************************************************************/
+
+static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo,
+ ulong rights, bool revoke_grant,
+ bool can_create_user, bool no_auto_create)
+{
+ int error = -1;
+ bool old_row_exists=0;
+ char what= (revoke_grant) ? 'N' : 'Y';
+ uchar user_key[MAX_KEY_LENGTH];
+ bool handle_as_role= combo.is_role();
+ LEX *lex= thd->lex;
+ DBUG_ENTER("replace_user_table");
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ if (combo.password.str && combo.password.str[0])
+ {
+ if (combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH &&
+ combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
+ {
+ my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH);
+ DBUG_RETURN(-1);
+ }
+ }
+ else
+ combo.password= empty_lex_str;
+
+ /* if the user table is not up to date, we can't handle role updates */
+ if (table->s->fields <= ROLE_ASSIGN_COLUMN_IDX && handle_as_role)
+ {
+ my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0),
+ table->alias.c_ptr(), ROLE_ASSIGN_COLUMN_IDX + 1, table->s->fields,
+ static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID);
+ DBUG_RETURN(-1);
+ }
+
+ table->use_all_columns();
+ table->field[0]->store(combo.host.str,combo.host.length,
+ system_charset_info);
+ table->field[1]->store(combo.user.str,combo.user.length,
+ system_charset_info);
+ key_copy(user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ if (table->file->ha_index_read_idx_map(table->record[0], 0, user_key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ /* what == 'N' means revoke */
+ if (what == 'N')
+ {
+ my_error(ER_NONEXISTING_GRANT, MYF(0), combo.user.str, combo.host.str);
+ goto end;
+ }
+ /*
+ There are four options which affect the process of creation of
+ a new user (mysqld option --safe-create-user, 'insert' privilege
+ on 'mysql.user' table, using 'GRANT' with 'IDENTIFIED BY' and
+ SQL_MODE flag NO_AUTO_CREATE_USER). Below is the simplified rule
+ how it should work.
+ if (safe-user-create && ! INSERT_priv) => reject
+ else if (identified_by) => create
+ else if (no_auto_create_user) => reject
+ else create
+
+ see also test_if_create_new_users()
+ */
+ else if (!combo.password.length && !combo.plugin.length && no_auto_create)
+ {
+ my_error(ER_PASSWORD_NO_MATCH, MYF(0));
+ goto end;
+ }
+ else if (!can_create_user)
+ {
+ my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0));
+ goto end;
+ }
+ else if (combo.plugin.str[0])
+ {
+ if (!plugin_is_ready(&combo.plugin, MYSQL_AUTHENTICATION_PLUGIN))
+ {
+ my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), combo.plugin.str);
+ goto end;
+ }
+ }
+
+ old_row_exists = 0;
+ restore_record(table,s->default_values);
+ table->field[0]->store(combo.host.str,combo.host.length,
+ system_charset_info);
+ table->field[1]->store(combo.user.str,combo.user.length,
+ system_charset_info);
+ }
+ else
+ {
+ old_row_exists = 1;
+ store_record(table,record[1]); // Save copy for update
+ }
+
+ /* Update table columns with new privileges */
+
+ Field **tmp_field;
+ ulong priv;
+ uint next_field;
+ for (tmp_field= table->field+3, priv = SELECT_ACL;
+ *tmp_field && (*tmp_field)->real_type() == MYSQL_TYPE_ENUM &&
+ ((Field_enum*) (*tmp_field))->typelib->count == 2 ;
+ tmp_field++, priv <<= 1)
+ {
+ if (priv & rights) // set requested privileges
+ (*tmp_field)->store(&what, 1, &my_charset_latin1);
+ }
+ rights= get_access(table, 3, &next_field);
+ DBUG_PRINT("info",("table fields: %d",table->s->fields));
+ if (combo.password.str[0])
+ table->field[2]->store(combo.password.str, combo.password.length, system_charset_info);
+ if (table->s->fields >= 31) /* From 4.0.0 we have more fields */
+ {
+ /* We write down SSL related ACL stuff */
+ switch (lex->ssl_type) {
+ case SSL_TYPE_ANY:
+ table->field[next_field]->store(STRING_WITH_LEN("ANY"),
+ &my_charset_latin1);
+ table->field[next_field+1]->store("", 0, &my_charset_latin1);
+ table->field[next_field+2]->store("", 0, &my_charset_latin1);
+ table->field[next_field+3]->store("", 0, &my_charset_latin1);
+ break;
+ case SSL_TYPE_X509:
+ table->field[next_field]->store(STRING_WITH_LEN("X509"),
+ &my_charset_latin1);
+ table->field[next_field+1]->store("", 0, &my_charset_latin1);
+ table->field[next_field+2]->store("", 0, &my_charset_latin1);
+ table->field[next_field+3]->store("", 0, &my_charset_latin1);
+ break;
+ case SSL_TYPE_SPECIFIED:
+ table->field[next_field]->store(STRING_WITH_LEN("SPECIFIED"),
+ &my_charset_latin1);
+ table->field[next_field+1]->store("", 0, &my_charset_latin1);
+ table->field[next_field+2]->store("", 0, &my_charset_latin1);
+ table->field[next_field+3]->store("", 0, &my_charset_latin1);
+ if (lex->ssl_cipher)
+ table->field[next_field+1]->store(lex->ssl_cipher,
+ strlen(lex->ssl_cipher), system_charset_info);
+ if (lex->x509_issuer)
+ table->field[next_field+2]->store(lex->x509_issuer,
+ strlen(lex->x509_issuer), system_charset_info);
+ if (lex->x509_subject)
+ table->field[next_field+3]->store(lex->x509_subject,
+ strlen(lex->x509_subject), system_charset_info);
+ break;
+ case SSL_TYPE_NOT_SPECIFIED:
+ break;
+ case SSL_TYPE_NONE:
+ table->field[next_field]->store("", 0, &my_charset_latin1);
+ table->field[next_field+1]->store("", 0, &my_charset_latin1);
+ table->field[next_field+2]->store("", 0, &my_charset_latin1);
+ table->field[next_field+3]->store("", 0, &my_charset_latin1);
+ break;
+ }
+ next_field+=4;
+
+ USER_RESOURCES mqh= lex->mqh;
+ if (mqh.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
+ table->field[next_field]->store((longlong) mqh.questions, TRUE);
+ if (mqh.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR)
+ table->field[next_field+1]->store((longlong) mqh.updates, TRUE);
+ if (mqh.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
+ table->field[next_field+2]->store((longlong) mqh.conn_per_hour, TRUE);
+ if (table->s->fields >= 36 &&
+ (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS))
+ table->field[next_field+3]->store((longlong) mqh.user_conn, FALSE);
+ mqh_used= mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour;
+
+ next_field+= 4;
+ if (table->s->fields >= 41)
+ {
+ table->field[next_field]->set_notnull();
+ table->field[next_field + 1]->set_notnull();
+ if (combo.plugin.str[0])
+ {
+ DBUG_ASSERT(combo.password.str[0] == 0);
+ table->field[2]->reset();
+ table->field[next_field]->store(combo.plugin.str, combo.plugin.length,
+ system_charset_info);
+ table->field[next_field + 1]->store(combo.auth.str, combo.auth.length,
+ system_charset_info);
+ }
+ if (combo.password.str[0])
+ {
+ DBUG_ASSERT(combo.plugin.str[0] == 0);
+ table->field[next_field]->reset();
+ table->field[next_field + 1]->reset();
+ }
+ }
+
+ /* table format checked earlier */
+ if (handle_as_role)
+ {
+ if (old_row_exists && !check_is_role(table))
+ {
+ goto end;
+ }
+ table->field[ROLE_ASSIGN_COLUMN_IDX]->store("Y", 1, system_charset_info);
+ }
+ }
+
+ if (old_row_exists)
+ {
+ /*
+ We should NEVER delete from the user table, as a uses can still
+ use mysqld even if he doesn't have any privileges in the user table!
+ */
+ if (cmp_record(table,record[1]))
+ {
+ if ((error=
+ table->file->ha_update_row(table->record[1],table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ { // This should never happen
+ table->file->print_error(error,MYF(0)); /* purecov: deadcode */
+ error= -1; /* purecov: deadcode */
+ goto end; /* purecov: deadcode */
+ }
+ else
+ error= 0;
+ }
+ }
+ else if ((error=table->file->ha_write_row(table->record[0]))) // insert
+ { // This should never happen
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP))
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: deadcode */
+ error= -1; /* purecov: deadcode */
+ goto end; /* purecov: deadcode */
+ }
+ }
+ error=0; // Privileges granted / revoked
+
+end:
+ if (!error)
+ {
+ acl_cache->clear(1); // Clear privilege cache
+ if (old_row_exists)
+ {
+ if (handle_as_role)
+ acl_update_role(combo.user.str, rights);
+ else
+ acl_update_user(combo.user.str, combo.host.str,
+ combo.password.str, combo.password.length,
+ lex->ssl_type,
+ lex->ssl_cipher,
+ lex->x509_issuer,
+ lex->x509_subject,
+ &lex->mqh,
+ rights,
+ &combo.plugin,
+ &combo.auth);
+ }
+ else
+ {
+ if (handle_as_role)
+ acl_insert_role(combo.user.str, rights);
+ else
+ acl_insert_user(combo.user.str, combo.host.str,
+ combo.password.str, combo.password.length,
+ lex->ssl_type,
+ lex->ssl_cipher,
+ lex->x509_issuer,
+ lex->x509_subject,
+ &lex->mqh,
+ rights,
+ &combo.plugin,
+ &combo.auth);
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ change grants in the mysql.db table
+*/
+
+static int replace_db_table(TABLE *table, const char *db,
+ const LEX_USER &combo,
+ ulong rights, bool revoke_grant)
+{
+ uint i;
+ ulong priv,store_rights;
+ bool old_row_exists=0;
+ int error;
+ char what= (revoke_grant) ? 'N' : 'Y';
+ uchar user_key[MAX_KEY_LENGTH];
+ DBUG_ENTER("replace_db_table");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
+ DBUG_RETURN(-1);
+ }
+
+ /* Check if there is such a user in user table in memory? */
+ if (!find_user_wild(combo.host.str,combo.user.str))
+ {
+ /* The user could be a role, check if the user is registered as a role */
+ if (!combo.host.length && !find_acl_role(combo.user.str))
+ {
+ my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0));
+ DBUG_RETURN(-1);
+ }
+ }
+
+ table->use_all_columns();
+ table->field[0]->store(combo.host.str,combo.host.length,
+ system_charset_info);
+ table->field[1]->store(db,(uint) strlen(db), system_charset_info);
+ table->field[2]->store(combo.user.str,combo.user.length,
+ system_charset_info);
+ key_copy(user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ if (table->file->ha_index_read_idx_map(table->record[0],0, user_key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ if (what == 'N')
+ { // no row, no revoke
+ my_error(ER_NONEXISTING_GRANT, MYF(0), combo.user.str, combo.host.str);
+ goto abort;
+ }
+ old_row_exists = 0;
+ restore_record(table, s->default_values);
+ table->field[0]->store(combo.host.str,combo.host.length,
+ system_charset_info);
+ table->field[1]->store(db,(uint) strlen(db), system_charset_info);
+ table->field[2]->store(combo.user.str,combo.user.length,
+ system_charset_info);
+ }
+ else
+ {
+ old_row_exists = 1;
+ store_record(table,record[1]);
+ }
+
+ store_rights=get_rights_for_db(rights);
+ for (i= 3, priv= 1; i < table->s->fields; i++, priv <<= 1)
+ {
+ if (priv & store_rights) // do it if priv is chosen
+ table->field [i]->store(&what,1, &my_charset_latin1);// set requested privileges
+ }
+ rights=get_access(table,3);
+ rights=fix_rights_for_db(rights);
+
+ if (old_row_exists)
+ {
+ /* update old existing row */
+ if (rights)
+ {
+ if ((error= table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ goto table_error; /* purecov: deadcode */
+ }
+ else /* must have been a revoke of all privileges */
+ {
+ if ((error= table->file->ha_delete_row(table->record[1])))
+ goto table_error; /* purecov: deadcode */
+ }
+ }
+ else if (rights && (error= table->file->ha_write_row(table->record[0])))
+ {
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ goto table_error; /* purecov: deadcode */
+ }
+
+ acl_cache->clear(1); // Clear privilege cache
+ if (old_row_exists)
+ acl_update_db(combo.user.str,combo.host.str,db,rights);
+ else
+ if (rights)
+ acl_insert_db(combo.user.str,combo.host.str,db,rights);
+ DBUG_RETURN(0);
+
+ /* This could only happen if the grant tables got corrupted */
+table_error:
+ table->file->print_error(error,MYF(0)); /* purecov: deadcode */
+
+abort:
+ DBUG_RETURN(-1);
+}
+
+/**
+ Updates the mysql.roles_mapping table
+
+ @param table TABLE to update
+ @param user user name of the grantee
+ @param host host name of the grantee
+ @param role role name to grant
+ @param with_admin WITH ADMIN OPTION flag
+ @param existing the entry in the acl_roles_mappings hash or NULL.
+ it is never NULL if revoke_grant is true.
+ it is NULL when a new pair is added, it's not NULL
+ when an existing pair is updated.
+ @param revoke_grant true for REVOKE, false for GRANT
+*/
+static int
+replace_roles_mapping_table(TABLE *table, LEX_STRING *user, LEX_STRING *host,
+ LEX_STRING *role, bool with_admin,
+ ROLE_GRANT_PAIR *existing, bool revoke_grant)
+{
+ DBUG_ENTER("replace_roles_mapping_table");
+
+ uchar row_key[MAX_KEY_LENGTH];
+ int error;
+ table->use_all_columns();
+ restore_record(table, s->default_values);
+ table->field[0]->store(host->str, host->length, system_charset_info);
+ table->field[1]->store(user->str, user->length, system_charset_info);
+ table->field[2]->store(role->str, role->length, system_charset_info);
+
+ DBUG_ASSERT(!revoke_grant || existing);
+
+ if (existing) // delete or update
+ {
+ key_copy(row_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+ if (table->file->ha_index_read_idx_map(table->record[1], 0, row_key,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT))
+ {
+ /* No match */
+ DBUG_RETURN(1);
+ }
+ if (revoke_grant && !with_admin)
+ {
+ if ((error= table->file->ha_delete_row(table->record[1])))
+ {
+ DBUG_PRINT("info", ("error deleting row '%s' '%s' '%s'",
+ host->str, user->str, role->str));
+ goto table_error;
+ }
+ }
+ else if (with_admin)
+ {
+ table->field[3]->store(!revoke_grant + 1);
+
+ if ((error= table->file->ha_update_row(table->record[1], table->record[0])))
+ {
+ DBUG_PRINT("info", ("error updating row '%s' '%s' '%s'",
+ host->str, user->str, role->str));
+ goto table_error;
+ }
+ }
+ DBUG_RETURN(0);
+ }
+
+ table->field[3]->store(with_admin + 1);
+
+ if ((error= table->file->ha_write_row(table->record[0])))
+ {
+ DBUG_PRINT("info", ("error inserting row '%s' '%s' '%s'",
+ host->str, user->str, role->str));
+ goto table_error;
+ }
+
+ /* all ok */
+ DBUG_RETURN(0);
+
+table_error:
+ DBUG_PRINT("info", ("table error"));
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Updates the acl_roles_mappings hash
+
+ @param user user name of the grantee
+ @param host host name of the grantee
+ @param role role name to grant
+ @param with_admin WITH ADMIN OPTION flag
+ @param existing the entry in the acl_roles_mappings hash or NULL.
+ it is never NULL if revoke_grant is true.
+ it is NULL when a new pair is added, it's not NULL
+ when an existing pair is updated.
+ @param revoke_grant true for REVOKE, false for GRANT
+*/
+static int
+update_role_mapping(LEX_STRING *user, LEX_STRING *host, LEX_STRING *role,
+ bool with_admin, ROLE_GRANT_PAIR *existing, bool revoke_grant)
+{
+ if (revoke_grant)
+ {
+ if (with_admin)
+ {
+ existing->with_admin= false;
+ return 0;
+ }
+ return my_hash_delete(&acl_roles_mappings, (uchar*)existing);
+ }
+
+ if (existing)
+ {
+ existing->with_admin|= with_admin;
+ return 0;
+ }
+
+ /* allocate a new entry that will go in the hash */
+ ROLE_GRANT_PAIR *hash_entry= new (&acl_memroot) ROLE_GRANT_PAIR;
+ if (hash_entry->init(&acl_memroot, user->str, host->str,
+ role->str, with_admin))
+ return 1;
+ return my_hash_insert(&acl_roles_mappings, (uchar*) hash_entry);
+}
+
+static void
+acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke)
+{
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ DBUG_ENTER("acl_update_proxy_user");
+ for (uint i= 0; i < acl_proxy_users.elements; i++)
+ {
+ ACL_PROXY_USER *acl_user=
+ dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *);
+
+ if (acl_user->pk_equals(new_value))
+ {
+ if (is_revoke)
+ {
+ DBUG_PRINT("info", ("delting ACL_PROXY_USER"));
+ delete_dynamic_element(&acl_proxy_users, i);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("updating ACL_PROXY_USER"));
+ acl_user->set_data(new_value);
+ }
+ break;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+static void
+acl_insert_proxy_user(ACL_PROXY_USER *new_value)
+{
+ DBUG_ENTER("acl_insert_proxy_user");
+ mysql_mutex_assert_owner(&acl_cache->lock);
+ (void) push_dynamic(&acl_proxy_users, (uchar *) new_value);
+ my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER *),
+ acl_proxy_users.elements,
+ sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare);
+ DBUG_VOID_RETURN;
+}
+
+
+static int
+replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user,
+ const LEX_USER *proxied_user, bool with_grant_arg,
+ bool revoke_grant)
+{
+ bool old_row_exists= 0;
+ int error;
+ uchar user_key[MAX_KEY_LENGTH];
+ ACL_PROXY_USER new_grant;
+ char grantor[USER_HOST_BUFF_SIZE];
+
+ DBUG_ENTER("replace_proxies_priv_table");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
+ DBUG_RETURN(-1);
+ }
+
+ /* Check if there is such a user in user table in memory? */
+ if (!find_user_wild(user->host.str,user->user.str))
+ {
+ my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0));
+ DBUG_RETURN(-1);
+ }
+
+ table->use_all_columns();
+ ACL_PROXY_USER::store_pk (table, &user->host, &user->user,
+ &proxied_user->host, &proxied_user->user);
+
+ key_copy(user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ get_grantor(thd, grantor);
+
+ if ((error= table->file->ha_index_init(0, 1)))
+ {
+ table->file->print_error(error, MYF(0));
+ DBUG_PRINT("info", ("ha_index_init error"));
+ DBUG_RETURN(-1);
+ }
+
+ if (table->file->ha_index_read_map(table->record[0], user_key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ DBUG_PRINT ("info", ("Row not found"));
+ if (revoke_grant)
+ { // no row, no revoke
+ my_error(ER_NONEXISTING_GRANT, MYF(0), user->user.str, user->host.str);
+ goto abort;
+ }
+ old_row_exists= 0;
+ restore_record(table, s->default_values);
+ ACL_PROXY_USER::store_data_record(table, &user->host, &user->user,
+ &proxied_user->host,
+ &proxied_user->user,
+ with_grant_arg,
+ grantor);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Row found"));
+ old_row_exists= 1;
+ store_record(table, record[1]);
+ }
+
+ if (old_row_exists)
+ {
+ /* update old existing row */
+ if (!revoke_grant)
+ {
+ if ((error= table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ goto table_error; /* purecov: inspected */
+ }
+ else
+ {
+ if ((error= table->file->ha_delete_row(table->record[1])))
+ goto table_error; /* purecov: inspected */
+ }
+ }
+ else if ((error= table->file->ha_write_row(table->record[0])))
+ {
+ DBUG_PRINT("info", ("error inserting the row"));
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ goto table_error; /* purecov: inspected */
+ }
+
+ acl_cache->clear(1); // Clear privilege cache
+ if (old_row_exists)
+ {
+ new_grant.init(user->host.str, user->user.str,
+ proxied_user->host.str, proxied_user->user.str,
+ with_grant_arg);
+ acl_update_proxy_user(&new_grant, revoke_grant);
+ }
+ else
+ {
+ new_grant.init(&acl_memroot, user->host.str, user->user.str,
+ proxied_user->host.str, proxied_user->user.str,
+ with_grant_arg);
+ acl_insert_proxy_user(&new_grant);
+ }
+
+ table->file->ha_index_end();
+ DBUG_RETURN(0);
+
+ /* This could only happen if the grant tables got corrupted */
+table_error:
+ DBUG_PRINT("info", ("table error"));
+ table->file->print_error(error, MYF(0)); /* purecov: inspected */
+
+abort:
+ DBUG_PRINT("info", ("aborting replace_proxies_priv_table"));
+ table->file->ha_index_end();
+ DBUG_RETURN(-1);
+}
+
+
+class GRANT_COLUMN :public Sql_alloc
+{
+public:
+ char *column;
+ ulong rights;
+ ulong init_rights;
+ uint key_length;
+ GRANT_COLUMN(String &c, ulong y) :rights (y), init_rights(y)
+ {
+ column= (char*) memdup_root(&grant_memroot,c.ptr(), key_length=c.length());
+ }
+
+ /* this constructor assumes thas source->column is allocated in grant_memroot */
+ GRANT_COLUMN(GRANT_COLUMN *source) : column(source->column),
+ rights (source->rights), init_rights(0), key_length(source->key_length) { }
+};
+
+
+static uchar* get_key_column(GRANT_COLUMN *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length=buff->key_length;
+ return (uchar*) buff->column;
+}
+
+class GRANT_NAME :public Sql_alloc
+{
+public:
+ acl_host_and_ip host;
+ char *db, *user, *tname, *hash_key;
+ ulong privs;
+ ulong init_privs; /* privileges found in physical table */
+ ulong sort;
+ size_t key_length;
+ GRANT_NAME(const char *h, const char *d,const char *u,
+ const char *t, ulong p, bool is_routine);
+ GRANT_NAME (TABLE *form, bool is_routine);
+ virtual ~GRANT_NAME() {};
+ virtual bool ok() { return privs != 0; }
+ void set_user_details(const char *h, const char *d,
+ const char *u, const char *t,
+ bool is_routine);
+};
+
+
+class GRANT_TABLE :public GRANT_NAME
+{
+public:
+ ulong cols;
+ ulong init_cols; /* privileges found in physical table */
+ HASH hash_columns;
+
+ GRANT_TABLE(const char *h, const char *d,const char *u,
+ const char *t, ulong p, ulong c);
+ GRANT_TABLE (TABLE *form, TABLE *col_privs);
+ ~GRANT_TABLE();
+ bool ok() { return privs != 0 || cols != 0; }
+ void init_hash()
+ {
+ my_hash_init2(&hash_columns, 4, system_charset_info, 0, 0, 0,
+ (my_hash_get_key) get_key_column, 0, 0, 0);
+ }
+};
+
+
+void GRANT_NAME::set_user_details(const char *h, const char *d,
+ const char *u, const char *t,
+ bool is_routine)
+{
+ /* Host given by user */
+ update_hostname(&host, strdup_root(&grant_memroot, h));
+ if (db != d)
+ {
+ db= strdup_root(&grant_memroot, d);
+ if (lower_case_table_names)
+ my_casedn_str(files_charset_info, db);
+ }
+ user = strdup_root(&grant_memroot,u);
+ sort= get_sort(3,host.hostname,db,user);
+ if (tname != t)
+ {
+ tname= strdup_root(&grant_memroot, t);
+ if (lower_case_table_names || is_routine)
+ my_casedn_str(files_charset_info, tname);
+ }
+ key_length= strlen(d) + strlen(u)+ strlen(t)+3;
+ hash_key= (char*) alloc_root(&grant_memroot,key_length);
+ strmov(strmov(strmov(hash_key,user)+1,db)+1,tname);
+}
+
+GRANT_NAME::GRANT_NAME(const char *h, const char *d,const char *u,
+ const char *t, ulong p, bool is_routine)
+ :db(0), tname(0), privs(p), init_privs(p)
+{
+ set_user_details(h, d, u, t, is_routine);
+}
+
+GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u,
+ const char *t, ulong p, ulong c)
+ :GRANT_NAME(h,d,u,t,p, FALSE), cols(c)
+{
+ init_hash();
+}
+
+/*
+ create a new GRANT_TABLE entry for role inheritance. init_* fields are set
+ to 0
+*/
+GRANT_NAME::GRANT_NAME(TABLE *form, bool is_routine)
+{
+ user= safe_str(get_field(&grant_memroot,form->field[2]));
+
+ const char *hostname= get_field(&grant_memroot, form->field[0]);
+ mysql_mutex_lock(&acl_cache->lock);
+ if (!hostname && find_acl_role(user))
+ hostname= "";
+ mysql_mutex_unlock(&acl_cache->lock);
+ update_hostname(&host, hostname);
+
+ db= get_field(&grant_memroot,form->field[1]);
+ sort= get_sort(3, host.hostname, db, user);
+ tname= get_field(&grant_memroot,form->field[3]);
+ if (!db || !tname)
+ {
+ /* Wrong table row; Ignore it */
+ privs= 0;
+ return; /* purecov: inspected */
+ }
+ if (lower_case_table_names)
+ {
+ my_casedn_str(files_charset_info, db);
+ }
+ if (lower_case_table_names || is_routine)
+ {
+ my_casedn_str(files_charset_info, tname);
+ }
+ key_length= (strlen(db) + strlen(user) + strlen(tname) + 3);
+ hash_key= (char*) alloc_root(&grant_memroot, key_length);
+ strmov(strmov(strmov(hash_key,user)+1,db)+1,tname);
+ privs = (ulong) form->field[6]->val_int();
+ privs = fix_rights_for_table(privs);
+ init_privs= privs;
+}
+
+
+GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs)
+ :GRANT_NAME(form, FALSE)
+{
+ uchar key[MAX_KEY_LENGTH];
+
+ if (!db || !tname)
+ {
+ /* Wrong table row; Ignore it */
+ my_hash_clear(&hash_columns); /* allow for destruction */
+ cols= 0;
+ return;
+ }
+ cols= (ulong) form->field[7]->val_int();
+ cols= fix_rights_for_column(cols);
+ /*
+ Initial columns privileges are the same as column privileges on creation.
+ In case of roles, the cols privilege bits can get inherited and thus
+ cause the cols field to change. The init_cols field is always the same
+ as the physical table entry
+ */
+ init_cols= cols;
+
+ init_hash();
+
+ if (cols)
+ {
+ uint key_prefix_len;
+ KEY_PART_INFO *key_part= col_privs->key_info->key_part;
+ col_privs->field[0]->store(host.hostname,
+ host.hostname ? (uint) strlen(host.hostname) :
+ 0,
+ system_charset_info);
+ col_privs->field[1]->store(db,(uint) strlen(db), system_charset_info);
+ col_privs->field[2]->store(user,(uint) strlen(user), system_charset_info);
+ col_privs->field[3]->store(tname,(uint) strlen(tname), system_charset_info);
+
+ key_prefix_len= (key_part[0].store_length +
+ key_part[1].store_length +
+ key_part[2].store_length +
+ key_part[3].store_length);
+ key_copy(key, col_privs->record[0], col_privs->key_info, key_prefix_len);
+ col_privs->field[4]->store("",0, &my_charset_latin1);
+
+ if (col_privs->file->ha_index_init(0, 1))
+ {
+ cols= 0;
+ init_cols= 0;
+ return;
+ }
+
+ if (col_privs->file->ha_index_read_map(col_privs->record[0], (uchar*) key,
+ (key_part_map)15,
+ HA_READ_KEY_EXACT))
+ {
+ cols= 0; /* purecov: deadcode */
+ init_cols= 0;
+ col_privs->file->ha_index_end();
+ return;
+ }
+ do
+ {
+ String *res,column_name;
+ GRANT_COLUMN *mem_check;
+ /* As column name is a string, we don't have to supply a buffer */
+ res=col_privs->field[4]->val_str(&column_name);
+ ulong priv= (ulong) col_privs->field[6]->val_int();
+ if (!(mem_check = new GRANT_COLUMN(*res,
+ fix_rights_for_column(priv))))
+ {
+ /* Don't use this entry */
+ privs= cols= init_privs= init_cols=0; /* purecov: deadcode */
+ return; /* purecov: deadcode */
+ }
+ if (my_hash_insert(&hash_columns, (uchar *) mem_check))
+ {
+ /* Invalidate this entry */
+ privs= cols= init_privs= init_cols=0;
+ return;
+ }
+ } while (!col_privs->file->ha_index_next(col_privs->record[0]) &&
+ !key_cmp_if_same(col_privs,key,0,key_prefix_len));
+ col_privs->file->ha_index_end();
+ }
+}
+
+
+GRANT_TABLE::~GRANT_TABLE()
+{
+ my_hash_free(&hash_columns);
+}
+
+
+static uchar* get_grant_table(GRANT_NAME *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length=buff->key_length;
+ return (uchar*) buff->hash_key;
+}
+
+
+static void free_grant_table(GRANT_TABLE *grant_table)
+{
+ grant_table->~GRANT_TABLE();
+}
+
+
+/* Search after a matching grant. Prefer exact grants before not exact ones */
+
+static GRANT_NAME *name_hash_search(HASH *name_hash,
+ const char *host,const char* ip,
+ const char *db,
+ const char *user, const char *tname,
+ bool exact, bool name_tolower)
+{
+ char helping[SAFE_NAME_LEN*2+USERNAME_LENGTH+3];
+ char *hend = helping + sizeof(helping);
+ uint len;
+ GRANT_NAME *grant_name,*found=0;
+ HASH_SEARCH_STATE state;
+
+ char *db_ptr= strmov(helping, user) + 1;
+ char *tname_ptr= strnmov(db_ptr, db, hend - db_ptr) + 1;
+ if (tname_ptr > hend)
+ return 0; // invalid name = not found
+ char *end= strnmov(tname_ptr, tname, hend - tname_ptr) + 1;
+ if (end > hend)
+ return 0; // invalid name = not found
+
+ len = (uint) (end - helping);
+ if (name_tolower)
+ my_casedn_str(files_charset_info, tname_ptr);
+ for (grant_name= (GRANT_NAME*) my_hash_first(name_hash, (uchar*) helping,
+ len, &state);
+ grant_name ;
+ grant_name= (GRANT_NAME*) my_hash_next(name_hash,(uchar*) helping,
+ len, &state))
+ {
+ if (exact)
+ {
+ if (!grant_name->host.hostname ||
+ (host &&
+ !my_strcasecmp(system_charset_info, host,
+ grant_name->host.hostname)) ||
+ (ip && !strcmp(ip, grant_name->host.hostname)))
+ return grant_name;
+ }
+ else
+ {
+ if (compare_hostname(&grant_name->host, host, ip) &&
+ (!found || found->sort < grant_name->sort))
+ found=grant_name; // Host ok
+ }
+ }
+ return found;
+}
+
+
+static GRANT_NAME *
+routine_hash_search(const char *host, const char *ip, const char *db,
+ const char *user, const char *tname, bool proc, bool exact)
+{
+ return (GRANT_TABLE*)
+ name_hash_search(proc ? &proc_priv_hash : &func_priv_hash,
+ host, ip, db, user, tname, exact, TRUE);
+}
+
+
+static GRANT_TABLE *
+table_hash_search(const char *host, const char *ip, const char *db,
+ const char *user, const char *tname, bool exact)
+{
+ return (GRANT_TABLE*) name_hash_search(&column_priv_hash, host, ip, db,
+ user, tname, exact, FALSE);
+}
+
+
+static GRANT_COLUMN *
+column_hash_search(GRANT_TABLE *t, const char *cname, uint length)
+{
+ return (GRANT_COLUMN*) my_hash_search(&t->hash_columns,
+ (uchar*) cname, length);
+}
+
+
+static int replace_column_table(GRANT_TABLE *g_t,
+ TABLE *table, const LEX_USER &combo,
+ List <LEX_COLUMN> &columns,
+ const char *db, const char *table_name,
+ ulong rights, bool revoke_grant)
+{
+ int result=0;
+ uchar key[MAX_KEY_LENGTH];
+ uint key_prefix_length;
+ KEY_PART_INFO *key_part= table->key_info->key_part;
+ DBUG_ENTER("replace_column_table");
+
+ table->use_all_columns();
+ table->field[0]->store(combo.host.str,combo.host.length,
+ system_charset_info);
+ table->field[1]->store(db,(uint) strlen(db),
+ system_charset_info);
+ table->field[2]->store(combo.user.str,combo.user.length,
+ system_charset_info);
+ table->field[3]->store(table_name,(uint) strlen(table_name),
+ system_charset_info);
+
+ /* Get length of 4 first key parts */
+ key_prefix_length= (key_part[0].store_length + key_part[1].store_length +
+ key_part[2].store_length + key_part[3].store_length);
+ key_copy(key, table->record[0], table->key_info, key_prefix_length);
+
+ rights&= COL_ACLS; // Only ACL for columns
+
+ /* first fix privileges for all columns in column list */
+
+ List_iterator <LEX_COLUMN> iter(columns);
+ class LEX_COLUMN *column;
+ int error= table->file->ha_index_init(0, 1);
+ if (error)
+ {
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(-1);
+ }
+
+ while ((column= iter++))
+ {
+ ulong privileges= column->rights;
+ bool old_row_exists=0;
+ uchar user_key[MAX_KEY_LENGTH];
+
+ key_restore(table->record[0],key,table->key_info,
+ key_prefix_length);
+ table->field[4]->store(column->column.ptr(), column->column.length(),
+ system_charset_info);
+ /* Get key for the first 4 columns */
+ key_copy(user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ if (table->file->ha_index_read_map(table->record[0], user_key,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT))
+ {
+ if (revoke_grant)
+ {
+ my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0),
+ combo.user.str, combo.host.str,
+ table_name); /* purecov: inspected */
+ result= -1; /* purecov: inspected */
+ continue; /* purecov: inspected */
+ }
+ old_row_exists = 0;
+ restore_record(table, s->default_values); // Get empty record
+ key_restore(table->record[0],key,table->key_info,
+ key_prefix_length);
+ table->field[4]->store(column->column.ptr(),column->column.length(),
+ system_charset_info);
+ }
+ else
+ {
+ ulong tmp= (ulong) table->field[6]->val_int();
+ tmp=fix_rights_for_column(tmp);
+
+ if (revoke_grant)
+ privileges = tmp & ~(privileges | rights);
+ else
+ privileges |= tmp;
+ old_row_exists = 1;
+ store_record(table,record[1]); // copy original row
+ }
+
+ table->field[6]->store((longlong) get_rights_for_column(privileges), TRUE);
+
+ if (old_row_exists)
+ {
+ GRANT_COLUMN *grant_column;
+ if (privileges)
+ error=table->file->ha_update_row(table->record[1],table->record[0]);
+ else
+ error=table->file->ha_delete_row(table->record[1]);
+ if (error && error != HA_ERR_RECORD_IS_THE_SAME)
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ result= -1; /* purecov: inspected */
+ goto end; /* purecov: inspected */
+ }
+ else
+ error= 0;
+ grant_column= column_hash_search(g_t, column->column.ptr(),
+ column->column.length());
+ if (grant_column) // Should always be true
+ grant_column->rights= privileges; // Update hash
+ }
+ else // new grant
+ {
+ GRANT_COLUMN *grant_column;
+ if ((error=table->file->ha_write_row(table->record[0])))
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ result= -1; /* purecov: inspected */
+ goto end; /* purecov: inspected */
+ }
+ grant_column= new GRANT_COLUMN(column->column,privileges);
+ if (my_hash_insert(&g_t->hash_columns,(uchar*) grant_column))
+ {
+ result= -1;
+ goto end;
+ }
+ }
+ }
+
+ /*
+ If revoke of privileges on the table level, remove all such privileges
+ for all columns
+ */
+
+ if (revoke_grant)
+ {
+ uchar user_key[MAX_KEY_LENGTH];
+ key_copy(user_key, table->record[0], table->key_info,
+ key_prefix_length);
+
+ if (table->file->ha_index_read_map(table->record[0], user_key,
+ (key_part_map)15,
+ HA_READ_KEY_EXACT))
+ goto end;
+
+ /* Scan through all rows with the same host,db,user and table */
+ do
+ {
+ ulong privileges = (ulong) table->field[6]->val_int();
+ privileges=fix_rights_for_column(privileges);
+ store_record(table,record[1]);
+
+ if (privileges & rights) // is in this record the priv to be revoked ??
+ {
+ GRANT_COLUMN *grant_column = NULL;
+ char colum_name_buf[HOSTNAME_LENGTH+1];
+ String column_name(colum_name_buf,sizeof(colum_name_buf),
+ system_charset_info);
+
+ privileges&= ~rights;
+ table->field[6]->store((longlong)
+ get_rights_for_column(privileges), TRUE);
+ table->field[4]->val_str(&column_name);
+ grant_column = column_hash_search(g_t,
+ column_name.ptr(),
+ column_name.length());
+ if (privileges)
+ {
+ int tmp_error;
+ if ((tmp_error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ tmp_error != HA_ERR_RECORD_IS_THE_SAME)
+ { /* purecov: deadcode */
+ table->file->print_error(tmp_error,MYF(0)); /* purecov: deadcode */
+ result= -1; /* purecov: deadcode */
+ goto end; /* purecov: deadcode */
+ }
+ if (grant_column)
+ {
+ grant_column->rights = privileges; // Update hash
+ grant_column->init_rights = privileges;
+ }
+ }
+ else
+ {
+ int tmp_error;
+ if ((tmp_error = table->file->ha_delete_row(table->record[1])))
+ { /* purecov: deadcode */
+ table->file->print_error(tmp_error,MYF(0)); /* purecov: deadcode */
+ result= -1; /* purecov: deadcode */
+ goto end; /* purecov: deadcode */
+ }
+ if (grant_column)
+ my_hash_delete(&g_t->hash_columns,(uchar*) grant_column);
+ }
+ }
+ } while (!table->file->ha_index_next(table->record[0]) &&
+ !key_cmp_if_same(table, key, 0, key_prefix_length));
+ }
+
+end:
+ table->file->ha_index_end();
+ DBUG_RETURN(result);
+}
+
+static inline void get_grantor(THD *thd, char *grantor)
+{
+ const char *user= thd->security_ctx->user;
+ const char *host= thd->security_ctx->host_or_ip;
+
+#if defined(HAVE_REPLICATION)
+ if (thd->slave_thread && thd->has_invoker())
+ {
+ user= thd->get_invoker_user().str;
+ host= thd->get_invoker_host().str;
+ }
+#endif
+ strxmov(grantor, user, "@", host, NullS);
+}
+
+static int replace_table_table(THD *thd, GRANT_TABLE *grant_table,
+ TABLE *table, const LEX_USER &combo,
+ const char *db, const char *table_name,
+ ulong rights, ulong col_rights,
+ bool revoke_grant)
+{
+ char grantor[USER_HOST_BUFF_SIZE];
+ int old_row_exists = 1;
+ int error=0;
+ ulong store_table_rights, store_col_rights;
+ uchar user_key[MAX_KEY_LENGTH];
+ DBUG_ENTER("replace_table_table");
+
+ get_grantor(thd, grantor);
+ /*
+ The following should always succeed as new users are created before
+ this function is called!
+ */
+ if (!find_user_wild(combo.host.str,combo.user.str))
+ {
+ if (!combo.host.length && !find_acl_role(combo.user.str))
+ {
+ my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH),
+ MYF(0)); /* purecov: deadcode */
+ DBUG_RETURN(-1); /* purecov: deadcode */
+ }
+ }
+
+ table->use_all_columns();
+ restore_record(table, s->default_values); // Get empty record
+ table->field[0]->store(combo.host.str,combo.host.length,
+ system_charset_info);
+ table->field[1]->store(db,(uint) strlen(db), system_charset_info);
+ table->field[2]->store(combo.user.str,combo.user.length,
+ system_charset_info);
+ table->field[3]->store(table_name,(uint) strlen(table_name),
+ system_charset_info);
+ store_record(table,record[1]); // store at pos 1
+ key_copy(user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ if (table->file->ha_index_read_idx_map(table->record[0], 0, user_key,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ /*
+ The following should never happen as we first check the in memory
+ grant tables for the user. There is however always a small change that
+ the user has modified the grant tables directly.
+ */
+ if (revoke_grant)
+ { // no row, no revoke
+ my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0),
+ combo.user.str, combo.host.str,
+ table_name); /* purecov: deadcode */
+ DBUG_RETURN(-1); /* purecov: deadcode */
+ }
+ old_row_exists = 0;
+ restore_record(table,record[1]); // Get saved record
+ }
+
+ store_table_rights= get_rights_for_table(rights);
+ store_col_rights= get_rights_for_column(col_rights);
+ if (old_row_exists)
+ {
+ ulong j,k;
+ store_record(table,record[1]);
+ j = (ulong) table->field[6]->val_int();
+ k = (ulong) table->field[7]->val_int();
+
+ if (revoke_grant)
+ {
+ /* column rights are already fixed in mysql_table_grant */
+ store_table_rights=j & ~store_table_rights;
+ }
+ else
+ {
+ store_table_rights|= j;
+ store_col_rights|= k;
+ }
+ }
+
+ table->field[4]->store(grantor,(uint) strlen(grantor), system_charset_info);
+ table->field[6]->store((longlong) store_table_rights, TRUE);
+ table->field[7]->store((longlong) store_col_rights, TRUE);
+ rights=fix_rights_for_table(store_table_rights);
+ col_rights=fix_rights_for_column(store_col_rights);
+
+ if (old_row_exists)
+ {
+ if (store_table_rights || store_col_rights)
+ {
+ if ((error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ goto table_error; /* purecov: deadcode */
+ }
+ else if ((error = table->file->ha_delete_row(table->record[1])))
+ goto table_error; /* purecov: deadcode */
+ }
+ else
+ {
+ error=table->file->ha_write_row(table->record[0]);
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ goto table_error; /* purecov: deadcode */
+ }
+
+ if (rights | col_rights)
+ {
+ grant_table->init_privs= rights;
+ grant_table->init_cols= col_rights;
+
+ grant_table->privs= rights;
+ grant_table->cols= col_rights;
+ }
+ else
+ {
+ my_hash_delete(&column_priv_hash,(uchar*) grant_table);
+ }
+ DBUG_RETURN(0);
+
+ /* This should never happen */
+table_error:
+ table->file->print_error(error,MYF(0)); /* purecov: deadcode */
+ DBUG_RETURN(-1); /* purecov: deadcode */
+}
+
+
+/**
+ @retval 0 success
+ @retval -1 error
+*/
+static int replace_routine_table(THD *thd, GRANT_NAME *grant_name,
+ TABLE *table, const LEX_USER &combo,
+ const char *db, const char *routine_name,
+ bool is_proc, ulong rights, bool revoke_grant)
+{
+ char grantor[USER_HOST_BUFF_SIZE];
+ int old_row_exists= 1;
+ int error=0;
+ ulong store_proc_rights;
+ HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash;
+ DBUG_ENTER("replace_routine_table");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
+ DBUG_RETURN(-1);
+ }
+
+ if (revoke_grant && !grant_name->init_privs) // only inherited role privs
+ {
+ my_hash_delete(hash, (uchar*) grant_name);
+ DBUG_RETURN(0);
+ }
+
+ get_grantor(thd, grantor);
+ /*
+ New users are created before this function is called.
+
+ There may be some cases where a routine's definer is removed but the
+ routine remains.
+ */
+
+ table->use_all_columns();
+ restore_record(table, s->default_values); // Get empty record
+ table->field[0]->store(combo.host.str,combo.host.length, &my_charset_latin1);
+ table->field[1]->store(db,(uint) strlen(db), &my_charset_latin1);
+ table->field[2]->store(combo.user.str,combo.user.length, &my_charset_latin1);
+ table->field[3]->store(routine_name,(uint) strlen(routine_name),
+ &my_charset_latin1);
+ table->field[4]->store((longlong)(is_proc ?
+ TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION),
+ TRUE);
+ store_record(table,record[1]); // store at pos 1
+
+ if (table->file->ha_index_read_idx_map(table->record[0], 0,
+ (uchar*) table->field[0]->ptr,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ /*
+ The following should never happen as we first check the in memory
+ grant tables for the user. There is however always a small change that
+ the user has modified the grant tables directly.
+
+ Also, there is also a second posibility that this routine entry
+ is created for a role by being inherited from a granted role.
+ */
+ if (revoke_grant)
+ { // no row, no revoke
+ my_error(ER_NONEXISTING_PROC_GRANT, MYF(0),
+ combo.user.str, combo.host.str, routine_name);
+ DBUG_RETURN(-1);
+ }
+ old_row_exists= 0;
+ restore_record(table,record[1]); // Get saved record
+ }
+
+ store_proc_rights= get_rights_for_procedure(rights);
+ if (old_row_exists)
+ {
+ ulong j;
+ store_record(table,record[1]);
+ j= (ulong) table->field[6]->val_int();
+
+ if (revoke_grant)
+ {
+ /* column rights are already fixed in mysql_table_grant */
+ store_proc_rights=j & ~store_proc_rights;
+ }
+ else
+ {
+ store_proc_rights|= j;
+ }
+ }
+
+ table->field[5]->store(grantor,(uint) strlen(grantor), &my_charset_latin1);
+ table->field[6]->store((longlong) store_proc_rights, TRUE);
+ rights=fix_rights_for_procedure(store_proc_rights);
+
+ if (old_row_exists)
+ {
+ if (store_proc_rights)
+ {
+ if ((error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ goto table_error;
+ }
+ else if ((error= table->file->ha_delete_row(table->record[1])))
+ goto table_error;
+ }
+ else
+ {
+ error=table->file->ha_write_row(table->record[0]);
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ goto table_error;
+ }
+
+ if (rights)
+ {
+ grant_name->init_privs= rights;
+ grant_name->privs= rights;
+ }
+ else
+ {
+ my_hash_delete(hash, (uchar*) grant_name);
+ }
+ DBUG_RETURN(0);
+
+ /* This should never happen */
+table_error:
+ table->file->print_error(error,MYF(0));
+ DBUG_RETURN(-1);
+}
+
+
+/*****************************************************************
+ Role privilege propagation and graph traversal functionality
+
+ According to the SQL standard, a role can be granted to a role,
+ thus role grants can create an arbitrarily complex directed acyclic
+ graph (a standard explicitly specifies that cycles are not allowed).
+
+ When a privilege is granted to a role, it becomes available to all grantees.
+ The code below recursively traverses a DAG of role grants, propagating
+ privilege changes.
+
+ The traversal function can work both ways, from roles to grantees or
+ from grantees to roles. The first is used for privilege propagation,
+ the second - for SHOW GRANTS and I_S.APPLICABLE_ROLES
+
+ The role propagation code is smart enough to propagate only privilege
+ changes to one specific database, table, or routine, if only they
+ were changed (like in GRANT ... ON ... TO ...) or it can propagate
+ everything (on startup or after FLUSH PRIVILEGES).
+
+ It traverses only a subgraph that's accessible from the modified role,
+ only visiting roles that can be possibly affected by the GRANT statement.
+
+ Additionally, it stops traversal early, if this particular GRANT statement
+ didn't result in any changes of privileges (e.g. both role1 and role2
+ are granted to the role3, both role1 and role2 have SELECT privilege.
+ if SELECT is revoked from role1 it won't change role3 privileges,
+ so we won't traverse from role3 to its grantees).
+******************************************************************/
+struct PRIVS_TO_MERGE
+{
+ enum what { ALL, GLOBAL, DB, TABLE_COLUMN, PROC, FUNC } what;
+ const char *db, *name;
+};
+
+static int init_role_for_merging(ACL_ROLE *role, void *context)
+{
+ role->counter= 0;
+ return 0;
+}
+
+static int count_subgraph_nodes(ACL_ROLE *role, ACL_ROLE *grantee, void *context)
+{
+ grantee->counter++;
+ return 0;
+}
+
+static int merge_role_privileges(ACL_ROLE *, ACL_ROLE *, void *);
+
+/**
+ rebuild privileges of all affected roles
+
+ entry point into role privilege propagation. after privileges of the
+ 'role' were changed, this function rebuilds privileges of all affected roles
+ as necessary.
+*/
+static void propagate_role_grants(ACL_ROLE *role,
+ enum PRIVS_TO_MERGE::what what,
+ const char *db= 0, const char *name= 0)
+{
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+ PRIVS_TO_MERGE data= { what, db, name };
+
+ /*
+ Changing privileges of a role causes all other roles that had
+ this role granted to them to have their rights invalidated.
+
+ We need to rebuild all roles' related access bits.
+
+ This cannot be a simple depth-first search, instead we have to merge
+ privieges for all roles granted to a specific grantee, *before*
+ merging privileges for this grantee. In other words, we must visit all
+ parent nodes of a specific node, before descencing into this node.
+
+ For example, if role1 is granted to role2 and role3, and role3 is
+ granted to role2, after "GRANT ... role1", we cannot merge privileges
+ for role2, until role3 is merged. The counter will be 0 for role1, 2
+ for role2, 1 for role3. Traversal will start from role1, go to role2,
+ decrement the counter, backtrack, go to role3, merge it, go to role2
+ again, merge it.
+
+ And the counter is not just "all parent nodes", but only parent nodes
+ that are part of the subgraph we're interested in. For example, if
+ both roleA and roleB are granted to roleC, then roleC has two parent
+ nodes. But when granting a privilege to roleA, we're only looking at a
+ subgraph that includes roleA and roleC (roleB cannot be possibly
+ affected by that grant statement). In this subgraph roleC has only one
+ parent.
+
+ (on the other hand, in acl_load we want to update all roles, and
+ the counter is exactly equal to the number of all parent nodes)
+
+ Thus, we do two graph traversals here. First we only count parents
+ that are part of the subgraph. On the second traversal we decrement
+ the counter and actually merge privileges for a node when a counter
+ drops to zero.
+ */
+ traverse_role_graph_up(role, &data, init_role_for_merging, count_subgraph_nodes);
+ traverse_role_graph_up(role, &data, NULL, merge_role_privileges);
+}
+
+
+// State of a node during a Depth First Search exploration
+struct NODE_STATE
+{
+ ACL_USER_BASE *node_data; /* pointer to the node data */
+ uint neigh_idx; /* the neighbour that needs to be evaluated next */
+};
+
+/**
+ Traverse the role grant graph and invoke callbacks at the specified points.
+
+ @param user user or role to start traversal from
+ @param context opaque parameter to pass to callbacks
+ @param offset offset to ACL_ROLE::parent_grantee or to
+ ACL_USER_BASE::role_grants. Depending on this value,
+ traversal will go from roles to grantees or from
+ grantees to roles.
+ @param on_node called when a node is visited for the first time.
+ Returning a value <0 will abort the traversal.
+ @param on_edge called for every edge in the graph, when traversal
+ goes from a node to a neighbour node.
+ Returning <0 will abort the traversal. Returning >0
+ will make the traversal not to follow this edge.
+
+ @note
+ The traverse method is a DEPTH FIRST SEARCH, but callbacks can influence
+ that (on_edge returning >0 value).
+
+ @note
+ This function should not be called directly, use
+ traverse_role_graph_up() and traverse_role_graph_down() instead.
+
+ @retval 0 traversal finished successfully
+ @retval ROLE_CYCLE_FOUND traversal aborted, cycle detected
+ @retval <0 traversal was aborted, because a callback returned
+ this error code
+*/
+static int traverse_role_graph_impl(ACL_USER_BASE *user, void *context,
+ off_t offset,
+ int (*on_node) (ACL_USER_BASE *role, void *context),
+ int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context))
+{
+ DBUG_ENTER("traverse_role_graph_impl");
+ DBUG_ASSERT(user);
+ DBUG_PRINT("enter",("role: '%s'", user->user.str));
+ /*
+ The search operation should always leave the ROLE_ON_STACK and
+ ROLE_EXPLORED flags clean for all nodes involved in the search
+ */
+ DBUG_ASSERT(!(user->flags & ROLE_ON_STACK));
+ DBUG_ASSERT(!(user->flags & ROLE_EXPLORED));
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ /*
+ Stack used to simulate the recursive calls of DFS.
+ It uses a Dynamic_array to reduce the number of
+ malloc calls to a minimum
+ */
+ Dynamic_array<NODE_STATE> stack(20,50);
+ Dynamic_array<ACL_USER_BASE *> to_clear(20,50);
+ NODE_STATE state; /* variable used to insert elements in the stack */
+ int result= 0;
+
+ state.neigh_idx= 0;
+ state.node_data= user;
+ user->flags|= ROLE_ON_STACK;
+
+ stack.push(state);
+ to_clear.push(user);
+
+ user->flags|= ROLE_OPENED;
+ if (on_node && ((result= on_node(user, context)) < 0))
+ goto end;
+
+ while (stack.elements())
+ {
+ NODE_STATE *curr_state= stack.back();
+
+ DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK);
+
+ ACL_USER_BASE *current= curr_state->node_data;
+ ACL_USER_BASE *neighbour= NULL;
+ DBUG_PRINT("info", ("Examining role %s", current->user.str));
+ /*
+ Iterate through the neighbours until a first valid jump-to
+ neighbour is found
+ */
+ my_bool found= FALSE;
+ uint i;
+ DYNAMIC_ARRAY *array= (DYNAMIC_ARRAY *)(((char*)current) + offset);
+
+ DBUG_ASSERT(array == &current->role_grants || current->flags & IS_ROLE);
+ for (i= curr_state->neigh_idx; i < array->elements; i++)
+ {
+ neighbour= *(dynamic_element(array, i, ACL_ROLE**));
+ if (!(neighbour->flags & IS_ROLE))
+ continue;
+
+ DBUG_PRINT("info", ("Examining neighbour role %s", neighbour->user.str));
+
+ /* check if it forms a cycle */
+ if (neighbour->flags & ROLE_ON_STACK)
+ {
+ DBUG_PRINT("info", ("Found cycle"));
+ result= ROLE_CYCLE_FOUND;
+ goto end;
+ }
+
+ if (!(neighbour->flags & ROLE_OPENED))
+ {
+ neighbour->flags|= ROLE_OPENED;
+ to_clear.push(neighbour);
+ if (on_node && ((result= on_node(neighbour, context)) < 0))
+ goto end;
+ }
+
+ if (on_edge)
+ {
+ result= on_edge(current, (ACL_ROLE*)neighbour, context);
+ if (result < 0)
+ goto end;
+ if (result > 0)
+ continue;
+ }
+
+ /* Check if it was already explored, in that case, move on */
+ if (neighbour->flags & ROLE_EXPLORED)
+ continue;
+
+ found= TRUE;
+ break;
+ }
+
+ /* found states that we have found a node to jump next into */
+ if (found)
+ {
+ curr_state->neigh_idx= i + 1;
+
+ /* some sanity checks */
+ DBUG_ASSERT(!(neighbour->flags & ROLE_ON_STACK));
+
+ /* add the neighbour on the stack */
+ neighbour->flags|= ROLE_ON_STACK;
+ state.neigh_idx= 0;
+ state.node_data= neighbour;
+ stack.push(state);
+ }
+ else
+ {
+ /* Make sure we got a correct node */
+ DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK);
+ /* Finished with exploring the current node, pop it off the stack */
+ curr_state= &stack.pop();
+ curr_state->node_data->flags&= ~ROLE_ON_STACK; /* clear the on-stack bit */
+ curr_state->node_data->flags|= ROLE_EXPLORED;
+ }
+ }
+
+end:
+ /* Cleanup */
+ for (uint i= 0; i < to_clear.elements(); i++)
+ {
+ ACL_USER_BASE *current= to_clear.at(i);
+ DBUG_ASSERT(current->flags & (ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED));
+ current->flags&= ~(ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED);
+ }
+ DBUG_RETURN(result);
+}
+
+/**
+ Traverse the role grant graph, going from a role to its grantees.
+
+ This is used to propagate changes in privileges, for example,
+ when GRANT or REVOKE is issued for a role.
+*/
+
+static int traverse_role_graph_up(ACL_ROLE *role, void *context,
+ int (*on_node) (ACL_ROLE *role, void *context),
+ int (*on_edge) (ACL_ROLE *current, ACL_ROLE *neighbour, void *context))
+{
+ return traverse_role_graph_impl(role, context,
+ my_offsetof(ACL_ROLE, parent_grantee),
+ (int (*)(ACL_USER_BASE *, void *))on_node,
+ (int (*)(ACL_USER_BASE *, ACL_ROLE *, void *))on_edge);
+}
+
+/**
+ Traverse the role grant graph, going from a user or a role to granted roles.
+
+ This is used, for example, to print all grants available to a user or a role
+ (as in SHOW GRANTS).
+*/
+
+static int traverse_role_graph_down(ACL_USER_BASE *user, void *context,
+ int (*on_node) (ACL_USER_BASE *role, void *context),
+ int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context))
+{
+ return traverse_role_graph_impl(user, context,
+ my_offsetof(ACL_USER_BASE, role_grants),
+ on_node, on_edge);
+}
+
+/*
+ To find all db/table/routine privilege for a specific role
+ we need to scan the array of privileges. It can be big.
+ But the set of privileges granted to a role in question (or
+ to roles directly granted to the role in question) is supposedly
+ much smaller.
+
+ We put a role and all roles directly granted to it in a hash, and iterate
+ the (suposedly long) array of privileges, filtering out "interesting"
+ entries using the role hash. We put all these "interesting"
+ entries in a (suposedly small) dynamic array and them use it for merging.
+*/
+static uchar* role_key(const ACL_ROLE *role, size_t *klen, my_bool)
+{
+ *klen= role->user.length;
+ return (uchar*) role->user.str;
+}
+typedef Hash_set<ACL_ROLE> role_hash_t;
+
+static bool merge_role_global_privileges(ACL_ROLE *grantee)
+{
+ ulong old= grantee->access;
+ grantee->access= grantee->initial_role_access;
+
+ DBUG_EXECUTE_IF("role_merge_stats", role_global_merges++;);
+
+ for (uint i= 0; i < grantee->role_grants.elements; i++)
+ {
+ ACL_ROLE *r= *dynamic_element(&grantee->role_grants, i, ACL_ROLE**);
+ grantee->access|= r->access;
+ }
+ return old != grantee->access;
+}
+
+static int db_name_sort(ACL_DB * const *db1, ACL_DB * const *db2)
+{
+ return strcmp((*db1)->db, (*db2)->db);
+}
+
+/**
+ update ACL_DB for given database and a given role with merged privileges
+
+ @param merged ACL_DB of the role in question (or NULL if it wasn't found)
+ @param first first ACL_DB in an array for the database in question
+ @param access new privileges for the given role on the gived database
+ @param role the name of the given role
+
+ @return a bitmap of
+ 1 - privileges were changed
+ 2 - ACL_DB was added
+ 4 - ACL_DB was deleted
+*/
+static int update_role_db(ACL_DB *merged, ACL_DB **first, ulong access, char *role)
+{
+ if (!first)
+ return 0;
+
+ DBUG_EXECUTE_IF("role_merge_stats", role_db_merges++;);
+
+ if (merged == NULL)
+ {
+ /*
+ there's no ACL_DB for this role (all db grants come from granted roles)
+ we need to create it
+
+ Note that we cannot use acl_insert_db() now:
+ 1. it'll sort elements in the acl_dbs, so the pointers will become invalid
+ 2. we may need many of them, no need to sort every time
+ */
+ DBUG_ASSERT(access);
+ ACL_DB acl_db;
+ acl_db.user= role;
+ acl_db.host.hostname= const_cast<char*>("");
+ acl_db.host.ip= acl_db.host.ip_mask= 0;
+ acl_db.db= first[0]->db;
+ acl_db.access= access;
+ acl_db.initial_access= 0;
+ acl_db.sort=get_sort(3, "", acl_db.db, role);
+ push_dynamic(&acl_dbs,(uchar*) &acl_db);
+ return 2;
+ }
+ else if (access == 0)
+ {
+ /*
+ there is ACL_DB but the role has no db privileges granted
+ (all privileges were coming from granted roles, and now those roles
+ were dropped or had their privileges revoked).
+ we need to remove this ACL_DB entry
+
+ Note, that we cannot delete now:
+ 1. it'll shift elements in the acl_dbs, so the pointers will become invalid
+ 2. it's O(N) operation, and we may need many of them
+ so we only mark elements deleted and will delete later.
+ */
+ merged->sort= 0; // lower than any valid ACL_DB sort value, will be sorted last
+ return 4;
+ }
+ else if (merged->access != access)
+ {
+ /* this is easy */
+ merged->access= access;
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ merges db privileges from roles granted to the role 'grantee'.
+
+ @return true if database privileges of the 'grantee' were changed
+
+*/
+static bool merge_role_db_privileges(ACL_ROLE *grantee, const char *dbname,
+ role_hash_t *rhash)
+{
+ Dynamic_array<ACL_DB *> dbs;
+
+ /*
+ Supposedly acl_dbs can be huge, but only a handful of db grants
+ apply to grantee or roles directly granted to grantee.
+
+ Collect these applicable db grants.
+ */
+ for (uint i=0 ; i < acl_dbs.elements ; i++)
+ {
+ ACL_DB *db= dynamic_element(&acl_dbs,i,ACL_DB*);
+ if (db->host.hostname[0])
+ continue;
+ if (dbname && strcmp(db->db, dbname))
+ continue;
+ ACL_ROLE *r= rhash->find(db->user, strlen(db->user));
+ if (!r)
+ continue;
+ dbs.append(db);
+ }
+ dbs.sort(db_name_sort);
+
+ /*
+ Because dbs array is sorted by the db name, all grants for the same db
+ (that should be merged) are sorted together. The grantee's ACL_DB element
+ is not necessarily the first and may be not present at all.
+ */
+ ACL_DB **first= NULL, *UNINIT_VAR(merged);
+ ulong UNINIT_VAR(access), update_flags= 0;
+ for (ACL_DB **cur= dbs.front(); cur <= dbs.back(); cur++)
+ {
+ if (!first || (!dbname && strcmp(cur[0]->db, cur[-1]->db)))
+ { // new db name series
+ update_flags|= update_role_db(merged, first, access, grantee->user.str);
+ merged= NULL;
+ access= 0;
+ first= cur;
+ }
+ if (strcmp(cur[0]->user, grantee->user.str) == 0)
+ access|= (merged= cur[0])->initial_access;
+ else
+ access|= cur[0]->access;
+ }
+ update_flags|= update_role_db(merged, first, access, grantee->user.str);
+
+ /*
+ to make this code a bit simpler, we sort on deletes, to move
+ deleted elements to the end of the array. strictly speaking it's
+ unnecessary, it'd be faster to remove them in one O(N) array scan.
+
+ on the other hand, qsort on almost sorted array is pretty fast anyway...
+ */
+ if (update_flags & (2|4))
+ { // inserted or deleted, need to sort
+ my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements,
+ sizeof(ACL_DB),(qsort_cmp) acl_compare);
+ }
+ if (update_flags & 4)
+ { // deleted, trim the end
+ while (acl_dbs.elements &&
+ dynamic_element(&acl_dbs, acl_dbs.elements-1, ACL_DB*)->sort == 0)
+ acl_dbs.elements--;
+ }
+ return update_flags;
+}
+
+static int table_name_sort(GRANT_TABLE * const *tbl1, GRANT_TABLE * const *tbl2)
+{
+ int res = strcmp((*tbl1)->db, (*tbl2)->db);
+ if (res) return res;
+ return strcmp((*tbl1)->tname, (*tbl2)->tname);
+}
+
+/**
+ merges column privileges for the entry 'merged'
+
+ @param merged GRANT_TABLE to merge the privileges into
+ @param cur first entry in the array of GRANT_TABLE's for a given table
+ @param last last entry in the array of GRANT_TABLE's for a given table,
+ all entries between cur and last correspond to the *same* table
+
+ @return 1 if the _set of columns_ in 'merged' was changed
+ (not if the _set of privileges_ was changed).
+*/
+static int update_role_columns(GRANT_TABLE *merged,
+ GRANT_TABLE **cur, GRANT_TABLE **last)
+
+{
+ ulong rights __attribute__((unused))= 0;
+ int changed= 0;
+ if (!merged->cols)
+ {
+ changed= merged->hash_columns.records > 0;
+ my_hash_reset(&merged->hash_columns);
+ return changed;
+ }
+
+ DBUG_EXECUTE_IF("role_merge_stats", role_column_merges++;);
+
+ HASH *mh= &merged->hash_columns;
+ for (uint i=0 ; i < mh->records ; i++)
+ {
+ GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i);
+ col->rights= col->init_rights;
+ }
+
+ for (; cur < last; cur++)
+ {
+ if (*cur == merged)
+ continue;
+ HASH *ch= &cur[0]->hash_columns;
+ for (uint i=0 ; i < ch->records ; i++)
+ {
+ GRANT_COLUMN *ccol = (GRANT_COLUMN *)my_hash_element(ch, i);
+ GRANT_COLUMN *mcol = (GRANT_COLUMN *)my_hash_search(mh,
+ (uchar *)ccol->column, ccol->key_length);
+ if (mcol)
+ mcol->rights|= ccol->rights;
+ else
+ {
+ changed= 1;
+ my_hash_insert(mh, (uchar*)new (&grant_memroot) GRANT_COLUMN(ccol));
+ }
+ }
+ }
+
+ for (uint i=0 ; i < mh->records ; i++)
+ {
+ GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i);
+ rights|= col->rights;
+ if (!col->rights)
+ {
+ changed= 1;
+ my_hash_delete(mh, (uchar*)col);
+ }
+ }
+ DBUG_ASSERT(rights == merged->cols);
+ return changed;
+}
+
+/**
+ update GRANT_TABLE for a given table and a given role with merged privileges
+
+ @param merged GRANT_TABLE of the role in question (or NULL if it wasn't found)
+ @param first first GRANT_TABLE in an array for the table in question
+ @param last last entry in the array of GRANT_TABLE's for a given table,
+ all entries between first and last correspond to the *same* table
+ @param privs new table-level privileges for 'merged'
+ @param cols new OR-ed column-level privileges for 'merged'
+ @param role the name of the given role
+
+ @return a bitmap of
+ 1 - privileges were changed
+ 2 - GRANT_TABLE was added
+ 4 - GRANT_TABLE was deleted
+*/
+static int update_role_table_columns(GRANT_TABLE *merged,
+ GRANT_TABLE **first, GRANT_TABLE **last,
+ ulong privs, ulong cols, char *role)
+{
+ if (!first)
+ return 0;
+
+ DBUG_EXECUTE_IF("role_merge_stats", role_table_merges++;);
+
+ if (merged == NULL)
+ {
+ /*
+ there's no GRANT_TABLE for this role (all table grants come from granted
+ roles) we need to create it
+ */
+ DBUG_ASSERT(privs | cols);
+ merged= new (&grant_memroot) GRANT_TABLE("", first[0]->db, role, first[0]->tname,
+ privs, cols);
+ merged->init_privs= merged->init_cols= 0;
+ update_role_columns(merged, first, last);
+ my_hash_insert(&column_priv_hash,(uchar*) merged);
+ return 2;
+ }
+ else if ((privs | cols) == 0)
+ {
+ /*
+ there is GRANT_TABLE object but the role has no table or column
+ privileges granted (all privileges were coming from granted roles, and
+ now those roles were dropped or had their privileges revoked).
+ we need to remove this GRANT_TABLE
+ */
+ DBUG_EXECUTE_IF("role_merge_stats",
+ role_column_merges+= MY_TEST(merged->cols););
+ my_hash_delete(&column_priv_hash,(uchar*) merged);
+ return 4;
+ }
+ else
+ {
+ bool changed= merged->cols != cols || merged->privs != privs;
+ merged->cols= cols;
+ merged->privs= privs;
+ if (update_role_columns(merged, first, last))
+ changed= true;
+ return changed;
+ }
+}
+
+/**
+ merges table privileges from roles granted to the role 'grantee'.
+
+ @return true if table privileges of the 'grantee' were changed
+
+*/
+static bool merge_role_table_and_column_privileges(ACL_ROLE *grantee,
+ const char *db, const char *tname, role_hash_t *rhash)
+{
+ Dynamic_array<GRANT_TABLE *> grants;
+ DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither
+
+ /*
+ first, collect table/column privileges granted to
+ roles in question.
+ */
+ for (uint i=0 ; i < column_priv_hash.records ; i++)
+ {
+ GRANT_TABLE *grant= (GRANT_TABLE *) my_hash_element(&column_priv_hash, i);
+ if (grant->host.hostname[0])
+ continue;
+ if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname)))
+ continue;
+ ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user));
+ if (!r)
+ continue;
+ grants.append(grant);
+ }
+ grants.sort(table_name_sort);
+
+ GRANT_TABLE **first= NULL, *UNINIT_VAR(merged), **cur;
+ ulong UNINIT_VAR(privs), UNINIT_VAR(cols), update_flags= 0;
+ for (cur= grants.front(); cur <= grants.back(); cur++)
+ {
+ if (!first ||
+ (!tname && (strcmp(cur[0]->db, cur[-1]->db) ||
+ strcmp(cur[0]->tname, cur[-1]->tname))))
+ { // new db.tname series
+ update_flags|= update_role_table_columns(merged, first, cur,
+ privs, cols, grantee->user.str);
+ merged= NULL;
+ privs= cols= 0;
+ first= cur;
+ }
+ if (strcmp(cur[0]->user, grantee->user.str) == 0)
+ {
+ merged= cur[0];
+ cols|= cur[0]->init_cols;
+ privs|= cur[0]->init_privs;
+ }
+ else
+ {
+ cols|= cur[0]->cols;
+ privs|= cur[0]->privs;
+ }
+ }
+ update_flags|= update_role_table_columns(merged, first, cur,
+ privs, cols, grantee->user.str);
+
+ return update_flags;
+}
+
+static int routine_name_sort(GRANT_NAME * const *r1, GRANT_NAME * const *r2)
+{
+ int res= strcmp((*r1)->db, (*r2)->db);
+ if (res) return res;
+ return strcmp((*r1)->tname, (*r2)->tname);
+}
+
+/**
+ update GRANT_NAME for a given routine and a given role with merged privileges
+
+ @param merged GRANT_NAME of the role in question (or NULL if it wasn't found)
+ @param first first GRANT_NAME in an array for the routine in question
+ @param privs new routine-level privileges for 'merged'
+ @param role the name of the given role
+ @param hash proc_priv_hash or func_priv_hash
+
+ @return a bitmap of
+ 1 - privileges were changed
+ 2 - GRANT_NAME was added
+ 4 - GRANT_NAME was deleted
+*/
+static int update_role_routines(GRANT_NAME *merged, GRANT_NAME **first,
+ ulong privs, char *role, HASH *hash)
+{
+ if (!first)
+ return 0;
+
+ DBUG_EXECUTE_IF("role_merge_stats", role_routine_merges++;);
+
+ if (merged == NULL)
+ {
+ /*
+ there's no GRANT_NAME for this role (all routine grants come from granted
+ roles) we need to create it
+ */
+ DBUG_ASSERT(privs);
+ merged= new (&grant_memroot) GRANT_NAME("", first[0]->db, role, first[0]->tname,
+ privs, true);
+ merged->init_privs= 0; // all privs are inherited
+ my_hash_insert(hash, (uchar *)merged);
+ return 2;
+ }
+ else if (privs == 0)
+ {
+ /*
+ there is GRANT_NAME but the role has no privileges granted
+ (all privileges were coming from granted roles, and now those roles
+ were dropped or had their privileges revoked).
+ we need to remove this entry
+ */
+ my_hash_delete(hash, (uchar*)merged);
+ return 4;
+ }
+ else if (merged->privs != privs)
+ {
+ /* this is easy */
+ merged->privs= privs;
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ merges routine privileges from roles granted to the role 'grantee'.
+
+ @return true if routine privileges of the 'grantee' were changed
+
+*/
+static bool merge_role_routine_grant_privileges(ACL_ROLE *grantee,
+ const char *db, const char *tname, role_hash_t *rhash, HASH *hash)
+{
+ ulong update_flags= 0;
+
+ DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither
+
+ Dynamic_array<GRANT_NAME *> grants;
+
+ /* first, collect routine privileges granted to roles in question */
+ for (uint i=0 ; i < hash->records ; i++)
+ {
+ GRANT_NAME *grant= (GRANT_NAME *) my_hash_element(hash, i);
+ if (grant->host.hostname[0])
+ continue;
+ if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname)))
+ continue;
+ ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user));
+ if (!r)
+ continue;
+ grants.append(grant);
+ }
+ grants.sort(routine_name_sort);
+
+ GRANT_NAME **first= NULL, *UNINIT_VAR(merged);
+ ulong UNINIT_VAR(privs);
+ for (GRANT_NAME **cur= grants.front(); cur <= grants.back(); cur++)
+ {
+ if (!first ||
+ (!tname && (strcmp(cur[0]->db, cur[-1]->db) ||
+ strcmp(cur[0]->tname, cur[-1]->tname))))
+ { // new db.tname series
+ update_flags|= update_role_routines(merged, first, privs,
+ grantee->user.str, hash);
+ merged= NULL;
+ privs= 0;
+ first= cur;
+ }
+ if (strcmp(cur[0]->user, grantee->user.str) == 0)
+ {
+ merged= cur[0];
+ privs|= cur[0]->init_privs;
+ }
+ else
+ {
+ privs|= cur[0]->privs;
+ }
+ }
+ update_flags|= update_role_routines(merged, first, privs,
+ grantee->user.str, hash);
+ return update_flags;
+}
+
+/**
+ update privileges of the 'grantee' from all roles, granted to it
+*/
+static int merge_role_privileges(ACL_ROLE *role __attribute__((unused)),
+ ACL_ROLE *grantee, void *context)
+{
+ PRIVS_TO_MERGE *data= (PRIVS_TO_MERGE *)context;
+
+ if (--grantee->counter)
+ return 1; // don't recurse into grantee just yet
+
+ /* if we'll do db/table/routine privileges, create a hash of role names */
+ role_hash_t role_hash(role_key);
+ if (data->what != PRIVS_TO_MERGE::GLOBAL)
+ {
+ role_hash.insert(grantee);
+ for (uint i= 0; i < grantee->role_grants.elements; i++)
+ role_hash.insert(*dynamic_element(&grantee->role_grants, i, ACL_ROLE**));
+ }
+
+ bool all= data->what == PRIVS_TO_MERGE::ALL;
+ bool changed= false;
+ if (all || data->what == PRIVS_TO_MERGE::GLOBAL)
+ changed|= merge_role_global_privileges(grantee);
+ if (all || data->what == PRIVS_TO_MERGE::DB)
+ changed|= merge_role_db_privileges(grantee, data->db, &role_hash);
+ if (all || data->what == PRIVS_TO_MERGE::TABLE_COLUMN)
+ changed|= merge_role_table_and_column_privileges(grantee,
+ data->db, data->name, &role_hash);
+ if (all || data->what == PRIVS_TO_MERGE::PROC)
+ changed|= merge_role_routine_grant_privileges(grantee,
+ data->db, data->name, &role_hash, &proc_priv_hash);
+ if (all || data->what == PRIVS_TO_MERGE::FUNC)
+ changed|= merge_role_routine_grant_privileges(grantee,
+ data->db, data->name, &role_hash, &func_priv_hash);
+
+ return !changed; // don't recurse into the subgraph if privs didn't change
+}
+
+static bool merge_one_role_privileges(ACL_ROLE *grantee)
+{
+ PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 };
+ grantee->counter= 1;
+ return merge_role_privileges(0, grantee, &data);
+}
+
+/*****************************************************************
+ End of the role privilege propagation and graph traversal code
+******************************************************************/
+
+bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, LEX *lex)
+{
+ if (to != from)
+ {
+ /* preserve authentication information, if LEX_USER was reallocated */
+ to->password= from->password;
+ to->plugin= from->plugin;
+ to->auth= from->auth;
+ }
+
+ /*
+ Note, that no password is null_lex_str, while no plugin is empty_lex_str.
+ See sql_yacc.yy
+ */
+ bool has_auth= to->password.str || to->plugin.length || to->auth.length ||
+ lex->ssl_type != SSL_TYPE_NOT_SPECIFIED || lex->ssl_cipher ||
+ lex->x509_issuer || lex->x509_subject ||
+ lex->mqh.specified_limits;
+
+ /*
+ Specifying authentication clauses forces the name to be interpreted
+ as a user, not a role. See also check_change_password()
+ */
+ if (to->is_role() && has_auth)
+ {
+ my_error(ER_PASSWORD_NO_MATCH, MYF(0));
+ return true;
+ }
+
+ return false;
+}
+
+
+/*
+ Store table level and column level grants in the privilege tables
+
+ SYNOPSIS
+ mysql_table_grant()
+ thd Thread handle
+ table_list List of tables to give grant
+ user_list List of users to give grant
+ columns List of columns to give grant
+ rights Table level grant
+ revoke_grant Set to 1 if this is a REVOKE command
+
+ RETURN
+ FALSE ok
+ TRUE error
+*/
+
+int mysql_table_grant(THD *thd, TABLE_LIST *table_list,
+ List <LEX_USER> &user_list,
+ List <LEX_COLUMN> &columns, ulong rights,
+ bool revoke_grant)
+{
+ ulong column_priv= 0;
+ List_iterator <LEX_USER> str_list (user_list);
+ LEX_USER *Str, *tmp_Str;
+ TABLE_LIST tables[3];
+ bool create_new_users=0;
+ char *db_name, *table_name;
+ Rpl_filter *rpl_filter;
+ DBUG_ENTER("mysql_table_grant");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
+ "--skip-grant-tables"); /* purecov: inspected */
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ }
+ if (rights & ~TABLE_ACLS)
+ {
+ my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE),
+ MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (!revoke_grant)
+ {
+ if (columns.elements)
+ {
+ class LEX_COLUMN *column;
+ List_iterator <LEX_COLUMN> column_iter(columns);
+
+ if (open_normal_and_derived_tables(thd, table_list, 0, DT_PREPARE))
+ DBUG_RETURN(TRUE);
+
+ while ((column = column_iter++))
+ {
+ uint unused_field_idx= NO_CACHED_FIELD_INDEX;
+ TABLE_LIST *dummy;
+ Field *f=find_field_in_table_ref(thd, table_list, column->column.ptr(),
+ column->column.length(),
+ column->column.ptr(), NULL, NULL,
+ NULL, TRUE, FALSE,
+ &unused_field_idx, FALSE, &dummy);
+ if (f == (Field*)0)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0),
+ column->column.c_ptr(), table_list->alias);
+ DBUG_RETURN(TRUE);
+ }
+ if (f == (Field *)-1)
+ DBUG_RETURN(TRUE);
+ column_priv|= column->rights;
+ }
+ close_mysql_tables(thd);
+ }
+ else
+ {
+ if (!(rights & CREATE_ACL))
+ {
+ if (!ha_table_exists(thd, table_list->db, table_list->table_name, 0))
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ if (table_list->grant.want_privilege)
+ {
+ char command[128];
+ get_privilege_desc(command, sizeof(command),
+ table_list->grant.want_privilege);
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
+ command, thd->security_ctx->priv_user,
+ thd->security_ctx->host_or_ip, table_list->alias);
+ DBUG_RETURN(-1);
+ }
+ }
+ }
+
+ /* open the mysql.tables_priv and mysql.columns_priv tables */
+
+ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("user"), "user", TL_WRITE);
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("tables_priv"),
+ "tables_priv", TL_WRITE);
+ tables[2].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("columns_priv"),
+ "columns_priv", TL_WRITE);
+ tables[0].next_local= tables[0].next_global= tables+1;
+ /* Don't open column table if we don't need it ! */
+ if (column_priv || (revoke_grant && ((rights & COL_ACLS) || columns.elements)))
+ tables[1].next_local= tables[1].next_global= tables+2;
+
+#ifdef HAVE_REPLICATION
+ /*
+ GRANT and REVOKE are applied the slave in/exclusion rules as they are
+ some kind of updates to the mysql.% tables.
+ */
+ if (thd->slave_thread &&
+ (rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter)->is_on())
+ {
+ /*
+ The tables must be marked "updating" so that tables_ok() takes them into
+ account in tests.
+ */
+ tables[0].updating= tables[1].updating= tables[2].updating= 1;
+ if (!(thd->spcont || rpl_filter->tables_ok(0, tables)))
+ DBUG_RETURN(FALSE);
+ }
+#endif
+
+ /*
+ The lock api is depending on the thd->lex variable which needs to be
+ re-initialized.
+ */
+ Query_tables_list backup;
+ thd->lex->reset_n_backup_query_tables_list(&backup);
+ /*
+ Restore Query_tables_list::sql_command value, which was reset
+ above, as the code writing query to the binary log assumes that
+ this value corresponds to the statement being executed.
+ */
+ thd->lex->sql_command= backup.sql_command;
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ { // Should never happen
+ thd->lex->restore_backup_query_tables_list(&backup);
+ DBUG_RETURN(TRUE); /* purecov: deadcode */
+ }
+
+ if (!revoke_grant)
+ create_new_users= test_if_create_new_users(thd);
+ bool result= FALSE;
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+ MEM_ROOT *old_root= thd->mem_root;
+ thd->mem_root= &grant_memroot;
+ grant_version++;
+
+ while ((tmp_Str = str_list++))
+ {
+ int error;
+ GRANT_TABLE *grant_table;
+ if (!(Str= get_current_user(thd, tmp_Str, false)))
+ {
+ result= TRUE;
+ continue;
+ }
+ /* Create user if needed */
+ if (copy_and_check_auth(Str, tmp_Str, thd->lex))
+ error= -1;
+ else
+ error=replace_user_table(thd, tables[0].table, *Str,
+ 0, revoke_grant, create_new_users,
+ MY_TEST(thd->variables.sql_mode &
+ MODE_NO_AUTO_CREATE_USER));
+ if (error)
+ {
+ result= TRUE; // Remember error
+ continue; // Add next user
+ }
+
+ db_name= table_list->get_db_name();
+ table_name= table_list->get_table_name();
+
+ /* Find/create cached table grant */
+ grant_table= table_hash_search(Str->host.str, NullS, db_name,
+ Str->user.str, table_name, 1);
+ if (!grant_table)
+ {
+ if (revoke_grant)
+ {
+ my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0),
+ Str->user.str, Str->host.str, table_list->table_name);
+ result= TRUE;
+ continue;
+ }
+ grant_table = new GRANT_TABLE (Str->host.str, db_name,
+ Str->user.str, table_name,
+ rights,
+ column_priv);
+ if (!grant_table ||
+ my_hash_insert(&column_priv_hash,(uchar*) grant_table))
+ {
+ result= TRUE; /* purecov: deadcode */
+ continue; /* purecov: deadcode */
+ }
+ }
+
+ /* If revoke_grant, calculate the new column privilege for tables_priv */
+ if (revoke_grant)
+ {
+ class LEX_COLUMN *column;
+ List_iterator <LEX_COLUMN> column_iter(columns);
+ GRANT_COLUMN *grant_column;
+
+ /* Fix old grants */
+ while ((column = column_iter++))
+ {
+ grant_column = column_hash_search(grant_table,
+ column->column.ptr(),
+ column->column.length());
+ if (grant_column)
+ grant_column->rights&= ~(column->rights | rights);
+ }
+ /* scan trough all columns to get new column grant */
+ column_priv= 0;
+ for (uint idx=0 ; idx < grant_table->hash_columns.records ; idx++)
+ {
+ grant_column= (GRANT_COLUMN*)
+ my_hash_element(&grant_table->hash_columns, idx);
+ grant_column->rights&= ~rights; // Fix other columns
+ column_priv|= grant_column->rights;
+ }
+ }
+ else
+ {
+ column_priv|= grant_table->cols;
+ }
+
+
+ /* update table and columns */
+
+ if (replace_table_table(thd, grant_table, tables[1].table, *Str,
+ db_name, table_name,
+ rights, column_priv, revoke_grant))
+ {
+ /* Should only happen if table is crashed */
+ result= TRUE; /* purecov: deadcode */
+ }
+ else if (tables[2].table)
+ {
+ if (replace_column_table(grant_table, tables[2].table, *Str, columns,
+ db_name, table_name, rights, revoke_grant))
+ {
+ result= TRUE;
+ }
+ }
+ if (Str->is_role())
+ propagate_role_grants(find_acl_role(Str->user.str),
+ PRIVS_TO_MERGE::TABLE_COLUMN, db_name, table_name);
+ }
+
+ thd->mem_root= old_root;
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (!result) /* success */
+ {
+ result= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ }
+
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ if (!result) /* success */
+ my_ok(thd);
+
+ /* Tables are automatically closed */
+ thd->lex->restore_backup_query_tables_list(&backup);
+ /* Restore the state of binlog format */
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Store routine level grants in the privilege tables
+
+ @param thd Thread handle
+ @param table_list List of routines to give grant
+ @param is_proc Is this a list of procedures?
+ @param user_list List of users to give grant
+ @param rights Table level grant
+ @param revoke_grant Is this is a REVOKE command?
+
+ @return
+ @retval FALSE Success.
+ @retval TRUE An error occurred.
+*/
+
+bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc,
+ List <LEX_USER> &user_list, ulong rights,
+ bool revoke_grant, bool write_to_binlog)
+{
+ List_iterator <LEX_USER> str_list (user_list);
+ LEX_USER *Str, *tmp_Str;
+ TABLE_LIST tables[2];
+ bool create_new_users=0, result=0;
+ char *db_name, *table_name;
+ Rpl_filter *rpl_filter;
+ DBUG_ENTER("mysql_routine_grant");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
+ "--skip-grant-tables");
+ DBUG_RETURN(TRUE);
+ }
+ if (rights & ~PROC_ACLS)
+ {
+ my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE),
+ MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (!revoke_grant)
+ {
+ if (sp_exist_routines(thd, table_list, is_proc))
+ DBUG_RETURN(TRUE);
+ }
+
+ /* open the mysql.user and mysql.procs_priv tables */
+
+ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("user"), "user", TL_WRITE);
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("procs_priv"), "procs_priv", TL_WRITE);
+ tables[0].next_local= tables[0].next_global= tables+1;
+
+#ifdef HAVE_REPLICATION
+ /*
+ GRANT and REVOKE are applied the slave in/exclusion rules as they are
+ some kind of updates to the mysql.% tables.
+ */
+ if (thd->slave_thread &&
+ (rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter)->is_on())
+ {
+ /*
+ The tables must be marked "updating" so that tables_ok() takes them into
+ account in tests.
+ */
+ tables[0].updating= tables[1].updating= 1;
+ if (!(thd->spcont || rpl_filter->tables_ok(0, tables)))
+ {
+ DBUG_RETURN(FALSE);
+ }
+ }
+#endif
+
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ DBUG_RETURN(TRUE);
+
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+
+ if (!revoke_grant)
+ create_new_users= test_if_create_new_users(thd);
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+ MEM_ROOT *old_root= thd->mem_root;
+ thd->mem_root= &grant_memroot;
+
+ DBUG_PRINT("info",("now time to iterate and add users"));
+
+ while ((tmp_Str= str_list++))
+ {
+ int error;
+ GRANT_NAME *grant_name;
+ if (!(Str= get_current_user(thd, tmp_Str, false)))
+ {
+ result= TRUE;
+ continue;
+ }
+ /* Create user if needed */
+ error=replace_user_table(thd, tables[0].table, *Str,
+ 0, revoke_grant, create_new_users,
+ MY_TEST(thd->variables.sql_mode &
+ MODE_NO_AUTO_CREATE_USER));
+ if (error)
+ {
+ result= TRUE; // Remember error
+ continue; // Add next user
+ }
+
+ db_name= table_list->db;
+ table_name= table_list->table_name;
+ grant_name= routine_hash_search(Str->host.str, NullS, db_name,
+ Str->user.str, table_name, is_proc, 1);
+ if (!grant_name || !grant_name->init_privs)
+ {
+ if (revoke_grant)
+ {
+ my_error(ER_NONEXISTING_PROC_GRANT, MYF(0),
+ Str->user.str, Str->host.str, table_name);
+ result= TRUE;
+ continue;
+ }
+ grant_name= new GRANT_NAME(Str->host.str, db_name,
+ Str->user.str, table_name,
+ rights, TRUE);
+ if (!grant_name ||
+ my_hash_insert(is_proc ?
+ &proc_priv_hash : &func_priv_hash,(uchar*) grant_name))
+ {
+ result= TRUE;
+ continue;
+ }
+ }
+
+ if (replace_routine_table(thd, grant_name, tables[1].table, *Str,
+ db_name, table_name, is_proc, rights,
+ revoke_grant) != 0)
+ {
+ result= TRUE;
+ continue;
+ }
+ if (Str->is_role())
+ propagate_role_grants(find_acl_role(Str->user.str),
+ is_proc ? PRIVS_TO_MERGE::PROC : PRIVS_TO_MERGE::FUNC,
+ db_name, table_name);
+ }
+ thd->mem_root= old_root;
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (write_to_binlog)
+ {
+ if (write_bin_log(thd, FALSE, thd->query(), thd->query_length()))
+ result= TRUE;
+ }
+
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ /* Tables are automatically closed */
+ DBUG_RETURN(result);
+}
+
+/**
+ append a user or role name to a buffer that will be later used as an error message
+*/
+static void append_user(THD *thd, String *str,
+ const LEX_STRING *u, const LEX_STRING *h)
+{
+ if (str->length())
+ str->append(',');
+ append_query_string(system_charset_info, str, u->str, u->length,
+ thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
+ /* hostname part is not relevant for roles, it is always empty */
+ if (u->length == 0 || h->length != 0)
+ {
+ str->append('@');
+ append_query_string(system_charset_info, str, h->str, h->length,
+ thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
+ }
+}
+
+static void append_user(THD *thd, String *str, LEX_USER *user)
+{
+ append_user(thd, str, & user->user, & user->host);
+}
+
+/**
+ append a string to a buffer that will be later used as an error message
+
+ @note
+ a string can be either CURRENT_USER or CURRENT_ROLE or NONE, it should be
+ neither quoted nor escaped.
+*/
+static void append_str(String *str, const char *s, size_t l)
+{
+ if (str->length())
+ str->append(',');
+ str->append(s, l);
+}
+
+static int can_grant_role_callback(ACL_USER_BASE *grantee,
+ ACL_ROLE *role, void *data)
+{
+ ROLE_GRANT_PAIR *pair;
+
+ if (role != (ACL_ROLE*)data)
+ return 0; // keep searching
+
+ if (grantee->flags & IS_ROLE)
+ pair= find_role_grant_pair(&grantee->user, &empty_lex_str, &role->user);
+ else
+ {
+ ACL_USER *user= (ACL_USER *)grantee;
+ LEX_STRING host= { user->host.hostname, user->hostname_length };
+ pair= find_role_grant_pair(&user->user, &host, &role->user);
+ }
+ if (!pair->with_admin)
+ return 0; // keep searching
+
+ return -1; // abort the traversal
+}
+
+
+/*
+ One can only grant a role if SELECT * FROM I_S.APPLICABLE_ROLES shows this
+ role as grantable.
+
+ What this really means - we need to traverse role graph for the current user
+ looking for our role being granted with the admin option.
+*/
+static bool can_grant_role(THD *thd, ACL_ROLE *role)
+{
+ Security_context *sctx= thd->security_ctx;
+
+ if (!sctx->user) // replication
+ return true;
+
+ ACL_USER *grantee= find_user_exact(sctx->priv_host, sctx->priv_user);
+ if (!grantee)
+ return false;
+
+ return traverse_role_graph_down(grantee, role, NULL,
+ can_grant_role_callback) == -1;
+}
+
+
+bool mysql_grant_role(THD *thd, List <LEX_USER> &list, bool revoke)
+{
+ DBUG_ENTER("mysql_grant_role");
+ /*
+ The first entry in the list is the granted role. Need at least two
+ entries for the command to be valid
+ */
+ DBUG_ASSERT(list.elements >= 2);
+ bool result= 0;
+ bool create_new_user, no_auto_create_user;
+ String wrong_users;
+ LEX_USER *user, *granted_role;
+ LEX_STRING rolename;
+ LEX_STRING username;
+ LEX_STRING hostname;
+ ACL_ROLE *role, *role_as_user;
+
+ List_iterator <LEX_USER> user_list(list);
+ granted_role= user_list++;
+ if (!(granted_role= get_current_user(thd, granted_role)))
+ DBUG_RETURN(TRUE);
+
+ DBUG_ASSERT(granted_role->is_role());
+ rolename= granted_role->user;
+
+ create_new_user= test_if_create_new_users(thd);
+ no_auto_create_user= MY_TEST(thd->variables.sql_mode &
+ MODE_NO_AUTO_CREATE_USER);
+
+ TABLE_LIST tables[2];
+ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("roles_mapping"),
+ "roles_mapping", TL_WRITE);
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("user"), "user", TL_WRITE);
+ tables[0].next_local= tables[0].next_global= tables+1;
+
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ DBUG_RETURN(TRUE); /* purecov: deadcode */
+
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+ if (!(role= find_acl_role(rolename.str)))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+ my_error(ER_INVALID_ROLE, MYF(0), rolename.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (!can_grant_role(thd, role))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+ my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
+ thd->security_ctx->priv_user, thd->security_ctx->priv_host);
+ DBUG_RETURN(TRUE);
+ }
+
+ while ((user= user_list++))
+ {
+ role_as_user= NULL;
+ /* current_role is treated slightly different */
+ if (user->user.str == current_role.str)
+ {
+ /* current_role is NONE */
+ if (!thd->security_ctx->priv_role[0])
+ {
+ my_error(ER_INVALID_ROLE, MYF(0), "NONE");
+ append_str(&wrong_users, STRING_WITH_LEN("NONE"));
+ result= 1;
+ continue;
+ }
+ if (!(role_as_user= find_acl_role(thd->security_ctx->priv_role)))
+ {
+ LEX_STRING ls= { thd->security_ctx->priv_role,
+ strlen(thd->security_ctx->priv_role) };
+ append_user(thd, &wrong_users, &ls, &empty_lex_str);
+ result= 1;
+ continue;
+ }
+
+ /* can not grant current_role to current_role */
+ if (granted_role->user.str == current_role.str)
+ {
+ append_user(thd, &wrong_users, &role_as_user->user, &empty_lex_str);
+ result= 1;
+ continue;
+ }
+ username.str= thd->security_ctx->priv_role;
+ username.length= strlen(username.str);
+ hostname= empty_lex_str;
+ }
+ else if (user->user.str == current_user.str)
+ {
+ username.str= thd->security_ctx->priv_user;
+ username.length= strlen(username.str);
+ hostname.str= thd->security_ctx->priv_host;
+ hostname.length= strlen(hostname.str);
+ }
+ else
+ {
+ username= user->user;
+ if (user->host.str)
+ hostname= user->host;
+ else
+ if ((role_as_user= find_acl_role(user->user.str)))
+ hostname= empty_lex_str;
+ else
+ {
+ if (is_invalid_role_name(username.str))
+ {
+ append_user(thd, &wrong_users, &username, &empty_lex_str);
+ result= 1;
+ continue;
+ }
+ hostname= host_not_specified;
+ }
+ }
+
+ ROLE_GRANT_PAIR *hash_entry= find_role_grant_pair(&username, &hostname,
+ &rolename);
+ ACL_USER_BASE *grantee= role_as_user;
+
+ if (!grantee)
+ grantee= find_user_exact(hostname.str, username.str);
+
+ if (!grantee && !revoke)
+ {
+ LEX_USER user_combo = *user;
+ user_combo.host = hostname;
+ user_combo.user = username;
+
+ /* create the user if it does not exist */
+ if (replace_user_table(thd, tables[1].table, user_combo, 0,
+ false, create_new_user,
+ no_auto_create_user))
+ {
+ append_user(thd, &wrong_users, &username, &hostname);
+ result= 1;
+ continue;
+ }
+ grantee= find_user_exact(hostname.str, username.str);
+
+ /* either replace_user_table failed, or we've added the user */
+ DBUG_ASSERT(grantee);
+ }
+
+ if (!grantee)
+ {
+ append_user(thd, &wrong_users, &username, &hostname);
+ result= 1;
+ continue;
+ }
+
+ if (!revoke)
+ {
+ if (hash_entry)
+ {
+ // perhaps, updating an existing grant, adding WITH ADMIN OPTION
+ }
+ else
+ {
+ add_role_user_mapping(grantee, role);
+
+ /*
+ Check if this grant would cause a cycle. It only needs to be run
+ if we're granting a role to a role
+ */
+ if (role_as_user &&
+ traverse_role_graph_down(role, 0, 0, 0) == ROLE_CYCLE_FOUND)
+ {
+ append_user(thd, &wrong_users, &username, &empty_lex_str);
+ result= 1;
+ undo_add_role_user_mapping(grantee, role);
+ continue;
+ }
+ }
+ }
+ else
+ {
+ /* grant was already removed or never existed */
+ if (!hash_entry)
+ {
+ append_user(thd, &wrong_users, &username, &hostname);
+ result= 1;
+ continue;
+ }
+ if (thd->lex->with_admin_option)
+ {
+ // only revoking an admin option, not the complete grant
+ }
+ else
+ {
+ /* revoke a role grant */
+ remove_role_user_mapping(grantee, role);
+ }
+ }
+
+ /* write into the roles_mapping table */
+ if (replace_roles_mapping_table(tables[0].table,
+ &username, &hostname, &rolename,
+ thd->lex->with_admin_option,
+ hash_entry, revoke))
+ {
+ append_user(thd, &wrong_users, &username, &empty_lex_str);
+ result= 1;
+ if (!revoke)
+ {
+ /* need to remove the mapping added previously */
+ undo_add_role_user_mapping(grantee, role);
+ }
+ else
+ {
+ /* need to restore the mapping deleted previously */
+ add_role_user_mapping(grantee, role);
+ }
+ continue;
+ }
+ update_role_mapping(&username, &hostname, &rolename,
+ thd->lex->with_admin_option, hash_entry, revoke);
+
+ /*
+ Only need to propagate grants when granting/revoking a role to/from
+ a role
+ */
+ if (role_as_user && merge_one_role_privileges(role_as_user) == 0)
+ propagate_role_grants(role_as_user, PRIVS_TO_MERGE::ALL);
+ }
+
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (result)
+ my_error(revoke ? ER_CANNOT_REVOKE_ROLE : ER_CANNOT_GRANT_ROLE, MYF(0),
+ rolename.str, wrong_users.c_ptr_safe());
+ else
+ result= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ DBUG_RETURN(result);
+}
+
+
+bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list,
+ ulong rights, bool revoke_grant, bool is_proxy)
+{
+ List_iterator <LEX_USER> str_list (list);
+ LEX_USER *Str, *tmp_Str, *proxied_user= NULL;
+ char tmp_db[SAFE_NAME_LEN+1];
+ bool create_new_users=0;
+ TABLE_LIST tables[2];
+ Rpl_filter *rpl_filter;
+ DBUG_ENTER("mysql_grant");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
+ "--skip-grant-tables"); /* purecov: tested */
+ DBUG_RETURN(TRUE); /* purecov: tested */
+ }
+
+ if (lower_case_table_names && db)
+ {
+ char *end= strnmov(tmp_db,db, sizeof(tmp_db));
+ if (end >= tmp_db + sizeof(tmp_db))
+ {
+ my_error(ER_WRONG_DB_NAME ,MYF(0), db);
+ DBUG_RETURN(TRUE);
+ }
+ my_casedn_str(files_charset_info, tmp_db);
+ db=tmp_db;
+ }
+
+ if (is_proxy)
+ {
+ DBUG_ASSERT(!db);
+ proxied_user= str_list++;
+ }
+
+ /* open the mysql.user and mysql.db or mysql.proxies_priv tables */
+ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("user"), "user", TL_WRITE);
+ if (is_proxy)
+
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("proxies_priv"),
+ "proxies_priv",
+ TL_WRITE);
+ else
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("db"),
+ "db",
+ TL_WRITE);
+ tables[0].next_local= tables[0].next_global= tables+1;
+
+#ifdef HAVE_REPLICATION
+ /*
+ GRANT and REVOKE are applied the slave in/exclusion rules as they are
+ some kind of updates to the mysql.% tables.
+ */
+ if (thd->slave_thread &&
+ (rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter)->is_on())
+ {
+ /*
+ The tables must be marked "updating" so that tables_ok() takes them into
+ account in tests.
+ */
+ tables[0].updating= tables[1].updating= 1;
+ if (!(thd->spcont || rpl_filter->tables_ok(0, tables)))
+ DBUG_RETURN(FALSE);
+ }
+#endif
+
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ DBUG_RETURN(TRUE); /* purecov: deadcode */
+
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+
+ if (!revoke_grant)
+ create_new_users= test_if_create_new_users(thd);
+
+ /* go through users in user_list */
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+ grant_version++;
+
+ if (proxied_user)
+ {
+ if (!(proxied_user= get_current_user(thd, proxied_user, false)))
+ DBUG_RETURN(TRUE);
+ DBUG_ASSERT(proxied_user->host.length); // not a Role
+ }
+
+ int result=0;
+ while ((tmp_Str = str_list++))
+ {
+ if (!(Str= get_current_user(thd, tmp_Str, false)))
+ {
+ result= TRUE;
+ continue;
+ }
+
+ if (copy_and_check_auth(Str, tmp_Str, thd->lex))
+ result= -1;
+ else
+ if (replace_user_table(thd, tables[0].table, *Str,
+ (!db ? rights : 0), revoke_grant, create_new_users,
+ MY_TEST(thd->variables.sql_mode &
+ MODE_NO_AUTO_CREATE_USER)))
+ result= -1;
+ else if (db)
+ {
+ ulong db_rights= rights & DB_ACLS;
+ if (db_rights == rights)
+ {
+ if (replace_db_table(tables[1].table, db, *Str, db_rights,
+ revoke_grant))
+ result= -1;
+ }
+ else
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "DB GRANT", "GLOBAL PRIVILEGES");
+ result= -1;
+ }
+ }
+ else if (is_proxy)
+ {
+ if (replace_proxies_priv_table (thd, tables[1].table, Str, proxied_user,
+ rights & GRANT_ACL ? TRUE : FALSE,
+ revoke_grant))
+ result= -1;
+ }
+ if (Str->is_role())
+ propagate_role_grants(find_acl_role(Str->user.str),
+ db ? PRIVS_TO_MERGE::DB : PRIVS_TO_MERGE::GLOBAL,
+ db);
+ }
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (!result)
+ {
+ result= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ }
+
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ if (!result)
+ my_ok(thd);
+
+ DBUG_RETURN(result);
+}
+
+
+/* Free grant array if possible */
+
+void grant_free(void)
+{
+ DBUG_ENTER("grant_free");
+ my_hash_free(&column_priv_hash);
+ my_hash_free(&proc_priv_hash);
+ my_hash_free(&func_priv_hash);
+ free_root(&grant_memroot,MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @brief Initialize structures responsible for table/column-level privilege
+ checking and load information for them from tables in the 'mysql' database.
+
+ @return Error status
+ @retval 0 OK
+ @retval 1 Could not initialize grant subsystem.
+*/
+
+my_bool grant_init()
+{
+ THD *thd;
+ my_bool return_val;
+ DBUG_ENTER("grant_init");
+
+ if (!(thd= new THD))
+ DBUG_RETURN(1); /* purecov: deadcode */
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+ return_val= grant_reload(thd);
+ delete thd;
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+ DBUG_RETURN(return_val);
+}
+
+
+/**
+ @brief Initialize structures responsible for table/column-level privilege
+ checking and load information about grants from open privilege tables.
+
+ @param thd Current thread
+ @param tables List containing open "mysql.tables_priv" and
+ "mysql.columns_priv" tables.
+
+ @see grant_reload
+
+ @return Error state
+ @retval FALSE Success
+ @retval TRUE Error
+*/
+
+static my_bool grant_load(THD *thd, TABLE_LIST *tables)
+{
+ MEM_ROOT *memex_ptr;
+ my_bool return_val= 1;
+ TABLE *t_table, *c_table, *p_table;
+ bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE;
+ MEM_ROOT **save_mem_root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**,
+ THR_MALLOC);
+ ulonglong old_sql_mode= thd->variables.sql_mode;
+ DBUG_ENTER("grant_load");
+
+ thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH;
+
+ (void) my_hash_init(&column_priv_hash, &my_charset_utf8_bin,
+ 0,0,0, (my_hash_get_key) get_grant_table,
+ (my_hash_free_key) free_grant_table,0);
+ (void) my_hash_init(&proc_priv_hash, &my_charset_utf8_bin,
+ 0,0,0, (my_hash_get_key) get_grant_table, 0,0);
+ (void) my_hash_init(&func_priv_hash, &my_charset_utf8_bin,
+ 0,0,0, (my_hash_get_key) get_grant_table, 0,0);
+ init_sql_alloc(&grant_memroot, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
+
+ t_table= tables[0].table;
+ c_table= tables[1].table;
+ p_table= tables[2].table; // this can be NULL
+
+ if (t_table->file->ha_index_init(0, 1))
+ goto end_index_init;
+
+ t_table->use_all_columns();
+ c_table->use_all_columns();
+
+ memex_ptr= &grant_memroot;
+ my_pthread_setspecific_ptr(THR_MALLOC, &memex_ptr);
+
+ if (!t_table->file->ha_index_first(t_table->record[0]))
+ {
+ do
+ {
+ GRANT_TABLE *mem_check;
+ if (!(mem_check=new (memex_ptr) GRANT_TABLE(t_table,c_table)))
+ {
+ /* This could only happen if we are out memory */
+ goto end_unlock;
+ }
+
+ if (check_no_resolve)
+ {
+ if (hostname_requires_resolving(mem_check->host.hostname))
+ {
+ sql_print_warning("'tables_priv' entry '%s %s@%s' "
+ "ignored in --skip-name-resolve mode.",
+ mem_check->tname,
+ safe_str(mem_check->user),
+ safe_str(mem_check->host.hostname));
+ continue;
+ }
+ }
+
+ if (! mem_check->ok())
+ delete mem_check;
+ else if (my_hash_insert(&column_priv_hash,(uchar*) mem_check))
+ {
+ delete mem_check;
+ goto end_unlock;
+ }
+ }
+ while (!t_table->file->ha_index_next(t_table->record[0]));
+ }
+
+ return_val= 0;
+
+ if (p_table)
+ {
+ if (p_table->file->ha_index_init(0, 1))
+ goto end_unlock;
+
+ p_table->use_all_columns();
+
+ if (!p_table->file->ha_index_first(p_table->record[0]))
+ {
+ do
+ {
+ GRANT_NAME *mem_check;
+ HASH *hash;
+ if (!(mem_check=new (memex_ptr) GRANT_NAME(p_table, TRUE)))
+ {
+ /* This could only happen if we are out memory */
+ goto end_unlock_p;
+ }
+
+ if (check_no_resolve)
+ {
+ if (hostname_requires_resolving(mem_check->host.hostname))
+ {
+ sql_print_warning("'procs_priv' entry '%s %s@%s' "
+ "ignored in --skip-name-resolve mode.",
+ mem_check->tname, mem_check->user,
+ safe_str(mem_check->host.hostname));
+ continue;
+ }
+ }
+ if (p_table->field[4]->val_int() == TYPE_ENUM_PROCEDURE)
+ {
+ hash= &proc_priv_hash;
+ }
+ else
+ if (p_table->field[4]->val_int() == TYPE_ENUM_FUNCTION)
+ {
+ hash= &func_priv_hash;
+ }
+ else
+ {
+ sql_print_warning("'procs_priv' entry '%s' "
+ "ignored, bad routine type",
+ mem_check->tname);
+ continue;
+ }
+
+ mem_check->privs= fix_rights_for_procedure(mem_check->privs);
+ mem_check->init_privs= mem_check->privs;
+ if (! mem_check->ok())
+ delete mem_check;
+ else if (my_hash_insert(hash, (uchar*) mem_check))
+ {
+ delete mem_check;
+ goto end_unlock_p;
+ }
+ }
+ while (!p_table->file->ha_index_next(p_table->record[0]));
+ }
+ }
+
+end_unlock_p:
+ if (p_table)
+ p_table->file->ha_index_end();
+end_unlock:
+ t_table->file->ha_index_end();
+ my_pthread_setspecific_ptr(THR_MALLOC, save_mem_root_ptr);
+end_index_init:
+ thd->variables.sql_mode= old_sql_mode;
+ DBUG_RETURN(return_val);
+}
+
+
+my_bool role_propagate_grants_action(void *ptr, void *unused __attribute__((unused)))
+{
+ ACL_ROLE *role= (ACL_ROLE *)ptr;
+ if (role->counter)
+ return 0;
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+ PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 };
+ traverse_role_graph_up(role, &data, NULL, merge_role_privileges);
+ return 0;
+}
+
+
+/**
+ @brief Reload information about table and column level privileges if possible
+
+ @param thd Current thread
+
+ Locked tables are checked by acl_reload() and doesn't have to be checked
+ in this call.
+ This function is also used for initialization of structures responsible
+ for table/column-level privilege checking.
+
+ @return Error state
+ @retval FALSE Success
+ @retval TRUE Error
+*/
+
+my_bool grant_reload(THD *thd)
+{
+ TABLE_LIST tables[3];
+ HASH old_column_priv_hash, old_proc_priv_hash, old_func_priv_hash;
+ MEM_ROOT old_mem;
+ my_bool return_val= 1;
+ DBUG_ENTER("grant_reload");
+
+ /* Don't do anything if running with --skip-grant-tables */
+ if (!initialized)
+ DBUG_RETURN(0);
+
+ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("tables_priv"),
+ "tables_priv", TL_READ);
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("columns_priv"),
+ "columns_priv", TL_READ);
+ tables[2].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("procs_priv"),
+ "procs_priv", TL_READ);
+ tables[0].next_local= tables[0].next_global= tables+1;
+ tables[1].next_local= tables[1].next_global= tables+2;
+ tables[0].open_type= tables[1].open_type= tables[2].open_type= OT_BASE_ONLY;
+ tables[2].open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
+
+ /*
+ To avoid deadlocks we should obtain table locks before
+ obtaining LOCK_grant rwlock.
+ */
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ goto end;
+
+ mysql_rwlock_wrlock(&LOCK_grant);
+ grant_version++;
+ old_column_priv_hash= column_priv_hash;
+ old_proc_priv_hash= proc_priv_hash;
+ old_func_priv_hash= func_priv_hash;
+
+ /*
+ Create a new memory pool but save the current memory pool to make an undo
+ opertion possible in case of failure.
+ */
+ old_mem= grant_memroot;
+
+ if ((return_val= grant_load(thd, tables)))
+ { // Error. Revert to old hash
+ DBUG_PRINT("error",("Reverting to old privileges"));
+ grant_free(); /* purecov: deadcode */
+ column_priv_hash= old_column_priv_hash; /* purecov: deadcode */
+ proc_priv_hash= old_proc_priv_hash;
+ func_priv_hash= old_func_priv_hash;
+ grant_memroot= old_mem; /* purecov: deadcode */
+ }
+ else
+ {
+ my_hash_free(&old_column_priv_hash);
+ my_hash_free(&old_proc_priv_hash);
+ my_hash_free(&old_func_priv_hash);
+ free_root(&old_mem,MYF(0));
+ }
+
+ mysql_mutex_lock(&acl_cache->lock);
+ my_hash_iterate(&acl_roles, role_propagate_grants_action, NULL);
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ close_mysql_tables(thd);
+
+end:
+ DBUG_RETURN(return_val);
+}
+
+
+/**
+ @brief Check table level grants
+
+ @param thd Thread handler
+ @param want_access Bits of privileges user needs to have.
+ @param tables List of tables to check. The user should have
+ 'want_access' to all tables in list.
+ @param any_combination_will_do TRUE if it's enough to have any privilege for
+ any combination of the table columns.
+ @param number Check at most this number of tables.
+ @param no_errors TRUE if no error should be sent directly to the client.
+
+ If table->grant.want_privilege != 0 then the requested privileges where
+ in the set of COL_ACLS but access was not granted on the table level. As
+ a consequence an extra check of column privileges is required.
+
+ Specifically if this function returns FALSE the user has some kind of
+ privilege on a combination of columns in each table.
+
+ This function is usually preceeded by check_access which establish the
+ User-, Db- and Host access rights.
+
+ @see check_access
+ @see check_table_access
+
+ @note
+ This functions assumes that either number of tables to be inspected
+ by it is limited explicitly (i.e. is is not UINT_MAX) or table list
+ used and thd->lex->query_tables_own_last value correspond to each
+ other (the latter should be either 0 or point to next_global member
+ of one of elements of this table list).
+
+ We delay locking of LOCK_grant until we really need it as we assume that
+ most privileges be resolved with user or db level accesses.
+
+ @return Access status
+ @retval FALSE Access granted; But column privileges might need to be
+ checked.
+ @retval TRUE The user did not have the requested privileges on any of the
+ tables.
+
+*/
+
+bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables,
+ bool any_combination_will_do, uint number, bool no_errors)
+{
+ TABLE_LIST *tl;
+ TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
+ Security_context *sctx= thd->security_ctx;
+ uint i;
+ ulong orig_want_access= want_access;
+ my_bool locked= 0;
+ GRANT_TABLE *grant_table;
+ GRANT_TABLE *grant_table_role= NULL;
+ DBUG_ENTER("check_grant");
+ DBUG_ASSERT(number > 0);
+
+ /*
+ Walk through the list of tables that belong to the query and save the
+ requested access (orig_want_privilege) to be able to use it when
+ checking access rights to the underlying tables of a view. Our grant
+ system gradually eliminates checked bits from want_privilege and thus
+ after all checks are done we can no longer use it.
+ The check that first_not_own_table is not reached is for the case when
+ the given table list refers to the list for prelocking (contains tables
+ of other queries). For simple queries first_not_own_table is 0.
+ */
+ for (i= 0, tl= tables;
+ i < number && tl != first_not_own_table;
+ tl= tl->next_global, i++)
+ {
+ /*
+ Save a copy of the privileges without the SHOW_VIEW_ACL attribute.
+ It will be checked during making view.
+ */
+ tl->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL);
+ }
+ number= i;
+
+ for (tl= tables; number-- ; tl= tl->next_global)
+ {
+ sctx= MY_TEST(tl->security_ctx) ? tl->security_ctx : thd->security_ctx;
+
+ const ACL_internal_table_access *access=
+ get_cached_table_access(&tl->grant.m_internal,
+ tl->get_db_name(),
+ tl->get_table_name());
+
+ if (access)
+ {
+ switch(access->check(orig_want_access, &tl->grant.privilege))
+ {
+ case ACL_INTERNAL_ACCESS_GRANTED:
+ /*
+ Currently,
+ - the information_schema does not subclass ACL_internal_table_access,
+ there are no per table privilege checks for I_S,
+ - the performance schema does use per tables checks, but at most
+ returns 'CHECK_GRANT', and never 'ACCESS_GRANTED'.
+ so this branch is not used.
+ */
+ DBUG_ASSERT(0);
+ case ACL_INTERNAL_ACCESS_DENIED:
+ goto err;
+ case ACL_INTERNAL_ACCESS_CHECK_GRANT:
+ break;
+ }
+ }
+
+ want_access= orig_want_access;
+ want_access&= ~sctx->master_access;
+ if (!want_access)
+ continue; // ok
+
+ if (!(~tl->grant.privilege & want_access) ||
+ tl->is_anonymous_derived_table() || tl->schema_table)
+ {
+ /*
+ It is subquery in the FROM clause. VIEW set tl->derived after
+ table opening, but this function always called before table opening.
+ */
+ if (!tl->referencing_view)
+ {
+ /*
+ If it's a temporary table created for a subquery in the FROM
+ clause, or an INFORMATION_SCHEMA table, drop the request for
+ a privilege.
+ */
+ tl->grant.want_privilege= 0;
+ }
+ continue;
+ }
+
+ if (is_temporary_table(tl))
+ {
+ /*
+ If this table list element corresponds to a pre-opened temporary
+ table skip checking of all relevant table-level privileges for it.
+ Note that during creation of temporary table we still need to check
+ if user has CREATE_TMP_ACL.
+ */
+ tl->grant.privilege|= TMP_TABLE_ACLS;
+ tl->grant.want_privilege= 0;
+ continue;
+ }
+
+ if (!locked)
+ {
+ locked= 1;
+ mysql_rwlock_rdlock(&LOCK_grant);
+ }
+
+ grant_table= table_hash_search(sctx->host, sctx->ip,
+ tl->get_db_name(),
+ sctx->priv_user,
+ tl->get_table_name(),
+ FALSE);
+ if (sctx->priv_role[0])
+ grant_table_role= table_hash_search("", NULL, tl->get_db_name(),
+ sctx->priv_role,
+ tl->get_table_name(),
+ TRUE);
+
+ if (!grant_table && !grant_table_role)
+ {
+ want_access&= ~tl->grant.privilege;
+ goto err;
+ }
+
+ /*
+ For SHOW COLUMNS, SHOW INDEX it is enough to have some
+ privileges on any column combination on the table.
+ */
+ if (any_combination_will_do)
+ continue;
+
+ tl->grant.grant_table_user= grant_table; // Remember for column test
+ tl->grant.grant_table_role= grant_table_role;
+ tl->grant.version= grant_version;
+ tl->grant.privilege|= grant_table ? grant_table->privs : 0;
+ tl->grant.privilege|= grant_table_role ? grant_table_role->privs : 0;
+ tl->grant.want_privilege= ((want_access & COL_ACLS) & ~tl->grant.privilege);
+
+ if (!(~tl->grant.privilege & want_access))
+ continue;
+
+ if ((want_access&= ~((grant_table ? grant_table->cols : 0) |
+ (grant_table_role ? grant_table_role->cols : 0) |
+ tl->grant.privilege)))
+ {
+ goto err; // impossible
+ }
+ }
+ if (locked)
+ mysql_rwlock_unlock(&LOCK_grant);
+ DBUG_RETURN(FALSE);
+
+err:
+ if (locked)
+ mysql_rwlock_unlock(&LOCK_grant);
+ if (!no_errors) // Not a silent skip of table
+ {
+ char command[128];
+ get_privilege_desc(command, sizeof(command), want_access);
+ status_var_increment(thd->status_var.access_denied_errors);
+
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
+ command,
+ sctx->priv_user,
+ sctx->host_or_ip,
+ tl ? tl->get_table_name() : "unknown");
+ }
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Check column rights in given security context
+
+ SYNOPSIS
+ check_grant_column()
+ thd thread handler
+ grant grant information structure
+ db_name db name
+ table_name table name
+ name column name
+ length column name length
+ sctx security context
+
+ RETURN
+ FALSE OK
+ TRUE access denied
+*/
+
+bool check_grant_column(THD *thd, GRANT_INFO *grant,
+ const char *db_name, const char *table_name,
+ const char *name, uint length, Security_context *sctx)
+{
+ GRANT_TABLE *grant_table;
+ GRANT_TABLE *grant_table_role;
+ GRANT_COLUMN *grant_column;
+ ulong want_access= grant->want_privilege & ~grant->privilege;
+ DBUG_ENTER("check_grant_column");
+ DBUG_PRINT("enter", ("table: %s want_access: %lu", table_name, want_access));
+
+ if (!want_access)
+ DBUG_RETURN(0); // Already checked
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+
+ /* reload table if someone has modified any grants */
+
+ if (grant->version != grant_version)
+ {
+ grant->grant_table_user=
+ table_hash_search(sctx->host, sctx->ip, db_name,
+ sctx->priv_user,
+ table_name, 0); /* purecov: inspected */
+ grant->grant_table_role=
+ sctx->priv_role[0] ? table_hash_search("", NULL, db_name,
+ sctx->priv_role,
+ table_name, TRUE) : NULL;
+ grant->version= grant_version; /* purecov: inspected */
+ }
+
+ grant_table= grant->grant_table_user;
+ grant_table_role= grant->grant_table_role;
+
+ if (!grant_table && !grant_table_role)
+ goto err;
+
+ if (grant_table)
+ {
+ grant_column= column_hash_search(grant_table, name, length);
+ if (grant_column)
+ {
+ want_access&= ~grant_column->rights;
+ }
+ }
+ if (grant_table_role)
+ {
+ grant_column= column_hash_search(grant_table_role, name, length);
+ if (grant_column)
+ {
+ want_access&= ~grant_column->rights;
+ }
+ }
+ if (!want_access)
+ {
+ mysql_rwlock_unlock(&LOCK_grant);
+ DBUG_RETURN(0);
+ }
+
+err:
+ mysql_rwlock_unlock(&LOCK_grant);
+ char command[128];
+ get_privilege_desc(command, sizeof(command), want_access);
+ /* TODO perhaps error should print current rolename aswell */
+ my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
+ command,
+ sctx->priv_user,
+ sctx->host_or_ip,
+ name,
+ table_name);
+ DBUG_RETURN(1);
+}
+
+
+/*
+ Check the access right to a column depending on the type of table.
+
+ SYNOPSIS
+ check_column_grant_in_table_ref()
+ thd thread handler
+ table_ref table reference where to check the field
+ name name of field to check
+ length length of name
+
+ DESCRIPTION
+ Check the access rights to a column depending on the type of table
+ reference where the column is checked. The function provides a
+ generic interface to check column access rights that hides the
+ heterogeneity of the column representation - whether it is a view
+ or a stored table colum.
+
+ RETURN
+ FALSE OK
+ TRUE access denied
+*/
+
+bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref,
+ const char *name, uint length)
+{
+ GRANT_INFO *grant;
+ const char *db_name;
+ const char *table_name;
+ Security_context *sctx= MY_TEST(table_ref->security_ctx) ?
+ table_ref->security_ctx : thd->security_ctx;
+
+ if (table_ref->view || table_ref->field_translation)
+ {
+ /* View or derived information schema table. */
+ ulong view_privs;
+ grant= &(table_ref->grant);
+ db_name= table_ref->view_db.str;
+ table_name= table_ref->view_name.str;
+ if (table_ref->belong_to_view &&
+ thd->lex->sql_command == SQLCOM_SHOW_FIELDS)
+ {
+ view_privs= get_column_grant(thd, grant, db_name, table_name, name);
+ if (view_privs & VIEW_ANY_ACL)
+ {
+ table_ref->belong_to_view->allowed_show= TRUE;
+ return FALSE;
+ }
+ table_ref->belong_to_view->allowed_show= FALSE;
+ my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0));
+ return TRUE;
+ }
+ }
+ else
+ {
+ /* Normal or temporary table. */
+ TABLE *table= table_ref->table;
+ grant= &(table->grant);
+ db_name= table->s->db.str;
+ table_name= table->s->table_name.str;
+ }
+
+ if (grant->want_privilege)
+ return check_grant_column(thd, grant, db_name, table_name, name,
+ length, sctx);
+ else
+ return FALSE;
+
+}
+
+
+/**
+ @brief check if a query can access a set of columns
+
+ @param thd the current thread
+ @param want_access_arg the privileges requested
+ @param fields an iterator over the fields of a table reference.
+ @return Operation status
+ @retval 0 Success
+ @retval 1 Falure
+ @details This function walks over the columns of a table reference
+ The columns may originate from different tables, depending on the kind of
+ table reference, e.g. join, view.
+ For each table it will retrieve the grant information and will use it
+ to check the required access privileges for the fields requested from it.
+*/
+bool check_grant_all_columns(THD *thd, ulong want_access_arg,
+ Field_iterator_table_ref *fields)
+{
+ Security_context *sctx= thd->security_ctx;
+ ulong UNINIT_VAR(want_access);
+ const char *table_name= NULL;
+ const char* db_name;
+ GRANT_INFO *grant;
+ GRANT_TABLE *UNINIT_VAR(grant_table);
+ GRANT_TABLE *UNINIT_VAR(grant_table_role);
+ /*
+ Flag that gets set if privilege checking has to be performed on column
+ level.
+ */
+ bool using_column_privileges= FALSE;
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+
+ for (; !fields->end_of_fields(); fields->next())
+ {
+ const char *field_name= fields->name();
+
+ if (table_name != fields->get_table_name())
+ {
+ table_name= fields->get_table_name();
+ db_name= fields->get_db_name();
+ grant= fields->grant();
+ /* get a fresh one for each table */
+ want_access= want_access_arg & ~grant->privilege;
+ if (want_access)
+ {
+ /* reload table if someone has modified any grants */
+ if (grant->version != grant_version)
+ {
+ grant->grant_table_user=
+ table_hash_search(sctx->host, sctx->ip, db_name,
+ sctx->priv_user,
+ table_name, 0); /* purecov: inspected */
+ grant->grant_table_role=
+ sctx->priv_role[0] ? table_hash_search("", NULL, db_name,
+ sctx->priv_role,
+ table_name, TRUE) : NULL;
+ grant->version= grant_version; /* purecov: inspected */
+ }
+
+ grant_table= grant->grant_table_user;
+ grant_table_role= grant->grant_table_role;
+ DBUG_ASSERT (grant_table || grant_table_role);
+ }
+ }
+
+ if (want_access)
+ {
+ ulong have_access= 0;
+ if (grant_table)
+ {
+ GRANT_COLUMN *grant_column=
+ column_hash_search(grant_table, field_name,
+ (uint) strlen(field_name));
+ if (grant_column)
+ have_access= grant_column->rights;
+ }
+ if (grant_table_role)
+ {
+ GRANT_COLUMN *grant_column=
+ column_hash_search(grant_table_role, field_name,
+ (uint) strlen(field_name));
+ if (grant_column)
+ have_access|= grant_column->rights;
+ }
+
+ if (have_access)
+ using_column_privileges= TRUE;
+ if (want_access & ~have_access)
+ goto err;
+ }
+ }
+ mysql_rwlock_unlock(&LOCK_grant);
+ return 0;
+
+err:
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ char command[128];
+ get_privilege_desc(command, sizeof(command), want_access);
+ /*
+ Do not give an error message listing a column name unless the user has
+ privilege to see all columns.
+ */
+ if (using_column_privileges)
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
+ command, sctx->priv_user,
+ sctx->host_or_ip, table_name);
+ else
+ my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
+ command,
+ sctx->priv_user,
+ sctx->host_or_ip,
+ fields->name(),
+ table_name);
+ return 1;
+}
+
+
+static bool check_grant_db_routine(THD *thd, const char *db, HASH *hash)
+{
+ Security_context *sctx= thd->security_ctx;
+
+ for (uint idx= 0; idx < hash->records; ++idx)
+ {
+ GRANT_NAME *item= (GRANT_NAME*) my_hash_element(hash, idx);
+
+ if (strcmp(item->user, sctx->priv_user) == 0 &&
+ strcmp(item->db, db) == 0 &&
+ compare_hostname(&item->host, sctx->host, sctx->ip))
+ {
+ return FALSE;
+ }
+ if (sctx->priv_role[0] && strcmp(item->user, sctx->priv_role) == 0 &&
+ strcmp(item->db, db) == 0 &&
+ (!item->host.hostname || !item->host.hostname[0]))
+ {
+ return FALSE; /* Found current role match */
+ }
+ }
+
+ return TRUE;
+}
+
+
+/*
+ Check if a user has the right to access a database
+ Access is accepted if he has a grant for any table/routine in the database
+ Return 1 if access is denied
+*/
+
+bool check_grant_db(THD *thd, const char *db)
+{
+ Security_context *sctx= thd->security_ctx;
+ char helping [SAFE_NAME_LEN + USERNAME_LENGTH+2], *end;
+ char helping2 [SAFE_NAME_LEN + USERNAME_LENGTH+2], *tmp_db;
+ uint len, UNINIT_VAR(len2);
+ bool error= TRUE;
+
+ tmp_db= strmov(helping, sctx->priv_user) + 1;
+ end= strnmov(tmp_db, db, helping + sizeof(helping) - tmp_db);
+
+ if (end >= helping + sizeof(helping)) // db name was truncated
+ return 1; // no privileges for an invalid db name
+
+ if (lower_case_table_names)
+ {
+ end = tmp_db + my_casedn_str(files_charset_info, tmp_db);
+ db=tmp_db;
+ }
+
+ len= (uint) (end - helping) + 1;
+
+ /*
+ If a role is set, we need to check for privileges
+ here aswell
+ */
+ if (sctx->priv_role[0])
+ {
+ end= strmov(helping2, sctx->priv_role) + 1;
+ end= strnmov(end, db, helping2 + sizeof(helping2) - end);
+ len2= (uint) (end - helping2) + 1;
+ }
+
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+
+ for (uint idx=0 ; idx < column_priv_hash.records ; idx++)
+ {
+ GRANT_TABLE *grant_table= (GRANT_TABLE*)
+ my_hash_element(&column_priv_hash,
+ idx);
+ if (len < grant_table->key_length &&
+ !memcmp(grant_table->hash_key,helping,len) &&
+ compare_hostname(&grant_table->host, sctx->host, sctx->ip))
+ {
+ error= FALSE; /* Found match. */
+ break;
+ }
+ if (sctx->priv_role[0] &&
+ len2 < grant_table->key_length &&
+ !memcmp(grant_table->hash_key,helping2,len) &&
+ (!grant_table->host.hostname || !grant_table->host.hostname[0]))
+ {
+ error= FALSE; /* Found role match */
+ break;
+ }
+ }
+
+ if (error)
+ error= check_grant_db_routine(thd, db, &proc_priv_hash) &&
+ check_grant_db_routine(thd, db, &func_priv_hash);
+
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ return error;
+}
+
+
+/****************************************************************************
+ Check routine level grants
+
+ SYNPOSIS
+ bool check_grant_routine()
+ thd Thread handler
+ want_access Bits of privileges user needs to have
+ procs List of routines to check. The user should have 'want_access'
+ is_proc True if the list is all procedures, else functions
+ no_errors If 0 then we write an error. The error is sent directly to
+ the client
+
+ RETURN
+ 0 ok
+ 1 Error: User did not have the requested privielges
+****************************************************************************/
+
+bool check_grant_routine(THD *thd, ulong want_access,
+ TABLE_LIST *procs, bool is_proc, bool no_errors)
+{
+ TABLE_LIST *table;
+ Security_context *sctx= thd->security_ctx;
+ char *user= sctx->priv_user;
+ char *host= sctx->priv_host;
+ char *role= sctx->priv_role;
+ DBUG_ENTER("check_grant_routine");
+
+ want_access&= ~sctx->master_access;
+ if (!want_access)
+ DBUG_RETURN(0); // ok
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+ for (table= procs; table; table= table->next_global)
+ {
+ GRANT_NAME *grant_proc;
+ if ((grant_proc= routine_hash_search(host, sctx->ip, table->db, user,
+ table->table_name, is_proc, 0)))
+ table->grant.privilege|= grant_proc->privs;
+ if (role[0]) /* current role set check */
+ {
+ if ((grant_proc= routine_hash_search("", NULL, table->db, role,
+ table->table_name, is_proc, 0)))
+ table->grant.privilege|= grant_proc->privs;
+ }
+
+ if (want_access & ~table->grant.privilege)
+ {
+ want_access &= ~table->grant.privilege;
+ goto err;
+ }
+ }
+ mysql_rwlock_unlock(&LOCK_grant);
+ DBUG_RETURN(0);
+err:
+ mysql_rwlock_unlock(&LOCK_grant);
+ if (!no_errors)
+ {
+ char buff[1024];
+ const char *command="";
+ if (table)
+ strxmov(buff, table->db, ".", table->table_name, NullS);
+ if (want_access & EXECUTE_ACL)
+ command= "execute";
+ else if (want_access & ALTER_PROC_ACL)
+ command= "alter routine";
+ else if (want_access & GRANT_ACL)
+ command= "grant";
+ my_error(ER_PROCACCESS_DENIED_ERROR, MYF(0),
+ command, user, host, table ? buff : "unknown");
+ }
+ DBUG_RETURN(1);
+}
+
+
+/*
+ Check if routine has any of the
+ routine level grants
+
+ SYNPOSIS
+ bool check_routine_level_acl()
+ thd Thread handler
+ db Database name
+ name Routine name
+
+ RETURN
+ 0 Ok
+ 1 error
+*/
+
+bool check_routine_level_acl(THD *thd, const char *db, const char *name,
+ bool is_proc)
+{
+ bool no_routine_acl= 1;
+ GRANT_NAME *grant_proc;
+ Security_context *sctx= thd->security_ctx;
+ mysql_rwlock_rdlock(&LOCK_grant);
+ if ((grant_proc= routine_hash_search(sctx->priv_host,
+ sctx->ip, db,
+ sctx->priv_user,
+ name, is_proc, 0)))
+ no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS);
+
+ if (no_routine_acl && sctx->priv_role[0]) /* current set role check */
+ {
+ if ((grant_proc= routine_hash_search("",
+ NULL, db,
+ sctx->priv_role,
+ name, is_proc, 0)))
+ no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS);
+ }
+ mysql_rwlock_unlock(&LOCK_grant);
+ return no_routine_acl;
+}
+
+
+/*****************************************************************************
+ Functions to retrieve the grant for a table/column (for SHOW functions)
+*****************************************************************************/
+
+ulong get_table_grant(THD *thd, TABLE_LIST *table)
+{
+ ulong privilege;
+ Security_context *sctx= thd->security_ctx;
+ const char *db = table->db ? table->db : thd->db;
+ GRANT_TABLE *grant_table;
+ GRANT_TABLE *grant_table_role= NULL;
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+#ifdef EMBEDDED_LIBRARY
+ grant_table= NULL;
+ grant_table_role= NULL;
+#else
+ grant_table= table_hash_search(sctx->host, sctx->ip, db, sctx->priv_user,
+ table->table_name, 0);
+ if (sctx->priv_role[0])
+ grant_table_role= table_hash_search("", "", db, sctx->priv_role,
+ table->table_name, 0);
+#endif
+ table->grant.grant_table_user= grant_table; // Remember for column test
+ table->grant.grant_table_role= grant_table_role;
+ table->grant.version=grant_version;
+ if (grant_table)
+ table->grant.privilege|= grant_table->privs;
+ if (grant_table_role)
+ table->grant.privilege|= grant_table_role->privs;
+ privilege= table->grant.privilege;
+ mysql_rwlock_unlock(&LOCK_grant);
+ return privilege;
+}
+
+
+/*
+ Determine the access priviliges for a field.
+
+ SYNOPSIS
+ get_column_grant()
+ thd thread handler
+ grant grants table descriptor
+ db_name name of database that the field belongs to
+ table_name name of table that the field belongs to
+ field_name name of field
+
+ DESCRIPTION
+ The procedure may also modify: grant->grant_table and grant->version.
+
+ RETURN
+ The access priviliges for the field db_name.table_name.field_name
+*/
+
+ulong get_column_grant(THD *thd, GRANT_INFO *grant,
+ const char *db_name, const char *table_name,
+ const char *field_name)
+{
+ GRANT_TABLE *grant_table;
+ GRANT_TABLE *grant_table_role;
+ GRANT_COLUMN *grant_column;
+ ulong priv= 0;
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+ /* reload table if someone has modified any grants */
+ if (grant->version != grant_version)
+ {
+ Security_context *sctx= thd->security_ctx;
+ grant->grant_table_user=
+ table_hash_search(sctx->host, sctx->ip,
+ db_name, sctx->priv_user,
+ table_name, 0); /* purecov: inspected */
+ grant->grant_table_role=
+ sctx->priv_role[0] ? table_hash_search("", "", db_name,
+ sctx->priv_role,
+ table_name, TRUE) : NULL;
+ grant->version= grant_version; /* purecov: inspected */
+ }
+
+ grant_table= grant->grant_table_user;
+ grant_table_role= grant->grant_table_role;
+
+ if (!grant_table && !grant_table_role)
+ priv= grant->privilege;
+ else
+ {
+ if (grant_table)
+ {
+ grant_column= column_hash_search(grant_table, field_name,
+ (uint) strlen(field_name));
+ if (!grant_column)
+ priv= (grant->privilege | grant_table->privs);
+ else
+ priv= (grant->privilege | grant_table->privs | grant_column->rights);
+ }
+
+ if (grant_table_role)
+ {
+ grant_column= column_hash_search(grant_table_role, field_name,
+ (uint) strlen(field_name));
+ if (!grant_column)
+ priv|= (grant->privilege | grant_table_role->privs);
+ else
+ priv|= (grant->privilege | grant_table->privs | grant_column->rights);
+ }
+ }
+ mysql_rwlock_unlock(&LOCK_grant);
+ return priv;
+}
+
+
+/* Help function for mysql_show_grants */
+
+static void add_user_option(String *grant, long value, const char *name,
+ my_bool is_signed)
+{
+ if (value)
+ {
+ char buff[22], *p; // just as in int2str
+ grant->append(' ');
+ grant->append(name, strlen(name));
+ grant->append(' ');
+ p=int10_to_str(value, buff, is_signed ? -10 : 10);
+ grant->append(buff,p-buff);
+ }
+}
+
+static const char *command_array[]=
+{
+ "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "RELOAD",
+ "SHUTDOWN", "PROCESS","FILE", "GRANT", "REFERENCES", "INDEX",
+ "ALTER", "SHOW DATABASES", "SUPER", "CREATE TEMPORARY TABLES",
+ "LOCK TABLES", "EXECUTE", "REPLICATION SLAVE", "REPLICATION CLIENT",
+ "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", "ALTER ROUTINE",
+ "CREATE USER", "EVENT", "TRIGGER", "CREATE TABLESPACE"
+};
+
+static uint command_lengths[]=
+{
+ 6, 6, 6, 6, 6, 4, 6, 8, 7, 4, 5, 10, 5, 5, 14, 5, 23, 11, 7, 17, 18, 11, 9,
+ 14, 13, 11, 5, 7, 17
+};
+
+
+static bool print_grants_for_role(THD *thd, ACL_ROLE * role)
+{
+ char buff[1024];
+
+ if (show_role_grants(thd, role->user.str, "", role, buff, sizeof(buff)))
+ return TRUE;
+
+ if (show_global_privileges(thd, role, TRUE, buff, sizeof(buff)))
+ return TRUE;
+
+ if (show_database_privileges(thd, role->user.str, "", buff, sizeof(buff)))
+ return TRUE;
+
+ if (show_table_and_column_privileges(thd, role->user.str, "", buff, sizeof(buff)))
+ return TRUE;
+
+ if (show_routine_grants(thd, role->user.str, "", &proc_priv_hash,
+ STRING_WITH_LEN("PROCEDURE"), buff, sizeof(buff)))
+ return TRUE;
+
+ if (show_routine_grants(thd, role->user.str, "", &func_priv_hash,
+ STRING_WITH_LEN("FUNCTION"), buff, sizeof(buff)))
+ return TRUE;
+
+ return FALSE;
+
+}
+
+
+static int show_grants_callback(ACL_USER_BASE *role, void *data)
+{
+ THD *thd= (THD *)data;
+ DBUG_ASSERT(role->flags & IS_ROLE);
+ if (print_grants_for_role(thd, (ACL_ROLE *)role))
+ return -1;
+ return 0;
+}
+
+
+/*
+ SHOW GRANTS; Send grants for a user to the client
+
+ IMPLEMENTATION
+ Send to client grant-like strings depicting user@host privileges
+*/
+
+bool mysql_show_grants(THD *thd, LEX_USER *lex_user)
+{
+ int error = -1;
+ ACL_USER *UNINIT_VAR(acl_user);
+ ACL_ROLE *acl_role= NULL;
+ char buff[1024];
+ Protocol *protocol= thd->protocol;
+ char *username= NULL;
+ char *hostname= NULL;
+ char *rolename= NULL;
+ DBUG_ENTER("mysql_show_grants");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
+ DBUG_RETURN(TRUE);
+ }
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ if (lex_user->user.str == current_user.str)
+ {
+ username= thd->security_ctx->priv_user;
+ hostname= thd->security_ctx->priv_host;
+ }
+ else if (lex_user->user.str == current_role.str)
+ {
+ rolename= thd->security_ctx->priv_role;
+ }
+ else if (lex_user->user.str == current_user_and_current_role.str)
+ {
+ username= thd->security_ctx->priv_user;
+ hostname= thd->security_ctx->priv_host;
+ rolename= thd->security_ctx->priv_role;
+ }
+ else
+ {
+ lex_user= get_current_user(thd, lex_user, false);
+ if (!lex_user)
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (lex_user->is_role())
+ {
+ rolename= lex_user->user.str;
+ }
+ else
+ {
+ username= lex_user->user.str;
+ hostname= lex_user->host.str;
+ }
+ }
+ DBUG_ASSERT(rolename || username);
+
+ Item_string *field=new Item_string_ascii("", 0);
+ List<Item> field_list;
+ field->name=buff;
+ field->max_length=1024;
+ if (!username)
+ strxmov(buff,"Grants for ",rolename, NullS);
+ else
+ strxmov(buff,"Grants for ",username,"@",hostname, NullS);
+ field_list.push_back(field);
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ DBUG_RETURN(TRUE);
+ }
+
+ if (username)
+ {
+ acl_user= find_user_exact(hostname, username);
+ if (!acl_user)
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ my_error(ER_NONEXISTING_GRANT, MYF(0),
+ username, hostname);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Show granted roles to acl_user */
+ if (show_role_grants(thd, username, hostname, acl_user, buff, sizeof(buff)))
+ goto end;
+
+ /* Add first global access grants */
+ if (show_global_privileges(thd, acl_user, FALSE, buff, sizeof(buff)))
+ goto end;
+
+ /* Add database access */
+ if (show_database_privileges(thd, username, hostname, buff, sizeof(buff)))
+ goto end;
+
+ /* Add table & column access */
+ if (show_table_and_column_privileges(thd, username, hostname, buff, sizeof(buff)))
+ goto end;
+
+ if (show_routine_grants(thd, username, hostname, &proc_priv_hash,
+ STRING_WITH_LEN("PROCEDURE"), buff, sizeof(buff)))
+ goto end;
+
+ if (show_routine_grants(thd, username, hostname, &func_priv_hash,
+ STRING_WITH_LEN("FUNCTION"), buff, sizeof(buff)))
+ goto end;
+
+ if (show_proxy_grants(thd, username, hostname, buff, sizeof(buff)))
+ goto end;
+ }
+
+ if (rolename)
+ {
+ acl_role= find_acl_role(rolename);
+ if (acl_role)
+ {
+ /* get a list of all inherited roles */
+ traverse_role_graph_down(acl_role, thd, show_grants_callback, NULL);
+ }
+ else
+ {
+ if (lex_user->user.str == current_role.str)
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+ my_error(ER_NONEXISTING_GRANT, MYF(0),
+ thd->security_ctx->priv_user,
+ thd->security_ctx->priv_host);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+
+ error= 0;
+end:
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ my_eof(thd);
+ DBUG_RETURN(error);
+}
+
+static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_STRING *u,
+ const LEX_STRING *h,
+ const LEX_STRING *r)
+{
+ char buf[1024];
+ String pair_key(buf, sizeof(buf), &my_charset_bin);
+
+ size_t key_length= u->length + h->length + r->length + 3;
+ pair_key.alloc(key_length);
+
+ strmov(strmov(strmov(const_cast<char*>(pair_key.ptr()),
+ safe_str(u->str)) + 1, h->str) + 1, r->str);
+
+ return (ROLE_GRANT_PAIR *)
+ my_hash_search(&acl_roles_mappings, (uchar*)pair_key.ptr(), key_length);
+}
+
+static bool show_role_grants(THD *thd, const char *username,
+ const char *hostname, ACL_USER_BASE *acl_entry,
+ char *buff, size_t buffsize)
+{
+ uint counter;
+ Protocol *protocol= thd->protocol;
+ LEX_STRING host= {const_cast<char*>(hostname), strlen(hostname)};
+
+ String grant(buff,sizeof(buff),system_charset_info);
+ for (counter= 0; counter < acl_entry->role_grants.elements; counter++)
+ {
+ grant.length(0);
+ grant.append(STRING_WITH_LEN("GRANT "));
+ ACL_ROLE *acl_role= *(dynamic_element(&acl_entry->role_grants, counter,
+ ACL_ROLE**));
+ grant.append(acl_role->user.str, acl_role->user.length,
+ system_charset_info);
+ grant.append(STRING_WITH_LEN(" TO '"));
+ grant.append(acl_entry->user.str, acl_entry->user.length,
+ system_charset_info);
+ if (!(acl_entry->flags & IS_ROLE))
+ {
+ grant.append(STRING_WITH_LEN("'@'"));
+ grant.append(&host);
+ }
+ grant.append('\'');
+
+ ROLE_GRANT_PAIR *pair=
+ find_role_grant_pair(&acl_entry->user, &host, &acl_role->user);
+ DBUG_ASSERT(pair);
+
+ if (pair->with_admin)
+ grant.append(STRING_WITH_LEN(" WITH ADMIN OPTION"));
+
+ protocol->prepare_for_resend();
+ protocol->store(grant.ptr(),grant.length(),grant.charset());
+ if (protocol->write())
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool show_global_privileges(THD *thd, ACL_USER_BASE *acl_entry,
+ bool handle_as_role,
+ char *buff, size_t buffsize)
+{
+ uint counter;
+ ulong want_access;
+ Protocol *protocol= thd->protocol;
+
+ String global(buff,sizeof(buff),system_charset_info);
+ global.length(0);
+ global.append(STRING_WITH_LEN("GRANT "));
+
+ if (handle_as_role)
+ want_access= ((ACL_ROLE *)acl_entry)->initial_role_access;
+ else
+ want_access= acl_entry->access;
+ if (test_all_bits(want_access, (GLOBAL_ACLS & ~ GRANT_ACL)))
+ global.append(STRING_WITH_LEN("ALL PRIVILEGES"));
+ else if (!(want_access & ~GRANT_ACL))
+ global.append(STRING_WITH_LEN("USAGE"));
+ else
+ {
+ bool found=0;
+ ulong j,test_access= want_access & ~GRANT_ACL;
+ for (counter=0, j = SELECT_ACL;j <= GLOBAL_ACLS;counter++,j <<= 1)
+ {
+ if (test_access & j)
+ {
+ if (found)
+ global.append(STRING_WITH_LEN(", "));
+ found=1;
+ global.append(command_array[counter],command_lengths[counter]);
+ }
+ }
+ }
+ global.append (STRING_WITH_LEN(" ON *.* TO '"));
+ global.append(acl_entry->user.str, acl_entry->user.length,
+ system_charset_info);
+ global.append('\'');
+
+ if (!handle_as_role)
+ {
+ ACL_USER *acl_user= (ACL_USER *)acl_entry;
+
+ global.append (STRING_WITH_LEN("@'"));
+ global.append(acl_user->host.hostname, acl_user->hostname_length,
+ system_charset_info);
+ global.append ('\'');
+
+ if (acl_user->plugin.str == native_password_plugin_name.str ||
+ acl_user->plugin.str == old_password_plugin_name.str)
+ {
+ if (acl_user->auth_string.length)
+ {
+ DBUG_ASSERT(acl_user->salt_len);
+ global.append(STRING_WITH_LEN(" IDENTIFIED BY PASSWORD '"));
+ global.append(acl_user->auth_string.str, acl_user->auth_string.length);
+ global.append('\'');
+ }
+ }
+ else
+ {
+ global.append(STRING_WITH_LEN(" IDENTIFIED VIA "));
+ global.append(acl_user->plugin.str, acl_user->plugin.length);
+ if (acl_user->auth_string.length)
+ {
+ global.append(STRING_WITH_LEN(" USING '"));
+ global.append(acl_user->auth_string.str, acl_user->auth_string.length);
+ global.append('\'');
+ }
+ }
+ /* "show grants" SSL related stuff */
+ if (acl_user->ssl_type == SSL_TYPE_ANY)
+ global.append(STRING_WITH_LEN(" REQUIRE SSL"));
+ else if (acl_user->ssl_type == SSL_TYPE_X509)
+ global.append(STRING_WITH_LEN(" REQUIRE X509"));
+ else if (acl_user->ssl_type == SSL_TYPE_SPECIFIED)
+ {
+ int ssl_options = 0;
+ global.append(STRING_WITH_LEN(" REQUIRE "));
+ if (acl_user->x509_issuer)
+ {
+ ssl_options++;
+ global.append(STRING_WITH_LEN("ISSUER \'"));
+ global.append(acl_user->x509_issuer,strlen(acl_user->x509_issuer));
+ global.append('\'');
+ }
+ if (acl_user->x509_subject)
+ {
+ if (ssl_options++)
+ global.append(' ');
+ global.append(STRING_WITH_LEN("SUBJECT \'"));
+ global.append(acl_user->x509_subject,strlen(acl_user->x509_subject),
+ system_charset_info);
+ global.append('\'');
+ }
+ if (acl_user->ssl_cipher)
+ {
+ if (ssl_options++)
+ global.append(' ');
+ global.append(STRING_WITH_LEN("CIPHER '"));
+ global.append(acl_user->ssl_cipher,strlen(acl_user->ssl_cipher),
+ system_charset_info);
+ global.append('\'');
+ }
+ }
+ if ((want_access & GRANT_ACL) ||
+ (acl_user->user_resource.questions ||
+ acl_user->user_resource.updates ||
+ acl_user->user_resource.conn_per_hour ||
+ acl_user->user_resource.user_conn))
+ {
+ global.append(STRING_WITH_LEN(" WITH"));
+ if (want_access & GRANT_ACL)
+ global.append(STRING_WITH_LEN(" GRANT OPTION"));
+ add_user_option(&global, acl_user->user_resource.questions,
+ "MAX_QUERIES_PER_HOUR", 0);
+ add_user_option(&global, acl_user->user_resource.updates,
+ "MAX_UPDATES_PER_HOUR", 0);
+ add_user_option(&global, acl_user->user_resource.conn_per_hour,
+ "MAX_CONNECTIONS_PER_HOUR", 0);
+ add_user_option(&global, acl_user->user_resource.user_conn,
+ "MAX_USER_CONNECTIONS", 1);
+ }
+ }
+
+ protocol->prepare_for_resend();
+ protocol->store(global.ptr(),global.length(),global.charset());
+ if (protocol->write())
+ return TRUE;
+
+ return FALSE;
+
+}
+
+static bool show_database_privileges(THD *thd, const char *username,
+ const char *hostname,
+ char *buff, size_t buffsize)
+{
+ ACL_DB *acl_db;
+ ulong want_access;
+ uint counter;
+ Protocol *protocol= thd->protocol;
+
+ for (counter=0 ; counter < acl_dbs.elements ; counter++)
+ {
+ const char *user, *host;
+
+ acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*);
+ user= safe_str(acl_db->user);
+ host=acl_db->host.hostname;
+
+ /*
+ We do not make SHOW GRANTS case-sensitive here (like REVOKE),
+ but make it case-insensitive because that's the way they are
+ actually applied, and showing fewer privileges than are applied
+ would be wrong from a security point of view.
+ */
+
+ if (!strcmp(username, user) &&
+ !my_strcasecmp(system_charset_info, hostname, host))
+ {
+ /*
+ do not print inherited access bits for roles,
+ the role bits present in the table are what matters
+ */
+ if (*hostname) // User
+ want_access=acl_db->access;
+ else // Role
+ want_access=acl_db->initial_access;
+ if (want_access)
+ {
+ String db(buff,sizeof(buff),system_charset_info);
+ db.length(0);
+ db.append(STRING_WITH_LEN("GRANT "));
+
+ if (test_all_bits(want_access,(DB_ACLS & ~GRANT_ACL)))
+ db.append(STRING_WITH_LEN("ALL PRIVILEGES"));
+ else if (!(want_access & ~GRANT_ACL))
+ db.append(STRING_WITH_LEN("USAGE"));
+ else
+ {
+ int found=0, cnt;
+ ulong j,test_access= want_access & ~GRANT_ACL;
+ for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1)
+ {
+ if (test_access & j)
+ {
+ if (found)
+ db.append(STRING_WITH_LEN(", "));
+ found = 1;
+ db.append(command_array[cnt],command_lengths[cnt]);
+ }
+ }
+ }
+ db.append (STRING_WITH_LEN(" ON "));
+ append_identifier(thd, &db, acl_db->db, strlen(acl_db->db));
+ db.append (STRING_WITH_LEN(".* TO '"));
+ db.append(username, strlen(username),
+ system_charset_info);
+ if (*hostname)
+ {
+ db.append (STRING_WITH_LEN("'@'"));
+ // host and lex_user->host are equal except for case
+ db.append(host, strlen(host), system_charset_info);
+ }
+ db.append ('\'');
+ if (want_access & GRANT_ACL)
+ db.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
+ protocol->prepare_for_resend();
+ protocol->store(db.ptr(),db.length(),db.charset());
+ if (protocol->write())
+ {
+ return TRUE;
+ }
+ }
+ }
+ }
+ return FALSE;
+
+}
+
+static bool show_table_and_column_privileges(THD *thd, const char *username,
+ const char *hostname,
+ char *buff, size_t buffsize)
+{
+ uint counter, index;
+ Protocol *protocol= thd->protocol;
+
+ for (index=0 ; index < column_priv_hash.records ; index++)
+ {
+ const char *user, *host;
+ GRANT_TABLE *grant_table= (GRANT_TABLE*)
+ my_hash_element(&column_priv_hash, index);
+
+ user= safe_str(grant_table->user);
+ host= grant_table->host.hostname;
+
+ /*
+ We do not make SHOW GRANTS case-sensitive here (like REVOKE),
+ but make it case-insensitive because that's the way they are
+ actually applied, and showing fewer privileges than are applied
+ would be wrong from a security point of view.
+ */
+
+ if (!strcmp(username,user) &&
+ !my_strcasecmp(system_charset_info, hostname, host))
+ {
+ ulong table_access;
+ ulong cols_access;
+ if (*hostname) // User
+ {
+ table_access= grant_table->privs;
+ cols_access= grant_table->cols;
+ }
+ else // Role
+ {
+ table_access= grant_table->init_privs;
+ cols_access= grant_table->init_cols;
+ }
+
+ if ((table_access | cols_access) != 0)
+ {
+ String global(buff, sizeof(buff), system_charset_info);
+ ulong test_access= (table_access | cols_access) & ~GRANT_ACL;
+
+ global.length(0);
+ global.append(STRING_WITH_LEN("GRANT "));
+
+ if (test_all_bits(table_access, (TABLE_ACLS & ~GRANT_ACL)))
+ global.append(STRING_WITH_LEN("ALL PRIVILEGES"));
+ else if (!test_access)
+ global.append(STRING_WITH_LEN("USAGE"));
+ else
+ {
+ /* Add specific column access */
+ int found= 0;
+ ulong j;
+
+ for (counter= 0, j= SELECT_ACL; j <= TABLE_ACLS; counter++, j<<= 1)
+ {
+ if (test_access & j)
+ {
+ if (found)
+ global.append(STRING_WITH_LEN(", "));
+ found= 1;
+ global.append(command_array[counter],command_lengths[counter]);
+
+ if (grant_table->cols)
+ {
+ uint found_col= 0;
+ HASH *hash_columns;
+ hash_columns= &grant_table->hash_columns;
+
+ for (uint col_index=0 ;
+ col_index < hash_columns->records ;
+ col_index++)
+ {
+ GRANT_COLUMN *grant_column = (GRANT_COLUMN*)
+ my_hash_element(hash_columns,col_index);
+ if (j & (*hostname ? grant_column->rights // User
+ : grant_column->init_rights)) // Role
+ {
+ if (!found_col)
+ {
+ found_col= 1;
+ /*
+ If we have a duplicated table level privilege, we
+ must write the access privilege name again.
+ */
+ if (table_access & j)
+ {
+ global.append(STRING_WITH_LEN(", "));
+ global.append(command_array[counter],
+ command_lengths[counter]);
+ }
+ global.append(STRING_WITH_LEN(" ("));
+ }
+ else
+ global.append(STRING_WITH_LEN(", "));
+ global.append(grant_column->column,
+ grant_column->key_length,
+ system_charset_info);
+ }
+ }
+ if (found_col)
+ global.append(')');
+ }
+ }
+ }
+ }
+ global.append(STRING_WITH_LEN(" ON "));
+ append_identifier(thd, &global, grant_table->db,
+ strlen(grant_table->db));
+ global.append('.');
+ append_identifier(thd, &global, grant_table->tname,
+ strlen(grant_table->tname));
+ global.append(STRING_WITH_LEN(" TO '"));
+ global.append(username, strlen(username),
+ system_charset_info);
+ if (*hostname)
+ {
+ global.append(STRING_WITH_LEN("'@'"));
+ // host and lex_user->host are equal except for case
+ global.append(host, strlen(host), system_charset_info);
+ }
+ global.append('\'');
+ if (table_access & GRANT_ACL)
+ global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
+ protocol->prepare_for_resend();
+ protocol->store(global.ptr(),global.length(),global.charset());
+ if (protocol->write())
+ {
+ return TRUE;
+ }
+ }
+ }
+ }
+ return FALSE;
+
+}
+
+static int show_routine_grants(THD* thd,
+ const char *username, const char *hostname,
+ HASH *hash, const char *type, int typelen,
+ char *buff, int buffsize)
+{
+ uint counter, index;
+ int error= 0;
+ Protocol *protocol= thd->protocol;
+ /* Add routine access */
+ for (index=0 ; index < hash->records ; index++)
+ {
+ const char *user, *host;
+ GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, index);
+
+ user= safe_str(grant_proc->user);
+ host= grant_proc->host.hostname;
+
+ /*
+ We do not make SHOW GRANTS case-sensitive here (like REVOKE),
+ but make it case-insensitive because that's the way they are
+ actually applied, and showing fewer privileges than are applied
+ would be wrong from a security point of view.
+ */
+
+ if (!strcmp(username, user) &&
+ !my_strcasecmp(system_charset_info, hostname, host))
+ {
+ ulong proc_access;
+ if (*hostname) // User
+ proc_access= grant_proc->privs;
+ else // Role
+ proc_access= grant_proc->init_privs;
+
+ if (proc_access != 0)
+ {
+ String global(buff, buffsize, system_charset_info);
+ ulong test_access= proc_access & ~GRANT_ACL;
+
+ global.length(0);
+ global.append(STRING_WITH_LEN("GRANT "));
+
+ if (!test_access)
+ global.append(STRING_WITH_LEN("USAGE"));
+ else
+ {
+ /* Add specific procedure access */
+ int found= 0;
+ ulong j;
+
+ for (counter= 0, j= SELECT_ACL; j <= PROC_ACLS; counter++, j<<= 1)
+ {
+ if (test_access & j)
+ {
+ if (found)
+ global.append(STRING_WITH_LEN(", "));
+ found= 1;
+ global.append(command_array[counter],command_lengths[counter]);
+ }
+ }
+ }
+ global.append(STRING_WITH_LEN(" ON "));
+ global.append(type,typelen);
+ global.append(' ');
+ append_identifier(thd, &global, grant_proc->db,
+ strlen(grant_proc->db));
+ global.append('.');
+ append_identifier(thd, &global, grant_proc->tname,
+ strlen(grant_proc->tname));
+ global.append(STRING_WITH_LEN(" TO '"));
+ global.append(username, strlen(username),
+ system_charset_info);
+ if (*hostname)
+ {
+ global.append(STRING_WITH_LEN("'@'"));
+ // host and lex_user->host are equal except for case
+ global.append(host, strlen(host), system_charset_info);
+ }
+ global.append('\'');
+ if (proc_access & GRANT_ACL)
+ global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
+ protocol->prepare_for_resend();
+ protocol->store(global.ptr(),global.length(),global.charset());
+ if (protocol->write())
+ {
+ error= -1;
+ break;
+ }
+ }
+ }
+ }
+ return error;
+}
+
+
+/*
+ Make a clear-text version of the requested privilege.
+*/
+
+void get_privilege_desc(char *to, uint max_length, ulong access)
+{
+ uint pos;
+ char *start=to;
+ DBUG_ASSERT(max_length >= 30); // For end ', ' removal
+
+ if (access)
+ {
+ max_length--; // Reserve place for end-zero
+ for (pos=0 ; access ; pos++, access>>=1)
+ {
+ if ((access & 1) &&
+ command_lengths[pos] + (uint) (to-start) < max_length)
+ {
+ to= strmov(to, command_array[pos]);
+ *to++= ',';
+ *to++= ' ';
+ }
+ }
+ to--; // Remove end ' '
+ to--; // Remove end ','
+ }
+ *to=0;
+}
+
+
+void get_mqh(const char *user, const char *host, USER_CONN *uc)
+{
+ ACL_USER *acl_user;
+
+ mysql_mutex_lock(&acl_cache->lock);
+
+ if (initialized && (acl_user= find_user_wild(host,user)))
+ uc->user_resources= acl_user->user_resource;
+ else
+ bzero((char*) &uc->user_resources, sizeof(uc->user_resources));
+
+ mysql_mutex_unlock(&acl_cache->lock);
+}
+
+/*
+ Open the grant tables.
+
+ SYNOPSIS
+ open_grant_tables()
+ thd The current thread.
+ tables (out) The 7 elements array for the opened tables.
+
+ DESCRIPTION
+ Tables are numbered as follows:
+ 0 user
+ 1 db
+ 2 tables_priv
+ 3 columns_priv
+ 4 procs_priv
+ 5 proxies_priv
+ 6 roles_mapping
+
+ RETURN
+ 1 Skip GRANT handling during replication.
+ 0 OK.
+ < 0 Error.
+*/
+
+#define GRANT_TABLES 7
+static int open_grant_tables(THD *thd, TABLE_LIST *tables)
+{
+ Rpl_filter *rpl_filter;
+ DBUG_ENTER("open_grant_tables");
+
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
+ DBUG_RETURN(-1);
+ }
+
+ tables->init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("user"), "user", TL_WRITE);
+ (tables+1)->init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("db"), "db", TL_WRITE);
+ (tables+2)->init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("tables_priv"),
+ "tables_priv", TL_WRITE);
+ (tables+3)->init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("columns_priv"),
+ "columns_priv", TL_WRITE);
+ (tables+4)->init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("procs_priv"),
+ "procs_priv", TL_WRITE);
+ (tables+5)->init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("proxies_priv"),
+ "proxies_priv", TL_WRITE);
+ (tables+5)->open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
+ (tables+6)->init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("roles_mapping"),
+ "roles_mapping", TL_WRITE);
+ (tables+6)->open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
+
+
+ tables->next_local= tables->next_global= tables + 1;
+ (tables+1)->next_local= (tables+1)->next_global= tables + 2;
+ (tables+2)->next_local= (tables+2)->next_global= tables + 3;
+ (tables+3)->next_local= (tables+3)->next_global= tables + 4;
+ (tables+4)->next_local= (tables+4)->next_global= tables + 5;
+ (tables+5)->next_local= (tables+5)->next_global= tables + 6;
+
+#ifdef HAVE_REPLICATION
+ /*
+ GRANT and REVOKE are applied the slave in/exclusion rules as they are
+ some kind of updates to the mysql.% tables.
+ */
+ if (thd->slave_thread &&
+ (rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter)->is_on())
+ {
+ /*
+ The tables must be marked "updating" so that tables_ok() takes them into
+ account in tests.
+ */
+ tables[0].updating= tables[1].updating= tables[2].updating=
+ tables[3].updating= tables[4].updating= tables[5].updating=
+ tables[6].updating= 1;
+ if (!(thd->spcont || rpl_filter->tables_ok(0, tables)))
+ DBUG_RETURN(1);
+ tables[0].updating= tables[1].updating= tables[2].updating=
+ tables[3].updating= tables[4].updating= tables[5].updating=
+ tables[6].updating= 0;
+ }
+#endif
+
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ { // This should never happen
+ DBUG_RETURN(-1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+ACL_USER *check_acl_user(LEX_USER *user_name, uint *acl_acl_userdx)
+{
+ ACL_USER *acl_user= 0;
+ uint counter;
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ for (counter= 0 ; counter < acl_users.elements ; counter++)
+ {
+ acl_user= dynamic_element(&acl_users, counter, ACL_USER*);
+ if(acl_user->eq(user_name->user.str, user_name->host.str))
+ break;
+ }
+ if (counter == acl_users.elements)
+ return 0;
+
+ *acl_acl_userdx= counter;
+ return acl_user;
+}
+
+/*
+ Modify a privilege table.
+
+ SYNOPSIS
+ modify_grant_table()
+ table The table to modify.
+ host_field The host name field.
+ user_field The user name field.
+ user_to The new name for the user if to be renamed,
+ NULL otherwise.
+
+ DESCRIPTION
+ Update user/host in the current record if user_to is not NULL.
+ Delete the current record if user_to is NULL.
+
+ RETURN
+ 0 OK.
+ != 0 Error.
+*/
+
+static int modify_grant_table(TABLE *table, Field *host_field,
+ Field *user_field, LEX_USER *user_to)
+{
+ int error;
+ DBUG_ENTER("modify_grant_table");
+
+ if (user_to)
+ {
+ /* rename */
+ store_record(table, record[1]);
+ host_field->store(user_to->host.str, user_to->host.length,
+ system_charset_info);
+ user_field->store(user_to->user.str, user_to->user.length,
+ system_charset_info);
+ if ((error= table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ table->file->print_error(error, MYF(0));
+ else
+ error= 0;
+ }
+ else
+ {
+ /* delete */
+ if ((error=table->file->ha_delete_row(table->record[0])))
+ table->file->print_error(error, MYF(0));
+ }
+
+ DBUG_RETURN(error);
+}
+
+/*
+ Handle the roles_mappings privilege table
+*/
+static int handle_roles_mappings_table(TABLE *table, bool drop,
+ LEX_USER *user_from, LEX_USER *user_to)
+{
+ /*
+ All entries (Host, User) that match user_from will be renamed,
+ as well as all Role entries that match if user_from.host.str == ""
+
+ Otherwise, only matching (Host, User) will be renamed.
+ */
+ DBUG_ENTER("handle_roles_mappings_table");
+
+ int error;
+ int result= 0;
+ THD *thd= current_thd;
+ const char *host, *user, *role;
+ Field *host_field= table->field[0];
+ Field *user_field= table->field[1];
+ Field *role_field= table->field[2];
+
+ DBUG_PRINT("info", ("Rewriting entry in roles_mappings table: %s@%s",
+ user_from->user.str, user_from->host.str));
+ table->use_all_columns();
+ if ((error= table->file->ha_rnd_init(1)))
+ {
+ table->file->print_error(error, MYF(0));
+ result= -1;
+ }
+ else
+ {
+ while((error= table->file->ha_rnd_next(table->record[0])) !=
+ HA_ERR_END_OF_FILE)
+ {
+ if (error)
+ {
+ DBUG_PRINT("info", ("scan error: %d", error));
+ continue;
+ }
+
+ host= safe_str(get_field(thd->mem_root, host_field));
+ user= safe_str(get_field(thd->mem_root, user_field));
+
+ if (!(strcmp(user_from->user.str, user) ||
+ my_strcasecmp(system_charset_info, user_from->host.str, host)))
+ result= ((drop || user_to) &&
+ modify_grant_table(table, host_field, user_field, user_to)) ?
+ -1 : result ? result : 1; /* Error or keep result or found. */
+ else
+ {
+ role= safe_str(get_field(thd->mem_root, role_field));
+
+ if (!user_from->is_role() || strcmp(user_from->user.str, role))
+ continue;
+
+ error= 0;
+
+ if (drop) /* drop if requested */
+ {
+ if ((error= table->file->ha_delete_row(table->record[0])))
+ table->file->print_error(error, MYF(0));
+ }
+ else if (user_to)
+ {
+ store_record(table, record[1]);
+ role_field->store(user_to->user.str, user_to->user.length,
+ system_charset_info);
+ if ((error= table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ table->file->print_error(error, MYF(0));
+ }
+
+ /* Error or keep result or found. */
+ result= error ? -1 : result ? result : 1;
+ }
+ }
+ table->file->ha_rnd_end();
+ }
+ DBUG_RETURN(result);
+}
+
+/*
+ Handle a privilege table.
+
+ SYNOPSIS
+ handle_grant_table()
+ tables The array with the four open tables.
+ table_no The number of the table to handle (0..4).
+ drop If user_from is to be dropped.
+ user_from The the user to be searched/dropped/renamed.
+ user_to The new name for the user if to be renamed,
+ NULL otherwise.
+
+ DESCRIPTION
+ Scan through all records in a grant table and apply the requested
+ operation. For the "user" table, a single index access is sufficient,
+ since there is an unique index on (host, user).
+ Delete from grant table if drop is true.
+ Update in grant table if drop is false and user_to is not NULL.
+ Search in grant table if drop is false and user_to is NULL.
+ Tables are numbered as follows:
+ 0 user
+ 1 db
+ 2 tables_priv
+ 3 columns_priv
+ 4 procs_priv
+ 5 proxies_priv
+ 6 roles_mapping
+
+ RETURN
+ > 0 At least one record matched.
+ 0 OK, but no record matched.
+ < 0 Error.
+*/
+
+static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop,
+ LEX_USER *user_from, LEX_USER *user_to)
+{
+ int result= 0;
+ int error;
+ TABLE *table= tables[table_no].table;
+ Field *host_field= table->field[0];
+ Field *user_field= table->field[table_no && table_no != 5 ? 2 : 1];
+ const char *host_str= user_from->host.str;
+ const char *user_str= user_from->user.str;
+ const char *host;
+ const char *user;
+ uchar user_key[MAX_KEY_LENGTH];
+ uint key_prefix_length;
+ DBUG_ENTER("handle_grant_table");
+ THD *thd= current_thd;
+
+ if (table_no == 6)
+ {
+ result= handle_roles_mappings_table(table, drop, user_from, user_to);
+ DBUG_RETURN(result);
+ }
+
+ table->use_all_columns();
+ if (! table_no) // mysql.user table
+ {
+ /*
+ The 'user' table has an unique index on (host, user).
+ Thus, we can handle everything with a single index access.
+ The host- and user fields are consecutive in the user table records.
+ So we set host- and user fields of table->record[0] and use the
+ pointer to the host field as key.
+ index_read_idx() will replace table->record[0] (its first argument)
+ by the searched record, if it exists.
+ */
+ DBUG_PRINT("info",("read table: '%s' search: '%s'@'%s'",
+ table->s->table_name.str, user_str, host_str));
+ host_field->store(host_str, user_from->host.length, system_charset_info);
+ user_field->store(user_str, user_from->user.length, system_charset_info);
+
+ key_prefix_length= (table->key_info->key_part[0].store_length +
+ table->key_info->key_part[1].store_length);
+ key_copy(user_key, table->record[0], table->key_info, key_prefix_length);
+
+ error= table->file->ha_index_read_idx_map(table->record[0], 0,
+ user_key, (key_part_map)3,
+ HA_READ_KEY_EXACT);
+ if (!error && !*host_str)
+ { // verify that we got a role or a user, as needed
+ if (check_is_role(table) != user_from->is_role())
+ error= HA_ERR_KEY_NOT_FOUND;
+ }
+ if (error)
+ {
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ {
+ table->file->print_error(error, MYF(0));
+ result= -1;
+ }
+ }
+ else
+ {
+ /* If requested, delete or update the record. */
+ result= ((drop || user_to) &&
+ modify_grant_table(table, host_field, user_field, user_to)) ?
+ -1 : 1; /* Error or found. */
+ }
+ DBUG_PRINT("info",("read result: %d", result));
+ }
+ else
+ {
+ /*
+ The non-'user' table do not have indexes on (host, user).
+ And their host- and user fields are not consecutive.
+ Thus, we need to do a table scan to find all matching records.
+ */
+ if ((error= table->file->ha_rnd_init(1)))
+ {
+ table->file->print_error(error, MYF(0));
+ result= -1;
+ }
+ else
+ {
+#ifdef EXTRA_DEBUG
+ DBUG_PRINT("info",("scan table: '%s' search: '%s'@'%s'",
+ table->s->table_name.str, user_str, host_str));
+#endif
+ while ((error= table->file->ha_rnd_next(table->record[0])) !=
+ HA_ERR_END_OF_FILE)
+ {
+ if (error)
+ {
+ /* Most probable 'deleted record'. */
+ DBUG_PRINT("info",("scan error: %d", error));
+ continue;
+ }
+ host= safe_str(get_field(thd->mem_root, host_field));
+ user= safe_str(get_field(thd->mem_root, user_field));
+
+#ifdef EXTRA_DEBUG
+ if (table_no != 5)
+ {
+ DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'",
+ user, host,
+ get_field(thd->mem_root, table->field[1]) /*db*/,
+ get_field(thd->mem_root, table->field[3]) /*table*/,
+ get_field(thd->mem_root,
+ table->field[4]) /*column*/));
+ }
+#endif
+ if (strcmp(user_str, user) ||
+ my_strcasecmp(system_charset_info, host_str, host))
+ continue;
+
+ /* If requested, delete or update the record. */
+ result= ((drop || user_to) &&
+ modify_grant_table(table, host_field, user_field, user_to)) ?
+ -1 : result ? result : 1; /* Error or keep result or found. */
+ /* If search is requested, we do not need to search further. */
+ if (! drop && ! user_to)
+ break ;
+ }
+ (void) table->file->ha_rnd_end();
+ DBUG_PRINT("info",("scan result: %d", result));
+ }
+ }
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Handle an in-memory privilege structure.
+
+ @param struct_no The number of the structure to handle (0..6).
+ @param drop If user_from is to be dropped.
+ @param user_from The the user to be searched/dropped/renamed.
+ @param user_to The new name for the user if to be renamed, NULL otherwise.
+
+ @note
+ Scan through all elements in an in-memory grant structure and apply
+ the requested operation.
+ Delete from grant structure if drop is true.
+ Update in grant structure if drop is false and user_to is not NULL.
+ Search in grant structure if drop is false and user_to is NULL.
+
+ @retval > 0 At least one element matched.
+ @retval 0 OK, but no element matched.
+*/
+
+static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop,
+ LEX_USER *user_from, LEX_USER *user_to)
+{
+ int result= 0;
+ int idx;
+ int elements;
+ const char *UNINIT_VAR(user);
+ const char *UNINIT_VAR(host);
+ ACL_USER *acl_user= NULL;
+ ACL_ROLE *acl_role= NULL;
+ ACL_DB *acl_db= NULL;
+ ACL_PROXY_USER *acl_proxy_user= NULL;
+ GRANT_NAME *grant_name= NULL;
+ ROLE_GRANT_PAIR *UNINIT_VAR(role_grant_pair);
+ HASH *grant_name_hash= NULL;
+ HASH *roles_mappings_hash= NULL;
+ DBUG_ENTER("handle_grant_struct");
+ DBUG_PRINT("info",("scan struct: %u search: '%s'@'%s'",
+ struct_no, user_from->user.str, user_from->host.str));
+
+ mysql_mutex_assert_owner(&acl_cache->lock);
+
+ /* No point in querying ROLE ACL if user_from is not a role */
+ if (struct_no == ROLE_ACL && user_from->host.length)
+ DBUG_RETURN(0);
+
+ /* same. no roles in PROXY_USERS_ACL */
+ if (struct_no == PROXY_USERS_ACL && user_from->is_role())
+ DBUG_RETURN(0);
+
+ if (struct_no == ROLE_ACL) //no need to scan the structures in this case
+ {
+ acl_role= find_acl_role(user_from->user.str);
+ if (!acl_role)
+ DBUG_RETURN(0);
+
+ if (!drop && !user_to) //role was found
+ DBUG_RETURN(1);
+
+ /* this calls for a role update */
+ char *old_key= acl_role->user.str;
+ size_t old_key_length= acl_role->user.length;
+ if (drop)
+ {
+ /* all grants must be revoked from this role by now. propagate this */
+ propagate_role_grants(acl_role, PRIVS_TO_MERGE::ALL);
+
+ // delete the role from cross-reference arrays
+ for (uint i=0; i < acl_role->role_grants.elements; i++)
+ {
+ ACL_ROLE *grant= *dynamic_element(&acl_role->role_grants,
+ i, ACL_ROLE**);
+ remove_ptr_from_dynarray(&grant->parent_grantee, acl_role);
+ }
+
+ for (uint i=0; i < acl_role->parent_grantee.elements; i++)
+ {
+ ACL_USER_BASE *grantee= *dynamic_element(&acl_role->parent_grantee,
+ i, ACL_USER_BASE**);
+ remove_ptr_from_dynarray(&grantee->role_grants, acl_role);
+ }
+
+ my_hash_delete(&acl_roles, (uchar*) acl_role);
+ DBUG_RETURN(1);
+ }
+ acl_role->user.str= strdup_root(&acl_memroot, user_to->user.str);
+ acl_role->user.length= user_to->user.length;
+
+ my_hash_update(&acl_roles, (uchar*) acl_role, (uchar*) old_key,
+ old_key_length);
+ DBUG_RETURN(1);
+
+ }
+
+ /* Get the number of elements in the in-memory structure. */
+ switch (struct_no) {
+ case USER_ACL:
+ elements= acl_users.elements;
+ break;
+ case DB_ACL:
+ elements= acl_dbs.elements;
+ break;
+ case COLUMN_PRIVILEGES_HASH:
+ grant_name_hash= &column_priv_hash;
+ elements= grant_name_hash->records;
+ break;
+ case PROC_PRIVILEGES_HASH:
+ grant_name_hash= &proc_priv_hash;
+ elements= grant_name_hash->records;
+ break;
+ case FUNC_PRIVILEGES_HASH:
+ grant_name_hash= &func_priv_hash;
+ elements= grant_name_hash->records;
+ break;
+ case PROXY_USERS_ACL:
+ elements= acl_proxy_users.elements;
+ break;
+ case ROLES_MAPPINGS_HASH:
+ roles_mappings_hash= &acl_roles_mappings;
+ elements= roles_mappings_hash->records;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ DBUG_RETURN(-1);
+ }
+
+#ifdef EXTRA_DEBUG
+ DBUG_PRINT("loop",("scan struct: %u search user: '%s' host: '%s'",
+ struct_no, user_from->user.str, user_from->host.str));
+#endif
+ /* Loop over all elements *backwards* (see the comment below). */
+ for (idx= elements - 1; idx >= 0; idx--)
+ {
+ /*
+ Get a pointer to the element.
+ */
+ switch (struct_no) {
+ case USER_ACL:
+ acl_user= dynamic_element(&acl_users, idx, ACL_USER*);
+ user= acl_user->user.str;
+ host= acl_user->host.hostname;
+ break;
+
+ case DB_ACL:
+ acl_db= dynamic_element(&acl_dbs, idx, ACL_DB*);
+ user= acl_db->user;
+ host= acl_db->host.hostname;
+ break;
+
+ case COLUMN_PRIVILEGES_HASH:
+ case PROC_PRIVILEGES_HASH:
+ case FUNC_PRIVILEGES_HASH:
+ grant_name= (GRANT_NAME*) my_hash_element(grant_name_hash, idx);
+ user= grant_name->user;
+ host= grant_name->host.hostname;
+ break;
+
+ case PROXY_USERS_ACL:
+ acl_proxy_user= dynamic_element(&acl_proxy_users, idx, ACL_PROXY_USER*);
+ user= acl_proxy_user->get_user();
+ host= acl_proxy_user->get_host();
+ break;
+
+ case ROLES_MAPPINGS_HASH:
+ role_grant_pair= (ROLE_GRANT_PAIR *) my_hash_element(roles_mappings_hash, idx);
+ user= role_grant_pair->u_uname;
+ host= role_grant_pair->u_hname;
+ break;
+
+ default:
+ DBUG_ASSERT(0);
+ }
+ if (! user)
+ user= "";
+ if (! host)
+ host= "";
+
+#ifdef EXTRA_DEBUG
+ DBUG_PRINT("loop",("scan struct: %u index: %u user: '%s' host: '%s'",
+ struct_no, idx, user, host));
+#endif
+
+ if (struct_no == ROLES_MAPPINGS_HASH)
+ {
+ const char* role= role_grant_pair->r_uname? role_grant_pair->r_uname: "";
+ if (user_from->is_role())
+ {
+ /* When searching for roles within the ROLES_MAPPINGS_HASH, we have
+ to check both the user field as well as the role field for a match.
+
+ It is possible to have a role granted to a role. If we are going
+ to modify the mapping entry, it needs to be done on either on the
+ "user" end (here represented by a role) or the "role" end. At least
+ one part must match.
+
+ If the "user" end has a not-empty host string, it can never match
+ as we are searching for a role here. A role always has an empty host
+ string.
+ */
+ if ((*host || strcmp(user_from->user.str, user)) &&
+ strcmp(user_from->user.str, role))
+ continue;
+ }
+ else
+ {
+ if (strcmp(user_from->user.str, user) ||
+ my_strcasecmp(system_charset_info, user_from->host.str, host))
+ continue;
+ }
+ }
+ else
+ {
+ if (strcmp(user_from->user.str, user) ||
+ my_strcasecmp(system_charset_info, user_from->host.str, host))
+ continue;
+ }
+
+ result= 1; /* At least one element found. */
+ if ( drop )
+ {
+ elements--;
+ switch ( struct_no ) {
+ case USER_ACL:
+ free_acl_user(dynamic_element(&acl_users, idx, ACL_USER*));
+ delete_dynamic_element(&acl_users, idx);
+ break;
+
+ case DB_ACL:
+ delete_dynamic_element(&acl_dbs, idx);
+ break;
+
+ case COLUMN_PRIVILEGES_HASH:
+ case PROC_PRIVILEGES_HASH:
+ case FUNC_PRIVILEGES_HASH:
+ my_hash_delete(grant_name_hash, (uchar*) grant_name);
+ /*
+ In our HASH implementation on deletion one elements
+ is moved into a place where a deleted element was,
+ and the last element is moved into the empty space.
+ Thus we need to re-examine the current element, but
+ we don't have to restart the search from the beginning.
+ */
+ if (idx != elements)
+ idx++;
+ break;
+
+ case PROXY_USERS_ACL:
+ delete_dynamic_element(&acl_proxy_users, idx);
+ break;
+
+ case ROLES_MAPPINGS_HASH:
+ my_hash_delete(roles_mappings_hash, (uchar*) role_grant_pair);
+ if (idx != elements)
+ idx++;
+ break;
+
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ }
+ else if ( user_to )
+ {
+ switch ( struct_no ) {
+ case USER_ACL:
+ acl_user->user.str= strdup_root(&acl_memroot, user_to->user.str);
+ acl_user->user.length= user_to->user.length;
+ acl_user->host.hostname= strdup_root(&acl_memroot, user_to->host.str);
+ acl_user->hostname_length= user_to->host.length;
+ break;
+
+ case DB_ACL:
+ acl_db->user= strdup_root(&acl_memroot, user_to->user.str);
+ acl_db->host.hostname= strdup_root(&acl_memroot, user_to->host.str);
+ break;
+
+ case COLUMN_PRIVILEGES_HASH:
+ case PROC_PRIVILEGES_HASH:
+ case FUNC_PRIVILEGES_HASH:
+ {
+ /*
+ Save old hash key and its length to be able to properly update
+ element position in hash.
+ */
+ char *old_key= grant_name->hash_key;
+ size_t old_key_length= grant_name->key_length;
+
+ /*
+ Update the grant structure with the new user name and host name.
+ */
+ grant_name->set_user_details(user_to->host.str, grant_name->db,
+ user_to->user.str, grant_name->tname,
+ TRUE);
+
+ /*
+ Since username is part of the hash key, when the user name
+ is renamed, the hash key is changed. Update the hash to
+ ensure that the position matches the new hash key value
+ */
+ my_hash_update(grant_name_hash, (uchar*) grant_name, (uchar*) old_key,
+ old_key_length);
+ /*
+ hash_update() operation could have moved element from the tail or
+ the head of the hash to the current position. But it can never
+ move an element from the head to the tail or from the tail to the
+ head over the current element.
+ So we need to examine the current element once again, but
+ we don't need to restart the search from the beginning.
+ */
+ if (idx != elements)
+ idx++;
+ break;
+ }
+
+ case PROXY_USERS_ACL:
+ acl_proxy_user->set_user (&acl_memroot, user_to->user.str);
+ acl_proxy_user->set_host (&acl_memroot, user_to->host.str);
+ break;
+
+ case ROLES_MAPPINGS_HASH:
+ {
+ /*
+ Save old hash key and its length to be able to properly update
+ element position in hash.
+ */
+ char *old_key= role_grant_pair->hashkey.str;
+ size_t old_key_length= role_grant_pair->hashkey.length;
+ bool oom;
+
+ if (user_to->is_role())
+ oom= role_grant_pair->init(&acl_memroot, role_grant_pair->u_uname,
+ role_grant_pair->u_hname,
+ user_to->user.str, false);
+ else
+ oom= role_grant_pair->init(&acl_memroot, user_to->user.str,
+ user_to->host.str,
+ role_grant_pair->r_uname, false);
+ if (oom)
+ DBUG_RETURN(-1);
+
+ my_hash_update(roles_mappings_hash, (uchar*) role_grant_pair,
+ (uchar*) old_key, old_key_length);
+ break;
+ }
+
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+
+ }
+ else
+ {
+ /* If search is requested, we do not need to search further. */
+ break;
+ }
+ }
+#ifdef EXTRA_DEBUG
+ DBUG_PRINT("loop",("scan struct: %u result %d", struct_no, result));
+#endif
+
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Handle all privilege tables and in-memory privilege structures.
+
+ SYNOPSIS
+ handle_grant_data()
+ tables The array with the four open tables.
+ drop If user_from is to be dropped.
+ user_from The the user to be searched/dropped/renamed.
+ user_to The new name for the user if to be renamed,
+ NULL otherwise.
+
+ DESCRIPTION
+ Go through all grant tables and in-memory grant structures and apply
+ the requested operation.
+ Delete from grant data if drop is true.
+ Update in grant data if drop is false and user_to is not NULL.
+ Search in grant data if drop is false and user_to is NULL.
+
+ RETURN
+ > 0 At least one element matched.
+ 0 OK, but no element matched.
+ < 0 Error.
+*/
+
+static int handle_grant_data(TABLE_LIST *tables, bool drop,
+ LEX_USER *user_from, LEX_USER *user_to)
+{
+ int result= 0;
+ int found;
+ bool handle_as_role= user_from->is_role();
+ bool search_only= !drop && !user_to;
+ DBUG_ENTER("handle_grant_data");
+
+ if (user_to)
+ DBUG_ASSERT(handle_as_role == user_to->is_role());
+
+ if (search_only)
+ {
+ /* quickly search in-memory structures first */
+ if (handle_as_role && find_acl_role(user_from->user.str))
+ DBUG_RETURN(1); // found
+
+ if (!handle_as_role && find_user_exact(user_from->host.str, user_from->user.str))
+ DBUG_RETURN(1); // found
+ }
+
+ /* Handle db table. */
+ if ((found= handle_grant_table(tables, 1, drop, user_from, user_to)) < 0)
+ {
+ /* Handle of table failed, don't touch the in-memory array. */
+ result= -1;
+ }
+ else
+ {
+ /* Handle db array. */
+ if ((handle_grant_struct(DB_ACL, drop, user_from, user_to) || found)
+ && ! result)
+ {
+ result= 1; /* At least one record/element found. */
+ /* If search is requested, we do not need to search further. */
+ if (search_only)
+ goto end;
+ acl_cache->clear(1);
+ }
+ }
+
+ /* Handle stored routines table. */
+ if ((found= handle_grant_table(tables, 4, drop, user_from, user_to)) < 0)
+ {
+ /* Handle of table failed, don't touch in-memory array. */
+ result= -1;
+ }
+ else
+ {
+ /* Handle procs array. */
+ if ((handle_grant_struct(PROC_PRIVILEGES_HASH, drop, user_from, user_to) || found)
+ && ! result)
+ {
+ result= 1; /* At least one record/element found. */
+ /* If search is requested, we do not need to search further. */
+ if (search_only)
+ goto end;
+ }
+ /* Handle funcs array. */
+ if ((handle_grant_struct(FUNC_PRIVILEGES_HASH, drop, user_from, user_to) || found)
+ && ! result)
+ {
+ result= 1; /* At least one record/element found. */
+ /* If search is requested, we do not need to search further. */
+ if (search_only)
+ goto end;
+ }
+ }
+
+ /* Handle tables table. */
+ if ((found= handle_grant_table(tables, 2, drop, user_from, user_to)) < 0)
+ {
+ /* Handle of table failed, don't touch columns and in-memory array. */
+ result= -1;
+ }
+ else
+ {
+ if (found && ! result)
+ {
+ result= 1; /* At least one record found. */
+ /* If search is requested, we do not need to search further. */
+ if (search_only)
+ goto end;
+ }
+
+ /* Handle columns table. */
+ if ((found= handle_grant_table(tables, 3, drop, user_from, user_to)) < 0)
+ {
+ /* Handle of table failed, don't touch the in-memory array. */
+ result= -1;
+ }
+ else
+ {
+ /* Handle columns hash. */
+ if ((handle_grant_struct(COLUMN_PRIVILEGES_HASH, drop, user_from, user_to) || found)
+ && ! result)
+ result= 1; /* At least one record/element found. */
+ if (search_only)
+ goto end;
+ }
+ }
+
+ /* Handle proxies_priv table. */
+ if (tables[5].table)
+ {
+ if ((found= handle_grant_table(tables, 5, drop, user_from, user_to)) < 0)
+ {
+ /* Handle of table failed, don't touch the in-memory array. */
+ result= -1;
+ }
+ else
+ {
+ /* Handle proxies_priv array. */
+ if ((handle_grant_struct(PROXY_USERS_ACL, drop, user_from, user_to) || found)
+ && ! result)
+ result= 1; /* At least one record/element found. */
+ if (search_only)
+ goto end;
+ }
+ }
+
+ /* Handle roles_mappings table. */
+ if (tables[6].table)
+ {
+ if ((found= handle_grant_table(tables, 6, drop, user_from, user_to)) < 0)
+ {
+ /* Handle of table failed, don't touch the in-memory array. */
+ result= -1;
+ }
+ else
+ {
+ /* Handle acl_roles_mappings array */
+ if ((handle_grant_struct(ROLES_MAPPINGS_HASH, drop, user_from, user_to) || found)
+ && ! result)
+ result= 1; /* At least one record/element found */
+ if (search_only)
+ goto end;
+ }
+ }
+
+ /* Handle user table. */
+ if ((found= handle_grant_table(tables, 0, drop, user_from, user_to)) < 0)
+ {
+ /* Handle of table failed, don't touch the in-memory array. */
+ result= -1;
+ }
+ else
+ {
+ enum enum_acl_lists what= handle_as_role ? ROLE_ACL : USER_ACL;
+ if (((handle_grant_struct(what, drop, user_from, user_to)) || found) && !result)
+ {
+ result= 1; /* At least one record/element found. */
+ DBUG_ASSERT(! search_only);
+ }
+ }
+
+end:
+ DBUG_RETURN(result);
+}
+
+/*
+ Create a list of users.
+
+ SYNOPSIS
+ mysql_create_user()
+ thd The current thread.
+ list The users to create.
+ handle_as_role Handle the user list as roles if true
+
+ RETURN
+ FALSE OK.
+ TRUE Error.
+*/
+
+bool mysql_create_user(THD *thd, List <LEX_USER> &list, bool handle_as_role)
+{
+ int result;
+ String wrong_users;
+ LEX_USER *user_name;
+ List_iterator <LEX_USER> user_list(list);
+ TABLE_LIST tables[GRANT_TABLES];
+ bool some_users_created= FALSE;
+ DBUG_ENTER("mysql_create_user");
+ DBUG_PRINT("entry", ("Handle as %s", handle_as_role ? "role" : "user"));
+
+ if (handle_as_role && sp_process_definer(thd))
+ DBUG_RETURN(TRUE);
+
+ /* CREATE USER may be skipped on replication client. */
+ if ((result= open_grant_tables(thd, tables)))
+ DBUG_RETURN(result != 1);
+
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ while ((user_name= user_list++))
+ {
+ if (user_name->user.str == current_user.str)
+ {
+ append_str(&wrong_users, STRING_WITH_LEN("CURRENT_USER"));
+ result= TRUE;
+ continue;
+ }
+
+ if (user_name->user.str == current_role.str)
+ {
+ append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE"));
+ result= TRUE;
+ continue;
+ }
+
+ if (handle_as_role && is_invalid_role_name(user_name->user.str))
+ {
+ append_user(thd, &wrong_users, user_name);
+ result= TRUE;
+ continue;
+ }
+
+ if (!user_name->host.str)
+ user_name->host= host_not_specified;
+
+ /*
+ Search all in-memory structures and grant tables
+ for a mention of the new user/role name.
+ */
+ if (handle_grant_data(tables, 0, user_name, NULL))
+ {
+ append_user(thd, &wrong_users, user_name);
+
+ result= TRUE;
+ continue;
+ }
+
+ some_users_created= TRUE;
+ if (replace_user_table(thd, tables[0].table, *user_name, 0, 0, 1, 0))
+ {
+ append_user(thd, &wrong_users, user_name);
+ result= TRUE;
+ continue;
+ }
+
+ // every created role is automatically granted to its creator-admin
+ if (handle_as_role)
+ {
+ ACL_USER_BASE *grantee= find_acl_user_base(thd->lex->definer->user.str,
+ thd->lex->definer->host.str);
+ ACL_ROLE *role= find_acl_role(user_name->user.str);
+
+ /*
+ just like with routines, views, triggers, and events we allow
+ non-existant definers here with a warning (see sp_process_definer())
+ */
+ if (grantee)
+ add_role_user_mapping(grantee, role);
+
+ if (replace_roles_mapping_table(tables[6].table,
+ &thd->lex->definer->user,
+ &thd->lex->definer->host,
+ &user_name->user, true,
+ NULL, false))
+ {
+ append_user(thd, &wrong_users, user_name);
+ if (grantee)
+ undo_add_role_user_mapping(grantee, role);
+ result= TRUE;
+ }
+ else if (grantee)
+ update_role_mapping(&thd->lex->definer->user,
+ &thd->lex->definer->host,
+ &user_name->user, true, NULL, false);
+ }
+ }
+
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (result)
+ my_error(ER_CANNOT_USER, MYF(0),
+ (handle_as_role) ? "CREATE ROLE" : "CREATE USER",
+ wrong_users.c_ptr_safe());
+
+ if (some_users_created)
+ result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
+
+ mysql_rwlock_unlock(&LOCK_grant);
+ DBUG_RETURN(result);
+}
+
+/*
+ Drop a list of users and all their privileges.
+
+ SYNOPSIS
+ mysql_drop_user()
+ thd The current thread.
+ list The users to drop.
+
+ RETURN
+ FALSE OK.
+ TRUE Error.
+*/
+
+bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool handle_as_role)
+{
+ int result;
+ String wrong_users;
+ LEX_USER *user_name, *tmp_user_name;
+ List_iterator <LEX_USER> user_list(list);
+ TABLE_LIST tables[GRANT_TABLES];
+ bool some_users_deleted= FALSE;
+ ulonglong old_sql_mode= thd->variables.sql_mode;
+ DBUG_ENTER("mysql_drop_user");
+ DBUG_PRINT("entry", ("Handle as %s", handle_as_role ? "role" : "user"));
+
+ /* DROP USER may be skipped on replication client. */
+ if ((result= open_grant_tables(thd, tables)))
+ DBUG_RETURN(result != 1);
+
+ thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH;
+
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ while ((tmp_user_name= user_list++))
+ {
+ user_name= get_current_user(thd, tmp_user_name, false);
+ if (!user_name)
+ {
+ thd->clear_error();
+ append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE"));
+ result= TRUE;
+ continue;
+ }
+
+ if (handle_as_role != user_name->is_role())
+ {
+ append_user(thd, &wrong_users, user_name);
+ result= TRUE;
+ continue;
+ }
+
+ if (handle_grant_data(tables, 1, user_name, NULL) <= 0)
+ {
+ append_user(thd, &wrong_users, user_name);
+ result= TRUE;
+ continue;
+ }
+
+ some_users_deleted= TRUE;
+ }
+
+ if (!handle_as_role)
+ {
+ /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
+ rebuild_check_host();
+
+ /*
+ Rebuild every user's role_grants since 'acl_users' has been sorted
+ and old pointers to ACL_USER elements are no longer valid
+ */
+ rebuild_role_grants();
+ }
+
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (result)
+ my_error(ER_CANNOT_USER, MYF(0),
+ (handle_as_role) ? "DROP ROLE" : "DROP USER",
+ wrong_users.c_ptr_safe());
+
+ if (some_users_deleted)
+ result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
+
+ mysql_rwlock_unlock(&LOCK_grant);
+ thd->variables.sql_mode= old_sql_mode;
+ DBUG_RETURN(result);
+}
+
+/*
+ Rename a user.
+
+ SYNOPSIS
+ mysql_rename_user()
+ thd The current thread.
+ list The user name pairs: (from, to).
+
+ RETURN
+ FALSE OK.
+ TRUE Error.
+*/
+
+bool mysql_rename_user(THD *thd, List <LEX_USER> &list)
+{
+ int result;
+ String wrong_users;
+ LEX_USER *user_from, *tmp_user_from;
+ LEX_USER *user_to, *tmp_user_to;
+ List_iterator <LEX_USER> user_list(list);
+ TABLE_LIST tables[GRANT_TABLES];
+ bool some_users_renamed= FALSE;
+ DBUG_ENTER("mysql_rename_user");
+
+ /* RENAME USER may be skipped on replication client. */
+ if ((result= open_grant_tables(thd, tables)))
+ DBUG_RETURN(result != 1);
+
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ while ((tmp_user_from= user_list++))
+ {
+ tmp_user_to= user_list++;
+ if (!(user_from= get_current_user(thd, tmp_user_from, false)))
+ {
+ append_user(thd, &wrong_users, user_from);
+ result= TRUE;
+ continue;
+ }
+ if (!(user_to= get_current_user(thd, tmp_user_to, false)))
+ {
+ append_user(thd, &wrong_users, user_to);
+ result= TRUE;
+ continue;
+ }
+ DBUG_ASSERT(!user_from->is_role());
+ DBUG_ASSERT(!user_to->is_role());
+
+ /*
+ Search all in-memory structures and grant tables
+ for a mention of the new user name.
+ */
+ if (handle_grant_data(tables, 0, user_to, NULL) ||
+ handle_grant_data(tables, 0, user_from, user_to) <= 0)
+ {
+ /* NOTE TODO renaming roles is not yet implemented */
+ append_user(thd, &wrong_users, user_from);
+ result= TRUE;
+ continue;
+ }
+ some_users_renamed= TRUE;
+ }
+
+ /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
+ rebuild_check_host();
+
+ /*
+ Rebuild every user's role_grants since 'acl_users' has been sorted
+ and old pointers to ACL_USER elements are no longer valid
+ */
+ rebuild_role_grants();
+
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (result)
+ my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe());
+
+ if (some_users_renamed && mysql_bin_log.is_open())
+ result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
+
+ mysql_rwlock_unlock(&LOCK_grant);
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Revoke all privileges from a list of users.
+
+ SYNOPSIS
+ mysql_revoke_all()
+ thd The current thread.
+ list The users to revoke all privileges from.
+
+ RETURN
+ > 0 Error. Error message already sent.
+ 0 OK.
+ < 0 Error. Error message not yet sent.
+*/
+
+bool mysql_revoke_all(THD *thd, List <LEX_USER> &list)
+{
+ uint counter, revoked, is_proc;
+ int result;
+ ACL_DB *acl_db;
+ TABLE_LIST tables[GRANT_TABLES];
+ DBUG_ENTER("mysql_revoke_all");
+
+ if ((result= open_grant_tables(thd, tables)))
+ DBUG_RETURN(result != 1);
+
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ LEX_USER *lex_user, *tmp_lex_user;
+ List_iterator <LEX_USER> user_list(list);
+ while ((tmp_lex_user= user_list++))
+ {
+ if (!(lex_user= get_current_user(thd, tmp_lex_user, false)))
+ {
+ result= -1;
+ continue;
+ }
+
+ /* This is not a role and the user could not be found */
+ if (!lex_user->is_role() &&
+ !find_user_exact(lex_user->host.str, lex_user->user.str))
+ {
+ result= -1;
+ continue;
+ }
+
+ if (replace_user_table(thd, tables[0].table, *lex_user, ~(ulong)0, 1, 0, 0))
+ {
+ result= -1;
+ continue;
+ }
+
+ /* Remove db access privileges */
+ /*
+ Because acl_dbs and column_priv_hash shrink and may re-order
+ as privileges are removed, removal occurs in a repeated loop
+ until no more privileges are revoked.
+ */
+ do
+ {
+ for (counter= 0, revoked= 0 ; counter < acl_dbs.elements ; )
+ {
+ const char *user,*host;
+
+ acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*);
+
+ user= safe_str(acl_db->user);
+ host= safe_str(acl_db->host.hostname);
+
+ if (!strcmp(lex_user->user.str, user) &&
+ !strcmp(lex_user->host.str, host))
+ {
+ if (!replace_db_table(tables[1].table, acl_db->db, *lex_user,
+ ~(ulong)0, 1))
+ {
+ /*
+ Don't increment counter as replace_db_table deleted the
+ current element in acl_dbs.
+ */
+ revoked= 1;
+ continue;
+ }
+ result= -1; // Something went wrong
+ }
+ counter++;
+ }
+ } while (revoked);
+
+ /* Remove column access */
+ do
+ {
+ for (counter= 0, revoked= 0 ; counter < column_priv_hash.records ; )
+ {
+ const char *user,*host;
+ GRANT_TABLE *grant_table=
+ (GRANT_TABLE*) my_hash_element(&column_priv_hash, counter);
+ user= safe_str(grant_table->user);
+ host= safe_str(grant_table->host.hostname);
+
+ if (!strcmp(lex_user->user.str,user) &&
+ !strcmp(lex_user->host.str, host))
+ {
+ if (replace_table_table(thd,grant_table,tables[2].table,*lex_user,
+ grant_table->db,
+ grant_table->tname,
+ ~(ulong)0, 0, 1))
+ {
+ result= -1;
+ }
+ else
+ {
+ if (!grant_table->cols)
+ {
+ revoked= 1;
+ continue;
+ }
+ List<LEX_COLUMN> columns;
+ if (!replace_column_table(grant_table,tables[3].table, *lex_user,
+ columns,
+ grant_table->db,
+ grant_table->tname,
+ ~(ulong)0, 1))
+ {
+ revoked= 1;
+ continue;
+ }
+ result= -1;
+ }
+ }
+ counter++;
+ }
+ } while (revoked);
+
+ /* Remove procedure access */
+ for (is_proc=0; is_proc<2; is_proc++) do {
+ HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash;
+ for (counter= 0, revoked= 0 ; counter < hash->records ; )
+ {
+ const char *user,*host;
+ GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter);
+ user= safe_str(grant_proc->user);
+ host= safe_str(grant_proc->host.hostname);
+
+ if (!strcmp(lex_user->user.str,user) &&
+ !strcmp(lex_user->host.str, host))
+ {
+ if (replace_routine_table(thd,grant_proc,tables[4].table,*lex_user,
+ grant_proc->db,
+ grant_proc->tname,
+ is_proc,
+ ~(ulong)0, 1) == 0)
+ {
+ revoked= 1;
+ continue;
+ }
+ result= -1; // Something went wrong
+ }
+ counter++;
+ }
+ } while (revoked);
+
+ ACL_USER_BASE *user_or_role;
+ /* remove role grants */
+ if (lex_user->is_role())
+ {
+ /* this can not fail due to get_current_user already having searched for it */
+ user_or_role= find_acl_role(lex_user->user.str);
+ }
+ else
+ {
+ user_or_role= find_user_exact(lex_user->host.str, lex_user->user.str);
+ }
+ /*
+ Find every role grant pair matching the role_grants array and remove it,
+ both from the acl_roles_mappings and the roles_mapping table
+ */
+ for (counter= 0; counter < user_or_role->role_grants.elements; counter++)
+ {
+ ACL_ROLE *role_grant= *dynamic_element(&user_or_role->role_grants,
+ counter, ACL_ROLE**);
+ ROLE_GRANT_PAIR *pair = find_role_grant_pair(&lex_user->user,
+ &lex_user->host,
+ &role_grant->user);
+ if (replace_roles_mapping_table(tables[6].table,
+ &lex_user->user,
+ &lex_user->host,
+ &role_grant->user, false, pair, true))
+ {
+ result= -1; //Something went wrong
+ }
+ update_role_mapping(&lex_user->user, &lex_user->host,
+ &role_grant->user, false, pair, true);
+ /*
+ Delete from the parent_grantee array of the roles granted,
+ the entry pointing to this user_or_role
+ */
+ remove_ptr_from_dynarray(&role_grant->parent_grantee, user_or_role);
+ }
+ /* TODO
+ How to handle an error in the replace_roles_mapping_table, in
+ regards to the privileges held in memory
+ */
+
+ /* Finally, clear the role_grants array */
+ if (counter == user_or_role->role_grants.elements)
+ {
+ reset_dynamic(&user_or_role->role_grants);
+ }
+ /*
+ If we are revoking from a role, we need to update all the parent grantees
+ */
+ if (lex_user->is_role())
+ {
+ propagate_role_grants((ACL_ROLE *)user_or_role, PRIVS_TO_MERGE::ALL);
+ }
+ }
+
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (result)
+ my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0));
+
+ result= result |
+ write_bin_log(thd, FALSE, thd->query(), thd->query_length());
+
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ DBUG_RETURN(result);
+}
+
+
+
+
+/**
+ If the defining user for a routine does not exist, then the ACL lookup
+ code should raise two errors which we should intercept. We convert the more
+ descriptive error into a warning, and consume the other.
+
+ If any other errors are raised, then we set a flag that should indicate
+ that there was some failure we should complain at a higher level.
+*/
+class Silence_routine_definer_errors : public Internal_error_handler
+{
+public:
+ Silence_routine_definer_errors()
+ : is_grave(FALSE)
+ {}
+
+ virtual ~Silence_routine_definer_errors()
+ {}
+
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+
+ bool has_errors() { return is_grave; }
+
+private:
+ bool is_grave;
+};
+
+bool
+Silence_routine_definer_errors::handle_condition(
+ THD *thd,
+ uint sql_errno,
+ const char*,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ if (level == Sql_condition::WARN_LEVEL_ERROR)
+ {
+ switch (sql_errno)
+ {
+ case ER_NONEXISTING_PROC_GRANT:
+ /* Convert the error into a warning. */
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ sql_errno, msg);
+ return TRUE;
+ default:
+ is_grave= TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/**
+ Revoke privileges for all users on a stored procedure. Use an error handler
+ that converts errors about missing grants into warnings.
+
+ @param
+ thd The current thread.
+ @param
+ db DB of the stored procedure
+ @param
+ name Name of the stored procedure
+
+ @retval
+ 0 OK.
+ @retval
+ < 0 Error. Error message not yet sent.
+*/
+
+bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name,
+ bool is_proc)
+{
+ uint counter, revoked;
+ int result;
+ TABLE_LIST tables[GRANT_TABLES];
+ HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash;
+ Silence_routine_definer_errors error_handler;
+ DBUG_ENTER("sp_revoke_privileges");
+
+ if ((result= open_grant_tables(thd, tables)))
+ DBUG_RETURN(result != 1);
+
+ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+
+ /* Be sure to pop this before exiting this scope! */
+ thd->push_internal_handler(&error_handler);
+
+ mysql_rwlock_wrlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ /* Remove procedure access */
+ do
+ {
+ for (counter= 0, revoked= 0 ; counter < hash->records ; )
+ {
+ GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter);
+ if (!my_strcasecmp(&my_charset_utf8_bin, grant_proc->db, sp_db) &&
+ !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name))
+ {
+ LEX_USER lex_user;
+ lex_user.user.str= grant_proc->user;
+ lex_user.user.length= strlen(grant_proc->user);
+ lex_user.host.str= safe_str(grant_proc->host.hostname);
+ lex_user.host.length= strlen(lex_user.host.str);
+ if (replace_routine_table(thd,grant_proc,tables[4].table,lex_user,
+ grant_proc->db, grant_proc->tname,
+ is_proc, ~(ulong)0, 1) == 0)
+ {
+ revoked= 1;
+ continue;
+ }
+ }
+ counter++;
+ }
+ } while (revoked);
+
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ thd->pop_internal_handler();
+
+ DBUG_RETURN(error_handler.has_errors());
+}
+
+
+/**
+ Grant EXECUTE,ALTER privilege for a stored procedure
+
+ @param thd The current thread.
+ @param sp_db
+ @param sp_name
+ @param is_proc
+
+ @return
+ @retval FALSE Success
+ @retval TRUE An error occured. Error message not yet sent.
+*/
+
+bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name,
+ bool is_proc)
+{
+ Security_context *sctx= thd->security_ctx;
+ LEX_USER *combo;
+ TABLE_LIST tables[1];
+ List<LEX_USER> user_list;
+ bool result;
+ ACL_USER *au;
+ Dummy_error_handler error_handler;
+ DBUG_ENTER("sp_grant_privileges");
+
+ if (!(combo=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
+ DBUG_RETURN(TRUE);
+
+ combo->user.str= sctx->user;
+
+ mysql_mutex_lock(&acl_cache->lock);
+
+ if ((au= find_user_wild(combo->host.str=(char*)sctx->host_or_ip, combo->user.str)))
+ goto found_acl;
+ if ((au= find_user_wild(combo->host.str=(char*)sctx->host, combo->user.str)))
+ goto found_acl;
+ if ((au= find_user_wild(combo->host.str=(char*)sctx->ip, combo->user.str)))
+ goto found_acl;
+ if ((au= find_user_wild(combo->host.str=(char*)"%", combo->user.str)))
+ goto found_acl;
+
+ mysql_mutex_unlock(&acl_cache->lock);
+ DBUG_RETURN(TRUE);
+
+ found_acl:
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ bzero((char*)tables, sizeof(TABLE_LIST));
+ user_list.empty();
+
+ tables->db= (char*)sp_db;
+ tables->table_name= tables->alias= (char*)sp_name;
+
+ thd->make_lex_string(&combo->user, combo->user.str, strlen(combo->user.str));
+ thd->make_lex_string(&combo->host, combo->host.str, strlen(combo->host.str));
+
+ combo->password= empty_lex_str;
+ combo->plugin= empty_lex_str;
+ combo->auth= empty_lex_str;
+
+ if(au)
+ {
+ if (au->plugin.str != native_password_plugin_name.str &&
+ au->plugin.str != old_password_plugin_name.str)
+ combo->plugin= au->plugin;
+ combo->auth= au->auth_string;
+ }
+
+ if (user_list.push_back(combo))
+ DBUG_RETURN(TRUE);
+
+ thd->lex->ssl_type= SSL_TYPE_NOT_SPECIFIED;
+ thd->lex->ssl_cipher= thd->lex->x509_subject= thd->lex->x509_issuer= 0;
+ bzero((char*) &thd->lex->mqh, sizeof(thd->lex->mqh));
+
+ /*
+ Only care about whether the operation failed or succeeded
+ as all errors will be handled later.
+ */
+ thd->push_internal_handler(&error_handler);
+ result= mysql_routine_grant(thd, tables, is_proc, user_list,
+ DEFAULT_CREATE_PROC_ACLS, FALSE, FALSE);
+ thd->pop_internal_handler();
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Validate if a user can proxy as another user
+
+ @thd current thread
+ @param user the logged in user (proxy user)
+ @param authenticated_as the effective user a plugin is trying to
+ impersonate as (proxied user)
+ @return proxy user definition
+ @retval NULL proxy user definition not found or not applicable
+ @retval non-null the proxy user data
+*/
+
+static ACL_PROXY_USER *
+acl_find_proxy_user(const char *user, const char *host, const char *ip,
+ const char *authenticated_as, bool *proxy_used)
+{
+ uint i;
+ /* if the proxied and proxy user are the same return OK */
+ DBUG_ENTER("acl_find_proxy_user");
+ DBUG_PRINT("info", ("user=%s host=%s ip=%s authenticated_as=%s",
+ user, host, ip, authenticated_as));
+
+ if (!strcmp(authenticated_as, user))
+ {
+ DBUG_PRINT ("info", ("user is the same as authenticated_as"));
+ DBUG_RETURN (NULL);
+ }
+
+ *proxy_used= TRUE;
+ for (i=0; i < acl_proxy_users.elements; i++)
+ {
+ ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i,
+ ACL_PROXY_USER *);
+ if (proxy->matches(host, user, ip, authenticated_as))
+ DBUG_RETURN(proxy);
+ }
+
+ DBUG_RETURN(NULL);
+}
+
+
+bool
+acl_check_proxy_grant_access(THD *thd, const char *host, const char *user,
+ bool with_grant)
+{
+ DBUG_ENTER("acl_check_proxy_grant_access");
+ DBUG_PRINT("info", ("user=%s host=%s with_grant=%d", user, host,
+ (int) with_grant));
+ if (!initialized)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
+ DBUG_RETURN(1);
+ }
+
+ /* replication slave thread can do anything */
+ if (thd->slave_thread)
+ {
+ DBUG_PRINT("info", ("replication slave"));
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ one can grant proxy for self to others.
+ Security context in THD contains two pairs of (user,host):
+ 1. (user,host) pair referring to inbound connection.
+ 2. (priv_user,priv_host) pair obtained from mysql.user table after doing
+ authnetication of incoming connection.
+ Privileges should be checked wrt (priv_user, priv_host) tuple, because
+ (user,host) pair obtained from inbound connection may have different
+ values than what is actually stored in mysql.user table and while granting
+ or revoking proxy privilege, user is expected to provide entries mentioned
+ in mysql.user table.
+ */
+ if (!strcmp(thd->security_ctx->priv_user, user) &&
+ !my_strcasecmp(system_charset_info, host,
+ thd->security_ctx->priv_host))
+ {
+ DBUG_PRINT("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal",
+ thd->security_ctx->priv_user, user,
+ host, thd->security_ctx->priv_host));
+ DBUG_RETURN(FALSE);
+ }
+
+ /* check for matching WITH PROXY rights */
+ for (uint i=0; i < acl_proxy_users.elements; i++)
+ {
+ ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i,
+ ACL_PROXY_USER *);
+ if (proxy->matches(thd->security_ctx->host,
+ thd->security_ctx->user,
+ thd->security_ctx->ip,
+ user) &&
+ proxy->get_with_grant())
+ {
+ DBUG_PRINT("info", ("found"));
+ DBUG_RETURN(FALSE);
+ }
+ }
+
+ my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
+ thd->security_ctx->user,
+ thd->security_ctx->host_or_ip);
+ DBUG_RETURN(TRUE);
+}
+
+
+static bool
+show_proxy_grants(THD *thd, const char *username, const char *hostname,
+ char *buff, size_t buffsize)
+{
+ Protocol *protocol= thd->protocol;
+ int error= 0;
+
+ for (uint i=0; i < acl_proxy_users.elements; i++)
+ {
+ ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i,
+ ACL_PROXY_USER *);
+ if (proxy->granted_on(hostname, username))
+ {
+ String global(buff, buffsize, system_charset_info);
+ global.length(0);
+ proxy->print_grant(&global);
+ protocol->prepare_for_resend();
+ protocol->store(global.ptr(), global.length(), global.charset());
+ if (protocol->write())
+ {
+ error= -1;
+ break;
+ }
+ }
+ }
+ return error;
+}
+
+static int enabled_roles_insert(ACL_USER_BASE *role, void *context_data)
+{
+ TABLE *table= (TABLE*) context_data;
+ DBUG_ASSERT(role->flags & IS_ROLE);
+
+ restore_record(table, s->default_values);
+ table->field[0]->set_notnull();
+ table->field[0]->store(role->user.str, role->user.length,
+ system_charset_info);
+ if (schema_table_store_record(table->in_use, table))
+ return -1;
+ return 0;
+}
+
+struct APPLICABLE_ROLES_DATA
+{
+ TABLE *table;
+ const LEX_STRING host;
+ const LEX_STRING user_and_host;
+ ACL_USER_BASE *user;
+};
+
+static int
+applicable_roles_insert(ACL_USER_BASE *grantee, ACL_ROLE *role, void *ptr)
+{
+ APPLICABLE_ROLES_DATA *data= (APPLICABLE_ROLES_DATA *)ptr;
+ CHARSET_INFO *cs= system_charset_info;
+ TABLE *table= data->table;
+ bool is_role= grantee != data->user;
+ const LEX_STRING *user_and_host= is_role ? &grantee->user
+ : &data->user_and_host;
+ const LEX_STRING *host= is_role ? &empty_lex_str : &data->host;
+
+ restore_record(table, s->default_values);
+ table->field[0]->store(user_and_host->str, user_and_host->length, cs);
+ table->field[1]->store(role->user.str, role->user.length, cs);
+
+ ROLE_GRANT_PAIR *pair=
+ find_role_grant_pair(&grantee->user, host, &role->user);
+ DBUG_ASSERT(pair);
+
+ if (pair->with_admin)
+ table->field[2]->store(STRING_WITH_LEN("YES"), cs);
+ else
+ table->field[2]->store(STRING_WITH_LEN("NO"), cs);
+
+ if (schema_table_store_record(table->in_use, table))
+ return -1;
+ return 0;
+}
+
+#endif /*NO_EMBEDDED_ACCESS_CHECKS */
+
+int fill_schema_enabled_roles(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ TABLE *table= tables->table;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (thd->security_ctx->priv_role[0])
+ {
+ mysql_rwlock_rdlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+ ACL_ROLE *acl_role= find_acl_role(thd->security_ctx->priv_role);
+ if (acl_role)
+ traverse_role_graph_down(acl_role, table, enabled_roles_insert, NULL);
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+ if (acl_role)
+ return 0;
+ }
+#endif
+
+ restore_record(table, s->default_values);
+ table->field[0]->set_null();
+ return schema_table_store_record(table->in_use, table);
+}
+
+
+/*
+ This shows all roles granted to current user
+ and recursively all roles granted to those roles
+*/
+int fill_schema_applicable_roles(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ int res= 0;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (initialized)
+ {
+ TABLE *table= tables->table;
+ Security_context *sctx= thd->security_ctx;
+ mysql_rwlock_rdlock(&LOCK_grant);
+ mysql_mutex_lock(&acl_cache->lock);
+ ACL_USER *user= find_user_exact(sctx->priv_host, sctx->priv_user);
+ if (user)
+ {
+ char buff[USER_HOST_BUFF_SIZE+10];
+ DBUG_ASSERT(user->user.length + user->hostname_length +2 < sizeof(buff));
+ char *end= strxmov(buff, user->user.str, "@", user->host.hostname, NULL);
+ APPLICABLE_ROLES_DATA data= { table,
+ { user->host.hostname, user->hostname_length },
+ { buff, (size_t)(end - buff) }, user
+ };
+
+ res= traverse_role_graph_down(user, &data, 0, applicable_roles_insert);
+ }
+
+ mysql_mutex_unlock(&acl_cache->lock);
+ mysql_rwlock_unlock(&LOCK_grant);
+ }
+#endif
+
+ return res;
+}
+
+
+int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr)
+{
+ reg3 int flag;
+ DBUG_ENTER("wild_case_compare");
+ DBUG_PRINT("enter",("str: '%s' wildstr: '%s'",str,wildstr));
+ while (*wildstr)
+ {
+ while (*wildstr && *wildstr != wild_many && *wildstr != wild_one)
+ {
+ if (*wildstr == wild_prefix && wildstr[1])
+ wildstr++;
+ if (my_toupper(cs, *wildstr++) !=
+ my_toupper(cs, *str++)) DBUG_RETURN(1);
+ }
+ if (! *wildstr ) DBUG_RETURN (*str != 0);
+ if (*wildstr++ == wild_one)
+ {
+ if (! *str++) DBUG_RETURN (1); /* One char; skip */
+ }
+ else
+ { /* Found '*' */
+ if (!*wildstr) DBUG_RETURN(0); /* '*' as last char: OK */
+ flag=(*wildstr != wild_many && *wildstr != wild_one);
+ do
+ {
+ if (flag)
+ {
+ char cmp;
+ if ((cmp= *wildstr) == wild_prefix && wildstr[1])
+ cmp=wildstr[1];
+ cmp=my_toupper(cs, cmp);
+ while (*str && my_toupper(cs, *str) != cmp)
+ str++;
+ if (!*str) DBUG_RETURN (1);
+ }
+ if (wild_case_compare(cs, str,wildstr) == 0) DBUG_RETURN (0);
+ } while (*str++);
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN (*str != '\0');
+}
+
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+static bool update_schema_privilege(THD *thd, TABLE *table, char *buff,
+ const char* db, const char* t_name,
+ const char* column, uint col_length,
+ const char *priv, uint priv_length,
+ const char* is_grantable)
+{
+ int i= 2;
+ CHARSET_INFO *cs= system_charset_info;
+ restore_record(table, s->default_values);
+ table->field[0]->store(buff, (uint) strlen(buff), cs);
+ table->field[1]->store(STRING_WITH_LEN("def"), cs);
+ if (db)
+ table->field[i++]->store(db, (uint) strlen(db), cs);
+ if (t_name)
+ table->field[i++]->store(t_name, (uint) strlen(t_name), cs);
+ if (column)
+ table->field[i++]->store(column, col_length, cs);
+ table->field[i++]->store(priv, priv_length, cs);
+ table->field[i]->store(is_grantable, strlen(is_grantable), cs);
+ return schema_table_store_record(thd, table);
+}
+#endif
+
+
+int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ int error= 0;
+ uint counter;
+ ACL_USER *acl_user;
+ ulong want_access;
+ char buff[100];
+ TABLE *table= tables->table;
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
+ NULL, NULL, 1, 1);
+ char *curr_host= thd->security_ctx->priv_host_name();
+ DBUG_ENTER("fill_schema_user_privileges");
+
+ if (!initialized)
+ DBUG_RETURN(0);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ for (counter=0 ; counter < acl_users.elements ; counter++)
+ {
+ const char *user,*host, *is_grantable="YES";
+ acl_user=dynamic_element(&acl_users,counter,ACL_USER*);
+ user= safe_str(acl_user->user.str);
+ host= safe_str(acl_user->host.hostname);
+
+ if (no_global_access &&
+ (strcmp(thd->security_ctx->priv_user, user) ||
+ my_strcasecmp(system_charset_info, curr_host, host)))
+ continue;
+
+ want_access= acl_user->access;
+ if (!(want_access & GRANT_ACL))
+ is_grantable= "NO";
+
+ strxmov(buff,"'",user,"'@'",host,"'",NullS);
+ if (!(want_access & ~GRANT_ACL))
+ {
+ if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
+ STRING_WITH_LEN("USAGE"), is_grantable))
+ {
+ error= 1;
+ goto err;
+ }
+ }
+ else
+ {
+ uint priv_id;
+ ulong j,test_access= want_access & ~GRANT_ACL;
+ for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1)
+ {
+ if (test_access & j)
+ {
+ if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
+ command_array[priv_id],
+ command_lengths[priv_id], is_grantable))
+ {
+ error= 1;
+ goto err;
+ }
+ }
+ }
+ }
+ }
+err:
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ DBUG_RETURN(error);
+#else
+ return(0);
+#endif
+}
+
+
+int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ int error= 0;
+ uint counter;
+ ACL_DB *acl_db;
+ ulong want_access;
+ char buff[100];
+ TABLE *table= tables->table;
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
+ NULL, NULL, 1, 1);
+ char *curr_host= thd->security_ctx->priv_host_name();
+ DBUG_ENTER("fill_schema_schema_privileges");
+
+ if (!initialized)
+ DBUG_RETURN(0);
+ mysql_mutex_lock(&acl_cache->lock);
+
+ for (counter=0 ; counter < acl_dbs.elements ; counter++)
+ {
+ const char *user, *host, *is_grantable="YES";
+
+ acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*);
+ user= safe_str(acl_db->user);
+ host= safe_str(acl_db->host.hostname);
+
+ if (no_global_access &&
+ (strcmp(thd->security_ctx->priv_user, user) ||
+ my_strcasecmp(system_charset_info, curr_host, host)))
+ continue;
+
+ want_access=acl_db->access;
+ if (want_access)
+ {
+ if (!(want_access & GRANT_ACL))
+ {
+ is_grantable= "NO";
+ }
+ strxmov(buff,"'",user,"'@'",host,"'",NullS);
+ if (!(want_access & ~GRANT_ACL))
+ {
+ if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0,
+ 0, STRING_WITH_LEN("USAGE"), is_grantable))
+ {
+ error= 1;
+ goto err;
+ }
+ }
+ else
+ {
+ int cnt;
+ ulong j,test_access= want_access & ~GRANT_ACL;
+ for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1)
+ if (test_access & j)
+ {
+ if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0, 0,
+ command_array[cnt], command_lengths[cnt],
+ is_grantable))
+ {
+ error= 1;
+ goto err;
+ }
+ }
+ }
+ }
+ }
+err:
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ DBUG_RETURN(error);
+#else
+ return (0);
+#endif
+}
+
+
+int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ int error= 0;
+ uint index;
+ char buff[100];
+ TABLE *table= tables->table;
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
+ NULL, NULL, 1, 1);
+ char *curr_host= thd->security_ctx->priv_host_name();
+ DBUG_ENTER("fill_schema_table_privileges");
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+
+ for (index=0 ; index < column_priv_hash.records ; index++)
+ {
+ const char *user, *host, *is_grantable= "YES";
+ GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash,
+ index);
+ user= safe_str(grant_table->user);
+ host= safe_str(grant_table->host.hostname);
+
+ if (no_global_access &&
+ (strcmp(thd->security_ctx->priv_user, user) ||
+ my_strcasecmp(system_charset_info, curr_host, host)))
+ continue;
+
+ ulong table_access= grant_table->privs;
+ if (table_access)
+ {
+ ulong test_access= table_access & ~GRANT_ACL;
+ /*
+ We should skip 'usage' privilege on table if
+ we have any privileges on column(s) of this table
+ */
+ if (!test_access && grant_table->cols)
+ continue;
+ if (!(table_access & GRANT_ACL))
+ is_grantable= "NO";
+
+ strxmov(buff, "'", user, "'@'", host, "'", NullS);
+ if (!test_access)
+ {
+ if (update_schema_privilege(thd, table, buff, grant_table->db,
+ grant_table->tname, 0, 0,
+ STRING_WITH_LEN("USAGE"), is_grantable))
+ {
+ error= 1;
+ goto err;
+ }
+ }
+ else
+ {
+ ulong j;
+ int cnt;
+ for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1)
+ {
+ if (test_access & j)
+ {
+ if (update_schema_privilege(thd, table, buff, grant_table->db,
+ grant_table->tname, 0, 0,
+ command_array[cnt],
+ command_lengths[cnt], is_grantable))
+ {
+ error= 1;
+ goto err;
+ }
+ }
+ }
+ }
+ }
+ }
+err:
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ DBUG_RETURN(error);
+#else
+ return (0);
+#endif
+}
+
+
+int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ int error= 0;
+ uint index;
+ char buff[100];
+ TABLE *table= tables->table;
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
+ NULL, NULL, 1, 1);
+ char *curr_host= thd->security_ctx->priv_host_name();
+ DBUG_ENTER("fill_schema_table_privileges");
+
+ mysql_rwlock_rdlock(&LOCK_grant);
+
+ for (index=0 ; index < column_priv_hash.records ; index++)
+ {
+ const char *user, *host, *is_grantable= "YES";
+ GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash,
+ index);
+ user= safe_str(grant_table->user);
+ host= safe_str(grant_table->host.hostname);
+
+ if (no_global_access &&
+ (strcmp(thd->security_ctx->priv_user, user) ||
+ my_strcasecmp(system_charset_info, curr_host, host)))
+ continue;
+
+ ulong table_access= grant_table->cols;
+ if (table_access != 0)
+ {
+ if (!(grant_table->privs & GRANT_ACL))
+ is_grantable= "NO";
+
+ ulong test_access= table_access & ~GRANT_ACL;
+ strxmov(buff, "'", user, "'@'", host, "'", NullS);
+ if (!test_access)
+ continue;
+ else
+ {
+ ulong j;
+ int cnt;
+ for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1)
+ {
+ if (test_access & j)
+ {
+ for (uint col_index=0 ;
+ col_index < grant_table->hash_columns.records ;
+ col_index++)
+ {
+ GRANT_COLUMN *grant_column = (GRANT_COLUMN*)
+ my_hash_element(&grant_table->hash_columns,col_index);
+ if ((grant_column->rights & j) && (table_access & j))
+ {
+ if (update_schema_privilege(thd, table, buff, grant_table->db,
+ grant_table->tname,
+ grant_column->column,
+ grant_column->key_length,
+ command_array[cnt],
+ command_lengths[cnt], is_grantable))
+ {
+ error= 1;
+ goto err;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+err:
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ DBUG_RETURN(error);
+#else
+ return (0);
+#endif
+}
+
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+/*
+ fill effective privileges for table
+
+ SYNOPSIS
+ fill_effective_table_privileges()
+ thd thread handler
+ grant grants table descriptor
+ db db name
+ table table name
+*/
+
+void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
+ const char *db, const char *table)
+{
+ Security_context *sctx= thd->security_ctx;
+ DBUG_ENTER("fill_effective_table_privileges");
+ DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', table: `%s`.`%s`",
+ sctx->priv_host, sctx->ip, sctx->priv_user, db, table));
+ /* --skip-grants */
+ if (!initialized)
+ {
+ DBUG_PRINT("info", ("skip grants"));
+ grant->privilege= ~NO_ACCESS; // everything is allowed
+ DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege));
+ DBUG_VOID_RETURN;
+ }
+
+ /* global privileges */
+ grant->privilege= sctx->master_access;
+
+ if (!sctx->priv_user)
+ {
+ DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege));
+ DBUG_VOID_RETURN; // it is slave
+ }
+
+ if (!thd->db || strcmp(db, thd->db))
+ {
+ /* db privileges */
+ grant->privilege|= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, 0);
+ /* db privileges for role */
+ if (sctx->priv_role[0])
+ grant->privilege|= acl_get("", "", sctx->priv_role, db, 0);
+ }
+ else
+ {
+ grant->privilege|= sctx->db_access;
+ }
+
+ /* table privileges */
+ mysql_rwlock_rdlock(&LOCK_grant);
+ if (grant->version != grant_version)
+ {
+ grant->grant_table_user=
+ table_hash_search(sctx->host, sctx->ip, db,
+ sctx->priv_user,
+ table, 0); /* purecov: inspected */
+ grant->grant_table_role=
+ sctx->priv_role[0] ? table_hash_search("", "", db,
+ sctx->priv_role,
+ table, TRUE) : NULL;
+ grant->version= grant_version; /* purecov: inspected */
+ }
+ if (grant->grant_table_user != 0)
+ {
+ grant->privilege|= grant->grant_table_user->privs;
+ }
+ if (grant->grant_table_role != 0)
+ {
+ grant->privilege|= grant->grant_table_role->privs;
+ }
+ mysql_rwlock_unlock(&LOCK_grant);
+
+ DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege));
+ DBUG_VOID_RETURN;
+}
+
+#else /* NO_EMBEDDED_ACCESS_CHECKS */
+
+/****************************************************************************
+ Dummy wrappers when we don't have any access checks
+****************************************************************************/
+
+bool check_routine_level_acl(THD *thd, const char *db, const char *name,
+ bool is_proc)
+{
+ return FALSE;
+}
+
+#endif
+
+/**
+ Return information about user or current user.
+
+ @param[in] thd thread handler
+ @param[in] user user
+ @param[in] lock whether &acl_cache->lock mutex needs to be locked
+
+ @return
+ - On success, return a valid pointer to initialized
+ LEX_USER, which contains user information.
+ - On error, return 0.
+*/
+
+LEX_USER *get_current_user(THD *thd, LEX_USER *user, bool lock)
+{
+ if (user->user.str == current_user.str) // current_user
+ return create_default_definer(thd, false);
+
+ if (user->user.str == current_role.str) // current_role
+ return create_default_definer(thd, true);
+
+ if (user->host.str == NULL) // Possibly a role
+ {
+ // to be reexecution friendly we have to make a copy
+ LEX_USER *dup= (LEX_USER*) thd->memdup(user, sizeof(*user));
+ if (!dup)
+ return 0;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (is_invalid_role_name(user->user.str))
+ return 0;
+
+ if (lock)
+ mysql_mutex_lock(&acl_cache->lock);
+ if (find_acl_role(dup->user.str))
+ dup->host= empty_lex_str;
+ else
+ dup->host= host_not_specified;
+ if (lock)
+ mysql_mutex_unlock(&acl_cache->lock);
+#endif
+
+ return dup;
+ }
+
+ return user;
+}
+
+struct ACL_internal_schema_registry_entry
+{
+ const LEX_STRING *m_name;
+ const ACL_internal_schema_access *m_access;
+};
+
+/**
+ Internal schema registered.
+ Currently, this is only:
+ - performance_schema
+ - information_schema,
+ This can be reused later for:
+ - mysql
+*/
+static ACL_internal_schema_registry_entry registry_array[2];
+static uint m_registry_array_size= 0;
+
+/**
+ Add an internal schema to the registry.
+ @param name the schema name
+ @param access the schema ACL specific rules
+*/
+void ACL_internal_schema_registry::register_schema
+ (const LEX_STRING *name, const ACL_internal_schema_access *access)
+{
+ DBUG_ASSERT(m_registry_array_size < array_elements(registry_array));
+
+ /* Not thread safe, and does not need to be. */
+ registry_array[m_registry_array_size].m_name= name;
+ registry_array[m_registry_array_size].m_access= access;
+ m_registry_array_size++;
+}
+
+/**
+ Search per internal schema ACL by name.
+ @param name a schema name
+ @return per schema rules, or NULL
+*/
+const ACL_internal_schema_access *
+ACL_internal_schema_registry::lookup(const char *name)
+{
+ DBUG_ASSERT(name != NULL);
+
+ uint i;
+
+ for (i= 0; i<m_registry_array_size; i++)
+ {
+ if (my_strcasecmp(system_charset_info, registry_array[i].m_name->str,
+ name) == 0)
+ return registry_array[i].m_access;
+ }
+ return NULL;
+}
+
+/**
+ Get a cached internal schema access.
+ @param grant_internal_info the cache
+ @param schema_name the name of the internal schema
+*/
+const ACL_internal_schema_access *
+get_cached_schema_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name)
+{
+ if (grant_internal_info)
+ {
+ if (! grant_internal_info->m_schema_lookup_done)
+ {
+ grant_internal_info->m_schema_access=
+ ACL_internal_schema_registry::lookup(schema_name);
+ grant_internal_info->m_schema_lookup_done= TRUE;
+ }
+ return grant_internal_info->m_schema_access;
+ }
+ return ACL_internal_schema_registry::lookup(schema_name);
+}
+
+/**
+ Get a cached internal table access.
+ @param grant_internal_info the cache
+ @param schema_name the name of the internal schema
+ @param table_name the name of the internal table
+*/
+const ACL_internal_table_access *
+get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name,
+ const char *table_name)
+{
+ DBUG_ASSERT(grant_internal_info);
+ if (! grant_internal_info->m_table_lookup_done)
+ {
+ const ACL_internal_schema_access *schema_access;
+ schema_access= get_cached_schema_access(grant_internal_info, schema_name);
+ if (schema_access)
+ grant_internal_info->m_table_access= schema_access->lookup(table_name);
+ grant_internal_info->m_table_lookup_done= TRUE;
+ }
+ return grant_internal_info->m_table_access;
+}
+
+
+/****************************************************************************
+ AUTHENTICATION CODE
+ including initial connect handshake, invoking appropriate plugins,
+ client-server plugin negotiation, COM_CHANGE_USER, and native
+ MySQL authentication plugins.
+****************************************************************************/
+
+/* few defines to have less ifdef's in the code below */
+#ifdef EMBEDDED_LIBRARY
+#undef HAVE_OPENSSL
+#ifdef NO_EMBEDDED_ACCESS_CHECKS
+#define initialized 0
+#define check_for_max_user_connections(X,Y) 0
+#define get_or_create_user_conn(A,B,C,D) 0
+#endif
+#endif
+#ifndef HAVE_OPENSSL
+#define ssl_acceptor_fd 0
+#define sslaccept(A,B,C,D) 1
+#endif
+
+/**
+ The internal version of what plugins know as MYSQL_PLUGIN_VIO,
+ basically the context of the authentication session
+*/
+struct MPVIO_EXT :public MYSQL_PLUGIN_VIO
+{
+ MYSQL_SERVER_AUTH_INFO auth_info;
+ THD *thd;
+ ACL_USER *acl_user; ///< a copy, independent from acl_users array
+ plugin_ref plugin; ///< what plugin we're under
+ LEX_STRING db; ///< db name from the handshake packet
+ /** when restarting a plugin this caches the last client reply */
+ struct {
+ char *plugin, *pkt; ///< pointers into NET::buff
+ uint pkt_len;
+ } cached_client_reply;
+ /** this caches the first plugin packet for restart request on the client */
+ struct {
+ char *pkt;
+ uint pkt_len;
+ } cached_server_packet;
+ int packets_read, packets_written; ///< counters for send/received packets
+ bool make_it_fail;
+ /** when plugin returns a failure this tells us what really happened */
+ enum { SUCCESS, FAILURE, RESTART } status;
+};
+
+/**
+ a helper function to report an access denied error in all the proper places
+*/
+static void login_failed_error(THD *thd)
+{
+ my_error(access_denied_error_code(thd->password), MYF(0),
+ thd->main_security_ctx.user,
+ thd->main_security_ctx.host_or_ip,
+ thd->password ? ER(ER_YES) : ER(ER_NO));
+ general_log_print(thd, COM_CONNECT,
+ ER(access_denied_error_code(thd->password)),
+ thd->main_security_ctx.user,
+ thd->main_security_ctx.host_or_ip,
+ thd->password ? ER(ER_YES) : ER(ER_NO));
+ status_var_increment(thd->status_var.access_denied_errors);
+ /*
+ Log access denied messages to the error log when log-warnings = 2
+ so that the overhead of the general query log is not required to track
+ failed connections.
+ */
+ if (global_system_variables.log_warnings > 1)
+ {
+ sql_print_warning(ER(access_denied_error_code(thd->password)),
+ thd->main_security_ctx.user,
+ thd->main_security_ctx.host_or_ip,
+ thd->password ? ER(ER_YES) : ER(ER_NO));
+ }
+}
+
+/**
+ sends a server handshake initialization packet, the very first packet
+ after the connection was established
+
+ Packet format:
+
+ Bytes Content
+ ----- ----
+ 1 protocol version (always 10)
+ n server version string, \0-terminated
+ 4 thread id
+ 8 first 8 bytes of the plugin provided data (scramble)
+ 1 \0 byte, terminating the first part of a scramble
+ 2 server capabilities (two lower bytes)
+ 1 server character set
+ 2 server status
+ 2 server capabilities (two upper bytes)
+ 1 length of the scramble
+ 10 reserved, always 0
+ n rest of the plugin provided data (at least 12 bytes)
+ 1 \0 byte, terminating the second part of a scramble
+
+ @retval 0 ok
+ @retval 1 error
+*/
+static bool send_server_handshake_packet(MPVIO_EXT *mpvio,
+ const char *data, uint data_len)
+{
+ DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE);
+ DBUG_ASSERT(data_len <= 255);
+
+ THD *thd= mpvio->thd;
+ char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + 1 + data_len + 64);
+ char scramble_buf[SCRAMBLE_LENGTH];
+ char *end= buff;
+ DBUG_ENTER("send_server_handshake_packet");
+
+ *end++= protocol_version;
+
+ thd->client_capabilities= CLIENT_BASIC_FLAGS;
+
+ if (opt_using_transactions)
+ thd->client_capabilities|= CLIENT_TRANSACTIONS;
+
+ thd->client_capabilities|= CAN_CLIENT_COMPRESS;
+
+ if (ssl_acceptor_fd)
+ {
+ thd->client_capabilities |= CLIENT_SSL;
+ thd->client_capabilities |= CLIENT_SSL_VERIFY_SERVER_CERT;
+ }
+
+ if (data_len)
+ {
+ mpvio->cached_server_packet.pkt= (char*)thd->memdup(data, data_len);
+ mpvio->cached_server_packet.pkt_len= data_len;
+ }
+
+ if (data_len < SCRAMBLE_LENGTH)
+ {
+ if (data_len)
+ {
+ /*
+ the first packet *must* have at least 20 bytes of a scramble.
+ if a plugin provided less, we pad it to 20 with zeros
+ */
+ memcpy(scramble_buf, data, data_len);
+ bzero(scramble_buf + data_len, SCRAMBLE_LENGTH - data_len);
+ data= scramble_buf;
+ }
+ else
+ {
+ /*
+ if the default plugin does not provide the data for the scramble at
+ all, we generate a scramble internally anyway, just in case the
+ user account (that will be known only later) uses a
+ native_password_plugin (which needs a scramble). If we don't send a
+ scramble now - wasting 20 bytes in the packet -
+ native_password_plugin will have to send it in a separate packet,
+ adding one more round trip.
+ */
+ create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand);
+ data= thd->scramble;
+ }
+ data_len= SCRAMBLE_LENGTH;
+ }
+
+ end= strxnmov(end, SERVER_VERSION_LENGTH, RPL_VERSION_HACK, server_version, NullS) + 1;
+ int4store((uchar*) end, mpvio->thd->thread_id);
+ end+= 4;
+
+ /*
+ Old clients does not understand long scrambles, but can ignore packet
+ tail: that's why first part of the scramble is placed here, and second
+ part at the end of packet.
+ */
+ end= (char*) memcpy(end, data, SCRAMBLE_LENGTH_323);
+ end+= SCRAMBLE_LENGTH_323;
+ *end++= 0;
+
+ int2store(end, thd->client_capabilities);
+ /* write server characteristics: up to 16 bytes allowed */
+ end[2]= (char) default_charset_info->number;
+ int2store(end+3, mpvio->thd->server_status);
+ int2store(end+5, thd->client_capabilities >> 16);
+ end[7]= data_len;
+ DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len", end[7]= -100;);
+ bzero(end + 8, 10);
+ end+= 18;
+ /* write scramble tail */
+ end= (char*) memcpy(end, data + SCRAMBLE_LENGTH_323,
+ data_len - SCRAMBLE_LENGTH_323);
+ end+= data_len - SCRAMBLE_LENGTH_323;
+ end= strmake(end, plugin_name(mpvio->plugin)->str,
+ plugin_name(mpvio->plugin)->length);
+
+ int res= my_net_write(&mpvio->thd->net, (uchar*) buff,
+ (size_t) (end - buff + 1)) ||
+ net_flush(&mpvio->thd->net);
+ my_afree(buff);
+ DBUG_RETURN (res);
+}
+
+static bool secure_auth(THD *thd)
+{
+ if (!opt_secure_auth)
+ return 0;
+
+ /*
+ If the server is running in secure auth mode, short scrambles are
+ forbidden. Extra juggling to report the same error as the old code.
+ */
+ if (thd->client_capabilities & CLIENT_PROTOCOL_41)
+ {
+ my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0),
+ thd->security_ctx->user,
+ thd->security_ctx->host_or_ip);
+ general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE),
+ thd->security_ctx->user,
+ thd->security_ctx->host_or_ip);
+ }
+ else
+ {
+ my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
+ general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE));
+ }
+ return 1;
+}
+
+/**
+ sends a "change plugin" packet, requesting a client to restart authentication
+ using a different authentication plugin
+
+ Packet format:
+
+ Bytes Content
+ ----- ----
+ 1 byte with the value 254
+ n client plugin to use, \0-terminated
+ n plugin provided data
+
+ In a special case of switching from native_password_plugin to
+ old_password_plugin, the packet contains only one - the first - byte,
+ plugin name is omitted, plugin data aren't needed as the scramble was
+ already sent. This one-byte packet is identical to the "use the short
+ scramble" packet in the protocol before plugins were introduced.
+
+ @retval 0 ok
+ @retval 1 error
+*/
+static bool send_plugin_request_packet(MPVIO_EXT *mpvio,
+ const uchar *data, uint data_len)
+{
+ DBUG_ASSERT(mpvio->packets_written == 1);
+ DBUG_ASSERT(mpvio->packets_read == 1);
+ NET *net= &mpvio->thd->net;
+ static uchar switch_plugin_request_buf[]= { 254 };
+ DBUG_ENTER("send_plugin_request_packet");
+
+ mpvio->status= MPVIO_EXT::FAILURE; // the status is no longer RESTART
+
+ const char *client_auth_plugin=
+ ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin;
+
+ DBUG_ASSERT(client_auth_plugin);
+
+ /*
+ we send an old "short 4.0 scramble request", if we need to request a
+ client to use 4.0 auth plugin (short scramble) and the scramble was
+ already sent to the client
+
+ below, cached_client_reply.plugin is the plugin name that client has used,
+ client_auth_plugin is derived from mysql.user table, for the given
+ user account, it's the plugin that the client need to use to login.
+ */
+ bool switch_from_long_to_short_scramble=
+ native_password_plugin_name.str == mpvio->cached_client_reply.plugin &&
+ client_auth_plugin == old_password_plugin_name.str;
+
+ if (switch_from_long_to_short_scramble)
+ DBUG_RETURN (secure_auth(mpvio->thd) ||
+ my_net_write(net, switch_plugin_request_buf, 1) ||
+ net_flush(net));
+
+ /*
+ We never request a client to switch from a short to long scramble.
+ Plugin-aware clients can do that, but traditionally it meant to
+ ask an old 4.0 client to use the new 4.1 authentication protocol.
+ */
+ bool switch_from_short_to_long_scramble=
+ old_password_plugin_name.str == mpvio->cached_client_reply.plugin &&
+ client_auth_plugin == native_password_plugin_name.str;
+
+ if (switch_from_short_to_long_scramble)
+ {
+ my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
+ general_log_print(mpvio->thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE));
+ DBUG_RETURN (1);
+ }
+
+ DBUG_PRINT("info", ("requesting client to use the %s plugin",
+ client_auth_plugin));
+ DBUG_RETURN(net_write_command(net, switch_plugin_request_buf[0],
+ (uchar*) client_auth_plugin,
+ strlen(client_auth_plugin) + 1,
+ (uchar*) data, data_len));
+}
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+/**
+ Finds acl entry in user database for authentication purposes.
+
+ Finds a user and copies it into mpvio. Creates a fake user
+ if no matching user account is found.
+
+ @retval 0 found
+ @retval 1 error
+*/
+static bool find_mpvio_user(MPVIO_EXT *mpvio)
+{
+ Security_context *sctx= mpvio->thd->security_ctx;
+ DBUG_ENTER("find_mpvio_user");
+ DBUG_ASSERT(mpvio->acl_user == 0);
+
+ mysql_mutex_lock(&acl_cache->lock);
+
+ ACL_USER *user= find_user_or_anon(sctx->host, sctx->user, sctx->ip);
+ if (user)
+ mpvio->acl_user= user->copy(mpvio->thd->mem_root);
+
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ if (!mpvio->acl_user)
+ {
+ /*
+ A matching user was not found. Fake it. Take any user, make the
+ authentication fail later.
+ This way we get a realistically looking failure, with occasional
+ "change auth plugin" requests even for nonexistent users. The ratio
+ of "change auth plugin" request will be the same for real and
+ nonexistent users.
+ Note, that we cannot pick any user at random, it must always be
+ the same user account for the incoming sctx->user name.
+ */
+ ulong nr1=1, nr2=4;
+ CHARSET_INFO *cs= &my_charset_latin1;
+ cs->coll->hash_sort(cs, (uchar*) sctx->user, strlen(sctx->user), &nr1, &nr2);
+
+ mysql_mutex_lock(&acl_cache->lock);
+ if (!acl_users.elements)
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ login_failed_error(mpvio->thd);
+ DBUG_RETURN(1);
+ }
+ uint i= nr1 % acl_users.elements;
+ ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*);
+ mpvio->acl_user= acl_user_tmp->copy(mpvio->thd->mem_root);
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ mpvio->make_it_fail= true;
+ }
+
+ /* user account requires non-default plugin and the client is too old */
+ if (mpvio->acl_user->plugin.str != native_password_plugin_name.str &&
+ mpvio->acl_user->plugin.str != old_password_plugin_name.str &&
+ !(mpvio->thd->client_capabilities & CLIENT_PLUGIN_AUTH))
+ {
+ DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str,
+ native_password_plugin_name.str));
+ DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str,
+ old_password_plugin_name.str));
+ my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
+ general_log_print(mpvio->thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE));
+ DBUG_RETURN (1);
+ }
+
+ mpvio->auth_info.user_name= sctx->user;
+ mpvio->auth_info.user_name_length= strlen(sctx->user);
+ mpvio->auth_info.auth_string= mpvio->acl_user->auth_string.str;
+ mpvio->auth_info.auth_string_length= (unsigned long) mpvio->acl_user->auth_string.length;
+ strmake_buf(mpvio->auth_info.authenticated_as, safe_str(mpvio->acl_user->user.str));
+
+ DBUG_PRINT("info", ("exit: user=%s, auth_string=%s, authenticated as=%s"
+ "plugin=%s",
+ mpvio->auth_info.user_name,
+ mpvio->auth_info.auth_string,
+ mpvio->auth_info.authenticated_as,
+ mpvio->acl_user->plugin.str));
+ DBUG_RETURN(0);
+}
+
+static bool
+read_client_connect_attrs(char **ptr, char *end,
+ const CHARSET_INFO *from_cs)
+{
+ size_t length;
+ char *ptr_save= *ptr;
+
+ /* not enough bytes to hold the length */
+ if (ptr_save >= end)
+ return true;
+
+ length= safe_net_field_length_ll((uchar **) ptr, end - ptr_save);
+
+ /* cannot even read the length */
+ if (*ptr == NULL)
+ return true;
+
+ /* length says there're more data than can fit into the packet */
+ if (*ptr + length > end)
+ return true;
+
+ /* impose an artificial length limit of 64k */
+ if (length > 65535)
+ return true;
+
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ if (PSI_THREAD_CALL(set_thread_connect_attrs)(*ptr, length, from_cs) &&
+ current_thd->variables.log_warnings)
+ sql_print_warning("Connection attributes of length %lu were truncated",
+ (unsigned long) length);
+#endif
+ return false;
+}
+
+#endif
+
+/* the packet format is described in send_change_user_packet() */
+static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length)
+{
+ THD *thd= mpvio->thd;
+ NET *net= &thd->net;
+ Security_context *sctx= thd->security_ctx;
+
+ char *user= (char*) net->read_pos;
+ char *end= user + packet_length;
+ /* Safe because there is always a trailing \0 at the end of the packet */
+ char *passwd= strend(user) + 1;
+ uint user_len= passwd - user - 1;
+ char *db= passwd;
+ char db_buff[SAFE_NAME_LEN + 1]; // buffer to store db in utf8
+ char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8
+ uint dummy_errors;
+ DBUG_ENTER ("parse_com_change_user_packet");
+
+ if (passwd >= end)
+ {
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ DBUG_RETURN (1);
+ }
+
+ /*
+ Old clients send null-terminated string as password; new clients send
+ the size (1 byte) + string (not null-terminated). Hence in case of empty
+ password both send '\0'.
+
+ This strlen() can't be easily deleted without changing protocol.
+
+ Cast *passwd to an unsigned char, so that it doesn't extend the sign for
+ *passwd > 127 and become 2**32-127+ after casting to uint.
+ */
+ uint passwd_len= (thd->client_capabilities & CLIENT_SECURE_CONNECTION ?
+ (uchar) (*passwd++) : strlen(passwd));
+
+ db+= passwd_len + 1;
+ /*
+ Database name is always NUL-terminated, so in case of empty database
+ the packet must contain at least the trailing '\0'.
+ */
+ if (db >= end)
+ {
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ DBUG_RETURN (1);
+ }
+
+ uint db_len= strlen(db);
+
+ char *next_field= db + db_len + 1;
+
+ if (next_field + 1 < end)
+ {
+ if (thd_init_client_charset(thd, uint2korr(next_field)))
+ DBUG_RETURN(1);
+ thd->update_charset();
+ next_field+= 2;
+ }
+
+ /* Convert database and user names to utf8 */
+ db_len= copy_and_convert(db_buff, sizeof(db_buff) - 1, system_charset_info,
+ db, db_len, thd->charset(), &dummy_errors);
+
+ user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1,
+ system_charset_info, user, user_len,
+ thd->charset(), &dummy_errors);
+
+ if (!(sctx->user= my_strndup(user_buff, user_len, MYF(MY_WME))))
+ DBUG_RETURN(1);
+
+ /* Clear variables that are allocated */
+ thd->user_connect= 0;
+ strmake_buf(sctx->priv_user, sctx->user);
+
+ if (thd->make_lex_string(&mpvio->db, db_buff, db_len) == 0)
+ DBUG_RETURN(1); /* The error is set by make_lex_string(). */
+
+ /*
+ Clear thd->db as it points to something, that will be freed when
+ connection is closed. We don't want to accidentally free a wrong
+ pointer if connect failed.
+ */
+ thd->reset_db(NULL, 0);
+
+ if (!initialized)
+ {
+ // if mysqld's been started with --skip-grant-tables option
+ mpvio->status= MPVIO_EXT::SUCCESS;
+ DBUG_RETURN(0);
+ }
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ thd->password= passwd_len > 0;
+ if (find_mpvio_user(mpvio))
+ DBUG_RETURN(1);
+
+ char *client_plugin;
+ if (thd->client_capabilities & CLIENT_PLUGIN_AUTH)
+ {
+ if (next_field >= end)
+ {
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ DBUG_RETURN(1);
+ }
+ client_plugin= fix_plugin_ptr(next_field);
+ next_field+= strlen(next_field) + 1;
+ }
+ else
+ {
+ if (thd->client_capabilities & CLIENT_SECURE_CONNECTION)
+ client_plugin= native_password_plugin_name.str;
+ else
+ {
+ client_plugin= old_password_plugin_name.str;
+ /*
+ For a passwordless accounts we use native_password_plugin.
+ But when an old 4.0 client connects to it, we change it to
+ old_password_plugin, otherwise MySQL will think that server
+ and client plugins don't match.
+ */
+ if (mpvio->acl_user->auth_string.length == 0)
+ mpvio->acl_user->plugin= old_password_plugin_name;
+ }
+ }
+
+ if ((mpvio->thd->client_capabilities & CLIENT_CONNECT_ATTRS) &&
+ read_client_connect_attrs(&next_field, end,
+ mpvio->thd->charset()))
+ {
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ DBUG_RETURN(packet_error);
+ }
+
+ DBUG_PRINT("info", ("client_plugin=%s, restart", client_plugin));
+ /*
+ Remember the data part of the packet, to present it to plugin in
+ read_packet()
+ */
+ mpvio->cached_client_reply.pkt= passwd;
+ mpvio->cached_client_reply.pkt_len= passwd_len;
+ mpvio->cached_client_reply.plugin= client_plugin;
+ mpvio->status= MPVIO_EXT::RESTART;
+#endif
+
+ DBUG_RETURN (0);
+}
+
+
+/* the packet format is described in send_client_reply_packet() */
+static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio,
+ uchar **buff, ulong pkt_len)
+{
+#ifndef EMBEDDED_LIBRARY
+ THD *thd= mpvio->thd;
+ NET *net= &thd->net;
+ char *end;
+ DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE);
+
+ if (pkt_len < MIN_HANDSHAKE_SIZE)
+ return packet_error;
+
+ /*
+ Protocol buffer is guaranteed to always end with \0. (see my_net_read())
+ As the code below depends on this, lets check that.
+ */
+ DBUG_ASSERT(net->read_pos[pkt_len] == 0);
+
+ ulong client_capabilities= uint2korr(net->read_pos);
+ if (client_capabilities & CLIENT_PROTOCOL_41)
+ {
+ if (pkt_len < 4)
+ return packet_error;
+ client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16;
+ }
+
+ /* Disable those bits which are not supported by the client. */
+ thd->client_capabilities&= client_capabilities;
+
+ DBUG_PRINT("info", ("client capabilities: %lu", thd->client_capabilities));
+ if (thd->client_capabilities & CLIENT_SSL)
+ {
+ unsigned long errptr __attribute__((unused));
+
+ /* Do the SSL layering. */
+ if (!ssl_acceptor_fd)
+ return packet_error;
+
+ DBUG_PRINT("info", ("IO layer change in progress..."));
+ if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout, &errptr))
+ {
+ DBUG_PRINT("error", ("Failed to accept new SSL connection"));
+ return packet_error;
+ }
+
+ DBUG_PRINT("info", ("Reading user information over SSL layer"));
+ pkt_len= my_net_read(net);
+ if (pkt_len == packet_error || pkt_len < NORMAL_HANDSHAKE_SIZE)
+ {
+ DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)",
+ pkt_len));
+ return packet_error;
+ }
+ }
+
+ if (client_capabilities & CLIENT_PROTOCOL_41)
+ {
+ if (pkt_len < 32)
+ return packet_error;
+ thd->max_client_packet_length= uint4korr(net->read_pos+4);
+ DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8]));
+ if (thd_init_client_charset(thd, (uint) net->read_pos[8]))
+ return packet_error;
+ thd->update_charset();
+ end= (char*) net->read_pos+32;
+ }
+ else
+ {
+ if (pkt_len < 5)
+ return packet_error;
+ thd->max_client_packet_length= uint3korr(net->read_pos+2);
+ end= (char*) net->read_pos+5;
+ }
+
+ if (end >= (char*) net->read_pos+ pkt_len +2)
+ return packet_error;
+
+ if (thd->client_capabilities & CLIENT_IGNORE_SPACE)
+ thd->variables.sql_mode|= MODE_IGNORE_SPACE;
+ if (thd->client_capabilities & CLIENT_INTERACTIVE)
+ thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout;
+
+ if (end >= (char*) net->read_pos+ pkt_len +2)
+ return packet_error;
+
+ if ((thd->client_capabilities & CLIENT_TRANSACTIONS) &&
+ opt_using_transactions)
+ net->return_status= &thd->server_status;
+
+ char *user= end;
+ char *passwd= strend(user)+1;
+ uint user_len= passwd - user - 1, db_len;
+ char *db= passwd;
+ char db_buff[SAFE_NAME_LEN + 1]; // buffer to store db in utf8
+ char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8
+ uint dummy_errors;
+
+ /*
+ Old clients send null-terminated string as password; new clients send
+ the size (1 byte) + string (not null-terminated). Hence in case of empty
+ password both send '\0'.
+
+ This strlen() can't be easily deleted without changing protocol.
+
+ Cast *passwd to an unsigned char, so that it doesn't extend the sign for
+ *passwd > 127 and become 2**32-127+ after casting to uint.
+ */
+ uint passwd_len;
+ if (!(thd->client_capabilities & CLIENT_SECURE_CONNECTION))
+ passwd_len= strlen(passwd);
+ else if (!(thd->client_capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA))
+ passwd_len= (uchar)(*passwd++);
+ else
+ passwd_len= safe_net_field_length_ll((uchar**)&passwd,
+ net->read_pos + pkt_len - (uchar*)passwd);
+
+ db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ?
+ db + passwd_len + 1 : 0;
+
+ if (passwd == NULL ||
+ passwd + passwd_len + MY_TEST(db) > (char*) net->read_pos + pkt_len)
+ return packet_error;
+
+ /* strlen() can't be easily deleted without changing protocol */
+ db_len= db ? strlen(db) : 0;
+
+ char *next_field;
+ char *client_plugin= next_field= passwd + passwd_len + (db ? db_len + 1 : 0);
+
+ /* Since 4.1 all database names are stored in utf8 */
+ if (db)
+ {
+ db_len= copy_and_convert(db_buff, sizeof(db_buff) - 1, system_charset_info,
+ db, db_len, thd->charset(), &dummy_errors);
+ db= db_buff;
+ }
+
+ user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1,
+ system_charset_info, user, user_len,
+ thd->charset(), &dummy_errors);
+ user= user_buff;
+
+ /* If username starts and ends in "'", chop them off */
+ if (user_len > 1 && user[0] == '\'' && user[user_len - 1] == '\'')
+ {
+ user++;
+ user_len-= 2;
+ }
+
+ /*
+ Clip username to allowed length in characters (not bytes). This is
+ mostly for backward compatibility (to truncate long usernames, as
+ old 5.1 did)
+ */
+ {
+ CHARSET_INFO *cs= system_charset_info;
+ int err;
+
+ user_len= (uint) cs->cset->well_formed_len(cs, user, user + user_len,
+ username_char_length, &err);
+ user[user_len]= '\0';
+ }
+
+ Security_context *sctx= thd->security_ctx;
+
+ if (thd->make_lex_string(&mpvio->db, db, db_len) == 0)
+ return packet_error; /* The error is set by make_lex_string(). */
+ my_free(sctx->user);
+ if (!(sctx->user= my_strndup(user, user_len, MYF(MY_WME))))
+ return packet_error; /* The error is set by my_strdup(). */
+
+
+ /*
+ Clear thd->db as it points to something, that will be freed when
+ connection is closed. We don't want to accidentally free a wrong
+ pointer if connect failed.
+ */
+ thd->reset_db(NULL, 0);
+
+ if (!initialized)
+ {
+ // if mysqld's been started with --skip-grant-tables option
+ mpvio->status= MPVIO_EXT::SUCCESS;
+ return packet_error;
+ }
+
+ thd->password= passwd_len > 0;
+ if (find_mpvio_user(mpvio))
+ return packet_error;
+
+ if ((thd->client_capabilities & CLIENT_PLUGIN_AUTH) &&
+ (client_plugin < (char *)net->read_pos + pkt_len))
+ {
+ client_plugin= fix_plugin_ptr(client_plugin);
+ next_field+= strlen(next_field) + 1;
+ }
+ else
+ {
+ /* Some clients lie. Sad, but true */
+ thd->client_capabilities &= ~CLIENT_PLUGIN_AUTH;
+
+ if (thd->client_capabilities & CLIENT_SECURE_CONNECTION)
+ client_plugin= native_password_plugin_name.str;
+ else
+ {
+ client_plugin= old_password_plugin_name.str;
+ /*
+ For a passwordless accounts we use native_password_plugin.
+ But when an old 4.0 client connects to it, we change it to
+ old_password_plugin, otherwise MySQL will think that server
+ and client plugins don't match.
+ */
+ if (mpvio->acl_user->auth_string.length == 0)
+ mpvio->acl_user->plugin= old_password_plugin_name;
+ }
+ }
+
+ if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) &&
+ read_client_connect_attrs(&next_field, ((char *)net->read_pos) + pkt_len,
+ mpvio->thd->charset()))
+ return packet_error;
+
+ /*
+ if the acl_user needs a different plugin to authenticate
+ (specified in GRANT ... AUTHENTICATED VIA plugin_name ..)
+ we need to restart the authentication in the server.
+ But perhaps the client has already used the correct plugin -
+ in that case the authentication on the client may not need to be
+ restarted and a server auth plugin will read the data that the client
+ has just send. Cache them to return in the next server_mpvio_read_packet().
+ */
+ if (my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str,
+ plugin_name(mpvio->plugin)->str) != 0)
+ {
+ mpvio->cached_client_reply.pkt= passwd;
+ mpvio->cached_client_reply.pkt_len= passwd_len;
+ mpvio->cached_client_reply.plugin= client_plugin;
+ mpvio->status= MPVIO_EXT::RESTART;
+ return packet_error;
+ }
+
+ /*
+ ok, we don't need to restart the authentication on the server.
+ but if the client used the wrong plugin, we need to restart
+ the authentication on the client. Do it here, the server plugin
+ doesn't need to know.
+ */
+ const char *client_auth_plugin=
+ ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin;
+
+ if (client_auth_plugin &&
+ my_strcasecmp(system_charset_info, client_plugin, client_auth_plugin))
+ {
+ mpvio->cached_client_reply.plugin= client_plugin;
+ if (send_plugin_request_packet(mpvio,
+ (uchar*) mpvio->cached_server_packet.pkt,
+ mpvio->cached_server_packet.pkt_len))
+ return packet_error;
+
+ passwd_len= my_net_read(&thd->net);
+ passwd= (char*)thd->net.read_pos;
+ }
+
+ *buff= (uchar*) passwd;
+ return passwd_len;
+#else
+ return 0;
+#endif
+}
+
+
+/**
+ vio->write_packet() callback method for server authentication plugins
+
+ This function is called by a server authentication plugin, when it wants
+ to send data to the client.
+
+ It transparently wraps the data into a handshake packet,
+ and handles plugin negotiation with the client. If necessary,
+ it escapes the plugin data, if it starts with a mysql protocol packet byte.
+*/
+static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param,
+ const uchar *packet, int packet_len)
+{
+ MPVIO_EXT *mpvio= (MPVIO_EXT *) param;
+ int res;
+ DBUG_ENTER("server_mpvio_write_packet");
+
+ /* reset cached_client_reply */
+ mpvio->cached_client_reply.pkt= 0;
+
+ /* for the 1st packet we wrap plugin data into the handshake packet */
+ if (mpvio->packets_written == 0)
+ res= send_server_handshake_packet(mpvio, (char*) packet, packet_len);
+ else if (mpvio->status == MPVIO_EXT::RESTART)
+ res= send_plugin_request_packet(mpvio, packet, packet_len);
+ else if (packet_len > 0 && (*packet == 1 || *packet == 255 || *packet == 254))
+ {
+ /*
+ we cannot allow plugin data packet to start from 255 or 254 -
+ as the client will treat it as an error or "change plugin" packet.
+ We'll escape these bytes with \1. Consequently, we
+ have to escape \1 byte too.
+ */
+ res= net_write_command(&mpvio->thd->net, 1, (uchar*)"", 0,
+ packet, packet_len);
+ }
+ else
+ {
+ res= my_net_write(&mpvio->thd->net, packet, packet_len) ||
+ net_flush(&mpvio->thd->net);
+ }
+ mpvio->packets_written++;
+ DBUG_RETURN(res);
+}
+
+/**
+ vio->read_packet() callback method for server authentication plugins
+
+ This function is called by a server authentication plugin, when it wants
+ to read data from the client.
+
+ It transparently extracts the client plugin data, if embedded into
+ a client authentication handshake packet, and handles plugin negotiation
+ with the client, if necessary.
+*/
+static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf)
+{
+ MPVIO_EXT *mpvio= (MPVIO_EXT *) param;
+ ulong pkt_len;
+ DBUG_ENTER("server_mpvio_read_packet");
+ if (mpvio->packets_written == 0)
+ {
+ /*
+ plugin wants to read the data without sending anything first.
+ send an empty packet to force a server handshake packet to be sent
+ */
+ if (server_mpvio_write_packet(mpvio, 0, 0))
+ pkt_len= packet_error;
+ else
+ pkt_len= my_net_read(&mpvio->thd->net);
+ }
+ else if (mpvio->cached_client_reply.pkt)
+ {
+ DBUG_ASSERT(mpvio->status == MPVIO_EXT::RESTART);
+ DBUG_ASSERT(mpvio->packets_read > 0);
+ /*
+ if the have the data cached from the last server_mpvio_read_packet
+ (which can be the case if it's a restarted authentication)
+ and a client has used the correct plugin, then we can return the
+ cached data straight away and avoid one round trip.
+ */
+ const char *client_auth_plugin=
+ ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin;
+ if (client_auth_plugin == 0 ||
+ my_strcasecmp(system_charset_info, mpvio->cached_client_reply.plugin,
+ client_auth_plugin) == 0)
+ {
+ mpvio->status= MPVIO_EXT::FAILURE;
+ *buf= (uchar*) mpvio->cached_client_reply.pkt;
+ mpvio->cached_client_reply.pkt= 0;
+ mpvio->packets_read++;
+
+ DBUG_RETURN ((int) mpvio->cached_client_reply.pkt_len);
+ }
+
+ /*
+ But if the client has used the wrong plugin, the cached data are
+ useless. Furthermore, we have to send a "change plugin" request
+ to the client.
+ */
+ if (server_mpvio_write_packet(mpvio, 0, 0))
+ pkt_len= packet_error;
+ else
+ pkt_len= my_net_read(&mpvio->thd->net);
+ }
+ else
+ pkt_len= my_net_read(&mpvio->thd->net);
+
+ if (pkt_len == packet_error)
+ goto err;
+
+ mpvio->packets_read++;
+
+ /*
+ the 1st packet has the plugin data wrapped into the client authentication
+ handshake packet
+ */
+ if (mpvio->packets_read == 1)
+ {
+ pkt_len= parse_client_handshake_packet(mpvio, buf, pkt_len);
+ if (pkt_len == packet_error)
+ goto err;
+ }
+ else
+ *buf= mpvio->thd->net.read_pos;
+
+ DBUG_RETURN((int)pkt_len);
+
+err:
+ if (mpvio->status == MPVIO_EXT::FAILURE)
+ {
+ if (!mpvio->thd->is_error())
+ my_error(ER_HANDSHAKE_ERROR, MYF(0));
+ }
+ DBUG_RETURN(-1);
+}
+
+/**
+ fills MYSQL_PLUGIN_VIO_INFO structure with the information about the
+ connection
+*/
+static void server_mpvio_info(MYSQL_PLUGIN_VIO *vio,
+ MYSQL_PLUGIN_VIO_INFO *info)
+{
+ MPVIO_EXT *mpvio= (MPVIO_EXT *) vio;
+ mpvio_info(mpvio->thd->net.vio, info);
+}
+
+static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user)
+{
+#ifdef HAVE_OPENSSL
+ Vio *vio= thd->net.vio;
+ SSL *ssl= (SSL *) vio->ssl_arg;
+ X509 *cert;
+#endif
+
+ /*
+ At this point we know that user is allowed to connect
+ from given host by given username/password pair. Now
+ we check if SSL is required, if user is using SSL and
+ if X509 certificate attributes are OK
+ */
+ switch (acl_user->ssl_type) {
+ case SSL_TYPE_NOT_SPECIFIED: // Impossible
+ case SSL_TYPE_NONE: // SSL is not required
+ return 0;
+#ifdef HAVE_OPENSSL
+ case SSL_TYPE_ANY: // Any kind of SSL is ok
+ return vio_type(vio) != VIO_TYPE_SSL;
+ case SSL_TYPE_X509: /* Client should have any valid certificate. */
+ /*
+ Connections with non-valid certificates are dropped already
+ in sslaccept() anyway, so we do not check validity here.
+
+ We need to check for absence of SSL because without SSL
+ we should reject connection.
+ */
+ if (vio_type(vio) == VIO_TYPE_SSL &&
+ SSL_get_verify_result(ssl) == X509_V_OK &&
+ (cert= SSL_get_peer_certificate(ssl)))
+ {
+ X509_free(cert);
+ return 0;
+ }
+ return 1;
+ case SSL_TYPE_SPECIFIED: /* Client should have specified attrib */
+ /* If a cipher name is specified, we compare it to actual cipher in use. */
+ if (vio_type(vio) != VIO_TYPE_SSL ||
+ SSL_get_verify_result(ssl) != X509_V_OK)
+ return 1;
+ if (acl_user->ssl_cipher)
+ {
+ DBUG_PRINT("info", ("comparing ciphers: '%s' and '%s'",
+ acl_user->ssl_cipher, SSL_get_cipher(ssl)));
+ if (strcmp(acl_user->ssl_cipher, SSL_get_cipher(ssl)))
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'",
+ acl_user->ssl_cipher, SSL_get_cipher(ssl));
+ return 1;
+ }
+ }
+ /* Prepare certificate (if exists) */
+ if (!(cert= SSL_get_peer_certificate(ssl)))
+ return 1;
+ /* If X509 issuer is specified, we check it... */
+ if (acl_user->x509_issuer)
+ {
+ char *ptr= X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
+ DBUG_PRINT("info", ("comparing issuers: '%s' and '%s'",
+ acl_user->x509_issuer, ptr));
+ if (strcmp(acl_user->x509_issuer, ptr))
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_information("X509 issuer mismatch: should be '%s' "
+ "but is '%s'", acl_user->x509_issuer, ptr);
+ free(ptr);
+ X509_free(cert);
+ return 1;
+ }
+ free(ptr);
+ }
+ /* X509 subject is specified, we check it .. */
+ if (acl_user->x509_subject)
+ {
+ char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
+ DBUG_PRINT("info", ("comparing subjects: '%s' and '%s'",
+ acl_user->x509_subject, ptr));
+ if (strcmp(acl_user->x509_subject, ptr))
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_information("X509 subject mismatch: should be '%s' but is '%s'",
+ acl_user->x509_subject, ptr);
+ free(ptr);
+ X509_free(cert);
+ return 1;
+ }
+ free(ptr);
+ }
+ X509_free(cert);
+ return 0;
+#else /* HAVE_OPENSSL */
+ default:
+ /*
+ If we don't have SSL but SSL is required for this user the
+ authentication should fail.
+ */
+ return 1;
+#endif /* HAVE_OPENSSL */
+ }
+ return 1;
+}
+
+
+static int do_auth_once(THD *thd, const LEX_STRING *auth_plugin_name,
+ MPVIO_EXT *mpvio)
+{
+ int res= CR_OK, old_status= MPVIO_EXT::FAILURE;
+ bool unlock_plugin= false;
+ plugin_ref plugin= NULL;
+
+ if (auth_plugin_name->str == native_password_plugin_name.str)
+ plugin= native_password_plugin;
+#ifndef EMBEDDED_LIBRARY
+ else if (auth_plugin_name->str == old_password_plugin_name.str)
+ plugin= old_password_plugin;
+ else if ((plugin= my_plugin_lock_by_name(thd, auth_plugin_name,
+ MYSQL_AUTHENTICATION_PLUGIN)))
+ unlock_plugin= true;
+#endif
+
+ mpvio->plugin= plugin;
+ old_status= mpvio->status;
+
+ if (plugin)
+ {
+ st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info;
+ switch (auth->interface_version) {
+ case 0x0200:
+ res= auth->authenticate_user(mpvio, &mpvio->auth_info);
+ break;
+ case 0x0100:
+ {
+ MYSQL_SERVER_AUTH_INFO_0x0100 compat;
+ compat.downgrade(&mpvio->auth_info);
+ res= auth->authenticate_user(mpvio, (MYSQL_SERVER_AUTH_INFO *)&compat);
+ compat.upgrade(&mpvio->auth_info);
+ }
+ break;
+ default: DBUG_ASSERT(0);
+ }
+
+ if (unlock_plugin)
+ plugin_unlock(thd, plugin);
+ }
+ else
+ {
+ /* Server cannot load the required plugin. */
+ Host_errors errors;
+ errors.m_no_auth_plugin= 1;
+ inc_host_errors(mpvio->thd->security_ctx->ip, &errors);
+ my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth_plugin_name->str);
+ res= CR_ERROR;
+ }
+
+ /*
+ If the status was MPVIO_EXT::RESTART before the authenticate_user() call
+ it can never be MPVIO_EXT::RESTART after the call, because any call
+ to write_packet() or read_packet() will reset the status.
+
+ But (!) if a plugin never called a read_packet() or write_packet(), the
+ status will stay unchanged. We'll fix it, by resetting the status here.
+ */
+ if (old_status == MPVIO_EXT::RESTART && mpvio->status == MPVIO_EXT::RESTART)
+ mpvio->status= MPVIO_EXT::FAILURE; // reset to the default
+
+ return res;
+}
+
+
+/**
+ Perform the handshake, authorize the client and update thd sctx variables.
+
+ @param thd thread handle
+ @param com_change_user_pkt_len size of the COM_CHANGE_USER packet
+ (without the first, command, byte) or 0
+ if it's not a COM_CHANGE_USER (that is, if
+ it's a new connection)
+
+ @retval 0 success, thd is updated.
+ @retval 1 error
+*/
+bool acl_authenticate(THD *thd, uint com_change_user_pkt_len)
+{
+ int res= CR_OK;
+ MPVIO_EXT mpvio;
+ const LEX_STRING *auth_plugin_name= default_auth_plugin_name;
+ enum enum_server_command command= com_change_user_pkt_len ? COM_CHANGE_USER
+ : COM_CONNECT;
+ DBUG_ENTER("acl_authenticate");
+
+ bzero(&mpvio, sizeof(mpvio));
+ mpvio.read_packet= server_mpvio_read_packet;
+ mpvio.write_packet= server_mpvio_write_packet;
+ mpvio.info= server_mpvio_info;
+ mpvio.thd= thd;
+ mpvio.status= MPVIO_EXT::FAILURE;
+ mpvio.make_it_fail= false;
+ mpvio.auth_info.host_or_ip= thd->security_ctx->host_or_ip;
+ mpvio.auth_info.host_or_ip_length=
+ (unsigned int) strlen(thd->security_ctx->host_or_ip);
+
+ DBUG_PRINT("info", ("com_change_user_pkt_len=%u", com_change_user_pkt_len));
+
+ if (command == COM_CHANGE_USER)
+ {
+ mpvio.packets_written++; // pretend that a server handshake packet was sent
+ mpvio.packets_read++; // take COM_CHANGE_USER packet into account
+
+ if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len))
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(mpvio.status == MPVIO_EXT::RESTART ||
+ mpvio.status == MPVIO_EXT::SUCCESS);
+ }
+ else
+ {
+ /* mark the thd as having no scramble yet */
+ thd->scramble[SCRAMBLE_LENGTH]= 1;
+
+ /*
+ perform the first authentication attempt, with the default plugin.
+ This sends the server handshake packet, reads the client reply
+ with a user name, and performs the authentication if everyone has used
+ the correct plugin.
+ */
+
+ res= do_auth_once(thd, auth_plugin_name, &mpvio);
+ }
+
+ /*
+ retry the authentication, if - after receiving the user name -
+ we found that we need to switch to a non-default plugin
+ */
+ if (mpvio.status == MPVIO_EXT::RESTART)
+ {
+ DBUG_ASSERT(mpvio.acl_user);
+ DBUG_ASSERT(command == COM_CHANGE_USER ||
+ my_strcasecmp(system_charset_info, auth_plugin_name->str,
+ mpvio.acl_user->plugin.str));
+ auth_plugin_name= &mpvio.acl_user->plugin;
+ res= do_auth_once(thd, auth_plugin_name, &mpvio);
+ }
+ if (mpvio.make_it_fail && res == CR_OK)
+ {
+ mpvio.status= MPVIO_EXT::FAILURE;
+ res= CR_ERROR;
+ }
+
+ Security_context *sctx= thd->security_ctx;
+ const ACL_USER *acl_user= mpvio.acl_user;
+
+ thd->password= mpvio.auth_info.password_used; // remember for error messages
+
+ /*
+ Log the command here so that the user can check the log
+ for the tried logins and also to detect break-in attempts.
+
+ if sctx->user is unset it's protocol failure, bad packet.
+ */
+ if (sctx->user)
+ {
+ if (strcmp(sctx->priv_user, sctx->user))
+ {
+ general_log_print(thd, command, "%s@%s as %s on %s",
+ sctx->user, sctx->host_or_ip,
+ sctx->priv_user[0] ? sctx->priv_user : "anonymous",
+ safe_str(mpvio.db.str));
+ }
+ else
+ general_log_print(thd, command, (char*) "%s@%s on %s",
+ sctx->user, sctx->host_or_ip,
+ safe_str(mpvio.db.str));
+ }
+
+ if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS)
+ {
+ Host_errors errors;
+ DBUG_ASSERT(mpvio.status == MPVIO_EXT::FAILURE);
+ switch (res)
+ {
+ case CR_AUTH_PLUGIN_ERROR:
+ errors.m_auth_plugin= 1;
+ break;
+ case CR_AUTH_HANDSHAKE:
+ errors.m_handshake= 1;
+ break;
+ case CR_AUTH_USER_CREDENTIALS:
+ errors.m_authentication= 1;
+ break;
+ case CR_ERROR:
+ default:
+ /* Unknown of unspecified auth plugin error. */
+ errors.m_auth_plugin= 1;
+ break;
+ }
+ inc_host_errors(mpvio.thd->security_ctx->ip, &errors);
+ if (!thd->is_error())
+ login_failed_error(thd);
+ DBUG_RETURN(1);
+ }
+
+ sctx->proxy_user[0]= 0;
+
+ if (initialized) // if not --skip-grant-tables
+ {
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ bool is_proxy_user= FALSE;
+ const char *auth_user = safe_str(acl_user->user.str);
+ ACL_PROXY_USER *proxy_user;
+ /* check if the user is allowed to proxy as another user */
+ proxy_user= acl_find_proxy_user(auth_user, sctx->host, sctx->ip,
+ mpvio.auth_info.authenticated_as,
+ &is_proxy_user);
+ if (is_proxy_user)
+ {
+ ACL_USER *acl_proxy_user;
+
+ /* we need to find the proxy user, but there was none */
+ if (!proxy_user)
+ {
+ Host_errors errors;
+ errors.m_proxy_user= 1;
+ inc_host_errors(mpvio.thd->security_ctx->ip, &errors);
+ if (!thd->is_error())
+ login_failed_error(thd);
+ DBUG_RETURN(1);
+ }
+
+ my_snprintf(sctx->proxy_user, sizeof(sctx->proxy_user) - 1,
+ "'%s'@'%s'", auth_user,
+ safe_str(acl_user->host.hostname));
+
+ /* we're proxying : find the proxy user definition */
+ mysql_mutex_lock(&acl_cache->lock);
+ acl_proxy_user= find_user_exact(safe_str(proxy_user->get_proxied_host()),
+ mpvio.auth_info.authenticated_as);
+ if (!acl_proxy_user)
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+
+ Host_errors errors;
+ errors.m_proxy_user_acl= 1;
+ inc_host_errors(mpvio.thd->security_ctx->ip, &errors);
+ if (!thd->is_error())
+ login_failed_error(thd);
+ DBUG_RETURN(1);
+ }
+ acl_user= acl_proxy_user->copy(thd->mem_root);
+ mysql_mutex_unlock(&acl_cache->lock);
+ }
+#endif
+
+ sctx->master_access= acl_user->access;
+ if (acl_user->user.str)
+ strmake_buf(sctx->priv_user, acl_user->user.str);
+ else
+ *sctx->priv_user= 0;
+
+ if (acl_user->host.hostname)
+ strmake_buf(sctx->priv_host, acl_user->host.hostname);
+ else
+ *sctx->priv_host= 0;
+
+ /*
+ OK. Let's check the SSL. Historically it was checked after the password,
+ as an additional layer, not instead of the password
+ (in which case it would've been a plugin too).
+ */
+ if (acl_check_ssl(thd, acl_user))
+ {
+ Host_errors errors;
+ errors.m_ssl= 1;
+ inc_host_errors(mpvio.thd->security_ctx->ip, &errors);
+ login_failed_error(thd);
+ DBUG_RETURN(1);
+ }
+
+ /*
+ Don't allow the user to connect if he has done too many queries.
+ As we are testing max_user_connections == 0 here, it means that we
+ can't let the user change max_user_connections from 0 in the server
+ without a restart as it would lead to wrong connect counting.
+ */
+ if ((acl_user->user_resource.questions ||
+ acl_user->user_resource.updates ||
+ acl_user->user_resource.conn_per_hour ||
+ acl_user->user_resource.user_conn || max_user_connections_checking) &&
+ get_or_create_user_conn(thd,
+ (opt_old_style_user_limits ? sctx->user : sctx->priv_user),
+ (opt_old_style_user_limits ? sctx->host_or_ip : sctx->priv_host),
+ &acl_user->user_resource))
+ DBUG_RETURN(1); // The error is set by get_or_create_user_conn()
+ }
+ else
+ sctx->skip_grants();
+
+ if (thd->user_connect &&
+ (thd->user_connect->user_resources.conn_per_hour ||
+ thd->user_connect->user_resources.user_conn ||
+ max_user_connections_checking) &&
+ check_for_max_user_connections(thd, thd->user_connect))
+ {
+ /* Ensure we don't decrement thd->user_connections->connections twice */
+ thd->user_connect= 0;
+ status_var_increment(denied_connections);
+ DBUG_RETURN(1); // The error is set in check_for_max_user_connections()
+ }
+
+ DBUG_PRINT("info",
+ ("Capabilities: %lu packet_length: %ld Host: '%s' "
+ "Login user: '%s' Priv_user: '%s' Using password: %s "
+ "Access: %lu db: '%s'",
+ thd->client_capabilities, thd->max_client_packet_length,
+ sctx->host_or_ip, sctx->user, sctx->priv_user,
+ thd->password ? "yes": "no",
+ sctx->master_access, mpvio.db.str));
+
+ if (command == COM_CONNECT &&
+ !(thd->main_security_ctx.master_access & SUPER_ACL))
+ {
+ mysql_mutex_lock(&LOCK_connection_count);
+ bool count_ok= (*thd->scheduler->connection_count <=
+ *thd->scheduler->max_connections);
+ mysql_mutex_unlock(&LOCK_connection_count);
+ if (!count_ok)
+ { // too many connections
+ my_error(ER_CON_COUNT_ERROR, MYF(0));
+ DBUG_RETURN(1);
+ }
+ }
+
+ /*
+ This is the default access rights for the current database. It's
+ set to 0 here because we don't have an active database yet (and we
+ may not have an active database to set.
+ */
+ sctx->db_access=0;
+
+ /* Change a database if necessary */
+ if (mpvio.db.length)
+ {
+ if (mysql_change_db(thd, &mpvio.db, FALSE))
+ {
+ /* mysql_change_db() has pushed the error message. */
+ status_var_increment(thd->status_var.access_denied_errors);
+ DBUG_RETURN(1);
+ }
+ }
+
+ thd->net.net_skip_rest_factor= 2; // skip at most 2*max_packet_size
+
+ if (mpvio.auth_info.external_user[0])
+ sctx->external_user= my_strdup(mpvio.auth_info.external_user, MYF(0));
+
+ if (res == CR_OK_HANDSHAKE_COMPLETE)
+ thd->get_stmt_da()->disable_status();
+ else
+ my_ok(thd);
+
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ PSI_THREAD_CALL(set_thread_user_host)
+ (thd->main_security_ctx.user, strlen(thd->main_security_ctx.user),
+ thd->main_security_ctx.host_or_ip, strlen(thd->main_security_ctx.host_or_ip));
+#endif
+
+ /* Ready to handle queries */
+ DBUG_RETURN(0);
+}
+
+/**
+ MySQL Server Password Authentication Plugin
+
+ In the MySQL authentication protocol:
+ 1. the server sends the random scramble to the client
+ 2. client sends the encrypted password back to the server
+ 3. the server checks the password.
+*/
+static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio,
+ MYSQL_SERVER_AUTH_INFO *info)
+{
+ uchar *pkt;
+ int pkt_len;
+ MPVIO_EXT *mpvio= (MPVIO_EXT *) vio;
+ THD *thd=mpvio->thd;
+ DBUG_ENTER("native_password_authenticate");
+
+ /* generate the scramble, or reuse the old one */
+ if (thd->scramble[SCRAMBLE_LENGTH])
+ {
+ create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand);
+ /* and send it to the client */
+ if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1))
+ DBUG_RETURN(CR_AUTH_HANDSHAKE);
+ }
+
+ /* reply and authenticate */
+
+ /*
+ <digression>
+ This is more complex than it looks.
+
+ The plugin (we) may be called right after the client was connected -
+ and will need to send a scramble, read reply, authenticate.
+
+ Or the plugin may be called after another plugin has sent a scramble,
+ and read the reply. If the client has used the correct client-plugin,
+ we won't need to read anything here from the client, the client
+ has already sent a reply with everything we need for authentication.
+
+ Or the plugin may be called after another plugin has sent a scramble,
+ and read the reply, but the client has used the wrong client-plugin.
+ We'll need to sent a "switch to another plugin" packet to the
+ client and read the reply. "Use the short scramble" packet is a special
+ case of "switch to another plugin" packet.
+
+ Or, perhaps, the plugin may be called after another plugin has
+ done the handshake but did not send a useful scramble. We'll need
+ to send a scramble (and perhaps a "switch to another plugin" packet)
+ and read the reply.
+
+ Besides, a client may be an old one, that doesn't understand plugins.
+ Or doesn't even understand 4.0 scramble.
+
+ And we want to keep the same protocol on the wire unless non-native
+ plugins are involved.
+
+ Anyway, it still looks simple from a plugin point of view:
+ "send the scramble, read the reply and authenticate".
+ All the magic is transparently handled by the server.
+ </digression>
+ */
+
+ /* read the reply with the encrypted password */
+ if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0)
+ DBUG_RETURN(CR_AUTH_HANDSHAKE);
+ DBUG_PRINT("info", ("reply read : pkt_len=%d", pkt_len));
+
+#ifdef NO_EMBEDDED_ACCESS_CHECKS
+ DBUG_RETURN(CR_OK);
+#endif
+
+ DBUG_EXECUTE_IF("native_password_bad_reply", { pkt_len= 12; });
+
+ if (pkt_len == 0) /* no password */
+ DBUG_RETURN(mpvio->acl_user->salt_len != 0 ? CR_AUTH_USER_CREDENTIALS : CR_OK);
+
+ info->password_used= PASSWORD_USED_YES;
+ if (pkt_len == SCRAMBLE_LENGTH)
+ {
+ if (!mpvio->acl_user->salt_len)
+ DBUG_RETURN(CR_AUTH_USER_CREDENTIALS);
+
+ if (check_scramble(pkt, thd->scramble, mpvio->acl_user->salt))
+ DBUG_RETURN(CR_AUTH_USER_CREDENTIALS);
+ else
+ DBUG_RETURN(CR_OK);
+ }
+
+ my_error(ER_HANDSHAKE_ERROR, MYF(0));
+ DBUG_RETURN(CR_AUTH_HANDSHAKE);
+}
+
+static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio,
+ MYSQL_SERVER_AUTH_INFO *info)
+{
+ uchar *pkt;
+ int pkt_len;
+ MPVIO_EXT *mpvio= (MPVIO_EXT *) vio;
+ THD *thd=mpvio->thd;
+
+ /* generate the scramble, or reuse the old one */
+ if (thd->scramble[SCRAMBLE_LENGTH])
+ {
+ create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand);
+ /* and send it to the client */
+ if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1))
+ return CR_AUTH_HANDSHAKE;
+ }
+
+ /* read the reply and authenticate */
+ if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0)
+ return CR_AUTH_HANDSHAKE;
+
+#ifdef NO_EMBEDDED_ACCESS_CHECKS
+ return CR_OK;
+#endif
+
+ /*
+ legacy: if switch_from_long_to_short_scramble,
+ the password is sent \0-terminated, the pkt_len is always 9 bytes.
+ We need to figure out the correct scramble length here.
+ */
+ if (pkt_len == SCRAMBLE_LENGTH_323 + 1)
+ pkt_len= strnlen((char*)pkt, pkt_len);
+
+ if (pkt_len == 0) /* no password */
+ return info->auth_string[0] ? CR_AUTH_USER_CREDENTIALS : CR_OK;
+
+ if (secure_auth(thd))
+ return CR_AUTH_HANDSHAKE;
+
+ info->password_used= PASSWORD_USED_YES;
+
+ if (pkt_len == SCRAMBLE_LENGTH_323)
+ {
+ if (!mpvio->acl_user->salt_len)
+ return CR_AUTH_USER_CREDENTIALS;
+
+ return check_scramble_323(pkt, thd->scramble,
+ (ulong *) mpvio->acl_user->salt) ?
+ CR_AUTH_USER_CREDENTIALS : CR_OK;
+ }
+
+ my_error(ER_HANDSHAKE_ERROR, MYF(0));
+ return CR_AUTH_HANDSHAKE;
+}
+
+static struct st_mysql_auth native_password_handler=
+{
+ MYSQL_AUTHENTICATION_INTERFACE_VERSION,
+ native_password_plugin_name.str,
+ native_password_authenticate
+};
+
+static struct st_mysql_auth old_password_handler=
+{
+ MYSQL_AUTHENTICATION_INTERFACE_VERSION,
+ old_password_plugin_name.str,
+ old_password_authenticate
+};
+
+maria_declare_plugin(mysql_password)
+{
+ MYSQL_AUTHENTICATION_PLUGIN, /* type constant */
+ &native_password_handler, /* type descriptor */
+ native_password_plugin_name.str, /* Name */
+ "R.J.Silk, Sergei Golubchik", /* Author */
+ "Native MySQL authentication", /* Description */
+ PLUGIN_LICENSE_GPL, /* License */
+ NULL, /* Init function */
+ NULL, /* Deinit function */
+ 0x0100, /* Version (1.0) */
+ NULL, /* status variables */
+ NULL, /* system variables */
+ "1.0", /* String version */
+ MariaDB_PLUGIN_MATURITY_STABLE /* Maturity */
+},
+{
+ MYSQL_AUTHENTICATION_PLUGIN, /* type constant */
+ &old_password_handler, /* type descriptor */
+ old_password_plugin_name.str, /* Name */
+ "R.J.Silk, Sergei Golubchik", /* Author */
+ "Old MySQL-4.0 authentication", /* Description */
+ PLUGIN_LICENSE_GPL, /* License */
+ NULL, /* Init function */
+ NULL, /* Deinit function */
+ 0x0100, /* Version (1.0) */
+ NULL, /* status variables */
+ NULL, /* system variables */
+ "1.0", /* String version */
+ MariaDB_PLUGIN_MATURITY_STABLE /* Maturity */
+}
+maria_declare_plugin_end;
+
+
+/* called when new user is created or exsisting password is changed */
+int check_password_policy(String *password)
+{
+ return (0);
+}
diff --git a/sql/sql_acl.h b/sql/sql_acl.h
new file mode 100644
index 00000000000..1aeb123153e
--- /dev/null
+++ b/sql/sql_acl.h
@@ -0,0 +1,409 @@
+#ifndef SQL_ACL_INCLUDED
+#define SQL_ACL_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "violite.h" /* SSL_type */
+#include "sql_class.h" /* LEX_COLUMN */
+
+#define SELECT_ACL (1L << 0)
+#define INSERT_ACL (1L << 1)
+#define UPDATE_ACL (1L << 2)
+#define DELETE_ACL (1L << 3)
+#define CREATE_ACL (1L << 4)
+#define DROP_ACL (1L << 5)
+#define RELOAD_ACL (1L << 6)
+#define SHUTDOWN_ACL (1L << 7)
+#define PROCESS_ACL (1L << 8)
+#define FILE_ACL (1L << 9)
+#define GRANT_ACL (1L << 10)
+#define REFERENCES_ACL (1L << 11)
+#define INDEX_ACL (1L << 12)
+#define ALTER_ACL (1L << 13)
+#define SHOW_DB_ACL (1L << 14)
+#define SUPER_ACL (1L << 15)
+#define CREATE_TMP_ACL (1L << 16)
+#define LOCK_TABLES_ACL (1L << 17)
+#define EXECUTE_ACL (1L << 18)
+#define REPL_SLAVE_ACL (1L << 19)
+#define REPL_CLIENT_ACL (1L << 20)
+#define CREATE_VIEW_ACL (1L << 21)
+#define SHOW_VIEW_ACL (1L << 22)
+#define CREATE_PROC_ACL (1L << 23)
+#define ALTER_PROC_ACL (1L << 24)
+#define CREATE_USER_ACL (1L << 25)
+#define EVENT_ACL (1L << 26)
+#define TRIGGER_ACL (1L << 27)
+#define CREATE_TABLESPACE_ACL (1L << 28)
+/*
+ don't forget to update
+ 1. static struct show_privileges_st sys_privileges[]
+ 2. static const char *command_array[] and static uint command_lengths[]
+ 3. mysql_system_tables.sql and mysql_system_tables_fix.sql
+ 4. acl_init() or whatever - to define behaviour for old privilege tables
+ 5. sql_yacc.yy - for GRANT/REVOKE to work
+*/
+#define NO_ACCESS (1L << 30)
+#define DB_ACLS \
+(UPDATE_ACL | SELECT_ACL | INSERT_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \
+ GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL | \
+ LOCK_TABLES_ACL | EXECUTE_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL | \
+ CREATE_PROC_ACL | ALTER_PROC_ACL | EVENT_ACL | TRIGGER_ACL)
+
+#define TABLE_ACLS \
+(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \
+ GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_VIEW_ACL | \
+ SHOW_VIEW_ACL | TRIGGER_ACL)
+
+#define COL_ACLS \
+(SELECT_ACL | INSERT_ACL | UPDATE_ACL | REFERENCES_ACL)
+
+#define PROC_ACLS \
+(ALTER_PROC_ACL | EXECUTE_ACL | GRANT_ACL)
+
+#define SHOW_PROC_ACLS \
+(ALTER_PROC_ACL | EXECUTE_ACL | CREATE_PROC_ACL)
+
+#define GLOBAL_ACLS \
+(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \
+ RELOAD_ACL | SHUTDOWN_ACL | PROCESS_ACL | FILE_ACL | GRANT_ACL | \
+ REFERENCES_ACL | INDEX_ACL | ALTER_ACL | SHOW_DB_ACL | SUPER_ACL | \
+ CREATE_TMP_ACL | LOCK_TABLES_ACL | REPL_SLAVE_ACL | REPL_CLIENT_ACL | \
+ EXECUTE_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL | CREATE_PROC_ACL | \
+ ALTER_PROC_ACL | CREATE_USER_ACL | EVENT_ACL | TRIGGER_ACL | \
+ CREATE_TABLESPACE_ACL)
+
+#define DEFAULT_CREATE_PROC_ACLS \
+(ALTER_PROC_ACL | EXECUTE_ACL)
+
+#define SHOW_CREATE_TABLE_ACLS \
+(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | \
+ CREATE_ACL | DROP_ACL | ALTER_ACL | INDEX_ACL | \
+ TRIGGER_ACL | REFERENCES_ACL | GRANT_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL)
+
+/**
+ Table-level privileges which are automatically "granted" to everyone on
+ existing temporary tables (CREATE_ACL is necessary for ALTER ... RENAME).
+*/
+#define TMP_TABLE_ACLS \
+(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \
+ INDEX_ACL | ALTER_ACL)
+
+/*
+ Defines to change the above bits to how things are stored in tables
+ This is needed as the 'host' and 'db' table is missing a few privileges
+*/
+
+/* Privileges that needs to be reallocated (in continous chunks) */
+#define DB_CHUNK0 (SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | \
+ CREATE_ACL | DROP_ACL)
+#define DB_CHUNK1 (GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL)
+#define DB_CHUNK2 (CREATE_TMP_ACL | LOCK_TABLES_ACL)
+#define DB_CHUNK3 (CREATE_VIEW_ACL | SHOW_VIEW_ACL | \
+ CREATE_PROC_ACL | ALTER_PROC_ACL )
+#define DB_CHUNK4 (EXECUTE_ACL)
+#define DB_CHUNK5 (EVENT_ACL | TRIGGER_ACL)
+
+#define fix_rights_for_db(A) (((A) & DB_CHUNK0) | \
+ (((A) << 4) & DB_CHUNK1) | \
+ (((A) << 6) & DB_CHUNK2) | \
+ (((A) << 9) & DB_CHUNK3) | \
+ (((A) << 2) & DB_CHUNK4))| \
+ (((A) << 9) & DB_CHUNK5)
+#define get_rights_for_db(A) (((A) & DB_CHUNK0) | \
+ (((A) & DB_CHUNK1) >> 4) | \
+ (((A) & DB_CHUNK2) >> 6) | \
+ (((A) & DB_CHUNK3) >> 9) | \
+ (((A) & DB_CHUNK4) >> 2))| \
+ (((A) & DB_CHUNK5) >> 9)
+#define TBL_CHUNK0 DB_CHUNK0
+#define TBL_CHUNK1 DB_CHUNK1
+#define TBL_CHUNK2 (CREATE_VIEW_ACL | SHOW_VIEW_ACL)
+#define TBL_CHUNK3 TRIGGER_ACL
+#define fix_rights_for_table(A) (((A) & TBL_CHUNK0) | \
+ (((A) << 4) & TBL_CHUNK1) | \
+ (((A) << 11) & TBL_CHUNK2) | \
+ (((A) << 15) & TBL_CHUNK3))
+#define get_rights_for_table(A) (((A) & TBL_CHUNK0) | \
+ (((A) & TBL_CHUNK1) >> 4) | \
+ (((A) & TBL_CHUNK2) >> 11) | \
+ (((A) & TBL_CHUNK3) >> 15))
+#define fix_rights_for_column(A) (((A) & 7) | (((A) & ~7) << 8))
+#define get_rights_for_column(A) (((A) & 7) | ((A) >> 8))
+#define fix_rights_for_procedure(A) ((((A) << 18) & EXECUTE_ACL) | \
+ (((A) << 23) & ALTER_PROC_ACL) | \
+ (((A) << 8) & GRANT_ACL))
+#define get_rights_for_procedure(A) ((((A) & EXECUTE_ACL) >> 18) | \
+ (((A) & ALTER_PROC_ACL) >> 23) | \
+ (((A) & GRANT_ACL) >> 8))
+
+enum mysql_db_table_field
+{
+ MYSQL_DB_FIELD_HOST = 0,
+ MYSQL_DB_FIELD_DB,
+ MYSQL_DB_FIELD_USER,
+ MYSQL_DB_FIELD_SELECT_PRIV,
+ MYSQL_DB_FIELD_INSERT_PRIV,
+ MYSQL_DB_FIELD_UPDATE_PRIV,
+ MYSQL_DB_FIELD_DELETE_PRIV,
+ MYSQL_DB_FIELD_CREATE_PRIV,
+ MYSQL_DB_FIELD_DROP_PRIV,
+ MYSQL_DB_FIELD_GRANT_PRIV,
+ MYSQL_DB_FIELD_REFERENCES_PRIV,
+ MYSQL_DB_FIELD_INDEX_PRIV,
+ MYSQL_DB_FIELD_ALTER_PRIV,
+ MYSQL_DB_FIELD_CREATE_TMP_TABLE_PRIV,
+ MYSQL_DB_FIELD_LOCK_TABLES_PRIV,
+ MYSQL_DB_FIELD_CREATE_VIEW_PRIV,
+ MYSQL_DB_FIELD_SHOW_VIEW_PRIV,
+ MYSQL_DB_FIELD_CREATE_ROUTINE_PRIV,
+ MYSQL_DB_FIELD_ALTER_ROUTINE_PRIV,
+ MYSQL_DB_FIELD_EXECUTE_PRIV,
+ MYSQL_DB_FIELD_EVENT_PRIV,
+ MYSQL_DB_FIELD_TRIGGER_PRIV,
+ MYSQL_DB_FIELD_COUNT
+};
+
+extern const TABLE_FIELD_DEF mysql_db_table_def;
+extern bool mysql_user_table_is_in_short_password_format;
+
+extern LEX_STRING host_not_specified;
+extern LEX_STRING current_user;
+extern LEX_STRING current_role;
+extern LEX_STRING current_user_and_current_role;
+
+
+static inline int access_denied_error_code(int passwd_used)
+{
+ return passwd_used == 2 ? ER_ACCESS_DENIED_NO_PASSWORD_ERROR
+ : ER_ACCESS_DENIED_ERROR;
+}
+
+
+/* prototypes */
+
+bool hostname_requires_resolving(const char *hostname);
+my_bool acl_init(bool dont_read_acl_tables);
+my_bool acl_reload(THD *thd);
+void acl_free(bool end=0);
+ulong acl_get(const char *host, const char *ip,
+ const char *user, const char *db, my_bool db_is_pattern);
+bool acl_authenticate(THD *thd, uint com_change_user_pkt_len);
+bool acl_getroot(Security_context *sctx, char *user, char *host,
+ char *ip, char *db);
+bool acl_check_host(const char *host, const char *ip);
+int check_change_password(THD *thd, const char *host, const char *user,
+ char *password, uint password_len);
+bool change_password(THD *thd, const char *host, const char *user,
+ char *password);
+
+bool mysql_grant_role(THD *thd, List<LEX_USER> &user_list, bool revoke);
+bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &user_list,
+ ulong rights, bool revoke, bool is_proxy);
+int mysql_table_grant(THD *thd, TABLE_LIST *table, List <LEX_USER> &user_list,
+ List <LEX_COLUMN> &column_list, ulong rights,
+ bool revoke);
+bool mysql_routine_grant(THD *thd, TABLE_LIST *table, bool is_proc,
+ List <LEX_USER> &user_list, ulong rights,
+ bool revoke, bool write_to_binlog);
+my_bool grant_init();
+void grant_free(void);
+my_bool grant_reload(THD *thd);
+bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables,
+ bool any_combination_will_do, uint number, bool no_errors);
+bool check_grant_column (THD *thd, GRANT_INFO *grant,
+ const char *db_name, const char *table_name,
+ const char *name, uint length, Security_context *sctx);
+bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref,
+ const char *name, uint length);
+bool check_grant_all_columns(THD *thd, ulong want_access,
+ Field_iterator_table_ref *fields);
+bool check_grant_routine(THD *thd, ulong want_access,
+ TABLE_LIST *procs, bool is_proc, bool no_error);
+bool check_grant_db(THD *thd,const char *db);
+ulong get_table_grant(THD *thd, TABLE_LIST *table);
+ulong get_column_grant(THD *thd, GRANT_INFO *grant,
+ const char *db_name, const char *table_name,
+ const char *field_name);
+bool mysql_show_grants(THD *thd, LEX_USER *user);
+int fill_schema_enabled_roles(THD *thd, TABLE_LIST *tables, COND *cond);
+int fill_schema_applicable_roles(THD *thd, TABLE_LIST *tables, COND *cond);
+void get_privilege_desc(char *to, uint max_length, ulong access);
+void get_mqh(const char *user, const char *host, USER_CONN *uc);
+bool mysql_create_user(THD *thd, List <LEX_USER> &list, bool handle_as_role);
+bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool handle_as_role);
+bool mysql_rename_user(THD *thd, List <LEX_USER> &list);
+bool mysql_revoke_all(THD *thd, List <LEX_USER> &list);
+void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
+ const char *db, const char *table);
+bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name,
+ bool is_proc);
+bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name,
+ bool is_proc);
+bool check_routine_level_acl(THD *thd, const char *db, const char *name,
+ bool is_proc);
+bool is_acl_user(const char *host, const char *user);
+int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
+int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
+int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
+int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
+int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr);
+int check_password_policy(String *password);
+#ifdef NO_EMBEDDED_ACCESS_CHECKS
+#define check_grant(A,B,C,D,E,F) 0
+#define check_grant_db(A,B) 0
+#endif
+
+/**
+ Result of an access check for an internal schema or table.
+ Internal ACL checks are always performed *before* using
+ the grant tables.
+ This mechanism enforces that the server implementation has full
+ control on its internal tables.
+ Depending on the internal check result, the server implementation
+ can choose to:
+ - always allow access,
+ - always deny access,
+ - delegate the decision to the database administrator,
+ by using the grant tables.
+*/
+enum ACL_internal_access_result
+{
+ /**
+ Access granted for all the requested privileges,
+ do not use the grant tables.
+ This flag is used only for the INFORMATION_SCHEMA privileges,
+ for compatibility reasons.
+ */
+ ACL_INTERNAL_ACCESS_GRANTED,
+ /** Access denied, do not use the grant tables. */
+ ACL_INTERNAL_ACCESS_DENIED,
+ /** No decision yet, use the grant tables. */
+ ACL_INTERNAL_ACCESS_CHECK_GRANT
+};
+
+/**
+ Per internal table ACL access rules.
+ This class is an interface.
+ Per table(s) specific access rule should be implemented in a subclass.
+ @sa ACL_internal_schema_access
+*/
+class ACL_internal_table_access
+{
+public:
+ ACL_internal_table_access()
+ {}
+
+ virtual ~ACL_internal_table_access()
+ {}
+
+ /**
+ Check access to an internal table.
+ When a privilege is granted, this method add the requested privilege
+ to save_priv.
+ @param want_access the privileges requested
+ @param [in, out] save_priv the privileges granted
+ @return
+ @retval ACL_INTERNAL_ACCESS_GRANTED All the requested privileges
+ are granted, and saved in save_priv.
+ @retval ACL_INTERNAL_ACCESS_DENIED At least one of the requested
+ privileges was denied.
+ @retval ACL_INTERNAL_ACCESS_CHECK_GRANT No requested privilege
+ was denied, and grant should be checked for at least one
+ privilege. Requested privileges that are granted, if any, are saved
+ in save_priv.
+ */
+ virtual ACL_internal_access_result check(ulong want_access,
+ ulong *save_priv) const= 0;
+};
+
+/**
+ Per internal schema ACL access rules.
+ This class is an interface.
+ Each per schema specific access rule should be implemented
+ in a different subclass, and registered.
+ Per schema access rules can control:
+ - every schema privileges on schema.*
+ - every table privileges on schema.table
+ @sa ACL_internal_schema_registry
+*/
+class ACL_internal_schema_access
+{
+public:
+ ACL_internal_schema_access()
+ {}
+
+ virtual ~ACL_internal_schema_access()
+ {}
+
+ /**
+ Check access to an internal schema.
+ @param want_access the privileges requested
+ @param [in, out] save_priv the privileges granted
+ @return
+ @retval ACL_INTERNAL_ACCESS_GRANTED All the requested privileges
+ are granted, and saved in save_priv.
+ @retval ACL_INTERNAL_ACCESS_DENIED At least one of the requested
+ privileges was denied.
+ @retval ACL_INTERNAL_ACCESS_CHECK_GRANT No requested privilege
+ was denied, and grant should be checked for at least one
+ privilege. Requested privileges that are granted, if any, are saved
+ in save_priv.
+ */
+ virtual ACL_internal_access_result check(ulong want_access,
+ ulong *save_priv) const= 0;
+
+ /**
+ Search for per table ACL access rules by table name.
+ @param name the table name
+ @return per table access rules, or NULL
+ */
+ virtual const ACL_internal_table_access *lookup(const char *name) const= 0;
+};
+
+/**
+ A registry for per internal schema ACL.
+ An 'internal schema' is a database schema maintained by the
+ server implementation, such as 'performance_schema' and 'INFORMATION_SCHEMA'.
+*/
+class ACL_internal_schema_registry
+{
+public:
+ static void register_schema(const LEX_STRING *name,
+ const ACL_internal_schema_access *access);
+ static const ACL_internal_schema_access *lookup(const char *name);
+};
+
+const ACL_internal_schema_access *
+get_cached_schema_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name);
+
+const ACL_internal_table_access *
+get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name,
+ const char *table_name);
+
+bool acl_check_proxy_grant_access (THD *thd, const char *host, const char *user,
+ bool with_grant);
+int acl_setrole(THD *thd, char *rolename, ulonglong access);
+int acl_check_setrole(THD *thd, char *rolename, ulonglong *access);
+
+#ifndef DBUG_OFF
+extern ulong role_global_merges, role_db_merges, role_table_merges,
+ role_column_merges, role_routine_merges;
+#endif
+#endif /* SQL_ACL_INCLUDED */
diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc
new file mode 100644
index 00000000000..afce7794dd7
--- /dev/null
+++ b/sql/sql_admin.cc
@@ -0,0 +1,1285 @@
+/* Copyright (c) 2010, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2012, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#include "sql_class.h" // THD and my_global.h
+#include "keycaches.h" // get_key_cache
+#include "sql_base.h" // Open_table_context
+#include "lock.h" // MYSQL_OPEN_*
+#include "sql_handler.h" // mysql_ha_rm_tables
+#include "partition_element.h" // PART_ADMIN
+#include "sql_partition.h" // set_part_state
+#include "transaction.h" // trans_rollback_stmt
+#include "sql_view.h" // view_checksum
+#include "sql_table.h" // mysql_recreate_table
+#include "debug_sync.h" // DEBUG_SYNC
+#include "sql_acl.h" // *_ACL
+#include "sp.h" // Sroutine_hash_entry
+#include "sql_parse.h" // check_table_access
+#include "strfunc.h"
+#include "sql_admin.h"
+#include "sql_statistics.h"
+
+/* Prepare, run and cleanup for mysql_recreate_table() */
+
+static bool admin_recreate_table(THD *thd, TABLE_LIST *table_list)
+{
+ bool result_code;
+ DBUG_ENTER("admin_recreate_table");
+
+ trans_rollback_stmt(thd);
+ trans_rollback(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+
+ /*
+ table_list->table has been closed and freed. Do not reference
+ uninitialized data. open_tables() could fail.
+ */
+ table_list->table= NULL;
+ /* Same applies to MDL ticket. */
+ table_list->mdl_request.ticket= NULL;
+
+ DEBUG_SYNC(thd, "ha_admin_try_alter");
+ tmp_disable_binlog(thd); // binlogging is done by caller if wanted
+ result_code= (open_temporary_tables(thd, table_list) ||
+ mysql_recreate_table(thd, table_list, false));
+ reenable_binlog(thd);
+ /*
+ mysql_recreate_table() can push OK or ERROR.
+ Clear 'OK' status. If there is an error, keep it:
+ we will store the error message in a result set row
+ and then clear.
+ */
+ if (thd->get_stmt_da()->is_ok())
+ thd->get_stmt_da()->reset_diagnostics_area();
+ table_list->table= NULL;
+ result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK;
+ DBUG_RETURN(result_code);
+}
+
+
+static int send_check_errmsg(THD *thd, TABLE_LIST* table,
+ const char* operator_name, const char* errmsg)
+
+{
+ Protocol *protocol= thd->protocol;
+ protocol->prepare_for_resend();
+ protocol->store(table->alias, system_charset_info);
+ protocol->store((char*) operator_name, system_charset_info);
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+ protocol->store(errmsg, system_charset_info);
+ thd->clear_error();
+ if (protocol->write())
+ return -1;
+ return 1;
+}
+
+
+static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
+ HA_CHECK_OPT *check_opt)
+{
+ int error= 0;
+ TABLE tmp_table, *table;
+ TABLE_LIST *pos_in_locked_tables= 0;
+ TABLE_SHARE *share;
+ bool has_mdl_lock= FALSE;
+ char from[FN_REFLEN],tmp[FN_REFLEN+32];
+ const char **ext;
+ MY_STAT stat_info;
+ Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH |
+ MYSQL_OPEN_HAS_MDL_LOCK |
+ MYSQL_LOCK_IGNORE_TIMEOUT));
+ DBUG_ENTER("prepare_for_repair");
+
+ if (!(check_opt->sql_flags & TT_USEFRM))
+ DBUG_RETURN(0);
+
+ if (!(table= table_list->table))
+ {
+ /*
+ If the table didn't exist, we have a shared metadata lock
+ on it that is left from mysql_admin_table()'s attempt to
+ open it. Release the shared metadata lock before trying to
+ acquire the exclusive lock to satisfy MDL asserts and avoid
+ deadlocks.
+ */
+ thd->mdl_context.release_transactional_locks();
+ /*
+ Attempt to do full-blown table open in mysql_admin_table() has failed.
+ Let us try to open at least a .FRM for this table.
+ */
+
+ table_list->mdl_request.init(MDL_key::TABLE,
+ table_list->db, table_list->table_name,
+ MDL_EXCLUSIVE, MDL_TRANSACTION);
+
+ if (lock_table_names(thd, table_list, table_list->next_global,
+ thd->variables.lock_wait_timeout, 0))
+ DBUG_RETURN(0);
+ has_mdl_lock= TRUE;
+
+ share= tdc_acquire_share_shortlived(thd, table_list, GTS_TABLE);
+ if (share == NULL)
+ DBUG_RETURN(0); // Can't open frm file
+
+ if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE))
+ {
+ tdc_release_share(share);
+ DBUG_RETURN(0); // Out of memory
+ }
+ table= &tmp_table;
+ }
+
+ /*
+ REPAIR TABLE ... USE_FRM for temporary tables makes little sense.
+ */
+ if (table->s->tmp_table)
+ {
+ error= send_check_errmsg(thd, table_list, "repair",
+ "Cannot repair temporary table from .frm file");
+ goto end;
+ }
+
+ /*
+ User gave us USE_FRM which means that the header in the index file is
+ trashed.
+ In this case we will try to fix the table the following way:
+ - Rename the data file to a temporary name
+ - Truncate the table
+ - Replace the new data file with the old one
+ - Run a normal repair using the new index file and the old data file
+ */
+
+ if (table->s->frm_version != FRM_VER_TRUE_VARCHAR &&
+ table->s->varchar_fields)
+ {
+ error= send_check_errmsg(thd, table_list, "repair",
+ "Failed repairing a very old .frm file as the data file format has changed between versions. Please dump the table in your old system with mysqldump and read it into this system with mysql or mysqlimport");
+ goto end;
+ }
+
+ /*
+ Check if this is a table type that stores index and data separately,
+ like ISAM or MyISAM. We assume fixed order of engine file name
+ extentions array. First element of engine file name extentions array
+ is meta/index file extention. Second element - data file extention.
+ */
+ ext= table->file->bas_ext();
+ if (!ext[0] || !ext[1])
+ goto end; // No data file
+
+ /* A MERGE table must not come here. */
+ DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM);
+
+ // Name of data file
+ strxmov(from, table->s->normalized_path.str, ext[1], NullS);
+ if (!mysql_file_stat(key_file_misc, from, &stat_info, MYF(0)))
+ goto end; // Can't use USE_FRM flag
+
+ my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx",
+ from, current_pid, thd->thread_id);
+
+ if (table_list->table)
+ {
+ /*
+ Table was successfully open in mysql_admin_table(). Now we need
+ to close it, but leave it protected by exclusive metadata lock.
+ */
+ pos_in_locked_tables= table->pos_in_locked_tables;
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_FORCED_CLOSE))
+ goto end;
+ /* Close table but don't remove from locked list */
+ close_all_tables_for_name(thd, table_list->table->s,
+ HA_EXTRA_NOT_USED, NULL);
+ table_list->table= 0;
+ }
+ /*
+ After this point we have an exclusive metadata lock on our table
+ in both cases when table was successfully open in mysql_admin_table()
+ and when it was open in prepare_for_repair().
+ */
+
+ if (my_rename(from, tmp, MYF(MY_WME)))
+ {
+ error= send_check_errmsg(thd, table_list, "repair",
+ "Failed renaming data file");
+ goto end;
+ }
+ if (dd_recreate_table(thd, table_list->db, table_list->table_name))
+ {
+ error= send_check_errmsg(thd, table_list, "repair",
+ "Failed generating table from .frm file");
+ goto end;
+ }
+ /*
+ 'FALSE' for 'using_transactions' means don't postpone
+ invalidation till the end of a transaction, but do it
+ immediately.
+ */
+ query_cache_invalidate3(thd, table_list, FALSE);
+ if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME)))
+ {
+ error= send_check_errmsg(thd, table_list, "repair",
+ "Failed restoring .MYD file");
+ goto end;
+ }
+
+ if (thd->locked_tables_list.locked_tables())
+ {
+ if (thd->locked_tables_list.reopen_tables(thd))
+ goto end;
+ /* Restore the table in the table list with the new opened table */
+ table_list->table= pos_in_locked_tables->table;
+ }
+ else
+ {
+ /*
+ Now we should be able to open the partially repaired table
+ to finish the repair in the handler later on.
+ */
+ if (open_table(thd, table_list, thd->mem_root, &ot_ctx))
+ {
+ error= send_check_errmsg(thd, table_list, "repair",
+ "Failed to open partially repaired table");
+ goto end;
+ }
+ }
+
+end:
+ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
+ if (table == &tmp_table)
+ closefrm(table, 1); // Free allocated memory
+ /* In case of a temporary table there will be no metadata lock. */
+ if (error && has_mdl_lock)
+ thd->mdl_context.release_transactional_locks();
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Check if a given error is something that could occur during
+ open_and_lock_tables() that does not indicate table corruption.
+
+ @param sql_errno Error number to check.
+
+ @retval TRUE Error does not indicate table corruption.
+ @retval FALSE Error could indicate table corruption.
+*/
+
+static inline bool table_not_corrupt_error(uint sql_errno)
+{
+ return (sql_errno == ER_NO_SUCH_TABLE ||
+ sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE ||
+ sql_errno == ER_FILE_NOT_FOUND ||
+ sql_errno == ER_LOCK_WAIT_TIMEOUT ||
+ sql_errno == ER_LOCK_DEADLOCK ||
+ sql_errno == ER_CANT_LOCK_LOG_TABLE ||
+ sql_errno == ER_OPEN_AS_READONLY);
+}
+
+
+/*
+ RETURN VALUES
+ FALSE Message sent to net (admin operation went ok)
+ TRUE Message should be sent by caller
+ (admin operation or network communication failed)
+*/
+static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
+ HA_CHECK_OPT* check_opt,
+ const char *operator_name,
+ thr_lock_type lock_type,
+ bool open_for_modify,
+ bool repair_table_use_frm,
+ uint extra_open_options,
+ int (*prepare_func)(THD *, TABLE_LIST *,
+ HA_CHECK_OPT *),
+ int (handler::*operator_func)(THD *,
+ HA_CHECK_OPT *),
+ int (view_operator_func)(THD *, TABLE_LIST*,
+ HA_CHECK_OPT *))
+{
+ TABLE_LIST *table;
+ SELECT_LEX *select= &thd->lex->select_lex;
+ List<Item> field_list;
+ Item *item;
+ Protocol *protocol= thd->protocol;
+ LEX *lex= thd->lex;
+ int result_code;
+ int compl_result_code;
+ bool need_repair_or_alter= 0;
+ wait_for_commit* suspended_wfc;
+
+ DBUG_ENTER("mysql_admin_table");
+ DBUG_PRINT("enter", ("extra_open_options: %u", extra_open_options));
+
+ field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2));
+ item->maybe_null = 1;
+ field_list.push_back(item = new Item_empty_string("Op", 10));
+ item->maybe_null = 1;
+ field_list.push_back(item = new Item_empty_string("Msg_type", 10));
+ item->maybe_null = 1;
+ field_list.push_back(item = new Item_empty_string("Msg_text",
+ SQL_ADMIN_MSG_TEXT_SIZE));
+ item->maybe_null = 1;
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ /*
+ This function calls trans_commit() during its operation, but that does not
+ imply that the operation is complete or binlogged. So we have to suspend
+ temporarily the wakeup_subsequent_commits() calls (if used).
+ */
+ suspended_wfc= thd->suspend_subsequent_commits();
+
+ mysql_ha_rm_tables(thd, tables);
+
+ /*
+ Close all temporary tables which were pre-open to simplify
+ privilege checking. Clear all references to closed tables.
+ */
+ close_thread_tables(thd);
+ for (table= tables; table; table= table->next_local)
+ table->table= NULL;
+
+ for (table= tables; table; table= table->next_local)
+ {
+ char table_name[SAFE_NAME_LEN*2+2];
+ char* db = table->db;
+ bool fatal_error=0;
+ bool open_error;
+
+ DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name));
+ strxmov(table_name, db, ".", table->table_name, NullS);
+ thd->open_options|= extra_open_options;
+ table->lock_type= lock_type;
+ /*
+ To make code safe for re-execution we need to reset type of MDL
+ request as code below may change it.
+ To allow concurrent execution of read-only operations we acquire
+ weak metadata lock for them.
+ */
+ table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ);
+ /* open only one table from local list of command */
+ {
+ TABLE_LIST *save_next_global, *save_next_local;
+ save_next_global= table->next_global;
+ table->next_global= 0;
+ save_next_local= table->next_local;
+ table->next_local= 0;
+ select->table_list.first= table;
+ /*
+ Time zone tables and SP tables can be add to lex->query_tables list,
+ so it have to be prepared.
+ TODO: Investigate if we can put extra tables into argument instead of
+ using lex->query_tables
+ */
+ lex->query_tables= table;
+ lex->query_tables_last= &table->next_global;
+ lex->query_tables_own_last= 0;
+
+ if (view_operator_func == NULL)
+ {
+ table->required_type=FRMTYPE_TABLE;
+ DBUG_ASSERT(!lex->only_view);
+ }
+ else if (lex->only_view)
+ {
+ table->required_type= FRMTYPE_VIEW;
+ }
+ else if (!lex->only_view && lex->sql_command == SQLCOM_REPAIR)
+ {
+ table->required_type= FRMTYPE_TABLE;
+ }
+
+ if (lex->sql_command == SQLCOM_CHECK ||
+ lex->sql_command == SQLCOM_REPAIR ||
+ lex->sql_command == SQLCOM_ANALYZE ||
+ lex->sql_command == SQLCOM_OPTIMIZE)
+ thd->prepare_derived_at_open= TRUE;
+ if (!thd->locked_tables_mode && repair_table_use_frm)
+ {
+ /*
+ If we're not under LOCK TABLES and we're executing REPAIR TABLE
+ USE_FRM, we need to ignore errors from open_and_lock_tables().
+ REPAIR TABLE USE_FRM is a heavy weapon used when a table is
+ critically damaged, so open_and_lock_tables() will most likely
+ report errors. Those errors are not interesting for the user
+ because it's already known that the table is badly damaged.
+ */
+
+ Diagnostics_area *da= thd->get_stmt_da();
+ Warning_info tmp_wi(thd->query_id, false, true);
+
+ da->push_warning_info(&tmp_wi);
+
+ open_error= (open_temporary_tables(thd, table) ||
+ open_and_lock_tables(thd, table, TRUE, 0));
+
+ da->pop_warning_info();
+ }
+ else
+ {
+ /*
+ It's assumed that even if it is REPAIR TABLE USE_FRM, the table
+ can be opened if we're under LOCK TABLES (otherwise LOCK TABLES
+ would fail). Thus, the only errors we could have from
+ open_and_lock_tables() are logical ones, like incorrect locking
+ mode. It does make sense for the user to see such errors.
+ */
+
+ open_error= (open_temporary_tables(thd, table) ||
+ open_and_lock_tables(thd, table, TRUE, 0));
+ }
+ thd->prepare_derived_at_open= FALSE;
+
+ table->next_global= save_next_global;
+ table->next_local= save_next_local;
+ thd->open_options&= ~extra_open_options;
+
+ /*
+ If open_and_lock_tables() failed, close_thread_tables() will close
+ the table and table->table can therefore be invalid.
+ */
+ if (open_error)
+ table->table= NULL;
+
+ /*
+ Under locked tables, we know that the table can be opened,
+ so any errors opening the table are logical errors.
+ In these cases it does not make sense to try to repair.
+ */
+ if (open_error && thd->locked_tables_mode)
+ {
+ result_code= HA_ADMIN_FAILED;
+ goto send_result;
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (table->table)
+ {
+ /*
+ Set up which partitions that should be processed
+ if ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR PARTITION ..
+ CACHE INDEX/LOAD INDEX for specified partitions
+ */
+ Alter_info *alter_info= &lex->alter_info;
+
+ if (alter_info->flags & Alter_info::ALTER_ADMIN_PARTITION)
+ {
+ if (!table->table->part_info)
+ {
+ my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
+ goto err2;
+ }
+ if (set_part_state(alter_info, table->table->part_info, PART_ADMIN))
+ {
+ char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE];
+ size_t length;
+ DBUG_PRINT("admin", ("sending non existent partition error"));
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store(operator_name, system_charset_info);
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+ length= my_snprintf(buff, sizeof(buff),
+ ER(ER_DROP_PARTITION_NON_EXISTENT),
+ table_name);
+ protocol->store(buff, length, system_charset_info);
+ if(protocol->write())
+ goto err;
+ my_eof(thd);
+ goto err;
+ }
+ }
+ }
+#endif
+ }
+ DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table));
+
+ if (prepare_func)
+ {
+ DBUG_PRINT("admin", ("calling prepare_func"));
+ switch ((*prepare_func)(thd, table, check_opt)) {
+ case 1: // error, message written to net
+ trans_rollback_stmt(thd);
+ trans_rollback(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ DBUG_PRINT("admin", ("simple error, admin next table"));
+ continue;
+ case -1: // error, message could be written to net
+ /* purecov: begin inspected */
+ DBUG_PRINT("admin", ("severe error, stop"));
+ goto err;
+ /* purecov: end */
+ default: // should be 0 otherwise
+ DBUG_PRINT("admin", ("prepare_func succeeded"));
+ ;
+ }
+ }
+
+ /*
+ CHECK/REPAIR TABLE command is only command where VIEW allowed here and
+ this command use only temporary table method for VIEWs resolving =>
+ there can't be VIEW tree substitition of join view => if opening table
+ succeed then table->table will have real TABLE pointer as value (in
+ case of join view substitution table->table can be 0, but here it is
+ impossible)
+ */
+ if (!table->table)
+ {
+ DBUG_PRINT("admin", ("open table failed"));
+ if (thd->get_stmt_da()->is_warning_info_empty())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE));
+ /* if it was a view will check md5 sum */
+ if (table->view &&
+ view_check(thd, table, check_opt) == HA_ADMIN_WRONG_CHECKSUM)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_VIEW_CHECKSUM, ER(ER_VIEW_CHECKSUM));
+ if (thd->get_stmt_da()->is_error() &&
+ table_not_corrupt_error(thd->get_stmt_da()->sql_errno()))
+ result_code= HA_ADMIN_FAILED;
+ else
+ /* Default failure code is corrupt table */
+ result_code= HA_ADMIN_CORRUPT;
+ goto send_result;
+ }
+
+ if (table->view)
+ {
+ DBUG_PRINT("admin", ("calling view_operator_func"));
+ result_code= (*view_operator_func)(thd, table, check_opt);
+ goto send_result;
+ }
+
+ if (table->schema_table)
+ {
+ result_code= HA_ADMIN_NOT_IMPLEMENTED;
+ goto send_result;
+ }
+
+ if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify)
+ {
+ /* purecov: begin inspected */
+ char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE];
+ size_t length;
+ enum_sql_command save_sql_command= lex->sql_command;
+ DBUG_PRINT("admin", ("sending error message"));
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store(operator_name, system_charset_info);
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+ length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY),
+ table_name);
+ protocol->store(buff, length, system_charset_info);
+ trans_commit_stmt(thd);
+ trans_commit(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ lex->reset_query_tables_list(FALSE);
+ /*
+ Restore Query_tables_list::sql_command value to make statement
+ safe for re-execution.
+ */
+ lex->sql_command= save_sql_command;
+ table->table=0; // For query cache
+ if (protocol->write())
+ goto err;
+ thd->get_stmt_da()->reset_diagnostics_area();
+ continue;
+ /* purecov: end */
+ }
+
+ /*
+ Close all instances of the table to allow MyISAM "repair"
+ to rename files.
+ @todo: This code does not close all instances of the table.
+ It only closes instances in other connections, but if this
+ connection has LOCK TABLE t1 a READ, t1 b WRITE,
+ both t1 instances will be kept open.
+ There is no need to execute this branch for InnoDB, which does
+ repair by recreate. There is no need to do it for OPTIMIZE,
+ which doesn't move files around.
+ Hence, this code should be moved to prepare_for_repair(),
+ and executed only for MyISAM engine.
+ */
+ if (lock_type == TL_WRITE && !table->table->s->tmp_table)
+ {
+ if (wait_while_table_is_used(thd, table->table,
+ HA_EXTRA_PREPARE_FOR_RENAME))
+ goto err;
+ DEBUG_SYNC(thd, "after_admin_flush");
+ /* Flush entries in the query cache involving this table. */
+ query_cache_invalidate3(thd, table->table, 0);
+ /*
+ XXX: hack: switch off open_for_modify to skip the
+ flush that is made later in the execution flow.
+ */
+ open_for_modify= 0;
+ }
+
+ if (table->table->s->crashed && operator_func == &handler::ha_check)
+ {
+ /* purecov: begin inspected */
+ DBUG_PRINT("admin", ("sending crashed warning"));
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store(operator_name, system_charset_info);
+ protocol->store(STRING_WITH_LEN("warning"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("Table is marked as crashed"),
+ system_charset_info);
+ if (protocol->write())
+ goto err;
+ /* purecov: end */
+ }
+
+ if (operator_func == &handler::ha_repair &&
+ !(check_opt->sql_flags & TT_USEFRM))
+ {
+ handler *file= table->table->file;
+ int check_old_types= file->check_old_types();
+ int check_for_upgrade= file->ha_check_for_upgrade(check_opt);
+
+ if (check_old_types == HA_ADMIN_NEEDS_ALTER ||
+ check_for_upgrade == HA_ADMIN_NEEDS_ALTER)
+ {
+ /* We use extra_open_options to be able to open crashed tables */
+ thd->open_options|= extra_open_options;
+ result_code= admin_recreate_table(thd, table);
+ thd->open_options&= ~extra_open_options;
+ goto send_result;
+ }
+ if (check_old_types || check_for_upgrade)
+ {
+ /* If repair is not implemented for the engine, run ALTER TABLE */
+ need_repair_or_alter= 1;
+ }
+ }
+
+ result_code= compl_result_code= HA_ADMIN_OK;
+
+ if (operator_func == &handler::ha_analyze)
+ {
+ TABLE *tab= table->table;
+ Field **field_ptr= tab->field;
+
+ if (lex->with_persistent_for_clause &&
+ tab->s->table_category != TABLE_CATEGORY_USER)
+ {
+ compl_result_code= result_code= HA_ADMIN_INVALID;
+ }
+
+ if (!lex->column_list)
+ {
+ uint fields= 0;
+ for ( ; *field_ptr; field_ptr++, fields++) ;
+ bitmap_set_prefix(tab->read_set, fields);
+ }
+ else
+ {
+ int pos;
+ LEX_STRING *column_name;
+ List_iterator_fast<LEX_STRING> it(*lex->column_list);
+
+ bitmap_clear_all(tab->read_set);
+ while ((column_name= it++))
+ {
+ if (tab->s->fieldnames.type_names == 0 ||
+ (pos= find_type(&tab->s->fieldnames, column_name->str,
+ column_name->length, 1)) <= 0)
+ {
+ compl_result_code= result_code= HA_ADMIN_INVALID;
+ break;
+ }
+ bitmap_set_bit(tab->read_set, pos-1);
+ }
+ tab->file->column_bitmaps_signal();
+ }
+
+ if (!lex->index_list)
+ {
+ tab->keys_in_use_for_query.init(tab->s->keys);
+ }
+ else
+ {
+ int pos;
+ LEX_STRING *index_name;
+ List_iterator_fast<LEX_STRING> it(*lex->index_list);
+
+ tab->keys_in_use_for_query.clear_all();
+ while ((index_name= it++))
+ {
+ if (tab->s->keynames.type_names == 0 ||
+ (pos= find_type(&tab->s->keynames, index_name->str,
+ index_name->length, 1)) <= 0)
+ {
+ compl_result_code= result_code= HA_ADMIN_INVALID;
+ break;
+ }
+ tab->keys_in_use_for_query.set_bit(--pos);
+ }
+ }
+ }
+
+ if (result_code == HA_ADMIN_OK)
+ {
+ DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name));
+ THD_STAGE_INFO(thd, stage_executing);
+ result_code = (table->table->file->*operator_func)(thd, check_opt);
+ THD_STAGE_INFO(thd, stage_sending_data);
+ DBUG_PRINT("admin", ("operator_func returned: %d", result_code));
+ }
+
+ if (compl_result_code == HA_ADMIN_OK &&
+ operator_func == &handler::ha_analyze &&
+ table->table->s->table_category == TABLE_CATEGORY_USER &&
+ (get_use_stat_tables_mode(thd) > NEVER ||
+ lex->with_persistent_for_clause))
+ {
+ if (!(compl_result_code=
+ alloc_statistics_for_table(thd, table->table)) &&
+ !(compl_result_code=
+ collect_statistics_for_table(thd, table->table)))
+ compl_result_code= update_statistics_for_table(thd, table->table);
+ if (compl_result_code)
+ result_code= HA_ADMIN_FAILED;
+ else
+ {
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store(operator_name, system_charset_info);
+ protocol->store(STRING_WITH_LEN("status"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("Engine-independent statistics collected"),
+ system_charset_info);
+ if (protocol->write())
+ goto err;
+ }
+ }
+
+ if (result_code == HA_ADMIN_NOT_IMPLEMENTED && need_repair_or_alter)
+ {
+ /*
+ repair was not implemented and we need to upgrade the table
+ to a new version so we recreate the table with ALTER TABLE
+ */
+ result_code= admin_recreate_table(thd, table);
+ }
+send_result:
+
+ lex->cleanup_after_one_table_open();
+ thd->clear_error(); // these errors shouldn't get client
+ {
+ Diagnostics_area::Sql_condition_iterator it=
+ thd->get_stmt_da()->sql_conditions();
+ const Sql_condition *err;
+ while ((err= it++))
+ {
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store((char*) operator_name, system_charset_info);
+ protocol->store(warning_level_names[err->get_level()].str,
+ warning_level_names[err->get_level()].length,
+ system_charset_info);
+ protocol->store(err->get_message_text(), system_charset_info);
+ if (protocol->write())
+ goto err;
+ }
+ thd->get_stmt_da()->clear_warning_info(thd->query_id);
+ }
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store(operator_name, system_charset_info);
+
+send_result_message:
+
+ DBUG_PRINT("info", ("result_code: %d", result_code));
+ switch (result_code) {
+ case HA_ADMIN_NOT_IMPLEMENTED:
+ {
+ char buf[MYSQL_ERRMSG_SIZE];
+ size_t length=my_snprintf(buf, sizeof(buf),
+ ER(ER_CHECK_NOT_IMPLEMENTED), operator_name);
+ protocol->store(STRING_WITH_LEN("note"), system_charset_info);
+ protocol->store(buf, length, system_charset_info);
+ }
+ break;
+
+ case HA_ADMIN_NOT_BASE_TABLE:
+ {
+ char buf[MYSQL_ERRMSG_SIZE];
+ size_t length= my_snprintf(buf, sizeof(buf),
+ ER(ER_BAD_TABLE_ERROR), table_name);
+ protocol->store(STRING_WITH_LEN("note"), system_charset_info);
+ protocol->store(buf, length, system_charset_info);
+ }
+ break;
+
+ case HA_ADMIN_OK:
+ protocol->store(STRING_WITH_LEN("status"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("OK"), system_charset_info);
+ break;
+
+ case HA_ADMIN_FAILED:
+ protocol->store(STRING_WITH_LEN("status"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("Operation failed"),
+ system_charset_info);
+ break;
+
+ case HA_ADMIN_REJECT:
+ protocol->store(STRING_WITH_LEN("status"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("Operation need committed state"),
+ system_charset_info);
+ open_for_modify= FALSE;
+ break;
+
+ case HA_ADMIN_ALREADY_DONE:
+ protocol->store(STRING_WITH_LEN("status"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("Table is already up to date"),
+ system_charset_info);
+ break;
+
+ case HA_ADMIN_CORRUPT:
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("Corrupt"), system_charset_info);
+ fatal_error=1;
+ break;
+
+ case HA_ADMIN_INVALID:
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+ protocol->store(STRING_WITH_LEN("Invalid argument"),
+ system_charset_info);
+ break;
+
+ case HA_ADMIN_TRY_ALTER:
+ {
+ Alter_info *alter_info= &lex->alter_info;
+
+ protocol->store(STRING_WITH_LEN("note"), system_charset_info);
+ if (alter_info->flags & Alter_info::ALTER_ADMIN_PARTITION)
+ {
+ protocol->store(STRING_WITH_LEN(
+ "Table does not support optimize on partitions. All partitions "
+ "will be rebuilt and analyzed."),system_charset_info);
+ }
+ else
+ {
+ protocol->store(STRING_WITH_LEN(
+ "Table does not support optimize, doing recreate + analyze instead"),
+ system_charset_info);
+ }
+ if (protocol->write())
+ goto err;
+ THD_STAGE_INFO(thd, stage_recreating_table);
+ DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze..."));
+ TABLE_LIST *save_next_local= table->next_local,
+ *save_next_global= table->next_global;
+ table->next_local= table->next_global= 0;
+
+ tmp_disable_binlog(thd); // binlogging is done by caller if wanted
+ result_code= admin_recreate_table(thd, table);
+ reenable_binlog(thd);
+ trans_commit_stmt(thd);
+ trans_commit(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ /* Clear references to TABLE and MDL_ticket after releasing them. */
+ table->mdl_request.ticket= NULL;
+
+ if (!result_code) // recreation went ok
+ {
+ /* Clear the ticket released above. */
+ table->mdl_request.ticket= NULL;
+ DEBUG_SYNC(thd, "ha_admin_open_ltable");
+ table->mdl_request.set_type(MDL_SHARED_WRITE);
+ if (!open_temporary_tables(thd, table) &&
+ (table->table= open_ltable(thd, table, lock_type, 0)))
+ {
+ uint save_flags;
+ /* Store the original value of alter_info->flags */
+ save_flags= alter_info->flags;
+
+ /*
+ Reset the ALTER_ADMIN_PARTITION bit in alter_info->flags
+ to force analyze on all partitions.
+ */
+ alter_info->flags &= ~(Alter_info::ALTER_ADMIN_PARTITION);
+ result_code= table->table->file->ha_analyze(thd, check_opt);
+ if (result_code == HA_ADMIN_ALREADY_DONE)
+ result_code= HA_ADMIN_OK;
+ else if (result_code) // analyze failed
+ table->table->file->print_error(result_code, MYF(0));
+ alter_info->flags= save_flags;
+ }
+ else
+ result_code= -1; // open failed
+ }
+ /* Start a new row for the final status row */
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store(operator_name, system_charset_info);
+ if (result_code) // either mysql_recreate_table or analyze failed
+ {
+ DBUG_ASSERT(thd->is_error());
+ if (thd->is_error())
+ {
+ const char *err_msg= thd->get_stmt_da()->message();
+ if (!thd->vio_ok())
+ {
+ sql_print_error("%s", err_msg);
+ }
+ else
+ {
+ /* Hijack the row already in-progress. */
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+ protocol->store(err_msg, system_charset_info);
+ if (protocol->write())
+ goto err;
+ /* Start off another row for HA_ADMIN_FAILED */
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+ protocol->store(operator_name, system_charset_info);
+ }
+ thd->clear_error();
+ }
+ /* Make sure this table instance is not reused after the operation. */
+ if (table->table)
+ table->table->m_needs_reopen= true;
+ }
+ result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK;
+ table->next_local= save_next_local;
+ table->next_global= save_next_global;
+ goto send_result_message;
+ }
+ case HA_ADMIN_WRONG_CHECKSUM:
+ {
+ protocol->store(STRING_WITH_LEN("note"), system_charset_info);
+ protocol->store(ER(ER_VIEW_CHECKSUM), strlen(ER(ER_VIEW_CHECKSUM)),
+ system_charset_info);
+ break;
+ }
+
+ case HA_ADMIN_NEEDS_UPGRADE:
+ case HA_ADMIN_NEEDS_ALTER:
+ {
+ char buf[MYSQL_ERRMSG_SIZE];
+ size_t length;
+
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+#if MYSQL_VERSION_ID > 100104
+#error fix the error message to take TABLE or VIEW as an argument
+#else
+ if (table->view)
+ length= my_snprintf(buf, sizeof(buf),
+ "Upgrade required. Please do \"REPAIR VIEW %`s\" or dump/reload to fix it!",
+ table->table_name);
+ else
+#endif
+ if (table->table->file->ha_table_flags() & HA_CAN_REPAIR || table->view)
+ length= my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_UPGRADE),
+ table->table_name);
+ else
+ length= my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_REBUILD),
+ table->table_name);
+ protocol->store(buf, length, system_charset_info);
+ fatal_error=1;
+ break;
+ }
+
+ default: // Probably HA_ADMIN_INTERNAL_ERROR
+ {
+ char buf[MYSQL_ERRMSG_SIZE];
+ size_t length=my_snprintf(buf, sizeof(buf),
+ "Unknown - internal error %d during operation",
+ result_code);
+ protocol->store(STRING_WITH_LEN("error"), system_charset_info);
+ protocol->store(buf, length, system_charset_info);
+ fatal_error=1;
+ break;
+ }
+ }
+ if (table->table)
+ {
+ if (table->table->s->tmp_table)
+ {
+ /*
+ If the table was not opened successfully, do not try to get
+ status information. (Bug#47633)
+ */
+ if (open_for_modify && !open_error)
+ table->table->file->info(HA_STATUS_CONST);
+ }
+ else if (open_for_modify || fatal_error)
+ {
+ tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED,
+ table->db, table->table_name, FALSE);
+ /*
+ May be something modified. Consequently, we have to
+ invalidate the query cache.
+ */
+ table->table= 0; // For query cache
+ query_cache_invalidate3(thd, table, 0);
+ }
+ }
+ /* Error path, a admin command failed. */
+ if (thd->transaction_rollback_request)
+ {
+ /*
+ Unlikely, but transaction rollback was requested by one of storage
+ engines (e.g. due to deadlock). Perform it.
+ */
+ if (trans_rollback_stmt(thd) || trans_rollback_implicit(thd))
+ goto err;
+ }
+ else
+ {
+ if (trans_commit_stmt(thd) || trans_commit_implicit(thd))
+ goto err;
+ }
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+
+ /*
+ If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run
+ separate open_tables() for each CHECK TABLE argument.
+ Right now we do not have a separate method to reset the prelocking
+ state in the lex to the state after parsing, so each open will pollute
+ this state: add elements to lex->srotuines_list, TABLE_LISTs to
+ lex->query_tables. Below is a lame attempt to recover from this
+ pollution.
+ @todo: have a method to reset a prelocking context, or use separate
+ contexts for each open.
+ */
+ for (Sroutine_hash_entry *rt=
+ (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
+ rt; rt= rt->next)
+ rt->mdl_request.ticket= NULL;
+
+ if (protocol->write())
+ goto err;
+ }
+
+ my_eof(thd);
+ thd->resume_subsequent_commits(suspended_wfc);
+ DBUG_EXECUTE_IF("inject_analyze_table_sleep", my_sleep(500000););
+ DBUG_RETURN(FALSE);
+
+err:
+ /* Make sure this table instance is not reused after the failure. */
+ trans_rollback_stmt(thd);
+ trans_rollback(thd);
+ if (table && table->table)
+ {
+ table->table->m_needs_reopen= true;
+ table->table= 0;
+ }
+ close_thread_tables(thd); // Shouldn't be needed
+ thd->mdl_context.release_transactional_locks();
+err2:
+ thd->resume_subsequent_commits(suspended_wfc);
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Assigned specified indexes for a table into key cache
+
+ SYNOPSIS
+ mysql_assign_to_keycache()
+ thd Thread object
+ tables Table list (one table only)
+
+ RETURN VALUES
+ FALSE ok
+ TRUE error
+*/
+
+bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables,
+ LEX_STRING *key_cache_name)
+{
+ HA_CHECK_OPT check_opt;
+ KEY_CACHE *key_cache;
+ DBUG_ENTER("mysql_assign_to_keycache");
+
+ THD_STAGE_INFO(thd, stage_finding_key_cache);
+ check_opt.init();
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ if (!(key_cache= get_key_cache(key_cache_name)))
+ {
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str);
+ DBUG_RETURN(TRUE);
+ }
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ if (!key_cache->key_cache_inited)
+ {
+ my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str);
+ DBUG_RETURN(true);
+ }
+ check_opt.key_cache= key_cache;
+ DBUG_RETURN(mysql_admin_table(thd, tables, &check_opt,
+ "assign_to_keycache", TL_READ_NO_INSERT, 0, 0,
+ 0, 0, &handler::assign_to_keycache, 0));
+}
+
+
+/*
+ Preload specified indexes for a table into key cache
+
+ SYNOPSIS
+ mysql_preload_keys()
+ thd Thread object
+ tables Table list (one table only)
+
+ RETURN VALUES
+ FALSE ok
+ TRUE error
+*/
+
+bool mysql_preload_keys(THD* thd, TABLE_LIST* tables)
+{
+ DBUG_ENTER("mysql_preload_keys");
+ /*
+ We cannot allow concurrent inserts. The storage engine reads
+ directly from the index file, bypassing the cache. It could read
+ outdated information if parallel inserts into cache blocks happen.
+ */
+ DBUG_RETURN(mysql_admin_table(thd, tables, 0,
+ "preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0,
+ &handler::preload_keys, 0));
+}
+
+
+bool Sql_cmd_analyze_table::execute(THD *thd)
+{
+ LEX *m_lex= thd->lex;
+ TABLE_LIST *first_table= m_lex->select_lex.table_list.first;
+ bool res= TRUE;
+ thr_lock_type lock_type = TL_READ_NO_INSERT;
+ DBUG_ENTER("Sql_cmd_analyze_table::execute");
+
+ if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table,
+ FALSE, UINT_MAX, FALSE))
+ goto error;
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+ res= mysql_admin_table(thd, first_table, &m_lex->check_opt,
+ "analyze", lock_type, 1, 0, 0, 0,
+ &handler::ha_analyze, 0);
+ /* ! we write after unlocking the table */
+ if (!res && !m_lex->no_write_to_binlog)
+ {
+ /*
+ Presumably, ANALYZE and binlog writing doesn't require synchronization
+ */
+ res= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ }
+ m_lex->select_lex.table_list.first= first_table;
+ m_lex->query_tables= first_table;
+
+error:
+ DBUG_RETURN(res);
+}
+
+
+bool Sql_cmd_check_table::execute(THD *thd)
+{
+ LEX *m_lex= thd->lex;
+ TABLE_LIST *first_table= m_lex->select_lex.table_list.first;
+ thr_lock_type lock_type = TL_READ_NO_INSERT;
+ bool res= TRUE;
+ DBUG_ENTER("Sql_cmd_check_table::execute");
+
+ if (check_table_access(thd, SELECT_ACL, first_table,
+ TRUE, UINT_MAX, FALSE))
+ goto error; /* purecov: inspected */
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+
+ res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "check",
+ lock_type, 0, 0, HA_OPEN_FOR_REPAIR, 0,
+ &handler::ha_check, &view_check);
+
+ m_lex->select_lex.table_list.first= first_table;
+ m_lex->query_tables= first_table;
+
+error:
+ DBUG_RETURN(res);
+}
+
+
+bool Sql_cmd_optimize_table::execute(THD *thd)
+{
+ LEX *m_lex= thd->lex;
+ TABLE_LIST *first_table= m_lex->select_lex.table_list.first;
+ bool res= TRUE;
+ DBUG_ENTER("Sql_cmd_optimize_table::execute");
+
+ if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table,
+ FALSE, UINT_MAX, FALSE))
+ goto error; /* purecov: inspected */
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+ res= (specialflag & SPECIAL_NO_NEW_FUNC) ?
+ mysql_recreate_table(thd, first_table, true) :
+ mysql_admin_table(thd, first_table, &m_lex->check_opt,
+ "optimize", TL_WRITE, 1, 0, 0, 0,
+ &handler::ha_optimize, 0);
+ /* ! we write after unlocking the table */
+ if (!res && !m_lex->no_write_to_binlog)
+ {
+ /*
+ Presumably, OPTIMIZE and binlog writing doesn't require synchronization
+ */
+ res= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ }
+ m_lex->select_lex.table_list.first= first_table;
+ m_lex->query_tables= first_table;
+
+error:
+ DBUG_RETURN(res);
+}
+
+
+bool Sql_cmd_repair_table::execute(THD *thd)
+{
+ LEX *m_lex= thd->lex;
+ TABLE_LIST *first_table= m_lex->select_lex.table_list.first;
+ bool res= TRUE;
+ DBUG_ENTER("Sql_cmd_repair_table::execute");
+
+ if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table,
+ FALSE, UINT_MAX, FALSE))
+ goto error; /* purecov: inspected */
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+ res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "repair",
+ TL_WRITE, 1,
+ MY_TEST(m_lex->check_opt.sql_flags & TT_USEFRM),
+ HA_OPEN_FOR_REPAIR, &prepare_for_repair,
+ &handler::ha_repair, &view_repair);
+
+ /* ! we write after unlocking the table */
+ if (!res && !m_lex->no_write_to_binlog)
+ {
+ /*
+ Presumably, REPAIR and binlog writing doesn't require synchronization
+ */
+ res= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ }
+ m_lex->select_lex.table_list.first= first_table;
+ m_lex->query_tables= first_table;
+
+error:
+ DBUG_RETURN(res);
+}
diff --git a/sql/sql_admin.h b/sql/sql_admin.h
new file mode 100644
index 00000000000..77fc41e2ec4
--- /dev/null
+++ b/sql/sql_admin.h
@@ -0,0 +1,125 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_TABLE_MAINTENANCE_H
+#define SQL_TABLE_MAINTENANCE_H
+
+/* Must be able to hold ALTER TABLE t PARTITION BY ... KEY ALGORITHM = 1 ... */
+#define SQL_ADMIN_MSG_TEXT_SIZE 128 * 1024
+
+bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list,
+ LEX_STRING *key_cache_name);
+bool mysql_preload_keys(THD* thd, TABLE_LIST* table_list);
+int reassign_keycache_tables(THD* thd, KEY_CACHE *src_cache,
+ KEY_CACHE *dst_cache);
+
+/**
+ Sql_cmd_analyze_table represents the ANALYZE TABLE statement.
+*/
+class Sql_cmd_analyze_table : public Sql_cmd
+{
+public:
+ /**
+ Constructor, used to represent a ANALYZE TABLE statement.
+ */
+ Sql_cmd_analyze_table()
+ {}
+
+ ~Sql_cmd_analyze_table()
+ {}
+
+ bool execute(THD *thd);
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ANALYZE;
+ }
+};
+
+
+
+/**
+ Sql_cmd_check_table represents the CHECK TABLE statement.
+*/
+class Sql_cmd_check_table : public Sql_cmd
+{
+public:
+ /**
+ Constructor, used to represent a CHECK TABLE statement.
+ */
+ Sql_cmd_check_table()
+ {}
+
+ ~Sql_cmd_check_table()
+ {}
+
+ bool execute(THD *thd);
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_CHECK;
+ }
+};
+
+
+/**
+ Sql_cmd_optimize_table represents the OPTIMIZE TABLE statement.
+*/
+class Sql_cmd_optimize_table : public Sql_cmd
+{
+public:
+ /**
+ Constructor, used to represent a OPTIMIZE TABLE statement.
+ */
+ Sql_cmd_optimize_table()
+ {}
+
+ ~Sql_cmd_optimize_table()
+ {}
+
+ bool execute(THD *thd);
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_OPTIMIZE;
+ }
+};
+
+
+
+/**
+ Sql_cmd_repair_table represents the REPAIR TABLE statement.
+*/
+class Sql_cmd_repair_table : public Sql_cmd
+{
+public:
+ /**
+ Constructor, used to represent a REPAIR TABLE statement.
+ */
+ Sql_cmd_repair_table()
+ {}
+
+ ~Sql_cmd_repair_table()
+ {}
+
+ bool execute(THD *thd);
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_REPAIR;
+ }
+};
+
+#endif
diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc
new file mode 100644
index 00000000000..97b9c127c22
--- /dev/null
+++ b/sql/sql_alter.cc
@@ -0,0 +1,347 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "sql_parse.h" // check_access
+#include "sql_table.h" // mysql_alter_table,
+ // mysql_exchange_partition
+#include "sql_base.h" // open_temporary_tables
+#include "sql_alter.h"
+
+Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root)
+ :drop_list(rhs.drop_list, mem_root),
+ alter_list(rhs.alter_list, mem_root),
+ key_list(rhs.key_list, mem_root),
+ create_list(rhs.create_list, mem_root),
+ flags(rhs.flags),
+ keys_onoff(rhs.keys_onoff),
+ partition_names(rhs.partition_names, mem_root),
+ num_parts(rhs.num_parts),
+ requested_algorithm(rhs.requested_algorithm),
+ requested_lock(rhs.requested_lock)
+{
+ /*
+ Make deep copies of used objects.
+ This is not a fully deep copy - clone() implementations
+ of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec
+ do not copy string constants. At the same length the only
+ reason we make a copy currently is that ALTER/CREATE TABLE
+ code changes input Alter_info definitions, but string
+ constants never change.
+ */
+ list_copy_and_replace_each_value(drop_list, mem_root);
+ list_copy_and_replace_each_value(alter_list, mem_root);
+ list_copy_and_replace_each_value(key_list, mem_root);
+ list_copy_and_replace_each_value(create_list, mem_root);
+ /* partition_names are not deeply copied currently */
+}
+
+
+bool Alter_info::set_requested_algorithm(const LEX_STRING *str)
+{
+ // To avoid adding new keywords to the grammar, we match strings here.
+ if (!my_strcasecmp(system_charset_info, str->str, "INPLACE"))
+ requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE;
+ else if (!my_strcasecmp(system_charset_info, str->str, "COPY"))
+ requested_algorithm= ALTER_TABLE_ALGORITHM_COPY;
+ else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
+ requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT;
+ else
+ return true;
+ return false;
+}
+
+
+bool Alter_info::set_requested_lock(const LEX_STRING *str)
+{
+ // To avoid adding new keywords to the grammar, we match strings here.
+ if (!my_strcasecmp(system_charset_info, str->str, "NONE"))
+ requested_lock= ALTER_TABLE_LOCK_NONE;
+ else if (!my_strcasecmp(system_charset_info, str->str, "SHARED"))
+ requested_lock= ALTER_TABLE_LOCK_SHARED;
+ else if (!my_strcasecmp(system_charset_info, str->str, "EXCLUSIVE"))
+ requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE;
+ else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
+ requested_lock= ALTER_TABLE_LOCK_DEFAULT;
+ else
+ return true;
+ return false;
+}
+
+
+Alter_table_ctx::Alter_table_ctx()
+ : datetime_field(NULL), error_if_not_empty(false),
+ tables_opened(0),
+ db(NULL), table_name(NULL), alias(NULL),
+ new_db(NULL), new_name(NULL), new_alias(NULL),
+ fk_error_if_delete_row(false), fk_error_id(NULL),
+ fk_error_table(NULL)
+#ifndef DBUG_OFF
+ , tmp_table(false)
+#endif
+{
+}
+
+
+Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list,
+ uint tables_opened_arg,
+ char *new_db_arg, char *new_name_arg)
+ : datetime_field(NULL), error_if_not_empty(false),
+ tables_opened(tables_opened_arg),
+ new_db(new_db_arg), new_name(new_name_arg),
+ fk_error_if_delete_row(false), fk_error_id(NULL),
+ fk_error_table(NULL)
+#ifndef DBUG_OFF
+ , tmp_table(false)
+#endif
+{
+ /*
+ Assign members db, table_name, new_db and new_name
+ to simplify further comparisions: we want to see if it's a RENAME
+ later just by comparing the pointers, avoiding the need for strcmp.
+ */
+ db= table_list->db;
+ table_name= table_list->table_name;
+ alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
+
+ if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
+ new_db= db;
+
+ if (new_name)
+ {
+ DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name));
+
+ if (lower_case_table_names == 1) // Convert new_name/new_alias to lower case
+ {
+ my_casedn_str(files_charset_info, new_name);
+ new_alias= new_name;
+ }
+ else if (lower_case_table_names == 2) // Convert new_name to lower case
+ {
+ strmov(new_alias= new_alias_buff, new_name);
+ my_casedn_str(files_charset_info, new_name);
+ }
+ else
+ new_alias= new_name; // LCTN=0 => case sensitive + case preserving
+
+ if (!is_database_changed() &&
+ !my_strcasecmp(table_alias_charset, new_name, table_name))
+ {
+ /*
+ Source and destination table names are equal:
+ make is_table_renamed() more efficient.
+ */
+ new_alias= table_name;
+ new_name= table_name;
+ }
+ }
+ else
+ {
+ new_alias= alias;
+ new_name= table_name;
+ }
+
+ my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix,
+ current_pid, thd->thread_id);
+ /* Safety fix for InnoDB */
+ if (lower_case_table_names)
+ my_casedn_str(files_charset_info, tmp_name);
+
+ if (table_list->table->s->tmp_table == NO_TMP_TABLE)
+ {
+ build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
+
+ build_table_filename(new_path, sizeof(new_path) - 1, new_db, new_name, "", 0);
+
+ build_table_filename(new_filename, sizeof(new_filename) - 1,
+ new_db, new_name, reg_ext, 0);
+
+ build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db, tmp_name, "",
+ FN_IS_TMP);
+ }
+ else
+ {
+ /*
+ We are not filling path, new_path and new_filename members if
+ we are altering temporary table as these members are not used in
+ this case. This fact is enforced with assert.
+ */
+ build_tmptable_filename(thd, tmp_path, sizeof(tmp_path));
+#ifndef DBUG_OFF
+ tmp_table= true;
+#endif
+ }
+}
+
+
+bool Sql_cmd_alter_table::execute(THD *thd)
+{
+ LEX *lex= thd->lex;
+ /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
+ SELECT_LEX *select_lex= &lex->select_lex;
+ /* first table of first SELECT_LEX */
+ TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first;
+ /*
+ Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
+ so we have to use a copy of this structure to make execution
+ prepared statement- safe. A shallow copy is enough as no memory
+ referenced from this structure will be modified.
+ @todo move these into constructor...
+ */
+ HA_CREATE_INFO create_info(lex->create_info);
+ Alter_info alter_info(lex->alter_info, thd->mem_root);
+ ulong priv=0;
+ ulong priv_needed= ALTER_ACL;
+ bool result;
+
+ DBUG_ENTER("Sql_cmd_alter_table::execute");
+
+ if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
+ DBUG_RETURN(TRUE);
+ /*
+ We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well
+ as for RENAME TO, as being done by SQLCOM_RENAME_TABLE
+ */
+ if (alter_info.flags & (Alter_info::ALTER_DROP_PARTITION |
+ Alter_info::ALTER_RENAME))
+ priv_needed|= DROP_ACL;
+
+ /* Must be set in the parser */
+ DBUG_ASSERT(select_lex->db);
+ DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION));
+ DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION));
+ if (check_access(thd, priv_needed, first_table->db,
+ &first_table->grant.privilege,
+ &first_table->grant.m_internal,
+ 0, 0) ||
+ check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db,
+ &priv,
+ NULL, /* Don't use first_tab->grant with sel_lex->db */
+ 0, 0))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ /* If it is a merge table, check privileges for merge children. */
+ if (create_info.merge_list.first)
+ {
+ /*
+ The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
+ underlying base tables, even if there are temporary tables with the same
+ names.
+
+ From user's point of view, it might look as if the user must have these
+ privileges on temporary tables to create a merge table over them. This is
+ one of two cases when a set of privileges is required for operations on
+ temporary tables (see also CREATE TABLE).
+
+ The reason for this behavior stems from the following facts:
+
+ - For merge tables, the underlying table privileges are checked only
+ at CREATE TABLE / ALTER TABLE time.
+
+ In other words, once a merge table is created, the privileges of
+ the underlying tables can be revoked, but the user will still have
+ access to the merge table (provided that the user has privileges on
+ the merge table itself).
+
+ - Temporary tables shadow base tables.
+
+ I.e. there might be temporary and base tables with the same name, and
+ the temporary table takes the precedence in all operations.
+
+ - For temporary MERGE tables we do not track if their child tables are
+ base or temporary. As result we can't guarantee that privilege check
+ which was done in presence of temporary child will stay relevant later
+ as this temporary table might be removed.
+
+ If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
+ the underlying *base* tables, it would create a security breach as in
+ Bug#12771903.
+ */
+
+ if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
+ create_info.merge_list.first, FALSE, UINT_MAX, FALSE))
+ DBUG_RETURN(TRUE);
+ }
+
+ if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL))
+ {
+ // Rename of table
+ TABLE_LIST tmp_table;
+ memset(&tmp_table, 0, sizeof(tmp_table));
+ tmp_table.table_name= lex->name.str;
+ tmp_table.db= select_lex->db;
+ tmp_table.grant.privilege= priv;
+ if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE,
+ UINT_MAX, FALSE))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ }
+
+ /* Don't yet allow changing of symlinks with ALTER TABLE */
+ if (create_info.data_file_name)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
+ "DATA DIRECTORY");
+ if (create_info.index_file_name)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
+ "INDEX DIRECTORY");
+ create_info.data_file_name= create_info.index_file_name= NULL;
+
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+
+ result= mysql_alter_table(thd, select_lex->db, lex->name.str,
+ &create_info,
+ first_table,
+ &alter_info,
+ select_lex->order_list.elements,
+ select_lex->order_list.first,
+ lex->ignore);
+
+ DBUG_RETURN(result);
+}
+
+bool Sql_cmd_discard_import_tablespace::execute(THD *thd)
+{
+ /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ /* first table of first SELECT_LEX */
+ TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first;
+
+ if (check_access(thd, ALTER_ACL, table_list->db,
+ &table_list->grant.privilege,
+ &table_list->grant.m_internal,
+ 0, 0))
+ return true;
+
+ if (check_grant(thd, ALTER_ACL, table_list, false, UINT_MAX, false))
+ return true;
+
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+
+ /*
+ Check if we attempt to alter mysql.slow_log or
+ mysql.general_log table and return an error if
+ it is the case.
+ TODO: this design is obsolete and will be removed.
+ */
+ if (check_if_log_table(table_list, TRUE, "ALTER"))
+ return true;
+
+ return
+ mysql_discard_or_import_tablespace(thd, table_list,
+ m_tablespace_op == DISCARD_TABLESPACE);
+}
diff --git a/sql/sql_alter.h b/sql/sql_alter.h
new file mode 100644
index 00000000000..526442e83e2
--- /dev/null
+++ b/sql/sql_alter.h
@@ -0,0 +1,429 @@
+/* Copyright (c) 2010, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2013, 2014, Monty Program Ab.
+
+ 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 */
+
+#ifndef SQL_ALTER_TABLE_H
+#define SQL_ALTER_TABLE_H
+
+class Alter_drop;
+class Alter_column;
+class Key;
+
+/**
+ Data describing the table being created by CREATE TABLE or
+ altered by ALTER TABLE.
+*/
+
+class Alter_info
+{
+public:
+ /*
+ These flags are set by the parser and describes the type of
+ operation(s) specified by the ALTER TABLE statement.
+
+ They do *not* describe the type operation(s) to be executed
+ by the storage engine. For example, we don't yet know the
+ type of index to be added/dropped.
+ */
+
+ // Set for ADD [COLUMN]
+ static const uint ALTER_ADD_COLUMN = 1L << 0;
+
+ // Set for DROP [COLUMN]
+ static const uint ALTER_DROP_COLUMN = 1L << 1;
+
+ // Set for CHANGE [COLUMN] | MODIFY [CHANGE]
+ // Set by mysql_recreate_table()
+ static const uint ALTER_CHANGE_COLUMN = 1L << 2;
+
+ // Set for ADD INDEX | ADD KEY | ADD PRIMARY KEY | ADD UNIQUE KEY |
+ // ADD UNIQUE INDEX | ALTER ADD [COLUMN]
+ static const uint ALTER_ADD_INDEX = 1L << 3;
+
+ // Set for DROP PRIMARY KEY | DROP FOREIGN KEY | DROP KEY | DROP INDEX
+ static const uint ALTER_DROP_INDEX = 1L << 4;
+
+ // Set for RENAME [TO]
+ static const uint ALTER_RENAME = 1L << 5;
+
+ // Set for ORDER BY
+ static const uint ALTER_ORDER = 1L << 6;
+
+ // Set for table_options
+ static const uint ALTER_OPTIONS = 1L << 7;
+
+ // Set for ALTER [COLUMN] ... SET DEFAULT ... | DROP DEFAULT
+ static const uint ALTER_CHANGE_COLUMN_DEFAULT = 1L << 8;
+
+ // Set for DISABLE KEYS | ENABLE KEYS
+ static const uint ALTER_KEYS_ONOFF = 1L << 9;
+
+ // Set for CONVERT TO CHARACTER SET
+ static const uint ALTER_CONVERT = 1L << 10;
+
+ // Set for FORCE
+ // Set for ENGINE(same engine)
+ // Set by mysql_recreate_table()
+ static const uint ALTER_RECREATE = 1L << 11;
+
+ // Set for ADD PARTITION
+ static const uint ALTER_ADD_PARTITION = 1L << 12;
+
+ // Set for DROP PARTITION
+ static const uint ALTER_DROP_PARTITION = 1L << 13;
+
+ // Set for COALESCE PARTITION
+ static const uint ALTER_COALESCE_PARTITION = 1L << 14;
+
+ // Set for REORGANIZE PARTITION ... INTO
+ static const uint ALTER_REORGANIZE_PARTITION = 1L << 15;
+
+ // Set for partition_options
+ static const uint ALTER_PARTITION = 1L << 16;
+
+ // Set for LOAD INDEX INTO CACHE ... PARTITION
+ // Set for CACHE INDEX ... PARTITION
+ static const uint ALTER_ADMIN_PARTITION = 1L << 17;
+
+ // Set for REORGANIZE PARTITION
+ static const uint ALTER_TABLE_REORG = 1L << 18;
+
+ // Set for REBUILD PARTITION
+ static const uint ALTER_REBUILD_PARTITION = 1L << 19;
+
+ // Set for partitioning operations specifying ALL keyword
+ static const uint ALTER_ALL_PARTITION = 1L << 20;
+
+ // Set for REMOVE PARTITIONING
+ static const uint ALTER_REMOVE_PARTITIONING = 1L << 21;
+
+ // Set for ADD FOREIGN KEY
+ static const uint ADD_FOREIGN_KEY = 1L << 22;
+
+ // Set for DROP FOREIGN KEY
+ static const uint DROP_FOREIGN_KEY = 1L << 23;
+
+ // Set for EXCHANGE PARITION
+ static const uint ALTER_EXCHANGE_PARTITION = 1L << 24;
+
+ // Set by Sql_cmd_alter_table_truncate_partition::execute()
+ static const uint ALTER_TRUNCATE_PARTITION = 1L << 25;
+
+ // Set for ADD [COLUMN] FIRST | AFTER
+ static const uint ALTER_COLUMN_ORDER = 1L << 26;
+
+
+ enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE };
+
+ /**
+ The different values of the ALGORITHM clause.
+ Describes which algorithm to use when altering the table.
+ */
+ enum enum_alter_table_algorithm
+ {
+ // In-place if supported, copy otherwise.
+ ALTER_TABLE_ALGORITHM_DEFAULT,
+
+ // In-place if supported, error otherwise.
+ ALTER_TABLE_ALGORITHM_INPLACE,
+
+ // Copy if supported, error otherwise.
+ ALTER_TABLE_ALGORITHM_COPY
+ };
+
+
+ /**
+ The different values of the LOCK clause.
+ Describes the level of concurrency during ALTER TABLE.
+ */
+ enum enum_alter_table_lock
+ {
+ // Maximum supported level of concurency for the given operation.
+ ALTER_TABLE_LOCK_DEFAULT,
+
+ // Allow concurrent reads & writes. If not supported, give erorr.
+ ALTER_TABLE_LOCK_NONE,
+
+ // Allow concurrent reads only. If not supported, give error.
+ ALTER_TABLE_LOCK_SHARED,
+
+ // Block reads and writes.
+ ALTER_TABLE_LOCK_EXCLUSIVE
+ };
+
+
+ // Columns and keys to be dropped.
+ List<Alter_drop> drop_list;
+ // Columns for ALTER_COLUMN_CHANGE_DEFAULT.
+ List<Alter_column> alter_list;
+ // List of keys, used by both CREATE and ALTER TABLE.
+ List<Key> key_list;
+ // List of columns, used by both CREATE and ALTER TABLE.
+ List<Create_field> create_list;
+ // Type of ALTER TABLE operation.
+ uint flags;
+ // Enable or disable keys.
+ enum_enable_or_disable keys_onoff;
+ // List of partitions.
+ List<char> partition_names;
+ // Number of partitions.
+ uint num_parts;
+ // Type of ALTER TABLE algorithm.
+ enum_alter_table_algorithm requested_algorithm;
+ // Type of ALTER TABLE lock.
+ enum_alter_table_lock requested_lock;
+
+
+ Alter_info() :
+ flags(0),
+ keys_onoff(LEAVE_AS_IS),
+ num_parts(0),
+ requested_algorithm(ALTER_TABLE_ALGORITHM_DEFAULT),
+ requested_lock(ALTER_TABLE_LOCK_DEFAULT)
+ {}
+
+ void reset()
+ {
+ drop_list.empty();
+ alter_list.empty();
+ key_list.empty();
+ create_list.empty();
+ flags= 0;
+ keys_onoff= LEAVE_AS_IS;
+ num_parts= 0;
+ partition_names.empty();
+ requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT;
+ requested_lock= ALTER_TABLE_LOCK_DEFAULT;
+ }
+
+
+ /**
+ Construct a copy of this object to be used for mysql_alter_table
+ and mysql_create_table.
+
+ Historically, these two functions modify their Alter_info
+ arguments. This behaviour breaks re-execution of prepared
+ statements and stored procedures and is compensated by always
+ supplying a copy of Alter_info to these functions.
+
+ @param rhs Alter_info to make copy of
+ @param mem_root Mem_root for new Alter_info
+
+ @note You need to use check the error in THD for out
+ of memory condition after calling this function.
+ */
+ Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root);
+
+
+ /**
+ Parses the given string and sets requested_algorithm
+ if the string value matches a supported value.
+ Supported values: INPLACE, COPY, DEFAULT
+
+ @param str String containing the supplied value
+ @retval false Supported value found, state updated
+ @retval true Not supported value, no changes made
+ */
+ bool set_requested_algorithm(const LEX_STRING *str);
+
+
+ /**
+ Parses the given string and sets requested_lock
+ if the string value matches a supported value.
+ Supported values: NONE, SHARED, EXCLUSIVE, DEFAULT
+
+ @param str String containing the supplied value
+ @retval false Supported value found, state updated
+ @retval true Not supported value, no changes made
+ */
+
+ bool set_requested_lock(const LEX_STRING *str);
+
+private:
+ Alter_info &operator=(const Alter_info &rhs); // not implemented
+ Alter_info(const Alter_info &rhs); // not implemented
+};
+
+
+/** Runtime context for ALTER TABLE. */
+class Alter_table_ctx
+{
+public:
+ Alter_table_ctx();
+
+ Alter_table_ctx(THD *thd, TABLE_LIST *table_list, uint tables_opened_arg,
+ char *new_db_arg, char *new_name_arg);
+
+ /**
+ @return true if the table is moved to another database, false otherwise.
+ */
+ bool is_database_changed() const
+ { return (new_db != db); };
+
+ /**
+ @return true if the table is renamed, false otherwise.
+ */
+ bool is_table_renamed() const
+ { return (is_database_changed() || new_name != table_name); };
+
+ /**
+ @return filename (including .frm) for the new table.
+ */
+ const char *get_new_filename() const
+ {
+ DBUG_ASSERT(!tmp_table);
+ return new_filename;
+ }
+
+ /**
+ @return path to the original table.
+ */
+ const char *get_path() const
+ {
+ DBUG_ASSERT(!tmp_table);
+ return path;
+ }
+
+ /**
+ @return path to the new table.
+ */
+ const char *get_new_path() const
+ {
+ DBUG_ASSERT(!tmp_table);
+ return new_path;
+ }
+
+ /**
+ @return path to the temporary table created during ALTER TABLE.
+ */
+ const char *get_tmp_path() const
+ { return tmp_path; }
+
+ /**
+ Mark ALTER TABLE as needing to produce foreign key error if
+ it deletes a row from the table being changed.
+ */
+ void set_fk_error_if_delete_row(FOREIGN_KEY_INFO *fk)
+ {
+ fk_error_if_delete_row= true;
+ fk_error_id= fk->foreign_id->str;
+ fk_error_table= fk->foreign_table->str;
+ }
+
+public:
+ Create_field *datetime_field;
+ bool error_if_not_empty;
+ uint tables_opened;
+ char *db;
+ char *table_name;
+ char *alias;
+ char *new_db;
+ char *new_name;
+ char *new_alias;
+ char tmp_name[80];
+ /**
+ Indicates that if a row is deleted during copying of data from old version
+ of table to the new version ER_FK_CANNOT_DELETE_PARENT error should be
+ emitted.
+ */
+ bool fk_error_if_delete_row;
+ /** Name of foreign key for the above error. */
+ const char *fk_error_id;
+ /** Name of table for the above error. */
+ const char *fk_error_table;
+
+private:
+ char new_filename[FN_REFLEN + 1];
+ char new_alias_buff[FN_REFLEN + 1];
+ char path[FN_REFLEN + 1];
+ char new_path[FN_REFLEN + 1];
+ char tmp_path[FN_REFLEN + 1];
+
+#ifndef DBUG_OFF
+ /** Indicates that we are altering temporary table. Used only in asserts. */
+ bool tmp_table;
+#endif
+
+ Alter_table_ctx &operator=(const Alter_table_ctx &rhs); // not implemented
+ Alter_table_ctx(const Alter_table_ctx &rhs); // not implemented
+};
+
+
+/**
+ Sql_cmd_common_alter_table represents the common properties of the ALTER TABLE
+ statements.
+ @todo move Alter_info and other ALTER generic structures from Lex here.
+*/
+class Sql_cmd_common_alter_table : public Sql_cmd
+{
+protected:
+ /**
+ Constructor.
+ */
+ Sql_cmd_common_alter_table()
+ {}
+
+ virtual ~Sql_cmd_common_alter_table()
+ {}
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ALTER_TABLE;
+ }
+};
+
+/**
+ Sql_cmd_alter_table represents the generic ALTER TABLE statement.
+ @todo move Alter_info and other ALTER specific structures from Lex here.
+*/
+class Sql_cmd_alter_table : public Sql_cmd_common_alter_table
+{
+public:
+ /**
+ Constructor, used to represent a ALTER TABLE statement.
+ */
+ Sql_cmd_alter_table()
+ {}
+
+ ~Sql_cmd_alter_table()
+ {}
+
+ bool execute(THD *thd);
+};
+
+
+/**
+ Sql_cmd_alter_table_tablespace represents ALTER TABLE
+ IMPORT/DISCARD TABLESPACE statements.
+*/
+class Sql_cmd_discard_import_tablespace : public Sql_cmd_common_alter_table
+{
+public:
+ enum enum_tablespace_op_type
+ {
+ DISCARD_TABLESPACE, IMPORT_TABLESPACE
+ };
+
+ Sql_cmd_discard_import_tablespace(enum_tablespace_op_type tablespace_op_arg)
+ : m_tablespace_op(tablespace_op_arg)
+ {}
+
+ bool execute(THD *thd);
+
+private:
+ const enum_tablespace_op_type m_tablespace_op;
+};
+
+#endif
diff --git a/sql/sql_analyse.cc b/sql/sql_analyse.cc
new file mode 100644
index 00000000000..32b447797cf
--- /dev/null
+++ b/sql/sql_analyse.cc
@@ -0,0 +1,1242 @@
+/*
+ Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* Analyse database */
+
+/* TODO: - Check if any character fields can be of any date type
+** (date, datetime, year, time, timestamp, newdate)
+** - Check if any number field should be a timestamp
+** - type set is out of optimization yet
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#define MYSQL_LEX 1
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "procedure.h"
+#include "sql_analyse.h"
+#include <m_ctype.h>
+
+#define MAX_TREEMEM 8192
+#define MAX_TREE_ELEMENTS 256
+
+int sortcmp2(void* cmp_arg __attribute__((unused)),
+ const String *a,const String *b)
+{
+ return sortcmp(a,b,a->charset());
+}
+
+int compare_double2(void* cmp_arg __attribute__((unused)),
+ const double *s, const double *t)
+{
+ return compare_double(s,t);
+}
+
+int compare_longlong2(void* cmp_arg __attribute__((unused)),
+ const longlong *s, const longlong *t)
+{
+ return compare_longlong(s,t);
+}
+
+int compare_ulonglong2(void* cmp_arg __attribute__((unused)),
+ const ulonglong *s, const ulonglong *t)
+{
+ return compare_ulonglong(s,t);
+}
+
+int compare_decimal2(int* len, const char *s, const char *t)
+{
+ return memcmp(s, t, *len);
+}
+
+
+Procedure *
+proc_analyse_init(THD *thd, ORDER *param, select_result *result,
+ List<Item> &field_list)
+{
+ char *proc_name = (*param->item)->name;
+ analyse *pc = new analyse(result);
+ field_info **f_info;
+ DBUG_ENTER("proc_analyse_init");
+
+ if (!pc)
+ DBUG_RETURN(0);
+
+ if (!(param = param->next))
+ {
+ pc->max_tree_elements = MAX_TREE_ELEMENTS;
+ pc->max_treemem = MAX_TREEMEM;
+ }
+ else if (param->next)
+ {
+ // first parameter
+ if (!(*param->item)->fixed && (*param->item)->fix_fields(thd, param->item))
+ {
+ DBUG_PRINT("info", ("fix_fields() for the first parameter failed"));
+ goto err;
+ }
+ if ((*param->item)->type() != Item::INT_ITEM ||
+ (*param->item)->val_real() < 0)
+ {
+ my_error(ER_WRONG_PARAMETERS_TO_PROCEDURE, MYF(0), proc_name);
+ goto err;
+ }
+ pc->max_tree_elements = (uint) (*param->item)->val_int();
+ param = param->next;
+ if (param->next) // no third parameter possible
+ {
+ my_error(ER_WRONG_PARAMCOUNT_TO_PROCEDURE, MYF(0), proc_name);
+ goto err;
+ }
+ // second parameter
+ if (!(*param->item)->fixed && (*param->item)->fix_fields(thd, param->item))
+ {
+ DBUG_PRINT("info", ("fix_fields() for the second parameter failed"));
+ goto err;
+ }
+ if ((*param->item)->type() != Item::INT_ITEM ||
+ (*param->item)->val_real() < 0)
+ {
+ my_error(ER_WRONG_PARAMETERS_TO_PROCEDURE, MYF(0), proc_name);
+ goto err;
+ }
+ pc->max_treemem = (uint) (*param->item)->val_int();
+ }
+ else if ((*param->item)->type() != Item::INT_ITEM ||
+ (*param->item)->val_real() < 0)
+ {
+ my_error(ER_WRONG_PARAMETERS_TO_PROCEDURE, MYF(0), proc_name);
+ goto err;
+ }
+ // if only one parameter was given, it will be the value of max_tree_elements
+ else
+ {
+ pc->max_tree_elements = (uint) (*param->item)->val_int();
+ pc->max_treemem = MAX_TREEMEM;
+ }
+
+ if (!(pc->f_info=
+ (field_info**)sql_alloc(sizeof(field_info*)*field_list.elements)))
+ goto err;
+ pc->f_end = pc->f_info + field_list.elements;
+ pc->fields = field_list;
+
+ {
+ List_iterator_fast<Item> it(pc->fields);
+ f_info = pc->f_info;
+
+ Item *item;
+ while ((item = it++))
+ {
+ field_info *new_field;
+ switch (item->result_type()) {
+ case INT_RESULT:
+ // Check if fieldtype is ulonglong
+ if (item->type() == Item::FIELD_ITEM &&
+ ((Item_field*) item)->field->type() == MYSQL_TYPE_LONGLONG &&
+ ((Field_longlong*) ((Item_field*) item)->field)->unsigned_flag)
+ new_field= new field_ulonglong(item, pc);
+ else
+ new_field= new field_longlong(item, pc);
+ break;
+ case REAL_RESULT:
+ new_field= new field_real(item, pc);
+ break;
+ case DECIMAL_RESULT:
+ new_field= new field_decimal(item, pc);
+ break;
+ case STRING_RESULT:
+ new_field= new field_str(item, pc);
+ break;
+ default:
+ goto err;
+ }
+ *f_info++= new_field;
+ }
+ }
+ DBUG_RETURN(pc);
+err:
+ delete pc;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Return 1 if number, else return 0
+ store info about found number in info
+ NOTE:It is expected, that elements of 'info' are all zero!
+*/
+
+bool test_if_number(NUM_INFO *info, const char *str, uint str_len)
+{
+ const char *begin, *end= str + str_len;
+ DBUG_ENTER("test_if_number");
+
+ /*
+ MySQL removes any endspaces of a string, so we must take care only of
+ spaces in front of a string
+ */
+ for (; str != end && my_isspace(system_charset_info, *str); str++) ;
+ if (str == end)
+ DBUG_RETURN(0);
+
+ if (*str == '-')
+ {
+ info->negative = 1;
+ if (++str == end || *str == '0') // converting -0 to a number
+ DBUG_RETURN(0); // might lose information
+ }
+ else
+ info->negative = 0;
+ begin = str;
+ for (; str != end && my_isdigit(system_charset_info,*str); str++)
+ {
+ if (!info->integers && *str == '0' && (str + 1) != end &&
+ my_isdigit(system_charset_info,*(str + 1)))
+ info->zerofill = 1; // could be a postnumber for example
+ info->integers++;
+ }
+ if (str == end && info->integers)
+ {
+ char *endpos= (char*) end;
+ int error;
+ info->ullval= (ulonglong) my_strtoll10(begin, &endpos, &error);
+ if (info->integers == 1)
+ DBUG_RETURN(0); // single number can't be zerofill
+ info->maybe_zerofill = 1;
+ DBUG_RETURN(1); // a zerofill number, or an integer
+ }
+ if (*str == '.' || *str == 'e' || *str == 'E')
+ {
+ if (info->zerofill) // can't be zerofill anymore
+ DBUG_RETURN(0);
+ if ((str + 1) == end) // number was something like '123[.eE]'
+ {
+ char *endpos= (char*) str;
+ int error;
+ info->ullval= (ulonglong) my_strtoll10(begin, &endpos, &error);
+ DBUG_RETURN(1);
+ }
+ if (*str == 'e' || *str == 'E') // number may be something like '1e+50'
+ {
+ str++;
+ if (*str != '-' && *str != '+')
+ DBUG_RETURN(0);
+ for (str++; str != end && my_isdigit(system_charset_info,*str); str++) ;
+ if (str == end)
+ {
+ info->is_float = 1; // we can't use variable decimals here
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+ }
+ for (str++; *(end - 1) == '0'; end--) // jump over zeros at the end
+ ;
+ if (str == end) // number was something like '123.000'
+ {
+ char *endpos= (char*) str;
+ int error;
+ info->ullval= (ulonglong) my_strtoll10(begin, &endpos, &error);
+ DBUG_RETURN(1);
+ }
+ for (; str != end && my_isdigit(system_charset_info,*str); str++)
+ info->decimals++;
+ if (str == end)
+ {
+ info->dval = my_atof(begin);
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Stores the biggest and the smallest value from current 'info'
+ to ev_num_info
+ If info contains an ulonglong number, which is bigger than
+ biggest positive number able to be stored in a longlong variable
+ and is marked as negative, function will return 0, else 1.
+*/
+
+bool get_ev_num_info(EV_NUM_INFO *ev_info, NUM_INFO *info, const char *num)
+{
+ if (info->negative)
+ {
+ if (((longlong) info->ullval) < 0)
+ return 0; // Impossible to store as a negative number
+ ev_info->llval = -(longlong) MY_MAX((ulonglong) -ev_info->llval,
+ info->ullval);
+ ev_info->min_dval = (double) -MY_MAX(-ev_info->min_dval, info->dval);
+ }
+ else // ulonglong is as big as bigint in MySQL
+ {
+ if ((check_ulonglong(num, info->integers) == DECIMAL_NUM))
+ return 0;
+ ev_info->ullval = (ulonglong) MY_MAX(ev_info->ullval, info->ullval);
+ ev_info->max_dval = (double) MY_MAX(ev_info->max_dval, info->dval);
+ }
+ return 1;
+} // get_ev_num_info
+
+
+void free_string(String *s)
+{
+ s->free();
+}
+
+
+void field_str::add()
+{
+ char buff[MAX_FIELD_WIDTH], *ptr;
+ String s(buff, sizeof(buff),&my_charset_bin), *res;
+ ulong length;
+
+ if (!(res = item->val_str(&s)))
+ {
+ nulls++;
+ return;
+ }
+
+ if (!(length = res->length()))
+ empty++;
+ else
+ {
+ ptr = (char*) res->ptr();
+ if (*(ptr + (length - 1)) == ' ')
+ must_be_blob = 1;
+ }
+
+ if (can_be_still_num)
+ {
+ bzero((char*) &num_info, sizeof(num_info));
+ if (!test_if_number(&num_info, res->ptr(), (uint) length))
+ can_be_still_num = 0;
+ if (!found)
+ {
+ bzero((char*) &ev_num_info, sizeof(ev_num_info));
+ was_zero_fill = num_info.zerofill;
+ }
+ else if (num_info.zerofill != was_zero_fill && !was_maybe_zerofill)
+ can_be_still_num = 0; // one more check needed, when length is counted
+ if (can_be_still_num)
+ can_be_still_num = get_ev_num_info(&ev_num_info, &num_info, res->ptr());
+ was_maybe_zerofill = num_info.maybe_zerofill;
+ }
+
+ /* Update min and max arguments */
+ if (!found)
+ {
+ found = 1;
+ min_arg.copy(*res);
+ max_arg.copy(*res);
+ min_length = max_length = length; sum=length;
+ }
+ else if (length)
+ {
+ sum += length;
+ if (length < min_length)
+ min_length = length;
+ if (length > max_length)
+ max_length = length;
+
+ if (sortcmp(res, &min_arg,item->collation.collation) < 0)
+ min_arg.copy(*res);
+ if (sortcmp(res, &max_arg,item->collation.collation) > 0)
+ max_arg.copy(*res);
+ }
+
+ if (room_in_tree)
+ {
+ if (res != &s)
+ s.copy(*res);
+ if (!tree_search(&tree, (void*) &s, tree.custom_arg)) // If not in tree
+ {
+ s.copy(); // slow, when SAFE_MALLOC is in use
+ if (!tree_insert(&tree, (void*) &s, 0, tree.custom_arg))
+ {
+ room_in_tree = 0; // Remove tree, out of RAM ?
+ delete_tree(&tree);
+ }
+ else
+ {
+ bzero((char*) &s, sizeof(s)); // Let tree handle free of this
+ if ((treemem += length) > pc->max_treemem)
+ {
+ room_in_tree = 0; // Remove tree, too big tree
+ delete_tree(&tree);
+ }
+ }
+ }
+ }
+
+ if ((num_info.zerofill && (max_length != min_length)) ||
+ (was_zero_fill && (max_length != min_length)))
+ can_be_still_num = 0; // zerofilled numbers must be of same length
+} // field_str::add
+
+
+void field_real::add()
+{
+ char buff[MAX_FIELD_WIDTH], *ptr, *end;
+ double num= item->val_real();
+ uint length, zero_count, decs;
+ TREE_ELEMENT *element;
+
+ if (item->null_value)
+ {
+ nulls++;
+ return;
+ }
+ if (num == 0.0)
+ empty++;
+
+ if ((decs = decimals()) == NOT_FIXED_DEC)
+ {
+ length= sprintf(buff, "%g", num);
+ if (rint(num) != num)
+ max_notzero_dec_len = 1;
+ }
+ else
+ {
+#ifdef HAVE_SNPRINTF
+ buff[sizeof(buff)-1]=0; // Safety
+ snprintf(buff, sizeof(buff)-1, "%-.*f", (int) decs, num);
+ length = (uint) strlen(buff);
+#else
+ length= sprintf(buff, "%-.*f", (int) decs, num);
+#endif
+
+ // We never need to check further than this
+ end = buff + length - 1 - decs + max_notzero_dec_len;
+
+ zero_count = 0;
+ for (ptr = buff + length - 1; ptr > end && *ptr == '0'; ptr--)
+ zero_count++;
+
+ if ((decs - zero_count > max_notzero_dec_len))
+ max_notzero_dec_len = decs - zero_count;
+ }
+
+ if (room_in_tree)
+ {
+ if (!(element = tree_insert(&tree, (void*) &num, 0, tree.custom_arg)))
+ {
+ room_in_tree = 0; // Remove tree, out of RAM ?
+ delete_tree(&tree);
+ }
+ /*
+ if element->count == 1, this element can be found only once from tree
+ if element->count == 2, or more, this element is already in tree
+ */
+ else if (element->count == 1 && (tree_elements++) >= pc->max_tree_elements)
+ {
+ room_in_tree = 0; // Remove tree, too many elements
+ delete_tree(&tree);
+ }
+ }
+
+ if (!found)
+ {
+ found = 1;
+ min_arg = max_arg = sum = num;
+ sum_sqr = num * num;
+ min_length = max_length = length;
+ }
+ else if (num != 0.0)
+ {
+ sum += num;
+ sum_sqr += num * num;
+ if (length < min_length)
+ min_length = length;
+ if (length > max_length)
+ max_length = length;
+ if (compare_double(&num, &min_arg) < 0)
+ min_arg = num;
+ if (compare_double(&num, &max_arg) > 0)
+ max_arg = num;
+ }
+} // field_real::add
+
+
+void field_decimal::add()
+{
+ /*TODO - remove rounding stuff after decimal_div returns proper frac */
+ my_decimal dec_buf, *dec= item->val_decimal(&dec_buf);
+ my_decimal rounded;
+ uint length;
+ TREE_ELEMENT *element;
+
+ if (item->null_value)
+ {
+ nulls++;
+ return;
+ }
+
+ my_decimal_round(E_DEC_FATAL_ERROR, dec, item->decimals, FALSE,&rounded);
+ dec= &rounded;
+
+ length= my_decimal_string_length(dec);
+
+ if (decimal_is_zero(dec))
+ empty++;
+
+ if (room_in_tree)
+ {
+ uchar buf[DECIMAL_MAX_FIELD_SIZE];
+ my_decimal2binary(E_DEC_FATAL_ERROR, dec, buf,
+ item->max_length, item->decimals);
+ if (!(element = tree_insert(&tree, (void*)buf, 0, tree.custom_arg)))
+ {
+ room_in_tree = 0; // Remove tree, out of RAM ?
+ delete_tree(&tree);
+ }
+ /*
+ if element->count == 1, this element can be found only once from tree
+ if element->count == 2, or more, this element is already in tree
+ */
+ else if (element->count == 1 && (tree_elements++) >= pc->max_tree_elements)
+ {
+ room_in_tree = 0; // Remove tree, too many elements
+ delete_tree(&tree);
+ }
+ }
+
+ if (!found)
+ {
+ found = 1;
+ min_arg = max_arg = sum[0] = *dec;
+ my_decimal_mul(E_DEC_FATAL_ERROR, sum_sqr, dec, dec);
+ cur_sum= 0;
+ min_length = max_length = length;
+ }
+ else if (!decimal_is_zero(dec))
+ {
+ int next_cur_sum= cur_sum ^ 1;
+ my_decimal sqr_buf;
+
+ my_decimal_add(E_DEC_FATAL_ERROR, sum+next_cur_sum, sum+cur_sum, dec);
+ my_decimal_mul(E_DEC_FATAL_ERROR, &sqr_buf, dec, dec);
+ my_decimal_add(E_DEC_FATAL_ERROR,
+ sum_sqr+next_cur_sum, sum_sqr+cur_sum, &sqr_buf);
+ cur_sum= next_cur_sum;
+ if (length < min_length)
+ min_length = length;
+ if (length > max_length)
+ max_length = length;
+ if (my_decimal_cmp(dec, &min_arg) < 0)
+ {
+ min_arg= *dec;
+ }
+ if (my_decimal_cmp(dec, &max_arg) > 0)
+ {
+ max_arg= *dec;
+ }
+ }
+}
+
+
+void field_longlong::add()
+{
+ char buff[MAX_FIELD_WIDTH];
+ longlong num = item->val_int();
+ uint length = (uint) (longlong10_to_str(num, buff, -10) - buff);
+ TREE_ELEMENT *element;
+
+ if (item->null_value)
+ {
+ nulls++;
+ return;
+ }
+ if (num == 0)
+ empty++;
+
+ if (room_in_tree)
+ {
+ if (!(element = tree_insert(&tree, (void*) &num, 0, tree.custom_arg)))
+ {
+ room_in_tree = 0; // Remove tree, out of RAM ?
+ delete_tree(&tree);
+ }
+ /*
+ if element->count == 1, this element can be found only once from tree
+ if element->count == 2, or more, this element is already in tree
+ */
+ else if (element->count == 1 && (tree_elements++) >= pc->max_tree_elements)
+ {
+ room_in_tree = 0; // Remove tree, too many elements
+ delete_tree(&tree);
+ }
+ }
+
+ if (!found)
+ {
+ found = 1;
+ min_arg = max_arg = sum = num;
+ sum_sqr = num * num;
+ min_length = max_length = length;
+ }
+ else if (num != 0)
+ {
+ sum += num;
+ sum_sqr += num * num;
+ if (length < min_length)
+ min_length = length;
+ if (length > max_length)
+ max_length = length;
+ if (compare_longlong(&num, &min_arg) < 0)
+ min_arg = num;
+ if (compare_longlong(&num, &max_arg) > 0)
+ max_arg = num;
+ }
+} // field_longlong::add
+
+
+void field_ulonglong::add()
+{
+ char buff[MAX_FIELD_WIDTH];
+ longlong num = item->val_int();
+ uint length = (uint) (longlong10_to_str(num, buff, 10) - buff);
+ TREE_ELEMENT *element;
+
+ if (item->null_value)
+ {
+ nulls++;
+ return;
+ }
+ if (num == 0)
+ empty++;
+
+ if (room_in_tree)
+ {
+ if (!(element = tree_insert(&tree, (void*) &num, 0, tree.custom_arg)))
+ {
+ room_in_tree = 0; // Remove tree, out of RAM ?
+ delete_tree(&tree);
+ }
+ /*
+ if element->count == 1, this element can be found only once from tree
+ if element->count == 2, or more, this element is already in tree
+ */
+ else if (element->count == 1 && (tree_elements++) >= pc->max_tree_elements)
+ {
+ room_in_tree = 0; // Remove tree, too many elements
+ delete_tree(&tree);
+ }
+ }
+
+ if (!found)
+ {
+ found = 1;
+ min_arg = max_arg = sum = num;
+ sum_sqr = num * num;
+ min_length = max_length = length;
+ }
+ else if (num != 0)
+ {
+ sum += num;
+ sum_sqr += num * num;
+ if (length < min_length)
+ min_length = length;
+ if (length > max_length)
+ max_length = length;
+ if (compare_ulonglong((ulonglong*) &num, &min_arg) < 0)
+ min_arg = num;
+ if (compare_ulonglong((ulonglong*) &num, &max_arg) > 0)
+ max_arg = num;
+ }
+} // field_ulonglong::add
+
+
+int analyse::send_row(List<Item> & /* field_list */)
+{
+ field_info **f = f_info;
+
+ rows++;
+
+ for (;f != f_end; f++)
+ {
+ (*f)->add();
+ }
+ return 0;
+} // analyse::send_row
+
+
+int analyse::end_of_records()
+{
+ field_info **f = f_info;
+ char buff[MAX_FIELD_WIDTH];
+ String *res, s_min(buff, sizeof(buff),&my_charset_bin),
+ s_max(buff, sizeof(buff),&my_charset_bin),
+ ans(buff, sizeof(buff),&my_charset_bin);
+
+ for (; f != f_end; f++)
+ {
+ func_items[0]->set((*f)->item->full_name());
+ if (!(*f)->found)
+ {
+ func_items[1]->null_value = 1;
+ func_items[2]->null_value = 1;
+ }
+ else
+ {
+ func_items[1]->null_value = 0;
+ res = (*f)->get_min_arg(&s_min);
+ func_items[1]->set(res->ptr(), res->length(), res->charset());
+ func_items[2]->null_value = 0;
+ res = (*f)->get_max_arg(&s_max);
+ func_items[2]->set(res->ptr(), res->length(), res->charset());
+ }
+ func_items[3]->set((longlong) (*f)->min_length);
+ func_items[4]->set((longlong) (*f)->max_length);
+ func_items[5]->set((longlong) (*f)->empty);
+ func_items[6]->set((longlong) (*f)->nulls);
+ res = (*f)->avg(&s_max, rows);
+ func_items[7]->set(res->ptr(), res->length(), res->charset());
+ func_items[8]->null_value = 0;
+ res = (*f)->std(&s_max, rows);
+ if (!res)
+ func_items[8]->null_value = 1;
+ else
+ func_items[8]->set(res->ptr(), res->length(), res->charset());
+ /*
+ count the dots, quotas, etc. in (ENUM("a","b","c"...))
+ If tree has been removed, don't suggest ENUM.
+ treemem is used to measure the size of tree for strings,
+ tree_elements is used to count the elements
+ max_treemem tells how long the string starting from ENUM("... and
+ ending to ..") shall at maximum be. If case is about numbers,
+ max_tree_elements will tell the length of the above, now
+ every number is considered as length 1
+ */
+ if (((*f)->treemem || (*f)->tree_elements) &&
+ (*f)->tree.elements_in_tree &&
+ (((*f)->treemem ? max_treemem : max_tree_elements) >
+ (((*f)->treemem ? (*f)->treemem : (*f)->tree_elements) +
+ ((*f)->tree.elements_in_tree * 3 - 1 + 6))))
+ {
+ char tmp[331]; //331, because one double prec. num. can be this long
+ String tmp_str(tmp, sizeof(tmp),&my_charset_bin);
+ TREE_INFO tree_info;
+
+ tree_info.str = &tmp_str;
+ tree_info.found = 0;
+ tree_info.item = (*f)->item;
+
+ tmp_str.set(STRING_WITH_LEN("ENUM("),&my_charset_bin);
+ tree_walk(&(*f)->tree, (*f)->collect_enum(), (char*) &tree_info,
+ left_root_right);
+ tmp_str.append(')');
+
+ if (!(*f)->nulls)
+ tmp_str.append(STRING_WITH_LEN(" NOT NULL"));
+ output_str_length = tmp_str.length();
+ func_items[9]->set(tmp_str.ptr(), tmp_str.length(), tmp_str.charset());
+ if (result->send_data(result_fields) > 0)
+ return -1;
+ continue;
+ }
+
+ ans.length(0);
+ if (!(*f)->treemem && !(*f)->tree_elements)
+ ans.append(STRING_WITH_LEN("CHAR(0)"));
+ else if ((*f)->item->type() == Item::FIELD_ITEM)
+ {
+ switch (((Item_field*) (*f)->item)->field->real_type())
+ {
+ case MYSQL_TYPE_TIMESTAMP:
+ ans.append(STRING_WITH_LEN("TIMESTAMP"));
+ break;
+ case MYSQL_TYPE_DATETIME:
+ ans.append(STRING_WITH_LEN("DATETIME"));
+ break;
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_NEWDATE:
+ ans.append(STRING_WITH_LEN("DATE"));
+ break;
+ case MYSQL_TYPE_SET:
+ ans.append(STRING_WITH_LEN("SET"));
+ break;
+ case MYSQL_TYPE_YEAR:
+ ans.append(STRING_WITH_LEN("YEAR"));
+ break;
+ case MYSQL_TYPE_TIME:
+ ans.append(STRING_WITH_LEN("TIME"));
+ break;
+ case MYSQL_TYPE_DECIMAL:
+ ans.append(STRING_WITH_LEN("DECIMAL"));
+ // if item is FIELD_ITEM, it _must_be_ Field_num in this case
+ if (((Field_num*) ((Item_field*) (*f)->item)->field)->zerofill)
+ ans.append(STRING_WITH_LEN(" ZEROFILL"));
+ break;
+ default:
+ (*f)->get_opt_type(&ans, rows);
+ break;
+ }
+ }
+ if (!(*f)->nulls)
+ ans.append(STRING_WITH_LEN(" NOT NULL"));
+ func_items[9]->set(ans.ptr(), ans.length(), ans.charset());
+ if (result->send_data(result_fields) > 0)
+ return -1;
+ }
+ return 0;
+} // analyse::end_of_records
+
+
+void field_str::get_opt_type(String *answer, ha_rows total_rows)
+{
+ char buff[MAX_FIELD_WIDTH];
+
+ if (can_be_still_num)
+ {
+ if (num_info.is_float)
+ sprintf(buff, "DOUBLE"); // number was like 1e+50... TODO:
+ else if (num_info.decimals) // DOUBLE(%d,%d) sometime
+ {
+ if (num_info.dval > -FLT_MAX && num_info.dval < FLT_MAX)
+ sprintf(buff, "FLOAT(%d,%d)", (num_info.integers + num_info.decimals), num_info.decimals);
+ else
+ sprintf(buff, "DOUBLE(%d,%d)", (num_info.integers + num_info.decimals), num_info.decimals);
+ }
+ else if (ev_num_info.llval >= -128 &&
+ ev_num_info.ullval <=
+ (ulonglong) (ev_num_info.llval >= 0 ? 255 : 127))
+ sprintf(buff, "TINYINT(%d)", num_info.integers);
+ else if (ev_num_info.llval >= INT_MIN16 &&
+ ev_num_info.ullval <= (ulonglong) (ev_num_info.llval >= 0 ?
+ UINT_MAX16 : INT_MAX16))
+ sprintf(buff, "SMALLINT(%d)", num_info.integers);
+ else if (ev_num_info.llval >= INT_MIN24 &&
+ ev_num_info.ullval <= (ulonglong) (ev_num_info.llval >= 0 ?
+ UINT_MAX24 : INT_MAX24))
+ sprintf(buff, "MEDIUMINT(%d)", num_info.integers);
+ else if (ev_num_info.llval >= INT_MIN32 &&
+ ev_num_info.ullval <= (ulonglong) (ev_num_info.llval >= 0 ?
+ UINT_MAX32 : INT_MAX32))
+ sprintf(buff, "INT(%d)", num_info.integers);
+ else
+ sprintf(buff, "BIGINT(%d)", num_info.integers);
+ answer->append(buff, (uint) strlen(buff));
+ if (ev_num_info.llval >= 0 && ev_num_info.min_dval >= 0)
+ answer->append(STRING_WITH_LEN(" UNSIGNED"));
+ if (num_info.zerofill)
+ answer->append(STRING_WITH_LEN(" ZEROFILL"));
+ }
+ else if (max_length < 256)
+ {
+ if (must_be_blob)
+ {
+ if (item->collation.collation == &my_charset_bin)
+ answer->append(STRING_WITH_LEN("TINYBLOB"));
+ else
+ answer->append(STRING_WITH_LEN("TINYTEXT"));
+ }
+ else if ((max_length * (total_rows - nulls)) < (sum + total_rows))
+ {
+ sprintf(buff, "CHAR(%d)", (int) max_length);
+ answer->append(buff, (uint) strlen(buff));
+ }
+ else
+ {
+ sprintf(buff, "VARCHAR(%d)", (int) max_length);
+ answer->append(buff, (uint) strlen(buff));
+ }
+ }
+ else if (max_length < (1L << 16))
+ {
+ if (item->collation.collation == &my_charset_bin)
+ answer->append(STRING_WITH_LEN("BLOB"));
+ else
+ answer->append(STRING_WITH_LEN("TEXT"));
+ }
+ else if (max_length < (1L << 24))
+ {
+ if (item->collation.collation == &my_charset_bin)
+ answer->append(STRING_WITH_LEN("MEDIUMBLOB"));
+ else
+ answer->append(STRING_WITH_LEN("MEDIUMTEXT"));
+ }
+ else
+ {
+ if (item->collation.collation == &my_charset_bin)
+ answer->append(STRING_WITH_LEN("LONGBLOB"));
+ else
+ answer->append(STRING_WITH_LEN("LONGTEXT"));
+ }
+} // field_str::get_opt_type
+
+
+void field_real::get_opt_type(String *answer,
+ ha_rows total_rows __attribute__((unused)))
+{
+ char buff[MAX_FIELD_WIDTH];
+
+ if (!max_notzero_dec_len)
+ {
+ int len= (int) max_length - ((item->decimals == NOT_FIXED_DEC) ?
+ 0 : (item->decimals + 1));
+
+ if (min_arg >= -128 && max_arg <= (min_arg >= 0 ? 255 : 127))
+ sprintf(buff, "TINYINT(%d)", len);
+ else if (min_arg >= INT_MIN16 && max_arg <= (min_arg >= 0 ?
+ UINT_MAX16 : INT_MAX16))
+ sprintf(buff, "SMALLINT(%d)", len);
+ else if (min_arg >= INT_MIN24 && max_arg <= (min_arg >= 0 ?
+ UINT_MAX24 : INT_MAX24))
+ sprintf(buff, "MEDIUMINT(%d)", len);
+ else if (min_arg >= INT_MIN32 && max_arg <= (min_arg >= 0 ?
+ UINT_MAX32 : INT_MAX32))
+ sprintf(buff, "INT(%d)", len);
+ else
+ sprintf(buff, "BIGINT(%d)", len);
+ answer->append(buff, (uint) strlen(buff));
+ if (min_arg >= 0)
+ answer->append(STRING_WITH_LEN(" UNSIGNED"));
+ }
+ else if (item->decimals == NOT_FIXED_DEC)
+ {
+ if (min_arg >= -FLT_MAX && max_arg <= FLT_MAX)
+ answer->append(STRING_WITH_LEN("FLOAT"));
+ else
+ answer->append(STRING_WITH_LEN("DOUBLE"));
+ }
+ else
+ {
+ if (min_arg >= -FLT_MAX && max_arg <= FLT_MAX)
+ sprintf(buff, "FLOAT(%d,%d)", (int) max_length - (item->decimals + 1) + max_notzero_dec_len,
+ max_notzero_dec_len);
+ else
+ sprintf(buff, "DOUBLE(%d,%d)", (int) max_length - (item->decimals + 1) + max_notzero_dec_len,
+ max_notzero_dec_len);
+ answer->append(buff, (uint) strlen(buff));
+ }
+ // if item is FIELD_ITEM, it _must_be_ Field_num in this class
+ if (item->type() == Item::FIELD_ITEM &&
+ // a single number shouldn't be zerofill
+ (max_length - (item->decimals + 1)) != 1 &&
+ ((Field_num*) ((Item_field*) item)->field)->zerofill)
+ answer->append(STRING_WITH_LEN(" ZEROFILL"));
+} // field_real::get_opt_type
+
+
+void field_longlong::get_opt_type(String *answer,
+ ha_rows total_rows __attribute__((unused)))
+{
+ char buff[MAX_FIELD_WIDTH];
+
+ if (min_arg >= -128 && max_arg <= (min_arg >= 0 ? 255 : 127))
+ sprintf(buff, "TINYINT(%d)", (int) max_length);
+ else if (min_arg >= INT_MIN16 && max_arg <= (min_arg >= 0 ?
+ UINT_MAX16 : INT_MAX16))
+ sprintf(buff, "SMALLINT(%d)", (int) max_length);
+ else if (min_arg >= INT_MIN24 && max_arg <= (min_arg >= 0 ?
+ UINT_MAX24 : INT_MAX24))
+ sprintf(buff, "MEDIUMINT(%d)", (int) max_length);
+ else if (min_arg >= INT_MIN32 && max_arg <= (min_arg >= 0 ?
+ UINT_MAX32 : INT_MAX32))
+ sprintf(buff, "INT(%d)", (int) max_length);
+ else
+ sprintf(buff, "BIGINT(%d)", (int) max_length);
+ answer->append(buff, (uint) strlen(buff));
+ if (min_arg >= 0)
+ answer->append(STRING_WITH_LEN(" UNSIGNED"));
+
+ // if item is FIELD_ITEM, it _must_be_ Field_num in this class
+ if ((item->type() == Item::FIELD_ITEM) &&
+ // a single number shouldn't be zerofill
+ max_length != 1 &&
+ ((Field_num*) ((Item_field*) item)->field)->zerofill)
+ answer->append(STRING_WITH_LEN(" ZEROFILL"));
+} // field_longlong::get_opt_type
+
+
+void field_ulonglong::get_opt_type(String *answer,
+ ha_rows total_rows __attribute__((unused)))
+{
+ char buff[MAX_FIELD_WIDTH];
+
+ if (max_arg < 256)
+ sprintf(buff, "TINYINT(%d) UNSIGNED", (int) max_length);
+ else if (max_arg <= ((2 * INT_MAX16) + 1))
+ sprintf(buff, "SMALLINT(%d) UNSIGNED", (int) max_length);
+ else if (max_arg <= ((2 * INT_MAX24) + 1))
+ sprintf(buff, "MEDIUMINT(%d) UNSIGNED", (int) max_length);
+ else if (max_arg < (((ulonglong) 1) << 32))
+ sprintf(buff, "INT(%d) UNSIGNED", (int) max_length);
+ else
+ sprintf(buff, "BIGINT(%d) UNSIGNED", (int) max_length);
+ // if item is FIELD_ITEM, it _must_be_ Field_num in this class
+ answer->append(buff, (uint) strlen(buff));
+ if (item->type() == Item::FIELD_ITEM &&
+ // a single number shouldn't be zerofill
+ max_length != 1 &&
+ ((Field_num*) ((Item_field*) item)->field)->zerofill)
+ answer->append(STRING_WITH_LEN(" ZEROFILL"));
+} //field_ulonglong::get_opt_type
+
+
+void field_decimal::get_opt_type(String *answer,
+ ha_rows total_rows __attribute__((unused)))
+{
+ my_decimal zero;
+ char buff[MAX_FIELD_WIDTH];
+ uint length;
+
+ my_decimal_set_zero(&zero);
+ my_bool is_unsigned= (my_decimal_cmp(&zero, &min_arg) >= 0);
+
+ length= sprintf(buff, "DECIMAL(%d, %d)",
+ (int) (max_length - (item->decimals ? 1 : 0)),
+ item->decimals);
+ if (is_unsigned)
+ length= (uint) (strmov(buff+length, " UNSIGNED")- buff);
+ answer->append(buff, length);
+}
+
+
+String *field_decimal::get_min_arg(String *str)
+{
+ my_decimal2string(E_DEC_FATAL_ERROR, &min_arg, 0, 0, '0', str);
+ return str;
+}
+
+
+String *field_decimal::get_max_arg(String *str)
+{
+ my_decimal2string(E_DEC_FATAL_ERROR, &max_arg, 0, 0, '0', str);
+ return str;
+}
+
+
+String *field_decimal::avg(String *s, ha_rows rows)
+{
+ if (!(rows - nulls))
+ {
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ return s;
+ }
+ my_decimal num, avg_val, rounded_avg;
+ int prec_increment= current_thd->variables.div_precincrement;
+
+ int2my_decimal(E_DEC_FATAL_ERROR, rows - nulls, FALSE, &num);
+ my_decimal_div(E_DEC_FATAL_ERROR, &avg_val, sum+cur_sum, &num, prec_increment);
+ /* TODO remove this after decimal_div returns proper frac */
+ my_decimal_round(E_DEC_FATAL_ERROR, &avg_val,
+ MY_MIN(sum[cur_sum].frac + prec_increment, DECIMAL_MAX_SCALE),
+ FALSE,&rounded_avg);
+ my_decimal2string(E_DEC_FATAL_ERROR, &rounded_avg, 0, 0, '0', s);
+ return s;
+}
+
+
+String *field_decimal::std(String *s, ha_rows rows)
+{
+ if (!(rows - nulls))
+ {
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ return s;
+ }
+ my_decimal num, tmp, sum2, sum2d;
+ double std_sqr;
+ int prec_increment= current_thd->variables.div_precincrement;
+
+ int2my_decimal(E_DEC_FATAL_ERROR, rows - nulls, FALSE, &num);
+ my_decimal_mul(E_DEC_FATAL_ERROR, &sum2, sum+cur_sum, sum+cur_sum);
+ my_decimal_div(E_DEC_FATAL_ERROR, &tmp, &sum2, &num, prec_increment);
+ my_decimal_sub(E_DEC_FATAL_ERROR, &sum2, sum_sqr+cur_sum, &tmp);
+ my_decimal_div(E_DEC_FATAL_ERROR, &tmp, &sum2, &num, prec_increment);
+ my_decimal2double(E_DEC_FATAL_ERROR, &tmp, &std_sqr);
+ s->set_real(((double) std_sqr <= 0.0 ? 0.0 : sqrt(std_sqr)),
+ MY_MIN(item->decimals + prec_increment, NOT_FIXED_DEC), my_thd_charset);
+
+ return s;
+}
+
+
+int collect_string(String *element,
+ element_count count __attribute__((unused)),
+ TREE_INFO *info)
+{
+ if (info->found)
+ info->str->append(',');
+ else
+ info->found = 1;
+ info->str->append('\'');
+ if (info->str->append_for_single_quote(element))
+ return 1;
+ info->str->append('\'');
+ return 0;
+} // collect_string
+
+
+int collect_real(double *element, element_count count __attribute__((unused)),
+ TREE_INFO *info)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String s(buff, sizeof(buff),current_thd->charset());
+
+ if (info->found)
+ info->str->append(',');
+ else
+ info->found = 1;
+ info->str->append('\'');
+ s.set_real(*element, info->item->decimals, current_thd->charset());
+ info->str->append(s);
+ info->str->append('\'');
+ return 0;
+} // collect_real
+
+
+int collect_decimal(uchar *element, element_count count,
+ TREE_INFO *info)
+{
+ char buff[DECIMAL_MAX_STR_LENGTH];
+ String s(buff, sizeof(buff),&my_charset_bin);
+
+ if (info->found)
+ info->str->append(',');
+ else
+ info->found = 1;
+ my_decimal dec;
+ binary2my_decimal(E_DEC_FATAL_ERROR, element, &dec,
+ info->item->max_length, info->item->decimals);
+
+ info->str->append('\'');
+ my_decimal2string(E_DEC_FATAL_ERROR, &dec, 0, 0, '0', &s);
+ info->str->append(s);
+ info->str->append('\'');
+ return 0;
+}
+
+
+int collect_longlong(longlong *element,
+ element_count count __attribute__((unused)),
+ TREE_INFO *info)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String s(buff, sizeof(buff),&my_charset_bin);
+
+ if (info->found)
+ info->str->append(',');
+ else
+ info->found = 1;
+ info->str->append('\'');
+ s.set(*element, current_thd->charset());
+ info->str->append(s);
+ info->str->append('\'');
+ return 0;
+} // collect_longlong
+
+
+int collect_ulonglong(ulonglong *element,
+ element_count count __attribute__((unused)),
+ TREE_INFO *info)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String s(buff, sizeof(buff),&my_charset_bin);
+
+ if (info->found)
+ info->str->append(',');
+ else
+ info->found = 1;
+ info->str->append('\'');
+ s.set(*element, current_thd->charset());
+ info->str->append(s);
+ info->str->append('\'');
+ return 0;
+} // collect_ulonglong
+
+
+bool analyse::change_columns(List<Item> &field_list)
+{
+ field_list.empty();
+
+ func_items[0] = new Item_proc_string("Field_name", 255);
+ func_items[1] = new Item_proc_string("Min_value", 255);
+ func_items[1]->maybe_null = 1;
+ func_items[2] = new Item_proc_string("Max_value", 255);
+ func_items[2]->maybe_null = 1;
+ func_items[3] = new Item_proc_int("Min_length");
+ func_items[4] = new Item_proc_int("Max_length");
+ func_items[5] = new Item_proc_int("Empties_or_zeros");
+ func_items[6] = new Item_proc_int("Nulls");
+ func_items[7] = new Item_proc_string("Avg_value_or_avg_length", 255);
+ func_items[8] = new Item_proc_string("Std", 255);
+ func_items[8]->maybe_null = 1;
+ func_items[9] = new Item_proc_string("Optimal_fieldtype",
+ MY_MAX(64, output_str_length));
+
+ for (uint i = 0; i < array_elements(func_items); i++)
+ field_list.push_back(func_items[i]);
+ result_fields = field_list;
+ return 0;
+} // analyse::change_columns
+
+int compare_double(const double *s, const double *t)
+{
+ return ((*s < *t) ? -1 : *s > *t ? 1 : 0);
+} /* compare_double */
+
+int compare_longlong(const longlong *s, const longlong *t)
+{
+ return ((*s < *t) ? -1 : *s > *t ? 1 : 0);
+} /* compare_longlong */
+
+ int compare_ulonglong(const ulonglong *s, const ulonglong *t)
+{
+ return ((*s < *t) ? -1 : *s > *t ? 1 : 0);
+} /* compare_ulonglong */
+
+
+uint check_ulonglong(const char *str, uint length)
+{
+ const char *long_str = "2147483647", *ulonglong_str = "18446744073709551615";
+ const uint long_len = 10, ulonglong_len = 20;
+
+ while (*str == '0' && length)
+ {
+ str++; length--;
+ }
+ if (length < long_len)
+ return NUM;
+
+ uint smaller, bigger;
+ const char *cmp;
+
+ if (length == long_len)
+ {
+ cmp = long_str;
+ smaller = NUM;
+ bigger = LONG_NUM;
+ }
+ else if (length > ulonglong_len)
+ return DECIMAL_NUM;
+ else
+ {
+ cmp = ulonglong_str;
+ smaller = LONG_NUM;
+ bigger = DECIMAL_NUM;
+ }
+ while (*cmp && *cmp++ == *str++) ;
+ return ((uchar) str[-1] <= (uchar) cmp[-1]) ? smaller : bigger;
+} /* check_ulonlong */
+
diff --git a/sql/sql_analyse.h b/sql/sql_analyse.h
new file mode 100644
index 00000000000..3d3662c3f4f
--- /dev/null
+++ b/sql/sql_analyse.h
@@ -0,0 +1,368 @@
+#ifndef SQL_ANALYSE_INCLUDED
+#define SQL_ANALYSE_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/* Analyse database */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "procedure.h" /* Procedure */
+
+#define my_thd_charset default_charset_info
+
+#define DEC_IN_AVG 4
+
+typedef struct st_number_info
+{
+ // if zerofill is true, the number must be zerofill, or string
+ bool negative, is_float, zerofill, maybe_zerofill;
+ int8 integers;
+ int8 decimals;
+ double dval;
+ ulonglong ullval;
+} NUM_INFO;
+
+typedef struct st_extreme_value_number_info
+{
+ ulonglong ullval;
+ longlong llval;
+ double max_dval, min_dval;
+} EV_NUM_INFO;
+
+typedef struct st_tree_info
+{
+ bool found;
+ String *str;
+ Item *item;
+} TREE_INFO;
+
+uint check_ulonglong(const char *str, uint length);
+bool get_ev_num_info(EV_NUM_INFO *ev_info, NUM_INFO *info, const char *num);
+bool test_if_number(NUM_INFO *info, const char *str, uint str_len);
+int compare_double(const double *s, const double *t);
+int compare_double2(void* cmp_arg __attribute__((unused)),
+ const double *s, const double *t);
+int compare_longlong(const longlong *s, const longlong *t);
+int compare_longlong2(void* cmp_arg __attribute__((unused)),
+ const longlong *s, const longlong *t);
+int compare_ulonglong(const ulonglong *s, const ulonglong *t);
+int compare_ulonglong2(void* cmp_arg __attribute__((unused)),
+ const ulonglong *s, const ulonglong *t);
+int compare_decimal2(int* len, const char *s, const char *t);
+Procedure *proc_analyse_init(THD *thd, ORDER *param, select_result *result,
+ List<Item> &field_list);
+void free_string(String*);
+class analyse;
+
+class field_info :public Sql_alloc
+{
+protected:
+ ulong treemem, tree_elements, empty, nulls, min_length, max_length;
+ uint room_in_tree;
+ bool found;
+ TREE tree;
+ Item *item;
+ analyse *pc;
+
+public:
+ field_info(Item* a, analyse* b) : treemem(0), tree_elements(0), empty(0),
+ nulls(0), min_length(0), max_length(0), room_in_tree(1),
+ found(0),item(a), pc(b) {};
+
+ virtual ~field_info() { delete_tree(&tree); }
+ virtual void add() = 0;
+ virtual void get_opt_type(String*, ha_rows) = 0;
+ virtual String *get_min_arg(String *) = 0;
+ virtual String *get_max_arg(String *) = 0;
+ virtual String *avg(String*, ha_rows) = 0;
+ virtual String *std(String*, ha_rows) = 0;
+ virtual tree_walk_action collect_enum() = 0;
+ virtual uint decimals() { return 0; }
+ friend class analyse;
+};
+
+
+int collect_string(String *element, element_count count,
+ TREE_INFO *info);
+
+int sortcmp2(void* cmp_arg __attribute__((unused)),
+ const String *a,const String *b);
+
+class field_str :public field_info
+{
+ String min_arg, max_arg;
+ ulonglong sum;
+ bool must_be_blob, was_zero_fill, was_maybe_zerofill,
+ can_be_still_num;
+ NUM_INFO num_info;
+ EV_NUM_INFO ev_num_info;
+
+public:
+ field_str(Item* a, analyse* b) :field_info(a,b),
+ min_arg("",default_charset_info),
+ max_arg("",default_charset_info), sum(0),
+ must_be_blob(0), was_zero_fill(0),
+ was_maybe_zerofill(0), can_be_still_num(1)
+ { init_tree(&tree, 0, 0, sizeof(String), (qsort_cmp2) sortcmp2,
+ (tree_element_free) free_string, NULL,
+ MYF(MY_THREAD_SPECIFIC)); };
+
+ void add();
+ void get_opt_type(String*, ha_rows);
+ String *get_min_arg(String *not_used __attribute__((unused)))
+ { return &min_arg; }
+ String *get_max_arg(String *not_used __attribute__((unused)))
+ { return &max_arg; }
+ String *avg(String *s, ha_rows rows)
+ {
+ if (!(rows - nulls))
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ else
+ s->set_real((ulonglong2double(sum) / ulonglong2double(rows - nulls)),
+ DEC_IN_AVG,my_thd_charset);
+ return s;
+ }
+ friend int collect_string(String *element, element_count count,
+ TREE_INFO *info);
+ tree_walk_action collect_enum()
+ { return (tree_walk_action) collect_string; }
+ String *std(String *s __attribute__((unused)),
+ ha_rows rows __attribute__((unused)))
+ { return (String*) 0; }
+};
+
+
+int collect_decimal(uchar *element, element_count count,
+ TREE_INFO *info);
+
+class field_decimal :public field_info
+{
+ my_decimal min_arg, max_arg;
+ my_decimal sum[2], sum_sqr[2];
+ int cur_sum;
+ int bin_size;
+public:
+ field_decimal(Item* a, analyse* b) :field_info(a,b)
+ {
+ bin_size= my_decimal_get_binary_size(a->max_length, a->decimals);
+ init_tree(&tree, 0, 0, bin_size, (qsort_cmp2)compare_decimal2,
+ 0, (void *)&bin_size, MYF(MY_THREAD_SPECIFIC));
+ };
+
+ void add();
+ void get_opt_type(String*, ha_rows);
+ String *get_min_arg(String *);
+ String *get_max_arg(String *);
+ String *avg(String *s, ha_rows rows);
+ friend int collect_decimal(uchar *element, element_count count,
+ TREE_INFO *info);
+ tree_walk_action collect_enum()
+ { return (tree_walk_action) collect_decimal; }
+ String *std(String *s, ha_rows rows);
+};
+
+
+int collect_real(double *element, element_count count, TREE_INFO *info);
+
+class field_real: public field_info
+{
+ double min_arg, max_arg;
+ double sum, sum_sqr;
+ uint max_notzero_dec_len;
+
+public:
+ field_real(Item* a, analyse* b) :field_info(a,b),
+ min_arg(0), max_arg(0), sum(0), sum_sqr(0), max_notzero_dec_len(0)
+ { init_tree(&tree, 0, 0, sizeof(double),
+ (qsort_cmp2) compare_double2, NULL, NULL,
+ MYF(MY_THREAD_SPECIFIC)); }
+
+ void add();
+ void get_opt_type(String*, ha_rows);
+ String *get_min_arg(String *s)
+ {
+ s->set_real(min_arg, item->decimals, my_thd_charset);
+ return s;
+ }
+ String *get_max_arg(String *s)
+ {
+ s->set_real(max_arg, item->decimals, my_thd_charset);
+ return s;
+ }
+ String *avg(String *s, ha_rows rows)
+ {
+ if (!(rows - nulls))
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ else
+ s->set_real(((double)sum / (double) (rows - nulls)), item->decimals,my_thd_charset);
+ return s;
+ }
+ String *std(String *s, ha_rows rows)
+ {
+ double tmp = ulonglong2double(rows);
+ if (!(tmp - nulls))
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ else
+ {
+ double tmp2 = ((sum_sqr - sum * sum / (tmp - nulls)) /
+ (tmp - nulls));
+ s->set_real(((double) tmp2 <= 0.0 ? 0.0 : sqrt(tmp2)), item->decimals,my_thd_charset);
+ }
+ return s;
+ }
+ uint decimals() { return item->decimals; }
+ friend int collect_real(double *element, element_count count,
+ TREE_INFO *info);
+ tree_walk_action collect_enum()
+ { return (tree_walk_action) collect_real;}
+};
+
+int collect_longlong(longlong *element, element_count count,
+ TREE_INFO *info);
+
+class field_longlong: public field_info
+{
+ longlong min_arg, max_arg;
+ longlong sum, sum_sqr;
+
+public:
+ field_longlong(Item* a, analyse* b) :field_info(a,b),
+ min_arg(0), max_arg(0), sum(0), sum_sqr(0)
+ { init_tree(&tree, 0, 0, sizeof(longlong),
+ (qsort_cmp2) compare_longlong2, NULL, NULL,
+ MYF(MY_THREAD_SPECIFIC)); }
+
+ void add();
+ void get_opt_type(String*, ha_rows);
+ String *get_min_arg(String *s) { s->set(min_arg,my_thd_charset); return s; }
+ String *get_max_arg(String *s) { s->set(max_arg,my_thd_charset); return s; }
+ String *avg(String *s, ha_rows rows)
+ {
+ if (!(rows - nulls))
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ else
+ s->set_real(((double) sum / (double) (rows - nulls)), DEC_IN_AVG,my_thd_charset);
+ return s;
+ }
+ String *std(String *s, ha_rows rows)
+ {
+ double tmp = ulonglong2double(rows);
+ if (!(tmp - nulls))
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ else
+ {
+ double tmp2 = ((sum_sqr - sum * sum / (tmp - nulls)) /
+ (tmp - nulls));
+ s->set_real(((double) tmp2 <= 0.0 ? 0.0 : sqrt(tmp2)), DEC_IN_AVG,my_thd_charset);
+ }
+ return s;
+ }
+ friend int collect_longlong(longlong *element, element_count count,
+ TREE_INFO *info);
+ tree_walk_action collect_enum()
+ { return (tree_walk_action) collect_longlong;}
+};
+
+int collect_ulonglong(ulonglong *element, element_count count,
+ TREE_INFO *info);
+
+class field_ulonglong: public field_info
+{
+ ulonglong min_arg, max_arg;
+ ulonglong sum, sum_sqr;
+
+public:
+ field_ulonglong(Item* a, analyse * b) :field_info(a,b),
+ min_arg(0), max_arg(0), sum(0),sum_sqr(0)
+ { init_tree(&tree, 0, 0, sizeof(ulonglong),
+ (qsort_cmp2) compare_ulonglong2, NULL, NULL,
+ MYF(MY_THREAD_SPECIFIC)); }
+ void add();
+ void get_opt_type(String*, ha_rows);
+ String *get_min_arg(String *s) { s->set(min_arg,my_thd_charset); return s; }
+ String *get_max_arg(String *s) { s->set(max_arg,my_thd_charset); return s; }
+ String *avg(String *s, ha_rows rows)
+ {
+ if (!(rows - nulls))
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ else
+ s->set_real((ulonglong2double(sum) / ulonglong2double(rows - nulls)),
+ DEC_IN_AVG,my_thd_charset);
+ return s;
+ }
+ String *std(String *s, ha_rows rows)
+ {
+ double tmp = ulonglong2double(rows);
+ if (!(tmp - nulls))
+ s->set_real((double) 0.0, 1,my_thd_charset);
+ else
+ {
+ double tmp2 = ((ulonglong2double(sum_sqr) -
+ ulonglong2double(sum * sum) / (tmp - nulls)) /
+ (tmp - nulls));
+ s->set_real(((double) tmp2 <= 0.0 ? 0.0 : sqrt(tmp2)), DEC_IN_AVG,my_thd_charset);
+ }
+ return s;
+ }
+ friend int collect_ulonglong(ulonglong *element, element_count count,
+ TREE_INFO *info);
+ tree_walk_action collect_enum()
+ { return (tree_walk_action) collect_ulonglong; }
+};
+
+
+Procedure *proc_analyse_init(THD *thd, ORDER *param,
+ select_result *result,
+ List<Item> &field_list);
+
+class analyse: public Procedure
+{
+protected:
+ Item_proc *func_items[10];
+ List<Item> fields, result_fields;
+ field_info **f_info, **f_end;
+ ha_rows rows;
+ uint output_str_length;
+
+public:
+ uint max_tree_elements, max_treemem;
+
+ analyse(select_result *res) :Procedure(res, PROC_NO_SORT), f_info(0),
+ rows(0), output_str_length(0) {}
+
+ ~analyse()
+ {
+ if (f_info)
+ {
+ for (field_info **f=f_info; f != f_end; f++)
+ delete (*f);
+ }
+ }
+ virtual void add() {}
+ virtual bool change_columns(List<Item> &fields);
+ virtual int send_row(List<Item> &field_list);
+ virtual void end_group(void) {}
+ virtual int end_of_records(void);
+ friend Procedure *proc_analyse_init(THD *thd, ORDER *param,
+ select_result *result,
+ List<Item> &field_list);
+};
+
+#endif /* SQL_ANALYSE_INCLUDED */
diff --git a/sql/sql_array.h b/sql/sql_array.h
new file mode 100644
index 00000000000..8202e94ce41
--- /dev/null
+++ b/sql/sql_array.h
@@ -0,0 +1,240 @@
+#ifndef SQL_ARRAY_INCLUDED
+#define SQL_ARRAY_INCLUDED
+
+/* Copyright (c) 2003, 2005-2007 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 <my_sys.h>
+
+/**
+ A wrapper class which provides array bounds checking.
+ We do *not* own the array, we simply have a pointer to the first element,
+ and a length.
+
+ @remark
+ We want the compiler-generated versions of:
+ - the copy CTOR (memberwise initialization)
+ - the assignment operator (memberwise assignment)
+
+ @param Element_type The type of the elements of the container.
+ */
+template <typename Element_type> class Bounds_checked_array
+{
+public:
+ Bounds_checked_array() : m_array(NULL), m_size(0) {}
+
+ Bounds_checked_array(Element_type *el, size_t size)
+ : m_array(el), m_size(size)
+ {}
+
+ void reset() { m_array= NULL; m_size= 0; }
+
+ void reset(Element_type *array, size_t size)
+ {
+ m_array= array;
+ m_size= size;
+ }
+
+ /**
+ Set a new bound on the array. Does not resize the underlying
+ array, so the new size must be smaller than or equal to the
+ current size.
+ */
+ void resize(size_t new_size)
+ {
+ DBUG_ASSERT(new_size <= m_size);
+ m_size= new_size;
+ }
+
+ Element_type &operator[](size_t n)
+ {
+ DBUG_ASSERT(n < m_size);
+ return m_array[n];
+ }
+
+ const Element_type &operator[](size_t n) const
+ {
+ DBUG_ASSERT(n < m_size);
+ return m_array[n];
+ }
+
+ size_t element_size() const { return sizeof(Element_type); }
+ size_t size() const { return m_size; }
+
+ bool is_null() const { return m_array == NULL; }
+
+ void pop_front()
+ {
+ DBUG_ASSERT(m_size > 0);
+ m_array+= 1;
+ m_size-= 1;
+ }
+
+ Element_type *array() const { return m_array; }
+
+private:
+ Element_type *m_array;
+ size_t m_size;
+};
+
+/*
+ A typesafe wrapper around DYNAMIC_ARRAY
+
+ TODO: Change creator to take a THREAD_SPECIFIC option.
+*/
+
+template <class Elem> class Dynamic_array
+{
+ DYNAMIC_ARRAY array;
+public:
+ Dynamic_array(uint prealloc=16, uint increment=16)
+ {
+ init(prealloc, increment);
+ }
+
+ void init(uint prealloc=16, uint increment=16)
+ {
+ my_init_dynamic_array(&array, sizeof(Elem), prealloc, increment,
+ MYF(0));
+ }
+
+ /**
+ @note Though formally this could be declared "const" it would be
+ misleading at it returns a non-const pointer to array's data.
+ */
+ Elem& at(size_t idx)
+ {
+ DBUG_ASSERT(idx < array.elements);
+ return *(((Elem*)array.buffer) + idx);
+ }
+ /// Const variant of at(), which cannot change data
+ const Elem& at(size_t idx) const
+ {
+ return *(((Elem*)array.buffer) + idx);
+ }
+
+ /// @returns pointer to first element
+ Elem *front()
+ {
+ return (Elem*)array.buffer;
+ }
+
+ /// @returns pointer to first element
+ const Elem *front() const
+ {
+ return (const Elem*)array.buffer;
+ }
+
+ /// @returns pointer to last element
+ Elem *back()
+ {
+ return ((Elem*)array.buffer) + array.elements - 1;
+ }
+
+ /// @returns pointer to last element
+ const Elem *back() const
+ {
+ return ((const Elem*)array.buffer) + array.elements - 1;
+ }
+
+ /**
+ @retval false ok
+ @retval true OOM, @c my_error() has been called.
+ */
+ bool append(const Elem &el)
+ {
+ return insert_dynamic(&array, &el);
+ }
+
+ bool append_val(Elem el)
+ {
+ return (insert_dynamic(&array, (uchar*)&el));
+ }
+
+ bool push(Elem &el)
+ {
+ return append(el);
+ }
+
+ /// Pops the last element. Does nothing if array is empty.
+ Elem& pop()
+ {
+ return *((Elem*)pop_dynamic(&array));
+ }
+
+ void del(uint idx)
+ {
+ delete_dynamic_element(&array, idx);
+ }
+
+ size_t elements() const
+ {
+ return array.elements;
+ }
+
+ void elements(size_t num_elements)
+ {
+ DBUG_ASSERT(num_elements <= array.max_element);
+ array.elements= num_elements;
+ }
+
+ void clear()
+ {
+ elements(0);
+ }
+
+ void set(uint idx, const Elem &el)
+ {
+ set_dynamic(&array, &el, idx);
+ }
+
+ bool resize(size_t new_size, Elem default_val)
+ {
+ size_t old_size= elements();
+ if (allocate_dynamic(&array, new_size))
+ return true;
+
+ if (new_size > old_size)
+ {
+ set_dynamic(&array, (uchar*)&default_val, new_size - 1);
+ /*for (size_t i= old_size; i != new_size; i++)
+ {
+ at(i)= default_val;
+ }*/
+ }
+ return false;
+ }
+
+ ~Dynamic_array()
+ {
+ delete_dynamic(&array);
+ }
+
+ typedef int (*CMP_FUNC)(const Elem *el1, const Elem *el2);
+
+ void sort(CMP_FUNC cmp_func)
+ {
+ my_qsort(array.buffer, array.elements, sizeof(Elem), (qsort_cmp)cmp_func);
+ }
+
+ typedef int (*CMP_FUNC2)(const Elem *el1, const Elem *el2, void *);
+ void sort(CMP_FUNC2 cmp_func, void *data)
+ {
+ my_qsort2(array.buffer, array.elements, sizeof(Elem), (qsort2_cmp)cmp_func, data);
+ }
+};
+
+#endif /* SQL_ARRAY_INCLUDED */
diff --git a/sql/sql_audit.cc b/sql/sql_audit.cc
new file mode 100644
index 00000000000..b659054a50b
--- /dev/null
+++ b/sql/sql_audit.cc
@@ -0,0 +1,585 @@
+/* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "sql_audit.h"
+
+extern int initialize_audit_plugin(st_plugin_int *plugin);
+extern int finalize_audit_plugin(st_plugin_int *plugin);
+
+#ifndef EMBEDDED_LIBRARY
+
+struct st_mysql_event_generic
+{
+ unsigned int event_class;
+ const void *event;
+};
+
+unsigned long mysql_global_audit_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
+
+static mysql_mutex_t LOCK_audit_mask;
+
+static void event_class_dispatch(THD *, unsigned int, const void *);
+
+
+static inline
+void set_audit_mask(unsigned long *mask, uint event_class)
+{
+ mask[0]= 1;
+ mask[0]<<= event_class;
+}
+
+static inline
+void add_audit_mask(unsigned long *mask, const unsigned long *rhs)
+{
+ mask[0]|= rhs[0];
+}
+
+static inline
+bool check_audit_mask(const unsigned long *lhs,
+ const unsigned long *rhs)
+{
+ return !(lhs[0] & rhs[0]);
+}
+
+
+typedef void (*audit_handler_t)(THD *thd, uint event_subtype, va_list ap);
+
+/**
+ MYSQL_AUDIT_GENERAL_CLASS handler
+
+ @param[in] thd
+ @param[in] event_subtype
+ @param[in] error_code
+ @param[in] ap
+
+*/
+
+static void general_class_handler(THD *thd, uint event_subtype, va_list ap)
+{
+ mysql_event_general event;
+ event.event_subclass= event_subtype;
+ event.general_error_code= va_arg(ap, int);
+ event.general_thread_id= thd ? thd->thread_id : 0;
+ event.general_time= va_arg(ap, time_t);
+ event.general_user= va_arg(ap, const char *);
+ event.general_user_length= va_arg(ap, unsigned int);
+ event.general_command= va_arg(ap, const char *);
+ event.general_command_length= va_arg(ap, unsigned int);
+ event.general_query= va_arg(ap, const char *);
+ event.general_query_length= va_arg(ap, unsigned int);
+ event.general_charset= va_arg(ap, struct charset_info_st *);
+ event.general_rows= (unsigned long long) va_arg(ap, ha_rows);
+ event.database= va_arg(ap, const char *);
+ event.database_length= va_arg(ap, unsigned int);
+ event.query_id= (unsigned long long) (thd ? thd->query_id : 0);
+ event_class_dispatch(thd, MYSQL_AUDIT_GENERAL_CLASS, &event);
+}
+
+
+static void connection_class_handler(THD *thd, uint event_subclass, va_list ap)
+{
+ mysql_event_connection event;
+ event.event_subclass= event_subclass;
+ event.status= va_arg(ap, int);
+ event.thread_id= va_arg(ap, unsigned long);
+ event.user= va_arg(ap, const char *);
+ event.user_length= va_arg(ap, unsigned int);
+ event.priv_user= va_arg(ap, const char *);
+ event.priv_user_length= va_arg(ap, unsigned int);
+ event.external_user= va_arg(ap, const char *);
+ event.external_user_length= va_arg(ap, unsigned int);
+ event.proxy_user= va_arg(ap, const char *);
+ event.proxy_user_length= va_arg(ap, unsigned int);
+ event.host= va_arg(ap, const char *);
+ event.host_length= va_arg(ap, unsigned int);
+ event.ip= va_arg(ap, const char *);
+ event.ip_length= va_arg(ap, unsigned int);
+ event.database= va_arg(ap, const char *);
+ event.database_length= va_arg(ap, unsigned int);
+ event_class_dispatch(thd, MYSQL_AUDIT_CONNECTION_CLASS, &event);
+}
+
+
+static void table_class_handler(THD *thd, uint event_subclass, va_list ap)
+{
+ mysql_event_table event;
+ event.event_subclass= event_subclass;
+ event.read_only= va_arg(ap, int);
+ event.thread_id= va_arg(ap, unsigned long);
+ event.user= va_arg(ap, const char *);
+ event.priv_user= va_arg(ap, const char *);
+ event.priv_host= va_arg(ap, const char *);
+ event.external_user= va_arg(ap, const char *);
+ event.proxy_user= va_arg(ap, const char *);
+ event.host= va_arg(ap, const char *);
+ event.ip= va_arg(ap, const char *);
+ event.database= va_arg(ap, const char *);
+ event.database_length= va_arg(ap, unsigned int);
+ event.table= va_arg(ap, const char *);
+ event.table_length= va_arg(ap, unsigned int);
+ event.new_database= va_arg(ap, const char *);
+ event.new_database_length= va_arg(ap, unsigned int);
+ event.new_table= va_arg(ap, const char *);
+ event.new_table_length= va_arg(ap, unsigned int);
+ event.query_id= (unsigned long long) (thd ? thd->query_id : 0);
+ event_class_dispatch(thd, MYSQL_AUDIT_TABLE_CLASS, &event);
+}
+
+
+static audit_handler_t audit_handlers[] =
+{
+ general_class_handler, connection_class_handler,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0, /* placeholders */
+ table_class_handler
+};
+
+static const uint audit_handlers_count=
+ (sizeof(audit_handlers) / sizeof(audit_handler_t));
+
+
+/**
+ Acquire and lock any additional audit plugins as required
+
+ @param[in] thd
+ @param[in] plugin
+ @param[in] arg
+
+ @retval FALSE Always
+*/
+
+static my_bool acquire_plugins(THD *thd, plugin_ref plugin, void *arg)
+{
+ ulong *event_class_mask= (ulong*) arg;
+ st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
+
+ /* Check if this plugin is interested in the event */
+ if (check_audit_mask(data->class_mask, event_class_mask))
+ return 0;
+
+ /*
+ Check if this plugin may already be registered. This will fail to
+ acquire a newly installed plugin on a specific corner case where
+ one or more event classes already in use by the calling thread
+ are an event class of which the audit plugin has interest.
+ */
+ if (!check_audit_mask(data->class_mask, thd->audit_class_mask))
+ return 0;
+
+ /* Check if we need to initialize the array of acquired plugins */
+ if (unlikely(!thd->audit_class_plugins.buffer))
+ {
+ /* specify some reasonable initialization defaults */
+ my_init_dynamic_array(&thd->audit_class_plugins,
+ sizeof(plugin_ref), 16, 16, MYF(0));
+ }
+
+ /* lock the plugin and add it to the list */
+ plugin= my_plugin_lock(NULL, plugin);
+ insert_dynamic(&thd->audit_class_plugins, (uchar*) &plugin);
+
+ return 0;
+}
+
+
+/**
+ @brief Acquire audit plugins
+
+ @param[in] thd MySQL thread handle
+ @param[in] event_class Audit event class
+
+ @details Ensure that audit plugins interested in given event
+ class are locked by current thread.
+*/
+void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask)
+{
+ DBUG_ENTER("mysql_audit_acquire_plugins");
+ if (thd && !check_audit_mask(mysql_global_audit_mask, event_class_mask) &&
+ check_audit_mask(thd->audit_class_mask, event_class_mask))
+ {
+ plugin_foreach(thd, acquire_plugins, MYSQL_AUDIT_PLUGIN, event_class_mask);
+ add_audit_mask(thd->audit_class_mask, event_class_mask);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Notify the audit system of an event
+
+ @param[in] thd
+ @param[in] event_class
+ @param[in] event_subtype
+ @param[in] error_code
+
+*/
+
+void mysql_audit_notify(THD *thd, uint event_class, uint event_subtype, ...)
+{
+ va_list ap;
+ audit_handler_t *handlers= audit_handlers + event_class;
+ DBUG_ASSERT(event_class < audit_handlers_count);
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
+ set_audit_mask(event_class_mask, event_class);
+ mysql_audit_acquire_plugins(thd, event_class_mask);
+ va_start(ap, event_subtype);
+ (*handlers)(thd, event_subtype, ap);
+ va_end(ap);
+}
+
+
+/**
+ Release any resources associated with the current thd.
+
+ @param[in] thd
+
+*/
+
+void mysql_audit_release(THD *thd)
+{
+ plugin_ref *plugins, *plugins_last;
+
+ if (!thd || !(thd->audit_class_plugins.elements))
+ return;
+
+ plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
+ plugins_last= plugins + thd->audit_class_plugins.elements;
+ for (; plugins < plugins_last; plugins++)
+ {
+ st_mysql_audit *data= plugin_data(*plugins, struct st_mysql_audit *);
+
+ /* Check to see if the plugin has a release method */
+ if (!(data->release_thd))
+ continue;
+
+ /* Tell the plugin to release its resources */
+ data->release_thd(thd);
+ }
+
+ /* Now we actually unlock the plugins */
+ plugin_unlock_list(NULL, (plugin_ref*) thd->audit_class_plugins.buffer,
+ thd->audit_class_plugins.elements);
+
+ /* Reset the state of thread values */
+ reset_dynamic(&thd->audit_class_plugins);
+ bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
+}
+
+
+/**
+ Initialize thd variables used by Audit
+
+ @param[in] thd
+
+*/
+
+void mysql_audit_init_thd(THD *thd)
+{
+ bzero(&thd->audit_class_plugins, sizeof(thd->audit_class_plugins));
+ bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
+}
+
+
+/**
+ Free thd variables used by Audit
+
+ @param[in] thd
+ @param[in] plugin
+ @param[in] arg
+
+ @retval FALSE Always
+*/
+
+void mysql_audit_free_thd(THD *thd)
+{
+ mysql_audit_release(thd);
+ DBUG_ASSERT(thd->audit_class_plugins.elements == 0);
+ delete_dynamic(&thd->audit_class_plugins);
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_LOCK_audit_mask;
+
+static PSI_mutex_info all_audit_mutexes[]=
+{
+ { &key_LOCK_audit_mask, "LOCK_audit_mask", PSI_FLAG_GLOBAL}
+};
+
+static void init_audit_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_audit_mutexes);
+ PSI_server->register_mutex(category, all_audit_mutexes, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+/**
+ Initialize Audit global variables
+*/
+
+void mysql_audit_initialize()
+{
+#ifdef HAVE_PSI_INTERFACE
+ init_audit_psi_keys();
+#endif
+
+ mysql_mutex_init(key_LOCK_audit_mask, &LOCK_audit_mask, MY_MUTEX_INIT_FAST);
+ bzero(mysql_global_audit_mask, sizeof(mysql_global_audit_mask));
+}
+
+
+/**
+ Finalize Audit global variables
+*/
+
+void mysql_audit_finalize()
+{
+ mysql_mutex_destroy(&LOCK_audit_mask);
+}
+
+
+/**
+ Initialize an Audit plug-in
+
+ @param[in] plugin
+
+ @retval FALSE OK
+ @retval TRUE There was an error.
+*/
+
+int initialize_audit_plugin(st_plugin_int *plugin)
+{
+ st_mysql_audit *data= (st_mysql_audit*) plugin->plugin->info;
+
+ if (!data->class_mask || !data->event_notify ||
+ !data->class_mask[0])
+ {
+ sql_print_error("Plugin '%s' has invalid data.",
+ plugin->name.str);
+ return 1;
+ }
+
+ if (plugin->plugin->init && plugin->plugin->init(NULL))
+ {
+ sql_print_error("Plugin '%s' init function returned error.",
+ plugin->name.str);
+ return 1;
+ }
+
+ /* Make the interface info more easily accessible */
+ plugin->data= plugin->plugin->info;
+
+ /* Add the bits the plugin is interested in to the global mask */
+ mysql_mutex_lock(&LOCK_audit_mask);
+ add_audit_mask(mysql_global_audit_mask, data->class_mask);
+ mysql_mutex_unlock(&LOCK_audit_mask);
+
+ /*
+ Pre-acquire the newly inslalled audit plugin for events that
+ may potentially occur further during INSTALL PLUGIN.
+
+ When audit event is triggered, audit subsystem acquires interested
+ plugins by walking through plugin list. Evidently plugin list
+ iterator protects plugin list by acquiring LOCK_plugin, see
+ plugin_foreach_with_mask().
+
+ On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
+ rather for a long time.
+
+ When audit event is triggered during [UN]INSTALL PLUGIN, plugin
+ list iterator acquires the same lock (within the same thread)
+ second time.
+
+ This hack should be removed when LOCK_plugin is fixed so it
+ protects only what it supposed to protect.
+
+ See also mysql_install_plugin() and mysql_uninstall_plugin()
+ */
+ THD *thd= current_thd;
+ if (thd)
+ {
+ acquire_plugins(thd, plugin_int_to_ref(plugin), data->class_mask);
+ add_audit_mask(thd->audit_class_mask, data->class_mask);
+ }
+
+ return 0;
+}
+
+
+/**
+ Performs a bitwise OR of the installed plugins event class masks
+
+ @param[in] thd
+ @param[in] plugin
+ @param[in] arg
+
+ @retval FALSE always
+*/
+static my_bool calc_class_mask(THD *thd, plugin_ref plugin, void *arg)
+{
+ st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
+ if ((data= plugin_data(plugin, struct st_mysql_audit *)))
+ add_audit_mask((unsigned long *) arg, data->class_mask);
+ return 0;
+}
+
+
+/**
+ Finalize an Audit plug-in
+
+ @param[in] plugin
+
+ @retval FALSE OK
+ @retval TRUE There was an error.
+*/
+int finalize_audit_plugin(st_plugin_int *plugin)
+{
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
+
+ if (plugin->plugin->deinit && plugin->plugin->deinit(NULL))
+ {
+ DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
+ plugin->name.str));
+ DBUG_EXECUTE("finalize_audit_plugin", return 1; );
+ }
+
+ plugin->data= NULL;
+ bzero(&event_class_mask, sizeof(event_class_mask));
+
+ /* Iterate through all the installed plugins to create new mask */
+
+ /*
+ LOCK_audit_mask/LOCK_plugin order is not fixed, but serialized with table
+ lock on mysql.plugin.
+ */
+ mysql_mutex_lock(&LOCK_audit_mask);
+ plugin_foreach(current_thd, calc_class_mask, MYSQL_AUDIT_PLUGIN,
+ &event_class_mask);
+
+ /* Set the global audit mask */
+ bmove(mysql_global_audit_mask, event_class_mask, sizeof(event_class_mask));
+ mysql_mutex_unlock(&LOCK_audit_mask);
+
+ return 0;
+}
+
+
+/**
+ Dispatches an event by invoking the plugin's event_notify method.
+
+ @param[in] thd
+ @param[in] plugin
+ @param[in] arg
+
+ @retval FALSE always
+*/
+
+static my_bool plugins_dispatch(THD *thd, plugin_ref plugin, void *arg)
+{
+ const struct st_mysql_event_generic *event_generic=
+ (const struct st_mysql_event_generic *) arg;
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
+ st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
+
+ set_audit_mask(event_class_mask, event_generic->event_class);
+
+ /* Check to see if the plugin is interested in this event */
+ if (check_audit_mask(data->class_mask, event_class_mask))
+ return 0;
+
+ /* Actually notify the plugin */
+ data->event_notify(thd, event_generic->event_class, event_generic->event);
+
+ return 0;
+}
+
+
+/**
+ Distributes an audit event to plug-ins
+
+ @param[in] thd
+ @param[in] event
+*/
+
+static void event_class_dispatch(THD *thd, unsigned int event_class,
+ const void *event)
+{
+ struct st_mysql_event_generic event_generic;
+ event_generic.event_class= event_class;
+ event_generic.event= event;
+ /*
+ Check if we are doing a slow global dispatch. This event occurs when
+ thd == NULL as it is not associated with any particular thread.
+ */
+ if (unlikely(!thd))
+ {
+ plugin_foreach(thd, plugins_dispatch, MYSQL_AUDIT_PLUGIN, &event_generic);
+ }
+ else
+ {
+ plugin_ref *plugins, *plugins_last;
+
+ /* Use the cached set of audit plugins */
+ plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
+ plugins_last= plugins + thd->audit_class_plugins.elements;
+
+ for (; plugins < plugins_last; plugins++)
+ plugins_dispatch(thd, *plugins, &event_generic);
+ }
+}
+
+
+#else /* EMBEDDED_LIBRARY */
+
+
+void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask)
+{
+}
+
+
+void mysql_audit_initialize()
+{
+}
+
+
+void mysql_audit_finalize()
+{
+}
+
+
+int initialize_audit_plugin(st_plugin_int *plugin)
+{
+ return 1;
+}
+
+
+int finalize_audit_plugin(st_plugin_int *plugin)
+{
+ return 0;
+}
+
+
+void mysql_audit_release(THD *thd)
+{
+}
+
+
+#endif /* EMBEDDED_LIBRARY */
diff --git a/sql/sql_audit.h b/sql/sql_audit.h
new file mode 100644
index 00000000000..68106f099cc
--- /dev/null
+++ b/sql/sql_audit.h
@@ -0,0 +1,309 @@
+#ifndef SQL_AUDIT_INCLUDED
+#define SQL_AUDIT_INCLUDED
+
+/* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+
+#include <mysql/plugin_audit.h>
+#include "sql_class.h"
+
+extern unsigned long mysql_global_audit_mask[];
+
+
+extern void mysql_audit_initialize();
+extern void mysql_audit_finalize();
+
+
+extern void mysql_audit_init_thd(THD *thd);
+extern void mysql_audit_free_thd(THD *thd);
+extern void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask);
+
+
+#ifndef EMBEDDED_LIBRARY
+extern void mysql_audit_notify(THD *thd, uint event_class,
+ uint event_subtype, ...);
+
+static inline bool mysql_audit_general_enabled()
+{
+ return mysql_global_audit_mask[0] & MYSQL_AUDIT_GENERAL_CLASSMASK;
+}
+
+static inline bool mysql_audit_connection_enabled()
+{
+ return mysql_global_audit_mask[0] & MYSQL_AUDIT_CONNECTION_CLASSMASK;
+}
+
+static inline bool mysql_audit_table_enabled()
+{
+ return mysql_global_audit_mask[0] & MYSQL_AUDIT_TABLE_CLASSMASK;
+}
+
+#else
+static inline void mysql_audit_notify(THD *thd, uint event_class,
+ uint event_subtype, ...) { }
+#define mysql_audit_general_enabled() 0
+#define mysql_audit_connection_enabled() 0
+#define mysql_audit_table_enabled() 0
+#endif
+extern void mysql_audit_release(THD *thd);
+
+#define MAX_USER_HOST_SIZE 512
+static inline uint make_user_name(THD *thd, char *buf)
+{
+ const Security_context *sctx= thd->security_ctx;
+ return strxnmov(buf, MAX_USER_HOST_SIZE,
+ sctx->priv_user[0] ? sctx->priv_user : "", "[",
+ sctx->user ? sctx->user : "", "] @ ",
+ sctx->host ? sctx->host : "", " [",
+ sctx->ip ? sctx->ip : "", "]", NullS) - buf;
+}
+
+/**
+ Call audit plugins of GENERAL audit class, MYSQL_AUDIT_GENERAL_LOG subtype.
+
+ @param[in] thd
+ @param[in] time time that event occurred
+ @param[in] user User name
+ @param[in] userlen User name length
+ @param[in] cmd Command name
+ @param[in] cmdlen Command name length
+ @param[in] query Query string
+ @param[in] querylen Query string length
+*/
+
+static inline
+void mysql_audit_general_log(THD *thd, time_t time,
+ const char *user, uint userlen,
+ const char *cmd, uint cmdlen,
+ const char *query, uint querylen)
+{
+ if (mysql_audit_general_enabled())
+ {
+ CHARSET_INFO *clientcs= thd ? thd->variables.character_set_client
+ : global_system_variables.character_set_client;
+ const char *db= thd ? thd->db : "";
+ size_t db_length= thd ? thd->db_length : 0;
+
+ mysql_audit_notify(thd, MYSQL_AUDIT_GENERAL_CLASS, MYSQL_AUDIT_GENERAL_LOG,
+ 0, time, user, userlen, cmd, cmdlen,
+ query, querylen, clientcs, (ha_rows) 0,
+ db, db_length);
+ }
+}
+
+/**
+ Call audit plugins of GENERAL audit class.
+ event_subtype should be set to one of:
+ MYSQL_AUDIT_GENERAL_ERROR
+ MYSQL_AUDIT_GENERAL_RESULT
+ MYSQL_AUDIT_GENERAL_STATUS
+
+ @param[in] thd
+ @param[in] event_subtype Type of general audit event.
+ @param[in] error_code Error code
+ @param[in] msg Message
+*/
+static inline
+void mysql_audit_general(THD *thd, uint event_subtype,
+ int error_code, const char *msg)
+{
+ if (mysql_audit_general_enabled())
+ {
+ time_t time= my_time(0);
+ uint msglen= msg ? strlen(msg) : 0;
+ const char *user;
+ uint userlen;
+ char user_buff[MAX_USER_HOST_SIZE];
+ CSET_STRING query;
+ ha_rows rows;
+ const char *db;
+ size_t db_length;
+
+ if (thd)
+ {
+ query= thd->query_string;
+ user= user_buff;
+ userlen= make_user_name(thd, user_buff);
+ rows= thd->get_stmt_da()->current_row_for_warning();
+ db= thd->db;
+ db_length= thd->db_length;
+ }
+ else
+ {
+ user= 0;
+ userlen= 0;
+ rows= 0;
+ db= "";
+ db_length= 0;
+ }
+
+ mysql_audit_notify(thd, MYSQL_AUDIT_GENERAL_CLASS, event_subtype,
+ error_code, time, user, userlen, msg, msglen,
+ query.str(), query.length(), query.charset(), rows,
+ db, db_length);
+ }
+}
+
+static inline
+void mysql_audit_notify_connection_connect(THD *thd)
+{
+ if (mysql_audit_connection_enabled())
+ {
+ const Security_context *sctx= thd->security_ctx;
+ Diagnostics_area *da= thd->get_stmt_da();
+ mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS,
+ MYSQL_AUDIT_CONNECTION_CONNECT,
+ da->is_error() ? da->sql_errno() : 0,
+ thd->thread_id,
+ sctx->user, sctx->user ? strlen(sctx->user) : 0,
+ sctx->priv_user, strlen(sctx->priv_user),
+ sctx->external_user,
+ sctx->external_user ? strlen(sctx->external_user) : 0,
+ sctx->proxy_user, strlen(sctx->proxy_user),
+ sctx->host, sctx->host ? strlen(sctx->host) : 0,
+ sctx->ip, sctx->ip ? strlen(sctx->ip) : 0,
+ thd->db, thd->db ? strlen(thd->db) : 0);
+ }
+}
+
+static inline
+void mysql_audit_notify_connection_disconnect(THD *thd, int errcode)
+{
+ if (mysql_audit_connection_enabled())
+ {
+ const Security_context *sctx= thd->security_ctx;
+ mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS,
+ MYSQL_AUDIT_CONNECTION_DISCONNECT,
+ errcode, thd->thread_id,
+ sctx->user, sctx->user ? strlen(sctx->user) : 0,
+ sctx->priv_user, strlen(sctx->priv_user),
+ sctx->external_user,
+ sctx->external_user ? strlen(sctx->external_user) : 0,
+ sctx->proxy_user, strlen(sctx->proxy_user),
+ sctx->host, sctx->host ? strlen(sctx->host) : 0,
+ sctx->ip, sctx->ip ? strlen(sctx->ip) : 0,
+ thd->db, thd->db ? strlen(thd->db) : 0);
+ }
+}
+
+static inline
+void mysql_audit_notify_connection_change_user(THD *thd)
+{
+ if (mysql_audit_connection_enabled())
+ {
+ const Security_context *sctx= thd->security_ctx;
+ Diagnostics_area *da= thd->get_stmt_da();
+ mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS,
+ MYSQL_AUDIT_CONNECTION_CHANGE_USER,
+ da->is_error() ? da->sql_errno() : 0,
+ thd->thread_id,
+ sctx->user, sctx->user ? strlen(sctx->user) : 0,
+ sctx->priv_user, strlen(sctx->priv_user),
+ sctx->external_user,
+ sctx->external_user ? strlen(sctx->external_user) : 0,
+ sctx->proxy_user, strlen(sctx->proxy_user),
+ sctx->host, sctx->host ? strlen(sctx->host) : 0,
+ sctx->ip, sctx->ip ? strlen(sctx->ip) : 0,
+ thd->db, thd->db ? strlen(thd->db) : 0);
+ }
+}
+
+static inline
+void mysql_audit_external_lock(THD *thd, TABLE_SHARE *share, int lock)
+{
+ if (lock != F_UNLCK && mysql_audit_table_enabled())
+ {
+ const Security_context *sctx= thd->security_ctx;
+ mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_LOCK,
+ (int)(lock == F_RDLCK), (ulong)thd->thread_id,
+ sctx->user, sctx->priv_user, sctx->priv_host,
+ sctx->external_user, sctx->proxy_user, sctx->host,
+ sctx->ip, share->db.str, (uint)share->db.length,
+ share->table_name.str, (uint)share->table_name.length,
+ 0,0,0,0);
+ }
+}
+
+static inline
+void mysql_audit_create_table(TABLE *table)
+{
+ if (mysql_audit_table_enabled())
+ {
+ THD *thd= table->in_use;
+ const TABLE_SHARE *share= table->s;
+ const Security_context *sctx= thd->security_ctx;
+ mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_CREATE,
+ 0, (ulong)thd->thread_id,
+ sctx->user, sctx->priv_user, sctx->priv_host,
+ sctx->external_user, sctx->proxy_user, sctx->host,
+ sctx->ip, share->db.str, (uint)share->db.length,
+ share->table_name.str, (uint)share->table_name.length,
+ 0,0,0,0);
+ }
+}
+
+static inline
+void mysql_audit_drop_table(THD *thd, TABLE_LIST *table)
+{
+ if (mysql_audit_table_enabled())
+ {
+ const Security_context *sctx= thd->security_ctx;
+ mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_DROP,
+ 0, (ulong)thd->thread_id,
+ sctx->user, sctx->priv_user, sctx->priv_host,
+ sctx->external_user, sctx->proxy_user, sctx->host,
+ sctx->ip, table->db, (uint)table->db_length,
+ table->table_name, (uint)table->table_name_length,
+ 0,0,0,0);
+ }
+}
+
+static inline
+void mysql_audit_rename_table(THD *thd, const char *old_db, const char *old_tb,
+ const char *new_db, const char *new_tb)
+{
+ if (mysql_audit_table_enabled())
+ {
+ const Security_context *sctx= thd->security_ctx;
+ mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_RENAME,
+ 0, (ulong)thd->thread_id,
+ sctx->user, sctx->priv_user, sctx->priv_host,
+ sctx->external_user, sctx->proxy_user, sctx->host,
+ sctx->ip,
+ old_db, (uint)strlen(old_db), old_tb, (uint)strlen(old_tb),
+ new_db, (uint)strlen(new_db), new_tb, (uint)strlen(new_tb));
+ }
+}
+
+static inline
+void mysql_audit_alter_table(THD *thd, TABLE_LIST *table)
+{
+ if (mysql_audit_table_enabled())
+ {
+ const Security_context *sctx= thd->security_ctx;
+ mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_ALTER,
+ 0, (ulong)thd->thread_id,
+ sctx->user, sctx->priv_user, sctx->priv_host,
+ sctx->external_user, sctx->proxy_user, sctx->host,
+ sctx->ip, table->db, (uint)table->db_length,
+ table->table_name, (uint)table->table_name_length,
+ 0,0,0,0);
+ }
+}
+
+#endif /* SQL_AUDIT_INCLUDED */
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
new file mode 100644
index 00000000000..3731c646b20
--- /dev/null
+++ b/sql/sql_base.cc
@@ -0,0 +1,9567 @@
+/* Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* Basic functions needed by many modules */
+
+#include <my_global.h>
+#include "sql_base.h" // setup_table_map
+#include "sql_priv.h"
+#include "unireg.h"
+#include "debug_sync.h"
+#include "lock.h" // mysql_lock_remove,
+ // mysql_unlock_tables,
+ // mysql_lock_have_duplicate
+#include "sql_show.h" // append_identifier
+#include "strfunc.h" // find_type
+#include "parse_file.h" // sql_parse_prepare, File_parser
+#include "sql_view.h" // mysql_make_view, VIEW_ANY_ACL
+#include "sql_parse.h" // check_table_access
+#include "sql_insert.h" // kill_delayed_threads
+#include "sql_acl.h" // *_ACL, check_grant_all_columns,
+ // check_column_grant_in_table_ref,
+ // get_column_grant
+#include "sql_partition.h" // ALTER_PARTITION_PARAM_TYPE
+#include "sql_derived.h" // mysql_derived_prepare,
+ // mysql_handle_derived,
+ // mysql_derived_filling
+#include "sql_handler.h" // mysql_ha_flush
+#include "sql_test.h"
+#include "sql_partition.h" // ALTER_PARTITION_PARAM_TYPE
+#include "log_event.h" // Query_log_event
+#include "sql_select.h"
+#include "sp_head.h"
+#include "sp.h"
+#include "sp_cache.h"
+#include "sql_trigger.h"
+#include "transaction.h"
+#include "sql_prepare.h"
+#include "sql_statistics.h"
+#include <m_ctype.h>
+#include <my_dir.h>
+#include <hash.h>
+#include "rpl_filter.h"
+#include "sql_table.h" // build_table_filename
+#include "datadict.h" // dd_frm_is_view()
+#include "sql_hset.h" // Hash_set
+#include "rpl_rli.h" // rpl_group_info
+#ifdef __WIN__
+#include <io.h>
+#endif
+
+
+bool
+No_such_table_error_handler::handle_condition(THD *,
+ uint sql_errno,
+ const char*,
+ Sql_condition::enum_warning_level level,
+ const char*,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ if (sql_errno == ER_NO_SUCH_TABLE || sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE)
+ {
+ m_handled_errors++;
+ return TRUE;
+ }
+
+ if (level == Sql_condition::WARN_LEVEL_ERROR)
+ m_unhandled_errors++;
+ return FALSE;
+}
+
+
+bool No_such_table_error_handler::safely_trapped_errors()
+{
+ /*
+ If m_unhandled_errors != 0, something else, unanticipated, happened,
+ so the error is not trapped but returned to the caller.
+ Multiple ER_NO_SUCH_TABLE can be raised in case of views.
+ */
+ return ((m_handled_errors > 0) && (m_unhandled_errors == 0));
+}
+
+
+/**
+ This internal handler is used to trap ER_NO_SUCH_TABLE and
+ ER_WRONG_MRG_TABLE errors during CHECK/REPAIR TABLE for MERGE
+ tables.
+*/
+
+class Repair_mrg_table_error_handler : public Internal_error_handler
+{
+public:
+ Repair_mrg_table_error_handler()
+ : m_handled_errors(false), m_unhandled_errors(false)
+ {}
+
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+
+ /**
+ Returns TRUE if there were ER_NO_SUCH_/WRONG_MRG_TABLE and there
+ were no unhandled errors. FALSE otherwise.
+ */
+ bool safely_trapped_errors()
+ {
+ /*
+ Check for m_handled_errors is here for extra safety.
+ It can be useful in situation when call to open_table()
+ fails because some error which was suppressed by another
+ error handler (e.g. in case of MDL deadlock which we
+ decided to solve by back-off and retry).
+ */
+ return (m_handled_errors && (! m_unhandled_errors));
+ }
+
+private:
+ bool m_handled_errors;
+ bool m_unhandled_errors;
+};
+
+
+bool
+Repair_mrg_table_error_handler::handle_condition(THD *,
+ uint sql_errno,
+ const char*,
+ Sql_condition::enum_warning_level level,
+ const char*,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ if (sql_errno == ER_NO_SUCH_TABLE ||
+ sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE ||
+ sql_errno == ER_WRONG_MRG_TABLE)
+ {
+ m_handled_errors= true;
+ return TRUE;
+ }
+
+ m_unhandled_errors= true;
+ return FALSE;
+}
+
+
+/**
+ @defgroup Data_Dictionary Data Dictionary
+ @{
+*/
+
+static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
+ TABLE_SHARE *table_share);
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry);
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list);
+static bool
+has_write_table_with_auto_increment(TABLE_LIST *tables);
+static bool
+has_write_table_with_auto_increment_and_select(TABLE_LIST *tables);
+static bool has_write_table_auto_increment_not_first_in_pk(TABLE_LIST *tables);
+
+
+/**
+ Create a table cache/table definition cache key
+
+ @param thd Thread context
+ @param key Buffer for the key to be created (must be of
+ size MAX_DBKEY_LENGTH).
+ @param db_name Database name.
+ @param table_name Table name.
+
+ @note
+ The table cache_key is created from:
+ db_name + \0
+ table_name + \0
+
+ additionally we add the following to make each tmp table
+ unique on the slave:
+
+ 4 bytes for master thread id
+ 4 bytes pseudo thread id
+
+ @return Length of key.
+*/
+
+uint create_tmp_table_def_key(THD *thd, char *key,
+ const char *db, const char *table_name)
+{
+ uint key_length= tdc_create_key(key, db, table_name);
+ int4store(key + key_length, thd->variables.server_id);
+ int4store(key + key_length + 4, thd->variables.pseudo_thread_id);
+ key_length+= TMP_TABLE_KEY_EXTRA;
+ return key_length;
+}
+
+
+/**
+ Get table cache key for a table list element.
+
+ @param table_list[in] Table list element.
+ @param key[out] On return points to table cache key for the table.
+
+ @note Unlike create_table_def_key() call this function doesn't construct
+ key in a buffer provider by caller. Instead it relies on the fact
+ that table list element for which key is requested has properly
+ initialized MDL_request object and the fact that table definition
+ cache key is suffix of key used in MDL subsystem. So to get table
+ definition key it simply needs to return pointer to appropriate
+ part of MDL_key object nested in this table list element.
+ Indeed, this means that lifetime of key produced by this call is
+ limited by the lifetime of table list element which it got as
+ parameter.
+
+ @return Length of key.
+*/
+
+uint get_table_def_key(const TABLE_LIST *table_list, const char **key)
+{
+ /*
+ This call relies on the fact that TABLE_LIST::mdl_request::key object
+ is properly initialized, so table definition cache can be produced
+ from key used by MDL subsystem.
+ */
+ DBUG_ASSERT(!strcmp(table_list->get_db_name(),
+ table_list->mdl_request.key.db_name()) &&
+ !strcmp(table_list->get_table_name(),
+ table_list->mdl_request.key.name()));
+
+ *key= (const char*)table_list->mdl_request.key.ptr() + 1;
+ return table_list->mdl_request.key.length() - 1;
+}
+
+
+
+/*****************************************************************************
+ Functions to handle table definition cache (TABLE_SHARE)
+*****************************************************************************/
+
+/*
+ Create a list for all open tables matching SQL expression
+
+ SYNOPSIS
+ list_open_tables()
+ thd Thread THD
+ wild SQL like expression
+
+ NOTES
+ One gets only a list of tables for which one has any kind of privilege.
+ db and table names are allocated in result struct, so one doesn't need
+ a lock when traversing the return list.
+
+ RETURN VALUES
+ NULL Error (Probably OOM)
+ # Pointer to list of names of open tables.
+*/
+
+OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
+{
+ OPEN_TABLE_LIST **start_list, *open_list;
+ TABLE_LIST table_list;
+ TABLE_SHARE *share;
+ TDC_iterator tdc_it;
+ DBUG_ENTER("list_open_tables");
+
+ bzero((char*) &table_list,sizeof(table_list));
+ start_list= &open_list;
+ open_list=0;
+
+ tdc_it.init();
+ while ((share= tdc_it.next()))
+ {
+ if (db && my_strcasecmp(system_charset_info, db, share->db.str))
+ continue;
+ if (wild && wild_compare(share->table_name.str, wild, 0))
+ continue;
+
+ /* Check if user has SELECT privilege for any column in the table */
+ table_list.db= share->db.str;
+ table_list.table_name= share->table_name.str;
+ table_list.grant.privilege=0;
+
+ if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE))
+ continue;
+
+ if (!(*start_list = (OPEN_TABLE_LIST *)
+ sql_alloc(sizeof(**start_list)+share->table_cache_key.length)))
+ {
+ open_list=0; // Out of memory
+ break;
+ }
+ strmov((*start_list)->table=
+ strmov(((*start_list)->db= (char*) ((*start_list)+1)),
+ share->db.str)+1,
+ share->table_name.str);
+ (*start_list)->in_use= 0;
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
+ TABLE *table;
+ while ((table= it++))
+ if (table->in_use)
+ ++(*start_list)->in_use;
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ (*start_list)->locked= 0; /* Obsolete. */
+ start_list= &(*start_list)->next;
+ *start_list=0;
+ }
+ tdc_it.deinit();
+ DBUG_RETURN(open_list);
+}
+
+/*****************************************************************************
+ * Functions to free open table cache
+ ****************************************************************************/
+
+
+void intern_close_table(TABLE *table)
+{ // Free all structures
+ DBUG_ENTER("intern_close_table");
+ DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx",
+ table->s ? table->s->db.str : "?",
+ table->s ? table->s->table_name.str : "?",
+ (long) table));
+
+ free_io_cache(table);
+ delete table->triggers;
+ if (table->file) // Not true if placeholder
+ (void) closefrm(table, 1); // close file
+ table->alias.free();
+ my_free(table);
+ DBUG_VOID_RETURN;
+}
+
+
+/* Free resources allocated by filesort() and read_record() */
+
+void free_io_cache(TABLE *table)
+{
+ DBUG_ENTER("free_io_cache");
+ if (table->sort.io_cache)
+ {
+ close_cached_file(table->sort.io_cache);
+ my_free(table->sort.io_cache);
+ table->sort.io_cache=0;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Auxiliary function which allows to kill delayed threads for
+ particular table identified by its share.
+
+ @param share Table share.
+
+ @pre Caller should have TABLE_SHARE::tdc.LOCK_table_share mutex.
+*/
+
+void kill_delayed_threads_for_table(TABLE_SHARE *share)
+{
+ TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
+ TABLE *tab;
+
+ mysql_mutex_assert_owner(&share->tdc.LOCK_table_share);
+
+ if (!delayed_insert_threads)
+ return;
+
+ while ((tab= it++))
+ {
+ THD *in_use= tab->in_use;
+
+ DBUG_ASSERT(in_use && tab->s->tdc.flushed);
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ ! in_use->killed)
+ {
+ in_use->killed= KILL_SYSTEM_THREAD;
+ mysql_mutex_lock(&in_use->mysys_var->mutex);
+ if (in_use->mysys_var->current_cond)
+ {
+ mysql_mutex_lock(in_use->mysys_var->current_mutex);
+ mysql_cond_broadcast(in_use->mysys_var->current_cond);
+ mysql_mutex_unlock(in_use->mysys_var->current_mutex);
+ }
+ mysql_mutex_unlock(&in_use->mysys_var->mutex);
+ }
+ }
+}
+
+
+/*
+ Close all tables which aren't in use by any thread
+
+ @param thd Thread context
+ @param tables List of tables to remove from the cache
+ @param wait_for_refresh Wait for a impending flush
+ @param timeout Timeout for waiting for flush to be completed.
+
+ @note THD can be NULL, but then wait_for_refresh must be FALSE
+ and tables must be NULL.
+
+ @note When called as part of FLUSH TABLES WITH READ LOCK this function
+ ignores metadata locks held by other threads. In order to avoid
+ situation when FLUSH TABLES WITH READ LOCK sneaks in at the moment
+ when some write-locked table is being reopened (by FLUSH TABLES or
+ ALTER TABLE) we have to rely on additional global shared metadata
+ lock taken by thread trying to obtain global read lock.
+*/
+
+bool close_cached_tables(THD *thd, TABLE_LIST *tables,
+ bool wait_for_refresh, ulong timeout)
+{
+ bool result= FALSE;
+ struct timespec abstime;
+ ulong refresh_version;
+ DBUG_ENTER("close_cached_tables");
+ DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
+
+ refresh_version= tdc_increment_refresh_version();
+
+ if (!tables)
+ {
+ /*
+ Force close of all open tables.
+
+ Note that code in TABLE_SHARE::wait_for_old_version() assumes that
+ incrementing of refresh_version is followed by purge of unused table
+ shares.
+ */
+ kill_delayed_threads();
+ /*
+ Get rid of all unused TABLE and TABLE_SHARE instances. By doing
+ this we automatically close all tables which were marked as "old".
+ */
+ tc_purge(true);
+ /* Free table shares which were not freed implicitly by loop above. */
+ tdc_purge(true);
+ }
+ else
+ {
+ bool found=0;
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
+ {
+ /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */
+ found|= tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db,
+ table->table_name, TRUE);
+ }
+ if (!found)
+ wait_for_refresh=0; // Nothing to wait for
+ }
+
+ DBUG_PRINT("info", ("open table definitions: %d",
+ (int) tdc_records()));
+
+ if (!wait_for_refresh)
+ DBUG_RETURN(result);
+
+ if (thd->locked_tables_mode)
+ {
+ /*
+ If we are under LOCK TABLES, we need to reopen the tables without
+ opening a door for any concurrent threads to sneak in and get
+ lock on our tables. To achieve this we use exclusive metadata
+ locks.
+ */
+ TABLE_LIST *tables_to_reopen= (tables ? tables :
+ thd->locked_tables_list.locked_tables());
+
+ /* Close open HANDLER instances to avoid self-deadlock. */
+ mysql_ha_flush_tables(thd, tables_to_reopen);
+
+ for (TABLE_LIST *table_list= tables_to_reopen; table_list;
+ table_list= table_list->next_global)
+ {
+ /* A check that the table was locked for write is done by the caller. */
+ TABLE *table= find_table_for_mdl_upgrade(thd, table_list->db,
+ table_list->table_name, TRUE);
+
+ /* May return NULL if this table has already been closed via an alias. */
+ if (! table)
+ continue;
+
+ if (wait_while_table_is_used(thd, table,
+ HA_EXTRA_PREPARE_FOR_FORCED_CLOSE))
+ {
+ result= TRUE;
+ goto err_with_reopen;
+ }
+ close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
+ }
+ }
+
+ /* Wait until all threads have closed all the tables we are flushing. */
+ DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
+
+ /*
+ To a self-deadlock or deadlocks with other FLUSH threads
+ waiting on our open HANDLERs, we have to flush them.
+ */
+ mysql_ha_flush(thd);
+ DEBUG_SYNC(thd, "after_flush_unlock");
+
+ if (!tables)
+ {
+ bool found= true;
+ set_timespec(abstime, timeout);
+ while (found && !thd->killed)
+ {
+ TABLE_SHARE *share;
+ TDC_iterator tdc_it;
+ found= false;
+
+ tdc_it.init();
+ while ((share= tdc_it.next()))
+ {
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ if (share->tdc.flushed && share->tdc.version < refresh_version)
+ {
+ /* wait_for_old_version() will unlock mutex and free share */
+ found= true;
+ break;
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ }
+ tdc_it.deinit();
+
+ if (found)
+ {
+ if (share->wait_for_old_version(thd, &abstime,
+ MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
+ {
+ result= TRUE;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
+ {
+ if (thd->killed)
+ break;
+ if (tdc_wait_for_old_version(thd, table->db, table->table_name, timeout,
+ MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL,
+ refresh_version))
+ {
+ result= TRUE;
+ break;
+ }
+ }
+ }
+
+err_with_reopen:
+ if (thd->locked_tables_mode)
+ {
+ /*
+ No other thread has the locked tables open; reopen them and get the
+ old locks. This should always succeed (unless some external process
+ has removed the tables)
+ */
+ thd->locked_tables_list.reopen_tables(thd);
+ /*
+ Since downgrade_lock() won't do anything with shared
+ metadata lock it is much simpler to go through all open tables rather
+ than picking only those tables that were flushed.
+ */
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+ tab->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ }
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Close all tables which match specified connection string or
+ if specified string is NULL, then any table with a connection string.
+*/
+
+bool close_cached_connection_tables(THD *thd, LEX_STRING *connection)
+{
+ TABLE_LIST tmp, *tables= NULL;
+ bool result= FALSE;
+ TABLE_SHARE *share;
+ TDC_iterator tdc_it;
+ DBUG_ENTER("close_cached_connections");
+ DBUG_ASSERT(thd);
+
+ bzero(&tmp, sizeof(TABLE_LIST));
+
+ tdc_it.init();
+ while ((share= tdc_it.next()))
+ {
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ /* Ignore if table is not open or does not have a connect_string */
+ if (!share->connect_string.length || !share->tdc.ref_count)
+ {
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ continue;
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+
+ /* Compare the connection string */
+ if (connection &&
+ (connection->length > share->connect_string.length ||
+ (connection->length < share->connect_string.length &&
+ (share->connect_string.str[connection->length] != '/' &&
+ share->connect_string.str[connection->length] != '\\')) ||
+ strncasecmp(connection->str, share->connect_string.str,
+ connection->length)))
+ continue;
+
+ /* close_cached_tables() only uses these elements */
+ tmp.db= share->db.str;
+ tmp.table_name= share->table_name.str;
+ tmp.next_local= tables;
+
+ tables= (TABLE_LIST *) memdup_root(thd->mem_root, (char*)&tmp,
+ sizeof(TABLE_LIST));
+ }
+ tdc_it.deinit();
+
+ if (tables)
+ result= close_cached_tables(thd, tables, FALSE, LONG_TIMEOUT);
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Mark all temporary tables which were used by the current statement or
+ substatement as free for reuse, but only if the query_id can be cleared.
+
+ @param thd thread context
+
+ @remark For temp tables associated with a open SQL HANDLER the query_id
+ is not reset until the HANDLER is closed.
+*/
+
+static void mark_temp_tables_as_free_for_reuse(THD *thd)
+{
+ rpl_group_info *rgi_slave;
+ DBUG_ENTER("mark_temp_tables_as_free_for_reuse");
+
+ if (thd->query_id == 0)
+ {
+ /* Thread has not executed any statement and has not used any tmp tables */
+ DBUG_VOID_RETURN;
+ }
+
+ rgi_slave=thd->rgi_slave;
+ if ((!rgi_slave && thd->temporary_tables) ||
+ (rgi_slave && unlikely(rgi_slave->rli->save_temporary_tables)))
+ {
+ thd->lock_temporary_tables();
+ for (TABLE *table= thd->temporary_tables ; table ; table= table->next)
+ {
+ if ((table->query_id == thd->query_id) && ! table->open_by_handler)
+ mark_tmp_table_for_reuse(table);
+ }
+ thd->unlock_temporary_tables();
+ if (rgi_slave)
+ {
+ /*
+ Temporary tables are shared with other by sql execution threads.
+ As a safety messure, clear the pointer to the common area.
+ */
+ thd->temporary_tables= 0;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Reset a single temporary table.
+ Effectively this "closes" one temporary table,
+ in a session.
+
+ @param table Temporary table.
+*/
+
+void mark_tmp_table_for_reuse(TABLE *table)
+{
+ DBUG_ENTER("mark_tmp_table_for_reuse");
+ DBUG_ASSERT(table->s->tmp_table);
+
+ table->query_id= 0;
+ table->file->ha_reset();
+
+ /* Detach temporary MERGE children from temporary parent. */
+ DBUG_ASSERT(table->file);
+ table->file->extra(HA_EXTRA_DETACH_CHILDREN);
+
+ /*
+ Reset temporary table lock type to it's default value (TL_WRITE).
+
+ Statements such as INSERT INTO .. SELECT FROM tmp, CREATE TABLE
+ .. SELECT FROM tmp and UPDATE may under some circumstances modify
+ the lock type of the tables participating in the statement. This
+ isn't a problem for non-temporary tables since their lock type is
+ reset at every open, but the same does not occur for temporary
+ tables for historical reasons.
+
+ Furthermore, the lock type of temporary tables is not really that
+ important because they can only be used by one query at a time and
+ not even twice in a query -- a temporary table is represented by
+ only one TABLE object. Nonetheless, it's safer from a maintenance
+ point of view to reset the lock type of this singleton TABLE object
+ as to not cause problems when the table is reused.
+
+ Even under LOCK TABLES mode its okay to reset the lock type as
+ LOCK TABLES is allowed (but ignored) for a temporary table.
+ */
+ table->reginfo.lock_type= TL_WRITE;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Mark all tables in the list which were used by current substatement
+ as free for reuse.
+
+ SYNOPSIS
+ mark_used_tables_as_free_for_reuse()
+ thd - thread context
+ table - head of the list of tables
+
+ DESCRIPTION
+ Marks all tables in the list which were used by current substatement
+ (they are marked by its query_id) as free for reuse.
+
+ NOTE
+ The reason we reset query_id is that it's not enough to just test
+ if table->query_id != thd->query_id to know if a table is in use.
+
+ For example
+ SELECT f1_that_uses_t1() FROM t1;
+ In f1_that_uses_t1() we will see one instance of t1 where query_id is
+ set to query_id of original query.
+*/
+
+static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table)
+{
+ for (; table ; table= table->next)
+ {
+ DBUG_ASSERT(table->pos_in_locked_tables == NULL ||
+ table->pos_in_locked_tables->table == table);
+ if (table->query_id == thd->query_id)
+ {
+ table->query_id= 0;
+ table->file->ha_reset();
+ }
+ }
+}
+
+
+/**
+ Auxiliary function to close all tables in the open_tables list.
+
+ @param thd Thread context.
+
+ @remark It should not ordinarily be called directly.
+*/
+
+static void close_open_tables(THD *thd)
+{
+ DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables));
+
+ while (thd->open_tables)
+ (void) close_thread_table(thd, &thd->open_tables);
+}
+
+
+/**
+ Close all open instances of the table but keep the MDL lock.
+
+ Works both under LOCK TABLES and in the normal mode.
+ Removes all closed instances of the table from the table cache.
+
+ @param thd thread handle
+ @param[in] share table share, but is just a handy way to
+ access the table cache key
+
+ @param[in] extra
+ HA_EXTRA_PREPARE_FOR_DROP
+ - The table is dropped
+ HA_EXTRA_PREPARE_FOR_RENAME
+ - The table is renamed
+ HA_EXTRA_NOT_USED
+ - The table is marked as closed in the
+ locked_table_list but kept there so one can call
+ locked_table_list->reopen_tables() to put it back.
+
+ In case of drop/rename the documented behavior is to
+ implicitly remove the table from LOCK TABLES
+ list.
+
+ @pre Must be called with an X MDL lock on the table.
+*/
+
+void
+close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
+ ha_extra_function extra,
+ TABLE *skip_table)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length= share->table_cache_key.length;
+ const char *db= key;
+ const char *table_name= db + share->db.length + 1;
+
+ memcpy(key, share->table_cache_key.str, key_length);
+
+ for (TABLE **prev= &thd->open_tables; *prev; )
+ {
+ TABLE *table= *prev;
+
+ if (table->s->table_cache_key.length == key_length &&
+ !memcmp(table->s->table_cache_key.str, key, key_length) &&
+ table != skip_table)
+ {
+ thd->locked_tables_list.unlink_from_list(thd,
+ table->pos_in_locked_tables,
+ extra != HA_EXTRA_NOT_USED);
+ /* Inform handler that there is a drop table or a rename going on */
+ if (extra != HA_EXTRA_NOT_USED && table->db_stat)
+ {
+ table->file->extra(extra);
+ extra= HA_EXTRA_NOT_USED; // Call extra once!
+ }
+
+ /*
+ Does nothing if the table is not locked.
+ This allows one to use this function after a table
+ has been unlocked, e.g. in partition management.
+ */
+ mysql_lock_remove(thd, thd->lock, table);
+ close_thread_table(thd, prev);
+ }
+ else
+ {
+ /* Step to next entry in open_tables list. */
+ prev= &table->next;
+ }
+ }
+ if (skip_table == NULL)
+ {
+ /* Remove the table share from the cache. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name,
+ FALSE);
+ }
+}
+
+
+/*
+ Close all tables used by the current substatement, or all tables
+ used by this thread if we are on the upper level.
+
+ SYNOPSIS
+ close_thread_tables()
+ thd Thread handler
+
+ IMPLEMENTATION
+ Unlocks tables and frees derived tables.
+ Put all normal tables used by thread in free list.
+
+ It will only close/mark as free for reuse tables opened by this
+ substatement, it will also check if we are closing tables after
+ execution of complete query (i.e. we are on upper level) and will
+ leave prelocked mode if needed.
+*/
+
+void close_thread_tables(THD *thd)
+{
+ TABLE *table;
+ DBUG_ENTER("close_thread_tables");
+
+ THD_STAGE_INFO(thd, stage_closing_tables);
+
+#ifdef EXTRA_DEBUG
+ DBUG_PRINT("tcache", ("open tables:"));
+ for (table= thd->open_tables; table; table= table->next)
+ DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+ table->s->table_name.str, (long) table));
+#endif
+
+#if defined(ENABLED_DEBUG_SYNC)
+ /* debug_sync may not be initialized for some slave threads */
+ if (thd->debug_sync_control)
+ DEBUG_SYNC(thd, "before_close_thread_tables");
+#endif
+
+ DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt ||
+ (thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+
+ /* Detach MERGE children after every statement. Even under LOCK TABLES. */
+ for (table= thd->open_tables; table; table= table->next)
+ {
+ /* Table might be in use by some outer statement. */
+ DBUG_PRINT("tcache", ("table: '%s' query_id: %lu",
+ table->s->table_name.str, (ulong) table->query_id));
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES ||
+ table->query_id == thd->query_id)
+ {
+ DBUG_ASSERT(table->file);
+ table->file->extra(HA_EXTRA_DETACH_CHILDREN);
+ }
+ }
+
+ /*
+ We are assuming here that thd->derived_tables contains ONLY derived
+ tables for this substatement. i.e. instead of approach which uses
+ query_id matching for determining which of the derived tables belong
+ to this substatement we rely on the ability of substatements to
+ save/restore thd->derived_tables during their execution.
+
+ TODO: Probably even better approach is to simply associate list of
+ derived tables with (sub-)statement instead of thread and destroy
+ them at the end of its execution.
+ */
+ if (thd->derived_tables)
+ {
+ TABLE *next;
+ /*
+ Close all derived tables generated in queries like
+ SELECT * FROM (SELECT * FROM t1)
+ */
+ for (table= thd->derived_tables ; table ; table= next)
+ {
+ next= table->next;
+ free_tmp_table(thd, table);
+ }
+ thd->derived_tables= 0;
+ }
+
+ /*
+ Mark all temporary tables used by this statement as free for reuse.
+ */
+ mark_temp_tables_as_free_for_reuse(thd);
+
+ if (thd->locked_tables_mode)
+ {
+
+ /* Ensure we are calling ha_reset() for all used tables */
+ mark_used_tables_as_free_for_reuse(thd, thd->open_tables);
+
+ /*
+ We are under simple LOCK TABLES or we're inside a sub-statement
+ of a prelocked statement, so should not do anything else.
+
+ Note that even if we are in LTM_LOCK_TABLES mode and statement
+ requires prelocking (e.g. when we are closing tables after
+ failing ot "open" all tables required for statement execution)
+ we will exit this function a few lines below.
+ */
+ if (! thd->lex->requires_prelocking())
+ DBUG_VOID_RETURN;
+
+ /*
+ We are in the top-level statement of a prelocked statement,
+ so we have to leave the prelocked mode now with doing implicit
+ UNLOCK TABLES if needed.
+ */
+ if (thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
+ thd->locked_tables_mode= LTM_LOCK_TABLES;
+
+ if (thd->locked_tables_mode == LTM_LOCK_TABLES)
+ DBUG_VOID_RETURN;
+
+ thd->leave_locked_tables_mode();
+
+ /* Fallthrough */
+ }
+
+ if (thd->lock)
+ {
+ /*
+ For RBR we flush the pending event just before we unlock all the
+ tables. This means that we are at the end of a topmost
+ statement, so we ensure that the STMT_END_F flag is set on the
+ pending event. For statements that are *inside* stored
+ functions, the pending event will not be flushed: that will be
+ handled either before writing a query log event (inside
+ binlog_query()) or when preparing a pending event.
+ */
+ (void)thd->binlog_flush_pending_rows_event(TRUE);
+ mysql_unlock_tables(thd, thd->lock);
+ thd->lock=0;
+ }
+ /*
+ Closing a MERGE child before the parent would be fatal if the
+ other thread tries to abort the MERGE lock in between.
+ */
+ if (thd->open_tables)
+ close_open_tables(thd);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/* move one table to free list */
+
+void close_thread_table(THD *thd, TABLE **table_ptr)
+{
+ TABLE *table= *table_ptr;
+ DBUG_ENTER("close_thread_table");
+ DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+ table->s->table_name.str, (long) table));
+ DBUG_ASSERT(table->key_read == 0);
+ DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
+
+ /*
+ The metadata lock must be released after giving back
+ the table to the table cache.
+ */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ table->s->db.str,
+ table->s->table_name.str,
+ MDL_SHARED));
+ table->mdl_ticket= NULL;
+
+ if (table->file)
+ {
+ table->file->update_global_table_stats();
+ table->file->update_global_index_stats();
+ }
+
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+ *table_ptr=table->next;
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+
+ if (! table->needs_reopen())
+ {
+ /* Avoid having MERGE tables with attached children in table cache. */
+ table->file->extra(HA_EXTRA_DETACH_CHILDREN);
+ /* Free memory and reset for next loop. */
+ free_field_buffers_larger_than(table, MAX_TDC_BLOB_SIZE);
+ table->file->ha_reset();
+ }
+
+ /*
+ Do this *before* entering the TABLE_SHARE::tdc.LOCK_table_share
+ critical section.
+ */
+ if (table->file != NULL)
+ table->file->unbind_psi();
+
+ tc_release_table(table);
+ DBUG_VOID_RETURN;
+}
+
+
+/* close_temporary_tables' internal, 4 is due to uint4korr definition */
+static inline uint tmpkeyval(THD *thd, TABLE *table)
+{
+ return uint4korr(table->s->table_cache_key.str + table->s->table_cache_key.length - 4);
+}
+
+
+/*
+ Close all temporary tables created by 'CREATE TEMPORARY TABLE' for thread
+ creates one DROP TEMPORARY TABLE binlog event for each pseudo-thread
+
+ Temporary tables created in a sql slave is closed by
+ Relay_log_info::close_temporary_tables()
+
+*/
+
+bool close_temporary_tables(THD *thd)
+{
+ DBUG_ENTER("close_temporary_tables");
+ TABLE *table;
+ TABLE *next= NULL;
+ TABLE *prev_table;
+ /* Assume thd->variables.option_bits has OPTION_QUOTE_SHOW_CREATE */
+ bool was_quote_show= TRUE;
+ bool error= 0;
+
+ if (!thd->temporary_tables)
+ DBUG_RETURN(FALSE);
+ DBUG_ASSERT(!thd->rgi_slave);
+
+ if (!mysql_bin_log.is_open())
+ {
+ TABLE *tmp_next;
+ for (table= thd->temporary_tables; table; table= tmp_next)
+ {
+ tmp_next= table->next;
+ close_temporary(table, 1, 1);
+ }
+ thd->temporary_tables= 0;
+ DBUG_RETURN(FALSE);
+ }
+
+ /* Better add "if exists", in case a RESET MASTER has been done */
+ const char stub[]= "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS ";
+ char buf[FN_REFLEN];
+ String s_query(buf, sizeof(buf), system_charset_info);
+ bool found_user_tables= FALSE;
+
+ s_query.copy(stub, sizeof(stub)-1, system_charset_info);
+
+ /*
+ Insertion sort of temp tables by pseudo_thread_id to build ordered list
+ of sublists of equal pseudo_thread_id
+ */
+
+ for (prev_table= thd->temporary_tables, table= prev_table->next;
+ table;
+ prev_table= table, table= table->next)
+ {
+ TABLE *prev_sorted /* same as for prev_table */, *sorted;
+ if (is_user_table(table))
+ {
+ if (!found_user_tables)
+ found_user_tables= true;
+ for (prev_sorted= NULL, sorted= thd->temporary_tables; sorted != table;
+ prev_sorted= sorted, sorted= sorted->next)
+ {
+ if (!is_user_table(sorted) ||
+ tmpkeyval(thd, sorted) > tmpkeyval(thd, table))
+ {
+ /* move into the sorted part of the list from the unsorted */
+ prev_table->next= table->next;
+ table->next= sorted;
+ if (prev_sorted)
+ {
+ prev_sorted->next= table;
+ }
+ else
+ {
+ thd->temporary_tables= table;
+ }
+ table= prev_table;
+ break;
+ }
+ }
+ }
+ }
+
+ /* We always quote db,table names though it is slight overkill */
+ if (found_user_tables &&
+ !(was_quote_show= MY_TEST(thd->variables.option_bits &
+ OPTION_QUOTE_SHOW_CREATE)))
+ {
+ thd->variables.option_bits |= OPTION_QUOTE_SHOW_CREATE;
+ }
+
+ /* scan sorted tmps to generate sequence of DROP */
+ for (table= thd->temporary_tables; table; table= next)
+ {
+ if (is_user_table(table))
+ {
+ bool save_thread_specific_used= thd->thread_specific_used;
+ my_thread_id save_pseudo_thread_id= thd->variables.pseudo_thread_id;
+ char db_buf[FN_REFLEN];
+ String db(db_buf, sizeof(db_buf), system_charset_info);
+
+ /* Set pseudo_thread_id to be that of the processed table */
+ thd->variables.pseudo_thread_id= tmpkeyval(thd, table);
+
+ db.copy(table->s->db.str, table->s->db.length, system_charset_info);
+ /* Reset s_query() if changed by previous loop */
+ s_query.length(sizeof(stub)-1);
+
+ /* Loop forward through all tables that belong to a common database
+ within the sublist of common pseudo_thread_id to create single
+ DROP query
+ */
+ for (;
+ table && is_user_table(table) &&
+ tmpkeyval(thd, table) == thd->variables.pseudo_thread_id &&
+ table->s->db.length == db.length() &&
+ memcmp(table->s->db.str, db.ptr(), db.length()) == 0;
+ table= next)
+ {
+ /*
+ We are going to add ` around the table names and possible more
+ due to special characters
+ */
+ append_identifier(thd, &s_query, table->s->table_name.str,
+ strlen(table->s->table_name.str));
+ s_query.append(',');
+ next= table->next;
+ close_temporary(table, 1, 1);
+ }
+ thd->clear_error();
+ CHARSET_INFO *cs_save= thd->variables.character_set_client;
+ thd->variables.character_set_client= system_charset_info;
+ thd->thread_specific_used= TRUE;
+ Query_log_event qinfo(thd, s_query.ptr(),
+ s_query.length() - 1 /* to remove trailing ',' */,
+ FALSE, TRUE, FALSE, 0);
+ qinfo.db= db.ptr();
+ qinfo.db_len= db.length();
+ thd->variables.character_set_client= cs_save;
+
+ thd->get_stmt_da()->set_overwrite_status(true);
+ if ((error= (mysql_bin_log.write(&qinfo) || error)))
+ {
+ /*
+ If we're here following THD::cleanup, thence the connection
+ has been closed already. So lets print a message to the
+ error log instead of pushing yet another error into the
+ stmt_da.
+
+ Also, we keep the error flag so that we propagate the error
+ up in the stack. This way, if we're the SQL thread we notice
+ that close_temporary_tables failed. (Actually, the SQL
+ thread only calls close_temporary_tables while applying old
+ Start_log_event_v3 events.)
+ */
+ sql_print_error("Failed to write the DROP statement for "
+ "temporary tables to binary log");
+ }
+ thd->get_stmt_da()->set_overwrite_status(false);
+
+ thd->variables.pseudo_thread_id= save_pseudo_thread_id;
+ thd->thread_specific_used= save_thread_specific_used;
+ }
+ else
+ {
+ next= table->next;
+ close_temporary(table, 1, 1);
+ }
+ }
+ if (!was_quote_show)
+ thd->variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE; /* restore option */
+ thd->temporary_tables=0;
+
+ DBUG_RETURN(error);
+}
+
+/*
+ Find table in list.
+
+ SYNOPSIS
+ find_table_in_list()
+ table Pointer to table list
+ offset Offset to which list in table structure to use
+ db_name Data base name
+ table_name Table name
+
+ NOTES:
+ This is called by find_table_in_local_list() and
+ find_table_in_global_list().
+
+ RETURN VALUES
+ NULL Table not found
+ # Pointer to found table.
+*/
+
+TABLE_LIST *find_table_in_list(TABLE_LIST *table,
+ TABLE_LIST *TABLE_LIST::*link,
+ const char *db_name,
+ const char *table_name)
+{
+ for (; table; table= table->*link )
+ {
+ if ((table->table == 0 || table->table->s->tmp_table == NO_TMP_TABLE) &&
+ strcmp(table->db, db_name) == 0 &&
+ strcmp(table->table_name, table_name) == 0)
+ break;
+ }
+ return table;
+}
+
+
+/**
+ Test that table is unique (It's only exists once in the table list)
+
+ @param thd thread handle
+ @param table table which should be checked
+ @param table_list list of tables
+ @param check_alias whether to check tables' aliases
+
+ NOTE: to exclude derived tables from check we use following mechanism:
+ a) during derived table processing set THD::derived_tables_processing
+ b) JOIN::prepare set SELECT::exclude_from_table_unique_test if
+ THD::derived_tables_processing set. (we can't use JOIN::execute
+ because for PS we perform only JOIN::prepare, but we can't set this
+ flag in JOIN::prepare if we are not sure that we are in derived table
+ processing loop, because multi-update call fix_fields() for some its
+ items (which mean JOIN::prepare for subqueries) before unique_table
+ call to detect which tables should be locked for write).
+ c) find_dup_table skip all tables which belong to SELECT with
+ SELECT::exclude_from_table_unique_test set.
+ Also SELECT::exclude_from_table_unique_test used to exclude from check
+ tables of main SELECT of multi-delete and multi-update
+
+ We also skip tables with TABLE_LIST::prelocking_placeholder set,
+ because we want to allow SELECTs from them, and their modification
+ will rise the error anyway.
+
+ TODO: when we will have table/view change detection we can do this check
+ only once for PS/SP
+
+ @retval !=0 found duplicate
+ @retval 0 if table is unique
+*/
+
+static
+TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
+ bool check_alias)
+{
+ TABLE_LIST *res;
+ const char *d_name, *t_name, *t_alias;
+ DBUG_ENTER("find_dup_table");
+ DBUG_PRINT("enter", ("table alias: %s", table->alias));
+
+ /*
+ If this function called for query which update table (INSERT/UPDATE/...)
+ then we have in table->table pointer to TABLE object which we are
+ updating even if it is VIEW so we need TABLE_LIST of this TABLE object
+ to get right names (even if lower_case_table_names used).
+
+ If this function called for CREATE command that we have not opened table
+ (table->table equal to 0) and right names is in current TABLE_LIST
+ object.
+ */
+ if (table->table)
+ {
+ /* All MyISAMMRG children are plain MyISAM tables. */
+ DBUG_ASSERT(table->table->file->ht->db_type != DB_TYPE_MRG_MYISAM);
+
+ /* temporary table is always unique */
+ if (table->table && table->table->s->tmp_table != NO_TMP_TABLE)
+ DBUG_RETURN(0);
+ table= table->find_underlying_table(table->table);
+ /*
+ as far as we have table->table we have to find real TABLE_LIST of
+ it in underlying tables
+ */
+ DBUG_ASSERT(table);
+ }
+ d_name= table->db;
+ t_name= table->table_name;
+ t_alias= table->alias;
+
+retry:
+ DBUG_PRINT("info", ("real table: %s.%s", d_name, t_name));
+ for (TABLE_LIST *tl= table_list;;)
+ {
+ if (tl &&
+ tl->select_lex && tl->select_lex->master_unit() &&
+ tl->select_lex->master_unit()->executed)
+ {
+ /*
+ There is no sense to check tables of already executed parts
+ of the query
+ */
+ tl= tl->next_global;
+ continue;
+ }
+ /*
+ Table is unique if it is present only once in the global list
+ of tables and once in the list of table locks.
+ */
+ if (! (res= find_table_in_global_list(tl, d_name, t_name)))
+ break;
+
+ /* Skip if same underlying table. */
+ if (res->table && (res->table == table->table))
+ goto next;
+
+ /* Skip if table alias does not match. */
+ if (check_alias)
+ {
+ if (my_strcasecmp(table_alias_charset, t_alias, res->alias))
+ goto next;
+ }
+
+ /*
+ Skip if marked to be excluded (could be a derived table) or if
+ entry is a prelocking placeholder.
+ */
+ if (res->select_lex &&
+ !res->select_lex->exclude_from_table_unique_test &&
+ !res->prelocking_placeholder)
+ break;
+
+ /*
+ If we found entry of this table or table of SELECT which already
+ processed in derived table or top select of multi-update/multi-delete
+ (exclude_from_table_unique_test) or prelocking placeholder.
+ */
+next:
+ tl= res->next_global;
+ DBUG_PRINT("info",
+ ("found same copy of table or table which we should skip"));
+ }
+ if (res && res->belong_to_derived)
+ {
+ /* Try to fix */
+ TABLE_LIST *derived= res->belong_to_derived;
+ if (derived->is_merged_derived())
+ {
+ DBUG_PRINT("info",
+ ("convert merged to materialization to resolve the conflict"));
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ goto retry;
+ }
+ }
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Test that the subject table of INSERT/UPDATE/DELETE/CREATE
+ or (in case of MyISAMMRG) one of its children are not used later
+ in the query.
+
+ For MyISAMMRG tables, it is assumed that all the underlying
+ tables of @c table (if any) are listed right after it and that
+ their @c parent_l field points at the main table.
+
+
+ @retval non-NULL The table list element for the table that
+ represents the duplicate.
+ @retval NULL No duplicates found.
+*/
+
+TABLE_LIST*
+unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
+ bool check_alias)
+{
+ TABLE_LIST *dup;
+
+ table= table->find_table_for_update();
+
+ if (table->table && table->table->file->ht->db_type == DB_TYPE_MRG_MYISAM)
+ {
+ TABLE_LIST *child;
+ dup= NULL;
+ /* Check duplicates of all merge children. */
+ for (child= table->next_global; child && child->parent_l == table;
+ child= child->next_global)
+ {
+ if ((dup= find_dup_table(thd, child, child->next_global, check_alias)))
+ break;
+ }
+ }
+ else
+ dup= find_dup_table(thd, table, table_list, check_alias);
+ return dup;
+}
+/*
+ Issue correct error message in case we found 2 duplicate tables which
+ prevent some update operation
+
+ SYNOPSIS
+ update_non_unique_table_error()
+ update table which we try to update
+ operation name of update operation
+ duplicate duplicate table which we found
+
+ NOTE:
+ here we hide view underlying tables if we have them
+*/
+
+void update_non_unique_table_error(TABLE_LIST *update,
+ const char *operation,
+ TABLE_LIST *duplicate)
+{
+ update= update->top_table();
+ duplicate= duplicate->top_table();
+ if (!update->view || !duplicate->view ||
+ update->view == duplicate->view ||
+ update->view_name.length != duplicate->view_name.length ||
+ update->view_db.length != duplicate->view_db.length ||
+ my_strcasecmp(table_alias_charset,
+ update->view_name.str, duplicate->view_name.str) != 0 ||
+ my_strcasecmp(table_alias_charset,
+ update->view_db.str, duplicate->view_db.str) != 0)
+ {
+ /*
+ it is not the same view repeated (but it can be parts of the same copy
+ of view), so we have to hide underlying tables.
+ */
+ if (update->view)
+ {
+ /* Issue the ER_NON_INSERTABLE_TABLE error for an INSERT */
+ if (update->view == duplicate->view)
+ my_error(!strncmp(operation, "INSERT", 6) ?
+ ER_NON_INSERTABLE_TABLE : ER_NON_UPDATABLE_TABLE, MYF(0),
+ update->alias, operation);
+ else
+ my_error(ER_VIEW_PREVENT_UPDATE, MYF(0),
+ (duplicate->view ? duplicate->alias : update->alias),
+ operation, update->alias);
+ return;
+ }
+ if (duplicate->view)
+ {
+ my_error(ER_VIEW_PREVENT_UPDATE, MYF(0), duplicate->alias, operation,
+ update->alias);
+ return;
+ }
+ }
+ my_error(ER_UPDATE_TABLE_USED, MYF(0), update->alias, operation);
+}
+
+
+/**
+ Find temporary table specified by database and table names in the
+ THD::temporary_tables list.
+
+ @return TABLE instance if a temporary table has been found; NULL otherwise.
+*/
+
+TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length= create_tmp_table_def_key(thd, key, db, table_name);
+ return find_temporary_table(thd, key, key_length);
+}
+
+
+/**
+ Find a temporary table specified by TABLE_LIST instance in the
+ THD::temporary_tables list.
+
+ @return TABLE instance if a temporary table has been found; NULL otherwise.
+*/
+
+TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl)
+{
+ const char *tmp_key;
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+
+ key_length= get_table_def_key(tl, &tmp_key);
+ memcpy(key, tmp_key, key_length);
+ int4store(key + key_length, thd->variables.server_id);
+ int4store(key + key_length + 4, thd->variables.pseudo_thread_id);
+
+ return find_temporary_table(thd, key, key_length + TMP_TABLE_KEY_EXTRA);
+}
+
+
+static bool
+use_temporary_table(THD *thd, TABLE *table, TABLE **out_table)
+{
+ *out_table= table;
+ if (!table)
+ return false;
+ /*
+ Temporary tables are not safe for parallel replication. They were
+ designed to be visible to one thread only, so have no table locking.
+ Thus there is no protection against two conflicting transactions
+ committing in parallel and things like that.
+
+ So for now, anything that uses temporary tables will be serialised
+ with anything before it, when using parallel replication.
+
+ ToDo: We might be able to introduce a reference count or something
+ on temp tables, and have slave worker threads wait for it to reach
+ zero before being allowed to use the temp table. Might not be worth
+ it though, as statement-based replication using temporary tables is
+ in any case rather fragile.
+ */
+ if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
+ thd->wait_for_prior_commit())
+ return true;
+ /*
+ We need to set the THD as it may be different in case of
+ parallel replication
+ */
+ if (table->in_use != thd)
+ {
+ table->in_use= thd;
+#ifdef REMOVE_AFTER_MERGE_WITH_10
+ if (thd->rgi_slave)
+ {
+ /*
+ We may be stealing an opened temporary tables from one slave
+ thread to another, we need to let the performance schema know that,
+ for aggregates per thread to work properly.
+ */
+ table->file->unbind_psi();
+ table->file->rebind_psi();
+ }
+#endif
+ }
+ return false;
+}
+
+bool
+find_and_use_temporary_table(THD *thd, const char *db, const char *table_name,
+ TABLE **out_table)
+{
+ return use_temporary_table(thd, find_temporary_table(thd, db, table_name),
+ out_table);
+}
+
+
+bool
+find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl, TABLE **out_table)
+{
+ return use_temporary_table(thd, find_temporary_table(thd, tl), out_table);
+}
+
+
+/**
+ Find a temporary table specified by a key in the THD::temporary_tables list.
+
+ @return TABLE instance if a temporary table has been found; NULL otherwise.
+*/
+
+TABLE *find_temporary_table(THD *thd,
+ const char *table_key,
+ uint table_key_length)
+{
+ TABLE *result= 0;
+ if (!thd->have_temporary_tables())
+ return NULL;
+
+ thd->lock_temporary_tables();
+ for (TABLE *table= thd->temporary_tables; table; table= table->next)
+ {
+ if (table->s->table_cache_key.length == table_key_length &&
+ !memcmp(table->s->table_cache_key.str, table_key, table_key_length))
+ {
+ result= table;
+ break;
+ }
+ }
+ thd->unlock_temporary_tables();
+ return result;
+}
+
+
+/**
+ Drop a temporary table.
+
+ Try to locate the table in the list of thd->temporary_tables.
+ If the table is found:
+ - if the table is being used by some outer statement, fail.
+ - if the table is locked with LOCK TABLES or by prelocking,
+ unlock it and remove it from the list of locked tables
+ (THD::lock). Currently only transactional temporary tables
+ are locked.
+ - Close the temporary table, remove its .FRM
+ - remove the table from the list of temporary tables
+
+ This function is used to drop user temporary tables, as well as
+ internal tables created in CREATE TEMPORARY TABLE ... SELECT
+ or ALTER TABLE. Even though part of the work done by this function
+ is redundant when the table is internal, as long as we
+ link both internal and user temporary tables into the same
+ thd->temporary_tables list, it's impossible to tell here whether
+ we're dealing with an internal or a user temporary table.
+
+ @param thd Thread handler
+ @param table Temporary table to be deleted
+ @param is_trans Is set to the type of the table:
+ transactional (e.g. innodb) as TRUE or non-transactional
+ (e.g. myisam) as FALSE.
+
+ @retval 0 the table was found and dropped successfully.
+ @retval -1 the table is in use by a outer query
+*/
+
+int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans)
+{
+ DBUG_ENTER("drop_temporary_table");
+ DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+ table->s->db.str, table->s->table_name.str));
+
+ /* Table might be in use by some outer statement. */
+ if (table->query_id && table->query_id != thd->query_id)
+ {
+ DBUG_PRINT("info", ("table->query_id: %lu thd->query_id: %lu",
+ (ulong) table->query_id, (ulong) thd->query_id));
+
+ my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr());
+ DBUG_RETURN(-1);
+ }
+
+ *is_trans= table->file->has_transactions();
+
+ /*
+ If LOCK TABLES list is not empty and contains this table,
+ unlock the table and remove the table from this list.
+ */
+ mysql_lock_remove(thd, thd->lock, table);
+ close_temporary_table(thd, table, 1, 1);
+ DBUG_RETURN(0);
+}
+
+
+/*
+ unlink from thd->temporary tables and close temporary table
+*/
+
+void close_temporary_table(THD *thd, TABLE *table,
+ bool free_share, bool delete_table)
+{
+ DBUG_ENTER("close_temporary_table");
+ DBUG_PRINT("tmptable", ("closing table: '%s'.'%s' 0x%lx alias: '%s'",
+ table->s->db.str, table->s->table_name.str,
+ (long) table, table->alias.c_ptr()));
+
+ thd->lock_temporary_tables();
+ if (table->prev)
+ {
+ table->prev->next= table->next;
+ if (table->prev->next)
+ table->next->prev= table->prev;
+ }
+ else
+ {
+ /* removing the item from the list */
+ DBUG_ASSERT(table == thd->temporary_tables);
+ /*
+ slave must reset its temporary list pointer to zero to exclude
+ passing non-zero value to end_slave via rli->save_temporary_tables
+ when no temp tables opened, see an invariant below.
+ */
+ thd->temporary_tables= table->next;
+ if (thd->temporary_tables)
+ table->next->prev= 0;
+ }
+ if (thd->rgi_slave)
+ {
+ /* natural invariant of temporary_tables */
+ DBUG_ASSERT(slave_open_temp_tables || !thd->temporary_tables);
+ thread_safe_decrement32(&slave_open_temp_tables, &thread_running_lock);
+ table->in_use= 0; // No statistics
+ }
+ thd->unlock_temporary_tables();
+ close_temporary(table, free_share, delete_table);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Close and delete a temporary table
+
+ NOTE
+ This dosn't unlink table from thd->temporary
+ If this is needed, use close_temporary_table()
+*/
+
+void close_temporary(TABLE *table, bool free_share, bool delete_table)
+{
+ handlerton *table_type= table->s->db_type();
+ DBUG_ENTER("close_temporary");
+ DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+ table->s->db.str, table->s->table_name.str));
+
+ free_io_cache(table);
+ closefrm(table, 0);
+ if (delete_table)
+ rm_temporary_table(table_type, table->s->path.str);
+ if (free_share)
+ {
+ free_table_share(table->s);
+ my_free(table);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Used by ALTER TABLE when the table is a temporary one. It changes something
+ only if the ALTER contained a RENAME clause (otherwise, table_name is the old
+ name).
+ Prepares a table cache key, which is the concatenation of db, table_name and
+ thd->slave_proxy_id, separated by '\0'.
+*/
+
+bool rename_temporary_table(THD* thd, TABLE *table, const char *db,
+ const char *table_name)
+{
+ char *key;
+ uint key_length;
+ TABLE_SHARE *share= table->s;
+ DBUG_ENTER("rename_temporary_table");
+
+ if (!(key=(char*) alloc_root(&share->mem_root, MAX_DBKEY_LENGTH)))
+ DBUG_RETURN(1); /* purecov: inspected */
+
+ key_length= create_tmp_table_def_key(thd, key, db, table_name);
+ share->set_table_cache_key(key, key_length);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Force all other threads to stop using the table by upgrading
+ metadata lock on it and remove unused TABLE instances from cache.
+
+ @param thd Thread handler
+ @param table Table to remove from cache
+ @param function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted
+ HA_EXTRA_FORCE_REOPEN if table is not be used
+ HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed
+ HA_EXTRA_NOT_USED Don't call extra()
+
+ @note When returning, the table will be unusable for other threads
+ until metadata lock is downgraded.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. because thread was killed).
+*/
+
+bool wait_while_table_is_used(THD *thd, TABLE *table,
+ enum ha_extra_function function)
+{
+ DBUG_ENTER("wait_while_table_is_used");
+ DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
+ table->s->table_name.str, (ulong) table->s,
+ table->db_stat, table->s->tdc.version));
+
+ if (thd->mdl_context.upgrade_shared_lock(
+ table->mdl_ticket, MDL_EXCLUSIVE,
+ thd->variables.lock_wait_timeout))
+ DBUG_RETURN(TRUE);
+
+ tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN,
+ table->s->db.str, table->s->table_name.str,
+ FALSE);
+ /* extra() call must come only after all instances above are closed */
+ if (function != HA_EXTRA_NOT_USED)
+ (void) table->file->extra(function);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Close a and drop a just created table in CREATE TABLE ... SELECT.
+
+ @param thd Thread handle
+ @param table TABLE object for the table to be dropped
+ @param db_name Name of database for this table
+ @param table_name Name of this table
+
+ This routine assumes that the table to be closed is open only
+ by the calling thread, so we needn't wait until other threads
+ close the table. It also assumes that the table is first
+ in thd->open_ables and a data lock on it, if any, has been
+ released. To sum up, it's tuned to work with
+ CREATE TABLE ... SELECT and CREATE TABLE .. SELECT only.
+ Note, that currently CREATE TABLE ... SELECT is not supported
+ under LOCK TABLES. This function, still, can be called in
+ prelocked mode, e.g. if we do CREATE TABLE .. SELECT f1();
+*/
+
+void drop_open_table(THD *thd, TABLE *table, const char *db_name,
+ const char *table_name)
+{
+ DBUG_ENTER("drop_open_table");
+ if (table->s->tmp_table)
+ close_temporary_table(thd, table, 1, 1);
+ else
+ {
+ DBUG_ASSERT(table == thd->open_tables);
+
+ handlerton *table_type= table->s->db_type();
+ table->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
+ close_thread_table(thd, &thd->open_tables);
+ /* Remove the table share from the table cache. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name,
+ FALSE);
+ /* Remove the table from the storage engine and rm the .frm. */
+ quick_rm_table(thd, table_type, db_name, table_name, 0);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ An error handler which converts, if possible, ER_LOCK_DEADLOCK error
+ that can occur when we are trying to acquire a metadata lock to
+ a request for back-off and re-start of open_tables() process.
+*/
+
+class MDL_deadlock_handler : public Internal_error_handler
+{
+public:
+ MDL_deadlock_handler(Open_table_context *ot_ctx_arg)
+ : m_ot_ctx(ot_ctx_arg), m_is_active(FALSE)
+ {}
+
+ virtual ~MDL_deadlock_handler() {}
+
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+
+private:
+ /** Open table context to be used for back-off request. */
+ Open_table_context *m_ot_ctx;
+ /**
+ Indicates that we are already in the process of handling
+ ER_LOCK_DEADLOCK error. Allows to re-emit the error from
+ the error handler without falling into infinite recursion.
+ */
+ bool m_is_active;
+};
+
+
+bool MDL_deadlock_handler::handle_condition(THD *,
+ uint sql_errno,
+ const char*,
+ Sql_condition::enum_warning_level,
+ const char*,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ if (! m_is_active && sql_errno == ER_LOCK_DEADLOCK)
+ {
+ /* Disable the handler to avoid infinite recursion. */
+ m_is_active= TRUE;
+ (void) m_ot_ctx->request_backoff_action(
+ Open_table_context::OT_BACKOFF_AND_RETRY,
+ NULL);
+ m_is_active= FALSE;
+ /*
+ If the above back-off request failed, a new instance of
+ ER_LOCK_DEADLOCK error was emitted. Thus the current
+ instance of error condition can be treated as handled.
+ */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Try to acquire an MDL lock for a table being opened.
+
+ @param[in,out] thd Session context, to report errors.
+ @param[out] ot_ctx Open table context, to hold the back off
+ state. If we failed to acquire a lock
+ due to a lock conflict, we add the
+ failed request to the open table context.
+ @param[in,out] mdl_request A request for an MDL lock.
+ If we managed to acquire a ticket
+ (no errors or lock conflicts occurred),
+ contains a reference to it on
+ return. However, is not modified if MDL
+ lock type- modifying flags were provided.
+ @param[in] flags flags MYSQL_OPEN_FORCE_SHARED_MDL,
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL or
+ MYSQL_OPEN_FAIL_ON_MDL_CONFLICT
+ @sa open_table().
+ @param[out] mdl_ticket Only modified if there was no error.
+ If we managed to acquire an MDL
+ lock, contains a reference to the
+ ticket, otherwise is set to NULL.
+
+ @retval TRUE An error occurred.
+ @retval FALSE No error, but perhaps a lock conflict, check mdl_ticket.
+*/
+
+static bool
+open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
+ MDL_request *mdl_request,
+ uint flags,
+ MDL_ticket **mdl_ticket)
+{
+ MDL_request mdl_request_shared;
+
+ if (flags & (MYSQL_OPEN_FORCE_SHARED_MDL |
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))
+ {
+ /*
+ MYSQL_OPEN_FORCE_SHARED_MDL flag means that we are executing
+ PREPARE for a prepared statement and want to override
+ the type-of-operation aware metadata lock which was set
+ in the parser/during view opening with a simple shared
+ metadata lock.
+ This is necessary to allow concurrent execution of PREPARE
+ and LOCK TABLES WRITE statement against the same table.
+
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL flag means that we open
+ the table in order to get information about it for one of I_S
+ queries and also want to override the type-of-operation aware
+ shared metadata lock which was set earlier (e.g. during view
+ opening) with a high-priority shared metadata lock.
+ This is necessary to avoid unnecessary waiting and extra
+ ER_WARN_I_S_SKIPPED_TABLE warnings when accessing I_S tables.
+
+ These two flags are mutually exclusive.
+ */
+ DBUG_ASSERT(!(flags & MYSQL_OPEN_FORCE_SHARED_MDL) ||
+ !(flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL));
+
+ mdl_request_shared.init(&mdl_request->key,
+ (flags & MYSQL_OPEN_FORCE_SHARED_MDL) ?
+ MDL_SHARED : MDL_SHARED_HIGH_PRIO,
+ MDL_TRANSACTION);
+ mdl_request= &mdl_request_shared;
+ }
+
+ if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT)
+ {
+ /*
+ When table is being open in order to get data for I_S table,
+ we might have some tables not only open but also locked (e.g. when
+ this happens under LOCK TABLES or in a stored function).
+ As a result by waiting on a conflicting metadata lock to go away
+ we may create a deadlock which won't entirely belong to the
+ MDL subsystem and thus won't be detectable by this subsystem's
+ deadlock detector.
+ To avoid such situation we skip the trouble-making table if
+ there is a conflicting lock.
+ */
+ if (thd->mdl_context.try_acquire_lock(mdl_request))
+ return TRUE;
+ if (mdl_request->ticket == NULL)
+ {
+ my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0),
+ mdl_request->key.db_name(), mdl_request->key.name());
+ return TRUE;
+ }
+ }
+ else
+ {
+ /*
+ We are doing a normal table open. Let us try to acquire a metadata
+ lock on the table. If there is a conflicting lock, acquire_lock()
+ will wait for it to go away. Sometimes this waiting may lead to a
+ deadlock, with the following results:
+ 1) If a deadlock is entirely within MDL subsystem, it is
+ detected by the deadlock detector of this subsystem.
+ ER_LOCK_DEADLOCK error is produced. Then, the error handler
+ that is installed prior to the call to acquire_lock() attempts
+ to request a back-off and retry. Upon success, ER_LOCK_DEADLOCK
+ error is suppressed, otherwise propagated up the calling stack.
+ 2) Otherwise, a deadlock may occur when the wait-for graph
+ includes edges not visible to the MDL deadlock detector.
+ One such example is a wait on an InnoDB row lock, e.g. when:
+ conn C1 gets SR MDL lock on t1 with SELECT * FROM t1
+ conn C2 gets a row lock on t2 with SELECT * FROM t2 FOR UPDATE
+ conn C3 gets in and waits on C1 with DROP TABLE t0, t1
+ conn C2 continues and blocks on C3 with SELECT * FROM t0
+ conn C1 deadlocks by waiting on C2 by issuing SELECT * FROM
+ t2 LOCK IN SHARE MODE.
+ Such circular waits are currently only resolved by timeouts,
+ e.g. @@innodb_lock_wait_timeout or @@lock_wait_timeout.
+ */
+ MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
+
+ thd->push_internal_handler(&mdl_deadlock_handler);
+ bool result= thd->mdl_context.acquire_lock(mdl_request,
+ ot_ctx->get_timeout());
+ thd->pop_internal_handler();
+
+ if (result && !ot_ctx->can_recover_from_failed_open())
+ return TRUE;
+ }
+ *mdl_ticket= mdl_request->ticket;
+ return FALSE;
+}
+
+
+/**
+ Open a base table.
+
+ @param thd Thread context.
+ @param table_list Open first table in list.
+ @param mem_root Temporary MEM_ROOT to be used for
+ parsing .FRMs for views.
+ @param ot_ctx Context with flags which modify how open works
+ and which is used to recover from a failed
+ open_table() attempt.
+ Some examples of flags:
+ MYSQL_OPEN_IGNORE_FLUSH - Open table even if
+ someone has done a flush. No version number
+ checking is done.
+ MYSQL_OPEN_HAS_MDL_LOCK - instead of acquiring
+ metadata locks rely on that caller already has
+ appropriate ones.
+
+ Uses a cache of open tables to find a TABLE instance not in use.
+
+ If TABLE_LIST::open_strategy is set to OPEN_IF_EXISTS, the table is
+ opened only if it exists. If the open strategy is OPEN_STUB, the
+ underlying table is never opened. In both cases, metadata locks are
+ always taken according to the lock strategy.
+
+ The function used to open temporary tables, but now it opens base tables
+ only.
+
+ @retval TRUE Open failed. "action" parameter may contain type of action
+ needed to remedy problem before retrying again.
+ @retval FALSE Success. Members of TABLE_LIST structure are filled properly
+ (e.g. TABLE_LIST::table is set for real tables and
+ TABLE_LIST::view is set for views).
+*/
+
+bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
+ Open_table_context *ot_ctx)
+{
+ TABLE *table;
+ const char *key;
+ uint key_length;
+ char *alias= table_list->alias;
+ uint flags= ot_ctx->get_flags();
+ MDL_ticket *mdl_ticket;
+ TABLE_SHARE *share;
+ uint gts_flags;
+ DBUG_ENTER("open_table");
+
+ /*
+ The table must not be opened already. The table can be pre-opened for
+ some statements if it is a temporary table.
+
+ open_temporary_table() must be used to open temporary tables.
+ */
+ DBUG_ASSERT(!table_list->table);
+
+ /* an open table operation needs a lot of the stack space */
+ if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias))
+ DBUG_RETURN(TRUE);
+
+ if (!(flags & MYSQL_OPEN_IGNORE_KILLED) && thd->killed)
+ {
+ thd->send_kill_message();
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Check if we're trying to take a write lock in a read only transaction.
+
+ Note that we allow write locks on log tables as otherwise logging
+ to general/slow log would be disabled in read only transactions.
+ */
+ if (table_list->mdl_request.type >= MDL_SHARED_WRITE &&
+ thd->tx_read_only &&
+ !(flags & (MYSQL_LOCK_LOG_TABLE | MYSQL_OPEN_HAS_MDL_LOCK)))
+ {
+ my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ key_length= get_table_def_key(table_list, &key);
+
+ /*
+ If we're in pre-locked or LOCK TABLES mode, let's try to find the
+ requested table in the list of pre-opened and locked tables. If the
+ table is not there, return an error - we can't open not pre-opened
+ tables in pre-locked/LOCK TABLES mode.
+ TODO: move this block into a separate function.
+ */
+ if (thd->locked_tables_mode &&
+ ! (flags & MYSQL_OPEN_GET_NEW_TABLE))
+ { // Using table locks
+ TABLE *best_table= 0;
+ int best_distance= INT_MIN;
+ for (table=thd->open_tables; table ; table=table->next)
+ {
+ if (table->s->table_cache_key.length == key_length &&
+ !memcmp(table->s->table_cache_key.str, key, key_length))
+ {
+ if (!my_strcasecmp(system_charset_info, table->alias.c_ptr(), alias) &&
+ table->query_id != thd->query_id && /* skip tables already used */
+ (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ table->query_id == 0))
+ {
+ int distance= ((int) table->reginfo.lock_type -
+ (int) table_list->lock_type);
+
+ /*
+ Find a table that either has the exact lock type requested,
+ or has the best suitable lock. In case there is no locked
+ table that has an equal or higher lock than requested,
+ we us the closest matching lock to be able to produce an error
+ message about wrong lock mode on the table. The best_table
+ is changed if bd < 0 <= d or bd < d < 0 or 0 <= d < bd.
+
+ distance < 0 - No suitable lock found
+ distance > 0 - we have lock mode higher then we require
+ distance == 0 - we have lock mode exactly which we need
+ */
+ if ((best_distance < 0 && distance > best_distance) ||
+ (distance >= 0 && distance < best_distance))
+ {
+ best_distance= distance;
+ best_table= table;
+ if (best_distance == 0)
+ {
+ /*
+ We have found a perfect match and can finish iterating
+ through open tables list. Check for table use conflict
+ between calling statement and SP/trigger is done in
+ lock_tables().
+ */
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (best_table)
+ {
+ table= best_table;
+ table->query_id= thd->query_id;
+ DBUG_PRINT("info",("Using locked table"));
+ goto reset;
+ }
+ /*
+ Is this table a view and not a base table?
+ (it is work around to allow to open view with locked tables,
+ real fix will be made after definition cache will be made)
+
+ Since opening of view which was not explicitly locked by LOCK
+ TABLES breaks metadata locking protocol (potentially can lead
+ to deadlocks) it should be disallowed.
+ */
+ if (thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ table_list->db,
+ table_list->table_name,
+ MDL_SHARED))
+ {
+ char path[FN_REFLEN + 1];
+ build_table_filename(path, sizeof(path) - 1,
+ table_list->db, table_list->table_name, reg_ext, 0);
+ /*
+ Note that we can't be 100% sure that it is a view since it's
+ possible that we either simply have not found unused TABLE
+ instance in THD::open_tables list or were unable to open table
+ during prelocking process (in this case in theory we still
+ should hold shared metadata lock on it).
+ */
+ if (dd_frm_is_view(thd, path))
+ {
+ if (!tdc_open_view(thd, table_list, alias, key, key_length,
+ mem_root, CHECK_METADATA_VERSION))
+ {
+ DBUG_ASSERT(table_list->view != 0);
+ DBUG_RETURN(FALSE); // VIEW
+ }
+ }
+ }
+ /*
+ No table in the locked tables list. In case of explicit LOCK TABLES
+ this can happen if a user did not include the table into the list.
+ In case of pre-locked mode locked tables list is generated automatically,
+ so we may only end up here if the table did not exist when
+ locked tables list was created.
+ */
+ if (thd->locked_tables_mode == LTM_PRELOCKED)
+ my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
+ else
+ my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Non pre-locked/LOCK TABLES mode, and the table is not temporary.
+ This is the normal use case.
+ */
+
+ if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK))
+ {
+ /*
+ We are not under LOCK TABLES and going to acquire write-lock/
+ modify the base table. We need to acquire protection against
+ global read lock until end of this statement in order to have
+ this statement blocked by active FLUSH TABLES WITH READ LOCK.
+
+ We don't need to acquire this protection under LOCK TABLES as
+ such protection already acquired at LOCK TABLES time and
+ not released until UNLOCK TABLES.
+
+ We don't block statements which modify only temporary tables
+ as these tables are not preserved by any form of
+ backup which uses FLUSH TABLES WITH READ LOCK.
+
+ TODO: The fact that we sometimes acquire protection against
+ GRL only when we encounter table to be write-locked
+ slightly increases probability of deadlock.
+ This problem will be solved once Alik pushes his
+ temporary table refactoring patch and we can start
+ pre-acquiring metadata locks at the beggining of
+ open_tables() call.
+ */
+ if (table_list->mdl_request.type >= MDL_SHARED_WRITE &&
+ ! (flags & (MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |
+ MYSQL_OPEN_FORCE_SHARED_MDL |
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL |
+ MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) &&
+ ! ot_ctx->has_protection_against_grl())
+ {
+ MDL_request protection_request;
+ MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
+
+ if (thd->global_read_lock.can_acquire_protection())
+ DBUG_RETURN(TRUE);
+
+ protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_STATEMENT);
+
+ /*
+ Install error handler which if possible will convert deadlock error
+ into request to back-off and restart process of opening tables.
+ */
+ thd->push_internal_handler(&mdl_deadlock_handler);
+ bool result= thd->mdl_context.acquire_lock(&protection_request,
+ ot_ctx->get_timeout());
+ thd->pop_internal_handler();
+
+ if (result)
+ DBUG_RETURN(TRUE);
+
+ ot_ctx->set_has_protection_against_grl();
+ }
+
+ if (open_table_get_mdl_lock(thd, ot_ctx, &table_list->mdl_request,
+ flags, &mdl_ticket) ||
+ mdl_ticket == NULL)
+ {
+ DEBUG_SYNC(thd, "before_open_table_wait_refresh");
+ DBUG_RETURN(TRUE);
+ }
+ DEBUG_SYNC(thd, "after_open_table_mdl_shared");
+ }
+ else
+ {
+ /*
+ Grab reference to the MDL lock ticket that was acquired
+ by the caller.
+ */
+ mdl_ticket= table_list->mdl_request.ticket;
+ }
+
+ if (table_list->open_strategy == TABLE_LIST::OPEN_IF_EXISTS)
+ {
+ if (!ha_table_exists(thd, table_list->db, table_list->table_name))
+ DBUG_RETURN(FALSE);
+ }
+ else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB)
+ DBUG_RETURN(FALSE);
+
+ /* Table exists. Let us try to open it. */
+
+ if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
+ gts_flags= GTS_TABLE;
+ else if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
+ gts_flags= GTS_VIEW;
+ else
+ gts_flags= GTS_TABLE | GTS_VIEW;
+
+retry_share:
+
+ share= tdc_acquire_share(thd, table_list->db, table_list->table_name,
+ key, key_length,
+ table_list->mdl_request.key.tc_hash_value(),
+ gts_flags, &table);
+
+ if (!share)
+ {
+ /*
+ Hide "Table doesn't exist" errors if the table belongs to a view.
+ The check for thd->is_error() is necessary to not push an
+ unwanted error in case the error was already silenced.
+ @todo Rework the alternative ways to deal with ER_NO_SUCH TABLE.
+ */
+ if (thd->is_error())
+ {
+ if (table_list->parent_l)
+ {
+ thd->clear_error();
+ my_error(ER_WRONG_MRG_TABLE, MYF(0));
+ }
+ else if (table_list->belong_to_view)
+ {
+ TABLE_LIST *view= table_list->belong_to_view;
+ thd->clear_error();
+ my_error(ER_VIEW_INVALID, MYF(0),
+ view->view_db.str, view->view_name.str);
+ }
+ }
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Check if this TABLE_SHARE-object corresponds to a view. Note, that there is
+ no need to check TABLE_SHARE::tdc.flushed as we do for regular tables,
+ because view shares are always up to date.
+ */
+ if (share->is_view)
+ {
+ /*
+ If parent_l of the table_list is non null then a merge table
+ has this view as child table, which is not supported.
+ */
+ if (table_list->parent_l)
+ {
+ my_error(ER_WRONG_MRG_TABLE, MYF(0));
+ goto err_lock;
+ }
+
+ /*
+ This table is a view. Validate its metadata version: in particular,
+ that it was a view when the statement was prepared.
+ */
+ if (check_and_update_table_version(thd, table_list, share))
+ goto err_lock;
+
+ /* Open view */
+ if (open_new_frm(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
+ thd->open_options,
+ 0, table_list, mem_root))
+ goto err_lock;
+
+ /* TODO: Don't free this */
+ tdc_release_share(share);
+
+ DBUG_ASSERT(table_list->view);
+
+ DBUG_RETURN(FALSE);
+ }
+
+ if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
+ {
+ if (share->tdc.flushed)
+ {
+ DBUG_PRINT("info", ("Found old share version: %lu current: %lu",
+ share->tdc.version, tdc_refresh_version()));
+ /*
+ We already have an MDL lock. But we have encountered an old
+ version of table in the table definition cache which is possible
+ when someone changes the table version directly in the cache
+ without acquiring a metadata lock (e.g. this can happen during
+ "rolling" FLUSH TABLE(S)).
+ Release our reference to share, wait until old version of
+ share goes away and then try to get new version of table share.
+ */
+ if (table)
+ tc_release_table(table);
+ else
+ tdc_release_share(share);
+
+ MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
+ bool wait_result;
+
+ thd->push_internal_handler(&mdl_deadlock_handler);
+ wait_result= tdc_wait_for_old_version(thd, table_list->db,
+ table_list->table_name,
+ ot_ctx->get_timeout(),
+ mdl_ticket->get_deadlock_weight());
+ thd->pop_internal_handler();
+
+ if (wait_result)
+ DBUG_RETURN(TRUE);
+
+ goto retry_share;
+ }
+
+ if (thd->open_tables && thd->open_tables->s->tdc.flushed)
+ {
+ /*
+ If the version changes while we're opening the tables,
+ we have to back off, close all the tables opened-so-far,
+ and try to reopen them. Note: refresh_version is currently
+ changed only during FLUSH TABLES.
+ */
+ if (table)
+ tc_release_table(table);
+ else
+ tdc_release_share(share);
+ (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES,
+ NULL);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (table)
+ {
+ DBUG_ASSERT(table->file != NULL);
+ table->file->rebind_psi();
+ }
+ else
+ {
+ enum open_frm_error error;
+
+ /* make a new table */
+ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+ goto err_lock;
+
+ error= open_table_from_share(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE |
+ HA_OPEN_RNDFILE |
+ HA_GET_INDEX |
+ HA_TRY_READ_ONLY),
+ (READ_KEYINFO | COMPUTE_TYPES |
+ EXTRA_RECORD),
+ thd->open_options, table, FALSE);
+
+ if (error)
+ {
+ my_free(table);
+
+ if (error == OPEN_FRM_DISCOVER)
+ (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
+ table_list);
+ else if (share->crashed)
+ (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR,
+ table_list);
+ goto err_lock;
+ }
+ if (open_table_entry_fini(thd, share, table))
+ {
+ closefrm(table, 0);
+ my_free(table);
+ goto err_lock;
+ }
+
+ /* Add table to the share's used tables list. */
+ tc_add_table(thd, table);
+ }
+
+ table->mdl_ticket= mdl_ticket;
+
+ table->next= thd->open_tables; /* Link into simple list */
+ thd->set_open_tables(table);
+
+ table->reginfo.lock_type=TL_READ; /* Assume read */
+
+ reset:
+ /*
+ Check that there is no reference to a condition from an earlier query
+ (cf. Bug#58553).
+ */
+ DBUG_ASSERT(table->file->pushed_cond == NULL);
+ table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
+ table_list->table= table;
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (table->part_info)
+ {
+ /* Set all [named] partitions as used. */
+ if (table->part_info->set_partition_bitmaps(table_list))
+ DBUG_RETURN(true);
+ }
+ else if (table_list->partition_names)
+ {
+ /* Don't allow PARTITION () clause on a nonpartitioned table */
+ my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(true);
+ }
+#endif
+
+ table->init(thd, table_list);
+
+ DBUG_RETURN(FALSE);
+
+err_lock:
+ tdc_release_share(share);
+
+ DBUG_PRINT("exit", ("failed"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Find table in the list of open tables.
+
+ @param list List of TABLE objects to be inspected.
+ @param db Database name
+ @param table_name Table name
+
+ @return Pointer to the TABLE object found, 0 if no table found.
+*/
+
+TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length= tdc_create_key(key, db, table_name);
+
+ for (TABLE *table= list; table ; table=table->next)
+ {
+ if (table->s->table_cache_key.length == key_length &&
+ !memcmp(table->s->table_cache_key.str, key, key_length))
+ return table;
+ }
+ return(0);
+}
+
+
+/**
+ Find instance of TABLE with upgradable or exclusive metadata
+ lock from the list of open tables, emit error if no such table
+ found.
+
+ @param thd Thread context
+ @param db Database name.
+ @param table_name Name of table.
+ @param no_error Don't emit error if no suitable TABLE
+ instance were found.
+
+ @note This function checks if the connection holds a global IX
+ metadata lock. If no such lock is found, it is not safe to
+ upgrade the lock and ER_TABLE_NOT_LOCKED_FOR_WRITE will be
+ reported.
+
+ @return Pointer to TABLE instance with MDL_SHARED_UPGRADABLE
+ MDL_SHARED_NO_WRITE, MDL_SHARED_NO_READ_WRITE, or
+ MDL_EXCLUSIVE metadata lock, NULL otherwise.
+*/
+
+TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db,
+ const char *table_name, bool no_error)
+{
+ TABLE *tab= find_locked_table(thd->open_tables, db, table_name);
+
+ if (!tab)
+ {
+ if (!no_error)
+ my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
+ return NULL;
+ }
+
+ /*
+ It is not safe to upgrade the metadata lock without a global IX lock.
+ This can happen with FLUSH TABLES <list> WITH READ LOCK as we in these
+ cases don't take a global IX lock in order to be compatible with
+ global read lock.
+ */
+ if (!thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
+ MDL_INTENTION_EXCLUSIVE))
+ {
+ if (!no_error)
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
+ return NULL;
+ }
+
+ while (tab->mdl_ticket != NULL &&
+ !tab->mdl_ticket->is_upgradable_or_exclusive() &&
+ (tab= find_locked_table(tab->next, db, table_name)))
+ continue;
+
+ if (!tab && !no_error)
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
+
+ return tab;
+}
+
+
+/***********************************************************************
+ class Locked_tables_list implementation. Declared in sql_class.h
+************************************************************************/
+
+/**
+ Enter LTM_LOCK_TABLES mode.
+
+ Enter the LOCK TABLES mode using all the tables that are
+ currently open and locked in this connection.
+ Initializes a TABLE_LIST instance for every locked table.
+
+ @param thd thread handle
+
+ @return TRUE if out of memory.
+*/
+
+bool
+Locked_tables_list::init_locked_tables(THD *thd)
+{
+ DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE);
+ DBUG_ASSERT(m_locked_tables == NULL);
+ DBUG_ASSERT(m_reopen_array == NULL);
+ DBUG_ASSERT(m_locked_tables_count == 0);
+
+ for (TABLE *table= thd->open_tables; table;
+ table= table->next, m_locked_tables_count++)
+ {
+ TABLE_LIST *src_table_list= table->pos_in_table_list;
+ char *db, *table_name, *alias;
+ size_t db_len= table->s->db.length;
+ size_t table_name_len= table->s->table_name.length;
+ size_t alias_len= table->alias.length();
+ TABLE_LIST *dst_table_list;
+
+ if (! multi_alloc_root(&m_locked_tables_root,
+ &dst_table_list, sizeof(*dst_table_list),
+ &db, db_len + 1,
+ &table_name, table_name_len + 1,
+ &alias, alias_len + 1,
+ NullS))
+ {
+ reset();
+ return TRUE;
+ }
+
+ memcpy(db, table->s->db.str, db_len + 1);
+ memcpy(table_name, table->s->table_name.str, table_name_len + 1);
+ strmake(alias, table->alias.ptr(), alias_len);
+ dst_table_list->init_one_table(db, db_len, table_name, table_name_len,
+ alias, table->reginfo.lock_type);
+ dst_table_list->table= table;
+ dst_table_list->mdl_request.ticket= src_table_list->mdl_request.ticket;
+
+ /* Link last into the list of tables */
+ *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list;
+ m_locked_tables_last= &dst_table_list->next_global;
+ table->pos_in_locked_tables= dst_table_list;
+ }
+ if (m_locked_tables_count)
+ {
+ /**
+ Allocate an auxiliary array to pass to mysql_lock_tables()
+ in reopen_tables(). reopen_tables() is a critical
+ path and we don't want to complicate it with extra allocations.
+ */
+ m_reopen_array= (TABLE**)alloc_root(&m_locked_tables_root,
+ sizeof(TABLE*) *
+ (m_locked_tables_count+1));
+ if (m_reopen_array == NULL)
+ {
+ reset();
+ return TRUE;
+ }
+ }
+ thd->enter_locked_tables_mode(LTM_LOCK_TABLES);
+
+ return FALSE;
+}
+
+
+/**
+ Leave LTM_LOCK_TABLES mode if it's been entered.
+
+ Close all locked tables, free memory, and leave the mode.
+
+ @note This function is a no-op if we're not in LOCK TABLES.
+*/
+
+void
+Locked_tables_list::unlock_locked_tables(THD *thd)
+{
+ DBUG_ASSERT(!thd->in_sub_stmt &&
+ !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+ /*
+ Sic: we must be careful to not close open tables if
+ we're not in LOCK TABLES mode: unlock_locked_tables() is
+ sometimes called implicitly, expecting no effect on
+ open tables, e.g. from begin_trans().
+ */
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ return;
+
+ for (TABLE_LIST *table_list= m_locked_tables;
+ table_list; table_list= table_list->next_global)
+ {
+ /*
+ Clear the position in the list, the TABLE object will be
+ returned to the table cache.
+ */
+ if (table_list->table) // If not closed
+ table_list->table->pos_in_locked_tables= NULL;
+ }
+ thd->leave_locked_tables_mode();
+
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ close_thread_tables(thd);
+
+ /*
+ We rely on the caller to implicitly commit the
+ transaction and release transactional locks.
+ */
+
+ /*
+ After closing tables we can free memory used for storing lock
+ request for metadata locks and TABLE_LIST elements.
+ */
+ reset();
+}
+
+
+/**
+ Remove all meta data locks associated with table and release locked
+ table mode if there is no locked tables anymore
+*/
+
+void
+Locked_tables_list::unlock_locked_table(THD *thd, MDL_ticket *mdl_ticket)
+{
+ /*
+ Ensure we are in locked table mode.
+ As this function is only called on error condition it's better
+ to check this condition here than in the caller.
+ */
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ return;
+
+ if (mdl_ticket)
+ {
+ /*
+ Under LOCK TABLES we may have several instances of table open
+ and locked and therefore have to remove several metadata lock
+ requests associated with them.
+ */
+ thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+ }
+
+ if (thd->lock->table_count == 0)
+ unlock_locked_tables(thd);
+}
+
+
+/*
+ Free memory allocated for storing locks
+*/
+
+void Locked_tables_list::reset()
+{
+ free_root(&m_locked_tables_root, MYF(0));
+ m_locked_tables= NULL;
+ m_locked_tables_last= &m_locked_tables;
+ m_reopen_array= NULL;
+ m_locked_tables_count= 0;
+}
+
+
+/**
+ Unlink a locked table from the locked tables list, either
+ temporarily or permanently.
+
+ @param thd thread handle
+ @param table_list the element of locked tables list.
+ The implementation assumes that this argument
+ points to a TABLE_LIST element linked into
+ the locked tables list. Passing a TABLE_LIST
+ instance that is not part of locked tables
+ list will lead to a crash.
+ @param remove_from_locked_tables
+ TRUE if the table is removed from the list
+ permanently.
+
+ This function is a no-op if we're not under LOCK TABLES.
+
+ @sa Locked_tables_list::reopen_tables()
+*/
+
+
+void Locked_tables_list::unlink_from_list(THD *thd,
+ TABLE_LIST *table_list,
+ bool remove_from_locked_tables)
+{
+ /*
+ If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover,
+ outside this mode pos_in_locked_tables value is not trustworthy.
+ */
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ return;
+
+ /*
+ table_list must be set and point to pos_in_locked_tables of some
+ table.
+ */
+ DBUG_ASSERT(table_list->table->pos_in_locked_tables == table_list);
+
+ /* Clear the pointer, the table will be returned to the table cache. */
+ table_list->table->pos_in_locked_tables= NULL;
+
+ /* Mark the table as closed in the locked tables list. */
+ table_list->table= NULL;
+
+ /*
+ If the table is being dropped or renamed, remove it from
+ the locked tables list (implicitly drop the LOCK TABLES lock
+ on it).
+ */
+ if (remove_from_locked_tables)
+ {
+ *table_list->prev_global= table_list->next_global;
+ if (table_list->next_global == NULL)
+ m_locked_tables_last= table_list->prev_global;
+ else
+ table_list->next_global->prev_global= table_list->prev_global;
+ m_locked_tables_count--;
+ }
+}
+
+/**
+ This is an attempt to recover (somewhat) in case of an error.
+ If we failed to reopen a closed table, let's unlink it from the
+ list and forget about it. From a user perspective that would look
+ as if the server "lost" the lock on one of the locked tables.
+
+ @note This function is a no-op if we're not under LOCK TABLES.
+*/
+
+void Locked_tables_list::
+unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count)
+{
+ /* If we managed to take a lock, unlock tables and free the lock. */
+ if (lock)
+ mysql_unlock_tables(thd, lock);
+ /*
+ If a failure happened in reopen_tables(), we may have succeeded
+ reopening some tables, but not all.
+ This works when the connection was killed in mysql_lock_tables().
+ */
+ if (reopen_count)
+ {
+ while (reopen_count--)
+ {
+ /*
+ When closing the table, we must remove it
+ from thd->open_tables list.
+ We rely on the fact that open_table() that was used
+ in reopen_tables() always links the opened table
+ to the beginning of the open_tables list.
+ */
+ DBUG_ASSERT(thd->open_tables == m_reopen_array[reopen_count]);
+
+ thd->open_tables->pos_in_locked_tables->table= NULL;
+
+ close_thread_table(thd, &thd->open_tables);
+ }
+ }
+ /* Exclude all closed tables from the LOCK TABLES list. */
+ for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list=
+ table_list->next_global)
+ {
+ if (table_list->table == NULL)
+ {
+ /* Unlink from list. */
+ *table_list->prev_global= table_list->next_global;
+ if (table_list->next_global == NULL)
+ m_locked_tables_last= table_list->prev_global;
+ else
+ table_list->next_global->prev_global= table_list->prev_global;
+ m_locked_tables_count--;
+ }
+ }
+
+ /* If no tables left, do an automatic UNLOCK TABLES */
+ if (thd->lock && thd->lock->table_count == 0)
+ unlock_locked_tables(thd);
+}
+
+
+/**
+ Reopen the tables locked with LOCK TABLES and temporarily closed
+ by a DDL statement or FLUSH TABLES.
+
+ @note This function is a no-op if we're not under LOCK TABLES.
+
+ @return TRUE if an error reopening the tables. May happen in
+ case of some fatal system error only, e.g. a disk
+ corruption, out of memory or a serious bug in the
+ locking.
+*/
+
+bool
+Locked_tables_list::reopen_tables(THD *thd)
+{
+ Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
+ size_t reopen_count= 0;
+ MYSQL_LOCK *lock;
+ MYSQL_LOCK *merged_lock;
+ DBUG_ENTER("Locked_tables_list::reopen_tables");
+
+ for (TABLE_LIST *table_list= m_locked_tables;
+ table_list; table_list= table_list->next_global)
+ {
+ if (table_list->table) /* The table was not closed */
+ continue;
+
+ /* Links into thd->open_tables upon success */
+ if (open_table(thd, table_list, thd->mem_root, &ot_ctx))
+ {
+ unlink_all_closed_tables(thd, 0, reopen_count);
+ DBUG_RETURN(TRUE);
+ }
+ table_list->table->pos_in_locked_tables= table_list;
+ /* See also the comment on lock type in init_locked_tables(). */
+ table_list->table->reginfo.lock_type= table_list->lock_type;
+
+ DBUG_ASSERT(reopen_count < m_locked_tables_count);
+ m_reopen_array[reopen_count++]= table_list->table;
+ }
+ if (reopen_count)
+ {
+ thd->in_lock_tables= 1;
+ /*
+ We re-lock all tables with mysql_lock_tables() at once rather
+ than locking one table at a time because of the case
+ reported in Bug#45035: when the same table is present
+ in the list many times, thr_lock.c fails to grant READ lock
+ on a table that is already locked by WRITE lock, even if
+ WRITE lock is taken by the same thread. If READ and WRITE
+ lock are passed to thr_lock.c in the same list, everything
+ works fine. Patching legacy code of thr_lock.c is risking to
+ break something else.
+ */
+ lock= mysql_lock_tables(thd, m_reopen_array, reopen_count,
+ MYSQL_OPEN_REOPEN);
+ thd->in_lock_tables= 0;
+ if (lock == NULL || (merged_lock=
+ mysql_lock_merge(thd->lock, lock)) == NULL)
+ {
+ unlink_all_closed_tables(thd, lock, reopen_count);
+ if (! thd->killed)
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ thd->lock= merged_lock;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Add back a locked table to the locked list that we just removed from it.
+ This is needed in CREATE OR REPLACE TABLE where we are dropping, creating
+ and re-opening a locked table.
+
+ @return 0 0k
+ @return 1 error
+*/
+
+bool Locked_tables_list::restore_lock(THD *thd, TABLE_LIST *dst_table_list,
+ TABLE *table, MYSQL_LOCK *lock)
+{
+ MYSQL_LOCK *merged_lock;
+ DBUG_ENTER("restore_lock");
+ DBUG_ASSERT(!strcmp(dst_table_list->table_name, table->s->table_name.str));
+
+ /* Ensure we have the memory to add the table back */
+ if (!(merged_lock= mysql_lock_merge(thd->lock, lock)))
+ DBUG_RETURN(1);
+ thd->lock= merged_lock;
+
+ /* Link to the new table */
+ dst_table_list->table= table;
+ /*
+ The lock type may have changed (normally it should not as create
+ table will lock the table in write mode
+ */
+ dst_table_list->lock_type= table->reginfo.lock_type;
+ table->pos_in_locked_tables= dst_table_list;
+
+ add_back_last_deleted_lock(dst_table_list);
+
+ table->mdl_ticket->downgrade_lock(table->reginfo.lock_type >=
+ TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_NO_READ_WRITE :
+ MDL_SHARED_READ);
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Add back the last deleted lock structure.
+ This should be followed by a call to reopen_tables() to
+ open the table.
+*/
+
+void Locked_tables_list::add_back_last_deleted_lock(TABLE_LIST *dst_table_list)
+{
+ /* Link the lock back in the locked tables list */
+ dst_table_list->prev_global= m_locked_tables_last;
+ *m_locked_tables_last= dst_table_list;
+ m_locked_tables_last= &dst_table_list->next_global;
+ dst_table_list->next_global= 0;
+ m_locked_tables_count++;
+}
+
+
+#ifndef DBUG_OFF
+/* Cause a spurious statement reprepare for debug purposes. */
+static bool inject_reprepare(THD *thd)
+{
+ if (thd->m_reprepare_observer && thd->stmt_arena->is_reprepared == FALSE)
+ {
+ thd->m_reprepare_observer->report_error(thd);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif
+
+/**
+ Compare metadata versions of an element obtained from the table
+ definition cache and its corresponding node in the parse tree.
+
+ @details If the new and the old values mismatch, invoke
+ Metadata_version_observer.
+ At prepared statement prepare, all TABLE_LIST version values are
+ NULL and we always have a mismatch. But there is no observer set
+ in THD, and therefore no error is reported. Instead, we update
+ the value in the parse tree, effectively recording the original
+ version.
+ At prepared statement execute, an observer may be installed. If
+ there is a version mismatch, we push an error and return TRUE.
+
+ For conventional execution (no prepared statements), the
+ observer is never installed.
+
+ @sa Execute_observer
+ @sa check_prepared_statement() to see cases when an observer is installed
+ @sa TABLE_LIST::is_table_ref_id_equal()
+ @sa TABLE_SHARE::get_table_ref_id()
+
+ @param[in] thd used to report errors
+ @param[in,out] tables TABLE_LIST instance created by the parser
+ Metadata version information in this object
+ is updated upon success.
+ @param[in] table_share an element from the table definition cache
+
+ @retval TRUE an error, which has been reported
+ @retval FALSE success, version in TABLE_LIST has been updated
+*/
+
+static bool
+check_and_update_table_version(THD *thd,
+ TABLE_LIST *tables, TABLE_SHARE *table_share)
+{
+ if (! tables->is_table_ref_id_equal(table_share))
+ {
+ if (thd->m_reprepare_observer &&
+ thd->m_reprepare_observer->report_error(thd))
+ {
+ /*
+ Version of the table share is different from the
+ previous execution of the prepared statement, and it is
+ unacceptable for this SQLCOM. Error has been reported.
+ */
+ DBUG_ASSERT(thd->is_error());
+ return TRUE;
+ }
+ /* Always maintain the latest version and type */
+ tables->set_table_ref_id(table_share);
+ }
+
+ DBUG_EXECUTE_IF("reprepare_each_statement", return inject_reprepare(thd););
+ return FALSE;
+}
+
+
+/**
+ Compares versions of a stored routine obtained from the sp cache
+ and the version used at prepare.
+
+ @details If the new and the old values mismatch, invoke
+ Metadata_version_observer.
+ At prepared statement prepare, all Sroutine_hash_entry version values
+ are NULL and we always have a mismatch. But there is no observer set
+ in THD, and therefore no error is reported. Instead, we update
+ the value in Sroutine_hash_entry, effectively recording the original
+ version.
+ At prepared statement execute, an observer may be installed. If
+ there is a version mismatch, we push an error and return TRUE.
+
+ For conventional execution (no prepared statements), the
+ observer is never installed.
+
+ @param[in] thd used to report errors
+ @param[in/out] rt pointer to stored routine entry in the
+ parse tree
+ @param[in] sp pointer to stored routine cache entry.
+ Can be NULL if there is no such routine.
+ @retval TRUE an error, which has been reported
+ @retval FALSE success, version in Sroutine_hash_entry has been updated
+*/
+
+static bool
+check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt,
+ sp_head *sp)
+{
+ ulong spc_version= sp_cache_version();
+ /* sp is NULL if there is no such routine. */
+ ulong version= sp ? sp->sp_cache_version() : spc_version;
+ /*
+ If the version in the parse tree is stale,
+ or the version in the cache is stale and sp is not used,
+ we need to reprepare.
+ Sic: version != spc_version <--> sp is not NULL.
+ */
+ if (rt->m_sp_cache_version != version ||
+ (version != spc_version && !sp->is_invoked()))
+ {
+ if (thd->m_reprepare_observer &&
+ thd->m_reprepare_observer->report_error(thd))
+ {
+ /*
+ Version of the sp cache is different from the
+ previous execution of the prepared statement, and it is
+ unacceptable for this SQLCOM. Error has been reported.
+ */
+ DBUG_ASSERT(thd->is_error());
+ return TRUE;
+ }
+ /* Always maintain the latest cache version. */
+ rt->m_sp_cache_version= version;
+ }
+ return FALSE;
+}
+
+
+/**
+ Open view by getting its definition from disk (and table cache in future).
+
+ @param thd Thread handle
+ @param table_list TABLE_LIST with db, table_name & belong_to_view
+ @param alias Alias name
+ @param cache_key Key for table definition cache
+ @param cache_key_length Length of cache_key
+ @param mem_root Memory to be used for .frm parsing.
+ @param flags Flags which modify how we open the view
+
+ @todo This function is needed for special handling of views under
+ LOCK TABLES. We probably should get rid of it in long term.
+
+ @return FALSE if success, TRUE - otherwise.
+*/
+
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+ const char *cache_key, uint cache_key_length,
+ MEM_ROOT *mem_root, uint flags)
+{
+ TABLE not_used;
+ TABLE_SHARE *share;
+ bool err= TRUE;
+
+ if (!(share= tdc_acquire_share(thd, table_list->db, table_list->table_name,
+ cache_key, cache_key_length, GTS_VIEW)))
+ return TRUE;
+
+ DBUG_ASSERT(share->is_view);
+
+ if (flags & CHECK_METADATA_VERSION)
+ {
+ /*
+ Check TABLE_SHARE-version of view only if we have been instructed to do
+ so. We do not need to check the version if we're executing CREATE VIEW or
+ ALTER VIEW statements.
+
+ In the future, this functionality should be moved out from
+ tdc_open_view(), and tdc_open_view() should became a part of a clean
+ table-definition-cache interface.
+ */
+ if (check_and_update_table_version(thd, table_list, share))
+ goto ret;
+ }
+
+ err= open_new_frm(thd, share, alias,
+ (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | flags,
+ thd->open_options, &not_used, table_list, mem_root);
+
+ret:
+ tdc_release_share(share);
+
+ return err;
+}
+
+
+/**
+ Finalize the process of TABLE creation by loading table triggers
+ and taking action if a HEAP table content was emptied implicitly.
+*/
+
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
+{
+ if (Table_triggers_list::check_n_load(thd, share->db.str,
+ share->table_name.str, entry, 0))
+ return TRUE;
+
+ /*
+ If we are here, there was no fatal error (but error may be still
+ unitialized).
+ */
+ if (unlikely(entry->file->implicit_emptied))
+ {
+ entry->file->implicit_emptied= 0;
+ if (mysql_bin_log.is_open())
+ {
+ char query_buf[2*FN_REFLEN + 21];
+ String query(query_buf, sizeof(query_buf), system_charset_info);
+
+ query.length(0);
+ query.append("DELETE FROM ");
+ append_identifier(thd, &query, share->db.str, share->db.length);
+ query.append(".");
+ append_identifier(thd, &query, share->table_name.str,
+ share->table_name.length);
+
+ /*
+ we bypass thd->binlog_query() here,
+ as it does a lot of extra work, that is simply wrong in this case
+ */
+ Query_log_event qinfo(thd, query.ptr(), query.length(),
+ FALSE, TRUE, TRUE, 0);
+ if (mysql_bin_log.write(&qinfo))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ Auxiliary routine which is used for performing automatical table repair.
+*/
+
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
+{
+ TABLE_SHARE *share;
+ TABLE *entry;
+ bool result= TRUE;
+
+ thd->clear_error();
+
+ if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME))))
+ return result;
+
+ if (!(share= tdc_acquire_share_shortlived(thd, table_list, GTS_TABLE)))
+ goto end_free;
+
+ DBUG_ASSERT(! share->is_view);
+
+ if (open_table_from_share(thd, share, table_list->alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX |
+ HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
+ ha_open_options | HA_OPEN_FOR_REPAIR,
+ entry, FALSE) || ! entry->file ||
+ (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd)))
+ {
+ /* Give right error message */
+ thd->clear_error();
+ my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str);
+ sql_print_error("Couldn't repair table: %s.%s", share->db.str,
+ share->table_name.str);
+ if (entry->file)
+ closefrm(entry, 0);
+ }
+ else
+ {
+ thd->clear_error(); // Clear error message
+ closefrm(entry, 0);
+ result= FALSE;
+ }
+
+ tdc_release_share(share);
+ /* Remove the repaired share from the table cache. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
+ table_list->db, table_list->table_name,
+ FALSE);
+end_free:
+ my_free(entry);
+ return result;
+}
+
+
+/** Open_table_context */
+
+Open_table_context::Open_table_context(THD *thd, uint flags)
+ :m_thd(thd),
+ m_failed_table(NULL),
+ m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()),
+ m_timeout(flags & MYSQL_LOCK_IGNORE_TIMEOUT ?
+ LONG_TIMEOUT : thd->variables.lock_wait_timeout),
+ m_flags(flags),
+ m_action(OT_NO_ACTION),
+ m_has_locks(thd->mdl_context.has_locks()),
+ m_has_protection_against_grl(FALSE)
+{}
+
+
+/**
+ Check if we can back-off and set back off action if we can.
+ Otherwise report and return error.
+
+ @retval TRUE if back-off is impossible.
+ @retval FALSE if we can back off. Back off action has been set.
+*/
+
+bool
+Open_table_context::
+request_backoff_action(enum_open_table_action action_arg,
+ TABLE_LIST *table)
+{
+ /*
+ A back off action may be one of three kinds:
+
+ * We met a broken table that needs repair, or a table that
+ is not present on this MySQL server and needs re-discovery.
+ To perform the action, we need an exclusive metadata lock on
+ the table. Acquiring an X lock while holding other shared
+ locks is very deadlock-prone. If this is a multi- statement
+ transaction that holds metadata locks for completed
+ statements, we don't do it, and report an error instead.
+ The action type in this case is OT_DISCOVER or OT_REPAIR.
+ * Our attempt to acquire an MDL lock lead to a deadlock,
+ detected by the MDL deadlock detector. The current
+ session was chosen a victim. If this is a multi-statement
+ transaction that holds metadata locks taken by completed
+ statements, restarting locking for the current statement
+ may lead to a livelock. Releasing locks of completed
+ statements can not be done as will lead to violation
+ of ACID. Thus, again, if m_has_locks is set,
+ we report an error. Otherwise, when there are no metadata
+ locks other than which belong to this statement, we can
+ try to recover from error by releasing all locks and
+ restarting the pre-locking.
+ Similarly, a deadlock error can occur when the
+ pre-locking process met a TABLE_SHARE that is being
+ flushed, and unsuccessfully waited for the flush to
+ complete. A deadlock in this case can happen, e.g.,
+ when our session is holding a metadata lock that
+ is being waited on by a session which is using
+ the table which is being flushed. The only way
+ to recover from this error is, again, to close all
+ open tables, release all locks, and retry pre-locking.
+ Action type name is OT_REOPEN_TABLES. Re-trying
+ while holding some locks may lead to a livelock,
+ and thus we don't do it.
+ * Finally, this session has open TABLEs from different
+ "generations" of the table cache. This can happen, e.g.,
+ when, after this session has successfully opened one
+ table used for a statement, FLUSH TABLES interfered and
+ expelled another table used in it. FLUSH TABLES then
+ blocks and waits on the table already opened by this
+ statement.
+ We detect this situation by ensuring that table cache
+ version of all tables used in a statement is the same.
+ If it isn't, all tables needs to be reopened.
+ Note, that we can always perform a reopen in this case,
+ even if we already have metadata locks, since we don't
+ keep tables open between statements and a livelock
+ is not possible.
+ */
+ if (action_arg != OT_REOPEN_TABLES && m_has_locks)
+ {
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ m_thd->mark_transaction_to_rollback(true);
+ return TRUE;
+ }
+ /*
+ If auto-repair or discovery are requested, a pointer to table
+ list element must be provided.
+ */
+ if (table)
+ {
+ DBUG_ASSERT(action_arg == OT_DISCOVER || action_arg == OT_REPAIR);
+ m_failed_table= (TABLE_LIST*) m_thd->alloc(sizeof(TABLE_LIST));
+ if (m_failed_table == NULL)
+ return TRUE;
+ m_failed_table->init_one_table(table->db, table->db_length,
+ table->table_name,
+ table->table_name_length,
+ table->alias, TL_WRITE);
+ m_failed_table->open_strategy= table->open_strategy;
+ m_failed_table->mdl_request.set_type(MDL_EXCLUSIVE);
+ }
+ m_action= action_arg;
+ return FALSE;
+}
+
+
+/**
+ Recover from failed attempt of open table by performing requested action.
+
+ @pre This function should be called only with "action" != OT_NO_ACTION
+ and after having called @sa close_tables_for_reopen().
+
+ @retval FALSE - Success. One should try to open tables once again.
+ @retval TRUE - Error
+*/
+
+bool
+Open_table_context::recover_from_failed_open()
+{
+ bool result= FALSE;
+ /* Execute the action. */
+ switch (m_action)
+ {
+ case OT_BACKOFF_AND_RETRY:
+ break;
+ case OT_REOPEN_TABLES:
+ break;
+ case OT_DISCOVER:
+ {
+ if ((result= lock_table_names(m_thd, m_failed_table, NULL,
+ get_timeout(), 0)))
+ break;
+
+ tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
+ m_failed_table->table_name, FALSE);
+
+ m_thd->get_stmt_da()->clear_warning_info(m_thd->query_id);
+ m_thd->clear_error(); // Clear error message
+
+ No_such_table_error_handler no_such_table_handler;
+ bool open_if_exists= m_failed_table->open_strategy == TABLE_LIST::OPEN_IF_EXISTS;
+
+ if (open_if_exists)
+ m_thd->push_internal_handler(&no_such_table_handler);
+
+ result= !tdc_acquire_share(m_thd, m_failed_table->db,
+ m_failed_table->table_name,
+ GTS_TABLE | GTS_FORCE_DISCOVERY | GTS_NOLOCK);
+ if (open_if_exists)
+ {
+ m_thd->pop_internal_handler();
+ if (result && no_such_table_handler.safely_trapped_errors())
+ result= FALSE;
+ }
+
+ m_thd->mdl_context.release_transactional_locks();
+ break;
+ }
+ case OT_REPAIR:
+ {
+ if ((result= lock_table_names(m_thd, m_failed_table, NULL,
+ get_timeout(), 0)))
+ break;
+
+ tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
+ m_failed_table->table_name, FALSE);
+
+ result= auto_repair_table(m_thd, m_failed_table);
+ m_thd->mdl_context.release_transactional_locks();
+ break;
+ }
+ default:
+ DBUG_ASSERT(0);
+ }
+ /*
+ Reset the pointers to conflicting MDL request and the
+ TABLE_LIST element, set when we need auto-discovery or repair,
+ for safety.
+ */
+ m_failed_table= NULL;
+ /*
+ Reset flag indicating that we have already acquired protection
+ against GRL. It is no longer valid as the corresponding lock was
+ released by close_tables_for_reopen().
+ */
+ m_has_protection_against_grl= FALSE;
+ /* Prepare for possible another back-off. */
+ m_action= OT_NO_ACTION;
+ return result;
+}
+
+
+/*
+ Return a appropriate read lock type given a table object.
+
+ @param thd Thread context
+ @param prelocking_ctx Prelocking context.
+ @param table_list Table list element for table to be locked.
+ @param routine_modifies_data
+ Some routine that is invoked by statement
+ modifies data.
+
+ @remark Due to a statement-based replication limitation, statements such as
+ INSERT INTO .. SELECT FROM .. and CREATE TABLE .. SELECT FROM need
+ to grab a TL_READ_NO_INSERT lock on the source table in order to
+ prevent the replication of a concurrent statement that modifies the
+ source table. If such a statement gets applied on the slave before
+ the INSERT .. SELECT statement finishes, data on the master could
+ differ from data on the slave and end-up with a discrepancy between
+ the binary log and table state.
+ This also applies to SELECT/SET/DO statements which use stored
+ functions. Calls to such functions are going to be logged as a
+ whole and thus should be serialized against concurrent changes
+ to tables used by those functions. This is avoided when functions
+ do not modify data but only read it, since in this case nothing is
+ written to the binary log. Argument routine_modifies_data
+ denotes the same. So effectively, if the statement is not a
+ update query and routine_modifies_data is false, then
+ prelocking_placeholder does not take importance.
+
+ Furthermore, this does not apply to I_S and log tables as it's
+ always unsafe to replicate such tables under statement-based
+ replication as the table on the slave might contain other data
+ (ie: general_log is enabled on the slave). The statement will
+ be marked as unsafe for SBR in decide_logging_format().
+ @remark Note that even in prelocked mode it is important to correctly
+ determine lock type value. In this mode lock type is passed to
+ handler::start_stmt() method and can be used by storage engine,
+ for example, to determine what kind of row locks it should acquire
+ when reading data from the table.
+*/
+
+thr_lock_type read_lock_type_for_table(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list,
+ bool routine_modifies_data)
+{
+ /*
+ In cases when this function is called for a sub-statement executed in
+ prelocked mode we can't rely on OPTION_BIN_LOG flag in THD::options
+ bitmap to determine that binary logging is turned on as this bit can
+ be cleared before executing sub-statement. So instead we have to look
+ at THD::variables::sql_log_bin member.
+ */
+ bool log_on= mysql_bin_log.is_open() && thd->variables.sql_log_bin;
+ ulong binlog_format= thd->variables.binlog_format;
+ if ((log_on == FALSE) || (binlog_format == BINLOG_FORMAT_ROW) ||
+ (table_list->table->s->table_category == TABLE_CATEGORY_LOG) ||
+ (table_list->table->s->table_category == TABLE_CATEGORY_PERFORMANCE) ||
+ !(is_update_query(prelocking_ctx->sql_command) ||
+ (routine_modifies_data && table_list->prelocking_placeholder) ||
+ (thd->locked_tables_mode > LTM_LOCK_TABLES)))
+ return TL_READ;
+ else
+ return TL_READ_NO_INSERT;
+}
+
+
+/*
+ Handle element of prelocking set other than table. E.g. cache routine
+ and, if prelocking strategy prescribes so, extend the prelocking set
+ with tables and routines used by it.
+
+ @param[in] thd Thread context.
+ @param[in] prelocking_ctx Prelocking context.
+ @param[in] rt Element of prelocking set to be processed.
+ @param[in] prelocking_strategy Strategy which specifies how the
+ prelocking set should be extended when
+ one of its elements is processed.
+ @param[in] has_prelocking_list Indicates that prelocking set/list for
+ this statement has already been built.
+ @param[in] ot_ctx Context of open_table used to recover from
+ locking failures.
+ @param[out] need_prelocking Set to TRUE if it was detected that this
+ statement will require prelocked mode for
+ its execution, not touched otherwise.
+ @param[out] routine_modifies_data Set to TRUE if it was detected that this
+ routine does modify table data.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (Conflicting metadata lock, OOM, other errors).
+*/
+
+static bool
+open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt,
+ Prelocking_strategy *prelocking_strategy,
+ bool has_prelocking_list,
+ Open_table_context *ot_ctx,
+ bool *need_prelocking, bool *routine_modifies_data)
+{
+ MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace();
+ DBUG_ENTER("open_and_process_routine");
+
+ *routine_modifies_data= false;
+
+ switch (mdl_type)
+ {
+ case MDL_key::FUNCTION:
+ case MDL_key::PROCEDURE:
+ {
+ sp_head *sp;
+ /*
+ Try to get MDL lock on the routine.
+ Note that we do not take locks on top-level CALLs as this can
+ lead to a deadlock. Not locking top-level CALLs does not break
+ the binlog as only the statements in the called procedure show
+ up there, not the CALL itself.
+ */
+ if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first ||
+ mdl_type != MDL_key::PROCEDURE)
+ {
+ /*
+ Since we acquire only shared lock on routines we don't
+ need to care about global intention exclusive locks.
+ */
+ DBUG_ASSERT(rt->mdl_request.type == MDL_SHARED);
+
+ /*
+ Waiting for a conflicting metadata lock to go away may
+ lead to a deadlock, detected by MDL subsystem.
+ If possible, we try to resolve such deadlocks by releasing all
+ metadata locks and restarting the pre-locking process.
+ To prevent the error from polluting the diagnostics area
+ in case of successful resolution, install a special error
+ handler for ER_LOCK_DEADLOCK error.
+ */
+ MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
+
+ thd->push_internal_handler(&mdl_deadlock_handler);
+ bool result= thd->mdl_context.acquire_lock(&rt->mdl_request,
+ ot_ctx->get_timeout());
+ thd->pop_internal_handler();
+
+ if (result)
+ DBUG_RETURN(TRUE);
+
+ DEBUG_SYNC(thd, "after_shared_lock_pname");
+
+ /* Ensures the routine is up-to-date and cached, if exists. */
+ if (sp_cache_routine(thd, rt, has_prelocking_list, &sp))
+ DBUG_RETURN(TRUE);
+
+ /* Remember the version of the routine in the parse tree. */
+ if (check_and_update_routine_version(thd, rt, sp))
+ DBUG_RETURN(TRUE);
+
+ /* 'sp' is NULL when there is no such routine. */
+ if (sp)
+ {
+ *routine_modifies_data= sp->modifies_data();
+
+ if (!has_prelocking_list)
+ prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
+ need_prelocking);
+ }
+ }
+ else
+ {
+ /*
+ If it's a top level call, just make sure we have a recent
+ version of the routine, if it exists.
+ Validating routine version is unnecessary, since CALL
+ does not affect the prepared statement prelocked list.
+ */
+ if (sp_cache_routine(thd, rt, FALSE, &sp))
+ DBUG_RETURN(TRUE);
+ }
+ }
+ break;
+ case MDL_key::TRIGGER:
+ /**
+ We add trigger entries to lex->sroutines_list, but we don't
+ load them here. The trigger entry is only used when building
+ a transitive closure of objects used in a statement, to avoid
+ adding to this closure objects that are used in the trigger more
+ than once.
+ E.g. if a trigger trg refers to table t2, and the trigger table t1
+ is used multiple times in the statement (say, because it's used in
+ function f1() twice), we will only add t2 once to the list of
+ tables to prelock.
+
+ We don't take metadata locks on triggers either: they are protected
+ by a respective lock on the table, on which the trigger is defined.
+
+ The only two cases which give "trouble" are SHOW CREATE TRIGGER
+ and DROP TRIGGER statements. For these, statement syntax doesn't
+ specify the table on which this trigger is defined, so we have
+ to make a "dirty" read in the data dictionary to find out the
+ table name. Once we discover the table name, we take a metadata
+ lock on it, and this protects all trigger operations.
+ Of course the table, in theory, may disappear between the dirty
+ read and metadata lock acquisition, but in that case we just return
+ a run-time error.
+
+ Grammar of other trigger DDL statements (CREATE, DROP) requires
+ the table to be specified explicitly, so we use the table metadata
+ lock to protect trigger metadata in these statements. Similarly, in
+ DML we always use triggers together with their tables, and thus don't
+ need to take separate metadata locks on them.
+ */
+ break;
+ default:
+ /* Impossible type value. */
+ DBUG_ASSERT(0);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Handle table list element by obtaining metadata lock, opening table or view
+ and, if prelocking strategy prescribes so, extending the prelocking set with
+ tables and routines used by it.
+
+ @param[in] thd Thread context.
+ @param[in] lex LEX structure for statement.
+ @param[in] tables Table list element to be processed.
+ @param[in,out] counter Number of tables which are open.
+ @param[in] flags Bitmap of flags to modify how the tables
+ will be open, see open_table() description
+ for details.
+ @param[in] prelocking_strategy Strategy which specifies how the
+ prelocking set should be extended
+ when table or view is processed.
+ @param[in] has_prelocking_list Indicates that prelocking set/list for
+ this statement has already been built.
+ @param[in] ot_ctx Context used to recover from a failed
+ open_table() attempt.
+ @param[in] new_frm_mem Temporary MEM_ROOT to be used for
+ parsing .FRMs for views.
+
+ @retval FALSE Success.
+ @retval TRUE Error, reported unless there is a chance to recover from it.
+*/
+
+static bool
+open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables,
+ uint *counter, uint flags,
+ Prelocking_strategy *prelocking_strategy,
+ bool has_prelocking_list,
+ Open_table_context *ot_ctx,
+ MEM_ROOT *new_frm_mem)
+{
+ bool error= FALSE;
+ bool safe_to_ignore_table= FALSE;
+ DBUG_ENTER("open_and_process_table");
+ DEBUG_SYNC(thd, "open_and_process_table");
+
+ /*
+ Ignore placeholders for derived tables. After derived tables
+ processing, link to created temporary table will be put here.
+ If this is derived table for view then we still want to process
+ routines used by this view.
+ */
+ if (tables->derived)
+ {
+ if (!tables->view)
+ goto end;
+ /*
+ We restore view's name and database wiped out by derived tables
+ processing and fall back to standard open process in order to
+ obtain proper metadata locks and do other necessary steps like
+ stored routine processing.
+ */
+ tables->db= tables->view_db.str;
+ tables->db_length= tables->view_db.length;
+ tables->table_name= tables->view_name.str;
+ tables->table_name_length= tables->view_name.length;
+ }
+ /*
+ If this TABLE_LIST object is a placeholder for an information_schema
+ table, create a temporary table to represent the information_schema
+ table in the query. Do not fill it yet - will be filled during
+ execution.
+ */
+ if (tables->schema_table)
+ {
+ /*
+ If this information_schema table is merged into a mergeable
+ view, ignore it for now -- it will be filled when its respective
+ TABLE_LIST is processed. This code works only during re-execution.
+ */
+ if (tables->view)
+ {
+ MDL_ticket *mdl_ticket;
+ /*
+ We still need to take a MDL lock on the merged view to protect
+ it from concurrent changes.
+ */
+ if (!open_table_get_mdl_lock(thd, ot_ctx, &tables->mdl_request,
+ flags, &mdl_ticket) &&
+ mdl_ticket != NULL)
+ goto process_view_routines;
+ /* Fall-through to return error. */
+ }
+ else if (!mysql_schema_table(thd, lex, tables) &&
+ !check_and_update_table_version(thd, tables, tables->table->s))
+ {
+ goto end;
+ }
+ error= TRUE;
+ goto end;
+ }
+ DBUG_PRINT("tcache", ("opening table: '%s'.'%s' item: %p",
+ tables->db, tables->table_name, tables)); //psergey: invalid read of size 1 here
+ (*counter)++;
+
+ /*
+ Not a placeholder: must be a base/temporary table or a view. Let us open it.
+ */
+ if (tables->table)
+ {
+ /*
+ If this TABLE_LIST object has an associated open TABLE object
+ (TABLE_LIST::table is not NULL), that TABLE object must be a pre-opened
+ temporary table.
+ */
+ DBUG_ASSERT(is_temporary_table(tables));
+ }
+ else if (tables->open_type == OT_TEMPORARY_ONLY)
+ {
+ /*
+ OT_TEMPORARY_ONLY means that we are in CREATE TEMPORARY TABLE statement.
+ Also such table list element can't correspond to prelocking placeholder
+ or to underlying table of merge table.
+ So existing temporary table should have been preopened by this moment
+ and we can simply continue without trying to open temporary or base
+ table.
+ */
+ DBUG_ASSERT(tables->open_strategy);
+ DBUG_ASSERT(!tables->prelocking_placeholder);
+ DBUG_ASSERT(!tables->parent_l);
+ DBUG_RETURN(0);
+ }
+
+ /* Not a placeholder: must be a base table or a view. Let us open it. */
+ if (tables->prelocking_placeholder)
+ {
+ /*
+ For the tables added by the pre-locking code, attempt to open
+ the table but fail silently if the table does not exist.
+ The real failure will occur when/if a statement attempts to use
+ that table.
+ */
+ No_such_table_error_handler no_such_table_handler;
+ thd->push_internal_handler(&no_such_table_handler);
+
+ /*
+ We're opening a table from the prelocking list.
+
+ Since this table list element might have been added after pre-opening
+ of temporary tables we have to try to open temporary table for it.
+
+ We can't simply skip this table list element and postpone opening of
+ temporary tabletill the execution of substatement for several reasons:
+ - Temporary table can be a MERGE table with base underlying tables,
+ so its underlying tables has to be properly open and locked at
+ prelocking stage.
+ - Temporary table can be a MERGE table and we might be in PREPARE
+ phase for a prepared statement. In this case it is important to call
+ HA_ATTACH_CHILDREN for all merge children.
+ This is necessary because merge children remember "TABLE_SHARE ref type"
+ and "TABLE_SHARE def version" in the HA_ATTACH_CHILDREN operation.
+ If HA_ATTACH_CHILDREN is not called, these attributes are not set.
+ Then, during the first EXECUTE, those attributes need to be updated.
+ That would cause statement re-preparing (because changing those
+ attributes during EXECUTE is caught by THD::m_reprepare_observers).
+ The problem is that since those attributes are not set in merge
+ children, another round of PREPARE will not help.
+ */
+ error= open_temporary_table(thd, tables);
+
+ if (!error && !tables->table)
+ error= open_table(thd, tables, new_frm_mem, ot_ctx);
+
+ thd->pop_internal_handler();
+ safe_to_ignore_table= no_such_table_handler.safely_trapped_errors();
+ }
+ else if (tables->parent_l && (thd->open_options & HA_OPEN_FOR_REPAIR))
+ {
+ /*
+ Also fail silently for underlying tables of a MERGE table if this
+ table is opened for CHECK/REPAIR TABLE statement. This is needed
+ to provide complete list of problematic underlying tables in
+ CHECK/REPAIR TABLE output.
+ */
+ Repair_mrg_table_error_handler repair_mrg_table_handler;
+ thd->push_internal_handler(&repair_mrg_table_handler);
+
+ error= open_temporary_table(thd, tables);
+ if (!error && !tables->table)
+ error= open_table(thd, tables, new_frm_mem, ot_ctx);
+
+ thd->pop_internal_handler();
+ safe_to_ignore_table= repair_mrg_table_handler.safely_trapped_errors();
+ }
+ else
+ {
+ if (tables->parent_l)
+ {
+ /*
+ Even if we are opening table not from the prelocking list we
+ still might need to look for a temporary table if this table
+ list element corresponds to underlying table of a merge table.
+ */
+ error= open_temporary_table(thd, tables);
+ }
+
+ if (!error && !tables->table)
+ error= open_table(thd, tables, new_frm_mem, ot_ctx);
+ }
+
+ free_root(new_frm_mem, MYF(MY_KEEP_PREALLOC));
+
+ if (error)
+ {
+ if (! ot_ctx->can_recover_from_failed_open() && safe_to_ignore_table)
+ {
+ DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'",
+ tables->db, tables->alias));
+ error= FALSE;
+ }
+ goto end;
+ }
+
+ /*
+ We can't rely on simple check for TABLE_LIST::view to determine
+ that this is a view since during re-execution we might reopen
+ ordinary table in place of view and thus have TABLE_LIST::view
+ set from repvious execution and TABLE_LIST::table set from
+ current.
+ */
+ if (!tables->table && tables->view)
+ {
+ /* VIEW placeholder */
+ (*counter)--;
+
+ /*
+ tables->next_global list consists of two parts:
+ 1) Query tables and underlying tables of views.
+ 2) Tables used by all stored routines that this statement invokes on
+ execution.
+ We need to know where the bound between these two parts is. If we've
+ just opened a view, which was the last table in part #1, and it
+ has added its base tables after itself, adjust the boundary pointer
+ accordingly.
+ */
+ if (lex->query_tables_own_last == &(tables->next_global) &&
+ tables->view->query_tables)
+ lex->query_tables_own_last= tables->view->query_tables_last;
+ /*
+ Let us free memory used by 'sroutines' hash here since we never
+ call destructor for this LEX.
+ */
+ my_hash_free(&tables->view->sroutines);
+ goto process_view_routines;
+ }
+
+ /*
+ Special types of open can succeed but still don't set
+ TABLE_LIST::table to anything.
+ */
+ if (tables->open_strategy && !tables->table)
+ goto end;
+
+ /*
+ If we are not already in prelocked mode and extended table list is not
+ yet built we might have to build the prelocking set for this statement.
+
+ Since currently no prelocking strategy prescribes doing anything for
+ tables which are only read, we do below checks only if table is going
+ to be changed.
+ */
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
+ ! has_prelocking_list &&
+ tables->lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ bool need_prelocking= FALSE;
+ TABLE_LIST **save_query_tables_last= lex->query_tables_last;
+ /*
+ Extend statement's table list and the prelocking set with
+ tables and routines according to the current prelocking
+ strategy.
+
+ For example, for DML statements we need to add tables and routines
+ used by triggers which are going to be invoked for this element of
+ table list and also add tables required for handling of foreign keys.
+ */
+ error= prelocking_strategy->handle_table(thd, lex, tables,
+ &need_prelocking);
+
+ if (need_prelocking && ! lex->requires_prelocking())
+ lex->mark_as_requiring_prelocking(save_query_tables_last);
+
+ if (error)
+ goto end;
+ }
+
+ /* Copy grant information from TABLE_LIST instance to TABLE one. */
+ tables->table->grant= tables->grant;
+
+ /* Check and update metadata version of a base table. */
+ error= check_and_update_table_version(thd, tables, tables->table->s);
+
+ if (error)
+ goto end;
+ /*
+ After opening a MERGE table add the children to the query list of
+ tables, so that they are opened too.
+ Note that placeholders don't have the handler open.
+ */
+ /* MERGE tables need to access parent and child TABLE_LISTs. */
+ DBUG_ASSERT(tables->table->pos_in_table_list == tables);
+ /* Non-MERGE tables ignore this call. */
+ if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST))
+ {
+ error= TRUE;
+ goto end;
+ }
+
+ if (get_use_stat_tables_mode(thd) > NEVER && tables->table)
+ {
+ TABLE_SHARE *table_share= tables->table->s;
+ if (table_share && table_share->table_category == TABLE_CATEGORY_USER &&
+ table_share->tmp_table == NO_TMP_TABLE)
+ {
+ if (table_share->stats_cb.stats_can_be_read ||
+ !alloc_statistics_for_table_share(thd, table_share, FALSE))
+ {
+ if (table_share->stats_cb.stats_can_be_read)
+ {
+ KEY *key_info= table_share->key_info;
+ KEY *key_info_end= key_info + table_share->keys;
+ KEY *table_key_info= tables->table->key_info;
+ for ( ; key_info < key_info_end; key_info++, table_key_info++)
+ table_key_info->read_stats= key_info->read_stats;
+ Field **field_ptr= table_share->field;
+ Field **table_field_ptr= tables->table->field;
+ for ( ; *field_ptr; field_ptr++, table_field_ptr++)
+ (*table_field_ptr)->read_stats= (*field_ptr)->read_stats;
+ tables->table->stats_is_read= table_share->stats_cb.stats_is_read;
+ }
+ }
+ }
+ }
+
+process_view_routines:
+ /*
+ Again we may need cache all routines used by this view and add
+ tables used by them to table list.
+ */
+ if (tables->view &&
+ thd->locked_tables_mode <= LTM_LOCK_TABLES &&
+ ! has_prelocking_list)
+ {
+ bool need_prelocking= FALSE;
+ TABLE_LIST **save_query_tables_last= lex->query_tables_last;
+
+ error= prelocking_strategy->handle_view(thd, lex, tables,
+ &need_prelocking);
+
+ if (need_prelocking && ! lex->requires_prelocking())
+ lex->mark_as_requiring_prelocking(save_query_tables_last);
+
+ if (error)
+ goto end;
+ }
+
+end:
+ DBUG_RETURN(error);
+}
+
+extern "C" uchar *schema_set_get_key(const TABLE_LIST *table, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= table->db_length;
+ return (uchar*) table->db;
+}
+
+/**
+ Acquire upgradable (SNW, SNRW) metadata locks on tables used by
+ LOCK TABLES or by a DDL statement. Under LOCK TABLES, we can't take
+ new locks, so use open_tables_check_upgradable_mdl() instead.
+
+ @param thd Thread context.
+ @param tables_start Start of list of tables on which upgradable locks
+ should be acquired.
+ @param tables_end End of list of tables.
+ @param lock_wait_timeout Seconds to wait before timeout.
+ @param flags Bitmap of flags to modify how the tables will be
+ open, see open_table() description for details.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. connection was killed) or table existed
+ for a CREATE TABLE.
+
+ @notes
+ In case of CREATE TABLE we avoid a wait for tables that are in use
+ by first trying to do a meta data lock with timeout == 0. If we get a
+ timeout we will check if table exists (it should) and retry with
+ normal timeout if it didn't exists.
+ Note that for CREATE TABLE IF EXISTS we only generate a warning
+ but still return TRUE (to abort the calling open_table() function).
+ On must check THD->is_error() if one wants to distinguish between warning
+ and error.
+*/
+
+bool
+lock_table_names(THD *thd,
+ TABLE_LIST *tables_start, TABLE_LIST *tables_end,
+ ulong lock_wait_timeout, uint flags)
+{
+ MDL_request_list mdl_requests;
+ TABLE_LIST *table;
+ MDL_request global_request;
+ Hash_set<TABLE_LIST> schema_set(schema_set_get_key);
+ ulong org_lock_wait_timeout= lock_wait_timeout;
+ /* Check if we are using CREATE TABLE ... IF NOT EXISTS */
+ bool create_table;
+ Dummy_error_handler error_handler;
+ DBUG_ENTER("lock_table_names");
+
+ DBUG_ASSERT(!thd->locked_tables_mode);
+
+ for (table= tables_start; table && table != tables_end;
+ table= table->next_global)
+ {
+ if (table->mdl_request.type < MDL_SHARED_UPGRADABLE ||
+ table->open_type == OT_TEMPORARY_ONLY ||
+ (table->open_type == OT_TEMPORARY_OR_BASE && is_temporary_table(table)))
+ {
+ continue;
+ }
+
+ /* Write lock on normal tables is not allowed in a read only transaction. */
+ if (thd->tx_read_only)
+ {
+ my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ if (! (flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK) &&
+ schema_set.insert(table))
+ DBUG_RETURN(TRUE);
+
+ mdl_requests.push_front(&table->mdl_request);
+ }
+
+ if (mdl_requests.is_empty())
+ DBUG_RETURN(FALSE);
+
+ /* Check if CREATE TABLE without REPLACE was used */
+ create_table= (thd->lex->sql_command == SQLCOM_CREATE_TABLE &&
+ !(thd->lex->create_info.options & HA_LEX_CREATE_REPLACE));
+
+ if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK))
+ {
+ /*
+ Scoped locks: Take intention exclusive locks on all involved
+ schemas.
+ */
+ Hash_set<TABLE_LIST>::Iterator it(schema_set);
+ while ((table= it++))
+ {
+ MDL_request *schema_request= new (thd->mem_root) MDL_request;
+ if (schema_request == NULL)
+ DBUG_RETURN(TRUE);
+ schema_request->init(MDL_key::SCHEMA, table->db, "",
+ MDL_INTENTION_EXCLUSIVE,
+ MDL_TRANSACTION);
+ mdl_requests.push_front(schema_request);
+ }
+
+ /*
+ Protect this statement against concurrent global read lock
+ by acquiring global intention exclusive lock with statement
+ duration.
+ */
+ if (thd->global_read_lock.can_acquire_protection())
+ DBUG_RETURN(TRUE);
+ global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_STATEMENT);
+ mdl_requests.push_front(&global_request);
+
+ if (create_table)
+ lock_wait_timeout= 0; // Don't wait for timeout
+ }
+
+ for (;;)
+ {
+ if (create_table)
+ thd->push_internal_handler(&error_handler); // Avoid warnings & errors
+ bool res= thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout);
+ if (create_table)
+ thd->pop_internal_handler();
+ if (!res)
+ DBUG_RETURN(FALSE); // Got locks
+
+ if (!create_table)
+ DBUG_RETURN(TRUE); // Return original error
+
+ /*
+ We come here in the case of lock timeout when executing CREATE TABLE.
+ Verify that table does exist (it usually does, as we got a lock conflict)
+ */
+ if (ha_table_exists(thd, tables_start->db, tables_start->table_name))
+ {
+ if (thd->lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
+ tables_start->table_name);
+ }
+ else
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), tables_start->table_name);
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ We got error from acquire_locks, but the table didn't exists.
+ This could happen if another connection runs a statement
+ involving this non-existent table, and this statement took the mdl,
+ but didn't error out with ER_NO_SUCH_TABLE yet (yes, a race condition).
+ We play safe and restart the original acquire_locks with the
+ original timeout.
+ */
+ create_table= 0;
+ lock_wait_timeout= org_lock_wait_timeout;
+ }
+}
+
+
+/**
+ Check for upgradable (SNW, SNRW) metadata locks on tables to be opened
+ for a DDL statement. Under LOCK TABLES, we can't take new locks, so we
+ must check if appropriate locks were pre-acquired.
+
+ @param thd Thread context.
+ @param tables_start Start of list of tables on which upgradable locks
+ should be searched for.
+ @param tables_end End of list of tables.
+ @param flags Bitmap of flags to modify how the tables will be
+ open, see open_table() description for details.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. connection was killed)
+*/
+
+static bool
+open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
+ TABLE_LIST *tables_end, uint flags)
+{
+ TABLE_LIST *table;
+
+ DBUG_ASSERT(thd->locked_tables_mode);
+
+ for (table= tables_start; table && table != tables_end;
+ table= table->next_global)
+ {
+ if (table->mdl_request.type < MDL_SHARED_UPGRADABLE ||
+ table->open_type == OT_TEMPORARY_ONLY ||
+ (table->open_type == OT_TEMPORARY_OR_BASE && is_temporary_table(table)))
+ {
+ continue;
+ }
+
+ /*
+ We don't need to do anything about the found TABLE instance as it
+ will be handled later in open_tables(), we only need to check that
+ an upgradable lock is already acquired. When we enter LOCK TABLES
+ mode, SNRW locks are acquired before all other locks. So if under
+ LOCK TABLES we find that there is TABLE instance with upgradeable
+ lock, all other instances of TABLE for the same table will have the
+ same ticket.
+
+ Note that this works OK even for CREATE TABLE statements which
+ request X type of metadata lock. This is because under LOCK TABLES
+ such statements don't create the table but only check if it exists
+ or, in most complex case, only insert into it.
+ Thus SNRW lock should be enough.
+
+ Note that find_table_for_mdl_upgrade() will report an error if
+ no suitable ticket is found.
+ */
+ if (!find_table_for_mdl_upgrade(thd, table->db, table->table_name, false))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/**
+ Open all tables in list
+
+ @param[in] thd Thread context.
+ @param[in,out] start List of tables to be open (it can be adjusted for
+ statement that uses tables only implicitly, e.g.
+ for "SELECT f1()").
+ @param[out] counter Number of tables which were open.
+ @param[in] flags Bitmap of flags to modify how the tables will be
+ open, see open_table() description for details.
+ @param[in] prelocking_strategy Strategy which specifies how prelocking
+ algorithm should work for this statement.
+
+ @note
+ Unless we are already in prelocked mode and prelocking strategy prescribes
+ so this function will also precache all SP/SFs explicitly or implicitly
+ (via views and triggers) used by the query and add tables needed for their
+ execution to table list. Statement that uses SFs, invokes triggers or
+ requires foreign key checks will be marked as requiring prelocking.
+ Prelocked mode will be enabled for such query during lock_tables() call.
+
+ If query for which we are opening tables is already marked as requiring
+ prelocking it won't do such precaching and will simply reuse table list
+ which is already built.
+
+ @retval FALSE Success.
+ @retval TRUE Error, reported.
+*/
+
+bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
+ Prelocking_strategy *prelocking_strategy)
+{
+ /*
+ We use pointers to "next_global" member in the last processed
+ TABLE_LIST element and to the "next" member in the last processed
+ Sroutine_hash_entry element as iterators over, correspondingly,
+ the table list and stored routines list which stay valid and allow
+ to continue iteration when new elements are added to the tail of
+ the lists.
+ */
+ TABLE_LIST **table_to_open;
+ Sroutine_hash_entry **sroutine_to_open;
+ TABLE_LIST *tables;
+ Open_table_context ot_ctx(thd, flags);
+ bool error= FALSE;
+ MEM_ROOT new_frm_mem;
+ bool some_routine_modifies_data= FALSE;
+ bool has_prelocking_list;
+ DBUG_ENTER("open_tables");
+
+ /* Accessing data in XA_IDLE or XA_PREPARED is not allowed. */
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ if (*start && (xa_state == XA_IDLE || xa_state == XA_PREPARED))
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ DBUG_RETURN(true);
+ }
+
+ /*
+ Initialize temporary MEM_ROOT for new .FRM parsing. Do not alloctaate
+ anything yet, to avoid penalty for statements which don't use views
+ and thus new .FRM format.
+ */
+ init_sql_alloc(&new_frm_mem, 8024, 0, MYF(0));
+
+ thd->current_tablenr= 0;
+restart:
+ /*
+ Close HANDLER tables which are marked for flush or against which there
+ are pending exclusive metadata locks. This is needed both in order to
+ avoid deadlocks and to have a point during statement execution at
+ which such HANDLERs are closed even if they don't create problems for
+ the current session (i.e. to avoid having a DDL blocked by HANDLERs
+ opened for a long time).
+ */
+ if (thd->handler_tables_hash.records)
+ mysql_ha_flush(thd);
+
+ has_prelocking_list= thd->lex->requires_prelocking();
+ table_to_open= start;
+ sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first;
+ *counter= 0;
+ THD_STAGE_INFO(thd, stage_opening_tables);
+
+ /*
+ If we are executing LOCK TABLES statement or a DDL statement
+ (in non-LOCK TABLES mode) we might have to acquire upgradable
+ semi-exclusive metadata locks (SNW or SNRW) on some of the
+ tables to be opened.
+ When executing CREATE TABLE .. If NOT EXISTS .. SELECT, the
+ table may not yet exist, in which case we acquire an exclusive
+ lock.
+ We acquire all such locks at once here as doing this in one
+ by one fashion may lead to deadlocks or starvation. Later when
+ we will be opening corresponding table pre-acquired metadata
+ lock will be reused (thanks to the fact that in recursive case
+ metadata locks are acquired without waiting).
+ */
+ if (! (flags & (MYSQL_OPEN_HAS_MDL_LOCK |
+ MYSQL_OPEN_FORCE_SHARED_MDL |
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)))
+ {
+ if (thd->locked_tables_mode)
+ {
+ /*
+ Under LOCK TABLES, we can't acquire new locks, so we instead
+ need to check if appropriate locks were pre-acquired.
+ */
+ if (open_tables_check_upgradable_mdl(thd, *start,
+ thd->lex->first_not_own_table(),
+ flags))
+ {
+ error= TRUE;
+ goto err;
+ }
+ }
+ else
+ {
+ TABLE_LIST *table;
+ if (lock_table_names(thd, *start, thd->lex->first_not_own_table(),
+ ot_ctx.get_timeout(), flags))
+ {
+ error= TRUE;
+ goto err;
+ }
+ for (table= *start; table && table != thd->lex->first_not_own_table();
+ table= table->next_global)
+ {
+ if (table->mdl_request.type >= MDL_SHARED_UPGRADABLE)
+ table->mdl_request.ticket= NULL;
+ }
+ }
+ }
+
+ /*
+ Perform steps of prelocking algorithm until there are unprocessed
+ elements in prelocking list/set.
+ */
+ while (*table_to_open ||
+ (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
+ *sroutine_to_open))
+ {
+ /*
+ For every table in the list of tables to open, try to find or open
+ a table.
+ */
+ for (tables= *table_to_open; tables;
+ table_to_open= &tables->next_global, tables= tables->next_global)
+ {
+ error= open_and_process_table(thd, thd->lex, tables, counter,
+ flags, prelocking_strategy,
+ has_prelocking_list, &ot_ctx,
+ &new_frm_mem);
+
+ if (error)
+ {
+ if (ot_ctx.can_recover_from_failed_open())
+ {
+ /*
+ We have met exclusive metadata lock or old version of table.
+ Now we have to close all tables and release metadata locks.
+ We also have to throw away set of prelocked tables (and thus
+ close tables from this set that were open by now) since it
+ is possible that one of tables which determined its content
+ was changed.
+
+ Instead of implementing complex/non-robust logic mentioned
+ above we simply close and then reopen all tables.
+
+ We have to save pointer to table list element for table which we
+ have failed to open since closing tables can trigger removal of
+ elements from the table list (if MERGE tables are involved),
+ */
+ close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp());
+
+ /*
+ Here we rely on the fact that 'tables' still points to the valid
+ TABLE_LIST element. Altough currently this assumption is valid
+ it may change in future.
+ */
+ if (ot_ctx.recover_from_failed_open())
+ goto err;
+
+ /* Re-open temporary tables after close_tables_for_reopen(). */
+ if (open_temporary_tables(thd, *start))
+ goto err;
+
+ error= FALSE;
+ goto restart;
+ }
+ goto err;
+ }
+
+ DEBUG_SYNC(thd, "open_tables_after_open_and_process_table");
+ }
+
+ /*
+ If we are not already in prelocked mode and extended table list is
+ not yet built for our statement we need to cache routines it uses
+ and build the prelocking list for it.
+ If we are not in prelocked mode but have built the extended table
+ list, we still need to call open_and_process_routine() to take
+ MDL locks on the routines.
+ */
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ {
+ /*
+ Process elements of the prelocking set which are present there
+ since parsing stage or were added to it by invocations of
+ Prelocking_strategy methods in the above loop over tables.
+
+ For example, if element is a routine, cache it and then,
+ if prelocking strategy prescribes so, add tables it uses to the
+ table list and routines it might invoke to the prelocking set.
+ */
+ for (Sroutine_hash_entry *rt= *sroutine_to_open; rt;
+ sroutine_to_open= &rt->next, rt= rt->next)
+ {
+ bool need_prelocking= false;
+ bool routine_modifies_data;
+ TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
+
+ error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy,
+ has_prelocking_list, &ot_ctx,
+ &need_prelocking,
+ &routine_modifies_data);
+
+ // Remember if any of SF modifies data.
+ some_routine_modifies_data|= routine_modifies_data;
+
+ if (need_prelocking && ! thd->lex->requires_prelocking())
+ thd->lex->mark_as_requiring_prelocking(save_query_tables_last);
+
+ if (need_prelocking && ! *start)
+ *start= thd->lex->query_tables;
+
+ if (error)
+ {
+ if (ot_ctx.can_recover_from_failed_open())
+ {
+ close_tables_for_reopen(thd, start,
+ ot_ctx.start_of_statement_svp());
+ if (ot_ctx.recover_from_failed_open())
+ goto err;
+
+ /* Re-open temporary tables after close_tables_for_reopen(). */
+ if (open_temporary_tables(thd, *start))
+ goto err;
+
+ error= FALSE;
+ goto restart;
+ }
+ /*
+ Serious error during reading stored routines from mysql.proc table.
+ Something is wrong with the table or its contents, and an error has
+ been emitted; we must abort.
+ */
+ goto err;
+ }
+ }
+ }
+ }
+
+ /*
+ After successful open of all tables, including MERGE parents and
+ children, attach the children to their parents. At end of statement,
+ the children are detached. Attaching and detaching are always done,
+ even under LOCK TABLES.
+
+ We also convert all TL_WRITE_DEFAULT and TL_READ_DEFAULT locks to
+ appropriate "real" lock types to be used for locking and to be passed
+ to storage engine.
+ */
+ for (tables= *start; tables; tables= tables->next_global)
+ {
+ TABLE *tbl= tables->table;
+
+ /* Schema tables may not have a TABLE object here. */
+ if (tbl && tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM)
+ {
+ /* MERGE tables need to access parent and child TABLE_LISTs. */
+ DBUG_ASSERT(tbl->pos_in_table_list == tables);
+ if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN))
+ {
+ error= TRUE;
+ goto err;
+ }
+ }
+
+ /* Set appropriate TABLE::lock_type. */
+ if (tbl && tables->lock_type != TL_UNLOCK && !thd->locked_tables_mode)
+ {
+ if (tables->lock_type == TL_WRITE_DEFAULT)
+ tbl->reginfo.lock_type= thd->update_lock_default;
+ else if (tables->lock_type == TL_READ_DEFAULT)
+ tbl->reginfo.lock_type=
+ read_lock_type_for_table(thd, thd->lex, tables,
+ some_routine_modifies_data);
+ else
+ tbl->reginfo.lock_type= tables->lock_type;
+ }
+ }
+
+err:
+ THD_STAGE_INFO(thd, stage_after_opening_tables);
+ free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block
+
+ if (error && *table_to_open)
+ {
+ (*table_to_open)->table= NULL;
+ }
+ DBUG_PRINT("open_tables", ("returning: %d", (int) error));
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Defines how prelocking algorithm for DML statements should handle routines:
+ - For CALL statements we do unrolling (i.e. open and lock tables for each
+ sub-statement individually). So for such statements prelocking is enabled
+ only if stored functions are used in parameter list and only for period
+ during which we calculate values of parameters. Thus in this strategy we
+ ignore procedure which is directly called by such statement and extend
+ the prelocking set only with tables/functions used by SF called from the
+ parameter list.
+ - For any other statement any routine which is directly or indirectly called
+ by statement is going to be executed in prelocked mode. So in this case we
+ simply add all tables and routines used by it to the prelocking set.
+
+ @param[in] thd Thread context.
+ @param[in] prelocking_ctx Prelocking context of the statement.
+ @param[in] rt Prelocking set element describing routine.
+ @param[in] sp Routine body.
+ @param[out] need_prelocking Set to TRUE if method detects that prelocking
+ required, not changed otherwise.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+bool DML_prelocking_strategy::
+handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking)
+{
+ /*
+ We assume that for any "CALL proc(...)" statement sroutines_list will
+ have 'proc' as first element (it may have several, consider e.g.
+ "proc(sp_func(...)))". This property is currently guaranted by the
+ parser.
+ */
+
+ if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first ||
+ rt->mdl_request.key.mdl_namespace() != MDL_key::PROCEDURE)
+ {
+ *need_prelocking= TRUE;
+ sp_update_stmt_used_routines(thd, prelocking_ctx, &sp->m_sroutines,
+ rt->belong_to_view);
+ (void)sp->add_used_tables_to_table_list(thd,
+ &prelocking_ctx->query_tables_last,
+ rt->belong_to_view);
+ }
+ sp->propagate_attributes(prelocking_ctx);
+ return FALSE;
+}
+
+
+/**
+ Defines how prelocking algorithm for DML statements should handle table list
+ elements:
+ - If table has triggers we should add all tables and routines
+ used by them to the prelocking set.
+
+ We do not need to acquire metadata locks on trigger names
+ in DML statements, since all DDL statements
+ that change trigger metadata always lock their
+ subject tables.
+
+ @param[in] thd Thread context.
+ @param[in] prelocking_ctx Prelocking context of the statement.
+ @param[in] table_list Table list element for table.
+ @param[in] sp Routine body.
+ @param[out] need_prelocking Set to TRUE if method detects that prelocking
+ required, not changed otherwise.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+bool DML_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ /* We rely on a caller to check that table is going to be changed. */
+ DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);
+
+ if (table_list->trg_event_map)
+ {
+ if (table_list->table->triggers)
+ {
+ *need_prelocking= TRUE;
+
+ if (table_list->table->triggers->
+ add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/**
+ Defines how prelocking algorithm for DML statements should handle view -
+ all view routines should be added to the prelocking set.
+
+ @param[in] thd Thread context.
+ @param[in] prelocking_ctx Prelocking context of the statement.
+ @param[in] table_list Table list element for view.
+ @param[in] sp Routine body.
+ @param[out] need_prelocking Set to TRUE if method detects that prelocking
+ required, not changed otherwise.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+bool DML_prelocking_strategy::
+handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ if (table_list->view->uses_stored_routines())
+ {
+ *need_prelocking= TRUE;
+
+ sp_update_stmt_used_routines(thd, prelocking_ctx,
+ &table_list->view->sroutines_list,
+ table_list->top_table());
+ }
+ return FALSE;
+}
+
+
+/**
+ Defines how prelocking algorithm for LOCK TABLES statement should handle
+ table list elements.
+
+ @param[in] thd Thread context.
+ @param[in] prelocking_ctx Prelocking context of the statement.
+ @param[in] table_list Table list element for table.
+ @param[in] sp Routine body.
+ @param[out] need_prelocking Set to TRUE if method detects that prelocking
+ required, not changed otherwise.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+bool Lock_tables_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ if (DML_prelocking_strategy::handle_table(thd, prelocking_ctx, table_list,
+ need_prelocking))
+ return TRUE;
+
+ /* We rely on a caller to check that table is going to be changed. */
+ DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);
+
+ return FALSE;
+}
+
+
+/**
+ Defines how prelocking algorithm for ALTER TABLE statement should handle
+ routines - do nothing as this statement is not supposed to call routines.
+
+ We still can end up in this method when someone tries
+ to define a foreign key referencing a view, and not just
+ a simple view, but one that uses stored routines.
+*/
+
+bool Alter_table_prelocking_strategy::
+handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking)
+{
+ return FALSE;
+}
+
+
+/**
+ Defines how prelocking algorithm for ALTER TABLE statement should handle
+ table list elements.
+
+ Unlike in DML, we do not process triggers here.
+
+ @param[in] thd Thread context.
+ @param[in] prelocking_ctx Prelocking context of the statement.
+ @param[in] table_list Table list element for table.
+ @param[in] sp Routine body.
+ @param[out] need_prelocking Set to TRUE if method detects that prelocking
+ required, not changed otherwise.
+
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+bool Alter_table_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ return FALSE;
+}
+
+
+/**
+ Defines how prelocking algorithm for ALTER TABLE statement
+ should handle view - do nothing. We don't need to add view
+ routines to the prelocking set in this case as view is not going
+ to be materialized.
+*/
+
+bool Alter_table_prelocking_strategy::
+handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ return FALSE;
+}
+
+
+/**
+ Check that lock is ok for tables; Call start stmt if ok
+
+ @param thd Thread handle.
+ @param prelocking_ctx Prelocking context.
+ @param table_list Table list element for table to be checked.
+
+ @retval FALSE - Ok.
+ @retval TRUE - Error.
+*/
+
+static bool check_lock_and_start_stmt(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list)
+{
+ int error;
+ thr_lock_type lock_type;
+ DBUG_ENTER("check_lock_and_start_stmt");
+
+ /*
+ Prelocking placeholder is not set for TABLE_LIST that
+ are directly used by TOP level statement.
+ */
+ DBUG_ASSERT(table_list->prelocking_placeholder == false);
+
+ /*
+ TL_WRITE_DEFAULT and TL_READ_DEFAULT are supposed to be parser only
+ types of locks so they should be converted to appropriate other types
+ to be passed to storage engine. The exact lock type passed to the
+ engine is important as, for example, InnoDB uses it to determine
+ what kind of row locks should be acquired when executing statement
+ in prelocked mode or under LOCK TABLES with @@innodb_table_locks = 0.
+
+ Last argument routine_modifies_data for read_lock_type_for_table()
+ is ignored, as prelocking placeholder will never be set here.
+ */
+ DBUG_ASSERT(table_list->prelocking_placeholder == false);
+ if (table_list->lock_type == TL_WRITE_DEFAULT)
+ lock_type= thd->update_lock_default;
+ else if (table_list->lock_type == TL_READ_DEFAULT)
+ lock_type= read_lock_type_for_table(thd, prelocking_ctx, table_list, true);
+ else
+ lock_type= table_list->lock_type;
+
+ if ((int) lock_type > (int) TL_WRITE_ALLOW_WRITE &&
+ (int) table_list->table->reginfo.lock_type <= (int) TL_WRITE_ALLOW_WRITE)
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+ table_list->table->alias.c_ptr());
+ DBUG_RETURN(1);
+ }
+ if ((error= table_list->table->file->start_stmt(thd, lock_type)))
+ {
+ table_list->table->file->print_error(error, MYF(0));
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Open and lock one table
+
+ @param[in] thd thread handle
+ @param[in] table_l table to open is first table in this list
+ @param[in] lock_type lock to use for table
+ @param[in] flags options to be used while opening and locking
+ table (see open_table(), mysql_lock_tables())
+ @param[in] prelocking_strategy Strategy which specifies how prelocking
+ algorithm should work for this statement.
+
+ @return table
+ @retval != NULL OK, opened table returned
+ @retval NULL Error
+
+ @note
+ If ok, the following are also set:
+ table_list->lock_type lock_type
+ table_list->table table
+
+ @note
+ If table_l is a list, not a single table, the list is temporarily
+ broken.
+
+ @detail
+ This function is meant as a replacement for open_ltable() when
+ MERGE tables can be opened. open_ltable() cannot open MERGE tables.
+
+ There may be more differences between open_n_lock_single_table() and
+ open_ltable(). One known difference is that open_ltable() does
+ neither call thd->decide_logging_format() nor handle some other logging
+ and locking issues because it does not call lock_tables().
+*/
+
+TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l,
+ thr_lock_type lock_type, uint flags,
+ Prelocking_strategy *prelocking_strategy)
+{
+ TABLE_LIST *save_next_global;
+ DBUG_ENTER("open_n_lock_single_table");
+
+ /* Remember old 'next' pointer. */
+ save_next_global= table_l->next_global;
+ /* Break list. */
+ table_l->next_global= NULL;
+
+ /* Set requested lock type. */
+ table_l->lock_type= lock_type;
+ /* Allow to open real tables only. */
+ table_l->required_type= FRMTYPE_TABLE;
+
+ /* Open the table. */
+ if (open_and_lock_tables(thd, table_l, FALSE, flags,
+ prelocking_strategy))
+ table_l->table= NULL; /* Just to be sure. */
+
+ /* Restore list. */
+ table_l->next_global= save_next_global;
+
+ DBUG_RETURN(table_l->table);
+}
+
+
+/*
+ Open and lock one table
+
+ SYNOPSIS
+ open_ltable()
+ thd Thread handler
+ table_list Table to open is first table in this list
+ lock_type Lock to use for open
+ lock_flags Flags passed to mysql_lock_table
+
+ NOTE
+ This function doesn't do anything like SP/SF/views/triggers analysis done
+ in open_table()/lock_tables(). It is intended for opening of only one
+ concrete table. And used only in special contexts.
+
+ RETURN VALUES
+ table Opened table
+ 0 Error
+
+ If ok, the following are also set:
+ table_list->lock_type lock_type
+ table_list->table table
+*/
+
+TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
+ uint lock_flags)
+{
+ TABLE *table;
+ Open_table_context ot_ctx(thd, lock_flags);
+ bool error;
+ DBUG_ENTER("open_ltable");
+
+ /* Ignore temporary tables as they have already ben opened*/
+ if (table_list->table)
+ DBUG_RETURN(table_list->table);
+
+ /* should not be used in a prelocked_mode context, see NOTE above */
+ DBUG_ASSERT(thd->locked_tables_mode < LTM_PRELOCKED);
+
+ THD_STAGE_INFO(thd, stage_opening_tables);
+ thd->current_tablenr= 0;
+ /* open_ltable can be used only for BASIC TABLEs */
+ table_list->required_type= FRMTYPE_TABLE;
+
+ /* This function can't properly handle requests for such metadata locks. */
+ DBUG_ASSERT(table_list->mdl_request.type < MDL_SHARED_UPGRADABLE);
+
+ while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx)) &&
+ ot_ctx.can_recover_from_failed_open())
+ {
+ /*
+ Even though we have failed to open table we still need to
+ call release_transactional_locks() to release metadata locks which
+ might have been acquired successfully.
+ */
+ thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp());
+ table_list->mdl_request.ticket= 0;
+ if (ot_ctx.recover_from_failed_open())
+ break;
+ }
+
+ if (!error)
+ {
+ /*
+ We can't have a view or some special "open_strategy" in this function
+ so there should be a TABLE instance.
+ */
+ DBUG_ASSERT(table_list->table);
+ table= table_list->table;
+ if (table->file->ht->db_type == DB_TYPE_MRG_MYISAM)
+ {
+ /* A MERGE table must not come here. */
+ /* purecov: begin tested */
+ my_error(ER_WRONG_OBJECT, MYF(0), table->s->db.str,
+ table->s->table_name.str, "BASE TABLE");
+ table= 0;
+ goto end;
+ /* purecov: end */
+ }
+
+ table_list->lock_type= lock_type;
+ table->grant= table_list->grant;
+ if (thd->locked_tables_mode)
+ {
+ if (check_lock_and_start_stmt(thd, thd->lex, table_list))
+ table= 0;
+ }
+ else
+ {
+ DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
+ if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK)
+ if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1,
+ lock_flags)))
+ {
+ table= 0;
+ }
+ }
+ }
+ else
+ table= 0;
+
+end:
+ if (table == NULL)
+ {
+ if (!thd->in_sub_stmt)
+ trans_rollback_stmt(thd);
+ close_thread_tables(thd);
+ }
+ THD_STAGE_INFO(thd, stage_after_opening_tables);
+ DBUG_RETURN(table);
+}
+
+
+/**
+ Open all tables in list, locks them and optionally process derived tables.
+
+ @param thd Thread context.
+ @param tables List of tables for open and locking.
+ @param derived If to handle derived tables.
+ @param flags Bitmap of options to be used to open and lock
+ tables (see open_tables() and mysql_lock_tables()
+ for details).
+ @param prelocking_strategy Strategy which specifies how prelocking algorithm
+ should work for this statement.
+
+ @note
+ The thr_lock locks will automatically be freed by
+ close_thread_tables().
+
+ @retval FALSE OK.
+ @retval TRUE Error
+*/
+
+bool open_and_lock_tables(THD *thd, TABLE_LIST *tables,
+ bool derived, uint flags,
+ Prelocking_strategy *prelocking_strategy)
+{
+ uint counter;
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ DBUG_ENTER("open_and_lock_tables");
+ DBUG_PRINT("enter", ("derived handling: %d", derived));
+
+ if (open_tables(thd, &tables, &counter, flags, prelocking_strategy))
+ goto err;
+
+ DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", {
+ const char *old_proc_info= thd->proc_info;
+ thd->proc_info= "DBUG sleep";
+ my_sleep(6000000);
+ thd->proc_info= old_proc_info;});
+
+ if (lock_tables(thd, tables, counter, flags))
+ goto err;
+
+ (void) read_statistics_for_tables_if_needed(thd, tables);
+
+ if (derived)
+ {
+ if (mysql_handle_derived(thd->lex, DT_INIT))
+ goto err;
+ if (thd->prepare_derived_at_open &&
+ (mysql_handle_derived(thd->lex, DT_PREPARE)))
+ goto err;
+ }
+
+ DBUG_RETURN(FALSE);
+err:
+ if (! thd->in_sub_stmt)
+ trans_rollback_stmt(thd); /* Necessary if derived handling failed. */
+ close_thread_tables(thd);
+ /* Don't keep locks for a failed statement. */
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Open all tables in list and process derived tables
+
+ SYNOPSIS
+ open_normal_and_derived_tables
+ thd - thread handler
+ tables - list of tables for open
+ flags - bitmap of flags to modify how the tables will be open:
+ MYSQL_LOCK_IGNORE_FLUSH - open table even if someone has
+ done a flush on it.
+ dt_phases - set of flags to pass to the mysql_handle_derived
+
+ RETURN
+ FALSE - ok
+ TRUE - error
+
+ NOTE
+ This is to be used on prepare stage when you don't read any
+ data from the tables.
+*/
+
+bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags,
+ uint dt_phases)
+{
+ DML_prelocking_strategy prelocking_strategy;
+ uint counter;
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ DBUG_ENTER("open_normal_and_derived_tables");
+ DBUG_ASSERT(!thd->fill_derived_tables());
+ if (open_tables(thd, &tables, &counter, flags, &prelocking_strategy) ||
+ mysql_handle_derived(thd->lex, dt_phases))
+ goto end;
+
+ DBUG_RETURN(0);
+end:
+ /*
+ No need to commit/rollback the statement transaction: it's
+ either not started or we're filling in an INFORMATION_SCHEMA
+ table on the fly, and thus mustn't manipulate with the
+ transaction of the enclosing statement.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty() ||
+ (thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+ close_thread_tables(thd);
+ /* Don't keep locks for a failed statement. */
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+}
+
+
+/*
+ Mark all real tables in the list as free for reuse.
+
+ SYNOPSIS
+ mark_real_tables_as_free_for_reuse()
+ thd - thread context
+ table - head of the list of tables
+
+ DESCRIPTION
+ Marks all real tables in the list (i.e. not views, derived
+ or schema tables) as free for reuse.
+*/
+
+static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table_list)
+{
+ TABLE_LIST *table;
+ for (table= table_list; table; table= table->next_global)
+ if (!table->placeholder())
+ {
+ table->table->query_id= 0;
+ }
+ for (table= table_list; table; table= table->next_global)
+ if (!table->placeholder())
+ {
+ /*
+ Detach children of MyISAMMRG tables used in
+ sub-statements, they will be reattached at open.
+ This has to be done in a separate loop to make sure
+ that children have had their query_id cleared.
+ */
+ table->table->file->extra(HA_EXTRA_DETACH_CHILDREN);
+ }
+}
+
+
+/**
+ Lock all tables in a list.
+
+ @param thd Thread handler
+ @param tables Tables to lock
+ @param count Number of opened tables
+ @param flags Options (see mysql_lock_tables() for details)
+
+ You can't call lock_tables() while holding thr_lock locks, as
+ this would break the dead-lock-free handling thr_lock gives us.
+ You must always get all needed locks at once.
+
+ If the query for which we are calling this function is marked as
+ requiring prelocking, this function will change
+ locked_tables_mode to LTM_PRELOCKED.
+
+ @retval FALSE Success.
+ @retval TRUE A lock wait timeout, deadlock or out of memory.
+*/
+
+bool lock_tables(THD *thd, TABLE_LIST *tables, uint count,
+ uint flags)
+{
+ TABLE_LIST *table;
+ DBUG_ENTER("lock_tables");
+ /*
+ We can't meet statement requiring prelocking if we already
+ in prelocked mode.
+ */
+ DBUG_ASSERT(thd->locked_tables_mode <= LTM_LOCK_TABLES ||
+ !thd->lex->requires_prelocking());
+
+ if (!tables && !thd->lex->requires_prelocking())
+ DBUG_RETURN(thd->decide_logging_format(tables));
+
+ /*
+ Check for thd->locked_tables_mode to avoid a redundant
+ and harmful attempt to lock the already locked tables again.
+ Checking for thd->lock is not enough in some situations. For example,
+ if a stored function contains
+ "drop table t3; create temporary t3 ..; insert into t3 ...;"
+ thd->lock may be 0 after drop tables, whereas locked_tables_mode
+ is still on. In this situation an attempt to lock temporary
+ table t3 will lead to a memory leak.
+ */
+ if (! thd->locked_tables_mode)
+ {
+ DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
+ TABLE **start,**ptr;
+
+ if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count)))
+ DBUG_RETURN(TRUE);
+ for (table= tables; table; table= table->next_global)
+ {
+ if (!table->placeholder())
+ *(ptr++)= table->table;
+ }
+
+ /*
+ DML statements that modify a table with an auto_increment column based on
+ rows selected from a table are unsafe as the order in which the rows are
+ fetched fron the select tables cannot be determined and may differ on
+ master and slave.
+ */
+ if (thd->variables.binlog_format != BINLOG_FORMAT_ROW && tables &&
+ has_write_table_with_auto_increment_and_select(tables))
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_WRITE_AUTOINC_SELECT);
+ /* Todo: merge all has_write_table_auto_inc with decide_logging_format */
+ if (thd->variables.binlog_format != BINLOG_FORMAT_ROW && tables)
+ {
+ if (has_write_table_auto_increment_not_first_in_pk(tables))
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_AUTOINC_NOT_FIRST);
+ }
+
+#ifdef NOT_USED_IN_MARIADB
+ /*
+ INSERT...ON DUPLICATE KEY UPDATE on a table with more than one unique keys
+ can be unsafe.
+ */
+ uint unique_keys= 0;
+ for (TABLE_LIST *query_table= tables; query_table && unique_keys <= 1;
+ query_table= query_table->next_global)
+ if(query_table->table)
+ {
+ uint keys= query_table->table->s->keys, i= 0;
+ unique_keys= 0;
+ for (KEY* keyinfo= query_table->table->s->key_info;
+ i < keys && unique_keys <= 1; i++, keyinfo++)
+ {
+ if (keyinfo->flags & HA_NOSAME)
+ unique_keys++;
+ }
+ if (!query_table->placeholder() &&
+ query_table->lock_type >= TL_WRITE_ALLOW_WRITE &&
+ unique_keys > 1 && thd->lex->sql_command == SQLCOM_INSERT &&
+ /* Duplicate key update is not supported by INSERT DELAYED */
+ thd->get_command() != COM_DELAYED_INSERT &&
+ thd->lex->duplicates == DUP_UPDATE)
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_TWO_KEYS);
+ }
+#endif
+
+ /* We have to emulate LOCK TABLES if we are statement needs prelocking. */
+ if (thd->lex->requires_prelocking())
+ {
+
+ /*
+ A query that modifies autoinc column in sub-statement can make the
+ master and slave inconsistent.
+ We can solve these problems in mixed mode by switching to binlogging
+ if at least one updated table is used by sub-statement
+ */
+ if (thd->variables.binlog_format != BINLOG_FORMAT_ROW && tables &&
+ has_write_table_with_auto_increment(thd->lex->first_not_own_table()))
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_AUTOINC_COLUMNS);
+ }
+
+ DEBUG_SYNC(thd, "before_lock_tables_takes_lock");
+
+ if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start),
+ flags)))
+ DBUG_RETURN(TRUE);
+
+ DEBUG_SYNC(thd, "after_lock_tables_takes_lock");
+
+ if (thd->lex->requires_prelocking() &&
+ thd->lex->sql_command != SQLCOM_LOCK_TABLES)
+ {
+ TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
+ /*
+ We just have done implicit LOCK TABLES, and now we have
+ to emulate first open_and_lock_tables() after it.
+
+ When open_and_lock_tables() is called for a single table out of
+ a table list, the 'next_global' chain is temporarily broken. We
+ may not find 'first_not_own' before the end of the "list".
+ Look for example at those places where open_n_lock_single_table()
+ is called. That function implements the temporary breaking of
+ a table list for opening a single table.
+ */
+ for (table= tables;
+ table && table != first_not_own;
+ table= table->next_global)
+ {
+ if (!table->placeholder())
+ {
+ table->table->query_id= thd->query_id;
+ if (check_lock_and_start_stmt(thd, thd->lex, table))
+ {
+ mysql_unlock_tables(thd, thd->lock);
+ thd->lock= 0;
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ /*
+ Let us mark all tables which don't belong to the statement itself,
+ and was marked as occupied during open_tables() as free for reuse.
+ */
+ mark_real_tables_as_free_for_reuse(first_not_own);
+ DBUG_PRINT("info",("locked_tables_mode= LTM_PRELOCKED"));
+ thd->enter_locked_tables_mode(LTM_PRELOCKED);
+ }
+ }
+ else
+ {
+ TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
+ /*
+ When open_and_lock_tables() is called for a single table out of
+ a table list, the 'next_global' chain is temporarily broken. We
+ may not find 'first_not_own' before the end of the "list".
+ Look for example at those places where open_n_lock_single_table()
+ is called. That function implements the temporary breaking of
+ a table list for opening a single table.
+ */
+ for (table= tables;
+ table && table != first_not_own;
+ table= table->next_global)
+ {
+ if (table->placeholder())
+ continue;
+
+ /*
+ In a stored function or trigger we should ensure that we won't change
+ a table that is already used by the calling statement.
+ */
+ if (thd->locked_tables_mode >= LTM_PRELOCKED &&
+ table->lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ for (TABLE* opentab= thd->open_tables; opentab; opentab= opentab->next)
+ {
+ if (table->table->s == opentab->s && opentab->query_id &&
+ table->table->query_id != opentab->query_id)
+ {
+ my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0),
+ table->table->s->table_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+
+ if (check_lock_and_start_stmt(thd, thd->lex, table))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ /*
+ If we are under explicit LOCK TABLES and our statement requires
+ prelocking, we should mark all "additional" tables as free for use
+ and enter prelocked mode.
+ */
+ if (thd->lex->requires_prelocking())
+ {
+ mark_real_tables_as_free_for_reuse(first_not_own);
+ DBUG_PRINT("info",
+ ("thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES"));
+ thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES;
+ }
+ }
+
+ DBUG_RETURN(thd->decide_logging_format(tables));
+}
+
+
+/*
+ Restart transaction for tables
+
+ This is used when we had to do an implicit commit after tables are opened
+ and want to restart transactions on tables.
+
+ This is used in case of:
+ LOCK TABLES xx
+ CREATE OR REPLACE TABLE xx;
+*/
+
+bool restart_trans_for_tables(THD *thd, TABLE_LIST *table)
+{
+ DBUG_ENTER("restart_trans_for_tables");
+
+ for (; table; table= table->next_global)
+ {
+ if (table->placeholder())
+ continue;
+
+ if (check_lock_and_start_stmt(thd, thd->lex, table))
+ {
+ DBUG_ASSERT(0); // Should never happen
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Prepare statement for reopening of tables and recalculation of set of
+ prelocked tables.
+
+ @param[in] thd Thread context.
+ @param[in,out] tables List of tables which we were trying to open
+ and lock.
+ @param[in] start_of_statement_svp MDL savepoint which represents the set
+ of metadata locks which the current transaction
+ managed to acquire before execution of the current
+ statement and to which we should revert before
+ trying to reopen tables. NULL if no metadata locks
+ were held and thus all metadata locks should be
+ released.
+*/
+
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables,
+ const MDL_savepoint &start_of_statement_svp)
+{
+ TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
+ TABLE_LIST *tmp;
+
+ /*
+ If table list consists only from tables from prelocking set, table list
+ for new attempt should be empty, so we have to update list's root pointer.
+ */
+ if (first_not_own_table == *tables)
+ *tables= 0;
+ thd->lex->chop_off_not_own_tables();
+ /* Reset MDL tickets for procedures/functions */
+ for (Sroutine_hash_entry *rt=
+ (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
+ rt; rt= rt->next)
+ rt->mdl_request.ticket= NULL;
+ sp_remove_not_own_routines(thd->lex);
+ for (tmp= *tables; tmp; tmp= tmp->next_global)
+ {
+ tmp->table= 0;
+ tmp->mdl_request.ticket= NULL;
+ /* We have to cleanup translation tables of views. */
+ tmp->cleanup_items();
+ }
+ /*
+ No need to commit/rollback the statement transaction: it's
+ either not started or we're filling in an INFORMATION_SCHEMA
+ table on the fly, and thus mustn't manipulate with the
+ transaction of the enclosing statement.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty() ||
+ (thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(start_of_statement_svp);
+}
+
+
+/**
+ Open a single table without table caching and don't add it to
+ THD::open_tables. Depending on the 'add_to_temporary_tables_list' value,
+ the opened TABLE instance will be addded to THD::temporary_tables list.
+
+ @param thd Thread context.
+ @param hton Storage engine of the table, if known,
+ or NULL otherwise.
+ @param path Path (without .frm)
+ @param db Database name.
+ @param table_name Table name.
+ @param add_to_temporary_tables_list Specifies if the opened TABLE
+ instance should be linked into
+ THD::temporary_tables list.
+ @param open_in_engine Indicates that we need to open table
+ in storage engine in addition to
+ constructing TABLE object for it.
+
+ @note This function is used:
+ - by alter_table() to open a temporary table;
+ - when creating a temporary table with CREATE TEMPORARY TABLE.
+
+ @return TABLE instance for opened table.
+ @retval NULL on error.
+*/
+
+TABLE *open_table_uncached(THD *thd, handlerton *hton,
+ const char *path, const char *db,
+ const char *table_name,
+ bool add_to_temporary_tables_list,
+ bool open_in_engine)
+{
+ TABLE *tmp_table;
+ TABLE_SHARE *share;
+ char cache_key[MAX_DBKEY_LENGTH], *saved_cache_key, *tmp_path;
+ uint key_length;
+ DBUG_ENTER("open_table_uncached");
+ DBUG_PRINT("enter",
+ ("table: '%s'.'%s' path: '%s' server_id: %u "
+ "pseudo_thread_id: %lu",
+ db, table_name, path,
+ (uint) thd->variables.server_id,
+ (ulong) thd->variables.pseudo_thread_id));
+
+ if (add_to_temporary_tables_list)
+ {
+ /* Temporary tables are not safe for parallel replication. */
+ if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
+ thd->wait_for_prior_commit())
+ return NULL;
+ }
+
+ /* Create the cache_key for temporary tables */
+ key_length= create_tmp_table_def_key(thd, cache_key, db, table_name);
+
+ if (!(tmp_table= (TABLE*) my_malloc(sizeof(*tmp_table) + sizeof(*share) +
+ strlen(path)+1 + key_length,
+ MYF(MY_WME))))
+ DBUG_RETURN(0); /* purecov: inspected */
+
+ share= (TABLE_SHARE*) (tmp_table+1);
+ tmp_path= (char*) (share+1);
+ saved_cache_key= strmov(tmp_path, path)+1;
+ memcpy(saved_cache_key, cache_key, key_length);
+
+ init_tmp_table_share(thd, share, saved_cache_key, key_length,
+ strend(saved_cache_key)+1, tmp_path);
+ share->db_plugin= ha_lock_engine(thd, hton);
+
+ if (open_table_def(thd, share, GTS_TABLE | GTS_USE_DISCOVERY))
+ {
+ /* No need to lock share->mutex as this is not needed for tmp tables */
+ free_table_share(share);
+ my_free(tmp_table);
+ DBUG_RETURN(0);
+ }
+
+ share->m_psi= PSI_CALL_get_table_share(true, share);
+
+ if (open_table_from_share(thd, share, table_name,
+ open_in_engine ?
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX) : 0,
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
+ ha_open_options,
+ tmp_table,
+ /*
+ Set "is_create_table" if the table does not
+ exist in SE
+ */
+ open_in_engine ? false : true))
+ {
+ /* No need to lock share->mutex as this is not needed for tmp tables */
+ free_table_share(share);
+ my_free(tmp_table);
+ DBUG_RETURN(0);
+ }
+
+ tmp_table->reginfo.lock_type= TL_WRITE; // Simulate locked
+ tmp_table->grant.privilege= TMP_TABLE_ACLS;
+ share->tmp_table= (tmp_table->file->has_transactions() ?
+ TRANSACTIONAL_TMP_TABLE : NON_TRANSACTIONAL_TMP_TABLE);
+
+ if (add_to_temporary_tables_list)
+ {
+ thd->lock_temporary_tables();
+ /* growing temp list at the head */
+ tmp_table->next= thd->temporary_tables;
+ if (tmp_table->next)
+ tmp_table->next->prev= tmp_table;
+ thd->temporary_tables= tmp_table;
+ thd->temporary_tables->prev= 0;
+ if (thd->rgi_slave)
+ {
+ thread_safe_increment32(&slave_open_temp_tables, &thread_running_lock);
+ }
+ thd->unlock_temporary_tables();
+ }
+ tmp_table->pos_in_table_list= 0;
+ DBUG_PRINT("tmptable", ("opened table: '%s'.'%s' 0x%lx", tmp_table->s->db.str,
+ tmp_table->s->table_name.str, (long) tmp_table));
+ DBUG_RETURN(tmp_table);
+}
+
+
+/**
+ Delete a temporary table.
+
+ @param base Handlerton for table to be deleted.
+ @param path Path to the table to be deleted (i.e. path
+ to its .frm without an extension).
+
+ @retval false - success.
+ @retval true - failure.
+*/
+
+bool rm_temporary_table(handlerton *base, const char *path)
+{
+ bool error=0;
+ handler *file;
+ char frm_path[FN_REFLEN + 1];
+ DBUG_ENTER("rm_temporary_table");
+
+ strxnmov(frm_path, sizeof(frm_path) - 1, path, reg_ext, NullS);
+ if (mysql_file_delete(key_file_frm, frm_path, MYF(0)))
+ error=1; /* purecov: inspected */
+ file= get_new_handler((TABLE_SHARE*) 0, current_thd->mem_root, base);
+ if (file && file->ha_delete_table(path))
+ {
+ error=1;
+ sql_print_warning("Could not remove temporary table: '%s', error: %d",
+ path, my_errno);
+ }
+ delete file;
+ DBUG_RETURN(error);
+}
+
+
+/*****************************************************************************
+* The following find_field_in_XXX procedures implement the core of the
+* name resolution functionality. The entry point to resolve a column name in a
+* list of tables is 'find_field_in_tables'. It calls 'find_field_in_table_ref'
+* for each table reference. In turn, depending on the type of table reference,
+* 'find_field_in_table_ref' calls one of the 'find_field_in_XXX' procedures
+* below specific for the type of table reference.
+******************************************************************************/
+
+/* Special Field pointers as return values of find_field_in_XXX functions. */
+Field *not_found_field= (Field*) 0x1;
+Field *view_ref_found= (Field*) 0x2;
+
+#define WRONG_GRANT (Field*) -1
+
+static void update_field_dependencies(THD *thd, Field *field, TABLE *table)
+{
+ DBUG_ENTER("update_field_dependencies");
+ if (thd->mark_used_columns != MARK_COLUMNS_NONE)
+ {
+ MY_BITMAP *bitmap;
+
+ /*
+ We always want to register the used keys, as the column bitmap may have
+ been set for all fields (for example for view).
+ */
+
+ table->covering_keys.intersect(field->part_of_key);
+ table->merge_keys.merge(field->part_of_key);
+
+ if (field->vcol_info)
+ table->mark_virtual_col(field);
+
+ if (thd->mark_used_columns == MARK_COLUMNS_READ)
+ bitmap= table->read_set;
+ else
+ bitmap= table->write_set;
+
+ /*
+ The test-and-set mechanism in the bitmap is not reliable during
+ multi-UPDATE statements under MARK_COLUMNS_READ mode
+ (thd->mark_used_columns == MARK_COLUMNS_READ), as this bitmap contains
+ only those columns that are used in the SET clause. I.e they are being
+ set here. See multi_update::prepare()
+ */
+ if (bitmap_fast_test_and_set(bitmap, field->field_index))
+ {
+ if (thd->mark_used_columns == MARK_COLUMNS_WRITE)
+ {
+ DBUG_PRINT("warning", ("Found duplicated field"));
+ thd->dup_field= field;
+ }
+ else
+ {
+ DBUG_PRINT("note", ("Field found before"));
+ }
+ DBUG_VOID_RETURN;
+ }
+ if (table->get_fields_in_item_tree)
+ field->flags|= GET_FIXED_FIELDS_FLAG;
+ table->used_fields++;
+ }
+ else if (table->get_fields_in_item_tree)
+ field->flags|= GET_FIXED_FIELDS_FLAG;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Find a temporary table specified by TABLE_LIST instance in the cache and
+ prepare its TABLE instance for use.
+
+ This function tries to resolve this table in the list of temporary tables
+ of this thread. Temporary tables are thread-local and "shadow" base
+ tables with the same name.
+
+ @note In most cases one should use open_temporary_tables() instead
+ of this call.
+
+ @note One should finalize process of opening temporary table for table
+ list element by calling open_and_process_table(). This function
+ is responsible for table version checking and handling of merge
+ tables.
+
+ @note We used to check global_read_lock before opening temporary tables.
+ However, that limitation was artificial and is removed now.
+
+ @return Error status.
+ @retval FALSE On success. If a temporary table exists for the given
+ key, tl->table is set.
+ @retval TRUE On error. my_error() has been called.
+*/
+
+bool open_temporary_table(THD *thd, TABLE_LIST *tl)
+{
+ TABLE *table;
+ DBUG_ENTER("open_temporary_table");
+ DBUG_PRINT("enter", ("table: '%s'.'%s'", tl->db, tl->table_name));
+
+ /*
+ Code in open_table() assumes that TABLE_LIST::table can
+ be non-zero only for pre-opened temporary tables.
+ */
+ DBUG_ASSERT(tl->table == NULL);
+
+ /*
+ This function should not be called for cases when derived or I_S
+ tables can be met since table list elements for such tables can
+ have invalid db or table name.
+ Instead open_temporary_tables() should be used.
+ */
+ DBUG_ASSERT(!tl->derived && !tl->schema_table);
+
+ if (tl->open_type == OT_BASE_ONLY || !thd->have_temporary_tables())
+ {
+ DBUG_PRINT("info", ("skip_temporary is set or no temporary tables"));
+ DBUG_RETURN(FALSE);
+ }
+
+ if (find_and_use_temporary_table(thd, tl, &table))
+ DBUG_RETURN(TRUE);
+ if (!table)
+ {
+ if (tl->open_type == OT_TEMPORARY_ONLY &&
+ tl->open_strategy == TABLE_LIST::OPEN_NORMAL)
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), tl->db, tl->table_name);
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ Temporary tables are not safe for parallel replication. They were
+ designed to be visible to one thread only, so have no table locking.
+ Thus there is no protection against two conflicting transactions
+ committing in parallel and things like that.
+
+ So for now, anything that uses temporary tables will be serialised
+ with anything before it, when using parallel replication.
+
+ ToDo: We might be able to introduce a reference count or something
+ on temp tables, and have slave worker threads wait for it to reach
+ zero before being allowed to use the temp table. Might not be worth
+ it though, as statement-based replication using temporary tables is
+ in any case rather fragile.
+ */
+ if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
+ thd->wait_for_prior_commit())
+ DBUG_RETURN(true);
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (tl->partition_names)
+ {
+ /* Partitioned temporary tables is not supported. */
+ DBUG_ASSERT(!table->part_info);
+ my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(true);
+ }
+#endif
+
+ if (table->query_id)
+ {
+ /*
+ We're trying to use the same temporary table twice in a query.
+ Right now we don't support this because a temporary table is always
+ represented by only one TABLE object in THD, and it can not be
+ cloned. Emit an error for an unsupported behaviour.
+ */
+
+ DBUG_PRINT("error",
+ ("query_id: %lu server_id: %u pseudo_thread_id: %lu",
+ (ulong) table->query_id, (uint) thd->variables.server_id,
+ (ulong) thd->variables.pseudo_thread_id));
+ my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr());
+ DBUG_RETURN(TRUE);
+ }
+
+ table->query_id= thd->query_id;
+ thd->thread_specific_used= TRUE;
+
+ tl->updatable= 1; // It is not derived table nor non-updatable VIEW.
+ tl->table= table;
+
+ table->init(thd, tl);
+
+ DBUG_PRINT("info", ("Using temporary table"));
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Pre-open temporary tables corresponding to table list elements.
+
+ @note One should finalize process of opening temporary tables
+ by calling open_tables(). This function is responsible
+ for table version checking and handling of merge tables.
+
+ @return Error status.
+ @retval FALSE On success. If a temporary tables exists for the
+ given element, tl->table is set.
+ @retval TRUE On error. my_error() has been called.
+*/
+
+bool open_temporary_tables(THD *thd, TABLE_LIST *tl_list)
+{
+ TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
+ DBUG_ENTER("open_temporary_tables");
+
+ for (TABLE_LIST *tl= tl_list; tl && tl != first_not_own; tl= tl->next_global)
+ {
+ if (tl->derived || tl->schema_table)
+ {
+ /*
+ Derived and I_S tables will be handled by a later call to open_tables().
+ */
+ continue;
+ }
+
+ if (open_temporary_table(thd, tl))
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ Find a field by name in a view that uses merge algorithm.
+
+ SYNOPSIS
+ find_field_in_view()
+ thd thread handler
+ table_list view to search for 'name'
+ name name of field
+ length length of name
+ item_name name of item if it will be created (VIEW)
+ ref expression substituted in VIEW should be passed
+ using this reference (return view_ref_found)
+ register_tree_change TRUE if ref is not stack variable and we
+ need register changes in item tree
+
+ RETURN
+ 0 field is not found
+ view_ref_found found value in VIEW (real result is in *ref)
+ # pointer to field - only for schema table fields
+*/
+
+static Field *
+find_field_in_view(THD *thd, TABLE_LIST *table_list,
+ const char *name, uint length,
+ const char *item_name, Item **ref,
+ bool register_tree_change)
+{
+ DBUG_ENTER("find_field_in_view");
+ DBUG_PRINT("enter",
+ ("view: '%s', field name: '%s', item name: '%s', ref 0x%lx",
+ table_list->alias, name, item_name, (ulong) ref));
+ Field_iterator_view field_it;
+ field_it.set(table_list);
+ Query_arena *arena= 0, backup;
+
+ for (; !field_it.end_of_fields(); field_it.next())
+ {
+ if (!my_strcasecmp(system_charset_info, field_it.name(), name))
+ {
+ // in PS use own arena or data will be freed after prepare
+ if (register_tree_change &&
+ thd->stmt_arena->is_stmt_prepare_or_first_stmt_execute())
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ /*
+ create_item() may, or may not create a new Item, depending on
+ the column reference. See create_view_field() for details.
+ */
+ Item *item= field_it.create_item(thd);
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+
+ if (!item)
+ DBUG_RETURN(0);
+ if (!ref)
+ DBUG_RETURN((Field*) view_ref_found);
+ /*
+ *ref != NULL means that *ref contains the item that we need to
+ replace. If the item was aliased by the user, set the alias to
+ the replacing item.
+ We need to set alias on both ref itself and on ref real item.
+ */
+ if (*ref && !(*ref)->is_autogenerated_name)
+ {
+ if (register_tree_change)
+ {
+ item->set_name_for_rollback(thd, (*ref)->name,
+ (*ref)->name_length,
+ system_charset_info);
+ item->real_item()->set_name_for_rollback(thd, (*ref)->name,
+ (*ref)->name_length,
+ system_charset_info);
+ }
+ else
+ {
+ item->set_name((*ref)->name, (*ref)->name_length,
+ system_charset_info);
+ item->real_item()->set_name((*ref)->name, (*ref)->name_length,
+ system_charset_info);
+ }
+ }
+ if (register_tree_change)
+ thd->change_item_tree(ref, item);
+ else
+ *ref= item;
+ DBUG_RETURN((Field*) view_ref_found);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Find field by name in a NATURAL/USING join table reference.
+
+ SYNOPSIS
+ find_field_in_natural_join()
+ thd [in] thread handler
+ table_ref [in] table reference to search
+ name [in] name of field
+ length [in] length of name
+ ref [in/out] if 'name' is resolved to a view field, ref is
+ set to point to the found view field
+ register_tree_change [in] TRUE if ref is not stack variable and we
+ need register changes in item tree
+ actual_table [out] the original table reference where the field
+ belongs - differs from 'table_list' only for
+ NATURAL/USING joins
+
+ DESCRIPTION
+ Search for a field among the result fields of a NATURAL/USING join.
+ Notice that this procedure is called only for non-qualified field
+ names. In the case of qualified fields, we search directly the base
+ tables of a natural join.
+
+ RETURN
+ NULL if the field was not found
+ WRONG_GRANT if no access rights to the found field
+ # Pointer to the found Field
+*/
+
+static Field *
+find_field_in_natural_join(THD *thd, TABLE_LIST *table_ref, const char *name,
+ uint length, Item **ref, bool register_tree_change,
+ TABLE_LIST **actual_table)
+{
+ List_iterator_fast<Natural_join_column>
+ field_it(*(table_ref->join_columns));
+ Natural_join_column *nj_col, *curr_nj_col;
+ Field *found_field;
+ Query_arena *arena, backup;
+ DBUG_ENTER("find_field_in_natural_join");
+ DBUG_PRINT("enter", ("field name: '%s', ref 0x%lx",
+ name, (ulong) ref));
+ DBUG_ASSERT(table_ref->is_natural_join && table_ref->join_columns);
+ DBUG_ASSERT(*actual_table == NULL);
+
+ LINT_INIT(arena);
+ LINT_INIT(found_field);
+
+ for (nj_col= NULL, curr_nj_col= field_it++; curr_nj_col;
+ curr_nj_col= field_it++)
+ {
+ if (!my_strcasecmp(system_charset_info, curr_nj_col->name(), name))
+ {
+ if (nj_col)
+ {
+ my_error(ER_NON_UNIQ_ERROR, MYF(0), name, thd->where);
+ DBUG_RETURN(NULL);
+ }
+ nj_col= curr_nj_col;
+ }
+ }
+ if (!nj_col)
+ DBUG_RETURN(NULL);
+
+ if (nj_col->view_field)
+ {
+ Item *item;
+ LINT_INIT(arena);
+ if (register_tree_change)
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ /*
+ create_item() may, or may not create a new Item, depending on the
+ column reference. See create_view_field() for details.
+ */
+ item= nj_col->create_item(thd);
+ /*
+ *ref != NULL means that *ref contains the item that we need to
+ replace. If the item was aliased by the user, set the alias to
+ the replacing item.
+ We need to set alias on both ref itself and on ref real item.
+ */
+ if (*ref && !(*ref)->is_autogenerated_name)
+ {
+ item->set_name((*ref)->name, (*ref)->name_length,
+ system_charset_info);
+ item->real_item()->set_name((*ref)->name, (*ref)->name_length,
+ system_charset_info);
+ }
+ if (register_tree_change && arena)
+ thd->restore_active_arena(arena, &backup);
+
+ if (!item)
+ DBUG_RETURN(NULL);
+ DBUG_ASSERT(nj_col->table_field == NULL);
+ if (nj_col->table_ref->schema_table_reformed)
+ {
+ /*
+ Translation table items are always Item_fields and fixed
+ already('mysql_schema_table' function). So we can return
+ ->field. It is used only for 'show & where' commands.
+ */
+ DBUG_RETURN(((Item_field*) (nj_col->view_field->item))->field);
+ }
+ if (register_tree_change)
+ thd->change_item_tree(ref, item);
+ else
+ *ref= item;
+ found_field= (Field*) view_ref_found;
+ }
+ else
+ {
+ /* This is a base table. */
+ DBUG_ASSERT(nj_col->view_field == NULL);
+ Item *ref= 0;
+ /*
+ This fix_fields is not necessary (initially this item is fixed by
+ the Item_field constructor; after reopen_tables the Item_func_eq
+ calls fix_fields on that item), it's just a check during table
+ reopening for columns that was dropped by the concurrent connection.
+ */
+ if (!nj_col->table_field->fixed &&
+ nj_col->table_field->fix_fields(thd, &ref))
+ {
+ DBUG_PRINT("info", ("column '%s' was dropped by the concurrent connection",
+ nj_col->table_field->name));
+ DBUG_RETURN(NULL);
+ }
+ DBUG_ASSERT(ref == 0); // Should not have changed
+ DBUG_ASSERT(nj_col->table_ref->table == nj_col->table_field->field->table);
+ found_field= nj_col->table_field->field;
+ update_field_dependencies(thd, found_field, nj_col->table_ref->table);
+ }
+
+ *actual_table= nj_col->table_ref;
+
+ DBUG_RETURN(found_field);
+}
+
+
+/*
+ Find field by name in a base table or a view with temp table algorithm.
+
+ The caller is expected to check column-level privileges.
+
+ SYNOPSIS
+ find_field_in_table()
+ thd thread handler
+ table table where to search for the field
+ name name of field
+ length length of name
+ allow_rowid do allow finding of "_rowid" field?
+ cached_field_index_ptr cached position in field list (used to speedup
+ lookup for fields in prepared tables)
+
+ RETURN
+ 0 field is not found
+ # pointer to field
+*/
+
+Field *
+find_field_in_table(THD *thd, TABLE *table, const char *name, uint length,
+ bool allow_rowid, uint *cached_field_index_ptr)
+{
+ Field **field_ptr, *field;
+ uint cached_field_index= *cached_field_index_ptr;
+ DBUG_ENTER("find_field_in_table");
+ DBUG_PRINT("enter", ("table: '%s', field name: '%s'", table->alias.c_ptr(),
+ name));
+
+ /* We assume here that table->field < NO_CACHED_FIELD_INDEX = UINT_MAX */
+ if (cached_field_index < table->s->fields &&
+ !my_strcasecmp(system_charset_info,
+ table->field[cached_field_index]->field_name, name))
+ field_ptr= table->field + cached_field_index;
+ else if (table->s->name_hash.records)
+ {
+ field_ptr= (Field**) my_hash_search(&table->s->name_hash, (uchar*) name,
+ length);
+ if (field_ptr)
+ {
+ /*
+ field_ptr points to field in TABLE_SHARE. Convert it to the matching
+ field in table
+ */
+ field_ptr= (table->field + (field_ptr - table->s->field));
+ }
+ }
+ else
+ {
+ if (!(field_ptr= table->field))
+ DBUG_RETURN((Field *)0);
+ for (; *field_ptr; ++field_ptr)
+ if (!my_strcasecmp(system_charset_info, (*field_ptr)->field_name, name))
+ break;
+ }
+
+ if (field_ptr && *field_ptr)
+ {
+ *cached_field_index_ptr= field_ptr - table->field;
+ field= *field_ptr;
+ }
+ else
+ {
+ if (!allow_rowid ||
+ my_strcasecmp(system_charset_info, name, "_rowid") ||
+ table->s->rowid_field_offset == 0)
+ DBUG_RETURN((Field*) 0);
+ field= table->field[table->s->rowid_field_offset-1];
+ }
+
+ update_field_dependencies(thd, field, table);
+
+ DBUG_RETURN(field);
+}
+
+
+/*
+ Find field in a table reference.
+
+ SYNOPSIS
+ find_field_in_table_ref()
+ thd [in] thread handler
+ table_list [in] table reference to search
+ name [in] name of field
+ length [in] field length of name
+ item_name [in] name of item if it will be created (VIEW)
+ db_name [in] optional database name that qualifies the
+ table_name [in] optional table name that qualifies the field
+ ref [in/out] if 'name' is resolved to a view field, ref
+ is set to point to the found view field
+ check_privileges [in] check privileges
+ allow_rowid [in] do allow finding of "_rowid" field?
+ cached_field_index_ptr [in] cached position in field list (used to
+ speedup lookup for fields in prepared tables)
+ register_tree_change [in] TRUE if ref is not stack variable and we
+ need register changes in item tree
+ actual_table [out] the original table reference where the field
+ belongs - differs from 'table_list' only for
+ NATURAL_USING joins.
+
+ DESCRIPTION
+ Find a field in a table reference depending on the type of table
+ reference. There are three types of table references with respect
+ to the representation of their result columns:
+ - an array of Field_translator objects for MERGE views and some
+ information_schema tables,
+ - an array of Field objects (and possibly a name hash) for stored
+ tables,
+ - a list of Natural_join_column objects for NATURAL/USING joins.
+ This procedure detects the type of the table reference 'table_list'
+ and calls the corresponding search routine.
+
+ The routine checks column-level privieleges for the found field.
+
+ RETURN
+ 0 field is not found
+ view_ref_found found value in VIEW (real result is in *ref)
+ # pointer to field
+*/
+
+Field *
+find_field_in_table_ref(THD *thd, TABLE_LIST *table_list,
+ const char *name, uint length,
+ const char *item_name, const char *db_name,
+ const char *table_name, Item **ref,
+ bool check_privileges, bool allow_rowid,
+ uint *cached_field_index_ptr,
+ bool register_tree_change, TABLE_LIST **actual_table)
+{
+ Field *fld;
+ DBUG_ENTER("find_field_in_table_ref");
+ DBUG_ASSERT(table_list->alias);
+ DBUG_ASSERT(name);
+ DBUG_ASSERT(item_name);
+ DBUG_PRINT("enter",
+ ("table: '%s' field name: '%s' item name: '%s' ref 0x%lx",
+ table_list->alias, name, item_name, (ulong) ref));
+
+ /*
+ Check that the table and database that qualify the current field name
+ are the same as the table reference we are going to search for the field.
+
+ Exclude from the test below nested joins because the columns in a
+ nested join generally originate from different tables. Nested joins
+ also have no table name, except when a nested join is a merge view
+ or an information schema table.
+
+ We include explicitly table references with a 'field_translation' table,
+ because if there are views over natural joins we don't want to search
+ inside the view, but we want to search directly in the view columns
+ which are represented as a 'field_translation'.
+
+ TODO: Ensure that table_name, db_name and tables->db always points to
+ something !
+ */
+ if (/* Exclude nested joins. */
+ (!table_list->nested_join ||
+ /* Include merge views and information schema tables. */
+ table_list->field_translation) &&
+ /*
+ Test if the field qualifiers match the table reference we plan
+ to search.
+ */
+ table_name && table_name[0] &&
+ (my_strcasecmp(table_alias_charset, table_list->alias, table_name) ||
+ (db_name && db_name[0] && table_list->db && table_list->db[0] &&
+ (table_list->schema_table ?
+ my_strcasecmp(system_charset_info, db_name, table_list->db) :
+ strcmp(db_name, table_list->db)))))
+ DBUG_RETURN(0);
+
+ *actual_table= NULL;
+
+ if (table_list->field_translation)
+ {
+ /* 'table_list' is a view or an information schema table. */
+ if ((fld= find_field_in_view(thd, table_list, name, length, item_name, ref,
+ register_tree_change)))
+ *actual_table= table_list;
+ }
+ else if (!table_list->nested_join)
+ {
+ /* 'table_list' is a stored table. */
+ DBUG_ASSERT(table_list->table);
+ if ((fld= find_field_in_table(thd, table_list->table, name, length,
+ allow_rowid,
+ cached_field_index_ptr)))
+ *actual_table= table_list;
+ }
+ else
+ {
+ /*
+ 'table_list' is a NATURAL/USING join, or an operand of such join that
+ is a nested join itself.
+
+ If the field name we search for is qualified, then search for the field
+ in the table references used by NATURAL/USING the join.
+ */
+ if (table_name && table_name[0])
+ {
+ List_iterator<TABLE_LIST> it(table_list->nested_join->join_list);
+ TABLE_LIST *table;
+ while ((table= it++))
+ {
+ if ((fld= find_field_in_table_ref(thd, table, name, length, item_name,
+ db_name, table_name, ref,
+ check_privileges, allow_rowid,
+ cached_field_index_ptr,
+ register_tree_change, actual_table)))
+ DBUG_RETURN(fld);
+ }
+ DBUG_RETURN(0);
+ }
+ /*
+ Non-qualified field, search directly in the result columns of the
+ natural join. The condition of the outer IF is true for the top-most
+ natural join, thus if the field is not qualified, we will search
+ directly the top-most NATURAL/USING join.
+ */
+ fld= find_field_in_natural_join(thd, table_list, name, length, ref,
+ register_tree_change, actual_table);
+ }
+
+ if (fld)
+ {
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* Check if there are sufficient access rights to the found field. */
+ if (check_privileges &&
+ check_column_grant_in_table_ref(thd, *actual_table, name, length))
+ fld= WRONG_GRANT;
+ else
+#endif
+ if (thd->mark_used_columns != MARK_COLUMNS_NONE)
+ {
+ /*
+ Get rw_set correct for this field so that the handler
+ knows that this field is involved in the query and gets
+ retrieved/updated
+ */
+ Field *field_to_set= NULL;
+ if (fld == view_ref_found)
+ {
+ if (!ref)
+ DBUG_RETURN(fld);
+ Item *it= (*ref)->real_item();
+ if (it->type() == Item::FIELD_ITEM)
+ field_to_set= ((Item_field*)it)->field;
+ else
+ {
+ if (thd->mark_used_columns == MARK_COLUMNS_READ)
+ it->walk(&Item::register_field_in_read_map, 0, (uchar *) 0);
+ else
+ it->walk(&Item::register_field_in_write_map, 0, (uchar *) 0);
+ }
+ }
+ else
+ field_to_set= fld;
+ if (field_to_set)
+ {
+ TABLE *table= field_to_set->table;
+ if (thd->mark_used_columns == MARK_COLUMNS_READ)
+ bitmap_set_bit(table->read_set, field_to_set->field_index);
+ else
+ bitmap_set_bit(table->write_set, field_to_set->field_index);
+ }
+ }
+ }
+ DBUG_RETURN(fld);
+}
+
+
+/*
+ Find field in table, no side effects, only purpose is to check for field
+ in table object and get reference to the field if found.
+
+ SYNOPSIS
+ find_field_in_table_sef()
+
+ table table where to find
+ name Name of field searched for
+
+ RETURN
+ 0 field is not found
+ # pointer to field
+*/
+
+Field *find_field_in_table_sef(TABLE *table, const char *name)
+{
+ Field **field_ptr;
+ if (table->s->name_hash.records)
+ {
+ field_ptr= (Field**)my_hash_search(&table->s->name_hash,(uchar*) name,
+ strlen(name));
+ if (field_ptr)
+ {
+ /*
+ field_ptr points to field in TABLE_SHARE. Convert it to the matching
+ field in table
+ */
+ field_ptr= (table->field + (field_ptr - table->s->field));
+ }
+ }
+ else
+ {
+ if (!(field_ptr= table->field))
+ return (Field *)0;
+ for (; *field_ptr; ++field_ptr)
+ if (!my_strcasecmp(system_charset_info, (*field_ptr)->field_name, name))
+ break;
+ }
+ if (field_ptr)
+ return *field_ptr;
+ else
+ return (Field *)0;
+}
+
+
+/*
+ Find field in table list.
+
+ SYNOPSIS
+ find_field_in_tables()
+ thd pointer to current thread structure
+ item field item that should be found
+ first_table list of tables to be searched for item
+ last_table end of the list of tables to search for item. If NULL
+ then search to the end of the list 'first_table'.
+ ref if 'item' is resolved to a view field, ref is set to
+ point to the found view field
+ report_error Degree of error reporting:
+ - IGNORE_ERRORS then do not report any error
+ - IGNORE_EXCEPT_NON_UNIQUE report only non-unique
+ fields, suppress all other errors
+ - REPORT_EXCEPT_NON_UNIQUE report all other errors
+ except when non-unique fields were found
+ - REPORT_ALL_ERRORS
+ check_privileges need to check privileges
+ register_tree_change TRUE if ref is not a stack variable and we
+ to need register changes in item tree
+
+ RETURN VALUES
+ 0 If error: the found field is not unique, or there are
+ no sufficient access priviliges for the found field,
+ or the field is qualified with non-existing table.
+ not_found_field The function was called with report_error ==
+ (IGNORE_ERRORS || IGNORE_EXCEPT_NON_UNIQUE) and a
+ field was not found.
+ view_ref_found View field is found, item passed through ref parameter
+ found field If a item was resolved to some field
+*/
+
+Field *
+find_field_in_tables(THD *thd, Item_ident *item,
+ TABLE_LIST *first_table, TABLE_LIST *last_table,
+ Item **ref, find_item_error_report_type report_error,
+ bool check_privileges, bool register_tree_change)
+{
+ Field *found=0;
+ const char *db= item->db_name;
+ const char *table_name= item->table_name;
+ const char *name= item->field_name;
+ uint length=(uint) strlen(name);
+ char name_buff[SAFE_NAME_LEN+1];
+ TABLE_LIST *cur_table= first_table;
+ TABLE_LIST *actual_table;
+ bool allow_rowid;
+
+ if (!table_name || !table_name[0])
+ {
+ table_name= 0; // For easier test
+ db= 0;
+ }
+
+ allow_rowid= table_name || (cur_table && !cur_table->next_local);
+
+ if (item->cached_table)
+ {
+ /*
+ This shortcut is used by prepared statements. We assume that
+ TABLE_LIST *first_table is not changed during query execution (which
+ is true for all queries except RENAME but luckily RENAME doesn't
+ use fields...) so we can rely on reusing pointer to its member.
+ With this optimization we also miss case when addition of one more
+ field makes some prepared query ambiguous and so erroneous, but we
+ accept this trade off.
+ */
+ TABLE_LIST *table_ref= item->cached_table;
+ /*
+ The condition (table_ref->view == NULL) ensures that we will call
+ find_field_in_table even in the case of information schema tables
+ when table_ref->field_translation != NULL.
+ */
+ if (table_ref->table && !table_ref->view &&
+ (!table_ref->is_merged_derived() ||
+ (!table_ref->is_multitable() && table_ref->merged_for_insert)))
+ {
+
+ found= find_field_in_table(thd, table_ref->table, name, length,
+ TRUE, &(item->cached_field_index));
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* Check if there are sufficient access rights to the found field. */
+ if (found && check_privileges &&
+ check_column_grant_in_table_ref(thd, table_ref, name, length))
+ found= WRONG_GRANT;
+#endif
+ }
+ else
+ found= find_field_in_table_ref(thd, table_ref, name, length, item->name,
+ NULL, NULL, ref, check_privileges,
+ TRUE, &(item->cached_field_index),
+ register_tree_change,
+ &actual_table);
+ if (found)
+ {
+ if (found == WRONG_GRANT)
+ return (Field*) 0;
+
+ /*
+ Only views fields should be marked as dependent, not an underlying
+ fields.
+ */
+ if (!table_ref->belong_to_view &&
+ !table_ref->belong_to_derived)
+ {
+ SELECT_LEX *current_sel= thd->lex->current_select;
+ SELECT_LEX *last_select= table_ref->select_lex;
+ bool all_merged= TRUE;
+ for (SELECT_LEX *sl= current_sel; sl && sl!=last_select;
+ sl=sl->outer_select())
+ {
+ Item *subs= sl->master_unit()->item;
+ if (subs->type() == Item::SUBSELECT_ITEM &&
+ ((Item_subselect*)subs)->substype() == Item_subselect::IN_SUBS &&
+ ((Item_in_subselect*)subs)->test_strategy(SUBS_SEMI_JOIN))
+ {
+ continue;
+ }
+ all_merged= FALSE;
+ break;
+ }
+ /*
+ If the field was an outer referencee, mark all selects using this
+ sub query as dependent on the outer query
+ */
+ if (!all_merged && current_sel != last_select)
+ {
+ mark_select_range_as_dependent(thd, last_select, current_sel,
+ found, *ref, item);
+ }
+ }
+ return found;
+ }
+ }
+ else
+ item->can_be_depended= TRUE;
+
+ if (db && lower_case_table_names)
+ {
+ /*
+ convert database to lower case for comparison.
+ We can't do this in Item_field as this would change the
+ 'name' of the item which may be used in the select list
+ */
+ strmake_buf(name_buff, db);
+ my_casedn_str(files_charset_info, name_buff);
+ db= name_buff;
+ }
+
+ if (last_table)
+ last_table= last_table->next_name_resolution_table;
+
+ for (; cur_table != last_table ;
+ cur_table= cur_table->next_name_resolution_table)
+ {
+ Field *cur_field= find_field_in_table_ref(thd, cur_table, name, length,
+ item->name, db, table_name, ref,
+ (thd->lex->sql_command ==
+ SQLCOM_SHOW_FIELDS)
+ ? false : check_privileges,
+ allow_rowid,
+ &(item->cached_field_index),
+ register_tree_change,
+ &actual_table);
+ if (cur_field)
+ {
+ if (cur_field == WRONG_GRANT)
+ {
+ if (thd->lex->sql_command != SQLCOM_SHOW_FIELDS)
+ return (Field*) 0;
+
+ thd->clear_error();
+ cur_field= find_field_in_table_ref(thd, cur_table, name, length,
+ item->name, db, table_name, ref,
+ false,
+ allow_rowid,
+ &(item->cached_field_index),
+ register_tree_change,
+ &actual_table);
+ if (cur_field)
+ {
+ Field *nf=new Field_null(NULL,0,Field::NONE,
+ cur_field->field_name,
+ &my_charset_bin);
+ nf->init(cur_table->table);
+ cur_field= nf;
+ }
+ }
+
+ /*
+ Store the original table of the field, which may be different from
+ cur_table in the case of NATURAL/USING join.
+ */
+ item->cached_table= (!actual_table->cacheable_table || found) ?
+ 0 : actual_table;
+
+ DBUG_ASSERT(thd->where);
+ /*
+ If we found a fully qualified field we return it directly as it can't
+ have duplicates.
+ */
+ if (db)
+ return cur_field;
+
+ if (found)
+ {
+ if (report_error == REPORT_ALL_ERRORS ||
+ report_error == IGNORE_EXCEPT_NON_UNIQUE)
+ my_error(ER_NON_UNIQ_ERROR, MYF(0),
+ table_name ? item->full_name() : name, thd->where);
+ return (Field*) 0;
+ }
+ found= cur_field;
+ }
+ }
+
+ if (found)
+ return found;
+
+ /*
+ If the field was qualified and there were no tables to search, issue
+ an error that an unknown table was given. The situation is detected
+ as follows: if there were no tables we wouldn't go through the loop
+ and cur_table wouldn't be updated by the loop increment part, so it
+ will be equal to the first table.
+ */
+ if (table_name && (cur_table == first_table) &&
+ (report_error == REPORT_ALL_ERRORS ||
+ report_error == REPORT_EXCEPT_NON_UNIQUE))
+ {
+ char buff[SAFE_NAME_LEN*2 + 2];
+ if (db && db[0])
+ {
+ strxnmov(buff,sizeof(buff)-1,db,".",table_name,NullS);
+ table_name=buff;
+ }
+ my_error(ER_UNKNOWN_TABLE, MYF(0), table_name, thd->where);
+ }
+ else
+ {
+ if (report_error == REPORT_ALL_ERRORS ||
+ report_error == REPORT_EXCEPT_NON_UNIQUE)
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), item->full_name(), thd->where);
+ else
+ found= not_found_field;
+ }
+ return found;
+}
+
+
+/*
+ Find Item in list of items (find_field_in_tables analog)
+
+ TODO
+ is it better return only counter?
+
+ SYNOPSIS
+ find_item_in_list()
+ find Item to find
+ items List of items
+ counter To return number of found item
+ report_error
+ REPORT_ALL_ERRORS report errors, return 0 if error
+ REPORT_EXCEPT_NOT_FOUND Do not report 'not found' error and
+ return not_found_item, report other errors,
+ return 0
+ IGNORE_ERRORS Do not report errors, return 0 if error
+ resolution Set to the resolution type if the item is found
+ (it says whether the item is resolved
+ against an alias name,
+ or as a field name without alias,
+ or as a field hidden by alias,
+ or ignoring alias)
+
+ RETURN VALUES
+ 0 Item is not found or item is not unique,
+ error message is reported
+ not_found_item Function was called with
+ report_error == REPORT_EXCEPT_NOT_FOUND and
+ item was not found. No error message was reported
+ found field
+*/
+
+/* Special Item pointer to serve as a return value from find_item_in_list(). */
+Item **not_found_item= (Item**) 0x1;
+
+
+Item **
+find_item_in_list(Item *find, List<Item> &items, uint *counter,
+ find_item_error_report_type report_error,
+ enum_resolution_type *resolution)
+{
+ List_iterator<Item> li(items);
+ Item **found=0, **found_unaliased= 0, *item;
+ const char *db_name=0;
+ const char *field_name=0;
+ const char *table_name=0;
+ bool found_unaliased_non_uniq= 0;
+ /*
+ true if the item that we search for is a valid name reference
+ (and not an item that happens to have a name).
+ */
+ bool is_ref_by_name= 0;
+ uint unaliased_counter= 0;
+
+ *resolution= NOT_RESOLVED;
+
+ is_ref_by_name= (find->type() == Item::FIELD_ITEM ||
+ find->type() == Item::REF_ITEM);
+ if (is_ref_by_name)
+ {
+ field_name= ((Item_ident*) find)->field_name;
+ table_name= ((Item_ident*) find)->table_name;
+ db_name= ((Item_ident*) find)->db_name;
+ }
+
+ for (uint i= 0; (item=li++); i++)
+ {
+ if (field_name && item->real_item()->type() == Item::FIELD_ITEM)
+ {
+ Item_ident *item_field= (Item_ident*) item;
+
+ /*
+ In case of group_concat() with ORDER BY condition in the QUERY
+ item_field can be field of temporary table without item name
+ (if this field created from expression argument of group_concat()),
+ => we have to check presence of name before compare
+ */
+ if (!item_field->name)
+ continue;
+
+ if (table_name)
+ {
+ /*
+ If table name is specified we should find field 'field_name' in
+ table 'table_name'. According to SQL-standard we should ignore
+ aliases in this case.
+
+ Since we should NOT prefer fields from the select list over
+ other fields from the tables participating in this select in
+ case of ambiguity we have to do extra check outside this function.
+
+ We use strcmp for table names and database names as these may be
+ case sensitive. In cases where they are not case sensitive, they
+ are always in lower case.
+
+ item_field->field_name and item_field->table_name can be 0x0 if
+ item is not fix_field()'ed yet.
+ */
+ if (item_field->field_name && item_field->table_name &&
+ !my_strcasecmp(system_charset_info, item_field->field_name,
+ field_name) &&
+ !my_strcasecmp(table_alias_charset, item_field->table_name,
+ table_name) &&
+ (!db_name || (item_field->db_name &&
+ !strcmp(item_field->db_name, db_name))))
+ {
+ if (found_unaliased)
+ {
+ if ((*found_unaliased)->eq(item, 0))
+ continue;
+ /*
+ Two matching fields in select list.
+ We already can bail out because we are searching through
+ unaliased names only and will have duplicate error anyway.
+ */
+ if (report_error != IGNORE_ERRORS)
+ my_error(ER_NON_UNIQ_ERROR, MYF(0),
+ find->full_name(), current_thd->where);
+ return (Item**) 0;
+ }
+ found_unaliased= li.ref();
+ unaliased_counter= i;
+ *resolution= RESOLVED_IGNORING_ALIAS;
+ if (db_name)
+ break; // Perfect match
+ }
+ }
+ else
+ {
+ int fname_cmp= my_strcasecmp(system_charset_info,
+ item_field->field_name,
+ field_name);
+ if (!my_strcasecmp(system_charset_info,
+ item_field->name,field_name))
+ {
+ /*
+ If table name was not given we should scan through aliases
+ and non-aliased fields first. We are also checking unaliased
+ name of the field in then next else-if, to be able to find
+ instantly field (hidden by alias) if no suitable alias or
+ non-aliased field was found.
+ */
+ if (found)
+ {
+ if ((*found)->eq(item, 0))
+ continue; // Same field twice
+ if (report_error != IGNORE_ERRORS)
+ my_error(ER_NON_UNIQ_ERROR, MYF(0),
+ find->full_name(), current_thd->where);
+ return (Item**) 0;
+ }
+ found= li.ref();
+ *counter= i;
+ *resolution= fname_cmp ? RESOLVED_AGAINST_ALIAS:
+ RESOLVED_WITH_NO_ALIAS;
+ }
+ else if (!fname_cmp)
+ {
+ /*
+ We will use non-aliased field or react on such ambiguities only if
+ we won't be able to find aliased field.
+ Again if we have ambiguity with field outside of select list
+ we should prefer fields from select list.
+ */
+ if (found_unaliased)
+ {
+ if ((*found_unaliased)->eq(item, 0))
+ continue; // Same field twice
+ found_unaliased_non_uniq= 1;
+ }
+ found_unaliased= li.ref();
+ unaliased_counter= i;
+ }
+ }
+ }
+ else if (!table_name)
+ {
+ if (is_ref_by_name && find->name && item->name &&
+ !my_strcasecmp(system_charset_info,item->name,find->name))
+ {
+ found= li.ref();
+ *counter= i;
+ *resolution= RESOLVED_AGAINST_ALIAS;
+ break;
+ }
+ else if (find->eq(item,0))
+ {
+ found= li.ref();
+ *counter= i;
+ *resolution= RESOLVED_IGNORING_ALIAS;
+ break;
+ }
+ }
+ else if (table_name && item->type() == Item::REF_ITEM &&
+ ((Item_ref *)item)->ref_type() == Item_ref::VIEW_REF)
+ {
+ /*
+ TODO:Here we process prefixed view references only. What we should
+ really do is process all types of Item_refs. But this will currently
+ lead to a clash with the way references to outer SELECTs (from the
+ HAVING clause) are handled in e.g. :
+ SELECT 1 FROM t1 AS t1_o GROUP BY a
+ HAVING (SELECT t1_o.a FROM t1 AS t1_i GROUP BY t1_i.a LIMIT 1).
+ Processing all Item_refs here will cause t1_o.a to resolve to itself.
+ We still need to process the special case of Item_direct_view_ref
+ because in the context of views they have the same meaning as
+ Item_field for tables.
+ */
+ Item_ident *item_ref= (Item_ident *) item;
+ if (field_name && item_ref->name && item_ref->table_name &&
+ !my_strcasecmp(system_charset_info, item_ref->name, field_name) &&
+ !my_strcasecmp(table_alias_charset, item_ref->table_name,
+ table_name) &&
+ (!db_name || (item_ref->db_name &&
+ !strcmp (item_ref->db_name, db_name))))
+ {
+ found= li.ref();
+ *counter= i;
+ *resolution= RESOLVED_IGNORING_ALIAS;
+ break;
+ }
+ }
+ }
+ if (!found)
+ {
+ if (found_unaliased_non_uniq)
+ {
+ if (report_error != IGNORE_ERRORS)
+ my_error(ER_NON_UNIQ_ERROR, MYF(0),
+ find->full_name(), current_thd->where);
+ return (Item **) 0;
+ }
+ if (found_unaliased)
+ {
+ found= found_unaliased;
+ *counter= unaliased_counter;
+ *resolution= RESOLVED_BEHIND_ALIAS;
+ }
+ }
+ if (found)
+ return found;
+ if (report_error != REPORT_EXCEPT_NOT_FOUND)
+ {
+ if (report_error == REPORT_ALL_ERRORS)
+ my_error(ER_BAD_FIELD_ERROR, MYF(0),
+ find->full_name(), current_thd->where);
+ return (Item **) 0;
+ }
+ else
+ return (Item **) not_found_item;
+}
+
+
+/*
+ Test if a string is a member of a list of strings.
+
+ SYNOPSIS
+ test_if_string_in_list()
+ find the string to look for
+ str_list a list of strings to be searched
+
+ DESCRIPTION
+ Sequentially search a list of strings for a string, and test whether
+ the list contains the same string.
+
+ RETURN
+ TRUE if find is in str_list
+ FALSE otherwise
+*/
+
+static bool
+test_if_string_in_list(const char *find, List<String> *str_list)
+{
+ List_iterator<String> str_list_it(*str_list);
+ String *curr_str;
+ size_t find_length= strlen(find);
+ while ((curr_str= str_list_it++))
+ {
+ if (find_length != curr_str->length())
+ continue;
+ if (!my_strcasecmp(system_charset_info, find, curr_str->ptr()))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Create a new name resolution context for an item so that it is
+ being resolved in a specific table reference.
+
+ SYNOPSIS
+ set_new_item_local_context()
+ thd pointer to current thread
+ item item for which new context is created and set
+ table_ref table ref where an item showld be resolved
+
+ DESCRIPTION
+ Create a new name resolution context for an item, so that the item
+ is resolved only the supplied 'table_ref'.
+
+ RETURN
+ FALSE if all OK
+ TRUE otherwise
+*/
+
+static bool
+set_new_item_local_context(THD *thd, Item_ident *item, TABLE_LIST *table_ref)
+{
+ Name_resolution_context *context;
+ if (!(context= new (thd->mem_root) Name_resolution_context))
+ return TRUE;
+ context->init();
+ context->first_name_resolution_table=
+ context->last_name_resolution_table= table_ref;
+ item->context= context;
+ return FALSE;
+}
+
+
+/*
+ Find and mark the common columns of two table references.
+
+ SYNOPSIS
+ mark_common_columns()
+ thd [in] current thread
+ table_ref_1 [in] the first (left) join operand
+ table_ref_2 [in] the second (right) join operand
+ using_fields [in] if the join is JOIN...USING - the join columns,
+ if NATURAL join, then NULL
+ found_using_fields [out] number of fields from the USING clause that were
+ found among the common fields
+
+ DESCRIPTION
+ The procedure finds the common columns of two relations (either
+ tables or intermediate join results), and adds an equi-join condition
+ to the ON clause of 'table_ref_2' for each pair of matching columns.
+ If some of table_ref_XXX represents a base table or view, then we
+ create new 'Natural_join_column' instances for each column
+ reference and store them in the 'join_columns' of the table
+ reference.
+
+ IMPLEMENTATION
+ The procedure assumes that store_natural_using_join_columns() was
+ called for the previous level of NATURAL/USING joins.
+
+ RETURN
+ TRUE error when some common column is non-unique, or out of memory
+ FALSE OK
+*/
+
+static bool
+mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2,
+ List<String> *using_fields, uint *found_using_fields)
+{
+ Field_iterator_table_ref it_1, it_2;
+ Natural_join_column *nj_col_1, *nj_col_2;
+ Query_arena *arena, backup;
+ bool result= TRUE;
+ bool first_outer_loop= TRUE;
+ /*
+ Leaf table references to which new natural join columns are added
+ if the leaves are != NULL.
+ */
+ TABLE_LIST *leaf_1= (table_ref_1->nested_join &&
+ !table_ref_1->is_natural_join) ?
+ NULL : table_ref_1;
+ TABLE_LIST *leaf_2= (table_ref_2->nested_join &&
+ !table_ref_2->is_natural_join) ?
+ NULL : table_ref_2;
+
+ DBUG_ENTER("mark_common_columns");
+ DBUG_PRINT("info", ("operand_1: %s operand_2: %s",
+ table_ref_1->alias, table_ref_2->alias));
+
+ *found_using_fields= 0;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ for (it_1.set(table_ref_1); !it_1.end_of_fields(); it_1.next())
+ {
+ bool found= FALSE;
+ const char *field_name_1;
+ /* true if field_name_1 is a member of using_fields */
+ bool is_using_column_1;
+ if (!(nj_col_1= it_1.get_or_create_column_ref(thd, leaf_1)))
+ goto err;
+ field_name_1= nj_col_1->name();
+ is_using_column_1= using_fields &&
+ test_if_string_in_list(field_name_1, using_fields);
+ DBUG_PRINT ("info", ("field_name_1=%s.%s",
+ nj_col_1->table_name() ? nj_col_1->table_name() : "",
+ field_name_1));
+
+ /*
+ Find a field with the same name in table_ref_2.
+
+ Note that for the second loop, it_2.set() will iterate over
+ table_ref_2->join_columns and not generate any new elements or
+ lists.
+ */
+ nj_col_2= NULL;
+ for (it_2.set(table_ref_2); !it_2.end_of_fields(); it_2.next())
+ {
+ Natural_join_column *cur_nj_col_2;
+ const char *cur_field_name_2;
+ if (!(cur_nj_col_2= it_2.get_or_create_column_ref(thd, leaf_2)))
+ goto err;
+ cur_field_name_2= cur_nj_col_2->name();
+ DBUG_PRINT ("info", ("cur_field_name_2=%s.%s",
+ cur_nj_col_2->table_name() ?
+ cur_nj_col_2->table_name() : "",
+ cur_field_name_2));
+
+ /*
+ Compare the two columns and check for duplicate common fields.
+ A common field is duplicate either if it was already found in
+ table_ref_2 (then found == TRUE), or if a field in table_ref_2
+ was already matched by some previous field in table_ref_1
+ (then cur_nj_col_2->is_common == TRUE).
+ Note that it is too early to check the columns outside of the
+ USING list for ambiguity because they are not actually "referenced"
+ here. These columns must be checked only on unqualified reference
+ by name (e.g. in SELECT list).
+ */
+ if (!my_strcasecmp(system_charset_info, field_name_1, cur_field_name_2))
+ {
+ DBUG_PRINT ("info", ("match c1.is_common=%d", nj_col_1->is_common));
+ if (cur_nj_col_2->is_common ||
+ (found && (!using_fields || is_using_column_1)))
+ {
+ my_error(ER_NON_UNIQ_ERROR, MYF(0), field_name_1, thd->where);
+ goto err;
+ }
+ nj_col_2= cur_nj_col_2;
+ found= TRUE;
+ }
+ }
+ if (first_outer_loop && leaf_2)
+ {
+ /*
+ Make sure that the next inner loop "knows" that all columns
+ are materialized already.
+ */
+ leaf_2->is_join_columns_complete= TRUE;
+ first_outer_loop= FALSE;
+ }
+ if (!found)
+ continue; // No matching field
+
+ /*
+ field_1 and field_2 have the same names. Check if they are in the USING
+ clause (if present), mark them as common fields, and add a new
+ equi-join condition to the ON clause.
+ */
+ if (nj_col_2 && (!using_fields ||is_using_column_1))
+ {
+ /*
+ Create non-fixed fully qualified field and let fix_fields to
+ resolve it.
+ */
+ Item *item_1= nj_col_1->create_item(thd);
+ Item *item_2= nj_col_2->create_item(thd);
+ Field *field_1= nj_col_1->field();
+ Field *field_2= nj_col_2->field();
+ Item_ident *item_ident_1, *item_ident_2;
+ Item_func_eq *eq_cond;
+
+ if (!item_1 || !item_2)
+ goto err; // out of memory
+
+ /*
+ The following assert checks that the two created items are of
+ type Item_ident.
+ */
+ DBUG_ASSERT(!thd->lex->current_select->no_wrap_view_item);
+ /*
+ In the case of no_wrap_view_item == 0, the created items must be
+ of sub-classes of Item_ident.
+ */
+ DBUG_ASSERT(item_1->type() == Item::FIELD_ITEM ||
+ item_1->type() == Item::REF_ITEM);
+ DBUG_ASSERT(item_2->type() == Item::FIELD_ITEM ||
+ item_2->type() == Item::REF_ITEM);
+
+ /*
+ We need to cast item_1,2 to Item_ident, because we need to hook name
+ resolution contexts specific to each item.
+ */
+ item_ident_1= (Item_ident*) item_1;
+ item_ident_2= (Item_ident*) item_2;
+ /*
+ Create and hook special name resolution contexts to each item in the
+ new join condition . We need this to both speed-up subsequent name
+ resolution of these items, and to enable proper name resolution of
+ the items during the execute phase of PS.
+ */
+ if (set_new_item_local_context(thd, item_ident_1, nj_col_1->table_ref) ||
+ set_new_item_local_context(thd, item_ident_2, nj_col_2->table_ref))
+ goto err;
+
+ if (!(eq_cond= new Item_func_eq(item_ident_1, item_ident_2)))
+ goto err; /* Out of memory. */
+
+ if (field_1 && field_1->vcol_info)
+ field_1->table->mark_virtual_col(field_1);
+ if (field_2 && field_2->vcol_info)
+ field_2->table->mark_virtual_col(field_2);
+
+ /*
+ Add the new equi-join condition to the ON clause. Notice that
+ fix_fields() is applied to all ON conditions in setup_conds()
+ so we don't do it here.
+ */
+ add_join_on((table_ref_1->outer_join & JOIN_TYPE_RIGHT ?
+ table_ref_1 : table_ref_2),
+ eq_cond);
+
+ nj_col_1->is_common= nj_col_2->is_common= TRUE;
+ DBUG_PRINT ("info", ("%s.%s and %s.%s are common",
+ nj_col_1->table_name() ?
+ nj_col_1->table_name() : "",
+ nj_col_1->name(),
+ nj_col_2->table_name() ?
+ nj_col_2->table_name() : "",
+ nj_col_2->name()));
+
+ if (field_1)
+ {
+ TABLE *table_1= nj_col_1->table_ref->table;
+ /* Mark field_1 used for table cache. */
+ bitmap_set_bit(table_1->read_set, field_1->field_index);
+ table_1->covering_keys.intersect(field_1->part_of_key);
+ table_1->merge_keys.merge(field_1->part_of_key);
+ }
+ if (field_2)
+ {
+ TABLE *table_2= nj_col_2->table_ref->table;
+ /* Mark field_2 used for table cache. */
+ bitmap_set_bit(table_2->read_set, field_2->field_index);
+ table_2->covering_keys.intersect(field_2->part_of_key);
+ table_2->merge_keys.merge(field_2->part_of_key);
+ }
+
+ if (using_fields != NULL)
+ ++(*found_using_fields);
+ }
+ }
+ if (leaf_1)
+ leaf_1->is_join_columns_complete= TRUE;
+
+ /*
+ Everything is OK.
+ Notice that at this point there may be some column names in the USING
+ clause that are not among the common columns. This is an SQL error and
+ we check for this error in store_natural_using_join_columns() when
+ (found_using_fields < length(join_using_fields)).
+ */
+ result= FALSE;
+
+ /*
+ Save the lists made during natural join matching (because
+ the matching done only once but we need the list in case
+ of prepared statements).
+ */
+ table_ref_1->persistent_used_items= table_ref_1->used_items;
+ table_ref_2->persistent_used_items= table_ref_2->used_items;
+
+err:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(result);
+}
+
+
+
+/*
+ Materialize and store the row type of NATURAL/USING join.
+
+ SYNOPSIS
+ store_natural_using_join_columns()
+ thd current thread
+ natural_using_join the table reference of the NATURAL/USING join
+ table_ref_1 the first (left) operand (of a NATURAL/USING join).
+ table_ref_2 the second (right) operand (of a NATURAL/USING join).
+ using_fields if the join is JOIN...USING - the join columns,
+ if NATURAL join, then NULL
+ found_using_fields number of fields from the USING clause that were
+ found among the common fields
+
+ DESCRIPTION
+ Iterate over the columns of both join operands and sort and store
+ all columns into the 'join_columns' list of natural_using_join
+ where the list is formed by three parts:
+ part1: The coalesced columns of table_ref_1 and table_ref_2,
+ sorted according to the column order of the first table.
+ part2: The other columns of the first table, in the order in
+ which they were defined in CREATE TABLE.
+ part3: The other columns of the second table, in the order in
+ which they were defined in CREATE TABLE.
+ Time complexity - O(N1+N2), where Ni = length(table_ref_i).
+
+ IMPLEMENTATION
+ The procedure assumes that mark_common_columns() has been called
+ for the join that is being processed.
+
+ RETURN
+ TRUE error: Some common column is ambiguous
+ FALSE OK
+*/
+
+static bool
+store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join,
+ TABLE_LIST *table_ref_1,
+ TABLE_LIST *table_ref_2,
+ List<String> *using_fields,
+ uint found_using_fields)
+{
+ Field_iterator_table_ref it_1, it_2;
+ Natural_join_column *nj_col_1, *nj_col_2;
+ Query_arena *arena, backup;
+ bool result= TRUE;
+ List<Natural_join_column> *non_join_columns;
+ DBUG_ENTER("store_natural_using_join_columns");
+
+ DBUG_ASSERT(!natural_using_join->join_columns);
+
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ if (!(non_join_columns= new List<Natural_join_column>) ||
+ !(natural_using_join->join_columns= new List<Natural_join_column>))
+ goto err;
+
+ /* Append the columns of the first join operand. */
+ for (it_1.set(table_ref_1); !it_1.end_of_fields(); it_1.next())
+ {
+ nj_col_1= it_1.get_natural_column_ref();
+ if (nj_col_1->is_common)
+ {
+ natural_using_join->join_columns->push_back(nj_col_1);
+ /* Reset the common columns for the next call to mark_common_columns. */
+ nj_col_1->is_common= FALSE;
+ }
+ else
+ non_join_columns->push_back(nj_col_1);
+ }
+
+ /*
+ Check that all columns in the USING clause are among the common
+ columns. If this is not the case, report the first one that was
+ not found in an error.
+ */
+ if (using_fields && found_using_fields < using_fields->elements)
+ {
+ String *using_field_name;
+ List_iterator_fast<String> using_fields_it(*using_fields);
+ while ((using_field_name= using_fields_it++))
+ {
+ const char *using_field_name_ptr= using_field_name->c_ptr();
+ List_iterator_fast<Natural_join_column>
+ it(*(natural_using_join->join_columns));
+ Natural_join_column *common_field;
+
+ for (;;)
+ {
+ /* If reached the end of fields, and none was found, report error. */
+ if (!(common_field= it++))
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), using_field_name_ptr,
+ current_thd->where);
+ goto err;
+ }
+ if (!my_strcasecmp(system_charset_info,
+ common_field->name(), using_field_name_ptr))
+ break; // Found match
+ }
+ }
+ }
+
+ /* Append the non-equi-join columns of the second join operand. */
+ for (it_2.set(table_ref_2); !it_2.end_of_fields(); it_2.next())
+ {
+ nj_col_2= it_2.get_natural_column_ref();
+ if (!nj_col_2->is_common)
+ non_join_columns->push_back(nj_col_2);
+ else
+ {
+ /* Reset the common columns for the next call to mark_common_columns. */
+ nj_col_2->is_common= FALSE;
+ }
+ }
+
+ if (non_join_columns->elements > 0)
+ natural_using_join->join_columns->concat(non_join_columns);
+ natural_using_join->is_join_columns_complete= TRUE;
+
+ result= FALSE;
+
+err:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Precompute and store the row types of the top-most NATURAL/USING joins.
+
+ SYNOPSIS
+ store_top_level_join_columns()
+ thd current thread
+ table_ref nested join or table in a FROM clause
+ left_neighbor neighbor table reference to the left of table_ref at the
+ same level in the join tree
+ right_neighbor neighbor table reference to the right of table_ref at the
+ same level in the join tree
+
+ DESCRIPTION
+ The procedure performs a post-order traversal of a nested join tree
+ and materializes the row types of NATURAL/USING joins in a
+ bottom-up manner until it reaches the TABLE_LIST elements that
+ represent the top-most NATURAL/USING joins. The procedure should be
+ applied to each element of SELECT_LEX::top_join_list (i.e. to each
+ top-level element of the FROM clause).
+
+ IMPLEMENTATION
+ Notice that the table references in the list nested_join->join_list
+ are in reverse order, thus when we iterate over it, we are moving
+ from the right to the left in the FROM clause.
+
+ RETURN
+ TRUE Error
+ FALSE OK
+*/
+
+static bool
+store_top_level_join_columns(THD *thd, TABLE_LIST *table_ref,
+ TABLE_LIST *left_neighbor,
+ TABLE_LIST *right_neighbor)
+{
+ Query_arena *arena, backup;
+ bool result= TRUE;
+
+ DBUG_ENTER("store_top_level_join_columns");
+
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ /* Call the procedure recursively for each nested table reference. */
+ if (table_ref->nested_join)
+ {
+ List_iterator_fast<TABLE_LIST> nested_it(table_ref->nested_join->join_list);
+ TABLE_LIST *same_level_left_neighbor= nested_it++;
+ TABLE_LIST *same_level_right_neighbor= NULL;
+ /* Left/right-most neighbors, possibly at higher levels in the join tree. */
+ TABLE_LIST *real_left_neighbor, *real_right_neighbor;
+
+ while (same_level_left_neighbor)
+ {
+ TABLE_LIST *cur_table_ref= same_level_left_neighbor;
+ same_level_left_neighbor= nested_it++;
+ /*
+ The order of RIGHT JOIN operands is reversed in 'join list' to
+ transform it into a LEFT JOIN. However, in this procedure we need
+ the join operands in their lexical order, so below we reverse the
+ join operands. Notice that this happens only in the first loop,
+ and not in the second one, as in the second loop
+ same_level_left_neighbor == NULL.
+ This is the correct behavior, because the second loop sets
+ cur_table_ref reference correctly after the join operands are
+ swapped in the first loop.
+ */
+ if (same_level_left_neighbor &&
+ cur_table_ref->outer_join & JOIN_TYPE_RIGHT)
+ {
+ /* This can happen only for JOIN ... ON. */
+ DBUG_ASSERT(table_ref->nested_join->join_list.elements == 2);
+ swap_variables(TABLE_LIST*, same_level_left_neighbor, cur_table_ref);
+ }
+
+ /*
+ Pick the parent's left and right neighbors if there are no immediate
+ neighbors at the same level.
+ */
+ real_left_neighbor= (same_level_left_neighbor) ?
+ same_level_left_neighbor : left_neighbor;
+ real_right_neighbor= (same_level_right_neighbor) ?
+ same_level_right_neighbor : right_neighbor;
+
+ if (cur_table_ref->nested_join &&
+ store_top_level_join_columns(thd, cur_table_ref,
+ real_left_neighbor, real_right_neighbor))
+ goto err;
+ same_level_right_neighbor= cur_table_ref;
+ }
+ }
+
+ /*
+ If this is a NATURAL/USING join, materialize its result columns and
+ convert to a JOIN ... ON.
+ */
+ if (table_ref->is_natural_join)
+ {
+ DBUG_ASSERT(table_ref->nested_join &&
+ table_ref->nested_join->join_list.elements == 2);
+ List_iterator_fast<TABLE_LIST> operand_it(table_ref->nested_join->join_list);
+ /*
+ Notice that the order of join operands depends on whether table_ref
+ represents a LEFT or a RIGHT join. In a RIGHT join, the operands are
+ in inverted order.
+ */
+ TABLE_LIST *table_ref_2= operand_it++; /* Second NATURAL join operand.*/
+ TABLE_LIST *table_ref_1= operand_it++; /* First NATURAL join operand. */
+ List<String> *using_fields= table_ref->join_using_fields;
+ uint found_using_fields;
+
+ /*
+ The two join operands were interchanged in the parser, change the order
+ back for 'mark_common_columns'.
+ */
+ if (table_ref_2->outer_join & JOIN_TYPE_RIGHT)
+ swap_variables(TABLE_LIST*, table_ref_1, table_ref_2);
+ if (mark_common_columns(thd, table_ref_1, table_ref_2,
+ using_fields, &found_using_fields))
+ goto err;
+
+ /*
+ Swap the join operands back, so that we pick the columns of the second
+ one as the coalesced columns. In this way the coalesced columns are the
+ same as of an equivalent LEFT JOIN.
+ */
+ if (table_ref_1->outer_join & JOIN_TYPE_RIGHT)
+ swap_variables(TABLE_LIST*, table_ref_1, table_ref_2);
+ if (store_natural_using_join_columns(thd, table_ref, table_ref_1,
+ table_ref_2, using_fields,
+ found_using_fields))
+ goto err;
+
+ /*
+ Change NATURAL JOIN to JOIN ... ON. We do this for both operands
+ because either one of them or the other is the one with the
+ natural join flag because RIGHT joins are transformed into LEFT,
+ and the two tables may be reordered.
+ */
+ table_ref_1->natural_join= table_ref_2->natural_join= NULL;
+
+ /* Add a TRUE condition to outer joins that have no common columns. */
+ if (table_ref_2->outer_join &&
+ !table_ref_1->on_expr && !table_ref_2->on_expr)
+ table_ref_2->on_expr= new Item_int((longlong) 1,1); /* Always true. */
+
+ /* Change this table reference to become a leaf for name resolution. */
+ if (left_neighbor)
+ {
+ TABLE_LIST *last_leaf_on_the_left;
+ last_leaf_on_the_left= left_neighbor->last_leaf_for_name_resolution();
+ last_leaf_on_the_left->next_name_resolution_table= table_ref;
+ }
+ if (right_neighbor)
+ {
+ TABLE_LIST *first_leaf_on_the_right;
+ first_leaf_on_the_right= right_neighbor->first_leaf_for_name_resolution();
+ table_ref->next_name_resolution_table= first_leaf_on_the_right;
+ }
+ else
+ table_ref->next_name_resolution_table= NULL;
+ }
+ result= FALSE; /* All is OK. */
+
+err:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Compute and store the row types of the top-most NATURAL/USING joins
+ in a FROM clause.
+
+ SYNOPSIS
+ setup_natural_join_row_types()
+ thd current thread
+ from_clause list of top-level table references in a FROM clause
+
+ DESCRIPTION
+ Apply the procedure 'store_top_level_join_columns' to each of the
+ top-level table referencs of the FROM clause. Adjust the list of tables
+ for name resolution - context->first_name_resolution_table to the
+ top-most, lef-most NATURAL/USING join.
+
+ IMPLEMENTATION
+ Notice that the table references in 'from_clause' are in reverse
+ order, thus when we iterate over it, we are moving from the right
+ to the left in the FROM clause.
+
+ NOTES
+ We can't run this many times as the first_name_resolution_table would
+ be different for subsequent runs when sub queries has been optimized
+ away.
+
+ RETURN
+ TRUE Error
+ FALSE OK
+*/
+
+static bool setup_natural_join_row_types(THD *thd,
+ List<TABLE_LIST> *from_clause,
+ Name_resolution_context *context)
+{
+ DBUG_ENTER("setup_natural_join_row_types");
+ thd->where= "from clause";
+ if (from_clause->elements == 0)
+ DBUG_RETURN(false); /* We come here in the case of UNIONs. */
+
+ /*
+ Do not redo work if already done:
+ 1) for stored procedures,
+ 2) for multitable update after lock failure and table reopening.
+ */
+ if (!context->select_lex->first_natural_join_processing)
+ {
+ context->first_name_resolution_table= context->natural_join_first_table;
+ DBUG_PRINT("info", ("using cached setup_natural_join_row_types"));
+ DBUG_RETURN(false);
+ }
+ context->select_lex->first_natural_join_processing= false;
+
+ List_iterator_fast<TABLE_LIST> table_ref_it(*from_clause);
+ TABLE_LIST *table_ref; /* Current table reference. */
+ /* Table reference to the left of the current. */
+ TABLE_LIST *left_neighbor;
+ /* Table reference to the right of the current. */
+ TABLE_LIST *right_neighbor= NULL;
+
+ /* Note that tables in the list are in reversed order */
+ for (left_neighbor= table_ref_it++; left_neighbor ; )
+ {
+ table_ref= left_neighbor;
+ do
+ {
+ left_neighbor= table_ref_it++;
+ }
+ while (left_neighbor && left_neighbor->sj_subq_pred);
+
+ if (store_top_level_join_columns(thd, table_ref,
+ left_neighbor, right_neighbor))
+ DBUG_RETURN(true);
+ if (left_neighbor)
+ {
+ TABLE_LIST *first_leaf_on_the_right;
+ first_leaf_on_the_right= table_ref->first_leaf_for_name_resolution();
+ left_neighbor->next_name_resolution_table= first_leaf_on_the_right;
+ }
+ right_neighbor= table_ref;
+ }
+
+ /*
+ Store the top-most, left-most NATURAL/USING join, so that we start
+ the search from that one instead of context->table_list. At this point
+ right_neighbor points to the left-most top-level table reference in the
+ FROM clause.
+ */
+ DBUG_ASSERT(right_neighbor);
+ context->first_name_resolution_table=
+ right_neighbor->first_leaf_for_name_resolution();
+ /*
+ This is only to ensure that first_name_resolution_table doesn't
+ change on re-execution
+ */
+ context->natural_join_first_table= context->first_name_resolution_table;
+ DBUG_RETURN (false);
+}
+
+
+/****************************************************************************
+** Expand all '*' in given fields
+****************************************************************************/
+
+int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields,
+ List<Item> *sum_func_list,
+ uint wild_num)
+{
+ Item *item;
+ List_iterator<Item> it(fields);
+ Query_arena *arena, backup;
+ DBUG_ENTER("setup_wild");
+ DBUG_ASSERT(wild_num != 0);
+
+ /*
+ Don't use arena if we are not in prepared statements or stored procedures
+ For PS/SP we have to use arena to remember the changes
+ */
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ thd->lex->current_select->cur_pos_in_select_list= 0;
+ while (wild_num && (item= it++))
+ {
+ if (item->type() == Item::FIELD_ITEM &&
+ ((Item_field*) item)->field_name &&
+ ((Item_field*) item)->field_name[0] == '*' &&
+ !((Item_field*) item)->field)
+ {
+ uint elem= fields.elements;
+ bool any_privileges= ((Item_field *) item)->any_privileges;
+ Item_subselect *subsel= thd->lex->current_select->master_unit()->item;
+ if (subsel &&
+ subsel->substype() == Item_subselect::EXISTS_SUBS)
+ {
+ /*
+ It is EXISTS(SELECT * ...) and we can replace * by any constant.
+
+ Item_int do not need fix_fields() because it is basic constant.
+ */
+ it.replace(new Item_int("Not_used", (longlong) 1,
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ }
+ else if (insert_fields(thd, ((Item_field*) item)->context,
+ ((Item_field*) item)->db_name,
+ ((Item_field*) item)->table_name, &it,
+ any_privileges))
+ {
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(-1);
+ }
+ if (sum_func_list)
+ {
+ /*
+ sum_func_list is a list that has the fields list as a tail.
+ Because of this we have to update the element count also for this
+ list after expanding the '*' entry.
+ */
+ sum_func_list->elements+= fields.elements - elem;
+ }
+ wild_num--;
+ }
+ else
+ thd->lex->current_select->cur_pos_in_select_list++;
+ }
+ thd->lex->current_select->cur_pos_in_select_list= UNDEF_POS;
+ if (arena)
+ {
+ /* make * substituting permanent */
+ SELECT_LEX *select_lex= thd->lex->current_select;
+ select_lex->with_wild= 0;
+#ifdef HAVE_valgrind
+ if (&select_lex->item_list != &fields) // Avoid warning
+#endif
+ /*
+ The assignment below is translated to memcpy() call (at least on some
+ platforms). memcpy() expects that source and destination areas do not
+ overlap. That problem was detected by valgrind.
+ */
+ if (&select_lex->item_list != &fields)
+ select_lex->item_list= fields;
+
+ thd->restore_active_arena(arena, &backup);
+ }
+ DBUG_RETURN(0);
+}
+
+/****************************************************************************
+** Check that all given fields exists and fill struct with current data
+****************************************************************************/
+
+bool setup_fields(THD *thd, Item **ref_pointer_array,
+ List<Item> &fields, enum_mark_columns mark_used_columns,
+ List<Item> *sum_func_list, bool allow_sum_func)
+{
+ reg2 Item *item;
+ enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
+ nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
+ List_iterator<Item> it(fields);
+ bool save_is_item_list_lookup;
+ DBUG_ENTER("setup_fields");
+ DBUG_PRINT("enter", ("ref_pointer_array: %p", ref_pointer_array));
+
+ thd->mark_used_columns= mark_used_columns;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+ if (allow_sum_func)
+ thd->lex->allow_sum_func|=
+ (nesting_map)1 << thd->lex->current_select->nest_level;
+ thd->where= THD::DEFAULT_WHERE;
+ save_is_item_list_lookup= thd->lex->current_select->is_item_list_lookup;
+ thd->lex->current_select->is_item_list_lookup= 0;
+
+ /*
+ To prevent fail on forward lookup we fill it with zerows,
+ then if we got pointer on zero after find_item_in_list we will know
+ that it is forward lookup.
+
+ There is other way to solve problem: fill array with pointers to list,
+ but it will be slower.
+
+ TODO: remove it when (if) we made one list for allfields and
+ ref_pointer_array
+ */
+ if (ref_pointer_array)
+ bzero(ref_pointer_array, sizeof(Item *) * fields.elements);
+
+ /*
+ We call set_entry() there (before fix_fields() of the whole list of field
+ items) because:
+ 1) the list of field items has same order as in the query, and the
+ Item_func_get_user_var item may go before the Item_func_set_user_var:
+ SELECT @a, @a := 10 FROM t;
+ 2) The entry->update_query_id value controls constantness of
+ Item_func_get_user_var items, so in presence of Item_func_set_user_var
+ items we have to refresh their entries before fixing of
+ Item_func_get_user_var items.
+ */
+ List_iterator<Item_func_set_user_var> li(thd->lex->set_var_list);
+ Item_func_set_user_var *var;
+ while ((var= li++))
+ var->set_entry(thd, FALSE);
+
+ Item **ref= ref_pointer_array;
+ thd->lex->current_select->cur_pos_in_select_list= 0;
+ while ((item= it++))
+ {
+ if ((!item->fixed && item->fix_fields(thd, it.ref())) ||
+ (item= *(it.ref()))->check_cols(1))
+ {
+ thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup;
+ thd->lex->allow_sum_func= save_allow_sum_func;
+ thd->mark_used_columns= save_mark_used_columns;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ }
+ if (ref)
+ *(ref++)= item;
+ if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM &&
+ sum_func_list)
+ item->split_sum_func(thd, ref_pointer_array, *sum_func_list);
+ thd->lex->used_tables|= item->used_tables();
+ thd->lex->current_select->cur_pos_in_select_list++;
+ }
+ thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup;
+ thd->lex->current_select->cur_pos_in_select_list= UNDEF_POS;
+
+ thd->lex->allow_sum_func= save_allow_sum_func;
+ thd->mark_used_columns= save_mark_used_columns;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+ DBUG_RETURN(MY_TEST(thd->is_error()));
+}
+
+
+/*
+ make list of leaves of join table tree
+
+ SYNOPSIS
+ make_leaves_list()
+ list pointer to pointer on list first element
+ tables table list
+ full_table_list whether to include tables from mergeable derived table/view.
+ we need them for checks for INSERT/UPDATE statements only.
+
+ RETURN pointer on pointer to next_leaf of last element
+*/
+
+void make_leaves_list(List<TABLE_LIST> &list, TABLE_LIST *tables,
+ bool full_table_list, TABLE_LIST *boundary)
+
+{
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
+ {
+ if (table == boundary)
+ full_table_list= !full_table_list;
+ if (full_table_list && table->is_merged_derived())
+ {
+ SELECT_LEX *select_lex= table->get_single_select();
+ /*
+ It's safe to use select_lex->leaf_tables because all derived
+ tables/views were already prepared and has their leaf_tables
+ set properly.
+ */
+ make_leaves_list(list, select_lex->get_table_list(),
+ full_table_list, boundary);
+ }
+ else
+ {
+ list.push_back(table);
+ }
+ }
+}
+
+/*
+ prepare tables
+
+ SYNOPSIS
+ setup_tables()
+ thd Thread handler
+ context name resolution contest to setup table list there
+ from_clause Top-level list of table references in the FROM clause
+ tables Table list (select_lex->table_list)
+ leaves List of join table leaves list (select_lex->leaf_tables)
+ refresh It is onle refresh for subquery
+ select_insert It is SELECT ... INSERT command
+ full_table_list a parameter to pass to the make_leaves_list function
+
+ NOTE
+ Check also that the 'used keys' and 'ignored keys' exists and set up the
+ table structure accordingly.
+ Create a list of leaf tables. For queries with NATURAL/USING JOINs,
+ compute the row types of the top most natural/using join table references
+ and link these into a list of table references for name resolution.
+
+ This has to be called for all tables that are used by items, as otherwise
+ table->map is not set and all Item_field will be regarded as const items.
+
+ RETURN
+ FALSE ok; In this case *map will includes the chosen index
+ TRUE error
+*/
+
+bool setup_tables(THD *thd, Name_resolution_context *context,
+ List<TABLE_LIST> *from_clause, TABLE_LIST *tables,
+ List<TABLE_LIST> &leaves, bool select_insert,
+ bool full_table_list)
+{
+ uint tablenr= 0;
+ List_iterator<TABLE_LIST> ti(leaves);
+ TABLE_LIST *table_list;
+
+ DBUG_ENTER("setup_tables");
+
+ DBUG_ASSERT ((select_insert && !tables->next_name_resolution_table) || !tables ||
+ (context->table_list && context->first_name_resolution_table));
+ /*
+ this is used for INSERT ... SELECT.
+ For select we setup tables except first (and its underlying tables)
+ */
+ TABLE_LIST *first_select_table= (select_insert ?
+ tables->next_local:
+ 0);
+ SELECT_LEX *select_lex= select_insert ? &thd->lex->select_lex :
+ thd->lex->current_select;
+ if (select_lex->first_cond_optimization)
+ {
+ leaves.empty();
+ if (!select_lex->is_prep_leaf_list_saved)
+ {
+ make_leaves_list(leaves, tables, full_table_list, first_select_table);
+ select_lex->leaf_tables_exec.empty();
+ }
+ else
+ {
+ List_iterator_fast <TABLE_LIST> ti(select_lex->leaf_tables_prep);
+ while ((table_list= ti++))
+ leaves.push_back(table_list);
+ }
+
+ while ((table_list= ti++))
+ {
+ TABLE *table= table_list->table;
+ if (table)
+ table->pos_in_table_list= table_list;
+ if (first_select_table &&
+ table_list->top_table() == first_select_table)
+ {
+ /* new counting for SELECT of INSERT ... SELECT command */
+ first_select_table= 0;
+ thd->lex->select_lex.insert_tables= tablenr;
+ tablenr= 0;
+ }
+ if(table_list->jtbm_subselect)
+ {
+ table_list->jtbm_table_no= tablenr;
+ }
+ else if (table)
+ {
+ table->pos_in_table_list= table_list;
+ setup_table_map(table, table_list, tablenr);
+
+ if (table_list->process_index_hints(table))
+ DBUG_RETURN(1);
+ }
+ tablenr++;
+ }
+ if (tablenr > MAX_TABLES)
+ {
+ my_error(ER_TOO_MANY_TABLES,MYF(0), static_cast<int>(MAX_TABLES));
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ List_iterator_fast <TABLE_LIST> ti(select_lex->leaf_tables_exec);
+ select_lex->leaf_tables.empty();
+ while ((table_list= ti++))
+ {
+ if(table_list->jtbm_subselect)
+ {
+ table_list->jtbm_table_no= table_list->tablenr_exec;
+ }
+ else
+ {
+ table_list->table->tablenr= table_list->tablenr_exec;
+ table_list->table->map= table_list->map_exec;
+ table_list->table->maybe_null= table_list->maybe_null_exec;
+ table_list->table->pos_in_table_list= table_list;
+ if (table_list->process_index_hints(table_list->table))
+ DBUG_RETURN(1);
+ }
+ select_lex->leaf_tables.push_back(table_list);
+ }
+ }
+
+ for (table_list= tables;
+ table_list;
+ table_list= table_list->next_local)
+ {
+ if (table_list->merge_underlying_list)
+ {
+ DBUG_ASSERT(table_list->is_merged_derived());
+ Query_arena *arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ bool res;
+ res= table_list->setup_underlying(thd);
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ if (res)
+ DBUG_RETURN(1);
+ }
+
+ if (table_list->jtbm_subselect)
+ {
+ Item *item= table_list->jtbm_subselect->optimizer;
+ if (table_list->jtbm_subselect->optimizer->fix_fields(thd, &item))
+ {
+ my_error(ER_TOO_MANY_TABLES,MYF(0), static_cast<int>(MAX_TABLES)); /* psergey-todo: WHY ER_TOO_MANY_TABLES ???*/
+ DBUG_RETURN(1);
+ }
+ DBUG_ASSERT(item == table_list->jtbm_subselect->optimizer);
+ }
+ }
+
+ /* Precompute and store the row types of NATURAL/USING joins. */
+ if (setup_natural_join_row_types(thd, from_clause, context))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ prepare tables and check access for the view tables
+
+ SYNOPSIS
+ setup_tables_and_check_access()
+ thd Thread handler
+ context name resolution contest to setup table list there
+ from_clause Top-level list of table references in the FROM clause
+ tables Table list (select_lex->table_list)
+ conds Condition of current SELECT (can be changed by VIEW)
+ leaves List of join table leaves list (select_lex->leaf_tables)
+ refresh It is onle refresh for subquery
+ select_insert It is SELECT ... INSERT command
+ want_access what access is needed
+ full_table_list a parameter to pass to the make_leaves_list function
+
+ NOTE
+ a wrapper for check_tables that will also check the resulting
+ table leaves list for access to all the tables that belong to a view
+
+ RETURN
+ FALSE ok; In this case *map will include the chosen index
+ TRUE error
+*/
+bool setup_tables_and_check_access(THD *thd,
+ Name_resolution_context *context,
+ List<TABLE_LIST> *from_clause,
+ TABLE_LIST *tables,
+ List<TABLE_LIST> &leaves,
+ bool select_insert,
+ ulong want_access_first,
+ ulong want_access,
+ bool full_table_list)
+{
+ bool first_table= true;
+ DBUG_ENTER("setup_tables_and_check_access");
+
+ if (setup_tables(thd, context, from_clause, tables,
+ leaves, select_insert, full_table_list))
+ DBUG_RETURN(TRUE);
+
+ List_iterator<TABLE_LIST> ti(leaves);
+ TABLE_LIST *table_list;
+ while((table_list= ti++))
+ {
+ if (table_list->belong_to_view && !table_list->view &&
+ check_single_table_access(thd, first_table ? want_access_first :
+ want_access, table_list, FALSE))
+ {
+ tables->hide_view_error(thd);
+ DBUG_RETURN(TRUE);
+ }
+ first_table= 0;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Create a key_map from a list of index names
+
+ SYNOPSIS
+ get_key_map_from_key_list()
+ map key_map to fill in
+ table Table
+ index_list List of index names
+
+ RETURN
+ 0 ok; In this case *map will includes the choosed index
+ 1 error
+*/
+
+bool get_key_map_from_key_list(key_map *map, TABLE *table,
+ List<String> *index_list)
+{
+ List_iterator_fast<String> it(*index_list);
+ String *name;
+ uint pos;
+
+ map->clear_all();
+ while ((name=it++))
+ {
+ if (table->s->keynames.type_names == 0 ||
+ (pos= find_type(&table->s->keynames, name->ptr(),
+ name->length(), 1)) <=
+ 0)
+ {
+ my_error(ER_KEY_DOES_NOT_EXITS, MYF(0), name->c_ptr(),
+ table->pos_in_table_list->alias);
+ map->set_all();
+ return 1;
+ }
+ map->set_bit(pos-1);
+ }
+ return 0;
+}
+
+
+/*
+ Drops in all fields instead of current '*' field
+
+ SYNOPSIS
+ insert_fields()
+ thd Thread handler
+ context Context for name resolution
+ db_name Database name in case of 'database_name.table_name.*'
+ table_name Table name in case of 'table_name.*'
+ it Pointer to '*'
+ any_privileges 0 If we should ensure that we have SELECT privileges
+ for all columns
+ 1 If any privilege is ok
+ RETURN
+ 0 ok 'it' is updated to point at last inserted
+ 1 error. Error message is generated but not sent to client
+*/
+
+bool
+insert_fields(THD *thd, Name_resolution_context *context, const char *db_name,
+ const char *table_name, List_iterator<Item> *it,
+ bool any_privileges)
+{
+ Field_iterator_table_ref field_iterator;
+ bool found;
+ char name_buff[SAFE_NAME_LEN+1];
+ DBUG_ENTER("insert_fields");
+ DBUG_PRINT("arena", ("stmt arena: 0x%lx", (ulong)thd->stmt_arena));
+
+ if (db_name && lower_case_table_names)
+ {
+ /*
+ convert database to lower case for comparison
+ We can't do this in Item_field as this would change the
+ 'name' of the item which may be used in the select list
+ */
+ strmake_buf(name_buff, db_name);
+ my_casedn_str(files_charset_info, name_buff);
+ db_name= name_buff;
+ }
+
+ found= FALSE;
+
+ /*
+ If table names are qualified, then loop over all tables used in the query,
+ else treat natural joins as leaves and do not iterate over their underlying
+ tables.
+ */
+ for (TABLE_LIST *tables= (table_name ? context->table_list :
+ context->first_name_resolution_table);
+ tables;
+ tables= (table_name ? tables->next_local :
+ tables->next_name_resolution_table)
+ )
+ {
+ Field *field;
+ TABLE *table= tables->table;
+
+ DBUG_ASSERT(tables->is_leaf_for_name_resolution());
+
+ if ((table_name && my_strcasecmp(table_alias_charset, table_name,
+ tables->alias)) ||
+ (db_name && strcmp(tables->db,db_name)))
+ continue;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ Ensure that we have access rights to all fields to be inserted. Under
+ some circumstances, this check may be skipped.
+
+ - If any_privileges is true, skip the check.
+
+ - If the SELECT privilege has been found as fulfilled already for both
+ the TABLE and TABLE_LIST objects (and both of these exist, of
+ course), the check is skipped.
+
+ - If the SELECT privilege has been found fulfilled for the TABLE object
+ and the TABLE_LIST represents a derived table other than a view (see
+ below), the check is skipped.
+
+ - If the TABLE_LIST object represents a view, we may skip checking if
+ the SELECT privilege has been found fulfilled for it, regardless of
+ the TABLE object.
+
+ - If there is no TABLE object, the test is skipped if either
+ * the TABLE_LIST does not represent a view, or
+ * the SELECT privilege has been found fulfilled.
+
+ A TABLE_LIST that is not a view may be a subquery, an
+ information_schema table, or a nested table reference. See the comment
+ for TABLE_LIST.
+ */
+ if (!((table && tables->is_non_derived() &&
+ (table->grant.privilege & SELECT_ACL)) ||
+ ((!tables->is_non_derived() &&
+ (tables->grant.privilege & SELECT_ACL)))) &&
+ !any_privileges)
+ {
+ field_iterator.set(tables);
+ if (check_grant_all_columns(thd, SELECT_ACL, &field_iterator))
+ DBUG_RETURN(TRUE);
+ }
+#endif
+
+ /*
+ Update the tables used in the query based on the referenced fields. For
+ views and natural joins this update is performed inside the loop below.
+ */
+ if (table)
+ thd->lex->used_tables|= table->map;
+
+ /*
+ Initialize a generic field iterator for the current table reference.
+ Notice that it is guaranteed that this iterator will iterate over the
+ fields of a single table reference, because 'tables' is a leaf (for
+ name resolution purposes).
+ */
+ field_iterator.set(tables);
+
+ for (; !field_iterator.end_of_fields(); field_iterator.next())
+ {
+ Item *item;
+
+ if (!(item= field_iterator.create_item(thd)))
+ DBUG_RETURN(TRUE);
+
+ /* cache the table for the Item_fields inserted by expanding stars */
+ if (item->type() == Item::FIELD_ITEM && tables->cacheable_table)
+ ((Item_field *)item)->cached_table= tables;
+
+ if (!found)
+ {
+ found= TRUE;
+ it->replace(item); /* Replace '*' with the first found item. */
+ }
+ else
+ it->after(item); /* Add 'item' to the SELECT list. */
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ Set privilege information for the fields of newly created views.
+ We have that (any_priviliges == TRUE) if and only if we are creating
+ a view. In the time of view creation we can't use the MERGE algorithm,
+ therefore if 'tables' is itself a view, it is represented by a
+ temporary table. Thus in this case we can be sure that 'item' is an
+ Item_field.
+ */
+ if (any_privileges)
+ {
+ DBUG_ASSERT((tables->field_translation == NULL && table) ||
+ tables->is_natural_join);
+ DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
+ Item_field *fld= (Item_field*) item;
+ const char *field_table_name= field_iterator.get_table_name();
+
+ if (!tables->schema_table &&
+ !(fld->have_privileges=
+ (get_column_grant(thd, field_iterator.grant(),
+ field_iterator.get_db_name(),
+ field_table_name, fld->field_name) &
+ VIEW_ANY_ACL)))
+ {
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), "ANY",
+ thd->security_ctx->priv_user,
+ thd->security_ctx->host_or_ip,
+ field_table_name);
+ DBUG_RETURN(TRUE);
+ }
+ }
+#endif
+ /*
+ field_iterator.create_item() builds used_items which we
+ have to save because changes made once and they are persistent
+ */
+ tables->persistent_used_items= tables->used_items;
+
+ if ((field= field_iterator.field()))
+ {
+ /* Mark fields as used to allow storage engine to optimze access */
+ bitmap_set_bit(field->table->read_set, field->field_index);
+ /*
+ Mark virtual fields for write and others that the virtual fields
+ depend on for read.
+ */
+ if (field->vcol_info)
+ field->table->mark_virtual_col(field);
+ if (table)
+ {
+ table->covering_keys.intersect(field->part_of_key);
+ table->merge_keys.merge(field->part_of_key);
+ }
+ if (tables->is_natural_join)
+ {
+ TABLE *field_table;
+ /*
+ In this case we are sure that the column ref will not be created
+ because it was already created and stored with the natural join.
+ */
+ Natural_join_column *nj_col;
+ if (!(nj_col= field_iterator.get_natural_column_ref()))
+ DBUG_RETURN(TRUE);
+ DBUG_ASSERT(nj_col->table_field);
+ field_table= nj_col->table_ref->table;
+ if (field_table)
+ {
+ thd->lex->used_tables|= field_table->map;
+ field_table->covering_keys.intersect(field->part_of_key);
+ field_table->merge_keys.merge(field->part_of_key);
+ field_table->used_fields++;
+ }
+ }
+ }
+ else
+ thd->lex->used_tables|= item->used_tables();
+ thd->lex->current_select->cur_pos_in_select_list++;
+ }
+ /*
+ In case of stored tables, all fields are considered as used,
+ while in the case of views, the fields considered as used are the
+ ones marked in setup_tables during fix_fields of view columns.
+ For NATURAL joins, used_tables is updated in the IF above.
+ */
+ if (table)
+ table->used_fields= table->s->fields;
+ }
+ if (found)
+ DBUG_RETURN(FALSE);
+
+ /*
+ TODO: in the case when we skipped all columns because there was a
+ qualified '*', and all columns were coalesced, we have to give a more
+ meaningful message than ER_BAD_TABLE_ERROR.
+ */
+ if (!table_name)
+ my_error(ER_NO_TABLES_USED, MYF(0));
+ else if (!db_name && !thd->db)
+ my_error(ER_NO_DB_ERROR, MYF(0));
+ else
+ {
+ char name[FN_REFLEN];
+ my_snprintf(name, sizeof(name), "%s.%s",
+ db_name ? db_name : thd->db, table_name);
+ my_error(ER_BAD_TABLE_ERROR, MYF(0), name);
+ }
+
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Wrap Item_ident
+
+ @param thd thread handle
+ @param conds pointer to the condition which should be wrapped
+*/
+
+void wrap_ident(THD *thd, Item **conds)
+{
+ Item_direct_ref_to_ident *wrapper;
+ DBUG_ASSERT((*conds)->type() == Item::FIELD_ITEM || (*conds)->type() == Item::REF_ITEM);
+ Query_arena *arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ if ((wrapper= new Item_direct_ref_to_ident((Item_ident *)(*conds))))
+ (*conds)= (Item*) wrapper;
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+}
+
+/**
+ Prepare ON expression
+
+ @param thd Thread handle
+ @param table Pointer to table list
+ @param is_update Update flag
+
+ @retval TRUE error.
+ @retval FALSE OK.
+*/
+
+bool setup_on_expr(THD *thd, TABLE_LIST *table, bool is_update)
+{
+ uchar buff[STACK_BUFF_ALLOC]; // Max argument in function
+ if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
+ return TRUE; // Fatal error flag is set!
+ for(; table; table= table->next_local)
+ {
+ TABLE_LIST *embedded; /* The table at the current level of nesting. */
+ TABLE_LIST *embedding= table; /* The parent nested table reference. */
+ do
+ {
+ embedded= embedding;
+ if (embedded->on_expr)
+ {
+ thd->where="on clause";
+ embedded->on_expr->mark_as_condition_AND_part(embedded);
+ if ((!embedded->on_expr->fixed &&
+ embedded->on_expr->fix_fields(thd, &embedded->on_expr)) ||
+ embedded->on_expr->check_cols(1))
+ return TRUE;
+ }
+ /*
+ If it's a semi-join nest, fix its "left expression", as it is used by
+ the SJ-Materialization
+ */
+ if (embedded->sj_subq_pred)
+ {
+ Item **left_expr= &embedded->sj_subq_pred->left_expr;
+ if (!(*left_expr)->fixed && (*left_expr)->fix_fields(thd, left_expr))
+ return TRUE;
+ }
+
+ embedding= embedded->embedding;
+ }
+ while (embedding &&
+ embedding->nested_join->join_list.head() == embedded);
+
+ if (table->is_merged_derived())
+ {
+ SELECT_LEX *select_lex= table->get_single_select();
+ setup_on_expr(thd, select_lex->get_table_list(), is_update);
+ }
+
+ /* process CHECK OPTION */
+ if (is_update)
+ {
+ TABLE_LIST *view= table->top_table();
+ if (view->effective_with_check)
+ {
+ if (view->prepare_check_option(thd))
+ return TRUE;
+ thd->change_item_tree(&table->check_option, view->check_option);
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ Fix all conditions and outer join expressions.
+
+ SYNOPSIS
+ setup_conds()
+ thd thread handler
+ tables list of tables for name resolving (select_lex->table_list)
+ leaves list of leaves of join table tree (select_lex->leaf_tables)
+ conds WHERE clause
+
+ DESCRIPTION
+ TODO
+
+ RETURN
+ TRUE if some error occured (e.g. out of memory)
+ FALSE if all is OK
+*/
+
+int setup_conds(THD *thd, TABLE_LIST *tables, List<TABLE_LIST> &leaves,
+ COND **conds)
+{
+ SELECT_LEX *select_lex= thd->lex->current_select;
+ TABLE_LIST *table= NULL; // For HP compilers
+ /*
+ it_is_update set to TRUE when tables of primary SELECT_LEX (SELECT_LEX
+ which belong to LEX, i.e. most up SELECT) will be updated by
+ INSERT/UPDATE/LOAD
+ NOTE: using this condition helps to prevent call of prepare_check_option()
+ from subquery of VIEW, because tables of subquery belongs to VIEW
+ (see condition before prepare_check_option() call)
+ */
+ bool it_is_update= (select_lex == &thd->lex->select_lex) &&
+ thd->lex->which_check_option_applicable();
+ bool save_is_item_list_lookup= select_lex->is_item_list_lookup;
+ TABLE_LIST *derived= select_lex->master_unit()->derived;
+ DBUG_ENTER("setup_conds");
+
+ /* Do not fix conditions for the derived tables that have been merged */
+ if (derived && derived->merged)
+ DBUG_RETURN(0);
+
+ select_lex->is_item_list_lookup= 0;
+
+ thd->mark_used_columns= MARK_COLUMNS_READ;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+ select_lex->cond_count= 0;
+ select_lex->between_count= 0;
+ select_lex->max_equal_elems= 0;
+
+ for (table= tables; table; table= table->next_local)
+ {
+ if (select_lex == &thd->lex->select_lex &&
+ select_lex->first_cond_optimization &&
+ table->merged_for_insert &&
+ table->prepare_where(thd, conds, FALSE))
+ goto err_no_arena;
+ }
+
+ if (*conds)
+ {
+ thd->where="where clause";
+ DBUG_EXECUTE("where",
+ print_where(*conds,
+ "WHERE in setup_conds",
+ QT_ORDINARY););
+ /*
+ Wrap alone field in WHERE clause in case it will be outer field of subquery
+ which need persistent pointer on it, but conds could be changed by optimizer
+ */
+ if ((*conds)->type() == Item::FIELD_ITEM && !derived)
+ wrap_ident(thd, conds);
+ (*conds)->mark_as_condition_AND_part(NO_JOIN_NEST);
+ if ((!(*conds)->fixed && (*conds)->fix_fields(thd, conds)) ||
+ (*conds)->check_cols(1))
+ goto err_no_arena;
+ }
+
+ /*
+ Apply fix_fields() to all ON clauses at all levels of nesting,
+ including the ones inside view definitions.
+ */
+ if (setup_on_expr(thd, tables, it_is_update))
+ goto err_no_arena;
+
+ if (!thd->stmt_arena->is_conventional())
+ {
+ /*
+ We are in prepared statement preparation code => we should store
+ WHERE clause changing for next executions.
+
+ We do this ON -> WHERE transformation only once per PS/SP statement.
+ */
+ select_lex->where= *conds;
+ }
+ thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup;
+ DBUG_RETURN(MY_TEST(thd->is_error()));
+
+err_no_arena:
+ select_lex->is_item_list_lookup= save_is_item_list_lookup;
+ DBUG_RETURN(1);
+}
+
+
+/******************************************************************************
+** Fill a record with data (for INSERT or UPDATE)
+** Returns : 1 if some field has wrong type
+******************************************************************************/
+
+
+/**
+ Fill the fields of a table with the values of an Item list
+
+ @param thd thread handler
+ @param table_arg the table that is being modified
+ @param fields Item_fields list to be filled
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
+
+ @details
+ fill_record() may set table->auto_increment_field_not_null and a
+ caller should make sure that it is reset after their last call to this
+ function.
+
+ @return Status
+ @retval true An error occured.
+ @retval false OK.
+*/
+
+bool
+fill_record(THD * thd, TABLE *table_arg, List<Item> &fields, List<Item> &values,
+ bool ignore_errors)
+{
+ List_iterator_fast<Item> f(fields),v(values);
+ Item *value, *fld;
+ Item_field *field;
+ TABLE *vcol_table= 0;
+ bool save_abort_on_warning= thd->abort_on_warning;
+ bool save_no_errors= thd->no_errors;
+ DBUG_ENTER("fill_record");
+
+ thd->no_errors= ignore_errors;
+ /*
+ Reset the table->auto_increment_field_not_null as it is valid for
+ only one row.
+ */
+ if (fields.elements)
+ {
+ /*
+ On INSERT or UPDATE fields are checked to be from the same table,
+ thus we safely can take table from the first field.
+ */
+ fld= (Item_field*)f++;
+ if (!(field= fld->field_for_view_update()))
+ {
+ my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name);
+ goto err;
+ }
+ DBUG_ASSERT(field->field->table == table_arg);
+ table_arg->auto_increment_field_not_null= FALSE;
+ f.rewind();
+ }
+ else if (thd->lex->unit.insert_table_with_stored_vcol)
+ vcol_table= thd->lex->unit.insert_table_with_stored_vcol;
+
+ while ((fld= f++))
+ {
+ if (!(field= fld->field_for_view_update()))
+ {
+ my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name);
+ goto err;
+ }
+ value=v++;
+ Field *rfield= field->field;
+ TABLE* table= rfield->table;
+ if (rfield == table->next_number_field)
+ table->auto_increment_field_not_null= TRUE;
+ if (rfield->vcol_info &&
+ value->type() != Item::DEFAULT_VALUE_ITEM &&
+ value->type() != Item::NULL_ITEM &&
+ table->s->table_category != TABLE_CATEGORY_TEMPORARY)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN,
+ ER(ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN),
+ rfield->field_name, table->s->table_name.str);
+ }
+ if ((!rfield->vcol_info || rfield->stored_in_db) &&
+ (value->save_in_field(rfield, 0)) < 0 && !ignore_errors)
+ {
+ my_message(ER_UNKNOWN_ERROR, ER(ER_UNKNOWN_ERROR), MYF(0));
+ goto err;
+ }
+ rfield->set_explicit_default(value);
+ DBUG_ASSERT(vcol_table == 0 || vcol_table == table);
+ vcol_table= table;
+ }
+ /* Update virtual fields*/
+ thd->abort_on_warning= FALSE;
+ if (vcol_table && vcol_table->vfield &&
+ update_virtual_fields(thd, vcol_table,
+ vcol_table->triggers ? VCOL_UPDATE_ALL :
+ VCOL_UPDATE_FOR_WRITE))
+ goto err;
+ thd->abort_on_warning= save_abort_on_warning;
+ thd->no_errors= save_no_errors;
+ DBUG_RETURN(thd->is_error());
+err:
+ thd->abort_on_warning= save_abort_on_warning;
+ thd->no_errors= save_no_errors;
+ if (fields.elements)
+ table_arg->auto_increment_field_not_null= FALSE;
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Fill fields in list with values from the list of items and invoke
+ before triggers.
+
+ @param thd thread context
+ @param table the table that is being modified
+ @param fields Item_fields list to be filled
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
+ @param event event type for triggers to be invoked
+
+ @detail
+ This function assumes that fields which values will be set and triggers
+ to be invoked belong to the same table, and that TABLE::record[0] and
+ record[1] buffers correspond to new and old versions of row respectively.
+
+ @return Status
+ @retval true An error occured.
+ @retval false OK.
+*/
+
+bool
+fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, List<Item> &fields,
+ List<Item> &values, bool ignore_errors,
+ enum trg_event_type event)
+{
+ bool result;
+ Table_triggers_list *triggers= table->triggers;
+ result= (fill_record(thd, table, fields, values, ignore_errors) ||
+ (triggers && triggers->process_triggers(thd, event,
+ TRG_ACTION_BEFORE, TRUE)));
+ /*
+ Re-calculate virtual fields to cater for cases when base columns are
+ updated by the triggers.
+ */
+ if (!result && triggers && table)
+ {
+ List_iterator_fast<Item> f(fields);
+ Item *fld;
+ Item_field *item_field;
+ if (fields.elements)
+ {
+ fld= (Item_field*)f++;
+ item_field= fld->field_for_view_update();
+ if (item_field && item_field->field && table && table->vfield)
+ {
+ DBUG_ASSERT(table == item_field->field->table);
+ result= update_virtual_fields(thd, table,
+ table->triggers ? VCOL_UPDATE_ALL :
+ VCOL_UPDATE_FOR_WRITE);
+ }
+ }
+ }
+ return result;
+}
+
+
+/**
+ Fill the field buffer of a table with the values of an Item list
+
+ @param thd thread handler
+ @param table_arg the table that is being modified
+ @param ptr pointer on pointer to record of fields
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
+ @param use_value forces usage of value of the items instead of result
+
+ @details
+ fill_record() may set table->auto_increment_field_not_null and a
+ caller should make sure that it is reset after their last call to this
+ function.
+
+ @return Status
+ @retval true An error occured.
+ @retval false OK.
+*/
+
+bool
+fill_record(THD *thd, TABLE *table, Field **ptr, List<Item> &values,
+ bool ignore_errors, bool use_value)
+{
+ List_iterator_fast<Item> v(values);
+ List<TABLE> tbl_list;
+ Item *value;
+ Field *field;
+ bool abort_on_warning_saved= thd->abort_on_warning;
+ DBUG_ENTER("fill_record");
+
+ if (!*ptr)
+ {
+ /* No fields to update, quite strange!*/
+ DBUG_RETURN(0);
+ }
+
+ /*
+ On INSERT or UPDATE fields are checked to be from the same table,
+ thus we safely can take table from the first field.
+ */
+ DBUG_ASSERT((*ptr)->table == table);
+
+ /*
+ Reset the table->auto_increment_field_not_null as it is valid for
+ only one row.
+ */
+ table->auto_increment_field_not_null= FALSE;
+ while ((field = *ptr++) && ! thd->is_error())
+ {
+ /* Ensure that all fields are from the same table */
+ DBUG_ASSERT(field->table == table);
+
+ value=v++;
+ if (field == table->next_number_field)
+ table->auto_increment_field_not_null= TRUE;
+ if (field->vcol_info &&
+ value->type() != Item::DEFAULT_VALUE_ITEM &&
+ value->type() != Item::NULL_ITEM &&
+ table->s->table_category != TABLE_CATEGORY_TEMPORARY)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN,
+ ER(ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN),
+ field->field_name, table->s->table_name.str);
+ }
+
+ if (use_value)
+ value->save_val(field);
+ else
+ if (value->save_in_field(field, 0) < 0)
+ goto err;
+ field->set_explicit_default(value);
+ }
+ /* Update virtual fields*/
+ thd->abort_on_warning= FALSE;
+ if (table->vfield &&
+ update_virtual_fields(thd, table,
+ table->triggers ? VCOL_UPDATE_ALL :
+ VCOL_UPDATE_FOR_WRITE))
+ goto err;
+ thd->abort_on_warning= abort_on_warning_saved;
+ DBUG_RETURN(thd->is_error());
+
+err:
+ thd->abort_on_warning= abort_on_warning_saved;
+ table->auto_increment_field_not_null= FALSE;
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Fill fields in an array with values from the list of items and invoke
+ before triggers.
+
+ @param thd thread context
+ @param table the table that is being modified
+ @param ptr the fields to be filled
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
+ @param event event type for triggers to be invoked
+
+ @detail
+ This function assumes that fields which values will be set and triggers
+ to be invoked belong to the same table, and that TABLE::record[0] and
+ record[1] buffers correspond to new and old versions of row respectively.
+
+ @return Status
+ @retval true An error occured.
+ @retval false OK.
+*/
+
+bool
+fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr,
+ List<Item> &values, bool ignore_errors,
+ enum trg_event_type event)
+{
+ bool result;
+ Table_triggers_list *triggers= table->triggers;
+ result= (fill_record(thd, table, ptr, values, ignore_errors, FALSE) ||
+ (triggers && triggers->process_triggers(thd, event,
+ TRG_ACTION_BEFORE, TRUE)));
+ /*
+ Re-calculate virtual fields to cater for cases when base columns are
+ updated by the triggers.
+ */
+ if (!result && triggers && *ptr)
+ {
+ DBUG_ASSERT(table == (*ptr)->table);
+ if (table->vfield)
+ result= update_virtual_fields(thd, table,
+ table->triggers ? VCOL_UPDATE_ALL :
+ VCOL_UPDATE_FOR_WRITE);
+ }
+ return result;
+
+}
+
+
+my_bool mysql_rm_tmp_tables(void)
+{
+ uint i, idx;
+ char filePath[FN_REFLEN], *tmpdir, filePathCopy[FN_REFLEN];
+ MY_DIR *dirp;
+ FILEINFO *file;
+ TABLE_SHARE share;
+ THD *thd;
+ DBUG_ENTER("mysql_rm_tmp_tables");
+
+ if (!(thd= new THD))
+ DBUG_RETURN(1);
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+
+ for (i=0; i<=mysql_tmpdir_list.max; i++)
+ {
+ tmpdir=mysql_tmpdir_list.list[i];
+ /* See if the directory exists */
+ if (!(dirp = my_dir(tmpdir,MYF(MY_WME | MY_DONT_SORT))))
+ continue;
+
+ /* Remove all SQLxxx tables from directory */
+
+ for (idx=0 ; idx < (uint) dirp->number_of_files ; idx++)
+ {
+ file=dirp->dir_entry+idx;
+
+ if (!memcmp(file->name, tmp_file_prefix,
+ tmp_file_prefix_length))
+ {
+ char *ext= fn_ext(file->name);
+ uint ext_len= strlen(ext);
+ uint filePath_len= my_snprintf(filePath, sizeof(filePath),
+ "%s%c%s", tmpdir, FN_LIBCHAR,
+ file->name);
+ if (!strcmp(reg_ext, ext))
+ {
+ handler *handler_file= 0;
+ /* We should cut file extention before deleting of table */
+ memcpy(filePathCopy, filePath, filePath_len - ext_len);
+ filePathCopy[filePath_len - ext_len]= 0;
+ init_tmp_table_share(thd, &share, "", 0, "", filePathCopy);
+ if (!open_table_def(thd, &share) &&
+ ((handler_file= get_new_handler(&share, thd->mem_root,
+ share.db_type()))))
+ {
+ handler_file->ha_delete_table(filePathCopy);
+ delete handler_file;
+ }
+ free_table_share(&share);
+ }
+ /*
+ File can be already deleted by tmp_table.file->delete_table().
+ So we hide error messages which happnes during deleting of these
+ files(MYF(0)).
+ */
+ (void) mysql_file_delete(key_file_misc, filePath, MYF(0));
+ }
+ }
+ my_dirend(dirp);
+ }
+ delete thd;
+ set_current_thd(0);
+ DBUG_RETURN(0);
+}
+
+
+/*****************************************************************************
+ unireg support functions
+*****************************************************************************/
+
+/**
+ A callback to the server internals that is used to address
+ special cases of the locking protocol.
+ Invoked when acquiring an exclusive lock, for each thread that
+ has a conflicting shared metadata lock.
+
+ This function:
+ - aborts waiting of the thread on a data lock, to make it notice
+ the pending exclusive lock and back off.
+ - if the thread is an INSERT DELAYED thread, sends it a KILL
+ signal to terminate it.
+
+ @note This function does not wait for the thread to give away its
+ locks. Waiting is done outside for all threads at once.
+
+ @param thd Current thread context
+ @param in_use The thread to wake up
+ @param needs_thr_lock_abort Indicates that to wake up thread
+ this call needs to abort its waiting
+ on table-level lock.
+
+ @retval TRUE if the thread was woken up
+ @retval FALSE otherwise.
+
+ @note It is one of two places where border between MDL and the
+ rest of the server is broken.
+*/
+
+bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
+ bool needs_thr_lock_abort)
+{
+ bool signalled= FALSE;
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ !in_use->killed)
+ {
+ in_use->killed= KILL_SYSTEM_THREAD;
+ mysql_mutex_lock(&in_use->mysys_var->mutex);
+ if (in_use->mysys_var->current_cond)
+ {
+ mysql_mutex_lock(in_use->mysys_var->current_mutex);
+ mysql_cond_broadcast(in_use->mysys_var->current_cond);
+ mysql_mutex_unlock(in_use->mysys_var->current_mutex);
+ }
+ mysql_mutex_unlock(&in_use->mysys_var->mutex);
+ signalled= TRUE;
+ }
+
+ if (needs_thr_lock_abort)
+ {
+ mysql_mutex_lock(&in_use->LOCK_thd_data);
+ for (TABLE *thd_table= in_use->open_tables;
+ thd_table ;
+ thd_table= thd_table->next)
+ {
+ /*
+ Check for TABLE::needs_reopen() is needed since in some places we call
+ handler::close() for table instance (and set TABLE::db_stat to 0)
+ and do not remove such instances from the THD::open_tables
+ for some time, during which other thread can see those instances
+ (e.g. see partitioning code).
+ */
+ if (!thd_table->needs_reopen())
+ signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ }
+ mysql_mutex_unlock(&in_use->LOCK_thd_data);
+ }
+ return signalled;
+}
+
+
+int setup_ftfuncs(SELECT_LEX *select_lex)
+{
+ List_iterator<Item_func_match> li(*(select_lex->ftfunc_list)),
+ lj(*(select_lex->ftfunc_list));
+ Item_func_match *ftf, *ftf2;
+
+ while ((ftf=li++))
+ {
+ if (ftf->fix_index())
+ return 1;
+ lj.rewind();
+ while ((ftf2=lj++) != ftf)
+ {
+ if (ftf->eq(ftf2,1) && !ftf2->master)
+ ftf2->master=ftf;
+ }
+ }
+
+ return 0;
+}
+
+
+int init_ftfuncs(THD *thd, SELECT_LEX *select_lex, bool no_order)
+{
+ if (select_lex->ftfunc_list->elements)
+ {
+ List_iterator<Item_func_match> li(*(select_lex->ftfunc_list));
+ Item_func_match *ifm;
+ DBUG_PRINT("info",("Performing FULLTEXT search"));
+
+ while ((ifm=li++))
+ ifm->init_search(no_order);
+ }
+ return 0;
+}
+
+
+/*
+ open new .frm format table
+
+ SYNOPSIS
+ open_new_frm()
+ THD thread handler
+ path path to .frm file (without extension)
+ alias alias for table
+ db database
+ table_name name of table
+ db_stat open flags (for example ->OPEN_KEYFILE|HA_OPEN_RNDFILE..)
+ can be 0 (example in ha_example_table)
+ prgflag READ_ALL etc..
+ ha_open_flags HA_OPEN_ABORT_IF_LOCKED etc..
+ outparam result table
+ table_desc TABLE_LIST descriptor
+ mem_root temporary MEM_ROOT for parsing
+*/
+
+bool
+open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
+ uint db_stat, uint prgflag,
+ uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc,
+ MEM_ROOT *mem_root)
+{
+ LEX_STRING pathstr;
+ File_parser *parser;
+ char path[FN_REFLEN+1];
+ DBUG_ENTER("open_new_frm");
+
+ /* Create path with extension */
+ pathstr.length= (uint) (strxnmov(path, sizeof(path) - 1,
+ share->normalized_path.str,
+ reg_ext,
+ NullS) - path);
+ pathstr.str= path;
+
+ if ((parser= sql_parse_prepare(&pathstr, mem_root, 1)))
+ {
+ if (is_equal(&view_type, parser->type()))
+ {
+ if (table_desc == 0 || table_desc->required_type == FRMTYPE_TABLE)
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str,
+ "BASE TABLE");
+ goto err;
+ }
+ if (mysql_make_view(thd, parser, table_desc,
+ (prgflag & OPEN_VIEW_NO_PARSE)))
+ goto err;
+ status_var_increment(thd->status_var.opened_views);
+ }
+ else
+ {
+ /* only VIEWs are supported now */
+ my_error(ER_FRM_UNKNOWN_TYPE, MYF(0), share->path.str, parser->type()->str);
+ goto err;
+ }
+ DBUG_RETURN(0);
+ }
+
+err:
+ DBUG_RETURN(1);
+}
+
+
+bool is_equal(const LEX_STRING *a, const LEX_STRING *b)
+{
+ return a->length == b->length && !strncmp(a->str, b->str, a->length);
+}
+
+
+/*
+ Tells if two (or more) tables have auto_increment columns and we want to
+ lock those tables with a write lock.
+
+ SYNOPSIS
+ has_two_write_locked_tables_with_auto_increment
+ tables Table list
+
+ NOTES:
+ Call this function only when you have established the list of all tables
+ which you'll want to update (including stored functions, triggers, views
+ inside your statement).
+*/
+
+static bool
+has_write_table_with_auto_increment(TABLE_LIST *tables)
+{
+ for (TABLE_LIST *table= tables; table; table= table->next_global)
+ {
+ /* we must do preliminary checks as table->table may be NULL */
+ if (!table->placeholder() &&
+ table->table->found_next_number_field &&
+ (table->lock_type >= TL_WRITE_ALLOW_WRITE))
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ checks if we have select tables in the table list and write tables
+ with auto-increment column.
+
+ SYNOPSIS
+ has_two_write_locked_tables_with_auto_increment_and_select
+ tables Table list
+
+ RETURN VALUES
+
+ -true if the table list has atleast one table with auto-increment column
+
+
+ and atleast one table to select from.
+ -false otherwise
+*/
+
+static bool
+has_write_table_with_auto_increment_and_select(TABLE_LIST *tables)
+{
+ bool has_select= false;
+ bool has_auto_increment_tables = has_write_table_with_auto_increment(tables);
+ for(TABLE_LIST *table= tables; table; table= table->next_global)
+ {
+ if (!table->placeholder() &&
+ (table->lock_type <= TL_READ_NO_INSERT))
+ {
+ has_select= true;
+ break;
+ }
+ }
+ return(has_select && has_auto_increment_tables);
+}
+
+/*
+ Tells if there is a table whose auto_increment column is a part
+ of a compound primary key while is not the first column in
+ the table definition.
+
+ @param tables Table list
+
+ @return true if the table exists, fais if does not.
+*/
+
+static bool
+has_write_table_auto_increment_not_first_in_pk(TABLE_LIST *tables)
+{
+ for (TABLE_LIST *table= tables; table; table= table->next_global)
+ {
+ /* we must do preliminary checks as table->table may be NULL */
+ if (!table->placeholder() &&
+ table->table->found_next_number_field &&
+ (table->lock_type >= TL_WRITE_ALLOW_WRITE)
+ && table->table->s->next_number_keypart != 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ Open and lock system tables for read.
+
+ SYNOPSIS
+ open_system_tables_for_read()
+ thd Thread context.
+ table_list List of tables to open.
+ backup Pointer to Open_tables_state instance where
+ information about currently open tables will be
+ saved, and from which will be restored when we will
+ end work with system tables.
+
+ NOTES
+ Thanks to restrictions which we put on opening and locking of
+ system tables for writing, we can open and lock them for reading
+ even when we already have some other tables open and locked. One
+ must call close_system_tables() to close systems tables opened
+ with this call.
+
+ NOTES
+ In some situations we use this function to open system tables for
+ writing. It happens, for examples, with statistical tables when
+ they are updated by an ANALYZE command. In these cases we should
+ guarantee that system tables will not be deadlocked.
+
+ RETURN
+ FALSE Success
+ TRUE Error
+*/
+
+bool
+open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
+ Open_tables_backup *backup)
+{
+ Query_tables_list query_tables_list_backup;
+ LEX *lex= thd->lex;
+
+ DBUG_ENTER("open_system_tables_for_read");
+
+ /*
+ Besides using new Open_tables_state for opening system tables,
+ we also have to backup and reset/and then restore part of LEX
+ which is accessed by open_tables() in order to determine if
+ prelocking is needed and what tables should be added for it.
+ close_system_tables() doesn't require such treatment.
+ */
+ lex->reset_n_backup_query_tables_list(&query_tables_list_backup);
+ thd->reset_n_backup_open_tables_state(backup);
+
+ if (open_and_lock_tables(thd, table_list, FALSE,
+ MYSQL_OPEN_IGNORE_FLUSH |
+ MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ lex->restore_backup_query_tables_list(&query_tables_list_backup);
+ thd->restore_backup_open_tables_state(backup);
+ DBUG_RETURN(TRUE);
+ }
+
+ for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
+ {
+ DBUG_ASSERT(tables->table->s->table_category == TABLE_CATEGORY_SYSTEM);
+ tables->table->use_all_columns();
+ }
+ lex->restore_backup_query_tables_list(&query_tables_list_backup);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Close system tables, opened with open_system_tables_for_read().
+
+ SYNOPSIS
+ close_system_tables()
+ thd Thread context
+ backup Pointer to Open_tables_backup instance which holds
+ information about tables which were open before we
+ decided to access system tables.
+*/
+
+void
+close_system_tables(THD *thd, Open_tables_backup *backup)
+{
+ close_thread_tables(thd);
+ thd->restore_backup_open_tables_state(backup);
+}
+
+
+/**
+ A helper function to close a mysql.* table opened
+ in an auxiliary THD during bootstrap or in the main
+ connection, when we know that there are no locks
+ held by the connection due to a preceding implicit
+ commit.
+
+ We need this function since we'd like to not
+ just close the system table, but also release
+ the metadata lock on it.
+
+ Note, that in LOCK TABLES mode this function
+ does not release the metadata lock. But in this
+ mode the table can be opened only if it is locked
+ explicitly with LOCK TABLES.
+*/
+
+void
+close_mysql_tables(THD *thd)
+{
+ if (! thd->in_sub_stmt)
+ trans_commit_stmt(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+}
+
+/*
+ Open and lock one system table for update.
+
+ SYNOPSIS
+ open_system_table_for_update()
+ thd Thread context.
+ one_table Table to open.
+
+ NOTES
+ Table opened with this call should closed using close_thread_tables().
+
+ RETURN
+ 0 Error
+ # Pointer to TABLE object of system table
+*/
+
+TABLE *
+open_system_table_for_update(THD *thd, TABLE_LIST *one_table)
+{
+ DBUG_ENTER("open_system_table_for_update");
+
+ TABLE *table= open_ltable(thd, one_table, one_table->lock_type,
+ MYSQL_LOCK_IGNORE_TIMEOUT);
+ if (table)
+ {
+ DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_SYSTEM);
+ table->use_all_columns();
+ }
+
+ DBUG_RETURN(table);
+}
+
+/**
+ Open a log table.
+ Opening such tables is performed internally in the server
+ implementation, and is a 'nested' open, since some tables
+ might be already opened by the current thread.
+ The thread context before this call is saved, and is restored
+ when calling close_log_table().
+ @param thd The current thread
+ @param one_table Log table to open
+ @param backup [out] Temporary storage used to save the thread context
+*/
+TABLE *
+open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup)
+{
+ uint flags= ( MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |
+ MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |
+ MYSQL_OPEN_IGNORE_FLUSH |
+ MYSQL_LOCK_IGNORE_TIMEOUT |
+ MYSQL_LOCK_LOG_TABLE);
+ TABLE *table;
+ /* Save value that is changed in mysql_lock_tables() */
+ ulonglong save_utime_after_lock= thd->utime_after_lock;
+ DBUG_ENTER("open_log_table");
+
+ thd->reset_n_backup_open_tables_state(backup);
+
+ if ((table= open_ltable(thd, one_table, one_table->lock_type, flags)))
+ {
+ DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_LOG);
+ /* Make sure all columns get assigned to a default value */
+ table->use_all_columns();
+ DBUG_ASSERT(table->no_replicate);
+ }
+ else
+ thd->restore_backup_open_tables_state(backup);
+
+ thd->utime_after_lock= save_utime_after_lock;
+ DBUG_RETURN(table);
+}
+
+/**
+ Close a log table.
+ The last table opened by open_log_table()
+ is closed, then the thread context is restored.
+ @param thd The current thread
+ @param backup [in] the context to restore.
+*/
+void close_log_table(THD *thd, Open_tables_backup *backup)
+{
+ close_system_tables(thd, backup);
+}
+
+
+/**
+ @brief
+ Remove 'fixed' flag from items in a list
+
+ @param items list of items to un-fix
+
+ @details
+ This function sets to 0 the 'fixed' flag for items in the 'items' list.
+ It's needed to force correct marking of views' fields for INSERT/UPDATE
+ statements.
+*/
+
+void unfix_fields(List<Item> &fields)
+{
+ List_iterator<Item> li(fields);
+ Item *item;
+ while ((item= li++))
+ item->fixed= 0;
+}
+
+
+/**
+ Check result of dynamic column function and issue error if it is needed
+
+ @param rc The result code of dynamic column function
+
+ @return the result code which was get as an argument\
+*/
+
+int dynamic_column_error_message(enum_dyncol_func_result rc)
+{
+ switch (rc) {
+ case ER_DYNCOL_YES:
+ case ER_DYNCOL_OK:
+ case ER_DYNCOL_TRUNCATED:
+ break; // it is not an error
+ case ER_DYNCOL_FORMAT:
+ my_error(ER_DYN_COL_WRONG_FORMAT, MYF(0));
+ break;
+ case ER_DYNCOL_LIMIT:
+ my_error(ER_DYN_COL_IMPLEMENTATION_LIMIT, MYF(0));
+ break;
+ case ER_DYNCOL_RESOURCE:
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ break;
+ case ER_DYNCOL_DATA:
+ my_error(ER_DYN_COL_DATA, MYF(0));
+ break;
+ case ER_DYNCOL_UNKNOWN_CHARSET:
+ my_error(ER_DYN_COL_WRONG_CHARSET, MYF(0));
+ break;
+ }
+ return rc;
+}
+
+/**
+ @} (end of group Data_Dictionary)
+*/
diff --git a/sql/sql_base.h b/sql/sql_base.h
new file mode 100644
index 00000000000..a6d90199860
--- /dev/null
+++ b/sql/sql_base.h
@@ -0,0 +1,640 @@
+/* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_BASE_INCLUDED
+#define SQL_BASE_INCLUDED
+
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_trigger.h" /* trg_event_type */
+#include "sql_class.h" /* enum_mark_columns */
+#include "mysqld.h" /* key_map */
+#include "table_cache.h"
+
+class Item_ident;
+struct Name_resolution_context;
+class Open_table_context;
+class Open_tables_state;
+class Prelocking_strategy;
+struct TABLE_LIST;
+class THD;
+struct handlerton;
+struct TABLE;
+
+typedef class st_select_lex SELECT_LEX;
+
+typedef struct st_lock_param_type ALTER_PARTITION_PARAM_TYPE;
+
+/*
+ This enumeration type is used only by the function find_item_in_list
+ to return the info on how an item has been resolved against a list
+ of possibly aliased items.
+ The item can be resolved:
+ - against an alias name of the list's element (RESOLVED_AGAINST_ALIAS)
+ - against non-aliased field name of the list (RESOLVED_WITH_NO_ALIAS)
+ - against an aliased field name of the list (RESOLVED_BEHIND_ALIAS)
+ - ignoring the alias name in cases when SQL requires to ignore aliases
+ (e.g. when the resolved field reference contains a table name or
+ when the resolved item is an expression) (RESOLVED_IGNORING_ALIAS)
+*/
+enum enum_resolution_type {
+ NOT_RESOLVED=0,
+ RESOLVED_IGNORING_ALIAS,
+ RESOLVED_BEHIND_ALIAS,
+ RESOLVED_WITH_NO_ALIAS,
+ RESOLVED_AGAINST_ALIAS
+};
+
+enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND,
+ IGNORE_ERRORS, REPORT_EXCEPT_NON_UNIQUE,
+ IGNORE_EXCEPT_NON_UNIQUE};
+
+uint create_tmp_table_def_key(THD *thd, char *key, const char *db,
+ const char *table_name);
+uint get_table_def_key(const TABLE_LIST *table_list, const char **key);
+TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update,
+ uint lock_flags);
+
+/* mysql_lock_tables() and open_table() flags bits */
+#define MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK 0x0001
+#define MYSQL_OPEN_IGNORE_FLUSH 0x0002
+/* MYSQL_OPEN_TEMPORARY_ONLY (0x0004) is not used anymore. */
+#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008
+#define MYSQL_LOCK_LOG_TABLE 0x0010
+/**
+ Do not try to acquire a metadata lock on the table: we
+ already have one.
+*/
+#define MYSQL_OPEN_HAS_MDL_LOCK 0x0020
+/**
+ If in locked tables mode, ignore the locked tables and get
+ a new instance of the table.
+*/
+#define MYSQL_OPEN_GET_NEW_TABLE 0x0040
+/* 0x0080 used to be MYSQL_OPEN_SKIP_TEMPORARY */
+/** Fail instead of waiting when conficting metadata lock is discovered. */
+#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100
+/** Open tables using MDL_SHARED lock instead of one specified in parser. */
+#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0200
+/**
+ Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified
+ in parser.
+*/
+#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0400
+/**
+ When opening or locking the table, use the maximum timeout
+ (LONG_TIMEOUT = 1 year) rather than the user-supplied timeout value.
+*/
+#define MYSQL_LOCK_IGNORE_TIMEOUT 0x0800
+/**
+ When acquiring "strong" (SNW, SNRW, X) metadata locks on tables to
+ be open do not acquire global and schema-scope IX locks.
+*/
+#define MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK 0x1000
+#define MYSQL_LOCK_NOT_TEMPORARY 0x2000
+/**
+ Only check THD::killed if waits happen (e.g. wait on MDL, wait on
+ table flush, wait on thr_lock.c locks) while opening and locking table.
+*/
+#define MYSQL_OPEN_IGNORE_KILLED 0x8000
+
+/** Please refer to the internals manual. */
+#define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\
+ MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |\
+ MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\
+ MYSQL_LOCK_IGNORE_TIMEOUT |\
+ MYSQL_OPEN_GET_NEW_TABLE |\
+ MYSQL_OPEN_HAS_MDL_LOCK)
+
+bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
+ Open_table_context *ot_ctx);
+
+bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
+ uint db_stat, uint prgflag,
+ uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc,
+ MEM_ROOT *mem_root);
+
+bool get_key_map_from_key_list(key_map *map, TABLE *table,
+ List<String> *index_list);
+TABLE *open_table_uncached(THD *thd, handlerton *hton, const char *path,
+ const char *db, const char *table_name,
+ bool add_to_temporary_tables_list,
+ bool open_in_engine);
+TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name);
+TABLE *find_write_locked_table(TABLE *list, const char *db,
+ const char *table_name);
+thr_lock_type read_lock_type_for_table(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list,
+ bool routine_modifies_data);
+
+my_bool mysql_rm_tmp_tables(void);
+bool rm_temporary_table(handlerton *base, const char *path);
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables,
+ const MDL_savepoint &start_of_statement_svp);
+TABLE_LIST *find_table_in_list(TABLE_LIST *table,
+ TABLE_LIST *TABLE_LIST::*link,
+ const char *db_name,
+ const char *table_name);
+TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name);
+bool find_and_use_temporary_table(THD *thd, const char *db,
+ const char *table_name, TABLE **out_table);
+TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl);
+bool find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl,
+ TABLE **out_table);
+TABLE *find_temporary_table(THD *thd, const char *table_key,
+ uint table_key_length);
+void close_thread_tables(THD *thd);
+bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
+ List<Item> &fields,
+ List<Item> &values,
+ bool ignore_errors,
+ enum trg_event_type event);
+bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
+ Field **field,
+ List<Item> &values,
+ bool ignore_errors,
+ enum trg_event_type event);
+bool insert_fields(THD *thd, Name_resolution_context *context,
+ const char *db_name, const char *table_name,
+ List_iterator<Item> *it, bool any_privileges);
+void make_leaves_list(List<TABLE_LIST> &list, TABLE_LIST *tables,
+ bool full_table_list, TABLE_LIST *boundary);
+int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields,
+ List<Item> *sum_func_list, uint wild_num);
+bool setup_fields(THD *thd, Item** ref_pointer_array,
+ List<Item> &item, enum_mark_columns mark_used_columns,
+ List<Item> *sum_func_list, bool allow_sum_func);
+void unfix_fields(List<Item> &items);
+bool fill_record(THD * thd, TABLE *table_arg, List<Item> &fields,
+ List<Item> &values, bool ignore_errors);
+bool fill_record(THD *thd, TABLE *table, Field **field, List<Item> &values,
+ bool ignore_errors, bool use_value);
+
+Field *
+find_field_in_tables(THD *thd, Item_ident *item,
+ TABLE_LIST *first_table, TABLE_LIST *last_table,
+ Item **ref, find_item_error_report_type report_error,
+ bool check_privileges, bool register_tree_change);
+Field *
+find_field_in_table_ref(THD *thd, TABLE_LIST *table_list,
+ const char *name, uint length,
+ const char *item_name, const char *db_name,
+ const char *table_name, Item **ref,
+ bool check_privileges, bool allow_rowid,
+ uint *cached_field_index_ptr,
+ bool register_tree_change, TABLE_LIST **actual_table);
+Field *
+find_field_in_table(THD *thd, TABLE *table, const char *name, uint length,
+ bool allow_rowid, uint *cached_field_index_ptr);
+Field *
+find_field_in_table_sef(TABLE *table, const char *name);
+Item ** find_item_in_list(Item *item, List<Item> &items, uint *counter,
+ find_item_error_report_type report_error,
+ enum_resolution_type *resolution);
+bool setup_tables(THD *thd, Name_resolution_context *context,
+ List<TABLE_LIST> *from_clause, TABLE_LIST *tables,
+ List<TABLE_LIST> &leaves, bool select_insert,
+ bool full_table_list);
+bool setup_tables_and_check_access(THD *thd,
+ Name_resolution_context *context,
+ List<TABLE_LIST> *from_clause,
+ TABLE_LIST *tables,
+ List<TABLE_LIST> &leaves,
+ bool select_insert,
+ ulong want_access_first,
+ ulong want_access,
+ bool full_table_list);
+bool wait_while_table_is_used(THD *thd, TABLE *table,
+ enum ha_extra_function function);
+
+void drop_open_table(THD *thd, TABLE *table, const char *db_name,
+ const char *table_name);
+void update_non_unique_table_error(TABLE_LIST *update,
+ const char *operation,
+ TABLE_LIST *duplicate);
+int setup_conds(THD *thd, TABLE_LIST *tables, List<TABLE_LIST> &leaves,
+ COND **conds);
+void wrap_ident(THD *thd, Item **conds);
+int setup_ftfuncs(SELECT_LEX* select);
+int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order);
+bool lock_table_names(THD *thd, TABLE_LIST *table_list,
+ TABLE_LIST *table_list_end, ulong lock_wait_timeout,
+ uint flags);
+bool open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags,
+ Prelocking_strategy *prelocking_strategy);
+/* open_and_lock_tables with optional derived handling */
+bool open_and_lock_tables(THD *thd, TABLE_LIST *tables,
+ bool derived, uint flags,
+ Prelocking_strategy *prelocking_strategy);
+/* simple open_and_lock_tables without derived handling for single table */
+TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l,
+ thr_lock_type lock_type, uint flags,
+ Prelocking_strategy *prelocking_strategy);
+bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags,
+ uint dt_phases);
+bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags);
+int decide_logging_format(THD *thd, TABLE_LIST *tables);
+void free_io_cache(TABLE *entry);
+void intern_close_table(TABLE *entry);
+void kill_delayed_threads_for_table(TABLE_SHARE *share);
+void close_thread_table(THD *thd, TABLE **table_ptr);
+bool close_temporary_tables(THD *thd);
+TABLE_LIST *unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
+ bool check_alias);
+int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans);
+void close_temporary_table(THD *thd, TABLE *table, bool free_share,
+ bool delete_table);
+void close_temporary(TABLE *table, bool free_share, bool delete_table);
+bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db,
+ const char *table_name);
+bool open_temporary_tables(THD *thd, TABLE_LIST *tl_list);
+bool open_temporary_table(THD *thd, TABLE_LIST *tl);
+bool is_equal(const LEX_STRING *a, const LEX_STRING *b);
+
+class Open_tables_backup;
+/* Functions to work with system tables. */
+bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
+ Open_tables_backup *backup);
+void close_system_tables(THD *thd, Open_tables_backup *backup);
+void close_mysql_tables(THD *thd);
+TABLE *open_system_table_for_update(THD *thd, TABLE_LIST *one_table);
+TABLE *open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup);
+void close_log_table(THD *thd, Open_tables_backup *backup);
+
+TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table,
+ Open_tables_state *backup);
+void close_performance_schema_table(THD *thd, Open_tables_state *backup);
+
+bool close_cached_tables(THD *thd, TABLE_LIST *tables,
+ bool wait_for_refresh, ulong timeout);
+bool close_cached_connection_tables(THD *thd, LEX_STRING *connect_string);
+void close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
+ ha_extra_function extra,
+ TABLE *skip_table);
+OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild);
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+ const char *cache_key, uint cache_key_length,
+ MEM_ROOT *mem_root, uint flags);
+
+static inline bool tdc_open_view(THD *thd, TABLE_LIST *table_list,
+ const char *alias, MEM_ROOT *mem_root,
+ uint flags)
+{
+ const char *key;
+ uint key_length= get_table_def_key(table_list, &key);
+ return tdc_open_view(thd, table_list, alias, key, key_length, mem_root, flags);
+}
+
+TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db,
+ const char *table_name,
+ bool no_error);
+void mark_tmp_table_for_reuse(TABLE *table);
+
+int update_virtual_fields(THD *thd, TABLE *table,
+ enum enum_vcol_update_mode vcol_update_mode= VCOL_UPDATE_FOR_READ);
+int dynamic_column_error_message(enum_dyncol_func_result rc);
+
+/* open_and_lock_tables with optional derived handling */
+int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived);
+
+extern "C" int simple_raw_key_cmp(void* arg, const void* key1,
+ const void* key2);
+extern "C" int count_distinct_walk(void *elem, element_count count, void *arg);
+int simple_str_key_cmp(void* arg, uchar* key1, uchar* key2);
+
+extern Item **not_found_item;
+extern Field *not_found_field;
+extern Field *view_ref_found;
+
+/**
+ clean/setup table fields and map.
+
+ @param table TABLE structure pointer (which should be setup)
+ @param table_list TABLE_LIST structure pointer (owner of TABLE)
+ @param tablenr table number
+*/
+
+
+inline void setup_table_map(TABLE *table, TABLE_LIST *table_list, uint tablenr)
+{
+ table->used_fields= 0;
+ table_list->reset_const_table();
+ table->null_row= 0;
+ table->status= STATUS_NO_RECORD;
+ table->maybe_null= table_list->outer_join;
+ TABLE_LIST *embedding= table_list->embedding;
+ while (!table->maybe_null && embedding)
+ {
+ table->maybe_null= embedding->outer_join;
+ embedding= embedding->embedding;
+ }
+ table->tablenr= tablenr;
+ table->map= (table_map) 1 << tablenr;
+ table->force_index= table_list->force_index;
+ table->force_index_order= table->force_index_group= 0;
+ table->covering_keys= table->s->keys_for_keyread;
+ table->merge_keys.clear_all();
+ TABLE_LIST *orig= table_list->select_lex ?
+ table_list->select_lex->master_unit()->derived : 0;
+ if (!orig || !orig->is_merged_derived())
+ {
+ /* Tables merged from derived were set up already.*/
+ table->covering_keys= table->s->keys_for_keyread;
+ table->merge_keys.clear_all();
+ }
+}
+
+inline TABLE_LIST *find_table_in_global_list(TABLE_LIST *table,
+ const char *db_name,
+ const char *table_name)
+{
+ return find_table_in_list(table, &TABLE_LIST::next_global,
+ db_name, table_name);
+}
+
+inline TABLE_LIST *find_table_in_local_list(TABLE_LIST *table,
+ const char *db_name,
+ const char *table_name)
+{
+ return find_table_in_list(table, &TABLE_LIST::next_local,
+ db_name, table_name);
+}
+
+
+inline bool setup_fields_with_no_wrap(THD *thd, Item **ref_pointer_array,
+ List<Item> &item,
+ enum_mark_columns mark_used_columns,
+ List<Item> *sum_func_list,
+ bool allow_sum_func)
+{
+ bool res;
+ thd->lex->select_lex.no_wrap_view_item= TRUE;
+ res= setup_fields(thd, ref_pointer_array, item, mark_used_columns,
+ sum_func_list, allow_sum_func);
+ thd->lex->select_lex.no_wrap_view_item= FALSE;
+ return res;
+}
+
+/**
+ An abstract class for a strategy specifying how the prelocking
+ algorithm should extend the prelocking set while processing
+ already existing elements in the set.
+*/
+
+class Prelocking_strategy
+{
+public:
+ virtual ~Prelocking_strategy() { }
+
+ virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp,
+ bool *need_prelocking) = 0;
+ virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking) = 0;
+ virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)= 0;
+};
+
+
+/**
+ A Strategy for prelocking algorithm suitable for DML statements.
+
+ Ensures that all tables used by all statement's SF/SP/triggers and
+ required for foreign key checks are prelocked and SF/SPs used are
+ cached.
+*/
+
+class DML_prelocking_strategy : public Prelocking_strategy
+{
+public:
+ virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp,
+ bool *need_prelocking);
+ virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+ virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+};
+
+
+/**
+ A strategy for prelocking algorithm to be used for LOCK TABLES
+ statement.
+*/
+
+class Lock_tables_prelocking_strategy : public DML_prelocking_strategy
+{
+ virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+};
+
+
+/**
+ Strategy for prelocking algorithm to be used for ALTER TABLE statements.
+
+ Unlike DML or LOCK TABLES strategy, it doesn't
+ prelock triggers, views or stored routines, since they are not
+ used during ALTER.
+*/
+
+class Alter_table_prelocking_strategy : public Prelocking_strategy
+{
+public:
+ virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp,
+ bool *need_prelocking);
+ virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+ virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+};
+
+
+inline bool
+open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags)
+{
+ DML_prelocking_strategy prelocking_strategy;
+
+ return open_tables(thd, tables, counter, flags, &prelocking_strategy);
+}
+
+inline TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l,
+ thr_lock_type lock_type, uint flags)
+{
+ DML_prelocking_strategy prelocking_strategy;
+
+ return open_n_lock_single_table(thd, table_l, lock_type, flags,
+ &prelocking_strategy);
+}
+
+
+/* open_and_lock_tables with derived handling */
+inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables,
+ bool derived, uint flags)
+{
+ DML_prelocking_strategy prelocking_strategy;
+
+ return open_and_lock_tables(thd, tables, derived, flags,
+ &prelocking_strategy);
+}
+
+
+bool restart_trans_for_tables(THD *thd, TABLE_LIST *table);
+
+/**
+ A context of open_tables() function, used to recover
+ from a failed open_table() or open_routine() attempt.
+*/
+
+class Open_table_context
+{
+public:
+ enum enum_open_table_action
+ {
+ OT_NO_ACTION= 0,
+ OT_BACKOFF_AND_RETRY,
+ OT_REOPEN_TABLES,
+ OT_DISCOVER,
+ OT_REPAIR
+ };
+ Open_table_context(THD *thd, uint flags);
+
+ bool recover_from_failed_open();
+ bool request_backoff_action(enum_open_table_action action_arg,
+ TABLE_LIST *table);
+
+ bool can_recover_from_failed_open() const
+ { return m_action != OT_NO_ACTION; }
+
+ /**
+ When doing a back-off, we close all tables acquired by this
+ statement. Return an MDL savepoint taken at the beginning of
+ the statement, so that we can rollback to it before waiting on
+ locks.
+ */
+ const MDL_savepoint &start_of_statement_svp() const
+ {
+ return m_start_of_statement_svp;
+ }
+
+ inline ulong get_timeout() const
+ {
+ return m_timeout;
+ }
+
+ uint get_flags() const { return m_flags; }
+
+ /**
+ Set flag indicating that we have already acquired metadata lock
+ protecting this statement against GRL while opening tables.
+ */
+ void set_has_protection_against_grl()
+ {
+ m_has_protection_against_grl= TRUE;
+ }
+
+ bool has_protection_against_grl() const
+ {
+ return m_has_protection_against_grl;
+ }
+
+private:
+ /* THD for which tables are opened. */
+ THD *m_thd;
+ /**
+ For OT_DISCOVER and OT_REPAIR actions, the table list element for
+ the table which definition should be re-discovered or which
+ should be repaired.
+ */
+ TABLE_LIST *m_failed_table;
+ MDL_savepoint m_start_of_statement_svp;
+ /**
+ Lock timeout in seconds. Initialized to LONG_TIMEOUT when opening system
+ tables or to the "lock_wait_timeout" system variable for regular tables.
+ */
+ ulong m_timeout;
+ /* open_table() flags. */
+ uint m_flags;
+ /** Back off action. */
+ enum enum_open_table_action m_action;
+ /**
+ Whether we had any locks when this context was created.
+ If we did, they are from the previous statement of a transaction,
+ and we can't safely do back-off (and release them).
+ */
+ bool m_has_locks;
+ /**
+ Indicates that in the process of opening tables we have acquired
+ protection against global read lock.
+ */
+ bool m_has_protection_against_grl;
+};
+
+
+/**
+ Check if a TABLE_LIST instance represents a pre-opened temporary table.
+*/
+
+inline bool is_temporary_table(TABLE_LIST *tl)
+{
+ if (tl->view || tl->schema_table)
+ return FALSE;
+
+ if (!tl->table)
+ return FALSE;
+
+ /*
+ NOTE: 'table->s' might be NULL for specially constructed TABLE
+ instances. See SHOW TRIGGERS for example.
+ */
+
+ if (!tl->table->s)
+ return FALSE;
+
+ return tl->table->s->tmp_table != NO_TMP_TABLE;
+}
+
+
+/**
+ This internal handler is used to trap ER_NO_SUCH_TABLE.
+*/
+
+class No_such_table_error_handler : public Internal_error_handler
+{
+public:
+ No_such_table_error_handler()
+ : m_handled_errors(0), m_unhandled_errors(0)
+ {}
+
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+
+ /**
+ Returns TRUE if one or more ER_NO_SUCH_TABLE errors have been
+ trapped and no other errors have been seen. FALSE otherwise.
+ */
+ bool safely_trapped_errors();
+
+private:
+ int m_handled_errors;
+ int m_unhandled_errors;
+};
+
+
+#endif /* SQL_BASE_INCLUDED */
diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc
new file mode 100644
index 00000000000..f0465cdf5bf
--- /dev/null
+++ b/sql/sql_binlog.cc
@@ -0,0 +1,280 @@
+/*
+ Copyright (c) 2005, 2013, Oracle and/or its affiliates.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "sql_binlog.h"
+#include "sql_parse.h" // check_global_access
+#include "sql_acl.h" // *_ACL
+#include "rpl_rli.h"
+#include "base64.h"
+#include "slave.h" // apply_event_and_update_pos
+#include "log_event.h" // Format_description_log_event,
+ // EVENT_LEN_OFFSET,
+ // EVENT_TYPE_OFFSET,
+ // FORMAT_DESCRIPTION_LOG_EVENT,
+ // START_EVENT_V3,
+ // Log_event_type,
+ // Log_event
+/**
+ Execute a BINLOG statement.
+
+ To execute the BINLOG command properly the server needs to know
+ which format the BINLOG command's event is in. Therefore, the first
+ BINLOG statement seen must be a base64 encoding of the
+ Format_description_log_event, as outputted by mysqlbinlog. This
+ Format_description_log_event is cached in
+ rli->description_event_for_exec.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+*/
+
+void mysql_client_binlog_statement(THD* thd)
+{
+ DBUG_ENTER("mysql_client_binlog_statement");
+ DBUG_PRINT("info",("binlog base64: '%*s'",
+ (int) (thd->lex->comment.length < 2048 ?
+ thd->lex->comment.length : 2048),
+ thd->lex->comment.str));
+
+ if (check_global_access(thd, SUPER_ACL))
+ DBUG_VOID_RETURN;
+
+ size_t coded_len= thd->lex->comment.length;
+ if (!coded_len)
+ {
+ my_error(ER_SYNTAX_ERROR, MYF(0));
+ DBUG_VOID_RETURN;
+ }
+ size_t decoded_len= base64_needed_decoded_length(coded_len);
+
+ /*
+ option_bits will be changed when applying the event. But we don't expect
+ it be changed permanently after BINLOG statement, so backup it first.
+ It will be restored at the end of this function.
+ */
+ ulonglong thd_options= thd->variables.option_bits;
+
+ /*
+ Allocation
+ */
+
+ /*
+ If we do not have a Format_description_event, we create a dummy
+ one here. In this case, the first event we read must be a
+ Format_description_event.
+ */
+ my_bool have_fd_event= TRUE;
+ int err;
+ Relay_log_info *rli;
+ rpl_group_info *rgi;
+
+ rli= thd->rli_fake;
+ if (!rli)
+ {
+ rli= thd->rli_fake= new Relay_log_info(FALSE);
+#ifdef HAVE_valgrind
+ rli->is_fake= TRUE;
+#endif
+ have_fd_event= FALSE;
+ }
+ if (rli && !rli->relay_log.description_event_for_exec)
+ {
+ rli->relay_log.description_event_for_exec=
+ new Format_description_log_event(4);
+ have_fd_event= FALSE;
+ }
+ if (!(rgi= thd->rgi_fake))
+ rgi= thd->rgi_fake= new rpl_group_info(rli);
+ rgi->thd= thd;
+
+ const char *error= 0;
+ char *buf= (char *) my_malloc(decoded_len, MYF(MY_WME));
+ Log_event *ev = 0;
+
+ /*
+ Out of memory check
+ */
+ if (!(rli &&
+ rli->relay_log.description_event_for_exec &&
+ buf))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 1); /* needed 1 bytes */
+ goto end;
+ }
+
+ rli->sql_driver_thd= thd;
+ rli->no_storage= TRUE;
+
+ for (char const *strptr= thd->lex->comment.str ;
+ strptr < thd->lex->comment.str + thd->lex->comment.length ; )
+ {
+ char const *endptr= 0;
+ int bytes_decoded= base64_decode(strptr, coded_len, buf, &endptr,
+ MY_BASE64_DECODE_ALLOW_MULTIPLE_CHUNKS);
+
+#ifndef HAVE_valgrind
+ /*
+ This debug printout should not be used for valgrind builds
+ since it will read from unassigned memory.
+ */
+ DBUG_PRINT("info",
+ ("bytes_decoded: %d strptr: 0x%lx endptr: 0x%lx ('%c':%d)",
+ bytes_decoded, (long) strptr, (long) endptr, *endptr,
+ *endptr));
+#endif
+
+ if (bytes_decoded < 0)
+ {
+ my_error(ER_BASE64_DECODE_ERROR, MYF(0));
+ goto end;
+ }
+ else if (bytes_decoded == 0)
+ break; // If no bytes where read, the string contained only whitespace
+
+ DBUG_ASSERT(bytes_decoded > 0);
+ DBUG_ASSERT(endptr > strptr);
+ coded_len-= endptr - strptr;
+ strptr= endptr;
+
+ /*
+ Now we have one or more events stored in the buffer. The size of
+ the buffer is computed based on how much base64-encoded data
+ there were, so there should be ample space for the data (maybe
+ even too much, since a statement can consist of a considerable
+ number of events).
+
+ TODO: Switch to use a stream-based base64 encoder/decoder in
+ order to be able to read exactly what is necessary.
+ */
+
+ DBUG_PRINT("info",("binlog base64 decoded_len: %lu bytes_decoded: %d",
+ (ulong) decoded_len, bytes_decoded));
+
+ /*
+ Now we start to read events of the buffer, until there are no
+ more.
+ */
+ for (char *bufptr= buf ; bytes_decoded > 0 ; )
+ {
+ /*
+ Checking that the first event in the buffer is not truncated.
+ */
+ ulong event_len;
+ if (bytes_decoded < EVENT_LEN_OFFSET + 4 ||
+ (event_len= uint4korr(bufptr + EVENT_LEN_OFFSET)) >
+ (uint) bytes_decoded)
+ {
+ my_error(ER_SYNTAX_ERROR, MYF(0));
+ goto end;
+ }
+ DBUG_PRINT("info", ("event_len=%lu, bytes_decoded=%d",
+ event_len, bytes_decoded));
+
+ /*
+ If we have not seen any Format_description_event, then we must
+ see one; it is the only statement that can be read in base64
+ without a prior Format_description_event.
+ */
+ if (!have_fd_event)
+ {
+ int type = (uchar)bufptr[EVENT_TYPE_OFFSET];
+ if (type == FORMAT_DESCRIPTION_EVENT || type == START_EVENT_V3)
+ have_fd_event= TRUE;
+ else
+ {
+ my_error(ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT,
+ MYF(0), Log_event::get_type_str((Log_event_type)type));
+ goto end;
+ }
+ }
+
+ ev= Log_event::read_log_event(bufptr, event_len, &error,
+ rli->relay_log.description_event_for_exec,
+ 0);
+
+ DBUG_PRINT("info",("binlog base64 err=%s", error));
+ if (!ev)
+ {
+ /*
+ This could actually be an out-of-memory, but it is more likely
+ causes by a bad statement
+ */
+ my_error(ER_SYNTAX_ERROR, MYF(0));
+ goto end;
+ }
+
+ bytes_decoded -= event_len;
+ bufptr += event_len;
+
+ DBUG_PRINT("info",("ev->get_type_code()=%d", ev->get_type_code()));
+ ev->thd= thd;
+ /*
+ We go directly to the application phase, since we don't need
+ to check if the event shall be skipped or not.
+
+ Neither do we have to update the log positions, since that is
+ not used at all: the rli_fake instance is used only for error
+ reporting.
+ */
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ ulonglong save_skip_replication=
+ thd->variables.option_bits & OPTION_SKIP_REPLICATION;
+ thd->variables.option_bits=
+ (thd->variables.option_bits & ~OPTION_SKIP_REPLICATION) |
+ (ev->flags & LOG_EVENT_SKIP_REPLICATION_F ?
+ OPTION_SKIP_REPLICATION : 0);
+
+ err= ev->apply_event(rgi);
+
+ thd->variables.option_bits=
+ (thd->variables.option_bits & ~OPTION_SKIP_REPLICATION) |
+ save_skip_replication;
+#else
+ err= 0;
+#endif
+ /*
+ Format_description_log_event should not be deleted because it
+ will be used to read info about the relay log's format; it
+ will be deleted when the SQL thread does not need it,
+ i.e. when this thread terminates.
+ */
+ if (ev->get_type_code() != FORMAT_DESCRIPTION_EVENT)
+ delete ev;
+ ev= 0;
+ if (err)
+ {
+ /*
+ TODO: Maybe a better error message since the BINLOG statement
+ now contains several events.
+ */
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ goto end;
+ }
+ }
+ }
+
+
+ DBUG_PRINT("info",("binlog base64 execution finished successfully"));
+ my_ok(thd);
+
+end:
+ thd->variables.option_bits= thd_options;
+ rgi->slave_close_thread_tables(thd);
+ my_free(buf);
+ DBUG_VOID_RETURN;
+}
diff --git a/sql/sql_binlog.h b/sql/sql_binlog.h
new file mode 100644
index 00000000000..3a6d561701a
--- /dev/null
+++ b/sql/sql_binlog.h
@@ -0,0 +1,23 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_BINLOG_INCLUDED
+#define SQL_BINLOG_INCLUDED
+
+class THD;
+
+void mysql_client_binlog_statement(THD *thd);
+
+#endif /* SQL_BINLOG_INCLUDED */
diff --git a/sql/sql_bitmap.h b/sql/sql_bitmap.h
new file mode 100644
index 00000000000..55b2d7eefd9
--- /dev/null
+++ b/sql/sql_bitmap.h
@@ -0,0 +1,187 @@
+/* Copyright (c) 2003, 2013, Oracle and/or its affiliates
+ Copyright (c) 2009, 2013, Monty Program Ab.
+
+ 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 */
+
+/*
+ Implementation of a bitmap type.
+ The idea with this is to be able to handle any constant number of bits but
+ also be able to use 32 or 64 bits bitmaps very efficiently
+*/
+
+#ifndef SQL_BITMAP_INCLUDED
+#define SQL_BITMAP_INCLUDED
+
+#include <my_sys.h>
+#include <my_bitmap.h>
+
+template <uint default_width> class Bitmap
+{
+ MY_BITMAP map;
+ uint32 buffer[(default_width+31)/32];
+public:
+ Bitmap() { init(); }
+ Bitmap(const Bitmap& from) { *this=from; }
+ explicit Bitmap(uint prefix_to_set) { init(prefix_to_set); }
+ void init() { my_bitmap_init(&map, buffer, default_width, 0); }
+ void init(uint prefix_to_set) { init(); set_prefix(prefix_to_set); }
+ uint length() const { return default_width; }
+ Bitmap& operator=(const Bitmap& map2)
+ {
+ init();
+ memcpy(buffer, map2.buffer, sizeof(buffer));
+ return *this;
+ }
+ void set_bit(uint n) { bitmap_set_bit(&map, n); }
+ void clear_bit(uint n) { bitmap_clear_bit(&map, n); }
+ void set_prefix(uint n) { bitmap_set_prefix(&map, n); }
+ void set_all() { bitmap_set_all(&map); }
+ void clear_all() { bitmap_clear_all(&map); }
+ void intersect(Bitmap& map2) { bitmap_intersect(&map, &map2.map); }
+ void intersect(ulonglong map2buff)
+ {
+ MY_BITMAP map2;
+ my_bitmap_init(&map2, (uint32 *)&map2buff, sizeof(ulonglong)*8, 0);
+ bitmap_intersect(&map, &map2);
+ }
+ /* Use highest bit for all bits above sizeof(ulonglong)*8. */
+ void intersect_extended(ulonglong map2buff)
+ {
+ intersect(map2buff);
+ if (map.n_bits > sizeof(ulonglong) * 8)
+ bitmap_set_above(&map, sizeof(ulonglong),
+ MY_TEST(map2buff & (1LL << (sizeof(ulonglong) * 8 - 1))));
+ }
+ void subtract(Bitmap& map2) { bitmap_subtract(&map, &map2.map); }
+ void merge(Bitmap& map2) { bitmap_union(&map, &map2.map); }
+ bool is_set(uint n) const { return bitmap_is_set(&map, n); }
+ bool is_prefix(uint n) const { return bitmap_is_prefix(&map, n); }
+ bool is_clear_all() const { return bitmap_is_clear_all(&map); }
+ bool is_set_all() const { return bitmap_is_set_all(&map); }
+ bool is_subset(const Bitmap& map2) const { return bitmap_is_subset(&map, &map2.map); }
+ bool is_overlapping(const Bitmap& map2) const { return bitmap_is_overlapping(&map, &map2.map); }
+ bool operator==(const Bitmap& map2) const { return bitmap_cmp(&map, &map2.map); }
+ bool operator!=(const Bitmap& map2) const { return !(*this == map2); }
+ char *print(char *buf) const
+ {
+ char *s=buf;
+ const uchar *e=(uchar *)buffer, *b=e+sizeof(buffer)-1;
+ while (!*b && b>e)
+ b--;
+ if ((*s=_dig_vec_upper[*b >> 4]) != '0')
+ s++;
+ *s++=_dig_vec_upper[*b & 15];
+ while (--b>=e)
+ {
+ *s++=_dig_vec_upper[*b >> 4];
+ *s++=_dig_vec_upper[*b & 15];
+ }
+ *s=0;
+ return buf;
+ }
+ ulonglong to_ulonglong() const
+ {
+ if (sizeof(buffer) >= 8)
+ return uint8korr(buffer);
+ DBUG_ASSERT(sizeof(buffer) >= 4);
+ return (ulonglong) uint4korr(buffer);
+ }
+ uint bits_set()
+ {
+ return bitmap_bits_set(&map);
+ }
+};
+
+/* An iterator to quickly walk over bits in unlonglong bitmap. */
+class Table_map_iterator
+{
+ ulonglong bmp;
+ uint no;
+public:
+ Table_map_iterator(ulonglong t) : bmp(t), no(0) {}
+ int next_bit()
+ {
+ static const char last_bit[16]= {32, 0, 1, 0,
+ 2, 0, 1, 0,
+ 3, 0, 1, 0,
+ 2, 0, 1, 0};
+ uint bit;
+ while ((bit= last_bit[bmp & 0xF]) == 32)
+ {
+ no += 4;
+ bmp= bmp >> 4;
+ if (!bmp)
+ return BITMAP_END;
+ }
+ bmp &= ~(1LL << bit);
+ return no + bit;
+ }
+ int operator++(int) { return next_bit(); }
+ enum { BITMAP_END= 64 };
+};
+
+template <> class Bitmap<64>
+{
+ ulonglong map;
+public:
+ Bitmap<64>() { }
+ explicit Bitmap<64>(uint prefix_to_set) { set_prefix(prefix_to_set); }
+ void init() { }
+ void init(uint prefix_to_set) { set_prefix(prefix_to_set); }
+ uint length() const { return 64; }
+ void set_bit(uint n) { map|= ((ulonglong)1) << n; }
+ void clear_bit(uint n) { map&= ~(((ulonglong)1) << n); }
+ void set_prefix(uint n)
+ {
+ if (n >= length())
+ set_all();
+ else
+ map= (((ulonglong)1) << n)-1;
+ }
+ void set_all() { map=~(ulonglong)0; }
+ void clear_all() { map=(ulonglong)0; }
+ void intersect(Bitmap<64>& map2) { map&= map2.map; }
+ void intersect(ulonglong map2) { map&= map2; }
+ void intersect_extended(ulonglong map2) { map&= map2; }
+ void subtract(Bitmap<64>& map2) { map&= ~map2.map; }
+ void merge(Bitmap<64>& map2) { map|= map2.map; }
+ bool is_set(uint n) const { return MY_TEST(map & (((ulonglong) 1) << n)); }
+ bool is_prefix(uint n) const { return map == (((ulonglong)1) << n)-1; }
+ bool is_clear_all() const { return map == (ulonglong)0; }
+ bool is_set_all() const { return map == ~(ulonglong)0; }
+ bool is_subset(const Bitmap<64>& map2) const { return !(map & ~map2.map); }
+ bool is_overlapping(const Bitmap<64>& map2) const { return (map & map2.map)!= 0; }
+ bool operator==(const Bitmap<64>& map2) const { return map == map2.map; }
+ char *print(char *buf) const { longlong2str(map,buf,16); return buf; }
+ ulonglong to_ulonglong() const { return map; }
+ class Iterator : public Table_map_iterator
+ {
+ public:
+ Iterator(Bitmap<64> &bmp) : Table_map_iterator(bmp.map) {}
+ };
+ uint bits_set()
+ {
+ //TODO: use my_count_bits()
+ uint res= 0, i= 0;
+ for (; i < 64 ; i++)
+ {
+ if (map & ((ulonglong)1<<i))
+ res++;
+ }
+ return res;
+ }
+};
+
+
+#endif /* SQL_BITMAP_INCLUDED */
diff --git a/sql/sql_bootstrap.cc b/sql/sql_bootstrap.cc
new file mode 100644
index 00000000000..30d03029ce6
--- /dev/null
+++ b/sql/sql_bootstrap.cc
@@ -0,0 +1,119 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+
+#include <my_global.h>
+#include <ctype.h>
+#include <string.h>
+#include "sql_bootstrap.h"
+
+int read_bootstrap_query(char *query, int *query_length,
+ fgets_input_t input, fgets_fn_t fgets_fn, int *error)
+{
+ char line_buffer[MAX_BOOTSTRAP_LINE_SIZE];
+ const char *line;
+ int len;
+ int query_len= 0;
+ int fgets_error= 0;
+ *error= 0;
+
+ for ( ; ; )
+ {
+ line= (*fgets_fn)(line_buffer, sizeof(line_buffer), input, &fgets_error);
+
+ if (error)
+ *error= fgets_error;
+
+ if (fgets_error != 0)
+ return READ_BOOTSTRAP_ERROR;
+
+ if (line == NULL)
+ return (query_len == 0) ? READ_BOOTSTRAP_EOF : READ_BOOTSTRAP_ERROR;
+
+ len= strlen(line);
+
+ /*
+ Remove trailing whitespace characters.
+ This assumes:
+ - no multibyte encoded character can be found at the very end of a line,
+ - whitespace characters from the "C" locale only.
+ which is sufficient for the kind of queries found
+ in the bootstrap scripts.
+ */
+ while (len && (isspace(line[len - 1])))
+ len--;
+ /*
+ Cleanly end the string, so we don't have to test len > x
+ all the time before reading line[x], in the code below.
+ */
+ line_buffer[len]= '\0';
+
+ /* Skip blank lines */
+ if (len == 0)
+ continue;
+
+ /* Skip # comments */
+ if (line[0] == '#')
+ continue;
+
+ /* Skip -- comments */
+ if ((line[0] == '-') && (line[1] == '-'))
+ continue;
+
+ /* Skip delimiter, ignored. */
+ if (strncmp(line, "delimiter", 9) == 0)
+ continue;
+
+ /* Append the current line to a multi line query. If the new line will make
+ the query too long, preserve the partial line to provide context for the
+ error message.
+ */
+ if (query_len + len + 1 >= MAX_BOOTSTRAP_QUERY_SIZE)
+ {
+ int new_len= MAX_BOOTSTRAP_QUERY_SIZE - query_len - 1;
+ if ((new_len > 0) && (query_len < MAX_BOOTSTRAP_QUERY_SIZE))
+ {
+ memcpy(query + query_len, line, new_len);
+ query_len+= new_len;
+ }
+ query[query_len]= '\0';
+ *query_length= query_len;
+ return READ_BOOTSTRAP_QUERY_SIZE;
+ }
+
+ if (query_len != 0)
+ {
+ /*
+ Append a \n to the current line, if any,
+ to preserve the intended presentation.
+ */
+ query[query_len++]= '\n';
+ }
+ memcpy(query + query_len, line, len);
+ query_len+= len;
+
+ if (line[len - 1] == ';')
+ {
+ /*
+ The last line is terminated by ';'.
+ Return the query found.
+ */
+ query[query_len]= '\0';
+ *query_length= query_len;
+ return READ_BOOTSTRAP_SUCCESS;
+ }
+ }
+}
+
diff --git a/sql/sql_bootstrap.h b/sql/sql_bootstrap.h
new file mode 100644
index 00000000000..b8a302a8646
--- /dev/null
+++ b/sql/sql_bootstrap.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+
+#ifndef SQL_BOOTSTRAP_H
+#define SQL_BOOTSTRAP_H
+
+/**
+ The maximum size of a bootstrap query.
+ Increase this size if parsing a longer query during bootstrap is necessary.
+ The longest query in use depends on the documentation content,
+ see the file fill_help_tables.sql
+*/
+#define MAX_BOOTSTRAP_QUERY_SIZE 20000
+/**
+ The maximum size of a bootstrap query, expressed in a single line.
+ Do not increase this size, use the multiline syntax instead.
+*/
+#define MAX_BOOTSTRAP_LINE_SIZE 20000
+#define MAX_BOOTSTRAP_ERROR_LEN 256
+
+#define READ_BOOTSTRAP_SUCCESS 0
+#define READ_BOOTSTRAP_EOF 1
+#define READ_BOOTSTRAP_ERROR 2
+#define READ_BOOTSTRAP_QUERY_SIZE 3
+
+typedef void *fgets_input_t;
+typedef char * (*fgets_fn_t)(char *, size_t, fgets_input_t, int *error);
+
+int read_bootstrap_query(char *query, int *query_length,
+ fgets_input_t input, fgets_fn_t fgets_fn, int *error);
+
+#endif
+
+
diff --git a/sql/sql_builtin.cc.in b/sql/sql_builtin.cc.in
new file mode 100644
index 00000000000..63850650ac9
--- /dev/null
+++ b/sql/sql_builtin.cc.in
@@ -0,0 +1,39 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include <mysql/plugin.h>
+
+typedef struct st_maria_plugin builtin_maria_plugin[];
+
+#ifdef _MSC_VER
+extern "C"
+#else
+extern
+#endif
+builtin_maria_plugin
+ @mysql_mandatory_plugins@ @mysql_optional_plugins@
+ builtin_maria_binlog_plugin, builtin_maria_mysql_password_plugin;
+
+struct st_maria_plugin *mysql_optional_plugins[]=
+{
+ @mysql_optional_plugins@ 0
+};
+
+struct st_maria_plugin *mysql_mandatory_plugins[]=
+{
+ builtin_maria_binlog_plugin, builtin_maria_mysql_password_plugin,
+ @mysql_mandatory_plugins@ 0
+};
diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc
new file mode 100644
index 00000000000..e1efb1e85d6
--- /dev/null
+++ b/sql/sql_cache.cc
@@ -0,0 +1,5246 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2013, Monty Program Ab
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+/*
+ Description of the query cache:
+
+1. Query_cache object consists of
+ - query cache memory pool (cache)
+ - queries hash (queries)
+ - tables hash (tables)
+ - list of blocks ordered as they allocated in memory
+(first_block)
+ - list of queries block (queries_blocks)
+ - list of used tables (tables_blocks)
+
+2. Query cache memory pool (cache) consists of
+ - table of steps of memory bins allocation
+ - table of free memory bins
+ - blocks of memory
+
+3. Memory blocks
+
+Every memory block has the following structure:
+
++----------------------------------------------------------+
+| Block header (Query_cache_block structure) |
++----------------------------------------------------------+
+|Table of database table lists (used for queries & tables) |
++----------------------------------------------------------+
+| Type depended header |
+|(Query_cache_query, Query_cache_table, Query_cache_result)|
++----------------------------------------------------------+
+| Data ... |
++----------------------------------------------------------+
+
+Block header consists of:
+- type:
+ FREE Free memory block
+ QUERY Query block
+ RESULT Ready to send result
+ RES_CONT Result's continuation
+ RES_BEG First block of results, that is not yet complete,
+ written to cache
+ RES_INCOMPLETE Allocated for results data block
+ TABLE Block with database table description
+ INCOMPLETE The destroyed block
+- length of block (length)
+- length of data & headers (used)
+- physical list links (pnext/pprev) - used for the list of
+ blocks ordered as they are allocated in physical memory
+- logical list links (next/prev) - used for queries block list, tables block
+ list, free memory block lists and list of results block in query
+- number of elements in table of database table list (n_tables)
+
+4. Query & results blocks
+
+Query stored in cache consists of following blocks:
+
+more more
+recent+-------------+ old
+<-----|Query block 1|------> double linked list of queries block
+ prev | | next
+ +-------------+
+ <-| table 0 |-> (see "Table of database table lists" description)
+ <-| table 1 |->
+ | ... | +--------------------------+
+ +-------------+ +-------------------------+ |
+NET | | | V V |
+struct| | +-+------------+ +------------+ |
+<-----|query header |----->|Result block|-->|Result block|-+ doublelinked
+writer| |result| |<--| | list of results
+ +-------------+ +------------+ +------------+
+ |charset | +------------+ +------------+ no table of dbtables
+ |encoding + | | result | | result |
+ |query text |<-----| header | | header |------+
+ +-------------+parent| | | |parent|
+ ^ +------------+ +------------+ |
+ | |result data | |result data | |
+ | +------------+ +------------+ |
+ +---------------------------------------------------+
+
+First query is registered. During the registration query block is
+allocated. This query block is included in query hash and is linked
+with appropriate database tables lists (if there is no appropriate
+list exists it will be created).
+
+Later when query has performed results is written into the result blocks.
+A result block cannot be smaller then QUERY_CACHE_MIN_RESULT_DATA_SIZE.
+
+When new result is written to cache it is appended to the last result
+block, if no more free space left in the last block, new block is
+allocated.
+
+5. Table of database table lists.
+
+For quick invalidation of queries all query are linked in lists on used
+database tables basis (when table will be changed (insert/delete/...)
+this queries will be removed from cache).
+
+Root of such list is table block:
+
+ +------------+ list of used tables (used while invalidation of
+<----| Table |-----> whole database)
+ prev| block |next +-----------+
+ | | +-----------+ |Query block|
+ | | |Query block| +-----------+
+ +------------+ +-----------+ | ... |
+ +->| table 0 |------>|table 0 |----->| table N |---+
+ |+-| |<------| |<-----| |<-+|
+ || +------------+ | ... | | ... | ||
+ || |table header| +-----------+ +-----------+ ||
+ || +------------+ | ... | | ... | ||
+ || |db name + | +-----------+ +-----------+ ||
+ || |table name | ||
+ || +------------+ ||
+ |+--------------------------------------------------------+|
+ +----------------------------------------------------------+
+
+Table block is included into the tables hash (tables).
+
+6. Free blocks, free blocks bins & steps of freeblock bins.
+
+When we just started only one free memory block existed. All query
+cache memory (that will be used for block allocation) were
+containing in this block.
+When a new block is allocated we find most suitable memory block
+(minimal of >= required size). If such a block can not be found, we try
+to find max block < required size (if we allocate block for results).
+If there is no free memory, oldest query is removed from cache, and then
+we try to allocate memory. Last step should be repeated until we find
+suitable block or until there is no unlocked query found.
+
+If the block is found and its length more then we need, it should be
+split into 2 blocks.
+New blocks cannot be smaller then min_allocation_unit_bytes.
+
+When a block becomes free, its neighbor-blocks should be tested and if
+there are free blocks among them, they should be joined into one block.
+
+Free memory blocks are stored in bins according to their sizes.
+The bins are stored in size-descending order.
+These bins are distributed (by size) approximately logarithmically.
+
+First bin (number 0) stores free blocks with
+size <= query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2.
+It is first (number 0) step.
+On the next step distributed (1 + QUERY_CACHE_MEM_BIN_PARTS_INC) *
+QUERY_CACHE_MEM_BIN_PARTS_MUL bins. This bins allocated in interval from
+query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 to
+query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 >>
+QUERY_CACHE_MEM_BIN_STEP_PWR2
+...
+On each step interval decreases in 2 power of
+QUERY_CACHE_MEM_BIN_STEP_PWR2
+times, number of bins (that distributed on this step) increases. If on
+the previous step there were N bins distributed , on the current there
+would be distributed
+(N + QUERY_CACHE_MEM_BIN_PARTS_INC) * QUERY_CACHE_MEM_BIN_PARTS_MUL
+bins.
+Last distributed bin stores blocks with size near min_allocation_unit
+bytes.
+
+For example:
+ query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 = 100,
+ min_allocation_unit = 17,
+ QUERY_CACHE_MEM_BIN_STEP_PWR2 = 1,
+ QUERY_CACHE_MEM_BIN_PARTS_INC = 1,
+ QUERY_CACHE_MEM_BIN_PARTS_MUL = 1
+ (in followed picture showed right (low) bound of bin):
+
+ | 100>>1 50>>1 |25>>1|
+ | | | | | |
+ | 100 75 50 41 33 25 21 18 15| 12 | - bins right (low) bounds
+
+ |\---/\-----/\--------/\--------|---/ |
+ | 0 1 2 3 | | - steps
+ \-----------------------------/ \---/
+ bins that we store in cache this bin showed for example only
+
+
+Calculation of steps/bins distribution is performed only when query cache
+is resized.
+
+When we need to find appropriate bin, first we should find appropriate
+step, then we should calculate number of bins that are using data
+stored in Query_cache_memory_bin_step structure.
+
+Free memory blocks are sorted in bins in lists with size-ascending order
+(more small blocks needed frequently then bigger one).
+
+7. Packing cache.
+
+Query cache packing is divided into two operation:
+ - pack_cache
+ - join_results
+
+pack_cache moved all blocks to "top" of cache and create one block of free
+space at the "bottom":
+
+ before pack_cache after pack_cache
+ +-------------+ +-------------+
+ | query 1 | | query 1 |
+ +-------------+ +-------------+
+ | table 1 | | table 1 |
+ +-------------+ +-------------+
+ | results 1.1 | | results 1.1 |
+ +-------------+ +-------------+
+ | free | | query 2 |
+ +-------------+ +-------------+
+ | query 2 | | table 2 |
+ +-------------+ ---> +-------------+
+ | table 2 | | results 1.2 |
+ +-------------+ +-------------+
+ | results 1.2 | | results 2 |
+ +-------------+ +-------------+
+ | free | | free |
+ +-------------+ | |
+ | results 2 | | |
+ +-------------+ | |
+ | free | | |
+ +-------------+ +-------------+
+
+pack_cache scan blocks in physical address order and move every non-free
+block "higher".
+
+pack_cach remove every free block it finds. The length of the deleted block
+is accumulated to the "gap". All non free blocks should be shifted with the
+"gap" step.
+
+join_results scans all complete queries. If the results of query are not
+stored in the same block, join_results tries to move results so, that they
+are stored in one block.
+
+ before join_results after join_results
+ +-------------+ +-------------+
+ | query 1 | | query 1 |
+ +-------------+ +-------------+
+ | table 1 | | table 1 |
+ +-------------+ +-------------+
+ | results 1.1 | | free |
+ +-------------+ +-------------+
+ | query 2 | | query 2 |
+ +-------------+ +-------------+
+ | table 2 | | table 2 |
+ +-------------+ ---> +-------------+
+ | results 1.2 | | free |
+ +-------------+ +-------------+
+ | results 2 | | results 2 |
+ +-------------+ +-------------+
+ | free | | results 1 |
+ | | | |
+ | | +-------------+
+ | | | free |
+ | | | |
+ +-------------+ +-------------+
+
+If join_results allocated new block(s) then we need call pack_cache again.
+
+7. Interface
+The query cache interfaces with the rest of the server code through 7
+functions:
+ 1. Query_cache::send_result_to_client
+ - Called before parsing and used to match a statement with the stored
+ queries hash.
+ If a match is found the cached result set is sent through repeated
+ calls to net_real_write. (note: calling thread doesn't have a regis-
+ tered result set writer: thd->net.query_cache_query=0)
+ 2. Query_cache::store_query
+ - Called just before handle_select() and is used to register a result
+ set writer to the statement currently being processed
+ (thd->net.query_cache_query).
+ 3. query_cache_insert
+ - Called from net_real_write to append a result set to a cached query
+ if (and only if) this query has a registered result set writer
+ (thd->net.query_cache_query).
+ 4. Query_cache::invalidate
+ Query_cache::invalidate_locked_for_write
+ - Called from various places to invalidate query cache based on data-
+ base, table and myisam file name. During an on going invalidation
+ the query cache is temporarily disabled.
+ 5. Query_cache::flush
+ - Used when a RESET QUERY CACHE is issued. This clears the entire
+ cache block by block.
+ 6. Query_cache::resize
+ - Used to change the available memory used by the query cache. This
+ will also invalidate the entrie query cache in one free operation.
+ 7. Query_cache::pack
+ - Used when a FLUSH QUERY CACHE is issued. This changes the order of
+ the used memory blocks in physical memory order and move all avail-
+ able memory to the 'bottom' of the memory.
+
+
+TODO list:
+
+ - Delayed till after-parsing qache answer (for column rights processing)
+ - Optimize cache resizing
+ - if new_size < old_size then pack & shrink
+ - if new_size > old_size copy cached query to new cache
+ - Move MRG_MYISAM table type processing to handlers, something like:
+ tables_used->table->file->register_used_filenames(callback,
+ first_argument);
+ - QC improvement suggested by Monty:
+ - Add a counter in open_table() for how many MERGE (ISAM or MyISAM)
+ tables are cached in the table cache.
+ (This will be trivial when we have the new table cache in place I
+ have been working on)
+ - After this we can add the following test around the for loop in
+ is_cacheable::
+
+ if (thd->temp_tables || global_merge_table_count)
+
+ - Another option would be to set thd->lex->safe_to_cache_query to 0
+ in 'get_lock_data' if any of the tables was a tmp table or a
+ MRG_ISAM table.
+ (This could be done with almost no speed penalty)
+*/
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "sql_cache.h"
+#include "sql_parse.h" // check_table_access
+#include "tztime.h" // struct Time_zone
+#include "sql_acl.h" // SELECT_ACL
+#include "sql_base.h" // TMP_TABLE_KEY_EXTRA
+#include "debug_sync.h" // DEBUG_SYNC
+#include "sql_table.h"
+#ifdef HAVE_QUERY_CACHE
+#include <m_ctype.h>
+#include <my_dir.h>
+#include <hash.h>
+#include "../storage/myisammrg/ha_myisammrg.h"
+#include "../storage/myisammrg/myrg_def.h"
+#include "probes_mysql.h"
+#include "log_slow.h"
+#include "transaction.h"
+#include "strfunc.h"
+
+const uchar *query_state_map;
+
+#ifdef EMBEDDED_LIBRARY
+#include "emb_qcache.h"
+#endif
+
+#if !defined(EXTRA_DBUG) && !defined(DBUG_OFF)
+#define RW_WLOCK(M) {DBUG_PRINT("lock", ("rwlock wlock 0x%lx",(ulong)(M))); \
+ if (!mysql_rwlock_wrlock(M)) DBUG_PRINT("lock", ("rwlock wlock ok")); \
+ else DBUG_PRINT("lock", ("rwlock wlock FAILED %d", errno)); }
+#define RW_RLOCK(M) {DBUG_PRINT("lock", ("rwlock rlock 0x%lx", (ulong)(M))); \
+ if (!mysql_rwlock_rdlock(M)) DBUG_PRINT("lock", ("rwlock rlock ok")); \
+ else DBUG_PRINT("lock", ("rwlock wlock FAILED %d", errno)); }
+#define RW_UNLOCK(M) {DBUG_PRINT("lock", ("rwlock unlock 0x%lx",(ulong)(M))); \
+ if (!mysql_rwlock_unlock(M)) DBUG_PRINT("lock", ("rwlock unlock ok")); \
+ else DBUG_PRINT("lock", ("rwlock unlock FAILED %d", errno)); }
+#define BLOCK_LOCK_WR(B) {DBUG_PRINT("lock", ("%d LOCK_WR 0x%lx",\
+ __LINE__,(ulong)(B))); \
+ B->query()->lock_writing();}
+#define BLOCK_LOCK_RD(B) {DBUG_PRINT("lock", ("%d LOCK_RD 0x%lx",\
+ __LINE__,(ulong)(B))); \
+ B->query()->lock_reading();}
+#define BLOCK_UNLOCK_WR(B) { \
+ DBUG_PRINT("lock", ("%d UNLOCK_WR 0x%lx",\
+ __LINE__,(ulong)(B)));B->query()->unlock_writing();}
+#define BLOCK_UNLOCK_RD(B) { \
+ DBUG_PRINT("lock", ("%d UNLOCK_RD 0x%lx",\
+ __LINE__,(ulong)(B)));B->query()->unlock_reading();}
+#define DUMP(C) DBUG_EXECUTE("qcache", {\
+ (C)->cache_dump(); (C)->queries_dump();(C)->tables_dump();})
+#else
+#define RW_WLOCK(M) mysql_rwlock_wrlock(M)
+#define RW_RLOCK(M) mysql_rwlock_rdlock(M)
+#define RW_UNLOCK(M) mysql_rwlock_unlock(M)
+#define BLOCK_LOCK_WR(B) B->query()->lock_writing()
+#define BLOCK_LOCK_RD(B) B->query()->lock_reading()
+#define BLOCK_UNLOCK_WR(B) B->query()->unlock_writing()
+#define BLOCK_UNLOCK_RD(B) B->query()->unlock_reading()
+#define DUMP(C)
+#endif
+
+
+/**
+ Macro that executes the requested action at a synchronization point
+ only if the thread has a associated THD session.
+*/
+#if defined(ENABLED_DEBUG_SYNC)
+#define QC_DEBUG_SYNC(name) \
+ do { \
+ THD *thd= current_thd; \
+ if (thd) \
+ DEBUG_SYNC(thd, name); \
+ } while (0)
+#else
+#define QC_DEBUG_SYNC(name)
+#endif
+
+
+/**
+ Thread state to be used when the query cache lock needs to be acquired.
+ Sets the thread state name in the constructor, resets on destructor.
+*/
+
+struct Query_cache_wait_state
+{
+ THD *m_thd;
+ PSI_stage_info m_old_stage;
+ const char *m_func;
+ const char *m_file;
+ int m_line;
+
+ Query_cache_wait_state(THD *thd, const char *func,
+ const char *file, unsigned int line)
+ : m_thd(thd),
+ m_old_stage(),
+ m_func(func), m_file(file), m_line(line)
+ {
+ if (m_thd)
+ set_thd_stage_info(m_thd,
+ &stage_waiting_for_query_cache_lock,
+ &m_old_stage,
+ m_func, m_file, m_line);
+ }
+
+ ~Query_cache_wait_state()
+ {
+ if (m_thd)
+ set_thd_stage_info(m_thd, &m_old_stage, NULL, m_func, m_file, m_line);
+ }
+};
+
+
+/*
+ Check if character is a white space.
+*/
+
+inline bool is_white_space(char c)
+{
+ return (query_state_map[(uint) ((uchar) c)] == MY_LEX_SKIP);
+}
+
+
+/**
+ Generate a query_string without query comments or duplicated space
+
+ @param new_query New query without 'fluff' is stored here
+ @param query Original query
+ @param query_length Length of original query
+ @param additional_length Extra space for query cache we need to allocate
+ in new_query buffer.
+
+ Note:
+ If there is no space to allocate new_query, we will put original query
+ into new_query.
+*/
+
+static void make_base_query(String *new_query,
+ const char *query, size_t query_length,
+ size_t additional_length)
+{
+ char *buffer;
+ const char *query_end, *last_space;
+
+ /* The following is guaranteed by the query_cache interface */
+ DBUG_ASSERT(query[query_length] == 0);
+ DBUG_ASSERT(!is_white_space(query[0]));
+ /* We do not support UCS2, UTF16, UTF32 as a client character set */
+ DBUG_ASSERT(current_thd->variables.character_set_client->mbminlen == 1);
+
+ new_query->length(0); // Don't copy anything from old buffer
+ if (new_query->realloc(query_length + additional_length))
+ {
+ /*
+ We could not allocate the query. Use original query for
+ the query cache; Better than nothing....
+ */
+ new_query->set(query, query_length, system_charset_info);
+ return;
+ }
+
+ buffer= (char*) new_query->ptr(); // Store base query here
+ query_end= query + query_length;
+ last_space= 0; // No space found yet
+
+ while (query < query_end)
+ {
+ char current = *(query++);
+ switch (current) {
+ case '\'':
+ case '`':
+ case '"':
+ *(buffer++)= current; // copy first quote
+ while (query < query_end)
+ {
+ *(buffer++)= *query;
+ if (*(query++) == current) // found pair quote
+ break;
+ }
+ continue; // Continue with next symbol
+ case '/': // Start of comment ?
+ /*
+ Comment of format /#!number #/ or /#M!number #/, must be skipped.
+ These may include '"' and other comments, but it should
+ be safe to parse the content as a normal string.
+ */
+ if (query[0] != '*' || query[1] == '!' ||
+ (query[1] == 'M' && query[2] == '!'))
+ break;
+
+ query++; // skip "/"
+ while (++query < query_end)
+ {
+ if (query[0] == '*' && query[1] == '/')
+ {
+ query+= 2;
+ goto insert_space;
+ }
+ }
+ continue; // Will end outer loop
+ case '-':
+ if (*query != '-' || !is_white_space(query[1])) // Not a comment
+ break;
+ query++; // skip second "-", and go to search of "\n"
+ /* fall through */
+ case '#':
+ while (query < query_end)
+ {
+ if (*(query++) == '\n')
+ goto insert_space;
+ }
+ continue; // Will end outer loop
+ default:
+ if (is_white_space(current))
+ goto insert_space;
+ break;
+ }
+ *(buffer++)= current;
+ continue;
+
+insert_space:
+ if (buffer != last_space)
+ {
+ *(buffer++)= ' ';
+ last_space= buffer;
+ }
+ }
+ if (buffer == last_space)
+ buffer--; // Remove the last space
+ *buffer= 0; // End zero after query
+ new_query->length((size_t) (buffer - new_query->ptr()));
+
+ /* Copy db_length */
+ memcpy(buffer+1, query_end+1, QUERY_CACHE_DB_LENGTH_SIZE);
+}
+
+
+/**
+ Check and change local variable if global one is switched
+
+ @param thd thread handle
+*/
+
+void inline fix_local_query_cache_mode(THD *thd)
+{
+ if (global_system_variables.query_cache_type == 0)
+ thd->variables.query_cache_type= 0;
+}
+
+
+/**
+ Serialize access to the query cache.
+ If the lock cannot be granted the thread hangs in a conditional wait which
+ is signalled on each unlock.
+
+ The lock attempt will also fail without wait if lock_and_suspend() is in
+ effect by another thread. This enables a quick path in execution to skip waits
+ when the outcome is known.
+
+ @param mode TIMEOUT the lock can abort because of a timeout
+ TRY the lock can abort because it is locked now
+ WAIT wait for lock (default)
+
+ @note mode is optional and default value is WAIT.
+
+ @return
+ @retval FALSE An exclusive lock was taken
+ @retval TRUE The locking attempt failed
+*/
+
+bool Query_cache::try_lock(THD *thd, Cache_try_lock_mode mode)
+{
+ bool interrupt= TRUE;
+ Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
+ DBUG_ENTER("Query_cache::try_lock");
+
+ mysql_mutex_lock(&structure_guard_mutex);
+ DBUG_EXECUTE_IF("status_wait_query_cache_mutex_sleep", { sleep(5); });
+ if (m_cache_status == DISABLED)
+ {
+ mysql_mutex_unlock(&structure_guard_mutex);
+ DBUG_RETURN(TRUE);
+ }
+ m_requests_in_progress++;
+ fix_local_query_cache_mode(thd);
+
+ while (1)
+ {
+ if (m_cache_lock_status == Query_cache::UNLOCKED)
+ {
+ m_cache_lock_status= Query_cache::LOCKED;
+#ifndef DBUG_OFF
+ m_cache_lock_thread_id= thd->thread_id;
+#endif
+ interrupt= FALSE;
+ break;
+ }
+ else if (m_cache_lock_status == Query_cache::LOCKED_NO_WAIT)
+ {
+ /*
+ If query cache is protected by a LOCKED_NO_WAIT lock this thread
+ should avoid using the query cache as it is being evicted.
+ */
+ break;
+ }
+ else
+ {
+ DBUG_ASSERT(m_cache_lock_status == Query_cache::LOCKED);
+ /*
+ To prevent send_result_to_client() and query_cache_insert() from
+ blocking execution for too long a timeout is put on the lock.
+ */
+ if (mode == WAIT)
+ {
+ mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
+ }
+ else if (mode == TIMEOUT)
+ {
+ struct timespec waittime;
+ set_timespec_nsec(waittime,(ulong)(50000000L)); /* Wait for 50 msec */
+ int res= mysql_cond_timedwait(&COND_cache_status_changed,
+ &structure_guard_mutex, &waittime);
+ if (res == ETIMEDOUT)
+ break;
+ }
+ else
+ {
+ /**
+ If we are here, then mode is == TRY and there was someone else using
+ the query cache. (m_cache_lock_status != Query_cache::UNLOCKED).
+ Signal that we didn't get a lock.
+ */
+ DBUG_ASSERT(m_requests_in_progress > 1);
+ DBUG_ASSERT(mode == TRY);
+ break;
+ }
+ }
+ }
+ if (interrupt)
+ m_requests_in_progress--;
+ mysql_mutex_unlock(&structure_guard_mutex);
+
+ DBUG_RETURN(interrupt);
+}
+
+
+/**
+ Serialize access to the query cache.
+ If the lock cannot be granted the thread hangs in a conditional wait which
+ is signalled on each unlock.
+
+ This method also suspends the query cache so that other threads attempting to
+ lock the cache with try_lock() will fail directly without waiting.
+
+ It is used by all methods which flushes or destroys the whole cache.
+ */
+
+void Query_cache::lock_and_suspend(void)
+{
+ THD *thd= current_thd;
+ Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
+ DBUG_ENTER("Query_cache::lock_and_suspend");
+
+ mysql_mutex_lock(&structure_guard_mutex);
+ m_requests_in_progress++;
+ while (m_cache_lock_status != Query_cache::UNLOCKED)
+ mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
+ m_cache_lock_status= Query_cache::LOCKED_NO_WAIT;
+#ifndef DBUG_OFF
+ /* Here thd may not be set during shutdown */
+ if (thd)
+ m_cache_lock_thread_id= thd->thread_id;
+#endif
+ /* Wake up everybody, a whole cache flush is starting! */
+ mysql_cond_broadcast(&COND_cache_status_changed);
+ mysql_mutex_unlock(&structure_guard_mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Serialize access to the query cache.
+ If the lock cannot be granted the thread hangs in a conditional wait which
+ is signalled on each unlock.
+
+ It is used by all methods which invalidates one or more tables.
+ */
+
+void Query_cache::lock(THD *thd)
+{
+ Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
+ DBUG_ENTER("Query_cache::lock");
+
+ mysql_mutex_lock(&structure_guard_mutex);
+ m_requests_in_progress++;
+ fix_local_query_cache_mode(thd);
+ while (m_cache_lock_status != Query_cache::UNLOCKED)
+ mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
+ m_cache_lock_status= Query_cache::LOCKED;
+#ifndef DBUG_OFF
+ m_cache_lock_thread_id= thd->thread_id;
+#endif
+ mysql_mutex_unlock(&structure_guard_mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set the query cache to UNLOCKED and signal waiting threads.
+*/
+
+void Query_cache::unlock(void)
+{
+ DBUG_ENTER("Query_cache::unlock");
+ mysql_mutex_lock(&structure_guard_mutex);
+#ifndef DBUG_OFF
+ /* Thd may not be set in resize() at mysqld start */
+ THD *thd= current_thd;
+ if (thd)
+ DBUG_ASSERT(m_cache_lock_thread_id == thd->thread_id);
+#endif
+ DBUG_ASSERT(m_cache_lock_status == Query_cache::LOCKED ||
+ m_cache_lock_status == Query_cache::LOCKED_NO_WAIT);
+ m_cache_lock_status= Query_cache::UNLOCKED;
+ DBUG_PRINT("Query_cache",("Sending signal"));
+ mysql_cond_signal(&COND_cache_status_changed);
+ DBUG_ASSERT(m_requests_in_progress > 0);
+ m_requests_in_progress--;
+ if (m_requests_in_progress == 0 && m_cache_status == DISABLE_REQUEST)
+ {
+ /* No clients => just free query cache */
+ free_cache();
+ m_cache_status= DISABLED;
+ }
+ mysql_mutex_unlock(&structure_guard_mutex);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Helper function for determine if a SELECT statement has a SQL_NO_CACHE
+ directive.
+
+ @param sql A pointer to the first white space character after SELECT
+
+ @return
+ @retval TRUE The character string contains SQL_NO_CACHE
+ @retval FALSE No directive found.
+*/
+
+static bool has_no_cache_directive(const char *sql)
+{
+ while (is_white_space(*sql))
+ sql++;
+
+ if (my_toupper(system_charset_info, sql[0]) == 'S' &&
+ my_toupper(system_charset_info, sql[1]) == 'Q' &&
+ my_toupper(system_charset_info, sql[2]) == 'L' &&
+ my_toupper(system_charset_info, sql[3]) == '_' &&
+ my_toupper(system_charset_info, sql[4]) == 'N' &&
+ my_toupper(system_charset_info, sql[5]) == 'O' &&
+ my_toupper(system_charset_info, sql[6]) == '_' &&
+ my_toupper(system_charset_info, sql[7]) == 'C' &&
+ my_toupper(system_charset_info, sql[8]) == 'A' &&
+ my_toupper(system_charset_info, sql[9]) == 'C' &&
+ my_toupper(system_charset_info, sql[10]) == 'H' &&
+ my_toupper(system_charset_info, sql[11]) == 'E' &&
+ my_isspace(system_charset_info, sql[12]))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/*****************************************************************************
+ Query_cache_block_table method(s)
+*****************************************************************************/
+
+inline Query_cache_block * Query_cache_block_table::block()
+{
+ return (Query_cache_block *)(((uchar*)this) -
+ ALIGN_SIZE(sizeof(Query_cache_block_table)*n) -
+ ALIGN_SIZE(sizeof(Query_cache_block)));
+}
+
+/*****************************************************************************
+ Query_cache_block method(s)
+*****************************************************************************/
+
+void Query_cache_block::init(ulong block_length)
+{
+ DBUG_ENTER("Query_cache_block::init");
+ DBUG_PRINT("qcache", ("init block: 0x%lx length: %lu", (ulong) this,
+ block_length));
+ length = block_length;
+ used = 0;
+ type = Query_cache_block::FREE;
+ n_tables = 0;
+ DBUG_VOID_RETURN;
+}
+
+void Query_cache_block::destroy()
+{
+ DBUG_ENTER("Query_cache_block::destroy");
+ DBUG_PRINT("qcache", ("destroy block 0x%lx, type %d",
+ (ulong) this, type));
+ type = INCOMPLETE;
+ DBUG_VOID_RETURN;
+}
+
+uint Query_cache_block::headers_len()
+{
+ return (ALIGN_SIZE(sizeof(Query_cache_block_table)*n_tables) +
+ ALIGN_SIZE(sizeof(Query_cache_block)));
+}
+
+uchar* Query_cache_block::data(void)
+{
+ return (uchar*)( ((uchar*)this) + headers_len() );
+}
+
+Query_cache_query * Query_cache_block::query()
+{
+#ifndef DBUG_OFF
+ if (type != QUERY)
+ query_cache.wreck(__LINE__, "incorrect block type");
+#endif
+ return (Query_cache_query *) data();
+}
+
+Query_cache_table * Query_cache_block::table()
+{
+#ifndef DBUG_OFF
+ if (type != TABLE)
+ query_cache.wreck(__LINE__, "incorrect block type");
+#endif
+ return (Query_cache_table *) data();
+}
+
+Query_cache_result * Query_cache_block::result()
+{
+#ifndef DBUG_OFF
+ if (type != RESULT && type != RES_CONT && type != RES_BEG &&
+ type != RES_INCOMPLETE)
+ query_cache.wreck(__LINE__, "incorrect block type");
+#endif
+ return (Query_cache_result *) data();
+}
+
+Query_cache_block_table * Query_cache_block::table(TABLE_COUNTER_TYPE n)
+{
+ return ((Query_cache_block_table *)
+ (((uchar*)this)+ALIGN_SIZE(sizeof(Query_cache_block)) +
+ n*sizeof(Query_cache_block_table)));
+}
+
+
+/*****************************************************************************
+ * Query_cache_table method(s)
+ *****************************************************************************/
+
+extern "C"
+{
+uchar *query_cache_table_get_key(const uchar *record, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ Query_cache_block* table_block = (Query_cache_block*) record;
+ *length = (table_block->used - table_block->headers_len() -
+ ALIGN_SIZE(sizeof(Query_cache_table)));
+ return (((uchar *) table_block->data()) +
+ ALIGN_SIZE(sizeof(Query_cache_table)));
+}
+}
+
+/*****************************************************************************
+ Query_cache_query methods
+*****************************************************************************/
+
+/*
+ Following methods work for block read/write locking only in this
+ particular case and in interaction with structure_guard_mutex.
+
+ Lock for write prevents any other locking. (exclusive use)
+ Lock for read prevents only locking for write.
+*/
+
+inline void Query_cache_query::lock_writing()
+{
+ RW_WLOCK(&lock);
+}
+
+
+/*
+ Needed for finding queries, that we may delete from cache.
+ We don't want to wait while block become unlocked. In addition,
+ block locking means that query is now used and we don't need to
+ remove it.
+*/
+
+bool Query_cache_query::try_lock_writing()
+{
+ DBUG_ENTER("Query_cache_block::try_lock_writing");
+ if (mysql_rwlock_trywrlock(&lock) != 0)
+ {
+ DBUG_PRINT("info", ("can't lock rwlock"));
+ DBUG_RETURN(0);
+ }
+ DBUG_PRINT("info", ("rwlock 0x%lx locked", (ulong) &lock));
+ DBUG_RETURN(1);
+}
+
+
+inline void Query_cache_query::lock_reading()
+{
+ RW_RLOCK(&lock);
+}
+
+
+inline void Query_cache_query::unlock_writing()
+{
+ RW_UNLOCK(&lock);
+}
+
+
+inline void Query_cache_query::unlock_reading()
+{
+ RW_UNLOCK(&lock);
+}
+
+
+void Query_cache_query::init_n_lock()
+{
+ DBUG_ENTER("Query_cache_query::init_n_lock");
+ res=0; wri = 0; len = 0;
+ mysql_rwlock_init(key_rwlock_query_cache_query_lock, &lock);
+ lock_writing();
+ DBUG_PRINT("qcache", ("inited & locked query for block 0x%lx",
+ (long) (((uchar*) this) -
+ ALIGN_SIZE(sizeof(Query_cache_block)))));
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache_query::unlock_n_destroy()
+{
+ DBUG_ENTER("Query_cache_query::unlock_n_destroy");
+ DBUG_PRINT("qcache", ("destroyed & unlocked query for block 0x%lx",
+ (long) (((uchar*) this) -
+ ALIGN_SIZE(sizeof(Query_cache_block)))));
+ /*
+ The following call is not needed on system where one can destroy an
+ active semaphore
+ */
+ this->unlock_writing();
+ mysql_rwlock_destroy(&lock);
+ DBUG_VOID_RETURN;
+}
+
+
+extern "C"
+{
+uchar *query_cache_query_get_key(const uchar *record, size_t *length,
+ my_bool not_used)
+{
+ Query_cache_block *query_block = (Query_cache_block*) record;
+ *length = (query_block->used - query_block->headers_len() -
+ ALIGN_SIZE(sizeof(Query_cache_query)));
+ return (((uchar *) query_block->data()) +
+ ALIGN_SIZE(sizeof(Query_cache_query)));
+}
+}
+
+/*****************************************************************************
+ Functions to store things into the query cache
+*****************************************************************************/
+
+/*
+ Note on double-check locking (DCL) usage.
+
+ Below, in query_cache_insert(), query_cache_abort() and
+ Query_cache::end_of_result() we use what is called double-check
+ locking (DCL) for Query_cache_tls::first_query_block.
+ I.e. we test it first without a lock, and, if positive, test again
+ under the lock.
+
+ This means that if we see 'first_query_block == 0' without a
+ lock we will skip the operation. But this is safe here: when we
+ started to cache a query, we called Query_cache::store_query(), and
+ 'first_query_block' was set to non-zero in this thread (and the
+ thread always sees results of its memory operations, mutex or not).
+ If later we see 'first_query_block == 0' without locking a
+ mutex, that may only mean that some other thread have reset it by
+ invalidating the query. Skipping the operation in this case is the
+ right thing to do, as first_query_block won't get non-zero for
+ this query again.
+
+ See also comments in Query_cache::store_query() and
+ Query_cache::send_result_to_client().
+
+ NOTE, however, that double-check locking is not applicable in
+ 'invalidate' functions, as we may erroneously skip invalidation,
+ because the thread doing invalidation may never see non-zero
+ 'first_query_block'.
+*/
+
+
+/**
+ libmysql convenience wrapper to insert data into query cache.
+*/
+void query_cache_insert(const char *packet, ulong length,
+ unsigned pkt_nr)
+{
+ THD *thd= current_thd;
+
+ /*
+ Current_thd can be NULL when a new connection is immediately ended
+ due to "Too many connections". thd->store_globals() has not been
+ called at this time and hence set_current_thd(this) has not been
+ called for this thread.
+ */
+
+ if (!thd)
+ return;
+
+ query_cache.insert(&thd->query_cache_tls,
+ packet, length,
+ pkt_nr);
+}
+
+
+/**
+ Insert the packet into the query cache.
+*/
+
+void
+Query_cache::insert(Query_cache_tls *query_cache_tls,
+ const char *packet, ulong length,
+ unsigned pkt_nr)
+{
+ DBUG_ENTER("Query_cache::insert");
+
+ /* First we check if query cache is disable without doing a mutex lock */
+ if (is_disabled() || query_cache_tls->first_query_block == NULL)
+ DBUG_VOID_RETURN;
+
+ DBUG_ASSERT(current_thd);
+
+ QC_DEBUG_SYNC("wait_in_query_cache_insert");
+
+ /*
+ Lock the cache with try_lock(). try_lock() will fail if
+ cache was disabled between the above test and lock.
+ */
+ if (try_lock(current_thd, Query_cache::WAIT))
+ DBUG_VOID_RETURN;
+
+ Query_cache_block *query_block = query_cache_tls->first_query_block;
+ if (query_block == NULL)
+ {
+ /*
+ We lost the writer and the currently processed query has been
+ invalidated; there is nothing left to do.
+ */
+ unlock();
+ DBUG_VOID_RETURN;
+ }
+ BLOCK_LOCK_WR(query_block);
+ Query_cache_query *header= query_block->query();
+ Query_cache_block *result= header->result();
+
+ DUMP(this);
+ DBUG_PRINT("qcache", ("insert packet %lu bytes long",length));
+
+ /*
+ On success, STRUCT_UNLOCK is done by append_result_data. Otherwise, we
+ still need structure_guard_mutex to free the query, and therefore unlock
+ it later in this function.
+ */
+ if (!append_result_data(&result, length, (uchar*) packet,
+ query_block))
+ {
+ DBUG_PRINT("warning", ("Can't append data"));
+ header->result(result);
+ DBUG_PRINT("qcache", ("free query 0x%lx", (ulong) query_block));
+ // The following call will remove the lock on query_block
+ query_cache.free_query(query_block);
+ query_cache.refused++;
+ // append_result_data no success => we need unlock
+ unlock();
+ DBUG_VOID_RETURN;
+ }
+
+ header->result(result);
+ header->last_pkt_nr= pkt_nr;
+ BLOCK_UNLOCK_WR(query_block);
+ DBUG_EXECUTE("check_querycache",check_integrity(0););
+
+ DBUG_VOID_RETURN;
+}
+
+
+void
+Query_cache::abort(Query_cache_tls *query_cache_tls)
+{
+ THD *thd;
+ DBUG_ENTER("query_cache_abort");
+
+ /* See the comment on double-check locking usage above. */
+ if (is_disabled() || query_cache_tls->first_query_block == NULL)
+ DBUG_VOID_RETURN;
+
+ if (try_lock(current_thd, Query_cache::WAIT))
+ DBUG_VOID_RETURN;
+
+ /*
+ While we were waiting another thread might have changed the status
+ of the writer. Make sure the writer still exists before continue.
+ */
+ Query_cache_block *query_block= query_cache_tls->first_query_block;
+ if (query_block)
+ {
+ thd= current_thd;
+ THD_STAGE_INFO(thd, stage_storing_result_in_query_cache);
+ DUMP(this);
+ BLOCK_LOCK_WR(query_block);
+ // The following call will remove the lock on query_block
+ free_query(query_block);
+ query_cache_tls->first_query_block= NULL;
+ DBUG_EXECUTE("check_querycache", check_integrity(1););
+ }
+
+ unlock();
+
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::end_of_result(THD *thd)
+{
+ Query_cache_block *query_block;
+ Query_cache_tls *query_cache_tls= &thd->query_cache_tls;
+ ulonglong limit_found_rows= thd->limit_found_rows;
+ DBUG_ENTER("Query_cache::end_of_result");
+
+ /* See the comment on double-check locking usage above. */
+ if (query_cache_tls->first_query_block == NULL)
+ DBUG_VOID_RETURN;
+
+ /* Ensure that only complete results are cached. */
+ DBUG_ASSERT(thd->get_stmt_da()->is_eof());
+
+ if (thd->killed)
+ {
+ query_cache_abort(&thd->query_cache_tls);
+ DBUG_VOID_RETURN;
+ }
+
+#ifdef EMBEDDED_LIBRARY
+ insert(query_cache_tls, (char*)thd,
+ emb_count_querycache_size(thd), 0);
+#endif
+
+ if (try_lock(thd, Query_cache::WAIT))
+ DBUG_VOID_RETURN;
+
+ query_block= query_cache_tls->first_query_block;
+ if (query_block)
+ {
+ /*
+ The writer is still present; finish last result block by chopping it to
+ suitable size if needed and setting block type. Since this is the last
+ block, the writer should be dropped.
+ */
+ THD_STAGE_INFO(thd, stage_storing_result_in_query_cache);
+ DUMP(this);
+ BLOCK_LOCK_WR(query_block);
+ Query_cache_query *header= query_block->query();
+ Query_cache_block *last_result_block;
+ ulong allign_size;
+ ulong len;
+
+ if (header->result() == 0)
+ {
+ DBUG_PRINT("error", ("End of data with no result blocks; "
+ "Query '%s' removed from cache.", header->query()));
+ /*
+ Extra safety: empty result should not happen in the normal call
+ to this function. In the release version that query should be ignored
+ and removed from QC.
+ */
+ DBUG_ASSERT(0);
+ free_query(query_block);
+ unlock();
+ DBUG_VOID_RETURN;
+ }
+ last_result_block= header->result()->prev;
+ allign_size= ALIGN_SIZE(last_result_block->used);
+ len= MY_MAX(query_cache.min_allocation_unit, allign_size);
+ if (last_result_block->length >= query_cache.min_allocation_unit + len)
+ query_cache.split_block(last_result_block,len);
+
+ header->found_rows(limit_found_rows);
+ header->result()->type= Query_cache_block::RESULT;
+
+ /* Drop the writer. */
+ header->writer(0);
+ query_cache_tls->first_query_block= NULL;
+ BLOCK_UNLOCK_WR(query_block);
+ DBUG_EXECUTE("check_querycache", check_integrity(1););
+ }
+
+ unlock();
+ DBUG_VOID_RETURN;
+}
+
+void query_cache_invalidate_by_MyISAM_filename(const char *filename)
+{
+ query_cache.invalidate_by_MyISAM_filename(filename);
+ DBUG_EXECUTE("check_querycache",query_cache.check_integrity(0););
+}
+
+
+/*
+ The following function forms part of the C plugin API
+*/
+extern "C"
+void mysql_query_cache_invalidate4(THD *thd,
+ const char *key, unsigned key_length,
+ int using_trx)
+{
+ query_cache.invalidate(thd, key, (uint32) key_length, (my_bool) using_trx);
+}
+
+
+/*****************************************************************************
+ Query_cache methods
+*****************************************************************************/
+
+Query_cache::Query_cache(ulong query_cache_limit_arg,
+ ulong min_allocation_unit_arg,
+ ulong min_result_data_size_arg,
+ uint def_query_hash_size_arg,
+ uint def_table_hash_size_arg)
+ :query_cache_size(0),
+ query_cache_limit(query_cache_limit_arg),
+ queries_in_cache(0), hits(0), inserts(0), refused(0),
+ total_blocks(0), lowmem_prunes(0),
+ m_cache_status(OK),
+ min_allocation_unit(ALIGN_SIZE(min_allocation_unit_arg)),
+ min_result_data_size(ALIGN_SIZE(min_result_data_size_arg)),
+ def_query_hash_size(ALIGN_SIZE(def_query_hash_size_arg)),
+ def_table_hash_size(ALIGN_SIZE(def_table_hash_size_arg)),
+ initialized(0)
+{
+ ulong min_needed= (ALIGN_SIZE(sizeof(Query_cache_block)) +
+ ALIGN_SIZE(sizeof(Query_cache_block_table)) +
+ ALIGN_SIZE(sizeof(Query_cache_query)) + 3);
+ set_if_bigger(min_allocation_unit,min_needed);
+ this->min_allocation_unit= ALIGN_SIZE(min_allocation_unit);
+ set_if_bigger(this->min_result_data_size,min_allocation_unit);
+}
+
+
+ulong Query_cache::resize(ulong query_cache_size_arg)
+{
+ ulong new_query_cache_size;
+ DBUG_ENTER("Query_cache::resize");
+ DBUG_PRINT("qcache", ("from %lu to %lu",query_cache_size,
+ query_cache_size_arg));
+ DBUG_ASSERT(initialized);
+
+ if (global_system_variables.query_cache_type == 0)
+ {
+ DBUG_ASSERT(query_cache_size_arg == 0);
+ if (query_cache_size_arg != 0)
+ my_error(ER_QUERY_CACHE_IS_DISABLED, MYF(0));
+ DBUG_RETURN(0);
+ }
+
+ lock_and_suspend();
+
+ /*
+ Wait for all readers and writers to exit. When the list of all queries
+ is iterated over with a block level lock, we are done.
+ */
+ Query_cache_block *block= queries_blocks;
+ if (block)
+ {
+ do
+ {
+ BLOCK_LOCK_WR(block);
+ Query_cache_query *query= block->query();
+ if (query->writer())
+ {
+ /*
+ Drop the writer; this will cancel any attempts to store
+ the processed statement associated with this writer.
+ */
+ query->writer()->first_query_block= NULL;
+ query->writer(0);
+ refused++;
+ }
+ query->unlock_n_destroy();
+ block= block->next;
+ } while (block != queries_blocks);
+ queries_blocks= NULL; // avoid second destroying by free_cache
+ }
+ free_cache();
+
+ query_cache_size= query_cache_size_arg;
+ new_query_cache_size= init_cache();
+
+ /*
+ m_cache_status is internal query cache switch so switching it on/off
+ will not be reflected on global_system_variables.query_cache_type
+ */
+ if (new_query_cache_size)
+ {
+ DBUG_EXECUTE("check_querycache",check_integrity(1););
+ m_cache_status= OK; // size > 0 => enable cache
+ }
+ else
+ m_cache_status= DISABLED; // size 0 means the cache disabled
+
+ unlock();
+ DBUG_RETURN(new_query_cache_size);
+}
+
+
+ulong Query_cache::set_min_res_unit(ulong size)
+{
+ DBUG_ASSERT(size % 8 == 0);
+ if (size < min_allocation_unit)
+ size= ALIGN_SIZE(min_allocation_unit);
+ return (min_result_data_size= size);
+}
+
+
+void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used)
+{
+ TABLE_COUNTER_TYPE local_tables;
+ ulong tot_length;
+ const char *query;
+ size_t query_length;
+ uint8 tables_type;
+ DBUG_ENTER("Query_cache::store_query");
+ /*
+ Testing 'query_cache_size' without a lock here is safe: the thing
+ we may loose is that the query won't be cached, but we save on
+ mutex locking in the case when query cache is disabled or the
+ query is uncachable.
+
+ See also a note on double-check locking usage above.
+ */
+ if (!thd->query_cache_is_applicable || query_cache_size == 0)
+ {
+ DBUG_PRINT("qcache", ("Query cache not ready"));
+ DBUG_VOID_RETURN;
+ }
+ if (thd->lex->sql_command != SQLCOM_SELECT)
+ {
+ DBUG_PRINT("qcache", ("Ignoring not SELECT command"));
+ DBUG_VOID_RETURN;
+ }
+
+ /* The following assert fails if we haven't called send_result_to_client */
+ DBUG_ASSERT(thd->base_query.is_alloced() ||
+ thd->base_query.ptr() == thd->query());
+
+ tables_type= 0;
+ if ((local_tables= is_cacheable(thd, thd->lex, tables_used,
+ &tables_type)))
+ {
+ NET *net= &thd->net;
+ Query_cache_query_flags flags;
+ // fill all gaps between fields with 0 to get repeatable key
+ bzero(&flags, QUERY_CACHE_FLAGS_SIZE);
+ flags.client_long_flag= MY_TEST(thd->client_capabilities & CLIENT_LONG_FLAG);
+ flags.client_protocol_41= MY_TEST(thd->client_capabilities &
+ CLIENT_PROTOCOL_41);
+ /*
+ Protocol influences result format, so statement results in the binary
+ protocol (COM_EXECUTE) cannot be served to statements asking for results
+ in the text protocol (COM_QUERY) and vice-versa.
+ */
+ flags.protocol_type= (unsigned int) thd->protocol->type();
+ /* PROTOCOL_LOCAL results are not cached. */
+ DBUG_ASSERT(flags.protocol_type != (unsigned int) Protocol::PROTOCOL_LOCAL);
+ flags.more_results_exists= MY_TEST(thd->server_status &
+ SERVER_MORE_RESULTS_EXISTS);
+ flags.in_trans= thd->in_active_multi_stmt_transaction();
+ flags.autocommit= MY_TEST(thd->server_status & SERVER_STATUS_AUTOCOMMIT);
+ flags.pkt_nr= net->pkt_nr;
+ flags.character_set_client_num=
+ thd->variables.character_set_client->number;
+ flags.character_set_results_num=
+ (thd->variables.character_set_results ?
+ thd->variables.character_set_results->number :
+ UINT_MAX);
+ flags.collation_connection_num=
+ thd->variables.collation_connection->number;
+ flags.limit= thd->variables.select_limit;
+ flags.time_zone= thd->variables.time_zone;
+ flags.sql_mode= thd->variables.sql_mode;
+ flags.max_sort_length= thd->variables.max_sort_length;
+ flags.lc_time_names= thd->variables.lc_time_names;
+ flags.group_concat_max_len= thd->variables.group_concat_max_len;
+ flags.div_precision_increment= thd->variables.div_precincrement;
+ flags.default_week_format= thd->variables.default_week_format;
+ DBUG_PRINT("qcache", ("\
+long %d, 4.1: %d, bin_proto: %d, more results %d, pkt_nr: %d, \
+CS client: %u, CS result: %u, CS conn: %u, limit: %lu, TZ: 0x%lx, \
+sql mode: 0x%llx, sort len: %lu, conncat len: %lu, div_precision: %lu, \
+def_week_frmt: %lu, in_trans: %d, autocommit: %d",
+ (int)flags.client_long_flag,
+ (int)flags.client_protocol_41,
+ (int)flags.protocol_type,
+ (int)flags.more_results_exists,
+ flags.pkt_nr,
+ flags.character_set_client_num,
+ flags.character_set_results_num,
+ flags.collation_connection_num,
+ (ulong) flags.limit,
+ (ulong) flags.time_zone,
+ flags.sql_mode,
+ flags.max_sort_length,
+ flags.group_concat_max_len,
+ flags.div_precision_increment,
+ flags.default_week_format,
+ (int)flags.in_trans,
+ (int)flags.autocommit));
+
+ /*
+ Make InnoDB to release the adaptive hash index latch before
+ acquiring the query cache mutex.
+ */
+ ha_release_temporary_latches(thd);
+
+ /*
+ A table- or a full flush operation can potentially take a long time to
+ finish. We choose not to wait for them and skip caching statements
+ instead.
+
+ In case the wait time can't be determined there is an upper limit which
+ causes try_lock() to abort with a time out.
+
+ The 'TIMEOUT' parameter indicate that the lock is allowed to timeout
+
+ */
+ if (try_lock(thd, Query_cache::TIMEOUT))
+ DBUG_VOID_RETURN;
+ if (query_cache_size == 0)
+ {
+ unlock();
+ DBUG_VOID_RETURN;
+ }
+ DUMP(this);
+
+ if (ask_handler_allowance(thd, tables_used))
+ {
+ refused++;
+ unlock();
+ DBUG_VOID_RETURN;
+ }
+
+ query= thd->base_query.ptr();
+ query_length= thd->base_query.length();
+
+ /* Key is query + database + flag */
+ if (thd->db_length)
+ {
+ memcpy((char*) (query + query_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE),
+ thd->db, thd->db_length);
+ DBUG_PRINT("qcache", ("database: %s length: %u",
+ thd->db, (unsigned) thd->db_length));
+ }
+ else
+ {
+ DBUG_PRINT("qcache", ("No active database"));
+ }
+ tot_length= (query_length + thd->db_length + 1 +
+ QUERY_CACHE_DB_LENGTH_SIZE + QUERY_CACHE_FLAGS_SIZE);
+ /*
+ We should only copy structure (don't use it location directly)
+ because of alignment issue
+ */
+ memcpy((void*) (query + (tot_length - QUERY_CACHE_FLAGS_SIZE)),
+ &flags, QUERY_CACHE_FLAGS_SIZE);
+
+ /* Check if another thread is processing the same query? */
+ Query_cache_block *competitor = (Query_cache_block *)
+ my_hash_search(&queries, (uchar*) query, tot_length);
+ DBUG_PRINT("qcache", ("competitor 0x%lx", (ulong) competitor));
+ if (competitor == 0)
+ {
+ /* Query is not in cache and no one is working with it; Store it */
+ Query_cache_block *query_block;
+ query_block= write_block_data(tot_length, (uchar*) query,
+ ALIGN_SIZE(sizeof(Query_cache_query)),
+ Query_cache_block::QUERY, local_tables);
+ if (query_block != 0)
+ {
+ DBUG_PRINT("qcache", ("query block 0x%lx allocated, %lu",
+ (ulong) query_block, query_block->used));
+
+ Query_cache_query *header = query_block->query();
+ header->init_n_lock();
+ if (my_hash_insert(&queries, (uchar*) query_block))
+ {
+ refused++;
+ DBUG_PRINT("qcache", ("insertion in query hash"));
+ header->unlock_n_destroy();
+ free_memory_block(query_block);
+ unlock();
+ goto end;
+ }
+ if (!register_all_tables(thd, query_block, tables_used, local_tables))
+ {
+ refused++;
+ DBUG_PRINT("warning", ("tables list including failed"));
+ my_hash_delete(&queries, (uchar *) query_block);
+ header->unlock_n_destroy();
+ free_memory_block(query_block);
+ unlock();
+ goto end;
+ }
+ double_linked_list_simple_include(query_block, &queries_blocks);
+ inserts++;
+ queries_in_cache++;
+ thd->query_cache_tls.first_query_block= query_block;
+ header->writer(&thd->query_cache_tls);
+ header->tables_type(tables_type);
+
+ unlock();
+
+ // init_n_lock make query block locked
+ BLOCK_UNLOCK_WR(query_block);
+ }
+ else
+ {
+ // We have not enough memory to store query => do nothing
+ refused++;
+ unlock();
+ DBUG_PRINT("warning", ("Can't allocate query"));
+ }
+ }
+ else
+ {
+ // Another thread is processing the same query => do nothing
+ refused++;
+ unlock();
+ DBUG_PRINT("qcache", ("Another thread process same query"));
+ }
+ }
+ else
+ statistic_increment(refused, &structure_guard_mutex);
+
+end:
+ DBUG_VOID_RETURN;
+}
+
+
+#ifndef EMBEDDED_LIBRARY
+/**
+ Send a single memory block from the query cache.
+
+ Respects the client/server protocol limits for the
+ size of the network packet, and splits a large block
+ in pieces to ensure that individual piece doesn't exceed
+ the maximal allowed size of the network packet (16M).
+
+ @param[in] net NET handler
+ @param[in] packet packet to send
+ @param[in] len packet length
+
+ @return Operation status
+ @retval FALSE On success
+ @retval TRUE On error
+*/
+static bool
+send_data_in_chunks(NET *net, const uchar *packet, ulong len)
+{
+ /*
+ On the client we may require more memory than max_allowed_packet
+ to keep, both, the truncated last logical packet, and the
+ compressed next packet. This never (or in practice never)
+ happens without compression, since without compression it's very
+ unlikely that a) a truncated logical packet would remain on the
+ client when it's time to read the next packet b) a subsequent
+ logical packet that is being read would be so large that
+ size-of-new-packet + size-of-old-packet-tail >
+ max_allowed_packet. To remedy this issue, we send data in 1MB
+ sized packets, that's below the current client default of 16MB
+ for max_allowed_packet, but large enough to ensure there is no
+ unnecessary overhead from too many syscalls per result set.
+ */
+ static const ulong MAX_CHUNK_LENGTH= 1024*1024;
+
+ while (len > MAX_CHUNK_LENGTH)
+ {
+ if (net_real_write(net, packet, MAX_CHUNK_LENGTH))
+ return TRUE;
+ packet+= MAX_CHUNK_LENGTH;
+ len-= MAX_CHUNK_LENGTH;
+ }
+ if (len && net_real_write(net, packet, len))
+ return TRUE;
+
+ return FALSE;
+}
+#endif
+
+
+/**
+ Build a normalized table name suitable for query cache engine callback
+
+ This consist of normalized directory '/' normalized_file_name
+ followed by suffix.
+ Suffix is needed for partitioned tables.
+*/
+
+size_t build_normalized_name(char *buff, size_t bufflen,
+ const char *db, size_t db_len,
+ const char *table_name, size_t table_len,
+ size_t suffix_len)
+{
+ uint errors;
+ size_t length;
+ char *pos= buff, *end= buff+bufflen;
+ DBUG_ENTER("build_normalized_name");
+
+ (*pos++)= FN_LIBCHAR;
+ length= strconvert(system_charset_info, db, db_len,
+ &my_charset_filename, pos, bufflen - 3,
+ &errors);
+ pos+= length;
+ (*pos++)= FN_LIBCHAR;
+ length= strconvert(system_charset_info, table_name, table_len,
+ &my_charset_filename, pos, (uint) (end - pos),
+ &errors);
+ pos+= length;
+ if (pos + suffix_len < end)
+ pos= strmake(pos, table_name + table_len, suffix_len);
+
+ DBUG_RETURN((size_t) (pos - buff));
+}
+
+
+/*
+ Check if the query is in the cache. If it was cached, send it
+ to the user.
+
+ @param thd Pointer to the thread handler
+ @param org_sql A pointer to the sql statement *
+ @param query_length Length of the statement in characters
+
+ @return status code
+ @retval 0 Query was not cached.
+ @retval 1 The query was cached and user was sent the result.
+ @retval -1 The query was cached but we didn't have rights to use it.
+
+ In case of -1, no error is sent to the client.
+
+ *) The buffer must be allocated memory of size:
+ tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE;
+*/
+
+int
+Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length)
+{
+ ulonglong engine_data;
+ Query_cache_query *query;
+#ifndef EMBEDDED_LIBRARY
+ Query_cache_block *first_result_block;
+#endif
+ Query_cache_block *result_block;
+ Query_cache_block_table *block_table, *block_table_end;
+ ulong tot_length;
+ Query_cache_query_flags flags;
+ const char *sql, *sql_end, *found_brace= 0;
+ DBUG_ENTER("Query_cache::send_result_to_client");
+
+ /*
+ Testing without a lock here is safe: the thing
+ we may loose is that the query won't be served from cache, but we
+ save on mutex locking in the case when query cache is disabled.
+
+ See also a note on double-check locking usage above.
+ */
+ if (is_disabled() || thd->locked_tables_mode ||
+ thd->variables.query_cache_type == 0)
+ goto err;
+
+ /*
+ The following can only happen for prepared statements that was found
+ during parsing or later that the query was not cacheable.
+ */
+ if (!thd->lex->safe_to_cache_query)
+ {
+ DBUG_PRINT("qcache", ("SELECT is non-cacheable"));
+ goto err;
+ }
+
+ thd->query_cache_is_applicable= 1;
+ sql= org_sql; sql_end= sql + query_length;
+
+ /*
+ Skip all comments at start of query. The following tests is false for
+ all normal queries.
+ */
+ if (!my_isalpha(system_charset_info, *sql))
+ {
+ while (sql < sql_end)
+ {
+ char current= *sql;
+ switch (current) {
+ case '/':
+ if (sql[1] != '*')
+ break;
+ sql+= 2; // Skip '/*'
+ if (*sql == '!')
+ {
+ /*
+ Found / *!number comment; Skip number to see if sql
+ starts with 'select'
+ */
+ sql++;
+ while (my_isdigit(system_charset_info, *sql))
+ sql++;
+ }
+ else
+ {
+ while (sql++ < sql_end)
+ {
+ if (sql[-1] == '*' && *sql == '/')
+ {
+ sql++;
+ break;
+ }
+ }
+ }
+ continue;
+ case '-':
+ if (sql[1] != '-' || !is_white_space(sql[2])) // Not a comment
+ break;
+ sql++; // Skip first '-'
+ /* Fall through */
+ case '#':
+ while (++sql < sql_end)
+ {
+ if (*sql == '\n')
+ {
+ sql++; // Skip '\n'
+ break;
+ }
+ }
+ /* Continue with analyzing current symbol */
+ continue;
+ case '\r':
+ case '\n':
+ case '\t':
+ case ' ':
+ sql++;
+ continue;
+ case '(': // To handle (select a from t1) union (select a from t1);
+ if (!found_brace)
+ {
+ found_brace= sql;
+ sql++;
+ continue;
+ }
+ /* fall trough */
+ default:
+ break;
+ }
+ /* We only come here when we found the first word of the sql */
+ break;
+ }
+ }
+ if ((my_toupper(system_charset_info, sql[0]) != 'S' ||
+ my_toupper(system_charset_info, sql[1]) != 'E' ||
+ my_toupper(system_charset_info, sql[2]) != 'L'))
+ {
+ DBUG_PRINT("qcache", ("The statement is not a SELECT; Not cached"));
+ goto err;
+ }
+
+ if ((sql_end - sql) > 20 && has_no_cache_directive(sql+6))
+ {
+ /*
+ We do not increase 'refused' statistics here since it will be done
+ later when the query is parsed.
+ */
+ DBUG_PRINT("qcache", ("The statement has a SQL_NO_CACHE directive"));
+ goto err;
+ }
+ {
+ /*
+ We have allocated buffer space (in alloc_query) to hold the
+ SQL statement(s) + the current database name + a flags struct.
+ If the database name has changed during execution, which might
+ happen if there are multiple statements, we need to make
+ sure the new current database has a name with the same length
+ as the previous one.
+ */
+ size_t db_len= uint2korr(sql_end+1);
+ if (thd->db_length != db_len)
+ {
+ /*
+ We should probably reallocate the buffer in this case,
+ but for now we just leave it uncached
+ */
+
+ DBUG_PRINT("qcache",
+ ("Current database has changed since start of query"));
+ goto err;
+ }
+ }
+ /*
+ Try to obtain an exclusive lock on the query cache. If the cache is
+ disabled or if a full cache flush is in progress, the attempt to
+ get the lock is aborted.
+
+ The TIMEOUT parameter indicate that the lock is allowed to timeout.
+ */
+ if (try_lock(thd, Query_cache::TIMEOUT))
+ goto err;
+
+ if (query_cache_size == 0)
+ {
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ goto err_unlock;
+ }
+
+ Query_cache_block *query_block;
+ if (thd->variables.query_cache_strip_comments)
+ {
+ if (found_brace)
+ sql= found_brace;
+ make_base_query(&thd->base_query, sql, (size_t) (sql_end - sql),
+ thd->db_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE +
+ QUERY_CACHE_FLAGS_SIZE);
+ sql= thd->base_query.ptr();
+ query_length= thd->base_query.length();
+ }
+ else
+ {
+ sql= org_sql;
+ thd->base_query.set(sql, query_length, system_charset_info);
+ }
+
+ tot_length= (query_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE +
+ thd->db_length + QUERY_CACHE_FLAGS_SIZE);
+
+ if (thd->db_length)
+ {
+ memcpy((uchar*) sql + query_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE,
+ thd->db, thd->db_length);
+ DBUG_PRINT("qcache", ("database: '%s' length: %u",
+ thd->db, (uint) thd->db_length));
+ }
+ else
+ {
+ DBUG_PRINT("qcache", ("No active database"));
+ }
+
+ THD_STAGE_INFO(thd, stage_checking_query_cache_for_query);
+
+ // fill all gaps between fields with 0 to get repeatable key
+ bzero(&flags, QUERY_CACHE_FLAGS_SIZE);
+ flags.client_long_flag= MY_TEST(thd->client_capabilities & CLIENT_LONG_FLAG);
+ flags.client_protocol_41= MY_TEST(thd->client_capabilities &
+ CLIENT_PROTOCOL_41);
+ flags.protocol_type= (unsigned int) thd->protocol->type();
+ flags.more_results_exists= MY_TEST(thd->server_status &
+ SERVER_MORE_RESULTS_EXISTS);
+ flags.in_trans= thd->in_active_multi_stmt_transaction();
+ flags.autocommit= MY_TEST(thd->server_status & SERVER_STATUS_AUTOCOMMIT);
+ flags.pkt_nr= thd->net.pkt_nr;
+ flags.character_set_client_num= thd->variables.character_set_client->number;
+ flags.character_set_results_num=
+ (thd->variables.character_set_results ?
+ thd->variables.character_set_results->number :
+ UINT_MAX);
+ flags.collation_connection_num= thd->variables.collation_connection->number;
+ flags.limit= thd->variables.select_limit;
+ flags.time_zone= thd->variables.time_zone;
+ flags.sql_mode= thd->variables.sql_mode;
+ flags.max_sort_length= thd->variables.max_sort_length;
+ flags.group_concat_max_len= thd->variables.group_concat_max_len;
+ flags.div_precision_increment= thd->variables.div_precincrement;
+ flags.default_week_format= thd->variables.default_week_format;
+ flags.lc_time_names= thd->variables.lc_time_names;
+ DBUG_PRINT("qcache", ("\
+long %d, 4.1: %d, bin_proto: %d, more results %d, pkt_nr: %d, \
+CS client: %u, CS result: %u, CS conn: %u, limit: %lu, TZ: 0x%lx, \
+sql mode: 0x%llx, sort len: %lu, conncat len: %lu, div_precision: %lu, \
+def_week_frmt: %lu, in_trans: %d, autocommit: %d",
+ (int)flags.client_long_flag,
+ (int)flags.client_protocol_41,
+ (int)flags.protocol_type,
+ (int)flags.more_results_exists,
+ flags.pkt_nr,
+ flags.character_set_client_num,
+ flags.character_set_results_num,
+ flags.collation_connection_num,
+ (ulong) flags.limit,
+ (ulong) flags.time_zone,
+ flags.sql_mode,
+ flags.max_sort_length,
+ flags.group_concat_max_len,
+ flags.div_precision_increment,
+ flags.default_week_format,
+ (int)flags.in_trans,
+ (int)flags.autocommit));
+ memcpy((uchar *)(sql + (tot_length - QUERY_CACHE_FLAGS_SIZE)),
+ (uchar*) &flags, QUERY_CACHE_FLAGS_SIZE);
+ query_block = (Query_cache_block *) my_hash_search(&queries, (uchar*) sql,
+ tot_length);
+ /* Quick abort on unlocked data */
+ if (query_block == 0 ||
+ query_block->query()->result() == 0 ||
+ query_block->query()->result()->type != Query_cache_block::RESULT)
+ {
+ DBUG_PRINT("qcache", ("No query in query hash or no results"));
+ goto err_unlock;
+ }
+ DBUG_PRINT("qcache", ("Query in query hash 0x%lx", (ulong)query_block));
+
+ /* Now lock and test that nothing changed while blocks was unlocked */
+ BLOCK_LOCK_RD(query_block);
+
+ query = query_block->query();
+ result_block= query->result();
+#ifndef EMBEDDED_LIBRARY
+ first_result_block= result_block;
+#endif
+
+ if (result_block == 0 || result_block->type != Query_cache_block::RESULT)
+ {
+ /* The query is probably yet processed */
+ DBUG_PRINT("qcache", ("query found, but no data or data incomplete"));
+ BLOCK_UNLOCK_RD(query_block);
+ goto err_unlock;
+ }
+ DBUG_PRINT("qcache", ("Query have result 0x%lx", (ulong) query));
+
+ if (thd->in_multi_stmt_transaction_mode() &&
+ (query->tables_type() & HA_CACHE_TBL_TRANSACT))
+ {
+ DBUG_PRINT("qcache",
+ ("we are in transaction and have transaction tables in query"));
+ BLOCK_UNLOCK_RD(query_block);
+ goto err_unlock;
+ }
+
+ // Check access;
+ THD_STAGE_INFO(thd, stage_checking_privileges_on_cached_query);
+ block_table= query_block->table(0);
+ block_table_end= block_table+query_block->n_tables;
+ for (; block_table != block_table_end; block_table++)
+ {
+ TABLE_LIST table_list;
+ TABLE *tmptable;
+ Query_cache_table *table = block_table->parent;
+
+ /*
+ Check that we have not temporary tables with same names of tables
+ of this query. If we have such tables, we will not send data from
+ query cache, because temporary tables hide real tables by which
+ query in query cache was made.
+ */
+ for (tmptable= thd->temporary_tables; tmptable ; tmptable= tmptable->next)
+ {
+ if (tmptable->s->table_cache_key.length - TMP_TABLE_KEY_EXTRA ==
+ table->key_length() &&
+ !memcmp(tmptable->s->table_cache_key.str, table->data(),
+ table->key_length()))
+ {
+ DBUG_PRINT("qcache",
+ ("Temporary table detected: '%s.%s'",
+ tmptable->s->db.str, tmptable->alias.c_ptr()));
+ unlock();
+ /*
+ We should not store result of this query because it contain
+ temporary tables => assign following variable to make check
+ faster.
+ */
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ thd->lex->safe_to_cache_query= 0; // For prepared statements
+ BLOCK_UNLOCK_RD(query_block);
+ DBUG_RETURN(-1);
+ }
+ }
+
+ bzero((char*) &table_list,sizeof(table_list));
+ table_list.db = table->db();
+ table_list.alias= table_list.table_name= table->table();
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (check_table_access(thd,SELECT_ACL,&table_list, FALSE, 1,TRUE))
+ {
+ DBUG_PRINT("qcache",
+ ("probably no SELECT access to %s.%s => return to normal processing",
+ table_list.db, table_list.alias));
+ unlock();
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ thd->lex->safe_to_cache_query= 0; // For prepared statements
+ BLOCK_UNLOCK_RD(query_block);
+ DBUG_RETURN(-1); // Privilege error
+ }
+ if (table_list.grant.want_privilege)
+ {
+ DBUG_PRINT("qcache", ("Need to check column privileges for %s.%s",
+ table_list.db, table_list.alias));
+ BLOCK_UNLOCK_RD(query_block);
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ thd->lex->safe_to_cache_query= 0; // For prepared statements
+ goto err_unlock; // Parse query
+ }
+#endif /*!NO_EMBEDDED_ACCESS_CHECKS*/
+ engine_data= table->engine_data();
+ if (table->callback())
+ {
+ char qcache_se_key_name[FN_REFLEN + 10];
+ uint qcache_se_key_len, db_length= strlen(table->db());
+ engine_data= table->engine_data();
+
+ qcache_se_key_len= build_normalized_name(qcache_se_key_name,
+ sizeof(qcache_se_key_name),
+ table->db(),
+ db_length,
+ table->table(),
+ table->key_length() -
+ db_length - 2 -
+ table->suffix_length(),
+ table->suffix_length());
+
+ if (!(*table->callback())(thd, qcache_se_key_name,
+ qcache_se_key_len, &engine_data))
+ {
+ DBUG_PRINT("qcache", ("Handler does not allow caching for %.*s",
+ qcache_se_key_len, qcache_se_key_name));
+ BLOCK_UNLOCK_RD(query_block);
+ if (engine_data != table->engine_data())
+ {
+ DBUG_PRINT("qcache",
+ ("Handler require invalidation queries of %.*s %lu-%lu",
+ qcache_se_key_len, qcache_se_key_name,
+ (ulong) engine_data, (ulong) table->engine_data()));
+ invalidate_table_internal(thd,
+ (uchar *) table->db(),
+ table->key_length());
+ }
+ else
+ {
+ /*
+ As this can change from call to call, don't reset set
+ thd->lex->safe_to_cache_query
+ */
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ }
+ /*
+ End the statement transaction potentially started by engine.
+ Currently our engines do not request rollback from callbacks.
+ If this is going to change code needs to be reworked.
+ */
+ DBUG_ASSERT(! thd->transaction_rollback_request);
+ trans_rollback_stmt(thd);
+ goto err_unlock; // Parse query
+ }
+ }
+ else
+ DBUG_PRINT("qcache", ("handler allow caching %s,%s",
+ table_list.db, table_list.alias));
+ }
+ move_to_query_list_end(query_block);
+ hits++;
+ unlock();
+
+ /*
+ Send cached result to client
+ */
+#ifndef EMBEDDED_LIBRARY
+ THD_STAGE_INFO(thd, stage_sending_cached_result_to_client);
+ do
+ {
+ DBUG_PRINT("qcache", ("Results (len: %lu used: %lu headers: %lu)",
+ result_block->length, result_block->used,
+ (ulong) (result_block->headers_len()+
+ ALIGN_SIZE(sizeof(Query_cache_result)))));
+
+ Query_cache_result *result = result_block->result();
+ if (send_data_in_chunks(&thd->net, result->data(),
+ result_block->used -
+ result_block->headers_len() -
+ ALIGN_SIZE(sizeof(Query_cache_result))))
+ break; // Client aborted
+ result_block = result_block->next;
+ thd->net.pkt_nr= query->last_pkt_nr; // Keep packet number updated
+ } while (result_block != first_result_block);
+#else
+ {
+ Querycache_stream qs(result_block, result_block->headers_len() +
+ ALIGN_SIZE(sizeof(Query_cache_result)));
+ emb_load_querycache_result(thd, &qs);
+ }
+#endif /*!EMBEDDED_LIBRARY*/
+
+ thd->set_sent_row_count(thd->limit_found_rows = query->found_rows());
+ thd->status_var.last_query_cost= 0.0;
+ thd->query_plan_flags= (thd->query_plan_flags & ~QPLAN_QC_NO) | QPLAN_QC;
+ if (!thd->get_sent_row_count())
+ status_var_increment(thd->status_var.empty_queries);
+ else
+ status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count());
+
+ /*
+ End the statement transaction potentially started by an
+ engine callback. We ignore the return value for now,
+ since as long as EOF packet is part of the query cache
+ response, we can't handle it anyway.
+ */
+ (void) trans_commit_stmt(thd);
+ if (!thd->get_stmt_da()->is_set())
+ thd->get_stmt_da()->disable_status();
+
+ BLOCK_UNLOCK_RD(query_block);
+ MYSQL_QUERY_CACHE_HIT(thd->query(), (ulong) thd->limit_found_rows);
+ DBUG_RETURN(1); // Result sent to client
+
+err_unlock:
+ unlock();
+ MYSQL_QUERY_CACHE_MISS(thd->query());
+ /*
+ query_plan_flags doesn't have to be changed here as it contains
+ QPLAN_QC_NO by default
+ */
+ DBUG_RETURN(0); // Query was not cached
+
+err:
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ DBUG_RETURN(0); // Query was not cached
+}
+
+
+/*
+ Remove all cached queries that uses any of the tables in the list
+*/
+
+void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used,
+ my_bool using_transactions)
+{
+ DBUG_ENTER("Query_cache::invalidate (table list)");
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ using_transactions= using_transactions && thd->in_multi_stmt_transaction_mode();
+ for (; tables_used; tables_used= tables_used->next_local)
+ {
+ DBUG_ASSERT(!using_transactions || tables_used->table!=0);
+ if (tables_used->derived)
+ continue;
+ if (using_transactions &&
+ (tables_used->table->file->table_cache_type() ==
+ HA_CACHE_TBL_TRANSACT))
+ /*
+ tables_used->table can't be 0 in transaction.
+ Only 'drop' invalidate not opened table, but 'drop'
+ force transaction finish.
+ */
+ thd->add_changed_table(tables_used->table);
+ else
+ invalidate_table(thd, tables_used);
+ }
+
+ DEBUG_SYNC(thd, "wait_after_query_cache_invalidate");
+
+ DBUG_VOID_RETURN;
+}
+
+void Query_cache::invalidate(THD *thd, CHANGED_TABLE_LIST *tables_used)
+{
+ DBUG_ENTER("Query_cache::invalidate (changed table list)");
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ for (; tables_used; tables_used= tables_used->next)
+ {
+ THD_STAGE_INFO(thd, stage_invalidating_query_cache_entries_table_list);
+ invalidate_table(thd, (uchar*) tables_used->key, tables_used->key_length);
+ DBUG_PRINT("qcache", ("db: %s table: %s", tables_used->key,
+ tables_used->key+
+ strlen(tables_used->key)+1));
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Invalidate locked for write
+
+ SYNOPSIS
+ Query_cache::invalidate_locked_for_write()
+ tables_used - table list
+
+ NOTE
+ can be used only for opened tables
+*/
+void Query_cache::invalidate_locked_for_write(THD *thd,
+ TABLE_LIST *tables_used)
+{
+ DBUG_ENTER("Query_cache::invalidate_locked_for_write");
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ for (; tables_used; tables_used= tables_used->next_local)
+ {
+ THD_STAGE_INFO(thd, stage_invalidating_query_cache_entries_table);
+ if (tables_used->lock_type >= TL_WRITE_ALLOW_WRITE &&
+ tables_used->table)
+ {
+ invalidate_table(thd, tables_used->table);
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Remove all cached queries that uses the given table
+*/
+
+void Query_cache::invalidate(THD *thd, TABLE *table,
+ my_bool using_transactions)
+{
+ DBUG_ENTER("Query_cache::invalidate (table)");
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ using_transactions= using_transactions && thd->in_multi_stmt_transaction_mode();
+ if (using_transactions &&
+ (table->file->table_cache_type() == HA_CACHE_TBL_TRANSACT))
+ thd->add_changed_table(table);
+ else
+ invalidate_table(thd, table);
+
+
+ DBUG_VOID_RETURN;
+}
+
+void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length,
+ my_bool using_transactions)
+{
+ DBUG_ENTER("Query_cache::invalidate (key)");
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ using_transactions= using_transactions && thd->in_multi_stmt_transaction_mode();
+ if (using_transactions) // used for innodb => has_transactions() is TRUE
+ thd->add_changed_table(key, key_length);
+ else
+ invalidate_table(thd, (uchar*)key, key_length);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Remove all cached queries that uses the given database.
+*/
+
+void Query_cache::invalidate(THD *thd, char *db)
+{
+ DBUG_ENTER("Query_cache::invalidate (db)");
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ DBUG_ASSERT(ok_for_lower_case_names(db));
+
+ bool restart= FALSE;
+ /*
+ Lock the query cache and queue all invalidation attempts to avoid
+ the risk of a race between invalidation, cache inserts and flushes.
+ */
+ lock(thd);
+
+ if (query_cache_size > 0)
+ {
+ if (tables_blocks)
+ {
+ Query_cache_block *table_block = tables_blocks;
+ do {
+ restart= FALSE;
+ do
+ {
+ Query_cache_block *next= table_block->next;
+ Query_cache_table *table = table_block->table();
+ if (strcmp(table->db(),db) == 0)
+ {
+ Query_cache_block_table *list_root= table_block->table(0);
+ invalidate_query_block_list(thd,list_root);
+ }
+
+ table_block= next;
+
+ /*
+ If our root node to used tables became null then the last element
+ in the table list was removed when a query was invalidated;
+ Terminate the search.
+ */
+ if (tables_blocks == 0)
+ {
+ table_block= tables_blocks;
+ }
+ /*
+ If the iterated list has changed underlying structure;
+ we need to restart the search.
+ */
+ else if (table_block->type == Query_cache_block::FREE)
+ {
+ restart= TRUE;
+ table_block= tables_blocks;
+ }
+ /*
+ The used tables are linked in a circular list;
+ loop until we return to the begining.
+ */
+ } while (table_block != tables_blocks);
+ /*
+ Invalidating a table will also mean that all cached queries using
+ this table also will be invalidated. This will in turn change the
+ list of tables associated with these queries and the linked list of
+ used table will be changed. Because of this we might need to restart
+ the search when a table has been invalidated.
+ */
+ } while (restart);
+ } // end if( tables_blocks )
+ }
+ unlock();
+
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::invalidate_by_MyISAM_filename(const char *filename)
+{
+ DBUG_ENTER("Query_cache::invalidate_by_MyISAM_filename");
+
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ /* Calculate the key outside the lock to make the lock shorter */
+ char key[MAX_DBKEY_LENGTH];
+ uint32 db_length;
+ uint key_length= filename_2_table_key(key, filename, &db_length);
+ THD *thd= current_thd;
+ invalidate_table(thd,(uchar *)key, key_length);
+ DBUG_VOID_RETURN;
+}
+
+ /* Remove all queries from cache */
+
+void Query_cache::flush()
+{
+ DBUG_ENTER("Query_cache::flush");
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ QC_DEBUG_SYNC("wait_in_query_cache_flush1");
+
+ lock_and_suspend();
+ if (query_cache_size > 0)
+ {
+ DUMP(this);
+ flush_cache();
+ DUMP(this);
+ }
+
+ DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
+ unlock();
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Rearrange the memory blocks and join result in cache in 1 block (if
+ result length > join_limit)
+
+ @param[in] join_limit If the minimum length of a result block to be joined.
+ @param[in] iteration_limit The maximum number of packing and joining
+ sequences.
+
+*/
+
+void Query_cache::pack(THD *thd, ulong join_limit, uint iteration_limit)
+{
+ DBUG_ENTER("Query_cache::pack");
+
+ if (is_disabled())
+ DBUG_VOID_RETURN;
+
+ /*
+ If the entire qc is being invalidated we can bail out early
+ instead of waiting for the lock.
+ */
+ if (try_lock(thd, Query_cache::WAIT))
+ DBUG_VOID_RETURN;
+
+ if (query_cache_size == 0)
+ {
+ unlock();
+ DBUG_VOID_RETURN;
+ }
+
+ uint i = 0;
+ do
+ {
+ pack_cache();
+ } while ((++i < iteration_limit) && join_results(join_limit));
+
+ unlock();
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::destroy()
+{
+ DBUG_ENTER("Query_cache::destroy");
+ if (!initialized)
+ {
+ DBUG_PRINT("qcache", ("Query Cache not initialized"));
+ }
+ else
+ {
+ /* Underlying code expects the lock. */
+ lock_and_suspend();
+ free_cache();
+ unlock();
+
+ mysql_cond_destroy(&COND_cache_status_changed);
+ mysql_mutex_destroy(&structure_guard_mutex);
+ initialized = 0;
+ DBUG_ASSERT(m_requests_in_progress == 0);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::disable_query_cache(THD *thd)
+{
+ m_cache_status= DISABLE_REQUEST;
+ /*
+ If there is no requests in progress try to free buffer.
+ try_lock(TRY) will exit immediately if there is lock.
+ unlock() should free block.
+ */
+ if (m_requests_in_progress == 0 && !try_lock(thd, TRY))
+ unlock();
+}
+
+
+/*****************************************************************************
+ init/destroy
+*****************************************************************************/
+
+void Query_cache::init()
+{
+ DBUG_ENTER("Query_cache::init");
+ mysql_mutex_init(key_structure_guard_mutex,
+ &structure_guard_mutex, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_cache_status_changed,
+ &COND_cache_status_changed, NULL);
+ m_cache_lock_status= Query_cache::UNLOCKED;
+ m_cache_status= Query_cache::OK;
+ m_requests_in_progress= 0;
+ initialized = 1;
+ /*
+ Using state_map from latin1 should be fine in all cases:
+ 1. We do not support UCS2, UTF16, UTF32 as a client character set.
+ 2. The other character sets are compatible on the lower ASCII-range
+ 0x00-0x20, and have the following characters marked as spaces:
+
+ 0x09 TAB
+ 0x0A LINE FEED
+ 0x0B VERTICAL TAB
+ 0x0C FORM FEED
+ 0x0D CARRIAGE RETUR
+ 0x20 SPACE
+
+ Additionally, only some of the ASCII-compatible character sets
+ (including latin1) can have 0xA0 mapped to "NON-BREAK SPACE"
+ and thus marked as space.
+ That should not be a problem for those charsets that map 0xA0
+ to something else: the parser will just return syntax error
+ if this character appears straight in the query
+ (i.e. not inside a string literal or comment).
+ */
+ query_state_map= my_charset_latin1.state_map;
+ /*
+ If we explicitly turn off query cache from the command line query
+ cache will be disabled for the reminder of the server life
+ time. This is because we want to avoid locking the QC specific
+ mutex if query cache isn't going to be used.
+ */
+ if (global_system_variables.query_cache_type == 0)
+ {
+ free_cache();
+ m_cache_status= DISABLED;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+ulong Query_cache::init_cache()
+{
+ uint mem_bin_count, num, step;
+ ulong mem_bin_size, prev_size, inc;
+ ulong additional_data_size, max_mem_bin_size, approx_additional_data_size;
+ int align;
+
+ DBUG_ENTER("Query_cache::init_cache");
+
+ approx_additional_data_size = (sizeof(Query_cache) +
+ sizeof(uchar*)*(def_query_hash_size+
+ def_table_hash_size));
+ if (query_cache_size < approx_additional_data_size)
+ goto err;
+
+ query_cache_size-= approx_additional_data_size;
+ align= query_cache_size % ALIGN_SIZE(1);
+ if (align)
+ {
+ query_cache_size-= align;
+ approx_additional_data_size+= align;
+ }
+
+ /*
+ Count memory bins number.
+ Check section 6. in start comment for the used algorithm.
+ */
+
+ max_mem_bin_size = query_cache_size >> QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2;
+ mem_bin_count = (uint) ((1 + QUERY_CACHE_MEM_BIN_PARTS_INC) *
+ QUERY_CACHE_MEM_BIN_PARTS_MUL);
+ mem_bin_num = 1;
+ mem_bin_steps = 1;
+ mem_bin_size = max_mem_bin_size >> QUERY_CACHE_MEM_BIN_STEP_PWR2;
+ prev_size = 0;
+ if (mem_bin_size <= min_allocation_unit)
+ {
+ DBUG_PRINT("qcache", ("too small query cache => query cache disabled"));
+ // TODO here (and above) should be warning in 4.1
+ goto err;
+ }
+ while (mem_bin_size > min_allocation_unit)
+ {
+ mem_bin_num += mem_bin_count;
+ prev_size = mem_bin_size;
+ mem_bin_size >>= QUERY_CACHE_MEM_BIN_STEP_PWR2;
+ mem_bin_steps++;
+ mem_bin_count += QUERY_CACHE_MEM_BIN_PARTS_INC;
+ mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL);
+
+ // Prevent too small bins spacing
+ if (mem_bin_count > (mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2))
+ mem_bin_count= (mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2);
+ }
+ inc = (prev_size - mem_bin_size) / mem_bin_count;
+ mem_bin_num += (mem_bin_count - (min_allocation_unit - mem_bin_size)/inc);
+ mem_bin_steps++;
+ additional_data_size = ((mem_bin_num+1) *
+ ALIGN_SIZE(sizeof(Query_cache_memory_bin))+
+ (mem_bin_steps *
+ ALIGN_SIZE(sizeof(Query_cache_memory_bin_step))));
+
+ if (query_cache_size < additional_data_size)
+ goto err;
+ query_cache_size -= additional_data_size;
+
+ if (!(cache= (uchar *)
+ my_malloc_lock(query_cache_size+additional_data_size, MYF(0))))
+ goto err;
+
+ DBUG_PRINT("qcache", ("cache length %lu, min unit %lu, %u bins",
+ query_cache_size, min_allocation_unit, mem_bin_num));
+
+ steps = (Query_cache_memory_bin_step *) cache;
+ bins = ((Query_cache_memory_bin *)
+ (cache + mem_bin_steps *
+ ALIGN_SIZE(sizeof(Query_cache_memory_bin_step))));
+
+ first_block = (Query_cache_block *) (cache + additional_data_size);
+ first_block->init(query_cache_size);
+ total_blocks++;
+ first_block->pnext=first_block->pprev=first_block;
+ first_block->next=first_block->prev=first_block;
+
+ /* Prepare bins */
+
+ bins[0].init(max_mem_bin_size);
+ steps[0].init(max_mem_bin_size,0,0);
+ mem_bin_count = (uint) ((1 + QUERY_CACHE_MEM_BIN_PARTS_INC) *
+ QUERY_CACHE_MEM_BIN_PARTS_MUL);
+ num= step= 1;
+ mem_bin_size = max_mem_bin_size >> QUERY_CACHE_MEM_BIN_STEP_PWR2;
+ while (mem_bin_size > min_allocation_unit)
+ {
+ ulong incr = (steps[step-1].size - mem_bin_size) / mem_bin_count;
+ unsigned long size = mem_bin_size;
+ for (uint i= mem_bin_count; i > 0; i--)
+ {
+ bins[num+i-1].init(size);
+ size += incr;
+ }
+ num += mem_bin_count;
+ steps[step].init(mem_bin_size, num-1, incr);
+ mem_bin_size >>= QUERY_CACHE_MEM_BIN_STEP_PWR2;
+ step++;
+ mem_bin_count += QUERY_CACHE_MEM_BIN_PARTS_INC;
+ mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL);
+ if (mem_bin_count > (mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2))
+ mem_bin_count=(mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2);
+ }
+ inc = (steps[step-1].size - mem_bin_size) / mem_bin_count;
+
+ /*
+ num + mem_bin_count > mem_bin_num, but index never be > mem_bin_num
+ because block with size < min_allocated_unit never will be requested
+ */
+
+ steps[step].init(mem_bin_size, num + mem_bin_count - 1, inc);
+ {
+ uint skiped = (min_allocation_unit - mem_bin_size)/inc;
+ ulong size = mem_bin_size + inc*skiped;
+ uint i = mem_bin_count - skiped;
+ while (i-- > 0)
+ {
+ bins[num+i].init(size);
+ size += inc;
+ }
+ }
+ bins[mem_bin_num].number = 1; // For easy end test in get_free_block
+ free_memory = free_memory_blocks = 0;
+ insert_into_free_memory_list(first_block);
+
+ DUMP(this);
+
+ (void) my_hash_init(&queries, &my_charset_bin, def_query_hash_size, 0, 0,
+ query_cache_query_get_key, 0, 0);
+#ifndef FN_NO_CASE_SENSE
+ /*
+ If lower_case_table_names!=0 then db and table names are already
+ converted to lower case and we can use binary collation for their
+ comparison (no matter if file system case sensitive or not).
+ If we have case-sensitive file system (like on most Unixes) and
+ lower_case_table_names == 0 then we should distinguish my_table
+ and MY_TABLE cases and so again can use binary collation.
+ */
+ (void) my_hash_init(&tables, &my_charset_bin, def_table_hash_size, 0, 0,
+ query_cache_table_get_key, 0, 0);
+#else
+ /*
+ On windows, OS/2, MacOS X with HFS+ or any other case insensitive
+ file system if lower_case_table_names!=0 we have same situation as
+ in previous case, but if lower_case_table_names==0 then we should
+ not distinguish cases (to be compatible in behavior with underlying
+ file system) and so should use case insensitive collation for
+ comparison.
+ */
+ (void) my_hash_init(&tables,
+ lower_case_table_names ? &my_charset_bin :
+ files_charset_info,
+ def_table_hash_size, 0, 0,query_cache_table_get_key,
+ 0, 0);
+#endif
+
+ queries_in_cache = 0;
+ queries_blocks = 0;
+ DBUG_RETURN(query_cache_size +
+ additional_data_size + approx_additional_data_size);
+
+err:
+ make_disabled();
+ DBUG_RETURN(0);
+}
+
+
+/* Disable the use of the query cache */
+
+void Query_cache::make_disabled()
+{
+ DBUG_ENTER("Query_cache::make_disabled");
+ query_cache_size= 0;
+ queries_blocks= 0;
+ free_memory= 0;
+ free_memory_blocks= 0;
+ bins= 0;
+ steps= 0;
+ cache= 0;
+ mem_bin_num= mem_bin_steps= 0;
+ queries_in_cache= 0;
+ first_block= 0;
+ total_blocks= 0;
+ tables_blocks= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @class Query_cache
+ Free all resources allocated by the cache.
+
+ This function frees all resources allocated by the cache. You
+ have to call init_cache() before using the cache again. This function
+ requires the structure_guard_mutex to be locked.
+*/
+
+void Query_cache::free_cache()
+{
+ DBUG_ENTER("Query_cache::free_cache");
+
+ /* Destroy locks */
+ Query_cache_block *block= queries_blocks;
+ if (block)
+ {
+ do
+ {
+ Query_cache_query *query= block->query();
+ mysql_rwlock_destroy(&query->lock);
+ block= block->next;
+ } while (block != queries_blocks);
+ }
+
+ my_free(cache);
+ make_disabled();
+ my_hash_free(&queries);
+ my_hash_free(&tables);
+ DBUG_VOID_RETURN;
+}
+
+/*****************************************************************************
+ Free block data
+*****************************************************************************/
+
+
+/**
+ Flush the cache.
+
+ This function will flush cache contents. It assumes we have
+ 'structure_guard_mutex' locked. The function sets the m_cache_status flag and
+ releases the lock, so other threads may proceed skipping the cache as if it
+ is disabled. Concurrent flushes are performed in turn.
+ After flush_cache() call, the cache is flushed, all the freed memory is
+ accumulated in bin[0], and the 'structure_guard_mutex' is locked. However,
+ since we could release the mutex during execution, the rest of the cache
+ state could have been changed, and should not be relied on.
+*/
+
+void Query_cache::flush_cache()
+{
+ QC_DEBUG_SYNC("wait_in_query_cache_flush2");
+
+ my_hash_reset(&queries);
+ while (queries_blocks != 0)
+ {
+ BLOCK_LOCK_WR(queries_blocks);
+ free_query_internal(queries_blocks);
+ }
+}
+
+/*
+ Free oldest query that is not in use by another thread.
+ Returns 1 if we couldn't remove anything
+*/
+
+my_bool Query_cache::free_old_query()
+{
+ DBUG_ENTER("Query_cache::free_old_query");
+ if (queries_blocks)
+ {
+ /*
+ try_lock_writing used to prevent client because here lock
+ sequence is breached.
+ Also we don't need remove locked queries at this point.
+ */
+ Query_cache_block *query_block= 0;
+ if (queries_blocks != 0)
+ {
+ Query_cache_block *block = queries_blocks;
+ /* Search until we find first query that we can remove */
+ do
+ {
+ Query_cache_query *header = block->query();
+ if (header->result() != 0 &&
+ header->result()->type == Query_cache_block::RESULT &&
+ block->query()->try_lock_writing())
+ {
+ query_block = block;
+ break;
+ }
+ } while ((block=block->next) != queries_blocks );
+ }
+
+ if (query_block != 0)
+ {
+ free_query(query_block);
+ lowmem_prunes++;
+ DBUG_RETURN(0);
+ }
+ }
+ DBUG_RETURN(1); // Nothing to remove
+}
+
+
+/*
+ free_query_internal() - free query from query cache.
+
+ SYNOPSIS
+ free_query_internal()
+ query_block Query_cache_block representing the query
+
+ DESCRIPTION
+ This function will remove the query from a cache, and place its
+ memory blocks to the list of free blocks. 'query_block' must be
+ locked for writing, this function will release (and destroy) this
+ lock.
+
+ NOTE
+ 'query_block' should be removed from 'queries' hash _before_
+ calling this method, as the lock will be destroyed here.
+*/
+
+void Query_cache::free_query_internal(Query_cache_block *query_block)
+{
+ DBUG_ENTER("Query_cache::free_query_internal");
+ DBUG_PRINT("qcache", ("free query 0x%lx %lu bytes result",
+ (ulong) query_block,
+ query_block->query()->length() ));
+
+ queries_in_cache--;
+
+ Query_cache_query *query= query_block->query();
+
+ if (query->writer() != 0)
+ {
+ /* Tell MySQL that this query should not be cached anymore */
+ query->writer()->first_query_block= NULL;
+ query->writer(0);
+ }
+ double_linked_list_exclude(query_block, &queries_blocks);
+ Query_cache_block_table *table= query_block->table(0);
+
+ for (TABLE_COUNTER_TYPE i= 0; i < query_block->n_tables; i++)
+ unlink_table(table++);
+ Query_cache_block *result_block= query->result();
+
+ /*
+ The following is true when query destruction was called and no results
+ in query . (query just registered and then abort/pack/flush called)
+ */
+ if (result_block != 0)
+ {
+ if (result_block->type != Query_cache_block::RESULT)
+ {
+ // removing unfinished query
+ refused++;
+ inserts--;
+ }
+ Query_cache_block *block= result_block;
+ do
+ {
+ Query_cache_block *current= block;
+ block= block->next;
+ free_memory_block(current);
+ } while (block != result_block);
+ }
+ else
+ {
+ // removing unfinished query
+ refused++;
+ inserts--;
+ }
+
+ query->unlock_n_destroy();
+ free_memory_block(query_block);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ free_query() - free query from query cache.
+
+ SYNOPSIS
+ free_query()
+ query_block Query_cache_block representing the query
+
+ DESCRIPTION
+ This function will remove 'query_block' from 'queries' hash, and
+ then call free_query_internal(), which see.
+*/
+
+void Query_cache::free_query(Query_cache_block *query_block)
+{
+ DBUG_ENTER("Query_cache::free_query");
+ DBUG_PRINT("qcache", ("free query 0x%lx %lu bytes result",
+ (ulong) query_block,
+ query_block->query()->length() ));
+
+ my_hash_delete(&queries,(uchar *) query_block);
+ free_query_internal(query_block);
+
+ DBUG_VOID_RETURN;
+}
+
+/*****************************************************************************
+ Query data creation
+*****************************************************************************/
+
+Query_cache_block *
+Query_cache::write_block_data(ulong data_len, uchar* data,
+ ulong header_len,
+ Query_cache_block::block_type type,
+ TABLE_COUNTER_TYPE ntab)
+{
+ ulong all_headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
+ ALIGN_SIZE(ntab*sizeof(Query_cache_block_table)) +
+ header_len);
+ ulong len = data_len + all_headers_len;
+ ulong align_len= ALIGN_SIZE(len);
+ DBUG_ENTER("Query_cache::write_block_data");
+ DBUG_PRINT("qcache", ("data: %ld, header: %ld, all header: %ld",
+ data_len, header_len, all_headers_len));
+ Query_cache_block *block= allocate_block(MY_MAX(align_len,
+ min_allocation_unit),1, 0);
+ if (block != 0)
+ {
+ block->type = type;
+ block->n_tables = ntab;
+ block->used = len;
+
+ memcpy((uchar *) block+ all_headers_len, data, data_len);
+ }
+ DBUG_RETURN(block);
+}
+
+
+my_bool
+Query_cache::append_result_data(Query_cache_block **current_block,
+ ulong data_len, uchar* data,
+ Query_cache_block *query_block)
+{
+ DBUG_ENTER("Query_cache::append_result_data");
+ DBUG_PRINT("qcache", ("append %lu bytes to 0x%lx query",
+ data_len, (long) query_block));
+
+ if (query_block->query()->add(data_len) > query_cache_limit)
+ {
+ DBUG_PRINT("qcache", ("size limit reached %lu > %lu",
+ query_block->query()->length(),
+ query_cache_limit));
+ DBUG_RETURN(0);
+ }
+ if (*current_block == 0)
+ {
+ DBUG_PRINT("qcache", ("allocated first result data block %lu", data_len));
+ DBUG_RETURN(write_result_data(current_block, data_len, data, query_block,
+ Query_cache_block::RES_BEG));
+ }
+ Query_cache_block *last_block = (*current_block)->prev;
+
+ DBUG_PRINT("qcache", ("lastblock 0x%lx len %lu used %lu",
+ (ulong) last_block, last_block->length,
+ last_block->used));
+ my_bool success = 1;
+ ulong last_block_free_space= last_block->length - last_block->used;
+
+ /*
+ We will first allocate and write the 'tail' of data, that doesn't fit
+ in the 'last_block'. Only if this succeeds, we will fill the last_block.
+ This saves us a memcpy if the query doesn't fit in the query cache.
+ */
+
+ // Try join blocks if physically next block is free...
+ ulong tail = data_len - last_block_free_space;
+ ulong append_min = get_min_append_result_data_size();
+ if (last_block_free_space < data_len &&
+ append_next_free_block(last_block,
+ MY_MAX(tail, append_min)))
+ last_block_free_space = last_block->length - last_block->used;
+ // If no space in last block (even after join) allocate new block
+ if (last_block_free_space < data_len)
+ {
+ DBUG_PRINT("qcache", ("allocate new block for %lu bytes",
+ data_len-last_block_free_space));
+ Query_cache_block *new_block = 0;
+ success = write_result_data(&new_block, data_len-last_block_free_space,
+ (uchar*)(((uchar*)data)+last_block_free_space),
+ query_block,
+ Query_cache_block::RES_CONT);
+ /*
+ new_block may be != 0 even !success (if write_result_data
+ allocate a small block but failed to allocate continue)
+ */
+ if (new_block != 0)
+ double_linked_list_join(last_block, new_block);
+ }
+ else
+ {
+ // It is success (nobody can prevent us write data)
+ unlock();
+ }
+
+ // Now finally write data to the last block
+ if (success && last_block_free_space > 0)
+ {
+ ulong to_copy = MY_MIN(data_len,last_block_free_space);
+ DBUG_PRINT("qcache", ("use free space %lub at block 0x%lx to copy %lub",
+ last_block_free_space, (ulong)last_block, to_copy));
+ memcpy((uchar*) last_block + last_block->used, data, to_copy);
+ last_block->used+=to_copy;
+ }
+ DBUG_RETURN(success);
+}
+
+
+my_bool Query_cache::write_result_data(Query_cache_block **result_block,
+ ulong data_len, uchar* data,
+ Query_cache_block *query_block,
+ Query_cache_block::block_type type)
+{
+ DBUG_ENTER("Query_cache::write_result_data");
+ DBUG_PRINT("qcache", ("data_len %lu",data_len));
+
+ /*
+ Reserve block(s) for filling
+ During data allocation we must have structure_guard_mutex locked.
+ As data copy is not a fast operation, it's better if we don't have
+ structure_guard_mutex locked during data coping.
+ Thus we first allocate space and lock query, then unlock
+ structure_guard_mutex and copy data.
+ */
+
+ my_bool success = allocate_data_chain(result_block, data_len, query_block,
+ type == Query_cache_block::RES_BEG);
+ if (success)
+ {
+ // It is success (nobody can prevent us write data)
+ unlock();
+ uint headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
+ ALIGN_SIZE(sizeof(Query_cache_result)));
+#ifndef EMBEDDED_LIBRARY
+ Query_cache_block *block= *result_block;
+ uchar *rest= data;
+ // Now fill list of blocks that created by allocate_data_chain
+ do
+ {
+ block->type = type;
+ ulong length = block->used - headers_len;
+ DBUG_PRINT("qcache", ("write %lu byte in block 0x%lx",length,
+ (ulong)block));
+ memcpy((uchar*) block+headers_len, rest, length);
+ rest += length;
+ block = block->next;
+ type = Query_cache_block::RES_CONT;
+ } while (block != *result_block);
+#else
+ /*
+ Set type of first block, emb_store_querycache_result() will handle
+ the others.
+ */
+ (*result_block)->type= type;
+ Querycache_stream qs(*result_block, headers_len);
+ emb_store_querycache_result(&qs, (THD*)data);
+#endif /*!EMBEDDED_LIBRARY*/
+ }
+ else
+ {
+ if (*result_block != 0)
+ {
+ // Destroy list of blocks that was created & locked by lock_result_data
+ Query_cache_block *block = *result_block;
+ do
+ {
+ Query_cache_block *current = block;
+ block = block->next;
+ free_memory_block(current);
+ } while (block != *result_block);
+ *result_block = 0;
+ /*
+ It is not success => not unlock structure_guard_mutex (we need it to
+ free query)
+ */
+ }
+ }
+ DBUG_PRINT("qcache", ("success %d", (int) success));
+ DBUG_RETURN(success);
+}
+
+inline ulong Query_cache::get_min_first_result_data_size()
+{
+ if (queries_in_cache < QUERY_CACHE_MIN_ESTIMATED_QUERIES_NUMBER)
+ return min_result_data_size;
+ ulong avg_result = (query_cache_size - free_memory) / queries_in_cache;
+ avg_result = MY_MIN(avg_result, query_cache_limit);
+ return MY_MAX(min_result_data_size, avg_result);
+}
+
+inline ulong Query_cache::get_min_append_result_data_size()
+{
+ return min_result_data_size;
+}
+
+/*
+ Allocate one or more blocks to hold data
+*/
+my_bool Query_cache::allocate_data_chain(Query_cache_block **result_block,
+ ulong data_len,
+ Query_cache_block *query_block,
+ my_bool first_block_arg)
+{
+ ulong all_headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
+ ALIGN_SIZE(sizeof(Query_cache_result)));
+ ulong min_size = (first_block_arg ?
+ get_min_first_result_data_size():
+ get_min_append_result_data_size());
+ Query_cache_block *prev_block= NULL;
+ Query_cache_block *new_block;
+ DBUG_ENTER("Query_cache::allocate_data_chain");
+ DBUG_PRINT("qcache", ("data_len %lu, all_headers_len %lu",
+ data_len, all_headers_len));
+
+ do
+ {
+ ulong len= data_len + all_headers_len;
+ ulong align_len= ALIGN_SIZE(len);
+
+ if (!(new_block= allocate_block(MY_MAX(min_size, align_len),
+ min_result_data_size == 0,
+ all_headers_len + min_result_data_size)))
+ {
+ DBUG_PRINT("warning", ("Can't allocate block for results"));
+ DBUG_RETURN(FALSE);
+ }
+
+ new_block->n_tables = 0;
+ new_block->used = MY_MIN(len, new_block->length);
+ new_block->type = Query_cache_block::RES_INCOMPLETE;
+ new_block->next = new_block->prev = new_block;
+ Query_cache_result *header = new_block->result();
+ header->parent(query_block);
+
+ DBUG_PRINT("qcache", ("Block len %lu used %lu",
+ new_block->length, new_block->used));
+
+ if (prev_block)
+ double_linked_list_join(prev_block, new_block);
+ else
+ *result_block= new_block;
+ if (new_block->length >= len)
+ break;
+
+ /*
+ We got less memory then we need (no big memory blocks) =>
+ Continue to allocated more blocks until we got everything we need.
+ */
+ data_len= len - new_block->length;
+ prev_block= new_block;
+ } while (1);
+
+ DBUG_RETURN(TRUE);
+}
+
+/*****************************************************************************
+ Tables management
+*****************************************************************************/
+
+/*
+ Invalidate the first table in the table_list
+*/
+
+void Query_cache::invalidate_table(THD *thd, TABLE_LIST *table_list)
+{
+ if (table_list->table != 0)
+ invalidate_table(thd, table_list->table); // Table is open
+ else
+ {
+ const char *key;
+ uint key_length;
+ key_length= get_table_def_key(table_list, &key);
+
+ // We don't store temporary tables => no key_length+=4 ...
+ invalidate_table(thd, (uchar *)key, key_length);
+ }
+}
+
+void Query_cache::invalidate_table(THD *thd, TABLE *table)
+{
+ invalidate_table(thd, (uchar*) table->s->table_cache_key.str,
+ table->s->table_cache_key.length);
+}
+
+void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length)
+{
+ DEBUG_SYNC(thd, "wait_in_query_cache_invalidate1");
+
+ /*
+ Lock the query cache and queue all invalidation attempts to avoid
+ the risk of a race between invalidation, cache inserts and flushes.
+ */
+ lock(thd);
+
+ DEBUG_SYNC(thd, "wait_in_query_cache_invalidate2");
+
+ if (query_cache_size > 0)
+ invalidate_table_internal(thd, key, key_length);
+
+ unlock();
+}
+
+
+/**
+ Try to locate and invalidate a table by name.
+ The caller must ensure that no other thread is trying to work with
+ the query cache when this function is executed.
+
+ @pre structure_guard_mutex is acquired or LOCKED is set.
+*/
+
+void
+Query_cache::invalidate_table_internal(THD *thd, uchar *key, uint32 key_length)
+{
+ Query_cache_block *table_block=
+ (Query_cache_block*)my_hash_search(&tables, key, key_length);
+ if (table_block)
+ {
+ Query_cache_block_table *list_root= table_block->table(0);
+ invalidate_query_block_list(thd, list_root);
+ }
+}
+
+/**
+ Invalidate a linked list of query cache blocks.
+
+ Each block tries to acquire a block level lock before
+ free_query is a called. This function will in turn affect
+ related table- and result-blocks.
+
+ @param[in,out] thd Thread context.
+ @param[in,out] list_root A pointer to a circular list of query blocks.
+
+*/
+
+void
+Query_cache::invalidate_query_block_list(THD *thd,
+ Query_cache_block_table *list_root)
+{
+ while (list_root->next != list_root)
+ {
+ Query_cache_block *query_block= list_root->next->block();
+ BLOCK_LOCK_WR(query_block);
+ free_query(query_block);
+ }
+}
+
+/*
+ Register given table list begining with given position in tables table of
+ block
+
+ SYNOPSIS
+ Query_cache::register_tables_from_list
+ thd thread handle
+ tables_used given table list
+ counter number current position in table of tables of block
+ block_table pointer to current position in tables table of block
+
+ RETURN
+ 0 error
+ number of next position of table entry in table of tables of block
+*/
+
+TABLE_COUNTER_TYPE
+Query_cache::register_tables_from_list(THD *thd, TABLE_LIST *tables_used,
+ TABLE_COUNTER_TYPE counter,
+ Query_cache_block_table **block_table)
+{
+ TABLE_COUNTER_TYPE n;
+ DBUG_ENTER("Query_cache::register_tables_from_list");
+ for (n= counter;
+ tables_used;
+ tables_used= tables_used->next_global, n++, (*block_table)++)
+ {
+ if (tables_used->is_anonymous_derived_table())
+ {
+ DBUG_PRINT("qcache", ("derived table skipped"));
+ n--;
+ (*block_table)--;
+ continue;
+ }
+ (*block_table)->n= n;
+ if (tables_used->view)
+ {
+ const char *key;
+ uint key_length;
+ DBUG_PRINT("qcache", ("view: %s db: %s",
+ tables_used->view_name.str,
+ tables_used->view_db.str));
+ key_length= get_table_def_key(tables_used, &key);
+ /*
+ There are not callback function for for VIEWs
+ */
+ if (!insert_table(key_length, key, (*block_table),
+ tables_used->view_db.length, 0,
+ HA_CACHE_TBL_NONTRANSACT, 0, 0, TRUE))
+ DBUG_RETURN(0);
+ /*
+ We do not need to register view tables here because they are already
+ present in the global list.
+ */
+ }
+ else
+ {
+ DBUG_PRINT("qcache",
+ ("table: %s db: %s openinfo: 0x%lx keylen: %lu key: 0x%lx",
+ tables_used->table->s->table_name.str,
+ tables_used->table->s->table_cache_key.str,
+ (ulong) tables_used->table,
+ (ulong) tables_used->table->s->table_cache_key.length,
+ (ulong) tables_used->table->s->table_cache_key.str));
+
+ if (!insert_table(tables_used->table->s->table_cache_key.length,
+ tables_used->table->s->table_cache_key.str,
+ (*block_table),
+ tables_used->db_length, 0,
+ tables_used->table->file->table_cache_type(),
+ tables_used->callback_func,
+ tables_used->engine_data,
+ TRUE))
+ DBUG_RETURN(0);
+
+ if (tables_used->table->file->
+ register_query_cache_dependant_tables(thd, this, block_table, &n))
+ DBUG_RETURN(0);
+ }
+ }
+ DBUG_RETURN(n - counter);
+}
+
+/*
+ Store all used tables
+
+ SYNOPSIS
+ register_all_tables()
+ thd Thread handle
+ block Store tables in this block
+ tables_used List if used tables
+ tables_arg Not used ?
+*/
+
+my_bool Query_cache::register_all_tables(THD *thd,
+ Query_cache_block *block,
+ TABLE_LIST *tables_used,
+ TABLE_COUNTER_TYPE tables_arg)
+{
+ TABLE_COUNTER_TYPE n;
+ DBUG_PRINT("qcache", ("register tables block 0x%lx, n %d, header %x",
+ (ulong) block, (int) tables_arg,
+ (int) ALIGN_SIZE(sizeof(Query_cache_block))));
+
+ Query_cache_block_table *block_table = block->table(0);
+
+ n= register_tables_from_list(thd, tables_used, 0, &block_table);
+
+ if (n==0)
+ {
+ /* Unlink the tables we allocated above */
+ for (Query_cache_block_table *tmp = block->table(0) ;
+ tmp != block_table;
+ tmp++)
+ unlink_table(tmp);
+ if (block_table->parent)
+ unlink_table(block_table);
+ }
+ return MY_TEST(n);
+}
+
+
+/**
+ Insert used table name into the cache.
+
+ @return Error status
+ @retval FALSE On error
+ @retval TRUE On success
+*/
+
+my_bool
+Query_cache::insert_table(uint key_len, const char *key,
+ Query_cache_block_table *node,
+ uint32 db_length, uint8 suffix_length_arg,
+ uint8 cache_type,
+ qc_engine_callback callback,
+ ulonglong engine_data,
+ my_bool hash)
+{
+ DBUG_ENTER("Query_cache::insert_table");
+ DBUG_PRINT("qcache", ("insert table node 0x%lx, len %d",
+ (ulong)node, key_len));
+
+ THD *thd= current_thd;
+
+ Query_cache_block *table_block=
+ (hash ?
+ (Query_cache_block *) my_hash_search(&tables, (uchar*) key, key_len) :
+ NULL);
+
+ if (table_block &&
+ table_block->table()->engine_data() != engine_data)
+ {
+ DBUG_PRINT("qcache",
+ ("Handler require invalidation queries of %s.%s %lu-%lu",
+ table_block->table()->db(),
+ table_block->table()->table(),
+ (ulong) engine_data,
+ (ulong) table_block->table()->engine_data()));
+ /*
+ as far as we delete all queries with this table, table block will be
+ deleted, too
+ */
+ {
+ Query_cache_block_table *list_root= table_block->table(0);
+ invalidate_query_block_list(thd, list_root);
+ }
+
+ table_block= 0;
+ }
+
+ if (table_block == 0)
+ {
+ DBUG_PRINT("qcache", ("new table block from 0x%lx (%u)",
+ (ulong) key, (int) key_len));
+ table_block= write_block_data(key_len, (uchar*) key,
+ ALIGN_SIZE(sizeof(Query_cache_table)),
+ Query_cache_block::TABLE, 1);
+ if (table_block == 0)
+ {
+ DBUG_PRINT("qcache", ("Can't write table name to cache"));
+ DBUG_RETURN(0);
+ }
+ Query_cache_table *header= table_block->table();
+ double_linked_list_simple_include(table_block,
+ &tables_blocks);
+ /*
+ First node in the Query_cache_block_table-chain is the table-type
+ block. This block will only have one Query_cache_block_table (n=0).
+ */
+ Query_cache_block_table *list_root= table_block->table(0);
+ list_root->n= 0;
+
+ /*
+ The node list is circular in nature.
+ */
+ list_root->next= list_root->prev= list_root;
+
+ if (hash &&
+ my_hash_insert(&tables, (const uchar *) table_block))
+ {
+ DBUG_PRINT("qcache", ("Can't insert table to hash"));
+ // write_block_data return locked block
+ free_memory_block(table_block);
+ DBUG_RETURN(0);
+ }
+ char *db= header->db();
+ header->table(db + db_length + 1);
+ header->key_length(key_len);
+ header->suffix_length(suffix_length_arg);
+ header->type(cache_type);
+ header->callback(callback);
+ header->engine_data(engine_data);
+ header->set_hashed(hash);
+
+ /*
+ We insert this table without the assumption that it isn't refrenenced by
+ any queries.
+ */
+ header->m_cached_query_count= 0;
+ }
+
+ /*
+ Table is now in the cache; link the table_block-node associated
+ with the currently processed query into the chain of queries depending
+ on the cached table.
+ */
+ Query_cache_block_table *list_root= table_block->table(0);
+ node->next= list_root->next;
+ list_root->next= node;
+ node->next->prev= node;
+ node->prev= list_root;
+ node->parent= table_block->table();
+ /*
+ Increase the counter to keep track on how long this chain
+ of queries is.
+ */
+ Query_cache_table *table_block_data= table_block->table();
+ table_block_data->m_cached_query_count++;
+ DBUG_RETURN(1);
+}
+
+
+void Query_cache::unlink_table(Query_cache_block_table *node)
+{
+ DBUG_ENTER("Query_cache::unlink_table");
+ node->prev->next= node->next;
+ node->next->prev= node->prev;
+ Query_cache_block_table *neighbour= node->next;
+ Query_cache_table *table_block_data= node->parent;
+ table_block_data->m_cached_query_count--;
+
+ DBUG_ASSERT(table_block_data->m_cached_query_count >= 0);
+
+ if (neighbour->next == neighbour)
+ {
+ DBUG_ASSERT(table_block_data->m_cached_query_count == 0);
+ /*
+ If neighbor is root of list, the list is empty.
+ The root of the list is always a table-type block
+ which contain exactly one Query_cache_block_table
+ node object, thus we can use the block() method
+ to calculate the Query_cache_block address.
+ */
+ Query_cache_block *table_block= neighbour->block();
+ double_linked_list_exclude(table_block,
+ &tables_blocks);
+ Query_cache_table *header= table_block->table();
+ if (header->is_hashed())
+ my_hash_delete(&tables,(uchar *) table_block);
+ free_memory_block(table_block);
+ }
+ DBUG_VOID_RETURN;
+}
+
+/*****************************************************************************
+ Free memory management
+*****************************************************************************/
+
+Query_cache_block *
+Query_cache::allocate_block(ulong len, my_bool not_less, ulong min)
+{
+ DBUG_ENTER("Query_cache::allocate_block");
+ DBUG_PRINT("qcache", ("len %lu, not less %d, min %lu",
+ len, not_less,min));
+
+ if (len >= MY_MIN(query_cache_size, query_cache_limit))
+ {
+ DBUG_PRINT("qcache", ("Query cache hase only %lu memory and limit %lu",
+ query_cache_size, query_cache_limit));
+ DBUG_RETURN(0); // in any case we don't have such piece of memory
+ }
+
+ /* Free old queries until we have enough memory to store this block */
+ Query_cache_block *block;
+ do
+ {
+ block= get_free_block(len, not_less, min);
+ }
+ while (block == 0 && !free_old_query());
+
+ if (block != 0) // If we found a suitable block
+ {
+ if (block->length >= ALIGN_SIZE(len) + min_allocation_unit)
+ split_block(block,ALIGN_SIZE(len));
+ }
+
+ DBUG_RETURN(block);
+}
+
+
+Query_cache_block *
+Query_cache::get_free_block(ulong len, my_bool not_less, ulong min)
+{
+ Query_cache_block *block = 0, *first = 0;
+ DBUG_ENTER("Query_cache::get_free_block");
+ DBUG_PRINT("qcache",("length %lu, not_less %d, min %lu", len,
+ (int)not_less, min));
+
+ /* Find block with minimal size > len */
+ uint start = find_bin(len);
+ // try matching bin
+ if (bins[start].number != 0)
+ {
+ Query_cache_block *list = bins[start].free_blocks;
+ if (list->prev->length >= len) // check block with max size
+ {
+ first = list;
+ uint n = 0;
+ while ( n < QUERY_CACHE_MEM_BIN_TRY &&
+ first->length < len) //we don't need irst->next != list
+ {
+ first=first->next;
+ n++;
+ }
+ if (first->length >= len)
+ block=first;
+ else // we don't need if (first->next != list)
+ {
+ n = 0;
+ block = list->prev;
+ while (n < QUERY_CACHE_MEM_BIN_TRY &&
+ block->length > len)
+ {
+ block=block->prev;
+ n++;
+ }
+ if (block->length < len)
+ block=block->next;
+ }
+ }
+ else
+ first = list->prev;
+ }
+ if (block == 0 && start > 0)
+ {
+ DBUG_PRINT("qcache",("Try bins with bigger block size"));
+ // Try more big bins
+ int i = start - 1;
+ while (i > 0 && bins[i].number == 0)
+ i--;
+ if (bins[i].number > 0)
+ block = bins[i].free_blocks;
+ }
+
+ // If no big blocks => try less size (if it is possible)
+ if (block == 0 && ! not_less)
+ {
+ DBUG_PRINT("qcache",("Try to allocate a smaller block"));
+ if (first != 0 && first->length > min)
+ block = first;
+ else
+ {
+ uint i = start + 1;
+ /* bins[mem_bin_num].number contains 1 for easy end test */
+ for (i= start+1 ; bins[i].number == 0 ; i++) ;
+ if (i < mem_bin_num && bins[i].free_blocks->prev->length >= min)
+ block = bins[i].free_blocks->prev;
+ }
+ }
+ if (block != 0)
+ exclude_from_free_memory_list(block);
+
+ DBUG_PRINT("qcache",("getting block 0x%lx", (ulong) block));
+ DBUG_RETURN(block);
+}
+
+
+void Query_cache::free_memory_block(Query_cache_block *block)
+{
+ DBUG_ENTER("Query_cache::free_memory_block");
+ block->used=0;
+ block->type= Query_cache_block::FREE; // mark block as free in any case
+ DBUG_PRINT("qcache",
+ ("first_block 0x%lx, block 0x%lx, pnext 0x%lx pprev 0x%lx",
+ (ulong) first_block, (ulong) block, (ulong) block->pnext,
+ (ulong) block->pprev));
+
+ if (block->pnext != first_block && block->pnext->is_free())
+ block = join_free_blocks(block, block->pnext);
+ if (block != first_block && block->pprev->is_free())
+ block = join_free_blocks(block->pprev, block->pprev);
+ insert_into_free_memory_list(block);
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::split_block(Query_cache_block *block, ulong len)
+{
+ DBUG_ENTER("Query_cache::split_block");
+ Query_cache_block *new_block = (Query_cache_block*)(((uchar*) block)+len);
+
+ new_block->init(block->length - len);
+ total_blocks++;
+ block->length=len;
+ new_block->pnext = block->pnext;
+ block->pnext = new_block;
+ new_block->pprev = block;
+ new_block->pnext->pprev = new_block;
+
+ if (block->type == Query_cache_block::FREE)
+ {
+ // if block was free then it already joined with all free neighbours
+ insert_into_free_memory_list(new_block);
+ }
+ else
+ free_memory_block(new_block);
+
+ DBUG_PRINT("qcache", ("split 0x%lx (%lu) new 0x%lx",
+ (ulong) block, len, (ulong) new_block));
+ DBUG_VOID_RETURN;
+}
+
+
+Query_cache_block *
+Query_cache::join_free_blocks(Query_cache_block *first_block_arg,
+ Query_cache_block *block_in_list)
+{
+ Query_cache_block *second_block;
+ DBUG_ENTER("Query_cache::join_free_blocks");
+ DBUG_PRINT("qcache",
+ ("join first 0x%lx, pnext 0x%lx, in list 0x%lx",
+ (ulong) first_block_arg, (ulong) first_block_arg->pnext,
+ (ulong) block_in_list));
+
+ exclude_from_free_memory_list(block_in_list);
+ second_block = first_block_arg->pnext;
+ // May be was not free block
+ second_block->used=0;
+ second_block->destroy();
+ total_blocks--;
+
+ first_block_arg->length += second_block->length;
+ first_block_arg->pnext = second_block->pnext;
+ second_block->pnext->pprev = first_block_arg;
+
+ DBUG_RETURN(first_block_arg);
+}
+
+
+my_bool Query_cache::append_next_free_block(Query_cache_block *block,
+ ulong add_size)
+{
+ Query_cache_block *next_block = block->pnext;
+ DBUG_ENTER("Query_cache::append_next_free_block");
+ DBUG_PRINT("enter", ("block 0x%lx, add_size %lu", (ulong) block,
+ add_size));
+
+ if (next_block != first_block && next_block->is_free())
+ {
+ ulong old_len = block->length;
+ exclude_from_free_memory_list(next_block);
+ next_block->destroy();
+ total_blocks--;
+
+ block->length += next_block->length;
+ block->pnext = next_block->pnext;
+ next_block->pnext->pprev = block;
+
+ if (block->length > ALIGN_SIZE(old_len + add_size) + min_allocation_unit)
+ split_block(block,ALIGN_SIZE(old_len + add_size));
+ DBUG_PRINT("exit", ("block was appended"));
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+void Query_cache::exclude_from_free_memory_list(Query_cache_block *free_block)
+{
+ DBUG_ENTER("Query_cache::exclude_from_free_memory_list");
+ Query_cache_memory_bin *bin = *((Query_cache_memory_bin **)
+ free_block->data());
+ double_linked_list_exclude(free_block, &bin->free_blocks);
+ bin->number--;
+ free_memory-=free_block->length;
+ free_memory_blocks--;
+ DBUG_PRINT("qcache",("exclude block 0x%lx, bin 0x%lx", (ulong) free_block,
+ (ulong) bin));
+ DBUG_VOID_RETURN;
+}
+
+void Query_cache::insert_into_free_memory_list(Query_cache_block *free_block)
+{
+ DBUG_ENTER("Query_cache::insert_into_free_memory_list");
+ uint idx = find_bin(free_block->length);
+ insert_into_free_memory_sorted_list(free_block, &bins[idx].free_blocks);
+ /*
+ We have enough memory in block for storing bin reference due to
+ min_allocation_unit choice
+ */
+ Query_cache_memory_bin **bin_ptr = ((Query_cache_memory_bin**)
+ free_block->data());
+ *bin_ptr = bins+idx;
+ (*bin_ptr)->number++;
+ DBUG_PRINT("qcache",("insert block 0x%lx, bin[%d] 0x%lx",
+ (ulong) free_block, idx, (ulong) *bin_ptr));
+ DBUG_VOID_RETURN;
+}
+
+uint Query_cache::find_bin(ulong size)
+{
+ DBUG_ENTER("Query_cache::find_bin");
+ // Binary search
+ int left = 0, right = mem_bin_steps;
+ do
+ {
+ int middle = (left + right) / 2;
+ if (steps[middle].size > size)
+ left = middle+1;
+ else
+ right = middle;
+ } while (left < right);
+ if (left == 0)
+ {
+ // first bin not subordinate of common rules
+ DBUG_PRINT("qcache", ("first bin (# 0), size %lu",size));
+ DBUG_RETURN(0);
+ }
+ uint bin = steps[left].idx -
+ (uint)((size - steps[left].size)/steps[left].increment);
+
+ DBUG_PRINT("qcache", ("bin %u step %u, size %lu step size %lu",
+ bin, left, size, steps[left].size));
+ DBUG_RETURN(bin);
+}
+
+
+/*****************************************************************************
+ Lists management
+*****************************************************************************/
+
+void Query_cache::move_to_query_list_end(Query_cache_block *query_block)
+{
+ DBUG_ENTER("Query_cache::move_to_query_list_end");
+ double_linked_list_exclude(query_block, &queries_blocks);
+ double_linked_list_simple_include(query_block, &queries_blocks);
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::insert_into_free_memory_sorted_list(Query_cache_block *
+ new_block,
+ Query_cache_block **
+ list)
+{
+ DBUG_ENTER("Query_cache::insert_into_free_memory_sorted_list");
+ /*
+ list sorted by size in ascendant order, because we need small blocks
+ more frequently than bigger ones
+ */
+
+ new_block->used = 0;
+ new_block->n_tables = 0;
+ new_block->type = Query_cache_block::FREE;
+
+ if (*list == 0)
+ {
+ *list = new_block->next=new_block->prev=new_block;
+ DBUG_PRINT("qcache", ("inserted into empty list"));
+ }
+ else
+ {
+ Query_cache_block *point = *list;
+ if (point->length >= new_block->length)
+ {
+ point = point->prev;
+ *list = new_block;
+ }
+ else
+ {
+ /* Find right position in sorted list to put block */
+ while (point->next != *list &&
+ point->next->length < new_block->length)
+ point=point->next;
+ }
+ new_block->prev = point;
+ new_block->next = point->next;
+ new_block->next->prev = new_block;
+ point->next = new_block;
+ }
+ free_memory+=new_block->length;
+ free_memory_blocks++;
+ DBUG_VOID_RETURN;
+}
+
+
+void
+Query_cache::double_linked_list_simple_include(Query_cache_block *point,
+ Query_cache_block **
+ list_pointer)
+{
+ DBUG_ENTER("Query_cache::double_linked_list_simple_include");
+ DBUG_PRINT("qcache", ("including block 0x%lx", (ulong) point));
+ if (*list_pointer == 0)
+ *list_pointer=point->next=point->prev=point;
+ else
+ {
+ // insert to the end of list
+ point->next = (*list_pointer);
+ point->prev = (*list_pointer)->prev;
+ point->prev->next = point;
+ (*list_pointer)->prev = point;
+ }
+ DBUG_VOID_RETURN;
+}
+
+void
+Query_cache::double_linked_list_exclude(Query_cache_block *point,
+ Query_cache_block **list_pointer)
+{
+ DBUG_ENTER("Query_cache::double_linked_list_exclude");
+ DBUG_PRINT("qcache", ("excluding block 0x%lx, list 0x%lx",
+ (ulong) point, (ulong) list_pointer));
+ if (point->next == point)
+ *list_pointer = 0; // empty list
+ else
+ {
+ point->next->prev = point->prev;
+ point->prev->next = point->next;
+ /*
+ If the root is removed; select a new root
+ */
+ if (point == *list_pointer)
+ *list_pointer= point->next;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::double_linked_list_join(Query_cache_block *head_tail,
+ Query_cache_block *tail_head)
+{
+ Query_cache_block *head_head = head_tail->next,
+ *tail_tail = tail_head->prev;
+ head_head->prev = tail_tail;
+ head_tail->next = tail_head;
+ tail_head->prev = head_tail;
+ tail_tail->next = head_head;
+}
+
+/*****************************************************************************
+ Query
+*****************************************************************************/
+
+/*
+ Collect information about table types, check that tables are cachable and
+ count them
+
+ SYNOPSIS
+ process_and_count_tables()
+ tables_used table list for processing
+ tables_type pointer to variable for table types collection
+
+ RETURN
+ 0 error
+ >0 number of tables
+*/
+
+TABLE_COUNTER_TYPE
+Query_cache::process_and_count_tables(THD *thd, TABLE_LIST *tables_used,
+ uint8 *tables_type)
+{
+ DBUG_ENTER("process_and_count_tables");
+ TABLE_COUNTER_TYPE table_count = 0;
+ for (; tables_used; tables_used= tables_used->next_global)
+ {
+ table_count++;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ Disable any attempt to store this statement if there are
+ column level grants on any referenced tables.
+ The grant.want_privileges flag was set to 1 in the
+ check_grant() function earlier if the TABLE_LIST object
+ had any associated column privileges.
+
+ We need to check that the TABLE_LIST object isn't part
+ of a VIEW definition because we want to be able to cache
+ views.
+
+ TODO: Although it is possible to cache views, the privilege
+ check on view tables always fall back on column privileges
+ even if there are more generic table privileges. Thus it isn't
+ currently possible to retrieve cached view-tables unless the
+ client has the super user privileges.
+ */
+ if (tables_used->grant.want_privilege &&
+ tables_used->belong_to_view == NULL)
+ {
+ DBUG_PRINT("qcache", ("Don't cache statement as it refers to "
+ "tables with column privileges."));
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ thd->lex->safe_to_cache_query= 0; // For prepared statements
+ DBUG_RETURN(0);
+ }
+#endif
+ if (tables_used->view)
+ {
+ DBUG_PRINT("qcache", ("view: %s db: %s",
+ tables_used->view_name.str,
+ tables_used->view_db.str));
+ *tables_type|= HA_CACHE_TBL_NONTRANSACT;
+ }
+ else
+ {
+ if (tables_used->derived)
+ {
+ DBUG_PRINT("qcache", ("table: %s", tables_used->alias));
+ table_count--;
+ DBUG_PRINT("qcache", ("derived table skipped"));
+ continue;
+ }
+ DBUG_PRINT("qcache", ("table: %s db: %s type: %u",
+ tables_used->table->s->table_name.str,
+ tables_used->table->s->db.str,
+ tables_used->table->s->db_type()->db_type));
+ *tables_type|= tables_used->table->file->table_cache_type();
+
+ /*
+ table_alias_charset used here because it depends of
+ lower_case_table_names variable
+ */
+ table_count+= tables_used->table->file->
+ count_query_cache_dependant_tables(tables_type);
+
+ if (tables_used->table->s->tmp_table != NO_TMP_TABLE ||
+ (*tables_type & HA_CACHE_TBL_NOCACHE) ||
+ (tables_used->db_length == 5 &&
+ my_strnncoll(table_alias_charset,
+ (uchar*)tables_used->table->s->table_cache_key.str, 6,
+ (uchar*)"mysql",6) == 0))
+ {
+ DBUG_PRINT("qcache",
+ ("select not cacheable: temporary, system or "
+ "other non-cacheable table(s)"));
+ DBUG_RETURN(0);
+ }
+ }
+ }
+ DBUG_RETURN(table_count);
+}
+
+
+/*
+In non-embedded QC intercepts result in net_real_write
+but if we have no net.vio then net_real_write
+will not be called, so QC can't get results of the query
+*/
+#ifdef EMBEDDED_LIBRARY
+#define qc_is_able_to_intercept_result(T) 1
+#else
+#define qc_is_able_to_intercept_result(T) ((T)->net.vio)
+#endif
+
+
+/*
+ If query is cacheable return number tables in query
+ (query without tables are not cached)
+*/
+
+TABLE_COUNTER_TYPE
+Query_cache::is_cacheable(THD *thd, LEX *lex,
+ TABLE_LIST *tables_used, uint8 *tables_type)
+{
+ TABLE_COUNTER_TYPE table_count;
+ DBUG_ENTER("Query_cache::is_cacheable");
+
+ if (thd->lex->safe_to_cache_query &&
+ (thd->variables.query_cache_type == 1 ||
+ (thd->variables.query_cache_type == 2 && (lex->select_lex.options &
+ OPTION_TO_QUERY_CACHE))) &&
+ qc_is_able_to_intercept_result(thd))
+ {
+ DBUG_PRINT("qcache", ("options: %lx %lx type: %u",
+ (long) OPTION_TO_QUERY_CACHE,
+ (long) lex->select_lex.options,
+ (int) thd->variables.query_cache_type));
+
+ if (!(table_count= process_and_count_tables(thd, tables_used,
+ tables_type)))
+ DBUG_RETURN(0);
+
+ if (thd->in_multi_stmt_transaction_mode() &&
+ ((*tables_type)&HA_CACHE_TBL_TRANSACT))
+ {
+ DBUG_PRINT("qcache", ("not in autocommin mode"));
+ DBUG_RETURN(0);
+ }
+ DBUG_PRINT("qcache", ("select is using %d tables", table_count));
+ DBUG_RETURN(table_count);
+ }
+
+ DBUG_PRINT("qcache",
+ ("not interesting query: %d or not cacheable, options %lx %lx type: %u net->vio present: %u",
+ (int) lex->sql_command,
+ (long) OPTION_TO_QUERY_CACHE,
+ (long) lex->select_lex.options,
+ (int) thd->variables.query_cache_type,
+ (uint) MY_TEST(qc_is_able_to_intercept_result(thd))));
+ DBUG_RETURN(0);
+}
+
+/*
+ Check handler allowance to cache query with these tables
+
+ SYNOPSYS
+ Query_cache::ask_handler_allowance()
+ thd - thread handlers
+ tables_used - tables list used in query
+
+ RETURN
+ 0 - caching allowed
+ 1 - caching disallowed
+*/
+my_bool Query_cache::ask_handler_allowance(THD *thd,
+ TABLE_LIST *tables_used)
+{
+ DBUG_ENTER("Query_cache::ask_handler_allowance");
+
+ for (; tables_used; tables_used= tables_used->next_global)
+ {
+ TABLE *table;
+ handler *handler;
+ if (!(table= tables_used->table))
+ continue;
+ handler= table->file;
+ if (!handler->register_query_cache_table(thd,
+ table->s->normalized_path.str,
+ table->s->normalized_path.length,
+ &tables_used->callback_func,
+ &tables_used->engine_data))
+ {
+ DBUG_PRINT("qcache", ("Handler does not allow caching for %s",
+ table->s->normalized_path.str));
+ /*
+ As this can change from call to call, don't reset set
+ thd->lex->safe_to_cache_query
+ */
+ thd->query_cache_is_applicable= 0; // Query can't be cached
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*****************************************************************************
+ Packing
+*****************************************************************************/
+
+
+/**
+ Rearrange all memory blocks so that free memory joins at the
+ 'bottom' of the allocated memory block containing all cache data.
+ @see Query_cache::pack(ulong join_limit, uint iteration_limit)
+*/
+
+void Query_cache::pack_cache()
+{
+ DBUG_ENTER("Query_cache::pack_cache");
+
+ DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
+
+ uchar *border = 0;
+ Query_cache_block *before = 0;
+ ulong gap = 0;
+ my_bool ok = 1;
+ Query_cache_block *block = first_block;
+ DUMP(this);
+
+ if (first_block)
+ {
+ do
+ {
+ Query_cache_block *next=block->pnext;
+ ok = move_by_type(&border, &before, &gap, block);
+ block = next;
+ } while (ok && block != first_block);
+
+ if (border != 0)
+ {
+ Query_cache_block *new_block = (Query_cache_block *) border;
+ new_block->init(gap);
+ total_blocks++;
+ new_block->pnext = before->pnext;
+ before->pnext = new_block;
+ new_block->pprev = before;
+ new_block->pnext->pprev = new_block;
+ insert_into_free_memory_list(new_block);
+ }
+ DUMP(this);
+ }
+
+ DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
+ DBUG_VOID_RETURN;
+}
+
+
+my_bool Query_cache::move_by_type(uchar **border,
+ Query_cache_block **before, ulong *gap,
+ Query_cache_block *block)
+{
+ DBUG_ENTER("Query_cache::move_by_type");
+
+ my_bool ok = 1;
+ switch (block->type) {
+ case Query_cache_block::FREE:
+ {
+ DBUG_PRINT("qcache", ("block 0x%lx FREE", (ulong) block));
+ if (*border == 0)
+ {
+ *border = (uchar *) block;
+ *before = block->pprev;
+ DBUG_PRINT("qcache", ("gap beginning here"));
+ }
+ exclude_from_free_memory_list(block);
+ *gap +=block->length;
+ block->pprev->pnext=block->pnext;
+ block->pnext->pprev=block->pprev;
+ block->destroy();
+ total_blocks--;
+ DBUG_PRINT("qcache", ("added to gap (%lu)", *gap));
+ break;
+ }
+ case Query_cache_block::TABLE:
+ {
+ HASH_SEARCH_STATE record_idx;
+ DBUG_PRINT("qcache", ("block 0x%lx TABLE", (ulong) block));
+ if (*border == 0)
+ break;
+ ulong len = block->length, used = block->used;
+ Query_cache_block_table *list_root = block->table(0);
+ Query_cache_block_table *tprev = list_root->prev,
+ *tnext = list_root->next;
+ Query_cache_block *prev = block->prev,
+ *next = block->next,
+ *pprev = block->pprev,
+ *pnext = block->pnext,
+ *new_block =(Query_cache_block *) *border;
+ uint tablename_offset = block->table()->table() - block->table()->db();
+ char *data = (char*) block->data();
+ uchar *key;
+ size_t key_length;
+ key=query_cache_table_get_key((uchar*) block, &key_length, 0);
+ my_hash_first(&tables, (uchar*) key, key_length, &record_idx);
+
+ block->destroy();
+ new_block->init(len);
+ new_block->type=Query_cache_block::TABLE;
+ new_block->used=used;
+ new_block->n_tables=1;
+ memmove((char*) new_block->data(), data, len-new_block->headers_len());
+ relink(block, new_block, next, prev, pnext, pprev);
+ if (tables_blocks == block)
+ tables_blocks = new_block;
+
+ Query_cache_block_table *nlist_root = new_block->table(0);
+ nlist_root->n = 0;
+ nlist_root->next = tnext;
+ tnext->prev = nlist_root;
+ nlist_root->prev = tprev;
+ tprev->next = nlist_root;
+ DBUG_PRINT("qcache",
+ ("list_root: 0x%lx tnext 0x%lx tprev 0x%lx tprev->next 0x%lx tnext->prev 0x%lx",
+ (ulong) list_root, (ulong) tnext, (ulong) tprev,
+ (ulong)tprev->next, (ulong)tnext->prev));
+ /*
+ Go through all queries that uses this table and change them to
+ point to the new table object
+ */
+ Query_cache_table *new_block_table=new_block->table();
+ for (;tnext != nlist_root; tnext=tnext->next)
+ tnext->parent= new_block_table;
+ *border += len;
+ *before = new_block;
+ /* Fix pointer to table name */
+ new_block->table()->table(new_block->table()->db() + tablename_offset);
+ /* Fix hash to point at moved block */
+ my_hash_replace(&tables, &record_idx, (uchar*) new_block);
+
+ DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx",
+ len, (ulong) new_block, (ulong) *border));
+ break;
+ }
+ case Query_cache_block::QUERY:
+ {
+ HASH_SEARCH_STATE record_idx;
+ DBUG_PRINT("qcache", ("block 0x%lx QUERY", (ulong) block));
+ if (*border == 0)
+ break;
+ BLOCK_LOCK_WR(block);
+ ulong len = block->length, used = block->used;
+ TABLE_COUNTER_TYPE n_tables = block->n_tables;
+ Query_cache_block *prev = block->prev,
+ *next = block->next,
+ *pprev = block->pprev,
+ *pnext = block->pnext,
+ *new_block =(Query_cache_block*) *border;
+ char *data = (char*) block->data();
+ Query_cache_block *first_result_block = ((Query_cache_query *)
+ block->data())->result();
+ uchar *key;
+ size_t key_length;
+ key=query_cache_query_get_key((uchar*) block, &key_length, 0);
+ my_hash_first(&queries, (uchar*) key, key_length, &record_idx);
+ block->query()->unlock_n_destroy();
+ block->destroy();
+ // Move table of used tables
+ memmove((char*) new_block->table(0), (char*) block->table(0),
+ ALIGN_SIZE(n_tables*sizeof(Query_cache_block_table)));
+ new_block->init(len);
+ new_block->type=Query_cache_block::QUERY;
+ new_block->used=used;
+ new_block->n_tables=n_tables;
+ memmove((char*) new_block->data(), data, len - new_block->headers_len());
+ relink(block, new_block, next, prev, pnext, pprev);
+ if (queries_blocks == block)
+ queries_blocks = new_block;
+ Query_cache_block_table *beg_of_table_table= block->table(0),
+ *end_of_table_table= block->table(n_tables);
+ uchar *beg_of_new_table_table= (uchar*) new_block->table(0);
+
+ for (TABLE_COUNTER_TYPE j=0; j < n_tables; j++)
+ {
+ Query_cache_block_table *block_table = new_block->table(j);
+
+ // use aligment from begining of table if 'next' is in same block
+ if ((beg_of_table_table <= block_table->next) &&
+ (block_table->next < end_of_table_table))
+ ((Query_cache_block_table *)(beg_of_new_table_table +
+ (((uchar*)block_table->next) -
+ ((uchar*)beg_of_table_table))))->prev=
+ block_table;
+ else
+ block_table->next->prev= block_table;
+
+ // use aligment from begining of table if 'prev' is in same block
+ if ((beg_of_table_table <= block_table->prev) &&
+ (block_table->prev < end_of_table_table))
+ ((Query_cache_block_table *)(beg_of_new_table_table +
+ (((uchar*)block_table->prev) -
+ ((uchar*)beg_of_table_table))))->next=
+ block_table;
+ else
+ block_table->prev->next = block_table;
+ }
+ DBUG_PRINT("qcache", ("after circle tt"));
+ *border += len;
+ *before = new_block;
+ new_block->query()->result(first_result_block);
+ if (first_result_block != 0)
+ {
+ Query_cache_block *result_block = first_result_block;
+ do
+ {
+ result_block->result()->parent(new_block);
+ result_block = result_block->next;
+ } while ( result_block != first_result_block );
+ }
+ Query_cache_query *new_query= ((Query_cache_query *) new_block->data());
+ mysql_rwlock_init(key_rwlock_query_cache_query_lock, &new_query->lock);
+
+ /*
+ If someone is writing to this block, inform the writer that the block
+ has been moved.
+ */
+ Query_cache_tls *query_cache_tls= new_block->query()->writer();
+ if (query_cache_tls != NULL)
+ {
+ query_cache_tls->first_query_block= new_block;
+ }
+ /* Fix hash to point at moved block */
+ my_hash_replace(&queries, &record_idx, (uchar*) new_block);
+ DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx",
+ len, (ulong) new_block, (ulong) *border));
+ break;
+ }
+ case Query_cache_block::RES_INCOMPLETE:
+ case Query_cache_block::RES_BEG:
+ case Query_cache_block::RES_CONT:
+ case Query_cache_block::RESULT:
+ {
+ DBUG_PRINT("qcache", ("block 0x%lx RES* (%d)", (ulong) block,
+ (int) block->type));
+ if (*border == 0)
+ break;
+ Query_cache_block *query_block= block->result()->parent();
+ BLOCK_LOCK_WR(query_block);
+ Query_cache_block *next= block->next, *prev= block->prev;
+ Query_cache_block::block_type type= block->type;
+ ulong len = block->length, used = block->used;
+ Query_cache_block *pprev = block->pprev,
+ *pnext = block->pnext,
+ *new_block =(Query_cache_block*) *border;
+ char *data = (char*) block->data();
+ block->destroy();
+ new_block->init(len);
+ new_block->type=type;
+ new_block->used=used;
+ memmove((char*) new_block->data(), data, len - new_block->headers_len());
+ relink(block, new_block, next, prev, pnext, pprev);
+ new_block->result()->parent(query_block);
+ Query_cache_query *query = query_block->query();
+ if (query->result() == block)
+ query->result(new_block);
+ *border += len;
+ *before = new_block;
+ /* If result writing complete && we have free space in block */
+ ulong free_space= new_block->length - new_block->used;
+ free_space-= free_space % ALIGN_SIZE(1);
+ if (query->result()->type == Query_cache_block::RESULT &&
+ new_block->length > new_block->used &&
+ *gap + free_space > min_allocation_unit &&
+ new_block->length - free_space > min_allocation_unit)
+ {
+ *border-= free_space;
+ *gap+= free_space;
+ DBUG_PRINT("qcache",
+ ("rest of result free space added to gap (%lu)", *gap));
+ new_block->length -= free_space;
+ }
+ BLOCK_UNLOCK_WR(query_block);
+ DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx",
+ len, (ulong) new_block, (ulong) *border));
+ break;
+ }
+ default:
+ DBUG_PRINT("error", ("unexpected block type %d, block 0x%lx",
+ (int)block->type, (ulong) block));
+ ok = 0;
+ }
+ DBUG_RETURN(ok);
+}
+
+
+void Query_cache::relink(Query_cache_block *oblock,
+ Query_cache_block *nblock,
+ Query_cache_block *next, Query_cache_block *prev,
+ Query_cache_block *pnext, Query_cache_block *pprev)
+{
+ if (prev == oblock) //check pointer to himself
+ {
+ nblock->prev = nblock;
+ nblock->next = nblock;
+ }
+ else
+ {
+ nblock->prev = prev;
+ prev->next=nblock;
+ }
+ if (next != oblock)
+ {
+ nblock->next = next;
+ next->prev=nblock;
+ }
+ nblock->pprev = pprev; // Physical pointer to himself have only 1 free block
+ nblock->pnext = pnext;
+ pprev->pnext=nblock;
+ pnext->pprev=nblock;
+}
+
+
+my_bool Query_cache::join_results(ulong join_limit)
+{
+ my_bool has_moving = 0;
+ DBUG_ENTER("Query_cache::join_results");
+
+ if (queries_blocks != 0)
+ {
+ DBUG_ASSERT(query_cache_size > 0);
+ Query_cache_block *block = queries_blocks;
+ do
+ {
+ Query_cache_query *header = block->query();
+ if (header->result() != 0 &&
+ header->result()->type == Query_cache_block::RESULT &&
+ header->length() > join_limit)
+ {
+ Query_cache_block *new_result_block =
+ get_free_block(ALIGN_SIZE(header->length()) +
+ ALIGN_SIZE(sizeof(Query_cache_block)) +
+ ALIGN_SIZE(sizeof(Query_cache_result)), 1, 0);
+ if (new_result_block != 0)
+ {
+ has_moving = 1;
+ Query_cache_block *first_result = header->result();
+ ulong new_len = (header->length() +
+ ALIGN_SIZE(sizeof(Query_cache_block)) +
+ ALIGN_SIZE(sizeof(Query_cache_result)));
+ if (new_result_block->length >
+ ALIGN_SIZE(new_len) + min_allocation_unit)
+ split_block(new_result_block, ALIGN_SIZE(new_len));
+ BLOCK_LOCK_WR(block);
+ header->result(new_result_block);
+ new_result_block->type = Query_cache_block::RESULT;
+ new_result_block->n_tables = 0;
+ new_result_block->used = new_len;
+
+ new_result_block->next = new_result_block->prev = new_result_block;
+ DBUG_PRINT("qcache", ("new block %lu/%lu (%lu)",
+ new_result_block->length,
+ new_result_block->used,
+ header->length()));
+
+ Query_cache_result *new_result = new_result_block->result();
+ new_result->parent(block);
+ uchar *write_to = (uchar*) new_result->data();
+ Query_cache_block *result_block = first_result;
+ do
+ {
+ ulong len = (result_block->used - result_block->headers_len() -
+ ALIGN_SIZE(sizeof(Query_cache_result)));
+ DBUG_PRINT("loop", ("add block %lu/%lu (%lu)",
+ result_block->length,
+ result_block->used,
+ len));
+ memcpy((char *) write_to,
+ (char*) result_block->result()->data(),
+ len);
+ write_to += len;
+ Query_cache_block *old_result_block = result_block;
+ result_block = result_block->next;
+ free_memory_block(old_result_block);
+ } while (result_block != first_result);
+ BLOCK_UNLOCK_WR(block);
+ }
+ }
+ block = block->next;
+ } while ( block != queries_blocks );
+ }
+ DBUG_RETURN(has_moving);
+}
+
+
+uint Query_cache::filename_2_table_key (char *key, const char *path,
+ uint32 *db_length)
+{
+ char tablename[FN_REFLEN+2], *filename, *dbname;
+ DBUG_ENTER("Query_cache::filename_2_table_key");
+
+ /* Safety if filename didn't have a directory name */
+ tablename[0]= FN_LIBCHAR;
+ tablename[1]= FN_LIBCHAR;
+ /* Convert filename to this OS's format in tablename */
+ fn_format(tablename + 2, path, "", "", MY_REPLACE_EXT);
+ filename= tablename + dirname_length(tablename + 2) + 2;
+ /* Find start of databasename */
+ for (dbname= filename - 2 ; dbname[-1] != FN_LIBCHAR ; dbname--) ;
+ *db_length= (filename - dbname) - 1;
+ DBUG_PRINT("qcache", ("table '%-.*s.%s'", *db_length, dbname, filename));
+
+ DBUG_RETURN((uint) (strmake(strmake(key, dbname,
+ MY_MIN(*db_length, NAME_LEN)) + 1,
+ filename, NAME_LEN) - key) + 1);
+}
+
+/****************************************************************************
+ Functions to be used when debugging
+****************************************************************************/
+
+#if defined(DBUG_OFF) && !defined(USE_QUERY_CACHE_INTEGRITY_CHECK)
+
+void wreck(uint line, const char *message) { query_cache_size = 0; }
+void bins_dump() {}
+void cache_dump() {}
+void queries_dump() {}
+void tables_dump() {}
+my_bool check_integrity(bool not_locked) { return 0; }
+my_bool in_list(Query_cache_block * root, Query_cache_block * point,
+ const char *name) { return 0;}
+my_bool in_blocks(Query_cache_block * point) { return 0; }
+
+#else
+
+
+/*
+ Debug method which switch query cache off but left content for
+ investigation.
+
+ SYNOPSIS
+ Query_cache::wreck()
+ line line of the wreck() call
+ message message for logging
+*/
+
+void Query_cache::wreck(uint line, const char *message)
+{
+ THD *thd=current_thd;
+ DBUG_ENTER("Query_cache::wreck");
+ query_cache_size = 0;
+ if (*message)
+ DBUG_PRINT("error", (" %s", message));
+ DBUG_PRINT("warning", ("=================================="));
+ DBUG_PRINT("warning", ("%5d QUERY CACHE WRECK => DISABLED",line));
+ DBUG_PRINT("warning", ("=================================="));
+ if (thd)
+ thd->killed= KILL_CONNECTION;
+ cache_dump();
+ /* check_integrity(0); */ /* Can't call it here because of locks */
+ bins_dump();
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::bins_dump()
+{
+ uint i;
+
+ if (!initialized || query_cache_size == 0)
+ {
+ DBUG_PRINT("qcache", ("Query Cache not initialized"));
+ return;
+ }
+
+ DBUG_PRINT("qcache", ("mem_bin_num=%u, mem_bin_steps=%u",
+ mem_bin_num, mem_bin_steps));
+ DBUG_PRINT("qcache", ("-------------------------"));
+ DBUG_PRINT("qcache", (" size idx step"));
+ DBUG_PRINT("qcache", ("-------------------------"));
+ for (i=0; i < mem_bin_steps; i++)
+ {
+ DBUG_PRINT("qcache", ("%10lu %3d %10lu", steps[i].size, steps[i].idx,
+ steps[i].increment));
+ }
+ DBUG_PRINT("qcache", ("-------------------------"));
+ DBUG_PRINT("qcache", (" size num"));
+ DBUG_PRINT("qcache", ("-------------------------"));
+ for (i=0; i < mem_bin_num; i++)
+ {
+ DBUG_PRINT("qcache", ("%10lu %3d 0x%lx", bins[i].size, bins[i].number,
+ (ulong)&(bins[i])));
+ if (bins[i].free_blocks)
+ {
+ Query_cache_block *block = bins[i].free_blocks;
+ do{
+ DBUG_PRINT("qcache", ("\\-- %lu 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx",
+ block->length, (ulong)block,
+ (ulong)block->next, (ulong)block->prev,
+ (ulong)block->pnext, (ulong)block->pprev));
+ block = block->next;
+ } while ( block != bins[i].free_blocks );
+ }
+ }
+ DBUG_PRINT("qcache", ("-------------------------"));
+}
+
+
+void Query_cache::cache_dump()
+{
+ if (!initialized || query_cache_size == 0)
+ {
+ DBUG_PRINT("qcache", ("Query Cache not initialized"));
+ return;
+ }
+
+ DBUG_PRINT("qcache", ("-------------------------------------"));
+ DBUG_PRINT("qcache", (" length used t nt"));
+ DBUG_PRINT("qcache", ("-------------------------------------"));
+ Query_cache_block *i = first_block;
+ do
+ {
+ DBUG_PRINT("qcache",
+ ("%10lu %10lu %1d %2d 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx",
+ i->length, i->used, (int)i->type,
+ i->n_tables, (ulong)i,
+ (ulong)i->next, (ulong)i->prev, (ulong)i->pnext,
+ (ulong)i->pprev));
+ i = i->pnext;
+ } while ( i != first_block );
+ DBUG_PRINT("qcache", ("-------------------------------------"));
+}
+
+
+void Query_cache::queries_dump()
+{
+
+ if (!initialized)
+ {
+ DBUG_PRINT("qcache", ("Query Cache not initialized"));
+ return;
+ }
+
+ DBUG_PRINT("qcache", ("------------------"));
+ DBUG_PRINT("qcache", (" QUERIES"));
+ DBUG_PRINT("qcache", ("------------------"));
+ if (queries_blocks != 0)
+ {
+ Query_cache_block *block = queries_blocks;
+ do
+ {
+ size_t len;
+ char *str = (char*) query_cache_query_get_key((uchar*) block, &len, 0);
+ len-= QUERY_CACHE_FLAGS_SIZE; // Point at flags
+ Query_cache_query_flags flags;
+ memcpy(&flags, str+len, QUERY_CACHE_FLAGS_SIZE);
+ str[len]= 0; // make zero ending DB name
+ DBUG_PRINT("qcache", ("F: %u C: %u L: %lu T: '%s' (%lu) '%s' '%s'",
+ flags.client_long_flag,
+ flags.character_set_client_num,
+ (ulong)flags.limit,
+ flags.time_zone->get_name()->ptr(),
+ (ulong) len, str, strend(str)+1));
+ DBUG_PRINT("qcache", ("-b- 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx", (ulong) block,
+ (ulong) block->next, (ulong) block->prev,
+ (ulong)block->pnext, (ulong)block->pprev));
+ memcpy(str + len, &flags, QUERY_CACHE_FLAGS_SIZE); // restore flags
+ for (TABLE_COUNTER_TYPE t= 0; t < block->n_tables; t++)
+ {
+ Query_cache_table *table= block->table(t)->parent;
+ DBUG_PRINT("qcache", ("-t- '%s' '%s'", table->db(), table->table()));
+ }
+ Query_cache_query *header = block->query();
+ if (header->result())
+ {
+ Query_cache_block *result_block = header->result();
+ Query_cache_block *result_beg = result_block;
+ do
+ {
+ DBUG_PRINT("qcache", ("-r- %u %lu/%lu 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx",
+ (uint) result_block->type,
+ result_block->length, result_block->used,
+ (ulong) result_block,
+ (ulong) result_block->next,
+ (ulong) result_block->prev,
+ (ulong) result_block->pnext,
+ (ulong) result_block->pprev));
+ result_block = result_block->next;
+ } while ( result_block != result_beg );
+ }
+ } while ((block=block->next) != queries_blocks);
+ }
+ else
+ {
+ DBUG_PRINT("qcache", ("no queries in list"));
+ }
+ DBUG_PRINT("qcache", ("------------------"));
+}
+
+
+void Query_cache::tables_dump()
+{
+ if (!initialized || query_cache_size == 0)
+ {
+ DBUG_PRINT("qcache", ("Query Cache not initialized"));
+ return;
+ }
+
+ DBUG_PRINT("qcache", ("--------------------"));
+ DBUG_PRINT("qcache", ("TABLES"));
+ DBUG_PRINT("qcache", ("--------------------"));
+ if (tables_blocks != 0)
+ {
+ Query_cache_block *table_block = tables_blocks;
+ do
+ {
+ Query_cache_table *table = table_block->table();
+ DBUG_PRINT("qcache", ("'%s' '%s'", table->db(), table->table()));
+ table_block = table_block->next;
+ } while (table_block != tables_blocks);
+ }
+ else
+ DBUG_PRINT("qcache", ("no tables in list"));
+ DBUG_PRINT("qcache", ("--------------------"));
+}
+
+
+/**
+ Checks integrity of the various linked lists
+
+ @return Error status code
+ @retval FALSE Query cache is operational.
+ @retval TRUE Query cache is broken.
+*/
+
+my_bool Query_cache::check_integrity(bool locked)
+{
+ my_bool result = 0;
+ uint i;
+ DBUG_ENTER("check_integrity");
+
+ if (!locked)
+ lock_and_suspend();
+
+ if (my_hash_check(&queries))
+ {
+ DBUG_PRINT("error", ("queries hash is damaged"));
+ result = 1;
+ }
+
+ if (my_hash_check(&tables))
+ {
+ DBUG_PRINT("error", ("tables hash is damaged"));
+ result = 1;
+ }
+
+ DBUG_PRINT("qcache", ("physical address check ..."));
+ ulong free=0, used=0;
+ Query_cache_block * block = first_block;
+ do
+ {
+ /* When checking at system start, there is no block. */
+ if (!block)
+ break;
+
+ DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
+ (ulong) block, (uint) block->type));
+ // Check allignment
+ if ((((long)block) % (long) ALIGN_SIZE(1)) !=
+ (((long)first_block) % (long)ALIGN_SIZE(1)))
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx do not aligned by %d", (ulong) block,
+ (int) ALIGN_SIZE(1)));
+ result = 1;
+ }
+ // Check memory allocation
+ if (block->pnext == first_block) // Is it last block?
+ {
+ if (((uchar*)block) + block->length !=
+ ((uchar*)first_block) + query_cache_size)
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx, type %u, ended at 0x%lx, but cache ended at 0x%lx",
+ (ulong) block, (uint) block->type,
+ (ulong) (((uchar*)block) + block->length),
+ (ulong) (((uchar*)first_block) + query_cache_size)));
+ result = 1;
+ }
+ }
+ else
+ if (((uchar*)block) + block->length != ((uchar*)block->pnext))
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx, type %u, ended at 0x%lx, but next block begining at 0x%lx",
+ (ulong) block, (uint) block->type,
+ (ulong) (((uchar*)block) + block->length),
+ (ulong) ((uchar*)block->pnext)));
+ }
+ if (block->type == Query_cache_block::FREE)
+ free+= block->length;
+ else
+ used+= block->length;
+ switch(block->type) {
+ case Query_cache_block::FREE:
+ {
+ Query_cache_memory_bin *bin = *((Query_cache_memory_bin **)
+ block->data());
+ //is it correct pointer?
+ if (((uchar*)bin) < ((uchar*)bins) ||
+ ((uchar*)bin) >= ((uchar*)first_block))
+ {
+ DBUG_PRINT("error",
+ ("free block 0x%lx have bin pointer 0x%lx beyaond of bins array bounds [0x%lx,0x%lx]",
+ (ulong) block,
+ (ulong) bin,
+ (ulong) bins,
+ (ulong) first_block));
+ result = 1;
+ }
+ else
+ {
+ int idx = (((uchar*)bin) - ((uchar*)bins)) /
+ sizeof(Query_cache_memory_bin);
+ if (in_list(bins[idx].free_blocks, block, "free memory"))
+ result = 1;
+ }
+ break;
+ }
+ case Query_cache_block::TABLE:
+ if (in_list(tables_blocks, block, "tables"))
+ result = 1;
+ if (in_table_list(block->table(0), block->table(0), "table list root"))
+ result = 1;
+ break;
+ case Query_cache_block::QUERY:
+ {
+ if (in_list(queries_blocks, block, "query"))
+ result = 1;
+ for (TABLE_COUNTER_TYPE j=0; j < block->n_tables; j++)
+ {
+ Query_cache_block_table *block_table = block->table(j);
+ Query_cache_block_table *block_table_root =
+ (Query_cache_block_table *)
+ (((uchar*)block_table->parent) -
+ ALIGN_SIZE(sizeof(Query_cache_block_table)));
+
+ if (in_table_list(block_table, block_table_root, "table list"))
+ result = 1;
+ }
+ break;
+ }
+ case Query_cache_block::RES_INCOMPLETE:
+ // This type of block can be not lincked yet (in multithread environment)
+ break;
+ case Query_cache_block::RES_BEG:
+ case Query_cache_block::RES_CONT:
+ case Query_cache_block::RESULT:
+ {
+ Query_cache_block * query_block = block->result()->parent();
+ if (((uchar*)query_block) < ((uchar*)first_block) ||
+ ((uchar*)query_block) >= (((uchar*)first_block) + query_cache_size))
+ {
+ DBUG_PRINT("error",
+ ("result block 0x%lx have query block pointer 0x%lx beyaond of block pool bounds [0x%lx,0x%lx]",
+ (ulong) block,
+ (ulong) query_block,
+ (ulong) first_block,
+ (ulong) (((uchar*)first_block) + query_cache_size)));
+ result = 1;
+ }
+ else
+ {
+ BLOCK_LOCK_RD(query_block);
+ if (in_list(queries_blocks, query_block, "query from results"))
+ result = 1;
+ if (in_list(query_block->query()->result(), block,
+ "results"))
+ result = 1;
+ BLOCK_UNLOCK_RD(query_block);
+ }
+ break;
+ }
+ default:
+ DBUG_PRINT("error", ("block 0x%lx have incorrect type %u",
+ (long) block, block->type));
+ result = 1;
+ }
+
+ block = block->pnext;
+ } while (block != first_block);
+
+ if (used + free != query_cache_size)
+ {
+ DBUG_PRINT("error",
+ ("used memory (%lu) + free memory (%lu) != query_cache_size (%lu)",
+ used, free, query_cache_size));
+ result = 1;
+ }
+
+ if (free != free_memory)
+ {
+ DBUG_PRINT("error",
+ ("free memory (%lu) != free_memory (%lu)",
+ free, free_memory));
+ result = 1;
+ }
+
+ DBUG_PRINT("qcache", ("check queries ..."));
+ if ((block = queries_blocks))
+ {
+ do
+ {
+ DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
+ (ulong) block, (uint) block->type));
+ size_t length;
+ uchar *key = query_cache_query_get_key((uchar*) block, &length, 0);
+ uchar* val = my_hash_search(&queries, key, length);
+ if (((uchar*)block) != val)
+ {
+ DBUG_PRINT("error", ("block 0x%lx found in queries hash like 0x%lx",
+ (ulong) block, (ulong) val));
+ }
+ if (in_blocks(block))
+ result = 1;
+ Query_cache_block * results = block->query()->result();
+ if (results)
+ {
+ Query_cache_block * result_block = results;
+ do
+ {
+ DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
+ (ulong) block, (uint) block->type));
+ if (in_blocks(result_block))
+ result = 1;
+
+ result_block = result_block->next;
+ } while (result_block != results);
+ }
+ block = block->next;
+ } while (block != queries_blocks);
+ }
+
+ DBUG_PRINT("qcache", ("check tables ..."));
+ if ((block = tables_blocks))
+ {
+ do
+ {
+ DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
+ (ulong) block, (uint) block->type));
+ size_t length;
+ uchar *key = query_cache_table_get_key((uchar*) block, &length, 0);
+ uchar* val = my_hash_search(&tables, key, length);
+ if (((uchar*)block) != val)
+ {
+ DBUG_PRINT("error", ("block 0x%lx found in tables hash like 0x%lx",
+ (ulong) block, (ulong) val));
+ }
+
+ if (in_blocks(block))
+ result = 1;
+ block=block->next;
+ } while (block != tables_blocks);
+ }
+
+ DBUG_PRINT("qcache", ("check free blocks"));
+ for (i = 0; i < mem_bin_num; i++)
+ {
+ if ((block = bins[i].free_blocks))
+ {
+ uint count = 0;
+ do
+ {
+ DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
+ (ulong) block, (uint) block->type));
+ if (in_blocks(block))
+ result = 1;
+
+ count++;
+ block=block->next;
+ } while (block != bins[i].free_blocks);
+ if (count != bins[i].number)
+ {
+ DBUG_PRINT("error", ("bins[%d].number= %d, but bin have %d blocks",
+ i, bins[i].number, count));
+ result = 1;
+ }
+ }
+ }
+ DBUG_ASSERT(result == 0);
+ if (!locked)
+ unlock();
+ DBUG_RETURN(result);
+}
+
+
+my_bool Query_cache::in_blocks(Query_cache_block * point)
+{
+ my_bool result = 0;
+ Query_cache_block *block = point;
+ //back
+ do
+ {
+ if (block->pprev->pnext != block)
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx in physical list is incorrect linked, prev block 0x%lx refered as next to 0x%lx (check from 0x%lx)",
+ (ulong) block, (ulong) block->pprev,
+ (ulong) block->pprev->pnext,
+ (ulong) point));
+ //back trace
+ for (; block != point; block = block->pnext)
+ DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
+ result = 1;
+ goto err1;
+ }
+ block = block->pprev;
+ } while (block != first_block && block != point);
+ if (block != first_block)
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx (0x%lx<-->0x%lx) not owned by pysical list",
+ (ulong) block, (ulong) block->pprev, (ulong )block->pnext));
+ return 1;
+ }
+
+err1:
+ //forward
+ block = point;
+ do
+ {
+ if (block->pnext->pprev != block)
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx in physicel list is incorrect linked, next block 0x%lx refered as prev to 0x%lx (check from 0x%lx)",
+ (ulong) block, (ulong) block->pnext,
+ (ulong) block->pnext->pprev,
+ (ulong) point));
+ //back trace
+ for (; block != point; block = block->pprev)
+ DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
+ result = 1;
+ goto err2;
+ }
+ block = block->pnext;
+ } while (block != first_block);
+err2:
+ return result;
+}
+
+
+my_bool Query_cache::in_list(Query_cache_block * root,
+ Query_cache_block * point,
+ const char *name)
+{
+ my_bool result = 0;
+ Query_cache_block *block = point;
+ //back
+ do
+ {
+ if (block->prev->next != block)
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx in list '%s' 0x%lx is incorrect linked, prev block 0x%lx refered as next to 0x%lx (check from 0x%lx)",
+ (ulong) block, name, (ulong) root, (ulong) block->prev,
+ (ulong) block->prev->next,
+ (ulong) point));
+ //back trace
+ for (; block != point; block = block->next)
+ DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
+ result = 1;
+ goto err1;
+ }
+ block = block->prev;
+ } while (block != root && block != point);
+ if (block != root)
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx (0x%lx<-->0x%lx) not owned by list '%s' 0x%lx",
+ (ulong) block,
+ (ulong) block->prev, (ulong) block->next,
+ name, (ulong) root));
+ return 1;
+ }
+err1:
+ // forward
+ block = point;
+ do
+ {
+ if (block->next->prev != block)
+ {
+ DBUG_PRINT("error",
+ ("block 0x%lx in list '%s' 0x%lx is incorrect linked, next block 0x%lx refered as prev to 0x%lx (check from 0x%lx)",
+ (ulong) block, name, (ulong) root, (ulong) block->next,
+ (ulong) block->next->prev,
+ (ulong) point));
+ //back trace
+ for (; block != point; block = block->prev)
+ DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
+ result = 1;
+ goto err2;
+ }
+ block = block->next;
+ } while (block != root);
+err2:
+ return result;
+}
+
+void dump_node(Query_cache_block_table * node,
+ const char * call, const char * descr)
+{
+ DBUG_PRINT("qcache", ("%s: %s: node: 0x%lx", call, descr, (ulong) node));
+ DBUG_PRINT("qcache", ("%s: %s: node block: 0x%lx",
+ call, descr, (ulong) node->block()));
+ DBUG_PRINT("qcache", ("%s: %s: next: 0x%lx", call, descr,
+ (ulong) node->next));
+ DBUG_PRINT("qcache", ("%s: %s: prev: 0x%lx", call, descr,
+ (ulong) node->prev));
+}
+
+my_bool Query_cache::in_table_list(Query_cache_block_table * root,
+ Query_cache_block_table * point,
+ const char *name)
+{
+ my_bool result = 0;
+ Query_cache_block_table *table = point;
+ dump_node(root, name, "parameter root");
+ //back
+ do
+ {
+ dump_node(table, name, "list element << ");
+ if (table->prev->next != table)
+ {
+ DBUG_PRINT("error",
+ ("table 0x%lx(0x%lx) in list '%s' 0x%lx(0x%lx) is incorrect linked, prev table 0x%lx(0x%lx) refered as next to 0x%lx(0x%lx) (check from 0x%lx(0x%lx))",
+ (ulong) table, (ulong) table->block(), name,
+ (ulong) root, (ulong) root->block(),
+ (ulong) table->prev, (ulong) table->prev->block(),
+ (ulong) table->prev->next,
+ (ulong) table->prev->next->block(),
+ (ulong) point, (ulong) point->block()));
+ //back trace
+ for (; table != point; table = table->next)
+ DBUG_PRINT("error", ("back trace 0x%lx(0x%lx)",
+ (ulong) table, (ulong) table->block()));
+ result = 1;
+ goto err1;
+ }
+ table = table->prev;
+ } while (table != root && table != point);
+ if (table != root)
+ {
+ DBUG_PRINT("error",
+ ("table 0x%lx(0x%lx) (0x%lx(0x%lx)<-->0x%lx(0x%lx)) not owned by list '%s' 0x%lx(0x%lx)",
+ (ulong) table, (ulong) table->block(),
+ (ulong) table->prev, (ulong) table->prev->block(),
+ (ulong) table->next, (ulong) table->next->block(),
+ name, (ulong) root, (ulong) root->block()));
+ return 1;
+ }
+err1:
+ // forward
+ table = point;
+ do
+ {
+ dump_node(table, name, "list element >> ");
+ if (table->next->prev != table)
+ {
+ DBUG_PRINT("error",
+ ("table 0x%lx(0x%lx) in list '%s' 0x%lx(0x%lx) is incorrect linked, next table 0x%lx(0x%lx) refered as prev to 0x%lx(0x%lx) (check from 0x%lx(0x%lx))",
+ (ulong) table, (ulong) table->block(),
+ name, (ulong) root, (ulong) root->block(),
+ (ulong) table->next, (ulong) table->next->block(),
+ (ulong) table->next->prev,
+ (ulong) table->next->prev->block(),
+ (ulong) point, (ulong) point->block()));
+ //back trace
+ for (; table != point; table = table->prev)
+ DBUG_PRINT("error", ("back trace 0x%lx(0x%lx)",
+ (ulong) table, (ulong) table->block()));
+ result = 1;
+ goto err2;
+ }
+ table = table->next;
+ } while (table != root);
+err2:
+ return result;
+}
+
+#endif /* DBUG_OFF */
+
+#endif /*HAVE_QUERY_CACHE*/
+
diff --git a/sql/sql_cache.h b/sql/sql_cache.h
new file mode 100644
index 00000000000..69520d668ac
--- /dev/null
+++ b/sql/sql_cache.h
@@ -0,0 +1,600 @@
+/* Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _SQL_CACHE_H
+#define _SQL_CACHE_H
+
+#include "hash.h"
+#include "my_base.h" /* ha_rows */
+
+class MY_LOCALE;
+struct TABLE_LIST;
+class Time_zone;
+struct LEX;
+struct TABLE;
+typedef struct st_changed_table_list CHANGED_TABLE_LIST;
+
+/* Query cache */
+
+/*
+ Can't create new free memory block if unused memory in block less
+ then QUERY_CACHE_MIN_ALLOCATION_UNIT.
+ if QUERY_CACHE_MIN_ALLOCATION_UNIT == 0 then
+ QUERY_CACHE_MIN_ALLOCATION_UNIT choosed automaticaly
+*/
+#define QUERY_CACHE_MIN_ALLOCATION_UNIT 512
+
+/* inittial size of hashes */
+#define QUERY_CACHE_DEF_QUERY_HASH_SIZE 1024
+#define QUERY_CACHE_DEF_TABLE_HASH_SIZE 1024
+
+/* minimal result data size when data allocated */
+#define QUERY_CACHE_MIN_RESULT_DATA_SIZE (1024*4)
+
+/*
+ start estimation of first result block size only when number of queries
+ bigger then:
+*/
+#define QUERY_CACHE_MIN_ESTIMATED_QUERIES_NUMBER 3
+
+
+
+/* memory bins size spacing (see at Query_cache::init_cache (sql_cache.cc)) */
+#define QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 4
+#define QUERY_CACHE_MEM_BIN_STEP_PWR2 2
+#define QUERY_CACHE_MEM_BIN_PARTS_INC 1
+#define QUERY_CACHE_MEM_BIN_PARTS_MUL 1.2
+#define QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2 3
+
+/* how many free blocks check when finding most suitable before other 'end'
+ of list of free blocks */
+#define QUERY_CACHE_MEM_BIN_TRY 5
+
+/* packing parameters */
+#define QUERY_CACHE_PACK_ITERATION 2
+#define QUERY_CACHE_PACK_LIMIT (512*1024L)
+
+#define TABLE_COUNTER_TYPE uint
+
+struct Query_cache_block;
+struct Query_cache_block_table;
+struct Query_cache_table;
+struct Query_cache_query;
+struct Query_cache_result;
+class Query_cache;
+struct Query_cache_tls;
+struct LEX;
+class THD;
+
+typedef my_bool (*qc_engine_callback)(THD *thd, char *table_key,
+ uint key_length,
+ ulonglong *engine_data);
+
+/**
+ This class represents a node in the linked chain of queries
+ belonging to one table.
+
+ @note The root of this linked list is not a query-type block, but the table-
+ type block which all queries has in common.
+*/
+struct Query_cache_block_table
+{
+ Query_cache_block_table() {} /* Remove gcc warning */
+
+ /**
+ This node holds a position in a static table list belonging
+ to the associated query (base 0).
+ */
+ TABLE_COUNTER_TYPE n;
+
+ /**
+ Pointers to the next and previous node, linking all queries with
+ a common table.
+ */
+ Query_cache_block_table *next, *prev;
+
+ /**
+ A pointer to the table-type block which all
+ linked queries has in common.
+ */
+ Query_cache_table *parent;
+
+ /**
+ A method to calculate the address of the query cache block
+ owning this node. The purpose of this calculation is to
+ make it easier to move the query cache block without having
+ to modify all the pointer addresses.
+ */
+ inline Query_cache_block *block();
+};
+
+struct Query_cache_block
+{
+ Query_cache_block() {} /* Remove gcc warning */
+ enum block_type {FREE, QUERY, RESULT, RES_CONT, RES_BEG,
+ RES_INCOMPLETE, TABLE, INCOMPLETE};
+
+ ulong length; // length of all block
+ ulong used; // length of data
+ /*
+ Not used **pprev, **prev because really needed access to pervious block:
+ *pprev to join free blocks
+ *prev to access to opposite side of list in cyclic sorted list
+ */
+ Query_cache_block *pnext,*pprev, // physical next/previous block
+ *next,*prev; // logical next/previous block
+ block_type type;
+ TABLE_COUNTER_TYPE n_tables; // number of tables in query
+
+ inline bool is_free(void) { return type == FREE; }
+ void init(ulong length);
+ void destroy();
+ uint headers_len();
+ uchar* data(void);
+ Query_cache_query *query();
+ Query_cache_table *table();
+ Query_cache_result *result();
+ Query_cache_block_table *table(TABLE_COUNTER_TYPE n);
+};
+
+struct Query_cache_query
+{
+ ulonglong limit_found_rows;
+ mysql_rwlock_t lock;
+ Query_cache_block *res;
+ Query_cache_tls *wri;
+ ulong len;
+ uint8 tbls_type;
+ unsigned int last_pkt_nr;
+
+ Query_cache_query() {} /* Remove gcc warning */
+ inline void init_n_lock();
+ void unlock_n_destroy();
+ inline ulonglong found_rows() { return limit_found_rows; }
+ inline void found_rows(ulonglong rows) { limit_found_rows= rows; }
+ inline Query_cache_block *result() { return res; }
+ inline void result(Query_cache_block *p) { res= p; }
+ inline Query_cache_tls *writer() { return wri; }
+ inline void writer(Query_cache_tls *p) { wri= p; }
+ inline uint8 tables_type() { return tbls_type; }
+ inline void tables_type(uint8 type) { tbls_type= type; }
+ inline ulong length() { return len; }
+ inline ulong add(ulong packet_len) { return(len+= packet_len); }
+ inline void length(ulong length_arg) { len= length_arg; }
+ inline uchar* query()
+ {
+ return (((uchar*)this) + ALIGN_SIZE(sizeof(Query_cache_query)));
+ }
+ void lock_writing();
+ void lock_reading();
+ bool try_lock_writing();
+ void unlock_writing();
+ void unlock_reading();
+};
+
+
+struct Query_cache_table
+{
+ Query_cache_table() {} /* Remove gcc warning */
+ char *tbl;
+ uint32 key_len;
+ uint8 suffix_len; /* For partitioned tables */
+ uint8 table_type;
+ /* unique for every engine reference */
+ qc_engine_callback callback_func;
+ /* data need by some engines */
+ ulonglong engine_data_buff;
+
+ /**
+ The number of queries depending of this table.
+ */
+ int32 m_cached_query_count;
+ /**
+ If table included in the table hash to be found by other queries
+ */
+ my_bool hashed;
+
+ inline char *db() { return (char *) data(); }
+ inline char *table() { return tbl; }
+ inline void table(char *table_arg) { tbl= table_arg; }
+ inline uint32 key_length() { return key_len; }
+ inline void key_length(uint32 len) { key_len= len; }
+ inline uint8 suffix_length() { return suffix_len; }
+ inline void suffix_length(uint8 len) { suffix_len= len; }
+ inline uint8 type() { return table_type; }
+ inline void type(uint8 t) { table_type= t; }
+ inline qc_engine_callback callback() { return callback_func; }
+ inline void callback(qc_engine_callback fn){ callback_func= fn; }
+ inline ulonglong engine_data() { return engine_data_buff; }
+ inline void engine_data(ulonglong data_arg){ engine_data_buff= data_arg; }
+ inline my_bool is_hashed() { return hashed; }
+ inline void set_hashed(my_bool hash) { hashed= hash; }
+ inline uchar* data()
+ {
+ return (uchar*)(((uchar*)this)+
+ ALIGN_SIZE(sizeof(Query_cache_table)));
+ }
+};
+
+struct Query_cache_result
+{
+ Query_cache_result() {} /* Remove gcc warning */
+ Query_cache_block *query;
+
+ inline uchar* data()
+ {
+ return (uchar*)(((uchar*) this)+
+ ALIGN_SIZE(sizeof(Query_cache_result)));
+ }
+ /* data_continue (if not whole packet contained by this block) */
+ inline Query_cache_block *parent() { return query; }
+ inline void parent (Query_cache_block *p) { query=p; }
+};
+
+
+extern "C"
+{
+ uchar *query_cache_query_get_key(const uchar *record, size_t *length,
+ my_bool not_used);
+ uchar *query_cache_table_get_key(const uchar *record, size_t *length,
+ my_bool not_used);
+}
+extern "C" void query_cache_invalidate_by_MyISAM_filename(const char* filename);
+
+
+struct Query_cache_memory_bin
+{
+ Query_cache_memory_bin() {} /* Remove gcc warning */
+#ifndef DBUG_OFF
+ ulong size;
+#endif
+ uint number;
+ Query_cache_block *free_blocks;
+
+ inline void init(ulong size_arg)
+ {
+#ifndef DBUG_OFF
+ size = size_arg;
+#endif
+ number = 0;
+ free_blocks = 0;
+ }
+};
+
+struct Query_cache_memory_bin_step
+{
+ Query_cache_memory_bin_step() {} /* Remove gcc warning */
+ ulong size;
+ ulong increment;
+ uint idx;
+ inline void init(ulong size_arg, uint idx_arg, ulong increment_arg)
+ {
+ size = size_arg;
+ idx = idx_arg;
+ increment = increment_arg;
+ }
+};
+
+class Query_cache
+{
+public:
+ /* Info */
+ ulong query_cache_size, query_cache_limit;
+ /* statistics */
+ ulong free_memory, queries_in_cache, hits, inserts, refused,
+ free_memory_blocks, total_blocks, lowmem_prunes;
+
+
+private:
+#ifndef DBUG_OFF
+ my_thread_id m_cache_lock_thread_id;
+#endif
+ mysql_cond_t COND_cache_status_changed;
+ uint m_requests_in_progress;
+ enum Cache_lock_status { UNLOCKED, LOCKED_NO_WAIT, LOCKED };
+ Cache_lock_status m_cache_lock_status;
+ enum Cache_staus {OK, DISABLE_REQUEST, DISABLED};
+ Cache_staus m_cache_status;
+
+ void free_query_internal(Query_cache_block *point);
+ void invalidate_table_internal(THD *thd, uchar *key, uint32 key_length);
+
+protected:
+ /*
+ The following mutex is locked when searching or changing global
+ query, tables lists or hashes. When we are operating inside the
+ query structure we locked an internal query block mutex.
+ LOCK SEQUENCE (to prevent deadlocks):
+ 1. structure_guard_mutex
+ 2. query block (for operation inside query (query block/results))
+
+ Thread doing cache flush releases the mutex once it sets
+ m_cache_lock_status flag, so other threads may bypass the cache as
+ if it is disabled, not waiting for reset to finish. The exception
+ is other threads that were going to do cache flush---they'll wait
+ till the end of a flush operation.
+ */
+ mysql_mutex_t structure_guard_mutex;
+ uchar *cache; // cache memory
+ Query_cache_block *first_block; // physical location block list
+ Query_cache_block *queries_blocks; // query list (LIFO)
+ Query_cache_block *tables_blocks;
+
+ Query_cache_memory_bin *bins; // free block lists
+ Query_cache_memory_bin_step *steps; // bins spacing info
+ HASH queries, tables;
+ /* options */
+ ulong min_allocation_unit, min_result_data_size;
+ uint def_query_hash_size, def_table_hash_size;
+
+ uint mem_bin_num, mem_bin_steps; // See at init_cache & find_bin
+
+ bool initialized;
+
+ /* Exclude/include from cyclic double linked list */
+ static void double_linked_list_exclude(Query_cache_block *point,
+ Query_cache_block **list_pointer);
+ static void double_linked_list_simple_include(Query_cache_block *point,
+ Query_cache_block **
+ list_pointer);
+ static void double_linked_list_join(Query_cache_block *head_tail,
+ Query_cache_block *tail_head);
+
+ /* The following functions require that structure_guard_mutex is locked */
+ void flush_cache();
+ my_bool free_old_query();
+ void free_query(Query_cache_block *point);
+ my_bool allocate_data_chain(Query_cache_block **result_block,
+ ulong data_len,
+ Query_cache_block *query_block,
+ my_bool first_block);
+ void invalidate_table(THD *thd, TABLE_LIST *table);
+ void invalidate_table(THD *thd, TABLE *table);
+ void invalidate_table(THD *thd, uchar *key, uint32 key_length);
+ void invalidate_table(THD *thd, Query_cache_block *table_block);
+ void invalidate_query_block_list(THD *thd,
+ Query_cache_block_table *list_root);
+
+ TABLE_COUNTER_TYPE
+ register_tables_from_list(THD *thd, TABLE_LIST *tables_used,
+ TABLE_COUNTER_TYPE counter,
+ Query_cache_block_table **block_table);
+ my_bool register_all_tables(THD *thd, Query_cache_block *block,
+ TABLE_LIST *tables_used,
+ TABLE_COUNTER_TYPE tables);
+ void unlink_table(Query_cache_block_table *node);
+ Query_cache_block *get_free_block (ulong len, my_bool not_less,
+ ulong min);
+ void free_memory_block(Query_cache_block *point);
+ void split_block(Query_cache_block *block, ulong len);
+ Query_cache_block *join_free_blocks(Query_cache_block *first_block,
+ Query_cache_block *block_in_list);
+ my_bool append_next_free_block(Query_cache_block *block,
+ ulong add_size);
+ void exclude_from_free_memory_list(Query_cache_block *free_block);
+ void insert_into_free_memory_list(Query_cache_block *new_block);
+ my_bool move_by_type(uchar **border, Query_cache_block **before,
+ ulong *gap, Query_cache_block *i);
+ uint find_bin(ulong size);
+ void move_to_query_list_end(Query_cache_block *block);
+ void insert_into_free_memory_sorted_list(Query_cache_block *new_block,
+ Query_cache_block **list);
+ void pack_cache();
+ void relink(Query_cache_block *oblock,
+ Query_cache_block *nblock,
+ Query_cache_block *next,
+ Query_cache_block *prev,
+ Query_cache_block *pnext,
+ Query_cache_block *pprev);
+ my_bool join_results(ulong join_limit);
+
+ /*
+ Following function control structure_guard_mutex
+ by themself or don't need structure_guard_mutex
+ */
+ ulong init_cache();
+ void make_disabled();
+ void free_cache();
+ Query_cache_block *write_block_data(ulong data_len, uchar* data,
+ ulong header_len,
+ Query_cache_block::block_type type,
+ TABLE_COUNTER_TYPE ntab = 0);
+ my_bool append_result_data(Query_cache_block **result,
+ ulong data_len, uchar* data,
+ Query_cache_block *parent);
+ my_bool write_result_data(Query_cache_block **result,
+ ulong data_len, uchar* data,
+ Query_cache_block *parent,
+ Query_cache_block::block_type
+ type=Query_cache_block::RESULT);
+ inline ulong get_min_first_result_data_size();
+ inline ulong get_min_append_result_data_size();
+ Query_cache_block *allocate_block(ulong len, my_bool not_less,
+ ulong min);
+ /*
+ If query is cacheable return number tables in query
+ (query without tables not cached)
+ */
+ TABLE_COUNTER_TYPE is_cacheable(THD *thd,
+ LEX *lex, TABLE_LIST *tables_used,
+ uint8 *tables_type);
+ TABLE_COUNTER_TYPE process_and_count_tables(THD *thd,
+ TABLE_LIST *tables_used,
+ uint8 *tables_type);
+
+ static my_bool ask_handler_allowance(THD *thd, TABLE_LIST *tables_used);
+ public:
+
+ Query_cache(ulong query_cache_limit = ULONG_MAX,
+ ulong min_allocation_unit = QUERY_CACHE_MIN_ALLOCATION_UNIT,
+ ulong min_result_data_size = QUERY_CACHE_MIN_RESULT_DATA_SIZE,
+ uint def_query_hash_size = QUERY_CACHE_DEF_QUERY_HASH_SIZE,
+ uint def_table_hash_size = QUERY_CACHE_DEF_TABLE_HASH_SIZE);
+
+ inline bool is_disabled(void) { return m_cache_status != OK; }
+ inline bool is_disable_in_progress(void)
+ { return m_cache_status == DISABLE_REQUEST; }
+
+ /* initialize cache (mutex) */
+ void init();
+ /* resize query cache (return real query size, 0 if disabled) */
+ ulong resize(ulong query_cache_size);
+ /* set limit on result size */
+ inline void result_size_limit(ulong limit){query_cache_limit=limit;}
+ /* set minimal result data allocation unit size */
+ ulong set_min_res_unit(ulong size);
+
+ /* register query in cache */
+ void store_query(THD *thd, TABLE_LIST *used_tables);
+
+ /*
+ Check if the query is in the cache and if this is true send the
+ data to client.
+ */
+ int send_result_to_client(THD *thd, char *query, uint query_length);
+
+ /* Remove all queries that uses any of the listed following tables */
+ void invalidate(THD *thd, TABLE_LIST *tables_used,
+ my_bool using_transactions);
+ void invalidate(THD *thd, CHANGED_TABLE_LIST *tables_used);
+ void invalidate_locked_for_write(THD *thd, TABLE_LIST *tables_used);
+ void invalidate(THD *thd, TABLE *table, my_bool using_transactions);
+ void invalidate(THD *thd, const char *key, uint32 key_length,
+ my_bool using_transactions);
+
+ /* Remove all queries that uses any of the tables in following database */
+ void invalidate(THD *thd, char *db);
+
+ /* Remove all queries that uses any of the listed following table */
+ void invalidate_by_MyISAM_filename(const char *filename);
+
+ void flush();
+ void pack(THD *thd,
+ ulong join_limit = QUERY_CACHE_PACK_LIMIT,
+ uint iteration_limit = QUERY_CACHE_PACK_ITERATION);
+
+ void destroy();
+
+ void insert(Query_cache_tls *query_cache_tls,
+ const char *packet,
+ ulong length,
+ unsigned pkt_nr);
+ my_bool insert_table(uint key_len, const char *key,
+ Query_cache_block_table *node,
+ uint32 db_length, uint8 suffix_length_arg,
+ uint8 cache_type,
+ qc_engine_callback callback,
+ ulonglong engine_data,
+ my_bool hash);
+
+ void end_of_result(THD *thd);
+ void abort(Query_cache_tls *query_cache_tls);
+
+ /*
+ The following functions are only used when debugging
+ We don't protect these with ifndef DBUG_OFF to not have to recompile
+ everything if we want to add checks of the cache at some places.
+ */
+ void wreck(uint line, const char *message);
+ void bins_dump();
+ void cache_dump();
+ void queries_dump();
+ void tables_dump();
+ my_bool check_integrity(bool not_locked);
+ my_bool in_list(Query_cache_block * root, Query_cache_block * point,
+ const char *name);
+ my_bool in_table_list(Query_cache_block_table * root,
+ Query_cache_block_table * point,
+ const char *name);
+ my_bool in_blocks(Query_cache_block * point);
+
+ /* Table key generation */
+ static uint filename_2_table_key (char *key, const char *filename,
+ uint32 *db_langth);
+
+ enum Cache_try_lock_mode {WAIT, TIMEOUT, TRY};
+ bool try_lock(THD *thd, Cache_try_lock_mode mode= WAIT);
+ void lock(THD *thd);
+ void lock_and_suspend(void);
+ void unlock(void);
+
+ void disable_query_cache(THD *thd);
+};
+
+#ifdef HAVE_QUERY_CACHE
+struct Query_cache_query_flags
+{
+ unsigned int client_long_flag:1;
+ unsigned int client_protocol_41:1;
+ unsigned int protocol_type:2;
+ unsigned int more_results_exists:1;
+ unsigned int in_trans:1;
+ unsigned int autocommit:1;
+ unsigned int pkt_nr;
+ uint character_set_client_num;
+ uint character_set_results_num;
+ uint collation_connection_num;
+ ha_rows limit;
+ Time_zone *time_zone;
+ ulonglong sql_mode;
+ ulong max_sort_length;
+ ulong group_concat_max_len;
+ ulong default_week_format;
+ ulong div_precision_increment;
+ MY_LOCALE *lc_time_names;
+};
+#define QUERY_CACHE_FLAGS_SIZE sizeof(Query_cache_query_flags)
+#define QUERY_CACHE_DB_LENGTH_SIZE 2
+#include "sql_cache.h"
+#define query_cache_abort(A) query_cache.abort(A)
+#define query_cache_end_of_result(A) query_cache.end_of_result(A)
+#define query_cache_store_query(A, B) query_cache.store_query(A, B)
+#define query_cache_destroy() query_cache.destroy()
+#define query_cache_result_size_limit(A) query_cache.result_size_limit(A)
+#define query_cache_init() query_cache.init()
+#define query_cache_resize(A) query_cache.resize(A)
+#define query_cache_set_min_res_unit(A) query_cache.set_min_res_unit(A)
+#define query_cache_invalidate3(A, B, C) query_cache.invalidate(A, B, C)
+#define query_cache_invalidate1(A, B) query_cache.invalidate(A, B)
+#define query_cache_send_result_to_client(A, B, C) \
+ query_cache.send_result_to_client(A, B, C)
+#define query_cache_invalidate_by_MyISAM_filename_ref \
+ &query_cache_invalidate_by_MyISAM_filename
+/* note the "maybe": it's a read without mutex */
+#define query_cache_maybe_disabled(T) \
+ (T->variables.query_cache_type == 0 || query_cache.query_cache_size == 0)
+#define query_cache_is_cacheable_query(L) \
+ (((L)->sql_command == SQLCOM_SELECT) && (L)->safe_to_cache_query)
+#else
+#define QUERY_CACHE_FLAGS_SIZE 0
+#define query_cache_store_query(A, B) do { } while(0)
+#define query_cache_destroy() do { } while(0)
+#define query_cache_result_size_limit(A) do { } while(0)
+#define query_cache_init() do { } while(0)
+#define query_cache_resize(A) do { } while(0)
+#define query_cache_set_min_res_unit(A) do { } while(0)
+#define query_cache_invalidate3(A, B, C) do { } while(0)
+#define query_cache_invalidate1(A,B) do { } while(0)
+#define query_cache_send_result_to_client(A, B, C) 0
+#define query_cache_invalidate_by_MyISAM_filename_ref NULL
+
+#define query_cache_abort(A) do { } while(0)
+#define query_cache_end_of_result(A) do { } while(0)
+#define query_cache_maybe_disabled(T) 1
+#define query_cache_is_cacheable_query(L) 0
+#endif /*HAVE_QUERY_CACHE*/
+
+extern Query_cache query_cache;
+#endif
diff --git a/sql/sql_callback.h b/sql/sql_callback.h
new file mode 100644
index 00000000000..316f94a0213
--- /dev/null
+++ b/sql/sql_callback.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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
+*/
+
+#ifndef SQL_CALLBACK_INCLUDED
+#define SQL_CALLBACK_INCLUDED
+
+/**
+ Macro used for an internal callback.
+
+ The macro will check that the object exists and that the function
+ is defined. If that is the case, it will call the function with the
+ given parameters.
+
+ If the object or the function is not defined, the callback will be
+ considered successful (nothing needed to be done) and will
+ therefore return no error.
+ */
+
+#define MYSQL_CALLBACK(OBJ, FUNC, PARAMS) \
+ do { \
+ if ((OBJ) && ((OBJ)->FUNC)) \
+ (OBJ)->FUNC PARAMS; \
+ } while (0)
+
+#define MYSQL_CALLBACK_ELSE(OBJ, FUNC, PARAMS, ELSE) \
+ (((OBJ) && ((OBJ)->FUNC)) ? (OBJ)->FUNC PARAMS : (ELSE))
+
+#endif /* SQL_CALLBACK_INCLUDED */
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
new file mode 100644
index 00000000000..ed2bddd7e8d
--- /dev/null
+++ b/sql/sql_class.cc
@@ -0,0 +1,6694 @@
+/*
+ Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+
+/*****************************************************************************
+**
+** This file implements classes defined in sql_class.h
+** Especially the classes to handle a result from a select
+**
+*****************************************************************************/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_class.h"
+#include "sql_cache.h" // query_cache_abort
+#include "sql_base.h" // close_thread_tables
+#include "sql_time.h" // date_time_format_copy
+#include "tztime.h" // MYSQL_TIME <-> my_time_t
+#include "sql_acl.h" // NO_ACCESS,
+ // acl_getroot_no_password
+#include "sql_base.h" // close_temporary_tables
+#include "sql_handler.h" // mysql_ha_cleanup
+#include "rpl_rli.h"
+#include "rpl_filter.h"
+#include "rpl_record.h"
+#include "slave.h"
+#include <my_bitmap.h>
+#include "log_event.h"
+#include "sql_audit.h"
+#include <m_ctype.h>
+#include <sys/stat.h>
+#include <thr_alarm.h>
+#ifdef __WIN__
+#include <io.h>
+#endif
+#include <mysys_err.h>
+#include <limits.h>
+
+#include "sp_rcontext.h"
+#include "sp_cache.h"
+#include "transaction.h"
+#include "sql_select.h" /* declares create_tmp_table() */
+#include "debug_sync.h"
+#include "sql_parse.h" // is_update_query
+#include "sql_callback.h"
+#include "lock.h"
+#include "sql_connect.h"
+
+/*
+ The following is used to initialise Table_ident with a internal
+ table name
+*/
+char internal_table_name[2]= "*";
+char empty_c_string[1]= {0}; /* used for not defined db */
+
+const char * const THD::DEFAULT_WHERE= "field list";
+
+/****************************************************************************
+** User variables
+****************************************************************************/
+
+extern "C" uchar *get_var_key(user_var_entry *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= entry->name.length;
+ return (uchar*) entry->name.str;
+}
+
+extern "C" void free_user_var(user_var_entry *entry)
+{
+ char *pos= (char*) entry+ALIGN_SIZE(sizeof(*entry));
+ if (entry->value && entry->value != pos)
+ my_free(entry->value);
+ my_free(entry);
+}
+
+bool Key_part_spec::operator==(const Key_part_spec& other) const
+{
+ return length == other.length &&
+ !my_strcasecmp(system_charset_info, field_name.str,
+ other.field_name.str);
+}
+
+/**
+ Construct an (almost) deep copy of this key. Only those
+ elements that are known to never change are not copied.
+ If out of memory, a partial copy is returned and an error is set
+ in THD.
+*/
+
+Key::Key(const Key &rhs, MEM_ROOT *mem_root)
+ :type(rhs.type),
+ key_create_info(rhs.key_create_info),
+ columns(rhs.columns, mem_root),
+ name(rhs.name),
+ option_list(rhs.option_list),
+ generated(rhs.generated),
+ create_if_not_exists(rhs.create_if_not_exists)
+{
+ list_copy_and_replace_each_value(columns, mem_root);
+}
+
+/**
+ Construct an (almost) deep copy of this foreign key. Only those
+ elements that are known to never change are not copied.
+ If out of memory, a partial copy is returned and an error is set
+ in THD.
+*/
+
+Foreign_key::Foreign_key(const Foreign_key &rhs, MEM_ROOT *mem_root)
+ :Key(rhs,mem_root),
+ ref_db(rhs.ref_db),
+ ref_table(rhs.ref_table),
+ ref_columns(rhs.ref_columns,mem_root),
+ delete_opt(rhs.delete_opt),
+ update_opt(rhs.update_opt),
+ match_opt(rhs.match_opt)
+{
+ list_copy_and_replace_each_value(ref_columns, mem_root);
+}
+
+/*
+ Test if a foreign key (= generated key) is a prefix of the given key
+ (ignoring key name, key type and order of columns)
+
+ NOTES:
+ This is only used to test if an index for a FOREIGN KEY exists
+
+ IMPLEMENTATION
+ We only compare field names
+
+ RETURN
+ 0 Generated key is a prefix of other key
+ 1 Not equal
+*/
+
+bool foreign_key_prefix(Key *a, Key *b)
+{
+ /* Ensure that 'a' is the generated key */
+ if (a->generated)
+ {
+ if (b->generated && a->columns.elements > b->columns.elements)
+ swap_variables(Key*, a, b); // Put shorter key in 'a'
+ }
+ else
+ {
+ if (!b->generated)
+ return TRUE; // No foreign key
+ swap_variables(Key*, a, b); // Put generated key in 'a'
+ }
+
+ /* Test if 'a' is a prefix of 'b' */
+ if (a->columns.elements > b->columns.elements)
+ return TRUE; // Can't be prefix
+
+ List_iterator<Key_part_spec> col_it1(a->columns);
+ List_iterator<Key_part_spec> col_it2(b->columns);
+ const Key_part_spec *col1, *col2;
+
+#ifdef ENABLE_WHEN_INNODB_CAN_HANDLE_SWAPED_FOREIGN_KEY_COLUMNS
+ while ((col1= col_it1++))
+ {
+ bool found= 0;
+ col_it2.rewind();
+ while ((col2= col_it2++))
+ {
+ if (*col1 == *col2)
+ {
+ found= TRUE;
+ break;
+ }
+ }
+ if (!found)
+ return TRUE; // Error
+ }
+ return FALSE; // Is prefix
+#else
+ while ((col1= col_it1++))
+ {
+ col2= col_it2++;
+ if (!(*col1 == *col2))
+ return TRUE;
+ }
+ return FALSE; // Is prefix
+#endif
+}
+
+/*
+ @brief
+ Check if the foreign key options are compatible with the specification
+ of the columns on which the key is created
+
+ @retval
+ FALSE The foreign key options are compatible with key columns
+ @retval
+ TRUE Otherwise
+*/
+bool Foreign_key::validate(List<Create_field> &table_fields)
+{
+ Create_field *sql_field;
+ Key_part_spec *column;
+ List_iterator<Key_part_spec> cols(columns);
+ List_iterator<Create_field> it(table_fields);
+ DBUG_ENTER("Foreign_key::validate");
+ while ((column= cols++))
+ {
+ it.rewind();
+ while ((sql_field= it++) &&
+ my_strcasecmp(system_charset_info,
+ column->field_name.str,
+ sql_field->field_name)) {}
+ if (!sql_field)
+ {
+ my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name);
+ DBUG_RETURN(TRUE);
+ }
+ if (type == Key::FOREIGN_KEY && sql_field->vcol_info)
+ {
+ if (delete_opt == FK_OPTION_SET_NULL)
+ {
+ my_error(ER_WRONG_FK_OPTION_FOR_VIRTUAL_COLUMN, MYF(0),
+ "ON DELETE SET NULL");
+ DBUG_RETURN(TRUE);
+ }
+ if (update_opt == FK_OPTION_SET_NULL)
+ {
+ my_error(ER_WRONG_FK_OPTION_FOR_VIRTUAL_COLUMN, MYF(0),
+ "ON UPDATE SET NULL");
+ DBUG_RETURN(TRUE);
+ }
+ if (update_opt == FK_OPTION_CASCADE)
+ {
+ my_error(ER_WRONG_FK_OPTION_FOR_VIRTUAL_COLUMN, MYF(0),
+ "ON UPDATE CASCADE");
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+/****************************************************************************
+** Thread specific functions
+****************************************************************************/
+#ifdef ONLY_FOR_MYSQL_CLOSED_SOURCE_SCHEDULED
+/**
+ Get reference to scheduler data object
+
+ @param thd THD object
+
+ @retval Scheduler data object on THD
+*/
+void *thd_get_scheduler_data(THD *thd)
+{
+ return thd->scheduler.data;
+}
+
+/**
+ Set reference to Scheduler data object for THD object
+
+ @param thd THD object
+ @param psi Scheduler data object to set on THD
+*/
+void thd_set_scheduler_data(THD *thd, void *data)
+{
+ thd->scheduler.data= data;
+}
+
+/**
+ Get reference to Performance Schema object for THD object
+
+ @param thd THD object
+
+ @retval Performance schema object for thread on THD
+*/
+PSI_thread *thd_get_psi(THD *thd)
+{
+ return thd->scheduler.m_psi;
+}
+
+/**
+ Get net_wait_timeout for THD object
+
+ @param thd THD object
+
+ @retval net_wait_timeout value for thread on THD
+*/
+ulong thd_get_net_wait_timeout(THD* thd)
+{
+ return thd->variables.net_wait_timeout;
+}
+
+/**
+ Set reference to Performance Schema object for THD object
+
+ @param thd THD object
+ @param psi Performance schema object for thread
+*/
+void thd_set_psi(THD *thd, PSI_thread *psi)
+{
+ thd->scheduler.m_psi= psi;
+}
+
+/**
+ Set the state on connection to killed
+
+ @param thd THD object
+*/
+void thd_set_killed(THD *thd)
+{
+ thd->killed= KILL_CONNECTION;
+}
+
+/**
+ Clear errors from the previous THD
+
+ @param thd THD object
+*/
+void thd_clear_errors(THD *thd)
+{
+ my_errno= 0;
+ thd->mysys_var->abort= 0;
+}
+
+/**
+ Set thread stack in THD object
+
+ @param thd Thread object
+ @param stack_start Start of stack to set in THD object
+*/
+void thd_set_thread_stack(THD *thd, char *stack_start)
+{
+ thd->thread_stack= stack_start;
+}
+
+/**
+ Close the socket used by this connection
+
+ @param thd THD object
+*/
+void thd_close_connection(THD *thd)
+{
+ if (thd->net.vio)
+ vio_close(thd->net.vio);
+}
+
+/**
+ Get current THD object from thread local data
+
+ @retval The THD object for the thread, NULL if not connection thread
+*/
+THD *thd_get_current_thd()
+{
+ return current_thd;
+}
+
+/**
+ Lock data that needs protection in THD object
+
+ @param thd THD object
+*/
+void thd_lock_data(THD *thd)
+{
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+}
+
+/**
+ Unlock data that needs protection in THD object
+
+ @param thd THD object
+*/
+void thd_unlock_data(THD *thd)
+{
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+}
+
+/**
+ Support method to check if connection has already started transcaction
+
+ @param client_cntx Low level client context
+
+ @retval TRUE if connection already started transaction
+*/
+bool thd_is_transaction_active(THD *thd)
+{
+ return thd->transaction.is_active();
+}
+
+/**
+ Check if there is buffered data on the socket representing the connection
+
+ @param thd THD object
+*/
+int thd_connection_has_data(THD *thd)
+{
+ Vio *vio= thd->net.vio;
+ return vio->has_data(vio);
+}
+
+/**
+ Set reading/writing on socket, used by SHOW PROCESSLIST
+
+ @param thd THD object
+ @param val Value to set it to (0 or 1)
+*/
+void thd_set_net_read_write(THD *thd, uint val)
+{
+ thd->net.reading_or_writing= val;
+}
+
+/**
+ Get reading/writing on socket from THD object
+ @param thd THD object
+
+ @retval net.reading_or_writing value for thread on THD.
+*/
+uint thd_get_net_read_write(THD *thd)
+{
+ return thd->net.reading_or_writing;
+}
+
+/**
+ Set reference to mysys variable in THD object
+
+ @param thd THD object
+ @param mysys_var Reference to set
+*/
+void thd_set_mysys_var(THD *thd, st_my_thread_var *mysys_var)
+{
+ thd->set_mysys_var(mysys_var);
+}
+
+/**
+ Get socket file descriptor for this connection
+
+ @param thd THD object
+
+ @retval Socket of the connection
+*/
+my_socket thd_get_fd(THD *thd)
+{
+ return mysql_socket_getfd(thd->net.vio->mysql_socket);
+}
+#endif
+
+/**
+ Get thread attributes for connection threads
+
+ @retval Reference to thread attribute for connection threads
+*/
+pthread_attr_t *get_connection_attrib(void)
+{
+ return &connection_attrib;
+}
+
+/**
+ Get max number of connections
+
+ @retval Max number of connections for MySQL Server
+*/
+ulong get_max_connections(void)
+{
+ return max_connections;
+}
+
+/*
+ The following functions form part of the C plugin API
+*/
+
+extern "C" int mysql_tmpfile(const char *prefix)
+{
+ char filename[FN_REFLEN];
+ File fd = create_temp_file(filename, mysql_tmpdir, prefix,
+#ifdef __WIN__
+ O_BINARY | O_TRUNC | O_SEQUENTIAL |
+ O_SHORT_LIVED |
+#endif /* __WIN__ */
+ O_CREAT | O_EXCL | O_RDWR | O_TEMPORARY,
+ MYF(MY_WME));
+ if (fd >= 0) {
+#ifndef __WIN__
+ /*
+ This can be removed once the following bug is fixed:
+ Bug #28903 create_temp_file() doesn't honor O_TEMPORARY option
+ (file not removed) (Unix)
+ */
+ unlink(filename);
+#endif /* !__WIN__ */
+ }
+
+ return fd;
+}
+
+
+extern "C"
+int thd_in_lock_tables(const THD *thd)
+{
+ return MY_TEST(thd->in_lock_tables);
+}
+
+
+extern "C"
+int thd_tablespace_op(const THD *thd)
+{
+ return MY_TEST(thd->tablespace_op);
+}
+
+extern "C"
+const char *set_thd_proc_info(THD *thd_arg, const char *info,
+ const char *calling_function,
+ const char *calling_file,
+ const unsigned int calling_line)
+{
+ PSI_stage_info old_stage;
+ PSI_stage_info new_stage;
+
+ old_stage.m_key= 0;
+ old_stage.m_name= info;
+
+ set_thd_stage_info(thd_arg, & old_stage, & new_stage,
+ calling_function, calling_file, calling_line);
+
+ return new_stage.m_name;
+}
+
+extern "C"
+void set_thd_stage_info(void *thd_arg,
+ const PSI_stage_info *new_stage,
+ PSI_stage_info *old_stage,
+ const char *calling_func,
+ const char *calling_file,
+ const unsigned int calling_line)
+{
+ THD *thd= (THD*) thd_arg;
+ if (thd == NULL)
+ thd= current_thd;
+
+ thd->enter_stage(new_stage, old_stage, calling_func, calling_file,
+ calling_line);
+}
+
+void THD::enter_stage(const PSI_stage_info *new_stage,
+ PSI_stage_info *old_stage,
+ const char *calling_func,
+ const char *calling_file,
+ const unsigned int calling_line)
+{
+ DBUG_PRINT("THD::enter_stage", ("%s:%d", calling_file, calling_line));
+
+ if (old_stage != NULL)
+ {
+ old_stage->m_key= m_current_stage_key;
+ old_stage->m_name= proc_info;
+ }
+
+ if (new_stage != NULL)
+ {
+ const char *msg= new_stage->m_name;
+
+#if defined(ENABLED_PROFILING)
+ profiling.status_change(msg, calling_func, calling_file, calling_line);
+#endif
+
+ m_current_stage_key= new_stage->m_key;
+ proc_info= msg;
+
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ PSI_THREAD_CALL(set_thread_state)(msg);
+ MYSQL_SET_STAGE(m_current_stage_key, calling_file, calling_line);
+#endif
+ }
+ return;
+}
+
+void thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond, mysql_mutex_t *mutex,
+ const PSI_stage_info *stage, PSI_stage_info *old_stage,
+ const char *src_function, const char *src_file,
+ int src_line)
+{
+ if (!thd)
+ thd= current_thd;
+
+ return thd->enter_cond(cond, mutex, stage, old_stage, src_function, src_file,
+ src_line);
+}
+
+void thd_exit_cond(MYSQL_THD thd, const PSI_stage_info *stage,
+ const char *src_function, const char *src_file,
+ int src_line)
+{
+ if (!thd)
+ thd= current_thd;
+
+ thd->exit_cond(stage, src_function, src_file, src_line);
+ return;
+}
+
+extern "C"
+void **thd_ha_data(const THD *thd, const struct handlerton *hton)
+{
+ return (void **) &thd->ha_data[hton->slot].ha_ptr;
+}
+
+extern "C"
+void thd_storage_lock_wait(THD *thd, long long value)
+{
+ thd->utime_after_lock+= value;
+}
+
+/**
+ Provide a handler data getter to simplify coding
+*/
+extern "C"
+void *thd_get_ha_data(const THD *thd, const struct handlerton *hton)
+{
+ return *thd_ha_data(thd, hton);
+}
+
+
+/**
+ Provide a handler data setter to simplify coding
+ @see thd_set_ha_data() definition in plugin.h
+*/
+extern "C"
+void thd_set_ha_data(THD *thd, const struct handlerton *hton,
+ const void *ha_data)
+{
+ plugin_ref *lock= &thd->ha_data[hton->slot].lock;
+ if (ha_data && !*lock)
+ *lock= ha_lock_engine(NULL, (handlerton*) hton);
+ else if (!ha_data && *lock)
+ {
+ plugin_unlock(NULL, *lock);
+ *lock= NULL;
+ }
+ *thd_ha_data(thd, hton)= (void*) ha_data;
+}
+
+
+/**
+ Allow storage engine to wakeup commits waiting in THD::wait_for_prior_commit.
+ @see thd_wakeup_subsequent_commits() definition in plugin.h
+*/
+extern "C"
+void thd_wakeup_subsequent_commits(THD *thd, int wakeup_error)
+{
+ thd->wakeup_subsequent_commits(wakeup_error);
+}
+
+
+extern "C"
+long long thd_test_options(const THD *thd, long long test_options)
+{
+ return thd->variables.option_bits & test_options;
+}
+
+extern "C"
+int thd_sql_command(const THD *thd)
+{
+ return (int) thd->lex->sql_command;
+}
+
+extern "C"
+int thd_tx_isolation(const THD *thd)
+{
+ return (int) thd->tx_isolation;
+}
+
+extern "C"
+int thd_tx_is_read_only(const THD *thd)
+{
+ return (int) thd->tx_read_only;
+}
+
+
+extern "C"
+{ /* Functions for thd_error_context_service */
+
+ const char *thd_get_error_message(const THD *thd)
+ {
+ return thd->get_stmt_da()->message();
+ }
+
+ uint thd_get_error_number(const THD *thd)
+ {
+ return thd->get_stmt_da()->sql_errno();
+ }
+
+ ulong thd_get_error_row(const THD *thd)
+ {
+ return thd->get_stmt_da()->current_row_for_warning();
+ }
+
+ void thd_inc_error_row(THD *thd)
+ {
+ thd->get_stmt_da()->inc_current_row_for_warning();
+ }
+}
+
+
+/**
+ Dumps a text description of a thread, its security context
+ (user, host) and the current query.
+
+ @param thd thread context
+ @param buffer pointer to preferred result buffer
+ @param length length of buffer
+ @param max_query_len how many chars of query to copy (0 for all)
+
+ @req LOCK_thread_count
+
+ @note LOCK_thread_count mutex is not necessary when the function is invoked on
+ the currently running thread (current_thd) or if the caller in some other
+ way guarantees that access to thd->query is serialized.
+
+ @return Pointer to string
+*/
+
+extern "C"
+char *thd_get_error_context_description(THD *thd, char *buffer,
+ unsigned int length,
+ unsigned int max_query_len)
+{
+ String str(buffer, length, &my_charset_latin1);
+ const Security_context *sctx= &thd->main_security_ctx;
+ char header[256];
+ int len;
+ /*
+ The pointers thd->query and thd->proc_info might change since they are
+ being modified concurrently. This is acceptable for proc_info since its
+ values doesn't have to very accurate and the memory it points to is static,
+ but we need to attempt a snapshot on the pointer values to avoid using NULL
+ values. The pointer to thd->query however, doesn't point to static memory
+ and has to be protected by thd->LOCK_thd_data or risk pointing to
+ uninitialized memory.
+ */
+ const char *proc_info= thd->proc_info;
+
+ len= my_snprintf(header, sizeof(header),
+ "MySQL thread id %lu, OS thread handle 0x%lx, query id %lu",
+ thd->thread_id, (ulong) thd->real_id, (ulong) thd->query_id);
+ str.length(0);
+ str.append(header, len);
+
+ if (sctx->host)
+ {
+ str.append(' ');
+ str.append(sctx->host);
+ }
+
+ if (sctx->ip)
+ {
+ str.append(' ');
+ str.append(sctx->ip);
+ }
+
+ if (sctx->user)
+ {
+ str.append(' ');
+ str.append(sctx->user);
+ }
+
+ if (proc_info)
+ {
+ str.append(' ');
+ str.append(proc_info);
+ }
+
+ /* Don't wait if LOCK_thd_data is used as this could cause a deadlock */
+ if (!mysql_mutex_trylock(&thd->LOCK_thd_data))
+ {
+ if (thd->query())
+ {
+ if (max_query_len < 1)
+ len= thd->query_length();
+ else
+ len= MY_MIN(thd->query_length(), max_query_len);
+ str.append('\n');
+ str.append(thd->query(), len);
+ }
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+ }
+
+ if (str.c_ptr_safe() == buffer)
+ return buffer;
+
+ /*
+ We have to copy the new string to the destination buffer because the string
+ was reallocated to a larger buffer to be able to fit.
+ */
+ DBUG_ASSERT(buffer != NULL);
+ length= MY_MIN(str.length(), length-1);
+ memcpy(buffer, str.c_ptr_quick(), length);
+ /* Make sure that the new string is null terminated */
+ buffer[length]= '\0';
+ return buffer;
+}
+
+
+#if MARIA_PLUGIN_INTERFACE_VERSION < 0x0200
+/**
+ TODO: This function is for API compatibility, remove it eventually.
+ All engines should switch to use thd_get_error_context_description()
+ plugin service function.
+*/
+extern "C"
+char *thd_security_context(THD *thd,
+ char *buffer, unsigned int length,
+ unsigned int max_query_len)
+{
+ return thd_get_error_context_description(thd, buffer, length, max_query_len);
+}
+#endif
+
+/**
+ Implementation of Drop_table_error_handler::handle_condition().
+ The reason in having this implementation is to silence technical low-level
+ warnings during DROP TABLE operation. Currently we don't want to expose
+ the following warnings during DROP TABLE:
+ - Some of table files are missed or invalid (the table is going to be
+ deleted anyway, so why bother that something was missed);
+ - A trigger associated with the table does not have DEFINER (One of the
+ MySQL specifics now is that triggers are loaded for the table being
+ dropped. So, we may have a warning that trigger does not have DEFINER
+ attribute during DROP TABLE operation).
+
+ @return TRUE if the condition is handled.
+*/
+bool Drop_table_error_handler::handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+{
+ *cond_hdl= NULL;
+ return ((sql_errno == EE_DELETE && my_errno == ENOENT) ||
+ sql_errno == ER_TRG_NO_DEFINER);
+}
+
+
+THD::THD()
+ :Statement(&main_lex, &main_mem_root, STMT_CONVENTIONAL_EXECUTION,
+ /* statement id */ 0),
+ rli_fake(0), rgi_fake(0), rgi_slave(NULL),
+ in_sub_stmt(0), log_all_errors(0),
+ binlog_unsafe_warning_flags(0),
+ binlog_table_maps(0),
+ table_map_for_update(0),
+ arg_of_last_insert_id_function(FALSE),
+ first_successful_insert_id_in_prev_stmt(0),
+ first_successful_insert_id_in_prev_stmt_for_binlog(0),
+ first_successful_insert_id_in_cur_stmt(0),
+ stmt_depends_on_first_successful_insert_id_in_prev_stmt(FALSE),
+ m_examined_row_count(0),
+ accessed_rows_and_keys(0),
+ m_digest(NULL),
+ m_statement_psi(NULL),
+ m_idle_psi(NULL),
+ thread_id(0),
+ global_disable_checkpoint(0),
+ failed_com_change_user(0),
+ is_fatal_error(0),
+ transaction_rollback_request(0),
+ is_fatal_sub_stmt_error(false),
+ rand_used(0),
+ time_zone_used(0),
+ in_lock_tables(0),
+ bootstrap(0),
+ derived_tables_processing(FALSE),
+ waiting_on_group_commit(FALSE), has_waiter(FALSE),
+ spcont(NULL),
+ m_parser_state(NULL),
+#if defined(ENABLED_DEBUG_SYNC)
+ debug_sync_control(0),
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+ wait_for_commit_ptr(0),
+ main_da(0, false, false),
+ m_stmt_da(&main_da)
+{
+ ulong tmp;
+
+ mdl_context.init(this);
+ /*
+ We set THR_THD to temporally point to this THD to register all the
+ variables that allocates memory for this THD
+ */
+ THD *old_THR_THD= current_thd;
+ set_current_thd(this);
+ status_var.memory_used= 0;
+ main_da.init();
+
+ /*
+ Pass nominal parameters to init_alloc_root only to ensure that
+ the destructor works OK in case of an error. The main_mem_root
+ will be re-initialized in init_for_queries().
+ */
+ init_sql_alloc(&main_mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0,
+ MYF(MY_THREAD_SPECIFIC));
+
+ stmt_arena= this;
+ thread_stack= 0;
+ scheduler= thread_scheduler; // Will be fixed later
+ event_scheduler.data= 0;
+ event_scheduler.m_psi= 0;
+ skip_wait_timeout= false;
+ extra_port= 0;
+ catalog= (char*)"std"; // the only catalog we have for now
+ main_security_ctx.init();
+ security_ctx= &main_security_ctx;
+ no_errors= 0;
+ password= 0;
+ query_start_used= query_start_sec_part_used= 0;
+ count_cuted_fields= CHECK_FIELD_IGNORE;
+ killed= NOT_KILLED;
+ col_access=0;
+ is_slave_error= thread_specific_used= FALSE;
+ my_hash_clear(&handler_tables_hash);
+ my_hash_clear(&ull_hash);
+ tmp_table=0;
+ cuted_fields= 0L;
+ m_sent_row_count= 0L;
+ limit_found_rows= 0;
+ m_row_count_func= -1;
+ statement_id_counter= 0UL;
+ // Must be reset to handle error with THD's created for init of mysqld
+ lex->current_select= 0;
+ user_time.val= start_time= start_time_sec_part= 0;
+ start_utime= prior_thr_create_utime= 0L;
+ utime_after_lock= 0L;
+ progress.arena= 0;
+ progress.report_to_client= 0;
+ progress.max_counter= 0;
+ current_linfo = 0;
+ slave_thread = 0;
+ connection_name.str= 0;
+ connection_name.length= 0;
+
+ bzero(&variables, sizeof(variables));
+ file_id = 0;
+ query_id= 0;
+ query_name_consts= 0;
+ db_charset= global_system_variables.collation_database;
+ bzero(ha_data, sizeof(ha_data));
+ mysys_var=0;
+ binlog_evt_union.do_union= FALSE;
+ enable_slow_log= 0;
+ durability_property= HA_REGULAR_DURABILITY;
+
+#ifndef DBUG_OFF
+ dbug_sentry=THD_SENTRY_MAGIC;
+#endif
+#ifndef EMBEDDED_LIBRARY
+ mysql_audit_init_thd(this);
+#endif
+ net.vio=0;
+ net.buff= 0;
+ client_capabilities= 0; // minimalistic client
+ system_thread= NON_SYSTEM_THREAD;
+ cleanup_done= abort_on_warning= 0;
+ peer_port= 0; // For SHOW PROCESSLIST
+ transaction.m_pending_rows_event= 0;
+ transaction.on= 1;
+ wt_thd_lazy_init(&transaction.wt, &variables.wt_deadlock_search_depth_short,
+ &variables.wt_timeout_short,
+ &variables.wt_deadlock_search_depth_long,
+ &variables.wt_timeout_long);
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ active_vio = 0;
+#endif
+ mysql_mutex_init(key_LOCK_thd_data, &LOCK_thd_data, MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(key_LOCK_wakeup_ready, &LOCK_wakeup_ready, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_wakeup_ready, &COND_wakeup_ready, 0);
+ /*
+ LOCK_thread_count goes before LOCK_thd_data - the former is called around
+ 'delete thd', the latter - in THD::~THD
+ */
+ mysql_mutex_record_order(&LOCK_thread_count, &LOCK_thd_data);
+
+ /* Variables with default values */
+ proc_info="login";
+ where= THD::DEFAULT_WHERE;
+ variables.server_id = global_system_variables.server_id;
+ slave_net = 0;
+ m_command=COM_CONNECT;
+ *scramble= '\0';
+
+ /* Call to init() below requires fully initialized Open_tables_state. */
+ reset_open_tables_state(this);
+
+ init();
+#if defined(ENABLED_PROFILING)
+ profiling.set_thd(this);
+#endif
+ user_connect=(USER_CONN *)0;
+ my_hash_init(&user_vars, system_charset_info, USER_VARS_HASH_SIZE, 0, 0,
+ (my_hash_get_key) get_var_key,
+ (my_hash_free_key) free_user_var, HASH_THREAD_SPECIFIC);
+
+ sp_proc_cache= NULL;
+ sp_func_cache= NULL;
+
+ /* For user vars replication*/
+ if (opt_bin_log)
+ my_init_dynamic_array(&user_var_events,
+ sizeof(BINLOG_USER_VAR_EVENT *), 16, 16, MYF(0));
+ else
+ bzero((char*) &user_var_events, sizeof(user_var_events));
+
+ /* Protocol */
+ protocol= &protocol_text; // Default protocol
+ protocol_text.init(this);
+ protocol_binary.init(this);
+
+ tablespace_op=FALSE;
+
+ /*
+ Initialize the random generator. We call my_rnd() without a lock as
+ it's not really critical if two threads modifies the structure at the
+ same time. We ensure that we have an unique number foreach thread
+ by adding the address of the stack.
+ */
+ tmp= (ulong) (my_rnd(&sql_rand) * 0xffffffff);
+ my_rnd_init(&rand, tmp + (ulong) &rand, tmp + (ulong) ::global_query_id);
+ substitute_null_with_insert_id = FALSE;
+ thr_lock_info_init(&lock_info); /* safety: will be reset after start */
+
+ m_token_array= NULL;
+ if (max_digest_length > 0)
+ {
+ m_token_array= (unsigned char*) my_malloc(max_digest_length,
+ MYF(MY_WME|MY_THREAD_SPECIFIC));
+ }
+
+ m_internal_handler= NULL;
+ m_binlog_invoker= INVOKER_NONE;
+ arena_for_cached_items= 0;
+ memset(&invoker_user, 0, sizeof(invoker_user));
+ memset(&invoker_host, 0, sizeof(invoker_host));
+ prepare_derived_at_open= FALSE;
+ create_tmp_table_for_derived= FALSE;
+ save_prep_leaf_list= FALSE;
+ /* Restore THR_THD */
+ set_current_thd(old_THR_THD);
+}
+
+
+void THD::push_internal_handler(Internal_error_handler *handler)
+{
+ DBUG_ENTER("THD::push_internal_handler");
+ if (m_internal_handler)
+ {
+ handler->m_prev_internal_handler= m_internal_handler;
+ m_internal_handler= handler;
+ }
+ else
+ {
+ m_internal_handler= handler;
+ }
+ DBUG_VOID_RETURN;
+}
+
+bool THD::handle_condition(uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+{
+ if (!m_internal_handler)
+ {
+ *cond_hdl= NULL;
+ return FALSE;
+ }
+
+ for (Internal_error_handler *error_handler= m_internal_handler;
+ error_handler;
+ error_handler= error_handler->m_prev_internal_handler)
+ {
+ if (error_handler->handle_condition(this, sql_errno, sqlstate, level, msg,
+ cond_hdl))
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+Internal_error_handler *THD::pop_internal_handler()
+{
+ DBUG_ENTER("THD::pop_internal_handler");
+ DBUG_ASSERT(m_internal_handler != NULL);
+ Internal_error_handler *popped_handler= m_internal_handler;
+ m_internal_handler= m_internal_handler->m_prev_internal_handler;
+ DBUG_RETURN(popped_handler);
+}
+
+
+void THD::raise_error(uint sql_errno)
+{
+ const char* msg= ER(sql_errno);
+ (void) raise_condition(sql_errno,
+ NULL,
+ Sql_condition::WARN_LEVEL_ERROR,
+ msg);
+}
+
+void THD::raise_error_printf(uint sql_errno, ...)
+{
+ va_list args;
+ char ebuff[MYSQL_ERRMSG_SIZE];
+ DBUG_ENTER("THD::raise_error_printf");
+ DBUG_PRINT("my", ("nr: %d errno: %d", sql_errno, errno));
+ const char* format= ER(sql_errno);
+ va_start(args, sql_errno);
+ my_vsnprintf(ebuff, sizeof(ebuff), format, args);
+ va_end(args);
+ (void) raise_condition(sql_errno,
+ NULL,
+ Sql_condition::WARN_LEVEL_ERROR,
+ ebuff);
+ DBUG_VOID_RETURN;
+}
+
+void THD::raise_warning(uint sql_errno)
+{
+ const char* msg= ER(sql_errno);
+ (void) raise_condition(sql_errno,
+ NULL,
+ Sql_condition::WARN_LEVEL_WARN,
+ msg);
+}
+
+void THD::raise_warning_printf(uint sql_errno, ...)
+{
+ va_list args;
+ char ebuff[MYSQL_ERRMSG_SIZE];
+ DBUG_ENTER("THD::raise_warning_printf");
+ DBUG_PRINT("enter", ("warning: %u", sql_errno));
+ const char* format= ER(sql_errno);
+ va_start(args, sql_errno);
+ my_vsnprintf(ebuff, sizeof(ebuff), format, args);
+ va_end(args);
+ (void) raise_condition(sql_errno,
+ NULL,
+ Sql_condition::WARN_LEVEL_WARN,
+ ebuff);
+ DBUG_VOID_RETURN;
+}
+
+void THD::raise_note(uint sql_errno)
+{
+ DBUG_ENTER("THD::raise_note");
+ DBUG_PRINT("enter", ("code: %d", sql_errno));
+ if (!(variables.option_bits & OPTION_SQL_NOTES))
+ DBUG_VOID_RETURN;
+ const char* msg= ER(sql_errno);
+ (void) raise_condition(sql_errno,
+ NULL,
+ Sql_condition::WARN_LEVEL_NOTE,
+ msg);
+ DBUG_VOID_RETURN;
+}
+
+void THD::raise_note_printf(uint sql_errno, ...)
+{
+ va_list args;
+ char ebuff[MYSQL_ERRMSG_SIZE];
+ DBUG_ENTER("THD::raise_note_printf");
+ DBUG_PRINT("enter",("code: %u", sql_errno));
+ if (!(variables.option_bits & OPTION_SQL_NOTES))
+ DBUG_VOID_RETURN;
+ const char* format= ER(sql_errno);
+ va_start(args, sql_errno);
+ my_vsnprintf(ebuff, sizeof(ebuff), format, args);
+ va_end(args);
+ (void) raise_condition(sql_errno,
+ NULL,
+ Sql_condition::WARN_LEVEL_NOTE,
+ ebuff);
+ DBUG_VOID_RETURN;
+}
+
+Sql_condition* THD::raise_condition(uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg)
+{
+ Diagnostics_area *da= get_stmt_da();
+ Sql_condition *cond= NULL;
+ DBUG_ENTER("THD::raise_condition");
+
+ if (!(variables.option_bits & OPTION_SQL_NOTES) &&
+ (level == Sql_condition::WARN_LEVEL_NOTE))
+ DBUG_RETURN(NULL);
+
+ da->opt_clear_warning_info(query_id);
+
+ /*
+ TODO: replace by DBUG_ASSERT(sql_errno != 0) once all bugs similar to
+ Bug#36768 are fixed: a SQL condition must have a real (!=0) error number
+ so that it can be caught by handlers.
+ */
+ if (sql_errno == 0)
+ sql_errno= ER_UNKNOWN_ERROR;
+ if (msg == NULL)
+ msg= ER(sql_errno);
+ if (sqlstate == NULL)
+ sqlstate= mysql_errno_to_sqlstate(sql_errno);
+
+ if ((level == Sql_condition::WARN_LEVEL_WARN) &&
+ really_abort_on_warning())
+ {
+ /*
+ FIXME:
+ push_warning and strict SQL_MODE case.
+ */
+ level= Sql_condition::WARN_LEVEL_ERROR;
+ killed= KILL_BAD_DATA;
+ }
+
+ switch (level)
+ {
+ case Sql_condition::WARN_LEVEL_NOTE:
+ case Sql_condition::WARN_LEVEL_WARN:
+ got_warning= 1;
+ break;
+ case Sql_condition::WARN_LEVEL_ERROR:
+ break;
+ default:
+ DBUG_ASSERT(FALSE);
+ }
+
+ if (handle_condition(sql_errno, sqlstate, level, msg, &cond))
+ DBUG_RETURN(cond);
+
+ if (level == Sql_condition::WARN_LEVEL_ERROR)
+ {
+ mysql_audit_general(this, MYSQL_AUDIT_GENERAL_ERROR, sql_errno, msg);
+
+ is_slave_error= 1; // needed to catch query errors during replication
+
+ if (!da->is_error())
+ {
+ set_row_count_func(-1);
+ da->set_error_status(sql_errno, msg, sqlstate, cond);
+ }
+ }
+
+ query_cache_abort(&query_cache_tls);
+
+ /*
+ Avoid pushing a condition for fatal out of memory errors as this will
+ require memory allocation and therefore might fail. Non fatal out of
+ memory errors can occur if raised by SIGNAL/RESIGNAL statement.
+ */
+ if (!(is_fatal_error && (sql_errno == EE_OUTOFMEMORY ||
+ sql_errno == ER_OUTOFMEMORY)))
+ {
+ cond= da->push_warning(this, sql_errno, sqlstate, level, msg);
+ }
+ DBUG_RETURN(cond);
+}
+
+extern "C"
+void *thd_alloc(MYSQL_THD thd, unsigned int size)
+{
+ return thd->alloc(size);
+}
+
+extern "C"
+void *thd_calloc(MYSQL_THD thd, unsigned int size)
+{
+ return thd->calloc(size);
+}
+
+extern "C"
+char *thd_strdup(MYSQL_THD thd, const char *str)
+{
+ return thd->strdup(str);
+}
+
+extern "C"
+char *thd_strmake(MYSQL_THD thd, const char *str, unsigned int size)
+{
+ return thd->strmake(str, size);
+}
+
+extern "C"
+LEX_STRING *thd_make_lex_string(THD *thd, LEX_STRING *lex_str,
+ const char *str, unsigned int size,
+ int allocate_lex_string)
+{
+ return allocate_lex_string ? thd->make_lex_string(str, size)
+ : thd->make_lex_string(lex_str, str, size);
+}
+
+extern "C"
+void *thd_memdup(MYSQL_THD thd, const void* str, unsigned int size)
+{
+ return thd->memdup(str, size);
+}
+
+extern "C"
+void thd_get_xid(const MYSQL_THD thd, MYSQL_XID *xid)
+{
+ *xid = *(MYSQL_XID *) &thd->transaction.xid_state.xid;
+}
+
+
+extern "C"
+my_time_t thd_TIME_to_gmt_sec(MYSQL_THD thd, const MYSQL_TIME *ltime,
+ unsigned int *errcode)
+{
+ Time_zone *tz= thd ? thd->variables.time_zone :
+ global_system_variables.time_zone;
+ return tz->TIME_to_gmt_sec(ltime, errcode);
+}
+
+
+extern "C"
+void thd_gmt_sec_to_TIME(MYSQL_THD thd, MYSQL_TIME *ltime, my_time_t t)
+{
+ Time_zone *tz= thd ? thd->variables.time_zone :
+ global_system_variables.time_zone;
+ tz->gmt_sec_to_TIME(ltime, t);
+}
+
+
+#ifdef _WIN32
+extern "C" THD *_current_thd_noinline(void)
+{
+ return my_pthread_getspecific_ptr(THD*,THR_THD);
+}
+#endif
+/*
+ Init common variables that has to be reset on start and on change_user
+*/
+
+void THD::init(void)
+{
+ DBUG_ENTER("thd::init");
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ plugin_thdvar_init(this);
+ /*
+ variables= global_system_variables above has reset
+ variables.pseudo_thread_id to 0. We need to correct it here to
+ avoid temporary tables replication failure.
+ */
+ variables.pseudo_thread_id= thread_id;
+
+ variables.default_master_connection.str= default_master_connection_buff;
+ ::strmake(variables.default_master_connection.str,
+ global_system_variables.default_master_connection.str,
+ variables.default_master_connection.length);
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ server_status= SERVER_STATUS_AUTOCOMMIT;
+ if (variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)
+ server_status|= SERVER_STATUS_NO_BACKSLASH_ESCAPES;
+
+ transaction.all.modified_non_trans_table=
+ transaction.stmt.modified_non_trans_table= FALSE;
+ open_options=ha_open_options;
+ update_lock_default= (variables.low_priority_updates ?
+ TL_WRITE_LOW_PRIORITY :
+ TL_WRITE);
+ tx_isolation= (enum_tx_isolation) variables.tx_isolation;
+ tx_read_only= variables.tx_read_only;
+ update_charset();
+ reset_current_stmt_binlog_format_row();
+ reset_binlog_local_stmt_filter();
+ set_status_var_init();
+ bzero((char *) &org_status_var, sizeof(org_status_var));
+ start_bytes_received= 0;
+ last_commit_gtid.seq_no= 0;
+
+ if (variables.sql_log_bin)
+ variables.option_bits|= OPTION_BIN_LOG;
+ else
+ variables.option_bits&= ~OPTION_BIN_LOG;
+
+ select_commands= update_commands= other_commands= 0;
+ /* Set to handle counting of aborted connections */
+ userstat_running= opt_userstat_running;
+ last_global_update_time= current_connect_time= time(NULL);
+#if defined(ENABLED_DEBUG_SYNC)
+ /* Initialize the Debug Sync Facility. See debug_sync.cc. */
+ debug_sync_init_thread(this);
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+ apc_target.init(&LOCK_thd_data);
+ DBUG_VOID_RETURN;
+}
+
+
+/* Updates some status variables to be used by update_global_user_stats */
+
+void THD::update_stats(void)
+{
+ /* sql_command == SQLCOM_END in case of parse errors or quit */
+ if (lex->sql_command != SQLCOM_END)
+ {
+ /* A SQL query. */
+ if (lex->sql_command == SQLCOM_SELECT)
+ select_commands++;
+ else if (sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND)
+ {
+ /* Ignore 'SHOW ' commands */
+ }
+ else if (is_update_query(lex->sql_command))
+ update_commands++;
+ else
+ other_commands++;
+ }
+}
+
+
+void THD::update_all_stats()
+{
+ ulonglong end_cpu_time, end_utime;
+ double busy_time, cpu_time;
+
+ /* This is set at start of query if opt_userstat_running was set */
+ if (!userstat_running)
+ return;
+
+ end_cpu_time= my_getcputime();
+ end_utime= microsecond_interval_timer();
+ busy_time= (end_utime - start_utime) / 1000000.0;
+ cpu_time= (end_cpu_time - start_cpu_time) / 10000000.0;
+ /* In case there are bad values, 2629743 is the #seconds in a month. */
+ if (cpu_time > 2629743.0)
+ cpu_time= 0;
+ status_var_add(status_var.cpu_time, cpu_time);
+ status_var_add(status_var.busy_time, busy_time);
+
+ update_global_user_stats(this, TRUE, my_time(0));
+ // Has to be updated after update_global_user_stats()
+ userstat_running= 0;
+}
+
+
+/*
+ Init THD for query processing.
+ This has to be called once before we call mysql_parse.
+ See also comments in sql_class.h.
+*/
+
+void THD::init_for_queries()
+{
+ set_time();
+ ha_enable_transaction(this,TRUE);
+
+ reset_root_defaults(mem_root, variables.query_alloc_block_size,
+ variables.query_prealloc_size);
+ reset_root_defaults(&transaction.mem_root,
+ variables.trans_alloc_block_size,
+ variables.trans_prealloc_size);
+ transaction.xid_state.xid.null();
+ transaction.xid_state.in_thd=1;
+}
+
+
+/*
+ Do what's needed when one invokes change user
+
+ SYNOPSIS
+ change_user()
+
+ IMPLEMENTATION
+ Reset all resources that are connection specific
+*/
+
+
+void THD::change_user(void)
+{
+ add_status_to_global();
+
+ cleanup();
+ reset_killed();
+ cleanup_done= 0;
+ init();
+ stmt_map.reset();
+ my_hash_init(&user_vars, system_charset_info, USER_VARS_HASH_SIZE, 0, 0,
+ (my_hash_get_key) get_var_key,
+ (my_hash_free_key) free_user_var, 0);
+ sp_cache_clear(&sp_proc_cache);
+ sp_cache_clear(&sp_func_cache);
+}
+
+
+/* Do operations that may take a long time */
+
+void THD::cleanup(void)
+{
+ DBUG_ENTER("THD::cleanup");
+ DBUG_ASSERT(cleanup_done == 0);
+
+ killed= KILL_CONNECTION;
+#ifdef ENABLE_WHEN_BINLOG_WILL_BE_ABLE_TO_PREPARE
+ if (transaction.xid_state.xa_state == XA_PREPARED)
+ {
+#error xid_state in the cache should be replaced by the allocated value
+ }
+#endif
+
+ mysql_ha_cleanup(this);
+ locked_tables_list.unlock_locked_tables(this);
+
+ delete_dynamic(&user_var_events);
+ close_temporary_tables(this);
+
+ transaction.xid_state.xa_state= XA_NOTR;
+ trans_rollback(this);
+ xid_cache_delete(&transaction.xid_state);
+
+ DBUG_ASSERT(open_tables == NULL);
+ /*
+ If the thread was in the middle of an ongoing transaction (rolled
+ back a few lines above) or under LOCK TABLES (unlocked the tables
+ and left the mode a few lines above), there will be outstanding
+ metadata locks. Release them.
+ */
+ mdl_context.release_transactional_locks();
+
+ /* Release the global read lock, if acquired. */
+ if (global_read_lock.is_acquired())
+ global_read_lock.unlock_global_read_lock(this);
+
+ if (user_connect)
+ {
+ decrease_user_connections(user_connect);
+ user_connect= 0; // Safety
+ }
+ wt_thd_destroy(&transaction.wt);
+
+#if defined(ENABLED_DEBUG_SYNC)
+ /* End the Debug Sync Facility. See debug_sync.cc. */
+ debug_sync_end_thread(this);
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
+ my_hash_free(&user_vars);
+ sp_cache_clear(&sp_proc_cache);
+ sp_cache_clear(&sp_func_cache);
+
+ mysql_ull_cleanup(this);
+ /* All metadata locks must have been released by now. */
+ DBUG_ASSERT(!mdl_context.has_locks());
+
+ apc_target.destroy();
+ cleanup_done=1;
+ DBUG_VOID_RETURN;
+}
+
+
+THD::~THD()
+{
+ THD *orig_thd= current_thd;
+ THD_CHECK_SENTRY(this);
+ DBUG_ENTER("~THD()");
+
+ /*
+ In error cases, thd may not be current thd. We have to fix this so
+ that memory allocation counting is done correctly
+ */
+ set_current_thd(this);
+
+ /* Ensure that no one is using THD */
+ mysql_mutex_lock(&LOCK_thd_data);
+ mysql_mutex_unlock(&LOCK_thd_data);
+
+ /* Close connection */
+#ifndef EMBEDDED_LIBRARY
+ if (net.vio)
+ vio_delete(net.vio);
+ net_end(&net);
+#endif
+ stmt_map.reset(); /* close all prepared statements */
+ if (!cleanup_done)
+ cleanup();
+
+ mdl_context.destroy();
+ ha_close_connection(this);
+ mysql_audit_release(this);
+ plugin_thdvar_cleanup(this);
+
+ main_security_ctx.destroy();
+ my_free(db);
+ db= NULL;
+ free_root(&transaction.mem_root,MYF(0));
+ mysql_cond_destroy(&COND_wakeup_ready);
+ mysql_mutex_destroy(&LOCK_wakeup_ready);
+ mysql_mutex_destroy(&LOCK_thd_data);
+#ifndef DBUG_OFF
+ dbug_sentry= THD_SENTRY_GONE;
+#endif
+#ifndef EMBEDDED_LIBRARY
+ if (rgi_fake)
+ {
+ delete rgi_fake;
+ rgi_fake= NULL;
+ }
+ if (rli_fake)
+ {
+ delete rli_fake;
+ rli_fake= NULL;
+ }
+
+ mysql_audit_free_thd(this);
+ if (rgi_slave)
+ rgi_slave->cleanup_after_session();
+#endif
+
+ free_root(&main_mem_root, MYF(0));
+ my_free(m_token_array);
+ main_da.free_memory();
+ if (status_var.memory_used != 0)
+ {
+ DBUG_PRINT("error", ("memory_used: %lld", status_var.memory_used));
+ SAFEMALLOC_REPORT_MEMORY(my_thread_dbug_id());
+ DBUG_ASSERT(status_var.memory_used == 0); // Ensure everything is freed
+ }
+
+ set_current_thd(orig_thd);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Add all status variables to another status variable array
+
+ SYNOPSIS
+ add_to_status()
+ to_var add to this array
+ from_var from this array
+
+ NOTES
+ This function assumes that all variables at start are long/ulong and
+ other types are handled explicitely
+*/
+
+void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var)
+{
+ ulong *end= (ulong*) ((uchar*) to_var +
+ offsetof(STATUS_VAR, last_system_status_var) +
+ sizeof(ulong));
+ ulong *to= (ulong*) to_var, *from= (ulong*) from_var;
+
+ while (to != end)
+ *(to++)+= *(from++);
+
+ /* Handle the not ulong variables. See end of system_status_var */
+ to_var->bytes_received+= from_var->bytes_received;
+ to_var->bytes_sent+= from_var->bytes_sent;
+ to_var->rows_read+= from_var->rows_read;
+ to_var->rows_sent+= from_var->rows_sent;
+ to_var->rows_tmp_read+= from_var->rows_tmp_read;
+ to_var->binlog_bytes_written+= from_var->binlog_bytes_written;
+ to_var->cpu_time+= from_var->cpu_time;
+ to_var->busy_time+= from_var->busy_time;
+}
+
+/*
+ Add the difference between two status variable arrays to another one.
+
+ SYNOPSIS
+ add_diff_to_status
+ to_var add to this array
+ from_var from this array
+ dec_var minus this array
+
+ NOTE
+ This function assumes that all variables at start are long/ulong and
+ other types are handled explicitely
+*/
+
+void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var,
+ STATUS_VAR *dec_var)
+{
+ ulong *end= (ulong*) ((uchar*) to_var + offsetof(STATUS_VAR,
+ last_system_status_var) +
+ sizeof(ulong));
+ ulong *to= (ulong*) to_var, *from= (ulong*) from_var, *dec= (ulong*) dec_var;
+
+ while (to != end)
+ *(to++)+= *(from++) - *(dec++);
+
+ to_var->bytes_received+= from_var->bytes_received -
+ dec_var->bytes_received;
+ to_var->bytes_sent+= from_var->bytes_sent - dec_var->bytes_sent;
+ to_var->rows_read+= from_var->rows_read - dec_var->rows_read;
+ to_var->rows_sent+= from_var->rows_sent - dec_var->rows_sent;
+ to_var->rows_tmp_read+= from_var->rows_tmp_read - dec_var->rows_tmp_read;
+ to_var->binlog_bytes_written+= from_var->binlog_bytes_written -
+ dec_var->binlog_bytes_written;
+ to_var->cpu_time+= from_var->cpu_time - dec_var->cpu_time;
+ to_var->busy_time+= from_var->busy_time - dec_var->busy_time;
+}
+
+#define SECONDS_TO_WAIT_FOR_KILL 2
+#if !defined(__WIN__) && defined(HAVE_SELECT)
+/* my_sleep() can wait for sub second times */
+#define WAIT_FOR_KILL_TRY_TIMES 20
+#else
+#define WAIT_FOR_KILL_TRY_TIMES 2
+#endif
+
+
+/**
+ Awake a thread.
+
+ @param[in] state_to_set value for THD::killed
+
+ This is normally called from another thread's THD object.
+
+ @note Do always call this while holding LOCK_thd_data.
+*/
+
+void THD::awake(killed_state state_to_set)
+{
+ DBUG_ENTER("THD::awake");
+ DBUG_PRINT("enter", ("this: %p current_thd: %p", this, current_thd));
+ THD_CHECK_SENTRY(this);
+ mysql_mutex_assert_owner(&LOCK_thd_data);
+
+ print_aborted_warning(3, "KILLED");
+
+ /* Set the 'killed' flag of 'this', which is the target THD object. */
+ killed= state_to_set;
+
+ if (state_to_set >= KILL_CONNECTION || state_to_set == NOT_KILLED)
+ {
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ if (this != current_thd)
+ {
+ if(active_vio)
+ vio_shutdown(active_vio, SHUT_RDWR);
+ }
+#endif
+
+ /* Mark the target thread's alarm request expired, and signal alarm. */
+ thr_alarm_kill(thread_id);
+
+ /* Send an event to the scheduler that a thread should be killed. */
+ if (!slave_thread)
+ MYSQL_CALLBACK(scheduler, post_kill_notification, (this));
+ }
+
+ /* Interrupt target waiting inside a storage engine. */
+ if (state_to_set != NOT_KILLED)
+ ha_kill_query(this, thd_kill_level(this));
+
+ /* Broadcast a condition to kick the target if it is waiting on it. */
+ if (mysys_var)
+ {
+ mysql_mutex_lock(&mysys_var->mutex);
+ if (!system_thread) // Don't abort locks
+ mysys_var->abort=1;
+ /*
+ This broadcast could be up in the air if the victim thread
+ exits the cond in the time between read and broadcast, but that is
+ ok since all we want to do is to make the victim thread get out
+ of waiting on current_cond.
+ If we see a non-zero current_cond: it cannot be an old value (because
+ then exit_cond() should have run and it can't because we have mutex); so
+ it is the true value but maybe current_mutex is not yet non-zero (we're
+ in the middle of enter_cond() and there is a "memory order
+ inversion"). So we test the mutex too to not lock 0.
+
+ Note that there is a small chance we fail to kill. If victim has locked
+ current_mutex, but hasn't yet entered enter_cond() (which means that
+ current_cond and current_mutex are 0), then the victim will not get
+ a signal and it may wait "forever" on the cond (until
+ we issue a second KILL or the status it's waiting for happens).
+ It's true that we have set its thd->killed but it may not
+ see it immediately and so may have time to reach the cond_wait().
+
+ However, where possible, we test for killed once again after
+ enter_cond(). This should make the signaling as safe as possible.
+ However, there is still a small chance of failure on platforms with
+ instruction or memory write reordering.
+
+ We have to do the loop with trylock, because if we would use
+ pthread_mutex_lock(), we can cause a deadlock as we are here locking
+ the mysys_var->mutex and mysys_var->current_mutex in a different order
+ than in the thread we are trying to kill.
+ We only sleep for 2 seconds as we don't want to have LOCK_thd_data
+ locked too long time.
+
+ There is a small change we may not succeed in aborting a thread that
+ is not yet waiting for a mutex, but as this happens only for a
+ thread that was doing something else when the kill was issued and
+ which should detect the kill flag before it starts to wait, this
+ should be good enough.
+ */
+ if (mysys_var->current_cond && mysys_var->current_mutex)
+ {
+ uint i;
+ for (i= 0; i < WAIT_FOR_KILL_TRY_TIMES * SECONDS_TO_WAIT_FOR_KILL; i++)
+ {
+ int ret= mysql_mutex_trylock(mysys_var->current_mutex);
+ mysql_cond_broadcast(mysys_var->current_cond);
+ if (!ret)
+ {
+ /* Signal is sure to get through */
+ mysql_mutex_unlock(mysys_var->current_mutex);
+ break;
+ }
+ my_sleep(1000000L / WAIT_FOR_KILL_TRY_TIMES);
+ }
+ }
+ mysql_mutex_unlock(&mysys_var->mutex);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Close the Vio associated this session.
+
+ @remark LOCK_thd_data is taken due to the fact that
+ the Vio might be disassociated concurrently.
+*/
+
+void THD::disconnect()
+{
+ Vio *vio= NULL;
+
+ mysql_mutex_lock(&LOCK_thd_data);
+
+ killed= KILL_CONNECTION;
+
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ /*
+ Since a active vio might might have not been set yet, in
+ any case save a reference to avoid closing a inexistent
+ one or closing the vio twice if there is a active one.
+ */
+ vio= active_vio;
+ close_active_vio();
+#endif
+
+ /* Disconnect even if a active vio is not associated. */
+ if (net.vio != vio)
+ vio_close(net.vio);
+
+ mysql_mutex_unlock(&LOCK_thd_data);
+}
+
+
+bool THD::notify_shared_lock(MDL_context_owner *ctx_in_use,
+ bool needs_thr_lock_abort)
+{
+ THD *in_use= ctx_in_use->get_thd();
+ bool signalled= FALSE;
+
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ !in_use->killed)
+ {
+ in_use->killed= KILL_CONNECTION;
+ mysql_mutex_lock(&in_use->mysys_var->mutex);
+ if (in_use->mysys_var->current_cond)
+ mysql_cond_broadcast(in_use->mysys_var->current_cond);
+ mysql_mutex_unlock(&in_use->mysys_var->mutex);
+ signalled= TRUE;
+ }
+
+ if (needs_thr_lock_abort)
+ {
+ mysql_mutex_lock(&in_use->LOCK_thd_data);
+ for (TABLE *thd_table= in_use->open_tables;
+ thd_table ;
+ thd_table= thd_table->next)
+ {
+ /*
+ Check for TABLE::needs_reopen() is needed since in some places we call
+ handler::close() for table instance (and set TABLE::db_stat to 0)
+ and do not remove such instances from the THD::open_tables
+ for some time, during which other thread can see those instances
+ (e.g. see partitioning code).
+ */
+ if (!thd_table->needs_reopen())
+ signalled|= mysql_lock_abort_for_thread(this, thd_table);
+ }
+ mysql_mutex_unlock(&in_use->LOCK_thd_data);
+ }
+ return signalled;
+}
+
+
+/*
+ Get error number for killed state
+ Note that the error message can't have any parameters.
+ See thd::kill_message()
+*/
+
+int killed_errno(killed_state killed)
+{
+ DBUG_ENTER("killed_errno");
+ DBUG_PRINT("enter", ("killed: %d", killed));
+
+ switch (killed) {
+ case NOT_KILLED:
+ case KILL_HARD_BIT:
+ DBUG_RETURN(0); // Probably wrong usage
+ case KILL_BAD_DATA:
+ case KILL_BAD_DATA_HARD:
+ case ABORT_QUERY_HARD:
+ case ABORT_QUERY:
+ DBUG_RETURN(0); // Not a real error
+ case KILL_CONNECTION:
+ case KILL_CONNECTION_HARD:
+ case KILL_SYSTEM_THREAD:
+ case KILL_SYSTEM_THREAD_HARD:
+ DBUG_RETURN(ER_CONNECTION_KILLED);
+ case KILL_QUERY:
+ case KILL_QUERY_HARD:
+ DBUG_RETURN(ER_QUERY_INTERRUPTED);
+ case KILL_SERVER:
+ case KILL_SERVER_HARD:
+ DBUG_RETURN(ER_SERVER_SHUTDOWN);
+ }
+ DBUG_RETURN(0); // Keep compiler happy
+}
+
+
+/*
+ Remember the location of thread info, the structure needed for
+ sql_alloc() and the structure for the net buffer
+*/
+
+bool THD::store_globals()
+{
+ /*
+ Assert that thread_stack is initialized: it's necessary to be able
+ to track stack overrun.
+ */
+ DBUG_ASSERT(thread_stack);
+
+ if (set_current_thd(this) ||
+ my_pthread_setspecific_ptr(THR_MALLOC, &mem_root))
+ return 1;
+ /*
+ mysys_var is concurrently readable by a killer thread.
+ It is protected by LOCK_thd_data, it is not needed to lock while the
+ pointer is changing from NULL not non-NULL. If the kill thread reads
+ NULL it doesn't refer to anything, but if it is non-NULL we need to
+ ensure that the thread doesn't proceed to assign another thread to
+ have the mysys_var reference (which in fact refers to the worker
+ threads local storage with key THR_KEY_mysys.
+ */
+ mysys_var=my_thread_var;
+ /*
+ Let mysqld define the thread id (not mysys)
+ This allows us to move THD to different threads if needed.
+ */
+ mysys_var->id= thread_id;
+ real_id= pthread_self(); // For debugging
+ mysys_var->stack_ends_here= thread_stack + // for consistency, see libevent_thread_proc
+ STACK_DIRECTION * (long)my_thread_stack_size;
+ vio_set_thread_id(net.vio, real_id);
+ /*
+ We have to call thr_lock_info_init() again here as THD may have been
+ created in another thread
+ */
+ thr_lock_info_init(&lock_info);
+
+ return 0;
+}
+
+/**
+ Untie THD from current thread
+
+ Used when using --thread-handling=pool-of-threads
+*/
+
+void THD::reset_globals()
+{
+ mysql_mutex_lock(&LOCK_thd_data);
+ mysys_var= 0;
+ mysql_mutex_unlock(&LOCK_thd_data);
+
+ /* Undocking the thread specific data. */
+ set_current_thd(0);
+ my_pthread_setspecific_ptr(THR_MALLOC, NULL);
+
+}
+
+/*
+ Cleanup after query.
+
+ SYNOPSIS
+ THD::cleanup_after_query()
+
+ DESCRIPTION
+ This function is used to reset thread data to its default state.
+
+ NOTE
+ This function is not suitable for setting thread data to some
+ non-default values, as there is only one replication thread, so
+ different master threads may overwrite data of each other on
+ slave.
+*/
+
+void THD::cleanup_after_query()
+{
+ DBUG_ENTER("THD::cleanup_after_query");
+
+ thd_progress_end(this);
+
+ /*
+ Reset rand_used so that detection of calls to rand() will save random
+ seeds if needed by the slave.
+
+ Do not reset rand_used if inside a stored function or trigger because
+ only the call to these operations is logged. Thus only the calling
+ statement needs to detect rand() calls made by its substatements. These
+ substatements must not set rand_used to 0 because it would remove the
+ detection of rand() by the calling statement.
+ */
+ if (!in_sub_stmt) /* stored functions and triggers are a special case */
+ {
+ /* Forget those values, for next binlogger: */
+ stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0;
+ auto_inc_intervals_in_cur_stmt_for_binlog.empty();
+ rand_used= 0;
+#ifndef EMBEDDED_LIBRARY
+ /*
+ Clean possible unused INSERT_ID events by current statement.
+ is_update_query() is needed to ignore SET statements:
+ Statements that don't update anything directly and don't
+ used stored functions. This is mostly necessary to ignore
+ statements in binlog between SET INSERT_ID and DML statement
+ which is intended to consume its event (there can be other
+ SET statements between them).
+ */
+ if ((rgi_slave || rli_fake) && is_update_query(lex->sql_command))
+ auto_inc_intervals_forced.empty();
+#endif
+ }
+ /*
+ Forget the binlog stmt filter for the next query.
+ There are some code paths that:
+ - do not call THD::decide_logging_format()
+ - do call THD::binlog_query(),
+ making this reset necessary.
+ */
+ reset_binlog_local_stmt_filter();
+ if (first_successful_insert_id_in_cur_stmt > 0)
+ {
+ /* set what LAST_INSERT_ID() will return */
+ first_successful_insert_id_in_prev_stmt=
+ first_successful_insert_id_in_cur_stmt;
+ first_successful_insert_id_in_cur_stmt= 0;
+ substitute_null_with_insert_id= TRUE;
+ }
+ arg_of_last_insert_id_function= 0;
+ /* Free Items that were created during this execution */
+ free_items();
+ /* Reset where. */
+ where= THD::DEFAULT_WHERE;
+ /* reset table map for multi-table update */
+ table_map_for_update= 0;
+ m_binlog_invoker= INVOKER_NONE;
+
+#ifndef EMBEDDED_LIBRARY
+ if (rgi_slave)
+ rgi_slave->cleanup_after_query();
+#endif
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Convert a string to another character set
+
+ SYNOPSIS
+ convert_string()
+ to Store new allocated string here
+ to_cs New character set for allocated string
+ from String to convert
+ from_length Length of string to convert
+ from_cs Original character set
+
+ NOTES
+ to will be 0-terminated to make it easy to pass to system funcs
+
+ RETURN
+ 0 ok
+ 1 End of memory.
+ In this case to->str will point to 0 and to->length will be 0.
+*/
+
+bool THD::convert_string(LEX_STRING *to, CHARSET_INFO *to_cs,
+ const char *from, uint from_length,
+ CHARSET_INFO *from_cs)
+{
+ DBUG_ENTER("convert_string");
+ size_t new_length= to_cs->mbmaxlen * from_length;
+ uint dummy_errors;
+ if (!(to->str= (char*) alloc(new_length+1)))
+ {
+ to->length= 0; // Safety fix
+ DBUG_RETURN(1); // EOM
+ }
+ to->length= copy_and_convert((char*) to->str, new_length, to_cs,
+ from, from_length, from_cs, &dummy_errors);
+ to->str[to->length]=0; // Safety
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Convert string from source character set to target character set inplace.
+
+ SYNOPSIS
+ THD::convert_string
+
+ DESCRIPTION
+ Convert string using convert_buffer - buffer for character set
+ conversion shared between all protocols.
+
+ RETURN
+ 0 ok
+ !0 out of memory
+*/
+
+bool THD::convert_string(String *s, CHARSET_INFO *from_cs, CHARSET_INFO *to_cs)
+{
+ uint dummy_errors;
+ if (convert_buffer.copy(s->ptr(), s->length(), from_cs, to_cs, &dummy_errors))
+ return TRUE;
+ /* If convert_buffer >> s copying is more efficient long term */
+ if (convert_buffer.alloced_length() >= convert_buffer.length() * 2 ||
+ !s->is_alloced())
+ {
+ return s->copy(convert_buffer);
+ }
+ s->swap(convert_buffer);
+ return FALSE;
+}
+
+
+/*
+ Update some cache variables when character set changes
+*/
+
+void THD::update_charset()
+{
+ uint32 not_used;
+ charset_is_system_charset=
+ !String::needs_conversion(0,
+ variables.character_set_client,
+ system_charset_info,
+ &not_used);
+ charset_is_collation_connection=
+ !String::needs_conversion(0,
+ variables.character_set_client,
+ variables.collation_connection,
+ &not_used);
+ charset_is_character_set_filesystem=
+ !String::needs_conversion(0,
+ variables.character_set_client,
+ variables.character_set_filesystem,
+ &not_used);
+}
+
+
+/* routings to adding tables to list of changed in transaction tables */
+
+inline static void list_include(CHANGED_TABLE_LIST** prev,
+ CHANGED_TABLE_LIST* curr,
+ CHANGED_TABLE_LIST* new_table)
+{
+ if (new_table)
+ {
+ *prev = new_table;
+ (*prev)->next = curr;
+ }
+}
+
+/* add table to list of changed in transaction tables */
+
+void THD::add_changed_table(TABLE *table)
+{
+ DBUG_ENTER("THD::add_changed_table(table)");
+
+ DBUG_ASSERT(in_multi_stmt_transaction_mode() && table->file->has_transactions());
+ add_changed_table(table->s->table_cache_key.str,
+ (long) table->s->table_cache_key.length);
+ DBUG_VOID_RETURN;
+}
+
+
+void THD::add_changed_table(const char *key, long key_length)
+{
+ DBUG_ENTER("THD::add_changed_table(key)");
+ CHANGED_TABLE_LIST **prev_changed = &transaction.changed_tables;
+ CHANGED_TABLE_LIST *curr = transaction.changed_tables;
+
+ for (; curr; prev_changed = &(curr->next), curr = curr->next)
+ {
+ int cmp = (long)curr->key_length - (long)key_length;
+ if (cmp < 0)
+ {
+ list_include(prev_changed, curr, changed_table_dup(key, key_length));
+ DBUG_PRINT("info",
+ ("key_length: %ld %u", key_length,
+ (*prev_changed)->key_length));
+ DBUG_VOID_RETURN;
+ }
+ else if (cmp == 0)
+ {
+ cmp = memcmp(curr->key, key, curr->key_length);
+ if (cmp < 0)
+ {
+ list_include(prev_changed, curr, changed_table_dup(key, key_length));
+ DBUG_PRINT("info",
+ ("key_length: %ld %u", key_length,
+ (*prev_changed)->key_length));
+ DBUG_VOID_RETURN;
+ }
+ else if (cmp == 0)
+ {
+ DBUG_PRINT("info", ("already in list"));
+ DBUG_VOID_RETURN;
+ }
+ }
+ }
+ *prev_changed = changed_table_dup(key, key_length);
+ DBUG_PRINT("info", ("key_length: %ld %u", key_length,
+ (*prev_changed)->key_length));
+ DBUG_VOID_RETURN;
+}
+
+
+CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length)
+{
+ CHANGED_TABLE_LIST* new_table =
+ (CHANGED_TABLE_LIST*) trans_alloc(ALIGN_SIZE(sizeof(CHANGED_TABLE_LIST))+
+ key_length + 1);
+ if (!new_table)
+ {
+ my_error(EE_OUTOFMEMORY, MYF(ME_BELL+ME_FATALERROR),
+ ALIGN_SIZE(sizeof(TABLE_LIST)) + key_length + 1);
+ killed= KILL_CONNECTION;
+ return 0;
+ }
+
+ new_table->key= ((char*)new_table)+ ALIGN_SIZE(sizeof(CHANGED_TABLE_LIST));
+ new_table->next = 0;
+ new_table->key_length = key_length;
+ ::memcpy(new_table->key, key, key_length);
+ return new_table;
+}
+
+
+int THD::send_explain_fields(select_result *result)
+{
+ List<Item> field_list;
+ make_explain_field_list(field_list);
+ result->prepare(field_list, NULL);
+ return (result->send_result_set_metadata(field_list,
+ Protocol::SEND_NUM_ROWS |
+ Protocol::SEND_EOF));
+}
+
+
+/*
+ Populate the provided field_list with EXPLAIN output columns.
+ this->lex->describe has the EXPLAIN flags
+*/
+
+void THD::make_explain_field_list(List<Item> &field_list)
+{
+ Item *item;
+ CHARSET_INFO *cs= system_charset_info;
+ field_list.push_back(item= new Item_return_int("id",3, MYSQL_TYPE_LONGLONG));
+ item->maybe_null= 1;
+ field_list.push_back(new Item_empty_string("select_type", 19, cs));
+ field_list.push_back(item= new Item_empty_string("table", NAME_CHAR_LEN, cs));
+ item->maybe_null= 1;
+ if (lex->describe & DESCRIBE_PARTITIONS)
+ {
+ /* Maximum length of string that make_used_partitions_str() can produce */
+ item= new Item_empty_string("partitions", MAX_PARTITIONS * (1 + FN_LEN),
+ cs);
+ field_list.push_back(item);
+ item->maybe_null= 1;
+ }
+ field_list.push_back(item= new Item_empty_string("type", 10, cs));
+ item->maybe_null= 1;
+ field_list.push_back(item=new Item_empty_string("possible_keys",
+ NAME_CHAR_LEN*MAX_KEY, cs));
+ item->maybe_null=1;
+ field_list.push_back(item=new Item_empty_string("key", NAME_CHAR_LEN, cs));
+ item->maybe_null=1;
+ field_list.push_back(item=new Item_empty_string("key_len",
+ NAME_CHAR_LEN*MAX_KEY));
+ item->maybe_null=1;
+ field_list.push_back(item=new Item_empty_string("ref",
+ NAME_CHAR_LEN*MAX_REF_PARTS,
+ cs));
+ item->maybe_null=1;
+ field_list.push_back(item= new Item_return_int("rows", 10,
+ MYSQL_TYPE_LONGLONG));
+ if (lex->describe & DESCRIBE_EXTENDED)
+ {
+ field_list.push_back(item= new Item_float("filtered", 0.1234, 2, 4));
+ item->maybe_null=1;
+ }
+ item->maybe_null= 1;
+ field_list.push_back(new Item_empty_string("Extra", 255, cs));
+}
+
+
+#ifdef SIGNAL_WITH_VIO_CLOSE
+void THD::close_active_vio()
+{
+ DBUG_ENTER("close_active_vio");
+ mysql_mutex_assert_owner(&LOCK_thd_data);
+#ifndef EMBEDDED_LIBRARY
+ if (active_vio)
+ {
+ vio_close(active_vio);
+ active_vio = 0;
+ }
+#endif
+ DBUG_VOID_RETURN;
+}
+#endif
+
+
+struct Item_change_record: public ilink
+{
+ Item **place;
+ Item *old_value;
+ /* Placement new was hidden by `new' in ilink (TODO: check): */
+ static void *operator new(size_t size, void *mem) { return mem; }
+ static void operator delete(void *ptr, size_t size) {}
+ static void operator delete(void *ptr, void *mem) { /* never called */ }
+};
+
+
+/*
+ Register an item tree tree transformation, performed by the query
+ optimizer. We need a pointer to runtime_memroot because it may be !=
+ thd->mem_root (due to possible set_n_backup_active_arena called for thd).
+*/
+
+void THD::nocheck_register_item_tree_change(Item **place, Item *old_value,
+ MEM_ROOT *runtime_memroot)
+{
+ Item_change_record *change;
+ /*
+ Now we use one node per change, which adds some memory overhead,
+ but still is rather fast as we use alloc_root for allocations.
+ A list of item tree changes of an average query should be short.
+ */
+ void *change_mem= alloc_root(runtime_memroot, sizeof(*change));
+ if (change_mem == 0)
+ {
+ /*
+ OOM, thd->fatal_error() is called by the error handler of the
+ memroot. Just return.
+ */
+ return;
+ }
+ change= new (change_mem) Item_change_record;
+ change->place= place;
+ change->old_value= old_value;
+ change_list.append(change);
+}
+
+/**
+ Check and register item change if needed
+
+ @param place place where we should assign new value
+ @param new_value place of the new value
+
+ @details
+ Let C be a reference to an item that changed the reference A
+ at the location (occurrence) L1 and this change has been registered.
+ If C is substituted for reference A another location (occurrence) L2
+ that is to be registered as well than this change has to be
+ consistent with the first change in order the procedure that rollback
+ changes to substitute the same reference at both locations L1 and L2.
+*/
+
+void THD::check_and_register_item_tree_change(Item **place, Item **new_value,
+ MEM_ROOT *runtime_memroot)
+{
+ Item_change_record *change;
+ I_List_iterator<Item_change_record> it(change_list);
+ while ((change= it++))
+ {
+ if (change->place == new_value)
+ break; // we need only very first value
+ }
+ if (change)
+ nocheck_register_item_tree_change(place, change->old_value,
+ runtime_memroot);
+}
+
+
+void THD::rollback_item_tree_changes()
+{
+ I_List_iterator<Item_change_record> it(change_list);
+ Item_change_record *change;
+ DBUG_ENTER("rollback_item_tree_changes");
+
+ while ((change= it++))
+ *change->place= change->old_value;
+ /* We can forget about changes memory: it's allocated in runtime memroot */
+ change_list.empty();
+ DBUG_VOID_RETURN;
+}
+
+
+/*****************************************************************************
+** Functions to provide a interface to select results
+*****************************************************************************/
+
+select_result::select_result()
+{
+ thd=current_thd;
+}
+
+void select_result::cleanup()
+{
+ /* do nothing */
+}
+
+bool select_result::check_simple_select() const
+{
+ my_error(ER_SP_BAD_CURSOR_QUERY, MYF(0));
+ return TRUE;
+}
+
+
+static String default_line_term("\n",default_charset_info);
+static String default_escaped("\\",default_charset_info);
+static String default_field_term("\t",default_charset_info);
+static String default_enclosed_and_line_start("", default_charset_info);
+static String default_xml_row_term("<row>", default_charset_info);
+
+sql_exchange::sql_exchange(char *name, bool flag,
+ enum enum_filetype filetype_arg)
+ :file_name(name), opt_enclosed(0), dumpfile(flag), skip_lines(0)
+{
+ filetype= filetype_arg;
+ field_term= &default_field_term;
+ enclosed= line_start= &default_enclosed_and_line_start;
+ line_term= filetype == FILETYPE_CSV ?
+ &default_line_term : &default_xml_row_term;
+ escaped= &default_escaped;
+ cs= NULL;
+}
+
+bool sql_exchange::escaped_given(void)
+{
+ return escaped != &default_escaped;
+}
+
+
+bool select_send::send_result_set_metadata(List<Item> &list, uint flags)
+{
+ bool res;
+ if (!(res= thd->protocol->send_result_set_metadata(&list, flags)))
+ is_result_set_started= 1;
+ return res;
+}
+
+void select_send::abort_result_set()
+{
+ DBUG_ENTER("select_send::abort_result_set");
+
+ if (is_result_set_started && thd->spcont)
+ {
+ /*
+ We're executing a stored procedure, have an open result
+ set and an SQL exception condition. In this situation we
+ must abort the current statement, silence the error and
+ start executing the continue/exit handler if one is found.
+ Before aborting the statement, let's end the open result set, as
+ otherwise the client will hang due to the violation of the
+ client/server protocol.
+ */
+ thd->spcont->end_partial_result_set= TRUE;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Cleanup an instance of this class for re-use
+ at next execution of a prepared statement/
+ stored procedure statement.
+*/
+
+void select_send::cleanup()
+{
+ is_result_set_started= FALSE;
+}
+
+/* Send data to client. Returns 0 if ok */
+
+int select_send::send_data(List<Item> &items)
+{
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("select_send::send_data");
+
+ /* unit is not set when using 'delete ... returning' */
+ if (unit && unit->offset_limit_cnt)
+ { // using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(FALSE);
+ }
+ if (thd->killed == ABORT_QUERY)
+ DBUG_RETURN(FALSE);
+
+ /*
+ We may be passing the control from mysqld to the client: release the
+ InnoDB adaptive hash S-latch to avoid thread deadlocks if it was reserved
+ by thd
+ */
+ ha_release_temporary_latches(thd);
+
+ protocol->prepare_for_resend();
+ if (protocol->send_result_set_row(&items))
+ {
+ protocol->remove_last_row();
+ DBUG_RETURN(TRUE);
+ }
+
+ thd->inc_sent_row_count(1);
+
+ if (thd->vio_ok())
+ DBUG_RETURN(protocol->write());
+
+ DBUG_RETURN(0);
+}
+
+
+bool select_send::send_eof()
+{
+ /*
+ We may be passing the control from mysqld to the client: release the
+ InnoDB adaptive hash S-latch to avoid thread deadlocks if it was reserved
+ by thd
+ */
+ ha_release_temporary_latches(thd);
+
+ /*
+ Don't send EOF if we're in error condition (which implies we've already
+ sent or are sending an error)
+ */
+ if (thd->is_error())
+ return TRUE;
+ ::my_eof(thd);
+ is_result_set_started= 0;
+ return FALSE;
+}
+
+
+/************************************************************************
+ Handling writing to file
+************************************************************************/
+
+bool select_to_file::send_eof()
+{
+ int error= MY_TEST(end_io_cache(&cache));
+ if (mysql_file_close(file, MYF(MY_WME)) || thd->is_error())
+ error= true;
+
+ if (!error)
+ {
+ ::my_ok(thd,row_count);
+ }
+ file= -1;
+ return error;
+}
+
+
+void select_to_file::cleanup()
+{
+ /* In case of error send_eof() may be not called: close the file here. */
+ if (file >= 0)
+ {
+ (void) end_io_cache(&cache);
+ mysql_file_close(file, MYF(0));
+ file= -1;
+ }
+ path[0]= '\0';
+ row_count= 0;
+}
+
+
+select_to_file::~select_to_file()
+{
+ if (file >= 0)
+ { // This only happens in case of error
+ (void) end_io_cache(&cache);
+ mysql_file_close(file, MYF(0));
+ file= -1;
+ }
+}
+
+/***************************************************************************
+** Export of select to textfile
+***************************************************************************/
+
+select_export::~select_export()
+{
+ thd->set_sent_row_count(row_count);
+}
+
+
+/*
+ Create file with IO cache
+
+ SYNOPSIS
+ create_file()
+ thd Thread handle
+ path File name
+ exchange Excange class
+ cache IO cache
+
+ RETURN
+ >= 0 File handle
+ -1 Error
+*/
+
+
+static File create_file(THD *thd, char *path, sql_exchange *exchange,
+ IO_CACHE *cache)
+{
+ File file;
+ uint option= MY_UNPACK_FILENAME | MY_RELATIVE_PATH;
+
+#ifdef DONT_ALLOW_FULL_LOAD_DATA_PATHS
+ option|= MY_REPLACE_DIR; // Force use of db directory
+#endif
+
+ if (!dirname_length(exchange->file_name))
+ {
+ strxnmov(path, FN_REFLEN-1, mysql_real_data_home, thd->db ? thd->db : "",
+ NullS);
+ (void) fn_format(path, exchange->file_name, path, "", option);
+ }
+ else
+ (void) fn_format(path, exchange->file_name, mysql_real_data_home, "", option);
+
+ if (!is_secure_file_path(path))
+ {
+ /* Write only allowed to dir or subdir specified by secure_file_priv */
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv");
+ return -1;
+ }
+
+ if (!access(path, F_OK))
+ {
+ my_error(ER_FILE_EXISTS_ERROR, MYF(0), exchange->file_name);
+ return -1;
+ }
+ /* Create the file world readable */
+ if ((file= mysql_file_create(key_select_to_file,
+ path, 0666, O_WRONLY|O_EXCL, MYF(MY_WME))) < 0)
+ return file;
+#ifdef HAVE_FCHMOD
+ (void) fchmod(file, 0666); // Because of umask()
+#else
+ (void) chmod(path, 0666);
+#endif
+ if (init_io_cache(cache, file, 0L, WRITE_CACHE, 0L, 1, MYF(MY_WME)))
+ {
+ mysql_file_close(file, MYF(0));
+ /* Delete file on error, it was just created */
+ mysql_file_delete(key_select_to_file, path, MYF(0));
+ return -1;
+ }
+ return file;
+}
+
+
+int
+select_export::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
+{
+ bool blob_flag=0;
+ bool string_results= FALSE, non_string_results= FALSE;
+ unit= u;
+ if ((uint) strlen(exchange->file_name) + NAME_LEN >= FN_REFLEN)
+ strmake_buf(path,exchange->file_name);
+
+ write_cs= exchange->cs ? exchange->cs : &my_charset_bin;
+
+ if ((file= create_file(thd, path, exchange, &cache)) < 0)
+ return 1;
+ /* Check if there is any blobs in data */
+ {
+ List_iterator_fast<Item> li(list);
+ Item *item;
+ while ((item=li++))
+ {
+ if (item->max_length >= MAX_BLOB_WIDTH)
+ {
+ blob_flag=1;
+ break;
+ }
+ if (item->result_type() == STRING_RESULT)
+ string_results= TRUE;
+ else
+ non_string_results= TRUE;
+ }
+ }
+ if (exchange->escaped->numchars() > 1 || exchange->enclosed->numchars() > 1)
+ {
+ my_error(ER_WRONG_FIELD_TERMINATORS, MYF(0));
+ return TRUE;
+ }
+ if (exchange->escaped->length() > 1 || exchange->enclosed->length() > 1 ||
+ !my_isascii(exchange->escaped->ptr()[0]) ||
+ !my_isascii(exchange->enclosed->ptr()[0]) ||
+ !exchange->field_term->is_ascii() || !exchange->line_term->is_ascii() ||
+ !exchange->line_start->is_ascii())
+ {
+ /*
+ Current LOAD DATA INFILE recognizes field/line separators "as is" without
+ converting from client charset to data file charset. So, it is supposed,
+ that input file of LOAD DATA INFILE consists of data in one charset and
+ separators in other charset. For the compatibility with that [buggy]
+ behaviour SELECT INTO OUTFILE implementation has been saved "as is" too,
+ but the new warning message has been added:
+
+ Non-ASCII separator arguments are not fully supported
+ */
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED,
+ ER(WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED));
+ }
+ field_term_length=exchange->field_term->length();
+ field_term_char= field_term_length ?
+ (int) (uchar) (*exchange->field_term)[0] : INT_MAX;
+ if (!exchange->line_term->length())
+ exchange->line_term=exchange->field_term; // Use this if it exists
+ field_sep_char= (exchange->enclosed->length() ?
+ (int) (uchar) (*exchange->enclosed)[0] : field_term_char);
+ if (exchange->escaped->length() && (exchange->escaped_given() ||
+ !(thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)))
+ escape_char= (int) (uchar) (*exchange->escaped)[0];
+ else
+ escape_char= -1;
+ is_ambiguous_field_sep= MY_TEST(strchr(ESCAPE_CHARS, field_sep_char));
+ is_unsafe_field_sep= MY_TEST(strchr(NUMERIC_CHARS, field_sep_char));
+ line_sep_char= (exchange->line_term->length() ?
+ (int) (uchar) (*exchange->line_term)[0] : INT_MAX);
+ if (!field_term_length)
+ exchange->opt_enclosed=0;
+ if (!exchange->enclosed->length())
+ exchange->opt_enclosed=1; // A little quicker loop
+ fixed_row_size= (!field_term_length && !exchange->enclosed->length() &&
+ !blob_flag);
+ if ((is_ambiguous_field_sep && exchange->enclosed->is_empty() &&
+ (string_results || is_unsafe_field_sep)) ||
+ (exchange->opt_enclosed && non_string_results &&
+ field_term_length && strchr(NUMERIC_CHARS, field_term_char)))
+ {
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_AMBIGUOUS_FIELD_TERM, ER(ER_AMBIGUOUS_FIELD_TERM));
+ is_ambiguous_field_term= TRUE;
+ }
+ else
+ is_ambiguous_field_term= FALSE;
+
+ return 0;
+}
+
+
+#define NEED_ESCAPING(x) ((int) (uchar) (x) == escape_char || \
+ (enclosed ? (int) (uchar) (x) == field_sep_char \
+ : (int) (uchar) (x) == field_term_char) || \
+ (int) (uchar) (x) == line_sep_char || \
+ !(x))
+
+int select_export::send_data(List<Item> &items)
+{
+
+ DBUG_ENTER("select_export::send_data");
+ char buff[MAX_FIELD_WIDTH],null_buff[2],space[MAX_FIELD_WIDTH];
+ char cvt_buff[MAX_FIELD_WIDTH];
+ String cvt_str(cvt_buff, sizeof(cvt_buff), write_cs);
+ bool space_inited=0;
+ String tmp(buff,sizeof(buff),&my_charset_bin),*res;
+ tmp.length(0);
+
+ if (unit->offset_limit_cnt)
+ { // using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(0);
+ }
+ if (thd->killed == ABORT_QUERY)
+ DBUG_RETURN(0);
+ row_count++;
+ Item *item;
+ uint used_length=0,items_left=items.elements;
+ List_iterator_fast<Item> li(items);
+
+ if (my_b_write(&cache,(uchar*) exchange->line_start->ptr(),
+ exchange->line_start->length()))
+ goto err;
+ while ((item=li++))
+ {
+ Item_result result_type=item->result_type();
+ bool enclosed = (exchange->enclosed->length() &&
+ (!exchange->opt_enclosed || result_type == STRING_RESULT));
+ res=item->str_result(&tmp);
+ if (res && !my_charset_same(write_cs, res->charset()) &&
+ !my_charset_same(write_cs, &my_charset_bin))
+ {
+ const char *well_formed_error_pos;
+ const char *cannot_convert_error_pos;
+ const char *from_end_pos;
+ const char *error_pos;
+ uint32 bytes;
+ uint64 estimated_bytes=
+ ((uint64) res->length() / res->charset()->mbminlen + 1) *
+ write_cs->mbmaxlen + 1;
+ set_if_smaller(estimated_bytes, UINT_MAX32);
+ if (cvt_str.realloc((uint32) estimated_bytes))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), (uint32) estimated_bytes);
+ goto err;
+ }
+
+ bytes= well_formed_copy_nchars(write_cs, (char *) cvt_str.ptr(),
+ cvt_str.alloced_length(),
+ res->charset(), res->ptr(), res->length(),
+ UINT_MAX32, // copy all input chars,
+ // i.e. ignore nchars parameter
+ &well_formed_error_pos,
+ &cannot_convert_error_pos,
+ &from_end_pos);
+ error_pos= well_formed_error_pos ? well_formed_error_pos
+ : cannot_convert_error_pos;
+ if (error_pos)
+ {
+ char printable_buff[32];
+ convert_to_printable(printable_buff, sizeof(printable_buff),
+ error_pos, res->ptr() + res->length() - error_pos,
+ res->charset(), 6);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
+ ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
+ "string", printable_buff,
+ item->name, static_cast<long>(row_count));
+ }
+ else if (from_end_pos < res->ptr() + res->length())
+ {
+ /*
+ result is longer than UINT_MAX32 and doesn't fit into String
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_DATA_TRUNCATED, ER(WARN_DATA_TRUNCATED),
+ item->full_name(), static_cast<long>(row_count));
+ }
+ cvt_str.length(bytes);
+ res= &cvt_str;
+ }
+ if (res && enclosed)
+ {
+ if (my_b_write(&cache,(uchar*) exchange->enclosed->ptr(),
+ exchange->enclosed->length()))
+ goto err;
+ }
+ if (!res)
+ { // NULL
+ if (!fixed_row_size)
+ {
+ if (escape_char != -1) // Use \N syntax
+ {
+ null_buff[0]=escape_char;
+ null_buff[1]='N';
+ if (my_b_write(&cache,(uchar*) null_buff,2))
+ goto err;
+ }
+ else if (my_b_write(&cache,(uchar*) "NULL",4))
+ goto err;
+ }
+ else
+ {
+ used_length=0; // Fill with space
+ }
+ }
+ else
+ {
+ if (fixed_row_size)
+ used_length=MY_MIN(res->length(),item->max_length);
+ else
+ used_length=res->length();
+ if ((result_type == STRING_RESULT || is_unsafe_field_sep) &&
+ escape_char != -1)
+ {
+ char *pos, *start, *end;
+ CHARSET_INFO *res_charset= res->charset();
+ CHARSET_INFO *character_set_client= thd->variables.
+ character_set_client;
+ bool check_second_byte= (res_charset == &my_charset_bin) &&
+ character_set_client->
+ escape_with_backslash_is_dangerous;
+ DBUG_ASSERT(character_set_client->mbmaxlen == 2 ||
+ !character_set_client->escape_with_backslash_is_dangerous);
+ for (start=pos=(char*) res->ptr(),end=pos+used_length ;
+ pos != end ;
+ pos++)
+ {
+#ifdef USE_MB
+ if (use_mb(res_charset))
+ {
+ int l;
+ if ((l=my_ismbchar(res_charset, pos, end)))
+ {
+ pos += l-1;
+ continue;
+ }
+ }
+#endif
+
+ /*
+ Special case when dumping BINARY/VARBINARY/BLOB values
+ for the clients with character sets big5, cp932, gbk and sjis,
+ which can have the escape character (0x5C "\" by default)
+ as the second byte of a multi-byte sequence.
+
+ If
+ - pos[0] is a valid multi-byte head (e.g 0xEE) and
+ - pos[1] is 0x00, which will be escaped as "\0",
+
+ then we'll get "0xEE + 0x5C + 0x30" in the output file.
+
+ If this file is later loaded using this sequence of commands:
+
+ mysql> create table t1 (a varchar(128)) character set big5;
+ mysql> LOAD DATA INFILE 'dump.txt' INTO TABLE t1;
+
+ then 0x5C will be misinterpreted as the second byte
+ of a multi-byte character "0xEE + 0x5C", instead of
+ escape character for 0x00.
+
+ To avoid this confusion, we'll escape the multi-byte
+ head character too, so the sequence "0xEE + 0x00" will be
+ dumped as "0x5C + 0xEE + 0x5C + 0x30".
+
+ Note, in the condition below we only check if
+ mbcharlen is equal to 2, because there are no
+ character sets with mbmaxlen longer than 2
+ and with escape_with_backslash_is_dangerous set.
+ DBUG_ASSERT before the loop makes that sure.
+ */
+
+ if ((NEED_ESCAPING(*pos) ||
+ (check_second_byte &&
+ my_mbcharlen(character_set_client, (uchar) *pos) == 2 &&
+ pos + 1 < end &&
+ NEED_ESCAPING(pos[1]))) &&
+ /*
+ Don't escape field_term_char by doubling - doubling is only
+ valid for ENCLOSED BY characters:
+ */
+ (enclosed || !is_ambiguous_field_term ||
+ (int) (uchar) *pos != field_term_char))
+ {
+ char tmp_buff[2];
+ tmp_buff[0]= ((int) (uchar) *pos == field_sep_char &&
+ is_ambiguous_field_sep) ?
+ field_sep_char : escape_char;
+ tmp_buff[1]= *pos ? *pos : '0';
+ if (my_b_write(&cache,(uchar*) start,(uint) (pos-start)) ||
+ my_b_write(&cache,(uchar*) tmp_buff,2))
+ goto err;
+ start=pos+1;
+ }
+ }
+ if (my_b_write(&cache,(uchar*) start,(uint) (pos-start)))
+ goto err;
+ }
+ else if (my_b_write(&cache,(uchar*) res->ptr(),used_length))
+ goto err;
+ }
+ if (fixed_row_size)
+ { // Fill with space
+ if (item->max_length > used_length)
+ {
+ if (!space_inited)
+ {
+ space_inited=1;
+ bfill(space,sizeof(space),' ');
+ }
+ uint length=item->max_length-used_length;
+ for (; length > sizeof(space) ; length-=sizeof(space))
+ {
+ if (my_b_write(&cache,(uchar*) space,sizeof(space)))
+ goto err;
+ }
+ if (my_b_write(&cache,(uchar*) space,length))
+ goto err;
+ }
+ }
+ if (res && enclosed)
+ {
+ if (my_b_write(&cache, (uchar*) exchange->enclosed->ptr(),
+ exchange->enclosed->length()))
+ goto err;
+ }
+ if (--items_left)
+ {
+ if (my_b_write(&cache, (uchar*) exchange->field_term->ptr(),
+ field_term_length))
+ goto err;
+ }
+ }
+ if (my_b_write(&cache,(uchar*) exchange->line_term->ptr(),
+ exchange->line_term->length()))
+ goto err;
+ DBUG_RETURN(0);
+err:
+ DBUG_RETURN(1);
+}
+
+
+/***************************************************************************
+** Dump of select to a binary file
+***************************************************************************/
+
+
+int
+select_dump::prepare(List<Item> &list __attribute__((unused)),
+ SELECT_LEX_UNIT *u)
+{
+ unit= u;
+ return (int) ((file= create_file(thd, path, exchange, &cache)) < 0);
+}
+
+
+int select_dump::send_data(List<Item> &items)
+{
+ List_iterator_fast<Item> li(items);
+ char buff[MAX_FIELD_WIDTH];
+ String tmp(buff,sizeof(buff),&my_charset_bin),*res;
+ tmp.length(0);
+ Item *item;
+ DBUG_ENTER("select_dump::send_data");
+
+ if (unit->offset_limit_cnt)
+ { // using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(0);
+ }
+ if (thd->killed == ABORT_QUERY)
+ DBUG_RETURN(0);
+
+ if (row_count++ > 1)
+ {
+ my_message(ER_TOO_MANY_ROWS, ER(ER_TOO_MANY_ROWS), MYF(0));
+ goto err;
+ }
+ while ((item=li++))
+ {
+ res=item->str_result(&tmp);
+ if (!res) // If NULL
+ {
+ if (my_b_write(&cache,(uchar*) "",1))
+ goto err;
+ }
+ else if (my_b_write(&cache,(uchar*) res->ptr(),res->length()))
+ {
+ my_error(ER_ERROR_ON_WRITE, MYF(0), path, my_errno);
+ goto err;
+ }
+ }
+ DBUG_RETURN(0);
+err:
+ DBUG_RETURN(1);
+}
+
+
+select_subselect::select_subselect(Item_subselect *item_arg)
+{
+ item= item_arg;
+}
+
+
+int select_singlerow_subselect::send_data(List<Item> &items)
+{
+ DBUG_ENTER("select_singlerow_subselect::send_data");
+ Item_singlerow_subselect *it= (Item_singlerow_subselect *)item;
+ if (it->assigned())
+ {
+ my_message(ER_SUBQUERY_NO_1_ROW, ER(ER_SUBQUERY_NO_1_ROW),
+ MYF(current_thd->lex->ignore ? ME_JUST_WARNING : 0));
+ DBUG_RETURN(1);
+ }
+ if (unit->offset_limit_cnt)
+ { // Using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(0);
+ }
+ if (thd->killed == ABORT_QUERY)
+ DBUG_RETURN(0);
+ List_iterator_fast<Item> li(items);
+ Item *val_item;
+ for (uint i= 0; (val_item= li++); i++)
+ it->store(i, val_item);
+ it->assigned(1);
+ DBUG_RETURN(0);
+}
+
+
+void select_max_min_finder_subselect::cleanup()
+{
+ DBUG_ENTER("select_max_min_finder_subselect::cleanup");
+ cache= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+int select_max_min_finder_subselect::send_data(List<Item> &items)
+{
+ DBUG_ENTER("select_max_min_finder_subselect::send_data");
+ Item_maxmin_subselect *it= (Item_maxmin_subselect *)item;
+ List_iterator_fast<Item> li(items);
+ Item *val_item= li++;
+ it->register_value();
+ if (it->assigned())
+ {
+ cache->store(val_item);
+ if ((this->*op)())
+ it->store(0, cache);
+ }
+ else
+ {
+ if (!cache)
+ {
+ cache= Item_cache::get_cache(val_item);
+ switch (val_item->result_type()) {
+ case REAL_RESULT:
+ op= &select_max_min_finder_subselect::cmp_real;
+ break;
+ case INT_RESULT:
+ op= &select_max_min_finder_subselect::cmp_int;
+ break;
+ case STRING_RESULT:
+ op= &select_max_min_finder_subselect::cmp_str;
+ break;
+ case DECIMAL_RESULT:
+ op= &select_max_min_finder_subselect::cmp_decimal;
+ break;
+ case ROW_RESULT:
+ case TIME_RESULT:
+ case IMPOSSIBLE_RESULT:
+ // This case should never be choosen
+ DBUG_ASSERT(0);
+ op= 0;
+ }
+ }
+ cache->store(val_item);
+ it->store(0, cache);
+ }
+ it->assigned(1);
+ DBUG_RETURN(0);
+}
+
+bool select_max_min_finder_subselect::cmp_real()
+{
+ Item *maxmin= ((Item_singlerow_subselect *)item)->element_index(0);
+ double val1= cache->val_real(), val2= maxmin->val_real();
+
+ /* Ignore NULLs for ANY and keep them for ALL subqueries */
+ if (cache->null_value)
+ return (is_all && !maxmin->null_value) || (!is_all && maxmin->null_value);
+ if (maxmin->null_value)
+ return !is_all;
+
+ if (fmax)
+ return(val1 > val2);
+ return (val1 < val2);
+}
+
+bool select_max_min_finder_subselect::cmp_int()
+{
+ Item *maxmin= ((Item_singlerow_subselect *)item)->element_index(0);
+ longlong val1= cache->val_int(), val2= maxmin->val_int();
+
+ /* Ignore NULLs for ANY and keep them for ALL subqueries */
+ if (cache->null_value)
+ return (is_all && !maxmin->null_value) || (!is_all && maxmin->null_value);
+ if (maxmin->null_value)
+ return !is_all;
+
+ if (fmax)
+ return(val1 > val2);
+ return (val1 < val2);
+}
+
+bool select_max_min_finder_subselect::cmp_decimal()
+{
+ Item *maxmin= ((Item_singlerow_subselect *)item)->element_index(0);
+ my_decimal cval, *cvalue= cache->val_decimal(&cval);
+ my_decimal mval, *mvalue= maxmin->val_decimal(&mval);
+
+ /* Ignore NULLs for ANY and keep them for ALL subqueries */
+ if (cache->null_value)
+ return (is_all && !maxmin->null_value) || (!is_all && maxmin->null_value);
+ if (maxmin->null_value)
+ return !is_all;
+
+ if (fmax)
+ return (my_decimal_cmp(cvalue, mvalue) > 0) ;
+ return (my_decimal_cmp(cvalue,mvalue) < 0);
+}
+
+bool select_max_min_finder_subselect::cmp_str()
+{
+ String *val1, *val2, buf1, buf2;
+ Item *maxmin= ((Item_singlerow_subselect *)item)->element_index(0);
+ /*
+ as far as both operand is Item_cache buf1 & buf2 will not be used,
+ but added for safety
+ */
+ val1= cache->val_str(&buf1);
+ val2= maxmin->val_str(&buf1);
+
+ /* Ignore NULLs for ANY and keep them for ALL subqueries */
+ if (cache->null_value)
+ return (is_all && !maxmin->null_value) || (!is_all && maxmin->null_value);
+ if (maxmin->null_value)
+ return !is_all;
+
+ if (fmax)
+ return (sortcmp(val1, val2, cache->collation.collation) > 0) ;
+ return (sortcmp(val1, val2, cache->collation.collation) < 0);
+}
+
+int select_exists_subselect::send_data(List<Item> &items)
+{
+ DBUG_ENTER("select_exists_subselect::send_data");
+ Item_exists_subselect *it= (Item_exists_subselect *)item;
+ if (unit->offset_limit_cnt)
+ { // Using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(0);
+ }
+ if (thd->killed == ABORT_QUERY)
+ DBUG_RETURN(0);
+ it->value= 1;
+ it->assigned(1);
+ DBUG_RETURN(0);
+}
+
+
+/***************************************************************************
+ Dump of select to variables
+***************************************************************************/
+
+int select_dumpvar::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
+{
+ unit= u;
+
+ if (var_list.elements != list.elements)
+ {
+ my_message(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT,
+ ER(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT), MYF(0));
+ return 1;
+ }
+ return 0;
+}
+
+
+bool select_dumpvar::check_simple_select() const
+{
+ my_error(ER_SP_BAD_CURSOR_SELECT, MYF(0));
+ return TRUE;
+}
+
+
+void select_dumpvar::cleanup()
+{
+ row_count= 0;
+}
+
+
+Query_arena::Type Query_arena::type() const
+{
+ DBUG_ASSERT(0); /* Should never be called */
+ return STATEMENT;
+}
+
+
+void Query_arena::free_items()
+{
+ Item *next;
+ DBUG_ENTER("Query_arena::free_items");
+ /* This works because items are allocated with sql_alloc() */
+ for (; free_list; free_list= next)
+ {
+ next= free_list->next;
+ DBUG_ASSERT(free_list != next);
+ free_list->delete_self();
+ }
+ /* Postcondition: free_list is 0 */
+ DBUG_VOID_RETURN;
+}
+
+
+void Query_arena::set_query_arena(Query_arena *set)
+{
+ mem_root= set->mem_root;
+ free_list= set->free_list;
+ state= set->state;
+}
+
+
+void Query_arena::cleanup_stmt()
+{
+ DBUG_ASSERT(! "Query_arena::cleanup_stmt() not implemented");
+}
+
+/*
+ Statement functions
+*/
+
+Statement::Statement(LEX *lex_arg, MEM_ROOT *mem_root_arg,
+ enum enum_state state_arg, ulong id_arg)
+ :Query_arena(mem_root_arg, state_arg),
+ id(id_arg),
+ mark_used_columns(MARK_COLUMNS_READ),
+ lex(lex_arg),
+ db(NULL),
+ db_length(0)
+{
+ name.str= NULL;
+}
+
+
+Query_arena::Type Statement::type() const
+{
+ return STATEMENT;
+}
+
+
+void Statement::set_statement(Statement *stmt)
+{
+ id= stmt->id;
+ mark_used_columns= stmt->mark_used_columns;
+ lex= stmt->lex;
+ query_string= stmt->query_string;
+}
+
+
+void
+Statement::set_n_backup_statement(Statement *stmt, Statement *backup)
+{
+ DBUG_ENTER("Statement::set_n_backup_statement");
+ backup->set_statement(this);
+ set_statement(stmt);
+ DBUG_VOID_RETURN;
+}
+
+
+void Statement::restore_backup_statement(Statement *stmt, Statement *backup)
+{
+ DBUG_ENTER("Statement::restore_backup_statement");
+ stmt->set_statement(this);
+ set_statement(backup);
+ DBUG_VOID_RETURN;
+}
+
+
+void THD::end_statement()
+{
+ DBUG_ENTER("THD::end_statement");
+ /* Cleanup SQL processing state to reuse this statement in next query. */
+ lex_end(lex);
+ delete lex->result;
+ lex->result= 0;
+ /* Note that free_list is freed in cleanup_after_query() */
+
+ /*
+ Don't free mem_root, as mem_root is freed in the end of dispatch_command
+ (once for any command).
+ */
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Start using arena specified by @set. Current arena data will be saved to
+ *backup.
+*/
+void THD::set_n_backup_active_arena(Query_arena *set, Query_arena *backup)
+{
+ DBUG_ENTER("THD::set_n_backup_active_arena");
+ DBUG_ASSERT(backup->is_backup_arena == FALSE);
+
+ backup->set_query_arena(this);
+ set_query_arena(set);
+#ifndef DBUG_OFF
+ backup->is_backup_arena= TRUE;
+#endif
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Stop using the temporary arena, and start again using the arena that is
+ specified in *backup.
+ The temporary arena is returned back into *set.
+*/
+
+void THD::restore_active_arena(Query_arena *set, Query_arena *backup)
+{
+ DBUG_ENTER("THD::restore_active_arena");
+ DBUG_ASSERT(backup->is_backup_arena);
+ set->set_query_arena(this);
+ set_query_arena(backup);
+#ifndef DBUG_OFF
+ backup->is_backup_arena= FALSE;
+#endif
+ DBUG_VOID_RETURN;
+}
+
+Statement::~Statement()
+{
+}
+
+C_MODE_START
+
+static uchar *
+get_statement_id_as_hash_key(const uchar *record, size_t *key_length,
+ my_bool not_used __attribute__((unused)))
+{
+ const Statement *statement= (const Statement *) record;
+ *key_length= sizeof(statement->id);
+ return (uchar *) &((const Statement *) statement)->id;
+}
+
+static void delete_statement_as_hash_key(void *key)
+{
+ delete (Statement *) key;
+}
+
+static uchar *get_stmt_name_hash_key(Statement *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= entry->name.length;
+ return (uchar*) entry->name.str;
+}
+
+C_MODE_END
+
+Statement_map::Statement_map() :
+ last_found_statement(0)
+{
+ enum
+ {
+ START_STMT_HASH_SIZE = 16,
+ START_NAME_HASH_SIZE = 16
+ };
+ my_hash_init(&st_hash, &my_charset_bin, START_STMT_HASH_SIZE, 0, 0,
+ get_statement_id_as_hash_key,
+ delete_statement_as_hash_key, MYF(0));
+ my_hash_init(&names_hash, system_charset_info, START_NAME_HASH_SIZE, 0, 0,
+ (my_hash_get_key) get_stmt_name_hash_key,
+ NULL,MYF(0));
+}
+
+
+/*
+ Insert a new statement to the thread-local statement map.
+
+ DESCRIPTION
+ If there was an old statement with the same name, replace it with the
+ new one. Otherwise, check if max_prepared_stmt_count is not reached yet,
+ increase prepared_stmt_count, and insert the new statement. It's okay
+ to delete an old statement and fail to insert the new one.
+
+ POSTCONDITIONS
+ All named prepared statements are also present in names_hash.
+ Statement names in names_hash are unique.
+ The statement is added only if prepared_stmt_count < max_prepard_stmt_count
+ last_found_statement always points to a valid statement or is 0
+
+ RETURN VALUE
+ 0 success
+ 1 error: out of resources or max_prepared_stmt_count limit has been
+ reached. An error is sent to the client, the statement is deleted.
+*/
+
+int Statement_map::insert(THD *thd, Statement *statement)
+{
+ if (my_hash_insert(&st_hash, (uchar*) statement))
+ {
+ /*
+ Delete is needed only in case of an insert failure. In all other
+ cases hash_delete will also delete the statement.
+ */
+ delete statement;
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto err_st_hash;
+ }
+ if (statement->name.str && my_hash_insert(&names_hash, (uchar*) statement))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ goto err_names_hash;
+ }
+ mysql_mutex_lock(&LOCK_prepared_stmt_count);
+ /*
+ We don't check that prepared_stmt_count is <= max_prepared_stmt_count
+ because we would like to allow to lower the total limit
+ of prepared statements below the current count. In that case
+ no new statements can be added until prepared_stmt_count drops below
+ the limit.
+ */
+ if (prepared_stmt_count >= max_prepared_stmt_count)
+ {
+ mysql_mutex_unlock(&LOCK_prepared_stmt_count);
+ my_error(ER_MAX_PREPARED_STMT_COUNT_REACHED, MYF(0),
+ max_prepared_stmt_count);
+ goto err_max;
+ }
+ prepared_stmt_count++;
+ mysql_mutex_unlock(&LOCK_prepared_stmt_count);
+
+ last_found_statement= statement;
+ return 0;
+
+err_max:
+ if (statement->name.str)
+ my_hash_delete(&names_hash, (uchar*) statement);
+err_names_hash:
+ my_hash_delete(&st_hash, (uchar*) statement);
+err_st_hash:
+ return 1;
+}
+
+
+void Statement_map::close_transient_cursors()
+{
+#ifdef TO_BE_IMPLEMENTED
+ Statement *stmt;
+ while ((stmt= transient_cursor_list.head()))
+ stmt->close_cursor(); /* deletes itself from the list */
+#endif
+}
+
+
+void Statement_map::erase(Statement *statement)
+{
+ if (statement == last_found_statement)
+ last_found_statement= 0;
+ if (statement->name.str)
+ my_hash_delete(&names_hash, (uchar *) statement);
+
+ my_hash_delete(&st_hash, (uchar *) statement);
+ mysql_mutex_lock(&LOCK_prepared_stmt_count);
+ DBUG_ASSERT(prepared_stmt_count > 0);
+ prepared_stmt_count--;
+ mysql_mutex_unlock(&LOCK_prepared_stmt_count);
+}
+
+
+void Statement_map::reset()
+{
+ /* Must be first, hash_free will reset st_hash.records */
+ mysql_mutex_lock(&LOCK_prepared_stmt_count);
+ DBUG_ASSERT(prepared_stmt_count >= st_hash.records);
+ prepared_stmt_count-= st_hash.records;
+ mysql_mutex_unlock(&LOCK_prepared_stmt_count);
+
+ my_hash_reset(&names_hash);
+ my_hash_reset(&st_hash);
+ last_found_statement= 0;
+}
+
+
+Statement_map::~Statement_map()
+{
+ /* Must go first, hash_free will reset st_hash.records */
+ mysql_mutex_lock(&LOCK_prepared_stmt_count);
+ DBUG_ASSERT(prepared_stmt_count >= st_hash.records);
+ prepared_stmt_count-= st_hash.records;
+ mysql_mutex_unlock(&LOCK_prepared_stmt_count);
+
+ my_hash_free(&names_hash);
+ my_hash_free(&st_hash);
+}
+
+int select_dumpvar::send_data(List<Item> &items)
+{
+ List_iterator_fast<my_var> var_li(var_list);
+ List_iterator<Item> it(items);
+ Item *item;
+ my_var *mv;
+ DBUG_ENTER("select_dumpvar::send_data");
+
+ if (unit->offset_limit_cnt)
+ { // using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(0);
+ }
+ if (row_count++)
+ {
+ my_message(ER_TOO_MANY_ROWS, ER(ER_TOO_MANY_ROWS), MYF(0));
+ DBUG_RETURN(1);
+ }
+ while ((mv= var_li++) && (item= it++))
+ {
+ if (mv->local)
+ {
+ if (thd->spcont->set_variable(thd, mv->offset, &item))
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ Item_func_set_user_var *suv= new Item_func_set_user_var(mv->s, item);
+ suv->save_item_result(item);
+ if (suv->fix_fields(thd, 0))
+ DBUG_RETURN (1);
+ if (suv->update())
+ DBUG_RETURN (1);
+ }
+ }
+ DBUG_RETURN(thd->is_error());
+}
+
+bool select_dumpvar::send_eof()
+{
+ if (! row_count)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA));
+ /*
+ Don't send EOF if we're in error condition (which implies we've already
+ sent or are sending an error)
+ */
+ if (thd->is_error())
+ return true;
+
+ ::my_ok(thd,row_count);
+ return 0;
+}
+
+
+bool
+select_materialize_with_stats::
+create_result_table(THD *thd_arg, List<Item> *column_types,
+ bool is_union_distinct, ulonglong options,
+ const char *table_alias, bool bit_fields_as_long,
+ bool create_table,
+ bool keep_row_order)
+{
+ DBUG_ASSERT(table == 0);
+ tmp_table_param.field_count= column_types->elements;
+ tmp_table_param.bit_fields_as_long= bit_fields_as_long;
+
+ if (! (table= create_tmp_table(thd_arg, &tmp_table_param, *column_types,
+ (ORDER*) 0, is_union_distinct, 1,
+ options, HA_POS_ERROR, (char*) table_alias,
+ keep_row_order)))
+ return TRUE;
+
+ col_stat= (Column_statistics*) table->in_use->alloc(table->s->fields *
+ sizeof(Column_statistics));
+ if (!col_stat)
+ return TRUE;
+
+ reset();
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ return FALSE;
+}
+
+
+void select_materialize_with_stats::reset()
+{
+ memset(col_stat, 0, table->s->fields * sizeof(Column_statistics));
+ max_nulls_in_row= 0;
+ count_rows= 0;
+}
+
+
+void select_materialize_with_stats::cleanup()
+{
+ reset();
+ select_union::cleanup();
+}
+
+
+/**
+ Override select_union::send_data to analyze each row for NULLs and to
+ update null_statistics before sending data to the client.
+
+ @return TRUE if fatal error when sending data to the client
+ @return FALSE on success
+*/
+
+int select_materialize_with_stats::send_data(List<Item> &items)
+{
+ List_iterator_fast<Item> item_it(items);
+ Item *cur_item;
+ Column_statistics *cur_col_stat= col_stat;
+ uint nulls_in_row= 0;
+ int res;
+
+ if ((res= select_union::send_data(items)))
+ return res;
+ if (table->null_catch_flags & REJECT_ROW_DUE_TO_NULL_FIELDS)
+ {
+ table->null_catch_flags&= ~REJECT_ROW_DUE_TO_NULL_FIELDS;
+ return 0;
+ }
+ /* Skip duplicate rows. */
+ if (write_err == HA_ERR_FOUND_DUPP_KEY ||
+ write_err == HA_ERR_FOUND_DUPP_UNIQUE)
+ return 0;
+
+ ++count_rows;
+
+ while ((cur_item= item_it++))
+ {
+ if (cur_item->is_null_result())
+ {
+ ++cur_col_stat->null_count;
+ cur_col_stat->max_null_row= count_rows;
+ if (!cur_col_stat->min_null_row)
+ cur_col_stat->min_null_row= count_rows;
+ ++nulls_in_row;
+ }
+ ++cur_col_stat;
+ }
+ if (nulls_in_row > max_nulls_in_row)
+ max_nulls_in_row= nulls_in_row;
+
+ return 0;
+}
+
+
+/****************************************************************************
+ TMP_TABLE_PARAM
+****************************************************************************/
+
+void TMP_TABLE_PARAM::init()
+{
+ DBUG_ENTER("TMP_TABLE_PARAM::init");
+ DBUG_PRINT("enter", ("this: 0x%lx", (ulong)this));
+ field_count= sum_func_count= func_count= hidden_field_count= 0;
+ group_parts= group_length= group_null_parts= 0;
+ quick_group= 1;
+ table_charset= 0;
+ precomputed_group_by= 0;
+ bit_fields_as_long= 0;
+ materialized_subquery= 0;
+ force_not_null_cols= 0;
+ skip_create_table= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+void thd_increment_bytes_sent(ulong length)
+{
+ THD *thd=current_thd;
+ if (likely(thd != 0))
+ {
+ /* current_thd == 0 when close_connection() calls net_send_error() */
+ thd->status_var.bytes_sent+= length;
+ }
+}
+
+
+void thd_increment_bytes_received(ulong length)
+{
+ current_thd->status_var.bytes_received+= length;
+}
+
+
+void thd_increment_net_big_packet_count(ulong length)
+{
+ current_thd->status_var.net_big_packet_count+= length;
+}
+
+
+void THD::set_status_var_init()
+{
+ bzero((char*) &status_var, offsetof(STATUS_VAR,
+ last_cleared_system_status_var));
+}
+
+
+void Security_context::init()
+{
+ host= user= ip= external_user= 0;
+ host_or_ip= "connecting host";
+ priv_user[0]= priv_host[0]= proxy_user[0]= priv_role[0]= '\0';
+ master_access= 0;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ db_access= NO_ACCESS;
+#endif
+}
+
+
+void Security_context::destroy()
+{
+ DBUG_PRINT("info", ("freeing security context"));
+ // If not pointer to constant
+ if (host != my_localhost)
+ {
+ my_free(host);
+ host= NULL;
+ }
+ if (user != delayed_user)
+ {
+ my_free(user);
+ user= NULL;
+ }
+
+ if (external_user)
+ {
+ my_free(external_user);
+ user= NULL;
+ }
+
+ my_free(ip);
+ ip= NULL;
+}
+
+
+void Security_context::skip_grants()
+{
+ /* privileges for the user are unknown everything is allowed */
+ host_or_ip= (char *)"";
+ master_access= ~NO_ACCESS;
+ *priv_user= *priv_host= '\0';
+}
+
+
+bool Security_context::set_user(char *user_arg)
+{
+ my_free(user);
+ user= my_strdup(user_arg, MYF(0));
+ return user == 0;
+}
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+/**
+ Initialize this security context from the passed in credentials
+ and activate it in the current thread.
+
+ @param thd
+ @param definer_user
+ @param definer_host
+ @param db
+ @param[out] backup Save a pointer to the current security context
+ in the thread. In case of success it points to the
+ saved old context, otherwise it points to NULL.
+
+
+ During execution of a statement, multiple security contexts may
+ be needed:
+ - the security context of the authenticated user, used as the
+ default security context for all top-level statements
+ - in case of a view or a stored program, possibly the security
+ context of the definer of the routine, if the object is
+ defined with SQL SECURITY DEFINER option.
+
+ The currently "active" security context is parameterized in THD
+ member security_ctx. By default, after a connection is
+ established, this member points at the "main" security context
+ - the credentials of the authenticated user.
+
+ Later, if we would like to execute some sub-statement or a part
+ of a statement under credentials of a different user, e.g.
+ definer of a procedure, we authenticate this user in a local
+ instance of Security_context by means of this method (and
+ ultimately by means of acl_getroot), and make the
+ local instance active in the thread by re-setting
+ thd->security_ctx pointer.
+
+ Note, that the life cycle and memory management of the "main" and
+ temporary security contexts are different.
+ For the main security context, the memory for user/host/ip is
+ allocated on system heap, and the THD class frees this memory in
+ its destructor. The only case when contents of the main security
+ context may change during its life time is when someone issued
+ CHANGE USER command.
+ Memory management of a "temporary" security context is
+ responsibility of the module that creates it.
+
+ @retval TRUE there is no user with the given credentials. The erro
+ is reported in the thread.
+ @retval FALSE success
+*/
+
+bool
+Security_context::
+change_security_context(THD *thd,
+ LEX_STRING *definer_user,
+ LEX_STRING *definer_host,
+ LEX_STRING *db,
+ Security_context **backup)
+{
+ bool needs_change;
+
+ DBUG_ENTER("Security_context::change_security_context");
+
+ DBUG_ASSERT(definer_user->str && definer_host->str);
+
+ *backup= NULL;
+ needs_change= (strcmp(definer_user->str, thd->security_ctx->priv_user) ||
+ my_strcasecmp(system_charset_info, definer_host->str,
+ thd->security_ctx->priv_host));
+ if (needs_change)
+ {
+ if (acl_getroot(this, definer_user->str, definer_host->str,
+ definer_host->str, db->str))
+ {
+ my_error(ER_NO_SUCH_USER, MYF(0), definer_user->str,
+ definer_host->str);
+ DBUG_RETURN(TRUE);
+ }
+ *backup= thd->security_ctx;
+ thd->security_ctx= this;
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+void
+Security_context::restore_security_context(THD *thd,
+ Security_context *backup)
+{
+ if (backup)
+ thd->security_ctx= backup;
+}
+#endif
+
+
+bool Security_context::user_matches(Security_context *them)
+{
+ return ((user != NULL) && (them->user != NULL) &&
+ !strcmp(user, them->user));
+}
+
+
+/****************************************************************************
+ Handling of open and locked tables states.
+
+ This is used when we want to open/lock (and then close) some tables when
+ we already have a set of tables open and locked. We use these methods for
+ access to mysql.proc table to find definitions of stored routines.
+****************************************************************************/
+
+void THD::reset_n_backup_open_tables_state(Open_tables_backup *backup)
+{
+ DBUG_ENTER("reset_n_backup_open_tables_state");
+ backup->set_open_tables_state(this);
+ backup->mdl_system_tables_svp= mdl_context.mdl_savepoint();
+ reset_open_tables_state(this);
+ state_flags|= Open_tables_state::BACKUPS_AVAIL;
+ DBUG_VOID_RETURN;
+}
+
+
+void THD::restore_backup_open_tables_state(Open_tables_backup *backup)
+{
+ DBUG_ENTER("restore_backup_open_tables_state");
+ mdl_context.rollback_to_savepoint(backup->mdl_system_tables_svp);
+ /*
+ Before we will throw away current open tables state we want
+ to be sure that it was properly cleaned up.
+ */
+ DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 &&
+ derived_tables == 0 &&
+ lock == 0 &&
+ locked_tables_mode == LTM_NONE &&
+ m_reprepare_observer == NULL);
+
+ set_open_tables_state(backup);
+ DBUG_VOID_RETURN;
+}
+
+#if MARIA_PLUGIN_INTERFACE_VERSION < 0x0200
+/**
+ This is a backward compatibility method, made obsolete
+ by the thd_kill_statement service. Keep it here to avoid breaking the
+ ABI in case some binary plugins still use it.
+*/
+#undef thd_killed
+extern "C" int thd_killed(const MYSQL_THD thd)
+{
+ return thd_kill_level(thd) > THD_ABORT_SOFTLY;
+}
+#else
+#error now thd_killed() function can go away
+#endif
+
+/*
+ return thd->killed status to the client,
+ mapped to the API enum thd_kill_levels values.
+*/
+extern "C" enum thd_kill_levels thd_kill_level(const MYSQL_THD thd)
+{
+ THD* current= current_thd;
+
+ if (!thd)
+ thd= current;
+
+ if (thd == current)
+ {
+ Apc_target *apc_target= (Apc_target*)&thd->apc_target;
+ if (apc_target->have_apc_requests())
+ apc_target->process_apc_requests();
+ }
+
+ if (likely(thd->killed == NOT_KILLED))
+ return THD_IS_NOT_KILLED;
+
+ return thd->killed & KILL_HARD_BIT ? THD_ABORT_ASAP : THD_ABORT_SOFTLY;
+}
+
+
+/**
+ Send an out-of-band progress report to the client
+
+ The report is sent every 'thd->...progress_report_time' second,
+ however not more often than global.progress_report_time.
+ If global.progress_report_time is 0, then don't send progress reports, but
+ check every second if the value has changed
+*/
+
+static void thd_send_progress(THD *thd)
+{
+ /* Check if we should send the client a progress report */
+ ulonglong report_time= my_interval_timer();
+ if (report_time > thd->progress.next_report_time)
+ {
+ uint seconds_to_next= MY_MAX(thd->variables.progress_report_time,
+ global_system_variables.progress_report_time);
+ if (seconds_to_next == 0) // Turned off
+ seconds_to_next= 1; // Check again after 1 second
+
+ thd->progress.next_report_time= (report_time +
+ seconds_to_next * 1000000000ULL);
+ if (global_system_variables.progress_report_time &&
+ thd->variables.progress_report_time)
+ net_send_progress_packet(thd);
+ }
+}
+
+
+/** Initialize progress report handling **/
+
+extern "C" void thd_progress_init(MYSQL_THD thd, uint max_stage)
+{
+ DBUG_ASSERT(thd->stmt_arena != thd->progress.arena);
+ if (thd->progress.arena)
+ return; // already initialized
+ /*
+ Send progress reports to clients that supports it, if the command
+ is a high level command (like ALTER TABLE) and we are not in a
+ stored procedure
+ */
+ thd->progress.report= ((thd->client_capabilities & CLIENT_PROGRESS) &&
+ thd->progress.report_to_client &&
+ !thd->in_sub_stmt);
+ thd->progress.next_report_time= 0;
+ thd->progress.stage= 0;
+ thd->progress.counter= thd->progress.max_counter= 0;
+ thd->progress.max_stage= max_stage;
+ thd->progress.arena= thd->stmt_arena;
+}
+
+
+/* Inform processlist and the client that some progress has been made */
+
+extern "C" void thd_progress_report(MYSQL_THD thd,
+ ulonglong progress, ulonglong max_progress)
+{
+ if (thd->stmt_arena != thd->progress.arena)
+ return;
+ if (thd->progress.max_counter != max_progress) // Simple optimization
+ {
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+ thd->progress.counter= progress;
+ thd->progress.max_counter= max_progress;
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+ }
+ else
+ thd->progress.counter= progress;
+
+ if (thd->progress.report)
+ thd_send_progress(thd);
+}
+
+/**
+ Move to next stage in process list handling
+
+ This will reset the timer to ensure the progress is sent to the client
+ if client progress reports are activated.
+*/
+
+extern "C" void thd_progress_next_stage(MYSQL_THD thd)
+{
+ if (thd->stmt_arena != thd->progress.arena)
+ return;
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+ thd->progress.stage++;
+ thd->progress.counter= 0;
+ DBUG_ASSERT(thd->progress.stage < thd->progress.max_stage);
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+ if (thd->progress.report)
+ {
+ thd->progress.next_report_time= 0; // Send new stage info
+ thd_send_progress(thd);
+ }
+}
+
+/**
+ Disable reporting of progress in process list.
+
+ @note
+ This function is safe to call even if one has not called thd_progress_init.
+
+ This function should be called by all parts that does progress
+ reporting to ensure that progress list doesn't contain 100 % done
+ forever.
+*/
+
+
+extern "C" void thd_progress_end(MYSQL_THD thd)
+{
+ if (thd->stmt_arena != thd->progress.arena)
+ return;
+ /*
+ It's enough to reset max_counter to set disable progress indicator
+ in processlist.
+ */
+ thd->progress.max_counter= 0;
+ thd->progress.arena= 0;
+}
+
+
+/**
+ Return the thread id of a user thread
+ @param thd user thread
+ @return thread id
+*/
+extern "C" unsigned long thd_get_thread_id(const MYSQL_THD thd)
+{
+ return((unsigned long)thd->thread_id);
+}
+
+/**
+ Check if THD socket is still connected.
+ */
+extern "C" int thd_is_connected(MYSQL_THD thd)
+{
+ return thd->is_connected();
+}
+
+
+#ifdef INNODB_COMPATIBILITY_HOOKS
+extern "C" const struct charset_info_st *thd_charset(MYSQL_THD thd)
+{
+ return(thd->charset());
+}
+
+/**
+ OBSOLETE : there's no way to ensure the string is null terminated.
+ Use thd_query_string instead()
+*/
+extern "C" char **thd_query(MYSQL_THD thd)
+{
+ return (&thd->query_string.string.str);
+}
+
+/**
+ Get the current query string for the thread.
+
+ @param The MySQL internal thread pointer
+ @return query string and length. May be non-null-terminated.
+*/
+extern "C" LEX_STRING * thd_query_string (MYSQL_THD thd)
+{
+ return(&thd->query_string.string);
+}
+
+extern "C" int thd_slave_thread(const MYSQL_THD thd)
+{
+ return(thd->slave_thread);
+}
+
+/* Returns true for a worker thread in parallel replication. */
+extern "C" int thd_rpl_is_parallel(const MYSQL_THD thd)
+{
+ return thd->rgi_slave && thd->rgi_slave->is_parallel_exec;
+}
+
+/*
+ This function can optionally be called to check if thd_report_wait_for()
+ needs to be called for waits done by a given transaction.
+
+ If this function returns false for a given thd, there is no need to do any
+ calls to thd_report_wait_for() on that thd.
+
+ This call is optional; it is safe to call thd_report_wait_for() in any case.
+ This call can be used to save some redundant calls to thd_report_wait_for()
+ if desired. (This is unlikely to matter much unless there are _lots_ of
+ waits to report, as the overhead of thd_report_wait_for() is small).
+*/
+extern "C" int
+thd_need_wait_for(const MYSQL_THD thd)
+{
+ rpl_group_info *rgi;
+
+ if (mysql_bin_log.is_open() && opt_binlog_commit_wait_count > 0)
+ return true;
+ if (!thd)
+ return false;
+ rgi= thd->rgi_slave;
+ if (!rgi)
+ return false;
+ return rgi->is_parallel_exec;
+}
+
+/*
+ Used by InnoDB/XtraDB to report that one transaction THD is about to go to
+ wait for a transactional lock held by another transactions OTHER_THD.
+
+ This is used for parallel replication, where transactions are required to
+ commit in the same order on the slave as they did on the master. If the
+ transactions on the slave encounters lock conflicts on the slave that did
+ not exist on the master, this can cause deadlocks.
+
+ Normally, such conflicts will not occur, because the same conflict would
+ have prevented the two transactions from committing in parallel on the
+ master, thus preventing them from running in parallel on the slave in the
+ first place. However, it is possible in case when the optimizer chooses a
+ different plan on the slave than on the master (eg. table scan instead of
+ index scan).
+
+ InnoDB/XtraDB reports lock waits using this call. If a lock wait causes a
+ deadlock with the pre-determined commit order, we kill the later transaction,
+ and later re-try it, to resolve the deadlock.
+
+ This call need only receive reports about waits for locks that will remain
+ until the holding transaction commits. InnoDB/XtraDB auto-increment locks
+ are released earlier, and so need not be reported. (Such false positives are
+ not harmful, but could lead to unnecessary kill and retry, so best avoided).
+*/
+extern "C" void
+thd_report_wait_for(MYSQL_THD thd, MYSQL_THD other_thd)
+{
+ rpl_group_info *rgi;
+ rpl_group_info *other_rgi;
+
+ if (!thd || !other_thd)
+ return;
+ binlog_report_wait_for(thd, other_thd);
+ rgi= thd->rgi_slave;
+ other_rgi= other_thd->rgi_slave;
+ if (!rgi || !other_rgi)
+ return;
+ if (!rgi->is_parallel_exec)
+ return;
+ if (rgi->rli != other_rgi->rli)
+ return;
+ if (!rgi->gtid_sub_id || !other_rgi->gtid_sub_id)
+ return;
+ if (rgi->current_gtid.domain_id != other_rgi->current_gtid.domain_id)
+ return;
+ if (rgi->gtid_sub_id > other_rgi->gtid_sub_id)
+ return;
+ /*
+ This transaction is about to wait for another transaction that is required
+ by replication binlog order to commit after. This would cause a deadlock.
+
+ So send a kill to the other transaction, with a temporary error; this will
+ cause replication to rollback (and later re-try) the other transaction,
+ releasing the lock for this transaction so replication can proceed.
+ */
+ other_rgi->killed_for_retry= true;
+ mysql_mutex_lock(&other_thd->LOCK_thd_data);
+ other_thd->awake(KILL_CONNECTION);
+ mysql_mutex_unlock(&other_thd->LOCK_thd_data);
+}
+
+/*
+ This function is called from InnoDB/XtraDB to check if the commit order of
+ two transactions has already been decided by the upper layer. This happens
+ in parallel replication, where the commit order is forced to be the same on
+ the slave as it was originally on the master.
+
+ If this function returns false, it means that such commit order will be
+ enforced. This allows the storage engine to optionally omit gap lock waits
+ or similar measures that would otherwise be needed to ensure that
+ transactions would be serialised in a way that would cause a commit order
+ that is correct for binlogging for statement-based replication.
+
+ Since transactions are only run in parallel on the slave if they ran without
+ lock conflicts on the master, normally no lock conflicts on the slave happen
+ during parallel replication. However, there are a couple of corner cases
+ where it can happen, like these secondary-index operations:
+
+ T1: INSERT INTO t1 VALUES (7, NULL);
+ T2: DELETE FROM t1 WHERE b <= 3;
+
+ T1: UPDATE t1 SET secondary=NULL WHERE primary=1
+ T2: DELETE t1 WHERE secondary <= 3
+
+ The DELETE takes a gap lock that can block the INSERT/UPDATE, but the row
+ locks set by INSERT/UPDATE do not block the DELETE. Thus, the execution
+ order of the transactions determine whether a lock conflict occurs or
+ not. Thus a lock conflict can occur on the slave where it did not on the
+ master.
+
+ If this function returns true, normal locking should be done as required by
+ the binlogging and transaction isolation level in effect. But if it returns
+ false, the correct order will be enforced anyway, and InnoDB/XtraDB can
+ avoid taking the gap lock, preventing the lock conflict.
+
+ Calling this function is just an optimisation to avoid unnecessary
+ deadlocks. If it was not used, a gap lock would be set that could eventually
+ cause a deadlock; the deadlock would be caught by thd_report_wait_for() and
+ the transaction T2 killed and rolled back (and later re-tried).
+*/
+extern "C" int
+thd_need_ordering_with(const MYSQL_THD thd, const MYSQL_THD other_thd)
+{
+ rpl_group_info *rgi, *other_rgi;
+
+ DBUG_EXECUTE_IF("disable_thd_need_ordering_with", return 1;);
+ if (!thd || !other_thd)
+ return 1;
+ rgi= thd->rgi_slave;
+ other_rgi= other_thd->rgi_slave;
+ if (!rgi || !other_rgi)
+ return 1;
+ if (!rgi->is_parallel_exec)
+ return 1;
+ if (rgi->rli != other_rgi->rli)
+ return 1;
+ if (rgi->current_gtid.domain_id != other_rgi->current_gtid.domain_id)
+ return 1;
+ if (!rgi->commit_id || rgi->commit_id != other_rgi->commit_id)
+ return 1;
+ DBUG_EXECUTE_IF("thd_need_ordering_with_force", return 1;);
+ /*
+ Otherwise, these two threads are doing parallel replication within the same
+ replication domain. Their commit order is already fixed, so we do not need
+ gap locks or similar to otherwise enforce ordering (and in fact such locks
+ could lead to unnecessary deadlocks and transaction retry).
+ */
+ return 0;
+}
+
+
+/*
+ If the storage engine detects a deadlock, and needs to choose a victim
+ transaction to roll back, it can call this function to ask the upper
+ server layer for which of two possible transactions is prefered to be
+ aborted and rolled back.
+
+ In parallel replication, if two transactions are running in parallel and
+ one is fixed to commit before the other, then the one that commits later
+ will be prefered as the victim - chosing the early transaction as a victim
+ will not resolve the deadlock anyway, as the later transaction still needs
+ to wait for the earlier to commit.
+
+ Otherwise, a transaction that uses only transactional tables, and can thus
+ be safely rolled back, will be prefered as a deadlock victim over a
+ transaction that also modified non-transactional (eg. MyISAM) tables.
+
+ The return value is -1 if the first transaction is prefered as a deadlock
+ victim, 1 if the second transaction is prefered, or 0 for no preference (in
+ which case the storage engine can make the choice as it prefers).
+*/
+extern "C" int
+thd_deadlock_victim_preference(const MYSQL_THD thd1, const MYSQL_THD thd2)
+{
+ rpl_group_info *rgi1, *rgi2;
+ bool nontrans1, nontrans2;
+
+ if (!thd1 || !thd2)
+ return 0;
+
+ /*
+ If the transactions are participating in the same replication domain in
+ parallel replication, then request to select the one that will commit
+ later (in the fixed commit order from the master) as the deadlock victim.
+ */
+ rgi1= thd1->rgi_slave;
+ rgi2= thd2->rgi_slave;
+ if (rgi1 && rgi2 &&
+ rgi1->is_parallel_exec &&
+ rgi1->rli == rgi2->rli &&
+ rgi1->current_gtid.domain_id == rgi2->current_gtid.domain_id)
+ return rgi1->gtid_sub_id < rgi2->gtid_sub_id ? 1 : -1;
+
+ /*
+ If one transaction has modified non-transactional tables (so that it
+ cannot be safely rolled back), and the other has not, then prefer to
+ select the purely transactional one as the victim.
+ */
+ nontrans1= thd1->transaction.all.modified_non_trans_table;
+ nontrans2= thd2->transaction.all.modified_non_trans_table;
+ if (nontrans1 && !nontrans2)
+ return 1;
+ else if (!nontrans1 && nontrans2)
+ return -1;
+
+ /* No preferences, let the storage engine decide. */
+ return 0;
+}
+
+
+extern "C" int thd_non_transactional_update(const MYSQL_THD thd)
+{
+ return(thd->transaction.all.modified_non_trans_table);
+}
+
+extern "C" int thd_binlog_format(const MYSQL_THD thd)
+{
+ if (mysql_bin_log.is_open() && (thd->variables.option_bits & OPTION_BIN_LOG))
+ return (int) thd->variables.binlog_format;
+ else
+ return BINLOG_FORMAT_UNSPEC;
+}
+
+extern "C" void thd_mark_transaction_to_rollback(MYSQL_THD thd, bool all)
+{
+ DBUG_ASSERT(thd);
+ thd->mark_transaction_to_rollback(all);
+}
+
+extern "C" bool thd_binlog_filter_ok(const MYSQL_THD thd)
+{
+ return binlog_filter->db_ok(thd->db);
+}
+
+/*
+ This is similar to sqlcom_can_generate_row_events, with the expection
+ that we only return 1 if we are going to generate row events in a
+ transaction.
+ CREATE OR REPLACE is always safe to do as this will run in it's own
+ transaction.
+*/
+
+extern "C" bool thd_sqlcom_can_generate_row_events(const MYSQL_THD thd)
+{
+ return (sqlcom_can_generate_row_events(thd) && thd->lex->sql_command !=
+ SQLCOM_CREATE_TABLE);
+}
+
+
+extern "C" enum durability_properties thd_get_durability_property(const MYSQL_THD thd)
+{
+ enum durability_properties ret= HA_REGULAR_DURABILITY;
+
+ if (thd != NULL)
+ ret= thd->durability_property;
+
+ return ret;
+}
+
+/** Get the auto_increment_offset auto_increment_increment.
+Exposed by thd_autoinc_service.
+Needed by InnoDB.
+@param thd Thread object
+@param off auto_increment_offset
+@param inc auto_increment_increment */
+extern "C" void thd_get_autoinc(const MYSQL_THD thd, ulong* off, ulong* inc)
+{
+ *off = thd->variables.auto_increment_offset;
+ *inc = thd->variables.auto_increment_increment;
+}
+
+
+/**
+ Is strict sql_mode set.
+ Needed by InnoDB.
+ @param thd Thread object
+ @return True if sql_mode has strict mode (all or trans).
+ @retval true sql_mode has strict mode (all or trans).
+ @retval false sql_mode has not strict mode (all or trans).
+*/
+extern "C" bool thd_is_strict_mode(const MYSQL_THD thd)
+{
+ return thd->is_strict_mode();
+}
+
+
+/*
+ Interface for MySQL Server, plugins and storage engines to report
+ when they are going to sleep/stall.
+
+ SYNOPSIS
+ thd_wait_begin()
+ thd Thread object
+ Can be NULL, in this case current THD is used.
+ wait_type Type of wait
+ 1 -- short wait (e.g. for mutex)
+ 2 -- medium wait (e.g. for disk io)
+ 3 -- large wait (e.g. for locked row/table)
+ NOTES
+ This is used by the threadpool to have better knowledge of which
+ threads that currently are actively running on CPUs. When a thread
+ reports that it's going to sleep/stall, the threadpool scheduler is
+ free to start another thread in the pool most likely. The expected wait
+ time is simply an indication of how long the wait is expected to
+ become, the real wait time could be very different.
+
+ thd_wait_end MUST be called immediately after waking up again.
+*/
+extern "C" void thd_wait_begin(MYSQL_THD thd, int wait_type)
+{
+ if (!thd)
+ {
+ thd= current_thd;
+ if (unlikely(!thd))
+ return;
+ }
+ MYSQL_CALLBACK(thd->scheduler, thd_wait_begin, (thd, wait_type));
+}
+
+/**
+ Interface for MySQL Server, plugins and storage engines to report
+ when they waking up from a sleep/stall.
+
+ @param thd Thread handle
+ Can be NULL, in this case current THD is used.
+*/
+extern "C" void thd_wait_end(MYSQL_THD thd)
+{
+ if (!thd)
+ {
+ thd= current_thd;
+ if (unlikely(!thd))
+ return;
+ }
+ MYSQL_CALLBACK(thd->scheduler, thd_wait_end, (thd));
+}
+
+#endif // INNODB_COMPATIBILITY_HOOKS */
+
+/****************************************************************************
+ Handling of statement states in functions and triggers.
+
+ This is used to ensure that the function/trigger gets a clean state
+ to work with and does not cause any side effects of the calling statement.
+
+ It also allows most stored functions and triggers to replicate even
+ if they are used items that would normally be stored in the binary
+ replication (like last_insert_id() etc...)
+
+ The following things is done
+ - Disable binary logging for the duration of the statement
+ - Disable multi-result-sets for the duration of the statement
+ - Value of last_insert_id() is saved and restored
+ - Value set by 'SET INSERT_ID=#' is reset and restored
+ - Value for found_rows() is reset and restored
+ - examined_row_count is added to the total
+ - cuted_fields is added to the total
+ - new savepoint level is created and destroyed
+
+ NOTES:
+ Seed for random() is saved for the first! usage of RAND()
+ We reset examined_row_count and cuted_fields and add these to the
+ result to ensure that if we have a bug that would reset these within
+ a function, we are not loosing any rows from the main statement.
+
+ We do not reset value of last_insert_id().
+****************************************************************************/
+
+void THD::reset_sub_statement_state(Sub_statement_state *backup,
+ uint new_state)
+{
+#ifndef EMBEDDED_LIBRARY
+ /* BUG#33029, if we are replicating from a buggy master, reset
+ auto_inc_intervals_forced to prevent substatement
+ (triggers/functions) from using erroneous INSERT_ID value
+ */
+ if (rpl_master_erroneous_autoinc(this))
+ {
+ DBUG_ASSERT(backup->auto_inc_intervals_forced.nb_elements() == 0);
+ auto_inc_intervals_forced.swap(&backup->auto_inc_intervals_forced);
+ }
+#endif
+
+ backup->option_bits= variables.option_bits;
+ backup->count_cuted_fields= count_cuted_fields;
+ backup->in_sub_stmt= in_sub_stmt;
+ backup->enable_slow_log= enable_slow_log;
+ backup->query_plan_flags= query_plan_flags;
+ backup->limit_found_rows= limit_found_rows;
+ backup->examined_row_count= m_examined_row_count;
+ backup->sent_row_count= m_sent_row_count;
+ backup->cuted_fields= cuted_fields;
+ backup->client_capabilities= client_capabilities;
+ backup->savepoints= transaction.savepoints;
+ backup->first_successful_insert_id_in_prev_stmt=
+ first_successful_insert_id_in_prev_stmt;
+ backup->first_successful_insert_id_in_cur_stmt=
+ first_successful_insert_id_in_cur_stmt;
+
+ if ((!lex->requires_prelocking() || is_update_query(lex->sql_command)) &&
+ !is_current_stmt_binlog_format_row())
+ {
+ variables.option_bits&= ~OPTION_BIN_LOG;
+ }
+
+ if ((backup->option_bits & OPTION_BIN_LOG) &&
+ is_update_query(lex->sql_command) &&
+ !is_current_stmt_binlog_format_row())
+ mysql_bin_log.start_union_events(this, this->query_id);
+
+ /* Disable result sets */
+ client_capabilities &= ~CLIENT_MULTI_RESULTS;
+ in_sub_stmt|= new_state;
+ m_examined_row_count= 0;
+ m_sent_row_count= 0;
+ cuted_fields= 0;
+ transaction.savepoints= 0;
+ first_successful_insert_id_in_cur_stmt= 0;
+}
+
+
+void THD::restore_sub_statement_state(Sub_statement_state *backup)
+{
+ DBUG_ENTER("THD::restore_sub_statement_state");
+#ifndef EMBEDDED_LIBRARY
+ /* BUG#33029, if we are replicating from a buggy master, restore
+ auto_inc_intervals_forced so that the top statement can use the
+ INSERT_ID value set before this statement.
+ */
+ if (rpl_master_erroneous_autoinc(this))
+ {
+ backup->auto_inc_intervals_forced.swap(&auto_inc_intervals_forced);
+ DBUG_ASSERT(backup->auto_inc_intervals_forced.nb_elements() == 0);
+ }
+#endif
+
+ /*
+ To save resources we want to release savepoints which were created
+ during execution of function or trigger before leaving their savepoint
+ level. It is enough to release first savepoint set on this level since
+ all later savepoints will be released automatically.
+ */
+ if (transaction.savepoints)
+ {
+ SAVEPOINT *sv;
+ for (sv= transaction.savepoints; sv->prev; sv= sv->prev)
+ {}
+ /* ha_release_savepoint() never returns error. */
+ (void)ha_release_savepoint(this, sv);
+ }
+ count_cuted_fields= backup->count_cuted_fields;
+ transaction.savepoints= backup->savepoints;
+ variables.option_bits= backup->option_bits;
+ in_sub_stmt= backup->in_sub_stmt;
+ enable_slow_log= backup->enable_slow_log;
+ query_plan_flags= backup->query_plan_flags;
+ first_successful_insert_id_in_prev_stmt=
+ backup->first_successful_insert_id_in_prev_stmt;
+ first_successful_insert_id_in_cur_stmt=
+ backup->first_successful_insert_id_in_cur_stmt;
+ limit_found_rows= backup->limit_found_rows;
+ set_sent_row_count(backup->sent_row_count);
+ client_capabilities= backup->client_capabilities;
+ /*
+ If we've left sub-statement mode, reset the fatal error flag.
+ Otherwise keep the current value, to propagate it up the sub-statement
+ stack.
+
+ NOTE: is_fatal_sub_stmt_error can be set only if we've been in the
+ sub-statement mode.
+ */
+ if (!in_sub_stmt)
+ is_fatal_sub_stmt_error= false;
+
+ if ((variables.option_bits & OPTION_BIN_LOG) && is_update_query(lex->sql_command) &&
+ !is_current_stmt_binlog_format_row())
+ mysql_bin_log.stop_union_events(this);
+
+ /*
+ The following is added to the old values as we are interested in the
+ total complexity of the query
+ */
+ inc_examined_row_count(backup->examined_row_count);
+ cuted_fields+= backup->cuted_fields;
+ DBUG_VOID_RETURN;
+}
+
+
+void THD::set_statement(Statement *stmt)
+{
+ mysql_mutex_lock(&LOCK_thd_data);
+ Statement::set_statement(stmt);
+ mysql_mutex_unlock(&LOCK_thd_data);
+}
+
+void THD::set_sent_row_count(ha_rows count)
+{
+ m_sent_row_count= count;
+ MYSQL_SET_STATEMENT_ROWS_SENT(m_statement_psi, m_sent_row_count);
+}
+
+void THD::set_examined_row_count(ha_rows count)
+{
+ m_examined_row_count= count;
+ MYSQL_SET_STATEMENT_ROWS_EXAMINED(m_statement_psi, m_examined_row_count);
+}
+
+void THD::inc_sent_row_count(ha_rows count)
+{
+ m_sent_row_count+= count;
+ MYSQL_SET_STATEMENT_ROWS_SENT(m_statement_psi, m_sent_row_count);
+}
+
+void THD::inc_examined_row_count(ha_rows count)
+{
+ m_examined_row_count+= count;
+ MYSQL_SET_STATEMENT_ROWS_EXAMINED(m_statement_psi, m_examined_row_count);
+}
+
+void THD::inc_status_created_tmp_disk_tables()
+{
+ status_var_increment(status_var.created_tmp_disk_tables_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_created_tmp_disk_tables)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_created_tmp_tables()
+{
+ status_var_increment(status_var.created_tmp_tables_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_created_tmp_tables)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_select_full_join()
+{
+ status_var_increment(status_var.select_full_join_count_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_select_full_join)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_select_full_range_join()
+{
+ status_var_increment(status_var.select_full_range_join_count_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_select_full_range_join)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_select_range()
+{
+ status_var_increment(status_var.select_range_count_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_select_range)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_select_range_check()
+{
+ status_var_increment(status_var.select_range_check_count_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_select_range_check)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_select_scan()
+{
+ status_var_increment(status_var.select_scan_count_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_select_scan)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_sort_merge_passes()
+{
+ status_var_increment(status_var.filesort_merge_passes_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_sort_merge_passes)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_sort_range()
+{
+ status_var_increment(status_var.filesort_range_count_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_sort_range)(m_statement_psi, 1);
+#endif
+}
+
+void THD::inc_status_sort_rows(ha_rows count)
+{
+ statistic_add(status_var.filesort_rows_, count, &LOCK_status);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_sort_rows)(m_statement_psi, count);
+#endif
+}
+
+void THD::inc_status_sort_scan()
+{
+ status_var_increment(status_var.filesort_scan_count_);
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(inc_statement_sort_scan)(m_statement_psi, 1);
+#endif
+}
+
+void THD::set_status_no_index_used()
+{
+ server_status|= SERVER_QUERY_NO_INDEX_USED;
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(set_statement_no_index_used)(m_statement_psi);
+#endif
+}
+
+void THD::set_status_no_good_index_used()
+{
+ server_status|= SERVER_QUERY_NO_GOOD_INDEX_USED;
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ PSI_STATEMENT_CALL(set_statement_no_good_index_used)(m_statement_psi);
+#endif
+}
+
+void THD::set_command(enum enum_server_command command)
+{
+ m_command= command;
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ PSI_STATEMENT_CALL(set_thread_command)(m_command);
+#endif
+}
+
+/** Assign a new value to thd->query. */
+
+void THD::set_query(const CSET_STRING &string_arg)
+{
+ mysql_mutex_lock(&LOCK_thd_data);
+ set_query_inner(string_arg);
+ mysql_mutex_unlock(&LOCK_thd_data);
+
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ PSI_THREAD_CALL(set_thread_info)(query(), query_length());
+#endif
+}
+
+/** Assign a new value to thd->query and thd->query_id. */
+
+void THD::set_query_and_id(char *query_arg, uint32 query_length_arg,
+ CHARSET_INFO *cs,
+ query_id_t new_query_id)
+{
+ mysql_mutex_lock(&LOCK_thd_data);
+ set_query_inner(query_arg, query_length_arg, cs);
+ mysql_mutex_unlock(&LOCK_thd_data);
+ query_id= new_query_id;
+}
+
+/** Assign a new value to thd->mysys_var. */
+void THD::set_mysys_var(struct st_my_thread_var *new_mysys_var)
+{
+ mysql_mutex_lock(&LOCK_thd_data);
+ mysys_var= new_mysys_var;
+ mysql_mutex_unlock(&LOCK_thd_data);
+}
+
+/**
+ Leave explicit LOCK TABLES or prelocked mode and restore value of
+ transaction sentinel in MDL subsystem.
+*/
+
+void THD::leave_locked_tables_mode()
+{
+ if (locked_tables_mode == LTM_LOCK_TABLES)
+ {
+ /*
+ When leaving LOCK TABLES mode we have to change the duration of most
+ of the metadata locks being held, except for HANDLER and GRL locks,
+ to transactional for them to be properly released at UNLOCK TABLES.
+ */
+ mdl_context.set_transaction_duration_for_all_locks();
+ /*
+ Make sure we don't release the global read lock and commit blocker
+ when leaving LTM.
+ */
+ global_read_lock.set_explicit_lock_duration(this);
+ /* Also ensure that we don't release metadata locks for open HANDLERs. */
+ if (handler_tables_hash.records)
+ mysql_ha_set_explicit_lock_duration(this);
+ if (ull_hash.records)
+ mysql_ull_set_explicit_lock_duration(this);
+ }
+ locked_tables_mode= LTM_NONE;
+}
+
+void THD::get_definer(LEX_USER *definer, bool role)
+{
+ binlog_invoker(role);
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ if (slave_thread && has_invoker())
+ {
+ definer->user = invoker_user;
+ definer->host= invoker_host;
+ definer->password= null_lex_str;
+ definer->plugin= empty_lex_str;
+ definer->auth= empty_lex_str;
+ }
+ else
+#endif
+ get_default_definer(this, definer, role);
+}
+
+
+/**
+ Mark transaction to rollback and mark error as fatal to a sub-statement.
+
+ @param all TRUE <=> rollback main transaction.
+*/
+
+void THD::mark_transaction_to_rollback(bool all)
+{
+ /*
+ There is no point in setting is_fatal_sub_stmt_error unless
+ we are actually in_sub_stmt.
+ */
+ if (in_sub_stmt)
+ is_fatal_sub_stmt_error= true;
+ transaction_rollback_request= all;
+}
+/***************************************************************************
+ Handling of XA id cacheing
+***************************************************************************/
+
+mysql_mutex_t LOCK_xid_cache;
+HASH xid_cache;
+
+extern "C" uchar *xid_get_hash_key(const uchar *, size_t *, my_bool);
+extern "C" void xid_free_hash(void *);
+
+uchar *xid_get_hash_key(const uchar *ptr, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length=((XID_STATE*)ptr)->xid.key_length();
+ return ((XID_STATE*)ptr)->xid.key();
+}
+
+void xid_free_hash(void *ptr)
+{
+ if (!((XID_STATE*)ptr)->in_thd)
+ my_free(ptr);
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_LOCK_xid_cache;
+
+static PSI_mutex_info all_xid_mutexes[]=
+{
+ { &key_LOCK_xid_cache, "LOCK_xid_cache", PSI_FLAG_GLOBAL}
+};
+
+static void init_xid_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_xid_mutexes);
+ PSI_server->register_mutex(category, all_xid_mutexes, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+bool xid_cache_init()
+{
+#ifdef HAVE_PSI_INTERFACE
+ init_xid_psi_keys();
+#endif
+
+ mysql_mutex_init(key_LOCK_xid_cache, &LOCK_xid_cache, MY_MUTEX_INIT_FAST);
+ return my_hash_init(&xid_cache, &my_charset_bin, 100, 0, 0,
+ xid_get_hash_key, xid_free_hash, 0) != 0;
+}
+
+void xid_cache_free()
+{
+ if (my_hash_inited(&xid_cache))
+ {
+ my_hash_free(&xid_cache);
+ mysql_mutex_destroy(&LOCK_xid_cache);
+ }
+}
+
+XID_STATE *xid_cache_search(XID *xid)
+{
+ mysql_mutex_lock(&LOCK_xid_cache);
+ XID_STATE *res=(XID_STATE *)my_hash_search(&xid_cache, xid->key(),
+ xid->key_length());
+ mysql_mutex_unlock(&LOCK_xid_cache);
+ return res;
+}
+
+
+bool xid_cache_insert(XID *xid, enum xa_states xa_state)
+{
+ XID_STATE *xs;
+ my_bool res;
+ mysql_mutex_lock(&LOCK_xid_cache);
+ if (my_hash_search(&xid_cache, xid->key(), xid->key_length()))
+ res=0;
+ else if (!(xs=(XID_STATE *)my_malloc(sizeof(*xs), MYF(MY_WME))))
+ res=1;
+ else
+ {
+ xs->xa_state=xa_state;
+ xs->xid.set(xid);
+ xs->in_thd=0;
+ xs->rm_error=0;
+ res=my_hash_insert(&xid_cache, (uchar*)xs);
+ }
+ mysql_mutex_unlock(&LOCK_xid_cache);
+ return res;
+}
+
+
+bool xid_cache_insert(XID_STATE *xid_state)
+{
+ mysql_mutex_lock(&LOCK_xid_cache);
+ if (my_hash_search(&xid_cache, xid_state->xid.key(),
+ xid_state->xid.key_length()))
+ {
+ mysql_mutex_unlock(&LOCK_xid_cache);
+ my_error(ER_XAER_DUPID, MYF(0));
+ return true;
+ }
+ bool res= my_hash_insert(&xid_cache, (uchar*)xid_state);
+ mysql_mutex_unlock(&LOCK_xid_cache);
+ return res;
+}
+
+
+void xid_cache_delete(XID_STATE *xid_state)
+{
+ mysql_mutex_lock(&LOCK_xid_cache);
+ my_hash_delete(&xid_cache, (uchar *)xid_state);
+ mysql_mutex_unlock(&LOCK_xid_cache);
+}
+
+
+/**
+ Decide on logging format to use for the statement and issue errors
+ or warnings as needed. The decision depends on the following
+ parameters:
+
+ - The logging mode, i.e., the value of binlog_format. Can be
+ statement, mixed, or row.
+
+ - The type of statement. There are three types of statements:
+ "normal" safe statements; unsafe statements; and row injections.
+ An unsafe statement is one that, if logged in statement format,
+ might produce different results when replayed on the slave (e.g.,
+ INSERT DELAYED). A row injection is either a BINLOG statement, or
+ a row event executed by the slave's SQL thread.
+
+ - The capabilities of tables modified by the statement. The
+ *capabilities vector* for a table is a set of flags associated
+ with the table. Currently, it only includes two flags: *row
+ capability flag* and *statement capability flag*.
+
+ The row capability flag is set if and only if the engine can
+ handle row-based logging. The statement capability flag is set if
+ and only if the table can handle statement-based logging.
+
+ Decision table for logging format
+ ---------------------------------
+
+ The following table summarizes how the format and generated
+ warning/error depends on the tables' capabilities, the statement
+ type, and the current binlog_format.
+
+ Row capable N NNNNNNNNN YYYYYYYYY YYYYYYYYY
+ Statement capable N YYYYYYYYY NNNNNNNNN YYYYYYYYY
+
+ Statement type * SSSUUUIII SSSUUUIII SSSUUUIII
+
+ binlog_format * SMRSMRSMR SMRSMRSMR SMRSMRSMR
+
+ Logged format - SS-S----- -RR-RR-RR SRRSRR-RR
+ Warning/Error 1 --2732444 5--5--6-- ---7--6--
+
+ Legend
+ ------
+
+ Row capable: N - Some table not row-capable, Y - All tables row-capable
+ Stmt capable: N - Some table not stmt-capable, Y - All tables stmt-capable
+ Statement type: (S)afe, (U)nsafe, or Row (I)njection
+ binlog_format: (S)TATEMENT, (M)IXED, or (R)OW
+ Logged format: (S)tatement or (R)ow
+ Warning/Error: Warnings and error messages are as follows:
+
+ 1. Error: Cannot execute statement: binlogging impossible since both
+ row-incapable engines and statement-incapable engines are
+ involved.
+
+ 2. Error: Cannot execute statement: binlogging impossible since
+ BINLOG_FORMAT = ROW and at least one table uses a storage engine
+ limited to statement-logging.
+
+ 3. Error: Cannot execute statement: binlogging of unsafe statement
+ is impossible when storage engine is limited to statement-logging
+ and BINLOG_FORMAT = MIXED.
+
+ 4. Error: Cannot execute row injection: binlogging impossible since
+ at least one table uses a storage engine limited to
+ statement-logging.
+
+ 5. Error: Cannot execute statement: binlogging impossible since
+ BINLOG_FORMAT = STATEMENT and at least one table uses a storage
+ engine limited to row-logging.
+
+ 6. Error: Cannot execute row injection: binlogging impossible since
+ BINLOG_FORMAT = STATEMENT.
+
+ 7. Warning: Unsafe statement binlogged in statement format since
+ BINLOG_FORMAT = STATEMENT.
+
+ In addition, we can produce the following error (not depending on
+ the variables of the decision diagram):
+
+ 8. Error: Cannot execute statement: binlogging impossible since more
+ than one engine is involved and at least one engine is
+ self-logging.
+
+ For each error case above, the statement is prevented from being
+ logged, we report an error, and roll back the statement. For
+ warnings, we set the thd->binlog_flags variable: the warning will be
+ printed only if the statement is successfully logged.
+
+ @see THD::binlog_query
+
+ @param[in] thd Client thread
+ @param[in] tables Tables involved in the query
+
+ @retval 0 No error; statement can be logged.
+ @retval -1 One of the error conditions above applies (1, 2, 4, 5, or 6).
+*/
+
+int THD::decide_logging_format(TABLE_LIST *tables)
+{
+ DBUG_ENTER("THD::decide_logging_format");
+ DBUG_PRINT("info", ("Query: %s", query()));
+ DBUG_PRINT("info", ("variables.binlog_format: %lu",
+ variables.binlog_format));
+ DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags(): 0x%x",
+ lex->get_stmt_unsafe_flags()));
+
+ reset_binlog_local_stmt_filter();
+
+ /*
+ We should not decide logging format if the binlog is closed or
+ binlogging is off, or if the statement is filtered out from the
+ binlog by filtering rules.
+ */
+ if (mysql_bin_log.is_open() && (variables.option_bits & OPTION_BIN_LOG) &&
+ !(variables.binlog_format == BINLOG_FORMAT_STMT &&
+ !binlog_filter->db_ok(db)))
+ {
+ /*
+ Compute one bit field with the union of all the engine
+ capabilities, and one with the intersection of all the engine
+ capabilities.
+ */
+ handler::Table_flags flags_write_some_set= 0;
+ handler::Table_flags flags_access_some_set= 0;
+ handler::Table_flags flags_write_all_set=
+ HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE;
+
+ /*
+ If different types of engines are about to be updated.
+ For example: Innodb and Falcon; Innodb and MyIsam.
+ */
+ my_bool multi_write_engine= FALSE;
+ /*
+ If different types of engines are about to be accessed
+ and any of them is about to be updated. For example:
+ Innodb and Falcon; Innodb and MyIsam.
+ */
+ my_bool multi_access_engine= FALSE;
+ /*
+ Identifies if a table is changed.
+ */
+ my_bool is_write= FALSE;
+ /*
+ A pointer to a previous table that was changed.
+ */
+ TABLE* prev_write_table= NULL;
+ /*
+ A pointer to a previous table that was accessed.
+ */
+ TABLE* prev_access_table= NULL;
+ /**
+ The number of tables used in the current statement,
+ that should be replicated.
+ */
+ uint replicated_tables_count= 0;
+ /**
+ The number of tables written to in the current statement,
+ that should not be replicated.
+ A table should not be replicated when it is considered
+ 'local' to a MySQL instance.
+ Currently, these tables are:
+ - mysql.slow_log
+ - mysql.general_log
+ - mysql.slave_relay_log_info
+ - mysql.slave_master_info
+ - mysql.slave_worker_info
+ - performance_schema.*
+ - TODO: information_schema.*
+ In practice, from this list, only performance_schema.* tables
+ are written to by user queries.
+ */
+ uint non_replicated_tables_count= 0;
+
+#ifndef DBUG_OFF
+ {
+ static const char *prelocked_mode_name[] = {
+ "NON_PRELOCKED",
+ "PRELOCKED",
+ "PRELOCKED_UNDER_LOCK_TABLES",
+ };
+ DBUG_PRINT("debug", ("prelocked_mode: %s",
+ prelocked_mode_name[locked_tables_mode]));
+ }
+#endif
+
+ /*
+ Get the capabilities vector for all involved storage engines and
+ mask out the flags for the binary log.
+ */
+ for (TABLE_LIST *table= tables; table; table= table->next_global)
+ {
+ if (table->placeholder())
+ continue;
+
+ handler::Table_flags const flags= table->table->file->ha_table_flags();
+
+ DBUG_PRINT("info", ("table: %s; ha_table_flags: 0x%llx",
+ table->table_name, flags));
+
+ if (table->table->no_replicate)
+ {
+ /*
+ The statement uses a table that is not replicated.
+ The following properties about the table:
+ - persistent / transient
+ - transactional / non transactional
+ - temporary / permanent
+ - read or write
+ - multiple engines involved because of this table
+ are not relevant, as this table is completely ignored.
+ Because the statement uses a non replicated table,
+ using STATEMENT format in the binlog is impossible.
+ Either this statement will be discarded entirely,
+ or it will be logged (possibly partially) in ROW format.
+ */
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_TABLE);
+
+ if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ non_replicated_tables_count++;
+ continue;
+ }
+ }
+
+ replicated_tables_count++;
+
+ if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ if (prev_write_table && prev_write_table->file->ht !=
+ table->table->file->ht)
+ multi_write_engine= TRUE;
+
+ my_bool trans= table->table->file->has_transactions();
+
+ if (table->table->s->tmp_table)
+ lex->set_stmt_accessed_table(trans ? LEX::STMT_WRITES_TEMP_TRANS_TABLE :
+ LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE);
+ else
+ lex->set_stmt_accessed_table(trans ? LEX::STMT_WRITES_TRANS_TABLE :
+ LEX::STMT_WRITES_NON_TRANS_TABLE);
+
+ flags_write_all_set &= flags;
+ flags_write_some_set |= flags;
+ is_write= TRUE;
+
+ prev_write_table= table->table;
+
+ }
+ flags_access_some_set |= flags;
+
+ if (lex->sql_command != SQLCOM_CREATE_TABLE ||
+ (lex->sql_command == SQLCOM_CREATE_TABLE &&
+ lex->create_info.tmp_table()))
+ {
+ my_bool trans= table->table->file->has_transactions();
+
+ if (table->table->s->tmp_table)
+ lex->set_stmt_accessed_table(trans ? LEX::STMT_READS_TEMP_TRANS_TABLE :
+ LEX::STMT_READS_TEMP_NON_TRANS_TABLE);
+ else
+ lex->set_stmt_accessed_table(trans ? LEX::STMT_READS_TRANS_TABLE :
+ LEX::STMT_READS_NON_TRANS_TABLE);
+ }
+
+ if (prev_access_table && prev_access_table->file->ht !=
+ table->table->file->ht)
+ multi_access_engine= TRUE;
+
+ prev_access_table= table->table;
+ }
+
+ DBUG_PRINT("info", ("flags_write_all_set: 0x%llx", flags_write_all_set));
+ DBUG_PRINT("info", ("flags_write_some_set: 0x%llx", flags_write_some_set));
+ DBUG_PRINT("info", ("flags_access_some_set: 0x%llx", flags_access_some_set));
+ DBUG_PRINT("info", ("multi_write_engine: %d", multi_write_engine));
+ DBUG_PRINT("info", ("multi_access_engine: %d", multi_access_engine));
+
+ int error= 0;
+ int unsafe_flags;
+
+ bool multi_stmt_trans= in_multi_stmt_transaction_mode();
+ bool trans_table= trans_has_updated_trans_table(this);
+ bool binlog_direct= variables.binlog_direct_non_trans_update;
+
+ if (lex->is_mixed_stmt_unsafe(multi_stmt_trans, binlog_direct,
+ trans_table, tx_isolation))
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MIXED_STATEMENT);
+ else if (multi_stmt_trans && trans_table && !binlog_direct &&
+ lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE))
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS);
+
+ /*
+ If more than one engine is involved in the statement and at
+ least one is doing it's own logging (is *self-logging*), the
+ statement cannot be logged atomically, so we generate an error
+ rather than allowing the binlog to become corrupt.
+ */
+ if (multi_write_engine &&
+ (flags_write_some_set & HA_HAS_OWN_BINLOGGING))
+ my_error((error= ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE),
+ MYF(0));
+ else if (multi_access_engine && flags_access_some_set & HA_HAS_OWN_BINLOGGING)
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE);
+
+ /* both statement-only and row-only engines involved */
+ if ((flags_write_all_set & (HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE)) == 0)
+ {
+ /*
+ 1. Error: Binary logging impossible since both row-incapable
+ engines and statement-incapable engines are involved
+ */
+ my_error((error= ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE), MYF(0));
+ }
+ /* statement-only engines involved */
+ else if ((flags_write_all_set & HA_BINLOG_ROW_CAPABLE) == 0)
+ {
+ if (lex->is_stmt_row_injection())
+ {
+ /*
+ 4. Error: Cannot execute row injection since table uses
+ storage engine limited to statement-logging
+ */
+ my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE), MYF(0));
+ }
+ else if (variables.binlog_format == BINLOG_FORMAT_ROW &&
+ sqlcom_can_generate_row_events(this))
+ {
+ /*
+ 2. Error: Cannot modify table that uses a storage engine
+ limited to statement-logging when BINLOG_FORMAT = ROW
+ */
+ my_error((error= ER_BINLOG_ROW_MODE_AND_STMT_ENGINE), MYF(0));
+ }
+ else if ((unsafe_flags= lex->get_stmt_unsafe_flags()) != 0)
+ {
+ /*
+ 3. Error: Cannot execute statement: binlogging of unsafe
+ statement is impossible when storage engine is limited to
+ statement-logging and BINLOG_FORMAT = MIXED.
+ */
+ for (int unsafe_type= 0;
+ unsafe_type < LEX::BINLOG_STMT_UNSAFE_COUNT;
+ unsafe_type++)
+ if (unsafe_flags & (1 << unsafe_type))
+ my_error((error= ER_BINLOG_UNSAFE_AND_STMT_ENGINE), MYF(0),
+ ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type]));
+ }
+ /* log in statement format! */
+ }
+ /* no statement-only engines */
+ else
+ {
+ /* binlog_format = STATEMENT */
+ if (variables.binlog_format == BINLOG_FORMAT_STMT)
+ {
+ if (lex->is_stmt_row_injection())
+ {
+ /*
+ 6. Error: Cannot execute row injection since
+ BINLOG_FORMAT = STATEMENT
+ */
+ my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_MODE), MYF(0));
+ }
+ else if ((flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0 &&
+ sqlcom_can_generate_row_events(this))
+ {
+ /*
+ 5. Error: Cannot modify table that uses a storage engine
+ limited to row-logging when binlog_format = STATEMENT
+ */
+ my_error((error= ER_BINLOG_STMT_MODE_AND_ROW_ENGINE), MYF(0), "");
+ }
+ else if (is_write && (unsafe_flags= lex->get_stmt_unsafe_flags()) != 0)
+ {
+ /*
+ 7. Warning: Unsafe statement logged as statement due to
+ binlog_format = STATEMENT
+ */
+ binlog_unsafe_warning_flags|= unsafe_flags;
+
+ DBUG_PRINT("info", ("Scheduling warning to be issued by "
+ "binlog_query: '%s'",
+ ER(ER_BINLOG_UNSAFE_STATEMENT)));
+ DBUG_PRINT("info", ("binlog_unsafe_warning_flags: 0x%x",
+ binlog_unsafe_warning_flags));
+ }
+ /* log in statement format! */
+ }
+ /* No statement-only engines and binlog_format != STATEMENT.
+ I.e., nothing prevents us from row logging if needed. */
+ else
+ {
+ if (lex->is_stmt_unsafe() || lex->is_stmt_row_injection()
+ || (flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0)
+ {
+ /* log in row format! */
+ set_current_stmt_binlog_format_row_if_mixed();
+ }
+ }
+ }
+
+ if (non_replicated_tables_count > 0)
+ {
+ if ((replicated_tables_count == 0) || ! is_write)
+ {
+ DBUG_PRINT("info", ("decision: no logging, no replicated table affected"));
+ set_binlog_local_stmt_filter();
+ }
+ else
+ {
+ if (! is_current_stmt_binlog_format_row())
+ {
+ my_error((error= ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES), MYF(0));
+ }
+ else
+ {
+ clear_binlog_local_stmt_filter();
+ }
+ }
+ }
+ else
+ {
+ clear_binlog_local_stmt_filter();
+ }
+
+ if (error) {
+ DBUG_PRINT("info", ("decision: no logging since an error was generated"));
+ DBUG_RETURN(-1);
+ }
+ DBUG_PRINT("info", ("decision: logging in %s format",
+ is_current_stmt_binlog_format_row() ?
+ "ROW" : "STATEMENT"));
+
+ if (variables.binlog_format == BINLOG_FORMAT_ROW &&
+ (lex->sql_command == SQLCOM_UPDATE ||
+ lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ lex->sql_command == SQLCOM_DELETE ||
+ lex->sql_command == SQLCOM_DELETE_MULTI))
+ {
+ String table_names;
+ /*
+ Generate a warning for UPDATE/DELETE statements that modify a
+ BLACKHOLE table, as row events are not logged in row format.
+ */
+ for (TABLE_LIST *table= tables; table; table= table->next_global)
+ {
+ if (table->placeholder())
+ continue;
+ if (table->table->file->ht->db_type == DB_TYPE_BLACKHOLE_DB &&
+ table->lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ table_names.append(table->table_name);
+ table_names.append(",");
+ }
+ }
+ if (!table_names.is_empty())
+ {
+ bool is_update= (lex->sql_command == SQLCOM_UPDATE ||
+ lex->sql_command == SQLCOM_UPDATE_MULTI);
+ /*
+ Replace the last ',' with '.' for table_names
+ */
+ table_names.replace(table_names.length()-1, 1, ".", 1);
+ push_warning_printf(this, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR,
+ "Row events are not logged for %s statements "
+ "that modify BLACKHOLE tables in row format. "
+ "Table(s): '%-.192s'",
+ is_update ? "UPDATE" : "DELETE",
+ table_names.c_ptr());
+ }
+ }
+ }
+#ifndef DBUG_OFF
+ else
+ DBUG_PRINT("info", ("decision: no logging since "
+ "mysql_bin_log.is_open() = %d "
+ "and (options & OPTION_BIN_LOG) = 0x%llx "
+ "and binlog_format = %lu "
+ "and binlog_filter->db_ok(db) = %d",
+ mysql_bin_log.is_open(),
+ (variables.option_bits & OPTION_BIN_LOG),
+ variables.binlog_format,
+ binlog_filter->db_ok(db)));
+#endif
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Implementation of interface to write rows to the binary log through the
+ thread. The thread is responsible for writing the rows it has
+ inserted/updated/deleted.
+*/
+
+#ifndef MYSQL_CLIENT
+
+/*
+ Template member function for ensuring that there is an rows log
+ event of the apropriate type before proceeding.
+
+ PRE CONDITION:
+ - Events of type 'RowEventT' have the type code 'type_code'.
+
+ POST CONDITION:
+ If a non-NULL pointer is returned, the pending event for thread 'thd' will
+ be an event of type 'RowEventT' (which have the type code 'type_code')
+ will either empty or have enough space to hold 'needed' bytes. In
+ addition, the columns bitmap will be correct for the row, meaning that
+ the pending event will be flushed if the columns in the event differ from
+ the columns suppled to the function.
+
+ RETURNS
+ If no error, a non-NULL pending event (either one which already existed or
+ the newly created one).
+ If error, NULL.
+ */
+
+template <class RowsEventT> Rows_log_event*
+THD::binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id,
+ MY_BITMAP const* cols,
+ size_t colcnt,
+ size_t needed,
+ bool is_transactional,
+ RowsEventT *hint __attribute__((unused)))
+{
+ DBUG_ENTER("binlog_prepare_pending_rows_event");
+ /* Pre-conditions */
+ DBUG_ASSERT(table->s->table_map_id != ~0UL);
+
+ /* Fetch the type code for the RowsEventT template parameter */
+ int const general_type_code= RowsEventT::TYPE_CODE;
+
+ /* Ensure that all events in a GTID group are in the same cache */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ is_transactional= 1;
+
+ /*
+ There is no good place to set up the transactional data, so we
+ have to do it here.
+ */
+ if (binlog_setup_trx_data() == NULL)
+ DBUG_RETURN(NULL);
+
+ Rows_log_event* pending= binlog_get_pending_rows_event(is_transactional);
+
+ if (unlikely(pending && !pending->is_valid()))
+ DBUG_RETURN(NULL);
+
+ /*
+ Check if the current event is non-NULL and a write-rows
+ event. Also check if the table provided is mapped: if it is not,
+ then we have switched to writing to a new table.
+ If there is no pending event, we need to create one. If there is a pending
+ event, but it's not about the same table id, or not of the same type
+ (between Write, Update and Delete), or not the same affected columns, or
+ going to be too big, flush this event to disk and create a new pending
+ event.
+ */
+ if (!pending ||
+ pending->server_id != serv_id ||
+ pending->get_table_id() != table->s->table_map_id ||
+ pending->get_general_type_code() != general_type_code ||
+ pending->get_data_size() + needed > opt_binlog_rows_event_max_size ||
+ pending->get_width() != colcnt ||
+ !bitmap_cmp(pending->get_cols(), cols))
+ {
+ /* Create a new RowsEventT... */
+ Rows_log_event* const
+ ev= new RowsEventT(this, table, table->s->table_map_id, cols,
+ is_transactional);
+ if (unlikely(!ev))
+ DBUG_RETURN(NULL);
+ ev->server_id= serv_id; // I don't like this, it's too easy to forget.
+ /*
+ flush the pending event and replace it with the newly created
+ event...
+ */
+ if (unlikely(
+ mysql_bin_log.flush_and_set_pending_rows_event(this, ev,
+ is_transactional)))
+ {
+ delete ev;
+ DBUG_RETURN(NULL);
+ }
+
+ DBUG_RETURN(ev); /* This is the new pending event */
+ }
+ DBUG_RETURN(pending); /* This is the current pending event */
+}
+
+/* Declare in unnamed namespace. */
+CPP_UNNAMED_NS_START
+ /**
+ Class to handle temporary allocation of memory for row data.
+
+ The responsibilities of the class is to provide memory for
+ packing one or two rows of packed data (depending on what
+ constructor is called).
+
+ In order to make the allocation more efficient for "simple" rows,
+ i.e., rows that do not contain any blobs, a pointer to the
+ allocated memory is of memory is stored in the table structure
+ for simple rows. If memory for a table containing a blob field
+ is requested, only memory for that is allocated, and subsequently
+ released when the object is destroyed.
+
+ */
+ class Row_data_memory {
+ public:
+ /**
+ Build an object to keep track of a block-local piece of memory
+ for storing a row of data.
+
+ @param table
+ Table where the pre-allocated memory is stored.
+
+ @param length
+ Length of data that is needed, if the record contain blobs.
+ */
+ Row_data_memory(TABLE *table, size_t const len1)
+ : m_memory(0)
+ {
+#ifndef DBUG_OFF
+ m_alloc_checked= FALSE;
+#endif
+ allocate_memory(table, len1);
+ m_ptr[0]= has_memory() ? m_memory : 0;
+ m_ptr[1]= 0;
+ }
+
+ Row_data_memory(TABLE *table, size_t const len1, size_t const len2)
+ : m_memory(0)
+ {
+#ifndef DBUG_OFF
+ m_alloc_checked= FALSE;
+#endif
+ allocate_memory(table, len1 + len2);
+ m_ptr[0]= has_memory() ? m_memory : 0;
+ m_ptr[1]= has_memory() ? m_memory + len1 : 0;
+ }
+
+ ~Row_data_memory()
+ {
+ if (m_memory != 0 && m_release_memory_on_destruction)
+ my_free(m_memory);
+ }
+
+ /**
+ Is there memory allocated?
+
+ @retval true There is memory allocated
+ @retval false Memory allocation failed
+ */
+ bool has_memory() const {
+#ifndef DBUG_OFF
+ m_alloc_checked= TRUE;
+#endif
+ return m_memory != 0;
+ }
+
+ uchar *slot(uint s)
+ {
+ DBUG_ASSERT(s < sizeof(m_ptr)/sizeof(*m_ptr));
+ DBUG_ASSERT(m_ptr[s] != 0);
+ DBUG_ASSERT(m_alloc_checked == TRUE);
+ return m_ptr[s];
+ }
+
+ private:
+ void allocate_memory(TABLE *const table, size_t const total_length)
+ {
+ if (table->s->blob_fields == 0)
+ {
+ /*
+ The maximum length of a packed record is less than this
+ length. We use this value instead of the supplied length
+ when allocating memory for records, since we don't know how
+ the memory will be used in future allocations.
+
+ Since table->s->reclength is for unpacked records, we have
+ to add two bytes for each field, which can potentially be
+ added to hold the length of a packed field.
+ */
+ size_t const maxlen= table->s->reclength + 2 * table->s->fields;
+
+ /*
+ Allocate memory for two records if memory hasn't been
+ allocated. We allocate memory for two records so that it can
+ be used when processing update rows as well.
+ */
+ if (table->write_row_record == 0)
+ table->write_row_record=
+ (uchar *) alloc_root(&table->mem_root, 2 * maxlen);
+ m_memory= table->write_row_record;
+ m_release_memory_on_destruction= FALSE;
+ }
+ else
+ {
+ m_memory= (uchar *) my_malloc(total_length, MYF(MY_WME));
+ m_release_memory_on_destruction= TRUE;
+ }
+ }
+
+#ifndef DBUG_OFF
+ mutable bool m_alloc_checked;
+#endif
+ bool m_release_memory_on_destruction;
+ uchar *m_memory;
+ uchar *m_ptr[2];
+ };
+
+CPP_UNNAMED_NS_END
+
+int THD::binlog_write_row(TABLE* table, bool is_trans,
+ MY_BITMAP const* cols, size_t colcnt,
+ uchar const *record)
+{
+ DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open());
+
+ /*
+ Pack records into format for transfer. We are allocating more
+ memory than needed, but that doesn't matter.
+ */
+ Row_data_memory memory(table, max_row_length(table, record));
+ if (!memory.has_memory())
+ return HA_ERR_OUT_OF_MEM;
+
+ uchar *row_data= memory.slot(0);
+
+ size_t const len= pack_row(table, cols, row_data, record);
+
+ /* Ensure that all events in a GTID group are in the same cache */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ is_trans= 1;
+
+ Rows_log_event* const ev=
+ binlog_prepare_pending_rows_event(table, variables.server_id, cols, colcnt,
+ len, is_trans,
+ static_cast<Write_rows_log_event*>(0));
+
+ if (unlikely(ev == 0))
+ return HA_ERR_OUT_OF_MEM;
+
+ return ev->add_row_data(row_data, len);
+}
+
+int THD::binlog_update_row(TABLE* table, bool is_trans,
+ MY_BITMAP const* cols, size_t colcnt,
+ const uchar *before_record,
+ const uchar *after_record)
+{
+ DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open());
+
+ size_t const before_maxlen = max_row_length(table, before_record);
+ size_t const after_maxlen = max_row_length(table, after_record);
+
+ Row_data_memory row_data(table, before_maxlen, after_maxlen);
+ if (!row_data.has_memory())
+ return HA_ERR_OUT_OF_MEM;
+
+ uchar *before_row= row_data.slot(0);
+ uchar *after_row= row_data.slot(1);
+
+ size_t const before_size= pack_row(table, cols, before_row,
+ before_record);
+ size_t const after_size= pack_row(table, cols, after_row,
+ after_record);
+
+ /* Ensure that all events in a GTID group are in the same cache */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ is_trans= 1;
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("before_record", before_record, table->s->reclength);
+ DBUG_DUMP("after_record", after_record, table->s->reclength);
+ DBUG_DUMP("before_row", before_row, before_size);
+ DBUG_DUMP("after_row", after_row, after_size);
+#endif
+
+ Rows_log_event* const ev=
+ binlog_prepare_pending_rows_event(table, variables.server_id, cols, colcnt,
+ before_size + after_size, is_trans,
+ static_cast<Update_rows_log_event*>(0));
+
+ if (unlikely(ev == 0))
+ return HA_ERR_OUT_OF_MEM;
+
+ return
+ ev->add_row_data(before_row, before_size) ||
+ ev->add_row_data(after_row, after_size);
+}
+
+int THD::binlog_delete_row(TABLE* table, bool is_trans,
+ MY_BITMAP const* cols, size_t colcnt,
+ uchar const *record)
+{
+ DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open());
+
+ /*
+ Pack records into format for transfer. We are allocating more
+ memory than needed, but that doesn't matter.
+ */
+ Row_data_memory memory(table, max_row_length(table, record));
+ if (unlikely(!memory.has_memory()))
+ return HA_ERR_OUT_OF_MEM;
+
+ uchar *row_data= memory.slot(0);
+
+ size_t const len= pack_row(table, cols, row_data, record);
+
+ /* Ensure that all events in a GTID group are in the same cache */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ is_trans= 1;
+
+ Rows_log_event* const ev=
+ binlog_prepare_pending_rows_event(table, variables.server_id, cols, colcnt,
+ len, is_trans,
+ static_cast<Delete_rows_log_event*>(0));
+
+ if (unlikely(ev == 0))
+ return HA_ERR_OUT_OF_MEM;
+
+ return ev->add_row_data(row_data, len);
+}
+
+
+int THD::binlog_remove_pending_rows_event(bool clear_maps,
+ bool is_transactional)
+{
+ DBUG_ENTER("THD::binlog_remove_pending_rows_event");
+
+ if (!mysql_bin_log.is_open())
+ DBUG_RETURN(0);
+
+ /* Ensure that all events in a GTID group are in the same cache */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ is_transactional= 1;
+
+ mysql_bin_log.remove_pending_rows_event(this, is_transactional);
+
+ if (clear_maps)
+ binlog_table_maps= 0;
+
+ DBUG_RETURN(0);
+}
+
+int THD::binlog_flush_pending_rows_event(bool stmt_end, bool is_transactional)
+{
+ DBUG_ENTER("THD::binlog_flush_pending_rows_event");
+ /*
+ We shall flush the pending event even if we are not in row-based
+ mode: it might be the case that we left row-based mode before
+ flushing anything (e.g., if we have explicitly locked tables).
+ */
+ if (!mysql_bin_log.is_open())
+ DBUG_RETURN(0);
+
+ /* Ensure that all events in a GTID group are in the same cache */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ is_transactional= 1;
+
+ /*
+ Mark the event as the last event of a statement if the stmt_end
+ flag is set.
+ */
+ int error= 0;
+ if (Rows_log_event *pending= binlog_get_pending_rows_event(is_transactional))
+ {
+ if (stmt_end)
+ {
+ pending->set_flags(Rows_log_event::STMT_END_F);
+ binlog_table_maps= 0;
+ }
+
+ error= mysql_bin_log.flush_and_set_pending_rows_event(this, 0,
+ is_transactional);
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+#if !defined(DBUG_OFF) && !defined(_lint)
+static const char *
+show_query_type(THD::enum_binlog_query_type qtype)
+{
+ switch (qtype) {
+ case THD::ROW_QUERY_TYPE:
+ return "ROW";
+ case THD::STMT_QUERY_TYPE:
+ return "STMT";
+ case THD::QUERY_TYPE_COUNT:
+ default:
+ DBUG_ASSERT(0 <= qtype && qtype < THD::QUERY_TYPE_COUNT);
+ }
+ static char buf[64];
+ sprintf(buf, "UNKNOWN#%d", qtype);
+ return buf;
+}
+#endif
+
+/*
+ Constants required for the limit unsafe warnings suppression
+*/
+//seconds after which the limit unsafe warnings suppression will be activated
+#define LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT 5*60
+//number of limit unsafe warnings after which the suppression will be activated
+#define LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT 10
+
+static ulonglong unsafe_suppression_start_time= 0;
+static bool unsafe_warning_suppression_active[LEX::BINLOG_STMT_UNSAFE_COUNT];
+static ulong unsafe_warnings_count[LEX::BINLOG_STMT_UNSAFE_COUNT];
+static ulong total_unsafe_warnings_count;
+
+/**
+ Auxiliary function to reset the limit unsafety warning suppression.
+ This is done without mutex protection, but this should be good
+ enough as it doesn't matter if we loose a couple of suppressed
+ messages or if this is called multiple times.
+*/
+
+static void reset_binlog_unsafe_suppression(ulonglong now)
+{
+ uint i;
+ DBUG_ENTER("reset_binlog_unsafe_suppression");
+
+ unsafe_suppression_start_time= now;
+ total_unsafe_warnings_count= 0;
+
+ for (i= 0 ; i < LEX::BINLOG_STMT_UNSAFE_COUNT ; i++)
+ {
+ unsafe_warnings_count[i]= 0;
+ unsafe_warning_suppression_active[i]= 0;
+ }
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Auxiliary function to print warning in the error log.
+*/
+static void print_unsafe_warning_to_log(int unsafe_type, char* buf,
+ char* query)
+{
+ DBUG_ENTER("print_unsafe_warning_in_log");
+ sprintf(buf, ER(ER_BINLOG_UNSAFE_STATEMENT),
+ ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type]));
+ sql_print_warning(ER(ER_MESSAGE_AND_STATEMENT), buf, query);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Auxiliary function to check if the warning for unsafe repliction statements
+ should be thrown or suppressed.
+
+ Logic is:
+ - If we get more than LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT errors
+ of one type, that type of errors will be suppressed for
+ LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT.
+ - When the time limit has been reached, all suppression is reset.
+
+ This means that if one gets many different types of errors, some of them
+ may be reset less than LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT. However at
+ least one error is disable for this time.
+
+ SYNOPSIS:
+ @params
+ unsafe_type - The type of unsafety.
+
+ RETURN:
+ 0 0k to log
+ 1 Message suppressed
+*/
+
+static bool protect_against_unsafe_warning_flood(int unsafe_type)
+{
+ ulong count;
+ ulonglong now= my_interval_timer()/1000000000ULL;
+ DBUG_ENTER("protect_against_unsafe_warning_flood");
+
+ count= ++unsafe_warnings_count[unsafe_type];
+ total_unsafe_warnings_count++;
+
+ /*
+ INITIALIZING:
+ If this is the first time this function is called with log warning
+ enabled, the monitoring the unsafe warnings should start.
+ */
+ if (unsafe_suppression_start_time == 0)
+ {
+ reset_binlog_unsafe_suppression(now);
+ DBUG_RETURN(0);
+ }
+
+ /*
+ The following is true if we got too many errors or if the error was
+ already suppressed
+ */
+ if (count >= LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT)
+ {
+ ulonglong diff_time= (now - unsafe_suppression_start_time);
+
+ if (!unsafe_warning_suppression_active[unsafe_type])
+ {
+ /*
+ ACTIVATION:
+ We got LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT warnings in
+ less than LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT we activate the
+ suppression.
+ */
+ if (diff_time <= LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT)
+ {
+ unsafe_warning_suppression_active[unsafe_type]= 1;
+ sql_print_information("Suppressing warnings of type '%s' for up to %d seconds because of flooding",
+ ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type]),
+ LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT);
+ }
+ else
+ {
+ /*
+ There is no flooding till now, therefore we restart the monitoring
+ */
+ reset_binlog_unsafe_suppression(now);
+ }
+ }
+ else
+ {
+ /* This type of warnings was suppressed */
+ if (diff_time > LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT)
+ {
+ ulong save_count= total_unsafe_warnings_count;
+ /* Print a suppression note and remove the suppression */
+ reset_binlog_unsafe_suppression(now);
+ sql_print_information("Suppressed %lu unsafe warnings during "
+ "the last %d seconds",
+ save_count, (int) diff_time);
+ }
+ }
+ }
+ DBUG_RETURN(unsafe_warning_suppression_active[unsafe_type]);
+}
+
+/**
+ Auxiliary method used by @c binlog_query() to raise warnings.
+
+ The type of warning and the type of unsafeness is stored in
+ THD::binlog_unsafe_warning_flags.
+*/
+void THD::issue_unsafe_warnings()
+{
+ char buf[MYSQL_ERRMSG_SIZE * 2];
+ uint32 unsafe_type_flags;
+ DBUG_ENTER("issue_unsafe_warnings");
+ /*
+ Ensure that binlog_unsafe_warning_flags is big enough to hold all
+ bits. This is actually a constant expression.
+ */
+ DBUG_ASSERT(LEX::BINLOG_STMT_UNSAFE_COUNT <=
+ sizeof(binlog_unsafe_warning_flags) * CHAR_BIT);
+
+ if (!(unsafe_type_flags= binlog_unsafe_warning_flags))
+ DBUG_VOID_RETURN; // Nothing to do
+
+ /*
+ For each unsafe_type, check if the statement is unsafe in this way
+ and issue a warning.
+ */
+ for (int unsafe_type=0;
+ unsafe_type < LEX::BINLOG_STMT_UNSAFE_COUNT;
+ unsafe_type++)
+ {
+ if ((unsafe_type_flags & (1 << unsafe_type)) != 0)
+ {
+ push_warning_printf(this, Sql_condition::WARN_LEVEL_NOTE,
+ ER_BINLOG_UNSAFE_STATEMENT,
+ ER(ER_BINLOG_UNSAFE_STATEMENT),
+ ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type]));
+ if (global_system_variables.log_warnings > 0 &&
+ !protect_against_unsafe_warning_flood(unsafe_type))
+ print_unsafe_warning_to_log(unsafe_type, buf, query());
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Log the current query.
+
+ The query will be logged in either row format or statement format
+ depending on the value of @c current_stmt_binlog_format_row field and
+ the value of the @c qtype parameter.
+
+ This function must be called:
+
+ - After the all calls to ha_*_row() functions have been issued.
+
+ - After any writes to system tables. Rationale: if system tables
+ were written after a call to this function, and the master crashes
+ after the call to this function and before writing the system
+ tables, then the master and slave get out of sync.
+
+ - Before tables are unlocked and closed.
+
+ @see decide_logging_format
+
+ @retval 0 Success
+
+ @retval nonzero If there is a failure when writing the query (e.g.,
+ write failure), then the error code is returned.
+*/
+int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg,
+ ulong query_len, bool is_trans, bool direct,
+ bool suppress_use, int errcode)
+{
+ DBUG_ENTER("THD::binlog_query");
+ DBUG_PRINT("enter", ("qtype: %s query: '%-.*s'",
+ show_query_type(qtype), (int) query_len, query_arg));
+ DBUG_ASSERT(query_arg && mysql_bin_log.is_open());
+
+ /* If this is withing a BEGIN ... COMMIT group, don't log it */
+ if (variables.option_bits & OPTION_GTID_BEGIN)
+ {
+ direct= 0;
+ is_trans= 1;
+ }
+ DBUG_PRINT("info", ("is_trans: %d direct: %d", is_trans, direct));
+
+ if (get_binlog_local_stmt_filter() == BINLOG_FILTER_SET)
+ {
+ /*
+ The current statement is to be ignored, and not written to
+ the binlog. Do not call issue_unsafe_warnings().
+ */
+ DBUG_RETURN(0);
+ }
+
+ /*
+ If we are not in prelocked mode, mysql_unlock_tables() will be
+ called after this binlog_query(), so we have to flush the pending
+ rows event with the STMT_END_F set to unlock all tables at the
+ slave side as well.
+
+ If we are in prelocked mode, the flushing will be done inside the
+ top-most close_thread_tables().
+ */
+ if (this->locked_tables_mode <= LTM_LOCK_TABLES)
+ if (int error= binlog_flush_pending_rows_event(TRUE, is_trans))
+ DBUG_RETURN(error);
+
+ /*
+ Warnings for unsafe statements logged in statement format are
+ printed in three places instead of in decide_logging_format().
+ This is because the warnings should be printed only if the statement
+ is actually logged. When executing decide_logging_format(), we cannot
+ know for sure if the statement will be logged:
+
+ 1 - sp_head::execute_procedure which prints out warnings for calls to
+ stored procedures.
+
+ 2 - sp_head::execute_function which prints out warnings for calls
+ involving functions.
+
+ 3 - THD::binlog_query (here) which prints warning for top level
+ statements not covered by the two cases above: i.e., if not insided a
+ procedure and a function.
+
+ Besides, we should not try to print these warnings if it is not
+ possible to write statements to the binary log as it happens when
+ the execution is inside a function, or generaly speaking, when
+ the variables.option_bits & OPTION_BIN_LOG is false.
+
+ */
+ if ((variables.option_bits & OPTION_BIN_LOG) &&
+ spcont == NULL && !binlog_evt_union.do_union)
+ issue_unsafe_warnings();
+
+ switch (qtype) {
+ /*
+ ROW_QUERY_TYPE means that the statement may be logged either in
+ row format or in statement format. If
+ current_stmt_binlog_format is row, it means that the
+ statement has already been logged in row format and hence shall
+ not be logged again.
+ */
+ case THD::ROW_QUERY_TYPE:
+ DBUG_PRINT("debug",
+ ("is_current_stmt_binlog_format_row: %d",
+ is_current_stmt_binlog_format_row()));
+ if (is_current_stmt_binlog_format_row())
+ DBUG_RETURN(0);
+ /* Fall through */
+
+ /*
+ STMT_QUERY_TYPE means that the query must be logged in statement
+ format; it cannot be logged in row format. This is typically
+ used by DDL statements. It is an error to use this query type
+ if current_stmt_binlog_format_row is row.
+
+ @todo Currently there are places that call this method with
+ STMT_QUERY_TYPE and current_stmt_binlog_format is row. Fix those
+ places and add assert to ensure correct behavior. /Sven
+ */
+ case THD::STMT_QUERY_TYPE:
+ /*
+ The MYSQL_LOG::write() function will set the STMT_END_F flag and
+ flush the pending rows event if necessary.
+ */
+ {
+ Query_log_event qinfo(this, query_arg, query_len, is_trans, direct,
+ suppress_use, errcode);
+ /*
+ Binlog table maps will be irrelevant after a Query_log_event
+ (they are just removed on the slave side) so after the query
+ log event is written to the binary log, we pretend that no
+ table maps were written.
+ */
+ int error= mysql_bin_log.write(&qinfo);
+ binlog_table_maps= 0;
+ DBUG_RETURN(error);
+ }
+
+ case THD::QUERY_TYPE_COUNT:
+ default:
+ DBUG_ASSERT(qtype < QUERY_TYPE_COUNT);
+ }
+ DBUG_RETURN(0);
+}
+
+void
+THD::wait_for_wakeup_ready()
+{
+ mysql_mutex_lock(&LOCK_wakeup_ready);
+ while (!wakeup_ready)
+ mysql_cond_wait(&COND_wakeup_ready, &LOCK_wakeup_ready);
+ mysql_mutex_unlock(&LOCK_wakeup_ready);
+}
+
+void
+THD::signal_wakeup_ready()
+{
+ mysql_mutex_lock(&LOCK_wakeup_ready);
+ wakeup_ready= true;
+ mysql_mutex_unlock(&LOCK_wakeup_ready);
+ mysql_cond_signal(&COND_wakeup_ready);
+}
+
+
+void THD::rgi_lock_temporary_tables()
+{
+ mysql_mutex_lock(&rgi_slave->rli->data_lock);
+ temporary_tables= rgi_slave->rli->save_temporary_tables;
+}
+
+void THD::rgi_unlock_temporary_tables()
+{
+ rgi_slave->rli->save_temporary_tables= temporary_tables;
+ mysql_mutex_unlock(&rgi_slave->rli->data_lock);
+}
+
+bool THD::rgi_have_temporary_tables()
+{
+ return rgi_slave->rli->save_temporary_tables != 0;
+}
+
+
+void
+wait_for_commit::reinit()
+{
+ subsequent_commits_list= NULL;
+ next_subsequent_commit= NULL;
+ waitee= NULL;
+ opaque_pointer= NULL;
+ wakeup_error= 0;
+ wakeup_subsequent_commits_running= false;
+ commit_started= false;
+#ifdef SAFE_MUTEX
+ /*
+ When using SAFE_MUTEX, the ordering between taking the LOCK_wait_commit
+ mutexes is checked. This causes a problem when we re-use a mutex, as then
+ the expected locking order may change.
+
+ So in this case, do a re-init of the mutex. In release builds, we want to
+ avoid the overhead of a re-init though.
+ */
+ mysql_mutex_destroy(&LOCK_wait_commit);
+ mysql_mutex_init(key_LOCK_wait_commit, &LOCK_wait_commit, MY_MUTEX_INIT_FAST);
+#endif
+}
+
+
+wait_for_commit::wait_for_commit()
+{
+ mysql_mutex_init(key_LOCK_wait_commit, &LOCK_wait_commit, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_COND_wait_commit, &COND_wait_commit, 0);
+ reinit();
+}
+
+
+wait_for_commit::~wait_for_commit()
+{
+ /*
+ Since we do a dirty read of the waiting_for_commit flag in
+ wait_for_prior_commit() and in unregister_wait_for_prior_commit(), we need
+ to take extra care before freeing the wait_for_commit object.
+
+ It is possible for the waitee to be pre-empted inside wakeup(), just after
+ it has cleared the waiting_for_commit flag and before it has released the
+ LOCK_wait_commit mutex. And then it is possible for the waiter to find the
+ flag cleared in wait_for_prior_commit() and go finish up things and
+ de-allocate the LOCK_wait_commit and COND_wait_commit objects before the
+ waitee has time to be re-scheduled and finish unlocking the mutex and
+ signalling the condition. This would lead to the waitee accessing no
+ longer valid memory.
+
+ To prevent this, we do an extra lock/unlock of the mutex here before
+ deallocation; this makes certain that any waitee has completed wakeup()
+ first.
+ */
+ mysql_mutex_lock(&LOCK_wait_commit);
+ mysql_mutex_unlock(&LOCK_wait_commit);
+
+ mysql_mutex_destroy(&LOCK_wait_commit);
+ mysql_cond_destroy(&COND_wait_commit);
+}
+
+
+void
+wait_for_commit::wakeup(int wakeup_error)
+{
+ /*
+ We signal each waiter on their own condition and mutex (rather than using
+ pthread_cond_broadcast() or something like that).
+
+ Otherwise we would need to somehow ensure that they were done
+ waking up before we could allow this THD to be destroyed, which would
+ be annoying and unnecessary.
+
+ Note that wakeup_subsequent_commits2() depends on this function being a
+ full memory barrier (it is, because it takes a mutex lock).
+
+ */
+ mysql_mutex_lock(&LOCK_wait_commit);
+ waitee= NULL;
+ this->wakeup_error= wakeup_error;
+ /*
+ Note that it is critical that the mysql_cond_signal() here is done while
+ still holding the mutex. As soon as we release the mutex, the waiter might
+ deallocate the condition object.
+ */
+ mysql_cond_signal(&COND_wait_commit);
+ mysql_mutex_unlock(&LOCK_wait_commit);
+}
+
+
+/*
+ Register that the next commit of this THD should wait to complete until
+ commit in another THD (the waitee) has completed.
+
+ The wait may occur explicitly, with the waiter sitting in
+ wait_for_prior_commit() until the waitee calls wakeup_subsequent_commits().
+
+ Alternatively, the TC (eg. binlog) may do the commits of both waitee and
+ waiter at once during group commit, resolving both of them in the right
+ order.
+
+ Only one waitee can be registered for a waiter; it must be removed by
+ wait_for_prior_commit() or unregister_wait_for_prior_commit() before a new
+ one is registered. But it is ok for several waiters to register a wait for
+ the same waitee. It is also permissible for one THD to be both a waiter and
+ a waitee at the same time.
+*/
+void
+wait_for_commit::register_wait_for_prior_commit(wait_for_commit *waitee)
+{
+ DBUG_ASSERT(!this->waitee /* No prior registration allowed */);
+ wakeup_error= 0;
+ this->waitee= waitee;
+
+ mysql_mutex_lock(&waitee->LOCK_wait_commit);
+ /*
+ If waitee is in the middle of wakeup, then there is nothing to wait for,
+ so we need not register. This is necessary to avoid a race in unregister,
+ see comments on wakeup_subsequent_commits2() for details.
+ */
+ if (waitee->wakeup_subsequent_commits_running)
+ this->waitee= NULL;
+ else
+ {
+ /*
+ Put ourself at the head of the waitee's list of transactions that must
+ wait for it to commit first.
+ */
+ this->next_subsequent_commit= waitee->subsequent_commits_list;
+ waitee->subsequent_commits_list= this;
+ }
+ mysql_mutex_unlock(&waitee->LOCK_wait_commit);
+}
+
+
+/*
+ Wait for commit of another transaction to complete, as already registered
+ with register_wait_for_prior_commit(). If the commit already completed,
+ returns immediately.
+*/
+int
+wait_for_commit::wait_for_prior_commit2(THD *thd)
+{
+ PSI_stage_info old_stage;
+ wait_for_commit *loc_waitee;
+
+ mysql_mutex_lock(&LOCK_wait_commit);
+ DEBUG_SYNC(thd, "wait_for_prior_commit_waiting");
+ thd->ENTER_COND(&COND_wait_commit, &LOCK_wait_commit,
+ &stage_waiting_for_prior_transaction_to_commit,
+ &old_stage);
+ while ((loc_waitee= this->waitee) && !thd->check_killed())
+ mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit);
+ if (!loc_waitee)
+ {
+ if (wakeup_error)
+ my_error(ER_PRIOR_COMMIT_FAILED, MYF(0));
+ goto end;
+ }
+ /*
+ Wait was interrupted by kill. We need to unregister our wait and give the
+ error. But if a wakeup is already in progress, then we must ignore the
+ kill and not give error, otherwise we get inconsistency between waitee and
+ waiter as to whether we succeed or fail (eg. we may roll back but waitee
+ might attempt to commit both us and any subsequent commits waiting for us).
+ */
+ mysql_mutex_lock(&loc_waitee->LOCK_wait_commit);
+ if (loc_waitee->wakeup_subsequent_commits_running)
+ {
+ /* We are being woken up; ignore the kill and just wait. */
+ mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit);
+ do
+ {
+ mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit);
+ } while (this->waitee);
+ if (wakeup_error)
+ my_error(ER_PRIOR_COMMIT_FAILED, MYF(0));
+ goto end;
+ }
+ remove_from_list(&loc_waitee->subsequent_commits_list);
+ mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit);
+ this->waitee= NULL;
+
+ wakeup_error= thd->killed_errno();
+ if (!wakeup_error)
+ wakeup_error= ER_QUERY_INTERRUPTED;
+ my_message(wakeup_error, ER(wakeup_error), MYF(0));
+ thd->EXIT_COND(&old_stage);
+ /*
+ Must do the DEBUG_SYNC() _after_ exit_cond(), as DEBUG_SYNC is not safe to
+ use within enter_cond/exit_cond.
+ */
+ DEBUG_SYNC(thd, "wait_for_prior_commit_killed");
+ return wakeup_error;
+
+end:
+ thd->EXIT_COND(&old_stage);
+ return wakeup_error;
+}
+
+
+/*
+ Wakeup anyone waiting for us to have committed.
+
+ Note about locking:
+
+ We have a potential race or deadlock between wakeup_subsequent_commits() in
+ the waitee and unregister_wait_for_prior_commit() in the waiter.
+
+ Both waiter and waitee needs to take their own lock before it is safe to take
+ a lock on the other party - else the other party might disappear and invalid
+ memory data could be accessed. But if we take the two locks in different
+ order, we may end up in a deadlock.
+
+ The waiter needs to lock the waitee to delete itself from the list in
+ unregister_wait_for_prior_commit(). Thus wakeup_subsequent_commits() can not
+ hold its own lock while locking waiters, as this could lead to deadlock.
+
+ So we need to prevent unregister_wait_for_prior_commit() running while wakeup
+ is in progress - otherwise the unregister could complete before the wakeup,
+ leading to incorrect spurious wakeup or accessing invalid memory.
+
+ However, if we are in the middle of running wakeup_subsequent_commits(), then
+ there is no need for unregister_wait_for_prior_commit() in the first place -
+ the waiter can just do a normal wait_for_prior_commit(), as it will be
+ immediately woken up.
+
+ So the solution to the potential race/deadlock is to set a flag in the waitee
+ that wakeup_subsequent_commits() is in progress. When this flag is set,
+ unregister_wait_for_prior_commit() becomes just wait_for_prior_commit().
+
+ Then also register_wait_for_prior_commit() needs to check if
+ wakeup_subsequent_commits() is running, and skip the registration if
+ so. This is needed in case a new waiter manages to register itself and
+ immediately try to unregister while wakeup_subsequent_commits() is
+ running. Else the new waiter would also wait rather than unregister, but it
+ would not be woken up until next wakeup, which could be potentially much
+ later than necessary.
+*/
+
+void
+wait_for_commit::wakeup_subsequent_commits2(int wakeup_error)
+{
+ wait_for_commit *waiter;
+
+ mysql_mutex_lock(&LOCK_wait_commit);
+ wakeup_subsequent_commits_running= true;
+ waiter= subsequent_commits_list;
+ subsequent_commits_list= NULL;
+ mysql_mutex_unlock(&LOCK_wait_commit);
+
+ while (waiter)
+ {
+ /*
+ Important: we must grab the next pointer before waking up the waiter;
+ once the wakeup is done, the field could be invalidated at any time.
+ */
+ wait_for_commit *next= waiter->next_subsequent_commit;
+ waiter->wakeup(wakeup_error);
+ waiter= next;
+ }
+
+ /*
+ We need a full memory barrier between walking the list above, and clearing
+ the flag wakeup_subsequent_commits_running below. This barrier is needed
+ to ensure that no other thread will start to modify the list pointers
+ before we are done traversing the list.
+
+ But wait_for_commit::wakeup() does a full memory barrier already (it locks
+ a mutex), so no extra explicit barrier is needed here.
+ */
+ wakeup_subsequent_commits_running= false;
+}
+
+
+/* Cancel a previously registered wait for another THD to commit before us. */
+void
+wait_for_commit::unregister_wait_for_prior_commit2()
+{
+ wait_for_commit *loc_waitee;
+
+ mysql_mutex_lock(&LOCK_wait_commit);
+ if ((loc_waitee= this->waitee))
+ {
+ mysql_mutex_lock(&loc_waitee->LOCK_wait_commit);
+ if (loc_waitee->wakeup_subsequent_commits_running)
+ {
+ /*
+ When a wakeup is running, we cannot safely remove ourselves from the
+ list without corrupting it. Instead we can just wait, as wakeup is
+ already in progress and will thus be immediate.
+
+ See comments on wakeup_subsequent_commits2() for more details.
+ */
+ mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit);
+ while (this->waitee)
+ mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit);
+ }
+ else
+ {
+ /* Remove ourselves from the list in the waitee. */
+ remove_from_list(&loc_waitee->subsequent_commits_list);
+ mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit);
+ this->waitee= NULL;
+ }
+ }
+ wakeup_error= 0;
+ mysql_mutex_unlock(&LOCK_wait_commit);
+}
+
+
+bool Discrete_intervals_list::append(ulonglong start, ulonglong val,
+ ulonglong incr)
+{
+ DBUG_ENTER("Discrete_intervals_list::append");
+ /* first, see if this can be merged with previous */
+ if ((head == NULL) || tail->merge_if_contiguous(start, val, incr))
+ {
+ /* it cannot, so need to add a new interval */
+ Discrete_interval *new_interval= new Discrete_interval(start, val, incr);
+ DBUG_RETURN(append(new_interval));
+ }
+ DBUG_RETURN(0);
+}
+
+bool Discrete_intervals_list::append(Discrete_interval *new_interval)
+{
+ DBUG_ENTER("Discrete_intervals_list::append");
+ if (unlikely(new_interval == NULL))
+ DBUG_RETURN(1);
+ DBUG_PRINT("info",("adding new auto_increment interval"));
+ if (head == NULL)
+ head= current= new_interval;
+ else
+ tail->next= new_interval;
+ tail= new_interval;
+ elements++;
+ DBUG_RETURN(0);
+}
+
+#endif /* !defined(MYSQL_CLIENT) */
diff --git a/sql/sql_class.h b/sql/sql_class.h
new file mode 100644
index 00000000000..a8d8444571e
--- /dev/null
+++ b/sql/sql_class.h
@@ -0,0 +1,5036 @@
+/*
+ Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#ifndef SQL_CLASS_INCLUDED
+#define SQL_CLASS_INCLUDED
+
+/* Classes in mysql */
+
+#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */
+#ifdef MYSQL_SERVER
+#include "unireg.h" // REQUIRED: for other includes
+#endif
+#include <waiting_threads.h>
+#include "sql_const.h"
+#include <mysql/plugin_audit.h>
+#include "log.h"
+#include "rpl_tblmap.h"
+#include "mdl.h"
+#include "probes_mysql.h"
+#include "sql_locale.h" /* my_locale_st */
+#include "sql_profile.h" /* PROFILING */
+#include "scheduler.h" /* thd_scheduler */
+#include "protocol.h" /* Protocol_text, Protocol_binary */
+#include "violite.h" /* vio_is_connected */
+#include "thr_lock.h" /* thr_lock_type, THR_LOCK_DATA,
+ THR_LOCK_INFO */
+#include "sql_digest_stream.h" // sql_digest_state
+
+#include <mysql/psi/mysql_stage.h>
+#include <mysql/psi/mysql_statement.h>
+#include <mysql/psi/mysql_idle.h>
+#include <mysql/psi/mysql_table.h>
+#include <mysql_com_server.h>
+
+extern "C"
+void set_thd_stage_info(void *thd,
+ const PSI_stage_info *new_stage,
+ PSI_stage_info *old_stage,
+ const char *calling_func,
+ const char *calling_file,
+ const unsigned int calling_line);
+
+#define THD_STAGE_INFO(thd, stage) \
+ (thd)->enter_stage(& stage, NULL, __func__, __FILE__, __LINE__)
+
+#include "my_apc.h"
+#include "rpl_gtid.h"
+
+class Reprepare_observer;
+class Relay_log_info;
+struct rpl_group_info;
+class Rpl_filter;
+
+class Query_log_event;
+class Load_log_event;
+class Slave_log_event;
+class sp_rcontext;
+class sp_cache;
+class Lex_input_stream;
+class Parser_state;
+class Rows_log_event;
+class Sroutine_hash_entry;
+class user_var_entry;
+class rpl_io_thread_info;
+class rpl_sql_thread_info;
+
+enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY, RNEXT_SAME };
+enum enum_duplicates { DUP_ERROR, DUP_REPLACE, DUP_UPDATE };
+enum enum_delay_key_write { DELAY_KEY_WRITE_NONE, DELAY_KEY_WRITE_ON,
+ DELAY_KEY_WRITE_ALL };
+enum enum_slave_exec_mode { SLAVE_EXEC_MODE_STRICT,
+ SLAVE_EXEC_MODE_IDEMPOTENT,
+ SLAVE_EXEC_MODE_LAST_BIT };
+enum enum_slave_run_triggers_for_rbr { SLAVE_RUN_TRIGGERS_FOR_RBR_NO,
+ SLAVE_RUN_TRIGGERS_FOR_RBR_YES,
+ SLAVE_RUN_TRIGGERS_FOR_RBR_LOGGING};
+enum enum_slave_type_conversions { SLAVE_TYPE_CONVERSIONS_ALL_LOSSY,
+ SLAVE_TYPE_CONVERSIONS_ALL_NON_LOSSY};
+enum enum_mark_columns
+{ MARK_COLUMNS_NONE, MARK_COLUMNS_READ, MARK_COLUMNS_WRITE};
+enum enum_filetype { FILETYPE_CSV, FILETYPE_XML };
+
+/* Bits for different SQL modes modes (including ANSI mode) */
+#define MODE_REAL_AS_FLOAT (1ULL << 0)
+#define MODE_PIPES_AS_CONCAT (1ULL << 1)
+#define MODE_ANSI_QUOTES (1ULL << 2)
+#define MODE_IGNORE_SPACE (1ULL << 3)
+#define MODE_IGNORE_BAD_TABLE_OPTIONS (1ULL << 4)
+#define MODE_ONLY_FULL_GROUP_BY (1ULL << 5)
+#define MODE_NO_UNSIGNED_SUBTRACTION (1ULL << 6)
+#define MODE_NO_DIR_IN_CREATE (1ULL << 7)
+#define MODE_POSTGRESQL (1ULL << 8)
+#define MODE_ORACLE (1ULL << 9)
+#define MODE_MSSQL (1ULL << 10)
+#define MODE_DB2 (1ULL << 11)
+#define MODE_MAXDB (1ULL << 12)
+#define MODE_NO_KEY_OPTIONS (1ULL << 13)
+#define MODE_NO_TABLE_OPTIONS (1ULL << 14)
+#define MODE_NO_FIELD_OPTIONS (1ULL << 15)
+#define MODE_MYSQL323 (1ULL << 16)
+#define MODE_MYSQL40 (1ULL << 17)
+#define MODE_ANSI (1ULL << 18)
+#define MODE_NO_AUTO_VALUE_ON_ZERO (1ULL << 19)
+#define MODE_NO_BACKSLASH_ESCAPES (1ULL << 20)
+#define MODE_STRICT_TRANS_TABLES (1ULL << 21)
+#define MODE_STRICT_ALL_TABLES (1ULL << 22)
+#define MODE_NO_ZERO_IN_DATE (1ULL << 23)
+#define MODE_NO_ZERO_DATE (1ULL << 24)
+#define MODE_INVALID_DATES (1ULL << 25)
+#define MODE_ERROR_FOR_DIVISION_BY_ZERO (1ULL << 26)
+#define MODE_TRADITIONAL (1ULL << 27)
+#define MODE_NO_AUTO_CREATE_USER (1ULL << 28)
+#define MODE_HIGH_NOT_PRECEDENCE (1ULL << 29)
+#define MODE_NO_ENGINE_SUBSTITUTION (1ULL << 30)
+#define MODE_PAD_CHAR_TO_FULL_LENGTH (1ULL << 31)
+
+/* Bits for different old style modes */
+#define OLD_MODE_NO_DUP_KEY_WARNINGS_WITH_IGNORE (1 << 0)
+#define OLD_MODE_NO_PROGRESS_INFO (1 << 1)
+#define OLD_MODE_ZERO_DATE_TIME_CAST (1 << 2)
+
+extern char internal_table_name[2];
+extern char empty_c_string[1];
+extern LEX_STRING EMPTY_STR;
+extern MYSQL_PLUGIN_IMPORT const char **errmesg;
+
+extern bool volatile shutdown_in_progress;
+
+extern "C" LEX_STRING * thd_query_string (MYSQL_THD thd);
+extern "C" char **thd_query(MYSQL_THD thd);
+
+/**
+ @class CSET_STRING
+ @brief Character set armed LEX_STRING
+*/
+class CSET_STRING
+{
+private:
+ LEX_STRING string;
+ CHARSET_INFO *cs;
+public:
+ CSET_STRING() : cs(&my_charset_bin)
+ {
+ string.str= NULL;
+ string.length= 0;
+ }
+ CSET_STRING(char *str_arg, size_t length_arg, CHARSET_INFO *cs_arg) :
+ cs(cs_arg)
+ {
+ DBUG_ASSERT(cs_arg != NULL);
+ string.str= str_arg;
+ string.length= length_arg;
+ }
+
+ inline char *str() const { return string.str; }
+ inline uint32 length() const { return string.length; }
+ CHARSET_INFO *charset() const { return cs; }
+
+ friend LEX_STRING * thd_query_string (MYSQL_THD thd);
+ friend char **thd_query(MYSQL_THD thd);
+};
+
+
+#define TC_HEURISTIC_RECOVER_COMMIT 1
+#define TC_HEURISTIC_RECOVER_ROLLBACK 2
+extern ulong tc_heuristic_recover;
+
+typedef struct st_user_var_events
+{
+ user_var_entry *user_var_event;
+ char *value;
+ ulong length;
+ Item_result type;
+ uint charset_number;
+ bool unsigned_flag;
+} BINLOG_USER_VAR_EVENT;
+
+/*
+ The COPY_INFO structure is used by INSERT/REPLACE code.
+ The schema of the row counting by the INSERT/INSERT ... ON DUPLICATE KEY
+ UPDATE code:
+ If a row is inserted then the copied variable is incremented.
+ If a row is updated by the INSERT ... ON DUPLICATE KEY UPDATE and the
+ new data differs from the old one then the copied and the updated
+ variables are incremented.
+ The touched variable is incremented if a row was touched by the update part
+ of the INSERT ... ON DUPLICATE KEY UPDATE no matter whether the row
+ was actually changed or not.
+*/
+typedef struct st_copy_info {
+ ha_rows records; /**< Number of processed records */
+ ha_rows deleted; /**< Number of deleted records */
+ ha_rows updated; /**< Number of updated records */
+ ha_rows copied; /**< Number of copied records */
+ ha_rows error_count;
+ ha_rows touched; /* Number of touched records */
+ enum enum_duplicates handle_duplicates;
+ int escape_char, last_errno;
+ bool ignore;
+ /* for INSERT ... UPDATE */
+ List<Item> *update_fields;
+ List<Item> *update_values;
+ /* for VIEW ... WITH CHECK OPTION */
+ TABLE_LIST *view;
+} COPY_INFO;
+
+
+class Key_part_spec :public Sql_alloc {
+public:
+ LEX_STRING field_name;
+ uint length;
+ Key_part_spec(const LEX_STRING &name, uint len)
+ : field_name(name), length(len)
+ {}
+ Key_part_spec(const char *name, const size_t name_len, uint len)
+ : length(len)
+ { field_name.str= (char *)name; field_name.length= name_len; }
+ bool operator==(const Key_part_spec& other) const;
+ /**
+ Construct a copy of this Key_part_spec. field_name is copied
+ by-pointer as it is known to never change. At the same time
+ 'length' may be reset in mysql_prepare_create_table, and this
+ is why we supply it with a copy.
+
+ @return If out of memory, 0 is returned and an error is set in
+ THD.
+ */
+ Key_part_spec *clone(MEM_ROOT *mem_root) const
+ { return new (mem_root) Key_part_spec(*this); }
+};
+
+
+class Alter_drop :public Sql_alloc {
+public:
+ enum drop_type {KEY, COLUMN, FOREIGN_KEY };
+ const char *name;
+ enum drop_type type;
+ bool drop_if_exists;
+ Alter_drop(enum drop_type par_type,const char *par_name, bool par_exists)
+ :name(par_name), type(par_type), drop_if_exists(par_exists)
+ {
+ DBUG_ASSERT(par_name != NULL);
+ }
+ /**
+ Used to make a clone of this object for ALTER/CREATE TABLE
+ @sa comment for Key_part_spec::clone
+ */
+ Alter_drop *clone(MEM_ROOT *mem_root) const
+ { return new (mem_root) Alter_drop(*this); }
+};
+
+
+class Alter_column :public Sql_alloc {
+public:
+ const char *name;
+ Item *def;
+ Alter_column(const char *par_name,Item *literal)
+ :name(par_name), def(literal) {}
+ /**
+ Used to make a clone of this object for ALTER/CREATE TABLE
+ @sa comment for Key_part_spec::clone
+ */
+ Alter_column *clone(MEM_ROOT *mem_root) const
+ { return new (mem_root) Alter_column(*this); }
+};
+
+
+class Key :public Sql_alloc {
+public:
+ enum Keytype { PRIMARY, UNIQUE, MULTIPLE, FULLTEXT, SPATIAL, FOREIGN_KEY};
+ enum Keytype type;
+ KEY_CREATE_INFO key_create_info;
+ List<Key_part_spec> columns;
+ LEX_STRING name;
+ engine_option_value *option_list;
+ bool generated;
+ bool create_if_not_exists;
+
+ Key(enum Keytype type_par, const LEX_STRING &name_arg,
+ KEY_CREATE_INFO *key_info_arg,
+ bool generated_arg, List<Key_part_spec> &cols,
+ engine_option_value *create_opt, bool if_not_exists_opt)
+ :type(type_par), key_create_info(*key_info_arg), columns(cols),
+ name(name_arg), option_list(create_opt), generated(generated_arg),
+ create_if_not_exists(if_not_exists_opt)
+ {}
+ Key(enum Keytype type_par, const char *name_arg, size_t name_len_arg,
+ KEY_CREATE_INFO *key_info_arg, bool generated_arg,
+ List<Key_part_spec> &cols,
+ engine_option_value *create_opt, bool if_not_exists_opt)
+ :type(type_par), key_create_info(*key_info_arg), columns(cols),
+ option_list(create_opt), generated(generated_arg),
+ create_if_not_exists(if_not_exists_opt)
+ {
+ name.str= (char *)name_arg;
+ name.length= name_len_arg;
+ }
+ Key(const Key &rhs, MEM_ROOT *mem_root);
+ virtual ~Key() {}
+ /* Equality comparison of keys (ignoring name) */
+ friend bool foreign_key_prefix(Key *a, Key *b);
+ /**
+ Used to make a clone of this object for ALTER/CREATE TABLE
+ @sa comment for Key_part_spec::clone
+ */
+ virtual Key *clone(MEM_ROOT *mem_root) const
+ { return new (mem_root) Key(*this, mem_root); }
+};
+
+
+class Foreign_key: public Key {
+public:
+ enum fk_match_opt { FK_MATCH_UNDEF, FK_MATCH_FULL,
+ FK_MATCH_PARTIAL, FK_MATCH_SIMPLE};
+ enum fk_option { FK_OPTION_UNDEF, FK_OPTION_RESTRICT, FK_OPTION_CASCADE,
+ FK_OPTION_SET_NULL, FK_OPTION_NO_ACTION, FK_OPTION_DEFAULT};
+
+ LEX_STRING ref_db;
+ LEX_STRING ref_table;
+ List<Key_part_spec> ref_columns;
+ uint delete_opt, update_opt, match_opt;
+ Foreign_key(const LEX_STRING &name_arg, List<Key_part_spec> &cols,
+ const LEX_STRING &ref_db_arg, const LEX_STRING &ref_table_arg,
+ List<Key_part_spec> &ref_cols,
+ uint delete_opt_arg, uint update_opt_arg, uint match_opt_arg,
+ bool if_not_exists_opt)
+ :Key(FOREIGN_KEY, name_arg, &default_key_create_info, 0, cols, NULL,
+ if_not_exists_opt),
+ ref_db(ref_db_arg), ref_table(ref_table_arg), ref_columns(ref_cols),
+ delete_opt(delete_opt_arg), update_opt(update_opt_arg),
+ match_opt(match_opt_arg)
+ {
+ // We don't check for duplicate FKs.
+ key_create_info.check_for_duplicate_indexes= false;
+ }
+ Foreign_key(const Foreign_key &rhs, MEM_ROOT *mem_root);
+ /**
+ Used to make a clone of this object for ALTER/CREATE TABLE
+ @sa comment for Key_part_spec::clone
+ */
+ virtual Key *clone(MEM_ROOT *mem_root) const
+ { return new (mem_root) Foreign_key(*this, mem_root); }
+ /* Used to validate foreign key options */
+ bool validate(List<Create_field> &table_fields);
+};
+
+typedef struct st_mysql_lock
+{
+ TABLE **table;
+ uint table_count,lock_count;
+ THR_LOCK_DATA **locks;
+} MYSQL_LOCK;
+
+
+class LEX_COLUMN : public Sql_alloc
+{
+public:
+ String column;
+ uint rights;
+ LEX_COLUMN (const String& x,const uint& y ): column (x),rights (y) {}
+};
+
+class MY_LOCALE;
+
+/**
+ Query_cache_tls -- query cache thread local data.
+*/
+
+struct Query_cache_block;
+
+struct Query_cache_tls
+{
+ /*
+ 'first_query_block' should be accessed only via query cache
+ functions and methods to maintain proper locking.
+ */
+ Query_cache_block *first_query_block;
+ void set_first_query_block(Query_cache_block *first_query_block_arg)
+ {
+ first_query_block= first_query_block_arg;
+ }
+
+ Query_cache_tls() :first_query_block(NULL) {}
+};
+
+/* SIGNAL / RESIGNAL / GET DIAGNOSTICS */
+
+/**
+ This enumeration list all the condition item names of a condition in the
+ SQL condition area.
+*/
+typedef enum enum_diag_condition_item_name
+{
+ /*
+ Conditions that can be set by the user (SIGNAL/RESIGNAL),
+ and by the server implementation.
+ */
+
+ DIAG_CLASS_ORIGIN= 0,
+ FIRST_DIAG_SET_PROPERTY= DIAG_CLASS_ORIGIN,
+ DIAG_SUBCLASS_ORIGIN= 1,
+ DIAG_CONSTRAINT_CATALOG= 2,
+ DIAG_CONSTRAINT_SCHEMA= 3,
+ DIAG_CONSTRAINT_NAME= 4,
+ DIAG_CATALOG_NAME= 5,
+ DIAG_SCHEMA_NAME= 6,
+ DIAG_TABLE_NAME= 7,
+ DIAG_COLUMN_NAME= 8,
+ DIAG_CURSOR_NAME= 9,
+ DIAG_MESSAGE_TEXT= 10,
+ DIAG_MYSQL_ERRNO= 11,
+ LAST_DIAG_SET_PROPERTY= DIAG_MYSQL_ERRNO
+} Diag_condition_item_name;
+
+/**
+ Name of each diagnostic condition item.
+ This array is indexed by Diag_condition_item_name.
+*/
+extern const LEX_STRING Diag_condition_item_names[];
+
+/**
+ These states are bit coded with HARD. For each state there must be a pair
+ <state_even_num>, and <state_odd_num>_HARD.
+*/
+enum killed_state
+{
+ NOT_KILLED= 0,
+ KILL_HARD_BIT= 1, /* Bit for HARD KILL */
+ KILL_BAD_DATA= 2,
+ KILL_BAD_DATA_HARD= 3,
+ KILL_QUERY= 4,
+ KILL_QUERY_HARD= 5,
+ /*
+ ABORT_QUERY signals to the query processor to stop execution ASAP without
+ issuing an error. Instead a warning is issued, and when possible a partial
+ query result is returned to the client.
+ */
+ ABORT_QUERY= 6,
+ ABORT_QUERY_HARD= 7,
+ /*
+ All of the following killed states will kill the connection
+ KILL_CONNECTION must be the first of these and it must start with
+ an even number (becasue of HARD bit)!
+ */
+ KILL_CONNECTION= 8,
+ KILL_CONNECTION_HARD= 9,
+ KILL_SYSTEM_THREAD= 10,
+ KILL_SYSTEM_THREAD_HARD= 11,
+ KILL_SERVER= 12,
+ KILL_SERVER_HARD= 13
+};
+
+extern int killed_errno(killed_state killed);
+#define killed_mask_hard(killed) ((killed_state) ((killed) & ~KILL_HARD_BIT))
+
+enum killed_type
+{
+ KILL_TYPE_ID,
+ KILL_TYPE_USER,
+ KILL_TYPE_QUERY
+};
+
+#include "sql_lex.h" /* Must be here */
+
+extern LEX_STRING sql_statement_names[(uint) SQLCOM_END + 1];
+class Delayed_insert;
+class select_result;
+class Time_zone;
+
+#define THD_SENTRY_MAGIC 0xfeedd1ff
+#define THD_SENTRY_GONE 0xdeadbeef
+
+#define THD_CHECK_SENTRY(thd) DBUG_ASSERT(thd->dbug_sentry == THD_SENTRY_MAGIC)
+
+typedef ulonglong sql_mode_t;
+
+typedef struct system_variables
+{
+ /*
+ How dynamically allocated system variables are handled:
+
+ The global_system_variables and max_system_variables are "authoritative"
+ They both should have the same 'version' and 'size'.
+ When attempting to access a dynamic variable, if the session version
+ is out of date, then the session version is updated and realloced if
+ neccessary and bytes copied from global to make up for missing data.
+
+ Note that one should use my_bool instead of bool here, as the variables
+ are used with my_getopt.c
+ */
+ ulong dynamic_variables_version;
+ char* dynamic_variables_ptr;
+ uint dynamic_variables_head; /* largest valid variable offset */
+ uint dynamic_variables_size; /* how many bytes are in use */
+
+ ulonglong max_heap_table_size;
+ ulonglong tmp_table_size;
+ ulonglong long_query_time;
+ ulonglong optimizer_switch;
+ sql_mode_t sql_mode; ///< which non-standard SQL behaviour should be enabled
+ sql_mode_t old_behavior; ///< which old SQL behaviour should be enabled
+ ulonglong option_bits; ///< OPTION_xxx constants, e.g. OPTION_PROFILING
+ ulonglong join_buff_space_limit;
+ ulonglong log_slow_filter;
+ ulonglong log_slow_verbosity;
+ ulonglong bulk_insert_buff_size;
+ ulonglong join_buff_size;
+ ulonglong sortbuff_size;
+ ulonglong group_concat_max_len;
+ ulonglong default_regex_flags;
+
+ /**
+ Place holders to store Multi-source variables in sys_var.cc during
+ update and show of variables.
+ */
+ ulonglong slave_skip_counter;
+ ulonglong max_relay_log_size;
+
+ ha_rows select_limit;
+ ha_rows max_join_size;
+ ha_rows expensive_subquery_limit;
+ ulong auto_increment_increment, auto_increment_offset;
+ ulong lock_wait_timeout;
+ ulong join_cache_level;
+ ulong max_allowed_packet;
+ ulong max_error_count;
+ ulong max_length_for_sort_data;
+ ulong max_sort_length;
+ ulong max_tmp_tables;
+ ulong max_insert_delayed_threads;
+ ulong min_examined_row_limit;
+ ulong multi_range_count;
+ ulong net_buffer_length;
+ ulong net_interactive_timeout;
+ ulong net_read_timeout;
+ ulong net_retry_count;
+ ulong net_wait_timeout;
+ ulong net_write_timeout;
+ ulong optimizer_prune_level;
+ ulong optimizer_search_depth;
+ ulong optimizer_selectivity_sampling_limit;
+ ulong optimizer_use_condition_selectivity;
+ ulong use_stat_tables;
+ ulong histogram_size;
+ ulong histogram_type;
+ ulong preload_buff_size;
+ ulong profiling_history_size;
+ ulong read_buff_size;
+ ulong read_rnd_buff_size;
+ ulong mrr_buff_size;
+ ulong div_precincrement;
+ /* Total size of all buffers used by the subselect_rowid_merge_engine. */
+ ulong rowid_merge_buff_size;
+ ulong max_sp_recursion_depth;
+ ulong default_week_format;
+ ulong max_seeks_for_key;
+ ulong range_alloc_block_size;
+ ulong query_alloc_block_size;
+ ulong query_prealloc_size;
+ ulong trans_alloc_block_size;
+ ulong trans_prealloc_size;
+ ulong log_warnings;
+ /* Flags for slow log filtering */
+ ulong log_slow_rate_limit;
+ ulong binlog_format; ///< binlog format for this thd (see enum_binlog_format)
+ ulong progress_report_time;
+ my_bool binlog_annotate_row_events;
+ my_bool binlog_direct_non_trans_update;
+ my_bool sql_log_bin;
+ ulong completion_type;
+ ulong query_cache_type;
+ ulong tx_isolation;
+ ulong updatable_views_with_limit;
+ int max_user_connections;
+ ulong server_id;
+ /**
+ In slave thread we need to know in behalf of which
+ thread the query is being run to replicate temp tables properly
+ */
+ my_thread_id pseudo_thread_id;
+ /**
+ When replicating an event group with GTID, keep these values around so
+ slave binlog can receive the same GTID as the original.
+ */
+ uint32 gtid_domain_id;
+ uint64 gtid_seq_no;
+
+ /**
+ Default transaction access mode. READ ONLY (true) or READ WRITE (false).
+ */
+ my_bool tx_read_only;
+ my_bool low_priority_updates;
+ my_bool query_cache_wlock_invalidate;
+ my_bool engine_condition_pushdown;
+ my_bool keep_files_on_create;
+
+ my_bool old_mode;
+ my_bool old_alter_table;
+ my_bool old_passwords;
+ my_bool big_tables;
+ my_bool query_cache_strip_comments;
+
+ plugin_ref table_plugin;
+
+ /* Only charset part of these variables is sensible */
+ CHARSET_INFO *character_set_filesystem;
+ CHARSET_INFO *character_set_client;
+ CHARSET_INFO *character_set_results;
+
+ /* Both charset and collation parts of these variables are important */
+ CHARSET_INFO *collation_server;
+ CHARSET_INFO *collation_database;
+ CHARSET_INFO *collation_connection;
+
+ /* Names. These will be allocated in buffers in thd */
+ LEX_STRING default_master_connection;
+
+ /* Error messages */
+ MY_LOCALE *lc_messages;
+ /* Locale Support */
+ MY_LOCALE *lc_time_names;
+
+ Time_zone *time_zone;
+
+ my_bool sysdate_is_now;
+
+ /* deadlock detection */
+ ulong wt_timeout_short, wt_deadlock_search_depth_short;
+ ulong wt_timeout_long, wt_deadlock_search_depth_long;
+
+ double long_query_time_double;
+
+ my_bool pseudo_slave_mode;
+
+} SV;
+
+/**
+ Per thread status variables.
+ Must be long/ulong up to last_system_status_var so that
+ add_to_status/add_diff_to_status can work.
+*/
+
+typedef struct system_status_var
+{
+ ulong com_other;
+ ulong com_stat[(uint) SQLCOM_END];
+ ulong created_tmp_disk_tables_;
+ ulong created_tmp_tables_;
+ ulong ha_commit_count;
+ ulong ha_delete_count;
+ ulong ha_read_first_count;
+ ulong ha_read_last_count;
+ ulong ha_read_key_count;
+ ulong ha_read_next_count;
+ ulong ha_read_prev_count;
+ ulong ha_read_rnd_count;
+ ulong ha_read_rnd_next_count;
+ ulong ha_read_rnd_deleted_count;
+ /*
+ This number doesn't include calls to the default implementation and
+ calls made by range access. The intent is to count only calls made by
+ BatchedKeyAccess.
+ */
+ ulong ha_mrr_init_count;
+ ulong ha_mrr_key_refills_count;
+ ulong ha_mrr_rowid_refills_count;
+
+ ulong ha_rollback_count;
+ ulong ha_update_count;
+ ulong ha_write_count;
+ /* The following are for internal temporary tables */
+ ulong ha_tmp_update_count;
+ ulong ha_tmp_write_count;
+ ulong ha_prepare_count;
+ ulong ha_icp_attempts;
+ ulong ha_icp_match;
+ ulong ha_discover_count;
+ ulong ha_savepoint_count;
+ ulong ha_savepoint_rollback_count;
+ ulong ha_external_lock_count;
+
+ ulong net_big_packet_count;
+ ulong opened_tables;
+ ulong opened_shares;
+ ulong opened_views; /* +1 opening a view */
+
+ ulong select_full_join_count_;
+ ulong select_full_range_join_count_;
+ ulong select_range_count_;
+ ulong select_range_check_count_;
+ ulong select_scan_count_;
+ ulong executed_triggers;
+ ulong long_query_count;
+ ulong filesort_merge_passes_;
+ ulong filesort_range_count_;
+ ulong filesort_rows_;
+ ulong filesort_scan_count_;
+ ulong filesort_pq_sorts_;
+ /* Prepared statements and binary protocol */
+ ulong com_stmt_prepare;
+ ulong com_stmt_reprepare;
+ ulong com_stmt_execute;
+ ulong com_stmt_send_long_data;
+ ulong com_stmt_fetch;
+ ulong com_stmt_reset;
+ ulong com_stmt_close;
+
+ /* Features used */
+ ulong feature_dynamic_columns; /* +1 when creating a dynamic column */
+ ulong feature_fulltext; /* +1 when MATCH is used */
+ ulong feature_gis; /* +1 opening a table with GIS features */
+ ulong feature_locale; /* +1 when LOCALE is set */
+ ulong feature_subquery; /* +1 when subqueries are used */
+ ulong feature_timezone; /* +1 when XPATH is used */
+ ulong feature_trigger; /* +1 opening a table with triggers */
+ ulong feature_xml; /* +1 when XPATH is used */
+
+ ulong empty_queries;
+ ulong access_denied_errors;
+ ulong lost_connections;
+ /*
+ Number of statements sent from the client
+ */
+ ulong questions;
+ /*
+ IMPORTANT!
+ SEE last_system_status_var DEFINITION BELOW.
+ Below 'last_system_status_var' are all variables that cannot be handled
+ automatically by add_to_status()/add_diff_to_status().
+ */
+ ulonglong bytes_received;
+ ulonglong bytes_sent;
+ ulonglong rows_read;
+ ulonglong rows_sent;
+ ulonglong rows_tmp_read;
+ ulonglong binlog_bytes_written;
+ double last_query_cost;
+ double cpu_time, busy_time;
+ /* Don't initialize */
+ volatile int64 memory_used; /* This shouldn't be accumulated */
+} STATUS_VAR;
+
+/*
+ This is used for 'SHOW STATUS'. It must be updated to the last ulong
+ variable in system_status_var which is makes sense to add to the global
+ counter
+*/
+
+#define last_system_status_var questions
+#define last_cleared_system_status_var memory_used
+
+/*
+ Global status variables
+*/
+
+extern ulong feature_files_opened_with_delayed_keys;
+
+
+void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var);
+
+void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var,
+ STATUS_VAR *dec_var);
+
+/**
+ Get collation by name, send error to client on failure.
+ @param name Collation name
+ @param name_cs Character set of the name string
+ @return
+ @retval NULL on error
+ @retval Pointter to CHARSET_INFO with the given name on success
+*/
+inline CHARSET_INFO *
+mysqld_collation_get_by_name(const char *name,
+ CHARSET_INFO *name_cs= system_charset_info)
+{
+ CHARSET_INFO *cs;
+ MY_CHARSET_LOADER loader;
+ my_charset_loader_init_mysys(&loader);
+ if (!(cs= my_collation_get_by_name(&loader, name, MYF(0))))
+ {
+ ErrConvString err(name, name_cs);
+ my_error(ER_UNKNOWN_COLLATION, MYF(0), err.ptr());
+ if (loader.error[0])
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_COLLATION, "%s", loader.error);
+ }
+ return cs;
+}
+
+#ifdef MYSQL_SERVER
+
+void free_tmp_table(THD *thd, TABLE *entry);
+
+
+/* The following macro is to make init of Query_arena simpler */
+#ifndef DBUG_OFF
+#define INIT_ARENA_DBUG_INFO is_backup_arena= 0; is_reprepared= FALSE;
+#else
+#define INIT_ARENA_DBUG_INFO
+#endif
+
+class Query_arena
+{
+public:
+ /*
+ List of items created in the parser for this query. Every item puts
+ itself to the list on creation (see Item::Item() for details))
+ */
+ Item *free_list;
+ MEM_ROOT *mem_root; // Pointer to current memroot
+#ifndef DBUG_OFF
+ bool is_backup_arena; /* True if this arena is used for backup. */
+ bool is_reprepared;
+#endif
+ /*
+ The states relfects three diffrent life cycles for three
+ different types of statements:
+ Prepared statement: STMT_INITIALIZED -> STMT_PREPARED -> STMT_EXECUTED.
+ Stored procedure: STMT_INITIALIZED_FOR_SP -> STMT_EXECUTED.
+ Other statements: STMT_CONVENTIONAL_EXECUTION never changes.
+ */
+ enum enum_state
+ {
+ STMT_INITIALIZED= 0, STMT_INITIALIZED_FOR_SP= 1, STMT_PREPARED= 2,
+ STMT_CONVENTIONAL_EXECUTION= 3, STMT_EXECUTED= 4, STMT_ERROR= -1
+ };
+
+ enum_state state;
+
+ /* We build without RTTI, so dynamic_cast can't be used. */
+ enum Type
+ {
+ STATEMENT, PREPARED_STATEMENT, STORED_PROCEDURE
+ };
+
+ Query_arena(MEM_ROOT *mem_root_arg, enum enum_state state_arg) :
+ free_list(0), mem_root(mem_root_arg), state(state_arg)
+ { INIT_ARENA_DBUG_INFO; }
+ /*
+ This constructor is used only when Query_arena is created as
+ backup storage for another instance of Query_arena.
+ */
+ Query_arena() { INIT_ARENA_DBUG_INFO; }
+
+ virtual Type type() const;
+ virtual ~Query_arena() {};
+
+ inline bool is_stmt_prepare() const { return state == STMT_INITIALIZED; }
+ inline bool is_stmt_prepare_or_first_sp_execute() const
+ { return (int)state < (int)STMT_PREPARED; }
+ inline bool is_stmt_prepare_or_first_stmt_execute() const
+ { return (int)state <= (int)STMT_PREPARED; }
+ inline bool is_stmt_execute() const
+ { return state == STMT_PREPARED || state == STMT_EXECUTED; }
+ inline bool is_conventional() const
+ { return state == STMT_CONVENTIONAL_EXECUTION; }
+
+ inline void* alloc(size_t size) { return alloc_root(mem_root,size); }
+ inline void* calloc(size_t size)
+ {
+ void *ptr;
+ if ((ptr=alloc_root(mem_root,size)))
+ bzero(ptr, size);
+ return ptr;
+ }
+ inline char *strdup(const char *str)
+ { return strdup_root(mem_root,str); }
+ inline char *strmake(const char *str, size_t size)
+ { return strmake_root(mem_root,str,size); }
+ inline void *memdup(const void *str, size_t size)
+ { return memdup_root(mem_root,str,size); }
+ inline void *memdup_w_gap(const void *str, size_t size, uint gap)
+ {
+ void *ptr;
+ if ((ptr= alloc_root(mem_root,size+gap)))
+ memcpy(ptr,str,size);
+ return ptr;
+ }
+
+ void set_query_arena(Query_arena *set);
+
+ void free_items();
+ /* Close the active state associated with execution of this statement */
+ virtual void cleanup_stmt();
+};
+
+
+class Server_side_cursor;
+
+/**
+ @class Statement
+ @brief State of a single command executed against this connection.
+
+ One connection can contain a lot of simultaneously running statements,
+ some of which could be:
+ - prepared, that is, contain placeholders,
+ - opened as cursors. We maintain 1 to 1 relationship between
+ statement and cursor - if user wants to create another cursor for his
+ query, we create another statement for it.
+ To perform some action with statement we reset THD part to the state of
+ that statement, do the action, and then save back modified state from THD
+ to the statement. It will be changed in near future, and Statement will
+ be used explicitly.
+*/
+
+class Statement: public ilink, public Query_arena
+{
+ Statement(const Statement &rhs); /* not implemented: */
+ Statement &operator=(const Statement &rhs); /* non-copyable */
+public:
+ /*
+ Uniquely identifies each statement object in thread scope; change during
+ statement lifetime. FIXME: must be const
+ */
+ ulong id;
+
+ /*
+ MARK_COLUMNS_NONE: Means mark_used_colums is not set and no indicator to
+ handler of fields used is set
+ MARK_COLUMNS_READ: Means a bit in read set is set to inform handler
+ that the field is to be read. If field list contains
+ duplicates, then thd->dup_field is set to point
+ to the last found duplicate.
+ MARK_COLUMNS_WRITE: Means a bit is set in write set to inform handler
+ that it needs to update this field in write_row
+ and update_row.
+ */
+ enum enum_mark_columns mark_used_columns;
+
+ LEX_STRING name; /* name for named prepared statements */
+ LEX *lex; // parse tree descriptor
+ /*
+ Points to the query associated with this statement. It's const, but
+ we need to declare it char * because all table handlers are written
+ in C and need to point to it.
+
+ Note that if we set query = NULL, we must at the same time set
+ query_length = 0, and protect the whole operation with
+ LOCK_thd_data mutex. To avoid crashes in races, if we do not
+ know that thd->query cannot change at the moment, we should print
+ thd->query like this:
+ (1) reserve the LOCK_thd_data mutex;
+ (2) print or copy the value of query and query_length
+ (3) release LOCK_thd_data mutex.
+ This printing is needed at least in SHOW PROCESSLIST and SHOW
+ ENGINE INNODB STATUS.
+ */
+ CSET_STRING query_string;
+ /*
+ If opt_query_cache_strip_comments is set, this contains query without
+ comments. If not set, it contains pointer to query_string.
+ */
+ String base_query;
+
+
+ inline char *query() const { return query_string.str(); }
+ inline uint32 query_length() const { return query_string.length(); }
+ CHARSET_INFO *query_charset() const { return query_string.charset(); }
+ void set_query_inner(const CSET_STRING &string_arg)
+ {
+ query_string= string_arg;
+ }
+ void set_query_inner(char *query_arg, uint32 query_length_arg,
+ CHARSET_INFO *cs_arg)
+ {
+ set_query_inner(CSET_STRING(query_arg, query_length_arg, cs_arg));
+ }
+ void reset_query_inner()
+ {
+ set_query_inner(CSET_STRING());
+ }
+ /**
+ Name of the current (default) database.
+
+ If there is the current (default) database, "db" contains its name. If
+ there is no current (default) database, "db" is NULL and "db_length" is
+ 0. In other words, "db", "db_length" must either be NULL, or contain a
+ valid database name.
+
+ @note this attribute is set and alloced by the slave SQL thread (for
+ the THD of that thread); that thread is (and must remain, for now) the
+ only responsible for freeing this member.
+ */
+
+ char *db;
+ size_t db_length;
+
+ /* This is set to 1 of last call to send_result_to_client() was ok */
+ my_bool query_cache_is_applicable;
+
+ /* This constructor is called for backup statements */
+ Statement() {}
+
+ Statement(LEX *lex_arg, MEM_ROOT *mem_root_arg,
+ enum enum_state state_arg, ulong id_arg);
+ virtual ~Statement();
+
+ /* Assign execution context (note: not all members) of given stmt to self */
+ virtual void set_statement(Statement *stmt);
+ void set_n_backup_statement(Statement *stmt, Statement *backup);
+ void restore_backup_statement(Statement *stmt, Statement *backup);
+ /* return class type */
+ virtual Type type() const;
+};
+
+
+/**
+ Container for all statements created/used in a connection.
+ Statements in Statement_map have unique Statement::id (guaranteed by id
+ assignment in Statement::Statement)
+ Non-empty statement names are unique too: attempt to insert a new statement
+ with duplicate name causes older statement to be deleted
+
+ Statements are auto-deleted when they are removed from the map and when the
+ map is deleted.
+*/
+
+class Statement_map
+{
+public:
+ Statement_map();
+
+ int insert(THD *thd, Statement *statement);
+
+ Statement *find_by_name(LEX_STRING *name)
+ {
+ Statement *stmt;
+ stmt= (Statement*)my_hash_search(&names_hash, (uchar*)name->str,
+ name->length);
+ return stmt;
+ }
+
+ Statement *find(ulong id)
+ {
+ if (last_found_statement == 0 || id != last_found_statement->id)
+ {
+ Statement *stmt;
+ stmt= (Statement *) my_hash_search(&st_hash, (uchar *) &id, sizeof(id));
+ if (stmt && stmt->name.str)
+ return NULL;
+ last_found_statement= stmt;
+ }
+ return last_found_statement;
+ }
+ /*
+ Close all cursors of this connection that use tables of a storage
+ engine that has transaction-specific state and therefore can not
+ survive COMMIT or ROLLBACK. Currently all but MyISAM cursors are closed.
+ */
+ void close_transient_cursors();
+ void erase(Statement *statement);
+ /* Erase all statements (calls Statement destructor) */
+ void reset();
+ ~Statement_map();
+private:
+ HASH st_hash;
+ HASH names_hash;
+ I_List<Statement> transient_cursor_list;
+ Statement *last_found_statement;
+};
+
+struct st_savepoint {
+ struct st_savepoint *prev;
+ char *name;
+ uint length;
+ Ha_trx_info *ha_list;
+ /** State of metadata locks before this savepoint was set. */
+ MDL_savepoint mdl_savepoint;
+};
+
+enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY};
+extern const char *xa_state_names[];
+
+typedef struct st_xid_state {
+ /* For now, this is only used to catch duplicated external xids */
+ XID xid; // transaction identifier
+ enum xa_states xa_state; // used by external XA only
+ bool in_thd;
+ /* Error reported by the Resource Manager (RM) to the Transaction Manager. */
+ uint rm_error;
+} XID_STATE;
+
+extern mysql_mutex_t LOCK_xid_cache;
+extern HASH xid_cache;
+bool xid_cache_init(void);
+void xid_cache_free(void);
+XID_STATE *xid_cache_search(XID *xid);
+bool xid_cache_insert(XID *xid, enum xa_states xa_state);
+bool xid_cache_insert(XID_STATE *xid_state);
+void xid_cache_delete(XID_STATE *xid_state);
+
+/**
+ @class Security_context
+ @brief A set of THD members describing the current authenticated user.
+*/
+
+class Security_context {
+public:
+ Security_context() {} /* Remove gcc warning */
+ /*
+ host - host of the client
+ user - user of the client, set to NULL until the user has been read from
+ the connection
+ priv_user - The user privilege we are using. May be "" for anonymous user.
+ ip - client IP
+ */
+ char *host, *user, *ip;
+ char priv_user[USERNAME_LENGTH];
+ char proxy_user[USERNAME_LENGTH + MAX_HOSTNAME + 5];
+ /* The host privilege we are using */
+ char priv_host[MAX_HOSTNAME];
+ /* The role privilege we are using */
+ char priv_role[USERNAME_LENGTH];
+ /* The external user (if available) */
+ char *external_user;
+ /* points to host if host is available, otherwise points to ip */
+ const char *host_or_ip;
+ ulong master_access; /* Global privileges from mysql.user */
+ ulong db_access; /* Privileges for current db */
+
+ void init();
+ void destroy();
+ void skip_grants();
+ inline char *priv_host_name()
+ {
+ return (*priv_host ? priv_host : (char *)"%");
+ }
+
+ bool set_user(char *user_arg);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ bool
+ change_security_context(THD *thd,
+ LEX_STRING *definer_user,
+ LEX_STRING *definer_host,
+ LEX_STRING *db,
+ Security_context **backup);
+
+ void
+ restore_security_context(THD *thd, Security_context *backup);
+#endif
+ bool user_matches(Security_context *);
+};
+
+
+/**
+ A registry for item tree transformations performed during
+ query optimization. We register only those changes which require
+ a rollback to re-execute a prepared statement or stored procedure
+ yet another time.
+*/
+
+struct Item_change_record;
+typedef I_List<Item_change_record> Item_change_list;
+
+
+/**
+ Type of locked tables mode.
+ See comment for THD::locked_tables_mode for complete description.
+*/
+
+enum enum_locked_tables_mode
+{
+ LTM_NONE= 0,
+ LTM_LOCK_TABLES,
+ LTM_PRELOCKED,
+ LTM_PRELOCKED_UNDER_LOCK_TABLES
+};
+
+
+/**
+ Class that holds information about tables which were opened and locked
+ by the thread. It is also used to save/restore this information in
+ push_open_tables_state()/pop_open_tables_state().
+*/
+
+class Open_tables_state
+{
+public:
+ /**
+ As part of class THD, this member is set during execution
+ of a prepared statement. When it is set, it is used
+ by the locking subsystem to report a change in table metadata.
+
+ When Open_tables_state part of THD is reset to open
+ a system or INFORMATION_SCHEMA table, the member is cleared
+ to avoid spurious ER_NEED_REPREPARE errors -- system and
+ INFORMATION_SCHEMA tables are not subject to metadata version
+ tracking.
+ @sa check_and_update_table_version()
+ */
+ Reprepare_observer *m_reprepare_observer;
+
+ /**
+ List of regular tables in use by this thread. Contains temporary and
+ base tables that were opened with @see open_tables().
+ */
+ TABLE *open_tables;
+ /**
+ List of temporary tables used by this thread. Contains user-level
+ temporary tables, created with CREATE TEMPORARY TABLE, and
+ internal temporary tables, created, e.g., to resolve a SELECT,
+ or for an intermediate table used in ALTER.
+ XXX Why are internal temporary tables added to this list?
+ */
+ TABLE *temporary_tables;
+ TABLE *derived_tables;
+ /*
+ During a MySQL session, one can lock tables in two modes: automatic
+ or manual. In automatic mode all necessary tables are locked just before
+ statement execution, and all acquired locks are stored in 'lock'
+ member. Unlocking takes place automatically as well, when the
+ statement ends.
+ Manual mode comes into play when a user issues a 'LOCK TABLES'
+ statement. In this mode the user can only use the locked tables.
+ Trying to use any other tables will give an error.
+ The locked tables are also stored in this member, however,
+ thd->locked_tables_mode is turned on. Manual locking is described in
+ the 'LOCK_TABLES' chapter of the MySQL manual.
+ See also lock_tables() for details.
+ */
+ MYSQL_LOCK *lock;
+
+ /*
+ CREATE-SELECT keeps an extra lock for the table being
+ created. This field is used to keep the extra lock available for
+ lower level routines, which would otherwise miss that lock.
+ */
+ MYSQL_LOCK *extra_lock;
+
+ /*
+ Enum enum_locked_tables_mode and locked_tables_mode member are
+ used to indicate whether the so-called "locked tables mode" is on,
+ and what kind of mode is active.
+
+ Locked tables mode is used when it's necessary to open and
+ lock many tables at once, for usage across multiple
+ (sub-)statements.
+ This may be necessary either for queries that use stored functions
+ and triggers, in which case the statements inside functions and
+ triggers may be executed many times, or for implementation of
+ LOCK TABLES, in which case the opened tables are reused by all
+ subsequent statements until a call to UNLOCK TABLES.
+
+ The kind of locked tables mode employed for stored functions and
+ triggers is also called "prelocked mode".
+ In this mode, first open_tables() call to open the tables used
+ in a statement analyses all functions used by the statement
+ and adds all indirectly used tables to the list of tables to
+ open and lock.
+ It also marks the parse tree of the statement as requiring
+ prelocking. After that, lock_tables() locks the entire list
+ of tables and changes THD::locked_tables_modeto LTM_PRELOCKED.
+ All statements executed inside functions or triggers
+ use the prelocked tables, instead of opening their own ones.
+ Prelocked mode is turned off automatically once close_thread_tables()
+ of the main statement is called.
+ */
+ enum enum_locked_tables_mode locked_tables_mode;
+ uint current_tablenr;
+
+ enum enum_flags {
+ BACKUPS_AVAIL = (1U << 0) /* There are backups available */
+ };
+
+ /*
+ Flags with information about the open tables state.
+ */
+ uint state_flags;
+ /**
+ This constructor initializes Open_tables_state instance which can only
+ be used as backup storage. To prepare Open_tables_state instance for
+ operations which open/lock/close tables (e.g. open_table()) one has to
+ call init_open_tables_state().
+ */
+ Open_tables_state() : state_flags(0U) { }
+
+ void set_open_tables_state(Open_tables_state *state)
+ {
+ *this= *state;
+ }
+
+ void reset_open_tables_state(THD *thd)
+ {
+ open_tables= temporary_tables= derived_tables= 0;
+ extra_lock= lock= 0;
+ locked_tables_mode= LTM_NONE;
+ state_flags= 0U;
+ m_reprepare_observer= NULL;
+ }
+};
+
+
+/**
+ Storage for backup of Open_tables_state. Must
+ be used only to open system tables (TABLE_CATEGORY_SYSTEM
+ and TABLE_CATEGORY_LOG).
+*/
+
+class Open_tables_backup: public Open_tables_state
+{
+public:
+ /**
+ When we backup the open tables state to open a system
+ table or tables, we want to save state of metadata
+ locks which were acquired before the backup. It is used
+ to release metadata locks on system tables after they are
+ no longer used.
+ */
+ MDL_savepoint mdl_system_tables_svp;
+};
+
+/**
+ @class Sub_statement_state
+ @brief Used to save context when executing a function or trigger
+*/
+
+/* Defines used for Sub_statement_state::in_sub_stmt */
+
+#define SUB_STMT_TRIGGER 1
+#define SUB_STMT_FUNCTION 2
+
+
+class Sub_statement_state
+{
+public:
+ ulonglong option_bits;
+ ulonglong first_successful_insert_id_in_prev_stmt;
+ ulonglong first_successful_insert_id_in_cur_stmt, insert_id_for_cur_row;
+ Discrete_interval auto_inc_interval_for_cur_row;
+ Discrete_intervals_list auto_inc_intervals_forced;
+ ulonglong limit_found_rows;
+ ha_rows cuted_fields, sent_row_count, examined_row_count;
+ ulong client_capabilities;
+ ulong query_plan_flags;
+ uint in_sub_stmt;
+ bool enable_slow_log;
+ bool last_insert_id_used;
+ SAVEPOINT *savepoints;
+ enum enum_check_fields count_cuted_fields;
+};
+
+
+/* Flags for the THD::system_thread variable */
+enum enum_thread_type
+{
+ NON_SYSTEM_THREAD= 0,
+ SYSTEM_THREAD_DELAYED_INSERT= 1,
+ SYSTEM_THREAD_SLAVE_IO= 2,
+ SYSTEM_THREAD_SLAVE_SQL= 4,
+ SYSTEM_THREAD_NDBCLUSTER_BINLOG= 8,
+ SYSTEM_THREAD_EVENT_SCHEDULER= 16,
+ SYSTEM_THREAD_EVENT_WORKER= 32,
+ SYSTEM_THREAD_BINLOG_BACKGROUND= 64,
+ SYSTEM_THREAD_SLAVE_INIT= 128,
+};
+
+inline char const *
+show_system_thread(enum_thread_type thread)
+{
+#define RETURN_NAME_AS_STRING(NAME) case (NAME): return #NAME
+ switch (thread) {
+ static char buf[64];
+ RETURN_NAME_AS_STRING(NON_SYSTEM_THREAD);
+ RETURN_NAME_AS_STRING(SYSTEM_THREAD_DELAYED_INSERT);
+ RETURN_NAME_AS_STRING(SYSTEM_THREAD_SLAVE_IO);
+ RETURN_NAME_AS_STRING(SYSTEM_THREAD_SLAVE_SQL);
+ RETURN_NAME_AS_STRING(SYSTEM_THREAD_NDBCLUSTER_BINLOG);
+ RETURN_NAME_AS_STRING(SYSTEM_THREAD_EVENT_SCHEDULER);
+ RETURN_NAME_AS_STRING(SYSTEM_THREAD_EVENT_WORKER);
+ default:
+ sprintf(buf, "<UNKNOWN SYSTEM THREAD: %d>", thread);
+ return buf;
+ }
+#undef RETURN_NAME_AS_STRING
+}
+
+/**
+ This class represents the interface for internal error handlers.
+ Internal error handlers are exception handlers used by the server
+ implementation.
+*/
+class Internal_error_handler
+{
+protected:
+ Internal_error_handler() :
+ m_prev_internal_handler(NULL)
+ {}
+
+ virtual ~Internal_error_handler() {}
+
+public:
+ /**
+ Handle a sql condition.
+ This method can be implemented by a subclass to achieve any of the
+ following:
+ - mask a warning/error internally, prevent exposing it to the user,
+ - mask a warning/error and throw another one instead.
+ When this method returns true, the sql condition is considered
+ 'handled', and will not be propagated to upper layers.
+ It is the responsability of the code installing an internal handler
+ to then check for trapped conditions, and implement logic to recover
+ from the anticipated conditions trapped during runtime.
+
+ This mechanism is similar to C++ try/throw/catch:
+ - 'try' correspond to <code>THD::push_internal_handler()</code>,
+ - 'throw' correspond to <code>my_error()</code>,
+ which invokes <code>my_message_sql()</code>,
+ - 'catch' correspond to checking how/if an internal handler was invoked,
+ before removing it from the exception stack with
+ <code>THD::pop_internal_handler()</code>.
+
+ @param thd the calling thread
+ @param cond the condition raised.
+ @return true if the condition is handled
+ */
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl) = 0;
+
+private:
+ Internal_error_handler *m_prev_internal_handler;
+ friend class THD;
+};
+
+
+/**
+ Implements the trivial error handler which cancels all error states
+ and prevents an SQLSTATE to be set.
+*/
+
+class Dummy_error_handler : public Internal_error_handler
+{
+public:
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+ {
+ /* Ignore error */
+ return TRUE;
+ }
+ Dummy_error_handler() {} /* Remove gcc warning */
+};
+
+
+/**
+ This class is an internal error handler implementation for
+ DROP TABLE statements. The thing is that there may be warnings during
+ execution of these statements, which should not be exposed to the user.
+ This class is intended to silence such warnings.
+*/
+
+class Drop_table_error_handler : public Internal_error_handler
+{
+public:
+ Drop_table_error_handler() {}
+
+public:
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+
+private:
+};
+
+
+/**
+ Tables that were locked with LOCK TABLES statement.
+
+ Encapsulates a list of TABLE_LIST instances for tables
+ locked by LOCK TABLES statement, memory root for metadata locks,
+ and, generally, the context of LOCK TABLES statement.
+
+ In LOCK TABLES mode, the locked tables are kept open between
+ statements.
+ Therefore, we can't allocate metadata locks on execution memory
+ root -- as well as tables, the locks need to stay around till
+ UNLOCK TABLES is called.
+ The locks are allocated in the memory root encapsulated in this
+ class.
+
+ Some SQL commands, like FLUSH TABLE or ALTER TABLE, demand that
+ the tables they operate on are closed, at least temporarily.
+ This class encapsulates a list of TABLE_LIST instances, one
+ for each base table from LOCK TABLES list,
+ which helps conveniently close the TABLEs when it's necessary
+ and later reopen them.
+
+ Implemented in sql_base.cc
+*/
+
+class Locked_tables_list
+{
+private:
+ MEM_ROOT m_locked_tables_root;
+ TABLE_LIST *m_locked_tables;
+ TABLE_LIST **m_locked_tables_last;
+ /** An auxiliary array used only in reopen_tables(). */
+ TABLE **m_reopen_array;
+ /**
+ Count the number of tables in m_locked_tables list. We can't
+ rely on thd->lock->table_count because it excludes
+ non-transactional temporary tables. We need to know
+ an exact number of TABLE objects.
+ */
+ size_t m_locked_tables_count;
+public:
+ Locked_tables_list()
+ :m_locked_tables(NULL),
+ m_locked_tables_last(&m_locked_tables),
+ m_reopen_array(NULL),
+ m_locked_tables_count(0)
+ {
+ init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ }
+ void unlock_locked_tables(THD *thd);
+ void unlock_locked_table(THD *thd, MDL_ticket *mdl_ticket);
+ ~Locked_tables_list()
+ {
+ reset();
+ }
+ void reset();
+ bool init_locked_tables(THD *thd);
+ TABLE_LIST *locked_tables() { return m_locked_tables; }
+ void unlink_from_list(THD *thd, TABLE_LIST *table_list,
+ bool remove_from_locked_tables);
+ void unlink_all_closed_tables(THD *thd,
+ MYSQL_LOCK *lock,
+ size_t reopen_count);
+ bool reopen_tables(THD *thd);
+ bool restore_lock(THD *thd, TABLE_LIST *dst_table_list, TABLE *table,
+ MYSQL_LOCK *lock);
+ void add_back_last_deleted_lock(TABLE_LIST *dst_table_list);
+};
+
+
+/**
+ Storage engine specific thread local data.
+*/
+
+struct Ha_data
+{
+ /**
+ Storage engine specific thread local data.
+ Lifetime: one user connection.
+ */
+ void *ha_ptr;
+ /**
+ 0: Life time: one statement within a transaction. If @@autocommit is
+ on, also represents the entire transaction.
+ @sa trans_register_ha()
+
+ 1: Life time: one transaction within a connection.
+ If the storage engine does not participate in a transaction,
+ this should not be used.
+ @sa trans_register_ha()
+ */
+ Ha_trx_info ha_info[2];
+ /**
+ NULL: engine is not bound to this thread
+ non-NULL: engine is bound to this thread, engine shutdown forbidden
+ */
+ plugin_ref lock;
+ Ha_data() :ha_ptr(NULL) {}
+};
+
+/**
+ An instance of the global read lock in a connection.
+ Implemented in lock.cc.
+*/
+
+class Global_read_lock
+{
+public:
+ enum enum_grl_state
+ {
+ GRL_NONE,
+ GRL_ACQUIRED,
+ GRL_ACQUIRED_AND_BLOCKS_COMMIT
+ };
+
+ Global_read_lock()
+ : m_state(GRL_NONE),
+ m_mdl_global_shared_lock(NULL),
+ m_mdl_blocks_commits_lock(NULL)
+ {}
+
+ bool lock_global_read_lock(THD *thd);
+ void unlock_global_read_lock(THD *thd);
+ /**
+ Check if this connection can acquire protection against GRL and
+ emit error if otherwise.
+ */
+ bool can_acquire_protection() const
+ {
+ if (m_state)
+ {
+ my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+ return TRUE;
+ }
+ return FALSE;
+ }
+ bool make_global_read_lock_block_commit(THD *thd);
+ bool is_acquired() const { return m_state != GRL_NONE; }
+ void set_explicit_lock_duration(THD *thd);
+private:
+ enum_grl_state m_state;
+ /**
+ In order to acquire the global read lock, the connection must
+ acquire shared metadata lock in GLOBAL namespace, to prohibit
+ all DDL.
+ */
+ MDL_ticket *m_mdl_global_shared_lock;
+ /**
+ Also in order to acquire the global read lock, the connection
+ must acquire a shared metadata lock in COMMIT namespace, to
+ prohibit commits.
+ */
+ MDL_ticket *m_mdl_blocks_commits_lock;
+};
+
+
+/*
+ Class to facilitate the commit of one transactions waiting for the commit of
+ another transaction to complete first.
+
+ This is used during (parallel) replication, to allow different transactions
+ to be applied in parallel, but still commit in order.
+
+ The transaction that wants to wait for a prior commit must first register
+ to wait with register_wait_for_prior_commit(waitee). Such registration
+ must be done holding the waitee->LOCK_wait_commit, to prevent the other
+ THD from disappearing during the registration.
+
+ Then during commit, if a THD is registered to wait, it will call
+ wait_for_prior_commit() as part of ha_commit_trans(). If no wait is
+ registered, or if the waitee for has already completed commit, then
+ wait_for_prior_commit() returns immediately.
+
+ And when a THD that may be waited for has completed commit (more precisely
+ commit_ordered()), then it must call wakeup_subsequent_commits() to wake
+ up any waiters. Note that this must be done at a point that is guaranteed
+ to be later than any waiters registering themselves. It is safe to call
+ wakeup_subsequent_commits() multiple times, as waiters are removed from
+ registration as part of the wakeup.
+
+ The reason for separate register and wait calls is that this allows to
+ register the wait early, at a point where the waited-for THD is known to
+ exist. And then the actual wait can be done much later, where the
+ waited-for THD may have been long gone. By registering early, the waitee
+ can signal before disappearing.
+*/
+struct wait_for_commit
+{
+ /*
+ The LOCK_wait_commit protects the fields subsequent_commits_list and
+ wakeup_subsequent_commits_running (for a waitee), and the pointer
+ waiterr and associated COND_wait_commit (for a waiter).
+ */
+ mysql_mutex_t LOCK_wait_commit;
+ mysql_cond_t COND_wait_commit;
+ /* List of threads that did register_wait_for_prior_commit() on us. */
+ wait_for_commit *subsequent_commits_list;
+ /* Link field for entries in subsequent_commits_list. */
+ wait_for_commit *next_subsequent_commit;
+ /*
+ Our waitee, if we did register_wait_for_prior_commit(), and were not
+ yet woken up. Else NULL.
+
+ When this is cleared for wakeup, the COND_wait_commit condition is
+ signalled.
+ */
+ wait_for_commit *waitee;
+ /*
+ Generic pointer for use by the transaction coordinator to optimise the
+ waiting for improved group commit.
+
+ Currently used by binlog TC to signal that a waiter is ready to commit, so
+ that the waitee can grab it and group commit it directly. It is free to be
+ used by another transaction coordinator for similar purposes.
+ */
+ void *opaque_pointer;
+ /* The wakeup error code from the waitee. 0 means no error. */
+ int wakeup_error;
+ /*
+ Flag set when wakeup_subsequent_commits_running() is active, see comments
+ on that function for details.
+ */
+ bool wakeup_subsequent_commits_running;
+ /*
+ This flag can be set when a commit starts, but has not completed yet.
+ It is used by binlog group commit to allow a waiting transaction T2 to
+ join the group commit of an earlier transaction T1. When T1 has queued
+ itself for group commit, it will set the commit_started flag. Then when
+ T2 becomes ready to commit and needs to wait for T1 to commit first, T2
+ can queue itself before waiting, and thereby participate in the same
+ group commit as T1.
+ */
+ bool commit_started;
+
+ void register_wait_for_prior_commit(wait_for_commit *waitee);
+ int wait_for_prior_commit(THD *thd)
+ {
+ /*
+ Quick inline check, to avoid function call and locking in the common case
+ where no wakeup is registered, or a registered wait was already signalled.
+ */
+ if (waitee)
+ return wait_for_prior_commit2(thd);
+ else
+ {
+ if (wakeup_error)
+ my_error(ER_PRIOR_COMMIT_FAILED, MYF(0));
+ return wakeup_error;
+ }
+ }
+ void wakeup_subsequent_commits(int wakeup_error)
+ {
+ /*
+ Do the check inline, so only the wakeup case takes the cost of a function
+ call for every commmit.
+
+ Note that the check is done without locking. It is the responsibility of
+ the user of the wakeup facility to ensure that no waiters can register
+ themselves after the last call to wakeup_subsequent_commits().
+
+ This avoids having to take another lock for every commit, which would be
+ pointless anyway - even if we check under lock, there is nothing to
+ prevent a waiter from arriving just after releasing the lock.
+ */
+ if (subsequent_commits_list)
+ wakeup_subsequent_commits2(wakeup_error);
+ }
+ void unregister_wait_for_prior_commit()
+ {
+ if (waitee)
+ unregister_wait_for_prior_commit2();
+ else
+ wakeup_error= 0;
+ }
+ /*
+ Remove a waiter from the list in the waitee. Used to unregister a wait.
+ The caller must be holding the locks of both waiter and waitee.
+ */
+ void remove_from_list(wait_for_commit **next_ptr_ptr)
+ {
+ wait_for_commit *cur;
+
+ while ((cur= *next_ptr_ptr) != NULL)
+ {
+ if (cur == this)
+ {
+ *next_ptr_ptr= this->next_subsequent_commit;
+ break;
+ }
+ next_ptr_ptr= &cur->next_subsequent_commit;
+ }
+ waitee= NULL;
+ }
+
+ void wakeup(int wakeup_error);
+
+ int wait_for_prior_commit2(THD *thd);
+ void wakeup_subsequent_commits2(int wakeup_error);
+ void unregister_wait_for_prior_commit2();
+
+ wait_for_commit();
+ ~wait_for_commit();
+ void reinit();
+};
+
+
+extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);
+
+class THD;
+#ifndef DBUG_OFF
+void dbug_serve_apcs(THD *thd, int n_calls);
+#endif
+
+/**
+ @class THD
+ For each client connection we create a separate thread with THD serving as
+ a thread/connection descriptor
+*/
+
+class THD :public Statement,
+ public MDL_context_owner,
+ public Open_tables_state
+{
+private:
+ inline bool is_stmt_prepare() const
+ { DBUG_ASSERT(0); return Statement::is_stmt_prepare(); }
+
+ inline bool is_stmt_prepare_or_first_sp_execute() const
+ { DBUG_ASSERT(0); return Statement::is_stmt_prepare_or_first_sp_execute(); }
+
+ inline bool is_stmt_prepare_or_first_stmt_execute() const
+ { DBUG_ASSERT(0); return Statement::is_stmt_prepare_or_first_stmt_execute(); }
+
+ inline bool is_conventional() const
+ { DBUG_ASSERT(0); return Statement::is_conventional(); }
+
+public:
+ MDL_context mdl_context;
+
+ /* Used to execute base64 coded binlog events in MySQL server */
+ Relay_log_info* rli_fake;
+ rpl_group_info* rgi_fake;
+ /* Slave applier execution context */
+ rpl_group_info* rgi_slave;
+
+ union {
+ rpl_io_thread_info *rpl_io_info;
+ rpl_sql_thread_info *rpl_sql_info;
+ } system_thread_info;
+
+ void reset_for_next_command();
+ /*
+ Constant for THD::where initialization in the beginning of every query.
+
+ It's needed because we do not save/restore THD::where normally during
+ primary (non subselect) query execution.
+ */
+ static const char * const DEFAULT_WHERE;
+
+#ifdef EMBEDDED_LIBRARY
+ struct st_mysql *mysql;
+ unsigned long client_stmt_id;
+ unsigned long client_param_count;
+ struct st_mysql_bind *client_params;
+ char *extra_data;
+ ulong extra_length;
+ struct st_mysql_data *cur_data;
+ struct st_mysql_data *first_data;
+ struct st_mysql_data **data_tail;
+ void clear_data_list();
+ struct st_mysql_data *alloc_new_dataset();
+ /*
+ In embedded server it points to the statement that is processed
+ in the current query. We store some results directly in statement
+ fields then.
+ */
+ struct st_mysql_stmt *current_stmt;
+#endif
+#ifdef HAVE_QUERY_CACHE
+ Query_cache_tls query_cache_tls;
+#endif
+ NET net; // client connection descriptor
+ /** Aditional network instrumentation for the server only. */
+ NET_SERVER m_net_server_extension;
+ scheduler_functions *scheduler; // Scheduler for this connection
+ Protocol *protocol; // Current protocol
+ Protocol_text protocol_text; // Normal protocol
+ Protocol_binary protocol_binary; // Binary protocol
+ HASH user_vars; // hash for user variables
+ String packet; // dynamic buffer for network I/O
+ String convert_buffer; // buffer for charset conversions
+ struct my_rnd_struct rand; // used for authentication
+ struct system_variables variables; // Changeable local variables
+ struct system_status_var status_var; // Per thread statistic vars
+ struct system_status_var org_status_var; // For user statistics
+ struct system_status_var *initial_status_var; /* used by show status */
+ THR_LOCK_INFO lock_info; // Locking info of this thread
+ /**
+ Protects THD data accessed from other threads:
+ - thd->query and thd->query_length (used by SHOW ENGINE
+ INNODB STATUS and SHOW PROCESSLIST
+ - thd->db and thd->db_length (used in SHOW PROCESSLIST)
+ - thd->mysys_var (used by KILL statement and shutdown).
+ Is locked when THD is deleted.
+ */
+ mysql_mutex_t LOCK_thd_data;
+
+ /* all prepared statements and cursors of this connection */
+ Statement_map stmt_map;
+ /*
+ A pointer to the stack frame of handle_one_connection(),
+ which is called first in the thread for handling a client
+ */
+ char *thread_stack;
+
+ /**
+ Currently selected catalog.
+ */
+ char *catalog;
+
+ /**
+ @note
+ Some members of THD (currently 'Statement::db',
+ 'catalog' and 'query') are set and alloced by the slave SQL thread
+ (for the THD of that thread); that thread is (and must remain, for now)
+ the only responsible for freeing these 3 members. If you add members
+ here, and you add code to set them in replication, don't forget to
+ free_them_and_set_them_to_0 in replication properly. For details see
+ the 'err:' label of the handle_slave_sql() in sql/slave.cc.
+
+ @see handle_slave_sql
+ */
+
+ Security_context main_security_ctx;
+ Security_context *security_ctx;
+
+ /*
+ Points to info-string that we show in SHOW PROCESSLIST
+ You are supposed to update thd->proc_info only if you have coded
+ a time-consuming piece that MySQL can get stuck in for a long time.
+
+ Set it using the thd_proc_info(THD *thread, const char *message)
+ macro/function.
+
+ This member is accessed and assigned without any synchronization.
+ Therefore, it may point only to constant (statically
+ allocated) strings, which memory won't go away over time.
+ */
+ const char *proc_info;
+
+private:
+ unsigned int m_current_stage_key;
+
+public:
+ void enter_stage(const PSI_stage_info *stage,
+ PSI_stage_info *old_stage,
+ const char *calling_func,
+ const char *calling_file,
+ const unsigned int calling_line);
+
+ const char *get_proc_info() const
+ { return proc_info; }
+
+ /*
+ Used in error messages to tell user in what part of MySQL we found an
+ error. E. g. when where= "having clause", if fix_fields() fails, user
+ will know that the error was in having clause.
+ */
+ const char *where;
+
+ ulong client_capabilities; /* What the client supports */
+ ulong max_client_packet_length;
+
+ HASH handler_tables_hash;
+ /*
+ A thread can hold named user-level locks. This variable
+ contains granted tickets if a lock is present. See item_func.cc and
+ chapter 'Miscellaneous functions', for functions GET_LOCK, RELEASE_LOCK.
+ */
+ HASH ull_hash;
+#ifndef DBUG_OFF
+ uint dbug_sentry; // watch out for memory corruption
+#endif
+ struct st_my_thread_var *mysys_var;
+private:
+ /*
+ Type of current query: COM_STMT_PREPARE, COM_QUERY, etc. Set from
+ first byte of the packet in do_command()
+ */
+ enum enum_server_command m_command;
+
+public:
+ uint32 file_id; // for LOAD DATA INFILE
+ /* remote (peer) port */
+ uint16 peer_port;
+ my_time_t start_time; // start_time and its sec_part
+ ulong start_time_sec_part; // are almost always used separately
+ my_hrtime_t user_time;
+ // track down slow pthread_create
+ ulonglong prior_thr_create_utime, thr_create_utime;
+ ulonglong start_utime, utime_after_lock, utime_after_query;
+
+ // Process indicator
+ struct {
+ /*
+ true, if the currently running command can send progress report
+ packets to a client. Set by mysql_execute_command() for safe commands
+ See CF_REPORT_PROGRESS
+ */
+ bool report_to_client;
+ /*
+ true, if we will send progress report packets to a client
+ (client has requested them, see CLIENT_PROGRESS; report_to_client
+ is true; not in sub-statement)
+ */
+ bool report;
+ uint stage, max_stage;
+ ulonglong counter, max_counter;
+ ulonglong next_report_time;
+ Query_arena *arena;
+ } progress;
+
+ thr_lock_type update_lock_default;
+ Delayed_insert *di;
+
+ /* <> 0 if we are inside of trigger or stored function. */
+ uint in_sub_stmt;
+ /* True when opt_userstat_running is set at start of query */
+ bool userstat_running;
+ /*
+ True if we have to log all errors. Are set by some engines to temporary
+ force errors to the error log.
+ */
+ bool log_all_errors;
+
+ /* Do not set socket timeouts for wait_timeout (used with threadpool) */
+ bool skip_wait_timeout;
+
+ /* container for handler's private per-connection data */
+ Ha_data ha_data[MAX_HA];
+
+ bool prepare_derived_at_open;
+
+ /*
+ To signal that the tmp table to be created is created for materialized
+ derived table or a view.
+ */
+ bool create_tmp_table_for_derived;
+
+ bool save_prep_leaf_list;
+
+#ifndef MYSQL_CLIENT
+ binlog_cache_mngr * binlog_setup_trx_data();
+
+ /*
+ Public interface to write RBR events to the binlog
+ */
+ void binlog_start_trans_and_stmt();
+ void binlog_set_stmt_begin();
+ int binlog_write_table_map(TABLE *table, bool is_transactional,
+ my_bool *with_annotate= 0);
+ int binlog_write_row(TABLE* table, bool is_transactional,
+ MY_BITMAP const* cols, size_t colcnt,
+ const uchar *buf);
+ int binlog_delete_row(TABLE* table, bool is_transactional,
+ MY_BITMAP const* cols, size_t colcnt,
+ const uchar *buf);
+ int binlog_update_row(TABLE* table, bool is_transactional,
+ MY_BITMAP const* cols, size_t colcnt,
+ const uchar *old_data, const uchar *new_data);
+
+ void set_server_id(uint32 sid) { variables.server_id = sid; }
+
+ /*
+ Member functions to handle pending event for row-level logging.
+ */
+ template <class RowsEventT> Rows_log_event*
+ binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id,
+ MY_BITMAP const* cols,
+ size_t colcnt,
+ size_t needed,
+ bool is_transactional,
+ RowsEventT* hint);
+ Rows_log_event* binlog_get_pending_rows_event(bool is_transactional) const;
+ void binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional);
+ inline int binlog_flush_pending_rows_event(bool stmt_end)
+ {
+ return (binlog_flush_pending_rows_event(stmt_end, FALSE) ||
+ binlog_flush_pending_rows_event(stmt_end, TRUE));
+ }
+ int binlog_flush_pending_rows_event(bool stmt_end, bool is_transactional);
+ int binlog_remove_pending_rows_event(bool clear_maps, bool is_transactional);
+
+ /**
+ Determine the binlog format of the current statement.
+
+ @retval 0 if the current statement will be logged in statement
+ format.
+ @retval nonzero if the current statement will be logged in row
+ format.
+ */
+ int is_current_stmt_binlog_format_row() const {
+ DBUG_ASSERT(current_stmt_binlog_format == BINLOG_FORMAT_STMT ||
+ current_stmt_binlog_format == BINLOG_FORMAT_ROW);
+ return current_stmt_binlog_format == BINLOG_FORMAT_ROW;
+ }
+
+ enum binlog_filter_state
+ {
+ BINLOG_FILTER_UNKNOWN,
+ BINLOG_FILTER_CLEAR,
+ BINLOG_FILTER_SET
+ };
+
+ inline void reset_binlog_local_stmt_filter()
+ {
+ m_binlog_filter_state= BINLOG_FILTER_UNKNOWN;
+ }
+
+ inline void clear_binlog_local_stmt_filter()
+ {
+ DBUG_ASSERT(m_binlog_filter_state == BINLOG_FILTER_UNKNOWN);
+ m_binlog_filter_state= BINLOG_FILTER_CLEAR;
+ }
+
+ inline void set_binlog_local_stmt_filter()
+ {
+ DBUG_ASSERT(m_binlog_filter_state == BINLOG_FILTER_UNKNOWN);
+ m_binlog_filter_state= BINLOG_FILTER_SET;
+ }
+
+ inline binlog_filter_state get_binlog_local_stmt_filter()
+ {
+ return m_binlog_filter_state;
+ }
+
+private:
+ /**
+ Indicate if the current statement should be discarded
+ instead of written to the binlog.
+ This is used to discard special statements, such as
+ DML or DDL that affects only 'local' (non replicated)
+ tables, such as performance_schema.*
+ */
+ binlog_filter_state m_binlog_filter_state;
+
+ /**
+ Indicates the format in which the current statement will be
+ logged. This can only be set from @c decide_logging_format().
+ */
+ enum_binlog_format current_stmt_binlog_format;
+
+ /**
+ Bit field for the state of binlog warnings.
+
+ The first Lex::BINLOG_STMT_UNSAFE_COUNT bits list all types of
+ unsafeness that the current statement has.
+
+ This must be a member of THD and not of LEX, because warnings are
+ detected and issued in different places (@c
+ decide_logging_format() and @c binlog_query(), respectively).
+ Between these calls, the THD->lex object may change; e.g., if a
+ stored routine is invoked. Only THD persists between the calls.
+ */
+ uint32 binlog_unsafe_warning_flags;
+
+ /*
+ Number of outstanding table maps, i.e., table maps in the
+ transaction cache.
+ */
+ uint binlog_table_maps;
+public:
+ void issue_unsafe_warnings();
+
+ uint get_binlog_table_maps() const {
+ return binlog_table_maps;
+ }
+ void clear_binlog_table_maps() {
+ binlog_table_maps= 0;
+ }
+#endif /* MYSQL_CLIENT */
+
+public:
+
+ struct st_transactions {
+ SAVEPOINT *savepoints;
+ THD_TRANS all; // Trans since BEGIN WORK
+ THD_TRANS stmt; // Trans for current statement
+ bool on; // see ha_enable_transaction()
+ XID_STATE xid_state;
+ WT_THD wt; ///< for deadlock detection
+ Rows_log_event *m_pending_rows_event;
+
+ /*
+ Tables changed in transaction (that must be invalidated in query cache).
+ List contain only transactional tables, that not invalidated in query
+ cache (instead of full list of changed in transaction tables).
+ */
+ CHANGED_TABLE_LIST* changed_tables;
+ MEM_ROOT mem_root; // Transaction-life memory allocation pool
+ void cleanup()
+ {
+ DBUG_ENTER("thd::cleanup");
+ changed_tables= 0;
+ savepoints= 0;
+ /*
+ If rm_error is raised, it means that this piece of a distributed
+ transaction has failed and must be rolled back. But the user must
+ rollback it explicitly, so don't start a new distributed XA until
+ then.
+ */
+ if (!xid_state.rm_error)
+ xid_state.xid.null();
+ free_root(&mem_root,MYF(MY_KEEP_PREALLOC));
+ DBUG_VOID_RETURN;
+ }
+ my_bool is_active()
+ {
+ return (all.ha_list != NULL);
+ }
+ st_transactions()
+ {
+ bzero((char*)this, sizeof(*this));
+ xid_state.xid.null();
+ init_sql_alloc(&mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0,
+ MYF(MY_THREAD_SPECIFIC));
+ }
+ } transaction;
+ Global_read_lock global_read_lock;
+ Field *dup_field;
+#ifndef __WIN__
+ sigset_t signals;
+#endif
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ Vio* active_vio;
+#endif
+ /*
+ This is to track items changed during execution of a prepared
+ statement/stored procedure. It's created by
+ nocheck_register_item_tree_change() in memory root of THD, and freed in
+ rollback_item_tree_changes(). For conventional execution it's always
+ empty.
+ */
+ Item_change_list change_list;
+
+ /*
+ A permanent memory area of the statement. For conventional
+ execution, the parsed tree and execution runtime reside in the same
+ memory root. In this case stmt_arena points to THD. In case of
+ a prepared statement or a stored procedure statement, thd->mem_root
+ conventionally points to runtime memory, and thd->stmt_arena
+ points to the memory of the PS/SP, where the parsed tree of the
+ statement resides. Whenever you need to perform a permanent
+ transformation of a parsed tree, you should allocate new memory in
+ stmt_arena, to allow correct re-execution of PS/SP.
+ Note: in the parser, stmt_arena == thd, even for PS/SP.
+ */
+ Query_arena *stmt_arena;
+
+ /*
+ map for tables that will be updated for a multi-table update query
+ statement, for other query statements, this will be zero.
+ */
+ table_map table_map_for_update;
+
+ /* Tells if LAST_INSERT_ID(#) was called for the current statement */
+ bool arg_of_last_insert_id_function;
+ /*
+ ALL OVER THIS FILE, "insert_id" means "*automatically generated* value for
+ insertion into an auto_increment column".
+ */
+ /*
+ This is the first autogenerated insert id which was *successfully*
+ inserted by the previous statement (exactly, if the previous statement
+ didn't successfully insert an autogenerated insert id, then it's the one
+ of the statement before, etc).
+ It can also be set by SET LAST_INSERT_ID=# or SELECT LAST_INSERT_ID(#).
+ It is returned by LAST_INSERT_ID().
+ */
+ ulonglong first_successful_insert_id_in_prev_stmt;
+ /*
+ Variant of the above, used for storing in statement-based binlog. The
+ difference is that the one above can change as the execution of a stored
+ function progresses, while the one below is set once and then does not
+ change (which is the value which statement-based binlog needs).
+ */
+ ulonglong first_successful_insert_id_in_prev_stmt_for_binlog;
+ /*
+ This is the first autogenerated insert id which was *successfully*
+ inserted by the current statement. It is maintained only to set
+ first_successful_insert_id_in_prev_stmt when statement ends.
+ */
+ ulonglong first_successful_insert_id_in_cur_stmt;
+ /*
+ We follow this logic:
+ - when stmt starts, first_successful_insert_id_in_prev_stmt contains the
+ first insert id successfully inserted by the previous stmt.
+ - as stmt makes progress, handler::insert_id_for_cur_row changes;
+ every time get_auto_increment() is called,
+ auto_inc_intervals_in_cur_stmt_for_binlog is augmented with the
+ reserved interval (if statement-based binlogging).
+ - at first successful insertion of an autogenerated value,
+ first_successful_insert_id_in_cur_stmt is set to
+ handler::insert_id_for_cur_row.
+ - when stmt goes to binlog,
+ auto_inc_intervals_in_cur_stmt_for_binlog is binlogged if
+ non-empty.
+ - when stmt ends, first_successful_insert_id_in_prev_stmt is set to
+ first_successful_insert_id_in_cur_stmt.
+ */
+ /*
+ stmt_depends_on_first_successful_insert_id_in_prev_stmt is set when
+ LAST_INSERT_ID() is used by a statement.
+ If it is set, first_successful_insert_id_in_prev_stmt_for_binlog will be
+ stored in the statement-based binlog.
+ This variable is CUMULATIVE along the execution of a stored function or
+ trigger: if one substatement sets it to 1 it will stay 1 until the
+ function/trigger ends, thus making sure that
+ first_successful_insert_id_in_prev_stmt_for_binlog does not change anymore
+ and is propagated to the caller for binlogging.
+ */
+ bool stmt_depends_on_first_successful_insert_id_in_prev_stmt;
+ /*
+ List of auto_increment intervals reserved by the thread so far, for
+ storage in the statement-based binlog.
+ Note that its minimum is not first_successful_insert_id_in_cur_stmt:
+ assuming a table with an autoinc column, and this happens:
+ INSERT INTO ... VALUES(3);
+ SET INSERT_ID=3; INSERT IGNORE ... VALUES (NULL);
+ then the latter INSERT will insert no rows
+ (first_successful_insert_id_in_cur_stmt == 0), but storing "INSERT_ID=3"
+ in the binlog is still needed; the list's minimum will contain 3.
+ This variable is cumulative: if several statements are written to binlog
+ as one (stored functions or triggers are used) this list is the
+ concatenation of all intervals reserved by all statements.
+ */
+ Discrete_intervals_list auto_inc_intervals_in_cur_stmt_for_binlog;
+ /* Used by replication and SET INSERT_ID */
+ Discrete_intervals_list auto_inc_intervals_forced;
+ /*
+ There is BUG#19630 where statement-based replication of stored
+ functions/triggers with two auto_increment columns breaks.
+ We however ensure that it works when there is 0 or 1 auto_increment
+ column; our rules are
+ a) on master, while executing a top statement involving substatements,
+ first top- or sub- statement to generate auto_increment values wins the
+ exclusive right to see its values be written to binlog (the write
+ will be done by the statement or its caller), and the losers won't see
+ their values be written to binlog.
+ b) on slave, while replicating a top statement involving substatements,
+ first top- or sub- statement to need to read auto_increment values from
+ the master's binlog wins the exclusive right to read them (so the losers
+ won't read their values from binlog but instead generate on their own).
+ a) implies that we mustn't backup/restore
+ auto_inc_intervals_in_cur_stmt_for_binlog.
+ b) implies that we mustn't backup/restore auto_inc_intervals_forced.
+
+ If there are more than 1 auto_increment columns, then intervals for
+ different columns may mix into the
+ auto_inc_intervals_in_cur_stmt_for_binlog list, which is logically wrong,
+ but there is no point in preventing this mixing by preventing intervals
+ from the secondly inserted column to come into the list, as such
+ prevention would be wrong too.
+ What will happen in the case of
+ INSERT INTO t1 (auto_inc) VALUES(NULL);
+ where t1 has a trigger which inserts into an auto_inc column of t2, is
+ that in binlog we'll store the interval of t1 and the interval of t2 (when
+ we store intervals, soon), then in slave, t1 will use both intervals, t2
+ will use none; if t1 inserts the same number of rows as on master,
+ normally the 2nd interval will not be used by t1, which is fine. t2's
+ values will be wrong if t2's internal auto_increment counter is different
+ from what it was on master (which is likely). In 5.1, in mixed binlogging
+ mode, row-based binlogging is used for such cases where two
+ auto_increment columns are inserted.
+ */
+ inline void record_first_successful_insert_id_in_cur_stmt(ulonglong id_arg)
+ {
+ if (first_successful_insert_id_in_cur_stmt == 0)
+ first_successful_insert_id_in_cur_stmt= id_arg;
+ }
+ inline ulonglong read_first_successful_insert_id_in_prev_stmt(void)
+ {
+ if (!stmt_depends_on_first_successful_insert_id_in_prev_stmt)
+ {
+ /* It's the first time we read it */
+ first_successful_insert_id_in_prev_stmt_for_binlog=
+ first_successful_insert_id_in_prev_stmt;
+ stmt_depends_on_first_successful_insert_id_in_prev_stmt= 1;
+ }
+ return first_successful_insert_id_in_prev_stmt;
+ }
+ /*
+ Used by Intvar_log_event::do_apply_event() and by "SET INSERT_ID=#"
+ (mysqlbinlog). We'll soon add a variant which can take many intervals in
+ argument.
+ */
+ inline void force_one_auto_inc_interval(ulonglong next_id)
+ {
+ auto_inc_intervals_forced.empty(); // in case of multiple SET INSERT_ID
+ auto_inc_intervals_forced.append(next_id, ULONGLONG_MAX, 0);
+ }
+
+ ulonglong limit_found_rows;
+
+private:
+ /**
+ Stores the result of ROW_COUNT() function.
+
+ ROW_COUNT() function is a MySQL extention, but we try to keep it
+ similar to ROW_COUNT member of the GET DIAGNOSTICS stack of the SQL
+ standard (see SQL99, part 2, search for ROW_COUNT). It's value is
+ implementation defined for anything except INSERT, DELETE, UPDATE.
+
+ ROW_COUNT is assigned according to the following rules:
+
+ - In my_ok():
+ - for DML statements: to the number of affected rows;
+ - for DDL statements: to 0.
+
+ - In my_eof(): to -1 to indicate that there was a result set.
+
+ We derive this semantics from the JDBC specification, where int
+ java.sql.Statement.getUpdateCount() is defined to (sic) "return the
+ current result as an update count; if the result is a ResultSet
+ object or there are no more results, -1 is returned".
+
+ - In my_error(): to -1 to be compatible with the MySQL C API and
+ MySQL ODBC driver.
+
+ - For SIGNAL statements: to 0 per WL#2110 specification (see also
+ sql_signal.cc comment). Zero is used since that's the "default"
+ value of ROW_COUNT in the diagnostics area.
+ */
+
+ longlong m_row_count_func; /* For the ROW_COUNT() function */
+
+public:
+ inline longlong get_row_count_func() const
+ {
+ return m_row_count_func;
+ }
+
+ inline void set_row_count_func(longlong row_count_func)
+ {
+ m_row_count_func= row_count_func;
+ }
+
+ ha_rows cuted_fields;
+
+private:
+ /*
+ number of rows we actually sent to the client, including "synthetic"
+ rows in ROLLUP etc.
+ */
+ ha_rows m_sent_row_count;
+
+ /**
+ Number of rows read and/or evaluated for a statement. Used for
+ slow log reporting.
+
+ An examined row is defined as a row that is read and/or evaluated
+ according to a statement condition, including in
+ create_sort_index(). Rows may be counted more than once, e.g., a
+ statement including ORDER BY could possibly evaluate the row in
+ filesort() before reading it for e.g. update.
+ */
+ ha_rows m_examined_row_count;
+
+public:
+ ha_rows get_sent_row_count() const
+ { return m_sent_row_count; }
+
+ ha_rows get_examined_row_count() const
+ { return m_examined_row_count; }
+
+ void set_sent_row_count(ha_rows count);
+ void set_examined_row_count(ha_rows count);
+
+ void inc_sent_row_count(ha_rows count);
+ void inc_examined_row_count(ha_rows count);
+
+ void inc_status_created_tmp_disk_tables();
+ void inc_status_created_tmp_files();
+ void inc_status_created_tmp_tables();
+ void inc_status_select_full_join();
+ void inc_status_select_full_range_join();
+ void inc_status_select_range();
+ void inc_status_select_range_check();
+ void inc_status_select_scan();
+ void inc_status_sort_merge_passes();
+ void inc_status_sort_range();
+ void inc_status_sort_rows(ha_rows count);
+ void inc_status_sort_scan();
+ void set_status_no_index_used();
+ void set_status_no_good_index_used();
+
+ /**
+ The number of rows and/or keys examined by the query, both read,
+ changed or written.
+ */
+ ulonglong accessed_rows_and_keys;
+
+ /**
+ Check if the number of rows accessed by a statement exceeded
+ LIMIT ROWS EXAMINED. If so, signal the query engine to stop execution.
+ */
+ void check_limit_rows_examined()
+ {
+ if (++accessed_rows_and_keys > lex->limit_rows_examined_cnt)
+ killed= ABORT_QUERY;
+ }
+
+ USER_CONN *user_connect;
+ CHARSET_INFO *db_charset;
+#if defined(ENABLED_PROFILING)
+ PROFILING profiling;
+#endif
+
+ /** Current statement digest. */
+ sql_digest_state *m_digest;
+ /** Current statement digest token array. */
+ unsigned char *m_token_array;
+ /** Top level statement digest. */
+ sql_digest_state m_digest_state;
+
+ /** Current statement instrumentation. */
+ PSI_statement_locker *m_statement_psi;
+#ifdef HAVE_PSI_STATEMENT_INTERFACE
+ /** Current statement instrumentation state. */
+ PSI_statement_locker_state m_statement_state;
+#endif /* HAVE_PSI_STATEMENT_INTERFACE */
+ /** Idle instrumentation. */
+ PSI_idle_locker *m_idle_psi;
+#ifdef HAVE_PSI_IDLE_INTERFACE
+ /** Idle instrumentation state. */
+ PSI_idle_locker_state m_idle_state;
+#endif /* HAVE_PSI_IDLE_INTERFACE */
+
+ /*
+ Id of current query. Statement can be reused to execute several queries
+ query_id is global in context of the whole MySQL server.
+ ID is automatically generated from mutex-protected counter.
+ It's used in handler code for various purposes: to check which columns
+ from table are necessary for this select, to check if it's necessary to
+ update auto-updatable fields (like auto_increment and timestamp).
+ */
+ query_id_t query_id;
+ ulong col_access;
+
+ /* Statement id is thread-wide. This counter is used to generate ids */
+ ulong statement_id_counter;
+ ulong rand_saved_seed1, rand_saved_seed2;
+ ulong query_plan_flags;
+ ulong query_plan_fsort_passes;
+ pthread_t real_id; /* For debugging */
+ my_thread_id thread_id;
+ uint tmp_table, global_disable_checkpoint;
+ uint server_status,open_options;
+ enum enum_thread_type system_thread;
+ uint select_number; //number of select (used for EXPLAIN)
+ /*
+ Current or next transaction isolation level.
+ When a connection is established, the value is taken from
+ @@session.tx_isolation (default transaction isolation for
+ the session), which is in turn taken from @@global.tx_isolation
+ (the global value).
+ If there is no transaction started, this variable
+ holds the value of the next transaction's isolation level.
+ When a transaction starts, the value stored in this variable
+ becomes "actual".
+ At transaction commit or rollback, we assign this variable
+ again from @@session.tx_isolation.
+ The only statement that can otherwise change the value
+ of this variable is SET TRANSACTION ISOLATION LEVEL.
+ Its purpose is to effect the isolation level of the next
+ transaction in this session. When this statement is executed,
+ the value in this variable is changed. However, since
+ this statement is only allowed when there is no active
+ transaction, this assignment (naturally) only affects the
+ upcoming transaction.
+ At the end of the current active transaction the value is
+ be reset again from @@session.tx_isolation, as described
+ above.
+ */
+ enum_tx_isolation tx_isolation;
+ /*
+ Current or next transaction access mode.
+ See comment above regarding tx_isolation.
+ */
+ bool tx_read_only;
+ enum_check_fields count_cuted_fields;
+
+ DYNAMIC_ARRAY user_var_events; /* For user variables replication */
+ MEM_ROOT *user_var_events_alloc; /* Allocate above array elements here */
+
+ /*
+ Define durability properties that engines may check to
+ improve performance. Not yet used in MariaDB
+ */
+ enum durability_properties durability_property;
+
+ /*
+ If checking this in conjunction with a wait condition, please
+ include a check after enter_cond() if you want to avoid a race
+ condition. For details see the implementation of awake(),
+ especially the "broadcast" part.
+ */
+ killed_state volatile killed;
+
+ /* See also thd_killed() */
+ inline bool check_killed()
+ {
+ if (killed)
+ return TRUE;
+ if (apc_target.have_apc_requests())
+ apc_target.process_apc_requests();
+ return FALSE;
+ }
+
+ /* scramble - random string sent to client on handshake */
+ char scramble[SCRAMBLE_LENGTH+1];
+
+ /*
+ If this is a slave, the name of the connection stored here.
+ This is used for taging error messages in the log files.
+ */
+ LEX_STRING connection_name;
+ char default_master_connection_buff[MAX_CONNECTION_NAME+1];
+ uint8 password; /* 0, 1 or 2 */
+ uint8 failed_com_change_user;
+ bool slave_thread;
+ bool extra_port; /* If extra connection */
+
+ bool no_errors;
+
+ /**
+ Set to TRUE if execution of the current compound statement
+ can not continue. In particular, disables activation of
+ CONTINUE or EXIT handlers of stored routines.
+ Reset in the end of processing of the current user request, in
+ @see mysql_reset_thd_for_next_command().
+ */
+ bool is_fatal_error;
+ /**
+ Set by a storage engine to request the entire
+ transaction (that possibly spans multiple engines) to
+ rollback. Reset in ha_rollback.
+ */
+ bool transaction_rollback_request;
+ /**
+ TRUE if we are in a sub-statement and the current error can
+ not be safely recovered until we left the sub-statement mode.
+ In particular, disables activation of CONTINUE and EXIT
+ handlers inside sub-statements. E.g. if it is a deadlock
+ error and requires a transaction-wide rollback, this flag is
+ raised (traditionally, MySQL first has to close all the reads
+ via @see handler::ha_index_or_rnd_end() and only then perform
+ the rollback).
+ Reset to FALSE when we leave the sub-statement mode.
+ */
+ bool is_fatal_sub_stmt_error;
+ bool query_start_used, rand_used, time_zone_used;
+ bool query_start_sec_part_used;
+ /* for IS NULL => = last_insert_id() fix in remove_eq_conds() */
+ bool substitute_null_with_insert_id;
+ bool in_lock_tables;
+ bool bootstrap, cleanup_done;
+
+ /** is set if some thread specific value(s) used in a statement. */
+ bool thread_specific_used;
+ /**
+ is set if a statement accesses a temporary table created through
+ CREATE TEMPORARY TABLE.
+ */
+ bool charset_is_system_charset, charset_is_collation_connection;
+ bool charset_is_character_set_filesystem;
+ bool enable_slow_log; /* enable slow log for current statement */
+ bool abort_on_warning;
+ bool got_warning; /* Set on call to push_warning() */
+ /* set during loop of derived table processing */
+ bool derived_tables_processing;
+ bool tablespace_op; /* This is TRUE in DISCARD/IMPORT TABLESPACE */
+ /* True if we have to log the current statement */
+ bool log_current_statement;
+ /**
+ True if a slave error. Causes the slave to stop. Not the same
+ as the statement execution error (is_error()), since
+ a statement may be expected to return an error, e.g. because
+ it returned an error on master, and this is OK on the slave.
+ */
+ bool is_slave_error;
+ /*
+ True when a transaction is queued up for binlog group commit.
+ Used so that if another transaction needs to wait for a row lock held by
+ this transaction, it can signal to trigger the group commit immediately,
+ skipping the normal --binlog-commit-wait-count wait.
+ */
+ bool waiting_on_group_commit;
+ /*
+ Set true when another transaction goes to wait on a row lock held by this
+ transaction. Used together with waiting_on_group_commit.
+ */
+ bool has_waiter;
+ /*
+ In case of a slave, set to the error code the master got when executing
+ the query. 0 if no error on the master.
+ */
+ int slave_expected_error;
+
+ sp_rcontext *spcont; // SP runtime context
+ sp_cache *sp_proc_cache;
+ sp_cache *sp_func_cache;
+
+ /** number of name_const() substitutions, see sp_head.cc:subst_spvars() */
+ uint query_name_consts;
+
+ /*
+ If we do a purge of binary logs, log index info of the threads
+ that are currently reading it needs to be adjusted. To do that
+ each thread that is using LOG_INFO needs to adjust the pointer to it
+ */
+ LOG_INFO* current_linfo;
+ NET* slave_net; // network connection from slave -> m.
+
+ /*
+ Used to update global user stats. The global user stats are updated
+ occasionally with the 'diff' variables. After the update, the 'diff'
+ variables are reset to 0.
+ */
+ /* Time when the current thread connected to MySQL. */
+ time_t current_connect_time;
+ /* Last time when THD stats were updated in global_user_stats. */
+ time_t last_global_update_time;
+ /* Number of commands not reflected in global_user_stats yet. */
+ uint select_commands, update_commands, other_commands;
+ ulonglong start_cpu_time;
+ ulonglong start_bytes_received;
+
+ /* Used by the sys_var class to store temporary values */
+ union
+ {
+ my_bool my_bool_value;
+ long long_value;
+ ulong ulong_value;
+ ulonglong ulonglong_value;
+ double double_value;
+ } sys_var_tmp;
+
+ struct {
+ /*
+ If true, mysql_bin_log::write(Log_event) call will not write events to
+ binlog, and maintain 2 below variables instead (use
+ mysql_bin_log.start_union_events to turn this on)
+ */
+ bool do_union;
+ /*
+ If TRUE, at least one mysql_bin_log::write(Log_event) call has been
+ made after last mysql_bin_log.start_union_events() call.
+ */
+ bool unioned_events;
+ /*
+ If TRUE, at least one mysql_bin_log::write(Log_event e), where
+ e.cache_stmt == TRUE call has been made after last
+ mysql_bin_log.start_union_events() call.
+ */
+ bool unioned_events_trans;
+
+ /*
+ 'queries' (actually SP statements) that run under inside this binlog
+ union have thd->query_id >= first_query_id.
+ */
+ query_id_t first_query_id;
+ } binlog_evt_union;
+
+ /**
+ Internal parser state.
+ Note that since the parser is not re-entrant, we keep only one parser
+ state here. This member is valid only when executing code during parsing.
+ */
+ Parser_state *m_parser_state;
+
+ Locked_tables_list locked_tables_list;
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *work_part_info;
+#endif
+
+#ifndef EMBEDDED_LIBRARY
+ /**
+ Array of active audit plugins which have been used by this THD.
+ This list is later iterated to invoke release_thd() on those
+ plugins.
+ */
+ DYNAMIC_ARRAY audit_class_plugins;
+ /**
+ Array of bits indicating which audit classes have already been
+ added to the list of audit plugins which are currently in use.
+ */
+ unsigned long audit_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
+#endif
+
+#if defined(ENABLED_DEBUG_SYNC)
+ /* Debug Sync facility. See debug_sync.cc. */
+ struct st_debug_sync_control *debug_sync_control;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+ THD();
+ ~THD();
+
+ void init(void);
+ /*
+ Initialize memory roots necessary for query processing and (!)
+ pre-allocate memory for it. We can't do that in THD constructor because
+ there are use cases (acl_init, delayed inserts, watcher threads,
+ killing mysqld) where it's vital to not allocate excessive and not used
+ memory. Note, that we still don't return error from init_for_queries():
+ if preallocation fails, we should notice that at the first call to
+ alloc_root.
+ */
+ void init_for_queries();
+ void update_all_stats();
+ void update_stats(void);
+ void change_user(void);
+ void cleanup(void);
+ void cleanup_after_query();
+ bool store_globals();
+ void reset_globals();
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ inline void set_active_vio(Vio* vio)
+ {
+ mysql_mutex_lock(&LOCK_thd_data);
+ active_vio = vio;
+ vio_set_thread_id(vio, pthread_self());
+ mysql_mutex_unlock(&LOCK_thd_data);
+ }
+ inline void clear_active_vio()
+ {
+ mysql_mutex_lock(&LOCK_thd_data);
+ active_vio = 0;
+ mysql_mutex_unlock(&LOCK_thd_data);
+ }
+ void close_active_vio();
+#endif
+ void awake(killed_state state_to_set);
+
+ /** Disconnect the associated communication endpoint. */
+ void disconnect();
+
+
+ /*
+ Allows this thread to serve as a target for others to schedule Async
+ Procedure Calls on.
+
+ It's possible to schedule any code to be executed this way, by
+ inheriting from the Apc_call object. Currently, only
+ Show_explain_request uses this.
+ */
+ Apc_target apc_target;
+
+#ifndef MYSQL_CLIENT
+ enum enum_binlog_query_type {
+ /* The query can be logged in row format or in statement format. */
+ ROW_QUERY_TYPE,
+
+ /* The query has to be logged in statement format. */
+ STMT_QUERY_TYPE,
+
+ QUERY_TYPE_COUNT
+ };
+
+ int binlog_query(enum_binlog_query_type qtype,
+ char const *query, ulong query_len, bool is_trans,
+ bool direct, bool suppress_use,
+ int errcode);
+#endif
+
+ inline void
+ enter_cond(mysql_cond_t *cond, mysql_mutex_t* mutex,
+ const PSI_stage_info *stage, PSI_stage_info *old_stage,
+ const char *src_function, const char *src_file,
+ int src_line)
+ {
+ mysql_mutex_assert_owner(mutex);
+ mysys_var->current_mutex = mutex;
+ mysys_var->current_cond = cond;
+ enter_stage(stage, old_stage, src_function, src_file, src_line);
+ }
+ inline void exit_cond(const PSI_stage_info *stage,
+ const char *src_function, const char *src_file,
+ int src_line)
+ {
+ /*
+ Putting the mutex unlock in thd->exit_cond() ensures that
+ mysys_var->current_mutex is always unlocked _before_ mysys_var->mutex is
+ locked (if that would not be the case, you'll get a deadlock if someone
+ does a THD::awake() on you).
+ */
+ mysql_mutex_unlock(mysys_var->current_mutex);
+ mysql_mutex_lock(&mysys_var->mutex);
+ mysys_var->current_mutex = 0;
+ mysys_var->current_cond = 0;
+ enter_stage(stage, NULL, src_function, src_file, src_line);
+ mysql_mutex_unlock(&mysys_var->mutex);
+ return;
+ }
+ virtual int is_killed() { return killed; }
+ virtual THD* get_thd() { return this; }
+
+ /**
+ A callback to the server internals that is used to address
+ special cases of the locking protocol.
+ Invoked when acquiring an exclusive lock, for each thread that
+ has a conflicting shared metadata lock.
+
+ This function:
+ - aborts waiting of the thread on a data lock, to make it notice
+ the pending exclusive lock and back off.
+ - if the thread is an INSERT DELAYED thread, sends it a KILL
+ signal to terminate it.
+
+ @note This function does not wait for the thread to give away its
+ locks. Waiting is done outside for all threads at once.
+
+ @param ctx_in_use The MDL context owner (thread) to wake up.
+ @param needs_thr_lock_abort Indicates that to wake up thread
+ this call needs to abort its waiting
+ on table-level lock.
+
+ @retval TRUE if the thread was woken up
+ @retval FALSE otherwise.
+ */
+ virtual bool notify_shared_lock(MDL_context_owner *ctx_in_use,
+ bool needs_thr_lock_abort);
+
+ // End implementation of MDL_context_owner interface.
+
+ inline bool use_cond_push(handler *file)
+ {
+ return (variables.optimizer_switch & OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN)
+ || (file->ha_table_flags() & HA_MUST_USE_TABLE_CONDITION_PUSHDOWN);
+ }
+ inline bool is_strict_mode() const
+ {
+ return (bool) (variables.sql_mode & (MODE_STRICT_TRANS_TABLES |
+ MODE_STRICT_ALL_TABLES));
+ }
+ inline my_time_t query_start() { query_start_used=1; return start_time; }
+ inline ulong query_start_sec_part()
+ { query_start_sec_part_used=1; return start_time_sec_part; }
+ inline void set_current_time()
+ {
+ my_hrtime_t hrtime= my_hrtime();
+ start_time= hrtime_to_my_time(hrtime);
+ start_time_sec_part= hrtime_sec_part(hrtime);
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ PSI_THREAD_CALL(set_thread_start_time)(start_time);
+#endif
+ }
+ inline void set_start_time()
+ {
+ if (user_time.val)
+ {
+ start_time= hrtime_to_my_time(user_time);
+ start_time_sec_part= hrtime_sec_part(user_time);
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ PSI_THREAD_CALL(set_thread_start_time)(start_time);
+#endif
+ }
+ else
+ set_current_time();
+ }
+ inline void set_time()
+ {
+ set_start_time();
+ start_utime= utime_after_lock= microsecond_interval_timer();
+ }
+ inline void set_time(my_hrtime_t t)
+ {
+ user_time= t;
+ set_time();
+ }
+ inline void set_time(my_time_t t, ulong sec_part)
+ {
+ my_hrtime_t hrtime= { hrtime_from_time(t) + sec_part };
+ set_time(hrtime);
+ }
+ void set_time_after_lock()
+ {
+ utime_after_lock= microsecond_interval_timer();
+ MYSQL_SET_STATEMENT_LOCK_TIME(m_statement_psi,
+ (utime_after_lock - start_utime));
+ }
+ ulonglong current_utime() { return microsecond_interval_timer(); }
+
+ /**
+ Update server status after execution of a top level statement.
+ Currently only checks if a query was slow, and assigns
+ the status accordingly.
+ Evaluate the current time, and if it exceeds the long-query-time
+ setting, mark the query as slow.
+ */
+ void update_server_status()
+ {
+ utime_after_query= current_utime();
+ if (utime_after_query > utime_after_lock + variables.long_query_time)
+ server_status|= SERVER_QUERY_WAS_SLOW;
+ }
+ inline ulonglong found_rows(void)
+ {
+ return limit_found_rows;
+ }
+ /**
+ Returns TRUE if session is in a multi-statement transaction mode.
+
+ OPTION_NOT_AUTOCOMMIT: When autocommit is off, a multi-statement
+ transaction is implicitly started on the first statement after a
+ previous transaction has been ended.
+
+ OPTION_BEGIN: Regardless of the autocommit status, a multi-statement
+ transaction can be explicitly started with the statements "START
+ TRANSACTION", "BEGIN [WORK]", "[COMMIT | ROLLBACK] AND CHAIN", etc.
+
+ Note: this doesn't tell you whether a transaction is active.
+ A session can be in multi-statement transaction mode, and yet
+ have no active transaction, e.g., in case of:
+ set @@autocommit=0;
+ set @a= 3; <-- these statements don't
+ set transaction isolation level serializable; <-- start an active
+ flush tables; <-- transaction
+
+ I.e. for the above scenario this function returns TRUE, even
+ though no active transaction has begun.
+ @sa in_active_multi_stmt_transaction()
+ */
+ inline bool in_multi_stmt_transaction_mode()
+ {
+ return variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN);
+ }
+ /**
+ TRUE if the session is in a multi-statement transaction mode
+ (@sa in_multi_stmt_transaction_mode()) *and* there is an
+ active transaction, i.e. there is an explicit start of a
+ transaction with BEGIN statement, or implicit with a
+ statement that uses a transactional engine.
+
+ For example, these scenarios don't start an active transaction
+ (even though the server is in multi-statement transaction mode):
+
+ set @@autocommit=0;
+ select * from nontrans_table;
+ set @var=TRUE;
+ flush tables;
+
+ Note, that even for a statement that starts a multi-statement
+ transaction (i.e. select * from trans_table), this
+ flag won't be set until we open the statement's tables
+ and the engines register themselves for the transaction
+ (see trans_register_ha()),
+ hence this method is reliable to use only after
+ open_tables() has completed.
+
+ Why do we need a flag?
+ ----------------------
+ We need to maintain a (at first glance redundant)
+ session flag, rather than looking at thd->transaction.all.ha_list
+ because of explicit start of a transaction with BEGIN.
+
+ I.e. in case of
+ BEGIN;
+ select * from nontrans_t1; <-- in_active_multi_stmt_transaction() is true
+ */
+ inline bool in_active_multi_stmt_transaction()
+ {
+ return server_status & SERVER_STATUS_IN_TRANS;
+ }
+ inline bool fill_derived_tables()
+ {
+ return !stmt_arena->is_stmt_prepare() && !lex->only_view_structure();
+ }
+ inline bool fill_information_schema_tables()
+ {
+ return !stmt_arena->is_stmt_prepare();
+ }
+ inline void* trans_alloc(unsigned int size)
+ {
+ return alloc_root(&transaction.mem_root,size);
+ }
+
+ LEX_STRING *make_lex_string(LEX_STRING *lex_str, const char* str, uint length)
+ {
+ if (!(lex_str->str= strmake_root(mem_root, str, length)))
+ return 0;
+ lex_str->length= length;
+ return lex_str;
+ }
+
+ LEX_STRING *make_lex_string(const char* str, uint length)
+ {
+ LEX_STRING *lex_str;
+ if (!(lex_str= (LEX_STRING *)alloc_root(mem_root, sizeof(LEX_STRING))))
+ return 0;
+ return make_lex_string(lex_str, str, length);
+ }
+
+ bool convert_string(LEX_STRING *to, CHARSET_INFO *to_cs,
+ const char *from, uint from_length,
+ CHARSET_INFO *from_cs);
+
+ bool convert_string(String *s, CHARSET_INFO *from_cs, CHARSET_INFO *to_cs);
+
+ void add_changed_table(TABLE *table);
+ void add_changed_table(const char *key, long key_length);
+ CHANGED_TABLE_LIST * changed_table_dup(const char *key, long key_length);
+ int send_explain_fields(select_result *result);
+ void make_explain_field_list(List<Item> &field_list);
+ /**
+ Clear the current error, if any.
+ We do not clear is_fatal_error or is_fatal_sub_stmt_error since we
+ assume this is never called if the fatal error is set.
+
+ @todo: To silence an error, one should use Internal_error_handler
+ mechanism. Issuing an error that can be possibly later "cleared" is not
+ compatible with other installed error handlers and audit plugins.
+ In future this function will be removed.
+ */
+ inline void clear_error()
+ {
+ DBUG_ENTER("clear_error");
+ if (get_stmt_da()->is_error())
+ get_stmt_da()->reset_diagnostics_area();
+ is_slave_error= 0;
+ if (killed == KILL_BAD_DATA)
+ killed= NOT_KILLED; // KILL_BAD_DATA can be reset w/o a mutex
+ DBUG_VOID_RETURN;
+ }
+#ifndef EMBEDDED_LIBRARY
+ inline bool vio_ok() const { return net.vio != 0; }
+ /** Return FALSE if connection to client is broken. */
+ bool is_connected()
+ {
+ /*
+ All system threads (e.g., the slave IO thread) are connected but
+ not using vio. So this function always returns true for all
+ system threads.
+ */
+ return system_thread || (vio_ok() ? vio_is_connected(net.vio) : FALSE);
+ }
+#else
+ inline bool vio_ok() const { return TRUE; }
+ inline bool is_connected() { return TRUE; }
+#endif
+ /**
+ Mark the current error as fatal. Warning: this does not
+ set any error, it sets a property of the error, so must be
+ followed or prefixed with my_error().
+ */
+ inline void fatal_error()
+ {
+ DBUG_ASSERT(get_stmt_da()->is_error() || killed);
+ is_fatal_error= 1;
+ DBUG_PRINT("error",("Fatal error set"));
+ }
+ /**
+ TRUE if there is an error in the error stack.
+
+ Please use this method instead of direct access to
+ net.report_error.
+
+ If TRUE, the current (sub)-statement should be aborted.
+ The main difference between this member and is_fatal_error
+ is that a fatal error can not be handled by a stored
+ procedure continue handler, whereas a normal error can.
+
+ To raise this flag, use my_error().
+ */
+ inline bool is_error() const { return m_stmt_da->is_error(); }
+
+ /// Returns Diagnostics-area for the current statement.
+ Diagnostics_area *get_stmt_da()
+ { return m_stmt_da; }
+
+ /// Returns Diagnostics-area for the current statement.
+ const Diagnostics_area *get_stmt_da() const
+ { return m_stmt_da; }
+
+ /// Sets Diagnostics-area for the current statement.
+ void set_stmt_da(Diagnostics_area *da)
+ { m_stmt_da= da; }
+
+ inline CHARSET_INFO *charset() { return variables.character_set_client; }
+ void update_charset();
+
+ inline Query_arena *activate_stmt_arena_if_needed(Query_arena *backup)
+ {
+ /*
+ Use the persistent arena if we are in a prepared statement or a stored
+ procedure statement and we have not already changed to use this arena.
+ */
+ if (!stmt_arena->is_conventional() && mem_root != stmt_arena->mem_root)
+ {
+ set_n_backup_active_arena(stmt_arena, backup);
+ return stmt_arena;
+ }
+ return 0;
+ }
+
+ void change_item_tree(Item **place, Item *new_value)
+ {
+ /* TODO: check for OOM condition here */
+ if (!stmt_arena->is_conventional())
+ nocheck_register_item_tree_change(place, *place, mem_root);
+ *place= new_value;
+ }
+ /**
+ Make change in item tree after checking whether it needs registering
+
+
+ @param place place where we should assign new value
+ @param new_value place of the new value
+
+ @details
+ see check_and_register_item_tree_change details
+ */
+ void check_and_register_item_tree(Item **place, Item **new_value)
+ {
+ if (!stmt_arena->is_conventional())
+ check_and_register_item_tree_change(place, new_value, mem_root);
+ /*
+ We have to use memcpy instead of *place= *new_value merge to
+ avoid problems with strict aliasing.
+ */
+ memcpy((char*) place, new_value, sizeof(*new_value));
+ }
+ void nocheck_register_item_tree_change(Item **place, Item *old_value,
+ MEM_ROOT *runtime_memroot);
+ void check_and_register_item_tree_change(Item **place, Item **new_value,
+ MEM_ROOT *runtime_memroot);
+ void rollback_item_tree_changes();
+
+ /*
+ Cleanup statement parse state (parse tree, lex) and execution
+ state after execution of a non-prepared SQL statement.
+ */
+ void end_statement();
+ inline int killed_errno() const
+ {
+ return ::killed_errno(killed);
+ }
+ inline void reset_killed()
+ {
+ /*
+ Resetting killed has to be done under a mutex to ensure
+ its not done during an awake() call.
+ */
+ if (killed != NOT_KILLED)
+ {
+ mysql_mutex_lock(&LOCK_thd_data);
+ killed= NOT_KILLED;
+ mysql_mutex_unlock(&LOCK_thd_data);
+ }
+ }
+ inline void reset_kill_query()
+ {
+ if (killed < KILL_CONNECTION)
+ {
+ reset_killed();
+ mysys_var->abort= 0;
+ }
+ }
+ inline void send_kill_message() const
+ {
+ int err= killed_errno();
+ if (err)
+ my_message(err, ER(err), MYF(0));
+ }
+ /* return TRUE if we will abort query if we make a warning now */
+ inline bool really_abort_on_warning()
+ {
+ return (abort_on_warning &&
+ (!transaction.stmt.modified_non_trans_table ||
+ (variables.sql_mode & MODE_STRICT_ALL_TABLES)));
+ }
+ void set_status_var_init();
+ void reset_n_backup_open_tables_state(Open_tables_backup *backup);
+ void restore_backup_open_tables_state(Open_tables_backup *backup);
+ void reset_sub_statement_state(Sub_statement_state *backup, uint new_state);
+ void restore_sub_statement_state(Sub_statement_state *backup);
+ void set_n_backup_active_arena(Query_arena *set, Query_arena *backup);
+ void restore_active_arena(Query_arena *set, Query_arena *backup);
+
+ inline void get_binlog_format(enum_binlog_format *format,
+ enum_binlog_format *current_format)
+ {
+ *format= (enum_binlog_format) variables.binlog_format;
+ *current_format= current_stmt_binlog_format;
+ }
+ inline void set_binlog_format(enum_binlog_format format,
+ enum_binlog_format current_format)
+ {
+ DBUG_ENTER("set_binlog_format");
+ variables.binlog_format= format;
+ current_stmt_binlog_format= current_format;
+ DBUG_VOID_RETURN;
+ }
+ inline void set_binlog_format_stmt()
+ {
+ DBUG_ENTER("set_binlog_format_stmt");
+ variables.binlog_format= BINLOG_FORMAT_STMT;
+ current_stmt_binlog_format= BINLOG_FORMAT_STMT;
+ DBUG_VOID_RETURN;
+ }
+ /*
+ @todo Make these methods private or remove them completely. Only
+ decide_logging_format should call them. /Sven
+ */
+ inline void set_current_stmt_binlog_format_row_if_mixed()
+ {
+ DBUG_ENTER("set_current_stmt_binlog_format_row_if_mixed");
+ /*
+ This should only be called from decide_logging_format.
+
+ @todo Once we have ensured this, uncomment the following
+ statement, remove the big comment below that, and remove the
+ in_sub_stmt==0 condition from the following 'if'.
+ */
+ /* DBUG_ASSERT(in_sub_stmt == 0); */
+ /*
+ If in a stored/function trigger, the caller should already have done the
+ change. We test in_sub_stmt to prevent introducing bugs where people
+ wouldn't ensure that, and would switch to row-based mode in the middle
+ of executing a stored function/trigger (which is too late, see also
+ reset_current_stmt_binlog_format_row()); this condition will make their
+ tests fail and so force them to propagate the
+ lex->binlog_row_based_if_mixed upwards to the caller.
+ */
+ if ((variables.binlog_format == BINLOG_FORMAT_MIXED) &&
+ (in_sub_stmt == 0))
+ set_current_stmt_binlog_format_row();
+
+ DBUG_VOID_RETURN;
+ }
+
+ inline void set_current_stmt_binlog_format_row()
+ {
+ DBUG_ENTER("set_current_stmt_binlog_format_row");
+ current_stmt_binlog_format= BINLOG_FORMAT_ROW;
+ DBUG_VOID_RETURN;
+ }
+ /* Set binlog format temporarily to statement. Returns old format */
+ inline enum_binlog_format set_current_stmt_binlog_format_stmt()
+ {
+ enum_binlog_format orig_format= current_stmt_binlog_format;
+ DBUG_ENTER("set_current_stmt_binlog_format_stmt");
+ current_stmt_binlog_format= BINLOG_FORMAT_STMT;
+ DBUG_RETURN(orig_format);
+ }
+ inline void restore_stmt_binlog_format(enum_binlog_format format)
+ {
+ DBUG_ENTER("restore_stmt_binlog_format");
+ DBUG_ASSERT(!is_current_stmt_binlog_format_row());
+ current_stmt_binlog_format= format;
+ DBUG_VOID_RETURN;
+ }
+ inline void reset_current_stmt_binlog_format_row()
+ {
+ DBUG_ENTER("reset_current_stmt_binlog_format_row");
+ /*
+ If there are temporary tables, don't reset back to
+ statement-based. Indeed it could be that:
+ CREATE TEMPORARY TABLE t SELECT UUID(); # row-based
+ # and row-based does not store updates to temp tables
+ # in the binlog.
+ INSERT INTO u SELECT * FROM t; # stmt-based
+ and then the INSERT will fail as data inserted into t was not logged.
+ So we continue with row-based until the temp table is dropped.
+ If we are in a stored function or trigger, we mustn't reset in the
+ middle of its execution (as the binary logging way of a stored function
+ or trigger is decided when it starts executing, depending for example on
+ the caller (for a stored function: if caller is SELECT or
+ INSERT/UPDATE/DELETE...).
+ */
+ DBUG_PRINT("debug",
+ ("temporary_tables: %s, in_sub_stmt: %s, system_thread: %s",
+ YESNO(temporary_tables), YESNO(in_sub_stmt),
+ show_system_thread(system_thread)));
+ if (in_sub_stmt == 0)
+ {
+ if (variables.binlog_format == BINLOG_FORMAT_ROW)
+ set_current_stmt_binlog_format_row();
+ else if (temporary_tables == NULL)
+ set_current_stmt_binlog_format_stmt();
+ }
+ DBUG_VOID_RETURN;
+ }
+
+ /**
+ Set the current database; use deep copy of C-string.
+
+ @param new_db a pointer to the new database name.
+ @param new_db_len length of the new database name.
+
+ Initialize the current database from a NULL-terminated string with
+ length. If we run out of memory, we free the current database and
+ return TRUE. This way the user will notice the error as there will be
+ no current database selected (in addition to the error message set by
+ malloc).
+
+ @note This operation just sets {db, db_length}. Switching the current
+ database usually involves other actions, like switching other database
+ attributes including security context. In the future, this operation
+ will be made private and more convenient interface will be provided.
+
+ @return Operation status
+ @retval FALSE Success
+ @retval TRUE Out-of-memory error
+ */
+ bool set_db(const char *new_db, size_t new_db_len)
+ {
+ /*
+ Acquiring mutex LOCK_thd_data as we either free the memory allocated
+ for the database and reallocating the memory for the new db or memcpy
+ the new_db to the db.
+ */
+ mysql_mutex_lock(&LOCK_thd_data);
+ /* Do not reallocate memory if current chunk is big enough. */
+ if (db && new_db && db_length >= new_db_len)
+ memcpy(db, new_db, new_db_len+1);
+ else
+ {
+ my_free(db);
+ if (new_db)
+ db= my_strndup(new_db, new_db_len, MYF(MY_WME | ME_FATALERROR));
+ else
+ db= NULL;
+ }
+ db_length= db ? new_db_len : 0;
+ bool result= new_db && !db;
+ mysql_mutex_unlock(&LOCK_thd_data);
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ if (result)
+ PSI_THREAD_CALL(set_thread_db)(new_db, new_db_len);
+#endif
+ return result;
+ }
+
+ /**
+ Set the current database; use shallow copy of C-string.
+
+ @param new_db a pointer to the new database name.
+ @param new_db_len length of the new database name.
+
+ @note This operation just sets {db, db_length}. Switching the current
+ database usually involves other actions, like switching other database
+ attributes including security context. In the future, this operation
+ will be made private and more convenient interface will be provided.
+ */
+ void reset_db(char *new_db, size_t new_db_len)
+ {
+ if (new_db != db || new_db_len != db_length)
+ {
+ mysql_mutex_lock(&LOCK_thd_data);
+ db= new_db;
+ db_length= new_db_len;
+ mysql_mutex_unlock(&LOCK_thd_data);
+#ifdef HAVE_PSI_THREAD_INTERFACE
+ PSI_THREAD_CALL(set_thread_db)(new_db, new_db_len);
+#endif
+ }
+ }
+ /*
+ Copy the current database to the argument. Use the current arena to
+ allocate memory for a deep copy: current database may be freed after
+ a statement is parsed but before it's executed.
+ */
+ bool copy_db_to(char **p_db, size_t *p_db_length)
+ {
+ if (db == NULL)
+ {
+ my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
+ return TRUE;
+ }
+ *p_db= strmake(db, db_length);
+ *p_db_length= db_length;
+ return FALSE;
+ }
+ thd_scheduler event_scheduler;
+
+public:
+ inline Internal_error_handler *get_internal_handler()
+ { return m_internal_handler; }
+
+ /**
+ Add an internal error handler to the thread execution context.
+ @param handler the exception handler to add
+ */
+ void push_internal_handler(Internal_error_handler *handler);
+
+private:
+ /**
+ Handle a sql condition.
+ @param sql_errno the condition error number
+ @param sqlstate the condition sqlstate
+ @param level the condition level
+ @param msg the condition message text
+ @param[out] cond_hdl the sql condition raised, if any
+ @return true if the condition is handled
+ */
+ bool handle_condition(uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl);
+
+public:
+ /**
+ Remove the error handler last pushed.
+ */
+ Internal_error_handler *pop_internal_handler();
+
+ /**
+ Raise an exception condition.
+ @param code the MYSQL_ERRNO error code of the error
+ */
+ void raise_error(uint code);
+
+ /**
+ Raise an exception condition, with a formatted message.
+ @param code the MYSQL_ERRNO error code of the error
+ */
+ void raise_error_printf(uint code, ...);
+
+ /**
+ Raise a completion condition (warning).
+ @param code the MYSQL_ERRNO error code of the warning
+ */
+ void raise_warning(uint code);
+
+ /**
+ Raise a completion condition (warning), with a formatted message.
+ @param code the MYSQL_ERRNO error code of the warning
+ */
+ void raise_warning_printf(uint code, ...);
+
+ /**
+ Raise a completion condition (note), with a fixed message.
+ @param code the MYSQL_ERRNO error code of the note
+ */
+ void raise_note(uint code);
+
+ /**
+ Raise an completion condition (note), with a formatted message.
+ @param code the MYSQL_ERRNO error code of the note
+ */
+ void raise_note_printf(uint code, ...);
+
+private:
+ /*
+ Only the implementation of the SIGNAL and RESIGNAL statements
+ is permitted to raise SQL conditions in a generic way,
+ or to raise them by bypassing handlers (RESIGNAL).
+ To raise a SQL condition, the code should use the public
+ raise_error() or raise_warning() methods provided by class THD.
+ */
+ friend class Sql_cmd_common_signal;
+ friend class Sql_cmd_signal;
+ friend class Sql_cmd_resignal;
+ friend void push_warning(THD*, Sql_condition::enum_warning_level, uint, const char*);
+ friend void my_message_sql(uint, const char *, myf);
+
+ /**
+ Raise a generic SQL condition.
+ @param sql_errno the condition error number
+ @param sqlstate the condition SQLSTATE
+ @param level the condition level
+ @param msg the condition message text
+ @return The condition raised, or NULL
+ */
+ Sql_condition*
+ raise_condition(uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg);
+
+public:
+ /** Overloaded to guard query/query_length fields */
+ virtual void set_statement(Statement *stmt);
+ void set_command(enum enum_server_command command);
+ inline enum enum_server_command get_command() const
+ { return m_command; }
+
+ /**
+ Assign a new value to thd->query and thd->query_id and mysys_var.
+ Protected with LOCK_thd_data mutex.
+ */
+ void set_query(char *query_arg, uint32 query_length_arg,
+ CHARSET_INFO *cs_arg)
+ {
+ set_query(CSET_STRING(query_arg, query_length_arg, cs_arg));
+ }
+ void set_query(char *query_arg, uint32 query_length_arg) /*Mutex protected*/
+ {
+ set_query(CSET_STRING(query_arg, query_length_arg, charset()));
+ }
+ void set_query(const CSET_STRING &str); /* Mutex protected */
+ void reset_query() /* Mutex protected */
+ { set_query(CSET_STRING()); }
+ void set_query_and_id(char *query_arg, uint32 query_length_arg,
+ CHARSET_INFO *cs, query_id_t new_query_id);
+ void set_query_id(query_id_t new_query_id)
+ {
+ query_id= new_query_id;
+ }
+ void set_open_tables(TABLE *open_tables_arg)
+ {
+ mysql_mutex_lock(&LOCK_thd_data);
+ open_tables= open_tables_arg;
+ mysql_mutex_unlock(&LOCK_thd_data);
+ }
+ void set_mysys_var(struct st_my_thread_var *new_mysys_var);
+ void enter_locked_tables_mode(enum_locked_tables_mode mode_arg)
+ {
+ DBUG_ASSERT(locked_tables_mode == LTM_NONE);
+
+ if (mode_arg == LTM_LOCK_TABLES)
+ {
+ /*
+ When entering LOCK TABLES mode we should set explicit duration
+ for all metadata locks acquired so far in order to avoid releasing
+ them till UNLOCK TABLES statement.
+ We don't do this when entering prelocked mode since sub-statements
+ don't release metadata locks and restoring status-quo after leaving
+ prelocking mode gets complicated.
+ */
+ mdl_context.set_explicit_duration_for_all_locks();
+ }
+
+ locked_tables_mode= mode_arg;
+ }
+ void leave_locked_tables_mode();
+ int decide_logging_format(TABLE_LIST *tables);
+
+ enum need_invoker { INVOKER_NONE=0, INVOKER_USER, INVOKER_ROLE};
+ void binlog_invoker(bool role) { m_binlog_invoker= role ? INVOKER_ROLE : INVOKER_USER; }
+ enum need_invoker need_binlog_invoker() { return m_binlog_invoker; }
+ void get_definer(LEX_USER *definer, bool role);
+ void set_invoker(const LEX_STRING *user, const LEX_STRING *host)
+ {
+ invoker_user= *user;
+ invoker_host= *host;
+ }
+ LEX_STRING get_invoker_user() { return invoker_user; }
+ LEX_STRING get_invoker_host() { return invoker_host; }
+ bool has_invoker() { return invoker_user.length > 0; }
+
+ void print_aborted_warning(uint threshold, const char *reason)
+ {
+ if (global_system_variables.log_warnings > threshold)
+ {
+ Security_context *sctx= &main_security_ctx;
+ sql_print_warning(ER_THD(this, ER_NEW_ABORTING_CONNECTION),
+ thread_id, (db ? db : "unconnected"),
+ sctx->user ? sctx->user : "unauthenticated",
+ sctx->host_or_ip, reason);
+ }
+ }
+
+private:
+ /*
+ This reference points to the table arena when the expression
+ for a virtual column is being evaluated
+ */
+ Query_arena *arena_for_cached_items;
+
+public:
+ void reset_arena_for_cached_items(Query_arena *new_arena)
+ {
+ arena_for_cached_items= new_arena;
+ }
+ Query_arena *switch_to_arena_for_cached_items(Query_arena *backup)
+ {
+ if (!arena_for_cached_items)
+ return 0;
+ set_n_backup_active_arena(arena_for_cached_items, backup);
+ return backup;
+ }
+
+ void clear_wakeup_ready() { wakeup_ready= false; }
+ /*
+ Sleep waiting for others to wake us up with signal_wakeup_ready().
+ Must call clear_wakeup_ready() before waiting.
+ */
+ void wait_for_wakeup_ready();
+ /* Wake this thread up from wait_for_wakeup_ready(). */
+ void signal_wakeup_ready();
+
+ void add_status_to_global()
+ {
+ mysql_mutex_lock(&LOCK_status);
+ add_to_status(&global_status_var, &status_var);
+ mysql_mutex_unlock(&LOCK_status);
+ }
+
+ wait_for_commit *wait_for_commit_ptr;
+ int wait_for_prior_commit()
+ {
+ if (wait_for_commit_ptr)
+ return wait_for_commit_ptr->wait_for_prior_commit(this);
+ return 0;
+ }
+ void wakeup_subsequent_commits(int wakeup_error)
+ {
+ if (wait_for_commit_ptr)
+ wait_for_commit_ptr->wakeup_subsequent_commits(wakeup_error);
+ }
+ wait_for_commit *suspend_subsequent_commits() {
+ wait_for_commit *suspended= wait_for_commit_ptr;
+ wait_for_commit_ptr= NULL;
+ return suspended;
+ }
+ void resume_subsequent_commits(wait_for_commit *suspended) {
+ DBUG_ASSERT(!wait_for_commit_ptr);
+ wait_for_commit_ptr= suspended;
+ }
+
+ void mark_transaction_to_rollback(bool all);
+private:
+
+ /** The current internal error handler for this thread, or NULL. */
+ Internal_error_handler *m_internal_handler;
+
+ /**
+ The lex to hold the parsed tree of conventional (non-prepared) queries.
+ Whereas for prepared and stored procedure statements we use an own lex
+ instance for each new query, for conventional statements we reuse
+ the same lex. (@see mysql_parse for details).
+ */
+ LEX main_lex;
+ /**
+ This memory root is used for two purposes:
+ - for conventional queries, to allocate structures stored in main_lex
+ during parsing, and allocate runtime data (execution plan, etc.)
+ during execution.
+ - for prepared queries, only to allocate runtime data. The parsed
+ tree itself is reused between executions and thus is stored elsewhere.
+ */
+ MEM_ROOT main_mem_root;
+ Diagnostics_area main_da;
+ Diagnostics_area *m_stmt_da;
+
+ /**
+ It will be set if CURRENT_USER() or CURRENT_ROLE() is called in account
+ management statements or default definer is set in CREATE/ALTER SP, SF,
+ Event, TRIGGER or VIEW statements.
+
+ Current user or role will be binlogged into Query_log_event if
+ m_binlog_invoker is not NONE; It will be stored into invoker_host and
+ invoker_user by SQL thread.
+ */
+ enum need_invoker m_binlog_invoker;
+
+ /**
+ It points to the invoker in the Query_log_event.
+ SQL thread use it as the default definer in CREATE/ALTER SP, SF, Event,
+ TRIGGER or VIEW statements or current user in account management
+ statements if it is not NULL.
+ */
+ LEX_STRING invoker_user;
+ LEX_STRING invoker_host;
+
+ /* Protect against add/delete of temporary tables in parallel replication */
+ void rgi_lock_temporary_tables();
+ void rgi_unlock_temporary_tables();
+ bool rgi_have_temporary_tables();
+public:
+ /*
+ Flag, mutex and condition for a thread to wait for a signal from another
+ thread.
+
+ Currently used to wait for group commit to complete, can also be used for
+ other purposes.
+ */
+ bool wakeup_ready;
+ mysql_mutex_t LOCK_wakeup_ready;
+ mysql_cond_t COND_wakeup_ready;
+ /*
+ The GTID assigned to the last commit. If no GTID was assigned to any commit
+ so far, this is indicated by last_commit_gtid.seq_no == 0.
+ */
+ rpl_gtid last_commit_gtid;
+
+ inline void lock_temporary_tables()
+ {
+ if (rgi_slave)
+ rgi_lock_temporary_tables();
+ }
+ inline void unlock_temporary_tables()
+ {
+ if (rgi_slave)
+ rgi_unlock_temporary_tables();
+ }
+ inline bool have_temporary_tables()
+ {
+ return (temporary_tables ||
+ (rgi_slave && rgi_have_temporary_tables()));
+ }
+};
+
+
+/** A short cut for thd->get_stmt_da()->set_ok_status(). */
+
+inline void
+my_ok(THD *thd, ulonglong affected_rows= 0, ulonglong id= 0,
+ const char *message= NULL)
+{
+ thd->set_row_count_func(affected_rows);
+ thd->get_stmt_da()->set_ok_status(affected_rows, id, message);
+}
+
+
+/** A short cut for thd->get_stmt_da()->set_eof_status(). */
+
+inline void
+my_eof(THD *thd)
+{
+ thd->set_row_count_func(-1);
+ thd->get_stmt_da()->set_eof_status(thd);
+}
+
+#define tmp_disable_binlog(A) \
+ {ulonglong tmp_disable_binlog__save_options= (A)->variables.option_bits; \
+ (A)->variables.option_bits&= ~OPTION_BIN_LOG
+
+#define reenable_binlog(A) (A)->variables.option_bits= tmp_disable_binlog__save_options;}
+
+
+inline sql_mode_t sql_mode_for_dates(THD *thd)
+{
+ return thd->variables.sql_mode &
+ (MODE_NO_ZERO_DATE | MODE_NO_ZERO_IN_DATE | MODE_INVALID_DATES);
+}
+
+/*
+ Used to hold information about file and file structure in exchange
+ via non-DB file (...INTO OUTFILE..., ...LOAD DATA...)
+ XXX: We never call destructor for objects of this class.
+*/
+
+class sql_exchange :public Sql_alloc
+{
+public:
+ enum enum_filetype filetype; /* load XML, Added by Arnold & Erik */
+ char *file_name;
+ String *field_term,*enclosed,*line_term,*line_start,*escaped;
+ bool opt_enclosed;
+ bool dumpfile;
+ ulong skip_lines;
+ CHARSET_INFO *cs;
+ sql_exchange(char *name, bool dumpfile_flag,
+ enum_filetype filetype_arg= FILETYPE_CSV);
+ bool escaped_given(void);
+};
+
+/*
+ This is used to get result from a select
+*/
+
+class JOIN;
+
+/* Pure interface for sending tabular data */
+class select_result_sink: public Sql_alloc
+{
+public:
+ /*
+ send_data returns 0 on ok, 1 on error and -1 if data was ignored, for
+ example for a duplicate row entry written to a temp table.
+ */
+ virtual int send_data(List<Item> &items)=0;
+ virtual ~select_result_sink() {};
+};
+
+
+/*
+ Interface for sending tabular data, together with some other stuff:
+
+ - Primary purpose seems to be seding typed tabular data:
+ = the DDL is sent with send_fields()
+ = the rows are sent with send_data()
+ Besides that,
+ - there seems to be an assumption that the sent data is a result of
+ SELECT_LEX_UNIT *unit,
+ - nest_level is used by SQL parser
+*/
+
+class select_result :public select_result_sink
+{
+protected:
+ THD *thd;
+ /*
+ All descendant classes have their send_data() skip the first
+ unit->offset_limit_cnt rows sent. Select_materialize
+ also uses unit->get_unit_column_types().
+ */
+ SELECT_LEX_UNIT *unit;
+ /* Something used only by the parser: */
+public:
+ select_result();
+ virtual ~select_result() {};
+ virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u)
+ {
+ unit= u;
+ return 0;
+ }
+ virtual int prepare2(void) { return 0; }
+ /*
+ Because of peculiarities of prepared statements protocol
+ we need to know number of columns in the result set (if
+ there is a result set) apart from sending columns metadata.
+ */
+ virtual uint field_count(List<Item> &fields) const
+ { return fields.elements; }
+ virtual bool send_result_set_metadata(List<Item> &list, uint flags)=0;
+ virtual bool initialize_tables (JOIN *join=0) { return 0; }
+ virtual bool send_eof()=0;
+ /**
+ Check if this query returns a result set and therefore is allowed in
+ cursors and set an error message if it is not the case.
+
+ @retval FALSE success
+ @retval TRUE error, an error message is set
+ */
+ virtual bool check_simple_select() const;
+ virtual void abort_result_set() {}
+ /*
+ Cleanup instance of this class for next execution of a prepared
+ statement/stored procedure.
+ */
+ virtual void cleanup();
+ void set_thd(THD *thd_arg) { thd= thd_arg; }
+#ifdef EMBEDDED_LIBRARY
+ virtual void begin_dataset() {}
+#else
+ void begin_dataset() {}
+#endif
+ virtual void update_used_tables() {}
+
+ void reset_offset_limit()
+ {
+ unit->offset_limit_cnt= 0;
+ }
+};
+
+
+/*
+ This is a select_result_sink which simply writes all data into a (temporary)
+ table. Creation/deletion of the table is outside of the scope of the class
+
+ It is aimed at capturing SHOW EXPLAIN output, so:
+ - Unlike select_result class, we don't assume that the sent data is an
+ output of a SELECT_LEX_UNIT (and so we dont apply "LIMIT x,y" from the
+ unit)
+ - We don't try to convert the target table to MyISAM
+*/
+
+class select_result_explain_buffer : public select_result_sink
+{
+public:
+ select_result_explain_buffer(THD *thd_arg, TABLE *table_arg) :
+ thd(thd_arg), dst_table(table_arg) {};
+
+ THD *thd;
+ TABLE *dst_table; /* table to write into */
+
+ /* The following is called in the child thread: */
+ int send_data(List<Item> &items);
+};
+
+
+/*
+ This is a select_result_sink which stores the data in text form.
+*/
+
+class select_result_text_buffer : public select_result_sink
+{
+public:
+ select_result_text_buffer(THD *thd_arg) : thd(thd_arg) {}
+ int send_data(List<Item> &items);
+ bool send_result_set_metadata(List<Item> &fields, uint flag);
+
+ void save_to(String *res);
+private:
+ int append_row(List<Item> &items, bool send_names);
+
+ THD *thd;
+ List<char*> rows;
+ int n_columns;
+};
+
+
+/*
+ Base class for select_result descendands which intercept and
+ transform result set rows. As the rows are not sent to the client,
+ sending of result set metadata should be suppressed as well.
+*/
+
+class select_result_interceptor: public select_result
+{
+public:
+ select_result_interceptor()
+ {
+ DBUG_ENTER("select_result_interceptor::select_result_interceptor");
+ DBUG_PRINT("enter", ("this 0x%lx", (ulong) this));
+ DBUG_VOID_RETURN;
+ } /* Remove gcc warning */
+ uint field_count(List<Item> &fields) const { return 0; }
+ bool send_result_set_metadata(List<Item> &fields, uint flag) { return FALSE; }
+};
+
+
+class select_send :public select_result {
+ /**
+ True if we have sent result set metadata to the client.
+ In this case the client always expects us to end the result
+ set with an eof or error packet
+ */
+ bool is_result_set_started;
+public:
+ select_send() :is_result_set_started(FALSE) {}
+ bool send_result_set_metadata(List<Item> &list, uint flags);
+ int send_data(List<Item> &items);
+ bool send_eof();
+ virtual bool check_simple_select() const { return FALSE; }
+ void abort_result_set();
+ virtual void cleanup();
+};
+
+
+class select_to_file :public select_result_interceptor {
+protected:
+ sql_exchange *exchange;
+ File file;
+ IO_CACHE cache;
+ ha_rows row_count;
+ char path[FN_REFLEN];
+
+public:
+ select_to_file(sql_exchange *ex) :exchange(ex), file(-1),row_count(0L)
+ { path[0]=0; }
+ ~select_to_file();
+ bool send_eof();
+ void cleanup();
+};
+
+
+#define ESCAPE_CHARS "ntrb0ZN" // keep synchronous with READ_INFO::unescape
+
+
+/*
+ List of all possible characters of a numeric value text representation.
+*/
+#define NUMERIC_CHARS ".0123456789e+-"
+
+
+class select_export :public select_to_file {
+ uint field_term_length;
+ int field_sep_char,escape_char,line_sep_char;
+ int field_term_char; // first char of FIELDS TERMINATED BY or MAX_INT
+ /*
+ The is_ambiguous_field_sep field is true if a value of the field_sep_char
+ field is one of the 'n', 't', 'r' etc characters
+ (see the READ_INFO::unescape method and the ESCAPE_CHARS constant value).
+ */
+ bool is_ambiguous_field_sep;
+ /*
+ The is_ambiguous_field_term is true if field_sep_char contains the first
+ char of the FIELDS TERMINATED BY (ENCLOSED BY is empty), and items can
+ contain this character.
+ */
+ bool is_ambiguous_field_term;
+ /*
+ The is_unsafe_field_sep field is true if a value of the field_sep_char
+ field is one of the '0'..'9', '+', '-', '.' and 'e' characters
+ (see the NUMERIC_CHARS constant value).
+ */
+ bool is_unsafe_field_sep;
+ bool fixed_row_size;
+ CHARSET_INFO *write_cs; // output charset
+public:
+ select_export(sql_exchange *ex) :select_to_file(ex) {}
+ ~select_export();
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+ int send_data(List<Item> &items);
+};
+
+
+class select_dump :public select_to_file {
+public:
+ select_dump(sql_exchange *ex) :select_to_file(ex) {}
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+ int send_data(List<Item> &items);
+};
+
+
+class select_insert :public select_result_interceptor {
+ public:
+ TABLE_LIST *table_list;
+ TABLE *table;
+ List<Item> *fields;
+ ulonglong autoinc_value_of_last_inserted_row; // autogenerated or not
+ COPY_INFO info;
+ bool insert_into_view;
+ select_insert(TABLE_LIST *table_list_par,
+ TABLE *table_par, List<Item> *fields_par,
+ List<Item> *update_fields, List<Item> *update_values,
+ enum_duplicates duplic, bool ignore);
+ ~select_insert();
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+ virtual int prepare2(void);
+ virtual int send_data(List<Item> &items);
+ virtual void store_values(List<Item> &values);
+ virtual bool can_rollback_data() { return 0; }
+ bool send_eof();
+ virtual void abort_result_set();
+ /* not implemented: select_insert is never re-used in prepared statements */
+ void cleanup();
+};
+
+
+class select_create: public select_insert {
+ ORDER *group;
+ TABLE_LIST *create_table;
+ HA_CREATE_INFO *create_info;
+ TABLE_LIST *select_tables;
+ Alter_info *alter_info;
+ Field **field;
+ /* lock data for tmp table */
+ MYSQL_LOCK *m_lock;
+ /* m_lock or thd->extra_lock */
+ MYSQL_LOCK **m_plock;
+ bool exit_done;
+
+public:
+ select_create (TABLE_LIST *table_arg,
+ HA_CREATE_INFO *create_info_par,
+ Alter_info *alter_info_arg,
+ List<Item> &select_fields,enum_duplicates duplic, bool ignore,
+ TABLE_LIST *select_tables_arg)
+ :select_insert (NULL, NULL, &select_fields, 0, 0, duplic, ignore),
+ create_table(table_arg),
+ create_info(create_info_par),
+ select_tables(select_tables_arg),
+ alter_info(alter_info_arg),
+ m_plock(NULL), exit_done(0)
+ {}
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+
+ int binlog_show_create_table(TABLE **tables, uint count);
+ void store_values(List<Item> &values);
+ bool send_eof();
+ virtual void abort_result_set();
+ virtual bool can_rollback_data() { return 1; }
+
+ // Needed for access from local class MY_HOOKS in prepare(), since thd is proteted.
+ const THD *get_thd(void) { return thd; }
+ const HA_CREATE_INFO *get_create_info() { return create_info; };
+ int prepare2(void) { return 0; }
+};
+
+#include <myisam.h>
+
+#ifdef WITH_ARIA_STORAGE_ENGINE
+#include <maria.h>
+#else
+#undef USE_ARIA_FOR_TMP_TABLES
+#endif
+
+#ifdef USE_ARIA_FOR_TMP_TABLES
+#define TMP_ENGINE_COLUMNDEF MARIA_COLUMNDEF
+#define TMP_ENGINE_HTON maria_hton
+#define TMP_ENGINE_NAME "Aria"
+#else
+#define TMP_ENGINE_COLUMNDEF MI_COLUMNDEF
+#define TMP_ENGINE_HTON myisam_hton
+#define TMP_ENGINE_NAME "MyISAM"
+#endif
+
+/*
+ Param to create temporary tables when doing SELECT:s
+ NOTE
+ This structure is copied using memcpy as a part of JOIN.
+*/
+
+class TMP_TABLE_PARAM :public Sql_alloc
+{
+private:
+ /* Prevent use of these (not safe because of lists and copy_field) */
+ TMP_TABLE_PARAM(const TMP_TABLE_PARAM &);
+ void operator=(TMP_TABLE_PARAM &);
+
+public:
+ List<Item> copy_funcs;
+ List<Item> save_copy_funcs;
+ Copy_field *copy_field, *copy_field_end;
+ Copy_field *save_copy_field, *save_copy_field_end;
+ uchar *group_buff;
+ Item **items_to_copy; /* Fields in tmp table */
+ TMP_ENGINE_COLUMNDEF *recinfo, *start_recinfo;
+ KEY *keyinfo;
+ ha_rows end_write_records;
+ /**
+ Number of normal fields in the query, including those referred to
+ from aggregate functions. Hence, "SELECT `field1`,
+ SUM(`field2`) from t1" sets this counter to 2.
+
+ @see count_field_types
+ */
+ uint field_count;
+ /**
+ Number of fields in the query that have functions. Includes both
+ aggregate functions (e.g., SUM) and non-aggregates (e.g., RAND).
+ Also counts functions referred to from aggregate functions, i.e.,
+ "SELECT SUM(RAND())" sets this counter to 2.
+
+ @see count_field_types
+ */
+ uint func_count;
+ /**
+ Number of fields in the query that have aggregate functions. Note
+ that the optimizer may choose to optimize away these fields by
+ replacing them with constants, in which case sum_func_count will
+ need to be updated.
+
+ @see opt_sum_query, count_field_types
+ */
+ uint sum_func_count;
+ uint hidden_field_count;
+ uint group_parts,group_length,group_null_parts;
+ uint quick_group;
+ bool using_indirect_summary_function;
+ /* If >0 convert all blob fields to varchar(convert_blob_length) */
+ uint convert_blob_length;
+ CHARSET_INFO *table_charset;
+ bool schema_table;
+ /* TRUE if the temp table is created for subquery materialization. */
+ bool materialized_subquery;
+ /* TRUE if all columns of the table are guaranteed to be non-nullable */
+ bool force_not_null_cols;
+ /*
+ True if GROUP BY and its aggregate functions are already computed
+ by a table access method (e.g. by loose index scan). In this case
+ query execution should not perform aggregation and should treat
+ aggregate functions as normal functions.
+ */
+ bool precomputed_group_by;
+ bool force_copy_fields;
+ /*
+ If TRUE, create_tmp_field called from create_tmp_table will convert
+ all BIT fields to 64-bit longs. This is a workaround the limitation
+ that MEMORY tables cannot index BIT columns.
+ */
+ bool bit_fields_as_long;
+ /*
+ Whether to create or postpone actual creation of this temporary table.
+ TRUE <=> create_tmp_table will create only the TABLE structure.
+ */
+ bool skip_create_table;
+
+ TMP_TABLE_PARAM()
+ :copy_field(0), group_parts(0),
+ group_length(0), group_null_parts(0), convert_blob_length(0),
+ schema_table(0), materialized_subquery(0), force_not_null_cols(0),
+ precomputed_group_by(0),
+ force_copy_fields(0), bit_fields_as_long(0), skip_create_table(0)
+ {}
+ ~TMP_TABLE_PARAM()
+ {
+ cleanup();
+ }
+ void init(void);
+ inline void cleanup(void)
+ {
+ if (copy_field) /* Fix for Intel compiler */
+ {
+ delete [] copy_field;
+ save_copy_field= copy_field= NULL;
+ save_copy_field_end= copy_field_end= NULL;
+ }
+ }
+};
+
+class select_union :public select_result_interceptor
+{
+public:
+ TMP_TABLE_PARAM tmp_table_param;
+ int write_err; /* Error code from the last send_data->ha_write_row call. */
+public:
+ TABLE *table;
+ ha_rows records;
+
+ select_union() :write_err(0), table(0), records(0) { tmp_table_param.init(); }
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+ int send_data(List<Item> &items);
+ bool send_eof();
+ bool flush();
+ void cleanup();
+ virtual bool create_result_table(THD *thd, List<Item> *column_types,
+ bool is_distinct, ulonglong options,
+ const char *alias,
+ bool bit_fields_as_long,
+ bool create_table,
+ bool keep_row_order= FALSE);
+ TMP_TABLE_PARAM *get_tmp_table_param() { return &tmp_table_param; }
+};
+
+/* Base subselect interface class */
+class select_subselect :public select_result_interceptor
+{
+protected:
+ Item_subselect *item;
+public:
+ select_subselect(Item_subselect *item);
+ int send_data(List<Item> &items)=0;
+ bool send_eof() { return 0; };
+};
+
+/* Single value subselect interface class */
+class select_singlerow_subselect :public select_subselect
+{
+public:
+ select_singlerow_subselect(Item_subselect *item_arg)
+ :select_subselect(item_arg)
+ {}
+ int send_data(List<Item> &items);
+};
+
+
+/*
+ This class specializes select_union to collect statistics about the
+ data stored in the temp table. Currently the class collects statistcs
+ about NULLs.
+*/
+
+class select_materialize_with_stats : public select_union
+{
+protected:
+ class Column_statistics
+ {
+ public:
+ /* Count of NULLs per column. */
+ ha_rows null_count;
+ /* The row number that contains the first NULL in a column. */
+ ha_rows min_null_row;
+ /* The row number that contains the last NULL in a column. */
+ ha_rows max_null_row;
+ };
+
+ /* Array of statistics data per column. */
+ Column_statistics* col_stat;
+
+ /*
+ The number of columns in the biggest sub-row that consists of only
+ NULL values.
+ */
+ uint max_nulls_in_row;
+ /*
+ Count of rows writtent to the temp table. This is redundant as it is
+ already stored in handler::stats.records, however that one is relatively
+ expensive to compute (given we need that for evry row).
+ */
+ ha_rows count_rows;
+
+protected:
+ void reset();
+
+public:
+ select_materialize_with_stats() { tmp_table_param.init(); }
+ bool create_result_table(THD *thd, List<Item> *column_types,
+ bool is_distinct, ulonglong options,
+ const char *alias,
+ bool bit_fields_as_long,
+ bool create_table,
+ bool keep_row_order= FALSE);
+ bool init_result_table(ulonglong select_options);
+ int send_data(List<Item> &items);
+ void cleanup();
+ ha_rows get_null_count_of_col(uint idx)
+ {
+ DBUG_ASSERT(idx < table->s->fields);
+ return col_stat[idx].null_count;
+ }
+ ha_rows get_max_null_of_col(uint idx)
+ {
+ DBUG_ASSERT(idx < table->s->fields);
+ return col_stat[idx].max_null_row;
+ }
+ ha_rows get_min_null_of_col(uint idx)
+ {
+ DBUG_ASSERT(idx < table->s->fields);
+ return col_stat[idx].min_null_row;
+ }
+ uint get_max_nulls_in_row() { return max_nulls_in_row; }
+};
+
+
+/* used in independent ALL/ANY optimisation */
+class select_max_min_finder_subselect :public select_subselect
+{
+ Item_cache *cache;
+ bool (select_max_min_finder_subselect::*op)();
+ bool fmax;
+ bool is_all;
+public:
+ select_max_min_finder_subselect(Item_subselect *item_arg, bool mx,
+ bool all)
+ :select_subselect(item_arg), cache(0), fmax(mx), is_all(all)
+ {}
+ void cleanup();
+ int send_data(List<Item> &items);
+ bool cmp_real();
+ bool cmp_int();
+ bool cmp_decimal();
+ bool cmp_str();
+};
+
+/* EXISTS subselect interface class */
+class select_exists_subselect :public select_subselect
+{
+public:
+ select_exists_subselect(Item_subselect *item_arg)
+ :select_subselect(item_arg){}
+ int send_data(List<Item> &items);
+};
+
+
+
+
+/*
+ Optimizer and executor structure for the materialized semi-join info. This
+ structure contains
+ - The sj-materialization temporary table
+ - Members needed to make index lookup or a full scan of the temptable.
+*/
+class SJ_MATERIALIZATION_INFO : public Sql_alloc
+{
+public:
+ /* Optimal join sub-order */
+ struct st_position *positions;
+
+ uint tables; /* Number of tables in the sj-nest */
+
+ /* Expected #rows in the materialized table */
+ double rows;
+
+ /*
+ Cost to materialize - execute the sub-join and write rows into temp.table
+ */
+ Cost_estimate materialization_cost;
+
+ /* Cost to make one lookup in the temptable */
+ Cost_estimate lookup_cost;
+
+ /* Cost of scanning the materialized table */
+ Cost_estimate scan_cost;
+
+ /* --- Execution structures ---------- */
+
+ /*
+ TRUE <=> This structure is used for execution. We don't necessarily pick
+ sj-materialization, so some of SJ_MATERIALIZATION_INFO structures are not
+ used by materialization
+ */
+ bool is_used;
+
+ bool materialized; /* TRUE <=> materialization already performed */
+ /*
+ TRUE - the temptable is read with full scan
+ FALSE - we use the temptable for index lookups
+ */
+ bool is_sj_scan;
+
+ /* The temptable and its related info */
+ TMP_TABLE_PARAM sjm_table_param;
+ List<Item> sjm_table_cols;
+ TABLE *table;
+
+ /* Structure used to make index lookups */
+ struct st_table_ref *tab_ref;
+ Item *in_equality; /* See create_subq_in_equalities() */
+
+ Item *join_cond; /* See comments in make_join_select() */
+ Copy_field *copy_field; /* Needed for SJ_Materialization scan */
+};
+
+
+/* Structs used when sorting */
+
+typedef struct st_sort_field {
+ Field *field; /* Field to sort */
+ Item *item; /* Item if not sorting fields */
+ uint length; /* Length of sort field */
+ uint suffix_length; /* Length suffix (0-4) */
+ Item_result result_type; /* Type of item */
+ bool reverse; /* if descending sort */
+ bool need_strxnfrm; /* If we have to use strxnfrm() */
+} SORT_FIELD;
+
+
+typedef struct st_sort_buffer {
+ uint index; /* 0 or 1 */
+ uint sort_orders;
+ uint change_pos; /* If sort-fields changed */
+ char **buff;
+ SORT_FIELD *sortorder;
+} SORT_BUFFER;
+
+/* Structure for db & table in sql_yacc */
+
+class Table_ident :public Sql_alloc
+{
+public:
+ LEX_STRING db;
+ LEX_STRING table;
+ SELECT_LEX_UNIT *sel;
+ inline Table_ident(THD *thd, LEX_STRING db_arg, LEX_STRING table_arg,
+ bool force)
+ :table(table_arg), sel((SELECT_LEX_UNIT *)0)
+ {
+ if (!force && (thd->client_capabilities & CLIENT_NO_SCHEMA))
+ db.str=0;
+ else
+ db= db_arg;
+ }
+ inline Table_ident(LEX_STRING table_arg)
+ :table(table_arg), sel((SELECT_LEX_UNIT *)0)
+ {
+ db.str=0;
+ }
+ /*
+ This constructor is used only for the case when we create a derived
+ table. A derived table has no name and doesn't belong to any database.
+ Later, if there was an alias specified for the table, it will be set
+ by add_table_to_list.
+ */
+ inline Table_ident(SELECT_LEX_UNIT *s) : sel(s)
+ {
+ /* We must have a table name here as this is used with add_table_to_list */
+ db.str= empty_c_string; /* a subject to casedn_str */
+ db.length= 0;
+ table.str= internal_table_name;
+ table.length=1;
+ }
+ bool is_derived_table() const { return MY_TEST(sel); }
+ inline void change_db(char *db_name)
+ {
+ db.str= db_name; db.length= (uint) strlen(db_name);
+ }
+};
+
+// this is needed for user_vars hash
+class user_var_entry
+{
+ public:
+ user_var_entry() {} /* Remove gcc warning */
+ LEX_STRING name;
+ char *value;
+ ulong length;
+ query_id_t update_query_id, used_query_id;
+ Item_result type;
+ bool unsigned_flag;
+
+ double val_real(bool *null_value);
+ longlong val_int(bool *null_value) const;
+ String *val_str(bool *null_value, String *str, uint decimals);
+ my_decimal *val_decimal(bool *null_value, my_decimal *result);
+ DTCollation collation;
+};
+
+user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
+ bool create_if_not_exists);
+
+/*
+ Unique -- class for unique (removing of duplicates).
+ Puts all values to the TREE. If the tree becomes too big,
+ it's dumped to the file. User can request sorted values, or
+ just iterate through them. In the last case tree merging is performed in
+ memory simultaneously with iteration, so it should be ~2-3x faster.
+ */
+
+class Unique :public Sql_alloc
+{
+ DYNAMIC_ARRAY file_ptrs;
+ ulong max_elements;
+ ulonglong max_in_memory_size;
+ IO_CACHE file;
+ TREE tree;
+ uchar *record_pointers;
+ ulong filtered_out_elems;
+ bool flush();
+ uint size;
+ uint full_size;
+ uint min_dupl_count; /* always 0 for unions, > 0 for intersections */
+ bool with_counters;
+
+ bool merge(TABLE *table, uchar *buff, bool without_last_merge);
+
+public:
+ ulong elements;
+ Unique(qsort_cmp2 comp_func, void *comp_func_fixed_arg,
+ uint size_arg, ulonglong max_in_memory_size_arg,
+ uint min_dupl_count_arg= 0);
+ ~Unique();
+ ulong elements_in_tree() { return tree.elements_in_tree; }
+ inline bool unique_add(void *ptr)
+ {
+ DBUG_ENTER("unique_add");
+ DBUG_PRINT("info", ("tree %u - %lu", tree.elements_in_tree, max_elements));
+ if (!(tree.flag & TREE_ONLY_DUPS) &&
+ tree.elements_in_tree >= max_elements && flush())
+ DBUG_RETURN(1);
+ DBUG_RETURN(!tree_insert(&tree, ptr, 0, tree.custom_arg));
+ }
+
+ bool is_in_memory() { return (my_b_tell(&file) == 0); }
+ void close_for_expansion() { tree.flag= TREE_ONLY_DUPS; }
+
+ bool get(TABLE *table);
+
+ /* Cost of searching for an element in the tree */
+ inline static double get_search_cost(ulonglong tree_elems, uint compare_factor)
+ {
+ return log((double) tree_elems) / (compare_factor * M_LN2);
+ }
+
+ static double get_use_cost(uint *buffer, size_t nkeys, uint key_size,
+ ulonglong max_in_memory_size, uint compare_factor,
+ bool intersect_fl, bool *in_memory);
+ inline static int get_cost_calc_buff_size(size_t nkeys, uint key_size,
+ ulonglong max_in_memory_size)
+ {
+ register ulonglong max_elems_in_tree=
+ max_in_memory_size / ALIGN_SIZE(sizeof(TREE_ELEMENT)+key_size);
+ return (int) (sizeof(uint)*(1 + nkeys/max_elems_in_tree));
+ }
+
+ void reset();
+ bool walk(TABLE *table, tree_walk_action action, void *walk_action_arg);
+
+ uint get_size() const { return size; }
+ ulonglong get_max_in_memory_size() const { return max_in_memory_size; }
+
+ friend int unique_write_to_file(uchar* key, element_count count, Unique *unique);
+ friend int unique_write_to_ptrs(uchar* key, element_count count, Unique *unique);
+
+ friend int unique_write_to_file_with_count(uchar* key, element_count count,
+ Unique *unique);
+ friend int unique_intersect_write_to_ptrs(uchar* key, element_count count,
+ Unique *unique);
+};
+
+
+class multi_delete :public select_result_interceptor
+{
+ TABLE_LIST *delete_tables, *table_being_deleted;
+ Unique **tempfiles;
+ ha_rows deleted, found;
+ uint num_of_tables;
+ int error;
+ bool do_delete;
+ /* True if at least one table we delete from is transactional */
+ bool transactional_tables;
+ /* True if at least one table we delete from is not transactional */
+ bool normal_tables;
+ bool delete_while_scanning;
+ /*
+ error handling (rollback and binlogging) can happen in send_eof()
+ so that afterward abort_result_set() needs to find out that.
+ */
+ bool error_handled;
+
+public:
+ multi_delete(TABLE_LIST *dt, uint num_of_tables);
+ ~multi_delete();
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+ int send_data(List<Item> &items);
+ bool initialize_tables (JOIN *join);
+ int do_deletes();
+ int do_table_deletes(TABLE *table, bool ignore);
+ bool send_eof();
+ inline ha_rows num_deleted()
+ {
+ return deleted;
+ }
+ virtual void abort_result_set();
+};
+
+
+class multi_update :public select_result_interceptor
+{
+ TABLE_LIST *all_tables; /* query/update command tables */
+ List<TABLE_LIST> *leaves; /* list of leves of join table tree */
+ TABLE_LIST *update_tables, *table_being_updated;
+ TABLE **tmp_tables, *main_table, *table_to_update;
+ TMP_TABLE_PARAM *tmp_table_param;
+ ha_rows updated, found;
+ List <Item> *fields, *values;
+ List <Item> **fields_for_table, **values_for_table;
+ uint table_count;
+ /*
+ List of tables referenced in the CHECK OPTION condition of
+ the updated view excluding the updated table.
+ */
+ List <TABLE> unupdated_check_opt_tables;
+ Copy_field *copy_field;
+ enum enum_duplicates handle_duplicates;
+ bool do_update, trans_safe;
+ /* True if the update operation has made a change in a transactional table */
+ bool transactional_tables;
+ bool ignore;
+ /*
+ error handling (rollback and binlogging) can happen in send_eof()
+ so that afterward abort_result_set() needs to find out that.
+ */
+ bool error_handled;
+
+ /* Need this to protect against multiple prepare() calls */
+ bool prepared;
+public:
+ multi_update(TABLE_LIST *ut, List<TABLE_LIST> *leaves_list,
+ List<Item> *fields, List<Item> *values,
+ enum_duplicates handle_duplicates, bool ignore);
+ ~multi_update();
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+ int send_data(List<Item> &items);
+ bool initialize_tables (JOIN *join);
+ int do_updates();
+ bool send_eof();
+ inline ha_rows num_found()
+ {
+ return found;
+ }
+ inline ha_rows num_updated()
+ {
+ return updated;
+ }
+ virtual void abort_result_set();
+ void update_used_tables();
+};
+
+class my_var : public Sql_alloc {
+public:
+ LEX_STRING s;
+#ifndef DBUG_OFF
+ /*
+ Routine to which this Item_splocal belongs. Used for checking if correct
+ runtime context is used for variable handling.
+ */
+ sp_head *sp;
+#endif
+ bool local;
+ uint offset;
+ enum_field_types type;
+ my_var (LEX_STRING& j, bool i, uint o, enum_field_types t)
+ :s(j), local(i), offset(o), type(t)
+ {}
+ ~my_var() {}
+};
+
+class select_dumpvar :public select_result_interceptor {
+ ha_rows row_count;
+public:
+ List<my_var> var_list;
+ select_dumpvar() { var_list.empty(); row_count= 0;}
+ ~select_dumpvar() {}
+ int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
+ int send_data(List<Item> &items);
+ bool send_eof();
+ virtual bool check_simple_select() const;
+ void cleanup();
+};
+
+/* Bits in sql_command_flags */
+
+#define CF_CHANGES_DATA (1U << 0)
+#define CF_REPORT_PROGRESS (1U << 1)
+#define CF_STATUS_COMMAND (1U << 2)
+#define CF_SHOW_TABLE_COMMAND (1U << 3)
+#define CF_WRITE_LOGS_COMMAND (1U << 4)
+
+/**
+ Must be set for SQL statements that may contain
+ Item expressions and/or use joins and tables.
+ Indicates that the parse tree of such statement may
+ contain rule-based optimizations that depend on metadata
+ (i.e. number of columns in a table), and consequently
+ that the statement must be re-prepared whenever
+ referenced metadata changes. Must not be set for
+ statements that themselves change metadata, e.g. RENAME,
+ ALTER and other DDL, since otherwise will trigger constant
+ reprepare. Consequently, complex item expressions and
+ joins are currently prohibited in these statements.
+*/
+#define CF_REEXECUTION_FRAGILE (1U << 5)
+/**
+ Implicitly commit before the SQL statement is executed.
+
+ Statements marked with this flag will cause any active
+ transaction to end (commit) before proceeding with the
+ command execution.
+
+ This flag should be set for statements that probably can't
+ be rolled back or that do not expect any previously metadata
+ locked tables.
+*/
+#define CF_IMPLICT_COMMIT_BEGIN (1U << 6)
+/**
+ Implicitly commit after the SQL statement.
+
+ Statements marked with this flag are automatically committed
+ at the end of the statement.
+
+ This flag should be set for statements that will implicitly
+ open and take metadata locks on system tables that should not
+ be carried for the whole duration of a active transaction.
+*/
+#define CF_IMPLICIT_COMMIT_END (1U << 7)
+/**
+ CF_IMPLICT_COMMIT_BEGIN and CF_IMPLICIT_COMMIT_END are used
+ to ensure that the active transaction is implicitly committed
+ before and after every DDL statement and any statement that
+ modifies our currently non-transactional system tables.
+*/
+#define CF_AUTO_COMMIT_TRANS (CF_IMPLICT_COMMIT_BEGIN | CF_IMPLICIT_COMMIT_END)
+
+/**
+ Diagnostic statement.
+ Diagnostic statements:
+ - SHOW WARNING
+ - SHOW ERROR
+ - GET DIAGNOSTICS (WL#2111)
+ do not modify the diagnostics area during execution.
+*/
+#define CF_DIAGNOSTIC_STMT (1U << 8)
+
+/**
+ Identifies statements that may generate row events
+ and that may end up in the binary log.
+*/
+#define CF_CAN_GENERATE_ROW_EVENTS (1U << 9)
+
+/**
+ Identifies statements which may deal with temporary tables and for which
+ temporary tables should be pre-opened to simplify privilege checks.
+*/
+#define CF_PREOPEN_TMP_TABLES (1U << 10)
+
+/**
+ Identifies statements for which open handlers should be closed in the
+ beginning of the statement.
+*/
+#define CF_HA_CLOSE (1U << 11)
+
+/**
+ Identifies statements that can be explained with EXPLAIN.
+*/
+#define CF_CAN_BE_EXPLAINED (1U << 12)
+
+/** Identifies statements which may generate an optimizer trace */
+#define CF_OPTIMIZER_TRACE (1U << 14)
+
+/**
+ Identifies statements that should always be disallowed in
+ read only transactions.
+*/
+#define CF_DISALLOW_IN_RO_TRANS (1U << 15)
+
+/**
+ Statement that need the binlog format to be unchanged.
+*/
+#define CF_FORCE_ORIGINAL_BINLOG_FORMAT (1U << 16)
+
+/**
+ Statement that inserts new rows (INSERT, REPLACE, LOAD, ALTER TABLE)
+*/
+#define CF_INSERTS_DATA (1U << 17)
+
+/**
+ Statement that updates existing rows (UPDATE, multi-update)
+*/
+#define CF_UPDATES_DATA (1U << 18)
+
+/* Bits in server_command_flags */
+
+/**
+ Skip the increase of the global query id counter. Commonly set for
+ commands that are stateless (won't cause any change on the server
+ internal states).
+*/
+#define CF_SKIP_QUERY_ID (1U << 0)
+
+/**
+ Skip the increase of the number of statements that clients have
+ sent to the server. Commonly used for commands that will cause
+ a statement to be executed but the statement might have not been
+ sent by the user (ie: stored procedure).
+*/
+#define CF_SKIP_QUESTIONS (1U << 1)
+
+/* Inline functions */
+
+inline bool add_item_to_list(THD *thd, Item *item)
+{
+ return thd->lex->current_select->add_item_to_list(thd, item);
+}
+
+inline bool add_value_to_list(THD *thd, Item *value)
+{
+ return thd->lex->value_list.push_back(value);
+}
+
+inline bool add_order_to_list(THD *thd, Item *item, bool asc)
+{
+ return thd->lex->current_select->add_order_to_list(thd, item, asc);
+}
+
+inline bool add_gorder_to_list(THD *thd, Item *item, bool asc)
+{
+ return thd->lex->current_select->add_gorder_to_list(thd, item, asc);
+}
+
+inline bool add_group_to_list(THD *thd, Item *item, bool asc)
+{
+ return thd->lex->current_select->add_group_to_list(thd, item, asc);
+}
+
+/* inline handler methods that need to know TABLE and THD structures */
+inline void handler::increment_statistics(ulong SSV::*offset) const
+{
+ status_var_increment(table->in_use->status_var.*offset);
+ table->in_use->check_limit_rows_examined();
+}
+
+inline void handler::decrement_statistics(ulong SSV::*offset) const
+{
+ status_var_decrement(table->in_use->status_var.*offset);
+}
+
+
+inline int handler::ha_ft_read(uchar *buf)
+{
+ int error= ft_read(buf);
+ if (!error)
+ update_rows_read();
+
+ table->status=error ? STATUS_NOT_FOUND: 0;
+ return error;
+}
+
+inline int handler::ha_rnd_pos_by_record(uchar *buf)
+{
+ int error= rnd_pos_by_record(buf);
+ if (!error)
+ update_rows_read();
+ table->status=error ? STATUS_NOT_FOUND: 0;
+ return error;
+}
+
+inline int handler::ha_read_first_row(uchar *buf, uint primary_key)
+{
+ int error= read_first_row(buf, primary_key);
+ if (!error)
+ update_rows_read();
+ table->status=error ? STATUS_NOT_FOUND: 0;
+ return error;
+}
+
+inline int handler::ha_write_tmp_row(uchar *buf)
+{
+ int error;
+ MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str);
+ increment_statistics(&SSV::ha_tmp_write_count);
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,
+ { error= write_row(buf); })
+ MYSQL_INSERT_ROW_DONE(error);
+ return error;
+}
+
+inline int handler::ha_update_tmp_row(const uchar *old_data, uchar *new_data)
+{
+ int error;
+ MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str);
+ increment_statistics(&SSV::ha_tmp_update_count);
+ MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_UPDATE_ROW, active_index, 0,
+ { error= update_row(old_data, new_data);})
+ MYSQL_UPDATE_ROW_DONE(error);
+ return error;
+}
+
+extern pthread_attr_t *get_connection_attrib(void);
+
+/**
+ Set thread entering a condition
+
+ This function should be called before putting a thread to wait for
+ a condition. @a mutex should be held before calling this
+ function. After being waken up, @f thd_exit_cond should be called.
+
+ @param thd The thread entering the condition, NULL means current thread
+ @param cond The condition the thread is going to wait for
+ @param mutex The mutex associated with the condition, this must be
+ held before call this function
+ @param stage The new process message for the thread
+ @param old_stage The old process message for the thread
+ @param src_function The caller source function name
+ @param src_file The caller source file name
+ @param src_line The caller source line number
+*/
+void thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond, mysql_mutex_t *mutex,
+ const PSI_stage_info *stage, PSI_stage_info *old_stage,
+ const char *src_function, const char *src_file,
+ int src_line);
+
+#define THD_ENTER_COND(P1, P2, P3, P4, P5) \
+ thd_enter_cond(P1, P2, P3, P4, P5, __func__, __FILE__, __LINE__)
+
+/**
+ Set thread leaving a condition
+
+ This function should be called after a thread being waken up for a
+ condition.
+
+ @param thd The thread entering the condition, NULL means current thread
+ @param stage The process message, ususally this should be the old process
+ message before calling @f thd_enter_cond
+ @param src_function The caller source function name
+ @param src_file The caller source file name
+ @param src_line The caller source line number
+*/
+void thd_exit_cond(MYSQL_THD thd, const PSI_stage_info *stage,
+ const char *src_function, const char *src_file,
+ int src_line);
+
+#define THD_EXIT_COND(P1, P2) \
+ thd_exit_cond(P1, P2, __func__, __FILE__, __LINE__)
+
+#endif /* MYSQL_SERVER */
+
+#endif /* SQL_CLASS_INCLUDED */
diff --git a/sql/sql_client.cc b/sql/sql_client.cc
new file mode 100644
index 00000000000..efac01f9894
--- /dev/null
+++ b/sql/sql_client.cc
@@ -0,0 +1,44 @@
+/* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2012, Monty Program Ab
+
+ 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 */
+
+/*
+ This files defines some MySQL C API functions that are server specific
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_class.h" // system_variables
+
+/*
+ Function called by my_net_init() to set some check variables
+*/
+
+extern "C" {
+void my_net_local_init(NET *net)
+{
+#ifndef EMBEDDED_LIBRARY
+ net->max_packet= (uint) global_system_variables.net_buffer_length;
+ net->read_timeout= net->write_timeout= 0;
+ my_net_set_read_timeout(net, (uint)global_system_variables.net_read_timeout);
+ my_net_set_write_timeout(net,
+ (uint)global_system_variables.net_write_timeout);
+
+ net->retry_count= (uint) global_system_variables.net_retry_count;
+ net->max_packet_size= MY_MAX(global_system_variables.net_buffer_length,
+ global_system_variables.max_allowed_packet);
+#endif
+}
+}
diff --git a/sql/sql_cmd.h b/sql/sql_cmd.h
new file mode 100644
index 00000000000..231db2a1d8c
--- /dev/null
+++ b/sql/sql_cmd.h
@@ -0,0 +1,165 @@
+/* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/**
+ @file Representation of an SQL command.
+*/
+
+#ifndef SQL_CMD_INCLUDED
+#define SQL_CMD_INCLUDED
+
+/*
+ When a command is added here, be sure it's also added in mysqld.cc
+ in "struct show_var_st status_vars[]= {" ...
+
+ If the command returns a result set or is not allowed in stored
+ functions or triggers, please also make sure that
+ sp_get_flags_for_command (sp_head.cc) returns proper flags for the
+ added SQLCOM_.
+*/
+
+enum enum_sql_command {
+ SQLCOM_SELECT, SQLCOM_CREATE_TABLE, SQLCOM_CREATE_INDEX, SQLCOM_ALTER_TABLE,
+ SQLCOM_UPDATE, SQLCOM_INSERT, SQLCOM_INSERT_SELECT,
+ SQLCOM_DELETE, SQLCOM_TRUNCATE, SQLCOM_DROP_TABLE, SQLCOM_DROP_INDEX,
+
+ SQLCOM_SHOW_DATABASES, SQLCOM_SHOW_TABLES, SQLCOM_SHOW_FIELDS,
+ SQLCOM_SHOW_KEYS, SQLCOM_SHOW_VARIABLES, SQLCOM_SHOW_STATUS,
+ SQLCOM_SHOW_ENGINE_LOGS, SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_MUTEX,
+ SQLCOM_SHOW_PROCESSLIST, SQLCOM_SHOW_MASTER_STAT, SQLCOM_SHOW_SLAVE_STAT,
+ SQLCOM_SHOW_GRANTS, SQLCOM_SHOW_CREATE, SQLCOM_SHOW_CHARSETS,
+ SQLCOM_SHOW_COLLATIONS, SQLCOM_SHOW_CREATE_DB, SQLCOM_SHOW_TABLE_STATUS,
+ SQLCOM_SHOW_TRIGGERS,
+
+ SQLCOM_LOAD,SQLCOM_SET_OPTION,SQLCOM_LOCK_TABLES,SQLCOM_UNLOCK_TABLES,
+ SQLCOM_GRANT,
+ SQLCOM_CHANGE_DB, SQLCOM_CREATE_DB, SQLCOM_DROP_DB, SQLCOM_ALTER_DB,
+ SQLCOM_REPAIR, SQLCOM_REPLACE, SQLCOM_REPLACE_SELECT,
+ SQLCOM_CREATE_FUNCTION, SQLCOM_DROP_FUNCTION,
+ SQLCOM_REVOKE,SQLCOM_OPTIMIZE, SQLCOM_CHECK,
+ SQLCOM_ASSIGN_TO_KEYCACHE, SQLCOM_PRELOAD_KEYS,
+ SQLCOM_FLUSH, SQLCOM_KILL, SQLCOM_ANALYZE,
+ SQLCOM_ROLLBACK, SQLCOM_ROLLBACK_TO_SAVEPOINT,
+ SQLCOM_COMMIT, SQLCOM_SAVEPOINT, SQLCOM_RELEASE_SAVEPOINT,
+ SQLCOM_SLAVE_START, SQLCOM_SLAVE_STOP,
+ SQLCOM_BEGIN, SQLCOM_CHANGE_MASTER,
+ SQLCOM_RENAME_TABLE,
+ SQLCOM_RESET, SQLCOM_PURGE, SQLCOM_PURGE_BEFORE, SQLCOM_SHOW_BINLOGS,
+ SQLCOM_SHOW_OPEN_TABLES,
+ SQLCOM_HA_OPEN, SQLCOM_HA_CLOSE, SQLCOM_HA_READ,
+ SQLCOM_SHOW_SLAVE_HOSTS, SQLCOM_DELETE_MULTI, SQLCOM_UPDATE_MULTI,
+ SQLCOM_SHOW_BINLOG_EVENTS, SQLCOM_DO,
+ SQLCOM_SHOW_WARNS, SQLCOM_EMPTY_QUERY, SQLCOM_SHOW_ERRORS,
+ SQLCOM_SHOW_STORAGE_ENGINES, SQLCOM_SHOW_PRIVILEGES,
+ SQLCOM_HELP, SQLCOM_CREATE_USER, SQLCOM_DROP_USER, SQLCOM_RENAME_USER,
+ SQLCOM_REVOKE_ALL, SQLCOM_CHECKSUM,
+ SQLCOM_CREATE_PROCEDURE, SQLCOM_CREATE_SPFUNCTION, SQLCOM_CALL,
+ SQLCOM_DROP_PROCEDURE, SQLCOM_ALTER_PROCEDURE,SQLCOM_ALTER_FUNCTION,
+ SQLCOM_SHOW_CREATE_PROC, SQLCOM_SHOW_CREATE_FUNC,
+ SQLCOM_SHOW_STATUS_PROC, SQLCOM_SHOW_STATUS_FUNC,
+ SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE,
+ SQLCOM_CREATE_VIEW, SQLCOM_DROP_VIEW,
+ SQLCOM_CREATE_TRIGGER, SQLCOM_DROP_TRIGGER,
+ SQLCOM_XA_START, SQLCOM_XA_END, SQLCOM_XA_PREPARE,
+ SQLCOM_XA_COMMIT, SQLCOM_XA_ROLLBACK, SQLCOM_XA_RECOVER,
+ SQLCOM_SHOW_PROC_CODE, SQLCOM_SHOW_FUNC_CODE,
+ SQLCOM_ALTER_TABLESPACE,
+ SQLCOM_INSTALL_PLUGIN, SQLCOM_UNINSTALL_PLUGIN,
+ SQLCOM_SHOW_AUTHORS, SQLCOM_BINLOG_BASE64_EVENT,
+ SQLCOM_SHOW_PLUGINS,
+ SQLCOM_SHOW_CONTRIBUTORS,
+ SQLCOM_CREATE_SERVER, SQLCOM_DROP_SERVER, SQLCOM_ALTER_SERVER,
+ SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT,
+ SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS,
+ SQLCOM_SHOW_CREATE_TRIGGER,
+ SQLCOM_ALTER_DB_UPGRADE,
+ SQLCOM_SHOW_PROFILE, SQLCOM_SHOW_PROFILES,
+ SQLCOM_SIGNAL, SQLCOM_RESIGNAL,
+ SQLCOM_SHOW_RELAYLOG_EVENTS,
+ SQLCOM_GET_DIAGNOSTICS,
+ SQLCOM_SHOW_USER_STATS, SQLCOM_SHOW_TABLE_STATS, SQLCOM_SHOW_INDEX_STATS,
+ SQLCOM_SHOW_CLIENT_STATS,
+ SQLCOM_SLAVE_ALL_START, SQLCOM_SLAVE_ALL_STOP,
+ SQLCOM_SHOW_EXPLAIN, SQLCOM_SHUTDOWN,
+ SQLCOM_CREATE_ROLE, SQLCOM_DROP_ROLE, SQLCOM_GRANT_ROLE, SQLCOM_REVOKE_ROLE,
+
+ /*
+ When a command is added here, be sure it's also added in mysqld.cc
+ in "struct show_var_st status_vars[]= {" ...
+ */
+ /* This should be the last !!! */
+ SQLCOM_END
+};
+
+/**
+ @class Sql_cmd - Representation of an SQL command.
+
+ This class is an interface between the parser and the runtime.
+ The parser builds the appropriate derived classes of Sql_cmd
+ to represent a SQL statement in the parsed tree.
+ The execute() method in the derived classes of Sql_cmd contain the runtime
+ implementation.
+ Note that this interface is used for SQL statements recently implemented,
+ the code for older statements tend to load the LEX structure with more
+ attributes instead.
+ Implement new statements by sub-classing Sql_cmd, as this improves
+ code modularity (see the 'big switch' in dispatch_command()), and decreases
+ the total size of the LEX structure (therefore saving memory in stored
+ programs).
+ The recommended name of a derived class of Sql_cmd is Sql_cmd_<derived>.
+
+ Notice that the Sql_cmd class should not be confused with the
+ Statement class. Statement is a class that is used to manage an SQL
+ command or a set of SQL commands. When the SQL statement text is
+ analyzed, the parser will create one or more Sql_cmd objects to
+ represent the actual SQL commands.
+*/
+class Sql_cmd : public Sql_alloc
+{
+private:
+ Sql_cmd(const Sql_cmd &); // No copy constructor wanted
+ void operator=(Sql_cmd &); // No assignment operator wanted
+
+public:
+ /**
+ @brief Return the command code for this statement
+ */
+ virtual enum_sql_command sql_command_code() const = 0;
+
+ /**
+ Execute this SQL statement.
+ @param thd the current thread.
+ @retval false on success.
+ @retval true on error
+ */
+ virtual bool execute(THD *thd) = 0;
+
+protected:
+ Sql_cmd()
+ {}
+
+ virtual ~Sql_cmd()
+ {
+ /*
+ Sql_cmd objects are allocated in thd->mem_root.
+ In MySQL, the C++ destructor is never called, the underlying MEM_ROOT is
+ simply destroyed instead.
+ Do not rely on the destructor for any cleanup.
+ */
+ DBUG_ASSERT(FALSE);
+ }
+};
+
+#endif // SQL_CMD_INCLUDED
diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc
new file mode 100644
index 00000000000..807b028a4b1
--- /dev/null
+++ b/sql/sql_connect.cc
@@ -0,0 +1,1397 @@
+/*
+ Copyright (c) 2007, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2014, SkySQL Ab.
+
+ 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
+*/
+
+/*
+ Functions to autenticate and handle reqests for a connection
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#ifndef __WIN__
+#include <netdb.h> // getservbyname, servent
+#endif
+#include "sql_audit.h"
+#include "sql_connect.h"
+#include "probes_mysql.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_parse.h" // sql_command_flags,
+ // execute_init_command,
+ // do_command
+#include "sql_db.h" // mysql_change_db
+#include "hostname.h" // inc_host_errors, ip_to_hostname,
+ // reset_host_errors
+#include "sql_acl.h" // acl_getroot, NO_ACCESS, SUPER_ACL
+#include "sql_callback.h"
+
+HASH global_user_stats, global_client_stats, global_table_stats;
+HASH global_index_stats;
+/* Protects the above global stats */
+extern mysql_mutex_t LOCK_global_user_client_stats;
+extern mysql_mutex_t LOCK_global_table_stats;
+extern mysql_mutex_t LOCK_global_index_stats;
+
+/*
+ Get structure for logging connection data for the current user
+*/
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+static HASH hash_user_connections;
+
+int get_or_create_user_conn(THD *thd, const char *user,
+ const char *host,
+ const USER_RESOURCES *mqh)
+{
+ int return_val= 0;
+ size_t temp_len, user_len;
+ char temp_user[USER_HOST_BUFF_SIZE];
+ struct user_conn *uc;
+
+ DBUG_ASSERT(user != 0);
+ DBUG_ASSERT(host != 0);
+ DBUG_ASSERT(thd->user_connect == 0);
+
+ user_len= strlen(user);
+ temp_len= (strmov(strmov(temp_user, user)+1, host) - temp_user)+1;
+ mysql_mutex_lock(&LOCK_user_conn);
+ if (!(uc = (struct user_conn *) my_hash_search(&hash_user_connections,
+ (uchar*) temp_user, temp_len)))
+ {
+ /* First connection for user; Create a user connection object */
+ if (!(uc= ((struct user_conn*)
+ my_malloc(sizeof(struct user_conn) + temp_len+1,
+ MYF(MY_WME)))))
+ {
+ /* MY_WME ensures an error is set in THD. */
+ return_val= 1;
+ goto end;
+ }
+ uc->user=(char*) (uc+1);
+ memcpy(uc->user,temp_user,temp_len+1);
+ uc->host= uc->user + user_len + 1;
+ uc->len= temp_len;
+ uc->connections= uc->questions= uc->updates= uc->conn_per_hour= 0;
+ uc->user_resources= *mqh;
+ uc->reset_utime= thd->thr_create_utime;
+ if (my_hash_insert(&hash_user_connections, (uchar*) uc))
+ {
+ /* The only possible error is out of memory, MY_WME sets an error. */
+ my_free(uc);
+ return_val= 1;
+ goto end;
+ }
+ }
+ thd->user_connect=uc;
+ uc->connections++;
+end:
+ mysql_mutex_unlock(&LOCK_user_conn);
+ return return_val;
+
+}
+
+
+/*
+ check if user has already too many connections
+
+ SYNOPSIS
+ check_for_max_user_connections()
+ thd Thread handle
+ uc User connect object
+
+ NOTES
+ If check fails, we decrease user connection count, which means one
+ shouldn't call decrease_user_connections() after this function.
+
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+int check_for_max_user_connections(THD *thd, USER_CONN *uc)
+{
+ int error= 1;
+ Host_errors errors;
+ DBUG_ENTER("check_for_max_user_connections");
+
+ mysql_mutex_lock(&LOCK_user_conn);
+
+ /* Root is not affected by the value of max_user_connections */
+ if (global_system_variables.max_user_connections &&
+ !uc->user_resources.user_conn &&
+ global_system_variables.max_user_connections < uc->connections &&
+ !(thd->security_ctx->master_access & SUPER_ACL))
+ {
+ my_error(ER_TOO_MANY_USER_CONNECTIONS, MYF(0), uc->user);
+ error=1;
+ errors.m_max_user_connection= 1;
+ goto end;
+ }
+ time_out_user_resource_limits(thd, uc);
+ if (uc->user_resources.user_conn &&
+ uc->user_resources.user_conn < uc->connections)
+ {
+ my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user,
+ "max_user_connections",
+ (long) uc->user_resources.user_conn);
+ error= 1;
+ errors.m_max_user_connection= 1;
+ goto end;
+ }
+ if (uc->user_resources.conn_per_hour &&
+ uc->user_resources.conn_per_hour <= uc->conn_per_hour)
+ {
+ my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user,
+ "max_connections_per_hour",
+ (long) uc->user_resources.conn_per_hour);
+ error=1;
+ errors.m_max_user_connection_per_hour= 1;
+ goto end;
+ }
+ uc->conn_per_hour++;
+ error= 0;
+
+end:
+ if (error)
+ {
+ uc->connections--; // no need for decrease_user_connections() here
+ /*
+ The thread may returned back to the pool and assigned to a user
+ that doesn't have a limit. Ensure the user is not using resources
+ of someone else.
+ */
+ thd->user_connect= NULL;
+ }
+ mysql_mutex_unlock(&LOCK_user_conn);
+ if (error)
+ {
+ inc_host_errors(thd->main_security_ctx.ip, &errors);
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Decrease user connection count
+
+ SYNOPSIS
+ decrease_user_connections()
+ uc User connection object
+
+ NOTES
+ If there is a n user connection object for a connection
+ (which only happens if 'max_user_connections' is defined or
+ if someone has created a resource grant for a user), then
+ the connection count is always incremented on connect.
+
+ The user connect object is not freed if some users has
+ 'max connections per hour' defined as we need to be able to hold
+ count over the lifetime of the connection.
+*/
+
+void decrease_user_connections(USER_CONN *uc)
+{
+ DBUG_ENTER("decrease_user_connections");
+ mysql_mutex_lock(&LOCK_user_conn);
+ DBUG_ASSERT(uc->connections);
+ if (!--uc->connections && !mqh_used)
+ {
+ /* Last connection for user; Delete it */
+ (void) my_hash_delete(&hash_user_connections,(uchar*) uc);
+ }
+ mysql_mutex_unlock(&LOCK_user_conn);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Reset per-hour user resource limits when it has been more than
+ an hour since they were last checked
+
+ SYNOPSIS:
+ time_out_user_resource_limits()
+ thd Thread handler
+ uc User connection details
+
+ NOTE:
+ This assumes that the LOCK_user_conn mutex has been acquired, so it is
+ safe to test and modify members of the USER_CONN structure.
+*/
+
+void time_out_user_resource_limits(THD *thd, USER_CONN *uc)
+{
+ ulonglong check_time= thd->start_utime;
+ DBUG_ENTER("time_out_user_resource_limits");
+
+ /* If more than a hour since last check, reset resource checking */
+ if (check_time - uc->reset_utime >= 3600000000ULL)
+ {
+ uc->questions=0;
+ uc->updates=0;
+ uc->conn_per_hour=0;
+ uc->reset_utime= check_time;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Check if maximum queries per hour limit has been reached
+ returns 0 if OK.
+*/
+
+bool check_mqh(THD *thd, uint check_command)
+{
+ bool error= 0;
+ USER_CONN *uc=thd->user_connect;
+ DBUG_ENTER("check_mqh");
+ DBUG_ASSERT(uc != 0);
+
+ mysql_mutex_lock(&LOCK_user_conn);
+
+ time_out_user_resource_limits(thd, uc);
+
+ /* Check that we have not done too many questions / hour */
+ if (uc->user_resources.questions &&
+ uc->questions++ >= uc->user_resources.questions)
+ {
+ my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_queries_per_hour",
+ (long) uc->user_resources.questions);
+ error=1;
+ goto end;
+ }
+ if (check_command < (uint) SQLCOM_END)
+ {
+ /* Check that we have not done too many updates / hour */
+ if (uc->user_resources.updates &&
+ (sql_command_flags[check_command] & CF_CHANGES_DATA) &&
+ uc->updates++ >= uc->user_resources.updates)
+ {
+ my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_updates_per_hour",
+ (long) uc->user_resources.updates);
+ error=1;
+ goto end;
+ }
+ }
+end:
+ mysql_mutex_unlock(&LOCK_user_conn);
+ DBUG_RETURN(error);
+}
+
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+
+/*
+ Check for maximum allowable user connections, if the mysqld server is
+ started with corresponding variable that is greater then 0.
+*/
+
+extern "C" uchar *get_key_conn(user_conn *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= buff->len;
+ return (uchar*) buff->user;
+}
+
+
+extern "C" void free_user(struct user_conn *uc)
+{
+ my_free(uc);
+}
+
+
+void init_max_user_conn(void)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (my_hash_init(&hash_user_connections,system_charset_info,max_connections,
+ 0,0, (my_hash_get_key) get_key_conn,
+ (my_hash_free_key) free_user, 0))
+ {
+ sql_print_error("Initializing hash_user_connections failed.");
+ exit(1);
+ }
+#endif
+}
+
+
+void free_max_user_conn(void)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ my_hash_free(&hash_user_connections);
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+}
+
+
+void reset_mqh(LEX_USER *lu, bool get_them= 0)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ mysql_mutex_lock(&LOCK_user_conn);
+ if (lu) // for GRANT
+ {
+ USER_CONN *uc;
+ uint temp_len=lu->user.length+lu->host.length+2;
+ char temp_user[USER_HOST_BUFF_SIZE];
+
+ memcpy(temp_user,lu->user.str,lu->user.length);
+ memcpy(temp_user+lu->user.length+1,lu->host.str,lu->host.length);
+ temp_user[lu->user.length]='\0'; temp_user[temp_len-1]=0;
+ if ((uc = (struct user_conn *) my_hash_search(&hash_user_connections,
+ (uchar*) temp_user,
+ temp_len)))
+ {
+ uc->questions=0;
+ get_mqh(temp_user,&temp_user[lu->user.length+1],uc);
+ uc->updates=0;
+ uc->conn_per_hour=0;
+ }
+ }
+ else
+ {
+ /* for FLUSH PRIVILEGES and FLUSH USER_RESOURCES */
+ for (uint idx=0;idx < hash_user_connections.records; idx++)
+ {
+ USER_CONN *uc=(struct user_conn *)
+ my_hash_element(&hash_user_connections, idx);
+ if (get_them)
+ get_mqh(uc->user,uc->host,uc);
+ uc->questions=0;
+ uc->updates=0;
+ uc->conn_per_hour=0;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_user_conn);
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+}
+
+/*****************************************************************************
+ Handle users statistics
+*****************************************************************************/
+
+/* 'mysql_system_user' is used for when the user is not defined for a THD. */
+static const char mysql_system_user[]= "#mysql_system#";
+
+// Returns 'user' if it's not NULL. Returns 'mysql_system_user' otherwise.
+static const char * get_valid_user_string(char* user)
+{
+ return user ? user : mysql_system_user;
+}
+
+/*
+ Returns string as 'IP' for the client-side of the connection represented by
+ 'client'. Does not allocate memory. May return "".
+*/
+
+static const char *get_client_host(THD *client)
+{
+ return client->security_ctx->host_or_ip[0] ?
+ client->security_ctx->host_or_ip :
+ client->security_ctx->host ? client->security_ctx->host : "";
+}
+
+extern "C" uchar *get_key_user_stats(USER_STATS *user_stats, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= user_stats->user_name_length;
+ return (uchar*) user_stats->user;
+}
+
+void free_user_stats(USER_STATS* user_stats)
+{
+ my_free(user_stats);
+}
+
+void init_user_stats(USER_STATS *user_stats,
+ const char *user,
+ size_t user_length,
+ const char *priv_user,
+ uint total_connections,
+ uint concurrent_connections,
+ time_t connected_time,
+ double busy_time,
+ double cpu_time,
+ ulonglong bytes_received,
+ ulonglong bytes_sent,
+ ulonglong binlog_bytes_written,
+ ha_rows rows_sent,
+ ha_rows rows_read,
+ ha_rows rows_inserted,
+ ha_rows rows_deleted,
+ ha_rows rows_updated,
+ ulonglong select_commands,
+ ulonglong update_commands,
+ ulonglong other_commands,
+ ulonglong commit_trans,
+ ulonglong rollback_trans,
+ ulonglong denied_connections,
+ ulonglong lost_connections,
+ ulonglong access_denied_errors,
+ ulonglong empty_queries)
+{
+ DBUG_ENTER("init_user_stats");
+ DBUG_PRINT("enter", ("user: %s priv_user: %s", user, priv_user));
+
+ user_length= MY_MIN(user_length, sizeof(user_stats->user)-1);
+ memcpy(user_stats->user, user, user_length);
+ user_stats->user[user_length]= 0;
+ user_stats->user_name_length= user_length;
+ strmake_buf(user_stats->priv_user, priv_user);
+
+ user_stats->total_connections= total_connections;
+ user_stats->concurrent_connections= concurrent_connections;
+ user_stats->connected_time= connected_time;
+ user_stats->busy_time= busy_time;
+ user_stats->cpu_time= cpu_time;
+ user_stats->bytes_received= bytes_received;
+ user_stats->bytes_sent= bytes_sent;
+ user_stats->binlog_bytes_written= binlog_bytes_written;
+ user_stats->rows_sent= rows_sent;
+ user_stats->rows_updated= rows_updated;
+ user_stats->rows_read= rows_read;
+ user_stats->select_commands= select_commands;
+ user_stats->update_commands= update_commands;
+ user_stats->other_commands= other_commands;
+ user_stats->commit_trans= commit_trans;
+ user_stats->rollback_trans= rollback_trans;
+ user_stats->denied_connections= denied_connections;
+ user_stats->lost_connections= lost_connections;
+ user_stats->access_denied_errors= access_denied_errors;
+ user_stats->empty_queries= empty_queries;
+ DBUG_VOID_RETURN;
+}
+
+
+#ifdef COMPLETE_PATCH_NOT_ADDED_YET
+
+void add_user_stats(USER_STATS *user_stats,
+ uint total_connections,
+ uint concurrent_connections,
+ time_t connected_time,
+ double busy_time,
+ double cpu_time,
+ ulonglong bytes_received,
+ ulonglong bytes_sent,
+ ulonglong binlog_bytes_written,
+ ha_rows rows_sent,
+ ha_rows rows_read,
+ ha_rows rows_inserted,
+ ha_rows rows_deleted,
+ ha_rows rows_updated,
+ ulonglong select_commands,
+ ulonglong update_commands,
+ ulonglong other_commands,
+ ulonglong commit_trans,
+ ulonglong rollback_trans,
+ ulonglong denied_connections,
+ ulonglong lost_connections,
+ ulonglong access_denied_errors,
+ ulonglong empty_queries)
+{
+ user_stats->total_connections+= total_connections;
+ user_stats->concurrent_connections+= concurrent_connections;
+ user_stats->connected_time+= connected_time;
+ user_stats->busy_time+= busy_time;
+ user_stats->cpu_time+= cpu_time;
+ user_stats->bytes_received+= bytes_received;
+ user_stats->bytes_sent+= bytes_sent;
+ user_stats->binlog_bytes_written+= binlog_bytes_written;
+ user_stats->rows_sent+= rows_sent;
+ user_stats->rows_inserted+= rows_inserted;
+ user_stats->rows_deleted+= rows_deleted;
+ user_stats->rows_updated+= rows_updated;
+ user_stats->rows_read+= rows_read;
+ user_stats->select_commands+= select_commands;
+ user_stats->update_commands+= update_commands;
+ user_stats->other_commands+= other_commands;
+ user_stats->commit_trans+= commit_trans;
+ user_stats->rollback_trans+= rollback_trans;
+ user_stats->denied_connections+= denied_connections;
+ user_stats->lost_connections+= lost_connections;
+ user_stats->access_denied_errors+= access_denied_errors;
+ user_stats->empty_queries+= empty_queries;
+}
+#endif
+
+
+void init_global_user_stats(void)
+{
+ if (my_hash_init(&global_user_stats, system_charset_info, max_connections,
+ 0, 0, (my_hash_get_key) get_key_user_stats,
+ (my_hash_free_key)free_user_stats, 0))
+ {
+ sql_print_error("Initializing global_user_stats failed.");
+ exit(1);
+ }
+}
+
+void init_global_client_stats(void)
+{
+ if (my_hash_init(&global_client_stats, system_charset_info, max_connections,
+ 0, 0, (my_hash_get_key) get_key_user_stats,
+ (my_hash_free_key)free_user_stats, 0))
+ {
+ sql_print_error("Initializing global_client_stats failed.");
+ exit(1);
+ }
+}
+
+extern "C" uchar *get_key_table_stats(TABLE_STATS *table_stats, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= table_stats->table_name_length;
+ return (uchar*) table_stats->table;
+}
+
+extern "C" void free_table_stats(TABLE_STATS* table_stats)
+{
+ my_free(table_stats);
+}
+
+void init_global_table_stats(void)
+{
+ if (my_hash_init(&global_table_stats, system_charset_info, max_connections,
+ 0, 0, (my_hash_get_key) get_key_table_stats,
+ (my_hash_free_key)free_table_stats, 0)) {
+ sql_print_error("Initializing global_table_stats failed.");
+ exit(1);
+ }
+}
+
+extern "C" uchar *get_key_index_stats(INDEX_STATS *index_stats, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= index_stats->index_name_length;
+ return (uchar*) index_stats->index;
+}
+
+extern "C" void free_index_stats(INDEX_STATS* index_stats)
+{
+ my_free(index_stats);
+}
+
+void init_global_index_stats(void)
+{
+ if (my_hash_init(&global_index_stats, system_charset_info, max_connections,
+ 0, 0, (my_hash_get_key) get_key_index_stats,
+ (my_hash_free_key)free_index_stats, 0))
+ {
+ sql_print_error("Initializing global_index_stats failed.");
+ exit(1);
+ }
+}
+
+
+void free_global_user_stats(void)
+{
+ my_hash_free(&global_user_stats);
+}
+
+void free_global_table_stats(void)
+{
+ my_hash_free(&global_table_stats);
+}
+
+void free_global_index_stats(void)
+{
+ my_hash_free(&global_index_stats);
+}
+
+void free_global_client_stats(void)
+{
+ my_hash_free(&global_client_stats);
+}
+
+/*
+ Increments the global stats connection count for an entry from
+ global_client_stats or global_user_stats. Returns 0 on success
+ and 1 on error.
+*/
+
+static bool increment_count_by_name(const char *name, size_t name_length,
+ const char *role_name,
+ HASH *users_or_clients, THD *thd)
+{
+ USER_STATS *user_stats;
+
+ if (!(user_stats= (USER_STATS*) my_hash_search(users_or_clients, (uchar*) name,
+ name_length)))
+ {
+ /* First connection for this user or client */
+ if (!(user_stats= ((USER_STATS*)
+ my_malloc(sizeof(USER_STATS),
+ MYF(MY_WME | MY_ZEROFILL)))))
+ return TRUE; // Out of memory
+
+ init_user_stats(user_stats, name, name_length, role_name,
+ 0, 0, // connections
+ 0, 0, 0, // time
+ 0, 0, 0, // bytes sent, received and written
+ 0, 0, // Rows sent and read
+ 0, 0, 0, // rows inserted, deleted and updated
+ 0, 0, 0, // select, update and other commands
+ 0, 0, // commit and rollback trans
+ thd->status_var.access_denied_errors,
+ 0, // lost connections
+ 0, // access denied errors
+ 0); // empty queries
+
+ if (my_hash_insert(users_or_clients, (uchar*)user_stats))
+ {
+ my_free(user_stats);
+ return TRUE; // Out of memory
+ }
+ }
+ user_stats->total_connections++;
+ return FALSE;
+}
+
+
+/*
+ Increments the global user and client stats connection count.
+
+ @param use_lock if true, LOCK_global_user_client_stats will be locked
+
+ @retval 0 ok
+ @retval 1 error.
+*/
+
+#ifndef EMBEDDED_LIBRARY
+static bool increment_connection_count(THD* thd, bool use_lock)
+{
+ const char *user_string= get_valid_user_string(thd->main_security_ctx.user);
+ const char *client_string= get_client_host(thd);
+ bool return_value= FALSE;
+
+ if (!thd->userstat_running)
+ return FALSE;
+
+ if (use_lock)
+ mysql_mutex_lock(&LOCK_global_user_client_stats);
+
+ if (increment_count_by_name(user_string, strlen(user_string), user_string,
+ &global_user_stats, thd))
+ {
+ return_value= TRUE;
+ goto end;
+ }
+ if (increment_count_by_name(client_string, strlen(client_string),
+ user_string, &global_client_stats, thd))
+ {
+ return_value= TRUE;
+ goto end;
+ }
+
+end:
+ if (use_lock)
+ mysql_mutex_unlock(&LOCK_global_user_client_stats);
+ return return_value;
+}
+#endif
+
+/*
+ Used to update the global user and client stats
+*/
+
+static void update_global_user_stats_with_user(THD *thd,
+ USER_STATS *user_stats,
+ time_t now)
+{
+ DBUG_ASSERT(thd->userstat_running);
+
+ user_stats->connected_time+= now - thd->last_global_update_time;
+ user_stats->busy_time+= (thd->status_var.busy_time -
+ thd->org_status_var.busy_time);
+ user_stats->cpu_time+= (thd->status_var.cpu_time -
+ thd->org_status_var.cpu_time);
+ /*
+ This is handle specially as bytes_received is incremented BEFORE
+ org_status_var is copied.
+ */
+ user_stats->bytes_received+= (thd->org_status_var.bytes_received-
+ thd->start_bytes_received);
+ user_stats->bytes_sent+= (thd->status_var.bytes_sent -
+ thd->org_status_var.bytes_sent);
+ user_stats->binlog_bytes_written+=
+ (thd->status_var.binlog_bytes_written -
+ thd->org_status_var.binlog_bytes_written);
+ /* We are not counting rows in internal temporary tables here ! */
+ user_stats->rows_read+= (thd->status_var.rows_read -
+ thd->org_status_var.rows_read);
+ user_stats->rows_sent+= (thd->status_var.rows_sent -
+ thd->org_status_var.rows_sent);
+ user_stats->rows_inserted+= (thd->status_var.ha_write_count -
+ thd->org_status_var.ha_write_count);
+ user_stats->rows_deleted+= (thd->status_var.ha_delete_count -
+ thd->org_status_var.ha_delete_count);
+ user_stats->rows_updated+= (thd->status_var.ha_update_count -
+ thd->org_status_var.ha_update_count);
+ user_stats->select_commands+= thd->select_commands;
+ user_stats->update_commands+= thd->update_commands;
+ user_stats->other_commands+= thd->other_commands;
+ user_stats->commit_trans+= (thd->status_var.ha_commit_count -
+ thd->org_status_var.ha_commit_count);
+ user_stats->rollback_trans+= (thd->status_var.ha_rollback_count +
+ thd->status_var.ha_savepoint_rollback_count -
+ thd->org_status_var.ha_rollback_count -
+ thd->org_status_var.
+ ha_savepoint_rollback_count);
+ user_stats->access_denied_errors+=
+ (thd->status_var.access_denied_errors -
+ thd->org_status_var.access_denied_errors);
+ user_stats->empty_queries+= (thd->status_var.empty_queries -
+ thd->org_status_var.empty_queries);
+
+ /* The following can only contain 0 or 1 and then connection ends */
+ user_stats->denied_connections+= thd->status_var.access_denied_errors;
+ user_stats->lost_connections+= thd->status_var.lost_connections;
+}
+
+
+/* Updates the global stats of a user or client */
+void update_global_user_stats(THD *thd, bool create_user, time_t now)
+{
+ const char *user_string, *client_string;
+ USER_STATS *user_stats;
+ size_t user_string_length, client_string_length;
+ DBUG_ASSERT(thd->userstat_running);
+
+ user_string= get_valid_user_string(thd->main_security_ctx.user);
+ user_string_length= strlen(user_string);
+ client_string= get_client_host(thd);
+ client_string_length= strlen(client_string);
+
+ mysql_mutex_lock(&LOCK_global_user_client_stats);
+
+ // Update by user name
+ if ((user_stats= (USER_STATS*) my_hash_search(&global_user_stats,
+ (uchar*) user_string,
+ user_string_length)))
+ {
+ /* Found user. */
+ update_global_user_stats_with_user(thd, user_stats, now);
+ }
+ else
+ {
+ /* Create the entry */
+ if (create_user)
+ {
+ increment_count_by_name(user_string, user_string_length, user_string,
+ &global_user_stats, thd);
+ }
+ }
+
+ /* Update by client IP */
+ if ((user_stats= (USER_STATS*)my_hash_search(&global_client_stats,
+ (uchar*) client_string,
+ client_string_length)))
+ {
+ // Found by client IP
+ update_global_user_stats_with_user(thd, user_stats, now);
+ }
+ else
+ {
+ // Create the entry
+ if (create_user)
+ {
+ increment_count_by_name(client_string, client_string_length,
+ user_string, &global_client_stats, thd);
+ }
+ }
+ /* Reset variables only used for counting */
+ thd->select_commands= thd->update_commands= thd->other_commands= 0;
+ thd->last_global_update_time= now;
+
+ mysql_mutex_unlock(&LOCK_global_user_client_stats);
+}
+
+
+/**
+ Set thread character set variables from the given ID
+
+ @param thd thread handle
+ @param cs_number character set and collation ID
+
+ @retval 0 OK; character_set_client, collation_connection and
+ character_set_results are set to the new value,
+ or to the default global values.
+
+ @retval 1 error, e.g. the given ID is not supported by parser.
+ Corresponding SQL error is sent.
+*/
+
+bool thd_init_client_charset(THD *thd, uint cs_number)
+{
+ CHARSET_INFO *cs;
+ /*
+ Use server character set and collation if
+ - opt_character_set_client_handshake is not set
+ - client has not specified a character set
+ - client character set doesn't exists in server
+ */
+ if (!opt_character_set_client_handshake ||
+ !(cs= get_charset(cs_number, MYF(0))))
+ {
+ thd->variables.character_set_client=
+ global_system_variables.character_set_client;
+ thd->variables.collation_connection=
+ global_system_variables.collation_connection;
+ thd->variables.character_set_results=
+ global_system_variables.character_set_results;
+ }
+ else
+ {
+ if (!is_supported_parser_charset(cs))
+ {
+ /* Disallow non-supported parser character sets: UCS2, UTF16, UTF32 */
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "character_set_client",
+ cs->csname);
+ return true;
+ }
+ thd->variables.character_set_results=
+ thd->variables.collation_connection=
+ thd->variables.character_set_client= cs;
+ }
+ return false;
+}
+
+
+/*
+ Initialize connection threads
+*/
+
+bool init_new_connection_handler_thread()
+{
+ pthread_detach_this_thread();
+ if (my_thread_init())
+ {
+ statistic_increment(connection_errors_internal, &LOCK_status);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ Perform handshake, authorize client and update thd ACL variables.
+
+ SYNOPSIS
+ check_connection()
+ thd thread handle
+
+ RETURN
+ 0 success, thd is updated.
+ 1 error
+*/
+
+#ifndef EMBEDDED_LIBRARY
+static int check_connection(THD *thd)
+{
+ uint connect_errors= 0;
+ int auth_rc;
+ NET *net= &thd->net;
+
+ DBUG_PRINT("info",
+ ("New connection received on %s", vio_description(net->vio)));
+
+#ifdef SIGNAL_WITH_VIO_CLOSE
+ thd->set_active_vio(net->vio);
+#endif
+
+ if (!thd->main_security_ctx.host) // If TCP/IP connection
+ {
+ my_bool peer_rc;
+ char ip[NI_MAXHOST];
+
+ peer_rc= vio_peer_addr(net->vio, ip, &thd->peer_port, NI_MAXHOST);
+
+ /*
+ ===========================================================================
+ DEBUG code only (begin)
+ Simulate various output from vio_peer_addr().
+ ===========================================================================
+ */
+
+ DBUG_EXECUTE_IF("vio_peer_addr_error",
+ {
+ peer_rc= 1;
+ }
+ );
+ DBUG_EXECUTE_IF("vio_peer_addr_fake_ipv4",
+ {
+ struct sockaddr *sa= (sockaddr *) &net->vio->remote;
+ sa->sa_family= AF_INET;
+ struct in_addr *ip4= &((struct sockaddr_in *) sa)->sin_addr;
+ /* See RFC 5737, 192.0.2.0/24 is reserved. */
+ const char* fake= "192.0.2.4";
+ ip4->s_addr= inet_addr(fake);
+ strcpy(ip, fake);
+ peer_rc= 0;
+ }
+ );
+
+#ifdef HAVE_IPV6
+ DBUG_EXECUTE_IF("vio_peer_addr_fake_ipv6",
+ {
+ struct sockaddr_in6 *sa= (sockaddr_in6 *) &net->vio->remote;
+ sa->sin6_family= AF_INET6;
+ struct in6_addr *ip6= & sa->sin6_addr;
+ /* See RFC 3849, ipv6 2001:DB8::/32 is reserved. */
+ const char* fake= "2001:db8::6:6";
+ /* inet_pton(AF_INET6, fake, ip6); not available on Windows XP. */
+ ip6->s6_addr[ 0] = 0x20;
+ ip6->s6_addr[ 1] = 0x01;
+ ip6->s6_addr[ 2] = 0x0d;
+ ip6->s6_addr[ 3] = 0xb8;
+ ip6->s6_addr[ 4] = 0x00;
+ ip6->s6_addr[ 5] = 0x00;
+ ip6->s6_addr[ 6] = 0x00;
+ ip6->s6_addr[ 7] = 0x00;
+ ip6->s6_addr[ 8] = 0x00;
+ ip6->s6_addr[ 9] = 0x00;
+ ip6->s6_addr[10] = 0x00;
+ ip6->s6_addr[11] = 0x00;
+ ip6->s6_addr[12] = 0x00;
+ ip6->s6_addr[13] = 0x06;
+ ip6->s6_addr[14] = 0x00;
+ ip6->s6_addr[15] = 0x06;
+ strcpy(ip, fake);
+ peer_rc= 0;
+ }
+ );
+#endif /* HAVE_IPV6 */
+
+ /*
+ ===========================================================================
+ DEBUG code only (end)
+ ===========================================================================
+ */
+
+ if (peer_rc)
+ {
+ /*
+ Since we can not even get the peer IP address,
+ there is nothing to show in the host_cache,
+ so increment the global status variable for peer address errors.
+ */
+ statistic_increment(connection_errors_peer_addr, &LOCK_status);
+ my_error(ER_BAD_HOST_ERROR, MYF(0));
+ return 1;
+ }
+ if (!(thd->main_security_ctx.ip= my_strdup(ip,MYF(MY_WME))))
+ {
+ /*
+ No error accounting per IP in host_cache,
+ this is treated as a global server OOM error.
+ TODO: remove the need for my_strdup.
+ */
+ statistic_increment(connection_errors_internal, &LOCK_status);
+ return 1; /* The error is set by my_strdup(). */
+ }
+ thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip;
+ if (!(specialflag & SPECIAL_NO_RESOLVE))
+ {
+ int rc;
+
+ rc= ip_to_hostname(&net->vio->remote,
+ thd->main_security_ctx.ip,
+ &thd->main_security_ctx.host,
+ &connect_errors);
+
+ /* Cut very long hostnames to avoid possible overflows */
+ if (thd->main_security_ctx.host)
+ {
+ if (thd->main_security_ctx.host != my_localhost)
+ thd->main_security_ctx.host[MY_MIN(strlen(thd->main_security_ctx.host),
+ HOSTNAME_LENGTH)]= 0;
+ thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host;
+ }
+
+ if (rc == RC_BLOCKED_HOST)
+ {
+ /* HOST_CACHE stats updated by ip_to_hostname(). */
+ my_error(ER_HOST_IS_BLOCKED, MYF(0), thd->main_security_ctx.host_or_ip);
+ return 1;
+ }
+ }
+ DBUG_PRINT("info",("Host: %s ip: %s",
+ (thd->main_security_ctx.host ?
+ thd->main_security_ctx.host : "unknown host"),
+ (thd->main_security_ctx.ip ?
+ thd->main_security_ctx.ip : "unknown ip")));
+ if (acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip))
+ {
+ /* HOST_CACHE stats updated by acl_check_host(). */
+ my_error(ER_HOST_NOT_PRIVILEGED, MYF(0),
+ thd->main_security_ctx.host_or_ip);
+ return 1;
+ }
+ }
+ else /* Hostname given means that the connection was on a socket */
+ {
+ DBUG_PRINT("info",("Host: %s", thd->main_security_ctx.host));
+ thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host;
+ thd->main_security_ctx.ip= 0;
+ /* Reset sin_addr */
+ bzero((char*) &net->vio->remote, sizeof(net->vio->remote));
+ }
+ vio_keepalive(net->vio, TRUE);
+
+ if (thd->packet.alloc(thd->variables.net_buffer_length))
+ {
+ /*
+ Important note:
+ net_buffer_length is a SESSION variable,
+ so it may be tempting to account OOM conditions per IP in the HOST_CACHE,
+ in case some clients are more demanding than others ...
+ However, this session variable is *not* initialized with a per client
+ value during the initial connection, it is initialized from the
+ GLOBAL net_buffer_length variable from the server.
+ Hence, there is no reason to account on OOM conditions per client IP,
+ we count failures in the global server status instead.
+ */
+ statistic_increment(connection_errors_internal, &LOCK_status);
+ return 1; /* The error is set by alloc(). */
+ }
+
+ auth_rc= acl_authenticate(thd, 0);
+ if (auth_rc == 0 && connect_errors != 0)
+ {
+ /*
+ A client connection from this IP was successful,
+ after some previous failures.
+ Reset the connection error counter.
+ */
+ reset_host_connect_errors(thd->main_security_ctx.ip);
+ }
+
+ return auth_rc;
+}
+
+
+/*
+ Setup thread to be used with the current thread
+
+ SYNOPSIS
+ bool setup_connection_thread_globals()
+ thd Thread/connection handler
+
+ RETURN
+ 0 ok
+ 1 Error (out of memory)
+ In this case we will close the connection and increment status
+*/
+
+bool setup_connection_thread_globals(THD *thd)
+{
+ if (thd->store_globals())
+ {
+ close_connection(thd, ER_OUT_OF_RESOURCES);
+ statistic_increment(aborted_connects,&LOCK_status);
+ MYSQL_CALLBACK(thd->scheduler, end_thread, (thd, 0));
+ return 1; // Error
+ }
+ return 0;
+}
+
+
+/*
+ Autenticate user, with error reporting
+
+ SYNOPSIS
+ login_connection()
+ thd Thread handler
+
+ NOTES
+ Connection is not closed in case of errors
+
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+bool login_connection(THD *thd)
+{
+ NET *net= &thd->net;
+ int error;
+ DBUG_ENTER("login_connection");
+ DBUG_PRINT("info", ("login_connection called by thread %lu",
+ thd->thread_id));
+
+ /* Use "connect_timeout" value during connection phase */
+ my_net_set_read_timeout(net, connect_timeout);
+ my_net_set_write_timeout(net, connect_timeout);
+
+ error= check_connection(thd);
+ thd->protocol->end_statement();
+
+ if (error)
+ { // Wrong permissions
+#ifdef _WIN32
+ if (vio_type(net->vio) == VIO_TYPE_NAMEDPIPE)
+ my_sleep(1000); /* must wait after eof() */
+#endif
+ statistic_increment(aborted_connects,&LOCK_status);
+ DBUG_RETURN(1);
+ }
+ /* Connect completed, set read/write timeouts back to default */
+ my_net_set_read_timeout(net, thd->variables.net_read_timeout);
+ my_net_set_write_timeout(net, thd->variables.net_write_timeout);
+
+ /* Updates global user connection stats. */
+ if (increment_connection_count(thd, TRUE))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), 2*sizeof(USER_STATS));
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Close an established connection
+
+ NOTES
+ This mainly updates status variables
+*/
+
+void end_connection(THD *thd)
+{
+ NET *net= &thd->net;
+ plugin_thdvar_cleanup(thd);
+
+ if (thd->user_connect)
+ {
+ /*
+ We decrease this variable early to make it easy to log again quickly.
+ This code is not critical as we will in any case do this test
+ again in thd->cleanup()
+ */
+ decrease_user_connections(thd->user_connect);
+ /*
+ The thread may returned back to the pool and assigned to a user
+ that doesn't have a limit. Ensure the user is not using resources
+ of someone else.
+ */
+ thd->user_connect= NULL;
+ }
+
+ if (thd->killed || (net->error && net->vio != 0))
+ {
+ statistic_increment(aborted_threads,&LOCK_status);
+ status_var_increment(thd->status_var.lost_connections);
+ }
+
+ if (!thd->killed && (net->error && net->vio != 0))
+ thd->print_aborted_warning(1, ER(ER_UNKNOWN_ERROR));
+}
+
+
+/*
+ Initialize THD to handle queries
+*/
+
+void prepare_new_connection_state(THD* thd)
+{
+ Security_context *sctx= thd->security_ctx;
+
+ if (thd->client_capabilities & CLIENT_COMPRESS)
+ thd->net.compress=1; // Use compression
+
+ /*
+ Much of this is duplicated in create_embedded_thd() for the
+ embedded server library.
+ TODO: refactor this to avoid code duplication there
+ */
+ thd->proc_info= 0;
+ thd->set_command(COM_SLEEP);
+ thd->set_time();
+ thd->init_for_queries();
+
+ if (opt_init_connect.length && !(sctx->master_access & SUPER_ACL))
+ {
+ execute_init_command(thd, &opt_init_connect, &LOCK_sys_init_connect);
+ if (thd->is_error())
+ {
+ Host_errors errors;
+ thd->killed= KILL_CONNECTION;
+ thd->print_aborted_warning(0, "init_connect command failed");
+ sql_print_warning("%s", thd->get_stmt_da()->message());
+
+ /*
+ now let client to send its first command,
+ to be able to send the error back
+ */
+ NET *net= &thd->net;
+ thd->lex->current_select= 0;
+ my_net_set_read_timeout(net, thd->variables.net_wait_timeout);
+ thd->clear_error();
+ net_new_transaction(net);
+ ulong packet_length= my_net_read(net);
+ /*
+ If my_net_read() failed, my_error() has been already called,
+ and the main Diagnostics Area contains an error condition.
+ */
+ if (packet_length != packet_error)
+ my_error(ER_NEW_ABORTING_CONNECTION, MYF(0),
+ thd->thread_id,
+ thd->db ? thd->db : "unconnected",
+ sctx->user ? sctx->user : "unauthenticated",
+ sctx->host_or_ip, "init_connect command failed");
+ thd->server_status&= ~SERVER_STATUS_CLEAR_SET;
+ thd->protocol->end_statement();
+ thd->killed = KILL_CONNECTION;
+ errors.m_init_connect= 1;
+ inc_host_errors(thd->main_security_ctx.ip, &errors);
+ return;
+ }
+
+ thd->proc_info=0;
+ thd->set_time();
+ thd->init_for_queries();
+ }
+}
+
+
+/*
+ Thread handler for a connection
+
+ SYNOPSIS
+ handle_one_connection()
+ arg Connection object (THD)
+
+ IMPLEMENTATION
+ This function (normally) does the following:
+ - Initialize thread
+ - Initialize THD to be used with this thread
+ - Authenticate user
+ - Execute all queries sent on the connection
+ - Take connection down
+ - End thread / Handle next connection using thread from thread cache
+*/
+
+pthread_handler_t handle_one_connection(void *arg)
+{
+ THD *thd= (THD*) arg;
+
+ mysql_thread_set_psi_id(thd->thread_id);
+
+ do_handle_one_connection(thd);
+ return 0;
+}
+
+bool thd_prepare_connection(THD *thd)
+{
+ bool rc;
+ lex_start(thd);
+ rc= login_connection(thd);
+ mysql_audit_notify_connection_connect(thd);
+ if (rc)
+ return rc;
+
+ MYSQL_CONNECTION_START(thd->thread_id, &thd->security_ctx->priv_user[0],
+ (char *) thd->security_ctx->host_or_ip);
+
+ prepare_new_connection_state(thd);
+ return FALSE;
+}
+
+bool thd_is_connection_alive(THD *thd)
+{
+ NET *net= &thd->net;
+ if (!net->error &&
+ net->vio != 0 &&
+ thd->killed < KILL_CONNECTION)
+ return TRUE;
+ return FALSE;
+}
+
+void do_handle_one_connection(THD *thd_arg)
+{
+ THD *thd= thd_arg;
+
+ thd->thr_create_utime= microsecond_interval_timer();
+ /* We need to set this because of time_out_user_resource_limits */
+ thd->start_utime= thd->thr_create_utime;
+
+ if (MYSQL_CALLBACK_ELSE(thd->scheduler, init_new_connection_thread, (), 0))
+ {
+ close_connection(thd, ER_OUT_OF_RESOURCES);
+ statistic_increment(aborted_connects,&LOCK_status);
+ MYSQL_CALLBACK(thd->scheduler, end_thread, (thd, 0));
+ return;
+ }
+
+ /*
+ If a thread was created to handle this connection:
+ increment slow_launch_threads counter if it took more than
+ slow_launch_time seconds to create the thread.
+ */
+ if (thd->prior_thr_create_utime)
+ {
+ ulong launch_time= (ulong) (thd->thr_create_utime -
+ thd->prior_thr_create_utime);
+ if (launch_time >= slow_launch_time*1000000L)
+ statistic_increment(slow_launch_threads, &LOCK_status);
+ thd->prior_thr_create_utime= 0;
+ }
+
+ /*
+ handle_one_connection() is normally the only way a thread would
+ start and would always be on the very high end of the stack ,
+ therefore, the thread stack always starts at the address of the
+ first local variable of handle_one_connection, which is thd. We
+ need to know the start of the stack so that we could check for
+ stack overruns.
+ */
+ thd->thread_stack= (char*) &thd;
+ if (setup_connection_thread_globals(thd))
+ return;
+
+ for (;;)
+ {
+ bool create_user= TRUE;
+
+ mysql_socket_set_thread_owner(thd->net.vio->mysql_socket);
+ if (thd_prepare_connection(thd))
+ {
+ create_user= FALSE;
+ goto end_thread;
+ }
+
+ while (thd_is_connection_alive(thd))
+ {
+ mysql_audit_release(thd);
+ if (do_command(thd))
+ break;
+ }
+ end_connection(thd);
+
+end_thread:
+ close_connection(thd);
+
+ if (thd->userstat_running)
+ update_global_user_stats(thd, create_user, time(NULL));
+
+ if (MYSQL_CALLBACK_ELSE(thd->scheduler, end_thread, (thd, 1), 0))
+ return; // Probably no-threads
+
+ /*
+ If end_thread() returns, this thread has been schedule to
+ handle the next connection.
+ */
+ thd= current_thd;
+ thd->thread_stack= (char*) &thd;
+ }
+}
+#endif /* EMBEDDED_LIBRARY */
diff --git a/sql/sql_connect.h b/sql/sql_connect.h
new file mode 100644
index 00000000000..bab171606ba
--- /dev/null
+++ b/sql/sql_connect.h
@@ -0,0 +1,73 @@
+/* Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_CONNECT_INCLUDED
+#define SQL_CONNECT_INCLUDED
+
+#include "my_sys.h" /* pthread_handler_t */
+#include "mysql_com.h" /* enum_server_command */
+#include "structs.h"
+#include <hash.h>
+
+class THD;
+typedef struct st_lex_user LEX_USER;
+typedef struct user_conn USER_CONN;
+
+void init_max_user_conn(void);
+void init_global_user_stats(void);
+void init_global_table_stats(void);
+void init_global_index_stats(void);
+void init_global_client_stats(void);
+void free_max_user_conn(void);
+void free_global_user_stats(void);
+void free_global_table_stats(void);
+void free_global_index_stats(void);
+void free_global_client_stats(void);
+
+pthread_handler_t handle_one_connection(void *arg);
+void do_handle_one_connection(THD *thd_arg);
+bool init_new_connection_handler_thread();
+void reset_mqh(LEX_USER *lu, bool get_them);
+bool check_mqh(THD *thd, uint check_command);
+void time_out_user_resource_limits(THD *thd, USER_CONN *uc);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+void decrease_user_connections(USER_CONN *uc);
+#else
+#define decrease_user_connections(X) do { } while(0) /* nothing */
+#endif
+bool thd_init_client_charset(THD *thd, uint cs_number);
+bool setup_connection_thread_globals(THD *thd);
+bool thd_prepare_connection(THD *thd);
+bool thd_is_connection_alive(THD *thd);
+
+bool login_connection(THD *thd);
+void prepare_new_connection_state(THD* thd);
+void end_connection(THD *thd);
+void update_global_user_stats(THD* thd, bool create_user, time_t now);
+int get_or_create_user_conn(THD *thd, const char *user,
+ const char *host, const USER_RESOURCES *mqh);
+int check_for_max_user_connections(THD *thd, USER_CONN *uc);
+
+extern HASH global_user_stats;
+extern HASH global_client_stats;
+extern HASH global_table_stats;
+extern HASH global_index_stats;
+
+extern mysql_mutex_t LOCK_global_user_client_stats;
+extern mysql_mutex_t LOCK_global_table_stats;
+extern mysql_mutex_t LOCK_global_index_stats;
+extern mysql_mutex_t LOCK_stats;
+
+#endif /* SQL_CONNECT_INCLUDED */
diff --git a/sql/sql_const.h b/sql/sql_const.h
new file mode 100644
index 00000000000..2cbc616559d
--- /dev/null
+++ b/sql/sql_const.h
@@ -0,0 +1,265 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/**
+ @file
+ File containing constants that can be used throughout the server.
+
+ @note This file shall not contain any includes of any kinds.
+*/
+
+#ifndef SQL_CONST_INCLUDED
+#define SQL_CONST_INCLUDED
+
+#define LIBLEN FN_REFLEN-FN_LEN /* Max l{ngd p} dev */
+/* extra 4+4 bytes for slave tmp tables */
+#define MAX_DBKEY_LENGTH (NAME_LEN*2+1+1+4+4)
+#define MAX_ALIAS_NAME 256
+#define MAX_FIELD_NAME 34 /* Max colum name length +2 */
+#define MAX_SYS_VAR_LENGTH 32
+#define MAX_KEY MAX_INDEXES /* Max used keys */
+#define MAX_REF_PARTS 32 /* Max parts used as ref */
+#define MAX_KEY_LENGTH 3072 /* max possible key */
+#if SIZEOF_OFF_T > 4
+#define MAX_REFLENGTH 8 /* Max length for record ref */
+#else
+#define MAX_REFLENGTH 4 /* Max length for record ref */
+#endif
+#define MAX_HOSTNAME 61 /* len+1 in mysql.user */
+#define MAX_CONNECTION_NAME NAME_LEN
+
+#define MAX_MBWIDTH 3 /* Max multibyte sequence */
+#define MAX_FILENAME_MBWIDTH 5
+#define MAX_FIELD_CHARLENGTH 255
+#define MAX_FIELD_VARCHARLENGTH 65535
+#define MAX_FIELD_BLOBLENGTH UINT_MAX32 /* cf field_blob::get_length() */
+#define CONVERT_IF_BIGGER_TO_BLOB 512 /* Threshold *in characters* */
+
+/* Max column width +1 */
+#define MAX_FIELD_WIDTH (MAX_FIELD_CHARLENGTH*MAX_MBWIDTH+1)
+
+#define MAX_BIT_FIELD_LENGTH 64 /* Max length in bits for bit fields */
+
+#define MAX_DATE_WIDTH 10 /* YYYY-MM-DD */
+#define MIN_TIME_WIDTH 10 /* -HHH:MM:SS */
+#define MAX_TIME_WIDTH 16 /* -DDDDDD HH:MM:SS */
+#define MAX_TIME_FULL_WIDTH 23 /* -DDDDDD HH:MM:SS.###### */
+#define MAX_DATETIME_FULL_WIDTH 26 /* YYYY-MM-DD HH:MM:SS.###### */
+#define MAX_DATETIME_WIDTH 19 /* YYYY-MM-DD HH:MM:SS */
+#define MAX_DATETIME_COMPRESSED_WIDTH 14 /* YYYYMMDDHHMMSS */
+#define MAX_DATETIME_PRECISION 6
+
+#define MAX_TABLES (sizeof(table_map)*8-3) /* Max tables in join */
+#define PARAM_TABLE_BIT (((table_map) 1) << (sizeof(table_map)*8-3))
+#define OUTER_REF_TABLE_BIT (((table_map) 1) << (sizeof(table_map)*8-2))
+#define RAND_TABLE_BIT (((table_map) 1) << (sizeof(table_map)*8-1))
+#define PSEUDO_TABLE_BITS (PARAM_TABLE_BIT | OUTER_REF_TABLE_BIT | \
+ RAND_TABLE_BIT)
+#define MAX_FIELDS 4096 /* Limit in the .frm file */
+#define MAX_PARTITIONS 8192
+
+#define MAX_SELECT_NESTING (sizeof(nesting_map)*8-1)
+
+#define MAX_SORT_MEMORY 2048*1024
+#define MIN_SORT_MEMORY 1024
+
+/* Some portable defines */
+
+#define STRING_BUFFER_USUAL_SIZE 80
+
+/* Memory allocated when parsing a statement / saving a statement */
+#define MEM_ROOT_BLOCK_SIZE 8192
+#define MEM_ROOT_PREALLOC 8192
+#define TRANS_MEM_ROOT_BLOCK_SIZE 4096
+#define TRANS_MEM_ROOT_PREALLOC 4096
+
+#define DEFAULT_ERROR_COUNT 64
+#define EXTRA_RECORDS 10 /* Extra records in sort */
+#define SCROLL_EXTRA 5 /* Extra scroll-rows. */
+#define FIELD_NAME_USED ((uint) 32768) /* Bit set if fieldname used */
+#define FORM_NAME_USED ((uint) 16384) /* Bit set if formname used */
+#define FIELD_NR_MASK 16383 /* To get fieldnumber */
+#define FERR -1 /* Error from my_functions */
+#define CREATE_MODE 0 /* Default mode on new files */
+#define NAMES_SEP_CHAR 255 /* Char to sep. names */
+
+#define READ_RECORD_BUFFER (uint) (IO_SIZE*8) /* Pointer_buffer_size */
+#define DISK_BUFFER_SIZE (uint) (IO_SIZE*16) /* Size of diskbuffer */
+
+#define FRM_VER_TRUE_VARCHAR (FRM_VER+4) /* 10 */
+
+/***************************************************************************
+ Configuration parameters
+****************************************************************************/
+
+#define ACL_CACHE_SIZE 256
+#define MAX_PASSWORD_LENGTH 32
+#define HOST_CACHE_SIZE 128
+#define MAX_ACCEPT_RETRY 10 // Test accept this many times
+#define MAX_FIELDS_BEFORE_HASH 32
+#define USER_VARS_HASH_SIZE 16
+#define TABLE_OPEN_CACHE_MIN 400
+#define TABLE_OPEN_CACHE_DEFAULT 400
+#define TABLE_DEF_CACHE_DEFAULT 400
+/**
+ We must have room for at least 400 table definitions in the table
+ cache, since otherwise there is no chance prepared
+ statements that use these many tables can work.
+ Prepared statements use table definition cache ids (table_map_id)
+ as table version identifiers. If the table definition
+ cache size is less than the number of tables used in a statement,
+ the contents of the table definition cache is guaranteed to rotate
+ between a prepare and execute. This leads to stable validation
+ errors. In future we shall use more stable version identifiers,
+ for now the only solution is to ensure that the table definition
+ cache can contain at least all tables of a given statement.
+*/
+#define TABLE_DEF_CACHE_MIN 400
+
+/**
+ Maximum number of connections default value.
+ 151 is larger than Apache's default max children,
+ to avoid "too many connections" error in a common setup.
+*/
+#define MAX_CONNECTIONS_DEFAULT 151
+
+/*
+ Stack reservation.
+ Feel free to raise this by the smallest amount you can to get the
+ "execution_constants" test to pass.
+*/
+#define STACK_MIN_SIZE 16000 // Abort if less stack during eval.
+
+#define STACK_MIN_SIZE_FOR_OPEN 1024*80
+#define STACK_BUFF_ALLOC 352 ///< For stack overrun checks
+#ifndef MYSQLD_NET_RETRY_COUNT
+#define MYSQLD_NET_RETRY_COUNT 10 ///< Abort read after this many int.
+#endif
+
+#define QUERY_ALLOC_BLOCK_SIZE 8192
+#define QUERY_ALLOC_PREALLOC_SIZE 8192
+#define TRANS_ALLOC_BLOCK_SIZE 4096
+#define TRANS_ALLOC_PREALLOC_SIZE 4096
+#define RANGE_ALLOC_BLOCK_SIZE 4096
+#define ACL_ALLOC_BLOCK_SIZE 1024
+#define UDF_ALLOC_BLOCK_SIZE 1024
+#define TABLE_ALLOC_BLOCK_SIZE 1024
+#define WARN_ALLOC_BLOCK_SIZE 2048
+#define WARN_ALLOC_PREALLOC_SIZE 1024
+
+/*
+ The following parameters is to decide when to use an extra cache to
+ optimise seeks when reading a big table in sorted order
+*/
+#define MIN_FILE_LENGTH_TO_USE_ROW_CACHE (10L*1024*1024)
+#define MIN_ROWS_TO_USE_TABLE_CACHE 100
+#define MIN_ROWS_TO_USE_BULK_INSERT 100
+
+/**
+ The following is used to decide if MySQL should use table scanning
+ instead of reading with keys. The number says how many evaluation of the
+ WHERE clause is comparable to reading one extra row from a table.
+*/
+#define TIME_FOR_COMPARE 5 // 5 compares == one read
+
+/**
+ Number of comparisons of table rowids equivalent to reading one row from a
+ table.
+*/
+#define TIME_FOR_COMPARE_ROWID (TIME_FOR_COMPARE*100)
+
+/* cost1 is better that cost2 only if cost1 + COST_EPS < cost2 */
+#define COST_EPS 0.001
+
+/*
+ For sequential disk seeks the cost formula is:
+ DISK_SEEK_BASE_COST + DISK_SEEK_PROP_COST * #blocks_to_skip
+
+ The cost of average seek
+ DISK_SEEK_BASE_COST + DISK_SEEK_PROP_COST*BLOCKS_IN_AVG_SEEK =1.0.
+*/
+#define DISK_SEEK_BASE_COST ((double)0.9)
+
+#define BLOCKS_IN_AVG_SEEK 128
+
+#define DISK_SEEK_PROP_COST ((double)0.1/BLOCKS_IN_AVG_SEEK)
+
+
+/**
+ Number of rows in a reference table when refereed through a not unique key.
+ This value is only used when we don't know anything about the key
+ distribution.
+*/
+#define MATCHING_ROWS_IN_OTHER_TABLE 10
+
+/*
+ Subquery materialization-related constants
+*/
+#define HEAP_TEMPTABLE_LOOKUP_COST 0.05
+#define DISK_TEMPTABLE_LOOKUP_COST 1.0
+
+#define MY_CHARSET_BIN_MB_MAXLEN 1
+
+/** Don't pack string keys shorter than this (if PACK_KEYS=1 isn't used). */
+#define KEY_DEFAULT_PACK_LENGTH 8
+
+/** Characters shown for the command in 'show processlist'. */
+#define PROCESS_LIST_WIDTH 100
+/* Characters shown for the command in 'information_schema.processlist' */
+#define PROCESS_LIST_INFO_WIDTH 65535
+
+#define PRECISION_FOR_DOUBLE 53
+#define PRECISION_FOR_FLOAT 24
+
+/* -[digits].E+## */
+#define MAX_FLOAT_STR_LENGTH (FLT_DIG + 6)
+/* -[digits].E+### */
+#define MAX_DOUBLE_STR_LENGTH (DBL_DIG + 7)
+
+/*
+ Default time to wait before aborting a new client connection
+ that does not respond to "initial server greeting" timely
+*/
+#define CONNECT_TIMEOUT 10
+
+/* The following can also be changed from the command line */
+#define DEFAULT_CONCURRENCY 10
+#define DELAYED_LIMIT 100 /**< pause after xxx inserts */
+#define DELAYED_QUEUE_SIZE 1000
+#define DELAYED_WAIT_TIMEOUT 5*60 /**< Wait for delayed insert */
+#define MAX_CONNECT_ERRORS 100 ///< errors before disabling host
+
+#define LONG_TIMEOUT ((ulong) 3600L*24L*365L)
+
+/**
+ Maximum length of time zone name that we support (Time zone name is
+ char(64) in db). mysqlbinlog needs it.
+*/
+#define MAX_TIME_ZONE_NAME_LENGTH (NAME_LEN + 1)
+
+#if defined(__WIN__)
+
+#define INTERRUPT_PRIOR -2
+#define CONNECT_PRIOR -1
+#define WAIT_PRIOR 0
+#define QUERY_PRIOR 2
+#else
+#define INTERRUPT_PRIOR 10
+#define CONNECT_PRIOR 9
+#define WAIT_PRIOR 8
+#define QUERY_PRIOR 6
+#endif /* __WIN92__ */
+
+#endif /* SQL_CONST_INCLUDED */
diff --git a/sql/sql_crypt.cc b/sql/sql_crypt.cc
new file mode 100644
index 00000000000..2460a16551d
--- /dev/null
+++ b/sql/sql_crypt.cc
@@ -0,0 +1,77 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+
+/*
+ Functions to handle the encode() and decode() functions
+ The strongness of this crypt is large based on how good the random
+ generator is. It should be ok for short strings, but for communication one
+ needs something like 'ssh'.
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_crypt.h"
+#include "password.h"
+
+void SQL_CRYPT::init(ulong *rand_nr)
+{
+ uint i;
+ my_rnd_init(&rand,rand_nr[0],rand_nr[1]);
+
+ for (i=0 ; i<=255; i++)
+ decode_buff[i]= (char) i;
+
+ for (i=0 ; i<= 255 ; i++)
+ {
+ int idx= (uint) (my_rnd(&rand)*255.0);
+ char a= decode_buff[idx];
+ decode_buff[idx]= decode_buff[i];
+ decode_buff[+i]=a;
+ }
+ for (i=0 ; i <= 255 ; i++)
+ encode_buff[(uchar) decode_buff[i]]=i;
+ org_rand=rand;
+ shift=0;
+}
+
+
+void SQL_CRYPT::encode(char *str,uint length)
+{
+ for (uint i=0; i < length; i++)
+ {
+ shift^=(uint) (my_rnd(&rand)*255.0);
+ uint idx= (uint) (uchar) str[0];
+ *str++ = (char) ((uchar) encode_buff[idx] ^ shift);
+ shift^= idx;
+ }
+}
+
+
+void SQL_CRYPT::decode(char *str,uint length)
+{
+ for (uint i=0; i < length; i++)
+ {
+ shift^=(uint) (my_rnd(&rand)*255.0);
+ uint idx= (uint) ((uchar) str[0] ^ shift);
+ *str = decode_buff[idx];
+ shift^= (uint) (uchar) *str++;
+ }
+}
diff --git a/sql/sql_crypt.h b/sql/sql_crypt.h
new file mode 100644
index 00000000000..3df554e9d31
--- /dev/null
+++ b/sql/sql_crypt.h
@@ -0,0 +1,45 @@
+#ifndef SQL_CRYPT_INCLUDED
+#define SQL_CRYPT_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_list.h" /* Sql_alloc */
+#include "my_rnd.h" /* rand_struct */
+
+class SQL_CRYPT :public Sql_alloc
+{
+ struct my_rnd_struct rand,org_rand;
+ char decode_buff[256],encode_buff[256];
+ uint shift;
+ public:
+ SQL_CRYPT() {}
+ SQL_CRYPT(ulong *seed)
+ {
+ init(seed);
+ }
+ ~SQL_CRYPT() {}
+ void init(ulong *seed);
+ void reinit() { shift=0; rand=org_rand; }
+ void encode(char *str, uint length);
+ void decode(char *str, uint length);
+};
+
+#endif /* SQL_CRYPT_INCLUDED */
diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc
new file mode 100644
index 00000000000..c09f3269d7a
--- /dev/null
+++ b/sql/sql_cursor.cc
@@ -0,0 +1,424 @@
+/*
+ Copyright (c) 2005, 2010, Oracle and/or its affiliates.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation /* gcc class implementation */
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_cursor.h"
+#include "probes_mysql.h"
+#include "sql_parse.h" // mysql_execute_command
+
+/****************************************************************************
+ Declarations.
+****************************************************************************/
+
+/**
+ Materialized_cursor -- an insensitive materialized server-side
+ cursor. The result set of this cursor is saved in a temporary
+ table at open. The cursor itself is simply an interface for the
+ handler of the temporary table.
+*/
+
+class Materialized_cursor: public Server_side_cursor
+{
+ MEM_ROOT main_mem_root;
+ /* A fake unit to supply to select_send when fetching */
+ SELECT_LEX_UNIT fake_unit;
+ TABLE *table;
+ List<Item> item_list;
+ ulong fetch_limit;
+ ulong fetch_count;
+ bool is_rnd_inited;
+public:
+ Materialized_cursor(select_result *result, TABLE *table);
+
+ int send_result_set_metadata(THD *thd, List<Item> &send_result_set_metadata);
+ virtual bool is_open() const { return table != 0; }
+ virtual int open(JOIN *join __attribute__((unused)));
+ virtual void fetch(ulong num_rows);
+ virtual void close();
+ virtual ~Materialized_cursor();
+};
+
+
+/**
+ Select_materialize -- a mediator between a cursor query and the
+ protocol. In case we were not able to open a non-materialzed
+ cursor, it creates an internal temporary HEAP table, and insert
+ all rows into it. When the table reaches max_heap_table_size,
+ it's converted to a MyISAM table. Later this table is used to
+ create a Materialized_cursor.
+*/
+
+class Select_materialize: public select_union
+{
+ select_result *result; /**< the result object of the caller (PS or SP) */
+public:
+ Materialized_cursor *materialized_cursor;
+ Select_materialize(select_result *result_arg)
+ :result(result_arg), materialized_cursor(0) {}
+ virtual bool send_result_set_metadata(List<Item> &list, uint flags);
+};
+
+
+/**************************************************************************/
+
+/**
+ Attempt to open a materialized cursor.
+
+ @param thd thread handle
+ @param[in] result result class of the caller used as a destination
+ for the rows fetched from the cursor
+ @param[out] pcursor a pointer to store a pointer to cursor in
+
+ @retval
+ 0 the query has been successfully executed; in this
+ case pcursor may or may not contain
+ a pointer to an open cursor.
+ @retval
+ non-zero an error, 'pcursor' has been left intact.
+*/
+
+int mysql_open_cursor(THD *thd, select_result *result,
+ Server_side_cursor **pcursor)
+{
+ sql_digest_state *parent_digest;
+ PSI_statement_locker *parent_locker;
+ select_result *save_result;
+ Select_materialize *result_materialize;
+ LEX *lex= thd->lex;
+ int rc;
+
+ if (! (result_materialize= new (thd->mem_root) Select_materialize(result)))
+ return 1;
+
+ save_result= lex->result;
+
+ lex->result= result_materialize;
+
+ MYSQL_QUERY_EXEC_START(thd->query(),
+ thd->thread_id,
+ (char *) (thd->db ? thd->db : ""),
+ &thd->security_ctx->priv_user[0],
+ (char *) thd->security_ctx->host_or_ip,
+ 2);
+ parent_digest= thd->m_digest;
+ parent_locker= thd->m_statement_psi;
+ thd->m_digest= NULL;
+ thd->m_statement_psi= NULL;
+ /* Mark that we can't use query cache with cursors */
+ thd->query_cache_is_applicable= 0;
+ rc= mysql_execute_command(thd);
+ thd->m_digest= parent_digest;
+ thd->m_statement_psi= parent_locker;
+ MYSQL_QUERY_EXEC_DONE(rc);
+
+ lex->result= save_result;
+ /*
+ Possible options here:
+ - a materialized cursor is open. In this case rc is 0 and
+ result_materialize->materialized is not NULL
+ - an error occurred during materialization.
+ result_materialize->materialized_cursor is not NULL, but rc != 0
+ - successful completion of mysql_execute_command without
+ a cursor: rc is 0, result_materialize->materialized_cursor is NULL.
+ This is possible if some command writes directly to the
+ network, bypassing select_result mechanism. An example of
+ such command is SHOW VARIABLES or SHOW STATUS.
+ */
+ if (rc)
+ {
+ if (result_materialize->materialized_cursor)
+ {
+ /* Rollback metadata in the client-server protocol. */
+ result_materialize->abort_result_set();
+
+ delete result_materialize->materialized_cursor;
+ }
+
+ goto end;
+ }
+
+ if (result_materialize->materialized_cursor)
+ {
+ Materialized_cursor *materialized_cursor=
+ result_materialize->materialized_cursor;
+
+ /*
+ NOTE: close_thread_tables() has been called in
+ mysql_execute_command(), so all tables except from the cursor
+ temporary table have been closed.
+ */
+
+ if ((rc= materialized_cursor->open(0)))
+ {
+ delete materialized_cursor;
+ goto end;
+ }
+
+ *pcursor= materialized_cursor;
+ thd->stmt_arena->cleanup_stmt();
+ }
+
+end:
+ delete result_materialize;
+ return rc;
+}
+
+/****************************************************************************
+ Server_side_cursor
+****************************************************************************/
+
+Server_side_cursor::~Server_side_cursor()
+{
+}
+
+
+void Server_side_cursor::operator delete(void *ptr, size_t size)
+{
+ Server_side_cursor *cursor= (Server_side_cursor*) ptr;
+ MEM_ROOT own_root= *cursor->mem_root;
+
+ DBUG_ENTER("Server_side_cursor::operator delete");
+ TRASH(ptr, size);
+ /*
+ If this cursor has never been opened mem_root is empty. Otherwise
+ mem_root points to the memory the cursor object was allocated in.
+ In this case it's important to call free_root last, and free a copy
+ instead of *mem_root to avoid writing into freed memory.
+ */
+ free_root(&own_root, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+
+/***************************************************************************
+ Materialized_cursor
+****************************************************************************/
+
+Materialized_cursor::Materialized_cursor(select_result *result_arg,
+ TABLE *table_arg)
+ :Server_side_cursor(&table_arg->mem_root, result_arg),
+ table(table_arg),
+ fetch_limit(0),
+ fetch_count(0),
+ is_rnd_inited(0)
+{
+ fake_unit.init_query();
+ fake_unit.thd= table->in_use;
+}
+
+
+/**
+ Preserve the original metadata to be sent to the client.
+ Initiate sending of the original metadata to the client
+ (call Protocol::send_result_set_metadata()).
+
+ @param thd Thread identifier.
+ @param send_result_set_metadata List of fields that would be sent.
+*/
+
+int Materialized_cursor::send_result_set_metadata(
+ THD *thd, List<Item> &send_result_set_metadata)
+{
+ Query_arena backup_arena;
+ int rc;
+ List_iterator_fast<Item> it_org(send_result_set_metadata);
+ List_iterator_fast<Item> it_dst(item_list);
+ Item *item_org;
+ Item *item_dst;
+
+ thd->set_n_backup_active_arena(this, &backup_arena);
+
+ if ((rc= table->fill_item_list(&item_list)))
+ goto end;
+
+ DBUG_ASSERT(send_result_set_metadata.elements == item_list.elements);
+
+ /*
+ Unless we preserve the original metadata, it will be lost,
+ since new fields describe columns of the temporary table.
+ Allocate a copy of the name for safety only. Currently
+ items with original names are always kept in memory,
+ but in case this changes a memory leak may be hard to notice.
+ */
+ while ((item_dst= it_dst++, item_org= it_org++))
+ {
+ Send_field send_field;
+ Item_ident *ident= static_cast<Item_ident *>(item_dst);
+ item_org->make_field(&send_field);
+
+ ident->db_name= thd->strdup(send_field.db_name);
+ ident->table_name= thd->strdup(send_field.table_name);
+ }
+
+ /*
+ Original metadata result set should be sent here. After
+ mysql_execute_command() is finished, item_list can not be used for
+ sending metadata, because it references closed table.
+ */
+ rc= result->send_result_set_metadata(item_list, Protocol::SEND_NUM_ROWS);
+
+end:
+ thd->restore_active_arena(this, &backup_arena);
+ /* Check for thd->is_error() in case of OOM */
+ return rc || thd->is_error();
+}
+
+
+int Materialized_cursor::open(JOIN *join __attribute__((unused)))
+{
+ THD *thd= fake_unit.thd;
+ int rc;
+ Query_arena backup_arena;
+
+ thd->set_n_backup_active_arena(this, &backup_arena);
+
+ /* Create a list of fields and start sequential scan. */
+
+ rc= result->prepare(item_list, &fake_unit);
+ rc= !rc && table->file->ha_rnd_init_with_error(TRUE);
+ is_rnd_inited= !rc;
+
+ thd->restore_active_arena(this, &backup_arena);
+
+ /* Commit or rollback metadata in the client-server protocol. */
+
+ if (!rc)
+ {
+ thd->server_status|= SERVER_STATUS_CURSOR_EXISTS;
+ result->send_eof();
+ }
+ else
+ {
+ result->abort_result_set();
+ }
+
+ return rc;
+}
+
+
+/**
+ Fetch up to the given number of rows from a materialized cursor.
+
+ Precondition: the cursor is open.
+
+ If the cursor points after the last row, the fetch will automatically
+ close the cursor and not send any data (except the 'EOF' packet
+ with SERVER_STATUS_LAST_ROW_SENT). This is an extra round trip
+ and probably should be improved to return
+ SERVER_STATUS_LAST_ROW_SENT along with the last row.
+*/
+
+void Materialized_cursor::fetch(ulong num_rows)
+{
+ THD *thd= table->in_use;
+
+ int res= 0;
+ result->begin_dataset();
+ for (fetch_limit+= num_rows; fetch_count < fetch_limit; fetch_count++)
+ {
+ if ((res= table->file->ha_rnd_next(table->record[0])))
+ break;
+ /* Send data only if the read was successful. */
+ /*
+ If network write failed (i.e. due to a closed socked),
+ the error has already been set. Just return.
+ */
+ if (result->send_data(item_list) > 0)
+ return;
+ }
+
+ switch (res) {
+ case 0:
+ thd->server_status|= SERVER_STATUS_CURSOR_EXISTS;
+ result->send_eof();
+ break;
+ case HA_ERR_END_OF_FILE:
+ thd->server_status|= SERVER_STATUS_LAST_ROW_SENT;
+ result->send_eof();
+ close();
+ break;
+ default:
+ table->file->print_error(res, MYF(0));
+ close();
+ break;
+ }
+}
+
+
+void Materialized_cursor::close()
+{
+ /* Free item_list items */
+ free_items();
+ if (is_rnd_inited)
+ (void) table->file->ha_rnd_end();
+ /*
+ We need to grab table->mem_root to prevent free_tmp_table from freeing:
+ the cursor object was allocated in this memory.
+ */
+ main_mem_root= table->mem_root;
+ mem_root= &main_mem_root;
+ clear_alloc_root(&table->mem_root);
+ free_tmp_table(table->in_use, table);
+ table= 0;
+}
+
+
+Materialized_cursor::~Materialized_cursor()
+{
+ if (is_open())
+ close();
+}
+
+
+/***************************************************************************
+ Select_materialize
+****************************************************************************/
+
+bool Select_materialize::send_result_set_metadata(List<Item> &list, uint flags)
+{
+ DBUG_ASSERT(table == 0);
+ if (create_result_table(unit->thd, unit->get_unit_column_types(),
+ FALSE,
+ thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS,
+ "", FALSE, TRUE, TRUE))
+ return TRUE;
+
+ materialized_cursor= new (&table->mem_root)
+ Materialized_cursor(result, table);
+
+ if (!materialized_cursor)
+ {
+ free_tmp_table(table->in_use, table);
+ table= 0;
+ return TRUE;
+ }
+
+ if (materialized_cursor->send_result_set_metadata(unit->thd, list))
+ {
+ delete materialized_cursor;
+ table= 0;
+ materialized_cursor= 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
diff --git a/sql/sql_cursor.h b/sql/sql_cursor.h
new file mode 100644
index 00000000000..bff47d654b3
--- /dev/null
+++ b/sql/sql_cursor.h
@@ -0,0 +1,66 @@
+/* Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _sql_cursor_h_
+#define _sql_cursor_h_
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class interface */
+#endif
+
+#include "sql_class.h" /* Query_arena */
+
+class JOIN;
+
+/**
+ @file
+
+ Declarations for implementation of server side cursors. Only
+ read-only non-scrollable cursors are currently implemented.
+*/
+
+/**
+ Server_side_cursor -- an interface for materialized
+ implementation of cursors. All cursors are self-contained
+ (created in their own memory root). For that reason they must
+ be deleted only using a pointer to Server_side_cursor, not to
+ its base class.
+*/
+
+class Server_side_cursor: protected Query_arena, public Sql_alloc
+{
+protected:
+ /** Row destination used for fetch */
+ select_result *result;
+public:
+ Server_side_cursor(MEM_ROOT *mem_root_arg, select_result *result_arg)
+ :Query_arena(mem_root_arg, STMT_INITIALIZED), result(result_arg)
+ {}
+
+ virtual bool is_open() const= 0;
+
+ virtual int open(JOIN *top_level_join)= 0;
+ virtual void fetch(ulong num_rows)= 0;
+ virtual void close()= 0;
+ virtual ~Server_side_cursor();
+
+ static void operator delete(void *ptr, size_t size);
+};
+
+
+int mysql_open_cursor(THD *thd, select_result *result,
+ Server_side_cursor **res);
+
+#endif /* _sql_cusor_h_ */
diff --git a/sql/sql_db.cc b/sql/sql_db.cc
new file mode 100644
index 00000000000..e89c3d9e745
--- /dev/null
+++ b/sql/sql_db.cc
@@ -0,0 +1,1838 @@
+/*
+ Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* create and drop of databases */
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_db.h"
+#include "sql_cache.h" // query_cache_*
+#include "lock.h" // lock_schema_name
+#include "sql_table.h" // build_table_filename,
+ // filename_to_tablename
+#include "sql_rename.h" // mysql_rename_tables
+#include "sql_acl.h" // SELECT_ACL, DB_ACLS,
+ // acl_get, check_grant_db
+#include "log_event.h" // Query_log_event
+#include "sql_base.h" // lock_table_names, tdc_remove_table
+#include "sql_handler.h" // mysql_ha_rm_tables
+#include "sql_class.h"
+#include <mysys_err.h>
+#include "sp_head.h"
+#include "sp.h"
+#include "events.h"
+#include "sql_handler.h"
+#include "sql_statistics.h"
+#include <my_dir.h>
+#include <m_ctype.h>
+#include "log.h"
+#ifdef __WIN__
+#include <direct.h>
+#endif
+#include "debug_sync.h"
+
+#define MAX_DROP_TABLE_Q_LEN 1024
+
+const char *del_exts[]= {".BAK", ".opt", NullS};
+static TYPELIB deletable_extentions=
+{array_elements(del_exts)-1,"del_exts", del_exts, NULL};
+
+static bool find_db_tables_and_rm_known_files(THD *, MY_DIR *, char *,
+ const char *, TABLE_LIST **);
+
+long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path);
+static my_bool rm_dir_w_symlink(const char *org_path, my_bool send_error);
+static void mysql_change_db_impl(THD *thd,
+ LEX_STRING *new_db_name,
+ ulong new_db_access,
+ CHARSET_INFO *new_db_charset);
+
+
+/* Database options hash */
+static HASH dboptions;
+static my_bool dboptions_init= 0;
+static mysql_rwlock_t LOCK_dboptions;
+
+/* Structure for database options */
+typedef struct my_dbopt_st
+{
+ char *name; /* Database name */
+ uint name_length; /* Database length name */
+ CHARSET_INFO *charset; /* Database default character set */
+} my_dbopt_t;
+
+
+/**
+ Return TRUE if db1_name is equal to db2_name, FALSE otherwise.
+
+ The function allows to compare database names according to the MariaDB
+ rules. The database names db1 and db2 are equal if:
+ - db1 is NULL and db2 is NULL;
+ or
+ - db1 is not-NULL, db2 is not-NULL, db1 is equal to db2 in
+ table_alias_charset
+
+ This is the same rules as we use for filenames.
+*/
+
+static inline bool
+cmp_db_names(const char *db1_name,
+ const char *db2_name)
+{
+ return ((!db1_name && !db2_name) ||
+ (db1_name && db2_name &&
+ my_strcasecmp(table_alias_charset, db1_name, db2_name) == 0));
+}
+
+
+/*
+ Function we use in the creation of our hash to get key.
+*/
+
+extern "C" uchar* dboptions_get_key(my_dbopt_t *opt, size_t *length,
+ my_bool not_used);
+
+uchar* dboptions_get_key(my_dbopt_t *opt, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= opt->name_length;
+ return (uchar*) opt->name;
+}
+
+
+/*
+ Helper function to write a query to binlog used by mysql_rm_db()
+*/
+
+static inline int write_to_binlog(THD *thd, char *query, uint q_len,
+ char *db, uint db_len)
+{
+ Query_log_event qinfo(thd, query, q_len, FALSE, TRUE, FALSE, 0);
+ qinfo.db= db;
+ qinfo.db_len= db_len;
+ return mysql_bin_log.write(&qinfo);
+}
+
+
+/*
+ Function to free dboptions hash element
+*/
+
+extern "C" void free_dbopt(void *dbopt);
+
+void free_dbopt(void *dbopt)
+{
+ my_free(dbopt);
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_rwlock_key key_rwlock_LOCK_dboptions;
+
+static PSI_rwlock_info all_database_names_rwlocks[]=
+{
+ { &key_rwlock_LOCK_dboptions, "LOCK_dboptions", PSI_FLAG_GLOBAL}
+};
+
+static void init_database_names_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_database_names_rwlocks);
+ PSI_server->register_rwlock(category, all_database_names_rwlocks, count);
+}
+#endif
+
+/**
+ Initialize database option cache.
+
+ @note Must be called before any other database function is called.
+
+ @retval 0 ok
+ @retval 1 Fatal error
+*/
+
+bool my_dboptions_cache_init(void)
+{
+#ifdef HAVE_PSI_INTERFACE
+ init_database_names_psi_keys();
+#endif
+
+ bool error= 0;
+ mysql_rwlock_init(key_rwlock_LOCK_dboptions, &LOCK_dboptions);
+ if (!dboptions_init)
+ {
+ dboptions_init= 1;
+ error= my_hash_init(&dboptions, table_alias_charset,
+ 32, 0, 0, (my_hash_get_key) dboptions_get_key,
+ free_dbopt,0);
+ }
+ return error;
+}
+
+
+
+/**
+ Free database option hash and locked databases hash.
+*/
+
+void my_dboptions_cache_free(void)
+{
+ if (dboptions_init)
+ {
+ dboptions_init= 0;
+ my_hash_free(&dboptions);
+ mysql_rwlock_destroy(&LOCK_dboptions);
+ }
+}
+
+
+/**
+ Cleanup cached options.
+*/
+
+void my_dbopt_cleanup(void)
+{
+ mysql_rwlock_wrlock(&LOCK_dboptions);
+ my_hash_free(&dboptions);
+ my_hash_init(&dboptions, table_alias_charset,
+ 32, 0, 0, (my_hash_get_key) dboptions_get_key,
+ free_dbopt,0);
+ mysql_rwlock_unlock(&LOCK_dboptions);
+}
+
+
+/*
+ Find database options in the hash.
+
+ DESCRIPTION
+ Search a database options in the hash, usings its path.
+ Fills "create" on success.
+
+ RETURN VALUES
+ 0 on success.
+ 1 on error.
+*/
+
+static my_bool get_dbopt(const char *dbname, HA_CREATE_INFO *create)
+{
+ my_dbopt_t *opt;
+ uint length;
+ my_bool error= 1;
+
+ length= (uint) strlen(dbname);
+
+ mysql_rwlock_rdlock(&LOCK_dboptions);
+ if ((opt= (my_dbopt_t*) my_hash_search(&dboptions, (uchar*) dbname, length)))
+ {
+ create->default_table_charset= opt->charset;
+ error= 0;
+ }
+ mysql_rwlock_unlock(&LOCK_dboptions);
+ return error;
+}
+
+
+/*
+ Writes database options into the hash.
+
+ DESCRIPTION
+ Inserts database options into the hash, or updates
+ options if they are already in the hash.
+
+ RETURN VALUES
+ 0 on success.
+ 1 on error.
+*/
+
+static my_bool put_dbopt(const char *dbname, HA_CREATE_INFO *create)
+{
+ my_dbopt_t *opt;
+ uint length;
+ my_bool error= 0;
+ DBUG_ENTER("put_dbopt");
+
+ length= (uint) strlen(dbname);
+
+ mysql_rwlock_wrlock(&LOCK_dboptions);
+ if (!(opt= (my_dbopt_t*) my_hash_search(&dboptions, (uchar*) dbname,
+ length)))
+ {
+ /* Options are not in the hash, insert them */
+ char *tmp_name;
+ if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
+ &opt, (uint) sizeof(*opt), &tmp_name, (uint) length+1,
+ NullS))
+ {
+ error= 1;
+ goto end;
+ }
+
+ opt->name= tmp_name;
+ strmov(opt->name, dbname);
+ opt->name_length= length;
+
+ if ((error= my_hash_insert(&dboptions, (uchar*) opt)))
+ {
+ my_free(opt);
+ goto end;
+ }
+ }
+
+ /* Update / write options in hash */
+ opt->charset= create->default_table_charset;
+
+end:
+ mysql_rwlock_unlock(&LOCK_dboptions);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Deletes database options from the hash.
+*/
+
+static void del_dbopt(const char *path)
+{
+ my_dbopt_t *opt;
+ mysql_rwlock_wrlock(&LOCK_dboptions);
+ if ((opt= (my_dbopt_t *)my_hash_search(&dboptions, (const uchar*) path,
+ strlen(path))))
+ my_hash_delete(&dboptions, (uchar*) opt);
+ mysql_rwlock_unlock(&LOCK_dboptions);
+}
+
+
+/*
+ Create database options file:
+
+ DESCRIPTION
+ Currently database default charset is only stored there.
+
+ RETURN VALUES
+ 0 ok
+ 1 Could not create file or write to it. Error sent through my_error()
+*/
+
+static bool write_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create)
+{
+ register File file;
+ char buf[256]; // Should be enough for one option
+ bool error=1;
+
+ if (!create->default_table_charset)
+ create->default_table_charset= thd->variables.collation_server;
+
+ if (put_dbopt(path, create))
+ return 1;
+
+ if ((file= mysql_file_create(key_file_dbopt, path, CREATE_MODE,
+ O_RDWR | O_TRUNC, MYF(MY_WME))) >= 0)
+ {
+ ulong length;
+ length= (ulong) (strxnmov(buf, sizeof(buf)-1, "default-character-set=",
+ create->default_table_charset->csname,
+ "\ndefault-collation=",
+ create->default_table_charset->name,
+ "\n", NullS) - buf);
+
+ /* Error is written by mysql_file_write */
+ if (!mysql_file_write(file, (uchar*) buf, length, MYF(MY_NABP+MY_WME)))
+ error=0;
+ mysql_file_close(file, MYF(0));
+ }
+ return error;
+}
+
+
+/*
+ Load database options file
+
+ load_db_opt()
+ path Path for option file
+ create Where to store the read options
+
+ DESCRIPTION
+
+ RETURN VALUES
+ 0 File found
+ 1 No database file or could not open it
+
+*/
+
+bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create)
+{
+ File file;
+ char buf[256];
+ DBUG_ENTER("load_db_opt");
+ bool error=1;
+ uint nbytes;
+
+ bzero((char*) create,sizeof(*create));
+ create->default_table_charset= thd->variables.collation_server;
+
+ /* Check if options for this database are already in the hash */
+ if (!get_dbopt(path, create))
+ DBUG_RETURN(0);
+
+ /* Otherwise, load options from the .opt file */
+ if ((file= mysql_file_open(key_file_dbopt,
+ path, O_RDONLY | O_SHARE, MYF(0))) < 0)
+ goto err1;
+
+ IO_CACHE cache;
+ if (init_io_cache(&cache, file, IO_SIZE, READ_CACHE, 0, 0, MYF(0)))
+ goto err2;
+
+ while ((int) (nbytes= my_b_gets(&cache, (char*) buf, sizeof(buf))) > 0)
+ {
+ char *pos= buf+nbytes-1;
+ /* Remove end space and control characters */
+ while (pos > buf && !my_isgraph(&my_charset_latin1, pos[-1]))
+ pos--;
+ *pos=0;
+ if ((pos= strchr(buf, '=')))
+ {
+ if (!strncmp(buf,"default-character-set", (pos-buf)))
+ {
+ /*
+ Try character set name, and if it fails
+ try collation name, probably it's an old
+ 4.1.0 db.opt file, which didn't have
+ separate default-character-set and
+ default-collation commands.
+ */
+ if (!(create->default_table_charset=
+ get_charset_by_csname(pos+1, MY_CS_PRIMARY, MYF(0))) &&
+ !(create->default_table_charset=
+ get_charset_by_name(pos+1, MYF(0))))
+ {
+ sql_print_error("Error while loading database options: '%s':",path);
+ sql_print_error(ER(ER_UNKNOWN_CHARACTER_SET),pos+1);
+ create->default_table_charset= default_charset_info;
+ }
+ }
+ else if (!strncmp(buf,"default-collation", (pos-buf)))
+ {
+ if (!(create->default_table_charset= get_charset_by_name(pos+1,
+ MYF(0))))
+ {
+ sql_print_error("Error while loading database options: '%s':",path);
+ sql_print_error(ER(ER_UNKNOWN_COLLATION),pos+1);
+ create->default_table_charset= default_charset_info;
+ }
+ }
+ }
+ }
+ /*
+ Put the loaded value into the hash.
+ Note that another thread could've added the same
+ entry to the hash after we called get_dbopt(),
+ but it's not an error, as put_dbopt() takes this
+ possibility into account.
+ */
+ error= put_dbopt(path, create);
+
+ end_io_cache(&cache);
+err2:
+ mysql_file_close(file, MYF(0));
+err1:
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Retrieve database options by name. Load database options file or fetch from
+ cache.
+
+ SYNOPSIS
+ load_db_opt_by_name()
+ db_name Database name
+ db_create_info Where to store the database options
+
+ DESCRIPTION
+ load_db_opt_by_name() is a shortcut for load_db_opt().
+
+ NOTE
+ Although load_db_opt_by_name() (and load_db_opt()) returns status of
+ the operation, it is useless usually and should be ignored. The problem
+ is that there are 1) system databases ("mysql") and 2) virtual
+ databases ("information_schema"), which do not contain options file.
+ So, load_db_opt[_by_name]() returns FALSE for these databases, but this
+ is not an error.
+
+ load_db_opt[_by_name]() clears db_create_info structure in any case, so
+ even on failure it contains valid data. So, common use case is just
+ call load_db_opt[_by_name]() without checking return value and use
+ db_create_info right after that.
+
+ RETURN VALUES (read NOTE!)
+ FALSE Success
+ TRUE Failed to retrieve options
+*/
+
+bool load_db_opt_by_name(THD *thd, const char *db_name,
+ HA_CREATE_INFO *db_create_info)
+{
+ char db_opt_path[FN_REFLEN + 1];
+
+ /*
+ Pass an empty file name, and the database options file name as extension
+ to avoid table name to file name encoding.
+ */
+ (void) build_table_filename(db_opt_path, sizeof(db_opt_path) - 1,
+ db_name, "", MY_DB_OPT_FILE, 0);
+
+ return load_db_opt(thd, db_opt_path, db_create_info);
+}
+
+
+/**
+ Return default database collation.
+
+ @param thd Thread context.
+ @param db_name Database name.
+
+ @return CHARSET_INFO object. The operation always return valid character
+ set, even if the database does not exist.
+*/
+
+CHARSET_INFO *get_default_db_collation(THD *thd, const char *db_name)
+{
+ HA_CREATE_INFO db_info;
+
+ if (thd->db != NULL && strcmp(db_name, thd->db) == 0)
+ return thd->db_charset;
+
+ load_db_opt_by_name(thd, db_name, &db_info);
+
+ /*
+ NOTE: even if load_db_opt_by_name() fails,
+ db_info.default_table_charset contains valid character set
+ (collation_server). We should not fail if load_db_opt_by_name() fails,
+ because it is valid case. If a database has been created just by
+ "mkdir", it does not contain db.opt file, but it is valid database.
+ */
+
+ return db_info.default_table_charset;
+}
+
+
+/*
+ Create a database
+
+ SYNOPSIS
+ mysql_create_db()
+ thd Thread handler
+ db Name of database to create
+ Function assumes that this is already validated.
+ create_info Database create options (like character set)
+ silent Used by replication when internally creating a database.
+ In this case the entry should not be logged.
+
+ SIDE-EFFECTS
+ 1. Report back to client that command succeeded (my_ok)
+ 2. Report errors to client
+ 3. Log event to binary log
+ (The 'silent' flags turns off 1 and 3.)
+
+ RETURN VALUES
+ FALSE ok
+ TRUE Error
+
+*/
+
+int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info,
+ bool silent)
+{
+ char path[FN_REFLEN+16];
+ long result= 1;
+ int error= 0;
+ MY_STAT stat_info;
+ uint create_options= create_info ? create_info->options : 0;
+ uint path_len;
+ DBUG_ENTER("mysql_create_db");
+
+ /* do not create 'information_schema' db */
+ if (is_infoschema_db(db))
+ {
+ my_error(ER_DB_CREATE_EXISTS, MYF(0), db);
+ DBUG_RETURN(-1);
+ }
+
+ char db_tmp[SAFE_NAME_LEN], *dbnorm;
+ if (lower_case_table_names)
+ {
+ strmake_buf(db_tmp, db);
+ my_casedn_str(system_charset_info, db_tmp);
+ dbnorm= db_tmp;
+ }
+ else
+ dbnorm= db;
+
+ if (lock_schema_name(thd, dbnorm))
+ DBUG_RETURN(-1);
+
+ /* Check directory */
+ path_len= build_table_filename(path, sizeof(path) - 1, db, "", "", 0);
+ path[path_len-1]= 0; // Remove last '/' from path
+
+ if (mysql_file_stat(key_file_misc, path, &stat_info, MYF(0)))
+ {
+ if (!(create_options & HA_LEX_CREATE_IF_NOT_EXISTS))
+ {
+ my_error(ER_DB_CREATE_EXISTS, MYF(0), db);
+ error= -1;
+ goto exit;
+ }
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_DB_CREATE_EXISTS, ER(ER_DB_CREATE_EXISTS), db);
+ error= 0;
+ goto not_silent;
+ }
+ else
+ {
+ if (my_errno != ENOENT)
+ {
+ my_error(EE_STAT, MYF(0), path, my_errno);
+ goto exit;
+ }
+ if (my_mkdir(path,0777,MYF(0)) < 0)
+ {
+ my_error(ER_CANT_CREATE_DB, MYF(0), db, my_errno);
+ error= -1;
+ goto exit;
+ }
+ }
+
+ path[path_len-1]= FN_LIBCHAR;
+ strmake(path+path_len, MY_DB_OPT_FILE, sizeof(path)-path_len-1);
+ if (write_db_opt(thd, path, create_info))
+ {
+ /*
+ Could not create options file.
+ Restore things to beginning.
+ */
+ path[path_len]= 0;
+ if (rmdir(path) >= 0)
+ {
+ error= -1;
+ goto exit;
+ }
+ /*
+ We come here when we managed to create the database, but not the option
+ file. In this case it's best to just continue as if nothing has
+ happened. (This is a very unlikely senario)
+ */
+ thd->clear_error();
+ }
+
+not_silent:
+ if (!silent)
+ {
+ char *query;
+ uint query_length;
+
+ query= thd->query();
+ query_length= thd->query_length();
+ DBUG_ASSERT(query);
+
+ ha_binlog_log_query(thd, 0, LOGCOM_CREATE_DB,
+ query, query_length,
+ db, "");
+
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= query_error_code(thd, TRUE);
+ Query_log_event qinfo(thd, query, query_length, FALSE, TRUE,
+ /* suppress_use */ TRUE, errcode);
+
+ /*
+ Write should use the database being created as the "current
+ database" and not the threads current database, which is the
+ default. If we do not change the "current database" to the
+ database being created, the CREATE statement will not be
+ replicated when using --binlog-do-db to select databases to be
+ replicated.
+
+ An example (--binlog-do-db=sisyfos):
+
+ CREATE DATABASE bob; # Not replicated
+ USE bob; # 'bob' is the current database
+ CREATE DATABASE sisyfos; # Not replicated since 'bob' is
+ # current database.
+ USE sisyfos; # Will give error on slave since
+ # database does not exist.
+ */
+ qinfo.db = db;
+ qinfo.db_len = strlen(db);
+
+ /*
+ These DDL methods and logging are protected with the exclusive
+ metadata lock on the schema
+ */
+ if (mysql_bin_log.write(&qinfo))
+ {
+ error= -1;
+ goto exit;
+ }
+ }
+ my_ok(thd, result);
+ }
+
+exit:
+ DBUG_RETURN(error);
+}
+
+
+/* db-name is already validated when we come here */
+
+bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info)
+{
+ char path[FN_REFLEN+16];
+ long result=1;
+ int error= 0;
+ DBUG_ENTER("mysql_alter_db");
+
+ if (lock_schema_name(thd, db))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Recreate db options file: /dbpath/.db.opt
+ We pass MY_DB_OPT_FILE as "extension" to avoid
+ "table name to file name" encoding.
+ */
+ build_table_filename(path, sizeof(path) - 1, db, "", MY_DB_OPT_FILE, 0);
+ if ((error=write_db_opt(thd, path, create_info)))
+ goto exit;
+
+ /* Change options if current database is being altered. */
+
+ if (thd->db && !strcmp(thd->db,db))
+ {
+ thd->db_charset= create_info->default_table_charset ?
+ create_info->default_table_charset :
+ thd->variables.collation_server;
+ thd->variables.collation_database= thd->db_charset;
+ }
+
+ ha_binlog_log_query(thd, 0, LOGCOM_ALTER_DB,
+ thd->query(), thd->query_length(),
+ db, "");
+
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= query_error_code(thd, TRUE);
+ Query_log_event qinfo(thd, thd->query(), thd->query_length(), FALSE, TRUE,
+ /* suppress_use */ TRUE, errcode);
+ /*
+ Write should use the database being created as the "current
+ database" and not the threads current database, which is the
+ default.
+ */
+ qinfo.db = db;
+ qinfo.db_len = strlen(db);
+
+ /*
+ These DDL methods and logging are protected with the exclusive
+ metadata lock on the schema.
+ */
+ if ((error= mysql_bin_log.write(&qinfo)))
+ goto exit;
+ }
+ my_ok(thd, result);
+
+exit:
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Drop all tables, routines and events in a database and the database itself.
+
+ @param thd Thread handle
+ @param db Database name in the case given by user
+ It's already validated and set to lower case
+ (if needed) when we come here
+ @param if_exists Don't give error if database doesn't exists
+ @param silent Don't write the statement to the binary log and don't
+ send ok packet to the client
+
+ @retval false OK (Database dropped)
+ @retval true Error
+*/
+
+bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
+{
+ ulong deleted_tables= 0;
+ bool error= true;
+ char path[FN_REFLEN + 16];
+ MY_DIR *dirp;
+ uint length;
+ TABLE_LIST *tables= NULL;
+ TABLE_LIST *table;
+ Drop_table_error_handler err_handler;
+ DBUG_ENTER("mysql_rm_db");
+
+ char db_tmp[SAFE_NAME_LEN], *dbnorm;
+ if (lower_case_table_names)
+ {
+ strmake_buf(db_tmp, db);
+ my_casedn_str(system_charset_info, db_tmp);
+ dbnorm= db_tmp;
+ }
+ else
+ dbnorm= db;
+
+ if (lock_schema_name(thd, dbnorm))
+ DBUG_RETURN(true);
+
+ length= build_table_filename(path, sizeof(path) - 1, db, "", "", 0);
+ strmov(path+length, MY_DB_OPT_FILE); // Append db option file name
+ del_dbopt(path); // Remove dboption hash entry
+ path[length]= '\0'; // Remove file name
+
+ /* See if the directory exists */
+ if (!(dirp= my_dir(path,MYF(MY_DONT_SORT))))
+ {
+ if (!if_exists)
+ {
+ my_error(ER_DB_DROP_EXISTS, MYF(0), db);
+ DBUG_RETURN(true);
+ }
+ else
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_DB_DROP_EXISTS, ER(ER_DB_DROP_EXISTS), db);
+ error= false;
+ goto update_binlog;
+ }
+ }
+
+ if (find_db_tables_and_rm_known_files(thd, dirp, dbnorm, path, &tables))
+ goto exit;
+
+ /*
+ Disable drop of enabled log tables, must be done before name locking.
+ This check is only needed if we are dropping the "mysql" database.
+ */
+ if ((my_strcasecmp(system_charset_info, MYSQL_SCHEMA_NAME.str, db) == 0))
+ {
+ for (table= tables; table; table= table->next_local)
+ if (check_if_log_table(table, TRUE, "DROP"))
+ goto exit;
+ }
+
+ /* Lock all tables and stored routines about to be dropped. */
+ if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout,
+ 0) ||
+ lock_db_routines(thd, dbnorm))
+ goto exit;
+
+ if (!in_bootstrap)
+ {
+ for (table= tables; table; table= table->next_local)
+ {
+ LEX_STRING db_name= { table->db, table->db_length };
+ LEX_STRING table_name= { table->table_name, table->table_name_length };
+ if (table->open_type == OT_BASE_ONLY || !find_temporary_table(thd, table))
+ (void) delete_statistics_for_table(thd, &db_name, &table_name);
+ }
+ }
+
+ /* mysql_ha_rm_tables() requires a non-null TABLE_LIST. */
+ if (tables)
+ mysql_ha_rm_tables(thd, tables);
+
+ for (table= tables; table; table= table->next_local)
+ deleted_tables++;
+
+ thd->push_internal_handler(&err_handler);
+ if (!thd->killed &&
+ !(tables &&
+ mysql_rm_table_no_locks(thd, tables, true, false, true, true, false)))
+ {
+ /*
+ We temporarily disable the binary log while dropping the objects
+ in the database. Since the DROP DATABASE statement is always
+ replicated as a statement, execution of it will drop all objects
+ in the database on the slave as well, so there is no need to
+ replicate the removal of the individual objects in the database
+ as well.
+
+ This is more of a safety precaution, since normally no objects
+ should be dropped while the database is being cleaned, but in
+ the event that a change in the code to remove other objects is
+ made, these drops should still not be logged.
+
+ Notice that the binary log have to be enabled over the call to
+ ha_drop_database(), since NDB otherwise detects the binary log
+ as disabled and will not log the drop database statement on any
+ other connected server.
+ */
+
+ ha_drop_database(path);
+ tmp_disable_binlog(thd);
+ query_cache_invalidate1(thd, dbnorm);
+ (void) sp_drop_db_routines(thd, dbnorm); /* @todo Do not ignore errors */
+#ifdef HAVE_EVENT_SCHEDULER
+ Events::drop_schema_events(thd, dbnorm);
+#endif
+ reenable_binlog(thd);
+
+ /*
+ If the directory is a symbolic link, remove the link first, then
+ remove the directory the symbolic link pointed at
+ */
+ error= rm_dir_w_symlink(path, true);
+ }
+ thd->pop_internal_handler();
+
+update_binlog:
+ if (!silent && !error)
+ {
+ const char *query;
+ ulong query_length;
+
+ query= thd->query();
+ query_length= thd->query_length();
+ DBUG_ASSERT(query);
+
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= query_error_code(thd, TRUE);
+ Query_log_event qinfo(thd, query, query_length, FALSE, TRUE,
+ /* suppress_use */ TRUE, errcode);
+ /*
+ Write should use the database being created as the "current
+ database" and not the threads current database, which is the
+ default.
+ */
+ qinfo.db = db;
+ qinfo.db_len = strlen(db);
+
+ /*
+ These DDL methods and logging are protected with the exclusive
+ metadata lock on the schema.
+ */
+ if (mysql_bin_log.write(&qinfo))
+ {
+ error= true;
+ goto exit;
+ }
+ }
+ thd->clear_error();
+ thd->server_status|= SERVER_STATUS_DB_DROPPED;
+ my_ok(thd, deleted_tables);
+ }
+ else if (mysql_bin_log.is_open() && !silent)
+ {
+ char *query, *query_pos, *query_end, *query_data_start;
+ TABLE_LIST *tbl;
+ uint db_len;
+
+ if (!(query= (char*) thd->alloc(MAX_DROP_TABLE_Q_LEN)))
+ goto exit; /* not much else we can do */
+ query_pos= query_data_start= strmov(query,"DROP TABLE IF EXISTS ");
+ query_end= query + MAX_DROP_TABLE_Q_LEN;
+ db_len= strlen(db);
+
+ for (tbl= tables; tbl; tbl= tbl->next_local)
+ {
+ uint tbl_name_len;
+ char quoted_name[FN_REFLEN+3];
+
+ // Only write drop table to the binlog for tables that no longer exist.
+ if (ha_table_exists(thd, tbl->db, tbl->table_name))
+ continue;
+
+ my_snprintf(quoted_name, sizeof(quoted_name), "%`s", tbl->table_name);
+ tbl_name_len= strlen(quoted_name) + 1; /* +1 for the comma */
+ if (query_pos + tbl_name_len + 1 >= query_end)
+ {
+ /*
+ These DDL methods and logging are protected with the exclusive
+ metadata lock on the schema.
+ */
+ if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len))
+ {
+ error= true;
+ goto exit;
+ }
+ query_pos= query_data_start;
+ }
+
+ query_pos= strmov(query_pos, quoted_name);
+ *query_pos++ = ',';
+ }
+
+ if (query_pos != query_data_start)
+ {
+ /*
+ These DDL methods and logging are protected with the exclusive
+ metadata lock on the schema.
+ */
+ if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len))
+ {
+ error= true;
+ goto exit;
+ }
+ }
+ }
+
+exit:
+ /*
+ If this database was the client's selected database, we silently
+ change the client's selected database to nothing (to have an empty
+ SELECT DATABASE() in the future). For this we free() thd->db and set
+ it to 0.
+ */
+ if (thd->db && cmp_db_names(thd->db, db) && !error)
+ mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
+ my_dirend(dirp);
+ DBUG_RETURN(error);
+}
+
+
+static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp,
+ char *dbname,
+ const char *path,
+ TABLE_LIST **tables)
+{
+ char filePath[FN_REFLEN];
+ LEX_STRING db= { dbname, strlen(dbname) };
+ TABLE_LIST *tot_list=0, **tot_list_next_local, **tot_list_next_global;
+ DBUG_ENTER("find_db_tables_and_rm_known_files");
+ DBUG_PRINT("enter",("path: %s", path));
+
+ /* first, get the list of tables */
+ Dynamic_array<LEX_STRING*> files(dirp->number_of_files);
+ Discovered_table_list tl(thd, &files);
+ if (ha_discover_table_names(thd, &db, dirp, &tl, true))
+ DBUG_RETURN(1);
+
+ /* Now put the tables in the list */
+ tot_list_next_local= tot_list_next_global= &tot_list;
+
+ for (size_t idx=0; idx < files.elements(); idx++)
+ {
+ LEX_STRING *table= files.at(idx);
+
+ /* Drop the table nicely */
+ TABLE_LIST *table_list=(TABLE_LIST*)thd->calloc(sizeof(*table_list));
+
+ if (!table_list)
+ DBUG_RETURN(true);
+ table_list->db= db.str;
+ table_list->db_length= db.length;
+ table_list->table_name= table->str;
+ table_list->table_name_length= table->length;
+ table_list->open_type= OT_BASE_ONLY;
+
+ /* To be able to correctly look up the table in the table cache. */
+ if (lower_case_table_names)
+ table_list->table_name_length= my_casedn_str(files_charset_info,
+ table_list->table_name);
+
+ table_list->alias= table_list->table_name; // If lower_case_table_names=2
+ table_list->mdl_request.init(MDL_key::TABLE, table_list->db,
+ table_list->table_name, MDL_EXCLUSIVE,
+ MDL_TRANSACTION);
+ /* Link into list */
+ (*tot_list_next_local)= table_list;
+ (*tot_list_next_global)= table_list;
+ tot_list_next_local= &table_list->next_local;
+ tot_list_next_global= &table_list->next_global;
+ }
+ *tables= tot_list;
+
+ /* and at last delete all non-table files */
+ for (uint idx=0 ;
+ idx < (uint) dirp->number_of_files && !thd->killed ;
+ idx++)
+ {
+ FILEINFO *file=dirp->dir_entry+idx;
+ char *extension;
+ DBUG_PRINT("info",("Examining: %s", file->name));
+
+ if (file->name[0] == 'a' && file->name[1] == 'r' &&
+ file->name[2] == 'c' && file->name[3] == '\0')
+ {
+ /* .frm archive:
+ Those archives are obsolete, but following code should
+ exist to remove existent "arc" directories.
+ */
+ char newpath[FN_REFLEN];
+ MY_DIR *new_dirp;
+ strxmov(newpath, path, "/", "arc", NullS);
+ (void) unpack_filename(newpath, newpath);
+ if ((new_dirp = my_dir(newpath, MYF(MY_DONT_SORT))))
+ {
+ DBUG_PRINT("my",("Archive subdir found: %s", newpath));
+ if ((mysql_rm_arc_files(thd, new_dirp, newpath)) < 0)
+ DBUG_RETURN(true);
+ }
+ continue;
+ }
+ if (!(extension= strrchr(file->name, '.')))
+ extension= strend(file->name);
+ if (find_type(extension, &deletable_extentions, FIND_TYPE_NO_PREFIX) > 0)
+ {
+ strxmov(filePath, path, "/", file->name, NullS);
+ /*
+ We ignore ENOENT error in order to skip files that was deleted
+ by concurrently running statement like REAPIR TABLE ...
+ */
+ if (my_delete_with_symlink(filePath, MYF(0)) &&
+ my_errno != ENOENT)
+ {
+ my_error(EE_DELETE, MYF(0), filePath, my_errno);
+ DBUG_RETURN(true);
+ }
+ }
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/*
+ Remove directory with symlink
+
+ SYNOPSIS
+ rm_dir_w_symlink()
+ org_path path of derictory
+ send_error send errors
+ RETURN
+ 0 OK
+ 1 ERROR
+*/
+
+static my_bool rm_dir_w_symlink(const char *org_path, my_bool send_error)
+{
+ char tmp_path[FN_REFLEN], *pos;
+ char *path= tmp_path;
+ DBUG_ENTER("rm_dir_w_symlink");
+ unpack_filename(tmp_path, org_path);
+#ifdef HAVE_READLINK
+ int error;
+ char tmp2_path[FN_REFLEN];
+
+ /* Remove end FN_LIBCHAR as this causes problem on Linux in readlink */
+ pos= strend(path);
+ if (pos > path && pos[-1] == FN_LIBCHAR)
+ *--pos=0;
+
+ if ((error= my_readlink(tmp2_path, path, MYF(MY_WME))) < 0)
+ DBUG_RETURN(1);
+ if (!error)
+ {
+ if (mysql_file_delete(key_file_misc, path, MYF(send_error ? MY_WME : 0)))
+ {
+ DBUG_RETURN(send_error);
+ }
+ /* Delete directory symbolic link pointed at */
+ path= tmp2_path;
+ }
+#endif
+ /* Remove last FN_LIBCHAR to not cause a problem on OS/2 */
+ pos= strend(path);
+
+ if (pos > path && pos[-1] == FN_LIBCHAR)
+ *--pos=0;
+ if (rmdir(path) < 0 && send_error)
+ {
+ my_error(ER_DB_DROP_RMDIR, MYF(0), path, errno);
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Remove .frm archives from directory
+
+ SYNOPSIS
+ thd thread handler
+ dirp list of files in archive directory
+ db data base name
+ org_path path of archive directory
+
+ RETURN
+ > 0 number of removed files
+ -1 error
+
+ NOTE
+ A support of "arc" directories is obsolete, however this
+ function should exist to remove existent "arc" directories.
+*/
+long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path)
+{
+ long deleted= 0;
+ ulong found_other_files= 0;
+ char filePath[FN_REFLEN];
+ DBUG_ENTER("mysql_rm_arc_files");
+ DBUG_PRINT("enter", ("path: %s", org_path));
+
+ for (uint idx=0 ;
+ idx < (uint) dirp->number_of_files && !thd->killed ;
+ idx++)
+ {
+ FILEINFO *file=dirp->dir_entry+idx;
+ char *extension, *revision;
+ DBUG_PRINT("info",("Examining: %s", file->name));
+
+ extension= fn_ext(file->name);
+ if (extension[0] != '.' ||
+ extension[1] != 'f' || extension[2] != 'r' ||
+ extension[3] != 'm' || extension[4] != '-')
+ {
+ found_other_files++;
+ continue;
+ }
+ revision= extension+5;
+ while (*revision && my_isdigit(system_charset_info, *revision))
+ revision++;
+ if (*revision)
+ {
+ found_other_files++;
+ continue;
+ }
+ strxmov(filePath, org_path, "/", file->name, NullS);
+ if (mysql_file_delete_with_symlink(key_file_misc, filePath, MYF(MY_WME)))
+ {
+ goto err;
+ }
+ deleted++;
+ }
+ if (thd->killed)
+ goto err;
+
+ my_dirend(dirp);
+
+ /*
+ If the directory is a symbolic link, remove the link first, then
+ remove the directory the symbolic link pointed at
+ */
+ if (!found_other_files &&
+ rm_dir_w_symlink(org_path, 0))
+ DBUG_RETURN(-1);
+ DBUG_RETURN(deleted);
+
+err:
+ my_dirend(dirp);
+ DBUG_RETURN(-1);
+}
+
+
+/**
+ @brief Internal implementation: switch current database to a valid one.
+
+ @param thd Thread context.
+ @param new_db_name Name of the database to switch to. The function will
+ take ownership of the name (the caller must not free
+ the allocated memory). If the name is NULL, we're
+ going to switch to NULL db.
+ @param new_db_access Privileges of the new database.
+ @param new_db_charset Character set of the new database.
+*/
+
+static void mysql_change_db_impl(THD *thd,
+ LEX_STRING *new_db_name,
+ ulong new_db_access,
+ CHARSET_INFO *new_db_charset)
+{
+ /* 1. Change current database in THD. */
+
+ if (new_db_name == NULL)
+ {
+ /*
+ THD::set_db() does all the job -- it frees previous database name and
+ sets the new one.
+ */
+
+ thd->set_db(NULL, 0);
+ }
+ else if (new_db_name == &INFORMATION_SCHEMA_NAME)
+ {
+ /*
+ Here we must use THD::set_db(), because we want to copy
+ INFORMATION_SCHEMA_NAME constant.
+ */
+
+ thd->set_db(INFORMATION_SCHEMA_NAME.str, INFORMATION_SCHEMA_NAME.length);
+ }
+ else
+ {
+ /*
+ Here we already have a copy of database name to be used in THD. So,
+ we just call THD::reset_db(). Since THD::reset_db() does not releases
+ the previous database name, we should do it explicitly.
+ */
+ thd->set_db(NULL, 0);
+ thd->reset_db(new_db_name->str, new_db_name->length);
+ }
+
+ /* 2. Update security context. */
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ thd->security_ctx->db_access= new_db_access;
+#endif
+
+ /* 3. Update db-charset environment variables. */
+
+ thd->db_charset= new_db_charset;
+ thd->variables.collation_database= new_db_charset;
+}
+
+
+
+/**
+ Backup the current database name before switch.
+
+ @param[in] thd thread handle
+ @param[in, out] saved_db_name IN: "str" points to a buffer where to store
+ the old database name, "length" contains the
+ buffer size
+ OUT: if the current (default) database is
+ not NULL, its name is copied to the
+ buffer pointed at by "str"
+ and "length" is updated accordingly.
+ Otherwise "str" is set to NULL and
+ "length" is set to 0.
+*/
+
+static void backup_current_db_name(THD *thd,
+ LEX_STRING *saved_db_name)
+{
+ if (!thd->db)
+ {
+ /* No current (default) database selected. */
+
+ saved_db_name->str= NULL;
+ saved_db_name->length= 0;
+ }
+ else
+ {
+ strmake(saved_db_name->str, thd->db, saved_db_name->length - 1);
+ saved_db_name->length= thd->db_length;
+ }
+}
+
+
+/**
+ @brief Change the current database and its attributes unconditionally.
+
+ @param thd thread handle
+ @param new_db_name database name
+ @param force_switch if force_switch is FALSE, then the operation will fail if
+
+ - new_db_name is NULL or empty;
+
+ - OR new database name is invalid
+ (check_db_name() failed);
+
+ - OR user has no privilege on the new database;
+
+ - OR new database does not exist;
+
+ if force_switch is TRUE, then
+
+ - if new_db_name is NULL or empty, the current
+ database will be NULL, @@collation_database will
+ be set to @@collation_server, the operation will
+ succeed.
+
+ - if new database name is invalid
+ (check_db_name() failed), the current database
+ will be NULL, @@collation_database will be set to
+ @@collation_server, but the operation will fail;
+
+ - user privileges will not be checked
+ (THD::db_access however is updated);
+
+ TODO: is this really the intention?
+ (see sp-security.test).
+
+ - if new database does not exist,the current database
+ will be NULL, @@collation_database will be set to
+ @@collation_server, a warning will be thrown, the
+ operation will succeed.
+
+ @details The function checks that the database name corresponds to a
+ valid and existent database, checks access rights and changes the current
+ database with database attributes (@@collation_database session variable,
+ THD::db_access).
+
+ This function is not the only way to switch the database that is
+ currently employed. When the replication slave thread switches the
+ database before executing a query, it calls thd->set_db directly.
+ However, if the query, in turn, uses a stored routine, the stored routine
+ will use this function, even if it's run on the slave.
+
+ This function allocates the name of the database on the system heap: this
+ is necessary to be able to uniformly change the database from any module
+ of the server. Up to 5.0 different modules were using different memory to
+ store the name of the database, and this led to memory corruption:
+ a stack pointer set by Stored Procedures was used by replication after
+ the stack address was long gone.
+
+ @return Operation status
+ @retval FALSE Success
+ @retval TRUE Error
+*/
+
+bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
+{
+ LEX_STRING new_db_file_name;
+
+ Security_context *sctx= thd->security_ctx;
+ ulong db_access= sctx->db_access;
+ CHARSET_INFO *db_default_cl;
+ DBUG_ENTER("mysql_change_db");
+
+ if (new_db_name->length == 0)
+ {
+ if (force_switch)
+ {
+ /*
+ This can happen only if we're switching the current database back
+ after loading stored program. The thing is that loading of stored
+ program can happen when there is no current database.
+
+ In case of stored program, new_db_name->str == "" and
+ new_db_name->length == 0.
+ */
+
+ mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
+
+ DBUG_RETURN(FALSE);
+ }
+ else
+ {
+ my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
+
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_PRINT("enter",("name: '%s'", new_db_name->str));
+
+ if (is_infoschema_db(new_db_name->str, new_db_name->length))
+ {
+ /* Switch the current database to INFORMATION_SCHEMA. */
+
+ mysql_change_db_impl(thd, &INFORMATION_SCHEMA_NAME, SELECT_ACL,
+ system_charset_info);
+
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ Now we need to make a copy because check_db_name requires a
+ non-constant argument. Actually, it takes database file name.
+
+ TODO: fix check_db_name().
+ */
+
+ new_db_file_name.str= my_strndup(new_db_name->str, new_db_name->length,
+ MYF(MY_WME));
+ new_db_file_name.length= new_db_name->length;
+
+ if (new_db_file_name.str == NULL)
+ DBUG_RETURN(TRUE); /* the error is set */
+
+ /*
+ NOTE: if check_db_name() fails, we should throw an error in any case,
+ even if we are called from sp_head::execute().
+
+ It's next to impossible however to get this error when we are called
+ from sp_head::execute(). But let's switch the current database to NULL
+ in this case to be sure.
+ */
+
+ if (check_db_name(&new_db_file_name))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), new_db_file_name.str);
+ my_free(new_db_file_name.str);
+
+ if (force_switch)
+ mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
+
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_PRINT("info",("Use database: %s", new_db_file_name.str));
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (test_all_bits(sctx->master_access, DB_ACLS))
+ db_access= DB_ACLS;
+ else
+ {
+ db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user,
+ new_db_file_name.str, FALSE) | sctx->master_access;
+ if (sctx->priv_role[0])
+ {
+ /* include a possible currently set role for access */
+ db_access|= acl_get("", "", sctx->priv_role, new_db_file_name.str, FALSE);
+ }
+ }
+
+ if (!force_switch &&
+ !(db_access & DB_ACLS) &&
+ check_grant_db(thd, new_db_file_name.str))
+ {
+ my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
+ sctx->priv_user,
+ sctx->priv_host,
+ new_db_file_name.str);
+ general_log_print(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR),
+ sctx->priv_user, sctx->priv_host, new_db_file_name.str);
+ my_free(new_db_file_name.str);
+ DBUG_RETURN(TRUE);
+ }
+#endif
+
+ DEBUG_SYNC(thd, "before_db_dir_check");
+
+ if (check_db_dir_existence(new_db_file_name.str))
+ {
+ if (force_switch)
+ {
+ /* Throw a warning and free new_db_file_name. */
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_BAD_DB_ERROR, ER(ER_BAD_DB_ERROR),
+ new_db_file_name.str);
+
+ my_free(new_db_file_name.str);
+
+ /* Change db to NULL. */
+
+ mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
+
+ /* The operation succeed. */
+
+ DBUG_RETURN(FALSE);
+ }
+ else
+ {
+ /* Report an error and free new_db_file_name. */
+
+ my_error(ER_BAD_DB_ERROR, MYF(0), new_db_file_name.str);
+ my_free(new_db_file_name.str);
+
+ /* The operation failed. */
+
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ /*
+ NOTE: in mysql_change_db_impl() new_db_file_name is assigned to THD
+ attributes and will be freed in THD::~THD().
+ */
+
+ db_default_cl= get_default_db_collation(thd, new_db_file_name.str);
+
+ mysql_change_db_impl(thd, &new_db_file_name, db_access, db_default_cl);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Change the current database and its attributes if needed.
+
+ @param thd thread handle
+ @param new_db_name database name
+ @param[in, out] saved_db_name IN: "str" points to a buffer where to store
+ the old database name, "length" contains the
+ buffer size
+ OUT: if the current (default) database is
+ not NULL, its name is copied to the
+ buffer pointed at by "str"
+ and "length" is updated accordingly.
+ Otherwise "str" is set to NULL and
+ "length" is set to 0.
+ @param force_switch @see mysql_change_db()
+ @param[out] cur_db_changed out-flag to indicate whether the current
+ database has been changed (valid only if
+ the function suceeded)
+*/
+
+bool mysql_opt_change_db(THD *thd,
+ const LEX_STRING *new_db_name,
+ LEX_STRING *saved_db_name,
+ bool force_switch,
+ bool *cur_db_changed)
+{
+ *cur_db_changed= !cmp_db_names(thd->db, new_db_name->str);
+
+ if (!*cur_db_changed)
+ return FALSE;
+
+ backup_current_db_name(thd, saved_db_name);
+
+ return mysql_change_db(thd, new_db_name, force_switch);
+}
+
+
+/**
+ Upgrade a 5.0 database.
+ This function is invoked whenever an ALTER DATABASE UPGRADE query is executed:
+ ALTER DATABASE 'olddb' UPGRADE DATA DIRECTORY NAME.
+
+ If we have managed to rename (move) tables to the new database
+ but something failed on a later step, then we store the
+ RENAME DATABASE event in the log. mysql_rename_db() is atomic in
+ the sense that it will rename all or none of the tables.
+
+ @param thd Current thread
+ @param old_db 5.0 database name, in #mysql50#name format
+ @return 0 on success, 1 on error
+*/
+bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db)
+{
+ int error= 0, change_to_newdb= 0;
+ char path[FN_REFLEN+16];
+ uint length;
+ HA_CREATE_INFO create_info;
+ MY_DIR *dirp;
+ TABLE_LIST *table_list;
+ SELECT_LEX *sl= thd->lex->current_select;
+ LEX_STRING new_db;
+ DBUG_ENTER("mysql_upgrade_db");
+
+ if ((old_db->length <= MYSQL50_TABLE_NAME_PREFIX_LENGTH) ||
+ (strncmp(old_db->str,
+ MYSQL50_TABLE_NAME_PREFIX,
+ MYSQL50_TABLE_NAME_PREFIX_LENGTH) != 0))
+ {
+ my_error(ER_WRONG_USAGE, MYF(0),
+ "ALTER DATABASE UPGRADE DATA DIRECTORY NAME",
+ "name");
+ DBUG_RETURN(1);
+ }
+
+ /* `#mysql50#<name>` converted to encoded `<name>` */
+ new_db.str= old_db->str + MYSQL50_TABLE_NAME_PREFIX_LENGTH;
+ new_db.length= old_db->length - MYSQL50_TABLE_NAME_PREFIX_LENGTH;
+
+ /* Lock the old name, the new name will be locked by mysql_create_db().*/
+ if (lock_schema_name(thd, old_db->str))
+ DBUG_RETURN(1);
+
+ /*
+ Let's remember if we should do "USE newdb" afterwards.
+ thd->db will be cleared in mysql_rename_db()
+ */
+ if (thd->db && !strcmp(thd->db, old_db->str))
+ change_to_newdb= 1;
+
+ build_table_filename(path, sizeof(path)-1,
+ old_db->str, "", MY_DB_OPT_FILE, 0);
+ if ((load_db_opt(thd, path, &create_info)))
+ create_info.default_table_charset= thd->variables.collation_server;
+
+ length= build_table_filename(path, sizeof(path)-1, old_db->str, "", "", 0);
+ if (length && path[length-1] == FN_LIBCHAR)
+ path[length-1]=0; // remove ending '\'
+ if ((error= my_access(path,F_OK)))
+ {
+ my_error(ER_BAD_DB_ERROR, MYF(0), old_db->str);
+ goto exit;
+ }
+
+ /* Step1: Create the new database */
+ if ((error= mysql_create_db(thd, new_db.str, &create_info, 1)))
+ goto exit;
+
+ /* Step2: Move tables to the new database */
+ if ((dirp = my_dir(path,MYF(MY_DONT_SORT))))
+ {
+ uint nfiles= (uint) dirp->number_of_files;
+ for (uint idx=0 ; idx < nfiles && !thd->killed ; idx++)
+ {
+ FILEINFO *file= dirp->dir_entry + idx;
+ char *extension, tname[FN_REFLEN + 1];
+ LEX_STRING table_str;
+ DBUG_PRINT("info",("Examining: %s", file->name));
+
+ /* skiping non-FRM files */
+ if (my_strcasecmp(files_charset_info,
+ (extension= fn_rext(file->name)), reg_ext))
+ continue;
+
+ /* A frm file found, add the table info rename list */
+ *extension= '\0';
+
+ table_str.length= filename_to_tablename(file->name,
+ tname, sizeof(tname)-1);
+ table_str.str= (char*) sql_memdup(tname, table_str.length + 1);
+ Table_ident *old_ident= new Table_ident(thd, *old_db, table_str, 0);
+ Table_ident *new_ident= new Table_ident(thd, new_db, table_str, 0);
+ if (!old_ident || !new_ident ||
+ !sl->add_table_to_list(thd, old_ident, NULL,
+ TL_OPTION_UPDATING, TL_IGNORE,
+ MDL_EXCLUSIVE) ||
+ !sl->add_table_to_list(thd, new_ident, NULL,
+ TL_OPTION_UPDATING, TL_IGNORE,
+ MDL_EXCLUSIVE))
+ {
+ error= 1;
+ my_dirend(dirp);
+ goto exit;
+ }
+ }
+ my_dirend(dirp);
+ }
+
+ if ((table_list= thd->lex->query_tables) &&
+ (error= mysql_rename_tables(thd, table_list, 1)))
+ {
+ /*
+ Failed to move all tables from the old database to the new one.
+ In the best case mysql_rename_tables() moved all tables back to the old
+ database. In the worst case mysql_rename_tables() moved some tables
+ to the new database, then failed, then started to move the tables back,
+ and then failed again. In this situation we have some tables in the
+ old database and some tables in the new database.
+ Let's delete the option file, and then the new database directory.
+ If some tables were left in the new directory, rmdir() will fail.
+ It garantees we never loose any tables.
+ */
+ build_table_filename(path, sizeof(path)-1,
+ new_db.str,"",MY_DB_OPT_FILE, 0);
+ mysql_file_delete(key_file_dbopt, path, MYF(MY_WME));
+ length= build_table_filename(path, sizeof(path)-1, new_db.str, "", "", 0);
+ if (length && path[length-1] == FN_LIBCHAR)
+ path[length-1]=0; // remove ending '\'
+ rmdir(path);
+ goto exit;
+ }
+
+
+ /*
+ Step3: move all remaining files to the new db's directory.
+ Skip db opt file: it's been created by mysql_create_db() in
+ the new directory, and will be dropped by mysql_rm_db() in the old one.
+ Trigger TRN and TRG files are be moved as regular files at the moment,
+ without any special treatment.
+
+ Triggers without explicit database qualifiers in table names work fine:
+ use d1;
+ create trigger trg1 before insert on t2 for each row set @a:=1
+ rename database d1 to d2;
+
+ TODO: Triggers, having the renamed database explicitely written
+ in the table qualifiers.
+ 1. when the same database is renamed:
+ create trigger d1.trg1 before insert on d1.t1 for each row set @a:=1;
+ rename database d1 to d2;
+ Problem: After database renaming, the trigger's body
+ still points to the old database d1.
+ 2. when another database is renamed:
+ create trigger d3.trg1 before insert on d3.t1 for each row
+ insert into d1.t1 values (...);
+ rename database d1 to d2;
+ Problem: After renaming d1 to d2, the trigger's body
+ in the database d3 still points to database d1.
+ */
+
+ if ((dirp = my_dir(path,MYF(MY_DONT_SORT))))
+ {
+ uint nfiles= (uint) dirp->number_of_files;
+ for (uint idx=0 ; idx < nfiles ; idx++)
+ {
+ FILEINFO *file= dirp->dir_entry + idx;
+ char oldname[FN_REFLEN + 1], newname[FN_REFLEN + 1];
+ DBUG_PRINT("info",("Examining: %s", file->name));
+
+ /* skiping MY_DB_OPT_FILE */
+ if (!my_strcasecmp(files_charset_info, file->name, MY_DB_OPT_FILE))
+ continue;
+
+ /* pass empty file name, and file->name as extension to avoid encoding */
+ build_table_filename(oldname, sizeof(oldname)-1,
+ old_db->str, "", file->name, 0);
+ build_table_filename(newname, sizeof(newname)-1,
+ new_db.str, "", file->name, 0);
+ mysql_file_rename(key_file_misc, oldname, newname, MYF(MY_WME));
+ }
+ my_dirend(dirp);
+ }
+
+ /*
+ Step7: drop the old database.
+ query_cache_invalidate(olddb) is done inside mysql_rm_db(), no need
+ to execute them again.
+ mysql_rm_db() also "unuses" if we drop the current database.
+ */
+ error= mysql_rm_db(thd, old_db->str, 0, 1);
+
+ /* Step8: logging */
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= query_error_code(thd, TRUE);
+ Query_log_event qinfo(thd, thd->query(), thd->query_length(),
+ FALSE, TRUE, TRUE, errcode);
+ thd->clear_error();
+ error|= mysql_bin_log.write(&qinfo);
+ }
+
+ /* Step9: Let's do "use newdb" if we renamed the current database */
+ if (change_to_newdb)
+ error|= mysql_change_db(thd, & new_db, FALSE);
+
+exit:
+ DBUG_RETURN(error);
+}
+
+
+
+/*
+ Check if there is directory for the database name.
+
+ SYNOPSIS
+ check_db_dir_existence()
+ db_name database name
+
+ RETURN VALUES
+ FALSE There is directory for the specified database name.
+ TRUE The directory does not exist.
+*/
+
+bool check_db_dir_existence(const char *db_name)
+{
+ char db_dir_path[FN_REFLEN + 1];
+ uint db_dir_path_len;
+
+ db_dir_path_len= build_table_filename(db_dir_path, sizeof(db_dir_path) - 1,
+ db_name, "", "", 0);
+
+ if (db_dir_path_len && db_dir_path[db_dir_path_len - 1] == FN_LIBCHAR)
+ db_dir_path[db_dir_path_len - 1]= 0;
+
+ /* Check access. */
+
+ return my_access(db_dir_path, F_OK);
+}
diff --git a/sql/sql_db.h b/sql/sql_db.h
new file mode 100644
index 00000000000..62d379c515d
--- /dev/null
+++ b/sql/sql_db.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_DB_INCLUDED
+#define SQL_DB_INCLUDED
+
+#include "hash.h" /* HASH */
+
+class THD;
+
+int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create, bool silent);
+bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create);
+bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent);
+bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db);
+bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name,
+ bool force_switch);
+
+bool mysql_opt_change_db(THD *thd,
+ const LEX_STRING *new_db_name,
+ LEX_STRING *saved_db_name,
+ bool force_switch,
+ bool *cur_db_changed);
+bool my_dboptions_cache_init(void);
+void my_dboptions_cache_free(void);
+bool check_db_dir_existence(const char *db_name);
+bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create);
+bool load_db_opt_by_name(THD *thd, const char *db_name,
+ HA_CREATE_INFO *db_create_info);
+CHARSET_INFO *get_default_db_collation(THD *thd, const char *db_name);
+bool my_dbopt_init(void);
+void my_dbopt_cleanup(void);
+
+#define MY_DB_OPT_FILE "db.opt"
+
+#endif /* SQL_DB_INCLUDED */
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
new file mode 100644
index 00000000000..5292b964576
--- /dev/null
+++ b/sql/sql_delete.cc
@@ -0,0 +1,1296 @@
+/*
+ Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/*
+ Delete of records tables.
+
+ Multi-table deletes were introduced by Monty and Sinisa
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_delete.h"
+#include "sql_cache.h" // query_cache_*
+#include "sql_base.h" // open_temprary_table
+#include "sql_table.h" // build_table_filename
+#include "lock.h" // unlock_table_name
+#include "sql_view.h" // check_key_in_view, mysql_frm_type
+#include "sql_parse.h" // mysql_init_select
+#include "sql_acl.h" // *_ACL
+#include "filesort.h" // filesort
+#include "sql_handler.h" // mysql_ha_rm_tables
+#include "sql_select.h"
+#include "sp_head.h"
+#include "sql_trigger.h"
+#include "sql_statistics.h"
+#include "transaction.h"
+#include "records.h" // init_read_record,
+#include "sql_derived.h" // mysql_handle_list_of_derived
+ // end_read_record
+#include "sql_partition.h" // make_used_partitions_str
+
+/*
+ @brief
+ Print query plan of a single-table DELETE command
+
+ @detail
+ This function is used by EXPLAIN DELETE and by SHOW EXPLAIN when it is
+ invoked on a running DELETE statement.
+*/
+
+void Delete_plan::save_explain_data(Explain_query *query)
+{
+ Explain_delete* explain= new Explain_delete;
+
+ if (deleting_all_rows)
+ {
+ explain->deleting_all_rows= true;
+ explain->select_type= "SIMPLE";
+ explain->rows= scanned_rows;
+ }
+ else
+ {
+ explain->deleting_all_rows= false;
+ Update_plan::save_explain_data_intern(query, explain);
+ }
+
+ query->add_upd_del_plan(explain);
+}
+
+
+void Update_plan::save_explain_data(Explain_query *query)
+{
+ Explain_update* explain= new Explain_update;
+ save_explain_data_intern(query, explain);
+ query->add_upd_del_plan(explain);
+}
+
+
+void Update_plan::save_explain_data_intern(Explain_query *query,
+ Explain_update *explain)
+{
+ explain->select_type= "SIMPLE";
+ explain->table_name.append(table->pos_in_table_list->alias);
+
+ explain->impossible_where= false;
+ explain->no_partitions= false;
+
+ if (impossible_where)
+ {
+ explain->impossible_where= true;
+ return;
+ }
+
+ if (no_partitions)
+ {
+ explain->no_partitions= true;
+ return;
+ }
+
+ select_lex->set_explain_type(TRUE);
+ explain->select_type= select_lex->type;
+ /* Partitions */
+ {
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info;
+ if ((part_info= table->part_info))
+ {
+ make_used_partitions_str(part_info, &explain->used_partitions);
+ explain->used_partitions_set= true;
+ }
+ else
+ explain->used_partitions_set= false;
+#else
+ /* just produce empty column if partitioning is not compiled in */
+ explain->used_partitions_set= false;
+#endif
+ }
+
+
+ /* Set jtype */
+ if (select && select->quick)
+ {
+ int quick_type= select->quick->get_type();
+ if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) ||
+ (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) ||
+ (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
+ (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
+ explain->jtype= JT_INDEX_MERGE;
+ else
+ explain->jtype= JT_RANGE;
+ }
+ else
+ {
+ if (index == MAX_KEY)
+ explain->jtype= JT_ALL;
+ else
+ explain->jtype= JT_NEXT;
+ }
+
+ explain->using_where= MY_TEST(select && select->cond);
+ explain->using_filesort= using_filesort;
+ explain->using_io_buffer= using_io_buffer;
+
+ make_possible_keys_line(table, possible_keys, &explain->possible_keys_line);
+
+ explain->quick_info= NULL;
+
+ /* Calculate key_len */
+ if (select && select->quick)
+ {
+ explain->quick_info= select->quick->get_explain(mem_root);
+ }
+ else
+ {
+ if (index != MAX_KEY)
+ {
+ explain->key_str.append(table->key_info[index].name);
+ char buf[64];
+ size_t length;
+ length= longlong10_to_str(table->key_info[index].key_length, buf, 10) - buf;
+ explain->key_len_str.append(buf, length);
+ }
+ }
+ explain->rows= scanned_rows;
+
+ if (select && select->quick &&
+ select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
+ {
+ explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick,
+ &explain->mrr_type);
+ }
+
+ bool skip= updating_a_view;
+
+ /* Save subquery children */
+ for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit();
+ unit;
+ unit= unit->next_unit())
+ {
+ if (skip)
+ {
+ skip= false;
+ continue;
+ }
+ /*
+ Display subqueries only if they are not parts of eliminated WHERE/ON
+ clauses.
+ */
+ if (!(unit->item && unit->item->eliminated))
+ explain->add_child(unit->first_select()->select_number);
+ }
+}
+
+
+/**
+ Implement DELETE SQL word.
+
+ @note Like implementations of other DDL/DML in MySQL, this function
+ relies on the caller to close the thread tables. This is done in the
+ end of dispatch_command().
+*/
+
+bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
+ SQL_I_List<ORDER> *order_list, ha_rows limit,
+ ulonglong options, select_result *result)
+{
+ bool will_batch;
+ int error, loc_error;
+ TABLE *table;
+ SQL_SELECT *select=0;
+ READ_RECORD info;
+ bool using_limit=limit != HA_POS_ERROR;
+ bool transactional_table, safe_update, const_cond;
+ bool const_cond_result;
+ ha_rows deleted= 0;
+ bool reverse= FALSE;
+ ORDER *order= (ORDER *) ((order_list && order_list->elements) ?
+ order_list->first : NULL);
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ killed_state killed_status= NOT_KILLED;
+ THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE;
+ bool with_select= !select_lex->item_list.is_empty();
+ Delete_plan query_plan(thd->mem_root);
+ query_plan.index= MAX_KEY;
+ query_plan.using_filesort= FALSE;
+ DBUG_ENTER("mysql_delete");
+
+ create_explain_query(thd->lex, thd->mem_root);
+ if (open_and_lock_tables(thd, table_list, TRUE, 0))
+ DBUG_RETURN(TRUE);
+
+ if (mysql_handle_list_of_derived(thd->lex, table_list, DT_MERGE_FOR_INSERT))
+ DBUG_RETURN(TRUE);
+ if (mysql_handle_list_of_derived(thd->lex, table_list, DT_PREPARE))
+ DBUG_RETURN(TRUE);
+
+ if (!table_list->single_table_updatable())
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "DELETE");
+ DBUG_RETURN(TRUE);
+ }
+ if (!(table= table_list->table) || !table->created)
+ {
+ my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0),
+ table_list->view_db.str, table_list->view_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ THD_STAGE_INFO(thd, stage_init);
+ table->map=1;
+ query_plan.select_lex= &thd->lex->select_lex;
+ query_plan.table= table;
+ query_plan.updating_a_view= MY_TEST(table_list->view);
+
+ if (mysql_prepare_delete(thd, table_list, select_lex->with_wild,
+ select_lex->item_list, &conds))
+ DBUG_RETURN(TRUE);
+
+ (void) result->prepare(select_lex->item_list, NULL);
+
+ if (thd->lex->current_select->first_cond_optimization)
+ {
+ thd->lex->current_select->save_leaf_tables(thd);
+ thd->lex->current_select->first_cond_optimization= 0;
+ }
+ /* check ORDER BY even if it can be ignored */
+ if (order)
+ {
+ TABLE_LIST tables;
+ List<Item> fields;
+ List<Item> all_fields;
+
+ bzero((char*) &tables,sizeof(tables));
+ tables.table = table;
+ tables.alias = table_list->alias;
+
+ if (select_lex->setup_ref_array(thd, order_list->elements) ||
+ setup_order(thd, select_lex->ref_pointer_array, &tables,
+ fields, all_fields, order))
+ {
+ delete select;
+ free_underlaid_joins(thd, &thd->lex->select_lex);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ /* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */
+ if (select_lex->optimize_unflattened_subqueries(false))
+ DBUG_RETURN(TRUE);
+
+ const_cond= (!conds || conds->const_item());
+ safe_update= MY_TEST(thd->variables.option_bits & OPTION_SAFE_UPDATES);
+ if (safe_update && const_cond)
+ {
+ my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE,
+ ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ const_cond_result= const_cond && (!conds || conds->val_int());
+ if (thd->is_error())
+ {
+ /* Error evaluating val_int(). */
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Test if the user wants to delete all rows and deletion doesn't have
+ any side-effects (because of triggers), so we can use optimized
+ handler::delete_all_rows() method.
+
+ We can use delete_all_rows() if and only if:
+ - We allow new functions (not using option --skip-new), and are
+ not in safe mode (not using option --safe-mode)
+ - There is no limit clause
+ - The condition is constant
+ - If there is a condition, then it it produces a non-zero value
+ - If the current command is DELETE FROM with no where clause, then:
+ - We should not be binlogging this statement in row-based, and
+ - there should be no delete triggers associated with the table.
+ */
+ if (!with_select && !using_limit && const_cond_result &&
+ (!thd->is_current_stmt_binlog_format_row() &&
+ !(table->triggers && table->triggers->has_delete_triggers())))
+ {
+ /* Update the table->file->stats.records number */
+ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
+ ha_rows const maybe_deleted= table->file->stats.records;
+ DBUG_PRINT("debug", ("Trying to use delete_all_rows()"));
+
+ query_plan.set_delete_all_rows(maybe_deleted);
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+
+ if (!(error=table->file->ha_delete_all_rows()))
+ {
+ /*
+ If delete_all_rows() is used, it is not possible to log the
+ query in row format, so we have to log it in statement format.
+ */
+ query_type= THD::STMT_QUERY_TYPE;
+ error= -1;
+ deleted= maybe_deleted;
+ goto cleanup;
+ }
+ if (error != HA_ERR_WRONG_COMMAND)
+ {
+ table->file->print_error(error,MYF(0));
+ error=0;
+ goto cleanup;
+ }
+ /* Handler didn't support fast delete; Delete rows one by one */
+ }
+ if (conds)
+ {
+ Item::cond_result result;
+ conds= remove_eq_conds(thd, conds, &result);
+ if (result == Item::COND_FALSE) // Impossible where
+ {
+ limit= 0;
+ query_plan.set_impossible_where();
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+ }
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (prune_partitions(thd, table, conds))
+ {
+ free_underlaid_joins(thd, select_lex);
+
+ query_plan.set_no_partitions();
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+
+ my_ok(thd, 0);
+ DBUG_RETURN(0);
+ }
+#endif
+ /* Update the table->file->stats.records number */
+ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
+ set_statistics_for_table(thd, table);
+
+ table->covering_keys.clear_all();
+ table->quick_keys.clear_all(); // Can't use 'only index'
+
+ select=make_select(table, 0, 0, conds, 0, &error);
+ if (error)
+ DBUG_RETURN(TRUE);
+ if ((select && select->check_quick(thd, safe_update, limit)) || !limit)
+ {
+ query_plan.set_impossible_where();
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ /*
+ Error was already created by quick select evaluation (check_quick()).
+ TODO: Add error code output parameter to Item::val_xxx() methods.
+ Currently they rely on the user checking DA for
+ errors when unwinding the stack after calling Item::val_xxx().
+ */
+ if (thd->is_error())
+ DBUG_RETURN(TRUE);
+ my_ok(thd, 0);
+ DBUG_RETURN(0); // Nothing to delete
+ }
+
+ /* If running in safe sql mode, don't allow updates without keys */
+ if (table->quick_keys.is_clear_all())
+ {
+ thd->set_status_no_index_used();
+ if (safe_update && !using_limit)
+ {
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE,
+ ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ }
+ if (options & OPTION_QUICK)
+ (void) table->file->extra(HA_EXTRA_QUICK);
+
+ query_plan.scanned_rows= select? select->records: table->file->stats.records;
+ if (order)
+ {
+ table->update_const_key_parts(conds);
+ order= simple_remove_const(order, conds);
+
+ if (select && select->quick && select->quick->unique_key_range())
+ { // Single row select (always "ordered")
+ query_plan.using_filesort= FALSE;
+ query_plan.index= MAX_KEY;
+ }
+ else
+ {
+ ha_rows scanned_limit= query_plan.scanned_rows;
+ query_plan.index= get_index_for_order(order, table, select, limit,
+ &scanned_limit,
+ &query_plan.using_filesort,
+ &reverse);
+ if (!query_plan.using_filesort)
+ query_plan.scanned_rows= scanned_limit;
+ }
+ }
+
+ query_plan.select= select;
+ query_plan.possible_keys= select? select->possible_keys: key_map(0);
+
+ /*
+ Ok, we have generated a query plan for the DELETE.
+ - if we're running EXPLAIN DELETE, goto produce explain output
+ - otherwise, execute the query plan
+ */
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+
+ query_plan.save_explain_data(thd->lex->explain);
+
+ DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start",
+ dbug_serve_apcs(thd, 1););
+
+ if (query_plan.using_filesort)
+ {
+ ha_rows examined_rows;
+ ha_rows found_rows;
+ uint length= 0;
+ SORT_FIELD *sortorder;
+
+ {
+ DBUG_ASSERT(query_plan.index == MAX_KEY);
+ table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE),
+ MYF(MY_FAE | MY_ZEROFILL |
+ MY_THREAD_SPECIFIC));
+
+ if (!(sortorder= make_unireg_sortorder(order, &length, NULL)) ||
+ (table->sort.found_records= filesort(thd, table, sortorder, length,
+ select, HA_POS_ERROR,
+ true,
+ &examined_rows, &found_rows))
+ == HA_POS_ERROR)
+ {
+ delete select;
+ free_underlaid_joins(thd, &thd->lex->select_lex);
+ DBUG_RETURN(TRUE);
+ }
+ thd->inc_examined_row_count(examined_rows);
+ /*
+ Filesort has already found and selected the rows we want to delete,
+ so we don't need the where clause
+ */
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ select= 0;
+ }
+ }
+
+ /* If quick select is used, initialize it before retrieving rows. */
+ if (select && select->quick && select->quick->reset())
+ {
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ DBUG_RETURN(TRUE);
+ }
+ if (query_plan.index == MAX_KEY || (select && select->quick))
+ {
+ if (init_read_record(&info, thd, table, select, 1, 1, FALSE))
+ {
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else
+ init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse);
+
+ init_ftfuncs(thd, select_lex, 1);
+ THD_STAGE_INFO(thd, stage_updating);
+
+ if (table->prepare_triggers_for_delete_stmt_or_event())
+ {
+ will_batch= FALSE;
+ }
+ else
+ will_batch= !table->file->start_bulk_delete();
+
+ table->mark_columns_needed_for_delete();
+
+ if (with_select)
+ {
+ if (result->send_result_set_metadata(select_lex->item_list,
+ Protocol::SEND_NUM_ROWS |
+ Protocol::SEND_EOF))
+ goto cleanup;
+ }
+
+ while (!(error=info.read_record(&info)) && !thd->killed &&
+ ! thd->is_error())
+ {
+ if (table->vfield)
+ update_virtual_fields(thd, table,
+ table->triggers ? VCOL_UPDATE_ALL :
+ VCOL_UPDATE_FOR_READ);
+ thd->inc_examined_row_count(1);
+ // thd->is_error() is tested to disallow delete row on error
+ if (!select || select->skip_record(thd) > 0)
+ {
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_BEFORE, FALSE))
+ {
+ error= 1;
+ break;
+ }
+
+ if (with_select && result->send_data(select_lex->item_list) < 0)
+ {
+ error=1;
+ break;
+ }
+
+ if (!(error= table->file->ha_delete_row(table->record[0])))
+ {
+ deleted++;
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_AFTER, FALSE))
+ {
+ error= 1;
+ break;
+ }
+ if (!--limit && using_limit)
+ {
+ error= -1;
+ break;
+ }
+ }
+ else
+ {
+ table->file->print_error(error,
+ MYF(thd->lex->ignore ? ME_JUST_WARNING : 0));
+ if (thd->is_error())
+ {
+ error= 1;
+ break;
+ }
+ }
+ }
+ /*
+ Don't try unlocking the row if skip_record reported an error since in
+ this case the transaction might have been rolled back already.
+ */
+ else if (!thd->is_error())
+ table->file->unlock_row(); // Row failed selection, release lock on it
+ else
+ break;
+ }
+ killed_status= thd->killed;
+ if (killed_status != NOT_KILLED || thd->is_error())
+ error= 1; // Aborted
+ if (will_batch && (loc_error= table->file->end_bulk_delete()))
+ {
+ if (error != 1)
+ table->file->print_error(loc_error,MYF(0));
+ error=1;
+ }
+ THD_STAGE_INFO(thd, stage_end);
+ end_read_record(&info);
+ if (options & OPTION_QUICK)
+ (void) table->file->extra(HA_EXTRA_NORMAL);
+
+cleanup:
+ /*
+ Invalidate the table in the query cache if something changed. This must
+ be before binlog writing and ha_autocommit_...
+ */
+ if (deleted)
+ {
+ query_cache_invalidate3(thd, table_list, 1);
+ }
+
+ if (thd->lex->current_select->first_cond_optimization)
+ {
+ thd->lex->current_select->save_leaf_tables(thd);
+ thd->lex->current_select->first_cond_optimization= 0;
+ }
+
+ delete select;
+ transactional_table= table->file->has_transactions();
+
+ if (!transactional_table && deleted > 0)
+ thd->transaction.stmt.modified_non_trans_table=
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ /* See similar binlogging code in sql_update.cc, for comments */
+ if ((error < 0) || thd->transaction.stmt.modified_non_trans_table)
+ {
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= 0;
+ if (error < 0)
+ thd->clear_error();
+ else
+ errcode= query_error_code(thd, killed_status == NOT_KILLED);
+
+ /*
+ [binlog]: If 'handler::delete_all_rows()' was called and the
+ storage engine does not inject the rows itself, we replicate
+ statement-based; otherwise, 'ha_delete_row()' was used to
+ delete specific rows which we might log row-based.
+ */
+ int log_result= thd->binlog_query(query_type,
+ thd->query(), thd->query_length(),
+ transactional_table, FALSE, FALSE,
+ errcode);
+
+ if (log_result)
+ {
+ error=1;
+ }
+ }
+ }
+ DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table);
+ free_underlaid_joins(thd, select_lex);
+ if (error < 0 ||
+ (thd->lex->ignore && !thd->is_error() && !thd->is_fatal_error))
+ {
+ if (!with_select)
+ my_ok(thd, deleted);
+ else
+ result->send_eof();
+ DBUG_PRINT("info",("%ld records deleted",(long) deleted));
+ }
+ DBUG_RETURN(error >= 0 || thd->is_error());
+
+ /* Special exits */
+exit_without_my_ok:
+ query_plan.save_explain_data(thd->lex->explain);
+ int err2= thd->lex->explain->send_explain(thd);
+
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ //table->set_keyread(false);
+ DBUG_RETURN((err2 || thd->is_error() || thd->killed) ? 1 : 0);
+}
+
+
+/*
+ Prepare items in DELETE statement
+
+ SYNOPSIS
+ mysql_prepare_delete()
+ thd - thread handler
+ table_list - global/local table list
+ wild_num - number of wildcards used in optional SELECT clause
+ field_list - list of items in optional SELECT clause
+ conds - conditions
+
+ RETURN VALUE
+ FALSE OK
+ TRUE error
+*/
+ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list,
+ uint wild_num, List<Item> &field_list, Item **conds)
+{
+ Item *fake_conds= 0;
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ DBUG_ENTER("mysql_prepare_delete");
+ List<Item> all_fields;
+
+ thd->lex->allow_sum_func= 0;
+ if (setup_tables_and_check_access(thd, &thd->lex->select_lex.context,
+ &thd->lex->select_lex.top_join_list,
+ table_list,
+ select_lex->leaf_tables, FALSE,
+ DELETE_ACL, SELECT_ACL, TRUE))
+ DBUG_RETURN(TRUE);
+ if ((wild_num && setup_wild(thd, table_list, field_list, NULL, wild_num)) ||
+ setup_fields(thd, NULL, field_list, MARK_COLUMNS_READ, NULL, 0) ||
+ setup_conds(thd, table_list, select_lex->leaf_tables, conds) ||
+ setup_ftfuncs(select_lex))
+ DBUG_RETURN(TRUE);
+ if (!table_list->single_table_updatable() ||
+ check_key_in_view(thd, table_list))
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "DELETE");
+ DBUG_RETURN(TRUE);
+ }
+ {
+ TABLE_LIST *duplicate;
+ if ((duplicate= unique_table(thd, table_list, table_list->next_global, 0)))
+ {
+ update_non_unique_table_error(table_list, "DELETE", duplicate);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (select_lex->inner_refs_list.elements &&
+ fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array))
+ DBUG_RETURN(TRUE);
+
+ select_lex->fix_prepare_information(thd, conds, &fake_conds);
+ DBUG_RETURN(FALSE);
+}
+
+
+/***************************************************************************
+ Delete multiple tables from join
+***************************************************************************/
+
+#define MEM_STRIP_BUF_SIZE current_thd->variables.sortbuff_size
+
+extern "C" int refpos_order_cmp(void* arg, const void *a,const void *b)
+{
+ handler *file= (handler*)arg;
+ return file->cmp_ref((const uchar*)a, (const uchar*)b);
+}
+
+/*
+ make delete specific preparation and checks after opening tables
+
+ SYNOPSIS
+ mysql_multi_delete_prepare()
+ thd thread handler
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+int mysql_multi_delete_prepare(THD *thd)
+{
+ LEX *lex= thd->lex;
+ TABLE_LIST *aux_tables= lex->auxiliary_table_list.first;
+ TABLE_LIST *target_tbl;
+ DBUG_ENTER("mysql_multi_delete_prepare");
+
+ if (mysql_handle_derived(lex, DT_INIT))
+ DBUG_RETURN(TRUE);
+ if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT))
+ DBUG_RETURN(TRUE);
+ if (mysql_handle_derived(lex, DT_PREPARE))
+ DBUG_RETURN(TRUE);
+ /*
+ setup_tables() need for VIEWs. JOIN::prepare() will not do it second
+ time.
+
+ lex->query_tables also point on local list of DELETE SELECT_LEX
+ */
+ if (setup_tables_and_check_access(thd, &thd->lex->select_lex.context,
+ &thd->lex->select_lex.top_join_list,
+ lex->query_tables,
+ lex->select_lex.leaf_tables, FALSE,
+ DELETE_ACL, SELECT_ACL, FALSE))
+ DBUG_RETURN(TRUE);
+
+ if (lex->select_lex.handle_derived(thd->lex, DT_MERGE))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Multi-delete can't be constructed over-union => we always have
+ single SELECT on top and have to check underlying SELECTs of it
+ */
+ lex->select_lex.exclude_from_table_unique_test= TRUE;
+ /* Fix tables-to-be-deleted-from list to point at opened tables */
+ for (target_tbl= (TABLE_LIST*) aux_tables;
+ target_tbl;
+ target_tbl= target_tbl->next_local)
+ {
+
+ target_tbl->table= target_tbl->correspondent_table->table;
+ if (target_tbl->correspondent_table->is_multitable())
+ {
+ my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0),
+ target_tbl->correspondent_table->view_db.str,
+ target_tbl->correspondent_table->view_name.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (!target_tbl->correspondent_table->single_table_updatable() ||
+ check_key_in_view(thd, target_tbl->correspondent_table))
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0),
+ target_tbl->table_name, "DELETE");
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Check that table from which we delete is not used somewhere
+ inside subqueries/view.
+ */
+ {
+ TABLE_LIST *duplicate;
+ if ((duplicate= unique_table(thd, target_tbl->correspondent_table,
+ lex->query_tables, 0)))
+ {
+ update_non_unique_table_error(target_tbl->correspondent_table,
+ "DELETE", duplicate);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ /*
+ Reset the exclude flag to false so it doesn't interfare
+ with further calls to unique_table
+ */
+ lex->select_lex.exclude_from_table_unique_test= FALSE;
+
+ if (lex->save_prep_leaf_tables())
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+multi_delete::multi_delete(TABLE_LIST *dt, uint num_of_tables_arg)
+ : delete_tables(dt), deleted(0), found(0),
+ num_of_tables(num_of_tables_arg), error(0),
+ do_delete(0), transactional_tables(0), normal_tables(0), error_handled(0)
+{
+ tempfiles= (Unique **) sql_calloc(sizeof(Unique *) * num_of_tables);
+}
+
+
+int
+multi_delete::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
+{
+ DBUG_ENTER("multi_delete::prepare");
+ unit= u;
+ do_delete= 1;
+ THD_STAGE_INFO(thd, stage_deleting_from_main_table);
+ SELECT_LEX *select_lex= u->first_select();
+ if (select_lex->first_cond_optimization)
+ {
+ if (select_lex->handle_derived(thd->lex, DT_MERGE))
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(0);
+}
+
+
+bool
+multi_delete::initialize_tables(JOIN *join)
+{
+ TABLE_LIST *walk;
+ Unique **tempfiles_ptr;
+ DBUG_ENTER("initialize_tables");
+
+ if ((thd->variables.option_bits & OPTION_SAFE_UPDATES) && error_if_full_join(join))
+ DBUG_RETURN(1);
+
+ table_map tables_to_delete_from=0;
+ delete_while_scanning= 1;
+ for (walk= delete_tables; walk; walk= walk->next_local)
+ {
+ TABLE_LIST *tbl= walk->correspondent_table->find_table_for_update();
+ tables_to_delete_from|= tbl->table->map;
+ if (delete_while_scanning &&
+ unique_table(thd, tbl, join->tables_list, false))
+ {
+ /*
+ If the table we are going to delete from appears
+ in join, we need to defer delete. So the delete
+ doesn't interfers with the scaning of results.
+ */
+ delete_while_scanning= 0;
+ }
+ }
+
+
+ walk= delete_tables;
+
+ for (JOIN_TAB *tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS,
+ WITH_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ if (!tab->bush_children && tab->table->map & tables_to_delete_from)
+ {
+ /* We are going to delete from this table */
+ TABLE *tbl=walk->table=tab->table;
+ walk= walk->next_local;
+ /* Don't use KEYREAD optimization on this table */
+ tbl->no_keyread=1;
+ /* Don't use record cache */
+ tbl->no_cache= 1;
+ tbl->covering_keys.clear_all();
+ if (tbl->file->has_transactions())
+ transactional_tables= 1;
+ else
+ normal_tables= 1;
+ tbl->prepare_triggers_for_delete_stmt_or_event();
+ tbl->prepare_for_position();
+ tbl->mark_columns_needed_for_delete();
+ }
+ else if ((tab->type != JT_SYSTEM && tab->type != JT_CONST) &&
+ walk == delete_tables)
+ {
+ /*
+ We are not deleting from the table we are scanning. In this
+ case send_data() shouldn't delete any rows a we may touch
+ the rows in the deleted table many times
+ */
+ delete_while_scanning= 0;
+ }
+ }
+ walk= delete_tables;
+ tempfiles_ptr= tempfiles;
+ if (delete_while_scanning)
+ {
+ table_being_deleted= delete_tables;
+ walk= walk->next_local;
+ }
+ for (;walk ;walk= walk->next_local)
+ {
+ TABLE *table=walk->table;
+ *tempfiles_ptr++= new Unique (refpos_order_cmp,
+ (void *) table->file,
+ table->file->ref_length,
+ MEM_STRIP_BUF_SIZE);
+ }
+ init_ftfuncs(thd, thd->lex->current_select, 1);
+ DBUG_RETURN(thd->is_fatal_error != 0);
+}
+
+
+multi_delete::~multi_delete()
+{
+ for (table_being_deleted= delete_tables;
+ table_being_deleted;
+ table_being_deleted= table_being_deleted->next_local)
+ {
+ TABLE *table= table_being_deleted->table;
+ table->no_keyread=0;
+ }
+
+ for (uint counter= 0; counter < num_of_tables; counter++)
+ {
+ if (tempfiles[counter])
+ delete tempfiles[counter];
+ }
+}
+
+
+int multi_delete::send_data(List<Item> &values)
+{
+ int secure_counter= delete_while_scanning ? -1 : 0;
+ TABLE_LIST *del_table;
+ DBUG_ENTER("multi_delete::send_data");
+
+ bool ignore= thd->lex->ignore;
+
+ for (del_table= delete_tables;
+ del_table;
+ del_table= del_table->next_local, secure_counter++)
+ {
+ TABLE *table= del_table->table;
+
+ /* Check if we are using outer join and we didn't find the row */
+ if (table->status & (STATUS_NULL_ROW | STATUS_DELETED))
+ continue;
+
+ table->file->position(table->record[0]);
+ found++;
+
+ if (secure_counter < 0)
+ {
+ /* We are scanning the current table */
+ DBUG_ASSERT(del_table == table_being_deleted);
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_BEFORE, FALSE))
+ DBUG_RETURN(1);
+ table->status|= STATUS_DELETED;
+ if (!(error=table->file->ha_delete_row(table->record[0])))
+ {
+ deleted++;
+ if (!table->file->has_transactions())
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_AFTER, FALSE))
+ DBUG_RETURN(1);
+ }
+ else if (!ignore)
+ {
+ /*
+ If the IGNORE option is used errors caused by ha_delete_row don't
+ have to stop the iteration.
+ */
+ table->file->print_error(error,MYF(0));
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ error=tempfiles[secure_counter]->unique_add((char*) table->file->ref);
+ if (error)
+ {
+ error= 1; // Fatal error
+ DBUG_RETURN(1);
+ }
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+void multi_delete::abort_result_set()
+{
+ DBUG_ENTER("multi_delete::abort_result_set");
+
+ /* the error was handled or nothing deleted and no side effects return */
+ if (error_handled ||
+ (!thd->transaction.stmt.modified_non_trans_table && !deleted))
+ DBUG_VOID_RETURN;
+
+ /* Something already deleted so we have to invalidate cache */
+ if (deleted)
+ query_cache_invalidate3(thd, delete_tables, 1);
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ /*
+ If rows from the first table only has been deleted and it is
+ transactional, just do rollback.
+ The same if all tables are transactional, regardless of where we are.
+ In all other cases do attempt deletes ...
+ */
+ if (do_delete && normal_tables &&
+ (table_being_deleted != delete_tables ||
+ !table_being_deleted->table->file->has_transactions()))
+ {
+ /*
+ We have to execute the recorded do_deletes() and write info into the
+ error log
+ */
+ error= 1;
+ send_eof();
+ DBUG_ASSERT(error_handled);
+ DBUG_VOID_RETURN;
+ }
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ {
+ /*
+ there is only side effects; to binlog with the error
+ */
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= query_error_code(thd, thd->killed == NOT_KILLED);
+ /* possible error of writing binary log is ignored deliberately */
+ (void) thd->binlog_query(THD::ROW_QUERY_TYPE,
+ thd->query(), thd->query_length(),
+ transactional_tables, FALSE, FALSE, errcode);
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+
+/**
+ Do delete from other tables.
+
+ @retval 0 ok
+ @retval 1 error
+
+ @todo Is there any reason not use the normal nested-loops join? If not, and
+ there is no documentation supporting it, this method and callee should be
+ removed and there should be hooks within normal execution.
+*/
+
+int multi_delete::do_deletes()
+{
+ DBUG_ENTER("do_deletes");
+ DBUG_ASSERT(do_delete);
+
+ do_delete= 0; // Mark called
+ if (!found)
+ DBUG_RETURN(0);
+
+ table_being_deleted= (delete_while_scanning ? delete_tables->next_local :
+ delete_tables);
+
+ for (uint counter= 0; table_being_deleted;
+ table_being_deleted= table_being_deleted->next_local, counter++)
+ {
+ TABLE *table = table_being_deleted->table;
+ int local_error;
+ if (tempfiles[counter]->get(table))
+ DBUG_RETURN(1);
+
+ local_error= do_table_deletes(table, thd->lex->ignore);
+
+ if (thd->killed && !local_error)
+ DBUG_RETURN(1);
+
+ if (local_error == -1) // End of file
+ local_error = 0;
+
+ if (local_error)
+ DBUG_RETURN(local_error);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Implements the inner loop of nested-loops join within multi-DELETE
+ execution.
+
+ @param table The table from which to delete.
+
+ @param ignore If used, all non fatal errors will be translated
+ to warnings and we should not break the row-by-row iteration.
+
+ @return Status code
+
+ @retval 0 All ok.
+ @retval 1 Triggers or handler reported error.
+ @retval -1 End of file from handler.
+*/
+int multi_delete::do_table_deletes(TABLE *table, bool ignore)
+{
+ int local_error= 0;
+ READ_RECORD info;
+ ha_rows last_deleted= deleted;
+ DBUG_ENTER("do_deletes_for_table");
+
+ if (init_read_record(&info, thd, table, NULL, 0, 1, FALSE))
+ DBUG_RETURN(1);
+
+ /*
+ Ignore any rows not found in reference tables as they may already have
+ been deleted by foreign key handling
+ */
+ info.ignore_not_found_rows= 1;
+ bool will_batch= !table->file->start_bulk_delete();
+ while (!(local_error= info.read_record(&info)) && !thd->killed)
+ {
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_BEFORE, FALSE))
+ {
+ local_error= 1;
+ break;
+ }
+
+ local_error= table->file->ha_delete_row(table->record[0]);
+ if (local_error && !ignore)
+ {
+ table->file->print_error(local_error, MYF(0));
+ break;
+ }
+
+ /*
+ Increase the reported number of deleted rows only if no error occurred
+ during ha_delete_row.
+ Also, don't execute the AFTER trigger if the row operation failed.
+ */
+ if (!local_error)
+ {
+ deleted++;
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_AFTER, FALSE))
+ {
+ local_error= 1;
+ break;
+ }
+ }
+ }
+ if (will_batch)
+ {
+ int tmp_error= table->file->end_bulk_delete();
+ if (tmp_error && !local_error)
+ {
+ local_error= tmp_error;
+ table->file->print_error(local_error, MYF(0));
+ }
+ }
+ if (last_deleted != deleted && !table->file->has_transactions())
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+
+ end_read_record(&info);
+
+ DBUG_RETURN(local_error);
+}
+
+/*
+ Send ok to the client
+
+ return: 0 sucess
+ 1 error
+*/
+
+bool multi_delete::send_eof()
+{
+ killed_state killed_status= NOT_KILLED;
+ THD_STAGE_INFO(thd, stage_deleting_from_reference_tables);
+
+ /* Does deletes for the last n - 1 tables, returns 0 if ok */
+ int local_error= do_deletes(); // returns 0 if success
+
+ /* compute a total error to know if something failed */
+ local_error= local_error || error;
+ killed_status= (local_error == 0)? NOT_KILLED : thd->killed;
+ /* reset used flags */
+ THD_STAGE_INFO(thd, stage_end);
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ /*
+ We must invalidate the query cache before binlog writing and
+ ha_autocommit_...
+ */
+ if (deleted)
+ {
+ query_cache_invalidate3(thd, delete_tables, 1);
+ }
+ if ((local_error == 0) || thd->transaction.stmt.modified_non_trans_table)
+ {
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= 0;
+ if (local_error == 0)
+ thd->clear_error();
+ else
+ errcode= query_error_code(thd, killed_status == NOT_KILLED);
+ if (thd->binlog_query(THD::ROW_QUERY_TYPE,
+ thd->query(), thd->query_length(),
+ transactional_tables, FALSE, FALSE, errcode) &&
+ !normal_tables)
+ {
+ local_error=1; // Log write failed: roll back the SQL statement
+ }
+ }
+ }
+ if (local_error != 0)
+ error_handled= TRUE; // to force early leave from ::abort_result_set()
+
+ if (!local_error)
+ {
+ ::my_ok(thd, deleted);
+ }
+ return 0;
+}
+
diff --git a/sql/sql_delete.h b/sql/sql_delete.h
new file mode 100644
index 00000000000..9cd09dc5722
--- /dev/null
+++ b/sql/sql_delete.h
@@ -0,0 +1,35 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_DELETE_INCLUDED
+#define SQL_DELETE_INCLUDED
+
+#include "my_base.h" /* ha_rows */
+
+class THD;
+struct TABLE_LIST;
+class Item;
+class select_result;
+
+typedef class Item COND;
+template <typename T> class SQL_I_List;
+
+int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list,
+ uint wild_num, List<Item> &field_list, Item **conds);
+bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
+ SQL_I_List<ORDER> *order, ha_rows rows,
+ ulonglong options, select_result *result);
+
+#endif /* SQL_DELETE_INCLUDED */
diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc
new file mode 100644
index 00000000000..fdc615d0fae
--- /dev/null
+++ b/sql/sql_derived.cc
@@ -0,0 +1,984 @@
+/*
+ Copyright (c) 2002, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+
+/*
+ Derived tables
+ These were introduced by Sinisa <sinisa@mysql.com>
+*/
+
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_derived.h"
+#include "sql_select.h"
+#include "sql_base.h"
+#include "sql_view.h" // check_duplicate_names
+#include "sql_acl.h" // SELECT_ACL
+
+typedef bool (*dt_processor)(THD *thd, LEX *lex, TABLE_LIST *derived);
+
+bool mysql_derived_init(THD *thd, LEX *lex, TABLE_LIST *derived);
+bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived);
+bool mysql_derived_optimize(THD *thd, LEX *lex, TABLE_LIST *derived);
+bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived);
+bool mysql_derived_create(THD *thd, LEX *lex, TABLE_LIST *derived);
+bool mysql_derived_fill(THD *thd, LEX *lex, TABLE_LIST *derived);
+bool mysql_derived_reinit(THD *thd, LEX *lex, TABLE_LIST *derived);
+bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived);
+
+
+dt_processor processors[]=
+{
+ &mysql_derived_init,
+ &mysql_derived_prepare,
+ &mysql_derived_optimize,
+ &mysql_derived_merge,
+ &mysql_derived_merge_for_insert,
+ &mysql_derived_create,
+ &mysql_derived_fill,
+ &mysql_derived_reinit,
+};
+
+/*
+ Run specified phases on all derived tables/views in given LEX.
+
+ @param lex LEX for this thread
+ @param phases phases to run derived tables/views through
+
+ @return FALSE OK
+ @return TRUE Error
+*/
+bool
+mysql_handle_derived(LEX *lex, uint phases)
+{
+ bool res= FALSE;
+ THD *thd= lex->thd;
+ DBUG_ENTER("mysql_handle_derived");
+ DBUG_PRINT("enter", ("phases: 0x%x", phases));
+ if (!lex->derived_tables)
+ DBUG_RETURN(FALSE);
+
+ lex->thd->derived_tables_processing= TRUE;
+
+ for (uint phase= 0; phase < DT_PHASES && !res; phase++)
+ {
+ uint phase_flag= DT_INIT << phase;
+ if (phase_flag > phases)
+ break;
+ if (!(phases & phase_flag))
+ continue;
+ if (phase_flag >= DT_CREATE && !thd->fill_derived_tables())
+ break;
+
+ for (SELECT_LEX *sl= lex->all_selects_list;
+ sl && !res;
+ sl= sl->next_select_in_list())
+ {
+ TABLE_LIST *cursor= sl->get_table_list();
+ /*
+ DT_MERGE_FOR_INSERT is not needed for views/derived tables inside
+ subqueries. Views and derived tables of subqueries should be
+ processed normally.
+ */
+ if (phases == DT_MERGE_FOR_INSERT &&
+ cursor && cursor->top_table()->select_lex != &lex->select_lex)
+ continue;
+ for (;
+ cursor && !res;
+ cursor= cursor->next_local)
+ {
+ if (!cursor->is_view_or_derived() && phases == DT_MERGE_FOR_INSERT)
+ continue;
+ uint8 allowed_phases= (cursor->is_merged_derived() ? DT_PHASES_MERGE :
+ DT_PHASES_MATERIALIZE | DT_MERGE_FOR_INSERT);
+ /*
+ Skip derived tables to which the phase isn't applicable.
+ TODO: mark derived at the parse time, later set it's type
+ (merged or materialized)
+ */
+ if ((phase_flag != DT_PREPARE && !(allowed_phases & phase_flag)) ||
+ (cursor->merged_for_insert && phase_flag != DT_REINIT &&
+ phase_flag != DT_PREPARE))
+ continue;
+ res= (*processors[phase])(lex->thd, lex, cursor);
+ }
+ if (lex->describe)
+ {
+ /*
+ Force join->join_tmp creation, because we will use this JOIN
+ twice for EXPLAIN and we have to have unchanged join for EXPLAINing
+ */
+ sl->uncacheable|= UNCACHEABLE_EXPLAIN;
+ sl->master_unit()->uncacheable|= UNCACHEABLE_EXPLAIN;
+ }
+ }
+ }
+ lex->thd->derived_tables_processing= FALSE;
+ DBUG_RETURN(res);
+}
+
+/*
+ Run through phases for the given derived table/view.
+
+ @param lex LEX for this thread
+ @param derived the derived table to handle
+ @param phase_map phases to process tables/views through
+
+ @details
+
+ This function process the derived table (view) 'derived' to performs all
+ actions that are to be done on the table at the phases specified by
+ phase_map. The processing is carried out starting from the actions
+ performed at the earlier phases (those having smaller ordinal numbers).
+
+ @note
+ This function runs specified phases of the derived tables handling on the
+ given derived table/view. This function is used in the chain of calls:
+ SELECT_LEX::handle_derived ->
+ TABLE_LIST::handle_derived ->
+ mysql_handle_single_derived
+ This chain of calls implements the bottom-up handling of the derived tables:
+ i.e. most inner derived tables/views are handled first. This order is
+ required for the all phases except the merge and the create steps.
+ For the sake of code simplicity this order is kept for all phases.
+
+ @return FALSE ok
+ @return TRUE error
+*/
+
+bool
+mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases)
+{
+ bool res= FALSE;
+ THD *thd= lex->thd;
+ uint8 allowed_phases= (derived->is_merged_derived() ? DT_PHASES_MERGE :
+ DT_PHASES_MATERIALIZE);
+ DBUG_ENTER("mysql_handle_single_derived");
+ DBUG_PRINT("enter", ("phases: 0x%x allowed: 0x%x alias: '%s'",
+ phases, allowed_phases,
+ (derived->alias ? derived->alias : "<NULL>")));
+ if (!lex->derived_tables)
+ DBUG_RETURN(FALSE);
+
+ lex->thd->derived_tables_processing= TRUE;
+
+ for (uint phase= 0; phase < DT_PHASES; phase++)
+ {
+ uint phase_flag= DT_INIT << phase;
+ if (phase_flag > phases)
+ break;
+ if (!(phases & phase_flag))
+ continue;
+ /* Skip derived tables to which the phase isn't applicable. */
+ if (phase_flag != DT_PREPARE &&
+ !(allowed_phases & phase_flag))
+ continue;
+ if (phase_flag >= DT_CREATE && !thd->fill_derived_tables())
+ break;
+
+ if ((res= (*processors[phase])(lex->thd, lex, derived)))
+ break;
+ }
+ lex->thd->derived_tables_processing= FALSE;
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Run specified phases for derived tables/views in the given list
+
+ @param lex LEX for this thread
+ @param table_list list of derived tables/view to handle
+ @param phase_map phases to process tables/views through
+
+ @details
+ This function runs phases specified by the 'phases_map' on derived
+ tables/views found in the 'dt_list' with help of the
+ TABLE_LIST::handle_derived function.
+ 'lex' is passed as an argument to the TABLE_LIST::handle_derived.
+
+ @return FALSE ok
+ @return TRUE error
+*/
+
+bool
+mysql_handle_list_of_derived(LEX *lex, TABLE_LIST *table_list, uint phases)
+{
+ for (TABLE_LIST *tl= table_list; tl; tl= tl->next_local)
+ {
+ if (tl->is_view_or_derived() &&
+ tl->handle_derived(lex, phases))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Merge a derived table/view into the embedding select
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ This function merges the given derived table / view into the parent select
+ construction. Any derived table/reference to view occurred in the FROM
+ clause of the embedding select is represented by a TABLE_LIST structure a
+ pointer to which is passed to the function as in the parameter 'derived'.
+ This structure contains the number/map, alias, a link to SELECT_LEX of the
+ derived table and other info. If the 'derived' table is used in a nested join
+ then additionally the structure contains a reference to the ON expression
+ for this join.
+
+ The merge process results in elimination of the derived table (or the
+ reference to a view) such that:
+ - the FROM list of the derived table/view is wrapped into a nested join
+ after which the nest is added to the FROM list of the embedding select
+ - the WHERE condition of the derived table (view) is ANDed with the ON
+ condition attached to the table.
+
+ @note
+ Tables are merged into the leaf_tables list, original derived table is removed
+ from this list also. SELECT_LEX::table_list list is left untouched.
+ Where expression is merged with derived table's on_expr and can be found after
+ the merge through the SELECT_LEX::table_list.
+
+ Examples of the derived table/view merge:
+
+ Schema:
+ Tables: t1(f1), t2(f2), t3(f3)
+ View v1: SELECT f1 FROM t1 WHERE f1 < 1
+
+ Example with a view:
+ Before merge:
+
+ The query (Q1): SELECT f1,f2 FROM t2 LEFT JOIN v1 ON f1 = f2
+
+ (LEX of the main query)
+ |
+ (select_lex)
+ |
+ (FROM table list)
+ |
+ (join list)= t2, v1
+ / \
+ / (on_expr)= (f1 = f2)
+ |
+ (LEX of the v1 view)
+ |
+ (select_lex)= SELECT f1 FROM t1 WHERE f1 < 1
+
+
+ After merge:
+
+ The rewritten query Q1 (Q1'):
+ SELECT f1,f2 FROM t2 LEFT JOIN (t1) ON ((f1 = f2) and (f1 < 1))
+
+ (LEX of the main query)
+ |
+ (select_lex)
+ |
+ (FROM table list)
+ |
+ (join list)= t2, (t1)
+ \
+ (on_expr)= (f1 = f2) and (f1 < 1)
+
+ In this example table numbers are assigned as follows:
+ (outer select): t2 - 1, v1 - 2
+ (inner select): t1 - 1
+ After the merge table numbers will be:
+ (outer select): t2 - 1, t1 - 2
+
+ Example with a derived table:
+ The query Q2:
+ SELECT f1,f2
+ FROM (SELECT f1 FROM t1, t3 WHERE f1=f3 and f1 < 1) tt, t2
+ WHERE f1 = f2
+
+ Before merge:
+ (LEX of the main query)
+ |
+ (select_lex)
+ / \
+ (FROM table list) (WHERE clause)= (f1 = f2)
+ |
+ (join list)= tt, t2
+ / \
+ / (on_expr)= (empty)
+ /
+ (select_lex)= SELECT f1 FROM t1, t3 WHERE f1 = f3 and f1 < 1
+
+ After merge:
+
+ The rewritten query Q2 (Q2'):
+ SELECT f1,f2
+ FROM (t1, t3) JOIN t2 ON (f1 = f3 and f1 < 1)
+ WHERE f1 = f2
+
+ (LEX of the main query)
+ |
+ (select_lex)
+ / \
+ (FROM table list) (WHERE clause)= (f1 = f2)
+ |
+ (join list)= t2, (t1, t3)
+ \
+ (on_expr)= (f1 = f3 and f1 < 1)
+
+ In this example table numbers are assigned as follows:
+ (outer select): tt - 1, t2 - 2
+ (inner select): t1 - 1, t3 - 2
+ After the merge table numbers will be:
+ (outer select): t1 - 1, t2 - 2, t3 - 3
+
+ @return FALSE if derived table/view were successfully merged.
+ @return TRUE if an error occur.
+*/
+
+bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ bool res= FALSE;
+ SELECT_LEX *dt_select= derived->get_single_select();
+ table_map map;
+ uint tablenr;
+ SELECT_LEX *parent_lex= derived->select_lex;
+ Query_arena *arena, backup;
+ DBUG_ENTER("mysql_derived_merge");
+
+ if (derived->merged)
+ DBUG_RETURN(FALSE);
+
+ if (dt_select->uncacheable & UNCACHEABLE_RAND)
+ {
+ /* There is random function => fall back to materialization. */
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ DBUG_RETURN(FALSE);
+ }
+
+ if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI)
+ thd->save_prep_leaf_list= TRUE;
+
+ arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test
+ derived->merged= TRUE;
+
+ if (!derived->merged_for_insert ||
+ (derived->is_multitable() &&
+ (thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI)))
+ {
+ /*
+ Check whether there is enough free bits in table map to merge subquery.
+ If not - materialize it. This check isn't cached so when there is a big
+ and small subqueries, and the bigger one can't be merged it wouldn't
+ block the smaller one.
+ */
+ if (parent_lex->get_free_table_map(&map, &tablenr))
+ {
+ /* There is no enough table bits, fall back to materialization. */
+ goto unconditional_materialization;
+ }
+
+ if (dt_select->leaf_tables.elements + tablenr > MAX_TABLES)
+ {
+ /* There is no enough table bits, fall back to materialization. */
+ goto unconditional_materialization;
+ }
+
+ if (dt_select->options & OPTION_SCHEMA_TABLE)
+ parent_lex->options |= OPTION_SCHEMA_TABLE;
+
+ if (!derived->get_unit()->prepared)
+ {
+ dt_select->leaf_tables.empty();
+ make_leaves_list(dt_select->leaf_tables, derived, TRUE, 0);
+ }
+
+ derived->nested_join= (NESTED_JOIN*) thd->calloc(sizeof(NESTED_JOIN));
+ if (!derived->nested_join)
+ {
+ res= TRUE;
+ goto exit_merge;
+ }
+
+ /* Merge derived table's subquery in the parent select. */
+ if (parent_lex->merge_subquery(thd, derived, dt_select, tablenr, map))
+ {
+ res= TRUE;
+ goto exit_merge;
+ }
+
+ /*
+ exclude select lex so it doesn't show up in explain.
+ do this only for derived table as for views this is already done.
+
+ From sql_view.cc
+ Add subqueries units to SELECT into which we merging current view.
+ unit(->next)* chain starts with subqueries that are used by this
+ view and continues with subqueries that are used by other views.
+ We must not add any subquery twice (otherwise we'll form a loop),
+ to do this we remember in end_unit the first subquery that has
+ been already added.
+ */
+ derived->get_unit()->exclude_level();
+ if (parent_lex->join)
+ parent_lex->join->table_count+= dt_select->join->table_count - 1;
+ }
+ if (derived->get_unit()->prepared)
+ {
+ Item *expr= derived->on_expr;
+ expr= and_conds(expr, dt_select->join ? dt_select->join->conds : 0);
+ if (expr && (derived->prep_on_expr || expr != derived->on_expr))
+ {
+ derived->on_expr= expr;
+ derived->prep_on_expr= expr->copy_andor_structure(thd);
+ }
+ if (derived->on_expr &&
+ ((!derived->on_expr->fixed &&
+ derived->on_expr->fix_fields(thd, &derived->on_expr)) ||
+ derived->on_expr->check_cols(1)))
+ {
+ res= TRUE; /* purecov: inspected */
+ goto exit_merge;
+ }
+ // Update used tables cache according to new table map
+ if (derived->on_expr)
+ {
+ derived->on_expr->fix_after_pullout(parent_lex, &derived->on_expr);
+ fix_list_after_tbl_changes(parent_lex, &derived->nested_join->join_list);
+ }
+ }
+
+exit_merge:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(res);
+
+unconditional_materialization:
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ if (!derived->table || !derived->table->created)
+ res= mysql_derived_create(thd, lex, derived);
+ if (!res)
+ res= mysql_derived_fill(thd, lex, derived);
+ goto exit_merge;
+}
+
+
+/**
+ Merge a view for the embedding INSERT/UPDATE/DELETE
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ This function substitutes the derived table for the first table from
+ the query of the derived table thus making it a correct target table for the
+ INSERT/UPDATE/DELETE statements. As this operation is correct only for
+ single table views only, for multi table views this function does nothing.
+ The derived parameter isn't checked to be a view as derived tables aren't
+ allowed for INSERT/UPDATE/DELETE statements.
+
+ @return FALSE if derived table/view were successfully merged.
+ @return TRUE if an error occur.
+*/
+
+bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ DBUG_ENTER("mysql_derived_merge_for_insert");
+ DBUG_PRINT("enter", ("derived: %p", derived));
+ DBUG_PRINT("info", ("merged_for_insert: %d is_materialized_derived: %d "
+ "is_multitable: %d single_table_updatable: %d "
+ "merge_underlying_list: %d",
+ derived->merged_for_insert,
+ derived->is_materialized_derived(),
+ derived->is_multitable(),
+ derived->single_table_updatable(),
+ derived->merge_underlying_list != 0));
+ if (derived->merged_for_insert)
+ DBUG_RETURN(FALSE);
+ if (derived->is_materialized_derived())
+ DBUG_RETURN(mysql_derived_prepare(thd, lex, derived));
+ if ((thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI))
+ DBUG_RETURN(FALSE);
+ if (!derived->is_multitable())
+ {
+ if (!derived->single_table_updatable())
+ DBUG_RETURN(derived->create_field_translation(thd));
+ if (derived->merge_underlying_list)
+ {
+ derived->table= derived->merge_underlying_list->table;
+ derived->schema_table= derived->merge_underlying_list->schema_table;
+ derived->merged_for_insert= TRUE;
+ DBUG_ASSERT(derived->table);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Initialize a derived table/view
+
+ @param thd Thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @detail
+ Fill info about derived table/view without preparing an
+ underlying select. Such as: create a field translation for views, mark it as
+ a multitable if it is and so on.
+
+ @return
+ false OK
+ true Error
+*/
+
+
+bool mysql_derived_init(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ DBUG_ENTER("mysql_derived_init");
+ DBUG_PRINT("enter", ("derived: %p", derived));
+
+ // Skip already prepared views/DT
+ if (!unit || unit->prepared)
+ DBUG_RETURN(FALSE);
+
+ DBUG_RETURN(derived->init_derived(thd, TRUE));
+}
+
+
+/*
+ Create temporary table structure (but do not fill it)
+
+ @param thd Thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @detail
+ Prepare underlying select for a derived table/view. To properly resolve
+ names in the embedding query the TABLE structure is created. Actual table
+ is created later by the mysql_derived_create function.
+
+ This function is called before any command containing derived table
+ is executed. All types of derived tables are handled by this function:
+ - Anonymous derived tables, or
+ - Named derived tables (aka views).
+
+ The table reference, contained in @c derived, is updated with the
+ fields of a new temporary table.
+ Derived tables are stored in @c thd->derived_tables and closed by
+ close_thread_tables().
+
+ This function is part of the procedure that starts in
+ open_and_lock_tables(), a procedure that - among other things - introduces
+ new table and table reference objects (to represent derived tables) that
+ don't exist in the privilege database. This means that normal privilege
+ checking cannot handle them. Hence this function does some extra tricks in
+ order to bypass normal privilege checking, by exploiting the fact that the
+ current state of privilege verification is attached as GRANT_INFO structures
+ on the relevant TABLE and TABLE_REF objects.
+
+ For table references, the current state of accrued access is stored inside
+ TABLE_LIST::grant. Hence this function must update the state of fulfilled
+ privileges for the new TABLE_LIST, an operation which is normally performed
+ exclusively by the table and database access checking functions,
+ check_access() and check_grant(), respectively. This modification is done
+ for both views and anonymous derived tables: The @c SELECT privilege is set
+ as fulfilled by the user. However, if a view is referenced and the table
+ reference is queried against directly (see TABLE_LIST::referencing_view),
+ the state of privilege checking (GRANT_INFO struct) is copied as-is to the
+ temporary table.
+
+ Only the TABLE structure is created here, actual table is created by the
+ mysql_derived_create function.
+
+ @note This function sets @c SELECT_ACL for @c TEMPTABLE views as well as
+ anonymous derived tables, but this is ok since later access checking will
+ distinguish between them.
+
+ @see mysql_handle_derived(), mysql_derived_fill(), GRANT_INFO
+
+ @return
+ false OK
+ true Error
+*/
+
+bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ DBUG_ENTER("mysql_derived_prepare");
+ bool res= FALSE;
+ DBUG_PRINT("enter", ("unit 0x%lx", (ulong) unit));
+
+ // Skip already prepared views/DT
+ if (!unit || unit->prepared ||
+ (derived->merged_for_insert &&
+ !(derived->is_multitable() &&
+ (thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI))))
+ DBUG_RETURN(FALSE);
+
+ SELECT_LEX *first_select= unit->first_select();
+
+ /* prevent name resolving out of derived table */
+ for (SELECT_LEX *sl= first_select; sl; sl= sl->next_select())
+ {
+ sl->context.outer_context= 0;
+ // Prepare underlying views/DT first.
+ if ((res= sl->handle_derived(lex, DT_PREPARE)))
+ goto exit;
+
+ if (derived->outer_join && sl->first_cond_optimization)
+ {
+ /* Mark that table is part of OUTER JOIN and fields may be NULL */
+ for (TABLE_LIST *cursor= (TABLE_LIST*) sl->table_list.first;
+ cursor;
+ cursor= cursor->next_local)
+ cursor->outer_join|= JOIN_TYPE_OUTER;
+ }
+ }
+
+ unit->derived= derived;
+
+ if (!(derived->derived_result= new select_union))
+ DBUG_RETURN(TRUE); // out of memory
+
+ lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED;
+ // st_select_lex_unit::prepare correctly work for single select
+ if ((res= unit->prepare(thd, derived->derived_result, 0)))
+ goto exit;
+ lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED;
+ if ((res= check_duplicate_names(unit->types, 0)))
+ goto exit;
+
+ /*
+ Check whether we can merge this derived table into main select.
+ Depending on the result field translation will or will not
+ be created.
+ */
+ if (derived->init_derived(thd, FALSE))
+ goto exit;
+
+ /*
+ Temp table is created so that it hounours if UNION without ALL is to be
+ processed
+
+ As 'distinct' parameter we always pass FALSE (0), because underlying
+ query will control distinct condition by itself. Correct test of
+ distinct underlying query will be is_union &&
+ !unit->union_distinct->next_select() (i.e. it is union and last distinct
+ SELECT is last SELECT of UNION).
+ */
+ thd->create_tmp_table_for_derived= TRUE;
+ if (derived->derived_result->create_result_table(thd, &unit->types, FALSE,
+ (first_select->options |
+ thd->variables.option_bits |
+ TMP_TABLE_ALL_COLUMNS),
+ derived->alias,
+ FALSE, FALSE))
+ {
+ thd->create_tmp_table_for_derived= FALSE;
+ goto exit;
+ }
+ thd->create_tmp_table_for_derived= FALSE;
+
+ derived->table= derived->derived_result->table;
+ DBUG_ASSERT(derived->table);
+ if (derived->is_derived() && derived->is_merged_derived())
+ first_select->mark_as_belong_to_derived(derived);
+
+exit:
+ /* Hide "Unknown column" or "Unknown function" error */
+ if (derived->view)
+ {
+ if (thd->is_error() &&
+ (thd->get_stmt_da()->sql_errno() == ER_BAD_FIELD_ERROR ||
+ thd->get_stmt_da()->sql_errno() == ER_FUNC_INEXISTENT_NAME_COLLISION ||
+ thd->get_stmt_da()->sql_errno() == ER_SP_DOES_NOT_EXIST))
+ {
+ thd->clear_error();
+ my_error(ER_VIEW_INVALID, MYF(0), derived->db,
+ derived->table_name);
+ }
+ }
+
+ /*
+ if it is preparation PS only or commands that need only VIEW structure
+ then we do not need real data and we can skip execution (and parameters
+ is not defined, too)
+ */
+ if (res)
+ {
+ if (derived->table)
+ free_tmp_table(thd, derived->table);
+ delete derived->derived_result;
+ }
+ else
+ {
+ TABLE *table= derived->table;
+ table->derived_select_number= first_select->select_number;
+ table->s->tmp_table= INTERNAL_TMP_TABLE;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (derived->referencing_view)
+ table->grant= derived->grant;
+ else
+ {
+ table->grant.privilege= SELECT_ACL;
+ if (derived->is_derived())
+ derived->grant.privilege= SELECT_ACL;
+ }
+#endif
+ /* Add new temporary table to list of open derived tables */
+ table->next= thd->derived_tables;
+ thd->derived_tables= table;
+
+ /* If table is used by a left join, mark that any column may be null */
+ if (derived->outer_join)
+ table->maybe_null= 1;
+ }
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Runs optimize phase for a derived table/view.
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ Runs optimize phase for given 'derived' derived table/view.
+ If optimizer finds out that it's of the type "SELECT a_constant" then this
+ functions also materializes it.
+
+ @return FALSE ok.
+ @return TRUE if an error occur.
+*/
+
+bool mysql_derived_optimize(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ SELECT_LEX *first_select= unit->first_select();
+ SELECT_LEX *save_current_select= lex->current_select;
+
+ bool res= FALSE;
+ DBUG_ENTER("mysql_derived_optimize");
+
+ if (unit->optimized)
+ DBUG_RETURN(FALSE);
+ lex->current_select= first_select;
+
+ if (unit->is_union())
+ {
+ // optimize union without execution
+ res= unit->optimize();
+ }
+ else if (unit->derived)
+ {
+ if (!derived->is_merged_derived())
+ {
+ JOIN *join= first_select->join;
+ unit->set_limit(unit->global_parameters);
+ unit->optimized= TRUE;
+ if ((res= join->optimize()))
+ goto err;
+ if (join->table_count == join->const_tables)
+ derived->fill_me= TRUE;
+ }
+ }
+ /*
+ Materialize derived tables/views of the "SELECT a_constant" type.
+ Such tables should be materialized at the optimization phase for
+ correct constant evaluation.
+ */
+ if (!res && derived->fill_me && !derived->merged_for_insert)
+ {
+ if (derived->is_merged_derived())
+ {
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ }
+ if ((res= mysql_derived_create(thd, lex, derived)))
+ goto err;
+ if ((res= mysql_derived_fill(thd, lex, derived)))
+ goto err;
+ }
+err:
+ lex->current_select= save_current_select;
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Actually create result table for a materialized derived table/view.
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ This function actually creates the result table for given 'derived'
+ table/view, but it doesn't fill it.
+ 'thd' and 'lex' parameters are not used by this function.
+
+ @return FALSE ok.
+ @return TRUE if an error occur.
+*/
+
+bool mysql_derived_create(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ DBUG_ENTER("mysql_derived_create");
+ TABLE *table= derived->table;
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+
+ if (table->created)
+ DBUG_RETURN(FALSE);
+ select_union *result= (select_union*)unit->result;
+ if (table->s->db_type() == TMP_ENGINE_HTON)
+ {
+ result->tmp_table_param.keyinfo= table->s->key_info;
+ if (create_internal_tmp_table(table, result->tmp_table_param.keyinfo,
+ result->tmp_table_param.start_recinfo,
+ &result->tmp_table_param.recinfo,
+ (unit->first_select()->options |
+ thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS)))
+ DBUG_RETURN(TRUE);
+ }
+ if (open_tmp_table(table))
+ DBUG_RETURN(TRUE);
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Execute subquery of a materialized derived table/view and fill the result
+ table.
+
+ @param thd Thread handle
+ @param lex LEX for this thread
+ @param derived reference to the derived table.
+
+ @details
+ Execute subquery of given 'derived' table/view and fill the result
+ table. After result table is filled, if this is not the EXPLAIN statement,
+ the entire unit / node is deleted. unit is deleted if UNION is used
+ for derived table and node is deleted is it is a simple SELECT.
+ 'lex' is unused and 'thd' is passed as an argument to an underlying function.
+
+ @note
+ If you use this function, make sure it's not called at prepare.
+ Due to evaluation of LIMIT clause it can not be used at prepared stage.
+
+ @return FALSE OK
+ @return TRUE Error
+*/
+
+bool mysql_derived_fill(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ DBUG_ENTER("mysql_derived_fill");
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ bool res= FALSE;
+
+ if (unit->executed && !unit->uncacheable && !unit->describe)
+ DBUG_RETURN(FALSE);
+ /*check that table creation passed without problems. */
+ DBUG_ASSERT(derived->table && derived->table->created);
+ SELECT_LEX *first_select= unit->first_select();
+ select_union *derived_result= derived->derived_result;
+ SELECT_LEX *save_current_select= lex->current_select;
+ if (unit->is_union())
+ {
+ // execute union without clean up
+ res= unit->exec();
+ }
+ else
+ {
+ unit->set_limit(unit->global_parameters);
+ if (unit->select_limit_cnt == HA_POS_ERROR)
+ first_select->options&= ~OPTION_FOUND_ROWS;
+
+ lex->current_select= first_select;
+ res= mysql_select(thd, &first_select->ref_pointer_array,
+ first_select->table_list.first,
+ first_select->with_wild,
+ first_select->item_list, first_select->where,
+ (first_select->order_list.elements+
+ first_select->group_list.elements),
+ first_select->order_list.first,
+ first_select->group_list.first,
+ first_select->having, (ORDER*) NULL,
+ (first_select->options |thd->variables.option_bits |
+ SELECT_NO_UNLOCK),
+ derived_result, unit, first_select);
+ }
+
+ if (!res)
+ {
+ if (derived_result->flush())
+ res= TRUE;
+ unit->executed= TRUE;
+ }
+ if (res || !lex->describe)
+ unit->cleanup();
+ lex->current_select= save_current_select;
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Re-initialize given derived table/view for the next execution.
+
+ @param thd thread handle
+ @param lex LEX for this thread
+ @param derived reference to the derived table.
+
+ @details
+ Re-initialize given 'derived' table/view for the next execution.
+ All underlying views/derived tables are recursively reinitialized prior
+ to re-initialization of given derived table.
+ 'thd' and 'lex' are passed as arguments to called functions.
+
+ @return FALSE OK
+ @return TRUE Error
+*/
+
+bool mysql_derived_reinit(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ DBUG_ENTER("mysql_derived_reinit");
+ st_select_lex_unit *unit= derived->get_unit();
+
+ derived->merged_for_insert= FALSE;
+ unit->unclean();
+ unit->types.empty();
+ /* for derived tables & PS (which can't be reset by Item_subquery) */
+ unit->reinit_exec_mechanism();
+ unit->set_thd(thd);
+ DBUG_RETURN(FALSE);
+}
diff --git a/sql/sql_derived.h b/sql/sql_derived.h
new file mode 100644
index 00000000000..1dffef7235b
--- /dev/null
+++ b/sql/sql_derived.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_DERIVED_INCLUDED
+#define SQL_DERIVED_INCLUDED
+
+struct TABLE_LIST;
+class THD;
+struct LEX;
+
+bool mysql_handle_derived(LEX *lex, uint phases);
+bool mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases);
+bool mysql_handle_list_of_derived(LEX *lex, TABLE_LIST *dt_list, uint phases);
+bool mysql_derived_reinit(THD *thd, LEX *lex, TABLE_LIST *derived);
+
+/**
+ Cleans up the SELECT_LEX_UNIT for the derived table (if any).
+
+ @param thd Thread handler
+ @param lex LEX for this thread
+ @param derived TABLE_LIST for the derived table
+
+ @retval false Success
+ @retval true Failure
+*/
+bool mysql_derived_cleanup(THD *thd, LEX *lex, TABLE_LIST *derived);
+
+#endif /* SQL_DERIVED_INCLUDED */
diff --git a/sql/sql_digest.cc b/sql/sql_digest.cc
new file mode 100644
index 00000000000..324f2fbd428
--- /dev/null
+++ b/sql/sql_digest.cc
@@ -0,0 +1,683 @@
+/* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+/*
+ This code needs extra visibility in the lexer structures
+*/
+
+#include "my_global.h"
+#include "my_md5.h"
+#include "mysqld_error.h"
+
+#include "sql_string.h"
+#include "sql_class.h"
+#include "sql_lex.h"
+#include "sql_digest.h"
+#include "sql_digest_stream.h"
+
+#include "sql_get_diagnostics.h"
+
+#ifdef NEVER
+#include "my_sys.h"
+#include "sql_signal.h"
+#endif
+
+/* Generated code */
+#include "sql_yacc.h"
+#define LEX_TOKEN_WITH_DEFINITION
+#include "lex_token.h"
+
+/* Name pollution from sql/sql_lex.h */
+#ifdef LEX_YYSTYPE
+#undef LEX_YYSTYPE
+#endif
+
+#define LEX_YYSTYPE YYSTYPE*
+
+#define SIZE_OF_A_TOKEN 2
+
+/**
+ Read a single token from token array.
+*/
+inline uint read_token(const sql_digest_storage *digest_storage,
+ uint index, uint *tok)
+{
+ uint safe_byte_count= digest_storage->m_byte_count;
+
+ if (index + SIZE_OF_A_TOKEN <= safe_byte_count &&
+ safe_byte_count <= digest_storage->m_token_array_length)
+ {
+ const unsigned char *src= & digest_storage->m_token_array[index];
+ *tok= src[0] | (src[1] << 8);
+ return index + SIZE_OF_A_TOKEN;
+ }
+
+ /* The input byte stream is exhausted. */
+ *tok= 0;
+ return MAX_DIGEST_STORAGE_SIZE + 1;
+}
+
+/**
+ Store a single token in token array.
+*/
+inline void store_token(sql_digest_storage* digest_storage, uint token)
+{
+ DBUG_ASSERT(digest_storage->m_byte_count <= digest_storage->m_token_array_length);
+
+ if (digest_storage->m_byte_count + SIZE_OF_A_TOKEN <= digest_storage->m_token_array_length)
+ {
+ unsigned char* dest= & digest_storage->m_token_array[digest_storage->m_byte_count];
+ dest[0]= token & 0xff;
+ dest[1]= (token >> 8) & 0xff;
+ digest_storage->m_byte_count+= SIZE_OF_A_TOKEN;
+ }
+ else
+ {
+ digest_storage->m_full= true;
+ }
+}
+
+/**
+ Read an identifier from token array.
+*/
+inline uint read_identifier(const sql_digest_storage* digest_storage,
+ uint index, char ** id_string, int *id_length)
+{
+ uint new_index;
+ uint safe_byte_count= digest_storage->m_byte_count;
+
+ DBUG_ASSERT(index <= safe_byte_count);
+ DBUG_ASSERT(safe_byte_count <= digest_storage->m_token_array_length);
+
+ /*
+ token + length + string are written in an atomic way,
+ so we do always expect a length + string here
+ */
+
+ uint bytes_needed= SIZE_OF_A_TOKEN;
+ /* If we can read token and identifier length */
+ if ((index + bytes_needed) <= safe_byte_count)
+ {
+ const unsigned char *src= & digest_storage->m_token_array[index];
+ /* Read the length of identifier */
+ uint length= src[0] | (src[1] << 8);
+ bytes_needed+= length;
+ /* If we can read entire identifier from token array */
+ if ((index + bytes_needed) <= safe_byte_count)
+ {
+ *id_string= (char *) (src + 2);
+ *id_length= length;
+
+ new_index= index + bytes_needed;
+ DBUG_ASSERT(new_index <= safe_byte_count);
+ return new_index;
+ }
+ }
+
+ /* The input byte stream is exhausted. */
+ return MAX_DIGEST_STORAGE_SIZE + 1;
+}
+
+/**
+ Store an identifier in token array.
+*/
+inline void store_token_identifier(sql_digest_storage* digest_storage,
+ uint token,
+ size_t id_length, const char *id_name)
+{
+ DBUG_ASSERT(digest_storage->m_byte_count <= digest_storage->m_token_array_length);
+
+ size_t bytes_needed= 2 * SIZE_OF_A_TOKEN + id_length;
+ if (digest_storage->m_byte_count + bytes_needed <= (unsigned int)digest_storage->m_token_array_length)
+ {
+ unsigned char* dest= & digest_storage->m_token_array[digest_storage->m_byte_count];
+ /* Write the token */
+ dest[0]= token & 0xff;
+ dest[1]= (token >> 8) & 0xff;
+ /* Write the string length */
+ dest[2]= id_length & 0xff;
+ dest[3]= (id_length >> 8) & 0xff;
+ /* Write the string data */
+ if (id_length > 0)
+ memcpy((char *)(dest + 4), id_name, id_length);
+ digest_storage->m_byte_count+= bytes_needed;
+ }
+ else
+ {
+ digest_storage->m_full= true;
+ }
+}
+
+void compute_digest_md5(const sql_digest_storage *digest_storage, unsigned char *md5)
+{
+ compute_md5_hash((char *) md5,
+ (const char *) digest_storage->m_token_array,
+ digest_storage->m_byte_count);
+}
+
+/*
+ Iterate token array and updates digest_text.
+*/
+void compute_digest_text(const sql_digest_storage* digest_storage,
+ String *digest_text)
+{
+ DBUG_ASSERT(digest_storage != NULL);
+ uint byte_count= digest_storage->m_byte_count;
+ String *digest_output= digest_text;
+ uint tok= 0;
+ uint current_byte= 0;
+ lex_token_string *tok_data;
+
+ /* Reset existing data */
+ digest_output->length(0);
+
+ if (byte_count > digest_storage->m_token_array_length)
+ {
+ digest_output->append("\0", 1);
+ return;
+ }
+
+ /* Convert text to utf8 */
+ const CHARSET_INFO *from_cs= get_charset(digest_storage->m_charset_number, MYF(0));
+ const CHARSET_INFO *to_cs= &my_charset_utf8_bin;
+
+ if (from_cs == NULL)
+ {
+ /*
+ Can happen, as we do dirty reads on digest_storage,
+ which can be written to in another thread.
+ */
+ digest_output->append("\0", 1);
+ return;
+ }
+
+ char id_buffer[NAME_LEN + 1]= {'\0'};
+ char *id_string;
+ size_t id_length;
+ bool convert_text= !my_charset_same(from_cs, to_cs);
+
+ while (current_byte < byte_count)
+ {
+ current_byte= read_token(digest_storage, current_byte, &tok);
+
+ if (tok <= 0 || tok >= array_elements(lex_token_array)
+ || current_byte > max_digest_length)
+ return;
+
+ tok_data= &lex_token_array[tok];
+
+ switch (tok)
+ {
+ /* All identifiers are printed with their name. */
+ case IDENT:
+ case IDENT_QUOTED:
+ {
+ char *id_ptr= NULL;
+ int id_len= 0;
+ uint err_cs= 0;
+
+ /* Get the next identifier from the storage buffer. */
+ current_byte= read_identifier(digest_storage, current_byte,
+ &id_ptr, &id_len);
+ if (current_byte > max_digest_length)
+ return;
+
+ if (convert_text)
+ {
+ /* Verify that the converted text will fit. */
+ if (to_cs->mbmaxlen*id_len > NAME_LEN)
+ {
+ digest_output->append("...", 3);
+ break;
+ }
+ /* Convert identifier string into the storage character set. */
+ id_length= my_convert(id_buffer, NAME_LEN, to_cs,
+ id_ptr, id_len, from_cs, &err_cs);
+ id_string= id_buffer;
+ }
+ else
+ {
+ id_string= id_ptr;
+ id_length= id_len;
+ }
+
+ if (id_length == 0 || err_cs != 0)
+ {
+ break;
+ }
+ /* Copy the converted identifier into the digest string. */
+ if (tok == IDENT_QUOTED)
+ digest_output->append("`", 1);
+ if (id_length > 0)
+ digest_output->append(id_string, id_length);
+ if (tok == IDENT_QUOTED)
+ digest_output->append("`", 1);
+ digest_output->append(" ", 1);
+ }
+ break;
+
+ /* Everything else is printed as is. */
+ default:
+ /*
+ Make sure not to overflow digest_text buffer.
+ +1 is to make sure extra space for ' '.
+ */
+ int tok_length= tok_data->m_token_length;
+
+ digest_output->append(tok_data->m_token_string, tok_length);
+ if (tok_data->m_append_space)
+ digest_output->append(" ", 1);
+ break;
+ }
+ }
+}
+
+static inline uint peek_token(const sql_digest_storage *digest, uint index)
+{
+ uint token;
+ DBUG_ASSERT(index + SIZE_OF_A_TOKEN <= digest->m_byte_count);
+ DBUG_ASSERT(digest->m_byte_count <= digest->m_token_array_length);
+
+ token= ((digest->m_token_array[index + 1])<<8) | digest->m_token_array[index];
+ return token;
+}
+
+/**
+ Function to read last two tokens from token array. If an identifier
+ is found, do not look for token before that.
+*/
+static inline void peek_last_two_tokens(const sql_digest_storage* digest_storage,
+ uint last_id_index, uint *t1, uint *t2)
+{
+ uint byte_count= digest_storage->m_byte_count;
+ uint peek_index= byte_count;
+
+ if (last_id_index + SIZE_OF_A_TOKEN <= peek_index)
+ {
+ /* Take last token. */
+ peek_index-= SIZE_OF_A_TOKEN;
+ *t1= peek_token(digest_storage, peek_index);
+
+ if (last_id_index + SIZE_OF_A_TOKEN <= peek_index)
+ {
+ /* Take 2nd token from last. */
+ peek_index-= SIZE_OF_A_TOKEN;
+ *t2= peek_token(digest_storage, peek_index);
+ }
+ else
+ {
+ *t2= TOK_UNUSED;
+ }
+ }
+ else
+ {
+ *t1= TOK_UNUSED;
+ *t2= TOK_UNUSED;
+ }
+}
+
+/**
+ Function to read last three tokens from token array. If an identifier
+ is found, do not look for token before that.
+*/
+static inline void peek_last_three_tokens(const sql_digest_storage* digest_storage,
+ uint last_id_index, uint *t1, uint *t2, uint *t3)
+{
+ uint byte_count= digest_storage->m_byte_count;
+ uint peek_index= byte_count;
+
+ if (last_id_index + SIZE_OF_A_TOKEN <= peek_index)
+ {
+ /* Take last token. */
+ peek_index-= SIZE_OF_A_TOKEN;
+ *t1= peek_token(digest_storage, peek_index);
+
+ if (last_id_index + SIZE_OF_A_TOKEN <= peek_index)
+ {
+ /* Take 2nd token from last. */
+ peek_index-= SIZE_OF_A_TOKEN;
+ *t2= peek_token(digest_storage, peek_index);
+
+ if (last_id_index + SIZE_OF_A_TOKEN <= peek_index)
+ {
+ /* Take 3rd token from last. */
+ peek_index-= SIZE_OF_A_TOKEN;
+ *t3= peek_token(digest_storage, peek_index);
+ }
+ else
+ {
+ *t3= TOK_UNUSED;
+ }
+ }
+ else
+ {
+ *t2= TOK_UNUSED;
+ *t3= TOK_UNUSED;
+ }
+ }
+ else
+ {
+ *t1= TOK_UNUSED;
+ *t2= TOK_UNUSED;
+ *t3= TOK_UNUSED;
+ }
+}
+
+sql_digest_state* digest_add_token(sql_digest_state *state,
+ uint token,
+ LEX_YYSTYPE yylval)
+{
+ sql_digest_storage *digest_storage= NULL;
+
+ digest_storage= &state->m_digest_storage;
+
+ /*
+ Stop collecting further tokens if digest storage is full or
+ if END token is received.
+ */
+ if (digest_storage->m_full || token == END_OF_INPUT)
+ return NULL;
+
+ /*
+ Take last_token 2 tokens collected till now. These tokens will be used
+ in reduce for normalisation. Make sure not to consider ID tokens in reduce.
+ */
+ uint last_token;
+ uint last_token2;
+
+ switch (token)
+ {
+ case NUM:
+ case LONG_NUM:
+ case ULONGLONG_NUM:
+ case DECIMAL_NUM:
+ case FLOAT_NUM:
+ case BIN_NUM:
+ case HEX_NUM:
+ {
+ bool found_unary;
+ do
+ {
+ found_unary= false;
+ peek_last_two_tokens(digest_storage, state->m_last_id_index,
+ &last_token, &last_token2);
+
+ if ((last_token == '-') || (last_token == '+'))
+ {
+ /*
+ We need to differentiate:
+ - a <unary minus> operator
+ - a <unary plus> operator
+ from
+ - a <binary minus> operator
+ - a <binary plus> operator
+ to only reduce "a = -1" to "a = ?", and not change "b - 1" to "b ?"
+
+ Binary operators are found inside an expression,
+ while unary operators are found at the beginning of an expression, or after operators.
+
+ To achieve this, every token that is followed by an <expr> expression
+ in the SQL grammar is flagged.
+ See sql/sql_yacc.yy
+ See sql/gen_lex_token.cc
+
+ For example,
+ "(-1)" is parsed as "(", "-", NUM, ")", and lex_token_array["("].m_start_expr is true,
+ so reduction of the "-" NUM is done, the result is "(?)".
+ "(a-1)" is parsed as "(", ID, "-", NUM, ")", and lex_token_array[ID].m_start_expr is false,
+ so the operator is binary, no reduction is done, and the result is "(a-?)".
+ */
+ if (lex_token_array[last_token2].m_start_expr)
+ {
+ /*
+ REDUCE:
+ TOK_GENERIC_VALUE := (UNARY_PLUS | UNARY_MINUS) (NUM | LOG_NUM | ... | FLOAT_NUM)
+
+ REDUCE:
+ TOK_GENERIC_VALUE := (UNARY_PLUS | UNARY_MINUS) TOK_GENERIC_VALUE
+ */
+ token= TOK_GENERIC_VALUE;
+ digest_storage->m_byte_count-= SIZE_OF_A_TOKEN;
+ found_unary= true;
+ }
+ }
+ } while (found_unary);
+ }
+ /* fall through, for case NULL_SYM below */
+ case LEX_HOSTNAME:
+ case TEXT_STRING:
+ case NCHAR_STRING:
+ case PARAM_MARKER:
+ {
+ /*
+ REDUCE:
+ TOK_GENERIC_VALUE := BIN_NUM | DECIMAL_NUM | ... | ULONGLONG_NUM
+ */
+ token= TOK_GENERIC_VALUE;
+
+ peek_last_two_tokens(digest_storage, state->m_last_id_index,
+ &last_token, &last_token2);
+
+ if ((last_token2 == TOK_GENERIC_VALUE ||
+ last_token2 == TOK_GENERIC_VALUE_LIST) &&
+ (last_token == ','))
+ {
+ /*
+ REDUCE:
+ TOK_GENERIC_VALUE_LIST :=
+ TOK_GENERIC_VALUE ',' TOK_GENERIC_VALUE
+
+ REDUCE:
+ TOK_GENERIC_VALUE_LIST :=
+ TOK_GENERIC_VALUE_LIST ',' TOK_GENERIC_VALUE
+ */
+ digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN;
+ token= TOK_GENERIC_VALUE_LIST;
+ }
+ /*
+ Add this token or the resulting reduce to digest storage.
+ */
+ store_token(digest_storage, token);
+ break;
+ }
+ case ')':
+ {
+ peek_last_two_tokens(digest_storage, state->m_last_id_index,
+ &last_token, &last_token2);
+
+ if (last_token == TOK_GENERIC_VALUE &&
+ last_token2 == '(')
+ {
+ /*
+ REDUCE:
+ TOK_ROW_SINGLE_VALUE :=
+ '(' TOK_GENERIC_VALUE ')'
+ */
+ digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN;
+ token= TOK_ROW_SINGLE_VALUE;
+
+ /* Read last two tokens again */
+ peek_last_two_tokens(digest_storage, state->m_last_id_index,
+ &last_token, &last_token2);
+
+ if ((last_token2 == TOK_ROW_SINGLE_VALUE ||
+ last_token2 == TOK_ROW_SINGLE_VALUE_LIST) &&
+ (last_token == ','))
+ {
+ /*
+ REDUCE:
+ TOK_ROW_SINGLE_VALUE_LIST :=
+ TOK_ROW_SINGLE_VALUE ',' TOK_ROW_SINGLE_VALUE
+
+ REDUCE:
+ TOK_ROW_SINGLE_VALUE_LIST :=
+ TOK_ROW_SINGLE_VALUE_LIST ',' TOK_ROW_SINGLE_VALUE
+ */
+ digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN;
+ token= TOK_ROW_SINGLE_VALUE_LIST;
+ }
+ }
+ else if (last_token == TOK_GENERIC_VALUE_LIST &&
+ last_token2 == '(')
+ {
+ /*
+ REDUCE:
+ TOK_ROW_MULTIPLE_VALUE :=
+ '(' TOK_GENERIC_VALUE_LIST ')'
+ */
+ digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN;
+ token= TOK_ROW_MULTIPLE_VALUE;
+
+ /* Read last two tokens again */
+ peek_last_two_tokens(digest_storage, state->m_last_id_index,
+ &last_token, &last_token2);
+
+ if ((last_token2 == TOK_ROW_MULTIPLE_VALUE ||
+ last_token2 == TOK_ROW_MULTIPLE_VALUE_LIST) &&
+ (last_token == ','))
+ {
+ /*
+ REDUCE:
+ TOK_ROW_MULTIPLE_VALUE_LIST :=
+ TOK_ROW_MULTIPLE_VALUE ',' TOK_ROW_MULTIPLE_VALUE
+
+ REDUCE:
+ TOK_ROW_MULTIPLE_VALUE_LIST :=
+ TOK_ROW_MULTIPLE_VALUE_LIST ',' TOK_ROW_MULTIPLE_VALUE
+ */
+ digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN;
+ token= TOK_ROW_MULTIPLE_VALUE_LIST;
+ }
+ }
+ /*
+ Add this token or the resulting reduce to digest storage.
+ */
+ store_token(digest_storage, token);
+ break;
+ }
+ case IDENT:
+ case IDENT_QUOTED:
+ {
+ YYSTYPE *lex_token= yylval;
+ char *yytext= lex_token->lex_str.str;
+ size_t yylen= lex_token->lex_str.length;
+
+ /* Add this token and identifier string to digest storage. */
+ store_token_identifier(digest_storage, token, yylen, yytext);
+
+ /* Update the index of last identifier found. */
+ state->m_last_id_index= digest_storage->m_byte_count;
+ break;
+ }
+ default:
+ {
+ /* Add this token to digest storage. */
+ store_token(digest_storage, token);
+ break;
+ }
+ }
+
+ return state;
+}
+
+sql_digest_state* digest_reduce_token(sql_digest_state *state,
+ uint token_left, uint token_right)
+{
+ sql_digest_storage *digest_storage= NULL;
+
+ digest_storage= &state->m_digest_storage;
+
+ /*
+ Stop collecting further tokens if digest storage is full.
+ */
+ if (digest_storage->m_full)
+ return NULL;
+
+ uint last_token;
+ uint last_token2;
+ uint last_token3;
+ uint token_to_push= TOK_UNUSED;
+
+ peek_last_two_tokens(digest_storage, state->m_last_id_index,
+ &last_token, &last_token2);
+
+ /*
+ There is only one caller of digest_reduce_token(),
+ see sql/sql_yacc.yy, rule literal := NULL_SYM.
+ REDUCE:
+ token_left := token_right
+ Used for:
+ TOK_GENERIC_VALUE := NULL_SYM
+ */
+
+ if (last_token == token_right)
+ {
+ /*
+ Current stream is like:
+ TOKEN_X TOKEN_RIGHT .
+ REDUCE to
+ TOKEN_X TOKEN_LEFT .
+ */
+ digest_storage->m_byte_count-= SIZE_OF_A_TOKEN;
+ store_token(digest_storage, token_left);
+ }
+ else
+ {
+ /*
+ Current stream is like:
+ TOKEN_X TOKEN_RIGHT TOKEN_Y .
+ Pop TOKEN_Y
+ TOKEN_X TOKEN_RIGHT . TOKEN_Y
+ REDUCE to
+ TOKEN_X TOKEN_LEFT . TOKEN_Y
+ */
+ DBUG_ASSERT(last_token2 == token_right);
+ digest_storage->m_byte_count-= 2 * SIZE_OF_A_TOKEN;
+ store_token(digest_storage, token_left);
+ token_to_push= last_token;
+ }
+
+ peek_last_three_tokens(digest_storage, state->m_last_id_index,
+ &last_token, &last_token2, &last_token3);
+
+ if ((last_token3 == TOK_GENERIC_VALUE ||
+ last_token3 == TOK_GENERIC_VALUE_LIST) &&
+ (last_token2 == ',') &&
+ (last_token == TOK_GENERIC_VALUE))
+ {
+ /*
+ REDUCE:
+ TOK_GENERIC_VALUE_LIST :=
+ TOK_GENERIC_VALUE ',' TOK_GENERIC_VALUE
+
+ REDUCE:
+ TOK_GENERIC_VALUE_LIST :=
+ TOK_GENERIC_VALUE_LIST ',' TOK_GENERIC_VALUE
+ */
+ digest_storage->m_byte_count-= 3*SIZE_OF_A_TOKEN;
+ store_token(digest_storage, TOK_GENERIC_VALUE_LIST);
+ }
+
+ if (token_to_push != TOK_UNUSED)
+ {
+ /*
+ Push TOKEN_Y
+ */
+ store_token(digest_storage, token_to_push);
+ }
+
+ return state;
+}
+
diff --git a/sql/sql_digest.h b/sql/sql_digest.h
new file mode 100644
index 00000000000..ce159283d4d
--- /dev/null
+++ b/sql/sql_digest.h
@@ -0,0 +1,130 @@
+/* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#ifndef SQL_DIGEST_H
+#define SQL_DIGEST_H
+
+#include <string.h>
+class String;
+#include "my_md5.h"
+
+#define MAX_DIGEST_STORAGE_SIZE (1024*1024)
+
+/**
+ Structure to store token count/array for a statement
+ on which digest is to be calculated.
+*/
+struct sql_digest_storage
+{
+ bool m_full;
+ uint m_byte_count;
+ unsigned char m_md5[MD5_HASH_SIZE];
+ /** Character set number. */
+ uint m_charset_number;
+ /**
+ Token array.
+ Token array is an array of bytes to store tokens received during parsing.
+ Following is the way token array is formed.
+ ... &lt;non-id-token&gt; &lt;non-id-token&gt; &lt;id-token&gt; &lt;id_len&gt; &lt;id_text&gt; ...
+ For Example:
+ SELECT * FROM T1;
+ &lt;SELECT_TOKEN&gt; &lt;*&gt; &lt;FROM_TOKEN&gt; &lt;ID_TOKEN&gt; &lt;2&gt; &lt;T1&gt;
+ */
+ unsigned char *m_token_array;
+ /* Length of the token array to be considered for DIGEST_TEXT calculation. */
+ uint m_token_array_length;
+
+ sql_digest_storage()
+ {
+ reset(NULL, 0);
+ }
+
+ inline void reset(unsigned char *token_array, uint length)
+ {
+ m_token_array= token_array;
+ m_token_array_length= length;
+ reset();
+ }
+
+ inline void reset()
+ {
+ m_full= false;
+ m_byte_count= 0;
+ m_charset_number= 0;
+ if (m_token_array_length > 0)
+ {
+ memset(m_token_array, 0, m_token_array_length);
+ }
+ memset(m_md5, 0, MD5_HASH_SIZE);
+ }
+
+ inline bool is_empty()
+ {
+ return (m_byte_count == 0);
+ }
+
+ inline void copy(const sql_digest_storage *from)
+ {
+ /*
+ Keep in mind this is a dirty copy of something that may change,
+ as the thread producing the digest is executing concurrently,
+ without any lock enforced.
+ */
+ uint byte_count_copy= m_token_array_length < from->m_byte_count ?
+ m_token_array_length : from->m_byte_count;
+
+ if (byte_count_copy > 0)
+ {
+ m_full= from->m_full;
+ m_byte_count= byte_count_copy;
+ m_charset_number= from->m_charset_number;
+ memcpy(m_token_array, from->m_token_array, m_byte_count);
+ memcpy(m_md5, from->m_md5, MD5_HASH_SIZE);
+ }
+ else
+ {
+ m_full= false;
+ m_byte_count= 0;
+ m_charset_number= 0;
+ }
+ }
+};
+typedef struct sql_digest_storage sql_digest_storage;
+
+/**
+ Compute a digest hash.
+ @param digest_storage The digest
+ @param [out] md5 The computed digest hash. This parameter is a buffer of size @c MD5_HASH_SIZE.
+*/
+void compute_digest_md5(const sql_digest_storage *digest_storage, unsigned char *md5);
+
+/**
+ Compute a digest text.
+ A 'digest text' is a textual representation of a query,
+ where:
+ - comments are removed,
+ - non significant spaces are removed,
+ - literal values are replaced with a special '?' marker,
+ - lists of values are collapsed using a shorter notation
+ @param digest_storage The digest
+ @param [out] digest_text
+ @param digest_text_length Size of @c digest_text.
+ @param [out] truncated true if the text representation was truncated
+*/
+void compute_digest_text(const sql_digest_storage *digest_storage,
+ String *digest_text);
+
+#endif
+
diff --git a/sql/sql_digest_stream.h b/sql/sql_digest_stream.h
new file mode 100644
index 00000000000..55f7e2293c6
--- /dev/null
+++ b/sql/sql_digest_stream.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
+
+ 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#ifndef SQL_DIGEST_STREAM_H
+#define SQL_DIGEST_STREAM_H
+
+#include "sql_digest.h"
+
+/**
+ State data storage for @c digest_start, @c digest_add_token.
+ This structure extends the @c sql_digest_storage structure
+ with temporary state used only during parsing.
+*/
+struct sql_digest_state
+{
+ /**
+ Index, in the digest token array, of the last identifier seen.
+ Reduce rules used in the digest computation can not
+ apply to tokens seen before an identifier.
+ @sa digest_add_token
+ */
+ int m_last_id_index;
+ sql_digest_storage m_digest_storage;
+
+ inline void reset(unsigned char *token_array, uint length)
+ {
+ m_last_id_index= 0;
+ m_digest_storage.reset(token_array, length);
+ }
+
+ inline bool is_empty()
+ {
+ return m_digest_storage.is_empty();
+ }
+};
+typedef struct sql_digest_state sql_digest_state;
+
+#endif
+
diff --git a/sql/sql_do.cc b/sql/sql_do.cc
new file mode 100644
index 00000000000..468b1bc33da
--- /dev/null
+++ b/sql/sql_do.cc
@@ -0,0 +1,51 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/* Execute DO statement */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "transaction.h"
+#include "unireg.h"
+#include "sql_do.h"
+#include "sql_base.h" // setup_fields
+#include "sql_select.h" // free_underlaid_joins
+
+bool mysql_do(THD *thd, List<Item> &values)
+{
+ List_iterator<Item> li(values);
+ Item *value;
+ DBUG_ENTER("mysql_do");
+ if (setup_fields(thd, 0, values, MARK_COLUMNS_NONE, 0, 0))
+ DBUG_RETURN(TRUE);
+ while ((value = li++))
+ value->val_int();
+ free_underlaid_joins(thd, &thd->lex->select_lex);
+
+ if (thd->is_error())
+ {
+ /*
+ Rollback the effect of the statement, since next instruction
+ will clear the error and the rollback in the end of
+ mysql_execute_command() won't work.
+ */
+ if (! thd->in_sub_stmt)
+ trans_rollback_stmt(thd);
+ thd->clear_error(); // DO always is OK
+ }
+ my_ok(thd);
+ DBUG_RETURN(FALSE);
+}
diff --git a/sql/sql_do.h b/sql/sql_do.h
new file mode 100644
index 00000000000..35130cc5836
--- /dev/null
+++ b/sql/sql_do.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_DO_INCLUDED
+#define SQL_DO_INCLUDED
+
+#include "sql_list.h" /* List */
+
+class THD;
+class Item;
+
+bool mysql_do(THD *thd, List<Item> &values);
+
+#endif /* SQL_DO_INCLUDED */
diff --git a/sql/sql_error.cc b/sql/sql_error.cc
new file mode 100644
index 00000000000..3e18b701031
--- /dev/null
+++ b/sql/sql_error.cc
@@ -0,0 +1,1039 @@
+/* Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/**********************************************************************
+This file contains the implementation of error and warnings related
+
+ - Whenever an error or warning occurred, it pushes it to a warning list
+ that the user can retrieve with SHOW WARNINGS or SHOW ERRORS.
+
+ - For each statement, we return the number of warnings generated from this
+ command. Note that this can be different from @@warning_count as
+ we reset the warning list only for questions that uses a table.
+ This is done to allow on to do:
+ INSERT ...;
+ SELECT @@warning_count;
+ SHOW WARNINGS;
+ (If we would reset after each command, we could not retrieve the number
+ of warnings)
+
+ - When client requests the information using SHOW command, then
+ server processes from this list and returns back in the form of
+ resultset.
+
+ Supported syntaxes:
+
+ SHOW [COUNT(*)] ERRORS [LIMIT [offset,] rows]
+ SHOW [COUNT(*)] WARNINGS [LIMIT [offset,] rows]
+ SELECT @@warning_count, @@error_count;
+
+***********************************************************************/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_error.h"
+#include "sp_rcontext.h"
+
+/*
+ Design notes about Sql_condition::m_message_text.
+
+ The member Sql_condition::m_message_text contains the text associated with
+ an error, warning or note (which are all SQL 'conditions')
+
+ Producer of Sql_condition::m_message_text:
+ ----------------------------------------
+
+ (#1) the server implementation itself, when invoking functions like
+ my_error() or push_warning()
+
+ (#2) user code in stored programs, when using the SIGNAL statement.
+
+ (#3) user code in stored programs, when using the RESIGNAL statement.
+
+ When invoking my_error(), the error number and message is typically
+ provided like this:
+ - my_error(ER_WRONG_DB_NAME, MYF(0), ...);
+ - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+
+ In both cases, the message is retrieved from ER(ER_XXX), which in turn
+ is read from the resource file errmsg.sys at server startup.
+ The strings stored in the errmsg.sys file are expressed in the character set
+ that corresponds to the server --language start option
+ (see error_message_charset_info).
+
+ When executing:
+ - a SIGNAL statement,
+ - a RESIGNAL statement,
+ the message text is provided by the user logic, and is expressed in UTF8.
+
+ Storage of Sql_condition::m_message_text:
+ ---------------------------------------
+
+ (#4) The class Sql_condition is used to hold the message text member.
+ This class represents a single SQL condition.
+
+ (#5) The class Warning_info represents a SQL condition area, and contains
+ a collection of SQL conditions in the Warning_info::m_warn_list
+
+ Consumer of Sql_condition::m_message_text:
+ ----------------------------------------
+
+ (#6) The statements SHOW WARNINGS and SHOW ERRORS display the content of
+ the warning list.
+
+ (#7) The GET DIAGNOSTICS statement (planned, not implemented yet) will
+ also read the content of:
+ - the top level statement condition area (when executed in a query),
+ - a sub statement (when executed in a stored program)
+ and return the data stored in a Sql_condition.
+
+ (#8) The RESIGNAL statement reads the Sql_condition caught by an exception
+ handler, to raise a new or modified condition (in #3).
+
+ The big picture
+ ---------------
+ --------------
+ | ^
+ V |
+ my_error(#1) SIGNAL(#2) RESIGNAL(#3) |
+ |(#A) |(#B) |(#C) |
+ | | | |
+ ----------------------------|---------------------------- |
+ | |
+ V |
+ Sql_condition(#4) |
+ | |
+ | |
+ V |
+ Warning_info(#5) |
+ | |
+ ----------------------------------------------------- |
+ | | | |
+ | | | |
+ | | | |
+ V V V |
+ SHOW WARNINGS(#6) GET DIAGNOSTICS(#7) RESIGNAL(#8) |
+ | | | | |
+ | -------- | V |
+ | | | --------------
+ V | |
+ Connectors | |
+ | | |
+ -------------------------
+ |
+ V
+ Client application
+
+ Current implementation status
+ -----------------------------
+
+ (#1) (my_error) produces data in the 'error_message_charset_info' CHARSET
+
+ (#2) and (#3) (SIGNAL, RESIGNAL) produces data internally in UTF8
+
+ (#6) (SHOW WARNINGS) produces data in the 'error_message_charset_info' CHARSET
+
+ (#7) (GET DIAGNOSTICS) is not implemented.
+
+ (#8) (RESIGNAL) produces data internally in UTF8 (see #3)
+
+ As a result, the design choice for (#4) and (#5) is to store data in
+ the 'error_message_charset_info' CHARSET, to minimize impact on the code base.
+ This is implemented by using 'String Sql_condition::m_message_text'.
+
+ The UTF8 -> error_message_charset_info conversion is implemented in
+ Sql_cmd_common_signal::eval_signal_informations() (for path #B and #C).
+
+ Future work
+ -----------
+
+ - Change (#1) (my_error) to generate errors in UTF8.
+ See WL#751 (Recoding of error messages)
+
+ - Change (#4 and #5) to store message text in UTF8 natively.
+ In practice, this means changing the type of the message text to
+ '<UTF8 String 128 class> Sql_condition::m_message_text', and is a direct
+ consequence of WL#751.
+
+ - Implement (#9) (GET DIAGNOSTICS).
+ See WL#2111 (Stored Procedures: Implement GET DIAGNOSTICS)
+*/
+
+Sql_condition::Sql_condition()
+ : Sql_alloc(),
+ m_class_origin((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_subclass_origin((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_constraint_catalog((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_constraint_schema((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_constraint_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_catalog_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_schema_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_table_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_column_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_message_text(),
+ m_sql_errno(0),
+ m_level(Sql_condition::WARN_LEVEL_ERROR),
+ m_mem_root(NULL)
+{
+ memset(m_returned_sqlstate, 0, sizeof(m_returned_sqlstate));
+}
+
+void Sql_condition::init(MEM_ROOT *mem_root)
+{
+ DBUG_ASSERT(mem_root != NULL);
+ DBUG_ASSERT(m_mem_root == NULL);
+ m_mem_root= mem_root;
+}
+
+void Sql_condition::clear()
+{
+ m_class_origin.length(0);
+ m_subclass_origin.length(0);
+ m_constraint_catalog.length(0);
+ m_constraint_schema.length(0);
+ m_constraint_name.length(0);
+ m_catalog_name.length(0);
+ m_schema_name.length(0);
+ m_table_name.length(0);
+ m_column_name.length(0);
+ m_cursor_name.length(0);
+ m_message_text.length(0);
+ m_sql_errno= 0;
+ m_level= Sql_condition::WARN_LEVEL_ERROR;
+}
+
+Sql_condition::Sql_condition(MEM_ROOT *mem_root)
+ : Sql_alloc(),
+ m_class_origin((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_subclass_origin((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_constraint_catalog((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_constraint_schema((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_constraint_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_catalog_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_schema_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_table_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_column_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin),
+ m_message_text(),
+ m_sql_errno(0),
+ m_level(Sql_condition::WARN_LEVEL_ERROR),
+ m_mem_root(mem_root)
+{
+ DBUG_ASSERT(mem_root != NULL);
+ memset(m_returned_sqlstate, 0, sizeof(m_returned_sqlstate));
+}
+
+static void copy_string(MEM_ROOT *mem_root, String* dst, const String* src)
+{
+ size_t len= src->length();
+ if (len)
+ {
+ char* copy= (char*) alloc_root(mem_root, len + 1);
+ if (copy)
+ {
+ memcpy(copy, src->ptr(), len);
+ copy[len]= '\0';
+ dst->set(copy, len, src->charset());
+ }
+ }
+ else
+ dst->length(0);
+}
+
+void
+Sql_condition::copy_opt_attributes(const Sql_condition *cond)
+{
+ DBUG_ASSERT(this != cond);
+ copy_string(m_mem_root, & m_class_origin, & cond->m_class_origin);
+ copy_string(m_mem_root, & m_subclass_origin, & cond->m_subclass_origin);
+ copy_string(m_mem_root, & m_constraint_catalog, & cond->m_constraint_catalog);
+ copy_string(m_mem_root, & m_constraint_schema, & cond->m_constraint_schema);
+ copy_string(m_mem_root, & m_constraint_name, & cond->m_constraint_name);
+ copy_string(m_mem_root, & m_catalog_name, & cond->m_catalog_name);
+ copy_string(m_mem_root, & m_schema_name, & cond->m_schema_name);
+ copy_string(m_mem_root, & m_table_name, & cond->m_table_name);
+ copy_string(m_mem_root, & m_column_name, & cond->m_column_name);
+ copy_string(m_mem_root, & m_cursor_name, & cond->m_cursor_name);
+}
+
+void
+Sql_condition::set(uint sql_errno, const char* sqlstate,
+ Sql_condition::enum_warning_level level, const char* msg)
+{
+ DBUG_ASSERT(sql_errno != 0);
+ DBUG_ASSERT(sqlstate != NULL);
+ DBUG_ASSERT(msg != NULL);
+
+ m_sql_errno= sql_errno;
+ memcpy(m_returned_sqlstate, sqlstate, SQLSTATE_LENGTH);
+ m_returned_sqlstate[SQLSTATE_LENGTH]= '\0';
+
+ set_builtin_message_text(msg);
+ m_level= level;
+}
+
+void
+Sql_condition::set_builtin_message_text(const char* str)
+{
+ /*
+ See the comments
+ "Design notes about Sql_condition::m_message_text."
+ */
+ const char* copy;
+
+ copy= strdup_root(m_mem_root, str);
+ m_message_text.set(copy, strlen(copy), error_message_charset_info);
+ DBUG_ASSERT(! m_message_text.is_alloced());
+}
+
+const char*
+Sql_condition::get_message_text() const
+{
+ return m_message_text.ptr();
+}
+
+int
+Sql_condition::get_message_octet_length() const
+{
+ return m_message_text.length();
+}
+
+void
+Sql_condition::set_sqlstate(const char* sqlstate)
+{
+ memcpy(m_returned_sqlstate, sqlstate, SQLSTATE_LENGTH);
+ m_returned_sqlstate[SQLSTATE_LENGTH]= '\0';
+}
+
+Diagnostics_area::Diagnostics_area(bool initialize)
+ : m_main_wi(0, false, initialize)
+{
+ push_warning_info(&m_main_wi);
+
+ reset_diagnostics_area();
+}
+
+Diagnostics_area::Diagnostics_area(ulonglong warning_info_id,
+ bool allow_unlimited_warnings,
+ bool initialize)
+ : m_main_wi(warning_info_id, allow_unlimited_warnings, initialize)
+{
+ push_warning_info(&m_main_wi);
+
+ reset_diagnostics_area();
+}
+
+/**
+ Clear this diagnostics area.
+
+ Normally called at the end of a statement.
+*/
+
+void
+Diagnostics_area::reset_diagnostics_area()
+{
+ DBUG_ENTER("reset_diagnostics_area");
+#ifdef DBUG_OFF
+ m_can_overwrite_status= FALSE;
+ /** Don't take chances in production */
+ m_message[0]= '\0';
+ m_sql_errno= 0;
+ m_affected_rows= 0;
+ m_last_insert_id= 0;
+ m_statement_warn_count= 0;
+#endif
+ get_warning_info()->clear_error_condition();
+ set_is_sent(false);
+ /** Tiny reset in debug mode to see garbage right away */
+ m_status= DA_EMPTY;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set OK status -- ends commands that do not return a
+ result set, e.g. INSERT/UPDATE/DELETE.
+*/
+
+void
+Diagnostics_area::set_ok_status(ulonglong affected_rows,
+ ulonglong last_insert_id,
+ const char *message)
+{
+ DBUG_ENTER("set_ok_status");
+ DBUG_ASSERT(! is_set());
+ /*
+ In production, refuse to overwrite an error or a custom response
+ with an OK packet.
+ */
+ if (is_error() || is_disabled())
+ return;
+
+ m_statement_warn_count= current_statement_warn_count();
+ m_affected_rows= affected_rows;
+ m_last_insert_id= last_insert_id;
+ if (message)
+ strmake_buf(m_message, message);
+ else
+ m_message[0]= '\0';
+ m_status= DA_OK;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set EOF status.
+*/
+
+void
+Diagnostics_area::set_eof_status(THD *thd)
+{
+ DBUG_ENTER("set_eof_status");
+ /* Only allowed to report eof if has not yet reported an error */
+ DBUG_ASSERT(! is_set());
+ /*
+ In production, refuse to overwrite an error or a custom response
+ with an EOF packet.
+ */
+ if (is_error() || is_disabled())
+ return;
+
+ /*
+ If inside a stored procedure, do not return the total
+ number of warnings, since they are not available to the client
+ anyway.
+ */
+ m_statement_warn_count= (thd->spcont ?
+ 0 :
+ current_statement_warn_count());
+
+ m_status= DA_EOF;
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Set ERROR status in the Diagnostics Area. This function should be used to
+ report fatal errors (such as out-of-memory errors) when no further
+ processing is possible.
+
+ @param sql_errno SQL-condition error number
+*/
+
+void
+Diagnostics_area::set_error_status(uint sql_errno)
+{
+ set_error_status(sql_errno,
+ ER(sql_errno),
+ mysql_errno_to_sqlstate(sql_errno),
+ NULL);
+}
+
+
+/**
+ Set ERROR status in the Diagnostics Area.
+
+ @note error_condition may be NULL. It happens if a) OOM error is being
+ reported; or b) when Warning_info is full.
+
+ @param sql_errno SQL-condition error number
+ @param message SQL-condition message
+ @param sqlstate SQL-condition state
+ @param error_condition SQL-condition object representing the error state
+
+ @note Note, that error_condition may be NULL. It happens if a) OOM error is
+ being reported; or b) when Warning_info is full.
+*/
+
+void
+Diagnostics_area::set_error_status(uint sql_errno,
+ const char *message,
+ const char *sqlstate,
+ const Sql_condition *error_condition)
+{
+ DBUG_ENTER("set_error_status");
+ DBUG_PRINT("enter", ("error: %d", sql_errno));
+ /*
+ Only allowed to report error if has not yet reported a success
+ The only exception is when we flush the message to the client,
+ an error can happen during the flush.
+ */
+ DBUG_ASSERT(! is_set() || m_can_overwrite_status);
+
+ // message must be set properly by the caller.
+ DBUG_ASSERT(message);
+
+ // sqlstate must be set properly by the caller.
+ DBUG_ASSERT(sqlstate);
+
+#ifdef DBUG_OFF
+ /*
+ In production, refuse to overwrite a custom response with an
+ ERROR packet.
+ */
+ if (is_disabled())
+ return;
+#endif
+
+ m_sql_errno= sql_errno;
+ memcpy(m_sqlstate, sqlstate, SQLSTATE_LENGTH);
+ m_sqlstate[SQLSTATE_LENGTH]= '\0';
+ strmake_buf(m_message, message);
+
+ get_warning_info()->set_error_condition(error_condition);
+
+ m_status= DA_ERROR;
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Mark the diagnostics area as 'DISABLED'.
+
+ This is used in rare cases when the COM_ command at hand sends a response
+ in a custom format. One example is the query cache, another is
+ COM_STMT_PREPARE.
+*/
+
+void
+Diagnostics_area::disable_status()
+{
+ DBUG_ASSERT(! is_set());
+ m_status= DA_DISABLED;
+}
+
+Warning_info::Warning_info(ulonglong warn_id_arg,
+ bool allow_unlimited_warnings, bool initialize)
+ :m_current_statement_warn_count(0),
+ m_current_row_for_warning(1),
+ m_warn_id(warn_id_arg),
+ m_error_condition(NULL),
+ m_allow_unlimited_warnings(allow_unlimited_warnings),
+ initialized(0),
+ m_read_only(FALSE)
+{
+ m_warn_list.empty();
+ memset(m_warn_count, 0, sizeof(m_warn_count));
+ if (initialize)
+ init();
+}
+
+void Warning_info::init()
+{
+ /* Initialize sub structures */
+ DBUG_ASSERT(initialized == 0);
+ init_sql_alloc(&m_warn_root, WARN_ALLOC_BLOCK_SIZE,
+ WARN_ALLOC_PREALLOC_SIZE, MYF(MY_THREAD_SPECIFIC));
+ initialized= 1;
+}
+
+void Warning_info::free_memory()
+{
+ if (initialized)
+ free_root(&m_warn_root,MYF(0));
+}
+
+Warning_info::~Warning_info()
+{
+ free_memory();
+}
+
+
+bool Warning_info::has_sql_condition(const char *message_str,
+ ulong message_length) const
+{
+ Diagnostics_area::Sql_condition_iterator it(m_warn_list);
+ const Sql_condition *err;
+
+ while ((err= it++))
+ {
+ if (strncmp(message_str, err->get_message_text(), message_length) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+
+void Warning_info::clear(ulonglong new_id)
+{
+ id(new_id);
+ m_warn_list.empty();
+ m_marked_sql_conditions.empty();
+ free_memory();
+ memset(m_warn_count, 0, sizeof(m_warn_count));
+ m_current_statement_warn_count= 0;
+ m_current_row_for_warning= 1; /* Start counting from the first row */
+ clear_error_condition();
+}
+
+void Warning_info::append_warning_info(THD *thd, const Warning_info *source)
+{
+ const Sql_condition *err;
+ Diagnostics_area::Sql_condition_iterator it(source->m_warn_list);
+ const Sql_condition *src_error_condition = source->get_error_condition();
+
+ while ((err= it++))
+ {
+ // Do not use ::push_warning() to avoid invocation of THD-internal-handlers.
+ Sql_condition *new_error= Warning_info::push_warning(thd, err);
+
+ if (src_error_condition && src_error_condition == err)
+ set_error_condition(new_error);
+
+ if (source->is_marked_for_removal(err))
+ mark_condition_for_removal(new_error);
+ }
+}
+
+
+/**
+ Copy Sql_conditions that are not WARN_LEVEL_ERROR from the source
+ Warning_info to the current Warning_info.
+
+ @param thd Thread context.
+ @param sp_wi Stored-program Warning_info
+ @param thd Thread context.
+ @param src_wi Warning_info to copy from.
+*/
+void Diagnostics_area::copy_non_errors_from_wi(THD *thd,
+ const Warning_info *src_wi)
+{
+ Sql_condition_iterator it(src_wi->m_warn_list);
+ const Sql_condition *cond;
+ Warning_info *wi= get_warning_info();
+
+ while ((cond= it++))
+ {
+ if (cond->get_level() == Sql_condition::WARN_LEVEL_ERROR)
+ continue;
+
+ Sql_condition *new_condition= wi->push_warning(thd, cond);
+
+ if (src_wi->is_marked_for_removal(cond))
+ wi->mark_condition_for_removal(new_condition);
+ }
+}
+
+
+void Warning_info::mark_sql_conditions_for_removal()
+{
+ Sql_condition_list::Iterator it(m_warn_list);
+ Sql_condition *cond;
+
+ while ((cond= it++))
+ mark_condition_for_removal(cond);
+}
+
+
+void Warning_info::remove_marked_sql_conditions()
+{
+ List_iterator_fast<Sql_condition> it(m_marked_sql_conditions);
+ Sql_condition *cond;
+
+ while ((cond= it++))
+ {
+ m_warn_list.remove(cond);
+ m_warn_count[cond->get_level()]--;
+ m_current_statement_warn_count--;
+ if (cond == m_error_condition)
+ m_error_condition= NULL;
+ }
+
+ m_marked_sql_conditions.empty();
+}
+
+
+bool Warning_info::is_marked_for_removal(const Sql_condition *cond) const
+{
+ List_iterator_fast<Sql_condition> it(
+ const_cast<List<Sql_condition>&> (m_marked_sql_conditions));
+ Sql_condition *c;
+
+ while ((c= it++))
+ {
+ if (c == cond)
+ return true;
+ }
+
+ return false;
+}
+
+
+void Warning_info::reserve_space(THD *thd, uint count)
+{
+ while (m_warn_list.elements() &&
+ (m_warn_list.elements() + count) > thd->variables.max_error_count)
+ m_warn_list.remove(m_warn_list.front());
+}
+
+Sql_condition *Warning_info::push_warning(THD *thd,
+ uint sql_errno, const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char *msg)
+{
+ Sql_condition *cond= NULL;
+
+ if (! m_read_only)
+ {
+ if (m_allow_unlimited_warnings ||
+ m_warn_list.elements() < thd->variables.max_error_count)
+ {
+ cond= new (& m_warn_root) Sql_condition(& m_warn_root);
+ if (cond)
+ {
+ cond->set(sql_errno, sqlstate, level, msg);
+ m_warn_list.push_back(cond);
+ }
+ }
+ m_warn_count[(uint) level]++;
+ }
+
+ m_current_statement_warn_count++;
+ return cond;
+}
+
+
+Sql_condition *Warning_info::push_warning(THD *thd, const Sql_condition *sql_condition)
+{
+ Sql_condition *new_condition= push_warning(thd,
+ sql_condition->get_sql_errno(),
+ sql_condition->get_sqlstate(),
+ sql_condition->get_level(),
+ sql_condition->get_message_text());
+
+ if (new_condition)
+ new_condition->copy_opt_attributes(sql_condition);
+
+ return new_condition;
+}
+
+/*
+ Push the warning to error list if there is still room in the list
+
+ SYNOPSIS
+ push_warning()
+ thd Thread handle
+ level Severity of warning (note, warning)
+ code Error number
+ msg Clear error message
+*/
+
+void push_warning(THD *thd, Sql_condition::enum_warning_level level,
+ uint code, const char *msg)
+{
+ DBUG_ENTER("push_warning");
+ DBUG_PRINT("enter", ("code: %d, msg: %s", code, msg));
+
+ /*
+ Calling push_warning/push_warning_printf with a level of
+ WARN_LEVEL_ERROR *is* a bug. Either use my_printf_error(),
+ my_error(), or WARN_LEVEL_WARN.
+ */
+ DBUG_ASSERT(level != Sql_condition::WARN_LEVEL_ERROR);
+
+ if (level == Sql_condition::WARN_LEVEL_ERROR)
+ level= Sql_condition::WARN_LEVEL_WARN;
+
+ (void) thd->raise_condition(code, NULL, level, msg);
+
+ /* Make sure we also count warnings pushed after calling set_ok_status(). */
+ thd->get_stmt_da()->increment_warning();
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Push the warning to error list if there is still room in the list
+
+ SYNOPSIS
+ push_warning_printf()
+ thd Thread handle
+ level Severity of warning (note, warning)
+ code Error number
+ msg Clear error message
+*/
+
+void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level,
+ uint code, const char *format, ...)
+{
+ va_list args;
+ char warning[MYSQL_ERRMSG_SIZE];
+ DBUG_ENTER("push_warning_printf");
+ DBUG_PRINT("enter",("warning: %u", code));
+
+ DBUG_ASSERT(code != 0);
+ DBUG_ASSERT(format != NULL);
+
+ va_start(args,format);
+ my_vsnprintf_ex(&my_charset_utf8_general_ci, warning,
+ sizeof(warning), format, args);
+ va_end(args);
+ push_warning(thd, level, code, warning);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Send all notes, errors or warnings to the client in a result set
+
+ SYNOPSIS
+ mysqld_show_warnings()
+ thd Thread handler
+ levels_to_show Bitmap for which levels to show
+
+ DESCRIPTION
+ Takes into account the current LIMIT
+
+ RETURN VALUES
+ FALSE ok
+ TRUE Error sending data to client
+*/
+
+const LEX_STRING warning_level_names[]=
+{
+ { C_STRING_WITH_LEN("Note") },
+ { C_STRING_WITH_LEN("Warning") },
+ { C_STRING_WITH_LEN("Error") },
+ { C_STRING_WITH_LEN("?") }
+};
+
+bool mysqld_show_warnings(THD *thd, ulong levels_to_show)
+{
+ List<Item> field_list;
+ DBUG_ENTER("mysqld_show_warnings");
+
+ DBUG_ASSERT(thd->get_stmt_da()->is_warning_info_read_only());
+
+ field_list.push_back(new Item_empty_string("Level", 7));
+ field_list.push_back(new Item_return_int("Code",4, MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_empty_string("Message",MYSQL_ERRMSG_SIZE));
+
+ if (thd->protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ const Sql_condition *err;
+ SELECT_LEX *sel= &thd->lex->select_lex;
+ SELECT_LEX_UNIT *unit= &thd->lex->unit;
+ ulonglong idx= 0;
+ Protocol *protocol=thd->protocol;
+
+ unit->set_limit(sel);
+
+ Diagnostics_area::Sql_condition_iterator it=
+ thd->get_stmt_da()->sql_conditions();
+ while ((err= it++))
+ {
+ /* Skip levels that the user is not interested in */
+ if (!(levels_to_show & ((ulong) 1 << err->get_level())))
+ continue;
+ if (++idx <= unit->offset_limit_cnt)
+ continue;
+ if (idx > unit->select_limit_cnt)
+ break;
+ protocol->prepare_for_resend();
+ protocol->store(warning_level_names[err->get_level()].str,
+ warning_level_names[err->get_level()].length,
+ system_charset_info);
+ protocol->store((uint32) err->get_sql_errno());
+ protocol->store(err->get_message_text(),
+ err->get_message_octet_length(),
+ system_charset_info);
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+ }
+ my_eof(thd);
+
+ thd->get_stmt_da()->set_warning_info_read_only(FALSE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Convert value for dispatch to error message(see WL#751).
+
+ @param to buffer for converted string
+ @param to_length size of the buffer
+ @param from string which should be converted
+ @param from_length string length
+ @param from_cs charset from convert
+
+ @retval
+ result string
+*/
+
+char *err_conv(char *buff, uint to_length, const char *from,
+ uint from_length, CHARSET_INFO *from_cs)
+{
+ char *to= buff;
+ const char *from_start= from;
+ size_t res;
+
+ DBUG_ASSERT(to_length > 0);
+ to_length--;
+ if (from_cs == &my_charset_bin)
+ {
+ uchar char_code;
+ res= 0;
+ while (1)
+ {
+ if ((uint)(from - from_start) >= from_length ||
+ res >= to_length)
+ {
+ *to= 0;
+ break;
+ }
+
+ char_code= ((uchar) *from);
+ if (char_code >= 0x20 && char_code <= 0x7E)
+ {
+ *to++= char_code;
+ from++;
+ res++;
+ }
+ else
+ {
+ if (res + 4 >= to_length)
+ {
+ *to= 0;
+ break;
+ }
+ res+= my_snprintf(to, 5, "\\x%02X", (uint) char_code);
+ to+=4;
+ from++;
+ }
+ }
+ }
+ else
+ {
+ uint errors;
+ res= copy_and_convert(to, to_length, system_charset_info,
+ from, from_length, from_cs, &errors);
+ to[res]= 0;
+ }
+ return buff;
+}
+
+
+/**
+ Convert string for dispatch to client(see WL#751).
+
+ @param to buffer to convert
+ @param to_length buffer length
+ @param to_cs chraset to convert
+ @param from string from convert
+ @param from_length string length
+ @param from_cs charset from convert
+ @param errors count of errors during convertion
+
+ @retval
+ length of converted string
+*/
+
+uint32 convert_error_message(char *to, uint32 to_length, CHARSET_INFO *to_cs,
+ const char *from, uint32 from_length,
+ CHARSET_INFO *from_cs, uint *errors)
+{
+ int cnvres;
+ my_wc_t wc;
+ const uchar *from_end= (const uchar*) from+from_length;
+ char *to_start= to;
+ uchar *to_end;
+ my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc;
+ my_charset_conv_wc_mb wc_mb;
+ uint error_count= 0;
+ uint length;
+
+ DBUG_ASSERT(to_length > 0);
+ /* Make room for the null terminator. */
+ to_length--;
+ to_end= (uchar*) (to + to_length);
+
+ if (!to_cs || from_cs == to_cs || to_cs == &my_charset_bin)
+ {
+ length= MY_MIN(to_length, from_length);
+ memmove(to, from, length);
+ to[length]= 0;
+ return length;
+ }
+
+ wc_mb= to_cs->cset->wc_mb;
+ while (1)
+ {
+ if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, from_end)) > 0)
+ {
+ if (!wc)
+ break;
+ from+= cnvres;
+ }
+ else if (cnvres == MY_CS_ILSEQ)
+ {
+ wc= (ulong) (uchar) *from;
+ from+=1;
+ }
+ else
+ break;
+
+ if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0)
+ to+= cnvres;
+ else if (cnvres == MY_CS_ILUNI)
+ {
+ length= (wc <= 0xFFFF) ? 6/* '\1234' format*/ : 9 /* '\+123456' format*/;
+ if ((uchar*)(to + length) >= to_end)
+ break;
+ cnvres= my_snprintf(to, 9,
+ (wc <= 0xFFFF) ? "\\%04X" : "\\+%06X", (uint) wc);
+ to+= cnvres;
+ }
+ else
+ break;
+ }
+
+ *to= 0;
+ *errors= error_count;
+ return (uint32) (to - to_start);
+}
+
+
+/**
+ Sanity check for SQLSTATEs. The function does not check if it's really an
+ existing SQL-state (there are just too many), it just checks string length and
+ looks for bad characters.
+
+ @param sqlstate the condition SQLSTATE.
+
+ @retval true if it's ok.
+ @retval false if it's bad.
+*/
+
+bool is_sqlstate_valid(const LEX_STRING *sqlstate)
+{
+ if (sqlstate->length != 5)
+ return false;
+
+ for (int i= 0 ; i < 5 ; ++i)
+ {
+ char c = sqlstate->str[i];
+
+ if ((c < '0' || '9' < c) &&
+ (c < 'A' || 'Z' < c))
+ return false;
+ }
+
+ return true;
+}
diff --git a/sql/sql_error.h b/sql/sql_error.h
new file mode 100644
index 00000000000..a993e9203c9
--- /dev/null
+++ b/sql/sql_error.h
@@ -0,0 +1,976 @@
+/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#ifndef SQL_ERROR_H
+#define SQL_ERROR_H
+
+#include "sql_list.h" /* Sql_alloc, MEM_ROOT */
+#include "m_string.h" /* LEX_STRING */
+#include "sql_string.h" /* String */
+#include "sql_plist.h" /* I_P_List */
+#include "mysql_com.h" /* MYSQL_ERRMSG_SIZE */
+#include "my_time.h" /* MYSQL_TIME */
+#include "decimal.h"
+
+class THD;
+class my_decimal;
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Representation of a SQL condition.
+ A SQL condition can be a completion condition (note, warning),
+ or an exception condition (error, not found).
+*/
+class Sql_condition : public Sql_alloc
+{
+public:
+ /*
+ Enumeration value describing the severity of the error.
+
+ Note that these enumeration values must correspond to the indices
+ of the sql_print_message_handlers array.
+ */
+ enum enum_warning_level
+ { WARN_LEVEL_NOTE, WARN_LEVEL_WARN, WARN_LEVEL_ERROR, WARN_LEVEL_END};
+
+ /**
+ Convert a bitmask consisting of MYSQL_TIME_{NOTE|WARN}_XXX bits
+ to WARN_LEVEL_XXX
+ */
+ static enum_warning_level time_warn_level(int warnings)
+ {
+ return MYSQL_TIME_WARN_HAVE_WARNINGS(warnings) ?
+ WARN_LEVEL_WARN : WARN_LEVEL_NOTE;
+ }
+
+ /**
+ Get the MESSAGE_TEXT of this condition.
+ @return the message text.
+ */
+ const char* get_message_text() const;
+
+ /**
+ Get the MESSAGE_OCTET_LENGTH of this condition.
+ @return the length in bytes of the message text.
+ */
+ int get_message_octet_length() const;
+
+ /**
+ Get the SQLSTATE of this condition.
+ @return the sql state.
+ */
+ const char* get_sqlstate() const
+ { return m_returned_sqlstate; }
+
+ /**
+ Get the SQL_ERRNO of this condition.
+ @return the sql error number condition item.
+ */
+ uint get_sql_errno() const
+ { return m_sql_errno; }
+
+ /**
+ Get the error level of this condition.
+ @return the error level condition item.
+ */
+ Sql_condition::enum_warning_level get_level() const
+ { return m_level; }
+
+private:
+ /*
+ The interface of Sql_condition is mostly private, by design,
+ so that only the following code:
+ - various raise_error() or raise_warning() methods in class THD,
+ - the implementation of SIGNAL / RESIGNAL / GET DIAGNOSTICS
+ - catch / re-throw of SQL conditions in stored procedures (sp_rcontext)
+ is allowed to create / modify a SQL condition.
+ Enforcing this policy prevents confusion, since the only public
+ interface available to the rest of the server implementation
+ is the interface offered by the THD methods (THD::raise_error()),
+ which should be used.
+ */
+ friend class THD;
+ friend class Warning_info;
+ friend class Sql_cmd_common_signal;
+ friend class Sql_cmd_signal;
+ friend class Sql_cmd_resignal;
+ friend class sp_rcontext;
+ friend class Condition_information_item;
+
+ /**
+ Default constructor.
+ This constructor is usefull when allocating arrays.
+ Note that the init() method should be called to complete the Sql_condition.
+ */
+ Sql_condition();
+
+ /**
+ Complete the Sql_condition initialisation.
+ @param mem_root The memory root to use for the condition items
+ of this condition
+ */
+ void init(MEM_ROOT *mem_root);
+
+ /**
+ Constructor.
+ @param mem_root The memory root to use for the condition items
+ of this condition
+ */
+ Sql_condition(MEM_ROOT *mem_root);
+
+ /** Destructor. */
+ ~Sql_condition()
+ {}
+
+ /**
+ Copy optional condition items attributes.
+ @param cond the condition to copy.
+ */
+ void copy_opt_attributes(const Sql_condition *cond);
+
+ /**
+ Set this condition area with a fixed message text.
+ @param thd the current thread.
+ @param code the error number for this condition.
+ @param str the message text for this condition.
+ @param level the error level for this condition.
+ @param MyFlags additional flags.
+ */
+ void set(uint sql_errno, const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg);
+
+ /**
+ Set the condition message test.
+ @param str Message text, expressed in the character set derived from
+ the server --language option
+ */
+ void set_builtin_message_text(const char* str);
+
+ /** Set the SQLSTATE of this condition. */
+ void set_sqlstate(const char* sqlstate);
+
+ /** Set the CLASS_ORIGIN of this condition. */
+ void set_class_origin();
+
+ /** Set the SUBCLASS_ORIGIN of this condition. */
+ void set_subclass_origin();
+
+ /**
+ Clear this SQL condition.
+ */
+ void clear();
+
+private:
+ /** SQL CLASS_ORIGIN condition item. */
+ String m_class_origin;
+
+ /** SQL SUBCLASS_ORIGIN condition item. */
+ String m_subclass_origin;
+
+ /** SQL CONSTRAINT_CATALOG condition item. */
+ String m_constraint_catalog;
+
+ /** SQL CONSTRAINT_SCHEMA condition item. */
+ String m_constraint_schema;
+
+ /** SQL CONSTRAINT_NAME condition item. */
+ String m_constraint_name;
+
+ /** SQL CATALOG_NAME condition item. */
+ String m_catalog_name;
+
+ /** SQL SCHEMA_NAME condition item. */
+ String m_schema_name;
+
+ /** SQL TABLE_NAME condition item. */
+ String m_table_name;
+
+ /** SQL COLUMN_NAME condition item. */
+ String m_column_name;
+
+ /** SQL CURSOR_NAME condition item. */
+ String m_cursor_name;
+
+ /** Message text, expressed in the character set implied by --language. */
+ String m_message_text;
+
+ /** MySQL extension, MYSQL_ERRNO condition item. */
+ uint m_sql_errno;
+
+ /**
+ SQL RETURNED_SQLSTATE condition item.
+ This member is always NUL terminated.
+ */
+ char m_returned_sqlstate[SQLSTATE_LENGTH+1];
+
+ /** Severity (error, warning, note) of this condition. */
+ Sql_condition::enum_warning_level m_level;
+
+ /** Pointers for participating in the list of conditions. */
+ Sql_condition *next_in_wi;
+ Sql_condition **prev_in_wi;
+
+ /** Memory root to use to hold condition item values. */
+ MEM_ROOT *m_mem_root;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Information about warnings of the current connection.
+*/
+class Warning_info
+{
+ /** The type of the counted and doubly linked list of conditions. */
+ typedef I_P_List<Sql_condition,
+ I_P_List_adapter<Sql_condition,
+ &Sql_condition::next_in_wi,
+ &Sql_condition::prev_in_wi>,
+ I_P_List_counter,
+ I_P_List_fast_push_back<Sql_condition> >
+ Sql_condition_list;
+
+ /** A memory root to allocate warnings and errors */
+ MEM_ROOT m_warn_root;
+
+ /** List of warnings of all severities (levels). */
+ Sql_condition_list m_warn_list;
+
+ /** A break down of the number of warnings per severity (level). */
+ uint m_warn_count[(uint) Sql_condition::WARN_LEVEL_END];
+
+ /**
+ The number of warnings of the current statement. Warning_info
+ life cycle differs from statement life cycle -- it may span
+ multiple statements. In that case we get
+ m_current_statement_warn_count 0, whereas m_warn_list is not empty.
+ */
+ uint m_current_statement_warn_count;
+
+ /*
+ Row counter, to print in errors and warnings. Not increased in
+ create_sort_index(); may differ from examined_row_count.
+ */
+ ulong m_current_row_for_warning;
+
+ /** Used to optionally clear warnings only once per statement. */
+ ulonglong m_warn_id;
+
+ /**
+ A pointer to an element of m_warn_list. It determines SQL-condition
+ instance which corresponds to the error state in Diagnostics_area.
+
+ This is needed for properly processing SQL-conditions in SQL-handlers.
+ When an SQL-handler is found for the current error state in Diagnostics_area,
+ this pointer is needed to remove the corresponding SQL-condition from the
+ Warning_info list.
+
+ @note m_error_condition might be NULL in the following cases:
+ - Diagnostics_area set to fatal error state (like OOM);
+ - Max number of Warning_info elements has been reached (thus, there is
+ no corresponding SQL-condition object in Warning_info).
+ */
+ const Sql_condition *m_error_condition;
+
+ /** Indicates if push_warning() allows unlimited number of warnings. */
+ bool m_allow_unlimited_warnings;
+ bool initialized; /* Set to 1 if init() has been called */
+
+ /** Read only status. */
+ bool m_read_only;
+
+ /** Pointers for participating in the stack of Warning_info objects. */
+ Warning_info *m_next_in_da;
+ Warning_info **m_prev_in_da;
+
+ List<Sql_condition> m_marked_sql_conditions;
+
+public:
+ Warning_info(ulonglong warn_id_arg, bool allow_unlimited_warnings,
+ bool initialized);
+ ~Warning_info();
+ /* Allocate memory for structures */
+ void init();
+ void free_memory();
+
+private:
+ Warning_info(const Warning_info &rhs); /* Not implemented */
+ Warning_info& operator=(const Warning_info &rhs); /* Not implemented */
+
+ /**
+ Checks if Warning_info contains SQL-condition with the given message.
+
+ @param message_str Message string.
+ @param message_length Length of message string.
+
+ @return true if the Warning_info contains an SQL-condition with the given
+ message.
+ */
+ bool has_sql_condition(const char *message_str, ulong message_length) const;
+
+ /**
+ Reset the warning information. Clear all warnings,
+ the number of warnings, reset current row counter
+ to point to the first row.
+
+ @param new_id new Warning_info id.
+ */
+ void clear(ulonglong new_id);
+
+ /**
+ Only clear warning info if haven't yet done that already
+ for the current query. Allows to be issued at any time
+ during the query, without risk of clearing some warnings
+ that have been generated by the current statement.
+
+ @todo: This is a sign of sloppy coding. Instead we need to
+ designate one place in a statement life cycle where we call
+ Warning_info::clear().
+
+ @param query_id Current query id.
+ */
+ void opt_clear(ulonglong query_id)
+ {
+ if (query_id != m_warn_id)
+ clear(query_id);
+ }
+
+ /**
+ Concatenate the list of warnings.
+
+ It's considered tolerable to lose an SQL-condition in case of OOM-error,
+ or if the number of SQL-conditions in the Warning_info reached top limit.
+
+ @param thd Thread context.
+ @param source Warning_info object to copy SQL-conditions from.
+ */
+ void append_warning_info(THD *thd, const Warning_info *source);
+
+ /**
+ Reset between two COM_ commands. Warnings are preserved
+ between commands, but statement_warn_count indicates
+ the number of warnings of this particular statement only.
+ */
+ void reset_for_next_command()
+ { m_current_statement_warn_count= 0; }
+
+ /**
+ Mark active SQL-conditions for later removal.
+ This is done to simulate stacked DAs for HANDLER statements.
+ */
+ void mark_sql_conditions_for_removal();
+
+ /**
+ Unmark SQL-conditions, which were marked for later removal.
+ This is done to simulate stacked DAs for HANDLER statements.
+ */
+ void unmark_sql_conditions_from_removal()
+ { m_marked_sql_conditions.empty(); }
+
+ /**
+ Remove SQL-conditions that are marked for deletion.
+ This is done to simulate stacked DAs for HANDLER statements.
+ */
+ void remove_marked_sql_conditions();
+
+ /**
+ Check if the given SQL-condition is marked for removal in this Warning_info
+ instance.
+
+ @param cond the SQL-condition.
+
+ @retval true if the given SQL-condition is marked for removal in this
+ Warning_info instance.
+ @retval false otherwise.
+ */
+ bool is_marked_for_removal(const Sql_condition *cond) const;
+
+ /**
+ Mark a single SQL-condition for removal (add the given SQL-condition to the
+ removal list of this Warning_info instance).
+ */
+ void mark_condition_for_removal(Sql_condition *cond)
+ { m_marked_sql_conditions.push_back(cond, &m_warn_root); }
+
+ /**
+ Used for @@warning_count system variable, which prints
+ the number of rows returned by SHOW WARNINGS.
+ */
+ ulong warn_count() const
+ {
+ /*
+ This may be higher than warn_list.elements() if we have
+ had more warnings than thd->variables.max_error_count.
+ */
+ return (m_warn_count[(uint) Sql_condition::WARN_LEVEL_NOTE] +
+ m_warn_count[(uint) Sql_condition::WARN_LEVEL_ERROR] +
+ m_warn_count[(uint) Sql_condition::WARN_LEVEL_WARN]);
+ }
+
+ /**
+ The number of errors, or number of rows returned by SHOW ERRORS,
+ also the value of session variable @@error_count.
+ */
+ ulong error_count() const
+ { return m_warn_count[(uint) Sql_condition::WARN_LEVEL_ERROR]; }
+
+ /**
+ The number of conditions (errors, warnings and notes) in the list.
+ */
+ uint cond_count() const
+ {
+ return m_warn_list.elements();
+ }
+
+ /** Id of the warning information area. */
+ ulonglong id() const { return m_warn_id; }
+
+ /** Set id of the warning information area. */
+ void id(ulonglong id) { m_warn_id= id; }
+
+ /** Do we have any errors and warnings that we can *show*? */
+ bool is_empty() const { return m_warn_list.is_empty(); }
+
+ /** Increment the current row counter to point at the next row. */
+ void inc_current_row_for_warning() { m_current_row_for_warning++; }
+
+ /** Reset the current row counter. Start counting from the first row. */
+ void reset_current_row_for_warning() { m_current_row_for_warning= 1; }
+
+ /** Return the current counter value. */
+ ulong current_row_for_warning() const { return m_current_row_for_warning; }
+
+ /** Return the number of warnings thrown by the current statement. */
+ ulong current_statement_warn_count() const
+ { return m_current_statement_warn_count; }
+
+ /** Make sure there is room for the given number of conditions. */
+ void reserve_space(THD *thd, uint count);
+
+ /**
+ Add a new SQL-condition to the current list and increment the respective
+ counters.
+
+ @param thd Thread context.
+ @param sql_errno SQL-condition error number.
+ @param sqlstate SQL-condition state.
+ @param level SQL-condition level.
+ @param msg SQL-condition message.
+
+ @return a pointer to the added SQL-condition.
+ */
+ Sql_condition *push_warning(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg);
+
+ /**
+ Add a new SQL-condition to the current list and increment the respective
+ counters.
+
+ @param thd Thread context.
+ @param sql_condition SQL-condition to copy values from.
+
+ @return a pointer to the added SQL-condition.
+ */
+ Sql_condition *push_warning(THD *thd, const Sql_condition *sql_condition);
+
+ /**
+ Set the read only status for this statement area.
+ This is a privileged operation, reserved for the implementation of
+ diagnostics related statements, to enforce that the statement area is
+ left untouched during execution.
+ The diagnostics statements are:
+ - SHOW WARNINGS
+ - SHOW ERRORS
+ - GET DIAGNOSTICS
+ @param read_only the read only property to set.
+ */
+ void set_read_only(bool read_only)
+ { m_read_only= read_only; }
+
+ /**
+ Read only status.
+ @return the read only property.
+ */
+ bool is_read_only() const
+ { return m_read_only; }
+
+ /**
+ @return SQL-condition, which corresponds to the error state in
+ Diagnostics_area.
+
+ @see m_error_condition.
+ */
+ const Sql_condition *get_error_condition() const
+ { return m_error_condition; }
+
+ /**
+ Set SQL-condition, which corresponds to the error state in Diagnostics_area.
+
+ @see m_error_condition.
+ */
+ void set_error_condition(const Sql_condition *error_condition)
+ { m_error_condition= error_condition; }
+
+ /**
+ Reset SQL-condition, which corresponds to the error state in
+ Diagnostics_area.
+
+ @see m_error_condition.
+ */
+ void clear_error_condition()
+ { m_error_condition= NULL; }
+
+ // for:
+ // - m_next_in_da / m_prev_in_da
+ // - is_marked_for_removal()
+ friend class Diagnostics_area;
+};
+
+
+extern char *err_conv(char *buff, uint to_length, const char *from,
+ uint from_length, CHARSET_INFO *from_cs);
+
+class ErrConv
+{
+protected:
+ mutable char err_buffer[MYSQL_ERRMSG_SIZE];
+public:
+ ErrConv() {}
+ virtual ~ErrConv() {}
+ virtual const char *ptr() const = 0;
+};
+
+class ErrConvString : public ErrConv
+{
+ const char *str;
+ size_t len;
+ CHARSET_INFO *cs;
+public:
+ ErrConvString(const char *str_arg, size_t len_arg, CHARSET_INFO *cs_arg)
+ : ErrConv(), str(str_arg), len(len_arg), cs(cs_arg) {}
+ ErrConvString(const char *str_arg, CHARSET_INFO *cs_arg)
+ : ErrConv(), str(str_arg), len(strlen(str_arg)), cs(cs_arg) {}
+ ErrConvString(String *s)
+ : ErrConv(), str(s->ptr()), len(s->length()), cs(s->charset()) {}
+ const char *ptr() const
+ { return err_conv(err_buffer, sizeof(err_buffer), str, len, cs); }
+};
+
+class ErrConvInteger : public ErrConv
+{
+ longlong m_value;
+ bool m_unsigned;
+public:
+ ErrConvInteger(longlong num_arg, bool unsigned_flag= false) :
+ ErrConv(), m_value(num_arg), m_unsigned(unsigned_flag) {}
+ const char *ptr() const
+ {
+ return m_unsigned ? ullstr(m_value, err_buffer) :
+ llstr(m_value, err_buffer);
+ }
+};
+
+class ErrConvDouble: public ErrConv
+{
+ double num;
+public:
+ ErrConvDouble(double num_arg) : ErrConv(), num(num_arg) {}
+ const char *ptr() const
+ {
+ my_gcvt(num, MY_GCVT_ARG_DOUBLE, sizeof(err_buffer), err_buffer, 0);
+ return err_buffer;
+ }
+};
+
+class ErrConvTime : public ErrConv
+{
+ const MYSQL_TIME *ltime;
+public:
+ ErrConvTime(const MYSQL_TIME *ltime_arg) : ErrConv(), ltime(ltime_arg) {}
+ const char *ptr() const
+ {
+ my_TIME_to_str(ltime, err_buffer, AUTO_SEC_PART_DIGITS);
+ return err_buffer;
+ }
+};
+
+class ErrConvDecimal : public ErrConv
+{
+ const decimal_t *d;
+public:
+ ErrConvDecimal(const decimal_t *d_arg) : ErrConv(), d(d_arg) {}
+ const char *ptr() const
+ {
+ int len= sizeof(err_buffer);
+ decimal2string(d, err_buffer, &len, 0, 0, ' ');
+ return err_buffer;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Stores status of the currently executed statement.
+ Cleared at the beginning of the statement, and then
+ can hold either OK, ERROR, or EOF status.
+ Can not be assigned twice per statement.
+*/
+
+class Diagnostics_area
+{
+private:
+ /** The type of the counted and doubly linked list of conditions. */
+ typedef I_P_List<Warning_info,
+ I_P_List_adapter<Warning_info,
+ &Warning_info::m_next_in_da,
+ &Warning_info::m_prev_in_da>,
+ I_P_List_counter,
+ I_P_List_fast_push_back<Warning_info> >
+ Warning_info_list;
+
+public:
+ /** Const iterator used to iterate through the warning list. */
+ typedef Warning_info::Sql_condition_list::Const_Iterator
+ Sql_condition_iterator;
+
+ enum enum_diagnostics_status
+ {
+ /** The area is cleared at start of a statement. */
+ DA_EMPTY= 0,
+ /** Set whenever one calls my_ok(). */
+ DA_OK,
+ /** Set whenever one calls my_eof(). */
+ DA_EOF,
+ /** Set whenever one calls my_error() or my_message(). */
+ DA_ERROR,
+ /** Set in case of a custom response, such as one from COM_STMT_PREPARE. */
+ DA_DISABLED
+ };
+
+ void set_overwrite_status(bool can_overwrite_status)
+ { m_can_overwrite_status= can_overwrite_status; }
+
+ /** True if status information is sent to the client. */
+ bool is_sent() const { return m_is_sent; }
+
+ void set_is_sent(bool is_sent) { m_is_sent= is_sent; }
+
+ void set_ok_status(ulonglong affected_rows,
+ ulonglong last_insert_id,
+ const char *message);
+
+ void set_eof_status(THD *thd);
+
+ void set_error_status(uint sql_errno);
+
+ void set_error_status(uint sql_errno,
+ const char *message,
+ const char *sqlstate,
+ const Sql_condition *error_condition);
+
+ void disable_status();
+
+ void reset_diagnostics_area();
+
+ bool is_set() const { return m_status != DA_EMPTY; }
+
+ bool is_error() const { return m_status == DA_ERROR; }
+
+ bool is_eof() const { return m_status == DA_EOF; }
+
+ bool is_ok() const { return m_status == DA_OK; }
+
+ bool is_disabled() const { return m_status == DA_DISABLED; }
+
+ enum_diagnostics_status status() const { return m_status; }
+
+ const char *message() const
+ { DBUG_ASSERT(m_status == DA_ERROR || m_status == DA_OK); return m_message; }
+
+ uint sql_errno() const
+ { DBUG_ASSERT(m_status == DA_ERROR); return m_sql_errno; }
+
+ const char* get_sqlstate() const
+ { DBUG_ASSERT(m_status == DA_ERROR); return m_sqlstate; }
+
+ ulonglong affected_rows() const
+ { DBUG_ASSERT(m_status == DA_OK); return m_affected_rows; }
+
+ ulonglong last_insert_id() const
+ { DBUG_ASSERT(m_status == DA_OK); return m_last_insert_id; }
+
+ uint statement_warn_count() const
+ {
+ DBUG_ASSERT(m_status == DA_OK || m_status == DA_EOF);
+ return m_statement_warn_count;
+ }
+
+ /* Used to count any warnings pushed after calling set_ok_status(). */
+ void increment_warning()
+ {
+ if (m_status != DA_EMPTY)
+ m_statement_warn_count++;
+ }
+
+ Diagnostics_area(bool initialize);
+ Diagnostics_area(ulonglong warning_info_id, bool allow_unlimited_warnings,
+ bool initialize);
+ void init() { m_main_wi.init() ; }
+ void free_memory() { m_main_wi.free_memory() ; }
+
+ void push_warning_info(Warning_info *wi)
+ { m_wi_stack.push_front(wi); }
+
+ void pop_warning_info()
+ {
+ DBUG_ASSERT(m_wi_stack.elements() > 0);
+ m_wi_stack.remove(m_wi_stack.front());
+ }
+
+ void set_warning_info_id(ulonglong id)
+ { get_warning_info()->id(id); }
+
+ ulonglong warning_info_id() const
+ { return get_warning_info()->id(); }
+
+ /**
+ Compare given current warning info and current warning info
+ and see if they are different. They will be different if
+ warnings have been generated or statements that use tables
+ have been executed. This is checked by comparing m_warn_id.
+
+ @param wi Warning info to compare with current Warning info.
+
+ @return false if they are equal, true if they are not.
+ */
+ bool warning_info_changed(const Warning_info *wi) const
+ { return get_warning_info()->id() != wi->id(); }
+
+ bool is_warning_info_empty() const
+ { return get_warning_info()->is_empty(); }
+
+ ulong current_statement_warn_count() const
+ { return get_warning_info()->current_statement_warn_count(); }
+
+ bool has_sql_condition(const char *message_str, ulong message_length) const
+ { return get_warning_info()->has_sql_condition(message_str, message_length); }
+
+ void reset_for_next_command()
+ { get_warning_info()->reset_for_next_command(); }
+
+ void clear_warning_info(ulonglong id)
+ { get_warning_info()->clear(id); }
+
+ void opt_clear_warning_info(ulonglong query_id)
+ { get_warning_info()->opt_clear(query_id); }
+
+ ulong current_row_for_warning() const
+ { return get_warning_info()->current_row_for_warning(); }
+
+ void inc_current_row_for_warning()
+ { get_warning_info()->inc_current_row_for_warning(); }
+
+ void reset_current_row_for_warning()
+ { get_warning_info()->reset_current_row_for_warning(); }
+
+ bool is_warning_info_read_only() const
+ { return get_warning_info()->is_read_only(); }
+
+ void set_warning_info_read_only(bool read_only)
+ { get_warning_info()->set_read_only(read_only); }
+
+ ulong error_count() const
+ { return get_warning_info()->error_count(); }
+
+ ulong warn_count() const
+ { return get_warning_info()->warn_count(); }
+
+ uint cond_count() const
+ { return get_warning_info()->cond_count(); }
+
+ Sql_condition_iterator sql_conditions() const
+ { return get_warning_info()->m_warn_list; }
+
+ void reserve_space(THD *thd, uint count)
+ { get_warning_info()->reserve_space(thd, count); }
+
+ Sql_condition *push_warning(THD *thd, const Sql_condition *sql_condition)
+ { return get_warning_info()->push_warning(thd, sql_condition); }
+
+ Sql_condition *push_warning(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg)
+ {
+ return get_warning_info()->push_warning(thd,
+ sql_errno, sqlstate, level, msg);
+ }
+
+ void mark_sql_conditions_for_removal()
+ { get_warning_info()->mark_sql_conditions_for_removal(); }
+
+ void unmark_sql_conditions_from_removal()
+ { get_warning_info()->unmark_sql_conditions_from_removal(); }
+
+ void remove_marked_sql_conditions()
+ { get_warning_info()->remove_marked_sql_conditions(); }
+
+ const Sql_condition *get_error_condition() const
+ { return get_warning_info()->get_error_condition(); }
+
+ void copy_sql_conditions_to_wi(THD *thd, Warning_info *dst_wi) const
+ { dst_wi->append_warning_info(thd, get_warning_info()); }
+
+ void copy_sql_conditions_from_wi(THD *thd, const Warning_info *src_wi)
+ { get_warning_info()->append_warning_info(thd, src_wi); }
+
+ void copy_non_errors_from_wi(THD *thd, const Warning_info *src_wi);
+
+private:
+ Warning_info *get_warning_info() { return m_wi_stack.front(); }
+
+ const Warning_info *get_warning_info() const { return m_wi_stack.front(); }
+
+private:
+ /** True if status information is sent to the client. */
+ bool m_is_sent;
+
+ /** Set to make set_error_status after set_{ok,eof}_status possible. */
+ bool m_can_overwrite_status;
+
+ /** Message buffer. Can be used by OK or ERROR status. */
+ char m_message[MYSQL_ERRMSG_SIZE];
+
+ /**
+ SQL error number. One of ER_ codes from share/errmsg.txt.
+ Set by set_error_status.
+ */
+ uint m_sql_errno;
+
+ char m_sqlstate[SQLSTATE_LENGTH+1];
+
+ /**
+ The number of rows affected by the last statement. This is
+ semantically close to thd->row_count_func, but has a different
+ life cycle. thd->row_count_func stores the value returned by
+ function ROW_COUNT() and is cleared only by statements that
+ update its value, such as INSERT, UPDATE, DELETE and few others.
+ This member is cleared at the beginning of the next statement.
+
+ We could possibly merge the two, but life cycle of thd->row_count_func
+ can not be changed.
+ */
+ ulonglong m_affected_rows;
+
+ /**
+ Similarly to the previous member, this is a replacement of
+ thd->first_successful_insert_id_in_prev_stmt, which is used
+ to implement LAST_INSERT_ID().
+ */
+
+ ulonglong m_last_insert_id;
+ /**
+ Number of warnings of this last statement. May differ from
+ the number of warnings returned by SHOW WARNINGS e.g. in case
+ the statement doesn't clear the warnings, and doesn't generate
+ them.
+ */
+ uint m_statement_warn_count;
+
+ enum_diagnostics_status m_status;
+
+ Warning_info m_main_wi;
+
+ Warning_info_list m_wi_stack;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+
+void push_warning(THD *thd, Sql_condition::enum_warning_level level,
+ uint code, const char *msg);
+
+void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level,
+ uint code, const char *format, ...);
+
+bool mysqld_show_warnings(THD *thd, ulong levels_to_show);
+
+uint32 convert_error_message(char *to, uint32 to_length,
+ CHARSET_INFO *to_cs,
+ const char *from, uint32 from_length,
+ CHARSET_INFO *from_cs, uint *errors);
+
+extern const LEX_STRING warning_level_names[];
+
+bool is_sqlstate_valid(const LEX_STRING *sqlstate);
+/**
+ Checks if the specified SQL-state-string defines COMPLETION condition.
+ This function assumes that the given string contains a valid SQL-state.
+
+ @param s the condition SQLSTATE.
+
+ @retval true if the given string defines COMPLETION condition.
+ @retval false otherwise.
+*/
+inline bool is_sqlstate_completion(const char *s)
+{ return s[0] == '0' && s[1] == '0'; }
+
+
+/**
+ Checks if the specified SQL-state-string defines WARNING condition.
+ This function assumes that the given string contains a valid SQL-state.
+
+ @param s the condition SQLSTATE.
+
+ @retval true if the given string defines WARNING condition.
+ @retval false otherwise.
+*/
+inline bool is_sqlstate_warning(const char *s)
+{ return s[0] == '0' && s[1] == '1'; }
+
+
+/**
+ Checks if the specified SQL-state-string defines NOT FOUND condition.
+ This function assumes that the given string contains a valid SQL-state.
+
+ @param s the condition SQLSTATE.
+
+ @retval true if the given string defines NOT FOUND condition.
+ @retval false otherwise.
+*/
+inline bool is_sqlstate_not_found(const char *s)
+{ return s[0] == '0' && s[1] == '2'; }
+
+
+/**
+ Checks if the specified SQL-state-string defines EXCEPTION condition.
+ This function assumes that the given string contains a valid SQL-state.
+
+ @param s the condition SQLSTATE.
+
+ @retval true if the given string defines EXCEPTION condition.
+ @retval false otherwise.
+*/
+inline bool is_sqlstate_exception(const char *s)
+{ return s[0] != '0' || s[1] > '2'; }
+
+
+#endif // SQL_ERROR_H
diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc
new file mode 100644
index 00000000000..75f6689ab98
--- /dev/null
+++ b/sql/sql_explain.cc
@@ -0,0 +1,950 @@
+/*
+ Copyright (c) 2013 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_select.h"
+
+
+Explain_query::Explain_query(THD *thd_arg) :
+ upd_del_plan(NULL), insert_plan(NULL), thd(thd_arg), apc_enabled(false)
+{
+ operations= 0;
+}
+
+
+Explain_query::~Explain_query()
+{
+ if (apc_enabled)
+ thd->apc_target.disable();
+
+ delete upd_del_plan;
+ delete insert_plan;
+ uint i;
+ for (i= 0 ; i < unions.elements(); i++)
+ delete unions.at(i);
+ for (i= 0 ; i < selects.elements(); i++)
+ delete selects.at(i);
+}
+
+
+Explain_node *Explain_query::get_node(uint select_id)
+{
+ Explain_union *u;
+ if ((u= get_union(select_id)))
+ return u;
+ else
+ return get_select(select_id);
+}
+
+Explain_union *Explain_query::get_union(uint select_id)
+{
+ return (unions.elements() > select_id) ? unions.at(select_id) : NULL;
+}
+
+Explain_select *Explain_query::get_select(uint select_id)
+{
+ return (selects.elements() > select_id) ? selects.at(select_id) : NULL;
+}
+
+
+void Explain_query::add_node(Explain_node *node)
+{
+ uint select_id;
+ operations++;
+ if (node->get_type() == Explain_node::EXPLAIN_UNION)
+ {
+ Explain_union *u= (Explain_union*)node;
+ select_id= u->get_select_id();
+ if (unions.elements() <= select_id)
+ unions.resize(MY_MAX(select_id+1, unions.elements()*2), NULL);
+
+ Explain_union *old_node;
+ if ((old_node= get_union(select_id)))
+ delete old_node;
+
+ unions.at(select_id)= u;
+ }
+ else
+ {
+ Explain_select *sel= (Explain_select*)node;
+ if (sel->select_id == FAKE_SELECT_LEX_ID)
+ {
+ DBUG_ASSERT(0); // this is a "fake select" from a UNION.
+ }
+ else
+ {
+ select_id= sel->select_id;
+ Explain_select *old_node;
+
+ if (selects.elements() <= select_id)
+ selects.resize(MY_MAX(select_id+1, selects.elements()*2), NULL);
+
+ if ((old_node= get_select(select_id)))
+ delete old_node;
+
+ selects.at(select_id)= sel;
+ }
+ }
+}
+
+
+void Explain_query::add_insert_plan(Explain_insert *insert_plan_arg)
+{
+ insert_plan= insert_plan_arg;
+ query_plan_ready();
+}
+
+
+void Explain_query::add_upd_del_plan(Explain_update *upd_del_plan_arg)
+{
+ upd_del_plan= upd_del_plan_arg;
+ query_plan_ready();
+}
+
+
+void Explain_query::query_plan_ready()
+{
+ if (!apc_enabled)
+ thd->apc_target.enable();
+ apc_enabled= true;
+}
+
+/*
+ Send EXPLAIN output to the client.
+*/
+
+int Explain_query::send_explain(THD *thd)
+{
+ select_result *result;
+ LEX *lex= thd->lex;
+
+ if (!(result= new select_send()) ||
+ thd->send_explain_fields(result))
+ return 1;
+
+ int res;
+ if ((res= print_explain(result, lex->describe)))
+ result->abort_result_set();
+ else
+ result->send_eof();
+
+ return res;
+}
+
+
+/*
+ The main entry point to print EXPLAIN of the entire query
+*/
+
+int Explain_query::print_explain(select_result_sink *output,
+ uint8 explain_flags)
+{
+ if (upd_del_plan)
+ {
+ upd_del_plan->print_explain(this, output, explain_flags);
+ return 0;
+ }
+ else if (insert_plan)
+ {
+ insert_plan->print_explain(this, output, explain_flags);
+ return 0;
+ }
+ else
+ {
+ /* Start printing from node with id=1 */
+ Explain_node *node= get_node(1);
+ if (!node)
+ return 1; /* No query plan */
+ return node->print_explain(this, output, explain_flags);
+ }
+}
+
+
+bool print_explain_query(LEX *lex, THD *thd, String *str)
+{
+ return lex->explain->print_explain_str(thd, str);
+}
+
+
+/*
+ Return tabular EXPLAIN output as a text string
+*/
+
+bool Explain_query::print_explain_str(THD *thd, String *out_str)
+{
+ List<Item> fields;
+ thd->make_explain_field_list(fields);
+
+ select_result_text_buffer output_buf(thd);
+ output_buf.send_result_set_metadata(fields, thd->lex->describe);
+ if (print_explain(&output_buf, thd->lex->describe))
+ return true;
+ output_buf.save_to(out_str);
+ return false;
+}
+
+
+static void push_str(List<Item> *item_list, const char *str)
+{
+ item_list->push_back(new Item_string_sys(str));
+}
+
+
+static void push_string(List<Item> *item_list, String *str)
+{
+ item_list->push_back(new Item_string_sys(str->ptr(), str->length()));
+}
+
+
+int Explain_union::print_explain(Explain_query *query,
+ select_result_sink *output,
+ uint8 explain_flags)
+{
+ char table_name_buffer[SAFE_NAME_LEN];
+
+ /* print all UNION children, in order */
+ for (int i= 0; i < (int) union_members.elements(); i++)
+ {
+ Explain_select *sel= query->get_select(union_members.at(i));
+ sel->print_explain(query, output, explain_flags);
+ }
+
+ /* Print a line with "UNION RESULT" */
+ List<Item> item_list;
+ Item *item_null= new Item_null();
+
+ /* `id` column */
+ item_list.push_back(item_null);
+
+ /* `select_type` column */
+ push_str(&item_list, fake_select_type);
+
+ /* `table` column: something like "<union1,2>" */
+ {
+ uint childno= 0;
+ uint len= 6, lastop= 0;
+ memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
+
+ for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN;
+ childno++)
+ {
+ len+= lastop;
+ lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len,
+ "%u,", union_members.at(childno));
+ }
+
+ if (childno < union_members.elements() || len + lastop >= NAME_LEN)
+ {
+ memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1);
+ len+= 4;
+ }
+ else
+ {
+ len+= lastop;
+ table_name_buffer[len - 1]= '>'; // change ',' to '>'
+ }
+ item_list.push_back(new Item_string_sys(table_name_buffer, len));
+ }
+
+ /* `partitions` column */
+ if (explain_flags & DESCRIBE_PARTITIONS)
+ item_list.push_back(item_null);
+
+ /* `type` column */
+ push_str(&item_list, join_type_str[JT_ALL]);
+
+ /* `possible_keys` column */
+ item_list.push_back(item_null);
+
+ /* `key` */
+ item_list.push_back(item_null);
+
+ /* `key_len` */
+ item_list.push_back(item_null);
+
+ /* `ref` */
+ item_list.push_back(item_null);
+
+ /* `rows` */
+ item_list.push_back(item_null);
+
+ /* `filtered` */
+ if (explain_flags & DESCRIBE_EXTENDED)
+ item_list.push_back(item_null);
+
+ /* `Extra` */
+ StringBuffer<256> extra_buf;
+ if (using_filesort)
+ {
+ extra_buf.append(STRING_WITH_LEN("Using filesort"));
+ }
+ item_list.push_back(new Item_string_sys(extra_buf.ptr(), extra_buf.length()));
+
+ //output->unit.offset_limit_cnt= 0;
+ if (output->send_data(item_list))
+ return 1;
+
+ /*
+ Print all subquery children (UNION children have already been printed at
+ the start of this function)
+ */
+ return print_explain_for_children(query, output, explain_flags);
+}
+
+
+/*
+ Print EXPLAINs for all children nodes (i.e. for subqueries)
+*/
+
+int Explain_node::print_explain_for_children(Explain_query *query,
+ select_result_sink *output,
+ uint8 explain_flags)
+{
+ for (int i= 0; i < (int) children.elements(); i++)
+ {
+ Explain_node *node= query->get_node(children.at(i));
+ if (node->print_explain(query, output, explain_flags))
+ return 1;
+ }
+ return 0;
+}
+
+
+Explain_select::~Explain_select()
+{
+ if (join_tabs)
+ {
+ for (uint i= 0; i< n_join_tabs; i++)
+ delete join_tabs[i];
+ my_free(join_tabs);
+ }
+}
+
+
+int Explain_select::print_explain(Explain_query *query,
+ select_result_sink *output,
+ uint8 explain_flags)
+{
+ if (message)
+ {
+ List<Item> item_list;
+ Item *item_null= new Item_null();
+
+ item_list.push_back(new Item_int((int32) select_id));
+ item_list.push_back(new Item_string_sys(select_type));
+ for (uint i=0 ; i < 7; i++)
+ item_list.push_back(item_null);
+ if (explain_flags & DESCRIBE_PARTITIONS)
+ item_list.push_back(item_null);
+ if (explain_flags & DESCRIBE_EXTENDED)
+ item_list.push_back(item_null);
+
+ item_list.push_back(new Item_string_sys(message));
+
+ if (output->send_data(item_list))
+ return 1;
+ }
+ else
+ {
+ bool using_tmp= using_temporary;
+ bool using_fs= using_filesort;
+ for (uint i=0; i< n_join_tabs; i++)
+ {
+ join_tabs[i]->print_explain(output, explain_flags, select_id,
+ select_type, using_tmp, using_fs);
+ if (i == 0)
+ {
+ /*
+ "Using temporary; Using filesort" should only be shown near the 1st
+ table
+ */
+ using_tmp= false;
+ using_fs= false;
+ }
+ }
+ }
+
+ return print_explain_for_children(query, output, explain_flags);
+}
+
+
+void Explain_table_access::push_extra(enum explain_extra_tag extra_tag)
+{
+ extra_tags.append(extra_tag);
+}
+
+
+int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags,
+ uint select_id, const char *select_type,
+ bool using_temporary, bool using_filesort)
+{
+ const CHARSET_INFO *cs= system_charset_info;
+ const char *hash_key_prefix= "#hash#";
+ bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
+ type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
+
+ List<Item> item_list;
+ Item *item_null= new Item_null();
+
+ if (sjm_nest_select_id)
+ select_id= sjm_nest_select_id;
+
+ /* `id` column */
+ item_list.push_back(new Item_int((int32) select_id));
+
+ /* `select_type` column */
+ if (sjm_nest_select_id)
+ push_str(&item_list, "MATERIALIZED");
+ else
+ push_str(&item_list, select_type);
+
+ /* `table` column */
+ push_string(&item_list, &table_name);
+
+ /* `partitions` column */
+ if (explain_flags & DESCRIBE_PARTITIONS)
+ {
+ if (used_partitions_set)
+ {
+ push_string(&item_list, &used_partitions);
+ }
+ else
+ item_list.push_back(item_null);
+ }
+
+ /* `type` column */
+ push_str(&item_list, join_type_str[type]);
+
+ /* `possible_keys` column */
+ if (possible_keys_str.length() > 0)
+ push_string(&item_list, &possible_keys_str);
+ else
+ item_list.push_back(item_null);
+
+ /* `key` */
+ StringBuffer<64> key_str;
+ if (key.get_key_name())
+ {
+ if (is_hj)
+ key_str.append(hash_key_prefix, strlen(hash_key_prefix), cs);
+
+ key_str.append(key.get_key_name());
+
+ if (is_hj && type != JT_HASH)
+ key_str.append(':');
+ }
+
+ if (quick_info)
+ {
+ StringBuffer<64> buf2;
+ quick_info->print_key(&buf2);
+ key_str.append(buf2);
+ }
+ if (type == JT_HASH_NEXT)
+ key_str.append(hash_next_key.get_key_name());
+
+ if (key_str.length() > 0)
+ push_string(&item_list, &key_str);
+ else
+ item_list.push_back(item_null);
+
+ /* `key_len` */
+ StringBuffer<64> key_len_str;
+
+ if (key.get_key_len() != (uint)-1)
+ {
+ char buf[64];
+ size_t length;
+ length= longlong10_to_str(key.get_key_len(), buf, 10) - buf;
+ key_len_str.append(buf, length);
+ if (is_hj && type != JT_HASH)
+ key_len_str.append(':');
+ }
+
+ if (quick_info)
+ {
+ StringBuffer<64> buf2;
+ quick_info->print_key_len(&buf2);
+ key_len_str.append(buf2);
+ }
+
+ if (type == JT_HASH_NEXT)
+ {
+ char buf[64];
+ size_t length;
+ length= longlong10_to_str(hash_next_key.get_key_len(), buf, 10) - buf;
+ key_len_str.append(buf, length);
+ }
+
+ if (key_len_str.length() > 0)
+ push_string(&item_list, &key_len_str);
+ else
+ item_list.push_back(item_null);
+
+ /* `ref` */
+ if (ref_set)
+ push_string(&item_list, &ref);
+ else
+ item_list.push_back(item_null);
+
+ /* `rows` */
+ if (rows_set)
+ {
+ item_list.push_back(new Item_int((longlong) (ulonglong) rows,
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ }
+ else
+ item_list.push_back(item_null);
+
+ /* `filtered` */
+ if (explain_flags & DESCRIBE_EXTENDED)
+ {
+ if (filtered_set)
+ {
+ item_list.push_back(new Item_float(filtered, 2));
+ }
+ else
+ item_list.push_back(item_null);
+ }
+
+ /* `Extra` */
+ StringBuffer<256> extra_buf;
+ bool first= true;
+ for (int i=0; i < (int)extra_tags.elements(); i++)
+ {
+ if (first)
+ first= false;
+ else
+ extra_buf.append(STRING_WITH_LEN("; "));
+ append_tag_name(&extra_buf, extra_tags.at(i));
+ }
+
+ if (using_temporary)
+ {
+ if (first)
+ first= false;
+ else
+ extra_buf.append(STRING_WITH_LEN("; "));
+ extra_buf.append(STRING_WITH_LEN("Using temporary"));
+ }
+
+ if (using_filesort)
+ {
+ if (first)
+ first= false;
+ else
+ extra_buf.append(STRING_WITH_LEN("; "));
+ extra_buf.append(STRING_WITH_LEN("Using filesort"));
+ }
+
+ item_list.push_back(new Item_string_sys(extra_buf.ptr(), extra_buf.length()));
+
+ if (output->send_data(item_list))
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ Elements in this array match members of enum Extra_tag, defined in
+ sql_explain.h
+*/
+
+const char * extra_tag_text[]=
+{
+ "ET_none",
+ "Using index condition",
+ "Using index condition(BKA)",
+ "Using ", // special handling
+ "Range checked for each record (index map: 0x", // special handling
+ "Using where with pushed condition",
+ "Using where",
+ "Not exists",
+
+ "Using index",
+ "Full scan on NULL key",
+ "Skip_open_table",
+ "Open_frm_only",
+ "Open_full_table",
+
+ "Scanned 0 databases",
+ "Scanned 1 database",
+ "Scanned all databases",
+
+ "Using index for group-by", // special handling
+
+ "USING MRR: DONT PRINT ME", // special handling
+
+ "Distinct",
+ "LooseScan",
+ "Start temporary",
+ "End temporary",
+ "FirstMatch", // special handling
+
+ "Using join buffer", // special handling
+
+ "const row not found",
+ "unique row not found",
+ "Impossible ON condition"
+};
+
+
+void Explain_table_access::append_tag_name(String *str, enum explain_extra_tag tag)
+{
+ switch (tag) {
+ case ET_USING:
+ {
+ // quick select
+ str->append(STRING_WITH_LEN("Using "));
+ quick_info->print_extra(str);
+ break;
+ }
+ case ET_RANGE_CHECKED_FOR_EACH_RECORD:
+ {
+ /* 4 bits per 1 hex digit + terminating '\0' */
+ char buf[MAX_KEY / 4 + 1];
+ str->append(STRING_WITH_LEN("Range checked for each "
+ "record (index map: 0x"));
+ str->append(range_checked_map.print(buf));
+ str->append(')');
+ break;
+ }
+ case ET_USING_MRR:
+ {
+ str->append(mrr_type);
+ break;
+ }
+ case ET_USING_JOIN_BUFFER:
+ {
+ str->append(extra_tag_text[tag]);
+
+ str->append(STRING_WITH_LEN(" ("));
+ const char *buffer_type= bka_type.incremental ? "incremental" : "flat";
+ str->append(buffer_type);
+ str->append(STRING_WITH_LEN(", "));
+ str->append(bka_type.join_alg);
+ str->append(STRING_WITH_LEN(" join"));
+ str->append(STRING_WITH_LEN(")"));
+ if (bka_type.mrr_type.length())
+ str->append(bka_type.mrr_type);
+
+ break;
+ }
+ case ET_FIRST_MATCH:
+ {
+ if (firstmatch_table_name.length())
+ {
+ str->append("FirstMatch(");
+ str->append(firstmatch_table_name);
+ str->append(")");
+ }
+ else
+ str->append(extra_tag_text[tag]);
+ break;
+ }
+ case ET_USING_INDEX_FOR_GROUP_BY:
+ {
+ str->append(extra_tag_text[tag]);
+ if (loose_scan_is_scanning)
+ str->append(" (scanning)");
+ break;
+ }
+ default:
+ str->append(extra_tag_text[tag]);
+ }
+}
+
+
+/*
+ This is called for top-level Explain_quick_select only. The point of this
+ function is:
+ - index_merge should print $index_merge_type (child, ...)
+ - 'range' should not print anything.
+*/
+
+void Explain_quick_select::print_extra(String *str)
+{
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
+ {
+ /* print nothing */
+ }
+ else
+ print_extra_recursive(str);
+}
+
+
+void Explain_quick_select::print_extra_recursive(String *str)
+{
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC)
+ {
+ str->append(range.get_key_name());
+ }
+ else
+ {
+ str->append(get_name_by_type());
+ str->append('(');
+ List_iterator_fast<Explain_quick_select> it (children);
+ Explain_quick_select* child;
+ bool first= true;
+ while ((child = it++))
+ {
+ if (first)
+ first= false;
+ else
+ str->append(',');
+
+ child->print_extra_recursive(str);
+ }
+ str->append(')');
+ }
+}
+
+
+const char * Explain_quick_select::get_name_by_type()
+{
+ switch (quick_type) {
+ case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE:
+ return "sort_union";
+ case QUICK_SELECT_I::QS_TYPE_ROR_UNION:
+ return "union";
+ case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT:
+ return "intersect";
+ case QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT:
+ return "sort_intersect";
+ default:
+ DBUG_ASSERT(0);
+ return "unknown quick select type";
+ }
+}
+
+
+/*
+ This prints a comma-separated list of used indexes, ignoring nesting
+*/
+
+void Explain_quick_select::print_key(String *str)
+{
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
+ {
+ if (str->length() > 0)
+ str->append(',');
+ str->append(range.get_key_name());
+ }
+ else
+ {
+ List_iterator_fast<Explain_quick_select> it (children);
+ Explain_quick_select* child;
+ while ((child = it++))
+ {
+ child->print_key(str);
+ }
+ }
+}
+
+
+/*
+ This prints a comma-separated list of used key_lengths, ignoring nesting
+*/
+
+void Explain_quick_select::print_key_len(String *str)
+{
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
+ {
+ char buf[64];
+ size_t length;
+ length= longlong10_to_str(range.get_key_len(), buf, 10) - buf;
+ if (str->length() > 0)
+ str->append(',');
+ str->append(buf, length);
+ }
+ else
+ {
+ List_iterator_fast<Explain_quick_select> it (children);
+ Explain_quick_select* child;
+ while ((child = it++))
+ {
+ child->print_key_len(str);
+ }
+ }
+}
+
+
+int Explain_delete::print_explain(Explain_query *query,
+ select_result_sink *output,
+ uint8 explain_flags)
+{
+ if (deleting_all_rows)
+ {
+ const char *msg= "Deleting all rows";
+ int res= print_explain_message_line(output, explain_flags,
+ 1 /*select number*/,
+ select_type, &rows, msg);
+ return res;
+
+ }
+ else
+ {
+ return Explain_update::print_explain(query, output, explain_flags);
+ }
+}
+
+
+int Explain_update::print_explain(Explain_query *query,
+ select_result_sink *output,
+ uint8 explain_flags)
+{
+ StringBuffer<64> key_buf;
+ StringBuffer<64> key_len_buf;
+ StringBuffer<64> extra_str;
+ if (impossible_where || no_partitions)
+ {
+ const char *msg= impossible_where ?
+ "Impossible WHERE" :
+ "No matching rows after partition pruning";
+ int res= print_explain_message_line(output, explain_flags,
+ 1 /*select number*/,
+ select_type,
+ NULL, /* rows */
+ msg);
+ return res;
+ }
+
+
+ if (quick_info)
+ {
+ quick_info->print_key(&key_buf);
+ quick_info->print_key_len(&key_len_buf);
+
+ StringBuffer<64> quick_buf;
+ quick_info->print_extra(&quick_buf);
+ if (quick_buf.length())
+ {
+ extra_str.append(STRING_WITH_LEN("Using "));
+ extra_str.append(quick_buf);
+ }
+ }
+ else
+ {
+ key_buf.copy(key_str);
+ key_len_buf.copy(key_len_str);
+ }
+
+ if (using_where)
+ {
+ if (extra_str.length() !=0)
+ extra_str.append(STRING_WITH_LEN("; "));
+ extra_str.append(STRING_WITH_LEN("Using where"));
+ }
+
+ if (mrr_type.length() != 0)
+ {
+ if (extra_str.length() !=0)
+ extra_str.append(STRING_WITH_LEN("; "));
+ extra_str.append(mrr_type);
+ }
+
+ if (using_filesort)
+ {
+ if (extra_str.length() !=0)
+ extra_str.append(STRING_WITH_LEN("; "));
+ extra_str.append(STRING_WITH_LEN("Using filesort"));
+ }
+
+ if (using_io_buffer)
+ {
+ if (extra_str.length() !=0)
+ extra_str.append(STRING_WITH_LEN("; "));
+ extra_str.append(STRING_WITH_LEN("Using buffer"));
+ }
+
+ /*
+ Single-table DELETE commands do not do "Using temporary".
+ "Using index condition" is also not possible (which is an unjustified limitation)
+ */
+
+ print_explain_row(output, explain_flags,
+ 1, /* id */
+ select_type,
+ table_name.c_ptr(),
+ used_partitions_set? used_partitions.c_ptr() : NULL,
+ jtype,
+ possible_keys_line.length()? possible_keys_line.c_ptr(): NULL,
+ key_buf.length()? key_buf.c_ptr() : NULL,
+ key_len_buf.length() ? key_len_buf.c_ptr() : NULL,
+ NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */
+ &rows,
+ extra_str.c_ptr_safe());
+
+ return print_explain_for_children(query, output, explain_flags);
+}
+
+
+int Explain_insert::print_explain(Explain_query *query,
+ select_result_sink *output,
+ uint8 explain_flags)
+{
+ const char *select_type="INSERT";
+ print_explain_row(output, explain_flags,
+ 1, /* id */
+ select_type,
+ table_name.c_ptr(),
+ NULL, // partitions
+ JT_ALL,
+ NULL, // possible_keys
+ NULL, // key
+ NULL, // key_len
+ NULL, // ref
+ NULL, // rows
+ NULL);
+
+ return print_explain_for_children(query, output, explain_flags);
+}
+
+
+void delete_explain_query(LEX *lex)
+{
+ delete lex->explain;
+ lex->explain= NULL;
+}
+
+
+void create_explain_query(LEX *lex, MEM_ROOT *mem_root)
+{
+ DBUG_ASSERT(!lex->explain);
+ lex->explain= new Explain_query(lex->thd);
+ DBUG_ASSERT(mem_root == current_thd->mem_root);
+ lex->explain->mem_root= mem_root;
+}
+
+void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root)
+{
+ if (!lex->explain)
+ create_explain_query(lex, mem_root);
+}
+
diff --git a/sql/sql_explain.h b/sql/sql_explain.h
new file mode 100644
index 00000000000..b9f381b867b
--- /dev/null
+++ b/sql/sql_explain.h
@@ -0,0 +1,550 @@
+/*
+ Copyright (c) 2013 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+/**************************************************************************************
+
+ Data structures for producing EXPLAIN outputs.
+
+ These structures
+ - Can be produced inexpensively from query plan.
+ - Store sufficient information to produce tabular EXPLAIN output (the goal is
+ to be able to produce JSON also)
+
+*************************************************************************************/
+
+
+const int FAKE_SELECT_LEX_ID= (int)UINT_MAX;
+
+class Explain_query;
+
+/*
+ A node can be either a SELECT, or a UNION.
+*/
+class Explain_node : public Sql_alloc
+{
+public:
+ enum explain_node_type
+ {
+ EXPLAIN_UNION,
+ EXPLAIN_SELECT,
+ EXPLAIN_UPDATE,
+ EXPLAIN_DELETE,
+ EXPLAIN_INSERT
+ };
+
+ virtual enum explain_node_type get_type()= 0;
+ virtual int get_select_id()= 0;
+
+ /*
+ A node may have children nodes. When a node's explain structure is
+ created, children nodes may not yet have QPFs. This is why we store ids.
+ */
+ Dynamic_array<int> children;
+ void add_child(int select_no)
+ {
+ children.append(select_no);
+ }
+
+ virtual int print_explain(Explain_query *query, select_result_sink *output,
+ uint8 explain_flags)=0;
+
+ int print_explain_for_children(Explain_query *query, select_result_sink *output,
+ uint8 explain_flags);
+ virtual ~Explain_node(){}
+};
+
+
+class Explain_table_access;
+
+
+/*
+ EXPLAIN structure for a SELECT.
+
+ A select can be:
+ 1. A degenerate case. In this case, message!=NULL, and it contains a
+ description of what kind of degenerate case it is (e.g. "Impossible
+ WHERE").
+ 2. a non-degenrate join. In this case, join_tabs describes the join.
+
+ In the non-degenerate case, a SELECT may have a GROUP BY/ORDER BY operation.
+
+ In both cases, the select may have children nodes. class Explain_node provides
+ a way get node's children.
+*/
+
+class Explain_select : public Explain_node
+{
+public:
+ enum explain_node_type get_type() { return EXPLAIN_SELECT; }
+
+ Explain_select() :
+ message(NULL), join_tabs(NULL),
+ using_temporary(false), using_filesort(false)
+ {}
+
+ ~Explain_select();
+
+ bool add_table(Explain_table_access *tab)
+ {
+ if (!join_tabs)
+ {
+ join_tabs= (Explain_table_access**) my_malloc(sizeof(Explain_table_access*) *
+ MAX_TABLES, MYF(0));
+ n_join_tabs= 0;
+ }
+ join_tabs[n_join_tabs++]= tab;
+ return false;
+ }
+
+public:
+ int select_id;
+ const char *select_type;
+
+ int get_select_id() { return select_id; }
+
+ /*
+ If message != NULL, this is a degenerate join plan, and all subsequent
+ members have no info
+ */
+ const char *message;
+
+ /*
+ A flat array of Explain structs for tables. The order is "just like EXPLAIN
+ would print them".
+ */
+ Explain_table_access** join_tabs;
+ uint n_join_tabs;
+
+ /* Global join attributes. In tabular form, they are printed on the first row */
+ bool using_temporary;
+ bool using_filesort;
+
+ int print_explain(Explain_query *query, select_result_sink *output,
+ uint8 explain_flags);
+};
+
+
+/*
+ Explain structure for a UNION.
+
+ A UNION may or may not have "Using filesort".
+*/
+
+class Explain_union : public Explain_node
+{
+public:
+ enum explain_node_type get_type() { return EXPLAIN_UNION; }
+
+ int get_select_id()
+ {
+ DBUG_ASSERT(union_members.elements() > 0);
+ return union_members.at(0);
+ }
+ /*
+ Members of the UNION. Note: these are different from UNION's "children".
+ Example:
+
+ (select * from t1) union
+ (select * from t2) order by (select col1 from t3 ...)
+
+ here
+ - select-from-t1 and select-from-t2 are "union members",
+ - select-from-t3 is the only "child".
+ */
+ Dynamic_array<int> union_members;
+
+ void add_select(int select_no)
+ {
+ union_members.append(select_no);
+ }
+ int print_explain(Explain_query *query, select_result_sink *output,
+ uint8 explain_flags);
+
+ const char *fake_select_type;
+ bool using_filesort;
+};
+
+
+class Explain_update;
+class Explain_delete;
+class Explain_insert;
+
+/*
+ Explain structure for a query (i.e. a statement).
+
+ This should be able to survive when the query plan was deleted. Currently,
+ we do not intend for it survive until after query's MEM_ROOT is freed. It
+ does surivive freeing of query's items.
+
+ For reference, the process of post-query cleanup is as follows:
+
+ >dispatch_command
+ | >mysql_parse
+ | | ...
+ | | lex_end()
+ | | ...
+ | | >THD::cleanup_after_query
+ | | | ...
+ | | | free_items()
+ | | | ...
+ | | <THD::cleanup_after_query
+ | |
+ | <mysql_parse
+ |
+ | log_slow_statement()
+ |
+ | free_root()
+ |
+ >dispatch_command
+
+ That is, the order of actions is:
+ - free query's Items
+ - write to slow query log
+ - free query's MEM_ROOT
+
+*/
+
+class Explain_query : public Sql_alloc
+{
+public:
+ Explain_query(THD *thd);
+ ~Explain_query();
+
+ /* Add a new node */
+ void add_node(Explain_node *node);
+ void add_insert_plan(Explain_insert *insert_plan_arg);
+ void add_upd_del_plan(Explain_update *upd_del_plan_arg);
+
+ /* This will return a select, or a union */
+ Explain_node *get_node(uint select_id);
+
+ /* This will return a select (even if there is a union with this id) */
+ Explain_select *get_select(uint select_id);
+
+ Explain_union *get_union(uint select_id);
+
+ /* Produce a tabular EXPLAIN output */
+ int print_explain(select_result_sink *output, uint8 explain_flags);
+
+ /* Send tabular EXPLAIN to the client */
+ int send_explain(THD *thd);
+
+ /* Return tabular EXPLAIN output as a text string */
+ bool print_explain_str(THD *thd, String *out_str);
+
+ /* If true, at least part of EXPLAIN can be printed */
+ bool have_query_plan() { return insert_plan || upd_del_plan|| get_node(1) != NULL; }
+
+ void query_plan_ready();
+
+ MEM_ROOT *mem_root;
+private:
+ /* Explain_delete inherits from Explain_update */
+ Explain_update *upd_del_plan;
+
+ /* Query "plan" for INSERTs */
+ Explain_insert *insert_plan;
+
+ Dynamic_array<Explain_union*> unions;
+ Dynamic_array<Explain_select*> selects;
+
+ THD *thd; // for APC start/stop
+ bool apc_enabled;
+ /*
+ Debugging aid: count how many times add_node() was called. Ideally, it
+ should be one, we currently allow O(1) query plan saves for each
+ select or union. The goal is not to have O(#rows_in_some_table), which
+ is unacceptable.
+ */
+ longlong operations;
+};
+
+
+/*
+ Some of the tags have matching text. See extra_tag_text for text names, and
+ Explain_table_access::append_tag_name() for code to convert from tag form to text
+ form.
+*/
+enum explain_extra_tag
+{
+ ET_none= 0, /* not-a-tag */
+ ET_USING_INDEX_CONDITION,
+ ET_USING_INDEX_CONDITION_BKA,
+ ET_USING, /* For quick selects of various kinds */
+ ET_RANGE_CHECKED_FOR_EACH_RECORD,
+ ET_USING_WHERE_WITH_PUSHED_CONDITION,
+ ET_USING_WHERE,
+ ET_NOT_EXISTS,
+
+ ET_USING_INDEX,
+ ET_FULL_SCAN_ON_NULL_KEY,
+ ET_SKIP_OPEN_TABLE,
+ ET_OPEN_FRM_ONLY,
+ ET_OPEN_FULL_TABLE,
+
+ ET_SCANNED_0_DATABASES,
+ ET_SCANNED_1_DATABASE,
+ ET_SCANNED_ALL_DATABASES,
+
+ ET_USING_INDEX_FOR_GROUP_BY,
+
+ ET_USING_MRR, // does not print "Using mrr".
+
+ ET_DISTINCT,
+ ET_LOOSESCAN,
+ ET_START_TEMPORARY,
+ ET_END_TEMPORARY,
+ ET_FIRST_MATCH,
+
+ ET_USING_JOIN_BUFFER,
+
+ ET_CONST_ROW_NOT_FOUND,
+ ET_UNIQUE_ROW_NOT_FOUND,
+ ET_IMPOSSIBLE_ON_CONDITION,
+
+ ET_total
+};
+
+
+typedef struct st_explain_bka_type
+{
+ bool incremental;
+ const char *join_alg;
+ StringBuffer<64> mrr_type;
+
+} EXPLAIN_BKA_TYPE;
+
+
+/*
+ Data about how an index is used by some access method
+*/
+class Explain_index_use : public Sql_alloc
+{
+ char *key_name;
+ uint key_len;
+ /* will add #keyparts here if we implement EXPLAIN FORMAT=JSON */
+public:
+
+ void set(MEM_ROOT *root, const char *key_name_arg, uint key_len_arg)
+ {
+ if (key_name_arg)
+ {
+ size_t name_len= strlen(key_name_arg);
+ if ((key_name= (char*)alloc_root(root, name_len+1)))
+ memcpy(key_name, key_name_arg, name_len+1);
+ }
+ else
+ key_name= NULL;
+ key_len= key_len_arg;
+ }
+
+ inline const char *get_key_name() { return key_name; }
+ inline uint get_key_len() { return key_len; }
+};
+
+
+/*
+ QPF for quick range selects, as well as index_merge select
+*/
+class Explain_quick_select : public Sql_alloc
+{
+public:
+ Explain_quick_select(int quick_type_arg) : quick_type(quick_type_arg)
+ {}
+
+ const int quick_type;
+
+ /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */
+ Explain_index_use range;
+
+ /* Used in all other cases */
+ List<Explain_quick_select> children;
+
+ void print_extra(String *str);
+ void print_key(String *str);
+ void print_key_len(String *str);
+private:
+ void print_extra_recursive(String *str);
+ const char *get_name_by_type();
+};
+
+
+/*
+ EXPLAIN data structure for a single JOIN_TAB.
+*/
+class Explain_table_access : public Sql_alloc
+{
+public:
+ void push_extra(enum explain_extra_tag extra_tag);
+
+ /* Internals */
+public:
+ /*
+ 0 means this tab is not inside SJM nest and should use Explain_select's id
+ other value means the tab is inside an SJM nest.
+ */
+ int sjm_nest_select_id;
+
+ /* id and 'select_type' are cared-of by the parent Explain_select */
+ StringBuffer<32> table_name;
+
+ enum join_type type;
+
+ StringBuffer<32> used_partitions;
+ bool used_partitions_set;
+
+ /* Empty string means "NULL" will be printed */
+ StringBuffer<32> possible_keys_str;
+
+ /*
+ Index use: key name and length.
+ Note: that when one is accessing I_S tables, those may show use of
+ non-existant indexes.
+
+ key.key_name == NULL means 'NULL' will be shown in tabular output.
+ key.key_len == (uint)-1 means 'NULL' will be shown in tabular output.
+ */
+ Explain_index_use key;
+
+ /*
+ when type==JT_HASH_NEXT, 'key' stores the hash join pseudo-key.
+ hash_next_key stores the table's key.
+ */
+ Explain_index_use hash_next_key;
+
+ bool ref_set; /* not set means 'NULL' should be printed */
+ StringBuffer<32> ref;
+
+ bool rows_set; /* not set means 'NULL' should be printed */
+ ha_rows rows;
+
+ bool filtered_set; /* not set means 'NULL' should be printed */
+ double filtered;
+
+ /*
+ Contents of the 'Extra' column. Some are converted into strings, some have
+ parameters, values for which are stored below.
+ */
+ Dynamic_array<enum explain_extra_tag> extra_tags;
+
+ // Valid if ET_USING tag is present
+ Explain_quick_select *quick_info;
+
+ // Valid if ET_USING_INDEX_FOR_GROUP_BY is present
+ bool loose_scan_is_scanning;
+
+ // valid with ET_RANGE_CHECKED_FOR_EACH_RECORD
+ key_map range_checked_map;
+
+ // valid with ET_USING_MRR
+ StringBuffer<32> mrr_type;
+
+ // valid with ET_USING_JOIN_BUFFER
+ EXPLAIN_BKA_TYPE bka_type;
+
+ StringBuffer<32> firstmatch_table_name;
+
+ int print_explain(select_result_sink *output, uint8 explain_flags,
+ uint select_id, const char *select_type,
+ bool using_temporary, bool using_filesort);
+private:
+ void append_tag_name(String *str, enum explain_extra_tag tag);
+};
+
+
+/*
+ EXPLAIN structure for single-table UPDATE.
+
+ This is similar to Explain_table_access, except that it is more restrictive.
+ Also, it can have UPDATE operation options, but currently there aren't any.
+*/
+
+class Explain_update : public Explain_node
+{
+public:
+ virtual enum explain_node_type get_type() { return EXPLAIN_UPDATE; }
+ virtual int get_select_id() { return 1; /* always root */ }
+
+ const char *select_type;
+
+ StringBuffer<32> used_partitions;
+ bool used_partitions_set;
+
+ bool impossible_where;
+ bool no_partitions;
+ StringBuffer<64> table_name;
+
+ enum join_type jtype;
+ StringBuffer<128> possible_keys_line;
+ StringBuffer<128> key_str;
+ StringBuffer<128> key_len_str;
+ StringBuffer<64> mrr_type;
+
+ Explain_quick_select *quick_info;
+
+ bool using_where;
+ ha_rows rows;
+
+ bool using_filesort;
+ bool using_io_buffer;
+
+ virtual int print_explain(Explain_query *query, select_result_sink *output,
+ uint8 explain_flags);
+};
+
+
+/*
+ EXPLAIN data structure for an INSERT.
+
+ At the moment this doesn't do much as we don't really have any query plans
+ for INSERT statements.
+*/
+
+class Explain_insert : public Explain_node
+{
+public:
+ StringBuffer<64> table_name;
+
+ enum explain_node_type get_type() { return EXPLAIN_INSERT; }
+ int get_select_id() { return 1; /* always root */ }
+
+ int print_explain(Explain_query *query, select_result_sink *output,
+ uint8 explain_flags);
+};
+
+
+/*
+ EXPLAIN data of a single-table DELETE.
+*/
+
+class Explain_delete: public Explain_update
+{
+public:
+ /*
+ TRUE means we're going to call handler->delete_all_rows() and not read any
+ rows.
+ */
+ bool deleting_all_rows;
+
+ virtual enum explain_node_type get_type() { return EXPLAIN_DELETE; }
+ virtual int get_select_id() { return 1; /* always root */ }
+
+ virtual int print_explain(Explain_query *query, select_result_sink *output,
+ uint8 explain_flags);
+};
+
+
diff --git a/sql/sql_expression_cache.cc b/sql/sql_expression_cache.cc
new file mode 100644
index 00000000000..824d21eea20
--- /dev/null
+++ b/sql/sql_expression_cache.cc
@@ -0,0 +1,325 @@
+/* Copyright (C) 2010-2011 Monty Program Ab & Oleksandr Byelkin
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <my_global.h>
+#include "sql_base.h"
+#include "sql_select.h"
+#include "sql_expression_cache.h"
+
+/**
+ Minimum hit ration to proceed on disk if in memory table overflowed.
+ hit_rate = hit / (miss + hit);
+*/
+#define EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE 0.7
+/**
+ Minimum hit ratio to keep in memory table (do not switch cache off)
+ hit_rate = hit / (miss + hit);
+*/
+#define EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE 0.2
+/**
+ Number of cache miss to check hit ratio (maximum cache performance
+ impact in the case when the cache is not applicable)
+*/
+#define EXPCACHE_CHECK_HIT_RATIO_AFTER 200
+
+/*
+ Expression cache is used only for caching subqueries now, so its statistic
+ variables we call subquery_cache*.
+*/
+ulong subquery_cache_miss, subquery_cache_hit;
+
+Expression_cache_tmptable::Expression_cache_tmptable(THD *thd,
+ List<Item> &dependants,
+ Item *value)
+ :cache_table(NULL), table_thd(thd), items(dependants), val(value),
+ hit(0), miss(0), inited (0)
+{
+ DBUG_ENTER("Expression_cache_tmptable::Expression_cache_tmptable");
+ DBUG_VOID_RETURN;
+};
+
+
+/**
+ Disable cache
+*/
+
+void Expression_cache_tmptable::disable_cache()
+{
+ if (cache_table->file->inited)
+ cache_table->file->ha_index_end();
+ free_tmp_table(table_thd, cache_table);
+ cache_table= NULL;
+}
+
+
+/**
+ Field enumerator for TABLE::add_tmp_key
+
+ @param arg reference variable with current field number
+
+ @return field number
+*/
+
+static uint field_enumerator(uchar *arg)
+{
+ return ((uint*)arg)[0]++;
+}
+
+
+/**
+ Initialize temporary table and auxiliary structures for the expression
+ cache
+
+ @details
+ The function creates a temporary table for the expression cache, defines
+ the search index and initializes auxiliary search structures used to check
+ whether a given set of of values of the expression parameters is in some
+ cache entry.
+*/
+
+void Expression_cache_tmptable::init()
+{
+ List_iterator<Item> li(items);
+ Item_iterator_list it(li);
+ uint field_counter;
+ DBUG_ENTER("Expression_cache_tmptable::init");
+ DBUG_ASSERT(!inited);
+ inited= TRUE;
+ cache_table= NULL;
+
+ if (items.elements == 0)
+ {
+ DBUG_PRINT("info", ("All parameters were removed by optimizer."));
+ DBUG_VOID_RETURN;
+ }
+
+ /* add result field */
+ items.push_front(val);
+
+ cache_table_param.init();
+ /* dependent items and result */
+ cache_table_param.field_count= items.elements;
+ /* postpone table creation to index description */
+ cache_table_param.skip_create_table= 1;
+
+ if (!(cache_table= create_tmp_table(table_thd, &cache_table_param,
+ items, (ORDER*) NULL,
+ FALSE, TRUE,
+ ((table_thd->variables.option_bits |
+ TMP_TABLE_ALL_COLUMNS) &
+ ~TMP_TABLE_FORCE_MYISAM),
+ HA_POS_ERROR,
+ (char *)"subquery-cache-table",
+ TRUE)))
+ {
+ DBUG_PRINT("error", ("create_tmp_table failed, caching switched off"));
+ DBUG_VOID_RETURN;
+ }
+
+ if (cache_table->s->db_type() != heap_hton)
+ {
+ DBUG_PRINT("error", ("we need only heap table"));
+ goto error;
+ }
+
+ field_counter= 1;
+
+ if (cache_table->alloc_keys(1) ||
+ cache_table->add_tmp_key(0, items.elements - 1, &field_enumerator,
+ (uchar*)&field_counter, TRUE) ||
+ ref.tmp_table_index_lookup_init(table_thd, cache_table->key_info, it,
+ TRUE, 1 /* skip result field*/))
+ {
+ DBUG_PRINT("error", ("creating index failed"));
+ goto error;
+ }
+ cache_table->s->keys= 1;
+ ref.null_rejecting= 1;
+ ref.disable_cache= FALSE;
+ ref.has_record= 0;
+ ref.use_count= 0;
+
+
+ if (open_tmp_table(cache_table))
+ {
+ DBUG_PRINT("error", ("Opening (creating) temporary table failed"));
+ goto error;
+ }
+
+ if (!(cached_result= new Item_field(cache_table->field[0])))
+ {
+ DBUG_PRINT("error", ("Creating Item_field failed"));
+ goto error;
+ }
+
+ DBUG_VOID_RETURN;
+
+error:
+ disable_cache();
+ DBUG_VOID_RETURN;
+}
+
+
+Expression_cache_tmptable::~Expression_cache_tmptable()
+{
+ /* Add accumulated statistics */
+ statistic_add(subquery_cache_miss, miss, &LOCK_status);
+ statistic_add(subquery_cache_hit, hit, &LOCK_status);
+
+ if (cache_table)
+ disable_cache();
+}
+
+
+/**
+ Check if a given set of parameters of the expression is in the cache
+
+ @param [out] value the expression value found in the cache if any
+
+ @details
+ For a given set of the parameters of the expression the function
+ checks whether it can be found in some entry of the cache. If so
+ the function returns the result of the expression extracted from
+ the cache.
+
+ @retval Expression_cache::HIT if the set of parameters is in the cache
+ @retval Expression_cache::MISS - otherwise
+*/
+
+Expression_cache::result Expression_cache_tmptable::check_value(Item **value)
+{
+ int res;
+ DBUG_ENTER("Expression_cache_tmptable::check_value");
+
+ if (cache_table)
+ {
+ DBUG_PRINT("info", ("status: %u has_record %u",
+ (uint)cache_table->status, (uint)ref.has_record));
+ if ((res= join_read_key2(table_thd, NULL, cache_table, &ref)) == 1)
+ DBUG_RETURN(ERROR);
+
+ if (res)
+ {
+ if (((++miss) == EXPCACHE_CHECK_HIT_RATIO_AFTER) &&
+ ((double)hit / ((double)hit + miss)) <
+ EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
+ {
+ DBUG_PRINT("info",
+ ("Early check: hit rate is not so good to keep the cache"));
+ disable_cache();
+ }
+
+ DBUG_RETURN(MISS);
+ }
+
+ hit++;
+ *value= cached_result;
+ DBUG_RETURN(Expression_cache::HIT);
+ }
+ DBUG_RETURN(Expression_cache::MISS);
+}
+
+
+/**
+ Put a new entry into the expression cache
+
+ @param value the result of the expression to be put into the cache
+
+ @details
+ The function evaluates 'value' and puts the result into the cache as the
+ result of the expression for the current set of parameters.
+
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+my_bool Expression_cache_tmptable::put_value(Item *value)
+{
+ int error;
+ DBUG_ENTER("Expression_cache_tmptable::put_value");
+ DBUG_ASSERT(inited);
+
+ if (!cache_table)
+ {
+ DBUG_PRINT("info", ("No table so behave as we successfully put value"));
+ DBUG_RETURN(FALSE);
+ }
+
+ *(items.head_ref())= value;
+ fill_record(table_thd, cache_table, cache_table->field, items, TRUE, TRUE);
+ if (table_thd->is_error())
+ goto err;;
+
+ if ((error= cache_table->file->ha_write_tmp_row(cache_table->record[0])))
+ {
+ /* create_myisam_from_heap will generate error if needed */
+ if (cache_table->file->is_fatal_error(error, HA_CHECK_DUP))
+ goto err;
+ else
+ {
+ double hit_rate= ((double)hit / ((double)hit + miss));
+ DBUG_ASSERT(miss > 0);
+ if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
+ {
+ DBUG_PRINT("info", ("hit rate is not so good to keep the cache"));
+ disable_cache();
+ DBUG_RETURN(FALSE);
+ }
+ else if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE)
+ {
+ DBUG_PRINT("info", ("hit rate is not so good to go to disk"));
+ if (cache_table->file->ha_delete_all_rows() ||
+ cache_table->file->ha_write_tmp_row(cache_table->record[0]))
+ goto err;
+ }
+ else
+ {
+ if (create_internal_tmp_table_from_heap(table_thd, cache_table,
+ cache_table_param.start_recinfo,
+ &cache_table_param.recinfo,
+ error, 1, NULL))
+ goto err;
+ }
+ }
+ }
+ cache_table->status= 0; /* cache_table->record contains an existed record */
+ ref.has_record= TRUE; /* the same as above */
+ DBUG_PRINT("info", ("has_record: TRUE status: 0"));
+
+ DBUG_RETURN(FALSE);
+
+err:
+ disable_cache();
+ DBUG_RETURN(TRUE);
+}
+
+
+void Expression_cache_tmptable::print(String *str, enum_query_type query_type)
+{
+ List_iterator<Item> li(items);
+ Item *item;
+ bool is_first= TRUE;
+
+ str->append('<');
+ li++; // skip result field
+ while ((item= li++))
+ {
+ if (!is_first)
+ str->append(',');
+ item->print(str, query_type);
+ is_first= FALSE;
+ }
+ str->append('>');
+}
diff --git a/sql/sql_expression_cache.h b/sql/sql_expression_cache.h
new file mode 100644
index 00000000000..48a8e33a787
--- /dev/null
+++ b/sql/sql_expression_cache.h
@@ -0,0 +1,111 @@
+/*
+ Copyright (c) 2010, 2011, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#ifndef SQL_EXPRESSION_CACHE_INCLUDED
+#define SQL_EXPRESSION_CACHE_INCLUDED
+
+#include "sql_select.h"
+
+/**
+ Interface for expression cache
+
+ @note
+ Parameters of an expression cache interface are set on the creation of the
+ cache. They are passed when a cache object of the implementation class is
+ constructed. That's why they are not visible in this interface.
+*/
+
+extern ulong subquery_cache_miss, subquery_cache_hit;
+
+class Expression_cache :public Sql_alloc
+{
+public:
+ enum result {ERROR, HIT, MISS};
+
+ Expression_cache(){};
+ virtual ~Expression_cache() {};
+ /**
+ Shall check the presence of expression value in the cache for a given
+ set of values of the expression parameters. Return the result of the
+ expression if it's found in the cache.
+ */
+ virtual result check_value(Item **value)= 0;
+ /**
+ Shall put the value of an expression for given set of its parameters
+ into the expression cache
+ */
+ virtual my_bool put_value(Item *value)= 0;
+
+ /**
+ Print cache parameters
+ */
+ virtual void print(String *str, enum_query_type query_type)= 0;
+
+ /**
+ Is this cache initialized
+ */
+ virtual bool is_inited()= 0;
+ /**
+ Initialize this cache
+ */
+ virtual void init()= 0;
+};
+
+struct st_table_ref;
+struct st_join_table;
+class Item_field;
+
+
+/**
+ Implementation of expression cache over a temporary table
+*/
+
+class Expression_cache_tmptable :public Expression_cache
+{
+public:
+ Expression_cache_tmptable(THD *thd, List<Item> &dependants, Item *value);
+ virtual ~Expression_cache_tmptable();
+ virtual result check_value(Item **value);
+ virtual my_bool put_value(Item *value);
+
+ void print(String *str, enum_query_type query_type);
+ bool is_inited() { return inited; };
+ void init();
+
+private:
+ void disable_cache();
+
+ /* tmp table parameters */
+ TMP_TABLE_PARAM cache_table_param;
+ /* temporary table to store this cache */
+ TABLE *cache_table;
+ /* Thread handle for the temporary table */
+ THD *table_thd;
+ /* TABLE_REF for index lookup */
+ struct st_table_ref ref;
+ /* Cached result */
+ Item_field *cached_result;
+ /* List of parameter items */
+ List<Item> &items;
+ /* Value Item example */
+ Item *val;
+ /* hit/miss counters */
+ uint hit, miss;
+ /* Set on if the object has been succesfully initialized with init() */
+ bool inited;
+};
+
+#endif /* SQL_EXPRESSION_CACHE_INCLUDED */
diff --git a/sql/sql_get_diagnostics.cc b/sql/sql_get_diagnostics.cc
new file mode 100644
index 00000000000..8b0d86aa7d1
--- /dev/null
+++ b/sql/sql_get_diagnostics.cc
@@ -0,0 +1,342 @@
+/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 02111-1307 USA */
+
+#include "sql_list.h" // Sql_alloc, List, List_iterator
+#include "sql_cmd.h" // Sql_cmd
+#include "sql_class.h" // Diagnostics_area
+#include "sql_get_diagnostics.h" // Sql_cmd_get_diagnostics
+
+/**
+ Execute this GET DIAGNOSTICS statement.
+
+ @param thd The current thread.
+
+ @remark Errors or warnings occurring during the execution of the GET
+ DIAGNOSTICS statement should not affect the diagnostics area
+ of a previous statement as the diagnostics information there
+ would be wiped out. Thus, in order to preserve the contents
+ of the diagnostics area from which information is being
+ retrieved, the GET DIAGNOSTICS statement is executed under
+ a separate diagnostics area. If any errors or warnings occur
+ during the execution of the GET DIAGNOSTICS statement, these
+ error or warnings (conditions) are appended to the list of
+ the original diagnostics area. The only exception to this is
+ fatal errors, which must always cause the statement to fail.
+
+ @retval false on success.
+ @retval true on error
+*/
+
+bool
+Sql_cmd_get_diagnostics::execute(THD *thd)
+{
+ bool rv;
+ Diagnostics_area new_stmt_da(thd->query_id, false, true);
+ Diagnostics_area *save_stmt_da= thd->get_stmt_da();
+ DBUG_ENTER("Sql_cmd_get_diagnostics::execute");
+
+ /* Disable the unneeded read-only mode of the original DA. */
+ save_stmt_da->set_warning_info_read_only(false);
+
+ /* Set new diagnostics area, execute statement and restore. */
+ thd->set_stmt_da(&new_stmt_da);
+ rv= m_info->aggregate(thd, save_stmt_da);
+ thd->set_stmt_da(save_stmt_da);
+
+ /* Bail out early if statement succeeded. */
+ if (! rv)
+ {
+ thd->get_stmt_da()->set_ok_status(0, 0, NULL);
+ DBUG_RETURN(false);
+ }
+
+ /* Statement failed, retrieve the error information for propagation. */
+ uint sql_errno= new_stmt_da.sql_errno();
+ const char *message= new_stmt_da.message();
+ const char *sqlstate= new_stmt_da.get_sqlstate();
+
+ /* In case of a fatal error, set it into the original DA.*/
+ if (thd->is_fatal_error)
+ {
+ save_stmt_da->set_error_status(sql_errno, message, sqlstate, NULL);
+ DBUG_RETURN(true);
+ }
+
+ /* Otherwise, just append the new error as a exception condition. */
+ save_stmt_da->push_warning(thd, sql_errno, sqlstate,
+ Sql_condition::WARN_LEVEL_ERROR,
+ message);
+
+ /* Appending might have failed. */
+ if (! (rv= thd->is_error()))
+ thd->get_stmt_da()->set_ok_status(0, 0, NULL);
+
+ DBUG_RETURN(rv);
+}
+
+
+/**
+ Set a value for this item.
+
+ @param thd The current thread.
+ @param value The obtained value.
+
+ @retval false on success.
+ @retval true on error.
+*/
+
+bool
+Diagnostics_information_item::set_value(THD *thd, Item **value)
+{
+ bool rv;
+ Settable_routine_parameter *srp;
+ DBUG_ENTER("Diagnostics_information_item::set_value");
+
+ /* Get a settable reference to the target. */
+ srp= m_target->get_settable_routine_parameter();
+
+ DBUG_ASSERT(srp);
+
+ /* Set variable/parameter value. */
+ rv= srp->set_value(thd, thd->spcont, value);
+
+ DBUG_RETURN(rv);
+}
+
+
+/**
+ Obtain statement information in the context of a given diagnostics area.
+
+ @param thd The current thread.
+ @param da The diagnostics area.
+
+ @retval false on success.
+ @retval true on error
+*/
+
+bool
+Statement_information::aggregate(THD *thd, const Diagnostics_area *da)
+{
+ bool rv= false;
+ Statement_information_item *stmt_info_item;
+ List_iterator<Statement_information_item> it(*m_items);
+ DBUG_ENTER("Statement_information::aggregate");
+
+ /*
+ Each specified target gets the value of each given
+ information item obtained from the diagnostics area.
+ */
+ while ((stmt_info_item= it++))
+ {
+ if ((rv= evaluate(thd, stmt_info_item, da)))
+ break;
+ }
+
+ DBUG_RETURN(rv);
+}
+
+
+/**
+ Obtain the value of this statement information item in the context of
+ a given diagnostics area.
+
+ @param thd The current thread.
+ @param da The diagnostics area.
+
+ @retval Item representing the value.
+ @retval NULL on error.
+*/
+
+Item *
+Statement_information_item::get_value(THD *thd, const Diagnostics_area *da)
+{
+ Item *value= NULL;
+ DBUG_ENTER("Statement_information_item::get_value");
+
+ switch (m_name)
+ {
+ /*
+ The number of condition areas that have information. That is,
+ the number of errors and warnings within the diagnostics area.
+ */
+ case NUMBER:
+ {
+ ulong count= da->cond_count();
+ value= new (thd->mem_root) Item_uint(count);
+ break;
+ }
+ /*
+ Number that shows how many rows were directly affected by
+ a data-change statement (INSERT, UPDATE, DELETE, MERGE,
+ REPLACE, LOAD).
+ */
+ case ROW_COUNT:
+ value= new (thd->mem_root) Item_int(thd->get_row_count_func());
+ break;
+ }
+
+ DBUG_RETURN(value);
+}
+
+
+/**
+ Obtain condition information in the context of a given diagnostics area.
+
+ @param thd The current thread.
+ @param da The diagnostics area.
+
+ @retval false on success.
+ @retval true on error
+*/
+
+bool
+Condition_information::aggregate(THD *thd, const Diagnostics_area *da)
+{
+ bool rv= false;
+ longlong cond_number;
+ const Sql_condition *cond= NULL;
+ Condition_information_item *cond_info_item;
+ Diagnostics_area::Sql_condition_iterator it_conds= da->sql_conditions();
+ List_iterator_fast<Condition_information_item> it_items(*m_items);
+ DBUG_ENTER("Condition_information::aggregate");
+
+ /* Prepare the expression for evaluation. */
+ if (!m_cond_number_expr->fixed &&
+ m_cond_number_expr->fix_fields(thd, &m_cond_number_expr))
+ DBUG_RETURN(true);
+
+ cond_number= m_cond_number_expr->val_int();
+
+ /*
+ Limit to the number of available conditions. Warning_info::warn_count()
+ is not used because it indicates the number of condition regardless of
+ @@max_error_count, which prevents conditions from being pushed, but not
+ counted.
+ */
+ if (cond_number < 1 || (ulonglong) cond_number > da->cond_count())
+ {
+ my_error(ER_DA_INVALID_CONDITION_NUMBER, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ /* Advance to the requested condition. */
+ while (cond_number--)
+ cond= it_conds++;
+
+ DBUG_ASSERT(cond);
+
+ /* Evaluate the requested information in the context of the condition. */
+ while ((cond_info_item= it_items++))
+ {
+ if ((rv= evaluate(thd, cond_info_item, cond)))
+ break;
+ }
+
+ DBUG_RETURN(rv);
+}
+
+
+/**
+ Create an UTF-8 string item to represent a condition item string.
+
+ @remark The string might not have a associated charset. For example,
+ this can be the case if the server does not or fails to process
+ the error message file.
+
+ @remark See "Design notes about Sql_condition::m_message_text." in sql_error.cc
+
+ @return Pointer to an string item, NULL on failure.
+*/
+
+Item *
+Condition_information_item::make_utf8_string_item(THD *thd, const String *str)
+{
+ /* Default is utf8 character set and utf8_general_ci collation. */
+ CHARSET_INFO *to_cs= &my_charset_utf8_general_ci;
+ /* If a charset was not set, assume that no conversion is needed. */
+ CHARSET_INFO *from_cs= str->charset() ? str->charset() : to_cs;
+ String tmp(str->ptr(), str->length(), from_cs);
+ /* If necessary, convert the string (ignoring errors), then copy it over. */
+ uint conv_errors;
+ return new Item_string(&tmp, to_cs, &conv_errors,
+ DERIVATION_COERCIBLE, MY_REPERTOIRE_UNICODE30);
+}
+
+
+/**
+ Obtain the value of this condition information item in the context of
+ a given condition.
+
+ @param thd The current thread.
+ @param da The diagnostics area.
+
+ @retval Item representing the value.
+ @retval NULL on error.
+*/
+
+Item *
+Condition_information_item::get_value(THD *thd, const Sql_condition *cond)
+{
+ String str;
+ Item *value= NULL;
+ DBUG_ENTER("Condition_information_item::get_value");
+
+ switch (m_name)
+ {
+ case CLASS_ORIGIN:
+ value= make_utf8_string_item(thd, &(cond->m_class_origin));
+ break;
+ case SUBCLASS_ORIGIN:
+ value= make_utf8_string_item(thd, &(cond->m_subclass_origin));
+ break;
+ case CONSTRAINT_CATALOG:
+ value= make_utf8_string_item(thd, &(cond->m_constraint_catalog));
+ break;
+ case CONSTRAINT_SCHEMA:
+ value= make_utf8_string_item(thd, &(cond->m_constraint_schema));
+ break;
+ case CONSTRAINT_NAME:
+ value= make_utf8_string_item(thd, &(cond->m_constraint_name));
+ break;
+ case CATALOG_NAME:
+ value= make_utf8_string_item(thd, &(cond->m_catalog_name));
+ break;
+ case SCHEMA_NAME:
+ value= make_utf8_string_item(thd, &(cond->m_schema_name));
+ break;
+ case TABLE_NAME:
+ value= make_utf8_string_item(thd, &(cond->m_table_name));
+ break;
+ case COLUMN_NAME:
+ value= make_utf8_string_item(thd, &(cond->m_column_name));
+ break;
+ case CURSOR_NAME:
+ value= make_utf8_string_item(thd, &(cond->m_cursor_name));
+ break;
+ case MESSAGE_TEXT:
+ value= make_utf8_string_item(thd, &(cond->m_message_text));
+ break;
+ case MYSQL_ERRNO:
+ value= new (thd->mem_root) Item_uint(cond->m_sql_errno);
+ break;
+ case RETURNED_SQLSTATE:
+ str.set_ascii(cond->get_sqlstate(), strlen(cond->get_sqlstate()));
+ value= make_utf8_string_item(thd, &str);
+ break;
+ }
+
+ DBUG_RETURN(value);
+}
+
diff --git a/sql/sql_get_diagnostics.h b/sql/sql_get_diagnostics.h
new file mode 100644
index 00000000000..f34820757f5
--- /dev/null
+++ b/sql/sql_get_diagnostics.h
@@ -0,0 +1,318 @@
+/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 02111-1307 USA */
+
+#ifndef SQL_GET_DIAGNOSTICS_H
+#define SQL_GET_DIAGNOSTICS_H
+
+/** Diagnostics information forward reference. */
+class Diagnostics_information;
+
+
+/**
+ Sql_cmd_get_diagnostics represents a GET DIAGNOSTICS statement.
+
+ The GET DIAGNOSTICS statement retrieves exception or completion
+ condition information from a diagnostics area, usually pertaining
+ to the last non-diagnostic SQL statement that was executed.
+*/
+class Sql_cmd_get_diagnostics : public Sql_cmd
+{
+public:
+ /**
+ Constructor, used to represent a GET DIAGNOSTICS statement.
+
+ @param info Diagnostics information to be obtained.
+ */
+ Sql_cmd_get_diagnostics(Diagnostics_information *info)
+ : m_info(info)
+ {}
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_GET_DIAGNOSTICS;
+ }
+
+ virtual bool execute(THD *thd);
+
+private:
+ /** The information to be obtained. */
+ Diagnostics_information *m_info;
+};
+
+
+/**
+ Represents the diagnostics information to be obtained.
+
+ Diagnostic information is made available through statement
+ information and condition information items.
+*/
+class Diagnostics_information : public Sql_alloc
+{
+public:
+ /**
+ Which diagnostics area to access.
+ Only CURRENT is supported for now.
+ */
+ enum Which_area
+ {
+ /** Access the first diagnostics area. */
+ CURRENT_AREA
+ };
+
+ /** Set which diagnostics area to access. */
+ void set_which_da(Which_area area)
+ { m_area= area; }
+
+ /** Get which diagnostics area to access. */
+ Which_area get_which_da(void) const
+ { return m_area; }
+
+ /**
+ Aggregate diagnostics information.
+
+ @param thd The current thread.
+ @param da The diagnostics area.
+
+ @retval false on success.
+ @retval true on error
+ */
+ virtual bool aggregate(THD *thd, const Diagnostics_area *da) = 0;
+
+protected:
+ /**
+ Diagnostics_information objects are allocated in thd->mem_root.
+ Do not rely on the destructor for any cleanup.
+ */
+ virtual ~Diagnostics_information()
+ {
+ DBUG_ASSERT(false);
+ }
+
+ /**
+ Evaluate a diagnostics information item in a specific context.
+
+ @param thd The current thread.
+ @param diag_item The diagnostics information item.
+ @param ctx The context to evaluate the item.
+
+ @retval false on success.
+ @retval true on error.
+ */
+ template <typename Diag_item, typename Context>
+ bool evaluate(THD *thd, Diag_item *diag_item, Context ctx)
+ {
+ Item *value;
+
+ /* Get this item's value. */
+ if (! (value= diag_item->get_value(thd, ctx)))
+ return true;
+
+ /* Set variable/parameter value. */
+ return diag_item->set_value(thd, &value);
+ }
+
+private:
+ /** Which diagnostics area to access. */
+ Which_area m_area;
+};
+
+
+/**
+ A diagnostics information item. Used to associate a specific
+ diagnostics information item to a target variable.
+*/
+class Diagnostics_information_item : public Sql_alloc
+{
+public:
+ /**
+ Set a value for this item.
+
+ @param thd The current thread.
+ @param value The obtained value.
+
+ @retval false on success.
+ @retval true on error.
+ */
+ bool set_value(THD *thd, Item **value);
+
+protected:
+ /**
+ Constructor, used to represent a diagnostics information item.
+
+ @param target A target that gets the value of this item.
+ */
+ Diagnostics_information_item(Item *target)
+ : m_target(target)
+ {}
+
+ /**
+ Diagnostics_information_item objects are allocated in thd->mem_root.
+ Do not rely on the destructor for any cleanup.
+ */
+ virtual ~Diagnostics_information_item()
+ {
+ DBUG_ASSERT(false);
+ }
+
+private:
+ /** The target variable that will receive the value of this item. */
+ Item *m_target;
+};
+
+
+/**
+ A statement information item.
+*/
+class Statement_information_item : public Diagnostics_information_item
+{
+public:
+ /** The name of a statement information item. */
+ enum Name
+ {
+ NUMBER,
+ ROW_COUNT
+ };
+
+ /**
+ Constructor, used to represent a statement information item.
+
+ @param name The name of this item.
+ @param target A target that gets the value of this item.
+ */
+ Statement_information_item(Name name, Item *target)
+ : Diagnostics_information_item(target), m_name(name)
+ {}
+
+ /** Obtain value of this statement information item. */
+ Item *get_value(THD *thd, const Diagnostics_area *da);
+
+private:
+ /** The name of this statement information item. */
+ Name m_name;
+};
+
+
+/**
+ Statement information.
+
+ @remark Provides information about the execution of a statement.
+*/
+class Statement_information : public Diagnostics_information
+{
+public:
+ /**
+ Constructor, used to represent the statement information of a
+ GET DIAGNOSTICS statement.
+
+ @param items List of requested statement information items.
+ */
+ Statement_information(List<Statement_information_item> *items)
+ : m_items(items)
+ {}
+
+ /** Obtain statement information in the context of a diagnostics area. */
+ bool aggregate(THD *thd, const Diagnostics_area *da);
+
+private:
+ /* List of statement information items. */
+ List<Statement_information_item> *m_items;
+};
+
+
+/**
+ A condition information item.
+*/
+class Condition_information_item : public Diagnostics_information_item
+{
+public:
+ /**
+ The name of a condition information item.
+ */
+ enum Name
+ {
+ CLASS_ORIGIN,
+ SUBCLASS_ORIGIN,
+ CONSTRAINT_CATALOG,
+ CONSTRAINT_SCHEMA,
+ CONSTRAINT_NAME,
+ CATALOG_NAME,
+ SCHEMA_NAME,
+ TABLE_NAME,
+ COLUMN_NAME,
+ CURSOR_NAME,
+ MESSAGE_TEXT,
+ MYSQL_ERRNO,
+ RETURNED_SQLSTATE
+ };
+
+ /**
+ Constructor, used to represent a condition information item.
+
+ @param name The name of this item.
+ @param target A target that gets the value of this item.
+ */
+ Condition_information_item(Name name, Item *target)
+ : Diagnostics_information_item(target), m_name(name)
+ {}
+
+ /** Obtain value of this condition information item. */
+ Item *get_value(THD *thd, const Sql_condition *cond);
+
+private:
+ /** The name of this condition information item. */
+ Name m_name;
+
+ /** Create an string item to represent a condition item string. */
+ Item *make_utf8_string_item(THD *thd, const String *str);
+};
+
+
+/**
+ Condition information.
+
+ @remark Provides information about conditions raised during the
+ execution of a statement.
+*/
+class Condition_information : public Diagnostics_information
+{
+public:
+ /**
+ Constructor, used to represent the condition information of a
+ GET DIAGNOSTICS statement.
+
+ @param cond_number_expr Number that identifies the diagnostic condition.
+ @param items List of requested condition information items.
+ */
+ Condition_information(Item *cond_number_expr,
+ List<Condition_information_item> *items)
+ : m_cond_number_expr(cond_number_expr), m_items(items)
+ {}
+
+ /** Obtain condition information in the context of a diagnostics area. */
+ bool aggregate(THD *thd, const Diagnostics_area *da);
+
+private:
+ /**
+ Number that identifies the diagnostic condition for which
+ information is to be obtained.
+ */
+ Item *m_cond_number_expr;
+
+ /** List of condition information items. */
+ List<Condition_information_item> *m_items;
+};
+
+#endif
+
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
new file mode 100644
index 00000000000..110bca96530
--- /dev/null
+++ b/sql/sql_handler.cc
@@ -0,0 +1,1192 @@
+/* Copyright (c) 2001, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2013, Monty Program Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* HANDLER ... commands - direct access to ISAM */
+
+/* TODO:
+ HANDLER blabla OPEN [ AS foobar ] [ (column-list) ]
+
+ the most natural (easiest, fastest) way to do it is to
+ compute List<Item> field_list not in mysql_ha_read
+ but in mysql_ha_open, and then store it in TABLE structure.
+
+ The problem here is that mysql_parse calls free_item to free all the
+ items allocated at the end of every query. The workaround would to
+ keep two item lists per THD - normal free_list and handler_items.
+ The second is to be freeed only on thread end. mysql_ha_open should
+ then do { handler_items=concat(handler_items, free_list); free_list=0; }
+
+ But !!! do_command calls free_root at the end of every query and frees up
+ all the sql_alloc'ed memory. It's harder to work around...
+*/
+
+/*
+ The information about open HANDLER objects is stored in a HASH.
+ It holds objects of type TABLE_LIST, which are indexed by table
+ name/alias, and allows us to quickly find a HANDLER table for any
+ operation at hand - be it HANDLER READ or HANDLER CLOSE.
+
+ It also allows us to maintain an "open" HANDLER even in cases
+ when there is no physically open cursor. E.g. a FLUSH TABLE
+ statement in this or some other connection demands that all open
+ HANDLERs against the flushed table are closed. In order to
+ preserve the information about an open HANDLER, we don't perform
+ a complete HANDLER CLOSE, but only close the TABLE object. The
+ corresponding TABLE_LIST is kept in the cache with 'table'
+ pointer set to NULL. The table will be reopened on next access
+ (this, however, leads to loss of cursor position, unless the
+ cursor points at the first record).
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_handler.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_base.h" // close_thread_tables
+#include "lock.h" // mysql_unlock_tables
+#include "key.h" // key_copy
+#include "sql_base.h" // insert_fields
+#include "sql_select.h"
+#include "transaction.h"
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#define HANDLER_TABLES_HASH_SIZE 120
+
+static enum enum_ha_read_modes rkey_to_rnext[]=
+{ RNEXT_SAME, RNEXT, RPREV, RNEXT, RPREV, RNEXT, RPREV, RPREV };
+
+/*
+ Set handler to state after create, but keep base information about
+ which table is used
+*/
+
+void SQL_HANDLER::reset()
+{
+ fields.empty();
+ arena.free_items();
+ free_root(&mem_root, MYF(0));
+ my_free(lock);
+ init();
+}
+
+/* Free all allocated data */
+
+SQL_HANDLER::~SQL_HANDLER()
+{
+ reset();
+ my_free(base_data);
+}
+
+/*
+ Get hash key and hash key length.
+
+ SYNOPSIS
+ mysql_ha_hash_get_key()
+ tables Pointer to the hash object.
+ key_len_p (out) Pointer to the result for key length.
+ first Unused.
+
+ DESCRIPTION
+ The hash object is an TABLE_LIST struct.
+ The hash key is the alias name.
+ The hash key length is the alias name length plus one for the
+ terminateing NUL character.
+
+ RETURN
+ Pointer to the TABLE_LIST struct.
+*/
+
+static char *mysql_ha_hash_get_key(SQL_HANDLER *table, size_t *key_len,
+ my_bool first __attribute__((unused)))
+{
+ *key_len= table->handler_name.length + 1 ; /* include '\0' in comparisons */
+ return table->handler_name.str;
+}
+
+
+/*
+ Free an hash object.
+
+ SYNOPSIS
+ mysql_ha_hash_free()
+ tables Pointer to the hash object.
+
+ DESCRIPTION
+ The hash object is an TABLE_LIST struct.
+
+ RETURN
+ Nothing
+*/
+
+static void mysql_ha_hash_free(SQL_HANDLER *table)
+{
+ delete table;
+}
+
+/**
+ Close a HANDLER table.
+
+ @param thd Thread identifier.
+ @param tables A list of tables with the first entry to close.
+
+ @note Though this function takes a list of tables, only the first list entry
+ will be closed.
+ @mote handler_object is not deleted!
+ @note Broadcasts refresh if it closed a table with old version.
+*/
+
+static void mysql_ha_close_table(SQL_HANDLER *handler)
+{
+ THD *thd= handler->thd;
+ TABLE *table= handler->table;
+
+ /* check if table was already closed */
+ if (!table)
+ return;
+
+ if (!table->s->tmp_table)
+ {
+ /* Non temporary table. */
+ if (handler->lock)
+ {
+ // Mark it unlocked, like in reset_lock_data()
+ reset_lock_data(handler->lock, 1);
+ }
+
+ table->file->ha_index_or_rnd_end();
+ table->open_by_handler= 0;
+ close_thread_table(thd, &table);
+ thd->mdl_context.release_lock(handler->mdl_request.ticket);
+ }
+ else
+ {
+ /* Must be a temporary table */
+ table->file->ha_index_or_rnd_end();
+ table->query_id= thd->query_id;
+ table->open_by_handler= 0;
+ mark_tmp_table_for_reuse(table);
+ }
+ my_free(handler->lock);
+ handler->init();
+}
+
+/*
+ Open a HANDLER table.
+
+ SYNOPSIS
+ mysql_ha_open()
+ thd Thread identifier.
+ tables A list of tables with the first entry to open.
+ reopen Re-open a previously opened handler table.
+
+ DESCRIPTION
+ Though this function takes a list of tables, only the first list entry
+ will be opened.
+ 'reopen' is set when a handler table is to be re-opened. In this case,
+ 'tables' is the pointer to the hashed SQL_HANDLER object which has been
+ saved on the original open.
+ 'reopen' is also used to suppress the sending of an 'ok' message.
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen)
+{
+ SQL_HANDLER *sql_handler= 0;
+ uint counter;
+ bool error;
+ TABLE *table, *backup_open_tables;
+ MDL_savepoint mdl_savepoint;
+ Query_arena backup_arena;
+ DBUG_ENTER("mysql_ha_open");
+ DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d",
+ tables->db, tables->table_name, tables->alias,
+ reopen != 0));
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (tables->schema_table)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "HANDLER OPEN",
+ INFORMATION_SCHEMA_NAME.str);
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (! my_hash_inited(&thd->handler_tables_hash))
+ {
+ /*
+ HASH entries are of type SQL_HANDLER
+ */
+ if (my_hash_init(&thd->handler_tables_hash, &my_charset_latin1,
+ HANDLER_TABLES_HASH_SIZE, 0, 0,
+ (my_hash_get_key) mysql_ha_hash_get_key,
+ (my_hash_free_key) mysql_ha_hash_free, 0))
+ {
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else if (! reopen) /* Otherwise we have 'tables' already. */
+ {
+ if (my_hash_search(&thd->handler_tables_hash, (uchar*) tables->alias,
+ strlen(tables->alias) + 1))
+ {
+ DBUG_PRINT("info",("duplicate '%s'", tables->alias));
+ DBUG_PRINT("exit",("ERROR"));
+ my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ /*
+ Save and reset the open_tables list so that open_tables() won't
+ be able to access (or know about) the previous list. And on return
+ from open_tables(), thd->open_tables will contain only the opened
+ table.
+
+ See open_table() back-off comments for more details.
+ */
+ backup_open_tables= thd->open_tables;
+ thd->set_open_tables(NULL);
+
+ /*
+ open_tables() will set 'tables->table' if successful.
+ It must be NULL for a real open when calling open_tables().
+ */
+ DBUG_ASSERT(! tables->table);
+
+ /*
+ We can't request lock with explicit duration for this table
+ right from the start as open_tables() can't handle properly
+ back-off for such locks.
+ */
+ tables->mdl_request.init(MDL_key::TABLE, tables->db, tables->table_name,
+ MDL_SHARED, MDL_TRANSACTION);
+ mdl_savepoint= thd->mdl_context.mdl_savepoint();
+
+ /* for now HANDLER can be used only for real TABLES */
+ tables->required_type= FRMTYPE_TABLE;
+
+ /*
+ We use open_tables() here, rather than, say,
+ open_ltable() or open_table() because we would like to be able
+ to open a temporary table.
+ */
+ error= (open_temporary_tables(thd, tables) ||
+ open_tables(thd, &tables, &counter, 0));
+
+ if (error)
+ goto err;
+
+ table= tables->table;
+
+ /* There can be only one table in '*tables'. */
+ if (! (table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
+ {
+ my_error(ER_ILLEGAL_HA, MYF(0), table->file->table_type(),
+ table->s->db.str, table->s->table_name.str);
+ goto err;
+ }
+
+ if (tables->mdl_request.ticket &&
+ thd->mdl_context.has_lock(mdl_savepoint, tables->mdl_request.ticket))
+ {
+ /* The ticket returned is within a savepoint. Make a copy. */
+ error= thd->mdl_context.clone_ticket(&tables->mdl_request);
+ tables->table->mdl_ticket= tables->mdl_request.ticket;
+ if (error)
+ goto err;
+ }
+
+ if (! reopen)
+ {
+ /* copy data to sql_handler */
+ if (!(sql_handler= new SQL_HANDLER(thd)))
+ goto err;
+ init_alloc_root(&sql_handler->mem_root, 1024, 0, MYF(MY_THREAD_SPECIFIC));
+
+ sql_handler->db.length= strlen(tables->db);
+ sql_handler->table_name.length= strlen(tables->table_name);
+ sql_handler->handler_name.length= strlen(tables->alias);
+
+ if (!(my_multi_malloc(MY_WME,
+ &sql_handler->db.str,
+ (uint) sql_handler->db.length + 1,
+ &sql_handler->table_name.str,
+ (uint) sql_handler->table_name.length + 1,
+ &sql_handler->handler_name.str,
+ (uint) sql_handler->handler_name.length + 1,
+ NullS)))
+ goto err;
+ sql_handler->base_data= sql_handler->db.str; // Free this
+ memcpy(sql_handler->db.str, tables->db, sql_handler->db.length +1);
+ memcpy(sql_handler->table_name.str, tables->table_name,
+ sql_handler->table_name.length+1);
+ memcpy(sql_handler->handler_name.str, tables->alias,
+ sql_handler->handler_name.length +1);
+
+ /* add to hash */
+ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) sql_handler))
+ goto err;
+ }
+ else
+ {
+ sql_handler= reopen;
+ sql_handler->reset();
+ }
+ sql_handler->table= table;
+ memcpy(&sql_handler->mdl_request, &tables->mdl_request,
+ sizeof(tables->mdl_request));
+
+ if (!(sql_handler->lock= get_lock_data(thd, &sql_handler->table, 1,
+ GET_LOCK_STORE_LOCKS)))
+ goto err;
+
+ /* Get a list of all fields for send_fields */
+ thd->set_n_backup_active_arena(&sql_handler->arena, &backup_arena);
+ error= table->fill_item_list(&sql_handler->fields);
+ thd->restore_active_arena(&sql_handler->arena, &backup_arena);
+
+ if (error)
+ goto err;
+
+ /* Always read all columns */
+ table->read_set= &table->s->all_set;
+ table->vcol_set= &table->s->all_set;
+
+ /* Restore the state. */
+ thd->set_open_tables(backup_open_tables);
+ if (sql_handler->mdl_request.ticket)
+ {
+ thd->mdl_context.set_lock_duration(sql_handler->mdl_request.ticket,
+ MDL_EXPLICIT);
+ thd->mdl_context.set_needs_thr_lock_abort(TRUE);
+ }
+
+ /*
+ Assert that the above check prevents opening of views and merge tables.
+ For temporary tables, TABLE::next can be set even if only one table
+ was opened for HANDLER as it is used to link them together
+ (see thd->temporary_tables).
+ */
+ DBUG_ASSERT(sql_handler->table->next == NULL ||
+ sql_handler->table->s->tmp_table);
+ /*
+ If it's a temp table, don't reset table->query_id as the table is
+ being used by this handler. For non-temp tables we use this flag
+ in asserts.
+ */
+ table->open_by_handler= 1;
+
+ /* Safety, cleanup the pointer to satisfy MDL assertions. */
+ tables->mdl_request.ticket= NULL;
+
+ if (! reopen)
+ my_ok(thd);
+ DBUG_PRINT("exit",("OK"));
+ DBUG_RETURN(FALSE);
+
+err:
+ /*
+ No need to rollback statement transaction, it's not started.
+ If called with reopen flag, no need to rollback either,
+ it will be done at statement end.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+ thd->set_open_tables(backup_open_tables);
+ if (sql_handler)
+ {
+ if (!reopen)
+ my_hash_delete(&thd->handler_tables_hash, (uchar*) sql_handler);
+ else
+ sql_handler->reset(); // or should it be init() ?
+ }
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Close a HANDLER table by alias or table name
+
+ SYNOPSIS
+ mysql_ha_close()
+ thd Thread identifier.
+ tables A list of tables with the first entry to close.
+
+ DESCRIPTION
+ Closes the table that is associated (on the handler tables hash) with the
+ name (table->alias) of the specified table.
+
+ RETURN
+ FALSE ok
+ TRUE error
+*/
+
+bool mysql_ha_close(THD *thd, TABLE_LIST *tables)
+{
+ SQL_HANDLER *handler;
+ DBUG_ENTER("mysql_ha_close");
+ DBUG_PRINT("enter",("'%s'.'%s' as '%s'",
+ tables->db, tables->table_name, tables->alias));
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if ((handler= (SQL_HANDLER*) my_hash_search(&thd->handler_tables_hash,
+ (uchar*) tables->alias,
+ strlen(tables->alias) + 1)))
+ {
+ mysql_ha_close_table(handler);
+ my_hash_delete(&thd->handler_tables_hash, (uchar*) handler);
+ }
+ else
+ {
+ my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER");
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Mark MDL_context as no longer breaking protocol if we have
+ closed last HANDLER.
+ */
+ if (! thd->handler_tables_hash.records)
+ thd->mdl_context.set_needs_thr_lock_abort(FALSE);
+
+ my_ok(thd);
+ DBUG_PRINT("exit", ("OK"));
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ A helper class to process an error from mysql_lock_tables().
+ HANDLER READ statement's attempt to lock the subject table
+ may get aborted if there is a pending DDL. In that case
+ we close the table, reopen it, and try to read again.
+ This is implicit and obscure, since HANDLER position
+ is lost in the process, but it's the legacy server
+ behaviour we should preserve.
+*/
+
+class Sql_handler_lock_error_handler: public Internal_error_handler
+{
+public:
+ virtual
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char *sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition **cond_hdl);
+
+ bool need_reopen() const { return m_need_reopen; };
+ void init() { m_need_reopen= FALSE; };
+private:
+ bool m_need_reopen;
+};
+
+
+/**
+ Handle an error from mysql_lock_tables().
+ Ignore ER_LOCK_ABORTED errors.
+*/
+
+bool
+Sql_handler_lock_error_handler::
+handle_condition(THD *thd,
+ uint sql_errno,
+ const char *sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition **cond_hdl)
+{
+ *cond_hdl= NULL;
+ if (sql_errno == ER_LOCK_ABORTED)
+ m_need_reopen= TRUE;
+
+ return m_need_reopen;
+}
+
+
+/**
+ Finds an open HANDLER table.
+
+ @params name Name of handler to open
+
+ @return 0 failure
+ @return handler
+*/
+
+SQL_HANDLER *mysql_ha_find_handler(THD *thd, const char *name)
+{
+ SQL_HANDLER *handler;
+ if ((handler= (SQL_HANDLER*) my_hash_search(&thd->handler_tables_hash,
+ (uchar*) name, strlen(name) + 1)))
+ {
+ DBUG_PRINT("info-in-hash",("'%s'.'%s' as '%s' table: %p",
+ handler->db.str,
+ handler->table_name.str,
+ handler->handler_name.str, handler->table));
+ if (!handler->table)
+ {
+ /* The handler table has been closed. Re-open it. */
+ TABLE_LIST tmp;
+ tmp.init_one_table(handler->db.str, handler->db.length,
+ handler->table_name.str, handler->table_name.length,
+ handler->handler_name.str, TL_READ);
+
+ if (mysql_ha_open(thd, &tmp, handler))
+ {
+ DBUG_PRINT("exit",("reopen failed"));
+ return 0;
+ }
+ }
+ }
+ else
+ {
+ my_error(ER_UNKNOWN_TABLE, MYF(0), name, "HANDLER");
+ return 0;
+ }
+ return handler;
+}
+
+
+/**
+ Check that condition and key name are ok
+
+ @param handler
+ @param mode Read mode (RFIRST, RNEXT etc...)
+ @param keyname Key to use.
+ @param key_expr List of key column values
+ @param cond Where clause
+ @param in_prepare If we are in prepare phase (we can't evalute items yet)
+
+ @return 0 ok
+ @return 1 error
+
+ In ok, then values of used key and mode is stored in sql_handler
+*/
+
+static bool
+mysql_ha_fix_cond_and_key(SQL_HANDLER *handler,
+ enum enum_ha_read_modes mode, char *keyname,
+ List<Item> *key_expr,
+ Item *cond, bool in_prepare)
+{
+ THD *thd= handler->thd;
+ TABLE *table= handler->table;
+ if (cond)
+ {
+ /* This can only be true for temp tables */
+ if (table->query_id != thd->query_id)
+ cond->cleanup(); // File was reopened
+ if ((!cond->fixed &&
+ cond->fix_fields(thd, &cond)) || cond->check_cols(1))
+ return 1;
+ }
+
+ if (keyname)
+ {
+ /* Check if same as last keyname. If not, do a full lookup */
+ if (handler->keyno < 0 ||
+ my_strcasecmp(&my_charset_latin1,
+ keyname,
+ table->s->key_info[handler->keyno].name))
+ {
+ if ((handler->keyno= find_type(keyname, &table->s->keynames,
+ FIND_TYPE_NO_PREFIX) - 1) < 0)
+ {
+ my_error(ER_KEY_DOES_NOT_EXITS, MYF(0), keyname,
+ handler->handler_name.str);
+ return 1;
+ }
+ }
+
+ /* Check key parts */
+ if (mode == RKEY)
+ {
+ TABLE *table= handler->table;
+ KEY *keyinfo= table->key_info + handler->keyno;
+ KEY_PART_INFO *key_part= keyinfo->key_part;
+ List_iterator<Item> it_ke(*key_expr);
+ Item *item;
+ key_part_map keypart_map;
+ uint key_len;
+
+ if (key_expr->elements > keyinfo->user_defined_key_parts)
+ {
+ my_error(ER_TOO_MANY_KEY_PARTS, MYF(0),
+ keyinfo->user_defined_key_parts);
+ return 1;
+ }
+ for (keypart_map= key_len=0 ; (item=it_ke++) ; key_part++)
+ {
+ my_bitmap_map *old_map;
+ /* note that 'item' can be changed by fix_fields() call */
+ if ((!item->fixed &&
+ item->fix_fields(thd, it_ke.ref())) ||
+ (item= *it_ke.ref())->check_cols(1))
+ return 1;
+ if (item->used_tables() & ~(RAND_TABLE_BIT | PARAM_TABLE_BIT))
+ {
+ my_error(ER_WRONG_ARGUMENTS,MYF(0),"HANDLER ... READ");
+ return 1;
+ }
+ if (!in_prepare)
+ {
+ old_map= dbug_tmp_use_all_columns(table, table->write_set);
+ (void) item->save_in_field(key_part->field, 1);
+ dbug_tmp_restore_column_map(table->write_set, old_map);
+ }
+ key_len+= key_part->store_length;
+ keypart_map= (keypart_map << 1) | 1;
+ }
+ handler->keypart_map= keypart_map;
+ handler->key_len= key_len;
+ }
+ else
+ {
+ /*
+ Check if the same index involved.
+ We need to always do this check because we may not have yet
+ called the handler since the last keyno change.
+ */
+ if ((uint) handler->keyno != table->file->get_index())
+ {
+ if (mode == RNEXT)
+ mode= RFIRST;
+ else if (mode == RPREV)
+ mode= RLAST;
+ }
+ }
+ }
+ else if (table->file->inited != handler::RND)
+ {
+ /* Convert RNEXT to RFIRST if we haven't started row scan */
+ if (mode == RNEXT)
+ mode= RFIRST;
+ }
+ handler->mode= mode; // Store adjusted mode
+ return 0;
+}
+
+/*
+ Read from a HANDLER table.
+
+ SYNOPSIS
+ mysql_ha_read()
+ thd Thread identifier.
+ tables A list of tables with the first entry to read.
+ mode
+ keyname
+ key_expr
+ ha_rkey_mode
+ cond
+ select_limit_cnt
+ offset_limit_cnt
+
+ RETURN
+ FALSE ok
+ TRUE error
+*/
+
+bool mysql_ha_read(THD *thd, TABLE_LIST *tables,
+ enum enum_ha_read_modes mode, char *keyname,
+ List<Item> *key_expr,
+ enum ha_rkey_function ha_rkey_mode, Item *cond,
+ ha_rows select_limit_cnt, ha_rows offset_limit_cnt)
+{
+ SQL_HANDLER *handler;
+ TABLE *table;
+ Protocol *protocol= thd->protocol;
+ char buff[MAX_FIELD_WIDTH];
+ String buffer(buff, sizeof(buff), system_charset_info);
+ int error, keyno;
+ uint num_rows;
+ uchar *UNINIT_VAR(key);
+ Sql_handler_lock_error_handler sql_handler_lock_error;
+ DBUG_ENTER("mysql_ha_read");
+ DBUG_PRINT("enter",("'%s'.'%s' as '%s'",
+ tables->db, tables->table_name, tables->alias));
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+retry:
+ if (!(handler= mysql_ha_find_handler(thd, tables->alias)))
+ goto err0;
+
+ table= handler->table;
+ tables->table= table; // This is used by fix_fields
+ table->pos_in_table_list= tables;
+
+ if (handler->lock->lock_count > 0)
+ {
+ int lock_error;
+
+ handler->lock->locks[0]->type= handler->lock->locks[0]->org_type;
+
+ /* save open_tables state */
+ TABLE* backup_open_tables= thd->open_tables;
+ /* Always a one-element list, see mysql_ha_open(). */
+ DBUG_ASSERT(table->next == NULL || table->s->tmp_table);
+ /*
+ mysql_lock_tables() needs thd->open_tables to be set correctly to
+ be able to handle aborts properly.
+ */
+ thd->set_open_tables(table);
+
+ sql_handler_lock_error.init();
+ thd->push_internal_handler(&sql_handler_lock_error);
+
+ lock_error= mysql_lock_tables(thd, handler->lock,
+ (table->s->tmp_table == NO_TMP_TABLE ?
+ MYSQL_LOCK_NOT_TEMPORARY : 0));
+
+ thd->pop_internal_handler();
+
+ /*
+ In 5.1 and earlier, mysql_lock_tables() could replace the TABLE
+ object with another one (reopen it). This is no longer the case
+ with new MDL.
+ */
+ DBUG_ASSERT(table == thd->open_tables);
+ /* Restore previous context. */
+ thd->set_open_tables(backup_open_tables);
+
+ if (sql_handler_lock_error.need_reopen())
+ {
+ DBUG_ASSERT(lock_error && !thd->is_error());
+ /*
+ Always close statement transaction explicitly,
+ so that the engine doesn't have to count locks.
+ There should be no need to perform transaction
+ rollback due to deadlock.
+ */
+ DBUG_ASSERT(! thd->transaction_rollback_request);
+ trans_rollback_stmt(thd);
+ mysql_ha_close_table(handler);
+ if (thd->stmt_arena->is_stmt_execute())
+ {
+ /*
+ As we have already sent field list and types to the client, we can't
+ handle any changes in the table format for prepared statements.
+ Better to force a reprepare.
+ */
+ my_error(ER_NEED_REPREPARE, MYF(0));
+ goto err0;
+ }
+ goto retry;
+ }
+
+ if (lock_error)
+ goto err0; // mysql_lock_tables() printed error message already
+ }
+
+ if (mysql_ha_fix_cond_and_key(handler, mode, keyname, key_expr, cond, 0))
+ goto err;
+ mode= handler->mode;
+ keyno= handler->keyno;
+
+ protocol->send_result_set_metadata(&handler->fields,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
+
+ /*
+ In ::external_lock InnoDB resets the fields which tell it that
+ the handle is used in the HANDLER interface. Tell it again that
+ we are using it for HANDLER.
+ */
+
+ table->file->init_table_handle_for_HANDLER();
+
+ for (num_rows=0; num_rows < select_limit_cnt; )
+ {
+ switch (mode) {
+ case RNEXT:
+ if (table->file->inited != handler::NONE)
+ {
+ if ((error= table->file->can_continue_handler_scan()))
+ break;
+ if (keyname)
+ {
+ /* Check if we read from the same index. */
+ DBUG_ASSERT((uint) keyno == table->file->get_index());
+ error= table->file->ha_index_next(table->record[0]);
+ }
+ else
+ error= table->file->ha_rnd_next(table->record[0]);
+ break;
+ }
+ /* else fall through */
+ case RFIRST:
+ if (keyname)
+ {
+ if (!(error= table->file->ha_index_or_rnd_end()) &&
+ !(error= table->file->ha_index_init(keyno, 1)))
+ error= table->file->ha_index_first(table->record[0]);
+ }
+ else
+ {
+ if (!(error= table->file->ha_index_or_rnd_end()) &&
+ !(error= table->file->ha_rnd_init(1)))
+ error= table->file->ha_rnd_next(table->record[0]);
+ }
+ mode= RNEXT;
+ break;
+ case RPREV:
+ DBUG_ASSERT(keyname != 0);
+ /* Check if we read from the same index. */
+ DBUG_ASSERT((uint) keyno == table->file->get_index());
+ if (table->file->inited != handler::NONE)
+ {
+ if ((error= table->file->can_continue_handler_scan()))
+ break;
+ error= table->file->ha_index_prev(table->record[0]);
+ break;
+ }
+ /* else fall through */
+ case RLAST:
+ DBUG_ASSERT(keyname != 0);
+ if (!(error= table->file->ha_index_or_rnd_end()) &&
+ !(error= table->file->ha_index_init(keyno, 1)))
+ error= table->file->ha_index_last(table->record[0]);
+ mode=RPREV;
+ break;
+ case RNEXT_SAME:
+ /* Continue scan on "(keypart1,keypart2,...)=(c1, c2, ...) */
+ DBUG_ASSERT(keyname != 0);
+ error= table->file->ha_index_next_same(table->record[0], key,
+ handler->key_len);
+ break;
+ case RKEY:
+ {
+ DBUG_ASSERT(keyname != 0);
+
+ if (!(key= (uchar*) thd->calloc(ALIGN_SIZE(handler->key_len))))
+ goto err;
+ if ((error= table->file->ha_index_or_rnd_end()))
+ break;
+ key_copy(key, table->record[0], table->key_info + keyno,
+ handler->key_len);
+ if (!(error= table->file->ha_index_init(keyno, 1)))
+ error= table->file->ha_index_read_map(table->record[0],
+ key, handler->keypart_map,
+ ha_rkey_mode);
+ mode= rkey_to_rnext[(int)ha_rkey_mode];
+ break;
+ }
+ default:
+ my_error(ER_ILLEGAL_HA, MYF(0), table->file->table_type(),
+ table->s->db.str, table->s->table_name.str);
+ goto err;
+ }
+
+ if (error)
+ {
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ {
+ /* Don't give error in the log file for some expected problems */
+ if (error != HA_ERR_RECORD_CHANGED && error != HA_ERR_WRONG_COMMAND)
+ sql_print_error("mysql_ha_read: Got error %d when reading "
+ "table '%s'",
+ error, tables->table_name);
+ table->file->print_error(error,MYF(0));
+ table->file->ha_index_or_rnd_end();
+ goto err;
+ }
+ goto ok;
+ }
+ /* Generate values for virtual fields */
+ if (table->vfield)
+ update_virtual_fields(thd, table);
+ if (cond && !cond->val_int())
+ {
+ if (thd->is_error())
+ goto err;
+ continue;
+ }
+ if (num_rows >= offset_limit_cnt)
+ {
+ protocol->prepare_for_resend();
+
+ if (protocol->send_result_set_row(&handler->fields))
+ goto err;
+
+ protocol->write();
+ }
+ num_rows++;
+ }
+ok:
+ /*
+ Always close statement transaction explicitly,
+ so that the engine doesn't have to count locks.
+ */
+ trans_commit_stmt(thd);
+ mysql_unlock_tables(thd, handler->lock, 0);
+ my_eof(thd);
+ DBUG_PRINT("exit",("OK"));
+ DBUG_RETURN(FALSE);
+
+err:
+ trans_rollback_stmt(thd);
+ mysql_unlock_tables(thd, handler->lock, 0);
+err0:
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Prepare for handler read
+
+ For parameters, see mysql_ha_read()
+*/
+
+SQL_HANDLER *mysql_ha_read_prepare(THD *thd, TABLE_LIST *tables,
+ enum enum_ha_read_modes mode, char *keyname,
+ List<Item> *key_expr, Item *cond)
+{
+ SQL_HANDLER *handler;
+ DBUG_ENTER("mysql_ha_read_prepare");
+ if (!(handler= mysql_ha_find_handler(thd, tables->alias)))
+ DBUG_RETURN(0);
+ tables->table= handler->table; // This is used by fix_fields
+ if (mysql_ha_fix_cond_and_key(handler, mode, keyname, key_expr, cond, 1))
+ DBUG_RETURN(0);
+ DBUG_RETURN(handler);
+}
+
+
+
+/**
+ Scan the handler tables hash for matching tables.
+
+ @param thd Thread identifier.
+ @param tables The list of tables to remove.
+
+ @return Pointer to head of linked list (TABLE_LIST::next_local) of matching
+ TABLE_LIST elements from handler_tables_hash. Otherwise, NULL if no
+ table was matched.
+*/
+
+static SQL_HANDLER *mysql_ha_find_match(THD *thd, TABLE_LIST *tables)
+{
+ SQL_HANDLER *hash_tables, *head= NULL;
+ TABLE_LIST *first= tables;
+ DBUG_ENTER("mysql_ha_find_match");
+
+ /* search for all handlers with matching table names */
+ for (uint i= 0; i < thd->handler_tables_hash.records; i++)
+ {
+ hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i);
+
+ for (tables= first; tables; tables= tables->next_local)
+ {
+ if (tables->is_anonymous_derived_table())
+ continue;
+ if ((! *tables->db ||
+ ! my_strcasecmp(&my_charset_latin1, hash_tables->db.str,
+ tables->get_db_name())) &&
+ ! my_strcasecmp(&my_charset_latin1, hash_tables->table_name.str,
+ tables->get_table_name()))
+ {
+ /* Link into hash_tables list */
+ hash_tables->next= head;
+ head= hash_tables;
+ break;
+ }
+ }
+ }
+ DBUG_RETURN(head);
+}
+
+
+/**
+ Remove matching tables from the HANDLER's hash table.
+
+ @param thd Thread identifier.
+ @param tables The list of tables to remove.
+
+ @note Broadcasts refresh if it closed a table with old version.
+*/
+
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables)
+{
+ SQL_HANDLER *hash_tables, *next;
+ DBUG_ENTER("mysql_ha_rm_tables");
+
+ DBUG_ASSERT(tables);
+
+ hash_tables= mysql_ha_find_match(thd, tables);
+
+ while (hash_tables)
+ {
+ next= hash_tables->next;
+ if (hash_tables->table)
+ mysql_ha_close_table(hash_tables);
+ my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
+ hash_tables= next;
+ }
+
+ /*
+ Mark MDL_context as no longer breaking protocol if we have
+ closed last HANDLER.
+ */
+ if (! thd->handler_tables_hash.records)
+ thd->mdl_context.set_needs_thr_lock_abort(FALSE);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Close cursors of matching tables from the HANDLER's hash table.
+
+ @param thd Thread identifier.
+ @param tables The list of tables to flush.
+*/
+
+void mysql_ha_flush_tables(THD *thd, TABLE_LIST *all_tables)
+{
+ DBUG_ENTER("mysql_ha_flush_tables");
+
+ for (TABLE_LIST *table_list= all_tables; table_list;
+ table_list= table_list->next_global)
+ {
+ SQL_HANDLER *hash_tables= mysql_ha_find_match(thd, table_list);
+ /* Close all aliases of the same table. */
+ while (hash_tables)
+ {
+ SQL_HANDLER *next_local= hash_tables->next;
+ if (hash_tables->table)
+ mysql_ha_close_table(hash_tables);
+ hash_tables= next_local;
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Flush (close and mark for re-open) all tables that should be should
+ be reopen.
+
+ @param thd Thread identifier.
+
+ @note Broadcasts refresh if it closed a table with old version.
+*/
+
+void mysql_ha_flush(THD *thd)
+{
+ SQL_HANDLER *hash_tables;
+ DBUG_ENTER("mysql_ha_flush");
+
+ /*
+ Don't try to flush open HANDLERs when we're working with
+ system tables. The main MDL context is backed up and we can't
+ properly release HANDLER locks stored there.
+ */
+ if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)
+ DBUG_VOID_RETURN;
+
+ for (uint i= 0; i < thd->handler_tables_hash.records; i++)
+ {
+ hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i);
+ /*
+ TABLE::mdl_ticket is 0 for temporary tables so we need extra check.
+ */
+ if (hash_tables->table &&
+ ((hash_tables->table->mdl_ticket &&
+ hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) ||
+ (!hash_tables->table->s->tmp_table &&
+ hash_tables->table->s->tdc.flushed)))
+ mysql_ha_close_table(hash_tables);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Close all HANDLER's tables.
+
+ @param thd Thread identifier.
+
+ @note Broadcasts refresh if it closed a table with old version.
+*/
+
+void mysql_ha_cleanup(THD *thd)
+{
+ SQL_HANDLER *hash_tables;
+ DBUG_ENTER("mysql_ha_cleanup");
+
+ for (uint i= 0; i < thd->handler_tables_hash.records; i++)
+ {
+ hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i);
+ if (hash_tables->table)
+ mysql_ha_close_table(hash_tables);
+ }
+
+ my_hash_free(&thd->handler_tables_hash);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set explicit duration for metadata locks corresponding to open HANDLERs
+ to protect them from being released at the end of transaction.
+
+ @param thd Thread identifier.
+*/
+
+void mysql_ha_set_explicit_lock_duration(THD *thd)
+{
+ SQL_HANDLER *hash_tables;
+ DBUG_ENTER("mysql_ha_set_explicit_lock_duration");
+
+ for (uint i= 0; i < thd->handler_tables_hash.records; i++)
+ {
+ hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i);
+ if (hash_tables->table && hash_tables->table->mdl_ticket)
+ thd->mdl_context.set_lock_duration(hash_tables->table->mdl_ticket,
+ MDL_EXPLICIT);
+ }
+ DBUG_VOID_RETURN;
+}
+
diff --git a/sql/sql_handler.h b/sql/sql_handler.h
new file mode 100644
index 00000000000..133f553675e
--- /dev/null
+++ b/sql/sql_handler.h
@@ -0,0 +1,80 @@
+#ifndef SQL_HANDLER_INCLUDED
+#define SQL_HANDLER_INCLUDED
+/* Copyright (C) 2010 Monty Program Ab
+ 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 */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_class.h" /* enum_ha_read_mode */
+#include "my_base.h" /* ha_rkey_function, ha_rows */
+#include "sql_list.h" /* List */
+
+/* Open handlers are stored here */
+
+class SQL_HANDLER {
+public:
+ TABLE *table;
+ List<Item> fields; /* Fields, set on open */
+ THD *thd;
+ LEX_STRING handler_name;
+ LEX_STRING db;
+ LEX_STRING table_name;
+ MEM_ROOT mem_root;
+ MYSQL_LOCK *lock;
+ MDL_request mdl_request;
+
+ key_part_map keypart_map;
+ int keyno; /* Used key */
+ uint key_len;
+ enum enum_ha_read_modes mode;
+
+ /* This is only used when deleting many handler objects */
+ SQL_HANDLER *next;
+
+ Query_arena arena;
+ char *base_data;
+ SQL_HANDLER(THD *thd_arg) :
+ thd(thd_arg), arena(&mem_root, Query_arena::STMT_INITIALIZED)
+ { init(); clear_alloc_root(&mem_root); base_data= 0; }
+ void init()
+ {
+ keyno= -1;
+ table= 0;
+ lock= 0;
+ mdl_request.ticket= 0;
+ }
+ void reset();
+
+ ~SQL_HANDLER();
+};
+
+class THD;
+struct TABLE_LIST;
+
+bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen);
+bool mysql_ha_close(THD *thd, TABLE_LIST *tables);
+bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *,
+ List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows);
+void mysql_ha_flush(THD *thd);
+void mysql_ha_flush_tables(THD *thd, TABLE_LIST *all_tables);
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables);
+void mysql_ha_cleanup(THD *thd);
+void mysql_ha_set_explicit_lock_duration(THD *thd);
+
+SQL_HANDLER *mysql_ha_read_prepare(THD *thd, TABLE_LIST *tables,
+ enum enum_ha_read_modes mode, char *keyname,
+ List<Item> *key_expr, Item *cond);
+#endif
diff --git a/sql/sql_help.cc b/sql/sql_help.cc
new file mode 100644
index 00000000000..afeb9395a55
--- /dev/null
+++ b/sql/sql_help.cc
@@ -0,0 +1,825 @@
+/* Copyright (c) 2002, 2012, Oracle and/or its affiliates.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_help.h"
+#include "sql_table.h" // primary_key_name
+#include "sql_base.h" // REPORT_ALL_ERRORS, setup_tables
+#include "opt_range.h" // SQL_SELECT
+#include "records.h" // init_read_record, end_read_record
+
+struct st_find_field
+{
+ const char *table_name, *field_name;
+ Field *field;
+};
+
+/* Used fields */
+
+static struct st_find_field init_used_fields[]=
+{
+ { "help_topic", "help_topic_id", 0},
+ { "help_topic", "name", 0},
+ { "help_topic", "help_category_id", 0},
+ { "help_topic", "description", 0},
+ { "help_topic", "example", 0},
+
+ { "help_category", "help_category_id", 0},
+ { "help_category", "parent_category_id", 0},
+ { "help_category", "name", 0},
+
+ { "help_keyword", "help_keyword_id", 0},
+ { "help_keyword", "name", 0},
+
+ { "help_relation", "help_topic_id", 0},
+ { "help_relation", "help_keyword_id", 0}
+};
+
+enum enum_used_fields
+{
+ help_topic_help_topic_id= 0,
+ help_topic_name,
+ help_topic_help_category_id,
+ help_topic_description,
+ help_topic_example,
+
+ help_category_help_category_id,
+ help_category_parent_category_id,
+ help_category_name,
+
+ help_keyword_help_keyword_id,
+ help_keyword_name,
+
+ help_relation_help_topic_id,
+ help_relation_help_keyword_id
+};
+
+
+/*
+ Fill st_find_field structure with pointers to fields
+
+ SYNOPSIS
+ init_fields()
+ thd Thread handler
+ tables list of all tables for fields
+ find_fields array of structures
+ count size of previous array
+
+ RETURN VALUES
+ 0 all ok
+ 1 one of the fileds was not found
+*/
+
+static bool init_fields(THD *thd, TABLE_LIST *tables,
+ struct st_find_field *find_fields, uint count)
+{
+ Name_resolution_context *context= &thd->lex->select_lex.context;
+ DBUG_ENTER("init_fields");
+ context->resolve_in_table_list_only(tables);
+ for (; count-- ; find_fields++)
+ {
+ /* We have to use 'new' here as field will be re_linked on free */
+ Item_field *field= new Item_field(context,
+ "mysql", find_fields->table_name,
+ find_fields->field_name);
+ if (!(find_fields->field= find_field_in_tables(thd, field, tables, NULL,
+ 0, REPORT_ALL_ERRORS, 1,
+ TRUE)))
+ DBUG_RETURN(1);
+ bitmap_set_bit(find_fields->field->table->read_set,
+ find_fields->field->field_index);
+ /* To make life easier when setting values in keys */
+ bitmap_set_bit(find_fields->field->table->write_set,
+ find_fields->field->field_index);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Returns variants of found topic for help (if it is just single topic,
+ returns description and example, or else returns only names..)
+
+ SYNOPSIS
+ memorize_variant_topic()
+
+ thd Thread handler
+ topics Table of topics
+ count number of alredy found topics
+ find_fields Filled array of information for work with fields
+
+ RETURN VALUES
+ names array of names of found topics (out)
+
+ name name of found topic (out)
+ description description of found topic (out)
+ example example for found topic (out)
+
+ NOTE
+ Field 'names' is set only if more than one topic is found.
+ Fields 'name', 'description', 'example' are set only if
+ found exactly one topic.
+*/
+
+void memorize_variant_topic(THD *thd, TABLE *topics, int count,
+ struct st_find_field *find_fields,
+ List<String> *names,
+ String *name, String *description, String *example)
+{
+ DBUG_ENTER("memorize_variant_topic");
+ MEM_ROOT *mem_root= thd->mem_root;
+ if (count==0)
+ {
+ get_field(mem_root,find_fields[help_topic_name].field, name);
+ get_field(mem_root,find_fields[help_topic_description].field, description);
+ get_field(mem_root,find_fields[help_topic_example].field, example);
+ }
+ else
+ {
+ if (count == 1)
+ names->push_back(name);
+ String *new_name= new (thd->mem_root) String;
+ get_field(mem_root,find_fields[help_topic_name].field,new_name);
+ names->push_back(new_name);
+ }
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Look for topics by mask
+
+ SYNOPSIS
+ search_topics()
+ thd Thread handler
+ topics Table of topics
+ find_fields Filled array of info for fields
+ select Function to test for matching help topic.
+ Normally 'help_topic.name like 'bit%'
+
+ RETURN VALUES
+ # number of topics found
+
+ names array of names of found topics (out)
+ name name of found topic (out)
+ description description of found topic (out)
+ example example for found topic (out)
+
+ NOTE
+ Field 'names' is set only if more than one topic was found.
+ Fields 'name', 'description', 'example' are set only if
+ exactly one topic was found.
+
+*/
+
+int search_topics(THD *thd, TABLE *topics, struct st_find_field *find_fields,
+ SQL_SELECT *select, List<String> *names,
+ String *name, String *description, String *example)
+{
+ int count= 0;
+ READ_RECORD read_record_info;
+ DBUG_ENTER("search_topics");
+
+ /* Should never happen. As this is part of help, we can ignore this */
+ if (init_read_record(&read_record_info, thd, topics, select, 1, 0, FALSE))
+ DBUG_RETURN(0);
+
+ while (!read_record_info.read_record(&read_record_info))
+ {
+ if (!select->cond->val_int()) // Doesn't match like
+ continue;
+ memorize_variant_topic(thd,topics,count,find_fields,
+ names,name,description,example);
+ count++;
+ }
+ end_read_record(&read_record_info);
+
+ DBUG_RETURN(count);
+}
+
+/*
+ Look for keyword by mask
+
+ SYNOPSIS
+ search_keyword()
+ thd Thread handler
+ keywords Table of keywords
+ find_fields Filled array of info for fields
+ select Function to test for matching keyword.
+ Normally 'help_keyword.name like 'bit%'
+
+ key_id help_keyword_if of found topics (out)
+
+ RETURN VALUES
+ 0 didn't find any topics matching the mask
+ 1 found exactly one topic matching the mask
+ 2 found more then one topic matching the mask
+*/
+
+int search_keyword(THD *thd, TABLE *keywords, struct st_find_field *find_fields,
+ SQL_SELECT *select, int *key_id)
+{
+ int count= 0;
+ READ_RECORD read_record_info;
+ DBUG_ENTER("search_keyword");
+ /* Should never happen. As this is part of help, we can ignore this */
+ if (init_read_record(&read_record_info, thd, keywords, select, 1, 0, FALSE))
+ DBUG_RETURN(0);
+
+ while (!read_record_info.read_record(&read_record_info) && count<2)
+ {
+ if (!select->cond->val_int()) // Dosn't match like
+ continue;
+
+ *key_id= (int)find_fields[help_keyword_help_keyword_id].field->val_int();
+
+ count++;
+ }
+ end_read_record(&read_record_info);
+
+ DBUG_RETURN(count);
+}
+
+/*
+ Look for all topics with keyword
+
+ SYNOPSIS
+ get_topics_for_keyword()
+ thd Thread handler
+ topics Table of topics
+ relations Table of m:m relation "topic/keyword"
+ find_fields Filled array of info for fields
+ key_id Primary index to use to find for keyword
+
+ RETURN VALUES
+ # number of topics found
+
+ names array of name of found topics (out)
+
+ name name of found topic (out)
+ description description of found topic (out)
+ example example for found topic (out)
+
+ NOTE
+ Field 'names' is set only if more than one topic was found.
+ Fields 'name', 'description', 'example' are set only if
+ exactly one topic was found.
+*/
+
+int get_topics_for_keyword(THD *thd, TABLE *topics, TABLE *relations,
+ struct st_find_field *find_fields, int16 key_id,
+ List<String> *names,
+ String *name, String *description, String *example)
+{
+ uchar buff[8]; // Max int length
+ int count= 0;
+ int iindex_topic, iindex_relations;
+ Field *rtopic_id, *rkey_id;
+ DBUG_ENTER("get_topics_for_keyword");
+
+ if ((iindex_topic=
+ find_type(primary_key_name, &topics->s->keynames,
+ FIND_TYPE_NO_PREFIX) - 1) < 0 ||
+ (iindex_relations=
+ find_type(primary_key_name, &relations->s->keynames,
+ FIND_TYPE_NO_PREFIX) - 1) < 0)
+ {
+ my_message(ER_CORRUPT_HELP_DB, ER(ER_CORRUPT_HELP_DB), MYF(0));
+ DBUG_RETURN(-1);
+ }
+ rtopic_id= find_fields[help_relation_help_topic_id].field;
+ rkey_id= find_fields[help_relation_help_keyword_id].field;
+
+ if (topics->file->ha_index_init(iindex_topic,1) ||
+ relations->file->ha_index_init(iindex_relations,1))
+ {
+ if (topics->file->inited)
+ topics->file->ha_index_end();
+ my_message(ER_CORRUPT_HELP_DB, ER(ER_CORRUPT_HELP_DB), MYF(0));
+ DBUG_RETURN(-1);
+ }
+
+ rkey_id->store((longlong) key_id, TRUE);
+ rkey_id->get_key_image(buff, rkey_id->pack_length(), Field::itRAW);
+ int key_res= relations->file->ha_index_read_map(relations->record[0],
+ buff, (key_part_map) 1,
+ HA_READ_KEY_EXACT);
+
+ for ( ;
+ !key_res && key_id == (int16) rkey_id->val_int() ;
+ key_res= relations->file->ha_index_next(relations->record[0]))
+ {
+ uchar topic_id_buff[8];
+ longlong topic_id= rtopic_id->val_int();
+ Field *field= find_fields[help_topic_help_topic_id].field;
+ field->store((longlong) topic_id, TRUE);
+ field->get_key_image(topic_id_buff, field->pack_length(), Field::itRAW);
+
+ if (!topics->file->ha_index_read_map(topics->record[0], topic_id_buff,
+ (key_part_map)1, HA_READ_KEY_EXACT))
+ {
+ memorize_variant_topic(thd,topics,count,find_fields,
+ names,name,description,example);
+ count++;
+ }
+ }
+ topics->file->ha_index_end();
+ relations->file->ha_index_end();
+ DBUG_RETURN(count);
+}
+
+/*
+ Look for categories by mask
+
+ SYNOPSIS
+ search_categories()
+ thd THD for init_read_record
+ categories Table of categories
+ find_fields Filled array of info for fields
+ select Function to test for if matching help topic.
+ Normally 'help_vategory.name like 'bit%'
+ names List of found categories names (out)
+ res_id Primary index of found category (only if
+ found exactly one category)
+
+ RETURN VALUES
+ # Number of categories found
+*/
+
+int search_categories(THD *thd, TABLE *categories,
+ struct st_find_field *find_fields,
+ SQL_SELECT *select, List<String> *names, int16 *res_id)
+{
+ Field *pfname= find_fields[help_category_name].field;
+ Field *pcat_id= find_fields[help_category_help_category_id].field;
+ int count= 0;
+ READ_RECORD read_record_info;
+ DBUG_ENTER("search_categories");
+
+ /* Should never happen. As this is part of help, we can ignore this */
+ if (init_read_record(&read_record_info, thd, categories, select,1,0,FALSE))
+ DBUG_RETURN(0);
+ while (!read_record_info.read_record(&read_record_info))
+ {
+ if (select && !select->cond->val_int())
+ continue;
+ String *lname= new (thd->mem_root) String;
+ get_field(thd->mem_root,pfname,lname);
+ if (++count == 1 && res_id)
+ *res_id= (int16) pcat_id->val_int();
+ names->push_back(lname);
+ }
+ end_read_record(&read_record_info);
+
+ DBUG_RETURN(count);
+}
+
+/*
+ Look for all topics or subcategories of category
+
+ SYNOPSIS
+ get_all_items_for_category()
+ thd Thread handler
+ items Table of items
+ pfname Field "name" in items
+ select "where" part of query..
+ res list of finded names
+*/
+
+void get_all_items_for_category(THD *thd, TABLE *items, Field *pfname,
+ SQL_SELECT *select, List<String> *res)
+{
+ READ_RECORD read_record_info;
+ DBUG_ENTER("get_all_items_for_category");
+
+ /* Should never happen. As this is part of help, we can ignore this */
+ if (init_read_record(&read_record_info, thd, items, select,1,0,FALSE))
+ DBUG_VOID_RETURN;
+
+ while (!read_record_info.read_record(&read_record_info))
+ {
+ if (!select->cond->val_int())
+ continue;
+ String *name= new (thd->mem_root) String();
+ get_field(thd->mem_root,pfname,name);
+ res->push_back(name);
+ }
+ end_read_record(&read_record_info);
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Send to client answer for help request
+
+ SYNOPSIS
+ send_answer_1()
+ protocol - protocol for sending
+ s1 - value of column "Name"
+ s2 - value of column "Description"
+ s3 - value of column "Example"
+
+ IMPLEMENTATION
+ Format used:
+ +----------+------------+------------+
+ |name |description |example |
+ +----------+------------+------------+
+ |String(64)|String(1000)|String(1000)|
+ +----------+------------+------------+
+ with exactly one row!
+
+ RETURN VALUES
+ 1 Writing of head failed
+ -1 Writing of row failed
+ 0 Successeful send
+*/
+
+int send_answer_1(Protocol *protocol, String *s1, String *s2, String *s3)
+{
+ DBUG_ENTER("send_answer_1");
+ List<Item> field_list;
+ field_list.push_back(new Item_empty_string("name",64));
+ field_list.push_back(new Item_empty_string("description",1000));
+ field_list.push_back(new Item_empty_string("example",1000));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(1);
+
+ protocol->prepare_for_resend();
+ protocol->store(s1);
+ protocol->store(s2);
+ protocol->store(s3);
+ if (protocol->write())
+ DBUG_RETURN(-1);
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Send to client help header
+
+ SYNOPSIS
+ send_header_2()
+ protocol - protocol for sending
+ is_it_category - need column 'source_category_name'
+
+ IMPLEMENTATION
+ +- -+
+ |+-------------------- | +----------+--------------+
+ ||source_category_name | |name |is_it_category|
+ |+-------------------- | +----------+--------------+
+ ||String(64) | |String(64)|String(1) |
+ |+-------------------- | +----------+--------------+
+ +- -+
+
+ RETURN VALUES
+ result of protocol->send_result_set_metadata
+*/
+
+int send_header_2(Protocol *protocol, bool for_category)
+{
+ DBUG_ENTER("send_header_2");
+ List<Item> field_list;
+ if (for_category)
+ field_list.push_back(new Item_empty_string("source_category_name",64));
+ field_list.push_back(new Item_empty_string("name",64));
+ field_list.push_back(new Item_empty_string("is_it_category",1));
+ DBUG_RETURN(protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS |
+ Protocol::SEND_EOF));
+}
+
+/*
+ strcmp for using in qsort
+
+ SYNOPSIS
+ strptrcmp()
+ ptr1 (const void*)&str1
+ ptr2 (const void*)&str2
+
+ RETURN VALUES
+ same as strcmp
+*/
+
+extern "C" int string_ptr_cmp(const void* ptr1, const void* ptr2)
+{
+ String *str1= *(String**)ptr1;
+ String *str2= *(String**)ptr2;
+ return strcmp(str1->c_ptr(),str2->c_ptr());
+}
+
+/*
+ Send to client rows in format:
+ column1 : <name>
+ column2 : <is_it_category>
+
+ SYNOPSIS
+ send_variant_2_list()
+ protocol Protocol for sending
+ names List of names
+ cat Value of the column <is_it_category>
+ source_name name of category for all items..
+
+ RETURN VALUES
+ -1 Writing fail
+ 0 Data was successefully send
+*/
+
+int send_variant_2_list(MEM_ROOT *mem_root, Protocol *protocol,
+ List<String> *names,
+ const char *cat, String *source_name)
+{
+ DBUG_ENTER("send_variant_2_list");
+
+ String **pointers= (String**)alloc_root(mem_root,
+ sizeof(String*)*names->elements);
+ String **pos;
+ String **end= pointers + names->elements;
+
+ List_iterator<String> it(*names);
+ for (pos= pointers; pos!=end; (*pos++= it++))
+ ;
+
+ my_qsort(pointers,names->elements,sizeof(String*),string_ptr_cmp);
+
+ for (pos= pointers; pos!=end; pos++)
+ {
+ protocol->prepare_for_resend();
+ if (source_name)
+ protocol->store(source_name);
+ protocol->store(*pos);
+ protocol->store(cat,1,&my_charset_latin1);
+ if (protocol->write())
+ DBUG_RETURN(-1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Prepare simple SQL_SELECT table.* WHERE <Item>
+
+ SYNOPSIS
+ prepare_simple_select()
+ thd Thread handler
+ cond WHERE part of select
+ table goal table
+
+ error code of error (out)
+
+ RETURN VALUES
+ # created SQL_SELECT
+*/
+
+SQL_SELECT *prepare_simple_select(THD *thd, Item *cond,
+ TABLE *table, int *error)
+{
+ if (!cond->fixed)
+ cond->fix_fields(thd, &cond); // can never fail
+
+ /* Assume that no indexes cover all required fields */
+ table->covering_keys.clear_all();
+
+ SQL_SELECT *res= make_select(table, 0, 0, cond, 0, error);
+ if (*error || (res && res->check_quick(thd, 0, HA_POS_ERROR)) ||
+ (res && res->quick && res->quick->reset()))
+ {
+ delete res;
+ res=0;
+ }
+ return res;
+}
+
+/*
+ Prepare simple SQL_SELECT table.* WHERE table.name LIKE mask
+
+ SYNOPSIS
+ prepare_select_for_name()
+ thd Thread handler
+ mask mask for compare with name
+ mlen length of mask
+ tables list of tables, used in WHERE
+ table goal table
+ pfname field "name" in table
+
+ error code of error (out)
+
+ RETURN VALUES
+ # created SQL_SELECT
+*/
+
+SQL_SELECT *prepare_select_for_name(THD *thd, const char *mask, uint mlen,
+ TABLE_LIST *tables, TABLE *table,
+ Field *pfname, int *error)
+{
+ Item *cond= new Item_func_like(new Item_field(pfname),
+ new Item_string(mask,mlen,pfname->charset()),
+ new Item_string_ascii("\\"),
+ FALSE);
+ if (thd->is_fatal_error)
+ return 0; // OOM
+ return prepare_simple_select(thd, cond, table, error);
+}
+
+
+/*
+ Server-side function 'help'
+
+ SYNOPSIS
+ mysqld_help()
+ thd Thread handler
+
+ RETURN VALUES
+ FALSE Success
+ TRUE Error and send_error already commited
+*/
+
+bool mysqld_help(THD *thd, const char *mask)
+{
+ Protocol *protocol= thd->protocol;
+ SQL_SELECT *select;
+ st_find_field used_fields[array_elements(init_used_fields)];
+ List<TABLE_LIST> leaves;
+ TABLE_LIST tables[4];
+ List<String> topics_list, categories_list, subcategories_list;
+ String name, description, example;
+ int count_topics, count_categories, error;
+ uint mlen= strlen(mask);
+ size_t i;
+ MEM_ROOT *mem_root= thd->mem_root;
+ DBUG_ENTER("mysqld_help");
+
+ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("help_topic"),
+ "help_topic", TL_READ);
+ tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("help_category"),
+ "help_category", TL_READ);
+ tables[2].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("help_relation"),
+ "help_relation", TL_READ);
+ tables[3].init_one_table(C_STRING_WITH_LEN("mysql"),
+ C_STRING_WITH_LEN("help_keyword"),
+ "help_keyword", TL_READ);
+ tables[0].next_global= tables[0].next_local=
+ tables[0].next_name_resolution_table= &tables[1];
+ tables[1].next_global= tables[1].next_local=
+ tables[1].next_name_resolution_table= &tables[2];
+ tables[2].next_global= tables[2].next_local=
+ tables[2].next_name_resolution_table= &tables[3];
+
+ /*
+ HELP must be available under LOCK TABLES.
+ Reset and backup the current open tables state to
+ make it possible.
+ */
+ Open_tables_backup open_tables_state_backup;
+ if (open_system_tables_for_read(thd, tables, &open_tables_state_backup))
+ goto error2;
+
+ /*
+ Init tables and fields to be usable from items
+ tables do not contain VIEWs => we can pass 0 as conds
+ */
+ thd->lex->select_lex.context.table_list=
+ thd->lex->select_lex.context.first_name_resolution_table= &tables[0];
+ if (setup_tables(thd, &thd->lex->select_lex.context,
+ &thd->lex->select_lex.top_join_list,
+ tables, leaves, FALSE, FALSE))
+ goto error;
+ memcpy((char*) used_fields, (char*) init_used_fields, sizeof(used_fields));
+ if (init_fields(thd, tables, used_fields, array_elements(used_fields)))
+ goto error;
+ for (i=0; i<sizeof(tables)/sizeof(TABLE_LIST); i++)
+ tables[i].table->file->init_table_handle_for_HANDLER();
+
+ if (!(select=
+ prepare_select_for_name(thd,mask,mlen,tables,tables[0].table,
+ used_fields[help_topic_name].field,&error)))
+ goto error;
+
+ count_topics= search_topics(thd,tables[0].table,used_fields,
+ select,&topics_list,
+ &name, &description, &example);
+ delete select;
+
+ if (count_topics == 0)
+ {
+ int UNINIT_VAR(key_id);
+ if (!(select=
+ prepare_select_for_name(thd,mask,mlen,tables,tables[3].table,
+ used_fields[help_keyword_name].field,
+ &error)))
+ goto error;
+
+ count_topics= search_keyword(thd,tables[3].table, used_fields, select,
+ &key_id);
+ delete select;
+ count_topics= (count_topics != 1) ? 0 :
+ get_topics_for_keyword(thd,tables[0].table,tables[2].table,
+ used_fields,key_id,&topics_list,&name,
+ &description,&example);
+ }
+
+ if (count_topics == 0)
+ {
+ int16 category_id;
+ Field *cat_cat_id= used_fields[help_category_parent_category_id].field;
+ if (!(select=
+ prepare_select_for_name(thd,mask,mlen,tables,tables[1].table,
+ used_fields[help_category_name].field,
+ &error)))
+ goto error;
+
+ count_categories= search_categories(thd, tables[1].table, used_fields,
+ select,
+ &categories_list,&category_id);
+ delete select;
+ if (!count_categories)
+ {
+ if (send_header_2(protocol,FALSE))
+ goto error;
+ }
+ else if (count_categories > 1)
+ {
+ if (send_header_2(protocol,FALSE) ||
+ send_variant_2_list(mem_root,protocol,&categories_list,"Y",0))
+ goto error;
+ }
+ else
+ {
+ Field *topic_cat_id= used_fields[help_topic_help_category_id].field;
+ Item *cond_topic_by_cat=
+ new Item_func_equal(new Item_field(topic_cat_id),
+ new Item_int((int32)category_id));
+ Item *cond_cat_by_cat=
+ new Item_func_equal(new Item_field(cat_cat_id),
+ new Item_int((int32)category_id));
+ if (!(select= prepare_simple_select(thd, cond_topic_by_cat,
+ tables[0].table, &error)))
+ goto error;
+ get_all_items_for_category(thd,tables[0].table,
+ used_fields[help_topic_name].field,
+ select,&topics_list);
+ delete select;
+ if (!(select= prepare_simple_select(thd, cond_cat_by_cat,
+ tables[1].table, &error)))
+ goto error;
+ get_all_items_for_category(thd,tables[1].table,
+ used_fields[help_category_name].field,
+ select,&subcategories_list);
+ delete select;
+ String *cat= categories_list.head();
+ if (send_header_2(protocol, TRUE) ||
+ send_variant_2_list(mem_root,protocol,&topics_list, "N",cat) ||
+ send_variant_2_list(mem_root,protocol,&subcategories_list,"Y",cat))
+ goto error;
+ }
+ }
+ else if (count_topics == 1)
+ {
+ if (send_answer_1(protocol,&name,&description,&example))
+ goto error;
+ }
+ else
+ {
+ /* First send header and functions */
+ if (send_header_2(protocol, FALSE) ||
+ send_variant_2_list(mem_root,protocol, &topics_list, "N", 0))
+ goto error;
+ if (!(select=
+ prepare_select_for_name(thd,mask,mlen,tables,tables[1].table,
+ used_fields[help_category_name].field,&error)))
+ goto error;
+ search_categories(thd, tables[1].table, used_fields,
+ select,&categories_list, 0);
+ delete select;
+ /* Then send categories */
+ if (send_variant_2_list(mem_root,protocol, &categories_list, "Y", 0))
+ goto error;
+ }
+ my_eof(thd);
+
+ close_system_tables(thd, &open_tables_state_backup);
+ DBUG_RETURN(FALSE);
+
+error:
+ close_system_tables(thd, &open_tables_state_backup);
+
+error2:
+ DBUG_RETURN(TRUE);
+}
+
diff --git a/sql/sql_help.h b/sql/sql_help.h
new file mode 100644
index 00000000000..b6ae490e757
--- /dev/null
+++ b/sql/sql_help.h
@@ -0,0 +1,28 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_HELP_INCLUDED
+#define SQL_HELP_INCLUDED
+
+class THD;
+
+
+/*
+ Function prototypes
+*/
+
+bool mysqld_help (THD *thd, const char *text);
+
+#endif /* SQL_HELP_INCLUDED */
diff --git a/sql/sql_hset.h b/sql/sql_hset.h
new file mode 100644
index 00000000000..dc3bd487ce5
--- /dev/null
+++ b/sql/sql_hset.h
@@ -0,0 +1,103 @@
+#ifndef SQL_HSET_INCLUDED
+#define SQL_HSET_INCLUDED
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+#include "my_global.h"
+#include "hash.h"
+
+
+/**
+ A type-safe wrapper around mysys HASH.
+*/
+
+template <typename T>
+class Hash_set
+{
+public:
+ enum { START_SIZE= 8 };
+ /**
+ Constructs an empty hash. Does not allocate memory, it is done upon
+ the first insert. Thus does not cause or return errors.
+ */
+ Hash_set(uchar *(*K)(const T *, size_t *, my_bool))
+ {
+ my_hash_clear(&m_hash);
+ m_hash.get_key= (my_hash_get_key)K;
+ }
+ /**
+ Destroy the hash by freeing the buckets table. Does
+ not call destructors for the elements.
+ */
+ ~Hash_set()
+ {
+ my_hash_free(&m_hash);
+ }
+ /**
+ Insert a single value into a hash. Does not tell whether
+ the value was inserted -- if an identical value existed,
+ it is not replaced.
+
+ @retval TRUE Out of memory.
+ @retval FALSE OK. The value either was inserted or existed
+ in the hash.
+ */
+ bool insert(T *value)
+ {
+ my_hash_init_opt(&m_hash, &my_charset_bin, START_SIZE, 0, 0,
+ m_hash.get_key, 0, MYF(0));
+ size_t key_len;
+ uchar *v= reinterpret_cast<uchar *>(value);
+ const uchar *key= m_hash.get_key(v, &key_len, FALSE);
+ if (find(key, key_len) == NULL)
+ return my_hash_insert(&m_hash, v);
+ return FALSE;
+ }
+ T *find(const void *key, size_t klen) const
+ {
+ return (T*)my_hash_search(&m_hash, reinterpret_cast<const uchar *>(key), klen);
+ }
+ /** Is this hash set empty? */
+ bool is_empty() const { return m_hash.records == 0; }
+ /** Returns the number of unique elements. */
+ size_t size() const { return static_cast<size_t>(m_hash.records); }
+ /** An iterator over hash elements. Is not insert-stable. */
+ class Iterator
+ {
+ public:
+ Iterator(Hash_set &hash_set)
+ : m_hash(&hash_set.m_hash),
+ m_idx(0)
+ {}
+ /**
+ Return the current element and reposition the iterator to the next
+ element.
+ */
+ inline T *operator++(int)
+ {
+ if (m_idx < m_hash->records)
+ return reinterpret_cast<T*>(my_hash_element(m_hash, m_idx++));
+ return NULL;
+ }
+ void rewind() { m_idx= 0; }
+ private:
+ HASH *m_hash;
+ uint m_idx;
+ };
+private:
+ HASH m_hash;
+};
+
+#endif // SQL_HSET_INCLUDED
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
new file mode 100644
index 00000000000..1ec33a0a0ac
--- /dev/null
+++ b/sql/sql_insert.cc
@@ -0,0 +1,4327 @@
+/*
+ Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2014, SkySQL Ab.
+
+ 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
+*/
+
+/* Insert of records */
+
+/*
+ INSERT DELAYED
+
+ Insert delayed is distinguished from a normal insert by lock_type ==
+ TL_WRITE_DELAYED instead of TL_WRITE. It first tries to open a
+ "delayed" table (delayed_get_table()), but falls back to
+ open_and_lock_tables() on error and proceeds as normal insert then.
+
+ Opening a "delayed" table means to find a delayed insert thread that
+ has the table open already. If this fails, a new thread is created and
+ waited for to open and lock the table.
+
+ If accessing the thread succeeded, in
+ Delayed_insert::get_local_table() the table of the thread is copied
+ for local use. A copy is required because the normal insert logic
+ works on a target table, but the other threads table object must not
+ be used. The insert logic uses the record buffer to create a record.
+ And the delayed insert thread uses the record buffer to pass the
+ record to the table handler. So there must be different objects. Also
+ the copied table is not included in the lock, so that the statement
+ can proceed even if the real table cannot be accessed at this moment.
+
+ Copying a table object is not a trivial operation. Besides the TABLE
+ object there are the field pointer array, the field objects and the
+ record buffer. After copying the field objects, their pointers into
+ the record must be "moved" to point to the new record buffer.
+
+ After this setup the normal insert logic is used. Only that for
+ delayed inserts write_delayed() is called instead of write_record().
+ It inserts the rows into a queue and signals the delayed insert thread
+ instead of writing directly to the table.
+
+ The delayed insert thread awakes from the signal. It locks the table,
+ inserts the rows from the queue, unlocks the table, and waits for the
+ next signal. It does normally live until a FLUSH TABLES or SHUTDOWN.
+
+*/
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_insert.h"
+#include "sql_update.h" // compare_record
+#include "sql_base.h" // close_thread_tables
+#include "sql_cache.h" // query_cache_*
+#include "key.h" // key_copy
+#include "lock.h" // mysql_unlock_tables
+#include "sp_head.h"
+#include "sql_view.h" // check_key_in_view, insert_view_fields
+#include "sql_table.h" // mysql_create_table_no_lock
+#include "sql_acl.h" // *_ACL, check_grant_all_columns
+#include "sql_trigger.h"
+#include "sql_select.h"
+#include "sql_show.h"
+#include "slave.h"
+#include "sql_parse.h" // end_active_trans
+#include "rpl_mi.h"
+#include "transaction.h"
+#include "sql_audit.h"
+#include "sql_derived.h" // mysql_handle_derived
+
+#include "debug_sync.h"
+
+#ifndef EMBEDDED_LIBRARY
+static bool delayed_get_table(THD *thd, MDL_request *grl_protection_request,
+ TABLE_LIST *table_list);
+static int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic,
+ LEX_STRING query, bool ignore, bool log_on);
+static void end_delayed_insert(THD *thd);
+pthread_handler_t handle_delayed_insert(void *arg);
+static void unlink_blobs(register TABLE *table);
+#endif
+static bool check_view_insertability(THD *thd, TABLE_LIST *view);
+
+/*
+ Check that insert/update fields are from the same single table of a view.
+
+ @param fields The insert/update fields to be checked.
+ @param values The insert/update values to be checked, NULL if
+ checking is not wanted.
+ @param view The view for insert.
+ @param map [in/out] The insert table map.
+
+ This function is called in 2 cases:
+ 1. to check insert fields. In this case *map will be set to 0.
+ Insert fields are checked to be all from the same single underlying
+ table of the given view. Otherwise the error is thrown. Found table
+ map is returned in the map parameter.
+ 2. to check update fields of the ON DUPLICATE KEY UPDATE clause.
+ In this case *map contains table_map found on the previous call of
+ the function to check insert fields. Update fields are checked to be
+ from the same table as the insert fields.
+
+ @returns false if success.
+*/
+
+bool check_view_single_update(List<Item> &fields, List<Item> *values,
+ TABLE_LIST *view, table_map *map,
+ bool insert)
+{
+ /* it is join view => we need to find the table for update */
+ List_iterator_fast<Item> it(fields);
+ Item *item;
+ TABLE_LIST *tbl= 0; // reset for call to check_single_table()
+ table_map tables= 0;
+
+ while ((item= it++))
+ tables|= item->used_tables();
+
+ if (values)
+ {
+ it.init(*values);
+ while ((item= it++))
+ tables|= item->view_used_tables(view);
+ }
+
+ /* Convert to real table bits */
+ tables&= ~PSEUDO_TABLE_BITS;
+
+ /* Check found map against provided map */
+ if (*map)
+ {
+ if (tables != *map)
+ goto error;
+ return FALSE;
+ }
+
+ if (view->check_single_table(&tbl, tables, view) || tbl == 0)
+ goto error;
+
+ /* view->table should have been set in mysql_derived_merge_for_insert */
+ DBUG_ASSERT(view->table);
+
+ /*
+ Use buffer for the insert values that was allocated for the merged view.
+ */
+ tbl->table->insert_values= view->table->insert_values;
+ view->table= tbl->table;
+ if (!tbl->single_table_updatable())
+ {
+ if (insert)
+ my_error(ER_NON_INSERTABLE_TABLE, MYF(0), view->alias, "INSERT");
+ else
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), view->alias, "UPDATE");
+ return TRUE;
+ }
+ *map= tables;
+
+ return FALSE;
+
+error:
+ my_error(ER_VIEW_MULTIUPDATE, MYF(0),
+ view->view_db.str, view->view_name.str);
+ return TRUE;
+}
+
+
+/*
+ Check if insert fields are correct.
+
+ @param thd The current thread.
+ @param table_list The table we are inserting into (may be view)
+ @param fields The insert fields.
+ @param values The insert values.
+ @param check_unique If duplicate values should be rejected.
+ @param fields_and_values_from_different_maps If 'values' are allowed to
+ refer to other tables than those of 'fields'
+ @param map See check_view_single_update
+
+ @returns 0 if success, -1 if error
+*/
+
+static int check_insert_fields(THD *thd, TABLE_LIST *table_list,
+ List<Item> &fields, List<Item> &values,
+ bool check_unique,
+ bool fields_and_values_from_different_maps,
+ table_map *map)
+{
+ TABLE *table= table_list->table;
+ DBUG_ENTER("check_insert_fields");
+
+ if (!table_list->single_table_updatable())
+ {
+ my_error(ER_NON_INSERTABLE_TABLE, MYF(0), table_list->alias, "INSERT");
+ DBUG_RETURN(-1);
+ }
+
+ if (fields.elements == 0 && values.elements != 0)
+ {
+ if (!table)
+ {
+ my_error(ER_VIEW_NO_INSERT_FIELD_LIST, MYF(0),
+ table_list->view_db.str, table_list->view_name.str);
+ DBUG_RETURN(-1);
+ }
+ if (values.elements != table->s->fields)
+ {
+ my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L);
+ DBUG_RETURN(-1);
+ }
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Field_iterator_table_ref field_it;
+ field_it.set(table_list);
+ if (check_grant_all_columns(thd, INSERT_ACL, &field_it))
+ DBUG_RETURN(-1);
+#endif
+ /*
+ No fields are provided so all fields must be provided in the values.
+ Thus we set all bits in the write set.
+ */
+ bitmap_set_all(table->write_set);
+ }
+ else
+ { // Part field list
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ Name_resolution_context *context= &select_lex->context;
+ Name_resolution_context_state ctx_state;
+ int res;
+
+ if (fields.elements != values.elements)
+ {
+ my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L);
+ DBUG_RETURN(-1);
+ }
+
+ thd->dup_field= 0;
+ select_lex->no_wrap_view_item= TRUE;
+
+ /* Save the state of the current name resolution context. */
+ ctx_state.save_state(context, table_list);
+
+ /*
+ Perform name resolution only in the first table - 'table_list',
+ which is the table that is inserted into.
+ */
+ table_list->next_local= 0;
+ context->resolve_in_table_list_only(table_list);
+ /* 'Unfix' fields to allow correct marking by the setup_fields function. */
+ if (table_list->is_view())
+ unfix_fields(fields);
+
+ res= setup_fields(thd, 0, fields, MARK_COLUMNS_WRITE, 0, 0);
+
+ /* Restore the current context. */
+ ctx_state.restore_state(context, table_list);
+ thd->lex->select_lex.no_wrap_view_item= FALSE;
+
+ if (res)
+ DBUG_RETURN(-1);
+
+ if (table_list->is_view() && table_list->is_merged_derived())
+ {
+ if (check_view_single_update(fields,
+ fields_and_values_from_different_maps ?
+ (List<Item>*) 0 : &values,
+ table_list, map, true))
+ DBUG_RETURN(-1);
+ table= table_list->table;
+ }
+
+ if (check_unique && thd->dup_field)
+ {
+ my_error(ER_FIELD_SPECIFIED_TWICE, MYF(0), thd->dup_field->field_name);
+ DBUG_RETURN(-1);
+ }
+ if (table->default_field)
+ table->mark_default_fields_for_write();
+ }
+ /* Mark virtual columns used in the insert statement */
+ if (table->vfield)
+ table->mark_virtual_columns_for_write(TRUE);
+ // For the values we need select_priv
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ table->grant.want_privilege= (SELECT_ACL & ~table->grant.privilege);
+#endif
+
+ if (check_key_in_view(thd, table_list) ||
+ (table_list->view &&
+ check_view_insertability(thd, table_list)))
+ {
+ my_error(ER_NON_INSERTABLE_TABLE, MYF(0), table_list->alias, "INSERT");
+ DBUG_RETURN(-1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Check if update fields are correct.
+
+ @param thd The current thread.
+ @param insert_table_list The table we are inserting into (may be view)
+ @param update_fields The update fields.
+ @param update_values The update values.
+ @param fields_and_values_from_different_maps If 'update_values' are allowed to
+ refer to other tables than those of 'update_fields'
+ @param map See check_view_single_update
+
+ @note
+ If the update fields include an autoinc field, set the
+ table->next_number_field_updated flag.
+
+ @returns 0 if success, -1 if error
+*/
+
+static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list,
+ List<Item> &update_fields,
+ List<Item> &update_values,
+ bool fields_and_values_from_different_maps,
+ table_map *map)
+{
+ TABLE *table= insert_table_list->table;
+ my_bool autoinc_mark;
+ LINT_INIT(autoinc_mark);
+
+ table->next_number_field_updated= FALSE;
+
+ if (table->found_next_number_field)
+ {
+ /*
+ Unmark the auto_increment field so that we can check if this is modified
+ by update_fields
+ */
+ autoinc_mark= bitmap_test_and_clear(table->write_set,
+ table->found_next_number_field->
+ field_index);
+ }
+
+ /* Check the fields we are going to modify */
+ if (setup_fields(thd, 0, update_fields, MARK_COLUMNS_WRITE, 0, 0))
+ return -1;
+
+ if (insert_table_list->is_view() &&
+ insert_table_list->is_merged_derived() &&
+ check_view_single_update(update_fields,
+ fields_and_values_from_different_maps ?
+ (List<Item>*) 0 : &update_values,
+ insert_table_list, map, false))
+ return -1;
+
+ if (table->default_field)
+ table->mark_default_fields_for_write();
+
+ if (table->found_next_number_field)
+ {
+ if (bitmap_is_set(table->write_set,
+ table->found_next_number_field->field_index))
+ table->next_number_field_updated= TRUE;
+
+ if (autoinc_mark)
+ bitmap_set_bit(table->write_set,
+ table->found_next_number_field->field_index);
+ }
+
+ return 0;
+}
+
+/**
+ Upgrade table-level lock of INSERT statement to TL_WRITE if
+ a more concurrent lock is infeasible for some reason. This is
+ necessary for engines without internal locking support (MyISAM).
+ An engine with internal locking implementation might later
+ downgrade the lock in handler::store_lock() method.
+*/
+
+static
+void upgrade_lock_type(THD *thd, thr_lock_type *lock_type,
+ enum_duplicates duplic)
+{
+ if (duplic == DUP_UPDATE ||
+ (duplic == DUP_REPLACE && *lock_type == TL_WRITE_CONCURRENT_INSERT))
+ {
+ *lock_type= TL_WRITE_DEFAULT;
+ return;
+ }
+
+ if (*lock_type == TL_WRITE_DELAYED)
+ {
+ /*
+ We do not use delayed threads if:
+ - we're running in the safe mode or skip-new mode -- the
+ feature is disabled in these modes
+ - we're executing this statement on a replication slave --
+ we need to ensure serial execution of queries on the
+ slave
+ - it is INSERT .. ON DUPLICATE KEY UPDATE - in this case the
+ insert cannot be concurrent
+ - this statement is directly or indirectly invoked from
+ a stored function or trigger (under pre-locking) - to
+ avoid deadlocks, since INSERT DELAYED involves a lock
+ upgrade (TL_WRITE_DELAYED -> TL_WRITE) which we should not
+ attempt while keeping other table level locks.
+ - this statement itself may require pre-locking.
+ We should upgrade the lock even though in most cases
+ delayed functionality may work. Unfortunately, we can't
+ easily identify whether the subject table is not used in
+ the statement indirectly via a stored function or trigger:
+ if it is used, that will lead to a deadlock between the
+ client connection and the delayed thread.
+ */
+ if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) ||
+ thd->variables.max_insert_delayed_threads == 0 ||
+ thd->locked_tables_mode > LTM_LOCK_TABLES ||
+ thd->lex->uses_stored_routines() /*||
+ thd->lex->describe*/)
+ {
+ *lock_type= TL_WRITE;
+ return;
+ }
+ if (thd->slave_thread)
+ {
+ /* Try concurrent insert */
+ *lock_type= (duplic == DUP_UPDATE || duplic == DUP_REPLACE) ?
+ TL_WRITE : TL_WRITE_CONCURRENT_INSERT;
+ return;
+ }
+
+ bool log_on= (thd->variables.option_bits & OPTION_BIN_LOG);
+ if (global_system_variables.binlog_format == BINLOG_FORMAT_STMT &&
+ log_on && mysql_bin_log.is_open())
+ {
+ /*
+ Statement-based binary logging does not work in this case, because:
+ a) two concurrent statements may have their rows intermixed in the
+ queue, leading to autoincrement replication problems on slave (because
+ the values generated used for one statement don't depend only on the
+ value generated for the first row of this statement, so are not
+ replicable)
+ b) if first row of the statement has an error the full statement is
+ not binlogged, while next rows of the statement may be inserted.
+ c) if first row succeeds, statement is binlogged immediately with a
+ zero error code (i.e. "no error"), if then second row fails, query
+ will fail on slave too and slave will stop (wrongly believing that the
+ master got no error).
+ So we fallback to non-delayed INSERT.
+ Note that to be fully correct, we should test the "binlog format which
+ the delayed thread is going to use for this row". But in the common case
+ where the global binlog format is not changed and the session binlog
+ format may be changed, that is equal to the global binlog format.
+ We test it without mutex for speed reasons (condition rarely true), and
+ in the common case (global not changed) it is as good as without mutex;
+ if global value is changed, anyway there is uncertainty as the delayed
+ thread may be old and use the before-the-change value.
+ */
+ *lock_type= TL_WRITE;
+ }
+ }
+}
+
+
+/**
+ Find or create a delayed insert thread for the first table in
+ the table list, then open and lock the remaining tables.
+ If a table can not be used with insert delayed, upgrade the lock
+ and open and lock all tables using the standard mechanism.
+
+ @param thd thread context
+ @param table_list list of "descriptors" for tables referenced
+ directly in statement SQL text.
+ The first element in the list corresponds to
+ the destination table for inserts, remaining
+ tables, if any, are usually tables referenced
+ by sub-queries in the right part of the
+ INSERT.
+
+ @return Status of the operation. In case of success 'table'
+ member of every table_list element points to an instance of
+ class TABLE.
+
+ @sa open_and_lock_tables for more information about MySQL table
+ level locking
+*/
+
+static
+bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
+{
+ MDL_request protection_request;
+ DBUG_ENTER("open_and_lock_for_insert_delayed");
+
+#ifndef EMBEDDED_LIBRARY
+ /* INSERT DELAYED is not allowed in a read only transaction. */
+ if (thd->tx_read_only)
+ {
+ my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ /*
+ In order for the deadlock detector to be able to find any deadlocks
+ caused by the handler thread waiting for GRL or this table, we acquire
+ protection against GRL (global IX metadata lock) and metadata lock on
+ table to being inserted into inside the connection thread.
+ If this goes ok, the tickets are cloned and added to the list of granted
+ locks held by the handler thread.
+ */
+ if (thd->global_read_lock.can_acquire_protection())
+ DBUG_RETURN(TRUE);
+
+ protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_STATEMENT);
+
+ if (thd->mdl_context.acquire_lock(&protection_request,
+ thd->variables.lock_wait_timeout))
+ DBUG_RETURN(TRUE);
+
+ if (thd->mdl_context.acquire_lock(&table_list->mdl_request,
+ thd->variables.lock_wait_timeout))
+ /*
+ If a lock can't be acquired, it makes no sense to try normal insert.
+ Therefore we just abort the statement.
+ */
+ DBUG_RETURN(TRUE);
+
+ bool error= FALSE;
+ if (delayed_get_table(thd, &protection_request, table_list))
+ error= TRUE;
+ else if (table_list->table)
+ {
+ /*
+ Open tables used for sub-selects or in stored functions, will also
+ cache these functions.
+ */
+ if (open_and_lock_tables(thd, table_list->next_global, TRUE, 0))
+ {
+ end_delayed_insert(thd);
+ error= TRUE;
+ }
+ else
+ {
+ /*
+ First table was not processed by open_and_lock_tables(),
+ we need to set updatability flag "by hand".
+ */
+ if (!table_list->derived && !table_list->view)
+ table_list->updatable= 1; // usual table
+ }
+ }
+
+ /*
+ We can't release protection against GRL and metadata lock on the table
+ being inserted into here. These locks might be required, for example,
+ because this INSERT DELAYED calls functions which may try to update
+ this or another tables (updating the same table is of course illegal,
+ but such an attempt can be discovered only later during statement
+ execution).
+ */
+
+ /*
+ Reset the ticket in case we end up having to use normal insert and
+ therefore will reopen the table and reacquire the metadata lock.
+ */
+ table_list->mdl_request.ticket= NULL;
+
+ if (error || table_list->table)
+ DBUG_RETURN(error);
+#endif
+ /*
+ * This is embedded library and we don't have auxiliary
+ threads OR
+ * a lock upgrade was requested inside delayed_get_table
+ because
+ - there are too many delayed insert threads OR
+ - the table has triggers.
+ Use a normal insert.
+ */
+ table_list->lock_type= TL_WRITE;
+ DBUG_RETURN(open_and_lock_tables(thd, table_list, TRUE, 0));
+}
+
+
+/**
+ Create a new query string for removing DELAYED keyword for
+ multi INSERT DEALAYED statement.
+
+ @param[in] thd Thread handler
+ @param[in] buf Query string
+
+ @return
+ 0 ok
+ 1 error
+*/
+static int
+create_insert_stmt_from_insert_delayed(THD *thd, String *buf)
+{
+ /* Make a copy of thd->query() and then remove the "DELAYED" keyword */
+ if (buf->append(thd->query()) ||
+ buf->replace(thd->lex->keyword_delayed_begin_offset,
+ thd->lex->keyword_delayed_end_offset -
+ thd->lex->keyword_delayed_begin_offset, 0))
+ return 1;
+ return 0;
+}
+
+
+static void save_insert_query_plan(THD* thd, TABLE_LIST *table_list)
+{
+ Explain_insert* explain= new Explain_insert;
+ explain->table_name.append(table_list->table->alias);
+
+ thd->lex->explain->add_insert_plan(explain);
+
+ /* See Update_plan::updating_a_view for details */
+ bool skip= MY_TEST(table_list->view);
+
+ /* Save subquery children */
+ for (SELECT_LEX_UNIT *unit= thd->lex->select_lex.first_inner_unit();
+ unit;
+ unit= unit->next_unit())
+ {
+ if (skip)
+ {
+ skip= false;
+ continue;
+ }
+ /*
+ Table elimination doesn't work for INSERTS, but let's still have this
+ here for consistency
+ */
+ if (!(unit->item && unit->item->eliminated))
+ explain->add_child(unit->first_select()->select_number);
+ }
+}
+
+
+/**
+ INSERT statement implementation
+
+ @note Like implementations of other DDL/DML in MySQL, this function
+ relies on the caller to close the thread tables. This is done in the
+ end of dispatch_command().
+*/
+
+bool mysql_insert(THD *thd,TABLE_LIST *table_list,
+ List<Item> &fields,
+ List<List_item> &values_list,
+ List<Item> &update_fields,
+ List<Item> &update_values,
+ enum_duplicates duplic,
+ bool ignore)
+{
+ bool retval= true;
+ int error, res;
+ bool transactional_table, joins_freed= FALSE;
+ bool changed;
+ const bool was_insert_delayed= (table_list->lock_type == TL_WRITE_DELAYED);
+ bool using_bulk_insert= 0;
+ uint value_count;
+ ulong counter = 1;
+ ulonglong id;
+ COPY_INFO info;
+ TABLE *table= 0;
+ List_iterator_fast<List_item> its(values_list);
+ List_item *values;
+ Name_resolution_context *context;
+ Name_resolution_context_state ctx_state;
+#ifndef EMBEDDED_LIBRARY
+ char *query= thd->query();
+ /*
+ log_on is about delayed inserts only.
+ By default, both logs are enabled (this won't cause problems if the server
+ runs without --log-bin).
+ */
+ bool log_on= (thd->variables.option_bits & OPTION_BIN_LOG);
+#endif
+ thr_lock_type lock_type;
+ Item *unused_conds= 0;
+ DBUG_ENTER("mysql_insert");
+
+ create_explain_query(thd->lex, thd->mem_root);
+ /*
+ Upgrade lock type if the requested lock is incompatible with
+ the current connection mode or table operation.
+ */
+ upgrade_lock_type(thd, &table_list->lock_type, duplic);
+
+ /*
+ We can't write-delayed into a table locked with LOCK TABLES:
+ this will lead to a deadlock, since the delayed thread will
+ never be able to get a lock on the table.
+ */
+ if (table_list->lock_type == TL_WRITE_DELAYED &&
+ thd->locked_tables_mode &&
+ find_locked_table(thd->open_tables, table_list->db,
+ table_list->table_name))
+ {
+ my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0),
+ table_list->table_name);
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ mark the table_list as a target for insert, to skip the DT/view prepare phase
+ for correct access rights checks
+ TODO: remove this hack
+ */
+ table_list->skip_prepare_derived= TRUE;
+
+ if (table_list->lock_type == TL_WRITE_DELAYED)
+ {
+ if (open_and_lock_for_insert_delayed(thd, table_list))
+ DBUG_RETURN(TRUE);
+ }
+ else
+ {
+ if (open_and_lock_tables(thd, table_list, TRUE, 0))
+ DBUG_RETURN(TRUE);
+ }
+
+ lock_type= table_list->lock_type;
+
+ THD_STAGE_INFO(thd, stage_init);
+ thd->lex->used_tables=0;
+ values= its++;
+ value_count= values->elements;
+
+ if (mysql_prepare_insert(thd, table_list, table, fields, values,
+ update_fields, update_values, duplic, &unused_conds,
+ FALSE,
+ (fields.elements || !value_count ||
+ table_list->view != 0),
+ !ignore && thd->is_strict_mode()))
+ goto abort;
+
+ /* mysql_prepare_insert set table_list->table if it was not set */
+ table= table_list->table;
+
+ context= &thd->lex->select_lex.context;
+ /*
+ These three asserts test the hypothesis that the resetting of the name
+ resolution context below is not necessary at all since the list of local
+ tables for INSERT always consists of one table.
+ */
+ DBUG_ASSERT(!table_list->next_local);
+ DBUG_ASSERT(!context->table_list->next_local);
+ DBUG_ASSERT(!context->first_name_resolution_table->next_name_resolution_table);
+
+ /* Save the state of the current name resolution context. */
+ ctx_state.save_state(context, table_list);
+
+ /*
+ Perform name resolution only in the first table - 'table_list',
+ which is the table that is inserted into.
+ */
+ table_list->next_local= 0;
+ context->resolve_in_table_list_only(table_list);
+
+ while ((values= its++))
+ {
+ counter++;
+ if (values->elements != value_count)
+ {
+ my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), counter);
+ goto abort;
+ }
+ if (setup_fields(thd, 0, *values, MARK_COLUMNS_READ, 0, 0))
+ goto abort;
+ }
+ its.rewind ();
+
+ /* Restore the current context. */
+ ctx_state.restore_state(context, table_list);
+
+ if (thd->lex->unit.first_select()->optimize_unflattened_subqueries(false))
+ {
+ goto abort;
+ }
+ save_insert_query_plan(thd, table_list);
+ if (thd->lex->describe)
+ {
+ retval= thd->lex->explain->send_explain(thd);
+ goto abort;
+ }
+
+ /*
+ Fill in the given fields and dump it to the table file
+ */
+ bzero((char*) &info,sizeof(info));
+ info.ignore= ignore;
+ info.handle_duplicates=duplic;
+ info.update_fields= &update_fields;
+ info.update_values= &update_values;
+ info.view= (table_list->view ? table_list : 0);
+
+ /*
+ Count warnings for all inserts.
+ For single line insert, generate an error if try to set a NOT NULL field
+ to NULL.
+ */
+ thd->count_cuted_fields= ((values_list.elements == 1 &&
+ !ignore) ?
+ CHECK_FIELD_ERROR_FOR_NULL :
+ CHECK_FIELD_WARN);
+ thd->cuted_fields = 0L;
+ table->next_number_field=table->found_next_number_field;
+
+#ifdef HAVE_REPLICATION
+ if (thd->rgi_slave &&
+ (info.handle_duplicates == DUP_UPDATE) &&
+ (table->next_number_field != NULL) &&
+ rpl_master_has_bug(thd->rgi_slave->rli, 24432, TRUE, NULL, NULL))
+ goto abort;
+#endif
+
+ error=0;
+ THD_STAGE_INFO(thd, stage_update);
+ if (duplic == DUP_REPLACE &&
+ (!table->triggers || !table->triggers->has_delete_triggers()))
+ table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ if (duplic == DUP_UPDATE)
+ table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE);
+ /*
+ let's *try* to start bulk inserts. It won't necessary
+ start them as values_list.elements should be greater than
+ some - handler dependent - threshold.
+ We should not start bulk inserts if this statement uses
+ functions or invokes triggers since they may access
+ to the same table and therefore should not see its
+ inconsistent state created by this optimization.
+ So we call start_bulk_insert to perform nesessary checks on
+ values_list.elements, and - if nothing else - to initialize
+ the code to make the call of end_bulk_insert() below safe.
+ */
+#ifndef EMBEDDED_LIBRARY
+ if (lock_type != TL_WRITE_DELAYED)
+#endif /* EMBEDDED_LIBRARY */
+ {
+ if (duplic != DUP_ERROR || ignore)
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ /**
+ This is a simple check for the case when the table has a trigger
+ that reads from it, or when the statement invokes a stored function
+ that reads from the table being inserted to.
+ Engines can't handle a bulk insert in parallel with a read form the
+ same table in the same connection.
+ */
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
+ values_list.elements > 1)
+ {
+ using_bulk_insert= 1;
+ table->file->ha_start_bulk_insert(values_list.elements);
+ }
+ }
+
+ thd->abort_on_warning= !ignore && thd->is_strict_mode();
+
+ table->prepare_triggers_for_insert_stmt_or_event();
+ table->mark_columns_needed_for_insert();
+
+
+ if (table_list->prepare_where(thd, 0, TRUE) ||
+ table_list->prepare_check_option(thd))
+ error= 1;
+
+ table->reset_default_fields();
+
+ while ((values= its++))
+ {
+ if (fields.elements || !value_count)
+ {
+ restore_record(table,s->default_values); // Get empty record
+ if (fill_record_n_invoke_before_triggers(thd, table, fields, *values, 0,
+ TRG_EVENT_INSERT))
+ {
+ if (values_list.elements != 1 && ! thd->is_error())
+ {
+ info.records++;
+ continue;
+ }
+ /*
+ TODO: set thd->abort_on_warning if values_list.elements == 1
+ and check that all items return warning in case of problem with
+ storing field.
+ */
+ error=1;
+ break;
+ }
+ }
+ else
+ {
+ if (thd->lex->used_tables) // Column used in values()
+ restore_record(table,s->default_values); // Get empty record
+ else
+ {
+ TABLE_SHARE *share= table->s;
+
+ /*
+ Fix delete marker. No need to restore rest of record since it will
+ be overwritten by fill_record() anyway (and fill_record() does not
+ use default values in this case).
+ */
+#ifdef HAVE_valgrind
+ if (table->file->ha_table_flags() && HA_RECORD_MUST_BE_CLEAN_ON_WRITE)
+ restore_record(table,s->default_values); // Get empty record
+ else
+#endif
+ table->record[0][0]= share->default_values[0];
+
+ /* Fix undefined null_bits. */
+ if (share->null_bytes > 1 && share->last_null_bit_pos)
+ {
+ table->record[0][share->null_bytes - 1]=
+ share->default_values[share->null_bytes - 1];
+ }
+ }
+ if (fill_record_n_invoke_before_triggers(thd, table, table->field, *values, 0,
+ TRG_EVENT_INSERT))
+ {
+ if (values_list.elements != 1 && ! thd->is_error())
+ {
+ info.records++;
+ continue;
+ }
+ error=1;
+ break;
+ }
+ }
+ if (table->default_field && table->update_default_fields())
+ {
+ error= 1;
+ break;
+ }
+
+ if ((res= table_list->view_check_option(thd,
+ (values_list.elements == 1 ?
+ 0 :
+ ignore))) ==
+ VIEW_CHECK_SKIP)
+ continue;
+ else if (res == VIEW_CHECK_ERROR)
+ {
+ error= 1;
+ break;
+ }
+#ifndef EMBEDDED_LIBRARY
+ if (lock_type == TL_WRITE_DELAYED)
+ {
+ LEX_STRING const st_query = { query, thd->query_length() };
+ DEBUG_SYNC(thd, "before_write_delayed");
+ error=write_delayed(thd, table, duplic, st_query, ignore, log_on);
+ DEBUG_SYNC(thd, "after_write_delayed");
+ query=0;
+ }
+ else
+#endif
+ error=write_record(thd, table ,&info);
+ if (error)
+ break;
+ thd->get_stmt_da()->inc_current_row_for_warning();
+ }
+
+ free_underlaid_joins(thd, &thd->lex->select_lex);
+ joins_freed= TRUE;
+
+ /*
+ Now all rows are inserted. Time to update logs and sends response to
+ user
+ */
+#ifndef EMBEDDED_LIBRARY
+ if (lock_type == TL_WRITE_DELAYED)
+ {
+ if (!error)
+ {
+ info.copied=values_list.elements;
+ end_delayed_insert(thd);
+ }
+ }
+ else
+#endif
+ {
+ /*
+ Do not do this release if this is a delayed insert, it would steal
+ auto_inc values from the delayed_insert thread as they share TABLE.
+ */
+ table->file->ha_release_auto_increment();
+ if (using_bulk_insert && table->file->ha_end_bulk_insert() && !error)
+ {
+ table->file->print_error(my_errno,MYF(0));
+ error=1;
+ }
+ if (duplic != DUP_ERROR || ignore)
+ table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+
+ transactional_table= table->file->has_transactions();
+
+ if ((changed= (info.copied || info.deleted || info.updated)))
+ {
+ /*
+ Invalidate the table in the query cache if something changed.
+ For the transactional algorithm to work the invalidation must be
+ before binlog writing and ha_autocommit_or_rollback
+ */
+ query_cache_invalidate3(thd, table_list, 1);
+ }
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ if (error <= 0 ||
+ thd->transaction.stmt.modified_non_trans_table ||
+ was_insert_delayed)
+ {
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= 0;
+ if (error <= 0)
+ {
+ /*
+ [Guilhem wrote] Temporary errors may have filled
+ thd->net.last_error/errno. For example if there has
+ been a disk full error when writing the row, and it was
+ MyISAM, then thd->net.last_error/errno will be set to
+ "disk full"... and the mysql_file_pwrite() will wait until free
+ space appears, and so when it finishes then the
+ write_row() was entirely successful
+ */
+ /* todo: consider removing */
+ thd->clear_error();
+ }
+ else
+ errcode= query_error_code(thd, thd->killed == NOT_KILLED);
+
+ /* bug#22725:
+
+ A query which per-row-loop can not be interrupted with
+ KILLED, like INSERT, and that does not invoke stored
+ routines can be binlogged with neglecting the KILLED error.
+
+ If there was no error (error == zero) until after the end of
+ inserting loop the KILLED flag that appeared later can be
+ disregarded since previously possible invocation of stored
+ routines did not result in any error due to the KILLED. In
+ such case the flag is ignored for constructing binlog event.
+ */
+ DBUG_ASSERT(thd->killed != KILL_BAD_DATA || error > 0);
+ if (was_insert_delayed && table_list->lock_type == TL_WRITE)
+ {
+ /* Binlog INSERT DELAYED as INSERT without DELAYED. */
+ String log_query;
+ if (create_insert_stmt_from_insert_delayed(thd, &log_query))
+ {
+ sql_print_error("Event Error: An error occurred while creating query string"
+ "for INSERT DELAYED stmt, before writing it into binary log.");
+
+ error= 1;
+ }
+ else if (thd->binlog_query(THD::ROW_QUERY_TYPE,
+ log_query.c_ptr(), log_query.length(),
+ transactional_table, FALSE, FALSE,
+ errcode))
+ error= 1;
+ }
+ else if (thd->binlog_query(THD::ROW_QUERY_TYPE,
+ thd->query(), thd->query_length(),
+ transactional_table, FALSE, FALSE,
+ errcode))
+ error= 1;
+ }
+ }
+ DBUG_ASSERT(transactional_table || !changed ||
+ thd->transaction.stmt.modified_non_trans_table);
+ }
+ THD_STAGE_INFO(thd, stage_end);
+ /*
+ We'll report to the client this id:
+ - if the table contains an autoincrement column and we successfully
+ inserted an autogenerated value, the autogenerated value.
+ - if the table contains no autoincrement column and LAST_INSERT_ID(X) was
+ called, X.
+ - if the table contains an autoincrement column, and some rows were
+ inserted, the id of the last "inserted" row (if IGNORE, that value may not
+ have been really inserted but ignored).
+ */
+ id= (thd->first_successful_insert_id_in_cur_stmt > 0) ?
+ thd->first_successful_insert_id_in_cur_stmt :
+ (thd->arg_of_last_insert_id_function ?
+ thd->first_successful_insert_id_in_prev_stmt :
+ ((table->next_number_field && info.copied) ?
+ table->next_number_field->val_int() : 0));
+ table->next_number_field=0;
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+ table->auto_increment_field_not_null= FALSE;
+ if (duplic == DUP_REPLACE &&
+ (!table->triggers || !table->triggers->has_delete_triggers()))
+ table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+
+ if (error)
+ goto abort;
+ if (values_list.elements == 1 && (!(thd->variables.option_bits & OPTION_WARNINGS) ||
+ !thd->cuted_fields))
+ {
+ my_ok(thd, info.copied + info.deleted +
+ ((thd->client_capabilities & CLIENT_FOUND_ROWS) ?
+ info.touched : info.updated),
+ id);
+ }
+ else
+ {
+ char buff[160];
+ ha_rows updated=((thd->client_capabilities & CLIENT_FOUND_ROWS) ?
+ info.touched : info.updated);
+ if (ignore)
+ sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records,
+ (lock_type == TL_WRITE_DELAYED) ? (ulong) 0 :
+ (ulong) (info.records - info.copied),
+ (long) thd->get_stmt_da()->current_statement_warn_count());
+ else
+ sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records,
+ (ulong) (info.deleted + updated),
+ (long) thd->get_stmt_da()->current_statement_warn_count());
+ ::my_ok(thd, info.copied + info.deleted + updated, id, buff);
+ }
+ thd->abort_on_warning= 0;
+ if (thd->lex->current_select->first_cond_optimization)
+ {
+ thd->lex->current_select->save_leaf_tables(thd);
+ thd->lex->current_select->first_cond_optimization= 0;
+ }
+
+ DBUG_RETURN(FALSE);
+
+abort:
+#ifndef EMBEDDED_LIBRARY
+ if (lock_type == TL_WRITE_DELAYED)
+ end_delayed_insert(thd);
+#endif
+ if (table != NULL)
+ table->file->ha_release_auto_increment();
+
+ if (!joins_freed)
+ free_underlaid_joins(thd, &thd->lex->select_lex);
+ thd->abort_on_warning= 0;
+ DBUG_RETURN(retval);
+}
+
+
+/*
+ Additional check for insertability for VIEW
+
+ SYNOPSIS
+ check_view_insertability()
+ thd - thread handler
+ view - reference on VIEW
+
+ IMPLEMENTATION
+ A view is insertable if the folloings are true:
+ - All columns in the view are columns from a table
+ - All not used columns in table have a default values
+ - All field in view are unique (not referring to the same column)
+
+ RETURN
+ FALSE - OK
+ view->contain_auto_increment is 1 if and only if the view contains an
+ auto_increment field
+
+ TRUE - can't be used for insert
+*/
+
+static bool check_view_insertability(THD * thd, TABLE_LIST *view)
+{
+ uint num= view->view->select_lex.item_list.elements;
+ TABLE *table= view->table;
+ Field_translator *trans_start= view->field_translation,
+ *trans_end= trans_start + num;
+ Field_translator *trans;
+ uint used_fields_buff_size= bitmap_buffer_size(table->s->fields);
+ uint32 *used_fields_buff= (uint32*)thd->alloc(used_fields_buff_size);
+ MY_BITMAP used_fields;
+ enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
+ DBUG_ENTER("check_key_in_view");
+
+ if (!used_fields_buff)
+ DBUG_RETURN(TRUE); // EOM
+
+ DBUG_ASSERT(view->table != 0 && view->field_translation != 0);
+
+ (void) my_bitmap_init(&used_fields, used_fields_buff, table->s->fields, 0);
+ bitmap_clear_all(&used_fields);
+
+ view->contain_auto_increment= 0;
+ /*
+ we must not set query_id for fields as they're not
+ really used in this context
+ */
+ thd->mark_used_columns= MARK_COLUMNS_NONE;
+ /* check simplicity and prepare unique test of view */
+ for (trans= trans_start; trans != trans_end; trans++)
+ {
+ if (!trans->item->fixed && trans->item->fix_fields(thd, &trans->item))
+ {
+ thd->mark_used_columns= save_mark_used_columns;
+ DBUG_RETURN(TRUE);
+ }
+ Item_field *field;
+ /* simple SELECT list entry (field without expression) */
+ if (!(field= trans->item->field_for_view_update()))
+ {
+ thd->mark_used_columns= save_mark_used_columns;
+ DBUG_RETURN(TRUE);
+ }
+ if (field->field->unireg_check == Field::NEXT_NUMBER)
+ view->contain_auto_increment= 1;
+ /* prepare unique test */
+ /*
+ remove collation (or other transparent for update function) if we have
+ it
+ */
+ trans->item= field;
+ }
+ thd->mark_used_columns= save_mark_used_columns;
+ /* unique test */
+ for (trans= trans_start; trans != trans_end; trans++)
+ {
+ /* Thanks to test above, we know that all columns are of type Item_field */
+ Item_field *field= (Item_field *)trans->item;
+ /* check fields belong to table in which we are inserting */
+ if (field->field->table == table &&
+ bitmap_fast_test_and_set(&used_fields, field->field->field_index))
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Check if table can be updated
+
+ SYNOPSIS
+ mysql_prepare_insert_check_table()
+ thd Thread handle
+ table_list Table list
+ fields List of fields to be updated
+ where Pointer to where clause
+ select_insert Check is making for SELECT ... INSERT
+
+ RETURN
+ FALSE ok
+ TRUE ERROR
+*/
+
+static bool mysql_prepare_insert_check_table(THD *thd, TABLE_LIST *table_list,
+ List<Item> &fields,
+ bool select_insert)
+{
+ bool insert_into_view= (table_list->view != 0);
+ DBUG_ENTER("mysql_prepare_insert_check_table");
+
+ if (!table_list->single_table_updatable())
+ {
+ my_error(ER_NON_INSERTABLE_TABLE, MYF(0), table_list->alias, "INSERT");
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ first table in list is the one we'll INSERT into, requires INSERT_ACL.
+ all others require SELECT_ACL only. the ACL requirement below is for
+ new leaves only anyway (view-constituents), so check for SELECT rather
+ than INSERT.
+ */
+
+ if (setup_tables_and_check_access(thd, &thd->lex->select_lex.context,
+ &thd->lex->select_lex.top_join_list,
+ table_list,
+ thd->lex->select_lex.leaf_tables,
+ select_insert, INSERT_ACL, SELECT_ACL,
+ TRUE))
+ DBUG_RETURN(TRUE);
+
+ if (insert_into_view && !fields.elements)
+ {
+ thd->lex->empty_field_list_on_rset= 1;
+ if (!thd->lex->select_lex.leaf_tables.head()->table ||
+ table_list->is_multitable())
+ {
+ my_error(ER_VIEW_NO_INSERT_FIELD_LIST, MYF(0),
+ table_list->view_db.str, table_list->view_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(insert_view_fields(thd, &fields, table_list));
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Get extra info for tables we insert into
+
+ @param table table(TABLE object) we insert into,
+ might be NULL in case of view
+ @param table(TABLE_LIST object) or view we insert into
+*/
+
+static void prepare_for_positional_update(TABLE *table, TABLE_LIST *tables)
+{
+ if (table)
+ {
+ if(table->reginfo.lock_type != TL_WRITE_DELAYED)
+ table->prepare_for_position();
+ return;
+ }
+
+ DBUG_ASSERT(tables->view);
+ List_iterator<TABLE_LIST> it(*tables->view_tables);
+ TABLE_LIST *tbl;
+ while ((tbl= it++))
+ prepare_for_positional_update(tbl->table, tbl);
+
+ return;
+}
+
+
+/*
+ Prepare items in INSERT statement
+
+ SYNOPSIS
+ mysql_prepare_insert()
+ thd Thread handler
+ table_list Global/local table list
+ table Table to insert into (can be NULL if table should
+ be taken from table_list->table)
+ where Where clause (for insert ... select)
+ select_insert TRUE if INSERT ... SELECT statement
+ check_fields TRUE if need to check that all INSERT fields are
+ given values.
+ abort_on_warning whether to report if some INSERT field is not
+ assigned as an error (TRUE) or as a warning (FALSE).
+
+ TODO (in far future)
+ In cases of:
+ INSERT INTO t1 SELECT a, sum(a) as sum1 from t2 GROUP BY a
+ ON DUPLICATE KEY ...
+ we should be able to refer to sum1 in the ON DUPLICATE KEY part
+
+ WARNING
+ You MUST set table->insert_values to 0 after calling this function
+ before releasing the table object.
+
+ RETURN VALUE
+ FALSE OK
+ TRUE error
+*/
+
+bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list,
+ TABLE *table, List<Item> &fields, List_item *values,
+ List<Item> &update_fields, List<Item> &update_values,
+ enum_duplicates duplic,
+ COND **where, bool select_insert,
+ bool check_fields, bool abort_on_warning)
+{
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ Name_resolution_context *context= &select_lex->context;
+ Name_resolution_context_state ctx_state;
+ bool insert_into_view= (table_list->view != 0);
+ bool res= 0;
+ table_map map= 0;
+ DBUG_ENTER("mysql_prepare_insert");
+ DBUG_PRINT("enter", ("table_list: 0x%lx table: 0x%lx view: %d",
+ (ulong)table_list, (ulong)table,
+ (int)insert_into_view));
+ /* INSERT should have a SELECT or VALUES clause */
+ DBUG_ASSERT (!select_insert || !values);
+
+ if (mysql_handle_derived(thd->lex, DT_INIT))
+ DBUG_RETURN(TRUE);
+ if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT))
+ DBUG_RETURN(TRUE);
+ if (mysql_handle_list_of_derived(thd->lex, table_list, DT_PREPARE))
+ DBUG_RETURN(TRUE);
+ /*
+ For subqueries in VALUES() we should not see the table in which we are
+ inserting (for INSERT ... SELECT this is done by changing table_list,
+ because INSERT ... SELECT share SELECT_LEX it with SELECT.
+ */
+ if (!select_insert)
+ {
+ for (SELECT_LEX_UNIT *un= select_lex->first_inner_unit();
+ un;
+ un= un->next_unit())
+ {
+ for (SELECT_LEX *sl= un->first_select();
+ sl;
+ sl= sl->next_select())
+ {
+ sl->context.outer_context= 0;
+ }
+ }
+ }
+
+ if (duplic == DUP_UPDATE)
+ {
+ /* it should be allocated before Item::fix_fields() */
+ if (table_list->set_insert_values(thd->mem_root))
+ DBUG_RETURN(TRUE);
+ }
+
+ if (mysql_prepare_insert_check_table(thd, table_list, fields, select_insert))
+ DBUG_RETURN(TRUE);
+
+ /* Prepare the fields in the statement. */
+ if (values)
+ {
+ /* if we have INSERT ... VALUES () we cannot have a GROUP BY clause */
+ DBUG_ASSERT (!select_lex->group_list.elements);
+
+ /* Save the state of the current name resolution context. */
+ ctx_state.save_state(context, table_list);
+
+ /*
+ Perform name resolution only in the first table - 'table_list',
+ which is the table that is inserted into.
+ */
+ table_list->next_local= 0;
+ context->resolve_in_table_list_only(table_list);
+
+ res= (setup_fields(thd, 0, *values, MARK_COLUMNS_READ, 0, 0) ||
+ check_insert_fields(thd, context->table_list, fields, *values,
+ !insert_into_view, 0, &map));
+
+ if (!res && check_fields)
+ {
+ bool saved_abort_on_warning= thd->abort_on_warning;
+ thd->abort_on_warning= abort_on_warning;
+ res= check_that_all_fields_are_given_values(thd,
+ table ? table :
+ context->table_list->table,
+ context->table_list);
+ thd->abort_on_warning= saved_abort_on_warning;
+ }
+
+ if (!res)
+ res= setup_fields(thd, 0, update_values, MARK_COLUMNS_READ, 0, 0);
+
+ if (!res && duplic == DUP_UPDATE)
+ {
+ select_lex->no_wrap_view_item= TRUE;
+ res= check_update_fields(thd, context->table_list, update_fields,
+ update_values, false, &map);
+ select_lex->no_wrap_view_item= FALSE;
+ }
+
+ /* Restore the current context. */
+ ctx_state.restore_state(context, table_list);
+ }
+
+ if (res)
+ DBUG_RETURN(res);
+
+ if (!table)
+ table= table_list->table;
+
+ if (!fields.elements && table->vfield)
+ {
+ for (Field **vfield_ptr= table->vfield; *vfield_ptr; vfield_ptr++)
+ {
+ if ((*vfield_ptr)->stored_in_db)
+ {
+ thd->lex->unit.insert_table_with_stored_vcol= table;
+ break;
+ }
+ }
+ }
+
+ if (!select_insert)
+ {
+ Item *fake_conds= 0;
+ TABLE_LIST *duplicate;
+ if ((duplicate= unique_table(thd, table_list, table_list->next_global, 1)))
+ {
+ update_non_unique_table_error(table_list, "INSERT", duplicate);
+ DBUG_RETURN(TRUE);
+ }
+ select_lex->fix_prepare_information(thd, &fake_conds, &fake_conds);
+ select_lex->first_execution= 0;
+ }
+ /*
+ Only call prepare_for_posistion() if we are not performing a DELAYED
+ operation. It will instead be executed by delayed insert thread.
+ */
+ if (duplic == DUP_UPDATE || duplic == DUP_REPLACE)
+ prepare_for_positional_update(table, table_list);
+ DBUG_RETURN(FALSE);
+}
+
+
+ /* Check if there is more uniq keys after field */
+
+static int last_uniq_key(TABLE *table,uint keynr)
+{
+ /*
+ When an underlying storage engine informs that the unique key
+ conflicts are not reported in the ascending order by setting
+ the HA_DUPLICATE_KEY_NOT_IN_ORDER flag, we cannot rely on this
+ information to determine the last key conflict.
+
+ The information about the last key conflict will be used to
+ do a replace of the new row on the conflicting row, rather
+ than doing a delete (of old row) + insert (of new row).
+
+ Hence check for this flag and disable replacing the last row
+ by returning 0 always. Returning 0 will result in doing
+ a delete + insert always.
+ */
+ if (table->file->ha_table_flags() & HA_DUPLICATE_KEY_NOT_IN_ORDER)
+ return 0;
+
+ while (++keynr < table->s->keys)
+ if (table->key_info[keynr].flags & HA_NOSAME)
+ return 0;
+ return 1;
+}
+
+
+/*
+ Write a record to table with optional deleting of conflicting records,
+ invoke proper triggers if needed.
+
+ SYNOPSIS
+ write_record()
+ thd - thread context
+ table - table to which record should be written
+ info - COPY_INFO structure describing handling of duplicates
+ and which is used for counting number of records inserted
+ and deleted.
+
+ NOTE
+ Once this record will be written to table after insert trigger will
+ be invoked. If instead of inserting new record we will update old one
+ then both on update triggers will work instead. Similarly both on
+ delete triggers will be invoked if we will delete conflicting records.
+
+ Sets thd->transaction.stmt.modified_non_trans_table to TRUE if table which is updated didn't have
+ transactions.
+
+ RETURN VALUE
+ 0 - success
+ non-0 - error
+*/
+
+
+int write_record(THD *thd, TABLE *table,COPY_INFO *info)
+{
+ int error, trg_error= 0;
+ char *key=0;
+ MY_BITMAP *save_read_set, *save_write_set;
+ ulonglong prev_insert_id= table->file->next_insert_id;
+ ulonglong insert_id_for_cur_row= 0;
+ ulonglong prev_insert_id_for_cur_row= 0;
+ DBUG_ENTER("write_record");
+
+ info->records++;
+ save_read_set= table->read_set;
+ save_write_set= table->write_set;
+
+ if (info->handle_duplicates == DUP_REPLACE ||
+ info->handle_duplicates == DUP_UPDATE)
+ {
+ while ((error=table->file->ha_write_row(table->record[0])))
+ {
+ uint key_nr;
+ /*
+ If we do more than one iteration of this loop, from the second one the
+ row will have an explicit value in the autoinc field, which was set at
+ the first call of handler::update_auto_increment(). So we must save
+ the autogenerated value to avoid thd->insert_id_for_cur_row to become
+ 0.
+ */
+ if (table->file->insert_id_for_cur_row > 0)
+ insert_id_for_cur_row= table->file->insert_id_for_cur_row;
+ else
+ table->file->insert_id_for_cur_row= insert_id_for_cur_row;
+ bool is_duplicate_key_error;
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP))
+ goto err;
+ is_duplicate_key_error= table->file->is_fatal_error(error, 0);
+ if (!is_duplicate_key_error)
+ {
+ /*
+ We come here when we had an ignorable error which is not a duplicate
+ key error. In this we ignore error if ignore flag is set, otherwise
+ report error as usual. We will not do any duplicate key processing.
+ */
+ if (info->ignore)
+ {
+ table->file->print_error(error, MYF(ME_JUST_WARNING));
+ goto ok_or_after_trg_err; /* Ignoring a not fatal error, return 0 */
+ }
+ goto err;
+ }
+ if ((int) (key_nr = table->file->get_dup_key(error)) < 0)
+ {
+ error= HA_ERR_FOUND_DUPP_KEY; /* Database can't find key */
+ goto err;
+ }
+ DEBUG_SYNC(thd, "write_row_replace");
+
+ /* Read all columns for the row we are going to replace */
+ table->use_all_columns();
+ /*
+ Don't allow REPLACE to replace a row when a auto_increment column
+ was used. This ensures that we don't get a problem when the
+ whole range of the key has been used.
+ */
+ if (info->handle_duplicates == DUP_REPLACE &&
+ table->next_number_field &&
+ key_nr == table->s->next_number_index &&
+ (insert_id_for_cur_row > 0))
+ goto err;
+ if (table->file->ha_table_flags() & HA_DUPLICATE_POS)
+ {
+ if (table->file->ha_rnd_pos(table->record[1],table->file->dup_ref))
+ goto err;
+ }
+ else
+ {
+ if (table->file->extra(HA_EXTRA_FLUSH_CACHE)) /* Not needed with NISAM */
+ {
+ error=my_errno;
+ goto err;
+ }
+
+ if (!key)
+ {
+ if (!(key=(char*) my_safe_alloca(table->s->max_unique_length,
+ MAX_KEY_LENGTH)))
+ {
+ error=ENOMEM;
+ goto err;
+ }
+ }
+ key_copy((uchar*) key,table->record[0],table->key_info+key_nr,0);
+ key_part_map keypart_map= (1 << table->key_info[key_nr].user_defined_key_parts) - 1;
+ if ((error= (table->file->ha_index_read_idx_map(table->record[1],
+ key_nr, (uchar*) key,
+ keypart_map,
+ HA_READ_KEY_EXACT))))
+ goto err;
+ }
+ if (info->handle_duplicates == DUP_UPDATE)
+ {
+ int res= 0;
+ /*
+ We don't check for other UNIQUE keys - the first row
+ that matches, is updated. If update causes a conflict again,
+ an error is returned
+ */
+ DBUG_ASSERT(table->insert_values != NULL);
+ store_record(table,insert_values);
+ restore_record(table,record[1]);
+
+ /*
+ in INSERT ... ON DUPLICATE KEY UPDATE the set of modified fields can
+ change per row. Thus, we have to do reset_default_fields() per row.
+ Twice (before insert and before update).
+ */
+ table->reset_default_fields();
+ DBUG_ASSERT(info->update_fields->elements ==
+ info->update_values->elements);
+ if (fill_record_n_invoke_before_triggers(thd, table, *info->update_fields,
+ *info->update_values,
+ info->ignore,
+ TRG_EVENT_UPDATE))
+ goto before_trg_err;
+
+ bool different_records= (!records_are_comparable(table) ||
+ compare_record(table));
+ /*
+ Default fields must be updated before checking view updateability.
+ This branch of INSERT is executed only when a UNIQUE key was violated
+ with the ON DUPLICATE KEY UPDATE option. In this case the INSERT
+ operation is transformed to an UPDATE, and the default fields must
+ be updated as if this is an UPDATE.
+ */
+ if (different_records && table->default_field)
+ {
+ bool res;
+ enum_sql_command cmd= thd->lex->sql_command;
+ thd->lex->sql_command= SQLCOM_UPDATE;
+ res= table->update_default_fields();
+ thd->lex->sql_command= cmd;
+ if (res)
+ goto err;
+ }
+ table->reset_default_fields();
+
+ /* CHECK OPTION for VIEW ... ON DUPLICATE KEY UPDATE ... */
+ if (info->view &&
+ (res= info->view->view_check_option(current_thd, info->ignore)) ==
+ VIEW_CHECK_SKIP)
+ goto ok_or_after_trg_err;
+ if (res == VIEW_CHECK_ERROR)
+ goto before_trg_err;
+
+ table->file->restore_auto_increment(prev_insert_id);
+ info->touched++;
+ if (different_records)
+ {
+ if ((error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ {
+ if (info->ignore &&
+ !table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ {
+ if (!(thd->variables.old_behavior &
+ OLD_MODE_NO_DUP_KEY_WARNINGS_WITH_IGNORE))
+ table->file->print_error(error, MYF(ME_JUST_WARNING));
+ goto ok_or_after_trg_err;
+ }
+ goto err;
+ }
+
+ if (error != HA_ERR_RECORD_IS_THE_SAME)
+ info->updated++;
+ else
+ error= 0;
+ /*
+ If ON DUP KEY UPDATE updates a row instead of inserting
+ one, it's like a regular UPDATE statement: it should not
+ affect the value of a next SELECT LAST_INSERT_ID() or
+ mysql_insert_id(). Except if LAST_INSERT_ID(#) was in the
+ INSERT query, which is handled separately by
+ THD::arg_of_last_insert_id_function.
+ */
+ prev_insert_id_for_cur_row= table->file->insert_id_for_cur_row;
+ insert_id_for_cur_row= table->file->insert_id_for_cur_row= 0;
+ trg_error= (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
+ TRG_ACTION_AFTER, TRUE));
+ info->copied++;
+ }
+
+ /*
+ Only update next_insert_id if the AUTO_INCREMENT value was explicitly
+ updated, so we don't update next_insert_id with the value from the
+ row being updated. Otherwise reset next_insert_id to what it was
+ before the duplicate key error, since that value is unused.
+ */
+ if (table->next_number_field_updated)
+ {
+ DBUG_ASSERT(table->next_number_field != NULL);
+
+ table->file->adjust_next_insert_id_after_explicit_value(table->next_number_field->val_int());
+ }
+ else if (prev_insert_id_for_cur_row)
+ {
+ table->file->restore_auto_increment(prev_insert_id_for_cur_row);
+ }
+ goto ok_or_after_trg_err;
+ }
+ else /* DUP_REPLACE */
+ {
+ /*
+ The manual defines the REPLACE semantics that it is either
+ an INSERT or DELETE(s) + INSERT; FOREIGN KEY checks in
+ InnoDB do not function in the defined way if we allow MySQL
+ to convert the latter operation internally to an UPDATE.
+ We also should not perform this conversion if we have
+ timestamp field with ON UPDATE which is different from DEFAULT.
+ Another case when conversion should not be performed is when
+ we have ON DELETE trigger on table so user may notice that
+ we cheat here. Note that it is ok to do such conversion for
+ tables which have ON UPDATE but have no ON DELETE triggers,
+ we just should not expose this fact to users by invoking
+ ON UPDATE triggers.
+ */
+ if (last_uniq_key(table,key_nr) &&
+ !table->file->referenced_by_foreign_key() &&
+ (!table->triggers || !table->triggers->has_delete_triggers()))
+ {
+ if ((error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ goto err;
+ if (error != HA_ERR_RECORD_IS_THE_SAME)
+ info->deleted++;
+ else
+ error= 0;
+ thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row);
+ /*
+ Since we pretend that we have done insert we should call
+ its after triggers.
+ */
+ goto after_trg_n_copied_inc;
+ }
+ else
+ {
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_BEFORE, TRUE))
+ goto before_trg_err;
+ if ((error=table->file->ha_delete_row(table->record[1])))
+ goto err;
+ info->deleted++;
+ if (!table->file->has_transactions())
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
+ TRG_ACTION_AFTER, TRUE))
+ {
+ trg_error= 1;
+ goto ok_or_after_trg_err;
+ }
+ /* Let us attempt do write_row() once more */
+ }
+ }
+ }
+
+ /*
+ If more than one iteration of the above while loop is done, from
+ the second one the row being inserted will have an explicit
+ value in the autoinc field, which was set at the first call of
+ handler::update_auto_increment(). This value is saved to avoid
+ thd->insert_id_for_cur_row becoming 0. Use this saved autoinc
+ value.
+ */
+ if (table->file->insert_id_for_cur_row == 0)
+ table->file->insert_id_for_cur_row= insert_id_for_cur_row;
+
+ thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row);
+ /*
+ Restore column maps if they where replaced during an duplicate key
+ problem.
+ */
+ if (table->read_set != save_read_set ||
+ table->write_set != save_write_set)
+ table->column_bitmaps_set(save_read_set, save_write_set);
+ }
+ else if ((error=table->file->ha_write_row(table->record[0])))
+ {
+ DEBUG_SYNC(thd, "write_row_noreplace");
+ if (!info->ignore ||
+ table->file->is_fatal_error(error, HA_CHECK_DUP))
+ goto err;
+ if (!(thd->variables.old_behavior &
+ OLD_MODE_NO_DUP_KEY_WARNINGS_WITH_IGNORE))
+ table->file->print_error(error, MYF(ME_JUST_WARNING));
+ table->file->restore_auto_increment(prev_insert_id);
+ goto ok_or_after_trg_err;
+ }
+
+after_trg_n_copied_inc:
+ info->copied++;
+ thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row);
+ trg_error= (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
+ TRG_ACTION_AFTER, TRUE));
+
+ok_or_after_trg_err:
+ if (key)
+ my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH);
+ if (!table->file->has_transactions())
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ DBUG_RETURN(trg_error);
+
+err:
+ info->last_errno= error;
+ table->file->print_error(error,MYF(0));
+
+before_trg_err:
+ table->file->restore_auto_increment(prev_insert_id);
+ if (key)
+ my_safe_afree(key, table->s->max_unique_length, MAX_KEY_LENGTH);
+ table->column_bitmaps_set(save_read_set, save_write_set);
+ DBUG_RETURN(1);
+}
+
+
+/******************************************************************************
+ Check that all fields with arn't null_fields are used
+******************************************************************************/
+
+int check_that_all_fields_are_given_values(THD *thd, TABLE *entry,
+ TABLE_LIST *table_list)
+{
+ int err= 0;
+ MY_BITMAP *write_set= entry->write_set;
+
+ for (Field **field=entry->field ; *field ; field++)
+ {
+ if (!bitmap_is_set(write_set, (*field)->field_index) &&
+ ((*field)->flags & NO_DEFAULT_VALUE_FLAG) &&
+ ((*field)->real_type() != MYSQL_TYPE_ENUM))
+ {
+ bool view= FALSE;
+ if (table_list)
+ {
+ table_list= table_list->top_table();
+ view= MY_TEST(table_list->view);
+ }
+ if (view)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_NO_DEFAULT_FOR_VIEW_FIELD,
+ ER(ER_NO_DEFAULT_FOR_VIEW_FIELD),
+ table_list->view_db.str,
+ table_list->view_name.str);
+ }
+ else
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_NO_DEFAULT_FOR_FIELD,
+ ER(ER_NO_DEFAULT_FOR_FIELD),
+ (*field)->field_name);
+ }
+ err= 1;
+ }
+ }
+ return thd->abort_on_warning ? err : 0;
+}
+
+/*****************************************************************************
+ Handling of delayed inserts
+ A thread is created for each table that one uses with the DELAYED attribute.
+*****************************************************************************/
+
+#ifndef EMBEDDED_LIBRARY
+
+class delayed_row :public ilink {
+public:
+ char *record;
+ enum_duplicates dup;
+ my_time_t start_time;
+ ulong start_time_sec_part;
+ ulonglong sql_mode;
+ bool auto_increment_field_not_null;
+ bool query_start_used, ignore, log_query, query_start_sec_part_used;
+ bool stmt_depends_on_first_successful_insert_id_in_prev_stmt;
+ ulonglong first_successful_insert_id_in_prev_stmt;
+ ulonglong forced_insert_id;
+ ulong auto_increment_increment;
+ ulong auto_increment_offset;
+ LEX_STRING query;
+ Time_zone *time_zone;
+
+ delayed_row(LEX_STRING const query_arg, enum_duplicates dup_arg,
+ bool ignore_arg, bool log_query_arg)
+ : record(0), dup(dup_arg), ignore(ignore_arg), log_query(log_query_arg),
+ forced_insert_id(0), query(query_arg), time_zone(0)
+ {}
+ ~delayed_row()
+ {
+ my_free(query.str);
+ my_free(record);
+ }
+};
+
+/**
+ Delayed_insert - context of a thread responsible for delayed insert
+ into one table. When processing delayed inserts, we create an own
+ thread for every distinct table. Later on all delayed inserts directed
+ into that table are handled by a dedicated thread.
+*/
+
+class Delayed_insert :public ilink {
+ uint locks_in_memory;
+ thr_lock_type delayed_lock;
+public:
+ THD thd;
+ TABLE *table;
+ mysql_mutex_t mutex;
+ mysql_cond_t cond, cond_client;
+ volatile uint tables_in_use,stacked_inserts;
+ volatile bool status;
+ /**
+ When the handler thread starts, it clones a metadata lock ticket
+ which protects against GRL and ticket for the table to be inserted.
+ This is done to allow the deadlock detector to detect deadlocks
+ resulting from these locks.
+ Before this is done, the connection thread cannot safely exit
+ without causing problems for clone_ticket().
+ Once handler_thread_initialized has been set, it is safe for the
+ connection thread to exit.
+ Access to handler_thread_initialized is protected by di->mutex.
+ */
+ bool handler_thread_initialized;
+ COPY_INFO info;
+ I_List<delayed_row> rows;
+ ulong group_count;
+ TABLE_LIST table_list; // Argument
+ /**
+ Request for IX metadata lock protecting against GRL which is
+ passed from connection thread to the handler thread.
+ */
+ MDL_request grl_protection;
+
+ Delayed_insert()
+ :locks_in_memory(0), table(0),tables_in_use(0),stacked_inserts(0),
+ status(0), handler_thread_initialized(FALSE), group_count(0)
+ {
+ DBUG_ENTER("Delayed_insert constructor");
+ thd.security_ctx->user=(char*) delayed_user;
+ thd.security_ctx->host=(char*) my_localhost;
+ strmake_buf(thd.security_ctx->priv_user, thd.security_ctx->user);
+ thd.current_tablenr=0;
+ thd.set_command(COM_DELAYED_INSERT);
+ thd.lex->current_select= 0; // for my_message_sql
+ thd.lex->sql_command= SQLCOM_INSERT; // For innodb::store_lock()
+ /*
+ Prevent changes to global.lock_wait_timeout from affecting
+ delayed insert threads as any timeouts in delayed inserts
+ are not communicated to the client.
+ */
+ thd.variables.lock_wait_timeout= LONG_TIMEOUT;
+
+ bzero((char*) &thd.net, sizeof(thd.net)); // Safety
+ bzero((char*) &table_list, sizeof(table_list)); // Safety
+ thd.system_thread= SYSTEM_THREAD_DELAYED_INSERT;
+ thd.security_ctx->host_or_ip= "";
+ bzero((char*) &info,sizeof(info));
+ mysql_mutex_init(key_delayed_insert_mutex, &mutex, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_delayed_insert_cond, &cond, NULL);
+ mysql_cond_init(key_delayed_insert_cond_client, &cond_client, NULL);
+ mysql_mutex_lock(&LOCK_thread_count);
+ delayed_insert_threads++;
+ delayed_lock= global_system_variables.low_priority_updates ?
+ TL_WRITE_LOW_PRIORITY : TL_WRITE;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_VOID_RETURN;
+ }
+ ~Delayed_insert()
+ {
+ /* The following is not really needed, but just for safety */
+ delayed_row *row;
+ while ((row=rows.get()))
+ delete row;
+ if (table)
+ {
+ close_thread_tables(&thd);
+ thd.mdl_context.release_transactional_locks();
+ }
+ mysql_mutex_lock(&LOCK_thread_count);
+ mysql_mutex_destroy(&mutex);
+ mysql_cond_destroy(&cond);
+ mysql_cond_destroy(&cond_client);
+ thd.unlink(); // Must be unlinked under lock
+ my_free(thd.query());
+ thd.security_ctx->user= thd.security_ctx->host=0;
+ delayed_insert_threads--;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ thread_safe_decrement32(&thread_count, &thread_count_lock);
+ mysql_cond_broadcast(&COND_thread_count); /* Tell main we are ready */
+ }
+
+ /* The following is for checking when we can delete ourselves */
+ inline void lock()
+ {
+ locks_in_memory++; // Assume LOCK_delay_insert
+ }
+ void unlock()
+ {
+ mysql_mutex_lock(&LOCK_delayed_insert);
+ if (!--locks_in_memory)
+ {
+ mysql_mutex_lock(&mutex);
+ if (thd.killed && ! stacked_inserts && ! tables_in_use)
+ {
+ mysql_cond_signal(&cond);
+ status=1;
+ }
+ mysql_mutex_unlock(&mutex);
+ }
+ mysql_mutex_unlock(&LOCK_delayed_insert);
+ }
+ inline uint lock_count() { return locks_in_memory; }
+
+ TABLE* get_local_table(THD* client_thd);
+ bool open_and_lock_table();
+ bool handle_inserts(void);
+};
+
+
+I_List<Delayed_insert> delayed_threads;
+
+
+/**
+ Return an instance of delayed insert thread that can handle
+ inserts into a given table, if it exists. Otherwise return NULL.
+*/
+
+static
+Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list)
+{
+ THD_STAGE_INFO(thd, stage_waiting_for_delay_list);
+ mysql_mutex_lock(&LOCK_delayed_insert); // Protect master list
+ I_List_iterator<Delayed_insert> it(delayed_threads);
+ Delayed_insert *di;
+ while ((di= it++))
+ {
+ if (!strcmp(table_list->db, di->table_list.db) &&
+ !strcmp(table_list->table_name, di->table_list.table_name))
+ {
+ di->lock();
+ break;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_delayed_insert); // For unlink from list
+ return di;
+}
+
+
+/**
+ Attempt to find or create a delayed insert thread to handle inserts
+ into this table.
+
+ @return In case of success, table_list->table points to a local copy
+ of the delayed table or is set to NULL, which indicates a
+ request for lock upgrade. In case of failure, value of
+ table_list->table is undefined.
+ @retval TRUE - this thread ran out of resources OR
+ - a newly created delayed insert thread ran out of
+ resources OR
+ - the created thread failed to open and lock the table
+ (e.g. because it does not exist) OR
+ - the table opened in the created thread turned out to
+ be a view
+ @retval FALSE - table successfully opened OR
+ - too many delayed insert threads OR
+ - the table has triggers and we have to fall back to
+ a normal INSERT
+ Two latter cases indicate a request for lock upgrade.
+
+ XXX: why do we regard INSERT DELAYED into a view as an error and
+ do not simply perform a lock upgrade?
+
+ TODO: The approach with using two mutexes to work with the
+ delayed thread list -- LOCK_delayed_insert and
+ LOCK_delayed_create -- is redundant, and we only need one of
+ them to protect the list. The reason we have two locks is that
+ we do not want to block look-ups in the list while we're waiting
+ for the newly created thread to open the delayed table. However,
+ this wait itself is redundant -- we always call get_local_table
+ later on, and there wait again until the created thread acquires
+ a table lock.
+
+ As is redundant the concept of locks_in_memory, since we already
+ have another counter with similar semantics - tables_in_use,
+ both of them are devoted to counting the number of producers for
+ a given consumer (delayed insert thread), only at different
+ stages of producer-consumer relationship.
+
+ The 'status' variable in Delayed_insert is redundant
+ too, since there is already di->stacked_inserts.
+*/
+
+static
+bool delayed_get_table(THD *thd, MDL_request *grl_protection_request,
+ TABLE_LIST *table_list)
+{
+ int error;
+ Delayed_insert *di;
+ DBUG_ENTER("delayed_get_table");
+
+ /* Must be set in the parser */
+ DBUG_ASSERT(table_list->db);
+
+ /* Find the thread which handles this table. */
+ if (!(di= find_handler(thd, table_list)))
+ {
+ /*
+ No match. Create a new thread to handle the table, but
+ no more than max_insert_delayed_threads.
+ */
+ if (delayed_insert_threads >= thd->variables.max_insert_delayed_threads)
+ DBUG_RETURN(0);
+ THD_STAGE_INFO(thd, stage_creating_delayed_handler);
+ mysql_mutex_lock(&LOCK_delayed_create);
+ /*
+ The first search above was done without LOCK_delayed_create.
+ Another thread might have created the handler in between. Search again.
+ */
+ if (! (di= find_handler(thd, table_list)))
+ {
+ if (!(di= new Delayed_insert()))
+ goto end_create;
+
+ thread_safe_increment32(&thread_count, &thread_count_lock);
+
+ /*
+ Annotating delayed inserts is not supported.
+ */
+ di->thd.variables.binlog_annotate_row_events= 0;
+
+ di->thd.set_db(table_list->db, (uint) strlen(table_list->db));
+ di->thd.set_query(my_strdup(table_list->table_name,
+ MYF(MY_WME | ME_FATALERROR)),
+ 0, system_charset_info);
+ if (di->thd.db == NULL || di->thd.query() == NULL)
+ {
+ /* The error is reported */
+ delete di;
+ goto end_create;
+ }
+ di->table_list= *table_list; // Needed to open table
+ /* Replace volatile strings with local copies */
+ di->table_list.alias= di->table_list.table_name= di->thd.query();
+ di->table_list.db= di->thd.db;
+ /* We need the tickets so that they can be cloned in handle_delayed_insert */
+ di->grl_protection.init(MDL_key::GLOBAL, "", "",
+ MDL_INTENTION_EXCLUSIVE, MDL_STATEMENT);
+ di->grl_protection.ticket= grl_protection_request->ticket;
+ init_mdl_requests(&di->table_list);
+ di->table_list.mdl_request.ticket= table_list->mdl_request.ticket;
+
+ di->lock();
+ mysql_mutex_lock(&di->mutex);
+ if ((error= mysql_thread_create(key_thread_delayed_insert,
+ &di->thd.real_id, &connection_attrib,
+ handle_delayed_insert, (void*) di)))
+ {
+ DBUG_PRINT("error",
+ ("Can't create thread to handle delayed insert (error %d)",
+ error));
+ mysql_mutex_unlock(&di->mutex);
+ di->unlock();
+ delete di;
+ my_error(ER_CANT_CREATE_THREAD, MYF(ME_FATALERROR), error);
+ goto end_create;
+ }
+
+ /*
+ Wait until table is open unless the handler thread or the connection
+ thread has been killed. Note that we in all cases must wait until the
+ handler thread has been properly initialized before exiting. Otherwise
+ we risk doing clone_ticket() on a ticket that is no longer valid.
+ */
+ THD_STAGE_INFO(thd, stage_waiting_for_handler_open);
+ while (!di->handler_thread_initialized ||
+ (!di->thd.killed && !di->table && !thd->killed))
+ {
+ mysql_cond_wait(&di->cond_client, &di->mutex);
+ }
+ mysql_mutex_unlock(&di->mutex);
+ THD_STAGE_INFO(thd, stage_got_old_table);
+ if (thd->killed)
+ {
+ di->unlock();
+ goto end_create;
+ }
+ if (di->thd.killed)
+ {
+ if (di->thd.is_error())
+ {
+ /*
+ Copy the error message. Note that we don't treat fatal
+ errors in the delayed thread as fatal errors in the
+ main thread. If delayed thread was killed, we don't
+ want to send "Server shutdown in progress" in the
+ INSERT THREAD.
+ */
+ my_message(di->thd.get_stmt_da()->sql_errno(),
+ di->thd.get_stmt_da()->message(),
+ MYF(0));
+ }
+ di->unlock();
+ goto end_create;
+ }
+ mysql_mutex_lock(&LOCK_delayed_insert);
+ delayed_threads.append(di);
+ mysql_mutex_unlock(&LOCK_delayed_insert);
+ }
+ mysql_mutex_unlock(&LOCK_delayed_create);
+ }
+
+ mysql_mutex_lock(&di->mutex);
+ table_list->table= di->get_local_table(thd);
+ mysql_mutex_unlock(&di->mutex);
+ if (table_list->table)
+ {
+ DBUG_ASSERT(! thd->is_error());
+ thd->di= di;
+ }
+ /* Unlock the delayed insert object after its last access. */
+ di->unlock();
+ DBUG_RETURN((table_list->table == NULL));
+
+end_create:
+ mysql_mutex_unlock(&LOCK_delayed_create);
+ DBUG_RETURN(thd->is_error());
+}
+
+
+/**
+ As we can't let many client threads modify the same TABLE
+ structure of the dedicated delayed insert thread, we create an
+ own structure for each client thread. This includes a row
+ buffer to save the column values and new fields that point to
+ the new row buffer. The memory is allocated in the client
+ thread and is freed automatically.
+
+ @pre This function is called from the client thread. Delayed
+ insert thread mutex must be acquired before invoking this
+ function.
+
+ @return Not-NULL table object on success. NULL in case of an error,
+ which is set in client_thd.
+*/
+
+TABLE *Delayed_insert::get_local_table(THD* client_thd)
+{
+ my_ptrdiff_t adjust_ptrs;
+ Field **field,**org_field, *found_next_number_field;
+ Field **UNINIT_VAR(vfield), **UNINIT_VAR(dfield_ptr);
+ TABLE *copy;
+ TABLE_SHARE *share;
+ uchar *bitmap;
+ char *copy_tmp;
+ DBUG_ENTER("Delayed_insert::get_local_table");
+
+ /* First request insert thread to get a lock */
+ status=1;
+ tables_in_use++;
+ if (!thd.lock) // Table is not locked
+ {
+ THD_STAGE_INFO(client_thd, stage_waiting_for_handler_lock);
+ mysql_cond_signal(&cond); // Tell handler to lock table
+ while (!thd.killed && !thd.lock && ! client_thd->killed)
+ {
+ mysql_cond_wait(&cond_client, &mutex);
+ }
+ THD_STAGE_INFO(client_thd, stage_got_handler_lock);
+ if (client_thd->killed)
+ goto error;
+ if (thd.killed)
+ {
+ /*
+ Copy the error message. Note that we don't treat fatal
+ errors in the delayed thread as fatal errors in the
+ main thread. If delayed thread was killed, we don't
+ want to send "Server shutdown in progress" in the
+ INSERT THREAD.
+
+ The thread could be killed with an error message if
+ di->handle_inserts() or di->open_and_lock_table() fails.
+ The thread could be killed without an error message if
+ killed using mysql_notify_thread_having_shared_lock() or
+ kill_delayed_threads_for_table().
+ */
+ if (!thd.is_error())
+ my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0));
+ else
+ my_message(thd.get_stmt_da()->sql_errno(),
+ thd.get_stmt_da()->message(), MYF(0));
+ goto error;
+ }
+ }
+ share= table->s;
+
+ /*
+ Allocate memory for the TABLE object, the field pointers array, and
+ one record buffer of reclength size. Normally a table has three
+ record buffers of rec_buff_length size, which includes alignment
+ bytes. Since the table copy is used for creating one record only,
+ the other record buffers and alignment are unnecessary.
+ */
+ THD_STAGE_INFO(client_thd, stage_allocating_local_table);
+ copy_tmp= (char*) client_thd->alloc(sizeof(*copy)+
+ (share->fields+1)*sizeof(Field**)+
+ share->reclength +
+ share->column_bitmap_size*3);
+ if (!copy_tmp)
+ goto error;
+
+ if (share->vfields)
+ {
+ vfield= (Field **) client_thd->alloc((share->vfields+1)*sizeof(Field*));
+ if (!vfield)
+ goto error;
+ }
+
+ /* Copy the TABLE object. */
+ copy= new (copy_tmp) TABLE;
+ *copy= *table;
+ /* We don't need to change the file handler here */
+ /* Assign the pointers for the field pointers array and the record. */
+ field= copy->field= (Field**) (copy + 1);
+ bitmap= (uchar*) (field + share->fields + 1);
+ copy->record[0]= (bitmap + share->column_bitmap_size*3);
+ memcpy((char*) copy->record[0], (char*) table->record[0], share->reclength);
+ if (share->default_fields)
+ {
+ copy->default_field= (Field**) client_thd->alloc((share->default_fields+1)*
+ sizeof(Field**));
+ if (!copy->default_field)
+ goto error;
+ dfield_ptr= copy->default_field;
+ }
+
+ /* Ensure we don't use the table list of the original table */
+ copy->pos_in_table_list= 0;
+
+ /*
+ Make a copy of all fields.
+ The copied fields need to point into the copied record. This is done
+ by copying the field objects with their old pointer values and then
+ "move" the pointers by the distance between the original and copied
+ records. That way we preserve the relative positions in the records.
+ */
+ adjust_ptrs= PTR_BYTE_DIFF(copy->record[0], table->record[0]);
+ found_next_number_field= table->found_next_number_field;
+ for (org_field= table->field; *org_field; org_field++, field++)
+ {
+ if (!(*field= (*org_field)->new_field(client_thd->mem_root, copy, 1)))
+ goto error;
+ (*field)->orig_table= copy; // Remove connection
+ (*field)->move_field_offset(adjust_ptrs); // Point at copy->record[0]
+ if (*org_field == found_next_number_field)
+ (*field)->table->found_next_number_field= *field;
+ if (share->default_fields &&
+ ((*org_field)->has_insert_default_function() ||
+ (*org_field)->has_update_default_function()))
+ {
+ /* Put the newly copied field into the set of default fields. */
+ *dfield_ptr= *field;
+ (*dfield_ptr)->unireg_check= (*org_field)->unireg_check;
+ dfield_ptr++;
+ }
+ }
+ *field=0;
+
+ if (share->vfields)
+ {
+ copy->vfield= vfield;
+ for (field= copy->field; *field; field++)
+ {
+ if ((*field)->vcol_info)
+ {
+ bool error_reported= FALSE;
+ if (unpack_vcol_info_from_frm(client_thd,
+ client_thd->mem_root,
+ copy,
+ *field,
+ &(*field)->vcol_info->expr_str,
+ &error_reported))
+ goto error;
+ *vfield++= *field;
+ }
+ }
+ *vfield= 0;
+ }
+
+ if (share->default_fields)
+ *dfield_ptr= NULL;
+
+ /* Adjust in_use for pointing to client thread */
+ copy->in_use= client_thd;
+
+ /* Adjust lock_count. This table object is not part of a lock. */
+ copy->lock_count= 0;
+
+ /* Adjust bitmaps */
+ copy->def_read_set.bitmap= (my_bitmap_map*) bitmap;
+ copy->def_write_set.bitmap= ((my_bitmap_map*)
+ (bitmap + share->column_bitmap_size));
+ copy->def_vcol_set.bitmap= ((my_bitmap_map*)
+ (bitmap + 2*share->column_bitmap_size));
+ copy->tmp_set.bitmap= 0; // To catch errors
+ bzero((char*) bitmap, share->column_bitmap_size*3);
+ copy->read_set= &copy->def_read_set;
+ copy->write_set= &copy->def_write_set;
+ copy->vcol_set= &copy->def_vcol_set;
+
+ DBUG_RETURN(copy);
+
+ /* Got fatal error */
+ error:
+ tables_in_use--;
+ status=1;
+ mysql_cond_signal(&cond); // Inform thread about abort
+ DBUG_RETURN(0);
+}
+
+
+/* Put a question in queue */
+
+static
+int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic,
+ LEX_STRING query, bool ignore, bool log_on)
+{
+ delayed_row *row= 0;
+ Delayed_insert *di=thd->di;
+ const Discrete_interval *forced_auto_inc;
+ DBUG_ENTER("write_delayed");
+ DBUG_PRINT("enter", ("query = '%s' length %lu", query.str,
+ (ulong) query.length));
+
+ THD_STAGE_INFO(thd, stage_waiting_for_handler_insert);
+ mysql_mutex_lock(&di->mutex);
+ while (di->stacked_inserts >= delayed_queue_size && !thd->killed)
+ mysql_cond_wait(&di->cond_client, &di->mutex);
+ THD_STAGE_INFO(thd, stage_storing_row_into_queue);
+
+ if (thd->killed)
+ goto err;
+
+ /*
+ Take a copy of the query string, if there is any. The string will
+ be free'ed when the row is destroyed. If there is no query string,
+ we don't do anything special.
+ */
+
+ if (query.str)
+ {
+ char *str;
+ if (!(str= my_strndup(query.str, query.length, MYF(MY_WME))))
+ goto err;
+ query.str= str;
+ }
+ row= new delayed_row(query, duplic, ignore, log_on);
+ if (row == NULL)
+ {
+ my_free(query.str);
+ goto err;
+ }
+
+ /* This can't be THREAD_SPECIFIC as it's freed in delayed thread */
+ if (!(row->record= (char*) my_malloc(table->s->reclength,
+ MYF(MY_WME))))
+ goto err;
+ memcpy(row->record, table->record[0], table->s->reclength);
+ row->start_time= thd->start_time;
+ row->query_start_used= thd->query_start_used;
+ row->start_time_sec_part= thd->start_time_sec_part;
+ row->query_start_sec_part_used= thd->query_start_sec_part_used;
+ /*
+ those are for the binlog: LAST_INSERT_ID() has been evaluated at this
+ time, so record does not need it, but statement-based binlogging of the
+ INSERT will need when the row is actually inserted.
+ As for SET INSERT_ID, DELAYED does not honour it (BUG#20830).
+ */
+ row->stmt_depends_on_first_successful_insert_id_in_prev_stmt=
+ thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt;
+ row->first_successful_insert_id_in_prev_stmt=
+ thd->first_successful_insert_id_in_prev_stmt;
+
+ /* Add session variable timezone
+ Time_zone object will not be freed even the thread is ended.
+ So we can get time_zone object from thread which handling delayed statement.
+ See the comment of my_tz_find() for detail.
+ */
+ if (thd->time_zone_used)
+ {
+ row->time_zone = thd->variables.time_zone;
+ }
+ else
+ {
+ row->time_zone = NULL;
+ }
+ /* Copy session variables. */
+ row->auto_increment_increment= thd->variables.auto_increment_increment;
+ row->auto_increment_offset= thd->variables.auto_increment_offset;
+ row->sql_mode= thd->variables.sql_mode;
+ row->auto_increment_field_not_null= table->auto_increment_field_not_null;
+
+ /* Copy the next forced auto increment value, if any. */
+ if ((forced_auto_inc= thd->auto_inc_intervals_forced.get_next()))
+ {
+ row->forced_insert_id= forced_auto_inc->minimum();
+ DBUG_PRINT("delayed", ("transmitting auto_inc: %lu",
+ (ulong) row->forced_insert_id));
+ }
+
+ di->rows.push_back(row);
+ di->stacked_inserts++;
+ di->status=1;
+ if (table->s->blob_fields)
+ unlink_blobs(table);
+ mysql_cond_signal(&di->cond);
+
+ thread_safe_increment(delayed_rows_in_use,&LOCK_delayed_status);
+ mysql_mutex_unlock(&di->mutex);
+ DBUG_RETURN(0);
+
+ err:
+ delete row;
+ mysql_mutex_unlock(&di->mutex);
+ DBUG_RETURN(1);
+}
+
+/**
+ Signal the delayed insert thread that this user connection
+ is finished using it for this statement.
+*/
+
+static void end_delayed_insert(THD *thd)
+{
+ DBUG_ENTER("end_delayed_insert");
+ Delayed_insert *di=thd->di;
+ mysql_mutex_lock(&di->mutex);
+ DBUG_PRINT("info",("tables in use: %d",di->tables_in_use));
+ if (!--di->tables_in_use || di->thd.killed)
+ { // Unlock table
+ di->status=1;
+ mysql_cond_signal(&di->cond);
+ }
+ mysql_mutex_unlock(&di->mutex);
+ DBUG_VOID_RETURN;
+}
+
+
+/* We kill all delayed threads when doing flush-tables */
+
+void kill_delayed_threads(void)
+{
+ mysql_mutex_lock(&LOCK_delayed_insert); // For unlink from list
+
+ I_List_iterator<Delayed_insert> it(delayed_threads);
+ Delayed_insert *di;
+ while ((di= it++))
+ {
+ di->thd.killed= KILL_CONNECTION;
+ mysql_mutex_lock(&di->thd.LOCK_thd_data);
+ if (di->thd.mysys_var)
+ {
+ mysql_mutex_lock(&di->thd.mysys_var->mutex);
+ if (di->thd.mysys_var->current_cond)
+ {
+ /*
+ We need the following test because the main mutex may be locked
+ in handle_delayed_insert()
+ */
+ if (&di->mutex != di->thd.mysys_var->current_mutex)
+ mysql_mutex_lock(di->thd.mysys_var->current_mutex);
+ mysql_cond_broadcast(di->thd.mysys_var->current_cond);
+ if (&di->mutex != di->thd.mysys_var->current_mutex)
+ mysql_mutex_unlock(di->thd.mysys_var->current_mutex);
+ }
+ mysql_mutex_unlock(&di->thd.mysys_var->mutex);
+ }
+ mysql_mutex_unlock(&di->thd.LOCK_thd_data);
+ }
+ mysql_mutex_unlock(&LOCK_delayed_insert); // For unlink from list
+}
+
+
+/**
+ A strategy for the prelocking algorithm which prevents the
+ delayed insert thread from opening tables with engines which
+ do not support delayed inserts.
+
+ Particularly it allows to abort open_tables() as soon as we
+ discover that we have opened a MERGE table, without acquiring
+ metadata locks on underlying tables.
+*/
+
+class Delayed_prelocking_strategy : public Prelocking_strategy
+{
+public:
+ virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp,
+ bool *need_prelocking);
+ virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+ virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+};
+
+
+bool Delayed_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ DBUG_ASSERT(table_list->lock_type == TL_WRITE_DELAYED);
+
+ if (!(table_list->table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED))
+ {
+ my_error(ER_DELAYED_NOT_SUPPORTED, MYF(0), table_list->table_name);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+bool Delayed_prelocking_strategy::
+handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp,
+ bool *need_prelocking)
+{
+ /* LEX used by the delayed insert thread has no routines. */
+ DBUG_ASSERT(0);
+ return FALSE;
+}
+
+
+bool Delayed_prelocking_strategy::
+handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ /* We don't open views in the delayed insert thread. */
+ DBUG_ASSERT(0);
+ return FALSE;
+}
+
+
+/**
+ Open and lock table for use by delayed thread and check that
+ this table is suitable for delayed inserts.
+
+ @retval FALSE - Success.
+ @retval TRUE - Failure.
+*/
+
+bool Delayed_insert::open_and_lock_table()
+{
+ Delayed_prelocking_strategy prelocking_strategy;
+
+ /*
+ Use special prelocking strategy to get ER_DELAYED_NOT_SUPPORTED
+ error for tables with engines which don't support delayed inserts.
+ */
+ if (!(table= open_n_lock_single_table(&thd, &table_list,
+ TL_WRITE_DELAYED,
+ MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK,
+ &prelocking_strategy)))
+ {
+ thd.fatal_error(); // Abort waiting inserts
+ return TRUE;
+ }
+
+ if (table->triggers)
+ {
+ /*
+ Table has triggers. This is not an error, but we do
+ not support triggers with delayed insert. Terminate the delayed
+ thread without an error and thus request lock upgrade.
+ */
+ return TRUE;
+ }
+ table->copy_blobs= 1;
+ return FALSE;
+}
+
+
+/*
+ * Create a new delayed insert thread
+*/
+
+pthread_handler_t handle_delayed_insert(void *arg)
+{
+ Delayed_insert *di=(Delayed_insert*) arg;
+ THD *thd= &di->thd;
+
+ pthread_detach_this_thread();
+ /* Add thread to THD list so that's it's visible in 'show processlist' */
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
+ thd->set_current_time();
+ threads.append(thd);
+ if (abort_loop)
+ thd->killed= KILL_CONNECTION;
+ else
+ thd->reset_killed();
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ mysql_thread_set_psi_id(thd->thread_id);
+
+ /*
+ Wait until the client runs into mysql_cond_wait(),
+ where we free it after the table is opened and di linked in the list.
+ If we did not wait here, the client might detect the opened table
+ before it is linked to the list. It would release LOCK_delayed_create
+ and allow another thread to create another handler for the same table,
+ since it does not find one in the list.
+ */
+ mysql_mutex_lock(&di->mutex);
+ if (my_thread_init())
+ {
+ /* Can't use my_error since store_globals has not yet been called */
+ thd->get_stmt_da()->set_error_status(ER_OUT_OF_RESOURCES);
+ di->handler_thread_initialized= TRUE;
+ }
+ else
+ {
+ DBUG_ENTER("handle_delayed_insert");
+ thd->thread_stack= (char*) &thd;
+ if (init_thr_lock() || thd->store_globals())
+ {
+ /* Can't use my_error since store_globals has perhaps failed */
+ thd->get_stmt_da()->set_error_status(ER_OUT_OF_RESOURCES);
+ di->handler_thread_initialized= TRUE;
+ thd->fatal_error();
+ goto err;
+ }
+
+ thd->lex->sql_command= SQLCOM_INSERT; // For innodb::store_lock()
+
+ /*
+ INSERT DELAYED has to go to row-based format because the time
+ at which rows are inserted cannot be determined in mixed mode.
+ */
+ thd->set_current_stmt_binlog_format_row_if_mixed();
+
+ /*
+ Clone tickets representing protection against GRL and the lock on
+ the target table for the insert and add them to the list of granted
+ metadata locks held by the handler thread. This is safe since the
+ handler thread is not holding nor waiting on any metadata locks.
+ */
+ if (thd->mdl_context.clone_ticket(&di->grl_protection) ||
+ thd->mdl_context.clone_ticket(&di->table_list.mdl_request))
+ {
+ thd->mdl_context.release_transactional_locks();
+ di->handler_thread_initialized= TRUE;
+ goto err;
+ }
+
+ /*
+ Now that the ticket has been cloned, it is safe for the connection
+ thread to exit.
+ */
+ di->handler_thread_initialized= TRUE;
+ di->table_list.mdl_request.ticket= NULL;
+
+ if (di->open_and_lock_table())
+ goto err;
+
+ /* Tell client that the thread is initialized */
+ mysql_cond_signal(&di->cond_client);
+
+ /* Now wait until we get an insert or lock to handle */
+ /* We will not abort as long as a client thread uses this thread */
+
+ for (;;)
+ {
+ if (thd->killed)
+ {
+ uint lock_count;
+ /*
+ Remove this from delay insert list so that no one can request a
+ table from this
+ */
+ mysql_mutex_unlock(&di->mutex);
+ mysql_mutex_lock(&LOCK_delayed_insert);
+ di->unlink();
+ lock_count=di->lock_count();
+ mysql_mutex_unlock(&LOCK_delayed_insert);
+ mysql_mutex_lock(&di->mutex);
+ if (!lock_count && !di->tables_in_use && !di->stacked_inserts)
+ break; // Time to die
+ }
+
+ /* Shouldn't wait if killed or an insert is waiting. */
+ if (!thd->killed && !di->status && !di->stacked_inserts)
+ {
+ struct timespec abstime;
+ set_timespec(abstime, delayed_insert_timeout);
+
+ /* Information for pthread_kill */
+ mysql_mutex_unlock(&di->mutex);
+ mysql_mutex_lock(&di->thd.mysys_var->mutex);
+ di->thd.mysys_var->current_mutex= &di->mutex;
+ di->thd.mysys_var->current_cond= &di->cond;
+ mysql_mutex_unlock(&di->thd.mysys_var->mutex);
+ mysql_mutex_lock(&di->mutex);
+ THD_STAGE_INFO(&(di->thd), stage_waiting_for_insert);
+
+ DBUG_PRINT("info",("Waiting for someone to insert rows"));
+ while (!thd->killed && !di->status)
+ {
+ int error;
+ mysql_audit_release(thd);
+ error= mysql_cond_timedwait(&di->cond, &di->mutex, &abstime);
+#ifdef EXTRA_DEBUG
+ if (error && error != EINTR && error != ETIMEDOUT)
+ {
+ fprintf(stderr, "Got error %d from mysql_cond_timedwait\n", error);
+ DBUG_PRINT("error", ("Got error %d from mysql_cond_timedwait",
+ error));
+ }
+#endif
+ if (error == ETIMEDOUT || error == ETIME)
+ thd->killed= KILL_CONNECTION;
+ }
+ /* We can't lock di->mutex and mysys_var->mutex at the same time */
+ mysql_mutex_unlock(&di->mutex);
+ mysql_mutex_lock(&di->thd.mysys_var->mutex);
+ di->thd.mysys_var->current_mutex= 0;
+ di->thd.mysys_var->current_cond= 0;
+ mysql_mutex_unlock(&di->thd.mysys_var->mutex);
+ mysql_mutex_lock(&di->mutex);
+ }
+
+ if (di->tables_in_use && ! thd->lock && !thd->killed)
+ {
+ /*
+ Request for new delayed insert.
+ Lock the table, but avoid to be blocked by a global read lock.
+ If we got here while a global read lock exists, then one or more
+ inserts started before the lock was requested. These are allowed
+ to complete their work before the server returns control to the
+ client which requested the global read lock. The delayed insert
+ handler will close the table and finish when the outstanding
+ inserts are done.
+ */
+ if (! (thd->lock= mysql_lock_tables(thd, &di->table, 1, 0)))
+ {
+ /* Fatal error */
+ thd->killed= KILL_CONNECTION;
+ }
+ mysql_cond_broadcast(&di->cond_client);
+ }
+ if (di->stacked_inserts)
+ {
+ if (di->handle_inserts())
+ {
+ /* Some fatal error */
+ thd->killed= KILL_CONNECTION;
+ }
+ }
+ di->status=0;
+ if (!di->stacked_inserts && !di->tables_in_use && thd->lock)
+ {
+ /*
+ No one is doing a insert delayed
+ Unlock table so that other threads can use it
+ */
+ MYSQL_LOCK *lock=thd->lock;
+ thd->lock=0;
+ mysql_mutex_unlock(&di->mutex);
+ /*
+ We need to release next_insert_id before unlocking. This is
+ enforced by handler::ha_external_lock().
+ */
+ di->table->file->ha_release_auto_increment();
+ mysql_unlock_tables(thd, lock);
+ trans_commit_stmt(thd);
+ di->group_count=0;
+ mysql_audit_release(thd);
+ mysql_mutex_lock(&di->mutex);
+ }
+ if (di->tables_in_use)
+ mysql_cond_broadcast(&di->cond_client); // If waiting clients
+ }
+
+ err:
+ DBUG_LEAVE;
+ }
+
+ {
+ DBUG_ENTER("handle_delayed_insert-cleanup");
+ di->table=0;
+ thd->killed= KILL_CONNECTION; // If error
+ mysql_mutex_unlock(&di->mutex);
+
+ close_thread_tables(thd); // Free the table
+ thd->mdl_context.release_transactional_locks();
+ mysql_cond_broadcast(&di->cond_client); // Safety
+
+ mysql_mutex_lock(&LOCK_delayed_create); // Because of delayed_get_table
+ mysql_mutex_lock(&LOCK_delayed_insert);
+ /*
+ di should be unlinked from the thread handler list and have no active
+ clients
+ */
+ delete di;
+ mysql_mutex_unlock(&LOCK_delayed_insert);
+ mysql_mutex_unlock(&LOCK_delayed_create);
+
+ DBUG_LEAVE;
+ }
+ my_thread_end();
+ pthread_exit(0);
+
+ return 0;
+}
+
+
+/* Remove pointers from temporary fields to allocated values */
+
+static void unlink_blobs(register TABLE *table)
+{
+ for (Field **ptr=table->field ; *ptr ; ptr++)
+ {
+ if ((*ptr)->flags & BLOB_FLAG)
+ ((Field_blob *) (*ptr))->clear_temporary();
+ }
+}
+
+/* Free blobs stored in current row */
+
+static void free_delayed_insert_blobs(register TABLE *table)
+{
+ for (Field **ptr=table->field ; *ptr ; ptr++)
+ {
+ if ((*ptr)->flags & BLOB_FLAG)
+ {
+ uchar *str;
+ ((Field_blob *) (*ptr))->get_ptr(&str);
+ my_free(str);
+ ((Field_blob *) (*ptr))->reset();
+ }
+ }
+}
+
+
+bool Delayed_insert::handle_inserts(void)
+{
+ int error;
+ ulong max_rows;
+ bool has_trans = TRUE;
+ bool using_ignore= 0, using_opt_replace= 0,
+ using_bin_log= mysql_bin_log.is_open();
+ delayed_row *row;
+ DBUG_ENTER("handle_inserts");
+
+ /* Allow client to insert new rows */
+ mysql_mutex_unlock(&mutex);
+
+ table->next_number_field=table->found_next_number_field;
+ table->use_all_columns();
+
+ THD_STAGE_INFO(&thd, stage_upgrading_lock);
+ if (thr_upgrade_write_delay_lock(*thd.lock->locks, delayed_lock,
+ thd.variables.lock_wait_timeout))
+ {
+ /*
+ This can happen if thread is killed either by a shutdown
+ or if another thread is removing the current table definition
+ from the table cache.
+ */
+ my_error(ER_DELAYED_CANT_CHANGE_LOCK, MYF(ME_FATALERROR | ME_NOREFRESH),
+ table->s->table_name.str);
+ goto err;
+ }
+
+ THD_STAGE_INFO(&thd, stage_insert);
+ max_rows= delayed_insert_limit;
+ if (thd.killed || table->s->tdc.flushed)
+ {
+ thd.killed= KILL_SYSTEM_THREAD;
+ max_rows= ULONG_MAX; // Do as much as possible
+ }
+
+ /*
+ We can't use row caching when using the binary log because if
+ we get a crash, then binary log will contain rows that are not yet
+ written to disk, which will cause problems in replication.
+ */
+ if (!using_bin_log)
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ mysql_mutex_lock(&mutex);
+
+ while ((row=rows.get()))
+ {
+ stacked_inserts--;
+ mysql_mutex_unlock(&mutex);
+ memcpy(table->record[0],row->record,table->s->reclength);
+
+ thd.start_time=row->start_time;
+ thd.query_start_used=row->query_start_used;
+ thd.start_time_sec_part=row->start_time_sec_part;
+ thd.query_start_sec_part_used=row->query_start_sec_part_used;
+ /*
+ To get the exact auto_inc interval to store in the binlog we must not
+ use values from the previous interval (of the previous rows).
+ */
+ bool log_query= (row->log_query && row->query.str != NULL);
+ DBUG_PRINT("delayed", ("query: '%s' length: %lu", row->query.str ?
+ row->query.str : "[NULL]",
+ (ulong) row->query.length));
+ if (log_query)
+ {
+ /*
+ Guaranteed that the INSERT DELAYED STMT will not be here
+ in SBR when mysql binlog is enabled.
+ */
+ DBUG_ASSERT(!(mysql_bin_log.is_open() &&
+ !thd.is_current_stmt_binlog_format_row()));
+
+ /*
+ This is the first value of an INSERT statement.
+ It is the right place to clear a forced insert_id.
+ This is usually done after the last value of an INSERT statement,
+ but we won't know this in the insert delayed thread. But before
+ the first value is sufficiently equivalent to after the last
+ value of the previous statement.
+ */
+ table->file->ha_release_auto_increment();
+ thd.auto_inc_intervals_in_cur_stmt_for_binlog.empty();
+ }
+ thd.first_successful_insert_id_in_prev_stmt=
+ row->first_successful_insert_id_in_prev_stmt;
+ thd.stmt_depends_on_first_successful_insert_id_in_prev_stmt=
+ row->stmt_depends_on_first_successful_insert_id_in_prev_stmt;
+ table->auto_increment_field_not_null= row->auto_increment_field_not_null;
+
+ /* Copy the session variables. */
+ thd.variables.auto_increment_increment= row->auto_increment_increment;
+ thd.variables.auto_increment_offset= row->auto_increment_offset;
+ thd.variables.sql_mode= row->sql_mode;
+
+ /* Copy a forced insert_id, if any. */
+ if (row->forced_insert_id)
+ {
+ DBUG_PRINT("delayed", ("received auto_inc: %lu",
+ (ulong) row->forced_insert_id));
+ thd.force_one_auto_inc_interval(row->forced_insert_id);
+ }
+
+ info.ignore= row->ignore;
+ info.handle_duplicates= row->dup;
+ if (info.ignore ||
+ info.handle_duplicates != DUP_ERROR)
+ {
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ using_ignore=1;
+ }
+ if (info.handle_duplicates == DUP_REPLACE &&
+ (!table->triggers ||
+ !table->triggers->has_delete_triggers()))
+ {
+ table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ using_opt_replace= 1;
+ }
+ if (info.handle_duplicates == DUP_UPDATE)
+ table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE);
+ thd.clear_error(); // reset error for binlog
+ if (write_record(&thd, table, &info))
+ {
+ info.error_count++; // Ignore errors
+ thread_safe_increment(delayed_insert_errors,&LOCK_delayed_status);
+ row->log_query = 0;
+ }
+
+ if (using_ignore)
+ {
+ using_ignore=0;
+ table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ }
+ if (using_opt_replace)
+ {
+ using_opt_replace= 0;
+ table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+ }
+
+ if (table->s->blob_fields)
+ free_delayed_insert_blobs(table);
+ thread_safe_decrement(delayed_rows_in_use,&LOCK_delayed_status);
+ thread_safe_increment(delayed_insert_writes,&LOCK_delayed_status);
+ mysql_mutex_lock(&mutex);
+
+ /*
+ Reset the table->auto_increment_field_not_null as it is valid for
+ only one row.
+ */
+ table->auto_increment_field_not_null= FALSE;
+
+ delete row;
+ /*
+ Let READ clients do something once in a while
+ We should however not break in the middle of a multi-line insert
+ if we have binary logging enabled as we don't want other commands
+ on this table until all entries has been processed
+ */
+ if (group_count++ >= max_rows && (row= rows.head()) &&
+ (!(row->log_query & using_bin_log)))
+ {
+ group_count=0;
+ if (stacked_inserts || tables_in_use) // Let these wait a while
+ {
+ if (tables_in_use)
+ mysql_cond_broadcast(&cond_client); // If waiting clients
+ THD_STAGE_INFO(&thd, stage_reschedule);
+ mysql_mutex_unlock(&mutex);
+ if ((error=table->file->extra(HA_EXTRA_NO_CACHE)))
+ {
+ /* This should never happen */
+ table->file->print_error(error,MYF(0));
+ sql_print_error("%s", thd.get_stmt_da()->message());
+ DBUG_PRINT("error", ("HA_EXTRA_NO_CACHE failed in loop"));
+ goto err;
+ }
+ query_cache_invalidate3(&thd, table, 1);
+ if (thr_reschedule_write_lock(*thd.lock->locks,
+ thd.variables.lock_wait_timeout))
+ {
+ /* This is not known to happen. */
+ my_error(ER_DELAYED_CANT_CHANGE_LOCK,
+ MYF(ME_FATALERROR | ME_NOREFRESH),
+ table->s->table_name.str);
+ goto err;
+ }
+ if (!using_bin_log)
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ mysql_mutex_lock(&mutex);
+ THD_STAGE_INFO(&thd, stage_insert);
+ }
+ if (tables_in_use)
+ mysql_cond_broadcast(&cond_client); // If waiting clients
+ }
+ }
+ mysql_mutex_unlock(&mutex);
+
+ /*
+ We need to flush the pending event when using row-based
+ replication since the flushing normally done in binlog_query() is
+ not done last in the statement: for delayed inserts, the insert
+ statement is logged *before* all rows are inserted.
+
+ We can flush the pending event without checking the thd->lock
+ since the delayed insert *thread* is not inside a stored function
+ or trigger.
+
+ TODO: Move the logging to last in the sequence of rows.
+ */
+ has_trans= thd.lex->sql_command == SQLCOM_CREATE_TABLE ||
+ table->file->has_transactions();
+ if (thd.is_current_stmt_binlog_format_row() &&
+ thd.binlog_flush_pending_rows_event(TRUE, has_trans))
+ goto err;
+
+ if ((error=table->file->extra(HA_EXTRA_NO_CACHE)))
+ { // This shouldn't happen
+ table->file->print_error(error,MYF(0));
+ sql_print_error("%s", thd.get_stmt_da()->message());
+ DBUG_PRINT("error", ("HA_EXTRA_NO_CACHE failed after loop"));
+ goto err;
+ }
+ query_cache_invalidate3(&thd, table, 1);
+ mysql_mutex_lock(&mutex);
+ DBUG_RETURN(0);
+
+ err:
+#ifndef DBUG_OFF
+ max_rows= 0; // For DBUG output
+#endif
+ /* Remove all not used rows */
+ while ((row=rows.get()))
+ {
+ if (table->s->blob_fields)
+ {
+ memcpy(table->record[0],row->record,table->s->reclength);
+ free_delayed_insert_blobs(table);
+ }
+ delete row;
+ thread_safe_increment(delayed_insert_errors,&LOCK_delayed_status);
+ stacked_inserts--;
+#ifndef DBUG_OFF
+ max_rows++;
+#endif
+ }
+ DBUG_PRINT("error", ("dropped %lu rows after an error", max_rows));
+ thread_safe_increment(delayed_insert_errors, &LOCK_delayed_status);
+ mysql_mutex_lock(&mutex);
+ DBUG_RETURN(1);
+}
+#endif /* EMBEDDED_LIBRARY */
+
+/***************************************************************************
+ Store records in INSERT ... SELECT *
+***************************************************************************/
+
+
+/*
+ make insert specific preparation and checks after opening tables
+
+ SYNOPSIS
+ mysql_insert_select_prepare()
+ thd thread handler
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+bool mysql_insert_select_prepare(THD *thd)
+{
+ LEX *lex= thd->lex;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ DBUG_ENTER("mysql_insert_select_prepare");
+
+
+ /*
+ SELECT_LEX do not belong to INSERT statement, so we can't add WHERE
+ clause if table is VIEW
+ */
+
+ if (mysql_prepare_insert(thd, lex->query_tables,
+ lex->query_tables->table, lex->field_list, 0,
+ lex->update_list, lex->value_list,
+ lex->duplicates,
+ &select_lex->where, TRUE, FALSE, FALSE))
+ DBUG_RETURN(TRUE);
+
+ DBUG_ASSERT(select_lex->leaf_tables.elements != 0);
+ List_iterator<TABLE_LIST> ti(select_lex->leaf_tables);
+ TABLE_LIST *table;
+ uint insert_tables;
+
+ if (select_lex->first_cond_optimization)
+ {
+ /* Back up leaf_tables list. */
+ Query_arena *arena= thd->stmt_arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test
+
+ insert_tables= select_lex->insert_tables;
+ while ((table= ti++) && insert_tables--)
+ {
+ select_lex->leaf_tables_exec.push_back(table);
+ table->tablenr_exec= table->table->tablenr;
+ table->map_exec= table->table->map;
+ table->maybe_null_exec= table->table->maybe_null;
+ }
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ }
+ ti.rewind();
+ /*
+ exclude first table from leaf tables list, because it belong to
+ INSERT
+ */
+ /* skip all leaf tables belonged to view where we are insert */
+ insert_tables= select_lex->insert_tables;
+ while ((table= ti++) && insert_tables--)
+ ti.remove();
+
+ DBUG_RETURN(FALSE);
+}
+
+
+select_insert::select_insert(TABLE_LIST *table_list_par, TABLE *table_par,
+ List<Item> *fields_par,
+ List<Item> *update_fields,
+ List<Item> *update_values,
+ enum_duplicates duplic,
+ bool ignore_check_option_errors)
+ :table_list(table_list_par), table(table_par), fields(fields_par),
+ autoinc_value_of_last_inserted_row(0),
+ insert_into_view(table_list_par && table_list_par->view != 0)
+{
+ bzero((char*) &info,sizeof(info));
+ info.handle_duplicates= duplic;
+ info.ignore= ignore_check_option_errors;
+ info.update_fields= update_fields;
+ info.update_values= update_values;
+ if (table_list_par)
+ info.view= (table_list_par->view ? table_list_par : 0);
+}
+
+
+int
+select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
+{
+ LEX *lex= thd->lex;
+ int res;
+ table_map map= 0;
+ SELECT_LEX *lex_current_select_save= lex->current_select;
+ DBUG_ENTER("select_insert::prepare");
+
+ unit= u;
+
+ /*
+ Since table in which we are going to insert is added to the first
+ select, LEX::current_select should point to the first select while
+ we are fixing fields from insert list.
+ */
+ lex->current_select= &lex->select_lex;
+
+ res= (setup_fields(thd, 0, values, MARK_COLUMNS_READ, 0, 0) ||
+ check_insert_fields(thd, table_list, *fields, values,
+ !insert_into_view, 1, &map));
+
+ if (!res && fields->elements)
+ {
+ bool saved_abort_on_warning= thd->abort_on_warning;
+ thd->abort_on_warning= !info.ignore && thd->is_strict_mode();
+ res= check_that_all_fields_are_given_values(thd, table_list->table,
+ table_list);
+ thd->abort_on_warning= saved_abort_on_warning;
+ }
+
+ if (info.handle_duplicates == DUP_UPDATE && !res)
+ {
+ Name_resolution_context *context= &lex->select_lex.context;
+ Name_resolution_context_state ctx_state;
+
+ /* Save the state of the current name resolution context. */
+ ctx_state.save_state(context, table_list);
+
+ /* Perform name resolution only in the first table - 'table_list'. */
+ table_list->next_local= 0;
+ context->resolve_in_table_list_only(table_list);
+
+ lex->select_lex.no_wrap_view_item= TRUE;
+ res= res ||
+ check_update_fields(thd, context->table_list,
+ *info.update_fields, *info.update_values,
+ /*
+ In INSERT SELECT ON DUPLICATE KEY UPDATE col=x
+ 'x' can legally refer to a non-inserted table.
+ 'x' is not even resolved yet.
+ */
+ true,
+ &map);
+ lex->select_lex.no_wrap_view_item= FALSE;
+ /*
+ When we are not using GROUP BY and there are no ungrouped aggregate functions
+ we can refer to other tables in the ON DUPLICATE KEY part.
+ We use next_name_resolution_table descructively, so check it first (views?)
+ */
+ DBUG_ASSERT (!table_list->next_name_resolution_table);
+ if (lex->select_lex.group_list.elements == 0 &&
+ !lex->select_lex.with_sum_func)
+ /*
+ We must make a single context out of the two separate name resolution contexts :
+ the INSERT table and the tables in the SELECT part of INSERT ... SELECT.
+ To do that we must concatenate the two lists
+ */
+ table_list->next_name_resolution_table=
+ ctx_state.get_first_name_resolution_table();
+
+ res= res || setup_fields(thd, 0, *info.update_values,
+ MARK_COLUMNS_READ, 0, 0);
+ if (!res)
+ {
+ /*
+ Traverse the update values list and substitute fields from the
+ select for references (Item_ref objects) to them. This is done in
+ order to get correct values from those fields when the select
+ employs a temporary table.
+ */
+ List_iterator<Item> li(*info.update_values);
+ Item *item;
+
+ while ((item= li++))
+ {
+ item->transform(&Item::update_value_transformer,
+ (uchar*)lex->current_select);
+ }
+ }
+
+ /* Restore the current context. */
+ ctx_state.restore_state(context, table_list);
+ }
+
+ lex->current_select= lex_current_select_save;
+ if (res)
+ DBUG_RETURN(1);
+ /*
+ if it is INSERT into join view then check_insert_fields already found
+ real table for insert
+ */
+ table= table_list->table;
+
+ /*
+ Is table which we are changing used somewhere in other parts of
+ query
+ */
+ if (unique_table(thd, table_list, table_list->next_global, 0))
+ {
+ /* Using same table for INSERT and SELECT */
+ lex->current_select->options|= OPTION_BUFFER_RESULT;
+ lex->current_select->join->select_options|= OPTION_BUFFER_RESULT;
+ }
+ else if (!(lex->current_select->options & OPTION_BUFFER_RESULT) &&
+ thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ {
+ /*
+ We must not yet prepare the result table if it is the same as one of the
+ source tables (INSERT SELECT). The preparation may disable
+ indexes on the result table, which may be used during the select, if it
+ is the same table (Bug #6034). Do the preparation after the select phase
+ in select_insert::prepare2().
+ We won't start bulk inserts at all if this statement uses functions or
+ should invoke triggers since they may access to the same table too.
+ */
+ table->file->ha_start_bulk_insert((ha_rows) 0);
+ }
+ restore_record(table,s->default_values); // Get empty record
+ table->reset_default_fields();
+ table->next_number_field=table->found_next_number_field;
+
+#ifdef HAVE_REPLICATION
+ if (thd->rgi_slave &&
+ (info.handle_duplicates == DUP_UPDATE) &&
+ (table->next_number_field != NULL) &&
+ rpl_master_has_bug(thd->rgi_slave->rli, 24432, TRUE, NULL, NULL))
+ DBUG_RETURN(1);
+#endif
+
+ thd->cuted_fields=0;
+ if (info.ignore || info.handle_duplicates != DUP_ERROR)
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ if (info.handle_duplicates == DUP_REPLACE &&
+ (!table->triggers || !table->triggers->has_delete_triggers()))
+ table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ if (info.handle_duplicates == DUP_UPDATE)
+ table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE);
+ thd->abort_on_warning= !info.ignore && thd->is_strict_mode();
+ res= (table_list->prepare_where(thd, 0, TRUE) ||
+ table_list->prepare_check_option(thd));
+
+ if (!res)
+ {
+ table->prepare_triggers_for_insert_stmt_or_event();
+ table->mark_columns_needed_for_insert();
+ }
+
+ DBUG_RETURN(res);
+}
+
+
+/*
+ Finish the preparation of the result table.
+
+ SYNOPSIS
+ select_insert::prepare2()
+ void
+
+ DESCRIPTION
+ If the result table is the same as one of the source tables (INSERT SELECT),
+ the result table is not finally prepared at the join prepair phase.
+ Do the final preparation now.
+
+ RETURN
+ 0 OK
+*/
+
+int select_insert::prepare2(void)
+{
+ DBUG_ENTER("select_insert::prepare2");
+ if (thd->lex->current_select->options & OPTION_BUFFER_RESULT &&
+ thd->locked_tables_mode <= LTM_LOCK_TABLES &&
+ !thd->lex->describe)
+ table->file->ha_start_bulk_insert((ha_rows) 0);
+ DBUG_RETURN(0);
+}
+
+
+void select_insert::cleanup()
+{
+ /* select_insert/select_create are never re-used in prepared statement */
+ DBUG_ASSERT(0);
+}
+
+select_insert::~select_insert()
+{
+ DBUG_ENTER("~select_insert");
+ if (table && table->created)
+ {
+ table->next_number_field=0;
+ table->auto_increment_field_not_null= FALSE;
+ table->file->ha_reset();
+ }
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+ thd->abort_on_warning= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+int select_insert::send_data(List<Item> &values)
+{
+ DBUG_ENTER("select_insert::send_data");
+ bool error=0;
+
+ if (unit->offset_limit_cnt)
+ { // using limit offset,count
+ unit->offset_limit_cnt--;
+ DBUG_RETURN(0);
+ }
+ if (thd->killed == ABORT_QUERY)
+ DBUG_RETURN(0);
+
+ thd->count_cuted_fields= CHECK_FIELD_WARN; // Calculate cuted fields
+ store_values(values);
+ if (table->default_field && table->update_default_fields())
+ DBUG_RETURN(1);
+ thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
+ if (thd->is_error())
+ {
+ table->auto_increment_field_not_null= FALSE;
+ DBUG_RETURN(1);
+ }
+ if (table_list) // Not CREATE ... SELECT
+ {
+ switch (table_list->view_check_option(thd, info.ignore)) {
+ case VIEW_CHECK_SKIP:
+ DBUG_RETURN(0);
+ case VIEW_CHECK_ERROR:
+ DBUG_RETURN(1);
+ }
+ }
+
+ // Release latches in case bulk insert takes a long time
+ ha_release_temporary_latches(thd);
+
+ error= write_record(thd, table, &info);
+ table->auto_increment_field_not_null= FALSE;
+
+ if (!error)
+ {
+ if (table->triggers || info.handle_duplicates == DUP_UPDATE)
+ {
+ /*
+ Restore fields of the record since it is possible that they were
+ changed by ON DUPLICATE KEY UPDATE clause.
+
+ If triggers exist then whey can modify some fields which were not
+ originally touched by INSERT ... SELECT, so we have to restore
+ their original values for the next row.
+ */
+ restore_record(table, s->default_values);
+ }
+ if (table->next_number_field)
+ {
+ /*
+ If no value has been autogenerated so far, we need to remember the
+ value we just saw, we may need to send it to client in the end.
+ */
+ if (thd->first_successful_insert_id_in_cur_stmt == 0) // optimization
+ autoinc_value_of_last_inserted_row=
+ table->next_number_field->val_int();
+ /*
+ Clear auto-increment field for the next record, if triggers are used
+ we will clear it twice, but this should be cheap.
+ */
+ table->next_number_field->reset();
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+
+void select_insert::store_values(List<Item> &values)
+{
+ if (fields->elements)
+ fill_record_n_invoke_before_triggers(thd, table, *fields, values, 1,
+ TRG_EVENT_INSERT);
+ else
+ fill_record_n_invoke_before_triggers(thd, table, table->field, values, 1,
+ TRG_EVENT_INSERT);
+}
+
+bool select_insert::send_eof()
+{
+ int error;
+ bool const trans_table= table->file->has_transactions();
+ ulonglong id, row_count;
+ bool changed;
+ killed_state killed_status= thd->killed;
+ DBUG_ENTER("select_insert::send_eof");
+ DBUG_PRINT("enter", ("trans_table=%d, table_type='%s'",
+ trans_table, table->file->table_type()));
+
+ error= (thd->locked_tables_mode <= LTM_LOCK_TABLES ?
+ table->file->ha_end_bulk_insert() : 0);
+ if (!error && thd->is_error())
+ error= thd->get_stmt_da()->sql_errno();
+
+ table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+
+ if ((changed= (info.copied || info.deleted || info.updated)))
+ {
+ /*
+ We must invalidate the table in the query cache before binlog writing
+ and ha_autocommit_or_rollback.
+ */
+ query_cache_invalidate3(thd, table, 1);
+ }
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ DBUG_ASSERT(trans_table || !changed ||
+ thd->transaction.stmt.modified_non_trans_table);
+
+ /*
+ Write to binlog before commiting transaction. No statement will
+ be written by the binlog_query() below in RBR mode. All the
+ events are in the transaction cache and will be written when
+ ha_autocommit_or_rollback() is issued below.
+ */
+ if (mysql_bin_log.is_open() &&
+ (!error || thd->transaction.stmt.modified_non_trans_table))
+ {
+ int errcode= 0;
+ if (!error)
+ thd->clear_error();
+ else
+ errcode= query_error_code(thd, killed_status == NOT_KILLED);
+ if (thd->binlog_query(THD::ROW_QUERY_TYPE,
+ thd->query(), thd->query_length(),
+ trans_table, FALSE, FALSE, errcode))
+ {
+ table->file->ha_release_auto_increment();
+ DBUG_RETURN(1);
+ }
+ }
+ table->file->ha_release_auto_increment();
+
+ if (error)
+ {
+ table->file->print_error(error,MYF(0));
+ DBUG_RETURN(1);
+ }
+ char buff[160];
+ if (info.ignore)
+ sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records,
+ (ulong) (info.records - info.copied),
+ (long) thd->get_stmt_da()->current_statement_warn_count());
+ else
+ sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records,
+ (ulong) (info.deleted+info.updated),
+ (long) thd->get_stmt_da()->current_statement_warn_count());
+ row_count= info.copied + info.deleted +
+ ((thd->client_capabilities & CLIENT_FOUND_ROWS) ?
+ info.touched : info.updated);
+ id= (thd->first_successful_insert_id_in_cur_stmt > 0) ?
+ thd->first_successful_insert_id_in_cur_stmt :
+ (thd->arg_of_last_insert_id_function ?
+ thd->first_successful_insert_id_in_prev_stmt :
+ (info.copied ? autoinc_value_of_last_inserted_row : 0));
+ ::my_ok(thd, row_count, id, buff);
+ DBUG_RETURN(0);
+}
+
+void select_insert::abort_result_set() {
+
+ DBUG_ENTER("select_insert::abort_result_set");
+ /*
+ If the creation of the table failed (due to a syntax error, for
+ example), no table will have been opened and therefore 'table'
+ will be NULL. In that case, we still need to execute the rollback
+ and the end of the function.
+ */
+ if (table)
+ {
+ bool changed, transactional_table;
+ /*
+ If we are not in prelocked mode, we end the bulk insert started
+ before.
+ */
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ table->file->ha_end_bulk_insert();
+
+ /*
+ If at least one row has been inserted/modified and will stay in
+ the table (the table doesn't have transactions) we must write to
+ the binlog (and the error code will make the slave stop).
+
+ For many errors (example: we got a duplicate key error while
+ inserting into a MyISAM table), no row will be added to the table,
+ so passing the error to the slave will not help since there will
+ be an error code mismatch (the inserts will succeed on the slave
+ with no error).
+
+ If table creation failed, the number of rows modified will also be
+ zero, so no check for that is made.
+ */
+ changed= (info.copied || info.deleted || info.updated);
+ transactional_table= table->file->has_transactions();
+ if (thd->transaction.stmt.modified_non_trans_table ||
+ thd->log_current_statement)
+ {
+ if (!can_rollback_data())
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= query_error_code(thd, thd->killed == NOT_KILLED);
+ /* error of writing binary log is ignored */
+ (void) thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(),
+ thd->query_length(),
+ transactional_table, FALSE, FALSE, errcode);
+ }
+ if (changed)
+ query_cache_invalidate3(thd, table, 1);
+ }
+ DBUG_ASSERT(transactional_table || !changed ||
+ thd->transaction.stmt.modified_non_trans_table);
+ table->file->ha_release_auto_increment();
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/***************************************************************************
+ CREATE TABLE (SELECT) ...
+***************************************************************************/
+
+/**
+ Create table from lists of fields and items (or just return TABLE
+ object for pre-opened existing table).
+
+ @param thd [in] Thread object
+ @param create_info [in] Create information (like MAX_ROWS, ENGINE or
+ temporary table flag)
+ @param create_table [in] Pointer to TABLE_LIST object providing database
+ and name for table to be created or to be open
+ @param alter_info [in/out] Initial list of columns and indexes for the
+ table to be created
+ @param items [in] List of items which should be used to produce
+ rest of fields for the table (corresponding
+ fields will be added to the end of
+ alter_info->create_list)
+ @param lock [out] Pointer to the MYSQL_LOCK object for table
+ created will be returned in this parameter.
+ Since this table is not included in THD::lock
+ caller is responsible for explicitly unlocking
+ this table.
+ @param hooks [in] Hooks to be invoked before and after obtaining
+ table lock on the table being created.
+
+ @note
+ This function assumes that either table exists and was pre-opened and
+ locked at open_and_lock_tables() stage (and in this case we just emit
+ error or warning and return pre-opened TABLE object) or an exclusive
+ metadata lock was acquired on table so we can safely create, open and
+ lock table in it (we don't acquire metadata lock if this create is
+ for temporary table).
+
+ @note
+ Since this function contains some logic specific to CREATE TABLE ...
+ SELECT it should be changed before it can be used in other contexts.
+
+ @retval non-zero Pointer to TABLE object for table created or opened
+ @retval 0 Error
+*/
+
+static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
+ TABLE_LIST *create_table,
+ Alter_info *alter_info,
+ List<Item> *items,
+ MYSQL_LOCK **lock,
+ TABLEOP_HOOKS *hooks)
+{
+ TABLE tmp_table; // Used during 'Create_field()'
+ TABLE_SHARE share;
+ TABLE *table= 0;
+ uint select_field_count= items->elements;
+ /* Add selected items to field list */
+ List_iterator_fast<Item> it(*items);
+ Item *item;
+ Field *tmp_field;
+ DBUG_ENTER("create_table_from_items");
+
+ tmp_table.alias= 0;
+ tmp_table.s= &share;
+ init_tmp_table_share(thd, &share, "", 0, "", "");
+
+ tmp_table.s->db_create_options=0;
+ tmp_table.null_row= 0;
+ tmp_table.maybe_null= 0;
+
+ promote_first_timestamp_column(&alter_info->create_list);
+
+ while ((item=it++))
+ {
+ Create_field *cr_field;
+ Field *field, *def_field;
+ if (item->type() == Item::FUNC_ITEM)
+ {
+ if (item->result_type() != STRING_RESULT)
+ field= item->tmp_table_field(&tmp_table);
+ else
+ field= item->tmp_table_field_from_field_type(&tmp_table, 0);
+ }
+ else
+ field= create_tmp_field(thd, &tmp_table, item, item->type(),
+ (Item ***) 0, &tmp_field, &def_field, 0, 0, 0, 0,
+ 0);
+ if (!field ||
+ !(cr_field=new Create_field(field,(item->type() == Item::FIELD_ITEM ?
+ ((Item_field *)item)->field :
+ (Field*) 0))))
+ DBUG_RETURN(0);
+ if (item->maybe_null)
+ cr_field->flags &= ~NOT_NULL_FLAG;
+ alter_info->create_list.push_back(cr_field);
+ }
+
+ DEBUG_SYNC(thd,"create_table_select_before_create");
+
+ /* Check if LOCK TABLES + CREATE OR REPLACE of existing normal table*/
+ if (thd->locked_tables_mode && create_table->table &&
+ !create_info->tmp_table())
+ {
+ /* Remember information about the locked table */
+ create_info->pos_in_locked_tables=
+ create_table->table->pos_in_locked_tables;
+ create_info->mdl_ticket= create_table->table->mdl_ticket;
+ }
+
+ /*
+ Create and lock table.
+
+ Note that we either creating (or opening existing) temporary table or
+ creating base table on which name we have exclusive lock. So code below
+ should not cause deadlocks or races.
+
+ We don't log the statement, it will be logged later.
+
+ If this is a HEAP table, the automatic DELETE FROM which is written to the
+ binlog when a HEAP table is opened for the first time since startup, must
+ not be written: 1) it would be wrong (imagine we're in CREATE SELECT: we
+ don't want to delete from it) 2) it would be written before the CREATE
+ TABLE, which is a wrong order. So we keep binary logging disabled when we
+ open_table().
+ */
+
+ if (!mysql_create_table_no_lock(thd, create_table->db,
+ create_table->table_name,
+ create_info, alter_info, NULL,
+ select_field_count))
+ {
+ DEBUG_SYNC(thd,"create_table_select_before_open");
+
+ /*
+ If we had a temporary table or a table used with LOCK TABLES,
+ it was closed by mysql_create()
+ */
+ create_table->table= 0;
+
+ if (!create_info->tmp_table())
+ {
+ Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
+ TABLE_LIST::enum_open_strategy save_open_strategy;
+
+ /* Force the newly created table to be opened */
+ save_open_strategy= create_table->open_strategy;
+ create_table->open_strategy= TABLE_LIST::OPEN_NORMAL;
+ /*
+ Here we open the destination table, on which we already have
+ an exclusive metadata lock.
+ */
+ if (open_table(thd, create_table, thd->mem_root, &ot_ctx))
+ {
+ quick_rm_table(thd, create_info->db_type, create_table->db,
+ table_case_name(create_info, create_table->table_name),
+ 0);
+ }
+ /* Restore */
+ create_table->open_strategy= save_open_strategy;
+ }
+ else
+ {
+ if (open_temporary_table(thd, create_table))
+ {
+ /*
+ This shouldn't happen as creation of temporary table should make
+ it preparable for open. Anyway we can't drop temporary table if
+ we are unable to find it.
+ */
+ DBUG_ASSERT(0);
+ }
+ DBUG_ASSERT(create_table->table == create_info->table);
+ }
+ }
+ else
+ create_table->table= 0; // Create failed
+
+ if (!(table= create_table->table))
+ {
+ if (!thd->is_error()) // CREATE ... IF NOT EXISTS
+ my_ok(thd); // succeed, but did nothing
+ DBUG_RETURN(0);
+ }
+
+ DEBUG_SYNC(thd,"create_table_select_before_lock");
+
+ table->reginfo.lock_type=TL_WRITE;
+ hooks->prelock(&table, 1); // Call prelock hooks
+ /*
+ mysql_lock_tables() below should never fail with request to reopen table
+ since it won't wait for the table lock (we have exclusive metadata lock on
+ the table) and thus can't get aborted.
+ */
+ if (! ((*lock)= mysql_lock_tables(thd, &table, 1, 0)) ||
+ hooks->postlock(&table, 1))
+ {
+ /* purecov: begin tested */
+ /*
+ This can happen in innodb when you get a deadlock when using same table
+ in insert and select or when you run out of memory.
+ */
+ my_error(ER_CANT_LOCK, MYF(0), my_errno);
+ if (*lock)
+ {
+ mysql_unlock_tables(thd, *lock);
+ *lock= 0;
+ }
+ drop_open_table(thd, table, create_table->db, create_table->table_name);
+ DBUG_RETURN(0);
+ /* purecov: end */
+ }
+ DBUG_RETURN(table);
+}
+
+
+int
+select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
+{
+ MYSQL_LOCK *extra_lock= NULL;
+ DBUG_ENTER("select_create::prepare");
+
+ TABLEOP_HOOKS *hook_ptr= NULL;
+ /*
+ For row-based replication, the CREATE-SELECT statement is written
+ in two pieces: the first one contain the CREATE TABLE statement
+ necessary to create the table and the second part contain the rows
+ that should go into the table.
+
+ For non-temporary tables, the start of the CREATE-SELECT
+ implicitly commits the previous transaction, and all events
+ forming the statement will be stored the transaction cache. At end
+ of the statement, the entire statement is committed as a
+ transaction, and all events are written to the binary log.
+
+ On the master, the table is locked for the duration of the
+ statement, but since the CREATE part is replicated as a simple
+ statement, there is no way to lock the table for accesses on the
+ slave. Hence, we have to hold on to the CREATE part of the
+ statement until the statement has finished.
+ */
+ class MY_HOOKS : public TABLEOP_HOOKS {
+ public:
+ MY_HOOKS(select_create *x, TABLE_LIST *create_table_arg,
+ TABLE_LIST *select_tables_arg)
+ : ptr(x),
+ create_table(create_table_arg),
+ select_tables(select_tables_arg)
+ {
+ }
+
+ private:
+ virtual int do_postlock(TABLE **tables, uint count)
+ {
+ int error;
+ THD *thd= const_cast<THD*>(ptr->get_thd());
+ TABLE_LIST *save_next_global= create_table->next_global;
+
+ create_table->next_global= select_tables;
+
+ error= thd->decide_logging_format(create_table);
+
+ create_table->next_global= save_next_global;
+
+ if (error)
+ return error;
+
+ TABLE const *const table = *tables;
+ if (thd->is_current_stmt_binlog_format_row() &&
+ !table->s->tmp_table)
+ {
+ if (int error= ptr->binlog_show_create_table(tables, count))
+ return error;
+ }
+ return 0;
+ }
+ select_create *ptr;
+ TABLE_LIST *create_table;
+ TABLE_LIST *select_tables;
+ };
+
+ MY_HOOKS hooks(this, create_table, select_tables);
+ hook_ptr= &hooks;
+
+ unit= u;
+
+ /*
+ Start a statement transaction before the create if we are using
+ row-based replication for the statement. If we are creating a
+ temporary table, we need to start a statement transaction.
+ */
+ if (!thd->lex->create_info.tmp_table() &&
+ thd->is_current_stmt_binlog_format_row() &&
+ mysql_bin_log.is_open())
+ {
+ thd->binlog_start_trans_and_stmt();
+ }
+
+ DEBUG_SYNC(thd,"create_table_select_before_check_if_exists");
+
+ if (!(table= create_table_from_items(thd, create_info, create_table,
+ alter_info, &values,
+ &extra_lock, hook_ptr)))
+ /* abort() deletes table */
+ DBUG_RETURN(-1);
+
+ if (extra_lock)
+ {
+ DBUG_ASSERT(m_plock == NULL);
+
+ if (create_info->tmp_table())
+ m_plock= &m_lock;
+ else
+ m_plock= &thd->extra_lock;
+
+ *m_plock= extra_lock;
+ }
+
+ if (table->s->fields < values.elements)
+ {
+ my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L);
+ DBUG_RETURN(-1);
+ }
+
+ /* First field to copy */
+ field= table->field+table->s->fields - values.elements;
+
+ /* Mark all fields that are given values */
+ for (Field **f= field ; *f ; f++)
+ bitmap_set_bit(table->write_set, (*f)->field_index);
+
+ table->next_number_field=table->found_next_number_field;
+
+ restore_record(table,s->default_values); // Get empty record
+ thd->cuted_fields=0;
+ if (info.ignore || info.handle_duplicates != DUP_ERROR)
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ if (info.handle_duplicates == DUP_REPLACE &&
+ (!table->triggers || !table->triggers->has_delete_triggers()))
+ table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ if (info.handle_duplicates == DUP_UPDATE)
+ table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE);
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ table->file->ha_start_bulk_insert((ha_rows) 0);
+ thd->abort_on_warning= !info.ignore && thd->is_strict_mode();
+ if (check_that_all_fields_are_given_values(thd, table, table_list))
+ DBUG_RETURN(1);
+ table->mark_columns_needed_for_insert();
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ DBUG_RETURN(0);
+}
+
+int
+select_create::binlog_show_create_table(TABLE **tables, uint count)
+{
+ /*
+ Note 1: In RBR mode, we generate a CREATE TABLE statement for the
+ created table by calling show_create_table(). In the event of an error,
+ nothing should be written to the binary log, even if the table is
+ non-transactional; therefore we pretend that the generated CREATE TABLE
+ statement is for a transactional table. The event will then be put in the
+ transaction cache, and any subsequent events (e.g., table-map events and
+ binrow events) will also be put there. We can then use
+ ha_autocommit_or_rollback() to either throw away the entire kaboodle of
+ events, or write them to the binary log.
+
+ We write the CREATE TABLE statement here and not in prepare()
+ since there potentially are sub-selects or accesses to information
+ schema that will do a close_thread_tables(), destroying the
+ statement transaction cache.
+ */
+ DBUG_ASSERT(thd->is_current_stmt_binlog_format_row());
+ DBUG_ASSERT(tables && *tables && count > 0);
+
+ char buf[2048];
+ String query(buf, sizeof(buf), system_charset_info);
+ int result;
+ TABLE_LIST tmp_table_list;
+
+ memset(&tmp_table_list, 0, sizeof(tmp_table_list));
+ tmp_table_list.table = *tables;
+ query.length(0); // Have to zero it since constructor doesn't
+
+ result= show_create_table(thd, &tmp_table_list, &query, create_info,
+ WITH_DB_NAME);
+ DBUG_ASSERT(result == 0); /* show_create_table() always return 0 */
+
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= query_error_code(thd, thd->killed == NOT_KILLED);
+ result= thd->binlog_query(THD::STMT_QUERY_TYPE,
+ query.ptr(), query.length(),
+ /* is_trans */ TRUE,
+ /* direct */ FALSE,
+ /* suppress_use */ FALSE,
+ errcode);
+ }
+ return result;
+}
+
+void select_create::store_values(List<Item> &values)
+{
+ fill_record_n_invoke_before_triggers(thd, table, field, values, 1,
+ TRG_EVENT_INSERT);
+}
+
+
+bool select_create::send_eof()
+{
+ if (select_insert::send_eof())
+ {
+ abort_result_set();
+ return 1;
+ }
+
+ exit_done= 1; // Avoid double calls
+ /*
+ Do an implicit commit at end of statement for non-temporary
+ tables. This can fail, but we should unlock the table
+ nevertheless.
+ */
+ if (!table->s->tmp_table)
+ {
+ trans_commit_stmt(thd);
+ if (!(thd->variables.option_bits & OPTION_GTID_BEGIN))
+ trans_commit_implicit(thd);
+ }
+ else if (!thd->is_current_stmt_binlog_format_row())
+ table->s->table_creation_was_logged= 1;
+
+ table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+
+ if (m_plock)
+ {
+ MYSQL_LOCK *lock= *m_plock;
+ *m_plock= NULL;
+ m_plock= NULL;
+
+ if (create_info->pos_in_locked_tables)
+ {
+ /*
+ If we are under lock tables, we have created a table that was
+ originally locked. We should add back the lock to ensure that
+ all tables in the thd->open_list are locked!
+ */
+ table->mdl_ticket= create_info->mdl_ticket;
+
+ /* The following should never fail, except if out of memory */
+ if (!thd->locked_tables_list.restore_lock(thd,
+ create_info->
+ pos_in_locked_tables,
+ table, lock))
+ return 0; // ok
+ /* Fail. Continue without locking the table */
+ }
+ mysql_unlock_tables(thd, lock);
+ }
+ return 0;
+}
+
+
+void select_create::abort_result_set()
+{
+ ulonglong save_option_bits;
+ DBUG_ENTER("select_create::abort_result_set");
+
+ /* Avoid double calls, could happen in case of out of memory on cleanup */
+ if (exit_done)
+ DBUG_VOID_RETURN;
+ exit_done= 1;
+
+ /*
+ In select_insert::abort_result_set() we roll back the statement, including
+ truncating the transaction cache of the binary log. To do this, we
+ pretend that the statement is transactional, even though it might
+ be the case that it was not.
+
+ We roll back the statement prior to deleting the table and prior
+ to releasing the lock on the table, since there might be potential
+ for failure if the rollback is executed after the drop or after
+ unlocking the table.
+
+ We also roll back the statement regardless of whether the creation
+ of the table succeeded or not, since we need to reset the binary
+ log state.
+
+ However if there was an original table that was deleted, as part of
+ create or replace table, then we must log the statement.
+ */
+
+ save_option_bits= thd->variables.option_bits;
+ thd->variables.option_bits&= ~OPTION_BIN_LOG;
+ select_insert::abort_result_set();
+ thd->transaction.stmt.modified_non_trans_table= FALSE;
+ thd->variables.option_bits= save_option_bits;
+
+ /* possible error of writing binary log is ignored deliberately */
+ (void) thd->binlog_flush_pending_rows_event(TRUE, TRUE);
+
+ if (create_info->table_was_deleted)
+ {
+ /* Unlock locked table that was dropped by CREATE */
+ thd->locked_tables_list.unlock_locked_table(thd,
+ create_info->mdl_ticket);
+ }
+ if (m_plock)
+ {
+ mysql_unlock_tables(thd, *m_plock);
+ *m_plock= NULL;
+ m_plock= NULL;
+ }
+
+ if (table)
+ {
+ bool tmp_table= table->s->tmp_table;
+ table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+ table->auto_increment_field_not_null= FALSE;
+ drop_open_table(thd, table, create_table->db, create_table->table_name);
+ table=0; // Safety
+ if (thd->log_current_statement && mysql_bin_log.is_open())
+ {
+ /* Remove logging of drop, create + insert rows */
+ binlog_reset_cache(thd);
+ /* Original table was deleted. We have to log it */
+ log_drop_table(thd, create_table->db, create_table->db_length,
+ create_table->table_name, create_table->table_name_length,
+ tmp_table);
+ }
+ }
+ DBUG_VOID_RETURN;
+}
diff --git a/sql/sql_insert.h b/sql/sql_insert.h
new file mode 100644
index 00000000000..cbfc1ea9dcd
--- /dev/null
+++ b/sql/sql_insert.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_INSERT_INCLUDED
+#define SQL_INSERT_INCLUDED
+
+#include "sql_class.h" /* enum_duplicates */
+#include "sql_list.h"
+
+/* Instead of including sql_lex.h we add this typedef here */
+typedef List<Item> List_item;
+typedef struct st_copy_info COPY_INFO;
+
+bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, TABLE *table,
+ List<Item> &fields, List_item *values,
+ List<Item> &update_fields,
+ List<Item> &update_values, enum_duplicates duplic,
+ COND **where, bool select_insert,
+ bool check_fields, bool abort_on_warning);
+bool mysql_insert(THD *thd,TABLE_LIST *table,List<Item> &fields,
+ List<List_item> &values, List<Item> &update_fields,
+ List<Item> &update_values, enum_duplicates flag,
+ bool ignore);
+void upgrade_lock_type_for_insert(THD *thd, thr_lock_type *lock_type,
+ enum_duplicates duplic,
+ bool is_multi_insert);
+int check_that_all_fields_are_given_values(THD *thd, TABLE *entry,
+ TABLE_LIST *table_list);
+int write_record(THD *thd, TABLE *table, COPY_INFO *info);
+void kill_delayed_threads(void);
+
+#ifdef EMBEDDED_LIBRARY
+inline void kill_delayed_threads(void) {}
+#endif
+
+#endif /* SQL_INSERT_INCLUDED */
diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc
new file mode 100644
index 00000000000..254b7026e96
--- /dev/null
+++ b/sql/sql_join_cache.cc
@@ -0,0 +1,4668 @@
+/* Copyright (C) 2000-2006 MySQL AB
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @file
+
+ @brief
+ join cache optimizations
+
+ @defgroup Query_Optimizer Query Optimizer
+ @{
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include "key.h"
+#include "sql_base.h"
+#include "sql_select.h"
+#include "opt_subselect.h"
+
+#define NO_MORE_RECORDS_IN_BUFFER (uint)(-1)
+
+static void save_or_restore_used_tabs(JOIN_TAB *join_tab, bool save);
+
+/*****************************************************************************
+ * Join cache module
+******************************************************************************/
+
+/*
+ Fill in the descriptor of a flag field associated with a join cache
+
+ SYNOPSIS
+ add_field_flag_to_join_cache()
+ str position in a record buffer to copy the field from/to
+ length length of the field
+ field IN/OUT pointer to the field descriptor to fill in
+
+ DESCRIPTION
+ The function fill in the descriptor of a cache flag field to which
+ the parameter 'field' points to. The function uses the first two
+ parameters to set the position in the record buffer from/to which
+ the field value is to be copied and the length of the copied fragment.
+ Before returning the result the function increments the value of
+ *field by 1.
+ The function ignores the fields 'blob_length' and 'ofset' of the
+ descriptor.
+
+ RETURN VALUE
+ the length of the field
+*/
+
+static
+uint add_flag_field_to_join_cache(uchar *str, uint length, CACHE_FIELD **field)
+{
+ CACHE_FIELD *copy= *field;
+ copy->str= str;
+ copy->length= length;
+ copy->type= 0;
+ copy->field= 0;
+ copy->referenced_field_no= 0;
+ (*field)++;
+ return length;
+}
+
+
+/*
+ Fill in the descriptors of table data fields associated with a join cache
+
+ SYNOPSIS
+ add_table_data_fields_to_join_cache()
+ tab descriptors of fields from this table are to be filled
+ field_set descriptors for only these fields are to be created
+ field_cnt IN/OUT counter of data fields
+ descr IN/OUT pointer to the first descriptor to be filled
+ field_ptr_cnt IN/OUT counter of pointers to the data fields
+ descr_ptr IN/OUT pointer to the first pointer to blob descriptors
+
+ DESCRIPTION
+ The function fills in the descriptors of cache data fields from the table
+ 'tab'. The descriptors are filled only for the fields marked in the
+ bitmap 'field_set'.
+ The function fills the descriptors starting from the position pointed
+ by 'descr'. If an added field is of a BLOB type then a pointer to the
+ its descriptor is added to the array descr_ptr.
+ At the return 'descr' points to the position after the last added
+ descriptor while 'descr_ptr' points to the position right after the
+ last added pointer.
+
+ RETURN VALUE
+ the total length of the added fields
+*/
+
+static
+uint add_table_data_fields_to_join_cache(JOIN_TAB *tab,
+ MY_BITMAP *field_set,
+ uint *field_cnt,
+ CACHE_FIELD **descr,
+ uint *field_ptr_cnt,
+ CACHE_FIELD ***descr_ptr)
+{
+ Field **fld_ptr;
+ uint len= 0;
+ CACHE_FIELD *copy= *descr;
+ CACHE_FIELD **copy_ptr= *descr_ptr;
+ uint used_fields= bitmap_bits_set(field_set);
+ for (fld_ptr= tab->table->field; used_fields; fld_ptr++)
+ {
+ if (bitmap_is_set(field_set, (*fld_ptr)->field_index))
+ {
+ len+= (*fld_ptr)->fill_cache_field(copy);
+ if (copy->type == CACHE_BLOB)
+ {
+ *copy_ptr= copy;
+ copy_ptr++;
+ (*field_ptr_cnt)++;
+ }
+ copy->field= *fld_ptr;
+ copy->referenced_field_no= 0;
+ copy++;
+ (*field_cnt)++;
+ used_fields--;
+ }
+ }
+ *descr= copy;
+ *descr_ptr= copy_ptr;
+ return len;
+}
+
+/*
+ Determine different counters of fields associated with a record in the cache
+
+ SYNOPSIS
+ calc_record_fields()
+
+ DESCRIPTION
+ The function counts the number of total fields stored in a record
+ of the cache and saves this number in the 'fields' member. It also
+ determines the number of flag fields and the number of blobs.
+ The function sets 'with_match_flag' on if 'join_tab' needs a match flag
+ i.e. if it is the first inner table of an outer join or a semi-join.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE::calc_record_fields()
+{
+ JOIN_TAB *tab;
+
+ if (prev_cache)
+ tab= prev_cache->join_tab;
+ else
+ {
+ if (join_tab->bush_root_tab)
+ {
+ /*
+ --ot1--SJM1--------------ot2--...
+ |
+ |
+ +-it1--...--itN
+ ^____________ this->join_tab is somewhere here,
+ inside an sjm nest.
+
+ The join buffer should store the values of it1.*, it2.*, ..
+ It should not store values of ot1.*.
+ */
+ tab= join_tab->bush_root_tab->bush_children->start;
+ }
+ else
+ {
+ /*
+ -ot1--ot2--SJM1--SJM2--------------ot3--...--otN
+ | | ^
+ | +-it21--...--it2N |
+ | \-- we're somewhere here,
+ +-it11--...--it1N at the top level
+
+ The join buffer should store the values of
+
+ ot1.*, ot2.*, it1{i}, it2{j}.*, ot3.*, ...
+
+ that is, we should start from the first non-const top-level table.
+
+ We will need to store columns of SJ-inner tables (it_X_Y.*), but we're
+ not interested in storing the columns of materialization tables
+ themselves. Beause of that, if the first non-const top-level table is a
+ materialized table, we move to its bush_children:
+ */
+ tab= join->join_tab + join->const_tables;
+ if (tab->bush_children)
+ tab= tab->bush_children->start;
+ }
+ }
+ DBUG_ASSERT(!tab->bush_children);
+
+ start_tab= tab;
+ fields= 0;
+ blobs= 0;
+ flag_fields= 0;
+ data_field_count= 0;
+ data_field_ptr_count= 0;
+ referenced_fields= 0;
+
+ /*
+ The following loop will get inside SJM nests, because data may be unpacked
+ to sjm-inner tables.
+ */
+ for (; tab != join_tab ; tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ tab->calc_used_field_length(FALSE);
+ flag_fields+= MY_TEST(tab->used_null_fields || tab->used_uneven_bit_fields);
+ flag_fields+= MY_TEST(tab->table->maybe_null);
+ fields+= tab->used_fields;
+ blobs+= tab->used_blobs;
+ }
+ if ((with_match_flag= join_tab->use_match_flag()))
+ flag_fields++;
+ fields+= flag_fields;
+}
+
+
+/*
+ Collect information on join key arguments
+
+ SYNOPSIS
+ collect_info_on_key_args()
+
+ DESCRIPTION
+ The function traverses the ref expressions that are used to access the
+ joined table join_tab. For each table 'tab' whose fields are to be stored
+ in the join buffer of the cache the function finds the fields from 'tab'
+ that occur in the ref expressions and marks these fields in the bitmap
+ tab->table->tmp_set. The function counts the number of them stored
+ in this cache and the total number of them stored in the previous caches
+ and saves the results of the counting in 'local_key_arg_fields' and
+ 'external_key_arg_fields' respectively.
+
+ NOTES
+ The function does not do anything if no key is used to join the records
+ from join_tab.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE::collect_info_on_key_args()
+{
+ JOIN_TAB *tab;
+ JOIN_CACHE *cache;
+ local_key_arg_fields= 0;
+ external_key_arg_fields= 0;
+
+ if (!is_key_access())
+ return;
+
+ TABLE_REF *ref= &join_tab->ref;
+ cache= this;
+ do
+ {
+ for (tab= cache->start_tab; tab != cache->join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ uint key_args;
+ bitmap_clear_all(&tab->table->tmp_set);
+ for (uint i= 0; i < ref->key_parts; i++)
+ {
+ Item *ref_item= ref->items[i];
+ if (!(tab->table->map & ref_item->used_tables()))
+ continue;
+ ref_item->walk(&Item::add_field_to_set_processor, 1,
+ (uchar *) tab->table);
+ }
+ if ((key_args= bitmap_bits_set(&tab->table->tmp_set)))
+ {
+ if (cache == this)
+ local_key_arg_fields+= key_args;
+ else
+ external_key_arg_fields+= key_args;
+ }
+ }
+ cache= cache->prev_cache;
+ }
+ while (cache);
+
+ return;
+}
+
+
+/*
+ Allocate memory for descriptors and pointers to them associated with the cache
+
+ SYNOPSIS
+ alloc_fields()
+
+ DESCRIPTION
+ The function allocates memory for the array of fields descriptors
+ and the array of pointers to the field descriptors used to copy
+ join record data from record buffers into the join buffer and
+ backward. Some pointers refer to the field descriptor associated
+ with previous caches. They are placed at the beginning of the array
+ of pointers and its total number is stored in external_key_arg_fields.
+ The pointer of the first array is assigned to field_descr and the number
+ of the elements in it is precalculated by the function calc_record_fields.
+ The allocated arrays are adjacent.
+
+ NOTES
+ The memory is allocated in join->thd->memroot
+
+ RETURN VALUE
+ pointer to the first array
+*/
+
+int JOIN_CACHE::alloc_fields()
+{
+ uint ptr_cnt= external_key_arg_fields+blobs+1;
+ uint fields_size= sizeof(CACHE_FIELD)*fields;
+ field_descr= (CACHE_FIELD*) sql_alloc(fields_size +
+ sizeof(CACHE_FIELD*)*ptr_cnt);
+ blob_ptr= (CACHE_FIELD **) ((uchar *) field_descr + fields_size);
+ return (field_descr == NULL);
+}
+
+
+/*
+ Create descriptors of the record flag fields stored in the join buffer
+
+ SYNOPSIS
+ create_flag_fields()
+
+ DESCRIPTION
+ The function creates descriptors of the record flag fields stored
+ in the join buffer. These are descriptors for:
+ - an optional match flag field,
+ - table null bitmap fields,
+ - table null row fields.
+ The match flag field is created when 'join_tab' is the first inner
+ table of an outer join our a semi-join. A null bitmap field is
+ created for any table whose fields are to be stored in the join
+ buffer if at least one of these fields is nullable or is a BIT field
+ whose bits are partially stored with null bits. A null row flag
+ is created for any table assigned to the cache if it is an inner
+ table of an outer join.
+ The descriptor for flag fields are placed one after another at the
+ beginning of the array of field descriptors 'field_descr' that
+ contains 'fields' elements. If there is a match flag field the
+ descriptor for it is always first in the sequence of flag fields.
+ The descriptors for other flag fields can follow in an arbitrary
+ order.
+ The flag field values follow in a record stored in the join buffer
+ in the same order as field descriptors, with the match flag always
+ following first.
+ The function sets the value of 'flag_fields' to the total number
+ of the descriptors created for the flag fields.
+ The function sets the value of 'length' to the total length of the
+ flag fields.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE::create_flag_fields()
+{
+ CACHE_FIELD *copy;
+ JOIN_TAB *tab;
+
+ copy= field_descr;
+
+ length=0;
+
+ /* If there is a match flag the first field is always used for this flag */
+ if (with_match_flag)
+ length+= add_flag_field_to_join_cache((uchar*) &join_tab->found,
+ sizeof(join_tab->found),
+ &copy);
+
+ /* Create fields for all null bitmaps and null row flags that are needed */
+ for (tab= start_tab; tab != join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ TABLE *table= tab->table;
+
+ /* Create a field for the null bitmap from table if needed */
+ if (tab->used_null_fields || tab->used_uneven_bit_fields)
+ length+= add_flag_field_to_join_cache(table->null_flags,
+ table->s->null_bytes,
+ &copy);
+
+ /* Create table for the null row flag if needed */
+ if (table->maybe_null)
+ length+= add_flag_field_to_join_cache((uchar*) &table->null_row,
+ sizeof(table->null_row),
+ &copy);
+ }
+
+ /* Theoretically the new value of flag_fields can be less than the old one */
+ flag_fields= copy-field_descr;
+}
+
+
+/*
+ Create descriptors of the fields used to build access keys to the joined table
+
+ SYNOPSIS
+ create_key_arg_fields()
+
+ DESCRIPTION
+ The function creates descriptors of the record fields stored in the join
+ buffer that are used to build access keys to the joined table. These
+ fields are put into the buffer ahead of other records fields stored in
+ the buffer. Such placement helps to optimize construction of access keys.
+ For each field that is used to build access keys to the joined table but
+ is stored in some other join cache buffer the function saves a pointer
+ to the the field descriptor. The array of such pointers are placed in the
+ the join cache structure just before the array of pointers to the
+ blob fields blob_ptr.
+ Any field stored in a join cache buffer that is used to construct keys
+ to access tables associated with other join caches is called a referenced
+ field. It receives a unique number that is saved by the function in the
+ member 'referenced_field_no' of the CACHE_FIELD descriptor for the field.
+ This number is used as index to the array of offsets to the referenced
+ fields that are saved and put in the join cache buffer after all record
+ fields.
+ The function also finds out whether that the keys to access join_tab
+ can be considered as embedded and, if so, sets the flag 'use_emb_key' in
+ this join cache appropriately.
+
+ NOTES.
+ When a key to access the joined table 'join_tab' is constructed the array
+ of pointers to the field descriptors for the external fields is looked
+ through. For each of this pointers we find out in what previous key cache
+ the referenced field is stored. The value of 'referenced_field_no'
+ provides us with the index into the array of offsets for referenced
+ fields stored in the join cache. The offset read by the the index allows
+ us to read the field without reading all other fields of the record
+ stored the join cache buffer. This optimizes the construction of keys
+ to access 'join_tab' when some key arguments are stored in the previous
+ join caches.
+
+ NOTES
+ The function does not do anything if no key is used to join the records
+ from join_tab.
+
+ RETURN VALUE
+ none
+*/
+void JOIN_CACHE::create_key_arg_fields()
+{
+ JOIN_TAB *tab;
+ JOIN_CACHE *cache;
+
+ if (!is_key_access())
+ return;
+
+ /*
+ Save pointers to the cache fields in previous caches
+ that are used to build keys for this key access.
+ */
+ cache= this;
+ uint ext_key_arg_cnt= external_key_arg_fields;
+ CACHE_FIELD *copy;
+ CACHE_FIELD **copy_ptr= blob_ptr;
+ while (ext_key_arg_cnt)
+ {
+ cache= cache->prev_cache;
+ for (tab= cache->start_tab; tab != cache->join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ CACHE_FIELD *copy_end;
+ MY_BITMAP *key_read_set= &tab->table->tmp_set;
+ /* key_read_set contains the bitmap of tab's fields referenced by ref */
+ if (bitmap_is_clear_all(key_read_set))
+ continue;
+ copy_end= cache->field_descr+cache->fields;
+ for (copy= cache->field_descr+cache->flag_fields; copy < copy_end; copy++)
+ {
+ /*
+ (1) - when we store rowids for DuplicateWeedout, they have
+ copy->field==NULL
+ */
+ if (copy->field && // (1)
+ copy->field->table == tab->table &&
+ bitmap_is_set(key_read_set, copy->field->field_index))
+ {
+ *copy_ptr++= copy;
+ ext_key_arg_cnt--;
+ if (!copy->referenced_field_no)
+ {
+ /*
+ Register the referenced field 'copy':
+ - set the offset number in copy->referenced_field_no,
+ - adjust the value of the flag 'with_length',
+ - adjust the values of 'pack_length' and
+ of 'pack_length_with_blob_ptrs'.
+ */
+ copy->referenced_field_no= ++cache->referenced_fields;
+ if (!cache->with_length)
+ {
+ cache->with_length= TRUE;
+ uint sz= cache->get_size_of_rec_length();
+ cache->base_prefix_length+= sz;
+ cache->pack_length+= sz;
+ cache->pack_length_with_blob_ptrs+= sz;
+ }
+ cache->pack_length+= cache->get_size_of_fld_offset();
+ cache->pack_length_with_blob_ptrs+= cache->get_size_of_fld_offset();
+ }
+ }
+ }
+ }
+ }
+ /* After this 'blob_ptr' shall not be be changed */
+ blob_ptr= copy_ptr;
+
+ /* Now create local fields that are used to build ref for this key access */
+ copy= field_descr+flag_fields;
+ for (tab= start_tab; tab != join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ length+= add_table_data_fields_to_join_cache(tab, &tab->table->tmp_set,
+ &data_field_count, &copy,
+ &data_field_ptr_count,
+ &copy_ptr);
+ }
+
+ use_emb_key= check_emb_key_usage();
+
+ return;
+}
+
+
+/*
+ Create descriptors of all remaining data fields stored in the join buffer
+
+ SYNOPSIS
+ create_remaining_fields()
+
+ DESCRIPTION
+ The function creates descriptors for all remaining data fields of a
+ record from the join buffer. If the value returned by is_key_access() is
+ false the function creates fields for all read record fields that
+ comprise the partial join record joined with join_tab. Otherwise,
+ for each table tab, the set of the read fields for which the descriptors
+ have to be added is determined as the difference between all read fields
+ and and those for which the descriptors have been already created.
+ The latter are supposed to be marked in the bitmap tab->table->tmp_set.
+ The function increases the value of 'length' to the the total length of
+ the added fields.
+
+ NOTES
+ If is_key_access() returns true the function modifies the value of
+ tab->table->tmp_set for a each table whose fields are stored in the cache.
+ The function calls the method Field::fill_cache_field to figure out
+ the type of the cache field and the maximal length of its representation
+ in the join buffer. If this is a blob field then additionally a pointer
+ to this field is added as an element of the array blob_ptr. For a blob
+ field only the size of the length of the blob data is taken into account.
+ It is assumed that 'data_field_count' contains the number of descriptors
+ for data fields that have been already created and 'data_field_ptr_count'
+ contains the number of the pointers to such descriptors having been
+ stored up to the moment.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE::create_remaining_fields()
+{
+ JOIN_TAB *tab;
+ bool all_read_fields= !is_key_access();
+ CACHE_FIELD *copy= field_descr+flag_fields+data_field_count;
+ CACHE_FIELD **copy_ptr= blob_ptr+data_field_ptr_count;
+
+ for (tab= start_tab; tab != join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ MY_BITMAP *rem_field_set;
+ TABLE *table= tab->table;
+
+ if (all_read_fields)
+ rem_field_set= table->read_set;
+ else
+ {
+ bitmap_invert(&table->tmp_set);
+ bitmap_intersect(&table->tmp_set, table->read_set);
+ rem_field_set= &table->tmp_set;
+ }
+
+ length+= add_table_data_fields_to_join_cache(tab, rem_field_set,
+ &data_field_count, &copy,
+ &data_field_ptr_count,
+ &copy_ptr);
+
+ /* SemiJoinDuplicateElimination: allocate space for rowid if needed */
+ if (tab->keep_current_rowid)
+ {
+ copy->str= table->file->ref;
+ if (copy->str)
+ copy->length= table->file->ref_length;
+ else
+ {
+ /* This may happen only for materialized derived tables and views */
+ copy->length= 0;
+ copy->str= (uchar *) table;
+ }
+ copy->type= CACHE_ROWID;
+ copy->field= 0;
+ copy->referenced_field_no= 0;
+ /*
+ Note: this may seem odd, but at this point we have
+ table->file->ref==NULL while table->file->ref_length is already set
+ to correct value.
+ */
+ length += table->file->ref_length;
+ data_field_count++;
+ copy++;
+ }
+ }
+}
+
+
+
+/*
+ Calculate and set all cache constants
+
+ SYNOPSIS
+ set_constants()
+
+ DESCRIPTION
+ The function calculates and set all precomputed constants that are used
+ when writing records into the join buffer and reading them from it.
+ It calculates the size of offsets of a record within the join buffer
+ and of a field within a record. It also calculates the number of bytes
+ used to store record lengths.
+ The function also calculates the maximal length of the representation
+ of record in the cache excluding blob_data. This value is used when
+ making a dicision whether more records should be added into the join
+ buffer or not.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE::set_constants()
+{
+ /*
+ Any record from a BKA cache is prepended with the record length.
+ We use the record length when reading the buffer and building key values
+ for each record. The length allows us not to read the fields that are
+ not needed for keys.
+ If a record has match flag it also may be skipped when the match flag
+ is on. It happens if the cache is used for a semi-join operation or
+ for outer join when the 'not exist' optimization can be applied.
+ If some of the fields are referenced from other caches then
+ the record length allows us to easily reach the saved offsets for
+ these fields since the offsets are stored at the very end of the record.
+ However at this moment we don't know whether we have referenced fields for
+ the cache or not. Later when a referenced field is registered for the cache
+ we adjust the value of the flag 'with_length'.
+ */
+ with_length= is_key_access() ||
+ join_tab->is_inner_table_of_semi_join_with_first_match() ||
+ join_tab->is_inner_table_of_outer_join();
+ /*
+ At this moment we don't know yet the value of 'referenced_fields',
+ but in any case it can't be greater than the value of 'fields'.
+ */
+ uint len= length + fields*sizeof(uint)+blobs*sizeof(uchar *) +
+ (prev_cache ? prev_cache->get_size_of_rec_offset() : 0) +
+ sizeof(ulong);
+ /*
+ The values of size_of_rec_ofs, size_of_rec_len, size_of_fld_ofs,
+ base_prefix_length, pack_length, pack_length_with_blob_ptrs
+ will be recalculated later in this function when we get the estimate
+ for the actual value of the join buffer size.
+ */
+ size_of_rec_ofs= size_of_rec_len= size_of_fld_ofs= 4;
+ base_prefix_length= (with_length ? size_of_rec_len : 0) +
+ (prev_cache ? prev_cache->get_size_of_rec_offset() : 0);
+ pack_length= (with_length ? size_of_rec_len : 0) +
+ (prev_cache ? prev_cache->get_size_of_rec_offset() : 0) +
+ length + fields*sizeof(uint);
+ pack_length_with_blob_ptrs= pack_length + blobs*sizeof(uchar *);
+ min_buff_size= 0;
+ min_records= 1;
+ buff_size= MY_MAX(join->thd->variables.join_buff_size,
+ get_min_join_buffer_size());
+ size_of_rec_ofs= offset_size(buff_size);
+ size_of_rec_len= blobs ? size_of_rec_ofs : offset_size(len);
+ size_of_fld_ofs= size_of_rec_len;
+ base_prefix_length= (with_length ? size_of_rec_len : 0) +
+ (prev_cache ? prev_cache->get_size_of_rec_offset() : 0);
+ /*
+ The size of the offsets for referenced fields will be added later.
+ The values of 'pack_length' and 'pack_length_with_blob_ptrs' are adjusted
+ every time when the first reference to the referenced field is registered.
+ */
+ pack_length= (with_length ? size_of_rec_len : 0) +
+ (prev_cache ? prev_cache->get_size_of_rec_offset() : 0) +
+ length;
+ pack_length_with_blob_ptrs= pack_length + blobs*sizeof(uchar *);
+}
+
+
+/*
+ Get maximum total length of all affixes of a record in the join cache buffer
+
+ SYNOPSIS
+ get_record_max_affix_length()
+
+ DESCRIPTION
+ The function calculates the maximum possible total length of all affixes
+ of a record in the join cache buffer, that is made of:
+ - the length of all prefixes used in this cache,
+ - the length of the match flag if it's needed
+ - the total length of the maximum possible offsets to the fields of
+ a record in the buffer.
+
+ RETURN VALUE
+ The maximum total length of all affixes of a record in the join buffer
+*/
+
+uint JOIN_CACHE::get_record_max_affix_length()
+{
+ uint len= get_prefix_length() +
+ MY_TEST(with_match_flag) +
+ size_of_fld_ofs * data_field_count;
+ return len;
+}
+
+
+/*
+ Get the minimum possible size of the cache join buffer
+
+ SYNOPSIS
+ get_min_join_buffer_size()
+
+ DESCRIPTION
+ At the first its invocation for the cache the function calculates the
+ minimum possible size of the join buffer of the cache. This value depends
+ on the minimal number of records 'min_records' to be stored in the join
+ buffer. The number is supposed to be determined by the procedure that
+ chooses the best access path to the joined table join_tab in the execution
+ plan. After the calculation of the interesting size the function saves it
+ in the field 'min_buff_size' in order to use it directly at the next
+ invocations of the function.
+
+ NOTES
+ Currently the number of minimal records is just set to 1.
+
+ RETURN VALUE
+ The minimal possible size of the join buffer of this cache
+*/
+
+ulong JOIN_CACHE::get_min_join_buffer_size()
+{
+ if (!min_buff_size)
+ {
+ size_t len= 0;
+ size_t len_last= 0;
+ for (JOIN_TAB *tab= start_tab; tab != join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ len+= tab->get_max_used_fieldlength();
+ len_last+= tab->get_used_fieldlength();
+ }
+ size_t len_addon= get_record_max_affix_length() +
+ get_max_key_addon_space_per_record();
+ len+= len_addon;
+ len_last+= len_addon;
+ size_t min_sz= len*(min_records-1) + len_last;
+ min_sz+= pack_length_with_blob_ptrs;
+ size_t add_sz= 0;
+ for (uint i=0; i < min_records; i++)
+ add_sz+= join_tab_scan->aux_buffer_incr(i+1);
+ avg_aux_buffer_incr= add_sz/min_records;
+ min_sz+= add_sz;
+ set_if_bigger(min_sz, 1);
+ min_buff_size= min_sz;
+ }
+ return min_buff_size;
+}
+
+
+/*
+ Get the maximum possible size of the cache join buffer
+
+ SYNOPSIS
+ get_max_join_buffer_size()
+
+ optimize_buff_size FALSE <-> do not take more memory than needed for
+ the estimated number of records in the partial join
+
+ DESCRIPTION
+ At the first its invocation for the cache the function calculates the
+ maximum possible size of join buffer for the cache. If the parameter
+ optimize_buff_size true then this value does not exceed the size of the
+ space needed for the estimated number of records 'max_records' in the
+ partial join that joins tables from the first one through join_tab. This
+ value is also capped off by the value of join_tab->join_buffer_size_limit,
+ if it has been set a to non-zero value, and by the value of the system
+ parameter join_buffer_size - otherwise. After the calculation of the
+ interesting size the function saves the value in the field 'max_buff_size'
+ in order to use it directly at the next invocations of the function.
+
+ NOTES
+ Currently the value of join_tab->join_buffer_size_limit is initialized
+ to 0 and is never reset.
+
+ RETURN VALUE
+ The maximum possible size of the join buffer of this cache
+*/
+
+ulong JOIN_CACHE::get_max_join_buffer_size(bool optimize_buff_size)
+{
+ if (!max_buff_size)
+ {
+ size_t max_sz;
+ size_t min_sz= get_min_join_buffer_size();
+ size_t len= 0;
+ for (JOIN_TAB *tab= start_tab; tab != join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ len+= tab->get_used_fieldlength();
+ }
+ len+= get_record_max_affix_length();
+ avg_record_length= len;
+ len+= get_max_key_addon_space_per_record() + avg_aux_buffer_incr;
+ space_per_record= len;
+
+ size_t limit_sz= join->thd->variables.join_buff_size;
+ if (join_tab->join_buffer_size_limit)
+ set_if_smaller(limit_sz, join_tab->join_buffer_size_limit);
+ if (!optimize_buff_size)
+ max_sz= limit_sz;
+ else
+ {
+ if (limit_sz / max_records > space_per_record)
+ max_sz= space_per_record * max_records;
+ else
+ max_sz= limit_sz;
+ max_sz+= pack_length_with_blob_ptrs;
+ set_if_smaller(max_sz, limit_sz);
+ }
+ set_if_bigger(max_sz, min_sz);
+ max_buff_size= max_sz;
+ }
+ return max_buff_size;
+}
+
+
+/*
+ Allocate memory for a join buffer
+
+ SYNOPSIS
+ alloc_buffer()
+
+ DESCRIPTION
+ The function allocates a lump of memory for the cache join buffer.
+ Initially the function sets the size of the buffer buff_size equal to
+ the value returned by get_max_join_buffer_size(). If the total size of
+ the space intended to be used for the join buffers employed by the
+ tables from the first one through join_tab exceeds the value of the
+ system parameter join_buff_space_limit, then the function first tries
+ to shrink the used buffers to make the occupied space fit the maximum
+ memory allowed to be used for all join buffers in total. After
+ this the function tries to allocate a join buffer for join_tab.
+ If it fails to do so, it decrements the requested size of the join
+ buffer, shrinks proportionally the join buffers used for the previous
+ tables and tries to allocate a buffer for join_tab. In the case of a
+ failure the function repeats its attempts with smaller and smaller
+ requested sizes of the buffer, but not more than 4 times.
+
+ RETURN VALUE
+ 0 if the memory has been successfully allocated
+ 1 otherwise
+*/
+
+int JOIN_CACHE::alloc_buffer()
+{
+ JOIN_TAB *tab;
+ JOIN_CACHE *cache;
+ ulonglong curr_buff_space_sz= 0;
+ ulonglong curr_min_buff_space_sz= 0;
+ ulonglong join_buff_space_limit=
+ join->thd->variables.join_buff_space_limit;
+ bool optimize_buff_size=
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_OPTIMIZE_JOIN_BUFFER_SIZE);
+ double partial_join_cardinality= (join_tab-1)->get_partial_join_cardinality();
+ buff= NULL;
+ min_buff_size= 0;
+ max_buff_size= 0;
+ min_records= 1;
+ max_records= (size_t) (partial_join_cardinality <= join_buff_space_limit ?
+ (ulonglong) partial_join_cardinality : join_buff_space_limit);
+ set_if_bigger(max_records, 10);
+ min_buff_size= get_min_join_buffer_size();
+ buff_size= get_max_join_buffer_size(optimize_buff_size);
+
+ for (tab= start_tab; tab!= join_tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ cache= tab->cache;
+ if (cache)
+ {
+ curr_min_buff_space_sz+= cache->get_min_join_buffer_size();
+ curr_buff_space_sz+= cache->get_join_buffer_size();
+ }
+ }
+ curr_min_buff_space_sz+= min_buff_size;
+ curr_buff_space_sz+= buff_size;
+
+ if (curr_min_buff_space_sz > join_buff_space_limit ||
+ (curr_buff_space_sz > join_buff_space_limit &&
+ (!optimize_buff_size ||
+ join->shrink_join_buffers(join_tab, curr_buff_space_sz,
+ join_buff_space_limit))))
+ goto fail;
+
+ if (for_explain_only)
+ return 0;
+
+ for (ulong buff_size_decr= (buff_size-min_buff_size)/4 + 1; ; )
+ {
+ ulong next_buff_size;
+
+ if ((buff= (uchar*) my_malloc(buff_size, MYF(MY_THREAD_SPECIFIC))))
+ break;
+
+ next_buff_size= buff_size > buff_size_decr ? buff_size-buff_size_decr : 0;
+ if (next_buff_size < min_buff_size ||
+ join->shrink_join_buffers(join_tab, curr_buff_space_sz,
+ curr_buff_space_sz-buff_size_decr))
+ goto fail;
+ buff_size= next_buff_size;
+
+ curr_buff_space_sz= 0;
+ for (tab= join->join_tab+join->const_tables; tab <= join_tab; tab++)
+ {
+ cache= tab->cache;
+ if (cache)
+ curr_buff_space_sz+= cache->get_join_buffer_size();
+ }
+ }
+ return 0;
+
+fail:
+ buff_size= 0;
+ return 1;
+}
+
+
+/*
+ Shrink the size if the cache join buffer in a given ratio
+
+ SYNOPSIS
+ shrink_join_buffer_in_ratio()
+ n nominator of the ratio to shrink the buffer in
+ d denominator if the ratio
+
+ DESCRIPTION
+ The function first deallocates the join buffer of the cache. Then
+ it allocates a buffer that is (n/d) times smaller.
+
+ RETURN VALUE
+ FALSE on success with allocation of the smaller join buffer
+ TRUE otherwise
+*/
+
+bool JOIN_CACHE::shrink_join_buffer_in_ratio(ulonglong n, ulonglong d)
+{
+ size_t next_buff_size;
+ if (n < d)
+ return FALSE;
+ next_buff_size= (size_t) ((double) buff_size / n * d);
+ set_if_bigger(next_buff_size, min_buff_size);
+ buff_size= next_buff_size;
+ return realloc_buffer();
+}
+
+
+/*
+ Reallocate the join buffer of a join cache
+
+ SYNOPSIS
+ realloc_buffer()
+
+ DESCRITION
+ The function reallocates the join buffer of the join cache. After this
+ it resets the buffer for writing.
+
+ NOTES
+ The function assumes that buff_size contains the new value for the join
+ buffer size.
+
+ RETURN VALUE
+ 0 if the buffer has been successfully reallocated
+ 1 otherwise
+*/
+
+int JOIN_CACHE::realloc_buffer()
+{
+ int rc;
+ free();
+ rc= MY_TEST(!(buff= (uchar*) my_malloc(buff_size, MYF(MY_THREAD_SPECIFIC))));
+ reset(TRUE);
+ return rc;
+}
+
+
+/*
+ Initialize a join cache
+
+ SYNOPSIS
+ init()
+ for_explain join buffer is initialized for explain only
+
+ DESCRIPTION
+ The function initializes the join cache structure. It supposed to be called
+ by init methods for classes derived from the JOIN_CACHE.
+ The function allocates memory for the join buffer and for descriptors of
+ the record fields stored in the buffer.
+
+ NOTES
+ The code of this function should have been included into the constructor
+ code itself. However the new operator for the class JOIN_CACHE would
+ never fail while memory allocation for the join buffer is not absolutely
+ unlikely to fail. That's why this memory allocation has to be placed in a
+ separate function that is called in a couple with a cache constructor.
+ It is quite natural to put almost all other constructor actions into
+ this function.
+
+ RETURN VALUE
+ 0 initialization with buffer allocations has been succeeded
+ 1 otherwise
+*/
+
+int JOIN_CACHE::init(bool for_explain)
+{
+ DBUG_ENTER("JOIN_CACHE::init");
+
+ for_explain_only= for_explain;
+
+ calc_record_fields();
+
+ collect_info_on_key_args();
+
+ if (alloc_fields())
+ DBUG_RETURN(1);
+
+ create_flag_fields();
+
+ create_key_arg_fields();
+
+ create_remaining_fields();
+
+ set_constants();
+
+ if (alloc_buffer())
+ DBUG_RETURN(1);
+
+ reset(TRUE);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Check the possibility to read the access keys directly from the join buffer
+ SYNOPSIS
+ check_emb_key_usage()
+
+ DESCRIPTION
+ The function checks some conditions at which the key values can be read
+ directly from the join buffer. This is possible when the key values can be
+ composed by concatenation of the record fields stored in the join buffer.
+ Sometimes when the access key is multi-component the function has to re-order
+ the fields written into the join buffer to make keys embedded. If key
+ values for the key access are detected as embedded then 'use_emb_key'
+ is set to TRUE.
+
+ EXAMPLE
+ Let table t2 has an index defined on the columns a,b . Let's assume also
+ that the columns t2.a, t2.b as well as the columns t1.a, t1.b are all
+ of the integer type. Then if the query
+ SELECT COUNT(*) FROM t1, t2 WHERE t1.a=t2.a and t1.b=t2.b
+ is executed with a join cache in such a way that t1 is the driving
+ table then the key values to access table t2 can be read directly
+ from the join buffer.
+
+ NOTES
+ In some cases key values could be read directly from the join buffer but
+ we still do not consider them embedded. In the future we'll expand the
+ the class of keys which we identify as embedded.
+
+ NOTES
+ The function returns FALSE if no key is used to join the records
+ from join_tab.
+
+ RETURN VALUE
+ TRUE key values will be considered as embedded,
+ FALSE otherwise.
+*/
+
+bool JOIN_CACHE::check_emb_key_usage()
+{
+
+ if (!is_key_access())
+ return FALSE;
+
+ uint i;
+ Item *item;
+ KEY_PART_INFO *key_part;
+ CACHE_FIELD *copy;
+ CACHE_FIELD *copy_end;
+ uint len= 0;
+ TABLE_REF *ref= &join_tab->ref;
+ KEY *keyinfo= join_tab->get_keyinfo_by_key_no(ref->key);
+
+ /*
+ If some of the key arguments are not from the local cache the key
+ is not considered as embedded.
+ TODO:
+ Expand it to the case when ref->key_parts=1 and local_key_arg_fields=0.
+ */
+ if (external_key_arg_fields != 0)
+ return FALSE;
+ /*
+ If the number of the local key arguments is not equal to the number
+ of key parts the key value cannot be read directly from the join buffer.
+ */
+ if (local_key_arg_fields != ref->key_parts)
+ return FALSE;
+
+ /*
+ A key is not considered embedded if one of the following is true:
+ - one of its key parts is not equal to a field
+ - it is a partial key
+ - definition of the argument field does not coincide with the
+ definition of the corresponding key component
+ - some of the key components are nullable
+ */
+ for (i=0; i < ref->key_parts; i++)
+ {
+ item= ref->items[i]->real_item();
+ if (item->type() != Item::FIELD_ITEM)
+ return FALSE;
+ key_part= keyinfo->key_part+i;
+ if (key_part->key_part_flag & HA_PART_KEY_SEG)
+ return FALSE;
+ if (!key_part->field->eq_def(((Item_field *) item)->field))
+ return FALSE;
+ if (key_part->field->maybe_null())
+ return FALSE;
+ }
+
+ copy= field_descr+flag_fields;
+ copy_end= copy+local_key_arg_fields;
+ for ( ; copy < copy_end; copy++)
+ {
+ /*
+ If some of the key arguments are of variable length the key
+ is not considered as embedded.
+ */
+ if (copy->type != 0)
+ return FALSE;
+ /*
+ If some of the key arguments are bit fields whose bits are partially
+ stored with null bits the key is not considered as embedded.
+ */
+ if (copy->field->type() == MYSQL_TYPE_BIT &&
+ ((Field_bit*) (copy->field))->bit_len)
+ return FALSE;
+ len+= copy->length;
+ }
+
+ emb_key_length= len;
+
+ /*
+ Make sure that key fields follow the order of the corresponding
+ key components these fields are equal to. For this the descriptors
+ of the fields that comprise the key might be re-ordered.
+ */
+ for (i= 0; i < ref->key_parts; i++)
+ {
+ uint j;
+ Item *item= ref->items[i]->real_item();
+ Field *fld= ((Item_field *) item)->field;
+ CACHE_FIELD *init_copy= field_descr+flag_fields+i;
+ for (j= i, copy= init_copy; i < local_key_arg_fields; i++, copy++)
+ {
+ if (fld->eq(copy->field))
+ {
+ if (j != i)
+ {
+ CACHE_FIELD key_part_copy= *copy;
+ *copy= *init_copy;
+ *init_copy= key_part_copy;
+ }
+ break;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+
+/*
+ Write record fields and their required offsets into the join cache buffer
+
+ SYNOPSIS
+ write_record_data()
+ link a reference to the associated info in the previous cache
+ is_full OUT true if it has been decided that no more records will be
+ added to the join buffer
+
+ DESCRIPTION
+ This function put into the cache buffer the following info that it reads
+ from the join record buffers or computes somehow:
+ (1) the length of all fields written for the record (optional)
+ (2) an offset to the associated info in the previous cache (if there is any)
+ determined by the link parameter
+ (3) all flag fields of the tables whose data field are put into the cache:
+ - match flag (optional),
+ - null bitmaps for all tables,
+ - null row flags for all tables
+ (4) values of all data fields including
+ - full images of those fixed legth data fields that cannot have
+ trailing spaces
+ - significant part of fixed length fields that can have trailing spaces
+ with the prepanded length
+ - data of non-blob variable length fields with the prepanded data length
+ - blob data from blob fields with the prepanded data length
+ (5) record offset values for the data fields that are referred to from
+ other caches
+
+ The record is written at the current position stored in the field 'pos'.
+ At the end of the function 'pos' points at the position right after the
+ written record data.
+ The function increments the number of records in the cache that is stored
+ in the 'records' field by 1. The function also modifies the values of
+ 'curr_rec_pos' and 'last_rec_pos' to point to the written record.
+ The 'end_pos' cursor is modified accordingly.
+ The 'last_rec_blob_data_is_in_rec_buff' is set on if the blob data
+ remains in the record buffers and not copied to the join buffer. It may
+ happen only to the blob data from the last record added into the cache.
+ If on_precond is attached to join_tab and it is not evaluated to TRUE
+ then MATCH_IMPOSSIBLE is placed in the match flag field of the record
+ written into the join buffer.
+
+ RETURN VALUE
+ length of the written record data
+*/
+
+uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full)
+{
+ uint len;
+ bool last_record;
+ CACHE_FIELD *copy;
+ CACHE_FIELD *copy_end;
+ uchar *flags_pos;
+ uchar *cp= pos;
+ uchar *init_pos= cp;
+ uchar *rec_len_ptr= 0;
+ uint key_extra= extra_key_length();
+
+ records++; /* Increment the counter of records in the cache */
+
+ len= pack_length + key_extra;
+
+ /* Make an adjustment for the size of the auxiliary buffer if there is any */
+ uint incr= aux_buffer_incr(records);
+ size_t rem= rem_space();
+ aux_buff_size+= len+incr < rem ? incr : rem;
+
+ /*
+ For each blob to be put into cache save its length and a pointer
+ to the value in the corresponding element of the blob_ptr array.
+ Blobs with null values are skipped.
+ Increment 'len' by the total length of all these blobs.
+ */
+ if (blobs)
+ {
+ CACHE_FIELD **copy_ptr= blob_ptr;
+ CACHE_FIELD **copy_ptr_end= copy_ptr+blobs;
+ for ( ; copy_ptr < copy_ptr_end; copy_ptr++)
+ {
+ Field_blob *blob_field= (Field_blob *) (*copy_ptr)->field;
+ if (!blob_field->is_null())
+ {
+ uint blob_len= blob_field->get_length();
+ (*copy_ptr)->blob_length= blob_len;
+ len+= blob_len;
+ blob_field->get_ptr(&(*copy_ptr)->str);
+ }
+ }
+ }
+
+ /*
+ Check whether we won't be able to add any new record into the cache after
+ this one because the cache will be full. Set last_record to TRUE if it's so.
+ The assume that the cache will be full after the record has been written
+ into it if either the remaining space of the cache is not big enough for the
+ record's blob values or if there is a chance that not all non-blob fields
+ of the next record can be placed there.
+ This function is called only in the case when there is enough space left in
+ the cache to store at least non-blob parts of the current record.
+ */
+ last_record= (len+pack_length_with_blob_ptrs+key_extra) > rem_space();
+
+ /*
+ Save the position for the length of the record in the cache if it's needed.
+ The length of the record will be inserted here when all fields of the record
+ are put into the cache.
+ */
+ if (with_length)
+ {
+ rec_len_ptr= cp;
+ DBUG_ASSERT(cp + size_of_rec_len <= buff + buff_size);
+ cp+= size_of_rec_len;
+ }
+
+ /*
+ Put a reference to the fields of the record that are stored in the previous
+ cache if there is any. This reference is passed by the 'link' parameter.
+ */
+ if (prev_cache)
+ {
+ DBUG_ASSERT(cp + prev_cache->get_size_of_rec_offset() <= buff + buff_size);
+ cp+= prev_cache->get_size_of_rec_offset();
+ prev_cache->store_rec_ref(cp, link);
+ }
+
+ curr_rec_pos= cp;
+
+ /* If the there is a match flag set its value to 0 */
+ copy= field_descr;
+ if (with_match_flag)
+ *copy[0].str= 0;
+
+ /* First put into the cache the values of all flag fields */
+ copy_end= field_descr+flag_fields;
+ flags_pos= cp;
+ for ( ; copy < copy_end; copy++)
+ {
+ DBUG_ASSERT(cp + copy->length <= buff + buff_size);
+ memcpy(cp, copy->str, copy->length);
+ cp+= copy->length;
+ }
+
+ /* Now put the values of the remaining fields as soon as they are not nulls */
+ copy_end= field_descr+fields;
+ for ( ; copy < copy_end; copy++)
+ {
+ Field *field= copy->field;
+ if (field && field->maybe_null() && field->is_null())
+ {
+ if (copy->referenced_field_no)
+ copy->offset= 0;
+ continue;
+ }
+ /* Save the offset of the field to put it later at the end of the record */
+ if (copy->referenced_field_no)
+ copy->offset= cp-curr_rec_pos;
+
+ if (copy->type == CACHE_BLOB)
+ {
+ Field_blob *blob_field= (Field_blob *) copy->field;
+ if (last_record)
+ {
+ last_rec_blob_data_is_in_rec_buff= 1;
+ /* Put down the length of the blob and the pointer to the data */
+ DBUG_ASSERT(cp + copy->length + sizeof(char*) <= buff + buff_size);
+ blob_field->get_image(cp, copy->length+sizeof(char*),
+ blob_field->charset());
+ cp+= copy->length+sizeof(char*);
+ }
+ else
+ {
+ /* First put down the length of the blob and then copy the data */
+ blob_field->get_image(cp, copy->length,
+ blob_field->charset());
+ DBUG_ASSERT(cp + copy->length + copy->blob_length <= buff + buff_size);
+ memcpy(cp+copy->length, copy->str, copy->blob_length);
+ cp+= copy->length+copy->blob_length;
+ }
+ }
+ else
+ {
+ switch (copy->type) {
+ case CACHE_VARSTR1:
+ /* Copy the significant part of the short varstring field */
+ len= (uint) copy->str[0] + 1;
+ DBUG_ASSERT(cp + len <= buff + buff_size);
+ memcpy(cp, copy->str, len);
+ cp+= len;
+ break;
+ case CACHE_VARSTR2:
+ /* Copy the significant part of the long varstring field */
+ len= uint2korr(copy->str) + 2;
+ DBUG_ASSERT(cp + len <= buff + buff_size);
+ memcpy(cp, copy->str, len);
+ cp+= len;
+ break;
+ case CACHE_STRIPPED:
+ {
+ /*
+ Put down the field value stripping all trailing spaces off.
+ After this insert the length of the written sequence of bytes.
+ */
+ uchar *str, *end;
+ for (str= copy->str, end= str+copy->length;
+ end > str && end[-1] == ' ';
+ end--) ;
+ len=(uint) (end-str);
+ DBUG_ASSERT(cp + len + 2 <= buff + buff_size);
+ int2store(cp, len);
+ memcpy(cp+2, str, len);
+ cp+= len+2;
+ break;
+ }
+ case CACHE_ROWID:
+ if (!copy->length)
+ {
+ /*
+ This may happen only for ROWID fields of materialized
+ derived tables and views.
+ */
+ TABLE *table= (TABLE *) copy->str;
+ copy->str= table->file->ref;
+ copy->length= table->file->ref_length;
+ if (!copy->str)
+ {
+ /*
+ If table is an empty inner table of an outer join and it is
+ a materialized derived table then table->file->ref == NULL.
+ */
+ cp+= copy->length;
+ break;
+ }
+ }
+ /* fall through */
+ default:
+ /* Copy the entire image of the field from the record buffer */
+ DBUG_ASSERT(cp + copy->length <= buff + buff_size);
+ if (copy->str)
+ memcpy(cp, copy->str, copy->length);
+ cp+= copy->length;
+ }
+ }
+ }
+
+ /* Add the offsets of the fields that are referenced from other caches */
+ if (referenced_fields)
+ {
+ uint cnt= 0;
+ for (copy= field_descr+flag_fields; copy < copy_end ; copy++)
+ {
+ if (copy->referenced_field_no)
+ {
+ store_fld_offset(cp+size_of_fld_ofs*(copy->referenced_field_no-1),
+ copy->offset);
+ cnt++;
+ }
+ }
+ DBUG_ASSERT(cp + size_of_fld_ofs*cnt <= buff + buff_size);
+ cp+= size_of_fld_ofs*cnt;
+ }
+
+ if (rec_len_ptr)
+ store_rec_length(rec_len_ptr, (ulong) (cp-rec_len_ptr-size_of_rec_len));
+ last_rec_pos= curr_rec_pos;
+ end_pos= pos= cp;
+ *is_full= last_record;
+
+ last_written_is_null_compl= 0;
+ if (!join_tab->first_unmatched && join_tab->on_precond)
+ {
+ join_tab->found= 0;
+ join_tab->not_null_compl= 1;
+ if (!join_tab->on_precond->val_int())
+ {
+ flags_pos[0]= MATCH_IMPOSSIBLE;
+ last_written_is_null_compl= 1;
+ }
+ }
+
+ return (uint) (cp-init_pos);
+}
+
+
+/*
+ Reset the join buffer for reading/writing: default implementation
+
+ SYNOPSIS
+ reset()
+ for_writing if it's TRUE the function reset the buffer for writing
+
+ DESCRIPTION
+ This default implementation of the virtual function reset() resets
+ the join buffer for reading or writing.
+ If the buffer is reset for reading only the 'pos' value is reset
+ to point to the very beginning of the join buffer. If the buffer is
+ reset for writing additionally:
+ - the counter of the records in the buffer is set to 0,
+ - the the value of 'last_rec_pos' gets pointing at the position just
+ before the buffer,
+ - 'end_pos' is set to point to the beginning of the join buffer,
+ - the size of the auxiliary buffer is reset to 0,
+ - the flag 'last_rec_blob_data_is_in_rec_buff' is set to 0.
+
+ RETURN VALUE
+ none
+*/
+void JOIN_CACHE::reset(bool for_writing)
+{
+ pos= buff;
+ curr_rec_link= 0;
+ if (for_writing)
+ {
+ records= 0;
+ last_rec_pos= buff;
+ aux_buff_size= 0;
+ end_pos= pos;
+ last_rec_blob_data_is_in_rec_buff= 0;
+ }
+}
+
+
+/*
+ Add a record into the join buffer: the default implementation
+
+ SYNOPSIS
+ put_record()
+
+ DESCRIPTION
+ This default implementation of the virtual function put_record writes
+ the next matching record into the join buffer.
+ It also links the record having been written into the join buffer with
+ the matched record in the previous cache if there is any.
+ The implementation assumes that the function get_curr_link()
+ will return exactly the pointer to this matched record.
+
+ RETURN VALUE
+ TRUE if it has been decided that it should be the last record
+ in the join buffer,
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE::put_record()
+{
+ bool is_full;
+ uchar *link= 0;
+ if (prev_cache)
+ link= prev_cache->get_curr_rec_link();
+ write_record_data(link, &is_full);
+ return is_full;
+}
+
+
+/*
+ Read the next record from the join buffer: the default implementation
+
+ SYNOPSIS
+ get_record()
+
+ DESCRIPTION
+ This default implementation of the virtual function get_record
+ reads fields of the next record from the join buffer of this cache.
+ The function also reads all other fields associated with this record
+ from the the join buffers of the previous caches. The fields are read
+ into the corresponding record buffers.
+ It is supposed that 'pos' points to the position in the buffer
+ right after the previous record when the function is called.
+ When the function returns the 'pos' values is updated to point
+ to the position after the read record.
+ The value of 'curr_rec_pos' is also updated by the function to
+ point to the beginning of the first field of the record in the
+ join buffer.
+
+ RETURN VALUE
+ TRUE there are no more records to read from the join buffer
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE::get_record()
+{
+ bool res;
+ uchar *prev_rec_ptr= 0;
+ if (with_length)
+ pos+= size_of_rec_len;
+ if (prev_cache)
+ {
+ pos+= prev_cache->get_size_of_rec_offset();
+ prev_rec_ptr= prev_cache->get_rec_ref(pos);
+ }
+ curr_rec_pos= pos;
+ if (!(res= read_all_record_fields() == NO_MORE_RECORDS_IN_BUFFER))
+ {
+ pos+= referenced_fields*size_of_fld_ofs;
+ if (prev_cache)
+ prev_cache->get_record_by_pos(prev_rec_ptr);
+ }
+ return res;
+}
+
+
+/*
+ Read a positioned record from the join buffer: the default implementation
+
+ SYNOPSIS
+ get_record_by_pos()
+ rec_ptr position of the first field of the record in the join buffer
+
+ DESCRIPTION
+ This default implementation of the virtual function get_record_pos
+ reads the fields of the record positioned at 'rec_ptr' from the join buffer.
+ The function also reads all other fields associated with this record
+ from the the join buffers of the previous caches. The fields are read
+ into the corresponding record buffers.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE::get_record_by_pos(uchar *rec_ptr)
+{
+ uchar *save_pos= pos;
+ pos= rec_ptr;
+ read_all_record_fields();
+ pos= save_pos;
+ if (prev_cache)
+ {
+ uchar *prev_rec_ptr= prev_cache->get_rec_ref(rec_ptr);
+ prev_cache->get_record_by_pos(prev_rec_ptr);
+ }
+}
+
+
+/*
+ Get the match flag from the referenced record: the default implementation
+
+ SYNOPSIS
+ get_match_flag_by_pos()
+ rec_ptr position of the first field of the record in the join buffer
+
+ DESCRIPTION
+ This default implementation of the virtual function get_match_flag_by_pos
+ get the match flag for the record pointed by the reference at the position
+ rec_ptr. If the match flag is placed in one of the previous buffers the
+ function first reaches the linked record fields in this buffer.
+
+ RETURN VALUE
+ match flag for the record at the position rec_ptr
+*/
+
+enum JOIN_CACHE::Match_flag JOIN_CACHE::get_match_flag_by_pos(uchar *rec_ptr)
+{
+ Match_flag match_fl= MATCH_NOT_FOUND;
+ if (with_match_flag)
+ {
+ match_fl= (enum Match_flag) rec_ptr[0];
+ return match_fl;
+ }
+ if (prev_cache)
+ {
+ uchar *prev_rec_ptr= prev_cache->get_rec_ref(rec_ptr);
+ return prev_cache->get_match_flag_by_pos(prev_rec_ptr);
+ }
+ DBUG_ASSERT(0);
+ return match_fl;
+}
+
+
+/*
+ Calculate the increment of the auxiliary buffer for a record write
+
+ SYNOPSIS
+ aux_buffer_incr()
+ recno the number of the record the increment to be calculated for
+
+ DESCRIPTION
+ This function calls the aux_buffer_incr the method of the
+ companion member join_tab_scan to calculate the growth of the
+ auxiliary buffer when the recno-th record is added to the
+ join_buffer of this cache.
+
+ RETURN VALUE
+ the number of bytes in the increment
+*/
+
+uint JOIN_CACHE::aux_buffer_incr(ulong recno)
+{
+ return join_tab_scan->aux_buffer_incr(recno);
+}
+
+/*
+ Read all flag and data fields of a record from the join buffer
+
+ SYNOPSIS
+ read_all_record_fields()
+
+ DESCRIPTION
+ The function reads all flag and data fields of a record from the join
+ buffer into the corresponding record buffers.
+ The fields are read starting from the position 'pos' which is
+ supposed to point to the beginning og the first record field.
+ The function increments the value of 'pos' by the length of the
+ read data.
+
+ RETURN VALUE
+ (-1) if there is no more records in the join buffer
+ length of the data read from the join buffer - otherwise
+*/
+
+uint JOIN_CACHE::read_all_record_fields()
+{
+ uchar *init_pos= pos;
+
+ if (pos > last_rec_pos || !records)
+ return NO_MORE_RECORDS_IN_BUFFER;
+
+ /* First match flag, read null bitmaps and null_row flag for each table */
+ read_flag_fields();
+
+ /* Now read the remaining table fields if needed */
+ CACHE_FIELD *copy= field_descr+flag_fields;
+ CACHE_FIELD *copy_end= field_descr+fields;
+ bool blob_in_rec_buff= blob_data_is_in_rec_buff(init_pos);
+ for ( ; copy < copy_end; copy++)
+ read_record_field(copy, blob_in_rec_buff);
+
+ return (uint) (pos-init_pos);
+}
+
+
+/*
+ Read all flag fields of a record from the join buffer
+
+ SYNOPSIS
+ read_flag_fields()
+
+ DESCRIPTION
+ The function reads all flag fields of a record from the join
+ buffer into the corresponding record buffers.
+ The fields are read starting from the position 'pos'.
+ The function increments the value of 'pos' by the length of the
+ read data.
+
+ RETURN VALUE
+ length of the data read from the join buffer
+*/
+
+uint JOIN_CACHE::read_flag_fields()
+{
+ uchar *init_pos= pos;
+ CACHE_FIELD *copy= field_descr;
+ CACHE_FIELD *copy_end= copy+flag_fields;
+ if (with_match_flag)
+ {
+ copy->str[0]= MY_TEST((Match_flag) pos[0] == MATCH_FOUND);
+ pos+= copy->length;
+ copy++;
+ }
+ for ( ; copy < copy_end; copy++)
+ {
+ memcpy(copy->str, pos, copy->length);
+ pos+= copy->length;
+ }
+ return (pos-init_pos);
+}
+
+
+/*
+ Read a data record field from the join buffer
+
+ SYNOPSIS
+ read_record_field()
+ copy the descriptor of the data field to be read
+ blob_in_rec_buff indicates whether this is the field from the record
+ whose blob data are in record buffers
+
+ DESCRIPTION
+ The function reads the data field specified by the parameter copy
+ from the join buffer into the corresponding record buffer.
+ The field is read starting from the position 'pos'.
+ The data of blob values is not copied from the join buffer.
+ The function increments the value of 'pos' by the length of the
+ read data.
+
+ RETURN VALUE
+ length of the data read from the join buffer
+*/
+
+uint JOIN_CACHE::read_record_field(CACHE_FIELD *copy, bool blob_in_rec_buff)
+{
+ uint len;
+ /* Do not copy the field if its value is null */
+ if (copy->field && copy->field->maybe_null() && copy->field->is_null())
+ return 0;
+ if (copy->type == CACHE_BLOB)
+ {
+ Field_blob *blob_field= (Field_blob *) copy->field;
+ /*
+ Copy the length and the pointer to data but not the blob data
+ itself to the record buffer
+ */
+ if (blob_in_rec_buff)
+ {
+ blob_field->set_image(pos, copy->length+sizeof(char*),
+ blob_field->charset());
+ len= copy->length+sizeof(char*);
+ }
+ else
+ {
+ blob_field->set_ptr(pos, pos+copy->length);
+ len= copy->length+blob_field->get_length();
+ }
+ }
+ else
+ {
+ switch (copy->type) {
+ case CACHE_VARSTR1:
+ /* Copy the significant part of the short varstring field */
+ len= (uint) pos[0] + 1;
+ memcpy(copy->str, pos, len);
+ break;
+ case CACHE_VARSTR2:
+ /* Copy the significant part of the long varstring field */
+ len= uint2korr(pos) + 2;
+ memcpy(copy->str, pos, len);
+ break;
+ case CACHE_STRIPPED:
+ /* Pad the value by spaces that has been stripped off */
+ len= uint2korr(pos);
+ memcpy(copy->str, pos+2, len);
+ memset(copy->str+len, ' ', copy->length-len);
+ len+= 2;
+ break;
+ case CACHE_ROWID:
+ if (!copy->str)
+ {
+ len= copy->length;
+ break;
+ }
+ /* fall through */
+ default:
+ /* Copy the entire image of the field from the record buffer */
+ len= copy->length;
+ memcpy(copy->str, pos, len);
+ }
+ }
+ pos+= len;
+ return len;
+}
+
+
+/*
+ Read a referenced field from the join buffer
+
+ SYNOPSIS
+ read_referenced_field()
+ copy pointer to the descriptor of the referenced field
+ rec_ptr pointer to the record that may contain this field
+ len IN/OUT total length of the record fields
+
+ DESCRIPTION
+ The function checks whether copy points to a data field descriptor
+ for this cache object. If it does not then the function returns
+ FALSE. Otherwise the function reads the field of the record in
+ the join buffer pointed by 'rec_ptr' into the corresponding record
+ buffer and returns TRUE.
+ If the value of *len is 0 then the function sets it to the total
+ length of the record fields including possible trailing offset
+ values. Otherwise *len is supposed to provide this value that
+ has been obtained earlier.
+
+ NOTE
+ If the value of the referenced field is null then the offset
+ for the value is set to 0. If the value of a field can be null
+ then the value of flag_fields is always positive. So the offset
+ for any non-null value cannot be 0 in this case.
+
+ RETURN VALUE
+ TRUE 'copy' points to a data descriptor of this join cache
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE::read_referenced_field(CACHE_FIELD *copy,
+ uchar *rec_ptr,
+ uint *len)
+{
+ uchar *ptr;
+ uint offset;
+ if (copy < field_descr || copy >= field_descr+fields)
+ return FALSE;
+ if (!*len)
+ {
+ /* Get the total length of the record fields */
+ uchar *len_ptr= rec_ptr;
+ if (prev_cache)
+ len_ptr-= prev_cache->get_size_of_rec_offset();
+ *len= get_rec_length(len_ptr-size_of_rec_len);
+ }
+
+ ptr= rec_ptr-(prev_cache ? prev_cache->get_size_of_rec_offset() : 0);
+ offset= get_fld_offset(ptr+ *len -
+ size_of_fld_ofs*
+ (referenced_fields+1-copy->referenced_field_no));
+ bool is_null= FALSE;
+ Field *field= copy->field;
+ if (offset == 0 && flag_fields)
+ is_null= TRUE;
+ if (is_null)
+ {
+ field->set_null();
+ if (!field->real_maybe_null())
+ field->table->null_row= 1;
+ }
+ else
+ {
+ uchar *save_pos= pos;
+ field->set_notnull();
+ if (!field->real_maybe_null())
+ field->table->null_row= 0;
+ pos= rec_ptr+offset;
+ read_record_field(copy, blob_data_is_in_rec_buff(rec_ptr));
+ pos= save_pos;
+ }
+ return TRUE;
+}
+
+
+/*
+ Skip record from join buffer if's already matched: default implementation
+
+ SYNOPSIS
+ skip_if_matched()
+
+ DESCRIPTION
+ This default implementation of the virtual function skip_if_matched
+ skips the next record from the join buffer if its match flag is set to
+ MATCH_FOUND.
+ If the record is skipped the value of 'pos' is set to point to the position
+ right after the record.
+
+ RETURN VALUE
+ TRUE the match flag is set to MATCH_FOUND and the record has been skipped
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE::skip_if_matched()
+{
+ DBUG_ASSERT(with_length);
+ uint offset= size_of_rec_len;
+ if (prev_cache)
+ offset+= prev_cache->get_size_of_rec_offset();
+ /* Check whether the match flag is MATCH_FOUND */
+ if (get_match_flag_by_pos(pos+offset) == MATCH_FOUND)
+ {
+ pos+= size_of_rec_len + get_rec_length(pos);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Skip record from join buffer if the match isn't needed: default implementation
+
+ SYNOPSIS
+ skip_if_not_needed_match()
+
+ DESCRIPTION
+ This default implementation of the virtual function skip_if_not_needed_match
+ skips the next record from the join buffer if its match flag is not
+ MATCH_NOT_FOUND, and, either its value is MATCH_FOUND and join_tab is the
+ first inner table of an inner join, or, its value is MATCH_IMPOSSIBLE
+ and join_tab is the first inner table of an outer join.
+ If the record is skipped the value of 'pos' is set to point to the position
+ right after the record.
+
+ RETURN VALUE
+ TRUE the record has to be skipped
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE::skip_if_not_needed_match()
+{
+ DBUG_ASSERT(with_length);
+ enum Match_flag match_fl;
+ uint offset= size_of_rec_len;
+ if (prev_cache)
+ offset+= prev_cache->get_size_of_rec_offset();
+
+ if ((match_fl= get_match_flag_by_pos(pos+offset)) != MATCH_NOT_FOUND &&
+ (join_tab->check_only_first_match() == (match_fl == MATCH_FOUND)) )
+ {
+ pos+= size_of_rec_len + get_rec_length(pos);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Restore the fields of the last record from the join buffer
+
+ SYNOPSIS
+ restore_last_record()
+
+ DESCRIPTION
+ This function restore the values of the fields of the last record put
+ into join buffer in record buffers. The values most probably have been
+ overwritten by the field values from other records when they were read
+ from the join buffer into the record buffer in order to check pushdown
+ predicates.
+
+ RETURN
+ none
+*/
+
+void JOIN_CACHE::restore_last_record()
+{
+ if (records)
+ get_record_by_pos(last_rec_pos);
+}
+
+
+/*
+ Join records from the join buffer with records from the next join table
+
+ SYNOPSIS
+ join_records()
+ skip_last do not find matches for the last record from the buffer
+
+ DESCRIPTION
+ The functions extends all records from the join buffer by the matched
+ records from join_tab. In the case of outer join operation it also
+ adds null complementing extensions for the records from the join buffer
+ that have no match.
+ No extensions are generated for the last record from the buffer if
+ skip_last is true.
+
+ NOTES
+ The function must make sure that if linked join buffers are used then
+ a join buffer cannot be refilled again until all extensions in the
+ buffers chained to this one are generated.
+ Currently an outer join operation with several inner tables always uses
+ at least two linked buffers with the match join flags placed in the
+ first buffer. Any record composed of rows of the inner tables that
+ matches a record in this buffer must refer to the position of the
+ corresponding match flag.
+
+ IMPLEMENTATION
+ When generating extensions for outer tables of an outer join operation
+ first we generate all extensions for those records from the join buffer
+ that have matches, after which null complementing extension for all
+ unmatched records from the join buffer are generated.
+
+ RETURN VALUE
+ return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS.
+*/
+
+enum_nested_loop_state JOIN_CACHE::join_records(bool skip_last)
+{
+ JOIN_TAB *tab;
+ enum_nested_loop_state rc= NESTED_LOOP_OK;
+ bool outer_join_first_inner= join_tab->is_first_inner_for_outer_join();
+ DBUG_ENTER("JOIN_CACHE::join_records");
+
+ if (outer_join_first_inner && !join_tab->first_unmatched)
+ join_tab->not_null_compl= TRUE;
+
+ if (!join_tab->first_unmatched)
+ {
+ /* Find all records from join_tab that match records from join buffer */
+ rc= join_matching_records(skip_last);
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ goto finish;
+ if (outer_join_first_inner)
+ {
+ if (next_cache && join_tab != join_tab->last_inner)
+ {
+ /*
+ Ensure that all matches for outer records from join buffer are to be
+ found. Now we ensure that all full records are found for records from
+ join buffer. Generally this is an overkill.
+ TODO: Ensure that only matches of the inner table records have to be
+ found for the records from join buffer.
+ */
+ rc= next_cache->join_records(skip_last);
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ goto finish;
+ }
+ join_tab->not_null_compl= FALSE;
+ /* Prepare for generation of null complementing extensions */
+ for (tab= join_tab->first_inner; tab <= join_tab->last_inner; tab++)
+ tab->first_unmatched= join_tab->first_inner;
+ }
+ }
+ if (join_tab->first_unmatched)
+ {
+ if (is_key_access())
+ restore_last_record();
+
+ /*
+ Generate all null complementing extensions for the records from
+ join buffer that don't have any matching rows from the inner tables.
+ */
+ reset(FALSE);
+ rc= join_null_complements(skip_last);
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ goto finish;
+ }
+ if(next_cache)
+ {
+ /*
+ When using linked caches we must ensure the records in the next caches
+ that refer to the records in the join buffer are fully extended.
+ Otherwise we could have references to the records that have been
+ already erased from the join buffer and replaced for new records.
+ */
+ rc= next_cache->join_records(skip_last);
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ goto finish;
+ }
+
+ if (skip_last)
+ {
+ DBUG_ASSERT(!is_key_access());
+ /*
+ Restore the last record from the join buffer to generate
+ all extentions for it.
+ */
+ get_record();
+ }
+
+finish:
+ if (outer_join_first_inner)
+ {
+ /*
+ All null complemented rows have been already generated for all
+ outer records from join buffer. Restore the state of the
+ first_unmatched values to 0 to avoid another null complementing.
+ */
+ for (tab= join_tab->first_inner; tab <= join_tab->last_inner; tab++)
+ tab->first_unmatched= 0;
+ }
+ restore_last_record();
+ reset(TRUE);
+ DBUG_PRINT("exit", ("rc: %d", rc));
+ DBUG_RETURN(rc);
+}
+
+
+/*
+ Find matches from the next table for records from the join buffer
+
+ SYNOPSIS
+ join_matching_records()
+ skip_last do not look for matches for the last partial join record
+
+ DESCRIPTION
+ The function retrieves rows of the join_tab table and checks whether they
+ match partial join records from the join buffer. If a match is found
+ the function will call the sub_select function trying to look for matches
+ for the remaining join operations.
+ This function currently is called only from the function join_records.
+ If the value of skip_last is true the function writes the partial join
+ record from the record buffer into the join buffer to save its value for
+ the future processing in the caller function.
+
+ NOTES
+ If employed by BNL or BNLH join algorithms the function performs a full
+ scan of join_tab for each refill of the join buffer. If BKA or BKAH
+ algorithms are used then the function iterates only over those records
+ from join_tab that can be accessed by keys built over records in the join
+ buffer. To apply a proper method of iteration the function just calls
+ virtual iterator methods (open, next, close) of the member join_tab_scan.
+ The member can be either of the JOIN_TAB_SCAN or JOIN_TAB_SCAN_MMR type.
+ The class JOIN_TAB_SCAN provides the iterator methods for BNL/BNLH join
+ algorithms. The class JOIN_TAB_SCAN_MRR provides the iterator methods
+ for BKA/BKAH join algorithms.
+ When the function looks for records from the join buffer that would
+ match a record from join_tab it iterates either over all records in
+ the buffer or only over selected records. If BNL join operation is
+ performed all records are checked for the match. If BNLH or BKAH
+ algorithm is employed to join join_tab then the function looks only
+ through the records with the same join key as the record from join_tab.
+ With the BKA join algorithm only one record from the join buffer is checked
+ for a match for any record from join_tab. To iterate over the candidates
+ for a match the virtual function get_next_candidate_for_match is used,
+ while the virtual function prepare_look_for_matches is called to prepare
+ for such iteration proccess.
+
+ NOTES
+ The function produces all matching extensions for the records in the
+ join buffer following the path of the employed blocked algorithm.
+ When an outer join operation is performed all unmatched records from
+ the join buffer must be extended by null values. The function
+ 'join_null_complements' serves this purpose.
+
+ RETURN VALUE
+ return one of enum_nested_loop_state
+*/
+
+enum_nested_loop_state JOIN_CACHE::join_matching_records(bool skip_last)
+{
+ int error;
+ enum_nested_loop_state rc= NESTED_LOOP_OK;
+ join_tab->table->null_row= 0;
+ bool check_only_first_match= join_tab->check_only_first_match();
+ bool outer_join_first_inner= join_tab->is_first_inner_for_outer_join();
+ DBUG_ENTER("JOIN_CACHE::join_matching_records");
+
+ /* Return at once if there are no records in the join buffer */
+ if (!records)
+ DBUG_RETURN(NESTED_LOOP_OK);
+
+ /*
+ When joining we read records from the join buffer back into record buffers.
+ If matches for the last partial join record are found through a call to
+ the sub_select function then this partial join record must be saved in the
+ join buffer in order to be restored just before the sub_select call.
+ */
+ if (skip_last)
+ put_record();
+
+ if (join_tab->use_quick == 2 && join_tab->select->quick)
+ {
+ /* A dynamic range access was used last. Clean up after it */
+ delete join_tab->select->quick;
+ join_tab->select->quick= 0;
+ }
+
+ if ((rc= join_tab_execution_startup(join_tab)) < 0)
+ goto finish2;
+
+ /* Prepare to retrieve all records of the joined table */
+ if ((error= join_tab_scan->open()))
+ {
+ /*
+ TODO: if we get here, we will assert in net_send_statement(). Add test
+ coverage and fix.
+ */
+ goto finish;
+ }
+
+ while (!(error= join_tab_scan->next()))
+ {
+ if (join->thd->check_killed())
+ {
+ /* The user has aborted the execution of the query */
+ join->thd->send_kill_message();
+ rc= NESTED_LOOP_KILLED;
+ goto finish;
+ }
+
+ if (join_tab->keep_current_rowid)
+ join_tab->table->file->position(join_tab->table->record[0]);
+
+ /* Prepare to read matching candidates from the join buffer */
+ if (prepare_look_for_matches(skip_last))
+ continue;
+
+ uchar *rec_ptr;
+ /* Read each possible candidate from the buffer and look for matches */
+ while ((rec_ptr= get_next_candidate_for_match()))
+ {
+ /*
+ If only the first match is needed, and, it has been already found for
+ the next record read from the join buffer, then the record is skipped.
+ Also those records that must be null complemented are not considered
+ as candidates for matches.
+ */
+ if ((!check_only_first_match && !outer_join_first_inner) ||
+ !skip_next_candidate_for_match(rec_ptr))
+ {
+ read_next_candidate_for_match(rec_ptr);
+ rc= generate_full_extensions(rec_ptr);
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ goto finish;
+ }
+ }
+ }
+
+finish:
+ if (error)
+ rc= error < 0 ? NESTED_LOOP_NO_MORE_ROWS: NESTED_LOOP_ERROR;
+finish2:
+ join_tab_scan->close();
+ DBUG_RETURN(rc);
+}
+
+
+/*
+ Set match flag for a record in join buffer if it has not been set yet
+
+ SYNOPSIS
+ set_match_flag_if_none()
+ first_inner the join table to which this flag is attached to
+ rec_ptr pointer to the record in the join buffer
+
+ DESCRIPTION
+ If the records of the table are accumulated in a join buffer the function
+ sets the match flag for the record in the buffer that is referred to by
+ the record from this cache positioned at 'rec_ptr'.
+ The function also sets the match flag 'found' of the table first inner
+ if it has not been set before.
+
+ NOTES
+ The function assumes that the match flag for any record in any cache
+ is placed in the first byte occupied by the record fields.
+
+ RETURN VALUE
+ TRUE the match flag is set by this call for the first time
+ FALSE the match flag has been set before this call
+*/
+
+bool JOIN_CACHE::set_match_flag_if_none(JOIN_TAB *first_inner,
+ uchar *rec_ptr)
+{
+ if (!first_inner->cache)
+ {
+ /*
+ Records of the first inner table to which the flag is attached to
+ are not accumulated in a join buffer.
+ */
+ if (first_inner->found)
+ return FALSE;
+ else
+ {
+ first_inner->found= 1;
+ return TRUE;
+ }
+ }
+ JOIN_CACHE *cache= this;
+ while (cache->join_tab != first_inner)
+ {
+ cache= cache->prev_cache;
+ DBUG_ASSERT(cache);
+ rec_ptr= cache->get_rec_ref(rec_ptr);
+ }
+ if ((Match_flag) rec_ptr[0] != MATCH_FOUND)
+ {
+ rec_ptr[0]= MATCH_FOUND;
+ first_inner->found= 1;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Generate all full extensions for a partial join record in the buffer
+
+ SYNOPSIS
+ generate_full_extensions()
+ rec_ptr pointer to the record from join buffer to generate extensions
+
+ DESCRIPTION
+ The function first checks whether the current record of 'join_tab' matches
+ the partial join record from join buffer located at 'rec_ptr'. If it is the
+ case the function calls the join_tab->next_select method to generate
+ all full extension for this partial join match.
+
+ RETURN VALUE
+ return one of enum_nested_loop_state.
+*/
+
+enum_nested_loop_state JOIN_CACHE::generate_full_extensions(uchar *rec_ptr)
+{
+ enum_nested_loop_state rc= NESTED_LOOP_OK;
+ DBUG_ENTER("JOIN_CACHE::generate_full_extensions");
+
+ /*
+ Check whether the extended partial join record meets
+ the pushdown conditions.
+ */
+ if (check_match(rec_ptr))
+ {
+ int res= 0;
+
+ if (!join_tab->check_weed_out_table ||
+ !(res= join_tab->check_weed_out_table->sj_weedout_check_row(join->thd)))
+ {
+ set_curr_rec_link(rec_ptr);
+ rc= (join_tab->next_select)(join, join_tab+1, 0);
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ {
+ reset(TRUE);
+ DBUG_RETURN(rc);
+ }
+ }
+ if (res == -1)
+ {
+ rc= NESTED_LOOP_ERROR;
+ DBUG_RETURN(rc);
+ }
+ }
+ else if (join->thd->is_error())
+ rc= NESTED_LOOP_ERROR;
+ DBUG_RETURN(rc);
+}
+
+
+/*
+ Check matching to a partial join record from the join buffer
+
+ SYNOPSIS
+ check_match()
+ rec_ptr pointer to the record from join buffer to check matching to
+
+ DESCRIPTION
+ The function checks whether the current record of 'join_tab' matches
+ the partial join record from join buffer located at 'rec_ptr'. If this is
+ the case and 'join_tab' is the last inner table of a semi-join or an outer
+ join the function turns on the match flag for the 'rec_ptr' record unless
+ it has been already set.
+
+ NOTES
+ Setting the match flag on can trigger re-evaluation of pushdown conditions
+ for the record when join_tab is the last inner table of an outer join.
+
+ RETURN VALUE
+ TRUE there is a match
+ FALSE there is no match
+ In this case the caller must also check thd->is_error() to see
+ if there was a fatal error for the query.
+*/
+
+inline bool JOIN_CACHE::check_match(uchar *rec_ptr)
+{
+ /* Check whether pushdown conditions are satisfied */
+ DBUG_ENTER("JOIN_CACHE:check_match");
+
+ if (join_tab->select && join_tab->select->skip_record(join->thd) <= 0)
+ DBUG_RETURN(FALSE);
+
+ if (!join_tab->is_last_inner_table())
+ DBUG_RETURN(TRUE);
+
+ /*
+ This is the last inner table of an outer join,
+ and maybe of other embedding outer joins, or
+ this is the last inner table of a semi-join.
+ */
+ JOIN_TAB *first_inner= join_tab->get_first_inner_table();
+ do
+ {
+ set_match_flag_if_none(first_inner, rec_ptr);
+ if (first_inner->check_only_first_match() &&
+ !join_tab->first_inner)
+ DBUG_RETURN(TRUE);
+ /*
+ This is the first match for the outer table row.
+ The function set_match_flag_if_none has turned the flag
+ first_inner->found on. The pushdown predicates for
+ inner tables must be re-evaluated with this flag on.
+ Note that, if first_inner is the first inner table
+ of a semi-join, but is not an inner table of an outer join
+ such that 'not exists' optimization can be applied to it,
+ the re-evaluation of the pushdown predicates is not needed.
+ */
+ for (JOIN_TAB *tab= first_inner; tab <= join_tab; tab++)
+ {
+ if (tab->select && tab->select->skip_record(join->thd) <= 0)
+ DBUG_RETURN(FALSE);
+ }
+ }
+ while ((first_inner= first_inner->first_upper) &&
+ first_inner->last_inner == join_tab);
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Add null complements for unmatched outer records from join buffer
+
+ SYNOPSIS
+ join_null_complements()
+ skip_last do not add null complements for the last record
+
+ DESCRIPTION
+ This function is called only for inner tables of outer joins.
+ The function retrieves all rows from the join buffer and adds null
+ complements for those of them that do not have matches for outer
+ table records.
+ If the 'join_tab' is the last inner table of the embedding outer
+ join and the null complemented record satisfies the outer join
+ condition then the the corresponding match flag is turned on
+ unless it has been set earlier. This setting may trigger
+ re-evaluation of pushdown conditions for the record.
+
+ NOTES
+ The same implementation of the virtual method join_null_complements
+ is used for BNL/BNLH/BKA/BKA join algorthm.
+
+ RETURN VALUE
+ return one of enum_nested_loop_state.
+*/
+
+enum_nested_loop_state JOIN_CACHE::join_null_complements(bool skip_last)
+{
+ ulonglong cnt;
+ enum_nested_loop_state rc= NESTED_LOOP_OK;
+ bool is_first_inner= join_tab == join_tab->first_unmatched;
+ DBUG_ENTER("JOIN_CACHE::join_null_complements");
+
+ /* Return at once if there are no records in the join buffer */
+ if (!records)
+ DBUG_RETURN(NESTED_LOOP_OK);
+
+ cnt= records - (is_key_access() ? 0 : MY_TEST(skip_last));
+
+ /* This function may be called only for inner tables of outer joins */
+ DBUG_ASSERT(join_tab->first_inner);
+
+ for ( ; cnt; cnt--)
+ {
+ if (join->thd->check_killed())
+ {
+ /* The user has aborted the execution of the query */
+ join->thd->send_kill_message();
+ rc= NESTED_LOOP_KILLED;
+ goto finish;
+ }
+ /* Just skip the whole record if a match for it has been already found */
+ if (!is_first_inner || !skip_if_matched())
+ {
+ get_record();
+ /* The outer row is complemented by nulls for each inner table */
+ restore_record(join_tab->table, s->default_values);
+ mark_as_null_row(join_tab->table);
+ rc= generate_full_extensions(get_curr_rec());
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ goto finish;
+ }
+ }
+
+finish:
+ DBUG_RETURN(rc);
+}
+
+
+/*
+ Save data on the join algorithm employed by the join cache
+
+ SYNOPSIS
+ save_explain_data()
+ str string to add the comment on the employed join algorithm to
+
+ DESCRIPTION
+ This function puts info about the type of the used join buffer (flat or
+ incremental) and on the type of the the employed join algorithm (BNL,
+ BNLH, BKA or BKAH) to the data structure
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE::save_explain_data(struct st_explain_bka_type *explain)
+{
+ explain->incremental= MY_TEST(prev_cache);
+
+ switch (get_join_alg()) {
+ case BNL_JOIN_ALG:
+ explain->join_alg= "BNL";
+ break;
+ case BNLH_JOIN_ALG:
+ explain->join_alg= "BNLH";
+ break;
+ case BKA_JOIN_ALG:
+ explain->join_alg= "BKA";
+ break;
+ case BKAH_JOIN_ALG:
+ explain->join_alg= "BKAH";
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+}
+
+/**
+ get thread handle.
+*/
+
+THD *JOIN_CACHE::thd()
+{
+ return join->thd;
+}
+
+
+static void add_mrr_explain_info(String *str, uint mrr_mode, handler *file)
+{
+ char mrr_str_buf[128]={0};
+ int len;
+ len= file->multi_range_read_explain_info(mrr_mode, mrr_str_buf,
+ sizeof(mrr_str_buf));
+ if (len > 0)
+ {
+ str->append(STRING_WITH_LEN("; "));
+ str->append(mrr_str_buf, len);
+ }
+}
+
+void JOIN_CACHE_BKA::save_explain_data(struct st_explain_bka_type *explain)
+{
+ JOIN_CACHE::save_explain_data(explain);
+ add_mrr_explain_info(&explain->mrr_type, mrr_mode, join_tab->table->file);
+}
+
+
+void JOIN_CACHE_BKAH::save_explain_data(struct st_explain_bka_type *explain)
+{
+ JOIN_CACHE::save_explain_data(explain);
+ add_mrr_explain_info(&explain->mrr_type, mrr_mode, join_tab->table->file);
+}
+
+
+/*
+ Initialize a hashed join cache
+
+ SYNOPSIS
+ init()
+ for_explain join buffer is initialized for explain only
+
+ DESCRIPTION
+ The function initializes the cache structure with a hash table in it.
+ The hash table will be used to store key values for the records from
+ the join buffer.
+ The function allocates memory for the join buffer and for descriptors of
+ the record fields stored in the buffer.
+ The function also initializes a hash table for record keys within the join
+ buffer space.
+
+ NOTES VALUE
+ The function is supposed to be called by the init methods of the classes
+ derived from JOIN_CACHE_HASHED.
+
+ RETURN VALUE
+ 0 initialization with buffer allocations has been succeeded
+ 1 otherwise
+*/
+
+int JOIN_CACHE_HASHED::init(bool for_explain)
+{
+ int rc= 0;
+ TABLE_REF *ref= &join_tab->ref;
+
+ DBUG_ENTER("JOIN_CACHE_HASHED::init");
+
+ hash_table= 0;
+ key_entries= 0;
+
+ key_length= ref->key_length;
+
+ if ((rc= JOIN_CACHE::init(for_explain)) || for_explain)
+ DBUG_RETURN (rc);
+
+ if (!(key_buff= (uchar*) sql_alloc(key_length)))
+ DBUG_RETURN(1);
+
+ /* Take into account a reference to the next record in the key chain */
+ pack_length+= get_size_of_rec_offset();
+ pack_length_with_blob_ptrs+= get_size_of_rec_offset();
+
+ ref_key_info= join_tab->get_keyinfo_by_key_no(join_tab->ref.key);
+ ref_used_key_parts= join_tab->ref.key_parts;
+
+ hash_func= &JOIN_CACHE_HASHED::get_hash_idx_simple;
+ hash_cmp_func= &JOIN_CACHE_HASHED::equal_keys_simple;
+
+ KEY_PART_INFO *key_part= ref_key_info->key_part;
+ KEY_PART_INFO *key_part_end= key_part+ref_used_key_parts;
+ for ( ; key_part < key_part_end; key_part++)
+ {
+ if (!key_part->field->eq_cmp_as_binary())
+ {
+ hash_func= &JOIN_CACHE_HASHED::get_hash_idx_complex;
+ hash_cmp_func= &JOIN_CACHE_HASHED::equal_keys_complex;
+ break;
+ }
+ }
+
+ init_hash_table();
+
+ rec_fields_offset= get_size_of_rec_offset()+get_size_of_rec_length()+
+ (prev_cache ? prev_cache->get_size_of_rec_offset() : 0);
+
+ data_fields_offset= 0;
+ if (use_emb_key)
+ {
+ CACHE_FIELD *copy= field_descr;
+ CACHE_FIELD *copy_end= copy+flag_fields;
+ for ( ; copy < copy_end; copy++)
+ data_fields_offset+= copy->length;
+ }
+
+ DBUG_RETURN(rc);
+}
+
+
+/*
+ Initialize the hash table of a hashed join cache
+
+ SYNOPSIS
+ init_hash_table()
+
+ DESCRIPTION
+ The function estimates the number of hash table entries in the hash
+ table to be used and initializes this hash table within the join buffer
+ space.
+
+ RETURN VALUE
+ Currently the function always returns 0;
+*/
+
+int JOIN_CACHE_HASHED::init_hash_table()
+{
+ hash_table= 0;
+ key_entries= 0;
+
+ /* Calculate the minimal possible value of size_of_key_ofs greater than 1 */
+ uint max_size_of_key_ofs= MY_MAX(2, get_size_of_rec_offset());
+ for (size_of_key_ofs= 2;
+ size_of_key_ofs <= max_size_of_key_ofs;
+ size_of_key_ofs+= 2)
+ {
+ key_entry_length= get_size_of_rec_offset() + // key chain header
+ size_of_key_ofs + // reference to the next key
+ (use_emb_key ? get_size_of_rec_offset() : key_length);
+
+ ulong space_per_rec= avg_record_length +
+ avg_aux_buffer_incr +
+ key_entry_length+size_of_key_ofs;
+ uint n= buff_size / space_per_rec;
+
+ /*
+ TODO: Make a better estimate for this upper bound of
+ the number of records in in the join buffer.
+ */
+ uint max_n= buff_size / (pack_length-length+
+ key_entry_length+size_of_key_ofs);
+
+ hash_entries= (uint) (n / 0.7);
+ set_if_bigger(hash_entries, 1);
+
+ if (offset_size(max_n*key_entry_length) <=
+ size_of_key_ofs)
+ break;
+ }
+
+ /* Initialize the hash table */
+ hash_table= buff + (buff_size-hash_entries*size_of_key_ofs);
+ cleanup_hash_table();
+ curr_key_entry= hash_table;
+
+ return 0;
+}
+
+
+/*
+ Reallocate the join buffer of a hashed join cache
+
+ SYNOPSIS
+ realloc_buffer()
+
+ DESCRITION
+ The function reallocates the join buffer of the hashed join cache.
+ After this it initializes a hash table within the buffer space and
+ resets the join cache for writing.
+
+ NOTES
+ The function assumes that buff_size contains the new value for the join
+ buffer size.
+
+ RETURN VALUE
+ 0 if the buffer has been successfully reallocated
+ 1 otherwise
+*/
+
+int JOIN_CACHE_HASHED::realloc_buffer()
+{
+ int rc;
+ free();
+ rc= MY_TEST(!(buff= (uchar*) my_malloc(buff_size, MYF(MY_THREAD_SPECIFIC))));
+ init_hash_table();
+ reset(TRUE);
+ return rc;
+}
+
+
+/*
+ Get maximum size of the additional space per record used for record keys
+
+ SYNOPSYS
+ get_max_key_addon_space_per_record()
+
+ DESCRIPTION
+ The function returns the size of the space occupied by one key entry
+ and one hash table entry.
+
+ RETURN VALUE
+ maximum size of the additional space per record that is used to store
+ record keys in the hash table
+*/
+
+uint JOIN_CACHE_HASHED::get_max_key_addon_space_per_record()
+{
+ ulong len;
+ TABLE_REF *ref= &join_tab->ref;
+ /*
+ The total number of hash entries in the hash tables is bounded by
+ ceiling(N/0.7) where N is the maximum number of records in the buffer.
+ That's why the multiplier 2 is used in the formula below.
+ */
+ len= (use_emb_key ? get_size_of_rec_offset() : ref->key_length) +
+ size_of_rec_ofs + // size of the key chain header
+ size_of_rec_ofs + // >= size of the reference to the next key
+ 2*size_of_rec_ofs; // >= 2*( size of hash table entry)
+ return len;
+}
+
+
+/*
+ Reset the buffer of a hashed join cache for reading/writing
+
+ SYNOPSIS
+ reset()
+ for_writing if it's TRUE the function reset the buffer for writing
+
+ DESCRIPTION
+ This implementation of the virtual function reset() resets the join buffer
+ of the JOIN_CACHE_HASHED class for reading or writing.
+ Additionally to what the default implementation does this function
+ cleans up the hash table allocated within the buffer.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE_HASHED::reset(bool for_writing)
+{
+ this->JOIN_CACHE::reset(for_writing);
+ if (for_writing && hash_table)
+ cleanup_hash_table();
+ curr_key_entry= hash_table;
+}
+
+
+/*
+ Add a record into the buffer of a hashed join cache
+
+ SYNOPSIS
+ put_record()
+
+ DESCRIPTION
+ This implementation of the virtual function put_record writes the next
+ matching record into the join buffer of the JOIN_CACHE_HASHED class.
+ Additionally to what the default implementation does this function
+ performs the following.
+ It extracts from the record the key value used in lookups for matching
+ records and searches for this key in the hash tables from the join cache.
+ If it finds the key in the hash table it joins the record to the chain
+ of records with this key. If the key is not found in the hash table the
+ key is placed into it and a chain containing only the newly added record
+ is attached to the key entry. The key value is either placed in the hash
+ element added for the key or, if the use_emb_key flag is set, remains in
+ the record from the partial join.
+ If the match flag field of a record contains MATCH_IMPOSSIBLE the key is
+ not created for this record.
+
+ RETURN VALUE
+ TRUE if it has been decided that it should be the last record
+ in the join buffer,
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_HASHED::put_record()
+{
+ bool is_full;
+ uchar *key;
+ uint key_len= key_length;
+ uchar *key_ref_ptr;
+ uchar *link= 0;
+ TABLE_REF *ref= &join_tab->ref;
+ uchar *next_ref_ptr= pos;
+
+ pos+= get_size_of_rec_offset();
+ /* Write the record into the join buffer */
+ if (prev_cache)
+ link= prev_cache->get_curr_rec_link();
+ write_record_data(link, &is_full);
+
+ if (last_written_is_null_compl)
+ return is_full;
+
+ if (use_emb_key)
+ key= get_curr_emb_key();
+ else
+ {
+ /* Build the key over the fields read into the record buffers */
+ cp_buffer_from_ref(join->thd, join_tab->table, ref);
+ key= ref->key_buff;
+ }
+
+ /* Look for the key in the hash table */
+ if (key_search(key, key_len, &key_ref_ptr))
+ {
+ uchar *last_next_ref_ptr;
+ /*
+ The key is found in the hash table.
+ Add the record to the circular list of the records attached to this key.
+ Below 'rec' is the record to be added into the record chain for the found
+ key, 'key_ref' points to a flatten representation of the st_key_entry
+ structure that contains the key and the head of the record chain.
+ */
+ last_next_ref_ptr= get_next_rec_ref(key_ref_ptr+get_size_of_key_offset());
+ /* rec->next_rec= key_entry->last_rec->next_rec */
+ memcpy(next_ref_ptr, last_next_ref_ptr, get_size_of_rec_offset());
+ /* key_entry->last_rec->next_rec= rec */
+ store_next_rec_ref(last_next_ref_ptr, next_ref_ptr);
+ /* key_entry->last_rec= rec */
+ store_next_rec_ref(key_ref_ptr+get_size_of_key_offset(), next_ref_ptr);
+ }
+ else
+ {
+ /*
+ The key is not found in the hash table.
+ Put the key into the join buffer linking it with the keys for the
+ corresponding hash entry. Create a circular list with one element
+ referencing the record and attach the list to the key in the buffer.
+ */
+ uchar *cp= last_key_entry;
+ cp-= get_size_of_rec_offset()+get_size_of_key_offset();
+ store_next_key_ref(key_ref_ptr, cp);
+ store_null_key_ref(cp);
+ store_next_rec_ref(next_ref_ptr, next_ref_ptr);
+ store_next_rec_ref(cp+get_size_of_key_offset(), next_ref_ptr);
+ if (use_emb_key)
+ {
+ cp-= get_size_of_rec_offset();
+ store_emb_key_ref(cp, key);
+ }
+ else
+ {
+ cp-= key_len;
+ memcpy(cp, key, key_len);
+ }
+ last_key_entry= cp;
+ DBUG_ASSERT(last_key_entry >= end_pos);
+ /* Increment the counter of key_entries in the hash table */
+ key_entries++;
+ }
+ return is_full;
+}
+
+
+/*
+ Read the next record from the buffer of a hashed join cache
+
+ SYNOPSIS
+ get_record()
+
+ DESCRIPTION
+ Additionally to what the default implementation of the virtual
+ function get_record does this implementation skips the link element
+ used to connect the records with the same key into a chain.
+
+ RETURN VALUE
+ TRUE there are no more records to read from the join buffer
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_HASHED::get_record()
+{
+ pos+= get_size_of_rec_offset();
+ return this->JOIN_CACHE::get_record();
+}
+
+
+/*
+ Skip record from a hashed join buffer if its match flag is set to MATCH_FOUND
+
+ SYNOPSIS
+ skip_if_matched()
+
+ DESCRIPTION
+ This implementation of the virtual function skip_if_matched does
+ the same as the default implementation does, but it takes into account
+ the link element used to connect the records with the same key into a chain.
+
+ RETURN VALUE
+ TRUE the match flag is MATCH_FOUND and the record has been skipped
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_HASHED::skip_if_matched()
+{
+ uchar *save_pos= pos;
+ pos+= get_size_of_rec_offset();
+ if (!this->JOIN_CACHE::skip_if_matched())
+ {
+ pos= save_pos;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/*
+ Skip record from a hashed join buffer if its match flag dictates to do so
+
+ SYNOPSIS
+ skip_if_uneeded_match()
+
+ DESCRIPTION
+ This implementation of the virtual function skip_if_not_needed_match does
+ the same as the default implementation does, but it takes into account
+ the link element used to connect the records with the same key into a chain.
+
+ RETURN VALUE
+ TRUE the match flag dictates to skip the record
+ FALSE the match flag is off
+*/
+
+bool JOIN_CACHE_HASHED::skip_if_not_needed_match()
+{
+ uchar *save_pos= pos;
+ pos+= get_size_of_rec_offset();
+ if (!this->JOIN_CACHE::skip_if_not_needed_match())
+ {
+ pos= save_pos;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/*
+ Search for a key in the hash table of the join buffer
+
+ SYNOPSIS
+ key_search()
+ key pointer to the key value
+ key_len key value length
+ key_ref_ptr OUT position of the reference to the next key from
+ the hash element for the found key , or
+ a position where the reference to the the hash
+ element for the key is to be added in the
+ case when the key has not been found
+
+ DESCRIPTION
+ The function looks for a key in the hash table of the join buffer.
+ If the key is found the functionreturns the position of the reference
+ to the next key from to the hash element for the given key.
+ Otherwise the function returns the position where the reference to the
+ newly created hash element for the given key is to be added.
+
+ RETURN VALUE
+ TRUE the key is found in the hash table
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_HASHED::key_search(uchar *key, uint key_len,
+ uchar **key_ref_ptr)
+{
+ bool is_found= FALSE;
+ uint idx= (this->*hash_func)(key, key_length);
+ uchar *ref_ptr= hash_table+size_of_key_ofs*idx;
+ while (!is_null_key_ref(ref_ptr))
+ {
+ uchar *next_key;
+ ref_ptr= get_next_key_ref(ref_ptr);
+ next_key= use_emb_key ? get_emb_key(ref_ptr-get_size_of_rec_offset()) :
+ ref_ptr-key_length;
+
+ if ((this->*hash_cmp_func)(next_key, key, key_len))
+ {
+ is_found= TRUE;
+ break;
+ }
+ }
+ *key_ref_ptr= ref_ptr;
+ return is_found;
+}
+
+
+/*
+ Hash function that considers a key in the hash table as byte array
+
+ SYNOPSIS
+ get_hash_idx_simple()
+ key pointer to the key value
+ key_len key value length
+
+ DESCRIPTION
+ The function calculates an index of the hash entry in the hash table
+ of the join buffer for the given key. It considers the key just as
+ a sequence of bytes of the length key_len.
+
+ RETURN VALUE
+ the calculated index of the hash entry for the given key
+*/
+
+inline
+uint JOIN_CACHE_HASHED::get_hash_idx_simple(uchar* key, uint key_len)
+{
+ ulong nr= 1;
+ ulong nr2= 4;
+ uchar *pos= key;
+ uchar *end= key+key_len;
+ for (; pos < end ; pos++)
+ {
+ nr^= (ulong) ((((uint) nr & 63)+nr2)*((uint) *pos))+ (nr << 8);
+ nr2+= 3;
+ }
+ return nr % hash_entries;
+}
+
+
+/*
+ Hash function that takes into account collations of the components of the key
+
+ SYNOPSIS
+ get_hash_idx_complex()
+ key pointer to the key value
+ key_len key value length
+
+ DESCRIPTION
+ The function calculates an index of the hash entry in the hash table
+ of the join buffer for the given key. It takes into account that the
+ components of the key may be of a varchar type with different collations.
+ The function guarantees that the same hash value for any two equal
+ keys that may differ as byte sequences.
+ The function takes the info about the components of the key, their
+ types and used collations from the class member ref_key_info containing
+ a pointer to the descriptor of the index that can be used for the join
+ operation.
+
+ RETURN VALUE
+ the calculated index of the hash entry for the given key
+*/
+
+inline
+uint JOIN_CACHE_HASHED::get_hash_idx_complex(uchar *key, uint key_len)
+{
+ return
+ (uint) (key_hashnr(ref_key_info, ref_used_key_parts, key) % hash_entries);
+}
+
+
+/*
+ Compare two key entries in the hash table as sequence of bytes
+
+ SYNOPSIS
+ equal_keys_simple()
+ key1 pointer to the first key entry
+ key2 pointer to the second key entry
+ key_len the length of the key values
+
+ DESCRIPTION
+ The function compares two key entries in the hash table key1 and key2
+ as two sequences bytes of the length key_len
+
+ RETURN VALUE
+ TRUE key1 coincides with key2
+ FALSE otherwise
+*/
+
+inline
+bool JOIN_CACHE_HASHED::equal_keys_simple(uchar *key1, uchar *key2,
+ uint key_len)
+{
+ return memcmp(key1, key2, key_len) == 0;
+}
+
+
+/*
+ Compare two key entries taking into account the used collation
+
+ SYNOPSIS
+ equal_keys_complex()
+ key1 pointer to the first key entry
+ key2 pointer to the second key entry
+ key_len the length of the key values
+
+ DESCRIPTION
+ The function checks whether two key entries in the hash table
+ key1 and key2 are equal as, possibly, compound keys of a certain
+ structure whose components may be of a varchar type and may
+ employ different collations.
+ The descriptor of the key structure is taken from the class
+ member ref_key_info.
+
+ RETURN VALUE
+ TRUE key1 is equal tokey2
+ FALSE otherwise
+*/
+
+inline
+bool JOIN_CACHE_HASHED::equal_keys_complex(uchar *key1, uchar *key2,
+ uint key_len)
+{
+ return key_buf_cmp(ref_key_info, ref_used_key_parts, key1, key2) == 0;
+}
+
+
+/*
+ Clean up the hash table of the join buffer
+
+ SYNOPSIS
+ cleanup_hash_table()
+ key pointer to the key value
+ key_len key value length
+
+ DESCRIPTION
+ The function cleans up the hash table in the join buffer removing all
+ hash elements from the table.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE_HASHED:: cleanup_hash_table()
+{
+ last_key_entry= hash_table;
+ bzero(hash_table, (buff+buff_size)-hash_table);
+ key_entries= 0;
+}
+
+
+/*
+ Check whether all records in a key chain have their match flags set on
+
+ SYNOPSIS
+ check_all_match_flags_for_key()
+ key_chain_ptr
+
+ DESCRIPTION
+ This function retrieves records in the given circular chain and checks
+ whether their match flags are set on. The parameter key_chain_ptr shall
+ point to the position in the join buffer storing the reference to the
+ last element of this chain.
+
+ RETURN VALUE
+ TRUE if each retrieved record has its match flag set to MATCH_FOUND
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_HASHED::check_all_match_flags_for_key(uchar *key_chain_ptr)
+{
+ uchar *last_rec_ref_ptr= get_next_rec_ref(key_chain_ptr);
+ uchar *next_rec_ref_ptr= last_rec_ref_ptr;
+ do
+ {
+ next_rec_ref_ptr= get_next_rec_ref(next_rec_ref_ptr);
+ uchar *rec_ptr= next_rec_ref_ptr+rec_fields_offset;
+ if (get_match_flag_by_pos(rec_ptr) != MATCH_FOUND)
+ return FALSE;
+ }
+ while (next_rec_ref_ptr != last_rec_ref_ptr);
+ return TRUE;
+}
+
+
+/*
+ Get the next key built for the records from the buffer of a hashed join cache
+
+ SYNOPSIS
+ get_next_key()
+ key pointer to the buffer where the key value is to be placed
+
+ DESCRIPTION
+ The function reads the next key value stored in the hash table of the
+ join buffer. Depending on the value of the use_emb_key flag of the
+ join cache the value is read either from the table itself or from
+ the record field where it occurs.
+
+ RETURN VALUE
+ length of the key value - if the starting value of 'cur_key_entry' refers
+ to the position after that referred by the the value of 'last_key_entry',
+ 0 - otherwise.
+*/
+
+uint JOIN_CACHE_HASHED::get_next_key(uchar ** key)
+{
+ if (curr_key_entry == last_key_entry)
+ return 0;
+
+ curr_key_entry-= key_entry_length;
+
+ *key = use_emb_key ? get_emb_key(curr_key_entry) : curr_key_entry;
+
+ DBUG_ASSERT(*key >= buff && *key < hash_table);
+
+ return key_length;
+}
+
+
+/*
+ Initiate an iteration process over records in the joined table
+
+ SYNOPSIS
+ open()
+
+ DESCRIPTION
+ The function initiates the process of iteration over records from the
+ joined table recurrently performed by the BNL/BKLH join algorithm.
+
+ RETURN VALUE
+ 0 the initiation is a success
+ error code otherwise
+*/
+
+int JOIN_TAB_SCAN::open()
+{
+ save_or_restore_used_tabs(join_tab, FALSE);
+ is_first_record= TRUE;
+ return join_init_read_record(join_tab);
+}
+
+
+/*
+ Read the next record that can match while scanning the joined table
+
+ SYNOPSIS
+ next()
+
+ DESCRIPTION
+ The function reads the next record from the joined table that can
+ match some records in the buffer of the join cache 'cache'. To do
+ this the function calls the function that scans table records and
+ looks for the next one that meets the condition pushed to the
+ joined table join_tab.
+
+ NOTES
+ The function catches the signal that kills the query.
+
+ RETURN VALUE
+ 0 the next record exists and has been successfully read
+ error code otherwise
+*/
+
+int JOIN_TAB_SCAN::next()
+{
+ int err= 0;
+ int skip_rc;
+ READ_RECORD *info= &join_tab->read_record;
+ SQL_SELECT *select= join_tab->cache_select;
+ TABLE *table= join_tab->table;
+ THD *thd= join->thd;
+
+ if (is_first_record)
+ is_first_record= FALSE;
+ else
+ err= info->read_record(info);
+ if (!err && table->vfield)
+ update_virtual_fields(thd, table);
+ while (!err && select && (skip_rc= select->skip_record(thd)) <= 0)
+ {
+ if (thd->check_killed() || skip_rc < 0)
+ return 1;
+ /*
+ Move to the next record if the last retrieved record does not
+ meet the condition pushed to the table join_tab.
+ */
+ err= info->read_record(info);
+ if (!err && table->vfield)
+ update_virtual_fields(thd, table);
+ }
+ return err;
+}
+
+
+/*
+ Walk back in join order from join_tab until we encounter a join tab with
+ tab->cache!=NULL, and save/restore tab->table->status along the way.
+
+ @param save TRUE save
+ FALSE restore
+*/
+
+static void save_or_restore_used_tabs(JOIN_TAB *join_tab, bool save)
+{
+ JOIN_TAB *first= join_tab->bush_root_tab?
+ join_tab->bush_root_tab->bush_children->start :
+ join_tab->join->join_tab + join_tab->join->const_tables;
+
+ for (JOIN_TAB *tab= join_tab-1; tab != first && !tab->cache; tab--)
+ {
+ if (tab->bush_children)
+ {
+ for (JOIN_TAB *child= tab->bush_children->start;
+ child != tab->bush_children->end;
+ child++)
+ {
+ if (save)
+ child->table->status= child->status;
+ else
+ {
+ tab->status= tab->table->status;
+ tab->table->status= 0;
+ }
+ }
+ }
+
+ if (save)
+ tab->table->status= tab->status;
+ else
+ {
+ tab->status= tab->table->status;
+ tab->table->status= 0;
+ }
+ }
+}
+
+
+/*
+ Perform finalizing actions for a scan over the table records
+
+ SYNOPSIS
+ close()
+
+ DESCRIPTION
+ The function performs the necessary restoring actions after
+ the table scan over the joined table has been finished.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_TAB_SCAN::close()
+{
+ save_or_restore_used_tabs(join_tab, TRUE);
+}
+
+
+/*
+ Prepare to iterate over the BNL join cache buffer to look for matches
+
+ SYNOPSIS
+ prepare_look_for_matches()
+ skip_last <-> ignore the last record in the buffer
+
+ DESCRIPTION
+ The function prepares the join cache for an iteration over the
+ records in the join buffer. The iteration is performed when looking
+ for matches for the record from the joined table join_tab that
+ has been placed into the record buffer of the joined table.
+ If the value of the parameter skip_last is TRUE then the last
+ record from the join buffer is ignored.
+ The function initializes the counter of the records that have been
+ not iterated over yet.
+
+ RETURN VALUE
+ TRUE there are no records in the buffer to iterate over
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_BNL::prepare_look_for_matches(bool skip_last)
+{
+ if (!records)
+ return TRUE;
+ reset(FALSE);
+ rem_records= records - MY_TEST(skip_last);
+ return rem_records == 0;
+}
+
+
+/*
+ Get next record from the BNL join cache buffer when looking for matches
+
+ SYNOPSIS
+ get_next_candidate_for_match
+
+ DESCRIPTION
+ This method is used for iterations over the records from the join
+ cache buffer when looking for matches for records from join_tab.
+ The methods performs the necessary preparations to read the next record
+ from the join buffer into the record buffer by the method
+ read_next_candidate_for_match, or, to skip the next record from the join
+ buffer by the method skip_recurrent_candidate_for_match.
+ This implementation of the virtual method get_next_candidate_for_match
+ just decrements the counter of the records that are to be iterated over
+ and returns the current value of the cursor 'pos' as the position of
+ the record to be processed.
+
+ RETURN VALUE
+ pointer to the position right after the prefix of the current record
+ in the join buffer if the there is another record to iterate over,
+ 0 - otherwise.
+*/
+
+uchar *JOIN_CACHE_BNL::get_next_candidate_for_match()
+{
+ if (!rem_records)
+ return 0;
+ rem_records--;
+ return pos+base_prefix_length;
+}
+
+
+/*
+ Check whether the matching record from the BNL cache is to be skipped
+
+ SYNOPSIS
+ skip_next_candidate_for_match
+ rec_ptr pointer to the position in the join buffer right after the prefix
+ of the current record
+
+ DESCRIPTION
+ This implementation of the virtual function just calls the
+ method skip_if_not_needed_match to check whether the record referenced by
+ ref_ptr has its match flag set either to MATCH_FOUND and join_tab is the
+ first inner table of a semi-join, or it's set to MATCH_IMPOSSIBLE and
+ join_tab is the first inner table of an outer join.
+ If so, the function just skips this record setting the value of the
+ cursor 'pos' to the position right after it.
+
+ RETURN VALUE
+ TRUE the record referenced by rec_ptr has been skipped
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_BNL::skip_next_candidate_for_match(uchar *rec_ptr)
+{
+ pos= rec_ptr-base_prefix_length;
+ return skip_if_not_needed_match();
+}
+
+
+/*
+ Read next record from the BNL join cache buffer when looking for matches
+
+ SYNOPSIS
+ read_next_candidate_for_match
+ rec_ptr pointer to the position in the join buffer right after the prefix
+ the current record.
+
+ DESCRIPTION
+ This implementation of the virtual method read_next_candidate_for_match
+ calls the method get_record to read the record referenced by rec_ptr from
+ the join buffer into the record buffer. If this record refers to the
+ fields in the other join buffers the call of get_record ensures that
+ these fields are read into the corresponding record buffers as well.
+ This function is supposed to be called after a successful call of
+ the method get_next_candidate_for_match.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE_BNL::read_next_candidate_for_match(uchar *rec_ptr)
+{
+ pos= rec_ptr-base_prefix_length;
+ get_record();
+}
+
+
+/*
+ Initialize the BNL join cache
+
+ SYNOPSIS
+ init
+ for_explain join buffer is initialized for explain only
+
+ DESCRIPTION
+ The function initializes the cache structure. It is supposed to be called
+ right after a constructor for the JOIN_CACHE_BNL.
+
+ NOTES
+ The function first constructs a companion object of the type JOIN_TAB_SCAN,
+ then it calls the init method of the parent class.
+
+ RETURN VALUE
+ 0 initialization with buffer allocations has been succeeded
+ 1 otherwise
+*/
+
+int JOIN_CACHE_BNL::init(bool for_explain)
+{
+ DBUG_ENTER("JOIN_CACHE_BNL::init");
+
+ if (!(join_tab_scan= new JOIN_TAB_SCAN(join, join_tab)))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(JOIN_CACHE::init(for_explain));
+}
+
+
+/*
+ Get the chain of records from buffer matching the current candidate for join
+
+ SYNOPSIS
+ get_matching_chain_by_join_key()
+
+ DESCRIPTION
+ This function first build a join key for the record of join_tab that
+ currently is in the join buffer for this table. Then it looks for
+ the key entry with this key in the hash table of the join cache.
+ If such a key entry is found the function returns the pointer to
+ the head of the chain of records in the join_buffer that match this
+ key.
+
+ RETURN VALUE
+ The pointer to the corresponding circular list of records if
+ the key entry with the join key is found, 0 - otherwise.
+*/
+
+uchar *JOIN_CACHE_BNLH::get_matching_chain_by_join_key()
+{
+ uchar *key_ref_ptr;
+ TABLE *table= join_tab->table;
+ TABLE_REF *ref= &join_tab->ref;
+ KEY *keyinfo= join_tab->get_keyinfo_by_key_no(ref->key);
+ /* Build the join key value out of the record in the record buffer */
+ key_copy(key_buff, table->record[0], keyinfo, key_length, TRUE);
+ /* Look for this key in the join buffer */
+ if (!key_search(key_buff, key_length, &key_ref_ptr))
+ return 0;
+ return key_ref_ptr+get_size_of_key_offset();
+}
+
+
+/*
+ Prepare to iterate over the BNLH join cache buffer to look for matches
+
+ SYNOPSIS
+ prepare_look_for_matches()
+ skip_last <-> ignore the last record in the buffer
+
+ DESCRIPTION
+ The function prepares the join cache for an iteration over the
+ records in the join buffer. The iteration is performed when looking
+ for matches for the record from the joined table join_tab that
+ has been placed into the record buffer of the joined table.
+ If the value of the parameter skip_last is TRUE then the last
+ record from the join buffer is ignored.
+ The function builds the hashed key from the join fields of join_tab
+ and uses this key to look in the hash table of the join cache for
+ the chain of matching records in in the join buffer. If it finds
+ such a chain it sets the member last_rec_ref_ptr to point to the
+ last link of the chain while setting the member next_rec_ref_po 0.
+
+ RETURN VALUE
+ TRUE there are no matching records in the buffer to iterate over
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_BNLH::prepare_look_for_matches(bool skip_last)
+{
+ uchar *curr_matching_chain;
+ last_matching_rec_ref_ptr= next_matching_rec_ref_ptr= 0;
+ if (!(curr_matching_chain= get_matching_chain_by_join_key()))
+ return 1;
+ last_matching_rec_ref_ptr= get_next_rec_ref(curr_matching_chain);
+ return 0;
+}
+
+
+/*
+ Get next record from the BNLH join cache buffer when looking for matches
+
+ SYNOPSIS
+ get_next_candidate_for_match
+
+ DESCRIPTION
+ This method is used for iterations over the records from the join
+ cache buffer when looking for matches for records from join_tab.
+ The methods performs the necessary preparations to read the next record
+ from the join buffer into the record buffer by the method
+ read_next_candidate_for_match, or, to skip the next record from the join
+ buffer by the method skip_next_candidate_for_match.
+ This implementation of the virtual method moves to the next record
+ in the chain of all records from the join buffer that are to be
+ equi-joined with the current record from join_tab.
+
+ RETURN VALUE
+ pointer to the beginning of the record fields in the join buffer
+ if the there is another record to iterate over, 0 - otherwise.
+*/
+
+uchar *JOIN_CACHE_BNLH::get_next_candidate_for_match()
+{
+ if (next_matching_rec_ref_ptr == last_matching_rec_ref_ptr)
+ return 0;
+ next_matching_rec_ref_ptr= get_next_rec_ref(next_matching_rec_ref_ptr ?
+ next_matching_rec_ref_ptr :
+ last_matching_rec_ref_ptr);
+ return next_matching_rec_ref_ptr+rec_fields_offset;
+}
+
+
+/*
+ Check whether the matching record from the BNLH cache is to be skipped
+
+ SYNOPSIS
+ skip_next_candidate_for_match
+ rec_ptr pointer to the position in the join buffer right after
+ the previous record
+
+ DESCRIPTION
+ This implementation of the virtual function just calls the
+ method get_match_flag_by_pos to check whether the record referenced
+ by ref_ptr has its match flag set to MATCH_FOUND.
+
+ RETURN VALUE
+ TRUE the record referenced by rec_ptr has its match flag set to
+ MATCH_FOUND
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_BNLH::skip_next_candidate_for_match(uchar *rec_ptr)
+{
+ return join_tab->check_only_first_match() &&
+ (get_match_flag_by_pos(rec_ptr) == MATCH_FOUND);
+}
+
+
+/*
+ Read next record from the BNLH join cache buffer when looking for matches
+
+ SYNOPSIS
+ read_next_candidate_for_match
+ rec_ptr pointer to the position in the join buffer right after
+ the previous record
+
+ DESCRIPTION
+ This implementation of the virtual method read_next_candidate_for_match
+ calls the method get_record_by_pos to read the record referenced by rec_ptr
+ from the join buffer into the record buffer. If this record refers to
+ fields in the other join buffers the call of get_record_by_po ensures that
+ these fields are read into the corresponding record buffers as well.
+ This function is supposed to be called after a successful call of
+ the method get_next_candidate_for_match.
+
+ RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE_BNLH::read_next_candidate_for_match(uchar *rec_ptr)
+{
+ get_record_by_pos(rec_ptr);
+}
+
+
+/*
+ Initialize the BNLH join cache
+
+ SYNOPSIS
+ init
+ for_explain join buffer is initialized for explain only
+
+ DESCRIPTION
+ The function initializes the cache structure. It is supposed to be called
+ right after a constructor for the JOIN_CACHE_BNLH.
+
+ NOTES
+ The function first constructs a companion object of the type JOIN_TAB_SCAN,
+ then it calls the init method of the parent class.
+
+ RETURN VALUE
+ 0 initialization with buffer allocations has been succeeded
+ 1 otherwise
+*/
+
+int JOIN_CACHE_BNLH::init(bool for_explain)
+{
+ DBUG_ENTER("JOIN_CACHE_BNLH::init");
+
+ if (!(join_tab_scan= new JOIN_TAB_SCAN(join, join_tab)))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(JOIN_CACHE_HASHED::init(for_explain));
+}
+
+
+/*
+ Calculate the increment of the MRR buffer for a record write
+
+ SYNOPSIS
+ aux_buffer_incr()
+
+ DESCRIPTION
+ This implementation of the virtual function aux_buffer_incr determines
+ for how much the size of the MRR buffer should be increased when another
+ record is added to the cache.
+
+ RETURN VALUE
+ the increment of the size of the MRR buffer for the next record
+*/
+
+uint JOIN_TAB_SCAN_MRR::aux_buffer_incr(ulong recno)
+{
+ uint incr= 0;
+ TABLE_REF *ref= &join_tab->ref;
+ TABLE *tab= join_tab->table;
+ ha_rows rec_per_key=
+ (ha_rows) tab->key_info[ref->key].actual_rec_per_key(ref->key_parts-1);
+ set_if_bigger(rec_per_key, 1);
+ if (recno == 1)
+ incr= ref->key_length + tab->file->ref_length;
+ incr+= tab->file->stats.mrr_length_per_rec * rec_per_key;
+ return incr;
+}
+
+
+/*
+ Initiate iteration over records returned by MRR for the current join buffer
+
+ SYNOPSIS
+ open()
+
+ DESCRIPTION
+ The function initiates the process of iteration over the records from
+ join_tab returned by the MRR interface functions for records from
+ the join buffer. Such an iteration is performed by the BKA/BKAH join
+ algorithm for each new refill of the join buffer.
+ The function calls the MRR handler function multi_range_read_init to
+ initiate this process.
+
+ RETURN VALUE
+ 0 the initiation is a success
+ error code otherwise
+*/
+
+int JOIN_TAB_SCAN_MRR::open()
+{
+ handler *file= join_tab->table->file;
+
+ join_tab->table->null_row= 0;
+
+
+ /* Dynamic range access is never used with BKA */
+ DBUG_ASSERT(join_tab->use_quick != 2);
+
+ save_or_restore_used_tabs(join_tab, FALSE);
+
+ init_mrr_buff();
+
+ /*
+ Prepare to iterate over keys from the join buffer and to get
+ matching candidates obtained with MMR handler functions.
+ */
+ if (!file->inited)
+ file->ha_index_init(join_tab->ref.key, 1);
+ ranges= cache->get_number_of_ranges_for_mrr();
+ if (!join_tab->cache_idx_cond)
+ range_seq_funcs.skip_index_tuple= 0;
+ return file->multi_range_read_init(&range_seq_funcs, (void*) cache,
+ ranges, mrr_mode, &mrr_buff);
+}
+
+
+/*
+ Read the next record returned by MRR for the current join buffer
+
+ SYNOPSIS
+ next()
+
+ DESCRIPTION
+ The function reads the next record from the joined table join_tab
+ returned by the MRR handler function multi_range_read_next for
+ the current refill of the join buffer. The record is read into
+ the record buffer used for join_tab records in join operations.
+
+ RETURN VALUE
+ 0 the next record exists and has been successfully read
+ error code otherwise
+*/
+
+int JOIN_TAB_SCAN_MRR::next()
+{
+ char **ptr= (char **) cache->get_curr_association_ptr();
+
+ DBUG_ASSERT(sizeof(range_id_t) == sizeof(*ptr));
+ int rc= join_tab->table->file->multi_range_read_next((range_id_t*)ptr) ? -1 : 0;
+ if (!rc)
+ {
+ /*
+ If a record in in an incremental cache contains no fields then the
+ association for the last record in cache will be equal to cache->end_pos
+ */
+ /*
+ psergey: this makes no sense where HA_MRR_NO_ASSOC is used.
+ DBUG_ASSERT(cache->buff <= (uchar *) (*ptr) &&
+ (uchar *) (*ptr) <= cache->end_pos);
+ */
+ if (join_tab->table->vfield)
+ update_virtual_fields(join->thd, join_tab->table);
+ }
+ return rc;
+}
+
+
+static
+void bka_range_seq_key_info(void *init_params, uint *length,
+ key_part_map *map)
+{
+TABLE_REF *ref= &(((JOIN_CACHE*)init_params)->join_tab->ref);
+*length= ref->key_length;
+*map= (key_part_map(1) << ref->key_parts) - 1;
+}
+
+
+/*
+Initialize retrieval of range sequence for BKA join algorithm
+
+SYNOPSIS
+ bka_range_seq_init()
+ init_params pointer to the BKA join cache object
+ n_ranges the number of ranges obtained
+ flags combination of MRR flags
+
+DESCRIPTION
+ The function interprets init_param as a pointer to a JOIN_CACHE_BKA
+ object. The function prepares for an iteration over the join keys
+ built for all records from the cache join buffer.
+
+NOTE
+ This function are used only as a callback function.
+
+RETURN VALUE
+ init_param value that is to be used as a parameter of bka_range_seq_next()
+*/
+
+static
+range_seq_t bka_range_seq_init(void *init_param, uint n_ranges, uint flags)
+{
+DBUG_ENTER("bka_range_seq_init");
+JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) init_param;
+cache->reset(0);
+DBUG_RETURN((range_seq_t) init_param);
+}
+
+
+/*
+Get the next range/key over records from the join buffer used by a BKA cache
+
+SYNOPSIS
+ bka_range_seq_next()
+ seq the value returned by bka_range_seq_init
+ range OUT reference to the next range
+
+DESCRIPTION
+ The function interprets seq as a pointer to a JOIN_CACHE_BKA
+ object. The function returns a pointer to the range descriptor
+ for the key built over the next record from the join buffer.
+
+NOTE
+ This function are used only as a callback function.
+
+RETURN VALUE
+ FALSE ok, the range structure filled with info about the next range/key
+ TRUE no more ranges
+*/
+
+static
+bool bka_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range)
+{
+DBUG_ENTER("bka_range_seq_next");
+JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq;
+TABLE_REF *ref= &cache->join_tab->ref;
+key_range *start_key= &range->start_key;
+if ((start_key->length= cache->get_next_key((uchar **) &start_key->key)))
+{
+ start_key->keypart_map= (1 << ref->key_parts) - 1;
+ start_key->flag= HA_READ_KEY_EXACT;
+ range->end_key= *start_key;
+ range->end_key.flag= HA_READ_AFTER_KEY;
+ range->ptr= (char *) cache->get_curr_rec();
+ range->range_flag= EQ_RANGE;
+ DBUG_RETURN(0);
+}
+DBUG_RETURN(1);
+}
+
+
+/*
+Check whether range_info orders to skip the next record from BKA buffer
+
+SYNOPSIS
+ bka_range_seq_skip_record()
+ seq value returned by bka_range_seq_init()
+ range_info information about the next range
+ rowid [NOT USED] rowid of the record to be checked
+
+
+DESCRIPTION
+ The function interprets seq as a pointer to a JOIN_CACHE_BKA object.
+ The function returns TRUE if the record with this range_info
+ is to be filtered out from the stream of records returned by
+ multi_range_read_next().
+
+NOTE
+ This function are used only as a callback function.
+
+RETURN VALUE
+ 1 record with this range_info is to be filtered out from the stream
+ of records returned by multi_range_read_next()
+ 0 the record is to be left in the stream
+*/
+
+static
+bool bka_range_seq_skip_record(range_seq_t rseq, range_id_t range_info, uchar *rowid)
+{
+DBUG_ENTER("bka_range_seq_skip_record");
+JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq;
+bool res= cache->get_match_flag_by_pos((uchar *) range_info) ==
+ JOIN_CACHE::MATCH_FOUND;
+DBUG_RETURN(res);
+}
+
+
+/*
+Check if the record combination from BKA cache matches the index condition
+
+SYNOPSIS
+ bka_skip_index_tuple()
+ rseq value returned by bka_range_seq_init()
+ range_info record chain for the next range/key returned by MRR
+
+DESCRIPTION
+ This is wrapper for JOIN_CACHE_BKA::skip_index_tuple method,
+ see comments there.
+
+NOTE
+ This function is used as a RANGE_SEQ_IF::skip_index_tuple callback.
+
+RETURN VALUE
+ 0 The record combination satisfies the index condition
+ 1 Otherwise
+*/
+
+static
+bool bka_skip_index_tuple(range_seq_t rseq, range_id_t range_info)
+{
+DBUG_ENTER("bka_skip_index_tuple");
+JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq;
+THD *thd= cache->thd();
+bool res;
+status_var_increment(thd->status_var.ha_icp_attempts);
+if (!(res= cache->skip_index_tuple(range_info)))
+ status_var_increment(thd->status_var.ha_icp_match);
+DBUG_RETURN(res);
+}
+
+
+/*
+Prepare to read the record from BKA cache matching the current joined record
+
+SYNOPSIS
+ prepare_look_for_matches()
+ skip_last <-> ignore the last record in the buffer (always unused here)
+
+DESCRIPTION
+ The function prepares to iterate over records in the join cache buffer
+ matching the record loaded into the record buffer for join_tab when
+ performing join operation by BKA join algorithm. With BKA algorithms the
+ record loaded into the record buffer for join_tab always has a direct
+ reference to the matching records from the join buffer. When the regular
+ BKA join algorithm is employed the record from join_tab can refer to
+ only one such record.
+ The function sets the counter of the remaining records from the cache
+ buffer that would match the current join_tab record to 1.
+
+RETURN VALUE
+ TRUE there are no records in the buffer to iterate over
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_BKA::prepare_look_for_matches(bool skip_last)
+{
+if (!records)
+ return TRUE;
+rem_records= 1;
+return FALSE;
+}
+
+
+/*
+Get the record from the BKA cache matching the current joined record
+
+SYNOPSIS
+ get_next_candidate_for_match
+
+DESCRIPTION
+ This method is used for iterations over the records from the join
+ cache buffer when looking for matches for records from join_tab.
+ The method performs the necessary preparations to read the next record
+ from the join buffer into the record buffer by the method
+ read_next_candidate_for_match, or, to skip the next record from the join
+ buffer by the method skip_if_not_needed_match.
+ This implementation of the virtual method get_next_candidate_for_match
+ just decrements the counter of the records that are to be iterated over
+ and returns the value of curr_association as a reference to the position
+ of the beginning of the record fields in the buffer.
+
+RETURN VALUE
+ pointer to the start of the record fields in the join buffer
+ if the there is another record to iterate over, 0 - otherwise.
+*/
+
+uchar *JOIN_CACHE_BKA::get_next_candidate_for_match()
+{
+if (!rem_records)
+ return 0;
+rem_records--;
+return curr_association;
+}
+
+
+/*
+Check whether the matching record from the BKA cache is to be skipped
+
+SYNOPSIS
+ skip_next_candidate_for_match
+ rec_ptr pointer to the position in the join buffer right after
+ the previous record
+
+DESCRIPTION
+ This implementation of the virtual function just calls the
+ method get_match_flag_by_pos to check whether the record referenced
+ by ref_ptr has its match flag set to MATCH_FOUND.
+
+RETURN VALUE
+ TRUE the record referenced by rec_ptr has its match flag set to
+ MATCH_FOUND
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_BKA::skip_next_candidate_for_match(uchar *rec_ptr)
+{
+return join_tab->check_only_first_match() &&
+ (get_match_flag_by_pos(rec_ptr) == MATCH_FOUND);
+}
+
+
+/*
+Read the next record from the BKA join cache buffer when looking for matches
+
+SYNOPSIS
+ read_next_candidate_for_match
+ rec_ptr pointer to the position in the join buffer right after
+ the previous record
+
+DESCRIPTION
+ This implementation of the virtual method read_next_candidate_for_match
+ calls the method get_record_by_pos to read the record referenced by rec_ptr
+ from the join buffer into the record buffer. If this record refers to
+ fields in the other join buffers the call of get_record_by_po ensures that
+ these fields are read into the corresponding record buffers as well.
+ This function is supposed to be called after a successful call of
+ the method get_next_candidate_for_match.
+
+RETURN VALUE
+ none
+*/
+
+void JOIN_CACHE_BKA::read_next_candidate_for_match(uchar *rec_ptr)
+{
+get_record_by_pos(rec_ptr);
+}
+
+
+/*
+Initialize the BKA join cache
+
+SYNOPSIS
+ init
+ for_explain join buffer is initialized for explain only
+
+
+DESCRIPTION
+ The function initializes the cache structure. It is supposed to be called
+ right after a constructor for the JOIN_CACHE_BKA.
+
+NOTES
+ The function first constructs a companion object of the type
+ JOIN_TAB_SCAN_MRR, then it calls the init method of the parent class.
+
+RETURN VALUE
+ 0 initialization with buffer allocations has been succeeded
+ 1 otherwise
+*/
+
+int JOIN_CACHE_BKA::init(bool for_explain)
+{
+int res;
+bool check_only_first_match= join_tab->check_only_first_match();
+
+RANGE_SEQ_IF rs_funcs= { bka_range_seq_key_info,
+ bka_range_seq_init,
+ bka_range_seq_next,
+ check_only_first_match ?
+ bka_range_seq_skip_record : 0,
+ bka_skip_index_tuple };
+
+DBUG_ENTER("JOIN_CACHE_BKA::init");
+
+JOIN_TAB_SCAN_MRR *jsm;
+if (!(join_tab_scan= jsm= new JOIN_TAB_SCAN_MRR(join, join_tab,
+ mrr_mode, rs_funcs)))
+ DBUG_RETURN(1);
+
+if ((res= JOIN_CACHE::init(for_explain)))
+ DBUG_RETURN(res);
+
+if (use_emb_key)
+ jsm->mrr_mode |= HA_MRR_MATERIALIZED_KEYS;
+
+DBUG_RETURN(0);
+}
+
+
+/*
+Get the key built over the next record from BKA join buffer
+
+SYNOPSIS
+ get_next_key()
+ key pointer to the buffer where the key value is to be placed
+
+DESCRIPTION
+ The function reads key fields from the current record in the join buffer.
+ and builds the key value out of these fields that will be used to access
+ the 'join_tab' table. Some of key fields may belong to previous caches.
+ They are accessed via record references to the record parts stored in the
+ previous join buffers. The other key fields always are placed right after
+ the flag fields of the record.
+ If the key is embedded, which means that its value can be read directly
+ from the join buffer, then *key is set to the beginning of the key in
+ this buffer. Otherwise the key is built in the join_tab->ref->key_buff.
+ The function returns the length of the key if it succeeds ro read it.
+ If is assumed that the functions starts reading at the position of
+ the record length which is provided for each records in a BKA cache.
+ After the key is built the 'pos' value points to the first position after
+ the current record.
+ The function just skips the records with MATCH_IMPOSSIBLE in the
+ match flag field if there is any.
+ The function returns 0 if the initial position is after the beginning
+ of the record fields for last record from the join buffer.
+
+RETURN VALUE
+ length of the key value - if the starting value of 'pos' points to
+ the position before the fields for the last record,
+ 0 - otherwise.
+*/
+
+uint JOIN_CACHE_BKA::get_next_key(uchar ** key)
+{
+uint len;
+uint32 rec_len;
+uchar *init_pos;
+JOIN_CACHE *cache;
+
+start:
+
+/* Any record in a BKA cache is prepended with its length */
+DBUG_ASSERT(with_length);
+
+if ((pos+size_of_rec_len) > last_rec_pos || !records)
+ return 0;
+
+/* Read the length of the record */
+rec_len= get_rec_length(pos);
+pos+= size_of_rec_len;
+init_pos= pos;
+
+/* Read a reference to the previous cache if any */
+if (prev_cache)
+ pos+= prev_cache->get_size_of_rec_offset();
+
+curr_rec_pos= pos;
+
+/* Read all flag fields of the record */
+read_flag_fields();
+
+if (with_match_flag &&
+ (Match_flag) curr_rec_pos[0] == MATCH_IMPOSSIBLE )
+{
+ pos= init_pos+rec_len;
+ goto start;
+}
+
+if (use_emb_key)
+{
+ /* An embedded key is taken directly from the join buffer */
+ *key= pos;
+ len= emb_key_length;
+}
+else
+{
+ /* Read key arguments from previous caches if there are any such fields */
+ if (external_key_arg_fields)
+ {
+ uchar *rec_ptr= curr_rec_pos;
+ uint key_arg_count= external_key_arg_fields;
+ CACHE_FIELD **copy_ptr= blob_ptr-key_arg_count;
+ for (cache= prev_cache; key_arg_count; cache= cache->prev_cache)
+ {
+ uint len= 0;
+ DBUG_ASSERT(cache);
+ rec_ptr= cache->get_rec_ref(rec_ptr);
+ while (!cache->referenced_fields)
+ {
+ cache= cache->prev_cache;
+ DBUG_ASSERT(cache);
+ rec_ptr= cache->get_rec_ref(rec_ptr);
+ }
+ while (key_arg_count &&
+ cache->read_referenced_field(*copy_ptr, rec_ptr, &len))
+ {
+ copy_ptr++;
+ --key_arg_count;
+ }
+ }
+ }
+
+ /*
+ Read the other key arguments from the current record. The fields for
+ these arguments are always first in the sequence of the record's fields.
+ */
+ CACHE_FIELD *copy= field_descr+flag_fields;
+ CACHE_FIELD *copy_end= copy+local_key_arg_fields;
+ bool blob_in_rec_buff= blob_data_is_in_rec_buff(curr_rec_pos);
+ for ( ; copy < copy_end; copy++)
+ read_record_field(copy, blob_in_rec_buff);
+
+ /* Build the key over the fields read into the record buffers */
+ TABLE_REF *ref= &join_tab->ref;
+ cp_buffer_from_ref(join->thd, join_tab->table, ref);
+ *key= ref->key_buff;
+ len= ref->key_length;
+}
+
+pos= init_pos+rec_len;
+
+return len;
+}
+
+
+/*
+Check the index condition of the joined table for a record from the BKA cache
+
+SYNOPSIS
+ skip_index_tuple()
+ range_info pointer to the record returned by MRR
+
+DESCRIPTION
+ This function is invoked from MRR implementation to check if an index
+ tuple matches the index condition. It is used in the case where the index
+ condition actually depends on both columns of the used index and columns
+ from previous tables.
+
+NOTES
+ Accessing columns of the previous tables requires special handling with
+ BKA. The idea of BKA is to collect record combinations in a buffer and
+ then do a batch of ref access lookups, i.e. by the time we're doing a
+ lookup its previous-records-combination is not in prev_table->record[0]
+ but somewhere in the join buffer.
+ We need to get it from there back into prev_table(s)->record[0] before we
+ can evaluate the index condition, and that's why we need this function
+ instead of regular IndexConditionPushdown.
+
+NOTES
+ Possible optimization:
+ Before we unpack the record from a previous table
+ check if this table is used in the condition.
+ If so then unpack the record otherwise skip the unpacking.
+ This should be done by a special virtual method
+ get_partial_record_by_pos().
+
+RETURN VALUE
+ 1 the record combination does not satisfies the index condition
+ 0 otherwise
+*/
+
+bool JOIN_CACHE_BKA::skip_index_tuple(range_id_t range_info)
+{
+DBUG_ENTER("JOIN_CACHE_BKA::skip_index_tuple");
+get_record_by_pos((uchar*)range_info);
+DBUG_RETURN(!join_tab->cache_idx_cond->val_int());
+}
+
+
+
+/*
+Initialize retrieval of range sequence for the BKAH join algorithm
+
+SYNOPSIS
+ bkah_range_seq_init()
+ init_params pointer to the BKAH join cache object
+ n_ranges the number of ranges obtained
+ flags combination of MRR flags
+
+DESCRIPTION
+ The function interprets init_param as a pointer to a JOIN_CACHE_BKAH
+ object. The function prepares for an iteration over distinct join keys
+ built over the records from the cache join buffer.
+
+NOTE
+ This function are used only as a callback function.
+
+RETURN VALUE
+ init_param value that is to be used as a parameter of
+ bkah_range_seq_next()
+*/
+
+static
+range_seq_t bkah_range_seq_init(void *init_param, uint n_ranges, uint flags)
+{
+DBUG_ENTER("bkah_range_seq_init");
+JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) init_param;
+cache->reset(0);
+DBUG_RETURN((range_seq_t) init_param);
+}
+
+
+/*
+Get the next range/key over records from the join buffer of a BKAH cache
+
+SYNOPSIS
+ bkah_range_seq_next()
+ seq value returned by bkah_range_seq_init()
+ range OUT reference to the next range
+
+DESCRIPTION
+ The function interprets seq as a pointer to a JOIN_CACHE_BKAH
+ object. The function returns a pointer to the range descriptor
+ for the next unique key built over records from the join buffer.
+
+NOTE
+ This function are used only as a callback function.
+
+RETURN VALUE
+ FALSE ok, the range structure filled with info about the next range/key
+ TRUE no more ranges
+*/
+
+static
+bool bkah_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range)
+{
+DBUG_ENTER("bkah_range_seq_next");
+JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq;
+TABLE_REF *ref= &cache->join_tab->ref;
+key_range *start_key= &range->start_key;
+if ((start_key->length= cache->get_next_key((uchar **) &start_key->key)))
+{
+ start_key->keypart_map= (1 << ref->key_parts) - 1;
+ start_key->flag= HA_READ_KEY_EXACT;
+ range->end_key= *start_key;
+ range->end_key.flag= HA_READ_AFTER_KEY;
+ range->ptr= (char *) cache->get_curr_key_chain();
+ range->range_flag= EQ_RANGE;
+ DBUG_RETURN(0);
+}
+DBUG_RETURN(1);
+}
+
+
+/*
+Check whether range_info orders to skip the next record from BKAH join buffer
+
+SYNOPSIS
+ bkah_range_seq_skip_record()
+ seq value returned by bkah_range_seq_init()
+ range_info information about the next range/key returned by MRR
+ rowid [NOT USED] rowid of the record to be checked (not used)
+
+DESCRIPTION
+ The function interprets seq as a pointer to a JOIN_CACHE_BKAH
+ object. The function returns TRUE if the record with this range_info
+ is to be filtered out from the stream of records returned by
+ multi_range_read_next().
+
+NOTE
+ This function are used only as a callback function.
+
+RETURN VALUE
+ 1 record with this range_info is to be filtered out from the stream
+ of records returned by multi_range_read_next()
+ 0 the record is to be left in the stream
+*/
+
+static
+bool bkah_range_seq_skip_record(range_seq_t rseq, range_id_t range_info,
+ uchar *rowid)
+{
+DBUG_ENTER("bkah_range_seq_skip_record");
+JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq;
+bool res= cache->check_all_match_flags_for_key((uchar *) range_info);
+DBUG_RETURN(res);
+}
+
+
+/*
+Check if the record combination from BKAH cache matches the index condition
+
+SYNOPSIS
+ bkah_skip_index_tuple()
+ rseq value returned by bka_range_seq_init()
+ range_info record chain for the next range/key returned by MRR
+
+DESCRIPTION
+ This is wrapper for JOIN_CACHE_BKA_UNIQUE::skip_index_tuple method,
+ see comments there.
+
+NOTE
+ This function is used as a RANGE_SEQ_IF::skip_index_tuple callback.
+
+RETURN VALUE
+ 0 some records from the chain satisfy the index condition
+ 1 otherwise
+*/
+
+static
+bool bkah_skip_index_tuple(range_seq_t rseq, range_id_t range_info)
+{
+DBUG_ENTER("bka_unique_skip_index_tuple");
+JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq;
+THD *thd= cache->thd();
+bool res;
+status_var_increment(thd->status_var.ha_icp_attempts);
+if (!(res= cache->skip_index_tuple(range_info)))
+ status_var_increment(thd->status_var.ha_icp_match);
+DBUG_RETURN(res);
+}
+
+
+/*
+Prepare to read record from BKAH cache matching the current joined record
+
+SYNOPSIS
+ prepare_look_for_matches()
+ skip_last <-> ignore the last record in the buffer (always unused here)
+
+DESCRIPTION
+ The function prepares to iterate over records in the join cache buffer
+ matching the record loaded into the record buffer for join_tab when
+ performing join operation by BKAH join algorithm. With BKAH algorithm, if
+ association labels are used, then record loaded into the record buffer
+ for join_tab always has a direct reference to the chain of the mathing
+ records from the join buffer. If association labels are not used then
+ then the chain of the matching records is obtained by the call of the
+ get_key_chain_by_join_key function.
+
+RETURN VALUE
+ TRUE there are no records in the buffer to iterate over
+ FALSE otherwise
+*/
+
+bool JOIN_CACHE_BKAH::prepare_look_for_matches(bool skip_last)
+{
+last_matching_rec_ref_ptr= next_matching_rec_ref_ptr= 0;
+if (no_association &&
+ !(curr_matching_chain= get_matching_chain_by_join_key())) //psergey: added '!'
+ return 1;
+ last_matching_rec_ref_ptr= get_next_rec_ref(curr_matching_chain);
+ return 0;
+}
+
+/*
+ Initialize the BKAH join cache
+
+ SYNOPSIS
+ init
+ for_explain join buffer is initialized for explain only
+
+ DESCRIPTION
+ The function initializes the cache structure. It is supposed to be called
+ right after a constructor for the JOIN_CACHE_BKAH.
+
+ NOTES
+ The function first constructs a companion object of the type
+ JOIN_TAB_SCAN_MRR, then it calls the init method of the parent class.
+
+ RETURN VALUE
+ 0 initialization with buffer allocations has been succeeded
+ 1 otherwise
+*/
+
+int JOIN_CACHE_BKAH::init(bool for_explain)
+{
+ bool check_only_first_match= join_tab->check_only_first_match();
+
+ no_association= MY_TEST(mrr_mode & HA_MRR_NO_ASSOCIATION);
+
+ RANGE_SEQ_IF rs_funcs= { bka_range_seq_key_info,
+ bkah_range_seq_init,
+ bkah_range_seq_next,
+ check_only_first_match && !no_association ?
+ bkah_range_seq_skip_record : 0,
+ bkah_skip_index_tuple };
+
+ DBUG_ENTER("JOIN_CACHE_BKAH::init");
+
+ if (!(join_tab_scan= new JOIN_TAB_SCAN_MRR(join, join_tab,
+ mrr_mode, rs_funcs)))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(JOIN_CACHE_HASHED::init(for_explain));
+}
+
+
+/*
+ Check the index condition of the joined table for a record from the BKA cache
+
+ SYNOPSIS
+ skip_index_tuple()
+ range_info record chain returned by MRR
+
+ DESCRIPTION
+ See JOIN_CACHE_BKA::skip_index_tuple().
+ This function is the variant for use with rhe class JOIN_CACHE_BKAH.
+ The difference from JOIN_CACHE_BKA case is that there may be multiple
+ previous table record combinations that share the same key(MRR range).
+ As a consequence, we need to loop through the chain of all table record
+ combinations that match the given MRR range key range_info until we find
+ one that satisfies the index condition.
+
+ NOTE
+ Possible optimization:
+ Before we unpack the record from a previous table
+ check if this table is used in the condition.
+ If so then unpack the record otherwise skip the unpacking.
+ This should be done by a special virtual method
+ get_partial_record_by_pos().
+
+ RETURN VALUE
+ 1 any record combination from the chain referred by range_info
+ does not satisfy the index condition
+ 0 otherwise
+
+
+*/
+
+bool JOIN_CACHE_BKAH::skip_index_tuple(range_id_t range_info)
+{
+ uchar *last_rec_ref_ptr= get_next_rec_ref((uchar*) range_info);
+ uchar *next_rec_ref_ptr= last_rec_ref_ptr;
+ DBUG_ENTER("JOIN_CACHE_BKAH::skip_index_tuple");
+ do
+ {
+ next_rec_ref_ptr= get_next_rec_ref(next_rec_ref_ptr);
+ uchar *rec_ptr= next_rec_ref_ptr + rec_fields_offset;
+ get_record_by_pos(rec_ptr);
+ if (join_tab->cache_idx_cond->val_int())
+ DBUG_RETURN(FALSE);
+ } while(next_rec_ref_ptr != last_rec_ref_ptr);
+ DBUG_RETURN(TRUE);
+}
diff --git a/sql/sql_join_cache.h b/sql/sql_join_cache.h
new file mode 100644
index 00000000000..a3e69f92e34
--- /dev/null
+++ b/sql/sql_join_cache.h
@@ -0,0 +1,1435 @@
+/*
+ Copyright (c) 2011, 2012, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/*
+ This file contains declarations for implementations
+ of block based join algorithms
+*/
+
+#define JOIN_CACHE_INCREMENTAL_BIT 1
+#define JOIN_CACHE_HASHED_BIT 2
+#define JOIN_CACHE_BKA_BIT 4
+
+/*
+ Categories of data fields of variable length written into join cache buffers.
+ The value of any of these fields is written into cache together with the
+ prepended length of the value.
+*/
+#define CACHE_BLOB 1 /* blob field */
+#define CACHE_STRIPPED 2 /* field stripped of trailing spaces */
+#define CACHE_VARSTR1 3 /* short string value (length takes 1 byte) */
+#define CACHE_VARSTR2 4 /* long string value (length takes 2 bytes) */
+#define CACHE_ROWID 5 /* ROWID field */
+
+/*
+ The CACHE_FIELD structure used to describe fields of records that
+ are written into a join cache buffer from record buffers and backward.
+*/
+typedef struct st_cache_field {
+ uchar *str; /**< buffer from/to where the field is to be copied */
+ uint length; /**< maximal number of bytes to be copied from/to str */
+ /*
+ Field object for the moved field
+ (0 - for a flag field, see JOIN_CACHE::create_flag_fields).
+ */
+ Field *field;
+ uint type; /**< category of the of the copied field (CACHE_BLOB et al.) */
+ /*
+ The number of the record offset value for the field in the sequence
+ of offsets placed after the last field of the record. These
+ offset values are used to access fields referred to from other caches.
+ If the value is 0 then no offset for the field is saved in the
+ trailing sequence of offsets.
+ */
+ uint referenced_field_no;
+ /* The remaining structure fields are used as containers for temp values */
+ uint blob_length; /**< length of the blob to be copied */
+ uint offset; /**< field offset to be saved in cache buffer */
+} CACHE_FIELD;
+
+
+class JOIN_TAB_SCAN;
+
+struct st_explain_bka_type;
+
+/*
+ JOIN_CACHE is the base class to support the implementations of
+ - Block Nested Loop (BNL) Join Algorithm,
+ - Block Nested Loop Hash (BNLH) Join Algorithm,
+ - Batched Key Access (BKA) Join Algorithm.
+
+ The first algorithm is supported by the derived class JOIN_CACHE_BNL,
+ the second algorithm is supported by the derived class JOIN_CACHE_BNLH,
+ while the third algorithm is implemented in two variant supported by
+ the classes JOIN_CACHE_BKA and JOIN_CACHE_BKAH.
+ These three algorithms have a lot in common. Each of them first accumulates
+ the records of the left join operand in a join buffer and then searches for
+ matching rows of the second operand for all accumulated records.
+ For the first two algorithms this strategy saves on logical I/O operations:
+ the entire set of records from the join buffer requires only one look-through
+ of the records provided by the second operand.
+ For the third algorithm the accumulation of records allows to optimize
+ fetching rows of the second operand from disk for some engines (MyISAM,
+ InnoDB), or to minimize the number of round-trips between the Server and
+ the engine nodes (NDB Cluster).
+*/
+
+class JOIN_CACHE :public Sql_alloc
+{
+
+private:
+
+ /* Size of the offset of a record from the cache */
+ uint size_of_rec_ofs;
+ /* Size of the length of a record in the cache */
+ uint size_of_rec_len;
+ /* Size of the offset of a field within a record in the cache */
+ uint size_of_fld_ofs;
+
+ /* This structure is used only for explain, not for execution */
+ bool for_explain_only;
+
+protected:
+
+ /* 3 functions below actually do not use the hidden parameter 'this' */
+
+ /* Calculate the number of bytes used to store an offset value */
+ uint offset_size(uint len)
+ { return (len < 256 ? 1 : len < 256*256 ? 2 : 4); }
+
+ /* Get the offset value that takes ofs_sz bytes at the position ptr */
+ ulong get_offset(uint ofs_sz, uchar *ptr)
+ {
+ switch (ofs_sz) {
+ case 1: return uint(*ptr);
+ case 2: return uint2korr(ptr);
+ case 4: return uint4korr(ptr);
+ }
+ return 0;
+ }
+
+ /* Set the offset value ofs that takes ofs_sz bytes at the position ptr */
+ void store_offset(uint ofs_sz, uchar *ptr, ulong ofs)
+ {
+ switch (ofs_sz) {
+ case 1: *ptr= (uchar) ofs; return;
+ case 2: int2store(ptr, (uint16) ofs); return;
+ case 4: int4store(ptr, (uint32) ofs); return;
+ }
+ }
+
+ /*
+ The maximum total length of the fields stored for a record in the cache.
+ For blob fields only the sizes of the blob lengths are taken into account.
+ */
+ uint length;
+
+ /*
+ Representation of the executed multi-way join through which all needed
+ context can be accessed.
+ */
+ JOIN *join;
+
+ /*
+ JOIN_TAB of the first table that can have it's fields in the join cache.
+ That is, tables in the [start_tab, tab) range can have their fields in the
+ join cache.
+ If a join tab in the range represents an SJM-nest, then all tables from the
+ nest can have their fields in the join cache, too.
+ */
+ JOIN_TAB *start_tab;
+
+ /*
+ The total number of flag and data fields that can appear in a record
+ written into the cache. Fields with null values are always skipped
+ to save space.
+ */
+ uint fields;
+
+ /*
+ The total number of flag fields in a record put into the cache. They are
+ used for table null bitmaps, table null row flags, and an optional match
+ flag. Flag fields go before other fields in a cache record with the match
+ flag field placed always at the very beginning of the record.
+ */
+ uint flag_fields;
+
+ /* The total number of blob fields that are written into the cache */
+ uint blobs;
+
+ /*
+ The total number of fields referenced from field descriptors for other join
+ caches. These fields are used to construct key values.
+ When BKA join algorithm is employed the constructed key values serve to
+ access matching rows with index lookups.
+ The key values are put into a hash table when the BNLH join algorithm
+ is employed and when BKAH is used for the join operation.
+ */
+ uint referenced_fields;
+
+ /*
+ The current number of already created data field descriptors.
+ This number can be useful for implementations of the init methods.
+ */
+ uint data_field_count;
+
+ /*
+ The current number of already created pointers to the data field
+ descriptors. This number can be useful for implementations of
+ the init methods.
+ */
+ uint data_field_ptr_count;
+
+ /*
+ Array of the descriptors of fields containing 'fields' elements.
+ These are all fields that are stored for a record in the cache.
+ */
+ CACHE_FIELD *field_descr;
+
+ /*
+ Array of pointers to the blob descriptors that contains 'blobs' elements.
+ */
+ CACHE_FIELD **blob_ptr;
+
+ /*
+ This flag indicates that records written into the join buffer contain
+ a match flag field. The flag must be set by the init method.
+ */
+ bool with_match_flag;
+ /*
+ This flag indicates that any record is prepended with the length of the
+ record which allows us to skip the record or part of it without reading.
+ */
+ bool with_length;
+
+ /*
+ The maximal number of bytes used for a record representation in
+ the cache excluding the space for blob data.
+ For future derived classes this representation may contains some
+ redundant info such as a key value associated with the record.
+ */
+ uint pack_length;
+ /*
+ The value of pack_length incremented by the total size of all
+ pointers of a record in the cache to the blob data.
+ */
+ uint pack_length_with_blob_ptrs;
+
+ /*
+ The total size of the record base prefix. The base prefix of record may
+ include the following components:
+ - the length of the record
+ - the link to a record in a previous buffer.
+ Each record in the buffer are supplied with the same set of the components.
+ */
+ uint base_prefix_length;
+
+ /*
+ The expected length of a record in the join buffer together with
+ all prefixes and postfixes
+ */
+ size_t avg_record_length;
+
+ /* The expected size of the space per record in the auxiliary buffer */
+ size_t avg_aux_buffer_incr;
+
+ /* Expected join buffer space used for one record */
+ size_t space_per_record;
+
+ /* Pointer to the beginning of the join buffer */
+ uchar *buff;
+ /*
+ Size of the entire memory allocated for the join buffer.
+ Part of this memory may be reserved for the auxiliary buffer.
+ */
+ size_t buff_size;
+ /* The minimal join buffer size when join buffer still makes sense to use */
+ size_t min_buff_size;
+ /* The maximum expected size if the join buffer to be used */
+ size_t max_buff_size;
+ /* Size of the auxiliary buffer */
+ size_t aux_buff_size;
+
+ /* The number of records put into the join buffer */
+ size_t records;
+ /*
+ The number of records in the fully refilled join buffer of
+ the minimal size equal to min_buff_size
+ */
+ size_t min_records;
+ /*
+ The maximum expected number of records to be put in the join buffer
+ at one refill
+ */
+ size_t max_records;
+
+ /*
+ Pointer to the current position in the join buffer.
+ This member is used both when writing to buffer and
+ when reading from it.
+ */
+ uchar *pos;
+ /*
+ Pointer to the first free position in the join buffer,
+ right after the last record into it.
+ */
+ uchar *end_pos;
+
+ /*
+ Pointer to the beginning of the first field of the current read/write
+ record from the join buffer. The value is adjusted by the
+ get_record/put_record functions.
+ */
+ uchar *curr_rec_pos;
+ /*
+ Pointer to the beginning of the first field of the last record
+ from the join buffer.
+ */
+ uchar *last_rec_pos;
+
+ /*
+ Flag is set if the blob data for the last record in the join buffer
+ is in record buffers rather than in the join cache.
+ */
+ bool last_rec_blob_data_is_in_rec_buff;
+
+ /*
+ Pointer to the position to the current record link.
+ Record links are used only with linked caches. Record links allow to set
+ connections between parts of one join record that are stored in different
+ join buffers.
+ In the simplest case a record link is just a pointer to the beginning of
+ the record stored in the buffer.
+ In a more general case a link could be a reference to an array of pointers
+ to records in the buffer.
+ */
+ uchar *curr_rec_link;
+
+ /*
+ This flag is set to TRUE if join_tab is the first inner table of an outer
+ join and the latest record written to the join buffer is detected to be
+ null complemented after checking on conditions over the outer tables for
+ this outer join operation
+ */
+ bool last_written_is_null_compl;
+
+ /*
+ The number of fields put in the join buffer of the join cache that are
+ used in building keys to access the table join_tab
+ */
+ uint local_key_arg_fields;
+ /*
+ The total number of the fields in the previous caches that are used
+ in building keys to access the table join_tab
+ */
+ uint external_key_arg_fields;
+
+ /*
+ This flag indicates that the key values will be read directly from the join
+ buffer. It will save us building key values in the key buffer.
+ */
+ bool use_emb_key;
+ /* The length of an embedded key value */
+ uint emb_key_length;
+
+ /*
+ This object provides the methods to iterate over records of
+ the joined table join_tab when looking for join matches between
+ records from join buffer and records from join_tab.
+ BNL and BNLH join algorithms retrieve all records from join_tab,
+ while BKA/BKAH algorithm iterates only over those records from
+ join_tab that can be accessed by look-ups with join keys built
+ from records in join buffer.
+ */
+ JOIN_TAB_SCAN *join_tab_scan;
+
+ void calc_record_fields();
+ void collect_info_on_key_args();
+ int alloc_fields();
+ void create_flag_fields();
+ void create_key_arg_fields();
+ void create_remaining_fields();
+ void set_constants();
+ int alloc_buffer();
+
+ /* Shall reallocate the join buffer */
+ virtual int realloc_buffer();
+
+ /* Check the possibility to read the access keys directly from join buffer */
+ bool check_emb_key_usage();
+
+ uint get_size_of_rec_offset() { return size_of_rec_ofs; }
+ uint get_size_of_rec_length() { return size_of_rec_len; }
+ uint get_size_of_fld_offset() { return size_of_fld_ofs; }
+
+ uchar *get_rec_ref(uchar *ptr)
+ {
+ return buff+get_offset(size_of_rec_ofs, ptr-size_of_rec_ofs);
+ }
+ ulong get_rec_length(uchar *ptr)
+ {
+ return (ulong) get_offset(size_of_rec_len, ptr);
+ }
+ ulong get_fld_offset(uchar *ptr)
+ {
+ return (ulong) get_offset(size_of_fld_ofs, ptr);
+ }
+
+ void store_rec_ref(uchar *ptr, uchar* ref)
+ {
+ store_offset(size_of_rec_ofs, ptr-size_of_rec_ofs, (ulong) (ref-buff));
+ }
+ void store_rec_length(uchar *ptr, ulong len)
+ {
+ store_offset(size_of_rec_len, ptr, len);
+ }
+ void store_fld_offset(uchar *ptr, ulong ofs)
+ {
+ store_offset(size_of_fld_ofs, ptr, ofs);
+ }
+
+ /* Write record fields and their required offsets into the join buffer */
+ uint write_record_data(uchar *link, bool *is_full);
+
+ /* Get the total length of all prefixes of a record in the join buffer */
+ virtual uint get_prefix_length() { return base_prefix_length; }
+ /* Get maximum total length of all affixes of a record in the join buffer */
+ virtual uint get_record_max_affix_length();
+
+ /*
+ Shall get maximum size of the additional space per record used for
+ record keys
+ */
+ virtual uint get_max_key_addon_space_per_record() { return 0; }
+
+ /*
+ This method must determine for how much the auxiliary buffer should be
+ incremented when a new record is added to the join buffer.
+ If no auxiliary buffer is needed the function should return 0.
+ */
+ virtual uint aux_buffer_incr(ulong recno);
+
+ /* Shall calculate how much space is remaining in the join buffer */
+ virtual size_t rem_space()
+ {
+ return MY_MAX(buff_size-(end_pos-buff)-aux_buff_size,0);
+ }
+
+ /*
+ Shall calculate how much space is taken by allocation of the key
+ for a record in the join buffer
+ */
+ virtual uint extra_key_length() { return 0; }
+
+ /* Read all flag and data fields of a record from the join buffer */
+ uint read_all_record_fields();
+
+ /* Read all flag fields of a record from the join buffer */
+ uint read_flag_fields();
+
+ /* Read a data record field from the join buffer */
+ uint read_record_field(CACHE_FIELD *copy, bool last_record);
+
+ /* Read a referenced field from the join buffer */
+ bool read_referenced_field(CACHE_FIELD *copy, uchar *rec_ptr, uint *len);
+
+ /*
+ Shall skip record from the join buffer if its match flag
+ is set to MATCH_FOUND
+ */
+ virtual bool skip_if_matched();
+
+ /*
+ Shall skip record from the join buffer if its match flag
+ commands to do so
+ */
+ virtual bool skip_if_not_needed_match();
+
+ /*
+ True if rec_ptr points to the record whose blob data stay in
+ record buffers
+ */
+ bool blob_data_is_in_rec_buff(uchar *rec_ptr)
+ {
+ return rec_ptr == last_rec_pos && last_rec_blob_data_is_in_rec_buff;
+ }
+
+ /* Find matches from the next table for records from the join buffer */
+ virtual enum_nested_loop_state join_matching_records(bool skip_last);
+
+ /* Shall set an auxiliary buffer up (currently used only by BKA joins) */
+ virtual int setup_aux_buffer(HANDLER_BUFFER &aux_buff)
+ {
+ DBUG_ASSERT(0);
+ return 0;
+ }
+
+ /*
+ Shall get the number of ranges in the cache buffer passed
+ to the MRR interface
+ */
+ virtual uint get_number_of_ranges_for_mrr() { return 0; };
+
+ /*
+ Shall prepare to look for records from the join cache buffer that would
+ match the record of the joined table read into the record buffer
+ */
+ virtual bool prepare_look_for_matches(bool skip_last)= 0;
+ /*
+ Shall return a pointer to the record from join buffer that is checked
+ as the next candidate for a match with the current record from join_tab.
+ Each implementation of this virtual function should bare in mind
+ that the record position it returns shall be exactly the position
+ passed as the parameter to the implementations of the virtual functions
+ skip_next_candidate_for_match and read_next_candidate_for_match.
+ */
+ virtual uchar *get_next_candidate_for_match()= 0;
+ /*
+ Shall check whether the given record from the join buffer has its match
+ flag settings commands to skip the record in the buffer.
+ */
+ virtual bool skip_next_candidate_for_match(uchar *rec_ptr)= 0;
+ /*
+ Shall read the given record from the join buffer into the
+ the corresponding record buffer
+ */
+ virtual void read_next_candidate_for_match(uchar *rec_ptr)= 0;
+
+ /*
+ Shall return the location of the association label returned by
+ the multi_read_range_next function for the current record loaded
+ into join_tab's record buffer
+ */
+ virtual uchar **get_curr_association_ptr() { return 0; };
+
+ /* Add null complements for unmatched outer records from the join buffer */
+ virtual enum_nested_loop_state join_null_complements(bool skip_last);
+
+ /* Restore the fields of the last record from the join buffer */
+ virtual void restore_last_record();
+
+ /* Set match flag for a record in join buffer if it has not been set yet */
+ bool set_match_flag_if_none(JOIN_TAB *first_inner, uchar *rec_ptr);
+
+ enum_nested_loop_state generate_full_extensions(uchar *rec_ptr);
+
+ /* Check matching to a partial join record from the join buffer */
+ bool check_match(uchar *rec_ptr);
+
+ /*
+ This constructor creates an unlinked join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter.
+ */
+ JOIN_CACHE(JOIN *j, JOIN_TAB *tab)
+ {
+ join= j;
+ join_tab= tab;
+ prev_cache= next_cache= 0;
+ buff= 0;
+ }
+
+ /*
+ This constructor creates a linked join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter. The parameter 'prev' specifies the previous
+ cache object to which this cache is linked.
+ */
+ JOIN_CACHE(JOIN *j, JOIN_TAB *tab, JOIN_CACHE *prev)
+ {
+ join= j;
+ join_tab= tab;
+ next_cache= 0;
+ prev_cache= prev;
+ buff= 0;
+ if (prev)
+ prev->next_cache= this;
+ }
+
+public:
+
+ /*
+ The enumeration type Join_algorithm includes a mnemonic constant for
+ each join algorithm that employs join buffers
+ */
+
+ enum Join_algorithm
+ {
+ BNL_JOIN_ALG, /* Block Nested Loop Join algorithm */
+ BNLH_JOIN_ALG, /* Block Nested Loop Hash Join algorithm */
+ BKA_JOIN_ALG, /* Batched Key Access Join algorithm */
+ BKAH_JOIN_ALG /* Batched Key Access with Hash Table Join Algorithm */
+ };
+
+ /*
+ The enumeration type Match_flag describes possible states of the match flag
+ field stored for the records of the first inner tables of outer joins and
+ semi-joins in the cases when the first match strategy is used for them.
+ When a record with match flag field is written into the join buffer the
+ state of the field usually is MATCH_NOT_FOUND unless this is a record of the
+ first inner table of the outer join for which the on precondition (the
+ condition from on expression over outer tables) has turned out not to be
+ true. In the last case the state of the match flag is MATCH_IMPOSSIBLE.
+ The state of the match flag field is changed to MATCH_FOUND as soon as
+ the first full matching combination of inner tables of the outer join or
+ the semi-join is discovered.
+ */
+ enum Match_flag { MATCH_NOT_FOUND, MATCH_FOUND, MATCH_IMPOSSIBLE };
+
+ /* Table to be joined with the partial join records from the cache */
+ JOIN_TAB *join_tab;
+
+ /* Pointer to the previous join cache if there is any */
+ JOIN_CACHE *prev_cache;
+ /* Pointer to the next join cache if there is any */
+ JOIN_CACHE *next_cache;
+
+ /* Shall initialize the join cache structure */
+ virtual int init(bool for_explain);
+
+ /* Get the current size of the cache join buffer */
+ size_t get_join_buffer_size() { return buff_size; }
+ /* Set the size of the cache join buffer to a new value */
+ void set_join_buffer_size(size_t sz) { buff_size= sz; }
+
+ /* Get the minimum possible size of the cache join buffer */
+ virtual ulong get_min_join_buffer_size();
+ /* Get the maximum possible size of the cache join buffer */
+ virtual ulong get_max_join_buffer_size(bool optimize_buff_size);
+
+ /* Shrink the size if the cache join buffer in a given ratio */
+ bool shrink_join_buffer_in_ratio(ulonglong n, ulonglong d);
+
+ /* Shall return the type of the employed join algorithm */
+ virtual enum Join_algorithm get_join_alg()= 0;
+
+ /*
+ The function shall return TRUE only when there is a key access
+ to the join table
+ */
+ virtual bool is_key_access()= 0;
+
+ /* Shall reset the join buffer for reading/writing */
+ virtual void reset(bool for_writing);
+
+ /*
+ This function shall add a record into the join buffer and return TRUE
+ if it has been decided that it should be the last record in the buffer.
+ */
+ virtual bool put_record();
+
+ /*
+ This function shall read the next record into the join buffer and return
+ TRUE if there is no more next records.
+ */
+ virtual bool get_record();
+
+ /*
+ This function shall read the record at the position rec_ptr
+ in the join buffer
+ */
+ virtual void get_record_by_pos(uchar *rec_ptr);
+
+ /* Shall return the value of the match flag for the positioned record */
+ virtual enum Match_flag get_match_flag_by_pos(uchar *rec_ptr);
+
+ /* Shall return the position of the current record */
+ virtual uchar *get_curr_rec() { return curr_rec_pos; }
+
+ /* Shall set the current record link */
+ virtual void set_curr_rec_link(uchar *link) { curr_rec_link= link; }
+
+ /* Shall return the current record link */
+ virtual uchar *get_curr_rec_link()
+ {
+ return (curr_rec_link ? curr_rec_link : get_curr_rec());
+ }
+
+ /* Join records from the join buffer with records from the next join table */
+ enum_nested_loop_state join_records(bool skip_last);
+
+ /* Add a comment on the join algorithm employed by the join cache */
+ virtual void save_explain_data(struct st_explain_bka_type *explain);
+
+ THD *thd();
+
+ virtual ~JOIN_CACHE() {}
+ void reset_join(JOIN *j) { join= j; }
+ void free()
+ {
+ my_free(buff);
+ buff= 0;
+ }
+
+ friend class JOIN_CACHE_HASHED;
+ friend class JOIN_CACHE_BNL;
+ friend class JOIN_CACHE_BKA;
+ friend class JOIN_TAB_SCAN;
+ friend class JOIN_TAB_SCAN_MRR;
+
+};
+
+
+/*
+ The class JOIN_CACHE_HASHED is the base class for the classes
+ JOIN_CACHE_HASHED_BNL and JOIN_CACHE_HASHED_BKA. The first of them supports
+ an implementation of Block Nested Loop Hash (BNLH) Join Algorithm,
+ while the second is used for a variant of the BKA Join algorithm that performs
+ only one lookup for any records from join buffer with the same key value.
+ For a join cache of this class the records from the join buffer that have
+ the same access key are linked into a chain attached to a key entry structure
+ that either itself contains the key value, or, in the case when the keys are
+ embedded, refers to its occurrence in one of the records from the chain.
+ To build the chains with the same keys a hash table is employed. It is placed
+ at the very end of the join buffer. The array of hash entries is allocated
+ first at the very bottom of the join buffer, while key entries are placed
+ before this array.
+ A hash entry contains a header of the list of the key entries with the same
+ hash value.
+ Each key entry is a structure of the following type:
+ struct st_join_cache_key_entry {
+ union {
+ uchar[] value;
+ cache_ref *value_ref; // offset from the beginning of the buffer
+ } hash_table_key;
+ key_ref next_key; // offset backward from the beginning of hash table
+ cache_ref *last_rec // offset from the beginning of the buffer
+ }
+ The references linking the records in a chain are always placed at the very
+ beginning of the record info stored in the join buffer. The records are
+ linked in a circular list. A new record is always added to the end of this
+ list.
+
+ The following picture represents a typical layout for the info stored in the
+ join buffer of a join cache object of the JOIN_CACHE_HASHED class.
+
+ buff
+ V
+ +----------------------------------------------------------------------------+
+ | |[*]record_1_1| |
+ | ^ | |
+ | | +--------------------------------------------------+ |
+ | | |[*]record_2_1| | |
+ | | ^ | V |
+ | | | +------------------+ |[*]record_1_2| |
+ | | +--------------------+-+ | |
+ |+--+ +---------------------+ | | +-------------+ |
+ || | | V | | |
+ |||[*]record_3_1| |[*]record_1_3| |[*]record_2_2| | |
+ ||^ ^ ^ | |
+ ||+----------+ | | | |
+ ||^ | |<---------------------------+-------------------+ |
+ |++ | | ... mrr | buffer ... ... | | |
+ | | | | |
+ | +-----+--------+ | +-----|-------+ |
+ | V | | | V | | |
+ ||key_3|[/]|[*]| | | |key_2|[/]|[*]| | |
+ | +-+---|-----------------------+ | |
+ | V | | | | |
+ | |key_1|[*]|[*]| | | ... |[*]| ... |[*]| ... | |
+ +----------------------------------------------------------------------------+
+ ^ ^ ^
+ | i-th entry j-th entry
+ hash table
+
+ i-th hash entry:
+ circular record chain for key_1:
+ record_1_1
+ record_1_2
+ record_1_3 (points to record_1_1)
+ circular record chain for key_3:
+ record_3_1 (points to itself)
+
+ j-th hash entry:
+ circular record chain for key_2:
+ record_2_1
+ record_2_2 (points to record_2_1)
+
+*/
+
+class JOIN_CACHE_HASHED: public JOIN_CACHE
+{
+
+ typedef uint (JOIN_CACHE_HASHED::*Hash_func) (uchar *key, uint key_len);
+ typedef bool (JOIN_CACHE_HASHED::*Hash_cmp_func) (uchar *key1, uchar *key2,
+ uint key_len);
+
+private:
+
+ /* Size of the offset of a key entry in the hash table */
+ uint size_of_key_ofs;
+
+ /*
+ Length of the key entry in the hash table.
+ A key entry either contains the key value, or it contains a reference
+ to the key value if use_emb_key flag is set for the cache.
+ */
+ uint key_entry_length;
+
+ /* The beginning of the hash table in the join buffer */
+ uchar *hash_table;
+ /* Number of hash entries in the hash table */
+ uint hash_entries;
+
+
+ /* The position of the currently retrieved key entry in the hash table */
+ uchar *curr_key_entry;
+
+ /* The offset of the data fields from the beginning of the record fields */
+ uint data_fields_offset;
+
+ inline uint get_hash_idx_simple(uchar *key, uint key_len);
+ inline uint get_hash_idx_complex(uchar *key, uint key_len);
+
+ inline bool equal_keys_simple(uchar *key1, uchar *key2, uint key_len);
+ inline bool equal_keys_complex(uchar *key1, uchar *key2, uint key_len);
+
+ int init_hash_table();
+ void cleanup_hash_table();
+
+protected:
+
+ /*
+ Index info on the TABLE_REF object used by the hash join
+ to look for matching records
+ */
+ KEY *ref_key_info;
+ /*
+ Number of the key parts the TABLE_REF object used by the hash join
+ to look for matching records
+ */
+ uint ref_used_key_parts;
+
+ /*
+ The hash function used in the hash table,
+ usually set by the init() method
+ */
+ Hash_func hash_func;
+ /*
+ The function to check whether two key entries in the hash table
+ are equal or not, usually set by the init() method
+ */
+ Hash_cmp_func hash_cmp_func;
+
+ /*
+ Length of a key value.
+ It is assumed that all key values have the same length.
+ */
+ uint key_length;
+ /* Buffer to store key values for probing */
+ uchar *key_buff;
+
+ /* Number of key entries in the hash table (number of distinct keys) */
+ uint key_entries;
+
+ /* The position of the last key entry in the hash table */
+ uchar *last_key_entry;
+
+ /*
+ The offset of the record fields from the beginning of the record
+ representation. The record representation starts with a reference to
+ the next record in the key record chain followed by the length of
+ the trailing record data followed by a reference to the record segment
+ in the previous cache, if any, followed by the record fields.
+ */
+ uint rec_fields_offset;
+
+ uint get_size_of_key_offset() { return size_of_key_ofs; }
+
+ /*
+ Get the position of the next_key_ptr field pointed to by
+ a linking reference stored at the position key_ref_ptr.
+ This reference is actually the offset backward from the
+ beginning of hash table.
+ */
+ uchar *get_next_key_ref(uchar *key_ref_ptr)
+ {
+ return hash_table-get_offset(size_of_key_ofs, key_ref_ptr);
+ }
+
+ /*
+ Store the linking reference to the next_key_ptr field at
+ the position key_ref_ptr. The position of the next_key_ptr
+ field is pointed to by ref. The stored reference is actually
+ the offset backward from the beginning of the hash table.
+ */
+ void store_next_key_ref(uchar *key_ref_ptr, uchar *ref)
+ {
+ store_offset(size_of_key_ofs, key_ref_ptr, (ulong) (hash_table-ref));
+ }
+
+ /*
+ Check whether the reference to the next_key_ptr field at the position
+ key_ref_ptr contains a nil value.
+ */
+ bool is_null_key_ref(uchar *key_ref_ptr)
+ {
+ ulong nil= 0;
+ return memcmp(key_ref_ptr, &nil, size_of_key_ofs ) == 0;
+ }
+
+ /*
+ Set the reference to the next_key_ptr field at the position
+ key_ref_ptr equal to nil.
+ */
+ void store_null_key_ref(uchar *key_ref_ptr)
+ {
+ ulong nil= 0;
+ store_offset(size_of_key_ofs, key_ref_ptr, nil);
+ }
+
+ uchar *get_next_rec_ref(uchar *ref_ptr)
+ {
+ return buff+get_offset(get_size_of_rec_offset(), ref_ptr);
+ }
+
+ void store_next_rec_ref(uchar *ref_ptr, uchar *ref)
+ {
+ store_offset(get_size_of_rec_offset(), ref_ptr, (ulong) (ref-buff));
+ }
+
+ /*
+ Get the position of the embedded key value for the current
+ record pointed to by get_curr_rec().
+ */
+ uchar *get_curr_emb_key()
+ {
+ return get_curr_rec()+data_fields_offset;
+ }
+
+ /*
+ Get the position of the embedded key value pointed to by a reference
+ stored at ref_ptr. The stored reference is actually the offset from
+ the beginning of the join buffer.
+ */
+ uchar *get_emb_key(uchar *ref_ptr)
+ {
+ return buff+get_offset(get_size_of_rec_offset(), ref_ptr);
+ }
+
+ /*
+ Store the reference to an embedded key at the position key_ref_ptr.
+ The position of the embedded key is pointed to by ref. The stored
+ reference is actually the offset from the beginning of the join buffer.
+ */
+ void store_emb_key_ref(uchar *ref_ptr, uchar *ref)
+ {
+ store_offset(get_size_of_rec_offset(), ref_ptr, (ulong) (ref-buff));
+ }
+
+ /* Get the total length of all prefixes of a record in hashed join buffer */
+ uint get_prefix_length()
+ {
+ return base_prefix_length + get_size_of_rec_offset();
+ }
+
+ /*
+ Get maximum size of the additional space per record used for
+ the hash table with record keys
+ */
+ uint get_max_key_addon_space_per_record();
+
+ /*
+ Calculate how much space in the buffer would not be occupied by
+ records, key entries and additional memory for the MMR buffer.
+ */
+ size_t rem_space()
+ {
+ return MY_MAX(last_key_entry-end_pos-aux_buff_size,0);
+ }
+
+ /*
+ Calculate how much space is taken by allocation of the key
+ entry for a record in the join buffer
+ */
+ uint extra_key_length() { return key_entry_length; }
+
+ /*
+ Skip record from a hashed join buffer if its match flag
+ is set to MATCH_FOUND
+ */
+ bool skip_if_matched();
+
+ /*
+ Skip record from a hashed join buffer if its match flag setting
+ commands to do so
+ */
+ bool skip_if_not_needed_match();
+
+ /* Search for a key in the hash table of the join buffer */
+ bool key_search(uchar *key, uint key_len, uchar **key_ref_ptr);
+
+ /* Reallocate the join buffer of a hashed join cache */
+ int realloc_buffer();
+
+ /*
+ This constructor creates an unlinked hashed join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter.
+ */
+ JOIN_CACHE_HASHED(JOIN *j, JOIN_TAB *tab) :JOIN_CACHE(j, tab) {}
+
+ /*
+ This constructor creates a linked hashed join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter. The parameter 'prev' specifies the previous
+ cache object to which this cache is linked.
+ */
+ JOIN_CACHE_HASHED(JOIN *j, JOIN_TAB *tab, JOIN_CACHE *prev)
+ :JOIN_CACHE(j, tab, prev) {}
+
+public:
+
+ /* Initialize a hashed join cache */
+ int init(bool for_explain);
+
+ /* Reset the buffer of a hashed join cache for reading/writing */
+ void reset(bool for_writing);
+
+ /* Add a record into the buffer of a hashed join cache */
+ bool put_record();
+
+ /* Read the next record from the buffer of a hashed join cache */
+ bool get_record();
+
+ /*
+ Shall check whether all records in a key chain have
+ their match flags set on
+ */
+ virtual bool check_all_match_flags_for_key(uchar *key_chain_ptr);
+
+ uint get_next_key(uchar **key);
+
+ /* Get the head of the record chain attached to the current key entry */
+ uchar *get_curr_key_chain()
+ {
+ return get_next_rec_ref(curr_key_entry+key_entry_length-
+ get_size_of_rec_offset());
+ }
+
+};
+
+
+/*
+ The class JOIN_TAB_SCAN is a companion class for the classes JOIN_CACHE_BNL
+ and JOIN_CACHE_BNLH. Actually the class implements the iterator over the
+ table joinded by BNL/BNLH join algorithm.
+ The virtual functions open, next and close are called for any iteration over
+ the table. The function open is called to initiate the process of the
+ iteration. The function next shall read the next record from the joined
+ table. The record is read into the record buffer of the joined table.
+ The record is to be matched with records from the join cache buffer.
+ The function close shall perform the finalizing actions for the iteration.
+*/
+
+class JOIN_TAB_SCAN: public Sql_alloc
+{
+
+private:
+ /* TRUE if this is the first record from the joined table to iterate over */
+ bool is_first_record;
+
+protected:
+
+ /* The joined table to be iterated over */
+ JOIN_TAB *join_tab;
+ /* The join cache used to join the table join_tab */
+ JOIN_CACHE *cache;
+ /*
+ Representation of the executed multi-way join through which
+ all needed context can be accessed.
+ */
+ JOIN *join;
+
+public:
+
+ JOIN_TAB_SCAN(JOIN *j, JOIN_TAB *tab)
+ {
+ join= j;
+ join_tab= tab;
+ cache= join_tab->cache;
+ }
+
+ virtual ~JOIN_TAB_SCAN() {}
+
+ /*
+ Shall calculate the increment of the auxiliary buffer for a record
+ write if such a buffer is used by the table scan object
+ */
+ virtual uint aux_buffer_incr(ulong recno) { return 0; }
+
+ /* Initiate the process of iteration over the joined table */
+ virtual int open();
+ /*
+ Shall read the next candidate for matches with records from
+ the join buffer.
+ */
+ virtual int next();
+ /*
+ Perform the finalizing actions for the process of iteration
+ over the joined_table.
+ */
+ virtual void close();
+
+};
+
+/*
+ The class JOIN_CACHE_BNL is used when the BNL join algorithm is
+ employed to perform a join operation
+*/
+
+class JOIN_CACHE_BNL :public JOIN_CACHE
+{
+private:
+ /*
+ The number of the records in the join buffer that have to be
+ checked yet for a match with the current record of join_tab
+ read into the record buffer.
+ */
+ uint rem_records;
+
+protected:
+
+ bool prepare_look_for_matches(bool skip_last);
+
+ uchar *get_next_candidate_for_match();
+
+ bool skip_next_candidate_for_match(uchar *rec_ptr);
+
+ void read_next_candidate_for_match(uchar *rec_ptr);
+
+public:
+
+ /*
+ This constructor creates an unlinked BNL join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter.
+ */
+ JOIN_CACHE_BNL(JOIN *j, JOIN_TAB *tab) :JOIN_CACHE(j, tab) {}
+
+ /*
+ This constructor creates a linked BNL join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter. The parameter 'prev' specifies the previous
+ cache object to which this cache is linked.
+ */
+ JOIN_CACHE_BNL(JOIN *j, JOIN_TAB *tab, JOIN_CACHE *prev)
+ :JOIN_CACHE(j, tab, prev) {}
+
+ /* Initialize the BNL cache */
+ int init(bool for_explain);
+
+ enum Join_algorithm get_join_alg() { return BNL_JOIN_ALG; }
+
+ bool is_key_access() { return FALSE; }
+
+};
+
+
+/*
+ The class JOIN_CACHE_BNLH is used when the BNLH join algorithm is
+ employed to perform a join operation
+*/
+
+class JOIN_CACHE_BNLH :public JOIN_CACHE_HASHED
+{
+
+protected:
+
+ /*
+ The pointer to the last record from the circular list of the records
+ that match the join key built out of the record in the join buffer for
+ the join_tab table
+ */
+ uchar *last_matching_rec_ref_ptr;
+ /*
+ The pointer to the next current record from the circular list of the
+ records that match the join key built out of the record in the join buffer
+ for the join_tab table. This pointer is used by the class method
+ get_next_candidate_for_match to iterate over records from the circular
+ list.
+ */
+ uchar *next_matching_rec_ref_ptr;
+
+ /*
+ Get the chain of records from buffer matching the current candidate
+ record for join
+ */
+ uchar *get_matching_chain_by_join_key();
+
+ bool prepare_look_for_matches(bool skip_last);
+
+ uchar *get_next_candidate_for_match();
+
+ bool skip_next_candidate_for_match(uchar *rec_ptr);
+
+ void read_next_candidate_for_match(uchar *rec_ptr);
+
+public:
+
+ /*
+ This constructor creates an unlinked BNLH join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter.
+ */
+ JOIN_CACHE_BNLH(JOIN *j, JOIN_TAB *tab) : JOIN_CACHE_HASHED(j, tab) {}
+
+ /*
+ This constructor creates a linked BNLH join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter. The parameter 'prev' specifies the previous
+ cache object to which this cache is linked.
+ */
+ JOIN_CACHE_BNLH(JOIN *j, JOIN_TAB *tab, JOIN_CACHE *prev)
+ : JOIN_CACHE_HASHED(j, tab, prev) {}
+
+ /* Initialize the BNLH cache */
+ int init(bool for_explain);
+
+ enum Join_algorithm get_join_alg() { return BNLH_JOIN_ALG; }
+
+ bool is_key_access() { return TRUE; }
+
+};
+
+
+/*
+ The class JOIN_TAB_SCAN_MRR is a companion class for the classes
+ JOIN_CACHE_BKA and JOIN_CACHE_BKAH. Actually the class implements the
+ iterator over the records from join_tab selected by BKA/BKAH join
+ algorithm as the candidates to be joined.
+ The virtual functions open, next and close are called for any iteration over
+ join_tab record candidates. The function open is called to initiate the
+ process of the iteration. The function next shall read the next record from
+ the set of the record candidates. The record is read into the record buffer
+ of the joined table. The function close shall perform the finalizing actions
+ for the iteration.
+*/
+
+class JOIN_TAB_SCAN_MRR: public JOIN_TAB_SCAN
+{
+ /* Interface object to generate key ranges for MRR */
+ RANGE_SEQ_IF range_seq_funcs;
+
+ /* Number of ranges to be processed by the MRR interface */
+ uint ranges;
+
+ /* Flag to to be passed to the MRR interface */
+ uint mrr_mode;
+
+ /* MRR buffer assotiated with this join cache */
+ HANDLER_BUFFER mrr_buff;
+
+ /* Shall initialize the MRR buffer */
+ virtual void init_mrr_buff()
+ {
+ cache->setup_aux_buffer(mrr_buff);
+ }
+
+public:
+
+ JOIN_TAB_SCAN_MRR(JOIN *j, JOIN_TAB *tab, uint flags, RANGE_SEQ_IF rs_funcs)
+ :JOIN_TAB_SCAN(j, tab), range_seq_funcs(rs_funcs), mrr_mode(flags) {}
+
+ uint aux_buffer_incr(ulong recno);
+
+ int open();
+
+ int next();
+
+ friend class JOIN_CACHE_BKA; /* it needs to add an mrr_mode flag after JOIN_CACHE::init() call */
+};
+
+/*
+ The class JOIN_CACHE_BKA is used when the BKA join algorithm is
+ employed to perform a join operation
+*/
+
+class JOIN_CACHE_BKA :public JOIN_CACHE
+{
+private:
+
+ /* Flag to to be passed to the companion JOIN_TAB_SCAN_MRR object */
+ uint mrr_mode;
+
+ /*
+ This value is set to 1 by the class prepare_look_for_matches method
+ and back to 0 by the class get_next_candidate_for_match method
+ */
+ uint rem_records;
+
+ /*
+ This field contains the current association label set by a call of
+ the multi_range_read_next handler function.
+ See the function JOIN_CACHE_BKA::get_curr_key_association()
+ */
+ uchar *curr_association;
+
+protected:
+
+ /*
+ Get the number of ranges in the cache buffer passed to the MRR
+ interface. For each record its own range is passed.
+ */
+ uint get_number_of_ranges_for_mrr() { return (uint)records; }
+
+ /*
+ Setup the MRR buffer as the space between the last record put
+ into the join buffer and the very end of the join buffer
+ */
+ int setup_aux_buffer(HANDLER_BUFFER &aux_buff)
+ {
+ aux_buff.buffer= end_pos;
+ aux_buff.buffer_end= buff+buff_size;
+ return 0;
+ }
+
+ bool prepare_look_for_matches(bool skip_last);
+
+ uchar *get_next_candidate_for_match();
+
+ bool skip_next_candidate_for_match(uchar *rec_ptr);
+
+ void read_next_candidate_for_match(uchar *rec_ptr);
+
+public:
+
+ /*
+ This constructor creates an unlinked BKA join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter.
+ The MRR mode initially is set to 'flags'.
+ */
+ JOIN_CACHE_BKA(JOIN *j, JOIN_TAB *tab, uint flags)
+ :JOIN_CACHE(j, tab), mrr_mode(flags) {}
+ /*
+ This constructor creates a linked BKA join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter. The parameter 'prev' specifies the previous
+ cache object to which this cache is linked.
+ The MRR mode initially is set to 'flags'.
+ */
+ JOIN_CACHE_BKA(JOIN *j, JOIN_TAB *tab, uint flags, JOIN_CACHE *prev)
+ :JOIN_CACHE(j, tab, prev), mrr_mode(flags) {}
+
+ uchar **get_curr_association_ptr() { return &curr_association; }
+
+ /* Initialize the BKA cache */
+ int init(bool for_explain);
+
+ enum Join_algorithm get_join_alg() { return BKA_JOIN_ALG; }
+
+ bool is_key_access() { return TRUE; }
+
+ /* Get the key built over the next record from the join buffer */
+ uint get_next_key(uchar **key);
+
+ /* Check index condition of the joined table for a record from BKA cache */
+ bool skip_index_tuple(range_id_t range_info);
+
+ void save_explain_data(struct st_explain_bka_type *explain);
+};
+
+
+
+/*
+ The class JOIN_CACHE_BKAH is used when the BKAH join algorithm is
+ employed to perform a join operation
+*/
+
+class JOIN_CACHE_BKAH :public JOIN_CACHE_BNLH
+{
+
+private:
+ /* Flag to to be passed to the companion JOIN_TAB_SCAN_MRR object */
+ uint mrr_mode;
+
+ /*
+ This flag is set to TRUE if the implementation of the MRR interface cannot
+ handle range association labels and does not return them to the caller of
+ the multi_range_read_next handler function. E.g. the implementation of
+ the MRR inteface for the Falcon engine could not return association
+ labels to the caller of multi_range_read_next.
+ The flag is set by JOIN_CACHE_BKA::init() and is not ever changed.
+ */
+ bool no_association;
+
+ /*
+ This field contains the association label returned by the
+ multi_range_read_next function.
+ See the function JOIN_CACHE_BKAH::get_curr_key_association()
+ */
+ uchar *curr_matching_chain;
+
+protected:
+
+ uint get_number_of_ranges_for_mrr() { return key_entries; }
+
+ /*
+ Initialize the MRR buffer allocating some space within the join buffer.
+ The entire space between the last record put into the join buffer and the
+ last key entry added to the hash table is used for the MRR buffer.
+ */
+ int setup_aux_buffer(HANDLER_BUFFER &aux_buff)
+ {
+ aux_buff.buffer= end_pos;
+ aux_buff.buffer_end= last_key_entry;
+ return 0;
+ }
+
+ bool prepare_look_for_matches(bool skip_last);
+
+ /*
+ The implementations of the methods
+ - get_next_candidate_for_match
+ - skip_recurrent_candidate_for_match
+ - read_next_candidate_for_match
+ are inherited from the JOIN_CACHE_BNLH class
+ */
+
+public:
+
+ /*
+ This constructor creates an unlinked BKAH join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter.
+ The MRR mode initially is set to 'flags'.
+ */
+ JOIN_CACHE_BKAH(JOIN *j, JOIN_TAB *tab, uint flags)
+ :JOIN_CACHE_BNLH(j, tab), mrr_mode(flags) {}
+
+ /*
+ This constructor creates a linked BKAH join cache. The cache is to be
+ used to join table 'tab' to the result of joining the previous tables
+ specified by the 'j' parameter. The parameter 'prev' specifies the previous
+ cache object to which this cache is linked.
+ The MRR mode initially is set to 'flags'.
+ */
+ JOIN_CACHE_BKAH(JOIN *j, JOIN_TAB *tab, uint flags, JOIN_CACHE *prev)
+ :JOIN_CACHE_BNLH(j, tab, prev), mrr_mode(flags) {}
+
+ uchar **get_curr_association_ptr() { return &curr_matching_chain; }
+
+ /* Initialize the BKAH cache */
+ int init(bool for_explain);
+
+ enum Join_algorithm get_join_alg() { return BKAH_JOIN_ALG; }
+
+ /* Check index condition of the joined table for a record from BKAH cache */
+ bool skip_index_tuple(range_id_t range_info);
+
+ void save_explain_data(struct st_explain_bka_type *explain);
+};
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
new file mode 100644
index 00000000000..d8f40897a59
--- /dev/null
+++ b/sql/sql_lex.cc
@@ -0,0 +1,4460 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+
+/* A lexical scanner on a temporary buffer with a yacc interface */
+
+#define MYSQL_LEX 1
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_class.h" // sql_lex.h: SQLCOM_END
+#include "sql_lex.h"
+#include "sql_parse.h" // add_to_list
+#include "item_create.h"
+#include <m_ctype.h>
+#include <hash.h>
+#include "sp_head.h"
+#include "sp.h"
+#include "sql_select.h"
+
+static int lex_one_token(YYSTYPE *yylval, THD *thd);
+
+/*
+ We are using pointer to this variable for distinguishing between assignment
+ to NEW row field (when parsing trigger definition) and structured variable.
+*/
+
+sys_var *trg_new_row_fake_var= (sys_var*) 0x01;
+
+/**
+ LEX_STRING constant for null-string to be used in parser and other places.
+*/
+const LEX_STRING null_lex_str= {NULL, 0};
+const LEX_STRING empty_lex_str= {(char *) "", 0};
+/**
+ @note The order of the elements of this array must correspond to
+ the order of elements in enum_binlog_stmt_unsafe.
+*/
+const int
+Query_tables_list::binlog_stmt_unsafe_errcode[BINLOG_STMT_UNSAFE_COUNT] =
+{
+ ER_BINLOG_UNSAFE_LIMIT,
+ ER_BINLOG_UNSAFE_INSERT_DELAYED,
+ ER_BINLOG_UNSAFE_SYSTEM_TABLE,
+ ER_BINLOG_UNSAFE_AUTOINC_COLUMNS,
+ ER_BINLOG_UNSAFE_UDF,
+ ER_BINLOG_UNSAFE_SYSTEM_VARIABLE,
+ ER_BINLOG_UNSAFE_SYSTEM_FUNCTION,
+ ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS,
+ ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE,
+ ER_BINLOG_UNSAFE_MIXED_STATEMENT,
+ ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT,
+ ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE,
+ ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT,
+ ER_BINLOG_UNSAFE_REPLACE_SELECT,
+ ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT,
+ ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT,
+ ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC,
+ ER_BINLOG_UNSAFE_UPDATE_IGNORE,
+ ER_BINLOG_UNSAFE_INSERT_TWO_KEYS,
+ ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST
+};
+
+
+/* Longest standard keyword name */
+
+#define TOCK_NAME_LENGTH 24
+
+/*
+ The following data is based on the latin1 character set, and is only
+ used when comparing keywords
+*/
+
+static uchar to_upper_lex[]=
+{
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127,
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
+ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213,214,247,216,217,218,219,220,221,222,255
+};
+
+/*
+ Names of the index hints (for error messages). Keep in sync with
+ index_hint_type
+*/
+
+const char * index_hint_type_name[] =
+{
+ "IGNORE INDEX",
+ "USE INDEX",
+ "FORCE INDEX"
+};
+
+inline int lex_casecmp(const char *s, const char *t, uint len)
+{
+ while (len-- != 0 &&
+ to_upper_lex[(uchar) *s++] == to_upper_lex[(uchar) *t++]) ;
+ return (int) len+1;
+}
+
+#include <lex_hash.h>
+
+
+void lex_init(void)
+{
+ uint i;
+ DBUG_ENTER("lex_init");
+ for (i=0 ; i < array_elements(symbols) ; i++)
+ symbols[i].length=(uchar) strlen(symbols[i].name);
+ for (i=0 ; i < array_elements(sql_functions) ; i++)
+ sql_functions[i].length=(uchar) strlen(sql_functions[i].name);
+
+ DBUG_VOID_RETURN;
+}
+
+
+void lex_free(void)
+{ // Call this when daemon ends
+ DBUG_ENTER("lex_free");
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Initialize lex object for use in fix_fields and parsing.
+
+ SYNOPSIS
+ init_lex_with_single_table()
+ @param thd The thread object
+ @param table The table object
+ @return Operation status
+ @retval TRUE An error occurred, memory allocation error
+ @retval FALSE Ok
+
+ DESCRIPTION
+ This function is used to initialize a lex object on the
+ stack for use by fix_fields and for parsing. In order to
+ work properly it also needs to initialize the
+ Name_resolution_context object of the lexer.
+ Finally it needs to set a couple of variables to ensure
+ proper functioning of fix_fields.
+*/
+
+int
+init_lex_with_single_table(THD *thd, TABLE *table, LEX *lex)
+{
+ TABLE_LIST *table_list;
+ Table_ident *table_ident;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ Name_resolution_context *context= &select_lex->context;
+ /*
+ We will call the parser to create a part_info struct based on the
+ partition string stored in the frm file.
+ We will use a local lex object for this purpose. However we also
+ need to set the Name_resolution_object for this lex object. We
+ do this by using add_table_to_list where we add the table that
+ we're working with to the Name_resolution_context.
+ */
+ thd->lex= lex;
+ lex_start(thd);
+ context->init();
+ if ((!(table_ident= new Table_ident(thd,
+ table->s->table_name,
+ table->s->db, TRUE))) ||
+ (!(table_list= select_lex->add_table_to_list(thd,
+ table_ident,
+ NULL,
+ 0))))
+ return TRUE;
+ context->resolve_in_table_list_only(table_list);
+ lex->use_only_table_context= TRUE;
+ lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VCOL_EXPR;
+ select_lex->cur_pos_in_select_list= UNDEF_POS;
+ table->map= 1; //To ensure correct calculation of const item
+ table->get_fields_in_item_tree= TRUE;
+ table_list->table= table;
+ table_list->cacheable_table= false;
+ return FALSE;
+}
+
+/**
+ End use of local lex with single table
+
+ SYNOPSIS
+ end_lex_with_single_table()
+ @param thd The thread object
+ @param table The table object
+ @param old_lex The real lex object connected to THD
+
+ DESCRIPTION
+ This function restores the real lex object after calling
+ init_lex_with_single_table and also restores some table
+ variables temporarily set.
+*/
+
+void
+end_lex_with_single_table(THD *thd, TABLE *table, LEX *old_lex)
+{
+ LEX *lex= thd->lex;
+ table->map= 0;
+ table->get_fields_in_item_tree= FALSE;
+ lex_end(lex);
+ thd->lex= old_lex;
+}
+
+
+void
+st_parsing_options::reset()
+{
+ allows_variable= TRUE;
+ allows_select_into= TRUE;
+ allows_select_procedure= TRUE;
+ allows_derived= TRUE;
+}
+
+
+/**
+ Perform initialization of Lex_input_stream instance.
+
+ Basically, a buffer for pre-processed query. This buffer should be large
+ enough to keep multi-statement query. The allocation is done once in
+ Lex_input_stream::init() in order to prevent memory pollution when
+ the server is processing large multi-statement queries.
+*/
+
+bool Lex_input_stream::init(THD *thd,
+ char* buff,
+ unsigned int length)
+{
+ DBUG_EXECUTE_IF("bug42064_simulate_oom",
+ DBUG_SET("+d,simulate_out_of_memory"););
+
+ m_cpp_buf= (char*) thd->alloc(length + 1);
+
+ DBUG_EXECUTE_IF("bug42064_simulate_oom",
+ DBUG_SET("-d,bug42064_simulate_oom"););
+
+ if (m_cpp_buf == NULL)
+ return TRUE;
+
+ m_thd= thd;
+ reset(buff, length);
+
+ return FALSE;
+}
+
+
+/**
+ Prepare Lex_input_stream instance state for use for handling next SQL statement.
+
+ It should be called between two statements in a multi-statement query.
+ The operation resets the input stream to the beginning-of-parse state,
+ but does not reallocate m_cpp_buf.
+*/
+
+void
+Lex_input_stream::reset(char *buffer, unsigned int length)
+{
+ yylineno= 1;
+ yytoklen= 0;
+ yylval= NULL;
+ lookahead_token= -1;
+ lookahead_yylval= NULL;
+ m_ptr= buffer;
+ m_tok_start= NULL;
+ m_tok_end= NULL;
+ m_end_of_query= buffer + length;
+ m_tok_start_prev= NULL;
+ m_buf= buffer;
+ m_buf_length= length;
+ m_echo= TRUE;
+ m_cpp_tok_start= NULL;
+ m_cpp_tok_start_prev= NULL;
+ m_cpp_tok_end= NULL;
+ m_body_utf8= NULL;
+ m_cpp_utf8_processed_ptr= NULL;
+ next_state= MY_LEX_START;
+ found_semicolon= NULL;
+ ignore_space= MY_TEST(m_thd->variables.sql_mode & MODE_IGNORE_SPACE);
+ stmt_prepare_mode= FALSE;
+ multi_statements= TRUE;
+ in_comment=NO_COMMENT;
+ m_underscore_cs= NULL;
+ m_cpp_ptr= m_cpp_buf;
+}
+
+
+/**
+ The operation is called from the parser in order to
+ 1) designate the intention to have utf8 body;
+ 1) Indicate to the lexer that we will need a utf8 representation of this
+ statement;
+ 2) Determine the beginning of the body.
+
+ @param thd Thread context.
+ @param begin_ptr Pointer to the start of the body in the pre-processed
+ buffer.
+*/
+
+void Lex_input_stream::body_utf8_start(THD *thd, const char *begin_ptr)
+{
+ DBUG_ASSERT(begin_ptr);
+ DBUG_ASSERT(m_cpp_buf <= begin_ptr && begin_ptr <= m_cpp_buf + m_buf_length);
+
+ uint body_utf8_length=
+ (m_buf_length / thd->variables.character_set_client->mbminlen) *
+ my_charset_utf8_bin.mbmaxlen;
+
+ m_body_utf8= (char *) thd->alloc(body_utf8_length + 1);
+ m_body_utf8_ptr= m_body_utf8;
+ *m_body_utf8_ptr= 0;
+
+ m_cpp_utf8_processed_ptr= begin_ptr;
+}
+
+/**
+ @brief The operation appends unprocessed part of pre-processed buffer till
+ the given pointer (ptr) and sets m_cpp_utf8_processed_ptr to end_ptr.
+
+ The idea is that some tokens in the pre-processed buffer (like character
+ set introducers) should be skipped.
+
+ Example:
+ CPP buffer: SELECT 'str1', _latin1 'str2';
+ m_cpp_utf8_processed_ptr -- points at the "SELECT ...";
+ In order to skip "_latin1", the following call should be made:
+ body_utf8_append(<pointer to "_latin1 ...">, <pointer to " 'str2'...">)
+
+ @param ptr Pointer in the pre-processed buffer, which specifies the
+ end of the chunk, which should be appended to the utf8
+ body.
+ @param end_ptr Pointer in the pre-processed buffer, to which
+ m_cpp_utf8_processed_ptr will be set in the end of the
+ operation.
+*/
+
+void Lex_input_stream::body_utf8_append(const char *ptr,
+ const char *end_ptr)
+{
+ DBUG_ASSERT(m_cpp_buf <= ptr && ptr <= m_cpp_buf + m_buf_length);
+ DBUG_ASSERT(m_cpp_buf <= end_ptr && end_ptr <= m_cpp_buf + m_buf_length);
+
+ if (!m_body_utf8)
+ return;
+
+ if (m_cpp_utf8_processed_ptr >= ptr)
+ return;
+
+ int bytes_to_copy= ptr - m_cpp_utf8_processed_ptr;
+
+ memcpy(m_body_utf8_ptr, m_cpp_utf8_processed_ptr, bytes_to_copy);
+ m_body_utf8_ptr += bytes_to_copy;
+ *m_body_utf8_ptr= 0;
+
+ m_cpp_utf8_processed_ptr= end_ptr;
+}
+
+/**
+ The operation appends unprocessed part of the pre-processed buffer till
+ the given pointer (ptr) and sets m_cpp_utf8_processed_ptr to ptr.
+
+ @param ptr Pointer in the pre-processed buffer, which specifies the end
+ of the chunk, which should be appended to the utf8 body.
+*/
+
+void Lex_input_stream::body_utf8_append(const char *ptr)
+{
+ body_utf8_append(ptr, ptr);
+}
+
+/**
+ The operation converts the specified text literal to the utf8 and appends
+ the result to the utf8-body.
+
+ @param thd Thread context.
+ @param txt Text literal.
+ @param txt_cs Character set of the text literal.
+ @param end_ptr Pointer in the pre-processed buffer, to which
+ m_cpp_utf8_processed_ptr will be set in the end of the
+ operation.
+*/
+
+void Lex_input_stream::body_utf8_append_literal(THD *thd,
+ const LEX_STRING *txt,
+ CHARSET_INFO *txt_cs,
+ const char *end_ptr)
+{
+ if (!m_cpp_utf8_processed_ptr)
+ return;
+
+ LEX_STRING utf_txt;
+
+ if (!my_charset_same(txt_cs, &my_charset_utf8_general_ci))
+ {
+ thd->convert_string(&utf_txt,
+ &my_charset_utf8_general_ci,
+ txt->str, (uint) txt->length,
+ txt_cs);
+ }
+ else
+ {
+ utf_txt.str= txt->str;
+ utf_txt.length= txt->length;
+ }
+
+ /* NOTE: utf_txt.length is in bytes, not in symbols. */
+
+ memcpy(m_body_utf8_ptr, utf_txt.str, utf_txt.length);
+ m_body_utf8_ptr += utf_txt.length;
+ *m_body_utf8_ptr= 0;
+
+ m_cpp_utf8_processed_ptr= end_ptr;
+}
+
+void Lex_input_stream::add_digest_token(uint token, LEX_YYSTYPE yylval)
+{
+ if (m_digest != NULL)
+ {
+ m_digest= digest_add_token(m_digest, token, yylval);
+ }
+}
+
+void Lex_input_stream::reduce_digest_token(uint token_left, uint token_right)
+{
+ if (m_digest != NULL)
+ {
+ m_digest= digest_reduce_token(m_digest, token_left, token_right);
+ }
+}
+
+/*
+ This is called before every query that is to be parsed.
+ Because of this, it's critical to not do too much things here.
+ (We already do too much here)
+*/
+
+void lex_start(THD *thd)
+{
+ LEX *lex= thd->lex;
+ DBUG_ENTER("lex_start");
+
+ lex->thd= lex->unit.thd= thd;
+
+ DBUG_ASSERT(!lex->explain);
+
+ lex->context_stack.empty();
+ lex->unit.init_query();
+ lex->unit.init_select();
+ /* 'parent_lex' is used in init_query() so it must be before it. */
+ lex->select_lex.parent_lex= lex;
+ lex->select_lex.init_query();
+ lex->value_list.empty();
+ lex->update_list.empty();
+ lex->set_var_list.empty();
+ lex->param_list.empty();
+ lex->view_list.empty();
+ lex->with_persistent_for_clause= FALSE;
+ lex->column_list= NULL;
+ lex->index_list= NULL;
+ lex->prepared_stmt_params.empty();
+ lex->auxiliary_table_list.empty();
+ lex->unit.next= lex->unit.master=
+ lex->unit.link_next= lex->unit.return_to= 0;
+ lex->unit.prev= lex->unit.link_prev= 0;
+ lex->unit.slave= lex->unit.global_parameters= lex->current_select=
+ lex->all_selects_list= &lex->select_lex;
+ lex->select_lex.master= &lex->unit;
+ lex->select_lex.prev= &lex->unit.slave;
+ lex->select_lex.link_next= lex->select_lex.slave= lex->select_lex.next= 0;
+ lex->select_lex.link_prev= (st_select_lex_node**)&(lex->all_selects_list);
+ lex->select_lex.options= 0;
+ lex->select_lex.sql_cache= SELECT_LEX::SQL_CACHE_UNSPECIFIED;
+ lex->select_lex.init_order();
+ lex->select_lex.group_list.empty();
+ if (lex->select_lex.group_list_ptrs)
+ lex->select_lex.group_list_ptrs->clear();
+ lex->describe= 0;
+ lex->subqueries= FALSE;
+ lex->context_analysis_only= 0;
+ lex->derived_tables= 0;
+ lex->safe_to_cache_query= 1;
+ lex->parsing_options.reset();
+ lex->empty_field_list_on_rset= 0;
+ lex->select_lex.select_number= 1;
+ lex->length=0;
+ lex->part_info= 0;
+ lex->select_lex.in_sum_expr=0;
+ lex->select_lex.ftfunc_list_alloc.empty();
+ lex->select_lex.ftfunc_list= &lex->select_lex.ftfunc_list_alloc;
+ lex->select_lex.group_list.empty();
+ lex->select_lex.order_list.empty();
+ lex->select_lex.gorder_list.empty();
+ lex->m_sql_cmd= NULL;
+ lex->duplicates= DUP_ERROR;
+ lex->ignore= 0;
+ lex->spname= NULL;
+ lex->sphead= NULL;
+ lex->spcont= NULL;
+ lex->proc_list.first= 0;
+ lex->escape_used= FALSE;
+ lex->query_tables= 0;
+ lex->reset_query_tables_list(FALSE);
+ lex->expr_allows_subselect= TRUE;
+ lex->use_only_table_context= FALSE;
+ lex->parse_vcol_expr= FALSE;
+ lex->check_exists= FALSE;
+ lex->verbose= 0;
+
+ lex->name.str= 0;
+ lex->name.length= 0;
+ lex->event_parse_data= NULL;
+ lex->profile_options= PROFILE_NONE;
+ lex->nest_level=0 ;
+ lex->select_lex.nest_level_base= &lex->unit;
+ lex->allow_sum_func= 0;
+ lex->in_sum_func= NULL;
+ /*
+ ok, there must be a better solution for this, long-term
+ I tried "bzero" in the sql_yacc.yy code, but that for
+ some reason made the values zero, even if they were set
+ */
+ lex->server_options.server_name= 0;
+ lex->server_options.server_name_length= 0;
+ lex->server_options.host= 0;
+ lex->server_options.db= 0;
+ lex->server_options.username= 0;
+ lex->server_options.password= 0;
+ lex->server_options.scheme= 0;
+ lex->server_options.socket= 0;
+ lex->server_options.owner= 0;
+ lex->server_options.port= -1;
+
+ lex->is_lex_started= TRUE;
+ lex->used_tables= 0;
+ lex->reset_slave_info.all= false;
+ lex->limit_rows_examined= 0;
+ lex->limit_rows_examined_cnt= ULONGLONG_MAX;
+ DBUG_VOID_RETURN;
+}
+
+void lex_end(LEX *lex)
+{
+ DBUG_ENTER("lex_end");
+ DBUG_PRINT("enter", ("lex: 0x%lx", (long) lex));
+
+ /* release used plugins */
+ if (lex->plugins.elements) /* No function call and no mutex if no plugins. */
+ {
+ plugin_unlock_list(0, (plugin_ref*)lex->plugins.buffer,
+ lex->plugins.elements);
+ }
+ reset_dynamic(&lex->plugins);
+
+ delete lex->sphead;
+ lex->sphead= NULL;
+
+ lex->mi.reset();
+
+ DBUG_VOID_RETURN;
+}
+
+Yacc_state::~Yacc_state()
+{
+ if (yacc_yyss)
+ {
+ my_free(yacc_yyss);
+ my_free(yacc_yyvs);
+ }
+}
+
+static int find_keyword(Lex_input_stream *lip, uint len, bool function)
+{
+ const char *tok= lip->get_tok_start();
+
+ SYMBOL *symbol= get_hash_symbol(tok, len, function);
+ if (symbol)
+ {
+ lip->yylval->symbol.symbol=symbol;
+ lip->yylval->symbol.str= (char*) tok;
+ lip->yylval->symbol.length=len;
+
+ if ((symbol->tok == NOT_SYM) &&
+ (lip->m_thd->variables.sql_mode & MODE_HIGH_NOT_PRECEDENCE))
+ return NOT2_SYM;
+ if ((symbol->tok == OR_OR_SYM) &&
+ !(lip->m_thd->variables.sql_mode & MODE_PIPES_AS_CONCAT))
+ return OR2_SYM;
+
+ return symbol->tok;
+ }
+ return 0;
+}
+
+/*
+ Check if name is a keyword
+
+ SYNOPSIS
+ is_keyword()
+ name checked name (must not be empty)
+ len length of checked name
+
+ RETURN VALUES
+ 0 name is a keyword
+ 1 name isn't a keyword
+*/
+
+bool is_keyword(const char *name, uint len)
+{
+ DBUG_ASSERT(len != 0);
+ return get_hash_symbol(name,len,0)!=0;
+}
+
+/**
+ Check if name is a sql function
+
+ @param name checked name
+
+ @return is this a native function or not
+ @retval 0 name is a function
+ @retval 1 name isn't a function
+*/
+
+bool is_lex_native_function(const LEX_STRING *name)
+{
+ DBUG_ASSERT(name != NULL);
+ return (get_hash_symbol(name->str, (uint) name->length, 1) != 0);
+}
+
+/* make a copy of token before ptr and set yytoklen */
+
+static LEX_STRING get_token(Lex_input_stream *lip, uint skip, uint length)
+{
+ LEX_STRING tmp;
+ lip->yyUnget(); // ptr points now after last token char
+ tmp.length=lip->yytoklen=length;
+ tmp.str= lip->m_thd->strmake(lip->get_tok_start() + skip, tmp.length);
+
+ lip->m_cpp_text_start= lip->get_cpp_tok_start() + skip;
+ lip->m_cpp_text_end= lip->m_cpp_text_start + tmp.length;
+
+ return tmp;
+}
+
+/*
+ todo:
+ There are no dangerous charsets in mysql for function
+ get_quoted_token yet. But it should be fixed in the
+ future to operate multichar strings (like ucs2)
+*/
+
+static LEX_STRING get_quoted_token(Lex_input_stream *lip,
+ uint skip,
+ uint length, char quote)
+{
+ LEX_STRING tmp;
+ const char *from, *end;
+ char *to;
+ lip->yyUnget(); // ptr points now after last token char
+ tmp.length= lip->yytoklen=length;
+ tmp.str=(char*) lip->m_thd->alloc(tmp.length+1);
+ from= lip->get_tok_start() + skip;
+ to= tmp.str;
+ end= to+length;
+
+ lip->m_cpp_text_start= lip->get_cpp_tok_start() + skip;
+ lip->m_cpp_text_end= lip->m_cpp_text_start + length;
+
+ for ( ; to != end; )
+ {
+ if ((*to++= *from++) == quote)
+ {
+ from++; // Skip double quotes
+ lip->m_cpp_text_start++;
+ }
+ }
+ *to= 0; // End null for safety
+ return tmp;
+}
+
+
+/*
+ Return an unescaped text literal without quotes
+ Fix sometimes to do only one scan of the string
+*/
+
+static char *get_text(Lex_input_stream *lip, int pre_skip, int post_skip)
+{
+ reg1 uchar c,sep;
+ uint found_escape=0;
+ CHARSET_INFO *cs= lip->m_thd->charset();
+
+ lip->tok_bitmap= 0;
+ sep= lip->yyGetLast(); // String should end with this
+ while (! lip->eof())
+ {
+ c= lip->yyGet();
+ lip->tok_bitmap|= c;
+#ifdef USE_MB
+ {
+ int l;
+ if (use_mb(cs) &&
+ (l = my_ismbchar(cs,
+ lip->get_ptr() -1,
+ lip->get_end_of_query()))) {
+ lip->skip_binary(l-1);
+ continue;
+ }
+ }
+#endif
+ if (c == '\\' &&
+ !(lip->m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES))
+ { // Escaped character
+ found_escape=1;
+ if (lip->eof())
+ return 0;
+ lip->yySkip();
+ }
+ else if (c == sep)
+ {
+ if (c == lip->yyGet()) // Check if two separators in a row
+ {
+ found_escape=1; // duplicate. Remember for delete
+ continue;
+ }
+ else
+ lip->yyUnget();
+
+ /* Found end. Unescape and return string */
+ const char *str, *end;
+ char *start;
+
+ str= lip->get_tok_start();
+ end= lip->get_ptr();
+ /* Extract the text from the token */
+ str += pre_skip;
+ end -= post_skip;
+ DBUG_ASSERT(end >= str);
+
+ if (!(start= (char*) lip->m_thd->alloc((uint) (end-str)+1)))
+ return (char*) ""; // Sql_alloc has set error flag
+
+ lip->m_cpp_text_start= lip->get_cpp_tok_start() + pre_skip;
+ lip->m_cpp_text_end= lip->get_cpp_ptr() - post_skip;
+
+ if (!found_escape)
+ {
+ lip->yytoklen=(uint) (end-str);
+ memcpy(start,str,lip->yytoklen);
+ start[lip->yytoklen]=0;
+ }
+ else
+ {
+ char *to;
+
+ for (to=start ; str != end ; str++)
+ {
+#ifdef USE_MB
+ int l;
+ if (use_mb(cs) &&
+ (l = my_ismbchar(cs, str, end))) {
+ while (l--)
+ *to++ = *str++;
+ str--;
+ continue;
+ }
+#endif
+ if (!(lip->m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) &&
+ *str == '\\' && str+1 != end)
+ {
+ switch(*++str) {
+ case 'n':
+ *to++='\n';
+ break;
+ case 't':
+ *to++= '\t';
+ break;
+ case 'r':
+ *to++ = '\r';
+ break;
+ case 'b':
+ *to++ = '\b';
+ break;
+ case '0':
+ *to++= 0; // Ascii null
+ break;
+ case 'Z': // ^Z must be escaped on Win32
+ *to++='\032';
+ break;
+ case '_':
+ case '%':
+ *to++= '\\'; // remember prefix for wildcard
+ /* Fall through */
+ default:
+ *to++= *str;
+ break;
+ }
+ }
+ else if (*str == sep)
+ *to++= *str++; // Two ' or "
+ else
+ *to++ = *str;
+ }
+ *to=0;
+ lip->yytoklen=(uint) (to-start);
+ }
+ return start;
+ }
+ }
+ return 0; // unexpected end of query
+}
+
+
+/*
+** Calc type of integer; long integer, longlong integer or real.
+** Returns smallest type that match the string.
+** When using unsigned long long values the result is converted to a real
+** because else they will be unexpected sign changes because all calculation
+** is done with longlong or double.
+*/
+
+static const char *long_str="2147483647";
+static const uint long_len=10;
+static const char *signed_long_str="-2147483648";
+static const char *longlong_str="9223372036854775807";
+static const uint longlong_len=19;
+static const char *signed_longlong_str="-9223372036854775808";
+static const uint signed_longlong_len=19;
+static const char *unsigned_longlong_str="18446744073709551615";
+static const uint unsigned_longlong_len=20;
+
+static inline uint int_token(const char *str,uint length)
+{
+ if (length < long_len) // quick normal case
+ return NUM;
+ bool neg=0;
+
+ if (*str == '+') // Remove sign and pre-zeros
+ {
+ str++; length--;
+ }
+ else if (*str == '-')
+ {
+ str++; length--;
+ neg=1;
+ }
+ while (*str == '0' && length)
+ {
+ str++; length --;
+ }
+ if (length < long_len)
+ return NUM;
+
+ uint smaller,bigger;
+ const char *cmp;
+ if (neg)
+ {
+ if (length == long_len)
+ {
+ cmp= signed_long_str+1;
+ smaller=NUM; // If <= signed_long_str
+ bigger=LONG_NUM; // If >= signed_long_str
+ }
+ else if (length < signed_longlong_len)
+ return LONG_NUM;
+ else if (length > signed_longlong_len)
+ return DECIMAL_NUM;
+ else
+ {
+ cmp=signed_longlong_str+1;
+ smaller=LONG_NUM; // If <= signed_longlong_str
+ bigger=DECIMAL_NUM;
+ }
+ }
+ else
+ {
+ if (length == long_len)
+ {
+ cmp= long_str;
+ smaller=NUM;
+ bigger=LONG_NUM;
+ }
+ else if (length < longlong_len)
+ return LONG_NUM;
+ else if (length > longlong_len)
+ {
+ if (length > unsigned_longlong_len)
+ return DECIMAL_NUM;
+ cmp=unsigned_longlong_str;
+ smaller=ULONGLONG_NUM;
+ bigger=DECIMAL_NUM;
+ }
+ else
+ {
+ cmp=longlong_str;
+ smaller=LONG_NUM;
+ bigger= ULONGLONG_NUM;
+ }
+ }
+ while (*cmp && *cmp++ == *str++) ;
+ return ((uchar) str[-1] <= (uchar) cmp[-1]) ? smaller : bigger;
+}
+
+
+/**
+ Given a stream that is advanced to the first contained character in
+ an open comment, consume the comment. Optionally, if we are allowed,
+ recurse so that we understand comments within this current comment.
+
+ At this level, we do not support version-condition comments. We might
+ have been called with having just passed one in the stream, though. In
+ that case, we probably want to tolerate mundane comments inside. Thus,
+ the case for recursion.
+
+ @retval Whether EOF reached before comment is closed.
+*/
+bool consume_comment(Lex_input_stream *lip, int remaining_recursions_permitted)
+{
+ reg1 uchar c;
+ while (! lip->eof())
+ {
+ c= lip->yyGet();
+
+ if (remaining_recursions_permitted > 0)
+ {
+ if ((c == '/') && (lip->yyPeek() == '*'))
+ {
+ lip->yySkip(); /* Eat asterisk */
+ consume_comment(lip, remaining_recursions_permitted-1);
+ continue;
+ }
+ }
+
+ if (c == '*')
+ {
+ if (lip->yyPeek() == '/')
+ {
+ lip->yySkip(); /* Eat slash */
+ return FALSE;
+ }
+ }
+
+ if (c == '\n')
+ lip->yylineno++;
+ }
+
+ return TRUE;
+}
+
+
+/*
+ MYSQLlex remember the following states from the following MYSQLlex()
+
+ @param yylval [out] semantic value of the token being parsed (yylval)
+ @param thd THD
+
+ - MY_LEX_EOQ Found end of query
+ - MY_LEX_OPERATOR_OR_IDENT Last state was an ident, text or number
+ (which can't be followed by a signed number)
+*/
+
+int MYSQLlex(YYSTYPE *yylval, THD *thd)
+{
+ Lex_input_stream *lip= & thd->m_parser_state->m_lip;
+ int token;
+
+ if (lip->lookahead_token >= 0)
+ {
+ /*
+ The next token was already parsed in advance,
+ return it.
+ */
+ token= lip->lookahead_token;
+ lip->lookahead_token= -1;
+ *yylval= *(lip->lookahead_yylval);
+ lip->lookahead_yylval= NULL;
+ lip->add_digest_token(token, yylval);
+ return token;
+ }
+
+ token= lex_one_token(yylval, thd);
+
+ switch(token) {
+ case WITH:
+ /*
+ Parsing 'WITH' 'ROLLUP' or 'WITH' 'CUBE' requires 2 look ups,
+ which makes the grammar LALR(2).
+ Replace by a single 'WITH_ROLLUP' or 'WITH_CUBE' token,
+ to transform the grammar into a LALR(1) grammar,
+ which sql_yacc.yy can process.
+ */
+ token= lex_one_token(yylval, thd);
+ switch(token) {
+ case CUBE_SYM:
+ lip->add_digest_token(WITH_CUBE_SYM, yylval);
+ return WITH_CUBE_SYM;
+ case ROLLUP_SYM:
+ lip->add_digest_token(WITH_ROLLUP_SYM, yylval);
+ return WITH_ROLLUP_SYM;
+ default:
+ /*
+ Save the token following 'WITH'
+ */
+ lip->lookahead_yylval= lip->yylval;
+ lip->yylval= NULL;
+ lip->lookahead_token= token;
+ lip->add_digest_token(WITH, yylval);
+ return WITH;
+ }
+ break;
+ default:
+ break;
+ }
+
+ lip->add_digest_token(token, yylval);
+ return token;
+}
+
+static int lex_one_token(YYSTYPE *yylval, THD *thd)
+{
+ reg1 uchar c;
+ bool comment_closed;
+ int tokval, result_state;
+ uint length;
+ enum my_lex_states state;
+ Lex_input_stream *lip= & thd->m_parser_state->m_lip;
+ LEX *lex= thd->lex;
+ CHARSET_INFO *const cs= thd->charset();
+ const uchar *const state_map= cs->state_map;
+ const uchar *const ident_map= cs->ident_map;
+
+ LINT_INIT(c);
+ lip->yylval=yylval; // The global state
+
+ lip->start_token();
+ state=lip->next_state;
+ lip->next_state=MY_LEX_OPERATOR_OR_IDENT;
+ for (;;)
+ {
+ switch (state) {
+ case MY_LEX_OPERATOR_OR_IDENT: // Next is operator or keyword
+ case MY_LEX_START: // Start of token
+ // Skip starting whitespace
+ while(state_map[c= lip->yyPeek()] == MY_LEX_SKIP)
+ {
+ if (c == '\n')
+ lip->yylineno++;
+
+ lip->yySkip();
+ }
+
+ /* Start of real token */
+ lip->restart_token();
+ c= lip->yyGet();
+ state= (enum my_lex_states) state_map[c];
+ break;
+ case MY_LEX_ESCAPE:
+ if (lip->yyGet() == 'N')
+ { // Allow \N as shortcut for NULL
+ yylval->lex_str.str=(char*) "\\N";
+ yylval->lex_str.length=2;
+ return NULL_SYM;
+ }
+ /* Fall through */
+ case MY_LEX_CHAR: // Unknown or single char token
+ case MY_LEX_SKIP: // This should not happen
+ if (c != ')')
+ lip->next_state= MY_LEX_START; // Allow signed numbers
+ return((int) c);
+
+ case MY_LEX_MINUS_OR_COMMENT:
+ if (lip->yyPeek() == '-' &&
+ (my_isspace(cs,lip->yyPeekn(1)) ||
+ my_iscntrl(cs,lip->yyPeekn(1))))
+ {
+ state=MY_LEX_COMMENT;
+ break;
+ }
+ lip->next_state= MY_LEX_START; // Allow signed numbers
+ return((int) c);
+
+ case MY_LEX_PLACEHOLDER:
+ /*
+ Check for a placeholder: it should not precede a possible identifier
+ because of binlogging: when a placeholder is replaced with
+ its value in a query for the binlog, the query must stay
+ grammatically correct.
+ */
+ lip->next_state= MY_LEX_START; // Allow signed numbers
+ if (lip->stmt_prepare_mode && !ident_map[(uchar) lip->yyPeek()])
+ return(PARAM_MARKER);
+ return((int) c);
+
+ case MY_LEX_COMMA:
+ lip->next_state= MY_LEX_START; // Allow signed numbers
+ /*
+ Warning:
+ This is a work around, to make the "remember_name" rule in
+ sql/sql_yacc.yy work properly.
+ The problem is that, when parsing "select expr1, expr2",
+ the code generated by bison executes the *pre* action
+ remember_name (see select_item) *before* actually parsing the
+ first token of expr2.
+ */
+ lip->restart_token();
+ return((int) c);
+
+ case MY_LEX_IDENT_OR_NCHAR:
+ if (lip->yyPeek() != '\'')
+ {
+ state= MY_LEX_IDENT;
+ break;
+ }
+ /* Found N'string' */
+ lip->yySkip(); // Skip '
+ if (!(yylval->lex_str.str = get_text(lip, 2, 1)))
+ {
+ state= MY_LEX_CHAR; // Read char by char
+ break;
+ }
+ yylval->lex_str.length= lip->yytoklen;
+ lex->text_string_is_7bit= (lip->tok_bitmap & 0x80) ? 0 : 1;
+ return(NCHAR_STRING);
+
+ case MY_LEX_IDENT_OR_HEX:
+ if (lip->yyPeek() == '\'')
+ { // Found x'hex-number'
+ state= MY_LEX_HEX_NUMBER;
+ break;
+ }
+ case MY_LEX_IDENT_OR_BIN:
+ if (lip->yyPeek() == '\'')
+ { // Found b'bin-number'
+ state= MY_LEX_BIN_NUMBER;
+ break;
+ }
+ case MY_LEX_IDENT:
+ const char *start;
+#if defined(USE_MB) && defined(USE_MB_IDENT)
+ if (use_mb(cs))
+ {
+ result_state= IDENT_QUOTED;
+ if (my_mbcharlen(cs, lip->yyGetLast()) > 1)
+ {
+ int l = my_ismbchar(cs,
+ lip->get_ptr() -1,
+ lip->get_end_of_query());
+ if (l == 0) {
+ state = MY_LEX_CHAR;
+ continue;
+ }
+ lip->skip_binary(l - 1);
+ }
+ while (ident_map[c=lip->yyGet()])
+ {
+ if (my_mbcharlen(cs, c) > 1)
+ {
+ int l;
+ if ((l = my_ismbchar(cs,
+ lip->get_ptr() -1,
+ lip->get_end_of_query())) == 0)
+ break;
+ lip->skip_binary(l-1);
+ }
+ }
+ }
+ else
+#endif
+ {
+ for (result_state= c;
+ ident_map[(uchar) (c= lip->yyGet())];
+ result_state|= c)
+ ;
+ /* If there were non-ASCII characters, mark that we must convert */
+ result_state= result_state & 0x80 ? IDENT_QUOTED : IDENT;
+ }
+ length= lip->yyLength();
+ start= lip->get_ptr();
+ if (lip->ignore_space)
+ {
+ /*
+ If we find a space then this can't be an identifier. We notice this
+ below by checking start != lex->ptr.
+ */
+ for (; state_map[(uchar) c] == MY_LEX_SKIP ; c= lip->yyGet())
+ ;
+ }
+ if (start == lip->get_ptr() && c == '.' &&
+ ident_map[(uchar) lip->yyPeek()])
+ lip->next_state=MY_LEX_IDENT_SEP;
+ else
+ { // '(' must follow directly if function
+ lip->yyUnget();
+ if ((tokval = find_keyword(lip, length, c == '(')))
+ {
+ lip->next_state= MY_LEX_START; // Allow signed numbers
+ return(tokval); // Was keyword
+ }
+ lip->yySkip(); // next state does a unget
+ }
+ yylval->lex_str=get_token(lip, 0, length);
+
+ /*
+ Note: "SELECT _bla AS 'alias'"
+ _bla should be considered as a IDENT if charset haven't been found.
+ So we don't use MYF(MY_WME) with get_charset_by_csname to avoid
+ producing an error.
+ */
+
+ if (yylval->lex_str.str[0] == '_')
+ {
+ CHARSET_INFO *cs= get_charset_by_csname(yylval->lex_str.str + 1,
+ MY_CS_PRIMARY, MYF(0));
+ if (cs)
+ {
+ yylval->charset= cs;
+ lip->m_underscore_cs= cs;
+
+ lip->body_utf8_append(lip->m_cpp_text_start,
+ lip->get_cpp_tok_start() + length);
+ return(UNDERSCORE_CHARSET);
+ }
+ }
+
+ lip->body_utf8_append(lip->m_cpp_text_start);
+
+ lip->body_utf8_append_literal(thd, &yylval->lex_str, cs,
+ lip->m_cpp_text_end);
+
+ return(result_state); // IDENT or IDENT_QUOTED
+
+ case MY_LEX_IDENT_SEP: // Found ident and now '.'
+ yylval->lex_str.str= (char*) lip->get_ptr();
+ yylval->lex_str.length= 1;
+ c= lip->yyGet(); // should be '.'
+ lip->next_state= MY_LEX_IDENT_START; // Next is ident (not keyword)
+ if (!ident_map[(uchar) lip->yyPeek()]) // Probably ` or "
+ lip->next_state= MY_LEX_START;
+ return((int) c);
+
+ case MY_LEX_NUMBER_IDENT: // number or ident which num-start
+ if (lip->yyGetLast() == '0')
+ {
+ c= lip->yyGet();
+ if (c == 'x')
+ {
+ while (my_isxdigit(cs,(c = lip->yyGet()))) ;
+ if ((lip->yyLength() >= 3) && !ident_map[c])
+ {
+ /* skip '0x' */
+ yylval->lex_str=get_token(lip, 2, lip->yyLength()-2);
+ return (HEX_NUM);
+ }
+ lip->yyUnget();
+ state= MY_LEX_IDENT_START;
+ break;
+ }
+ else if (c == 'b')
+ {
+ while ((c= lip->yyGet()) == '0' || c == '1')
+ ;
+ if ((lip->yyLength() >= 3) && !ident_map[c])
+ {
+ /* Skip '0b' */
+ yylval->lex_str= get_token(lip, 2, lip->yyLength()-2);
+ return (BIN_NUM);
+ }
+ lip->yyUnget();
+ state= MY_LEX_IDENT_START;
+ break;
+ }
+ lip->yyUnget();
+ }
+
+ while (my_isdigit(cs, (c = lip->yyGet()))) ;
+ if (!ident_map[c])
+ { // Can't be identifier
+ state=MY_LEX_INT_OR_REAL;
+ break;
+ }
+ if (c == 'e' || c == 'E')
+ {
+ // The following test is written this way to allow numbers of type 1e1
+ if (my_isdigit(cs,lip->yyPeek()) ||
+ (c=(lip->yyGet())) == '+' || c == '-')
+ { // Allow 1E+10
+ if (my_isdigit(cs,lip->yyPeek())) // Number must have digit after sign
+ {
+ lip->yySkip();
+ while (my_isdigit(cs,lip->yyGet())) ;
+ yylval->lex_str=get_token(lip, 0, lip->yyLength());
+ return(FLOAT_NUM);
+ }
+ }
+ lip->yyUnget();
+ }
+ // fall through
+ case MY_LEX_IDENT_START: // We come here after '.'
+ result_state= IDENT;
+#if defined(USE_MB) && defined(USE_MB_IDENT)
+ if (use_mb(cs))
+ {
+ result_state= IDENT_QUOTED;
+ while (ident_map[c=lip->yyGet()])
+ {
+ if (my_mbcharlen(cs, c) > 1)
+ {
+ int l;
+ if ((l = my_ismbchar(cs,
+ lip->get_ptr() -1,
+ lip->get_end_of_query())) == 0)
+ break;
+ lip->skip_binary(l-1);
+ }
+ }
+ }
+ else
+#endif
+ {
+ for (result_state=0; ident_map[c= lip->yyGet()]; result_state|= c)
+ ;
+ /* If there were non-ASCII characters, mark that we must convert */
+ result_state= result_state & 0x80 ? IDENT_QUOTED : IDENT;
+ }
+ if (c == '.' && ident_map[(uchar) lip->yyPeek()])
+ lip->next_state=MY_LEX_IDENT_SEP;// Next is '.'
+
+ yylval->lex_str= get_token(lip, 0, lip->yyLength());
+
+ lip->body_utf8_append(lip->m_cpp_text_start);
+
+ lip->body_utf8_append_literal(thd, &yylval->lex_str, cs,
+ lip->m_cpp_text_end);
+
+ return(result_state);
+
+ case MY_LEX_USER_VARIABLE_DELIMITER: // Found quote char
+ {
+ uint double_quotes= 0;
+ char quote_char= c; // Used char
+ while ((c=lip->yyGet()))
+ {
+ int var_length;
+ if ((var_length= my_mbcharlen(cs, c)) == 1)
+ {
+ if (c == quote_char)
+ {
+ if (lip->yyPeek() != quote_char)
+ break;
+ c=lip->yyGet();
+ double_quotes++;
+ continue;
+ }
+ }
+#ifdef USE_MB
+ else if (use_mb(cs))
+ {
+ if ((var_length= my_ismbchar(cs, lip->get_ptr() - 1,
+ lip->get_end_of_query())))
+ lip->skip_binary(var_length-1);
+ }
+#endif
+ }
+ if (double_quotes)
+ yylval->lex_str=get_quoted_token(lip, 1,
+ lip->yyLength() - double_quotes -1,
+ quote_char);
+ else
+ yylval->lex_str=get_token(lip, 1, lip->yyLength() -1);
+ if (c == quote_char)
+ lip->yySkip(); // Skip end `
+ lip->next_state= MY_LEX_START;
+
+ lip->body_utf8_append(lip->m_cpp_text_start);
+
+ lip->body_utf8_append_literal(thd, &yylval->lex_str, cs,
+ lip->m_cpp_text_end);
+
+ return(IDENT_QUOTED);
+ }
+ case MY_LEX_INT_OR_REAL: // Complete int or incomplete real
+ if (c != '.')
+ { // Found complete integer number.
+ yylval->lex_str=get_token(lip, 0, lip->yyLength());
+ return int_token(yylval->lex_str.str, (uint) yylval->lex_str.length);
+ }
+ // fall through
+ case MY_LEX_REAL: // Incomplete real number
+ while (my_isdigit(cs,c = lip->yyGet())) ;
+
+ if (c == 'e' || c == 'E')
+ {
+ c = lip->yyGet();
+ if (c == '-' || c == '+')
+ c = lip->yyGet(); // Skip sign
+ if (!my_isdigit(cs,c))
+ { // No digit after sign
+ state= MY_LEX_CHAR;
+ break;
+ }
+ while (my_isdigit(cs,lip->yyGet())) ;
+ yylval->lex_str=get_token(lip, 0, lip->yyLength());
+ return(FLOAT_NUM);
+ }
+ yylval->lex_str=get_token(lip, 0, lip->yyLength());
+ return(DECIMAL_NUM);
+
+ case MY_LEX_HEX_NUMBER: // Found x'hexstring'
+ lip->yySkip(); // Accept opening '
+ while (my_isxdigit(cs, (c= lip->yyGet()))) ;
+ if (c != '\'')
+ return(ABORT_SYM); // Illegal hex constant
+ lip->yySkip(); // Accept closing '
+ length= lip->yyLength(); // Length of hexnum+3
+ if ((length % 2) == 0)
+ return(ABORT_SYM); // odd number of hex digits
+ yylval->lex_str=get_token(lip,
+ 2, // skip x'
+ length-3); // don't count x' and last '
+ return HEX_STRING;
+
+ case MY_LEX_BIN_NUMBER: // Found b'bin-string'
+ lip->yySkip(); // Accept opening '
+ while ((c= lip->yyGet()) == '0' || c == '1')
+ ;
+ if (c != '\'')
+ return(ABORT_SYM); // Illegal hex constant
+ lip->yySkip(); // Accept closing '
+ length= lip->yyLength(); // Length of bin-num + 3
+ yylval->lex_str= get_token(lip,
+ 2, // skip b'
+ length-3); // don't count b' and last '
+ return (BIN_NUM);
+
+ case MY_LEX_CMP_OP: // Incomplete comparison operator
+ if (state_map[(uchar) lip->yyPeek()] == MY_LEX_CMP_OP ||
+ state_map[(uchar) lip->yyPeek()] == MY_LEX_LONG_CMP_OP)
+ lip->yySkip();
+ if ((tokval = find_keyword(lip, lip->yyLength() + 1, 0)))
+ {
+ lip->next_state= MY_LEX_START; // Allow signed numbers
+ return(tokval);
+ }
+ state = MY_LEX_CHAR; // Something fishy found
+ break;
+
+ case MY_LEX_LONG_CMP_OP: // Incomplete comparison operator
+ if (state_map[(uchar) lip->yyPeek()] == MY_LEX_CMP_OP ||
+ state_map[(uchar) lip->yyPeek()] == MY_LEX_LONG_CMP_OP)
+ {
+ lip->yySkip();
+ if (state_map[(uchar) lip->yyPeek()] == MY_LEX_CMP_OP)
+ lip->yySkip();
+ }
+ if ((tokval = find_keyword(lip, lip->yyLength() + 1, 0)))
+ {
+ lip->next_state= MY_LEX_START; // Found long op
+ return(tokval);
+ }
+ state = MY_LEX_CHAR; // Something fishy found
+ break;
+
+ case MY_LEX_BOOL:
+ if (c != lip->yyPeek())
+ {
+ state=MY_LEX_CHAR;
+ break;
+ }
+ lip->yySkip();
+ tokval = find_keyword(lip,2,0); // Is a bool operator
+ lip->next_state= MY_LEX_START; // Allow signed numbers
+ return(tokval);
+
+ case MY_LEX_STRING_OR_DELIMITER:
+ if (thd->variables.sql_mode & MODE_ANSI_QUOTES)
+ {
+ state= MY_LEX_USER_VARIABLE_DELIMITER;
+ break;
+ }
+ /* " used for strings */
+ case MY_LEX_STRING: // Incomplete text string
+ if (!(yylval->lex_str.str = get_text(lip, 1, 1)))
+ {
+ state= MY_LEX_CHAR; // Read char by char
+ break;
+ }
+ yylval->lex_str.length=lip->yytoklen;
+
+ lip->body_utf8_append(lip->m_cpp_text_start);
+
+ lip->body_utf8_append_literal(thd, &yylval->lex_str,
+ lip->m_underscore_cs ? lip->m_underscore_cs : cs,
+ lip->m_cpp_text_end);
+
+ lip->m_underscore_cs= NULL;
+
+ lex->text_string_is_7bit= (lip->tok_bitmap & 0x80) ? 0 : 1;
+ return(TEXT_STRING);
+
+ case MY_LEX_COMMENT: // Comment
+ lex->select_lex.options|= OPTION_FOUND_COMMENT;
+ while ((c = lip->yyGet()) != '\n' && c) ;
+ lip->yyUnget(); // Safety against eof
+ state = MY_LEX_START; // Try again
+ break;
+ case MY_LEX_LONG_COMMENT: /* Long C comment? */
+ if (lip->yyPeek() != '*')
+ {
+ state=MY_LEX_CHAR; // Probable division
+ break;
+ }
+ lex->select_lex.options|= OPTION_FOUND_COMMENT;
+ /* Reject '/' '*', since we might need to turn off the echo */
+ lip->yyUnget();
+
+ lip->save_in_comment_state();
+
+ if (lip->yyPeekn(2) == '!' ||
+ (lip->yyPeekn(2) == 'M' && lip->yyPeekn(3) == '!'))
+ {
+ bool maria_comment_syntax= lip->yyPeekn(2) == 'M';
+ lip->in_comment= DISCARD_COMMENT;
+ /* Accept '/' '*' '!', but do not keep this marker. */
+ lip->set_echo(FALSE);
+ lip->yySkipn(maria_comment_syntax ? 4 : 3);
+
+ /*
+ The special comment format is very strict:
+ '/' '*' '!', followed by an optional 'M' and exactly
+ 1-2 digits (major), 2 digits (minor), then 2 digits (dot).
+ 32302 -> 3.23.02
+ 50032 -> 5.0.32
+ 50114 -> 5.1.14
+ 100000 -> 10.0.0
+ */
+ if ( my_isdigit(cs, lip->yyPeekn(0))
+ && my_isdigit(cs, lip->yyPeekn(1))
+ && my_isdigit(cs, lip->yyPeekn(2))
+ && my_isdigit(cs, lip->yyPeekn(3))
+ && my_isdigit(cs, lip->yyPeekn(4))
+ )
+ {
+ ulong version;
+ uint length= 5;
+ char *end_ptr= (char*) lip->get_ptr()+length;
+ int error;
+ if (my_isdigit(cs, lip->yyPeekn(5)))
+ {
+ end_ptr++; // 6 digit number
+ length++;
+ }
+
+ version= (ulong) my_strtoll10(lip->get_ptr(), &end_ptr, &error);
+
+ /*
+ MySQL-5.7 has new features and might have new SQL syntax that
+ MariaDB-10.0 does not understand. Ignore all versioned comments
+ with MySQL versions in the range 50700-999999, but
+ do not ignore MariaDB specific comments for the same versions.
+ */
+ if (version <= MYSQL_VERSION_ID &&
+ (version < 50700 || version > 99999 || maria_comment_syntax))
+ {
+ /* Accept 'M' 'm' 'm' 'd' 'd' */
+ lip->yySkipn(length);
+ /* Expand the content of the special comment as real code */
+ lip->set_echo(TRUE);
+ state=MY_LEX_START;
+ break; /* Do not treat contents as a comment. */
+ }
+ else
+ {
+ /*
+ Patch and skip the conditional comment to avoid it
+ being propagated infinitely (eg. to a slave).
+ */
+ char *pcom= lip->yyUnput(' ');
+ comment_closed= ! consume_comment(lip, 1);
+ if (! comment_closed)
+ {
+ *pcom= '!';
+ }
+ /* version allowed to have one level of comment inside. */
+ }
+ }
+ else
+ {
+ /* Not a version comment. */
+ state=MY_LEX_START;
+ lip->set_echo(TRUE);
+ break;
+ }
+ }
+ else
+ {
+ lip->in_comment= PRESERVE_COMMENT;
+ lip->yySkip(); // Accept /
+ lip->yySkip(); // Accept *
+ comment_closed= ! consume_comment(lip, 0);
+ /* regular comments can have zero comments inside. */
+ }
+ /*
+ Discard:
+ - regular '/' '*' comments,
+ - special comments '/' '*' '!' for a future version,
+ by scanning until we find a closing '*' '/' marker.
+
+ Nesting regular comments isn't allowed. The first
+ '*' '/' returns the parser to the previous state.
+
+ /#!VERSI oned containing /# regular #/ is allowed #/
+
+ Inside one versioned comment, another versioned comment
+ is treated as a regular discardable comment. It gets
+ no special parsing.
+ */
+
+ /* Unbalanced comments with a missing '*' '/' are a syntax error */
+ if (! comment_closed)
+ return (ABORT_SYM);
+ state = MY_LEX_START; // Try again
+ lip->restore_in_comment_state();
+ break;
+ case MY_LEX_END_LONG_COMMENT:
+ if ((lip->in_comment != NO_COMMENT) && lip->yyPeek() == '/')
+ {
+ /* Reject '*' '/' */
+ lip->yyUnget();
+ /* Accept '*' '/', with the proper echo */
+ lip->set_echo(lip->in_comment == PRESERVE_COMMENT);
+ lip->yySkipn(2);
+ /* And start recording the tokens again */
+ lip->set_echo(TRUE);
+ lip->in_comment=NO_COMMENT;
+ state=MY_LEX_START;
+ }
+ else
+ state=MY_LEX_CHAR; // Return '*'
+ break;
+ case MY_LEX_SET_VAR: // Check if ':='
+ if (lip->yyPeek() != '=')
+ {
+ state=MY_LEX_CHAR; // Return ':'
+ break;
+ }
+ lip->yySkip();
+ return (SET_VAR);
+ case MY_LEX_SEMICOLON: // optional line terminator
+ state= MY_LEX_CHAR; // Return ';'
+ break;
+ case MY_LEX_EOL:
+ if (lip->eof())
+ {
+ lip->yyUnget(); // Reject the last '\0'
+ lip->set_echo(FALSE);
+ lip->yySkip();
+ lip->set_echo(TRUE);
+ /* Unbalanced comments with a missing '*' '/' are a syntax error */
+ if (lip->in_comment != NO_COMMENT)
+ return (ABORT_SYM);
+ lip->next_state=MY_LEX_END; // Mark for next loop
+ return(END_OF_INPUT);
+ }
+ state=MY_LEX_CHAR;
+ break;
+ case MY_LEX_END:
+ lip->next_state=MY_LEX_END;
+ return(0); // We found end of input last time
+
+ /* Actually real shouldn't start with . but allow them anyhow */
+ case MY_LEX_REAL_OR_POINT:
+ if (my_isdigit(cs,lip->yyPeek()))
+ state = MY_LEX_REAL; // Real
+ else
+ {
+ state= MY_LEX_IDENT_SEP; // return '.'
+ lip->yyUnget(); // Put back '.'
+ }
+ break;
+ case MY_LEX_USER_END: // end '@' of user@hostname
+ switch (state_map[(uchar) lip->yyPeek()]) {
+ case MY_LEX_STRING:
+ case MY_LEX_USER_VARIABLE_DELIMITER:
+ case MY_LEX_STRING_OR_DELIMITER:
+ break;
+ case MY_LEX_USER_END:
+ lip->next_state=MY_LEX_SYSTEM_VAR;
+ break;
+ default:
+ lip->next_state=MY_LEX_HOSTNAME;
+ break;
+ }
+ yylval->lex_str.str=(char*) lip->get_ptr();
+ yylval->lex_str.length=1;
+ return((int) '@');
+ case MY_LEX_HOSTNAME: // end '@' of user@hostname
+ for (c=lip->yyGet() ;
+ my_isalnum(cs,c) || c == '.' || c == '_' || c == '$';
+ c= lip->yyGet()) ;
+ yylval->lex_str=get_token(lip, 0, lip->yyLength());
+ return(LEX_HOSTNAME);
+ case MY_LEX_SYSTEM_VAR:
+ yylval->lex_str.str=(char*) lip->get_ptr();
+ yylval->lex_str.length=1;
+ lip->yySkip(); // Skip '@'
+ lip->next_state= (state_map[(uchar) lip->yyPeek()] ==
+ MY_LEX_USER_VARIABLE_DELIMITER ?
+ MY_LEX_OPERATOR_OR_IDENT :
+ MY_LEX_IDENT_OR_KEYWORD);
+ return((int) '@');
+ case MY_LEX_IDENT_OR_KEYWORD:
+ /*
+ We come here when we have found two '@' in a row.
+ We should now be able to handle:
+ [(global | local | session) .]variable_name
+ */
+
+ for (result_state= 0; ident_map[c= lip->yyGet()]; result_state|= c)
+ ;
+ /* If there were non-ASCII characters, mark that we must convert */
+ result_state= result_state & 0x80 ? IDENT_QUOTED : IDENT;
+
+ if (c == '.')
+ lip->next_state=MY_LEX_IDENT_SEP;
+ length= lip->yyLength();
+ if (length == 0)
+ return(ABORT_SYM); // Names must be nonempty.
+ if ((tokval= find_keyword(lip, length,0)))
+ {
+ lip->yyUnget(); // Put back 'c'
+ return(tokval); // Was keyword
+ }
+ yylval->lex_str=get_token(lip, 0, length);
+
+ lip->body_utf8_append(lip->m_cpp_text_start);
+
+ lip->body_utf8_append_literal(thd, &yylval->lex_str, cs,
+ lip->m_cpp_text_end);
+
+ return(result_state);
+ }
+ }
+}
+
+
+void trim_whitespace(CHARSET_INFO *cs, LEX_STRING *str)
+{
+ /*
+ TODO:
+ This code assumes that there are no multi-bytes characters
+ that can be considered white-space.
+ */
+
+ while ((str->length > 0) && (my_isspace(cs, str->str[0])))
+ {
+ str->length --;
+ str->str ++;
+ }
+
+ /*
+ FIXME:
+ Also, parsing backward is not safe with multi bytes characters
+ */
+ while ((str->length > 0) && (my_isspace(cs, str->str[str->length-1])))
+ {
+ str->length --;
+ }
+}
+
+
+/*
+ st_select_lex structures initialisations
+*/
+
+void st_select_lex_node::init_query()
+{
+ options= 0;
+ sql_cache= SQL_CACHE_UNSPECIFIED;
+ linkage= UNSPECIFIED_TYPE;
+ no_table_names_allowed= 0;
+ uncacheable= 0;
+}
+
+void st_select_lex_node::init_select()
+{
+}
+
+void st_select_lex_unit::init_query()
+{
+ st_select_lex_node::init_query();
+ linkage= GLOBAL_OPTIONS_TYPE;
+ global_parameters= first_select();
+ select_limit_cnt= HA_POS_ERROR;
+ offset_limit_cnt= 0;
+ union_distinct= 0;
+ prepared= optimized= executed= 0;
+ item= 0;
+ union_result= 0;
+ table= 0;
+ fake_select_lex= 0;
+ cleaned= 0;
+ item_list.empty();
+ describe= 0;
+ found_rows_for_union= 0;
+ insert_table_with_stored_vcol= 0;
+ derived= 0;
+}
+
+void st_select_lex::init_query()
+{
+ st_select_lex_node::init_query();
+ table_list.empty();
+ top_join_list.empty();
+ join_list= &top_join_list;
+ embedding= 0;
+ leaf_tables_prep.empty();
+ leaf_tables.empty();
+ item_list.empty();
+ join= 0;
+ having= prep_having= where= prep_where= 0;
+ olap= UNSPECIFIED_OLAP_TYPE;
+ having_fix_field= 0;
+ context.select_lex= this;
+ context.init();
+ /*
+ Add the name resolution context of the current (sub)query to the
+ stack of contexts for the whole query.
+ TODO:
+ push_context may return an error if there is no memory for a new
+ element in the stack, however this method has no return value,
+ thus push_context should be moved to a place where query
+ initialization is checked for failure.
+ */
+ parent_lex->push_context(&context);
+ cond_count= between_count= with_wild= 0;
+ max_equal_elems= 0;
+ ref_pointer_array= 0;
+ ref_pointer_array_size= 0;
+ select_n_where_fields= 0;
+ select_n_reserved= 0;
+ select_n_having_items= 0;
+ n_sum_items= 0;
+ n_child_sum_items= 0;
+ subquery_in_having= explicit_limit= 0;
+ is_item_list_lookup= 0;
+ first_execution= 1;
+ first_natural_join_processing= 1;
+ first_cond_optimization= 1;
+ parsing_place= NO_MATTER;
+ exclude_from_table_unique_test= no_wrap_view_item= FALSE;
+ nest_level= 0;
+ link_next= 0;
+ is_prep_leaf_list_saved= FALSE;
+ have_merged_subqueries= FALSE;
+ bzero((char*) expr_cache_may_be_used, sizeof(expr_cache_may_be_used));
+ m_non_agg_field_used= false;
+ m_agg_func_used= false;
+}
+
+void st_select_lex::init_select()
+{
+ st_select_lex_node::init_select();
+ sj_nests.empty();
+ sj_subselects.empty();
+ group_list.empty();
+ if (group_list_ptrs)
+ group_list_ptrs->clear();
+ type= db= 0;
+ having= 0;
+ table_join_options= 0;
+ in_sum_expr= with_wild= 0;
+ options= 0;
+ sql_cache= SQL_CACHE_UNSPECIFIED;
+ braces= 0;
+ interval_list.empty();
+ ftfunc_list_alloc.empty();
+ inner_sum_func_list= 0;
+ ftfunc_list= &ftfunc_list_alloc;
+ linkage= UNSPECIFIED_TYPE;
+ order_list.elements= 0;
+ order_list.first= 0;
+ order_list.next= &order_list.first;
+ /* Set limit and offset to default values */
+ select_limit= 0; /* denotes the default limit = HA_POS_ERROR */
+ offset_limit= 0; /* denotes the default offset = 0 */
+ with_sum_func= 0;
+ is_correlated= 0;
+ cur_pos_in_select_list= UNDEF_POS;
+ non_agg_fields.empty();
+ cond_value= having_value= Item::COND_UNDEF;
+ inner_refs_list.empty();
+ insert_tables= 0;
+ merged_into= 0;
+ m_non_agg_field_used= false;
+ m_agg_func_used= false;
+ name_visibility_map= 0;
+}
+
+/*
+ st_select_lex structures linking
+*/
+
+/* include on level down */
+void st_select_lex_node::include_down(st_select_lex_node *upper)
+{
+ if ((next= upper->slave))
+ next->prev= &next;
+ prev= &upper->slave;
+ upper->slave= this;
+ master= upper;
+ slave= 0;
+}
+
+
+void st_select_lex_node::add_slave(st_select_lex_node *slave_arg)
+{
+ for (; slave; slave= slave->next)
+ if (slave == slave_arg)
+ return;
+
+ if (slave)
+ {
+ st_select_lex_node *slave_arg_slave= slave_arg->slave;
+ /* Insert in the front of list of slaves if any. */
+ slave_arg->include_neighbour(slave);
+ /* include_neighbour() sets slave_arg->slave=0, restore it. */
+ slave_arg->slave= slave_arg_slave;
+ /* Count on include_neighbour() setting the master. */
+ DBUG_ASSERT(slave_arg->master == this);
+ }
+ else
+ {
+ slave= slave_arg;
+ slave_arg->master= this;
+ }
+}
+
+
+/*
+ include on level down (but do not link)
+
+ SYNOPSYS
+ st_select_lex_node::include_standalone()
+ upper - reference on node underr which this node should be included
+ ref - references on reference on this node
+*/
+void st_select_lex_node::include_standalone(st_select_lex_node *upper,
+ st_select_lex_node **ref)
+{
+ next= 0;
+ prev= ref;
+ master= upper;
+ slave= 0;
+}
+
+/* include neighbour (on same level) */
+void st_select_lex_node::include_neighbour(st_select_lex_node *before)
+{
+ if ((next= before->next))
+ next->prev= &next;
+ prev= &before->next;
+ before->next= this;
+ master= before->master;
+ slave= 0;
+}
+
+/* including in global SELECT_LEX list */
+void st_select_lex_node::include_global(st_select_lex_node **plink)
+{
+ if ((link_next= *plink))
+ link_next->link_prev= &link_next;
+ link_prev= plink;
+ *plink= this;
+}
+
+//excluding from global list (internal function)
+void st_select_lex_node::fast_exclude()
+{
+ if (link_prev)
+ {
+ if ((*link_prev= link_next))
+ link_next->link_prev= link_prev;
+ }
+ // Remove slave structure
+ for (; slave; slave= slave->next)
+ slave->fast_exclude();
+
+}
+
+
+/*
+ Exclude a node from the tree lex structure, but leave it in the global
+ list of nodes.
+*/
+
+void st_select_lex_node::exclude_from_tree()
+{
+ if ((*prev= next))
+ next->prev= prev;
+}
+
+
+/*
+ Exclude select_lex structure (except first (first select can't be
+ deleted, because it is most upper select))
+*/
+void st_select_lex_node::exclude()
+{
+ /* exclude from global list */
+ fast_exclude();
+ /* exclude from other structures */
+ exclude_from_tree();
+ /*
+ We do not need following statements, because prev pointer of first
+ list element point to master->slave
+ if (master->slave == this)
+ master->slave= next;
+ */
+}
+
+
+/*
+ Exclude level of current unit from tree of SELECTs
+
+ SYNOPSYS
+ st_select_lex_unit::exclude_level()
+
+ NOTE: units which belong to current will be brought up on level of
+ currernt unit
+*/
+void st_select_lex_unit::exclude_level()
+{
+ SELECT_LEX_UNIT *units= 0, **units_last= &units;
+ for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select())
+ {
+ // unlink current level from global SELECTs list
+ if (sl->link_prev && (*sl->link_prev= sl->link_next))
+ sl->link_next->link_prev= sl->link_prev;
+
+ // bring up underlay levels
+ SELECT_LEX_UNIT **last= 0;
+ for (SELECT_LEX_UNIT *u= sl->first_inner_unit(); u; u= u->next_unit())
+ {
+ u->master= master;
+ last= (SELECT_LEX_UNIT**)&(u->next);
+ }
+ if (last)
+ {
+ (*units_last)= sl->first_inner_unit();
+ units_last= last;
+ }
+ }
+ if (units)
+ {
+ // include brought up levels in place of current
+ (*prev)= units;
+ (*units_last)= (SELECT_LEX_UNIT*)next;
+ if (next)
+ next->prev= (SELECT_LEX_NODE**)units_last;
+ units->prev= prev;
+ }
+ else
+ {
+ // exclude currect unit from list of nodes
+ (*prev)= next;
+ if (next)
+ next->prev= prev;
+ }
+}
+
+
+/*
+ Exclude subtree of current unit from tree of SELECTs
+
+ SYNOPSYS
+ st_select_lex_unit::exclude_tree()
+*/
+void st_select_lex_unit::exclude_tree()
+{
+ for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select())
+ {
+ // unlink current level from global SELECTs list
+ if (sl->link_prev && (*sl->link_prev= sl->link_next))
+ sl->link_next->link_prev= sl->link_prev;
+
+ // unlink underlay levels
+ for (SELECT_LEX_UNIT *u= sl->first_inner_unit(); u; u= u->next_unit())
+ {
+ u->exclude_level();
+ }
+ }
+ // exclude currect unit from list of nodes
+ (*prev)= next;
+ if (next)
+ next->prev= prev;
+}
+
+
+/*
+ st_select_lex_node::mark_as_dependent mark all st_select_lex struct from
+ this to 'last' as dependent
+
+ SYNOPSIS
+ last - pointer to last st_select_lex struct, before which all
+ st_select_lex have to be marked as dependent
+
+ NOTE
+ 'last' should be reachable from this st_select_lex_node
+*/
+
+bool st_select_lex::mark_as_dependent(THD *thd, st_select_lex *last,
+ Item *dependency)
+{
+
+ DBUG_ASSERT(this != last);
+
+ /*
+ Mark all selects from resolved to 1 before select where was
+ found table as depended (of select where was found table)
+ */
+ SELECT_LEX *s= this;
+ do
+ {
+ if (!(s->uncacheable & UNCACHEABLE_DEPENDENT_GENERATED))
+ {
+ // Select is dependent of outer select
+ s->uncacheable= (s->uncacheable & ~UNCACHEABLE_UNITED) |
+ UNCACHEABLE_DEPENDENT_GENERATED;
+ SELECT_LEX_UNIT *munit= s->master_unit();
+ munit->uncacheable= (munit->uncacheable & ~UNCACHEABLE_UNITED) |
+ UNCACHEABLE_DEPENDENT_GENERATED;
+ for (SELECT_LEX *sl= munit->first_select(); sl ; sl= sl->next_select())
+ {
+ if (sl != s &&
+ !(sl->uncacheable & (UNCACHEABLE_DEPENDENT_GENERATED |
+ UNCACHEABLE_UNITED)))
+ sl->uncacheable|= UNCACHEABLE_UNITED;
+ }
+ }
+
+ Item_subselect *subquery_expr= s->master_unit()->item;
+ if (subquery_expr && subquery_expr->mark_as_dependent(thd, last,
+ dependency))
+ return TRUE;
+ } while ((s= s->outer_select()) != last && s != 0);
+ is_correlated= TRUE;
+ this->master_unit()->item->is_correlated= TRUE;
+ return FALSE;
+}
+
+bool st_select_lex_node::set_braces(bool value) { return 1; }
+bool st_select_lex_node::inc_in_sum_expr() { return 1; }
+uint st_select_lex_node::get_in_sum_expr() { return 0; }
+TABLE_LIST* st_select_lex_node::get_table_list() { return 0; }
+List<Item>* st_select_lex_node::get_item_list() { return 0; }
+TABLE_LIST *st_select_lex_node::add_table_to_list(THD *thd, Table_ident *table,
+ LEX_STRING *alias,
+ ulong table_join_options,
+ thr_lock_type flags,
+ enum_mdl_type mdl_type,
+ List<Index_hint> *hints,
+ List<String> *partition_names,
+ LEX_STRING *option)
+{
+ return 0;
+}
+ulong st_select_lex_node::get_table_join_options()
+{
+ return 0;
+}
+
+/*
+ prohibit using LIMIT clause
+*/
+bool st_select_lex::test_limit()
+{
+ if (select_limit != 0)
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "LIMIT & IN/ALL/ANY/SOME subquery");
+ return(1);
+ }
+ return(0);
+}
+
+
+st_select_lex_unit* st_select_lex_unit::master_unit()
+{
+ return this;
+}
+
+
+st_select_lex* st_select_lex_unit::outer_select()
+{
+ return (st_select_lex*) master;
+}
+
+
+bool st_select_lex::add_order_to_list(THD *thd, Item *item, bool asc)
+{
+ return add_to_list(thd, order_list, item, asc);
+}
+
+
+bool st_select_lex::add_gorder_to_list(THD *thd, Item *item, bool asc)
+{
+ return add_to_list(thd, gorder_list, item, asc);
+}
+
+bool st_select_lex::add_item_to_list(THD *thd, Item *item)
+{
+ DBUG_ENTER("st_select_lex::add_item_to_list");
+ DBUG_PRINT("info", ("Item: 0x%lx", (long) item));
+ DBUG_RETURN(item_list.push_back(item));
+}
+
+
+bool st_select_lex::add_group_to_list(THD *thd, Item *item, bool asc)
+{
+ return add_to_list(thd, group_list, item, asc);
+}
+
+
+bool st_select_lex::add_ftfunc_to_list(Item_func_match *func)
+{
+ return !func || ftfunc_list->push_back(func); // end of memory?
+}
+
+
+st_select_lex_unit* st_select_lex::master_unit()
+{
+ return (st_select_lex_unit*) master;
+}
+
+
+st_select_lex* st_select_lex::outer_select()
+{
+ return (st_select_lex*) master->get_master();
+}
+
+
+bool st_select_lex::set_braces(bool value)
+{
+ braces= value;
+ return 0;
+}
+
+
+bool st_select_lex::inc_in_sum_expr()
+{
+ in_sum_expr++;
+ return 0;
+}
+
+
+uint st_select_lex::get_in_sum_expr()
+{
+ return in_sum_expr;
+}
+
+
+TABLE_LIST* st_select_lex::get_table_list()
+{
+ return table_list.first;
+}
+
+List<Item>* st_select_lex::get_item_list()
+{
+ return &item_list;
+}
+
+ulong st_select_lex::get_table_join_options()
+{
+ return table_join_options;
+}
+
+
+bool st_select_lex::setup_ref_array(THD *thd, uint order_group_num)
+{
+ // find_order_in_list() may need some extra space, so multiply by two.
+ order_group_num*= 2;
+
+ /*
+ We have to create array in prepared statement memory if it is a
+ prepared statement
+ */
+ Query_arena *arena= thd->stmt_arena;
+ const uint n_elems= (n_sum_items +
+ n_child_sum_items +
+ item_list.elements +
+ select_n_reserved +
+ select_n_having_items +
+ select_n_where_fields +
+ order_group_num) * 5;
+ if (ref_pointer_array != NULL)
+ {
+ /*
+ We need to take 'n_sum_items' into account when allocating the array,
+ and this may actually increase during the optimization phase due to
+ MIN/MAX rewrite in Item_in_subselect::single_value_transformer.
+ In the usual case we can reuse the array from the prepare phase.
+ If we need a bigger array, we must allocate a new one.
+ */
+ if (ref_pointer_array_size >= n_elems)
+ {
+ DBUG_PRINT("info", ("reusing old ref_array"));
+ return false;
+ }
+ }
+ ref_pointer_array= static_cast<Item**>(arena->alloc(sizeof(Item*) * n_elems));
+ if (ref_pointer_array != NULL)
+ ref_pointer_array_size= n_elems;
+
+ return ref_pointer_array == NULL;
+}
+
+
+void st_select_lex_unit::print(String *str, enum_query_type query_type)
+{
+ bool union_all= !union_distinct;
+ for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select())
+ {
+ if (sl != first_select())
+ {
+ str->append(STRING_WITH_LEN(" union "));
+ if (union_all)
+ str->append(STRING_WITH_LEN("all "));
+ else if (union_distinct == sl)
+ union_all= TRUE;
+ }
+ if (sl->braces)
+ str->append('(');
+ sl->print(thd, str, query_type);
+ if (sl->braces)
+ str->append(')');
+ }
+ if (fake_select_lex == global_parameters)
+ {
+ if (fake_select_lex->order_list.elements)
+ {
+ str->append(STRING_WITH_LEN(" order by "));
+ fake_select_lex->print_order(str,
+ fake_select_lex->order_list.first,
+ query_type);
+ }
+ fake_select_lex->print_limit(thd, str, query_type);
+ }
+}
+
+
+void st_select_lex::print_order(String *str,
+ ORDER *order,
+ enum_query_type query_type)
+{
+ for (; order; order= order->next)
+ {
+ if (order->counter_used)
+ {
+ if (query_type != QT_VIEW_INTERNAL)
+ {
+ char buffer[20];
+ size_t length= my_snprintf(buffer, 20, "%d", order->counter);
+ str->append(buffer, (uint) length);
+ }
+ else
+ {
+ /* replace numeric reference with expression */
+ if (order->item[0]->type() == Item::INT_ITEM &&
+ order->item[0]->basic_const_item())
+ {
+ char buffer[20];
+ size_t length= my_snprintf(buffer, 20, "%d", order->counter);
+ str->append(buffer, (uint) length);
+ /* make it expression instead of integer constant */
+ str->append(STRING_WITH_LEN("+0"));
+ }
+ else
+ (*order->item)->print(str, query_type);
+ }
+ }
+ else
+ (*order->item)->print(str, query_type);
+ if (!order->asc)
+ str->append(STRING_WITH_LEN(" desc"));
+ if (order->next)
+ str->append(',');
+ }
+}
+
+
+void st_select_lex::print_limit(THD *thd,
+ String *str,
+ enum_query_type query_type)
+{
+ SELECT_LEX_UNIT *unit= master_unit();
+ Item_subselect *item= unit->item;
+
+ if (item && unit->global_parameters == this)
+ {
+ Item_subselect::subs_type subs_type= item->substype();
+ if (subs_type == Item_subselect::EXISTS_SUBS ||
+ subs_type == Item_subselect::IN_SUBS ||
+ subs_type == Item_subselect::ALL_SUBS)
+ {
+ return;
+ }
+ }
+ if (explicit_limit)
+ {
+ str->append(STRING_WITH_LEN(" limit "));
+ if (offset_limit)
+ {
+ offset_limit->print(str, query_type);
+ str->append(',');
+ }
+ select_limit->print(str, query_type);
+ }
+}
+
+
+/**
+ @brief Restore the LEX and THD in case of a parse error.
+
+ This is a clean up call that is invoked by the Bison generated
+ parser before returning an error from MYSQLparse. If your
+ semantic actions manipulate with the global thread state (which
+ is a very bad practice and should not normally be employed) and
+ need a clean-up in case of error, and you can not use %destructor
+ rule in the grammar file itself, this function should be used
+ to implement the clean up.
+*/
+
+void LEX::cleanup_lex_after_parse_error(THD *thd)
+{
+ /*
+ Delete sphead for the side effect of restoring of the original
+ LEX state, thd->lex, thd->mem_root and thd->free_list if they
+ were replaced when parsing stored procedure statements. We
+ will never use sphead object after a parse error, so it's okay
+ to delete it only for the sake of the side effect.
+ TODO: make this functionality explicit in sp_head class.
+ Sic: we must nullify the member of the main lex, not the
+ current one that will be thrown away
+ */
+ if (thd->lex->sphead)
+ {
+ thd->lex->sphead->restore_thd_mem_root(thd);
+ delete thd->lex->sphead;
+ thd->lex->sphead= NULL;
+ }
+}
+
+/*
+ Initialize (or reset) Query_tables_list object.
+
+ SYNOPSIS
+ reset_query_tables_list()
+ init TRUE - we should perform full initialization of object with
+ allocating needed memory
+ FALSE - object is already initialized so we should only reset
+ its state so it can be used for parsing/processing
+ of new statement
+
+ DESCRIPTION
+ This method initializes Query_tables_list so it can be used as part
+ of LEX object for parsing/processing of statement. One can also use
+ this method to reset state of already initialized Query_tables_list
+ so it can be used for processing of new statement.
+*/
+
+void Query_tables_list::reset_query_tables_list(bool init)
+{
+ sql_command= SQLCOM_END;
+ if (!init && query_tables)
+ {
+ TABLE_LIST *table= query_tables;
+ for (;;)
+ {
+ delete table->view;
+ if (query_tables_last == &table->next_global ||
+ !(table= table->next_global))
+ break;
+ }
+ }
+ query_tables= 0;
+ query_tables_last= &query_tables;
+ query_tables_own_last= 0;
+ if (init)
+ {
+ /*
+ We delay real initialization of hash (and therefore related
+ memory allocation) until first insertion into this hash.
+ */
+ my_hash_clear(&sroutines);
+ }
+ else if (sroutines.records)
+ {
+ /* Non-zero sroutines.records means that hash was initialized. */
+ my_hash_reset(&sroutines);
+ }
+ sroutines_list.empty();
+ sroutines_list_own_last= sroutines_list.next;
+ sroutines_list_own_elements= 0;
+ binlog_stmt_flags= 0;
+ stmt_accessed_table_flag= 0;
+}
+
+
+/*
+ Destroy Query_tables_list object with freeing all resources used by it.
+
+ SYNOPSIS
+ destroy_query_tables_list()
+*/
+
+void Query_tables_list::destroy_query_tables_list()
+{
+ my_hash_free(&sroutines);
+}
+
+
+/*
+ Initialize LEX object.
+
+ SYNOPSIS
+ LEX::LEX()
+
+ NOTE
+ LEX object initialized with this constructor can be used as part of
+ THD object for which one can safely call open_tables(), lock_tables()
+ and close_thread_tables() functions. But it is not yet ready for
+ statement parsing. On should use lex_start() function to prepare LEX
+ for this.
+*/
+
+LEX::LEX()
+ : explain(NULL),
+ result(0), option_type(OPT_DEFAULT), is_lex_started(0),
+ limit_rows_examined_cnt(ULONGLONG_MAX)
+{
+
+ my_init_dynamic_array2(&plugins, sizeof(plugin_ref),
+ plugins_static_buffer,
+ INITIAL_LEX_PLUGIN_LIST_SIZE,
+ INITIAL_LEX_PLUGIN_LIST_SIZE, 0);
+ reset_query_tables_list(TRUE);
+ mi.init();
+}
+
+
+/*
+ Check whether the merging algorithm can be used on this VIEW
+
+ SYNOPSIS
+ LEX::can_be_merged()
+
+ DESCRIPTION
+ We can apply merge algorithm if it is single SELECT view with
+ subqueries only in WHERE clause (we do not count SELECTs of underlying
+ views, and second level subqueries) and we have not grpouping, ordering,
+ HAVING clause, aggregate functions, DISTINCT clause, LIMIT clause and
+ several underlying tables.
+
+ RETURN
+ FALSE - only temporary table algorithm can be used
+ TRUE - merge algorithm can be used
+*/
+
+bool LEX::can_be_merged()
+{
+ // TODO: do not forget implement case when select_lex.table_list.elements==0
+
+ /* find non VIEW subqueries/unions */
+ bool selects_allow_merge= (select_lex.next_select() == 0 &&
+ !(select_lex.uncacheable &
+ UNCACHEABLE_RAND));
+ if (selects_allow_merge)
+ {
+ for (SELECT_LEX_UNIT *tmp_unit= select_lex.first_inner_unit();
+ tmp_unit;
+ tmp_unit= tmp_unit->next_unit())
+ {
+ if (tmp_unit->first_select()->parent_lex == this &&
+ (tmp_unit->item == 0 ||
+ (tmp_unit->item->place() != IN_WHERE &&
+ tmp_unit->item->place() != IN_ON &&
+ tmp_unit->item->place() != SELECT_LIST)))
+ {
+ selects_allow_merge= 0;
+ break;
+ }
+ }
+ }
+
+ return (selects_allow_merge &&
+ select_lex.group_list.elements == 0 &&
+ select_lex.having == 0 &&
+ select_lex.with_sum_func == 0 &&
+ select_lex.table_list.elements >= 1 &&
+ !(select_lex.options & SELECT_DISTINCT) &&
+ select_lex.select_limit == 0);
+}
+
+
+/*
+ check if command can use VIEW with MERGE algorithm (for top VIEWs)
+
+ SYNOPSIS
+ LEX::can_use_merged()
+
+ DESCRIPTION
+ Only listed here commands can use merge algorithm in top level
+ SELECT_LEX (for subqueries will be used merge algorithm if
+ LEX::can_not_use_merged() is not TRUE).
+
+ RETURN
+ FALSE - command can't use merged VIEWs
+ TRUE - VIEWs with MERGE algorithms can be used
+*/
+
+bool LEX::can_use_merged()
+{
+ switch (sql_command)
+ {
+ case SQLCOM_SELECT:
+ case SQLCOM_CREATE_TABLE:
+ case SQLCOM_UPDATE:
+ case SQLCOM_UPDATE_MULTI:
+ case SQLCOM_DELETE:
+ case SQLCOM_DELETE_MULTI:
+ case SQLCOM_INSERT:
+ case SQLCOM_INSERT_SELECT:
+ case SQLCOM_REPLACE:
+ case SQLCOM_REPLACE_SELECT:
+ case SQLCOM_LOAD:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/*
+ Check if command can't use merged views in any part of command
+
+ SYNOPSIS
+ LEX::can_not_use_merged()
+
+ DESCRIPTION
+ Temporary table algorithm will be used on all SELECT levels for queries
+ listed here (see also LEX::can_use_merged()).
+
+ RETURN
+ FALSE - command can't use merged VIEWs
+ TRUE - VIEWs with MERGE algorithms can be used
+*/
+
+bool LEX::can_not_use_merged()
+{
+ switch (sql_command)
+ {
+ case SQLCOM_CREATE_VIEW:
+ case SQLCOM_SHOW_CREATE:
+ /*
+ SQLCOM_SHOW_FIELDS is necessary to make
+ information schema tables working correctly with views.
+ see get_schema_tables_result function
+ */
+ case SQLCOM_SHOW_FIELDS:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/*
+ Detect that we need only table structure of derived table/view
+
+ SYNOPSIS
+ only_view_structure()
+
+ RETURN
+ TRUE yes, we need only structure
+ FALSE no, we need data
+*/
+
+bool LEX::only_view_structure()
+{
+ switch (sql_command) {
+ case SQLCOM_SHOW_CREATE:
+ case SQLCOM_SHOW_TABLES:
+ case SQLCOM_SHOW_FIELDS:
+ case SQLCOM_REVOKE_ALL:
+ case SQLCOM_REVOKE:
+ case SQLCOM_GRANT:
+ case SQLCOM_CREATE_VIEW:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+
+/*
+ Should Items_ident be printed correctly
+
+ SYNOPSIS
+ need_correct_ident()
+
+ RETURN
+ TRUE yes, we need only structure
+ FALSE no, we need data
+*/
+
+
+bool LEX::need_correct_ident()
+{
+ switch(sql_command)
+ {
+ case SQLCOM_SHOW_CREATE:
+ case SQLCOM_SHOW_TABLES:
+ case SQLCOM_CREATE_VIEW:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/*
+ Get effective type of CHECK OPTION for given view
+
+ SYNOPSIS
+ get_effective_with_check()
+ view given view
+
+ NOTE
+ It have not sense to set CHECK OPTION for SELECT satement or subqueries,
+ so we do not.
+
+ RETURN
+ VIEW_CHECK_NONE no need CHECK OPTION
+ VIEW_CHECK_LOCAL CHECK OPTION LOCAL
+ VIEW_CHECK_CASCADED CHECK OPTION CASCADED
+*/
+
+uint8 LEX::get_effective_with_check(TABLE_LIST *view)
+{
+ if (view->select_lex->master_unit() == &unit &&
+ which_check_option_applicable())
+ return (uint8)view->with_check;
+ return VIEW_CHECK_NONE;
+}
+
+
+/**
+ This method should be called only during parsing.
+ It is aware of compound statements (stored routine bodies)
+ and will initialize the destination with the default
+ database of the stored routine, rather than the default
+ database of the connection it is parsed in.
+ E.g. if one has no current database selected, or current database
+ set to 'bar' and then issues:
+
+ CREATE PROCEDURE foo.p1() BEGIN SELECT * FROM t1 END//
+
+ t1 is meant to refer to foo.t1, not to bar.t1.
+
+ This method is needed to support this rule.
+
+ @return TRUE in case of error (parsing should be aborted, FALSE in
+ case of success
+*/
+
+bool
+LEX::copy_db_to(char **p_db, size_t *p_db_length) const
+{
+ if (sphead)
+ {
+ DBUG_ASSERT(sphead->m_db.str && sphead->m_db.length);
+ /*
+ It is safe to assign the string by-pointer, both sphead and
+ its statements reside in the same memory root.
+ */
+ *p_db= sphead->m_db.str;
+ if (p_db_length)
+ *p_db_length= sphead->m_db.length;
+ return FALSE;
+ }
+ return thd->copy_db_to(p_db, p_db_length);
+}
+
+/*
+ initialize limit counters
+
+ SYNOPSIS
+ st_select_lex_unit::set_limit()
+ values - SELECT_LEX with initial values for counters
+*/
+
+void st_select_lex_unit::set_limit(st_select_lex *sl)
+{
+ ha_rows select_limit_val;
+ ulonglong val;
+
+ DBUG_ASSERT(! thd->stmt_arena->is_stmt_prepare());
+ if (sl->select_limit)
+ {
+ Item *item = sl->select_limit;
+ /*
+ fix_fields() has not been called for sl->select_limit. That's due to the
+ historical reasons -- this item could be only of type Item_int, and
+ Item_int does not require fix_fields(). Thus, fix_fields() was never
+ called for sl->select_limit.
+
+ Some time ago, Item_splocal was also allowed for LIMIT / OFFSET clauses.
+ However, the fix_fields() behavior was not updated, which led to a crash
+ in some cases.
+
+ There is no single place where to call fix_fields() for LIMIT / OFFSET
+ items during the fix-fields-phase. Thus, for the sake of readability,
+ it was decided to do it here, on the evaluation phase (which is a
+ violation of design, but we chose the lesser of two evils).
+
+ We can call fix_fields() here, because sl->select_limit can be of two
+ types only: Item_int and Item_splocal. Item_int::fix_fields() is trivial,
+ and Item_splocal::fix_fields() (or rather Item_sp_variable::fix_fields())
+ has the following specific:
+ 1) it does not affect other items;
+ 2) it does not fail.
+
+ Nevertheless DBUG_ASSERT was added to catch future changes in
+ fix_fields() implementation. Also added runtime check against a result
+ of fix_fields() in order to handle error condition in non-debug build.
+ */
+ bool fix_fields_successful= true;
+ if (!item->fixed)
+ {
+ fix_fields_successful= !item->fix_fields(thd, NULL);
+
+ DBUG_ASSERT(fix_fields_successful);
+ }
+ val= fix_fields_successful ? item->val_uint() : HA_POS_ERROR;
+ }
+ else
+ val= HA_POS_ERROR;
+
+ select_limit_val= (ha_rows)val;
+#ifndef BIG_TABLES
+ /*
+ Check for overflow : ha_rows can be smaller then ulonglong if
+ BIG_TABLES is off.
+ */
+ if (val != (ulonglong)select_limit_val)
+ select_limit_val= HA_POS_ERROR;
+#endif
+ if (sl->offset_limit)
+ {
+ Item *item = sl->offset_limit;
+ // see comment for sl->select_limit branch.
+ bool fix_fields_successful= true;
+ if (!item->fixed)
+ {
+ fix_fields_successful= !item->fix_fields(thd, NULL);
+
+ DBUG_ASSERT(fix_fields_successful);
+ }
+ val= fix_fields_successful ? item->val_uint() : 0;
+ }
+ else
+ val= 0;
+
+ offset_limit_cnt= (ha_rows)val;
+#ifndef BIG_TABLES
+ /* Check for truncation. */
+ if (val != (ulonglong)offset_limit_cnt)
+ offset_limit_cnt= HA_POS_ERROR;
+#endif
+ select_limit_cnt= select_limit_val + offset_limit_cnt;
+ if (select_limit_cnt < select_limit_val)
+ select_limit_cnt= HA_POS_ERROR; // no limit
+}
+
+
+/**
+ @brief Set the initial purpose of this TABLE_LIST object in the list of used
+ tables.
+
+ We need to track this information on table-by-table basis, since when this
+ table becomes an element of the pre-locked list, it's impossible to identify
+ which SQL sub-statement it has been originally used in.
+
+ E.g.:
+
+ User request: SELECT * FROM t1 WHERE f1();
+ FUNCTION f1(): DELETE FROM t2; RETURN 1;
+ BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a);
+
+ For this user request, the pre-locked list will contain t1, t2, t3
+ table elements, each needed for different DML.
+
+ The trigger event map is updated to reflect INSERT, UPDATE, DELETE,
+ REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE ..
+ REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE
+ clause.
+*/
+
+void LEX::set_trg_event_type_for_tables()
+{
+ uint8 new_trg_event_map= 0;
+ DBUG_ENTER("LEX::set_trg_event_type_for_tables");
+
+ /*
+ Some auxiliary operations
+ (e.g. GRANT processing) create TABLE_LIST instances outside
+ the parser. Additionally, some commands (e.g. OPTIMIZE) change
+ the lock type for a table only after parsing is done. Luckily,
+ these do not fire triggers and do not need to pre-load them.
+ For these TABLE_LISTs set_trg_event_type is never called, and
+ trg_event_map is always empty. That means that the pre-locking
+ algorithm will ignore triggers defined on these tables, if
+ any, and the execution will either fail with an assert in
+ sql_trigger.cc or with an error that a used table was not
+ pre-locked, in case of a production build.
+
+ TODO: this usage pattern creates unnecessary module dependencies
+ and should be rewritten to go through the parser.
+ Table list instances created outside the parser in most cases
+ refer to mysql.* system tables. It is not allowed to have
+ a trigger on a system table, but keeping track of
+ initialization provides extra safety in case this limitation
+ is circumvented.
+ */
+
+ switch (sql_command) {
+ case SQLCOM_LOCK_TABLES:
+ /*
+ On a LOCK TABLE, all triggers must be pre-loaded for this TABLE_LIST
+ when opening an associated TABLE.
+ */
+ new_trg_event_map= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_INSERT)) |
+ static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_UPDATE)) |
+ static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_DELETE));
+ break;
+ /*
+ Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE
+ clause, it will be handled later in this method.
+ */
+ case SQLCOM_INSERT: /* fall through */
+ case SQLCOM_INSERT_SELECT:
+ /*
+ LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT
+ triggers.
+ If the statement also has REPLACE clause, it will be
+ handled later in this method.
+ */
+ case SQLCOM_LOAD: /* fall through */
+ /*
+ REPLACE is semantically equivalent to INSERT. In case
+ of a primary or unique key conflict, it deletes the old
+ record and inserts a new one. So we also may need to
+ fire ON DELETE triggers. This functionality is handled
+ later in this method.
+ */
+ case SQLCOM_REPLACE: /* fall through */
+ case SQLCOM_REPLACE_SELECT:
+ /*
+ CREATE TABLE ... SELECT defaults to INSERT if the table or
+ view already exists. REPLACE option of CREATE TABLE ...
+ REPLACE SELECT is handled later in this method.
+ */
+ case SQLCOM_CREATE_TABLE:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_INSERT));
+ break;
+ /* Basic update and multi-update */
+ case SQLCOM_UPDATE: /* fall through */
+ case SQLCOM_UPDATE_MULTI:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_UPDATE));
+ break;
+ /* Basic delete and multi-delete */
+ case SQLCOM_DELETE: /* fall through */
+ case SQLCOM_DELETE_MULTI:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_DELETE));
+ break;
+ default:
+ break;
+ }
+
+ switch (duplicates) {
+ case DUP_UPDATE:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_UPDATE));
+ break;
+ case DUP_REPLACE:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_DELETE));
+ break;
+ case DUP_ERROR:
+ default:
+ break;
+ }
+
+
+ /*
+ Do not iterate over sub-selects, only the tables in the outermost
+ SELECT_LEX can be modified, if any.
+ */
+ TABLE_LIST *tables= select_lex.get_table_list();
+
+ while (tables)
+ {
+ /*
+ This is a fast check to filter out statements that do
+ not change data, or tables on the right side, in case of
+ INSERT .. SELECT, CREATE TABLE .. SELECT and so on.
+ Here we also filter out OPTIMIZE statement and non-updateable
+ views, for which lock_type is TL_UNLOCK or TL_READ after
+ parsing.
+ */
+ if (static_cast<int>(tables->lock_type) >=
+ static_cast<int>(TL_WRITE_ALLOW_WRITE))
+ tables->trg_event_map= new_trg_event_map;
+ tables= tables->next_local;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Unlink the first table from the global table list and the first table from
+ outer select (lex->select_lex) local list
+
+ SYNOPSIS
+ unlink_first_table()
+ link_to_local Set to 1 if caller should link this table to local list
+
+ NOTES
+ We assume that first tables in both lists is the same table or the local
+ list is empty.
+
+ RETURN
+ 0 If 'query_tables' == 0
+ unlinked table
+ In this case link_to_local is set.
+
+*/
+TABLE_LIST *LEX::unlink_first_table(bool *link_to_local)
+{
+ TABLE_LIST *first;
+ if ((first= query_tables))
+ {
+ /*
+ Exclude from global table list
+ */
+ if ((query_tables= query_tables->next_global))
+ query_tables->prev_global= &query_tables;
+ else
+ query_tables_last= &query_tables;
+ first->next_global= 0;
+
+ /*
+ and from local list if it is not empty
+ */
+ if ((*link_to_local= MY_TEST(select_lex.table_list.first)))
+ {
+ select_lex.context.table_list=
+ select_lex.context.first_name_resolution_table= first->next_local;
+ select_lex.table_list.first= first->next_local;
+ select_lex.table_list.elements--; //safety
+ first->next_local= 0;
+ /*
+ Ensure that the global list has the same first table as the local
+ list.
+ */
+ first_lists_tables_same();
+ }
+ }
+ return first;
+}
+
+
+/*
+ Bring first local table of first most outer select to first place in global
+ table list
+
+ SYNOPSYS
+ LEX::first_lists_tables_same()
+
+ NOTES
+ In many cases (for example, usual INSERT/DELETE/...) the first table of
+ main SELECT_LEX have special meaning => check that it is the first table
+ in global list and re-link to be first in the global list if it is
+ necessary. We need such re-linking only for queries with sub-queries in
+ the select list, as only in this case tables of sub-queries will go to
+ the global list first.
+*/
+
+void LEX::first_lists_tables_same()
+{
+ TABLE_LIST *first_table= select_lex.table_list.first;
+ if (query_tables != first_table && first_table != 0)
+ {
+ TABLE_LIST *next;
+ if (query_tables_last == &first_table->next_global)
+ query_tables_last= first_table->prev_global;
+
+ if ((next= *first_table->prev_global= first_table->next_global))
+ next->prev_global= first_table->prev_global;
+ /* include in new place */
+ first_table->next_global= query_tables;
+ /*
+ We are sure that query_tables is not 0, because first_table was not
+ first table in the global list => we can use
+ query_tables->prev_global without check of query_tables
+ */
+ query_tables->prev_global= &first_table->next_global;
+ first_table->prev_global= &query_tables;
+ query_tables= first_table;
+ }
+}
+
+
+/*
+ Link table back that was unlinked with unlink_first_table()
+
+ SYNOPSIS
+ link_first_table_back()
+ link_to_local do we need link this table to local
+
+ RETURN
+ global list
+*/
+
+void LEX::link_first_table_back(TABLE_LIST *first,
+ bool link_to_local)
+{
+ if (first)
+ {
+ if ((first->next_global= query_tables))
+ query_tables->prev_global= &first->next_global;
+ else
+ query_tables_last= &first->next_global;
+ query_tables= first;
+
+ if (link_to_local)
+ {
+ first->next_local= select_lex.table_list.first;
+ select_lex.context.table_list= first;
+ select_lex.table_list.first= first;
+ select_lex.table_list.elements++; //safety
+ }
+ }
+}
+
+
+
+/*
+ cleanup lex for case when we open table by table for processing
+
+ SYNOPSIS
+ LEX::cleanup_after_one_table_open()
+
+ NOTE
+ This method is mostly responsible for cleaning up of selects lists and
+ derived tables state. To rollback changes in Query_tables_list one has
+ to call Query_tables_list::reset_query_tables_list(FALSE).
+*/
+
+void LEX::cleanup_after_one_table_open()
+{
+ /*
+ thd->lex->derived_tables & additional units may be set if we open
+ a view. It is necessary to clear thd->lex->derived_tables flag
+ to prevent processing of derived tables during next open_and_lock_tables
+ if next table is a real table and cleanup & remove underlying units
+ NOTE: all units will be connected to thd->lex->select_lex, because we
+ have not UNION on most upper level.
+ */
+ if (all_selects_list != &select_lex)
+ {
+ derived_tables= 0;
+ select_lex.exclude_from_table_unique_test= false;
+ /* cleunup underlying units (units of VIEW) */
+ for (SELECT_LEX_UNIT *un= select_lex.first_inner_unit();
+ un;
+ un= un->next_unit())
+ un->cleanup();
+ /* reduce all selects list to default state */
+ all_selects_list= &select_lex;
+ /* remove underlying units (units of VIEW) subtree */
+ select_lex.cut_subtree();
+ }
+}
+
+
+/*
+ Save current state of Query_tables_list for this LEX, and prepare it
+ for processing of new statemnt.
+
+ SYNOPSIS
+ reset_n_backup_query_tables_list()
+ backup Pointer to Query_tables_list instance to be used for backup
+*/
+
+void LEX::reset_n_backup_query_tables_list(Query_tables_list *backup)
+{
+ backup->set_query_tables_list(this);
+ /*
+ We have to perform full initialization here since otherwise we
+ will damage backed up state.
+ */
+ this->reset_query_tables_list(TRUE);
+}
+
+
+/*
+ Restore state of Query_tables_list for this LEX from backup.
+
+ SYNOPSIS
+ restore_backup_query_tables_list()
+ backup Pointer to Query_tables_list instance used for backup
+*/
+
+void LEX::restore_backup_query_tables_list(Query_tables_list *backup)
+{
+ this->destroy_query_tables_list();
+ this->set_query_tables_list(backup);
+}
+
+
+/*
+ Checks for usage of routines and/or tables in a parsed statement
+
+ SYNOPSIS
+ LEX:table_or_sp_used()
+
+ RETURN
+ FALSE No routines and tables used
+ TRUE Either or both routines and tables are used.
+*/
+
+bool LEX::table_or_sp_used()
+{
+ DBUG_ENTER("table_or_sp_used");
+
+ if (sroutines.records || query_tables)
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Do end-of-prepare fixup for list of tables and their merge-VIEWed tables
+
+ SYNOPSIS
+ fix_prepare_info_in_table_list()
+ thd Thread handle
+ tbl List of tables to process
+
+ DESCRIPTION
+ Perform end-end-of prepare fixup for list of tables, if any of the tables
+ is a merge-algorithm VIEW, recursively fix up its underlying tables as
+ well.
+
+*/
+
+static void fix_prepare_info_in_table_list(THD *thd, TABLE_LIST *tbl)
+{
+ for (; tbl; tbl= tbl->next_local)
+ {
+ if (tbl->on_expr && !tbl->prep_on_expr)
+ {
+ thd->check_and_register_item_tree(&tbl->prep_on_expr, &tbl->on_expr);
+ tbl->on_expr= tbl->on_expr->copy_andor_structure(thd);
+ }
+ if (tbl->is_view_or_derived() && tbl->is_merged_derived())
+ {
+ SELECT_LEX *sel= tbl->get_single_select();
+ fix_prepare_info_in_table_list(thd, sel->get_table_list());
+ }
+ }
+}
+
+
+/*
+ Save WHERE/HAVING/ON clauses and replace them with disposable copies
+
+ SYNOPSIS
+ st_select_lex::fix_prepare_information
+ thd thread handler
+ conds in/out pointer to WHERE condition to be met at execution
+ having_conds in/out pointer to HAVING condition to be met at execution
+
+ DESCRIPTION
+ The passed WHERE and HAVING are to be saved for the future executions.
+ This function saves it, and returns a copy which can be thrashed during
+ this execution of the statement. By saving/thrashing here we mean only
+ We also save the chain of ORDER::next in group_list, in case
+ the list is modified by remove_const().
+ AND/OR trees.
+ The function also calls fix_prepare_info_in_table_list that saves all
+ ON expressions.
+*/
+
+void st_select_lex::fix_prepare_information(THD *thd, Item **conds,
+ Item **having_conds)
+{
+ DBUG_ENTER("st_select_lex::fix_prepare_information");
+ if (!thd->stmt_arena->is_conventional() && first_execution)
+ {
+ first_execution= 0;
+ if (group_list.first)
+ {
+ if (!group_list_ptrs)
+ {
+ void *mem= thd->stmt_arena->alloc(sizeof(Group_list_ptrs));
+ group_list_ptrs= new (mem) Group_list_ptrs(thd->stmt_arena->mem_root);
+ }
+ group_list_ptrs->reserve(group_list.elements);
+ for (ORDER *order= group_list.first; order; order= order->next)
+ {
+ group_list_ptrs->push_back(order);
+ }
+ }
+ if (*conds)
+ {
+ thd->check_and_register_item_tree(&prep_where, conds);
+ *conds= where= prep_where->copy_andor_structure(thd);
+ }
+ if (*having_conds)
+ {
+ thd->check_and_register_item_tree(&prep_having, having_conds);
+ *having_conds= having= prep_having->copy_andor_structure(thd);
+ }
+ fix_prepare_info_in_table_list(thd, table_list.first);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ There are st_select_lex::add_table_to_list &
+ st_select_lex::set_lock_for_tables are in sql_parse.cc
+
+ st_select_lex::print is in sql_select.cc
+
+ st_select_lex_unit::prepare, st_select_lex_unit::exec,
+ st_select_lex_unit::cleanup, st_select_lex_unit::reinit_exec_mechanism,
+ st_select_lex_unit::change_result
+ are in sql_union.cc
+*/
+
+/*
+ Sets the kind of hints to be added by the calls to add_index_hint().
+
+ SYNOPSIS
+ set_index_hint_type()
+ type_arg The kind of hints to be added from now on.
+ clause The clause to use for hints to be added from now on.
+
+ DESCRIPTION
+ Used in filling up the tagged hints list.
+ This list is filled by first setting the kind of the hint as a
+ context variable and then adding hints of the current kind.
+ Then the context variable index_hint_type can be reset to the
+ next hint type.
+*/
+void st_select_lex::set_index_hint_type(enum index_hint_type type_arg,
+ index_clause_map clause)
+{
+ current_index_hint_type= type_arg;
+ current_index_hint_clause= clause;
+}
+
+
+/*
+ Makes an array to store index usage hints (ADD/FORCE/IGNORE INDEX).
+
+ SYNOPSIS
+ alloc_index_hints()
+ thd current thread.
+*/
+
+void st_select_lex::alloc_index_hints (THD *thd)
+{
+ index_hints= new (thd->mem_root) List<Index_hint>();
+}
+
+
+
+/*
+ adds an element to the array storing index usage hints
+ (ADD/FORCE/IGNORE INDEX).
+
+ SYNOPSIS
+ add_index_hint()
+ thd current thread.
+ str name of the index.
+ length number of characters in str.
+
+ RETURN VALUE
+ 0 on success, non-zero otherwise
+*/
+bool st_select_lex::add_index_hint (THD *thd, char *str, uint length)
+{
+ return index_hints->push_front (new (thd->mem_root)
+ Index_hint(current_index_hint_type,
+ current_index_hint_clause,
+ str, length));
+}
+
+
+/**
+ Optimize all subqueries that have not been flattened into semi-joins.
+
+ @details
+ This functionality is a method of SELECT_LEX instead of JOIN because
+ SQL statements as DELETE/UPDATE do not have a corresponding JOIN object.
+
+ @see JOIN::optimize_unflattened_subqueries
+
+ @param const_only Restrict subquery optimization to constant subqueries
+
+ @return Operation status
+ @retval FALSE success.
+ @retval TRUE error occurred.
+*/
+
+bool st_select_lex::optimize_unflattened_subqueries(bool const_only)
+{
+ for (SELECT_LEX_UNIT *un= first_inner_unit(); un; un= un->next_unit())
+ {
+ Item_subselect *subquery_predicate= un->item;
+
+ if (subquery_predicate)
+ {
+ if (subquery_predicate->substype() == Item_subselect::IN_SUBS)
+ {
+ Item_in_subselect *in_subs= (Item_in_subselect*) subquery_predicate;
+ if (in_subs->is_jtbm_merged)
+ continue;
+ }
+
+ if (const_only && !subquery_predicate->const_item())
+ {
+ /* Skip non-constant subqueries if the caller asked so. */
+ continue;
+ }
+
+ bool empty_union_result= true;
+ bool is_correlated_unit= false;
+ /*
+ If the subquery is a UNION, optimize all the subqueries in the UNION. If
+ there is no UNION, then the loop will execute once for the subquery.
+ */
+ for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select())
+ {
+ JOIN *inner_join= sl->join;
+ if (!inner_join)
+ continue;
+ SELECT_LEX *save_select= un->thd->lex->current_select;
+ ulonglong save_options;
+ int res;
+ /* We need only 1 row to determine existence */
+ un->set_limit(un->global_parameters);
+ un->thd->lex->current_select= sl;
+ save_options= inner_join->select_options;
+ if (options & SELECT_DESCRIBE)
+ {
+ /* Optimize the subquery in the context of EXPLAIN. */
+ sl->set_explain_type(FALSE);
+ sl->options|= SELECT_DESCRIBE;
+ inner_join->select_options|= SELECT_DESCRIBE;
+ }
+ res= inner_join->optimize();
+ sl->update_correlated_cache();
+ is_correlated_unit|= sl->is_correlated;
+ inner_join->select_options= save_options;
+ un->thd->lex->current_select= save_select;
+
+ Explain_query *eq;
+ if ((eq= inner_join->thd->lex->explain))
+ {
+ Explain_select *expl_sel;
+ if ((expl_sel= eq->get_select(inner_join->select_lex->select_number)))
+ {
+ sl->set_explain_type(TRUE);
+ expl_sel->select_type= sl->type;
+ }
+ }
+
+ if (empty_union_result)
+ {
+ /*
+ If at least one subquery in a union is non-empty, the UNION result
+ is non-empty. If there is no UNION, the only subquery is non-empy.
+ */
+ empty_union_result= inner_join->empty_result();
+ }
+ if (res)
+ return TRUE;
+ }
+ if (empty_union_result)
+ subquery_predicate->no_rows_in_result();
+ if (!is_correlated_unit)
+ un->uncacheable&= ~UNCACHEABLE_DEPENDENT;
+ subquery_predicate->is_correlated= is_correlated_unit;
+ }
+ }
+ return FALSE;
+}
+
+
+
+/**
+ @brief Process all derived tables/views of the SELECT.
+
+ @param lex LEX of this thread
+ @param phase phases to run derived tables/views through
+
+ @details
+ This function runs specified 'phases' on all tables from the
+ table_list of this select.
+
+ @return FALSE ok.
+ @return TRUE an error occur.
+*/
+
+bool st_select_lex::handle_derived(LEX *lex, uint phases)
+{
+ for (TABLE_LIST *cursor= (TABLE_LIST*) table_list.first;
+ cursor;
+ cursor= cursor->next_local)
+ {
+ if (cursor->is_view_or_derived() && cursor->handle_derived(lex, phases))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ @brief
+ Returns first unoccupied table map and table number
+
+ @param map [out] return found map
+ @param tablenr [out] return found tablenr
+
+ @details
+ Returns first unoccupied table map and table number in this select.
+ Map and table are returned in *'map' and *'tablenr' accordingly.
+
+ @retrun TRUE no free table map/table number
+ @return FALSE found free table map/table number
+*/
+
+bool st_select_lex::get_free_table_map(table_map *map, uint *tablenr)
+{
+ *map= 0;
+ *tablenr= 0;
+ TABLE_LIST *tl;
+ List_iterator<TABLE_LIST> ti(leaf_tables);
+ while ((tl= ti++))
+ {
+ if (tl->table->map > *map)
+ *map= tl->table->map;
+ if (tl->table->tablenr > *tablenr)
+ *tablenr= tl->table->tablenr;
+ }
+ (*map)<<= 1;
+ (*tablenr)++;
+ if (*tablenr >= MAX_TABLES)
+ return TRUE;
+ return FALSE;
+}
+
+
+/**
+ @brief
+ Append given table to the leaf_tables list.
+
+ @param link Offset to which list in table structure to use
+ @param table Table to append
+
+ @details
+ Append given 'table' to the leaf_tables list using the 'link' offset.
+ If the 'table' is linked with other tables through next_leaf/next_local
+ chains then whole list will be appended.
+*/
+
+void st_select_lex::append_table_to_list(TABLE_LIST *TABLE_LIST::*link,
+ TABLE_LIST *table)
+{
+ TABLE_LIST *tl;
+ for (tl= leaf_tables.head(); tl->*link; tl= tl->*link) ;
+ tl->*link= table;
+}
+
+
+/*
+ @brief
+ Replace given table from the leaf_tables list for a list of tables
+
+ @param table Table to replace
+ @param list List to substititute the table for
+
+ @details
+ Replace 'table' from the leaf_tables list for a list of tables 'tbl_list'.
+*/
+
+void st_select_lex::replace_leaf_table(TABLE_LIST *table, List<TABLE_LIST> &tbl_list)
+{
+ TABLE_LIST *tl;
+ List_iterator<TABLE_LIST> ti(leaf_tables);
+ while ((tl= ti++))
+ {
+ if (tl == table)
+ {
+ ti.replace(tbl_list);
+ break;
+ }
+ }
+}
+
+
+/**
+ @brief
+ Assigns new table maps to tables in the leaf_tables list
+
+ @param derived Derived table to take initial table map from
+ @param map table map to begin with
+ @param tablenr table number to begin with
+ @param parent_lex new parent select_lex
+
+ @details
+ Assign new table maps/table numbers to all tables in the leaf_tables list.
+ 'map'/'tablenr' are used for the first table and shifted to left/
+ increased for each consequent table in the leaf_tables list.
+ If the 'derived' table is given then it's table map/number is used for the
+ first table in the list and 'map'/'tablenr' are used for the second and
+ all consequent tables.
+ The 'parent_lex' is set as the new parent select_lex for all tables in the
+ list.
+*/
+
+void st_select_lex::remap_tables(TABLE_LIST *derived, table_map map,
+ uint tablenr, SELECT_LEX *parent_lex)
+{
+ bool first_table= TRUE;
+ TABLE_LIST *tl;
+ table_map first_map;
+ uint first_tablenr;
+
+ if (derived && derived->table)
+ {
+ first_map= derived->table->map;
+ first_tablenr= derived->table->tablenr;
+ }
+ else
+ {
+ first_map= map;
+ map<<= 1;
+ first_tablenr= tablenr++;
+ }
+ /*
+ Assign table bit/table number.
+ To the first table of the subselect the table bit/tablenr of the
+ derived table is assigned. The rest of tables are getting bits
+ sequentially, starting from the provided table map/tablenr.
+ */
+ List_iterator<TABLE_LIST> ti(leaf_tables);
+ while ((tl= ti++))
+ {
+ if (first_table)
+ {
+ first_table= FALSE;
+ tl->table->set_table_map(first_map, first_tablenr);
+ }
+ else
+ {
+ tl->table->set_table_map(map, tablenr);
+ tablenr++;
+ map<<= 1;
+ }
+ SELECT_LEX *old_sl= tl->select_lex;
+ tl->select_lex= parent_lex;
+ for(TABLE_LIST *emb= tl->embedding;
+ emb && emb->select_lex == old_sl;
+ emb= emb->embedding)
+ emb->select_lex= parent_lex;
+ }
+}
+
+/**
+ @brief
+ Merge a subquery into this select.
+
+ @param derived derived table of the subquery to be merged
+ @param subq_select select_lex of the subquery
+ @param map table map for assigning to merged tables from subquery
+ @param table_no table number for assigning to merged tables from subquery
+
+ @details
+ This function merges a subquery into its parent select. In short the
+ merge operation appends the subquery FROM table list to the parent's
+ FROM table list. In more details:
+ .) the top_join_list of the subquery is wrapped into a join_nest
+ and attached to 'derived'
+ .) subquery's leaf_tables list is merged with the leaf_tables
+ list of this select_lex
+ .) the table maps and table numbers of the tables merged from
+ the subquery are adjusted to reflect their new binding to
+ this select
+
+ @return TRUE an error occur
+ @return FALSE ok
+*/
+
+bool SELECT_LEX::merge_subquery(THD *thd, TABLE_LIST *derived,
+ SELECT_LEX *subq_select,
+ uint table_no, table_map map)
+{
+ derived->wrap_into_nested_join(subq_select->top_join_list);
+
+ ftfunc_list->concat(subq_select->ftfunc_list);
+ if (join ||
+ thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI)
+ {
+ List_iterator_fast<Item_in_subselect> li(subq_select->sj_subselects);
+ Item_in_subselect *in_subq;
+ while ((in_subq= li++))
+ {
+ sj_subselects.push_back(in_subq);
+ if (in_subq->emb_on_expr_nest == NO_JOIN_NEST)
+ in_subq->emb_on_expr_nest= derived;
+ }
+ }
+
+ /* Walk through child's tables and adjust table map, tablenr,
+ * parent_lex */
+ subq_select->remap_tables(derived, map, table_no, this);
+ subq_select->merged_into= this;
+
+ replace_leaf_table(derived, subq_select->leaf_tables);
+
+ return FALSE;
+}
+
+
+/**
+ @brief
+ Mark tables from the leaf_tables list as belong to a derived table.
+
+ @param derived tables will be marked as belonging to this derived
+
+ @details
+ Run through the leaf_list and mark all tables as belonging to the 'derived'.
+*/
+
+void SELECT_LEX::mark_as_belong_to_derived(TABLE_LIST *derived)
+{
+ /* Mark tables as belonging to this DT */
+ TABLE_LIST *tl;
+ List_iterator<TABLE_LIST> ti(leaf_tables);
+ while ((tl= ti++))
+ tl->belong_to_derived= derived;
+}
+
+
+/**
+ @brief
+ Update used_tables cache for this select
+
+ @details
+ This function updates used_tables cache of ON expressions of all tables
+ in the leaf_tables list and of the conds expression (if any).
+*/
+
+void SELECT_LEX::update_used_tables()
+{
+ TABLE_LIST *tl;
+ List_iterator<TABLE_LIST> ti(leaf_tables);
+
+ while ((tl= ti++))
+ {
+ if (tl->table && !tl->is_view_or_derived())
+ {
+ TABLE_LIST *embedding= tl->embedding;
+ for (embedding= tl->embedding; embedding; embedding=embedding->embedding)
+ {
+ if (embedding->is_view_or_derived())
+ {
+ DBUG_ASSERT(embedding->is_merged_derived());
+ TABLE *tab= tl->table;
+ tab->covering_keys= tab->s->keys_for_keyread;
+ tab->covering_keys.intersect(tab->keys_in_use_for_query);
+ tab->merge_keys.clear_all();
+ bitmap_clear_all(tab->read_set);
+ bitmap_clear_all(tab->vcol_set);
+ break;
+ }
+ }
+ }
+ }
+
+ ti.rewind();
+ while ((tl= ti++))
+ {
+ TABLE_LIST *embedding= tl;
+ do
+ {
+ bool maybe_null;
+ if ((maybe_null= MY_TEST(embedding->outer_join)))
+ {
+ tl->table->maybe_null= maybe_null;
+ break;
+ }
+ }
+ while ((embedding= embedding->embedding));
+ if (tl->on_expr)
+ {
+ tl->on_expr->update_used_tables();
+ tl->on_expr->walk(&Item::eval_not_null_tables, 0, NULL);
+ }
+ embedding= tl->embedding;
+ while (embedding)
+ {
+ if (embedding->on_expr &&
+ embedding->nested_join->join_list.head() == tl)
+ {
+ embedding->on_expr->update_used_tables();
+ embedding->on_expr->walk(&Item::eval_not_null_tables, 0, NULL);
+ }
+ tl= embedding;
+ embedding= tl->embedding;
+ }
+ }
+
+ if (join->conds)
+ {
+ join->conds->update_used_tables();
+ join->conds->walk(&Item::eval_not_null_tables, 0, NULL);
+ }
+ if (join->having)
+ {
+ join->having->update_used_tables();
+ }
+
+ Item *item;
+ List_iterator_fast<Item> it(join->fields_list);
+ while ((item= it++))
+ {
+ item->update_used_tables();
+ }
+ Item_outer_ref *ref;
+ List_iterator_fast<Item_outer_ref> ref_it(inner_refs_list);
+ while ((ref= ref_it++))
+ {
+ item= ref->outer_ref;
+ item->update_used_tables();
+ }
+ for (ORDER *order= group_list.first; order; order= order->next)
+ (*order->item)->update_used_tables();
+ if (!master_unit()->is_union() || master_unit()->global_parameters != this)
+ {
+ for (ORDER *order= order_list.first; order; order= order->next)
+ (*order->item)->update_used_tables();
+ }
+ join->result->update_used_tables();
+}
+
+
+/**
+ @brief
+ Update is_correlated cache for this select
+
+ @details
+*/
+
+void st_select_lex::update_correlated_cache()
+{
+ TABLE_LIST *tl;
+ List_iterator<TABLE_LIST> ti(leaf_tables);
+
+ is_correlated= false;
+
+ while ((tl= ti++))
+ {
+ if (tl->on_expr)
+ is_correlated|= MY_TEST(tl->on_expr->used_tables() & OUTER_REF_TABLE_BIT);
+ for (TABLE_LIST *embedding= tl->embedding ; embedding ;
+ embedding= embedding->embedding)
+ {
+ if (embedding->on_expr)
+ is_correlated|= MY_TEST(embedding->on_expr->used_tables() &
+ OUTER_REF_TABLE_BIT);
+ }
+ }
+
+ if (join->conds)
+ is_correlated|= MY_TEST(join->conds->used_tables() & OUTER_REF_TABLE_BIT);
+
+ if (join->having)
+ is_correlated|= MY_TEST(join->having->used_tables() & OUTER_REF_TABLE_BIT);
+
+ if (join->tmp_having)
+ is_correlated|= MY_TEST(join->tmp_having->used_tables() &
+ OUTER_REF_TABLE_BIT);
+
+ Item *item;
+ List_iterator_fast<Item> it(join->fields_list);
+ while ((item= it++))
+ is_correlated|= MY_TEST(item->used_tables() & OUTER_REF_TABLE_BIT);
+
+ for (ORDER *order= group_list.first; order; order= order->next)
+ is_correlated|= MY_TEST((*order->item)->used_tables() &
+ OUTER_REF_TABLE_BIT);
+
+ if (!master_unit()->is_union())
+ {
+ for (ORDER *order= order_list.first; order; order= order->next)
+ is_correlated|= MY_TEST((*order->item)->used_tables() &
+ OUTER_REF_TABLE_BIT);
+ }
+
+ if (!is_correlated)
+ uncacheable&= ~UNCACHEABLE_DEPENDENT;
+}
+
+
+/**
+ Set the EXPLAIN type for this subquery.
+
+ @param on_the_fly TRUE<=> We're running a SHOW EXPLAIN command, so we must
+ not change any variables
+*/
+
+void st_select_lex::set_explain_type(bool on_the_fly)
+{
+ bool is_primary= FALSE;
+ if (next_select())
+ is_primary= TRUE;
+
+ if (!is_primary && first_inner_unit())
+ {
+ /*
+ If there is at least one materialized derived|view then it's a PRIMARY select.
+ Otherwise, all derived tables/views were merged and this select is a SIMPLE one.
+ */
+ for (SELECT_LEX_UNIT *un= first_inner_unit(); un; un= un->next_unit())
+ {
+ if ((!un->derived || un->derived->is_materialized_derived()))
+ {
+ is_primary= TRUE;
+ break;
+ }
+ }
+ }
+
+ if (on_the_fly && !is_primary && have_merged_subqueries)
+ is_primary= TRUE;
+
+ SELECT_LEX *first= master_unit()->first_select();
+ /* drop UNCACHEABLE_EXPLAIN, because it is for internal usage only */
+ uint8 is_uncacheable= (uncacheable & ~UNCACHEABLE_EXPLAIN);
+
+ bool using_materialization= FALSE;
+ Item_subselect *parent_item;
+ if ((parent_item= master_unit()->item) &&
+ parent_item->substype() == Item_subselect::IN_SUBS)
+ {
+ Item_in_subselect *in_subs= (Item_in_subselect*)parent_item;
+ /*
+ Surprisingly, in_subs->is_set_strategy() can return FALSE here,
+ even for the last invocation of this function for the select.
+ */
+ if (in_subs->test_strategy(SUBS_MATERIALIZATION))
+ using_materialization= TRUE;
+ }
+
+ if (&master_unit()->thd->lex->select_lex == this)
+ {
+ type= is_primary ? "PRIMARY" : "SIMPLE";
+ }
+ else
+ {
+ if (this == first)
+ {
+ /* If we're a direct child of a UNION, we're the first sibling there */
+ if (linkage == DERIVED_TABLE_TYPE)
+ type= "DERIVED";
+ else if (using_materialization)
+ type= "MATERIALIZED";
+ else
+ {
+ if (is_uncacheable & UNCACHEABLE_DEPENDENT)
+ type= "DEPENDENT SUBQUERY";
+ else
+ {
+ type= is_uncacheable? "UNCACHEABLE SUBQUERY" :
+ "SUBQUERY";
+ }
+ }
+ }
+ else
+ {
+ /* This a non-first sibling in UNION */
+ if (is_uncacheable & UNCACHEABLE_DEPENDENT)
+ type= "DEPENDENT UNION";
+ else if (using_materialization)
+ type= "MATERIALIZED UNION";
+ else
+ {
+ type= is_uncacheable ? "UNCACHEABLE UNION": "UNION";
+ if (this == master_unit()->fake_select_lex)
+ type= "UNION RESULT";
+
+ }
+ }
+ }
+
+ if (!on_the_fly)
+ options|= SELECT_DESCRIBE;
+}
+
+
+/**
+ @brief
+ Increase estimated number of records for a derived table/view
+
+ @param records number of records to increase estimate by
+
+ @details
+ This function increases estimated number of records by the 'records'
+ for the derived table to which this select belongs to.
+*/
+
+void SELECT_LEX::increase_derived_records(ha_rows records)
+{
+ SELECT_LEX_UNIT *unit= master_unit();
+ DBUG_ASSERT(unit->derived);
+
+ select_union *result= (select_union*)unit->result;
+ result->records+= records;
+}
+
+
+/**
+ @brief
+ Mark select's derived table as a const one.
+
+ @param empty Whether select has an empty result set
+
+ @details
+ Mark derived table/view of this select as a constant one (to
+ materialize it at the optimization phase) unless this select belongs to a
+ union. Estimated number of rows is incremented if this select has non empty
+ result set.
+*/
+
+void SELECT_LEX::mark_const_derived(bool empty)
+{
+ TABLE_LIST *derived= master_unit()->derived;
+ /* join == NULL in DELETE ... RETURNING */
+ if (!(join && join->thd->lex->describe) && derived)
+ {
+ if (!empty)
+ increase_derived_records(1);
+ if (!master_unit()->is_union() && !derived->is_merged_derived())
+ derived->fill_me= TRUE;
+ }
+}
+
+
+bool st_select_lex::save_leaf_tables(THD *thd)
+{
+ Query_arena *arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ List_iterator_fast<TABLE_LIST> li(leaf_tables);
+ TABLE_LIST *table;
+ while ((table= li++))
+ {
+ if (leaf_tables_exec.push_back(table))
+ return 1;
+ table->tablenr_exec= table->get_tablenr();
+ table->map_exec= table->get_map();
+ if (join && (join->select_options & SELECT_DESCRIBE))
+ table->maybe_null_exec= 0;
+ else
+ table->maybe_null_exec= table->table? table->table->maybe_null: 0;
+ }
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+
+ return 0;
+}
+
+
+bool LEX::save_prep_leaf_tables()
+{
+ if (!thd->save_prep_leaf_list)
+ return FALSE;
+
+ Query_arena *arena= thd->stmt_arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ //It is used for DETETE/UPDATE so top level has only one SELECT
+ DBUG_ASSERT(select_lex.next_select() == NULL);
+ bool res= select_lex.save_prep_leaf_tables(thd);
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+
+ if (res)
+ return TRUE;
+
+ thd->save_prep_leaf_list= FALSE;
+ return FALSE;
+}
+
+
+bool st_select_lex::save_prep_leaf_tables(THD *thd)
+{
+ List_iterator_fast<TABLE_LIST> li(leaf_tables);
+ TABLE_LIST *table;
+ while ((table= li++))
+ {
+ if (leaf_tables_prep.push_back(table))
+ return TRUE;
+ }
+ is_prep_leaf_list_saved= TRUE;
+ for (SELECT_LEX_UNIT *u= first_inner_unit(); u; u= u->next_unit())
+ {
+ for (SELECT_LEX *sl= u->first_select(); sl; sl= sl->next_select())
+ {
+ if (sl->save_prep_leaf_tables(thd))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/*
+ Return true if this select_lex has been converted into a semi-join nest
+ within 'ancestor'.
+
+ We need a loop to check this because there could be several nested
+ subselects, like
+
+ SELECT ... FROM grand_parent
+ WHERE expr1 IN (SELECT ... FROM parent
+ WHERE expr2 IN ( SELECT ... FROM child)
+
+ which were converted into:
+
+ SELECT ...
+ FROM grand_parent SEMI_JOIN (parent JOIN child)
+ WHERE
+ expr1 AND expr2
+
+ In this case, both parent and child selects were merged into the parent.
+*/
+
+bool st_select_lex::is_merged_child_of(st_select_lex *ancestor)
+{
+ bool all_merged= TRUE;
+ for (SELECT_LEX *sl= this; sl && sl!=ancestor;
+ sl=sl->outer_select())
+ {
+ Item *subs= sl->master_unit()->item;
+ if (subs && subs->type() == Item::SUBSELECT_ITEM &&
+ ((Item_subselect*)subs)->substype() == Item_subselect::IN_SUBS &&
+ ((Item_in_subselect*)subs)->test_strategy(SUBS_SEMI_JOIN))
+ {
+ continue;
+ }
+ all_merged= FALSE;
+ break;
+ }
+ return all_merged;
+}
+
+/*
+ This is used by SHOW EXPLAIN. It assuses query plan has been already
+ collected into QPF structures and we only need to print it out.
+*/
+
+int LEX::print_explain(select_result_sink *output, uint8 explain_flags,
+ bool *printed_anything)
+{
+ int res;
+ if (explain && explain->have_query_plan())
+ {
+ res= explain->print_explain(output, explain_flags);
+ *printed_anything= true;
+ }
+ else
+ {
+ res= 0;
+ *printed_anything= false;
+ }
+ return res;
+}
+
+
+/*
+ Save explain structures of a UNION. The only variable member is whether the
+ union has "Using filesort".
+
+ There is also save_union_explain_part2() function, which is called before we read
+ UNION's output.
+
+ The reason for it is examples like this:
+
+ SELECT col1 FROM t1 UNION SELECT col2 FROM t2 ORDER BY (select ... from t3 ...)
+
+ Here, the (select ... from t3 ...) subquery must be a child of UNION's
+ st_select_lex. However, it is not connected as child until a very late
+ stage in execution.
+*/
+
+int st_select_lex_unit::save_union_explain(Explain_query *output)
+{
+ SELECT_LEX *first= first_select();
+ Explain_union *eu= new (output->mem_root) Explain_union;
+
+ for (SELECT_LEX *sl= first; sl; sl= sl->next_select())
+ eu->add_select(sl->select_number);
+
+ eu->fake_select_type= "UNION RESULT";
+ eu->using_filesort= MY_TEST(global_parameters->order_list.first);
+
+ // Save the UNION node
+ output->add_node(eu);
+
+ if (eu->get_select_id() == 1)
+ output->query_plan_ready();
+
+ return 0;
+}
+
+
+/*
+ @see st_select_lex_unit::save_union_explain
+*/
+
+int st_select_lex_unit::save_union_explain_part2(Explain_query *output)
+{
+ Explain_union *eu= output->get_union(first_select()->select_number);
+ if (fake_select_lex)
+ {
+ for (SELECT_LEX_UNIT *unit= fake_select_lex->first_inner_unit();
+ unit; unit= unit->next_unit())
+ {
+ if (!(unit->item && unit->item->eliminated))
+ {
+ eu->add_child(unit->first_select()->select_number);
+ }
+ }
+ }
+ return 0;
+}
+
+
+/**
+ A routine used by the parser to decide whether we are specifying a full
+ partitioning or if only partitions to add or to split.
+
+ @note This needs to be outside of WITH_PARTITION_STORAGE_ENGINE since it
+ is used from the sql parser that doesn't have any ifdef's
+
+ @retval TRUE Yes, it is part of a management partition command
+ @retval FALSE No, not a management partition command
+*/
+
+bool LEX::is_partition_management() const
+{
+ return (sql_command == SQLCOM_ALTER_TABLE &&
+ (alter_info.flags == Alter_info::ALTER_ADD_PARTITION ||
+ alter_info.flags == Alter_info::ALTER_REORGANIZE_PARTITION));
+}
+
+#ifdef MYSQL_SERVER
+uint binlog_unsafe_map[256];
+
+#define UNSAFE(a, b, c) \
+ { \
+ DBUG_PRINT("unsafe_mixed_statement", ("SETTING BASE VALUES: %s, %s, %02X\n", \
+ LEX::stmt_accessed_table_string(a), \
+ LEX::stmt_accessed_table_string(b), \
+ c)); \
+ unsafe_mixed_statement(a, b, c); \
+ }
+
+/*
+ Sets the combination given by "a" and "b" and automatically combinations
+ given by other types of access, i.e. 2^(8 - 2), as unsafe.
+
+ It may happen a colision when automatically defining a combination as unsafe.
+ For that reason, a combination has its unsafe condition redefined only when
+ the new_condition is greater then the old. For instance,
+
+ . (BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY) is never overwritten by
+ . (BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF).
+*/
+void unsafe_mixed_statement(LEX::enum_stmt_accessed_table a,
+ LEX::enum_stmt_accessed_table b, uint condition)
+{
+ int type= 0;
+ int index= (1U << a) | (1U << b);
+
+
+ for (type= 0; type < 256; type++)
+ {
+ if ((type & index) == index)
+ {
+ binlog_unsafe_map[type] |= condition;
+ }
+ }
+}
+/*
+ The BINLOG_* AND TRX_CACHE_* values can be combined by using '&' or '|',
+ which means that both conditions need to be satisfied or any of them is
+ enough. For example,
+
+ . BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY means that the statment is
+ unsafe when the option is on and trx-cache is not empty;
+
+ . BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF means the statement is unsafe
+ in all cases.
+
+ . TRX_CACHE_EMPTY | TRX_CACHE_NOT_EMPTY means the statement is unsafe
+ in all cases. Similar as above.
+*/
+void binlog_unsafe_map_init()
+{
+ memset((void*) binlog_unsafe_map, 0, sizeof(uint) * 256);
+
+ /*
+ Classify a statement as unsafe when there is a mixed statement and an
+ on-going transaction at any point of the execution if:
+
+ 1. The mixed statement is about to update a transactional table and
+ a non-transactional table.
+
+ 2. The mixed statement is about to update a transactional table and
+ read from a non-transactional table.
+
+ 3. The mixed statement is about to update a non-transactional table
+ and temporary transactional table.
+
+ 4. The mixed statement is about to update a temporary transactional
+ table and read from a non-transactional table.
+
+ 5. The mixed statement is about to update a transactional table and
+ a temporary non-transactional table.
+
+ 6. The mixed statement is about to update a transactional table and
+ read from a temporary non-transactional table.
+
+ 7. The mixed statement is about to update a temporary transactional
+ table and temporary non-transactional table.
+
+ 8. The mixed statement is about to update a temporary transactional
+ table and read from a temporary non-transactional table.
+
+ After updating a transactional table if:
+
+ 9. The mixed statement is about to update a non-transactional table
+ and read from a transactional table.
+
+ 10. The mixed statement is about to update a non-transactional table
+ and read from a temporary transactional table.
+
+ 11. The mixed statement is about to update a temporary non-transactional
+ table and read from a transactional table.
+
+ 12. The mixed statement is about to update a temporary non-transactional
+ table and read from a temporary transactional table.
+
+ 13. The mixed statement is about to update a temporary non-transactional
+ table and read from a non-transactional table.
+
+ The reason for this is that locks acquired may not protected a concurrent
+ transaction of interfering in the current execution and by consequence in
+ the result.
+ */
+ /* Case 1. */
+ UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_WRITES_NON_TRANS_TABLE,
+ BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF);
+ /* Case 2. */
+ UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_READS_NON_TRANS_TABLE,
+ BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF);
+ /* Case 3. */
+ UNSAFE(LEX::STMT_WRITES_NON_TRANS_TABLE, LEX::STMT_WRITES_TEMP_TRANS_TABLE,
+ BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF);
+ /* Case 4. */
+ UNSAFE(LEX::STMT_WRITES_TEMP_TRANS_TABLE, LEX::STMT_READS_NON_TRANS_TABLE,
+ BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF);
+ /* Case 5. */
+ UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE,
+ BINLOG_DIRECT_ON);
+ /* Case 6. */
+ UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_READS_TEMP_NON_TRANS_TABLE,
+ BINLOG_DIRECT_ON);
+ /* Case 7. */
+ UNSAFE(LEX::STMT_WRITES_TEMP_TRANS_TABLE, LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE,
+ BINLOG_DIRECT_ON);
+ /* Case 8. */
+ UNSAFE(LEX::STMT_WRITES_TEMP_TRANS_TABLE, LEX::STMT_READS_TEMP_NON_TRANS_TABLE,
+ BINLOG_DIRECT_ON);
+ /* Case 9. */
+ UNSAFE(LEX::STMT_WRITES_NON_TRANS_TABLE, LEX::STMT_READS_TRANS_TABLE,
+ (BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF) & TRX_CACHE_NOT_EMPTY);
+ /* Case 10 */
+ UNSAFE(LEX::STMT_WRITES_NON_TRANS_TABLE, LEX::STMT_READS_TEMP_TRANS_TABLE,
+ (BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF) & TRX_CACHE_NOT_EMPTY);
+ /* Case 11. */
+ UNSAFE(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, LEX::STMT_READS_TRANS_TABLE,
+ BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY);
+ /* Case 12. */
+ UNSAFE(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, LEX::STMT_READS_TEMP_TRANS_TABLE,
+ BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY);
+ /* Case 13. */
+ UNSAFE(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, LEX::STMT_READS_NON_TRANS_TABLE,
+ BINLOG_DIRECT_OFF & TRX_CACHE_NOT_EMPTY);
+}
+#endif
+
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
new file mode 100644
index 00000000000..b17f0f4ec63
--- /dev/null
+++ b/sql/sql_lex.h
@@ -0,0 +1,2967 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/**
+ @defgroup Semantic_Analysis Semantic Analysis
+*/
+
+#ifndef SQL_LEX_INCLUDED
+#define SQL_LEX_INCLUDED
+
+#include "violite.h" /* SSL_type */
+#include "sql_trigger.h"
+#include "item.h" /* From item_subselect.h: subselect_union_engine */
+#include "thr_lock.h" /* thr_lock_type, TL_UNLOCK */
+#include "mem_root_array.h"
+#include "sql_cmd.h"
+#include "sql_alter.h" // Alter_info
+
+/* YACC and LEX Definitions */
+
+/* These may not be declared yet */
+class Table_ident;
+class sql_exchange;
+class LEX_COLUMN;
+class sp_head;
+class sp_name;
+class sp_instr;
+class sp_pcontext;
+class st_alter_tablespace;
+class partition_info;
+class Event_parse_data;
+class set_var_base;
+class sys_var;
+class Item_func_match;
+class File_parser;
+class Key_part_spec;
+struct sql_digest_state;
+
+#ifdef MYSQL_SERVER
+/*
+ There are 8 different type of table access so there is no more than
+ combinations 2^8 = 256:
+
+ . STMT_READS_TRANS_TABLE
+
+ . STMT_READS_NON_TRANS_TABLE
+
+ . STMT_READS_TEMP_TRANS_TABLE
+
+ . STMT_READS_TEMP_NON_TRANS_TABLE
+
+ . STMT_WRITES_TRANS_TABLE
+
+ . STMT_WRITES_NON_TRANS_TABLE
+
+ . STMT_WRITES_TEMP_TRANS_TABLE
+
+ . STMT_WRITES_TEMP_NON_TRANS_TABLE
+
+ The unsafe conditions for each combination is represented within a byte
+ and stores the status of the option --binlog-direct-non-trans-updates,
+ whether the trx-cache is empty or not, and whether the isolation level
+ is lower than ISO_REPEATABLE_READ:
+
+ . option (OFF/ON)
+ . trx-cache (empty/not empty)
+ . isolation (>= ISO_REPEATABLE_READ / < ISO_REPEATABLE_READ)
+
+ bits 0 : . OFF, . empty, . >= ISO_REPEATABLE_READ
+ bits 1 : . OFF, . empty, . < ISO_REPEATABLE_READ
+ bits 2 : . OFF, . not empty, . >= ISO_REPEATABLE_READ
+ bits 3 : . OFF, . not empty, . < ISO_REPEATABLE_READ
+ bits 4 : . ON, . empty, . >= ISO_REPEATABLE_READ
+ bits 5 : . ON, . empty, . < ISO_REPEATABLE_READ
+ bits 6 : . ON, . not empty, . >= ISO_REPEATABLE_READ
+ bits 7 : . ON, . not empty, . < ISO_REPEATABLE_READ
+*/
+extern uint binlog_unsafe_map[256];
+/*
+ Initializes the array with unsafe combinations and its respective
+ conditions.
+*/
+void binlog_unsafe_map_init();
+#endif
+
+/**
+ used by the parser to store internal variable name
+*/
+struct sys_var_with_base
+{
+ sys_var *var;
+ LEX_STRING base_name;
+};
+
+struct LEX_TYPE
+{
+ enum enum_field_types type;
+ char *length, *dec;
+ CHARSET_INFO *charset;
+ void set(int t, char *l, char *d, CHARSET_INFO *cs)
+ { type= (enum_field_types)t; length= l; dec= d; charset= cs; }
+};
+
+#ifdef MYSQL_SERVER
+/*
+ The following hack is needed because mysql_yacc.cc does not define
+ YYSTYPE before including this file
+*/
+#ifdef MYSQL_YACC
+#define LEX_YYSTYPE void *
+#else
+#include "lex_symbol.h"
+#if MYSQL_LEX
+#include "item_func.h" /* Cast_target used in sql_yacc.h */
+#include "sql_get_diagnostics.h" /* Types used in sql_yacc.h */
+#include "sql_yacc.h"
+#define LEX_YYSTYPE YYSTYPE *
+#else
+#define LEX_YYSTYPE void *
+#endif
+#endif
+#endif
+
+// describe/explain types
+#define DESCRIBE_NORMAL 1
+#define DESCRIBE_EXTENDED 2
+/*
+ This is not within #ifdef because we want "EXPLAIN PARTITIONS ..." to produce
+ additional "partitions" column even if partitioning is not compiled in.
+*/
+#define DESCRIBE_PARTITIONS 4
+
+#ifdef MYSQL_SERVER
+
+enum enum_sp_suid_behaviour
+{
+ SP_IS_DEFAULT_SUID= 0,
+ SP_IS_NOT_SUID,
+ SP_IS_SUID
+};
+
+enum enum_sp_data_access
+{
+ SP_DEFAULT_ACCESS= 0,
+ SP_CONTAINS_SQL,
+ SP_NO_SQL,
+ SP_READS_SQL_DATA,
+ SP_MODIFIES_SQL_DATA
+};
+
+const LEX_STRING sp_data_access_name[]=
+{
+ { C_STRING_WITH_LEN("") },
+ { C_STRING_WITH_LEN("CONTAINS SQL") },
+ { C_STRING_WITH_LEN("NO SQL") },
+ { C_STRING_WITH_LEN("READS SQL DATA") },
+ { C_STRING_WITH_LEN("MODIFIES SQL DATA") }
+};
+
+#define DERIVED_SUBQUERY 1
+#define DERIVED_VIEW 2
+
+enum enum_view_create_mode
+{
+ VIEW_CREATE_NEW, // check that there are not such VIEW/table
+ VIEW_ALTER, // check that VIEW .frm with such name exists
+ VIEW_CREATE_OR_REPLACE // check only that there are not such table
+};
+
+enum enum_drop_mode
+{
+ DROP_DEFAULT, // mode is not specified
+ DROP_CASCADE, // CASCADE option
+ DROP_RESTRICT // RESTRICT option
+};
+
+/* Options to add_table_to_list() */
+#define TL_OPTION_UPDATING 1
+#define TL_OPTION_FORCE_INDEX 2
+#define TL_OPTION_IGNORE_LEAVES 4
+#define TL_OPTION_ALIAS 8
+
+typedef List<Item> List_item;
+typedef Mem_root_array<ORDER*, true> Group_list_ptrs;
+
+/* SERVERS CACHE CHANGES */
+typedef struct st_lex_server_options
+{
+ long port;
+ uint server_name_length;
+ char *server_name, *host, *db, *username, *password, *scheme, *socket, *owner;
+} LEX_SERVER_OPTIONS;
+
+
+/**
+ Structure to hold parameters for CHANGE MASTER, START SLAVE, and STOP SLAVE.
+
+ Remark: this should not be confused with Master_info (and perhaps
+ would better be renamed to st_lex_replication_info). Some fields,
+ e.g., delay, are saved in Relay_log_info, not in Master_info.
+*/
+struct LEX_MASTER_INFO
+{
+ DYNAMIC_ARRAY repl_ignore_server_ids;
+ char *host, *user, *password, *log_file_name;
+ char *ssl_key, *ssl_cert, *ssl_ca, *ssl_capath, *ssl_cipher;
+ char *ssl_crl, *ssl_crlpath;
+ char *relay_log_name;
+ LEX_STRING connection_name;
+ /* Value in START SLAVE UNTIL master_gtid_pos=xxx */
+ LEX_STRING gtid_pos_str;
+ ulonglong pos;
+ ulong relay_log_pos;
+ ulong server_id;
+ uint port, connect_retry;
+ float heartbeat_period;
+ /*
+ Enum is used for making it possible to detect if the user
+ changed variable or if it should be left at old value
+ */
+ enum {LEX_MI_UNCHANGED, LEX_MI_DISABLE, LEX_MI_ENABLE}
+ ssl, ssl_verify_server_cert, heartbeat_opt, repl_ignore_server_ids_opt;
+ enum {
+ LEX_GTID_UNCHANGED, LEX_GTID_NO, LEX_GTID_CURRENT_POS, LEX_GTID_SLAVE_POS
+ } use_gtid_opt;
+
+ void init()
+ {
+ bzero(this, sizeof(*this));
+ my_init_dynamic_array(&repl_ignore_server_ids,
+ sizeof(::server_id), 0, 16, MYF(0));
+ }
+ void reset()
+ {
+ delete_dynamic(&repl_ignore_server_ids);
+ host= user= password= log_file_name= ssl_key= ssl_cert= ssl_ca=
+ ssl_capath= ssl_cipher= relay_log_name= 0;
+ pos= relay_log_pos= server_id= port= connect_retry= 0;
+ heartbeat_period= 0;
+ ssl= ssl_verify_server_cert= heartbeat_opt=
+ repl_ignore_server_ids_opt= LEX_MI_UNCHANGED;
+ gtid_pos_str.length= 0;
+ gtid_pos_str.str= NULL;
+ use_gtid_opt= LEX_GTID_UNCHANGED;
+ }
+};
+
+typedef struct st_lex_reset_slave
+{
+ bool all;
+} LEX_RESET_SLAVE;
+
+enum sub_select_type
+{
+ UNSPECIFIED_TYPE,UNION_TYPE, INTERSECT_TYPE,
+ EXCEPT_TYPE, GLOBAL_OPTIONS_TYPE, DERIVED_TABLE_TYPE, OLAP_TYPE
+};
+
+enum olap_type
+{
+ UNSPECIFIED_OLAP_TYPE, CUBE_TYPE, ROLLUP_TYPE
+};
+
+/*
+ String names used to print a statement with index hints.
+ Keep in sync with index_hint_type.
+*/
+extern const char * index_hint_type_name[];
+typedef uchar index_clause_map;
+
+/*
+ Bits in index_clause_map : one for each possible FOR clause in
+ USE/FORCE/IGNORE INDEX index hint specification
+*/
+#define INDEX_HINT_MASK_JOIN (1)
+#define INDEX_HINT_MASK_GROUP (1 << 1)
+#define INDEX_HINT_MASK_ORDER (1 << 2)
+
+#define INDEX_HINT_MASK_ALL (INDEX_HINT_MASK_JOIN | INDEX_HINT_MASK_GROUP | \
+ INDEX_HINT_MASK_ORDER)
+
+class select_result_sink;
+
+/* Single element of an USE/FORCE/IGNORE INDEX list specified as a SQL hint */
+class Index_hint : public Sql_alloc
+{
+public:
+ /* The type of the hint : USE/FORCE/IGNORE */
+ enum index_hint_type type;
+ /* Where the hit applies to. A bitmask of INDEX_HINT_MASK_<place> values */
+ index_clause_map clause;
+ /*
+ The index name. Empty (str=NULL) name represents an empty list
+ USE INDEX () clause
+ */
+ LEX_STRING key_name;
+
+ Index_hint (enum index_hint_type type_arg, index_clause_map clause_arg,
+ char *str, uint length) :
+ type(type_arg), clause(clause_arg)
+ {
+ key_name.str= str;
+ key_name.length= length;
+ }
+
+ void print(THD *thd, String *str);
+};
+
+/*
+ The state of the lex parsing for selects
+
+ master and slaves are pointers to select_lex.
+ master is pointer to upper level node.
+ slave is pointer to lower level node
+ select_lex is a SELECT without union
+ unit is container of either
+ - One SELECT
+ - UNION of selects
+ select_lex and unit are both inherited form select_lex_node
+ neighbors are two select_lex or units on the same level
+
+ All select describing structures linked with following pointers:
+ - list of neighbors (next/prev) (prev of first element point to slave
+ pointer of upper structure)
+ - For select this is a list of UNION's (or one element list)
+ - For units this is a list of sub queries for the upper level select
+
+ - pointer to master (master), which is
+ If this is a unit
+ - pointer to outer select_lex
+ If this is a select_lex
+ - pointer to outer unit structure for select
+
+ - pointer to slave (slave), which is either:
+ If this is a unit:
+ - first SELECT that belong to this unit
+ If this is a select_lex
+ - first unit that belong to this SELECT (subquries or derived tables)
+
+ - list of all select_lex (link_next/link_prev)
+ This is to be used for things like derived tables creation, where we
+ go through this list and create the derived tables.
+
+ If unit contain several selects (UNION now, INTERSECT etc later)
+ then it have special select_lex called fake_select_lex. It used for
+ storing global parameters (like ORDER BY, LIMIT) and executing union.
+ Subqueries used in global ORDER BY clause will be attached to this
+ fake_select_lex, which will allow them correctly resolve fields of
+ 'upper' UNION and outer selects.
+
+ For example for following query:
+
+ select *
+ from table1
+ where table1.field IN (select * from table1_1_1 union
+ select * from table1_1_2)
+ union
+ select *
+ from table2
+ where table2.field=(select (select f1 from table2_1_1_1_1
+ where table2_1_1_1_1.f2=table2_1_1.f3)
+ from table2_1_1
+ where table2_1_1.f1=table2.f2)
+ union
+ select * from table3;
+
+ we will have following structure:
+
+ select1: (select * from table1 ...)
+ select2: (select * from table2 ...)
+ select3: (select * from table3)
+ select1.1.1: (select * from table1_1_1)
+ ...
+
+ main unit
+ fake0
+ select1 select2 select3
+ |^^ |^
+ s||| ||master
+ l||| |+---------------------------------+
+ a||| +---------------------------------+|
+ v|||master slave ||
+ e||+-------------------------+ ||
+ V| neighbor | V|
+ unit1.1<+==================>unit1.2 unit2.1
+ fake1.1
+ select1.1.1 select 1.1.2 select1.2.1 select2.1.1
+ |^
+ ||
+ V|
+ unit2.1.1.1
+ select2.1.1.1.1
+
+
+ relation in main unit will be following:
+ (bigger picture for:
+ main unit
+ fake0
+ select1 select2 select3
+ in the above picture)
+
+ main unit
+ |^^^^|fake_select_lex
+ |||||+--------------------------------------------+
+ ||||+--------------------------------------------+|
+ |||+------------------------------+ ||
+ ||+--------------+ | ||
+ slave||master | | ||
+ V| neighbor | neighbor | master|V
+ select1<========>select2<========>select3 fake0
+
+ list of all select_lex will be following (as it will be constructed by
+ parser):
+
+ select1->select2->select3->select2.1.1->select 2.1.2->select2.1.1.1.1-+
+ |
+ +---------------------------------------------------------------------+
+ |
+ +->select1.1.1->select1.1.2
+
+*/
+
+/*
+ Base class for st_select_lex (SELECT_LEX) &
+ st_select_lex_unit (SELECT_LEX_UNIT)
+*/
+struct LEX;
+class st_select_lex;
+class st_select_lex_unit;
+
+
+class st_select_lex_node {
+protected:
+ st_select_lex_node *next, **prev, /* neighbor list */
+ *master, *slave, /* vertical links */
+ *link_next, **link_prev; /* list of whole SELECT_LEX */
+public:
+
+ ulonglong options;
+
+ /*
+ In sql_cache we store SQL_CACHE flag as specified by user to be
+ able to restore SELECT statement from internal structures.
+ */
+ enum e_sql_cache { SQL_CACHE_UNSPECIFIED, SQL_NO_CACHE, SQL_CACHE };
+ e_sql_cache sql_cache;
+
+ /*
+ result of this query can't be cached, bit field, can be :
+ UNCACHEABLE_DEPENDENT_GENERATED
+ UNCACHEABLE_DEPENDENT_INJECTED
+ UNCACHEABLE_RAND
+ UNCACHEABLE_SIDEEFFECT
+ UNCACHEABLE_EXPLAIN
+ UNCACHEABLE_PREPARE
+ */
+ uint8 uncacheable;
+ enum sub_select_type linkage;
+ bool no_table_names_allowed; /* used for global order by */
+
+ static void *operator new(size_t size) throw ()
+ {
+ return sql_alloc(size);
+ }
+ static void *operator new(size_t size, MEM_ROOT *mem_root) throw ()
+ { return (void*) alloc_root(mem_root, (uint) size); }
+ static void operator delete(void *ptr,size_t size) { TRASH(ptr, size); }
+ static void operator delete(void *ptr, MEM_ROOT *mem_root) {}
+
+ // Ensures that at least all members used during cleanup() are initialized.
+ st_select_lex_node()
+ : next(NULL), prev(NULL),
+ master(NULL), slave(NULL),
+ link_next(NULL), link_prev(NULL),
+ linkage(UNSPECIFIED_TYPE)
+ {
+ }
+ virtual ~st_select_lex_node() {}
+
+ inline st_select_lex_node* get_master() { return master; }
+ virtual void init_query();
+ virtual void init_select();
+ void include_down(st_select_lex_node *upper);
+ void add_slave(st_select_lex_node *slave_arg);
+ void include_neighbour(st_select_lex_node *before);
+ void include_standalone(st_select_lex_node *sel, st_select_lex_node **ref);
+ void include_global(st_select_lex_node **plink);
+ void exclude();
+ void exclude_from_tree();
+
+ virtual st_select_lex_unit* master_unit()= 0;
+ virtual st_select_lex* outer_select()= 0;
+ virtual st_select_lex* return_after_parsing()= 0;
+
+ virtual bool set_braces(bool value);
+ virtual bool inc_in_sum_expr();
+ virtual uint get_in_sum_expr();
+ virtual TABLE_LIST* get_table_list();
+ virtual List<Item>* get_item_list();
+ virtual ulong get_table_join_options();
+ virtual TABLE_LIST *add_table_to_list(THD *thd, Table_ident *table,
+ LEX_STRING *alias,
+ ulong table_options,
+ thr_lock_type flags= TL_UNLOCK,
+ enum_mdl_type mdl_type= MDL_SHARED_READ,
+ List<Index_hint> *hints= 0,
+ List<String> *partition_names= 0,
+ LEX_STRING *option= 0);
+ virtual void set_lock_for_tables(thr_lock_type lock_type) {}
+
+ friend class st_select_lex_unit;
+ friend bool mysql_new_select(LEX *lex, bool move_down);
+ friend bool mysql_make_view(THD *thd, File_parser *parser,
+ TABLE_LIST *table, uint flags);
+ friend bool mysql_derived_prepare(THD *thd, LEX *lex,
+ TABLE_LIST *orig_table_list);
+ friend bool mysql_derived_merge(THD *thd, LEX *lex,
+ TABLE_LIST *orig_table_list);
+ friend bool TABLE_LIST::init_derived(THD *thd, bool init_view);
+private:
+ void fast_exclude();
+};
+typedef class st_select_lex_node SELECT_LEX_NODE;
+
+/*
+ SELECT_LEX_UNIT - unit of selects (UNION, INTERSECT, ...) group
+ SELECT_LEXs
+*/
+class THD;
+class select_result;
+class JOIN;
+class select_union;
+class Procedure;
+class Explain_query;
+
+void delete_explain_query(LEX *lex);
+void create_explain_query(LEX *lex, MEM_ROOT *mem_root);
+void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root);
+bool print_explain_query(LEX *lex, THD *thd, String *str);
+
+class st_select_lex_unit: public st_select_lex_node {
+protected:
+ TABLE_LIST result_table_list;
+ select_union *union_result;
+ ulonglong found_rows_for_union;
+ bool saved_error;
+
+public:
+ // Ensures that at least all members used during cleanup() are initialized.
+ st_select_lex_unit()
+ : union_result(NULL), table(NULL), result(NULL),
+ cleaned(false),
+ fake_select_lex(NULL)
+ {
+ }
+
+
+ TABLE *table; /* temporary table using for appending UNION results */
+ select_result *result;
+ bool prepared, // prepare phase already performed for UNION (unit)
+ optimized, // optimize phase already performed for UNION (unit)
+ executed, // already executed
+ cleaned;
+
+ // list of fields which points to temporary table for union
+ List<Item> item_list;
+ /*
+ list of types of items inside union (used for union & derived tables)
+
+ Item_type_holders from which this list consist may have pointers to Field,
+ pointers is valid only after preparing SELECTS of this unit and before
+ any SELECT of this unit execution
+ */
+ List<Item> types;
+ /*
+ Pointer to 'last' select or pointer to unit where stored
+ global parameters for union
+ */
+ st_select_lex *global_parameters;
+ //node on wich we should return current_select pointer after parsing subquery
+ st_select_lex *return_to;
+ /* LIMIT clause runtime counters */
+ ha_rows select_limit_cnt, offset_limit_cnt;
+ /* not NULL if unit used in subselect, point to subselect item */
+ Item_subselect *item;
+ /*
+ TABLE_LIST representing this union in the embedding select. Used for
+ derived tables/views handling.
+ */
+ TABLE_LIST *derived;
+ /* thread handler */
+ THD *thd;
+ /*
+ SELECT_LEX for hidden SELECT in onion which process global
+ ORDER BY and LIMIT
+ */
+ st_select_lex *fake_select_lex;
+
+ st_select_lex *union_distinct; /* pointer to the last UNION DISTINCT */
+ bool describe; /* union exec() called for EXPLAIN */
+ Procedure *last_procedure; /* Pointer to procedure, if such exists */
+
+ /*
+ Insert table with stored virtual columns.
+ This is used only in those rare cases
+ when the list of inserted values is empty.
+ */
+ TABLE *insert_table_with_stored_vcol;
+
+ void init_query();
+ st_select_lex_unit* master_unit();
+ st_select_lex* outer_select();
+ st_select_lex* first_select()
+ {
+ return reinterpret_cast<st_select_lex*>(slave);
+ }
+ st_select_lex_unit* next_unit()
+ {
+ return reinterpret_cast<st_select_lex_unit*>(next);
+ }
+ st_select_lex* return_after_parsing() { return return_to; }
+ void exclude_level();
+ void exclude_tree();
+
+ /* UNION methods */
+ bool prepare(THD *thd, select_result *result, ulong additional_options);
+ bool optimize();
+ bool exec();
+ bool cleanup();
+ inline void unclean() { cleaned= 0; }
+ void reinit_exec_mechanism();
+
+ void print(String *str, enum_query_type query_type);
+
+ bool add_fake_select_lex(THD *thd);
+ void init_prepare_fake_select_lex(THD *thd, bool first_execution);
+ inline bool is_prepared() { return prepared; }
+ bool change_result(select_result_interceptor *result,
+ select_result_interceptor *old_result);
+ void set_limit(st_select_lex *values);
+ void set_thd(THD *thd_arg) { thd= thd_arg; }
+ inline bool is_union ();
+
+ void set_unique_exclude();
+
+ friend void lex_start(THD *thd);
+ friend int subselect_union_engine::exec();
+
+ List<Item> *get_unit_column_types();
+
+ int save_union_explain(Explain_query *output);
+ int save_union_explain_part2(Explain_query *output);
+};
+
+typedef class st_select_lex_unit SELECT_LEX_UNIT;
+
+/*
+ SELECT_LEX - store information of parsed SELECT statment
+*/
+class st_select_lex: public st_select_lex_node
+{
+public:
+ Name_resolution_context context;
+ char *db;
+ Item *where, *having; /* WHERE & HAVING clauses */
+ Item *prep_where; /* saved WHERE clause for prepared statement processing */
+ Item *prep_having;/* saved HAVING clause for prepared statement processing */
+ /* Saved values of the WHERE and HAVING clauses*/
+ Item::cond_result cond_value, having_value;
+ /* point on lex in which it was created, used in view subquery detection */
+ LEX *parent_lex;
+ enum olap_type olap;
+ /* FROM clause - points to the beginning of the TABLE_LIST::next_local list. */
+ SQL_I_List<TABLE_LIST> table_list;
+
+ /*
+ GROUP BY clause.
+ This list may be mutated during optimization (by remove_const()),
+ so for prepared statements, we keep a copy of the ORDER.next pointers in
+ group_list_ptrs, and re-establish the original list before each execution.
+ */
+ SQL_I_List<ORDER> group_list;
+ Group_list_ptrs *group_list_ptrs;
+
+ List<Item> item_list; /* list of fields & expressions */
+ List<String> interval_list;
+ bool is_item_list_lookup;
+ /*
+ Usualy it is pointer to ftfunc_list_alloc, but in union used to create fake
+ select_lex for calling mysql_select under results of union
+ */
+ List<Item_func_match> *ftfunc_list;
+ List<Item_func_match> ftfunc_list_alloc;
+ JOIN *join; /* after JOIN::prepare it is pointer to corresponding JOIN */
+ List<TABLE_LIST> top_join_list; /* join list of the top level */
+ List<TABLE_LIST> *join_list; /* list for the currently parsed join */
+ TABLE_LIST *embedding; /* table embedding to the above list */
+ List<TABLE_LIST> sj_nests; /* Semi-join nests within this join */
+ /*
+ Beginning of the list of leaves in a FROM clause, where the leaves
+ inlcude all base tables including view tables. The tables are connected
+ by TABLE_LIST::next_leaf, so leaf_tables points to the left-most leaf.
+
+ List of all base tables local to a subquery including all view
+ tables. Unlike 'next_local', this in this list views are *not*
+ leaves. Created in setup_tables() -> make_leaves_list().
+ */
+ /*
+ Subqueries that will need to be converted to semi-join nests, including
+ those converted to jtbm nests. The list is emptied when conversion is done.
+ */
+ List<Item_in_subselect> sj_subselects;
+
+ /*
+ Needed to correctly generate 'PRIMARY' or 'SIMPLE' for select_type column
+ of EXPLAIN
+ */
+ bool have_merged_subqueries;
+
+ List<TABLE_LIST> leaf_tables;
+ List<TABLE_LIST> leaf_tables_exec;
+ List<TABLE_LIST> leaf_tables_prep;
+ bool is_prep_leaf_list_saved;
+ uint insert_tables;
+ st_select_lex *merged_into; /* select which this select is merged into */
+ /* (not 0 only for views/derived tables) */
+
+ const char *type; /* type of select for EXPLAIN */
+
+ SQL_I_List<ORDER> order_list; /* ORDER clause */
+ SQL_I_List<ORDER> gorder_list;
+ Item *select_limit, *offset_limit; /* LIMIT clause parameters */
+ // Arrays of pointers to top elements of all_fields list
+ Item **ref_pointer_array;
+ size_t ref_pointer_array_size; // Number of elements in array.
+
+ /*
+ number of items in select_list and HAVING clause used to get number
+ bigger then can be number of entries that will be added to all item
+ list during split_sum_func
+ */
+ uint select_n_having_items;
+ uint cond_count; /* number of arguments of and/or/xor in where/having/on */
+ uint between_count; /* number of between predicates in where/having/on */
+ uint max_equal_elems; /* maximal number of elements in multiple equalities */
+ /*
+ Number of fields used in select list or where clause of current select
+ and all inner subselects.
+ */
+ uint select_n_where_fields;
+ /* reserved for exists 2 in */
+ uint select_n_reserved;
+ enum_parsing_place parsing_place; /* where we are parsing expression */
+ bool with_sum_func; /* sum function indicator */
+
+ ulong table_join_options;
+ uint in_sum_expr;
+ uint select_number; /* number of select (used for EXPLAIN) */
+
+ /*
+ nest_levels are local to the query or VIEW,
+ and that view merge procedure does not re-calculate them.
+ So we also have to remember unit against which we count levels.
+ */
+ SELECT_LEX_UNIT *nest_level_base;
+ int nest_level; /* nesting level of select */
+ Item_sum *inner_sum_func_list; /* list of sum func in nested selects */
+ uint with_wild; /* item list contain '*' */
+ bool braces; /* SELECT ... UNION (SELECT ... ) <- this braces */
+ /* TRUE when having fix field called in processing of this SELECT */
+ bool having_fix_field;
+ /* List of references to fields referenced from inner selects */
+ List<Item_outer_ref> inner_refs_list;
+ /* Number of Item_sum-derived objects in this SELECT */
+ uint n_sum_items;
+ /* Number of Item_sum-derived objects in children and descendant SELECTs */
+ uint n_child_sum_items;
+
+ /* explicit LIMIT clause was used */
+ bool explicit_limit;
+ /*
+ This array is used to note whether we have any candidates for
+ expression caching in the corresponding clauses
+ */
+ bool expr_cache_may_be_used[PARSING_PLACE_SIZE];
+ /*
+ there are subquery in HAVING clause => we can't close tables before
+ query processing end even if we use temporary table
+ */
+ bool subquery_in_having;
+ /* TRUE <=> this SELECT is correlated w.r.t. some ancestor select */
+ bool is_correlated;
+ /*
+ This variable is required to ensure proper work of subqueries and
+ stored procedures. Generally, one should use the states of
+ Query_arena to determine if it's a statement prepare or first
+ execution of a stored procedure. However, in case when there was an
+ error during the first execution of a stored procedure, the SP body
+ is not expelled from the SP cache. Therefore, a deeply nested
+ subquery might be left unoptimized. So we need this per-subquery
+ variable to inidicate the optimization/execution state of every
+ subquery. Prepared statements work OK in that regard, as in
+ case of an error during prepare the PS is not created.
+ */
+ bool first_execution;
+ bool first_natural_join_processing;
+ bool first_cond_optimization;
+ /* do not wrap view fields with Item_ref */
+ bool no_wrap_view_item;
+ /* exclude this select from check of unique_table() */
+ bool exclude_from_table_unique_test;
+ /* List of fields that aren't under an aggregate function */
+ List<Item_field> non_agg_fields;
+ /* index in the select list of the expression currently being fixed */
+ int cur_pos_in_select_list;
+
+ List<udf_func> udf_list; /* udf function calls stack */
+
+ /*
+ This is a copy of the original JOIN USING list that comes from
+ the parser. The parser :
+ 1. Sets the natural_join of the second TABLE_LIST in the join
+ and the st_select_lex::prev_join_using.
+ 2. Makes a parent TABLE_LIST and sets its is_natural_join/
+ join_using_fields members.
+ 3. Uses the wrapper TABLE_LIST as a table in the upper level.
+ We cannot assign directly to join_using_fields in the parser because
+ at stage (1.) the parent TABLE_LIST is not constructed yet and
+ the assignment will override the JOIN USING fields of the lower level
+ joins on the right.
+ */
+ List<String> *prev_join_using;
+
+ /* namp of nesting SELECT visibility (for aggregate functions check) */
+ nesting_map name_visibility_map;
+
+ void init_query();
+ void init_select();
+ st_select_lex_unit* master_unit();
+ st_select_lex_unit* first_inner_unit()
+ {
+ return (st_select_lex_unit*) slave;
+ }
+ st_select_lex* outer_select();
+ st_select_lex* next_select() { return (st_select_lex*) next; }
+ st_select_lex* next_select_in_list()
+ {
+ return (st_select_lex*) link_next;
+ }
+ st_select_lex_node** next_select_in_list_addr()
+ {
+ return &link_next;
+ }
+ st_select_lex* return_after_parsing()
+ {
+ return master_unit()->return_after_parsing();
+ }
+ inline bool is_subquery_function() { return master_unit()->item != 0; }
+
+ bool mark_as_dependent(THD *thd, st_select_lex *last, Item *dependency);
+
+ bool set_braces(bool value);
+ bool inc_in_sum_expr();
+ uint get_in_sum_expr();
+
+ bool add_item_to_list(THD *thd, Item *item);
+ bool add_group_to_list(THD *thd, Item *item, bool asc);
+ bool add_ftfunc_to_list(Item_func_match *func);
+ bool add_order_to_list(THD *thd, Item *item, bool asc);
+ bool add_gorder_to_list(THD *thd, Item *item, bool asc);
+ TABLE_LIST* add_table_to_list(THD *thd, Table_ident *table,
+ LEX_STRING *alias,
+ ulong table_options,
+ thr_lock_type flags= TL_UNLOCK,
+ enum_mdl_type mdl_type= MDL_SHARED_READ,
+ List<Index_hint> *hints= 0,
+ List<String> *partition_names= 0,
+ LEX_STRING *option= 0);
+ TABLE_LIST* get_table_list();
+ bool init_nested_join(THD *thd);
+ TABLE_LIST *end_nested_join(THD *thd);
+ TABLE_LIST *nest_last_join(THD *thd);
+ void add_joined_table(TABLE_LIST *table);
+ TABLE_LIST *convert_right_join();
+ List<Item>* get_item_list();
+ ulong get_table_join_options();
+ void set_lock_for_tables(thr_lock_type lock_type);
+ inline void init_order()
+ {
+ order_list.elements= 0;
+ order_list.first= 0;
+ order_list.next= &order_list.first;
+ }
+ /*
+ This method created for reiniting LEX in mysql_admin_table() and can be
+ used only if you are going remove all SELECT_LEX & units except belonger
+ to LEX (LEX::unit & LEX::select, for other purposes there are
+ SELECT_LEX_UNIT::exclude_level & SELECT_LEX_UNIT::exclude_tree
+ */
+ void cut_subtree() { slave= 0; }
+ bool test_limit();
+
+ friend void lex_start(THD *thd);
+ st_select_lex() : group_list_ptrs(NULL), n_sum_items(0), n_child_sum_items(0)
+ {}
+ void make_empty_select()
+ {
+ init_query();
+ init_select();
+ }
+ bool setup_ref_array(THD *thd, uint order_group_num);
+ void print(THD *thd, String *str, enum_query_type query_type);
+ static void print_order(String *str,
+ ORDER *order,
+ enum_query_type query_type);
+ void print_limit(THD *thd, String *str, enum_query_type query_type);
+ void fix_prepare_information(THD *thd, Item **conds, Item **having_conds);
+ /*
+ Destroy the used execution plan (JOIN) of this subtree (this
+ SELECT_LEX and all nested SELECT_LEXes and SELECT_LEX_UNITs).
+ */
+ bool cleanup();
+ /*
+ Recursively cleanup the join of this select lex and of all nested
+ select lexes.
+ */
+ void cleanup_all_joins(bool full);
+
+ void set_index_hint_type(enum index_hint_type type, index_clause_map clause);
+
+ /*
+ Add a index hint to the tagged list of hints. The type and clause of the
+ hint will be the current ones (set by set_index_hint())
+ */
+ bool add_index_hint (THD *thd, char *str, uint length);
+
+ /* make a list to hold index hints */
+ void alloc_index_hints (THD *thd);
+ /* read and clear the index hints */
+ List<Index_hint>* pop_index_hints(void)
+ {
+ List<Index_hint> *hints= index_hints;
+ index_hints= NULL;
+ return hints;
+ }
+
+ void clear_index_hints(void) { index_hints= NULL; }
+ bool is_part_of_union() { return master_unit()->is_union(); }
+ bool is_top_level_node()
+ {
+ return (select_number == 1) && !is_part_of_union();
+ }
+ bool optimize_unflattened_subqueries(bool const_only);
+ /* Set the EXPLAIN type for this subquery. */
+ void set_explain_type(bool on_the_fly);
+ bool handle_derived(LEX *lex, uint phases);
+ void append_table_to_list(TABLE_LIST *TABLE_LIST::*link, TABLE_LIST *table);
+ bool get_free_table_map(table_map *map, uint *tablenr);
+ void replace_leaf_table(TABLE_LIST *table, List<TABLE_LIST> &tbl_list);
+ void remap_tables(TABLE_LIST *derived, table_map map,
+ uint tablenr, st_select_lex *parent_lex);
+ bool merge_subquery(THD *thd, TABLE_LIST *derived, st_select_lex *subq_lex,
+ uint tablenr, table_map map);
+ inline bool is_mergeable()
+ {
+ return (next_select() == 0 && group_list.elements == 0 &&
+ having == 0 && with_sum_func == 0 &&
+ table_list.elements >= 1 && !(options & SELECT_DISTINCT) &&
+ select_limit == 0);
+ }
+ void mark_as_belong_to_derived(TABLE_LIST *derived);
+ void increase_derived_records(ha_rows records);
+ void update_used_tables();
+ void update_correlated_cache();
+ void mark_const_derived(bool empty);
+
+ bool save_leaf_tables(THD *thd);
+ bool save_prep_leaf_tables(THD *thd);
+
+ bool is_merged_child_of(st_select_lex *ancestor);
+
+ /*
+ For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags:
+ - Non-aggregated fields are used in this select.
+ - Aggregate functions are used in this select.
+ In MODE_ONLY_FULL_GROUP_BY only one of these may be true.
+ */
+ bool non_agg_field_used() const { return m_non_agg_field_used; }
+ bool agg_func_used() const { return m_agg_func_used; }
+
+ void set_non_agg_field_used(bool val) { m_non_agg_field_used= val; }
+ void set_agg_func_used(bool val) { m_agg_func_used= val; }
+
+private:
+ bool m_non_agg_field_used;
+ bool m_agg_func_used;
+
+ /* current index hint kind. used in filling up index_hints */
+ enum index_hint_type current_index_hint_type;
+ index_clause_map current_index_hint_clause;
+ /* a list of USE/FORCE/IGNORE INDEX */
+ List<Index_hint> *index_hints;
+};
+typedef class st_select_lex SELECT_LEX;
+
+inline bool st_select_lex_unit::is_union ()
+{
+ return first_select()->next_select() &&
+ first_select()->next_select()->linkage == UNION_TYPE;
+}
+
+
+struct st_sp_chistics
+{
+ LEX_STRING comment;
+ enum enum_sp_suid_behaviour suid;
+ bool detistic;
+ enum enum_sp_data_access daccess;
+};
+
+extern const LEX_STRING null_lex_str;
+extern const LEX_STRING empty_lex_str;
+
+struct st_trg_chistics
+{
+ enum trg_action_time_type action_time;
+ enum trg_event_type event;
+};
+
+extern sys_var *trg_new_row_fake_var;
+
+enum xa_option_words {XA_NONE, XA_JOIN, XA_RESUME, XA_ONE_PHASE,
+ XA_SUSPEND, XA_FOR_MIGRATE};
+
+extern const LEX_STRING null_lex_str;
+extern const LEX_STRING empty_lex_str;
+
+class Sroutine_hash_entry;
+
+/*
+ Class representing list of all tables used by statement and other
+ information which is necessary for opening and locking its tables,
+ like SQL command for this statement.
+
+ Also contains information about stored functions used by statement
+ since during its execution we may have to add all tables used by its
+ stored functions/triggers to this list in order to pre-open and lock
+ them.
+
+ Also used by LEX::reset_n_backup/restore_backup_query_tables_list()
+ methods to save and restore this information.
+*/
+
+class Query_tables_list
+{
+public:
+ /**
+ SQL command for this statement. Part of this class since the
+ process of opening and locking tables for the statement needs
+ this information to determine correct type of lock for some of
+ the tables.
+ */
+ enum_sql_command sql_command;
+ /* Global list of all tables used by this statement */
+ TABLE_LIST *query_tables;
+ /* Pointer to next_global member of last element in the previous list. */
+ TABLE_LIST **query_tables_last;
+ /*
+ If non-0 then indicates that query requires prelocking and points to
+ next_global member of last own element in query table list (i.e. last
+ table which was not added to it as part of preparation to prelocking).
+ 0 - indicates that this query does not need prelocking.
+ */
+ TABLE_LIST **query_tables_own_last;
+ /*
+ Set of stored routines called by statement.
+ (Note that we use lazy-initialization for this hash).
+ */
+ enum { START_SROUTINES_HASH_SIZE= 16 };
+ HASH sroutines;
+ /*
+ List linking elements of 'sroutines' set. Allows you to add new elements
+ to this set as you iterate through the list of existing elements.
+ 'sroutines_list_own_last' is pointer to ::next member of last element of
+ this list which represents routine which is explicitly used by query.
+ 'sroutines_list_own_elements' number of explicitly used routines.
+ We use these two members for restoring of 'sroutines_list' to the state
+ in which it was right after query parsing.
+ */
+ SQL_I_List<Sroutine_hash_entry> sroutines_list;
+ Sroutine_hash_entry **sroutines_list_own_last;
+ uint sroutines_list_own_elements;
+
+ /**
+ Locking state of tables in this particular statement.
+
+ If we under LOCK TABLES or in prelocked mode we consider tables
+ for the statement to be "locked" if there was a call to lock_tables()
+ (which called handler::start_stmt()) for tables of this statement
+ and there was no matching close_thread_tables() call.
+
+ As result this state may differ significantly from one represented
+ by Open_tables_state::lock/locked_tables_mode more, which are always
+ "on" under LOCK TABLES or in prelocked mode.
+ */
+ enum enum_lock_tables_state {
+ LTS_NOT_LOCKED = 0,
+ LTS_LOCKED
+ };
+ enum_lock_tables_state lock_tables_state;
+ bool is_query_tables_locked()
+ {
+ return (lock_tables_state == LTS_LOCKED);
+ }
+
+ /**
+ Number of tables which were open by open_tables() and to be locked
+ by lock_tables().
+ Note that we set this member only in some cases, when this value
+ needs to be passed from open_tables() to lock_tables() which are
+ separated by some amount of code.
+ */
+ uint table_count;
+
+ /*
+ These constructor and destructor serve for creation/destruction
+ of Query_tables_list instances which are used as backup storage.
+ */
+ Query_tables_list() {}
+ ~Query_tables_list() {}
+
+ /* Initializes (or resets) Query_tables_list object for "real" use. */
+ void reset_query_tables_list(bool init);
+ void destroy_query_tables_list();
+ void set_query_tables_list(Query_tables_list *state)
+ {
+ *this= *state;
+ }
+
+ /*
+ Direct addition to the list of query tables.
+ If you are using this function, you must ensure that the table
+ object, in particular table->db member, is initialized.
+ */
+ void add_to_query_tables(TABLE_LIST *table)
+ {
+ *(table->prev_global= query_tables_last)= table;
+ query_tables_last= &table->next_global;
+ }
+ bool requires_prelocking()
+ {
+ return MY_TEST(query_tables_own_last);
+ }
+ void mark_as_requiring_prelocking(TABLE_LIST **tables_own_last)
+ {
+ query_tables_own_last= tables_own_last;
+ }
+ /* Return pointer to first not-own table in query-tables or 0 */
+ TABLE_LIST* first_not_own_table()
+ {
+ return ( query_tables_own_last ? *query_tables_own_last : 0);
+ }
+ void chop_off_not_own_tables()
+ {
+ if (query_tables_own_last)
+ {
+ *query_tables_own_last= 0;
+ query_tables_last= query_tables_own_last;
+ query_tables_own_last= 0;
+ }
+ }
+
+ /** Return a pointer to the last element in query table list. */
+ TABLE_LIST *last_table()
+ {
+ /* Don't use offsetof() macro in order to avoid warnings. */
+ return query_tables ?
+ (TABLE_LIST*) ((char*) query_tables_last -
+ ((char*) &(query_tables->next_global) -
+ (char*) query_tables)) :
+ 0;
+ }
+
+ /**
+ Enumeration listing of all types of unsafe statement.
+
+ @note The order of elements of this enumeration type must
+ correspond to the order of the elements of the @c explanations
+ array defined in the body of @c THD::issue_unsafe_warnings.
+ */
+ enum enum_binlog_stmt_unsafe {
+ /**
+ SELECT..LIMIT is unsafe because the set of rows returned cannot
+ be predicted.
+ */
+ BINLOG_STMT_UNSAFE_LIMIT= 0,
+ /**
+ INSERT DELAYED is unsafe because the time when rows are inserted
+ cannot be predicted.
+ */
+ BINLOG_STMT_UNSAFE_INSERT_DELAYED,
+ /**
+ Access to log tables is unsafe because slave and master probably
+ log different things.
+ */
+ BINLOG_STMT_UNSAFE_SYSTEM_TABLE,
+ /**
+ Inserting into an autoincrement column in a stored routine is unsafe.
+ Even with just one autoincrement column, if the routine is invoked more than
+ once slave is not guaranteed to execute the statement graph same way as
+ the master.
+ And since it's impossible to estimate how many times a routine can be invoked at
+ the query pre-execution phase (see lock_tables), the statement is marked
+ pessimistically unsafe.
+ */
+ BINLOG_STMT_UNSAFE_AUTOINC_COLUMNS,
+ /**
+ Using a UDF (user-defined function) is unsafe.
+ */
+ BINLOG_STMT_UNSAFE_UDF,
+ /**
+ Using most system variables is unsafe, because slave may run
+ with different options than master.
+ */
+ BINLOG_STMT_UNSAFE_SYSTEM_VARIABLE,
+ /**
+ Using some functions is unsafe (e.g., UUID).
+ */
+ BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION,
+
+ /**
+ Mixing transactional and non-transactional statements are unsafe if
+ non-transactional reads or writes are occur after transactional
+ reads or writes inside a transaction.
+ */
+ BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS,
+
+ /**
+ Mixing self-logging and non-self-logging engines in a statement
+ is unsafe.
+ */
+ BINLOG_STMT_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE,
+
+ /**
+ Statements that read from both transactional and non-transactional
+ tables and write to any of them are unsafe.
+ */
+ BINLOG_STMT_UNSAFE_MIXED_STATEMENT,
+
+ /**
+ INSERT...IGNORE SELECT is unsafe because which rows are ignored depends
+ on the order that rows are retrieved by SELECT. This order cannot be
+ predicted and may differ on master and the slave.
+ */
+ BINLOG_STMT_UNSAFE_INSERT_IGNORE_SELECT,
+
+ /**
+ INSERT...SELECT...UPDATE is unsafe because which rows are updated depends
+ on the order that rows are retrieved by SELECT. This order cannot be
+ predicted and may differ on master and the slave.
+ */
+ BINLOG_STMT_UNSAFE_INSERT_SELECT_UPDATE,
+
+ /**
+ Query that writes to a table with auto_inc column after selecting from
+ other tables are unsafe as the order in which the rows are retrieved by
+ select may differ on master and slave.
+ */
+ BINLOG_STMT_UNSAFE_WRITE_AUTOINC_SELECT,
+
+ /**
+ INSERT...REPLACE SELECT is unsafe because which rows are replaced depends
+ on the order that rows are retrieved by SELECT. This order cannot be
+ predicted and may differ on master and the slave.
+ */
+ BINLOG_STMT_UNSAFE_REPLACE_SELECT,
+
+ /**
+ CREATE TABLE... IGNORE... SELECT is unsafe because which rows are ignored
+ depends on the order that rows are retrieved by SELECT. This order cannot
+ be predicted and may differ on master and the slave.
+ */
+ BINLOG_STMT_UNSAFE_CREATE_IGNORE_SELECT,
+
+ /**
+ CREATE TABLE...REPLACE... SELECT is unsafe because which rows are replaced
+ depends on the order that rows are retrieved from SELECT. This order
+ cannot be predicted and may differ on master and the slave
+ */
+ BINLOG_STMT_UNSAFE_CREATE_REPLACE_SELECT,
+
+ /**
+ CREATE TABLE...SELECT on a table with auto-increment column is unsafe
+ because which rows are replaced depends on the order that rows are
+ retrieved from SELECT. This order cannot be predicted and may differ on
+ master and the slave
+ */
+ BINLOG_STMT_UNSAFE_CREATE_SELECT_AUTOINC,
+
+ /**
+ UPDATE...IGNORE is unsafe because which rows are ignored depends on the
+ order that rows are updated. This order cannot be predicted and may differ
+ on master and the slave.
+ */
+ BINLOG_STMT_UNSAFE_UPDATE_IGNORE,
+
+ /**
+ INSERT... ON DUPLICATE KEY UPDATE on a table with more than one
+ UNIQUE KEYS is unsafe.
+ */
+ BINLOG_STMT_UNSAFE_INSERT_TWO_KEYS,
+
+ /**
+ INSERT into auto-inc field which is not the first part of composed
+ primary key.
+ */
+ BINLOG_STMT_UNSAFE_AUTOINC_NOT_FIRST,
+
+ /* The last element of this enumeration type. */
+ BINLOG_STMT_UNSAFE_COUNT
+ };
+ /**
+ This has all flags from 0 (inclusive) to BINLOG_STMT_FLAG_COUNT
+ (exclusive) set.
+ */
+ static const int BINLOG_STMT_UNSAFE_ALL_FLAGS=
+ ((1 << BINLOG_STMT_UNSAFE_COUNT) - 1);
+
+ /**
+ Maps elements of enum_binlog_stmt_unsafe to error codes.
+ */
+ static const int binlog_stmt_unsafe_errcode[BINLOG_STMT_UNSAFE_COUNT];
+
+ /**
+ Determine if this statement is marked as unsafe.
+
+ @retval 0 if the statement is not marked as unsafe.
+ @retval nonzero if the statement is marked as unsafe.
+ */
+ inline bool is_stmt_unsafe() const {
+ return get_stmt_unsafe_flags() != 0;
+ }
+
+ /**
+ Flag the current (top-level) statement as unsafe.
+ The flag will be reset after the statement has finished.
+
+ @param unsafe_type The type of unsafety: one of the @c
+ BINLOG_STMT_FLAG_UNSAFE_* flags in @c enum_binlog_stmt_flag.
+ */
+ inline void set_stmt_unsafe(enum_binlog_stmt_unsafe unsafe_type) {
+ DBUG_ENTER("set_stmt_unsafe");
+ DBUG_ASSERT(unsafe_type >= 0 && unsafe_type < BINLOG_STMT_UNSAFE_COUNT);
+ binlog_stmt_flags|= (1U << unsafe_type);
+ DBUG_VOID_RETURN;
+ }
+
+ /**
+ Set the bits of binlog_stmt_flags determining the type of
+ unsafeness of the current statement. No existing bits will be
+ cleared, but new bits may be set.
+
+ @param flags A binary combination of zero or more bits, (1<<flag)
+ where flag is a member of enum_binlog_stmt_unsafe.
+ */
+ inline void set_stmt_unsafe_flags(uint32 flags) {
+ DBUG_ENTER("set_stmt_unsafe_flags");
+ DBUG_ASSERT((flags & ~BINLOG_STMT_UNSAFE_ALL_FLAGS) == 0);
+ binlog_stmt_flags|= flags;
+ DBUG_VOID_RETURN;
+ }
+
+ /**
+ Return a binary combination of all unsafe warnings for the
+ statement. If the statement has been marked as unsafe by the
+ 'flag' member of enum_binlog_stmt_unsafe, then the return value
+ from this function has bit (1<<flag) set to 1.
+ */
+ inline uint32 get_stmt_unsafe_flags() const {
+ DBUG_ENTER("get_stmt_unsafe_flags");
+ DBUG_RETURN(binlog_stmt_flags & BINLOG_STMT_UNSAFE_ALL_FLAGS);
+ }
+
+ /**
+ Mark the current statement as safe; i.e., clear all bits in
+ binlog_stmt_flags that correspond to elements of
+ enum_binlog_stmt_unsafe.
+ */
+ inline void clear_stmt_unsafe() {
+ DBUG_ENTER("clear_stmt_unsafe");
+ binlog_stmt_flags&= ~BINLOG_STMT_UNSAFE_ALL_FLAGS;
+ DBUG_VOID_RETURN;
+ }
+
+ /**
+ Determine if this statement is a row injection.
+
+ @retval 0 if the statement is not a row injection
+ @retval nonzero if the statement is a row injection
+ */
+ inline bool is_stmt_row_injection() const {
+ return binlog_stmt_flags &
+ (1U << (BINLOG_STMT_UNSAFE_COUNT + BINLOG_STMT_TYPE_ROW_INJECTION));
+ }
+
+ /**
+ Flag the statement as a row injection. A row injection is either
+ a BINLOG statement, or a row event in the relay log executed by
+ the slave SQL thread.
+ */
+ inline void set_stmt_row_injection() {
+ DBUG_ENTER("set_stmt_row_injection");
+ binlog_stmt_flags|=
+ (1U << (BINLOG_STMT_UNSAFE_COUNT + BINLOG_STMT_TYPE_ROW_INJECTION));
+ DBUG_VOID_RETURN;
+ }
+
+ enum enum_stmt_accessed_table
+ {
+ /*
+ If a transactional table is about to be read. Note that
+ a write implies a read.
+ */
+ STMT_READS_TRANS_TABLE= 0,
+ /*
+ If a non-transactional table is about to be read. Note that
+ a write implies a read.
+ */
+ STMT_READS_NON_TRANS_TABLE,
+ /*
+ If a temporary transactional table is about to be read. Note
+ that a write implies a read.
+ */
+ STMT_READS_TEMP_TRANS_TABLE,
+ /*
+ If a temporary non-transactional table is about to be read. Note
+ that a write implies a read.
+ */
+ STMT_READS_TEMP_NON_TRANS_TABLE,
+ /*
+ If a transactional table is about to be updated.
+ */
+ STMT_WRITES_TRANS_TABLE,
+ /*
+ If a non-transactional table is about to be updated.
+ */
+ STMT_WRITES_NON_TRANS_TABLE,
+ /*
+ If a temporary transactional table is about to be updated.
+ */
+ STMT_WRITES_TEMP_TRANS_TABLE,
+ /*
+ If a temporary non-transactional table is about to be updated.
+ */
+ STMT_WRITES_TEMP_NON_TRANS_TABLE,
+ /*
+ The last element of the enumeration. Please, if necessary add
+ anything before this.
+ */
+ STMT_ACCESS_TABLE_COUNT
+ };
+
+#ifndef DBUG_OFF
+ static inline const char *stmt_accessed_table_string(enum_stmt_accessed_table accessed_table)
+ {
+ switch (accessed_table)
+ {
+ case STMT_READS_TRANS_TABLE:
+ return "STMT_READS_TRANS_TABLE";
+ break;
+ case STMT_READS_NON_TRANS_TABLE:
+ return "STMT_READS_NON_TRANS_TABLE";
+ break;
+ case STMT_READS_TEMP_TRANS_TABLE:
+ return "STMT_READS_TEMP_TRANS_TABLE";
+ break;
+ case STMT_READS_TEMP_NON_TRANS_TABLE:
+ return "STMT_READS_TEMP_NON_TRANS_TABLE";
+ break;
+ case STMT_WRITES_TRANS_TABLE:
+ return "STMT_WRITES_TRANS_TABLE";
+ break;
+ case STMT_WRITES_NON_TRANS_TABLE:
+ return "STMT_WRITES_NON_TRANS_TABLE";
+ break;
+ case STMT_WRITES_TEMP_TRANS_TABLE:
+ return "STMT_WRITES_TEMP_TRANS_TABLE";
+ break;
+ case STMT_WRITES_TEMP_NON_TRANS_TABLE:
+ return "STMT_WRITES_TEMP_NON_TRANS_TABLE";
+ break;
+ case STMT_ACCESS_TABLE_COUNT:
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ MY_ASSERT_UNREACHABLE();
+ return "";
+ }
+#endif /* DBUG */
+
+ #define BINLOG_DIRECT_ON 0xF0 /* unsafe when
+ --binlog-direct-non-trans-updates
+ is ON */
+
+ #define BINLOG_DIRECT_OFF 0xF /* unsafe when
+ --binlog-direct-non-trans-updates
+ is OFF */
+
+ #define TRX_CACHE_EMPTY 0x33 /* unsafe when trx-cache is empty */
+
+ #define TRX_CACHE_NOT_EMPTY 0xCC /* unsafe when trx-cache is not empty */
+
+ #define IL_LT_REPEATABLE 0xAA /* unsafe when < ISO_REPEATABLE_READ */
+
+ #define IL_GTE_REPEATABLE 0x55 /* unsafe when >= ISO_REPEATABLE_READ */
+
+ /**
+ Sets the type of table that is about to be accessed while executing a
+ statement.
+
+ @param accessed_table Enumeration type that defines the type of table,
+ e.g. temporary, transactional, non-transactional.
+ */
+ inline void set_stmt_accessed_table(enum_stmt_accessed_table accessed_table)
+ {
+ DBUG_ENTER("LEX::set_stmt_accessed_table");
+
+ DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT);
+ stmt_accessed_table_flag |= (1U << accessed_table);
+
+ DBUG_VOID_RETURN;
+ }
+
+ /**
+ Checks if a type of table is about to be accessed while executing a
+ statement.
+
+ @param accessed_table Enumeration type that defines the type of table,
+ e.g. temporary, transactional, non-transactional.
+
+ @return
+ @retval TRUE if the type of the table is about to be accessed
+ @retval FALSE otherwise
+ */
+ inline bool stmt_accessed_table(enum_stmt_accessed_table accessed_table)
+ {
+ DBUG_ENTER("LEX::stmt_accessed_table");
+
+ DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT);
+
+ DBUG_RETURN((stmt_accessed_table_flag & (1U << accessed_table)) != 0);
+ }
+
+ /**
+ Checks if a temporary non-transactional table is about to be accessed
+ while executing a statement.
+
+ @return
+ @retval TRUE if a temporary non-transactional table is about to be
+ accessed
+ @retval FALSE otherwise
+ */
+ inline bool stmt_accessed_non_trans_temp_table()
+ {
+ DBUG_ENTER("THD::stmt_accessed_non_trans_temp_table");
+
+ DBUG_RETURN((stmt_accessed_table_flag &
+ ((1U << STMT_READS_TEMP_NON_TRANS_TABLE) |
+ (1U << STMT_WRITES_TEMP_NON_TRANS_TABLE))) != 0);
+ }
+
+ /*
+ Checks if a mixed statement is unsafe.
+
+
+ @param in_multi_stmt_transaction_mode defines if there is an on-going
+ multi-transactional statement.
+ @param binlog_direct defines if --binlog-direct-non-trans-updates is
+ active.
+ @param trx_cache_is_not_empty defines if the trx-cache is empty or not.
+ @param trx_isolation defines the isolation level.
+
+ @return
+ @retval TRUE if the mixed statement is unsafe
+ @retval FALSE otherwise
+ */
+ inline bool is_mixed_stmt_unsafe(bool in_multi_stmt_transaction_mode,
+ bool binlog_direct,
+ bool trx_cache_is_not_empty,
+ uint tx_isolation)
+ {
+ bool unsafe= FALSE;
+
+ if (in_multi_stmt_transaction_mode)
+ {
+ uint condition=
+ (binlog_direct ? BINLOG_DIRECT_ON : BINLOG_DIRECT_OFF) &
+ (trx_cache_is_not_empty ? TRX_CACHE_NOT_EMPTY : TRX_CACHE_EMPTY) &
+ (tx_isolation >= ISO_REPEATABLE_READ ? IL_GTE_REPEATABLE : IL_LT_REPEATABLE);
+
+ unsafe= (binlog_unsafe_map[stmt_accessed_table_flag] & condition);
+
+#if !defined(DBUG_OFF)
+ DBUG_PRINT("LEX::is_mixed_stmt_unsafe", ("RESULT %02X %02X %02X\n", condition,
+ binlog_unsafe_map[stmt_accessed_table_flag],
+ (binlog_unsafe_map[stmt_accessed_table_flag] & condition)));
+
+ int type_in= 0;
+ for (; type_in < STMT_ACCESS_TABLE_COUNT; type_in++)
+ {
+ if (stmt_accessed_table((enum_stmt_accessed_table) type_in))
+ DBUG_PRINT("LEX::is_mixed_stmt_unsafe", ("ACCESSED %s ",
+ stmt_accessed_table_string((enum_stmt_accessed_table) type_in)));
+ }
+#endif
+ }
+
+ if (stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE) &&
+ stmt_accessed_table(STMT_READS_TRANS_TABLE) &&
+ tx_isolation < ISO_REPEATABLE_READ)
+ unsafe= TRUE;
+ else if (stmt_accessed_table(STMT_WRITES_TEMP_NON_TRANS_TABLE) &&
+ stmt_accessed_table(STMT_READS_TRANS_TABLE) &&
+ tx_isolation < ISO_REPEATABLE_READ)
+ unsafe= TRUE;
+
+ return(unsafe);
+ }
+
+ /**
+ true if the parsed tree contains references to stored procedures
+ or functions, false otherwise
+ */
+ bool uses_stored_routines() const
+ { return sroutines_list.elements != 0; }
+
+private:
+
+ /**
+ Enumeration listing special types of statements.
+
+ Currently, the only possible type is ROW_INJECTION.
+ */
+ enum enum_binlog_stmt_type {
+ /**
+ The statement is a row injection (i.e., either a BINLOG
+ statement or a row event executed by the slave SQL thread).
+ */
+ BINLOG_STMT_TYPE_ROW_INJECTION = 0,
+
+ /** The last element of this enumeration type. */
+ BINLOG_STMT_TYPE_COUNT
+ };
+
+ /**
+ Bit field indicating the type of statement.
+
+ There are two groups of bits:
+
+ - The low BINLOG_STMT_UNSAFE_COUNT bits indicate the types of
+ unsafeness that the current statement has.
+
+ - The next BINLOG_STMT_TYPE_COUNT bits indicate if the statement
+ is of some special type.
+
+ This must be a member of LEX, not of THD: each stored procedure
+ needs to remember its unsafeness state between calls and each
+ stored procedure has its own LEX object (but no own THD object).
+ */
+ uint32 binlog_stmt_flags;
+
+ /**
+ Bit field that determines the type of tables that are about to be
+ be accessed while executing a statement.
+ */
+ uint32 stmt_accessed_table_flag;
+};
+
+
+/*
+ st_parsing_options contains the flags for constructions that are
+ allowed in the current statement.
+*/
+
+struct st_parsing_options
+{
+ bool allows_variable;
+ bool allows_select_into;
+ bool allows_select_procedure;
+ bool allows_derived;
+
+ st_parsing_options() { reset(); }
+ void reset();
+};
+
+
+/**
+ The state of the lexical parser, when parsing comments.
+*/
+enum enum_comment_state
+{
+ /**
+ Not parsing comments.
+ */
+ NO_COMMENT,
+ /**
+ Parsing comments that need to be preserved.
+ Typically, these are user comments '/' '*' ... '*' '/'.
+ */
+ PRESERVE_COMMENT,
+ /**
+ Parsing comments that need to be discarded.
+ Typically, these are special comments '/' '*' '!' ... '*' '/',
+ or '/' '*' '!' 'M' 'M' 'm' 'm' 'm' ... '*' '/', where the comment
+ markers should not be expanded.
+ */
+ DISCARD_COMMENT
+};
+
+
+/**
+ @brief This class represents the character input stream consumed during
+ lexical analysis.
+
+ In addition to consuming the input stream, this class performs some
+ comment pre processing, by filtering out out of bound special text
+ from the query input stream.
+ Two buffers, with pointers inside each buffers, are maintained in
+ parallel. The 'raw' buffer is the original query text, which may
+ contain out-of-bound comments. The 'cpp' (for comments pre processor)
+ is the pre-processed buffer that contains only the query text that
+ should be seen once out-of-bound data is removed.
+*/
+
+class Lex_input_stream
+{
+public:
+ Lex_input_stream()
+ {
+ }
+
+ ~Lex_input_stream()
+ {
+ }
+
+ /**
+ Object initializer. Must be called before usage.
+
+ @retval FALSE OK
+ @retval TRUE Error
+ */
+ bool init(THD *thd, char *buff, unsigned int length);
+
+ void reset(char *buff, unsigned int length);
+
+ /**
+ Set the echo mode.
+
+ When echo is true, characters parsed from the raw input stream are
+ preserved. When false, characters parsed are silently ignored.
+ @param echo the echo mode.
+ */
+ void set_echo(bool echo)
+ {
+ m_echo= echo;
+ }
+
+ void save_in_comment_state()
+ {
+ m_echo_saved= m_echo;
+ in_comment_saved= in_comment;
+ }
+
+ void restore_in_comment_state()
+ {
+ m_echo= m_echo_saved;
+ in_comment= in_comment_saved;
+ }
+
+ /**
+ Skip binary from the input stream.
+ @param n number of bytes to accept.
+ */
+ void skip_binary(int n)
+ {
+ if (m_echo)
+ {
+ memcpy(m_cpp_ptr, m_ptr, n);
+ m_cpp_ptr += n;
+ }
+ m_ptr += n;
+ }
+
+ /**
+ Get a character, and advance in the stream.
+ @return the next character to parse.
+ */
+ unsigned char yyGet()
+ {
+ char c= *m_ptr++;
+ if (m_echo)
+ *m_cpp_ptr++ = c;
+ return c;
+ }
+
+ /**
+ Get the last character accepted.
+ @return the last character accepted.
+ */
+ unsigned char yyGetLast()
+ {
+ return m_ptr[-1];
+ }
+
+ /**
+ Look at the next character to parse, but do not accept it.
+ */
+ unsigned char yyPeek()
+ {
+ return m_ptr[0];
+ }
+
+ /**
+ Look ahead at some character to parse.
+ @param n offset of the character to look up
+ */
+ unsigned char yyPeekn(int n)
+ {
+ return m_ptr[n];
+ }
+
+ /**
+ Cancel the effect of the last yyGet() or yySkip().
+ Note that the echo mode should not change between calls to yyGet / yySkip
+ and yyUnget. The caller is responsible for ensuring that.
+ */
+ void yyUnget()
+ {
+ m_ptr--;
+ if (m_echo)
+ m_cpp_ptr--;
+ }
+
+ /**
+ Accept a character, by advancing the input stream.
+ */
+ void yySkip()
+ {
+ if (m_echo)
+ *m_cpp_ptr++ = *m_ptr++;
+ else
+ m_ptr++;
+ }
+
+ /**
+ Accept multiple characters at once.
+ @param n the number of characters to accept.
+ */
+ void yySkipn(int n)
+ {
+ if (m_echo)
+ {
+ memcpy(m_cpp_ptr, m_ptr, n);
+ m_cpp_ptr += n;
+ }
+ m_ptr += n;
+ }
+
+ /**
+ Puts a character back into the stream, canceling
+ the effect of the last yyGet() or yySkip().
+ Note that the echo mode should not change between calls
+ to unput, get, or skip from the stream.
+ */
+ char *yyUnput(char ch)
+ {
+ *--m_ptr= ch;
+ if (m_echo)
+ m_cpp_ptr--;
+ return m_ptr;
+ }
+
+ /**
+ End of file indicator for the query text to parse.
+ @return true if there are no more characters to parse
+ */
+ bool eof()
+ {
+ return (m_ptr >= m_end_of_query);
+ }
+
+ /**
+ End of file indicator for the query text to parse.
+ @param n number of characters expected
+ @return true if there are less than n characters to parse
+ */
+ bool eof(int n)
+ {
+ return ((m_ptr + n) >= m_end_of_query);
+ }
+
+ /** Get the raw query buffer. */
+ const char *get_buf()
+ {
+ return m_buf;
+ }
+
+ /** Get the pre-processed query buffer. */
+ const char *get_cpp_buf()
+ {
+ return m_cpp_buf;
+ }
+
+ /** Get the end of the raw query buffer. */
+ const char *get_end_of_query()
+ {
+ return m_end_of_query;
+ }
+
+ /** Mark the stream position as the start of a new token. */
+ void start_token()
+ {
+ m_tok_start_prev= m_tok_start;
+ m_tok_start= m_ptr;
+ m_tok_end= m_ptr;
+
+ m_cpp_tok_start_prev= m_cpp_tok_start;
+ m_cpp_tok_start= m_cpp_ptr;
+ m_cpp_tok_end= m_cpp_ptr;
+ }
+
+ /**
+ Adjust the starting position of the current token.
+ This is used to compensate for starting whitespace.
+ */
+ void restart_token()
+ {
+ m_tok_start= m_ptr;
+ m_cpp_tok_start= m_cpp_ptr;
+ }
+
+ /** Get the token start position, in the raw buffer. */
+ const char *get_tok_start()
+ {
+ return m_tok_start;
+ }
+
+ /** Get the token start position, in the pre-processed buffer. */
+ const char *get_cpp_tok_start()
+ {
+ return m_cpp_tok_start;
+ }
+
+ /** Get the token end position, in the raw buffer. */
+ const char *get_tok_end()
+ {
+ return m_tok_end;
+ }
+
+ /** Get the token end position, in the pre-processed buffer. */
+ const char *get_cpp_tok_end()
+ {
+ return m_cpp_tok_end;
+ }
+
+ /** Get the previous token start position, in the raw buffer. */
+ const char *get_tok_start_prev()
+ {
+ return m_tok_start_prev;
+ }
+
+ /** Get the current stream pointer, in the raw buffer. */
+ const char *get_ptr()
+ {
+ return m_ptr;
+ }
+
+ /** Get the current stream pointer, in the pre-processed buffer. */
+ const char *get_cpp_ptr()
+ {
+ return m_cpp_ptr;
+ }
+
+ /** Get the length of the current token, in the raw buffer. */
+ uint yyLength()
+ {
+ /*
+ The assumption is that the lexical analyser is always 1 character ahead,
+ which the -1 account for.
+ */
+ DBUG_ASSERT(m_ptr > m_tok_start);
+ return (uint) ((m_ptr - m_tok_start) - 1);
+ }
+
+ /** Get the utf8-body string. */
+ const char *get_body_utf8_str()
+ {
+ return m_body_utf8;
+ }
+
+ /** Get the utf8-body length. */
+ uint get_body_utf8_length()
+ {
+ return (uint) (m_body_utf8_ptr - m_body_utf8);
+ }
+
+ void body_utf8_start(THD *thd, const char *begin_ptr);
+ void body_utf8_append(const char *ptr);
+ void body_utf8_append(const char *ptr, const char *end_ptr);
+ void body_utf8_append_literal(THD *thd,
+ const LEX_STRING *txt,
+ CHARSET_INFO *txt_cs,
+ const char *end_ptr);
+
+ /** Current thread. */
+ THD *m_thd;
+
+ /** Current line number. */
+ uint yylineno;
+
+ /** Length of the last token parsed. */
+ uint yytoklen;
+
+ /** Interface with bison, value of the last token parsed. */
+ LEX_YYSTYPE yylval;
+
+ /**
+ LALR(2) resolution, look ahead token.
+ Value of the next token to return, if any,
+ or -1, if no token was parsed in advance.
+ Note: 0 is a legal token, and represents YYEOF.
+ */
+ int lookahead_token;
+
+ /** LALR(2) resolution, value of the look ahead token.*/
+ LEX_YYSTYPE lookahead_yylval;
+
+ void add_digest_token(uint token, LEX_YYSTYPE yylval);
+
+ void reduce_digest_token(uint token_left, uint token_right);
+
+private:
+ /** Pointer to the current position in the raw input stream. */
+ char *m_ptr;
+
+ /** Starting position of the last token parsed, in the raw buffer. */
+ const char *m_tok_start;
+
+ /** Ending position of the previous token parsed, in the raw buffer. */
+ const char *m_tok_end;
+
+ /** End of the query text in the input stream, in the raw buffer. */
+ const char *m_end_of_query;
+
+ /** Starting position of the previous token parsed, in the raw buffer. */
+ const char *m_tok_start_prev;
+
+ /** Begining of the query text in the input stream, in the raw buffer. */
+ const char *m_buf;
+
+ /** Length of the raw buffer. */
+ uint m_buf_length;
+
+ /** Echo the parsed stream to the pre-processed buffer. */
+ bool m_echo;
+ bool m_echo_saved;
+
+ /** Pre-processed buffer. */
+ char *m_cpp_buf;
+
+ /** Pointer to the current position in the pre-processed input stream. */
+ char *m_cpp_ptr;
+
+ /**
+ Starting position of the last token parsed,
+ in the pre-processed buffer.
+ */
+ const char *m_cpp_tok_start;
+
+ /**
+ Starting position of the previous token parsed,
+ in the pre-procedded buffer.
+ */
+ const char *m_cpp_tok_start_prev;
+
+ /**
+ Ending position of the previous token parsed,
+ in the pre-processed buffer.
+ */
+ const char *m_cpp_tok_end;
+
+ /** UTF8-body buffer created during parsing. */
+ char *m_body_utf8;
+
+ /** Pointer to the current position in the UTF8-body buffer. */
+ char *m_body_utf8_ptr;
+
+ /**
+ Position in the pre-processed buffer. The query from m_cpp_buf to
+ m_cpp_utf_processed_ptr is converted to UTF8-body.
+ */
+ const char *m_cpp_utf8_processed_ptr;
+
+public:
+
+ /** Current state of the lexical analyser. */
+ enum my_lex_states next_state;
+
+ /**
+ Position of ';' in the stream, to delimit multiple queries.
+ This delimiter is in the raw buffer.
+ */
+ const char *found_semicolon;
+
+ /** Token character bitmaps, to detect 7bit strings. */
+ uchar tok_bitmap;
+
+ /** SQL_MODE = IGNORE_SPACE. */
+ bool ignore_space;
+
+ /**
+ TRUE if we're parsing a prepared statement: in this mode
+ we should allow placeholders.
+ */
+ bool stmt_prepare_mode;
+ /**
+ TRUE if we should allow multi-statements.
+ */
+ bool multi_statements;
+
+ /** State of the lexical analyser for comments. */
+ enum_comment_state in_comment;
+ enum_comment_state in_comment_saved;
+
+ /**
+ Starting position of the TEXT_STRING or IDENT in the pre-processed
+ buffer.
+
+ NOTE: this member must be used within MYSQLlex() function only.
+ */
+ const char *m_cpp_text_start;
+
+ /**
+ Ending position of the TEXT_STRING or IDENT in the pre-processed
+ buffer.
+
+ NOTE: this member must be used within MYSQLlex() function only.
+ */
+ const char *m_cpp_text_end;
+
+ /**
+ Character set specified by the character-set-introducer.
+
+ NOTE: this member must be used within MYSQLlex() function only.
+ */
+ CHARSET_INFO *m_underscore_cs;
+
+ /**
+ Current statement digest instrumentation.
+ */
+ sql_digest_state* m_digest;
+};
+
+/**
+ Abstract representation of a statement.
+ This class is an interface between the parser and the runtime.
+ The parser builds the appropriate sub classes of Sql_statement
+ to represent a SQL statement in the parsed tree.
+ The execute() method in the sub classes contain the runtime implementation.
+ Note that this interface is used for SQL statement recently implemented,
+ the code for older statements tend to load the LEX structure with more
+ attributes instead.
+ The recommended way to implement new statements is to sub-class
+ Sql_statement, as this improves code modularity (see the 'big switch' in
+ dispatch_command()), and decrease the total size of the LEX structure
+ (therefore saving memory in stored programs).
+*/
+class Sql_statement : public Sql_alloc
+{
+public:
+ /**
+ Execute this SQL statement.
+ @param thd the current thread.
+ @return 0 on success.
+ */
+ virtual bool execute(THD *thd) = 0;
+
+protected:
+ /**
+ Constructor.
+ @param lex the LEX structure that represents parts of this statement.
+ */
+ Sql_statement(LEX *lex)
+ : m_lex(lex)
+ {}
+
+ /** Destructor. */
+ virtual ~Sql_statement()
+ {
+ /*
+ Sql_statement objects are allocated in thd->mem_root.
+ In MySQL, the C++ destructor is never called, the underlying MEM_ROOT is
+ simply destroyed instead.
+ Do not rely on the destructor for any cleanup.
+ */
+ DBUG_ASSERT(FALSE);
+ }
+
+protected:
+ /**
+ The legacy LEX structure for this statement.
+ The LEX structure contains the existing properties of the parsed tree.
+ TODO: with time, attributes from LEX should move to sub classes of
+ Sql_statement, so that the parser only builds Sql_statement objects
+ with the minimum set of attributes, instead of a LEX structure that
+ contains the collection of every possible attribute.
+ */
+ LEX *m_lex;
+};
+
+
+class Delete_plan;
+class SQL_SELECT;
+
+class Explain_query;
+class Explain_update;
+
+/*
+ Query plan of a single-table UPDATE.
+ (This is actually a plan for single-table DELETE also)
+*/
+
+class Update_plan
+{
+protected:
+ bool impossible_where;
+ bool no_partitions;
+public:
+ /*
+ When single-table UPDATE updates a VIEW, that VIEW's select is still
+ listed as the first child. When we print EXPLAIN, it looks like a
+ subquery.
+ In order to get rid of it, updating_a_view=TRUE means that first child
+ select should not be shown when printing EXPLAIN.
+ */
+ bool updating_a_view;
+
+ /* Allocate things there */
+ MEM_ROOT *mem_root;
+
+ TABLE *table;
+ SQL_SELECT *select;
+ uint index;
+ ha_rows scanned_rows;
+ /*
+ Top-level select_lex. Most of its fields are not used, we need it only to
+ get to the subqueries.
+ */
+ SELECT_LEX *select_lex;
+
+ key_map possible_keys;
+ bool using_filesort;
+ bool using_io_buffer;
+
+ /* Set this plan to be a plan to do nothing because of impossible WHERE */
+ void set_impossible_where() { impossible_where= true; }
+ void set_no_partitions() { no_partitions= true; }
+
+ void save_explain_data(Explain_query *query);
+ void save_explain_data_intern(Explain_query *query, Explain_update *eu);
+ virtual ~Update_plan() {}
+
+ Update_plan(MEM_ROOT *mem_root_arg) :
+ impossible_where(false), no_partitions(false),
+ mem_root(mem_root_arg),
+ using_filesort(false), using_io_buffer(false)
+ {}
+};
+
+
+/* Query plan of a single-table DELETE */
+class Delete_plan : public Update_plan
+{
+ bool deleting_all_rows;
+public:
+
+ /* Construction functions */
+ Delete_plan(MEM_ROOT *mem_root_arg) :
+ Update_plan(mem_root_arg),
+ deleting_all_rows(false)
+ {}
+
+ /* Set this query plan to be a plan to make a call to h->delete_all_rows() */
+ void set_delete_all_rows(ha_rows rows_arg)
+ {
+ deleting_all_rows= true;
+ scanned_rows= rows_arg;
+ }
+
+ void save_explain_data(Explain_query *query);
+};
+
+
+/* The state of the lex parsing. This is saved in the THD struct */
+
+struct LEX: public Query_tables_list
+{
+ SELECT_LEX_UNIT unit; /* most upper unit */
+ SELECT_LEX select_lex; /* first SELECT_LEX */
+ /* current SELECT_LEX in parsing */
+ SELECT_LEX *current_select;
+ /* list of all SELECT_LEX */
+ SELECT_LEX *all_selects_list;
+
+ /* Query Plan Footprint of a currently running select */
+ Explain_query *explain;
+
+ char *length,*dec,*change;
+ LEX_STRING name;
+ char *help_arg;
+ char *backup_dir; /* For RESTORE/BACKUP */
+ char* to_log; /* For PURGE MASTER LOGS TO */
+ char* x509_subject,*x509_issuer,*ssl_cipher;
+ String *wild; /* Wildcard in SHOW {something} LIKE 'wild'*/
+ sql_exchange *exchange;
+ select_result *result;
+ Item *default_value, *on_update_value;
+ LEX_STRING comment, ident;
+ LEX_USER *grant_user;
+ XID *xid;
+ THD *thd;
+ Virtual_column_info *vcol_info;
+
+ /* maintain a list of used plugins for this LEX */
+ DYNAMIC_ARRAY plugins;
+ plugin_ref plugins_static_buffer[INITIAL_LEX_PLUGIN_LIST_SIZE];
+
+ CHARSET_INFO *charset;
+ bool text_string_is_7bit;
+
+ /** SELECT of CREATE VIEW statement */
+ LEX_STRING create_view_select;
+
+ /** Start of 'ON table', in trigger statements. */
+ const char* raw_trg_on_table_name_begin;
+ /** End of 'ON table', in trigger statements. */
+ const char* raw_trg_on_table_name_end;
+
+ /* Partition info structure filled in by PARTITION BY parse part */
+ partition_info *part_info;
+
+ /*
+ The definer of the object being created (view, trigger, stored routine).
+ I.e. the value of DEFINER clause.
+ */
+ LEX_USER *definer;
+
+ List<Key_part_spec> col_list;
+ List<Key_part_spec> ref_list;
+ List<String> interval_list;
+ List<LEX_USER> users_list;
+ List<LEX_COLUMN> columns;
+ List<Item> *insert_list,field_list,value_list,update_list;
+ List<List_item> many_values;
+ List<set_var_base> var_list;
+ List<Item_func_set_user_var> set_var_list; // in-query assignment list
+ List<Item_param> param_list;
+ List<LEX_STRING> view_list; // view list (list of field names in view)
+ List<LEX_STRING> *column_list; // list of column names (in ANALYZE)
+ List<LEX_STRING> *index_list; // list of index names (in ANALYZE)
+ /*
+ A stack of name resolution contexts for the query. This stack is used
+ at parse time to set local name resolution contexts for various parts
+ of a query. For example, in a JOIN ... ON (some_condition) clause the
+ Items in 'some_condition' must be resolved only against the operands
+ of the the join, and not against the whole clause. Similarly, Items in
+ subqueries should be resolved against the subqueries (and outer queries).
+ The stack is used in the following way: when the parser detects that
+ all Items in some clause need a local context, it creates a new context
+ and pushes it on the stack. All newly created Items always store the
+ top-most context in the stack. Once the parser leaves the clause that
+ required a local context, the parser pops the top-most context.
+ */
+ List<Name_resolution_context> context_stack;
+
+ SQL_I_List<ORDER> proc_list;
+ SQL_I_List<TABLE_LIST> auxiliary_table_list, save_list;
+ Create_field *last_field;
+ Item_sum *in_sum_func;
+ udf_func udf;
+ HA_CHECK_OPT check_opt; // check/repair options
+ HA_CREATE_INFO create_info;
+ KEY_CREATE_INFO key_create_info;
+ LEX_MASTER_INFO mi; // used by CHANGE MASTER
+ LEX_SERVER_OPTIONS server_options;
+ LEX_STRING relay_log_connection_name;
+ USER_RESOURCES mqh;
+ LEX_RESET_SLAVE reset_slave_info;
+ ulonglong type;
+ /* The following is used by KILL */
+ killed_state kill_signal;
+ killed_type kill_type;
+ /*
+ This variable is used in post-parse stage to declare that sum-functions,
+ or functions which have sense only if GROUP BY is present, are allowed.
+ For example in a query
+ SELECT ... FROM ...WHERE MIN(i) == 1 GROUP BY ... HAVING MIN(i) > 2
+ MIN(i) in the WHERE clause is not allowed in the opposite to MIN(i)
+ in the HAVING clause. Due to possible nesting of select construct
+ the variable can contain 0 or 1 for each nest level.
+ */
+ nesting_map allow_sum_func;
+
+ Sql_cmd *m_sql_cmd;
+
+ /*
+ Usually `expr` rule of yacc is quite reused but some commands better
+ not support subqueries which comes standard with this rule, like
+ KILL, HA_READ, CREATE/ALTER EVENT etc. Set this to `false` to get
+ syntax error back.
+ */
+ bool expr_allows_subselect;
+ /*
+ A special command "PARSE_VCOL_EXPR" is defined for the parser
+ to translate a defining expression of a virtual column into an
+ Item object.
+ The following flag is used to prevent other applications to use
+ this command.
+ */
+ bool parse_vcol_expr;
+
+ enum SSL_type ssl_type; /* defined in violite.h */
+ enum enum_duplicates duplicates;
+ enum enum_tx_isolation tx_isolation;
+ enum enum_ha_read_modes ha_read_mode;
+ union {
+ enum ha_rkey_function ha_rkey_mode;
+ enum xa_option_words xa_opt;
+ bool with_admin_option; // GRANT role
+ bool with_persistent_for_clause; // uses PERSISTENT FOR clause (in ANALYZE)
+ };
+ enum enum_var_type option_type;
+ enum enum_view_create_mode create_view_mode;
+ enum enum_drop_mode drop_mode;
+
+ uint profile_query_id;
+ uint profile_options;
+ uint uint_geom_type;
+ uint grant, grant_tot_col, which_columns;
+ enum Foreign_key::fk_match_opt fk_match_option;
+ enum Foreign_key::fk_option fk_update_opt;
+ enum Foreign_key::fk_option fk_delete_opt;
+ uint slave_thd_opt, start_transaction_opt;
+ int nest_level;
+ /*
+ In LEX representing update which were transformed to multi-update
+ stores total number of tables. For LEX representing multi-delete
+ holds number of tables from which we will delete records.
+ */
+ uint table_count;
+ uint8 describe;
+ /*
+ A flag that indicates what kinds of derived tables are present in the
+ query (0 if no derived tables, otherwise a combination of flags
+ DERIVED_SUBQUERY and DERIVED_VIEW).
+ */
+ uint8 derived_tables;
+ uint16 create_view_algorithm;
+ uint8 create_view_check;
+ uint8 context_analysis_only;
+ bool drop_temporary, local_file;
+ bool check_exists;
+ bool autocommit;
+ bool verbose, no_write_to_binlog;
+
+ enum enum_yes_no_unknown tx_chain, tx_release;
+ bool safe_to_cache_query;
+ bool subqueries, ignore;
+ st_parsing_options parsing_options;
+ Alter_info alter_info;
+ /*
+ For CREATE TABLE statement last element of table list which is not
+ part of SELECT or LIKE part (i.e. either element for table we are
+ creating or last of tables referenced by foreign keys).
+ */
+ TABLE_LIST *create_last_non_select_table;
+ /* Prepared statements SQL syntax:*/
+ LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */
+ /*
+ Prepared statement query text or name of variable that holds the
+ prepared statement (in PREPARE ... queries)
+ */
+ LEX_STRING prepared_stmt_code;
+ /* If true, prepared_stmt_code is a name of variable that holds the query */
+ bool prepared_stmt_code_is_varref;
+ /* Names of user variables holding parameters (in EXECUTE) */
+ List<LEX_STRING> prepared_stmt_params;
+ sp_head *sphead;
+ sp_name *spname;
+ bool sp_lex_in_use; /* Keep track on lex usage in SPs for error handling */
+ bool all_privileges;
+ bool proxy_priv;
+
+ sp_pcontext *spcont;
+
+ st_sp_chistics sp_chistics;
+
+ Event_parse_data *event_parse_data;
+
+ bool only_view; /* used for SHOW CREATE TABLE/VIEW */
+ /*
+ field_list was created for view and should be removed before PS/SP
+ rexecuton
+ */
+ bool empty_field_list_on_rset;
+ /*
+ view created to be run from definer (standard behaviour)
+ */
+ uint8 create_view_suid;
+ /* Characterstics of trigger being created */
+ st_trg_chistics trg_chistics;
+ /*
+ List of all items (Item_trigger_field objects) representing fields in
+ old/new version of row in trigger. We use this list for checking whenever
+ all such fields are valid at trigger creation time and for binding these
+ fields to TABLE object at table open (altough for latter pointer to table
+ being opened is probably enough).
+ */
+ SQL_I_List<Item_trigger_field> trg_table_fields;
+
+ /*
+ stmt_definition_begin is intended to point to the next word after
+ DEFINER-clause in the following statements:
+ - CREATE TRIGGER (points to "TRIGGER");
+ - CREATE PROCEDURE (points to "PROCEDURE");
+ - CREATE FUNCTION (points to "FUNCTION" or "AGGREGATE");
+ - CREATE EVENT (points to "EVENT")
+
+ This pointer is required to add possibly omitted DEFINER-clause to the
+ DDL-statement before dumping it to the binlog.
+
+ keyword_delayed_begin_offset is the offset to the beginning of the DELAYED
+ keyword in INSERT DELAYED statement. keyword_delayed_end_offset is the
+ offset to the character right after the DELAYED keyword.
+ */
+ union {
+ const char *stmt_definition_begin;
+ uint keyword_delayed_begin_offset;
+ };
+
+ union {
+ const char *stmt_definition_end;
+ uint keyword_delayed_end_offset;
+ };
+
+ /**
+ Collects create options for Field and KEY
+ */
+ engine_option_value *option_list, *option_list_last;
+
+ /**
+ During name resolution search only in the table list given by
+ Name_resolution_context::first_name_resolution_table and
+ Name_resolution_context::last_name_resolution_table
+ (see Item_field::fix_fields()).
+ */
+ bool use_only_table_context;
+
+ /*
+ Reference to a struct that contains information in various commands
+ to add/create/drop/change table spaces.
+ */
+ st_alter_tablespace *alter_tablespace_info;
+
+ bool escape_used;
+ bool is_lex_started; /* If lex_start() did run. For debugging. */
+
+ /*
+ The set of those tables whose fields are referenced in all subqueries
+ of the query.
+ TODO: possibly this it is incorrect to have used tables in LEX because
+ with subquery, it is not clear what does the field mean. To fix this
+ we should aggregate used tables information for selected expressions
+ into the select_lex.
+ */
+ table_map used_tables;
+ /**
+ Maximum number of rows and/or keys examined by the query, both read,
+ changed or written. This is the argument of LIMIT ROWS EXAMINED.
+ The limit is represented by two variables - the Item is needed because
+ in case of parameters we have to delay its evaluation until execution.
+ Once evaluated, its value is stored in examined_rows_limit_cnt.
+ */
+ Item *limit_rows_examined;
+ ulonglong limit_rows_examined_cnt;
+ inline void set_limit_rows_examined()
+ {
+ if (limit_rows_examined)
+ limit_rows_examined_cnt= limit_rows_examined->val_uint();
+ else
+ limit_rows_examined_cnt= ULONGLONG_MAX;
+ }
+
+ LEX();
+
+ virtual ~LEX()
+ {
+ destroy_query_tables_list();
+ plugin_unlock_list(NULL, (plugin_ref *)plugins.buffer, plugins.elements);
+ delete_dynamic(&plugins);
+ }
+
+ inline bool is_ps_or_view_context_analysis()
+ {
+ return (context_analysis_only &
+ (CONTEXT_ANALYSIS_ONLY_PREPARE |
+ CONTEXT_ANALYSIS_ONLY_VCOL_EXPR |
+ CONTEXT_ANALYSIS_ONLY_VIEW));
+ }
+
+ inline bool is_view_context_analysis()
+ {
+ return (context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW);
+ }
+
+ inline void uncacheable(uint8 cause)
+ {
+ safe_to_cache_query= 0;
+
+ /*
+ There are no sense to mark select_lex and union fields of LEX,
+ but we should merk all subselects as uncacheable from current till
+ most upper
+ */
+ SELECT_LEX *sl;
+ SELECT_LEX_UNIT *un;
+ for (sl= current_select, un= sl->master_unit();
+ un != &unit;
+ sl= sl->outer_select(), un= sl->master_unit())
+ {
+ sl->uncacheable|= cause;
+ un->uncacheable|= cause;
+ }
+ select_lex.uncacheable|= cause;
+ }
+ void set_trg_event_type_for_tables();
+
+ TABLE_LIST *unlink_first_table(bool *link_to_local);
+ void link_first_table_back(TABLE_LIST *first, bool link_to_local);
+ void first_lists_tables_same();
+
+ bool can_be_merged();
+ bool can_use_merged();
+ bool can_not_use_merged();
+ bool only_view_structure();
+ bool need_correct_ident();
+ uint8 get_effective_with_check(TABLE_LIST *view);
+ /*
+ Is this update command where 'WHITH CHECK OPTION' clause is important
+
+ SYNOPSIS
+ LEX::which_check_option_applicable()
+
+ RETURN
+ TRUE have to take 'WHITH CHECK OPTION' clause into account
+ FALSE 'WHITH CHECK OPTION' clause do not need
+ */
+ inline bool which_check_option_applicable()
+ {
+ switch (sql_command) {
+ case SQLCOM_UPDATE:
+ case SQLCOM_UPDATE_MULTI:
+ case SQLCOM_DELETE:
+ case SQLCOM_DELETE_MULTI:
+ case SQLCOM_INSERT:
+ case SQLCOM_INSERT_SELECT:
+ case SQLCOM_REPLACE:
+ case SQLCOM_REPLACE_SELECT:
+ case SQLCOM_LOAD:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+ }
+
+ void cleanup_after_one_table_open();
+
+ bool push_context(Name_resolution_context *context)
+ {
+ return context_stack.push_front(context);
+ }
+
+ void pop_context()
+ {
+ context_stack.pop();
+ }
+
+ bool copy_db_to(char **p_db, size_t *p_db_length) const;
+
+ Name_resolution_context *current_context()
+ {
+ return context_stack.head();
+ }
+ /*
+ Restore the LEX and THD in case of a parse error.
+ */
+ static void cleanup_lex_after_parse_error(THD *thd);
+
+ void reset_n_backup_query_tables_list(Query_tables_list *backup);
+ void restore_backup_query_tables_list(Query_tables_list *backup);
+
+ bool table_or_sp_used();
+ bool is_partition_management() const;
+
+ /**
+ @brief check if the statement is a single-level join
+ @return result of the check
+ @retval TRUE The statement doesn't contain subqueries, unions and
+ stored procedure calls.
+ @retval FALSE There are subqueries, UNIONs or stored procedure calls.
+ */
+ bool is_single_level_stmt()
+ {
+ /*
+ This check exploits the fact that the last added to all_select_list is
+ on its top. So select_lex (as the first added) will be at the tail
+ of the list.
+ */
+ if (&select_lex == all_selects_list && !sroutines.records)
+ {
+ DBUG_ASSERT(!all_selects_list->next_select_in_list());
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ bool save_prep_leaf_tables();
+
+ int print_explain(select_result_sink *output, uint8 explain_flags,
+ bool *printed_anything);
+};
+
+
+/**
+ Set_signal_information is a container used in the parsed tree to represent
+ the collection of assignments to condition items in the SIGNAL and RESIGNAL
+ statements.
+*/
+class Set_signal_information
+{
+public:
+ /** Empty default constructor, use clear() */
+ Set_signal_information() {}
+
+ /** Copy constructor. */
+ Set_signal_information(const Set_signal_information& set);
+
+ /** Destructor. */
+ ~Set_signal_information()
+ {}
+
+ /** Clear all items. */
+ void clear();
+
+ /**
+ For each condition item assignment, m_item[] contains the parsed tree
+ that represents the expression assigned, if any.
+ m_item[] is an array indexed by Diag_condition_item_name.
+ */
+ Item *m_item[LAST_DIAG_SET_PROPERTY+1];
+};
+
+
+/**
+ The internal state of the syntax parser.
+ This object is only available during parsing,
+ and is private to the syntax parser implementation (sql_yacc.yy).
+*/
+class Yacc_state
+{
+public:
+ Yacc_state()
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ yacc_yyss= NULL;
+ yacc_yyvs= NULL;
+ m_set_signal_info.clear();
+ m_lock_type= TL_READ_DEFAULT;
+ m_mdl_type= MDL_SHARED_READ;
+ }
+
+ ~Yacc_state();
+
+ /**
+ Reset part of the state which needs resetting before parsing
+ substatement.
+ */
+ void reset_before_substatement()
+ {
+ m_lock_type= TL_READ_DEFAULT;
+ m_mdl_type= MDL_SHARED_READ;
+ }
+
+ /**
+ Bison internal state stack, yyss, when dynamically allocated using
+ my_yyoverflow().
+ */
+ uchar *yacc_yyss;
+
+ /**
+ Bison internal semantic value stack, yyvs, when dynamically allocated using
+ my_yyoverflow().
+ */
+ uchar *yacc_yyvs;
+
+ /**
+ Fragments of parsed tree,
+ used during the parsing of SIGNAL and RESIGNAL.
+ */
+ Set_signal_information m_set_signal_info;
+
+ /**
+ Type of lock to be used for tables being added to the statement's
+ table list in table_factor, table_alias_ref, single_multi and
+ table_wild_one rules.
+ Statements which use these rules but require lock type different
+ from one specified by this member have to override it by using
+ st_select_lex::set_lock_for_tables() method.
+
+ The default value of this member is TL_READ_DEFAULT. The only two
+ cases in which we change it are:
+ - When parsing SELECT HIGH_PRIORITY.
+ - Rule for DELETE. In which we use this member to pass information
+ about type of lock from delete to single_multi part of rule.
+
+ We should try to avoid introducing new use cases as we would like
+ to get rid of this member eventually.
+ */
+ thr_lock_type m_lock_type;
+
+ /**
+ The type of requested metadata lock for tables added to
+ the statement table list.
+ */
+ enum_mdl_type m_mdl_type;
+
+ /*
+ TODO: move more attributes from the LEX structure here.
+ */
+};
+
+/**
+ Input parameters to the parser.
+*/
+struct Parser_input
+{
+ bool m_compute_digest;
+
+ Parser_input()
+ : m_compute_digest(false)
+ {}
+};
+
+/**
+ Internal state of the parser.
+ The complete state consist of:
+ - state data used during lexical parsing,
+ - state data used during syntactic parsing.
+*/
+class Parser_state
+{
+public:
+ Parser_state()
+ : m_yacc()
+ {}
+
+ /**
+ Object initializer. Must be called before usage.
+
+ @retval FALSE OK
+ @retval TRUE Error
+ */
+ bool init(THD *thd, char *buff, unsigned int length)
+ {
+ return m_lip.init(thd, buff, length);
+ }
+
+ ~Parser_state()
+ {}
+
+ Parser_input m_input;
+ Lex_input_stream m_lip;
+ Yacc_state m_yacc;
+
+ /**
+ Current performance digest instrumentation.
+ */
+ PSI_digest_locker* m_digest_psi;
+
+ void reset(char *found_semicolon, unsigned int length)
+ {
+ m_lip.reset(found_semicolon, length);
+ m_yacc.reset();
+ }
+};
+
+extern sql_digest_state *
+digest_add_token(sql_digest_state *state, uint token, LEX_YYSTYPE yylval);
+
+extern sql_digest_state *
+digest_reduce_token(sql_digest_state *state, uint token_left, uint token_right);
+
+struct st_lex_local: public LEX
+{
+ static void *operator new(size_t size) throw()
+ {
+ return sql_alloc(size);
+ }
+ static void *operator new(size_t size, MEM_ROOT *mem_root) throw()
+ {
+ return (void*) alloc_root(mem_root, (uint) size);
+ }
+ static void operator delete(void *ptr,size_t size)
+ { TRASH(ptr, size); }
+ static void operator delete(void *ptr, MEM_ROOT *mem_root)
+ { /* Never called */ }
+};
+
+extern void lex_init(void);
+extern void lex_free(void);
+extern void lex_start(THD *thd);
+extern void lex_end(LEX *lex);
+void end_lex_with_single_table(THD *thd, TABLE *table, LEX *old_lex);
+int init_lex_with_single_table(THD *thd, TABLE *table, LEX *lex);
+extern int MYSQLlex(union YYSTYPE *yylval, THD *thd);
+
+extern void trim_whitespace(CHARSET_INFO *cs, LEX_STRING *str);
+
+extern bool is_lex_native_function(const LEX_STRING *name);
+
+/**
+ @} (End of group Semantic_Analysis)
+*/
+
+void my_missing_function_error(const LEX_STRING &token, const char *name);
+bool is_keyword(const char *name, uint len);
+
+#endif /* MYSQL_SERVER */
+#endif /* SQL_LEX_INCLUDED */
diff --git a/sql/sql_lifo_buffer.h b/sql/sql_lifo_buffer.h
new file mode 100644
index 00000000000..feec4aeb4c2
--- /dev/null
+++ b/sql/sql_lifo_buffer.h
@@ -0,0 +1,359 @@
+/*
+ Copyright (c) 2010, 2011, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @defgroup Bi-directional LIFO buffers used by DS-MRR implementation
+ @{
+*/
+
+class Forward_lifo_buffer;
+class Backward_lifo_buffer;
+
+
+/*
+ A base class for in-memory buffer used by DS-MRR implementation. Common
+ properties:
+ - The buffer is last-in-first-out, i.e. elements that are written last are
+ read first.
+ - The buffer contains fixed-size elements. The elements are either atomic
+ byte sequences or pairs of them.
+ - The buffer resides in the memory provided by the user. It is possible to
+ = dynamically (ie. between write operations) add ajacent memory space to
+ the buffer
+ = dynamically remove unused space from the buffer.
+ The intent of this is to allow to have two buffers on adjacent memory
+ space, one is being read from (and so its space shrinks), while the other
+ is being written to (and so it needs more and more space).
+
+ There are two concrete classes, Forward_lifo_buffer and Backward_lifo_buffer.
+*/
+
+class Lifo_buffer
+{
+protected:
+ size_t size1;
+ size_t size2;
+
+public:
+ /**
+ write() will put into buffer size1 bytes pointed by write_ptr1. If
+ size2!=0, then they will be accompanied by size2 bytes pointed by
+ write_ptr2.
+ */
+ uchar *write_ptr1;
+ uchar *write_ptr2;
+
+ /**
+ read() will do reading by storing pointers to read data into read_ptr1 or
+ into (read_ptr1, read_ptr2), depending on whether the buffer was set to
+ store single objects or pairs.
+ */
+ uchar *read_ptr1;
+ uchar *read_ptr2;
+
+protected:
+ uchar *start; /**< points to start of buffer space */
+ uchar *end; /**< points to just beyond the end of buffer space */
+public:
+
+ enum enum_direction {
+ BACKWARD=-1, /**< buffer is filled/read from bigger to smaller memory addresses */
+ FORWARD=1 /**< buffer is filled/read from smaller to bigger memory addresses */
+ };
+
+ virtual enum_direction type() = 0;
+
+ /* Buffer space control functions */
+
+ /** Let the buffer store data in the given space. */
+ void set_buffer_space(uchar *start_arg, uchar *end_arg)
+ {
+ start= start_arg;
+ end= end_arg;
+ if (end != start)
+ TRASH(start, end - start);
+ reset();
+ }
+
+ /**
+ Specify where write() should get the source data from, as well as source
+ data size.
+ */
+ void setup_writing(size_t len1, size_t len2)
+ {
+ size1= len1;
+ size2= len2;
+ }
+
+ /**
+ Specify where read() should store pointers to read data, as well as read
+ data size. The sizes must match those passed to setup_writing().
+ */
+ void setup_reading(size_t len1, size_t len2)
+ {
+ DBUG_ASSERT(len1 == size1);
+ DBUG_ASSERT(len2 == size2);
+ }
+
+ bool can_write()
+ {
+ return have_space_for(size1 + size2);
+ }
+ virtual void write() = 0;
+
+ bool is_empty() { return used_size() == 0; }
+ virtual bool read() = 0;
+
+ void sort(qsort2_cmp cmp_func, void *cmp_func_arg)
+ {
+ size_t elem_size= size1 + size2;
+ size_t n_elements= used_size() / elem_size;
+ my_qsort2(used_area(), n_elements, elem_size, cmp_func, cmp_func_arg);
+ }
+
+ virtual void reset() = 0;
+ virtual uchar *end_of_space() = 0;
+protected:
+ virtual size_t used_size() = 0;
+
+ /* To be used only by iterator class: */
+ virtual uchar *get_pos()= 0;
+ virtual bool read(uchar **position, uchar **ptr1, uchar **ptr2)= 0;
+ friend class Lifo_buffer_iterator;
+public:
+ virtual bool have_space_for(size_t bytes) = 0;
+
+ virtual void remove_unused_space(uchar **unused_start, uchar **unused_end)=0;
+ virtual uchar *used_area() = 0;
+ virtual ~Lifo_buffer() {};
+};
+
+
+/**
+ Forward LIFO buffer
+
+ The buffer that is being written to from start to end and read in the
+ reverse. 'pos' points to just beyond the end of used space.
+
+ It is possible to grow/shink the buffer at the end bound
+
+ used space unused space
+ *==============*-----------------*
+ ^ ^ ^
+ | | +--- end
+ | +---- pos
+ +--- start
+*/
+
+class Forward_lifo_buffer: public Lifo_buffer
+{
+ uchar *pos;
+public:
+ enum_direction type() { return FORWARD; }
+ size_t used_size()
+ {
+ return (size_t)(pos - start);
+ }
+ void reset()
+ {
+ pos= start;
+ }
+ uchar *end_of_space() { return pos; }
+ bool have_space_for(size_t bytes)
+ {
+ return (pos + bytes < end);
+ }
+
+ void write()
+ {
+ write_bytes(write_ptr1, size1);
+ if (size2)
+ write_bytes(write_ptr2, size2);
+ }
+ void write_bytes(const uchar *data, size_t bytes)
+ {
+ DBUG_ASSERT(have_space_for(bytes));
+ memcpy(pos, data, bytes);
+ pos += bytes;
+ }
+ bool have_data(uchar *position, size_t bytes)
+ {
+ return ((position - start) >= (ptrdiff_t)bytes);
+ }
+ uchar *read_bytes(uchar **position, size_t bytes)
+ {
+ DBUG_ASSERT(have_data(*position, bytes));
+ *position= (*position) - bytes;
+ return *position;
+ }
+ bool read() { return read(&pos, &read_ptr1, &read_ptr2); }
+ bool read(uchar **position, uchar **ptr1, uchar **ptr2)
+ {
+ if (!have_data(*position, size1 + size2))
+ return TRUE;
+ if (size2)
+ *ptr2= read_bytes(position, size2);
+ *ptr1= read_bytes(position, size1);
+ return FALSE;
+ }
+ void remove_unused_space(uchar **unused_start, uchar **unused_end)
+ {
+ DBUG_ASSERT(0); /* Don't need this yet */
+ }
+ /**
+ Add more space to the buffer. The caller is responsible that the space
+ being added is adjacent to the end of the buffer.
+
+ @param unused_start Start of space
+ @param unused_end End of space
+ */
+ void grow(uchar *unused_start, uchar *unused_end)
+ {
+ DBUG_ASSERT(unused_end >= unused_start);
+ DBUG_ASSERT(end == unused_start);
+ TRASH(unused_start, unused_end - unused_start);
+ end= unused_end;
+ }
+ /* Return pointer to start of the memory area that is occupied by the data */
+ uchar *used_area() { return start; }
+ friend class Lifo_buffer_iterator;
+ uchar *get_pos() { return pos; }
+};
+
+
+
+/**
+ Backward LIFO buffer
+
+ The buffer that is being written to from start to end and read in the
+ reverse. 'pos' points to the start of used space.
+
+ It is possible to grow/shink the buffer at the start.
+
+ unused space used space
+ *--------------*=================*
+ ^ ^ ^
+ | | +--- end
+ | +---- pos
+ +--- start
+*/
+class Backward_lifo_buffer: public Lifo_buffer
+{
+ uchar *pos;
+public:
+ enum_direction type() { return BACKWARD; }
+
+ size_t used_size()
+ {
+ return (size_t)(end - pos);
+ }
+ void reset()
+ {
+ pos= end;
+ }
+ uchar *end_of_space() { return end; }
+ bool have_space_for(size_t bytes)
+ {
+ return (pos - bytes >= start);
+ }
+ void write()
+ {
+ if (write_ptr2)
+ write_bytes(write_ptr2, size2);
+ write_bytes(write_ptr1, size1);
+ }
+ void write_bytes(const uchar *data, size_t bytes)
+ {
+ DBUG_ASSERT(have_space_for(bytes));
+ pos -= bytes;
+ memcpy(pos, data, bytes);
+ }
+ bool read()
+ {
+ return read(&pos, &read_ptr1, &read_ptr2);
+ }
+ bool read(uchar **position, uchar **ptr1, uchar **ptr2)
+ {
+ if (!have_data(*position, size1 + size2))
+ return TRUE;
+ *ptr1= read_bytes(position, size1);
+ if (size2)
+ *ptr2= read_bytes(position, size2);
+ return FALSE;
+ }
+ bool have_data(uchar *position, size_t bytes)
+ {
+ return ((end - position) >= (ptrdiff_t)bytes);
+ }
+ uchar *read_bytes(uchar **position, size_t bytes)
+ {
+ DBUG_ASSERT(have_data(*position, bytes));
+ uchar *ret= *position;
+ *position= *position + bytes;
+ return ret;
+ }
+ /**
+ Stop using/return the unused part of the space
+ @param unused_start OUT Start of the unused space
+ @param unused_end OUT End of the unused space
+ */
+ void remove_unused_space(uchar **unused_start, uchar **unused_end)
+ {
+ *unused_start= start;
+ *unused_end= pos;
+ start= pos;
+ }
+ void grow(uchar *unused_start, uchar *unused_end)
+ {
+ DBUG_ASSERT(0); /* Not used for backward buffers */
+ }
+ /* Return pointer to start of the memory area that is occupied by the data */
+ uchar *used_area() { return pos; }
+ friend class Lifo_buffer_iterator;
+ uchar *get_pos() { return pos; }
+};
+
+
+/** Iterator to walk over contents of the buffer without reading from it */
+class Lifo_buffer_iterator
+{
+ uchar *pos;
+ Lifo_buffer *buf;
+
+public:
+ /* The data is read to here */
+ uchar *read_ptr1;
+ uchar *read_ptr2;
+
+ void init(Lifo_buffer *buf_arg)
+ {
+ buf= buf_arg;
+ pos= buf->get_pos();
+ }
+ /*
+ Read the next value. The calling convention is the same as buf->read()
+ has.
+
+ @retval FALSE - ok
+ @retval TRUE - EOF, reached the end of the buffer
+ */
+ bool read()
+ {
+ return buf->read(&pos, &read_ptr1, &read_ptr2);
+ }
+};
+
+
diff --git a/sql/sql_list.cc b/sql/sql_list.cc
new file mode 100644
index 00000000000..2c1b3c47d55
--- /dev/null
+++ b/sql/sql_list.cc
@@ -0,0 +1,72 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include "sql_list.h"
+
+list_node end_of_list;
+
+void free_list(I_List <i_string_pair> *list)
+{
+ i_string_pair *tmp;
+ while ((tmp= list->get()))
+ delete tmp;
+}
+
+
+void free_list(I_List <i_string> *list)
+{
+ i_string *tmp;
+ while ((tmp= list->get()))
+ delete tmp;
+}
+
+
+base_list::base_list(const base_list &rhs, MEM_ROOT *mem_root)
+{
+ if (rhs.elements)
+ {
+ /*
+ It's okay to allocate an array of nodes at once: we never
+ call a destructor for list_node objects anyway.
+ */
+ first= (list_node*) alloc_root(mem_root,
+ sizeof(list_node) * rhs.elements);
+ if (first)
+ {
+ elements= rhs.elements;
+ list_node *dst= first;
+ list_node *src= rhs.first;
+ for (; dst < first + elements - 1; dst++, src= src->next)
+ {
+ dst->info= src->info;
+ dst->next= dst + 1;
+ }
+ /* Copy the last node */
+ dst->info= src->info;
+ dst->next= &end_of_list;
+ /* Setup 'last' member */
+ last= &dst->next;
+ return;
+ }
+ }
+ elements= 0;
+ first= &end_of_list;
+ last= &first;
+}
diff --git a/sql/sql_list.h b/sql/sql_list.h
new file mode 100644
index 00000000000..7538f69766d
--- /dev/null
+++ b/sql/sql_list.h
@@ -0,0 +1,815 @@
+#ifndef INCLUDES_MYSQL_SQL_LIST_H
+#define INCLUDES_MYSQL_SQL_LIST_H
+/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "my_sys.h" /* alloc_root, TRASH, MY_WME,
+ MY_FAE, MY_ALLOW_ZERO_PTR */
+#include "m_string.h" /* bfill */
+#include "thr_malloc.h" /* sql_alloc */
+
+/* mysql standard class memory allocator */
+
+class Sql_alloc
+{
+public:
+ static void *operator new(size_t size) throw ()
+ {
+ return sql_alloc(size);
+ }
+ static void *operator new[](size_t size) throw ()
+ {
+ return sql_alloc(size);
+ }
+ static void *operator new[](size_t size, MEM_ROOT *mem_root) throw ()
+ { return alloc_root(mem_root, size); }
+ static void *operator new(size_t size, MEM_ROOT *mem_root) throw ()
+ { return alloc_root(mem_root, size); }
+ static void operator delete(void *ptr, size_t size) { TRASH(ptr, size); }
+ static void operator delete(void *ptr, MEM_ROOT *mem_root)
+ { /* never called */ }
+ static void operator delete[](void *ptr, MEM_ROOT *mem_root)
+ { /* never called */ }
+ static void operator delete[](void *ptr, size_t size) { TRASH(ptr, size); }
+#ifdef HAVE_valgrind
+ bool dummy;
+ inline Sql_alloc() :dummy(0) {}
+ inline ~Sql_alloc() {}
+#else
+ inline Sql_alloc() {}
+ inline ~Sql_alloc() {}
+#endif
+
+};
+
+
+/**
+ Simple intrusive linked list.
+
+ @remark Similar in nature to base_list, but intrusive. It keeps a
+ a pointer to the first element in the list and a indirect
+ reference to the last element.
+*/
+template <typename T>
+class SQL_I_List :public Sql_alloc
+{
+public:
+ uint elements;
+ /** The first element in the list. */
+ T *first;
+ /** A reference to the next element in the list. */
+ T **next;
+
+ SQL_I_List() { empty(); }
+
+ SQL_I_List(const SQL_I_List &tmp) : Sql_alloc()
+ {
+ elements= tmp.elements;
+ first= tmp.first;
+ next= elements ? tmp.next : &first;
+ }
+
+ inline void empty()
+ {
+ elements= 0;
+ first= NULL;
+ next= &first;
+ }
+
+ inline void link_in_list(T *element, T **next_ptr)
+ {
+ elements++;
+ (*next)= element;
+ next= next_ptr;
+ *next= NULL;
+ }
+
+ inline void save_and_clear(SQL_I_List<T> *save)
+ {
+ *save= *this;
+ empty();
+ }
+
+ inline void push_front(SQL_I_List<T> *save)
+ {
+ /* link current list last */
+ *save->next= first;
+ first= save->first;
+ elements+= save->elements;
+ }
+
+ inline void push_back(SQL_I_List<T> *save)
+ {
+ if (save->first)
+ {
+ *next= save->first;
+ next= save->next;
+ elements+= save->elements;
+ }
+ }
+};
+
+
+/*
+ Basic single linked list
+ Used for item and item_buffs.
+ All list ends with a pointer to the 'end_of_list' element, which
+ data pointer is a null pointer and the next pointer points to itself.
+ This makes it very fast to traverse lists as we don't have to
+ test for a specialend condition for list that can't contain a null
+ pointer.
+*/
+
+
+/**
+ list_node - a node of a single-linked list.
+ @note We never call a destructor for instances of this class.
+*/
+
+struct list_node :public Sql_alloc
+{
+ list_node *next;
+ void *info;
+ list_node(void *info_par,list_node *next_par)
+ :next(next_par),info(info_par)
+ {}
+ list_node() /* For end_of_list */
+ {
+ info= 0;
+ next= this;
+ }
+};
+
+typedef bool List_eq(void *a, void *b);
+
+extern MYSQL_PLUGIN_IMPORT list_node end_of_list;
+
+class base_list :public Sql_alloc
+{
+protected:
+ list_node *first,**last;
+
+public:
+ uint elements;
+
+ bool operator==(const base_list &rhs) const
+ {
+ return
+ elements == rhs.elements &&
+ first == rhs.first &&
+ last == rhs.last;
+ }
+
+ inline void empty() { elements=0; first= &end_of_list; last=&first;}
+ inline base_list() { empty(); }
+ /**
+ This is a shallow copy constructor that implicitly passes the ownership
+ from the source list to the new instance. The old instance is not
+ updated, so both objects end up sharing the same nodes. If one of
+ the instances then adds or removes a node, the other becomes out of
+ sync ('last' pointer), while still operational. Some old code uses and
+ relies on this behaviour. This logic is quite tricky: please do not use
+ it in any new code.
+ */
+ inline base_list(const base_list &tmp) :Sql_alloc()
+ {
+ elements= tmp.elements;
+ first= tmp.first;
+ last= elements ? tmp.last : &first;
+ }
+ /**
+ Construct a deep copy of the argument in memory root mem_root.
+ The elements themselves are copied by pointer. If you also
+ need to copy elements by value, you should employ
+ list_copy_and_replace_each_value after creating a copy.
+ */
+ base_list(const base_list &rhs, MEM_ROOT *mem_root);
+ inline base_list(bool error) { }
+ inline bool push_back(void *info)
+ {
+ if (((*last)=new list_node(info, &end_of_list)))
+ {
+ last= &(*last)->next;
+ elements++;
+ return 0;
+ }
+ return 1;
+ }
+ inline bool push_back(void *info, MEM_ROOT *mem_root)
+ {
+ if (((*last)=new (mem_root) list_node(info, &end_of_list)))
+ {
+ last= &(*last)->next;
+ elements++;
+ return 0;
+ }
+ return 1;
+ }
+ inline bool push_front(void *info)
+ {
+ list_node *node=new list_node(info,first);
+ if (node)
+ {
+ if (last == &first)
+ last= &node->next;
+ first=node;
+ elements++;
+ return 0;
+ }
+ return 1;
+ }
+ void remove(list_node **prev)
+ {
+ list_node *node=(*prev)->next;
+ if (!--elements)
+ last= &first;
+ else if (last == &(*prev)->next)
+ last= prev;
+ delete *prev;
+ *prev=node;
+ }
+ inline void concat(base_list *list)
+ {
+ if (!list->is_empty())
+ {
+ if (is_empty())
+ {
+ *this= *list;
+ return;
+ }
+ *last= list->first;
+ last= list->last;
+ elements+= list->elements;
+ }
+ }
+ inline void *pop(void)
+ {
+ if (first == &end_of_list) return 0;
+ list_node *tmp=first;
+ first=first->next;
+ if (!--elements)
+ last= &first;
+ return tmp->info;
+ }
+
+ /*
+ Remove from this list elements that are contained in the passed list.
+ We assume that the passed list is a tail of this list (that is, the whole
+ list_node* elements are shared).
+ */
+ inline void disjoin(const base_list *list)
+ {
+ list_node **prev= &first;
+ list_node *node= first;
+ list_node *list_first= list->first;
+ elements=0;
+ while (node != &end_of_list && node != list_first)
+ {
+ prev= &node->next;
+ node= node->next;
+ elements++;
+ if (node == &end_of_list)
+ return;
+ }
+ *prev= &end_of_list;
+ last= prev;
+ }
+ inline void prepand(base_list *list)
+ {
+ if (!list->is_empty())
+ {
+ if (is_empty())
+ last= list->last;
+ *list->last= first;
+ first= list->first;
+ elements+= list->elements;
+ }
+ }
+ /**
+ Swap two lists.
+ */
+ inline void swap(base_list &rhs)
+ {
+ swap_variables(list_node *, first, rhs.first);
+ swap_variables(list_node **, last, rhs.last);
+ swap_variables(uint, elements, rhs.elements);
+ }
+ inline list_node* last_node() { return *last; }
+ inline list_node* first_node() { return first;}
+ inline void *head() { return first->info; }
+ inline void **head_ref() { return first != &end_of_list ? &first->info : 0; }
+ inline bool is_empty() { return first == &end_of_list ; }
+ inline list_node *last_ref() { return &end_of_list; }
+ inline bool add_unique(void *info, List_eq *eq)
+ {
+ list_node *node= first;
+ for (;
+ node != &end_of_list && (!(*eq)(node->info, info));
+ node= node->next) ;
+ if (node == &end_of_list)
+ return push_back(info);
+ return 1;
+ }
+ friend class base_list_iterator;
+ friend class error_list;
+ friend class error_list_iterator;
+
+#ifndef DBUG_OFF
+ /*
+ Debugging help: return N-th element in the list, or NULL if the list has
+ less than N elements.
+ */
+ void *elem(int n)
+ {
+ list_node *node= first;
+ void *data= NULL;
+ for (int i=0; i <= n; i++)
+ {
+ if (node == &end_of_list)
+ {
+ data= NULL;
+ break;
+ }
+ data= node->info;
+ node= node->next;
+ }
+ return data;
+ }
+#endif
+
+#ifdef LIST_EXTRA_DEBUG
+ /*
+ Check list invariants and print results into trace. Invariants are:
+ - (*last) points to end_of_list
+ - There are no NULLs in the list.
+ - base_list::elements is the number of elements in the list.
+
+ SYNOPSIS
+ check_list()
+ name Name to print to trace file
+
+ RETURN
+ 1 The list is Ok.
+ 0 List invariants are not met.
+ */
+
+ bool check_list(const char *name)
+ {
+ base_list *list= this;
+ list_node *node= first;
+ uint cnt= 0;
+
+ while (node->next != &end_of_list)
+ {
+ if (!node->info)
+ {
+ DBUG_PRINT("list_invariants",("%s: error: NULL element in the list",
+ name));
+ return FALSE;
+ }
+ node= node->next;
+ cnt++;
+ }
+ if (last != &(node->next))
+ {
+ DBUG_PRINT("list_invariants", ("%s: error: wrong last pointer", name));
+ return FALSE;
+ }
+ if (cnt+1 != elements)
+ {
+ DBUG_PRINT("list_invariants", ("%s: error: wrong element count", name));
+ return FALSE;
+ }
+ DBUG_PRINT("list_invariants", ("%s: list is ok", name));
+ return TRUE;
+ }
+#endif // LIST_EXTRA_DEBUG
+
+protected:
+ void after(void *info,list_node *node)
+ {
+ list_node *new_node=new list_node(info,node->next);
+ node->next=new_node;
+ elements++;
+ if (last == &(node->next))
+ last= &new_node->next;
+ }
+};
+
+
+class base_list_iterator
+{
+protected:
+ base_list *list;
+ list_node **el,**prev,*current;
+ void sublist(base_list &ls, uint elm)
+ {
+ ls.first= *el;
+ ls.last= list->last;
+ ls.elements= elm;
+ }
+public:
+ base_list_iterator()
+ :list(0), el(0), prev(0), current(0)
+ {}
+
+ base_list_iterator(base_list &list_par)
+ { init(list_par); }
+
+ inline void init(base_list &list_par)
+ {
+ list= &list_par;
+ el= &list_par.first;
+ prev= 0;
+ current= 0;
+ }
+
+ inline void *next(void)
+ {
+ prev=el;
+ current= *el;
+ el= &current->next;
+ return current->info;
+ }
+ inline void *next_fast(void)
+ {
+ list_node *tmp;
+ tmp= *el;
+ el= &tmp->next;
+ return tmp->info;
+ }
+ inline void rewind(void)
+ {
+ el= &list->first;
+ }
+ inline void *replace(void *element)
+ { // Return old element
+ void *tmp=current->info;
+ DBUG_ASSERT(current->info != 0);
+ current->info=element;
+ return tmp;
+ }
+ void *replace(base_list &new_list)
+ {
+ void *ret_value=current->info;
+ if (!new_list.is_empty())
+ {
+ *new_list.last=current->next;
+ current->info=new_list.first->info;
+ current->next=new_list.first->next;
+ if ((list->last == &current->next) && (new_list.elements > 1))
+ list->last= new_list.last;
+ list->elements+=new_list.elements-1;
+ }
+ return ret_value; // return old element
+ }
+ inline void remove(void) // Remove current
+ {
+ list->remove(prev);
+ el=prev;
+ current=0; // Safeguard
+ }
+ void after(void *element) // Insert element after current
+ {
+ list->after(element,current);
+ current=current->next;
+ el= &current->next;
+ }
+ inline void **ref(void) // Get reference pointer
+ {
+ return &current->info;
+ }
+ inline bool is_last(void)
+ {
+ return el == &list->last_ref()->next;
+ }
+ friend class error_list_iterator;
+};
+
+template <class T> class List :public base_list
+{
+public:
+ inline List() :base_list() {}
+ inline List(const List<T> &tmp) :base_list(tmp) {}
+ inline List(const List<T> &tmp, MEM_ROOT *mem_root) :
+ base_list(tmp, mem_root) {}
+ inline bool push_back(T *a) { return base_list::push_back(a); }
+ inline bool push_back(T *a, MEM_ROOT *mem_root)
+ { return base_list::push_back(a, mem_root); }
+ inline bool push_front(T *a) { return base_list::push_front(a); }
+ inline T* head() {return (T*) base_list::head(); }
+ inline T** head_ref() {return (T**) base_list::head_ref(); }
+ inline T* pop() {return (T*) base_list::pop(); }
+ inline void concat(List<T> *list) { base_list::concat(list); }
+ inline void disjoin(List<T> *list) { base_list::disjoin(list); }
+ inline void prepand(List<T> *list) { base_list::prepand(list); }
+ inline bool add_unique(T *a, bool (*eq)(T *a, T *b))
+ { return base_list::add_unique(a, (List_eq *)eq); }
+ void delete_elements(void)
+ {
+ list_node *element,*next;
+ for (element=first; element != &end_of_list; element=next)
+ {
+ next=element->next;
+ delete (T*) element->info;
+ }
+ empty();
+ }
+#ifndef DBUG_OFF
+ T *elem(int n) { return (T*)base_list::elem(n); }
+#endif
+};
+
+
+template <class T> class List_iterator :public base_list_iterator
+{
+public:
+ List_iterator(List<T> &a) : base_list_iterator(a) {}
+ List_iterator() : base_list_iterator() {}
+ inline void init(List<T> &a) { base_list_iterator::init(a); }
+ inline T* operator++(int) { return (T*) base_list_iterator::next(); }
+ inline T *replace(T *a) { return (T*) base_list_iterator::replace(a); }
+ inline T *replace(List<T> &a) { return (T*) base_list_iterator::replace(a); }
+ inline void rewind(void) { base_list_iterator::rewind(); }
+ inline void remove() { base_list_iterator::remove(); }
+ inline void after(T *a) { base_list_iterator::after(a); }
+ inline T** ref(void) { return (T**) base_list_iterator::ref(); }
+};
+
+
+template <class T> class List_iterator_fast :public base_list_iterator
+{
+protected:
+ inline T *replace(T *a) { return (T*) 0; }
+ inline T *replace(List<T> &a) { return (T*) 0; }
+ inline void remove(void) { }
+ inline void after(T *a) { }
+ inline T** ref(void) { return (T**) 0; }
+
+public:
+ inline List_iterator_fast(List<T> &a) : base_list_iterator(a) {}
+ inline List_iterator_fast() : base_list_iterator() {}
+ inline void init(List<T> &a) { base_list_iterator::init(a); }
+ inline T* operator++(int) { return (T*) base_list_iterator::next_fast(); }
+ inline void rewind(void) { base_list_iterator::rewind(); }
+ void sublist(List<T> &list_arg, uint el_arg)
+ {
+ base_list_iterator::sublist(list_arg, el_arg);
+ }
+};
+
+
+/*
+ Bubble sort algorithm for List<T>.
+ This sort function is supposed to be used only for very short list.
+ Currently it is used for the lists of Item_equal objects and
+ for some lists in the table elimination algorithms. In both
+ cases the sorted lists are very short.
+*/
+
+template <class T>
+inline void bubble_sort(List<T> *list_to_sort,
+ int (*sort_func)(T *a, T *b, void *arg), void *arg)
+{
+ bool swap;
+ T **ref1= 0;
+ T **ref2= 0;
+ List_iterator<T> it(*list_to_sort);
+ do
+ {
+ T **last_ref= ref1;
+ T *item1= it++;
+ ref1= it.ref();
+ T *item2;
+
+ swap= FALSE;
+ while ((item2= it++) && (ref2= it.ref()) != last_ref)
+ {
+ if (sort_func(item1, item2, arg) < 0)
+ {
+ *ref1= item2;
+ *ref2= item1;
+ swap= TRUE;
+ }
+ else
+ item1= item2;
+ ref1= ref2;
+ }
+ it.rewind();
+ } while (swap);
+}
+
+
+/*
+ A simple intrusive list which automaticly removes element from list
+ on delete (for THD element)
+*/
+
+struct ilink
+{
+ struct ilink **prev,*next;
+ static void *operator new(size_t size) throw ()
+ {
+ return (void*)my_malloc((uint)size, MYF(MY_WME | MY_FAE | ME_FATALERROR));
+ }
+ static void operator delete(void* ptr_arg, size_t size)
+ {
+ my_free(ptr_arg);
+ }
+
+ inline ilink()
+ {
+ prev=0; next=0;
+ }
+ inline void unlink()
+ {
+ /* Extra tests because element doesn't have to be linked */
+ if (prev) *prev= next;
+ if (next) next->prev=prev;
+ prev=0 ; next=0;
+ }
+ virtual ~ilink() { unlink(); } /*lint -e1740 */
+};
+
+
+/* Needed to be able to have an I_List of char* strings in mysqld.cc. */
+
+class i_string: public ilink
+{
+public:
+ const char* ptr;
+ i_string():ptr(0) { }
+ i_string(const char* s) : ptr(s) {}
+};
+
+/* needed for linked list of two strings for replicate-rewrite-db */
+class i_string_pair: public ilink
+{
+public:
+ const char* key;
+ const char* val;
+ i_string_pair():key(0),val(0) { }
+ i_string_pair(const char* key_arg, const char* val_arg) :
+ key(key_arg),val(val_arg) {}
+};
+
+
+template <class T> class I_List_iterator;
+
+
+class base_ilist
+{
+ struct ilink *first;
+ struct ilink last;
+public:
+ inline void empty() { first= &last; last.prev= &first; }
+ base_ilist() { empty(); }
+ inline bool is_empty() { return first == &last; }
+ // Returns true if p is the last "real" object in the list,
+ // i.e. p->next points to the sentinel.
+ inline bool is_last(ilink *p) { return p->next == NULL || p->next == &last; }
+ inline void append(ilink *a)
+ {
+ first->prev= &a->next;
+ a->next=first; a->prev= &first; first=a;
+ }
+ inline void push_back(ilink *a)
+ {
+ *last.prev= a;
+ a->next= &last;
+ a->prev= last.prev;
+ last.prev= &a->next;
+ }
+ inline struct ilink *get()
+ {
+ struct ilink *first_link=first;
+ if (first_link == &last)
+ return 0;
+ first_link->unlink(); // Unlink from list
+ return first_link;
+ }
+ inline struct ilink *head()
+ {
+ return (first != &last) ? first : 0;
+ }
+
+ /**
+ Moves list elements to new owner, and empties current owner (i.e. this).
+
+ @param[in,out] new_owner The new owner of the list elements.
+ Should be empty in input.
+ */
+
+ void move_elements_to(base_ilist *new_owner)
+ {
+ DBUG_ASSERT(new_owner->is_empty());
+ new_owner->first= first;
+ new_owner->last= last;
+ empty();
+ }
+
+ friend class base_ilist_iterator;
+ private:
+ /*
+ We don't want to allow copying of this class, as that would give us
+ two list heads containing the same elements.
+ So we declare, but don't define copy CTOR and assignment operator.
+ */
+ base_ilist(const base_ilist&);
+ void operator=(const base_ilist&);
+};
+
+
+class base_ilist_iterator
+{
+ base_ilist *list;
+ struct ilink **el,*current;
+public:
+ base_ilist_iterator(base_ilist &list_par) :list(&list_par),
+ el(&list_par.first),current(0) {}
+ void *next(void)
+ {
+ /* This is coded to allow push_back() while iterating */
+ current= *el;
+ if (current == &list->last) return 0;
+ el= &current->next;
+ return current;
+ }
+};
+
+
+template <class T>
+class I_List :private base_ilist
+{
+public:
+ I_List() :base_ilist() {}
+ inline bool is_last(T *p) { return base_ilist::is_last(p); }
+ inline void empty() { base_ilist::empty(); }
+ inline bool is_empty() { return base_ilist::is_empty(); }
+ inline void append(T* a) { base_ilist::append(a); }
+ inline void push_back(T* a) { base_ilist::push_back(a); }
+ inline T* get() { return (T*) base_ilist::get(); }
+ inline T* head() { return (T*) base_ilist::head(); }
+ inline void move_elements_to(I_List<T>* new_owner) {
+ base_ilist::move_elements_to(new_owner);
+ }
+#ifndef _lint
+ friend class I_List_iterator<T>;
+#endif
+};
+
+
+template <class T> class I_List_iterator :public base_ilist_iterator
+{
+public:
+ I_List_iterator(I_List<T> &a) : base_ilist_iterator(a) {}
+ inline T* operator++(int) { return (T*) base_ilist_iterator::next(); }
+};
+
+/**
+ Make a deep copy of each list element.
+
+ @note A template function and not a template method of class List
+ is employed because of explicit template instantiation:
+ in server code there are explicit instantiations of List<T> and
+ an explicit instantiation of a template requires that any method
+ of the instantiated class used in the template can be resolved.
+ Evidently not all template arguments have clone() method with
+ the right signature.
+
+ @return You must query the error state in THD for out-of-memory
+ situation after calling this function.
+*/
+
+template <typename T>
+inline
+void
+list_copy_and_replace_each_value(List<T> &list, MEM_ROOT *mem_root)
+{
+ /* Make a deep copy of each element */
+ List_iterator<T> it(list);
+ T *el;
+ while ((el= it++))
+ it.replace(el->clone(mem_root));
+}
+
+void free_list(I_List <i_string_pair> *list);
+void free_list(I_List <i_string> *list);
+
+#endif // INCLUDES_MYSQL_SQL_LIST_H
diff --git a/sql/sql_load.cc b/sql/sql_load.cc
new file mode 100644
index 00000000000..6ecdddc3008
--- /dev/null
+++ b/sql/sql_load.cc
@@ -0,0 +1,2094 @@
+/*
+ Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2014, SkySQL Ab.
+
+ 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 */
+
+
+/* Copy data from a textfile to table */
+/* 2006-12 Erik Wetterberg : LOAD XML added */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_load.h"
+#include "sql_load.h"
+#include "sql_cache.h" // query_cache_*
+#include "sql_base.h" // fill_record_n_invoke_before_triggers
+#include <my_dir.h>
+#include "sql_view.h" // check_key_in_view
+#include "sql_insert.h" // check_that_all_fields_are_given_values,
+ // write_record
+#include "sql_acl.h" // INSERT_ACL, UPDATE_ACL
+#include "log_event.h" // Delete_file_log_event,
+ // Execute_load_query_log_event,
+ // LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F
+#include <m_ctype.h>
+#include "rpl_mi.h"
+#include "sql_repl.h"
+#include "sp_head.h"
+#include "sql_trigger.h"
+#include "sql_derived.h"
+#include "sql_show.h"
+
+class XML_TAG {
+public:
+ int level;
+ String field;
+ String value;
+ XML_TAG(int l, String f, String v);
+};
+
+
+XML_TAG::XML_TAG(int l, String f, String v)
+{
+ level= l;
+ field.append(f);
+ value.append(v);
+}
+
+
+#define GET (stack_pos != stack ? *--stack_pos : my_b_get(&cache))
+#define PUSH(A) *(stack_pos++)=(A)
+
+class READ_INFO {
+ File file;
+ uchar *buffer, /* Buffer for read text */
+ *end_of_buff; /* Data in bufferts ends here */
+ uint buff_length, /* Length of buffert */
+ max_length; /* Max length of row */
+ const uchar *field_term_ptr,*line_term_ptr;
+ const char *line_start_ptr,*line_start_end;
+ uint field_term_length,line_term_length,enclosed_length;
+ int field_term_char,line_term_char,enclosed_char,escape_char;
+ int *stack,*stack_pos;
+ bool found_end_of_line,start_of_line,eof;
+ bool need_end_io_cache;
+ IO_CACHE cache;
+ NET *io_net;
+ int level; /* for load xml */
+
+public:
+ bool error,line_cuted,found_null,enclosed;
+ uchar *row_start, /* Found row starts here */
+ *row_end; /* Found row ends here */
+ CHARSET_INFO *read_charset;
+
+ READ_INFO(File file,uint tot_length,CHARSET_INFO *cs,
+ String &field_term,String &line_start,String &line_term,
+ String &enclosed,int escape,bool get_it_from_net, bool is_fifo);
+ ~READ_INFO();
+ int read_field();
+ int read_fixed_length(void);
+ int next_line(void);
+ char unescape(char chr);
+ int terminator(const uchar *ptr, uint length);
+ bool find_start_of_fields();
+ /* load xml */
+ List<XML_TAG> taglist;
+ int read_value(int delim, String *val);
+ int read_xml();
+ int clear_level(int level);
+
+ /*
+ We need to force cache close before destructor is invoked to log
+ the last read block
+ */
+ void end_io_cache()
+ {
+ ::end_io_cache(&cache);
+ need_end_io_cache = 0;
+ }
+ my_off_t file_length() { return cache.end_of_file; }
+ my_off_t position() { return my_b_tell(&cache); }
+
+ /*
+ Either this method, or we need to make cache public
+ Arg must be set from mysql_load() since constructor does not see
+ either the table or THD value
+ */
+ void set_io_cache_arg(void* arg) { cache.arg = arg; }
+
+ /**
+ skip all data till the eof.
+ */
+ void skip_data_till_eof()
+ {
+ while (GET != my_b_EOF)
+ ;
+ }
+};
+
+static int read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values, READ_INFO &read_info,
+ ulong skip_lines,
+ bool ignore_check_option_errors);
+static int read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values, READ_INFO &read_info,
+ String &enclosed, ulong skip_lines,
+ bool ignore_check_option_errors);
+
+static int read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values, READ_INFO &read_info,
+ String &enclosed, ulong skip_lines,
+ bool ignore_check_option_errors);
+
+#ifndef EMBEDDED_LIBRARY
+static bool write_execute_load_query_log_event(THD *, sql_exchange*, const
+ char*, const char*, bool, enum enum_duplicates, bool, bool, int);
+#endif /* EMBEDDED_LIBRARY */
+
+/*
+ Execute LOAD DATA query
+
+ SYNOPSYS
+ mysql_load()
+ thd - current thread
+ ex - sql_exchange object representing source file and its parsing rules
+ table_list - list of tables to which we are loading data
+ fields_vars - list of fields and variables to which we read
+ data from file
+ set_fields - list of fields mentioned in set clause
+ set_values - expressions to assign to fields in previous list
+ handle_duplicates - indicates whenever we should emit error or
+ replace row if we will meet duplicates.
+ ignore - - indicates whenever we should ignore duplicates
+ read_file_from_client - is this LOAD DATA LOCAL ?
+
+ RETURN VALUES
+ TRUE - error / FALSE - success
+*/
+
+int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values,
+ enum enum_duplicates handle_duplicates, bool ignore,
+ bool read_file_from_client)
+{
+ char name[FN_REFLEN];
+ File file;
+ TABLE *table= NULL;
+ int error= 0;
+ String *field_term=ex->field_term,*escaped=ex->escaped;
+ String *enclosed=ex->enclosed;
+ bool is_fifo=0;
+#ifndef EMBEDDED_LIBRARY
+ LOAD_FILE_INFO lf_info;
+ killed_state killed_status;
+ bool is_concurrent;
+#endif
+ char *db = table_list->db; // This is never null
+ /*
+ If path for file is not defined, we will use the current database.
+ If this is not set, we will use the directory where the table to be
+ loaded is located
+ */
+ char *tdb= thd->db ? thd->db : db; // Result is never null
+ ulong skip_lines= ex->skip_lines;
+ bool transactional_table __attribute__((unused));
+ DBUG_ENTER("mysql_load");
+
+ /*
+ Bug #34283
+ mysqlbinlog leaves tmpfile after termination if binlog contains
+ load data infile, so in mixed mode we go to row-based for
+ avoiding the problem.
+ */
+ thd->set_current_stmt_binlog_format_row_if_mixed();
+
+#ifdef EMBEDDED_LIBRARY
+ read_file_from_client = 0; //server is always in the same process
+#endif
+
+ if (escaped->length() > 1 || enclosed->length() > 1)
+ {
+ my_message(ER_WRONG_FIELD_TERMINATORS,ER(ER_WRONG_FIELD_TERMINATORS),
+ MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Report problems with non-ascii separators */
+ if (!escaped->is_ascii() || !enclosed->is_ascii() ||
+ !field_term->is_ascii() ||
+ !ex->line_term->is_ascii() || !ex->line_start->is_ascii())
+ {
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED,
+ ER(WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED));
+ }
+
+ if (open_and_lock_tables(thd, table_list, TRUE, 0))
+ DBUG_RETURN(TRUE);
+ if (mysql_handle_single_derived(thd->lex, table_list, DT_MERGE_FOR_INSERT) ||
+ mysql_handle_single_derived(thd->lex, table_list, DT_PREPARE))
+ DBUG_RETURN(TRUE);
+ if (setup_tables_and_check_access(thd, &thd->lex->select_lex.context,
+ &thd->lex->select_lex.top_join_list,
+ table_list,
+ thd->lex->select_lex.leaf_tables, FALSE,
+ INSERT_ACL | UPDATE_ACL,
+ INSERT_ACL | UPDATE_ACL, FALSE))
+ DBUG_RETURN(-1);
+ if (!table_list->table || // do not suport join view
+ !table_list->single_table_updatable() || // and derived tables
+ check_key_in_view(thd, table_list))
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "LOAD");
+ DBUG_RETURN(TRUE);
+ }
+ if (table_list->prepare_where(thd, 0, TRUE) ||
+ table_list->prepare_check_option(thd))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Let us emit an error if we are loading data to table which is used
+ in subselect in SET clause like we do it for INSERT.
+
+ The main thing to fix to remove this restriction is to ensure that the
+ table is marked to be 'used for insert' in which case we should never
+ mark this table as 'const table' (ie, one that has only one row).
+ */
+ if (unique_table(thd, table_list, table_list->next_global, 0))
+ {
+ my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->table_name,
+ "LOAD DATA");
+ DBUG_RETURN(TRUE);
+ }
+
+ table= table_list->table;
+ transactional_table= table->file->has_transactions();
+#ifndef EMBEDDED_LIBRARY
+ is_concurrent= (table_list->lock_type == TL_WRITE_CONCURRENT_INSERT);
+#endif
+
+ if (!fields_vars.elements)
+ {
+ Field_iterator_table_ref field_iterator;
+ field_iterator.set(table_list);
+ for (; !field_iterator.end_of_fields(); field_iterator.next())
+ {
+ Item *item;
+ if (!(item= field_iterator.create_item(thd)))
+ DBUG_RETURN(TRUE);
+ fields_vars.push_back(item->real_item());
+ }
+ bitmap_set_all(table->write_set);
+ /*
+ Let us also prepare SET clause, altough it is probably empty
+ in this case.
+ */
+ if (setup_fields(thd, 0, set_fields, MARK_COLUMNS_WRITE, 0, 0) ||
+ setup_fields(thd, 0, set_values, MARK_COLUMNS_READ, 0, 0))
+ DBUG_RETURN(TRUE);
+ }
+ else
+ { // Part field list
+ /* TODO: use this conds for 'WITH CHECK OPTIONS' */
+ if (setup_fields(thd, 0, fields_vars, MARK_COLUMNS_WRITE, 0, 0) ||
+ setup_fields(thd, 0, set_fields, MARK_COLUMNS_WRITE, 0, 0) ||
+ check_that_all_fields_are_given_values(thd, table, table_list))
+ DBUG_RETURN(TRUE);
+ /* Add all fields with default functions to table->write_set. */
+ if (table->default_field)
+ table->mark_default_fields_for_write();
+ /* Fix the expressions in SET clause */
+ if (setup_fields(thd, 0, set_values, MARK_COLUMNS_READ, 0, 0))
+ DBUG_RETURN(TRUE);
+ }
+
+ table->prepare_triggers_for_insert_stmt_or_event();
+ table->mark_columns_needed_for_insert();
+
+ uint tot_length=0;
+ bool use_blobs= 0, use_vars= 0;
+ List_iterator_fast<Item> it(fields_vars);
+ Item *item;
+
+ while ((item= it++))
+ {
+ Item *real_item= item->real_item();
+
+ if (real_item->type() == Item::FIELD_ITEM)
+ {
+ Field *field= ((Item_field*)real_item)->field;
+ if (field->flags & BLOB_FLAG)
+ {
+ use_blobs= 1;
+ tot_length+= 256; // Will be extended if needed
+ }
+ else
+ tot_length+= field->field_length;
+ }
+ else if (item->type() == Item::STRING_ITEM)
+ use_vars= 1;
+ }
+ if (use_blobs && !ex->line_term->length() && !field_term->length())
+ {
+ my_message(ER_BLOBS_AND_NO_TERMINATED,ER(ER_BLOBS_AND_NO_TERMINATED),
+ MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (use_vars && !field_term->length() && !enclosed->length())
+ {
+ my_error(ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /* We can't give an error in the middle when using LOCAL files */
+ if (read_file_from_client && handle_duplicates == DUP_ERROR)
+ ignore= 1;
+
+#ifndef EMBEDDED_LIBRARY
+ if (read_file_from_client)
+ {
+ (void)net_request_file(&thd->net,ex->file_name);
+ file = -1;
+ }
+ else
+#endif
+ {
+#ifdef DONT_ALLOW_FULL_LOAD_DATA_PATHS
+ ex->file_name+=dirname_length(ex->file_name);
+#endif
+ if (!dirname_length(ex->file_name))
+ {
+ strxnmov(name, FN_REFLEN-1, mysql_real_data_home, tdb, NullS);
+ (void) fn_format(name, ex->file_name, name, "",
+ MY_RELATIVE_PATH | MY_UNPACK_FILENAME);
+ }
+ else
+ {
+ (void) fn_format(name, ex->file_name, mysql_real_data_home, "",
+ MY_RELATIVE_PATH | MY_UNPACK_FILENAME |
+ MY_RETURN_REAL_PATH);
+ }
+
+ if (thd->rgi_slave)
+ {
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+ if (strncmp(thd->rgi_slave->rli->slave_patternload_file, name,
+ thd->rgi_slave->rli->slave_patternload_file_size))
+ {
+ /*
+ LOAD DATA INFILE in the slave SQL Thread can only read from
+ --slave-load-tmpdir". This should never happen. Please, report a bug.
+ */
+
+ sql_print_error("LOAD DATA INFILE in the slave SQL Thread can only read from --slave-load-tmpdir. " \
+ "Please, report a bug.");
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--slave-load-tmpdir");
+ DBUG_RETURN(TRUE);
+ }
+#else
+ /*
+ This is impossible and should never happen.
+ */
+ DBUG_ASSERT(FALSE);
+#endif
+ }
+ else if (!is_secure_file_path(name))
+ {
+ /* Read only allowed from within dir specified by secure_file_priv */
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv");
+ DBUG_RETURN(TRUE);
+ }
+
+#if !defined(__WIN__) && ! defined(__NETWARE__)
+ MY_STAT stat_info;
+ if (!my_stat(name, &stat_info, MYF(MY_WME)))
+ DBUG_RETURN(TRUE);
+
+ // if we are not in slave thread, the file must be:
+ if (!thd->slave_thread &&
+ !((stat_info.st_mode & S_IFLNK) != S_IFLNK && // symlink
+ ((stat_info.st_mode & S_IFREG) == S_IFREG || // regular file
+ (stat_info.st_mode & S_IFIFO) == S_IFIFO))) // named pipe
+ {
+ my_error(ER_TEXTFILE_NOT_READABLE, MYF(0), name);
+ DBUG_RETURN(TRUE);
+ }
+ if ((stat_info.st_mode & S_IFIFO) == S_IFIFO)
+ is_fifo= 1;
+#endif
+ if ((file= mysql_file_open(key_file_load,
+ name, O_RDONLY, MYF(MY_WME))) < 0)
+
+ DBUG_RETURN(TRUE);
+ }
+
+ COPY_INFO info;
+ bzero((char*) &info,sizeof(info));
+ info.ignore= ignore;
+ info.handle_duplicates=handle_duplicates;
+ info.escape_char= (escaped->length() && (ex->escaped_given() ||
+ !(thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)))
+ ? (*escaped)[0] : INT_MAX;
+
+ READ_INFO read_info(file,tot_length,
+ ex->cs ? ex->cs : thd->variables.collation_database,
+ *field_term,*ex->line_start, *ex->line_term, *enclosed,
+ info.escape_char, read_file_from_client, is_fifo);
+ if (read_info.error)
+ {
+ if (file >= 0)
+ mysql_file_close(file, MYF(0)); // no files in net reading
+ DBUG_RETURN(TRUE); // Can't allocate buffers
+ }
+
+#ifndef EMBEDDED_LIBRARY
+ if (mysql_bin_log.is_open())
+ {
+ lf_info.thd = thd;
+ lf_info.wrote_create_file = 0;
+ lf_info.last_pos_in_file = HA_POS_ERROR;
+ lf_info.log_delayed= transactional_table;
+ read_info.set_io_cache_arg((void*) &lf_info);
+ }
+#endif /*!EMBEDDED_LIBRARY*/
+
+ thd->count_cuted_fields= CHECK_FIELD_WARN; /* calc cuted fields */
+ thd->cuted_fields=0L;
+ /* Skip lines if there is a line terminator */
+ if (ex->line_term->length() && ex->filetype != FILETYPE_XML)
+ {
+ /* ex->skip_lines needs to be preserved for logging */
+ while (skip_lines > 0)
+ {
+ skip_lines--;
+ if (read_info.next_line())
+ break;
+ }
+ }
+
+ thd_proc_info(thd, "reading file");
+ if (!(error= MY_TEST(read_info.error)))
+ {
+ table->reset_default_fields();
+ table->next_number_field=table->found_next_number_field;
+ if (ignore ||
+ handle_duplicates == DUP_REPLACE)
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ if (handle_duplicates == DUP_REPLACE &&
+ (!table->triggers ||
+ !table->triggers->has_delete_triggers()))
+ table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
+ table->file->ha_start_bulk_insert((ha_rows) 0);
+ table->copy_blobs=1;
+
+ thd->abort_on_warning= !ignore && thd->is_strict_mode();
+
+ thd_progress_init(thd, 2);
+ if (ex->filetype == FILETYPE_XML) /* load xml */
+ error= read_xml_field(thd, info, table_list, fields_vars,
+ set_fields, set_values, read_info,
+ *(ex->line_term), skip_lines, ignore);
+ else if (!field_term->length() && !enclosed->length())
+ error= read_fixed_length(thd, info, table_list, fields_vars,
+ set_fields, set_values, read_info,
+ skip_lines, ignore);
+ else
+ error= read_sep_field(thd, info, table_list, fields_vars,
+ set_fields, set_values, read_info,
+ *enclosed, skip_lines, ignore);
+
+ thd_proc_info(thd, "End bulk insert");
+ thd_progress_next_stage(thd);
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
+ table->file->ha_end_bulk_insert() && !error)
+ {
+ table->file->print_error(my_errno, MYF(0));
+ error= 1;
+ }
+ table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
+ table->next_number_field=0;
+ }
+ if (file >= 0)
+ mysql_file_close(file, MYF(0));
+ free_blobs(table); /* if pack_blob was used */
+ table->copy_blobs=0;
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+ /*
+ simulated killing in the middle of per-row loop
+ must be effective for binlogging
+ */
+ DBUG_EXECUTE_IF("simulate_kill_bug27571",
+ {
+ error=1;
+ thd->killed= KILL_QUERY;
+ };);
+
+#ifndef EMBEDDED_LIBRARY
+ killed_status= (error == 0) ? NOT_KILLED : thd->killed;
+#endif
+
+ /*
+ We must invalidate the table in query cache before binlog writing and
+ ha_autocommit_...
+ */
+ query_cache_invalidate3(thd, table_list, 0);
+ if (error)
+ {
+ if (read_file_from_client)
+ read_info.skip_data_till_eof();
+
+#ifndef EMBEDDED_LIBRARY
+ if (mysql_bin_log.is_open())
+ {
+ {
+ /*
+ Make sure last block (the one which caused the error) gets
+ logged. This is needed because otherwise after write of (to
+ the binlog, not to read_info (which is a cache))
+ Delete_file_log_event the bad block will remain in read_info
+ (because pre_read is not called at the end of the last
+ block; remember pre_read is called whenever a new block is
+ read from disk). At the end of mysql_load(), the destructor
+ of read_info will call end_io_cache() which will flush
+ read_info, so we will finally have this in the binlog:
+
+ Append_block # The last successfull block
+ Delete_file
+ Append_block # The failing block
+ which is nonsense.
+ Or could also be (for a small file)
+ Create_file # The failing block
+ which is nonsense (Delete_file is not written in this case, because:
+ Create_file has not been written, so Delete_file is not written, then
+ when read_info is destroyed end_io_cache() is called which writes
+ Create_file.
+ */
+ read_info.end_io_cache();
+ /* If the file was not empty, wrote_create_file is true */
+ if (lf_info.wrote_create_file)
+ {
+ int errcode= query_error_code(thd, killed_status == NOT_KILLED);
+
+ /* since there is already an error, the possible error of
+ writing binary log will be ignored */
+ if (thd->transaction.stmt.modified_non_trans_table)
+ (void) write_execute_load_query_log_event(thd, ex,
+ table_list->db,
+ table_list->table_name,
+ is_concurrent,
+ handle_duplicates, ignore,
+ transactional_table,
+ errcode);
+ else
+ {
+ Delete_file_log_event d(thd, db, transactional_table);
+ (void) mysql_bin_log.write(&d);
+ }
+ }
+ }
+ }
+#endif /*!EMBEDDED_LIBRARY*/
+ error= -1; // Error on read
+ goto err;
+ }
+ sprintf(name, ER(ER_LOAD_INFO), (ulong) info.records, (ulong) info.deleted,
+ (ulong) (info.records - info.copied),
+ (long) thd->get_stmt_da()->current_statement_warn_count());
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
+#ifndef EMBEDDED_LIBRARY
+ if (mysql_bin_log.is_open())
+ {
+ /*
+ We need to do the job that is normally done inside
+ binlog_query() here, which is to ensure that the pending event
+ is written before tables are unlocked and before any other
+ events are written. We also need to update the table map
+ version for the binary log to mark that table maps are invalid
+ after this point.
+ */
+ if (thd->is_current_stmt_binlog_format_row())
+ error= thd->binlog_flush_pending_rows_event(TRUE, transactional_table);
+ else
+ {
+ /*
+ As already explained above, we need to call end_io_cache() or the last
+ block will be logged only after Execute_load_query_log_event (which is
+ wrong), when read_info is destroyed.
+ */
+ read_info.end_io_cache();
+ if (lf_info.wrote_create_file)
+ {
+ int errcode= query_error_code(thd, killed_status == NOT_KILLED);
+ error= write_execute_load_query_log_event(thd, ex,
+ table_list->db, table_list->table_name,
+ is_concurrent,
+ handle_duplicates, ignore,
+ transactional_table,
+ errcode);
+ }
+
+ /*
+ Flushing the IO CACHE while writing the execute load query log event
+ may result in error (for instance, because the max_binlog_size has been
+ reached, and rotation of the binary log failed).
+ */
+ error= error || mysql_bin_log.get_log_file()->error;
+ }
+ if (error)
+ goto err;
+ }
+#endif /*!EMBEDDED_LIBRARY*/
+
+ /* ok to client sent only after binlog write and engine commit */
+ my_ok(thd, info.copied + info.deleted, 0L, name);
+err:
+ DBUG_ASSERT(transactional_table || !(info.copied || info.deleted) ||
+ thd->transaction.stmt.modified_non_trans_table);
+ table->file->ha_release_auto_increment();
+ table->auto_increment_field_not_null= FALSE;
+ thd->abort_on_warning= 0;
+ DBUG_RETURN(error);
+}
+
+
+#ifndef EMBEDDED_LIBRARY
+
+/* Not a very useful function; just to avoid duplication of code */
+static bool write_execute_load_query_log_event(THD *thd, sql_exchange* ex,
+ const char* db_arg, /* table's database */
+ const char* table_name_arg,
+ bool is_concurrent,
+ enum enum_duplicates duplicates,
+ bool ignore,
+ bool transactional_table,
+ int errcode)
+{
+ char *load_data_query;
+ my_off_t fname_start,
+ fname_end;
+ List<Item> fv;
+ Item *item, *val;
+ int n;
+ const char *tdb= (thd->db != NULL ? thd->db : db_arg);
+ const char *qualify_db= NULL;
+ char command_buffer[1024];
+ String query_str(command_buffer, sizeof(command_buffer),
+ system_charset_info);
+
+ Load_log_event lle(thd, ex, tdb, table_name_arg, fv, is_concurrent,
+ duplicates, ignore, transactional_table);
+
+ /*
+ force in a LOCAL if there was one in the original.
+ */
+ if (thd->lex->local_file)
+ lle.set_fname_outside_temp_buf(ex->file_name, strlen(ex->file_name));
+
+ query_str.length(0);
+ if (!thd->db || strcmp(db_arg, thd->db))
+ {
+ /*
+ If used database differs from table's database,
+ prefix table name with database name so that it
+ becomes a FQ name.
+ */
+ qualify_db= db_arg;
+ }
+ lle.print_query(thd, FALSE, (const char *) ex->cs?ex->cs->csname:NULL,
+ &query_str, &fname_start, &fname_end, qualify_db);
+
+ /*
+ prepare fields-list and SET if needed; print_query won't do that for us.
+ */
+ if (!thd->lex->field_list.is_empty())
+ {
+ List_iterator<Item> li(thd->lex->field_list);
+
+ query_str.append(" (");
+ n= 0;
+
+ while ((item= li++))
+ {
+ if (n++)
+ query_str.append(", ");
+ if (item->real_type() == Item::FIELD_ITEM)
+ append_identifier(thd, &query_str, item->name, strlen(item->name));
+ else
+ {
+ /* Actually Item_user_var_as_out_param despite claiming STRING_ITEM. */
+ DBUG_ASSERT(item->type() == Item::STRING_ITEM);
+ ((Item_user_var_as_out_param *)item)->print_for_load(thd, &query_str);
+ }
+ }
+ query_str.append(")");
+ }
+
+ if (!thd->lex->update_list.is_empty())
+ {
+ List_iterator<Item> lu(thd->lex->update_list);
+ List_iterator<Item> lv(thd->lex->value_list);
+
+ query_str.append(STRING_WITH_LEN(" SET "));
+ n= 0;
+
+ while ((item= lu++))
+ {
+ val= lv++;
+ if (n++)
+ query_str.append(STRING_WITH_LEN(", "));
+ append_identifier(thd, &query_str, item->name, strlen(item->name));
+ query_str.append(val->name);
+ }
+ }
+
+ if (!(load_data_query= (char *)thd->strmake(query_str.ptr(), query_str.length())))
+ return TRUE;
+
+ Execute_load_query_log_event
+ e(thd, load_data_query, query_str.length(),
+ (uint) (fname_start - 1), (uint) fname_end,
+ (duplicates == DUP_REPLACE) ? LOAD_DUP_REPLACE :
+ (ignore ? LOAD_DUP_IGNORE : LOAD_DUP_ERROR),
+ transactional_table, FALSE, FALSE, errcode);
+ return mysql_bin_log.write(&e);
+}
+
+#endif
+
+/****************************************************************************
+** Read of rows of fixed size + optional garage + optonal newline
+****************************************************************************/
+
+static int
+read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values, READ_INFO &read_info,
+ ulong skip_lines, bool ignore_check_option_errors)
+{
+ List_iterator_fast<Item> it(fields_vars);
+ Item_field *sql_field;
+ TABLE *table= table_list->table;
+ bool err, progress_reports;
+ ulonglong counter, time_to_report_progress;
+ DBUG_ENTER("read_fixed_length");
+
+ counter= 0;
+ time_to_report_progress= MY_HOW_OFTEN_TO_WRITE/10;
+ progress_reports= 1;
+ if ((thd->progress.max_counter= read_info.file_length()) == ~(my_off_t) 0)
+ progress_reports= 0;
+
+ while (!read_info.read_fixed_length())
+ {
+ if (thd->killed)
+ {
+ thd->send_kill_message();
+ DBUG_RETURN(1);
+ }
+ if (progress_reports)
+ {
+ thd->progress.counter= read_info.position();
+ if (++counter >= time_to_report_progress)
+ {
+ time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10;
+ thd_progress_report(thd, thd->progress.counter,
+ thd->progress.max_counter);
+ }
+ }
+ if (skip_lines)
+ {
+ /*
+ We could implement this with a simple seek if:
+ - We are not using DATA INFILE LOCAL
+ - escape character is ""
+ - line starting prefix is ""
+ */
+ skip_lines--;
+ continue;
+ }
+ it.rewind();
+ uchar *pos=read_info.row_start;
+#ifdef HAVE_valgrind
+ read_info.row_end[0]=0;
+#endif
+
+ restore_record(table, s->default_values);
+ /*
+ There is no variables in fields_vars list in this format so
+ this conversion is safe.
+ */
+ while ((sql_field= (Item_field*) it++))
+ {
+ Field *field= sql_field->field;
+ if (field == table->next_number_field)
+ table->auto_increment_field_not_null= TRUE;
+ /*
+ No fields specified in fields_vars list can be null in this format.
+ Mark field as not null, we should do this for each row because of
+ restore_record...
+ */
+ field->set_notnull();
+
+ if (pos == read_info.row_end)
+ {
+ thd->cuted_fields++; /* Not enough fields */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_TOO_FEW_RECORDS,
+ ER(ER_WARN_TOO_FEW_RECORDS),
+ thd->get_stmt_da()->current_row_for_warning());
+ /*
+ Timestamp fields that are NOT NULL are autoupdated if there is no
+ corresponding value in the data file.
+ */
+ if (!field->maybe_null() && field->type() == FIELD_TYPE_TIMESTAMP)
+ field->set_time();
+ }
+ else
+ {
+ uint length;
+ uchar save_chr;
+ if ((length=(uint) (read_info.row_end-pos)) >
+ field->field_length)
+ length=field->field_length;
+ save_chr=pos[length]; pos[length]='\0'; // Safeguard aganst malloc
+ field->store((char*) pos,length,read_info.read_charset);
+ pos[length]=save_chr;
+ if ((pos+=length) > read_info.row_end)
+ pos= read_info.row_end; /* Fills rest with space */
+ }
+ /* Do not auto-update this field. */
+ field->set_has_explicit_value();
+ }
+ if (pos != read_info.row_end)
+ {
+ thd->cuted_fields++; /* To long row */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_TOO_MANY_RECORDS,
+ ER(ER_WARN_TOO_MANY_RECORDS),
+ thd->get_stmt_da()->current_row_for_warning());
+ }
+
+ if (thd->killed ||
+ fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values,
+ ignore_check_option_errors,
+ TRG_EVENT_INSERT) ||
+ (table->default_field && table->update_default_fields()))
+ DBUG_RETURN(1);
+
+ switch (table_list->view_check_option(thd,
+ ignore_check_option_errors)) {
+ case VIEW_CHECK_SKIP:
+ read_info.next_line();
+ goto continue_loop;
+ case VIEW_CHECK_ERROR:
+ DBUG_RETURN(-1);
+ }
+
+ err= write_record(thd, table, &info);
+ table->auto_increment_field_not_null= FALSE;
+ if (err)
+ DBUG_RETURN(1);
+
+ /*
+ We don't need to reset auto-increment field since we are restoring
+ its default value at the beginning of each loop iteration.
+ */
+ if (read_info.next_line()) // Skip to next line
+ break;
+ if (read_info.line_cuted)
+ {
+ thd->cuted_fields++; /* To long row */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_TOO_MANY_RECORDS,
+ ER(ER_WARN_TOO_MANY_RECORDS),
+ thd->get_stmt_da()->current_row_for_warning());
+ }
+ thd->get_stmt_da()->inc_current_row_for_warning();
+continue_loop:;
+ }
+ DBUG_RETURN(MY_TEST(read_info.error));
+}
+
+
+
+static int
+read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values, READ_INFO &read_info,
+ String &enclosed, ulong skip_lines,
+ bool ignore_check_option_errors)
+{
+ List_iterator_fast<Item> it(fields_vars);
+ Item *item;
+ TABLE *table= table_list->table;
+ uint enclosed_length;
+ bool err, progress_reports;
+ ulonglong counter, time_to_report_progress;
+ DBUG_ENTER("read_sep_field");
+
+ enclosed_length=enclosed.length();
+
+ counter= 0;
+ time_to_report_progress= MY_HOW_OFTEN_TO_WRITE/10;
+ progress_reports= 1;
+ if ((thd->progress.max_counter= read_info.file_length()) == ~(my_off_t) 0)
+ progress_reports= 0;
+
+ for (;;it.rewind())
+ {
+ if (thd->killed)
+ {
+ thd->send_kill_message();
+ DBUG_RETURN(1);
+ }
+
+ if (progress_reports)
+ {
+ thd->progress.counter= read_info.position();
+ if (++counter >= time_to_report_progress)
+ {
+ time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10;
+ thd_progress_report(thd, thd->progress.counter,
+ thd->progress.max_counter);
+ }
+ }
+ restore_record(table, s->default_values);
+
+ while ((item= it++))
+ {
+ uint length;
+ uchar *pos;
+ Item *real_item;
+
+ if (read_info.read_field())
+ break;
+
+ /* If this line is to be skipped we don't want to fill field or var */
+ if (skip_lines)
+ continue;
+
+ pos=read_info.row_start;
+ length=(uint) (read_info.row_end-pos);
+
+ real_item= item->real_item();
+
+ if ((!read_info.enclosed &&
+ (enclosed_length && length == 4 &&
+ !memcmp(pos, STRING_WITH_LEN("NULL")))) ||
+ (length == 1 && read_info.found_null))
+ {
+ if (real_item->type() == Item::FIELD_ITEM)
+ {
+ Field *field= ((Item_field *)real_item)->field;
+ if (field->reset())
+ {
+ my_error(ER_WARN_NULL_TO_NOTNULL, MYF(0), field->field_name,
+ thd->get_stmt_da()->current_row_for_warning());
+ DBUG_RETURN(1);
+ }
+ field->set_null();
+ if (!field->maybe_null())
+ {
+ /*
+ Timestamp fields that are NOT NULL are autoupdated if there is no
+ corresponding value in the data file.
+ */
+ if (field->type() == MYSQL_TYPE_TIMESTAMP)
+ field->set_time();
+ else if (field != table->next_number_field)
+ field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_NULL_TO_NOTNULL, 1);
+ }
+ /* Do not auto-update this field. */
+ field->set_has_explicit_value();
+ }
+ else if (item->type() == Item::STRING_ITEM)
+ {
+ ((Item_user_var_as_out_param *)item)->set_null_value(
+ read_info.read_charset);
+ }
+ else
+ {
+ my_error(ER_LOAD_DATA_INVALID_COLUMN, MYF(0), item->full_name());
+ DBUG_RETURN(1);
+ }
+
+ continue;
+ }
+
+ if (real_item->type() == Item::FIELD_ITEM)
+ {
+ Field *field= ((Item_field *)real_item)->field;
+ field->set_notnull();
+ read_info.row_end[0]=0; // Safe to change end marker
+ if (field == table->next_number_field)
+ table->auto_increment_field_not_null= TRUE;
+ field->store((char*) pos, length, read_info.read_charset);
+ field->set_has_explicit_value();
+ }
+ else if (item->type() == Item::STRING_ITEM)
+ {
+ ((Item_user_var_as_out_param *)item)->set_value((char*) pos, length,
+ read_info.read_charset);
+ }
+ else
+ {
+ my_error(ER_LOAD_DATA_INVALID_COLUMN, MYF(0), item->full_name());
+ DBUG_RETURN(1);
+ }
+ }
+
+ if (thd->is_error())
+ read_info.error= 1;
+
+ if (read_info.error)
+ break;
+ if (skip_lines)
+ {
+ skip_lines--;
+ continue;
+ }
+ if (item)
+ {
+ /* Have not read any field, thus input file is simply ended */
+ if (item == fields_vars.head())
+ break;
+ for (; item ; item= it++)
+ {
+ Item *real_item= item->real_item();
+ if (real_item->type() == Item::FIELD_ITEM)
+ {
+ Field *field= ((Item_field *)real_item)->field;
+ if (field->reset())
+ {
+ my_error(ER_WARN_NULL_TO_NOTNULL, MYF(0),field->field_name,
+ thd->get_stmt_da()->current_row_for_warning());
+ DBUG_RETURN(1);
+ }
+ if (!field->maybe_null() && field->type() == FIELD_TYPE_TIMESTAMP)
+ field->set_time();
+ field->set_has_explicit_value();
+ /*
+ TODO: We probably should not throw warning for each field.
+ But how about intention to always have the same number
+ of warnings in THD::cuted_fields (and get rid of cuted_fields
+ in the end ?)
+ */
+ thd->cuted_fields++;
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_TOO_FEW_RECORDS,
+ ER(ER_WARN_TOO_FEW_RECORDS),
+ thd->get_stmt_da()->current_row_for_warning());
+ }
+ else if (item->type() == Item::STRING_ITEM)
+ {
+ ((Item_user_var_as_out_param *)item)->set_null_value(
+ read_info.read_charset);
+ }
+ else
+ {
+ my_error(ER_LOAD_DATA_INVALID_COLUMN, MYF(0), item->full_name());
+ DBUG_RETURN(1);
+ }
+ }
+ }
+
+ if (thd->killed ||
+ fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values,
+ ignore_check_option_errors,
+ TRG_EVENT_INSERT) ||
+ (table->default_field && table->update_default_fields()))
+ DBUG_RETURN(1);
+
+ switch (table_list->view_check_option(thd,
+ ignore_check_option_errors)) {
+ case VIEW_CHECK_SKIP:
+ read_info.next_line();
+ goto continue_loop;
+ case VIEW_CHECK_ERROR:
+ DBUG_RETURN(-1);
+ }
+
+ err= write_record(thd, table, &info);
+ table->auto_increment_field_not_null= FALSE;
+ if (err)
+ DBUG_RETURN(1);
+ /*
+ We don't need to reset auto-increment field since we are restoring
+ its default value at the beginning of each loop iteration.
+ */
+ if (read_info.next_line()) // Skip to next line
+ break;
+ if (read_info.line_cuted)
+ {
+ thd->cuted_fields++; /* To long row */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_TOO_MANY_RECORDS, ER(ER_WARN_TOO_MANY_RECORDS),
+ thd->get_stmt_da()->current_row_for_warning());
+ if (thd->killed)
+ DBUG_RETURN(1);
+ }
+ thd->get_stmt_da()->inc_current_row_for_warning();
+continue_loop:;
+ }
+ DBUG_RETURN(MY_TEST(read_info.error));
+}
+
+
+/****************************************************************************
+** Read rows in xml format
+****************************************************************************/
+static int
+read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values, READ_INFO &read_info,
+ String &row_tag, ulong skip_lines,
+ bool ignore_check_option_errors)
+{
+ List_iterator_fast<Item> it(fields_vars);
+ Item *item;
+ TABLE *table= table_list->table;
+ bool no_trans_update_stmt;
+ CHARSET_INFO *cs= read_info.read_charset;
+ DBUG_ENTER("read_xml_field");
+
+ no_trans_update_stmt= !table->file->has_transactions();
+
+ for ( ; ; it.rewind())
+ {
+ if (thd->killed)
+ {
+ thd->send_kill_message();
+ DBUG_RETURN(1);
+ }
+
+ // read row tag and save values into tag list
+ if (read_info.read_xml())
+ break;
+
+ List_iterator_fast<XML_TAG> xmlit(read_info.taglist);
+ xmlit.rewind();
+ XML_TAG *tag= NULL;
+
+#ifndef DBUG_OFF
+ DBUG_PRINT("read_xml_field", ("skip_lines=%d", (int) skip_lines));
+ while ((tag= xmlit++))
+ {
+ DBUG_PRINT("read_xml_field", ("got tag:%i '%s' '%s'",
+ tag->level, tag->field.c_ptr(),
+ tag->value.c_ptr()));
+ }
+#endif
+
+ restore_record(table, s->default_values);
+
+ while ((item= it++))
+ {
+ /* If this line is to be skipped we don't want to fill field or var */
+ if (skip_lines)
+ continue;
+
+ /* find field in tag list */
+ xmlit.rewind();
+ tag= xmlit++;
+
+ while(tag && strcmp(tag->field.c_ptr(), item->name) != 0)
+ tag= xmlit++;
+
+ if (!tag) // found null
+ {
+ if (item->type() == Item::FIELD_ITEM)
+ {
+ Field *field= ((Item_field *) item)->field;
+ field->reset();
+ field->set_null();
+ if (field == table->next_number_field)
+ table->auto_increment_field_not_null= TRUE;
+ if (!field->maybe_null())
+ {
+ if (field->type() == FIELD_TYPE_TIMESTAMP)
+ field->set_time();
+ else if (field != table->next_number_field)
+ field->set_warning(Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_NULL_TO_NOTNULL, 1);
+ }
+ /* Do not auto-update this field. */
+ field->set_has_explicit_value();
+ }
+ else
+ ((Item_user_var_as_out_param *) item)->set_null_value(cs);
+ continue;
+ }
+
+ if (item->type() == Item::FIELD_ITEM)
+ {
+
+ Field *field= ((Item_field *)item)->field;
+ field->set_notnull();
+ if (field == table->next_number_field)
+ table->auto_increment_field_not_null= TRUE;
+ field->store((char *) tag->value.ptr(), tag->value.length(), cs);
+ field->set_has_explicit_value();
+ }
+ else
+ ((Item_user_var_as_out_param *) item)->set_value(
+ (char *) tag->value.ptr(),
+ tag->value.length(), cs);
+ }
+
+ if (read_info.error)
+ break;
+
+ if (skip_lines)
+ {
+ skip_lines--;
+ continue;
+ }
+
+ if (item)
+ {
+ /* Have not read any field, thus input file is simply ended */
+ if (item == fields_vars.head())
+ break;
+
+ for ( ; item; item= it++)
+ {
+ if (item->type() == Item::FIELD_ITEM)
+ {
+ /*
+ QQ: We probably should not throw warning for each field.
+ But how about intention to always have the same number
+ of warnings in THD::cuted_fields (and get rid of cuted_fields
+ in the end ?)
+ */
+ thd->cuted_fields++;
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_TOO_FEW_RECORDS,
+ ER(ER_WARN_TOO_FEW_RECORDS),
+ thd->get_stmt_da()->current_row_for_warning());
+ }
+ else
+ ((Item_user_var_as_out_param *)item)->set_null_value(cs);
+ }
+ }
+
+ if (thd->killed ||
+ fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values,
+ ignore_check_option_errors,
+ TRG_EVENT_INSERT) ||
+ (table->default_field && table->update_default_fields()))
+ DBUG_RETURN(1);
+
+ switch (table_list->view_check_option(thd,
+ ignore_check_option_errors)) {
+ case VIEW_CHECK_SKIP:
+ read_info.next_line();
+ goto continue_loop;
+ case VIEW_CHECK_ERROR:
+ DBUG_RETURN(-1);
+ }
+
+ if (write_record(thd, table, &info))
+ DBUG_RETURN(1);
+
+ /*
+ We don't need to reset auto-increment field since we are restoring
+ its default value at the beginning of each loop iteration.
+ */
+ thd->transaction.stmt.modified_non_trans_table= no_trans_update_stmt;
+ thd->get_stmt_da()->inc_current_row_for_warning();
+ continue_loop:;
+ }
+ DBUG_RETURN(MY_TEST(read_info.error) || thd->is_error());
+} /* load xml end */
+
+
+/* Unescape all escape characters, mark \N as null */
+
+char
+READ_INFO::unescape(char chr)
+{
+ /* keep this switch synchornous with the ESCAPE_CHARS macro */
+ switch(chr) {
+ case 'n': return '\n';
+ case 't': return '\t';
+ case 'r': return '\r';
+ case 'b': return '\b';
+ case '0': return 0; // Ascii null
+ case 'Z': return '\032'; // Win32 end of file
+ case 'N': found_null=1;
+
+ /* fall through */
+ default: return chr;
+ }
+}
+
+
+/*
+ Read a line using buffering
+ If last line is empty (in line mode) then it isn't outputed
+*/
+
+
+READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs,
+ String &field_term, String &line_start, String &line_term,
+ String &enclosed_par, int escape, bool get_it_from_net,
+ bool is_fifo)
+ :file(file_par), buffer(NULL), buff_length(tot_length), escape_char(escape),
+ found_end_of_line(false), eof(false), need_end_io_cache(false),
+ error(false), line_cuted(false), found_null(false), read_charset(cs)
+{
+ /*
+ Field and line terminators must be interpreted as sequence of unsigned char.
+ Otherwise, non-ascii terminators will be negative on some platforms,
+ and positive on others (depending on the implementation of char).
+ */
+ field_term_ptr=
+ static_cast<const uchar*>(static_cast<const void*>(field_term.ptr()));
+ field_term_length= field_term.length();
+ line_term_ptr=
+ static_cast<const uchar*>(static_cast<const void*>(line_term.ptr()));
+ line_term_length= line_term.length();
+
+ level= 0; /* for load xml */
+ if (line_start.length() == 0)
+ {
+ line_start_ptr=0;
+ start_of_line= 0;
+ }
+ else
+ {
+ line_start_ptr= line_start.ptr();
+ line_start_end=line_start_ptr+line_start.length();
+ start_of_line= 1;
+ }
+ /* If field_terminator == line_terminator, don't use line_terminator */
+ if (field_term_length == line_term_length &&
+ !memcmp(field_term_ptr,line_term_ptr,field_term_length))
+ {
+ line_term_length=0;
+ line_term_ptr= NULL;
+ }
+ enclosed_char= (enclosed_length=enclosed_par.length()) ?
+ (uchar) enclosed_par[0] : INT_MAX;
+ field_term_char= field_term_length ? field_term_ptr[0] : INT_MAX;
+ line_term_char= line_term_length ? line_term_ptr[0] : INT_MAX;
+
+ /* Set of a stack for unget if long terminators */
+ uint length= MY_MAX(cs->mbmaxlen, MY_MAX(field_term_length, line_term_length)) + 1;
+ set_if_bigger(length,line_start.length());
+ stack=stack_pos=(int*) sql_alloc(sizeof(int)*length);
+
+ if (!(buffer=(uchar*) my_malloc(buff_length+1,MYF(MY_THREAD_SPECIFIC))))
+ error=1; /* purecov: inspected */
+ else
+ {
+ end_of_buff=buffer+buff_length;
+ if (init_io_cache(&cache,(get_it_from_net) ? -1 : file, 0,
+ (get_it_from_net) ? READ_NET :
+ (is_fifo ? READ_FIFO : READ_CACHE),0L,1,
+ MYF(MY_WME | MY_THREAD_SPECIFIC)))
+ {
+ my_free(buffer); /* purecov: inspected */
+ buffer= NULL;
+ error=1;
+ }
+ else
+ {
+ /*
+ init_io_cache() will not initialize read_function member
+ if the cache is READ_NET. So we work around the problem with a
+ manual assignment
+ */
+ need_end_io_cache = 1;
+
+#ifndef EMBEDDED_LIBRARY
+ if (get_it_from_net)
+ cache.read_function = _my_b_net_read;
+
+ if (mysql_bin_log.is_open())
+ cache.pre_read = cache.pre_close =
+ (IO_CACHE_CALLBACK) log_loaded_block;
+#endif
+ }
+ }
+}
+
+
+READ_INFO::~READ_INFO()
+{
+ if (need_end_io_cache)
+ ::end_io_cache(&cache);
+ my_free(buffer);
+ List_iterator<XML_TAG> xmlit(taglist);
+ XML_TAG *t;
+ while ((t= xmlit++))
+ delete(t);
+}
+
+
+inline int READ_INFO::terminator(const uchar *ptr,uint length)
+{
+ int chr=0; // Keep gcc happy
+ uint i;
+ for (i=1 ; i < length ; i++)
+ {
+ if ((chr=GET) != *(uchar*)++ptr)
+ {
+ break;
+ }
+ }
+ if (i == length)
+ return 1;
+ PUSH(chr);
+ while (i-- > 1)
+ PUSH(*--ptr);
+ return 0;
+}
+
+
+int READ_INFO::read_field()
+{
+ int chr,found_enclosed_char;
+ uchar *to,*new_buffer;
+
+ found_null=0;
+ if (found_end_of_line)
+ return 1; // One have to call next_line
+
+ /* Skip until we find 'line_start' */
+
+ if (start_of_line)
+ { // Skip until line_start
+ start_of_line=0;
+ if (find_start_of_fields())
+ return 1;
+ }
+ if ((chr=GET) == my_b_EOF)
+ {
+ found_end_of_line=eof=1;
+ return 1;
+ }
+ to=buffer;
+ if (chr == enclosed_char)
+ {
+ found_enclosed_char=enclosed_char;
+ *to++=(uchar) chr; // If error
+ }
+ else
+ {
+ found_enclosed_char= INT_MAX;
+ PUSH(chr);
+ }
+
+ for (;;)
+ {
+ while ( to < end_of_buff)
+ {
+ chr = GET;
+ if (chr == my_b_EOF)
+ goto found_eof;
+ if (chr == escape_char)
+ {
+ if ((chr=GET) == my_b_EOF)
+ {
+ *to++= (uchar) escape_char;
+ goto found_eof;
+ }
+ /*
+ When escape_char == enclosed_char, we treat it like we do for
+ handling quotes in SQL parsing -- you can double-up the
+ escape_char to include it literally, but it doesn't do escapes
+ like \n. This allows: LOAD DATA ... ENCLOSED BY '"' ESCAPED BY '"'
+ with data like: "fie""ld1", "field2"
+ */
+ if (escape_char != enclosed_char || chr == escape_char)
+ {
+ *to++ = (uchar) unescape((char) chr);
+ continue;
+ }
+ PUSH(chr);
+ chr= escape_char;
+ }
+#ifdef ALLOW_LINESEPARATOR_IN_STRINGS
+ if (chr == line_term_char)
+#else
+ if (chr == line_term_char && found_enclosed_char == INT_MAX)
+#endif
+ {
+ if (terminator(line_term_ptr,line_term_length))
+ { // Maybe unexpected linefeed
+ enclosed=0;
+ found_end_of_line=1;
+ row_start=buffer;
+ row_end= to;
+ return 0;
+ }
+ }
+ if (chr == found_enclosed_char)
+ {
+ if ((chr=GET) == found_enclosed_char)
+ { // Remove dupplicated
+ *to++ = (uchar) chr;
+ continue;
+ }
+ // End of enclosed field if followed by field_term or line_term
+ if (chr == my_b_EOF ||
+ (chr == line_term_char && terminator(line_term_ptr,
+ line_term_length)))
+ {
+ /* Maybe unexpected linefeed */
+ enclosed=1;
+ found_end_of_line=1;
+ row_start=buffer+1;
+ row_end= to;
+ return 0;
+ }
+ if (chr == field_term_char &&
+ terminator(field_term_ptr,field_term_length))
+ {
+ enclosed=1;
+ row_start=buffer+1;
+ row_end= to;
+ return 0;
+ }
+ /*
+ The string didn't terminate yet.
+ Store back next character for the loop
+ */
+ PUSH(chr);
+ /* copy the found term character to 'to' */
+ chr= found_enclosed_char;
+ }
+ else if (chr == field_term_char && found_enclosed_char == INT_MAX)
+ {
+ if (terminator(field_term_ptr,field_term_length))
+ {
+ enclosed=0;
+ row_start=buffer;
+ row_end= to;
+ return 0;
+ }
+ }
+#ifdef USE_MB
+ if (my_mbcharlen(read_charset, chr) > 1 &&
+ to + my_mbcharlen(read_charset, chr) <= end_of_buff)
+ {
+ uchar* p= to;
+ int ml, i;
+ *to++ = chr;
+
+ ml= my_mbcharlen(read_charset, chr);
+
+ for (i= 1; i < ml; i++)
+ {
+ chr= GET;
+ if (chr == my_b_EOF)
+ {
+ /*
+ Need to back up the bytes already ready from illformed
+ multi-byte char
+ */
+ to-= i;
+ goto found_eof;
+ }
+ *to++ = chr;
+ }
+ if (my_ismbchar(read_charset,
+ (const char *)p,
+ (const char *)to))
+ continue;
+ for (i= 0; i < ml; i++)
+ PUSH(*--to);
+ chr= GET;
+ }
+#endif
+ *to++ = (uchar) chr;
+ }
+ /*
+ ** We come here if buffer is too small. Enlarge it and continue
+ */
+ if (!(new_buffer=(uchar*) my_realloc((char*) buffer,buff_length+1+IO_SIZE,
+ MYF(MY_WME | MY_THREAD_SPECIFIC))))
+ return (error=1);
+ to=new_buffer + (to-buffer);
+ buffer=new_buffer;
+ buff_length+=IO_SIZE;
+ end_of_buff=buffer+buff_length;
+ }
+
+found_eof:
+ enclosed=0;
+ found_end_of_line=eof=1;
+ row_start=buffer;
+ row_end=to;
+ return 0;
+}
+
+/*
+ Read a row with fixed length.
+
+ NOTES
+ The row may not be fixed size on disk if there are escape
+ characters in the file.
+
+ IMPLEMENTATION NOTE
+ One can't use fixed length with multi-byte charset **
+
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+int READ_INFO::read_fixed_length()
+{
+ int chr;
+ uchar *to;
+ if (found_end_of_line)
+ return 1; // One have to call next_line
+
+ if (start_of_line)
+ { // Skip until line_start
+ start_of_line=0;
+ if (find_start_of_fields())
+ return 1;
+ }
+
+ to=row_start=buffer;
+ while (to < end_of_buff)
+ {
+ if ((chr=GET) == my_b_EOF)
+ goto found_eof;
+ if (chr == escape_char)
+ {
+ if ((chr=GET) == my_b_EOF)
+ {
+ *to++= (uchar) escape_char;
+ goto found_eof;
+ }
+ *to++ =(uchar) unescape((char) chr);
+ continue;
+ }
+ if (chr == line_term_char)
+ {
+ if (terminator(line_term_ptr,line_term_length))
+ { // Maybe unexpected linefeed
+ found_end_of_line=1;
+ row_end= to;
+ return 0;
+ }
+ }
+ *to++ = (uchar) chr;
+ }
+ row_end=to; // Found full line
+ return 0;
+
+found_eof:
+ found_end_of_line=eof=1;
+ row_start=buffer;
+ row_end=to;
+ return to == buffer ? 1 : 0;
+}
+
+
+int READ_INFO::next_line()
+{
+ line_cuted=0;
+ start_of_line= line_start_ptr != 0;
+ if (found_end_of_line || eof)
+ {
+ found_end_of_line=0;
+ return eof;
+ }
+ found_end_of_line=0;
+ if (!line_term_length)
+ return 0; // No lines
+ for (;;)
+ {
+ int chr = GET;
+#ifdef USE_MB
+ if (my_mbcharlen(read_charset, chr) > 1)
+ {
+ for (uint i=1;
+ chr != my_b_EOF && i<my_mbcharlen(read_charset, chr);
+ i++)
+ chr = GET;
+ if (chr == escape_char)
+ continue;
+ }
+#endif
+ if (chr == my_b_EOF)
+ {
+ eof=1;
+ return 1;
+ }
+ if (chr == escape_char)
+ {
+ line_cuted=1;
+ if (GET == my_b_EOF)
+ return 1;
+ continue;
+ }
+ if (chr == line_term_char && terminator(line_term_ptr,line_term_length))
+ return 0;
+ line_cuted=1;
+ }
+}
+
+
+bool READ_INFO::find_start_of_fields()
+{
+ int chr;
+ try_again:
+ do
+ {
+ if ((chr=GET) == my_b_EOF)
+ {
+ found_end_of_line=eof=1;
+ return 1;
+ }
+ } while ((char) chr != line_start_ptr[0]);
+ for (const char *ptr=line_start_ptr+1 ; ptr != line_start_end ; ptr++)
+ {
+ chr=GET; // Eof will be checked later
+ if ((char) chr != *ptr)
+ { // Can't be line_start
+ PUSH(chr);
+ while (--ptr != line_start_ptr)
+ { // Restart with next char
+ PUSH( *ptr);
+ }
+ goto try_again;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ Clear taglist from tags with a specified level
+*/
+int READ_INFO::clear_level(int level_arg)
+{
+ DBUG_ENTER("READ_INFO::read_xml clear_level");
+ List_iterator<XML_TAG> xmlit(taglist);
+ xmlit.rewind();
+ XML_TAG *tag;
+
+ while ((tag= xmlit++))
+ {
+ if(tag->level >= level_arg)
+ {
+ xmlit.remove();
+ delete tag;
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Convert an XML entity to Unicode value.
+ Return -1 on error;
+*/
+static int
+my_xml_entity_to_char(const char *name, uint length)
+{
+ if (length == 2)
+ {
+ if (!memcmp(name, "gt", length))
+ return '>';
+ if (!memcmp(name, "lt", length))
+ return '<';
+ }
+ else if (length == 3)
+ {
+ if (!memcmp(name, "amp", length))
+ return '&';
+ }
+ else if (length == 4)
+ {
+ if (!memcmp(name, "quot", length))
+ return '"';
+ if (!memcmp(name, "apos", length))
+ return '\'';
+ }
+ return -1;
+}
+
+
+/**
+ @brief Convert newline, linefeed, tab to space
+
+ @param chr character
+
+ @details According to the "XML 1.0" standard,
+ only space (#x20) characters, carriage returns,
+ line feeds or tabs are considered as spaces.
+ Convert all of them to space (#x20) for parsing simplicity.
+*/
+static int
+my_tospace(int chr)
+{
+ return (chr == '\t' || chr == '\r' || chr == '\n') ? ' ' : chr;
+}
+
+
+/*
+ Read an xml value: handle multibyte and xml escape
+*/
+int READ_INFO::read_value(int delim, String *val)
+{
+ int chr;
+ String tmp;
+
+ for (chr= GET; my_tospace(chr) != delim && chr != my_b_EOF;)
+ {
+#ifdef USE_MB
+ if (my_mbcharlen(read_charset, chr) > 1)
+ {
+ DBUG_PRINT("read_xml",("multi byte"));
+ int i, ml= my_mbcharlen(read_charset, chr);
+ for (i= 1; i < ml; i++)
+ {
+ val->append(chr);
+ /*
+ Don't use my_tospace() in the middle of a multi-byte character
+ TODO: check that the multi-byte sequence is valid.
+ */
+ chr= GET;
+ if (chr == my_b_EOF)
+ return chr;
+ }
+ }
+#endif
+ if(chr == '&')
+ {
+ tmp.length(0);
+ for (chr= my_tospace(GET) ; chr != ';' ; chr= my_tospace(GET))
+ {
+ if (chr == my_b_EOF)
+ return chr;
+ tmp.append(chr);
+ }
+ if ((chr= my_xml_entity_to_char(tmp.ptr(), tmp.length())) >= 0)
+ val->append(chr);
+ else
+ {
+ val->append('&');
+ val->append(tmp);
+ val->append(';');
+ }
+ }
+ else
+ val->append(chr);
+ chr= GET;
+ }
+ return my_tospace(chr);
+}
+
+
+/*
+ Read a record in xml format
+ tags and attributes are stored in taglist
+ when tag set in ROWS IDENTIFIED BY is closed, we are ready and return
+*/
+int READ_INFO::read_xml()
+{
+ DBUG_ENTER("READ_INFO::read_xml");
+ int chr, chr2, chr3;
+ int delim= 0;
+ String tag, attribute, value;
+ bool in_tag= false;
+
+ tag.length(0);
+ attribute.length(0);
+ value.length(0);
+
+ for (chr= my_tospace(GET); chr != my_b_EOF ; )
+ {
+ switch(chr){
+ case '<': /* read tag */
+ /* TODO: check if this is a comment <!-- comment --> */
+ chr= my_tospace(GET);
+ if(chr == '!')
+ {
+ chr2= GET;
+ chr3= GET;
+
+ if(chr2 == '-' && chr3 == '-')
+ {
+ chr2= 0;
+ chr3= 0;
+ chr= my_tospace(GET);
+
+ while(chr != '>' || chr2 != '-' || chr3 != '-')
+ {
+ if(chr == '-')
+ {
+ chr3= chr2;
+ chr2= chr;
+ }
+ else if (chr2 == '-')
+ {
+ chr2= 0;
+ chr3= 0;
+ }
+ chr= my_tospace(GET);
+ if (chr == my_b_EOF)
+ goto found_eof;
+ }
+ break;
+ }
+ }
+
+ tag.length(0);
+ while(chr != '>' && chr != ' ' && chr != '/' && chr != my_b_EOF)
+ {
+ if(chr != delim) /* fix for the '<field name =' format */
+ tag.append(chr);
+ chr= my_tospace(GET);
+ }
+
+ // row tag should be in ROWS IDENTIFIED BY '<row>' - stored in line_term
+ if((tag.length() == line_term_length -2) &&
+ (memcmp(tag.ptr(), line_term_ptr + 1, tag.length()) == 0))
+ {
+ DBUG_PRINT("read_xml", ("start-of-row: %i %s %s",
+ level,tag.c_ptr_safe(), line_term_ptr));
+ }
+
+ if(chr == ' ' || chr == '>')
+ {
+ level++;
+ clear_level(level + 1);
+ }
+
+ if (chr == ' ')
+ in_tag= true;
+ else
+ in_tag= false;
+ break;
+
+ case ' ': /* read attribute */
+ while(chr == ' ') /* skip blanks */
+ chr= my_tospace(GET);
+
+ if(!in_tag)
+ break;
+
+ while(chr != '=' && chr != '/' && chr != '>' && chr != my_b_EOF)
+ {
+ attribute.append(chr);
+ chr= my_tospace(GET);
+ }
+ break;
+
+ case '>': /* end tag - read tag value */
+ in_tag= false;
+ chr= read_value('<', &value);
+ if(chr == my_b_EOF)
+ goto found_eof;
+
+ /* save value to list */
+ if(tag.length() > 0 && value.length() > 0)
+ {
+ DBUG_PRINT("read_xml", ("lev:%i tag:%s val:%s",
+ level,tag.c_ptr_safe(), value.c_ptr_safe()));
+ taglist.push_front( new XML_TAG(level, tag, value));
+ }
+ tag.length(0);
+ value.length(0);
+ attribute.length(0);
+ break;
+
+ case '/': /* close tag */
+ level--;
+ chr= my_tospace(GET);
+ if(chr != '>') /* if this is an empty tag <tag /> */
+ tag.length(0); /* we should keep tag value */
+ while(chr != '>' && chr != my_b_EOF)
+ {
+ tag.append(chr);
+ chr= my_tospace(GET);
+ }
+
+ if((tag.length() == line_term_length -2) &&
+ (memcmp(tag.ptr(), line_term_ptr + 1, tag.length()) == 0))
+ {
+ DBUG_PRINT("read_xml", ("found end-of-row %i %s",
+ level, tag.c_ptr_safe()));
+ DBUG_RETURN(0); //normal return
+ }
+ chr= my_tospace(GET);
+ break;
+
+ case '=': /* attribute name end - read the value */
+ //check for tag field and attribute name
+ if(!memcmp(tag.c_ptr_safe(), STRING_WITH_LEN("field")) &&
+ !memcmp(attribute.c_ptr_safe(), STRING_WITH_LEN("name")))
+ {
+ /*
+ this is format <field name="xx">xx</field>
+ where actual fieldname is in attribute
+ */
+ delim= my_tospace(GET);
+ tag.length(0);
+ attribute.length(0);
+ chr= '<'; /* we pretend that it is a tag */
+ level--;
+ break;
+ }
+
+ //check for " or '
+ chr= GET;
+ if (chr == my_b_EOF)
+ goto found_eof;
+ if(chr == '"' || chr == '\'')
+ {
+ delim= chr;
+ }
+ else
+ {
+ delim= ' '; /* no delimiter, use space */
+ PUSH(chr);
+ }
+
+ chr= read_value(delim, &value);
+ if(attribute.length() > 0 && value.length() > 0)
+ {
+ DBUG_PRINT("read_xml", ("lev:%i att:%s val:%s\n",
+ level + 1,
+ attribute.c_ptr_safe(),
+ value.c_ptr_safe()));
+ taglist.push_front(new XML_TAG(level + 1, attribute, value));
+ }
+ attribute.length(0);
+ value.length(0);
+ if (chr != ' ')
+ chr= my_tospace(GET);
+ break;
+
+ default:
+ chr= my_tospace(GET);
+ } /* end switch */
+ } /* end while */
+
+found_eof:
+ DBUG_PRINT("read_xml",("Found eof"));
+ eof= 1;
+ DBUG_RETURN(1);
+}
diff --git a/sql/sql_load.h b/sql/sql_load.h
new file mode 100644
index 00000000000..f767e39387b
--- /dev/null
+++ b/sql/sql_load.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_LOAD_INCLUDED
+#define SQL_LOAD_INCLUDED
+
+#include "sql_list.h" /* List */
+
+class Item;
+
+#include "sql_class.h" /* enum_duplicates */
+
+class sql_exchange;
+
+int mysql_load(THD *thd, sql_exchange *ex, TABLE_LIST *table_list,
+ List<Item> &fields_vars, List<Item> &set_fields,
+ List<Item> &set_values_list,
+ enum enum_duplicates handle_duplicates, bool ignore,
+ bool local_file);
+
+
+#endif /* SQL_LOAD_INCLUDED */
diff --git a/sql/sql_locale.cc b/sql/sql_locale.cc
new file mode 100644
index 00000000000..d918d5c9cf4
--- /dev/null
+++ b/sql/sql_locale.cc
@@ -0,0 +1,3518 @@
+/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/*
+ The beginnings of locale(7) support.
+ Sponsored for subset of LC_TIME support, WorkLog entry 2928, -- Josh Chamas
+
+ !! This file is built from my_locale.pl !!
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_locale.h"
+#include "sql_class.h" // THD
+#include "my_sys.h" // MY_*, NullS, NULL
+
+
+enum err_msgs_index
+{
+ en_US= 0, cs_CZ, da_DK, nl_NL, et_EE, fr_FR, de_DE, el_GR, hu_HU, it_IT,
+ ja_JP, ko_KR, no_NO, nn_NO, pl_PL, pt_PT, ro_RO, ru_RU, sr_RS, sk_SK,
+ es_ES, sv_SE, uk_UA
+} ERR_MSGS_INDEX;
+
+
+MY_LOCALE_ERRMSGS global_errmsgs[]=
+{
+ {"english", NULL},
+ {"czech", NULL},
+ {"danish", NULL},
+ {"dutch", NULL},
+ {"estonian", NULL},
+ {"french", NULL},
+ {"german", NULL},
+ {"greek", NULL},
+ {"hungarian", NULL},
+ {"italian", NULL},
+ {"japanese", NULL},
+ {"korean", NULL},
+ {"norwegian", NULL},
+ {"norwegian-ny", NULL},
+ {"polish", NULL},
+ {"portuguese", NULL},
+ {"romanian", NULL},
+ {"russian", NULL},
+ {"serbian", NULL},
+ {"slovak", NULL},
+ {"spanish", NULL},
+ {"swedish", NULL},
+ {"ukrainian", NULL},
+ {NULL, NULL}
+};
+
+
+/***** LOCALE BEGIN ar_AE: Arabic - United Arab Emirates *****/
+static const char *my_locale_month_names_ar_AE[13] =
+ {"يناير","ÙØ¨Ø±Ø§ÙŠØ±","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوÙمبر","ديسمبر", NullS };
+static const char *my_locale_ab_month_names_ar_AE[13] =
+ {"ينا","ÙØ¨Ø±","مار","أبر","ماي","يون","يول","أغس","سبت","أكت","نوÙ","ديس", NullS };
+static const char *my_locale_day_names_ar_AE[8] =
+ {"الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت ","الأحد", NullS };
+static const char *my_locale_ab_day_names_ar_AE[8] =
+ {"ن","ث","ر","خ","ج","س","ح", NullS };
+static TYPELIB my_locale_typelib_month_names_ar_AE =
+ { array_elements(my_locale_month_names_ar_AE)-1, "", my_locale_month_names_ar_AE, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ar_AE =
+ { array_elements(my_locale_ab_month_names_ar_AE)-1, "", my_locale_ab_month_names_ar_AE, NULL };
+static TYPELIB my_locale_typelib_day_names_ar_AE =
+ { array_elements(my_locale_day_names_ar_AE)-1, "", my_locale_day_names_ar_AE, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ar_AE =
+ { array_elements(my_locale_ab_day_names_ar_AE)-1, "", my_locale_ab_day_names_ar_AE, NULL };
+MY_LOCALE my_locale_ar_AE
+(
+ 6,
+ "ar_AE",
+ "Arabic - United Arab Emirates",
+ FALSE,
+ &my_locale_typelib_month_names_ar_AE,
+ &my_locale_typelib_ab_month_names_ar_AE,
+ &my_locale_typelib_day_names_ar_AE,
+ &my_locale_typelib_ab_day_names_ar_AE,
+ 6,
+ 8,
+ '.', /* decimal point ar_AE */
+ ',', /* thousands_sep ar_AE */
+ "\x03", /* grouping ar_AE */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_AE *****/
+
+/***** LOCALE BEGIN ar_BH: Arabic - Bahrain *****/
+static const char *my_locale_month_names_ar_BH[13] =
+ {"يناير","ÙØ¨Ø±Ø§ÙŠØ±","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوÙمبر","ديسمبر", NullS };
+static const char *my_locale_ab_month_names_ar_BH[13] =
+ {"ينا","ÙØ¨Ø±","مار","أبر","ماي","يون","يول","أغس","سبت","أكت","نوÙ","ديس", NullS };
+static const char *my_locale_day_names_ar_BH[8] =
+ {"الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد", NullS };
+static const char *my_locale_ab_day_names_ar_BH[8] =
+ {"ن","ث","ر","خ","ج","س","ح", NullS };
+static TYPELIB my_locale_typelib_month_names_ar_BH =
+ { array_elements(my_locale_month_names_ar_BH)-1, "", my_locale_month_names_ar_BH, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ar_BH =
+ { array_elements(my_locale_ab_month_names_ar_BH)-1, "", my_locale_ab_month_names_ar_BH, NULL };
+static TYPELIB my_locale_typelib_day_names_ar_BH =
+ { array_elements(my_locale_day_names_ar_BH)-1, "", my_locale_day_names_ar_BH, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ar_BH =
+ { array_elements(my_locale_ab_day_names_ar_BH)-1, "", my_locale_ab_day_names_ar_BH, NULL };
+MY_LOCALE my_locale_ar_BH
+(
+ 7,
+ "ar_BH",
+ "Arabic - Bahrain",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_BH */
+ ',', /* thousands_sep ar_BH */
+ "\x03", /* grouping ar_BH */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_BH *****/
+
+/***** LOCALE BEGIN ar_JO: Arabic - Jordan *****/
+static const char *my_locale_month_names_ar_JO[13] =
+ {"كانون الثاني","شباط","آذار","نيسان","نوار","حزيران","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول", NullS };
+static const char *my_locale_ab_month_names_ar_JO[13] =
+ {"كانون الثاني","شباط","آذار","نيسان","نوار","حزيران","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول", NullS };
+static const char *my_locale_day_names_ar_JO[8] =
+ {"الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد", NullS };
+static const char *my_locale_ab_day_names_ar_JO[8] =
+ {"الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد", NullS };
+static TYPELIB my_locale_typelib_month_names_ar_JO =
+ { array_elements(my_locale_month_names_ar_JO)-1, "", my_locale_month_names_ar_JO, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ar_JO =
+ { array_elements(my_locale_ab_month_names_ar_JO)-1, "", my_locale_ab_month_names_ar_JO, NULL };
+static TYPELIB my_locale_typelib_day_names_ar_JO =
+ { array_elements(my_locale_day_names_ar_JO)-1, "", my_locale_day_names_ar_JO, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ar_JO =
+ { array_elements(my_locale_ab_day_names_ar_JO)-1, "", my_locale_ab_day_names_ar_JO, NULL };
+MY_LOCALE my_locale_ar_JO
+(
+ 8,
+ "ar_JO",
+ "Arabic - Jordan",
+ FALSE,
+ &my_locale_typelib_month_names_ar_JO,
+ &my_locale_typelib_ab_month_names_ar_JO,
+ &my_locale_typelib_day_names_ar_JO,
+ &my_locale_typelib_ab_day_names_ar_JO,
+ 12,
+ 8,
+ '.', /* decimal point ar_JO */
+ ',', /* thousands_sep ar_JO */
+ "\x03", /* grouping ar_JO */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_JO *****/
+
+/***** LOCALE BEGIN ar_SA: Arabic - Saudi Arabia *****/
+static const char *my_locale_month_names_ar_SA[13] =
+ {"كانون الثاني","شباط","آذار","نيسـان","أيار","حزيران","تـمـوز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول", NullS };
+static const char *my_locale_ab_month_names_ar_SA[13] =
+ {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec", NullS };
+static const char *my_locale_day_names_ar_SA[8] =
+ {"الإثنين","الثلاثاء","الأربعاء","الخميس","الجمعـة","السبت","الأحد", NullS };
+static const char *my_locale_ab_day_names_ar_SA[8] =
+ {"Mon","Tue","Wed","Thu","Fri","Sat","Sun", NullS };
+static TYPELIB my_locale_typelib_month_names_ar_SA =
+ { array_elements(my_locale_month_names_ar_SA)-1, "", my_locale_month_names_ar_SA, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ar_SA =
+ { array_elements(my_locale_ab_month_names_ar_SA)-1, "", my_locale_ab_month_names_ar_SA, NULL };
+static TYPELIB my_locale_typelib_day_names_ar_SA =
+ { array_elements(my_locale_day_names_ar_SA)-1, "", my_locale_day_names_ar_SA, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ar_SA =
+ { array_elements(my_locale_ab_day_names_ar_SA)-1, "", my_locale_ab_day_names_ar_SA, NULL };
+MY_LOCALE my_locale_ar_SA
+(
+ 9,
+ "ar_SA",
+ "Arabic - Saudi Arabia",
+ FALSE,
+ &my_locale_typelib_month_names_ar_SA,
+ &my_locale_typelib_ab_month_names_ar_SA,
+ &my_locale_typelib_day_names_ar_SA,
+ &my_locale_typelib_ab_day_names_ar_SA,
+ 12,
+ 8,
+ '.', /* decimal point ar_SA */
+ '\0', /* thousands_sep ar_SA */
+ "\x80", /* grouping ar_SA */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_SA *****/
+
+/***** LOCALE BEGIN ar_SY: Arabic - Syria *****/
+static const char *my_locale_month_names_ar_SY[13] =
+ {"كانون الثاني","شباط","آذار","نيسان","نواران","حزير","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول", NullS };
+static const char *my_locale_ab_month_names_ar_SY[13] =
+ {"كانون الثاني","شباط","آذار","نيسان","نوار","حزيران","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول", NullS };
+static const char *my_locale_day_names_ar_SY[8] =
+ {"الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد", NullS };
+static const char *my_locale_ab_day_names_ar_SY[8] =
+ {"الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد", NullS };
+static TYPELIB my_locale_typelib_month_names_ar_SY =
+ { array_elements(my_locale_month_names_ar_SY)-1, "", my_locale_month_names_ar_SY, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ar_SY =
+ { array_elements(my_locale_ab_month_names_ar_SY)-1, "", my_locale_ab_month_names_ar_SY, NULL };
+static TYPELIB my_locale_typelib_day_names_ar_SY =
+ { array_elements(my_locale_day_names_ar_SY)-1, "", my_locale_day_names_ar_SY, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ar_SY =
+ { array_elements(my_locale_ab_day_names_ar_SY)-1, "", my_locale_ab_day_names_ar_SY, NULL };
+MY_LOCALE my_locale_ar_SY
+(
+ 10,
+ "ar_SY",
+ "Arabic - Syria",
+ FALSE,
+ &my_locale_typelib_month_names_ar_SY,
+ &my_locale_typelib_ab_month_names_ar_SY,
+ &my_locale_typelib_day_names_ar_SY,
+ &my_locale_typelib_ab_day_names_ar_SY,
+ 12,
+ 8,
+ '.', /* decimal point ar_SY */
+ ',', /* thousands_sep ar_SY */
+ "\x03", /* grouping ar_SY */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_SY *****/
+
+/***** LOCALE BEGIN be_BY: Belarusian - Belarus *****/
+static const char *my_locale_month_names_be_BY[13] =
+ {"Студзень","Люты","Сакавік","КраÑавік","Травень","ЧÑрвень","Ліпень","Жнівень","ВераÑень","КаÑтрычнік","ЛіÑтапад","Снежань", NullS };
+static const char *my_locale_ab_month_names_be_BY[13] =
+ {"Стд","Лют","Сак","КрÑ","Тра","ЧÑÑ€","Ліп","Жнв","Ð’Ñ€Ñ","КÑÑ‚","ЛіÑ","Снж", NullS };
+static const char *my_locale_day_names_be_BY[8] =
+ {"ПанÑдзелак","Ðўторак","Серада","Чацвер","ПÑтніца","Субота","ÐÑдзелÑ", NullS };
+static const char *my_locale_ab_day_names_be_BY[8] =
+ {"Пан","Ðўт","Срд","Чцв","ПÑÑ‚","Суб","ÐÑд", NullS };
+static TYPELIB my_locale_typelib_month_names_be_BY =
+ { array_elements(my_locale_month_names_be_BY)-1, "", my_locale_month_names_be_BY, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_be_BY =
+ { array_elements(my_locale_ab_month_names_be_BY)-1, "", my_locale_ab_month_names_be_BY, NULL };
+static TYPELIB my_locale_typelib_day_names_be_BY =
+ { array_elements(my_locale_day_names_be_BY)-1, "", my_locale_day_names_be_BY, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_be_BY =
+ { array_elements(my_locale_ab_day_names_be_BY)-1, "", my_locale_ab_day_names_be_BY, NULL };
+MY_LOCALE my_locale_be_BY
+(
+ 11,
+ "be_BY",
+ "Belarusian - Belarus",
+ FALSE,
+ &my_locale_typelib_month_names_be_BY,
+ &my_locale_typelib_ab_month_names_be_BY,
+ &my_locale_typelib_day_names_be_BY,
+ &my_locale_typelib_ab_day_names_be_BY,
+ 10,
+ 10,
+ ',', /* decimal point be_BY */
+ '.', /* thousands_sep be_BY */
+ "\x03\x03", /* grouping be_BY */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END be_BY *****/
+
+/***** LOCALE BEGIN bg_BG: Bulgarian - Bulgaria *****/
+static const char *my_locale_month_names_bg_BG[13] =
+ {"Ñнуари","февруари","март","април","май","юни","юли","авгуÑÑ‚","Ñептември","октомври","ноември","декември", NullS };
+static const char *my_locale_ab_month_names_bg_BG[13] =
+ {"Ñну","фев","мар","апр","май","юни","юли","авг","Ñеп","окт","ное","дек", NullS };
+static const char *my_locale_day_names_bg_BG[8] =
+ {"понеделник","вторник","ÑÑ€Ñда","четвъртък","петък","Ñъбота","неделÑ", NullS };
+static const char *my_locale_ab_day_names_bg_BG[8] =
+ {"пн","вт","ÑÑ€","чт","пт","Ñб","нд", NullS };
+static TYPELIB my_locale_typelib_month_names_bg_BG =
+ { array_elements(my_locale_month_names_bg_BG)-1, "", my_locale_month_names_bg_BG, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_bg_BG =
+ { array_elements(my_locale_ab_month_names_bg_BG)-1, "", my_locale_ab_month_names_bg_BG, NULL };
+static TYPELIB my_locale_typelib_day_names_bg_BG =
+ { array_elements(my_locale_day_names_bg_BG)-1, "", my_locale_day_names_bg_BG, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_bg_BG =
+ { array_elements(my_locale_ab_day_names_bg_BG)-1, "", my_locale_ab_day_names_bg_BG, NULL };
+MY_LOCALE my_locale_bg_BG
+(
+ 12,
+ "bg_BG",
+ "Bulgarian - Bulgaria",
+ FALSE,
+ &my_locale_typelib_month_names_bg_BG,
+ &my_locale_typelib_ab_month_names_bg_BG,
+ &my_locale_typelib_day_names_bg_BG,
+ &my_locale_typelib_ab_day_names_bg_BG,
+ 9,
+ 10,
+ ',', /* decimal point bg_BG */
+ '\0', /* thousands_sep bg_BG */
+ "\x03\x03", /* grouping bg_BG */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END bg_BG *****/
+
+/***** LOCALE BEGIN ca_ES: Catalan - Catalan *****/
+static const char *my_locale_month_names_ca_ES[13] =
+ {"gener","febrer","març","abril","maig","juny","juliol","agost","setembre","octubre","novembre","desembre", NullS };
+static const char *my_locale_ab_month_names_ca_ES[13] =
+ {"gen","feb","mar","abr","mai","jun","jul","ago","set","oct","nov","des", NullS };
+static const char *my_locale_day_names_ca_ES[8] =
+ {"dilluns","dimarts","dimecres","dijous","divendres","dissabte","diumenge", NullS };
+static const char *my_locale_ab_day_names_ca_ES[8] =
+ {"dl","dt","dc","dj","dv","ds","dg", NullS };
+static TYPELIB my_locale_typelib_month_names_ca_ES =
+ { array_elements(my_locale_month_names_ca_ES)-1, "", my_locale_month_names_ca_ES, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ca_ES =
+ { array_elements(my_locale_ab_month_names_ca_ES)-1, "", my_locale_ab_month_names_ca_ES, NULL };
+static TYPELIB my_locale_typelib_day_names_ca_ES =
+ { array_elements(my_locale_day_names_ca_ES)-1, "", my_locale_day_names_ca_ES, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ca_ES =
+ { array_elements(my_locale_ab_day_names_ca_ES)-1, "", my_locale_ab_day_names_ca_ES, NULL };
+MY_LOCALE my_locale_ca_ES
+(
+ 13,
+ "ca_ES",
+ "Catalan - Catalan",
+ FALSE,
+ &my_locale_typelib_month_names_ca_ES,
+ &my_locale_typelib_ab_month_names_ca_ES,
+ &my_locale_typelib_day_names_ca_ES,
+ &my_locale_typelib_ab_day_names_ca_ES,
+ 8,
+ 9,
+ ',', /* decimal point ca_ES */
+ '\0', /* thousands_sep ca_ES */
+ "\x80\x80", /* grouping ca_ES */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ca_ES *****/
+
+/***** LOCALE BEGIN cs_CZ: Czech - Czech Republic *****/
+static const char *my_locale_month_names_cs_CZ[13] =
+ {"leden","únor","bÅ™ezen","duben","kvÄ›ten","Äerven","Äervenec","srpen","září","říjen","listopad","prosinec", NullS };
+static const char *my_locale_ab_month_names_cs_CZ[13] =
+ {"led","úno","bÅ™e","dub","kvÄ›","Äen","Äec","srp","zář","říj","lis","pro", NullS };
+static const char *my_locale_day_names_cs_CZ[8] =
+ {"Pondělí","Úterý","Středa","Čtvrtek","Pátek","Sobota","Neděle", NullS };
+static const char *my_locale_ab_day_names_cs_CZ[8] =
+ {"Po","Út","St","Čt","Pá","So","Ne", NullS };
+static TYPELIB my_locale_typelib_month_names_cs_CZ =
+ { array_elements(my_locale_month_names_cs_CZ)-1, "", my_locale_month_names_cs_CZ, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_cs_CZ =
+ { array_elements(my_locale_ab_month_names_cs_CZ)-1, "", my_locale_ab_month_names_cs_CZ, NULL };
+static TYPELIB my_locale_typelib_day_names_cs_CZ =
+ { array_elements(my_locale_day_names_cs_CZ)-1, "", my_locale_day_names_cs_CZ, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_cs_CZ =
+ { array_elements(my_locale_ab_day_names_cs_CZ)-1, "", my_locale_ab_day_names_cs_CZ, NULL };
+MY_LOCALE my_locale_cs_CZ
+(
+ 14,
+ "cs_CZ",
+ "Czech - Czech Republic",
+ FALSE,
+ &my_locale_typelib_month_names_cs_CZ,
+ &my_locale_typelib_ab_month_names_cs_CZ,
+ &my_locale_typelib_day_names_cs_CZ,
+ &my_locale_typelib_ab_day_names_cs_CZ,
+ 8,
+ 7,
+ ',', /* decimal point cs_CZ */
+ ' ', /* thousands_sep cs_CZ */
+ "\x03\x03", /* grouping cs_CZ */
+ &global_errmsgs[cs_CZ]
+);
+/***** LOCALE END cs_CZ *****/
+
+/***** LOCALE BEGIN da_DK: Danish - Denmark *****/
+static const char *my_locale_month_names_da_DK[13] =
+ {"januar","februar","marts","april","maj","juni","juli","august","september","oktober","november","december", NullS };
+static const char *my_locale_ab_month_names_da_DK[13] =
+ {"jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec", NullS };
+static const char *my_locale_day_names_da_DK[8] =
+ {"mandag","tirsdag","onsdag","torsdag","fredag","lørdag","søndag", NullS };
+static const char *my_locale_ab_day_names_da_DK[8] =
+ {"man","tir","ons","tor","fre","lør","søn", NullS };
+static TYPELIB my_locale_typelib_month_names_da_DK =
+ { array_elements(my_locale_month_names_da_DK)-1, "", my_locale_month_names_da_DK, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_da_DK =
+ { array_elements(my_locale_ab_month_names_da_DK)-1, "", my_locale_ab_month_names_da_DK, NULL };
+static TYPELIB my_locale_typelib_day_names_da_DK =
+ { array_elements(my_locale_day_names_da_DK)-1, "", my_locale_day_names_da_DK, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_da_DK =
+ { array_elements(my_locale_ab_day_names_da_DK)-1, "", my_locale_ab_day_names_da_DK, NULL };
+MY_LOCALE my_locale_da_DK
+(
+ 15,
+ "da_DK",
+ "Danish - Denmark",
+ FALSE,
+ &my_locale_typelib_month_names_da_DK,
+ &my_locale_typelib_ab_month_names_da_DK,
+ &my_locale_typelib_day_names_da_DK,
+ &my_locale_typelib_ab_day_names_da_DK,
+ 9,
+ 7,
+ ',', /* decimal point da_DK */
+ '.', /* thousands_sep da_DK */
+ "\x03\x03", /* grouping da_DK */
+ &global_errmsgs[da_DK]
+);
+/***** LOCALE END da_DK *****/
+
+/***** LOCALE BEGIN de_AT: German - Austria *****/
+static const char *my_locale_month_names_de_AT[13] =
+ {"Jänner","Feber","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember", NullS };
+static const char *my_locale_ab_month_names_de_AT[13] =
+ {"Jän","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez", NullS };
+static const char *my_locale_day_names_de_AT[8] =
+ {"Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag", NullS };
+static const char *my_locale_ab_day_names_de_AT[8] =
+ {"Mon","Die","Mit","Don","Fre","Sam","Son", NullS };
+static TYPELIB my_locale_typelib_month_names_de_AT =
+ { array_elements(my_locale_month_names_de_AT)-1, "", my_locale_month_names_de_AT, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_de_AT =
+ { array_elements(my_locale_ab_month_names_de_AT)-1, "", my_locale_ab_month_names_de_AT, NULL };
+static TYPELIB my_locale_typelib_day_names_de_AT =
+ { array_elements(my_locale_day_names_de_AT)-1, "", my_locale_day_names_de_AT, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_de_AT =
+ { array_elements(my_locale_ab_day_names_de_AT)-1, "", my_locale_ab_day_names_de_AT, NULL };
+MY_LOCALE my_locale_de_AT
+(
+ 16,
+ "de_AT",
+ "German - Austria",
+ FALSE,
+ &my_locale_typelib_month_names_de_AT,
+ &my_locale_typelib_ab_month_names_de_AT,
+ &my_locale_typelib_day_names_de_AT,
+ &my_locale_typelib_ab_day_names_de_AT,
+ 9,
+ 10,
+ ',', /* decimal point de_AT */
+ '\0', /* thousands_sep de_AT */
+ "\x80\x80", /* grouping de_AT */
+ &global_errmsgs[de_DE]
+);
+/***** LOCALE END de_AT *****/
+
+/***** LOCALE BEGIN de_DE: German - Germany *****/
+static const char *my_locale_month_names_de_DE[13] =
+ {"Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember", NullS };
+static const char *my_locale_ab_month_names_de_DE[13] =
+ {"Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez", NullS };
+static const char *my_locale_day_names_de_DE[8] =
+ {"Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag", NullS };
+static const char *my_locale_ab_day_names_de_DE[8] =
+ {"Mo","Di","Mi","Do","Fr","Sa","So", NullS };
+static TYPELIB my_locale_typelib_month_names_de_DE =
+ { array_elements(my_locale_month_names_de_DE)-1, "", my_locale_month_names_de_DE, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_de_DE =
+ { array_elements(my_locale_ab_month_names_de_DE)-1, "", my_locale_ab_month_names_de_DE, NULL };
+static TYPELIB my_locale_typelib_day_names_de_DE =
+ { array_elements(my_locale_day_names_de_DE)-1, "", my_locale_day_names_de_DE, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_de_DE =
+ { array_elements(my_locale_ab_day_names_de_DE)-1, "", my_locale_ab_day_names_de_DE, NULL };
+MY_LOCALE my_locale_de_DE
+(
+ 4,
+ "de_DE",
+ "German - Germany",
+ FALSE,
+ &my_locale_typelib_month_names_de_DE,
+ &my_locale_typelib_ab_month_names_de_DE,
+ &my_locale_typelib_day_names_de_DE,
+ &my_locale_typelib_ab_day_names_de_DE,
+ 9,
+ 10,
+ ',', /* decimal point de_DE */
+ '.', /* thousands_sep de_DE */
+ "\x03\x03", /* grouping de_DE */
+ &global_errmsgs[de_DE]
+);
+/***** LOCALE END de_DE *****/
+
+/***** LOCALE BEGIN en_US: English - United States *****/
+static const char *my_locale_month_names_en_US[13] =
+ {"January","February","March","April","May","June","July","August","September","October","November","December", NullS };
+static const char *my_locale_ab_month_names_en_US[13] =
+ {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec", NullS };
+static const char *my_locale_day_names_en_US[8] =
+ {"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday", NullS };
+static const char *my_locale_ab_day_names_en_US[8] =
+ {"Mon","Tue","Wed","Thu","Fri","Sat","Sun", NullS };
+static TYPELIB my_locale_typelib_month_names_en_US =
+ { array_elements(my_locale_month_names_en_US)-1, "", my_locale_month_names_en_US, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_en_US =
+ { array_elements(my_locale_ab_month_names_en_US)-1, "", my_locale_ab_month_names_en_US, NULL };
+static TYPELIB my_locale_typelib_day_names_en_US =
+ { array_elements(my_locale_day_names_en_US)-1, "", my_locale_day_names_en_US, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_en_US =
+ { array_elements(my_locale_ab_day_names_en_US)-1, "", my_locale_ab_day_names_en_US, NULL };
+MY_LOCALE my_locale_en_US
+(
+ 0,
+ "en_US",
+ "English - United States",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_US */
+ ',', /* thousands_sep en_US */
+ "\x03\x03", /* grouping en_US */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_US *****/
+
+/***** LOCALE BEGIN es_ES: Spanish - Spain *****/
+static const char *my_locale_month_names_es_ES[13] =
+ {"enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre", NullS };
+static const char *my_locale_ab_month_names_es_ES[13] =
+ {"ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic", NullS };
+static const char *my_locale_day_names_es_ES[8] =
+ {"lunes","martes","miércoles","jueves","viernes","sábado","domingo", NullS };
+static const char *my_locale_ab_day_names_es_ES[8] =
+ {"lun","mar","mié","jue","vie","sáb","dom", NullS };
+static TYPELIB my_locale_typelib_month_names_es_ES =
+ { array_elements(my_locale_month_names_es_ES)-1, "", my_locale_month_names_es_ES, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_es_ES =
+ { array_elements(my_locale_ab_month_names_es_ES)-1, "", my_locale_ab_month_names_es_ES, NULL };
+static TYPELIB my_locale_typelib_day_names_es_ES =
+ { array_elements(my_locale_day_names_es_ES)-1, "", my_locale_day_names_es_ES, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_es_ES =
+ { array_elements(my_locale_ab_day_names_es_ES)-1, "", my_locale_ab_day_names_es_ES, NULL };
+MY_LOCALE my_locale_es_ES
+(
+ 17,
+ "es_ES",
+ "Spanish - Spain",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_ES */
+ '\0', /* thousands_sep es_ES */
+ "\x80\x80", /* grouping es_ES */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_ES *****/
+
+/***** LOCALE BEGIN et_EE: Estonian - Estonia *****/
+static const char *my_locale_month_names_et_EE[13] =
+ {"jaanuar","veebruar","märts","aprill","mai","juuni","juuli","august","september","oktoober","november","detsember", NullS };
+static const char *my_locale_ab_month_names_et_EE[13] =
+ {"jaan ","veebr","märts","apr ","mai ","juuni","juuli","aug ","sept ","okt ","nov ","dets ", NullS };
+static const char *my_locale_day_names_et_EE[8] =
+ {"esmaspäev","teisipäev","kolmapäev","neljapäev","reede","laupäev","pühapäev", NullS };
+static const char *my_locale_ab_day_names_et_EE[8] =
+ {"E","T","K","N","R","L","P", NullS };
+static TYPELIB my_locale_typelib_month_names_et_EE =
+ { array_elements(my_locale_month_names_et_EE)-1, "", my_locale_month_names_et_EE, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_et_EE =
+ { array_elements(my_locale_ab_month_names_et_EE)-1, "", my_locale_ab_month_names_et_EE, NULL };
+static TYPELIB my_locale_typelib_day_names_et_EE =
+ { array_elements(my_locale_day_names_et_EE)-1, "", my_locale_day_names_et_EE, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_et_EE =
+ { array_elements(my_locale_ab_day_names_et_EE)-1, "", my_locale_ab_day_names_et_EE, NULL };
+MY_LOCALE my_locale_et_EE
+(
+ 18,
+ "et_EE",
+ "Estonian - Estonia",
+ FALSE,
+ &my_locale_typelib_month_names_et_EE,
+ &my_locale_typelib_ab_month_names_et_EE,
+ &my_locale_typelib_day_names_et_EE,
+ &my_locale_typelib_ab_day_names_et_EE,
+ 9,
+ 9,
+ ',', /* decimal point et_EE */
+ ' ', /* thousands_sep et_EE */
+ "\x03\x03", /* grouping et_EE */
+ &global_errmsgs[et_EE]
+);
+/***** LOCALE END et_EE *****/
+
+/***** LOCALE BEGIN eu_ES: Basque - Basque *****/
+static const char *my_locale_month_names_eu_ES[13] =
+ {"urtarrila","otsaila","martxoa","apirila","maiatza","ekaina","uztaila","abuztua","iraila","urria","azaroa","abendua", NullS };
+static const char *my_locale_ab_month_names_eu_ES[13] =
+ {"urt","ots","mar","api","mai","eka","uzt","abu","ira","urr","aza","abe", NullS };
+static const char *my_locale_day_names_eu_ES[8] =
+ {"astelehena","asteartea","asteazkena","osteguna","ostirala","larunbata","igandea", NullS };
+static const char *my_locale_ab_day_names_eu_ES[8] =
+ {"al.","ar.","az.","og.","or.","lr.","ig.", NullS };
+static TYPELIB my_locale_typelib_month_names_eu_ES =
+ { array_elements(my_locale_month_names_eu_ES)-1, "", my_locale_month_names_eu_ES, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_eu_ES =
+ { array_elements(my_locale_ab_month_names_eu_ES)-1, "", my_locale_ab_month_names_eu_ES, NULL };
+static TYPELIB my_locale_typelib_day_names_eu_ES =
+ { array_elements(my_locale_day_names_eu_ES)-1, "", my_locale_day_names_eu_ES, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_eu_ES =
+ { array_elements(my_locale_ab_day_names_eu_ES)-1, "", my_locale_ab_day_names_eu_ES, NULL };
+MY_LOCALE my_locale_eu_ES
+(
+ 19,
+ "eu_ES",
+ "Basque - Basque",
+ TRUE,
+ &my_locale_typelib_month_names_eu_ES,
+ &my_locale_typelib_ab_month_names_eu_ES,
+ &my_locale_typelib_day_names_eu_ES,
+ &my_locale_typelib_ab_day_names_eu_ES,
+ 9,
+ 10,
+ ',', /* decimal point eu_ES */
+ '\0', /* thousands_sep eu_ES */
+ "\x80\x80", /* grouping eu_ES */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END eu_ES *****/
+
+/***** LOCALE BEGIN fi_FI: Finnish - Finland *****/
+static const char *my_locale_month_names_fi_FI[13] =
+ {"tammikuu","helmikuu","maaliskuu","huhtikuu","toukokuu","kesäkuu","heinäkuu","elokuu","syyskuu","lokakuu","marraskuu","joulukuu", NullS };
+static const char *my_locale_ab_month_names_fi_FI[13] =
+ {"tammi ","helmi ","maalis","huhti ","touko ","kesä  ","heinä ","elo   ","syys  ","loka  ","marras","joulu ", NullS };
+static const char *my_locale_day_names_fi_FI[8] =
+ {"maanantai","tiistai","keskiviikko","torstai","perjantai","lauantai","sunnuntai", NullS };
+static const char *my_locale_ab_day_names_fi_FI[8] =
+ {"ma","ti","ke","to","pe","la","su", NullS };
+static TYPELIB my_locale_typelib_month_names_fi_FI =
+ { array_elements(my_locale_month_names_fi_FI)-1, "", my_locale_month_names_fi_FI, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_fi_FI =
+ { array_elements(my_locale_ab_month_names_fi_FI)-1, "", my_locale_ab_month_names_fi_FI, NULL };
+static TYPELIB my_locale_typelib_day_names_fi_FI =
+ { array_elements(my_locale_day_names_fi_FI)-1, "", my_locale_day_names_fi_FI, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_fi_FI =
+ { array_elements(my_locale_ab_day_names_fi_FI)-1, "", my_locale_ab_day_names_fi_FI, NULL };
+MY_LOCALE my_locale_fi_FI
+(
+ 20,
+ "fi_FI",
+ "Finnish - Finland",
+ FALSE,
+ &my_locale_typelib_month_names_fi_FI,
+ &my_locale_typelib_ab_month_names_fi_FI,
+ &my_locale_typelib_day_names_fi_FI,
+ &my_locale_typelib_ab_day_names_fi_FI,
+ 9,
+ 11,
+ ',', /* decimal point fi_FI */
+ ' ', /* thousands_sep fi_FI */
+ "\x03\x03", /* grouping fi_FI */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END fi_FI *****/
+
+/***** LOCALE BEGIN fo_FO: Faroese - Faroe Islands *****/
+static const char *my_locale_month_names_fo_FO[13] =
+ {"januar","februar","mars","apríl","mai","juni","juli","august","september","oktober","november","desember", NullS };
+static const char *my_locale_ab_month_names_fo_FO[13] =
+ {"jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des", NullS };
+static const char *my_locale_day_names_fo_FO[8] =
+ {"mánadagur","týsdagur","mikudagur","hósdagur","fríggjadagur","leygardagur","sunnudagur", NullS };
+static const char *my_locale_ab_day_names_fo_FO[8] =
+ {"mán","týs","mik","hós","frí","ley","sun", NullS };
+static TYPELIB my_locale_typelib_month_names_fo_FO =
+ { array_elements(my_locale_month_names_fo_FO)-1, "", my_locale_month_names_fo_FO, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_fo_FO =
+ { array_elements(my_locale_ab_month_names_fo_FO)-1, "", my_locale_ab_month_names_fo_FO, NULL };
+static TYPELIB my_locale_typelib_day_names_fo_FO =
+ { array_elements(my_locale_day_names_fo_FO)-1, "", my_locale_day_names_fo_FO, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_fo_FO =
+ { array_elements(my_locale_ab_day_names_fo_FO)-1, "", my_locale_ab_day_names_fo_FO, NULL };
+MY_LOCALE my_locale_fo_FO
+(
+ 21,
+ "fo_FO",
+ "Faroese - Faroe Islands",
+ FALSE,
+ &my_locale_typelib_month_names_fo_FO,
+ &my_locale_typelib_ab_month_names_fo_FO,
+ &my_locale_typelib_day_names_fo_FO,
+ &my_locale_typelib_ab_day_names_fo_FO,
+ 9,
+ 12,
+ ',', /* decimal point fo_FO */
+ '.', /* thousands_sep fo_FO */
+ "\x03\x03", /* grouping fo_FO */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END fo_FO *****/
+
+/***** LOCALE BEGIN fr_FR: French - France *****/
+static const char *my_locale_month_names_fr_FR[13] =
+ {"janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre", NullS };
+static const char *my_locale_ab_month_names_fr_FR[13] =
+ {"jan","fév","mar","avr","mai","jun","jui","aoû","sep","oct","nov","déc", NullS };
+static const char *my_locale_day_names_fr_FR[8] =
+ {"lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche", NullS };
+static const char *my_locale_ab_day_names_fr_FR[8] =
+ {"lun","mar","mer","jeu","ven","sam","dim", NullS };
+static TYPELIB my_locale_typelib_month_names_fr_FR =
+ { array_elements(my_locale_month_names_fr_FR)-1, "", my_locale_month_names_fr_FR, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_fr_FR =
+ { array_elements(my_locale_ab_month_names_fr_FR)-1, "", my_locale_ab_month_names_fr_FR, NULL };
+static TYPELIB my_locale_typelib_day_names_fr_FR =
+ { array_elements(my_locale_day_names_fr_FR)-1, "", my_locale_day_names_fr_FR, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_fr_FR =
+ { array_elements(my_locale_ab_day_names_fr_FR)-1, "", my_locale_ab_day_names_fr_FR, NULL };
+MY_LOCALE my_locale_fr_FR
+(
+ 5,
+ "fr_FR",
+ "French - France",
+ FALSE,
+ &my_locale_typelib_month_names_fr_FR,
+ &my_locale_typelib_ab_month_names_fr_FR,
+ &my_locale_typelib_day_names_fr_FR,
+ &my_locale_typelib_ab_day_names_fr_FR,
+ 9,
+ 8,
+ ',', /* decimal point fr_FR */
+ '\0', /* thousands_sep fr_FR */
+ "\x80\x80", /* grouping fr_FR */
+ &global_errmsgs[fr_FR]
+);
+/***** LOCALE END fr_FR *****/
+
+/***** LOCALE BEGIN gl_ES: Galician - Galician *****/
+static const char *my_locale_month_names_gl_ES[13] =
+ {"Xaneiro","Febreiro","Marzo","Abril","Maio","Xuño","Xullo","Agosto","Setembro","Outubro","Novembro","Decembro", NullS };
+static const char *my_locale_ab_month_names_gl_ES[13] =
+ {"Xan","Feb","Mar","Abr","Mai","Xuñ","Xul","Ago","Set","Out","Nov","Dec", NullS };
+static const char *my_locale_day_names_gl_ES[8] =
+ {"Luns","Martes","Mércores","Xoves","Venres","Sábado","Domingo", NullS };
+static const char *my_locale_ab_day_names_gl_ES[8] =
+ {"Lun","Mar","Mér","Xov","Ven","Sáb","Dom", NullS };
+static TYPELIB my_locale_typelib_month_names_gl_ES =
+ { array_elements(my_locale_month_names_gl_ES)-1, "", my_locale_month_names_gl_ES, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_gl_ES =
+ { array_elements(my_locale_ab_month_names_gl_ES)-1, "", my_locale_ab_month_names_gl_ES, NULL };
+static TYPELIB my_locale_typelib_day_names_gl_ES =
+ { array_elements(my_locale_day_names_gl_ES)-1, "", my_locale_day_names_gl_ES, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_gl_ES =
+ { array_elements(my_locale_ab_day_names_gl_ES)-1, "", my_locale_ab_day_names_gl_ES, NULL };
+MY_LOCALE my_locale_gl_ES
+(
+ 22,
+ "gl_ES",
+ "Galician - Galician",
+ FALSE,
+ &my_locale_typelib_month_names_gl_ES,
+ &my_locale_typelib_ab_month_names_gl_ES,
+ &my_locale_typelib_day_names_gl_ES,
+ &my_locale_typelib_ab_day_names_gl_ES,
+ 8,
+ 8,
+ ',', /* decimal point gl_ES */
+ '\0', /* thousands_sep gl_ES */
+ "\x80\x80", /* grouping gl_ES */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END gl_ES *****/
+
+/***** LOCALE BEGIN gu_IN: Gujarati - India *****/
+static const char *my_locale_month_names_gu_IN[13] =
+ {"જાનà«àª¯à«àª†àª°à«€","ફેબà«àª°à«àª†àª°à«€","મારà«àªš","àªàªªà«àª°àª¿àª²","મે","જà«àª¨","જà«àª²àª¾àª‡","ઓગસà«àªŸ","સેપà«àªŸà«‡àª®à«àª¬àª°","ઓકà«àªŸà«‹àª¬àª°","નવેમà«àª¬àª°","ડિસેમà«àª¬àª°", NullS };
+static const char *my_locale_ab_month_names_gu_IN[13] =
+ {"જાન","ફેબ","માર","àªàªªà«àª°","મે","જà«àª¨","જà«àª²","ઓગ","સેપà«àªŸ","ઓકà«àªŸ","નોવ","ડિસ", NullS };
+static const char *my_locale_day_names_gu_IN[8] =
+ {"સોમવાર","મનà«àª—ળવાર","બà«àª§àªµàª¾àª°","ગà«àª°à«àªµàª¾àª°","શà«àª•à«àª°àªµàª¾àª°","શનિવાર","રવિવાર", NullS };
+static const char *my_locale_ab_day_names_gu_IN[8] =
+ {"સોમ","મનà«àª—ળ","બà«àª§","ગà«àª°à«","શà«àª•à«àª°","શનિ","રવિ", NullS };
+static TYPELIB my_locale_typelib_month_names_gu_IN =
+ { array_elements(my_locale_month_names_gu_IN)-1, "", my_locale_month_names_gu_IN, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_gu_IN =
+ { array_elements(my_locale_ab_month_names_gu_IN)-1, "", my_locale_ab_month_names_gu_IN, NULL };
+static TYPELIB my_locale_typelib_day_names_gu_IN =
+ { array_elements(my_locale_day_names_gu_IN)-1, "", my_locale_day_names_gu_IN, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_gu_IN =
+ { array_elements(my_locale_ab_day_names_gu_IN)-1, "", my_locale_ab_day_names_gu_IN, NULL };
+MY_LOCALE my_locale_gu_IN
+(
+ 23,
+ "gu_IN",
+ "Gujarati - India",
+ FALSE,
+ &my_locale_typelib_month_names_gu_IN,
+ &my_locale_typelib_ab_month_names_gu_IN,
+ &my_locale_typelib_day_names_gu_IN,
+ &my_locale_typelib_ab_day_names_gu_IN,
+ 10,
+ 8,
+ '.', /* decimal point gu_IN */
+ ',', /* thousands_sep gu_IN */
+ "\x03", /* grouping gu_IN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END gu_IN *****/
+
+/***** LOCALE BEGIN he_IL: Hebrew - Israel *****/
+static const char *my_locale_month_names_he_IL[13] =
+ {"ינו×ר","פברו×ר","מרץ","×פריל","מ××™","יוני","יולי","×וגוסט","ספטמבר","×וקטובר","נובמבר","דצמבר", NullS };
+static const char *my_locale_ab_month_names_he_IL[13] =
+ {"ינו","פבר","מרץ","×פר","מ××™","יונ","יול","×וג","ספט","×וק","נוב","דצמ", NullS };
+static const char *my_locale_day_names_he_IL[8] =
+ {"שני","שלישי","רביעי","חמישי","שישי","שבת","ר×שון", NullS };
+static const char *my_locale_ab_day_names_he_IL[8] =
+ {"ב'","×’'","ד'","×”'","ו'","ש'","×'", NullS };
+static TYPELIB my_locale_typelib_month_names_he_IL =
+ { array_elements(my_locale_month_names_he_IL)-1, "", my_locale_month_names_he_IL, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_he_IL =
+ { array_elements(my_locale_ab_month_names_he_IL)-1, "", my_locale_ab_month_names_he_IL, NULL };
+static TYPELIB my_locale_typelib_day_names_he_IL =
+ { array_elements(my_locale_day_names_he_IL)-1, "", my_locale_day_names_he_IL, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_he_IL =
+ { array_elements(my_locale_ab_day_names_he_IL)-1, "", my_locale_ab_day_names_he_IL, NULL };
+MY_LOCALE my_locale_he_IL
+(
+ 24,
+ "he_IL",
+ "Hebrew - Israel",
+ FALSE,
+ &my_locale_typelib_month_names_he_IL,
+ &my_locale_typelib_ab_month_names_he_IL,
+ &my_locale_typelib_day_names_he_IL,
+ &my_locale_typelib_ab_day_names_he_IL,
+ 7,
+ 5,
+ '.', /* decimal point he_IL */
+ ',', /* thousands_sep he_IL */
+ "\x03\x03", /* grouping he_IL */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END he_IL *****/
+
+/***** LOCALE BEGIN hi_IN: Hindi - India *****/
+static const char *my_locale_month_names_hi_IN[13] =
+ {"जनवरी","फ़रवरी","मारà¥à¤š","अपà¥à¤°à¥‡à¤²","मई","जून","जà¥à¤²à¤¾à¤ˆ","अगसà¥à¤¤","सितमà¥à¤¬à¤°","अकà¥à¤Ÿà¥‚बर","नवमà¥à¤¬à¤°","दिसमà¥à¤¬à¤°", NullS };
+static const char *my_locale_ab_month_names_hi_IN[13] =
+ {"जनवरी","फ़रवरी","मारà¥à¤š","अपà¥à¤°à¥‡à¤²","मई","जून","जà¥à¤²à¤¾à¤ˆ","अगसà¥à¤¤","सितमà¥à¤¬à¤°","अकà¥à¤Ÿà¥‚बर","नवमà¥à¤¬à¤°","दिसमà¥à¤¬à¤°", NullS };
+static const char *my_locale_day_names_hi_IN[8] =
+ {"सोमवार ","मंगलवार ","बà¥à¤§à¤µà¤¾à¤° ","गà¥à¤°à¥à¤µà¤¾à¤° ","शà¥à¤•à¥à¤°à¤µà¤¾à¤° ","शनिवार ","रविवार ", NullS };
+static const char *my_locale_ab_day_names_hi_IN[8] =
+ {"सोम ","मंगल ","बà¥à¤§ ","गà¥à¤°à¥ ","शà¥à¤•à¥à¤° ","शनि ","रवि ", NullS };
+static TYPELIB my_locale_typelib_month_names_hi_IN =
+ { array_elements(my_locale_month_names_hi_IN)-1, "", my_locale_month_names_hi_IN, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_hi_IN =
+ { array_elements(my_locale_ab_month_names_hi_IN)-1, "", my_locale_ab_month_names_hi_IN, NULL };
+static TYPELIB my_locale_typelib_day_names_hi_IN =
+ { array_elements(my_locale_day_names_hi_IN)-1, "", my_locale_day_names_hi_IN, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_hi_IN =
+ { array_elements(my_locale_ab_day_names_hi_IN)-1, "", my_locale_ab_day_names_hi_IN, NULL };
+MY_LOCALE my_locale_hi_IN
+(
+ 25,
+ "hi_IN",
+ "Hindi - India",
+ FALSE,
+ &my_locale_typelib_month_names_hi_IN,
+ &my_locale_typelib_ab_month_names_hi_IN,
+ &my_locale_typelib_day_names_hi_IN,
+ &my_locale_typelib_ab_day_names_hi_IN,
+ 7,
+ 9,
+ '.', /* decimal point hi_IN */
+ ',', /* thousands_sep hi_IN */
+ "\x03", /* grouping hi_IN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END hi_IN *****/
+
+/***** LOCALE BEGIN hr_HR: Croatian - Croatia *****/
+static const char *my_locale_month_names_hr_HR[13] =
+ {"SijeÄanj","VeljaÄa","Ožujak","Travanj","Svibanj","Lipanj","Srpanj","Kolovoz","Rujan","Listopad","Studeni","Prosinac", NullS };
+static const char *my_locale_ab_month_names_hr_HR[13] =
+ {"Sij","Vel","Ožu","Tra","Svi","Lip","Srp","Kol","Ruj","Lis","Stu","Pro", NullS };
+static const char *my_locale_day_names_hr_HR[8] =
+ {"Ponedjeljak","Utorak","Srijeda","ÄŒetvrtak","Petak","Subota","Nedjelja", NullS };
+static const char *my_locale_ab_day_names_hr_HR[8] =
+ {"Pon","Uto","Sri","ÄŒet","Pet","Sub","Ned", NullS };
+static TYPELIB my_locale_typelib_month_names_hr_HR =
+ { array_elements(my_locale_month_names_hr_HR)-1, "", my_locale_month_names_hr_HR, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_hr_HR =
+ { array_elements(my_locale_ab_month_names_hr_HR)-1, "", my_locale_ab_month_names_hr_HR, NULL };
+static TYPELIB my_locale_typelib_day_names_hr_HR =
+ { array_elements(my_locale_day_names_hr_HR)-1, "", my_locale_day_names_hr_HR, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_hr_HR =
+ { array_elements(my_locale_ab_day_names_hr_HR)-1, "", my_locale_ab_day_names_hr_HR, NULL };
+MY_LOCALE my_locale_hr_HR
+(
+ 26,
+ "hr_HR",
+ "Croatian - Croatia",
+ FALSE,
+ &my_locale_typelib_month_names_hr_HR,
+ &my_locale_typelib_ab_month_names_hr_HR,
+ &my_locale_typelib_day_names_hr_HR,
+ &my_locale_typelib_ab_day_names_hr_HR,
+ 8,
+ 11,
+ ',', /* decimal point hr_HR */
+ '\0', /* thousands_sep hr_HR */
+ "\x80\x80", /* grouping hr_HR */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END hr_HR *****/
+
+/***** LOCALE BEGIN hu_HU: Hungarian - Hungary *****/
+static const char *my_locale_month_names_hu_HU[13] =
+ {"január","február","március","április","május","június","július","augusztus","szeptember","október","november","december", NullS };
+static const char *my_locale_ab_month_names_hu_HU[13] =
+ {"jan","feb","már","ápr","máj","jún","júl","aug","sze","okt","nov","dec", NullS };
+static const char *my_locale_day_names_hu_HU[8] =
+ {"hétfő","kedd","szerda","csütörtök","péntek","szombat","vasárnap", NullS };
+static const char *my_locale_ab_day_names_hu_HU[8] =
+ {"h","k","sze","cs","p","szo","v", NullS };
+static TYPELIB my_locale_typelib_month_names_hu_HU =
+ { array_elements(my_locale_month_names_hu_HU)-1, "", my_locale_month_names_hu_HU, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_hu_HU =
+ { array_elements(my_locale_ab_month_names_hu_HU)-1, "", my_locale_ab_month_names_hu_HU, NULL };
+static TYPELIB my_locale_typelib_day_names_hu_HU =
+ { array_elements(my_locale_day_names_hu_HU)-1, "", my_locale_day_names_hu_HU, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_hu_HU =
+ { array_elements(my_locale_ab_day_names_hu_HU)-1, "", my_locale_ab_day_names_hu_HU, NULL };
+MY_LOCALE my_locale_hu_HU
+(
+ 27,
+ "hu_HU",
+ "Hungarian - Hungary",
+ FALSE,
+ &my_locale_typelib_month_names_hu_HU,
+ &my_locale_typelib_ab_month_names_hu_HU,
+ &my_locale_typelib_day_names_hu_HU,
+ &my_locale_typelib_ab_day_names_hu_HU,
+ 10,
+ 9,
+ ',', /* decimal point hu_HU */
+ '.', /* thousands_sep hu_HU */
+ "\x03\x03", /* grouping hu_HU */
+ &global_errmsgs[hu_HU]
+);
+/***** LOCALE END hu_HU *****/
+
+/***** LOCALE BEGIN id_ID: Indonesian - Indonesia *****/
+static const char *my_locale_month_names_id_ID[13] =
+ {"Januari","Pebruari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember", NullS };
+static const char *my_locale_ab_month_names_id_ID[13] =
+ {"Jan","Peb","Mar","Apr","Mei","Jun","Jul","Agu","Sep","Okt","Nov","Des", NullS };
+static const char *my_locale_day_names_id_ID[8] =
+ {"Senin","Selasa","Rabu","Kamis","Jumat","Sabtu","Minggu", NullS };
+static const char *my_locale_ab_day_names_id_ID[8] =
+ {"Sen","Sel","Rab","Kam","Jum","Sab","Min", NullS };
+static TYPELIB my_locale_typelib_month_names_id_ID =
+ { array_elements(my_locale_month_names_id_ID)-1, "", my_locale_month_names_id_ID, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_id_ID =
+ { array_elements(my_locale_ab_month_names_id_ID)-1, "", my_locale_ab_month_names_id_ID, NULL };
+static TYPELIB my_locale_typelib_day_names_id_ID =
+ { array_elements(my_locale_day_names_id_ID)-1, "", my_locale_day_names_id_ID, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_id_ID =
+ { array_elements(my_locale_ab_day_names_id_ID)-1, "", my_locale_ab_day_names_id_ID, NULL };
+MY_LOCALE my_locale_id_ID
+(
+ 28,
+ "id_ID",
+ "Indonesian - Indonesia",
+ TRUE,
+ &my_locale_typelib_month_names_id_ID,
+ &my_locale_typelib_ab_month_names_id_ID,
+ &my_locale_typelib_day_names_id_ID,
+ &my_locale_typelib_ab_day_names_id_ID,
+ 9,
+ 6,
+ ',', /* decimal point id_ID */
+ '.', /* thousands_sep id_ID */
+ "\x03\x03", /* grouping id_ID */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END id_ID *****/
+
+/***** LOCALE BEGIN is_IS: Icelandic - Iceland *****/
+static const char *my_locale_month_names_is_IS[13] =
+ {"janúar","febrúar","mars","apríl","maí","júní","júlí","ágúst","september","október","nóvember","desember", NullS };
+static const char *my_locale_ab_month_names_is_IS[13] =
+ {"jan","feb","mar","apr","maí","jún","júl","ágú","sep","okt","nóv","des", NullS };
+static const char *my_locale_day_names_is_IS[8] =
+ {"mánudagur","þriðjudagur","miðvikudagur","fimmtudagur","föstudagur","laugardagur","sunnudagur", NullS };
+static const char *my_locale_ab_day_names_is_IS[8] =
+ {"mán","þri","mið","fim","fös","lau","sun", NullS };
+static TYPELIB my_locale_typelib_month_names_is_IS =
+ { array_elements(my_locale_month_names_is_IS)-1, "", my_locale_month_names_is_IS, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_is_IS =
+ { array_elements(my_locale_ab_month_names_is_IS)-1, "", my_locale_ab_month_names_is_IS, NULL };
+static TYPELIB my_locale_typelib_day_names_is_IS =
+ { array_elements(my_locale_day_names_is_IS)-1, "", my_locale_day_names_is_IS, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_is_IS =
+ { array_elements(my_locale_ab_day_names_is_IS)-1, "", my_locale_ab_day_names_is_IS, NULL };
+MY_LOCALE my_locale_is_IS
+(
+ 29,
+ "is_IS",
+ "Icelandic - Iceland",
+ FALSE,
+ &my_locale_typelib_month_names_is_IS,
+ &my_locale_typelib_ab_month_names_is_IS,
+ &my_locale_typelib_day_names_is_IS,
+ &my_locale_typelib_ab_day_names_is_IS,
+ 9,
+ 12,
+ ',', /* decimal point is_IS */
+ '.', /* thousands_sep is_IS */
+ "\x03\x03", /* grouping is_IS */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END is_IS *****/
+
+/***** LOCALE BEGIN it_CH: Italian - Switzerland *****/
+static const char *my_locale_month_names_it_CH[13] =
+ {"gennaio","febbraio","marzo","aprile","maggio","giugno","luglio","agosto","settembre","ottobre","novembre","dicembre", NullS };
+static const char *my_locale_ab_month_names_it_CH[13] =
+ {"gen","feb","mar","apr","mag","giu","lug","ago","set","ott","nov","dic", NullS };
+static const char *my_locale_day_names_it_CH[8] =
+ {"lunedì","martedì","mercoledì","giovedì","venerdì","sabato","domenica", NullS };
+static const char *my_locale_ab_day_names_it_CH[8] =
+ {"lun","mar","mer","gio","ven","sab","dom", NullS };
+static TYPELIB my_locale_typelib_month_names_it_CH =
+ { array_elements(my_locale_month_names_it_CH)-1, "", my_locale_month_names_it_CH, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_it_CH =
+ { array_elements(my_locale_ab_month_names_it_CH)-1, "", my_locale_ab_month_names_it_CH, NULL };
+static TYPELIB my_locale_typelib_day_names_it_CH =
+ { array_elements(my_locale_day_names_it_CH)-1, "", my_locale_day_names_it_CH, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_it_CH =
+ { array_elements(my_locale_ab_day_names_it_CH)-1, "", my_locale_ab_day_names_it_CH, NULL };
+MY_LOCALE my_locale_it_CH
+(
+ 30,
+ "it_CH",
+ "Italian - Switzerland",
+ FALSE,
+ &my_locale_typelib_month_names_it_CH,
+ &my_locale_typelib_ab_month_names_it_CH,
+ &my_locale_typelib_day_names_it_CH,
+ &my_locale_typelib_ab_day_names_it_CH,
+ 9,
+ 9,
+ ',', /* decimal point it_CH */
+ '\'', /* thousands_sep it_CH */
+ "\x03\x03", /* grouping it_CH */
+ &global_errmsgs[it_IT]
+);
+/***** LOCALE END it_CH *****/
+
+/***** LOCALE BEGIN ja_JP: Japanese - Japan *****/
+static const char *my_locale_month_names_ja_JP[13] =
+ {"1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月", NullS };
+static const char *my_locale_ab_month_names_ja_JP[13] =
+ {" 1月"," 2月"," 3月"," 4月"," 5月"," 6月"," 7月"," 8月"," 9月","10月","11月","12月", NullS };
+static const char *my_locale_day_names_ja_JP[8] =
+ {"月曜日","ç«æ›œæ—¥","水曜日","木曜日","金曜日","土曜日","日曜日", NullS };
+static const char *my_locale_ab_day_names_ja_JP[8] =
+ {"月","ç«","æ°´","木","金","土","æ—¥", NullS };
+static TYPELIB my_locale_typelib_month_names_ja_JP =
+ { array_elements(my_locale_month_names_ja_JP)-1, "", my_locale_month_names_ja_JP, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ja_JP =
+ { array_elements(my_locale_ab_month_names_ja_JP)-1, "", my_locale_ab_month_names_ja_JP, NULL };
+static TYPELIB my_locale_typelib_day_names_ja_JP =
+ { array_elements(my_locale_day_names_ja_JP)-1, "", my_locale_day_names_ja_JP, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ja_JP =
+ { array_elements(my_locale_ab_day_names_ja_JP)-1, "", my_locale_ab_day_names_ja_JP, NULL };
+MY_LOCALE my_locale_ja_JP
+(
+ 2,
+ "ja_JP",
+ "Japanese - Japan",
+ FALSE,
+ &my_locale_typelib_month_names_ja_JP,
+ &my_locale_typelib_ab_month_names_ja_JP,
+ &my_locale_typelib_day_names_ja_JP,
+ &my_locale_typelib_ab_day_names_ja_JP,
+ 3,
+ 3,
+ '.', /* decimal point ja_JP */
+ ',', /* thousands_sep ja_JP */
+ "\x03", /* grouping ja_JP */
+ &global_errmsgs[ja_JP]
+);
+/***** LOCALE END ja_JP *****/
+
+/***** LOCALE BEGIN ko_KR: Korean - Korea *****/
+static const char *my_locale_month_names_ko_KR[13] =
+ {"ì¼ì›”","ì´ì›”","삼월","사월","오월","유월","ì¹ ì›”","팔월","구월","시월","ì‹­ì¼ì›”","ì‹­ì´ì›”", NullS };
+static const char *my_locale_ab_month_names_ko_KR[13] =
+ {" 1ì›”"," 2ì›”"," 3ì›”"," 4ì›”"," 5ì›”"," 6ì›”"," 7ì›”"," 8ì›”"," 9ì›”","10ì›”","11ì›”","12ì›”", NullS };
+static const char *my_locale_day_names_ko_KR[8] =
+ {"월요ì¼","화요ì¼","수요ì¼","목요ì¼","금요ì¼","토요ì¼","ì¼ìš”ì¼", NullS };
+static const char *my_locale_ab_day_names_ko_KR[8] =
+ {"ì›”","í™”","수","목","금","토","ì¼", NullS };
+static TYPELIB my_locale_typelib_month_names_ko_KR =
+ { array_elements(my_locale_month_names_ko_KR)-1, "", my_locale_month_names_ko_KR, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ko_KR =
+ { array_elements(my_locale_ab_month_names_ko_KR)-1, "", my_locale_ab_month_names_ko_KR, NULL };
+static TYPELIB my_locale_typelib_day_names_ko_KR =
+ { array_elements(my_locale_day_names_ko_KR)-1, "", my_locale_day_names_ko_KR, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ko_KR =
+ { array_elements(my_locale_ab_day_names_ko_KR)-1, "", my_locale_ab_day_names_ko_KR, NULL };
+MY_LOCALE my_locale_ko_KR
+(
+ 31,
+ "ko_KR",
+ "Korean - Korea",
+ FALSE,
+ &my_locale_typelib_month_names_ko_KR,
+ &my_locale_typelib_ab_month_names_ko_KR,
+ &my_locale_typelib_day_names_ko_KR,
+ &my_locale_typelib_ab_day_names_ko_KR,
+ 3,
+ 3,
+ '.', /* decimal point ko_KR */
+ ',', /* thousands_sep ko_KR */
+ "\x03\x03", /* grouping ko_KR */
+ &global_errmsgs[ko_KR]
+);
+/***** LOCALE END ko_KR *****/
+
+/***** LOCALE BEGIN lt_LT: Lithuanian - Lithuania *****/
+static const char *my_locale_month_names_lt_LT[13] =
+ {"sausio","vasario","kovo","balandžio","gegužės","birželio","liepos","rugpjÅ«Äio","rugsÄ—jo","spalio","lapkriÄio","gruodžio", NullS };
+static const char *my_locale_ab_month_names_lt_LT[13] =
+ {"Sau","Vas","Kov","Bal","Geg","Bir","Lie","Rgp","Rgs","Spa","Lap","Grd", NullS };
+static const char *my_locale_day_names_lt_LT[8] =
+ {"Pirmadienis","Antradienis","TreÄiadienis","Ketvirtadienis","Penktadienis","Å eÅ¡tadienis","Sekmadienis", NullS };
+static const char *my_locale_ab_day_names_lt_LT[8] =
+ {"Pr","An","Tr","Kt","Pn","Å t","Sk", NullS };
+static TYPELIB my_locale_typelib_month_names_lt_LT =
+ { array_elements(my_locale_month_names_lt_LT)-1, "", my_locale_month_names_lt_LT, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_lt_LT =
+ { array_elements(my_locale_ab_month_names_lt_LT)-1, "", my_locale_ab_month_names_lt_LT, NULL };
+static TYPELIB my_locale_typelib_day_names_lt_LT =
+ { array_elements(my_locale_day_names_lt_LT)-1, "", my_locale_day_names_lt_LT, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_lt_LT =
+ { array_elements(my_locale_ab_day_names_lt_LT)-1, "", my_locale_ab_day_names_lt_LT, NULL };
+MY_LOCALE my_locale_lt_LT
+(
+ 32,
+ "lt_LT",
+ "Lithuanian - Lithuania",
+ FALSE,
+ &my_locale_typelib_month_names_lt_LT,
+ &my_locale_typelib_ab_month_names_lt_LT,
+ &my_locale_typelib_day_names_lt_LT,
+ &my_locale_typelib_ab_day_names_lt_LT,
+ 9,
+ 14,
+ ',', /* decimal point lt_LT */
+ '.', /* thousands_sep lt_LT */
+ "\x03\x03", /* grouping lt_LT */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END lt_LT *****/
+
+/***** LOCALE BEGIN lv_LV: Latvian - Latvia *****/
+static const char *my_locale_month_names_lv_LV[13] =
+ {"janvÄris","februÄris","marts","aprÄ«lis","maijs","jÅ«nijs","jÅ«lijs","augusts","septembris","oktobris","novembris","decembris", NullS };
+static const char *my_locale_ab_month_names_lv_LV[13] =
+ {"jan","feb","mar","apr","mai","jūn","jūl","aug","sep","okt","nov","dec", NullS };
+static const char *my_locale_day_names_lv_LV[8] =
+ {"pirmdiena","otrdiena","trešdiena","ceturtdiena","piektdiena","sestdiena","svētdiena", NullS };
+static const char *my_locale_ab_day_names_lv_LV[8] =
+ {"P ","O ","T ","C ","Pk","S ","Sv", NullS };
+static TYPELIB my_locale_typelib_month_names_lv_LV =
+ { array_elements(my_locale_month_names_lv_LV)-1, "", my_locale_month_names_lv_LV, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_lv_LV =
+ { array_elements(my_locale_ab_month_names_lv_LV)-1, "", my_locale_ab_month_names_lv_LV, NULL };
+static TYPELIB my_locale_typelib_day_names_lv_LV =
+ { array_elements(my_locale_day_names_lv_LV)-1, "", my_locale_day_names_lv_LV, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_lv_LV =
+ { array_elements(my_locale_ab_day_names_lv_LV)-1, "", my_locale_ab_day_names_lv_LV, NULL };
+MY_LOCALE my_locale_lv_LV
+(
+ 33,
+ "lv_LV",
+ "Latvian - Latvia",
+ FALSE,
+ &my_locale_typelib_month_names_lv_LV,
+ &my_locale_typelib_ab_month_names_lv_LV,
+ &my_locale_typelib_day_names_lv_LV,
+ &my_locale_typelib_ab_day_names_lv_LV,
+ 10,
+ 11,
+ ',', /* decimal point lv_LV */
+ ' ', /* thousands_sep lv_LV */
+ "\x03\x03", /* grouping lv_LV */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END lv_LV *****/
+
+/***** LOCALE BEGIN mk_MK: Macedonian - FYROM *****/
+static const char *my_locale_month_names_mk_MK[13] =
+ {"јануари","февруари","март","април","мај","јуни","јули","авгуÑÑ‚","Ñептември","октомври","ноември","декември", NullS };
+static const char *my_locale_ab_month_names_mk_MK[13] =
+ {"јан","фев","мар","апр","мај","јун","јул","авг","Ñеп","окт","ное","дек", NullS };
+static const char *my_locale_day_names_mk_MK[8] =
+ {"понеделник","вторник","Ñреда","четврток","петок","Ñабота","недела", NullS };
+static const char *my_locale_ab_day_names_mk_MK[8] =
+ {"пон","вто","Ñре","чет","пет","Ñаб","нед", NullS };
+static TYPELIB my_locale_typelib_month_names_mk_MK =
+ { array_elements(my_locale_month_names_mk_MK)-1, "", my_locale_month_names_mk_MK, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_mk_MK =
+ { array_elements(my_locale_ab_month_names_mk_MK)-1, "", my_locale_ab_month_names_mk_MK, NULL };
+static TYPELIB my_locale_typelib_day_names_mk_MK =
+ { array_elements(my_locale_day_names_mk_MK)-1, "", my_locale_day_names_mk_MK, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_mk_MK =
+ { array_elements(my_locale_ab_day_names_mk_MK)-1, "", my_locale_ab_day_names_mk_MK, NULL };
+MY_LOCALE my_locale_mk_MK
+(
+ 34,
+ "mk_MK",
+ "Macedonian - FYROM",
+ FALSE,
+ &my_locale_typelib_month_names_mk_MK,
+ &my_locale_typelib_ab_month_names_mk_MK,
+ &my_locale_typelib_day_names_mk_MK,
+ &my_locale_typelib_ab_day_names_mk_MK,
+ 9,
+ 10,
+ ',', /* decimal point mk_MK */
+ ' ', /* thousands_sep mk_MK */
+ "\x03\x03", /* grouping mk_MK */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END mk_MK *****/
+
+/***** LOCALE BEGIN mn_MN: Mongolia - Mongolian *****/
+static const char *my_locale_month_names_mn_MN[13] =
+ {"ÐÑгдүгÑÑÑ€ Ñар","Хоёрдугаар Ñар","Гуравдугаар Ñар","ДөрөвдүгÑÑÑ€ Ñар","Тавдугаар Ñар","Зургаадугар Ñар","Долоодугаар Ñар","Ðаймдугаар Ñар","ЕÑдүгÑÑÑ€ Ñар","Ðравдугаар Ñар","ÐрваннÑгдүгÑÑÑ€ Ñар","Ðрванхоёрдгаар Ñар", NullS };
+static const char *my_locale_ab_month_names_mn_MN[13] =
+ {"1-Ñ€","2-Ñ€","3-Ñ€","4-Ñ€","5-Ñ€","6-Ñ€","7-Ñ€","8-Ñ€","9-Ñ€","10-Ñ€","11-Ñ€","12-Ñ€", NullS };
+static const char *my_locale_day_names_mn_MN[8] =
+ {"Даваа","МÑгмар","Лхагва","ПүрÑв","БааÑан","БÑмба","ÐÑм", NullS };
+static const char *my_locale_ab_day_names_mn_MN[8] =
+ {"Да","МÑ","Лх","Пү","Ба","БÑ","ÐÑ", NullS };
+static TYPELIB my_locale_typelib_month_names_mn_MN =
+ { array_elements(my_locale_month_names_mn_MN)-1, "", my_locale_month_names_mn_MN, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_mn_MN =
+ { array_elements(my_locale_ab_month_names_mn_MN)-1, "", my_locale_ab_month_names_mn_MN, NULL };
+static TYPELIB my_locale_typelib_day_names_mn_MN =
+ { array_elements(my_locale_day_names_mn_MN)-1, "", my_locale_day_names_mn_MN, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_mn_MN =
+ { array_elements(my_locale_ab_day_names_mn_MN)-1, "", my_locale_ab_day_names_mn_MN, NULL };
+MY_LOCALE my_locale_mn_MN
+(
+ 35,
+ "mn_MN",
+ "Mongolia - Mongolian",
+ FALSE,
+ &my_locale_typelib_month_names_mn_MN,
+ &my_locale_typelib_ab_month_names_mn_MN,
+ &my_locale_typelib_day_names_mn_MN,
+ &my_locale_typelib_ab_day_names_mn_MN,
+ 18,
+ 6,
+ ',', /* decimal point mn_MN */
+ '.', /* thousands_sep mn_MN */
+ "\x03\x03", /* grouping mn_MN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END mn_MN *****/
+
+/***** LOCALE BEGIN ms_MY: Malay - Malaysia *****/
+static const char *my_locale_month_names_ms_MY[13] =
+ {"Januari","Februari","Mac","April","Mei","Jun","Julai","Ogos","September","Oktober","November","Disember", NullS };
+static const char *my_locale_ab_month_names_ms_MY[13] =
+ {"Jan","Feb","Mac","Apr","Mei","Jun","Jul","Ogos","Sep","Okt","Nov","Dis", NullS };
+static const char *my_locale_day_names_ms_MY[8] =
+ {"Isnin","Selasa","Rabu","Khamis","Jumaat","Sabtu","Ahad", NullS };
+static const char *my_locale_ab_day_names_ms_MY[8] =
+ {"Isn","Sel","Rab","Kha","Jum","Sab","Ahd", NullS };
+static TYPELIB my_locale_typelib_month_names_ms_MY =
+ { array_elements(my_locale_month_names_ms_MY)-1, "", my_locale_month_names_ms_MY, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ms_MY =
+ { array_elements(my_locale_ab_month_names_ms_MY)-1, "", my_locale_ab_month_names_ms_MY, NULL };
+static TYPELIB my_locale_typelib_day_names_ms_MY =
+ { array_elements(my_locale_day_names_ms_MY)-1, "", my_locale_day_names_ms_MY, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ms_MY =
+ { array_elements(my_locale_ab_day_names_ms_MY)-1, "", my_locale_ab_day_names_ms_MY, NULL };
+MY_LOCALE my_locale_ms_MY
+(
+ 36,
+ "ms_MY",
+ "Malay - Malaysia",
+ TRUE,
+ &my_locale_typelib_month_names_ms_MY,
+ &my_locale_typelib_ab_month_names_ms_MY,
+ &my_locale_typelib_day_names_ms_MY,
+ &my_locale_typelib_ab_day_names_ms_MY,
+ 9,
+ 6,
+ '.', /* decimal point ms_MY */
+ ',', /* thousands_sep ms_MY */
+ "\x03", /* grouping ms_MY */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ms_MY *****/
+
+/***** LOCALE BEGIN nb_NO: Norwegian(Bokml) - Norway *****/
+static const char *my_locale_month_names_nb_NO[13] =
+ {"januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember", NullS };
+static const char *my_locale_ab_month_names_nb_NO[13] =
+ {"jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des", NullS };
+static const char *my_locale_day_names_nb_NO[8] =
+ {"mandag","tirsdag","onsdag","torsdag","fredag","lørdag","søndag", NullS };
+static const char *my_locale_ab_day_names_nb_NO[8] =
+ {"man","tir","ons","tor","fre","lør","søn", NullS };
+static TYPELIB my_locale_typelib_month_names_nb_NO =
+ { array_elements(my_locale_month_names_nb_NO)-1, "", my_locale_month_names_nb_NO, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_nb_NO =
+ { array_elements(my_locale_ab_month_names_nb_NO)-1, "", my_locale_ab_month_names_nb_NO, NULL };
+static TYPELIB my_locale_typelib_day_names_nb_NO =
+ { array_elements(my_locale_day_names_nb_NO)-1, "", my_locale_day_names_nb_NO, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_nb_NO =
+ { array_elements(my_locale_ab_day_names_nb_NO)-1, "", my_locale_ab_day_names_nb_NO, NULL };
+MY_LOCALE my_locale_nb_NO
+(
+ 37,
+ "nb_NO",
+ "Norwegian(Bokml) - Norway",
+ FALSE,
+ &my_locale_typelib_month_names_nb_NO,
+ &my_locale_typelib_ab_month_names_nb_NO,
+ &my_locale_typelib_day_names_nb_NO,
+ &my_locale_typelib_ab_day_names_nb_NO,
+ 9,
+ 7,
+ ',', /* decimal point nb_NO */
+ '.', /* thousands_sep nb_NO */
+ "\x03\x03", /* grouping nb_NO */
+ &global_errmsgs[no_NO]
+);
+/***** LOCALE END nb_NO *****/
+
+/***** LOCALE BEGIN nl_NL: Dutch - The Netherlands *****/
+static const char *my_locale_month_names_nl_NL[13] =
+ {"januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december", NullS };
+static const char *my_locale_ab_month_names_nl_NL[13] =
+ {"jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec", NullS };
+static const char *my_locale_day_names_nl_NL[8] =
+ {"maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag","zondag", NullS };
+static const char *my_locale_ab_day_names_nl_NL[8] =
+ {"ma","di","wo","do","vr","za","zo", NullS };
+static TYPELIB my_locale_typelib_month_names_nl_NL =
+ { array_elements(my_locale_month_names_nl_NL)-1, "", my_locale_month_names_nl_NL, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_nl_NL =
+ { array_elements(my_locale_ab_month_names_nl_NL)-1, "", my_locale_ab_month_names_nl_NL, NULL };
+static TYPELIB my_locale_typelib_day_names_nl_NL =
+ { array_elements(my_locale_day_names_nl_NL)-1, "", my_locale_day_names_nl_NL, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_nl_NL =
+ { array_elements(my_locale_ab_day_names_nl_NL)-1, "", my_locale_ab_day_names_nl_NL, NULL };
+MY_LOCALE my_locale_nl_NL
+(
+ 38,
+ "nl_NL",
+ "Dutch - The Netherlands",
+ TRUE,
+ &my_locale_typelib_month_names_nl_NL,
+ &my_locale_typelib_ab_month_names_nl_NL,
+ &my_locale_typelib_day_names_nl_NL,
+ &my_locale_typelib_ab_day_names_nl_NL,
+ 9,
+ 9,
+ ',', /* decimal point nl_NL */
+ '\0', /* thousands_sep nl_NL */
+ "\x80\x80", /* grouping nl_NL */
+ &global_errmsgs[nl_NL]
+);
+/***** LOCALE END nl_NL *****/
+
+/***** LOCALE BEGIN pl_PL: Polish - Poland *****/
+static const char *my_locale_month_names_pl_PL[13] =
+ {"styczeń","luty","marzec","kwiecień","maj","czerwiec","lipiec","sierpień","wrzesień","październik","listopad","grudzień", NullS };
+static const char *my_locale_ab_month_names_pl_PL[13] =
+ {"sty","lut","mar","kwi","maj","cze","lip","sie","wrz","paź","lis","gru", NullS };
+static const char *my_locale_day_names_pl_PL[8] =
+ {"poniedziałek","wtorek","środa","czwartek","piątek","sobota","niedziela", NullS };
+static const char *my_locale_ab_day_names_pl_PL[8] =
+ {"pon","wto","śro","czw","pią","sob","nie", NullS };
+static TYPELIB my_locale_typelib_month_names_pl_PL =
+ { array_elements(my_locale_month_names_pl_PL)-1, "", my_locale_month_names_pl_PL, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_pl_PL =
+ { array_elements(my_locale_ab_month_names_pl_PL)-1, "", my_locale_ab_month_names_pl_PL, NULL };
+static TYPELIB my_locale_typelib_day_names_pl_PL =
+ { array_elements(my_locale_day_names_pl_PL)-1, "", my_locale_day_names_pl_PL, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_pl_PL =
+ { array_elements(my_locale_ab_day_names_pl_PL)-1, "", my_locale_ab_day_names_pl_PL, NULL };
+MY_LOCALE my_locale_pl_PL
+(
+ 39,
+ "pl_PL",
+ "Polish - Poland",
+ FALSE,
+ &my_locale_typelib_month_names_pl_PL,
+ &my_locale_typelib_ab_month_names_pl_PL,
+ &my_locale_typelib_day_names_pl_PL,
+ &my_locale_typelib_ab_day_names_pl_PL,
+ 11,
+ 12,
+ ',', /* decimal point pl_PL */
+ '\0', /* thousands_sep pl_PL */
+ "\x80\x80", /* grouping pl_PL */
+ &global_errmsgs[pl_PL]
+);
+/***** LOCALE END pl_PL *****/
+
+/***** LOCALE BEGIN pt_BR: Portugese - Brazil *****/
+static const char *my_locale_month_names_pt_BR[13] =
+ {"janeiro","fevereiro","março","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro", NullS };
+static const char *my_locale_ab_month_names_pt_BR[13] =
+ {"Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez", NullS };
+static const char *my_locale_day_names_pt_BR[8] =
+ {"segunda","terça","quarta","quinta","sexta","sábado","domingo", NullS };
+static const char *my_locale_ab_day_names_pt_BR[8] =
+ {"Seg","Ter","Qua","Qui","Sex","Sáb","Dom", NullS };
+static TYPELIB my_locale_typelib_month_names_pt_BR =
+ { array_elements(my_locale_month_names_pt_BR)-1, "", my_locale_month_names_pt_BR, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_pt_BR =
+ { array_elements(my_locale_ab_month_names_pt_BR)-1, "", my_locale_ab_month_names_pt_BR, NULL };
+static TYPELIB my_locale_typelib_day_names_pt_BR =
+ { array_elements(my_locale_day_names_pt_BR)-1, "", my_locale_day_names_pt_BR, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_pt_BR =
+ { array_elements(my_locale_ab_day_names_pt_BR)-1, "", my_locale_ab_day_names_pt_BR, NULL };
+MY_LOCALE my_locale_pt_BR
+(
+ 40,
+ "pt_BR",
+ "Portugese - Brazil",
+ FALSE,
+ &my_locale_typelib_month_names_pt_BR,
+ &my_locale_typelib_ab_month_names_pt_BR,
+ &my_locale_typelib_day_names_pt_BR,
+ &my_locale_typelib_ab_day_names_pt_BR,
+ 9,
+ 7,
+ ',', /* decimal point pt_BR */
+ '\0', /* thousands_sep pt_BR */
+ "\x80\x80", /* grouping pt_BR */
+ &global_errmsgs[pt_PT]
+);
+/***** LOCALE END pt_BR *****/
+
+/***** LOCALE BEGIN pt_PT: Portugese - Portugal *****/
+static const char *my_locale_month_names_pt_PT[13] =
+ {"Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro", NullS };
+static const char *my_locale_ab_month_names_pt_PT[13] =
+ {"Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez", NullS };
+static const char *my_locale_day_names_pt_PT[8] =
+ {"Segunda","Terça","Quarta","Quinta","Sexta","Sábado","Domingo", NullS };
+static const char *my_locale_ab_day_names_pt_PT[8] =
+ {"Seg","Ter","Qua","Qui","Sex","Sáb","Dom", NullS };
+static TYPELIB my_locale_typelib_month_names_pt_PT =
+ { array_elements(my_locale_month_names_pt_PT)-1, "", my_locale_month_names_pt_PT, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_pt_PT =
+ { array_elements(my_locale_ab_month_names_pt_PT)-1, "", my_locale_ab_month_names_pt_PT, NULL };
+static TYPELIB my_locale_typelib_day_names_pt_PT =
+ { array_elements(my_locale_day_names_pt_PT)-1, "", my_locale_day_names_pt_PT, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_pt_PT =
+ { array_elements(my_locale_ab_day_names_pt_PT)-1, "", my_locale_ab_day_names_pt_PT, NULL };
+MY_LOCALE my_locale_pt_PT
+(
+ 41,
+ "pt_PT",
+ "Portugese - Portugal",
+ FALSE,
+ &my_locale_typelib_month_names_pt_PT,
+ &my_locale_typelib_ab_month_names_pt_PT,
+ &my_locale_typelib_day_names_pt_PT,
+ &my_locale_typelib_ab_day_names_pt_PT,
+ 9,
+ 7,
+ ',', /* decimal point pt_PT */
+ '\0', /* thousands_sep pt_PT */
+ "\x80\x80", /* grouping pt_PT */
+ &global_errmsgs[pt_PT]
+);
+/***** LOCALE END pt_PT *****/
+
+/***** LOCALE BEGIN ro_RO: Romanian - Romania *****/
+static const char *my_locale_month_names_ro_RO[13] =
+ {"Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie","Iulie","August","Septembrie","Octombrie","Noiembrie","Decembrie", NullS };
+static const char *my_locale_ab_month_names_ro_RO[13] =
+ {"ian","feb","mar","apr","mai","iun","iul","aug","sep","oct","nov","dec", NullS };
+static const char *my_locale_day_names_ro_RO[8] =
+ {"Luni","Marţi","Miercuri","Joi","Vineri","Sâmbătă","Duminică", NullS };
+static const char *my_locale_ab_day_names_ro_RO[8] =
+ {"Lu","Ma","Mi","Jo","Vi","Sâ","Du", NullS };
+static TYPELIB my_locale_typelib_month_names_ro_RO =
+ { array_elements(my_locale_month_names_ro_RO)-1, "", my_locale_month_names_ro_RO, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ro_RO =
+ { array_elements(my_locale_ab_month_names_ro_RO)-1, "", my_locale_ab_month_names_ro_RO, NULL };
+static TYPELIB my_locale_typelib_day_names_ro_RO =
+ { array_elements(my_locale_day_names_ro_RO)-1, "", my_locale_day_names_ro_RO, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ro_RO =
+ { array_elements(my_locale_ab_day_names_ro_RO)-1, "", my_locale_ab_day_names_ro_RO, NULL };
+MY_LOCALE my_locale_ro_RO
+(
+ 42,
+ "ro_RO",
+ "Romanian - Romania",
+ FALSE,
+ &my_locale_typelib_month_names_ro_RO,
+ &my_locale_typelib_ab_month_names_ro_RO,
+ &my_locale_typelib_day_names_ro_RO,
+ &my_locale_typelib_ab_day_names_ro_RO,
+ 10,
+ 8,
+ ',', /* decimal point ro_RO */
+ '.', /* thousands_sep ro_RO */
+ "\x03\x03", /* grouping ro_RO */
+ &global_errmsgs[ro_RO]
+);
+/***** LOCALE END ro_RO *****/
+
+/***** LOCALE BEGIN ru_RU: Russian - Russia *****/
+static const char *my_locale_month_names_ru_RU[13] =
+ {"ЯнварÑ","ФевралÑ","Марта","ÐпрелÑ","МаÑ","ИюнÑ","ИюлÑ","ÐвгуÑта","СентÑбрÑ","ОктÑбрÑ","ÐоÑбрÑ","ДекабрÑ", NullS };
+static const char *my_locale_ab_month_names_ru_RU[13] =
+ {"Янв","Фев","Мар","Ðпр","Май","Июн","Июл","Ðвг","Сен","Окт","ÐоÑ","Дек", NullS };
+static const char *my_locale_day_names_ru_RU[8] =
+ {"Понедельник","Вторник","Среда","Четверг","ПÑтница","Суббота","ВоÑкреÑенье", NullS };
+static const char *my_locale_ab_day_names_ru_RU[8] =
+ {"Пнд","Втр","Срд","Чтв","Птн","Сбт","Ð’Ñк", NullS };
+static TYPELIB my_locale_typelib_month_names_ru_RU =
+ { array_elements(my_locale_month_names_ru_RU)-1, "", my_locale_month_names_ru_RU, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ru_RU =
+ { array_elements(my_locale_ab_month_names_ru_RU)-1, "", my_locale_ab_month_names_ru_RU, NULL };
+static TYPELIB my_locale_typelib_day_names_ru_RU =
+ { array_elements(my_locale_day_names_ru_RU)-1, "", my_locale_day_names_ru_RU, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ru_RU =
+ { array_elements(my_locale_ab_day_names_ru_RU)-1, "", my_locale_ab_day_names_ru_RU, NULL };
+MY_LOCALE my_locale_ru_RU
+(
+ 43,
+ "ru_RU",
+ "Russian - Russia",
+ FALSE,
+ &my_locale_typelib_month_names_ru_RU,
+ &my_locale_typelib_ab_month_names_ru_RU,
+ &my_locale_typelib_day_names_ru_RU,
+ &my_locale_typelib_ab_day_names_ru_RU,
+ 8,
+ 11,
+ ',', /* decimal point ru_RU */
+ ' ', /* thousands_sep ru_RU */
+ "\x03\x03", /* grouping ru_RU */
+ &global_errmsgs[ru_RU]
+);
+/***** LOCALE END ru_RU *****/
+
+/***** LOCALE BEGIN ru_UA: Russian - Ukraine *****/
+static const char *my_locale_month_names_ru_UA[13] =
+ {"Январь","Февраль","Март","Ðпрель","Май","Июнь","Июль","ÐвгуÑÑ‚","СентÑбрь","ОктÑбрь","ÐоÑбрь","Декабрь", NullS };
+static const char *my_locale_ab_month_names_ru_UA[13] =
+ {"Янв","Фев","Мар","Ðпр","Май","Июн","Июл","Ðвг","Сен","Окт","ÐоÑ","Дек", NullS };
+static const char *my_locale_day_names_ru_UA[8] =
+ {"Понедельник","Вторник","Среда","Четверг","ПÑтница","Суббота","ВоÑкреÑенье", NullS };
+static const char *my_locale_ab_day_names_ru_UA[8] =
+ {"Пнд","Вто","Срд","Чтв","Птн","Суб","Ð’Ñк", NullS };
+static TYPELIB my_locale_typelib_month_names_ru_UA =
+ { array_elements(my_locale_month_names_ru_UA)-1, "", my_locale_month_names_ru_UA, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ru_UA =
+ { array_elements(my_locale_ab_month_names_ru_UA)-1, "", my_locale_ab_month_names_ru_UA, NULL };
+static TYPELIB my_locale_typelib_day_names_ru_UA =
+ { array_elements(my_locale_day_names_ru_UA)-1, "", my_locale_day_names_ru_UA, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ru_UA =
+ { array_elements(my_locale_ab_day_names_ru_UA)-1, "", my_locale_ab_day_names_ru_UA, NULL };
+MY_LOCALE my_locale_ru_UA
+(
+ 44,
+ "ru_UA",
+ "Russian - Ukraine",
+ FALSE,
+ &my_locale_typelib_month_names_ru_UA,
+ &my_locale_typelib_ab_month_names_ru_UA,
+ &my_locale_typelib_day_names_ru_UA,
+ &my_locale_typelib_ab_day_names_ru_UA,
+ 8,
+ 11,
+ ',', /* decimal point ru_UA */
+ '.', /* thousands_sep ru_UA */
+ "\x03\x03", /* grouping ru_UA */
+ &global_errmsgs[ru_RU]
+);
+/***** LOCALE END ru_UA *****/
+
+/***** LOCALE BEGIN sk_SK: Slovak - Slovakia *****/
+static const char *my_locale_month_names_sk_SK[13] =
+ {"január","február","marec","apríl","máj","jún","júl","august","september","október","november","december", NullS };
+static const char *my_locale_ab_month_names_sk_SK[13] =
+ {"jan","feb","mar","apr","máj","jún","júl","aug","sep","okt","nov","dec", NullS };
+static const char *my_locale_day_names_sk_SK[8] =
+ {"Pondelok","Utorok","Streda","Štvrtok","Piatok","Sobota","Nedeľa", NullS };
+static const char *my_locale_ab_day_names_sk_SK[8] =
+ {"Po","Ut","St","Å t","Pi","So","Ne", NullS };
+static TYPELIB my_locale_typelib_month_names_sk_SK =
+ { array_elements(my_locale_month_names_sk_SK)-1, "", my_locale_month_names_sk_SK, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_sk_SK =
+ { array_elements(my_locale_ab_month_names_sk_SK)-1, "", my_locale_ab_month_names_sk_SK, NULL };
+static TYPELIB my_locale_typelib_day_names_sk_SK =
+ { array_elements(my_locale_day_names_sk_SK)-1, "", my_locale_day_names_sk_SK, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_sk_SK =
+ { array_elements(my_locale_ab_day_names_sk_SK)-1, "", my_locale_ab_day_names_sk_SK, NULL };
+MY_LOCALE my_locale_sk_SK
+(
+ 45,
+ "sk_SK",
+ "Slovak - Slovakia",
+ FALSE,
+ &my_locale_typelib_month_names_sk_SK,
+ &my_locale_typelib_ab_month_names_sk_SK,
+ &my_locale_typelib_day_names_sk_SK,
+ &my_locale_typelib_ab_day_names_sk_SK,
+ 9,
+ 8,
+ ',', /* decimal point sk_SK */
+ ' ', /* thousands_sep sk_SK */
+ "\x03\x03", /* grouping sk_SK */
+ &global_errmsgs[sk_SK]
+);
+/***** LOCALE END sk_SK *****/
+
+/***** LOCALE BEGIN sl_SI: Slovenian - Slovenia *****/
+static const char *my_locale_month_names_sl_SI[13] =
+ {"januar","februar","marec","april","maj","junij","julij","avgust","september","oktober","november","december", NullS };
+static const char *my_locale_ab_month_names_sl_SI[13] =
+ {"jan","feb","mar","apr","maj","jun","jul","avg","sep","okt","nov","dec", NullS };
+static const char *my_locale_day_names_sl_SI[8] =
+ {"ponedeljek","torek","sreda","Äetrtek","petek","sobota","nedelja", NullS };
+static const char *my_locale_ab_day_names_sl_SI[8] =
+ {"pon","tor","sre","Äet","pet","sob","ned", NullS };
+static TYPELIB my_locale_typelib_month_names_sl_SI =
+ { array_elements(my_locale_month_names_sl_SI)-1, "", my_locale_month_names_sl_SI, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_sl_SI =
+ { array_elements(my_locale_ab_month_names_sl_SI)-1, "", my_locale_ab_month_names_sl_SI, NULL };
+static TYPELIB my_locale_typelib_day_names_sl_SI =
+ { array_elements(my_locale_day_names_sl_SI)-1, "", my_locale_day_names_sl_SI, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_sl_SI =
+ { array_elements(my_locale_ab_day_names_sl_SI)-1, "", my_locale_ab_day_names_sl_SI, NULL };
+MY_LOCALE my_locale_sl_SI
+(
+ 46,
+ "sl_SI",
+ "Slovenian - Slovenia",
+ FALSE,
+ &my_locale_typelib_month_names_sl_SI,
+ &my_locale_typelib_ab_month_names_sl_SI,
+ &my_locale_typelib_day_names_sl_SI,
+ &my_locale_typelib_ab_day_names_sl_SI,
+ 9,
+ 10,
+ ',', /* decimal point sl_SI */
+ ' ', /* thousands_sep sl_SI */
+ "\x80\x80", /* grouping sl_SI */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END sl_SI *****/
+
+/***** LOCALE BEGIN sq_AL: Albanian - Albania *****/
+static const char *my_locale_month_names_sq_AL[13] =
+ {"janar","shkurt","mars","prill","maj","qershor","korrik","gusht","shtator","tetor","nëntor","dhjetor", NullS };
+static const char *my_locale_ab_month_names_sq_AL[13] =
+ {"Jan","Shk","Mar","Pri","Maj","Qer","Kor","Gsh","Sht","Tet","Nën","Dhj", NullS };
+static const char *my_locale_day_names_sq_AL[8] =
+ {"e hënë ","e martë ","e mërkurë ","e enjte ","e premte ","e shtunë ","e diel ", NullS };
+static const char *my_locale_ab_day_names_sq_AL[8] =
+ {"Hën ","Mar ","Mër ","Enj ","Pre ","Sht ","Die ", NullS };
+static TYPELIB my_locale_typelib_month_names_sq_AL =
+ { array_elements(my_locale_month_names_sq_AL)-1, "", my_locale_month_names_sq_AL, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_sq_AL =
+ { array_elements(my_locale_ab_month_names_sq_AL)-1, "", my_locale_ab_month_names_sq_AL, NULL };
+static TYPELIB my_locale_typelib_day_names_sq_AL =
+ { array_elements(my_locale_day_names_sq_AL)-1, "", my_locale_day_names_sq_AL, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_sq_AL =
+ { array_elements(my_locale_ab_day_names_sq_AL)-1, "", my_locale_ab_day_names_sq_AL, NULL };
+MY_LOCALE my_locale_sq_AL
+(
+ 47,
+ "sq_AL",
+ "Albanian - Albania",
+ FALSE,
+ &my_locale_typelib_month_names_sq_AL,
+ &my_locale_typelib_ab_month_names_sq_AL,
+ &my_locale_typelib_day_names_sq_AL,
+ &my_locale_typelib_ab_day_names_sq_AL,
+ 7,
+ 10,
+ ',', /* decimal point sq_AL */
+ '.', /* thousands_sep sq_AL */
+ "\x03", /* grouping sq_AL */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END sq_AL *****/
+
+/***** LOCALE BEGIN sr_RS: Serbian - Serbia *****/
+static const char *my_locale_month_names_sr_RS[13] =
+ {"januar","februar","mart","april","maj","juni","juli","avgust","septembar","oktobar","novembar","decembar", NullS };
+static const char *my_locale_ab_month_names_sr_RS[13] =
+ {"jan","feb","mar","apr","maj","jun","jul","avg","sep","okt","nov","dec", NullS };
+static const char *my_locale_day_names_sr_RS[8] =
+ {"ponedeljak","utorak","sreda","Äetvrtak","petak","subota","nedelja", NullS };
+static const char *my_locale_ab_day_names_sr_RS[8] =
+ {"pon","uto","sre","Äet","pet","sub","ned", NullS };
+static TYPELIB my_locale_typelib_month_names_sr_RS =
+ { array_elements(my_locale_month_names_sr_RS)-1, "", my_locale_month_names_sr_RS, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_sr_RS =
+ { array_elements(my_locale_ab_month_names_sr_RS)-1, "", my_locale_ab_month_names_sr_RS, NULL };
+static TYPELIB my_locale_typelib_day_names_sr_RS =
+ { array_elements(my_locale_day_names_sr_RS)-1, "", my_locale_day_names_sr_RS, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_sr_RS =
+ { array_elements(my_locale_ab_day_names_sr_RS)-1, "", my_locale_ab_day_names_sr_RS, NULL };
+MY_LOCALE my_locale_sr_YU /* Deprecated, use sr_RS instead */
+(
+ 48,
+ "sr_YU",
+ "Serbian - Yugoslavia",
+ FALSE,
+ &my_locale_typelib_month_names_sr_RS,
+ &my_locale_typelib_ab_month_names_sr_RS,
+ &my_locale_typelib_day_names_sr_RS,
+ &my_locale_typelib_ab_day_names_sr_RS,
+ 9,
+ 10,
+ '.', /* decimal point sr_RS */
+ '\0', /* thousands_sep sr_RS */
+ "\x80", /* grouping sr_RS */
+ &global_errmsgs[sr_RS]
+);
+
+MY_LOCALE my_locale_sr_RS
+(
+ 48,
+ "sr_RS",
+ "Serbian - Serbia",
+ FALSE,
+ &my_locale_typelib_month_names_sr_RS,
+ &my_locale_typelib_ab_month_names_sr_RS,
+ &my_locale_typelib_day_names_sr_RS,
+ &my_locale_typelib_ab_day_names_sr_RS,
+ 9,
+ 10,
+ '.', /* decimal point sr_RS */
+ '\0', /* thousands_sep sr_RS */
+ "\x80", /* grouping sr_RS */
+ &global_errmsgs[sr_RS]
+);
+/***** LOCALE END sr_RS *****/
+
+/***** LOCALE BEGIN sv_SE: Swedish - Sweden *****/
+static const char *my_locale_month_names_sv_SE[13] =
+ {"januari","februari","mars","april","maj","juni","juli","augusti","september","oktober","november","december", NullS };
+static const char *my_locale_ab_month_names_sv_SE[13] =
+ {"jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec", NullS };
+static const char *my_locale_day_names_sv_SE[8] =
+ {"måndag","tisdag","onsdag","torsdag","fredag","lördag","söndag", NullS };
+static const char *my_locale_ab_day_names_sv_SE[8] =
+ {"mån","tis","ons","tor","fre","lör","sön", NullS };
+static TYPELIB my_locale_typelib_month_names_sv_SE =
+ { array_elements(my_locale_month_names_sv_SE)-1, "", my_locale_month_names_sv_SE, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_sv_SE =
+ { array_elements(my_locale_ab_month_names_sv_SE)-1, "", my_locale_ab_month_names_sv_SE, NULL };
+static TYPELIB my_locale_typelib_day_names_sv_SE =
+ { array_elements(my_locale_day_names_sv_SE)-1, "", my_locale_day_names_sv_SE, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_sv_SE =
+ { array_elements(my_locale_ab_day_names_sv_SE)-1, "", my_locale_ab_day_names_sv_SE, NULL };
+MY_LOCALE my_locale_sv_SE
+(
+ 3,
+ "sv_SE",
+ "Swedish - Sweden",
+ FALSE,
+ &my_locale_typelib_month_names_sv_SE,
+ &my_locale_typelib_ab_month_names_sv_SE,
+ &my_locale_typelib_day_names_sv_SE,
+ &my_locale_typelib_ab_day_names_sv_SE,
+ 9,
+ 7,
+ ',', /* decimal point sv_SE */
+ ' ', /* thousands_sep sv_SE */
+ "\x03\x03", /* grouping sv_SE */
+ &global_errmsgs[sv_SE]
+);
+/***** LOCALE END sv_SE *****/
+
+/***** LOCALE BEGIN ta_IN: Tamil - India *****/
+static const char *my_locale_month_names_ta_IN[13] =
+ {"ஜனவரி","பெபà¯à®°à®µà®°à®¿","மாரà¯à®šà¯","à®à®ªà¯à®°à®²à¯","மே","ஜூனà¯","ஜூலை","ஆகஸà¯à®Ÿà¯","செபà¯à®Ÿà®®à¯à®ªà®°à¯","அகà¯à®Ÿà¯‹à®ªà®°à¯","நவமà¯à®ªà®°à¯","டிசமà¯à®ªà®°à¯r", NullS };
+static const char *my_locale_ab_month_names_ta_IN[13] =
+ {"ஜனவரி","பெபà¯à®°à®µà®°à®¿","மாரà¯à®šà¯","à®à®ªà¯à®°à®²à¯","மே","ஜூனà¯","ஜூலை","ஆகஸà¯à®Ÿà¯","செபà¯à®Ÿà®®à¯à®ªà®°à¯","அகà¯à®Ÿà¯‹à®ªà®°à¯","நவமà¯à®ªà®°à¯","டிசமà¯à®ªà®°à¯r", NullS };
+static const char *my_locale_day_names_ta_IN[8] =
+ {"திஙà¯à®•ளà¯","செவà¯à®µà®¾à®¯à¯","பà¯à®¤à®©à¯","வியாழனà¯","வெளà¯à®³à®¿","சனி","ஞாயிறà¯", NullS };
+static const char *my_locale_ab_day_names_ta_IN[8] =
+ {"த","ச","ப","வ","வ","ச","ஞ", NullS };
+static TYPELIB my_locale_typelib_month_names_ta_IN =
+ { array_elements(my_locale_month_names_ta_IN)-1, "", my_locale_month_names_ta_IN, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ta_IN =
+ { array_elements(my_locale_ab_month_names_ta_IN)-1, "", my_locale_ab_month_names_ta_IN, NULL };
+static TYPELIB my_locale_typelib_day_names_ta_IN =
+ { array_elements(my_locale_day_names_ta_IN)-1, "", my_locale_day_names_ta_IN, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ta_IN =
+ { array_elements(my_locale_ab_day_names_ta_IN)-1, "", my_locale_ab_day_names_ta_IN, NULL };
+MY_LOCALE my_locale_ta_IN
+(
+ 49,
+ "ta_IN",
+ "Tamil - India",
+ FALSE,
+ &my_locale_typelib_month_names_ta_IN,
+ &my_locale_typelib_ab_month_names_ta_IN,
+ &my_locale_typelib_day_names_ta_IN,
+ &my_locale_typelib_ab_day_names_ta_IN,
+ 10,
+ 8,
+ '.', /* decimal point ta_IN */
+ ',', /* thousands_sep ta_IN */
+ "\x03\x02", /* grouping ta_IN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ta_IN *****/
+
+/***** LOCALE BEGIN te_IN: Telugu - India *****/
+static const char *my_locale_month_names_te_IN[13] =
+ {"జనవరి","à°«à°¿à°¬à±à°°à°µà°°à°¿","మారà±à°šà°¿","à°à°ªà±à°°à°¿à°²à±","మే","జూనà±","జూలై","ఆగసà±à°Ÿà±","సెపà±à°Ÿà±†à°‚బరà±","à°…à°•à±à°Ÿà±‹à°¬à°°à±","నవంబరà±","డిసెంబరà±", NullS };
+static const char *my_locale_ab_month_names_te_IN[13] =
+ {"జనవరి","à°«à°¿à°¬à±à°°à°µà°°à°¿","మారà±à°šà°¿","à°à°ªà±à°°à°¿à°²à±","మే","జూనà±","జూలై","ఆగసà±à°Ÿà±","సెపà±à°Ÿà±†à°‚బరà±","à°…à°•à±à°Ÿà±‹à°¬à°°à±","నవంబరà±","డిసెంబరà±", NullS };
+static const char *my_locale_day_names_te_IN[8] =
+ {"సోమవారం","మంగళవారం","à°¬à±à°§à°µà°¾à°°à°‚","à°—à±à°°à±à°µà°¾à°°à°‚","à°¶à±à°•à±à°°à°µà°¾à°°à°‚","శనివారం","ఆదివారం", NullS };
+static const char *my_locale_ab_day_names_te_IN[8] =
+ {"సోమ","మంగళ","à°¬à±à°§","à°—à±à°°à±","à°¶à±à°•à±à°°","శని","ఆది", NullS };
+static TYPELIB my_locale_typelib_month_names_te_IN =
+ { array_elements(my_locale_month_names_te_IN)-1, "", my_locale_month_names_te_IN, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_te_IN =
+ { array_elements(my_locale_ab_month_names_te_IN)-1, "", my_locale_ab_month_names_te_IN, NULL };
+static TYPELIB my_locale_typelib_day_names_te_IN =
+ { array_elements(my_locale_day_names_te_IN)-1, "", my_locale_day_names_te_IN, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_te_IN =
+ { array_elements(my_locale_ab_day_names_te_IN)-1, "", my_locale_ab_day_names_te_IN, NULL };
+MY_LOCALE my_locale_te_IN
+(
+ 50,
+ "te_IN",
+ "Telugu - India",
+ FALSE,
+ &my_locale_typelib_month_names_te_IN,
+ &my_locale_typelib_ab_month_names_te_IN,
+ &my_locale_typelib_day_names_te_IN,
+ &my_locale_typelib_ab_day_names_te_IN,
+ 10,
+ 9,
+ '.', /* decimal point te_IN */
+ ',', /* thousands_sep te_IN */
+ "\x03\x02", /* grouping te_IN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END te_IN *****/
+
+/***** LOCALE BEGIN th_TH: Thai - Thailand *****/
+static const char *my_locale_month_names_th_TH[13] =
+ {"มà¸à¸£à¸²à¸„ม","à¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","à¸à¸£à¸à¸Žà¸²à¸„ม","สิงหาคม","à¸à¸±à¸™à¸¢à¸²à¸¢à¸™","ตุลาคม","พฤศจิà¸à¸²à¸¢à¸™","ธันวาคม", NullS };
+static const char *my_locale_ab_month_names_th_TH[13] =
+ {"ม.ค.","à¸.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","à¸.ค.","ส.ค.","à¸.ย.","ต.ค.","พ.ย.","ธ.ค.", NullS };
+static const char *my_locale_day_names_th_TH[8] =
+ {"จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุà¸à¸£à¹Œ","เสาร์","อาทิตย์", NullS };
+static const char *my_locale_ab_day_names_th_TH[8] =
+ {"จ.","อ.","พ.","พฤ.","ศ.","ส.","อา.", NullS };
+static TYPELIB my_locale_typelib_month_names_th_TH =
+ { array_elements(my_locale_month_names_th_TH)-1, "", my_locale_month_names_th_TH, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_th_TH =
+ { array_elements(my_locale_ab_month_names_th_TH)-1, "", my_locale_ab_month_names_th_TH, NULL };
+static TYPELIB my_locale_typelib_day_names_th_TH =
+ { array_elements(my_locale_day_names_th_TH)-1, "", my_locale_day_names_th_TH, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_th_TH =
+ { array_elements(my_locale_ab_day_names_th_TH)-1, "", my_locale_ab_day_names_th_TH, NULL };
+MY_LOCALE my_locale_th_TH
+(
+ 51,
+ "th_TH",
+ "Thai - Thailand",
+ FALSE,
+ &my_locale_typelib_month_names_th_TH,
+ &my_locale_typelib_ab_month_names_th_TH,
+ &my_locale_typelib_day_names_th_TH,
+ &my_locale_typelib_ab_day_names_th_TH,
+ 10,
+ 8,
+ '.', /* decimal point th_TH */
+ ',', /* thousands_sep th_TH */
+ "\x03", /* grouping th_TH */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END th_TH *****/
+
+/***** LOCALE BEGIN tr_TR: Turkish - Turkey *****/
+static const char *my_locale_month_names_tr_TR[13] =
+ {"Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık", NullS };
+static const char *my_locale_ab_month_names_tr_TR[13] =
+ {"Oca","Åžub","Mar","Nis","May","Haz","Tem","AÄŸu","Eyl","Eki","Kas","Ara", NullS };
+static const char *my_locale_day_names_tr_TR[8] =
+ {"Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi","Pazar", NullS };
+static const char *my_locale_ab_day_names_tr_TR[8] =
+ {"Pzt","Sal","Çrş","Prş","Cum","Cts","Paz", NullS };
+static TYPELIB my_locale_typelib_month_names_tr_TR =
+ { array_elements(my_locale_month_names_tr_TR)-1, "", my_locale_month_names_tr_TR, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_tr_TR =
+ { array_elements(my_locale_ab_month_names_tr_TR)-1, "", my_locale_ab_month_names_tr_TR, NULL };
+static TYPELIB my_locale_typelib_day_names_tr_TR =
+ { array_elements(my_locale_day_names_tr_TR)-1, "", my_locale_day_names_tr_TR, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_tr_TR =
+ { array_elements(my_locale_ab_day_names_tr_TR)-1, "", my_locale_ab_day_names_tr_TR, NULL };
+MY_LOCALE my_locale_tr_TR
+(
+ 52,
+ "tr_TR",
+ "Turkish - Turkey",
+ FALSE,
+ &my_locale_typelib_month_names_tr_TR,
+ &my_locale_typelib_ab_month_names_tr_TR,
+ &my_locale_typelib_day_names_tr_TR,
+ &my_locale_typelib_ab_day_names_tr_TR,
+ 7,
+ 9,
+ ',', /* decimal point tr_TR */
+ '.', /* thousands_sep tr_TR */
+ "\x03\x03", /* grouping tr_TR */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END tr_TR *****/
+
+/***** LOCALE BEGIN uk_UA: Ukrainian - Ukraine *****/
+static const char *my_locale_month_names_uk_UA[13] =
+ {"Січень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","ВереÑень","Жовтень","ЛиÑтопад","Грудень", NullS };
+static const char *my_locale_ab_month_names_uk_UA[13] =
+ {"Січ","Лют","Бер","Кві","Тра","Чер","Лип","Сер","Вер","Жов","ЛиÑ","Гру", NullS };
+static const char *my_locale_day_names_uk_UA[8] =
+ {"Понеділок","Вівторок","Середа","Четвер","П'ÑтницÑ","Субота","ÐеділÑ", NullS };
+static const char *my_locale_ab_day_names_uk_UA[8] =
+ {"Пнд","Втр","Срд","Чтв","Птн","Сбт","Ðдл", NullS };
+static TYPELIB my_locale_typelib_month_names_uk_UA =
+ { array_elements(my_locale_month_names_uk_UA)-1, "", my_locale_month_names_uk_UA, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_uk_UA =
+ { array_elements(my_locale_ab_month_names_uk_UA)-1, "", my_locale_ab_month_names_uk_UA, NULL };
+static TYPELIB my_locale_typelib_day_names_uk_UA =
+ { array_elements(my_locale_day_names_uk_UA)-1, "", my_locale_day_names_uk_UA, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_uk_UA =
+ { array_elements(my_locale_ab_day_names_uk_UA)-1, "", my_locale_ab_day_names_uk_UA, NULL };
+MY_LOCALE my_locale_uk_UA
+(
+ 53,
+ "uk_UA",
+ "Ukrainian - Ukraine",
+ FALSE,
+ &my_locale_typelib_month_names_uk_UA,
+ &my_locale_typelib_ab_month_names_uk_UA,
+ &my_locale_typelib_day_names_uk_UA,
+ &my_locale_typelib_ab_day_names_uk_UA,
+ 8,
+ 9,
+ ',', /* decimal point uk_UA */
+ '.', /* thousands_sep uk_UA */
+ "\x03\x03", /* grouping uk_UA */
+ &global_errmsgs[uk_UA]
+);
+/***** LOCALE END uk_UA *****/
+
+/***** LOCALE BEGIN ur_PK: Urdu - Pakistan *****/
+static const char *my_locale_month_names_ur_PK[13] =
+ {"جنوري","ÙØ±ÙˆØ±ÙŠ","مارچ","اپريل","مٓی","جون","جولاي","اگست","ستمبر","اكتوبر","نومبر","دسمبر", NullS };
+static const char *my_locale_ab_month_names_ur_PK[13] =
+ {"جنوري","ÙØ±ÙˆØ±ÙŠ","مارچ","اپريل","مٓی","جون","جولاي","اگست","ستمبر","اكتوبر","نومبر","دسمبر", NullS };
+static const char *my_locale_day_names_ur_PK[8] =
+ {"پير","منگل","بدھ","جمعرات","جمعه","Ù‡ÙØªÙ‡","اتوار", NullS };
+static const char *my_locale_ab_day_names_ur_PK[8] =
+ {"پير","منگل","بدھ","جمعرات","جمعه","Ù‡ÙØªÙ‡","اتوار", NullS };
+static TYPELIB my_locale_typelib_month_names_ur_PK =
+ { array_elements(my_locale_month_names_ur_PK)-1, "", my_locale_month_names_ur_PK, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_ur_PK =
+ { array_elements(my_locale_ab_month_names_ur_PK)-1, "", my_locale_ab_month_names_ur_PK, NULL };
+static TYPELIB my_locale_typelib_day_names_ur_PK =
+ { array_elements(my_locale_day_names_ur_PK)-1, "", my_locale_day_names_ur_PK, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_ur_PK =
+ { array_elements(my_locale_ab_day_names_ur_PK)-1, "", my_locale_ab_day_names_ur_PK, NULL };
+MY_LOCALE my_locale_ur_PK
+(
+ 54,
+ "ur_PK",
+ "Urdu - Pakistan",
+ FALSE,
+ &my_locale_typelib_month_names_ur_PK,
+ &my_locale_typelib_ab_month_names_ur_PK,
+ &my_locale_typelib_day_names_ur_PK,
+ &my_locale_typelib_ab_day_names_ur_PK,
+ 6,
+ 6,
+ '.', /* decimal point ur_PK */
+ ',', /* thousands_sep ur_PK */
+ "\x03\x03", /* grouping ur_PK */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ur_PK *****/
+
+/***** LOCALE BEGIN vi_VN: Vietnamese - Vietnam *****/
+static const char *my_locale_month_names_vi_VN[13] =
+ {"Tháng một","Tháng hai","Tháng ba","Tháng tư","Tháng năm","Tháng sáu","Tháng bảy","Tháng tám","Tháng chín","Tháng mười","Tháng mười một","Tháng mười hai", NullS };
+static const char *my_locale_ab_month_names_vi_VN[13] =
+ {"Thg 1","Thg 2","Thg 3","Thg 4","Thg 5","Thg 6","Thg 7","Thg 8","Thg 9","Thg 10","Thg 11","Thg 12", NullS };
+static const char *my_locale_day_names_vi_VN[8] =
+ {"ThÆ°Ì hai ","ThÆ°Ì ba ","ThÆ°Ì tư ","ThÆ°Ì năm ","ThÆ°Ì sáu ","ThÆ°Ì bảy ","Chủ nhật ", NullS };
+static const char *my_locale_ab_day_names_vi_VN[8] =
+ {"Th 2 ","Th 3 ","Th 4 ","Th 5 ","Th 6 ","Th 7 ","CN ", NullS };
+static TYPELIB my_locale_typelib_month_names_vi_VN =
+ { array_elements(my_locale_month_names_vi_VN)-1, "", my_locale_month_names_vi_VN, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_vi_VN =
+ { array_elements(my_locale_ab_month_names_vi_VN)-1, "", my_locale_ab_month_names_vi_VN, NULL };
+static TYPELIB my_locale_typelib_day_names_vi_VN =
+ { array_elements(my_locale_day_names_vi_VN)-1, "", my_locale_day_names_vi_VN, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_vi_VN =
+ { array_elements(my_locale_ab_day_names_vi_VN)-1, "", my_locale_ab_day_names_vi_VN, NULL };
+MY_LOCALE my_locale_vi_VN
+(
+ 55,
+ "vi_VN",
+ "Vietnamese - Vietnam",
+ FALSE,
+ &my_locale_typelib_month_names_vi_VN,
+ &my_locale_typelib_ab_month_names_vi_VN,
+ &my_locale_typelib_day_names_vi_VN,
+ &my_locale_typelib_ab_day_names_vi_VN,
+ 16,
+ 11,
+ ',', /* decimal point vi_VN */
+ '.', /* thousands_sep vi_VN */
+ "\x03\x03", /* grouping vi_VN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END vi_VN *****/
+
+/***** LOCALE BEGIN zh_CN: Chinese - Peoples Republic of China *****/
+static const char *my_locale_month_names_zh_CN[13] =
+ {"一月","二月","三月","四月","五月","六月","七月","八月","乿œˆ","åæœˆ","å一月","å二月", NullS };
+static const char *my_locale_ab_month_names_zh_CN[13] =
+ {" 1月"," 2月"," 3月"," 4月"," 5月"," 6月"," 7月"," 8月"," 9月","10月","11月","12月", NullS };
+static const char *my_locale_day_names_zh_CN[8] =
+ {"星期一","星期二","星期三","星期四","星期五","星期六","星期日", NullS };
+static const char *my_locale_ab_day_names_zh_CN[8] =
+ {"一","二","三","四","五","六","日", NullS };
+static TYPELIB my_locale_typelib_month_names_zh_CN =
+ { array_elements(my_locale_month_names_zh_CN)-1, "", my_locale_month_names_zh_CN, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_zh_CN =
+ { array_elements(my_locale_ab_month_names_zh_CN)-1, "", my_locale_ab_month_names_zh_CN, NULL };
+static TYPELIB my_locale_typelib_day_names_zh_CN =
+ { array_elements(my_locale_day_names_zh_CN)-1, "", my_locale_day_names_zh_CN, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_zh_CN =
+ { array_elements(my_locale_ab_day_names_zh_CN)-1, "", my_locale_ab_day_names_zh_CN, NULL };
+MY_LOCALE my_locale_zh_CN
+(
+ 56,
+ "zh_CN",
+ "Chinese - Peoples Republic of China",
+ FALSE,
+ &my_locale_typelib_month_names_zh_CN,
+ &my_locale_typelib_ab_month_names_zh_CN,
+ &my_locale_typelib_day_names_zh_CN,
+ &my_locale_typelib_ab_day_names_zh_CN,
+ 3,
+ 3,
+ '.', /* decimal point zh_CN */
+ ',', /* thousands_sep zh_CN */
+ "\x03", /* grouping zh_CN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END zh_CN *****/
+
+/***** LOCALE BEGIN zh_TW: Chinese - Taiwan *****/
+static const char *my_locale_month_names_zh_TW[13] =
+ {"一月","二月","三月","四月","五月","六月","七月","八月","乿œˆ","åæœˆ","å一月","å二月", NullS };
+static const char *my_locale_ab_month_names_zh_TW[13] =
+ {" 1月"," 2月"," 3月"," 4月"," 5月"," 6月"," 7月"," 8月"," 9月","10月","11月","12月", NullS };
+static const char *my_locale_day_names_zh_TW[8] =
+ {"週一","週二","週三","週四","週五","週六","週日", NullS };
+static const char *my_locale_ab_day_names_zh_TW[8] =
+ {"一","二","三","四","五","六","日", NullS };
+static TYPELIB my_locale_typelib_month_names_zh_TW =
+ { array_elements(my_locale_month_names_zh_TW)-1, "", my_locale_month_names_zh_TW, NULL };
+static TYPELIB my_locale_typelib_ab_month_names_zh_TW =
+ { array_elements(my_locale_ab_month_names_zh_TW)-1, "", my_locale_ab_month_names_zh_TW, NULL };
+static TYPELIB my_locale_typelib_day_names_zh_TW =
+ { array_elements(my_locale_day_names_zh_TW)-1, "", my_locale_day_names_zh_TW, NULL };
+static TYPELIB my_locale_typelib_ab_day_names_zh_TW =
+ { array_elements(my_locale_ab_day_names_zh_TW)-1, "", my_locale_ab_day_names_zh_TW, NULL };
+MY_LOCALE my_locale_zh_TW
+(
+ 57,
+ "zh_TW",
+ "Chinese - Taiwan",
+ FALSE,
+ &my_locale_typelib_month_names_zh_TW,
+ &my_locale_typelib_ab_month_names_zh_TW,
+ &my_locale_typelib_day_names_zh_TW,
+ &my_locale_typelib_ab_day_names_zh_TW,
+ 3,
+ 2,
+ '.', /* decimal point zh_TW */
+ ',', /* thousands_sep zh_TW */
+ "\x03", /* grouping zh_TW */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END zh_TW *****/
+
+/***** LOCALE BEGIN ar_DZ: Arabic - Algeria *****/
+MY_LOCALE my_locale_ar_DZ
+(
+ 58,
+ "ar_DZ",
+ "Arabic - Algeria",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_DZ */
+ ',', /* thousands_sep ar_DZ */
+ "\x03", /* grouping ar_DZ */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_DZ *****/
+
+/***** LOCALE BEGIN ar_EG: Arabic - Egypt *****/
+MY_LOCALE my_locale_ar_EG
+(
+ 59,
+ "ar_EG",
+ "Arabic - Egypt",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_EG */
+ ',', /* thousands_sep ar_EG */
+ "\x03", /* grouping ar_EG */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_EG *****/
+
+/***** LOCALE BEGIN ar_IN: Arabic - Iran *****/
+MY_LOCALE my_locale_ar_IN
+(
+ 60,
+ "ar_IN",
+ "Arabic - Iran",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_IN */
+ ',', /* thousands_sep ar_IN */
+ "\x03", /* grouping ar_IN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_IN *****/
+
+/***** LOCALE BEGIN ar_IQ: Arabic - Iraq *****/
+MY_LOCALE my_locale_ar_IQ
+(
+ 61,
+ "ar_IQ",
+ "Arabic - Iraq",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_IQ */
+ ',', /* thousands_sep ar_IQ */
+ "\x03", /* grouping ar_IQ */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_IQ *****/
+
+/***** LOCALE BEGIN ar_KW: Arabic - Kuwait *****/
+MY_LOCALE my_locale_ar_KW
+(
+ 62,
+ "ar_KW",
+ "Arabic - Kuwait",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_KW */
+ ',', /* thousands_sep ar_KW */
+ "\x03", /* grouping ar_KW */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_KW *****/
+
+/***** LOCALE BEGIN ar_LB: Arabic - Lebanon *****/
+MY_LOCALE my_locale_ar_LB
+(
+ 63,
+ "ar_LB",
+ "Arabic - Lebanon",
+ FALSE,
+ &my_locale_typelib_month_names_ar_JO,
+ &my_locale_typelib_ab_month_names_ar_JO,
+ &my_locale_typelib_day_names_ar_JO,
+ &my_locale_typelib_ab_day_names_ar_JO,
+ 12,
+ 8,
+ '.', /* decimal point ar_LB */
+ ',', /* thousands_sep ar_LB */
+ "\x03", /* grouping ar_LB */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_LB *****/
+
+/***** LOCALE BEGIN ar_LY: Arabic - Libya *****/
+MY_LOCALE my_locale_ar_LY
+(
+ 64,
+ "ar_LY",
+ "Arabic - Libya",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_LY */
+ ',', /* thousands_sep ar_LY */
+ "\x03", /* grouping ar_LY */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_LY *****/
+
+/***** LOCALE BEGIN ar_MA: Arabic - Morocco *****/
+MY_LOCALE my_locale_ar_MA
+(
+ 65,
+ "ar_MA",
+ "Arabic - Morocco",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_MA */
+ ',', /* thousands_sep ar_MA */
+ "\x03", /* grouping ar_MA */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_MA *****/
+
+/***** LOCALE BEGIN ar_OM: Arabic - Oman *****/
+MY_LOCALE my_locale_ar_OM
+(
+ 66,
+ "ar_OM",
+ "Arabic - Oman",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_OM */
+ ',', /* thousands_sep ar_OM */
+ "\x03", /* grouping ar_OM */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_OM *****/
+
+/***** LOCALE BEGIN ar_QA: Arabic - Qatar *****/
+MY_LOCALE my_locale_ar_QA
+(
+ 67,
+ "ar_QA",
+ "Arabic - Qatar",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_QA */
+ ',', /* thousands_sep ar_QA */
+ "\x03", /* grouping ar_QA */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_QA *****/
+
+/***** LOCALE BEGIN ar_SD: Arabic - Sudan *****/
+MY_LOCALE my_locale_ar_SD
+(
+ 68,
+ "ar_SD",
+ "Arabic - Sudan",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_SD */
+ ',', /* thousands_sep ar_SD */
+ "\x03", /* grouping ar_SD */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_SD *****/
+
+/***** LOCALE BEGIN ar_TN: Arabic - Tunisia *****/
+MY_LOCALE my_locale_ar_TN
+(
+ 69,
+ "ar_TN",
+ "Arabic - Tunisia",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_TN */
+ ',', /* thousands_sep ar_TN */
+ "\x03", /* grouping ar_TN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_TN *****/
+
+/***** LOCALE BEGIN ar_YE: Arabic - Yemen *****/
+MY_LOCALE my_locale_ar_YE
+(
+ 70,
+ "ar_YE",
+ "Arabic - Yemen",
+ FALSE,
+ &my_locale_typelib_month_names_ar_BH,
+ &my_locale_typelib_ab_month_names_ar_BH,
+ &my_locale_typelib_day_names_ar_BH,
+ &my_locale_typelib_ab_day_names_ar_BH,
+ 6,
+ 8,
+ '.', /* decimal point ar_YE */
+ ',', /* thousands_sep ar_YE */
+ "\x03", /* grouping ar_YE */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END ar_YE *****/
+
+/***** LOCALE BEGIN de_BE: German - Belgium *****/
+MY_LOCALE my_locale_de_BE
+(
+ 71,
+ "de_BE",
+ "German - Belgium",
+ FALSE,
+ &my_locale_typelib_month_names_de_DE,
+ &my_locale_typelib_ab_month_names_de_DE,
+ &my_locale_typelib_day_names_de_DE,
+ &my_locale_typelib_ab_day_names_de_DE,
+ 9,
+ 10,
+ ',', /* decimal point de_BE */
+ '.', /* thousands_sep de_BE */
+ "\x03\x03", /* grouping de_BE */
+ &global_errmsgs[de_DE]
+);
+/***** LOCALE END de_BE *****/
+
+/***** LOCALE BEGIN de_CH: German - Switzerland *****/
+MY_LOCALE my_locale_de_CH
+(
+ 72,
+ "de_CH",
+ "German - Switzerland",
+ FALSE,
+ &my_locale_typelib_month_names_de_DE,
+ &my_locale_typelib_ab_month_names_de_DE,
+ &my_locale_typelib_day_names_de_DE,
+ &my_locale_typelib_ab_day_names_de_DE,
+ 9,
+ 10,
+ '.', /* decimal point de_CH */
+ '\'', /* thousands_sep de_CH */
+ "\x03\x03", /* grouping de_CH */
+ &global_errmsgs[de_DE]
+);
+/***** LOCALE END de_CH *****/
+
+/***** LOCALE BEGIN de_LU: German - Luxembourg *****/
+MY_LOCALE my_locale_de_LU
+(
+ 73,
+ "de_LU",
+ "German - Luxembourg",
+ FALSE,
+ &my_locale_typelib_month_names_de_DE,
+ &my_locale_typelib_ab_month_names_de_DE,
+ &my_locale_typelib_day_names_de_DE,
+ &my_locale_typelib_ab_day_names_de_DE,
+ 9,
+ 10,
+ ',', /* decimal point de_LU */
+ '.', /* thousands_sep de_LU */
+ "\x03\x03", /* grouping de_LU */
+ &global_errmsgs[de_DE]
+);
+/***** LOCALE END de_LU *****/
+
+/***** LOCALE BEGIN en_AU: English - Australia *****/
+MY_LOCALE my_locale_en_AU
+(
+ 74,
+ "en_AU",
+ "English - Australia",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_AU */
+ ',', /* thousands_sep en_AU */
+ "\x03\x03", /* grouping en_AU */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_AU *****/
+
+/***** LOCALE BEGIN en_CA: English - Canada *****/
+MY_LOCALE my_locale_en_CA
+(
+ 75,
+ "en_CA",
+ "English - Canada",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_CA */
+ ',', /* thousands_sep en_CA */
+ "\x03\x03", /* grouping en_CA */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_CA *****/
+
+/***** LOCALE BEGIN en_GB: English - United Kingdom *****/
+MY_LOCALE my_locale_en_GB
+(
+ 1,
+ "en_GB",
+ "English - United Kingdom",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_GB */
+ ',', /* thousands_sep en_GB */
+ "\x03\x03", /* grouping en_GB */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_GB *****/
+
+/***** LOCALE BEGIN en_IN: English - India *****/
+MY_LOCALE my_locale_en_IN
+(
+ 76,
+ "en_IN",
+ "English - India",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_IN */
+ ',', /* thousands_sep en_IN */
+ "\x03\x02", /* grouping en_IN */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_IN *****/
+
+/***** LOCALE BEGIN en_NZ: English - New Zealand *****/
+MY_LOCALE my_locale_en_NZ
+(
+ 77,
+ "en_NZ",
+ "English - New Zealand",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_NZ */
+ ',', /* thousands_sep en_NZ */
+ "\x03\x03", /* grouping en_NZ */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_NZ *****/
+
+/***** LOCALE BEGIN en_PH: English - Philippines *****/
+MY_LOCALE my_locale_en_PH
+(
+ 78,
+ "en_PH",
+ "English - Philippines",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_PH */
+ ',', /* thousands_sep en_PH */
+ "\x03", /* grouping en_PH */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_PH *****/
+
+/***** LOCALE BEGIN en_ZA: English - South Africa *****/
+MY_LOCALE my_locale_en_ZA
+(
+ 79,
+ "en_ZA",
+ "English - South Africa",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_ZA */
+ ',', /* thousands_sep en_ZA */
+ "\x03\x03", /* grouping en_ZA */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_ZA *****/
+
+/***** LOCALE BEGIN en_ZW: English - Zimbabwe *****/
+MY_LOCALE my_locale_en_ZW
+(
+ 80,
+ "en_ZW",
+ "English - Zimbabwe",
+ TRUE,
+ &my_locale_typelib_month_names_en_US,
+ &my_locale_typelib_ab_month_names_en_US,
+ &my_locale_typelib_day_names_en_US,
+ &my_locale_typelib_ab_day_names_en_US,
+ 9,
+ 9,
+ '.', /* decimal point en_ZW */
+ ',', /* thousands_sep en_ZW */
+ "\x03\x03", /* grouping en_ZW */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END en_ZW *****/
+
+/***** LOCALE BEGIN es_AR: Spanish - Argentina *****/
+MY_LOCALE my_locale_es_AR
+(
+ 81,
+ "es_AR",
+ "Spanish - Argentina",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_AR */
+ '.', /* thousands_sep es_AR */
+ "\x03\x03", /* grouping es_AR */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_AR *****/
+
+/***** LOCALE BEGIN es_BO: Spanish - Bolivia *****/
+MY_LOCALE my_locale_es_BO
+(
+ 82,
+ "es_BO",
+ "Spanish - Bolivia",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_BO */
+ '\0', /* thousands_sep es_BO */
+ "\x80\x80", /* grouping es_BO */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_BO *****/
+
+/***** LOCALE BEGIN es_CL: Spanish - Chile *****/
+MY_LOCALE my_locale_es_CL
+(
+ 83,
+ "es_CL",
+ "Spanish - Chile",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_CL */
+ '\0', /* thousands_sep es_CL */
+ "\x80\x80", /* grouping es_CL */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_CL *****/
+
+/***** LOCALE BEGIN es_CO: Spanish - Columbia *****/
+MY_LOCALE my_locale_es_CO
+(
+ 84,
+ "es_CO",
+ "Spanish - Columbia",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_CO */
+ '\0', /* thousands_sep es_CO */
+ "\x80\x80", /* grouping es_CO */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_CO *****/
+
+/***** LOCALE BEGIN es_CR: Spanish - Costa Rica *****/
+MY_LOCALE my_locale_es_CR
+(
+ 85,
+ "es_CR",
+ "Spanish - Costa Rica",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_CR */
+ '\0', /* thousands_sep es_CR */
+ "\x80\x80", /* grouping es_CR */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_CR *****/
+
+/***** LOCALE BEGIN es_DO: Spanish - Dominican Republic *****/
+MY_LOCALE my_locale_es_DO
+(
+ 86,
+ "es_DO",
+ "Spanish - Dominican Republic",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_DO */
+ '\0', /* thousands_sep es_DO */
+ "\x80\x80", /* grouping es_DO */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_DO *****/
+
+/***** LOCALE BEGIN es_EC: Spanish - Ecuador *****/
+MY_LOCALE my_locale_es_EC
+(
+ 87,
+ "es_EC",
+ "Spanish - Ecuador",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_EC */
+ '\0', /* thousands_sep es_EC */
+ "\x80\x80", /* grouping es_EC */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_EC *****/
+
+/***** LOCALE BEGIN es_GT: Spanish - Guatemala *****/
+MY_LOCALE my_locale_es_GT
+(
+ 88,
+ "es_GT",
+ "Spanish - Guatemala",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_GT */
+ '\0', /* thousands_sep es_GT */
+ "\x80\x80", /* grouping es_GT */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_GT *****/
+
+/***** LOCALE BEGIN es_HN: Spanish - Honduras *****/
+MY_LOCALE my_locale_es_HN
+(
+ 89,
+ "es_HN",
+ "Spanish - Honduras",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_HN */
+ '\0', /* thousands_sep es_HN */
+ "\x80\x80", /* grouping es_HN */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_HN *****/
+
+/***** LOCALE BEGIN es_MX: Spanish - Mexico *****/
+MY_LOCALE my_locale_es_MX
+(
+ 90,
+ "es_MX",
+ "Spanish - Mexico",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_MX */
+ '\0', /* thousands_sep es_MX */
+ "\x80\x80", /* grouping es_MX */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_MX *****/
+
+/***** LOCALE BEGIN es_NI: Spanish - Nicaragua *****/
+MY_LOCALE my_locale_es_NI
+(
+ 91,
+ "es_NI",
+ "Spanish - Nicaragua",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_NI */
+ '\0', /* thousands_sep es_NI */
+ "\x80\x80", /* grouping es_NI */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_NI *****/
+
+/***** LOCALE BEGIN es_PA: Spanish - Panama *****/
+MY_LOCALE my_locale_es_PA
+(
+ 92,
+ "es_PA",
+ "Spanish - Panama",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_PA */
+ '\0', /* thousands_sep es_PA */
+ "\x80\x80", /* grouping es_PA */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_PA *****/
+
+/***** LOCALE BEGIN es_PE: Spanish - Peru *****/
+MY_LOCALE my_locale_es_PE
+(
+ 93,
+ "es_PE",
+ "Spanish - Peru",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_PE */
+ '\0', /* thousands_sep es_PE */
+ "\x80\x80", /* grouping es_PE */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_PE *****/
+
+/***** LOCALE BEGIN es_PR: Spanish - Puerto Rico *****/
+MY_LOCALE my_locale_es_PR
+(
+ 94,
+ "es_PR",
+ "Spanish - Puerto Rico",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_PR */
+ '\0', /* thousands_sep es_PR */
+ "\x80\x80", /* grouping es_PR */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_PR *****/
+
+/***** LOCALE BEGIN es_PY: Spanish - Paraguay *****/
+MY_LOCALE my_locale_es_PY
+(
+ 95,
+ "es_PY",
+ "Spanish - Paraguay",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_PY */
+ '\0', /* thousands_sep es_PY */
+ "\x80\x80", /* grouping es_PY */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_PY *****/
+
+/***** LOCALE BEGIN es_SV: Spanish - El Salvador *****/
+MY_LOCALE my_locale_es_SV
+(
+ 96,
+ "es_SV",
+ "Spanish - El Salvador",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_SV */
+ '\0', /* thousands_sep es_SV */
+ "\x80\x80", /* grouping es_SV */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_SV *****/
+
+/***** LOCALE BEGIN es_US: Spanish - United States *****/
+MY_LOCALE my_locale_es_US
+(
+ 97,
+ "es_US",
+ "Spanish - United States",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ '.', /* decimal point es_US */
+ ',', /* thousands_sep es_US */
+ "\x03\x03", /* grouping es_US */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_US *****/
+
+/***** LOCALE BEGIN es_UY: Spanish - Uruguay *****/
+MY_LOCALE my_locale_es_UY
+(
+ 98,
+ "es_UY",
+ "Spanish - Uruguay",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_UY */
+ '\0', /* thousands_sep es_UY */
+ "\x80\x80", /* grouping es_UY */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_UY *****/
+
+/***** LOCALE BEGIN es_VE: Spanish - Venezuela *****/
+MY_LOCALE my_locale_es_VE
+(
+ 99,
+ "es_VE",
+ "Spanish - Venezuela",
+ FALSE,
+ &my_locale_typelib_month_names_es_ES,
+ &my_locale_typelib_ab_month_names_es_ES,
+ &my_locale_typelib_day_names_es_ES,
+ &my_locale_typelib_ab_day_names_es_ES,
+ 10,
+ 9,
+ ',', /* decimal point es_VE */
+ '\0', /* thousands_sep es_VE */
+ "\x80\x80", /* grouping es_VE */
+ &global_errmsgs[es_ES]
+);
+/***** LOCALE END es_VE *****/
+
+/***** LOCALE BEGIN fr_BE: French - Belgium *****/
+MY_LOCALE my_locale_fr_BE
+(
+ 100,
+ "fr_BE",
+ "French - Belgium",
+ FALSE,
+ &my_locale_typelib_month_names_fr_FR,
+ &my_locale_typelib_ab_month_names_fr_FR,
+ &my_locale_typelib_day_names_fr_FR,
+ &my_locale_typelib_ab_day_names_fr_FR,
+ 9,
+ 8,
+ ',', /* decimal point fr_BE */
+ '.', /* thousands_sep fr_BE */
+ "\x80\x80", /* grouping fr_BE */
+ &global_errmsgs[fr_FR]
+);
+/***** LOCALE END fr_BE *****/
+
+/***** LOCALE BEGIN fr_CA: French - Canada *****/
+MY_LOCALE my_locale_fr_CA
+(
+ 101,
+ "fr_CA",
+ "French - Canada",
+ FALSE,
+ &my_locale_typelib_month_names_fr_FR,
+ &my_locale_typelib_ab_month_names_fr_FR,
+ &my_locale_typelib_day_names_fr_FR,
+ &my_locale_typelib_ab_day_names_fr_FR,
+ 9,
+ 8,
+ ',', /* decimal point fr_CA */
+ ' ', /* thousands_sep fr_CA */
+ "\x80\x80", /* grouping fr_CA */
+ &global_errmsgs[fr_FR]
+);
+/***** LOCALE END fr_CA *****/
+
+/***** LOCALE BEGIN fr_CH: French - Switzerland *****/
+MY_LOCALE my_locale_fr_CH
+(
+ 102,
+ "fr_CH",
+ "French - Switzerland",
+ FALSE,
+ &my_locale_typelib_month_names_fr_FR,
+ &my_locale_typelib_ab_month_names_fr_FR,
+ &my_locale_typelib_day_names_fr_FR,
+ &my_locale_typelib_ab_day_names_fr_FR,
+ 9,
+ 8,
+ ',', /* decimal point fr_CH */
+ '\0', /* thousands_sep fr_CH */
+ "\x80\x80", /* grouping fr_CH */
+ &global_errmsgs[fr_FR]
+);
+/***** LOCALE END fr_CH *****/
+
+/***** LOCALE BEGIN fr_LU: French - Luxembourg *****/
+MY_LOCALE my_locale_fr_LU
+(
+ 103,
+ "fr_LU",
+ "French - Luxembourg",
+ FALSE,
+ &my_locale_typelib_month_names_fr_FR,
+ &my_locale_typelib_ab_month_names_fr_FR,
+ &my_locale_typelib_day_names_fr_FR,
+ &my_locale_typelib_ab_day_names_fr_FR,
+ 9,
+ 8,
+ ',', /* decimal point fr_LU */
+ '\0', /* thousands_sep fr_LU */
+ "\x80\x80", /* grouping fr_LU */
+ &global_errmsgs[fr_FR]
+);
+/***** LOCALE END fr_LU *****/
+
+/***** LOCALE BEGIN it_IT: Italian - Italy *****/
+MY_LOCALE my_locale_it_IT
+(
+ 104,
+ "it_IT",
+ "Italian - Italy",
+ FALSE,
+ &my_locale_typelib_month_names_it_CH,
+ &my_locale_typelib_ab_month_names_it_CH,
+ &my_locale_typelib_day_names_it_CH,
+ &my_locale_typelib_ab_day_names_it_CH,
+ 9,
+ 9,
+ ',', /* decimal point it_IT */
+ '\0', /* thousands_sep it_IT */
+ "\x80\x80", /* grouping it_IT */
+ &global_errmsgs[it_IT]
+);
+/***** LOCALE END it_IT *****/
+
+/***** LOCALE BEGIN nl_BE: Dutch - Belgium *****/
+MY_LOCALE my_locale_nl_BE
+(
+ 105,
+ "nl_BE",
+ "Dutch - Belgium",
+ TRUE,
+ &my_locale_typelib_month_names_nl_NL,
+ &my_locale_typelib_ab_month_names_nl_NL,
+ &my_locale_typelib_day_names_nl_NL,
+ &my_locale_typelib_ab_day_names_nl_NL,
+ 9,
+ 9,
+ ',', /* decimal point nl_BE */
+ '.', /* thousands_sep nl_BE */
+ "\x80\x80", /* grouping nl_BE */
+ &global_errmsgs[nl_NL]
+);
+/***** LOCALE END nl_BE *****/
+
+/***** LOCALE BEGIN no_NO: Norwegian - Norway *****/
+MY_LOCALE my_locale_no_NO
+(
+ 106,
+ "no_NO",
+ "Norwegian - Norway",
+ FALSE,
+ &my_locale_typelib_month_names_nb_NO,
+ &my_locale_typelib_ab_month_names_nb_NO,
+ &my_locale_typelib_day_names_nb_NO,
+ &my_locale_typelib_ab_day_names_nb_NO,
+ 9,
+ 7,
+ ',', /* decimal point no_NO */
+ '.', /* thousands_sep no_NO */
+ "\x03\x03", /* grouping no_NO */
+ &global_errmsgs[no_NO]
+);
+/***** LOCALE END no_NO *****/
+
+/***** LOCALE BEGIN sv_FI: Swedish - Finland *****/
+MY_LOCALE my_locale_sv_FI
+(
+ 107,
+ "sv_FI",
+ "Swedish - Finland",
+ FALSE,
+ &my_locale_typelib_month_names_sv_SE,
+ &my_locale_typelib_ab_month_names_sv_SE,
+ &my_locale_typelib_day_names_sv_SE,
+ &my_locale_typelib_ab_day_names_sv_SE,
+ 9,
+ 7,
+ ',', /* decimal point sv_FI */
+ ' ', /* thousands_sep sv_FI */
+ "\x03\x03", /* grouping sv_FI */
+ &global_errmsgs[sv_SE]
+);
+/***** LOCALE END sv_FI *****/
+
+/***** LOCALE BEGIN zh_HK: Chinese - Hong Kong SAR *****/
+MY_LOCALE my_locale_zh_HK
+(
+ 108,
+ "zh_HK",
+ "Chinese - Hong Kong SAR",
+ FALSE,
+ &my_locale_typelib_month_names_zh_CN,
+ &my_locale_typelib_ab_month_names_zh_CN,
+ &my_locale_typelib_day_names_zh_CN,
+ &my_locale_typelib_ab_day_names_zh_CN,
+ 3,
+ 3,
+ '.', /* decimal point zh_HK */
+ ',', /* thousands_sep zh_HK */
+ "\x03", /* grouping zh_HK */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END zh_HK *****/
+
+
+/***** LOCALE BEGIN el_GR: Greek - Greece *****/
+static const char *my_locale_month_names_el_GR[13]=
+{
+ "ΙανουάÏιος", "ΦεβÏουάÏιος", "ΜάÏτιος",
+ "ΑπÏίλιος", "Μάιος", "ΙοÏνιος",
+ "ΙοÏλιος", "ΑÏγουστος", "ΣεπτέμβÏιος",
+ "ΟκτώβÏιος", "ÎοέμβÏιος", "ΔεκέμβÏιος", NullS
+};
+
+static const char *my_locale_ab_month_names_el_GR[13]=
+{
+ "Ιαν", "Φεβ", "ΜάÏ",
+ "ΑπÏ", "Μάι", "ΙοÏν",
+ "ΙοÏλ","ΑÏγ", "Σεπ",
+ "Οκτ", "Îοέ", "Δεκ", NullS
+};
+
+static const char *my_locale_day_names_el_GR[8] =
+{
+ "ΔευτέÏα", "ΤÏίτη", "ΤετάÏτη", "Πέμπτη",
+ "ΠαÏασκευή", "Σάββατο", "ΚυÏιακή", NullS
+};
+
+static const char *my_locale_ab_day_names_el_GR[8]=
+{
+ "Δευ", "ΤÏί", "Τετ", "Πέμ",
+ "ΠαÏ", "Σάβ", "ΚυÏ", NullS
+};
+
+static TYPELIB my_locale_typelib_month_names_el_GR=
+{
+ array_elements(my_locale_month_names_el_GR) - 1,
+ "", my_locale_month_names_el_GR, NULL
+};
+
+static TYPELIB my_locale_typelib_ab_month_names_el_GR=
+{
+ array_elements(my_locale_ab_month_names_el_GR)-1,
+ "", my_locale_ab_month_names_el_GR, NULL
+};
+
+static TYPELIB my_locale_typelib_day_names_el_GR=
+{
+ array_elements(my_locale_day_names_el_GR)-1,
+ "", my_locale_day_names_el_GR, NULL
+};
+
+static TYPELIB my_locale_typelib_ab_day_names_el_GR=
+{
+ array_elements(my_locale_ab_day_names_el_GR) - 1,
+ "", my_locale_ab_day_names_el_GR, NULL
+};
+
+MY_LOCALE my_locale_el_GR
+(
+ 109,
+ "el_GR",
+ "Greek - Greece",
+ FALSE,
+ &my_locale_typelib_month_names_el_GR,
+ &my_locale_typelib_ab_month_names_el_GR,
+ &my_locale_typelib_day_names_el_GR,
+ &my_locale_typelib_ab_day_names_el_GR,
+ 11, /* max mon name length */
+ 9, /* max day name length */
+ ',', /* decimal point el_GR */
+ '.', /* thousands_sep el_GR */
+ "\x80", /* grouping el_GR */
+ &global_errmsgs[el_GR]
+);
+/***** LOCALE END el_GR *****/
+
+
+/***** LOCALE BEGIN rm_CH: Romansh - Switzerland *****/
+static const char *my_locale_month_names_rm_CH[13]=
+{
+ "schaner", "favrer", "mars", "avrigl", "matg", "zercladur",
+ "fanadur", "avust", "settember", "october", "november", "december", NullS
+};
+
+static const char *my_locale_ab_month_names_rm_CH[13]=
+{
+ "schan", "favr", "mars", "avr", "matg", "zercl",
+ "fan", "avust", "sett", "oct", "nov", "dec", NullS
+};
+
+static const char *my_locale_day_names_rm_CH[8]=
+{
+ "glindesdi", "mardi", "mesemna", "gievgia",
+ "venderdi", "sonda", "dumengia", NullS
+};
+
+static const char *my_locale_ab_day_names_rm_CH[8]=
+{
+ "gli", "ma", "me", "gie", "ve", "so", "du", NullS
+};
+
+static TYPELIB my_locale_typelib_month_names_rm_CH=
+{
+ array_elements(my_locale_month_names_rm_CH) - 1,
+ "", my_locale_month_names_rm_CH, NULL
+};
+
+static TYPELIB my_locale_typelib_ab_month_names_rm_CH=
+{
+ array_elements(my_locale_ab_month_names_rm_CH) - 1,
+ "", my_locale_ab_month_names_rm_CH, NULL
+};
+
+static TYPELIB my_locale_typelib_day_names_rm_CH=
+{
+ array_elements(my_locale_day_names_rm_CH) - 1,
+ "", my_locale_day_names_rm_CH, NULL
+};
+
+static TYPELIB my_locale_typelib_ab_day_names_rm_CH=
+{
+ array_elements(my_locale_ab_day_names_rm_CH) - 1,
+ "", my_locale_ab_day_names_rm_CH, NULL
+};
+
+MY_LOCALE my_locale_rm_CH
+(
+ 110,
+ "rm_CH",
+ "Romansh - Switzerland",
+ FALSE,
+ &my_locale_typelib_month_names_rm_CH,
+ &my_locale_typelib_ab_month_names_rm_CH,
+ &my_locale_typelib_day_names_rm_CH,
+ &my_locale_typelib_ab_day_names_rm_CH,
+ 9, /* max mon name length */
+ 9, /* max day name length */
+ ',', /* decimal point rm_CH */
+ '\'', /* thousands_sep rm_CH */
+ "\x03\x03", /* grouping rm_CH */
+ &global_errmsgs[en_US]
+);
+/***** LOCALE END rm_CH *****/
+
+
+/*
+ The list of all locales.
+ Note, locales must be ordered according to their
+ numbers to make my_locale_by_number() work fast.
+ Some debug asserts below check this.
+*/
+MY_LOCALE *my_locales[]=
+ {
+ &my_locale_en_US,
+ &my_locale_en_GB,
+ &my_locale_ja_JP,
+ &my_locale_sv_SE,
+ &my_locale_de_DE,
+ &my_locale_fr_FR,
+ &my_locale_ar_AE,
+ &my_locale_ar_BH,
+ &my_locale_ar_JO,
+ &my_locale_ar_SA,
+ &my_locale_ar_SY,
+ &my_locale_be_BY,
+ &my_locale_bg_BG,
+ &my_locale_ca_ES,
+ &my_locale_cs_CZ,
+ &my_locale_da_DK,
+ &my_locale_de_AT,
+ &my_locale_es_ES,
+ &my_locale_et_EE,
+ &my_locale_eu_ES,
+ &my_locale_fi_FI,
+ &my_locale_fo_FO,
+ &my_locale_gl_ES,
+ &my_locale_gu_IN,
+ &my_locale_he_IL,
+ &my_locale_hi_IN,
+ &my_locale_hr_HR,
+ &my_locale_hu_HU,
+ &my_locale_id_ID,
+ &my_locale_is_IS,
+ &my_locale_it_CH,
+ &my_locale_ko_KR,
+ &my_locale_lt_LT,
+ &my_locale_lv_LV,
+ &my_locale_mk_MK,
+ &my_locale_mn_MN,
+ &my_locale_ms_MY,
+ &my_locale_nb_NO,
+ &my_locale_nl_NL,
+ &my_locale_pl_PL,
+ &my_locale_pt_BR,
+ &my_locale_pt_PT,
+ &my_locale_ro_RO,
+ &my_locale_ru_RU,
+ &my_locale_ru_UA,
+ &my_locale_sk_SK,
+ &my_locale_sl_SI,
+ &my_locale_sq_AL,
+ &my_locale_sr_RS,
+ &my_locale_ta_IN,
+ &my_locale_te_IN,
+ &my_locale_th_TH,
+ &my_locale_tr_TR,
+ &my_locale_uk_UA,
+ &my_locale_ur_PK,
+ &my_locale_vi_VN,
+ &my_locale_zh_CN,
+ &my_locale_zh_TW,
+ &my_locale_ar_DZ,
+ &my_locale_ar_EG,
+ &my_locale_ar_IN,
+ &my_locale_ar_IQ,
+ &my_locale_ar_KW,
+ &my_locale_ar_LB,
+ &my_locale_ar_LY,
+ &my_locale_ar_MA,
+ &my_locale_ar_OM,
+ &my_locale_ar_QA,
+ &my_locale_ar_SD,
+ &my_locale_ar_TN,
+ &my_locale_ar_YE,
+ &my_locale_de_BE,
+ &my_locale_de_CH,
+ &my_locale_de_LU,
+ &my_locale_en_AU,
+ &my_locale_en_CA,
+ &my_locale_en_IN,
+ &my_locale_en_NZ,
+ &my_locale_en_PH,
+ &my_locale_en_ZA,
+ &my_locale_en_ZW,
+ &my_locale_es_AR,
+ &my_locale_es_BO,
+ &my_locale_es_CL,
+ &my_locale_es_CO,
+ &my_locale_es_CR,
+ &my_locale_es_DO,
+ &my_locale_es_EC,
+ &my_locale_es_GT,
+ &my_locale_es_HN,
+ &my_locale_es_MX,
+ &my_locale_es_NI,
+ &my_locale_es_PA,
+ &my_locale_es_PE,
+ &my_locale_es_PR,
+ &my_locale_es_PY,
+ &my_locale_es_SV,
+ &my_locale_es_US,
+ &my_locale_es_UY,
+ &my_locale_es_VE,
+ &my_locale_fr_BE,
+ &my_locale_fr_CA,
+ &my_locale_fr_CH,
+ &my_locale_fr_LU,
+ &my_locale_it_IT,
+ &my_locale_nl_BE,
+ &my_locale_no_NO,
+ &my_locale_sv_FI,
+ &my_locale_zh_HK,
+ &my_locale_el_GR,
+ &my_locale_rm_CH,
+ NULL
+ };
+
+
+MY_LOCALE *my_locales_deprecated[]=
+{
+ &my_locale_sr_YU,
+ NULL
+};
+
+
+MY_LOCALE *my_locale_by_number(uint number)
+{
+ MY_LOCALE *locale;
+ if (number >= array_elements(my_locales) - 1)
+ return NULL;
+ locale= my_locales[number];
+ // Check that locale is on its correct position in the array
+ DBUG_ASSERT(locale == my_locales[locale->number]);
+ return locale;
+}
+
+
+static MY_LOCALE*
+my_locale_by_name(MY_LOCALE** locales, const char *name)
+{
+ MY_LOCALE **locale;
+ for (locale= locales; *locale != NULL; locale++)
+ {
+ if (!my_strcasecmp(&my_charset_latin1, (*locale)->name, name))
+ return *locale;
+ }
+ return NULL;
+}
+
+
+MY_LOCALE *my_locale_by_name(const char *name)
+{
+ MY_LOCALE *locale;
+
+ if ((locale= my_locale_by_name(my_locales, name)))
+ {
+ // Check that locale is on its correct position in the array
+ DBUG_ASSERT(locale == my_locales[locale->number]);
+ return locale;
+ }
+ else if ((locale= my_locale_by_name(my_locales_deprecated, name)))
+ {
+ THD *thd= current_thd;
+ /*
+ Replace the deprecated locale to the corresponding
+ 'fresh' locale with the same ID.
+ */
+ locale= my_locales[locale->number];
+ if (thd)
+ {
+ // Send a warning to the client
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_DEPRECATED_SYNTAX, ER(ER_WARN_DEPRECATED_SYNTAX),
+ name, locale->name);
+ }
+ else
+ {
+ // Send a warning to mysqld error log
+ sql_print_warning("The syntax '%s' is deprecated and will be removed. "
+ "Please use %s instead.",
+ name, locale->name);
+ }
+ }
+ return locale;
+}
+
+
+void cleanup_errmsgs()
+{
+ for (MY_LOCALE_ERRMSGS *msgs= global_errmsgs; msgs->language; msgs++)
+ {
+ my_free(msgs->errmsgs);
+ }
+}
diff --git a/sql/sql_locale.h b/sql/sql_locale.h
new file mode 100644
index 00000000000..8357a9ecba4
--- /dev/null
+++ b/sql/sql_locale.h
@@ -0,0 +1,78 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_LOCALE_INCLUDED
+#define SQL_LOCALE_INCLUDED
+
+typedef struct my_locale_errmsgs
+{
+ const char *language;
+ const char **errmsgs;
+} MY_LOCALE_ERRMSGS;
+
+#include "my_global.h" /* uint */
+
+typedef struct st_typelib TYPELIB;
+
+class MY_LOCALE
+{
+public:
+ uint number;
+ const char *name;
+ const char *description;
+ const bool is_ascii;
+ TYPELIB *month_names;
+ TYPELIB *ab_month_names;
+ TYPELIB *day_names;
+ TYPELIB *ab_day_names;
+ uint max_month_name_length;
+ uint max_day_name_length;
+ uint decimal_point;
+ uint thousand_sep;
+ const char *grouping;
+ MY_LOCALE_ERRMSGS *errmsgs;
+ MY_LOCALE(uint number_par,
+ const char *name_par, const char *descr_par, bool is_ascii_par,
+ TYPELIB *month_names_par, TYPELIB *ab_month_names_par,
+ TYPELIB *day_names_par, TYPELIB *ab_day_names_par,
+ uint max_month_name_length_par, uint max_day_name_length_par,
+ uint decimal_point_par, uint thousand_sep_par,
+ const char *grouping_par, MY_LOCALE_ERRMSGS *errmsgs_par) :
+ number(number_par),
+ name(name_par), description(descr_par), is_ascii(is_ascii_par),
+ month_names(month_names_par), ab_month_names(ab_month_names_par),
+ day_names(day_names_par), ab_day_names(ab_day_names_par),
+ max_month_name_length(max_month_name_length_par),
+ max_day_name_length(max_day_name_length_par),
+ decimal_point(decimal_point_par),
+ thousand_sep(thousand_sep_par),
+ grouping(grouping_par),
+ errmsgs(errmsgs_par)
+ {}
+};
+/* Exported variables */
+
+extern MY_LOCALE my_locale_en_US;
+extern MY_LOCALE *my_locales[];
+extern MY_LOCALE *my_default_lc_messages;
+extern MY_LOCALE *my_default_lc_time_names;
+
+/* Exported functions */
+
+MY_LOCALE *my_locale_by_name(const char *name);
+MY_LOCALE *my_locale_by_number(uint number);
+void cleanup_errmsgs(void);
+
+#endif /* SQL_LOCALE_INCLUDED */
diff --git a/sql/sql_manager.cc b/sql/sql_manager.cc
new file mode 100644
index 00000000000..c6c465aa4e2
--- /dev/null
+++ b/sql/sql_manager.cc
@@ -0,0 +1,170 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/*
+ * sql_manager.cc
+ * This thread manages various maintenance tasks.
+ *
+ * o Flushing the tables every flush_time seconds.
+ * o Berkeley DB: removing unneeded log files.
+ */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_manager.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_base.h" // flush_tables
+
+static bool volatile manager_thread_in_use;
+static bool abort_manager;
+
+pthread_t manager_thread;
+mysql_mutex_t LOCK_manager;
+mysql_cond_t COND_manager;
+
+struct handler_cb {
+ struct handler_cb *next;
+ void (*action)(void);
+};
+
+static struct handler_cb * volatile cb_list;
+
+bool mysql_manager_submit(void (*action)())
+{
+ bool result= FALSE;
+ DBUG_ASSERT(manager_thread_in_use);
+ struct handler_cb * volatile *cb;
+ mysql_mutex_lock(&LOCK_manager);
+ cb= &cb_list;
+ while (*cb && (*cb)->action != action)
+ cb= &(*cb)->next;
+ if (!*cb)
+ {
+ *cb= (struct handler_cb *)my_malloc(sizeof(struct handler_cb), MYF(MY_WME));
+ if (!*cb)
+ result= TRUE;
+ else
+ {
+ (*cb)->next= NULL;
+ (*cb)->action= action;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_manager);
+ return result;
+}
+
+pthread_handler_t handle_manager(void *arg __attribute__((unused)))
+{
+ int error = 0;
+ struct timespec abstime;
+ bool reset_flush_time = TRUE;
+ struct handler_cb *cb= NULL;
+ my_thread_init();
+ DBUG_ENTER("handle_manager");
+
+ pthread_detach_this_thread();
+ manager_thread = pthread_self();
+ mysql_cond_init(key_COND_manager, &COND_manager,NULL);
+ mysql_mutex_init(key_LOCK_manager, &LOCK_manager, NULL);
+ manager_thread_in_use = 1;
+ for (;;)
+ {
+ mysql_mutex_lock(&LOCK_manager);
+ /* XXX: This will need to be made more general to handle different
+ * polling needs. */
+ if (flush_time)
+ {
+ if (reset_flush_time)
+ {
+ set_timespec(abstime, flush_time);
+ reset_flush_time = FALSE;
+ }
+ while ((!error || error == EINTR) && !abort_manager)
+ error= mysql_cond_timedwait(&COND_manager, &LOCK_manager, &abstime);
+ }
+ else
+ {
+ while ((!error || error == EINTR) && !abort_manager)
+ error= mysql_cond_wait(&COND_manager, &LOCK_manager);
+ }
+ if (cb == NULL)
+ {
+ cb= cb_list;
+ cb_list= NULL;
+ }
+ mysql_mutex_unlock(&LOCK_manager);
+
+ if (abort_manager)
+ break;
+
+ if (error == ETIMEDOUT || error == ETIME)
+ {
+ tc_purge();
+ error = 0;
+ reset_flush_time = TRUE;
+ }
+
+ while (cb)
+ {
+ struct handler_cb *next= cb->next;
+ cb->action();
+ my_free(cb);
+ cb= next;
+ }
+ }
+ manager_thread_in_use = 0;
+ mysql_mutex_destroy(&LOCK_manager);
+ mysql_cond_destroy(&COND_manager);
+ DBUG_LEAVE; // Can't use DBUG_RETURN after my_thread_end
+ my_thread_end();
+ return (NULL);
+}
+
+
+/* Start handle manager thread */
+void start_handle_manager()
+{
+ DBUG_ENTER("start_handle_manager");
+ abort_manager = false;
+ if (flush_time && flush_time != ~(ulong) 0L)
+ {
+ pthread_t hThread;
+ int error;
+ if ((error= mysql_thread_create(key_thread_handle_manager,
+ &hThread, &connection_attrib,
+ handle_manager, 0)))
+ sql_print_warning("Can't create handle_manager thread (errno= %d)",
+ error);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/* Initiate shutdown of handle manager thread */
+void stop_handle_manager()
+{
+ DBUG_ENTER("stop_handle_manager");
+ abort_manager = true;
+ if (manager_thread_in_use)
+ {
+ mysql_mutex_lock(&LOCK_manager);
+ DBUG_PRINT("quit", ("initiate shutdown of handle manager thread: 0x%lx",
+ (ulong)manager_thread));
+ mysql_cond_signal(&COND_manager);
+ mysql_mutex_unlock(&LOCK_manager);
+ }
+ DBUG_VOID_RETURN;
+}
+
diff --git a/sql/sql_manager.h b/sql/sql_manager.h
new file mode 100644
index 00000000000..8debbe6ead9
--- /dev/null
+++ b/sql/sql_manager.h
@@ -0,0 +1,23 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_MANAGER_INCLUDED
+#define SQL_MANAGER_INCLUDED
+
+void start_handle_manager();
+void stop_handle_manager();
+bool mysql_manager_submit(void (*action)());
+
+#endif /* SQL_MANAGER_INCLUDED */
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
new file mode 100644
index 00000000000..5635e9ad4b7
--- /dev/null
+++ b/sql/sql_parse.cc
@@ -0,0 +1,8671 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2014, SkySQL Ab.
+
+ 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 */
+
+#define MYSQL_LEX 1
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_parse.h" // sql_kill, *_precheck, *_prepare
+#include "lock.h" // try_transactional_lock,
+ // check_transactional_lock,
+ // set_handler_table_locks,
+ // lock_global_read_lock,
+ // make_global_read_lock_block_commit
+#include "sql_base.h" // find_temporary_table
+#include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE, query_cache_*
+#include "sql_show.h" // mysqld_list_*, mysqld_show_*,
+ // calc_sum_of_all_status
+#include "mysqld.h"
+#include "sql_locale.h" // my_locale_en_US
+#include "log.h" // flush_error_log
+#include "sql_view.h" // mysql_create_view, mysql_drop_view
+#include "sql_delete.h" // mysql_delete
+#include "sql_insert.h" // mysql_insert
+#include "sql_update.h" // mysql_update, mysql_multi_update
+#include "sql_partition.h" // struct partition_info
+#include "sql_db.h" // mysql_change_db, mysql_create_db,
+ // mysql_rm_db, mysql_upgrade_db,
+ // mysql_alter_db,
+ // check_db_dir_existence,
+ // my_dbopt_cleanup
+#include "sql_table.h" // mysql_create_like_table,
+ // mysql_create_table,
+ // mysql_alter_table,
+ // mysql_backup_table,
+ // mysql_restore_table
+#include "sql_reload.h" // reload_acl_and_cache
+#include "sql_admin.h" // mysql_assign_to_keycache
+#include "sql_connect.h" // decrease_user_connections,
+ // check_mqh,
+ // reset_mqh
+#include "sql_rename.h" // mysql_rename_table
+#include "sql_tablespace.h" // mysql_alter_tablespace
+#include "hostname.h" // hostname_cache_refresh
+#include "sql_acl.h" // *_ACL, check_grant, is_acl_user,
+ // has_any_table_level_privileges,
+ // mysql_drop_user, mysql_rename_user,
+ // check_grant_routine,
+ // mysql_routine_grant,
+ // mysql_show_grants,
+ // sp_grant_privileges, ...
+#include "sql_test.h" // mysql_print_status
+#include "sql_select.h" // handle_select, mysql_select,
+ // mysql_explain_union
+#include "sql_load.h" // mysql_load
+#include "sql_servers.h" // create_servers, alter_servers,
+ // drop_servers, servers_reload
+#include "sql_handler.h" // mysql_ha_open, mysql_ha_close,
+ // mysql_ha_read
+#include "sql_binlog.h" // mysql_client_binlog_statement
+#include "sql_do.h" // mysql_do
+#include "sql_help.h" // mysqld_help
+#include "rpl_constants.h" // Incident, INCIDENT_LOST_EVENTS
+#include "log_event.h"
+#include "sql_repl.h"
+#include "rpl_filter.h"
+#include "repl_failsafe.h"
+#include <m_ctype.h>
+#include <myisam.h>
+#include <my_dir.h>
+#include "rpl_handler.h"
+#include "rpl_mi.h"
+
+#include "sql_digest.h"
+
+#include "sp_head.h"
+#include "sp.h"
+#include "sp_cache.h"
+#include "events.h"
+#include "sql_trigger.h"
+#include "transaction.h"
+#include "sql_audit.h"
+#include "sql_prepare.h"
+#include "debug_sync.h"
+#include "probes_mysql.h"
+#include "set_var.h"
+#include "log_slow.h"
+#include "sql_bootstrap.h"
+
+#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
+
+#ifdef WITH_ARIA_STORAGE_ENGINE
+#include "../storage/maria/ha_maria.h"
+#endif
+
+/**
+ @defgroup Runtime_Environment Runtime Environment
+ @{
+*/
+
+/* Used in error handling only */
+#define SP_TYPE_STRING(LP) \
+ ((LP)->sphead->m_type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE")
+#define SP_COM_STRING(LP) \
+ ((LP)->sql_command == SQLCOM_CREATE_SPFUNCTION || \
+ (LP)->sql_command == SQLCOM_ALTER_FUNCTION || \
+ (LP)->sql_command == SQLCOM_SHOW_CREATE_FUNC || \
+ (LP)->sql_command == SQLCOM_DROP_FUNCTION ? \
+ "FUNCTION" : "PROCEDURE")
+
+static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables);
+static void sql_kill(THD *thd, longlong id, killed_state state, killed_type type);
+static void sql_kill_user(THD *thd, LEX_USER *user, killed_state state);
+static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables);
+static bool execute_show_status(THD *, TABLE_LIST *);
+static bool execute_rename_table(THD *, TABLE_LIST *, TABLE_LIST *);
+
+const char *any_db="*any*"; // Special symbol for check_access
+
+const LEX_STRING command_name[]={
+ { C_STRING_WITH_LEN("Sleep") },
+ { C_STRING_WITH_LEN("Quit") },
+ { C_STRING_WITH_LEN("Init DB") },
+ { C_STRING_WITH_LEN("Query") },
+ { C_STRING_WITH_LEN("Field List") },
+ { C_STRING_WITH_LEN("Create DB") },
+ { C_STRING_WITH_LEN("Drop DB") },
+ { C_STRING_WITH_LEN("Refresh") },
+ { C_STRING_WITH_LEN("Shutdown") },
+ { C_STRING_WITH_LEN("Statistics") },
+ { C_STRING_WITH_LEN("Processlist") },
+ { C_STRING_WITH_LEN("Connect") },
+ { C_STRING_WITH_LEN("Kill") },
+ { C_STRING_WITH_LEN("Debug") },
+ { C_STRING_WITH_LEN("Ping") },
+ { C_STRING_WITH_LEN("Time") },
+ { C_STRING_WITH_LEN("Delayed insert") },
+ { C_STRING_WITH_LEN("Change user") },
+ { C_STRING_WITH_LEN("Binlog Dump") },
+ { C_STRING_WITH_LEN("Table Dump") },
+ { C_STRING_WITH_LEN("Connect Out") },
+ { C_STRING_WITH_LEN("Register Slave") },
+ { C_STRING_WITH_LEN("Prepare") },
+ { C_STRING_WITH_LEN("Execute") },
+ { C_STRING_WITH_LEN("Long Data") },
+ { C_STRING_WITH_LEN("Close stmt") },
+ { C_STRING_WITH_LEN("Reset stmt") },
+ { C_STRING_WITH_LEN("Set option") },
+ { C_STRING_WITH_LEN("Fetch") },
+ { C_STRING_WITH_LEN("Daemon") },
+ { C_STRING_WITH_LEN("Error") } // Last command number
+};
+
+const char *xa_state_names[]={
+ "NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY"
+};
+
+#ifdef HAVE_REPLICATION
+/**
+ Returns true if all tables should be ignored.
+*/
+inline bool all_tables_not_ok(THD *thd, TABLE_LIST *tables)
+{
+ Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter;
+ return rpl_filter->is_on() && tables && !thd->spcont &&
+ !rpl_filter->tables_ok(thd->db, tables);
+}
+#endif
+
+
+static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables)
+{
+ for (TABLE_LIST *table= tables; table; table= table->next_global)
+ {
+ DBUG_ASSERT(table->db && table->table_name);
+ if (table->updating && !find_temporary_table(thd, table))
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ Implicitly commit a active transaction if statement requires so.
+
+ @param thd Thread handle.
+ @param mask Bitmask used for the SQL command match.
+
+ @return 0 No implicit commit
+ @return 1 Do a commit
+*/
+static bool stmt_causes_implicit_commit(THD *thd, uint mask)
+{
+ LEX *lex= thd->lex;
+ bool skip= FALSE;
+ DBUG_ENTER("stmt_causes_implicit_commit");
+
+ if (!(sql_command_flags[lex->sql_command] & mask))
+ DBUG_RETURN(FALSE);
+
+ switch (lex->sql_command) {
+ case SQLCOM_DROP_TABLE:
+ skip= (lex->drop_temporary ||
+ (thd->variables.option_bits & OPTION_GTID_BEGIN));
+ break;
+ case SQLCOM_ALTER_TABLE:
+ /* If ALTER TABLE of non-temporary table, do implicit commit */
+ skip= (lex->create_info.tmp_table());
+ break;
+ case SQLCOM_CREATE_TABLE:
+ /*
+ If CREATE TABLE of non-temporary table and the table is not part
+ if a BEGIN GTID ... COMMIT group, do a implicit commit.
+ This ensures that CREATE ... SELECT will in the same GTID group on the
+ master and slave.
+ */
+ skip= (lex->create_info.tmp_table() ||
+ (thd->variables.option_bits & OPTION_GTID_BEGIN));
+ break;
+ case SQLCOM_SET_OPTION:
+ skip= lex->autocommit ? FALSE : TRUE;
+ break;
+ default:
+ break;
+ }
+
+ DBUG_RETURN(!skip);
+}
+
+
+/**
+ Mark all commands that somehow changes a table.
+
+ This is used to check number of updates / hour.
+
+ sql_command is actually set to SQLCOM_END sometimes
+ so we need the +1 to include it in the array.
+
+ See COMMAND_FLAG_xxx for different type of commands
+ 2 - query that returns meaningful ROW_COUNT() -
+ a number of modified rows
+*/
+
+uint sql_command_flags[SQLCOM_END+1];
+uint server_command_flags[COM_END+1];
+
+void init_update_queries(void)
+{
+ /* Initialize the server command flags array. */
+ memset(server_command_flags, 0, sizeof(server_command_flags));
+
+ server_command_flags[COM_STATISTICS]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS;
+ server_command_flags[COM_PING]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS;
+ server_command_flags[COM_STMT_PREPARE]= CF_SKIP_QUESTIONS;
+ server_command_flags[COM_STMT_CLOSE]= CF_SKIP_QUESTIONS;
+ server_command_flags[COM_STMT_RESET]= CF_SKIP_QUESTIONS;
+
+ /* Initialize the sql command flags array. */
+ memset(sql_command_flags, 0, sizeof(sql_command_flags));
+
+ /*
+ In general, DDL statements do not generate row events and do not go
+ through a cache before being written to the binary log. However, the
+ CREATE TABLE...SELECT is an exception because it may generate row
+ events. For that reason, the SQLCOM_CREATE_TABLE which represents
+ a CREATE TABLE, including the CREATE TABLE...SELECT, has the
+ CF_CAN_GENERATE_ROW_EVENTS flag. The distinction between a regular
+ CREATE TABLE and the CREATE TABLE...SELECT is made in other parts of
+ the code, in particular in the Query_log_event's constructor.
+ */
+ sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS |
+ CF_CAN_GENERATE_ROW_EVENTS;
+ sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS;
+ sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
+ CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS |
+ CF_INSERTS_DATA;
+ sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
+ CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS | CF_REPORT_PROGRESS |
+ CF_INSERTS_DATA;
+ sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS;
+ sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+
+ sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED |
+ CF_UPDATES_DATA;
+ sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED |
+ CF_UPDATES_DATA;
+ sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED |
+ CF_INSERTS_DATA;
+ sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED |
+ CF_INSERTS_DATA;
+ sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED;
+ sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED;;
+ sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED |
+ CF_INSERTS_DATA;;
+ sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED |
+ CF_INSERTS_DATA;
+ sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE |
+ CF_CAN_BE_EXPLAINED;
+ // (1) so that subquery is traced when doing "SET @var = (subquery)"
+ /*
+ @todo SQLCOM_SET_OPTION should have CF_CAN_GENERATE_ROW_EVENTS
+ set, because it may invoke a stored function that generates row
+ events. /Sven
+ */
+ sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE |
+ CF_AUTO_COMMIT_TRANS |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE; // (1)
+ // (1) so that subquery is traced when doing "DO @var := (subquery)"
+ sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE; // (1)
+
+ sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_DATABASES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_TRIGGERS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_EVENTS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_OPEN_TABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_PLUGINS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_FIELDS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_KEYS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_CHARSETS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_COLLATIONS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_SLAVE_HOSTS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_BINLOG_EVENTS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_STORAGE_ENGINES]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_AUTHORS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_CONTRIBUTORS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT;
+ sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT;
+ sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_EXPLAIN]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_CREATE]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_SLAVE_STAT]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_CREATE_TRIGGER]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
+ sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND | CF_CAN_GENERATE_ROW_EVENTS;
+ sql_command_flags[SQLCOM_SHOW_CLIENT_STATS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_USER_STATS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_TABLE_STATS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_INDEX_STATS]= CF_STATUS_COMMAND;
+ sql_command_flags[SQLCOM_SHOW_TABLES]= (CF_STATUS_COMMAND | CF_SHOW_TABLE_COMMAND | CF_REEXECUTION_FRAGILE);
+ sql_command_flags[SQLCOM_SHOW_TABLE_STATUS]= (CF_STATUS_COMMAND | CF_SHOW_TABLE_COMMAND | CF_REEXECUTION_FRAGILE);
+
+
+ sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_CREATE_ROLE]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_GRANT_ROLE]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_REVOKE_ROLE]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
+
+ /*
+ The following is used to preserver CF_ROW_COUNT during the
+ a CALL or EXECUTE statement, so the value generated by the
+ last called (or executed) statement is preserved.
+ See mysql_execute_command() for how CF_ROW_COUNT is used.
+ */
+ /*
+ (1): without it, in "CALL some_proc((subq))", subquery would not be
+ traced.
+ */
+ sql_command_flags[SQLCOM_CALL]= CF_REEXECUTION_FRAGILE |
+ CF_CAN_GENERATE_ROW_EVENTS |
+ CF_OPTIMIZER_TRACE; // (1)
+ sql_command_flags[SQLCOM_EXECUTE]= CF_CAN_GENERATE_ROW_EVENTS;
+
+ /*
+ We don't want to change to statement based replication for these commands
+ */
+ sql_command_flags[SQLCOM_ROLLBACK]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT;
+ /* We don't want to replicate ALTER TABLE for temp tables in row format */
+ sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT;
+ /* We don't want to replicate TRUNCATE for temp tables in row format */
+ sql_command_flags[SQLCOM_TRUNCATE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT;
+ /* We don't want to replicate DROP for temp tables in row format */
+ sql_command_flags[SQLCOM_DROP_TABLE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT;
+ /* One can change replication mode with SET */
+ sql_command_flags[SQLCOM_SET_OPTION]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT;
+
+ /*
+ The following admin table operations are allowed
+ on log tables.
+ */
+ sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS;
+ sql_command_flags[SQLCOM_OPTIMIZE]|= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS;
+ sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS;
+ sql_command_flags[SQLCOM_CHECK]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS;
+ sql_command_flags[SQLCOM_CHECKSUM]= CF_REPORT_PROGRESS;
+
+ sql_command_flags[SQLCOM_CREATE_USER]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_USER]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_RENAME_USER]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_CREATE_ROLE]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_ROLE]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_REVOKE_ROLE]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_GRANT_ROLE]|= CF_AUTO_COMMIT_TRANS;
+
+ sql_command_flags[SQLCOM_FLUSH]= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_RESET]= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_CREATE_SERVER]= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_SERVER]= CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS;
+
+ /*
+ The following statements can deal with temporary tables,
+ so temporary tables should be pre-opened for those statements to
+ simplify privilege checking.
+
+ There are other statements that deal with temporary tables and open
+ them, but which are not listed here. The thing is that the order of
+ pre-opening temporary tables for those statements is somewhat custom.
+ */
+ sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_DROP_TABLE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_TRUNCATE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_LOAD]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_DROP_INDEX]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_UPDATE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_UPDATE_MULTI]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_INSERT_SELECT]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_DELETE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_DELETE_MULTI]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_REPLACE_SELECT]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_SELECT]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_SET_OPTION]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_DO]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_HA_OPEN]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_CALL]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_CHECKSUM]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_ANALYZE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_CHECK]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_OPTIMIZE]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_REPAIR]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_PREOPEN_TMP_TABLES;
+ sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_PREOPEN_TMP_TABLES;
+
+ /*
+ DDL statements that should start with closing opened handlers.
+
+ We use this flag only for statements for which open HANDLERs
+ have to be closed before temporary tables are pre-opened.
+ */
+ sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_DROP_TABLE]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_TRUNCATE]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_REPAIR]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_OPTIMIZE]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_ANALYZE]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_CHECK]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_DROP_INDEX]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_HA_CLOSE;
+ sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_HA_CLOSE;
+
+ /*
+ Mark statements that always are disallowed in read-only
+ transactions. Note that according to the SQL standard,
+ even temporary table DDL should be disallowed.
+ */
+ sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_RENAME_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_INDEX]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_DB]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_DB]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_DB]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_VIEW]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_VIEW]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_EVENT]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_EVENT]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_EVENT]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_USER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_RENAME_USER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_USER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_SERVER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_SERVER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_SERVER]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|=CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_DROP_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_TRUNCATE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_ALTER_TABLESPACE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_REPAIR]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_OPTIMIZE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_GRANT]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_REVOKE]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_REVOKE_ALL]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_INSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS;
+ sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS;
+}
+
+bool sqlcom_can_generate_row_events(const THD *thd)
+{
+ return (sql_command_flags[thd->lex->sql_command] &
+ CF_CAN_GENERATE_ROW_EVENTS);
+}
+
+bool is_update_query(enum enum_sql_command command)
+{
+ DBUG_ASSERT(command <= SQLCOM_END);
+ return (sql_command_flags[command] & CF_CHANGES_DATA) != 0;
+}
+
+/**
+ Check if a sql command is allowed to write to log tables.
+ @param command The SQL command
+ @return true if writing is allowed
+*/
+bool is_log_table_write_query(enum enum_sql_command command)
+{
+ DBUG_ASSERT(command <= SQLCOM_END);
+ return (sql_command_flags[command] & CF_WRITE_LOGS_COMMAND) != 0;
+}
+
+void execute_init_command(THD *thd, LEX_STRING *init_command,
+ mysql_rwlock_t *var_lock)
+{
+ Vio* save_vio;
+ ulong save_client_capabilities;
+
+ mysql_rwlock_rdlock(var_lock);
+ if (!init_command->length)
+ {
+ mysql_rwlock_unlock(var_lock);
+ return;
+ }
+
+ /*
+ copy the value under a lock, and release the lock.
+ init_command has to be executed without a lock held,
+ as it may try to change itself
+ */
+ size_t len= init_command->length;
+ char *buf= thd->strmake(init_command->str, len);
+ mysql_rwlock_unlock(var_lock);
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.start_new_query();
+ thd->profiling.set_query_source(buf, len);
+#endif
+
+ THD_STAGE_INFO(thd, stage_execution_of_init_command);
+ save_client_capabilities= thd->client_capabilities;
+ thd->client_capabilities|= CLIENT_MULTI_QUERIES;
+ /*
+ We don't need return result of execution to client side.
+ To forbid this we should set thd->net.vio to 0.
+ */
+ save_vio= thd->net.vio;
+ thd->net.vio= 0;
+ dispatch_command(COM_QUERY, thd, buf, len);
+ thd->client_capabilities= save_client_capabilities;
+ thd->net.vio= save_vio;
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.finish_current_query();
+#endif
+}
+
+
+static char *fgets_fn(char *buffer, size_t size, fgets_input_t input, int *error)
+{
+ MYSQL_FILE *in= static_cast<MYSQL_FILE*> (input);
+ char *line= mysql_file_fgets(buffer, size, in);
+ if (error)
+ *error= (line == NULL) ? ferror(in->m_file) : 0;
+ return line;
+}
+
+
+static void handle_bootstrap_impl(THD *thd)
+{
+ MYSQL_FILE *file= bootstrap_file;
+ DBUG_ENTER("handle_bootstrap");
+
+#ifndef EMBEDDED_LIBRARY
+ pthread_detach_this_thread();
+ thd->thread_stack= (char*) &thd;
+#endif /* EMBEDDED_LIBRARY */
+
+ thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME));
+ thd->security_ctx->priv_user[0]= thd->security_ctx->priv_host[0]=
+ thd->security_ctx->priv_role[0]= 0;
+ /*
+ Make the "client" handle multiple results. This is necessary
+ to enable stored procedures with SELECTs and Dynamic SQL
+ in init-file.
+ */
+ thd->client_capabilities|= CLIENT_MULTI_RESULTS;
+
+ thd->init_for_queries();
+
+ for ( ; ; )
+ {
+ char buffer[MAX_BOOTSTRAP_QUERY_SIZE] = "";
+ int rc, length;
+ char *query;
+ int error= 0;
+
+ rc= read_bootstrap_query(buffer, &length, file, fgets_fn, &error);
+
+ if (rc == READ_BOOTSTRAP_EOF)
+ break;
+ /*
+ Check for bootstrap file errors. SQL syntax errors will be
+ caught below.
+ */
+ if (rc != READ_BOOTSTRAP_SUCCESS)
+ {
+ /*
+ mysql_parse() may have set a successful error status for the previous
+ query. We must clear the error status to report the bootstrap error.
+ */
+ thd->get_stmt_da()->reset_diagnostics_area();
+
+ /* Get the nearest query text for reference. */
+ char *err_ptr= buffer + (length <= MAX_BOOTSTRAP_ERROR_LEN ?
+ 0 : (length - MAX_BOOTSTRAP_ERROR_LEN));
+ switch (rc)
+ {
+ case READ_BOOTSTRAP_ERROR:
+ my_printf_error(ER_UNKNOWN_ERROR, "Bootstrap file error, return code (%d). "
+ "Nearest query: '%s'", MYF(0), error, err_ptr);
+ break;
+
+ case READ_BOOTSTRAP_QUERY_SIZE:
+ my_printf_error(ER_UNKNOWN_ERROR, "Boostrap file error. Query size "
+ "exceeded %d bytes near '%s'.", MYF(0),
+ MAX_BOOTSTRAP_LINE_SIZE, err_ptr);
+ break;
+
+ default:
+ DBUG_ASSERT(false);
+ break;
+ }
+
+ thd->protocol->end_statement();
+ bootstrap_error= 1;
+ break;
+ }
+
+ query= (char *) thd->memdup_w_gap(buffer, length + 1,
+ thd->db_length + 1 +
+ QUERY_CACHE_DB_LENGTH_SIZE +
+ QUERY_CACHE_FLAGS_SIZE);
+ size_t db_len= 0;
+ memcpy(query + length + 1, (char *) &db_len, sizeof(size_t));
+ thd->set_query_and_id(query, length, thd->charset(), next_query_id());
+ int2store(query + length + 1, 0); // No db in bootstrap
+ DBUG_PRINT("query",("%-.4096s",thd->query()));
+#if defined(ENABLED_PROFILING)
+ thd->profiling.start_new_query();
+ thd->profiling.set_query_source(thd->query(), length);
+#endif
+
+ /*
+ We don't need to obtain LOCK_thread_count here because in bootstrap
+ mode we have only one thread.
+ */
+ thd->set_time();
+ Parser_state parser_state;
+ if (parser_state.init(thd, thd->query(), length))
+ {
+ thd->protocol->end_statement();
+ bootstrap_error= 1;
+ break;
+ }
+
+ mysql_parse(thd, thd->query(), length, &parser_state);
+
+ bootstrap_error= thd->is_error();
+ thd->protocol->end_statement();
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.finish_current_query();
+#endif
+ delete_explain_query(thd->lex);
+
+ if (bootstrap_error)
+ break;
+
+ free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
+ free_root(&thd->transaction.mem_root,MYF(MY_KEEP_PREALLOC));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Execute commands from bootstrap_file.
+
+ Used when creating the initial grant tables.
+*/
+
+pthread_handler_t handle_bootstrap(void *arg)
+{
+ THD *thd=(THD*) arg;
+
+ mysql_thread_set_psi_id(thd->thread_id);
+
+ do_handle_bootstrap(thd);
+ return 0;
+}
+
+void do_handle_bootstrap(THD *thd)
+{
+ /* The following must be called before DBUG_ENTER */
+ thd->thread_stack= (char*) &thd;
+ if (my_thread_init() || thd->store_globals())
+ {
+#ifndef EMBEDDED_LIBRARY
+ close_connection(thd, ER_OUT_OF_RESOURCES);
+#endif
+ thd->fatal_error();
+ goto end;
+ }
+
+ handle_bootstrap_impl(thd);
+
+end:
+ delete thd;
+
+#ifndef EMBEDDED_LIBRARY
+ thread_safe_decrement32(&thread_count, &thread_count_lock);
+ in_bootstrap= FALSE;
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ mysql_cond_broadcast(&COND_thread_count);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ my_thread_end();
+ pthread_exit(0);
+#endif
+
+ return;
+}
+
+
+/* This works because items are allocated with sql_alloc() */
+
+void free_items(Item *item)
+{
+ Item *next;
+ DBUG_ENTER("free_items");
+ for (; item ; item=next)
+ {
+ next=item->next;
+ item->delete_self();
+ }
+ DBUG_VOID_RETURN;
+}
+
+/**
+ This works because items are allocated with sql_alloc().
+ @note The function also handles null pointers (empty list).
+*/
+void cleanup_items(Item *item)
+{
+ DBUG_ENTER("cleanup_items");
+ for (; item ; item=item->next)
+ item->cleanup();
+ DBUG_VOID_RETURN;
+}
+
+#ifndef EMBEDDED_LIBRARY
+
+/**
+ Read one command from connection and execute it (query or simple command).
+ This function is called in loop from thread function.
+
+ For profiling to work, it must never be called recursively.
+
+ @retval
+ 0 success
+ @retval
+ 1 request of thread shutdown (see dispatch_command() description)
+*/
+
+bool do_command(THD *thd)
+{
+ bool return_value;
+ char *packet= 0;
+ ulong packet_length;
+ NET *net= &thd->net;
+ enum enum_server_command command;
+ DBUG_ENTER("do_command");
+
+ /*
+ indicator of uninitialized lex => normal flow of errors handling
+ (see my_message_sql)
+ */
+ thd->lex->current_select= 0;
+
+ /*
+ This thread will do a blocking read from the client which
+ will be interrupted when the next command is received from
+ the client, the connection is closed or "net_wait_timeout"
+ number of seconds has passed.
+ */
+ if(!thd->skip_wait_timeout)
+ my_net_set_read_timeout(net, thd->variables.net_wait_timeout);
+
+
+ /*
+ XXX: this code is here only to clear possible errors of init_connect.
+ Consider moving to init_connect() instead.
+ */
+ thd->clear_error(); // Clear error message
+ thd->get_stmt_da()->reset_diagnostics_area();
+
+ net_new_transaction(net);
+
+ /* Save for user statistics */
+ thd->start_bytes_received= thd->status_var.bytes_received;
+
+ /*
+ Synchronization point for testing of KILL_CONNECTION.
+ This sync point can wait here, to simulate slow code execution
+ between the last test of thd->killed and blocking in read().
+
+ The goal of this test is to verify that a connection does not
+ hang, if it is killed at this point of execution.
+ (Bug#37780 - main.kill fails randomly)
+
+ Note that the sync point wait itself will be terminated by a
+ kill. In this case it consumes a condition broadcast, but does
+ not change anything else. The consumed broadcast should not
+ matter here, because the read/recv() below doesn't use it.
+ */
+ DEBUG_SYNC(thd, "before_do_command_net_read");
+
+ packet_length= my_net_read_packet(net, 1);
+
+ if (packet_length == packet_error)
+ {
+ DBUG_PRINT("info",("Got error %d reading command from socket %s",
+ net->error,
+ vio_description(net->vio)));
+
+ /* Instrument this broken statement as "statement/com/error" */
+ thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
+ com_statement_info[COM_END].
+ m_key);
+
+
+ /* Check if we can continue without closing the connection */
+
+ /* The error must be set. */
+ DBUG_ASSERT(thd->is_error());
+ thd->protocol->end_statement();
+
+ /* Mark the statement completed. */
+ MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
+ thd->m_statement_psi= NULL;
+ thd->m_digest= NULL;
+
+ if (net->error != 3)
+ {
+ return_value= TRUE; // We have to close it.
+ goto out;
+ }
+
+ net->error= 0;
+ return_value= FALSE;
+ goto out;
+ }
+
+ packet= (char*) net->read_pos;
+ /*
+ 'packet_length' contains length of data, as it was stored in packet
+ header. In case of malformed header, my_net_read returns zero.
+ If packet_length is not zero, my_net_read ensures that the returned
+ number of bytes was actually read from network.
+ There is also an extra safety measure in my_net_read:
+ it sets packet[packet_length]= 0, but only for non-zero packets.
+ */
+ if (packet_length == 0) /* safety */
+ {
+ /* Initialize with COM_SLEEP packet */
+ packet[0]= (uchar) COM_SLEEP;
+ packet_length= 1;
+ }
+ /* Do not rely on my_net_read, extra safety against programming errors. */
+ packet[packet_length]= '\0'; /* safety */
+
+ command= (enum enum_server_command) (uchar) packet[0];
+
+ if (command >= COM_END)
+ command= COM_END; // Wrong command
+
+ DBUG_PRINT("info",("Command on %s = %d (%s)",
+ vio_description(net->vio), command,
+ command_name[command].str));
+
+ /* Restore read timeout value */
+ my_net_set_read_timeout(net, thd->variables.net_read_timeout);
+
+ DBUG_ASSERT(packet_length);
+ DBUG_ASSERT(!thd->apc_target.is_enabled());
+ return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
+ DBUG_ASSERT(!thd->apc_target.is_enabled());
+
+out:
+ /* The statement instrumentation must be closed in all cases. */
+ DBUG_ASSERT(thd->m_digest == NULL);
+ DBUG_ASSERT(thd->m_statement_psi == NULL);
+ DBUG_RETURN(return_value);
+}
+#endif /* EMBEDDED_LIBRARY */
+
+/**
+ @brief Determine if an attempt to update a non-temporary table while the
+ read-only option was enabled has been made.
+
+ This is a helper function to mysql_execute_command.
+
+ @note SQLCOM_MULTI_UPDATE is an exception and delt with elsewhere.
+
+ @see mysql_execute_command
+ @returns Status code
+ @retval TRUE The statement should be denied.
+ @retval FALSE The statement isn't updating any relevant tables.
+*/
+
+static my_bool deny_updates_if_read_only_option(THD *thd,
+ TABLE_LIST *all_tables)
+{
+ DBUG_ENTER("deny_updates_if_read_only_option");
+
+ if (!opt_readonly)
+ DBUG_RETURN(FALSE);
+
+ LEX *lex= thd->lex;
+
+ const my_bool user_is_super=
+ ((ulong)(thd->security_ctx->master_access & SUPER_ACL) ==
+ (ulong)SUPER_ACL);
+
+ if (user_is_super)
+ DBUG_RETURN(FALSE);
+
+ if (!(sql_command_flags[lex->sql_command] & CF_CHANGES_DATA))
+ DBUG_RETURN(FALSE);
+
+ /* Multi update is an exception and is dealt with later. */
+ if (lex->sql_command == SQLCOM_UPDATE_MULTI)
+ DBUG_RETURN(FALSE);
+
+ const my_bool create_temp_tables=
+ (lex->sql_command == SQLCOM_CREATE_TABLE) &&
+ lex->create_info.tmp_table();
+
+ const my_bool drop_temp_tables=
+ (lex->sql_command == SQLCOM_DROP_TABLE) &&
+ lex->drop_temporary;
+
+ const my_bool update_real_tables=
+ some_non_temp_table_to_be_updated(thd, all_tables) &&
+ !(create_temp_tables || drop_temp_tables);
+
+
+ const my_bool create_or_drop_databases=
+ (lex->sql_command == SQLCOM_CREATE_DB) ||
+ (lex->sql_command == SQLCOM_DROP_DB);
+
+ if (update_real_tables || create_or_drop_databases)
+ {
+ /*
+ An attempt was made to modify one or more non-temporary tables.
+ */
+ DBUG_RETURN(TRUE);
+ }
+
+
+ /* Assuming that only temporary tables are modified. */
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Perform one connection-level (COM_XXXX) command.
+
+ @param command type of command to perform
+ @param thd connection handle
+ @param packet data for the command, packet is always null-terminated
+ @param packet_length length of packet + 1 (to show that data is
+ null-terminated) except for COM_SLEEP, where it
+ can be zero.
+
+ @todo
+ set thd->lex->sql_command to SQLCOM_END here.
+ @todo
+ The following has to be changed to an 8 byte integer
+
+ @retval
+ 0 ok
+ @retval
+ 1 request of thread shutdown, i. e. if command is
+ COM_QUIT/COM_SHUTDOWN
+*/
+bool dispatch_command(enum enum_server_command command, THD *thd,
+ char* packet, uint packet_length)
+{
+ NET *net= &thd->net;
+ bool error= 0;
+ DBUG_ENTER("dispatch_command");
+ DBUG_PRINT("info", ("command: %d", command));
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.start_new_query();
+#endif
+ MYSQL_COMMAND_START(thd->thread_id, command,
+ &thd->security_ctx->priv_user[0],
+ (char *) thd->security_ctx->host_or_ip);
+
+ DBUG_EXECUTE_IF("crash_dispatch_command_before",
+ { DBUG_PRINT("crash_dispatch_command_before", ("now"));
+ DBUG_ABORT(); });
+
+ /* Performance Schema Interface instrumentation, begin */
+ thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
+ com_statement_info[command].
+ m_key);
+ thd->set_command(command);
+
+ /*
+ Commands which always take a long time are logged into
+ the slow log only if opt_log_slow_admin_statements is set.
+ */
+ thd->enable_slow_log= TRUE;
+ thd->query_plan_flags= QPLAN_INIT;
+ thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */
+ thd->reset_kill_query();
+
+ DEBUG_SYNC(thd,"dispatch_command_before_set_time");
+
+ thd->set_time();
+ if (!(server_command_flags[command] & CF_SKIP_QUERY_ID))
+ thd->set_query_id(next_query_id());
+ else
+ {
+ /*
+ ping, get statistics or similar stateless command.
+ No reason to increase query id here.
+ */
+ thd->set_query_id(get_query_id());
+ }
+ inc_thread_running();
+
+ if (!(server_command_flags[command] & CF_SKIP_QUESTIONS))
+ statistic_increment(thd->status_var.questions, &LOCK_status);
+
+ /* Copy data for user stats */
+ if ((thd->userstat_running= opt_userstat_running))
+ {
+ thd->start_cpu_time= my_getcputime();
+ memcpy(&thd->org_status_var, &thd->status_var, sizeof(thd->status_var));
+ thd->select_commands= thd->update_commands= thd->other_commands= 0;
+ }
+
+ /**
+ Clear the set of flags that are expected to be cleared at the
+ beginning of each command.
+ */
+ thd->server_status&= ~SERVER_STATUS_CLEAR_SET;
+ switch (command) {
+ case COM_INIT_DB:
+ {
+ LEX_STRING tmp;
+ status_var_increment(thd->status_var.com_stat[SQLCOM_CHANGE_DB]);
+ thd->convert_string(&tmp, system_charset_info,
+ packet, packet_length, thd->charset());
+ if (!mysql_change_db(thd, &tmp, FALSE))
+ {
+ general_log_write(thd, command, thd->db, thd->db_length);
+ my_ok(thd);
+ }
+ break;
+ }
+#ifdef HAVE_REPLICATION
+ case COM_REGISTER_SLAVE:
+ {
+ if (!register_slave(thd, (uchar*)packet, packet_length))
+ my_ok(thd);
+ break;
+ }
+#endif
+ case COM_CHANGE_USER:
+ {
+ int auth_rc;
+ status_var_increment(thd->status_var.com_other);
+
+ thd->change_user();
+ thd->clear_error(); // if errors from rollback
+
+ /* acl_authenticate() takes the data from net->read_pos */
+ net->read_pos= (uchar*)packet;
+
+ uint save_db_length= thd->db_length;
+ char *save_db= thd->db;
+ USER_CONN *save_user_connect= thd->user_connect;
+ Security_context save_security_ctx= *thd->security_ctx;
+ CHARSET_INFO *save_character_set_client=
+ thd->variables.character_set_client;
+ CHARSET_INFO *save_collation_connection=
+ thd->variables.collation_connection;
+ CHARSET_INFO *save_character_set_results=
+ thd->variables.character_set_results;
+
+ /* Ensure we don't free security_ctx->user in case we have to revert */
+ thd->security_ctx->user= 0;
+ thd->user_connect= 0;
+
+ /*
+ to limit COM_CHANGE_USER ability to brute-force passwords,
+ we only allow three unsuccessful COM_CHANGE_USER per connection.
+ */
+ if (thd->failed_com_change_user >= 3)
+ {
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ auth_rc= 1;
+ }
+ else
+ auth_rc= acl_authenticate(thd, packet_length);
+
+ mysql_audit_notify_connection_change_user(thd);
+ if (auth_rc)
+ {
+ /* Free user if allocated by acl_authenticate */
+ my_free(thd->security_ctx->user);
+ *thd->security_ctx= save_security_ctx;
+ if (thd->user_connect)
+ decrease_user_connections(thd->user_connect);
+ thd->user_connect= save_user_connect;
+ thd->reset_db(save_db, save_db_length);
+ thd->variables.character_set_client= save_character_set_client;
+ thd->variables.collation_connection= save_collation_connection;
+ thd->variables.character_set_results= save_character_set_results;
+ thd->update_charset();
+ thd->failed_com_change_user++;
+ my_sleep(1000000);
+ }
+ else
+ {
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* we've authenticated new user */
+ if (save_user_connect)
+ decrease_user_connections(save_user_connect);
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+ my_free(save_db);
+ my_free(save_security_ctx.user);
+ }
+ break;
+ }
+ case COM_STMT_EXECUTE:
+ {
+ mysqld_stmt_execute(thd, packet, packet_length);
+ break;
+ }
+ case COM_STMT_FETCH:
+ {
+ mysqld_stmt_fetch(thd, packet, packet_length);
+ break;
+ }
+ case COM_STMT_SEND_LONG_DATA:
+ {
+ mysql_stmt_get_longdata(thd, packet, packet_length);
+ break;
+ }
+ case COM_STMT_PREPARE:
+ {
+ mysqld_stmt_prepare(thd, packet, packet_length);
+ break;
+ }
+ case COM_STMT_CLOSE:
+ {
+ mysqld_stmt_close(thd, packet);
+ break;
+ }
+ case COM_STMT_RESET:
+ {
+ mysqld_stmt_reset(thd, packet);
+ break;
+ }
+ case COM_QUERY:
+ {
+ DBUG_ASSERT(thd->m_digest == NULL);
+ thd->m_digest= & thd->m_digest_state;
+ thd->m_digest->reset(thd->m_token_array, max_digest_length);
+
+ if (alloc_query(thd, packet, packet_length))
+ break; // fatal error is set
+ MYSQL_QUERY_START(thd->query(), thd->thread_id,
+ (char *) (thd->db ? thd->db : ""),
+ &thd->security_ctx->priv_user[0],
+ (char *) thd->security_ctx->host_or_ip);
+ char *packet_end= thd->query() + thd->query_length();
+ general_log_write(thd, command, thd->query(), thd->query_length());
+ DBUG_PRINT("query",("%-.4096s",thd->query()));
+#if defined(ENABLED_PROFILING)
+ thd->profiling.set_query_source(thd->query(), thd->query_length());
+#endif
+ MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, thd->query(),
+ thd->query_length());
+
+ Parser_state parser_state;
+ if (parser_state.init(thd, thd->query(), thd->query_length()))
+ break;
+
+ mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);
+
+ while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) &&
+ ! thd->is_error())
+ {
+ /*
+ Multiple queries exist, execute them individually
+ */
+ char *beginning_of_next_stmt= (char*) parser_state.m_lip.found_semicolon;
+
+#ifdef WITH_ARIA_STORAGE_ENGINE
+ ha_maria::implicit_commit(thd, FALSE);
+#endif
+
+ /* Finalize server status flags after executing a statement. */
+ thd->update_server_status();
+ thd->protocol->end_statement();
+ query_cache_end_of_result(thd);
+
+ mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS,
+ thd->get_stmt_da()->is_error()
+ ? thd->get_stmt_da()->sql_errno()
+ : 0,
+ command_name[command].str);
+
+ ulong length= (ulong)(packet_end - beginning_of_next_stmt);
+
+ log_slow_statement(thd);
+ DBUG_ASSERT(!thd->apc_target.is_enabled());
+
+ /* Remove garbage at start of query */
+ while (length > 0 && my_isspace(thd->charset(), *beginning_of_next_stmt))
+ {
+ beginning_of_next_stmt++;
+ length--;
+ }
+
+ /* PSI end */
+ MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
+ thd->m_statement_psi= NULL;
+ thd->m_digest= NULL;
+
+ /* DTRACE end */
+ if (MYSQL_QUERY_DONE_ENABLED())
+ {
+ MYSQL_QUERY_DONE(thd->is_error());
+ }
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.finish_current_query();
+ thd->profiling.start_new_query("continuing");
+ thd->profiling.set_query_source(beginning_of_next_stmt, length);
+#endif
+
+ /* DTRACE begin */
+ MYSQL_QUERY_START(beginning_of_next_stmt, thd->thread_id,
+ (char *) (thd->db ? thd->db : ""),
+ &thd->security_ctx->priv_user[0],
+ (char *) thd->security_ctx->host_or_ip);
+
+ /* PSI begin */
+ thd->m_digest= & thd->m_digest_state;
+
+ thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state,
+ com_statement_info[command].m_key,
+ thd->db, thd->db_length,
+ thd->charset());
+ THD_STAGE_INFO(thd, stage_init);
+ MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, beginning_of_next_stmt,
+ length);
+
+ thd->set_query_and_id(beginning_of_next_stmt, length,
+ thd->charset(), next_query_id());
+ /*
+ Count each statement from the client.
+ */
+ statistic_increment(thd->status_var.questions, &LOCK_status);
+ thd->set_time(); /* Reset the query start time. */
+ parser_state.reset(beginning_of_next_stmt, length);
+ /* TODO: set thd->lex->sql_command to SQLCOM_END here */
+ mysql_parse(thd, beginning_of_next_stmt, length, &parser_state);
+ }
+
+ DBUG_PRINT("info",("query ready"));
+ break;
+ }
+ case COM_FIELD_LIST: // This isn't actually needed
+#ifdef DONT_ALLOW_SHOW_COMMANDS
+ my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
+ MYF(0)); /* purecov: inspected */
+ break;
+#else
+ {
+ char *fields, *packet_end= packet + packet_length, *arg_end;
+ /* Locked closure of all tables */
+ TABLE_LIST table_list;
+ LEX_STRING table_name;
+ LEX_STRING db;
+ /*
+ SHOW statements should not add the used tables to the list of tables
+ used in a transaction.
+ */
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+
+ status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]);
+ if (thd->copy_db_to(&db.str, &db.length))
+ break;
+ /*
+ We have name + wildcard in packet, separated by endzero
+ (The packet is guaranteed to end with an end zero)
+ */
+ arg_end= strend(packet);
+ uint arg_length= arg_end - packet;
+
+ /* Check given table name length. */
+ if (packet_length - arg_length > NAME_LEN + 1 || arg_length > SAFE_NAME_LEN)
+ {
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ break;
+ }
+ thd->convert_string(&table_name, system_charset_info,
+ packet, arg_length, thd->charset());
+ if (check_table_name(table_name.str, table_name.length, FALSE))
+ {
+ /* this is OK due to convert_string() null-terminating the string */
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str);
+ break;
+ }
+ packet= arg_end + 1;
+ mysql_reset_thd_for_next_command(thd);
+ lex_start(thd);
+ /* Must be before we init the table list. */
+ if (lower_case_table_names)
+ {
+ table_name.length= my_casedn_str(files_charset_info, table_name.str);
+ db.length= my_casedn_str(files_charset_info, db.str);
+ }
+ table_list.init_one_table(db.str, db.length, table_name.str,
+ table_name.length, table_name.str, TL_READ);
+ /*
+ Init TABLE_LIST members necessary when the undelrying
+ table is view.
+ */
+ table_list.select_lex= &(thd->lex->select_lex);
+ thd->lex->
+ select_lex.table_list.link_in_list(&table_list,
+ &table_list.next_local);
+ thd->lex->add_to_query_tables(&table_list);
+
+ if (is_infoschema_db(table_list.db, table_list.db_length))
+ {
+ ST_SCHEMA_TABLE *schema_table= find_schema_table(thd, table_list.alias);
+ if (schema_table)
+ table_list.schema_table= schema_table;
+ }
+
+ uint query_length= (uint) (packet_end - packet); // Don't count end \0
+ if (!(fields= (char *) thd->memdup(packet, query_length + 1)))
+ break;
+ thd->set_query(fields, query_length);
+ general_log_print(thd, command, "%s %s", table_list.table_name, fields);
+
+ if (open_temporary_tables(thd, &table_list))
+ break;
+
+ if (check_table_access(thd, SELECT_ACL, &table_list,
+ TRUE, UINT_MAX, FALSE))
+ break;
+ /*
+ Turn on an optimization relevant if the underlying table
+ is a view: do not fill derived tables.
+ */
+ thd->lex->sql_command= SQLCOM_SHOW_FIELDS;
+
+ mysqld_list_fields(thd,&table_list,fields);
+ thd->lex->unit.cleanup();
+ /* No need to rollback statement transaction, it's not started. */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ if (thd->transaction_rollback_request)
+ {
+ /*
+ Transaction rollback was requested since MDL deadlock was
+ discovered while trying to open tables. Rollback transaction
+ in all storage engines including binary log and release all
+ locks.
+ */
+ trans_rollback_implicit(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+
+ thd->cleanup_after_query();
+ break;
+ }
+#endif
+ case COM_QUIT:
+ /* We don't calculate statistics for this command */
+ general_log_print(thd, command, NullS);
+ net->error=0; // Don't give 'abort' message
+ thd->get_stmt_da()->disable_status(); // Don't send anything back
+ error=TRUE; // End server
+ break;
+#ifndef EMBEDDED_LIBRARY
+ case COM_BINLOG_DUMP:
+ {
+ ulong pos;
+ ushort flags;
+ uint32 slave_server_id;
+
+ status_var_increment(thd->status_var.com_other);
+
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+ thd->query_plan_flags|= QPLAN_ADMIN;
+ if (check_global_access(thd, REPL_SLAVE_ACL))
+ break;
+
+ /* TODO: The following has to be changed to an 8 byte integer */
+ pos = uint4korr(packet);
+ flags = uint2korr(packet + 4);
+ thd->variables.server_id=0; /* avoid suicide */
+ if ((slave_server_id= uint4korr(packet+6))) // mysqlbinlog.server_id==0
+ kill_zombie_dump_threads(slave_server_id);
+ thd->variables.server_id = slave_server_id;
+
+ general_log_print(thd, command, "Log: '%s' Pos: %ld", packet+10,
+ (long) pos);
+ mysql_binlog_send(thd, thd->strdup(packet + 10), (my_off_t) pos, flags);
+ unregister_slave(thd,1,1);
+ /* fake COM_QUIT -- if we get here, the thread needs to terminate */
+ error = TRUE;
+ break;
+ }
+#endif
+ case COM_REFRESH:
+ {
+ int not_used;
+
+ /*
+ Initialize thd->lex since it's used in many base functions, such as
+ open_tables(). Otherwise, it remains unitialized and may cause crash
+ during execution of COM_REFRESH.
+ */
+ lex_start(thd);
+
+ status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]);
+ ulonglong options= (ulonglong) (uchar) packet[0];
+ if (trans_commit_implicit(thd))
+ break;
+ thd->mdl_context.release_transactional_locks();
+ if (check_global_access(thd,RELOAD_ACL))
+ break;
+ general_log_print(thd, command, NullS);
+#ifndef DBUG_OFF
+ bool debug_simulate= FALSE;
+ DBUG_EXECUTE_IF("simulate_detached_thread_refresh", debug_simulate= TRUE;);
+ if (debug_simulate)
+ {
+ /*
+ Simulate a reload without a attached thread session.
+ Provides a environment similar to that of when the
+ server receives a SIGHUP signal and reloads caches
+ and flushes tables.
+ */
+ bool res;
+ set_current_thd(0);
+ res= reload_acl_and_cache(NULL, options | REFRESH_FAST,
+ NULL, &not_used);
+ set_current_thd(thd);
+ if (res)
+ break;
+ }
+ else
+#endif
+ {
+ thd->lex->relay_log_connection_name.str= (char*) "";
+ thd->lex->relay_log_connection_name.length= 0;
+ if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, &not_used))
+ break;
+ }
+ if (trans_commit_implicit(thd))
+ break;
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ my_ok(thd);
+ break;
+ }
+#ifndef EMBEDDED_LIBRARY
+ case COM_SHUTDOWN:
+ {
+ status_var_increment(thd->status_var.com_other);
+ if (check_global_access(thd,SHUTDOWN_ACL))
+ break; /* purecov: inspected */
+ /*
+ If the client is < 4.1.3, it is going to send us no argument; then
+ packet_length is 0, packet[0] is the end 0 of the packet. Note that
+ SHUTDOWN_DEFAULT is 0. If client is >= 4.1.3, the shutdown level is in
+ packet[0].
+ */
+ enum mysql_enum_shutdown_level level;
+ level= (enum mysql_enum_shutdown_level) (uchar) packet[0];
+ if (level == SHUTDOWN_DEFAULT)
+ level= SHUTDOWN_WAIT_ALL_BUFFERS; // soon default will be configurable
+ else if (level != SHUTDOWN_WAIT_ALL_BUFFERS)
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "this shutdown level");
+ break;
+ }
+ DBUG_PRINT("quit",("Got shutdown command for level %u", level));
+ general_log_print(thd, command, NullS);
+ my_eof(thd);
+ kill_mysql();
+ error=TRUE;
+ break;
+ }
+#endif
+ case COM_STATISTICS:
+ {
+ STATUS_VAR *current_global_status_var; // Big; Don't allocate on stack
+ ulong uptime;
+ uint length __attribute__((unused));
+ ulonglong queries_per_second1000;
+ char buff[250];
+ uint buff_len= sizeof(buff);
+
+ if (!(current_global_status_var= (STATUS_VAR*)
+ thd->alloc(sizeof(STATUS_VAR))))
+ break;
+ general_log_print(thd, command, NullS);
+ status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_STATUS]);
+ calc_sum_of_all_status(current_global_status_var);
+ if (!(uptime= (ulong) (thd->start_time - server_start_time)))
+ queries_per_second1000= 0;
+ else
+ queries_per_second1000= thd->query_id * 1000 / uptime;
+
+ length= my_snprintf(buff, buff_len - 1,
+ "Uptime: %lu Threads: %d Questions: %lu "
+ "Slow queries: %lu Opens: %lu Flush tables: %lu "
+ "Open tables: %u Queries per second avg: %u.%03u",
+ uptime,
+ (int) thread_count, (ulong) thd->query_id,
+ current_global_status_var->long_query_count,
+ current_global_status_var->opened_tables,
+ tdc_refresh_version(),
+ tc_records(),
+ (uint) (queries_per_second1000 / 1000),
+ (uint) (queries_per_second1000 % 1000));
+#ifdef EMBEDDED_LIBRARY
+ /* Store the buffer in permanent memory */
+ my_ok(thd, 0, 0, buff);
+#else
+ (void) my_net_write(net, (uchar*) buff, length);
+ (void) net_flush(net);
+ thd->get_stmt_da()->disable_status();
+#endif
+ break;
+ }
+ case COM_PING:
+ status_var_increment(thd->status_var.com_other);
+ my_ok(thd); // Tell client we are alive
+ break;
+ case COM_PROCESS_INFO:
+ status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_PROCESSLIST]);
+ if (!thd->security_ctx->priv_user[0] &&
+ check_global_access(thd, PROCESS_ACL))
+ break;
+ general_log_print(thd, command, NullS);
+ mysqld_list_processes(thd,
+ thd->security_ctx->master_access & PROCESS_ACL ?
+ NullS : thd->security_ctx->priv_user, 0);
+ break;
+ case COM_PROCESS_KILL:
+ {
+ status_var_increment(thd->status_var.com_stat[SQLCOM_KILL]);
+ ulong id=(ulong) uint4korr(packet);
+ sql_kill(thd, id, KILL_CONNECTION_HARD, KILL_TYPE_ID);
+ break;
+ }
+ case COM_SET_OPTION:
+ {
+ status_var_increment(thd->status_var.com_stat[SQLCOM_SET_OPTION]);
+ uint opt_command= uint2korr(packet);
+
+ switch (opt_command) {
+ case (int) MYSQL_OPTION_MULTI_STATEMENTS_ON:
+ thd->client_capabilities|= CLIENT_MULTI_STATEMENTS;
+ my_eof(thd);
+ break;
+ case (int) MYSQL_OPTION_MULTI_STATEMENTS_OFF:
+ thd->client_capabilities&= ~CLIENT_MULTI_STATEMENTS;
+ my_eof(thd);
+ break;
+ default:
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ break;
+ }
+ break;
+ }
+ case COM_DEBUG:
+ status_var_increment(thd->status_var.com_other);
+ if (check_global_access(thd, SUPER_ACL))
+ break; /* purecov: inspected */
+ mysql_print_status();
+ general_log_print(thd, command, NullS);
+ my_eof(thd);
+ break;
+ case COM_SLEEP:
+ case COM_CONNECT: // Impossible here
+ case COM_TIME: // Impossible from client
+ case COM_DELAYED_INSERT:
+ case COM_END:
+ default:
+ my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
+ break;
+ }
+ DBUG_ASSERT(thd->derived_tables == NULL &&
+ (thd->open_tables == NULL ||
+ (thd->locked_tables_mode == LTM_LOCK_TABLES)));
+
+ thd_proc_info(thd, "updating status");
+ /* Finalize server status flags after executing a command. */
+ thd->update_server_status();
+ thd->protocol->end_statement();
+ query_cache_end_of_result(thd);
+
+ if (!thd->is_error() && !thd->killed_errno())
+ mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_RESULT, 0, 0);
+
+ mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS,
+ thd->get_stmt_da()->is_error() ?
+ thd->get_stmt_da()->sql_errno() : 0,
+ command_name[command].str);
+
+ thd->update_all_stats();
+
+ log_slow_statement(thd);
+
+ THD_STAGE_INFO(thd, stage_cleaning_up);
+ thd->reset_query();
+ thd->set_examined_row_count(0); // For processlist
+ thd->set_command(COM_SLEEP);
+
+ /* Performance Schema Interface instrumentation, end */
+ MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
+ thd->m_statement_psi= NULL;
+ thd->m_digest= NULL;
+
+ thd->set_time();
+ dec_thread_running();
+ thd->packet.shrink(thd->variables.net_buffer_length); // Reclaim some memory
+ free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.finish_current_query();
+#endif
+ if (MYSQL_QUERY_DONE_ENABLED() || MYSQL_COMMAND_DONE_ENABLED())
+ {
+ int res __attribute__((unused));
+ res= (int) thd->is_error();
+ if (command == COM_QUERY)
+ {
+ MYSQL_QUERY_DONE(res);
+ }
+ MYSQL_COMMAND_DONE(res);
+ }
+ DEBUG_SYNC(thd,"dispatch_command_end");
+
+ /* Check that some variables are reset properly */
+ DBUG_ASSERT(thd->abort_on_warning == 0);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ @note
+ This function must call delete_explain_query().
+*/
+void log_slow_statement(THD *thd)
+{
+ DBUG_ENTER("log_slow_statement");
+
+
+ /*
+ The following should never be true with our current code base,
+ but better to keep this here so we don't accidently try to log a
+ statement in a trigger or stored function
+ */
+ if (unlikely(thd->in_sub_stmt))
+ goto end; // Don't set time for sub stmt
+
+
+ /* Follow the slow log filter configuration. */
+ if (!thd->enable_slow_log ||
+ (thd->variables.log_slow_filter
+ && !(thd->variables.log_slow_filter & thd->query_plan_flags)))
+ {
+ goto end;
+ }
+
+ if (((thd->server_status & SERVER_QUERY_WAS_SLOW) ||
+ ((thd->server_status &
+ (SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED)) &&
+ opt_log_queries_not_using_indexes &&
+ !(sql_command_flags[thd->lex->sql_command] & CF_STATUS_COMMAND))) &&
+ thd->get_examined_row_count() >= thd->variables.min_examined_row_limit)
+ {
+ thd->status_var.long_query_count++;
+ /*
+ If rate limiting of slow log writes is enabled, decide whether to log
+ this query to the log or not.
+ */
+ if (thd->variables.log_slow_rate_limit > 1 &&
+ (global_query_id % thd->variables.log_slow_rate_limit) != 0)
+ goto end;
+
+ THD_STAGE_INFO(thd, stage_logging_slow_query);
+ slow_log_print(thd, thd->query(), thd->query_length(),
+ thd->utime_after_query);
+ }
+
+end:
+ delete_explain_query(thd->lex);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Create a TABLE_LIST object for an INFORMATION_SCHEMA table.
+
+ This function is used in the parser to convert a SHOW or DESCRIBE
+ table_name command to a SELECT from INFORMATION_SCHEMA.
+ It prepares a SELECT_LEX and a TABLE_LIST object to represent the
+ given command as a SELECT parse tree.
+
+ @param thd thread handle
+ @param lex current lex
+ @param table_ident table alias if it's used
+ @param schema_table_idx the type of the INFORMATION_SCHEMA table to be
+ created
+
+ @note
+ Due to the way this function works with memory and LEX it cannot
+ be used outside the parser (parse tree transformations outside
+ the parser break PS and SP).
+
+ @retval
+ 0 success
+ @retval
+ 1 out of memory or SHOW commands are not allowed
+ in this version of the server.
+*/
+
+int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
+ enum enum_schema_tables schema_table_idx)
+{
+ SELECT_LEX *schema_select_lex= NULL;
+ DBUG_ENTER("prepare_schema_table");
+
+ switch (schema_table_idx) {
+ case SCH_SCHEMATA:
+#if defined(DONT_ALLOW_SHOW_COMMANDS)
+ my_message(ER_NOT_ALLOWED_COMMAND,
+ ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(1);
+#else
+ break;
+#endif
+
+ case SCH_TABLE_NAMES:
+ case SCH_TABLES:
+ case SCH_VIEWS:
+ case SCH_TRIGGERS:
+ case SCH_EVENTS:
+#ifdef DONT_ALLOW_SHOW_COMMANDS
+ my_message(ER_NOT_ALLOWED_COMMAND,
+ ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(1);
+#else
+ {
+ LEX_STRING db;
+ size_t dummy;
+ if (lex->select_lex.db == NULL &&
+ lex->copy_db_to(&lex->select_lex.db, &dummy))
+ {
+ DBUG_RETURN(1);
+ }
+ schema_select_lex= new SELECT_LEX();
+ db.str= schema_select_lex->db= lex->select_lex.db;
+ schema_select_lex->table_list.first= NULL;
+ db.length= strlen(db.str);
+
+ if (check_db_name(&db))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), db.str);
+ DBUG_RETURN(1);
+ }
+ break;
+ }
+#endif
+ case SCH_COLUMNS:
+ case SCH_STATISTICS:
+ {
+#ifdef DONT_ALLOW_SHOW_COMMANDS
+ my_message(ER_NOT_ALLOWED_COMMAND,
+ ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(1);
+#else
+ DBUG_ASSERT(table_ident);
+ TABLE_LIST **query_tables_last= lex->query_tables_last;
+ schema_select_lex= new SELECT_LEX();
+ /* 'parent_lex' is used in init_query() so it must be before it. */
+ schema_select_lex->parent_lex= lex;
+ schema_select_lex->init_query();
+ if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ,
+ MDL_SHARED_READ))
+ DBUG_RETURN(1);
+ lex->query_tables_last= query_tables_last;
+ break;
+#endif
+ }
+ case SCH_PROFILES:
+ /*
+ Mark this current profiling record to be discarded. We don't
+ wish to have SHOW commands show up in profiling.
+ */
+#if defined(ENABLED_PROFILING)
+ thd->profiling.discard_current_query();
+#endif
+ break;
+ case SCH_USER_STATS:
+ case SCH_CLIENT_STATS:
+ if (check_global_access(thd, SUPER_ACL | PROCESS_ACL, true))
+ DBUG_RETURN(1);
+ case SCH_TABLE_STATS:
+ case SCH_INDEX_STATS:
+ case SCH_OPEN_TABLES:
+ case SCH_VARIABLES:
+ case SCH_STATUS:
+ case SCH_PROCEDURES:
+ case SCH_CHARSETS:
+ case SCH_ENGINES:
+ case SCH_COLLATIONS:
+ case SCH_COLLATION_CHARACTER_SET_APPLICABILITY:
+ case SCH_USER_PRIVILEGES:
+ case SCH_SCHEMA_PRIVILEGES:
+ case SCH_TABLE_PRIVILEGES:
+ case SCH_COLUMN_PRIVILEGES:
+ case SCH_TABLE_CONSTRAINTS:
+ case SCH_KEY_COLUMN_USAGE:
+ default:
+ break;
+ }
+
+ SELECT_LEX *select_lex= lex->current_select;
+ if (make_schema_select(thd, select_lex, schema_table_idx))
+ {
+ DBUG_RETURN(1);
+ }
+ TABLE_LIST *table_list= select_lex->table_list.first;
+ table_list->schema_select_lex= schema_select_lex;
+ table_list->schema_table_reformed= 1;
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Read query from packet and store in thd->query.
+ Used in COM_QUERY and COM_STMT_PREPARE.
+
+ Sets the following THD variables:
+ - query
+ - query_length
+
+ @retval
+ FALSE ok
+ @retval
+ TRUE error; In this case thd->fatal_error is set
+*/
+
+bool alloc_query(THD *thd, const char *packet, uint packet_length)
+{
+ char *query;
+ /* Remove garbage at start and end of query */
+ while (packet_length > 0 && my_isspace(thd->charset(), packet[0]))
+ {
+ packet++;
+ packet_length--;
+ }
+ const char *pos= packet + packet_length; // Point at end null
+ while (packet_length > 0 &&
+ (pos[-1] == ';' || my_isspace(thd->charset() ,pos[-1])))
+ {
+ pos--;
+ packet_length--;
+ }
+ /* We must allocate some extra memory for query cache
+
+ The query buffer layout is:
+ buffer :==
+ <statement> The input statement(s)
+ '\0' Terminating null char (1 byte)
+ <length> Length of following current database name (size_t)
+ <db_name> Name of current database
+ <flags> Flags struct
+ */
+ if (! (query= (char*) thd->memdup_w_gap(packet,
+ packet_length,
+ 1 + thd->db_length +
+ QUERY_CACHE_DB_LENGTH_SIZE +
+ QUERY_CACHE_FLAGS_SIZE)))
+ return TRUE;
+ query[packet_length]= '\0';
+ /*
+ Space to hold the name of the current database is allocated. We
+ also store this length, in case current database is changed during
+ execution. We might need to reallocate the 'query' buffer
+ */
+ int2store(query + packet_length + 1, thd->db_length);
+
+ thd->set_query(query, packet_length);
+
+ /* Reclaim some memory */
+ thd->packet.shrink(thd->variables.net_buffer_length);
+ thd->convert_buffer.shrink(thd->variables.net_buffer_length);
+
+ return FALSE;
+}
+
+
+bool sp_process_definer(THD *thd)
+{
+ DBUG_ENTER("sp_process_definer");
+
+ LEX *lex= thd->lex;
+
+ /*
+ If the definer is not specified, this means that CREATE-statement missed
+ DEFINER-clause. DEFINER-clause can be missed in two cases:
+
+ - The user submitted a statement w/o the clause. This is a normal
+ case, we should assign CURRENT_USER as definer.
+
+ - Our slave received an updated from the master, that does not
+ replicate definer for stored rountines. We should also assign
+ CURRENT_USER as definer here, but also we should mark this routine
+ as NON-SUID. This is essential for the sake of backward
+ compatibility.
+
+ The problem is the slave thread is running under "special" user (@),
+ that actually does not exist. In the older versions we do not fail
+ execution of a stored routine if its definer does not exist and
+ continue the execution under the authorization of the invoker
+ (BUG#13198). And now if we try to switch to slave-current-user (@),
+ we will fail.
+
+ Actually, this leads to the inconsistent state of master and
+ slave (different definers, different SUID behaviour), but it seems,
+ this is the best we can do.
+ */
+
+ if (!lex->definer)
+ {
+ Query_arena original_arena;
+ Query_arena *ps_arena= thd->activate_stmt_arena_if_needed(&original_arena);
+
+ lex->definer= create_default_definer(thd, false);
+
+ if (ps_arena)
+ thd->restore_active_arena(ps_arena, &original_arena);
+
+ /* Error has been already reported. */
+ if (lex->definer == NULL)
+ DBUG_RETURN(TRUE);
+
+ if (thd->slave_thread && lex->sphead)
+ lex->sphead->m_chistics->suid= SP_IS_NOT_SUID;
+ }
+ else
+ {
+ LEX_USER *d= lex->definer= get_current_user(thd, lex->definer);
+ if (!d)
+ DBUG_RETURN(TRUE);
+
+ /*
+ If the specified definer differs from the current user or role, we
+ should check that the current user has SUPER privilege (in order
+ to create a stored routine under another user one must have
+ SUPER privilege).
+ */
+ bool curuser= !strcmp(d->user.str, thd->security_ctx->priv_user);
+ bool currole= !curuser && !strcmp(d->user.str, thd->security_ctx->priv_role);
+ bool curuserhost= curuser && d->host.str &&
+ !my_strcasecmp(system_charset_info, d->host.str,
+ thd->security_ctx->priv_host);
+ if (!curuserhost && !currole &&
+ check_global_access(thd, SUPER_ACL, false))
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Check that the specified definer exists. Emit a warning if not. */
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (!is_acl_user(lex->definer->host.str, lex->definer->user.str))
+ {
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_NOTE,
+ ER_NO_SUCH_USER,
+ ER(ER_NO_SUCH_USER),
+ lex->definer->user.str,
+ lex->definer->host.str);
+ }
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Auxiliary call that opens and locks tables for LOCK TABLES statement
+ and initializes the list of locked tables.
+
+ @param thd Thread context.
+ @param tables List of tables to be locked.
+
+ @return FALSE in case of success, TRUE in case of error.
+*/
+
+static bool lock_tables_open_and_lock_tables(THD *thd, TABLE_LIST *tables)
+{
+ Lock_tables_prelocking_strategy lock_tables_prelocking_strategy;
+ uint counter;
+ TABLE_LIST *table;
+
+ thd->in_lock_tables= 1;
+
+ if (open_tables(thd, &tables, &counter, 0, &lock_tables_prelocking_strategy))
+ goto err;
+
+ /*
+ We allow to change temporary tables even if they were locked for read
+ by LOCK TABLES. To avoid a discrepancy between lock acquired at LOCK
+ TABLES time and by the statement which is later executed under LOCK TABLES
+ we ensure that for temporary tables we always request a write lock (such
+ discrepancy can cause problems for the storage engine).
+ We don't set TABLE_LIST::lock_type in this case as this might result in
+ extra warnings from THD::decide_logging_format() even though binary logging
+ is totally irrelevant for LOCK TABLES.
+ */
+ for (table= tables; table; table= table->next_global)
+ if (!table->placeholder() && table->table->s->tmp_table)
+ table->table->reginfo.lock_type= TL_WRITE;
+
+ if (lock_tables(thd, tables, counter, 0) ||
+ thd->locked_tables_list.init_locked_tables(thd))
+ goto err;
+
+ thd->in_lock_tables= 0;
+
+ return FALSE;
+
+err:
+ thd->in_lock_tables= 0;
+
+ trans_rollback_stmt(thd);
+ /*
+ Need to end the current transaction, so the storage engine (InnoDB)
+ can free its locks if LOCK TABLES locked some tables before finding
+ that it can't lock a table in its list
+ */
+ trans_rollback(thd);
+ /* Close tables and release metadata locks. */
+ close_thread_tables(thd);
+ DBUG_ASSERT(!thd->locked_tables_mode);
+ thd->mdl_context.release_transactional_locks();
+ return TRUE;
+}
+
+
+/**
+ Execute command saved in thd and lex->sql_command.
+
+ @param thd Thread handle
+
+ @todo
+ - Invalidate the table in the query cache if something changed
+ after unlocking when changes become visible.
+ TODO: this is workaround. right way will be move invalidating in
+ the unlock procedure.
+ - TODO: use check_change_password()
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+int
+mysql_execute_command(THD *thd)
+{
+ int res= FALSE;
+ int up_result= 0;
+ LEX *lex= thd->lex;
+ /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
+ SELECT_LEX *select_lex= &lex->select_lex;
+ /* first table of first SELECT_LEX */
+ TABLE_LIST *first_table= select_lex->table_list.first;
+ /* list of all tables in query */
+ TABLE_LIST *all_tables;
+ /* most outer SELECT_LEX_UNIT of query */
+ SELECT_LEX_UNIT *unit= &lex->unit;
+#ifdef HAVE_REPLICATION
+ /* have table map for update for multi-update statement (BUG#37051) */
+ bool have_table_map_for_update= FALSE;
+ /* */
+ Rpl_filter *rpl_filter;
+#endif
+ DBUG_ENTER("mysql_execute_command");
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ thd->work_part_info= 0;
+#endif
+
+ DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt);
+ /*
+ Each statement or replication event which might produce deadlock
+ should handle transaction rollback on its own. So by the start of
+ the next statement transaction rollback request should be fulfilled
+ already.
+ */
+ DBUG_ASSERT(! thd->transaction_rollback_request || thd->in_sub_stmt);
+ /*
+ In many cases first table of main SELECT_LEX have special meaning =>
+ check that it is first table in global list and relink it first in
+ queries_tables list if it is necessary (we need such relinking only
+ for queries with subqueries in select list, in this case tables of
+ subqueries will go to global list first)
+
+ all_tables will differ from first_table only if most upper SELECT_LEX
+ do not contain tables.
+
+ Because of above in place where should be at least one table in most
+ outer SELECT_LEX we have following check:
+ DBUG_ASSERT(first_table == all_tables);
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ */
+ lex->first_lists_tables_same();
+ /* should be assigned after making first tables same */
+ all_tables= lex->query_tables;
+ /* set context for commands which do not use setup_tables */
+ select_lex->
+ context.resolve_in_table_list_only(select_lex->
+ table_list.first);
+
+ /*
+ Reset warning count for each query that uses tables
+ A better approach would be to reset this for any commands
+ that is not a SHOW command or a select that only access local
+ variables, but for now this is probably good enough.
+ */
+ if ((sql_command_flags[lex->sql_command] & CF_DIAGNOSTIC_STMT) != 0)
+ thd->get_stmt_da()->set_warning_info_read_only(TRUE);
+ else
+ {
+ thd->get_stmt_da()->set_warning_info_read_only(FALSE);
+ if (all_tables)
+ thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
+ }
+
+#ifdef HAVE_REPLICATION
+ if (unlikely(thd->slave_thread))
+ {
+ if (lex->sql_command == SQLCOM_DROP_TRIGGER)
+ {
+ /*
+ When dropping a trigger, we need to load its table name
+ before checking slave filter rules.
+ */
+ add_table_for_trigger(thd, thd->lex->spname, 1, &all_tables);
+
+ if (!all_tables)
+ {
+ /*
+ If table name cannot be loaded,
+ it means the trigger does not exists possibly because
+ CREATE TRIGGER was previously skipped for this trigger
+ according to slave filtering rules.
+ Returning success without producing any errors in this case.
+ */
+ if (!thd->lex->check_exists)
+ DBUG_RETURN(0);
+ /*
+ DROP TRIGGER IF NOT EXISTS will return without an error later
+ after possibly writing the query to a binlog
+ */
+ }
+ else // force searching in slave.cc:tables_ok()
+ all_tables->updating= 1;
+ }
+
+ /*
+ For fix of BUG#37051, the master stores the table map for update
+ in the Query_log_event, and the value is assigned to
+ thd->variables.table_map_for_update before executing the update
+ query.
+
+ If thd->variables.table_map_for_update is set, then we are
+ replicating from a new master, we can use this value to apply
+ filter rules without opening all the tables. However If
+ thd->variables.table_map_for_update is not set, then we are
+ replicating from an old master, so we just skip this and
+ continue with the old method. And of course, the bug would still
+ exist for old masters.
+ */
+ if (lex->sql_command == SQLCOM_UPDATE_MULTI &&
+ thd->table_map_for_update)
+ {
+ have_table_map_for_update= TRUE;
+ table_map table_map_for_update= thd->table_map_for_update;
+ uint nr= 0;
+ TABLE_LIST *table;
+ for (table=all_tables; table; table=table->next_global, nr++)
+ {
+ if (table_map_for_update & ((table_map)1 << nr))
+ table->updating= TRUE;
+ else
+ table->updating= FALSE;
+ }
+
+ if (all_tables_not_ok(thd, all_tables))
+ {
+ /* we warn the slave SQL thread */
+ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+ }
+
+ for (table=all_tables; table; table=table->next_global)
+ table->updating= TRUE;
+ }
+
+ /*
+ Check if statment should be skipped because of slave filtering
+ rules
+
+ Exceptions are:
+ - UPDATE MULTI: For this statement, we want to check the filtering
+ rules later in the code
+ - SET: we always execute it (Not that many SET commands exists in
+ the binary log anyway -- only 4.1 masters write SET statements,
+ in 5.0 there are no SET statements in the binary log)
+ - DROP TEMPORARY TABLE IF EXISTS: we always execute it (otherwise we
+ have stale files on slave caused by exclusion of one tmp table).
+ */
+ if (!(lex->sql_command == SQLCOM_UPDATE_MULTI) &&
+ !(lex->sql_command == SQLCOM_SET_OPTION) &&
+ !(lex->sql_command == SQLCOM_DROP_TABLE &&
+ lex->drop_temporary && lex->check_exists) &&
+ all_tables_not_ok(thd, all_tables))
+ {
+ /* we warn the slave SQL thread */
+ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+ DBUG_RETURN(0);
+ }
+ /*
+ Execute deferred events first
+ */
+ if (slave_execute_deferred_events(thd))
+ DBUG_RETURN(-1);
+ }
+ else
+ {
+#endif /* HAVE_REPLICATION */
+ /*
+ When option readonly is set deny operations which change non-temporary
+ tables. Except for the replication thread and the 'super' users.
+ */
+ if (deny_updates_if_read_only_option(thd, all_tables))
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
+ DBUG_RETURN(-1);
+ }
+#ifdef HAVE_REPLICATION
+ } /* endif unlikely slave */
+#endif
+
+ status_var_increment(thd->status_var.com_stat[lex->sql_command]);
+ thd->progress.report_to_client= MY_TEST(sql_command_flags[lex->sql_command] &
+ CF_REPORT_PROGRESS);
+
+ DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE);
+
+ /* store old value of binlog format */
+ enum_binlog_format orig_binlog_format,orig_current_stmt_binlog_format;
+
+ thd->get_binlog_format(&orig_binlog_format,
+ &orig_current_stmt_binlog_format);
+
+ /*
+ Force statement logging for DDL commands to allow us to update
+ privilege, system or statistic tables directly without the updates
+ getting logged.
+ */
+ if (!(sql_command_flags[lex->sql_command] &
+ (CF_CAN_GENERATE_ROW_EVENTS | CF_FORCE_ORIGINAL_BINLOG_FORMAT |
+ CF_STATUS_COMMAND)))
+ thd->set_binlog_format_stmt();
+
+ /*
+ End a active transaction so that this command will have it's
+ own transaction and will also sync the binary log. If a DDL is
+ not run in it's own transaction it may simply never appear on
+ the slave in case the outside transaction rolls back.
+ */
+ if (stmt_causes_implicit_commit(thd, CF_IMPLICT_COMMIT_BEGIN))
+ {
+ /*
+ Note that this should never happen inside of stored functions
+ or triggers as all such statements prohibited there.
+ */
+ DBUG_ASSERT(! thd->in_sub_stmt);
+ /* Statement transaction still should not be started. */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ if (!(thd->variables.option_bits & OPTION_GTID_BEGIN))
+ {
+ /* Commit the normal transaction if one is active. */
+ if (trans_commit_implicit(thd))
+ goto error;
+ /* Release metadata locks acquired in this transaction. */
+ thd->mdl_context.release_transactional_locks();
+ }
+ }
+
+#ifndef DBUG_OFF
+ if (lex->sql_command != SQLCOM_SET_OPTION)
+ DEBUG_SYNC(thd,"before_execute_sql_command");
+#endif
+
+ /*
+ Check if we are in a read-only transaction and we're trying to
+ execute a statement which should always be disallowed in such cases.
+
+ Note that this check is done after any implicit commits.
+ */
+ if (thd->tx_read_only &&
+ (sql_command_flags[lex->sql_command] & CF_DISALLOW_IN_RO_TRANS))
+ {
+ my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
+ goto error;
+ }
+
+ /*
+ Close tables open by HANDLERs before executing DDL statement
+ which is going to affect those tables.
+
+ This should happen before temporary tables are pre-opened as
+ otherwise we will get errors about attempt to re-open tables
+ if table to be changed is open through HANDLER.
+
+ Note that even although this is done before any privilege
+ checks there is no security problem here as closing open
+ HANDLER doesn't require any privileges anyway.
+ */
+ if (sql_command_flags[lex->sql_command] & CF_HA_CLOSE)
+ mysql_ha_rm_tables(thd, all_tables);
+
+ /*
+ Pre-open temporary tables to simplify privilege checking
+ for statements which need this.
+ */
+ if (sql_command_flags[lex->sql_command] & CF_PREOPEN_TMP_TABLES)
+ {
+ if (open_temporary_tables(thd, all_tables))
+ goto error;
+ }
+
+ switch (lex->sql_command) {
+
+ case SQLCOM_SHOW_EVENTS:
+#ifndef HAVE_EVENT_SCHEDULER
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "embedded server");
+ break;
+#endif
+ case SQLCOM_SHOW_STATUS:
+ {
+ execute_show_status(thd, all_tables);
+ break;
+ }
+ case SQLCOM_SHOW_EXPLAIN:
+ {
+ if (!thd->security_ctx->priv_user[0] &&
+ check_global_access(thd,PROCESS_ACL))
+ break;
+
+ /*
+ The select should use only one table, it's the SHOW EXPLAIN pseudo-table
+ */
+ if (lex->sroutines.records || lex->query_tables->next_global)
+ {
+ my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY),
+ MYF(0));
+ goto error;
+ }
+
+ Item **it= lex->value_list.head_ref();
+ if (!(*it)->basic_const_item() ||
+ (!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) ||
+ (*it)->check_cols(1))
+ {
+ my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY),
+ MYF(0));
+ goto error;
+ }
+ /* no break; fall through */
+ }
+ case SQLCOM_SHOW_STATUS_PROC:
+ case SQLCOM_SHOW_STATUS_FUNC:
+ case SQLCOM_SHOW_DATABASES:
+ case SQLCOM_SHOW_TABLES:
+ case SQLCOM_SHOW_TRIGGERS:
+ case SQLCOM_SHOW_TABLE_STATUS:
+ case SQLCOM_SHOW_OPEN_TABLES:
+ case SQLCOM_SHOW_PLUGINS:
+ case SQLCOM_SHOW_FIELDS:
+ case SQLCOM_SHOW_KEYS:
+ case SQLCOM_SHOW_VARIABLES:
+ case SQLCOM_SHOW_CHARSETS:
+ case SQLCOM_SHOW_COLLATIONS:
+ case SQLCOM_SHOW_STORAGE_ENGINES:
+ case SQLCOM_SHOW_PROFILE:
+ case SQLCOM_SHOW_CLIENT_STATS:
+ case SQLCOM_SHOW_USER_STATS:
+ case SQLCOM_SHOW_TABLE_STATS:
+ case SQLCOM_SHOW_INDEX_STATS:
+ case SQLCOM_SELECT:
+ {
+ thd->status_var.last_query_cost= 0.0;
+
+ /*
+ lex->exchange != NULL implies SELECT .. INTO OUTFILE and this
+ requires FILE_ACL access.
+ */
+ ulong privileges_requested= lex->exchange ? SELECT_ACL | FILE_ACL :
+ SELECT_ACL;
+
+ if (all_tables)
+ res= check_table_access(thd,
+ privileges_requested,
+ all_tables, FALSE, UINT_MAX, FALSE);
+ else
+ res= check_access(thd, privileges_requested, any_db, NULL, NULL, 0, 0);
+
+ if (res)
+ break;
+
+ res= execute_sqlcom_select(thd, all_tables);
+ break;
+ }
+case SQLCOM_PREPARE:
+ {
+ mysql_sql_stmt_prepare(thd);
+ break;
+ }
+ case SQLCOM_EXECUTE:
+ {
+ mysql_sql_stmt_execute(thd);
+ break;
+ }
+ case SQLCOM_DEALLOCATE_PREPARE:
+ {
+ mysql_sql_stmt_close(thd);
+ break;
+ }
+ case SQLCOM_DO:
+ if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)
+ || open_and_lock_tables(thd, all_tables, TRUE, 0))
+ goto error;
+
+ res= mysql_do(thd, *lex->insert_list);
+ break;
+
+ case SQLCOM_EMPTY_QUERY:
+ my_ok(thd);
+ break;
+
+ case SQLCOM_HELP:
+ res= mysqld_help(thd,lex->help_arg);
+ break;
+
+#ifndef EMBEDDED_LIBRARY
+ case SQLCOM_PURGE:
+ {
+ if (check_global_access(thd, SUPER_ACL))
+ goto error;
+ /* PURGE MASTER LOGS TO 'file' */
+ res = purge_master_logs(thd, lex->to_log);
+ break;
+ }
+ case SQLCOM_PURGE_BEFORE:
+ {
+ Item *it;
+
+ if (check_global_access(thd, SUPER_ACL))
+ goto error;
+ /* PURGE MASTER LOGS BEFORE 'data' */
+ it= (Item *)lex->value_list.head();
+ if ((!it->fixed && it->fix_fields(lex->thd, &it)) ||
+ it->check_cols(1))
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "PURGE LOGS BEFORE");
+ goto error;
+ }
+ it= new Item_func_unix_timestamp(it);
+ it->fix_fields(thd, &it);
+ res = purge_master_logs_before_date(thd, (ulong)it->val_int());
+ break;
+ }
+#endif
+ case SQLCOM_SHOW_WARNS:
+ {
+ res= mysqld_show_warnings(thd, (ulong)
+ ((1L << (uint) Sql_condition::WARN_LEVEL_NOTE) |
+ (1L << (uint) Sql_condition::WARN_LEVEL_WARN) |
+ (1L << (uint) Sql_condition::WARN_LEVEL_ERROR)
+ ));
+ break;
+ }
+ case SQLCOM_SHOW_ERRORS:
+ {
+ res= mysqld_show_warnings(thd, (ulong)
+ (1L << (uint) Sql_condition::WARN_LEVEL_ERROR));
+ break;
+ }
+ case SQLCOM_SHOW_PROFILES:
+ {
+#if defined(ENABLED_PROFILING)
+ thd->profiling.discard_current_query();
+ res= thd->profiling.show_profiles();
+ if (res)
+ goto error;
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0), "SHOW PROFILES", "enable-profiling");
+ goto error;
+#endif
+ break;
+ }
+
+#ifdef HAVE_REPLICATION
+ case SQLCOM_SHOW_SLAVE_HOSTS:
+ {
+ if (check_global_access(thd, REPL_SLAVE_ACL))
+ goto error;
+ res = show_slave_hosts(thd);
+ break;
+ }
+ case SQLCOM_SHOW_RELAYLOG_EVENTS: /* fall through */
+ case SQLCOM_SHOW_BINLOG_EVENTS:
+ {
+ if (check_global_access(thd, REPL_SLAVE_ACL))
+ goto error;
+ res = mysql_show_binlog_events(thd);
+ break;
+ }
+#endif
+
+ case SQLCOM_ASSIGN_TO_KEYCACHE:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if (check_access(thd, INDEX_ACL, first_table->db,
+ &first_table->grant.privilege,
+ &first_table->grant.m_internal,
+ 0, 0))
+ goto error;
+ res= mysql_assign_to_keycache(thd, first_table, &lex->ident);
+ break;
+ }
+ case SQLCOM_PRELOAD_KEYS:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if (check_access(thd, INDEX_ACL, first_table->db,
+ &first_table->grant.privilege,
+ &first_table->grant.m_internal,
+ 0, 0))
+ goto error;
+ res = mysql_preload_keys(thd, first_table);
+ break;
+ }
+#ifdef HAVE_REPLICATION
+ case SQLCOM_CHANGE_MASTER:
+ {
+ LEX_MASTER_INFO *lex_mi= &thd->lex->mi;
+ Master_info *mi;
+ bool new_master= 0;
+ bool master_info_added;
+
+ if (check_global_access(thd, SUPER_ACL))
+ goto error;
+ mysql_mutex_lock(&LOCK_active_mi);
+
+ if (!master_info_index)
+ goto error;
+
+ mi= master_info_index->get_master_info(&lex_mi->connection_name,
+ Sql_condition::WARN_LEVEL_NOTE);
+
+ if (mi == NULL)
+ {
+ /* New replication created */
+ mi= new Master_info(&lex_mi->connection_name, relay_log_recovery);
+ if (!mi || mi->error())
+ {
+ delete mi;
+ res= 1;
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+ new_master= 1;
+ }
+
+ res= change_master(thd, mi, &master_info_added);
+ if (res && new_master)
+ {
+ /*
+ If the new master was added by change_master(), remove it as it didn't
+ work (this will free mi as well).
+
+ If new master was not added, we still need to free mi.
+ */
+ if (master_info_added)
+ master_info_index->remove_master_info(&lex_mi->connection_name);
+ else
+ delete mi;
+ }
+ else
+ {
+ mi->rpl_filter= get_or_create_rpl_filter(lex_mi->connection_name.str,
+ lex_mi->connection_name.length);
+ }
+
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+ case SQLCOM_SHOW_SLAVE_STAT:
+ {
+ /* Accept one of two privileges */
+ if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL))
+ goto error;
+ mysql_mutex_lock(&LOCK_active_mi);
+
+ if (lex->verbose)
+ res= show_all_master_info(thd);
+ else
+ {
+ LEX_MASTER_INFO *lex_mi= &thd->lex->mi;
+ Master_info *mi;
+ mi= master_info_index->get_master_info(&lex_mi->connection_name,
+ Sql_condition::WARN_LEVEL_ERROR);
+ if (mi != NULL)
+ {
+ res= show_master_info(thd, mi, 0);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+ case SQLCOM_SHOW_MASTER_STAT:
+ {
+ /* Accept one of two privileges */
+ if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL))
+ goto error;
+ res = show_binlog_info(thd);
+ break;
+ }
+
+#endif /* HAVE_REPLICATION */
+ case SQLCOM_SHOW_ENGINE_STATUS:
+ {
+ if (check_global_access(thd, PROCESS_ACL))
+ goto error;
+ res = ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_STATUS);
+ break;
+ }
+ case SQLCOM_SHOW_ENGINE_MUTEX:
+ {
+ if (check_global_access(thd, PROCESS_ACL))
+ goto error;
+ res = ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_MUTEX);
+ break;
+ }
+ case SQLCOM_CREATE_TABLE:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ bool link_to_local;
+ TABLE_LIST *create_table= first_table;
+ TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global;
+
+ /*
+ Code below (especially in mysql_create_table() and select_create
+ methods) may modify HA_CREATE_INFO structure in LEX, so we have to
+ use a copy of this structure to make execution prepared statement-
+ safe. A shallow copy is enough as this code won't modify any memory
+ referenced from this structure.
+ */
+ HA_CREATE_INFO create_info(lex->create_info);
+ /*
+ We need to copy alter_info for the same reasons of re-execution
+ safety, only in case of Alter_info we have to do (almost) a deep
+ copy.
+ */
+ Alter_info alter_info(lex->alter_info, thd->mem_root);
+
+ if (thd->is_fatal_error)
+ {
+ /* If out of memory when creating a copy of alter_info. */
+ res= 1;
+ goto end_with_restore_list;
+ }
+
+ /* Check privileges */
+ if ((res= create_table_precheck(thd, select_tables, create_table)))
+ goto end_with_restore_list;
+
+ /* Might have been updated in create_table_precheck */
+ create_info.alias= create_table->alias;
+
+ /* Fix names if symlinked or relocated tables */
+ if (append_file_to_dir(thd, &create_info.data_file_name,
+ create_table->table_name) ||
+ append_file_to_dir(thd, &create_info.index_file_name,
+ create_table->table_name))
+ goto end_with_restore_list;
+
+ /*
+ If no engine type was given, work out the default now
+ rather than at parse-time.
+ */
+ if (!(create_info.used_fields & HA_CREATE_USED_ENGINE))
+ create_info.db_type= ha_default_handlerton(thd);
+ /*
+ If we are using SET CHARSET without DEFAULT, add an implicit
+ DEFAULT to not confuse old users. (This may change).
+ */
+ if ((create_info.used_fields &
+ (HA_CREATE_USED_DEFAULT_CHARSET | HA_CREATE_USED_CHARSET)) ==
+ HA_CREATE_USED_CHARSET)
+ {
+ create_info.used_fields&= ~HA_CREATE_USED_CHARSET;
+ create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET;
+ create_info.default_table_charset= create_info.table_charset;
+ create_info.table_charset= 0;
+ }
+
+ /*
+ For CREATE TABLE we should not open the table even if it exists.
+ If the table exists, we should either not create it or replace it
+ */
+ lex->query_tables->open_strategy= TABLE_LIST::OPEN_STUB;
+
+ /*
+ If we are a slave, we should add OR REPLACE if we don't have
+ IF EXISTS. This will help a slave to recover from
+ CREATE TABLE OR EXISTS failures by dropping the table and
+ retrying the create.
+ */
+ create_info.org_options= create_info.options;
+ if (thd->slave_thread &&
+ slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT &&
+ !(lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS))
+ create_info.options|= HA_LEX_CREATE_REPLACE;
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ {
+ partition_info *part_info= thd->lex->part_info;
+ if (part_info && !(part_info= thd->lex->part_info->get_clone()))
+ {
+ res= -1;
+ goto end_with_restore_list;
+ }
+ thd->work_part_info= part_info;
+ }
+#endif
+
+ if (select_lex->item_list.elements) // With select
+ {
+ select_result *result;
+
+ /*
+ CREATE TABLE...IGNORE/REPLACE SELECT... can be unsafe, unless
+ ORDER BY PRIMARY KEY clause is used in SELECT statement. We therefore
+ use row based logging if mixed or row based logging is available.
+ TODO: Check if the order of the output of the select statement is
+ deterministic. Waiting for BUG#42415
+ */
+ if(lex->ignore)
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_IGNORE_SELECT);
+
+ if(lex->duplicates == DUP_REPLACE)
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_REPLACE_SELECT);
+
+ /*
+ If:
+ a) we inside an SP and there was NAME_CONST substitution,
+ b) binlogging is on (STMT mode),
+ c) we log the SP as separate statements
+ raise a warning, as it may cause problems
+ (see 'NAME_CONST issues' in 'Binary Logging of Stored Programs')
+ */
+ if (thd->query_name_consts &&
+ mysql_bin_log.is_open() &&
+ thd->variables.binlog_format == BINLOG_FORMAT_STMT &&
+ !mysql_bin_log.is_query_in_union(thd, thd->query_id))
+ {
+ List_iterator_fast<Item> it(select_lex->item_list);
+ Item *item;
+ uint splocal_refs= 0;
+ /* Count SP local vars in the top-level SELECT list */
+ while ((item= it++))
+ {
+ if (item->is_splocal())
+ splocal_refs++;
+ }
+ /*
+ If it differs from number of NAME_CONST substitution applied,
+ we may have a SOME_FUNC(NAME_CONST()) in the SELECT list,
+ that may cause a problem with binary log (see BUG#35383),
+ raise a warning.
+ */
+ if (splocal_refs != thd->query_name_consts)
+ push_warning(thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR,
+"Invoked routine ran a statement that may cause problems with "
+"binary log, see 'NAME_CONST issues' in 'Binary Logging of Stored Programs' "
+"section of the manual.");
+ }
+
+ select_lex->options|= SELECT_NO_UNLOCK;
+ unit->set_limit(select_lex);
+
+ /*
+ Disable non-empty MERGE tables with CREATE...SELECT. Too
+ complicated. See Bug #26379. Empty MERGE tables are read-only
+ and don't allow CREATE...SELECT anyway.
+ */
+ if (create_info.used_fields & HA_CREATE_USED_UNION)
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0), create_table->db,
+ create_table->table_name, "BASE TABLE");
+ res= 1;
+ goto end_with_restore_list;
+ }
+
+ /* Copy temporarily the statement flags to thd for lock_table_names() */
+ uint save_thd_create_info_options= thd->lex->create_info.options;
+ thd->lex->create_info.options|= create_info.options;
+ res= open_and_lock_tables(thd, lex->query_tables, TRUE, 0);
+ thd->lex->create_info.options= save_thd_create_info_options;
+ if (res)
+ {
+ /* Got error or warning. Set res to 1 if error */
+ if (!(res= thd->is_error()))
+ my_ok(thd); // CREATE ... IF NOT EXISTS
+ goto end_with_restore_list;
+ }
+
+ /* Ensure we don't try to create something from which we select from */
+ if ((create_info.options & HA_LEX_CREATE_REPLACE) &&
+ !create_info.tmp_table())
+ {
+ TABLE_LIST *duplicate;
+ if ((duplicate= unique_table(thd, lex->query_tables,
+ lex->query_tables->next_global,
+ 0)))
+ {
+ update_non_unique_table_error(lex->query_tables, "CREATE",
+ duplicate);
+ res= TRUE;
+ goto end_with_restore_list;
+ }
+ }
+ {
+ /*
+ Remove target table from main select and name resolution
+ context. This can't be done earlier as it will break view merging in
+ statements like "CREATE TABLE IF NOT EXISTS existing_view SELECT".
+ */
+ lex->unlink_first_table(&link_to_local);
+
+ /* Store reference to table in case of LOCK TABLES */
+ create_info.table= create_table->table;
+
+ /*
+ select_create is currently not re-execution friendly and
+ needs to be created for every execution of a PS/SP.
+ */
+ if ((result= new select_create(create_table,
+ &create_info,
+ &alter_info,
+ select_lex->item_list,
+ lex->duplicates,
+ lex->ignore,
+ select_tables)))
+ {
+ /*
+ CREATE from SELECT give its SELECT_LEX for SELECT,
+ and item_list belong to SELECT
+ */
+ if (!(res= handle_select(thd, lex, result, 0)))
+ {
+ if (create_info.tmp_table())
+ thd->variables.option_bits|= OPTION_KEEP_LOG;
+ }
+ delete result;
+ }
+ lex->link_first_table_back(create_table, link_to_local);
+ }
+ }
+ else
+ {
+ /* regular create */
+ if (create_info.options & HA_LEX_CREATE_TABLE_LIKE)
+ {
+ /* CREATE TABLE ... LIKE ... */
+ res= mysql_create_like_table(thd, create_table, select_tables,
+ &create_info);
+ }
+ else
+ {
+ /* Regular CREATE TABLE */
+ res= mysql_create_table(thd, create_table,
+ &create_info, &alter_info);
+ }
+ if (!res)
+ {
+ /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */
+ if (create_info.tmp_table())
+ thd->variables.option_bits|= OPTION_KEEP_LOG;
+ my_ok(thd);
+ }
+ }
+
+end_with_restore_list:
+ break;
+ }
+ case SQLCOM_CREATE_INDEX:
+ case SQLCOM_DROP_INDEX:
+ /*
+ CREATE INDEX and DROP INDEX are implemented by calling ALTER
+ TABLE with proper arguments.
+
+ In the future ALTER TABLE will notice that the request is to
+ only add indexes and create these one by one for the existing
+ table without having to do a full rebuild.
+ */
+ {
+ /* Prepare stack copies to be re-execution safe */
+ HA_CREATE_INFO create_info;
+ Alter_info alter_info(lex->alter_info, thd->mem_root);
+
+ if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
+ goto error;
+
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if (check_one_table_access(thd, INDEX_ACL, all_tables))
+ goto error; /* purecov: inspected */
+ /*
+ Currently CREATE INDEX or DROP INDEX cause a full table rebuild
+ and thus classify as slow administrative statements just like
+ ALTER TABLE.
+ */
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+ thd->query_plan_flags|= QPLAN_ADMIN;
+
+ bzero((char*) &create_info, sizeof(create_info));
+ create_info.db_type= 0;
+ create_info.row_type= ROW_TYPE_NOT_USED;
+ create_info.default_table_charset= thd->variables.collation_database;
+
+ res= mysql_alter_table(thd, first_table->db, first_table->table_name,
+ &create_info, first_table, &alter_info,
+ 0, (ORDER*) 0, 0);
+ break;
+ }
+#ifdef HAVE_REPLICATION
+ case SQLCOM_SLAVE_START:
+ {
+ LEX_MASTER_INFO* lex_mi= &thd->lex->mi;
+ Master_info *mi;
+ int load_error;
+
+ load_error= rpl_load_gtid_slave_state(thd);
+
+ mysql_mutex_lock(&LOCK_active_mi);
+
+ if ((mi= (master_info_index->
+ get_master_info(&lex_mi->connection_name,
+ Sql_condition::WARN_LEVEL_ERROR))))
+ {
+ if (load_error)
+ {
+ /*
+ We cannot start a slave using GTID if we cannot load the GTID position
+ from the mysql.gtid_slave_pos table. But we can allow non-GTID
+ replication (useful eg. during upgrade).
+ */
+ if (mi->using_gtid != Master_info::USE_GTID_NO)
+ {
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+ else
+ thd->clear_error();
+ }
+ if (!start_slave(thd, mi, 1 /* net report*/))
+ my_ok(thd);
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+ case SQLCOM_SLAVE_STOP:
+ {
+ LEX_MASTER_INFO *lex_mi;
+ Master_info *mi;
+ /*
+ If the client thread has locked tables, a deadlock is possible.
+ Assume that
+ - the client thread does LOCK TABLE t READ.
+ - then the master updates t.
+ - then the SQL slave thread wants to update t,
+ so it waits for the client thread because t is locked by it.
+ - then the client thread does SLAVE STOP.
+ SLAVE STOP waits for the SQL slave thread to terminate its
+ update t, which waits for the client thread because t is locked by it.
+ To prevent that, refuse SLAVE STOP if the
+ client thread has locked tables
+ */
+ if (thd->locked_tables_mode ||
+ thd->in_active_multi_stmt_transaction() ||
+ thd->global_read_lock.is_acquired())
+ {
+ my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
+ ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
+ goto error;
+ }
+
+ lex_mi= &thd->lex->mi;
+ mysql_mutex_lock(&LOCK_active_mi);
+ if ((mi= (master_info_index->
+ get_master_info(&lex_mi->connection_name,
+ Sql_condition::WARN_LEVEL_ERROR))))
+ if (!stop_slave(thd, mi, 1/* net report*/))
+ my_ok(thd);
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+ case SQLCOM_SLAVE_ALL_START:
+ {
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index && !master_info_index->start_all_slaves(thd))
+ my_ok(thd);
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+ case SQLCOM_SLAVE_ALL_STOP:
+ {
+ if (thd->locked_tables_mode ||
+ thd->in_active_multi_stmt_transaction() ||
+ thd->global_read_lock.is_acquired())
+ {
+ my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
+ ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
+ goto error;
+ }
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index && !master_info_index->stop_all_slaves(thd))
+ my_ok(thd);
+ mysql_mutex_unlock(&LOCK_active_mi);
+ break;
+ }
+#endif /* HAVE_REPLICATION */
+ case SQLCOM_RENAME_TABLE:
+ {
+ if (execute_rename_table(thd, first_table, all_tables))
+ goto error;
+ break;
+ }
+#ifndef EMBEDDED_LIBRARY
+ case SQLCOM_SHOW_BINLOGS:
+#ifdef DONT_ALLOW_SHOW_COMMANDS
+ my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
+ MYF(0)); /* purecov: inspected */
+ goto error;
+#else
+ {
+ if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL))
+ goto error;
+ res = show_binlogs(thd);
+ break;
+ }
+#endif
+#endif /* EMBEDDED_LIBRARY */
+ case SQLCOM_SHOW_CREATE:
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+#ifdef DONT_ALLOW_SHOW_COMMANDS
+ my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
+ MYF(0)); /* purecov: inspected */
+ goto error;
+#else
+ {
+ /*
+ Access check:
+ SHOW CREATE TABLE require any privileges on the table level (ie
+ effecting all columns in the table).
+ SHOW CREATE VIEW require the SHOW_VIEW and SELECT ACLs on the table
+ level.
+ NOTE: SHOW_VIEW ACL is checked when the view is created.
+ */
+
+ DBUG_PRINT("debug", ("lex->only_view: %d, table: %s.%s",
+ lex->only_view,
+ first_table->db, first_table->table_name));
+ if (lex->only_view)
+ {
+ if (check_table_access(thd, SELECT_ACL, first_table, FALSE, 1, FALSE))
+ {
+ DBUG_PRINT("debug", ("check_table_access failed"));
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
+ "SHOW", thd->security_ctx->priv_user,
+ thd->security_ctx->host_or_ip, first_table->alias);
+ goto error;
+ }
+ DBUG_PRINT("debug", ("check_table_access succeeded"));
+
+ /* Ignore temporary tables if this is "SHOW CREATE VIEW" */
+ first_table->open_type= OT_BASE_ONLY;
+
+ }
+ else
+ {
+ /*
+ Temporary tables should be opened for SHOW CREATE TABLE, but not
+ for SHOW CREATE VIEW.
+ */
+ if (open_temporary_tables(thd, all_tables))
+ goto error;
+
+ /*
+ The fact that check_some_access() returned FALSE does not mean that
+ access is granted. We need to check if first_table->grant.privilege
+ contains any table-specific privilege.
+ */
+ DBUG_PRINT("debug", ("first_table->grant.privilege: %lx",
+ first_table->grant.privilege));
+ if (check_some_access(thd, SHOW_CREATE_TABLE_ACLS, first_table) ||
+ (first_table->grant.privilege & SHOW_CREATE_TABLE_ACLS) == 0)
+ {
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
+ "SHOW", thd->security_ctx->priv_user,
+ thd->security_ctx->host_or_ip, first_table->alias);
+ goto error;
+ }
+ }
+
+ /* Access is granted. Execute the command. */
+ res= mysqld_show_create(thd, first_table);
+ break;
+ }
+#endif
+ case SQLCOM_CHECKSUM:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if (check_table_access(thd, SELECT_ACL, all_tables,
+ FALSE, UINT_MAX, FALSE))
+ goto error; /* purecov: inspected */
+
+ res = mysql_checksum_table(thd, first_table, &lex->check_opt);
+ break;
+ }
+ case SQLCOM_UPDATE:
+ {
+ ha_rows found= 0, updated= 0;
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if (update_precheck(thd, all_tables))
+ break;
+
+ /*
+ UPDATE IGNORE can be unsafe. We therefore use row based
+ logging if mixed or row based logging is available.
+ TODO: Check if the order of the output of the select statement is
+ deterministic. Waiting for BUG#42415
+ */
+ if (lex->ignore)
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_UPDATE_IGNORE);
+
+ DBUG_ASSERT(select_lex->offset_limit == 0);
+ unit->set_limit(select_lex);
+ MYSQL_UPDATE_START(thd->query());
+ res= (up_result= mysql_update(thd, all_tables,
+ select_lex->item_list,
+ lex->value_list,
+ select_lex->where,
+ select_lex->order_list.elements,
+ select_lex->order_list.first,
+ unit->select_limit_cnt,
+ lex->duplicates, lex->ignore,
+ &found, &updated));
+ MYSQL_UPDATE_DONE(res, found, updated);
+ /* mysql_update return 2 if we need to switch to multi-update */
+ if (up_result != 2)
+ break;
+ /* Fall through */
+ }
+ case SQLCOM_UPDATE_MULTI:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ /* if we switched from normal update, rights are checked */
+ if (up_result != 2)
+ {
+ if ((res= multi_update_precheck(thd, all_tables)))
+ break;
+ }
+ else
+ res= 0;
+
+ res= mysql_multi_update_prepare(thd);
+
+#ifdef HAVE_REPLICATION
+ /* Check slave filtering rules */
+ if (unlikely(thd->slave_thread && !have_table_map_for_update))
+ {
+ if (all_tables_not_ok(thd, all_tables))
+ {
+ if (res!= 0)
+ {
+ res= 0; /* don't care of prev failure */
+ thd->clear_error(); /* filters are of highest prior */
+ }
+ /* we warn the slave SQL thread */
+ my_error(ER_SLAVE_IGNORED_TABLE, MYF(0));
+ break;
+ }
+ if (res)
+ break;
+ }
+ else
+ {
+#endif /* HAVE_REPLICATION */
+ if (res)
+ break;
+ if (opt_readonly &&
+ !(thd->security_ctx->master_access & SUPER_ACL) &&
+ some_non_temp_table_to_be_updated(thd, all_tables))
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
+ break;
+ }
+#ifdef HAVE_REPLICATION
+ } /* unlikely */
+#endif
+ {
+ multi_update *result_obj;
+ MYSQL_MULTI_UPDATE_START(thd->query());
+ res= mysql_multi_update(thd, all_tables,
+ &select_lex->item_list,
+ &lex->value_list,
+ select_lex->where,
+ select_lex->options,
+ lex->duplicates,
+ lex->ignore,
+ unit,
+ select_lex,
+ &result_obj);
+ if (result_obj)
+ {
+ MYSQL_MULTI_UPDATE_DONE(res, result_obj->num_found(),
+ result_obj->num_updated());
+ res= FALSE; /* Ignore errors here */
+ delete result_obj;
+ }
+ else
+ {
+ MYSQL_MULTI_UPDATE_DONE(1, 0, 0);
+ }
+ }
+ break;
+ }
+ case SQLCOM_REPLACE:
+#ifndef DBUG_OFF
+ if (mysql_bin_log.is_open())
+ {
+ /*
+ Generate an incident log event before writing the real event
+ to the binary log. We put this event is before the statement
+ since that makes it simpler to check that the statement was
+ not executed on the slave (since incidents usually stop the
+ slave).
+
+ Observe that any row events that are generated will be
+ generated before.
+
+ This is only for testing purposes and will not be present in a
+ release build.
+ */
+
+ Incident incident= INCIDENT_NONE;
+ DBUG_PRINT("debug", ("Just before generate_incident()"));
+ DBUG_EXECUTE_IF("incident_database_resync_on_replace",
+ incident= INCIDENT_LOST_EVENTS;);
+ if (incident)
+ {
+ Incident_log_event ev(thd, incident);
+ (void) mysql_bin_log.write(&ev); /* error is ignored */
+ if (mysql_bin_log.rotate_and_purge(true))
+ {
+ res= 1;
+ break;
+ }
+ }
+ DBUG_PRINT("debug", ("Just after generate_incident()"));
+ }
+#endif
+ case SQLCOM_INSERT:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+
+ /*
+ Since INSERT DELAYED doesn't support temporary tables, we could
+ not pre-open temporary tables for SQLCOM_INSERT / SQLCOM_REPLACE.
+ Open them here instead.
+ */
+ if (first_table->lock_type != TL_WRITE_DELAYED)
+ {
+ if ((res= open_temporary_tables(thd, all_tables)))
+ break;
+ }
+
+ if ((res= insert_precheck(thd, all_tables)))
+ break;
+
+ MYSQL_INSERT_START(thd->query());
+ res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
+ lex->update_list, lex->value_list,
+ lex->duplicates, lex->ignore);
+ MYSQL_INSERT_DONE(res, (ulong) thd->get_row_count_func());
+ /*
+ If we have inserted into a VIEW, and the base table has
+ AUTO_INCREMENT column, but this column is not accessible through
+ a view, then we should restore LAST_INSERT_ID to the value it
+ had before the statement.
+ */
+ if (first_table->view && !first_table->contain_auto_increment)
+ thd->first_successful_insert_id_in_cur_stmt=
+ thd->first_successful_insert_id_in_prev_stmt;
+
+#ifdef ENABLED_DEBUG_SYNC
+ DBUG_EXECUTE_IF("after_mysql_insert",
+ {
+ const char act1[]=
+ "now "
+ "wait_for signal.continue";
+ const char act2[]=
+ "now "
+ "signal signal.continued";
+ DBUG_ASSERT(debug_sync_service);
+ DBUG_ASSERT(!debug_sync_set_action(thd,
+ STRING_WITH_LEN(act1)));
+ DBUG_ASSERT(!debug_sync_set_action(thd,
+ STRING_WITH_LEN(act2)));
+ };);
+ DEBUG_SYNC(thd, "after_mysql_insert");
+#endif
+ break;
+ }
+ case SQLCOM_REPLACE_SELECT:
+ case SQLCOM_INSERT_SELECT:
+ {
+ select_result *sel_result;
+ bool explain= MY_TEST(lex->describe);
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if ((res= insert_precheck(thd, all_tables)))
+ break;
+ /*
+ INSERT...SELECT...ON DUPLICATE KEY UPDATE/REPLACE SELECT/
+ INSERT...IGNORE...SELECT can be unsafe, unless ORDER BY PRIMARY KEY
+ clause is used in SELECT statement. We therefore use row based
+ logging if mixed or row based logging is available.
+ TODO: Check if the order of the output of the select statement is
+ deterministic. Waiting for BUG#42415
+ */
+ if (lex->sql_command == SQLCOM_INSERT_SELECT &&
+ lex->duplicates == DUP_UPDATE)
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_SELECT_UPDATE);
+
+ if (lex->sql_command == SQLCOM_INSERT_SELECT && lex->ignore)
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_IGNORE_SELECT);
+
+ if (lex->sql_command == SQLCOM_REPLACE_SELECT)
+ lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_REPLACE_SELECT);
+
+ /* Fix lock for first table */
+ if (first_table->lock_type == TL_WRITE_DELAYED)
+ first_table->lock_type= TL_WRITE;
+
+ /* Don't unlock tables until command is written to binary log */
+ select_lex->options|= SELECT_NO_UNLOCK;
+
+ unit->set_limit(select_lex);
+
+ if (!(res= open_and_lock_tables(thd, all_tables, TRUE, 0)))
+ {
+ MYSQL_INSERT_SELECT_START(thd->query());
+ /*
+ Only the INSERT table should be merged. Other will be handled by
+ select.
+ */
+ /* Skip first table, which is the table we are inserting in */
+ TABLE_LIST *second_table= first_table->next_local;
+ select_lex->table_list.first= second_table;
+ select_lex->context.table_list=
+ select_lex->context.first_name_resolution_table= second_table;
+ res= mysql_insert_select_prepare(thd);
+ if (!res && (sel_result= new select_insert(first_table,
+ first_table->table,
+ &lex->field_list,
+ &lex->update_list,
+ &lex->value_list,
+ lex->duplicates,
+ lex->ignore)))
+ {
+ res= handle_select(thd, lex, sel_result, OPTION_SETUP_TABLES_DONE);
+ /*
+ Invalidate the table in the query cache if something changed
+ after unlocking when changes become visible.
+ TODO: this is workaround. right way will be move invalidating in
+ the unlock procedure.
+ */
+ if (!res && first_table->lock_type == TL_WRITE_CONCURRENT_INSERT &&
+ thd->lock)
+ {
+ /* INSERT ... SELECT should invalidate only the very first table */
+ TABLE_LIST *save_table= first_table->next_local;
+ first_table->next_local= 0;
+ query_cache_invalidate3(thd, first_table, 1);
+ first_table->next_local= save_table;
+ }
+ delete sel_result;
+ }
+
+ if (!res && explain)
+ res= thd->lex->explain->send_explain(thd);
+
+ /* revert changes for SP */
+ MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func());
+ select_lex->table_list.first= first_table;
+ }
+ /*
+ If we have inserted into a VIEW, and the base table has
+ AUTO_INCREMENT column, but this column is not accessible through
+ a view, then we should restore LAST_INSERT_ID to the value it
+ had before the statement.
+ */
+ if (first_table->view && !first_table->contain_auto_increment)
+ thd->first_successful_insert_id_in_cur_stmt=
+ thd->first_successful_insert_id_in_prev_stmt;
+
+ break;
+ }
+ case SQLCOM_DELETE:
+ {
+ select_result *sel_result=lex->result;
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if ((res= delete_precheck(thd, all_tables)))
+ break;
+ DBUG_ASSERT(select_lex->offset_limit == 0);
+ unit->set_limit(select_lex);
+
+ MYSQL_DELETE_START(thd->query());
+ if (!(sel_result= lex->result) && !(sel_result= new select_send()))
+ return 1;
+ res = mysql_delete(thd, all_tables,
+ select_lex->where, &select_lex->order_list,
+ unit->select_limit_cnt, select_lex->options,
+ sel_result);
+ delete sel_result;
+ MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func());
+ break;
+ }
+ case SQLCOM_DELETE_MULTI:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first;
+ bool explain= MY_TEST(lex->describe);
+ multi_delete *result;
+
+ if ((res= multi_delete_precheck(thd, all_tables)))
+ break;
+
+ /* condition will be TRUE on SP re-excuting */
+ if (select_lex->item_list.elements != 0)
+ select_lex->item_list.empty();
+ if (add_item_to_list(thd, new Item_null()))
+ goto error;
+
+ THD_STAGE_INFO(thd, stage_init);
+ if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0)))
+ break;
+
+ MYSQL_MULTI_DELETE_START(thd->query());
+ if ((res= mysql_multi_delete_prepare(thd)))
+ {
+ MYSQL_MULTI_DELETE_DONE(1, 0);
+ goto error;
+ }
+
+ if (!thd->is_fatal_error)
+ {
+ result= new multi_delete(aux_tables, lex->table_count);
+ if (result)
+ {
+ res= mysql_select(thd, &select_lex->ref_pointer_array,
+ select_lex->get_table_list(),
+ select_lex->with_wild,
+ select_lex->item_list,
+ select_lex->where,
+ 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL,
+ (ORDER *)NULL,
+ (select_lex->options | thd->variables.option_bits |
+ SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
+ OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT,
+ result, unit, select_lex);
+ res|= thd->is_error();
+
+ MYSQL_MULTI_DELETE_DONE(res, result->num_deleted());
+ if (res)
+ result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */
+ else
+ {
+ if (explain)
+ res= thd->lex->explain->send_explain(thd);
+ }
+ delete result;
+ }
+ }
+ else
+ {
+ res= TRUE; // Error
+ MYSQL_MULTI_DELETE_DONE(1, 0);
+ }
+ break;
+ }
+ case SQLCOM_DROP_TABLE:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if (!lex->drop_temporary)
+ {
+ if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE))
+ goto error; /* purecov: inspected */
+ }
+ else
+ {
+ /* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */
+ thd->variables.option_bits|= OPTION_KEEP_LOG;
+ }
+ /*
+ If we are a slave, we should add IF EXISTS if the query executed
+ on the master without an error. This will help a slave to
+ recover from multi-table DROP TABLE that was aborted in the
+ middle.
+ */
+ if (thd->slave_thread && !thd->slave_expected_error &&
+ slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT)
+ lex->check_exists= 1;
+
+ /* DDL and binlog write order are protected by metadata locks. */
+ res= mysql_rm_table(thd, first_table, lex->check_exists,
+ lex->drop_temporary);
+ }
+ break;
+ case SQLCOM_SHOW_PROCESSLIST:
+ if (!thd->security_ctx->priv_user[0] &&
+ check_global_access(thd,PROCESS_ACL))
+ break;
+ mysqld_list_processes(thd,
+ (thd->security_ctx->master_access & PROCESS_ACL ?
+ NullS :
+ thd->security_ctx->priv_user),
+ lex->verbose);
+ break;
+ case SQLCOM_SHOW_AUTHORS:
+ res= mysqld_show_authors(thd);
+ break;
+ case SQLCOM_SHOW_CONTRIBUTORS:
+ res= mysqld_show_contributors(thd);
+ break;
+ case SQLCOM_SHOW_PRIVILEGES:
+ res= mysqld_show_privileges(thd);
+ break;
+ case SQLCOM_SHOW_ENGINE_LOGS:
+#ifdef DONT_ALLOW_SHOW_COMMANDS
+ my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
+ MYF(0)); /* purecov: inspected */
+ goto error;
+#else
+ {
+ if (check_access(thd, FILE_ACL, any_db, NULL, NULL, 0, 0))
+ goto error;
+ res= ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_LOGS);
+ break;
+ }
+#endif
+ case SQLCOM_CHANGE_DB:
+ {
+ LEX_STRING db_str= { (char *) select_lex->db, strlen(select_lex->db) };
+
+ if (!mysql_change_db(thd, &db_str, FALSE))
+ my_ok(thd);
+
+ break;
+ }
+
+ case SQLCOM_LOAD:
+ {
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ uint privilege= (lex->duplicates == DUP_REPLACE ?
+ INSERT_ACL | DELETE_ACL : INSERT_ACL) |
+ (lex->local_file ? 0 : FILE_ACL);
+
+ if (lex->local_file)
+ {
+ if (!(thd->client_capabilities & CLIENT_LOCAL_FILES) ||
+ !opt_local_infile)
+ {
+ my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), MYF(0));
+ goto error;
+ }
+ }
+
+ if (check_one_table_access(thd, privilege, all_tables))
+ goto error;
+
+ res= mysql_load(thd, lex->exchange, first_table, lex->field_list,
+ lex->update_list, lex->value_list, lex->duplicates,
+ lex->ignore, (bool) lex->local_file);
+ break;
+ }
+
+ case SQLCOM_SET_OPTION:
+ {
+ List<set_var_base> *lex_var_list= &lex->var_list;
+
+ if ((check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)
+ || open_and_lock_tables(thd, all_tables, TRUE, 0)))
+ goto error;
+ if (!(res= sql_set_variables(thd, lex_var_list)))
+ {
+ my_ok(thd);
+ }
+ else
+ {
+ /*
+ We encountered some sort of error, but no message was sent.
+ Send something semi-generic here since we don't know which
+ assignment in the list caused the error.
+ */
+ if (!thd->is_error())
+ my_error(ER_WRONG_ARGUMENTS,MYF(0),"SET");
+ goto error;
+ }
+
+ break;
+ }
+
+ case SQLCOM_UNLOCK_TABLES:
+ /*
+ It is critical for mysqldump --single-transaction --master-data that
+ UNLOCK TABLES does not implicitely commit a connection which has only
+ done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes
+ false, mysqldump will not work.
+ */
+ if (thd->variables.option_bits & OPTION_TABLE_LOCK)
+ {
+ res= trans_commit_implicit(thd);
+ thd->locked_tables_list.unlock_locked_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ thd->variables.option_bits&= ~(OPTION_TABLE_LOCK);
+ }
+ if (thd->global_read_lock.is_acquired())
+ thd->global_read_lock.unlock_global_read_lock(thd);
+ if (res)
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_LOCK_TABLES:
+ /* We must end the transaction first, regardless of anything */
+ res= trans_commit_implicit(thd);
+ thd->locked_tables_list.unlock_locked_tables(thd);
+ /* Release transactional metadata locks. */
+ thd->mdl_context.release_transactional_locks();
+ if (res)
+ goto error;
+
+ /*
+ Here we have to pre-open temporary tables for LOCK TABLES.
+
+ CF_PREOPEN_TMP_TABLES is not set for this SQL statement simply
+ because LOCK TABLES calls close_thread_tables() as a first thing
+ (it's called from unlock_locked_tables() above). So even if
+ CF_PREOPEN_TMP_TABLES was set and the tables would be pre-opened
+ in a usual way, they would have been closed.
+ */
+ if (open_temporary_tables(thd, all_tables))
+ goto error;
+
+ if (lock_tables_precheck(thd, all_tables))
+ goto error;
+
+ thd->variables.option_bits|= OPTION_TABLE_LOCK;
+
+ res= lock_tables_open_and_lock_tables(thd, all_tables);
+
+ if (res)
+ {
+ thd->variables.option_bits&= ~(OPTION_TABLE_LOCK);
+ }
+ else
+ {
+#ifdef HAVE_QUERY_CACHE
+ if (thd->variables.query_cache_wlock_invalidate)
+ query_cache.invalidate_locked_for_write(thd, first_table);
+#endif /*HAVE_QUERY_CACHE*/
+ my_ok(thd);
+ }
+ break;
+ case SQLCOM_CREATE_DB:
+ {
+ /*
+ As mysql_create_db() may modify HA_CREATE_INFO structure passed to
+ it, we need to use a copy of LEX::create_info to make execution
+ prepared statement- safe.
+ */
+ HA_CREATE_INFO create_info(lex->create_info);
+ if (check_db_name(&lex->name))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str);
+ break;
+ }
+ /*
+ If in a slave thread :
+ CREATE DATABASE DB was certainly not preceded by USE DB.
+ For that reason, db_ok() in sql/slave.cc did not check the
+ do_db/ignore_db. And as this query involves no tables, tables_ok()
+ above was not called. So we have to check rules again here.
+ */
+#ifdef HAVE_REPLICATION
+ if (thd->slave_thread)
+ {
+ rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter;
+ if (!rpl_filter->db_ok(lex->name.str) ||
+ !rpl_filter->db_ok_with_wild_table(lex->name.str))
+ {
+ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+ break;
+ }
+ }
+#endif
+ if (check_access(thd, CREATE_ACL, lex->name.str, NULL, NULL, 1, 0))
+ break;
+ res= mysql_create_db(thd, lex->name.str, &create_info, 0);
+ break;
+ }
+ case SQLCOM_DROP_DB:
+ {
+ if (check_db_name(&lex->name))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str);
+ break;
+ }
+ /*
+ If in a slave thread :
+ DROP DATABASE DB may not be preceded by USE DB.
+ For that reason, maybe db_ok() in sql/slave.cc did not check the
+ do_db/ignore_db. And as this query involves no tables, tables_ok()
+ above was not called. So we have to check rules again here.
+ */
+#ifdef HAVE_REPLICATION
+ if (thd->slave_thread)
+ {
+ rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter;
+ if (!rpl_filter->db_ok(lex->name.str) ||
+ !rpl_filter->db_ok_with_wild_table(lex->name.str))
+ {
+ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+ break;
+ }
+ }
+#endif
+ if (check_access(thd, DROP_ACL, lex->name.str, NULL, NULL, 1, 0))
+ break;
+ res= mysql_rm_db(thd, lex->name.str, lex->check_exists, 0);
+ break;
+ }
+ case SQLCOM_ALTER_DB_UPGRADE:
+ {
+ LEX_STRING *db= & lex->name;
+#ifdef HAVE_REPLICATION
+ if (thd->slave_thread)
+ {
+ rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter;
+ if (!rpl_filter->db_ok(db->str) ||
+ !rpl_filter->db_ok_with_wild_table(db->str))
+ {
+ res= 1;
+ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+ break;
+ }
+ }
+#endif
+ if (check_db_name(db))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), db->str);
+ break;
+ }
+ if (check_access(thd, ALTER_ACL, db->str, NULL, NULL, 1, 0) ||
+ check_access(thd, DROP_ACL, db->str, NULL, NULL, 1, 0) ||
+ check_access(thd, CREATE_ACL, db->str, NULL, NULL, 1, 0))
+ {
+ res= 1;
+ break;
+ }
+ res= mysql_upgrade_db(thd, db);
+ if (!res)
+ my_ok(thd);
+ break;
+ }
+ case SQLCOM_ALTER_DB:
+ {
+ LEX_STRING *db= &lex->name;
+ HA_CREATE_INFO create_info(lex->create_info);
+ if (check_db_name(db))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), db->str);
+ break;
+ }
+ /*
+ If in a slave thread :
+ ALTER DATABASE DB may not be preceded by USE DB.
+ For that reason, maybe db_ok() in sql/slave.cc did not check the
+ do_db/ignore_db. And as this query involves no tables, tables_ok()
+ above was not called. So we have to check rules again here.
+ */
+#ifdef HAVE_REPLICATION
+ if (thd->slave_thread)
+ {
+ rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter;
+ if (!rpl_filter->db_ok(db->str) ||
+ !rpl_filter->db_ok_with_wild_table(db->str))
+ {
+ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+ break;
+ }
+ }
+#endif
+ if (check_access(thd, ALTER_ACL, db->str, NULL, NULL, 1, 0))
+ break;
+ res= mysql_alter_db(thd, db->str, &create_info);
+ break;
+ }
+ case SQLCOM_SHOW_CREATE_DB:
+ {
+ char db_name_buff[NAME_LEN+1];
+ LEX_STRING db_name;
+ DBUG_EXECUTE_IF("4x_server_emul",
+ my_error(ER_UNKNOWN_ERROR, MYF(0)); goto error;);
+
+ db_name.str= db_name_buff;
+ db_name.length= lex->name.length;
+ strmov(db_name.str, lex->name.str);
+ if (check_db_name(&db_name))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str);
+ break;
+ }
+ res= mysqld_show_create_db(thd, &db_name, &lex->name, &lex->create_info);
+ break;
+ }
+ case SQLCOM_CREATE_EVENT:
+ case SQLCOM_ALTER_EVENT:
+ #ifdef HAVE_EVENT_SCHEDULER
+ do
+ {
+ DBUG_ASSERT(lex->event_parse_data);
+ if (lex->table_or_sp_used())
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored "
+ "function calls as part of this statement");
+ break;
+ }
+
+ res= sp_process_definer(thd);
+ if (res)
+ break;
+
+ switch (lex->sql_command) {
+ case SQLCOM_CREATE_EVENT:
+ {
+ bool if_not_exists= (lex->create_info.options &
+ HA_LEX_CREATE_IF_NOT_EXISTS);
+ res= Events::create_event(thd, lex->event_parse_data, if_not_exists);
+ break;
+ }
+ case SQLCOM_ALTER_EVENT:
+ res= Events::update_event(thd, lex->event_parse_data,
+ lex->spname ? &lex->spname->m_db : NULL,
+ lex->spname ? &lex->spname->m_name : NULL);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ DBUG_PRINT("info",("DDL error code=%d", res));
+ if (!res)
+ my_ok(thd);
+
+ } while (0);
+ /* Don't do it, if we are inside a SP */
+ if (!thd->spcont)
+ {
+ delete lex->sphead;
+ lex->sphead= NULL;
+ }
+ /* lex->unit.cleanup() is called outside, no need to call it here */
+ break;
+ case SQLCOM_SHOW_CREATE_EVENT:
+ res= Events::show_create_event(thd, lex->spname->m_db,
+ lex->spname->m_name);
+ break;
+ case SQLCOM_DROP_EVENT:
+ if (!(res= Events::drop_event(thd,
+ lex->spname->m_db, lex->spname->m_name,
+ lex->check_exists)))
+ my_ok(thd);
+ break;
+#else
+ my_error(ER_NOT_SUPPORTED_YET,MYF(0),"embedded server");
+ break;
+#endif
+ case SQLCOM_CREATE_FUNCTION: // UDF function
+ {
+ if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 0))
+ break;
+#ifdef HAVE_DLOPEN
+ if (!(res = mysql_create_function(thd, &lex->udf)))
+ my_ok(thd);
+#else
+ my_error(ER_CANT_OPEN_LIBRARY, MYF(0), lex->udf.dl, 0, "feature disabled");
+ res= TRUE;
+#endif
+ break;
+ }
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ case SQLCOM_CREATE_USER:
+ case SQLCOM_CREATE_ROLE:
+ {
+ if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 1) &&
+ check_global_access(thd,CREATE_USER_ACL))
+ break;
+ /* Conditionally writes to binlog */
+ if (!(res= mysql_create_user(thd, lex->users_list,
+ lex->sql_command == SQLCOM_CREATE_ROLE)))
+ my_ok(thd);
+ break;
+ }
+ case SQLCOM_DROP_USER:
+ case SQLCOM_DROP_ROLE:
+ {
+ if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 1) &&
+ check_global_access(thd,CREATE_USER_ACL))
+ break;
+ /* Conditionally writes to binlog */
+ if (!(res= mysql_drop_user(thd, lex->users_list,
+ lex->sql_command == SQLCOM_DROP_ROLE)))
+ my_ok(thd);
+ break;
+ }
+ case SQLCOM_RENAME_USER:
+ {
+ if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) &&
+ check_global_access(thd,CREATE_USER_ACL))
+ break;
+ /* Conditionally writes to binlog */
+ if (!(res= mysql_rename_user(thd, lex->users_list)))
+ my_ok(thd);
+ break;
+ }
+ case SQLCOM_REVOKE_ALL:
+ {
+ if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) &&
+ check_global_access(thd,CREATE_USER_ACL))
+ break;
+
+ /* Conditionally writes to binlog */
+ if (!(res = mysql_revoke_all(thd, lex->users_list)))
+ my_ok(thd);
+ break;
+ }
+ case SQLCOM_REVOKE:
+ case SQLCOM_GRANT:
+ {
+ if (lex->type != TYPE_ENUM_PROXY &&
+ check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL,
+ first_table ? first_table->db : select_lex->db,
+ first_table ? &first_table->grant.privilege : NULL,
+ first_table ? &first_table->grant.m_internal : NULL,
+ first_table ? 0 : 1, 0))
+ goto error;
+
+ /* Replicate current user as grantor */
+ thd->binlog_invoker(false);
+
+ if (thd->security_ctx->user) // If not replication
+ {
+ LEX_USER *user;
+ bool first_user= TRUE;
+
+ List_iterator <LEX_USER> user_list(lex->users_list);
+ while ((user= user_list++))
+ {
+ if (specialflag & SPECIAL_NO_RESOLVE &&
+ hostname_requires_resolving(user->host.str))
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_HOSTNAME_WONT_WORK,
+ ER(ER_WARN_HOSTNAME_WONT_WORK));
+
+ /*
+ GRANT/REVOKE PROXY has the target user as a first entry in the list.
+ */
+ if (lex->type == TYPE_ENUM_PROXY && first_user)
+ {
+ if (!(user= get_current_user(thd, user)) || !user->host.str)
+ goto error;
+
+ first_user= FALSE;
+ if (acl_check_proxy_grant_access (thd, user->host.str, user->user.str,
+ lex->grant & GRANT_ACL))
+ goto error;
+ }
+ else if (user->password.str)
+ {
+ // Are we trying to change a password of another user?
+ const char *hostname= user->host.str, *username=user->user.str;
+ bool userok;
+ if (username == current_user.str)
+ {
+ username= thd->security_ctx->priv_user;
+ hostname= thd->security_ctx->priv_host;
+ userok= true;
+ }
+ else
+ {
+ if (!hostname)
+ hostname= host_not_specified.str;
+ userok= is_acl_user(hostname, username);
+ }
+
+ if (userok && check_change_password (thd, hostname, username,
+ user->password.str,
+ user->password.length))
+ goto error;
+ }
+ }
+ }
+ if (first_table)
+ {
+ if (lex->type == TYPE_ENUM_PROCEDURE ||
+ lex->type == TYPE_ENUM_FUNCTION)
+ {
+ uint grants= lex->all_privileges
+ ? (PROC_ACLS & ~GRANT_ACL) | (lex->grant & GRANT_ACL)
+ : lex->grant;
+ if (check_grant_routine(thd, grants | GRANT_ACL, all_tables,
+ lex->type == TYPE_ENUM_PROCEDURE, 0))
+ goto error;
+ /* Conditionally writes to binlog */
+ res= mysql_routine_grant(thd, all_tables,
+ lex->type == TYPE_ENUM_PROCEDURE,
+ lex->users_list, grants,
+ lex->sql_command == SQLCOM_REVOKE, TRUE);
+ if (!res)
+ my_ok(thd);
+ }
+ else
+ {
+ if (check_grant(thd,(lex->grant | lex->grant_tot_col | GRANT_ACL),
+ all_tables, FALSE, UINT_MAX, FALSE))
+ goto error;
+ /* Conditionally writes to binlog */
+ res= mysql_table_grant(thd, all_tables, lex->users_list,
+ lex->columns, lex->grant,
+ lex->sql_command == SQLCOM_REVOKE);
+ }
+ }
+ else
+ {
+ if (lex->columns.elements || (lex->type && lex->type != TYPE_ENUM_PROXY))
+ {
+ my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE),
+ MYF(0));
+ goto error;
+ }
+ else
+ {
+ /* Conditionally writes to binlog */
+ res= mysql_grant(thd, select_lex->db, lex->users_list, lex->grant,
+ lex->sql_command == SQLCOM_REVOKE,
+ lex->type == TYPE_ENUM_PROXY);
+ }
+ if (!res)
+ {
+ if (lex->sql_command == SQLCOM_GRANT)
+ {
+ List_iterator <LEX_USER> str_list(lex->users_list);
+ LEX_USER *user, *tmp_user;
+ while ((tmp_user=str_list++))
+ {
+ if (!(user= get_current_user(thd, tmp_user)))
+ goto error;
+ reset_mqh(user, 0);
+ }
+ }
+ }
+ }
+ break;
+ }
+ case SQLCOM_REVOKE_ROLE:
+ case SQLCOM_GRANT_ROLE:
+ {
+ if (!(res= mysql_grant_role(thd, lex->users_list,
+ lex->sql_command != SQLCOM_GRANT_ROLE)))
+ my_ok(thd);
+ break;
+ }
+#endif /*!NO_EMBEDDED_ACCESS_CHECKS*/
+ case SQLCOM_RESET:
+ /*
+ RESET commands are never written to the binary log, so we have to
+ initialize this variable because RESET shares the same code as FLUSH
+ */
+ lex->no_write_to_binlog= 1;
+ case SQLCOM_FLUSH:
+ {
+ int write_to_binlog;
+ if (check_global_access(thd,RELOAD_ACL))
+ goto error;
+
+ if (first_table && lex->type & (REFRESH_READ_LOCK|REFRESH_FOR_EXPORT))
+ {
+ /* Check table-level privileges. */
+ if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables,
+ FALSE, UINT_MAX, FALSE))
+ goto error;
+
+ if (flush_tables_with_read_lock(thd, all_tables))
+ goto error;
+
+ my_ok(thd);
+ break;
+ }
+
+ /*
+ reload_acl_and_cache() will tell us if we are allowed to write to the
+ binlog or not.
+ */
+ if (!reload_acl_and_cache(thd, lex->type, first_table, &write_to_binlog))
+ {
+ /*
+ We WANT to write and we CAN write.
+ ! we write after unlocking the table.
+ */
+ /*
+ Presumably, RESET and binlog writing doesn't require synchronization
+ */
+
+ if (write_to_binlog > 0) // we should write
+ {
+ if (!lex->no_write_to_binlog)
+ res= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
+ } else if (write_to_binlog < 0)
+ {
+ /*
+ We should not write, but rather report error because
+ reload_acl_and_cache binlog interactions failed
+ */
+ res= 1;
+ }
+
+ if (!res)
+ my_ok(thd);
+ }
+
+ break;
+ }
+ case SQLCOM_KILL:
+ {
+ if (lex->table_or_sp_used())
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored "
+ "function calls as part of this statement");
+ break;
+ }
+
+ if (lex->kill_type == KILL_TYPE_ID || lex->kill_type == KILL_TYPE_QUERY)
+ {
+ Item *it= (Item *)lex->value_list.head();
+ if ((!it->fixed && it->fix_fields(lex->thd, &it)) || it->check_cols(1))
+ {
+ my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY),
+ MYF(0));
+ goto error;
+ }
+ sql_kill(thd, it->val_int(), lex->kill_signal, lex->kill_type);
+ }
+ else
+ sql_kill_user(thd, get_current_user(thd, lex->users_list.head()),
+ lex->kill_signal);
+ break;
+ }
+ case SQLCOM_SHUTDOWN:
+#ifndef EMBEDDED_LIBRARY
+ if (check_global_access(thd,SHUTDOWN_ACL))
+ goto error;
+ kill_mysql();
+ my_ok(thd);
+#else
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "embedded server");
+#endif
+ break;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ case SQLCOM_SHOW_GRANTS:
+ {
+ LEX_USER *grant_user= lex->grant_user;
+ Security_context *sctx= thd->security_ctx;
+ if (!grant_user)
+ goto error;
+
+ if (grant_user->user.str && !strcmp(sctx->priv_user, grant_user->user.str) &&
+ grant_user->host.str && !strcmp(sctx->priv_host, grant_user->host.str))
+ grant_user->user= current_user;
+
+ if (grant_user->user.str == current_user.str ||
+ grant_user->user.str == current_role.str ||
+ grant_user->user.str == current_user_and_current_role.str ||
+ !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 0))
+ {
+ res = mysql_show_grants(thd, grant_user);
+ }
+ break;
+ }
+#endif
+ case SQLCOM_HA_OPEN:
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE))
+ goto error;
+ /* Close temporary tables which were pre-opened for privilege checking. */
+ close_thread_tables(thd);
+ all_tables->table= NULL;
+ res= mysql_ha_open(thd, first_table, 0);
+ break;
+ case SQLCOM_HA_CLOSE:
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ res= mysql_ha_close(thd, first_table);
+ break;
+ case SQLCOM_HA_READ:
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ /*
+ There is no need to check for table permissions here, because
+ if a user has no permissions to read a table, he won't be
+ able to open it (with SQLCOM_HA_OPEN) in the first place.
+ */
+ unit->set_limit(select_lex);
+ res= mysql_ha_read(thd, first_table, lex->ha_read_mode, lex->ident.str,
+ lex->insert_list, lex->ha_rkey_mode, select_lex->where,
+ unit->select_limit_cnt, unit->offset_limit_cnt);
+ break;
+
+ case SQLCOM_BEGIN:
+ DBUG_PRINT("info", ("Executing SQLCOM_BEGIN thd: %p", thd));
+ if (trans_begin(thd, lex->start_transaction_opt))
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_COMMIT:
+ {
+ DBUG_ASSERT(thd->lock == NULL ||
+ thd->locked_tables_mode == LTM_LOCK_TABLES);
+ bool tx_chain= (lex->tx_chain == TVL_YES ||
+ (thd->variables.completion_type == 1 &&
+ lex->tx_chain != TVL_NO));
+ bool tx_release= (lex->tx_release == TVL_YES ||
+ (thd->variables.completion_type == 2 &&
+ lex->tx_release != TVL_NO));
+ if (trans_commit(thd))
+ goto error;
+ thd->mdl_context.release_transactional_locks();
+ /* Begin transaction with the same isolation level. */
+ if (tx_chain)
+ {
+ if (trans_begin(thd))
+ goto error;
+ }
+ else
+ {
+ /* Reset the isolation level and access mode if no chaining transaction.*/
+ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+ thd->tx_read_only= thd->variables.tx_read_only;
+ }
+ /* Disconnect the current client connection. */
+ if (tx_release)
+ {
+ thd->killed= KILL_CONNECTION;
+ thd->print_aborted_warning(3, "RELEASE");
+ }
+ my_ok(thd);
+ break;
+ }
+ case SQLCOM_ROLLBACK:
+ {
+ DBUG_ASSERT(thd->lock == NULL ||
+ thd->locked_tables_mode == LTM_LOCK_TABLES);
+ bool tx_chain= (lex->tx_chain == TVL_YES ||
+ (thd->variables.completion_type == 1 &&
+ lex->tx_chain != TVL_NO));
+ bool tx_release= (lex->tx_release == TVL_YES ||
+ (thd->variables.completion_type == 2 &&
+ lex->tx_release != TVL_NO));
+
+ if (trans_rollback(thd))
+ goto error;
+ thd->mdl_context.release_transactional_locks();
+ /* Begin transaction with the same isolation level. */
+ if (tx_chain)
+ {
+ if (trans_begin(thd))
+ goto error;
+ }
+ else
+ {
+ /* Reset the isolation level and access mode if no chaining transaction.*/
+ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+ thd->tx_read_only= thd->variables.tx_read_only;
+ }
+ /* Disconnect the current client connection. */
+ if (tx_release)
+ thd->killed= KILL_CONNECTION;
+ my_ok(thd);
+ break;
+ }
+ case SQLCOM_RELEASE_SAVEPOINT:
+ if (trans_release_savepoint(thd, lex->ident))
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_ROLLBACK_TO_SAVEPOINT:
+ if (trans_rollback_to_savepoint(thd, lex->ident))
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_SAVEPOINT:
+ if (trans_savepoint(thd, lex->ident))
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_CREATE_PROCEDURE:
+ case SQLCOM_CREATE_SPFUNCTION:
+ {
+ uint namelen;
+ char *name;
+ int sp_result= SP_INTERNAL_ERROR;
+
+ DBUG_ASSERT(lex->sphead != 0);
+ DBUG_ASSERT(lex->sphead->m_db.str); /* Must be initialized in the parser */
+ /*
+ Verify that the database name is allowed, optionally
+ lowercase it.
+ */
+ if (check_db_name(&lex->sphead->m_db))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), lex->sphead->m_db.str);
+ goto create_sp_error;
+ }
+
+ if (check_access(thd, CREATE_PROC_ACL, lex->sphead->m_db.str,
+ NULL, NULL, 0, 0))
+ goto create_sp_error;
+
+ /*
+ Check that a database directory with this name
+ exists. Design note: This won't work on virtual databases
+ like information_schema.
+ */
+ if (check_db_dir_existence(lex->sphead->m_db.str))
+ {
+ my_error(ER_BAD_DB_ERROR, MYF(0), lex->sphead->m_db.str);
+ goto create_sp_error;
+ }
+
+ name= lex->sphead->name(&namelen);
+#ifdef HAVE_DLOPEN
+ if (lex->sphead->m_type == TYPE_ENUM_FUNCTION)
+ {
+ udf_func *udf = find_udf(name, namelen);
+
+ if (udf)
+ {
+ my_error(ER_UDF_EXISTS, MYF(0), name);
+ goto create_sp_error;
+ }
+ }
+#endif
+
+ if (sp_process_definer(thd))
+ goto create_sp_error;
+
+ res= (sp_result= sp_create_routine(thd, lex->sphead->m_type, lex->sphead));
+ switch (sp_result) {
+ case SP_OK: {
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* only add privileges if really neccessary */
+
+ Security_context security_context;
+ bool restore_backup_context= false;
+ Security_context *backup= NULL;
+ LEX_USER *definer= thd->lex->definer;
+ /*
+ We're going to issue an implicit GRANT statement so we close all
+ open tables. We have to keep metadata locks as this ensures that
+ this statement is atomic against concurent FLUSH TABLES WITH READ
+ LOCK. Deadlocks which can arise due to fact that this implicit
+ statement takes metadata locks should be detected by a deadlock
+ detector in MDL subsystem and reported as errors.
+
+ No need to commit/rollback statement transaction, it's not started.
+
+ TODO: Long-term we should either ensure that implicit GRANT statement
+ is written into binary log as a separate statement or make both
+ creation of routine and implicit GRANT parts of one fully atomic
+ statement.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ close_thread_tables(thd);
+ /*
+ Check if the definer exists on slave,
+ then use definer privilege to insert routine privileges to mysql.procs_priv.
+
+ For current user of SQL thread has GLOBAL_ACL privilege,
+ which doesn't any check routine privileges,
+ so no routine privilege record will insert into mysql.procs_priv.
+ */
+ if (thd->slave_thread && is_acl_user(definer->host.str, definer->user.str))
+ {
+ security_context.change_security_context(thd,
+ &thd->lex->definer->user,
+ &thd->lex->definer->host,
+ &thd->lex->sphead->m_db,
+ &backup);
+ restore_backup_context= true;
+ }
+
+ if (sp_automatic_privileges && !opt_noacl &&
+ check_routine_access(thd, DEFAULT_CREATE_PROC_ACLS,
+ lex->sphead->m_db.str, name,
+ lex->sql_command == SQLCOM_CREATE_PROCEDURE, 1))
+ {
+ if (sp_grant_privileges(thd, lex->sphead->m_db.str, name,
+ lex->sql_command == SQLCOM_CREATE_PROCEDURE))
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_PROC_AUTO_GRANT_FAIL, ER(ER_PROC_AUTO_GRANT_FAIL));
+ thd->clear_error();
+ }
+
+ /*
+ Restore current user with GLOBAL_ACL privilege of SQL thread
+ */
+ if (restore_backup_context)
+ {
+ DBUG_ASSERT(thd->slave_thread == 1);
+ thd->security_ctx->restore_security_context(thd, backup);
+ }
+
+#endif
+ break;
+ }
+ case SP_WRITE_ROW_FAILED:
+ my_error(ER_SP_ALREADY_EXISTS, MYF(0), SP_TYPE_STRING(lex), name);
+ break;
+ case SP_BAD_IDENTIFIER:
+ my_error(ER_TOO_LONG_IDENT, MYF(0), name);
+ break;
+ case SP_BODY_TOO_LONG:
+ my_error(ER_TOO_LONG_BODY, MYF(0), name);
+ break;
+ case SP_FLD_STORE_FAILED:
+ my_error(ER_CANT_CREATE_SROUTINE, MYF(0), name);
+ break;
+ default:
+ my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(lex), name);
+ break;
+ } /* end switch */
+
+ /*
+ Capture all errors within this CASE and
+ clean up the environment.
+ */
+create_sp_error:
+ if (sp_result != SP_OK )
+ goto error;
+ my_ok(thd);
+ break; /* break super switch */
+ } /* end case group bracket */
+ case SQLCOM_CALL:
+ {
+ sp_head *sp;
+ /*
+ This will cache all SP and SF and open and lock all tables
+ required for execution.
+ */
+ if (check_table_access(thd, SELECT_ACL, all_tables, FALSE,
+ UINT_MAX, FALSE) ||
+ open_and_lock_tables(thd, all_tables, TRUE, 0))
+ goto error;
+
+ if (check_routine_access(thd, EXECUTE_ACL, lex->spname->m_db.str,
+ lex->spname->m_name.str, TRUE, FALSE))
+ goto error;
+
+ /*
+ By this moment all needed SPs should be in cache so no need to look
+ into DB.
+ */
+ if (!(sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname,
+ &thd->sp_proc_cache, TRUE)))
+ {
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PROCEDURE",
+ lex->spname->m_qname.str);
+ goto error;
+ }
+ else
+ {
+ ha_rows select_limit;
+ /* bits that should be cleared in thd->server_status */
+ uint bits_to_be_cleared= 0;
+ /*
+ Check that the stored procedure doesn't contain Dynamic SQL
+ and doesn't return result sets: such stored procedures can't
+ be called from a function or trigger.
+ */
+ if (thd->in_sub_stmt)
+ {
+ const char *where= (thd->in_sub_stmt & SUB_STMT_TRIGGER ?
+ "trigger" : "function");
+ if (sp->is_not_allowed_in_function(where))
+ goto error;
+ }
+
+ if (sp->m_flags & sp_head::MULTI_RESULTS)
+ {
+ if (! (thd->client_capabilities & CLIENT_MULTI_RESULTS))
+ {
+ /*
+ The client does not support multiple result sets being sent
+ back
+ */
+ my_error(ER_SP_BADSELECT, MYF(0), sp->m_qname.str);
+ goto error;
+ }
+ /*
+ If SERVER_MORE_RESULTS_EXISTS is not set,
+ then remember that it should be cleared
+ */
+ bits_to_be_cleared= (~thd->server_status &
+ SERVER_MORE_RESULTS_EXISTS);
+ thd->server_status|= SERVER_MORE_RESULTS_EXISTS;
+ }
+
+ select_limit= thd->variables.select_limit;
+ thd->variables.select_limit= HA_POS_ERROR;
+
+ /*
+ We never write CALL statements into binlog:
+ - If the mode is non-prelocked, each statement will be logged
+ separately.
+ - If the mode is prelocked, the invoking statement will care
+ about writing into binlog.
+ So just execute the statement.
+ */
+ res= sp->execute_procedure(thd, &lex->value_list);
+
+ thd->variables.select_limit= select_limit;
+
+ thd->server_status&= ~bits_to_be_cleared;
+
+ if (!res)
+ {
+ my_ok(thd, (thd->get_row_count_func() < 0) ? 0 : thd->get_row_count_func());
+ }
+ else
+ {
+ DBUG_ASSERT(thd->is_error() || thd->killed);
+ goto error; // Substatement should already have sent error
+ }
+ }
+ break;
+ }
+ case SQLCOM_ALTER_PROCEDURE:
+ case SQLCOM_ALTER_FUNCTION:
+ {
+ int sp_result;
+ enum stored_procedure_type type;
+ type= (lex->sql_command == SQLCOM_ALTER_PROCEDURE ?
+ TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
+
+ if (check_routine_access(thd, ALTER_PROC_ACL, lex->spname->m_db.str,
+ lex->spname->m_name.str,
+ lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0))
+ goto error;
+
+ /*
+ Note that if you implement the capability of ALTER FUNCTION to
+ alter the body of the function, this command should be made to
+ follow the restrictions that log-bin-trust-function-creators=0
+ already puts on CREATE FUNCTION.
+ */
+ /* Conditionally writes to binlog */
+ sp_result= sp_update_routine(thd, type, lex->spname, &lex->sp_chistics);
+ switch (sp_result)
+ {
+ case SP_OK:
+ my_ok(thd);
+ break;
+ case SP_KEY_NOT_FOUND:
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
+ SP_COM_STRING(lex), lex->spname->m_qname.str);
+ goto error;
+ default:
+ my_error(ER_SP_CANT_ALTER, MYF(0),
+ SP_COM_STRING(lex), lex->spname->m_qname.str);
+ goto error;
+ }
+ break;
+ }
+ case SQLCOM_DROP_PROCEDURE:
+ case SQLCOM_DROP_FUNCTION:
+ {
+#ifdef HAVE_DLOPEN
+ if (lex->sql_command == SQLCOM_DROP_FUNCTION &&
+ ! lex->spname->m_explicit_name)
+ {
+ /* DROP FUNCTION <non qualified name> */
+ udf_func *udf = find_udf(lex->spname->m_name.str,
+ lex->spname->m_name.length);
+ if (udf)
+ {
+ if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 0))
+ goto error;
+
+ if (!(res = mysql_drop_function(thd, &lex->spname->m_name)))
+ {
+ my_ok(thd);
+ break;
+ }
+ my_error(ER_SP_DROP_FAILED, MYF(0),
+ "FUNCTION (UDF)", lex->spname->m_name.str);
+ goto error;
+ }
+
+ if (lex->spname->m_db.str == NULL)
+ {
+ if (lex->check_exists)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST),
+ "FUNCTION (UDF)", lex->spname->m_name.str);
+ res= FALSE;
+ my_ok(thd);
+ break;
+ }
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
+ "FUNCTION (UDF)", lex->spname->m_name.str);
+ goto error;
+ }
+ /* Fall thought to test for a stored function */
+ }
+#endif
+
+ int sp_result;
+ enum stored_procedure_type type;
+ type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ?
+ TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
+ char *db= lex->spname->m_db.str;
+ char *name= lex->spname->m_name.str;
+
+ if (check_routine_access(thd, ALTER_PROC_ACL, db, name,
+ lex->sql_command == SQLCOM_DROP_PROCEDURE, 0))
+ goto error;
+
+ /* Conditionally writes to binlog */
+ sp_result= sp_drop_routine(thd, type, lex->spname);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ We're going to issue an implicit REVOKE statement so we close all
+ open tables. We have to keep metadata locks as this ensures that
+ this statement is atomic against concurent FLUSH TABLES WITH READ
+ LOCK. Deadlocks which can arise due to fact that this implicit
+ statement takes metadata locks should be detected by a deadlock
+ detector in MDL subsystem and reported as errors.
+
+ No need to commit/rollback statement transaction, it's not started.
+
+ TODO: Long-term we should either ensure that implicit REVOKE statement
+ is written into binary log as a separate statement or make both
+ dropping of routine and implicit REVOKE parts of one fully atomic
+ statement.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ close_thread_tables(thd);
+
+ if (sp_result != SP_KEY_NOT_FOUND &&
+ sp_automatic_privileges && !opt_noacl &&
+ sp_revoke_privileges(thd, db, name,
+ lex->sql_command == SQLCOM_DROP_PROCEDURE))
+ {
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_PROC_AUTO_REVOKE_FAIL,
+ ER(ER_PROC_AUTO_REVOKE_FAIL));
+ /* If this happens, an error should have been reported. */
+ goto error;
+ }
+#endif
+
+ res= sp_result;
+ switch (sp_result) {
+ case SP_OK:
+ my_ok(thd);
+ break;
+ case SP_KEY_NOT_FOUND:
+ if (lex->check_exists)
+ {
+ res= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST),
+ SP_COM_STRING(lex), lex->spname->m_qname.str);
+ if (!res)
+ my_ok(thd);
+ break;
+ }
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
+ SP_COM_STRING(lex), lex->spname->m_qname.str);
+ goto error;
+ default:
+ my_error(ER_SP_DROP_FAILED, MYF(0),
+ SP_COM_STRING(lex), lex->spname->m_qname.str);
+ goto error;
+ }
+ break;
+ }
+ case SQLCOM_SHOW_CREATE_PROC:
+ {
+ if (sp_show_create_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname))
+ goto error;
+ break;
+ }
+ case SQLCOM_SHOW_CREATE_FUNC:
+ {
+ if (sp_show_create_routine(thd, TYPE_ENUM_FUNCTION, lex->spname))
+ goto error;
+ break;
+ }
+ case SQLCOM_SHOW_PROC_CODE:
+ case SQLCOM_SHOW_FUNC_CODE:
+ {
+#ifndef DBUG_OFF
+ sp_head *sp;
+ stored_procedure_type type= (lex->sql_command == SQLCOM_SHOW_PROC_CODE ?
+ TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
+
+ if (sp_cache_routine(thd, type, lex->spname, FALSE, &sp))
+ goto error;
+ if (!sp || sp->show_routine_code(thd))
+ {
+ /* We don't distinguish between errors for now */
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
+ SP_COM_STRING(lex), lex->spname->m_name.str);
+ goto error;
+ }
+ break;
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0),
+ "SHOW PROCEDURE|FUNCTION CODE", "--with-debug");
+ goto error;
+#endif // ifndef DBUG_OFF
+ }
+ case SQLCOM_SHOW_CREATE_TRIGGER:
+ {
+ if (lex->spname->m_name.length > NAME_LEN)
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str);
+ goto error;
+ }
+
+ if (show_create_trigger(thd, lex->spname))
+ goto error; /* Error has been already logged. */
+
+ break;
+ }
+ case SQLCOM_CREATE_VIEW:
+ {
+ /*
+ Note: SQLCOM_CREATE_VIEW also handles 'ALTER VIEW' commands
+ as specified through the thd->lex->create_view_mode flag.
+ */
+ res= mysql_create_view(thd, first_table, thd->lex->create_view_mode);
+ break;
+ }
+ case SQLCOM_DROP_VIEW:
+ {
+ if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE))
+ goto error;
+ /* Conditionally writes to binlog. */
+ res= mysql_drop_view(thd, first_table, thd->lex->drop_mode);
+ break;
+ }
+ case SQLCOM_CREATE_TRIGGER:
+ {
+ /* Conditionally writes to binlog. */
+ res= mysql_create_or_drop_trigger(thd, all_tables, 1);
+
+ break;
+ }
+ case SQLCOM_DROP_TRIGGER:
+ {
+ /* Conditionally writes to binlog. */
+ res= mysql_create_or_drop_trigger(thd, all_tables, 0);
+ break;
+ }
+ case SQLCOM_XA_START:
+ if (trans_xa_start(thd))
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_XA_END:
+ if (trans_xa_end(thd))
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_XA_PREPARE:
+ if (trans_xa_prepare(thd))
+ goto error;
+ my_ok(thd);
+ break;
+ case SQLCOM_XA_COMMIT:
+ if (trans_xa_commit(thd))
+ goto error;
+ thd->mdl_context.release_transactional_locks();
+ /*
+ We've just done a commit, reset transaction
+ isolation level and access mode to the session default.
+ */
+ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+ thd->tx_read_only= thd->variables.tx_read_only;
+ my_ok(thd);
+ break;
+ case SQLCOM_XA_ROLLBACK:
+ if (trans_xa_rollback(thd))
+ goto error;
+ thd->mdl_context.release_transactional_locks();
+ /*
+ We've just done a rollback, reset transaction
+ isolation level and access mode to the session default.
+ */
+ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+ thd->tx_read_only= thd->variables.tx_read_only;
+ my_ok(thd);
+ break;
+ case SQLCOM_XA_RECOVER:
+ res= mysql_xa_recover(thd);
+ break;
+ case SQLCOM_ALTER_TABLESPACE:
+ if (check_global_access(thd, CREATE_TABLESPACE_ACL))
+ break;
+ if (!(res= mysql_alter_tablespace(thd, lex->alter_tablespace_info)))
+ my_ok(thd);
+ break;
+ case SQLCOM_INSTALL_PLUGIN:
+ if (! (res= mysql_install_plugin(thd, &thd->lex->comment,
+ &thd->lex->ident)))
+ my_ok(thd);
+ break;
+ case SQLCOM_UNINSTALL_PLUGIN:
+ if (! (res= mysql_uninstall_plugin(thd, &thd->lex->comment,
+ &thd->lex->ident)))
+ my_ok(thd);
+ break;
+ case SQLCOM_BINLOG_BASE64_EVENT:
+ {
+#ifndef EMBEDDED_LIBRARY
+ mysql_client_binlog_statement(thd);
+#else /* EMBEDDED_LIBRARY */
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "embedded");
+#endif /* EMBEDDED_LIBRARY */
+ break;
+ }
+ case SQLCOM_CREATE_SERVER:
+ {
+ int error;
+ LEX *lex= thd->lex;
+ DBUG_PRINT("info", ("case SQLCOM_CREATE_SERVER"));
+
+ if (check_global_access(thd, SUPER_ACL))
+ break;
+
+ if ((error= create_server(thd, &lex->server_options)))
+ {
+ DBUG_PRINT("info", ("problem creating server <%s>",
+ lex->server_options.server_name));
+ my_error(error, MYF(0), lex->server_options.server_name);
+ break;
+ }
+ my_ok(thd, 1);
+ break;
+ }
+ case SQLCOM_ALTER_SERVER:
+ {
+ int error;
+ LEX *lex= thd->lex;
+ DBUG_PRINT("info", ("case SQLCOM_ALTER_SERVER"));
+
+ if (check_global_access(thd, SUPER_ACL))
+ break;
+
+ if ((error= alter_server(thd, &lex->server_options)))
+ {
+ DBUG_PRINT("info", ("problem altering server <%s>",
+ lex->server_options.server_name));
+ my_error(error, MYF(0), lex->server_options.server_name);
+ break;
+ }
+ my_ok(thd, 1);
+ break;
+ }
+ case SQLCOM_DROP_SERVER:
+ {
+ int err_code;
+ LEX *lex= thd->lex;
+ DBUG_PRINT("info", ("case SQLCOM_DROP_SERVER"));
+
+ if (check_global_access(thd, SUPER_ACL))
+ break;
+
+ if ((err_code= drop_server(thd, &lex->server_options)))
+ {
+ if (! lex->check_exists && err_code == ER_FOREIGN_SERVER_DOESNT_EXIST)
+ {
+ DBUG_PRINT("info", ("problem dropping server %s",
+ lex->server_options.server_name));
+ my_error(err_code, MYF(0), lex->server_options.server_name);
+ }
+ else
+ {
+ my_ok(thd, 0);
+ }
+ break;
+ }
+ my_ok(thd, 1);
+ break;
+ }
+ case SQLCOM_ANALYZE:
+ case SQLCOM_CHECK:
+ case SQLCOM_OPTIMIZE:
+ case SQLCOM_REPAIR:
+ case SQLCOM_TRUNCATE:
+ case SQLCOM_ALTER_TABLE:
+ thd->query_plan_flags|= QPLAN_ADMIN;
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ /* fall through */
+ case SQLCOM_SIGNAL:
+ case SQLCOM_RESIGNAL:
+ case SQLCOM_GET_DIAGNOSTICS:
+ DBUG_ASSERT(lex->m_sql_cmd != NULL);
+ res= lex->m_sql_cmd->execute(thd);
+ break;
+ default:
+
+#ifndef EMBEDDED_LIBRARY
+ DBUG_ASSERT(0); /* Impossible */
+#endif
+ my_ok(thd);
+ break;
+ }
+ THD_STAGE_INFO(thd, stage_query_end);
+ thd->update_stats();
+
+ goto finish;
+
+error:
+ res= TRUE;
+
+finish:
+
+ DBUG_ASSERT(!thd->in_active_multi_stmt_transaction() ||
+ thd->in_multi_stmt_transaction_mode());
+
+ lex->unit.cleanup();
+
+ if (! thd->in_sub_stmt)
+ {
+ if (thd->killed != NOT_KILLED)
+ {
+ /* report error issued during command execution */
+ if (thd->killed_errno())
+ {
+ /* If we already sent 'ok', we can ignore any kill query statements */
+ if (! thd->get_stmt_da()->is_set())
+ thd->send_kill_message();
+ }
+ thd->reset_kill_query();
+ }
+ if (thd->is_error() || (thd->variables.option_bits & OPTION_MASTER_SQL_ERROR))
+ trans_rollback_stmt(thd);
+ else
+ {
+ /* If commit fails, we should be able to reset the OK status. */
+ thd->get_stmt_da()->set_overwrite_status(true);
+ trans_commit_stmt(thd);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ }
+#ifdef WITH_ARIA_STORAGE_ENGINE
+ ha_maria::implicit_commit(thd, FALSE);
+#endif
+ }
+
+ /* Free tables */
+ close_thread_tables(thd);
+
+#ifndef DBUG_OFF
+ if (lex->sql_command != SQLCOM_SET_OPTION && ! thd->in_sub_stmt)
+ DEBUG_SYNC(thd, "execute_command_after_close_tables");
+#endif
+ if (!(sql_command_flags[lex->sql_command] &
+ (CF_CAN_GENERATE_ROW_EVENTS | CF_FORCE_ORIGINAL_BINLOG_FORMAT |
+ CF_STATUS_COMMAND)))
+ thd->set_binlog_format(orig_binlog_format,
+ orig_current_stmt_binlog_format);
+
+ if (! thd->in_sub_stmt && thd->transaction_rollback_request)
+ {
+ /*
+ We are not in sub-statement and transaction rollback was requested by
+ one of storage engines (e.g. due to deadlock). Rollback transaction in
+ all storage engines including binary log.
+ */
+ trans_rollback_implicit(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+ else if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END))
+ {
+ /* No transaction control allowed in sub-statements. */
+ DBUG_ASSERT(! thd->in_sub_stmt);
+ if (!(thd->variables.option_bits & OPTION_GTID_BEGIN))
+ {
+ /* If commit fails, we should be able to reset the OK status. */
+ thd->get_stmt_da()->set_overwrite_status(true);
+ /* Commit the normal transaction if one is active. */
+ trans_commit_implicit(thd);
+ thd->get_stmt_da()->set_overwrite_status(false);
+ thd->mdl_context.release_transactional_locks();
+ }
+ }
+ else if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
+ {
+ /*
+ - If inside a multi-statement transaction,
+ defer the release of metadata locks until the current
+ transaction is either committed or rolled back. This prevents
+ other statements from modifying the table for the entire
+ duration of this transaction. This provides commit ordering
+ and guarantees serializability across multiple transactions.
+ - If in autocommit mode, or outside a transactional context,
+ automatically release metadata locks of the current statement.
+ */
+ thd->mdl_context.release_transactional_locks();
+ }
+ else if (! thd->in_sub_stmt)
+ {
+ thd->mdl_context.release_statement_locks();
+ }
+
+ DBUG_RETURN(res || thd->is_error());
+}
+
+
+static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables)
+{
+ LEX *lex= thd->lex;
+ select_result *result=lex->result;
+ bool res;
+ /* assign global limit variable if limit is not given */
+ {
+ SELECT_LEX *param= lex->unit.global_parameters;
+ if (!param->explicit_limit)
+ param->select_limit=
+ new Item_int((ulonglong) thd->variables.select_limit);
+ }
+ if (!(res= open_and_lock_tables(thd, all_tables, TRUE, 0)))
+ {
+ if (lex->describe)
+ {
+ /*
+ We always use select_send for EXPLAIN, even if it's an EXPLAIN
+ for SELECT ... INTO OUTFILE: a user application should be able
+ to prepend EXPLAIN to any query and receive output for it,
+ even if the query itself redirects the output.
+ */
+ if (!(result= new select_send()))
+ return 1; /* purecov: inspected */
+ thd->send_explain_fields(result);
+
+ /*
+ This will call optimize() for all parts of query. The query plan is
+ printed out below.
+ */
+ res= mysql_explain_union(thd, &thd->lex->unit, result);
+
+ /* Print EXPLAIN only if we don't have an error */
+ if (!res)
+ {
+ /*
+ Do like the original select_describe did: remove OFFSET from the
+ top-level LIMIT
+ */
+ result->reset_offset_limit();
+ thd->lex->explain->print_explain(result, thd->lex->describe);
+ if (lex->describe & DESCRIBE_EXTENDED)
+ {
+ char buff[1024];
+ String str(buff,(uint32) sizeof(buff), system_charset_info);
+ str.length(0);
+ /*
+ The warnings system requires input in utf8, @see
+ mysqld_show_warnings().
+ */
+ thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET);
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_YES, str.c_ptr_safe());
+ }
+ }
+
+ if (res)
+ result->abort_result_set();
+ else
+ result->send_eof();
+ delete result;
+ }
+ else
+ {
+ if (!result && !(result= new select_send()))
+ return 1; /* purecov: inspected */
+ query_cache_store_query(thd, all_tables);
+ res= handle_select(thd, lex, result, 0);
+ if (result != lex->result)
+ delete result;
+ }
+ }
+ /* Count number of empty select queries */
+ if (!thd->get_sent_row_count())
+ status_var_increment(thd->status_var.empty_queries);
+ else
+ status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count());
+ return res;
+}
+
+
+static bool execute_show_status(THD *thd, TABLE_LIST *all_tables)
+{
+ bool res;
+ system_status_var old_status_var= thd->status_var;
+ thd->initial_status_var= &old_status_var;
+ if (!(res= check_table_access(thd, SELECT_ACL, all_tables, FALSE,
+ UINT_MAX, FALSE)))
+ res= execute_sqlcom_select(thd, all_tables);
+ /* Don't log SHOW STATUS commands to slow query log */
+ thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED |
+ SERVER_QUERY_NO_GOOD_INDEX_USED);
+ /*
+ restore status variables, as we don't want 'show status' to cause
+ changes
+ */
+ mysql_mutex_lock(&LOCK_status);
+ add_diff_to_status(&global_status_var, &thd->status_var,
+ &old_status_var);
+ memcpy(&thd->status_var, &old_status_var,
+ offsetof(STATUS_VAR, last_cleared_system_status_var));
+ mysql_mutex_unlock(&LOCK_status);
+ return res;
+}
+
+
+static bool execute_rename_table(THD *thd, TABLE_LIST *first_table,
+ TABLE_LIST *all_tables)
+{
+ DBUG_ASSERT(first_table == all_tables && first_table != 0);
+ TABLE_LIST *table;
+ for (table= first_table; table; table= table->next_local->next_local)
+ {
+ if (check_access(thd, ALTER_ACL | DROP_ACL, table->db,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 0) ||
+ check_access(thd, INSERT_ACL | CREATE_ACL, table->next_local->db,
+ &table->next_local->grant.privilege,
+ &table->next_local->grant.m_internal,
+ 0, 0))
+ return 1;
+ TABLE_LIST old_list, new_list;
+ /*
+ we do not need initialize old_list and new_list because we will
+ come table[0] and table->next[0] there
+ */
+ old_list= table[0];
+ new_list= table->next_local[0];
+ if (check_grant(thd, ALTER_ACL | DROP_ACL, &old_list, FALSE, 1, FALSE) ||
+ (!test_all_bits(table->next_local->grant.privilege,
+ INSERT_ACL | CREATE_ACL) &&
+ check_grant(thd, INSERT_ACL | CREATE_ACL, &new_list, FALSE, 1,
+ FALSE)))
+ return 1;
+ }
+
+ return mysql_rename_tables(thd, first_table, 0);
+}
+
+
+/**
+ @brief Compare requested privileges with the privileges acquired from the
+ User- and Db-tables.
+ @param thd Thread handler
+ @param want_access The requested access privileges.
+ @param db A pointer to the Db name.
+ @param[out] save_priv A pointer to the granted privileges will be stored.
+ @param grant_internal_info A pointer to the internal grant cache.
+ @param dont_check_global_grants True if no global grants are checked.
+ @param no_error True if no errors should be sent to the client.
+
+ 'save_priv' is used to save the User-table (global) and Db-table grants for
+ the supplied db name. Note that we don't store db level grants if the global
+ grants is enough to satisfy the request AND the global grants contains a
+ SELECT grant.
+
+ For internal databases (INFORMATION_SCHEMA, PERFORMANCE_SCHEMA),
+ additional rules apply, see ACL_internal_schema_access.
+
+ @see check_grant
+
+ @return Status of denial of access by exclusive ACLs.
+ @retval FALSE Access can't exclusively be denied by Db- and User-table
+ access unless Column- and Table-grants are checked too.
+ @retval TRUE Access denied.
+*/
+
+bool
+check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv,
+ GRANT_INTERNAL_INFO *grant_internal_info,
+ bool dont_check_global_grants, bool no_errors)
+{
+#ifdef NO_EMBEDDED_ACCESS_CHECKS
+ if (save_priv)
+ *save_priv= GLOBAL_ACLS;
+ return false;
+#else
+ Security_context *sctx= thd->security_ctx;
+ ulong db_access;
+
+ /*
+ GRANT command:
+ In case of database level grant the database name may be a pattern,
+ in case of table|column level grant the database name can not be a pattern.
+ We use 'dont_check_global_grants' as a flag to determine
+ if it's database level grant command
+ (see SQLCOM_GRANT case, mysql_execute_command() function) and
+ set db_is_pattern according to 'dont_check_global_grants' value.
+ */
+ bool db_is_pattern= ((want_access & GRANT_ACL) && dont_check_global_grants);
+ ulong dummy;
+ DBUG_ENTER("check_access");
+ DBUG_PRINT("enter",("db: %s want_access: %lu master_access: %lu",
+ db ? db : "", want_access, sctx->master_access));
+
+ if (save_priv)
+ *save_priv=0;
+ else
+ {
+ save_priv= &dummy;
+ dummy= 0;
+ }
+
+ THD_STAGE_INFO(thd, stage_checking_permissions);
+ if ((!db || !db[0]) && !thd->db && !dont_check_global_grants)
+ {
+ DBUG_PRINT("error",("No database"));
+ if (!no_errors)
+ my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR),
+ MYF(0)); /* purecov: tested */
+ DBUG_RETURN(TRUE); /* purecov: tested */
+ }
+
+ if ((db != NULL) && (db != any_db))
+ {
+ /*
+ Check if this is reserved database, like information schema or
+ performance schema
+ */
+ const ACL_internal_schema_access *access;
+ access= get_cached_schema_access(grant_internal_info, db);
+ if (access)
+ {
+ switch (access->check(want_access, save_priv))
+ {
+ case ACL_INTERNAL_ACCESS_GRANTED:
+ /*
+ All the privileges requested have been granted internally.
+ [out] *save_privileges= Internal privileges.
+ */
+ DBUG_RETURN(FALSE);
+ case ACL_INTERNAL_ACCESS_DENIED:
+ if (! no_errors)
+ {
+ status_var_increment(thd->status_var.access_denied_errors);
+ my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
+ sctx->priv_user, sctx->priv_host, db);
+ }
+ DBUG_RETURN(TRUE);
+ case ACL_INTERNAL_ACCESS_CHECK_GRANT:
+ /*
+ Only some of the privilege requested have been granted internally,
+ proceed with the remaining bits of the request (want_access).
+ */
+ want_access&= ~(*save_priv);
+ break;
+ }
+ }
+ }
+
+ if ((sctx->master_access & want_access) == want_access)
+ {
+ /*
+ 1. If we don't have a global SELECT privilege, we have to get the
+ database specific access rights to be able to handle queries of type
+ UPDATE t1 SET a=1 WHERE b > 0
+ 2. Change db access if it isn't current db which is being addressed
+ */
+ if (!(sctx->master_access & SELECT_ACL))
+ {
+ if (db && (!thd->db || db_is_pattern || strcmp(db, thd->db)))
+ {
+ db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, db,
+ db_is_pattern);
+ if (sctx->priv_role[0])
+ db_access|= acl_get("", "", sctx->priv_role, db, db_is_pattern);
+ }
+ else
+ {
+ /* get access for current db */
+ db_access= sctx->db_access;
+ }
+ /*
+ The effective privileges are the union of the global privileges
+ and the intersection of db- and host-privileges,
+ plus the internal privileges.
+ */
+ *save_priv|= sctx->master_access | db_access;
+ }
+ else
+ *save_priv|= sctx->master_access;
+ DBUG_RETURN(FALSE);
+ }
+ if (((want_access & ~sctx->master_access) & ~DB_ACLS) ||
+ (! db && dont_check_global_grants))
+ { // We can never grant this
+ DBUG_PRINT("error",("No possible access"));
+ if (!no_errors)
+ {
+ status_var_increment(thd->status_var.access_denied_errors);
+ my_error(access_denied_error_code(thd->password), MYF(0),
+ sctx->priv_user,
+ sctx->priv_host,
+ (thd->password ?
+ ER(ER_YES) :
+ ER(ER_NO))); /* purecov: tested */
+ }
+ DBUG_RETURN(TRUE); /* purecov: tested */
+ }
+
+ if (db == any_db)
+ {
+ /*
+ Access granted; Allow select on *any* db.
+ [out] *save_privileges= 0
+ */
+ DBUG_RETURN(FALSE);
+ }
+
+ if (db && (!thd->db || db_is_pattern || strcmp(db,thd->db)))
+ {
+ db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, db,
+ db_is_pattern);
+ if (sctx->priv_role[0])
+ {
+ db_access|= acl_get("", "", sctx->priv_role, db, db_is_pattern);
+ }
+ }
+ else
+ db_access= sctx->db_access;
+ DBUG_PRINT("info",("db_access: %lu want_access: %lu",
+ db_access, want_access));
+
+ /*
+ Save the union of User-table and the intersection between Db-table and
+ Host-table privileges, with the already saved internal privileges.
+ */
+ db_access= (db_access | sctx->master_access);
+ *save_priv|= db_access;
+
+ /*
+ We need to investigate column- and table access if all requested privileges
+ belongs to the bit set of .
+ */
+ bool need_table_or_column_check=
+ (want_access & (TABLE_ACLS | PROC_ACLS | db_access)) == want_access;
+
+ /*
+ Grant access if the requested access is in the intersection of
+ host- and db-privileges (as retrieved from the acl cache),
+ also grant access if all the requested privileges are in the union of
+ TABLES_ACLS and PROC_ACLS; see check_grant.
+ */
+ if ( (db_access & want_access) == want_access ||
+ (!dont_check_global_grants &&
+ need_table_or_column_check))
+ {
+ /*
+ Ok; but need to check table- and column privileges.
+ [out] *save_privileges is (User-priv | (Db-priv & Host-priv) | Internal-priv)
+ */
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ Access is denied;
+ [out] *save_privileges is (User-priv | (Db-priv & Host-priv) | Internal-priv)
+ */
+ DBUG_PRINT("error",("Access denied"));
+ if (!no_errors)
+ {
+ status_var_increment(thd->status_var.access_denied_errors);
+ my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
+ sctx->priv_user, sctx->priv_host,
+ (db ? db : (thd->db ?
+ thd->db :
+ "unknown")));
+ }
+ DBUG_RETURN(TRUE);
+#endif // NO_EMBEDDED_ACCESS_CHECKS
+}
+
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+/**
+ Check grants for commands which work only with one table.
+
+ @param thd Thread handler
+ @param privilege requested privilege
+ @param all_tables global table list of query
+ @param no_errors FALSE/TRUE - report/don't report error to
+ the client (using my_error() call).
+
+ @retval
+ 0 OK
+ @retval
+ 1 access denied, error is sent to client
+*/
+
+bool check_single_table_access(THD *thd, ulong privilege,
+ TABLE_LIST *all_tables, bool no_errors)
+{
+ Security_context * backup_ctx= thd->security_ctx;
+
+ /* we need to switch to the saved context (if any) */
+ if (all_tables->security_ctx)
+ thd->security_ctx= all_tables->security_ctx;
+
+ const char *db_name;
+ if ((all_tables->view || all_tables->field_translation) &&
+ !all_tables->schema_table)
+ db_name= all_tables->view_db.str;
+ else
+ db_name= all_tables->db;
+
+ if (check_access(thd, privilege, db_name,
+ &all_tables->grant.privilege,
+ &all_tables->grant.m_internal,
+ 0, no_errors))
+ goto deny;
+
+ /* Show only 1 table for check_grant */
+ if (!(all_tables->belong_to_view &&
+ (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) &&
+ check_grant(thd, privilege, all_tables, FALSE, 1, no_errors))
+ goto deny;
+
+ thd->security_ctx= backup_ctx;
+ return 0;
+
+deny:
+ thd->security_ctx= backup_ctx;
+ return 1;
+}
+
+/**
+ Check grants for commands which work only with one table and all other
+ tables belonging to subselects or implicitly opened tables.
+
+ @param thd Thread handler
+ @param privilege requested privilege
+ @param all_tables global table list of query
+
+ @retval
+ 0 OK
+ @retval
+ 1 access denied, error is sent to client
+*/
+
+bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables)
+{
+ if (check_single_table_access (thd,privilege,all_tables, FALSE))
+ return 1;
+
+ /* Check rights on tables of subselects and implictly opened tables */
+ TABLE_LIST *subselects_tables, *view= all_tables->view ? all_tables : 0;
+ if ((subselects_tables= all_tables->next_global))
+ {
+ /*
+ Access rights asked for the first table of a view should be the same
+ as for the view
+ */
+ if (view && subselects_tables->belong_to_view == view)
+ {
+ if (check_single_table_access (thd, privilege, subselects_tables, FALSE))
+ return 1;
+ subselects_tables= subselects_tables->next_global;
+ }
+ if (subselects_tables &&
+ (check_table_access(thd, SELECT_ACL, subselects_tables, FALSE,
+ UINT_MAX, FALSE)))
+ return 1;
+ }
+ return 0;
+}
+
+
+static bool check_show_access(THD *thd, TABLE_LIST *table)
+{
+ /*
+ This is a SHOW command using an INFORMATION_SCHEMA table.
+ check_access() has not been called for 'table',
+ and SELECT is currently always granted on the I_S, so we automatically
+ grant SELECT on table here, to bypass a call to check_access().
+ Note that not calling check_access(table) is an optimization,
+ which needs to be revisited if the INFORMATION_SCHEMA does
+ not always automatically grant SELECT but use the grant tables.
+ See Bug#38837 need a way to disable information_schema for security
+ */
+ table->grant.privilege= SELECT_ACL;
+
+ switch (get_schema_table_idx(table->schema_table)) {
+ case SCH_SCHEMATA:
+ return (specialflag & SPECIAL_SKIP_SHOW_DB) &&
+ check_global_access(thd, SHOW_DB_ACL);
+
+ case SCH_TABLE_NAMES:
+ case SCH_TABLES:
+ case SCH_VIEWS:
+ case SCH_TRIGGERS:
+ case SCH_EVENTS:
+ {
+ const char *dst_db_name= table->schema_select_lex->db;
+
+ DBUG_ASSERT(dst_db_name);
+
+ if (check_access(thd, SELECT_ACL, dst_db_name,
+ &thd->col_access, NULL, FALSE, FALSE))
+ return TRUE;
+
+ if (!thd->col_access && check_grant_db(thd, dst_db_name))
+ {
+ status_var_increment(thd->status_var.access_denied_errors);
+ my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
+ thd->security_ctx->priv_user,
+ thd->security_ctx->priv_host,
+ dst_db_name);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ case SCH_COLUMNS:
+ case SCH_STATISTICS:
+ {
+ TABLE_LIST *dst_table;
+ dst_table= table->schema_select_lex->table_list.first;
+
+ DBUG_ASSERT(dst_table);
+
+ /*
+ Open temporary tables to be able to detect them during privilege check.
+ */
+ if (open_temporary_tables(thd, dst_table))
+ return TRUE;
+
+ if (check_access(thd, SELECT_ACL, dst_table->db,
+ &dst_table->grant.privilege,
+ &dst_table->grant.m_internal,
+ FALSE, FALSE))
+ return TRUE; /* Access denied */
+
+ /*
+ Check_grant will grant access if there is any column privileges on
+ all of the tables thanks to the fourth parameter (bool show_table).
+ */
+ if (check_grant(thd, SELECT_ACL, dst_table, TRUE, UINT_MAX, FALSE))
+ return TRUE; /* Access denied */
+
+ close_thread_tables(thd);
+ dst_table->table= NULL;
+
+ /* Access granted */
+ return FALSE;
+ }
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+
+
+/**
+ @brief Check if the requested privileges exists in either User-, Host- or
+ Db-tables.
+ @param thd Thread context
+ @param want_access Privileges requested
+ @param tables List of tables to be compared against
+ @param no_errors Don't report error to the client (using my_error() call).
+ @param any_combination_of_privileges_will_do TRUE if any privileges on any
+ column combination is enough.
+ @param number Only the first 'number' tables in the linked list are
+ relevant.
+
+ The suppled table list contains cached privileges. This functions calls the
+ help functions check_access and check_grant to verify the first three steps
+ in the privileges check queue:
+ 1. Global privileges
+ 2. OR (db privileges AND host privileges)
+ 3. OR table privileges
+ 4. OR column privileges (not checked by this function!)
+ 5. OR routine privileges (not checked by this function!)
+
+ @see check_access
+ @see check_grant
+
+ @note This functions assumes that table list used and
+ thd->lex->query_tables_own_last value correspond to each other
+ (the latter should be either 0 or point to next_global member
+ of one of elements of this table list).
+
+ @return
+ @retval FALSE OK
+ @retval TRUE Access denied; But column or routine privileges might need to
+ be checked also.
+*/
+
+bool
+check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables,
+ bool any_combination_of_privileges_will_do,
+ uint number, bool no_errors)
+{
+ TABLE_LIST *org_tables= tables;
+ TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
+ Security_context *sctx= thd->security_ctx, *backup_ctx= thd->security_ctx;
+ uint i= 0;
+ /*
+ The check that first_not_own_table is not reached is for the case when
+ the given table list refers to the list for prelocking (contains tables
+ of other queries). For simple queries first_not_own_table is 0.
+ */
+ for (; i < number && tables != first_not_own_table && tables;
+ tables= tables->next_global, i++)
+ {
+ ulong want_access= requirements;
+ if (tables->security_ctx)
+ sctx= tables->security_ctx;
+ else
+ sctx= backup_ctx;
+
+ /*
+ Register access for view underlying table.
+ Remove SHOW_VIEW_ACL, because it will be checked during making view
+ */
+ tables->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL);
+
+ if (tables->schema_table_reformed)
+ {
+ if (check_show_access(thd, tables))
+ goto deny;
+ continue;
+ }
+
+ DBUG_PRINT("info", ("derived: %d view: %d", tables->derived != 0,
+ tables->view != 0));
+
+ if (tables->is_anonymous_derived_table())
+ continue;
+
+ thd->security_ctx= sctx;
+
+ if (check_access(thd, want_access, tables->get_db_name(),
+ &tables->grant.privilege,
+ &tables->grant.m_internal,
+ 0, no_errors))
+ goto deny;
+ }
+ thd->security_ctx= backup_ctx;
+ return check_grant(thd,requirements,org_tables,
+ any_combination_of_privileges_will_do,
+ number, no_errors);
+deny:
+ thd->security_ctx= backup_ctx;
+ return TRUE;
+}
+
+
+bool
+check_routine_access(THD *thd, ulong want_access,char *db, char *name,
+ bool is_proc, bool no_errors)
+{
+ TABLE_LIST tables[1];
+
+ bzero((char *)tables, sizeof(TABLE_LIST));
+ tables->db= db;
+ tables->table_name= tables->alias= name;
+
+ /*
+ The following test is just a shortcut for check_access() (to avoid
+ calculating db_access) under the assumption that it's common to
+ give persons global right to execute all stored SP (but not
+ necessary to create them).
+ Note that this effectively bypasses the ACL_internal_schema_access checks
+ that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA,
+ which are located in check_access().
+ Since the I_S and P_S do not contain routines, this bypass is ok,
+ as long as this code path is not abused to create routines.
+ The assert enforce that.
+ */
+ DBUG_ASSERT((want_access & CREATE_PROC_ACL) == 0);
+ if ((thd->security_ctx->master_access & want_access) == want_access)
+ tables->grant.privilege= want_access;
+ else if (check_access(thd, want_access, db,
+ &tables->grant.privilege,
+ &tables->grant.m_internal,
+ 0, no_errors))
+ return TRUE;
+
+ return check_grant_routine(thd, want_access, tables, is_proc, no_errors);
+}
+
+
+/**
+ Check if the routine has any of the routine privileges.
+
+ @param thd Thread handler
+ @param db Database name
+ @param name Routine name
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool check_some_routine_access(THD *thd, const char *db, const char *name,
+ bool is_proc)
+{
+ ulong save_priv;
+ /*
+ The following test is just a shortcut for check_access() (to avoid
+ calculating db_access)
+ Note that this effectively bypasses the ACL_internal_schema_access checks
+ that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA,
+ which are located in check_access().
+ Since the I_S and P_S do not contain routines, this bypass is ok,
+ as it only opens SHOW_PROC_ACLS.
+ */
+ if (thd->security_ctx->master_access & SHOW_PROC_ACLS)
+ return FALSE;
+ if (!check_access(thd, SHOW_PROC_ACLS, db, &save_priv, NULL, 0, 1) ||
+ (save_priv & SHOW_PROC_ACLS))
+ return FALSE;
+ return check_routine_level_acl(thd, db, name, is_proc);
+}
+
+
+/*
+ Check if the given table has any of the asked privileges
+
+ @param thd Thread handler
+ @param want_access Bitmap of possible privileges to check for
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table)
+{
+ ulong access;
+ DBUG_ENTER("check_some_access");
+
+ /* This loop will work as long as we have less than 32 privileges */
+ for (access= 1; access < want_access ; access<<= 1)
+ {
+ if (access & want_access)
+ {
+ if (!check_access(thd, access, table->db,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 1) &&
+ !check_grant(thd, access, table, FALSE, 1, TRUE))
+ DBUG_RETURN(0);
+ }
+ }
+ DBUG_PRINT("exit",("no matching access rights"));
+ DBUG_RETURN(1);
+}
+
+#endif /*NO_EMBEDDED_ACCESS_CHECKS*/
+
+
+/**
+ check for global access and give descriptive error message if it fails.
+
+ @param thd Thread handler
+ @param want_access Use should have any of these global rights
+
+ @warning
+ One gets access right if one has ANY of the rights in want_access.
+ This is useful as one in most cases only need one global right,
+ but in some case we want to check if the user has SUPER or
+ REPL_CLIENT_ACL rights.
+
+ @retval
+ 0 ok
+ @retval
+ 1 Access denied. In this case an error is sent to the client
+*/
+
+bool check_global_access(THD *thd, ulong want_access, bool no_errors)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ char command[128];
+ if ((thd->security_ctx->master_access & want_access))
+ return 0;
+ if (!no_errors)
+ {
+ get_privilege_desc(command, sizeof(command), want_access);
+ my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), command);
+ }
+ status_var_increment(thd->status_var.access_denied_errors);
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+
+/**
+ Checks foreign key's parent table access.
+
+ @param thd [in] Thread handler
+ @param create_info [in] Create information (like MAX_ROWS, ENGINE or
+ temporary table flag)
+ @param alter_info [in] Initial list of columns and indexes for the
+ table to be created
+
+ @retval
+ false ok.
+ @retval
+ true error or access denied. Error is sent to client in this case.
+*/
+bool check_fk_parent_table_access(THD *thd,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info)
+{
+ Key *key;
+ List_iterator<Key> key_iterator(alter_info->key_list);
+
+ while ((key= key_iterator++))
+ {
+ if (key->type == Key::FOREIGN_KEY)
+ {
+ TABLE_LIST parent_table;
+ bool is_qualified_table_name;
+ Foreign_key *fk_key= (Foreign_key *)key;
+ LEX_STRING db_name;
+ LEX_STRING table_name= { fk_key->ref_table.str,
+ fk_key->ref_table.length };
+ const ulong privileges= (SELECT_ACL | INSERT_ACL | UPDATE_ACL |
+ DELETE_ACL | REFERENCES_ACL);
+
+ // Check if tablename is valid or not.
+ DBUG_ASSERT(table_name.str != NULL);
+ if (check_table_name(table_name.str, table_name.length, false))
+ {
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str);
+ return true;
+ }
+
+ if (fk_key->ref_db.str)
+ {
+ is_qualified_table_name= true;
+ db_name.str= (char *) thd->memdup(fk_key->ref_db.str,
+ fk_key->ref_db.length+1);
+ db_name.length= fk_key->ref_db.length;
+
+ // Check if database name is valid or not.
+ if (fk_key->ref_db.str && check_db_name(&db_name))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str);
+ return true;
+ }
+ }
+ else if (thd->lex->copy_db_to(&db_name.str, &db_name.length))
+ return true;
+ else
+ is_qualified_table_name= false;
+
+ // if lower_case_table_names is set then convert tablename to lower case.
+ if (lower_case_table_names)
+ {
+ table_name.str= (char *) thd->memdup(fk_key->ref_table.str,
+ fk_key->ref_table.length+1);
+ table_name.length= my_casedn_str(files_charset_info, table_name.str);
+ }
+
+ parent_table.init_one_table(db_name.str, db_name.length,
+ table_name.str, table_name.length,
+ table_name.str, TL_IGNORE);
+
+ /*
+ Check if user has any of the "privileges" at table level on
+ "parent_table".
+ Having privilege on any of the parent_table column is not
+ enough so checking whether user has any of the "privileges"
+ at table level only here.
+ */
+ if (check_some_access(thd, privileges, &parent_table) ||
+ parent_table.grant.want_privilege)
+ {
+ if (is_qualified_table_name)
+ {
+ const size_t qualified_table_name_len= NAME_LEN + 1 + NAME_LEN + 1;
+ char *qualified_table_name= (char *) thd->alloc(qualified_table_name_len);
+
+ my_snprintf(qualified_table_name, qualified_table_name_len, "%s.%s",
+ db_name.str, table_name.str);
+ table_name.str= qualified_table_name;
+ }
+
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
+ "REFERENCES",
+ thd->security_ctx->priv_user,
+ thd->security_ctx->host_or_ip,
+ table_name.str);
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+/****************************************************************************
+ Check stack size; Send error if there isn't enough stack to continue
+****************************************************************************/
+
+
+#if STACK_DIRECTION < 0
+#define used_stack(A,B) (long) (A - B)
+#else
+#define used_stack(A,B) (long) (B - A)
+#endif
+
+#ifndef DBUG_OFF
+long max_stack_used;
+#endif
+
+/**
+ @note
+ Note: The 'buf' parameter is necessary, even if it is unused here.
+ - fix_fields functions has a "dummy" buffer large enough for the
+ corresponding exec. (Thus we only have to check in fix_fields.)
+ - Passing to check_stack_overrun() prevents the compiler from removing it.
+*/
+bool check_stack_overrun(THD *thd, long margin,
+ uchar *buf __attribute__((unused)))
+{
+ long stack_used;
+ DBUG_ASSERT(thd == current_thd);
+ if ((stack_used=used_stack(thd->thread_stack,(char*) &stack_used)) >=
+ (long) (my_thread_stack_size - margin))
+ {
+ /*
+ Do not use stack for the message buffer to ensure correct
+ behaviour in cases we have close to no stack left.
+ */
+ char* ebuff= new char[MYSQL_ERRMSG_SIZE];
+ if (ebuff) {
+ my_snprintf(ebuff, MYSQL_ERRMSG_SIZE, ER(ER_STACK_OVERRUN_NEED_MORE),
+ stack_used, my_thread_stack_size, margin);
+ my_message(ER_STACK_OVERRUN_NEED_MORE, ebuff, MYF(ME_FATALERROR));
+ delete [] ebuff;
+ }
+ return 1;
+ }
+#ifndef DBUG_OFF
+ max_stack_used= MY_MAX(max_stack_used, stack_used);
+#endif
+ return 0;
+}
+
+
+#define MY_YACC_INIT 1000 // Start with big alloc
+#define MY_YACC_MAX 32000 // Because of 'short'
+
+bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, ulong *yystacksize)
+{
+ Yacc_state *state= & current_thd->m_parser_state->m_yacc;
+ ulong old_info=0;
+ DBUG_ASSERT(state);
+ if ((uint) *yystacksize >= MY_YACC_MAX)
+ return 1;
+ if (!state->yacc_yyvs)
+ old_info= *yystacksize;
+ *yystacksize= set_zone((*yystacksize)*2,MY_YACC_INIT,MY_YACC_MAX);
+ if (!(state->yacc_yyvs= (uchar*)
+ my_realloc(state->yacc_yyvs,
+ *yystacksize*sizeof(**yyvs),
+ MYF(MY_ALLOW_ZERO_PTR | MY_FREE_ON_ERROR))) ||
+ !(state->yacc_yyss= (uchar*)
+ my_realloc(state->yacc_yyss,
+ *yystacksize*sizeof(**yyss),
+ MYF(MY_ALLOW_ZERO_PTR | MY_FREE_ON_ERROR))))
+ return 1;
+ if (old_info)
+ {
+ /*
+ Only copy the old stack on the first call to my_yyoverflow(),
+ when replacing a static stack (YYINITDEPTH) by a dynamic stack.
+ For subsequent calls, my_realloc already did preserve the old stack.
+ */
+ memcpy(state->yacc_yyss, *yyss, old_info*sizeof(**yyss));
+ memcpy(state->yacc_yyvs, *yyvs, old_info*sizeof(**yyvs));
+ }
+ *yyss= (short*) state->yacc_yyss;
+ *yyvs= (YYSTYPE*) state->yacc_yyvs;
+ return 0;
+}
+
+
+/**
+ Reset the part of THD responsible for the state of command
+ processing.
+
+ This needs to be called before execution of every statement
+ (prepared or conventional). It is not called by substatements of
+ routines.
+
+ @todo Remove mysql_reset_thd_for_next_command and only use the
+ member function.
+
+ @todo Call it after we use THD for queries, not before.
+*/
+void mysql_reset_thd_for_next_command(THD *thd)
+{
+ thd->reset_for_next_command();
+}
+
+void THD::reset_for_next_command()
+{
+ THD *thd= this;
+ DBUG_ENTER("THD::reset_for_next_command");
+ DBUG_ASSERT(!thd->spcont); /* not for substatements of routines */
+ DBUG_ASSERT(! thd->in_sub_stmt);
+ thd->free_list= 0;
+ thd->select_number= 1;
+ /*
+ Those two lines below are theoretically unneeded as
+ THD::cleanup_after_query() should take care of this already.
+ */
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.empty();
+ thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0;
+
+ thd->query_start_used= 0;
+ thd->query_start_sec_part_used= 0;
+ thd->is_fatal_error= thd->time_zone_used= 0;
+ thd->log_current_statement= 0;
+
+ /*
+ Clear the status flag that are expected to be cleared at the
+ beginning of each SQL statement.
+ */
+ thd->server_status&= ~SERVER_STATUS_CLEAR_SET;
+ /*
+ If in autocommit mode and not in a transaction, reset
+ OPTION_STATUS_NO_TRANS_UPDATE | OPTION_KEEP_LOG to not get warnings
+ in ha_rollback_trans() about some tables couldn't be rolled back.
+ */
+ if (!thd->in_multi_stmt_transaction_mode())
+ {
+ thd->variables.option_bits&= ~OPTION_KEEP_LOG;
+ thd->transaction.all.modified_non_trans_table= FALSE;
+ }
+ DBUG_ASSERT(thd->security_ctx== &thd->main_security_ctx);
+ thd->thread_specific_used= FALSE;
+
+ if (opt_bin_log)
+ {
+ reset_dynamic(&thd->user_var_events);
+ thd->user_var_events_alloc= thd->mem_root;
+ }
+ thd->clear_error();
+ thd->get_stmt_da()->reset_diagnostics_area();
+ thd->get_stmt_da()->reset_for_next_command();
+ thd->rand_used= 0;
+ thd->m_sent_row_count= thd->m_examined_row_count= 0;
+ thd->accessed_rows_and_keys= 0;
+
+ thd->query_plan_flags= QPLAN_INIT;
+ thd->query_plan_fsort_passes= 0;
+
+ thd->reset_current_stmt_binlog_format_row();
+ thd->binlog_unsafe_warning_flags= 0;
+
+ DBUG_PRINT("debug",
+ ("is_current_stmt_binlog_format_row(): %d",
+ thd->is_current_stmt_binlog_format_row()));
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Resets the lex->current_select object.
+ @note It is assumed that lex->current_select != NULL
+
+ This function is a wrapper around select_lex->init_select() with an added
+ check for the special situation when using INTO OUTFILE and LOAD DATA.
+*/
+
+void
+mysql_init_select(LEX *lex)
+{
+ SELECT_LEX *select_lex= lex->current_select;
+ select_lex->init_select();
+ lex->wild= 0;
+ if (select_lex == &lex->select_lex)
+ {
+ DBUG_ASSERT(lex->result == 0);
+ lex->exchange= 0;
+ }
+}
+
+
+/**
+ Used to allocate a new SELECT_LEX object on the current thd mem_root and
+ link it into the relevant lists.
+
+ This function is always followed by mysql_init_select.
+
+ @see mysql_init_select
+
+ @retval TRUE An error occurred
+ @retval FALSE The new SELECT_LEX was successfully allocated.
+*/
+
+bool
+mysql_new_select(LEX *lex, bool move_down)
+{
+ SELECT_LEX *select_lex;
+ THD *thd= lex->thd;
+ DBUG_ENTER("mysql_new_select");
+
+ if (!(select_lex= new (thd->mem_root) SELECT_LEX()))
+ DBUG_RETURN(1);
+ select_lex->select_number= ++thd->select_number;
+ select_lex->parent_lex= lex; /* Used in init_query. */
+ select_lex->init_query();
+ select_lex->init_select();
+ lex->nest_level++;
+ if (lex->nest_level > (int) MAX_SELECT_NESTING)
+ {
+ my_error(ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT, MYF(0));
+ DBUG_RETURN(1);
+ }
+ select_lex->nest_level= lex->nest_level;
+ select_lex->nest_level_base= &thd->lex->unit;
+ if (move_down)
+ {
+ SELECT_LEX_UNIT *unit;
+ lex->subqueries= TRUE;
+ /* first select_lex of subselect or derived table */
+ if (!(unit= new (thd->mem_root) SELECT_LEX_UNIT()))
+ DBUG_RETURN(1);
+
+ unit->init_query();
+ unit->init_select();
+ unit->thd= thd;
+ unit->include_down(lex->current_select);
+ unit->link_next= 0;
+ unit->link_prev= 0;
+ unit->return_to= lex->current_select;
+ select_lex->include_down(unit);
+ /*
+ By default we assume that it is usual subselect and we have outer name
+ resolution context, if no we will assign it to 0 later
+ */
+ select_lex->context.outer_context= &select_lex->outer_select()->context;
+ }
+ else
+ {
+ if (lex->current_select->order_list.first && !lex->current_select->braces)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "UNION", "ORDER BY");
+ DBUG_RETURN(1);
+ }
+ select_lex->include_neighbour(lex->current_select);
+ SELECT_LEX_UNIT *unit= select_lex->master_unit();
+ if (!unit->fake_select_lex && unit->add_fake_select_lex(lex->thd))
+ DBUG_RETURN(1);
+ select_lex->context.outer_context=
+ unit->first_select()->context.outer_context;
+ }
+
+ select_lex->master_unit()->global_parameters= select_lex;
+ select_lex->include_global((st_select_lex_node**)&lex->all_selects_list);
+ lex->current_select= select_lex;
+ /*
+ in subquery is SELECT query and we allow resolution of names in SELECT
+ list
+ */
+ select_lex->context.resolve_in_select_list= TRUE;
+ DBUG_RETURN(0);
+}
+
+/**
+ Create a select to return the same output as 'SELECT @@var_name'.
+
+ Used for SHOW COUNT(*) [ WARNINGS | ERROR].
+
+ This will crash with a core dump if the variable doesn't exists.
+
+ @param var_name Variable name
+*/
+
+void create_select_for_variable(const char *var_name)
+{
+ THD *thd;
+ LEX *lex;
+ LEX_STRING tmp, null_lex_string;
+ Item *var;
+ char buff[MAX_SYS_VAR_LENGTH*2+4+8], *end;
+ DBUG_ENTER("create_select_for_variable");
+
+ thd= current_thd;
+ lex= thd->lex;
+ mysql_init_select(lex);
+ lex->sql_command= SQLCOM_SELECT;
+ tmp.str= (char*) var_name;
+ tmp.length=strlen(var_name);
+ bzero((char*) &null_lex_string.str, sizeof(null_lex_string));
+ /*
+ We set the name of Item to @@session.var_name because that then is used
+ as the column name in the output.
+ */
+ if ((var= get_system_var(thd, OPT_SESSION, tmp, null_lex_string)))
+ {
+ end= strxmov(buff, "@@session.", var_name, NullS);
+ var->set_name(buff, end-buff, system_charset_info);
+ add_item_to_list(thd, var);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void mysql_init_multi_delete(LEX *lex)
+{
+ lex->sql_command= SQLCOM_DELETE_MULTI;
+ mysql_init_select(lex);
+ lex->select_lex.select_limit= 0;
+ lex->unit.select_limit_cnt= HA_POS_ERROR;
+ lex->select_lex.table_list.save_and_clear(&lex->auxiliary_table_list);
+ lex->query_tables= 0;
+ lex->query_tables_last= &lex->query_tables;
+}
+
+
+/*
+ When you modify mysql_parse(), you may need to mofify
+ mysql_test_parse_for_slave() in this same file.
+*/
+
+/**
+ Parse a query.
+
+ @param thd Current thread
+ @param rawbuf Begining of the query text
+ @param length Length of the query text
+ @param[out] found_semicolon For multi queries, position of the character of
+ the next query in the query text.
+*/
+
+void mysql_parse(THD *thd, char *rawbuf, uint length,
+ Parser_state *parser_state)
+{
+ int error __attribute__((unused));
+ DBUG_ENTER("mysql_parse");
+ DBUG_EXECUTE_IF("parser_debug", turn_parser_debug_on(););
+
+ /*
+ Warning.
+ The purpose of query_cache_send_result_to_client() is to lookup the
+ query in the query cache first, to avoid parsing and executing it.
+ So, the natural implementation would be to:
+ - first, call query_cache_send_result_to_client,
+ - second, if caching failed, initialise the lexical and syntactic parser.
+ The problem is that the query cache depends on a clean initialization
+ of (among others) lex->safe_to_cache_query and thd->server_status,
+ which are reset respectively in
+ - lex_start()
+ - mysql_reset_thd_for_next_command()
+ So, initializing the lexical analyser *before* using the query cache
+ is required for the cache to work properly.
+ FIXME: cleanup the dependencies in the code to simplify this.
+ */
+ lex_start(thd);
+ mysql_reset_thd_for_next_command(thd);
+
+ if (query_cache_send_result_to_client(thd, rawbuf, length) <= 0)
+ {
+ LEX *lex= thd->lex;
+
+ bool err= parse_sql(thd, parser_state, NULL, true);
+
+ if (!err)
+ {
+ thd->m_statement_psi=
+ MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
+ sql_statement_info[thd->lex->sql_command].
+ m_key);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (mqh_used && thd->user_connect &&
+ check_mqh(thd, lex->sql_command))
+ {
+ thd->net.error = 0;
+ }
+ else
+#endif
+ {
+ if (! thd->is_error())
+ {
+ const char *found_semicolon= parser_state->m_lip.found_semicolon;
+ /*
+ Binlog logs a string starting from thd->query and having length
+ thd->query_length; so we set thd->query_length correctly (to not
+ log several statements in one event, when we executed only first).
+ We set it to not see the ';' (otherwise it would get into binlog
+ and Query_log_event::print() would give ';;' output).
+ This also helps display only the current query in SHOW
+ PROCESSLIST.
+ Note that we don't need LOCK_thread_count to modify query_length.
+ */
+ if (found_semicolon && (ulong) (found_semicolon - thd->query()))
+ thd->set_query_inner(thd->query(),
+ (uint32) (found_semicolon -
+ thd->query() - 1),
+ thd->charset());
+ /* Actually execute the query */
+ if (found_semicolon)
+ {
+ lex->safe_to_cache_query= 0;
+ thd->server_status|= SERVER_MORE_RESULTS_EXISTS;
+ }
+ lex->set_trg_event_type_for_tables();
+ MYSQL_QUERY_EXEC_START(thd->query(),
+ thd->thread_id,
+ (char *) (thd->db ? thd->db : ""),
+ &thd->security_ctx->priv_user[0],
+ (char *) thd->security_ctx->host_or_ip,
+ 0);
+
+ error= mysql_execute_command(thd);
+ MYSQL_QUERY_EXEC_DONE(error);
+ }
+ }
+ }
+ else
+ {
+ /* Instrument this broken statement as "statement/sql/error" */
+ thd->m_statement_psi=
+ MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
+ sql_statement_info[SQLCOM_END].m_key);
+ DBUG_ASSERT(thd->is_error());
+ DBUG_PRINT("info",("Command aborted. Fatal_error: %d",
+ thd->is_fatal_error));
+
+ query_cache_abort(&thd->query_cache_tls);
+ }
+ THD_STAGE_INFO(thd, stage_freeing_items);
+ sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size);
+ sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size);
+ thd->end_statement();
+ thd->cleanup_after_query();
+ DBUG_ASSERT(thd->change_list.is_empty());
+ }
+ else
+ {
+ /* Update statistics for getting the query from the cache */
+ thd->lex->sql_command= SQLCOM_SELECT;
+ thd->m_statement_psi=
+ MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
+ sql_statement_info[SQLCOM_SELECT].m_key);
+ status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]);
+ thd->update_stats();
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+#ifdef HAVE_REPLICATION
+/*
+ Usable by the replication SQL thread only: just parse a query to know if it
+ can be ignored because of replicate-*-table rules.
+
+ @retval
+ 0 cannot be ignored
+ @retval
+ 1 can be ignored
+*/
+
+bool mysql_test_parse_for_slave(THD *thd, char *rawbuf, uint length)
+{
+ LEX *lex= thd->lex;
+ bool error= 0;
+ DBUG_ENTER("mysql_test_parse_for_slave");
+
+ Parser_state parser_state;
+ if (!(error= parser_state.init(thd, rawbuf, length)))
+ {
+ lex_start(thd);
+ mysql_reset_thd_for_next_command(thd);
+
+ if (!parse_sql(thd, & parser_state, NULL, true) &&
+ all_tables_not_ok(thd, lex->select_lex.table_list.first))
+ error= 1; /* Ignore question */
+ thd->end_statement();
+ }
+ thd->cleanup_after_query();
+ DBUG_RETURN(error);
+}
+#endif
+
+
+
+/**
+ Store field definition for create.
+
+ @return
+ Return 0 if ok
+*/
+
+bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type,
+ char *length, char *decimals,
+ uint type_modifier,
+ Item *default_value, Item *on_update_value,
+ LEX_STRING *comment,
+ char *change,
+ List<String> *interval_list, CHARSET_INFO *cs,
+ uint uint_geom_type,
+ Virtual_column_info *vcol_info,
+ engine_option_value *create_options)
+{
+ register Create_field *new_field;
+ LEX *lex= thd->lex;
+ uint8 datetime_precision= length ? atoi(length) : 0;
+ DBUG_ENTER("add_field_to_list");
+
+ if (check_string_char_length(field_name, "", NAME_CHAR_LEN,
+ system_charset_info, 1))
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), field_name->str); /* purecov: inspected */
+ DBUG_RETURN(1); /* purecov: inspected */
+ }
+ if (type_modifier & PRI_KEY_FLAG)
+ {
+ Key *key;
+ lex->col_list.push_back(new Key_part_spec(*field_name, 0));
+ key= new Key(Key::PRIMARY, null_lex_str,
+ &default_key_create_info,
+ 0, lex->col_list, NULL, lex->check_exists);
+ lex->alter_info.key_list.push_back(key);
+ lex->col_list.empty();
+ }
+ if (type_modifier & (UNIQUE_FLAG | UNIQUE_KEY_FLAG))
+ {
+ Key *key;
+ lex->col_list.push_back(new Key_part_spec(*field_name, 0));
+ key= new Key(Key::UNIQUE, null_lex_str,
+ &default_key_create_info, 0,
+ lex->col_list, NULL, lex->check_exists);
+ lex->alter_info.key_list.push_back(key);
+ lex->col_list.empty();
+ }
+
+ if (default_value)
+ {
+ /*
+ Default value should be literal => basic constants =>
+ no need fix_fields()
+
+ We allow only one function as part of default value -
+ NOW() as default for TIMESTAMP and DATETIME type.
+ */
+ if (default_value->type() == Item::FUNC_ITEM &&
+ (static_cast<Item_func*>(default_value)->functype() !=
+ Item_func::NOW_FUNC ||
+ (mysql_type_to_time_type(type) != MYSQL_TIMESTAMP_DATETIME) ||
+ default_value->decimals < datetime_precision))
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), field_name->str);
+ DBUG_RETURN(1);
+ }
+ else if (default_value->type() == Item::NULL_ITEM)
+ {
+ default_value= 0;
+ if ((type_modifier & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) ==
+ NOT_NULL_FLAG)
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), field_name->str);
+ DBUG_RETURN(1);
+ }
+ }
+ else if (type_modifier & AUTO_INCREMENT_FLAG)
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), field_name->str);
+ DBUG_RETURN(1);
+ }
+ }
+
+ if (on_update_value &&
+ (mysql_type_to_time_type(type) != MYSQL_TIMESTAMP_DATETIME ||
+ on_update_value->decimals < datetime_precision))
+ {
+ my_error(ER_INVALID_ON_UPDATE, MYF(0), field_name->str);
+ DBUG_RETURN(1);
+ }
+
+ if (!(new_field= new Create_field()) ||
+ new_field->init(thd, field_name->str, type, length, decimals, type_modifier,
+ default_value, on_update_value, comment, change,
+ interval_list, cs, uint_geom_type, vcol_info,
+ create_options, lex->check_exists))
+ DBUG_RETURN(1);
+
+ lex->alter_info.create_list.push_back(new_field);
+ lex->last_field=new_field;
+ DBUG_RETURN(0);
+}
+
+
+/** Store position for column in ALTER TABLE .. ADD column. */
+
+void store_position_for_column(const char *name)
+{
+ current_thd->lex->last_field->after=(char*) (name);
+}
+
+bool
+add_proc_to_list(THD* thd, Item *item)
+{
+ ORDER *order;
+ Item **item_ptr;
+
+ if (!(order = (ORDER *) thd->alloc(sizeof(ORDER)+sizeof(Item*))))
+ return 1;
+ item_ptr = (Item**) (order+1);
+ *item_ptr= item;
+ order->item=item_ptr;
+ order->free_me=0;
+ thd->lex->proc_list.link_in_list(order, &order->next);
+ return 0;
+}
+
+
+/**
+ save order by and tables in own lists.
+*/
+
+bool add_to_list(THD *thd, SQL_I_List<ORDER> &list, Item *item,bool asc)
+{
+ ORDER *order;
+ DBUG_ENTER("add_to_list");
+ if (!(order = (ORDER *) thd->alloc(sizeof(ORDER))))
+ DBUG_RETURN(1);
+ order->item_ptr= item;
+ order->item= &order->item_ptr;
+ order->asc = asc;
+ order->free_me=0;
+ order->used=0;
+ order->counter_used= 0;
+ order->fast_field_copier_setup= 0;
+ list.link_in_list(order, &order->next);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Add a table to list of used tables.
+
+ @param table Table to add
+ @param alias alias for table (or null if no alias)
+ @param table_options A set of the following bits:
+ - TL_OPTION_UPDATING : Table will be updated
+ - TL_OPTION_FORCE_INDEX : Force usage of index
+ - TL_OPTION_ALIAS : an alias in multi table DELETE
+ @param lock_type How table should be locked
+ @param mdl_type Type of metadata lock to acquire on the table.
+ @param use_index List of indexed used in USE INDEX
+ @param ignore_index List of indexed used in IGNORE INDEX
+
+ @retval
+ 0 Error
+ @retval
+ \# Pointer to TABLE_LIST element added to the total table list
+*/
+
+TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
+ Table_ident *table,
+ LEX_STRING *alias,
+ ulong table_options,
+ thr_lock_type lock_type,
+ enum_mdl_type mdl_type,
+ List<Index_hint> *index_hints_arg,
+ List<String> *partition_names,
+ LEX_STRING *option)
+{
+ register TABLE_LIST *ptr;
+ TABLE_LIST *previous_table_ref; /* The table preceding the current one. */
+ char *alias_str;
+ LEX *lex= thd->lex;
+ DBUG_ENTER("add_table_to_list");
+ LINT_INIT(previous_table_ref);
+
+ if (!table)
+ DBUG_RETURN(0); // End of memory
+ alias_str= alias ? alias->str : table->table.str;
+ if (!MY_TEST(table_options & TL_OPTION_ALIAS) &&
+ check_table_name(table->table.str, table->table.length, FALSE))
+ {
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), table->table.str);
+ DBUG_RETURN(0);
+ }
+
+ if (table->is_derived_table() == FALSE && table->db.str &&
+ check_db_name(&table->db))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), table->db.str);
+ DBUG_RETURN(0);
+ }
+
+ if (!alias) /* Alias is case sensitive */
+ {
+ if (table->sel)
+ {
+ my_message(ER_DERIVED_MUST_HAVE_ALIAS,
+ ER(ER_DERIVED_MUST_HAVE_ALIAS), MYF(0));
+ DBUG_RETURN(0);
+ }
+ if (!(alias_str= (char*) thd->memdup(alias_str,table->table.length+1)))
+ DBUG_RETURN(0);
+ }
+ if (!(ptr = (TABLE_LIST *) thd->calloc(sizeof(TABLE_LIST))))
+ DBUG_RETURN(0); /* purecov: inspected */
+ if (table->db.str)
+ {
+ ptr->is_fqtn= TRUE;
+ ptr->db= table->db.str;
+ ptr->db_length= table->db.length;
+ }
+ else if (lex->copy_db_to(&ptr->db, &ptr->db_length))
+ DBUG_RETURN(0);
+ else
+ ptr->is_fqtn= FALSE;
+
+ ptr->alias= alias_str;
+ ptr->is_alias= alias ? TRUE : FALSE;
+ if (lower_case_table_names)
+ {
+ if (table->table.length)
+ table->table.length= my_casedn_str(files_charset_info, table->table.str);
+ if (ptr->db_length && ptr->db != any_db)
+ ptr->db_length= my_casedn_str(files_charset_info, ptr->db);
+ }
+
+ ptr->table_name=table->table.str;
+ ptr->table_name_length=table->table.length;
+ ptr->lock_type= lock_type;
+ ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING);
+ /* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */
+ ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX);
+ ptr->ignore_leaves= MY_TEST(table_options & TL_OPTION_IGNORE_LEAVES);
+ ptr->derived= table->sel;
+ if (!ptr->derived && is_infoschema_db(ptr->db, ptr->db_length))
+ {
+ ST_SCHEMA_TABLE *schema_table;
+ if (ptr->updating &&
+ /* Special cases which are processed by commands itself */
+ lex->sql_command != SQLCOM_CHECK &&
+ lex->sql_command != SQLCOM_CHECKSUM)
+ {
+ my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
+ thd->security_ctx->priv_user,
+ thd->security_ctx->priv_host,
+ INFORMATION_SCHEMA_NAME.str);
+ DBUG_RETURN(0);
+ }
+ schema_table= find_schema_table(thd, ptr->table_name);
+ if (!schema_table ||
+ (schema_table->hidden &&
+ ((sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) == 0 ||
+ /*
+ this check is used for show columns|keys from I_S hidden table
+ */
+ lex->sql_command == SQLCOM_SHOW_FIELDS ||
+ lex->sql_command == SQLCOM_SHOW_KEYS)))
+ {
+ my_error(ER_UNKNOWN_TABLE, MYF(0),
+ ptr->table_name, INFORMATION_SCHEMA_NAME.str);
+ DBUG_RETURN(0);
+ }
+ ptr->schema_table_name= ptr->table_name;
+ ptr->schema_table= schema_table;
+ }
+ ptr->select_lex= lex->current_select;
+ /*
+ We can't cache internal temporary tables between prepares as the
+ table may be deleted before next exection.
+ */
+ ptr->cacheable_table= !table->is_derived_table();
+ ptr->index_hints= index_hints_arg;
+ ptr->option= option ? option->str : 0;
+ /* check that used name is unique */
+ if (lock_type != TL_IGNORE)
+ {
+ TABLE_LIST *first_table= table_list.first;
+ if (lex->sql_command == SQLCOM_CREATE_VIEW)
+ first_table= first_table ? first_table->next_local : NULL;
+ for (TABLE_LIST *tables= first_table ;
+ tables ;
+ tables=tables->next_local)
+ {
+ if (!my_strcasecmp(table_alias_charset, alias_str, tables->alias) &&
+ !strcmp(ptr->db, tables->db))
+ {
+ my_error(ER_NONUNIQ_TABLE, MYF(0), alias_str); /* purecov: tested */
+ DBUG_RETURN(0); /* purecov: tested */
+ }
+ }
+ }
+ /* Store the table reference preceding the current one. */
+ if (table_list.elements > 0)
+ {
+ /*
+ table_list.next points to the last inserted TABLE_LIST->next_local'
+ element
+ We don't use the offsetof() macro here to avoid warnings from gcc
+ */
+ previous_table_ref= (TABLE_LIST*) ((char*) table_list.next -
+ ((char*) &(ptr->next_local) -
+ (char*) ptr));
+ /*
+ Set next_name_resolution_table of the previous table reference to point
+ to the current table reference. In effect the list
+ TABLE_LIST::next_name_resolution_table coincides with
+ TABLE_LIST::next_local. Later this may be changed in
+ store_top_level_join_columns() for NATURAL/USING joins.
+ */
+ previous_table_ref->next_name_resolution_table= ptr;
+ }
+
+ /*
+ Link the current table reference in a local list (list for current select).
+ Notice that as a side effect here we set the next_local field of the
+ previous table reference to 'ptr'. Here we also add one element to the
+ list 'table_list'.
+ */
+ table_list.link_in_list(ptr, &ptr->next_local);
+ ptr->next_name_resolution_table= NULL;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ ptr->partition_names= partition_names;
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
+ /* Link table in global list (all used tables) */
+ lex->add_to_query_tables(ptr);
+
+ // Pure table aliases do not need to be locked:
+ if (!MY_TEST(table_options & TL_OPTION_ALIAS))
+ {
+ ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type,
+ MDL_TRANSACTION);
+ }
+ DBUG_RETURN(ptr);
+}
+
+
+/**
+ Initialize a new table list for a nested join.
+
+ The function initializes a structure of the TABLE_LIST type
+ for a nested join. It sets up its nested join list as empty.
+ The created structure is added to the front of the current
+ join list in the st_select_lex object. Then the function
+ changes the current nest level for joins to refer to the newly
+ created empty list after having saved the info on the old level
+ in the initialized structure.
+
+ @param thd current thread
+
+ @retval
+ 0 if success
+ @retval
+ 1 otherwise
+*/
+
+bool st_select_lex::init_nested_join(THD *thd)
+{
+ TABLE_LIST *ptr;
+ NESTED_JOIN *nested_join;
+ DBUG_ENTER("init_nested_join");
+
+ if (!(ptr= (TABLE_LIST*) thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST))+
+ sizeof(NESTED_JOIN))))
+ DBUG_RETURN(1);
+ nested_join= ptr->nested_join=
+ ((NESTED_JOIN*) ((uchar*) ptr + ALIGN_SIZE(sizeof(TABLE_LIST))));
+
+ join_list->push_front(ptr);
+ ptr->embedding= embedding;
+ ptr->join_list= join_list;
+ ptr->alias= (char*) "(nested_join)";
+ embedding= ptr;
+ join_list= &nested_join->join_list;
+ join_list->empty();
+ DBUG_RETURN(0);
+}
+
+
+/**
+ End a nested join table list.
+
+ The function returns to the previous join nest level.
+ If the current level contains only one member, the function
+ moves it one level up, eliminating the nest.
+
+ @param thd current thread
+
+ @return
+ - Pointer to TABLE_LIST element added to the total table list, if success
+ - 0, otherwise
+*/
+
+TABLE_LIST *st_select_lex::end_nested_join(THD *thd)
+{
+ TABLE_LIST *ptr;
+ NESTED_JOIN *nested_join;
+ DBUG_ENTER("end_nested_join");
+
+ DBUG_ASSERT(embedding);
+ ptr= embedding;
+ join_list= ptr->join_list;
+ embedding= ptr->embedding;
+ nested_join= ptr->nested_join;
+ if (nested_join->join_list.elements == 1)
+ {
+ TABLE_LIST *embedded= nested_join->join_list.head();
+ join_list->pop();
+ embedded->join_list= join_list;
+ embedded->embedding= embedding;
+ join_list->push_front(embedded);
+ ptr= embedded;
+ embedded->lifted= 1;
+ }
+ else if (nested_join->join_list.elements == 0)
+ {
+ join_list->pop();
+ ptr= 0; // return value
+ }
+ DBUG_RETURN(ptr);
+}
+
+
+/**
+ Nest last join operation.
+
+ The function nest last join operation as if it was enclosed in braces.
+
+ @param thd current thread
+
+ @retval
+ 0 Error
+ @retval
+ \# Pointer to TABLE_LIST element created for the new nested join
+*/
+
+TABLE_LIST *st_select_lex::nest_last_join(THD *thd)
+{
+ TABLE_LIST *ptr;
+ NESTED_JOIN *nested_join;
+ List<TABLE_LIST> *embedded_list;
+ DBUG_ENTER("nest_last_join");
+
+ if (!(ptr= (TABLE_LIST*) thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST))+
+ sizeof(NESTED_JOIN))))
+ DBUG_RETURN(0);
+ nested_join= ptr->nested_join=
+ ((NESTED_JOIN*) ((uchar*) ptr + ALIGN_SIZE(sizeof(TABLE_LIST))));
+
+ ptr->embedding= embedding;
+ ptr->join_list= join_list;
+ ptr->alias= (char*) "(nest_last_join)";
+ embedded_list= &nested_join->join_list;
+ embedded_list->empty();
+
+ for (uint i=0; i < 2; i++)
+ {
+ TABLE_LIST *table= join_list->pop();
+ if (!table)
+ DBUG_RETURN(NULL);
+ table->join_list= embedded_list;
+ table->embedding= ptr;
+ embedded_list->push_back(table);
+ if (table->natural_join)
+ {
+ ptr->is_natural_join= TRUE;
+ /*
+ If this is a JOIN ... USING, move the list of joined fields to the
+ table reference that describes the join.
+ */
+ if (prev_join_using)
+ ptr->join_using_fields= prev_join_using;
+ }
+ }
+ join_list->push_front(ptr);
+ nested_join->used_tables= nested_join->not_null_tables= (table_map) 0;
+ DBUG_RETURN(ptr);
+}
+
+
+/**
+ Add a table to the current join list.
+
+ The function puts a table in front of the current join list
+ of st_select_lex object.
+ Thus, joined tables are put into this list in the reverse order
+ (the most outer join operation follows first).
+
+ @param table the table to add
+
+ @return
+ None
+*/
+
+void st_select_lex::add_joined_table(TABLE_LIST *table)
+{
+ DBUG_ENTER("add_joined_table");
+ join_list->push_front(table);
+ table->join_list= join_list;
+ table->embedding= embedding;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Convert a right join into equivalent left join.
+
+ The function takes the current join list t[0],t[1] ... and
+ effectively converts it into the list t[1],t[0] ...
+ Although the outer_join flag for the new nested table contains
+ JOIN_TYPE_RIGHT, it will be handled as the inner table of a left join
+ operation.
+
+ EXAMPLES
+ @verbatim
+ SELECT * FROM t1 RIGHT JOIN t2 ON on_expr =>
+ SELECT * FROM t2 LEFT JOIN t1 ON on_expr
+
+ SELECT * FROM t1,t2 RIGHT JOIN t3 ON on_expr =>
+ SELECT * FROM t1,t3 LEFT JOIN t2 ON on_expr
+
+ SELECT * FROM t1,t2 RIGHT JOIN (t3,t4) ON on_expr =>
+ SELECT * FROM t1,(t3,t4) LEFT JOIN t2 ON on_expr
+
+ SELECT * FROM t1 LEFT JOIN t2 ON on_expr1 RIGHT JOIN t3 ON on_expr2 =>
+ SELECT * FROM t3 LEFT JOIN (t1 LEFT JOIN t2 ON on_expr2) ON on_expr1
+ @endverbatim
+
+ @param thd current thread
+
+ @return
+ - Pointer to the table representing the inner table, if success
+ - 0, otherwise
+*/
+
+TABLE_LIST *st_select_lex::convert_right_join()
+{
+ TABLE_LIST *tab2= join_list->pop();
+ TABLE_LIST *tab1= join_list->pop();
+ DBUG_ENTER("convert_right_join");
+
+ join_list->push_front(tab2);
+ join_list->push_front(tab1);
+ tab1->outer_join|= JOIN_TYPE_RIGHT;
+
+ DBUG_RETURN(tab1);
+}
+
+/**
+ Set lock for all tables in current select level.
+
+ @param lock_type Lock to set for tables
+
+ @note
+ If lock is a write lock, then tables->updating is set 1
+ This is to get tables_ok to know that the table is updated by the
+ query
+*/
+
+void st_select_lex::set_lock_for_tables(thr_lock_type lock_type)
+{
+ bool for_update= lock_type >= TL_READ_NO_INSERT;
+ DBUG_ENTER("set_lock_for_tables");
+ DBUG_PRINT("enter", ("lock_type: %d for_update: %d", lock_type,
+ for_update));
+ for (TABLE_LIST *tables= table_list.first;
+ tables;
+ tables= tables->next_local)
+ {
+ tables->lock_type= lock_type;
+ tables->updating= for_update;
+ tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Create a fake SELECT_LEX for a unit.
+
+ The method create a fake SELECT_LEX object for a unit.
+ This object is created for any union construct containing a union
+ operation and also for any single select union construct of the form
+ @verbatim
+ (SELECT ... ORDER BY order_list [LIMIT n]) ORDER BY ...
+ @endvarbatim
+ or of the form
+ @varbatim
+ (SELECT ... ORDER BY LIMIT n) ORDER BY ...
+ @endvarbatim
+
+ @param thd_arg thread handle
+
+ @note
+ The object is used to retrieve rows from the temporary table
+ where the result on the union is obtained.
+
+ @retval
+ 1 on failure to create the object
+ @retval
+ 0 on success
+*/
+
+bool st_select_lex_unit::add_fake_select_lex(THD *thd_arg)
+{
+ SELECT_LEX *first_sl= first_select();
+ DBUG_ENTER("add_fake_select_lex");
+ DBUG_ASSERT(!fake_select_lex);
+
+ if (!(fake_select_lex= new (thd_arg->mem_root) SELECT_LEX()))
+ DBUG_RETURN(1);
+ fake_select_lex->include_standalone(this,
+ (SELECT_LEX_NODE**)&fake_select_lex);
+ fake_select_lex->select_number= INT_MAX;
+ fake_select_lex->parent_lex= thd_arg->lex; /* Used in init_query. */
+ fake_select_lex->make_empty_select();
+ fake_select_lex->linkage= GLOBAL_OPTIONS_TYPE;
+ fake_select_lex->select_limit= 0;
+
+ fake_select_lex->context.outer_context=first_sl->context.outer_context;
+ /* allow item list resolving in fake select for ORDER BY */
+ fake_select_lex->context.resolve_in_select_list= TRUE;
+ fake_select_lex->context.select_lex= fake_select_lex;
+
+ if (!is_union())
+ {
+ /*
+ This works only for
+ (SELECT ... ORDER BY list [LIMIT n]) ORDER BY order_list [LIMIT m],
+ (SELECT ... LIMIT n) ORDER BY order_list [LIMIT m]
+ just before the parser starts processing order_list
+ */
+ global_parameters= fake_select_lex;
+ fake_select_lex->no_table_names_allowed= 1;
+ thd_arg->lex->current_select= fake_select_lex;
+ }
+ thd_arg->lex->pop_context();
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Push a new name resolution context for a JOIN ... ON clause to the
+ context stack of a query block.
+
+ Create a new name resolution context for a JOIN ... ON clause,
+ set the first and last leaves of the list of table references
+ to be used for name resolution, and push the newly created
+ context to the stack of contexts of the query.
+
+ @param thd pointer to current thread
+ @param left_op left operand of the JOIN
+ @param right_op rigth operand of the JOIN
+
+ @retval
+ FALSE if all is OK
+ @retval
+ TRUE if a memory allocation error occured
+*/
+
+bool
+push_new_name_resolution_context(THD *thd,
+ TABLE_LIST *left_op, TABLE_LIST *right_op)
+{
+ Name_resolution_context *on_context;
+ if (!(on_context= new (thd->mem_root) Name_resolution_context))
+ return TRUE;
+ on_context->init();
+ on_context->first_name_resolution_table=
+ left_op->first_leaf_for_name_resolution();
+ on_context->last_name_resolution_table=
+ right_op->last_leaf_for_name_resolution();
+ return thd->lex->push_context(on_context);
+}
+
+
+/**
+ Fix condition which contains only field (f turns to f <> 0 )
+
+ @param cond The condition to fix
+
+ @return fixed condition
+*/
+
+Item *normalize_cond(Item *cond)
+{
+ if (cond)
+ {
+ Item::Type type= cond->type();
+ if (type == Item::FIELD_ITEM || type == Item::REF_ITEM)
+ {
+ cond= new Item_func_ne(cond, new Item_int(0));
+ }
+ }
+ return cond;
+}
+
+
+/**
+ Add an ON condition to the second operand of a JOIN ... ON.
+
+ Add an ON condition to the right operand of a JOIN ... ON clause.
+
+ @param b the second operand of a JOIN ... ON
+ @param expr the condition to be added to the ON clause
+
+ @retval
+ FALSE if there was some error
+ @retval
+ TRUE if all is OK
+*/
+
+void add_join_on(TABLE_LIST *b, Item *expr)
+{
+ if (expr)
+ {
+ expr= normalize_cond(expr);
+ if (!b->on_expr)
+ b->on_expr= expr;
+ else
+ {
+ /*
+ If called from the parser, this happens if you have both a
+ right and left join. If called later, it happens if we add more
+ than one condition to the ON clause.
+ */
+ b->on_expr= new Item_cond_and(b->on_expr,expr);
+ }
+ b->on_expr->top_level_item();
+ }
+}
+
+
+/**
+ Mark that there is a NATURAL JOIN or JOIN ... USING between two
+ tables.
+
+ This function marks that table b should be joined with a either via
+ a NATURAL JOIN or via JOIN ... USING. Both join types are special
+ cases of each other, so we treat them together. The function
+ setup_conds() creates a list of equal condition between all fields
+ of the same name for NATURAL JOIN or the fields in 'using_fields'
+ for JOIN ... USING. The list of equality conditions is stored
+ either in b->on_expr, or in JOIN::conds, depending on whether there
+ was an outer join.
+
+ EXAMPLE
+ @verbatim
+ SELECT * FROM t1 NATURAL LEFT JOIN t2
+ <=>
+ SELECT * FROM t1 LEFT JOIN t2 ON (t1.i=t2.i and t1.j=t2.j ... )
+
+ SELECT * FROM t1 NATURAL JOIN t2 WHERE <some_cond>
+ <=>
+ SELECT * FROM t1, t2 WHERE (t1.i=t2.i and t1.j=t2.j and <some_cond>)
+
+ SELECT * FROM t1 JOIN t2 USING(j) WHERE <some_cond>
+ <=>
+ SELECT * FROM t1, t2 WHERE (t1.j=t2.j and <some_cond>)
+ @endverbatim
+
+ @param a Left join argument
+ @param b Right join argument
+ @param using_fields Field names from USING clause
+*/
+
+void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List<String> *using_fields,
+ SELECT_LEX *lex)
+{
+ b->natural_join= a;
+ lex->prev_join_using= using_fields;
+}
+
+
+/**
+ Find a thread by id and return it, locking it LOCK_thd_data
+
+ @param id Identifier of the thread we're looking for
+ @param query_id If true, search by query_id instead of thread_id
+
+ @return NULL - not found
+ pointer - thread found, and its LOCK_thd_data is locked.
+*/
+
+THD *find_thread_by_id(longlong id, bool query_id)
+{
+ THD *tmp;
+ mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
+ I_List_iterator<THD> it(threads);
+ while ((tmp=it++))
+ {
+ if (tmp->get_command() == COM_DAEMON)
+ continue;
+ if (id == (query_id ? tmp->query_id : (longlong) tmp->thread_id))
+ {
+ mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
+ break;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+ return tmp;
+}
+
+
+/**
+ kill one thread.
+
+ @param thd Thread class
+ @param id Thread id or query id
+ @param kill_signal Should it kill the query or the connection
+ @param type Type of id: thread id or query id
+
+ @note
+ This is written such that we have a short lock on LOCK_thread_count
+*/
+
+uint
+kill_one_thread(THD *thd, longlong id, killed_state kill_signal, killed_type type)
+{
+ THD *tmp;
+ uint error= (type == KILL_TYPE_QUERY ? ER_NO_SUCH_QUERY : ER_NO_SUCH_THREAD);
+ DBUG_ENTER("kill_one_thread");
+ DBUG_PRINT("enter", ("id: %lld signal: %u", id, (uint) kill_signal));
+
+ if (id && (tmp= find_thread_by_id(id, type == KILL_TYPE_QUERY)))
+ {
+ /*
+ If we're SUPER, we can KILL anything, including system-threads.
+ No further checks.
+
+ KILLer: thd->security_ctx->user could in theory be NULL while
+ we're still in "unauthenticated" state. This is a theoretical
+ case (the code suggests this could happen, so we play it safe).
+
+ KILLee: tmp->security_ctx->user will be NULL for system threads.
+ We need to check so Jane Random User doesn't crash the server
+ when trying to kill a) system threads or b) unauthenticated users'
+ threads (Bug#43748).
+
+ If user of both killer and killee are non-NULL, proceed with
+ slayage if both are string-equal.
+
+ It's ok to also kill DELAYED threads with KILL_CONNECTION instead of
+ KILL_SYSTEM_THREAD; The difference is that KILL_CONNECTION may be
+ faster and do a harder kill than KILL_SYSTEM_THREAD;
+ */
+
+ if ((thd->security_ctx->master_access & SUPER_ACL) ||
+ thd->security_ctx->user_matches(tmp->security_ctx))
+ {
+ tmp->awake(kill_signal);
+ error=0;
+ }
+ else
+ error=ER_KILL_DENIED_ERROR;
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+ }
+ DBUG_PRINT("exit", ("%d", error));
+ DBUG_RETURN(error);
+}
+
+
+/**
+ kill all threads from one user
+
+ @param thd Thread class
+ @param user_name User name for threads we should kill
+ @param only_kill_query Should it kill the query or the connection
+
+ @note
+ This is written such that we have a short lock on LOCK_thread_count
+
+ If we can't kill all threads because of security issues, no threads
+ are killed.
+*/
+
+static uint kill_threads_for_user(THD *thd, LEX_USER *user,
+ killed_state kill_signal, ha_rows *rows)
+{
+ THD *tmp;
+ List<THD> threads_to_kill;
+ DBUG_ENTER("kill_threads_for_user");
+
+ *rows= 0;
+
+ if (thd->is_fatal_error) // If we run out of memory
+ DBUG_RETURN(ER_OUT_OF_RESOURCES);
+
+ DBUG_PRINT("enter", ("user: %s signal: %u", user->user.str,
+ (uint) kill_signal));
+
+ mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
+ I_List_iterator<THD> it(threads);
+ while ((tmp=it++))
+ {
+ if (!tmp->security_ctx->user)
+ continue;
+ /*
+ Check that hostname (if given) and user name matches.
+
+ host.str[0] == '%' means that host name was not given. See sql_yacc.yy
+ */
+ if (((user->host.str[0] == '%' && !user->host.str[1]) ||
+ !strcmp(tmp->security_ctx->host_or_ip, user->host.str)) &&
+ !strcmp(tmp->security_ctx->user, user->user.str))
+ {
+ if (!(thd->security_ctx->master_access & SUPER_ACL) &&
+ !thd->security_ctx->user_matches(tmp->security_ctx))
+ {
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_RETURN(ER_KILL_DENIED_ERROR);
+ }
+ if (!threads_to_kill.push_back(tmp, thd->mem_root))
+ mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
+ }
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+ if (!threads_to_kill.is_empty())
+ {
+ List_iterator_fast<THD> it(threads_to_kill);
+ THD *next_ptr;
+ THD *ptr= it++;
+ do
+ {
+ ptr->awake(kill_signal);
+ /*
+ Careful here: The list nodes are allocated on the memroots of the
+ THDs to be awakened.
+ But those THDs may be terminated and deleted as soon as we release
+ LOCK_thd_data, which will make the list nodes invalid.
+ Since the operation "it++" dereferences the "next" pointer of the
+ previous list node, we need to do this while holding LOCK_thd_data.
+ */
+ next_ptr= it++;
+ mysql_mutex_unlock(&ptr->LOCK_thd_data);
+ (*rows)++;
+ } while ((ptr= next_ptr));
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ kills a thread and sends response.
+
+ @param thd Thread class
+ @param id Thread id or query id
+ @param state Should it kill the query or the connection
+ @param type Type of id: thread id or query id
+*/
+
+static
+void sql_kill(THD *thd, longlong id, killed_state state, killed_type type)
+{
+ uint error;
+ if (!(error= kill_one_thread(thd, id, state, type)))
+ {
+ if ((!thd->killed))
+ my_ok(thd);
+ else
+ my_error(killed_errno(thd->killed), MYF(0), id);
+ }
+ else
+ my_error(error, MYF(0), id);
+}
+
+
+static
+void sql_kill_user(THD *thd, LEX_USER *user, killed_state state)
+{
+ uint error;
+ ha_rows rows;
+ if (!(error= kill_threads_for_user(thd, user, state, &rows)))
+ my_ok(thd, rows);
+ else
+ {
+ /*
+ This is probably ER_OUT_OF_RESOURCES, but in the future we may
+ want to write the name of the user we tried to kill
+ */
+ my_error(error, MYF(0), user->host.str, user->user.str);
+ }
+}
+
+
+/** If pointer is not a null pointer, append filename to it. */
+
+bool append_file_to_dir(THD *thd, const char **filename_ptr,
+ const char *table_name)
+{
+ char buff[FN_REFLEN],*ptr, *end;
+ if (!*filename_ptr)
+ return 0; // nothing to do
+
+ /* Check that the filename is not too long and it's a hard path */
+ if (strlen(*filename_ptr)+strlen(table_name) >= FN_REFLEN-1 ||
+ !test_if_hard_path(*filename_ptr))
+ {
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), *filename_ptr);
+ return 1;
+ }
+ /* Fix is using unix filename format on dos */
+ strmov(buff,*filename_ptr);
+ end=convert_dirname(buff, *filename_ptr, NullS);
+ if (!(ptr= (char*) thd->alloc((size_t) (end-buff) + strlen(table_name)+1)))
+ return 1; // End of memory
+ *filename_ptr=ptr;
+ strxmov(ptr,buff,table_name,NullS);
+ return 0;
+}
+
+
+/**
+ Check if the select is a simple select (not an union).
+
+ @retval
+ 0 ok
+ @retval
+ 1 error ; In this case the error messege is sent to the client
+*/
+
+bool check_simple_select()
+{
+ THD *thd= current_thd;
+ LEX *lex= thd->lex;
+ if (lex->current_select != &lex->select_lex)
+ {
+ char command[80];
+ Lex_input_stream *lip= & thd->m_parser_state->m_lip;
+ strmake(command, lip->yylval->symbol.str,
+ MY_MIN(lip->yylval->symbol.length, sizeof(command)-1));
+ my_error(ER_CANT_USE_OPTION_HERE, MYF(0), command);
+ return 1;
+ }
+ return 0;
+}
+
+
+Comp_creator *comp_eq_creator(bool invert)
+{
+ return invert?(Comp_creator *)&ne_creator:(Comp_creator *)&eq_creator;
+}
+
+
+Comp_creator *comp_ge_creator(bool invert)
+{
+ return invert?(Comp_creator *)&lt_creator:(Comp_creator *)&ge_creator;
+}
+
+
+Comp_creator *comp_gt_creator(bool invert)
+{
+ return invert?(Comp_creator *)&le_creator:(Comp_creator *)&gt_creator;
+}
+
+
+Comp_creator *comp_le_creator(bool invert)
+{
+ return invert?(Comp_creator *)&gt_creator:(Comp_creator *)&le_creator;
+}
+
+
+Comp_creator *comp_lt_creator(bool invert)
+{
+ return invert?(Comp_creator *)&ge_creator:(Comp_creator *)&lt_creator;
+}
+
+
+Comp_creator *comp_ne_creator(bool invert)
+{
+ return invert?(Comp_creator *)&eq_creator:(Comp_creator *)&ne_creator;
+}
+
+
+/**
+ Construct ALL/ANY/SOME subquery Item.
+
+ @param left_expr pointer to left expression
+ @param cmp compare function creator
+ @param all true if we create ALL subquery
+ @param select_lex pointer on parsed subquery structure
+
+ @return
+ constructed Item (or 0 if out of memory)
+*/
+Item * all_any_subquery_creator(Item *left_expr,
+ chooser_compare_func_creator cmp,
+ bool all,
+ SELECT_LEX *select_lex)
+{
+ if ((cmp == &comp_eq_creator) && !all) // = ANY <=> IN
+ return new Item_in_subselect(left_expr, select_lex);
+
+ if ((cmp == &comp_ne_creator) && all) // <> ALL <=> NOT IN
+ return new Item_func_not(new Item_in_subselect(left_expr, select_lex));
+
+ Item_allany_subselect *it=
+ new Item_allany_subselect(left_expr, cmp, select_lex, all);
+ if (all)
+ return it->upper_item= new Item_func_not_all(it); /* ALL */
+
+ return it->upper_item= new Item_func_nop_all(it); /* ANY/SOME */
+}
+
+
+/**
+ Multi update query pre-check.
+
+ @param thd Thread handler
+ @param tables Global/local table list (have to be the same)
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+bool multi_update_precheck(THD *thd, TABLE_LIST *tables)
+{
+ const char *msg= 0;
+ TABLE_LIST *table;
+ LEX *lex= thd->lex;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ DBUG_ENTER("multi_update_precheck");
+
+ if (select_lex->item_list.elements != lex->value_list.elements)
+ {
+ my_message(ER_WRONG_VALUE_COUNT, ER(ER_WRONG_VALUE_COUNT), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Ensure that we have UPDATE or SELECT privilege for each table
+ The exact privilege is checked in mysql_multi_update()
+ */
+ for (table= tables; table; table= table->next_local)
+ {
+ if (table->derived)
+ table->grant.privilege= SELECT_ACL;
+ else if ((check_access(thd, UPDATE_ACL, table->db,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 1) ||
+ check_grant(thd, UPDATE_ACL, table, FALSE, 1, TRUE)) &&
+ (check_access(thd, SELECT_ACL, table->db,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 0) ||
+ check_grant(thd, SELECT_ACL, table, FALSE, 1, FALSE)))
+ DBUG_RETURN(TRUE);
+
+ table->grant.orig_want_privilege= 0;
+ table->table_in_first_from_clause= 1;
+ }
+ /*
+ Is there tables of subqueries?
+ */
+ if (&lex->select_lex != lex->all_selects_list)
+ {
+ DBUG_PRINT("info",("Checking sub query list"));
+ for (table= tables; table; table= table->next_global)
+ {
+ if (!table->table_in_first_from_clause)
+ {
+ if (check_access(thd, SELECT_ACL, table->db,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 0) ||
+ check_grant(thd, SELECT_ACL, table, FALSE, 1, FALSE))
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+
+ if (select_lex->order_list.elements)
+ msg= "ORDER BY";
+ else if (select_lex->select_limit)
+ msg= "LIMIT";
+ if (msg)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "UPDATE", msg);
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ Multi delete query pre-check.
+
+ @param thd Thread handler
+ @param tables Global/local table list
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+bool multi_delete_precheck(THD *thd, TABLE_LIST *tables)
+{
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first;
+ TABLE_LIST **save_query_tables_own_last= thd->lex->query_tables_own_last;
+ DBUG_ENTER("multi_delete_precheck");
+
+ /*
+ Temporary tables are pre-opened in 'tables' list only. Here we need to
+ initialize TABLE instances in 'aux_tables' list.
+ */
+ for (TABLE_LIST *tl= aux_tables; tl; tl= tl->next_global)
+ {
+ if (tl->table)
+ continue;
+
+ if (tl->correspondent_table)
+ tl->table= tl->correspondent_table->table;
+ }
+
+ /* sql_yacc guarantees that tables and aux_tables are not zero */
+ DBUG_ASSERT(aux_tables != 0);
+ if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Since aux_tables list is not part of LEX::query_tables list we
+ have to juggle with LEX::query_tables_own_last value to be able
+ call check_table_access() safely.
+ */
+ thd->lex->query_tables_own_last= 0;
+ if (check_table_access(thd, DELETE_ACL, aux_tables, FALSE, UINT_MAX, FALSE))
+ {
+ thd->lex->query_tables_own_last= save_query_tables_own_last;
+ DBUG_RETURN(TRUE);
+ }
+ thd->lex->query_tables_own_last= save_query_tables_own_last;
+
+ if ((thd->variables.option_bits & OPTION_SAFE_UPDATES) && !select_lex->where)
+ {
+ my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE,
+ ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Given a table in the source list, find a correspondent table in the
+ table references list.
+
+ @param lex Pointer to LEX representing multi-delete.
+ @param src Source table to match.
+ @param ref Table references list.
+
+ @remark The source table list (tables listed before the FROM clause
+ or tables listed in the FROM clause before the USING clause) may
+ contain table names or aliases that must match unambiguously one,
+ and only one, table in the target table list (table references list,
+ after FROM/USING clause).
+
+ @return Matching table, NULL otherwise.
+*/
+
+static TABLE_LIST *multi_delete_table_match(LEX *lex, TABLE_LIST *tbl,
+ TABLE_LIST *tables)
+{
+ TABLE_LIST *match= NULL;
+ DBUG_ENTER("multi_delete_table_match");
+
+ for (TABLE_LIST *elem= tables; elem; elem= elem->next_local)
+ {
+ int cmp;
+
+ if (tbl->is_fqtn && elem->is_alias)
+ continue; /* no match */
+ if (tbl->is_fqtn && elem->is_fqtn)
+ cmp= my_strcasecmp(table_alias_charset, tbl->table_name, elem->table_name) ||
+ strcmp(tbl->db, elem->db);
+ else if (elem->is_alias)
+ cmp= my_strcasecmp(table_alias_charset, tbl->alias, elem->alias);
+ else
+ cmp= my_strcasecmp(table_alias_charset, tbl->table_name, elem->table_name) ||
+ strcmp(tbl->db, elem->db);
+
+ if (cmp)
+ continue;
+
+ if (match)
+ {
+ my_error(ER_NONUNIQ_TABLE, MYF(0), elem->alias);
+ DBUG_RETURN(NULL);
+ }
+
+ match= elem;
+ }
+
+ if (!match)
+ my_error(ER_UNKNOWN_TABLE, MYF(0), tbl->table_name, "MULTI DELETE");
+
+ DBUG_RETURN(match);
+}
+
+
+/**
+ Link tables in auxilary table list of multi-delete with corresponding
+ elements in main table list, and set proper locks for them.
+
+ @param lex pointer to LEX representing multi-delete
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error
+*/
+
+bool multi_delete_set_locks_and_link_aux_tables(LEX *lex)
+{
+ TABLE_LIST *tables= lex->select_lex.table_list.first;
+ TABLE_LIST *target_tbl;
+ DBUG_ENTER("multi_delete_set_locks_and_link_aux_tables");
+
+ lex->table_count= 0;
+
+ for (target_tbl= lex->auxiliary_table_list.first;
+ target_tbl; target_tbl= target_tbl->next_local)
+ {
+ lex->table_count++;
+ /* All tables in aux_tables must be found in FROM PART */
+ TABLE_LIST *walk= multi_delete_table_match(lex, target_tbl, tables);
+ if (!walk)
+ DBUG_RETURN(TRUE);
+ if (!walk->derived)
+ {
+ target_tbl->table_name= walk->table_name;
+ target_tbl->table_name_length= walk->table_name_length;
+ }
+ walk->updating= target_tbl->updating;
+ walk->lock_type= target_tbl->lock_type;
+ /* We can assume that tables to be deleted from are locked for write. */
+ DBUG_ASSERT(walk->lock_type >= TL_WRITE_ALLOW_WRITE);
+ walk->mdl_request.set_type(MDL_SHARED_WRITE);
+ target_tbl->correspondent_table= walk; // Remember corresponding table
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ simple UPDATE query pre-check.
+
+ @param thd Thread handler
+ @param tables Global table list
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+bool update_precheck(THD *thd, TABLE_LIST *tables)
+{
+ DBUG_ENTER("update_precheck");
+ if (thd->lex->select_lex.item_list.elements != thd->lex->value_list.elements)
+ {
+ my_message(ER_WRONG_VALUE_COUNT, ER(ER_WRONG_VALUE_COUNT), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(check_one_table_access(thd, UPDATE_ACL, tables));
+}
+
+
+/**
+ simple DELETE query pre-check.
+
+ @param thd Thread handler
+ @param tables Global table list
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+bool delete_precheck(THD *thd, TABLE_LIST *tables)
+{
+ DBUG_ENTER("delete_precheck");
+ if (check_one_table_access(thd, DELETE_ACL, tables))
+ DBUG_RETURN(TRUE);
+ /* Set privilege for the WHERE clause */
+ tables->grant.want_privilege=(SELECT_ACL & ~tables->grant.privilege);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ simple INSERT query pre-check.
+
+ @param thd Thread handler
+ @param tables Global table list
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+bool insert_precheck(THD *thd, TABLE_LIST *tables)
+{
+ LEX *lex= thd->lex;
+ DBUG_ENTER("insert_precheck");
+
+ /*
+ Check that we have modify privileges for the first table and
+ select privileges for the rest
+ */
+ ulong privilege= (INSERT_ACL |
+ (lex->duplicates == DUP_REPLACE ? DELETE_ACL : 0) |
+ (lex->value_list.elements ? UPDATE_ACL : 0));
+
+ if (check_one_table_access(thd, privilege, tables))
+ DBUG_RETURN(TRUE);
+
+ if (lex->update_list.elements != lex->value_list.elements)
+ {
+ my_message(ER_WRONG_VALUE_COUNT, ER(ER_WRONG_VALUE_COUNT), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Set proper open mode and table type for element representing target table
+ of CREATE TABLE statement, also adjust statement table list if necessary.
+*/
+
+void create_table_set_open_action_and_adjust_tables(LEX *lex)
+{
+ TABLE_LIST *create_table= lex->query_tables;
+
+ if (lex->create_info.tmp_table())
+ create_table->open_type= OT_TEMPORARY_ONLY;
+ else
+ create_table->open_type= OT_BASE_ONLY;
+
+ if (!lex->select_lex.item_list.elements)
+ {
+ /*
+ Avoid opening and locking target table for ordinary CREATE TABLE
+ or CREATE TABLE LIKE for write (unlike in CREATE ... SELECT we
+ won't do any insertions in it anyway). Not doing this causes
+ problems when running CREATE TABLE IF NOT EXISTS for already
+ existing log table.
+ */
+ create_table->lock_type= TL_READ;
+ }
+}
+
+
+/**
+ CREATE TABLE query pre-check.
+
+ @param thd Thread handler
+ @param tables Global table list
+ @param create_table Table which will be created
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+bool create_table_precheck(THD *thd, TABLE_LIST *tables,
+ TABLE_LIST *create_table)
+{
+ LEX *lex= thd->lex;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ ulong want_priv;
+ bool error= TRUE; // Error message is given
+ DBUG_ENTER("create_table_precheck");
+
+ /*
+ Require CREATE [TEMPORARY] privilege on new table; for
+ CREATE TABLE ... SELECT, also require INSERT.
+ */
+
+ want_priv= lex->create_info.tmp_table() ? CREATE_TMP_ACL :
+ (CREATE_ACL | (select_lex->item_list.elements ? INSERT_ACL : 0));
+
+ /* CREATE OR REPLACE on not temporary tables require DROP_ACL */
+ if ((lex->create_info.options & HA_LEX_CREATE_REPLACE) &&
+ !lex->create_info.tmp_table())
+ want_priv|= DROP_ACL;
+
+ if (check_access(thd, want_priv, create_table->db,
+ &create_table->grant.privilege,
+ &create_table->grant.m_internal,
+ 0, 0))
+ goto err;
+
+ /* If it is a merge table, check privileges for merge children. */
+ if (lex->create_info.merge_list.first)
+ {
+ /*
+ The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
+ underlying base tables, even if there are temporary tables with the same
+ names.
+
+ From user's point of view, it might look as if the user must have these
+ privileges on temporary tables to create a merge table over them. This is
+ one of two cases when a set of privileges is required for operations on
+ temporary tables (see also CREATE TABLE).
+
+ The reason for this behavior stems from the following facts:
+
+ - For merge tables, the underlying table privileges are checked only
+ at CREATE TABLE / ALTER TABLE time.
+
+ In other words, once a merge table is created, the privileges of
+ the underlying tables can be revoked, but the user will still have
+ access to the merge table (provided that the user has privileges on
+ the merge table itself).
+
+ - Temporary tables shadow base tables.
+
+ I.e. there might be temporary and base tables with the same name, and
+ the temporary table takes the precedence in all operations.
+
+ - For temporary MERGE tables we do not track if their child tables are
+ base or temporary. As result we can't guarantee that privilege check
+ which was done in presence of temporary child will stay relevant
+ later as this temporary table might be removed.
+
+ If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
+ the underlying *base* tables, it would create a security breach as in
+ Bug#12771903.
+ */
+
+ if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
+ lex->create_info.merge_list.first,
+ FALSE, UINT_MAX, FALSE))
+ goto err;
+ }
+
+ if (want_priv != CREATE_TMP_ACL &&
+ check_grant(thd, want_priv, create_table, FALSE, 1, FALSE))
+ goto err;
+
+ if (select_lex->item_list.elements)
+ {
+ /* Check permissions for used tables in CREATE TABLE ... SELECT */
+ if (tables && check_table_access(thd, SELECT_ACL, tables, FALSE,
+ UINT_MAX, FALSE))
+ goto err;
+ }
+ else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE)
+ {
+ if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE))
+ goto err;
+ }
+
+ if (check_fk_parent_table_access(thd, &lex->create_info, &lex->alter_info))
+ goto err;
+
+ /*
+ For CREATE TABLE we should not open the table even if it exists.
+ If the table exists, we should either not create it or replace it
+ */
+ lex->query_tables->open_strategy= TABLE_LIST::OPEN_STUB;
+
+ error= FALSE;
+
+err:
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Check privileges for LOCK TABLES statement.
+
+ @param thd Thread context.
+ @param tables List of tables to be locked.
+
+ @retval FALSE - Success.
+ @retval TRUE - Failure.
+*/
+
+static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables)
+{
+ TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
+
+ for (TABLE_LIST *table= tables; table != first_not_own_table && table;
+ table= table->next_global)
+ {
+ if (is_temporary_table(table))
+ continue;
+
+ if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, table,
+ FALSE, 1, FALSE))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/**
+ negate given expression.
+
+ @param thd thread handler
+ @param expr expression for negation
+
+ @return
+ negated expression
+*/
+
+Item *negate_expression(THD *thd, Item *expr)
+{
+ Item *negated;
+ if (expr->type() == Item::FUNC_ITEM &&
+ ((Item_func *) expr)->functype() == Item_func::NOT_FUNC)
+ {
+ /* it is NOT(NOT( ... )) */
+ Item *arg= ((Item_func *) expr)->arguments()[0];
+ enum_parsing_place place= thd->lex->current_select->parsing_place;
+ if (arg->is_bool_func() || place == IN_WHERE || place == IN_HAVING)
+ return arg;
+ /*
+ if it is not boolean function then we have to emulate value of
+ not(not(a)), it will be a != 0
+ */
+ return new Item_func_ne(arg, new Item_int((char*) "0", 0, 1));
+ }
+
+ if ((negated= expr->neg_transformer(thd)) != 0)
+ return negated;
+ return new Item_func_not(expr);
+}
+
+/**
+ Set the specified definer to the default value, which is the
+ current user in the thread.
+
+ @param[in] thd thread handler
+ @param[out] definer definer
+*/
+
+void get_default_definer(THD *thd, LEX_USER *definer, bool role)
+{
+ const Security_context *sctx= thd->security_ctx;
+
+ if (role)
+ {
+ definer->user.str= const_cast<char*>(sctx->priv_role);
+ definer->host= empty_lex_str;
+ }
+ else
+ {
+ definer->user.str= const_cast<char*>(sctx->priv_user);
+ definer->host.str= const_cast<char*>(sctx->priv_host);
+ definer->host.length= strlen(definer->host.str);
+ }
+ definer->user.length= strlen(definer->user.str);
+
+ definer->password= null_lex_str;
+ definer->plugin= empty_lex_str;
+ definer->auth= empty_lex_str;
+}
+
+
+/**
+ Create default definer for the specified THD.
+
+ @param[in] thd thread handler
+
+ @return
+ - On success, return a valid pointer to the created and initialized
+ LEX_USER, which contains definer information.
+ - On error, return 0.
+*/
+
+LEX_USER *create_default_definer(THD *thd, bool role)
+{
+ LEX_USER *definer;
+
+ if (! (definer= (LEX_USER*) thd->alloc(sizeof(LEX_USER))))
+ return 0;
+
+ thd->get_definer(definer, role);
+
+ if (role && definer->user.length == 0)
+ {
+ my_error(ER_MALFORMED_DEFINER, MYF(0));
+ return 0;
+ }
+ else
+ return definer;
+}
+
+
+/**
+ Create definer with the given user and host names.
+
+ @param[in] thd thread handler
+ @param[in] user_name user name
+ @param[in] host_name host name
+
+ @return
+ - On success, return a valid pointer to the created and initialized
+ LEX_USER, which contains definer information.
+ - On error, return 0.
+*/
+
+LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name)
+{
+ LEX_USER *definer;
+
+ /* Create and initialize. */
+
+ if (! (definer= (LEX_USER*) thd->alloc(sizeof(LEX_USER))))
+ return 0;
+
+ definer->user= *user_name;
+ definer->host= *host_name;
+ definer->password.str= NULL;
+ definer->password.length= 0;
+
+ return definer;
+}
+
+
+/**
+ Check that byte length of a string does not exceed some limit.
+
+ @param str string to be checked
+ @param err_msg error message to be displayed if the string is too long
+ @param max_length max length
+
+ @retval
+ FALSE the passed string is not longer than max_length
+ @retval
+ TRUE the passed string is longer than max_length
+
+ NOTE
+ The function is not used in existing code but can be useful later?
+*/
+
+bool check_string_byte_length(LEX_STRING *str, const char *err_msg,
+ uint max_byte_length)
+{
+ if (str->length <= max_byte_length)
+ return FALSE;
+
+ my_error(ER_WRONG_STRING_LENGTH, MYF(0), str->str, err_msg, max_byte_length);
+
+ return TRUE;
+}
+
+
+/*
+ Check that char length of a string does not exceed some limit.
+
+ SYNOPSIS
+ check_string_char_length()
+ str string to be checked
+ err_msg error message to be displayed if the string is too long
+ max_char_length max length in symbols
+ cs string charset
+
+ RETURN
+ FALSE the passed string is not longer than max_char_length
+ TRUE the passed string is longer than max_char_length
+*/
+
+
+bool check_string_char_length(LEX_STRING *str, const char *err_msg,
+ uint max_char_length, CHARSET_INFO *cs,
+ bool no_error)
+{
+ int well_formed_error;
+ uint res= cs->cset->well_formed_len(cs, str->str, str->str + str->length,
+ max_char_length, &well_formed_error);
+
+ if (!well_formed_error && str->length == res)
+ return FALSE;
+
+ if (!no_error)
+ {
+ ErrConvString err(str->str, str->length, cs);
+ my_error(ER_WRONG_STRING_LENGTH, MYF(0), err.ptr(), err_msg, max_char_length);
+ }
+ return TRUE;
+}
+
+C_MODE_START
+
+/*
+ Check if path does not contain mysql data home directory
+
+ SYNOPSIS
+ test_if_data_home_dir()
+ dir directory
+
+ RETURN VALUES
+ 0 ok
+ 1 error ; Given path contains data directory
+*/
+
+int test_if_data_home_dir(const char *dir)
+{
+ char path[FN_REFLEN];
+ int dir_len;
+ DBUG_ENTER("test_if_data_home_dir");
+
+ if (!dir)
+ DBUG_RETURN(0);
+
+ /*
+ data_file_name and index_file_name include the table name without
+ extension. Mostly this does not refer to an existing file. When
+ comparing data_file_name or index_file_name against the data
+ directory, we try to resolve all symbolic links. On some systems,
+ we use realpath(3) for the resolution. This returns ENOENT if the
+ resolved path does not refer to an existing file. my_realpath()
+ does then copy the requested path verbatim, without symlink
+ resolution. Thereafter the comparison can fail even if the
+ requested path is within the data directory. E.g. if symlinks to
+ another file system are used. To make realpath(3) return the
+ resolved path, we strip the table name and compare the directory
+ path only. If the directory doesn't exist either, table creation
+ will fail anyway.
+ */
+
+ (void) fn_format(path, dir, "", "",
+ (MY_RETURN_REAL_PATH|MY_RESOLVE_SYMLINKS));
+ dir_len= strlen(path);
+ if (mysql_unpacked_real_data_home_len<= dir_len)
+ {
+ if (dir_len > mysql_unpacked_real_data_home_len &&
+ path[mysql_unpacked_real_data_home_len] != FN_LIBCHAR)
+ DBUG_RETURN(0);
+
+ if (lower_case_file_system)
+ {
+ if (!my_strnncoll(default_charset_info, (const uchar*) path,
+ mysql_unpacked_real_data_home_len,
+ (const uchar*) mysql_unpacked_real_data_home,
+ mysql_unpacked_real_data_home_len))
+ {
+ DBUG_PRINT("error", ("Path is part of mysql_real_data_home"));
+ DBUG_RETURN(1);
+ }
+ }
+ else if (!memcmp(path, mysql_unpacked_real_data_home,
+ mysql_unpacked_real_data_home_len))
+ {
+ DBUG_PRINT("error", ("Path is part of mysql_real_data_home"));
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+C_MODE_END
+
+
+int error_if_data_home_dir(const char *path, const char *what)
+{
+ size_t dirlen;
+ char dirpath[FN_REFLEN];
+ if (path)
+ {
+ dirname_part(dirpath, path, &dirlen);
+ if (test_if_data_home_dir(dirpath))
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), what);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ Check that host name string is valid.
+
+ @param[in] str string to be checked
+
+ @return Operation status
+ @retval FALSE host name is ok
+ @retval TRUE host name string is longer than max_length or
+ has invalid symbols
+*/
+
+bool check_host_name(LEX_STRING *str)
+{
+ const char *name= str->str;
+ const char *end= str->str + str->length;
+ if (check_string_byte_length(str, ER(ER_HOSTNAME), HOSTNAME_LENGTH))
+ return TRUE;
+
+ while (name != end)
+ {
+ if (*name == '@')
+ {
+ my_printf_error(ER_UNKNOWN_ERROR,
+ "Malformed hostname (illegal symbol: '%c')", MYF(0),
+ *name);
+ return TRUE;
+ }
+ name++;
+ }
+ return FALSE;
+}
+
+
+extern int MYSQLparse(THD *thd); // from sql_yacc.cc
+
+
+/**
+ This is a wrapper of MYSQLparse(). All the code should call parse_sql()
+ instead of MYSQLparse().
+
+ @param thd Thread context.
+ @param parser_state Parser state.
+ @param creation_ctx Object creation context.
+
+ @return Error status.
+ @retval FALSE on success.
+ @retval TRUE on parsing error.
+*/
+
+bool parse_sql(THD *thd, Parser_state *parser_state,
+ Object_creation_ctx *creation_ctx, bool do_pfs_digest)
+{
+ bool ret_value;
+ DBUG_ENTER("parse_sql");
+ DBUG_ASSERT(thd->m_parser_state == NULL);
+ DBUG_ASSERT(thd->lex->m_sql_cmd == NULL);
+
+ MYSQL_QUERY_PARSE_START(thd->query());
+ /* Backup creation context. */
+
+ Object_creation_ctx *backup_ctx= NULL;
+
+ if (creation_ctx)
+ backup_ctx= creation_ctx->set_n_backup(thd);
+
+ /* Set parser state. */
+
+ thd->m_parser_state= parser_state;
+
+ parser_state->m_digest_psi= NULL;
+ parser_state->m_lip.m_digest= NULL;
+
+ if (do_pfs_digest)
+ {
+ /* Start Digest */
+ parser_state->m_digest_psi= MYSQL_DIGEST_START(thd->m_statement_psi);
+
+ if (parser_state->m_input.m_compute_digest ||
+ (parser_state->m_digest_psi != NULL))
+ {
+ /*
+ If either:
+ - the caller wants to compute a digest
+ - the performance schema wants to compute a digest
+ set the digest listener in the lexer.
+ */
+ parser_state->m_lip.m_digest= thd->m_digest;
+ parser_state->m_lip.m_digest->m_digest_storage.m_charset_number= thd->charset()->number;
+ }
+ }
+
+ /* Parse the query. */
+
+ bool mysql_parse_status= MYSQLparse(thd) != 0;
+
+ /*
+ Check that if MYSQLparse() failed either thd->is_error() is set, or an
+ internal error handler is set.
+
+ The assert will not catch a situation where parsing fails without an
+ error reported if an error handler exists. The problem is that the
+ error handler might have intercepted the error, so thd->is_error() is
+ not set. However, there is no way to be 100% sure here (the error
+ handler might be for other errors than parsing one).
+ */
+
+ DBUG_ASSERT(!mysql_parse_status ||
+ thd->is_error() ||
+ thd->get_internal_handler());
+
+ /* Reset parser state. */
+
+ thd->m_parser_state= NULL;
+
+ /* Restore creation context. */
+
+ if (creation_ctx)
+ creation_ctx->restore_env(thd, backup_ctx);
+
+ /* That's it. */
+
+ ret_value= mysql_parse_status || thd->is_fatal_error;
+
+ if ((ret_value == 0) && (parser_state->m_digest_psi != NULL))
+ {
+ /*
+ On parsing success, record the digest in the performance schema.
+ */
+ DBUG_ASSERT(do_pfs_digest);
+ DBUG_ASSERT(thd->m_digest != NULL);
+ MYSQL_DIGEST_END(parser_state->m_digest_psi,
+ & thd->m_digest->m_digest_storage);
+ }
+
+ MYSQL_QUERY_PARSE_DONE(ret_value);
+ DBUG_RETURN(ret_value);
+}
+
+/**
+ @} (end of group Runtime_Environment)
+*/
+
+
+
+/**
+ Check and merge "CHARACTER SET cs [ COLLATE cl ]" clause
+
+ @param cs character set pointer.
+ @param cl collation pointer.
+
+ Check if collation "cl" is applicable to character set "cs".
+
+ If "cl" is NULL (e.g. when COLLATE clause is not specified),
+ then simply "cs" is returned.
+
+ @return Error status.
+ @retval NULL, if "cl" is not applicable to "cs".
+ @retval pointer to merged CHARSET_INFO on success.
+*/
+
+
+CHARSET_INFO*
+merge_charset_and_collation(CHARSET_INFO *cs, CHARSET_INFO *cl)
+{
+ if (cl)
+ {
+ if (!my_charset_same(cs, cl))
+ {
+ my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0), cl->name, cs->csname);
+ return NULL;
+ }
+ return cl;
+ }
+ return cs;
+}
diff --git a/sql/sql_parse.h b/sql/sql_parse.h
new file mode 100644
index 00000000000..40ae5427133
--- /dev/null
+++ b/sql/sql_parse.h
@@ -0,0 +1,210 @@
+/* Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_PARSE_INCLUDED
+#define SQL_PARSE_INCLUDED
+
+#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_acl.h" /* GLOBAL_ACLS */
+
+class Comp_creator;
+class Item;
+class Object_creation_ctx;
+class Parser_state;
+struct TABLE_LIST;
+class THD;
+class Table_ident;
+struct LEX;
+
+enum enum_mysql_completiontype {
+ ROLLBACK_RELEASE=-2, ROLLBACK=1, ROLLBACK_AND_CHAIN=7,
+ COMMIT_RELEASE=-1, COMMIT=0, COMMIT_AND_CHAIN=6
+};
+
+extern "C" int test_if_data_home_dir(const char *dir);
+int error_if_data_home_dir(const char *path, const char *what);
+
+bool multi_update_precheck(THD *thd, TABLE_LIST *tables);
+bool multi_delete_precheck(THD *thd, TABLE_LIST *tables);
+int mysql_multi_update_prepare(THD *thd);
+int mysql_multi_delete_prepare(THD *thd);
+bool mysql_insert_select_prepare(THD *thd);
+bool update_precheck(THD *thd, TABLE_LIST *tables);
+bool delete_precheck(THD *thd, TABLE_LIST *tables);
+bool insert_precheck(THD *thd, TABLE_LIST *tables);
+bool create_table_precheck(THD *thd, TABLE_LIST *tables,
+ TABLE_LIST *create_table);
+bool check_fk_parent_table_access(THD *thd,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info);
+
+bool parse_sql(THD *thd, Parser_state *parser_state,
+ Object_creation_ctx *creation_ctx, bool do_pfs_digest=false);
+
+void free_items(Item *item);
+void cleanup_items(Item *item);
+
+Comp_creator *comp_eq_creator(bool invert);
+Comp_creator *comp_ge_creator(bool invert);
+Comp_creator *comp_gt_creator(bool invert);
+Comp_creator *comp_le_creator(bool invert);
+Comp_creator *comp_lt_creator(bool invert);
+Comp_creator *comp_ne_creator(bool invert);
+
+int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
+ enum enum_schema_tables schema_table_idx);
+void get_default_definer(THD *thd, LEX_USER *definer, bool role);
+LEX_USER *create_default_definer(THD *thd, bool role);
+LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name);
+LEX_USER *get_current_user(THD *thd, LEX_USER *user, bool lock=true);
+bool sp_process_definer(THD *thd);
+bool check_string_byte_length(LEX_STRING *str, const char *err_msg,
+ uint max_byte_length);
+bool check_string_char_length(LEX_STRING *str, const char *err_msg,
+ uint max_char_length, CHARSET_INFO *cs,
+ bool no_error);
+CHARSET_INFO* merge_charset_and_collation(CHARSET_INFO *cs, CHARSET_INFO *cl);
+bool check_host_name(LEX_STRING *str);
+bool check_identifier_name(LEX_STRING *str, uint max_char_length,
+ uint err_code, const char *param_for_err_msg);
+bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length);
+bool sqlcom_can_generate_row_events(const THD *thd);
+bool is_update_query(enum enum_sql_command command);
+bool is_log_table_write_query(enum enum_sql_command command);
+bool alloc_query(THD *thd, const char *packet, uint packet_length);
+void mysql_init_select(LEX *lex);
+void mysql_parse(THD *thd, char *rawbuf, uint length,
+ Parser_state *parser_state);
+void mysql_reset_thd_for_next_command(THD *thd);
+bool mysql_new_select(LEX *lex, bool move_down);
+void create_select_for_variable(const char *var_name);
+void create_table_set_open_action_and_adjust_tables(LEX *lex);
+void mysql_init_multi_delete(LEX *lex);
+bool multi_delete_set_locks_and_link_aux_tables(LEX *lex);
+void create_table_set_open_action_and_adjust_tables(LEX *lex);
+pthread_handler_t handle_bootstrap(void *arg);
+int mysql_execute_command(THD *thd);
+bool do_command(THD *thd);
+void do_handle_bootstrap(THD *thd);
+bool dispatch_command(enum enum_server_command command, THD *thd,
+ char* packet, uint packet_length);
+void log_slow_statement(THD *thd);
+bool append_file_to_dir(THD *thd, const char **filename_ptr,
+ const char *table_name);
+bool append_file_to_dir(THD *thd, const char **filename_ptr,
+ const char *table_name);
+void execute_init_command(THD *thd, LEX_STRING *init_command,
+ mysql_rwlock_t *var_lock);
+bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum enum_field_types type,
+ char *length, char *decimal,
+ uint type_modifier,
+ Item *default_value, Item *on_update_value,
+ LEX_STRING *comment,
+ char *change, List<String> *interval_list,
+ CHARSET_INFO *cs,
+ uint uint_geom_type,
+ Virtual_column_info *vcol_info,
+ engine_option_value *create_options);
+bool add_to_list(THD *thd, SQL_I_List<ORDER> &list, Item *group, bool asc);
+void add_join_on(TABLE_LIST *b,Item *expr);
+void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List<String> *using_fields,
+ SELECT_LEX *lex);
+bool add_proc_to_list(THD *thd, Item *item);
+bool push_new_name_resolution_context(THD *thd,
+ TABLE_LIST *left_op,
+ TABLE_LIST *right_op);
+void store_position_for_column(const char *name);
+void init_update_queries(void);
+bool check_simple_select();
+Item *normalize_cond(Item *cond);
+Item *negate_expression(THD *thd, Item *expr);
+bool check_stack_overrun(THD *thd, long margin, uchar *dummy);
+
+/* Variables */
+
+extern const char* any_db;
+extern uint sql_command_flags[];
+extern uint server_command_flags[];
+extern const LEX_STRING command_name[];
+extern uint server_command_flags[];
+
+/* Inline functions */
+inline bool check_identifier_name(LEX_STRING *str, uint err_code)
+{
+ return check_identifier_name(str, NAME_CHAR_LEN, err_code, "");
+}
+
+inline bool check_identifier_name(LEX_STRING *str)
+{
+ return check_identifier_name(str, NAME_CHAR_LEN, 0, "");
+}
+
+
+/*
+ check_access() is needed for the connect engine.
+ It cannot be inlined - it must be exported.
+*/
+bool check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv,
+ GRANT_INTERNAL_INFO *grant_internal_info,
+ bool dont_check_global_grants, bool no_errors);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables);
+bool check_single_table_access(THD *thd, ulong privilege,
+ TABLE_LIST *tables, bool no_errors);
+bool check_routine_access(THD *thd,ulong want_access,char *db,char *name,
+ bool is_proc, bool no_errors);
+bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table);
+bool check_some_routine_access(THD *thd, const char *db, const char *name, bool is_proc);
+bool check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables,
+ bool any_combination_of_privileges_will_do,
+ uint number,
+ bool no_errors);
+#else
+inline bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables)
+{ return false; }
+inline bool check_single_table_access(THD *thd, ulong privilege,
+ TABLE_LIST *tables, bool no_errors)
+{ return false; }
+inline bool check_routine_access(THD *thd,ulong want_access,char *db,
+ char *name, bool is_proc, bool no_errors)
+{ return false; }
+inline bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table)
+{
+ table->grant.privilege= want_access;
+ return false;
+}
+inline bool check_some_routine_access(THD *thd, const char *db,
+ const char *name, bool is_proc)
+{ return false; }
+inline bool
+check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables,
+ bool any_combination_of_privileges_will_do,
+ uint number,
+ bool no_errors)
+{ return false; }
+#endif /*NO_EMBEDDED_ACCESS_CHECKS*/
+
+/* These were under the INNODB_COMPATIBILITY_HOOKS */
+
+bool check_global_access(THD *thd, ulong want_access, bool no_errors= false);
+
+inline bool is_supported_parser_charset(CHARSET_INFO *cs)
+{
+ return MY_TEST(cs->mbminlen == 1);
+}
+
+
+#endif /* SQL_PARSE_INCLUDED */
diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc
new file mode 100644
index 00000000000..f0fde223984
--- /dev/null
+++ b/sql/sql_partition.cc
@@ -0,0 +1,8358 @@
+/* Copyright (c) 2005, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, SkySQL Ab.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/*
+ This file is a container for general functionality related
+ to partitioning introduced in MySQL version 5.1. It contains functionality
+ used by all handlers that support partitioning, such as
+ the partitioning handler itself and the NDB handler.
+ (Much of the code in this file has been split into partition_info.cc and
+ the header files partition_info.h + partition_element.h + sql_partition.h)
+
+ The first version was written by Mikael Ronstrom 2004-2006.
+ Various parts of the optimizer code was written by Sergey Petrunia.
+ Code have been maintained by Mattias Jonsson.
+ The second version was written by Mikael Ronstrom 2006-2007 with some
+ final fixes for partition pruning in 2008-2009 with assistance from Sergey
+ Petrunia and Mattias Jonsson.
+
+ The first version supports RANGE partitioning, LIST partitioning, HASH
+ partitioning and composite partitioning (hereafter called subpartitioning)
+ where each RANGE/LIST partitioning is HASH partitioned. The hash function
+ can either be supplied by the user or by only a list of fields (also
+ called KEY partitioning), where the MySQL server will use an internal
+ hash function.
+ There are quite a few defaults that can be used as well.
+
+ The second version introduces a new variant of RANGE and LIST partitioning
+ which is often referred to as column lists in the code variables. This
+ enables a user to specify a set of columns and their concatenated value
+ as the partition value. By comparing the concatenation of these values
+ the proper partition can be choosen.
+*/
+
+/* Some general useful functions */
+
+#define MYSQL_LEX 1
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_partition.h"
+#include "key.h" // key_restore
+#include "sql_parse.h" // parse_sql
+#include "sql_cache.h" // query_cache_invalidate3
+#include "lock.h" // mysql_lock_remove
+#include "sql_show.h" // append_identifier
+#include <m_ctype.h>
+#include "transaction.h"
+#include "debug_sync.h"
+
+#include "sql_base.h" // close_all_tables_for_name
+#include "sql_table.h" // build_table_filename,
+ // build_table_shadow_filename,
+ // table_to_filename
+ // mysql_*_alter_copy_data
+#include "opt_range.h" // store_key_image_to_rec
+#include "sql_alter.h" // Alter_table_ctx
+
+#include <algorithm>
+using std::max;
+using std::min;
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+#include "ha_partition.h"
+
+#define ERROR_INJECT_CRASH(code) \
+ DBUG_EVALUATE_IF(code, (DBUG_SUICIDE(), 0), 0)
+#define ERROR_INJECT_ERROR(code) \
+ DBUG_EVALUATE_IF(code, (my_error(ER_UNKNOWN_ERROR, MYF(0)), TRUE), 0)
+
+/*
+ Partition related functions declarations and some static constants;
+*/
+const LEX_STRING partition_keywords[]=
+{
+ { C_STRING_WITH_LEN("HASH") },
+ { C_STRING_WITH_LEN("RANGE") },
+ { C_STRING_WITH_LEN("LIST") },
+ { C_STRING_WITH_LEN("KEY") },
+ { C_STRING_WITH_LEN("MAXVALUE") },
+ { C_STRING_WITH_LEN("LINEAR ") },
+ { C_STRING_WITH_LEN(" COLUMNS") },
+ { C_STRING_WITH_LEN("ALGORITHM") }
+
+};
+static const char *part_str= "PARTITION";
+static const char *sub_str= "SUB";
+static const char *by_str= "BY";
+static const char *space_str= " ";
+static const char *equal_str= "=";
+static const char *end_paren_str= ")";
+static const char *begin_paren_str= "(";
+static const char *comma_str= ",";
+
+int get_partition_id_list_col(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_list(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_range_col(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_range(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+static int get_part_id_charset_func_part(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+static int get_part_id_charset_func_subpart(partition_info *part_info,
+ uint32 *part_id);
+int get_partition_id_hash_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_key_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_linear_hash_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_linear_key_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_with_sub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value);
+int get_partition_id_hash_sub(partition_info *part_info,
+ uint32 *part_id);
+int get_partition_id_key_sub(partition_info *part_info,
+ uint32 *part_id);
+int get_partition_id_linear_hash_sub(partition_info *part_info,
+ uint32 *part_id);
+int get_partition_id_linear_key_sub(partition_info *part_info,
+ uint32 *part_id);
+static uint32 get_next_partition_via_walking(PARTITION_ITERATOR*);
+static void set_up_range_analysis_info(partition_info *part_info);
+static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR*);
+#endif
+
+uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter);
+uint32 get_next_partition_id_list(PARTITION_ITERATOR* part_iter);
+int get_part_iter_for_interval_via_mapping(partition_info *part_info,
+ bool is_subpart,
+ uint32 *store_length_array,
+ uchar *min_value, uchar *max_value,
+ uint min_len, uint max_len,
+ uint flags,
+ PARTITION_ITERATOR *part_iter);
+int get_part_iter_for_interval_cols_via_map(partition_info *part_info,
+ bool is_subpart,
+ uint32 *store_length_array,
+ uchar *min_value, uchar *max_value,
+ uint min_len, uint max_len,
+ uint flags,
+ PARTITION_ITERATOR *part_iter);
+int get_part_iter_for_interval_via_walking(partition_info *part_info,
+ bool is_subpart,
+ uint32 *store_length_array,
+ uchar *min_value, uchar *max_value,
+ uint min_len, uint max_len,
+ uint flags,
+ PARTITION_ITERATOR *part_iter);
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec);
+static int cmp_rec_and_tuple_prune(part_column_list_val *val,
+ uint32 n_vals_in_rec,
+ bool is_left_endpoint,
+ bool include_endpoint);
+
+/*
+ Convert constants in VALUES definition to the character set the
+ corresponding field uses.
+
+ SYNOPSIS
+ convert_charset_partition_constant()
+ item Item to convert
+ cs Character set to convert to
+
+ RETURN VALUE
+ NULL Error
+ item New converted item
+*/
+
+Item* convert_charset_partition_constant(Item *item, const CHARSET_INFO *cs)
+{
+ THD *thd= current_thd;
+ Name_resolution_context *context= &thd->lex->current_select->context;
+ TABLE_LIST *save_list= context->table_list;
+ const char *save_where= thd->where;
+
+ item= item->safe_charset_converter(cs);
+ context->table_list= NULL;
+ thd->where= "convert character set partition constant";
+ if (!item || item->fix_fields(thd, (Item**)NULL))
+ item= NULL;
+ thd->where= save_where;
+ context->table_list= save_list;
+ return item;
+}
+
+
+/**
+ A support function to check if a name is in a list of strings.
+
+ @param name String searched for
+ @param list_names A list of names searched in
+
+ @return True if if the name is in the list.
+ @retval true String found
+ @retval false String not found
+*/
+
+static bool is_name_in_list(char *name, List<char> list_names)
+{
+ List_iterator<char> names_it(list_names);
+ uint num_names= list_names.elements;
+ uint i= 0;
+
+ do
+ {
+ char *list_name= names_it++;
+ if (!(my_strcasecmp(system_charset_info, name, list_name)))
+ return TRUE;
+ } while (++i < num_names);
+ return FALSE;
+}
+
+
+
+/*
+ Set-up defaults for partitions.
+
+ SYNOPSIS
+ partition_default_handling()
+ table Table object
+ part_info Partition info to set up
+ is_create_table_ind Is this part of a table creation
+ normalized_path Normalized path name of table and database
+
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+*/
+
+bool partition_default_handling(TABLE *table, partition_info *part_info,
+ bool is_create_table_ind,
+ const char *normalized_path)
+{
+ DBUG_ENTER("partition_default_handling");
+
+ if (!is_create_table_ind)
+ {
+ if (part_info->use_default_num_partitions)
+ {
+ if (table->file->get_no_parts(normalized_path, &part_info->num_parts))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else if (part_info->is_sub_partitioned() &&
+ part_info->use_default_num_subpartitions)
+ {
+ uint num_parts;
+ if (table->file->get_no_parts(normalized_path, &num_parts))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_ASSERT(part_info->num_parts > 0);
+ DBUG_ASSERT((num_parts % part_info->num_parts) == 0);
+ part_info->num_subparts= num_parts / part_info->num_parts;
+ }
+ }
+ part_info->set_up_defaults_for_partitioning(table->file,
+ NULL, 0U);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ A useful routine used by update_row for partition handlers to calculate
+ the partition ids of the old and the new record.
+
+ SYNOPSIS
+ get_part_for_update()
+ old_data Buffer of old record
+ new_data Buffer of new record
+ rec0 Reference to table->record[0]
+ part_info Reference to partition information
+ out:old_part_id The returned partition id of old record
+ out:new_part_id The returned partition id of new record
+
+ RETURN VALUE
+ 0 Success
+ > 0 Error code
+*/
+
+int get_parts_for_update(const uchar *old_data, uchar *new_data,
+ const uchar *rec0, partition_info *part_info,
+ uint32 *old_part_id, uint32 *new_part_id,
+ longlong *new_func_value)
+{
+ Field **part_field_array= part_info->full_part_field_array;
+ int error;
+ longlong old_func_value;
+ DBUG_ENTER("get_parts_for_update");
+
+ DBUG_ASSERT(new_data == rec0); // table->record[0]
+ set_field_ptr(part_field_array, old_data, rec0);
+ error= part_info->get_partition_id(part_info, old_part_id,
+ &old_func_value);
+ set_field_ptr(part_field_array, rec0, old_data);
+ if (unlikely(error)) // Should never happen
+ {
+ DBUG_ASSERT(0);
+ DBUG_RETURN(error);
+ }
+#ifdef NOT_NEEDED
+ if (new_data == rec0)
+#endif
+ {
+ if (unlikely(error= part_info->get_partition_id(part_info,
+ new_part_id,
+ new_func_value)))
+ {
+ DBUG_RETURN(error);
+ }
+ }
+#ifdef NOT_NEEDED
+ else
+ {
+ /*
+ This branch should never execute but it is written anyways for
+ future use. It will be tested by ensuring that the above
+ condition is false in one test situation before pushing the code.
+ */
+ set_field_ptr(part_field_array, new_data, rec0);
+ error= part_info->get_partition_id(part_info, new_part_id,
+ new_func_value);
+ set_field_ptr(part_field_array, rec0, new_data);
+ if (unlikely(error))
+ {
+ DBUG_RETURN(error);
+ }
+ }
+#endif
+ DBUG_RETURN(0);
+}
+
+
+/*
+ A useful routine used by delete_row for partition handlers to calculate
+ the partition id.
+
+ SYNOPSIS
+ get_part_for_delete()
+ buf Buffer of old record
+ rec0 Reference to table->record[0]
+ part_info Reference to partition information
+ out:part_id The returned partition id to delete from
+
+ RETURN VALUE
+ 0 Success
+ > 0 Error code
+
+ DESCRIPTION
+ Dependent on whether buf is not record[0] we need to prepare the
+ fields. Then we call the function pointer get_partition_id to
+ calculate the partition id.
+*/
+
+int get_part_for_delete(const uchar *buf, const uchar *rec0,
+ partition_info *part_info, uint32 *part_id)
+{
+ int error;
+ longlong func_value;
+ DBUG_ENTER("get_part_for_delete");
+
+ if (likely(buf == rec0))
+ {
+ if (unlikely((error= part_info->get_partition_id(part_info, part_id,
+ &func_value))))
+ {
+ DBUG_RETURN(error);
+ }
+ DBUG_PRINT("info", ("Delete from partition %d", *part_id));
+ }
+ else
+ {
+ Field **part_field_array= part_info->full_part_field_array;
+ set_field_ptr(part_field_array, buf, rec0);
+ error= part_info->get_partition_id(part_info, part_id, &func_value);
+ set_field_ptr(part_field_array, rec0, buf);
+ if (unlikely(error))
+ {
+ DBUG_RETURN(error);
+ }
+ DBUG_PRINT("info", ("Delete from partition %d (path2)", *part_id));
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ This method is used to set-up both partition and subpartitioning
+ field array and used for all types of partitioning.
+ It is part of the logic around fix_partition_func.
+
+ SYNOPSIS
+ set_up_field_array()
+ table TABLE object for which partition fields are set-up
+ sub_part Is the table subpartitioned as well
+
+ RETURN VALUE
+ TRUE Error, some field didn't meet requirements
+ FALSE Ok, partition field array set-up
+
+ DESCRIPTION
+
+ A great number of functions below here is part of the fix_partition_func
+ method. It is used to set up the partition structures for execution from
+ openfrm. It is called at the end of the openfrm when the table struct has
+ been set-up apart from the partition information.
+ It involves:
+ 1) Setting arrays of fields for the partition functions.
+ 2) Setting up binary search array for LIST partitioning
+ 3) Setting up array for binary search for RANGE partitioning
+ 4) Setting up key_map's to assist in quick evaluation whether one
+ can deduce anything from a given index of what partition to use
+ 5) Checking whether a set of partitions can be derived from a range on
+ a field in the partition function.
+ As part of doing this there is also a great number of error controls.
+ This is actually the place where most of the things are checked for
+ partition information when creating a table.
+ Things that are checked includes
+ 1) All fields of partition function in Primary keys and unique indexes
+ (if not supported)
+
+
+ Create an array of partition fields (NULL terminated). Before this method
+ is called fix_fields or find_table_in_sef has been called to set
+ GET_FIXED_FIELDS_FLAG on all fields that are part of the partition
+ function.
+*/
+
+static bool set_up_field_array(TABLE *table,
+ bool is_sub_part)
+{
+ Field **ptr, *field, **field_array;
+ uint num_fields= 0;
+ uint size_field_array;
+ uint i= 0;
+ uint inx;
+ partition_info *part_info= table->part_info;
+ int result= FALSE;
+ DBUG_ENTER("set_up_field_array");
+
+ ptr= table->field;
+ while ((field= *(ptr++)))
+ {
+ if (field->flags & GET_FIXED_FIELDS_FLAG)
+ num_fields++;
+ }
+ if (num_fields > MAX_REF_PARTS)
+ {
+ char *err_str;
+ if (is_sub_part)
+ err_str= (char*)"subpartition function";
+ else
+ err_str= (char*)"partition function";
+ my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), err_str);
+ DBUG_RETURN(TRUE);
+ }
+ if (num_fields == 0)
+ {
+ /*
+ We are using hidden key as partitioning field
+ */
+ DBUG_ASSERT(!is_sub_part);
+ DBUG_RETURN(result);
+ }
+ size_field_array= (num_fields+1)*sizeof(Field*);
+ field_array= (Field**)sql_calloc(size_field_array);
+ if (unlikely(!field_array))
+ {
+ mem_alloc_error(size_field_array);
+ result= TRUE;
+ }
+ ptr= table->field;
+ while ((field= *(ptr++)))
+ {
+ if (field->flags & GET_FIXED_FIELDS_FLAG)
+ {
+ field->flags&= ~GET_FIXED_FIELDS_FLAG;
+ field->flags|= FIELD_IN_PART_FUNC_FLAG;
+ if (likely(!result))
+ {
+ if (!is_sub_part && part_info->column_list)
+ {
+ List_iterator<char> it(part_info->part_field_list);
+ char *field_name;
+
+ DBUG_ASSERT(num_fields == part_info->part_field_list.elements);
+ inx= 0;
+ do
+ {
+ field_name= it++;
+ if (!my_strcasecmp(system_charset_info,
+ field_name,
+ field->field_name))
+ break;
+ } while (++inx < num_fields);
+ if (inx == num_fields)
+ {
+ /*
+ Should not occur since it should already been checked in either
+ add_column_list_values, handle_list_of_fields,
+ check_partition_info etc.
+ */
+ DBUG_ASSERT(0);
+ my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
+ result= TRUE;
+ continue;
+ }
+ }
+ else
+ inx= i;
+ field_array[inx]= field;
+ i++;
+
+ /*
+ We check that the fields are proper. It is required for each
+ field in a partition function to:
+ 1) Not be a BLOB of any type
+ A BLOB takes too long time to evaluate so we don't want it for
+ performance reasons.
+ */
+
+ if (unlikely(field->flags & BLOB_FLAG))
+ {
+ my_error(ER_BLOB_FIELD_IN_PART_FUNC_ERROR, MYF(0));
+ result= TRUE;
+ }
+ }
+ }
+ }
+ field_array[num_fields]= 0;
+ if (!is_sub_part)
+ {
+ part_info->part_field_array= field_array;
+ part_info->num_part_fields= num_fields;
+ }
+ else
+ {
+ part_info->subpart_field_array= field_array;
+ part_info->num_subpart_fields= num_fields;
+ }
+ DBUG_RETURN(result);
+}
+
+
+
+/*
+ Create a field array including all fields of both the partitioning and the
+ subpartitioning functions.
+
+ SYNOPSIS
+ create_full_part_field_array()
+ thd Thread handle
+ table TABLE object for which partition fields are set-up
+ part_info Reference to partitioning data structure
+
+ RETURN VALUE
+ TRUE Memory allocation of field array failed
+ FALSE Ok
+
+ DESCRIPTION
+ If there is no subpartitioning then the same array is used as for the
+ partitioning. Otherwise a new array is built up using the flag
+ FIELD_IN_PART_FUNC in the field object.
+ This function is called from fix_partition_func
+*/
+
+static bool create_full_part_field_array(THD *thd, TABLE *table,
+ partition_info *part_info)
+{
+ bool result= FALSE;
+ Field **ptr;
+ my_bitmap_map *bitmap_buf;
+ DBUG_ENTER("create_full_part_field_array");
+
+ if (!part_info->is_sub_partitioned())
+ {
+ part_info->full_part_field_array= part_info->part_field_array;
+ part_info->num_full_part_fields= part_info->num_part_fields;
+ }
+ else
+ {
+ Field *field, **field_array;
+ uint num_part_fields=0, size_field_array;
+ ptr= table->field;
+ while ((field= *(ptr++)))
+ {
+ if (field->flags & FIELD_IN_PART_FUNC_FLAG)
+ num_part_fields++;
+ }
+ size_field_array= (num_part_fields+1)*sizeof(Field*);
+ field_array= (Field**)sql_calloc(size_field_array);
+ if (unlikely(!field_array))
+ {
+ mem_alloc_error(size_field_array);
+ result= TRUE;
+ goto end;
+ }
+ num_part_fields= 0;
+ ptr= table->field;
+ while ((field= *(ptr++)))
+ {
+ if (field->flags & FIELD_IN_PART_FUNC_FLAG)
+ field_array[num_part_fields++]= field;
+ }
+ field_array[num_part_fields]=0;
+ part_info->full_part_field_array= field_array;
+ part_info->num_full_part_fields= num_part_fields;
+ }
+
+ /*
+ Initialize the set of all fields used in partition and subpartition
+ expression. Required for testing of partition fields in write_set
+ when updating. We need to set all bits in read_set because the row
+ may need to be inserted in a different [sub]partition.
+ */
+ if (!(bitmap_buf= (my_bitmap_map*)
+ thd->alloc(bitmap_buffer_size(table->s->fields))))
+ {
+ mem_alloc_error(bitmap_buffer_size(table->s->fields));
+ result= TRUE;
+ goto end;
+ }
+ if (my_bitmap_init(&part_info->full_part_field_set, bitmap_buf,
+ table->s->fields, FALSE))
+ {
+ mem_alloc_error(table->s->fields);
+ result= TRUE;
+ goto end;
+ }
+ /*
+ full_part_field_array may be NULL if storage engine supports native
+ partitioning.
+ */
+ if ((ptr= part_info->full_part_field_array))
+ for (; *ptr; ptr++)
+ bitmap_set_bit(&part_info->full_part_field_set, (*ptr)->field_index);
+
+end:
+ DBUG_RETURN(result);
+}
+
+
+/*
+
+ Clear flag GET_FIXED_FIELDS_FLAG in all fields of a key previously set by
+ set_indicator_in_key_fields (always used in pairs).
+
+ SYNOPSIS
+ clear_indicator_in_key_fields()
+ key_info Reference to find the key fields
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ These support routines is used to set/reset an indicator of all fields
+ in a certain key. It is used in conjunction with another support routine
+ that traverse all fields in the PF to find if all or some fields in the
+ PF is part of the key. This is used to check primary keys and unique
+ keys involve all fields in PF (unless supported) and to derive the
+ key_map's used to quickly decide whether the index can be used to
+ derive which partitions are needed to scan.
+*/
+
+static void clear_indicator_in_key_fields(KEY *key_info)
+{
+ KEY_PART_INFO *key_part;
+ uint key_parts= key_info->user_defined_key_parts, i;
+ for (i= 0, key_part=key_info->key_part; i < key_parts; i++, key_part++)
+ key_part->field->flags&= (~GET_FIXED_FIELDS_FLAG);
+}
+
+
+/*
+ Set flag GET_FIXED_FIELDS_FLAG in all fields of a key.
+
+ SYNOPSIS
+ set_indicator_in_key_fields
+ key_info Reference to find the key fields
+
+ RETURN VALUE
+ NONE
+*/
+
+static void set_indicator_in_key_fields(KEY *key_info)
+{
+ KEY_PART_INFO *key_part;
+ uint key_parts= key_info->user_defined_key_parts, i;
+ for (i= 0, key_part=key_info->key_part; i < key_parts; i++, key_part++)
+ key_part->field->flags|= GET_FIXED_FIELDS_FLAG;
+}
+
+
+/*
+ Check if all or some fields in partition field array is part of a key
+ previously used to tag key fields.
+
+ SYNOPSIS
+ check_fields_in_PF()
+ ptr Partition field array
+ out:all_fields Is all fields of partition field array used in key
+ out:some_fields Is some fields of partition field array used in key
+
+ RETURN VALUE
+ all_fields, some_fields
+*/
+
+static void check_fields_in_PF(Field **ptr, bool *all_fields,
+ bool *some_fields)
+{
+ DBUG_ENTER("check_fields_in_PF");
+
+ *all_fields= TRUE;
+ *some_fields= FALSE;
+ if ((!ptr) || !(*ptr))
+ {
+ *all_fields= FALSE;
+ DBUG_VOID_RETURN;
+ }
+ do
+ {
+ /* Check if the field of the PF is part of the current key investigated */
+ if ((*ptr)->flags & GET_FIXED_FIELDS_FLAG)
+ *some_fields= TRUE;
+ else
+ *all_fields= FALSE;
+ } while (*(++ptr));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Clear flag GET_FIXED_FIELDS_FLAG in all fields of the table.
+ This routine is used for error handling purposes.
+
+ SYNOPSIS
+ clear_field_flag()
+ table TABLE object for which partition fields are set-up
+
+ RETURN VALUE
+ NONE
+*/
+
+static void clear_field_flag(TABLE *table)
+{
+ Field **ptr;
+ DBUG_ENTER("clear_field_flag");
+
+ for (ptr= table->field; *ptr; ptr++)
+ (*ptr)->flags&= (~GET_FIXED_FIELDS_FLAG);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ find_field_in_table_sef finds the field given its name. All fields get
+ GET_FIXED_FIELDS_FLAG set.
+
+ SYNOPSIS
+ handle_list_of_fields()
+ it A list of field names for the partition function
+ table TABLE object for which partition fields are set-up
+ part_info Reference to partitioning data structure
+ sub_part Is the table subpartitioned as well
+
+ RETURN VALUE
+ TRUE Fields in list of fields not part of table
+ FALSE All fields ok and array created
+
+ DESCRIPTION
+ This routine sets-up the partition field array for KEY partitioning, it
+ also verifies that all fields in the list of fields is actually a part of
+ the table.
+
+*/
+
+
+static bool handle_list_of_fields(List_iterator<char> it,
+ TABLE *table,
+ partition_info *part_info,
+ bool is_sub_part)
+{
+ Field *field;
+ bool result;
+ char *field_name;
+ bool is_list_empty= TRUE;
+ DBUG_ENTER("handle_list_of_fields");
+
+ while ((field_name= it++))
+ {
+ is_list_empty= FALSE;
+ field= find_field_in_table_sef(table, field_name);
+ if (likely(field != 0))
+ field->flags|= GET_FIXED_FIELDS_FLAG;
+ else
+ {
+ my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
+ clear_field_flag(table);
+ result= TRUE;
+ goto end;
+ }
+ }
+ if (is_list_empty && part_info->part_type == HASH_PARTITION)
+ {
+ uint primary_key= table->s->primary_key;
+ if (primary_key != MAX_KEY)
+ {
+ uint num_key_parts= table->key_info[primary_key].user_defined_key_parts, i;
+ /*
+ In the case of an empty list we use primary key as partition key.
+ */
+ for (i= 0; i < num_key_parts; i++)
+ {
+ Field *field= table->key_info[primary_key].key_part[i].field;
+ field->flags|= GET_FIXED_FIELDS_FLAG;
+ }
+ }
+ else
+ {
+ if (table->s->db_type()->partition_flags &&
+ (table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) &&
+ (table->s->db_type()->partition_flags() & HA_CAN_PARTITION))
+ {
+ /*
+ This engine can handle automatic partitioning and there is no
+ primary key. In this case we rely on that the engine handles
+ partitioning based on a hidden key. Thus we allocate no
+ array for partitioning fields.
+ */
+ DBUG_RETURN(FALSE);
+ }
+ else
+ {
+ my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ result= set_up_field_array(table, is_sub_part);
+end:
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Support function to check if all VALUES * (expression) is of the
+ right sign (no signed constants when unsigned partition function)
+
+ SYNOPSIS
+ check_signed_flag()
+ part_info Partition info object
+
+ RETURN VALUES
+ 0 No errors due to sign errors
+ >0 Sign error
+*/
+
+int check_signed_flag(partition_info *part_info)
+{
+ int error= 0;
+ uint i= 0;
+ if (part_info->part_type != HASH_PARTITION &&
+ part_info->part_expr->unsigned_flag)
+ {
+ List_iterator<partition_element> part_it(part_info->partitions);
+ do
+ {
+ partition_element *part_elem= part_it++;
+
+ if (part_elem->signed_flag)
+ {
+ my_error(ER_PARTITION_CONST_DOMAIN_ERROR, MYF(0));
+ error= ER_PARTITION_CONST_DOMAIN_ERROR;
+ break;
+ }
+ } while (++i < part_info->num_parts);
+ }
+ return error;
+}
+
+/*
+ init_lex_with_single_table and end_lex_with_single_table
+ are now in sql_lex.cc
+*/
+
+/*
+ The function uses a new feature in fix_fields where the flag
+ GET_FIXED_FIELDS_FLAG is set for all fields in the item tree.
+ This field must always be reset before returning from the function
+ since it is used for other purposes as well.
+
+ SYNOPSIS
+ fix_fields_part_func()
+ thd The thread object
+ func_expr The item tree reference of the partition function
+ table The table object
+ part_info Reference to partitioning data structure
+ is_sub_part Is the table subpartitioned as well
+ is_create_table_ind Indicator of whether openfrm was called as part of
+ CREATE or ALTER TABLE
+
+ RETURN VALUE
+ TRUE An error occurred, something was wrong with the
+ partition function.
+ FALSE Ok, a partition field array was created
+
+ DESCRIPTION
+ This function is used to build an array of partition fields for the
+ partitioning function and subpartitioning function. The partitioning
+ function is an item tree that must reference at least one field in the
+ table. This is checked first in the parser that the function doesn't
+ contain non-cacheable parts (like a random function) and by checking
+ here that the function isn't a constant function.
+
+ Calculate the number of fields in the partition function.
+ Use it allocate memory for array of Field pointers.
+ Initialise array of field pointers. Use information set when
+ calling fix_fields and reset it immediately after.
+ The get_fields_in_item_tree activates setting of bit in flags
+ on the field object.
+*/
+
+static bool fix_fields_part_func(THD *thd, Item* func_expr, TABLE *table,
+ bool is_sub_part, bool is_create_table_ind)
+{
+ partition_info *part_info= table->part_info;
+ bool result= TRUE;
+ int error;
+ LEX *old_lex= thd->lex;
+ LEX lex;
+ DBUG_ENTER("fix_fields_part_func");
+
+ if (init_lex_with_single_table(thd, table, &lex))
+ goto end;
+
+ func_expr->walk(&Item::change_context_processor, 0,
+ (uchar*) &lex.select_lex.context);
+ thd->where= "partition function";
+ /*
+ In execution we must avoid the use of thd->change_item_tree since
+ we might release memory before statement is completed. We do this
+ by temporarily setting the stmt_arena->mem_root to be the mem_root
+ of the table object, this also ensures that any memory allocated
+ during fix_fields will not be released at end of execution of this
+ statement. Thus the item tree will remain valid also in subsequent
+ executions of this table object. We do however not at the moment
+ support allocations during execution of val_int so any item class
+ that does this during val_int must be disallowed as partition
+ function.
+ SEE Bug #21658
+
+ This is a tricky call to prepare for since it can have a large number
+ of interesting side effects, both desirable and undesirable.
+ */
+ {
+ const bool save_agg_field= thd->lex->current_select->non_agg_field_used();
+ const bool save_agg_func= thd->lex->current_select->agg_func_used();
+ const nesting_map saved_allow_sum_func= thd->lex->allow_sum_func;
+ thd->lex->allow_sum_func= 0;
+
+ if (!(error= func_expr->fix_fields(thd, (Item**)&func_expr)))
+ func_expr->walk(&Item::vcol_in_partition_func_processor, 0, NULL);
+
+ /*
+ Restore agg_field/agg_func and allow_sum_func,
+ fix_fields should not affect mysql_select later, see Bug#46923.
+ */
+ thd->lex->current_select->set_non_agg_field_used(save_agg_field);
+ thd->lex->current_select->set_agg_func_used(save_agg_func);
+ thd->lex->allow_sum_func= saved_allow_sum_func;
+ }
+ if (unlikely(error))
+ {
+ DBUG_PRINT("info", ("Field in partition function not part of table"));
+ clear_field_flag(table);
+ goto end;
+ }
+ if (unlikely(func_expr->const_item()))
+ {
+ my_error(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR, MYF(0));
+ clear_field_flag(table);
+ goto end;
+ }
+
+ /*
+ We don't allow creating partitions with expressions with non matching
+ arguments as a (sub)partitioning function,
+ but we want to allow such expressions when opening existing tables for
+ easier maintenance. This exception should be deprecated at some point
+ in future so that we always throw an error.
+ */
+ if (func_expr->walk(&Item::check_valid_arguments_processor,
+ 0, NULL))
+ {
+ if (is_create_table_ind)
+ {
+ my_error(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR, MYF(0));
+ goto end;
+ }
+ else
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR,
+ ER(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR));
+ }
+
+ if ((!is_sub_part) && (error= check_signed_flag(part_info)))
+ goto end;
+ result= set_up_field_array(table, is_sub_part);
+end:
+ end_lex_with_single_table(thd, table, old_lex);
+#if !defined(DBUG_OFF)
+ func_expr->walk(&Item::change_context_processor, 0,
+ (uchar*) 0);
+#endif
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Check that the primary key contains all partition fields if defined
+
+ SYNOPSIS
+ check_primary_key()
+ table TABLE object for which partition fields are set-up
+
+ RETURN VALUES
+ TRUE Not all fields in partitioning function was part
+ of primary key
+ FALSE Ok, all fields of partitioning function were part
+ of primary key
+
+ DESCRIPTION
+ This function verifies that if there is a primary key that it contains
+ all the fields of the partition function.
+ This is a temporary limitation that will hopefully be removed after a
+ while.
+*/
+
+static bool check_primary_key(TABLE *table)
+{
+ uint primary_key= table->s->primary_key;
+ bool all_fields, some_fields;
+ bool result= FALSE;
+ DBUG_ENTER("check_primary_key");
+
+ if (primary_key < MAX_KEY)
+ {
+ set_indicator_in_key_fields(table->key_info+primary_key);
+ check_fields_in_PF(table->part_info->full_part_field_array,
+ &all_fields, &some_fields);
+ clear_indicator_in_key_fields(table->key_info+primary_key);
+ if (unlikely(!all_fields))
+ {
+ my_error(ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF,MYF(0),"PRIMARY KEY");
+ result= TRUE;
+ }
+ }
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Check that unique keys contains all partition fields
+
+ SYNOPSIS
+ check_unique_keys()
+ table TABLE object for which partition fields are set-up
+
+ RETURN VALUES
+ TRUE Not all fields in partitioning function was part
+ of all unique keys
+ FALSE Ok, all fields of partitioning function were part
+ of unique keys
+
+ DESCRIPTION
+ This function verifies that if there is a unique index that it contains
+ all the fields of the partition function.
+ This is a temporary limitation that will hopefully be removed after a
+ while.
+*/
+
+static bool check_unique_keys(TABLE *table)
+{
+ bool all_fields, some_fields;
+ bool result= FALSE;
+ uint keys= table->s->keys;
+ uint i;
+ DBUG_ENTER("check_unique_keys");
+
+ for (i= 0; i < keys; i++)
+ {
+ if (table->key_info[i].flags & HA_NOSAME) //Unique index
+ {
+ set_indicator_in_key_fields(table->key_info+i);
+ check_fields_in_PF(table->part_info->full_part_field_array,
+ &all_fields, &some_fields);
+ clear_indicator_in_key_fields(table->key_info+i);
+ if (unlikely(!all_fields))
+ {
+ my_error(ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF,MYF(0),"UNIQUE INDEX");
+ result= TRUE;
+ break;
+ }
+ }
+ }
+ DBUG_RETURN(result);
+}
+
+
+/*
+ An important optimisation is whether a range on a field can select a subset
+ of the partitions.
+ A prerequisite for this to happen is that the PF is a growing function OR
+ a shrinking function.
+ This can never happen for a multi-dimensional PF. Thus this can only happen
+ with PF with at most one field involved in the PF.
+ The idea is that if the function is a growing function and you know that
+ the field of the PF is 4 <= A <= 6 then we can convert this to a range
+ in the PF instead by setting the range to PF(4) <= PF(A) <= PF(6). In the
+ case of RANGE PARTITIONING and LIST PARTITIONING this can be used to
+ calculate a set of partitions rather than scanning all of them.
+ Thus the following prerequisites are there to check if sets of partitions
+ can be found.
+ 1) Only possible for RANGE and LIST partitioning (not for subpartitioning)
+ 2) Only possible if PF only contains 1 field
+ 3) Possible if PF is a growing function of the field
+ 4) Possible if PF is a shrinking function of the field
+ OBSERVATION:
+ 1) IF f1(A) is a growing function AND f2(A) is a growing function THEN
+ f1(A) + f2(A) is a growing function
+ f1(A) * f2(A) is a growing function if f1(A) >= 0 and f2(A) >= 0
+ 2) IF f1(A) is a growing function and f2(A) is a shrinking function THEN
+ f1(A) / f2(A) is a growing function if f1(A) >= 0 and f2(A) > 0
+ 3) IF A is a growing function then a function f(A) that removes the
+ least significant portion of A is a growing function
+ E.g. DATE(datetime) is a growing function
+ MONTH(datetime) is not a growing/shrinking function
+ 4) IF f1(A) is a growing function and f2(A) is a growing function THEN
+ f1(f2(A)) and f2(f1(A)) are also growing functions
+ 5) IF f1(A) is a shrinking function and f2(A) is a growing function THEN
+ f1(f2(A)) is a shrinking function and f2(f1(A)) is a shrinking function
+ 6) f1(A) = A is a growing function
+ 7) f1(A) = A*a + b (where a and b are constants) is a growing function
+
+ By analysing the item tree of the PF we can use these deducements and
+ derive whether the PF is a growing function or a shrinking function or
+ neither of it.
+
+ If the PF is range capable then a flag is set on the table object
+ indicating this to notify that we can use also ranges on the field
+ of the PF to deduce a set of partitions if the fields of the PF were
+ not all fully bound.
+
+ SYNOPSIS
+ check_range_capable_PF()
+ table TABLE object for which partition fields are set-up
+
+ DESCRIPTION
+ Support for this is not implemented yet.
+*/
+
+void check_range_capable_PF(TABLE *table)
+{
+ DBUG_ENTER("check_range_capable_PF");
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set up partition bitmaps
+
+ @param thd Thread object
+ @param part_info Reference to partitioning data structure
+
+ @return Operation status
+ @retval TRUE Memory allocation failure
+ @retval FALSE Success
+
+ Allocate memory for bitmaps of the partitioned table
+ and initialise it.
+*/
+
+static bool set_up_partition_bitmaps(THD *thd, partition_info *part_info)
+{
+ uint32 *bitmap_buf;
+ uint bitmap_bits= part_info->num_subparts?
+ (part_info->num_subparts* part_info->num_parts):
+ part_info->num_parts;
+ uint bitmap_bytes= bitmap_buffer_size(bitmap_bits);
+ DBUG_ENTER("set_up_partition_bitmaps");
+
+ DBUG_ASSERT(!part_info->bitmaps_are_initialized);
+
+ /* Allocate for both read and lock_partitions */
+ if (!(bitmap_buf= (uint32*) alloc_root(&part_info->table->mem_root,
+ bitmap_bytes * 2)))
+ {
+ mem_alloc_error(bitmap_bytes * 2);
+ DBUG_RETURN(TRUE);
+ }
+ my_bitmap_init(&part_info->read_partitions, bitmap_buf, bitmap_bits, FALSE);
+ /* Use the second half of the allocated buffer for lock_partitions */
+ my_bitmap_init(&part_info->lock_partitions, bitmap_buf + (bitmap_bytes / 4),
+ bitmap_bits, FALSE);
+ part_info->bitmaps_are_initialized= TRUE;
+ part_info->set_partition_bitmaps(NULL);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Set up partition key maps
+
+ SYNOPSIS
+ set_up_partition_key_maps()
+ table TABLE object for which partition fields are set-up
+ part_info Reference to partitioning data structure
+
+ RETURN VALUES
+ None
+
+ DESCRIPTION
+ This function sets up a couple of key maps to be able to quickly check
+ if an index ever can be used to deduce the partition fields or even
+ a part of the fields of the partition function.
+ We set up the following key_map's.
+ PF = Partition Function
+ 1) All fields of the PF is set even by equal on the first fields in the
+ key
+ 2) All fields of the PF is set if all fields of the key is set
+ 3) At least one field in the PF is set if all fields is set
+ 4) At least one field in the PF is part of the key
+*/
+
+static void set_up_partition_key_maps(TABLE *table,
+ partition_info *part_info)
+{
+ uint keys= table->s->keys;
+ uint i;
+ bool all_fields, some_fields;
+ DBUG_ENTER("set_up_partition_key_maps");
+
+ part_info->all_fields_in_PF.clear_all();
+ part_info->all_fields_in_PPF.clear_all();
+ part_info->all_fields_in_SPF.clear_all();
+ part_info->some_fields_in_PF.clear_all();
+ for (i= 0; i < keys; i++)
+ {
+ set_indicator_in_key_fields(table->key_info+i);
+ check_fields_in_PF(part_info->full_part_field_array,
+ &all_fields, &some_fields);
+ if (all_fields)
+ part_info->all_fields_in_PF.set_bit(i);
+ if (some_fields)
+ part_info->some_fields_in_PF.set_bit(i);
+ if (part_info->is_sub_partitioned())
+ {
+ check_fields_in_PF(part_info->part_field_array,
+ &all_fields, &some_fields);
+ if (all_fields)
+ part_info->all_fields_in_PPF.set_bit(i);
+ check_fields_in_PF(part_info->subpart_field_array,
+ &all_fields, &some_fields);
+ if (all_fields)
+ part_info->all_fields_in_SPF.set_bit(i);
+ }
+ clear_indicator_in_key_fields(table->key_info+i);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Set up function pointers for partition function
+
+ SYNOPSIS
+ set_up_partition_func_pointers()
+ part_info Reference to partitioning data structure
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ Set-up all function pointers for calculation of partition id,
+ subpartition id and the upper part in subpartitioning. This is to speed up
+ execution of get_partition_id which is executed once every record to be
+ written and deleted and twice for updates.
+*/
+
+static void set_up_partition_func_pointers(partition_info *part_info)
+{
+ DBUG_ENTER("set_up_partition_func_pointers");
+
+ if (part_info->is_sub_partitioned())
+ {
+ part_info->get_partition_id= get_partition_id_with_sub;
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ if (part_info->column_list)
+ part_info->get_part_partition_id= get_partition_id_range_col;
+ else
+ part_info->get_part_partition_id= get_partition_id_range;
+ if (part_info->list_of_subpart_fields)
+ {
+ if (part_info->linear_hash_ind)
+ part_info->get_subpartition_id= get_partition_id_linear_key_sub;
+ else
+ part_info->get_subpartition_id= get_partition_id_key_sub;
+ }
+ else
+ {
+ if (part_info->linear_hash_ind)
+ part_info->get_subpartition_id= get_partition_id_linear_hash_sub;
+ else
+ part_info->get_subpartition_id= get_partition_id_hash_sub;
+ }
+ }
+ else /* LIST Partitioning */
+ {
+ if (part_info->column_list)
+ part_info->get_part_partition_id= get_partition_id_list_col;
+ else
+ part_info->get_part_partition_id= get_partition_id_list;
+ if (part_info->list_of_subpart_fields)
+ {
+ if (part_info->linear_hash_ind)
+ part_info->get_subpartition_id= get_partition_id_linear_key_sub;
+ else
+ part_info->get_subpartition_id= get_partition_id_key_sub;
+ }
+ else
+ {
+ if (part_info->linear_hash_ind)
+ part_info->get_subpartition_id= get_partition_id_linear_hash_sub;
+ else
+ part_info->get_subpartition_id= get_partition_id_hash_sub;
+ }
+ }
+ }
+ else /* No subpartitioning */
+ {
+ part_info->get_part_partition_id= NULL;
+ part_info->get_subpartition_id= NULL;
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ if (part_info->column_list)
+ part_info->get_partition_id= get_partition_id_range_col;
+ else
+ part_info->get_partition_id= get_partition_id_range;
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+ if (part_info->column_list)
+ part_info->get_partition_id= get_partition_id_list_col;
+ else
+ part_info->get_partition_id= get_partition_id_list;
+ }
+ else /* HASH partitioning */
+ {
+ if (part_info->list_of_part_fields)
+ {
+ if (part_info->linear_hash_ind)
+ part_info->get_partition_id= get_partition_id_linear_key_nosub;
+ else
+ part_info->get_partition_id= get_partition_id_key_nosub;
+ }
+ else
+ {
+ if (part_info->linear_hash_ind)
+ part_info->get_partition_id= get_partition_id_linear_hash_nosub;
+ else
+ part_info->get_partition_id= get_partition_id_hash_nosub;
+ }
+ }
+ }
+ /*
+ We need special functions to handle character sets since they require copy
+ of field pointers and restore afterwards. For subpartitioned tables we do
+ the copy and restore individually on the part and subpart parts. For non-
+ subpartitioned tables we use the same functions as used for the parts part
+ of subpartioning.
+ Thus for subpartitioned tables the get_partition_id is always
+ get_partition_id_with_sub, even when character sets exists.
+ */
+ if (part_info->part_charset_field_array)
+ {
+ if (part_info->is_sub_partitioned())
+ {
+ DBUG_ASSERT(part_info->get_part_partition_id);
+ if (!part_info->column_list)
+ {
+ part_info->get_part_partition_id_charset=
+ part_info->get_part_partition_id;
+ part_info->get_part_partition_id= get_part_id_charset_func_part;
+ }
+ }
+ else
+ {
+ DBUG_ASSERT(part_info->get_partition_id);
+ if (!part_info->column_list)
+ {
+ part_info->get_part_partition_id_charset= part_info->get_partition_id;
+ part_info->get_part_partition_id= get_part_id_charset_func_part;
+ }
+ }
+ }
+ if (part_info->subpart_charset_field_array)
+ {
+ DBUG_ASSERT(part_info->get_subpartition_id);
+ part_info->get_subpartition_id_charset=
+ part_info->get_subpartition_id;
+ part_info->get_subpartition_id= get_part_id_charset_func_subpart;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ For linear hashing we need a mask which is on the form 2**n - 1 where
+ 2**n >= num_parts. Thus if num_parts is 6 then mask is 2**3 - 1 = 8 - 1 = 7.
+
+ SYNOPSIS
+ set_linear_hash_mask()
+ part_info Reference to partitioning data structure
+ num_parts Number of parts in linear hash partitioning
+
+ RETURN VALUE
+ NONE
+*/
+
+void set_linear_hash_mask(partition_info *part_info, uint num_parts)
+{
+ uint mask;
+
+ for (mask= 1; mask < num_parts; mask<<=1)
+ ;
+ part_info->linear_hash_mask= mask - 1;
+}
+
+
+/*
+ This function calculates the partition id provided the result of the hash
+ function using linear hashing parameters, mask and number of partitions.
+
+ SYNOPSIS
+ get_part_id_from_linear_hash()
+ hash_value Hash value calculated by HASH function or KEY function
+ mask Mask calculated previously by set_linear_hash_mask
+ num_parts Number of partitions in HASH partitioned part
+
+ RETURN VALUE
+ part_id The calculated partition identity (starting at 0)
+
+ DESCRIPTION
+ The partition is calculated according to the theory of linear hashing.
+ See e.g. Linear hashing: a new tool for file and table addressing,
+ Reprinted from VLDB-80 in Readings Database Systems, 2nd ed, M. Stonebraker
+ (ed.), Morgan Kaufmann 1994.
+*/
+
+static uint32 get_part_id_from_linear_hash(longlong hash_value, uint mask,
+ uint num_parts)
+{
+ uint32 part_id= (uint32)(hash_value & mask);
+
+ if (part_id >= num_parts)
+ {
+ uint new_mask= ((mask + 1) >> 1) - 1;
+ part_id= (uint32)(hash_value & new_mask);
+ }
+ return part_id;
+}
+
+
+/*
+ Check if a particular field is in need of character set
+ handling for partition functions.
+
+ SYNOPSIS
+ field_is_partition_charset()
+ field The field to check
+
+ RETURN VALUES
+ FALSE Not in need of character set handling
+ TRUE In need of character set handling
+*/
+
+bool field_is_partition_charset(Field *field)
+{
+ if (!(field->type() == MYSQL_TYPE_STRING) &&
+ !(field->type() == MYSQL_TYPE_VARCHAR))
+ return FALSE;
+ {
+ CHARSET_INFO *cs= field->charset();
+ if (!(field->type() == MYSQL_TYPE_STRING) ||
+ !(cs->state & MY_CS_BINSORT))
+ return TRUE;
+ return FALSE;
+ }
+}
+
+
+/*
+ Check that partition function doesn't contain any forbidden
+ character sets and collations.
+
+ SYNOPSIS
+ check_part_func_fields()
+ ptr Array of Field pointers
+ ok_with_charsets Will we report allowed charset
+ fields as ok
+ RETURN VALUES
+ FALSE Success
+ TRUE Error
+
+ DESCRIPTION
+ We will check in this routine that the fields of the partition functions
+ do not contain unallowed parts. It can also be used to check if there
+ are fields that require special care by calling my_strnxfrm before
+ calling the functions to calculate partition id.
+*/
+
+bool check_part_func_fields(Field **ptr, bool ok_with_charsets)
+{
+ Field *field;
+ DBUG_ENTER("check_part_func_fields");
+
+ while ((field= *(ptr++)))
+ {
+ /*
+ For CHAR/VARCHAR fields we need to take special precautions.
+ Binary collation with CHAR is automatically supported. Other
+ types need some kind of standardisation function handling
+ */
+ if (field_is_partition_charset(field))
+ {
+ CHARSET_INFO *cs= field->charset();
+ if (!ok_with_charsets ||
+ cs->mbmaxlen > 1 ||
+ cs->strxfrm_multiply > 1)
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ fix partition functions
+
+ SYNOPSIS
+ fix_partition_func()
+ thd The thread object
+ table TABLE object for which partition fields are set-up
+ is_create_table_ind Indicator of whether openfrm was called as part of
+ CREATE or ALTER TABLE
+
+ RETURN VALUE
+ TRUE Error
+ FALSE Success
+
+ DESCRIPTION
+ The name parameter contains the full table name and is used to get the
+ database name of the table which is used to set-up a correct
+ TABLE_LIST object for use in fix_fields.
+
+NOTES
+ This function is called as part of opening the table by opening the .frm
+ file. It is a part of CREATE TABLE to do this so it is quite permissible
+ that errors due to erroneus syntax isn't found until we come here.
+ If the user has used a non-existing field in the table is one such example
+ of an error that is not discovered until here.
+*/
+
+bool fix_partition_func(THD *thd, TABLE *table,
+ bool is_create_table_ind)
+{
+ bool result= TRUE;
+ partition_info *part_info= table->part_info;
+ enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
+ DBUG_ENTER("fix_partition_func");
+
+ if (part_info->fixed)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ thd->mark_used_columns= MARK_COLUMNS_NONE;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+
+ if (!is_create_table_ind ||
+ thd->lex->sql_command != SQLCOM_CREATE_TABLE)
+ {
+ if (partition_default_handling(table, part_info,
+ is_create_table_ind,
+ table->s->normalized_path.str))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ if (part_info->is_sub_partitioned())
+ {
+ DBUG_ASSERT(part_info->subpart_type == HASH_PARTITION);
+ /*
+ Subpartition is defined. We need to verify that subpartitioning
+ function is correct.
+ */
+ if (part_info->linear_hash_ind)
+ set_linear_hash_mask(part_info, part_info->num_subparts);
+ if (part_info->list_of_subpart_fields)
+ {
+ List_iterator<char> it(part_info->subpart_field_list);
+ if (unlikely(handle_list_of_fields(it, table, part_info, TRUE)))
+ goto end;
+ }
+ else
+ {
+ if (unlikely(fix_fields_part_func(thd, part_info->subpart_expr,
+ table, TRUE, is_create_table_ind)))
+ goto end;
+ if (unlikely(part_info->subpart_expr->result_type() != INT_RESULT))
+ {
+ part_info->report_part_expr_error(TRUE);
+ goto end;
+ }
+ }
+ }
+ DBUG_ASSERT(part_info->part_type != NOT_A_PARTITION);
+ /*
+ Partition is defined. We need to verify that partitioning
+ function is correct.
+ */
+ if (part_info->part_type == HASH_PARTITION)
+ {
+ if (part_info->linear_hash_ind)
+ set_linear_hash_mask(part_info, part_info->num_parts);
+ if (part_info->list_of_part_fields)
+ {
+ List_iterator<char> it(part_info->part_field_list);
+ if (unlikely(handle_list_of_fields(it, table, part_info, FALSE)))
+ goto end;
+ }
+ else
+ {
+ if (unlikely(fix_fields_part_func(thd, part_info->part_expr,
+ table, FALSE, is_create_table_ind)))
+ goto end;
+ if (unlikely(part_info->part_expr->result_type() != INT_RESULT))
+ {
+ part_info->report_part_expr_error(FALSE);
+ goto end;
+ }
+ }
+ part_info->fixed= TRUE;
+ }
+ else
+ {
+ const char *error_str;
+ if (part_info->column_list)
+ {
+ List_iterator<char> it(part_info->part_field_list);
+ if (unlikely(handle_list_of_fields(it, table, part_info, FALSE)))
+ goto end;
+ }
+ else
+ {
+ if (unlikely(fix_fields_part_func(thd, part_info->part_expr,
+ table, FALSE, is_create_table_ind)))
+ goto end;
+ }
+ part_info->fixed= TRUE;
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ error_str= partition_keywords[PKW_RANGE].str;
+ if (unlikely(part_info->check_range_constants(thd)))
+ goto end;
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+ error_str= partition_keywords[PKW_LIST].str;
+ if (unlikely(part_info->check_list_constants(thd)))
+ goto end;
+ }
+ else
+ {
+ DBUG_ASSERT(0);
+ my_error(ER_INCONSISTENT_PARTITION_INFO_ERROR, MYF(0));
+ goto end;
+ }
+ if (unlikely(part_info->num_parts < 1))
+ {
+ my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), error_str);
+ goto end;
+ }
+ if (unlikely(!part_info->column_list &&
+ part_info->part_expr->result_type() != INT_RESULT))
+ {
+ part_info->report_part_expr_error(FALSE);
+ goto end;
+ }
+ }
+ if (((part_info->part_type != HASH_PARTITION ||
+ part_info->list_of_part_fields == FALSE) &&
+ !part_info->column_list &&
+ check_part_func_fields(part_info->part_field_array, TRUE)) ||
+ (part_info->list_of_subpart_fields == FALSE &&
+ part_info->is_sub_partitioned() &&
+ check_part_func_fields(part_info->subpart_field_array, TRUE)))
+ {
+ /*
+ Range/List/HASH (but not KEY) and not COLUMNS or HASH subpartitioning
+ with columns in the partitioning expression using unallowed charset.
+ */
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ goto end;
+ }
+ if (unlikely(create_full_part_field_array(thd, table, part_info)))
+ goto end;
+ if (unlikely(check_primary_key(table)))
+ goto end;
+ if (unlikely((!(table->s->db_type()->partition_flags &&
+ (table->s->db_type()->partition_flags() & HA_CAN_PARTITION_UNIQUE))) &&
+ check_unique_keys(table)))
+ goto end;
+ if (unlikely(set_up_partition_bitmaps(thd, part_info)))
+ goto end;
+ if (unlikely(part_info->set_up_charset_field_preps()))
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ goto end;
+ }
+ if (unlikely(part_info->check_partition_field_length()))
+ {
+ my_error(ER_PARTITION_FIELDS_TOO_LONG, MYF(0));
+ goto end;
+ }
+ check_range_capable_PF(table);
+ set_up_partition_key_maps(table, part_info);
+ set_up_partition_func_pointers(part_info);
+ set_up_range_analysis_info(part_info);
+ table->file->set_part_info(part_info);
+ result= FALSE;
+end:
+ thd->mark_used_columns= save_mark_used_columns;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+ DBUG_RETURN(result);
+}
+
+
+/*
+ The code below is support routines for the reverse parsing of the
+ partitioning syntax. This feature is very useful to generate syntax for
+ all default values to avoid all default checking when opening the frm
+ file. It is also used when altering the partitioning by use of various
+ ALTER TABLE commands. Finally it is used for SHOW CREATE TABLES.
+*/
+
+static int add_write(File fptr, const char *buf, uint len)
+{
+ uint ret_code= mysql_file_write(fptr, (const uchar*)buf, len, MYF(MY_FNABP));
+
+ if (likely(ret_code == 0))
+ return 0;
+ else
+ return 1;
+}
+
+static int add_string_object(File fptr, String *string)
+{
+ return add_write(fptr, string->ptr(), string->length());
+}
+
+static int add_string(File fptr, const char *string)
+{
+ return add_write(fptr, string, strlen(string));
+}
+
+static int add_string_len(File fptr, const char *string, uint len)
+{
+ return add_write(fptr, string, len);
+}
+
+static int add_space(File fptr)
+{
+ return add_string(fptr, space_str);
+}
+
+static int add_comma(File fptr)
+{
+ return add_string(fptr, comma_str);
+}
+
+static int add_equal(File fptr)
+{
+ return add_string(fptr, equal_str);
+}
+
+static int add_end_parenthesis(File fptr)
+{
+ return add_string(fptr, end_paren_str);
+}
+
+static int add_begin_parenthesis(File fptr)
+{
+ return add_string(fptr, begin_paren_str);
+}
+
+static int add_part_key_word(File fptr, const char *key_string)
+{
+ int err= add_string(fptr, key_string);
+ err+= add_space(fptr);
+ return err;
+}
+
+static int add_partition(File fptr)
+{
+ char buff[22];
+ strxmov(buff, part_str, space_str, NullS);
+ return add_string(fptr, buff);
+}
+
+static int add_subpartition(File fptr)
+{
+ int err= add_string(fptr, sub_str);
+
+ return err + add_partition(fptr);
+}
+
+static int add_partition_by(File fptr)
+{
+ char buff[22];
+ strxmov(buff, part_str, space_str, by_str, space_str, NullS);
+ return add_string(fptr, buff);
+}
+
+static int add_subpartition_by(File fptr)
+{
+ int err= add_string(fptr, sub_str);
+
+ return err + add_partition_by(fptr);
+}
+
+static int add_part_field_list(File fptr, List<char> field_list)
+{
+ uint i, num_fields;
+ int err= 0;
+
+ List_iterator<char> part_it(field_list);
+ num_fields= field_list.elements;
+ i= 0;
+ err+= add_begin_parenthesis(fptr);
+ while (i < num_fields)
+ {
+ const char *field_str= part_it++;
+ String field_string("", 0, system_charset_info);
+ THD *thd= current_thd;
+ ulonglong save_options= thd->variables.option_bits;
+ thd->variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE;
+ append_identifier(thd, &field_string, field_str,
+ strlen(field_str));
+ thd->variables.option_bits= save_options;
+ err+= add_string_object(fptr, &field_string);
+ if (i != (num_fields-1))
+ err+= add_comma(fptr);
+ i++;
+ }
+ err+= add_end_parenthesis(fptr);
+ return err;
+}
+
+static int add_name_string(File fptr, const char *name)
+{
+ int err;
+ String name_string("", 0, system_charset_info);
+ THD *thd= current_thd;
+ ulonglong save_options= thd->variables.option_bits;
+ thd->variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE;
+ append_identifier(thd, &name_string, name,
+ strlen(name));
+ thd->variables.option_bits= save_options;
+ err= add_string_object(fptr, &name_string);
+ return err;
+}
+
+static int add_int(File fptr, longlong number)
+{
+ char buff[32];
+ llstr(number, buff);
+ return add_string(fptr, buff);
+}
+
+static int add_uint(File fptr, ulonglong number)
+{
+ char buff[32];
+ longlong2str(number, buff, 10);
+ return add_string(fptr, buff);
+}
+
+/*
+ Must escape strings in partitioned tables frm-files,
+ parsing it later with mysql_unpack_partition will fail otherwise.
+*/
+static int add_quoted_string(File fptr, const char *quotestr)
+{
+ String escapedstr;
+ int err= add_string(fptr, "'");
+ err+= escapedstr.append_for_single_quote(quotestr);
+ err+= add_string(fptr, escapedstr.c_ptr_safe());
+ return err + add_string(fptr, "'");
+}
+
+/**
+ @brief Truncate the partition file name from a path it it exists.
+
+ @note A partition file name will contian one or more '#' characters.
+One of the occurances of '#' will be either "#P#" or "#p#" depending
+on whether the storage engine has converted the filename to lower case.
+*/
+void truncate_partition_filename(char *path)
+{
+ if (path)
+ {
+ char* last_slash= strrchr(path, FN_LIBCHAR);
+
+ if (!last_slash)
+ last_slash= strrchr(path, FN_LIBCHAR2);
+
+ if (last_slash)
+ {
+ /* Look for a partition-type filename */
+ for (char* pound= strchr(last_slash, '#');
+ pound; pound = strchr(pound + 1, '#'))
+ {
+ if ((pound[1] == 'P' || pound[1] == 'p') && pound[2] == '#')
+ {
+ last_slash[0] = '\0'; /* truncate the file name */
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ @brief Output a filepath. Similar to add_keyword_string except it
+also converts \ to / on Windows and skips the partition file name at
+the end if found.
+
+ @note When Mysql sends a DATA DIRECTORY from SQL for partitions it does
+not use a file name, but it does for DATA DIRECTORY on a non-partitioned
+table. So when the storage engine is asked for the DATA DIRECTORY string
+after a restart through Handler::update_create_options(), the storage
+engine may include the filename.
+*/
+static int add_keyword_path(File fptr, const char *keyword,
+ const char *path)
+{
+ int err= add_string(fptr, keyword);
+
+ err+= add_space(fptr);
+ err+= add_equal(fptr);
+ err+= add_space(fptr);
+
+ char temp_path[FN_REFLEN];
+ strcpy(temp_path, path);
+#ifdef __WIN__
+ /* Convert \ to / to be able to create table on unix */
+ char *pos, *end;
+ uint length= strlen(temp_path);
+ for (pos= temp_path, end= pos+length ; pos < end ; pos++)
+ {
+ if (*pos == '\\')
+ *pos = '/';
+ }
+#endif
+
+ /*
+ If the partition file name with its "#P#" identifier
+ is found after the last slash, truncate that filename.
+ */
+ truncate_partition_filename(temp_path);
+
+ err+= add_quoted_string(fptr, temp_path);
+
+ return err + add_space(fptr);
+}
+
+static int add_keyword_string(File fptr, const char *keyword,
+ bool should_use_quotes,
+ const char *keystr)
+{
+ int err= add_string(fptr, keyword);
+
+ err+= add_space(fptr);
+ err+= add_equal(fptr);
+ err+= add_space(fptr);
+ if (should_use_quotes)
+ err+= add_quoted_string(fptr, keystr);
+ else
+ err+= add_string(fptr, keystr);
+ return err + add_space(fptr);
+}
+
+static int add_keyword_int(File fptr, const char *keyword, longlong num)
+{
+ int err= add_string(fptr, keyword);
+
+ err+= add_space(fptr);
+ err+= add_equal(fptr);
+ err+= add_space(fptr);
+ err+= add_int(fptr, num);
+ return err + add_space(fptr);
+}
+
+static int add_engine(File fptr, handlerton *engine_type)
+{
+ const char *engine_str= ha_resolve_storage_engine_name(engine_type);
+ DBUG_PRINT("info", ("ENGINE: %s", engine_str));
+ int err= add_string(fptr, "ENGINE = ");
+ return err + add_string(fptr, engine_str);
+}
+
+static int add_partition_options(File fptr, partition_element *p_elem)
+{
+ int err= 0;
+
+ err+= add_space(fptr);
+ if (p_elem->tablespace_name)
+ err+= add_keyword_string(fptr,"TABLESPACE", FALSE,
+ p_elem->tablespace_name);
+ if (p_elem->nodegroup_id != UNDEF_NODEGROUP)
+ err+= add_keyword_int(fptr,"NODEGROUP",(longlong)p_elem->nodegroup_id);
+ if (p_elem->part_max_rows)
+ err+= add_keyword_int(fptr,"MAX_ROWS",(longlong)p_elem->part_max_rows);
+ if (p_elem->part_min_rows)
+ err+= add_keyword_int(fptr,"MIN_ROWS",(longlong)p_elem->part_min_rows);
+ if (!(current_thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE))
+ {
+ if (p_elem->data_file_name)
+ err+= add_keyword_path(fptr, "DATA DIRECTORY", p_elem->data_file_name);
+ if (p_elem->index_file_name)
+ err+= add_keyword_path(fptr, "INDEX DIRECTORY", p_elem->index_file_name);
+ }
+ if (p_elem->part_comment)
+ err+= add_keyword_string(fptr, "COMMENT", TRUE, p_elem->part_comment);
+ if (p_elem->connect_string.length)
+ err+= add_keyword_string(fptr, "CONNECTION", TRUE,
+ p_elem->connect_string.str);
+ return err + add_engine(fptr,p_elem->engine_type);
+}
+
+
+/*
+ Check partition fields for result type and if they need
+ to check the character set.
+
+ SYNOPSIS
+ check_part_field()
+ sql_type Type provided by user
+ field_name Name of field, used for error handling
+ result_type Out value: Result type of field
+ need_cs_check Out value: Do we need character set check
+
+ RETURN VALUES
+ TRUE Error
+ FALSE Ok
+*/
+
+static int check_part_field(enum_field_types sql_type,
+ const char *field_name,
+ Item_result *result_type,
+ bool *need_cs_check)
+{
+ if (sql_type >= MYSQL_TYPE_TINY_BLOB &&
+ sql_type <= MYSQL_TYPE_BLOB)
+ {
+ my_error(ER_BLOB_FIELD_IN_PART_FUNC_ERROR, MYF(0));
+ return TRUE;
+ }
+ switch (sql_type)
+ {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ *result_type= INT_RESULT;
+ *need_cs_check= FALSE;
+ return FALSE;
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIME2:
+ case MYSQL_TYPE_DATETIME2:
+ *result_type= STRING_RESULT;
+ *need_cs_check= TRUE;
+ return FALSE;
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VAR_STRING:
+ *result_type= STRING_RESULT;
+ *need_cs_check= TRUE;
+ return FALSE;
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_TIMESTAMP2:
+ case MYSQL_TYPE_NULL:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_GEOMETRY:
+ goto error;
+ default:
+ goto error;
+ }
+error:
+ my_error(ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD, MYF(0),
+ field_name);
+ return TRUE;
+}
+
+
+/*
+ Find the given field's Create_field object using name of field
+
+ SYNOPSIS
+ get_sql_field()
+ field_name Field name
+ alter_info Info from ALTER TABLE/CREATE TABLE
+
+ RETURN VALUE
+ sql_field Object filled in by parser about field
+ NULL No field found
+*/
+
+static Create_field* get_sql_field(char *field_name,
+ Alter_info *alter_info)
+{
+ List_iterator<Create_field> it(alter_info->create_list);
+ Create_field *sql_field;
+ DBUG_ENTER("get_sql_field");
+
+ while ((sql_field= it++))
+ {
+ if (!(my_strcasecmp(system_charset_info,
+ sql_field->field_name,
+ field_name)))
+ {
+ DBUG_RETURN(sql_field);
+ }
+ }
+ DBUG_RETURN(NULL);
+}
+
+
+static int add_column_list_values(File fptr, partition_info *part_info,
+ part_elem_value *list_value,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info)
+{
+ int err= 0;
+ uint i;
+ List_iterator<char> it(part_info->part_field_list);
+ uint num_elements= part_info->part_field_list.elements;
+ bool use_parenthesis= (part_info->part_type == LIST_PARTITION &&
+ part_info->num_columns > 1U);
+
+ if (use_parenthesis)
+ err+= add_begin_parenthesis(fptr);
+ for (i= 0; i < num_elements; i++)
+ {
+ part_column_list_val *col_val= &list_value->col_val_array[i];
+ char *field_name= it++;
+ if (col_val->max_value)
+ err+= add_string(fptr, partition_keywords[PKW_MAXVALUE].str);
+ else if (col_val->null_value)
+ err+= add_string(fptr, "NULL");
+ else
+ {
+ char buffer[MAX_KEY_LENGTH];
+ String str(buffer, sizeof(buffer), &my_charset_bin);
+ Item *item_expr= col_val->item_expression;
+ if (item_expr->null_value)
+ err+= add_string(fptr, "NULL");
+ else
+ {
+ String *res;
+ const CHARSET_INFO *field_cs;
+ bool need_cs_check= FALSE;
+ Item_result result_type= STRING_RESULT;
+
+ /*
+ This function is called at a very early stage, even before
+ we have prepared the sql_field objects. Thus we have to
+ find the proper sql_field object and get the character set
+ from that object.
+ */
+ if (create_info)
+ {
+ Create_field *sql_field;
+
+ if (!(sql_field= get_sql_field(field_name,
+ alter_info)))
+ {
+ my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
+ return 1;
+ }
+ if (check_part_field(sql_field->sql_type,
+ sql_field->field_name,
+ &result_type,
+ &need_cs_check))
+ return 1;
+ if (need_cs_check)
+ field_cs= get_sql_field_charset(sql_field, create_info);
+ else
+ field_cs= NULL;
+ }
+ else
+ {
+ Field *field= part_info->part_field_array[i];
+ result_type= field->result_type();
+ if (check_part_field(field->real_type(),
+ field->field_name,
+ &result_type,
+ &need_cs_check))
+ return 1;
+ DBUG_ASSERT(result_type == field->result_type());
+ if (need_cs_check)
+ field_cs= field->charset();
+ else
+ field_cs= NULL;
+ }
+ if (result_type != item_expr->result_type())
+ {
+ my_error(ER_WRONG_TYPE_COLUMN_VALUE_ERROR, MYF(0));
+ return 1;
+ }
+ if (field_cs && field_cs != item_expr->collation.collation)
+ {
+ if (!(item_expr= convert_charset_partition_constant(item_expr,
+ field_cs)))
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ return 1;
+ }
+ }
+ {
+ String val_conv;
+ val_conv.set_charset(system_charset_info);
+ res= item_expr->val_str(&str);
+ if (get_cs_converted_part_value_from_string(current_thd,
+ item_expr, res,
+ &val_conv, field_cs,
+ (bool)(alter_info != NULL)))
+ return 1;
+ err+= add_string_object(fptr, &val_conv);
+ }
+ }
+ }
+ if (i != (num_elements - 1))
+ err+= add_string(fptr, comma_str);
+ }
+ if (use_parenthesis)
+ err+= add_end_parenthesis(fptr);
+ return err;
+}
+
+static int add_partition_values(File fptr, partition_info *part_info,
+ partition_element *p_elem,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info)
+{
+ int err= 0;
+
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ err+= add_string(fptr, " VALUES LESS THAN ");
+ if (part_info->column_list)
+ {
+ List_iterator<part_elem_value> list_val_it(p_elem->list_val_list);
+ part_elem_value *list_value= list_val_it++;
+ err+= add_begin_parenthesis(fptr);
+ err+= add_column_list_values(fptr, part_info, list_value,
+ create_info, alter_info);
+ err+= add_end_parenthesis(fptr);
+ }
+ else
+ {
+ if (!p_elem->max_value)
+ {
+ err+= add_begin_parenthesis(fptr);
+ if (p_elem->signed_flag)
+ err+= add_int(fptr, p_elem->range_value);
+ else
+ err+= add_uint(fptr, p_elem->range_value);
+ err+= add_end_parenthesis(fptr);
+ }
+ else
+ err+= add_string(fptr, partition_keywords[PKW_MAXVALUE].str);
+ }
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+ uint i;
+ List_iterator<part_elem_value> list_val_it(p_elem->list_val_list);
+ err+= add_string(fptr, " VALUES IN ");
+ uint num_items= p_elem->list_val_list.elements;
+
+ err+= add_begin_parenthesis(fptr);
+ if (p_elem->has_null_value)
+ {
+ err+= add_string(fptr, "NULL");
+ if (num_items == 0)
+ {
+ err+= add_end_parenthesis(fptr);
+ goto end;
+ }
+ err+= add_comma(fptr);
+ }
+ i= 0;
+ do
+ {
+ part_elem_value *list_value= list_val_it++;
+
+ if (part_info->column_list)
+ err+= add_column_list_values(fptr, part_info, list_value,
+ create_info, alter_info);
+ else
+ {
+ if (!list_value->unsigned_flag)
+ err+= add_int(fptr, list_value->value);
+ else
+ err+= add_uint(fptr, list_value->value);
+ }
+ if (i != (num_items-1))
+ err+= add_comma(fptr);
+ } while (++i < num_items);
+ err+= add_end_parenthesis(fptr);
+ }
+end:
+ return err;
+}
+
+
+/**
+ Add 'KEY' word, with optional 'ALGORTIHM = N'.
+
+ @param fptr File to write to.
+ @param part_info partition_info holding the used key_algorithm
+ @param current_comment_start NULL, or comment string encapsulating the
+ PARTITION BY clause.
+
+ @return Operation status.
+ @retval 0 Success
+ @retval != 0 Failure
+*/
+
+static int add_key_with_algorithm(File fptr, partition_info *part_info,
+ const char *current_comment_start)
+{
+ int err= 0;
+ err+= add_part_key_word(fptr, partition_keywords[PKW_KEY].str);
+
+ /*
+ current_comment_start is given when called from SHOW CREATE TABLE,
+ Then only add ALGORITHM = 1, not the default 2 or non-set 0!
+ For .frm current_comment_start is NULL, then add ALGORITHM if != 0.
+ */
+ if (part_info->key_algorithm == partition_info::KEY_ALGORITHM_51 || // SHOW
+ (!current_comment_start && // .frm
+ (part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE)))
+ {
+ /* If we already are within a comment, end that comment first. */
+ if (current_comment_start)
+ err+= add_string(fptr, "*/ ");
+ err+= add_string(fptr, "/*!50611 ");
+ err+= add_part_key_word(fptr, partition_keywords[PKW_ALGORITHM].str);
+ err+= add_equal(fptr);
+ err+= add_space(fptr);
+ err+= add_int(fptr, part_info->key_algorithm);
+ err+= add_space(fptr);
+ err+= add_string(fptr, "*/ ");
+ if (current_comment_start)
+ {
+ /* Skip new line. */
+ if (current_comment_start[0] == '\n')
+ current_comment_start++;
+ err+= add_string(fptr, current_comment_start);
+ err+= add_space(fptr);
+ }
+ }
+ return err;
+}
+
+
+/*
+ Generate the partition syntax from the partition data structure.
+ Useful for support of generating defaults, SHOW CREATE TABLES
+ and easy partition management.
+
+ SYNOPSIS
+ generate_partition_syntax()
+ part_info The partitioning data structure
+ buf_length A pointer to the returned buffer length
+ use_sql_alloc Allocate buffer from sql_alloc if true
+ otherwise use my_malloc
+ show_partition_options Should we display partition options
+ create_info Info generated by parser
+ alter_info Info generated by parser
+
+ RETURN VALUES
+ NULL error
+ buf, buf_length Buffer and its length
+
+ DESCRIPTION
+ Here we will generate the full syntax for the given command where all
+ defaults have been expanded. By so doing the it is also possible to
+ make lots of checks of correctness while at it.
+ This could will also be reused for SHOW CREATE TABLES and also for all
+ type ALTER TABLE commands focusing on changing the PARTITION structure
+ in any fashion.
+
+ The implementation writes the syntax to a temporary file (essentially
+ an abstraction of a dynamic array) and if all writes goes well it
+ allocates a buffer and writes the syntax into this one and returns it.
+
+ As a security precaution the file is deleted before writing into it. This
+ means that no other processes on the machine can open and read the file
+ while this processing is ongoing.
+
+ The code is optimised for minimal code size since it is not used in any
+ common queries.
+*/
+
+char *generate_partition_syntax(partition_info *part_info,
+ uint *buf_length,
+ bool use_sql_alloc,
+ bool show_partition_options,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ const char *current_comment_start)
+{
+ uint i,j, tot_num_parts, num_subparts;
+ partition_element *part_elem;
+ ulonglong buffer_length;
+ char path[FN_REFLEN];
+ int err= 0;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ File fptr;
+ char *buf= NULL; //Return buffer
+ DBUG_ENTER("generate_partition_syntax");
+
+ if (unlikely(((fptr= create_temp_file(path,mysql_tmpdir,"psy",
+ O_RDWR | O_BINARY | O_TRUNC |
+ O_TEMPORARY, MYF(MY_WME)))) < 0))
+ DBUG_RETURN(NULL);
+#ifndef __WIN__
+ unlink(path);
+#endif
+ err+= add_space(fptr);
+ err+= add_partition_by(fptr);
+ switch (part_info->part_type)
+ {
+ case RANGE_PARTITION:
+ err+= add_part_key_word(fptr, partition_keywords[PKW_RANGE].str);
+ break;
+ case LIST_PARTITION:
+ err+= add_part_key_word(fptr, partition_keywords[PKW_LIST].str);
+ break;
+ case HASH_PARTITION:
+ if (part_info->linear_hash_ind)
+ err+= add_string(fptr, partition_keywords[PKW_LINEAR].str);
+ if (part_info->list_of_part_fields)
+ {
+ err+= add_key_with_algorithm(fptr, part_info,
+ current_comment_start);
+ err+= add_part_field_list(fptr, part_info->part_field_list);
+ }
+ else
+ err+= add_part_key_word(fptr, partition_keywords[PKW_HASH].str);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ /* We really shouldn't get here, no use in continuing from here */
+ my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+ DBUG_RETURN(NULL);
+ }
+ if (part_info->part_expr)
+ {
+ err+= add_begin_parenthesis(fptr);
+ err+= add_string_len(fptr, part_info->part_func_string,
+ part_info->part_func_len);
+ err+= add_end_parenthesis(fptr);
+ }
+ else if (part_info->column_list)
+ {
+ err+= add_string(fptr, partition_keywords[PKW_COLUMNS].str);
+ err+= add_part_field_list(fptr, part_info->part_field_list);
+ }
+ if ((!part_info->use_default_num_partitions) &&
+ part_info->use_default_partitions)
+ {
+ err+= add_string(fptr, "\n");
+ err+= add_string(fptr, "PARTITIONS ");
+ err+= add_int(fptr, part_info->num_parts);
+ }
+ if (part_info->is_sub_partitioned())
+ {
+ err+= add_string(fptr, "\n");
+ err+= add_subpartition_by(fptr);
+ /* Must be hash partitioning for subpartitioning */
+ if (part_info->linear_hash_ind)
+ err+= add_string(fptr, partition_keywords[PKW_LINEAR].str);
+ if (part_info->list_of_subpart_fields)
+ {
+ err+= add_key_with_algorithm(fptr, part_info,
+ current_comment_start);
+ err+= add_part_field_list(fptr, part_info->subpart_field_list);
+ }
+ else
+ err+= add_part_key_word(fptr, partition_keywords[PKW_HASH].str);
+ if (part_info->subpart_expr)
+ {
+ err+= add_begin_parenthesis(fptr);
+ err+= add_string_len(fptr, part_info->subpart_func_string,
+ part_info->subpart_func_len);
+ err+= add_end_parenthesis(fptr);
+ }
+ if ((!part_info->use_default_num_subpartitions) &&
+ part_info->use_default_subpartitions)
+ {
+ err+= add_string(fptr, "\n");
+ err+= add_string(fptr, "SUBPARTITIONS ");
+ err+= add_int(fptr, part_info->num_subparts);
+ }
+ }
+ tot_num_parts= part_info->partitions.elements;
+ num_subparts= part_info->num_subparts;
+
+ if (!part_info->use_default_partitions)
+ {
+ bool first= TRUE;
+ err+= add_string(fptr, "\n");
+ err+= add_begin_parenthesis(fptr);
+ i= 0;
+ do
+ {
+ part_elem= part_it++;
+ if (part_elem->part_state != PART_TO_BE_DROPPED &&
+ part_elem->part_state != PART_REORGED_DROPPED)
+ {
+ if (!first)
+ {
+ err+= add_comma(fptr);
+ err+= add_string(fptr, "\n");
+ err+= add_space(fptr);
+ }
+ first= FALSE;
+ err+= add_partition(fptr);
+ err+= add_name_string(fptr, part_elem->partition_name);
+ err+= add_partition_values(fptr, part_info, part_elem,
+ create_info, alter_info);
+ if (!part_info->is_sub_partitioned() ||
+ part_info->use_default_subpartitions)
+ {
+ if (show_partition_options)
+ err+= add_partition_options(fptr, part_elem);
+ }
+ else
+ {
+ err+= add_string(fptr, "\n");
+ err+= add_space(fptr);
+ err+= add_begin_parenthesis(fptr);
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ j= 0;
+ do
+ {
+ part_elem= sub_it++;
+ err+= add_subpartition(fptr);
+ err+= add_name_string(fptr, part_elem->partition_name);
+ if (show_partition_options)
+ err+= add_partition_options(fptr, part_elem);
+ if (j != (num_subparts-1))
+ {
+ err+= add_comma(fptr);
+ err+= add_string(fptr, "\n");
+ err+= add_space(fptr);
+ err+= add_space(fptr);
+ }
+ else
+ err+= add_end_parenthesis(fptr);
+ } while (++j < num_subparts);
+ }
+ }
+ if (i == (tot_num_parts-1))
+ err+= add_end_parenthesis(fptr);
+ } while (++i < tot_num_parts);
+ }
+ if (err)
+ goto close_file;
+ buffer_length= mysql_file_seek(fptr, 0L, MY_SEEK_END, MYF(0));
+ if (unlikely(buffer_length == MY_FILEPOS_ERROR))
+ goto close_file;
+ if (unlikely(mysql_file_seek(fptr, 0L, MY_SEEK_SET, MYF(0))
+ == MY_FILEPOS_ERROR))
+ goto close_file;
+ *buf_length= (uint)buffer_length;
+ if (use_sql_alloc)
+ buf= (char*) sql_alloc(*buf_length+1);
+ else
+ buf= (char*) my_malloc(*buf_length+1, MYF(MY_WME));
+ if (!buf)
+ goto close_file;
+
+ if (unlikely(mysql_file_read(fptr, (uchar*)buf, *buf_length, MYF(MY_FNABP))))
+ {
+ if (!use_sql_alloc)
+ my_free(buf);
+ else
+ buf= NULL;
+ }
+ else
+ buf[*buf_length]= 0;
+
+close_file:
+ mysql_file_close(fptr, MYF(0));
+ DBUG_RETURN(buf);
+}
+
+
+/*
+ Check if partition key fields are modified and if it can be handled by the
+ underlying storage engine.
+
+ SYNOPSIS
+ partition_key_modified
+ table TABLE object for which partition fields are set-up
+ fields Bitmap representing fields to be modified
+
+ RETURN VALUES
+ TRUE Need special handling of UPDATE
+ FALSE Normal UPDATE handling is ok
+*/
+
+bool partition_key_modified(TABLE *table, const MY_BITMAP *fields)
+{
+ Field **fld;
+ partition_info *part_info= table->part_info;
+ DBUG_ENTER("partition_key_modified");
+
+ if (!part_info)
+ DBUG_RETURN(FALSE);
+ if (table->s->db_type()->partition_flags &&
+ (table->s->db_type()->partition_flags() & HA_CAN_UPDATE_PARTITION_KEY))
+ DBUG_RETURN(FALSE);
+ for (fld= part_info->full_part_field_array; *fld; fld++)
+ if (bitmap_is_set(fields, (*fld)->field_index))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ A function to handle correct handling of NULL values in partition
+ functions.
+ SYNOPSIS
+ part_val_int()
+ item_expr The item expression to evaluate
+ out:result The value of the partition function,
+ LONGLONG_MIN if any null value in function
+ RETURN VALUES
+ TRUE Error in val_int()
+ FALSE ok
+*/
+
+static inline int part_val_int(Item *item_expr, longlong *result)
+{
+ *result= item_expr->val_int();
+ if (item_expr->null_value)
+ {
+ if (current_thd->is_error())
+ return TRUE;
+ else
+ *result= LONGLONG_MIN;
+ }
+ return FALSE;
+}
+
+
+/*
+ The next set of functions are used to calculate the partition identity.
+ A handler sets up a variable that corresponds to one of these functions
+ to be able to quickly call it whenever the partition id needs to calculated
+ based on the record in table->record[0] (or set up to fake that).
+ There are 4 functions for hash partitioning and 2 for RANGE/LIST partitions.
+ In addition there are 4 variants for RANGE subpartitioning and 4 variants
+ for LIST subpartitioning thus in total there are 14 variants of this
+ function.
+
+ We have a set of support functions for these 14 variants. There are 4
+ variants of hash functions and there is a function for each. The KEY
+ partitioning uses the function calculate_key_hash_value to calculate the hash
+ value based on an array of fields. The linear hash variants uses the
+ method get_part_id_from_linear_hash to get the partition id using the
+ hash value and some parameters calculated from the number of partitions.
+*/
+
+/*
+ A simple support function to calculate part_id given local part and
+ sub part.
+
+ SYNOPSIS
+ get_part_id_for_sub()
+ loc_part_id Local partition id
+ sub_part_id Subpartition id
+ num_subparts Number of subparts
+*/
+
+inline
+static uint32 get_part_id_for_sub(uint32 loc_part_id, uint32 sub_part_id,
+ uint num_subparts)
+{
+ return (uint32)((loc_part_id * num_subparts) + sub_part_id);
+}
+
+
+/*
+ Calculate part_id for (SUB)PARTITION BY HASH
+
+ SYNOPSIS
+ get_part_id_hash()
+ num_parts Number of hash partitions
+ part_expr Item tree of hash function
+ out:part_id The returned partition id
+ out:func_value Value of hash function
+
+ RETURN VALUE
+ != 0 Error code
+ FALSE Success
+*/
+
+static int get_part_id_hash(uint num_parts,
+ Item *part_expr,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ longlong int_hash_id;
+ DBUG_ENTER("get_part_id_hash");
+
+ if (part_val_int(part_expr, func_value))
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+
+ int_hash_id= *func_value % num_parts;
+
+ *part_id= int_hash_id < 0 ? (uint32) -int_hash_id : (uint32) int_hash_id;
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Calculate part_id for (SUB)PARTITION BY LINEAR HASH
+
+ SYNOPSIS
+ get_part_id_linear_hash()
+ part_info A reference to the partition_info struct where all the
+ desired information is given
+ num_parts Number of hash partitions
+ part_expr Item tree of hash function
+ out:part_id The returned partition id
+ out:func_value Value of hash function
+
+ RETURN VALUE
+ != 0 Error code
+ 0 OK
+*/
+
+static int get_part_id_linear_hash(partition_info *part_info,
+ uint num_parts,
+ Item *part_expr,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ DBUG_ENTER("get_part_id_linear_hash");
+
+ if (part_val_int(part_expr, func_value))
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+
+ *part_id= get_part_id_from_linear_hash(*func_value,
+ part_info->linear_hash_mask,
+ num_parts);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Calculate part_id for (SUB)PARTITION BY KEY
+
+ @param file Handler to storage engine
+ @param field_array Array of fields for PARTTION KEY
+ @param num_parts Number of KEY partitions
+ @param func_value[out] Returns calculated hash value
+
+ @return Calculated partition id
+*/
+
+inline
+static uint32 get_part_id_key(handler *file,
+ Field **field_array,
+ uint num_parts,
+ longlong *func_value)
+{
+ DBUG_ENTER("get_part_id_key");
+ *func_value= ha_partition::calculate_key_hash_value(field_array);
+ DBUG_RETURN((uint32) (*func_value % num_parts));
+}
+
+
+/*
+ Calculate part_id for (SUB)PARTITION BY LINEAR KEY
+
+ SYNOPSIS
+ get_part_id_linear_key()
+ part_info A reference to the partition_info struct where all the
+ desired information is given
+ field_array Array of fields for PARTTION KEY
+ num_parts Number of KEY partitions
+
+ RETURN VALUE
+ Calculated partition id
+*/
+
+inline
+static uint32 get_part_id_linear_key(partition_info *part_info,
+ Field **field_array,
+ uint num_parts,
+ longlong *func_value)
+{
+ DBUG_ENTER("get_part_id_linear_key");
+
+ *func_value= ha_partition::calculate_key_hash_value(field_array);
+ DBUG_RETURN(get_part_id_from_linear_hash(*func_value,
+ part_info->linear_hash_mask,
+ num_parts));
+}
+
+/*
+ Copy to field buffers and set up field pointers
+
+ SYNOPSIS
+ copy_to_part_field_buffers()
+ ptr Array of fields to copy
+ field_bufs Array of field buffers to copy to
+ restore_ptr Array of pointers to restore to
+
+ RETURN VALUES
+ NONE
+ DESCRIPTION
+ This routine is used to take the data from field pointer, convert
+ it to a standard format and store this format in a field buffer
+ allocated for this purpose. Next the field pointers are moved to
+ point to the field buffers. There is a separate to restore the
+ field pointers after this call.
+*/
+
+static void copy_to_part_field_buffers(Field **ptr,
+ uchar **field_bufs,
+ uchar **restore_ptr)
+{
+ Field *field;
+ while ((field= *(ptr++)))
+ {
+ *restore_ptr= field->ptr;
+ restore_ptr++;
+ if (!field->maybe_null() || !field->is_null())
+ {
+ CHARSET_INFO *cs= field->charset();
+ uint max_len= field->pack_length();
+ uint data_len= field->data_length();
+ uchar *field_buf= *field_bufs;
+ /*
+ We only use the field buffer for VARCHAR and CHAR strings
+ which isn't of a binary collation. We also only use the
+ field buffer for fields which are not currently NULL.
+ The field buffer will store a normalised string. We use
+ the strnxfrm method to normalise the string.
+ */
+ if (field->type() == MYSQL_TYPE_VARCHAR)
+ {
+ uint len_bytes= ((Field_varstring*)field)->length_bytes;
+ my_strnxfrm(cs, field_buf + len_bytes, max_len,
+ field->ptr + len_bytes, data_len);
+ if (len_bytes == 1)
+ *field_buf= (uchar) data_len;
+ else
+ int2store(field_buf, data_len);
+ }
+ else
+ {
+ my_strnxfrm(cs, field_buf, max_len,
+ field->ptr, max_len);
+ }
+ field->ptr= field_buf;
+ }
+ field_bufs++;
+ }
+ return;
+}
+
+/*
+ Restore field pointers
+ SYNOPSIS
+ restore_part_field_pointers()
+ ptr Array of fields to restore
+ restore_ptr Array of field pointers to restore to
+
+ RETURN VALUES
+*/
+
+static void restore_part_field_pointers(Field **ptr, uchar **restore_ptr)
+{
+ Field *field;
+ while ((field= *(ptr++)))
+ {
+ field->ptr= *restore_ptr;
+ restore_ptr++;
+ }
+ return;
+}
+
+/*
+ This function is used to calculate the partition id where all partition
+ fields have been prepared to point to a record where the partition field
+ values are bound.
+
+ SYNOPSIS
+ get_partition_id()
+ part_info A reference to the partition_info struct where all the
+ desired information is given
+ out:part_id The partition id is returned through this pointer
+ out:func_value Value of partition function (longlong)
+
+ RETURN VALUE
+ part_id Partition id of partition that would contain
+ row with given values of PF-fields
+ HA_ERR_NO_PARTITION_FOUND The fields of the partition function didn't
+ fit into any partition and thus the values of
+ the PF-fields are not allowed.
+
+ DESCRIPTION
+ A routine used from write_row, update_row and delete_row from any
+ handler supporting partitioning. It is also a support routine for
+ get_partition_set used to find the set of partitions needed to scan
+ for a certain index scan or full table scan.
+
+ It is actually 9 different variants of this function which are called
+ through a function pointer.
+
+ get_partition_id_list
+ get_partition_id_list_col
+ get_partition_id_range
+ get_partition_id_range_col
+ get_partition_id_hash_nosub
+ get_partition_id_key_nosub
+ get_partition_id_linear_hash_nosub
+ get_partition_id_linear_key_nosub
+ get_partition_id_with_sub
+*/
+
+/*
+ This function is used to calculate the main partition to use in the case of
+ subpartitioning and we don't know enough to get the partition identity in
+ total.
+
+ SYNOPSIS
+ get_part_partition_id()
+ part_info A reference to the partition_info struct where all the
+ desired information is given
+ out:part_id The partition id is returned through this pointer
+ out:func_value The value calculated by partition function
+
+ RETURN VALUE
+ HA_ERR_NO_PARTITION_FOUND The fields of the partition function didn't
+ fit into any partition and thus the values of
+ the PF-fields are not allowed.
+ 0 OK
+
+ DESCRIPTION
+
+ It is actually 8 different variants of this function which are called
+ through a function pointer.
+
+ get_partition_id_list
+ get_partition_id_list_col
+ get_partition_id_range
+ get_partition_id_range_col
+ get_partition_id_hash_nosub
+ get_partition_id_key_nosub
+ get_partition_id_linear_hash_nosub
+ get_partition_id_linear_key_nosub
+*/
+
+static int get_part_id_charset_func_part(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ int res;
+ DBUG_ENTER("get_part_id_charset_func_part");
+
+ copy_to_part_field_buffers(part_info->part_charset_field_array,
+ part_info->part_field_buffers,
+ part_info->restore_part_field_ptrs);
+ res= part_info->get_part_partition_id_charset(part_info,
+ part_id, func_value);
+ restore_part_field_pointers(part_info->part_charset_field_array,
+ part_info->restore_part_field_ptrs);
+ DBUG_RETURN(res);
+}
+
+
+static int get_part_id_charset_func_subpart(partition_info *part_info,
+ uint32 *part_id)
+{
+ int res;
+ DBUG_ENTER("get_part_id_charset_func_subpart");
+
+ copy_to_part_field_buffers(part_info->subpart_charset_field_array,
+ part_info->subpart_field_buffers,
+ part_info->restore_subpart_field_ptrs);
+ res= part_info->get_subpartition_id_charset(part_info, part_id);
+ restore_part_field_pointers(part_info->subpart_charset_field_array,
+ part_info->restore_subpart_field_ptrs);
+ DBUG_RETURN(res);
+}
+
+int get_partition_id_list_col(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ part_column_list_val *list_col_array= part_info->list_col_array;
+ uint num_columns= part_info->part_field_list.elements;
+ int list_index, cmp;
+ int min_list_index= 0;
+ int max_list_index= part_info->num_list_values - 1;
+ DBUG_ENTER("get_partition_id_list_col");
+
+ while (max_list_index >= min_list_index)
+ {
+ list_index= (max_list_index + min_list_index) >> 1;
+ cmp= cmp_rec_and_tuple(list_col_array + list_index*num_columns,
+ num_columns);
+ if (cmp > 0)
+ min_list_index= list_index + 1;
+ else if (cmp < 0)
+ {
+ if (!list_index)
+ goto notfound;
+ max_list_index= list_index - 1;
+ }
+ else
+ {
+ *part_id= (uint32)list_col_array[list_index*num_columns].partition_id;
+ DBUG_RETURN(0);
+ }
+ }
+notfound:
+ *part_id= 0;
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+}
+
+
+int get_partition_id_list(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ LIST_PART_ENTRY *list_array= part_info->list_array;
+ int list_index;
+ int min_list_index= 0;
+ int max_list_index= part_info->num_list_values - 1;
+ longlong part_func_value;
+ int error= part_val_int(part_info->part_expr, &part_func_value);
+ longlong list_value;
+ bool unsigned_flag= part_info->part_expr->unsigned_flag;
+ DBUG_ENTER("get_partition_id_list");
+
+ if (error)
+ goto notfound;
+
+ if (part_info->part_expr->null_value)
+ {
+ if (part_info->has_null_value)
+ {
+ *part_id= part_info->has_null_part_id;
+ DBUG_RETURN(0);
+ }
+ goto notfound;
+ }
+ *func_value= part_func_value;
+ if (unsigned_flag)
+ part_func_value-= 0x8000000000000000ULL;
+ while (max_list_index >= min_list_index)
+ {
+ list_index= (max_list_index + min_list_index) >> 1;
+ list_value= list_array[list_index].list_value;
+ if (list_value < part_func_value)
+ min_list_index= list_index + 1;
+ else if (list_value > part_func_value)
+ {
+ if (!list_index)
+ goto notfound;
+ max_list_index= list_index - 1;
+ }
+ else
+ {
+ *part_id= (uint32)list_array[list_index].partition_id;
+ DBUG_RETURN(0);
+ }
+ }
+notfound:
+ *part_id= 0;
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+}
+
+
+uint32 get_partition_id_cols_list_for_endpoint(partition_info *part_info,
+ bool left_endpoint,
+ bool include_endpoint,
+ uint32 nparts)
+{
+ part_column_list_val *list_col_array= part_info->list_col_array;
+ uint num_columns= part_info->part_field_list.elements;
+ uint list_index;
+ uint min_list_index= 0;
+ int cmp;
+ /* Notice that max_list_index = last_index + 1 here! */
+ uint max_list_index= part_info->num_list_values;
+ DBUG_ENTER("get_partition_id_cols_list_for_endpoint");
+
+ /* Find the matching partition (including taking endpoint into account). */
+ do
+ {
+ /* Midpoint, adjusted down, so it can never be >= max_list_index. */
+ list_index= (max_list_index + min_list_index) >> 1;
+ cmp= cmp_rec_and_tuple_prune(list_col_array + list_index*num_columns,
+ nparts, left_endpoint, include_endpoint);
+ if (cmp > 0)
+ {
+ min_list_index= list_index + 1;
+ }
+ else
+ {
+ max_list_index= list_index;
+ if (cmp == 0)
+ break;
+ }
+ } while (max_list_index > min_list_index);
+ list_index= max_list_index;
+
+ /* Given value must be LESS THAN or EQUAL to the found partition. */
+ DBUG_ASSERT(list_index == part_info->num_list_values ||
+ (0 >= cmp_rec_and_tuple_prune(list_col_array +
+ list_index*num_columns,
+ nparts, left_endpoint,
+ include_endpoint)));
+ /* Given value must be GREATER THAN the previous partition. */
+ DBUG_ASSERT(list_index == 0 ||
+ (0 < cmp_rec_and_tuple_prune(list_col_array +
+ (list_index - 1)*num_columns,
+ nparts, left_endpoint,
+ include_endpoint)));
+
+ /* Include the right endpoint if not already passed end of array. */
+ if (!left_endpoint && include_endpoint && cmp == 0 &&
+ list_index < part_info->num_list_values)
+ list_index++;
+
+ DBUG_RETURN(list_index);
+}
+
+
+/**
+ Find the sub-array part_info->list_array that corresponds to given interval.
+
+ @param part_info Partitioning info (partitioning type must be LIST)
+ @param left_endpoint TRUE - the interval is [a; +inf) or (a; +inf)
+ FALSE - the interval is (-inf; a] or (-inf; a)
+ @param include_endpoint TRUE iff the interval includes the endpoint
+
+ This function finds the sub-array of part_info->list_array where values of
+ list_array[idx].list_value are contained within the specifed interval.
+ list_array is ordered by list_value, so
+ 1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the
+ sought sub-array starts at some index idx and continues till array end.
+ The function returns first number idx, such that
+ list_array[idx].list_value is contained within the passed interval.
+
+ 2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the
+ sought sub-array starts at array start and continues till some last
+ index idx.
+ The function returns first number idx, such that
+ list_array[idx].list_value is NOT contained within the passed interval.
+ If all array elements are contained, part_info->num_list_values is
+ returned.
+
+ @note The caller will call this function and then will run along the
+ sub-array of list_array to collect partition ids. If the number of list
+ values is significantly higher then number of partitions, this could be slow
+ and we could invent some other approach. The "run over list array" part is
+ already wrapped in a get_next()-like function.
+
+ @return The index of corresponding sub-array of part_info->list_array.
+*/
+
+uint32 get_list_array_idx_for_endpoint_charset(partition_info *part_info,
+ bool left_endpoint,
+ bool include_endpoint)
+{
+ uint32 res;
+ copy_to_part_field_buffers(part_info->part_field_array,
+ part_info->part_field_buffers,
+ part_info->restore_part_field_ptrs);
+ res= get_list_array_idx_for_endpoint(part_info, left_endpoint,
+ include_endpoint);
+ restore_part_field_pointers(part_info->part_field_array,
+ part_info->restore_part_field_ptrs);
+ return res;
+}
+
+uint32 get_list_array_idx_for_endpoint(partition_info *part_info,
+ bool left_endpoint,
+ bool include_endpoint)
+{
+ LIST_PART_ENTRY *list_array= part_info->list_array;
+ uint list_index;
+ uint min_list_index= 0, max_list_index= part_info->num_list_values - 1;
+ longlong list_value;
+ /* Get the partitioning function value for the endpoint */
+ longlong part_func_value=
+ part_info->part_expr->val_int_endpoint(left_endpoint, &include_endpoint);
+ bool unsigned_flag= part_info->part_expr->unsigned_flag;
+ DBUG_ENTER("get_list_array_idx_for_endpoint");
+
+ if (part_info->part_expr->null_value)
+ {
+ /*
+ Special handling for MONOTONIC functions that can return NULL for
+ values that are comparable. I.e.
+ '2000-00-00' can be compared to '2000-01-01' but TO_DAYS('2000-00-00')
+ returns NULL which cannot be compared used <, >, <=, >= etc.
+
+ Otherwise, just return the the first index (lowest value).
+ */
+ enum_monotonicity_info monotonic;
+ monotonic= part_info->part_expr->get_monotonicity_info();
+ if (monotonic != MONOTONIC_INCREASING_NOT_NULL &&
+ monotonic != MONOTONIC_STRICT_INCREASING_NOT_NULL)
+ {
+ /* F(col) can not return NULL, return index with lowest value */
+ DBUG_RETURN(0);
+ }
+ }
+
+ if (unsigned_flag)
+ part_func_value-= 0x8000000000000000ULL;
+ DBUG_ASSERT(part_info->num_list_values);
+ do
+ {
+ list_index= (max_list_index + min_list_index) >> 1;
+ list_value= list_array[list_index].list_value;
+ if (list_value < part_func_value)
+ min_list_index= list_index + 1;
+ else if (list_value > part_func_value)
+ {
+ if (!list_index)
+ goto notfound;
+ max_list_index= list_index - 1;
+ }
+ else
+ {
+ DBUG_RETURN(list_index + MY_TEST(left_endpoint ^ include_endpoint));
+ }
+ } while (max_list_index >= min_list_index);
+notfound:
+ if (list_value < part_func_value)
+ list_index++;
+ DBUG_RETURN(list_index);
+}
+
+
+int get_partition_id_range_col(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ part_column_list_val *range_col_array= part_info->range_col_array;
+ uint num_columns= part_info->part_field_list.elements;
+ uint max_partition= part_info->num_parts - 1;
+ uint min_part_id= 0;
+ uint max_part_id= max_partition;
+ uint loc_part_id;
+ DBUG_ENTER("get_partition_id_range_col");
+
+ while (max_part_id > min_part_id)
+ {
+ loc_part_id= (max_part_id + min_part_id + 1) >> 1;
+ if (cmp_rec_and_tuple(range_col_array + loc_part_id*num_columns,
+ num_columns) >= 0)
+ min_part_id= loc_part_id + 1;
+ else
+ max_part_id= loc_part_id - 1;
+ }
+ loc_part_id= max_part_id;
+ if (loc_part_id != max_partition)
+ if (cmp_rec_and_tuple(range_col_array + loc_part_id*num_columns,
+ num_columns) >= 0)
+ loc_part_id++;
+ *part_id= (uint32)loc_part_id;
+ if (loc_part_id == max_partition &&
+ (cmp_rec_and_tuple(range_col_array + loc_part_id*num_columns,
+ num_columns) >= 0))
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+
+ DBUG_PRINT("exit",("partition: %d", *part_id));
+ DBUG_RETURN(0);
+}
+
+
+int get_partition_id_range(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ longlong *range_array= part_info->range_int_array;
+ uint max_partition= part_info->num_parts - 1;
+ uint min_part_id= 0;
+ uint max_part_id= max_partition;
+ uint loc_part_id;
+ longlong part_func_value;
+ int error= part_val_int(part_info->part_expr, &part_func_value);
+ bool unsigned_flag= part_info->part_expr->unsigned_flag;
+ DBUG_ENTER("get_partition_id_range");
+
+ if (error)
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+
+ if (part_info->part_expr->null_value)
+ {
+ *part_id= 0;
+ DBUG_RETURN(0);
+ }
+ *func_value= part_func_value;
+ if (unsigned_flag)
+ part_func_value-= 0x8000000000000000ULL;
+ /* Search for the partition containing part_func_value */
+ while (max_part_id > min_part_id)
+ {
+ loc_part_id= (max_part_id + min_part_id) / 2;
+ if (range_array[loc_part_id] <= part_func_value)
+ min_part_id= loc_part_id + 1;
+ else
+ max_part_id= loc_part_id;
+ }
+ loc_part_id= max_part_id;
+ *part_id= (uint32)loc_part_id;
+ if (loc_part_id == max_partition &&
+ part_func_value >= range_array[loc_part_id] &&
+ !part_info->defined_max_value)
+ DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
+
+ DBUG_PRINT("exit",("partition: %d", *part_id));
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Find the sub-array of part_info->range_int_array that covers given interval
+
+ SYNOPSIS
+ get_partition_id_range_for_endpoint()
+ part_info Partitioning info (partitioning type must be RANGE)
+ left_endpoint TRUE - the interval is [a; +inf) or (a; +inf)
+ FALSE - the interval is (-inf; a] or (-inf; a).
+ include_endpoint TRUE <=> the endpoint itself is included in the
+ interval
+
+ DESCRIPTION
+ This function finds the sub-array of part_info->range_int_array where the
+ elements have non-empty intersections with the given interval.
+
+ A range_int_array element at index idx represents the interval
+
+ [range_int_array[idx-1], range_int_array[idx]),
+
+ intervals are disjoint and ordered by their right bound, so
+
+ 1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the
+ sought sub-array starts at some index idx and continues till array end.
+ The function returns first number idx, such that the interval
+ represented by range_int_array[idx] has non empty intersection with
+ the passed interval.
+
+ 2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the
+ sought sub-array starts at array start and continues till some last
+ index idx.
+ The function returns first number idx, such that the interval
+ represented by range_int_array[idx] has EMPTY intersection with the
+ passed interval.
+ If the interval represented by the last array element has non-empty
+ intersection with the passed interval, part_info->num_parts is
+ returned.
+
+ RETURN
+ The edge of corresponding part_info->range_int_array sub-array.
+*/
+
+static uint32
+get_partition_id_range_for_endpoint_charset(partition_info *part_info,
+ bool left_endpoint,
+ bool include_endpoint)
+{
+ uint32 res;
+ copy_to_part_field_buffers(part_info->part_field_array,
+ part_info->part_field_buffers,
+ part_info->restore_part_field_ptrs);
+ res= get_partition_id_range_for_endpoint(part_info, left_endpoint,
+ include_endpoint);
+ restore_part_field_pointers(part_info->part_field_array,
+ part_info->restore_part_field_ptrs);
+ return res;
+}
+
+uint32 get_partition_id_range_for_endpoint(partition_info *part_info,
+ bool left_endpoint,
+ bool include_endpoint)
+{
+ longlong *range_array= part_info->range_int_array;
+ longlong part_end_val;
+ uint max_partition= part_info->num_parts - 1;
+ uint min_part_id= 0, max_part_id= max_partition, loc_part_id;
+ /* Get the partitioning function value for the endpoint */
+ longlong part_func_value=
+ part_info->part_expr->val_int_endpoint(left_endpoint, &include_endpoint);
+
+ bool unsigned_flag= part_info->part_expr->unsigned_flag;
+ DBUG_ENTER("get_partition_id_range_for_endpoint");
+
+ if (part_info->part_expr->null_value)
+ {
+ /*
+ Special handling for MONOTONIC functions that can return NULL for
+ values that are comparable. I.e.
+ '2000-00-00' can be compared to '2000-01-01' but TO_DAYS('2000-00-00')
+ returns NULL which cannot be compared used <, >, <=, >= etc.
+
+ Otherwise, just return the first partition
+ (may be included if not left endpoint)
+ */
+ enum_monotonicity_info monotonic;
+ monotonic= part_info->part_expr->get_monotonicity_info();
+ if (monotonic != MONOTONIC_INCREASING_NOT_NULL &&
+ monotonic != MONOTONIC_STRICT_INCREASING_NOT_NULL)
+ {
+ /* F(col) can not return NULL, return partition with lowest value */
+ if (!left_endpoint && include_endpoint)
+ DBUG_RETURN(1);
+ DBUG_RETURN(0);
+
+ }
+ }
+
+ if (unsigned_flag)
+ part_func_value-= 0x8000000000000000ULL;
+ if (left_endpoint && !include_endpoint)
+ part_func_value++;
+
+ /*
+ Search for the partition containing part_func_value
+ (including the right endpoint).
+ */
+ while (max_part_id > min_part_id)
+ {
+ loc_part_id= (max_part_id + min_part_id) / 2;
+ if (range_array[loc_part_id] < part_func_value)
+ min_part_id= loc_part_id + 1;
+ else
+ max_part_id= loc_part_id;
+ }
+ loc_part_id= max_part_id;
+
+ /* Adjust for endpoints */
+ part_end_val= range_array[loc_part_id];
+ if (left_endpoint)
+ {
+ DBUG_ASSERT(part_func_value > part_end_val ?
+ (loc_part_id == max_partition &&
+ !part_info->defined_max_value) :
+ 1);
+ /*
+ In case of PARTITION p VALUES LESS THAN MAXVALUE
+ the maximum value is in the current (last) partition.
+ If value is equal or greater than the endpoint,
+ the range starts from the next partition.
+ */
+ if (part_func_value >= part_end_val &&
+ (loc_part_id < max_partition || !part_info->defined_max_value))
+ loc_part_id++;
+ }
+ else
+ {
+ /* if 'WHERE <= X' and partition is LESS THAN (X) include next partition */
+ if (include_endpoint && loc_part_id < max_partition &&
+ part_func_value == part_end_val)
+ loc_part_id++;
+
+ /* Right endpoint, set end after correct partition */
+ loc_part_id++;
+ }
+ DBUG_RETURN(loc_part_id);
+}
+
+
+int get_partition_id_hash_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ return get_part_id_hash(part_info->num_parts, part_info->part_expr,
+ part_id, func_value);
+}
+
+
+int get_partition_id_linear_hash_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ return get_part_id_linear_hash(part_info, part_info->num_parts,
+ part_info->part_expr, part_id, func_value);
+}
+
+
+int get_partition_id_key_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ *part_id= get_part_id_key(part_info->table->file,
+ part_info->part_field_array,
+ part_info->num_parts, func_value);
+ return 0;
+}
+
+
+int get_partition_id_linear_key_nosub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ *part_id= get_part_id_linear_key(part_info,
+ part_info->part_field_array,
+ part_info->num_parts, func_value);
+ return 0;
+}
+
+
+int get_partition_id_with_sub(partition_info *part_info,
+ uint32 *part_id,
+ longlong *func_value)
+{
+ uint32 loc_part_id, sub_part_id;
+ uint num_subparts;
+ int error;
+ DBUG_ENTER("get_partition_id_with_sub");
+
+ if (unlikely((error= part_info->get_part_partition_id(part_info,
+ &loc_part_id,
+ func_value))))
+ {
+ DBUG_RETURN(error);
+ }
+ num_subparts= part_info->num_subparts;
+ if (unlikely((error= part_info->get_subpartition_id(part_info,
+ &sub_part_id))))
+ {
+ DBUG_RETURN(error);
+ }
+ *part_id= get_part_id_for_sub(loc_part_id, sub_part_id, num_subparts);
+ DBUG_RETURN(0);
+}
+
+
+/*
+ This function is used to calculate the subpartition id
+
+ SYNOPSIS
+ get_subpartition_id()
+ part_info A reference to the partition_info struct where all the
+ desired information is given
+
+ RETURN VALUE
+ part_id The subpartition identity
+
+ DESCRIPTION
+ A routine used in some SELECT's when only partial knowledge of the
+ partitions is known.
+
+ It is actually 4 different variants of this function which are called
+ through a function pointer.
+
+ get_partition_id_hash_sub
+ get_partition_id_key_sub
+ get_partition_id_linear_hash_sub
+ get_partition_id_linear_key_sub
+*/
+
+int get_partition_id_hash_sub(partition_info *part_info,
+ uint32 *part_id)
+{
+ longlong func_value;
+ return get_part_id_hash(part_info->num_subparts, part_info->subpart_expr,
+ part_id, &func_value);
+}
+
+
+int get_partition_id_linear_hash_sub(partition_info *part_info,
+ uint32 *part_id)
+{
+ longlong func_value;
+ return get_part_id_linear_hash(part_info, part_info->num_subparts,
+ part_info->subpart_expr, part_id,
+ &func_value);
+}
+
+
+int get_partition_id_key_sub(partition_info *part_info,
+ uint32 *part_id)
+{
+ longlong func_value;
+ *part_id= get_part_id_key(part_info->table->file,
+ part_info->subpart_field_array,
+ part_info->num_subparts, &func_value);
+ return FALSE;
+}
+
+
+int get_partition_id_linear_key_sub(partition_info *part_info,
+ uint32 *part_id)
+{
+ longlong func_value;
+ *part_id= get_part_id_linear_key(part_info,
+ part_info->subpart_field_array,
+ part_info->num_subparts, &func_value);
+ return FALSE;
+}
+
+
+/*
+ Set an indicator on all partition fields that are set by the key
+
+ SYNOPSIS
+ set_PF_fields_in_key()
+ key_info Information about the index
+ key_length Length of key
+
+ RETURN VALUE
+ TRUE Found partition field set by key
+ FALSE No partition field set by key
+*/
+
+static bool set_PF_fields_in_key(KEY *key_info, uint key_length)
+{
+ KEY_PART_INFO *key_part;
+ bool found_part_field= FALSE;
+ DBUG_ENTER("set_PF_fields_in_key");
+
+ for (key_part= key_info->key_part; (int)key_length > 0; key_part++)
+ {
+ if (key_part->null_bit)
+ key_length--;
+ if (key_part->type == HA_KEYTYPE_BIT)
+ {
+ if (((Field_bit*)key_part->field)->bit_len)
+ key_length--;
+ }
+ if (key_part->key_part_flag & (HA_BLOB_PART + HA_VAR_LENGTH_PART))
+ {
+ key_length-= HA_KEY_BLOB_LENGTH;
+ }
+ if (key_length < key_part->length)
+ break;
+ key_length-= key_part->length;
+ if (key_part->field->flags & FIELD_IN_PART_FUNC_FLAG)
+ {
+ found_part_field= TRUE;
+ key_part->field->flags|= GET_FIXED_FIELDS_FLAG;
+ }
+ }
+ DBUG_RETURN(found_part_field);
+}
+
+
+/*
+ We have found that at least one partition field was set by a key, now
+ check if a partition function has all its fields bound or not.
+
+ SYNOPSIS
+ check_part_func_bound()
+ ptr Array of fields NULL terminated (partition fields)
+
+ RETURN VALUE
+ TRUE All fields in partition function are set
+ FALSE Not all fields in partition function are set
+*/
+
+static bool check_part_func_bound(Field **ptr)
+{
+ bool result= TRUE;
+ DBUG_ENTER("check_part_func_bound");
+
+ for (; *ptr; ptr++)
+ {
+ if (!((*ptr)->flags & GET_FIXED_FIELDS_FLAG))
+ {
+ result= FALSE;
+ break;
+ }
+ }
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Get the id of the subpartitioning part by using the key buffer of the
+ index scan.
+
+ SYNOPSIS
+ get_sub_part_id_from_key()
+ table The table object
+ buf A buffer that can be used to evaluate the partition function
+ key_info The index object
+ key_spec A key_range containing key and key length
+ out:part_id The returned partition id
+
+ RETURN VALUES
+ TRUE All fields in partition function are set
+ FALSE Not all fields in partition function are set
+
+ DESCRIPTION
+ Use key buffer to set-up record in buf, move field pointers and
+ get the partition identity and restore field pointers afterwards.
+*/
+
+static int get_sub_part_id_from_key(const TABLE *table,uchar *buf,
+ KEY *key_info,
+ const key_range *key_spec,
+ uint32 *part_id)
+{
+ uchar *rec0= table->record[0];
+ partition_info *part_info= table->part_info;
+ int res;
+ DBUG_ENTER("get_sub_part_id_from_key");
+
+ key_restore(buf, (uchar*)key_spec->key, key_info, key_spec->length);
+ if (likely(rec0 == buf))
+ {
+ res= part_info->get_subpartition_id(part_info, part_id);
+ }
+ else
+ {
+ Field **part_field_array= part_info->subpart_field_array;
+ set_field_ptr(part_field_array, buf, rec0);
+ res= part_info->get_subpartition_id(part_info, part_id);
+ set_field_ptr(part_field_array, rec0, buf);
+ }
+ DBUG_RETURN(res);
+}
+
+/*
+ Get the id of the partitioning part by using the key buffer of the
+ index scan.
+
+ SYNOPSIS
+ get_part_id_from_key()
+ table The table object
+ buf A buffer that can be used to evaluate the partition function
+ key_info The index object
+ key_spec A key_range containing key and key length
+ out:part_id Partition to use
+
+ RETURN VALUES
+ TRUE Partition to use not found
+ FALSE Ok, part_id indicates partition to use
+
+ DESCRIPTION
+ Use key buffer to set-up record in buf, move field pointers and
+ get the partition identity and restore field pointers afterwards.
+*/
+
+bool get_part_id_from_key(const TABLE *table, uchar *buf, KEY *key_info,
+ const key_range *key_spec, uint32 *part_id)
+{
+ bool result;
+ uchar *rec0= table->record[0];
+ partition_info *part_info= table->part_info;
+ longlong func_value;
+ DBUG_ENTER("get_part_id_from_key");
+
+ key_restore(buf, (uchar*)key_spec->key, key_info, key_spec->length);
+ if (likely(rec0 == buf))
+ {
+ result= part_info->get_part_partition_id(part_info, part_id,
+ &func_value);
+ }
+ else
+ {
+ Field **part_field_array= part_info->part_field_array;
+ set_field_ptr(part_field_array, buf, rec0);
+ result= part_info->get_part_partition_id(part_info, part_id,
+ &func_value);
+ set_field_ptr(part_field_array, rec0, buf);
+ }
+ DBUG_RETURN(result);
+}
+
+/*
+ Get the partitioning id of the full PF by using the key buffer of the
+ index scan.
+
+ SYNOPSIS
+ get_full_part_id_from_key()
+ table The table object
+ buf A buffer that is used to evaluate the partition function
+ key_info The index object
+ key_spec A key_range containing key and key length
+ out:part_spec A partition id containing start part and end part
+
+ RETURN VALUES
+ part_spec
+ No partitions to scan is indicated by end_part > start_part when returning
+
+ DESCRIPTION
+ Use key buffer to set-up record in buf, move field pointers if needed and
+ get the partition identity and restore field pointers afterwards.
+*/
+
+void get_full_part_id_from_key(const TABLE *table, uchar *buf,
+ KEY *key_info,
+ const key_range *key_spec,
+ part_id_range *part_spec)
+{
+ bool result;
+ partition_info *part_info= table->part_info;
+ uchar *rec0= table->record[0];
+ longlong func_value;
+ DBUG_ENTER("get_full_part_id_from_key");
+
+ key_restore(buf, (uchar*)key_spec->key, key_info, key_spec->length);
+ if (likely(rec0 == buf))
+ {
+ result= part_info->get_partition_id(part_info, &part_spec->start_part,
+ &func_value);
+ }
+ else
+ {
+ Field **part_field_array= part_info->full_part_field_array;
+ set_field_ptr(part_field_array, buf, rec0);
+ result= part_info->get_partition_id(part_info, &part_spec->start_part,
+ &func_value);
+ set_field_ptr(part_field_array, rec0, buf);
+ }
+ part_spec->end_part= part_spec->start_part;
+ if (unlikely(result))
+ part_spec->start_part++;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @brief Verify that all rows in a table is in the given partition
+
+ @param table Table which contains the data that will be checked if
+ it is matching the partition definition.
+ @param part_table Partitioned table containing the partition to check.
+ @param part_id Which partition to match with.
+
+ @return Operation status
+ @retval TRUE Not all rows match the given partition
+ @retval FALSE OK
+*/
+bool verify_data_with_partition(TABLE *table, TABLE *part_table,
+ uint32 part_id)
+{
+ uint32 found_part_id;
+ longlong func_value; /* Unused */
+ handler *file;
+ int error;
+ uchar *old_rec;
+ partition_info *part_info;
+ DBUG_ENTER("verify_data_with_partition");
+ DBUG_ASSERT(table && table->file && part_table && part_table->part_info &&
+ part_table->file);
+
+ /*
+ Verify all table rows.
+ First implementation uses full scan + evaluates partition functions for
+ every row. TODO: add optimization to use index if possible, see WL#5397.
+
+ 1) Open both tables (already done) and set the row buffers to use
+ the same buffer (to avoid copy).
+ 2) Init rnd on table.
+ 3) loop over all rows.
+ 3.1) verify that partition_id on the row is correct. Break if error.
+ */
+ file= table->file;
+ part_info= part_table->part_info;
+ bitmap_union(table->read_set, &part_info->full_part_field_set);
+ old_rec= part_table->record[0];
+ part_table->record[0]= table->record[0];
+ set_field_ptr(part_info->full_part_field_array, table->record[0], old_rec);
+ if ((error= file->ha_rnd_init(TRUE)))
+ {
+ file->print_error(error, MYF(0));
+ goto err;
+ }
+
+ do
+ {
+ if ((error= file->ha_rnd_next(table->record[0])))
+ {
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ if (error == HA_ERR_END_OF_FILE)
+ error= 0;
+ else
+ file->print_error(error, MYF(0));
+ break;
+ }
+ if ((error= part_info->get_partition_id(part_info, &found_part_id,
+ &func_value)))
+ {
+ part_table->file->print_error(error, MYF(0));
+ break;
+ }
+ DEBUG_SYNC(current_thd, "swap_partition_first_row_read");
+ if (found_part_id != part_id)
+ {
+ my_error(ER_ROW_DOES_NOT_MATCH_PARTITION, MYF(0));
+ error= 1;
+ break;
+ }
+ } while (TRUE);
+ (void) file->ha_rnd_end();
+err:
+ set_field_ptr(part_info->full_part_field_array, old_rec,
+ table->record[0]);
+ part_table->record[0]= old_rec;
+ if (error)
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Prune the set of partitions to use in query
+
+ SYNOPSIS
+ prune_partition_set()
+ table The table object
+ out:part_spec Contains start part, end part
+
+ DESCRIPTION
+ This function is called to prune the range of partitions to scan by
+ checking the read_partitions bitmap.
+ If start_part > end_part at return it means no partition needs to be
+ scanned. If start_part == end_part it always means a single partition
+ needs to be scanned.
+
+ RETURN VALUE
+ part_spec
+*/
+void prune_partition_set(const TABLE *table, part_id_range *part_spec)
+{
+ int last_partition= -1;
+ uint i;
+ partition_info *part_info= table->part_info;
+
+ DBUG_ENTER("prune_partition_set");
+ for (i= part_spec->start_part; i <= part_spec->end_part; i++)
+ {
+ if (bitmap_is_set(&(part_info->read_partitions), i))
+ {
+ DBUG_PRINT("info", ("Partition %d is set", i));
+ if (last_partition == -1)
+ /* First partition found in set and pruned bitmap */
+ part_spec->start_part= i;
+ last_partition= i;
+ }
+ }
+ if (last_partition == -1)
+ /* No partition found in pruned bitmap */
+ part_spec->start_part= part_spec->end_part + 1;
+ else //if (last_partition != -1)
+ part_spec->end_part= last_partition;
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Get the set of partitions to use in query.
+
+ SYNOPSIS
+ get_partition_set()
+ table The table object
+ buf A buffer that can be used to evaluate the partition function
+ index The index of the key used, if MAX_KEY no index used
+ key_spec A key_range containing key and key length
+ out:part_spec Contains start part, end part and indicator if bitmap is
+ used for which partitions to scan
+
+ DESCRIPTION
+ This function is called to discover which partitions to use in an index
+ scan or a full table scan.
+ It returns a range of partitions to scan. If there are holes in this
+ range with partitions that are not needed to scan a bit array is used
+ to signal which partitions to use and which not to use.
+ If start_part > end_part at return it means no partition needs to be
+ scanned. If start_part == end_part it always means a single partition
+ needs to be scanned.
+
+ RETURN VALUE
+ part_spec
+*/
+void get_partition_set(const TABLE *table, uchar *buf, const uint index,
+ const key_range *key_spec, part_id_range *part_spec)
+{
+ partition_info *part_info= table->part_info;
+ uint num_parts= part_info->get_tot_partitions();
+ uint i, part_id;
+ uint sub_part= num_parts;
+ uint32 part_part= num_parts;
+ KEY *key_info= NULL;
+ bool found_part_field= FALSE;
+ DBUG_ENTER("get_partition_set");
+
+ part_spec->start_part= 0;
+ part_spec->end_part= num_parts - 1;
+ if ((index < MAX_KEY) &&
+ key_spec && key_spec->flag == (uint)HA_READ_KEY_EXACT &&
+ part_info->some_fields_in_PF.is_set(index))
+ {
+ key_info= table->key_info+index;
+ /*
+ The index can potentially provide at least one PF-field (field in the
+ partition function). Thus it is interesting to continue our probe.
+ */
+ if (key_spec->length == key_info->key_length)
+ {
+ /*
+ The entire key is set so we can check whether we can immediately
+ derive either the complete PF or if we can derive either
+ the top PF or the subpartitioning PF. This can be established by
+ checking precalculated bits on each index.
+ */
+ if (part_info->all_fields_in_PF.is_set(index))
+ {
+ /*
+ We can derive the exact partition to use, no more than this one
+ is needed.
+ */
+ get_full_part_id_from_key(table,buf,key_info,key_spec,part_spec);
+ /*
+ Check if range can be adjusted by looking in read_partitions
+ */
+ prune_partition_set(table, part_spec);
+ DBUG_VOID_RETURN;
+ }
+ else if (part_info->is_sub_partitioned())
+ {
+ if (part_info->all_fields_in_SPF.is_set(index))
+ {
+ if (get_sub_part_id_from_key(table, buf, key_info, key_spec, &sub_part))
+ {
+ part_spec->start_part= num_parts;
+ DBUG_VOID_RETURN;
+ }
+ }
+ else if (part_info->all_fields_in_PPF.is_set(index))
+ {
+ if (get_part_id_from_key(table,buf,key_info,
+ key_spec,(uint32*)&part_part))
+ {
+ /*
+ The value of the RANGE or LIST partitioning was outside of
+ allowed values. Thus it is certain that the result of this
+ scan will be empty.
+ */
+ part_spec->start_part= num_parts;
+ DBUG_VOID_RETURN;
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ Set an indicator on all partition fields that are bound.
+ If at least one PF-field was bound it pays off to check whether
+ the PF or PPF or SPF has been bound.
+ (PF = Partition Function, SPF = Subpartition Function and
+ PPF = Partition Function part of subpartitioning)
+ */
+ if ((found_part_field= set_PF_fields_in_key(key_info,
+ key_spec->length)))
+ {
+ if (check_part_func_bound(part_info->full_part_field_array))
+ {
+ /*
+ We were able to bind all fields in the partition function even
+ by using only a part of the key. Calculate the partition to use.
+ */
+ get_full_part_id_from_key(table,buf,key_info,key_spec,part_spec);
+ clear_indicator_in_key_fields(key_info);
+ /*
+ Check if range can be adjusted by looking in read_partitions
+ */
+ prune_partition_set(table, part_spec);
+ DBUG_VOID_RETURN;
+ }
+ else if (part_info->is_sub_partitioned())
+ {
+ if (check_part_func_bound(part_info->subpart_field_array))
+ {
+ if (get_sub_part_id_from_key(table, buf, key_info, key_spec, &sub_part))
+ {
+ part_spec->start_part= num_parts;
+ clear_indicator_in_key_fields(key_info);
+ DBUG_VOID_RETURN;
+ }
+ }
+ else if (check_part_func_bound(part_info->part_field_array))
+ {
+ if (get_part_id_from_key(table,buf,key_info,key_spec,&part_part))
+ {
+ part_spec->start_part= num_parts;
+ clear_indicator_in_key_fields(key_info);
+ DBUG_VOID_RETURN;
+ }
+ }
+ }
+ }
+ }
+ }
+ {
+ /*
+ The next step is to analyse the table condition to see whether any
+ information about which partitions to scan can be derived from there.
+ Currently not implemented.
+ */
+ }
+ /*
+ If we come here we have found a range of sorts we have either discovered
+ nothing or we have discovered a range of partitions with possible holes
+ in it. We need a bitvector to further the work here.
+ */
+ if (!(part_part == num_parts && sub_part == num_parts))
+ {
+ /*
+ We can only arrive here if we are using subpartitioning.
+ */
+ if (part_part != num_parts)
+ {
+ /*
+ We know the top partition and need to scan all underlying
+ subpartitions. This is a range without holes.
+ */
+ DBUG_ASSERT(sub_part == num_parts);
+ part_spec->start_part= part_part * part_info->num_subparts;
+ part_spec->end_part= part_spec->start_part+part_info->num_subparts - 1;
+ }
+ else
+ {
+ DBUG_ASSERT(sub_part != num_parts);
+ part_spec->start_part= sub_part;
+ part_spec->end_part=sub_part+
+ (part_info->num_subparts*(part_info->num_parts-1));
+ for (i= 0, part_id= sub_part; i < part_info->num_parts;
+ i++, part_id+= part_info->num_subparts)
+ ; //Set bit part_id in bit array
+ }
+ }
+ if (found_part_field)
+ clear_indicator_in_key_fields(key_info);
+ /*
+ Check if range can be adjusted by looking in read_partitions
+ */
+ prune_partition_set(table, part_spec);
+ DBUG_VOID_RETURN;
+}
+
+/*
+ If the table is partitioned we will read the partition info into the
+ .frm file here.
+ -------------------------------
+ | Fileinfo 64 bytes |
+ -------------------------------
+ | Formnames 7 bytes |
+ -------------------------------
+ | Not used 4021 bytes |
+ -------------------------------
+ | Keyinfo + record |
+ -------------------------------
+ | Padded to next multiple |
+ | of IO_SIZE |
+ -------------------------------
+ | Forminfo 288 bytes |
+ -------------------------------
+ | Screen buffer, to make |
+ |field names readable |
+ -------------------------------
+ | Packed field info |
+ |17 + 1 + strlen(field_name) |
+ | + 1 end of file character |
+ -------------------------------
+ | Partition info |
+ -------------------------------
+ We provide the length of partition length in Fileinfo[55-58].
+
+ Read the partition syntax from the frm file and parse it to get the
+ data structures of the partitioning.
+
+ SYNOPSIS
+ mysql_unpack_partition()
+ thd Thread object
+ part_buf Partition info from frm file
+ part_info_len Length of partition syntax
+ table Table object of partitioned table
+ create_table_ind Is it called from CREATE TABLE
+ default_db_type What is the default engine of the table
+ work_part_info_used Flag is raised if we don't create new
+ part_info, but used thd->work_part_info
+
+ RETURN VALUE
+ TRUE Error
+ FALSE Sucess
+
+ DESCRIPTION
+ Read the partition syntax from the current position in the frm file.
+ Initiate a LEX object, save the list of item tree objects to free after
+ the query is done. Set-up partition info object such that parser knows
+ it is called from internally. Call parser to create data structures
+ (best possible recreation of item trees and so forth since there is no
+ serialisation of these objects other than in parseable text format).
+ We need to save the text of the partition functions since it is not
+ possible to retrace this given an item tree.
+*/
+
+bool mysql_unpack_partition(THD *thd,
+ char *part_buf, uint part_info_len,
+ TABLE* table, bool is_create_table_ind,
+ handlerton *default_db_type,
+ bool *work_part_info_used)
+{
+ bool result= TRUE;
+ partition_info *part_info;
+ const CHARSET_INFO *old_character_set_client=
+ thd->variables.character_set_client;
+ LEX *old_lex= thd->lex;
+ LEX lex;
+ PSI_statement_locker *parent_locker= thd->m_statement_psi;
+ DBUG_ENTER("mysql_unpack_partition");
+
+ thd->variables.character_set_client= system_charset_info;
+
+ Parser_state parser_state;
+ if (parser_state.init(thd, part_buf, part_info_len))
+ goto end;
+
+ if (init_lex_with_single_table(thd, table, &lex))
+ goto end;
+
+ /*
+ All Items created is put into a free list on the THD object. This list
+ is used to free all Item objects after completing a query. We don't
+ want that to happen with the Item tree created as part of the partition
+ info. This should be attached to the table object and remain so until
+ the table object is released.
+ Thus we move away the current list temporarily and start a new list that
+ we then save in the partition info structure.
+ */
+ *work_part_info_used= FALSE;
+ lex.part_info= new partition_info();/* Indicates MYSQLparse from this place */
+ if (!lex.part_info)
+ {
+ mem_alloc_error(sizeof(partition_info));
+ goto end;
+ }
+ part_info= lex.part_info;
+ DBUG_PRINT("info", ("Parse: %s", part_buf));
+
+ thd->m_statement_psi= NULL;
+ if (parse_sql(thd, & parser_state, NULL) ||
+ part_info->fix_parser_data(thd))
+ {
+ thd->free_items();
+ thd->m_statement_psi= parent_locker;
+ goto end;
+ }
+ thd->m_statement_psi= parent_locker;
+ /*
+ The parsed syntax residing in the frm file can still contain defaults.
+ The reason is that the frm file is sometimes saved outside of this
+ MySQL Server and used in backup and restore of clusters or partitioned
+ tables. It is not certain that the restore will restore exactly the
+ same default partitioning.
+
+ The easiest manner of handling this is to simply continue using the
+ part_info we already built up during mysql_create_table if we are
+ in the process of creating a table. If the table already exists we
+ need to discover the number of partitions for the default parts. Since
+ the handler object hasn't been created here yet we need to postpone this
+ to the fix_partition_func method.
+ */
+
+ DBUG_PRINT("info", ("Successful parse"));
+ DBUG_PRINT("info", ("default engine = %s, default_db_type = %s",
+ ha_resolve_storage_engine_name(part_info->default_engine_type),
+ ha_resolve_storage_engine_name(default_db_type)));
+ if (is_create_table_ind && old_lex->sql_command == SQLCOM_CREATE_TABLE)
+ {
+ /*
+ When we come here we are doing a create table. In this case we
+ have already done some preparatory work on the old part_info
+ object. We don't really need this new partition_info object.
+ Thus we go back to the old partition info object.
+ We need to free any memory objects allocated on item_free_list
+ by the parser since we are keeping the old info from the first
+ parser call in CREATE TABLE.
+
+ This table object can not be used any more. However, since
+ this is CREATE TABLE, we know that it will be destroyed by the
+ caller, and rely on that.
+ */
+ thd->free_items();
+ part_info= thd->work_part_info;
+ *work_part_info_used= true;
+ }
+ table->part_info= part_info;
+ part_info->table= table;
+ table->file->set_part_info(part_info);
+ if (!part_info->default_engine_type)
+ part_info->default_engine_type= default_db_type;
+ DBUG_ASSERT(part_info->default_engine_type == default_db_type);
+ DBUG_ASSERT(part_info->default_engine_type->db_type != DB_TYPE_UNKNOWN);
+ DBUG_ASSERT(part_info->default_engine_type != partition_hton);
+
+ {
+ /*
+ This code part allocates memory for the serialised item information for
+ the partition functions. In most cases this is not needed but if the
+ table is used for SHOW CREATE TABLES or ALTER TABLE that modifies
+ partition information it is needed and the info is lost if we don't
+ save it here so unfortunately we have to do it here even if in most
+ cases it is not needed. This is a consequence of that item trees are
+ not serialisable.
+ */
+ uint part_func_len= part_info->part_func_len;
+ uint subpart_func_len= part_info->subpart_func_len;
+ char *part_func_string= NULL;
+ char *subpart_func_string= NULL;
+ if ((part_func_len &&
+ !((part_func_string= (char*) thd->alloc(part_func_len)))) ||
+ (subpart_func_len &&
+ !((subpart_func_string= (char*) thd->alloc(subpart_func_len)))))
+ {
+ mem_alloc_error(part_func_len);
+ thd->free_items();
+ goto end;
+ }
+ if (part_func_len)
+ memcpy(part_func_string, part_info->part_func_string, part_func_len);
+ if (subpart_func_len)
+ memcpy(subpart_func_string, part_info->subpart_func_string,
+ subpart_func_len);
+ part_info->part_func_string= part_func_string;
+ part_info->subpart_func_string= subpart_func_string;
+ }
+
+ result= FALSE;
+end:
+ end_lex_with_single_table(thd, table, old_lex);
+ thd->variables.character_set_client= old_character_set_client;
+ DBUG_RETURN(result);
+}
+
+
+/*
+ Set engine type on all partition element objects
+ SYNOPSIS
+ set_engine_all_partitions()
+ part_info Partition info
+ engine_type Handlerton reference of engine
+ RETURN VALUES
+ NONE
+*/
+
+static
+void
+set_engine_all_partitions(partition_info *part_info,
+ handlerton *engine_type)
+{
+ uint i= 0;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ do
+ {
+ partition_element *part_elem= part_it++;
+
+ part_elem->engine_type= engine_type;
+ if (part_info->is_sub_partitioned())
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ uint j= 0;
+
+ do
+ {
+ partition_element *sub_elem= sub_it++;
+
+ sub_elem->engine_type= engine_type;
+ } while (++j < part_info->num_subparts);
+ }
+ } while (++i < part_info->num_parts);
+}
+
+
+/**
+ Support routine to handle the successful cases for partition management.
+
+ @param thd Thread object
+ @param copied Number of records copied
+ @param deleted Number of records deleted
+ @param table_list Table list with the one table in it
+
+ @return Operation status
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+static int fast_end_partition(THD *thd, ulonglong copied,
+ ulonglong deleted,
+ TABLE_LIST *table_list)
+{
+ char tmp_name[80];
+ DBUG_ENTER("fast_end_partition");
+
+ thd->proc_info="end";
+
+ query_cache_invalidate3(thd, table_list, 0);
+
+ my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
+ (ulong) (copied + deleted),
+ (ulong) deleted,
+ (ulong) 0);
+ my_ok(thd, (ha_rows) (copied+deleted),0L, tmp_name);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ We need to check if engine used by all partitions can handle
+ partitioning natively.
+
+ SYNOPSIS
+ check_native_partitioned()
+ create_info Create info in CREATE TABLE
+ out:ret_val Return value
+ part_info Partition info
+ thd Thread object
+
+ RETURN VALUES
+ Value returned in bool ret_value
+ TRUE Native partitioning supported by engine
+ FALSE Need to use partition handler
+
+ Return value from function
+ TRUE Error
+ FALSE Success
+*/
+
+static bool check_native_partitioned(HA_CREATE_INFO *create_info,bool *ret_val,
+ partition_info *part_info, THD *thd)
+{
+ bool table_engine_set;
+ handlerton *engine_type= part_info->default_engine_type;
+ handlerton *old_engine_type= engine_type;
+ DBUG_ENTER("check_native_partitioned");
+
+ if (create_info->used_fields & HA_CREATE_USED_ENGINE)
+ {
+ table_engine_set= TRUE;
+ engine_type= create_info->db_type;
+ }
+ else
+ {
+ table_engine_set= FALSE;
+ if (thd->lex->sql_command != SQLCOM_CREATE_TABLE)
+ {
+ table_engine_set= TRUE;
+ DBUG_ASSERT(engine_type && engine_type != partition_hton);
+ }
+ }
+ DBUG_PRINT("info", ("engine_type = %s, table_engine_set = %u",
+ ha_resolve_storage_engine_name(engine_type),
+ table_engine_set));
+ if (part_info->check_engine_mix(engine_type, table_engine_set))
+ goto error;
+
+ /*
+ All engines are of the same type. Check if this engine supports
+ native partitioning.
+ */
+
+ if (!engine_type)
+ engine_type= old_engine_type;
+ DBUG_PRINT("info", ("engine_type = %s",
+ ha_resolve_storage_engine_name(engine_type)));
+ if (engine_type->partition_flags &&
+ (engine_type->partition_flags() & HA_CAN_PARTITION))
+ {
+ create_info->db_type= engine_type;
+ DBUG_PRINT("info", ("Changed to native partitioning"));
+ *ret_val= TRUE;
+ }
+ DBUG_RETURN(FALSE);
+error:
+ /*
+ Mixed engines not yet supported but when supported it will need
+ the partition handler
+ */
+ my_error(ER_MIX_HANDLER_ERROR, MYF(0));
+ *ret_val= FALSE;
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Sets which partitions to be used in the command.
+
+ @param alter_info Alter_info pointer holding partition names and flags.
+ @param tab_part_info partition_info holding all partitions.
+ @param part_state Which state to set for the named partitions.
+
+ @return Operation status
+ @retval false Success
+ @retval true Failure
+*/
+
+bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info,
+ enum partition_state part_state)
+{
+ uint part_count= 0;
+ uint num_parts_found= 0;
+ List_iterator<partition_element> part_it(tab_part_info->partitions);
+
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if ((alter_info->flags & Alter_info::ALTER_ALL_PARTITION) ||
+ (is_name_in_list(part_elem->partition_name,
+ alter_info->partition_names)))
+ {
+ /*
+ Mark the partition.
+ I.e mark the partition as a partition to be "changed" by
+ analyzing/optimizing/rebuilding/checking/repairing/...
+ */
+ num_parts_found++;
+ part_elem->part_state= part_state;
+ DBUG_PRINT("info", ("Setting part_state to %u for partition %s",
+ part_state, part_elem->partition_name));
+ }
+ else
+ part_elem->part_state= PART_NORMAL;
+ } while (++part_count < tab_part_info->num_parts);
+
+ if (num_parts_found != alter_info->partition_names.elements &&
+ !(alter_info->flags & Alter_info::ALTER_ALL_PARTITION))
+ {
+ /* Not all given partitions found, revert and return failure */
+ part_it.rewind();
+ part_count= 0;
+ do
+ {
+ partition_element *part_elem= part_it++;
+ part_elem->part_state= PART_NORMAL;
+ } while (++part_count < tab_part_info->num_parts);
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ @brief Check if partition is exchangable with table by checking table options
+
+ @param table_create_info Table options from table.
+ @param part_elem All the info of the partition.
+
+ @retval FALSE if they are equal, otherwise TRUE.
+
+ @note Any differens that would cause a change in the frm file is prohibited.
+ Such options as data_file_name, index_file_name, min_rows, max_rows etc. are
+ not allowed to differ. But comment is allowed to differ.
+*/
+bool compare_partition_options(HA_CREATE_INFO *table_create_info,
+ partition_element *part_elem)
+{
+#define MAX_COMPARE_PARTITION_OPTION_ERRORS 5
+ const char *option_diffs[MAX_COMPARE_PARTITION_OPTION_ERRORS + 1];
+ int i, errors= 0;
+ DBUG_ENTER("compare_partition_options");
+ DBUG_ASSERT(!part_elem->tablespace_name &&
+ !table_create_info->tablespace);
+
+ /*
+ Note that there are not yet any engine supporting tablespace together
+ with partitioning. TODO: when there are, add compare.
+ */
+ if (part_elem->tablespace_name || table_create_info->tablespace)
+ option_diffs[errors++]= "TABLESPACE";
+ if (part_elem->part_max_rows != table_create_info->max_rows)
+ option_diffs[errors++]= "MAX_ROWS";
+ if (part_elem->part_min_rows != table_create_info->min_rows)
+ option_diffs[errors++]= "MIN_ROWS";
+ if (part_elem->data_file_name || table_create_info->data_file_name)
+ option_diffs[errors++]= "DATA DIRECTORY";
+ if (part_elem->index_file_name || table_create_info->index_file_name)
+ option_diffs[errors++]= "INDEX DIRECTORY";
+
+ for (i= 0; i < errors; i++)
+ my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
+ option_diffs[i]);
+ DBUG_RETURN(errors != 0);
+}
+
+
+/*
+ Prepare for ALTER TABLE of partition structure
+
+ @param[in] thd Thread object
+ @param[in] table Table object
+ @param[in,out] alter_info Alter information
+ @param[in,out] create_info Create info for CREATE TABLE
+ @param[in] alter_ctx ALTER TABLE runtime context
+ @param[out] partition_changed Boolean indicating whether partition changed
+ @param[out] fast_alter_table Boolean indicating if fast partition alter is
+ possible.
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+
+ @note
+ This method handles all preparations for ALTER TABLE for partitioned
+ tables.
+ We need to handle both partition management command such as Add Partition
+ and others here as well as an ALTER TABLE that completely changes the
+ partitioning and yet others that don't change anything at all. We start
+ by checking the partition management variants and then check the general
+ change patterns.
+*/
+
+uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ Alter_table_ctx *alter_ctx,
+ bool *partition_changed,
+ bool *fast_alter_table)
+{
+ DBUG_ENTER("prep_alter_part_table");
+
+ /* Foreign keys on partitioned tables are not supported, waits for WL#148 */
+ if (table->part_info && (alter_info->flags & Alter_info::ADD_FOREIGN_KEY ||
+ alter_info->flags & Alter_info::DROP_FOREIGN_KEY))
+ {
+ my_error(ER_FOREIGN_KEY_ON_PARTITIONED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ /* Remove partitioning on a not partitioned table is not possible */
+ if (!table->part_info && (alter_info->flags &
+ Alter_info::ALTER_REMOVE_PARTITIONING))
+ {
+ my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ thd->work_part_info= thd->lex->part_info;
+
+ if (thd->work_part_info &&
+ !(thd->work_part_info= thd->lex->part_info->get_clone()))
+ DBUG_RETURN(TRUE);
+
+ /* ALTER_ADMIN_PARTITION is handled in mysql_admin_table */
+ DBUG_ASSERT(!(alter_info->flags & Alter_info::ALTER_ADMIN_PARTITION));
+
+ if (alter_info->flags &
+ (Alter_info::ALTER_ADD_PARTITION |
+ Alter_info::ALTER_DROP_PARTITION |
+ Alter_info::ALTER_COALESCE_PARTITION |
+ Alter_info::ALTER_REORGANIZE_PARTITION |
+ Alter_info::ALTER_TABLE_REORG |
+ Alter_info::ALTER_REBUILD_PARTITION))
+ {
+ partition_info *tab_part_info;
+ partition_info *alt_part_info= thd->work_part_info;
+ uint flags= 0;
+ bool is_last_partition_reorged= FALSE;
+ part_elem_value *tab_max_elem_val= NULL;
+ part_elem_value *alt_max_elem_val= NULL;
+ longlong tab_max_range= 0, alt_max_range= 0;
+
+ if (!table->part_info)
+ {
+ my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Open our intermediate table, we will operate on a temporary instance
+ of the original table, to be able to skip copying all partitions.
+ Open it as a copy of the original table, and modify its partition_info
+ object to allow fast_alter_partition_table to perform the changes.
+ */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ alter_ctx->db,
+ alter_ctx->table_name,
+ MDL_INTENTION_EXCLUSIVE));
+
+ tab_part_info= table->part_info;
+
+ if (alter_info->flags & Alter_info::ALTER_TABLE_REORG)
+ {
+ uint new_part_no, curr_part_no;
+ /*
+ 'ALTER TABLE t REORG PARTITION' only allowed with auto partition
+ if default partitioning is used.
+ */
+
+ if (tab_part_info->part_type != HASH_PARTITION ||
+ ((table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) &&
+ !tab_part_info->use_default_num_partitions) ||
+ ((!(table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION)) &&
+ tab_part_info->use_default_num_partitions))
+ {
+ my_error(ER_REORG_NO_PARAM_ERROR, MYF(0));
+ goto err;
+ }
+ new_part_no= table->file->get_default_no_partitions(create_info);
+ curr_part_no= tab_part_info->num_parts;
+ if (new_part_no == curr_part_no)
+ {
+ /*
+ No change is needed, we will have the same number of partitions
+ after the change as before. Thus we can reply ok immediately
+ without any changes at all.
+ */
+ flags= table->file->alter_table_flags(alter_info->flags);
+ if (flags & (HA_FAST_CHANGE_PARTITION | HA_PARTITION_ONE_PHASE))
+ {
+ *fast_alter_table= true;
+ /* Force table re-open for consistency with the main case. */
+ table->m_needs_reopen= true;
+ }
+ else
+ {
+ /*
+ Create copy of partition_info to avoid modifying original
+ TABLE::part_info, to keep it safe for later use.
+ */
+ if (!(tab_part_info= tab_part_info->get_clone()))
+ DBUG_RETURN(TRUE);
+ }
+
+ thd->work_part_info= tab_part_info;
+ DBUG_RETURN(FALSE);
+ }
+ else if (new_part_no > curr_part_no)
+ {
+ /*
+ We will add more partitions, we use the ADD PARTITION without
+ setting the flag for no default number of partitions
+ */
+ alter_info->flags|= Alter_info::ALTER_ADD_PARTITION;
+ thd->work_part_info->num_parts= new_part_no - curr_part_no;
+ }
+ else
+ {
+ /*
+ We will remove hash partitions, we use the COALESCE PARTITION
+ without setting the flag for no default number of partitions
+ */
+ alter_info->flags|= Alter_info::ALTER_COALESCE_PARTITION;
+ alter_info->num_parts= curr_part_no - new_part_no;
+ }
+ }
+ if (!(flags= table->file->alter_table_flags(alter_info->flags)))
+ {
+ my_error(ER_PARTITION_FUNCTION_FAILURE, MYF(0));
+ goto err;
+ }
+ if ((flags & (HA_FAST_CHANGE_PARTITION | HA_PARTITION_ONE_PHASE)) != 0)
+ {
+ /*
+ "Fast" change of partitioning is supported in this case.
+ We will change TABLE::part_info (as this is how we pass
+ information to storage engine in this case), so the table
+ must be reopened.
+ */
+ *fast_alter_table= true;
+ table->m_needs_reopen= true;
+ }
+ else
+ {
+ /*
+ "Fast" changing of partitioning is not supported. Create
+ a copy of TABLE::part_info object, so we can modify it safely.
+ Modifying original TABLE::part_info will cause problems when
+ we read data from old version of table using this TABLE object
+ while copying them to new version of table.
+ */
+ if (!(tab_part_info= tab_part_info->get_clone()))
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_PRINT("info", ("*fast_alter_table flags: 0x%x", flags));
+ if ((alter_info->flags & Alter_info::ALTER_ADD_PARTITION) ||
+ (alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION))
+ {
+ if (thd->work_part_info->part_type != tab_part_info->part_type)
+ {
+ if (thd->work_part_info->part_type == NOT_A_PARTITION)
+ {
+ if (tab_part_info->part_type == RANGE_PARTITION)
+ {
+ my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), "RANGE");
+ goto err;
+ }
+ else if (tab_part_info->part_type == LIST_PARTITION)
+ {
+ my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), "LIST");
+ goto err;
+ }
+ /*
+ Hash partitions can be altered without parser finds out about
+ that it is HASH partitioned. So no error here.
+ */
+ }
+ else
+ {
+ if (thd->work_part_info->part_type == RANGE_PARTITION)
+ {
+ my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0),
+ "RANGE", "LESS THAN");
+ }
+ else if (thd->work_part_info->part_type == LIST_PARTITION)
+ {
+ DBUG_ASSERT(thd->work_part_info->part_type == LIST_PARTITION);
+ my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0),
+ "LIST", "IN");
+ }
+ else if (tab_part_info->part_type == RANGE_PARTITION)
+ {
+ my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0),
+ "RANGE", "LESS THAN");
+ }
+ else
+ {
+ DBUG_ASSERT(tab_part_info->part_type == LIST_PARTITION);
+ my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0),
+ "LIST", "IN");
+ }
+ goto err;
+ }
+ }
+ if ((tab_part_info->column_list &&
+ alt_part_info->num_columns != tab_part_info->num_columns) ||
+ (!tab_part_info->column_list &&
+ (tab_part_info->part_type == RANGE_PARTITION ||
+ tab_part_info->part_type == LIST_PARTITION) &&
+ alt_part_info->num_columns != 1U) ||
+ (!tab_part_info->column_list &&
+ tab_part_info->part_type == HASH_PARTITION &&
+ alt_part_info->num_columns != 0))
+ {
+ my_error(ER_PARTITION_COLUMN_LIST_ERROR, MYF(0));
+ goto err;
+ }
+ alt_part_info->column_list= tab_part_info->column_list;
+ if (alt_part_info->fix_parser_data(thd))
+ {
+ goto err;
+ }
+ }
+ if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION)
+ {
+ /*
+ We start by moving the new partitions to the list of temporary
+ partitions. We will then check that the new partitions fit in the
+ partitioning scheme as currently set-up.
+ Partitions are always added at the end in ADD PARTITION.
+ */
+ uint num_new_partitions= alt_part_info->num_parts;
+ uint num_orig_partitions= tab_part_info->num_parts;
+ uint check_total_partitions= num_new_partitions + num_orig_partitions;
+ uint new_total_partitions= check_total_partitions;
+ /*
+ We allow quite a lot of values to be supplied by defaults, however we
+ must know the number of new partitions in this case.
+ */
+ if (thd->lex->no_write_to_binlog &&
+ tab_part_info->part_type != HASH_PARTITION)
+ {
+ my_error(ER_NO_BINLOG_ERROR, MYF(0));
+ goto err;
+ }
+ if (tab_part_info->defined_max_value)
+ {
+ my_error(ER_PARTITION_MAXVALUE_ERROR, MYF(0));
+ goto err;
+ }
+ if (num_new_partitions == 0)
+ {
+ my_error(ER_ADD_PARTITION_NO_NEW_PARTITION, MYF(0));
+ goto err;
+ }
+ if (tab_part_info->is_sub_partitioned())
+ {
+ if (alt_part_info->num_subparts == 0)
+ alt_part_info->num_subparts= tab_part_info->num_subparts;
+ else if (alt_part_info->num_subparts != tab_part_info->num_subparts)
+ {
+ my_error(ER_ADD_PARTITION_SUBPART_ERROR, MYF(0));
+ goto err;
+ }
+ check_total_partitions= new_total_partitions*
+ alt_part_info->num_subparts;
+ }
+ if (check_total_partitions > MAX_PARTITIONS)
+ {
+ my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
+ goto err;
+ }
+ alt_part_info->part_type= tab_part_info->part_type;
+ alt_part_info->subpart_type= tab_part_info->subpart_type;
+ if (alt_part_info->set_up_defaults_for_partitioning(table->file, 0,
+ tab_part_info->num_parts))
+ {
+ goto err;
+ }
+/*
+Handling of on-line cases:
+
+ADD PARTITION for RANGE/LIST PARTITIONING:
+------------------------------------------
+For range and list partitions add partition is simply adding a
+new empty partition to the table. If the handler support this we
+will use the simple method of doing this. The figure below shows
+an example of this and the states involved in making this change.
+
+Existing partitions New added partitions
+------ ------ ------ ------ | ------ ------
+| | | | | | | | | | | | |
+| p0 | | p1 | | p2 | | p3 | | | p4 | | p5 |
+------ ------ ------ ------ | ------ ------
+PART_NORMAL PART_NORMAL PART_NORMAL PART_NORMAL PART_TO_BE_ADDED*2
+PART_NORMAL PART_NORMAL PART_NORMAL PART_NORMAL PART_IS_ADDED*2
+
+The first line is the states before adding the new partitions and the
+second line is after the new partitions are added. All the partitions are
+in the partitions list, no partitions are placed in the temp_partitions
+list.
+
+ADD PARTITION for HASH PARTITIONING
+-----------------------------------
+This little figure tries to show the various partitions involved when
+adding two new partitions to a linear hash based partitioned table with
+four partitions to start with, which lists are used and the states they
+pass through. Adding partitions to a normal hash based is similar except
+that it is always all the existing partitions that are reorganised not
+only a subset of them.
+
+Existing partitions New added partitions
+------ ------ ------ ------ | ------ ------
+| | | | | | | | | | | | |
+| p0 | | p1 | | p2 | | p3 | | | p4 | | p5 |
+------ ------ ------ ------ | ------ ------
+PART_CHANGED PART_CHANGED PART_NORMAL PART_NORMAL PART_TO_BE_ADDED
+PART_IS_CHANGED*2 PART_NORMAL PART_NORMAL PART_IS_ADDED
+PART_NORMAL PART_NORMAL PART_NORMAL PART_NORMAL PART_IS_ADDED
+
+Reorganised existing partitions
+------ ------
+| | | |
+| p0'| | p1'|
+------ ------
+
+p0 - p5 will be in the partitions list of partitions.
+p0' and p1' will actually not exist as separate objects, there presence can
+be deduced from the state of the partition and also the names of those
+partitions can be deduced this way.
+
+After adding the partitions and copying the partition data to p0', p1',
+p4 and p5 from p0 and p1 the states change to adapt for the new situation
+where p0 and p1 is dropped and replaced by p0' and p1' and the new p4 and
+p5 are in the table again.
+
+The first line above shows the states of the partitions before we start
+adding and copying partitions, the second after completing the adding
+and copying and finally the third line after also dropping the partitions
+that are reorganised.
+*/
+ if (*fast_alter_table &&
+ tab_part_info->part_type == HASH_PARTITION)
+ {
+ uint part_no= 0, start_part= 1, start_sec_part= 1;
+ uint end_part= 0, end_sec_part= 0;
+ uint upper_2n= tab_part_info->linear_hash_mask + 1;
+ uint lower_2n= upper_2n >> 1;
+ bool all_parts= TRUE;
+ if (tab_part_info->linear_hash_ind &&
+ num_new_partitions < upper_2n)
+ {
+ /*
+ An analysis of which parts needs reorganisation shows that it is
+ divided into two intervals. The first interval is those parts
+ that are reorganised up until upper_2n - 1. From upper_2n and
+ onwards it starts again from partition 0 and goes on until
+ it reaches p(upper_2n - 1). If the last new partition reaches
+ beyond upper_2n - 1 then the first interval will end with
+ p(lower_2n - 1) and start with p(num_orig_partitions - lower_2n).
+ If lower_2n partitions are added then p0 to p(lower_2n - 1) will
+ be reorganised which means that the two interval becomes one
+ interval at this point. Thus only when adding less than
+ lower_2n partitions and going beyond a total of upper_2n we
+ actually get two intervals.
+
+ To exemplify this assume we have 6 partitions to start with and
+ add 1, 2, 3, 5, 6, 7, 8, 9 partitions.
+ The first to add after p5 is p6 = 110 in bit numbers. Thus we
+ can see that 10 = p2 will be partition to reorganise if only one
+ partition.
+ If 2 partitions are added we reorganise [p2, p3]. Those two
+ cases are covered by the second if part below.
+ If 3 partitions are added we reorganise [p2, p3] U [p0,p0]. This
+ part is covered by the else part below.
+ If 5 partitions are added we get [p2,p3] U [p0, p2] = [p0, p3].
+ This is covered by the first if part where we need the max check
+ to here use lower_2n - 1.
+ If 7 partitions are added we get [p2,p3] U [p0, p4] = [p0, p4].
+ This is covered by the first if part but here we use the first
+ calculated end_part.
+ Finally with 9 new partitions we would also reorganise p6 if we
+ used the method below but we cannot reorganise more partitions
+ than what we had from the start and thus we simply set all_parts
+ to TRUE. In this case we don't get into this if-part at all.
+ */
+ all_parts= FALSE;
+ if (num_new_partitions >= lower_2n)
+ {
+ /*
+ In this case there is only one interval since the two intervals
+ overlap and this starts from zero to last_part_no - upper_2n
+ */
+ start_part= 0;
+ end_part= new_total_partitions - (upper_2n + 1);
+ end_part= max(lower_2n - 1, end_part);
+ }
+ else if (new_total_partitions <= upper_2n)
+ {
+ /*
+ Also in this case there is only one interval since we are not
+ going over a 2**n boundary
+ */
+ start_part= num_orig_partitions - lower_2n;
+ end_part= start_part + (num_new_partitions - 1);
+ }
+ else
+ {
+ /* We have two non-overlapping intervals since we are not
+ passing a 2**n border and we have not at least lower_2n
+ new parts that would ensure that the intervals become
+ overlapping.
+ */
+ start_part= num_orig_partitions - lower_2n;
+ end_part= upper_2n - 1;
+ start_sec_part= 0;
+ end_sec_part= new_total_partitions - (upper_2n + 1);
+ }
+ }
+ List_iterator<partition_element> tab_it(tab_part_info->partitions);
+ part_no= 0;
+ do
+ {
+ partition_element *p_elem= tab_it++;
+ if (all_parts ||
+ (part_no >= start_part && part_no <= end_part) ||
+ (part_no >= start_sec_part && part_no <= end_sec_part))
+ {
+ p_elem->part_state= PART_CHANGED;
+ }
+ } while (++part_no < num_orig_partitions);
+ }
+ /*
+ Need to concatenate the lists here to make it possible to check the
+ partition info for correctness using check_partition_info.
+ For on-line add partition we set the state of this partition to
+ PART_TO_BE_ADDED to ensure that it is known that it is not yet
+ usable (becomes usable when partition is created and the switch of
+ partition configuration is made.
+ */
+ {
+ List_iterator<partition_element> alt_it(alt_part_info->partitions);
+ uint part_count= 0;
+ do
+ {
+ partition_element *part_elem= alt_it++;
+ if (*fast_alter_table)
+ part_elem->part_state= PART_TO_BE_ADDED;
+ if (tab_part_info->partitions.push_back(part_elem))
+ {
+ mem_alloc_error(1);
+ goto err;
+ }
+ } while (++part_count < num_new_partitions);
+ tab_part_info->num_parts+= num_new_partitions;
+ }
+ /*
+ If we specify partitions explicitly we don't use defaults anymore.
+ Using ADD PARTITION also means that we don't have the default number
+ of partitions anymore. We use this code also for Table reorganisations
+ and here we don't set any default flags to FALSE.
+ */
+ if (!(alter_info->flags & Alter_info::ALTER_TABLE_REORG))
+ {
+ if (!alt_part_info->use_default_partitions)
+ {
+ DBUG_PRINT("info", ("part_info: 0x%lx", (long) tab_part_info));
+ tab_part_info->use_default_partitions= FALSE;
+ }
+ tab_part_info->use_default_num_partitions= FALSE;
+ tab_part_info->is_auto_partitioned= FALSE;
+ }
+ }
+ else if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION)
+ {
+ /*
+ Drop a partition from a range partition and list partitioning is
+ always safe and can be made more or less immediate. It is necessary
+ however to ensure that the partition to be removed is safely removed
+ and that REPAIR TABLE can remove the partition if for some reason the
+ command to drop the partition failed in the middle.
+ */
+ uint part_count= 0;
+ uint num_parts_dropped= alter_info->partition_names.elements;
+ uint num_parts_found= 0;
+ List_iterator<partition_element> part_it(tab_part_info->partitions);
+
+ tab_part_info->is_auto_partitioned= FALSE;
+ if (!(tab_part_info->part_type == RANGE_PARTITION ||
+ tab_part_info->part_type == LIST_PARTITION))
+ {
+ my_error(ER_ONLY_ON_RANGE_LIST_PARTITION, MYF(0), "DROP");
+ goto err;
+ }
+ if (num_parts_dropped >= tab_part_info->num_parts)
+ {
+ my_error(ER_DROP_LAST_PARTITION, MYF(0));
+ goto err;
+ }
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (is_name_in_list(part_elem->partition_name,
+ alter_info->partition_names))
+ {
+ /*
+ Set state to indicate that the partition is to be dropped.
+ */
+ num_parts_found++;
+ part_elem->part_state= PART_TO_BE_DROPPED;
+ }
+ } while (++part_count < tab_part_info->num_parts);
+ if (num_parts_found != num_parts_dropped)
+ {
+ my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), "DROP");
+ goto err;
+ }
+ if (table->file->is_fk_defined_on_table_or_index(MAX_KEY))
+ {
+ my_error(ER_ROW_IS_REFERENCED, MYF(0));
+ goto err;
+ }
+ tab_part_info->num_parts-= num_parts_dropped;
+ }
+ else if (alter_info->flags & Alter_info::ALTER_REBUILD_PARTITION)
+ {
+ set_engine_all_partitions(tab_part_info,
+ tab_part_info->default_engine_type);
+ if (set_part_state(alter_info, tab_part_info, PART_CHANGED))
+ {
+ my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), "REBUILD");
+ goto err;
+ }
+ if (!(*fast_alter_table))
+ {
+ table->file->print_error(HA_ERR_WRONG_COMMAND, MYF(0));
+ goto err;
+ }
+ }
+ else if (alter_info->flags & Alter_info::ALTER_COALESCE_PARTITION)
+ {
+ uint num_parts_coalesced= alter_info->num_parts;
+ uint num_parts_remain= tab_part_info->num_parts - num_parts_coalesced;
+ List_iterator<partition_element> part_it(tab_part_info->partitions);
+ if (tab_part_info->part_type != HASH_PARTITION)
+ {
+ my_error(ER_COALESCE_ONLY_ON_HASH_PARTITION, MYF(0));
+ goto err;
+ }
+ if (num_parts_coalesced == 0)
+ {
+ my_error(ER_COALESCE_PARTITION_NO_PARTITION, MYF(0));
+ goto err;
+ }
+ if (num_parts_coalesced >= tab_part_info->num_parts)
+ {
+ my_error(ER_DROP_LAST_PARTITION, MYF(0));
+ goto err;
+ }
+/*
+Online handling:
+COALESCE PARTITION:
+-------------------
+The figure below shows the manner in which partitions are handled when
+performing an on-line coalesce partition and which states they go through
+at start, after adding and copying partitions and finally after dropping
+the partitions to drop. The figure shows an example using four partitions
+to start with, using linear hash and coalescing one partition (always the
+last partition).
+
+Using linear hash then all remaining partitions will have a new reorganised
+part.
+
+Existing partitions Coalesced partition
+------ ------ ------ | ------
+| | | | | | | | |
+| p0 | | p1 | | p2 | | | p3 |
+------ ------ ------ | ------
+PART_NORMAL PART_CHANGED PART_NORMAL PART_REORGED_DROPPED
+PART_NORMAL PART_IS_CHANGED PART_NORMAL PART_TO_BE_DROPPED
+PART_NORMAL PART_NORMAL PART_NORMAL PART_IS_DROPPED
+
+Reorganised existing partitions
+ ------
+ | |
+ | p1'|
+ ------
+
+p0 - p3 is in the partitions list.
+The p1' partition will actually not be in any list it is deduced from the
+state of p1.
+*/
+ {
+ uint part_count= 0, start_part= 1, start_sec_part= 1;
+ uint end_part= 0, end_sec_part= 0;
+ bool all_parts= TRUE;
+ if (*fast_alter_table &&
+ tab_part_info->linear_hash_ind)
+ {
+ uint upper_2n= tab_part_info->linear_hash_mask + 1;
+ uint lower_2n= upper_2n >> 1;
+ all_parts= FALSE;
+ if (num_parts_coalesced >= lower_2n)
+ {
+ all_parts= TRUE;
+ }
+ else if (num_parts_remain >= lower_2n)
+ {
+ end_part= tab_part_info->num_parts - (lower_2n + 1);
+ start_part= num_parts_remain - lower_2n;
+ }
+ else
+ {
+ start_part= 0;
+ end_part= tab_part_info->num_parts - (lower_2n + 1);
+ end_sec_part= (lower_2n >> 1) - 1;
+ start_sec_part= end_sec_part - (lower_2n - (num_parts_remain + 1));
+ }
+ }
+ do
+ {
+ partition_element *p_elem= part_it++;
+ if (*fast_alter_table &&
+ (all_parts ||
+ (part_count >= start_part && part_count <= end_part) ||
+ (part_count >= start_sec_part && part_count <= end_sec_part)))
+ p_elem->part_state= PART_CHANGED;
+ if (++part_count > num_parts_remain)
+ {
+ if (*fast_alter_table)
+ p_elem->part_state= PART_REORGED_DROPPED;
+ else
+ part_it.remove();
+ }
+ } while (part_count < tab_part_info->num_parts);
+ tab_part_info->num_parts= num_parts_remain;
+ }
+ if (!(alter_info->flags & Alter_info::ALTER_TABLE_REORG))
+ {
+ tab_part_info->use_default_num_partitions= FALSE;
+ tab_part_info->is_auto_partitioned= FALSE;
+ }
+ }
+ else if (alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION)
+ {
+ /*
+ Reorganise partitions takes a number of partitions that are next
+ to each other (at least for RANGE PARTITIONS) and then uses those
+ to create a set of new partitions. So data is copied from those
+ partitions into the new set of partitions. Those new partitions
+ can have more values in the LIST value specifications or less both
+ are allowed. The ranges can be different but since they are
+ changing a set of consecutive partitions they must cover the same
+ range as those changed from.
+ This command can be used on RANGE and LIST partitions.
+ */
+ uint num_parts_reorged= alter_info->partition_names.elements;
+ uint num_parts_new= thd->work_part_info->partitions.elements;
+ uint check_total_partitions;
+
+ tab_part_info->is_auto_partitioned= FALSE;
+ if (num_parts_reorged > tab_part_info->num_parts)
+ {
+ my_error(ER_REORG_PARTITION_NOT_EXIST, MYF(0));
+ goto err;
+ }
+ if (!(tab_part_info->part_type == RANGE_PARTITION ||
+ tab_part_info->part_type == LIST_PARTITION) &&
+ (num_parts_new != num_parts_reorged))
+ {
+ my_error(ER_REORG_HASH_ONLY_ON_SAME_NO, MYF(0));
+ goto err;
+ }
+ if (tab_part_info->is_sub_partitioned() &&
+ alt_part_info->num_subparts &&
+ alt_part_info->num_subparts != tab_part_info->num_subparts)
+ {
+ my_error(ER_PARTITION_WRONG_NO_SUBPART_ERROR, MYF(0));
+ goto err;
+ }
+ check_total_partitions= tab_part_info->num_parts + num_parts_new;
+ check_total_partitions-= num_parts_reorged;
+ if (check_total_partitions > MAX_PARTITIONS)
+ {
+ my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
+ goto err;
+ }
+ alt_part_info->part_type= tab_part_info->part_type;
+ alt_part_info->subpart_type= tab_part_info->subpart_type;
+ alt_part_info->num_subparts= tab_part_info->num_subparts;
+ DBUG_ASSERT(!alt_part_info->use_default_partitions);
+ /* We specified partitions explicitly so don't use defaults anymore. */
+ tab_part_info->use_default_partitions= FALSE;
+ if (alt_part_info->set_up_defaults_for_partitioning(table->file, 0, 0))
+ {
+ goto err;
+ }
+/*
+Online handling:
+REORGANIZE PARTITION:
+---------------------
+The figure exemplifies the handling of partitions, their state changes and
+how they are organised. It exemplifies four partitions where two of the
+partitions are reorganised (p1 and p2) into two new partitions (p4 and p5).
+The reason of this change could be to change range limits, change list
+values or for hash partitions simply reorganise the partition which could
+also involve moving them to new disks or new node groups (MySQL Cluster).
+
+Existing partitions
+------ ------ ------ ------
+| | | | | | | |
+| p0 | | p1 | | p2 | | p3 |
+------ ------ ------ ------
+PART_NORMAL PART_TO_BE_REORGED PART_NORMAL
+PART_NORMAL PART_TO_BE_DROPPED PART_NORMAL
+PART_NORMAL PART_IS_DROPPED PART_NORMAL
+
+Reorganised new partitions (replacing p1 and p2)
+------ ------
+| | | |
+| p4 | | p5 |
+------ ------
+PART_TO_BE_ADDED
+PART_IS_ADDED
+PART_IS_ADDED
+
+All unchanged partitions and the new partitions are in the partitions list
+in the order they will have when the change is completed. The reorganised
+partitions are placed in the temp_partitions list. PART_IS_ADDED is only a
+temporary state not written in the frm file. It is used to ensure we write
+the generated partition syntax in a correct manner.
+*/
+ {
+ List_iterator<partition_element> tab_it(tab_part_info->partitions);
+ uint part_count= 0;
+ bool found_first= FALSE;
+ bool found_last= FALSE;
+ uint drop_count= 0;
+ do
+ {
+ partition_element *part_elem= tab_it++;
+ is_last_partition_reorged= FALSE;
+ if (is_name_in_list(part_elem->partition_name,
+ alter_info->partition_names))
+ {
+ is_last_partition_reorged= TRUE;
+ drop_count++;
+ if (tab_part_info->column_list)
+ {
+ List_iterator<part_elem_value> p(part_elem->list_val_list);
+ tab_max_elem_val= p++;
+ }
+ else
+ tab_max_range= part_elem->range_value;
+ if (*fast_alter_table &&
+ tab_part_info->temp_partitions.push_back(part_elem))
+ {
+ mem_alloc_error(1);
+ goto err;
+ }
+ if (*fast_alter_table)
+ part_elem->part_state= PART_TO_BE_REORGED;
+ if (!found_first)
+ {
+ uint alt_part_count= 0;
+ partition_element *alt_part_elem;
+ List_iterator<partition_element>
+ alt_it(alt_part_info->partitions);
+ found_first= TRUE;
+ do
+ {
+ alt_part_elem= alt_it++;
+ if (tab_part_info->column_list)
+ {
+ List_iterator<part_elem_value> p(alt_part_elem->list_val_list);
+ alt_max_elem_val= p++;
+ }
+ else
+ alt_max_range= alt_part_elem->range_value;
+
+ if (*fast_alter_table)
+ alt_part_elem->part_state= PART_TO_BE_ADDED;
+ if (alt_part_count == 0)
+ tab_it.replace(alt_part_elem);
+ else
+ tab_it.after(alt_part_elem);
+ } while (++alt_part_count < num_parts_new);
+ }
+ else if (found_last)
+ {
+ my_error(ER_CONSECUTIVE_REORG_PARTITIONS, MYF(0));
+ goto err;
+ }
+ else
+ tab_it.remove();
+ }
+ else
+ {
+ if (found_first)
+ found_last= TRUE;
+ }
+ } while (++part_count < tab_part_info->num_parts);
+ if (drop_count != num_parts_reorged)
+ {
+ my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), "REORGANIZE");
+ goto err;
+ }
+ tab_part_info->num_parts= check_total_partitions;
+ }
+ }
+ else
+ {
+ DBUG_ASSERT(FALSE);
+ }
+ *partition_changed= TRUE;
+ thd->work_part_info= tab_part_info;
+ if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION ||
+ alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION)
+ {
+ if (tab_part_info->use_default_subpartitions &&
+ !alt_part_info->use_default_subpartitions)
+ {
+ tab_part_info->use_default_subpartitions= FALSE;
+ tab_part_info->use_default_num_subpartitions= FALSE;
+ }
+ if (tab_part_info->check_partition_info(thd, (handlerton**)NULL,
+ table->file, 0, TRUE))
+ {
+ goto err;
+ }
+ /*
+ The check below needs to be performed after check_partition_info
+ since this function "fixes" the item trees of the new partitions
+ to reorganize into
+ */
+ if (alter_info->flags == Alter_info::ALTER_REORGANIZE_PARTITION &&
+ tab_part_info->part_type == RANGE_PARTITION &&
+ ((is_last_partition_reorged &&
+ (tab_part_info->column_list ?
+ (tab_part_info->compare_column_values(
+ alt_max_elem_val->col_val_array,
+ tab_max_elem_val->col_val_array) < 0) :
+ alt_max_range < tab_max_range)) ||
+ (!is_last_partition_reorged &&
+ (tab_part_info->column_list ?
+ (tab_part_info->compare_column_values(
+ alt_max_elem_val->col_val_array,
+ tab_max_elem_val->col_val_array) != 0) :
+ alt_max_range != tab_max_range))))
+ {
+ /*
+ For range partitioning the total resulting range before and
+ after the change must be the same except in one case. This is
+ when the last partition is reorganised, in this case it is
+ acceptable to increase the total range.
+ The reason is that it is not allowed to have "holes" in the
+ middle of the ranges and thus we should not allow to reorganise
+ to create "holes".
+ */
+ my_error(ER_REORG_OUTSIDE_RANGE, MYF(0));
+ goto err;
+ }
+ }
+ }
+ else
+ {
+ /*
+ When thd->lex->part_info has a reference to a partition_info the
+ ALTER TABLE contained a definition of a partitioning.
+
+ Case I:
+ If there was a partition before and there is a new one defined.
+ We use the new partitioning. The new partitioning is already
+ defined in the correct variable so no work is needed to
+ accomplish this.
+ We do however need to update partition_changed to ensure that not
+ only the frm file is changed in the ALTER TABLE command.
+
+ Case IIa:
+ There was a partitioning before and there is no new one defined.
+ Also the user has not specified to remove partitioning explicitly.
+
+ We use the old partitioning also for the new table. We do this
+ by assigning the partition_info from the table loaded in
+ open_table to the partition_info struct used by mysql_create_table
+ later in this method.
+
+ Case IIb:
+ There was a partitioning before and there is no new one defined.
+ The user has specified explicitly to remove partitioning
+
+ Since the user has specified explicitly to remove partitioning
+ we override the old partitioning info and create a new table using
+ the specified engine.
+ In this case the partition also is changed.
+
+ Case III:
+ There was no partitioning before altering the table, there is
+ partitioning defined in the altered table. Use the new partitioning.
+ No work needed since the partitioning info is already in the
+ correct variable.
+
+ In this case we discover one case where the new partitioning is using
+ the same partition function as the default (PARTITION BY KEY or
+ PARTITION BY LINEAR KEY with the list of fields equal to the primary
+ key fields OR PARTITION BY [LINEAR] KEY() for tables without primary
+ key)
+ Also here partition has changed and thus a new table must be
+ created.
+
+ Case IV:
+ There was no partitioning before and no partitioning defined.
+ Obviously no work needed.
+ */
+ partition_info *tab_part_info= table->part_info;
+
+ if (tab_part_info)
+ {
+ if (alter_info->flags & Alter_info::ALTER_REMOVE_PARTITIONING)
+ {
+ DBUG_PRINT("info", ("Remove partitioning"));
+ if (!(create_info->used_fields & HA_CREATE_USED_ENGINE))
+ {
+ DBUG_PRINT("info", ("No explicit engine used"));
+ create_info->db_type= tab_part_info->default_engine_type;
+ }
+ DBUG_PRINT("info", ("New engine type: %s",
+ ha_resolve_storage_engine_name(create_info->db_type)));
+ thd->work_part_info= NULL;
+ *partition_changed= TRUE;
+ }
+ else if (!thd->work_part_info)
+ {
+ /*
+ Retain partitioning but possibly with a new storage engine
+ beneath.
+
+ Create a copy of TABLE::part_info to be able to modify it freely.
+ */
+ if (!(tab_part_info= tab_part_info->get_clone()))
+ DBUG_RETURN(TRUE);
+ thd->work_part_info= tab_part_info;
+ if (create_info->used_fields & HA_CREATE_USED_ENGINE &&
+ create_info->db_type != tab_part_info->default_engine_type)
+ {
+ /*
+ Make sure change of engine happens to all partitions.
+ */
+ DBUG_PRINT("info", ("partition changed"));
+ if (tab_part_info->is_auto_partitioned)
+ {
+ /*
+ If the user originally didn't specify partitioning to be
+ used we can remove it now.
+ */
+ thd->work_part_info= NULL;
+ }
+ else
+ {
+ /*
+ Ensure that all partitions have the proper engine set-up
+ */
+ set_engine_all_partitions(thd->work_part_info,
+ create_info->db_type);
+ }
+ *partition_changed= TRUE;
+ }
+ }
+ }
+ if (thd->work_part_info)
+ {
+ partition_info *part_info= thd->work_part_info;
+ bool is_native_partitioned= FALSE;
+ /*
+ Need to cater for engine types that can handle partition without
+ using the partition handler.
+ */
+ if (part_info != tab_part_info)
+ {
+ if (part_info->fix_parser_data(thd))
+ {
+ goto err;
+ }
+ /*
+ Compare the old and new part_info. If only key_algorithm
+ change is done, don't consider it as changed partitioning (to avoid
+ rebuild). This is to handle KEY (numeric_cols) partitioned tables
+ created in 5.1. For more info, see bug#14521864.
+ */
+ if (alter_info->flags != Alter_info::ALTER_PARTITION ||
+ !table->part_info ||
+ alter_info->requested_algorithm !=
+ Alter_info::ALTER_TABLE_ALGORITHM_INPLACE ||
+ !table->part_info->has_same_partitioning(part_info))
+ {
+ DBUG_PRINT("info", ("partition changed"));
+ *partition_changed= true;
+ }
+ }
+ /*
+ Set up partition default_engine_type either from the create_info
+ or from the previus table
+ */
+ if (create_info->used_fields & HA_CREATE_USED_ENGINE)
+ part_info->default_engine_type= create_info->db_type;
+ else
+ {
+ if (tab_part_info)
+ part_info->default_engine_type= tab_part_info->default_engine_type;
+ else
+ part_info->default_engine_type= create_info->db_type;
+ }
+ DBUG_ASSERT(part_info->default_engine_type &&
+ part_info->default_engine_type != partition_hton);
+ if (check_native_partitioned(create_info, &is_native_partitioned,
+ part_info, thd))
+ {
+ goto err;
+ }
+ if (!is_native_partitioned)
+ {
+ DBUG_ASSERT(create_info->db_type);
+ create_info->db_type= partition_hton;
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+err:
+ *fast_alter_table= false;
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Change partitions, used to implement ALTER TABLE ADD/REORGANIZE/COALESCE
+ partitions. This method is used to implement both single-phase and multi-
+ phase implementations of ADD/REORGANIZE/COALESCE partitions.
+
+ SYNOPSIS
+ mysql_change_partitions()
+ lpt Struct containing parameters
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+
+ DESCRIPTION
+ Request handler to add partitions as set in states of the partition
+
+ Elements of the lpt parameters used:
+ create_info Create information used to create partitions
+ db Database name
+ table_name Table name
+ copied Output parameter where number of copied
+ records are added
+ deleted Output parameter where number of deleted
+ records are added
+*/
+
+static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ char path[FN_REFLEN+1];
+ int error;
+ handler *file= lpt->table->file;
+ THD *thd= lpt->thd;
+ DBUG_ENTER("mysql_change_partitions");
+
+ build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0);
+
+ if(mysql_trans_prepare_alter_copy_data(thd))
+ DBUG_RETURN(TRUE);
+
+ /* TODO: test if bulk_insert would increase the performance */
+
+ if ((error= file->ha_change_partitions(lpt->create_info, path, &lpt->copied,
+ &lpt->deleted, lpt->pack_frm_data,
+ lpt->pack_frm_len)))
+ {
+ file->print_error(error, MYF(error != ER_OUTOFMEMORY ? 0 : ME_FATALERROR));
+ }
+
+ if (mysql_trans_commit_alter_copy_data(thd))
+ error= 1; /* The error has been reported */
+
+ DBUG_RETURN(MY_TEST(error));
+}
+
+
+/*
+ Rename partitions in an ALTER TABLE of partitions
+
+ SYNOPSIS
+ mysql_rename_partitions()
+ lpt Struct containing parameters
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+
+ DESCRIPTION
+ Request handler to rename partitions as set in states of the partition
+
+ Parameters used:
+ db Database name
+ table_name Table name
+*/
+
+static bool mysql_rename_partitions(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ char path[FN_REFLEN+1];
+ int error;
+ DBUG_ENTER("mysql_rename_partitions");
+
+ build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0);
+ if ((error= lpt->table->file->ha_rename_partitions(path)))
+ {
+ if (error != 1)
+ lpt->table->file->print_error(error, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Drop partitions in an ALTER TABLE of partitions
+
+ SYNOPSIS
+ mysql_drop_partitions()
+ lpt Struct containing parameters
+
+ RETURN VALUES
+ TRUE Failure
+ FALSE Success
+ DESCRIPTION
+ Drop the partitions marked with PART_TO_BE_DROPPED state and remove
+ those partitions from the list.
+
+ Parameters used:
+ table Table object
+ db Database name
+ table_name Table name
+*/
+
+static bool mysql_drop_partitions(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ char path[FN_REFLEN+1];
+ partition_info *part_info= lpt->table->part_info;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ uint i= 0;
+ uint remove_count= 0;
+ int error;
+ DBUG_ENTER("mysql_drop_partitions");
+
+ DBUG_ASSERT(lpt->thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ lpt->table->s->db.str,
+ lpt->table->s->table_name.str,
+ MDL_EXCLUSIVE));
+
+ build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0);
+ if ((error= lpt->table->file->ha_drop_partitions(path)))
+ {
+ lpt->table->file->print_error(error, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_IS_DROPPED)
+ {
+ part_it.remove();
+ remove_count++;
+ }
+ } while (++i < part_info->num_parts);
+ part_info->num_parts-= remove_count;
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Insert log entry into list
+ SYNOPSIS
+ insert_part_info_log_entry_list()
+ log_entry
+ RETURN VALUES
+ NONE
+*/
+
+static void insert_part_info_log_entry_list(partition_info *part_info,
+ DDL_LOG_MEMORY_ENTRY *log_entry)
+{
+ log_entry->next_active_log_entry= part_info->first_log_entry;
+ part_info->first_log_entry= log_entry;
+}
+
+
+/*
+ Release all log entries for this partition info struct
+ SYNOPSIS
+ release_part_info_log_entries()
+ first_log_entry First log entry in list to release
+ RETURN VALUES
+ NONE
+*/
+
+static void release_part_info_log_entries(DDL_LOG_MEMORY_ENTRY *log_entry)
+{
+ DBUG_ENTER("release_part_info_log_entries");
+
+ while (log_entry)
+ {
+ release_ddl_log_memory_entry(log_entry);
+ log_entry= log_entry->next_active_log_entry;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Log an delete/rename frm file
+ SYNOPSIS
+ write_log_replace_delete_frm()
+ lpt Struct for parameters
+ next_entry Next reference to use in log record
+ from_path Name to rename from
+ to_path Name to rename to
+ replace_flag TRUE if replace, else delete
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ Support routine that writes a replace or delete of an frm file into the
+ ddl log. It also inserts an entry that keeps track of used space into
+ the partition info object
+*/
+
+static bool write_log_replace_delete_frm(ALTER_PARTITION_PARAM_TYPE *lpt,
+ uint next_entry,
+ const char *from_path,
+ const char *to_path,
+ bool replace_flag)
+{
+ DDL_LOG_ENTRY ddl_log_entry;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ DBUG_ENTER("write_log_replace_delete_frm");
+
+ if (replace_flag)
+ ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION;
+ else
+ ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION;
+ ddl_log_entry.next_entry= next_entry;
+ ddl_log_entry.handler_name= reg_ext;
+ ddl_log_entry.name= to_path;
+ if (replace_flag)
+ ddl_log_entry.from_name= from_path;
+ if (write_ddl_log_entry(&ddl_log_entry, &log_entry))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ insert_part_info_log_entry_list(lpt->part_info, log_entry);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Log final partition changes in change partition
+ SYNOPSIS
+ write_log_changed_partitions()
+ lpt Struct containing parameters
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ This code is used to perform safe ADD PARTITION for HASH partitions
+ and COALESCE for HASH partitions and REORGANIZE for any type of
+ partitions.
+ We prepare entries for all partitions except the reorganised partitions
+ in REORGANIZE partition, those are handled by
+ write_log_dropped_partitions. For those partitions that are replaced
+ special care is needed to ensure that this is performed correctly and
+ this requires a two-phased approach with this log as a helper for this.
+
+ This code is closely intertwined with the code in rename_partitions in
+ the partition handler.
+*/
+
+static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt,
+ uint *next_entry, const char *path)
+{
+ DDL_LOG_ENTRY ddl_log_entry;
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ char tmp_path[FN_REFLEN];
+ char normal_path[FN_REFLEN];
+ List_iterator<partition_element> part_it(part_info->partitions);
+ uint temp_partitions= part_info->temp_partitions.elements;
+ uint num_elements= part_info->partitions.elements;
+ uint i= 0;
+ DBUG_ENTER("write_log_changed_partitions");
+
+ do
+ {
+ partition_element *part_elem= part_it++;
+ if (part_elem->part_state == PART_IS_CHANGED ||
+ (part_elem->part_state == PART_IS_ADDED && temp_partitions))
+ {
+ if (part_info->is_sub_partitioned())
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ uint num_subparts= part_info->num_subparts;
+ uint j= 0;
+ do
+ {
+ partition_element *sub_elem= sub_it++;
+ ddl_log_entry.next_entry= *next_entry;
+ ddl_log_entry.handler_name=
+ ha_resolve_storage_engine_name(sub_elem->engine_type);
+ create_subpartition_name(tmp_path, path,
+ part_elem->partition_name,
+ sub_elem->partition_name,
+ TEMP_PART_NAME);
+ create_subpartition_name(normal_path, path,
+ part_elem->partition_name,
+ sub_elem->partition_name,
+ NORMAL_PART_NAME);
+ ddl_log_entry.name= normal_path;
+ ddl_log_entry.from_name= tmp_path;
+ if (part_elem->part_state == PART_IS_CHANGED)
+ ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION;
+ else
+ ddl_log_entry.action_type= DDL_LOG_RENAME_ACTION;
+ if (write_ddl_log_entry(&ddl_log_entry, &log_entry))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ *next_entry= log_entry->entry_pos;
+ sub_elem->log_entry= log_entry;
+ insert_part_info_log_entry_list(part_info, log_entry);
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ ddl_log_entry.next_entry= *next_entry;
+ ddl_log_entry.handler_name=
+ ha_resolve_storage_engine_name(part_elem->engine_type);
+ create_partition_name(tmp_path, path,
+ part_elem->partition_name,
+ TEMP_PART_NAME, TRUE);
+ create_partition_name(normal_path, path,
+ part_elem->partition_name,
+ NORMAL_PART_NAME, TRUE);
+ ddl_log_entry.name= normal_path;
+ ddl_log_entry.from_name= tmp_path;
+ if (part_elem->part_state == PART_IS_CHANGED)
+ ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION;
+ else
+ ddl_log_entry.action_type= DDL_LOG_RENAME_ACTION;
+ if (write_ddl_log_entry(&ddl_log_entry, &log_entry))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ *next_entry= log_entry->entry_pos;
+ part_elem->log_entry= log_entry;
+ insert_part_info_log_entry_list(part_info, log_entry);
+ }
+ }
+ } while (++i < num_elements);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Log dropped partitions
+ SYNOPSIS
+ write_log_dropped_partitions()
+ lpt Struct containing parameters
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+*/
+
+static bool write_log_dropped_partitions(ALTER_PARTITION_PARAM_TYPE *lpt,
+ uint *next_entry,
+ const char *path,
+ bool temp_list)
+{
+ DDL_LOG_ENTRY ddl_log_entry;
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ char tmp_path[FN_LEN];
+ List_iterator<partition_element> part_it(part_info->partitions);
+ List_iterator<partition_element> temp_it(part_info->temp_partitions);
+ uint num_temp_partitions= part_info->temp_partitions.elements;
+ uint num_elements= part_info->partitions.elements;
+ DBUG_ENTER("write_log_dropped_partitions");
+
+ ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION;
+ if (temp_list)
+ num_elements= num_temp_partitions;
+ while (num_elements--)
+ {
+ partition_element *part_elem;
+ if (temp_list)
+ part_elem= temp_it++;
+ else
+ part_elem= part_it++;
+ if (part_elem->part_state == PART_TO_BE_DROPPED ||
+ part_elem->part_state == PART_TO_BE_ADDED ||
+ part_elem->part_state == PART_CHANGED)
+ {
+ uint name_variant;
+ if (part_elem->part_state == PART_CHANGED ||
+ (part_elem->part_state == PART_TO_BE_ADDED &&
+ num_temp_partitions))
+ name_variant= TEMP_PART_NAME;
+ else
+ name_variant= NORMAL_PART_NAME;
+ if (part_info->is_sub_partitioned())
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ uint num_subparts= part_info->num_subparts;
+ uint j= 0;
+ do
+ {
+ partition_element *sub_elem= sub_it++;
+ ddl_log_entry.next_entry= *next_entry;
+ ddl_log_entry.handler_name=
+ ha_resolve_storage_engine_name(sub_elem->engine_type);
+ create_subpartition_name(tmp_path, path,
+ part_elem->partition_name,
+ sub_elem->partition_name,
+ name_variant);
+ ddl_log_entry.name= tmp_path;
+ if (write_ddl_log_entry(&ddl_log_entry, &log_entry))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ *next_entry= log_entry->entry_pos;
+ sub_elem->log_entry= log_entry;
+ insert_part_info_log_entry_list(part_info, log_entry);
+ } while (++j < num_subparts);
+ }
+ else
+ {
+ ddl_log_entry.next_entry= *next_entry;
+ ddl_log_entry.handler_name=
+ ha_resolve_storage_engine_name(part_elem->engine_type);
+ create_partition_name(tmp_path, path,
+ part_elem->partition_name,
+ name_variant, TRUE);
+ ddl_log_entry.name= tmp_path;
+ if (write_ddl_log_entry(&ddl_log_entry, &log_entry))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ *next_entry= log_entry->entry_pos;
+ part_elem->log_entry= log_entry;
+ insert_part_info_log_entry_list(part_info, log_entry);
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Set execute log entry in ddl log for this partitioned table
+ SYNOPSIS
+ set_part_info_exec_log_entry()
+ part_info Partition info object
+ exec_log_entry Log entry
+ RETURN VALUES
+ NONE
+*/
+
+static void set_part_info_exec_log_entry(partition_info *part_info,
+ DDL_LOG_MEMORY_ENTRY *exec_log_entry)
+{
+ part_info->exec_log_entry= exec_log_entry;
+ exec_log_entry->next_active_log_entry= NULL;
+}
+
+
+/*
+ Write the log entry to ensure that the shadow frm file is removed at
+ crash.
+ SYNOPSIS
+ write_log_drop_shadow_frm()
+ lpt Struct containing parameters
+ install_frm Should we log action to install shadow frm or should
+ the action be to remove the shadow frm file.
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ Prepare an entry to the ddl log indicating a drop/install of the shadow frm
+ file and its corresponding handler file.
+*/
+
+static bool write_log_drop_shadow_frm(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL;
+ char shadow_path[FN_REFLEN + 1];
+ DBUG_ENTER("write_log_drop_shadow_frm");
+
+ build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
+ mysql_mutex_lock(&LOCK_gdl);
+ if (write_log_replace_delete_frm(lpt, 0UL, NULL,
+ (const char*)shadow_path, FALSE))
+ goto error;
+ log_entry= part_info->first_log_entry;
+ if (write_execute_ddl_log_entry(log_entry->entry_pos,
+ FALSE, &exec_log_entry))
+ goto error;
+ mysql_mutex_unlock(&LOCK_gdl);
+ set_part_info_exec_log_entry(part_info, exec_log_entry);
+ DBUG_RETURN(FALSE);
+
+error:
+ release_part_info_log_entries(part_info->first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ part_info->first_log_entry= NULL;
+ my_error(ER_DDL_LOG_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Log renaming of shadow frm to real frm name and dropping of old frm
+ SYNOPSIS
+ write_log_rename_frm()
+ lpt Struct containing parameters
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ Prepare an entry to ensure that we complete the renaming of the frm
+ file if failure occurs in the middle of the rename process.
+*/
+
+static bool write_log_rename_frm(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
+ char path[FN_REFLEN + 1];
+ char shadow_path[FN_REFLEN + 1];
+ DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
+ DBUG_ENTER("write_log_rename_frm");
+
+ part_info->first_log_entry= NULL;
+ build_table_filename(path, sizeof(path) - 1, lpt->db,
+ lpt->table_name, "", 0);
+ build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
+ mysql_mutex_lock(&LOCK_gdl);
+ if (write_log_replace_delete_frm(lpt, 0UL, shadow_path, path, TRUE))
+ goto error;
+ log_entry= part_info->first_log_entry;
+ part_info->frm_log_entry= log_entry;
+ if (write_execute_ddl_log_entry(log_entry->entry_pos,
+ FALSE, &exec_log_entry))
+ goto error;
+ release_part_info_log_entries(old_first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ DBUG_RETURN(FALSE);
+
+error:
+ release_part_info_log_entries(part_info->first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ part_info->first_log_entry= old_first_log_entry;
+ part_info->frm_log_entry= NULL;
+ my_error(ER_DDL_LOG_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Write the log entries to ensure that the drop partition command is completed
+ even in the presence of a crash.
+
+ SYNOPSIS
+ write_log_drop_partition()
+ lpt Struct containing parameters
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ Prepare entries to the ddl log indicating all partitions to drop and to
+ install the shadow frm file and remove the old frm file.
+*/
+
+static bool write_log_drop_partition(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
+ char tmp_path[FN_REFLEN + 1];
+ char path[FN_REFLEN + 1];
+ uint next_entry= 0;
+ DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
+ DBUG_ENTER("write_log_drop_partition");
+
+ part_info->first_log_entry= NULL;
+ build_table_filename(path, sizeof(path) - 1, lpt->db,
+ lpt->table_name, "", 0);
+ build_table_shadow_filename(tmp_path, sizeof(tmp_path) - 1, lpt);
+ mysql_mutex_lock(&LOCK_gdl);
+ if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path,
+ FALSE))
+ goto error;
+ if (write_log_replace_delete_frm(lpt, next_entry, (const char*)tmp_path,
+ (const char*)path, TRUE))
+ goto error;
+ log_entry= part_info->first_log_entry;
+ part_info->frm_log_entry= log_entry;
+ if (write_execute_ddl_log_entry(log_entry->entry_pos,
+ FALSE, &exec_log_entry))
+ goto error;
+ release_part_info_log_entries(old_first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ DBUG_RETURN(FALSE);
+
+error:
+ release_part_info_log_entries(part_info->first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ part_info->first_log_entry= old_first_log_entry;
+ part_info->frm_log_entry= NULL;
+ my_error(ER_DDL_LOG_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Write the log entries to ensure that the add partition command is not
+ executed at all if a crash before it has completed
+
+ SYNOPSIS
+ write_log_add_change_partition()
+ lpt Struct containing parameters
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ Prepare entries to the ddl log indicating all partitions to drop and to
+ remove the shadow frm file.
+ We always inject entries backwards in the list in the ddl log since we
+ don't know the entry position until we have written it.
+*/
+
+static bool write_log_add_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
+ char tmp_path[FN_REFLEN + 1];
+ char path[FN_REFLEN + 1];
+ uint next_entry= 0;
+ DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
+ /* write_log_drop_shadow_frm(lpt) must have been run first */
+ DBUG_ASSERT(old_first_log_entry);
+ DBUG_ENTER("write_log_add_change_partition");
+
+ build_table_filename(path, sizeof(path) - 1, lpt->db,
+ lpt->table_name, "", 0);
+ build_table_shadow_filename(tmp_path, sizeof(tmp_path) - 1, lpt);
+ mysql_mutex_lock(&LOCK_gdl);
+
+ /* Relink the previous drop shadow frm entry */
+ if (old_first_log_entry)
+ next_entry= old_first_log_entry->entry_pos;
+ if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path,
+ FALSE))
+ goto error;
+ log_entry= part_info->first_log_entry;
+
+ if (write_execute_ddl_log_entry(log_entry->entry_pos,
+ FALSE,
+ /* Reuse the old execute ddl_log_entry */
+ &exec_log_entry))
+ goto error;
+ mysql_mutex_unlock(&LOCK_gdl);
+ set_part_info_exec_log_entry(part_info, exec_log_entry);
+ DBUG_RETURN(FALSE);
+
+error:
+ release_part_info_log_entries(part_info->first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ part_info->first_log_entry= old_first_log_entry;
+ my_error(ER_DDL_LOG_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Write description of how to complete the operation after first phase of
+ change partitions.
+
+ SYNOPSIS
+ write_log_final_change_partition()
+ lpt Struct containing parameters
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+ DESCRIPTION
+ We will write log entries that specify to
+ 1) Install the shadow frm file.
+ 2) Remove all partitions reorganized. (To be able to reorganize a partition
+ to the same name. Like in REORGANIZE p0 INTO (p0, p1),
+ so that the later rename from the new p0-temporary name to p0 don't
+ fail because the partition already exists.
+ 3) Rename others to reflect the new naming scheme.
+
+ Note that it is written in the ddl log in reverse.
+*/
+
+static bool write_log_final_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry;
+ DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
+ char path[FN_REFLEN + 1];
+ char shadow_path[FN_REFLEN + 1];
+ DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
+ uint next_entry= 0;
+ DBUG_ENTER("write_log_final_change_partition");
+
+ /*
+ Do not link any previous log entry.
+ Replace the revert operations with forced retry operations.
+ */
+ part_info->first_log_entry= NULL;
+ build_table_filename(path, sizeof(path) - 1, lpt->db,
+ lpt->table_name, "", 0);
+ build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
+ mysql_mutex_lock(&LOCK_gdl);
+ if (write_log_changed_partitions(lpt, &next_entry, (const char*)path))
+ goto error;
+ if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path,
+ lpt->alter_info->flags &
+ Alter_info::ALTER_REORGANIZE_PARTITION))
+ goto error;
+ if (write_log_replace_delete_frm(lpt, next_entry, shadow_path, path, TRUE))
+ goto error;
+ log_entry= part_info->first_log_entry;
+ part_info->frm_log_entry= log_entry;
+ /* Overwrite the revert execute log entry with this retry execute entry */
+ if (write_execute_ddl_log_entry(log_entry->entry_pos,
+ FALSE, &exec_log_entry))
+ goto error;
+ release_part_info_log_entries(old_first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ DBUG_RETURN(FALSE);
+
+error:
+ release_part_info_log_entries(part_info->first_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ part_info->first_log_entry= old_first_log_entry;
+ part_info->frm_log_entry= NULL;
+ my_error(ER_DDL_LOG_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Remove entry from ddl log and release resources for others to use
+
+ SYNOPSIS
+ write_log_completed()
+ lpt Struct containing parameters
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+*/
+
+static void write_log_completed(ALTER_PARTITION_PARAM_TYPE *lpt,
+ bool dont_crash)
+{
+ partition_info *part_info= lpt->part_info;
+ DDL_LOG_MEMORY_ENTRY *log_entry= part_info->exec_log_entry;
+ DBUG_ENTER("write_log_completed");
+
+ DBUG_ASSERT(log_entry);
+ mysql_mutex_lock(&LOCK_gdl);
+ if (write_execute_ddl_log_entry(0UL, TRUE, &log_entry))
+ {
+ /*
+ Failed to write, Bad...
+ We have completed the operation but have log records to REMOVE
+ stuff that shouldn't be removed. What clever things could one do
+ here? An error output was written to the error output by the
+ above method so we don't do anything here.
+ */
+ ;
+ }
+ release_part_info_log_entries(part_info->first_log_entry);
+ release_part_info_log_entries(part_info->exec_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ part_info->exec_log_entry= NULL;
+ part_info->first_log_entry= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Release all log entries
+ SYNOPSIS
+ release_log_entries()
+ part_info Partition info struct
+ RETURN VALUES
+ NONE
+*/
+
+static void release_log_entries(partition_info *part_info)
+{
+ mysql_mutex_lock(&LOCK_gdl);
+ release_part_info_log_entries(part_info->first_log_entry);
+ release_part_info_log_entries(part_info->exec_log_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ part_info->first_log_entry= NULL;
+ part_info->exec_log_entry= NULL;
+}
+
+
+/*
+ Final part of partition changes to handle things when under
+ LOCK TABLES.
+ SYNPOSIS
+ alter_partition_lock_handling()
+ lpt Struct carrying parameters
+ RETURN VALUES
+ NONE
+*/
+static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ THD *thd= lpt->thd;
+
+ if (lpt->table)
+ {
+ /*
+ Remove all instances of the table and its locks and other resources.
+ */
+ close_all_tables_for_name(thd, lpt->table->s, HA_EXTRA_NOT_USED, NULL);
+ }
+ lpt->table= 0;
+ lpt->table_list->table= 0;
+ if (thd->locked_tables_mode)
+ {
+ Diagnostics_area *stmt_da= NULL;
+ Diagnostics_area tmp_stmt_da(true);
+
+ if (thd->is_error())
+ {
+ /* reopen might fail if we have a previous error, use a temporary da. */
+ stmt_da= thd->get_stmt_da();
+ thd->set_stmt_da(&tmp_stmt_da);
+ }
+
+ if (thd->locked_tables_list.reopen_tables(thd))
+ sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE");
+
+ if (stmt_da)
+ thd->set_stmt_da(stmt_da);
+ }
+}
+
+
+/**
+ Unlock and close table before renaming and dropping partitions.
+
+ @param lpt Struct carrying parameters
+
+ @return Always 0.
+*/
+
+static int alter_close_table(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ DBUG_ENTER("alter_close_table");
+
+ if (lpt->table->db_stat)
+ {
+ mysql_lock_remove(lpt->thd, lpt->thd->lock, lpt->table);
+ lpt->table->file->ha_close();
+ lpt->table->db_stat= 0; // Mark file closed
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Handle errors for ALTER TABLE for partitioning.
+
+ @param lpt Struct carrying parameters
+ @param action_completed The action must be completed, NOT reverted
+ @param drop_partition Partitions has not been dropped yet
+ @param frm_install The shadow frm-file has not yet been installed
+ @param close_table Table is still open, close it before reverting
+*/
+
+void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt,
+ bool action_completed,
+ bool drop_partition,
+ bool frm_install,
+ bool close_table)
+{
+ partition_info *part_info= lpt->part_info;
+ THD *thd= lpt->thd;
+ TABLE *table= lpt->table;
+ DBUG_ENTER("handle_alter_part_error");
+ DBUG_ASSERT(table->m_needs_reopen);
+
+ if (close_table)
+ {
+ /*
+ All instances of this table needs to be closed.
+ Better to do that here, than leave the cleaning up to others.
+ Aquire EXCLUSIVE mdl lock if not already aquired.
+ */
+ if (!thd->mdl_context.is_lock_owner(MDL_key::TABLE, lpt->db,
+ lpt->table_name,
+ MDL_EXCLUSIVE))
+ {
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ {
+ /* At least remove this instance on failure */
+ goto err_exclusive_lock;
+ }
+ }
+ /* Ensure the share is destroyed and reopened. */
+ part_info= lpt->part_info->get_clone();
+ close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
+ }
+ else
+ {
+err_exclusive_lock:
+ /*
+ Temporarily remove it from the locked table list, so that it will get
+ reopened.
+ */
+ thd->locked_tables_list.unlink_from_list(thd,
+ table->pos_in_locked_tables,
+ false);
+ /*
+ Make sure that the table is unlocked, closed and removed from
+ the table cache.
+ */
+ mysql_lock_remove(thd, thd->lock, table);
+ part_info= lpt->part_info->get_clone();
+ close_thread_table(thd, &thd->open_tables);
+ lpt->table_list->table= NULL;
+ }
+
+ if (part_info->first_log_entry &&
+ execute_ddl_log_entry(thd, part_info->first_log_entry->entry_pos))
+ {
+ /*
+ We couldn't recover from error, most likely manual interaction
+ is required.
+ */
+ write_log_completed(lpt, FALSE);
+ release_log_entries(part_info);
+ if (!action_completed)
+ {
+ if (drop_partition)
+ {
+ /* Table is still ok, but we left a shadow frm file behind. */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1,
+ "%s %s",
+ "Operation was unsuccessful, table is still intact,",
+ "but it is possible that a shadow frm file was left behind");
+ }
+ else
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1,
+ "%s %s %s %s",
+ "Operation was unsuccessful, table is still intact,",
+ "but it is possible that a shadow frm file was left behind.",
+ "It is also possible that temporary partitions are left behind,",
+ "these could be empty or more or less filled with records");
+ }
+ }
+ else
+ {
+ if (frm_install)
+ {
+ /*
+ Failed during install of shadow frm file, table isn't intact
+ and dropped partitions are still there
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1,
+ "%s %s %s",
+ "Failed during alter of partitions, table is no longer intact.",
+ "The frm file is in an unknown state, and a backup",
+ "is required.");
+ }
+ else if (drop_partition)
+ {
+ /*
+ Table is ok, we have switched to new table but left dropped
+ partitions still in their places. We remove the log records and
+ ask the user to perform the action manually. We remove the log
+ records and ask the user to perform the action manually.
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1,
+ "%s %s",
+ "Failed during drop of partitions, table is intact.",
+ "Manual drop of remaining partitions is required");
+ }
+ else
+ {
+ /*
+ We failed during renaming of partitions. The table is most
+ certainly in a very bad state so we give user warning and disable
+ the table by writing an ancient frm version into it.
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1,
+ "%s %s %s",
+ "Failed during renaming of partitions. We are now in a position",
+ "where table is not reusable",
+ "Table is disabled by writing ancient frm file version into it");
+ }
+ }
+ }
+ else
+ {
+ release_log_entries(part_info);
+ if (!action_completed)
+ {
+ /*
+ We hit an error before things were completed but managed
+ to recover from the error. An error occurred and we have
+ restored things to original so no need for further action.
+ */
+ ;
+ }
+ else
+ {
+ /*
+ We hit an error after we had completed most of the operation
+ and were successful in a second attempt so the operation
+ actually is successful now. We need to issue a warning that
+ even though we reported an error the operation was successfully
+ completed.
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1,"%s %s",
+ "Operation was successfully completed by failure handling,",
+ "after failure of normal operation");
+ }
+ }
+
+ if (thd->locked_tables_mode)
+ {
+ Diagnostics_area *stmt_da= NULL;
+ Diagnostics_area tmp_stmt_da(true);
+
+ if (thd->is_error())
+ {
+ /* reopen might fail if we have a previous error, use a temporary da. */
+ stmt_da= thd->get_stmt_da();
+ thd->set_stmt_da(&tmp_stmt_da);
+ }
+
+ if (thd->locked_tables_list.reopen_tables(thd))
+ sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE");
+
+ if (stmt_da)
+ thd->set_stmt_da(stmt_da);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Downgrade an exclusive MDL lock if under LOCK TABLE.
+
+ If we don't downgrade the lock, it will not be downgraded or released
+ until the table is unlocked, resulting in blocking other threads using
+ the table.
+*/
+
+static void downgrade_mdl_if_lock_tables_mode(THD *thd, MDL_ticket *ticket,
+ enum_mdl_type type)
+{
+ if (thd->locked_tables_mode)
+ ticket->downgrade_lock(type);
+}
+
+
+/**
+ Actually perform the change requested by ALTER TABLE of partitions
+ previously prepared.
+
+ @param thd Thread object
+ @param table Original table object with new part_info
+ @param alter_info ALTER TABLE info
+ @param create_info Create info for CREATE TABLE
+ @param table_list List of the table involved
+ @param db Database name of new table
+ @param table_name Table name of new table
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+
+ @note
+ Perform all ALTER TABLE operations for partitioned tables that can be
+ performed fast without a full copy of the original table.
+*/
+
+uint fast_alter_partition_table(THD *thd, TABLE *table,
+ Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ TABLE_LIST *table_list,
+ char *db,
+ const char *table_name)
+{
+ /* Set-up struct used to write frm files */
+ partition_info *part_info;
+ ALTER_PARTITION_PARAM_TYPE lpt_obj;
+ ALTER_PARTITION_PARAM_TYPE *lpt= &lpt_obj;
+ bool action_completed= FALSE;
+ bool close_table_on_failure= FALSE;
+ bool frm_install= FALSE;
+ MDL_ticket *mdl_ticket= table->mdl_ticket;
+ DBUG_ENTER("fast_alter_partition_table");
+ DBUG_ASSERT(table->m_needs_reopen);
+
+ part_info= table->part_info;
+ lpt->thd= thd;
+ lpt->table_list= table_list;
+ lpt->part_info= part_info;
+ lpt->alter_info= alter_info;
+ lpt->create_info= create_info;
+ lpt->db_options= create_info->table_options;
+ if (create_info->row_type == ROW_TYPE_DYNAMIC)
+ lpt->db_options|= HA_OPTION_PACK_RECORD;
+ lpt->table= table;
+ lpt->key_info_buffer= 0;
+ lpt->key_count= 0;
+ lpt->db= db;
+ lpt->table_name= table_name;
+ lpt->copied= 0;
+ lpt->deleted= 0;
+ lpt->pack_frm_data= NULL;
+ lpt->pack_frm_len= 0;
+
+ if (table->file->alter_table_flags(alter_info->flags) &
+ HA_PARTITION_ONE_PHASE)
+ {
+ /*
+ In the case where the engine supports one phase online partition
+ changes it is not necessary to have any exclusive locks. The
+ correctness is upheld instead by transactions being aborted if they
+ access the table after its partition definition has changed (if they
+ are still using the old partition definition).
+
+ The handler is in this case responsible to ensure that all users
+ start using the new frm file after it has changed. To implement
+ one phase it is necessary for the handler to have the master copy
+ of the frm file and use discovery mechanisms to renew it. Thus
+ write frm will write the frm, pack the new frm and finally
+ the frm is deleted and the discovery mechanisms will either restore
+ back to the old or installing the new after the change is activated.
+
+ Thus all open tables will be discovered that they are old, if not
+ earlier as soon as they try an operation using the old table. One
+ should ensure that this is checked already when opening a table,
+ even if it is found in the cache of open tables.
+
+ change_partitions will perform all operations and it is the duty of
+ the handler to ensure that the frm files in the system gets updated
+ in synch with the changes made and if an error occurs that a proper
+ error handling is done.
+
+ If the MySQL Server crashes at this moment but the handler succeeds
+ in performing the change then the binlog is not written for the
+ change. There is no way to solve this as long as the binlog is not
+ transactional and even then it is hard to solve it completely.
+
+ The first approach here was to downgrade locks. Now a different approach
+ is decided upon. The idea is that the handler will have access to the
+ Alter_info when store_lock arrives with TL_WRITE_ALLOW_READ. So if the
+ handler knows that this functionality can be handled with a lower lock
+ level it will set the lock level to TL_WRITE_ALLOW_WRITE immediately.
+ Thus the need to downgrade the lock disappears.
+ 1) Write the new frm, pack it and then delete it
+ 2) Perform the change within the handler
+ */
+ if (mysql_write_frm(lpt, WFRM_WRITE_SHADOW | WFRM_PACK_FRM) ||
+ mysql_change_partitions(lpt))
+ {
+ goto err;
+ }
+ }
+ else if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION)
+ {
+ /*
+ Now after all checks and setting state on dropped partitions we can
+ start the actual dropping of the partitions.
+
+ Drop partition is actually two things happening. The first is that
+ a lot of records are deleted. The second is that the behaviour of
+ subsequent updates and writes and deletes will change. The delete
+ part can be handled without any particular high lock level by
+ transactional engines whereas non-transactional engines need to
+ ensure that this change is done with an exclusive lock on the table.
+ The second part, the change of partitioning does however require
+ an exclusive lock to install the new partitioning as one atomic
+ operation. If this is not the case, it is possible for two
+ transactions to see the change in a different order than their
+ serialisation order. Thus we need an exclusive lock for both
+ transactional and non-transactional engines.
+
+ For LIST partitions it could be possible to avoid the exclusive lock
+ (and for RANGE partitions if they didn't rearrange range definitions
+ after a DROP PARTITION) if one ensured that failed accesses to the
+ dropped partitions was aborted for sure (thus only possible for
+ transactional engines).
+
+ 0) Write an entry that removes the shadow frm file if crash occurs
+ 1) Write the new frm file as a shadow frm
+ 2) Get an exclusive metadata lock on the table (waits for all active
+ transactions using this table). This ensures that we
+ can release all other locks on the table and since no one can open
+ the table, there can be no new threads accessing the table. They
+ will be hanging on this exclusive lock.
+ 3) Write the ddl log to ensure that the operation is completed
+ even in the presence of a MySQL Server crash (the log is executed
+ before any other threads are started, so there are no locking issues).
+ 4) Close the table that have already been opened but didn't stumble on
+ the abort locked previously. This is done as part of the
+ alter_close_table call.
+ 5) Write the bin log
+ Unfortunately the writing of the binlog is not synchronised with
+ other logging activities. So no matter in which order the binlog
+ is written compared to other activities there will always be cases
+ where crashes make strange things occur. In this placement it can
+ happen that the ALTER TABLE DROP PARTITION gets performed in the
+ master but not in the slaves if we have a crash, after writing the
+ ddl log but before writing the binlog. A solution to this would
+ require writing the statement first in the ddl log and then
+ when recovering from the crash read the binlog and insert it into
+ the binlog if not written already.
+ 6) Install the previously written shadow frm file
+ 7) Prepare handlers for drop of partitions
+ 8) Drop the partitions
+ 9) Remove entries from ddl log
+ 10) Reopen table if under lock tables
+ 11) Complete query
+
+ We insert Error injections at all places where it could be interesting
+ to test if recovery is properly done.
+ */
+ if (write_log_drop_shadow_frm(lpt) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_1") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_1") ||
+ mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_2") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_2") ||
+ wait_while_table_is_used(thd, table, HA_EXTRA_NOT_USED) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_3") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_3") ||
+ (close_table_on_failure= TRUE, FALSE) ||
+ write_log_drop_partition(lpt) ||
+ (action_completed= TRUE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_4") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_4") ||
+ alter_close_table(lpt) ||
+ (close_table_on_failure= FALSE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_5") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_5") ||
+ ((!thd->lex->no_write_to_binlog) &&
+ (write_bin_log(thd, FALSE,
+ thd->query(), thd->query_length()), FALSE)) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_6") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_6") ||
+ (frm_install= TRUE, FALSE) ||
+ mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) ||
+ (frm_install= FALSE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_7") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_7") ||
+ mysql_drop_partitions(lpt) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_8") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_8") ||
+ (write_log_completed(lpt, FALSE), FALSE) ||
+ ERROR_INJECT_CRASH("crash_drop_partition_9") ||
+ ERROR_INJECT_ERROR("fail_drop_partition_9") ||
+ (alter_partition_lock_handling(lpt), FALSE))
+ {
+ handle_alter_part_error(lpt, action_completed, TRUE, frm_install,
+ close_table_on_failure);
+ goto err;
+ }
+ }
+ else if ((alter_info->flags & Alter_info::ALTER_ADD_PARTITION) &&
+ (part_info->part_type == RANGE_PARTITION ||
+ part_info->part_type == LIST_PARTITION))
+ {
+ /*
+ ADD RANGE/LIST PARTITIONS
+ In this case there are no tuples removed and no tuples are added.
+ Thus the operation is merely adding a new partition. Thus it is
+ necessary to perform the change as an atomic operation. Otherwise
+ someone reading without seeing the new partition could potentially
+ miss updates made by a transaction serialised before it that are
+ inserted into the new partition.
+
+ 0) Write an entry that removes the shadow frm file if crash occurs
+ 1) Write the new frm file as a shadow frm file
+ 2) Get an exclusive metadata lock on the table (waits for all active
+ transactions using this table). This ensures that we
+ can release all other locks on the table and since no one can open
+ the table, there can be no new threads accessing the table. They
+ will be hanging on this exclusive lock.
+ 3) Write an entry to remove the new parttions if crash occurs
+ 4) Add the new partitions.
+ 5) Close all instances of the table and remove them from the table cache.
+ 6) Write binlog
+ 7) Now the change is completed except for the installation of the
+ new frm file. We thus write an action in the log to change to
+ the shadow frm file
+ 8) Install the new frm file of the table where the partitions are
+ added to the table.
+ 9) Remove entries from ddl log
+ 10)Reopen tables if under lock tables
+ 11)Complete query
+ */
+ if (write_log_drop_shadow_frm(lpt) ||
+ ERROR_INJECT_CRASH("crash_add_partition_1") ||
+ ERROR_INJECT_ERROR("fail_add_partition_1") ||
+ mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
+ ERROR_INJECT_CRASH("crash_add_partition_2") ||
+ ERROR_INJECT_ERROR("fail_add_partition_2") ||
+ wait_while_table_is_used(thd, table, HA_EXTRA_NOT_USED) ||
+ ERROR_INJECT_CRASH("crash_add_partition_3") ||
+ ERROR_INJECT_ERROR("fail_add_partition_3") ||
+ (close_table_on_failure= TRUE, FALSE) ||
+ write_log_add_change_partition(lpt) ||
+ ERROR_INJECT_CRASH("crash_add_partition_4") ||
+ ERROR_INJECT_ERROR("fail_add_partition_4") ||
+ mysql_change_partitions(lpt) ||
+ ERROR_INJECT_CRASH("crash_add_partition_5") ||
+ ERROR_INJECT_ERROR("fail_add_partition_5") ||
+ (close_table_on_failure= FALSE, FALSE) ||
+ alter_close_table(lpt) ||
+ ERROR_INJECT_CRASH("crash_add_partition_6") ||
+ ERROR_INJECT_ERROR("fail_add_partition_6") ||
+ ((!thd->lex->no_write_to_binlog) &&
+ (write_bin_log(thd, FALSE,
+ thd->query(), thd->query_length()), FALSE)) ||
+ ERROR_INJECT_CRASH("crash_add_partition_7") ||
+ ERROR_INJECT_ERROR("fail_add_partition_7") ||
+ write_log_rename_frm(lpt) ||
+ (action_completed= TRUE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_add_partition_8") ||
+ ERROR_INJECT_ERROR("fail_add_partition_8") ||
+ (frm_install= TRUE, FALSE) ||
+ mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) ||
+ (frm_install= FALSE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_add_partition_9") ||
+ ERROR_INJECT_ERROR("fail_add_partition_9") ||
+ (write_log_completed(lpt, FALSE), FALSE) ||
+ ERROR_INJECT_CRASH("crash_add_partition_10") ||
+ ERROR_INJECT_ERROR("fail_add_partition_10") ||
+ (alter_partition_lock_handling(lpt), FALSE))
+ {
+ handle_alter_part_error(lpt, action_completed, FALSE, frm_install,
+ close_table_on_failure);
+ goto err;
+ }
+ }
+ else
+ {
+ /*
+ ADD HASH PARTITION/
+ COALESCE PARTITION/
+ REBUILD PARTITION/
+ REORGANIZE PARTITION
+
+ In this case all records are still around after the change although
+ possibly organised into new partitions, thus by ensuring that all
+ updates go to both the old and the new partitioning scheme we can
+ actually perform this operation lock-free. The only exception to
+ this is when REORGANIZE PARTITION adds/drops ranges. In this case
+ there needs to be an exclusive lock during the time when the range
+ changes occur.
+ This is only possible if the handler can ensure double-write for a
+ period. The double write will ensure that it doesn't matter where the
+ data is read from since both places are updated for writes. If such
+ double writing is not performed then it is necessary to perform the
+ change with the usual exclusive lock. With double writes it is even
+ possible to perform writes in parallel with the reorganisation of
+ partitions.
+
+ Without double write procedure we get the following procedure.
+ The only difference with using double write is that we can downgrade
+ the lock to TL_WRITE_ALLOW_WRITE. Double write in this case only
+ double writes from old to new. If we had double writing in both
+ directions we could perform the change completely without exclusive
+ lock for HASH partitions.
+ Handlers that perform double writing during the copy phase can actually
+ use a lower lock level. This can be handled inside store_lock in the
+ respective handler.
+
+ 0) Write an entry that removes the shadow frm file if crash occurs.
+ 1) Write the shadow frm file of new partitioning.
+ 2) Log such that temporary partitions added in change phase are
+ removed in a crash situation.
+ 3) Add the new partitions.
+ Copy from the reorganised partitions to the new partitions.
+ 4) Get an exclusive metadata lock on the table (waits for all active
+ transactions using this table). This ensures that we
+ can release all other locks on the table and since no one can open
+ the table, there can be no new threads accessing the table. They
+ will be hanging on this exclusive lock.
+ 5) Close the table.
+ 6) Log that operation is completed and log all complete actions
+ needed to complete operation from here.
+ 7) Write bin log.
+ 8) Prepare handlers for rename and delete of partitions.
+ 9) Rename and drop the reorged partitions such that they are no
+ longer used and rename those added to their real new names.
+ 10) Install the shadow frm file.
+ 11) Reopen the table if under lock tables.
+ 12) Complete query.
+ */
+ if (write_log_drop_shadow_frm(lpt) ||
+ ERROR_INJECT_CRASH("crash_change_partition_1") ||
+ ERROR_INJECT_ERROR("fail_change_partition_1") ||
+ mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
+ ERROR_INJECT_CRASH("crash_change_partition_2") ||
+ ERROR_INJECT_ERROR("fail_change_partition_2") ||
+ (close_table_on_failure= TRUE, FALSE) ||
+ write_log_add_change_partition(lpt) ||
+ ERROR_INJECT_CRASH("crash_change_partition_3") ||
+ ERROR_INJECT_ERROR("fail_change_partition_3") ||
+ mysql_change_partitions(lpt) ||
+ ERROR_INJECT_CRASH("crash_change_partition_4") ||
+ ERROR_INJECT_ERROR("fail_change_partition_4") ||
+ wait_while_table_is_used(thd, table, HA_EXTRA_NOT_USED) ||
+ ERROR_INJECT_CRASH("crash_change_partition_5") ||
+ ERROR_INJECT_ERROR("fail_change_partition_5") ||
+ alter_close_table(lpt) ||
+ (close_table_on_failure= FALSE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_change_partition_6") ||
+ ERROR_INJECT_ERROR("fail_change_partition_6") ||
+ write_log_final_change_partition(lpt) ||
+ (action_completed= TRUE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_change_partition_7") ||
+ ERROR_INJECT_ERROR("fail_change_partition_7") ||
+ ((!thd->lex->no_write_to_binlog) &&
+ (write_bin_log(thd, FALSE,
+ thd->query(), thd->query_length()), FALSE)) ||
+ ERROR_INJECT_CRASH("crash_change_partition_8") ||
+ ERROR_INJECT_ERROR("fail_change_partition_8") ||
+ ((frm_install= TRUE), FALSE) ||
+ mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) ||
+ (frm_install= FALSE, FALSE) ||
+ ERROR_INJECT_CRASH("crash_change_partition_9") ||
+ ERROR_INJECT_ERROR("fail_change_partition_9") ||
+ mysql_drop_partitions(lpt) ||
+ ERROR_INJECT_CRASH("crash_change_partition_10") ||
+ ERROR_INJECT_ERROR("fail_change_partition_10") ||
+ mysql_rename_partitions(lpt) ||
+ ERROR_INJECT_CRASH("crash_change_partition_11") ||
+ ERROR_INJECT_ERROR("fail_change_partition_11") ||
+ (write_log_completed(lpt, FALSE), FALSE) ||
+ ERROR_INJECT_CRASH("crash_change_partition_12") ||
+ ERROR_INJECT_ERROR("fail_change_partition_12") ||
+ (alter_partition_lock_handling(lpt), FALSE))
+ {
+ handle_alter_part_error(lpt, action_completed, FALSE, frm_install,
+ close_table_on_failure);
+ goto err;
+ }
+ }
+ downgrade_mdl_if_lock_tables_mode(thd, mdl_ticket, MDL_SHARED_NO_READ_WRITE);
+ /*
+ A final step is to write the query to the binlog and send ok to the
+ user
+ */
+ DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted, table_list));
+err:
+ downgrade_mdl_if_lock_tables_mode(thd, mdl_ticket, MDL_SHARED_NO_READ_WRITE);
+ DBUG_RETURN(TRUE);
+}
+#endif
+
+
+/*
+ Prepare for calling val_int on partition function by setting fields to
+ point to the record where the values of the PF-fields are stored.
+
+ SYNOPSIS
+ set_field_ptr()
+ ptr Array of fields to change ptr
+ new_buf New record pointer
+ old_buf Old record pointer
+
+ DESCRIPTION
+ Set ptr in field objects of field array to refer to new_buf record
+ instead of previously old_buf. Used before calling val_int and after
+ it is used to restore pointers to table->record[0].
+ This routine is placed outside of partition code since it can be useful
+ also for other programs.
+*/
+
+void set_field_ptr(Field **ptr, const uchar *new_buf,
+ const uchar *old_buf)
+{
+ my_ptrdiff_t diff= (new_buf - old_buf);
+ DBUG_ENTER("set_field_ptr");
+
+ do
+ {
+ (*ptr)->move_field_offset(diff);
+ } while (*(++ptr));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Prepare for calling val_int on partition function by setting fields to
+ point to the record where the values of the PF-fields are stored.
+ This variant works on a key_part reference.
+ It is not required that all fields are NOT NULL fields.
+
+ SYNOPSIS
+ set_key_field_ptr()
+ key_info key info with a set of fields to change ptr
+ new_buf New record pointer
+ old_buf Old record pointer
+
+ DESCRIPTION
+ Set ptr in field objects of field array to refer to new_buf record
+ instead of previously old_buf. Used before calling val_int and after
+ it is used to restore pointers to table->record[0].
+ This routine is placed outside of partition code since it can be useful
+ also for other programs.
+*/
+
+void set_key_field_ptr(KEY *key_info, const uchar *new_buf,
+ const uchar *old_buf)
+{
+ KEY_PART_INFO *key_part= key_info->key_part;
+ uint key_parts= key_info->user_defined_key_parts;
+ uint i= 0;
+ my_ptrdiff_t diff= (new_buf - old_buf);
+ DBUG_ENTER("set_key_field_ptr");
+
+ do
+ {
+ key_part->field->move_field_offset(diff);
+ key_part++;
+ } while (++i < key_parts);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ SYNOPSIS
+ mem_alloc_error()
+ size Size of memory attempted to allocate
+ None
+
+ RETURN VALUES
+ None
+
+ DESCRIPTION
+ A routine to use for all the many places in the code where memory
+ allocation error can happen, a tremendous amount of them, needs
+ simple routine that signals this error.
+*/
+
+void mem_alloc_error(size_t size)
+{
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR),
+ static_cast<int>(size));
+}
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+/**
+ Return comma-separated list of used partitions in the provided given string.
+
+ @param part_info Partitioning info
+ @param[out] parts The resulting list of string to fill
+
+ Generate a list of used partitions (from bits in part_info->read_partitions
+ bitmap), and store it into the provided String object.
+
+ @note
+ The produced string must not be longer then MAX_PARTITIONS * (1 + FN_LEN).
+ In case of UPDATE, only the partitions read is given, not the partitions
+ that was written or locked.
+*/
+
+void make_used_partitions_str(partition_info *part_info, String *parts_str)
+{
+ parts_str->length(0);
+ partition_element *pe;
+ uint partition_id= 0;
+ List_iterator<partition_element> it(part_info->partitions);
+
+ if (part_info->is_sub_partitioned())
+ {
+ partition_element *head_pe;
+ while ((head_pe= it++))
+ {
+ List_iterator<partition_element> it2(head_pe->subpartitions);
+ while ((pe= it2++))
+ {
+ if (bitmap_is_set(&part_info->read_partitions, partition_id))
+ {
+ if (parts_str->length())
+ parts_str->append(',');
+ parts_str->append(head_pe->partition_name,
+ strlen(head_pe->partition_name),
+ system_charset_info);
+ parts_str->append('_');
+ parts_str->append(pe->partition_name,
+ strlen(pe->partition_name),
+ system_charset_info);
+ }
+ partition_id++;
+ }
+ }
+ }
+ else
+ {
+ while ((pe= it++))
+ {
+ if (bitmap_is_set(&part_info->read_partitions, partition_id))
+ {
+ if (parts_str->length())
+ parts_str->append(',');
+ parts_str->append(pe->partition_name, strlen(pe->partition_name),
+ system_charset_info);
+ }
+ partition_id++;
+ }
+ }
+}
+#endif
+
+/****************************************************************************
+ * Partition interval analysis support
+ ***************************************************************************/
+
+/*
+ Setup partition_info::* members related to partitioning range analysis
+
+ SYNOPSIS
+ set_up_partition_func_pointers()
+ part_info Partitioning info structure
+
+ DESCRIPTION
+ Assuming that passed partition_info structure already has correct values
+ for members that specify [sub]partitioning type, table fields, and
+ functions, set up partition_info::* members that are related to
+ Partitioning Interval Analysis (see get_partitions_in_range_iter for its
+ definition)
+
+ IMPLEMENTATION
+ There are three available interval analyzer functions:
+ (1) get_part_iter_for_interval_via_mapping
+ (2) get_part_iter_for_interval_cols_via_map
+ (3) get_part_iter_for_interval_via_walking
+
+ They all have limited applicability:
+ (1) is applicable for "PARTITION BY <RANGE|LIST>(func(t.field))", where
+ func is a monotonic function.
+
+ (2) is applicable for "PARTITION BY <RANGE|LIST> COLUMNS (field_list)
+
+ (3) is applicable for
+ "[SUB]PARTITION BY <any-partitioning-type>(any_func(t.integer_field))"
+
+ If both (1) and (3) are applicable, (1) is preferred over (3).
+
+ This function sets part_info::get_part_iter_for_interval according to
+ this criteria, and also sets some auxilary fields that the function
+ uses.
+*/
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+static void set_up_range_analysis_info(partition_info *part_info)
+{
+ /* Set the catch-all default */
+ part_info->get_part_iter_for_interval= NULL;
+ part_info->get_subpart_iter_for_interval= NULL;
+
+ /*
+ Check if get_part_iter_for_interval_via_mapping() can be used for
+ partitioning
+ */
+ switch (part_info->part_type) {
+ case RANGE_PARTITION:
+ case LIST_PARTITION:
+ if (!part_info->column_list)
+ {
+ if (part_info->part_expr->get_monotonicity_info() != NON_MONOTONIC)
+ {
+ part_info->get_part_iter_for_interval=
+ get_part_iter_for_interval_via_mapping;
+ goto setup_subparts;
+ }
+ }
+ else
+ {
+ part_info->get_part_iter_for_interval=
+ get_part_iter_for_interval_cols_via_map;
+ goto setup_subparts;
+ }
+ default:
+ ;
+ }
+
+ /*
+ Check if get_part_iter_for_interval_via_walking() can be used for
+ partitioning
+ */
+ if (part_info->num_part_fields == 1)
+ {
+ Field *field= part_info->part_field_array[0];
+ switch (field->type()) {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ part_info->get_part_iter_for_interval=
+ get_part_iter_for_interval_via_walking;
+ break;
+ default:
+ ;
+ }
+ }
+
+setup_subparts:
+ /*
+ Check if get_part_iter_for_interval_via_walking() can be used for
+ subpartitioning
+ */
+ if (part_info->num_subpart_fields == 1)
+ {
+ Field *field= part_info->subpart_field_array[0];
+ switch (field->type()) {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_LONGLONG:
+ part_info->get_subpart_iter_for_interval=
+ get_part_iter_for_interval_via_walking;
+ break;
+ default:
+ ;
+ }
+ }
+}
+
+
+/*
+ This function takes a memory of packed fields in opt-range format
+ and stores it in record format. To avoid having to worry about how
+ the length of fields are calculated in opt-range format we send
+ an array of lengths used for each field in store_length_array.
+
+ SYNOPSIS
+ store_tuple_to_record()
+ pfield Field array
+ store_length_array Array of field lengths
+ value Memory where fields are stored
+ value_end End of memory
+
+ RETURN VALUE
+ nparts Number of fields assigned
+*/
+uint32 store_tuple_to_record(Field **pfield,
+ uint32 *store_length_array,
+ uchar *value,
+ uchar *value_end)
+{
+ /* This function is inspired by store_key_image_rec. */
+ uint32 nparts= 0;
+ uchar *loc_value;
+ while (value < value_end)
+ {
+ loc_value= value;
+ if ((*pfield)->real_maybe_null())
+ {
+ if (*loc_value)
+ (*pfield)->set_null();
+ else
+ (*pfield)->set_notnull();
+ loc_value++;
+ }
+ uint len= (*pfield)->pack_length();
+ (*pfield)->set_key_image(loc_value, len);
+ value+= *store_length_array;
+ store_length_array++;
+ nparts++;
+ pfield++;
+ }
+ return nparts;
+}
+
+/**
+ RANGE(columns) partitioning: compare partition value bound and probe tuple.
+
+ @param val Partition column values.
+ @param nvals_in_rec Number of (prefix) fields to compare.
+
+ @return Less than/Equal to/Greater than 0 if the record is L/E/G than val.
+
+ @note The partition value bound is always a full tuple (but may include the
+ MAXVALUE special value). The probe tuple may be a prefix of partitioning
+ tuple.
+*/
+
+static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec)
+{
+ partition_info *part_info= val->part_info;
+ Field **field= part_info->part_field_array;
+ Field **fields_end= field + nvals_in_rec;
+ int res;
+
+ for (; field != fields_end; field++, val++)
+ {
+ if (val->max_value)
+ return -1;
+ if ((*field)->is_null())
+ {
+ if (val->null_value)
+ continue;
+ return -1;
+ }
+ if (val->null_value)
+ return +1;
+ res= (*field)->cmp((const uchar*)val->column_value);
+ if (res)
+ return res;
+ }
+ return 0;
+}
+
+
+/**
+ Compare record and columns partition tuple including endpoint handling.
+
+ @param val Columns partition tuple
+ @param n_vals_in_rec Number of columns to compare
+ @param is_left_endpoint True if left endpoint (part_tuple < rec or
+ part_tuple <= rec)
+ @param include_endpoint If endpoint is included (part_tuple <= rec or
+ rec <= part_tuple)
+
+ @return Less than/Equal to/Greater than 0 if the record is L/E/G than
+ the partition tuple.
+
+ @see get_list_array_idx_for_endpoint() and
+ get_partition_id_range_for_endpoint().
+*/
+
+static int cmp_rec_and_tuple_prune(part_column_list_val *val,
+ uint32 n_vals_in_rec,
+ bool is_left_endpoint,
+ bool include_endpoint)
+{
+ int cmp;
+ Field **field;
+ if ((cmp= cmp_rec_and_tuple(val, n_vals_in_rec)))
+ return cmp;
+ field= val->part_info->part_field_array + n_vals_in_rec;
+ if (!(*field))
+ {
+ /* Full match. Only equal if including endpoint. */
+ if (include_endpoint)
+ return 0;
+
+ if (is_left_endpoint)
+ return +4; /* Start of range, part_tuple < rec, return higher. */
+ return -4; /* End of range, rec < part_tupe, return lesser. */
+ }
+ /*
+ The prefix is equal and there are more partition columns to compare.
+
+ If including left endpoint or not including right endpoint
+ then the record is considered lesser compared to the partition.
+
+ i.e:
+ part(10, x) <= rec(10, unknown) and rec(10, unknown) < part(10, x)
+ part <= rec -> lesser (i.e. this or previous partitions)
+ rec < part -> lesser (i.e. this or previous partitions)
+ */
+ if (is_left_endpoint == include_endpoint)
+ return -2;
+
+ /*
+ If right endpoint and the first additional partition value
+ is MAXVALUE, then the record is lesser.
+ */
+ if (!is_left_endpoint && (val + n_vals_in_rec)->max_value)
+ return -3;
+
+ /*
+ Otherwise the record is considered greater.
+
+ rec <= part -> greater (i.e. does not match this partition, seek higher).
+ part < rec -> greater (i.e. does not match this partition, seek higher).
+ */
+ return 2;
+}
+
+
+typedef uint32 (*get_endpoint_func)(partition_info*, bool left_endpoint,
+ bool include_endpoint);
+
+typedef uint32 (*get_col_endpoint_func)(partition_info*, bool left_endpoint,
+ bool include_endpoint,
+ uint32 num_parts);
+
+/**
+ Get partition for RANGE COLUMNS endpoint.
+
+ @param part_info Partitioning metadata.
+ @param is_left_endpoint True if left endpoint (const <=/< cols)
+ @param include_endpoint True if range includes the endpoint (<=/>=)
+ @param nparts Total number of partitions
+
+ @return Partition id of matching partition.
+
+ @see get_partition_id_cols_list_for_endpoint and
+ get_partition_id_range_for_endpoint.
+*/
+
+uint32 get_partition_id_cols_range_for_endpoint(partition_info *part_info,
+ bool is_left_endpoint,
+ bool include_endpoint,
+ uint32 nparts)
+{
+ uint min_part_id= 0, max_part_id= part_info->num_parts, loc_part_id;
+ part_column_list_val *range_col_array= part_info->range_col_array;
+ uint num_columns= part_info->part_field_list.elements;
+ DBUG_ENTER("get_partition_id_cols_range_for_endpoint");
+
+ /* Find the matching partition (including taking endpoint into account). */
+ do
+ {
+ /* Midpoint, adjusted down, so it can never be > last partition. */
+ loc_part_id= (max_part_id + min_part_id) >> 1;
+ if (0 <= cmp_rec_and_tuple_prune(range_col_array +
+ loc_part_id * num_columns,
+ nparts,
+ is_left_endpoint,
+ include_endpoint))
+ min_part_id= loc_part_id + 1;
+ else
+ max_part_id= loc_part_id;
+ } while (max_part_id > min_part_id);
+ loc_part_id= max_part_id;
+
+ /* Given value must be LESS THAN the found partition. */
+ DBUG_ASSERT(loc_part_id == part_info->num_parts ||
+ (0 > cmp_rec_and_tuple_prune(range_col_array +
+ loc_part_id * num_columns,
+ nparts, is_left_endpoint,
+ include_endpoint)));
+ /* Given value must be GREATER THAN or EQUAL to the previous partition. */
+ DBUG_ASSERT(loc_part_id == 0 ||
+ (0 <= cmp_rec_and_tuple_prune(range_col_array +
+ (loc_part_id - 1) * num_columns,
+ nparts, is_left_endpoint,
+ include_endpoint)));
+
+ if (!is_left_endpoint)
+ {
+ /* Set the end after this partition if not already after the last. */
+ if (loc_part_id < part_info->num_parts)
+ loc_part_id++;
+ }
+ DBUG_RETURN(loc_part_id);
+}
+
+
+int get_part_iter_for_interval_cols_via_map(partition_info *part_info,
+ bool is_subpart,
+ uint32 *store_length_array,
+ uchar *min_value, uchar *max_value,
+ uint min_len, uint max_len,
+ uint flags,
+ PARTITION_ITERATOR *part_iter)
+{
+ uint32 nparts;
+ get_col_endpoint_func UNINIT_VAR(get_col_endpoint);
+ DBUG_ENTER("get_part_iter_for_interval_cols_via_map");
+
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ get_col_endpoint= get_partition_id_cols_range_for_endpoint;
+ part_iter->get_next= get_next_partition_id_range;
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+ get_col_endpoint= get_partition_id_cols_list_for_endpoint;
+ part_iter->get_next= get_next_partition_id_list;
+ part_iter->part_info= part_info;
+ DBUG_ASSERT(part_info->num_list_values);
+ }
+ else
+ assert(0);
+
+ if (flags & NO_MIN_RANGE)
+ part_iter->part_nums.start= part_iter->part_nums.cur= 0;
+ else
+ {
+ // Copy from min_value to record
+ nparts= store_tuple_to_record(part_info->part_field_array,
+ store_length_array,
+ min_value,
+ min_value + min_len);
+ part_iter->part_nums.start= part_iter->part_nums.cur=
+ get_col_endpoint(part_info, TRUE, !(flags & NEAR_MIN),
+ nparts);
+ }
+ if (flags & NO_MAX_RANGE)
+ {
+ if (part_info->part_type == RANGE_PARTITION)
+ part_iter->part_nums.end= part_info->num_parts;
+ else /* LIST_PARTITION */
+ {
+ DBUG_ASSERT(part_info->part_type == LIST_PARTITION);
+ part_iter->part_nums.end= part_info->num_list_values;
+ }
+ }
+ else
+ {
+ // Copy from max_value to record
+ nparts= store_tuple_to_record(part_info->part_field_array,
+ store_length_array,
+ max_value,
+ max_value + max_len);
+ part_iter->part_nums.end= get_col_endpoint(part_info, FALSE,
+ !(flags & NEAR_MAX),
+ nparts);
+ }
+ if (part_iter->part_nums.start == part_iter->part_nums.end)
+ DBUG_RETURN(0);
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Partitioning Interval Analysis: Initialize the iterator for "mapping" case
+
+ @param part_info Partition info
+ @param is_subpart TRUE - act for subpartitioning
+ FALSE - act for partitioning
+ @param store_length_array Ignored.
+ @param min_value minimum field value, in opt_range key format.
+ @param max_value minimum field value, in opt_range key format.
+ @param min_len Ignored.
+ @param max_len Ignored.
+ @param flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE,
+ NO_MAX_RANGE.
+ @param part_iter Iterator structure to be initialized
+
+ @details Initialize partition set iterator to walk over the interval in
+ ordered-array-of-partitions (for RANGE partitioning) or
+ ordered-array-of-list-constants (for LIST partitioning) space.
+
+ This function is used when partitioning is done by
+ <RANGE|LIST>(ascending_func(t.field)), and we can map an interval in
+ t.field space into a sub-array of partition_info::range_int_array or
+ partition_info::list_array (see get_partition_id_range_for_endpoint,
+ get_list_array_idx_for_endpoint for details).
+
+ The function performs this interval mapping, and sets the iterator to
+ traverse the sub-array and return appropriate partitions.
+
+ @return Status of iterator
+ @retval 0 No matching partitions (iterator not initialized)
+ @retval 1 Ok, iterator intialized for traversal of matching partitions.
+ @retval -1 All partitions would match (iterator not initialized)
+*/
+
+int get_part_iter_for_interval_via_mapping(partition_info *part_info,
+ bool is_subpart,
+ uint32 *store_length_array, /* ignored */
+ uchar *min_value, uchar *max_value,
+ uint min_len, uint max_len, /* ignored */
+ uint flags,
+ PARTITION_ITERATOR *part_iter)
+{
+ Field *field= part_info->part_field_array[0];
+ uint32 UNINIT_VAR(max_endpoint_val);
+ get_endpoint_func UNINIT_VAR(get_endpoint);
+ bool can_match_multiple_values; /* is not '=' */
+ uint field_len= field->pack_length_in_rec();
+ MYSQL_TIME start_date;
+ bool check_zero_dates= false;
+ bool zero_in_start_date= true;
+ DBUG_ENTER("get_part_iter_for_interval_via_mapping");
+ DBUG_ASSERT(!is_subpart);
+ (void) store_length_array;
+ (void)min_len;
+ (void)max_len;
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= FALSE;
+
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ if (part_info->part_charset_field_array)
+ get_endpoint= get_partition_id_range_for_endpoint_charset;
+ else
+ get_endpoint= get_partition_id_range_for_endpoint;
+ max_endpoint_val= part_info->num_parts;
+ part_iter->get_next= get_next_partition_id_range;
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+
+ if (part_info->part_charset_field_array)
+ get_endpoint= get_list_array_idx_for_endpoint_charset;
+ else
+ get_endpoint= get_list_array_idx_for_endpoint;
+ max_endpoint_val= part_info->num_list_values;
+ part_iter->get_next= get_next_partition_id_list;
+ part_iter->part_info= part_info;
+ if (max_endpoint_val == 0)
+ {
+ /*
+ We handle this special case without optimisations since it is
+ of little practical value but causes a great number of complex
+ checks later in the code.
+ */
+ part_iter->part_nums.start= part_iter->part_nums.end= 0;
+ part_iter->part_nums.cur= 0;
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
+ DBUG_RETURN(-1);
+ }
+ }
+ else
+ MY_ASSERT_UNREACHABLE();
+
+ can_match_multiple_values= (flags || !min_value || !max_value ||
+ memcmp(min_value, max_value, field_len));
+ if (can_match_multiple_values &&
+ (part_info->part_type == RANGE_PARTITION ||
+ part_info->has_null_value))
+ {
+ /* Range scan on RANGE or LIST partitioned table */
+ enum_monotonicity_info monotonic;
+ monotonic= part_info->part_expr->get_monotonicity_info();
+ if (monotonic == MONOTONIC_INCREASING_NOT_NULL ||
+ monotonic == MONOTONIC_STRICT_INCREASING_NOT_NULL)
+ {
+ /* col is NOT NULL, but F(col) can return NULL, add NULL partition */
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
+ check_zero_dates= true;
+ }
+ }
+
+ /*
+ Find minimum: Do special handling if the interval has left bound in form
+ " NULL <= X ":
+ */
+ if (field->real_maybe_null() && part_info->has_null_value &&
+ !(flags & (NO_MIN_RANGE | NEAR_MIN)) && *min_value)
+ {
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
+ part_iter->part_nums.start= part_iter->part_nums.cur= 0;
+ if (!(flags & NO_MAX_RANGE) && *max_value)
+ {
+ /* The right bound is X <= NULL, i.e. it is a "X IS NULL" interval */
+ part_iter->part_nums.end= 0;
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ if (flags & NO_MIN_RANGE)
+ part_iter->part_nums.start= part_iter->part_nums.cur= 0;
+ else
+ {
+ /*
+ Store the interval edge in the record buffer, and call the
+ function that maps the edge in table-field space to an edge
+ in ordered-set-of-partitions (for RANGE partitioning) or
+ index-in-ordered-array-of-list-constants (for LIST) space.
+ */
+ store_key_image_to_rec(field, min_value, field_len);
+ bool include_endp= !MY_TEST(flags & NEAR_MIN);
+ part_iter->part_nums.start= get_endpoint(part_info, 1, include_endp);
+ if (!can_match_multiple_values && part_info->part_expr->null_value)
+ {
+ /* col = x and F(x) = NULL -> only search NULL partition */
+ part_iter->part_nums.cur= part_iter->part_nums.start= 0;
+ part_iter->part_nums.end= 0;
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
+ DBUG_RETURN(1);
+ }
+ part_iter->part_nums.cur= part_iter->part_nums.start;
+ if (check_zero_dates && !part_info->part_expr->null_value)
+ {
+ if (!(flags & NO_MAX_RANGE) &&
+ (field->type() == MYSQL_TYPE_DATE ||
+ field->type() == MYSQL_TYPE_DATETIME))
+ {
+ /* Monotonic, but return NULL for dates with zeros in month/day. */
+ zero_in_start_date= field->get_date(&start_date, 0);
+ DBUG_PRINT("info", ("zero start %u %04d-%02d-%02d",
+ zero_in_start_date, start_date.year,
+ start_date.month, start_date.day));
+ }
+ }
+ if (part_iter->part_nums.start == max_endpoint_val)
+ DBUG_RETURN(0); /* No partitions */
+ }
+ }
+
+ /* Find maximum, do the same as above but for right interval bound */
+ if (flags & NO_MAX_RANGE)
+ part_iter->part_nums.end= max_endpoint_val;
+ else
+ {
+ store_key_image_to_rec(field, max_value, field_len);
+ bool include_endp= !MY_TEST(flags & NEAR_MAX);
+ part_iter->part_nums.end= get_endpoint(part_info, 0, include_endp);
+ if (check_zero_dates &&
+ !zero_in_start_date &&
+ !part_info->part_expr->null_value)
+ {
+ MYSQL_TIME end_date;
+ bool zero_in_end_date= field->get_date(&end_date, 0);
+ /*
+ This is an optimization for TO_DAYS()/TO_SECONDS() to avoid scanning
+ the NULL partition for ranges that cannot include a date with 0 as
+ month/day.
+ */
+ DBUG_PRINT("info", ("zero end %u %04d-%02d-%02d",
+ zero_in_end_date,
+ end_date.year, end_date.month, end_date.day));
+ DBUG_ASSERT(!memcmp(((Item_func*) part_info->part_expr)->func_name(),
+ "to_days", 7) ||
+ !memcmp(((Item_func*) part_info->part_expr)->func_name(),
+ "to_seconds", 10));
+ if (!zero_in_end_date &&
+ start_date.month == end_date.month &&
+ start_date.year == end_date.year)
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= false;
+ }
+ if (part_iter->part_nums.start >= part_iter->part_nums.end &&
+ !part_iter->ret_null_part)
+ DBUG_RETURN(0); /* No partitions */
+ }
+ DBUG_RETURN(1); /* Ok, iterator initialized */
+}
+
+
+/* See get_part_iter_for_interval_via_walking for definition of what this is */
+#define MAX_RANGE_TO_WALK 32
+
+
+/*
+ Partitioning Interval Analysis: Initialize iterator to walk field interval
+
+ SYNOPSIS
+ get_part_iter_for_interval_via_walking()
+ part_info Partition info
+ is_subpart TRUE - act for subpartitioning
+ FALSE - act for partitioning
+ min_value minimum field value, in opt_range key format.
+ max_value minimum field value, in opt_range key format.
+ flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE,
+ NO_MAX_RANGE.
+ part_iter Iterator structure to be initialized
+
+ DESCRIPTION
+ Initialize partition set iterator to walk over interval in integer field
+ space. That is, for "const1 <=? t.field <=? const2" interval, initialize
+ the iterator to return a set of [sub]partitions obtained with the
+ following procedure:
+ get partition id for t.field = const1, return it
+ get partition id for t.field = const1+1, return it
+ ... t.field = const1+2, ...
+ ... ... ...
+ ... t.field = const2 ...
+
+ IMPLEMENTATION
+ See get_partitions_in_range_iter for general description of interval
+ analysis. We support walking over the following intervals:
+ "t.field IS NULL"
+ "c1 <=? t.field <=? c2", where c1 and c2 are finite.
+ Intervals with +inf/-inf, and [NULL, c1] interval can be processed but
+ that is more tricky and I don't have time to do it right now.
+
+ RETURN
+ 0 - No matching partitions, iterator not initialized
+ 1 - Some partitions would match, iterator intialized for traversing them
+ -1 - All partitions would match, iterator not initialized
+*/
+
+int get_part_iter_for_interval_via_walking(partition_info *part_info,
+ bool is_subpart,
+ uint32 *store_length_array, /* ignored */
+ uchar *min_value, uchar *max_value,
+ uint min_len, uint max_len, /* ignored */
+ uint flags,
+ PARTITION_ITERATOR *part_iter)
+{
+ Field *field;
+ uint total_parts;
+ partition_iter_func get_next_func;
+ DBUG_ENTER("get_part_iter_for_interval_via_walking");
+ (void)store_length_array;
+ (void)min_len;
+ (void)max_len;
+
+ part_iter->ret_null_part= part_iter->ret_null_part_orig= FALSE;
+ if (is_subpart)
+ {
+ field= part_info->subpart_field_array[0];
+ total_parts= part_info->num_subparts;
+ get_next_func= get_next_subpartition_via_walking;
+ }
+ else
+ {
+ field= part_info->part_field_array[0];
+ total_parts= part_info->num_parts;
+ get_next_func= get_next_partition_via_walking;
+ }
+
+ /* Handle the "t.field IS NULL" interval, it is a special case */
+ if (field->real_maybe_null() && !(flags & (NO_MIN_RANGE | NO_MAX_RANGE)) &&
+ *min_value && *max_value)
+ {
+ /*
+ We don't have a part_iter->get_next() function that would find which
+ partition "t.field IS NULL" belongs to, so find partition that contains
+ NULL right here, and return an iterator over singleton set.
+ */
+ uint32 part_id;
+ field->set_null();
+ if (is_subpart)
+ {
+ if (!part_info->get_subpartition_id(part_info, &part_id))
+ {
+ init_single_partition_iterator(part_id, part_iter);
+ DBUG_RETURN(1); /* Ok, iterator initialized */
+ }
+ }
+ else
+ {
+ longlong dummy;
+ int res= part_info->is_sub_partitioned() ?
+ part_info->get_part_partition_id(part_info, &part_id,
+ &dummy):
+ part_info->get_partition_id(part_info, &part_id, &dummy);
+ if (!res)
+ {
+ init_single_partition_iterator(part_id, part_iter);
+ DBUG_RETURN(1); /* Ok, iterator initialized */
+ }
+ }
+ DBUG_RETURN(0); /* No partitions match */
+ }
+
+ if ((field->real_maybe_null() &&
+ ((!(flags & NO_MIN_RANGE) && *min_value) || // NULL <? X
+ (!(flags & NO_MAX_RANGE) && *max_value))) || // X <? NULL
+ (flags & (NO_MIN_RANGE | NO_MAX_RANGE))) // -inf at any bound
+ {
+ DBUG_RETURN(-1); /* Can't handle this interval, have to use all partitions */
+ }
+
+ /* Get integers for left and right interval bound */
+ longlong a, b;
+ uint len= field->pack_length_in_rec();
+ store_key_image_to_rec(field, min_value, len);
+ a= field->val_int();
+
+ store_key_image_to_rec(field, max_value, len);
+ b= field->val_int();
+
+ /*
+ Handle a special case where the distance between interval bounds is
+ exactly 4G-1. This interval is too big for range walking, and if it is an
+ (x,y]-type interval then the following "b +=..." code will convert it to
+ an empty interval by "wrapping around" a + 4G-1 + 1 = a.
+ */
+ if ((ulonglong)b - (ulonglong)a == ~0ULL)
+ DBUG_RETURN(-1);
+
+ a+= MY_TEST(flags & NEAR_MIN);
+ b+= MY_TEST(!(flags & NEAR_MAX));
+ ulonglong n_values= b - a;
+
+ /*
+ Will it pay off to enumerate all values in the [a..b] range and evaluate
+ the partitioning function for every value? It depends on
+ 1. whether we'll be able to infer that some partitions are not used
+ 2. if time savings from not scanning these partitions will be greater
+ than time spent in enumeration.
+ We will assume that the cost of accessing one extra partition is greater
+ than the cost of evaluating the partitioning function O(#partitions).
+ This means we should jump at any chance to eliminate a partition, which
+ gives us this logic:
+
+ Do the enumeration if
+ - the number of values to enumerate is comparable to the number of
+ partitions, or
+ - there are not many values to enumerate.
+ */
+ if ((n_values > 2*total_parts) && n_values > MAX_RANGE_TO_WALK)
+ DBUG_RETURN(-1);
+
+ part_iter->field_vals.start= part_iter->field_vals.cur= a;
+ part_iter->field_vals.end= b;
+ part_iter->part_info= part_info;
+ part_iter->get_next= get_next_func;
+ DBUG_RETURN(1);
+}
+
+
+/*
+ PARTITION_ITERATOR::get_next implementation: enumerate partitions in range
+
+ SYNOPSIS
+ get_next_partition_id_range()
+ part_iter Partition set iterator structure
+
+ DESCRIPTION
+ This is implementation of PARTITION_ITERATOR::get_next() that returns
+ [sub]partition ids in [min_partition_id, max_partition_id] range.
+ The function conforms to partition_iter_func type.
+
+ RETURN
+ partition id
+ NOT_A_PARTITION_ID if there are no more partitions
+*/
+
+uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter)
+{
+ if (part_iter->part_nums.cur >= part_iter->part_nums.end)
+ {
+ if (part_iter->ret_null_part)
+ {
+ part_iter->ret_null_part= FALSE;
+ return 0; /* NULL always in first range partition */
+ }
+ part_iter->part_nums.cur= part_iter->part_nums.start;
+ part_iter->ret_null_part= part_iter->ret_null_part_orig;
+ return NOT_A_PARTITION_ID;
+ }
+ else
+ return part_iter->part_nums.cur++;
+}
+
+
+/*
+ PARTITION_ITERATOR::get_next implementation for LIST partitioning
+
+ SYNOPSIS
+ get_next_partition_id_list()
+ part_iter Partition set iterator structure
+
+ DESCRIPTION
+ This implementation of PARTITION_ITERATOR::get_next() is special for
+ LIST partitioning: it enumerates partition ids in
+ part_info->list_array[i] (list_col_array[i*cols] for COLUMNS LIST
+ partitioning) where i runs over [min_idx, max_idx] interval.
+ The function conforms to partition_iter_func type.
+
+ RETURN
+ partition id
+ NOT_A_PARTITION_ID if there are no more partitions
+*/
+
+uint32 get_next_partition_id_list(PARTITION_ITERATOR *part_iter)
+{
+ if (part_iter->part_nums.cur >= part_iter->part_nums.end)
+ {
+ if (part_iter->ret_null_part)
+ {
+ part_iter->ret_null_part= FALSE;
+ return part_iter->part_info->has_null_part_id;
+ }
+ part_iter->part_nums.cur= part_iter->part_nums.start;
+ part_iter->ret_null_part= part_iter->ret_null_part_orig;
+ return NOT_A_PARTITION_ID;
+ }
+ else
+ {
+ partition_info *part_info= part_iter->part_info;
+ uint32 num_part= part_iter->part_nums.cur++;
+ if (part_info->column_list)
+ {
+ uint num_columns= part_info->part_field_list.elements;
+ return part_info->list_col_array[num_part*num_columns].partition_id;
+ }
+ return part_info->list_array[num_part].partition_id;
+ }
+}
+
+
+/*
+ PARTITION_ITERATOR::get_next implementation: walk over field-space interval
+
+ SYNOPSIS
+ get_next_partition_via_walking()
+ part_iter Partitioning iterator
+
+ DESCRIPTION
+ This implementation of PARTITION_ITERATOR::get_next() returns ids of
+ partitions that contain records with partitioning field value within
+ [start_val, end_val] interval.
+ The function conforms to partition_iter_func type.
+
+ RETURN
+ partition id
+ NOT_A_PARTITION_ID if there are no more partitioning.
+*/
+
+static uint32 get_next_partition_via_walking(PARTITION_ITERATOR *part_iter)
+{
+ uint32 part_id;
+ Field *field= part_iter->part_info->part_field_array[0];
+ while (part_iter->field_vals.cur != part_iter->field_vals.end)
+ {
+ longlong dummy;
+ field->store(part_iter->field_vals.cur++, field->flags & UNSIGNED_FLAG);
+ if ((part_iter->part_info->is_sub_partitioned() &&
+ !part_iter->part_info->get_part_partition_id(part_iter->part_info,
+ &part_id, &dummy)) ||
+ !part_iter->part_info->get_partition_id(part_iter->part_info,
+ &part_id, &dummy))
+ return part_id;
+ }
+ part_iter->field_vals.cur= part_iter->field_vals.start;
+ return NOT_A_PARTITION_ID;
+}
+
+
+/* Same as get_next_partition_via_walking, but for subpartitions */
+
+static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR *part_iter)
+{
+ Field *field= part_iter->part_info->subpart_field_array[0];
+ uint32 res;
+ if (part_iter->field_vals.cur == part_iter->field_vals.end)
+ {
+ part_iter->field_vals.cur= part_iter->field_vals.start;
+ return NOT_A_PARTITION_ID;
+ }
+ field->store(part_iter->field_vals.cur++, field->flags & UNSIGNED_FLAG);
+ if (part_iter->part_info->get_subpartition_id(part_iter->part_info,
+ &res))
+ return NOT_A_PARTITION_ID;
+ return res;
+}
+
+
+/*
+ Create partition names
+
+ SYNOPSIS
+ create_partition_name()
+ out:out Created partition name string
+ in1 First part
+ in2 Second part
+ name_variant Normal, temporary or renamed partition name
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ This method is used to calculate the partition name, service routine to
+ the del_ren_cre_table method.
+*/
+
+void create_partition_name(char *out, const char *in1,
+ const char *in2, uint name_variant,
+ bool translate)
+{
+ char transl_part_name[FN_REFLEN];
+ const char *transl_part;
+
+ if (translate)
+ {
+ tablename_to_filename(in2, transl_part_name, FN_REFLEN);
+ transl_part= transl_part_name;
+ }
+ else
+ transl_part= in2;
+ if (name_variant == NORMAL_PART_NAME)
+ strxmov(out, in1, "#P#", transl_part, NullS);
+ else if (name_variant == TEMP_PART_NAME)
+ strxmov(out, in1, "#P#", transl_part, "#TMP#", NullS);
+ else if (name_variant == RENAMED_PART_NAME)
+ strxmov(out, in1, "#P#", transl_part, "#REN#", NullS);
+}
+
+
+/*
+ Create subpartition name
+
+ SYNOPSIS
+ create_subpartition_name()
+ out:out Created partition name string
+ in1 First part
+ in2 Second part
+ in3 Third part
+ name_variant Normal, temporary or renamed partition name
+
+ RETURN VALUE
+ NONE
+
+ DESCRIPTION
+ This method is used to calculate the subpartition name, service routine to
+ the del_ren_cre_table method.
+*/
+
+void create_subpartition_name(char *out, const char *in1,
+ const char *in2, const char *in3,
+ uint name_variant)
+{
+ char transl_part_name[FN_REFLEN], transl_subpart_name[FN_REFLEN];
+
+ tablename_to_filename(in2, transl_part_name, FN_REFLEN);
+ tablename_to_filename(in3, transl_subpart_name, FN_REFLEN);
+ if (name_variant == NORMAL_PART_NAME)
+ strxmov(out, in1, "#P#", transl_part_name,
+ "#SP#", transl_subpart_name, NullS);
+ else if (name_variant == TEMP_PART_NAME)
+ strxmov(out, in1, "#P#", transl_part_name,
+ "#SP#", transl_subpart_name, "#TMP#", NullS);
+ else if (name_variant == RENAMED_PART_NAME)
+ strxmov(out, in1, "#P#", transl_part_name,
+ "#SP#", transl_subpart_name, "#REN#", NullS);
+}
+
+uint get_partition_field_store_length(Field *field)
+{
+ uint store_length;
+
+ store_length= field->key_length();
+ if (field->real_maybe_null())
+ store_length+= HA_KEY_NULL_LENGTH;
+ if (field->real_type() == MYSQL_TYPE_VARCHAR)
+ store_length+= HA_KEY_BLOB_LENGTH;
+ return store_length;
+}
+#endif
diff --git a/sql/sql_partition.h b/sql/sql_partition.h
new file mode 100644
index 00000000000..5da132661c9
--- /dev/null
+++ b/sql/sql_partition.h
@@ -0,0 +1,290 @@
+#ifndef SQL_PARTITION_INCLUDED
+#define SQL_PARTITION_INCLUDED
+
+/* Copyright (c) 2006, 2013, Oracle and/or its affiliates.
+
+ 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 */
+
+#ifdef __GNUC__
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_list.h" /* List */
+#include "table.h" /* TABLE_LIST */
+
+class Alter_info;
+class Alter_table_ctx;
+class Field;
+class String;
+class handler;
+class partition_info;
+struct TABLE;
+struct TABLE_LIST;
+typedef struct st_bitmap MY_BITMAP;
+typedef struct st_key KEY;
+typedef struct st_key_range key_range;
+
+/* Flags for partition handlers */
+#define HA_CAN_PARTITION (1 << 0) /* Partition support */
+#define HA_CAN_UPDATE_PARTITION_KEY (1 << 1)
+#define HA_CAN_PARTITION_UNIQUE (1 << 2)
+#define HA_USE_AUTO_PARTITION (1 << 3)
+
+#define NORMAL_PART_NAME 0
+#define TEMP_PART_NAME 1
+#define RENAMED_PART_NAME 2
+
+typedef struct st_lock_param_type
+{
+ TABLE_LIST *table_list;
+ ulonglong copied;
+ ulonglong deleted;
+ THD *thd;
+ HA_CREATE_INFO *create_info;
+ Alter_info *alter_info;
+ TABLE *table;
+ KEY *key_info_buffer;
+ const char *db;
+ const char *table_name;
+ uchar *pack_frm_data;
+ uint key_count;
+ uint db_options;
+ size_t pack_frm_len;
+ partition_info *part_info;
+} ALTER_PARTITION_PARAM_TYPE;
+
+typedef struct {
+ longlong list_value;
+ uint32 partition_id;
+} LIST_PART_ENTRY;
+
+typedef struct {
+ uint32 start_part;
+ uint32 end_part;
+} part_id_range;
+
+struct st_partition_iter;
+#define NOT_A_PARTITION_ID UINT_MAX32
+
+bool is_partition_in_list(char *part_name, List<char> list_part_names);
+char *are_partitions_in_table(partition_info *new_part_info,
+ partition_info *old_part_info);
+bool check_reorganise_list(partition_info *new_part_info,
+ partition_info *old_part_info,
+ List<char> list_part_names);
+handler *get_ha_partition(partition_info *part_info);
+int get_parts_for_update(const uchar *old_data, uchar *new_data,
+ const uchar *rec0, partition_info *part_info,
+ uint32 *old_part_id, uint32 *new_part_id,
+ longlong *func_value);
+int get_part_for_delete(const uchar *buf, const uchar *rec0,
+ partition_info *part_info, uint32 *part_id);
+void prune_partition_set(const TABLE *table, part_id_range *part_spec);
+bool check_partition_info(partition_info *part_info,handlerton **eng_type,
+ TABLE *table, handler *file, HA_CREATE_INFO *info);
+void set_linear_hash_mask(partition_info *part_info, uint num_parts);
+bool fix_partition_func(THD *thd, TABLE *table, bool create_table_ind);
+void get_partition_set(const TABLE *table, uchar *buf, const uint index,
+ const key_range *key_spec,
+ part_id_range *part_spec);
+uint get_partition_field_store_length(Field *field);
+int get_cs_converted_part_value_from_string(THD *thd,
+ Item *item,
+ String *input_str,
+ String *output_str,
+ CHARSET_INFO *cs,
+ bool use_hex);
+void get_full_part_id_from_key(const TABLE *table, uchar *buf,
+ KEY *key_info,
+ const key_range *key_spec,
+ part_id_range *part_spec);
+bool mysql_unpack_partition(THD *thd, char *part_buf,
+ uint part_info_len,
+ TABLE *table, bool is_create_table_ind,
+ handlerton *default_db_type,
+ bool *work_part_info_used);
+void make_used_partitions_str(partition_info *part_info, String *parts_str);
+uint32 get_list_array_idx_for_endpoint(partition_info *part_info,
+ bool left_endpoint,
+ bool include_endpoint);
+uint32 get_partition_id_range_for_endpoint(partition_info *part_info,
+ bool left_endpoint,
+ bool include_endpoint);
+bool check_part_func_fields(Field **ptr, bool ok_with_charsets);
+bool field_is_partition_charset(Field *field);
+Item* convert_charset_partition_constant(Item *item, CHARSET_INFO *cs);
+void mem_alloc_error(size_t size);
+void truncate_partition_filename(char *path);
+
+/*
+ A "Get next" function for partition iterator.
+
+ SYNOPSIS
+ partition_iter_func()
+ part_iter Partition iterator, you call only "iter.get_next(&iter)"
+
+ DESCRIPTION
+ Depending on whether partitions or sub-partitions are iterated, the
+ function returns next subpartition id/partition number. The sequence of
+ returned numbers is not ordered and may contain duplicates.
+
+ When the end of sequence is reached, NOT_A_PARTITION_ID is returned, and
+ the iterator resets itself (so next get_next() call will start to
+ enumerate the set all over again).
+
+ RETURN
+ NOT_A_PARTITION_ID if there are no more partitions.
+ [sub]partition_id of the next partition
+*/
+
+typedef uint32 (*partition_iter_func)(st_partition_iter* part_iter);
+
+
+/*
+ Partition set iterator. Used to enumerate a set of [sub]partitions
+ obtained in partition interval analysis (see get_partitions_in_range_iter).
+
+ For the user, the only meaningful field is get_next, which may be used as
+ follows:
+ part_iterator.get_next(&part_iterator);
+
+ Initialization is done by any of the following calls:
+ - get_partitions_in_range_iter-type function call
+ - init_single_partition_iterator()
+ - init_all_partitions_iterator()
+ Cleanup is not needed.
+*/
+
+typedef struct st_partition_iter
+{
+ partition_iter_func get_next;
+ /*
+ Valid for "Interval mapping" in LIST partitioning: if true, let the
+ iterator also produce id of the partition that contains NULL value.
+ */
+ bool ret_null_part, ret_null_part_orig;
+ struct st_part_num_range
+ {
+ uint32 start;
+ uint32 cur;
+ uint32 end;
+ };
+
+ struct st_field_value_range
+ {
+ longlong start;
+ longlong cur;
+ longlong end;
+ };
+
+ union
+ {
+ struct st_part_num_range part_nums;
+ struct st_field_value_range field_vals;
+ };
+ partition_info *part_info;
+} PARTITION_ITERATOR;
+
+
+/*
+ Get an iterator for set of partitions that match given field-space interval
+
+ SYNOPSIS
+ get_partitions_in_range_iter()
+ part_info Partitioning info
+ is_subpart
+ store_length_array Length of fields packed in opt_range_key format
+ min_val Left edge, field value in opt_range_key format
+ max_val Right edge, field value in opt_range_key format
+ min_len Length of minimum value
+ max_len Length of maximum value
+ flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE,
+ NO_MAX_RANGE
+ part_iter Iterator structure to be initialized
+
+ DESCRIPTION
+ Functions with this signature are used to perform "Partitioning Interval
+ Analysis". This analysis is applicable for any type of [sub]partitioning
+ by some function of a single fieldX. The idea is as follows:
+ Given an interval "const1 <=? fieldX <=? const2", find a set of partitions
+ that may contain records with value of fieldX within the given interval.
+
+ The min_val, max_val and flags parameters specify the interval.
+ The set of partitions is returned by initializing an iterator in *part_iter
+
+ NOTES
+ There are currently three functions of this type:
+ - get_part_iter_for_interval_via_walking
+ - get_part_iter_for_interval_cols_via_map
+ - get_part_iter_for_interval_via_mapping
+
+ RETURN
+ 0 - No matching partitions, iterator not initialized
+ 1 - Some partitions would match, iterator intialized for traversing them
+ -1 - All partitions would match, iterator not initialized
+*/
+
+typedef int (*get_partitions_in_range_iter)(partition_info *part_info,
+ bool is_subpart,
+ uint32 *store_length_array,
+ uchar *min_val, uchar *max_val,
+ uint min_len, uint max_len,
+ uint flags,
+ PARTITION_ITERATOR *part_iter);
+
+#include "partition_info.h"
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+uint fast_alter_partition_table(THD *thd, TABLE *table,
+ Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ TABLE_LIST *table_list,
+ char *db,
+ const char *table_name);
+bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info,
+ enum partition_state part_state);
+uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ Alter_table_ctx *alter_ctx,
+ bool *partition_changed,
+ bool *fast_alter_table);
+char *generate_partition_syntax(partition_info *part_info,
+ uint *buf_length, bool use_sql_alloc,
+ bool show_partition_options,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ const char *current_comment_start);
+bool verify_data_with_partition(TABLE *table, TABLE *part_table,
+ uint32 part_id);
+bool compare_partition_options(HA_CREATE_INFO *table_create_info,
+ partition_element *part_elem);
+bool partition_key_modified(TABLE *table, const MY_BITMAP *fields);
+#else
+#define partition_key_modified(X,Y) 0
+#endif
+
+void create_partition_name(char *out, const char *in1,
+ const char *in2, uint name_variant,
+ bool translate);
+void create_subpartition_name(char *out, const char *in1,
+ const char *in2, const char *in3,
+ uint name_variant);
+
+void set_field_ptr(Field **ptr, const uchar *new_buf, const uchar *old_buf);
+void set_key_field_ptr(KEY *key_info, const uchar *new_buf,
+ const uchar *old_buf);
+
+extern const LEX_STRING partition_keywords[];
+
+#endif /* SQL_PARTITION_INCLUDED */
diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc
new file mode 100644
index 00000000000..8c59febeb77
--- /dev/null
+++ b/sql/sql_partition_admin.cc
@@ -0,0 +1,849 @@
+/* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "sql_parse.h" // check_one_table_access
+ // check_merge_table_access
+ // check_one_table_access
+#include "sql_table.h" // mysql_alter_table, etc.
+#include "sql_cmd.h" // Sql_cmd
+#include "sql_alter.h" // Sql_cmd_alter_table
+#include "sql_partition.h" // struct partition_info, etc.
+#include "debug_sync.h" // DEBUG_SYNC
+#include "sql_truncate.h" // mysql_truncate_table,
+ // Sql_cmd_truncate_table
+#include "sql_admin.h" // Analyze/Check/.._table_statement
+#include "sql_partition_admin.h" // Alter_table_*_partition
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+#include "ha_partition.h" // ha_partition
+#endif
+#include "sql_base.h" // open_and_lock_tables
+
+#ifndef WITH_PARTITION_STORAGE_ENGINE
+
+bool Sql_cmd_partition_unsupported::execute(THD *)
+{
+ DBUG_ENTER("Sql_cmd_partition_unsupported::execute");
+ /* error, partitioning support not compiled in... */
+ my_error(ER_FEATURE_DISABLED, MYF(0), "partitioning",
+ "--with-plugin-partition");
+ DBUG_RETURN(TRUE);
+}
+
+#else
+
+bool Sql_cmd_alter_table_exchange_partition::execute(THD *thd)
+{
+ /* Moved from mysql_execute_command */
+ LEX *lex= thd->lex;
+ /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
+ SELECT_LEX *select_lex= &lex->select_lex;
+ /* first table of first SELECT_LEX */
+ TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first;
+ /*
+ Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
+ so we have to use a copy of this structure to make execution
+ prepared statement- safe. A shallow copy is enough as no memory
+ referenced from this structure will be modified.
+ @todo move these into constructor...
+ */
+ HA_CREATE_INFO create_info(lex->create_info);
+ Alter_info alter_info(lex->alter_info, thd->mem_root);
+ ulong priv_needed= ALTER_ACL | DROP_ACL | INSERT_ACL | CREATE_ACL;
+
+ DBUG_ENTER("Sql_cmd_alter_table_exchange_partition::execute");
+
+ if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
+ DBUG_RETURN(TRUE);
+
+ /* Must be set in the parser */
+ DBUG_ASSERT(select_lex->db);
+ /* also check the table to be exchanged with the partition */
+ DBUG_ASSERT(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION);
+
+ if (check_access(thd, priv_needed, first_table->db,
+ &first_table->grant.privilege,
+ &first_table->grant.m_internal,
+ 0, 0) ||
+ check_access(thd, priv_needed, first_table->next_local->db,
+ &first_table->next_local->grant.privilege,
+ &first_table->next_local->grant.m_internal,
+ 0, 0))
+ DBUG_RETURN(TRUE);
+
+ if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
+ DBUG_RETURN(TRUE);
+
+ /* Not allowed with EXCHANGE PARTITION */
+ DBUG_ASSERT(!create_info.data_file_name && !create_info.index_file_name);
+
+ thd->enable_slow_log= opt_log_slow_admin_statements;
+ DBUG_RETURN(exchange_partition(thd, first_table, &alter_info));
+}
+
+
+/**
+ @brief Checks that the tables will be able to be used for EXCHANGE PARTITION.
+ @param table Non partitioned table.
+ @param part_table Partitioned table.
+
+ @retval FALSE if OK, otherwise error is reported and TRUE is returned.
+*/
+static bool check_exchange_partition(TABLE *table, TABLE *part_table)
+{
+ DBUG_ENTER("check_exchange_partition");
+
+ /* Both tables must exist */
+ if (!part_table || !table)
+ {
+ my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /* The first table must be partitioned, and the second must not */
+ if (!part_table->part_info)
+ {
+ my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (table->part_info)
+ {
+ my_error(ER_PARTITION_EXCHANGE_PART_TABLE, MYF(0),
+ table->s->table_name.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (part_table->file->ht != partition_hton)
+ {
+ /*
+ Only allowed on partitioned tables throught the generic ha_partition
+ handler, i.e not yet for native partitioning (NDB).
+ */
+ my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (table->file->ht != part_table->part_info->default_engine_type)
+ {
+ my_error(ER_MIX_HANDLER_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Verify that table is not tmp table, partitioned tables cannot be tmp. */
+ if (table->s->tmp_table != NO_TMP_TABLE)
+ {
+ my_error(ER_PARTITION_EXCHANGE_TEMP_TABLE, MYF(0),
+ table->s->table_name.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* The table cannot have foreign keys constraints or be referenced */
+ if(!table->file->can_switch_engines())
+ {
+ my_error(ER_PARTITION_EXCHANGE_FOREIGN_KEY, MYF(0),
+ table->s->table_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief Compare table structure/options between a non partitioned table
+ and a specific partition of a partitioned table.
+
+ @param thd Thread object.
+ @param table Non partitioned table.
+ @param part_table Partitioned table.
+ @param part_elem Partition element to use for partition specific compare.
+*/
+static bool compare_table_with_partition(THD *thd, TABLE *table,
+ TABLE *part_table,
+ partition_element *part_elem)
+{
+ HA_CREATE_INFO table_create_info, part_create_info;
+ Alter_info part_alter_info;
+ Alter_table_ctx part_alter_ctx; // Not used
+ DBUG_ENTER("compare_table_with_partition");
+
+ bool metadata_equal= false;
+ memset(&part_create_info, 0, sizeof(HA_CREATE_INFO));
+ memset(&table_create_info, 0, sizeof(HA_CREATE_INFO));
+
+ update_create_info_from_table(&table_create_info, table);
+ /* get the current auto_increment value */
+ table->file->update_create_info(&table_create_info);
+ /* mark all columns used, since they are used when preparing the new table */
+ part_table->use_all_columns();
+ table->use_all_columns();
+ if (mysql_prepare_alter_table(thd, part_table, &part_create_info,
+ &part_alter_info, &part_alter_ctx))
+ {
+ my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ /* db_type is not set in prepare_alter_table */
+ part_create_info.db_type= part_table->part_info->default_engine_type;
+ /*
+ Since we exchange the partition with the table, allow exchanging
+ auto_increment value as well.
+ */
+ part_create_info.auto_increment_value=
+ table_create_info.auto_increment_value;
+
+ /* Check compatible row_types and set create_info accordingly. */
+ {
+ enum row_type part_row_type= part_table->file->get_row_type();
+ enum row_type table_row_type= table->file->get_row_type();
+ if (part_row_type != table_row_type)
+ {
+ my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
+ "ROW_FORMAT");
+ DBUG_RETURN(true);
+ }
+ part_create_info.row_type= table->s->row_type;
+ }
+
+ /*
+ NOTE: ha_blackhole does not support check_if_compatible_data,
+ so this always fail for blackhole tables.
+ ha_myisam compares pointers to verify that DATA/INDEX DIRECTORY is
+ the same, so any table using data/index_file_name will fail.
+ */
+ if (mysql_compare_tables(table, &part_alter_info, &part_create_info,
+ &metadata_equal))
+ {
+ my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ DEBUG_SYNC(thd, "swap_partition_after_compare_tables");
+ if (!metadata_equal)
+ {
+ my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_ASSERT(table->s->db_create_options ==
+ part_table->s->db_create_options);
+ DBUG_ASSERT(table->s->db_options_in_use ==
+ part_table->s->db_options_in_use);
+
+ if (table_create_info.avg_row_length != part_create_info.avg_row_length)
+ {
+ my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
+ "AVG_ROW_LENGTH");
+ DBUG_RETURN(TRUE);
+ }
+
+ if (table_create_info.table_options != part_create_info.table_options)
+ {
+ my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
+ "TABLE OPTION");
+ DBUG_RETURN(TRUE);
+ }
+
+ if (table->s->table_charset != part_table->s->table_charset)
+ {
+ my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
+ "CHARACTER SET");
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ NOTE: We do not support update of frm-file, i.e. change
+ max/min_rows, data/index_file_name etc.
+ The workaround is to use REORGANIZE PARTITION to rewrite
+ the frm file and then use EXCHANGE PARTITION when they are the same.
+ */
+ if (compare_partition_options(&table_create_info, part_elem))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief Exchange partition/table with ddl log.
+
+ @details How to handle a crash in the middle of the rename (break on error):
+ 1) register in ddl_log that we are going to exchange swap_table with part.
+ 2) do the first rename (swap_table -> tmp-name) and sync the ddl_log.
+ 3) do the second rename (part -> swap_table) and sync the ddl_log.
+ 4) do the last rename (tmp-name -> part).
+ 5) mark the entry done.
+
+ Recover by:
+ 5) is done, All completed. Nothing to recover.
+ 4) is done see 3). (No mark or sync in the ddl_log...)
+ 3) is done -> try rename part -> tmp-name (ignore failure) goto 2).
+ 2) is done -> try rename swap_table -> part (ignore failure) goto 1).
+ 1) is done -> try rename tmp-name -> swap_table (ignore failure).
+ before 1) Nothing to recover...
+
+ @param thd Thread handle
+ @param name name of table/partition 1 (to be exchanged with 2)
+ @param from_name name of table/partition 2 (to be exchanged with 1)
+ @param tmp_name temporary name to use while exchaning
+ @param ht handlerton of the table/partitions
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+
+ @note ha_heap always succeeds in rename (since it is created upon usage).
+ This is OK when to recover from a crash since all heap are empty and the
+ recover is done early in the startup of the server (right before
+ read_init_file which can populate the tables).
+
+ And if no crash we can trust the syncs in the ddl_log.
+
+ What about if the rename is put into a background thread? That will cause
+ corruption and is avoided by the exlusive metadata lock.
+*/
+static bool exchange_name_with_ddl_log(THD *thd,
+ const char *name,
+ const char *from_name,
+ const char *tmp_name,
+ handlerton *ht)
+{
+ DDL_LOG_ENTRY exchange_entry;
+ DDL_LOG_MEMORY_ENTRY *log_entry= NULL;
+ DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL;
+ bool error= TRUE;
+ bool error_set= FALSE;
+ handler *file= NULL;
+ DBUG_ENTER("exchange_name_with_ddl_log");
+
+ if (!(file= get_new_handler(NULL, thd->mem_root, ht)))
+ {
+ mem_alloc_error(sizeof(handler));
+ DBUG_RETURN(TRUE);
+ }
+
+ /* prepare the action entry */
+ exchange_entry.entry_type= DDL_LOG_ENTRY_CODE;
+ exchange_entry.action_type= DDL_LOG_EXCHANGE_ACTION;
+ exchange_entry.next_entry= 0;
+ exchange_entry.name= name;
+ exchange_entry.from_name= from_name;
+ exchange_entry.tmp_name= tmp_name;
+ exchange_entry.handler_name= ha_resolve_storage_engine_name(ht);
+ exchange_entry.phase= EXCH_PHASE_NAME_TO_TEMP;
+
+ mysql_mutex_lock(&LOCK_gdl);
+ /*
+ write to the ddl log what to do by:
+ 1) write the action entry (i.e. which names to be exchanged)
+ 2) write the execution entry with a link to the action entry
+ */
+ DBUG_EXECUTE_IF("exchange_partition_fail_1", goto err_no_action_written;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_1", DBUG_SUICIDE(););
+ if (write_ddl_log_entry(&exchange_entry, &log_entry))
+ goto err_no_action_written;
+
+ DBUG_EXECUTE_IF("exchange_partition_fail_2", goto err_no_execute_written;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_2", DBUG_SUICIDE(););
+ if (write_execute_ddl_log_entry(log_entry->entry_pos, FALSE, &exec_log_entry))
+ goto err_no_execute_written;
+ /* ddl_log is written and synced */
+
+ mysql_mutex_unlock(&LOCK_gdl);
+ /*
+ Execute the name exchange.
+ Do one rename, increase the phase, update the action entry and sync.
+ In case of errors in the ddl_log we must fail and let the ddl_log try
+ to revert the changes, since otherwise it could revert the command after
+ we sent OK to the client.
+ */
+ /* call rename table from table to tmp-name */
+ DBUG_EXECUTE_IF("exchange_partition_fail_3",
+ my_error(ER_ERROR_ON_RENAME, MYF(0),
+ name, tmp_name, 0, "n/a");
+ error_set= TRUE;
+ goto err_rename;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_3", DBUG_SUICIDE(););
+ if (file->ha_rename_table(name, tmp_name))
+ {
+ char errbuf[MYSYS_STRERROR_SIZE];
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ my_error(ER_ERROR_ON_RENAME, MYF(0), name, tmp_name,
+ my_errno, errbuf);
+ error_set= TRUE;
+ goto err_rename;
+ }
+ DBUG_EXECUTE_IF("exchange_partition_fail_4", goto err_rename;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_4", DBUG_SUICIDE(););
+ if (deactivate_ddl_log_entry(log_entry->entry_pos))
+ goto err_rename;
+
+ /* call rename table from partition to table */
+ DBUG_EXECUTE_IF("exchange_partition_fail_5",
+ my_error(ER_ERROR_ON_RENAME, MYF(0),
+ from_name, name, 0, "n/a");
+ error_set= TRUE;
+ goto err_rename;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_5", DBUG_SUICIDE(););
+ if (file->ha_rename_table(from_name, name))
+ {
+ char errbuf[MYSYS_STRERROR_SIZE];
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ my_error(ER_ERROR_ON_RENAME, MYF(0), from_name, name,
+ my_errno, errbuf);
+ error_set= TRUE;
+ goto err_rename;
+ }
+ DBUG_EXECUTE_IF("exchange_partition_fail_6", goto err_rename;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_6", DBUG_SUICIDE(););
+ if (deactivate_ddl_log_entry(log_entry->entry_pos))
+ goto err_rename;
+
+ /* call rename table from tmp-nam to partition */
+ DBUG_EXECUTE_IF("exchange_partition_fail_7",
+ my_error(ER_ERROR_ON_RENAME, MYF(0),
+ tmp_name, from_name, 0, "n/a");
+ error_set= TRUE;
+ goto err_rename;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_7", DBUG_SUICIDE(););
+ if (file->ha_rename_table(tmp_name, from_name))
+ {
+ char errbuf[MYSYS_STRERROR_SIZE];
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ my_error(ER_ERROR_ON_RENAME, MYF(0), tmp_name, from_name,
+ my_errno, errbuf);
+ error_set= TRUE;
+ goto err_rename;
+ }
+ DBUG_EXECUTE_IF("exchange_partition_fail_8", goto err_rename;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_8", DBUG_SUICIDE(););
+ if (deactivate_ddl_log_entry(log_entry->entry_pos))
+ goto err_rename;
+
+ /* The exchange is complete and ddl_log is deactivated */
+ DBUG_EXECUTE_IF("exchange_partition_fail_9", goto err_rename;);
+ DBUG_EXECUTE_IF("exchange_partition_abort_9", DBUG_SUICIDE(););
+ /* all OK */
+ error= FALSE;
+ delete file;
+ DBUG_RETURN(error);
+err_rename:
+ /*
+ Nothing to do if any of these commands fails :( the commands itselfs
+ will log to the error log about the failures...
+ */
+ /* execute the ddl log entry to revert the renames */
+ (void) execute_ddl_log_entry(current_thd, log_entry->entry_pos);
+ mysql_mutex_lock(&LOCK_gdl);
+ /* mark the execute log entry done */
+ (void) write_execute_ddl_log_entry(0, TRUE, &exec_log_entry);
+ /* release the execute log entry */
+ (void) release_ddl_log_memory_entry(exec_log_entry);
+err_no_execute_written:
+ /* release the action log entry */
+ (void) release_ddl_log_memory_entry(log_entry);
+err_no_action_written:
+ mysql_mutex_unlock(&LOCK_gdl);
+ delete file;
+ if (!error_set)
+ my_error(ER_DDL_LOG_ERROR, MYF(0));
+ DBUG_RETURN(error);
+}
+
+
+/**
+ @brief Swap places between a partition and a table.
+
+ @details Verify that the tables are compatible (same engine, definition etc),
+ verify that all rows in the table will fit in the partition,
+ if all OK, rename table to tmp name, rename partition to table
+ and finally rename tmp name to partition.
+
+ 1) Take upgradable mdl, open tables and then lock them (inited in parse)
+ 2) Verify that metadata matches
+ 3) verify data
+ 4) Upgrade to exclusive mdl for both tables
+ 5) Rename table <-> partition
+ 6) Rely on close_thread_tables to release mdl and table locks
+
+ @param thd Thread handle
+ @param table_list Table where the partition exists as first table,
+ Table to swap with the partition as second table
+ @param alter_info Contains partition name to swap
+
+ @note This is a DDL operation so triggers will not be used.
+*/
+bool Sql_cmd_alter_table_exchange_partition::
+ exchange_partition(THD *thd, TABLE_LIST *table_list, Alter_info *alter_info)
+{
+ TABLE *part_table, *swap_table;
+ TABLE_LIST *swap_table_list;
+ handlerton *table_hton;
+ partition_element *part_elem;
+ char *partition_name;
+ char temp_name[FN_REFLEN+1];
+ char part_file_name[FN_REFLEN+1];
+ char swap_file_name[FN_REFLEN+1];
+ char temp_file_name[FN_REFLEN+1];
+ uint swap_part_id;
+ uint part_file_name_len;
+ Alter_table_prelocking_strategy alter_prelocking_strategy;
+ MDL_ticket *swap_table_mdl_ticket= NULL;
+ MDL_ticket *part_table_mdl_ticket= NULL;
+ uint table_counter;
+ bool error= TRUE;
+ DBUG_ENTER("mysql_exchange_partition");
+ DBUG_ASSERT(alter_info->flags & Alter_info::ALTER_EXCHANGE_PARTITION);
+
+ /* Don't allow to exchange with log table */
+ swap_table_list= table_list->next_local;
+ if (check_if_log_table(swap_table_list, FALSE, "ALTER PARTITION"))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Currently no MDL lock that allows both read and write and is upgradeable
+ to exclusive, so leave the lock type to TL_WRITE_ALLOW_READ also on the
+ partitioned table.
+
+ TODO: add MDL lock that allows both read and write and is upgradable to
+ exclusive lock. This would allow to continue using the partitioned table
+ also with update/insert/delete while the verification of the swap table
+ is running.
+ */
+
+ /*
+ NOTE: It is not possible to exchange a crashed partition/table since
+ we need some info from the engine, which we can only access after open,
+ to be able to verify the structure/metadata.
+ */
+ table_list->mdl_request.set_type(MDL_SHARED_NO_WRITE);
+ if (open_tables(thd, &table_list, &table_counter, 0,
+ &alter_prelocking_strategy))
+ DBUG_RETURN(true);
+
+ part_table= table_list->table;
+ swap_table= swap_table_list->table;
+
+ if (check_exchange_partition(swap_table, part_table))
+ DBUG_RETURN(TRUE);
+
+ /* set lock pruning on first table */
+ partition_name= alter_info->partition_names.head();
+ if (table_list->table->part_info->
+ set_named_partition_bitmap(partition_name, strlen(partition_name)))
+ DBUG_RETURN(true);
+
+ if (lock_tables(thd, table_list, table_counter, 0))
+ DBUG_RETURN(true);
+
+
+ table_hton= swap_table->file->ht;
+
+ THD_STAGE_INFO(thd, stage_verifying_table);
+
+ /* Will append the partition name later in part_info->get_part_elem() */
+ part_file_name_len= build_table_filename(part_file_name,
+ sizeof(part_file_name),
+ table_list->db,
+ table_list->table_name,
+ "", 0);
+ build_table_filename(swap_file_name,
+ sizeof(swap_file_name),
+ swap_table_list->db,
+ swap_table_list->table_name,
+ "", 0);
+ /* create a unique temp name #sqlx-nnnn_nnnn, x for eXchange */
+ my_snprintf(temp_name, sizeof(temp_name), "%sx-%lx_%lx",
+ tmp_file_prefix, current_pid, thd->thread_id);
+ if (lower_case_table_names)
+ my_casedn_str(files_charset_info, temp_name);
+ build_table_filename(temp_file_name, sizeof(temp_file_name),
+ table_list->next_local->db,
+ temp_name, "", FN_IS_TMP);
+
+ if (!(part_elem= part_table->part_info->get_part_elem(partition_name,
+ part_file_name +
+ part_file_name_len,
+ &swap_part_id)))
+ {
+ // my_error(ER_UNKNOWN_PARTITION, MYF(0), partition_name,
+ // part_table->alias);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (swap_part_id == NOT_A_PARTITION_ID)
+ {
+ DBUG_ASSERT(part_table->part_info->is_sub_partitioned());
+ my_error(ER_PARTITION_INSTEAD_OF_SUBPARTITION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (compare_table_with_partition(thd, swap_table, part_table, part_elem))
+ DBUG_RETURN(TRUE);
+
+ /* Table and partition has same structure/options, OK to exchange */
+
+ thd_proc_info(thd, "verifying data with partition");
+
+ if (verify_data_with_partition(swap_table, part_table, swap_part_id))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Get exclusive mdl lock on both tables, alway the non partitioned table
+ first. Remember the tickets for downgrading locks later.
+ */
+ swap_table_mdl_ticket= swap_table->mdl_ticket;
+ part_table_mdl_ticket= part_table->mdl_ticket;
+
+ /*
+ No need to set used_partitions to only propagate
+ HA_EXTRA_PREPARE_FOR_RENAME to one part since no built in engine uses
+ that flag. And the action would probably be to force close all other
+ instances which is what we are doing any way.
+ */
+ if (wait_while_table_is_used(thd, swap_table, HA_EXTRA_PREPARE_FOR_RENAME) ||
+ wait_while_table_is_used(thd, part_table, HA_EXTRA_PREPARE_FOR_RENAME))
+ goto err;
+
+ DEBUG_SYNC(thd, "swap_partition_after_wait");
+
+ close_all_tables_for_name(thd, swap_table->s, HA_EXTRA_NOT_USED, NULL);
+ close_all_tables_for_name(thd, part_table->s, HA_EXTRA_NOT_USED, NULL);
+
+ DEBUG_SYNC(thd, "swap_partition_before_rename");
+
+ if (exchange_name_with_ddl_log(thd, swap_file_name, part_file_name,
+ temp_file_name, table_hton))
+ goto err;
+
+ /*
+ Reopen tables under LOCK TABLES. Ignore the return value for now. It's
+ better to keep master/slave in consistent state. Alternative would be to
+ try to revert the exchange operation and issue error.
+ */
+ (void) thd->locked_tables_list.reopen_tables(thd);
+
+ if ((error= write_bin_log(thd, TRUE, thd->query(), thd->query_length())))
+ {
+ /*
+ The error is reported in write_bin_log().
+ We try to revert to make it easier to keep the master/slave in sync.
+ */
+ (void) exchange_name_with_ddl_log(thd, part_file_name, swap_file_name,
+ temp_file_name, table_hton);
+ }
+
+err:
+ if (thd->locked_tables_mode)
+ {
+ if (swap_table_mdl_ticket)
+ swap_table_mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ if (part_table_mdl_ticket)
+ part_table_mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ }
+
+ if (!error)
+ my_ok(thd);
+
+ // For query cache
+ table_list->table= NULL;
+ table_list->next_local->table= NULL;
+ query_cache_invalidate3(thd, table_list, FALSE);
+
+ DBUG_RETURN(error);
+}
+
+bool Sql_cmd_alter_table_analyze_partition::execute(THD *thd)
+{
+ bool res;
+ DBUG_ENTER("Sql_cmd_alter_table_analyze_partition::execute");
+
+ /*
+ Flag that it is an ALTER command which administrates partitions, used
+ by ha_partition
+ */
+ thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
+
+ res= Sql_cmd_analyze_table::execute(thd);
+
+ DBUG_RETURN(res);
+}
+
+
+bool Sql_cmd_alter_table_check_partition::execute(THD *thd)
+{
+ bool res;
+ DBUG_ENTER("Sql_cmd_alter_table_check_partition::execute");
+
+ /*
+ Flag that it is an ALTER command which administrates partitions, used
+ by ha_partition
+ */
+ thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
+
+ res= Sql_cmd_check_table::execute(thd);
+
+ DBUG_RETURN(res);
+}
+
+
+bool Sql_cmd_alter_table_optimize_partition::execute(THD *thd)
+{
+ bool res;
+ DBUG_ENTER("Alter_table_optimize_partition_statement::execute");
+
+ /*
+ Flag that it is an ALTER command which administrates partitions, used
+ by ha_partition
+ */
+ thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
+
+ res= Sql_cmd_optimize_table::execute(thd);
+
+ DBUG_RETURN(res);
+}
+
+
+bool Sql_cmd_alter_table_repair_partition::execute(THD *thd)
+{
+ bool res;
+ DBUG_ENTER("Sql_cmd_alter_table_repair_partition::execute");
+
+ /*
+ Flag that it is an ALTER command which administrates partitions, used
+ by ha_partition
+ */
+ thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
+
+ res= Sql_cmd_repair_table::execute(thd);
+
+ DBUG_RETURN(res);
+}
+
+
+bool Sql_cmd_alter_table_truncate_partition::execute(THD *thd)
+{
+ int error;
+ ha_partition *partition;
+ ulong timeout= thd->variables.lock_wait_timeout;
+ TABLE_LIST *first_table= thd->lex->select_lex.table_list.first;
+ Alter_info *alter_info= &thd->lex->alter_info;
+ uint table_counter, i;
+ List<String> partition_names_list;
+ bool binlog_stmt;
+ DBUG_ENTER("Sql_cmd_alter_table_truncate_partition::execute");
+
+ /*
+ Flag that it is an ALTER command which administrates partitions, used
+ by ha_partition.
+ */
+ thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION |
+ Alter_info::ALTER_TRUNCATE_PARTITION;
+
+ /* Fix the lock types (not the same as ordinary ALTER TABLE). */
+ first_table->lock_type= TL_WRITE;
+ first_table->mdl_request.set_type(MDL_EXCLUSIVE);
+
+ /*
+ Check table permissions and open it with a exclusive lock.
+ Ensure it is a partitioned table and finally, upcast the
+ handler and invoke the partition truncate method. Lastly,
+ write the statement to the binary log if necessary.
+ */
+
+ if (check_one_table_access(thd, DROP_ACL, first_table))
+ DBUG_RETURN(TRUE);
+
+ if (open_tables(thd, &first_table, &table_counter, 0))
+ DBUG_RETURN(true);
+
+ /*
+ TODO: Add support for TRUNCATE PARTITION for NDB and other
+ engines supporting native partitioning.
+ */
+
+ if (!first_table->table || first_table->view ||
+ first_table->table->s->db_type() != partition_hton)
+ {
+ my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+
+ /*
+ Prune all, but named partitions,
+ to avoid excessive calls to external_lock().
+ */
+ List_iterator<char> partition_names_it(alter_info->partition_names);
+ uint num_names= alter_info->partition_names.elements;
+ for (i= 0; i < num_names; i++)
+ {
+ char *partition_name= partition_names_it++;
+ String *str_partition_name= new (thd->mem_root)
+ String(partition_name, system_charset_info);
+ if (!str_partition_name)
+ DBUG_RETURN(true);
+ partition_names_list.push_back(str_partition_name);
+ }
+ first_table->partition_names= &partition_names_list;
+ if (first_table->table->part_info->set_partition_bitmaps(first_table))
+ DBUG_RETURN(true);
+
+ if (lock_tables(thd, first_table, table_counter, 0))
+ DBUG_RETURN(true);
+
+ /*
+ Under locked table modes this might still not be an exclusive
+ lock. Hence, upgrade the lock since the handler truncate method
+ mandates an exclusive metadata lock.
+ */
+ MDL_ticket *ticket= first_table->table->mdl_ticket;
+ if (thd->mdl_context.upgrade_shared_lock(ticket, MDL_EXCLUSIVE, timeout))
+ DBUG_RETURN(TRUE);
+
+ tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, first_table->db,
+ first_table->table_name, FALSE);
+
+ partition= (ha_partition*) first_table->table->file;
+ /* Invoke the handler method responsible for truncating the partition. */
+ if ((error= partition->truncate_partition(alter_info, &binlog_stmt)))
+ partition->print_error(error, MYF(0));
+
+ /*
+ All effects of a truncate operation are committed even if the
+ operation fails. Thus, the query must be written to the binary
+ log. The exception is a unimplemented truncate method or failure
+ before any call to handler::truncate() is done.
+ Also, it is logged in statement format, regardless of the binlog format.
+ */
+ if (error != HA_ERR_WRONG_COMMAND && binlog_stmt)
+ error|= write_bin_log(thd, !error, thd->query(), thd->query_length());
+
+ /*
+ A locked table ticket was upgraded to a exclusive lock. After the
+ the query has been written to the binary log, downgrade the lock
+ to a shared one.
+ */
+ if (thd->locked_tables_mode)
+ ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+
+ if (! error)
+ my_ok(thd);
+
+ // Invalidate query cache
+ DBUG_ASSERT(!first_table->next_local);
+ query_cache_invalidate3(thd, first_table, FALSE);
+
+ DBUG_RETURN(error);
+}
+
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
diff --git a/sql/sql_partition_admin.h b/sql/sql_partition_admin.h
new file mode 100644
index 00000000000..9c53744d9bc
--- /dev/null
+++ b/sql/sql_partition_admin.h
@@ -0,0 +1,269 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_PARTITION_ADMIN_H
+#define SQL_PARTITION_ADMIN_H
+
+#ifndef WITH_PARTITION_STORAGE_ENGINE
+
+/**
+ Stub class that returns a error if the partition storage engine is
+ not supported.
+*/
+class Sql_cmd_partition_unsupported : public Sql_cmd
+{
+public:
+ Sql_cmd_partition_unsupported()
+ {}
+
+ ~Sql_cmd_partition_unsupported()
+ {}
+
+ /* Override SQLCOM_*, since it is an ALTER command */
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ALTER_TABLE;
+ }
+
+ bool execute(THD *thd);
+};
+
+
+class Sql_cmd_alter_table_exchange_partition :
+ public Sql_cmd_partition_unsupported
+{
+public:
+ Sql_cmd_alter_table_exchange_partition()
+ {}
+
+ ~Sql_cmd_alter_table_exchange_partition()
+ {}
+};
+
+
+class Sql_cmd_alter_table_analyze_partition :
+ public Sql_cmd_partition_unsupported
+{
+public:
+ Sql_cmd_alter_table_analyze_partition()
+ {}
+
+ ~Sql_cmd_alter_table_analyze_partition()
+ {}
+};
+
+
+class Sql_cmd_alter_table_check_partition :
+ public Sql_cmd_partition_unsupported
+{
+public:
+ Sql_cmd_alter_table_check_partition()
+ {}
+
+ ~Sql_cmd_alter_table_check_partition()
+ {}
+};
+
+
+class Sql_cmd_alter_table_optimize_partition :
+ public Sql_cmd_partition_unsupported
+{
+public:
+ Sql_cmd_alter_table_optimize_partition()
+ {}
+
+ ~Sql_cmd_alter_table_optimize_partition()
+ {}
+};
+
+
+class Sql_cmd_alter_table_repair_partition :
+ public Sql_cmd_partition_unsupported
+{
+public:
+ Sql_cmd_alter_table_repair_partition()
+ {}
+
+ ~Sql_cmd_alter_table_repair_partition()
+ {}
+};
+
+
+class Sql_cmd_alter_table_truncate_partition :
+ public Sql_cmd_partition_unsupported
+{
+public:
+ Sql_cmd_alter_table_truncate_partition()
+ {}
+
+ ~Sql_cmd_alter_table_truncate_partition()
+ {}
+};
+
+#else
+
+/**
+ Class that represents the ALTER TABLE t1 ANALYZE PARTITION p statement.
+*/
+class Sql_cmd_alter_table_exchange_partition : public Sql_cmd_common_alter_table
+{
+public:
+ /**
+ Constructor, used to represent a ALTER TABLE EXCHANGE PARTITION statement.
+ */
+ Sql_cmd_alter_table_exchange_partition()
+ : Sql_cmd_common_alter_table()
+ {}
+
+ ~Sql_cmd_alter_table_exchange_partition()
+ {}
+
+ bool execute(THD *thd);
+
+private:
+ bool exchange_partition(THD *thd, TABLE_LIST *, Alter_info *);
+};
+
+
+/**
+ Class that represents the ALTER TABLE t1 ANALYZE PARTITION p statement.
+*/
+class Sql_cmd_alter_table_analyze_partition : public Sql_cmd_analyze_table
+{
+public:
+ /**
+ Constructor, used to represent a ALTER TABLE ANALYZE PARTITION statement.
+ */
+ Sql_cmd_alter_table_analyze_partition()
+ : Sql_cmd_analyze_table()
+ {}
+
+ ~Sql_cmd_alter_table_analyze_partition()
+ {}
+
+ bool execute(THD *thd);
+
+ /* Override SQLCOM_ANALYZE, since it is an ALTER command */
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ALTER_TABLE;
+ }
+};
+
+
+/**
+ Class that represents the ALTER TABLE t1 CHECK PARTITION p statement.
+*/
+class Sql_cmd_alter_table_check_partition : public Sql_cmd_check_table
+{
+public:
+ /**
+ Constructor, used to represent a ALTER TABLE CHECK PARTITION statement.
+ */
+ Sql_cmd_alter_table_check_partition()
+ : Sql_cmd_check_table()
+ {}
+
+ ~Sql_cmd_alter_table_check_partition()
+ {}
+
+ bool execute(THD *thd);
+
+ /* Override SQLCOM_CHECK, since it is an ALTER command */
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ALTER_TABLE;
+ }
+};
+
+
+/**
+ Class that represents the ALTER TABLE t1 OPTIMIZE PARTITION p statement.
+*/
+class Sql_cmd_alter_table_optimize_partition : public Sql_cmd_optimize_table
+{
+public:
+ /**
+ Constructor, used to represent a ALTER TABLE OPTIMIZE PARTITION statement.
+ */
+ Sql_cmd_alter_table_optimize_partition()
+ : Sql_cmd_optimize_table()
+ {}
+
+ ~Sql_cmd_alter_table_optimize_partition()
+ {}
+
+ bool execute(THD *thd);
+
+ /* Override SQLCOM_OPTIMIZE, since it is an ALTER command */
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ALTER_TABLE;
+ }
+};
+
+
+/**
+ Class that represents the ALTER TABLE t1 REPAIR PARTITION p statement.
+*/
+class Sql_cmd_alter_table_repair_partition : public Sql_cmd_repair_table
+{
+public:
+ /**
+ Constructor, used to represent a ALTER TABLE REPAIR PARTITION statement.
+ */
+ Sql_cmd_alter_table_repair_partition()
+ : Sql_cmd_repair_table()
+ {}
+
+ ~Sql_cmd_alter_table_repair_partition()
+ {}
+
+ bool execute(THD *thd);
+
+ /* Override SQLCOM_REPAIR, since it is an ALTER command */
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ALTER_TABLE;
+ }
+};
+
+
+/**
+ Class that represents the ALTER TABLE t1 TRUNCATE PARTITION p statement.
+*/
+class Sql_cmd_alter_table_truncate_partition : public Sql_cmd_truncate_table
+{
+public:
+ /**
+ Constructor, used to represent a ALTER TABLE TRUNCATE PARTITION statement.
+ */
+ Sql_cmd_alter_table_truncate_partition()
+ {}
+
+ virtual ~Sql_cmd_alter_table_truncate_partition()
+ {}
+
+ bool execute(THD *thd);
+
+ /* Override SQLCOM_TRUNCATE, since it is an ALTER command */
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_ALTER_TABLE;
+ }
+};
+
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
+#endif /* SQL_PARTITION_ADMIN_H */
diff --git a/sql/sql_plist.h b/sql/sql_plist.h
new file mode 100644
index 00000000000..df50cccc874
--- /dev/null
+++ b/sql/sql_plist.h
@@ -0,0 +1,294 @@
+#ifndef SQL_PLIST_H
+#define SQL_PLIST_H
+/* Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+
+template <typename T, typename L>
+class I_P_List_iterator;
+class I_P_List_null_counter;
+template <typename T> class I_P_List_no_push_back;
+
+
+/**
+ Intrusive parameterized list.
+
+ Unlike I_List does not require its elements to be descendant of ilink
+ class and therefore allows them to participate in several such lists
+ simultaneously.
+
+ Unlike List is doubly-linked list and thus supports efficient deletion
+ of element without iterator.
+
+ @param T Type of elements which will belong to list.
+ @param B Class which via its methods specifies which members
+ of T should be used for participating in this list.
+ Here is typical layout of such class:
+
+ struct B
+ {
+ static inline T **next_ptr(T *el)
+ {
+ return &el->next;
+ }
+ static inline T ***prev_ptr(T *el)
+ {
+ return &el->prev;
+ }
+ };
+ @param C Policy class specifying how counting of elements in the list
+ should be done. Instance of this class is also used as a place
+ where information about number of list elements is stored.
+ @sa I_P_List_null_counter, I_P_List_counter
+ @param I Policy class specifying whether I_P_List should support
+ efficient push_back() operation. Instance of this class
+ is used as place where we store information to support
+ this operation.
+ @sa I_P_List_no_push_back, I_P_List_fast_push_back.
+*/
+
+template <typename T, typename B,
+ typename C = I_P_List_null_counter,
+ typename I = I_P_List_no_push_back<T> >
+class I_P_List : public C, public I
+{
+ T *m_first;
+
+ /*
+ Do not prohibit copying of I_P_List object to simplify their usage in
+ backup/restore scenarios. Note that performing any operations on such
+ is a bad idea.
+ */
+public:
+ I_P_List() : I(&m_first), m_first(NULL) {};
+ /*
+ empty() is used in many places in the code instead of a constructor, to
+ initialize a bzero-ed I_P_List instance.
+ */
+
+ inline void empty() { m_first= NULL; C::reset(); I::set_last(&m_first); }
+ inline bool is_empty() const { return (m_first == NULL); }
+ inline void push_front(T* a)
+ {
+ *B::next_ptr(a)= m_first;
+ if (m_first)
+ *B::prev_ptr(m_first)= B::next_ptr(a);
+ else
+ I::set_last(B::next_ptr(a));
+ m_first= a;
+ *B::prev_ptr(a)= &m_first;
+ C::inc();
+ }
+ inline void push_back(T *a)
+ {
+ T **last= I::get_last();
+ *B::next_ptr(a)= *last;
+ *last= a;
+ *B::prev_ptr(a)= last;
+ I::set_last(B::next_ptr(a));
+ C::inc();
+ }
+ inline void insert_after(T *pos, T *a)
+ {
+ if (pos == NULL)
+ push_front(a);
+ else
+ {
+ *B::next_ptr(a)= *B::next_ptr(pos);
+ *B::prev_ptr(a)= B::next_ptr(pos);
+ *B::next_ptr(pos)= a;
+ if (*B::next_ptr(a))
+ {
+ T *old_next= *B::next_ptr(a);
+ *B::prev_ptr(old_next)= B::next_ptr(a);
+ }
+ else
+ I::set_last(B::next_ptr(a));
+ C::inc();
+ }
+ }
+ inline void remove(T *a)
+ {
+ T *next= *B::next_ptr(a);
+ if (next)
+ *B::prev_ptr(next)= *B::prev_ptr(a);
+ else
+ I::set_last(*B::prev_ptr(a));
+ **B::prev_ptr(a)= next;
+ C::dec();
+ }
+ inline T* front() { return m_first; }
+ inline const T *front() const { return m_first; }
+ inline T* pop_front()
+ {
+ T *result= front();
+
+ if (result)
+ remove(result);
+
+ return result;
+ }
+ void swap(I_P_List<T, B, C> &rhs)
+ {
+ swap_variables(T *, m_first, rhs.m_first);
+ I::swap(rhs);
+ if (m_first)
+ *B::prev_ptr(m_first)= &m_first;
+ else
+ I::set_last(&m_first);
+ if (rhs.m_first)
+ *B::prev_ptr(rhs.m_first)= &rhs.m_first;
+ else
+ I::set_last(&rhs.m_first);
+ C::swap(rhs);
+ }
+ typedef B Adapter;
+ typedef I_P_List<T, B, C, I> Base;
+ typedef I_P_List_iterator<T, Base> Iterator;
+ typedef I_P_List_iterator<const T, Base> Const_Iterator;
+#ifndef _lint
+ friend class I_P_List_iterator<T, Base>;
+ friend class I_P_List_iterator<const T, Base>;
+#endif
+};
+
+
+/**
+ Iterator for I_P_List.
+*/
+
+template <typename T, typename L>
+class I_P_List_iterator
+{
+ const L *list;
+ T *current;
+public:
+ I_P_List_iterator(const L &a)
+ : list(&a), current(a.m_first) {}
+ I_P_List_iterator(const L &a, T* current_arg)
+ : list(&a), current(current_arg) {}
+ inline void init(const L &a)
+ {
+ list= &a;
+ current= a.m_first;
+ }
+ /* Operator for it++ */
+ inline T* operator++(int)
+ {
+ T *result= current;
+ if (result)
+ current= *L::Adapter::next_ptr(current);
+ return result;
+ }
+ /* Operator for ++it */
+ inline T* operator++()
+ {
+ current= *L::Adapter::next_ptr(current);
+ return current;
+ }
+ inline void rewind()
+ {
+ current= list->m_first;
+ }
+};
+
+
+/**
+ Hook class which via its methods specifies which members
+ of T should be used for participating in a intrusive list.
+*/
+
+template <typename T, T* T::*next, T** T::*prev>
+struct I_P_List_adapter
+{
+ static inline T **next_ptr(T *el) { return &(el->*next); }
+ static inline const T* const* next_ptr(const T *el) { return &(el->*next); }
+ static inline T ***prev_ptr(T *el) { return &(el->*prev); }
+};
+
+
+/**
+ Element counting policy class for I_P_List to be used in
+ cases when no element counting should be done.
+*/
+
+class I_P_List_null_counter
+{
+protected:
+ void reset() {}
+ void inc() {}
+ void dec() {}
+ void swap(I_P_List_null_counter &rhs) {}
+};
+
+
+/**
+ Element counting policy class for I_P_List which provides
+ basic element counting.
+*/
+
+class I_P_List_counter
+{
+ uint m_counter;
+protected:
+ I_P_List_counter() : m_counter (0) {}
+ void reset() {m_counter= 0;}
+ void inc() {m_counter++;}
+ void dec() {m_counter--;}
+ void swap(I_P_List_counter &rhs)
+ { swap_variables(uint, m_counter, rhs.m_counter); }
+public:
+ uint elements() const { return m_counter; }
+};
+
+
+/**
+ A null insertion policy class for I_P_List to be used
+ in cases when push_back() operation is not necessary.
+*/
+
+template <typename T> class I_P_List_no_push_back
+{
+protected:
+ I_P_List_no_push_back(T **a) {};
+ void set_last(T **a) {}
+ /*
+ T** get_last() const method is intentionally left unimplemented
+ in order to prohibit usage of push_back() method in lists which
+ use this policy.
+ */
+ void swap(I_P_List_no_push_back<T> &rhs) {}
+};
+
+
+/**
+ An insertion policy class for I_P_List which can
+ be used when fast push_back() operation is required.
+*/
+
+template <typename T> class I_P_List_fast_push_back
+{
+ T **m_last;
+protected:
+ I_P_List_fast_push_back(T **a) : m_last(a) { };
+ void set_last(T **a) { m_last= a; }
+ T** get_last() const { return m_last; }
+ void swap(I_P_List_fast_push_back<T> &rhs)
+ { swap_variables(T**, m_last, rhs.m_last); }
+};
+
+#endif
diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc
new file mode 100644
index 00000000000..7fbd6b63490
--- /dev/null
+++ b/sql/sql_plugin.cc
@@ -0,0 +1,4090 @@
+/*
+ Copyright (c) 2005, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2014, SkySQL Ab.
+
+ 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 "sql_plugin.h" // Includes my_global.h
+#include "sql_priv.h" // SHOW_MY_BOOL
+#include "unireg.h"
+#include "sql_class.h" // set_var.h: THD
+#include "sys_vars_shared.h"
+#include "sql_locale.h"
+#include "sql_plugin.h"
+#include "sql_parse.h" // check_table_access
+#include "sql_base.h" // close_mysql_tables
+#include "key.h" // key_copy
+#include "sql_show.h" // remove_status_vars, add_status_vars
+#include "strfunc.h" // find_set
+#include "sql_acl.h" // *_ACL
+#include "records.h" // init_read_record, end_read_record
+#include <my_pthread.h>
+#include <my_getopt.h>
+#include "sql_audit.h"
+#include <mysql/plugin_auth.h>
+#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
+#include <mysql/plugin_auth.h>
+#include "sql_plugin_compat.h"
+
+#define REPORT_TO_LOG 1
+#define REPORT_TO_USER 2
+
+extern struct st_maria_plugin *mysql_optional_plugins[];
+extern struct st_maria_plugin *mysql_mandatory_plugins[];
+
+/**
+ @note The order of the enumeration is critical.
+ @see construct_options
+*/
+const char *global_plugin_typelib_names[]=
+ { "OFF", "ON", "FORCE", "FORCE_PLUS_PERMANENT", NULL };
+static TYPELIB global_plugin_typelib=
+ { array_elements(global_plugin_typelib_names)-1,
+ "", global_plugin_typelib_names, NULL };
+
+static I_List<i_string> opt_plugin_load_list;
+I_List<i_string> *opt_plugin_load_list_ptr= &opt_plugin_load_list;
+char *opt_plugin_dir_ptr;
+char opt_plugin_dir[FN_REFLEN];
+ulong plugin_maturity;
+
+/*
+ not really needed now, this map will become essential when we add more
+ maturity levels. We cannot change existing maturity constants,
+ so the next value - even if it will be MariaDB_PLUGIN_MATURITY_VERY_BUGGY -
+ will inevitably be larger than MariaDB_PLUGIN_MATURITY_STABLE.
+ To be able to compare them we use this mapping array
+*/
+uint plugin_maturity_map[]=
+{ 0, 1, 2, 3, 4, 5, 6 };
+
+/*
+ When you ad a new plugin type, add both a string and make sure that the
+ init and deinit array are correctly updated.
+*/
+const LEX_STRING plugin_type_names[MYSQL_MAX_PLUGIN_TYPE_NUM]=
+{
+ { C_STRING_WITH_LEN("UDF") },
+ { C_STRING_WITH_LEN("STORAGE ENGINE") },
+ { C_STRING_WITH_LEN("FTPARSER") },
+ { C_STRING_WITH_LEN("DAEMON") },
+ { C_STRING_WITH_LEN("INFORMATION SCHEMA") },
+ { C_STRING_WITH_LEN("AUDIT") },
+ { C_STRING_WITH_LEN("REPLICATION") },
+ { C_STRING_WITH_LEN("AUTHENTICATION") }
+};
+
+extern int initialize_schema_table(st_plugin_int *plugin);
+extern int finalize_schema_table(st_plugin_int *plugin);
+
+extern int initialize_audit_plugin(st_plugin_int *plugin);
+extern int finalize_audit_plugin(st_plugin_int *plugin);
+
+/*
+ The number of elements in both plugin_type_initialize and
+ plugin_type_deinitialize should equal to the number of plugins
+ defined.
+*/
+plugin_type_init plugin_type_initialize[MYSQL_MAX_PLUGIN_TYPE_NUM]=
+{
+ 0,ha_initialize_handlerton,0,0,initialize_schema_table,
+ initialize_audit_plugin, 0, 0
+};
+
+plugin_type_init plugin_type_deinitialize[MYSQL_MAX_PLUGIN_TYPE_NUM]=
+{
+ 0,ha_finalize_handlerton,0,0,finalize_schema_table,
+ finalize_audit_plugin, 0, 0
+};
+
+#ifdef HAVE_DLOPEN
+static const char *plugin_interface_version_sym=
+ "_mysql_plugin_interface_version_";
+static const char *sizeof_st_plugin_sym=
+ "_mysql_sizeof_struct_st_plugin_";
+static const char *plugin_declarations_sym= "_mysql_plugin_declarations_";
+static int min_plugin_interface_version= MYSQL_PLUGIN_INTERFACE_VERSION & ~0xFF;
+static const char *maria_plugin_interface_version_sym=
+ "_maria_plugin_interface_version_";
+static const char *maria_sizeof_st_plugin_sym=
+ "_maria_sizeof_struct_st_plugin_";
+static const char *maria_plugin_declarations_sym=
+ "_maria_plugin_declarations_";
+static int min_maria_plugin_interface_version=
+ MARIA_PLUGIN_INTERFACE_VERSION & ~0xFF;
+#endif
+
+/* Note that 'int version' must be the first field of every plugin
+ sub-structure (plugin->info).
+*/
+static int min_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]=
+{
+ 0x0000,
+ MYSQL_HANDLERTON_INTERFACE_VERSION,
+ MYSQL_FTPARSER_INTERFACE_VERSION,
+ MYSQL_DAEMON_INTERFACE_VERSION,
+ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION,
+ MYSQL_AUDIT_INTERFACE_VERSION,
+ MYSQL_REPLICATION_INTERFACE_VERSION,
+ MIN_AUTHENTICATION_INTERFACE_VERSION
+};
+static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]=
+{
+ 0x0000, /* UDF: not implemented */
+ MYSQL_HANDLERTON_INTERFACE_VERSION,
+ MYSQL_FTPARSER_INTERFACE_VERSION,
+ MYSQL_DAEMON_INTERFACE_VERSION,
+ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION,
+ MYSQL_AUDIT_INTERFACE_VERSION,
+ MYSQL_REPLICATION_INTERFACE_VERSION,
+ MYSQL_AUTHENTICATION_INTERFACE_VERSION
+};
+
+static struct
+{
+ const char *plugin_name;
+ enum enum_plugin_load_option override;
+} override_plugin_load_policy[]={
+ /*
+ If the performance schema is compiled in,
+ treat the storage engine plugin as 'mandatory',
+ to suppress any plugin-level options such as '--performance-schema'.
+ This is specific to the performance schema, and is done on purpose:
+ the server-level option '--performance-schema' controls the overall
+ performance schema initialization, which consists of much more that
+ the underlying storage engine initialization.
+ See mysqld.cc, set_vars.cc.
+ Suppressing ways to interfere directly with the storage engine alone
+ prevents awkward situations where:
+ - the user wants the performance schema functionality, by using
+ '--enable-performance-schema' (the server option),
+ - yet disable explicitly a component needed for the functionality
+ to work, by using '--skip-performance-schema' (the plugin)
+ */
+ { "performance_schema", PLUGIN_FORCE },
+
+ /* we disable few other plugins by default */
+ { "ndbcluster", PLUGIN_OFF },
+ { "feedback", PLUGIN_OFF }
+};
+
+/* support for Services */
+
+#include "sql_plugin_services.h"
+
+/*
+ A mutex LOCK_plugin must be acquired before accessing the
+ following variables/structures.
+ We are always manipulating ref count, so a rwlock here is unneccessary.
+*/
+mysql_mutex_t LOCK_plugin;
+static DYNAMIC_ARRAY plugin_dl_array;
+static DYNAMIC_ARRAY plugin_array;
+static HASH plugin_hash[MYSQL_MAX_PLUGIN_TYPE_NUM];
+static MEM_ROOT plugin_mem_root;
+static bool reap_needed= false;
+static int plugin_array_version=0;
+
+static bool initialized= 0;
+ulong dlopen_count;
+
+
+/*
+ write-lock on LOCK_system_variables_hash is required before modifying
+ the following variables/structures
+*/
+static MEM_ROOT plugin_vars_mem_root;
+static uint global_variables_dynamic_size= 0;
+static HASH bookmark_hash;
+
+
+/*
+ hidden part of opaque value passed to variable check functions.
+ Used to provide a object-like structure to non C++ consumers.
+*/
+struct st_item_value_holder : public st_mysql_value
+{
+ Item *item;
+};
+
+
+/*
+ stored in bookmark_hash, this structure is never removed from the
+ hash and is used to mark a single offset for a thd local variable
+ even if plugins have been uninstalled and reinstalled, repeatedly.
+ This structure is allocated from plugin_mem_root.
+
+ The key format is as follows:
+ 1 byte - variable type code
+ name_len bytes - variable name
+ '\0' - end of key
+*/
+struct st_bookmark
+{
+ uint name_len;
+ int offset;
+ uint version;
+ char key[1];
+};
+
+
+/*
+ skeleton of a plugin variable - portion of structure common to all.
+*/
+struct st_mysql_sys_var
+{
+ MYSQL_PLUGIN_VAR_HEADER;
+};
+
+static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var);
+
+
+/*
+ sys_var class for access to all plugin variables visible to the user
+*/
+class sys_var_pluginvar: public sys_var
+{
+public:
+ struct st_plugin_int *plugin;
+ struct st_mysql_sys_var *plugin_var;
+ static void *operator new(size_t size, MEM_ROOT *mem_root)
+ { return (void*) alloc_root(mem_root, size); }
+ static void operator delete(void *ptr_arg,size_t size)
+ { TRASH(ptr_arg, size); }
+
+ sys_var_pluginvar(sys_var_chain *chain, const char *name_arg,
+ struct st_mysql_sys_var *plugin_var_arg)
+ :sys_var(chain, name_arg, plugin_var_arg->comment,
+ (plugin_var_arg->flags & PLUGIN_VAR_THDLOCAL ? SESSION : GLOBAL) |
+ (plugin_var_arg->flags & PLUGIN_VAR_READONLY ? READONLY : 0),
+ 0, -1, NO_ARG, pluginvar_show_type(plugin_var_arg), 0, 0,
+ VARIABLE_NOT_IN_BINLOG, NULL, NULL, NULL),
+ plugin_var(plugin_var_arg)
+ { plugin_var->name= name_arg; }
+ sys_var_pluginvar *cast_pluginvar() { return this; }
+ bool check_update_type(Item_result type);
+ SHOW_TYPE show_type();
+ uchar* real_value_ptr(THD *thd, enum_var_type type);
+ TYPELIB* plugin_var_typelib(void);
+ uchar* do_value_ptr(THD *thd, enum_var_type type, LEX_STRING *base);
+ uchar* session_value_ptr(THD *thd, LEX_STRING *base)
+ { return do_value_ptr(thd, OPT_SESSION, base); }
+ uchar* global_value_ptr(THD *thd, LEX_STRING *base)
+ { return do_value_ptr(thd, OPT_GLOBAL, base); }
+ bool do_check(THD *thd, set_var *var);
+ virtual void session_save_default(THD *thd, set_var *var) {}
+ virtual void global_save_default(THD *thd, set_var *var) {}
+ bool session_update(THD *thd, set_var *var);
+ bool global_update(THD *thd, set_var *var);
+};
+
+
+/* prototypes */
+static void plugin_load(MEM_ROOT *tmp_root);
+static bool plugin_load_list(MEM_ROOT *, const char *);
+static int test_plugin_options(MEM_ROOT *, struct st_plugin_int *,
+ int *, char **);
+static bool register_builtin(struct st_maria_plugin *, struct st_plugin_int *,
+ struct st_plugin_int **);
+static void unlock_variables(THD *thd, struct system_variables *vars);
+static void cleanup_variables(THD *thd, struct system_variables *vars);
+static void plugin_vars_free_values(sys_var *vars);
+static void restore_ptr_backup(uint n, st_ptr_backup *backup);
+static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref plugin);
+static void intern_plugin_unlock(LEX *lex, plugin_ref plugin);
+static void reap_plugins(void);
+
+static void report_error(int where_to, uint error, ...)
+{
+ va_list args;
+ DBUG_ASSERT(where_to & (REPORT_TO_USER | REPORT_TO_LOG));
+ if (where_to & REPORT_TO_USER)
+ {
+ va_start(args, error);
+ my_printv_error(error, ER(error), MYF(0), args);
+ va_end(args);
+ }
+ if (where_to & REPORT_TO_LOG)
+ {
+ va_start(args, error);
+ error_log_print(ERROR_LEVEL, ER_DEFAULT(error), args);
+ va_end(args);
+ }
+}
+
+/**
+ Check if the provided path is valid in the sense that it does cause
+ a relative reference outside the directory.
+
+ @note Currently, this function only check if there are any
+ characters in FN_DIRSEP in the string, but it might change in the
+ future.
+
+ @code
+ check_valid_path("../foo.so") -> true
+ check_valid_path("foo.so") -> false
+ @endcode
+ */
+bool check_valid_path(const char *path, size_t len)
+{
+ size_t prefix= my_strcspn(files_charset_info, path, path + len, FN_DIRSEP);
+ return prefix < len;
+}
+
+static void fix_dl_name(MEM_ROOT *root, LEX_STRING *dl)
+{
+ const size_t so_ext_len= sizeof(SO_EXT) - 1;
+ if (my_strcasecmp(&my_charset_latin1, dl->str + dl->length - so_ext_len,
+ SO_EXT))
+ {
+ char *s= (char*)alloc_root(root, dl->length + so_ext_len + 1);
+ memcpy(s, dl->str, dl->length);
+ strcpy(s + dl->length, SO_EXT);
+ dl->str= s;
+ dl->length+= so_ext_len;
+ }
+}
+
+
+/****************************************************************************
+ Value type thunks, allows the C world to play in the C++ world
+****************************************************************************/
+
+static int item_value_type(struct st_mysql_value *value)
+{
+ switch (((st_item_value_holder*)value)->item->result_type()) {
+ case INT_RESULT:
+ return MYSQL_VALUE_TYPE_INT;
+ case REAL_RESULT:
+ return MYSQL_VALUE_TYPE_REAL;
+ default:
+ return MYSQL_VALUE_TYPE_STRING;
+ }
+}
+
+static const char *item_val_str(struct st_mysql_value *value,
+ char *buffer, int *length)
+{
+ String str(buffer, *length, system_charset_info), *res;
+ if (!(res= ((st_item_value_holder*)value)->item->val_str(&str)))
+ return NULL;
+ *length= res->length();
+ if (res->c_ptr_quick() == buffer)
+ return buffer;
+
+ /*
+ Lets be nice and create a temporary string since the
+ buffer was too small
+ */
+ return current_thd->strmake(res->ptr(), res->length());
+}
+
+
+static int item_val_int(struct st_mysql_value *value, long long *buf)
+{
+ Item *item= ((st_item_value_holder*)value)->item;
+ *buf= item->val_int();
+ if (item->is_null())
+ return 1;
+ return 0;
+}
+
+static int item_is_unsigned(struct st_mysql_value *value)
+{
+ Item *item= ((st_item_value_holder*)value)->item;
+ return item->unsigned_flag;
+}
+
+static int item_val_real(struct st_mysql_value *value, double *buf)
+{
+ Item *item= ((st_item_value_holder*)value)->item;
+ *buf= item->val_real();
+ if (item->is_null())
+ return 1;
+ return 0;
+}
+
+
+/****************************************************************************
+ Plugin support code
+****************************************************************************/
+
+#ifdef HAVE_DLOPEN
+
+static struct st_plugin_dl *plugin_dl_find(const LEX_STRING *dl)
+{
+ uint i;
+ struct st_plugin_dl *tmp;
+ DBUG_ENTER("plugin_dl_find");
+ for (i= 0; i < plugin_dl_array.elements; i++)
+ {
+ tmp= *dynamic_element(&plugin_dl_array, i, struct st_plugin_dl **);
+ if (tmp->ref_count &&
+ ! my_strnncoll(files_charset_info,
+ (const uchar *)dl->str, dl->length,
+ (const uchar *)tmp->dl.str, tmp->dl.length))
+ DBUG_RETURN(tmp);
+ }
+ DBUG_RETURN(0);
+}
+
+
+static st_plugin_dl *plugin_dl_insert_or_reuse(struct st_plugin_dl *plugin_dl)
+{
+ uint i;
+ struct st_plugin_dl *tmp;
+ DBUG_ENTER("plugin_dl_insert_or_reuse");
+ for (i= 0; i < plugin_dl_array.elements; i++)
+ {
+ tmp= *dynamic_element(&plugin_dl_array, i, struct st_plugin_dl **);
+ if (! tmp->ref_count)
+ {
+ memcpy(tmp, plugin_dl, sizeof(struct st_plugin_dl));
+ DBUG_RETURN(tmp);
+ }
+ }
+ if (insert_dynamic(&plugin_dl_array, (uchar*)&plugin_dl))
+ DBUG_RETURN(0);
+ tmp= *dynamic_element(&plugin_dl_array, plugin_dl_array.elements - 1,
+ struct st_plugin_dl **)=
+ (struct st_plugin_dl *) memdup_root(&plugin_mem_root, (uchar*)plugin_dl,
+ sizeof(struct st_plugin_dl));
+ DBUG_RETURN(tmp);
+}
+#endif /* HAVE_DLOPEN */
+
+
+static void free_plugin_mem(struct st_plugin_dl *p)
+{
+#ifdef HAVE_DLOPEN
+ if (p->ptr_backup)
+ {
+ DBUG_ASSERT(p->nbackups);
+ DBUG_ASSERT(p->handle);
+ restore_ptr_backup(p->nbackups, p->ptr_backup);
+ my_free(p->ptr_backup);
+ }
+ if (p->handle)
+ dlclose(p->handle);
+#endif
+ my_free(p->dl.str);
+ if (p->allocated)
+ my_free(p->plugins);
+}
+
+
+/**
+ Reads data from mysql plugin interface
+
+ @param plugin_dl Structure where the data should be put
+ @param sym Reverence on version info
+ @param dlpath Path to the module
+ @param report What errors should be reported
+
+ @retval FALSE OK
+ @retval TRUE ERROR
+*/
+
+#ifdef HAVE_DLOPEN
+static my_bool read_mysql_plugin_info(struct st_plugin_dl *plugin_dl,
+ void *sym, char *dlpath,
+ int report)
+{
+ DBUG_ENTER("read_maria_plugin_info");
+ /* Determine interface version */
+ if (!sym)
+ {
+ report_error(report, ER_CANT_FIND_DL_ENTRY, plugin_interface_version_sym);
+ DBUG_RETURN(TRUE);
+ }
+ plugin_dl->mariaversion= 0;
+ plugin_dl->mysqlversion= *(int *)sym;
+ /* Versioning */
+ if (plugin_dl->mysqlversion < min_plugin_interface_version ||
+ (plugin_dl->mysqlversion >> 8) > (MYSQL_PLUGIN_INTERFACE_VERSION >> 8))
+ {
+ report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, ENOEXEC,
+ "plugin interface version mismatch");
+ DBUG_RETURN(TRUE);
+ }
+ /* Find plugin declarations */
+ if (!(sym= dlsym(plugin_dl->handle, plugin_declarations_sym)))
+ {
+ report_error(report, ER_CANT_FIND_DL_ENTRY, plugin_declarations_sym);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* convert mysql declaration to maria one */
+ {
+ int i;
+ uint sizeof_st_plugin;
+ struct st_mysql_plugin *old;
+ struct st_maria_plugin *cur;
+ char *ptr= (char *)sym;
+
+ if ((sym= dlsym(plugin_dl->handle, sizeof_st_plugin_sym)))
+ sizeof_st_plugin= *(int *)sym;
+ else
+ {
+ DBUG_ASSERT(min_plugin_interface_version == 0);
+ sizeof_st_plugin= (int)offsetof(struct st_mysql_plugin, version);
+ }
+
+ for (i= 0;
+ ((struct st_mysql_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ /* no op */;
+
+ cur= (struct st_maria_plugin*)
+ my_malloc((i + 1) * sizeof(struct st_maria_plugin),
+ MYF(MY_ZEROFILL|MY_WME));
+ if (!cur)
+ {
+ report_error(report, ER_OUTOFMEMORY,
+ static_cast<int>(plugin_dl->dl.length));
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ All st_plugin fields not initialized in the plugin explicitly, are
+ set to 0. It matches C standard behaviour for struct initializers that
+ have less values than the struct definition.
+ */
+ for (i=0;
+ (old= (struct st_mysql_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ {
+
+ cur[i].type= old->type;
+ cur[i].info= old->info;
+ cur[i].name= old->name;
+ cur[i].author= old->author;
+ cur[i].descr= old->descr;
+ cur[i].license= old->license;
+ cur[i].init= old->init;
+ cur[i].deinit= old->deinit;
+ cur[i].version= old->version;
+ cur[i].status_vars= old->status_vars;
+ cur[i].system_vars= old->system_vars;
+ /*
+ Something like this should be added to process
+ new mysql plugin versions:
+ if (plugin_dl->mysqlversion > 0x0101)
+ {
+ cur[i].newfield= CONSTANT_MEANS_UNKNOWN;
+ }
+ else
+ {
+ cur[i].newfield= old->newfield;
+ }
+ */
+ /* Maria only fields */
+ cur[i].version_info= "Unknown";
+ cur[i].maturity= MariaDB_PLUGIN_MATURITY_UNKNOWN;
+ }
+ plugin_dl->allocated= true;
+ plugin_dl->plugins= (struct st_maria_plugin *)cur;
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Reads data from maria plugin interface
+
+ @param plugin_dl Structure where the data should be put
+ @param sym Reverence on version info
+ @param dlpath Path to the module
+ @param report what errors should be reported
+
+ @retval FALSE OK
+ @retval TRUE ERROR
+*/
+
+static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl,
+ void *sym, char *dlpath,
+ int report)
+{
+ DBUG_ENTER("read_maria_plugin_info");
+
+ /* Determine interface version */
+ if (!(sym))
+ {
+ /*
+ Actually this branch impossible because in case of absence of maria
+ version we try mysql version.
+ */
+ report_error(report, ER_CANT_FIND_DL_ENTRY,
+ maria_plugin_interface_version_sym);
+ DBUG_RETURN(TRUE);
+ }
+ plugin_dl->mariaversion= *(int *)sym;
+ plugin_dl->mysqlversion= 0;
+ /* Versioning */
+ if (plugin_dl->mariaversion < min_maria_plugin_interface_version ||
+ (plugin_dl->mariaversion >> 8) > (MARIA_PLUGIN_INTERFACE_VERSION >> 8))
+ {
+ report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, ENOEXEC,
+ "plugin interface version mismatch");
+ DBUG_RETURN(TRUE);
+ }
+ /* Find plugin declarations */
+ if (!(sym= dlsym(plugin_dl->handle, maria_plugin_declarations_sym)))
+ {
+ report_error(report, ER_CANT_FIND_DL_ENTRY, maria_plugin_declarations_sym);
+ DBUG_RETURN(TRUE);
+ }
+ if (plugin_dl->mariaversion != MARIA_PLUGIN_INTERFACE_VERSION)
+ {
+ uint sizeof_st_plugin;
+ struct st_maria_plugin *old, *cur;
+ char *ptr= (char *)sym;
+
+ if ((sym= dlsym(plugin_dl->handle, maria_sizeof_st_plugin_sym)))
+ sizeof_st_plugin= *(int *)sym;
+ else
+ {
+ report_error(report, ER_CANT_FIND_DL_ENTRY, maria_sizeof_st_plugin_sym);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (sizeof_st_plugin != sizeof(st_mysql_plugin))
+ {
+ int i;
+ for (i= 0;
+ ((struct st_maria_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ /* no op */;
+
+ cur= (struct st_maria_plugin*)
+ my_malloc((i + 1) * sizeof(struct st_maria_plugin),
+ MYF(MY_ZEROFILL|MY_WME));
+ if (!cur)
+ {
+ report_error(report, ER_OUTOFMEMORY,
+ static_cast<int>(plugin_dl->dl.length));
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ All st_plugin fields not initialized in the plugin explicitly, are
+ set to 0. It matches C standard behaviour for struct initializers that
+ have less values than the struct definition.
+ */
+ for (i=0;
+ (old= (struct st_maria_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ memcpy(cur + i, old, MY_MIN(sizeof(cur[i]), sizeof_st_plugin));
+
+ sym= cur;
+ plugin_dl->allocated= true;
+ }
+ else
+ sym= ptr;
+ }
+ plugin_dl->plugins= (struct st_maria_plugin *)sym;
+
+ DBUG_RETURN(FALSE);
+}
+#endif /* HAVE_DLOPEN */
+
+static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report)
+{
+#ifdef HAVE_DLOPEN
+ char dlpath[FN_REFLEN];
+ uint plugin_dir_len, dummy_errors, dlpathlen, i;
+ struct st_plugin_dl *tmp= 0, plugin_dl;
+ void *sym;
+ st_ptr_backup tmp_backup[array_elements(list_of_services)];
+ DBUG_ENTER("plugin_dl_add");
+ DBUG_PRINT("enter", ("dl->str: '%s', dl->length: %d",
+ dl->str, (int) dl->length));
+ mysql_mutex_assert_owner(&LOCK_plugin);
+ plugin_dir_len= strlen(opt_plugin_dir);
+ /*
+ Ensure that the dll doesn't have a path.
+ This is done to ensure that only approved libraries from the
+ plugin directory are used (to make this even remotely secure).
+ */
+ if (check_valid_path(dl->str, dl->length) ||
+ check_string_char_length((LEX_STRING *) dl, "", NAME_CHAR_LEN,
+ system_charset_info, 1) ||
+ plugin_dir_len + dl->length + 1 >= FN_REFLEN)
+ {
+ report_error(report, ER_UDF_NO_PATHS);
+ DBUG_RETURN(0);
+ }
+ /* If this dll is already loaded just increase ref_count. */
+ if ((tmp= plugin_dl_find(dl)))
+ {
+ tmp->ref_count++;
+ DBUG_RETURN(tmp);
+ }
+ bzero(&plugin_dl, sizeof(plugin_dl));
+ /* Compile dll path */
+ strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", dl->str, NullS);
+ (void) unpack_filename(dlpath, dlpath);
+ plugin_dl.ref_count= 1;
+ /* Open new dll handle */
+ if (!(plugin_dl.handle= dlopen(dlpath, RTLD_NOW)))
+ {
+ const char *errmsg=dlerror();
+ dlpathlen= strlen(dlpath);
+ if (!strncmp(dlpath, errmsg, dlpathlen))
+ { // if errmsg starts from dlpath, trim this prefix.
+ errmsg+=dlpathlen;
+ if (*errmsg == ':') errmsg++;
+ if (*errmsg == ' ') errmsg++;
+ }
+ report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, errno, errmsg);
+ goto ret;
+ }
+ dlopen_count++;
+
+ /* Checks which plugin interface present and reads info */
+ if (!(sym= dlsym(plugin_dl.handle, maria_plugin_interface_version_sym)))
+ {
+ if (read_mysql_plugin_info(&plugin_dl,
+ dlsym(plugin_dl.handle,
+ plugin_interface_version_sym),
+ dlpath,
+ report))
+ goto ret;
+ }
+ else
+ {
+ if (read_maria_plugin_info(&plugin_dl, sym, dlpath, report))
+ goto ret;
+ }
+
+ /* link the services in */
+ for (i= 0; i < array_elements(list_of_services); i++)
+ {
+ if ((sym= dlsym(plugin_dl.handle, list_of_services[i].name)))
+ {
+ void **ptr= (void **)sym;
+ uint ver= (uint)(intptr)*ptr;
+ if (ver > list_of_services[i].version ||
+ (ver >> 8) < (list_of_services[i].version >> 8))
+ {
+ char buf[MYSQL_ERRMSG_SIZE];
+ my_snprintf(buf, sizeof(buf),
+ "service '%s' interface version mismatch",
+ list_of_services[i].name);
+ report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, ENOEXEC, buf);
+ goto ret;
+ }
+ tmp_backup[plugin_dl.nbackups++].save(ptr);
+ *ptr= list_of_services[i].service;
+ }
+ }
+
+ if (plugin_dl.nbackups)
+ {
+ size_t bytes= plugin_dl.nbackups * sizeof(plugin_dl.ptr_backup[0]);
+ plugin_dl.ptr_backup= (st_ptr_backup *)my_malloc(bytes, MYF(0));
+ if (!plugin_dl.ptr_backup)
+ {
+ restore_ptr_backup(plugin_dl.nbackups, tmp_backup);
+ report_error(report, ER_OUTOFMEMORY, bytes);
+ goto ret;
+ }
+ memcpy(plugin_dl.ptr_backup, tmp_backup, bytes);
+ }
+
+ /* Duplicate and convert dll name */
+ plugin_dl.dl.length= dl->length * files_charset_info->mbmaxlen + 1;
+ if (! (plugin_dl.dl.str= (char*) my_malloc(plugin_dl.dl.length, MYF(0))))
+ {
+ report_error(report, ER_OUTOFMEMORY,
+ static_cast<int>(plugin_dl.dl.length));
+ goto ret;
+ }
+ plugin_dl.dl.length= copy_and_convert(plugin_dl.dl.str, plugin_dl.dl.length,
+ files_charset_info, dl->str, dl->length, system_charset_info,
+ &dummy_errors);
+ plugin_dl.dl.str[plugin_dl.dl.length]= 0;
+ /* Add this dll to array */
+ if (! (tmp= plugin_dl_insert_or_reuse(&plugin_dl)))
+ {
+ report_error(report, ER_OUTOFMEMORY,
+ static_cast<int>(sizeof(struct st_plugin_dl)));
+ goto ret;
+ }
+
+ret:
+ if (!tmp)
+ free_plugin_mem(&plugin_dl);
+
+ DBUG_RETURN(tmp);
+
+#else
+ DBUG_ENTER("plugin_dl_add");
+ report_error(report, ER_FEATURE_DISABLED, "plugin", "HAVE_DLOPEN");
+ DBUG_RETURN(0);
+#endif
+}
+
+
+static void plugin_dl_del(struct st_plugin_dl *plugin_dl)
+{
+ DBUG_ENTER("plugin_dl_del");
+
+ if (!plugin_dl)
+ DBUG_VOID_RETURN;
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ /* Do not remove this element, unless no other plugin uses this dll. */
+ if (! --plugin_dl->ref_count)
+ {
+ free_plugin_mem(plugin_dl);
+ bzero(plugin_dl, sizeof(struct st_plugin_dl));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+static struct st_plugin_int *plugin_find_internal(const LEX_STRING *name, int type)
+{
+ uint i;
+ DBUG_ENTER("plugin_find_internal");
+ if (! initialized)
+ DBUG_RETURN(0);
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (type == MYSQL_ANY_PLUGIN)
+ {
+ for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ {
+ struct st_plugin_int *plugin= (st_plugin_int *)
+ my_hash_search(&plugin_hash[i], (const uchar *)name->str, name->length);
+ if (plugin)
+ DBUG_RETURN(plugin);
+ }
+ }
+ else
+ DBUG_RETURN((st_plugin_int *)
+ my_hash_search(&plugin_hash[type], (const uchar *)name->str,
+ name->length));
+ DBUG_RETURN(0);
+}
+
+
+static SHOW_COMP_OPTION plugin_status(const LEX_STRING *name, int type)
+{
+ SHOW_COMP_OPTION rc= SHOW_OPTION_NO;
+ struct st_plugin_int *plugin;
+ DBUG_ENTER("plugin_is_ready");
+ mysql_mutex_lock(&LOCK_plugin);
+ if ((plugin= plugin_find_internal(name, type)))
+ {
+ rc= SHOW_OPTION_DISABLED;
+ if (plugin->state == PLUGIN_IS_READY)
+ rc= SHOW_OPTION_YES;
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_RETURN(rc);
+}
+
+
+bool plugin_is_ready(const LEX_STRING *name, int type)
+{
+ bool rc= FALSE;
+ if (plugin_status(name, type) == SHOW_OPTION_YES)
+ rc= TRUE;
+ return rc;
+}
+
+
+SHOW_COMP_OPTION plugin_status(const char *name, size_t len, int type)
+{
+ LEX_STRING plugin_name= { (char *) name, len };
+ return plugin_status(&plugin_name, type);
+}
+
+
+static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref rc)
+{
+ st_plugin_int *pi= plugin_ref_to_int(rc);
+ DBUG_ENTER("intern_plugin_lock");
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (pi->state & (PLUGIN_IS_READY | PLUGIN_IS_UNINITIALIZED |
+ PLUGIN_IS_DELETED))
+ {
+ plugin_ref plugin;
+#ifdef DBUG_OFF
+ /*
+ In optimized builds we don't do reference counting for built-in
+ (plugin->plugin_dl == 0) plugins.
+ */
+ if (!pi->plugin_dl)
+ DBUG_RETURN(pi);
+
+ plugin= pi;
+#else
+ /*
+ For debugging, we do an additional malloc which allows the
+ memory manager and/or valgrind to track locked references and
+ double unlocks to aid resolving reference counting problems.
+ */
+ if (!(plugin= (plugin_ref) my_malloc(sizeof(pi), MYF(MY_WME))))
+ DBUG_RETURN(NULL);
+
+ *plugin= pi;
+#endif
+ pi->ref_count++;
+ DBUG_PRINT("lock",("thd: 0x%lx plugin: \"%s\" LOCK ref_count: %d",
+ (long) current_thd, pi->name.str, pi->ref_count));
+
+ if (lex)
+ insert_dynamic(&lex->plugins, (uchar*)&plugin);
+ DBUG_RETURN(plugin);
+ }
+ DBUG_RETURN(NULL);
+}
+
+
+plugin_ref plugin_lock(THD *thd, plugin_ref ptr)
+{
+ LEX *lex= thd ? thd->lex : 0;
+ plugin_ref rc;
+ DBUG_ENTER("plugin_lock");
+
+#ifdef DBUG_OFF
+ /*
+ In optimized builds we don't do reference counting for built-in
+ (plugin->plugin_dl == 0) plugins.
+
+ Note that we access plugin->plugin_dl outside of LOCK_plugin, and for
+ dynamic plugins a 'plugin' could correspond to plugin that was unloaded
+ meanwhile! But because st_plugin_int is always allocated on
+ plugin_mem_root, the pointer can never be invalid - the memory is never
+ freed.
+ Of course, the memory that 'plugin' points to can be overwritten by
+ another plugin being loaded, but plugin->plugin_dl can never change
+ from zero to non-zero or vice versa.
+ That is, it's always safe to check for plugin->plugin_dl==0 even
+ without a mutex.
+ */
+ if (! plugin_dlib(ptr))
+ {
+ plugin_ref_to_int(ptr)->locks_total++;
+ DBUG_RETURN(ptr);
+ }
+#endif
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin_ref_to_int(ptr)->locks_total++;
+ rc= intern_plugin_lock(lex, ptr);
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_RETURN(rc);
+}
+
+
+plugin_ref plugin_lock_by_name(THD *thd, const LEX_STRING *name, int type)
+{
+ LEX *lex= thd ? thd->lex : 0;
+ plugin_ref rc= NULL;
+ st_plugin_int *plugin;
+ DBUG_ENTER("plugin_lock_by_name");
+ mysql_mutex_lock(&LOCK_plugin);
+ if ((plugin= plugin_find_internal(name, type)))
+ rc= intern_plugin_lock(lex, plugin_int_to_ref(plugin));
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_RETURN(rc);
+}
+
+
+static st_plugin_int *plugin_insert_or_reuse(struct st_plugin_int *plugin)
+{
+ uint i;
+ struct st_plugin_int *tmp;
+ DBUG_ENTER("plugin_insert_or_reuse");
+ for (i= 0; i < plugin_array.elements; i++)
+ {
+ tmp= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
+ if (tmp->state == PLUGIN_IS_FREED)
+ {
+ memcpy(tmp, plugin, sizeof(struct st_plugin_int));
+ DBUG_RETURN(tmp);
+ }
+ }
+ if (insert_dynamic(&plugin_array, (uchar*)&plugin))
+ DBUG_RETURN(0);
+ tmp= *dynamic_element(&plugin_array, plugin_array.elements - 1,
+ struct st_plugin_int **)=
+ (struct st_plugin_int *) memdup_root(&plugin_mem_root, (uchar*)plugin,
+ sizeof(struct st_plugin_int));
+ DBUG_RETURN(tmp);
+}
+
+
+/*
+ NOTE
+ Requires that a write-lock is held on LOCK_system_variables_hash
+*/
+static bool plugin_add(MEM_ROOT *tmp_root,
+ const LEX_STRING *name, LEX_STRING *dl, int report)
+{
+ struct st_plugin_int tmp, *maybe_dupe;
+ struct st_maria_plugin *plugin;
+ uint oks= 0, errs= 0, dupes= 0;
+ DBUG_ENTER("plugin_add");
+ DBUG_PRINT("enter", ("name: %s dl: %s", name->str, dl->str));
+
+ if (name->str && plugin_find_internal(name, MYSQL_ANY_PLUGIN))
+ {
+ report_error(report, ER_UDF_EXISTS, name->str);
+ DBUG_RETURN(TRUE);
+ }
+ /* Clear the whole struct to catch future extensions. */
+ bzero((char*) &tmp, sizeof(tmp));
+ fix_dl_name(tmp_root, dl);
+ if (! (tmp.plugin_dl= plugin_dl_add(dl, report)))
+ DBUG_RETURN(TRUE);
+ /* Find plugin by name */
+ for (plugin= tmp.plugin_dl->plugins; plugin->info; plugin++)
+ {
+ tmp.name.str= (char *)plugin->name;
+ tmp.name.length= strlen(plugin->name);
+
+ if (plugin->type < 0 || plugin->type >= MYSQL_MAX_PLUGIN_TYPE_NUM)
+ continue; // invalid plugin
+
+ if (name->str && my_strnncoll(system_charset_info,
+ (const uchar *)name->str, name->length,
+ (const uchar *)tmp.name.str, tmp.name.length))
+ continue; // plugin name doesn't match
+
+ if (!name->str &&
+ (maybe_dupe= plugin_find_internal(&tmp.name, MYSQL_ANY_PLUGIN)))
+ {
+ if (plugin->name != maybe_dupe->plugin->name)
+ {
+ report_error(report, ER_UDF_EXISTS, plugin->name);
+ DBUG_RETURN(TRUE);
+ }
+ dupes++;
+ continue; // already installed
+ }
+ struct st_plugin_int *tmp_plugin_ptr;
+ if (*(int*)plugin->info <
+ min_plugin_info_interface_version[plugin->type] ||
+ ((*(int*)plugin->info) >> 8) >
+ (cur_plugin_info_interface_version[plugin->type] >> 8))
+ {
+ char buf[256];
+ strxnmov(buf, sizeof(buf) - 1, "API version for ",
+ plugin_type_names[plugin->type].str,
+ " plugin ", tmp.name.str,
+ " not supported by this version of the server", NullS);
+ report_error(report, ER_CANT_OPEN_LIBRARY, dl->str, ENOEXEC, buf);
+ goto err;
+ }
+ if (plugin_maturity_map[plugin->maturity] < plugin_maturity)
+ {
+ char buf[256];
+ strxnmov(buf, sizeof(buf) - 1, "Loading of ",
+ plugin_maturity_names[plugin->maturity],
+ " plugin ", tmp.name.str,
+ " is prohibited by --plugin-maturity=",
+ plugin_maturity_names[plugin_maturity],
+ NullS);
+ report_error(report, ER_CANT_OPEN_LIBRARY, dl->str, EPERM, buf);
+ goto err;
+ }
+ tmp.plugin= plugin;
+ tmp.ref_count= 0;
+ tmp.state= PLUGIN_IS_UNINITIALIZED;
+ tmp.load_option= PLUGIN_ON;
+
+ if (!(tmp_plugin_ptr= plugin_insert_or_reuse(&tmp)))
+ goto err;
+ plugin_array_version++;
+ if (my_hash_insert(&plugin_hash[plugin->type], (uchar*)tmp_plugin_ptr))
+ tmp_plugin_ptr->state= PLUGIN_IS_FREED;
+ init_alloc_root(&tmp_plugin_ptr->mem_root, 4096, 4096, MYF(0));
+
+ if (name->str)
+ DBUG_RETURN(FALSE); // all done
+
+ oks++;
+ tmp.plugin_dl->ref_count++;
+ continue; // otherwise - go on
+
+err:
+ errs++;
+ if (name->str)
+ break;
+ }
+
+ DBUG_ASSERT(!name->str || !dupes); // dupes is ONLY for name->str == 0
+
+ if (errs == 0 && oks == 0 && !dupes) // no plugin was found
+ report_error(report, ER_CANT_FIND_DL_ENTRY, name->str);
+
+ plugin_dl_del(tmp.plugin_dl);
+ DBUG_RETURN(errs > 0 || oks + dupes == 0);
+}
+
+
+static void plugin_deinitialize(struct st_plugin_int *plugin, bool ref_check)
+{
+ /*
+ we don't want to hold the LOCK_plugin mutex as it may cause
+ deinitialization to deadlock if plugins have worker threads
+ with plugin locks
+ */
+ mysql_mutex_assert_not_owner(&LOCK_plugin);
+
+ if (plugin->plugin->status_vars)
+ {
+ /*
+ historical ndb behavior caused MySQL plugins to specify
+ status var names in full, with the plugin name prefix.
+ this was never fixed in MySQL.
+ MariaDB fixes that but support MySQL style too.
+ */
+ SHOW_VAR *show_vars= plugin->plugin->status_vars;
+ SHOW_VAR tmp_array[2]= {
+ {plugin->plugin->name, (char*)plugin->plugin->status_vars, SHOW_ARRAY},
+ {0, 0, SHOW_UNDEF}
+ };
+ if (strncasecmp(show_vars->name, plugin->name.str, plugin->name.length))
+ show_vars= tmp_array;
+
+ remove_status_vars(show_vars);
+ }
+
+ if (plugin_type_deinitialize[plugin->plugin->type])
+ {
+ if ((*plugin_type_deinitialize[plugin->plugin->type])(plugin))
+ {
+ sql_print_error("Plugin '%s' of type %s failed deinitialization",
+ plugin->name.str, plugin_type_names[plugin->plugin->type].str);
+ }
+ }
+ else if (plugin->plugin->deinit)
+ {
+ DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str));
+ if (plugin->plugin->deinit(plugin))
+ {
+ DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
+ plugin->name.str));
+ }
+ }
+ plugin->state= PLUGIN_IS_UNINITIALIZED;
+
+ /*
+ We do the check here because NDB has a worker THD which doesn't
+ exit until NDB is shut down.
+ */
+ if (ref_check && plugin->ref_count)
+ sql_print_error("Plugin '%s' has ref_count=%d after deinitialization.",
+ plugin->name.str, plugin->ref_count);
+
+ mysql_del_sys_var_chain(plugin->system_vars);
+}
+
+static void plugin_del(struct st_plugin_int *plugin)
+{
+ DBUG_ENTER("plugin_del");
+ mysql_mutex_assert_owner(&LOCK_plugin);
+ /* Free allocated strings before deleting the plugin. */
+ plugin_vars_free_values(plugin->system_vars);
+ restore_ptr_backup(plugin->nbackups, plugin->ptr_backup);
+ my_hash_delete(&plugin_hash[plugin->plugin->type], (uchar*)plugin);
+ plugin_dl_del(plugin->plugin_dl);
+ plugin->state= PLUGIN_IS_FREED;
+ plugin_array_version++;
+ free_root(&plugin->mem_root, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+static void reap_plugins(void)
+{
+ uint count, idx;
+ struct st_plugin_int *plugin, **reap, **list;
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (!reap_needed)
+ return;
+
+ reap_needed= false;
+ count= plugin_array.elements;
+ reap= (struct st_plugin_int **)my_alloca(sizeof(plugin)*(count+1));
+ *(reap++)= NULL;
+
+ for (idx= 0; idx < count; idx++)
+ {
+ plugin= *dynamic_element(&plugin_array, idx, struct st_plugin_int **);
+ if (plugin->state == PLUGIN_IS_DELETED && !plugin->ref_count)
+ {
+ /* change the status flag to prevent reaping by another thread */
+ plugin->state= PLUGIN_IS_DYING;
+ *(reap++)= plugin;
+ }
+ }
+
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ list= reap;
+ while ((plugin= *(--list)))
+ plugin_deinitialize(plugin, true);
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ while ((plugin= *(--reap)))
+ plugin_del(plugin);
+
+ my_afree(reap);
+}
+
+static void intern_plugin_unlock(LEX *lex, plugin_ref plugin)
+{
+ int i;
+ st_plugin_int *pi;
+ DBUG_ENTER("intern_plugin_unlock");
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (!plugin)
+ DBUG_VOID_RETURN;
+
+ pi= plugin_ref_to_int(plugin);
+
+#ifdef DBUG_OFF
+ if (!pi->plugin_dl)
+ DBUG_VOID_RETURN;
+#else
+ my_free(plugin);
+#endif
+
+ if (lex)
+ {
+ /*
+ Remove one instance of this plugin from the use list.
+ We are searching backwards so that plugins locked last
+ could be unlocked faster - optimizing for LIFO semantics.
+ */
+ for (i= lex->plugins.elements - 1; i >= 0; i--)
+ if (plugin == *dynamic_element(&lex->plugins, i, plugin_ref*))
+ {
+ delete_dynamic_element(&lex->plugins, i);
+ break;
+ }
+ DBUG_ASSERT(i >= 0);
+ }
+
+ DBUG_ASSERT(pi->ref_count);
+ pi->ref_count--;
+
+ DBUG_PRINT("lock",("thd: 0x%lx plugin: \"%s\" UNLOCK ref_count: %d",
+ (long) current_thd, pi->name.str, pi->ref_count));
+
+ if (pi->state == PLUGIN_IS_DELETED && !pi->ref_count)
+ reap_needed= true;
+
+ DBUG_VOID_RETURN;
+}
+
+
+void plugin_unlock(THD *thd, plugin_ref plugin)
+{
+ LEX *lex= thd ? thd->lex : 0;
+ DBUG_ENTER("plugin_unlock");
+ if (!plugin)
+ DBUG_VOID_RETURN;
+#ifdef DBUG_OFF
+ /* built-in plugins don't need ref counting */
+ if (!plugin_dlib(plugin))
+ DBUG_VOID_RETURN;
+#endif
+ mysql_mutex_lock(&LOCK_plugin);
+ intern_plugin_unlock(lex, plugin);
+ reap_plugins();
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_VOID_RETURN;
+}
+
+
+void plugin_unlock_list(THD *thd, plugin_ref *list, uint count)
+{
+ LEX *lex= thd ? thd->lex : 0;
+ DBUG_ENTER("plugin_unlock_list");
+ if (count == 0)
+ DBUG_VOID_RETURN;
+
+ DBUG_ASSERT(list);
+ mysql_mutex_lock(&LOCK_plugin);
+ while (count--)
+ intern_plugin_unlock(lex, *list++);
+ reap_plugins();
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_VOID_RETURN;
+}
+
+
+static int plugin_initialize(MEM_ROOT *tmp_root, struct st_plugin_int *plugin,
+ int *argc, char **argv, bool options_only)
+{
+ int ret= 1;
+ DBUG_ENTER("plugin_initialize");
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+ uint state= plugin->state;
+ DBUG_ASSERT(state == PLUGIN_IS_UNINITIALIZED);
+
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ mysql_rwlock_wrlock(&LOCK_system_variables_hash);
+ if (test_plugin_options(tmp_root, plugin, argc, argv))
+ state= PLUGIN_IS_DISABLED;
+ mysql_rwlock_unlock(&LOCK_system_variables_hash);
+
+ if (options_only || state == PLUGIN_IS_DISABLED)
+ {
+ ret= 0;
+ goto err;
+ }
+
+ if (plugin->plugin_dl && global_system_variables.log_warnings >= 9)
+ {
+ void *sym= dlsym(plugin->plugin_dl->handle,
+ plugin->plugin_dl->mariaversion ?
+ maria_plugin_declarations_sym : plugin_declarations_sym);
+ DBUG_ASSERT(sym);
+ sql_print_information("Plugin %s loaded at %p",
+ plugin->name.str, sym);
+ }
+
+ if (plugin_type_initialize[plugin->plugin->type])
+ {
+ if ((*plugin_type_initialize[plugin->plugin->type])(plugin))
+ {
+ sql_print_error("Plugin '%s' registration as a %s failed.",
+ plugin->name.str, plugin_type_names[plugin->plugin->type].str);
+ goto err;
+ }
+ }
+ else if (plugin->plugin->init)
+ {
+ if (plugin->plugin->init(plugin))
+ {
+ sql_print_error("Plugin '%s' init function returned error.",
+ plugin->name.str);
+ goto err;
+ }
+ }
+ state= PLUGIN_IS_READY; // plugin->init() succeeded
+
+ if (plugin->plugin->status_vars)
+ {
+ /*
+ historical ndb behavior caused MySQL plugins to specify
+ status var names in full, with the plugin name prefix.
+ this was never fixed in MySQL.
+ MariaDB fixes that, but supports MySQL style too.
+ */
+ SHOW_VAR *show_vars= plugin->plugin->status_vars;
+ SHOW_VAR tmp_array[2]= {
+ {plugin->plugin->name, (char*)plugin->plugin->status_vars, SHOW_ARRAY},
+ {0, 0, SHOW_UNDEF}
+ };
+ if (strncasecmp(show_vars->name, plugin->name.str, plugin->name.length))
+ show_vars= tmp_array;
+
+ if (add_status_vars(show_vars))
+ goto err;
+ }
+
+ /*
+ set the plugin attribute of plugin's sys vars so they are pointing
+ to the active plugin
+ */
+ if (plugin->system_vars)
+ {
+ sys_var_pluginvar *var= plugin->system_vars->cast_pluginvar();
+ for (;;)
+ {
+ var->plugin= plugin;
+ if (!var->next)
+ break;
+ var= var->next->cast_pluginvar();
+ }
+ }
+
+ ret= 0;
+
+err:
+ if (ret)
+ mysql_del_sys_var_chain(plugin->system_vars);
+
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin->state= state;
+
+ DBUG_RETURN(ret);
+}
+
+
+extern "C" uchar *get_plugin_hash_key(const uchar *, size_t *, my_bool);
+extern "C" uchar *get_bookmark_hash_key(const uchar *, size_t *, my_bool);
+
+
+uchar *get_plugin_hash_key(const uchar *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ struct st_plugin_int *plugin= (st_plugin_int *)buff;
+ *length= (uint)plugin->name.length;
+ return((uchar *)plugin->name.str);
+}
+
+
+uchar *get_bookmark_hash_key(const uchar *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ struct st_bookmark *var= (st_bookmark *)buff;
+ *length= var->name_len + 1;
+ return (uchar*) var->key;
+}
+
+static inline void convert_dash_to_underscore(char *str, int len)
+{
+ for (char *p= str; p <= str+len; p++)
+ if (*p == '-')
+ *p= '_';
+}
+
+static inline void convert_underscore_to_dash(char *str, int len)
+{
+ for (char *p= str; p <= str+len; p++)
+ if (*p == '_')
+ *p= '-';
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_LOCK_plugin;
+
+static PSI_mutex_info all_plugin_mutexes[]=
+{
+ { &key_LOCK_plugin, "LOCK_plugin", PSI_FLAG_GLOBAL}
+};
+
+static void init_plugin_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_plugin_mutexes);
+ PSI_server->register_mutex(category, all_plugin_mutexes, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+/*
+ The logic is that we first load and initialize all compiled in plugins.
+ From there we load up the dynamic types (assuming we have not been told to
+ skip this part).
+
+ Finally we initialize everything, aka the dynamic that have yet to initialize.
+*/
+int plugin_init(int *argc, char **argv, int flags)
+{
+ uint i;
+ bool is_myisam;
+ struct st_maria_plugin **builtins;
+ struct st_maria_plugin *plugin;
+ struct st_plugin_int tmp, *plugin_ptr, **reap;
+ MEM_ROOT tmp_root;
+ bool reaped_mandatory_plugin= false;
+ bool mandatory= true;
+ DBUG_ENTER("plugin_init");
+
+ if (initialized)
+ DBUG_RETURN(0);
+
+ dlopen_count =0;
+
+#ifdef HAVE_PSI_INTERFACE
+ init_plugin_psi_keys();
+#endif
+
+ init_alloc_root(&plugin_mem_root, 4096, 4096, MYF(0));
+ init_alloc_root(&plugin_vars_mem_root, 4096, 4096, MYF(0));
+ init_alloc_root(&tmp_root, 4096, 4096, MYF(0));
+
+ if (my_hash_init(&bookmark_hash, &my_charset_bin, 16, 0, 0,
+ get_bookmark_hash_key, NULL, HASH_UNIQUE))
+ goto err;
+
+
+ mysql_mutex_init(key_LOCK_plugin, &LOCK_plugin, MY_MUTEX_INIT_FAST);
+
+ if (my_init_dynamic_array(&plugin_dl_array,
+ sizeof(struct st_plugin_dl *), 16, 16, MYF(0)) ||
+ my_init_dynamic_array(&plugin_array,
+ sizeof(struct st_plugin_int *), 16, 16, MYF(0)))
+ goto err;
+
+ for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ {
+ if (my_hash_init(&plugin_hash[i], system_charset_info, 16, 0, 0,
+ get_plugin_hash_key, NULL, HASH_UNIQUE))
+ goto err;
+ }
+
+ /* prepare debug_sync service */
+ DBUG_ASSERT(strcmp(list_of_services[4].name, "debug_sync_service") == 0);
+ list_of_services[4].service= *(void**)&debug_sync_C_callback_ptr;
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ initialized= 1;
+
+ /*
+ First we register builtin plugins
+ */
+ for (builtins= mysql_mandatory_plugins; *builtins || mandatory; builtins++)
+ {
+ if (!*builtins)
+ {
+ builtins= mysql_optional_plugins;
+ mandatory= false;
+ if (!*builtins)
+ break;
+ }
+ for (plugin= *builtins; plugin->info; plugin++)
+ {
+ if (opt_ignore_builtin_innodb &&
+ !my_strnncoll(&my_charset_latin1, (const uchar*) plugin->name,
+ 6, (const uchar*) "InnoDB", 6))
+ continue;
+
+ bzero(&tmp, sizeof(tmp));
+ tmp.plugin= plugin;
+ tmp.name.str= (char *)plugin->name;
+ tmp.name.length= strlen(plugin->name);
+ tmp.state= 0;
+ tmp.load_option= mandatory ? PLUGIN_FORCE : PLUGIN_ON;
+
+ for (i=0; i < array_elements(override_plugin_load_policy); i++)
+ {
+ if (!my_strcasecmp(&my_charset_latin1, plugin->name,
+ override_plugin_load_policy[i].plugin_name))
+ {
+ tmp.load_option= override_plugin_load_policy[i].override;
+ break;
+ }
+ }
+
+ free_root(&tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ tmp.state= PLUGIN_IS_UNINITIALIZED;
+ if (register_builtin(plugin, &tmp, &plugin_ptr))
+ goto err_unlock;
+
+ is_myisam= !my_strcasecmp(&my_charset_latin1, plugin->name, "MyISAM");
+
+ /*
+ strictly speaking, we should to initialize all plugins,
+ even for mysqld --help, because important subsystems
+ may be disabled otherwise, and the help will be incomplete.
+ For example, if the mysql.plugin table is not MyISAM.
+ But for now it's an unlikely corner case, and to optimize
+ mysqld --help for all other users, we will only initialize
+ MyISAM here.
+ */
+ if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, !is_myisam &&
+ (flags & PLUGIN_INIT_SKIP_INITIALIZATION)))
+ {
+ if (plugin_ptr->load_option == PLUGIN_FORCE)
+ goto err_unlock;
+ plugin_ptr->state= PLUGIN_IS_DISABLED;
+ }
+
+ /*
+ initialize the global default storage engine so that it may
+ not be null in any child thread.
+ */
+ if (is_myisam)
+ {
+ DBUG_ASSERT(!global_system_variables.table_plugin);
+ global_system_variables.table_plugin=
+ intern_plugin_lock(NULL, plugin_int_to_ref(plugin_ptr));
+ DBUG_ASSERT(plugin_ptr->ref_count == 1);
+ }
+ }
+ }
+
+ /* should now be set to MyISAM storage engine */
+ DBUG_ASSERT(global_system_variables.table_plugin);
+
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ /* Register all dynamic plugins */
+ if (!(flags & PLUGIN_INIT_SKIP_DYNAMIC_LOADING))
+ {
+ I_List_iterator<i_string> iter(opt_plugin_load_list);
+ i_string *item;
+ while (NULL != (item= iter++))
+ plugin_load_list(&tmp_root, item->ptr);
+
+ if (!(flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE))
+ plugin_load(&tmp_root);
+ }
+
+ /*
+ Now we initialize all remaining plugins
+ */
+
+ mysql_mutex_lock(&LOCK_plugin);
+ reap= (st_plugin_int **) my_alloca((plugin_array.elements+1) * sizeof(void*));
+ *(reap++)= NULL;
+
+ for (i= 0; i < plugin_array.elements; i++)
+ {
+ plugin_ptr= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
+ if (plugin_ptr->plugin_dl && plugin_ptr->state == PLUGIN_IS_UNINITIALIZED)
+ {
+ if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv,
+ (flags & PLUGIN_INIT_SKIP_INITIALIZATION)))
+ {
+ plugin_ptr->state= PLUGIN_IS_DYING;
+ *(reap++)= plugin_ptr;
+ }
+ }
+ }
+
+ /*
+ Check if any plugins have to be reaped
+ */
+ while ((plugin_ptr= *(--reap)))
+ {
+ mysql_mutex_unlock(&LOCK_plugin);
+ if (plugin_ptr->load_option == PLUGIN_FORCE ||
+ plugin_ptr->load_option == PLUGIN_FORCE_PLUS_PERMANENT)
+ reaped_mandatory_plugin= TRUE;
+ plugin_deinitialize(plugin_ptr, true);
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin_del(plugin_ptr);
+ }
+
+ mysql_mutex_unlock(&LOCK_plugin);
+ my_afree(reap);
+ if (reaped_mandatory_plugin)
+ goto err;
+
+ free_root(&tmp_root, MYF(0));
+
+ DBUG_RETURN(0);
+
+err_unlock:
+ mysql_mutex_unlock(&LOCK_plugin);
+err:
+ free_root(&tmp_root, MYF(0));
+ DBUG_RETURN(1);
+}
+
+
+static bool register_builtin(struct st_maria_plugin *plugin,
+ struct st_plugin_int *tmp,
+ struct st_plugin_int **ptr)
+{
+ DBUG_ENTER("register_builtin");
+ tmp->ref_count= 0;
+ tmp->plugin_dl= 0;
+
+ if (insert_dynamic(&plugin_array, (uchar*)&tmp))
+ DBUG_RETURN(1);
+
+ *ptr= *dynamic_element(&plugin_array, plugin_array.elements - 1,
+ struct st_plugin_int **)=
+ (struct st_plugin_int *) memdup_root(&plugin_mem_root, (uchar*)tmp,
+ sizeof(struct st_plugin_int));
+
+ if (my_hash_insert(&plugin_hash[plugin->type],(uchar*) *ptr))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ called only by plugin_init()
+*/
+static void plugin_load(MEM_ROOT *tmp_root)
+{
+ TABLE_LIST tables;
+ TABLE *table;
+ READ_RECORD read_record_info;
+ int error;
+ THD *new_thd= new THD;
+ bool result;
+ DBUG_ENTER("plugin_load");
+
+ new_thd->thread_stack= (char*) &tables;
+ new_thd->store_globals();
+ new_thd->db= my_strdup("mysql", MYF(0));
+ new_thd->db_length= 5;
+ bzero((char*) &new_thd->net, sizeof(new_thd->net));
+ tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_READ);
+ tables.open_strategy= TABLE_LIST:: IF_EMBEDDED(OPEN_IF_EXISTS, OPEN_NORMAL);
+
+ result= open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT);
+
+ table= tables.table;
+ if (IF_EMBEDDED(!table, false))
+ goto end;
+
+ if (result)
+ {
+ DBUG_PRINT("error",("Can't open plugin table"));
+ if (!opt_help)
+ sql_print_error("Can't open the mysql.plugin table. Please "
+ "run mysql_upgrade to create it.");
+ else
+ sql_print_warning("Could not open mysql.plugin table. Some options may be missing from the help text");
+ goto end;
+ }
+
+ if (init_read_record(&read_record_info, new_thd, table, NULL, 1, 0, FALSE))
+ {
+ sql_print_error("Could not initialize init_read_record; Plugins not "
+ "loaded");
+ goto end;
+ }
+ table->use_all_columns();
+ while (!(error= read_record_info.read_record(&read_record_info)))
+ {
+ DBUG_PRINT("info", ("init plugin record"));
+ String str_name, str_dl;
+ get_field(tmp_root, table->field[0], &str_name);
+ get_field(tmp_root, table->field[1], &str_dl);
+
+ LEX_STRING name= {(char *)str_name.ptr(), str_name.length()};
+ LEX_STRING dl= {(char *)str_dl.ptr(), str_dl.length()};
+
+ /*
+ there're no other threads running yet, so we don't need a mutex.
+ but plugin_add() before is designed to work in multi-threaded
+ environment, and it uses mysql_mutex_assert_owner(), so we lock
+ the mutex here to satisfy the assert
+ */
+ mysql_mutex_lock(&LOCK_plugin);
+ if (plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG))
+ sql_print_warning("Couldn't load plugin named '%s' with soname '%s'.",
+ str_name.c_ptr(), str_dl.c_ptr());
+ free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ mysql_mutex_unlock(&LOCK_plugin);
+ }
+ if (error > 0)
+ sql_print_error(ER(ER_GET_ERRNO), my_errno, table->file->table_type());
+ end_read_record(&read_record_info);
+ table->m_needs_reopen= TRUE; // Force close to free memory
+ close_mysql_tables(new_thd);
+end:
+ /* Remember that we don't have a THD */
+ delete new_thd;
+ set_current_thd(0);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ called only by plugin_init()
+*/
+static bool plugin_load_list(MEM_ROOT *tmp_root, const char *list)
+{
+ char buffer[FN_REFLEN];
+ LEX_STRING name= {buffer, 0}, dl= {NULL, 0}, *str= &name;
+ char *p= buffer;
+ DBUG_ENTER("plugin_load_list");
+ while (list)
+ {
+ if (p == buffer + sizeof(buffer) - 1)
+ {
+ sql_print_error("plugin-load parameter too long");
+ DBUG_RETURN(TRUE);
+ }
+
+ switch ((*(p++)= *(list++))) {
+ case '\0':
+ list= NULL; /* terminate the loop */
+ /* fall through */
+#ifndef __WIN__
+ case ':': /* can't use this as delimiter as it may be drive letter */
+#endif
+ case ';':
+ str->str[str->length]= '\0';
+ if (str == &name) // load all plugins in named module
+ {
+ if (!name.length)
+ {
+ p--; /* reset pointer */
+ continue;
+ }
+
+ dl= name;
+ mysql_mutex_lock(&LOCK_plugin);
+ free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ name.str= 0; // load everything
+ if (plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG))
+ goto error;
+ }
+ else
+ {
+ free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ mysql_mutex_lock(&LOCK_plugin);
+ if (plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG))
+ goto error;
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+ name.length= dl.length= 0;
+ dl.str= NULL; name.str= p= buffer;
+ str= &name;
+ continue;
+ case '=':
+ case '#':
+ if (str == &name)
+ {
+ name.str[name.length]= '\0';
+ str= &dl;
+ str->str= p;
+ continue;
+ }
+ default:
+ str->length++;
+ continue;
+ }
+ }
+ DBUG_RETURN(FALSE);
+error:
+ mysql_mutex_unlock(&LOCK_plugin);
+ if (name.str)
+ sql_print_error("Couldn't load plugin '%s' from '%s'.",
+ name.str, dl.str);
+ else
+ sql_print_error("Couldn't load plugins from '%s'.", dl.str);
+ DBUG_RETURN(TRUE);
+}
+
+
+void plugin_shutdown(void)
+{
+ uint i, count= plugin_array.elements;
+ struct st_plugin_int **plugins, *plugin;
+ struct st_plugin_dl **dl;
+ DBUG_ENTER("plugin_shutdown");
+
+ if (initialized)
+ {
+ mysql_mutex_lock(&LOCK_plugin);
+
+ reap_needed= true;
+
+ /*
+ We want to shut down plugins in a reasonable order, this will
+ become important when we have plugins which depend upon each other.
+ Circular references cannot be reaped so they are forced afterwards.
+ TODO: Have an additional step here to notify all active plugins that
+ shutdown is requested to allow plugins to deinitialize in parallel.
+ */
+ while (reap_needed && (count= plugin_array.elements))
+ {
+ reap_plugins();
+ for (i= 0; i < count; i++)
+ {
+ plugin= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
+ if (plugin->state == PLUGIN_IS_READY)
+ {
+ plugin->state= PLUGIN_IS_DELETED;
+ reap_needed= true;
+ }
+ }
+ if (!reap_needed)
+ {
+ /*
+ release any plugin references held.
+ */
+ unlock_variables(NULL, &global_system_variables);
+ unlock_variables(NULL, &max_system_variables);
+ }
+ }
+
+ plugins= (struct st_plugin_int **) my_alloca(sizeof(void*) * (count+1));
+
+ /*
+ If we have any plugins which did not die cleanly, we force shutdown
+ */
+ for (i= 0; i < count; i++)
+ {
+ plugins[i]= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
+ /* change the state to ensure no reaping races */
+ if (plugins[i]->state == PLUGIN_IS_DELETED)
+ plugins[i]->state= PLUGIN_IS_DYING;
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ /*
+ We loop through all plugins and call deinit() if they have one.
+ */
+ for (i= 0; i < count; i++)
+ if (!(plugins[i]->state & (PLUGIN_IS_UNINITIALIZED | PLUGIN_IS_FREED |
+ PLUGIN_IS_DISABLED)))
+ {
+ sql_print_warning("Plugin '%s' will be forced to shutdown",
+ plugins[i]->name.str);
+ /*
+ We are forcing deinit on plugins so we don't want to do a ref_count
+ check until we have processed all the plugins.
+ */
+ plugin_deinitialize(plugins[i], false);
+ }
+
+ /*
+ It's perfectly safe not to lock LOCK_plugin, as there're no
+ concurrent threads anymore. But some functions called from here
+ use mysql_mutex_assert_owner(), so we lock the mutex to satisfy it
+ */
+ mysql_mutex_lock(&LOCK_plugin);
+
+ /*
+ We defer checking ref_counts until after all plugins are deinitialized
+ as some may have worker threads holding on to plugin references.
+ */
+ for (i= 0; i < count; i++)
+ {
+ if (plugins[i]->ref_count)
+ sql_print_error("Plugin '%s' has ref_count=%d after shutdown.",
+ plugins[i]->name.str, plugins[i]->ref_count);
+ if (plugins[i]->state & PLUGIN_IS_UNINITIALIZED ||
+ plugins[i]->state & PLUGIN_IS_DISABLED)
+ plugin_del(plugins[i]);
+ }
+
+ /*
+ Now we can deallocate all memory.
+ */
+
+ cleanup_variables(NULL, &global_system_variables);
+ cleanup_variables(NULL, &max_system_variables);
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ initialized= 0;
+ mysql_mutex_destroy(&LOCK_plugin);
+
+ my_afree(plugins);
+ }
+
+ /* Dispose of the memory */
+
+ for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ my_hash_free(&plugin_hash[i]);
+ delete_dynamic(&plugin_array);
+
+ count= plugin_dl_array.elements;
+ dl= (struct st_plugin_dl **)my_alloca(sizeof(void*) * count);
+ for (i= 0; i < count; i++)
+ dl[i]= *dynamic_element(&plugin_dl_array, i, struct st_plugin_dl **);
+ for (i= 0; i < plugin_dl_array.elements; i++)
+ free_plugin_mem(dl[i]);
+ my_afree(dl);
+ delete_dynamic(&plugin_dl_array);
+
+ my_hash_free(&bookmark_hash);
+ free_root(&plugin_mem_root, MYF(0));
+ free_root(&plugin_vars_mem_root, MYF(0));
+
+ global_variables_dynamic_size= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ complete plugin installation (after plugin_add).
+
+ That is, initialize it, and update mysql.plugin table
+*/
+static bool finalize_install(THD *thd, TABLE *table, const LEX_STRING *name,
+ int *argc, char **argv)
+{
+ struct st_plugin_int *tmp= plugin_find_internal(name, MYSQL_ANY_PLUGIN);
+ int error;
+ DBUG_ASSERT(tmp);
+ mysql_mutex_assert_owner(&LOCK_plugin); // because of tmp->state
+
+ if (tmp->state != PLUGIN_IS_UNINITIALIZED)
+ {
+ /* already installed */
+ return 0;
+ }
+ else
+ {
+ if (plugin_initialize(thd->mem_root, tmp, argc, argv, false))
+ {
+ report_error(REPORT_TO_USER, ER_CANT_INITIALIZE_UDF, name->str,
+ "Plugin initialization function failed.");
+ tmp->state= PLUGIN_IS_DELETED;
+ return 1;
+ }
+ }
+ if (tmp->state == PLUGIN_IS_DISABLED)
+ {
+ if (global_system_variables.log_warnings)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_CANT_INITIALIZE_UDF, ER(ER_CANT_INITIALIZE_UDF),
+ name->str, "Plugin is disabled");
+ }
+
+ /*
+ We do not replicate the INSTALL PLUGIN statement. Disable binlogging
+ of the insert into the plugin table, so that it is not replicated in
+ row based mode.
+ */
+ tmp_disable_binlog(thd);
+ table->use_all_columns();
+ restore_record(table, s->default_values);
+ table->field[0]->store(name->str, name->length, system_charset_info);
+ table->field[1]->store(tmp->plugin_dl->dl.str, tmp->plugin_dl->dl.length,
+ files_charset_info);
+ error= table->file->ha_write_row(table->record[0]);
+ reenable_binlog(thd);
+ if (error)
+ {
+ table->file->print_error(error, MYF(0));
+ tmp->state= PLUGIN_IS_DELETED;
+ return 1;
+ }
+ return 0;
+}
+
+bool mysql_install_plugin(THD *thd, const LEX_STRING *name,
+ const LEX_STRING *dl_arg)
+{
+ TABLE_LIST tables;
+ TABLE *table;
+ LEX_STRING dl= *dl_arg;
+ bool error;
+ int argc=orig_argc;
+ char **argv=orig_argv;
+ DBUG_ENTER("mysql_install_plugin");
+
+ tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE);
+ if (!opt_noacl && check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE))
+ DBUG_RETURN(TRUE);
+
+ /* need to open before acquiring LOCK_plugin or it will deadlock */
+ if (! (table = open_ltable(thd, &tables, TL_WRITE,
+ MYSQL_LOCK_IGNORE_TIMEOUT)))
+ DBUG_RETURN(TRUE);
+
+ if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL))
+ {
+ report_error(REPORT_TO_USER, ER_PLUGIN_IS_NOT_LOADED, name->str);
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Pre-acquire audit plugins for events that may potentially occur
+ during [UN]INSTALL PLUGIN.
+
+ When audit event is triggered, audit subsystem acquires interested
+ plugins by walking through plugin list. Evidently plugin list
+ iterator protects plugin list by acquiring LOCK_plugin, see
+ plugin_foreach_with_mask().
+
+ On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
+ rather for a long time.
+
+ When audit event is triggered during [UN]INSTALL PLUGIN, plugin
+ list iterator acquires the same lock (within the same thread)
+ second time.
+
+ This hack should be removed when LOCK_plugin is fixed so it
+ protects only what it supposed to protect.
+
+ See also mysql_uninstall_plugin() and initialize_audit_plugin()
+ */
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] =
+ { MYSQL_AUDIT_GENERAL_CLASSMASK };
+ mysql_audit_acquire_plugins(thd, event_class_mask);
+
+ mysql_mutex_lock(&LOCK_plugin);
+ error= plugin_add(thd->mem_root, name, &dl, REPORT_TO_USER);
+ if (error)
+ goto err;
+
+ if (name->str)
+ error= finalize_install(thd, table, name, &argc, argv);
+ else
+ {
+ st_plugin_dl *plugin_dl= plugin_dl_find(&dl);
+ struct st_maria_plugin *plugin;
+ for (plugin= plugin_dl->plugins; plugin->info; plugin++)
+ {
+ LEX_STRING str= { const_cast<char*>(plugin->name), strlen(plugin->name) };
+ error|= finalize_install(thd, table, &str, &argc, argv);
+ }
+ }
+
+ if (error)
+ {
+ reap_needed= true;
+ reap_plugins();
+ }
+err:
+ mysql_mutex_unlock(&LOCK_plugin);
+ if (argv)
+ free_defaults(argv);
+ DBUG_RETURN(error);
+}
+
+
+static bool do_uninstall(THD *thd, TABLE *table, const LEX_STRING *name)
+{
+ struct st_plugin_int *plugin;
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (!(plugin= plugin_find_internal(name, MYSQL_ANY_PLUGIN)) ||
+ plugin->state & (PLUGIN_IS_UNINITIALIZED | PLUGIN_IS_DYING))
+ {
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PLUGIN", name->str);
+ return 1;
+ }
+ if (!plugin->plugin_dl)
+ {
+ my_error(ER_PLUGIN_DELETE_BUILTIN, MYF(0));
+ return 1;
+ }
+ if (plugin->load_option == PLUGIN_FORCE_PLUS_PERMANENT)
+ {
+ my_error(ER_PLUGIN_IS_PERMANENT, MYF(0), name->str);
+ return 1;
+ }
+
+ plugin->state= PLUGIN_IS_DELETED;
+ if (plugin->ref_count)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_PLUGIN_BUSY, ER(WARN_PLUGIN_BUSY));
+ else
+ reap_needed= true;
+
+ uchar user_key[MAX_KEY_LENGTH];
+ table->use_all_columns();
+ table->field[0]->store(name->str, name->length, system_charset_info);
+ key_copy(user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+ if (! table->file->ha_index_read_idx_map(table->record[0], 0, user_key,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT))
+ {
+ int error;
+ /*
+ We do not replicate the UNINSTALL PLUGIN statement. Disable binlogging
+ of the delete from the plugin table, so that it is not replicated in
+ row based mode.
+ */
+ tmp_disable_binlog(thd);
+ error= table->file->ha_delete_row(table->record[0]);
+ reenable_binlog(thd);
+ if (error)
+ {
+ table->file->print_error(error, MYF(0));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name,
+ const LEX_STRING *dl_arg)
+{
+ TABLE *table;
+ TABLE_LIST tables;
+ LEX_STRING dl= *dl_arg;
+ bool error= false;
+ DBUG_ENTER("mysql_uninstall_plugin");
+
+ tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE);
+
+ if (!opt_noacl && check_table_access(thd, DELETE_ACL, &tables, FALSE, 1, FALSE))
+ DBUG_RETURN(TRUE);
+
+ /* need to open before acquiring LOCK_plugin or it will deadlock */
+ if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Pre-acquire audit plugins for events that may potentially occur
+ during [UN]INSTALL PLUGIN.
+
+ When audit event is triggered, audit subsystem acquires interested
+ plugins by walking through plugin list. Evidently plugin list
+ iterator protects plugin list by acquiring LOCK_plugin, see
+ plugin_foreach_with_mask().
+
+ On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
+ rather for a long time.
+
+ When audit event is triggered during [UN]INSTALL PLUGIN, plugin
+ list iterator acquires the same lock (within the same thread)
+ second time.
+
+ This hack should be removed when LOCK_plugin is fixed so it
+ protects only what it supposed to protect.
+
+ See also mysql_install_plugin() and initialize_audit_plugin()
+ */
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] =
+ { MYSQL_AUDIT_GENERAL_CLASSMASK };
+ mysql_audit_acquire_plugins(thd, event_class_mask);
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ if (name->str)
+ error= do_uninstall(thd, table, name);
+ else
+ {
+ fix_dl_name(thd->mem_root, &dl);
+ st_plugin_dl *plugin_dl= plugin_dl_find(&dl);
+ if (plugin_dl)
+ {
+ for (struct st_maria_plugin *plugin= plugin_dl->plugins;
+ plugin->info; plugin++)
+ {
+ LEX_STRING str= { const_cast<char*>(plugin->name), strlen(plugin->name) };
+ error|= do_uninstall(thd, table, &str);
+ }
+ }
+ else
+ {
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SONAME", dl.str);
+ error= true;
+ }
+ }
+ reap_plugins();
+
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_RETURN(error);
+}
+
+
+bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func,
+ int type, uint state_mask, void *arg)
+{
+ uint idx, total;
+ struct st_plugin_int *plugin, **plugins;
+ int version=plugin_array_version;
+ DBUG_ENTER("plugin_foreach_with_mask");
+
+ if (!initialized)
+ DBUG_RETURN(FALSE);
+
+ state_mask= ~state_mask; // do it only once
+
+ mysql_mutex_lock(&LOCK_plugin);
+ total= type == MYSQL_ANY_PLUGIN ? plugin_array.elements
+ : plugin_hash[type].records;
+ /*
+ Do the alloca out here in case we do have a working alloca:
+ leaving the nested stack frame invalidates alloca allocation.
+ */
+ plugins=(struct st_plugin_int **)my_alloca(total*sizeof(plugin));
+ if (type == MYSQL_ANY_PLUGIN)
+ {
+ for (idx= 0; idx < total; idx++)
+ {
+ plugin= *dynamic_element(&plugin_array, idx, struct st_plugin_int **);
+ plugins[idx]= !(plugin->state & state_mask) ? plugin : NULL;
+ }
+ }
+ else
+ {
+ HASH *hash= plugin_hash + type;
+ for (idx= 0; idx < total; idx++)
+ {
+ plugin= (struct st_plugin_int *) my_hash_element(hash, idx);
+ plugins[idx]= !(plugin->state & state_mask) ? plugin : NULL;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ for (idx= 0; idx < total; idx++)
+ {
+ if (unlikely(version != plugin_array_version))
+ {
+ mysql_mutex_lock(&LOCK_plugin);
+ for (uint i=idx; i < total; i++)
+ if (plugins[i] && plugins[i]->state & state_mask)
+ plugins[i]=0;
+ mysql_mutex_unlock(&LOCK_plugin);
+ }
+ plugin= plugins[idx];
+ /* It will stop iterating on first engine error when "func" returns TRUE */
+ if (plugin && func(thd, plugin_int_to_ref(plugin), arg))
+ goto err;
+ }
+
+ my_afree(plugins);
+ DBUG_RETURN(FALSE);
+err:
+ my_afree(plugins);
+ DBUG_RETURN(TRUE);
+}
+
+
+static bool plugin_dl_foreach_internal(THD *thd, st_plugin_dl *plugin_dl,
+ st_maria_plugin *plug,
+ plugin_foreach_func *func, void *arg)
+{
+ for (; plug->name; plug++)
+ {
+ st_plugin_int tmp, *plugin;
+
+ tmp.name.str= const_cast<char*>(plug->name);
+ tmp.name.length= strlen(plug->name);
+ tmp.plugin= plug;
+ tmp.plugin_dl= plugin_dl;
+
+ mysql_mutex_lock(&LOCK_plugin);
+ if ((plugin= plugin_find_internal(&tmp.name, MYSQL_ANY_PLUGIN)) &&
+ plugin->plugin == plug)
+
+ {
+ tmp.state= plugin->state;
+ tmp.load_option= plugin->load_option;
+ }
+ else
+ {
+ tmp.state= PLUGIN_IS_FREED;
+ tmp.load_option= PLUGIN_OFF;
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ plugin= &tmp;
+ if (func(thd, plugin_int_to_ref(plugin), arg))
+ return 1;
+ }
+ return 0;
+}
+
+bool plugin_dl_foreach(THD *thd, const LEX_STRING *dl,
+ plugin_foreach_func *func, void *arg)
+{
+ bool err= 0;
+
+ if (dl)
+ {
+ mysql_mutex_lock(&LOCK_plugin);
+ st_plugin_dl *plugin_dl= plugin_dl_add(dl, REPORT_TO_USER);
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ if (!plugin_dl)
+ return 1;
+
+ err= plugin_dl_foreach_internal(thd, plugin_dl, plugin_dl->plugins,
+ func, arg);
+
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin_dl_del(plugin_dl);
+ mysql_mutex_unlock(&LOCK_plugin);
+ }
+ else
+ {
+ struct st_maria_plugin **builtins;
+ for (builtins= mysql_mandatory_plugins; !err && *builtins; builtins++)
+ err= plugin_dl_foreach_internal(thd, 0, *builtins, func, arg);
+ for (builtins= mysql_optional_plugins; !err && *builtins; builtins++)
+ err= plugin_dl_foreach_internal(thd, 0, *builtins, func, arg);
+ }
+ return err;
+}
+
+
+/****************************************************************************
+ Internal type declarations for variables support
+****************************************************************************/
+
+#undef MYSQL_SYSVAR_NAME
+#define MYSQL_SYSVAR_NAME(name) name
+#define PLUGIN_VAR_TYPEMASK 0x7f
+#define BOOKMARK_MEMALLOC 0x80
+
+static inline char plugin_var_bookmark_key(uint flags)
+{
+ return (flags & PLUGIN_VAR_TYPEMASK) |
+ (flags & PLUGIN_VAR_MEMALLOC ? BOOKMARK_MEMALLOC : 0);
+}
+
+#define EXTRA_OPTIONS 3 /* options for: 'foo', 'plugin-foo' and NULL */
+
+typedef DECLARE_MYSQL_SYSVAR_BASIC(sysvar_bool_t, my_bool);
+typedef DECLARE_MYSQL_THDVAR_BASIC(thdvar_bool_t, my_bool);
+typedef DECLARE_MYSQL_SYSVAR_BASIC(sysvar_str_t, char *);
+typedef DECLARE_MYSQL_THDVAR_BASIC(thdvar_str_t, char *);
+
+typedef DECLARE_MYSQL_SYSVAR_TYPELIB(sysvar_enum_t, unsigned long);
+typedef DECLARE_MYSQL_THDVAR_TYPELIB(thdvar_enum_t, unsigned long);
+typedef DECLARE_MYSQL_SYSVAR_TYPELIB(sysvar_set_t, ulonglong);
+typedef DECLARE_MYSQL_THDVAR_TYPELIB(thdvar_set_t, ulonglong);
+
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_int_t, int);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_long_t, long);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_longlong_t, longlong);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_uint_t, uint);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_ulong_t, ulong);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_ulonglong_t, ulonglong);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_double_t, double);
+
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_int_t, int);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_long_t, long);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_longlong_t, longlong);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_uint_t, uint);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_ulong_t, ulong);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_ulonglong_t, ulonglong);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_double_t, double);
+
+
+/****************************************************************************
+ default variable data check and update functions
+****************************************************************************/
+
+static int check_func_bool(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ const char *str;
+ int result, length;
+ long long tmp;
+
+ if (value->value_type(value) == MYSQL_VALUE_TYPE_STRING)
+ {
+ length= sizeof(buff);
+ if (!(str= value->val_str(value, buff, &length)) ||
+ (result= find_type(&bool_typelib, str, length, 1)-1) < 0)
+ goto err;
+ }
+ else
+ {
+ if (value->val_int(value, &tmp) < 0)
+ goto err;
+ if (tmp != 0 && tmp != 1)
+ goto err;
+ result= (int) tmp;
+ }
+ *(my_bool *) save= result ? 1 : 0;
+ return 0;
+err:
+ return 1;
+}
+
+
+static int check_func_int(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ my_bool fixed1, fixed2;
+ long long orig, val;
+ struct my_option options;
+ value->val_int(value, &orig);
+ val= orig;
+ plugin_opt_set_limits(&options, var);
+
+ if (var->flags & PLUGIN_VAR_UNSIGNED)
+ {
+ if ((fixed1= (!value->is_unsigned(value) && val < 0)))
+ val=0;
+ *(uint *)save= (uint) getopt_ull_limit_value((ulonglong) val, &options,
+ &fixed2);
+ }
+ else
+ {
+ if ((fixed1= (value->is_unsigned(value) && val < 0)))
+ val=LONGLONG_MAX;
+ *(int *)save= (int) getopt_ll_limit_value(val, &options, &fixed2);
+ }
+
+ return throw_bounds_warning(thd, var->name, fixed1 || fixed2,
+ value->is_unsigned(value), (longlong) orig);
+}
+
+
+static int check_func_long(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ my_bool fixed1, fixed2;
+ long long orig, val;
+ struct my_option options;
+ value->val_int(value, &orig);
+ val= orig;
+ plugin_opt_set_limits(&options, var);
+
+ if (var->flags & PLUGIN_VAR_UNSIGNED)
+ {
+ if ((fixed1= (!value->is_unsigned(value) && val < 0)))
+ val=0;
+ *(ulong *)save= (ulong) getopt_ull_limit_value((ulonglong) val, &options,
+ &fixed2);
+ }
+ else
+ {
+ if ((fixed1= (value->is_unsigned(value) && val < 0)))
+ val=LONGLONG_MAX;
+ *(long *)save= (long) getopt_ll_limit_value(val, &options, &fixed2);
+ }
+
+ return throw_bounds_warning(thd, var->name, fixed1 || fixed2,
+ value->is_unsigned(value), (longlong) orig);
+}
+
+
+static int check_func_longlong(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ my_bool fixed1, fixed2;
+ long long orig, val;
+ struct my_option options;
+ value->val_int(value, &orig);
+ val= orig;
+ plugin_opt_set_limits(&options, var);
+
+ if (var->flags & PLUGIN_VAR_UNSIGNED)
+ {
+ if ((fixed1= (!value->is_unsigned(value) && val < 0)))
+ val=0;
+ *(ulonglong *)save= getopt_ull_limit_value((ulonglong) val, &options,
+ &fixed2);
+ }
+ else
+ {
+ if ((fixed1= (value->is_unsigned(value) && val < 0)))
+ val=LONGLONG_MAX;
+ *(longlong *)save= getopt_ll_limit_value(val, &options, &fixed2);
+ }
+
+ return throw_bounds_warning(thd, var->name, fixed1 || fixed2,
+ value->is_unsigned(value), (longlong) orig);
+}
+
+static int check_func_str(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ const char *str;
+ int length;
+
+ length= sizeof(buff);
+ if ((str= value->val_str(value, buff, &length)))
+ str= thd->strmake(str, length);
+ *(const char**)save= str;
+ return 0;
+}
+
+
+static int check_func_enum(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ const char *str;
+ TYPELIB *typelib;
+ long long tmp;
+ long result;
+ int length;
+
+ if (var->flags & PLUGIN_VAR_THDLOCAL)
+ typelib= ((thdvar_enum_t*) var)->typelib;
+ else
+ typelib= ((sysvar_enum_t*) var)->typelib;
+
+ if (value->value_type(value) == MYSQL_VALUE_TYPE_STRING)
+ {
+ length= sizeof(buff);
+ if (!(str= value->val_str(value, buff, &length)))
+ goto err;
+ if ((result= (long)find_type(typelib, str, length, 0) - 1) < 0)
+ goto err;
+ }
+ else
+ {
+ if (value->val_int(value, &tmp))
+ goto err;
+ if (tmp < 0 || tmp >= typelib->count)
+ goto err;
+ result= (long) tmp;
+ }
+ *(long*)save= result;
+ return 0;
+err:
+ return 1;
+}
+
+
+static int check_func_set(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE], *error= 0;
+ const char *str;
+ TYPELIB *typelib;
+ ulonglong result;
+ uint error_len= 0; // init as only set on error
+ bool not_used;
+ int length;
+
+ if (var->flags & PLUGIN_VAR_THDLOCAL)
+ typelib= ((thdvar_set_t*) var)->typelib;
+ else
+ typelib= ((sysvar_set_t*)var)->typelib;
+
+ if (value->value_type(value) == MYSQL_VALUE_TYPE_STRING)
+ {
+ length= sizeof(buff);
+ if (!(str= value->val_str(value, buff, &length)))
+ goto err;
+ result= find_set(typelib, str, length, NULL,
+ &error, &error_len, &not_used);
+ if (error_len)
+ goto err;
+ }
+ else
+ {
+ if (value->val_int(value, (long long *)&result))
+ goto err;
+ if (unlikely((result >= (1ULL << typelib->count)) &&
+ (typelib->count < sizeof(long)*8)))
+ goto err;
+ }
+ *(ulonglong*)save= result;
+ return 0;
+err:
+ return 1;
+}
+
+static int check_func_double(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ double v;
+ my_bool fixed;
+ struct my_option option;
+
+ value->val_real(value, &v);
+ plugin_opt_set_limits(&option, var);
+ *(double *) save= getopt_double_limit_value(v, &option, &fixed);
+
+ return throw_bounds_warning(thd, var->name, fixed, v);
+}
+
+
+static void update_func_bool(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(my_bool *) tgt= *(my_bool *) save ? 1 : 0;
+}
+
+
+static void update_func_int(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(int *)tgt= *(int *) save;
+}
+
+
+static void update_func_long(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(long *)tgt= *(long *) save;
+}
+
+
+static void update_func_longlong(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(longlong *)tgt= *(ulonglong *) save;
+}
+
+
+static void update_func_str(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ char *value= *(char**) save;
+ if (var->flags & PLUGIN_VAR_MEMALLOC)
+ {
+ char *old= *(char**) tgt;
+ if (value)
+ *(char**) tgt= my_strdup(value, MYF(0));
+ else
+ *(char**) tgt= 0;
+ my_free(old);
+ }
+ else
+ *(char**) tgt= value;
+}
+
+static void update_func_double(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(double *) tgt= *(double *) save;
+}
+
+/****************************************************************************
+ System Variables support
+****************************************************************************/
+
+
+sys_var *find_sys_var(THD *thd, const char *str, uint length)
+{
+ sys_var *var;
+ sys_var_pluginvar *pi= NULL;
+ plugin_ref plugin;
+ DBUG_ENTER("find_sys_var");
+
+ mysql_mutex_lock(&LOCK_plugin);
+ mysql_rwlock_rdlock(&LOCK_system_variables_hash);
+ if ((var= intern_find_sys_var(str, length)) &&
+ (pi= var->cast_pluginvar()))
+ {
+ mysql_rwlock_unlock(&LOCK_system_variables_hash);
+ LEX *lex= thd ? thd->lex : 0;
+ if (!(plugin= intern_plugin_lock(lex, plugin_int_to_ref(pi->plugin))))
+ var= NULL; /* failed to lock it, it must be uninstalling */
+ else
+ if (!(plugin_state(plugin) & PLUGIN_IS_READY))
+ {
+ /* initialization not completed */
+ var= NULL;
+ intern_plugin_unlock(lex, plugin);
+ }
+ }
+ else
+ mysql_rwlock_unlock(&LOCK_system_variables_hash);
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ if (!var)
+ my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), (char*) str);
+ DBUG_RETURN(var);
+}
+
+
+/*
+ called by register_var, construct_options and test_plugin_options.
+ Returns the 'bookmark' for the named variable.
+ LOCK_system_variables_hash should be at least read locked
+*/
+static st_bookmark *find_bookmark(const char *plugin, const char *name,
+ int flags)
+{
+ st_bookmark *result= NULL;
+ uint namelen, length, pluginlen= 0;
+ char *varname, *p;
+
+ if (!(flags & PLUGIN_VAR_THDLOCAL))
+ return NULL;
+
+ namelen= strlen(name);
+ if (plugin)
+ pluginlen= strlen(plugin) + 1;
+ length= namelen + pluginlen + 2;
+ varname= (char*) my_alloca(length);
+
+ if (plugin)
+ {
+ strxmov(varname + 1, plugin, "_", name, NullS);
+ for (p= varname + 1; *p; p++)
+ if (*p == '-')
+ *p= '_';
+ }
+ else
+ memcpy(varname + 1, name, namelen + 1);
+
+ varname[0]= plugin_var_bookmark_key(flags);
+
+ result= (st_bookmark*) my_hash_search(&bookmark_hash,
+ (const uchar*) varname, length - 1);
+
+ my_afree(varname);
+ return result;
+}
+
+
+/*
+ returns a bookmark for thd-local variables, creating if neccessary.
+ returns null for non thd-local variables.
+ Requires that a write lock is obtained on LOCK_system_variables_hash
+*/
+static st_bookmark *register_var(const char *plugin, const char *name,
+ int flags)
+{
+ uint length= strlen(plugin) + strlen(name) + 3, size= 0, offset, new_size;
+ st_bookmark *result;
+ char *varname, *p;
+
+ if (!(flags & PLUGIN_VAR_THDLOCAL))
+ return NULL;
+
+ switch (flags & PLUGIN_VAR_TYPEMASK) {
+ case PLUGIN_VAR_BOOL:
+ size= sizeof(my_bool);
+ break;
+ case PLUGIN_VAR_INT:
+ size= sizeof(int);
+ break;
+ case PLUGIN_VAR_LONG:
+ case PLUGIN_VAR_ENUM:
+ size= sizeof(long);
+ break;
+ case PLUGIN_VAR_LONGLONG:
+ case PLUGIN_VAR_SET:
+ size= sizeof(ulonglong);
+ break;
+ case PLUGIN_VAR_STR:
+ size= sizeof(char*);
+ break;
+ case PLUGIN_VAR_DOUBLE:
+ size= sizeof(double);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ return NULL;
+ };
+
+ varname= ((char*) my_alloca(length));
+ strxmov(varname + 1, plugin, "_", name, NullS);
+ for (p= varname + 1; *p; p++)
+ if (*p == '-')
+ *p= '_';
+
+ if (!(result= find_bookmark(NULL, varname + 1, flags)))
+ {
+ result= (st_bookmark*) alloc_root(&plugin_vars_mem_root,
+ sizeof(struct st_bookmark) + length-1);
+ varname[0]= plugin_var_bookmark_key(flags);
+ memcpy(result->key, varname, length);
+ result->name_len= length - 2;
+ result->offset= -1;
+
+ DBUG_ASSERT(size && !(size & (size-1))); /* must be power of 2 */
+
+ offset= global_system_variables.dynamic_variables_size;
+ offset= (offset + size - 1) & ~(size - 1);
+ result->offset= (int) offset;
+
+ new_size= (offset + size + 63) & ~63;
+
+ if (new_size > global_variables_dynamic_size)
+ {
+ global_system_variables.dynamic_variables_ptr= (char*)
+ my_realloc(global_system_variables.dynamic_variables_ptr, new_size,
+ MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR));
+ max_system_variables.dynamic_variables_ptr= (char*)
+ my_realloc(max_system_variables.dynamic_variables_ptr, new_size,
+ MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR));
+ /*
+ Clear the new variable value space. This is required for string
+ variables. If their value is non-NULL, it must point to a valid
+ string.
+ */
+ bzero(global_system_variables.dynamic_variables_ptr +
+ global_variables_dynamic_size,
+ new_size - global_variables_dynamic_size);
+ bzero(max_system_variables.dynamic_variables_ptr +
+ global_variables_dynamic_size,
+ new_size - global_variables_dynamic_size);
+ global_variables_dynamic_size= new_size;
+ }
+
+ global_system_variables.dynamic_variables_head= offset;
+ max_system_variables.dynamic_variables_head= offset;
+ global_system_variables.dynamic_variables_size= offset + size;
+ max_system_variables.dynamic_variables_size= offset + size;
+ global_system_variables.dynamic_variables_version++;
+ max_system_variables.dynamic_variables_version++;
+
+ result->version= global_system_variables.dynamic_variables_version;
+
+ /* this should succeed because we have already checked if a dup exists */
+ if (my_hash_insert(&bookmark_hash, (uchar*) result))
+ {
+ fprintf(stderr, "failed to add placeholder to hash");
+ DBUG_ASSERT(0);
+ }
+ }
+ my_afree(varname);
+ return result;
+}
+
+/*
+ returns a pointer to the memory which holds the thd-local variable or
+ a pointer to the global variable if thd==null.
+ If required, will sync with global variables if the requested variable
+ has not yet been allocated in the current thread.
+*/
+static uchar *intern_sys_var_ptr(THD* thd, int offset, bool global_lock)
+{
+ DBUG_ENTER("intern_sys_var_ptr");
+ DBUG_ASSERT(offset >= 0);
+ DBUG_ASSERT((uint)offset <= global_system_variables.dynamic_variables_head);
+
+ if (!thd)
+ DBUG_RETURN((uchar*) global_system_variables.dynamic_variables_ptr + offset);
+
+ /*
+ dynamic_variables_head points to the largest valid offset
+ */
+ if (!thd->variables.dynamic_variables_ptr ||
+ (uint)offset > thd->variables.dynamic_variables_head)
+ {
+ uint idx;
+
+ mysql_rwlock_rdlock(&LOCK_system_variables_hash);
+
+ thd->variables.dynamic_variables_ptr= (char*)
+ my_realloc(thd->variables.dynamic_variables_ptr,
+ global_variables_dynamic_size,
+ MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR));
+
+ if (global_lock)
+ mysql_mutex_lock(&LOCK_global_system_variables);
+
+ mysql_mutex_assert_owner(&LOCK_global_system_variables);
+
+ memcpy(thd->variables.dynamic_variables_ptr +
+ thd->variables.dynamic_variables_size,
+ global_system_variables.dynamic_variables_ptr +
+ thd->variables.dynamic_variables_size,
+ global_system_variables.dynamic_variables_size -
+ thd->variables.dynamic_variables_size);
+
+ /*
+ now we need to iterate through any newly copied 'defaults'
+ and if it is a string type with MEMALLOC flag, we need to strdup
+ */
+ for (idx= 0; idx < bookmark_hash.records; idx++)
+ {
+ sys_var_pluginvar *pi;
+ sys_var *var;
+ st_bookmark *v= (st_bookmark*) my_hash_element(&bookmark_hash,idx);
+
+ if (v->version <= thd->variables.dynamic_variables_version)
+ continue; /* already in thd->variables */
+
+ if (!(var= intern_find_sys_var(v->key + 1, v->name_len)) ||
+ !(pi= var->cast_pluginvar()) ||
+ v->key[0] != plugin_var_bookmark_key(pi->plugin_var->flags))
+ continue;
+
+ /* Here we do anything special that may be required of the data types */
+
+ if ((pi->plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
+ pi->plugin_var->flags & PLUGIN_VAR_MEMALLOC)
+ {
+ char **pp= (char**) (thd->variables.dynamic_variables_ptr +
+ *(int*)(pi->plugin_var + 1));
+ if ((*pp= *(char**) (global_system_variables.dynamic_variables_ptr +
+ *(int*)(pi->plugin_var + 1))))
+ *pp= my_strdup(*pp, MYF(MY_WME|MY_FAE));
+ }
+ }
+
+ if (global_lock)
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ thd->variables.dynamic_variables_version=
+ global_system_variables.dynamic_variables_version;
+ thd->variables.dynamic_variables_head=
+ global_system_variables.dynamic_variables_head;
+ thd->variables.dynamic_variables_size=
+ global_system_variables.dynamic_variables_size;
+
+ mysql_rwlock_unlock(&LOCK_system_variables_hash);
+ }
+ DBUG_RETURN((uchar*)thd->variables.dynamic_variables_ptr + offset);
+}
+
+
+/**
+ For correctness and simplicity's sake, a pointer to a function
+ must be compatible with pointed-to type, that is, the return and
+ parameters types must be the same. Thus, a callback function is
+ defined for each scalar type. The functions are assigned in
+ construct_options to their respective types.
+*/
+
+static char *mysql_sys_var_char(THD* thd, int offset)
+{
+ return (char *) intern_sys_var_ptr(thd, offset, true);
+}
+
+static int *mysql_sys_var_int(THD* thd, int offset)
+{
+ return (int *) intern_sys_var_ptr(thd, offset, true);
+}
+
+static long *mysql_sys_var_long(THD* thd, int offset)
+{
+ return (long *) intern_sys_var_ptr(thd, offset, true);
+}
+
+static unsigned long *mysql_sys_var_ulong(THD* thd, int offset)
+{
+ return (unsigned long *) intern_sys_var_ptr(thd, offset, true);
+}
+
+static long long *mysql_sys_var_longlong(THD* thd, int offset)
+{
+ return (long long *) intern_sys_var_ptr(thd, offset, true);
+}
+
+static unsigned long long *mysql_sys_var_ulonglong(THD* thd, int offset)
+{
+ return (unsigned long long *) intern_sys_var_ptr(thd, offset, true);
+}
+
+static char **mysql_sys_var_str(THD* thd, int offset)
+{
+ return (char **) intern_sys_var_ptr(thd, offset, true);
+}
+
+static double *mysql_sys_var_double(THD* thd, int offset)
+{
+ return (double *) intern_sys_var_ptr(thd, offset, true);
+}
+
+void plugin_thdvar_init(THD *thd)
+{
+ plugin_ref old_table_plugin= thd->variables.table_plugin;
+ DBUG_ENTER("plugin_thdvar_init");
+
+ thd->variables.table_plugin= NULL;
+ cleanup_variables(thd, &thd->variables);
+
+ thd->variables= global_system_variables;
+ thd->variables.table_plugin= NULL;
+
+ /* we are going to allocate these lazily */
+ thd->variables.dynamic_variables_version= 0;
+ thd->variables.dynamic_variables_size= 0;
+ thd->variables.dynamic_variables_ptr= 0;
+
+ mysql_mutex_lock(&LOCK_plugin);
+ thd->variables.table_plugin=
+ intern_plugin_lock(NULL, global_system_variables.table_plugin);
+ intern_plugin_unlock(NULL, old_table_plugin);
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Unlocks all system variables which hold a reference
+*/
+static void unlock_variables(THD *thd, struct system_variables *vars)
+{
+ intern_plugin_unlock(NULL, vars->table_plugin);
+ vars->table_plugin= NULL;
+}
+
+
+/*
+ Frees memory used by system variables
+
+ Unlike plugin_vars_free_values() it frees all variables of all plugins,
+ it's used on shutdown.
+*/
+static void cleanup_variables(THD *thd, struct system_variables *vars)
+{
+ st_bookmark *v;
+ uint idx;
+
+ mysql_rwlock_rdlock(&LOCK_system_variables_hash);
+ for (idx= 0; idx < bookmark_hash.records; idx++)
+ {
+ v= (st_bookmark*) my_hash_element(&bookmark_hash, idx);
+
+ if (v->version > vars->dynamic_variables_version)
+ continue; /* not in vars */
+
+ DBUG_ASSERT((uint)v->offset <= vars->dynamic_variables_head);
+
+ if ((v->key[0] & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
+ v->key[0] & BOOKMARK_MEMALLOC)
+ {
+ char **ptr= (char**)(vars->dynamic_variables_ptr + v->offset);
+ my_free(*ptr);
+ *ptr= NULL;
+ }
+ }
+ mysql_rwlock_unlock(&LOCK_system_variables_hash);
+
+ DBUG_ASSERT(vars->table_plugin == NULL);
+
+ my_free(vars->dynamic_variables_ptr);
+ vars->dynamic_variables_ptr= NULL;
+ vars->dynamic_variables_size= 0;
+ vars->dynamic_variables_version= 0;
+}
+
+
+void plugin_thdvar_cleanup(THD *thd)
+{
+ uint idx;
+ plugin_ref *list;
+ DBUG_ENTER("plugin_thdvar_cleanup");
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ unlock_variables(thd, &thd->variables);
+ cleanup_variables(thd, &thd->variables);
+
+ if ((idx= thd->lex->plugins.elements))
+ {
+ list= ((plugin_ref*) thd->lex->plugins.buffer) + idx - 1;
+ DBUG_PRINT("info",("unlocking %d plugins", idx));
+ while ((uchar*) list >= thd->lex->plugins.buffer)
+ intern_plugin_unlock(NULL, *list--);
+ }
+
+ reap_plugins();
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ reset_dynamic(&thd->lex->plugins);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @brief Free values of thread variables of a plugin.
+
+ This must be called before a plugin is deleted. Otherwise its
+ variables are no longer accessible and the value space is lost. Note
+ that only string values with PLUGIN_VAR_MEMALLOC are allocated and
+ must be freed.
+
+ @param[in] vars Chain of system variables of a plugin
+*/
+
+static void plugin_vars_free_values(sys_var *vars)
+{
+ DBUG_ENTER("plugin_vars_free_values");
+
+ for (sys_var *var= vars; var; var= var->next)
+ {
+ sys_var_pluginvar *piv= var->cast_pluginvar();
+ if (piv &&
+ ((piv->plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR) &&
+ (piv->plugin_var->flags & PLUGIN_VAR_MEMALLOC))
+ {
+ /* Free the string from global_system_variables. */
+ char **valptr= (char**) piv->real_value_ptr(NULL, OPT_GLOBAL);
+ DBUG_PRINT("plugin", ("freeing value for: '%s' addr: 0x%lx",
+ var->name.str, (long) valptr));
+ my_free(*valptr);
+ *valptr= NULL;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var)
+{
+ switch (plugin_var->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_UNSIGNED)) {
+ case PLUGIN_VAR_BOOL:
+ return SHOW_MY_BOOL;
+ case PLUGIN_VAR_INT:
+ return SHOW_SINT;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_UNSIGNED:
+ return SHOW_UINT;
+ case PLUGIN_VAR_LONG:
+ return SHOW_SLONG;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_UNSIGNED:
+ return SHOW_ULONG;
+ case PLUGIN_VAR_LONGLONG:
+ return SHOW_SLONGLONG;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_UNSIGNED:
+ return SHOW_ULONGLONG;
+ case PLUGIN_VAR_STR:
+ return SHOW_CHAR_PTR;
+ case PLUGIN_VAR_ENUM:
+ case PLUGIN_VAR_SET:
+ return SHOW_CHAR;
+ case PLUGIN_VAR_DOUBLE:
+ return SHOW_DOUBLE;
+ default:
+ DBUG_ASSERT(0);
+ return SHOW_UNDEF;
+ }
+}
+
+
+bool sys_var_pluginvar::check_update_type(Item_result type)
+{
+ switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) {
+ case PLUGIN_VAR_INT:
+ case PLUGIN_VAR_LONG:
+ case PLUGIN_VAR_LONGLONG:
+ return type != INT_RESULT;
+ case PLUGIN_VAR_STR:
+ return type != STRING_RESULT;
+ case PLUGIN_VAR_ENUM:
+ case PLUGIN_VAR_BOOL:
+ case PLUGIN_VAR_SET:
+ return type != STRING_RESULT && type != INT_RESULT;
+ case PLUGIN_VAR_DOUBLE:
+ return type != INT_RESULT && type != REAL_RESULT && type != DECIMAL_RESULT;
+ default:
+ return true;
+ }
+}
+
+
+uchar* sys_var_pluginvar::real_value_ptr(THD *thd, enum_var_type type)
+{
+ DBUG_ASSERT(thd || (type == OPT_GLOBAL));
+ if (plugin_var->flags & PLUGIN_VAR_THDLOCAL)
+ {
+ if (type == OPT_GLOBAL)
+ thd= NULL;
+
+ return intern_sys_var_ptr(thd, *(int*) (plugin_var+1), false);
+ }
+ return *(uchar**) (plugin_var+1);
+}
+
+
+TYPELIB* sys_var_pluginvar::plugin_var_typelib(void)
+{
+ switch (plugin_var->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_THDLOCAL)) {
+ case PLUGIN_VAR_ENUM:
+ return ((sysvar_enum_t *)plugin_var)->typelib;
+ case PLUGIN_VAR_SET:
+ return ((sysvar_set_t *)plugin_var)->typelib;
+ case PLUGIN_VAR_ENUM | PLUGIN_VAR_THDLOCAL:
+ return ((thdvar_enum_t *)plugin_var)->typelib;
+ case PLUGIN_VAR_SET | PLUGIN_VAR_THDLOCAL:
+ return ((thdvar_set_t *)plugin_var)->typelib;
+ default:
+ return NULL;
+ }
+ return NULL; /* Keep compiler happy */
+}
+
+
+uchar* sys_var_pluginvar::do_value_ptr(THD *thd, enum_var_type type,
+ LEX_STRING *base)
+{
+ uchar* result;
+
+ result= real_value_ptr(thd, type);
+
+ if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_ENUM)
+ result= (uchar*) get_type(plugin_var_typelib(), *(ulong*)result);
+ else if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_SET)
+ result= (uchar*) set_to_string(thd, 0, *(ulonglong*) result,
+ plugin_var_typelib()->type_names);
+ return result;
+}
+
+bool sys_var_pluginvar::do_check(THD *thd, set_var *var)
+{
+ st_item_value_holder value;
+ DBUG_ASSERT(!is_readonly());
+ DBUG_ASSERT(plugin_var->check);
+
+ value.value_type= item_value_type;
+ value.val_str= item_val_str;
+ value.val_int= item_val_int;
+ value.val_real= item_val_real;
+ value.is_unsigned= item_is_unsigned;
+ value.item= var->value;
+
+ return plugin_var->check(thd, plugin_var, &var->save_result, &value);
+}
+
+bool sys_var_pluginvar::session_update(THD *thd, set_var *var)
+{
+ DBUG_ASSERT(!is_readonly());
+ DBUG_ASSERT(plugin_var->flags & PLUGIN_VAR_THDLOCAL);
+ DBUG_ASSERT(thd == current_thd);
+
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ void *tgt= real_value_ptr(thd, var->type);
+ const void *src= var->value ? (void*)&var->save_result
+ : (void*)real_value_ptr(thd, OPT_GLOBAL);
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ plugin_var->update(thd, plugin_var, tgt, src);
+
+ return false;
+}
+
+bool sys_var_pluginvar::global_update(THD *thd, set_var *var)
+{
+ DBUG_ASSERT(!is_readonly());
+ mysql_mutex_assert_owner(&LOCK_global_system_variables);
+
+ void *tgt= real_value_ptr(thd, var->type);
+ const void *src= &var->save_result;
+
+ if (!var->value)
+ {
+ switch (plugin_var->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_THDLOCAL)) {
+ case PLUGIN_VAR_INT:
+ src= &((sysvar_uint_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_LONG:
+ src= &((sysvar_ulong_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_LONGLONG:
+ src= &((sysvar_ulonglong_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_ENUM:
+ src= &((sysvar_enum_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_SET:
+ src= &((sysvar_set_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_BOOL:
+ src= &((sysvar_bool_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_STR:
+ src= &((sysvar_str_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_DOUBLE:
+ src= &((sysvar_double_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_uint_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_ulong_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_ulonglong_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_ENUM | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_enum_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_SET | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_set_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_BOOL | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_bool_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_STR | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_str_t*) plugin_var)->def_val;
+ break;
+ case PLUGIN_VAR_DOUBLE | PLUGIN_VAR_THDLOCAL:
+ src= &((thdvar_double_t*) plugin_var)->def_val;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ }
+
+ plugin_var->update(thd, plugin_var, tgt, src);
+ return false;
+}
+
+
+#define OPTION_SET_LIMITS(type, options, opt) \
+ options->var_type= type; \
+ options->def_value= (opt)->def_val; \
+ options->min_value= (opt)->min_val; \
+ options->max_value= (opt)->max_val; \
+ options->block_size= (long) (opt)->blk_sz
+
+#define OPTION_SET_LIMITS_DOUBLE(options, opt) \
+ options->var_type= GET_DOUBLE; \
+ options->def_value= (longlong) getopt_double2ulonglong((opt)->def_val); \
+ options->min_value= (longlong) getopt_double2ulonglong((opt)->min_val); \
+ options->max_value= getopt_double2ulonglong((opt)->max_val); \
+ options->block_size= (long) (opt)->blk_sz;
+
+
+void plugin_opt_set_limits(struct my_option *options,
+ const struct st_mysql_sys_var *opt)
+{
+ options->sub_size= 0;
+
+ switch (opt->flags & (PLUGIN_VAR_TYPEMASK |
+ PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL)) {
+ /* global system variables */
+ case PLUGIN_VAR_INT:
+ OPTION_SET_LIMITS(GET_INT, options, (sysvar_int_t*) opt);
+ break;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_UNSIGNED:
+ OPTION_SET_LIMITS(GET_UINT, options, (sysvar_uint_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG:
+ OPTION_SET_LIMITS(GET_LONG, options, (sysvar_long_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_UNSIGNED:
+ OPTION_SET_LIMITS(GET_ULONG, options, (sysvar_ulong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG:
+ OPTION_SET_LIMITS(GET_LL, options, (sysvar_longlong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_UNSIGNED:
+ OPTION_SET_LIMITS(GET_ULL, options, (sysvar_ulonglong_t*) opt);
+ break;
+ case PLUGIN_VAR_ENUM:
+ options->var_type= GET_ENUM;
+ options->typelib= ((sysvar_enum_t*) opt)->typelib;
+ options->def_value= ((sysvar_enum_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= options->typelib->count - 1;
+ break;
+ case PLUGIN_VAR_SET:
+ options->var_type= GET_SET;
+ options->typelib= ((sysvar_set_t*) opt)->typelib;
+ options->def_value= ((sysvar_set_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= (1ULL << options->typelib->count) - 1;
+ break;
+ case PLUGIN_VAR_BOOL:
+ options->var_type= GET_BOOL;
+ options->def_value= ((sysvar_bool_t*) opt)->def_val;
+ break;
+ case PLUGIN_VAR_STR:
+ options->var_type= ((opt->flags & PLUGIN_VAR_MEMALLOC) ?
+ GET_STR_ALLOC : GET_STR);
+ options->def_value= (intptr) ((sysvar_str_t*) opt)->def_val;
+ break;
+ case PLUGIN_VAR_DOUBLE:
+ OPTION_SET_LIMITS_DOUBLE(options, (sysvar_double_t*) opt);
+ break;
+ /* threadlocal variables */
+ case PLUGIN_VAR_INT | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_INT, options, (thdvar_int_t*) opt);
+ break;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_UINT, options, (thdvar_uint_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_LONG, options, (thdvar_long_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_ULONG, options, (thdvar_ulong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_LL, options, (thdvar_longlong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_ULL, options, (thdvar_ulonglong_t*) opt);
+ break;
+ case PLUGIN_VAR_DOUBLE | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS_DOUBLE(options, (thdvar_double_t*) opt);
+ break;
+ case PLUGIN_VAR_ENUM | PLUGIN_VAR_THDLOCAL:
+ options->var_type= GET_ENUM;
+ options->typelib= ((thdvar_enum_t*) opt)->typelib;
+ options->def_value= ((thdvar_enum_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= options->typelib->count - 1;
+ break;
+ case PLUGIN_VAR_SET | PLUGIN_VAR_THDLOCAL:
+ options->var_type= GET_SET;
+ options->typelib= ((thdvar_set_t*) opt)->typelib;
+ options->def_value= ((thdvar_set_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= (1ULL << options->typelib->count) - 1;
+ break;
+ case PLUGIN_VAR_BOOL | PLUGIN_VAR_THDLOCAL:
+ options->var_type= GET_BOOL;
+ options->def_value= ((thdvar_bool_t*) opt)->def_val;
+ break;
+ case PLUGIN_VAR_STR | PLUGIN_VAR_THDLOCAL:
+ options->var_type= ((opt->flags & PLUGIN_VAR_MEMALLOC) ?
+ GET_STR_ALLOC : GET_STR);
+ options->def_value= (intptr) ((thdvar_str_t*) opt)->def_val;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ options->arg_type= REQUIRED_ARG;
+ if (opt->flags & PLUGIN_VAR_NOCMDARG)
+ options->arg_type= NO_ARG;
+ if (opt->flags & PLUGIN_VAR_OPCMDARG)
+ options->arg_type= OPT_ARG;
+}
+
+/**
+ Creates a set of my_option objects associated with a specified plugin-
+ handle.
+
+ @param mem_root Memory allocator to be used.
+ @param tmp A pointer to a plugin handle
+ @param[out] options A pointer to a pre-allocated static array
+
+ The set is stored in the pre-allocated static array supplied to the function.
+ The size of the array is calculated as (number_of_plugin_varaibles*2+3). The
+ reason is that each option can have a prefix '--plugin-' in addtion to the
+ shorter form '--&lt;plugin-name&gt;'. There is also space allocated for
+ terminating NULL pointers.
+
+ @return
+ @retval -1 An error occurred
+ @retval 0 Success
+*/
+
+static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp,
+ my_option *options)
+{
+ const char *plugin_name= tmp->plugin->name;
+ const LEX_STRING plugin_dash = { C_STRING_WITH_LEN("plugin-") };
+ uint plugin_name_len= strlen(plugin_name);
+ uint optnamelen;
+ const int max_comment_len= 180;
+ char *comment= (char *) alloc_root(mem_root, max_comment_len + 1);
+ char *optname;
+
+ int index= 0, offset= 0;
+ st_mysql_sys_var *opt, **plugin_option;
+ st_bookmark *v;
+
+ /** Used to circumvent the const attribute on my_option::name */
+ char *plugin_name_ptr, *plugin_name_with_prefix_ptr;
+
+ DBUG_ENTER("construct_options");
+
+ plugin_name_ptr= (char*) alloc_root(mem_root, plugin_name_len + 1);
+ strcpy(plugin_name_ptr, plugin_name);
+ my_casedn_str(&my_charset_latin1, plugin_name_ptr);
+ convert_underscore_to_dash(plugin_name_ptr, plugin_name_len);
+ plugin_name_with_prefix_ptr= (char*) alloc_root(mem_root,
+ plugin_name_len +
+ plugin_dash.length + 1);
+ strxmov(plugin_name_with_prefix_ptr, plugin_dash.str, plugin_name_ptr, NullS);
+
+ if (tmp->load_option != PLUGIN_FORCE &&
+ tmp->load_option != PLUGIN_FORCE_PLUS_PERMANENT)
+ {
+ /* support --skip-plugin-foo syntax */
+ options[0].name= plugin_name_ptr;
+ options[1].name= plugin_name_with_prefix_ptr;
+ options[0].id= options[1].id= 0;
+ options[0].var_type= options[1].var_type= GET_ENUM;
+ options[0].arg_type= options[1].arg_type= OPT_ARG;
+ options[0].def_value= options[1].def_value= 1; /* ON */
+ options[0].typelib= options[1].typelib= &global_plugin_typelib;
+
+ strxnmov(comment, max_comment_len, "Enable or disable ", plugin_name,
+ " plugin. Possible values are ON, OFF, FORCE (don't start "
+ "if the plugin fails to load).", NullS);
+ options[0].comment= comment;
+ /*
+ Allocate temporary space for the value of the tristate.
+ This option will have a limited lifetime and is not used beyond
+ server initialization.
+ GET_ENUM value is an unsigned long integer.
+ */
+ options[0].value= options[1].value=
+ (uchar **)alloc_root(mem_root, sizeof(ulong));
+ *((ulong*) options[0].value)= (ulong) options[0].def_value;
+
+ options+= 2;
+ }
+
+ if (!my_strcasecmp(&my_charset_latin1, plugin_name_ptr, "NDBCLUSTER"))
+ {
+ plugin_name_ptr= const_cast<char*>("ndb"); // Use legacy "ndb" prefix
+ plugin_name_len= 3;
+ }
+
+ /*
+ Two passes as the 2nd pass will take pointer addresses for use
+ by my_getopt and register_var() in the first pass uses realloc
+ */
+
+ for (plugin_option= tmp->plugin->system_vars;
+ plugin_option && *plugin_option; plugin_option++, index++)
+ {
+ opt= *plugin_option;
+ if (!(opt->flags & PLUGIN_VAR_THDLOCAL))
+ continue;
+ if (!(register_var(plugin_name_ptr, opt->name, opt->flags)))
+ continue;
+ switch (opt->flags & PLUGIN_VAR_TYPEMASK) {
+ case PLUGIN_VAR_BOOL:
+ ((thdvar_bool_t *) opt)->resolve= mysql_sys_var_char;
+ break;
+ case PLUGIN_VAR_INT:
+ ((thdvar_int_t *) opt)->resolve= mysql_sys_var_int;
+ break;
+ case PLUGIN_VAR_LONG:
+ ((thdvar_long_t *) opt)->resolve= mysql_sys_var_long;
+ break;
+ case PLUGIN_VAR_LONGLONG:
+ ((thdvar_longlong_t *) opt)->resolve= mysql_sys_var_longlong;
+ break;
+ case PLUGIN_VAR_STR:
+ ((thdvar_str_t *) opt)->resolve= mysql_sys_var_str;
+ break;
+ case PLUGIN_VAR_ENUM:
+ ((thdvar_enum_t *) opt)->resolve= mysql_sys_var_ulong;
+ break;
+ case PLUGIN_VAR_SET:
+ ((thdvar_set_t *) opt)->resolve= mysql_sys_var_ulonglong;
+ break;
+ case PLUGIN_VAR_DOUBLE:
+ ((thdvar_double_t *) opt)->resolve= mysql_sys_var_double;
+ break;
+ default:
+ sql_print_error("Unknown variable type code 0x%x in plugin '%s'.",
+ opt->flags, plugin_name);
+ DBUG_RETURN(-1);
+ };
+ }
+
+ for (plugin_option= tmp->plugin->system_vars;
+ plugin_option && *plugin_option; plugin_option++, index++)
+ {
+ switch ((opt= *plugin_option)->flags & PLUGIN_VAR_TYPEMASK) {
+ case PLUGIN_VAR_BOOL:
+ if (!opt->check)
+ opt->check= check_func_bool;
+ if (!opt->update)
+ opt->update= update_func_bool;
+ break;
+ case PLUGIN_VAR_INT:
+ if (!opt->check)
+ opt->check= check_func_int;
+ if (!opt->update)
+ opt->update= update_func_int;
+ break;
+ case PLUGIN_VAR_LONG:
+ if (!opt->check)
+ opt->check= check_func_long;
+ if (!opt->update)
+ opt->update= update_func_long;
+ break;
+ case PLUGIN_VAR_LONGLONG:
+ if (!opt->check)
+ opt->check= check_func_longlong;
+ if (!opt->update)
+ opt->update= update_func_longlong;
+ break;
+ case PLUGIN_VAR_STR:
+ if (!opt->check)
+ opt->check= check_func_str;
+ if (!opt->update)
+ {
+ opt->update= update_func_str;
+ if (!(opt->flags & (PLUGIN_VAR_MEMALLOC | PLUGIN_VAR_READONLY)))
+ {
+ opt->flags|= PLUGIN_VAR_READONLY;
+ sql_print_warning("Server variable %s of plugin %s was forced "
+ "to be read-only: string variable without "
+ "update_func and PLUGIN_VAR_MEMALLOC flag",
+ opt->name, plugin_name);
+ }
+ }
+ break;
+ case PLUGIN_VAR_ENUM:
+ if (!opt->check)
+ opt->check= check_func_enum;
+ if (!opt->update)
+ opt->update= update_func_long;
+ break;
+ case PLUGIN_VAR_SET:
+ if (!opt->check)
+ opt->check= check_func_set;
+ if (!opt->update)
+ opt->update= update_func_longlong;
+ break;
+ case PLUGIN_VAR_DOUBLE:
+ if (!opt->check)
+ opt->check= check_func_double;
+ if (!opt->update)
+ opt->update= update_func_double;
+ break;
+ default:
+ sql_print_error("Unknown variable type code 0x%x in plugin '%s'.",
+ opt->flags, plugin_name);
+ DBUG_RETURN(-1);
+ }
+
+ if ((opt->flags & (PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_THDLOCAL))
+ == PLUGIN_VAR_NOCMDOPT)
+ continue;
+
+ if (!opt->name)
+ {
+ sql_print_error("Missing variable name in plugin '%s'.",
+ plugin_name);
+ DBUG_RETURN(-1);
+ }
+
+ if (!(opt->flags & PLUGIN_VAR_THDLOCAL))
+ {
+ optnamelen= strlen(opt->name);
+ optname= (char*) alloc_root(mem_root, plugin_name_len + optnamelen + 2);
+ strxmov(optname, plugin_name_ptr, "-", opt->name, NullS);
+ optnamelen= plugin_name_len + optnamelen + 1;
+ }
+ else
+ {
+ /* this should not fail because register_var should create entry */
+ if (!(v= find_bookmark(plugin_name_ptr, opt->name, opt->flags)))
+ {
+ sql_print_error("Thread local variable '%s' not allocated "
+ "in plugin '%s'.", opt->name, plugin_name);
+ DBUG_RETURN(-1);
+ }
+
+ *(int*)(opt + 1)= offset= v->offset;
+
+ if (opt->flags & PLUGIN_VAR_NOCMDOPT)
+ continue;
+
+ optname= (char*) memdup_root(mem_root, v->key + 1,
+ (optnamelen= v->name_len) + 1);
+ }
+
+ convert_underscore_to_dash(optname, optnamelen);
+
+ options->name= optname;
+ options->comment= opt->comment;
+ options->app_type= opt;
+ options->id= 0;
+
+ plugin_opt_set_limits(options, opt);
+
+ if (opt->flags & PLUGIN_VAR_THDLOCAL)
+ options->value= options->u_max_value= (uchar**)
+ (global_system_variables.dynamic_variables_ptr + offset);
+ else
+ options->value= options->u_max_value= *(uchar***) (opt + 1);
+
+ char *option_name_ptr;
+ options[1]= options[0];
+ options[1].name= option_name_ptr= (char*) alloc_root(mem_root,
+ plugin_dash.length +
+ optnamelen + 1);
+ options[1].comment= 0; /* Hidden from the help text */
+ strxmov(option_name_ptr, plugin_dash.str, optname, NullS);
+
+ options+= 2;
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+static my_option *construct_help_options(MEM_ROOT *mem_root,
+ struct st_plugin_int *p)
+{
+ st_mysql_sys_var **opt;
+ my_option *opts;
+ uint count= EXTRA_OPTIONS;
+ DBUG_ENTER("construct_help_options");
+
+ for (opt= p->plugin->system_vars; opt && *opt; opt++, count+= 2)
+ ;
+
+ if (!(opts= (my_option*) alloc_root(mem_root, sizeof(my_option) * count)))
+ DBUG_RETURN(NULL);
+
+ bzero(opts, sizeof(my_option) * count);
+
+ /**
+ some plugin variables (those that don't have PLUGIN_VAR_NOSYSVAR flag)
+ have their names prefixed with the plugin name. Restore the names here
+ to get the correct (not double-prefixed) help text.
+ We won't need @@sysvars anymore and don't care about their proper names.
+ */
+ restore_ptr_backup(p->nbackups, p->ptr_backup);
+
+ if (construct_options(mem_root, p, opts))
+ DBUG_RETURN(NULL);
+
+ DBUG_RETURN(opts);
+}
+
+/**
+ Create and register system variables supplied from the plugin and
+ assigns initial values from corresponding command line arguments.
+
+ @param tmp_root Temporary scratch space
+ @param[out] plugin Internal plugin structure
+ @param argc Number of command line arguments
+ @param argv Command line argument vector
+
+ The plugin will be updated with a policy on how to handle errors during
+ initialization.
+
+ @note Requires that a write-lock is held on LOCK_system_variables_hash
+
+ @return How initialization of the plugin should be handled.
+ @retval 0 Initialization should proceed.
+ @retval 1 Plugin is disabled.
+ @retval -1 An error has occurred.
+*/
+
+static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp,
+ int *argc, char **argv)
+{
+ struct sys_var_chain chain= { NULL, NULL };
+ bool disable_plugin;
+ enum_plugin_load_option plugin_load_option= tmp->load_option;
+
+ MEM_ROOT *mem_root= alloc_root_inited(&tmp->mem_root) ?
+ &tmp->mem_root : &plugin_vars_mem_root;
+ st_mysql_sys_var **opt;
+ my_option *opts= NULL;
+ LEX_STRING plugin_name;
+ char *varname;
+ int error;
+ sys_var *v __attribute__((unused));
+ struct st_bookmark *var;
+ uint len, count= EXTRA_OPTIONS;
+ st_ptr_backup *tmp_backup= 0;
+ DBUG_ENTER("test_plugin_options");
+ DBUG_ASSERT(tmp->plugin && tmp->name.str);
+
+ for (opt= tmp->plugin->system_vars; opt && *opt; opt++)
+ count+= 2; /* --{plugin}-{optname} and --plugin-{plugin}-{optname} */
+
+ if (count > EXTRA_OPTIONS || (*argc > 1))
+ {
+ if (!(opts= (my_option*) alloc_root(tmp_root, sizeof(my_option) * count)))
+ {
+ sql_print_error("Out of memory for plugin '%s'.", tmp->name.str);
+ DBUG_RETURN(-1);
+ }
+ bzero(opts, sizeof(my_option) * count);
+
+ if (construct_options(tmp_root, tmp, opts))
+ {
+ sql_print_error("Bad options for plugin '%s'.", tmp->name.str);
+ DBUG_RETURN(-1);
+ }
+
+ /*
+ We adjust the default value to account for the hardcoded exceptions
+ we have set for the federated and ndbcluster storage engines.
+ */
+ if (tmp->load_option != PLUGIN_FORCE &&
+ tmp->load_option != PLUGIN_FORCE_PLUS_PERMANENT)
+ opts[0].def_value= opts[1].def_value= plugin_load_option;
+
+ error= handle_options(argc, &argv, opts, NULL);
+ (*argc)++; /* add back one for the program name */
+
+ if (error)
+ {
+ sql_print_error("Parsing options for plugin '%s' failed.",
+ tmp->name.str);
+ goto err;
+ }
+ /*
+ Set plugin loading policy from option value. First element in the option
+ list is always the <plugin name> option value.
+ */
+ if (tmp->load_option != PLUGIN_FORCE &&
+ tmp->load_option != PLUGIN_FORCE_PLUS_PERMANENT)
+ plugin_load_option= (enum_plugin_load_option) *(ulong*) opts[0].value;
+ }
+
+ disable_plugin= (plugin_load_option == PLUGIN_OFF);
+ tmp->load_option= plugin_load_option;
+
+ /*
+ If the plugin is disabled it should not be initialized.
+ */
+ if (disable_plugin)
+ {
+ if (global_system_variables.log_warnings)
+ sql_print_information("Plugin '%s' is disabled.",
+ tmp->name.str);
+ if (opts)
+ my_cleanup_options(opts);
+ DBUG_RETURN(1);
+ }
+
+ if (!my_strcasecmp(&my_charset_latin1, tmp->name.str, "NDBCLUSTER"))
+ {
+ plugin_name.str= const_cast<char*>("ndb"); // Use legacy "ndb" prefix
+ plugin_name.length= 3;
+ }
+ else
+ plugin_name= tmp->name;
+
+ error= 1;
+
+ if (tmp->plugin->system_vars)
+ {
+ for (len=0, opt= tmp->plugin->system_vars; *opt; len++, opt++) /* no-op */;
+ tmp_backup= (st_ptr_backup *)my_alloca(len * sizeof(tmp_backup[0]));
+ DBUG_ASSERT(tmp->nbackups == 0);
+ DBUG_ASSERT(tmp->ptr_backup == 0);
+
+ for (opt= tmp->plugin->system_vars; *opt; opt++)
+ {
+ st_mysql_sys_var *o= *opt;
+
+ /*
+ PLUGIN_VAR_STR command-line options without PLUGIN_VAR_MEMALLOC, point
+ directly to values in the argv[] array. For plugins started at the
+ server startup, argv[] array is allocated with load_defaults(), and
+ freed when the server is shut down. But for plugins loaded with
+ INSTALL PLUGIN, the memory allocated with load_defaults() is freed with
+ freed() at the end of mysql_install_plugin(). Which means we cannot
+ allow any pointers into that area.
+ Thus, for all plugins loaded after the server was started,
+ we copy string values to a plugin's memroot.
+ */
+ if (mysqld_server_started &&
+ ((o->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_NOCMDOPT |
+ PLUGIN_VAR_MEMALLOC)) == PLUGIN_VAR_STR))
+ {
+ sysvar_str_t* str= (sysvar_str_t *)o;
+ if (*str->value)
+ *str->value= strdup_root(mem_root, *str->value);
+ }
+
+ if (o->flags & PLUGIN_VAR_NOSYSVAR)
+ continue;
+ tmp_backup[tmp->nbackups++].save(&o->name);
+ if ((var= find_bookmark(plugin_name.str, o->name, o->flags)))
+ v= new (mem_root) sys_var_pluginvar(&chain, var->key + 1, o);
+ else
+ {
+ len= plugin_name.length + strlen(o->name) + 2;
+ varname= (char*) alloc_root(mem_root, len);
+ strxmov(varname, plugin_name.str, "-", o->name, NullS);
+ my_casedn_str(&my_charset_latin1, varname);
+ convert_dash_to_underscore(varname, len-1);
+ v= new (mem_root) sys_var_pluginvar(&chain, varname, o);
+ }
+ DBUG_ASSERT(v); /* check that an object was actually constructed */
+ } /* end for */
+
+ if (tmp->nbackups)
+ {
+ size_t bytes= tmp->nbackups * sizeof(tmp->ptr_backup[0]);
+ tmp->ptr_backup= (st_ptr_backup *)alloc_root(mem_root, bytes);
+ if (!tmp->ptr_backup)
+ {
+ restore_ptr_backup(tmp->nbackups, tmp_backup);
+ goto err;
+ }
+ memcpy(tmp->ptr_backup, tmp_backup, bytes);
+ }
+
+ if (chain.first)
+ {
+ chain.last->next = NULL;
+ if (mysql_add_sys_var_chain(chain.first))
+ {
+ sql_print_error("Plugin '%s' has conflicting system variables",
+ tmp->name.str);
+ goto err;
+ }
+ tmp->system_vars= chain.first;
+ }
+ my_afree(tmp_backup);
+ }
+
+ DBUG_RETURN(0);
+
+err:
+ if (tmp_backup)
+ my_afree(tmp_backup);
+ if (opts)
+ my_cleanup_options(opts);
+ DBUG_RETURN(error);
+}
+
+
+/****************************************************************************
+ Help Verbose text with Plugin System Variables
+****************************************************************************/
+
+
+void add_plugin_options(DYNAMIC_ARRAY *options, MEM_ROOT *mem_root)
+{
+ struct st_plugin_int *p;
+ my_option *opt;
+
+ if (!initialized)
+ return;
+
+ for (uint idx= 0; idx < plugin_array.elements; idx++)
+ {
+ p= *dynamic_element(&plugin_array, idx, struct st_plugin_int **);
+
+ if (!(opt= construct_help_options(mem_root, p)))
+ continue;
+
+ /* Only options with a non-NULL comment are displayed in help text */
+ for (;opt->name; opt++)
+ if (opt->comment)
+ insert_dynamic(options, (uchar*) opt);
+ }
+}
+
+
+/**
+ Returns a sys_var corresponding to a particular MYSQL_SYSVAR(...)
+*/
+sys_var *find_plugin_sysvar(st_plugin_int *plugin, st_mysql_sys_var *plugin_var)
+{
+ for (sys_var *var= plugin->system_vars; var; var= var->next)
+ {
+ sys_var_pluginvar *pvar=var->cast_pluginvar();
+ if (pvar->plugin_var == plugin_var)
+ return var;
+ }
+ return 0;
+}
+
+/*
+ On dlclose() we need to restore values of all symbols that we've modified in
+ the DSO. The reason is - the DSO might not actually be unloaded, so on the
+ next dlopen() these symbols will have old values, they won't be
+ reinitialized.
+
+ Perhaps, there can be many reason, why a DSO won't be unloaded. Strictly
+ speaking, it's implementation defined whether to unload an unused DSO or to
+ keep it in memory.
+
+ In particular, this happens for some plugins: In 2009 a new ELF stub was
+ introduced, see Ulrich Drepper's email "Unique symbols for C++"
+ http://www.redhat.com/archives/posix-c++-wg/2009-August/msg00002.html
+
+ DSO that has objects with this stub (STB_GNU_UNIQUE) cannot be unloaded
+ (this is mentioned in the email, see the url above).
+
+ These "unique" objects are, for example, static variables in templates,
+ in inline functions, in classes. So any DSO that uses them can
+ only be loaded once. And because Boost has them, any DSO that uses Boost
+ almost certainly cannot be unloaded.
+
+ To know whether a particular DSO has these objects, one can use
+
+ readelf -s /path/to/plugin.so|grep UNIQUE
+
+ There's nothing we can do about it, but to reset the DSO to its initial
+ state before dlclose().
+*/
+static void restore_ptr_backup(uint n, st_ptr_backup *backup)
+{
+ while (n--)
+ (backup++)->restore();
+}
+
diff --git a/sql/sql_plugin.h b/sql/sql_plugin.h
new file mode 100644
index 00000000000..a0225f4a071
--- /dev/null
+++ b/sql/sql_plugin.h
@@ -0,0 +1,193 @@
+/* Copyright (c) 2005, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2012, Monty Program Ab
+
+ 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 */
+
+#ifndef _sql_plugin_h
+#define _sql_plugin_h
+
+
+/*
+ the following #define adds server-only members to enum_mysql_show_type,
+ that is defined in plugin.h
+*/
+#define SHOW_always_last SHOW_KEY_CACHE_LONG, \
+ SHOW_LONG_STATUS, SHOW_DOUBLE_STATUS, \
+ SHOW_HAVE, SHOW_MY_BOOL, SHOW_HA_ROWS, SHOW_SYS, \
+ SHOW_LONG_NOFLUSH, SHOW_LONGLONG_STATUS, SHOW_LEX_STRING
+#include <my_global.h>
+#undef SHOW_always_last
+
+#include "m_string.h" /* LEX_STRING */
+#include "my_alloc.h" /* MEM_ROOT */
+
+class sys_var;
+enum SHOW_COMP_OPTION { SHOW_OPTION_YES, SHOW_OPTION_NO, SHOW_OPTION_DISABLED};
+enum enum_plugin_load_option { PLUGIN_OFF, PLUGIN_ON, PLUGIN_FORCE,
+ PLUGIN_FORCE_PLUS_PERMANENT };
+extern const char *global_plugin_typelib_names[];
+
+extern ulong dlopen_count;
+
+#include <my_sys.h>
+#include "sql_list.h"
+
+#ifdef DBUG_OFF
+#define plugin_ref_to_int(A) A
+#define plugin_int_to_ref(A) A
+#else
+#define plugin_ref_to_int(A) (A ? A[0] : NULL)
+#define plugin_int_to_ref(A) &(A)
+#endif
+
+/*
+ the following flags are valid for plugin_init()
+*/
+#define PLUGIN_INIT_SKIP_DYNAMIC_LOADING 1
+#define PLUGIN_INIT_SKIP_PLUGIN_TABLE 2
+#define PLUGIN_INIT_SKIP_INITIALIZATION 4
+
+#define INITIAL_LEX_PLUGIN_LIST_SIZE 16
+
+typedef enum enum_mysql_show_type SHOW_TYPE;
+typedef struct st_mysql_show_var SHOW_VAR;
+
+#define MYSQL_ANY_PLUGIN -1
+
+/*
+ different values of st_plugin_int::state
+ though they look like a bitmap, plugin may only
+ be in one of those eigenstates, not in a superposition of them :)
+ It's a bitmap, because it makes it easier to test
+ "whether the state is one of those..."
+*/
+#define PLUGIN_IS_FREED 1
+#define PLUGIN_IS_DELETED 2
+#define PLUGIN_IS_UNINITIALIZED 4
+#define PLUGIN_IS_READY 8
+#define PLUGIN_IS_DYING 16
+#define PLUGIN_IS_DISABLED 32
+
+/* A handle for the dynamic library containing a plugin or plugins. */
+
+struct st_ptr_backup {
+ void **ptr;
+ void *value;
+ void save(void **p) { ptr= p; value= *p; }
+ void save(const char **p) { save((void**)p); }
+ void restore() { *ptr= value; }
+};
+
+struct st_plugin_dl
+{
+ LEX_STRING dl;
+ void *handle;
+ struct st_maria_plugin *plugins;
+ st_ptr_backup *ptr_backup;
+ uint nbackups;
+ uint ref_count; /* number of plugins loaded from the library */
+ int mysqlversion;
+ int mariaversion;
+ bool allocated;
+};
+
+/* A handle of a plugin */
+
+struct st_plugin_int
+{
+ LEX_STRING name;
+ struct st_maria_plugin *plugin;
+ struct st_plugin_dl *plugin_dl;
+ st_ptr_backup *ptr_backup;
+ uint nbackups;
+ uint state;
+ uint ref_count; /* number of threads using the plugin */
+ uint locks_total; /* how many times the plugin was locked */
+ void *data; /* plugin type specific, e.g. handlerton */
+ MEM_ROOT mem_root; /* memory for dynamic plugin structures */
+ sys_var *system_vars; /* server variables for this plugin */
+ enum enum_plugin_load_option load_option; /* OFF, ON, FORCE, F+PERMANENT */
+};
+
+
+/*
+ See intern_plugin_lock() for the explanation for the
+ conditionally defined plugin_ref type
+*/
+#ifdef DBUG_OFF
+typedef struct st_plugin_int *plugin_ref;
+#define plugin_ref_to_int(A) A
+#define plugin_int_to_ref(A) A
+#define plugin_decl(pi) ((pi)->plugin)
+#define plugin_dlib(pi) ((pi)->plugin_dl)
+#define plugin_data(pi,cast) ((cast)((pi)->data))
+#define plugin_name(pi) (&((pi)->name))
+#define plugin_state(pi) ((pi)->state)
+#define plugin_load_option(pi) ((pi)->load_option)
+#define plugin_equals(p1,p2) ((p1) == (p2))
+#else
+typedef struct st_plugin_int **plugin_ref;
+#define plugin_ref_to_int(A) (A ? A[0] : NULL)
+#define plugin_int_to_ref(A) &(A)
+#define plugin_decl(pi) ((pi)[0]->plugin)
+#define plugin_dlib(pi) ((pi)[0]->plugin_dl)
+#define plugin_data(pi,cast) ((cast)((pi)[0]->data))
+#define plugin_name(pi) (&((pi)[0]->name))
+#define plugin_state(pi) ((pi)[0]->state)
+#define plugin_load_option(pi) ((pi)[0]->load_option)
+#define plugin_equals(p1,p2) ((p1) && (p2) && (p1)[0] == (p2)[0])
+#endif
+
+typedef int (*plugin_type_init)(struct st_plugin_int *);
+
+extern I_List<i_string> *opt_plugin_load_list_ptr;
+extern char *opt_plugin_dir_ptr;
+extern char opt_plugin_dir[FN_REFLEN];
+extern const LEX_STRING plugin_type_names[];
+extern ulong plugin_maturity;
+extern TYPELIB plugin_maturity_values;
+extern const char *plugin_maturity_names[];
+
+extern int plugin_init(int *argc, char **argv, int init_flags);
+extern void plugin_shutdown(void);
+void add_plugin_options(DYNAMIC_ARRAY *options, MEM_ROOT *mem_root);
+extern bool plugin_is_ready(const LEX_STRING *name, int type);
+#define my_plugin_lock_by_name(A,B,C) plugin_lock_by_name(A,B,C)
+#define my_plugin_lock(A,B) plugin_lock(A,B)
+extern plugin_ref plugin_lock(THD *thd, plugin_ref ptr);
+extern plugin_ref plugin_lock_by_name(THD *thd, const LEX_STRING *name,
+ int type);
+extern void plugin_unlock(THD *thd, plugin_ref plugin);
+extern void plugin_unlock_list(THD *thd, plugin_ref *list, uint count);
+extern bool mysql_install_plugin(THD *thd, const LEX_STRING *name,
+ const LEX_STRING *dl);
+extern bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name,
+ const LEX_STRING *dl);
+extern bool plugin_register_builtin(struct st_mysql_plugin *plugin);
+extern void plugin_thdvar_init(THD *thd);
+extern void plugin_thdvar_cleanup(THD *thd);
+sys_var *find_plugin_sysvar(st_plugin_int *plugin, st_mysql_sys_var *var);
+void plugin_opt_set_limits(struct my_option *, const struct st_mysql_sys_var *);
+extern SHOW_COMP_OPTION plugin_status(const char *name, size_t len, int type);
+extern bool check_valid_path(const char *path, size_t length);
+
+typedef my_bool (plugin_foreach_func)(THD *thd,
+ plugin_ref plugin,
+ void *arg);
+#define plugin_foreach(A,B,C,D) plugin_foreach_with_mask(A,B,C,PLUGIN_IS_READY,D)
+extern bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func,
+ int type, uint state_mask, void *arg);
+extern bool plugin_dl_foreach(THD *thd, const LEX_STRING *dl,
+ plugin_foreach_func *func, void *arg);
+#endif
diff --git a/sql/sql_plugin_compat.h b/sql/sql_plugin_compat.h
new file mode 100644
index 00000000000..5c7bb620575
--- /dev/null
+++ b/sql/sql_plugin_compat.h
@@ -0,0 +1,65 @@
+/* Copyright (C) 2013 Sergei Golubchik and Monty Program Ab
+
+ 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 */
+
+/* old plugin api structures, used for backward compatibility */
+
+#define upgrade_var(X) latest->X= X
+#define upgrade_str(X) strmake_buf(latest->X, X)
+#define downgrade_var(X) X= latest->X
+#define downgrade_str(X) strmake_buf(X, latest->X)
+
+/**************************************************************/
+/* Authentication API, version 0x0100 *************************/
+#define MIN_AUTHENTICATION_INTERFACE_VERSION 0x0100
+
+struct MYSQL_SERVER_AUTH_INFO_0x0100 {
+ char *user_name;
+ unsigned int user_name_length;
+ const char *auth_string;
+ unsigned long auth_string_length;
+ char authenticated_as[49];
+ char external_user[512];
+ int password_used;
+ const char *host_or_ip;
+ unsigned int host_or_ip_length;
+
+ void upgrade(MYSQL_SERVER_AUTH_INFO *latest)
+ {
+ upgrade_var(user_name);
+ upgrade_var(user_name_length);
+ upgrade_var(auth_string);
+ upgrade_var(auth_string_length);
+ upgrade_str(authenticated_as);
+ upgrade_str(external_user);
+ upgrade_var(password_used);
+ upgrade_var(host_or_ip);
+ upgrade_var(host_or_ip_length);
+ }
+ void downgrade(MYSQL_SERVER_AUTH_INFO *latest)
+ {
+ downgrade_var(user_name);
+ downgrade_var(user_name_length);
+ downgrade_var(auth_string);
+ downgrade_var(auth_string_length);
+ downgrade_str(authenticated_as);
+ downgrade_str(external_user);
+ downgrade_var(password_used);
+ downgrade_var(host_or_ip);
+ downgrade_var(host_or_ip_length);
+ }
+};
+
+/**************************************************************/
+
diff --git a/sql/sql_plugin_services.h b/sql/sql_plugin_services.h
new file mode 100644
index 00000000000..38b4c4074be
--- /dev/null
+++ b/sql/sql_plugin_services.h
@@ -0,0 +1,103 @@
+/* Copyright (c) 2009, 2010, Oracle and/or its affiliates.
+ Copyright (c) 2012, 2013, Monty Program Ab
+
+ 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 */
+
+/* support for Services */
+#include <service_versions.h>
+
+struct st_service_ref {
+ const char *name;
+ uint version;
+ void *service;
+};
+
+static struct my_snprintf_service_st my_snprintf_handler = {
+ my_snprintf,
+ my_vsnprintf
+};
+
+static struct thd_alloc_service_st thd_alloc_handler= {
+ thd_alloc,
+ thd_calloc,
+ thd_strdup,
+ thd_strmake,
+ thd_memdup,
+ thd_make_lex_string
+};
+
+static struct thd_wait_service_st thd_wait_handler= {
+ thd_wait_begin,
+ thd_wait_end
+};
+
+static struct progress_report_service_st progress_report_handler= {
+ thd_progress_init,
+ thd_progress_report,
+ thd_progress_next_stage,
+ thd_progress_end,
+ set_thd_proc_info
+};
+
+static struct kill_statement_service_st thd_kill_statement_handler= {
+ thd_kill_level
+};
+
+static struct thd_timezone_service_st thd_timezone_handler= {
+ thd_TIME_to_gmt_sec,
+ thd_gmt_sec_to_TIME
+};
+
+static struct my_sha1_service_st my_sha1_handler = {
+ my_sha1,
+ my_sha1_multi
+};
+
+static struct logger_service_st logger_service_handler= {
+ logger_init_mutexes,
+ logger_open,
+ logger_close,
+ logger_vprintf,
+ logger_printf,
+ logger_write,
+ logger_rotate
+};
+
+static struct thd_autoinc_service_st thd_autoinc_handler= {
+ thd_get_autoinc
+};
+
+static struct thd_error_context_service_st thd_error_conext_handler= {
+ thd_get_error_message,
+ thd_get_error_number,
+ thd_get_error_row,
+ thd_inc_error_row,
+ thd_get_error_context_description
+};
+
+static struct st_service_ref list_of_services[]=
+{
+ { "my_snprintf_service", VERSION_my_snprintf, &my_snprintf_handler },
+ { "thd_alloc_service", VERSION_thd_alloc, &thd_alloc_handler },
+ { "thd_wait_service", VERSION_thd_wait, &thd_wait_handler },
+ { "progress_report_service", VERSION_progress_report, &progress_report_handler },
+ { "debug_sync_service", VERSION_debug_sync, 0 }, // updated in plugin_init()
+ { "thd_kill_statement_service", VERSION_kill_statement, &thd_kill_statement_handler },
+ { "thd_timezone_service", VERSION_thd_timezone, &thd_timezone_handler },
+ { "my_sha1_service", VERSION_my_sha1, &my_sha1_handler},
+ { "logger_service", VERSION_logger, &logger_service_handler },
+ { "thd_autoinc_service", VERSION_thd_autoinc, &thd_autoinc_handler },
+ { "thd_error_context_service", VERSION_thd_error_context, &thd_error_conext_handler },
+};
+
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
new file mode 100644
index 00000000000..48d7415a7b3
--- /dev/null
+++ b/sql/sql_prepare.cc
@@ -0,0 +1,4632 @@
+/* Copyright (c) 2002, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2013, Monty Program Ab
+
+ 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 */
+
+/**
+ @file
+
+This file contains the implementation of prepared statements.
+
+When one prepares a statement:
+
+ - Server gets the query from client with command 'COM_STMT_PREPARE';
+ in the following format:
+ [COM_STMT_PREPARE:1] [query]
+ - Parse the query and recognize any parameter markers '?' and
+ store its information list in lex->param_list
+ - Allocate a new statement for this prepare; and keep this in
+ 'thd->stmt_map'.
+ - Without executing the query, return back to client the total
+ number of parameters along with result-set metadata information
+ (if any) in the following format:
+ @verbatim
+ [STMT_ID:4]
+ [Column_count:2]
+ [Param_count:2]
+ [Params meta info (stubs only for now)] (if Param_count > 0)
+ [Columns meta info] (if Column_count > 0)
+ @endverbatim
+
+ During prepare the tables used in a statement are opened, but no
+ locks are acquired. Table opening will block any DDL during the
+ operation, and we do not need any locks as we neither read nor
+ modify any data during prepare. Tables are closed after prepare
+ finishes.
+
+When one executes a statement:
+
+ - Server gets the command 'COM_STMT_EXECUTE' to execute the
+ previously prepared query. If there are any parameter markers, then the
+ client will send the data in the following format:
+ @verbatim
+ [COM_STMT_EXECUTE:1]
+ [STMT_ID:4]
+ [NULL_BITS:(param_count+7)/8)]
+ [TYPES_SUPPLIED_BY_CLIENT(0/1):1]
+ [[length]data]
+ [[length]data] .. [[length]data].
+ @endverbatim
+ (Note: Except for string/binary types; all other types will not be
+ supplied with length field)
+ - If it is a first execute or types of parameters were altered by client,
+ then setup the conversion routines.
+ - Assign parameter items from the supplied data.
+ - Execute the query without re-parsing and send back the results
+ to client
+
+ During execution of prepared statement tables are opened and locked
+ the same way they would for normal (non-prepared) statement
+ execution. Tables are unlocked and closed after the execution.
+
+When one supplies long data for a placeholder:
+
+ - Server gets the long data in pieces with command type
+ 'COM_STMT_SEND_LONG_DATA'.
+ - The packet received will have the format as:
+ [COM_STMT_SEND_LONG_DATA:1][STMT_ID:4][parameter_number:2][data]
+ - data from the packet is appended to the long data value buffer for this
+ placeholder.
+ - It's up to the client to stop supplying data chunks at any point. The
+ server doesn't care; also, the server doesn't notify the client whether
+ it got the data or not; if there is any error, then it will be returned
+ at statement execute.
+*/
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_class.h" // set_var.h: THD
+#include "set_var.h"
+#include "sql_prepare.h"
+#include "sql_parse.h" // insert_precheck, update_precheck, delete_precheck
+#include "sql_base.h" // open_normal_and_derived_tables
+#include "sql_cache.h" // query_cache_*
+#include "sql_view.h" // create_view_precheck
+#include "sql_delete.h" // mysql_prepare_delete
+#include "sql_select.h" // for JOIN
+#include "sql_insert.h" // upgrade_lock_type_for_insert, mysql_prepare_insert
+#include "sql_update.h" // mysql_prepare_update
+#include "sql_db.h" // mysql_opt_change_db, mysql_change_db
+#include "sql_acl.h" // *_ACL
+#include "sql_derived.h" // mysql_derived_prepare,
+ // mysql_handle_derived
+#include "sql_cursor.h"
+#include "sp_head.h"
+#include "sp.h"
+#include "sp_cache.h"
+#include "sql_handler.h" // mysql_ha_rm_tables
+#include "probes_mysql.h"
+#ifdef EMBEDDED_LIBRARY
+/* include MYSQL_BIND headers */
+#include <mysql.h>
+#else
+#include <mysql_com.h>
+#endif
+#include "lock.h" // MYSQL_OPEN_FORCE_SHARED_MDL
+#include "sql_handler.h"
+#include "transaction.h" // trans_rollback_implicit
+
+/**
+ A result class used to send cursor rows using the binary protocol.
+*/
+
+class Select_fetch_protocol_binary: public select_send
+{
+ Protocol_binary protocol;
+public:
+ Select_fetch_protocol_binary(THD *thd);
+ virtual bool send_result_set_metadata(List<Item> &list, uint flags);
+ virtual int send_data(List<Item> &items);
+ virtual bool send_eof();
+#ifdef EMBEDDED_LIBRARY
+ void begin_dataset()
+ {
+ protocol.begin_dataset();
+ }
+#endif
+};
+
+/****************************************************************************/
+
+/**
+ Prepared_statement: a statement that can contain placeholders.
+*/
+
+class Prepared_statement: public Statement
+{
+public:
+ enum flag_values
+ {
+ IS_IN_USE= 1,
+ IS_SQL_PREPARE= 2
+ };
+
+ THD *thd;
+ Select_fetch_protocol_binary result;
+ Item_param **param_array;
+ Server_side_cursor *cursor;
+ uint param_count;
+ uint last_errno;
+ uint flags;
+ char last_error[MYSQL_ERRMSG_SIZE];
+#ifndef EMBEDDED_LIBRARY
+ bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end,
+ uchar *read_pos, String *expanded_query);
+#else
+ bool (*set_params_data)(Prepared_statement *st, String *expanded_query);
+#endif
+ bool (*set_params_from_vars)(Prepared_statement *stmt,
+ List<LEX_STRING>& varnames,
+ String *expanded_query);
+public:
+ Prepared_statement(THD *thd_arg);
+ virtual ~Prepared_statement();
+ void setup_set_params();
+ virtual Query_arena::Type type() const;
+ virtual void cleanup_stmt();
+ bool set_name(LEX_STRING *name);
+ inline void close_cursor() { delete cursor; cursor= 0; }
+ inline bool is_in_use() { return flags & (uint) IS_IN_USE; }
+ inline bool is_sql_prepare() const { return flags & (uint) IS_SQL_PREPARE; }
+ void set_sql_prepare() { flags|= (uint) IS_SQL_PREPARE; }
+ bool prepare(const char *packet, uint packet_length);
+ bool execute_loop(String *expanded_query,
+ bool open_cursor,
+ uchar *packet_arg, uchar *packet_end_arg);
+ bool execute_server_runnable(Server_runnable *server_runnable);
+ /* Destroy this statement */
+ void deallocate();
+private:
+ /**
+ The memory root to allocate parsed tree elements (instances of Item,
+ SELECT_LEX and other classes).
+ */
+ MEM_ROOT main_mem_root;
+private:
+ bool set_db(const char *db, uint db_length);
+ bool set_parameters(String *expanded_query,
+ uchar *packet, uchar *packet_end);
+ bool execute(String *expanded_query, bool open_cursor);
+ bool reprepare();
+ bool validate_metadata(Prepared_statement *copy);
+ void swap_prepared_statement(Prepared_statement *copy);
+};
+
+/**
+ Execute one SQL statement in an isolated context.
+*/
+
+class Execute_sql_statement: public Server_runnable
+{
+public:
+ Execute_sql_statement(LEX_STRING sql_text);
+ virtual bool execute_server_code(THD *thd);
+private:
+ LEX_STRING m_sql_text;
+};
+
+
+class Ed_connection;
+
+/**
+ Protocol_local: a helper class to intercept the result
+ of the data written to the network.
+*/
+
+class Protocol_local :public Protocol
+{
+public:
+ Protocol_local(THD *thd, Ed_connection *ed_connection);
+ ~Protocol_local() { free_root(&m_rset_root, MYF(0)); }
+protected:
+ virtual void prepare_for_resend();
+ virtual bool write();
+ virtual bool store_null();
+ virtual bool store_tiny(longlong from);
+ virtual bool store_short(longlong from);
+ virtual bool store_long(longlong from);
+ virtual bool store_longlong(longlong from, bool unsigned_flag);
+ virtual bool store_decimal(const my_decimal *);
+ virtual bool store(const char *from, size_t length, CHARSET_INFO *cs);
+ virtual bool store(const char *from, size_t length,
+ CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
+ virtual bool store(MYSQL_TIME *time, int decimals);
+ virtual bool store_date(MYSQL_TIME *time);
+ virtual bool store_time(MYSQL_TIME *time, int decimals);
+ virtual bool store(float value, uint32 decimals, String *buffer);
+ virtual bool store(double value, uint32 decimals, String *buffer);
+ virtual bool store(Field *field);
+
+ virtual bool send_result_set_metadata(List<Item> *list, uint flags);
+ virtual bool send_out_parameters(List<Item_param> *sp_params);
+#ifdef EMBEDDED_LIBRARY
+ void remove_last_row();
+#endif
+ virtual enum enum_protocol_type type() { return PROTOCOL_LOCAL; };
+
+ virtual bool send_ok(uint server_status, uint statement_warn_count,
+ ulonglong affected_rows, ulonglong last_insert_id,
+ const char *message);
+
+ virtual bool send_eof(uint server_status, uint statement_warn_count);
+ virtual bool send_error(uint sql_errno, const char *err_msg, const char* sqlstate);
+private:
+ bool store_string(const char *str, size_t length,
+ CHARSET_INFO *src_cs, CHARSET_INFO *dst_cs);
+
+ bool store_column(const void *data, size_t length);
+ void opt_add_row_to_rset();
+private:
+ Ed_connection *m_connection;
+ MEM_ROOT m_rset_root;
+ List<Ed_row> *m_rset;
+ size_t m_column_count;
+ Ed_column *m_current_row;
+ Ed_column *m_current_column;
+};
+
+/******************************************************************************
+ Implementation
+******************************************************************************/
+
+
+inline bool is_param_null(const uchar *pos, ulong param_no)
+{
+ return pos[param_no/8] & (1 << (param_no & 7));
+}
+
+/**
+ Find a prepared statement in the statement map by id.
+
+ Try to find a prepared statement and set THD error if it's not found.
+
+ @param thd thread handle
+ @param id statement id
+ @param where the place from which this function is called (for
+ error reporting).
+
+ @return
+ 0 if the statement was not found, a pointer otherwise.
+*/
+
+static Prepared_statement *
+find_prepared_statement(THD *thd, ulong id)
+{
+ /*
+ To strictly separate namespaces of SQL prepared statements and C API
+ prepared statements find() will return 0 if there is a named prepared
+ statement with such id.
+ */
+ Statement *stmt= thd->stmt_map.find(id);
+
+ if (stmt == 0 || stmt->type() != Query_arena::PREPARED_STATEMENT)
+ return NULL;
+
+ return (Prepared_statement *) stmt;
+}
+
+
+/**
+ Send prepared statement id and metadata to the client after prepare.
+
+ @todo
+ Fix this nasty upcast from List<Item_param> to List<Item>
+
+ @return
+ 0 in case of success, 1 otherwise
+*/
+
+#ifndef EMBEDDED_LIBRARY
+static bool send_prep_stmt(Prepared_statement *stmt, uint columns)
+{
+ NET *net= &stmt->thd->net;
+ uchar buff[12];
+ uint tmp;
+ int error;
+ THD *thd= stmt->thd;
+ DBUG_ENTER("send_prep_stmt");
+ DBUG_PRINT("enter",("stmt->id: %lu columns: %d param_count: %d",
+ stmt->id, columns, stmt->param_count));
+
+ buff[0]= 0; /* OK packet indicator */
+ int4store(buff+1, stmt->id);
+ int2store(buff+5, columns);
+ int2store(buff+7, stmt->param_count);
+ buff[9]= 0; // Guard against a 4.1 client
+ tmp= MY_MIN(stmt->thd->get_stmt_da()->current_statement_warn_count(), 65535);
+ int2store(buff+10, tmp);
+
+ /*
+ Send types and names of placeholders to the client
+ XXX: fix this nasty upcast from List<Item_param> to List<Item>
+ */
+ error= my_net_write(net, buff, sizeof(buff));
+ if (stmt->param_count && ! error)
+ {
+ error= thd->protocol_text.send_result_set_metadata((List<Item> *)
+ &stmt->lex->param_list,
+ Protocol::SEND_EOF);
+ }
+
+ if (!error)
+ /* Flag that a response has already been sent */
+ thd->get_stmt_da()->disable_status();
+
+ DBUG_RETURN(error);
+}
+#else
+static bool send_prep_stmt(Prepared_statement *stmt,
+ uint columns __attribute__((unused)))
+{
+ THD *thd= stmt->thd;
+
+ thd->client_stmt_id= stmt->id;
+ thd->client_param_count= stmt->param_count;
+ thd->clear_error();
+ thd->get_stmt_da()->disable_status();
+
+ return 0;
+}
+#endif /*!EMBEDDED_LIBRARY*/
+
+
+#ifndef EMBEDDED_LIBRARY
+
+/**
+ Read the length of the parameter data and return it back to
+ the caller.
+
+ Read data length, position the packet to the first byte after it,
+ and return the length to the caller.
+
+ @param packet a pointer to the data
+ @param len remaining packet length
+
+ @return
+ Length of data piece.
+*/
+
+static ulong get_param_length(uchar **packet, ulong len)
+{
+ reg1 uchar *pos= *packet;
+ if (len < 1)
+ return 0;
+ if (*pos < 251)
+ {
+ (*packet)++;
+ return (ulong) *pos;
+ }
+ if (len < 3)
+ return 0;
+ if (*pos == 252)
+ {
+ (*packet)+=3;
+ return (ulong) uint2korr(pos+1);
+ }
+ if (len < 4)
+ return 0;
+ if (*pos == 253)
+ {
+ (*packet)+=4;
+ return (ulong) uint3korr(pos+1);
+ }
+ if (len < 5)
+ return 0;
+ (*packet)+=9; // Must be 254 when here
+ /*
+ In our client-server protocol all numbers bigger than 2^24
+ stored as 8 bytes with uint8korr. Here we always know that
+ parameter length is less than 2^4 so don't look at the second
+ 4 bytes. But still we need to obey the protocol hence 9 in the
+ assignment above.
+ */
+ return (ulong) uint4korr(pos+1);
+}
+#else
+#define get_param_length(packet, len) len
+#endif /*!EMBEDDED_LIBRARY*/
+
+/**
+ Data conversion routines.
+
+ All these functions read the data from pos, convert it to requested
+ type and assign to param; pos is advanced to predefined length.
+
+ Make a note that the NULL handling is examined at first execution
+ (i.e. when input types altered) and for all subsequent executions
+ we don't read any values for this.
+
+ @param param parameter item
+ @param pos input data buffer
+ @param len length of data in the buffer
+*/
+
+static void set_param_tiny(Item_param *param, uchar **pos, ulong len)
+{
+#ifndef EMBEDDED_LIBRARY
+ if (len < 1)
+ return;
+#endif
+ int8 value= (int8) **pos;
+ param->set_int(param->unsigned_flag ? (longlong) ((uint8) value) :
+ (longlong) value, 4);
+ *pos+= 1;
+}
+
+static void set_param_short(Item_param *param, uchar **pos, ulong len)
+{
+ int16 value;
+#ifndef EMBEDDED_LIBRARY
+ if (len < 2)
+ return;
+ value= sint2korr(*pos);
+#else
+ shortget(value, *pos);
+#endif
+ param->set_int(param->unsigned_flag ? (longlong) ((uint16) value) :
+ (longlong) value, 6);
+ *pos+= 2;
+}
+
+static void set_param_int32(Item_param *param, uchar **pos, ulong len)
+{
+ int32 value;
+#ifndef EMBEDDED_LIBRARY
+ if (len < 4)
+ return;
+ value= sint4korr(*pos);
+#else
+ longget(value, *pos);
+#endif
+ param->set_int(param->unsigned_flag ? (longlong) ((uint32) value) :
+ (longlong) value, 11);
+ *pos+= 4;
+}
+
+static void set_param_int64(Item_param *param, uchar **pos, ulong len)
+{
+ longlong value;
+#ifndef EMBEDDED_LIBRARY
+ if (len < 8)
+ return;
+ value= (longlong) sint8korr(*pos);
+#else
+ longlongget(value, *pos);
+#endif
+ param->set_int(value, 21);
+ *pos+= 8;
+}
+
+static void set_param_float(Item_param *param, uchar **pos, ulong len)
+{
+ float data;
+#ifndef EMBEDDED_LIBRARY
+ if (len < 4)
+ return;
+ float4get(data,*pos);
+#else
+ floatget(data, *pos);
+#endif
+ param->set_double((double) data);
+ *pos+= 4;
+}
+
+static void set_param_double(Item_param *param, uchar **pos, ulong len)
+{
+ double data;
+#ifndef EMBEDDED_LIBRARY
+ if (len < 8)
+ return;
+ float8get(data,*pos);
+#else
+ doubleget(data, *pos);
+#endif
+ param->set_double((double) data);
+ *pos+= 8;
+}
+
+static void set_param_decimal(Item_param *param, uchar **pos, ulong len)
+{
+ ulong length= get_param_length(pos, len);
+ param->set_decimal((char*)*pos, length);
+ *pos+= length;
+}
+
+#ifndef EMBEDDED_LIBRARY
+
+/*
+ Read date/time/datetime parameter values from network (binary
+ protocol). See writing counterparts of these functions in
+ libmysql.c (store_param_{time,date,datetime}).
+*/
+
+/**
+ @todo
+ Add warning 'Data truncated' here
+*/
+static void set_param_time(Item_param *param, uchar **pos, ulong len)
+{
+ MYSQL_TIME tm;
+ ulong length= get_param_length(pos, len);
+
+ if (length >= 8)
+ {
+ uchar *to= *pos;
+ uint day;
+
+ tm.neg= (bool) to[0];
+ day= (uint) sint4korr(to+1);
+ tm.hour= (uint) to[5] + day * 24;
+ tm.minute= (uint) to[6];
+ tm.second= (uint) to[7];
+ tm.second_part= (length > 8) ? (ulong) sint4korr(to+8) : 0;
+ if (tm.hour > 838)
+ {
+ /* TODO: add warning 'Data truncated' here */
+ tm.hour= 838;
+ tm.minute= 59;
+ tm.second= 59;
+ }
+ tm.day= tm.year= tm.month= 0;
+ }
+ else
+ set_zero_time(&tm, MYSQL_TIMESTAMP_TIME);
+ param->set_time(&tm, MYSQL_TIMESTAMP_TIME, MAX_TIME_FULL_WIDTH);
+ *pos+= length;
+}
+
+static void set_param_datetime(Item_param *param, uchar **pos, ulong len)
+{
+ MYSQL_TIME tm;
+ ulong length= get_param_length(pos, len);
+
+ if (length >= 4)
+ {
+ uchar *to= *pos;
+
+ tm.neg= 0;
+ tm.year= (uint) sint2korr(to);
+ tm.month= (uint) to[2];
+ tm.day= (uint) to[3];
+ if (length > 4)
+ {
+ tm.hour= (uint) to[4];
+ tm.minute= (uint) to[5];
+ tm.second= (uint) to[6];
+ }
+ else
+ tm.hour= tm.minute= tm.second= 0;
+
+ tm.second_part= (length > 7) ? (ulong) sint4korr(to+7) : 0;
+ }
+ else
+ set_zero_time(&tm, MYSQL_TIMESTAMP_DATETIME);
+ param->set_time(&tm, MYSQL_TIMESTAMP_DATETIME,
+ MAX_DATETIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
+ *pos+= length;
+}
+
+
+static void set_param_date(Item_param *param, uchar **pos, ulong len)
+{
+ MYSQL_TIME tm;
+ ulong length= get_param_length(pos, len);
+
+ if (length >= 4)
+ {
+ uchar *to= *pos;
+
+ tm.year= (uint) sint2korr(to);
+ tm.month= (uint) to[2];
+ tm.day= (uint) to[3];
+
+ tm.hour= tm.minute= tm.second= 0;
+ tm.second_part= 0;
+ tm.neg= 0;
+ }
+ else
+ set_zero_time(&tm, MYSQL_TIMESTAMP_DATE);
+ param->set_time(&tm, MYSQL_TIMESTAMP_DATE,
+ MAX_DATE_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
+ *pos+= length;
+}
+
+#else/*!EMBEDDED_LIBRARY*/
+/**
+ @todo
+ Add warning 'Data truncated' here
+*/
+void set_param_time(Item_param *param, uchar **pos, ulong len)
+{
+ MYSQL_TIME tm= *((MYSQL_TIME*)*pos);
+ tm.hour+= tm.day * 24;
+ tm.day= tm.year= tm.month= 0;
+ if (tm.hour > 838)
+ {
+ /* TODO: add warning 'Data truncated' here */
+ tm.hour= 838;
+ tm.minute= 59;
+ tm.second= 59;
+ }
+ param->set_time(&tm, MYSQL_TIMESTAMP_TIME,
+ MAX_TIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
+
+}
+
+void set_param_datetime(Item_param *param, uchar **pos, ulong len)
+{
+ MYSQL_TIME tm= *((MYSQL_TIME*)*pos);
+ tm.neg= 0;
+
+ param->set_time(&tm, MYSQL_TIMESTAMP_DATETIME,
+ MAX_DATETIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
+}
+
+void set_param_date(Item_param *param, uchar **pos, ulong len)
+{
+ MYSQL_TIME *to= (MYSQL_TIME*)*pos;
+
+ param->set_time(to, MYSQL_TIMESTAMP_DATE,
+ MAX_DATE_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
+}
+#endif /*!EMBEDDED_LIBRARY*/
+
+
+static void set_param_str(Item_param *param, uchar **pos, ulong len)
+{
+ ulong length= get_param_length(pos, len);
+ if (length > len)
+ length= len;
+ param->set_str((const char *)*pos, length);
+ *pos+= length;
+}
+
+
+#undef get_param_length
+
+static void setup_one_conversion_function(THD *thd, Item_param *param,
+ uchar param_type)
+{
+ switch (param_type) {
+ case MYSQL_TYPE_TINY:
+ param->set_param_func= set_param_tiny;
+ param->item_type= Item::INT_ITEM;
+ param->item_result_type= INT_RESULT;
+ break;
+ case MYSQL_TYPE_SHORT:
+ param->set_param_func= set_param_short;
+ param->item_type= Item::INT_ITEM;
+ param->item_result_type= INT_RESULT;
+ break;
+ case MYSQL_TYPE_LONG:
+ param->set_param_func= set_param_int32;
+ param->item_type= Item::INT_ITEM;
+ param->item_result_type= INT_RESULT;
+ break;
+ case MYSQL_TYPE_LONGLONG:
+ param->set_param_func= set_param_int64;
+ param->item_type= Item::INT_ITEM;
+ param->item_result_type= INT_RESULT;
+ break;
+ case MYSQL_TYPE_FLOAT:
+ param->set_param_func= set_param_float;
+ param->item_type= Item::REAL_ITEM;
+ param->item_result_type= REAL_RESULT;
+ break;
+ case MYSQL_TYPE_DOUBLE:
+ param->set_param_func= set_param_double;
+ param->item_type= Item::REAL_ITEM;
+ param->item_result_type= REAL_RESULT;
+ break;
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ param->set_param_func= set_param_decimal;
+ param->item_type= Item::DECIMAL_ITEM;
+ param->item_result_type= DECIMAL_RESULT;
+ break;
+ case MYSQL_TYPE_TIME:
+ param->set_param_func= set_param_time;
+ param->item_type= Item::STRING_ITEM;
+ param->item_result_type= STRING_RESULT;
+ break;
+ case MYSQL_TYPE_DATE:
+ param->set_param_func= set_param_date;
+ param->item_type= Item::STRING_ITEM;
+ param->item_result_type= STRING_RESULT;
+ break;
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIMESTAMP:
+ param->set_param_func= set_param_datetime;
+ param->item_type= Item::STRING_ITEM;
+ param->item_result_type= STRING_RESULT;
+ break;
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ param->set_param_func= set_param_str;
+ param->value.cs_info.character_set_of_placeholder= &my_charset_bin;
+ param->value.cs_info.character_set_client=
+ thd->variables.character_set_client;
+ DBUG_ASSERT(thd->variables.character_set_client);
+ param->value.cs_info.final_character_set_of_str_value= &my_charset_bin;
+ param->item_type= Item::STRING_ITEM;
+ param->item_result_type= STRING_RESULT;
+ break;
+ default:
+ /*
+ The client library ensures that we won't get any other typecodes
+ except typecodes above and typecodes for string types. Marking
+ label as 'default' lets us to handle malformed packets as well.
+ */
+ {
+ CHARSET_INFO *fromcs= thd->variables.character_set_client;
+ CHARSET_INFO *tocs= thd->variables.collation_connection;
+ uint32 dummy_offset;
+
+ param->value.cs_info.character_set_of_placeholder= fromcs;
+ param->value.cs_info.character_set_client= fromcs;
+
+ /*
+ Setup source and destination character sets so that they
+ are different only if conversion is necessary: this will
+ make later checks easier.
+ */
+ param->value.cs_info.final_character_set_of_str_value=
+ String::needs_conversion(0, fromcs, tocs, &dummy_offset) ?
+ tocs : fromcs;
+ param->set_param_func= set_param_str;
+ /*
+ Exact value of max_length is not known unless data is converted to
+ charset of connection, so we have to set it later.
+ */
+ param->item_type= Item::STRING_ITEM;
+ param->item_result_type= STRING_RESULT;
+ }
+ }
+ param->param_type= (enum enum_field_types) param_type;
+}
+
+#ifndef EMBEDDED_LIBRARY
+
+/**
+ Check whether this parameter data type is compatible with long data.
+ Used to detect whether a long data stream has been supplied to a
+ incompatible data type.
+*/
+inline bool is_param_long_data_type(Item_param *param)
+{
+ return ((param->param_type >= MYSQL_TYPE_TINY_BLOB) &&
+ (param->param_type <= MYSQL_TYPE_STRING));
+}
+
+
+/**
+ Routines to assign parameters from data supplied by the client.
+
+ Update the parameter markers by reading data from the packet and
+ and generate a valid query for logging.
+
+ @note
+ This function, along with other _with_log functions is called when one of
+ binary, slow or general logs is open. Logging of prepared statements in
+ all cases is performed by means of conventional queries: if parameter
+ data was supplied from C API, each placeholder in the query is
+ replaced with its actual value; if we're logging a [Dynamic] SQL
+ prepared statement, parameter markers are replaced with variable names.
+ Example:
+ @verbatim
+ mysqld_stmt_prepare("UPDATE t1 SET a=a*1.25 WHERE a=?")
+ --> general logs gets [Prepare] UPDATE t1 SET a*1.25 WHERE a=?"
+ mysqld_stmt_execute(stmt);
+ --> general and binary logs get
+ [Execute] UPDATE t1 SET a*1.25 WHERE a=1"
+ @endverbatim
+
+ If a statement has been prepared using SQL syntax:
+ @verbatim
+ PREPARE stmt FROM "UPDATE t1 SET a=a*1.25 WHERE a=?"
+ --> general log gets
+ [Query] PREPARE stmt FROM "UPDATE ..."
+ EXECUTE stmt USING @a
+ --> general log gets
+ [Query] EXECUTE stmt USING @a;
+ @endverbatim
+
+ @retval
+ 0 if success
+ @retval
+ 1 otherwise
+*/
+
+static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array,
+ uchar *read_pos, uchar *data_end,
+ String *query)
+{
+ THD *thd= stmt->thd;
+ Item_param **begin= stmt->param_array;
+ Item_param **end= begin + stmt->param_count;
+ uint32 length= 0;
+ String str;
+ const String *res;
+ DBUG_ENTER("insert_params_with_log");
+
+ if (query->copy(stmt->query(), stmt->query_length(), default_charset_info))
+ DBUG_RETURN(1);
+
+ for (Item_param **it= begin; it < end; ++it)
+ {
+ Item_param *param= *it;
+ if (param->state != Item_param::LONG_DATA_VALUE)
+ {
+ if (is_param_null(null_array, (uint) (it - begin)))
+ param->set_null();
+ else
+ {
+ if (read_pos >= data_end)
+ DBUG_RETURN(1);
+ param->set_param_func(param, &read_pos, (uint) (data_end - read_pos));
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
+
+ if (param->limit_clause_param && param->state != Item_param::INT_VALUE)
+ {
+ param->set_int(param->val_int(), MY_INT64_NUM_DECIMAL_DIGITS);
+ param->item_type= Item::INT_ITEM;
+ if (!param->unsigned_flag && param->value.integer < 0)
+ DBUG_RETURN(1);
+ }
+ }
+ }
+ /*
+ A long data stream was supplied for this parameter marker.
+ This was done after prepare, prior to providing a placeholder
+ type (the types are supplied at execute). Check that the
+ supplied type of placeholder can accept a data stream.
+ */
+ else if (! is_param_long_data_type(param))
+ DBUG_RETURN(1);
+ res= param->query_val_str(thd, &str);
+ if (param->convert_str_value(thd))
+ DBUG_RETURN(1); /* out of memory */
+
+ if (query->replace(param->pos_in_query+length, 1, *res))
+ DBUG_RETURN(1);
+
+ length+= res->length()-1;
+ }
+ DBUG_RETURN(0);
+}
+
+
+static bool insert_params(Prepared_statement *stmt, uchar *null_array,
+ uchar *read_pos, uchar *data_end,
+ String *expanded_query)
+{
+ Item_param **begin= stmt->param_array;
+ Item_param **end= begin + stmt->param_count;
+
+ DBUG_ENTER("insert_params");
+
+ for (Item_param **it= begin; it < end; ++it)
+ {
+ Item_param *param= *it;
+ if (param->state != Item_param::LONG_DATA_VALUE)
+ {
+ if (is_param_null(null_array, (uint) (it - begin)))
+ param->set_null();
+ else
+ {
+ if (read_pos >= data_end)
+ DBUG_RETURN(1);
+ param->set_param_func(param, &read_pos, (uint) (data_end - read_pos));
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
+ }
+ }
+ /*
+ A long data stream was supplied for this parameter marker.
+ This was done after prepare, prior to providing a placeholder
+ type (the types are supplied at execute). Check that the
+ supplied type of placeholder can accept a data stream.
+ */
+ else if (! is_param_long_data_type(param))
+ DBUG_RETURN(1);
+ if (param->convert_str_value(stmt->thd))
+ DBUG_RETURN(1); /* out of memory */
+ }
+ DBUG_RETURN(0);
+}
+
+
+static bool setup_conversion_functions(Prepared_statement *stmt,
+ uchar **data, uchar *data_end)
+{
+ /* skip null bits */
+ uchar *read_pos= *data + (stmt->param_count+7) / 8;
+
+ DBUG_ENTER("setup_conversion_functions");
+
+ if (*read_pos++) //types supplied / first execute
+ {
+ /*
+ First execute or types altered by the client, setup the
+ conversion routines for all parameters (one time)
+ */
+ Item_param **it= stmt->param_array;
+ Item_param **end= it + stmt->param_count;
+ THD *thd= stmt->thd;
+ for (; it < end; ++it)
+ {
+ ushort typecode;
+ const uint signed_bit= 1 << 15;
+
+ if (read_pos >= data_end)
+ DBUG_RETURN(1);
+
+ typecode= sint2korr(read_pos);
+ read_pos+= 2;
+ (**it).unsigned_flag= MY_TEST(typecode & signed_bit);
+ setup_one_conversion_function(thd, *it, (uchar) (typecode & ~signed_bit));
+ }
+ }
+ *data= read_pos;
+ DBUG_RETURN(0);
+}
+
+#else
+
+/**
+ Embedded counterparts of parameter assignment routines.
+
+ The main difference between the embedded library and the server is
+ that in embedded case we don't serialize/deserialize parameters data.
+
+ Additionally, for unknown reason, the client-side flag raised for
+ changed types of placeholders is ignored and we simply setup conversion
+ functions at each execute (TODO: fix).
+*/
+
+static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query)
+{
+ THD *thd= stmt->thd;
+ Item_param **it= stmt->param_array;
+ Item_param **end= it + stmt->param_count;
+ MYSQL_BIND *client_param= stmt->thd->client_params;
+
+ DBUG_ENTER("emb_insert_params");
+
+ for (; it < end; ++it, ++client_param)
+ {
+ Item_param *param= *it;
+ setup_one_conversion_function(thd, param, client_param->buffer_type);
+ if (param->state != Item_param::LONG_DATA_VALUE)
+ {
+ if (*client_param->is_null)
+ param->set_null();
+ else
+ {
+ uchar *buff= (uchar*) client_param->buffer;
+ param->unsigned_flag= client_param->is_unsigned;
+ param->set_param_func(param, &buff,
+ client_param->length ?
+ *client_param->length :
+ client_param->buffer_length);
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
+ }
+ }
+ if (param->convert_str_value(thd))
+ DBUG_RETURN(1); /* out of memory */
+ }
+ DBUG_RETURN(0);
+}
+
+
+static bool emb_insert_params_with_log(Prepared_statement *stmt,
+ String *query)
+{
+ THD *thd= stmt->thd;
+ Item_param **it= stmt->param_array;
+ Item_param **end= it + stmt->param_count;
+ MYSQL_BIND *client_param= thd->client_params;
+
+ String str;
+ const String *res;
+ uint32 length= 0;
+
+ DBUG_ENTER("emb_insert_params_with_log");
+
+ if (query->copy(stmt->query(), stmt->query_length(), default_charset_info))
+ DBUG_RETURN(1);
+
+ for (; it < end; ++it, ++client_param)
+ {
+ Item_param *param= *it;
+ setup_one_conversion_function(thd, param, client_param->buffer_type);
+ if (param->state != Item_param::LONG_DATA_VALUE)
+ {
+ if (*client_param->is_null)
+ param->set_null();
+ else
+ {
+ uchar *buff= (uchar*)client_param->buffer;
+ param->unsigned_flag= client_param->is_unsigned;
+ param->set_param_func(param, &buff,
+ client_param->length ?
+ *client_param->length :
+ client_param->buffer_length);
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
+ }
+ }
+ res= param->query_val_str(thd, &str);
+ if (param->convert_str_value(thd))
+ DBUG_RETURN(1); /* out of memory */
+
+ if (query->replace(param->pos_in_query+length, 1, *res))
+ DBUG_RETURN(1);
+
+ length+= res->length()-1;
+ }
+ DBUG_RETURN(0);
+}
+
+#endif /*!EMBEDDED_LIBRARY*/
+
+/**
+ Setup data conversion routines using an array of parameter
+ markers from the original prepared statement.
+ Swap the parameter data of the original prepared
+ statement to the new one.
+
+ Used only when we re-prepare a prepared statement.
+ There are two reasons for this function to exist:
+
+ 1) In the binary client/server protocol, parameter metadata
+ is sent only at first execute. Consequently, if we need to
+ reprepare a prepared statement at a subsequent execution,
+ we may not have metadata information in the packet.
+ In that case we use the parameter array of the original
+ prepared statement to setup parameter types of the new
+ prepared statement.
+
+ 2) In the binary client/server protocol, we may supply
+ long data in pieces. When the last piece is supplied,
+ we assemble the pieces and convert them from client
+ character set to the connection character set. After
+ that the parameter value is only available inside
+ the parameter, the original pieces are lost, and thus
+ we can only assign the corresponding parameter of the
+ reprepared statement from the original value.
+
+ @param[out] param_array_dst parameter markers of the new statement
+ @param[in] param_array_src parameter markers of the original
+ statement
+ @param[in] param_count total number of parameters. Is the
+ same in src and dst arrays, since
+ the statement query is the same
+
+ @return this function never fails
+*/
+
+static void
+swap_parameter_array(Item_param **param_array_dst,
+ Item_param **param_array_src,
+ uint param_count)
+{
+ Item_param **dst= param_array_dst;
+ Item_param **src= param_array_src;
+ Item_param **end= param_array_dst + param_count;
+
+ for (; dst < end; ++src, ++dst)
+ (*dst)->set_param_type_and_swap_value(*src);
+}
+
+
+/**
+ Assign prepared statement parameters from user variables.
+
+ @param stmt Statement
+ @param varnames List of variables. Caller must ensure that number
+ of variables in the list is equal to number of statement
+ parameters
+ @param query Ignored
+*/
+
+static bool insert_params_from_vars(Prepared_statement *stmt,
+ List<LEX_STRING>& varnames,
+ String *query __attribute__((unused)))
+{
+ Item_param **begin= stmt->param_array;
+ Item_param **end= begin + stmt->param_count;
+ user_var_entry *entry;
+ LEX_STRING *varname;
+ List_iterator<LEX_STRING> var_it(varnames);
+ DBUG_ENTER("insert_params_from_vars");
+
+ for (Item_param **it= begin; it < end; ++it)
+ {
+ Item_param *param= *it;
+ varname= var_it++;
+ entry= (user_var_entry*)my_hash_search(&stmt->thd->user_vars,
+ (uchar*) varname->str,
+ varname->length);
+ if (param->set_from_user_var(stmt->thd, entry) ||
+ param->convert_str_value(stmt->thd))
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Do the same as insert_params_from_vars but also construct query text for
+ binary log.
+
+ @param stmt Prepared statement
+ @param varnames List of variables. Caller must ensure that number of
+ variables in the list is equal to number of statement
+ parameters
+ @param query The query with parameter markers replaced with corresponding
+ user variables that were used to execute the query.
+*/
+
+static bool insert_params_from_vars_with_log(Prepared_statement *stmt,
+ List<LEX_STRING>& varnames,
+ String *query)
+{
+ Item_param **begin= stmt->param_array;
+ Item_param **end= begin + stmt->param_count;
+ user_var_entry *entry;
+ LEX_STRING *varname;
+ List_iterator<LEX_STRING> var_it(varnames);
+ String buf;
+ const String *val;
+ uint32 length= 0;
+ THD *thd= stmt->thd;
+
+ DBUG_ENTER("insert_params_from_vars_with_log");
+
+ if (query->copy(stmt->query(), stmt->query_length(), default_charset_info))
+ DBUG_RETURN(1);
+
+ for (Item_param **it= begin; it < end; ++it)
+ {
+ Item_param *param= *it;
+ varname= var_it++;
+
+ entry= (user_var_entry *) my_hash_search(&thd->user_vars, (uchar*)
+ varname->str, varname->length);
+ /*
+ We have to call the setup_one_conversion_function() here to set
+ the parameter's members that might be needed further
+ (e.g. value.cs_info.character_set_client is used in the query_val_str()).
+ */
+ setup_one_conversion_function(thd, param, param->param_type);
+ if (param->set_from_user_var(thd, entry))
+ DBUG_RETURN(1);
+ val= param->query_val_str(thd, &buf);
+
+ if (param->convert_str_value(thd))
+ DBUG_RETURN(1); /* out of memory */
+
+ if (query->replace(param->pos_in_query+length, 1, *val))
+ DBUG_RETURN(1);
+ length+= val->length()-1;
+ }
+ DBUG_RETURN(0);
+}
+
+/**
+ Validate INSERT statement.
+
+ @param stmt prepared statement
+ @param tables global/local table list
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_insert(Prepared_statement *stmt,
+ TABLE_LIST *table_list,
+ List<Item> &fields,
+ List<List_item> &values_list,
+ List<Item> &update_fields,
+ List<Item> &update_values,
+ enum_duplicates duplic)
+{
+ THD *thd= stmt->thd;
+ List_iterator_fast<List_item> its(values_list);
+ List_item *values;
+ DBUG_ENTER("mysql_test_insert");
+
+ /*
+ Since INSERT DELAYED doesn't support temporary tables, we could
+ not pre-open temporary tables for SQLCOM_INSERT / SQLCOM_REPLACE.
+ Open them here instead.
+ */
+ if (table_list->lock_type != TL_WRITE_DELAYED)
+ {
+ if (open_temporary_tables(thd, table_list))
+ goto error;
+ }
+
+ if (insert_precheck(thd, table_list))
+ goto error;
+
+ //upgrade_lock_type_for_insert(thd, &table_list->lock_type, duplic,
+ // values_list.elements > 1);
+ /*
+ open temporary memory pool for temporary data allocated by derived
+ tables & preparation procedure
+ Note that this is done without locks (should not be needed as we will not
+ access any data here)
+ If we would use locks, then we have to ensure we are not using
+ TL_WRITE_DELAYED as having two such locks can cause table corruption.
+ */
+ if (open_normal_and_derived_tables(thd, table_list,
+ MYSQL_OPEN_FORCE_SHARED_MDL, DT_INIT))
+ goto error;
+
+ if ((values= its++))
+ {
+ uint value_count;
+ ulong counter= 0;
+ Item *unused_conds= 0;
+
+ if (table_list->table)
+ {
+ // don't allocate insert_values
+ table_list->table->insert_values=(uchar *)1;
+ }
+
+ if (mysql_prepare_insert(thd, table_list, table_list->table,
+ fields, values, update_fields, update_values,
+ duplic, &unused_conds, FALSE, FALSE, FALSE))
+ goto error;
+
+ value_count= values->elements;
+ its.rewind();
+
+ if (table_list->lock_type == TL_WRITE_DELAYED &&
+ !(table_list->table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED))
+ {
+ my_error(ER_DELAYED_NOT_SUPPORTED, MYF(0), (table_list->view ?
+ table_list->view_name.str :
+ table_list->table_name));
+ goto error;
+ }
+ while ((values= its++))
+ {
+ counter++;
+ if (values->elements != value_count)
+ {
+ my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), counter);
+ goto error;
+ }
+ if (setup_fields(thd, 0, *values, MARK_COLUMNS_NONE, 0, 0))
+ goto error;
+ }
+ }
+ DBUG_RETURN(FALSE);
+
+error:
+ /* insert_values is cleared in open_table */
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Validate UPDATE statement.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+
+ @todo
+ - here we should send types of placeholders to the client.
+
+ @retval
+ 0 success
+ @retval
+ 1 error, error message is set in THD
+ @retval
+ 2 convert to multi_update
+*/
+
+static int mysql_test_update(Prepared_statement *stmt,
+ TABLE_LIST *table_list)
+{
+ int res;
+ THD *thd= stmt->thd;
+ uint table_count= 0;
+ SELECT_LEX *select= &stmt->lex->select_lex;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ uint want_privilege;
+#endif
+ DBUG_ENTER("mysql_test_update");
+
+ if (update_precheck(thd, table_list) ||
+ open_tables(thd, &table_list, &table_count, MYSQL_OPEN_FORCE_SHARED_MDL))
+ goto error;
+
+ if (mysql_handle_derived(thd->lex, DT_INIT))
+ goto error;
+
+ if (table_list->is_multitable())
+ {
+ DBUG_ASSERT(table_list->view != 0);
+ DBUG_PRINT("info", ("Switch to multi-update"));
+ /* pass counter value */
+ thd->lex->table_count= table_count;
+ /* convert to multiupdate */
+ DBUG_RETURN(2);
+ }
+
+ /*
+ thd->fill_derived_tables() is false here for sure (because it is
+ preparation of PS, so we even do not check it).
+ */
+ if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT))
+ goto error;
+ if (table_list->handle_derived(thd->lex, DT_PREPARE))
+ goto error;
+
+ if (!table_list->single_table_updatable())
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE");
+ goto error;
+ }
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* Force privilege re-checking for views after they have been opened. */
+ want_privilege= (table_list->view ? UPDATE_ACL :
+ table_list->grant.want_privilege);
+#endif
+
+ if (mysql_prepare_update(thd, table_list, &select->where,
+ select->order_list.elements,
+ select->order_list.first))
+ goto error;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ table_list->grant.want_privilege= want_privilege;
+ table_list->table->grant.want_privilege= want_privilege;
+ table_list->register_want_access(want_privilege);
+#endif
+ thd->lex->select_lex.no_wrap_view_item= TRUE;
+ res= setup_fields(thd, 0, select->item_list, MARK_COLUMNS_READ, 0, 0);
+ thd->lex->select_lex.no_wrap_view_item= FALSE;
+ if (res)
+ goto error;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* Check values */
+ table_list->grant.want_privilege=
+ table_list->table->grant.want_privilege=
+ (SELECT_ACL & ~table_list->table->grant.privilege);
+ table_list->register_want_access(SELECT_ACL);
+#endif
+ if (setup_fields(thd, 0, stmt->lex->value_list, MARK_COLUMNS_NONE, 0, 0))
+ goto error;
+ /* TODO: here we should send types of placeholders to the client. */
+ DBUG_RETURN(0);
+error:
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Validate DELETE statement.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_delete(Prepared_statement *stmt,
+ TABLE_LIST *table_list)
+{
+ uint table_count= 0;
+ THD *thd= stmt->thd;
+ LEX *lex= stmt->lex;
+ DBUG_ENTER("mysql_test_delete");
+
+ if (delete_precheck(thd, table_list) ||
+ open_tables(thd, &table_list, &table_count, MYSQL_OPEN_FORCE_SHARED_MDL))
+ goto error;
+
+ if (mysql_handle_derived(thd->lex, DT_INIT))
+ goto error;
+ if (mysql_handle_derived(thd->lex, DT_MERGE_FOR_INSERT))
+ goto error;
+ if (mysql_handle_derived(thd->lex, DT_PREPARE))
+ goto error;
+
+ if (!table_list->single_table_updatable())
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "DELETE");
+ goto error;
+ }
+ if (!table_list->table || !table_list->table->created)
+ {
+ my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0),
+ table_list->view_db.str, table_list->view_name.str);
+ goto error;
+ }
+
+ DBUG_RETURN(mysql_prepare_delete(thd, table_list,
+ lex->select_lex.with_wild,
+ lex->select_lex.item_list,
+ &lex->select_lex.where));
+error:
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Validate SELECT statement.
+
+ In case of success, if this query is not EXPLAIN, send column list info
+ back to the client.
+
+ @param stmt prepared statement
+ @param tables list of tables used in the query
+
+ @retval
+ 0 success
+ @retval
+ 1 error, error message is set in THD
+ @retval
+ 2 success, and statement metadata has been sent
+*/
+
+static int mysql_test_select(Prepared_statement *stmt,
+ TABLE_LIST *tables)
+{
+ THD *thd= stmt->thd;
+ LEX *lex= stmt->lex;
+ SELECT_LEX_UNIT *unit= &lex->unit;
+ DBUG_ENTER("mysql_test_select");
+
+ lex->select_lex.context.resolve_in_select_list= TRUE;
+
+ ulong privilege= lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL;
+ if (tables)
+ {
+ if (check_table_access(thd, privilege, tables, FALSE, UINT_MAX, FALSE))
+ goto error;
+ }
+ else if (check_access(thd, privilege, any_db, NULL, NULL, 0, 0))
+ goto error;
+
+ if (!lex->result && !(lex->result= new (stmt->mem_root) select_send))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR),
+ static_cast<int>(sizeof(select_send)));
+ goto error;
+ }
+
+ if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
+ DT_PREPARE | DT_CREATE))
+ goto error;
+
+ thd->lex->used_tables= 0; // Updated by setup_fields
+
+ /*
+ JOIN::prepare calls
+ It is not SELECT COMMAND for sure, so setup_tables will be called as
+ usual, and we pass 0 as setup_tables_done_option
+ */
+ if (unit->prepare(thd, 0, 0))
+ goto error;
+ if (!lex->describe && !stmt->is_sql_prepare())
+ {
+ /* Make copy of item list, as change_columns may change it */
+ List<Item> fields(lex->select_lex.item_list);
+
+ /* Change columns if a procedure like analyse() */
+ if (unit->last_procedure && unit->last_procedure->change_columns(fields))
+ goto error;
+
+ /*
+ We can use lex->result as it should've been prepared in
+ unit->prepare call above.
+ */
+ if (send_prep_stmt(stmt, lex->result->field_count(fields)) ||
+ lex->result->send_result_set_metadata(fields, Protocol::SEND_EOF) ||
+ thd->protocol->flush())
+ goto error;
+ DBUG_RETURN(2);
+ }
+ DBUG_RETURN(0);
+error:
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Validate and prepare for execution DO statement expressions.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+ @param values list of expressions
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_do_fields(Prepared_statement *stmt,
+ TABLE_LIST *tables,
+ List<Item> *values)
+{
+ THD *thd= stmt->thd;
+
+ DBUG_ENTER("mysql_test_do_fields");
+ if (tables && check_table_access(thd, SELECT_ACL, tables, FALSE,
+ UINT_MAX, FALSE))
+ DBUG_RETURN(TRUE);
+
+ if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
+ DT_PREPARE | DT_CREATE))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(setup_fields(thd, 0, *values, MARK_COLUMNS_NONE, 0, 0));
+}
+
+
+/**
+ Validate and prepare for execution SET statement expressions.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+ @param values list of expressions
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_set_fields(Prepared_statement *stmt,
+ TABLE_LIST *tables,
+ List<set_var_base> *var_list)
+{
+ DBUG_ENTER("mysql_test_set_fields");
+ List_iterator_fast<set_var_base> it(*var_list);
+ THD *thd= stmt->thd;
+ set_var_base *var;
+
+ if ((tables &&
+ check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) ||
+ open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
+ DT_PREPARE | DT_CREATE))
+ goto error;
+
+ while ((var= it++))
+ {
+ if (var->light_check(thd))
+ goto error;
+ }
+ DBUG_RETURN(FALSE);
+error:
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Validate and prepare for execution CALL statement expressions.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+ @param value_list list of expressions
+
+ @retval FALSE success
+ @retval TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_call_fields(Prepared_statement *stmt,
+ TABLE_LIST *tables,
+ List<Item> *value_list)
+{
+ DBUG_ENTER("mysql_test_call_fields");
+
+ List_iterator<Item> it(*value_list);
+ THD *thd= stmt->thd;
+ Item *item;
+
+ if ((tables &&
+ check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) ||
+ open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL, DT_PREPARE))
+ goto err;
+
+ while ((item= it++))
+ {
+ if ((!item->fixed && item->fix_fields(thd, it.ref())) ||
+ item->check_cols(1))
+ goto err;
+ }
+ DBUG_RETURN(FALSE);
+err:
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Check internal SELECT of the prepared command.
+
+ @param stmt prepared statement
+ @param specific_prepare function of command specific prepare
+ @param setup_tables_done_option options to be passed to LEX::unit.prepare()
+
+ @note
+ This function won't directly open tables used in select. They should
+ be opened either by calling function (and in this case you probably
+ should use select_like_stmt_test_with_open()) or by
+ "specific_prepare" call (like this happens in case of multi-update).
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool select_like_stmt_test(Prepared_statement *stmt,
+ int (*specific_prepare)(THD *thd),
+ ulong setup_tables_done_option)
+{
+ DBUG_ENTER("select_like_stmt_test");
+ THD *thd= stmt->thd;
+ LEX *lex= stmt->lex;
+
+ lex->select_lex.context.resolve_in_select_list= TRUE;
+
+ if (specific_prepare && (*specific_prepare)(thd))
+ DBUG_RETURN(TRUE);
+
+ thd->lex->used_tables= 0; // Updated by setup_fields
+
+ /* Calls JOIN::prepare */
+ DBUG_RETURN(lex->unit.prepare(thd, 0, setup_tables_done_option));
+}
+
+/**
+ Check internal SELECT of the prepared command (with opening of used
+ tables).
+
+ @param stmt prepared statement
+ @param tables list of tables to be opened
+ before calling specific_prepare function
+ @param specific_prepare function of command specific prepare
+ @param setup_tables_done_option options to be passed to LEX::unit.prepare()
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error
+*/
+
+static bool
+select_like_stmt_test_with_open(Prepared_statement *stmt,
+ TABLE_LIST *tables,
+ int (*specific_prepare)(THD *thd),
+ ulong setup_tables_done_option)
+{
+ uint table_count= 0;
+ DBUG_ENTER("select_like_stmt_test_with_open");
+
+ /*
+ We should not call LEX::unit.cleanup() after this
+ open_normal_and_derived_tables() call because we don't allow
+ prepared EXPLAIN yet so derived tables will clean up after
+ themself.
+ */
+ THD *thd= stmt->thd;
+ if (open_tables(thd, &tables, &table_count, MYSQL_OPEN_FORCE_SHARED_MDL))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(select_like_stmt_test(stmt, specific_prepare,
+ setup_tables_done_option));
+}
+
+
+/**
+ Validate and prepare for execution CREATE TABLE statement.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_create_table(Prepared_statement *stmt)
+{
+ DBUG_ENTER("mysql_test_create_table");
+ THD *thd= stmt->thd;
+ LEX *lex= stmt->lex;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ bool res= FALSE;
+ bool link_to_local;
+ TABLE_LIST *create_table= lex->query_tables;
+ TABLE_LIST *tables= lex->create_last_non_select_table->next_global;
+
+ if (create_table_precheck(thd, tables, create_table))
+ DBUG_RETURN(TRUE);
+
+ if (select_lex->item_list.elements)
+ {
+ /* Base table and temporary table are not in the same name space. */
+ if (!lex->create_info.tmp_table())
+ create_table->open_type= OT_BASE_ONLY;
+
+ if (open_normal_and_derived_tables(stmt->thd, lex->query_tables,
+ MYSQL_OPEN_FORCE_SHARED_MDL,
+ DT_PREPARE | DT_CREATE))
+ DBUG_RETURN(TRUE);
+
+ select_lex->context.resolve_in_select_list= TRUE;
+
+ lex->unlink_first_table(&link_to_local);
+
+ res= select_like_stmt_test(stmt, 0, 0);
+
+ lex->link_first_table_back(create_table, link_to_local);
+ }
+ else
+ {
+ /*
+ Check that the source table exist, and also record
+ its metadata version. Even though not strictly necessary,
+ we validate metadata of all CREATE TABLE statements,
+ which keeps metadata validation code simple.
+ */
+ if (open_normal_and_derived_tables(stmt->thd, lex->query_tables,
+ MYSQL_OPEN_FORCE_SHARED_MDL,
+ DT_PREPARE))
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ @brief Validate and prepare for execution CREATE VIEW statement
+
+ @param stmt prepared statement
+
+ @note This function handles create view commands.
+
+ @retval FALSE Operation was a success.
+ @retval TRUE An error occured.
+*/
+
+static bool mysql_test_create_view(Prepared_statement *stmt)
+{
+ DBUG_ENTER("mysql_test_create_view");
+ THD *thd= stmt->thd;
+ LEX *lex= stmt->lex;
+ bool res= TRUE;
+ /* Skip first table, which is the view we are creating */
+ bool link_to_local;
+ TABLE_LIST *view= lex->unlink_first_table(&link_to_local);
+ TABLE_LIST *tables= lex->query_tables;
+
+ if (create_view_precheck(thd, tables, view, lex->create_view_mode))
+ goto err;
+
+ /*
+ Since we can't pre-open temporary tables for SQLCOM_CREATE_VIEW,
+ (see mysql_create_view) we have to do it here instead.
+ */
+ if (open_temporary_tables(thd, tables))
+ goto err;
+
+ if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
+ DT_PREPARE))
+ goto err;
+
+ lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VIEW;
+ res= select_like_stmt_test(stmt, 0, 0);
+
+err:
+ /* put view back for PS rexecuting */
+ lex->link_first_table_back(view, link_to_local);
+ DBUG_RETURN(res);
+}
+
+
+/*
+ Validate and prepare for execution a multi update statement.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+ @param converted converted to multi-update from usual update
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_multiupdate(Prepared_statement *stmt,
+ TABLE_LIST *tables,
+ bool converted)
+{
+ /* if we switched from normal update, rights are checked */
+ if (!converted && multi_update_precheck(stmt->thd, tables))
+ return TRUE;
+
+ return select_like_stmt_test(stmt, &mysql_multi_update_prepare,
+ OPTION_SETUP_TABLES_DONE);
+}
+
+
+/**
+ Validate and prepare for execution a multi delete statement.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message in THD is set.
+*/
+
+static bool mysql_test_multidelete(Prepared_statement *stmt,
+ TABLE_LIST *tables)
+{
+ stmt->thd->lex->current_select= &stmt->thd->lex->select_lex;
+ if (add_item_to_list(stmt->thd, new Item_null()))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 0);
+ goto error;
+ }
+
+ if (multi_delete_precheck(stmt->thd, tables) ||
+ select_like_stmt_test_with_open(stmt, tables,
+ &mysql_multi_delete_prepare,
+ OPTION_SETUP_TABLES_DONE))
+ goto error;
+ if (!tables->table)
+ {
+ my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0),
+ tables->view_db.str, tables->view_name.str);
+ goto error;
+ }
+ return FALSE;
+error:
+ return TRUE;
+}
+
+
+/**
+ Wrapper for mysql_insert_select_prepare, to make change of local tables
+ after open_normal_and_derived_tables() call.
+
+ @param thd thread handle
+
+ @note
+ We need to remove the first local table after
+ open_normal_and_derived_tables(), because mysql_handle_derived
+ uses local tables lists.
+*/
+
+static int mysql_insert_select_prepare_tester(THD *thd)
+{
+ SELECT_LEX *first_select= &thd->lex->select_lex;
+ TABLE_LIST *second_table= first_select->table_list.first->next_local;
+
+ /* Skip first table, which is the table we are inserting in */
+ first_select->table_list.first= second_table;
+ thd->lex->select_lex.context.table_list=
+ thd->lex->select_lex.context.first_name_resolution_table= second_table;
+
+ return mysql_insert_select_prepare(thd);
+}
+
+
+/**
+ Validate and prepare for execution INSERT ... SELECT statement.
+
+ @param stmt prepared statement
+ @param tables list of tables used in this query
+
+ @retval
+ FALSE success
+ @retval
+ TRUE error, error message is set in THD
+*/
+
+static bool mysql_test_insert_select(Prepared_statement *stmt,
+ TABLE_LIST *tables)
+{
+ int res;
+ LEX *lex= stmt->lex;
+ TABLE_LIST *first_local_table;
+
+ if (tables->table)
+ {
+ // don't allocate insert_values
+ tables->table->insert_values=(uchar *)1;
+ }
+
+ if (insert_precheck(stmt->thd, tables))
+ return 1;
+
+ /* store it, because mysql_insert_select_prepare_tester change it */
+ first_local_table= lex->select_lex.table_list.first;
+ DBUG_ASSERT(first_local_table != 0);
+
+ res=
+ select_like_stmt_test_with_open(stmt, tables,
+ &mysql_insert_select_prepare_tester,
+ OPTION_SETUP_TABLES_DONE);
+ /* revert changes made by mysql_insert_select_prepare_tester */
+ lex->select_lex.table_list.first= first_local_table;
+ return res;
+}
+
+/**
+ Validate SELECT statement.
+
+ In case of success, if this query is not EXPLAIN, send column list info
+ back to the client.
+
+ @param stmt prepared statement
+ @param tables list of tables used in the query
+
+ @retval 0 success
+ @retval 1 error, error message is set in THD
+ @retval 2 success, and statement metadata has been sent
+*/
+
+static int mysql_test_handler_read(Prepared_statement *stmt,
+ TABLE_LIST *tables)
+{
+ THD *thd= stmt->thd;
+ LEX *lex= stmt->lex;
+ SQL_HANDLER *ha_table;
+ DBUG_ENTER("mysql_test_select");
+
+ lex->select_lex.context.resolve_in_select_list= TRUE;
+
+ /*
+ We don't have to test for permissions as this is already done during
+ HANDLER OPEN
+ */
+ if (!(ha_table= mysql_ha_read_prepare(thd, tables, lex->ha_read_mode,
+ lex->ident.str,
+ lex->insert_list,
+ lex->select_lex.where)))
+ DBUG_RETURN(1);
+
+ if (!stmt->is_sql_prepare())
+ {
+ if (!lex->result && !(lex->result= new (stmt->mem_root) select_send))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(0), sizeof(select_send));
+ DBUG_RETURN(1);
+ }
+ if (send_prep_stmt(stmt, ha_table->fields.elements) ||
+ lex->result->send_result_set_metadata(ha_table->fields, Protocol::SEND_EOF) ||
+ thd->protocol->flush())
+ DBUG_RETURN(1);
+ DBUG_RETURN(2);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Perform semantic analysis of the parsed tree and send a response packet
+ to the client.
+
+ This function
+ - opens all tables and checks access rights
+ - validates semantics of statement columns and SQL functions
+ by calling fix_fields.
+
+ @param stmt prepared statement
+
+ @retval
+ FALSE success, statement metadata is sent to client
+ @retval
+ TRUE error, error message is set in THD (but not sent)
+*/
+
+static bool check_prepared_statement(Prepared_statement *stmt)
+{
+ THD *thd= stmt->thd;
+ LEX *lex= stmt->lex;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ TABLE_LIST *tables;
+ enum enum_sql_command sql_command= lex->sql_command;
+ int res= 0;
+ DBUG_ENTER("check_prepared_statement");
+ DBUG_PRINT("enter",("command: %d param_count: %u",
+ sql_command, stmt->param_count));
+
+ lex->first_lists_tables_same();
+ tables= lex->query_tables;
+
+ /* set context for commands which do not use setup_tables */
+ lex->select_lex.context.resolve_in_table_list_only(select_lex->
+ get_table_list());
+
+ /* Reset warning count for each query that uses tables */
+ if (tables)
+ thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
+
+ if (sql_command_flags[sql_command] & CF_HA_CLOSE)
+ mysql_ha_rm_tables(thd, tables);
+
+ /*
+ Open temporary tables that are known now. Temporary tables added by
+ prelocking will be opened afterwards (during open_tables()).
+ */
+ if (sql_command_flags[sql_command] & CF_PREOPEN_TMP_TABLES)
+ {
+ if (open_temporary_tables(thd, tables))
+ goto error;
+ }
+
+ switch (sql_command) {
+ case SQLCOM_REPLACE:
+ case SQLCOM_INSERT:
+ res= mysql_test_insert(stmt, tables, lex->field_list,
+ lex->many_values,
+ lex->update_list, lex->value_list,
+ lex->duplicates);
+ break;
+
+ case SQLCOM_UPDATE:
+ res= mysql_test_update(stmt, tables);
+ /* mysql_test_update returns 2 if we need to switch to multi-update */
+ if (res != 2)
+ break;
+
+ case SQLCOM_UPDATE_MULTI:
+ res= mysql_test_multiupdate(stmt, tables, res == 2);
+ break;
+
+ case SQLCOM_DELETE:
+ res= mysql_test_delete(stmt, tables);
+ break;
+ /* The following allow WHERE clause, so they must be tested like SELECT */
+ case SQLCOM_SHOW_DATABASES:
+ case SQLCOM_SHOW_TABLES:
+ case SQLCOM_SHOW_TRIGGERS:
+ case SQLCOM_SHOW_EVENTS:
+ case SQLCOM_SHOW_OPEN_TABLES:
+ case SQLCOM_SHOW_FIELDS:
+ case SQLCOM_SHOW_KEYS:
+ case SQLCOM_SHOW_COLLATIONS:
+ case SQLCOM_SHOW_CHARSETS:
+ case SQLCOM_SHOW_VARIABLES:
+ case SQLCOM_SHOW_STATUS:
+ case SQLCOM_SHOW_TABLE_STATUS:
+ case SQLCOM_SHOW_STATUS_PROC:
+ case SQLCOM_SHOW_STATUS_FUNC:
+ case SQLCOM_SELECT:
+ res= mysql_test_select(stmt, tables);
+ if (res == 2)
+ {
+ /* Statement and field info has already been sent */
+ DBUG_RETURN(FALSE);
+ }
+ break;
+ case SQLCOM_CREATE_TABLE:
+ res= mysql_test_create_table(stmt);
+ break;
+
+ case SQLCOM_CREATE_VIEW:
+ if (lex->create_view_mode == VIEW_ALTER)
+ {
+ my_message(ER_UNSUPPORTED_PS, ER(ER_UNSUPPORTED_PS), MYF(0));
+ goto error;
+ }
+ res= mysql_test_create_view(stmt);
+ break;
+ case SQLCOM_DO:
+ res= mysql_test_do_fields(stmt, tables, lex->insert_list);
+ break;
+
+ case SQLCOM_CALL:
+ res= mysql_test_call_fields(stmt, tables, &lex->value_list);
+ break;
+ case SQLCOM_SET_OPTION:
+ res= mysql_test_set_fields(stmt, tables, &lex->var_list);
+ break;
+
+ case SQLCOM_DELETE_MULTI:
+ res= mysql_test_multidelete(stmt, tables);
+ break;
+
+ case SQLCOM_INSERT_SELECT:
+ case SQLCOM_REPLACE_SELECT:
+ res= mysql_test_insert_select(stmt, tables);
+ break;
+
+ case SQLCOM_HA_READ:
+ res= mysql_test_handler_read(stmt, tables);
+ /* Statement and field info has already been sent */
+ DBUG_RETURN(res == 1 ? TRUE : FALSE);
+
+ /*
+ Note that we don't need to have cases in this list if they are
+ marked with CF_STATUS_COMMAND in sql_command_flags
+ */
+ case SQLCOM_SHOW_EXPLAIN:
+ case SQLCOM_DROP_TABLE:
+ case SQLCOM_RENAME_TABLE:
+ case SQLCOM_ALTER_TABLE:
+ case SQLCOM_COMMIT:
+ case SQLCOM_CREATE_INDEX:
+ case SQLCOM_DROP_INDEX:
+ case SQLCOM_ROLLBACK:
+ case SQLCOM_TRUNCATE:
+ case SQLCOM_DROP_VIEW:
+ case SQLCOM_REPAIR:
+ case SQLCOM_ANALYZE:
+ case SQLCOM_OPTIMIZE:
+ case SQLCOM_CHANGE_MASTER:
+ case SQLCOM_RESET:
+ case SQLCOM_FLUSH:
+ case SQLCOM_SLAVE_START:
+ case SQLCOM_SLAVE_STOP:
+ case SQLCOM_SLAVE_ALL_START:
+ case SQLCOM_SLAVE_ALL_STOP:
+ case SQLCOM_INSTALL_PLUGIN:
+ case SQLCOM_UNINSTALL_PLUGIN:
+ case SQLCOM_CREATE_DB:
+ case SQLCOM_DROP_DB:
+ case SQLCOM_ALTER_DB_UPGRADE:
+ case SQLCOM_CHECKSUM:
+ case SQLCOM_CREATE_USER:
+ case SQLCOM_RENAME_USER:
+ case SQLCOM_DROP_USER:
+ case SQLCOM_ASSIGN_TO_KEYCACHE:
+ case SQLCOM_PRELOAD_KEYS:
+ case SQLCOM_GRANT:
+ case SQLCOM_REVOKE:
+ case SQLCOM_KILL:
+ case SQLCOM_SHUTDOWN:
+ break;
+
+ case SQLCOM_PREPARE:
+ case SQLCOM_EXECUTE:
+ case SQLCOM_DEALLOCATE_PREPARE:
+ default:
+ /*
+ Trivial check of all status commands. This is easier than having
+ things in the above case list, as it's less chance for mistakes.
+ */
+ if (!(sql_command_flags[sql_command] & CF_STATUS_COMMAND))
+ {
+ /* All other statements are not supported yet. */
+ my_message(ER_UNSUPPORTED_PS, ER(ER_UNSUPPORTED_PS), MYF(0));
+ goto error;
+ }
+ break;
+ }
+ if (res == 0)
+ DBUG_RETURN(stmt->is_sql_prepare() ?
+ FALSE : (send_prep_stmt(stmt, 0) || thd->protocol->flush()));
+error:
+ DBUG_RETURN(TRUE);
+}
+
+/**
+ Initialize array of parameters in statement from LEX.
+ (We need to have quick access to items by number in mysql_stmt_get_longdata).
+ This is to avoid using malloc/realloc in the parser.
+*/
+
+static bool init_param_array(Prepared_statement *stmt)
+{
+ LEX *lex= stmt->lex;
+ if ((stmt->param_count= lex->param_list.elements))
+ {
+ if (stmt->param_count > (uint) UINT_MAX16)
+ {
+ /* Error code to be defined in 5.0 */
+ my_message(ER_PS_MANY_PARAM, ER(ER_PS_MANY_PARAM), MYF(0));
+ return TRUE;
+ }
+ Item_param **to;
+ List_iterator<Item_param> param_iterator(lex->param_list);
+ /* Use thd->mem_root as it points at statement mem_root */
+ stmt->param_array= (Item_param **)
+ alloc_root(stmt->thd->mem_root,
+ sizeof(Item_param*) * stmt->param_count);
+ if (!stmt->param_array)
+ return TRUE;
+ for (to= stmt->param_array;
+ to < stmt->param_array + stmt->param_count;
+ ++to)
+ {
+ *to= param_iterator++;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ COM_STMT_PREPARE handler.
+
+ Given a query string with parameter markers, create a prepared
+ statement from it and send PS info back to the client.
+
+ If parameter markers are found in the query, then store the information
+ using Item_param along with maintaining a list in lex->param_array, so
+ that a fast and direct retrieval can be made without going through all
+ field items.
+
+ @param packet query to be prepared
+ @param packet_length query string length, including ignored
+ trailing NULL or quote char.
+
+ @note
+ This function parses the query and sends the total number of parameters
+ and resultset metadata information back to client (if any), without
+ executing the query i.e. without any log/disk writes. This allows the
+ queries to be re-executed without re-parsing during execute.
+
+ @return
+ none: in case of success a new statement id and metadata is sent
+ to the client, otherwise an error message is set in THD.
+*/
+
+void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length)
+{
+ Protocol *save_protocol= thd->protocol;
+ Prepared_statement *stmt;
+ DBUG_ENTER("mysqld_stmt_prepare");
+ DBUG_PRINT("prep_query", ("%s", packet));
+
+ /* First of all clear possible warnings from the previous command */
+ mysql_reset_thd_for_next_command(thd);
+
+ if (! (stmt= new Prepared_statement(thd)))
+ goto end; /* out of memory: error is set in Sql_alloc */
+
+ if (thd->stmt_map.insert(thd, stmt))
+ {
+ /*
+ The error is set in the insert. The statement itself
+ will be also deleted there (this is how the hash works).
+ */
+ goto end;
+ }
+
+ thd->protocol= &thd->protocol_binary;
+
+ if (stmt->prepare(packet, packet_length))
+ {
+ /* Statement map deletes statement on erase */
+ thd->stmt_map.erase(stmt);
+ }
+
+ thd->protocol= save_protocol;
+
+ sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size);
+ sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size);
+
+ /* check_prepared_statemnt sends the metadata packet in case of success */
+end:
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Get an SQL statement text from a user variable or from plain text.
+
+ If the statement is plain text, just assign the
+ pointers, otherwise allocate memory in thd->mem_root and copy
+ the contents of the variable, possibly with character
+ set conversion.
+
+ @param[in] lex main lex
+ @param[out] query_len length of the SQL statement (is set only
+ in case of success)
+
+ @retval
+ non-zero success
+ @retval
+ 0 in case of error (out of memory)
+*/
+
+static const char *get_dynamic_sql_string(LEX *lex, uint *query_len)
+{
+ THD *thd= lex->thd;
+ char *query_str= 0;
+
+ if (lex->prepared_stmt_code_is_varref)
+ {
+ /* This is PREPARE stmt FROM or EXECUTE IMMEDIATE @var. */
+ String str;
+ CHARSET_INFO *to_cs= thd->variables.collation_connection;
+ bool needs_conversion;
+ user_var_entry *entry;
+ String *var_value= &str;
+ uint32 unused, len;
+ /*
+ Convert @var contents to string in connection character set. Although
+ it is known that int/real/NULL value cannot be a valid query we still
+ convert it for error messages to be uniform.
+ */
+ if ((entry=
+ (user_var_entry*)my_hash_search(&thd->user_vars,
+ (uchar*)lex->prepared_stmt_code.str,
+ lex->prepared_stmt_code.length))
+ && entry->value)
+ {
+ bool is_var_null;
+ var_value= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC);
+ /*
+ NULL value of variable checked early as entry->value so here
+ we can't get NULL in normal conditions
+ */
+ DBUG_ASSERT(!is_var_null);
+ if (!var_value)
+ goto end;
+ }
+ else
+ {
+ /*
+ variable absent or equal to NULL, so we need to set variable to
+ something reasonable to get a readable error message during parsing
+ */
+ str.set(STRING_WITH_LEN("NULL"), &my_charset_latin1);
+ }
+
+ needs_conversion= String::needs_conversion(var_value->length(),
+ var_value->charset(), to_cs,
+ &unused);
+
+ len= (needs_conversion ? var_value->length() * to_cs->mbmaxlen :
+ var_value->length());
+ if (!(query_str= (char*) alloc_root(thd->mem_root, len+1)))
+ goto end;
+
+ if (needs_conversion)
+ {
+ uint dummy_errors;
+ len= copy_and_convert(query_str, len, to_cs, var_value->ptr(),
+ var_value->length(), var_value->charset(),
+ &dummy_errors);
+ }
+ else
+ memcpy(query_str, var_value->ptr(), var_value->length());
+ query_str[len]= '\0'; // Safety (mostly for debug)
+ *query_len= len;
+ }
+ else
+ {
+ query_str= lex->prepared_stmt_code.str;
+ *query_len= lex->prepared_stmt_code.length;
+ }
+end:
+ return query_str;
+}
+
+
+/**
+ SQLCOM_PREPARE implementation.
+
+ Prepare an SQL prepared statement. This is called from
+ mysql_execute_command and should therefore behave like an
+ ordinary query (e.g. should not reset any global THD data).
+
+ @param thd thread handle
+
+ @return
+ none: in case of success, OK packet is sent to the client,
+ otherwise an error message is set in THD
+*/
+
+void mysql_sql_stmt_prepare(THD *thd)
+{
+ LEX *lex= thd->lex;
+ LEX_STRING *name= &lex->prepared_stmt_name;
+ Prepared_statement *stmt;
+ const char *query;
+ uint query_len= 0;
+ DBUG_ENTER("mysql_sql_stmt_prepare");
+
+ if ((stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name)))
+ {
+ /*
+ If there is a statement with the same name, remove it. It is ok to
+ remove old and fail to insert a new one at the same time.
+ */
+ if (stmt->is_in_use())
+ {
+ my_error(ER_PS_NO_RECURSION, MYF(0));
+ DBUG_VOID_RETURN;
+ }
+
+ stmt->deallocate();
+ }
+
+ if (! (query= get_dynamic_sql_string(lex, &query_len)) ||
+ ! (stmt= new Prepared_statement(thd)))
+ {
+ DBUG_VOID_RETURN; /* out of memory */
+ }
+
+ stmt->set_sql_prepare();
+
+ /* Set the name first, insert should know that this statement has a name */
+ if (stmt->set_name(name))
+ {
+ delete stmt;
+ DBUG_VOID_RETURN;
+ }
+
+ if (thd->stmt_map.insert(thd, stmt))
+ {
+ /* The statement is deleted and an error is set if insert fails */
+ DBUG_VOID_RETURN;
+ }
+
+ if (stmt->prepare(query, query_len))
+ {
+ /* Statement map deletes the statement on erase */
+ thd->stmt_map.erase(stmt);
+ }
+ else
+ my_ok(thd, 0L, 0L, "Statement prepared");
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Reinit prepared statement/stored procedure before execution.
+
+ @todo
+ When the new table structure is ready, then have a status bit
+ to indicate the table is altered, and re-do the setup_*
+ and open the tables back.
+*/
+
+void reinit_stmt_before_use(THD *thd, LEX *lex)
+{
+ SELECT_LEX *sl= lex->all_selects_list;
+ DBUG_ENTER("reinit_stmt_before_use");
+
+ /*
+ We have to update "thd" pointer in LEX, all its units and in LEX::result,
+ since statements which belong to trigger body are associated with TABLE
+ object and because of this can be used in different threads.
+ */
+ lex->thd= thd;
+ DBUG_ASSERT(!lex->explain);
+
+ if (lex->empty_field_list_on_rset)
+ {
+ lex->empty_field_list_on_rset= 0;
+ lex->field_list.empty();
+ }
+ for (; sl; sl= sl->next_select_in_list())
+ {
+ if (!sl->first_execution)
+ {
+ /* remove option which was put by mysql_explain_union() */
+ sl->options&= ~SELECT_DESCRIBE;
+
+ /* see unique_table() */
+ sl->exclude_from_table_unique_test= FALSE;
+
+ /*
+ Copy WHERE, HAVING clause pointers to avoid damaging them
+ by optimisation
+ */
+ if (sl->prep_where)
+ {
+ /*
+ We need this rollback because memory allocated in
+ copy_andor_structure() will be freed
+ */
+ thd->change_item_tree((Item**)&sl->where,
+ sl->prep_where->copy_andor_structure(thd));
+ sl->where->cleanup();
+ }
+ else
+ sl->where= NULL;
+ if (sl->prep_having)
+ {
+ /*
+ We need this rollback because memory allocated in
+ copy_andor_structure() will be freed
+ */
+ thd->change_item_tree((Item**)&sl->having,
+ sl->prep_having->copy_andor_structure(thd));
+ sl->having->cleanup();
+ }
+ else
+ sl->having= NULL;
+ DBUG_ASSERT(sl->join == 0);
+ ORDER *order;
+ /* Fix GROUP list */
+ if (sl->group_list_ptrs && sl->group_list_ptrs->size() > 0)
+ {
+ for (uint ix= 0; ix < sl->group_list_ptrs->size() - 1; ++ix)
+ {
+ order= sl->group_list_ptrs->at(ix);
+ order->next= sl->group_list_ptrs->at(ix+1);
+ }
+ }
+ for (order= sl->group_list.first; order; order= order->next)
+ order->item= &order->item_ptr;
+ /* Fix ORDER list */
+ for (order= sl->order_list.first; order; order= order->next)
+ order->item= &order->item_ptr;
+ {
+#ifndef DBUG_OFF
+ bool res=
+#endif
+ sl->handle_derived(lex, DT_REINIT);
+ DBUG_ASSERT(res == 0);
+ }
+ }
+ {
+ SELECT_LEX_UNIT *unit= sl->master_unit();
+ unit->unclean();
+ unit->types.empty();
+ /* for derived tables & PS (which can't be reset by Item_subquery) */
+ unit->reinit_exec_mechanism();
+ unit->set_thd(thd);
+ }
+ }
+
+ /*
+ TODO: When the new table structure is ready, then have a status bit
+ to indicate the table is altered, and re-do the setup_*
+ and open the tables back.
+ */
+ /*
+ NOTE: We should reset whole table list here including all tables added
+ by prelocking algorithm (it is not a problem for substatements since
+ they have their own table list).
+ */
+ for (TABLE_LIST *tables= lex->query_tables;
+ tables;
+ tables= tables->next_global)
+ {
+ tables->reinit_before_use(thd);
+ }
+
+ /* Reset MDL tickets for procedures/functions */
+ for (Sroutine_hash_entry *rt=
+ (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
+ rt; rt= rt->next)
+ rt->mdl_request.ticket= NULL;
+
+ /*
+ Cleanup of the special case of DELETE t1, t2 FROM t1, t2, t3 ...
+ (multi-delete). We do a full clean up, although at the moment all we
+ need to clean in the tables of MULTI-DELETE list is 'table' member.
+ */
+ for (TABLE_LIST *tables= lex->auxiliary_table_list.first;
+ tables;
+ tables= tables->next_global)
+ {
+ tables->reinit_before_use(thd);
+ }
+ lex->current_select= &lex->select_lex;
+
+
+ if (lex->result)
+ {
+ lex->result->cleanup();
+ lex->result->set_thd(thd);
+ }
+ lex->allow_sum_func= 0;
+ lex->in_sum_func= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Clears parameters from data left from previous execution or long data.
+
+ @param stmt prepared statement for which parameters should
+ be reset
+*/
+
+static void reset_stmt_params(Prepared_statement *stmt)
+{
+ Item_param **item= stmt->param_array;
+ Item_param **end= item + stmt->param_count;
+ for (;item < end ; ++item)
+ (**item).reset();
+}
+
+
+/**
+ COM_STMT_EXECUTE handler: execute a previously prepared statement.
+
+ If there are any parameters, then replace parameter markers with the
+ data supplied from the client, and then execute the statement.
+ This function uses binary protocol to send a possible result set
+ to the client.
+
+ @param thd current thread
+ @param packet_arg parameter types and data, if any
+ @param packet_length packet length, including the terminator character.
+
+ @return
+ none: in case of success OK packet or a result set is sent to the
+ client, otherwise an error message is set in THD.
+*/
+
+void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
+{
+ uchar *packet= (uchar*)packet_arg; // GCC 4.0.1 workaround
+ ulong stmt_id= uint4korr(packet);
+ ulong flags= (ulong) packet[4];
+ /* Query text for binary, general or slow log, if any of them is open */
+ String expanded_query;
+ uchar *packet_end= packet + packet_length;
+ Prepared_statement *stmt;
+ Protocol *save_protocol= thd->protocol;
+ bool open_cursor;
+ DBUG_ENTER("mysqld_stmt_execute");
+
+ packet+= 9; /* stmt_id + 5 bytes of flags */
+
+ /* First of all clear possible warnings from the previous command */
+ mysql_reset_thd_for_next_command(thd);
+
+ if (!(stmt= find_prepared_statement(thd, stmt_id)))
+ {
+ char llbuf[22];
+ my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), static_cast<int>(sizeof(llbuf)),
+ llstr(stmt_id, llbuf), "mysqld_stmt_execute");
+ DBUG_VOID_RETURN;
+ }
+
+#if defined(ENABLED_PROFILING)
+ thd->profiling.set_query_source(stmt->query(), stmt->query_length());
+#endif
+ DBUG_PRINT("exec_query", ("%s", stmt->query()));
+ DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
+
+ open_cursor= MY_TEST(flags & (ulong) CURSOR_TYPE_READ_ONLY);
+
+ thd->protocol= &thd->protocol_binary;
+ stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end);
+ thd->protocol= save_protocol;
+
+ sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size);
+ sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size);
+
+ /* Close connection socket; for use with client testing (Bug#43560). */
+ DBUG_EXECUTE_IF("close_conn_after_stmt_execute", vio_close(thd->net.vio););
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ SQLCOM_EXECUTE implementation.
+
+ Execute prepared statement using parameter values from
+ lex->prepared_stmt_params and send result to the client using
+ text protocol. This is called from mysql_execute_command and
+ therefore should behave like an ordinary query (e.g. not change
+ global THD data, such as warning count, server status, etc).
+ This function uses text protocol to send a possible result set.
+
+ @param thd thread handle
+
+ @return
+ none: in case of success, OK (or result set) packet is sent to the
+ client, otherwise an error is set in THD
+*/
+
+void mysql_sql_stmt_execute(THD *thd)
+{
+ LEX *lex= thd->lex;
+ Prepared_statement *stmt;
+ LEX_STRING *name= &lex->prepared_stmt_name;
+ /* Query text for binary, general or slow log, if any of them is open */
+ String expanded_query;
+ DBUG_ENTER("mysql_sql_stmt_execute");
+ DBUG_PRINT("info", ("EXECUTE: %.*s\n", (int) name->length, name->str));
+
+ if (!(stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name)))
+ {
+ my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0),
+ static_cast<int>(name->length), name->str, "EXECUTE");
+ DBUG_VOID_RETURN;
+ }
+
+ if (stmt->param_count != lex->prepared_stmt_params.elements)
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE");
+ DBUG_VOID_RETURN;
+ }
+
+ DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
+
+ (void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ COM_STMT_FETCH handler: fetches requested amount of rows from cursor.
+
+ @param thd Thread handle
+ @param packet Packet from client (with stmt_id & num_rows)
+ @param packet_length Length of packet
+*/
+
+void mysqld_stmt_fetch(THD *thd, char *packet, uint packet_length)
+{
+ /* assume there is always place for 8-16 bytes */
+ ulong stmt_id= uint4korr(packet);
+ ulong num_rows= uint4korr(packet+4);
+ Prepared_statement *stmt;
+ Statement stmt_backup;
+ Server_side_cursor *cursor;
+ DBUG_ENTER("mysqld_stmt_fetch");
+
+ /* First of all clear possible warnings from the previous command */
+ mysql_reset_thd_for_next_command(thd);
+
+ status_var_increment(thd->status_var.com_stmt_fetch);
+ if (!(stmt= find_prepared_statement(thd, stmt_id)))
+ {
+ char llbuf[22];
+ my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), static_cast<int>(sizeof(llbuf)),
+ llstr(stmt_id, llbuf), "mysqld_stmt_fetch");
+ DBUG_VOID_RETURN;
+ }
+
+ cursor= stmt->cursor;
+ if (!cursor)
+ {
+ my_error(ER_STMT_HAS_NO_OPEN_CURSOR, MYF(0), stmt_id);
+ DBUG_VOID_RETURN;
+ }
+
+ thd->stmt_arena= stmt;
+ thd->set_n_backup_statement(stmt, &stmt_backup);
+
+ cursor->fetch(num_rows);
+
+ if (!cursor->is_open())
+ {
+ stmt->close_cursor();
+ reset_stmt_params(stmt);
+ }
+
+ thd->restore_backup_statement(stmt, &stmt_backup);
+ thd->stmt_arena= thd;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Reset a prepared statement in case there was a recoverable error.
+
+ This function resets statement to the state it was right after prepare.
+ It can be used to:
+ - clear an error happened during mysqld_stmt_send_long_data
+ - cancel long data stream for all placeholders without
+ having to call mysqld_stmt_execute.
+ - close an open cursor
+ Sends 'OK' packet in case of success (statement was reset)
+ or 'ERROR' packet (unrecoverable error/statement not found/etc).
+
+ @param thd Thread handle
+ @param packet Packet with stmt id
+*/
+
+void mysqld_stmt_reset(THD *thd, char *packet)
+{
+ /* There is always space for 4 bytes in buffer */
+ ulong stmt_id= uint4korr(packet);
+ Prepared_statement *stmt;
+ DBUG_ENTER("mysqld_stmt_reset");
+
+ /* First of all clear possible warnings from the previous command */
+ mysql_reset_thd_for_next_command(thd);
+
+ status_var_increment(thd->status_var.com_stmt_reset);
+ if (!(stmt= find_prepared_statement(thd, stmt_id)))
+ {
+ char llbuf[22];
+ my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), static_cast<int>(sizeof(llbuf)),
+ llstr(stmt_id, llbuf), "mysqld_stmt_reset");
+ DBUG_VOID_RETURN;
+ }
+
+ stmt->close_cursor();
+
+ /*
+ Clear parameters from data which could be set by
+ mysqld_stmt_send_long_data() call.
+ */
+ reset_stmt_params(stmt);
+
+ stmt->state= Query_arena::STMT_PREPARED;
+
+ general_log_print(thd, thd->get_command(), NullS);
+
+ my_ok(thd);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Delete a prepared statement from memory.
+
+ @note
+ we don't send any reply to this command.
+*/
+
+void mysqld_stmt_close(THD *thd, char *packet)
+{
+ /* There is always space for 4 bytes in packet buffer */
+ ulong stmt_id= uint4korr(packet);
+ Prepared_statement *stmt;
+ DBUG_ENTER("mysqld_stmt_close");
+
+ thd->get_stmt_da()->disable_status();
+
+ if (!(stmt= find_prepared_statement(thd, stmt_id)))
+ DBUG_VOID_RETURN;
+
+ /*
+ The only way currently a statement can be deallocated when it's
+ in use is from within Dynamic SQL.
+ */
+ DBUG_ASSERT(! stmt->is_in_use());
+ stmt->deallocate();
+ general_log_print(thd, thd->get_command(), NullS);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ SQLCOM_DEALLOCATE implementation.
+
+ Close an SQL prepared statement. As this can be called from Dynamic
+ SQL, we should be careful to not close a statement that is currently
+ being executed.
+
+ @return
+ none: OK packet is sent in case of success, otherwise an error
+ message is set in THD
+*/
+
+void mysql_sql_stmt_close(THD *thd)
+{
+ Prepared_statement* stmt;
+ LEX_STRING *name= &thd->lex->prepared_stmt_name;
+ DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n", (int) name->length,
+ name->str));
+
+ if (! (stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name)))
+ my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0),
+ static_cast<int>(name->length), name->str, "DEALLOCATE PREPARE");
+ else if (stmt->is_in_use())
+ my_error(ER_PS_NO_RECURSION, MYF(0));
+ else
+ {
+ stmt->deallocate();
+ my_ok(thd);
+ }
+}
+
+
+/**
+ Handle long data in pieces from client.
+
+ Get a part of a long data. To make the protocol efficient, we are
+ not sending any return packets here. If something goes wrong, then
+ we will send the error on 'execute' We assume that the client takes
+ care of checking that all parts are sent to the server. (No checking
+ that we get a 'end of column' in the server is performed).
+
+ @param thd Thread handle
+ @param packet String to append
+ @param packet_length Length of string (including end \\0)
+*/
+
+void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length)
+{
+ ulong stmt_id;
+ uint param_number;
+ Prepared_statement *stmt;
+ Item_param *param;
+#ifndef EMBEDDED_LIBRARY
+ char *packet_end= packet + packet_length;
+#endif
+ DBUG_ENTER("mysql_stmt_get_longdata");
+
+ status_var_increment(thd->status_var.com_stmt_send_long_data);
+
+ thd->get_stmt_da()->disable_status();
+#ifndef EMBEDDED_LIBRARY
+ /* Minimal size of long data packet is 6 bytes */
+ if (packet_length < MYSQL_LONG_DATA_HEADER)
+ DBUG_VOID_RETURN;
+#endif
+
+ stmt_id= uint4korr(packet);
+ packet+= 4;
+
+ if (!(stmt=find_prepared_statement(thd, stmt_id)))
+ DBUG_VOID_RETURN;
+
+ param_number= uint2korr(packet);
+ packet+= 2;
+#ifndef EMBEDDED_LIBRARY
+ if (param_number >= stmt->param_count)
+ {
+ /* Error will be sent in execute call */
+ stmt->state= Query_arena::STMT_ERROR;
+ stmt->last_errno= ER_WRONG_ARGUMENTS;
+ sprintf(stmt->last_error, ER(ER_WRONG_ARGUMENTS),
+ "mysqld_stmt_send_long_data");
+ DBUG_VOID_RETURN;
+ }
+#endif
+
+ param= stmt->param_array[param_number];
+
+ Diagnostics_area new_stmt_da(thd->query_id, false, true);
+ Diagnostics_area *save_stmt_da= thd->get_stmt_da();
+
+ thd->set_stmt_da(&new_stmt_da);
+
+#ifndef EMBEDDED_LIBRARY
+ param->set_longdata(packet, (ulong) (packet_end - packet));
+#else
+ param->set_longdata(thd->extra_data, thd->extra_length);
+#endif
+ if (thd->get_stmt_da()->is_error())
+ {
+ stmt->state= Query_arena::STMT_ERROR;
+ stmt->last_errno= thd->get_stmt_da()->sql_errno();
+ strncpy(stmt->last_error, thd->get_stmt_da()->message(), MYSQL_ERRMSG_SIZE);
+ }
+ thd->set_stmt_da(save_stmt_da);
+
+ general_log_print(thd, thd->get_command(), NullS);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/***************************************************************************
+ Select_fetch_protocol_binary
+****************************************************************************/
+
+Select_fetch_protocol_binary::Select_fetch_protocol_binary(THD *thd_arg)
+ :protocol(thd_arg)
+{}
+
+bool Select_fetch_protocol_binary::send_result_set_metadata(List<Item> &list, uint flags)
+{
+ bool rc;
+ Protocol *save_protocol= thd->protocol;
+
+ /*
+ Protocol::send_result_set_metadata caches the information about column types:
+ this information is later used to send data. Therefore, the same
+ dedicated Protocol object must be used for all operations with
+ a cursor.
+ */
+ thd->protocol= &protocol;
+ rc= select_send::send_result_set_metadata(list, flags);
+ thd->protocol= save_protocol;
+
+ return rc;
+}
+
+bool Select_fetch_protocol_binary::send_eof()
+{
+ /*
+ Don't send EOF if we're in error condition (which implies we've already
+ sent or are sending an error)
+ */
+ if (thd->is_error())
+ return true;
+
+ ::my_eof(thd);
+ return false;
+}
+
+
+int
+Select_fetch_protocol_binary::send_data(List<Item> &fields)
+{
+ Protocol *save_protocol= thd->protocol;
+ int rc;
+
+ thd->protocol= &protocol;
+ rc= select_send::send_data(fields);
+ thd->protocol= save_protocol;
+ return rc;
+}
+
+/*******************************************************************
+* Reprepare_observer
+*******************************************************************/
+/** Push an error to the error stack and return TRUE for now. */
+
+bool
+Reprepare_observer::report_error(THD *thd)
+{
+ /*
+ This 'error' is purely internal to the server:
+ - No exception handler is invoked,
+ - No condition is added in the condition area (warn_list).
+ The diagnostics area is set to an error status to enforce
+ that this thread execution stops and returns to the caller,
+ backtracking all the way to Prepared_statement::execute_loop().
+ */
+ thd->get_stmt_da()->set_error_status(ER_NEED_REPREPARE);
+ m_invalidated= TRUE;
+
+ return TRUE;
+}
+
+
+/*******************************************************************
+* Server_runnable
+*******************************************************************/
+
+Server_runnable::~Server_runnable()
+{
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+Execute_sql_statement::
+Execute_sql_statement(LEX_STRING sql_text)
+ :m_sql_text(sql_text)
+{}
+
+
+/**
+ Parse and execute a statement. Does not prepare the query.
+
+ Allows to execute a statement from within another statement.
+ The main property of the implementation is that it does not
+ affect the environment -- i.e. you can run many
+ executions without having to cleanup/reset THD in between.
+*/
+
+bool
+Execute_sql_statement::execute_server_code(THD *thd)
+{
+ PSI_statement_locker *parent_locker;
+ bool error;
+
+ if (alloc_query(thd, m_sql_text.str, m_sql_text.length))
+ return TRUE;
+
+ Parser_state parser_state;
+ if (parser_state.init(thd, thd->query(), thd->query_length()))
+ return TRUE;
+
+ parser_state.m_lip.multi_statements= FALSE;
+ lex_start(thd);
+
+ error= parse_sql(thd, &parser_state, NULL) || thd->is_error();
+
+ if (error)
+ goto end;
+
+ thd->lex->set_trg_event_type_for_tables();
+
+ parent_locker= thd->m_statement_psi;
+ thd->m_statement_psi= NULL;
+ error= mysql_execute_command(thd);
+ thd->m_statement_psi= parent_locker;
+
+ /* report error issued during command execution */
+ if (error == 0 && thd->spcont == NULL)
+ general_log_write(thd, COM_STMT_EXECUTE,
+ thd->query(), thd->query_length());
+
+end:
+ lex_end(thd->lex);
+
+ return error;
+}
+
+/***************************************************************************
+ Prepared_statement
+****************************************************************************/
+
+Prepared_statement::Prepared_statement(THD *thd_arg)
+ :Statement(NULL, &main_mem_root,
+ STMT_INITIALIZED, ++thd_arg->statement_id_counter),
+ thd(thd_arg),
+ result(thd_arg),
+ param_array(0),
+ cursor(0),
+ param_count(0),
+ last_errno(0),
+ flags((uint) IS_IN_USE)
+{
+ init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
+ thd_arg->variables.query_prealloc_size, MYF(MY_THREAD_SPECIFIC));
+ *last_error= '\0';
+}
+
+
+void Prepared_statement::setup_set_params()
+{
+ /*
+ Note: BUG#25843 applies here too (query cache lookup uses thd->db, not
+ db from "prepare" time).
+ */
+ if (query_cache_maybe_disabled(thd)) // we won't expand the query
+ lex->safe_to_cache_query= FALSE; // so don't cache it at Execution
+
+ /*
+ Decide if we have to expand the query (because we must write it to logs or
+ because we want to look it up in the query cache) or not.
+ */
+ if ((mysql_bin_log.is_open() && is_update_query(lex->sql_command)) ||
+ opt_log || opt_slow_log ||
+ query_cache_is_cacheable_query(lex))
+ {
+ set_params_from_vars= insert_params_from_vars_with_log;
+#ifndef EMBEDDED_LIBRARY
+ set_params= insert_params_with_log;
+#else
+ set_params_data= emb_insert_params_with_log;
+#endif
+ }
+ else
+ {
+ set_params_from_vars= insert_params_from_vars;
+#ifndef EMBEDDED_LIBRARY
+ set_params= insert_params;
+#else
+ set_params_data= emb_insert_params;
+#endif
+ }
+}
+
+
+/**
+ Destroy this prepared statement, cleaning up all used memory
+ and resources.
+
+ This is called from ::deallocate() to handle COM_STMT_CLOSE and
+ DEALLOCATE PREPARE or when THD ends and all prepared statements are freed.
+*/
+
+Prepared_statement::~Prepared_statement()
+{
+ DBUG_ENTER("Prepared_statement::~Prepared_statement");
+ DBUG_PRINT("enter",("stmt: 0x%lx cursor: 0x%lx",
+ (long) this, (long) cursor));
+ delete cursor;
+ /*
+ We have to call free on the items even if cleanup is called as some items,
+ like Item_param, don't free everything until free_items()
+ */
+ free_items();
+ if (lex)
+ {
+ delete lex->result;
+ delete (st_lex_local *) lex;
+ }
+ free_root(&main_mem_root, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+
+Query_arena::Type Prepared_statement::type() const
+{
+ return PREPARED_STATEMENT;
+}
+
+
+void Prepared_statement::cleanup_stmt()
+{
+ DBUG_ENTER("Prepared_statement::cleanup_stmt");
+ DBUG_PRINT("enter",("stmt: 0x%lx", (long) this));
+
+ cleanup_items(free_list);
+ thd->cleanup_after_query();
+ thd->rollback_item_tree_changes();
+
+ DBUG_VOID_RETURN;
+}
+
+
+bool Prepared_statement::set_name(LEX_STRING *name_arg)
+{
+ name.length= name_arg->length;
+ name.str= (char*) memdup_root(mem_root, name_arg->str, name_arg->length);
+ return name.str == 0;
+}
+
+
+/**
+ Remember the current database.
+
+ We must reset/restore the current database during execution of
+ a prepared statement since it affects execution environment:
+ privileges, @@character_set_database, and other.
+
+ @return Returns an error if out of memory.
+*/
+
+bool
+Prepared_statement::set_db(const char *db_arg, uint db_length_arg)
+{
+ /* Remember the current database. */
+ if (db_arg && db_length_arg)
+ {
+ db= this->strmake(db_arg, db_length_arg);
+ db_length= db_length_arg;
+ }
+ else
+ {
+ db= NULL;
+ db_length= 0;
+ }
+ return db_arg != NULL && db == NULL;
+}
+
+/**************************************************************************
+ Common parts of mysql_[sql]_stmt_prepare, mysql_[sql]_stmt_execute.
+ Essentially, these functions do all the magic of preparing/executing
+ a statement, leaving network communication, input data handling and
+ global THD state management to the caller.
+***************************************************************************/
+
+/**
+ Parse statement text, validate the statement, and prepare it for execution.
+
+ You should not change global THD state in this function, if at all
+ possible: it may be called from any context, e.g. when executing
+ a COM_* command, and SQLCOM_* command, or a stored procedure.
+
+ @param packet statement text
+ @param packet_len
+
+ @note
+ Precondition:
+ The caller must ensure that thd->change_list and thd->free_list
+ is empty: this function will not back them up but will free
+ in the end of its execution.
+
+ @note
+ Postcondition:
+ thd->mem_root contains unused memory allocated during validation.
+*/
+
+bool Prepared_statement::prepare(const char *packet, uint packet_len)
+{
+ bool error;
+ Statement stmt_backup;
+ Query_arena *old_stmt_arena;
+ DBUG_ENTER("Prepared_statement::prepare");
+ /*
+ If this is an SQLCOM_PREPARE, we also increase Com_prepare_sql.
+ However, it seems handy if com_stmt_prepare is increased always,
+ no matter what kind of prepare is processed.
+ */
+ status_var_increment(thd->status_var.com_stmt_prepare);
+
+ if (! (lex= new (mem_root) st_lex_local))
+ DBUG_RETURN(TRUE);
+
+ if (set_db(thd->db, thd->db_length))
+ DBUG_RETURN(TRUE);
+
+ /*
+ alloc_query() uses thd->memroot && thd->query, so we should call
+ both of backup_statement() and backup_query_arena() here.
+ */
+ thd->set_n_backup_statement(this, &stmt_backup);
+ thd->set_n_backup_active_arena(this, &stmt_backup);
+
+ if (alloc_query(thd, packet, packet_len))
+ {
+ thd->restore_backup_statement(this, &stmt_backup);
+ thd->restore_active_arena(this, &stmt_backup);
+ DBUG_RETURN(TRUE);
+ }
+
+ old_stmt_arena= thd->stmt_arena;
+ thd->stmt_arena= this;
+
+ Parser_state parser_state;
+ if (parser_state.init(thd, thd->query(), thd->query_length()))
+ {
+ thd->restore_backup_statement(this, &stmt_backup);
+ thd->restore_active_arena(this, &stmt_backup);
+ thd->stmt_arena= old_stmt_arena;
+ DBUG_RETURN(TRUE);
+ }
+
+ parser_state.m_lip.stmt_prepare_mode= TRUE;
+ parser_state.m_lip.multi_statements= FALSE;
+
+ lex_start(thd);
+ lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_PREPARE;
+
+ error= parse_sql(thd, & parser_state, NULL) ||
+ thd->is_error() ||
+ init_param_array(this);
+
+ lex->set_trg_event_type_for_tables();
+
+ /*
+ While doing context analysis of the query (in check_prepared_statement)
+ we allocate a lot of additional memory: for open tables, JOINs, derived
+ tables, etc. Let's save a snapshot of current parse tree to the
+ statement and restore original THD. In cases when some tree
+ transformation can be reused on execute, we set again thd->mem_root from
+ stmt->mem_root (see setup_wild for one place where we do that).
+ */
+ thd->restore_active_arena(this, &stmt_backup);
+
+ /*
+ If called from a stored procedure, ensure that we won't rollback
+ external changes when cleaning up after validation.
+ */
+ DBUG_ASSERT(thd->change_list.is_empty());
+
+ /*
+ Marker used to release metadata locks acquired while the prepared
+ statement is being checked.
+ */
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+
+ /*
+ The only case where we should have items in the thd->free_list is
+ after stmt->set_params_from_vars(), which may in some cases create
+ Item_null objects.
+ */
+
+ if (error == 0)
+ error= check_prepared_statement(this);
+
+ /*
+ Currently CREATE PROCEDURE/TRIGGER/EVENT are prohibited in prepared
+ statements: ensure we have no memory leak here if by someone tries
+ to PREPARE stmt FROM "CREATE PROCEDURE ..."
+ */
+ DBUG_ASSERT(lex->sphead == NULL || error != 0);
+ /* The order is important */
+ lex->unit.cleanup();
+
+ /* No need to commit statement transaction, it's not started. */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+
+ close_thread_tables(thd);
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+ /*
+ Transaction rollback was requested since MDL deadlock was discovered
+ while trying to open tables. Rollback transaction in all storage
+ engines including binary log and release all locks.
+
+ Once dynamic SQL is allowed as substatements the below if-statement
+ has to be adjusted to not do rollback in substatement.
+ */
+ DBUG_ASSERT(! thd->in_sub_stmt);
+ if (thd->transaction_rollback_request)
+ {
+ trans_rollback_implicit(thd);
+ thd->mdl_context.release_transactional_locks();
+ }
+
+ lex_end(lex);
+ cleanup_stmt();
+ thd->restore_backup_statement(this, &stmt_backup);
+ thd->stmt_arena= old_stmt_arena;
+
+ if (error == 0)
+ {
+ setup_set_params();
+ lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_PREPARE;
+ state= Query_arena::STMT_PREPARED;
+ flags&= ~ (uint) IS_IN_USE;
+
+ /*
+ Log COM_EXECUTE to the general log. Note, that in case of SQL
+ prepared statements this causes two records to be output:
+
+ Query PREPARE stmt from @user_variable
+ Prepare <statement SQL text>
+
+ This is considered user-friendly, since in the
+ second log entry we output the actual statement text.
+
+ Do not print anything if this is an SQL prepared statement and
+ we're inside a stored procedure (also called Dynamic SQL) --
+ sub-statements inside stored procedures are not logged into
+ the general log.
+ */
+ if (thd->spcont == NULL)
+ general_log_write(thd, COM_STMT_PREPARE, query(), query_length());
+ }
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Assign parameter values either from variables, in case of SQL PS
+ or from the execute packet.
+
+ @param expanded_query a container with the original SQL statement.
+ '?' placeholders will be replaced with
+ their values in case of success.
+ The result is used for logging and replication
+ @param packet pointer to execute packet.
+ NULL in case of SQL PS
+ @param packet_end end of the packet. NULL in case of SQL PS
+
+ @todo Use a paremeter source class family instead of 'if's, and
+ support stored procedure variables.
+
+ @retval TRUE an error occurred when assigning a parameter (likely
+ a conversion error or out of memory, or malformed packet)
+ @retval FALSE success
+*/
+
+bool
+Prepared_statement::set_parameters(String *expanded_query,
+ uchar *packet, uchar *packet_end)
+{
+ bool is_sql_ps= packet == NULL;
+ bool res= FALSE;
+
+ if (is_sql_ps)
+ {
+ /* SQL prepared statement */
+ res= set_params_from_vars(this, thd->lex->prepared_stmt_params,
+ expanded_query);
+ }
+ else if (param_count)
+ {
+#ifndef EMBEDDED_LIBRARY
+ uchar *null_array= packet;
+ res= (setup_conversion_functions(this, &packet, packet_end) ||
+ set_params(this, null_array, packet, packet_end, expanded_query));
+#else
+ /*
+ In embedded library we re-install conversion routines each time
+ we set parameters, and also we don't need to parse packet.
+ So we do it in one function.
+ */
+ res= set_params_data(this, expanded_query);
+#endif
+ }
+ if (res)
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0),
+ is_sql_ps ? "EXECUTE" : "mysqld_stmt_execute");
+ reset_stmt_params(this);
+ }
+ return res;
+}
+
+
+/**
+ Execute a prepared statement. Re-prepare it a limited number
+ of times if necessary.
+
+ Try to execute a prepared statement. If there is a metadata
+ validation error, prepare a new copy of the prepared statement,
+ swap the old and the new statements, and try again.
+ If there is a validation error again, repeat the above, but
+ perform no more than MAX_REPREPARE_ATTEMPTS.
+
+ @note We have to try several times in a loop since we
+ release metadata locks on tables after prepared statement
+ prepare. Therefore, a DDL statement may sneak in between prepare
+ and execute of a new statement. If this happens repeatedly
+ more than MAX_REPREPARE_ATTEMPTS times, we give up.
+
+ @return TRUE if an error, FALSE if success
+ @retval TRUE either MAX_REPREPARE_ATTEMPTS has been reached,
+ or some general error
+ @retval FALSE successfully executed the statement, perhaps
+ after having reprepared it a few times.
+*/
+
+bool
+Prepared_statement::execute_loop(String *expanded_query,
+ bool open_cursor,
+ uchar *packet,
+ uchar *packet_end)
+{
+ const int MAX_REPREPARE_ATTEMPTS= 3;
+ Reprepare_observer reprepare_observer;
+ bool error;
+ int reprepare_attempt= 0;
+
+ /* Check if we got an error when sending long data */
+ if (state == Query_arena::STMT_ERROR)
+ {
+ my_message(last_errno, last_error, MYF(0));
+ return TRUE;
+ }
+
+ if (set_parameters(expanded_query, packet, packet_end))
+ return TRUE;
+
+#ifdef NOT_YET_FROM_MYSQL_5_6
+ if (unlikely(thd->security_ctx->password_expired &&
+ !lex->is_change_password))
+ {
+ my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
+ return true;
+ }
+#endif
+
+reexecute:
+ /*
+ If the free_list is not empty, we'll wrongly free some externally
+ allocated items when cleaning up after validation of the prepared
+ statement.
+ */
+ DBUG_ASSERT(thd->free_list == NULL);
+
+ /*
+ Install the metadata observer. If some metadata version is
+ different from prepare time and an observer is installed,
+ the observer method will be invoked to push an error into
+ the error stack.
+ */
+
+ if (sql_command_flags[lex->sql_command] & CF_REEXECUTION_FRAGILE)
+ {
+ reprepare_observer.reset_reprepare_observer();
+ DBUG_ASSERT(thd->m_reprepare_observer == NULL);
+ thd->m_reprepare_observer= &reprepare_observer;
+ }
+
+ error= execute(expanded_query, open_cursor) || thd->is_error();
+
+ thd->m_reprepare_observer= NULL;
+
+ if ((sql_command_flags[lex->sql_command] & CF_REEXECUTION_FRAGILE) &&
+ error && !thd->is_fatal_error && !thd->killed &&
+ reprepare_observer.is_invalidated() &&
+ reprepare_attempt++ < MAX_REPREPARE_ATTEMPTS)
+ {
+ DBUG_ASSERT(thd->get_stmt_da()->sql_errno() == ER_NEED_REPREPARE);
+ thd->clear_error();
+
+ error= reprepare();
+
+ if (! error) /* Success */
+ goto reexecute;
+ }
+ reset_stmt_params(this);
+
+ return error;
+}
+
+
+bool
+Prepared_statement::execute_server_runnable(Server_runnable *server_runnable)
+{
+ Statement stmt_backup;
+ bool error;
+ Query_arena *save_stmt_arena= thd->stmt_arena;
+ Item_change_list save_change_list;
+ thd->change_list.move_elements_to(&save_change_list);
+
+ state= STMT_CONVENTIONAL_EXECUTION;
+
+ if (!(lex= new (mem_root) st_lex_local))
+ return TRUE;
+
+ thd->set_n_backup_statement(this, &stmt_backup);
+ thd->set_n_backup_active_arena(this, &stmt_backup);
+ thd->stmt_arena= this;
+
+ error= server_runnable->execute_server_code(thd);
+
+ thd->cleanup_after_query();
+
+ thd->restore_active_arena(this, &stmt_backup);
+ thd->restore_backup_statement(this, &stmt_backup);
+ thd->stmt_arena= save_stmt_arena;
+
+ save_change_list.move_elements_to(&thd->change_list);
+
+ /* Items and memory will freed in destructor */
+
+ return error;
+}
+
+
+/**
+ Reprepare this prepared statement.
+
+ Currently this is implemented by creating a new prepared
+ statement, preparing it with the original query and then
+ swapping the new statement and the original one.
+
+ @retval TRUE an error occurred. Possible errors include
+ incompatibility of new and old result set
+ metadata
+ @retval FALSE success, the statement has been reprepared
+*/
+
+bool
+Prepared_statement::reprepare()
+{
+ char saved_cur_db_name_buf[SAFE_NAME_LEN+1];
+ LEX_STRING saved_cur_db_name=
+ { saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
+ LEX_STRING stmt_db_name= { db, db_length };
+ bool cur_db_changed;
+ bool error;
+
+ Prepared_statement copy(thd);
+
+ copy.set_sql_prepare(); /* To suppress sending metadata to the client. */
+
+ status_var_increment(thd->status_var.com_stmt_reprepare);
+
+ if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE,
+ &cur_db_changed))
+ return TRUE;
+
+ error= ((name.str && copy.set_name(&name)) ||
+ copy.prepare(query(), query_length()) ||
+ validate_metadata(&copy));
+
+ if (cur_db_changed)
+ mysql_change_db(thd, &saved_cur_db_name, TRUE);
+
+ if (! error)
+ {
+ swap_prepared_statement(&copy);
+ swap_parameter_array(param_array, copy.param_array, param_count);
+#ifndef DBUG_OFF
+ is_reprepared= TRUE;
+#endif
+ /*
+ Clear possible warnings during reprepare, it has to be completely
+ transparent to the user. We use clear_warning_info() since
+ there were no separate query id issued for re-prepare.
+ Sic: we can't simply silence warnings during reprepare, because if
+ it's failed, we need to return all the warnings to the user.
+ */
+ thd->get_stmt_da()->clear_warning_info(thd->query_id);
+ }
+ return error;
+}
+
+
+/**
+ Validate statement result set metadata (if the statement returns
+ a result set).
+
+ Currently we only check that the number of columns of the result
+ set did not change.
+ This is a helper method used during re-prepare.
+
+ @param[in] copy the re-prepared prepared statement to verify
+ the metadata of
+
+ @retval TRUE error, ER_PS_REBIND is reported
+ @retval FALSE statement return no or compatible metadata
+*/
+
+
+bool Prepared_statement::validate_metadata(Prepared_statement *copy)
+{
+ /**
+ If this is an SQL prepared statement or EXPLAIN,
+ return FALSE -- the metadata of the original SELECT,
+ if any, has not been sent to the client.
+ */
+ if (is_sql_prepare() || lex->describe)
+ return FALSE;
+
+ if (lex->select_lex.item_list.elements !=
+ copy->lex->select_lex.item_list.elements)
+ {
+ /** Column counts mismatch, update the client */
+ thd->server_status|= SERVER_STATUS_METADATA_CHANGED;
+ }
+
+ return FALSE;
+}
+
+
+/**
+ Replace the original prepared statement with a prepared copy.
+
+ This is a private helper that is used as part of statement
+ reprepare
+
+ @return This function does not return any errors.
+*/
+
+void
+Prepared_statement::swap_prepared_statement(Prepared_statement *copy)
+{
+ Statement tmp_stmt;
+
+ /* Swap memory roots. */
+ swap_variables(MEM_ROOT, main_mem_root, copy->main_mem_root);
+
+ /* Swap the arenas */
+ tmp_stmt.set_query_arena(this);
+ set_query_arena(copy);
+ copy->set_query_arena(&tmp_stmt);
+
+ /* Swap the statement parent classes */
+ tmp_stmt.set_statement(this);
+ set_statement(copy);
+ copy->set_statement(&tmp_stmt);
+
+ /* Swap ids back, we need the original id */
+ swap_variables(ulong, id, copy->id);
+ /* Swap mem_roots back, they must continue pointing at the main_mem_roots */
+ swap_variables(MEM_ROOT *, mem_root, copy->mem_root);
+ /*
+ Swap the old and the new parameters array. The old array
+ is allocated in the old arena.
+ */
+ swap_variables(Item_param **, param_array, copy->param_array);
+ /* Don't swap flags: the copy has IS_SQL_PREPARE always set. */
+ /* swap_variables(uint, flags, copy->flags); */
+ /* Swap names, the old name is allocated in the wrong memory root */
+ swap_variables(LEX_STRING, name, copy->name);
+ /* Ditto */
+ swap_variables(char *, db, copy->db);
+
+ DBUG_ASSERT(db_length == copy->db_length);
+ DBUG_ASSERT(param_count == copy->param_count);
+ DBUG_ASSERT(thd == copy->thd);
+ last_error[0]= '\0';
+ last_errno= 0;
+}
+
+
+/**
+ Execute a prepared statement.
+
+ You should not change global THD state in this function, if at all
+ possible: it may be called from any context, e.g. when executing
+ a COM_* command, and SQLCOM_* command, or a stored procedure.
+
+ @param expanded_query A query for binlogging which has all parameter
+ markers ('?') replaced with their actual values.
+ @param open_cursor True if an attempt to open a cursor should be made.
+ Currenlty used only in the binary protocol.
+
+ @note
+ Preconditions, postconditions.
+ - See the comment for Prepared_statement::prepare().
+
+ @retval
+ FALSE ok
+ @retval
+ TRUE Error
+*/
+
+bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
+{
+ Statement stmt_backup;
+ Query_arena *old_stmt_arena;
+ bool error= TRUE;
+
+ char saved_cur_db_name_buf[SAFE_NAME_LEN+1];
+ LEX_STRING saved_cur_db_name=
+ { saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
+ bool cur_db_changed;
+
+ LEX_STRING stmt_db_name= { db, db_length };
+
+ status_var_increment(thd->status_var.com_stmt_execute);
+
+ if (flags & (uint) IS_IN_USE)
+ {
+ my_error(ER_PS_NO_RECURSION, MYF(0));
+ return TRUE;
+ }
+
+ /*
+ For SHOW VARIABLES lex->result is NULL, as it's a non-SELECT
+ command. For such queries we don't return an error and don't
+ open a cursor -- the client library will recognize this case and
+ materialize the result set.
+ For SELECT statements lex->result is created in
+ check_prepared_statement. lex->result->simple_select() is FALSE
+ in INSERT ... SELECT and similar commands.
+ */
+
+ if (open_cursor && lex->result && lex->result->check_simple_select())
+ {
+ DBUG_PRINT("info",("Cursor asked for not SELECT stmt"));
+ return TRUE;
+ }
+
+ /* In case the command has a call to SP which re-uses this statement name */
+ flags|= IS_IN_USE;
+
+ close_cursor();
+
+ /*
+ If the free_list is not empty, we'll wrongly free some externally
+ allocated items when cleaning up after execution of this statement.
+ */
+ DBUG_ASSERT(thd->change_list.is_empty());
+
+ /*
+ The only case where we should have items in the thd->free_list is
+ after stmt->set_params_from_vars(), which may in some cases create
+ Item_null objects.
+ */
+
+ thd->set_n_backup_statement(this, &stmt_backup);
+
+ /*
+ Change the current database (if needed).
+
+ Force switching, because the database of the prepared statement may be
+ NULL (prepared statements can be created while no current database
+ selected).
+ */
+
+ if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE,
+ &cur_db_changed))
+ goto error;
+
+ /* Allocate query. */
+
+ if (expanded_query->length() &&
+ alloc_query(thd, (char*) expanded_query->ptr(),
+ expanded_query->length()))
+ {
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), expanded_query->length());
+ goto error;
+ }
+ /*
+ Expanded query is needed for slow logging, so we want thd->query
+ to point at it even after we restore from backup. This is ok, as
+ expanded query was allocated in thd->mem_root.
+ */
+ stmt_backup.set_query_inner(thd->query_string);
+
+ /*
+ At first execution of prepared statement we may perform logical
+ transformations of the query tree. Such changes should be performed
+ on the parse tree of current prepared statement and new items should
+ be allocated in its memory root. Set the appropriate pointer in THD
+ to the arena of the statement.
+ */
+ old_stmt_arena= thd->stmt_arena;
+ thd->stmt_arena= this;
+ reinit_stmt_before_use(thd, lex);
+
+ /* Go! */
+
+ if (open_cursor)
+ error= mysql_open_cursor(thd, &result, &cursor);
+ else
+ {
+ /*
+ Try to find it in the query cache, if not, execute it.
+ Note that multi-statements cannot exist here (they are not supported in
+ prepared statements).
+ */
+ if (query_cache_send_result_to_client(thd, thd->query(),
+ thd->query_length()) <= 0)
+ {
+ PSI_statement_locker *parent_locker;
+ MYSQL_QUERY_EXEC_START(thd->query(),
+ thd->thread_id,
+ (char *) (thd->db ? thd->db : ""),
+ &thd->security_ctx->priv_user[0],
+ (char *) thd->security_ctx->host_or_ip,
+ 1);
+ parent_locker= thd->m_statement_psi;
+ thd->m_statement_psi= NULL;
+ error= mysql_execute_command(thd);
+ thd->m_statement_psi= parent_locker;
+ MYSQL_QUERY_EXEC_DONE(error);
+ }
+ else
+ {
+ thd->lex->sql_command= SQLCOM_SELECT;
+ status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]);
+ thd->update_stats();
+ }
+ }
+
+ /*
+ Restore the current database (if changed).
+
+ Force switching back to the saved current database (if changed),
+ because it may be NULL. In this case, mysql_change_db() would generate
+ an error.
+ */
+
+ if (cur_db_changed)
+ mysql_change_db(thd, &saved_cur_db_name, TRUE);
+
+ /* Assert that if an error, no cursor is open */
+ DBUG_ASSERT(! (error && cursor));
+
+ if (! cursor)
+ cleanup_stmt();
+
+ /*
+ EXECUTE command has its own dummy "explain data". We don't need it,
+ instead, we want to keep the query plan of the statement that was
+ executed.
+ */
+ if (!stmt_backup.lex->explain ||
+ !stmt_backup.lex->explain->have_query_plan())
+ {
+ delete_explain_query(stmt_backup.lex);
+ stmt_backup.lex->explain = thd->lex->explain;
+ thd->lex->explain= NULL;
+ }
+ else
+ delete_explain_query(thd->lex);
+
+ thd->set_statement(&stmt_backup);
+ thd->stmt_arena= old_stmt_arena;
+
+ if (state == Query_arena::STMT_PREPARED)
+ state= Query_arena::STMT_EXECUTED;
+
+ if (error == 0 && this->lex->sql_command == SQLCOM_CALL)
+ {
+ if (is_sql_prepare())
+ thd->protocol_text.send_out_parameters(&this->lex->param_list);
+ else
+ thd->protocol->send_out_parameters(&this->lex->param_list);
+ }
+
+ /*
+ Log COM_EXECUTE to the general log. Note, that in case of SQL
+ prepared statements this causes two records to be output:
+
+ Query EXECUTE <statement name>
+ Execute <statement SQL text>
+
+ This is considered user-friendly, since in the
+ second log entry we output values of parameter markers.
+
+ Do not print anything if this is an SQL prepared statement and
+ we're inside a stored procedure (also called Dynamic SQL) --
+ sub-statements inside stored procedures are not logged into
+ the general log.
+ */
+ if (error == 0 && thd->spcont == NULL)
+ general_log_write(thd, COM_STMT_EXECUTE, thd->query(), thd->query_length());
+
+error:
+ flags&= ~ (uint) IS_IN_USE;
+ return error;
+}
+
+
+/** Common part of DEALLOCATE PREPARE and mysqld_stmt_close. */
+
+void Prepared_statement::deallocate()
+{
+ /* We account deallocate in the same manner as mysqld_stmt_close */
+ status_var_increment(thd->status_var.com_stmt_close);
+ /* Statement map calls delete stmt on erase */
+ thd->stmt_map.erase(this);
+}
+
+
+/***************************************************************************
+* Ed_result_set
+***************************************************************************/
+/**
+ Use operator delete to free memory of Ed_result_set.
+ Accessing members of a class after the class has been destroyed
+ is a violation of the C++ standard but is commonly used in the
+ server code.
+*/
+
+void Ed_result_set::operator delete(void *ptr, size_t size) throw ()
+{
+ if (ptr)
+ {
+ /*
+ Make a stack copy, otherwise free_root() will attempt to
+ write to freed memory.
+ */
+ MEM_ROOT own_root= ((Ed_result_set*) ptr)->m_mem_root;
+ free_root(&own_root, MYF(0));
+ }
+}
+
+
+/**
+ Initialize an instance of Ed_result_set.
+
+ Instances of the class, as well as all result set rows, are
+ always allocated in the memory root passed over as the second
+ argument. In the constructor, we take over ownership of the
+ memory root. It will be freed when the class is destroyed.
+
+ sic: Ed_result_est is not designed to be allocated on stack.
+*/
+
+Ed_result_set::Ed_result_set(List<Ed_row> *rows_arg,
+ size_t column_count_arg,
+ MEM_ROOT *mem_root_arg)
+ :m_mem_root(*mem_root_arg),
+ m_column_count(column_count_arg),
+ m_rows(rows_arg),
+ m_next_rset(NULL)
+{
+ /* Take over responsibility for the memory */
+ clear_alloc_root(mem_root_arg);
+}
+
+/***************************************************************************
+* Ed_result_set
+***************************************************************************/
+
+/**
+ Create a new "execute direct" connection.
+*/
+
+Ed_connection::Ed_connection(THD *thd)
+ :m_diagnostics_area(thd->query_id, false, true),
+ m_thd(thd),
+ m_rsets(0),
+ m_current_rset(0)
+{
+}
+
+
+/**
+ Free all result sets of the previous statement, if any,
+ and reset warnings and errors.
+
+ Called before execution of the next query.
+*/
+
+void
+Ed_connection::free_old_result()
+{
+ while (m_rsets)
+ {
+ Ed_result_set *rset= m_rsets->m_next_rset;
+ delete m_rsets;
+ m_rsets= rset;
+ }
+ m_current_rset= m_rsets;
+ m_diagnostics_area.reset_diagnostics_area();
+ m_diagnostics_area.clear_warning_info(m_thd->query_id);
+}
+
+
+/**
+ A simple wrapper that uses a helper class to execute SQL statements.
+*/
+
+bool
+Ed_connection::execute_direct(LEX_STRING sql_text)
+{
+ Execute_sql_statement execute_sql_statement(sql_text);
+ DBUG_PRINT("ed_query", ("%s", sql_text.str));
+
+ return execute_direct(&execute_sql_statement);
+}
+
+
+/**
+ Execute a fragment of server functionality without an effect on
+ thd, and store results in memory.
+
+ Conventions:
+ - the code fragment must finish with OK, EOF or ERROR.
+ - the code fragment doesn't have to close thread tables,
+ free memory, commit statement transaction or do any other
+ cleanup that is normally done in the end of dispatch_command().
+
+ @param server_runnable A code fragment to execute.
+*/
+
+bool Ed_connection::execute_direct(Server_runnable *server_runnable)
+{
+ bool rc= FALSE;
+ Protocol_local protocol_local(m_thd, this);
+ Prepared_statement stmt(m_thd);
+ Protocol *save_protocol= m_thd->protocol;
+ Diagnostics_area *save_diagnostics_area= m_thd->get_stmt_da();
+
+ DBUG_ENTER("Ed_connection::execute_direct");
+
+ free_old_result(); /* Delete all data from previous execution, if any */
+
+ m_thd->protocol= &protocol_local;
+ m_thd->set_stmt_da(&m_diagnostics_area);
+
+ rc= stmt.execute_server_runnable(server_runnable);
+ m_thd->protocol->end_statement();
+
+ m_thd->protocol= save_protocol;
+ m_thd->set_stmt_da(save_diagnostics_area);
+ /*
+ Protocol_local makes use of m_current_rset to keep
+ track of the last result set, while adding result sets to the end.
+ Reset it to point to the first result set instead.
+ */
+ m_current_rset= m_rsets;
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ A helper method that is called only during execution.
+
+ Although Ed_connection doesn't support multi-statements,
+ a statement may generate many result sets. All subsequent
+ result sets are appended to the end.
+
+ @pre This is called only by Protocol_local.
+*/
+
+void
+Ed_connection::add_result_set(Ed_result_set *ed_result_set)
+{
+ if (m_rsets)
+ {
+ m_current_rset->m_next_rset= ed_result_set;
+ /* While appending, use m_current_rset as a pointer to the tail. */
+ m_current_rset= ed_result_set;
+ }
+ else
+ m_current_rset= m_rsets= ed_result_set;
+}
+
+
+/**
+ Release ownership of the current result set to the client.
+
+ Since we use a simple linked list for result sets,
+ this method uses a linear search of the previous result
+ set to exclude the released instance from the list.
+
+ @todo Use double-linked list, when this is really used.
+
+ XXX: This has never been tested with more than one result set!
+
+ @pre There must be a result set.
+*/
+
+Ed_result_set *
+Ed_connection::store_result_set()
+{
+ Ed_result_set *ed_result_set;
+
+ DBUG_ASSERT(m_current_rset);
+
+ if (m_current_rset == m_rsets)
+ {
+ /* Assign the return value */
+ ed_result_set= m_current_rset;
+ /* Exclude the return value from the list. */
+ m_current_rset= m_rsets= m_rsets->m_next_rset;
+ }
+ else
+ {
+ Ed_result_set *prev_rset= m_rsets;
+ /* Assign the return value. */
+ ed_result_set= m_current_rset;
+
+ /* Exclude the return value from the list */
+ while (prev_rset->m_next_rset != m_current_rset)
+ prev_rset= ed_result_set->m_next_rset;
+ m_current_rset= prev_rset->m_next_rset= m_current_rset->m_next_rset;
+ }
+ ed_result_set->m_next_rset= NULL; /* safety */
+
+ return ed_result_set;
+}
+
+/*************************************************************************
+* Protocol_local
+**************************************************************************/
+
+Protocol_local::Protocol_local(THD *thd, Ed_connection *ed_connection)
+ :Protocol(thd),
+ m_connection(ed_connection),
+ m_rset(NULL),
+ m_column_count(0),
+ m_current_row(NULL),
+ m_current_column(NULL)
+{
+ clear_alloc_root(&m_rset_root);
+}
+
+/**
+ Called between two result set rows.
+
+ Prepare structures to fill result set rows.
+ Unfortunately, we can't return an error here. If memory allocation
+ fails, we'll have to return an error later. And so is done
+ in methods such as @sa store_column().
+*/
+
+void Protocol_local::prepare_for_resend()
+{
+ DBUG_ASSERT(alloc_root_inited(&m_rset_root));
+
+ opt_add_row_to_rset();
+ /* Start a new row. */
+ m_current_row= (Ed_column *) alloc_root(&m_rset_root,
+ sizeof(Ed_column) * m_column_count);
+ m_current_column= m_current_row;
+}
+
+
+/**
+ In "real" protocols this is called to finish a result set row.
+ Unused in the local implementation.
+*/
+
+bool Protocol_local::write()
+{
+ return FALSE;
+}
+
+/**
+ A helper function to add the current row to the current result
+ set. Called in @sa prepare_for_resend(), when a new row is started,
+ and in send_eof(), when the result set is finished.
+*/
+
+void Protocol_local::opt_add_row_to_rset()
+{
+ if (m_current_row)
+ {
+ /* Add the old row to the result set */
+ Ed_row *ed_row= new (&m_rset_root) Ed_row(m_current_row, m_column_count);
+ if (ed_row)
+ m_rset->push_back(ed_row, &m_rset_root);
+ }
+}
+
+
+/**
+ Add a NULL column to the current row.
+*/
+
+bool Protocol_local::store_null()
+{
+ if (m_current_column == NULL)
+ return TRUE; /* prepare_for_resend() failed to allocate memory. */
+
+ bzero(m_current_column, sizeof(*m_current_column));
+ ++m_current_column;
+ return FALSE;
+}
+
+
+/**
+ A helper method to add any column to the current row
+ in its binary form.
+
+ Allocates memory for the data in the result set memory root.
+*/
+
+bool Protocol_local::store_column(const void *data, size_t length)
+{
+ if (m_current_column == NULL)
+ return TRUE; /* prepare_for_resend() failed to allocate memory. */
+ /*
+ alloc_root() automatically aligns memory, so we don't need to
+ do any extra alignment if we're pointing to, say, an integer.
+ */
+ m_current_column->str= (char*) memdup_root(&m_rset_root,
+ data,
+ length + 1 /* Safety */);
+ if (! m_current_column->str)
+ return TRUE;
+ m_current_column->str[length]= '\0'; /* Safety */
+ m_current_column->length= length;
+ ++m_current_column;
+ return FALSE;
+}
+
+
+/**
+ Store a string value in a result set column, optionally
+ having converted it to character_set_results.
+*/
+
+bool
+Protocol_local::store_string(const char *str, size_t length,
+ CHARSET_INFO *src_cs, CHARSET_INFO *dst_cs)
+{
+ /* Store with conversion */
+ uint error_unused;
+
+ if (dst_cs && !my_charset_same(src_cs, dst_cs) &&
+ src_cs != &my_charset_bin &&
+ dst_cs != &my_charset_bin)
+ {
+ if (convert->copy(str, length, src_cs, dst_cs, &error_unused))
+ return TRUE;
+ str= convert->ptr();
+ length= convert->length();
+ }
+ return store_column(str, length);
+}
+
+
+/** Store a tiny int as is (1 byte) in a result set column. */
+
+bool Protocol_local::store_tiny(longlong value)
+{
+ char v= (char) value;
+ return store_column(&v, 1);
+}
+
+
+/** Store a short as is (2 bytes, host order) in a result set column. */
+
+bool Protocol_local::store_short(longlong value)
+{
+ int16 v= (int16) value;
+ return store_column(&v, 2);
+}
+
+
+/** Store a "long" as is (4 bytes, host order) in a result set column. */
+
+bool Protocol_local::store_long(longlong value)
+{
+ int32 v= (int32) value;
+ return store_column(&v, 4);
+}
+
+
+/** Store a "longlong" as is (8 bytes, host order) in a result set column. */
+
+bool Protocol_local::store_longlong(longlong value, bool unsigned_flag)
+{
+ int64 v= (int64) value;
+ return store_column(&v, 8);
+}
+
+
+/** Store a decimal in string format in a result set column */
+
+bool Protocol_local::store_decimal(const my_decimal *value)
+{
+ char buf[DECIMAL_MAX_STR_LENGTH];
+ String str(buf, sizeof (buf), &my_charset_bin);
+ int rc;
+
+ rc= my_decimal2string(E_DEC_FATAL_ERROR, value, 0, 0, 0, &str);
+
+ if (rc)
+ return TRUE;
+
+ return store_column(str.ptr(), str.length());
+}
+
+
+/** Convert to cs_results and store a string. */
+
+bool Protocol_local::store(const char *str, size_t length,
+ CHARSET_INFO *src_cs)
+{
+ CHARSET_INFO *dst_cs;
+
+ dst_cs= m_connection->m_thd->variables.character_set_results;
+ return store_string(str, length, src_cs, dst_cs);
+}
+
+
+/** Store a string. */
+
+bool Protocol_local::store(const char *str, size_t length,
+ CHARSET_INFO *src_cs, CHARSET_INFO *dst_cs)
+{
+ return store_string(str, length, src_cs, dst_cs);
+}
+
+
+/* Store MYSQL_TIME (in binary format) */
+
+bool Protocol_local::store(MYSQL_TIME *time, int decimals)
+{
+ if (decimals != AUTO_SEC_PART_DIGITS)
+ my_time_trunc(time, decimals);
+ return store_column(time, sizeof(MYSQL_TIME));
+}
+
+
+/** Store MYSQL_TIME (in binary format) */
+
+bool Protocol_local::store_date(MYSQL_TIME *time)
+{
+ return store_column(time, sizeof(MYSQL_TIME));
+}
+
+
+/** Store MYSQL_TIME (in binary format) */
+
+bool Protocol_local::store_time(MYSQL_TIME *time, int decimals)
+{
+ if (decimals != AUTO_SEC_PART_DIGITS)
+ my_time_trunc(time, decimals);
+ return store_column(time, sizeof(MYSQL_TIME));
+}
+
+
+/* Store a floating point number, as is. */
+
+bool Protocol_local::store(float value, uint32 decimals, String *buffer)
+{
+ return store_column(&value, sizeof(float));
+}
+
+
+/* Store a double precision number, as is. */
+
+bool Protocol_local::store(double value, uint32 decimals, String *buffer)
+{
+ return store_column(&value, sizeof (double));
+}
+
+
+/* Store a Field. */
+
+bool Protocol_local::store(Field *field)
+{
+ if (field->is_null())
+ return store_null();
+ return field->send_binary(this);
+}
+
+
+/** Called to start a new result set. */
+
+bool Protocol_local::send_result_set_metadata(List<Item> *columns, uint)
+{
+ DBUG_ASSERT(m_rset == 0 && !alloc_root_inited(&m_rset_root));
+
+ init_sql_alloc(&m_rset_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC));
+
+ if (! (m_rset= new (&m_rset_root) List<Ed_row>))
+ return TRUE;
+
+ m_column_count= columns->elements;
+
+ return FALSE;
+}
+
+
+/**
+ Normally this is a separate result set with OUT parameters
+ of stored procedures. Currently unsupported for the local
+ version.
+*/
+
+bool Protocol_local::send_out_parameters(List<Item_param> *sp_params)
+{
+ return FALSE;
+}
+
+
+/** Called for statements that don't have a result set, at statement end. */
+
+bool
+Protocol_local::send_ok(uint server_status, uint statement_warn_count,
+ ulonglong affected_rows, ulonglong last_insert_id,
+ const char *message)
+{
+ /*
+ Just make sure nothing is sent to the client, we have grabbed
+ the status information in the connection diagnostics area.
+ */
+ return FALSE;
+}
+
+
+/**
+ Called at the end of a result set. Append a complete
+ result set to the list in Ed_connection.
+
+ Don't send anything to the client, but instead finish
+ building of the result set at hand.
+*/
+
+bool Protocol_local::send_eof(uint server_status, uint statement_warn_count)
+{
+ Ed_result_set *ed_result_set;
+
+ DBUG_ASSERT(m_rset);
+
+ opt_add_row_to_rset();
+ m_current_row= 0;
+
+ ed_result_set= new (&m_rset_root) Ed_result_set(m_rset, m_column_count,
+ &m_rset_root);
+
+ m_rset= NULL;
+
+ if (! ed_result_set)
+ return TRUE;
+
+ /* In case of successful allocation memory ownership was transferred. */
+ DBUG_ASSERT(!alloc_root_inited(&m_rset_root));
+
+ /*
+ Link the created Ed_result_set instance into the list of connection
+ result sets. Never fails.
+ */
+ m_connection->add_result_set(ed_result_set);
+ return FALSE;
+}
+
+
+/** Called to send an error to the client at the end of a statement. */
+
+bool
+Protocol_local::send_error(uint sql_errno, const char *err_msg, const char*)
+{
+ /*
+ Just make sure that nothing is sent to the client (default
+ implementation).
+ */
+ return FALSE;
+}
+
+
+#ifdef EMBEDDED_LIBRARY
+void Protocol_local::remove_last_row()
+{ }
+#endif
diff --git a/sql/sql_prepare.h b/sql/sql_prepare.h
new file mode 100644
index 00000000000..b468ac1bf9b
--- /dev/null
+++ b/sql/sql_prepare.h
@@ -0,0 +1,360 @@
+#ifndef SQL_PREPARE_H
+#define SQL_PREPARE_H
+/* Copyright (c) 1995-2008 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 "sql_error.h"
+
+class THD;
+struct LEX;
+
+/**
+ An interface that is used to take an action when
+ the locking module notices that a table version has changed
+ since the last execution. "Table" here may refer to any kind of
+ table -- a base table, a temporary table, a view or an
+ information schema table.
+
+ When we open and lock tables for execution of a prepared
+ statement, we must verify that they did not change
+ since statement prepare. If some table did change, the statement
+ parse tree *may* be no longer valid, e.g. in case it contains
+ optimizations that depend on table metadata.
+
+ This class provides an interface (a method) that is
+ invoked when such a situation takes place.
+ The implementation of the method simply reports an error, but
+ the exact details depend on the nature of the SQL statement.
+
+ At most 1 instance of this class is active at a time, in which
+ case THD::m_reprepare_observer is not NULL.
+
+ @sa check_and_update_table_version() for details of the
+ version tracking algorithm
+
+ @sa Open_tables_state::m_reprepare_observer for the life cycle
+ of metadata observers.
+*/
+
+class Reprepare_observer
+{
+public:
+ /**
+ Check if a change of metadata is OK. In future
+ the signature of this method may be extended to accept the old
+ and the new versions, but since currently the check is very
+ simple, we only need the THD to report an error.
+ */
+ bool report_error(THD *thd);
+ bool is_invalidated() const { return m_invalidated; }
+ void reset_reprepare_observer() { m_invalidated= FALSE; }
+private:
+ bool m_invalidated;
+};
+
+
+void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length);
+void mysqld_stmt_execute(THD *thd, char *packet, uint packet_length);
+void mysqld_stmt_close(THD *thd, char *packet);
+void mysql_sql_stmt_prepare(THD *thd);
+void mysql_sql_stmt_execute(THD *thd);
+void mysql_sql_stmt_close(THD *thd);
+void mysqld_stmt_fetch(THD *thd, char *packet, uint packet_length);
+void mysqld_stmt_reset(THD *thd, char *packet);
+void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length);
+void reinit_stmt_before_use(THD *thd, LEX *lex);
+
+/**
+ Execute a fragment of server code in an isolated context, so that
+ it doesn't leave any effect on THD. THD must have no open tables.
+ The code must not leave any open tables around.
+ The result of execution (if any) is stored in Ed_result.
+*/
+
+class Server_runnable
+{
+public:
+ virtual bool execute_server_code(THD *thd)= 0;
+ virtual ~Server_runnable();
+};
+
+
+/**
+ Execute direct interface.
+
+ @todo Implement support for prelocked mode.
+*/
+
+class Ed_row;
+
+/**
+ Ed_result_set -- a container with result set rows.
+ @todo Implement support for result set metadata and
+ automatic type conversion.
+*/
+
+class Ed_result_set: public Sql_alloc
+{
+public:
+ operator List<Ed_row>&() { return *m_rows; }
+ unsigned int size() const { return m_rows->elements; }
+
+ Ed_result_set(List<Ed_row> *rows_arg, size_t column_count,
+ MEM_ROOT *mem_root_arg);
+
+ /** We don't call member destructors, they all are POD types. */
+ ~Ed_result_set() {}
+
+ size_t get_field_count() const { return m_column_count; }
+
+ static void operator delete(void *ptr, size_t size) throw ();
+private:
+ Ed_result_set(const Ed_result_set &); /* not implemented */
+ Ed_result_set &operator=(Ed_result_set &); /* not implemented */
+private:
+ MEM_ROOT m_mem_root;
+ size_t m_column_count;
+ List<Ed_row> *m_rows;
+ Ed_result_set *m_next_rset;
+ friend class Ed_connection;
+};
+
+
+class Ed_connection
+{
+public:
+ /**
+ Construct a new "execute direct" connection.
+
+ The connection can be used to execute SQL statements.
+ If the connection failed to initialize, the error
+ will be returned on the attempt to execute a statement.
+
+ @pre thd must have no open tables
+ while the connection is used. However,
+ Ed_connection works okay in LOCK TABLES mode.
+ Other properties of THD, such as the current warning
+ information, errors, etc. do not matter and are
+ preserved by Ed_connection. One thread may have many
+ Ed_connections created for it.
+ */
+ Ed_connection(THD *thd);
+
+ /**
+ Execute one SQL statement.
+
+ Until this method is executed, no other methods of
+ Ed_connection can be used. Life cycle of Ed_connection is:
+
+ Initialized -> a statement has been executed ->
+ look at result, move to next result ->
+ look at result, move to next result ->
+ ...
+ moved beyond the last result == Initialized.
+
+ This method can be called repeatedly. Once it's invoked,
+ results of the previous execution are lost.
+
+ A result of execute_direct() can be either:
+
+ - success, no result set rows. In this case get_field_count()
+ returns 0. This happens after execution of INSERT, UPDATE,
+ DELETE, DROP and similar statements. Some other methods, such
+ as get_affected_rows() can be used to retrieve additional
+ result information.
+
+ - success, there are some result set rows (maybe 0). E.g.
+ happens after SELECT. In this case get_field_count() returns
+ the number of columns in a result set and store_result()
+ can be used to retrieve a result set..
+
+ - an error, methods to retrieve error information can
+ be used.
+
+ @return execution status
+ @retval FALSE success, use get_field_count()
+ to determine what to do next.
+ @retval TRUE error, use get_last_error()
+ to see the error number.
+ */
+ bool execute_direct(LEX_STRING sql_text);
+
+ /**
+ Same as the previous, but takes an instance of Server_runnable
+ instead of SQL statement text.
+
+ @return execution status
+
+ @retval FALSE success, use get_field_count()
+ if your code fragment is supposed to
+ return a result set
+ @retval TRUE failure
+ */
+ bool execute_direct(Server_runnable *server_runnable);
+
+ /**
+ Get the number of result set fields.
+
+ This method is valid only if we have a result:
+ execute_direct() has been called. Otherwise
+ the returned value is undefined.
+
+ @sa Documentation for C API function
+ mysql_field_count()
+ */
+ ulong get_field_count() const
+ {
+ return m_current_rset ? m_current_rset->get_field_count() : 0;
+ }
+
+ /**
+ Get the number of affected (deleted, updated)
+ rows for the current statement. Can be
+ used for statements with get_field_count() == 0.
+
+ @sa Documentation for C API function
+ mysql_affected_rows().
+ */
+ ulonglong get_affected_rows() const
+ {
+ return m_diagnostics_area.affected_rows();
+ }
+
+ /**
+ Get the last insert id, if any.
+
+ @sa Documentation for mysql_insert_id().
+ */
+ ulonglong get_last_insert_id() const
+ {
+ return m_diagnostics_area.last_insert_id();
+ }
+
+ /**
+ Get the total number of warnings for the last executed
+ statement. Note, that there is only one warning list even
+ if a statement returns multiple results.
+
+ @sa Documentation for C API function
+ mysql_num_warnings().
+ */
+ ulong get_warn_count() const
+ {
+ return m_diagnostics_area.warn_count();
+ }
+
+ /**
+ The following members are only valid if execute_direct()
+ or move_to_next_result() returned an error.
+ They never fail, but if they are called when there is no
+ result, or no error, the result is not defined.
+ */
+ const char *get_last_error() const { return m_diagnostics_area.message(); }
+ unsigned int get_last_errno() const { return m_diagnostics_area.sql_errno(); }
+ const char *get_last_sqlstate() const { return m_diagnostics_area.get_sqlstate(); }
+
+ /**
+ Provided get_field_count() is not 0, this never fails. You don't
+ need to free the result set, this is done automatically when
+ you advance to the next result set or destroy the connection.
+ Not returning const because of List iterator not accepting
+ Should be used when you would like Ed_connection to manage
+ result set memory for you.
+ */
+ Ed_result_set *use_result_set() { return m_current_rset; }
+ /**
+ Provided get_field_count() is not 0, this never fails. You
+ must free the returned result set. This can be called only
+ once after execute_direct().
+ Should be used when you would like to get the results
+ and destroy the connection.
+ */
+ Ed_result_set *store_result_set();
+
+ /**
+ If the query returns multiple results, this method
+ can be checked if there is another result beyond the next
+ one.
+ Never fails.
+ */
+ bool has_next_result() const { return MY_TEST(m_current_rset->m_next_rset); }
+ /**
+ Only valid to call if has_next_result() returned true.
+ Otherwise the result is undefined.
+ */
+ bool move_to_next_result()
+ {
+ m_current_rset= m_current_rset->m_next_rset;
+ return MY_TEST(m_current_rset);
+ }
+
+ ~Ed_connection() { free_old_result(); }
+private:
+ Diagnostics_area m_diagnostics_area;
+ /**
+ Execute direct interface does not support multi-statements, only
+ multi-results. So we never have a situation when we have
+ a mix of result sets and OK or error packets. We either
+ have a single result set, a single error, or a single OK,
+ or we have a series of result sets, followed by an OK or error.
+ */
+ THD *m_thd;
+ Ed_result_set *m_rsets;
+ Ed_result_set *m_current_rset;
+ friend class Protocol_local;
+private:
+ void free_old_result();
+ void add_result_set(Ed_result_set *ed_result_set);
+private:
+ Ed_connection(const Ed_connection &); /* not implemented */
+ Ed_connection &operator=(Ed_connection &); /* not implemented */
+};
+
+
+/** One result set column. */
+
+struct Ed_column: public LEX_STRING
+{
+ /** Implementation note: destructor for this class is never called. */
+};
+
+
+/** One result set record. */
+
+class Ed_row: public Sql_alloc
+{
+public:
+ const Ed_column &operator[](const unsigned int column_index) const
+ {
+ return *get_column(column_index);
+ }
+ const Ed_column *get_column(const unsigned int column_index) const
+ {
+ DBUG_ASSERT(column_index < size());
+ return m_column_array + column_index;
+ }
+ size_t size() const { return m_column_count; }
+
+ Ed_row(Ed_column *column_array_arg, size_t column_count_arg)
+ :m_column_array(column_array_arg),
+ m_column_count(column_count_arg)
+ {}
+private:
+ Ed_column *m_column_array;
+ size_t m_column_count; /* TODO: change to point to metadata */
+};
+
+#endif // SQL_PREPARE_H
diff --git a/sql/sql_priv.h b/sql/sql_priv.h
new file mode 100644
index 00000000000..09a22ba0444
--- /dev/null
+++ b/sql/sql_priv.h
@@ -0,0 +1,407 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2014, Monty Program Ab.
+
+ 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 */
+
+/**
+ @file
+
+ @details
+ Mostly this file is used in the server. But a little part of it is used in
+ mysqlbinlog too (definition of SELECT_DISTINCT and others).
+ The consequence is that 90% of the file is wrapped in \#ifndef MYSQL_CLIENT,
+ except the part which must be in the server and in the client.
+*/
+
+#ifndef SQL_PRIV_INCLUDED
+#define SQL_PRIV_INCLUDED
+
+#ifndef MYSQL_CLIENT
+
+/*
+ Generates a warning that a feature is deprecated. After a specified
+ version asserts that the feature is removed.
+
+ Using it as
+
+ WARN_DEPRECATED(thd, 6,2, "BAD", "'GOOD'");
+
+ Will result in a warning
+
+ "The syntax 'BAD' is deprecated and will be removed in MySQL 6.2. Please
+ use 'GOOD' instead"
+
+ Note that in macro arguments BAD is not quoted, while 'GOOD' is.
+ Note that the version is TWO numbers, separated with a comma
+ (two macro arguments, that is)
+*/
+#define WARN_DEPRECATED(Thd,VerHi,VerLo,Old,New) \
+ do { \
+ compile_time_assert(MYSQL_VERSION_ID < VerHi * 10000 + VerLo * 100); \
+ if (((THD *) Thd) != NULL) \
+ push_warning_printf(((THD *) Thd), Sql_condition::WARN_LEVEL_WARN, \
+ ER_WARN_DEPRECATED_SYNTAX, \
+ ER(ER_WARN_DEPRECATED_SYNTAX), \
+ (Old), (New)); \
+ else \
+ sql_print_warning("The syntax '%s' is deprecated and will be removed " \
+ "in a future release. Please use %s instead.", \
+ (Old), (New)); \
+ } while(0)
+
+
+/*
+ Generates a warning that a feature is deprecated and there is no replacement.
+
+ Using it as
+
+ WARN_DEPRECATED_NO_REPLACEMENT(thd, "BAD");
+
+ Will result in a warning
+
+ "'BAD' is deprecated and will be removed in a future release."
+
+ Note that in macro arguments BAD is not quoted.
+*/
+
+#define WARN_DEPRECATED_NO_REPLACEMENT(Thd,Old) \
+ do { \
+ if (((THD *) Thd) != NULL) \
+ push_warning_printf(((THD *) Thd), Sql_condition::WARN_LEVEL_WARN, \
+ ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT, \
+ ER(ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT), \
+ (Old)); \
+ else \
+ sql_print_warning("'%s' is deprecated and will be removed " \
+ "in a future release.", (Old)); \
+ } while(0)
+
+/*************************************************************************/
+
+#endif
+
+/*
+ This is included in the server and in the client.
+ Options for select set by the yacc parser (stored in lex->options).
+
+ NOTE
+ log_event.h defines OPTIONS_WRITTEN_TO_BIN_LOG to specify what THD
+ options list are written into binlog. These options can NOT change their
+ values, or it will break replication between version.
+
+ context is encoded as following:
+ SELECT - SELECT_LEX_NODE::options
+ THD - THD::options
+ intern - neither. used only as
+ func(..., select_node->options | thd->options | OPTION_XXX, ...)
+
+ TODO: separate three contexts above, move them to separate bitfields.
+*/
+
+#define SELECT_DISTINCT (1ULL << 0) // SELECT, user
+#define SELECT_STRAIGHT_JOIN (1ULL << 1) // SELECT, user
+#define SELECT_DESCRIBE (1ULL << 2) // SELECT, user
+#define SELECT_SMALL_RESULT (1ULL << 3) // SELECT, user
+#define SELECT_BIG_RESULT (1ULL << 4) // SELECT, user
+#define OPTION_FOUND_ROWS (1ULL << 5) // SELECT, user
+#define OPTION_TO_QUERY_CACHE (1ULL << 6) // SELECT, user
+#define SELECT_NO_JOIN_CACHE (1ULL << 7) // intern
+/** always the opposite of OPTION_NOT_AUTOCOMMIT except when in fix_autocommit() */
+#define OPTION_AUTOCOMMIT (1ULL << 8) // THD, user
+#define OPTION_BIG_SELECTS (1ULL << 9) // THD, user
+#define OPTION_LOG_OFF (1ULL << 10) // THD, user
+#define OPTION_QUOTE_SHOW_CREATE (1ULL << 11) // THD, user, unused
+#define TMP_TABLE_ALL_COLUMNS (1ULL << 12) // SELECT, intern
+#define OPTION_WARNINGS (1ULL << 13) // THD, user
+#define OPTION_AUTO_IS_NULL (1ULL << 14) // THD, user, binlog
+#define OPTION_FOUND_COMMENT (1ULL << 15) // SELECT, intern, parser
+#define OPTION_SAFE_UPDATES (1ULL << 16) // THD, user
+#define OPTION_BUFFER_RESULT (1ULL << 17) // SELECT, user
+#define OPTION_BIN_LOG (1ULL << 18) // THD, user
+#define OPTION_NOT_AUTOCOMMIT (1ULL << 19) // THD, user
+#define OPTION_BEGIN (1ULL << 20) // THD, intern
+#define OPTION_TABLE_LOCK (1ULL << 21) // THD, intern
+#define OPTION_QUICK (1ULL << 22) // SELECT (for DELETE)
+#define OPTION_KEEP_LOG (1ULL << 23) // THD, user
+
+/* The following is used to detect a conflict with DISTINCT */
+#define SELECT_ALL (1ULL << 24) // SELECT, user, parser
+#define OPTION_GTID_BEGIN (1ULL << 25) // GTID BEGIN found in log
+
+/** The following can be set when importing tables in a 'wrong order'
+ to suppress foreign key checks */
+#define OPTION_NO_FOREIGN_KEY_CHECKS (1ULL << 26) // THD, user, binlog
+/** The following speeds up inserts to InnoDB tables by suppressing unique
+ key checks in some cases */
+#define OPTION_RELAXED_UNIQUE_CHECKS (1ULL << 27) // THD, user, binlog
+#define SELECT_NO_UNLOCK (1ULL << 28) // SELECT, intern
+#define OPTION_SCHEMA_TABLE (1ULL << 29) // SELECT, intern
+/** Flag set if setup_tables already done */
+#define OPTION_SETUP_TABLES_DONE (1ULL << 30) // intern
+/** If not set then the thread will ignore all warnings with level notes. */
+#define OPTION_SQL_NOTES (1ULL << 31) // THD, user
+/**
+ Force the used temporary table to be a MyISAM table (because we will use
+ fulltext functions when reading from it.
+*/
+#define TMP_TABLE_FORCE_MYISAM (1ULL << 32)
+#define OPTION_PROFILING (1ULL << 33)
+/**
+ Indicates that this is a HIGH_PRIORITY SELECT.
+ Currently used only for printing of such selects.
+ Type of locks to be acquired is specified directly.
+*/
+#define SELECT_HIGH_PRIORITY (1ULL << 34) // SELECT, user
+/**
+ Is set in slave SQL thread when there was an
+ error on master, which, when is not reproducible
+ on slave (i.e. the query succeeds on slave),
+ is not terminal to the state of repliation,
+ and should be ignored. The slave SQL thread,
+ however, needs to rollback the effects of the
+ succeeded statement to keep replication consistent.
+*/
+#define OPTION_MASTER_SQL_ERROR (1ULL << 35)
+
+/*
+ Dont report errors for individual rows,
+ But just report error on commit (or read ofcourse)
+ Note! Reserved for use in MySQL Cluster
+*/
+#define OPTION_ALLOW_BATCH (1ULL << 36) // THD, intern (slave)
+#define OPTION_SKIP_REPLICATION (1ULL << 37) // THD, user
+
+/* The rest of the file is included in the server only */
+#ifndef MYSQL_CLIENT
+
+/* @@optimizer_switch flags. These must be in sync with optimizer_switch_typelib */
+#define OPTIMIZER_SWITCH_INDEX_MERGE (1ULL << 0)
+#define OPTIMIZER_SWITCH_INDEX_MERGE_UNION (1ULL << 1)
+#define OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION (1ULL << 2)
+#define OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT (1ULL << 3)
+#define OPTIMIZER_SWITCH_INDEX_MERGE_SORT_INTERSECT (1ULL << 4)
+#define OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN (1ULL << 5)
+#define OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN (1ULL << 6)
+#define OPTIMIZER_SWITCH_DERIVED_MERGE (1ULL << 7)
+#define OPTIMIZER_SWITCH_DERIVED_WITH_KEYS (1ULL << 8)
+#define OPTIMIZER_SWITCH_FIRSTMATCH (1ULL << 9)
+#define OPTIMIZER_SWITCH_LOOSE_SCAN (1ULL << 10)
+#define OPTIMIZER_SWITCH_MATERIALIZATION (1ULL << 11)
+#define OPTIMIZER_SWITCH_IN_TO_EXISTS (1ULL << 12)
+#define OPTIMIZER_SWITCH_SEMIJOIN (1ULL << 13)
+#define OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE (1ULL << 14)
+#define OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN (1ULL << 15)
+#define OPTIMIZER_SWITCH_SUBQUERY_CACHE (1ULL << 16)
+/** If this is off, MRR is never used. */
+#define OPTIMIZER_SWITCH_MRR (1ULL << 17)
+/**
+ If OPTIMIZER_SWITCH_MRR is on and this is on, MRR is used depending on a
+ cost-based choice ("automatic"). If OPTIMIZER_SWITCH_MRR is on and this is
+ off, MRR is "forced" (i.e. used as long as the storage engine is capable of
+ doing it).
+*/
+#define OPTIMIZER_SWITCH_MRR_COST_BASED (1ULL << 18)
+#define OPTIMIZER_SWITCH_MRR_SORT_KEYS (1ULL << 19)
+#define OPTIMIZER_SWITCH_OUTER_JOIN_WITH_CACHE (1ULL << 20)
+#define OPTIMIZER_SWITCH_SEMIJOIN_WITH_CACHE (1ULL << 21)
+#define OPTIMIZER_SWITCH_JOIN_CACHE_INCREMENTAL (1ULL << 22)
+#define OPTIMIZER_SWITCH_JOIN_CACHE_HASHED (1ULL << 23)
+#define OPTIMIZER_SWITCH_JOIN_CACHE_BKA (1ULL << 24)
+#define OPTIMIZER_SWITCH_OPTIMIZE_JOIN_BUFFER_SIZE (1ULL << 25)
+#define OPTIMIZER_SWITCH_TABLE_ELIMINATION (1ULL << 26)
+#define OPTIMIZER_SWITCH_EXTENDED_KEYS (1ULL << 27)
+#define OPTIMIZER_SWITCH_EXISTS_TO_IN (1ULL << 28)
+#define OPTIMIZER_SWITCH_USE_CONDITION_SELECTIVITY (1ULL << 29)
+
+#define OPTIMIZER_SWITCH_DEFAULT (OPTIMIZER_SWITCH_INDEX_MERGE | \
+ OPTIMIZER_SWITCH_INDEX_MERGE_UNION | \
+ OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION | \
+ OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT | \
+ OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN | \
+ OPTIMIZER_SWITCH_DERIVED_MERGE | \
+ OPTIMIZER_SWITCH_DERIVED_WITH_KEYS | \
+ OPTIMIZER_SWITCH_TABLE_ELIMINATION | \
+ OPTIMIZER_SWITCH_EXTENDED_KEYS | \
+ OPTIMIZER_SWITCH_IN_TO_EXISTS | \
+ OPTIMIZER_SWITCH_MATERIALIZATION | \
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN|\
+ OPTIMIZER_SWITCH_OUTER_JOIN_WITH_CACHE | \
+ OPTIMIZER_SWITCH_SEMIJOIN_WITH_CACHE | \
+ OPTIMIZER_SWITCH_JOIN_CACHE_INCREMENTAL | \
+ OPTIMIZER_SWITCH_JOIN_CACHE_HASHED | \
+ OPTIMIZER_SWITCH_JOIN_CACHE_BKA | \
+ OPTIMIZER_SWITCH_SUBQUERY_CACHE | \
+ OPTIMIZER_SWITCH_SEMIJOIN | \
+ OPTIMIZER_SWITCH_FIRSTMATCH | \
+ OPTIMIZER_SWITCH_LOOSE_SCAN | \
+ OPTIMIZER_SWITCH_EXISTS_TO_IN)
+/*
+ Replication uses 8 bytes to store SQL_MODE in the binary log. The day you
+ use strictly more than 64 bits by adding one more define above, you should
+ contact the replication team because the replication code should then be
+ updated (to store more bytes on disk).
+
+ NOTE: When adding new SQL_MODE types, make sure to also add them to
+ the scripts used for creating the MySQL system tables
+ in scripts/mysql_system_tables.sql and scripts/mysql_system_tables_fix.sql
+
+*/
+
+/*
+ Flags below are set when we perform
+ context analysis of the statement and make
+ subqueries non-const. It prevents subquery
+ evaluation at context analysis stage.
+*/
+
+/*
+ Don't evaluate this subquery during statement prepare even if
+ it's a constant one. The flag is switched off in the end of
+ mysqld_stmt_prepare.
+*/
+#define CONTEXT_ANALYSIS_ONLY_PREPARE 1
+/*
+ Special JOIN::prepare mode: changing of query is prohibited.
+ When creating a view, we need to just check its syntax omitting
+ any optimizations: afterwards definition of the view will be
+ reconstructed by means of ::print() methods and written to
+ to an .frm file. We need this definition to stay untouched.
+*/
+#define CONTEXT_ANALYSIS_ONLY_VIEW 2
+/*
+ Don't evaluate this subquery during derived table prepare even if
+ it's a constant one.
+*/
+#define CONTEXT_ANALYSIS_ONLY_DERIVED 4
+/*
+ Don't evaluate constant sub-expressions of virtual column
+ expressions when opening tables
+*/
+#define CONTEXT_ANALYSIS_ONLY_VCOL_EXPR 8
+
+
+/*
+ Uncachable causes:
+*/
+/* This subquery has fields from outer query (put by user) */
+#define UNCACHEABLE_DEPENDENT_GENERATED 1
+/* This subquery contains functions with random result */
+#define UNCACHEABLE_RAND 2
+/* This subquery contains functions with side effect */
+#define UNCACHEABLE_SIDEEFFECT 4
+/* Forcing to save JOIN tables for explain */
+#define UNCACHEABLE_EXPLAIN 8
+/* For uncorrelated SELECT in an UNION with some correlated SELECTs */
+#define UNCACHEABLE_UNITED 16
+#define UNCACHEABLE_CHECKOPTION 32
+/*
+ This subquery has fields from outer query injected during
+ transformation process
+*/
+#define UNCACHEABLE_DEPENDENT_INJECTED 64
+/* This subquery has fields from outer query (any nature) */
+#define UNCACHEABLE_DEPENDENT (UNCACHEABLE_DEPENDENT_GENERATED | \
+ UNCACHEABLE_DEPENDENT_INJECTED)
+
+/* Used to check GROUP BY list in the MODE_ONLY_FULL_GROUP_BY mode */
+#define UNDEF_POS (-1)
+
+/* BINLOG_DUMP options */
+
+#define BINLOG_DUMP_NON_BLOCK 1
+#endif /* !MYSQL_CLIENT */
+
+#define BINLOG_SEND_ANNOTATE_ROWS_EVENT 2
+
+#ifndef MYSQL_CLIENT
+
+/*
+ Some defines for exit codes for ::is_equal class functions.
+*/
+#define IS_EQUAL_NO 0
+#define IS_EQUAL_YES 1
+#define IS_EQUAL_PACK_LENGTH 2
+
+enum enum_parsing_place
+{
+ NO_MATTER,
+ IN_HAVING,
+ SELECT_LIST,
+ IN_WHERE,
+ IN_ON,
+ IN_GROUP_BY,
+ PARSING_PLACE_SIZE /* always should be the last */
+};
+
+
+enum enum_var_type
+{
+ OPT_DEFAULT= 0, OPT_SESSION, OPT_GLOBAL
+};
+
+class sys_var;
+
+enum enum_yes_no_unknown
+{
+ TVL_YES, TVL_NO, TVL_UNKNOWN
+};
+
+#ifdef MYSQL_SERVER
+
+/*
+ External variables
+*/
+
+
+/* sql_yacc.cc */
+#ifndef DBUG_OFF
+extern void turn_parser_debug_on();
+
+#endif
+
+/**
+ convert a hex digit into number.
+*/
+
+inline int hexchar_to_int(char c)
+{
+ if (c <= '9' && c >= '0')
+ return c-'0';
+ c|=32;
+ if (c <= 'f' && c >= 'a')
+ return c-'a'+10;
+ return -1;
+}
+
+/* This must match the path length limit in the ER_NOT_RW_DIR error msg. */
+#define ER_NOT_RW_DIR_PATHSIZE 200
+
+#define IS_TABLESPACES_TABLESPACE_NAME 0
+#define IS_TABLESPACES_ENGINE 1
+#define IS_TABLESPACES_TABLESPACE_TYPE 2
+#define IS_TABLESPACES_LOGFILE_GROUP_NAME 3
+#define IS_TABLESPACES_EXTENT_SIZE 4
+#define IS_TABLESPACES_AUTOEXTEND_SIZE 5
+#define IS_TABLESPACES_MAXIMUM_SIZE 6
+#define IS_TABLESPACES_NODEGROUP_ID 7
+#define IS_TABLESPACES_TABLESPACE_COMMENT 8
+
+bool db_name_is_in_ignore_db_dirs_list(const char *dbase);
+
+#endif /* MYSQL_SERVER */
+
+#endif /* MYSQL_CLIENT */
+
+#endif /* SQL_PRIV_INCLUDED */
diff --git a/sql/sql_profile.cc b/sql/sql_profile.cc
new file mode 100644
index 00000000000..26d515842ed
--- /dev/null
+++ b/sql/sql_profile.cc
@@ -0,0 +1,748 @@
+/* Copyright (c) 2007, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2012, Monty Program Ab
+
+ 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 */
+
+
+/**
+ @file
+
+ Implement query profiling as as list of metaphorical fences, with one fence
+ per query, and each fencepost a change of thd->proc_info state (with a
+ snapshot of system statistics). When asked, we can then iterate over the
+ fenceposts and calculate the distance between them, to inform the user what
+ happened during a particular query or thd->proc_info state.
+
+ User variables that inform profiling behavior:
+ - "profiling", boolean, session only, "Are queries profiled?"
+ - "profiling_history_size", integer, session + global, "Num queries stored?"
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_profile.h"
+#include <my_sys.h>
+#include "sql_show.h" // schema_table_store_record
+#include "sql_class.h" // THD
+
+#ifdef _WIN32
+#pragma comment(lib,"psapi.lib")
+#endif
+
+#define TIME_FLOAT_DIGITS 9
+/** two vals encoded: (len*100)+dec */
+#define TIME_I_S_DECIMAL_SIZE (TIME_FLOAT_DIGITS*100)+(TIME_FLOAT_DIGITS-3)
+
+#define MAX_QUERY_LENGTH 300
+#define MAX_QUERY_HISTORY 101
+
+/**
+ Connects Information_Schema and Profiling.
+*/
+int fill_query_profile_statistics_info(THD *thd, TABLE_LIST *tables,
+ Item *cond)
+{
+#if defined(ENABLED_PROFILING)
+ return(thd->profiling.fill_statistics_info(thd, tables, cond));
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0), "SHOW PROFILE", "enable-profiling");
+ return(1);
+#endif
+}
+
+ST_FIELD_INFO query_profile_statistics_info[]=
+{
+ /* name, length, type, value, maybe_null, old_name, open_method */
+ {"QUERY_ID", 20, MYSQL_TYPE_LONG, 0, false, "Query_id", SKIP_OPEN_TABLE},
+ {"SEQ", 20, MYSQL_TYPE_LONG, 0, false, "Seq", SKIP_OPEN_TABLE},
+ {"STATE", 30, MYSQL_TYPE_STRING, 0, false, "Status", SKIP_OPEN_TABLE},
+ {"DURATION", TIME_I_S_DECIMAL_SIZE, MYSQL_TYPE_DECIMAL, 0, false, "Duration", SKIP_OPEN_TABLE},
+ {"CPU_USER", TIME_I_S_DECIMAL_SIZE, MYSQL_TYPE_DECIMAL, 0, true, "CPU_user", SKIP_OPEN_TABLE},
+ {"CPU_SYSTEM", TIME_I_S_DECIMAL_SIZE, MYSQL_TYPE_DECIMAL, 0, true, "CPU_system", SKIP_OPEN_TABLE},
+ {"CONTEXT_VOLUNTARY", 20, MYSQL_TYPE_LONG, 0, true, "Context_voluntary", SKIP_OPEN_TABLE},
+ {"CONTEXT_INVOLUNTARY", 20, MYSQL_TYPE_LONG, 0, true, "Context_involuntary", SKIP_OPEN_TABLE},
+ {"BLOCK_OPS_IN", 20, MYSQL_TYPE_LONG, 0, true, "Block_ops_in", SKIP_OPEN_TABLE},
+ {"BLOCK_OPS_OUT", 20, MYSQL_TYPE_LONG, 0, true, "Block_ops_out", SKIP_OPEN_TABLE},
+ {"MESSAGES_SENT", 20, MYSQL_TYPE_LONG, 0, true, "Messages_sent", SKIP_OPEN_TABLE},
+ {"MESSAGES_RECEIVED", 20, MYSQL_TYPE_LONG, 0, true, "Messages_received", SKIP_OPEN_TABLE},
+ {"PAGE_FAULTS_MAJOR", 20, MYSQL_TYPE_LONG, 0, true, "Page_faults_major", SKIP_OPEN_TABLE},
+ {"PAGE_FAULTS_MINOR", 20, MYSQL_TYPE_LONG, 0, true, "Page_faults_minor", SKIP_OPEN_TABLE},
+ {"SWAPS", 20, MYSQL_TYPE_LONG, 0, true, "Swaps", SKIP_OPEN_TABLE},
+ {"SOURCE_FUNCTION", 30, MYSQL_TYPE_STRING, 0, true, "Source_function", SKIP_OPEN_TABLE},
+ {"SOURCE_FILE", 20, MYSQL_TYPE_STRING, 0, true, "Source_file", SKIP_OPEN_TABLE},
+ {"SOURCE_LINE", 20, MYSQL_TYPE_LONG, 0, true, "Source_line", SKIP_OPEN_TABLE},
+ {NULL, 0, MYSQL_TYPE_STRING, 0, true, NULL, 0}
+};
+
+
+int make_profile_table_for_show(THD *thd, ST_SCHEMA_TABLE *schema_table)
+{
+ uint profile_options = thd->lex->profile_options;
+ uint fields_include_condition_truth_values[]= {
+ FALSE, /* Query_id */
+ FALSE, /* Seq */
+ TRUE, /* Status */
+ TRUE, /* Duration */
+ profile_options & PROFILE_CPU, /* CPU_user */
+ profile_options & PROFILE_CPU, /* CPU_system */
+ profile_options & PROFILE_CONTEXT, /* Context_voluntary */
+ profile_options & PROFILE_CONTEXT, /* Context_involuntary */
+ profile_options & PROFILE_BLOCK_IO, /* Block_ops_in */
+ profile_options & PROFILE_BLOCK_IO, /* Block_ops_out */
+ profile_options & PROFILE_IPC, /* Messages_sent */
+ profile_options & PROFILE_IPC, /* Messages_received */
+ profile_options & PROFILE_PAGE_FAULTS, /* Page_faults_major */
+ profile_options & PROFILE_PAGE_FAULTS, /* Page_faults_minor */
+ profile_options & PROFILE_SWAPS, /* Swaps */
+ profile_options & PROFILE_SOURCE, /* Source_function */
+ profile_options & PROFILE_SOURCE, /* Source_file */
+ profile_options & PROFILE_SOURCE, /* Source_line */
+ };
+
+ ST_FIELD_INFO *field_info;
+ Name_resolution_context *context= &thd->lex->select_lex.context;
+ int i;
+
+ for (i= 0; schema_table->fields_info[i].field_name != NULL; i++)
+ {
+ if (! fields_include_condition_truth_values[i])
+ continue;
+
+ field_info= &schema_table->fields_info[i];
+ Item_field *field= new Item_field(context,
+ NullS, NullS, field_info->field_name);
+ if (field)
+ {
+ field->set_name(field_info->old_name,
+ (uint) strlen(field_info->old_name),
+ system_charset_info);
+ if (add_item_to_list(thd, field))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+#if defined(ENABLED_PROFILING)
+
+#define RUSAGE_USEC(tv) ((tv).tv_sec*1000*1000 + (tv).tv_usec)
+#define RUSAGE_DIFF_USEC(tv1, tv2) (RUSAGE_USEC((tv1))-RUSAGE_USEC((tv2)))
+
+#ifdef _WIN32
+static ULONGLONG FileTimeToQuadWord(FILETIME *ft)
+{
+ // Overlay FILETIME onto a ULONGLONG.
+ union {
+ ULONGLONG qwTime;
+ FILETIME ft;
+ } u;
+
+ u.ft = *ft;
+ return u.qwTime;
+}
+
+
+// Get time difference between to FILETIME objects in seconds.
+static double GetTimeDiffInSeconds(FILETIME *a, FILETIME *b)
+{
+ return ((FileTimeToQuadWord(a) - FileTimeToQuadWord(b)) / 1e7);
+}
+#endif
+
+PROF_MEASUREMENT::PROF_MEASUREMENT(QUERY_PROFILE *profile_arg, const char
+ *status_arg)
+ :profile(profile_arg)
+{
+ collect();
+ set_label(status_arg, NULL, NULL, 0);
+}
+
+PROF_MEASUREMENT::PROF_MEASUREMENT(QUERY_PROFILE *profile_arg,
+ const char *status_arg,
+ const char *function_arg,
+ const char *file_arg,
+ unsigned int line_arg)
+ :profile(profile_arg)
+{
+ collect();
+ set_label(status_arg, function_arg, file_arg, line_arg);
+}
+
+PROF_MEASUREMENT::~PROF_MEASUREMENT()
+{
+ my_free(allocated_status_memory);
+ status= function= file= NULL;
+}
+
+void PROF_MEASUREMENT::set_label(const char *status_arg,
+ const char *function_arg,
+ const char *file_arg, unsigned int line_arg)
+{
+ size_t sizes[3]; /* 3 == status+function+file */
+ char *cursor;
+
+ /*
+ Compute all the space we'll need to allocate one block for everything
+ we'll need, instead of N mallocs.
+ */
+ sizes[0]= (status_arg == NULL) ? 0 : strlen(status_arg) + 1;
+ sizes[1]= (function_arg == NULL) ? 0 : strlen(function_arg) + 1;
+ sizes[2]= (file_arg == NULL) ? 0 : strlen(file_arg) + 1;
+
+ allocated_status_memory= (char *) my_malloc(sizes[0] + sizes[1] + sizes[2], MYF(0));
+ DBUG_ASSERT(allocated_status_memory != NULL);
+
+ cursor= allocated_status_memory;
+
+ if (status_arg != NULL)
+ {
+ strcpy(cursor, status_arg);
+ status= cursor;
+ cursor+= sizes[0];
+ }
+ else
+ status= NULL;
+
+ if (function_arg != NULL)
+ {
+ strcpy(cursor, function_arg);
+ function= cursor;
+ cursor+= sizes[1];
+ }
+ else
+ function= NULL;
+
+ if (file_arg != NULL)
+ {
+ strcpy(cursor, file_arg);
+ file= cursor;
+ cursor+= sizes[2];
+ }
+ else
+ file= NULL;
+
+ line= line_arg;
+}
+
+/**
+ This updates the statistics for this moment of time. It captures the state
+ of the running system, so later we can compare points in time and infer what
+ happened in the mean time. It should only be called immediately upon
+ instantiation of this PROF_MEASUREMENT.
+
+ @todo Implement resource capture for OSes not like BSD.
+*/
+void PROF_MEASUREMENT::collect()
+{
+ time_usecs= my_interval_timer() / 1e3; /* ns to us */
+#ifdef HAVE_GETRUSAGE
+ getrusage(RUSAGE_SELF, &rusage);
+#elif defined(_WIN32)
+ FILETIME ftDummy;
+ // NOTE: Get{Process|Thread}Times has a granularity of the clock interval,
+ // which is typically ~15ms. So intervals shorter than that will not be
+ // measurable by this function.
+ GetProcessTimes(GetCurrentProcess(), &ftDummy, &ftDummy, &ftKernel, &ftUser);
+ GetProcessIoCounters(GetCurrentProcess(), &io_count);
+ GetProcessMemoryInfo(GetCurrentProcess(), &mem_count, sizeof(mem_count));
+#endif
+}
+
+
+QUERY_PROFILE::QUERY_PROFILE(PROFILING *profiling_arg, const char *status_arg)
+ :profiling(profiling_arg), profiling_query_id(0), query_source(NULL)
+{
+ m_seq_counter= 1;
+ PROF_MEASUREMENT *prof= new PROF_MEASUREMENT(this, status_arg);
+ prof->m_seq= m_seq_counter++;
+ m_start_time_usecs= prof->time_usecs;
+ m_end_time_usecs= m_start_time_usecs;
+ entries.push_back(prof);
+}
+
+QUERY_PROFILE::~QUERY_PROFILE()
+{
+ while (! entries.is_empty())
+ delete entries.pop();
+
+ my_free(query_source);
+}
+
+/**
+ @todo Provide a way to include the full text, as in SHOW PROCESSLIST.
+*/
+void QUERY_PROFILE::set_query_source(char *query_source_arg,
+ uint query_length_arg)
+{
+ /* Truncate to avoid DoS attacks. */
+ uint length= MY_MIN(MAX_QUERY_LENGTH, query_length_arg);
+
+ DBUG_ASSERT(query_source == NULL); /* we don't leak memory */
+ if (query_source_arg != NULL)
+ query_source= my_strndup(query_source_arg, length, MYF(0));
+}
+
+void QUERY_PROFILE::new_status(const char *status_arg,
+ const char *function_arg, const char *file_arg,
+ unsigned int line_arg)
+{
+ PROF_MEASUREMENT *prof;
+ DBUG_ENTER("QUERY_PROFILE::status");
+
+ DBUG_ASSERT(status_arg != NULL);
+
+ if ((function_arg != NULL) && (file_arg != NULL))
+ prof= new PROF_MEASUREMENT(this, status_arg, function_arg, base_name(file_arg), line_arg);
+ else
+ prof= new PROF_MEASUREMENT(this, status_arg);
+
+ prof->m_seq= m_seq_counter++;
+ m_end_time_usecs= prof->time_usecs;
+ entries.push_back(prof);
+
+ /* Maintain the query history size. */
+ while (entries.elements > MAX_QUERY_HISTORY)
+ delete entries.pop();
+
+ DBUG_VOID_RETURN;
+}
+
+
+
+PROFILING::PROFILING()
+ :profile_id_counter(1), current(NULL), last(NULL)
+{
+}
+
+PROFILING::~PROFILING()
+{
+ while (! history.is_empty())
+ delete history.pop();
+
+ if (current != NULL)
+ delete current;
+}
+
+/**
+ A new state is given, and that signals the profiler to start a new
+ timed step for the current query's profile.
+
+ @param status_arg name of this step
+ @param function_arg calling function (usually supplied from compiler)
+ @param function_arg calling file (usually supplied from compiler)
+ @param function_arg calling line number (usually supplied from compiler)
+*/
+void PROFILING::status_change(const char *status_arg,
+ const char *function_arg,
+ const char *file_arg, unsigned int line_arg)
+{
+ DBUG_ENTER("PROFILING::status_change");
+
+ if (status_arg == NULL) /* We don't know how to handle that */
+ DBUG_VOID_RETURN;
+
+ if (current == NULL) /* This profile was already discarded. */
+ DBUG_VOID_RETURN;
+
+ if (unlikely(enabled))
+ current->new_status(status_arg, function_arg, file_arg, line_arg);
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Prepare to start processing a new query. It is an error to do this
+ if there's a query already in process; nesting is not supported.
+
+ @param initial_state (optional) name of period before first state change
+*/
+void PROFILING::start_new_query(const char *initial_state)
+{
+ DBUG_ENTER("PROFILING::start_new_query");
+
+ /* This should never happen unless the server is radically altered. */
+ if (unlikely(current != NULL))
+ {
+ DBUG_PRINT("warning", ("profiling code was asked to start a new query "
+ "before the old query was finished. This is "
+ "probably a bug."));
+ finish_current_query();
+ }
+
+ enabled= ((thd->variables.option_bits & OPTION_PROFILING) != 0);
+
+ if (! enabled) DBUG_VOID_RETURN;
+
+ DBUG_ASSERT(current == NULL);
+ current= new QUERY_PROFILE(this, initial_state);
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Throw away the current profile, because it's useless or unwanted
+ or corrupted.
+*/
+void PROFILING::discard_current_query()
+{
+ DBUG_ENTER("PROFILING::discard_current_profile");
+
+ delete current;
+ current= NULL;
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Try to save the current profile entry, clean up the data if it shouldn't be
+ saved, and maintain the profile history size. Naturally, this may not
+ succeed if the profile was previously discarded, and that's expected.
+*/
+void PROFILING::finish_current_query()
+{
+ DBUG_ENTER("PROFILING::finish_current_profile");
+ if (current != NULL)
+ {
+ /* The last fence-post, so we can support the span before this. */
+ status_change("ending", NULL, NULL, 0);
+
+ if ((enabled) && /* ON at start? */
+ ((thd->variables.option_bits & OPTION_PROFILING) != 0) && /* and ON at end? */
+ (current->query_source != NULL) &&
+ (! current->entries.is_empty()))
+ {
+ current->profiling_query_id= next_profile_id(); /* assign an id */
+
+ history.push_back(current);
+ last= current; /* never contains something that is not in the history. */
+ current= NULL;
+ }
+ else
+ {
+ delete current;
+ current= NULL;
+ }
+ }
+
+ /* Maintain the history size. */
+ while (history.elements > thd->variables.profiling_history_size)
+ delete history.pop();
+
+ DBUG_VOID_RETURN;
+}
+
+bool PROFILING::show_profiles()
+{
+ DBUG_ENTER("PROFILING::show_profiles");
+ QUERY_PROFILE *prof;
+ List<Item> field_list;
+
+ field_list.push_back(new Item_return_int("Query_ID", 10,
+ MYSQL_TYPE_LONG));
+ field_list.push_back(new Item_return_int("Duration", TIME_FLOAT_DIGITS-1,
+ MYSQL_TYPE_DOUBLE));
+ field_list.push_back(new Item_empty_string("Query", 40));
+
+ if (thd->protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ SELECT_LEX *sel= &thd->lex->select_lex;
+ SELECT_LEX_UNIT *unit= &thd->lex->unit;
+ ha_rows idx= 0;
+ Protocol *protocol= thd->protocol;
+
+ unit->set_limit(sel);
+
+ void *iterator;
+ for (iterator= history.new_iterator();
+ iterator != NULL;
+ iterator= history.iterator_next(iterator))
+ {
+ prof= history.iterator_value(iterator);
+
+ String elapsed;
+
+ double query_time_usecs= prof->m_end_time_usecs - prof->m_start_time_usecs;
+
+ if (++idx <= unit->offset_limit_cnt)
+ continue;
+ if (idx > unit->select_limit_cnt)
+ break;
+
+ protocol->prepare_for_resend();
+ protocol->store((uint32)(prof->profiling_query_id));
+ protocol->store((double)(query_time_usecs/(1000.0*1000)),
+ (uint32) TIME_FLOAT_DIGITS-1, &elapsed);
+ if (prof->query_source != NULL)
+ protocol->store(prof->query_source, strlen(prof->query_source),
+ system_charset_info);
+ else
+ protocol->store_null();
+
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+ }
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ At a point in execution where we know the query source, save the text
+ of it in the query profile.
+
+ This must be called exactly once per descrete statement.
+*/
+void PROFILING::set_query_source(char *query_source_arg, uint query_length_arg)
+{
+ DBUG_ENTER("PROFILING::set_query_source");
+
+ if (! enabled)
+ DBUG_VOID_RETURN;
+
+ if (current != NULL)
+ current->set_query_source(query_source_arg, query_length_arg);
+ else
+ DBUG_PRINT("info", ("no current profile to send query source to"));
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Fill the information schema table, "query_profile", as defined in show.cc .
+ There are two ways to get to this function: Selecting from the information
+ schema, and a SHOW command.
+*/
+int PROFILING::fill_statistics_info(THD *thd_arg, TABLE_LIST *tables, Item *cond)
+{
+ DBUG_ENTER("PROFILING::fill_statistics_info");
+ TABLE *table= tables->table;
+ ulonglong row_number= 0;
+
+ QUERY_PROFILE *query;
+ /* Go through each query in this thread's stored history... */
+ void *history_iterator;
+ for (history_iterator= history.new_iterator();
+ history_iterator != NULL;
+ history_iterator= history.iterator_next(history_iterator))
+ {
+ query= history.iterator_value(history_iterator);
+
+ /*
+ Because we put all profiling info into a table that may be reordered, let
+ us also include a numbering of each state per query. The query_id and
+ the "seq" together are unique.
+ */
+ ulong seq;
+
+ void *entry_iterator;
+ PROF_MEASUREMENT *entry, *previous= NULL;
+ /* ...and for each query, go through all its state-change steps. */
+ for (entry_iterator= query->entries.new_iterator();
+ entry_iterator != NULL;
+ entry_iterator= query->entries.iterator_next(entry_iterator),
+ previous=entry, row_number++)
+ {
+ entry= query->entries.iterator_value(entry_iterator);
+ seq= entry->m_seq;
+
+ /* Skip the first. We count spans of fence, not fence-posts. */
+ if (previous == NULL) continue;
+
+ if (thd_arg->lex->sql_command == SQLCOM_SHOW_PROFILE)
+ {
+ /*
+ We got here via a SHOW command. That means that we stored
+ information about the query we wish to show and that isn't
+ in a WHERE clause at a higher level to filter out rows we
+ wish to exclude.
+
+ Because that functionality isn't available in the server yet,
+ we must filter here, at the wrong level. Once one can con-
+ struct where and having conditions at the SQL layer, then this
+ condition should be ripped out.
+ */
+ if (thd_arg->lex->profile_query_id == 0) /* 0 == show final query */
+ {
+ if (query != last)
+ continue;
+ }
+ else
+ {
+ if (thd_arg->lex->profile_query_id != query->profiling_query_id)
+ continue;
+ }
+ }
+
+ /* Set default values for this row. */
+ restore_record(table, s->default_values);
+
+ /*
+ The order of these fields is set by the query_profile_statistics_info
+ array.
+ */
+ table->field[0]->store((ulonglong) query->profiling_query_id, TRUE);
+ table->field[1]->store((ulonglong) seq, TRUE); /* the step in the sequence */
+ /*
+ This entry, n, has a point in time, T(n), and a status phrase, S(n).
+ The status phrase S(n) describes the period of time that begins at
+ T(n). The previous status phrase S(n-1) describes the period of time
+ that starts at T(n-1) and ends at T(n). Since we want to describe the
+ time that a status phrase took T(n)-T(n-1), this line must describe the
+ previous status.
+ */
+ table->field[2]->store(previous->status, strlen(previous->status),
+ system_charset_info);
+
+ my_decimal duration_decimal;
+ double2my_decimal(E_DEC_FATAL_ERROR,
+ (entry->time_usecs-previous->time_usecs)/(1000.0*1000),
+ &duration_decimal);
+
+ table->field[3]->store_decimal(&duration_decimal);
+
+
+#ifdef HAVE_GETRUSAGE
+
+ my_decimal cpu_utime_decimal, cpu_stime_decimal;
+
+ double2my_decimal(E_DEC_FATAL_ERROR,
+ RUSAGE_DIFF_USEC(entry->rusage.ru_utime,
+ previous->rusage.ru_utime) /
+ (1000.0*1000),
+ &cpu_utime_decimal);
+
+ double2my_decimal(E_DEC_FATAL_ERROR,
+ RUSAGE_DIFF_USEC(entry->rusage.ru_stime,
+ previous->rusage.ru_stime) /
+ (1000.0*1000),
+ &cpu_stime_decimal);
+
+ table->field[4]->store_decimal(&cpu_utime_decimal);
+ table->field[5]->store_decimal(&cpu_stime_decimal);
+ table->field[4]->set_notnull();
+ table->field[5]->set_notnull();
+#elif defined(_WIN32)
+ my_decimal cpu_utime_decimal, cpu_stime_decimal;
+
+ double2my_decimal(E_DEC_FATAL_ERROR,
+ GetTimeDiffInSeconds(&entry->ftUser,
+ &previous->ftUser),
+ &cpu_utime_decimal);
+ double2my_decimal(E_DEC_FATAL_ERROR,
+ GetTimeDiffInSeconds(&entry->ftKernel,
+ &previous->ftKernel),
+ &cpu_stime_decimal);
+
+ // Store the result.
+ table->field[4]->store_decimal(&cpu_utime_decimal);
+ table->field[5]->store_decimal(&cpu_stime_decimal);
+ table->field[4]->set_notnull();
+ table->field[5]->set_notnull();
+#else
+ /* TODO: Add CPU-usage info for non-BSD systems */
+#endif
+
+#ifdef HAVE_GETRUSAGE
+ table->field[6]->store((uint32)(entry->rusage.ru_nvcsw -
+ previous->rusage.ru_nvcsw));
+ table->field[6]->set_notnull();
+ table->field[7]->store((uint32)(entry->rusage.ru_nivcsw -
+ previous->rusage.ru_nivcsw));
+ table->field[7]->set_notnull();
+#else
+ /* TODO: Add context switch info for non-BSD systems */
+#endif
+
+#ifdef HAVE_GETRUSAGE
+ table->field[8]->store((uint32)(entry->rusage.ru_inblock -
+ previous->rusage.ru_inblock));
+ table->field[8]->set_notnull();
+ table->field[9]->store((uint32)(entry->rusage.ru_oublock -
+ previous->rusage.ru_oublock));
+ table->field[9]->set_notnull();
+#elif defined(__WIN__)
+ ULONGLONG reads_delta = entry->io_count.ReadOperationCount -
+ previous->io_count.ReadOperationCount;
+ ULONGLONG writes_delta = entry->io_count.WriteOperationCount -
+ previous->io_count.WriteOperationCount;
+
+ table->field[8]->store((uint32)reads_delta);
+ table->field[8]->set_notnull();
+
+ table->field[9]->store((uint32)writes_delta);
+ table->field[9]->set_notnull();
+#else
+ /* TODO: Add block IO info for non-BSD systems */
+#endif
+
+#ifdef HAVE_GETRUSAGE
+ table->field[10]->store((uint32)(entry->rusage.ru_msgsnd -
+ previous->rusage.ru_msgsnd), true);
+ table->field[10]->set_notnull();
+ table->field[11]->store((uint32)(entry->rusage.ru_msgrcv -
+ previous->rusage.ru_msgrcv), true);
+ table->field[11]->set_notnull();
+#else
+ /* TODO: Add message info for non-BSD systems */
+#endif
+
+#ifdef HAVE_GETRUSAGE
+ table->field[12]->store((uint32)(entry->rusage.ru_majflt -
+ previous->rusage.ru_majflt), true);
+ table->field[12]->set_notnull();
+ table->field[13]->store((uint32)(entry->rusage.ru_minflt -
+ previous->rusage.ru_minflt), true);
+ table->field[13]->set_notnull();
+#elif defined(__WIN__)
+ /* Windows APIs don't easily distinguish between hard and soft page
+ faults, so we just fill the 'major' column and leave the second NULL.
+ */
+ table->field[12]->store((uint32)(entry->mem_count.PageFaultCount -
+ previous->mem_count.PageFaultCount), true);
+ table->field[12]->set_notnull();
+#else
+ /* TODO: Add page fault info for non-BSD systems */
+#endif
+
+#ifdef HAVE_GETRUSAGE
+ table->field[14]->store((uint32)(entry->rusage.ru_nswap -
+ previous->rusage.ru_nswap), true);
+ table->field[14]->set_notnull();
+#else
+ /* TODO: Add swap info for non-BSD systems */
+#endif
+
+ /* Emit the location that started this step, not that ended it. */
+ if ((previous->function != NULL) && (previous->file != NULL))
+ {
+ table->field[15]->store(previous->function, strlen(previous->function),
+ system_charset_info);
+ table->field[15]->set_notnull();
+ table->field[16]->store(previous->file, strlen(previous->file), system_charset_info);
+ table->field[16]->set_notnull();
+ table->field[17]->store(previous->line, true);
+ table->field[17]->set_notnull();
+ }
+
+ if (schema_table_store_record(thd_arg, table))
+ DBUG_RETURN(1);
+
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+#endif /* ENABLED_PROFILING */
diff --git a/sql/sql_profile.h b/sql/sql_profile.h
new file mode 100644
index 00000000000..f8970bb162a
--- /dev/null
+++ b/sql/sql_profile.h
@@ -0,0 +1,293 @@
+/* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef _SQL_PROFILE_H
+#define _SQL_PROFILE_H
+
+class Item;
+struct TABLE_LIST;
+class THD;
+typedef struct st_field_info ST_FIELD_INFO;
+typedef struct st_schema_table ST_SCHEMA_TABLE;
+
+extern ST_FIELD_INFO query_profile_statistics_info[];
+int fill_query_profile_statistics_info(THD *thd, TABLE_LIST *tables, Item *cond);
+int make_profile_table_for_show(THD *thd, ST_SCHEMA_TABLE *schema_table);
+
+
+#define PROFILE_NONE (uint)0
+#define PROFILE_CPU (uint)(1<<0)
+#define PROFILE_MEMORY (uint)(1<<1)
+#define PROFILE_BLOCK_IO (uint)(1<<2)
+#define PROFILE_CONTEXT (uint)(1<<3)
+#define PROFILE_PAGE_FAULTS (uint)(1<<4)
+#define PROFILE_IPC (uint)(1<<5)
+#define PROFILE_SWAPS (uint)(1<<6)
+#define PROFILE_SOURCE (uint)(1<<16)
+#define PROFILE_ALL (uint)(~0)
+
+
+#if defined(ENABLED_PROFILING)
+#include "sql_priv.h"
+#include "unireg.h"
+
+#ifdef __WIN__
+#include <psapi.h>
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+
+class PROF_MEASUREMENT;
+class QUERY_PROFILE;
+class PROFILING;
+
+
+/**
+ Implements a persistent FIFO using server List method names. Not
+ thread-safe. Intended to be used on thread-local data only.
+*/
+template <class T> class Queue
+{
+private:
+
+ struct queue_item
+ {
+ T *payload;
+ struct queue_item *next, *previous;
+ };
+
+ struct queue_item *first, *last;
+
+public:
+ Queue()
+ {
+ elements= 0;
+ first= last= NULL;
+ }
+
+ void empty()
+ {
+ struct queue_item *i, *after_i;
+ for (i= first; i != NULL; i= after_i)
+ {
+ after_i= i->next;
+ my_free(i);
+ }
+ elements= 0;
+ }
+
+ ulong elements; /* The count of items in the Queue */
+
+ void push_back(T *payload)
+ {
+ struct queue_item *new_item;
+
+ new_item= (struct queue_item *) my_malloc(sizeof(struct queue_item), MYF(0));
+
+ new_item->payload= payload;
+
+ if (first == NULL)
+ first= new_item;
+ if (last != NULL)
+ {
+ DBUG_ASSERT(last->next == NULL);
+ last->next= new_item;
+ }
+ new_item->previous= last;
+ new_item->next= NULL;
+ last= new_item;
+
+ elements++;
+ }
+
+ T *pop()
+ {
+ struct queue_item *old_item= first;
+ T *ret= NULL;
+
+ if (first == NULL)
+ {
+ DBUG_PRINT("warning", ("tried to pop nonexistent item from Queue"));
+ return NULL;
+ }
+
+ ret= old_item->payload;
+ if (first->next != NULL)
+ first->next->previous= NULL;
+ else
+ last= NULL;
+ first= first->next;
+
+ my_free(old_item);
+ elements--;
+
+ return ret;
+ }
+
+ bool is_empty()
+ {
+ DBUG_ASSERT(((elements > 0) && (first != NULL)) || ((elements == 0) || (first == NULL)));
+ return (elements == 0);
+ }
+
+ void *new_iterator()
+ {
+ return first;
+ }
+
+ void *iterator_next(void *current)
+ {
+ return ((struct queue_item *) current)->next;
+ }
+
+ T *iterator_value(void *current)
+ {
+ return ((struct queue_item *) current)->payload;
+ }
+
+};
+
+
+/**
+ A single entry in a single profile.
+*/
+class PROF_MEASUREMENT
+{
+private:
+ friend class QUERY_PROFILE;
+ friend class PROFILING;
+
+ QUERY_PROFILE *profile;
+ char *status;
+#ifdef HAVE_GETRUSAGE
+ struct rusage rusage;
+#elif defined(_WIN32)
+ FILETIME ftKernel, ftUser;
+ IO_COUNTERS io_count;
+ PROCESS_MEMORY_COUNTERS mem_count;
+#endif
+
+ char *function;
+ char *file;
+ unsigned int line;
+
+ ulong m_seq;
+ double time_usecs;
+ char *allocated_status_memory;
+
+ void set_label(const char *status_arg, const char *function_arg,
+ const char *file_arg, unsigned int line_arg);
+ void clean_up();
+
+ PROF_MEASUREMENT();
+ PROF_MEASUREMENT(QUERY_PROFILE *profile_arg, const char *status_arg);
+ PROF_MEASUREMENT(QUERY_PROFILE *profile_arg, const char *status_arg,
+ const char *function_arg,
+ const char *file_arg, unsigned int line_arg);
+ ~PROF_MEASUREMENT();
+ void collect();
+};
+
+
+/**
+ The full profile for a single query, and includes multiple PROF_MEASUREMENT
+ objects.
+*/
+class QUERY_PROFILE
+{
+private:
+ friend class PROFILING;
+
+ PROFILING *profiling;
+
+ query_id_t profiling_query_id; /* Session-specific id. */
+ char *query_source;
+
+ double m_start_time_usecs;
+ double m_end_time_usecs;
+ ulong m_seq_counter;
+ Queue<PROF_MEASUREMENT> entries;
+
+
+ QUERY_PROFILE(PROFILING *profiling_arg, const char *status_arg);
+ ~QUERY_PROFILE();
+
+ void set_query_source(char *query_source_arg, uint query_length_arg);
+
+ /* Add a profile status change to the current profile. */
+ void new_status(const char *status_arg,
+ const char *function_arg,
+ const char *file_arg, unsigned int line_arg);
+
+ /* Reset the contents of this profile entry. */
+ void reset();
+
+ /* Show this profile. This is called by PROFILING. */
+ bool show(uint options);
+};
+
+
+/**
+ Profiling state for a single THD; contains multiple QUERY_PROFILE objects.
+*/
+class PROFILING
+{
+private:
+ friend class PROF_MEASUREMENT;
+ friend class QUERY_PROFILE;
+
+ /*
+ Not the system query_id, but a counter unique to profiling.
+ */
+ query_id_t profile_id_counter;
+ THD *thd;
+ bool keeping;
+ bool enabled;
+
+ QUERY_PROFILE *current;
+ QUERY_PROFILE *last;
+ Queue<QUERY_PROFILE> history;
+
+ query_id_t next_profile_id() { return(profile_id_counter++); }
+
+public:
+ PROFILING();
+ ~PROFILING();
+ void set_query_source(char *query_source_arg, uint query_length_arg);
+
+ void start_new_query(const char *initial_state= "starting");
+
+ void discard_current_query();
+
+ void finish_current_query();
+
+ void status_change(const char *status_arg,
+ const char *function_arg,
+ const char *file_arg, unsigned int line_arg);
+
+ inline void set_thd(THD *thd_arg) { thd= thd_arg; };
+
+ /* SHOW PROFILES */
+ bool show_profiles();
+
+ /* ... from INFORMATION_SCHEMA.PROFILING ... */
+ int fill_statistics_info(THD *thd, TABLE_LIST *tables, Item *cond);
+};
+
+# endif /* ENABLED_PROFILING */
+#endif /* _SQL_PROFILE_H */
diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc
new file mode 100644
index 00000000000..7390aa0bb0f
--- /dev/null
+++ b/sql/sql_reload.cc
@@ -0,0 +1,595 @@
+/* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_reload.h"
+#include "sql_priv.h"
+#include "mysqld.h" // select_errors
+#include "sql_class.h" // THD
+#include "sql_acl.h" // acl_reload
+#include "sql_servers.h" // servers_reload
+#include "sql_connect.h" // reset_mqh
+#include "sql_base.h" // close_cached_tables
+#include "sql_db.h" // my_dbopt_cleanup
+#include "hostname.h" // hostname_cache_refresh
+#include "sql_repl.h" // reset_master, reset_slave
+#include "rpl_mi.h" // Master_info::data_lock
+#include "debug_sync.h"
+#include "rpl_mi.h"
+
+static void disable_checkpoints(THD *thd);
+
+/**
+ Reload/resets privileges and the different caches.
+
+ @param thd Thread handler (can be NULL!)
+ @param options What should be reset/reloaded (tables, privileges, slave...)
+ @param tables Tables to flush (if any)
+ @param write_to_binlog < 0 if there was an error while interacting with the binary log inside
+ reload_acl_and_cache,
+ 0 if we should not write to the binary log,
+ > 0 if we can write to the binlog.
+
+
+ @note Depending on 'options', it may be very bad to write the
+ query to the binlog (e.g. FLUSH SLAVE); this is a
+ pointer where reload_acl_and_cache() will put 0 if
+ it thinks we really should not write to the binlog.
+ Otherwise it will put 1.
+
+ @return Error status code
+ @retval 0 Ok
+ @retval !=0 Error; thd->killed is set or thd->is_error() is true
+*/
+
+bool reload_acl_and_cache(THD *thd, unsigned long long options,
+ TABLE_LIST *tables, int *write_to_binlog)
+{
+ bool result=0;
+ select_errors=0; /* Write if more errors */
+ int tmp_write_to_binlog= *write_to_binlog= 1;
+
+ DBUG_ASSERT(!thd || !thd->in_sub_stmt);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (options & REFRESH_GRANT)
+ {
+ THD *tmp_thd= 0;
+ /*
+ If reload_acl_and_cache() is called from SIGHUP handler we have to
+ allocate temporary THD for execution of acl_reload()/grant_reload().
+ */
+ if (!thd && (thd= (tmp_thd= new THD)))
+ {
+ thd->thread_stack= (char*) &tmp_thd;
+ thd->store_globals();
+ }
+
+ if (thd)
+ {
+ bool reload_acl_failed= acl_reload(thd);
+ bool reload_grants_failed= grant_reload(thd);
+ bool reload_servers_failed= servers_reload(thd);
+
+ if (reload_acl_failed || reload_grants_failed || reload_servers_failed)
+ {
+ result= 1;
+ /*
+ When an error is returned, my_message may have not been called and
+ the client will hang waiting for a response.
+ */
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ }
+ }
+
+ if (tmp_thd)
+ {
+ delete tmp_thd;
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+ thd= 0;
+ }
+ reset_mqh((LEX_USER *)NULL, TRUE);
+ }
+#endif
+ if (options & REFRESH_LOG)
+ {
+ /*
+ Flush the normal query log, the update log, the binary log,
+ the slow query log, the relay log (if it exists) and the log
+ tables.
+ */
+
+ options|= REFRESH_BINARY_LOG;
+ options|= REFRESH_RELAY_LOG;
+ options|= REFRESH_SLOW_LOG;
+ options|= REFRESH_GENERAL_LOG;
+ options|= REFRESH_ENGINE_LOG;
+ options|= REFRESH_ERROR_LOG;
+ }
+
+ if (options & REFRESH_ERROR_LOG)
+ if (flush_error_log())
+ {
+ /*
+ When flush_error_log() failed, my_error() has not been called.
+ So, we have to do it here to keep the protocol.
+ */
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ result= 1;
+ }
+
+ if ((options & REFRESH_SLOW_LOG) && opt_slow_log)
+ logger.flush_slow_log();
+
+ if ((options & REFRESH_GENERAL_LOG) && opt_log)
+ logger.flush_general_log();
+
+ if (options & REFRESH_ENGINE_LOG)
+ if (ha_flush_logs(NULL))
+ result= 1;
+
+ if (options & REFRESH_BINARY_LOG)
+ {
+ /*
+ Writing this command to the binlog may result in infinite loops
+ when doing mysqlbinlog|mysql, and anyway it does not really make
+ sense to log it automatically (would cause more trouble to users
+ than it would help them)
+ */
+ tmp_write_to_binlog= 0;
+ if (mysql_bin_log.is_open())
+ {
+ if (mysql_bin_log.rotate_and_purge(true))
+ *write_to_binlog= -1;
+ }
+ }
+ if (options & REFRESH_RELAY_LOG)
+ {
+#ifdef HAVE_REPLICATION
+ LEX_STRING connection_name;
+ Master_info *mi;
+ if (thd)
+ connection_name= thd->lex->relay_log_connection_name;
+ else
+ {
+ connection_name.str= (char*) "";
+ connection_name.length= 0;
+ }
+
+ /*
+ Writing this command to the binlog may cause problems as the
+ slave is not likely to have the same connection names.
+ */
+ tmp_write_to_binlog= 0;
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index)
+ {
+ if (!(mi= (master_info_index->
+ get_master_info(&connection_name,
+ Sql_condition::WARN_LEVEL_ERROR))))
+ {
+ result= 1;
+ }
+ else
+ {
+ mysql_mutex_lock(&mi->data_lock);
+ if (rotate_relay_log(mi))
+ *write_to_binlog= -1;
+ mysql_mutex_unlock(&mi->data_lock);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+#endif
+ }
+#ifdef HAVE_QUERY_CACHE
+ if (options & REFRESH_QUERY_CACHE_FREE)
+ {
+ query_cache.pack(thd); // FLUSH QUERY CACHE
+ options &= ~REFRESH_QUERY_CACHE; // Don't flush cache, just free memory
+ }
+ if (options & (REFRESH_TABLES | REFRESH_QUERY_CACHE))
+ {
+ query_cache.flush(); // RESET QUERY CACHE
+ }
+#endif /*HAVE_QUERY_CACHE*/
+
+ DBUG_ASSERT(!thd || thd->locked_tables_mode ||
+ !thd->mdl_context.has_locks() ||
+ thd->handler_tables_hash.records ||
+ thd->ull_hash.records ||
+ thd->global_read_lock.is_acquired());
+
+ /*
+ Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too
+ (see sql_yacc.yy)
+ */
+ if (options & (REFRESH_TABLES | REFRESH_READ_LOCK))
+ {
+ if ((options & REFRESH_READ_LOCK) && thd)
+ {
+ /*
+ On the first hand we need write lock on the tables to be flushed,
+ on the other hand we must not try to aspire a global read lock
+ if we have a write locked table as this would lead to a deadlock
+ when trying to reopen (and re-lock) the table after the flush.
+ */
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ return 1;
+ }
+ /*
+ Writing to the binlog could cause deadlocks, as we don't log
+ UNLOCK TABLES
+ */
+ tmp_write_to_binlog= 0;
+ if (thd->global_read_lock.lock_global_read_lock(thd))
+ return 1; // Killed
+ if (close_cached_tables(thd, tables,
+ ((options & REFRESH_FAST) ? FALSE : TRUE),
+ thd->variables.lock_wait_timeout))
+ {
+ /*
+ NOTE: my_error() has been already called by reopen_tables() within
+ close_cached_tables().
+ */
+ result= 1;
+ }
+
+ if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed
+ {
+ /* Don't leave things in a half-locked state */
+ thd->global_read_lock.unlock_global_read_lock(thd);
+ return 1;
+ }
+ if (options & REFRESH_CHECKPOINT)
+ disable_checkpoints(thd);
+ }
+ else
+ {
+ if (thd && thd->locked_tables_mode)
+ {
+ /*
+ If we are under LOCK TABLES we should have a write
+ lock on tables which we are going to flush.
+ */
+ if (tables)
+ {
+ for (TABLE_LIST *t= tables; t; t= t->next_local)
+ if (!find_table_for_mdl_upgrade(thd, t->db, t->table_name, false))
+ return 1;
+ }
+ else
+ {
+ /*
+ It is not safe to upgrade the metadata lock without GLOBAL IX lock.
+ This can happen with FLUSH TABLES <list> WITH READ LOCK as we in
+ these cases don't take a GLOBAL IX lock in order to be compatible
+ with global read lock.
+ */
+ if (thd->open_tables &&
+ !thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
+ MDL_INTENTION_EXCLUSIVE))
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+ thd->open_tables->s->table_name.str);
+ return true;
+ }
+
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+ {
+ if (! tab->mdl_ticket->is_upgradable_or_exclusive())
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+ tab->s->table_name.str);
+ return 1;
+ }
+ }
+ }
+ }
+
+ if (close_cached_tables(thd, tables,
+ ((options & REFRESH_FAST) ? FALSE : TRUE),
+ (thd ? thd->variables.lock_wait_timeout :
+ LONG_TIMEOUT)))
+ {
+ /*
+ NOTE: my_error() has been already called by reopen_tables() within
+ close_cached_tables().
+ */
+ result= 1;
+ }
+ }
+ my_dbopt_cleanup();
+ }
+ if (options & REFRESH_HOSTS)
+ hostname_cache_refresh();
+ if (thd && (options & REFRESH_STATUS))
+ refresh_status(thd);
+ if (options & REFRESH_THREADS)
+ flush_thread_cache();
+#ifdef HAVE_REPLICATION
+ if (options & REFRESH_MASTER)
+ {
+ DBUG_ASSERT(thd);
+ tmp_write_to_binlog= 0;
+ if (reset_master(thd, NULL, 0))
+ {
+ /* NOTE: my_error() has been already called by reset_master(). */
+ result= 1;
+ }
+ }
+#endif
+#ifdef OPENSSL
+ if (options & REFRESH_DES_KEY_FILE)
+ {
+ if (des_key_file && load_des_key_file(des_key_file))
+ {
+ /* NOTE: my_error() has been already called by load_des_key_file(). */
+ result= 1;
+ }
+ }
+#endif
+#ifdef HAVE_REPLICATION
+ if (options & REFRESH_SLAVE)
+ {
+ LEX_MASTER_INFO* lex_mi= &thd->lex->mi;
+ Master_info *mi;
+ tmp_write_to_binlog= 0;
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index)
+ {
+ if (!(mi= (master_info_index->
+ get_master_info(&lex_mi->connection_name,
+ Sql_condition::WARN_LEVEL_ERROR))))
+ {
+ result= 1;
+ }
+ else if (reset_slave(thd, mi))
+ {
+ /* NOTE: my_error() has been already called by reset_slave(). */
+ result= 1;
+ }
+ else if (mi->connection_name.length && thd->lex->reset_slave_info.all)
+ {
+ /* If not default connection and 'all' is used */
+ master_info_index->remove_master_info(&mi->connection_name);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ }
+#endif
+ if (options & REFRESH_USER_RESOURCES)
+ reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */
+ if (options & REFRESH_TABLE_STATS)
+ {
+ mysql_mutex_lock(&LOCK_global_table_stats);
+ free_global_table_stats();
+ init_global_table_stats();
+ mysql_mutex_unlock(&LOCK_global_table_stats);
+ }
+ if (options & REFRESH_INDEX_STATS)
+ {
+ mysql_mutex_lock(&LOCK_global_index_stats);
+ free_global_index_stats();
+ init_global_index_stats();
+ mysql_mutex_unlock(&LOCK_global_index_stats);
+ }
+ if (options & (REFRESH_USER_STATS | REFRESH_CLIENT_STATS))
+ {
+ mysql_mutex_lock(&LOCK_global_user_client_stats);
+ if (options & REFRESH_USER_STATS)
+ {
+ free_global_user_stats();
+ init_global_user_stats();
+ }
+ if (options & REFRESH_CLIENT_STATS)
+ {
+ free_global_client_stats();
+ init_global_client_stats();
+ }
+ mysql_mutex_unlock(&LOCK_global_user_client_stats);
+ }
+ if (*write_to_binlog != -1)
+ *write_to_binlog= tmp_write_to_binlog;
+ /*
+ If the query was killed then this function must fail.
+ */
+ return result || (thd ? thd->killed : 0);
+}
+
+
+/**
+ Implementation of FLUSH TABLES <table_list> WITH READ LOCK
+ and FLUSH TABLES <table_list> FOR EXPORT
+
+ In brief: take exclusive locks, expel tables from the table
+ cache, reopen the tables, enter the 'LOCKED TABLES' mode,
+ downgrade the locks.
+ Note: the function is written to be called from
+ mysql_execute_command(), it is not reusable in arbitrary
+ execution context.
+
+ Required privileges
+ -------------------
+ Since the statement implicitly enters LOCK TABLES mode,
+ it requires LOCK TABLES privilege on every table.
+ But since the rest of FLUSH commands require
+ the global RELOAD_ACL, it also requires RELOAD_ACL.
+
+ Compatibility with the global read lock
+ ---------------------------------------
+ We don't wait for the GRL, since neither the
+ 5.1 combination that this new statement is intended to
+ replace (LOCK TABLE <list> WRITE; FLUSH TABLES;),
+ nor FLUSH TABLES WITH READ LOCK do.
+ @todo: this is not implemented, Dmitry disagrees.
+ Currently we wait for GRL in another connection,
+ but are compatible with a GRL in our own connection.
+
+ Behaviour under LOCK TABLES
+ ---------------------------
+ Bail out: i.e. don't perform an implicit UNLOCK TABLES.
+ This is not consistent with LOCK TABLES statement, but is
+ in line with behaviour of FLUSH TABLES WITH READ LOCK, and we
+ try to not introduce any new statements with implicit
+ semantics.
+
+ Compatibility with parallel updates
+ -----------------------------------
+ As a result, we will wait for all open transactions
+ against the tables to complete. After the lock downgrade,
+ new transactions will be able to read the tables, but not
+ write to them.
+
+ Differences from FLUSH TABLES <list>
+ -------------------------------------
+ - you can't flush WITH READ LOCK a non-existent table
+ - you can't flush WITH READ LOCK under LOCK TABLES
+
+ Effect on views and temporary tables.
+ ------------------------------------
+ You can only apply this command to existing base tables.
+ If a view with such name exists, ER_WRONG_OBJECT is returned.
+ If a temporary table with such name exists, it's ignored:
+ if there is a base table, it's used, otherwise ER_NO_SUCH_TABLE
+ is returned.
+
+ Handling of MERGE tables
+ ------------------------
+ For MERGE table this statement will open and lock child tables
+ for read (it is impossible to lock parent table without it).
+ Child tables won't be flushed unless they are explicitly present
+ in the statement's table list.
+
+ Implicit commit
+ ---------------
+ This statement causes an implicit commit before and
+ after it.
+
+ HANDLER SQL
+ -----------
+ If this connection has HANDLERs open against
+ some of the tables being FLUSHed, these handlers
+ are implicitly flushed (lose their position).
+*/
+
+bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables)
+{
+ Lock_tables_prelocking_strategy lock_tables_prelocking_strategy;
+ TABLE_LIST *table_list;
+
+ /*
+ This is called from SQLCOM_FLUSH, the transaction has
+ been committed implicitly.
+ */
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ goto error;
+ }
+
+ if (thd->lex->type & REFRESH_READ_LOCK)
+ {
+ /*
+ Acquire SNW locks on tables to be flushed. Don't acquire global
+ IX and database-scope IX locks on the tables as this will make
+ this statement incompatible with FLUSH TABLES WITH READ LOCK.
+ */
+ if (lock_table_names(thd, all_tables, NULL,
+ thd->variables.lock_wait_timeout,
+ MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK))
+ goto error;
+
+ DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks");
+
+ for (table_list= all_tables; table_list;
+ table_list= table_list->next_global)
+ {
+ /* Request removal of table from cache. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED,
+ table_list->db,
+ table_list->table_name, FALSE);
+ /* Reset ticket to satisfy asserts in open_tables(). */
+ table_list->mdl_request.ticket= NULL;
+ }
+ }
+
+ /*
+ Before opening and locking tables the below call also waits
+ for old shares to go away, so the fact that we don't pass
+ MYSQL_OPEN_IGNORE_FLUSH flag to it is important.
+ Also we don't pass MYSQL_OPEN_HAS_MDL_LOCK flag as we want
+ to open underlying tables if merge table is flushed.
+ For underlying tables of the merge the below call has to
+ acquire SNW locks to ensure that they can be locked for
+ read without further waiting.
+ */
+ if (open_and_lock_tables(thd, all_tables, FALSE,
+ MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK,
+ &lock_tables_prelocking_strategy))
+ goto error;
+
+ if (thd->lex->type & REFRESH_FOR_EXPORT)
+ {
+ // Check if all storage engines support FOR EXPORT.
+ for (TABLE_LIST *table_list= all_tables; table_list;
+ table_list= table_list->next_global)
+ {
+ if (!(table_list->table->file->ha_table_flags() & HA_CAN_EXPORT))
+ {
+ my_error(ER_ILLEGAL_HA, MYF(0),table_list->table->file->table_type(),
+ table_list->db, table_list->table_name);
+ return true;
+ }
+ }
+ }
+
+ if (thd->locked_tables_list.init_locked_tables(thd))
+ goto error;
+
+ thd->variables.option_bits|= OPTION_TABLE_LOCK;
+
+ /*
+ We don't downgrade MDL_SHARED_NO_WRITE here as the intended
+ post effect of this call is identical to LOCK TABLES <...> READ,
+ and we didn't use thd->in_lock_talbes and
+ thd->sql_command= SQLCOM_LOCK_TABLES hacks to enter the LTM.
+ */
+
+ return FALSE;
+
+error:
+ return TRUE;
+}
+
+
+/**
+ Disable checkpoints for all handlers
+ This is released in unlock_global_read_lock()
+*/
+
+static void disable_checkpoints(THD *thd)
+{
+ if (!thd->global_disable_checkpoint)
+ {
+ thd->global_disable_checkpoint= 1;
+ if (!global_disable_checkpoint++)
+ ha_checkpoint_state(1); // Disable checkpoints
+ }
+}
+
diff --git a/sql/sql_reload.h b/sql/sql_reload.h
new file mode 100644
index 00000000000..33ca022dc14
--- /dev/null
+++ b/sql/sql_reload.h
@@ -0,0 +1,26 @@
+#ifndef SQL_RELOAD_INCLUDED
+#define SQL_RELOAD_INCLUDED
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+class THD;
+struct TABLE_LIST;
+
+bool reload_acl_and_cache(THD *thd, unsigned long long options,
+ TABLE_LIST *tables, int *write_to_binlog);
+
+bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables);
+
+#endif
diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc
new file mode 100644
index 00000000000..2c17898f07c
--- /dev/null
+++ b/sql/sql_rename.cc
@@ -0,0 +1,363 @@
+/*
+ Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2013, Monty Program Ab.
+
+ 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 */
+
+/*
+ Atomic rename of table; RENAME TABLE t1 to t2, tmp to t1 [,...]
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_rename.h"
+#include "sql_cache.h" // query_cache_*
+#include "sql_table.h" // build_table_filename
+#include "sql_view.h" // mysql_frm_type, mysql_rename_view
+#include "sql_trigger.h"
+#include "lock.h" // MYSQL_OPEN_SKIP_TEMPORARY
+#include "sql_base.h" // tdc_remove_table, lock_table_names,
+#include "sql_handler.h" // mysql_ha_rm_tables
+#include "sql_statistics.h"
+
+static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list,
+ bool skip_error);
+static bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db,
+ char *new_table_name, char *new_table_alias,
+ bool skip_error);
+
+static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list);
+
+/*
+ Every two entries in the table_list form a pair of original name and
+ the new name.
+*/
+
+bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
+{
+ bool error= 1;
+ bool binlog_error= 0;
+ TABLE_LIST *ren_table= 0;
+ int to_table;
+ char *rename_log_table[2]= {NULL, NULL};
+ DBUG_ENTER("mysql_rename_tables");
+
+ /*
+ Avoid problems with a rename on a table that we have locked or
+ if the user is trying to to do this in a transcation context
+ */
+
+ if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction())
+ {
+ my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
+ ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ mysql_ha_rm_tables(thd, table_list);
+
+ if (logger.is_log_table_enabled(QUERY_LOG_GENERAL) ||
+ logger.is_log_table_enabled(QUERY_LOG_SLOW))
+ {
+
+ /*
+ Rules for rename of a log table:
+
+ IF 1. Log tables are enabled
+ AND 2. Rename operates on the log table and nothing is being
+ renamed to the log table.
+ DO 3. Throw an error message.
+ ELSE 4. Perform rename.
+ */
+
+ for (to_table= 0, ren_table= table_list; ren_table;
+ to_table= 1 - to_table, ren_table= ren_table->next_local)
+ {
+ int log_table_rename;
+ if ((log_table_rename= check_if_log_table(ren_table, TRUE, NullS)))
+ {
+ /*
+ as we use log_table_rename as an array index, we need it to start
+ with 0, while QUERY_LOG_SLOW == 1 and QUERY_LOG_GENERAL == 2.
+ So, we shift the value to start with 0;
+ */
+ log_table_rename--;
+ if (rename_log_table[log_table_rename])
+ {
+ if (to_table)
+ rename_log_table[log_table_rename]= NULL;
+ else
+ {
+ /*
+ Two renames of "log_table TO" w/o rename "TO log_table" in
+ between.
+ */
+ my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), ren_table->table_name,
+ ren_table->table_name);
+ goto err;
+ }
+ }
+ else
+ {
+ if (to_table)
+ {
+ /*
+ Attempt to rename a table TO log_table w/o renaming
+ log_table TO some table.
+ */
+ my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), ren_table->table_name,
+ ren_table->table_name);
+ goto err;
+ }
+ else
+ {
+ /* save the name of the log table to report an error */
+ rename_log_table[log_table_rename]= ren_table->table_name;
+ }
+ }
+ }
+ }
+ if (rename_log_table[0] || rename_log_table[1])
+ {
+ if (rename_log_table[0])
+ my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), rename_log_table[0],
+ rename_log_table[0]);
+ else
+ my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), rename_log_table[1],
+ rename_log_table[1]);
+ goto err;
+ }
+ }
+
+ if (lock_table_names(thd, table_list, 0, thd->variables.lock_wait_timeout,
+ 0))
+ goto err;
+
+ error=0;
+ /*
+ An exclusive lock on table names is satisfactory to ensure
+ no other thread accesses this table.
+ */
+ if ((ren_table=rename_tables(thd,table_list,0)))
+ {
+ /* Rename didn't succeed; rename back the tables in reverse order */
+ TABLE_LIST *table;
+
+ /* Reverse the table list */
+ table_list= reverse_table_list(table_list);
+
+ /* Find the last renamed table */
+ for (table= table_list;
+ table->next_local != ren_table ;
+ table= table->next_local->next_local) ;
+ table= table->next_local->next_local; // Skip error table
+ /* Revert to old names */
+ rename_tables(thd, table, 1);
+
+ /* Revert the table list (for prepared statements) */
+ table_list= reverse_table_list(table_list);
+
+ error= 1;
+ }
+
+ if (!silent && !error)
+ {
+ binlog_error= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ if (!binlog_error)
+ my_ok(thd);
+ }
+
+ if (!error)
+ query_cache_invalidate3(thd, table_list, 0);
+
+err:
+ DBUG_RETURN(error || binlog_error);
+}
+
+
+/*
+ reverse table list
+
+ SYNOPSIS
+ reverse_table_list()
+ table_list pointer to table _list
+
+ RETURN
+ pointer to new (reversed) list
+*/
+static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list)
+{
+ TABLE_LIST *prev= 0;
+
+ while (table_list)
+ {
+ TABLE_LIST *next= table_list->next_local;
+ table_list->next_local= prev;
+ prev= table_list;
+ table_list= next;
+ }
+ return (prev);
+}
+
+
+/*
+ Rename a single table or a view
+
+ SYNPOSIS
+ do_rename()
+ thd Thread handle
+ ren_table A table/view to be renamed
+ new_db The database to which the table to be moved to
+ new_table_name The new table/view name
+ new_table_alias The new table/view alias
+ skip_error Whether to skip error
+
+ DESCRIPTION
+ Rename a single table or a view.
+
+ RETURN
+ false Ok
+ true rename failed
+*/
+
+static bool
+do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name,
+ char *new_table_alias, bool skip_error)
+{
+ int rc= 1;
+ handlerton *hton;
+ bool new_exists, old_exists;
+ const char *new_alias, *old_alias;
+ DBUG_ENTER("do_rename");
+
+ if (lower_case_table_names == 2)
+ {
+ old_alias= ren_table->alias;
+ new_alias= new_table_alias;
+ }
+ else
+ {
+ old_alias= ren_table->table_name;
+ new_alias= new_table_name;
+ }
+ DBUG_ASSERT(new_alias);
+
+ new_exists= ha_table_exists(thd, new_db, new_alias);
+
+ if (new_exists)
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
+ DBUG_RETURN(1); // This can't be skipped
+ }
+
+ old_exists= ha_table_exists(thd, ren_table->db, old_alias, &hton);
+
+ if (old_exists)
+ {
+ DBUG_ASSERT(!thd->locked_tables_mode);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
+ ren_table->db, ren_table->table_name, false);
+
+ if (hton != view_pseudo_hton)
+ {
+ if (!(rc= mysql_rename_table(hton, ren_table->db, old_alias,
+ new_db, new_alias, 0)))
+ {
+ LEX_STRING db_name= { ren_table->db, ren_table->db_length };
+ LEX_STRING table_name= { ren_table->table_name,
+ ren_table->table_name_length };
+ LEX_STRING new_table= { (char *) new_alias, strlen(new_alias) };
+ (void) rename_table_in_stat_tables(thd, &db_name, &table_name,
+ &db_name, &new_table);
+ if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db,
+ old_alias,
+ ren_table->table_name,
+ new_db,
+ new_alias)))
+ {
+ /*
+ We've succeeded in renaming table's .frm and in updating
+ corresponding handler data, but have failed to update table's
+ triggers appropriately. So let us revert operations on .frm
+ and handler's data and report about failure to rename table.
+ */
+ (void) mysql_rename_table(hton, new_db, new_alias,
+ ren_table->db, old_alias, NO_FK_CHECKS);
+ }
+ }
+ }
+ else
+ {
+ /*
+ change of schema is not allowed
+ except of ALTER ...UPGRADE DATA DIRECTORY NAME command
+ because a view has valid internal db&table names in this case.
+ */
+ if (thd->lex->sql_command != SQLCOM_ALTER_DB_UPGRADE &&
+ strcmp(ren_table->db, new_db))
+ my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db, new_db);
+ else
+ rc= mysql_rename_view(thd, new_db, new_alias, ren_table);
+ }
+ }
+ else
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), ren_table->db, old_alias);
+ }
+ if (rc && !skip_error)
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+/*
+ Rename all tables in list; Return pointer to wrong entry if something goes
+ wrong. Note that the table_list may be empty!
+*/
+
+/*
+ Rename tables/views in the list
+
+ SYNPOSIS
+ rename_tables()
+ thd Thread handle
+ table_list List of tables to rename
+ skip_error Whether to skip errors
+
+ DESCRIPTION
+ Take a table/view name from and odd list element and rename it to a
+ the name taken from list element+1. Note that the table_list may be
+ empty.
+
+ RETURN
+ false Ok
+ true rename failed
+*/
+
+static TABLE_LIST *
+rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error)
+{
+ TABLE_LIST *ren_table, *new_table;
+
+ DBUG_ENTER("rename_tables");
+
+ for (ren_table= table_list; ren_table; ren_table= new_table->next_local)
+ {
+ new_table= ren_table->next_local;
+ if (do_rename(thd, ren_table, new_table->db, new_table->table_name,
+ new_table->alias, skip_error))
+ DBUG_RETURN(ren_table);
+ }
+ DBUG_RETURN(0);
+}
diff --git a/sql/sql_rename.h b/sql/sql_rename.h
new file mode 100644
index 00000000000..aaf09a8d030
--- /dev/null
+++ b/sql/sql_rename.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_RENAME_INCLUDED
+#define SQL_RENAME_INCLUDED
+
+class THD;
+struct TABLE_LIST;
+
+bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent);
+
+#endif /* SQL_RENAME_INCLUDED */
diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc
new file mode 100644
index 00000000000..4055c8ea8af
--- /dev/null
+++ b/sql/sql_repl.cc
@@ -0,0 +1,4200 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2014, SkySQL Ab.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_base.h"
+#include "sql_parse.h" // check_access
+#ifdef HAVE_REPLICATION
+
+#include "rpl_mi.h"
+#include "rpl_rli.h"
+#include "sql_repl.h"
+#include "sql_acl.h" // SUPER_ACL
+#include "log_event.h"
+#include "rpl_filter.h"
+#include <my_dir.h>
+#include "rpl_handler.h"
+#include "debug_sync.h"
+
+
+enum enum_gtid_until_state {
+ GTID_UNTIL_NOT_DONE,
+ GTID_UNTIL_STOP_AFTER_STANDALONE,
+ GTID_UNTIL_STOP_AFTER_TRANSACTION
+};
+
+
+int max_binlog_dump_events = 0; // unlimited
+my_bool opt_sporadic_binlog_dump_fail = 0;
+#ifndef DBUG_OFF
+static int binlog_dump_count = 0;
+#endif
+
+extern TYPELIB binlog_checksum_typelib;
+
+
+static int
+fake_event_header(String* packet, Log_event_type event_type, ulong extra_len,
+ my_bool *do_checksum, ha_checksum *crc, const char** errmsg,
+ uint8 checksum_alg_arg, uint32 end_pos)
+{
+ char header[LOG_EVENT_HEADER_LEN];
+ ulong event_len;
+
+ *do_checksum= checksum_alg_arg != BINLOG_CHECKSUM_ALG_OFF &&
+ checksum_alg_arg != BINLOG_CHECKSUM_ALG_UNDEF;
+
+ /*
+ 'when' (the timestamp) is set to 0 so that slave could distinguish between
+ real and fake Rotate events (if necessary)
+ */
+ memset(header, 0, 4);
+ header[EVENT_TYPE_OFFSET] = (uchar)event_type;
+ event_len= LOG_EVENT_HEADER_LEN + extra_len +
+ (*do_checksum ? BINLOG_CHECKSUM_LEN : 0);
+ int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id);
+ int4store(header + EVENT_LEN_OFFSET, event_len);
+ int2store(header + FLAGS_OFFSET, LOG_EVENT_ARTIFICIAL_F);
+ // TODO: check what problems this may cause and fix them
+ int4store(header + LOG_POS_OFFSET, end_pos);
+ if (packet->append(header, sizeof(header)))
+ {
+ *errmsg= "Failed due to out-of-memory writing event";
+ return -1;
+ }
+ if (*do_checksum)
+ {
+ *crc= my_checksum(0L, NULL, 0);
+ *crc= my_checksum(*crc, (uchar*)header, sizeof(header));
+ }
+ return 0;
+}
+
+
+static int
+fake_event_footer(String *packet, my_bool do_checksum, ha_checksum crc, const char **errmsg)
+{
+ if (do_checksum)
+ {
+ char b[BINLOG_CHECKSUM_LEN];
+ int4store(b, crc);
+ if (packet->append(b, sizeof(b)))
+ {
+ *errmsg= "Failed due to out-of-memory writing event checksum";
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+static int
+fake_event_write(NET *net, String *packet, const char **errmsg)
+{
+ if (my_net_write(net, (uchar*) packet->ptr(), packet->length()))
+ {
+ *errmsg = "failed on my_net_write()";
+ return -1;
+ }
+ return 0;
+}
+
+
+/*
+ Helper structure, used to pass miscellaneous info from mysql_binlog_send()
+ into the helper functions that it calls.
+*/
+struct binlog_send_info {
+ rpl_binlog_state until_binlog_state;
+ slave_connection_state gtid_state;
+ THD *thd;
+ NET *net;
+ String *packet;
+ char *log_file_name;
+ slave_connection_state *until_gtid_state;
+ Format_description_log_event *fdev;
+ int mariadb_slave_capability;
+ enum_gtid_skip_type gtid_skip_group;
+ enum_gtid_until_state gtid_until_group;
+ ushort flags;
+ uint8 current_checksum_alg;
+ bool slave_gtid_strict_mode;
+ bool send_fake_gtid_list;
+ bool slave_gtid_ignore_duplicates;
+ bool using_gtid_state;
+
+ binlog_send_info(THD *thd_arg, String *packet_arg, ushort flags_arg, char *lfn)
+ : thd(thd_arg), net(&thd_arg->net), packet(packet_arg),
+ log_file_name(lfn), until_gtid_state(NULL), fdev(NULL),
+ gtid_skip_group(GTID_SKIP_NOT), gtid_until_group(GTID_UNTIL_NOT_DONE),
+ flags(flags_arg), current_checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF),
+ slave_gtid_strict_mode(false), send_fake_gtid_list(false),
+ slave_gtid_ignore_duplicates(false)
+ { }
+};
+
+/*
+ fake_rotate_event() builds a fake (=which does not exist physically in any
+ binlog) Rotate event, which contains the name of the binlog we are going to
+ send to the slave (because the slave may not know it if it just asked for
+ MASTER_LOG_FILE='', MASTER_LOG_POS=4).
+ < 4.0.14, fake_rotate_event() was called only if the requested pos was 4.
+ After this version we always call it, so that a 3.23.58 slave can rely on
+ it to detect if the master is 4.0 (and stop) (the _fake_ Rotate event has
+ zeros in the good positions which, by chance, make it possible for the 3.23
+ slave to detect that this event is unexpected) (this is luck which happens
+ because the master and slave disagree on the size of the header of
+ Log_event).
+
+ Relying on the event length of the Rotate event instead of these
+ well-placed zeros was not possible as Rotate events have a variable-length
+ part.
+*/
+
+static int fake_rotate_event(binlog_send_info *info, ulonglong position,
+ const char** errmsg, uint8 checksum_alg_arg)
+{
+ DBUG_ENTER("fake_rotate_event");
+ char buf[ROTATE_HEADER_LEN+100];
+ my_bool do_checksum;
+ int err;
+ char* p = info->log_file_name+dirname_length(info->log_file_name);
+ uint ident_len = (uint) strlen(p);
+ String *packet= info->packet;
+ ha_checksum crc;
+
+ if ((err= fake_event_header(packet, ROTATE_EVENT,
+ ident_len + ROTATE_HEADER_LEN, &do_checksum, &crc,
+ errmsg, checksum_alg_arg, 0)))
+ DBUG_RETURN(err);
+
+ int8store(buf+R_POS_OFFSET,position);
+ packet->append(buf, ROTATE_HEADER_LEN);
+ packet->append(p, ident_len);
+
+ if (do_checksum)
+ {
+ crc= my_checksum(crc, (uchar*)buf, ROTATE_HEADER_LEN);
+ crc= my_checksum(crc, (uchar*)p, ident_len);
+ }
+
+ if ((err= fake_event_footer(packet, do_checksum, crc, errmsg)) ||
+ (err= fake_event_write(info->net, packet, errmsg)))
+ DBUG_RETURN(err);
+
+ DBUG_RETURN(0);
+}
+
+
+static int fake_gtid_list_event(binlog_send_info *info,
+ Gtid_list_log_event *glev, const char** errmsg,
+ uint32 current_pos)
+{
+ my_bool do_checksum;
+ int err;
+ ha_checksum crc;
+ char buf[128];
+ String str(buf, sizeof(buf), system_charset_info);
+ String* packet= info->packet;
+
+ str.length(0);
+ if (glev->to_packet(&str))
+ {
+ *errmsg= "Failed due to out-of-memory writing Gtid_list event";
+ return -1;
+ }
+ if ((err= fake_event_header(packet, GTID_LIST_EVENT,
+ str.length(), &do_checksum, &crc,
+ errmsg, info->current_checksum_alg, current_pos)))
+ return err;
+
+ packet->append(str);
+ if (do_checksum)
+ {
+ crc= my_checksum(crc, (uchar*)str.ptr(), str.length());
+ }
+
+ if ((err= fake_event_footer(packet, do_checksum, crc, errmsg)) ||
+ (err= fake_event_write(info->net, packet, errmsg)))
+ return err;
+
+ return 0;
+}
+
+
+/*
+ Reset thread transmit packet buffer for event sending
+
+ This function allocates header bytes for event transmission, and
+ should be called before store the event data to the packet buffer.
+*/
+static int reset_transmit_packet(THD *thd, ushort flags,
+ ulong *ev_offset, const char **errmsg)
+{
+ int ret= 0;
+ String *packet= &thd->packet;
+
+ /* reserve and set default header */
+ packet->length(0);
+ packet->set("\0", 1, &my_charset_bin);
+
+ if (RUN_HOOK(binlog_transmit, reserve_header, (thd, flags, packet)))
+ {
+ *errmsg= "Failed to run hook 'reserve_header'";
+ my_errno= ER_UNKNOWN_ERROR;
+ ret= 1;
+ }
+ *ev_offset= packet->length();
+ return ret;
+}
+
+static int send_file(THD *thd)
+{
+ NET* net = &thd->net;
+ int fd = -1, error = 1;
+ size_t bytes;
+ char fname[FN_REFLEN+1];
+ const char *errmsg = 0;
+ int old_timeout;
+ unsigned long packet_len;
+ uchar buf[IO_SIZE]; // It's safe to alloc this
+ DBUG_ENTER("send_file");
+
+ /*
+ The client might be slow loading the data, give him wait_timeout to do
+ the job
+ */
+ old_timeout= net->read_timeout;
+ my_net_set_read_timeout(net, thd->variables.net_wait_timeout);
+
+ /*
+ We need net_flush here because the client will not know it needs to send
+ us the file name until it has processed the load event entry
+ */
+ if (net_flush(net) || (packet_len = my_net_read(net)) == packet_error)
+ {
+ errmsg = "while reading file name";
+ goto err;
+ }
+
+ // terminate with \0 for fn_format
+ *((char*)net->read_pos + packet_len) = 0;
+ fn_format(fname, (char*) net->read_pos + 1, "", "", 4);
+ // this is needed to make replicate-ignore-db
+ if (!strcmp(fname,"/dev/null"))
+ goto end;
+
+ if ((fd= mysql_file_open(key_file_send_file,
+ fname, O_RDONLY, MYF(0))) < 0)
+ {
+ errmsg = "on open of file";
+ goto err;
+ }
+
+ while ((long) (bytes= mysql_file_read(fd, buf, IO_SIZE, MYF(0))) > 0)
+ {
+ if (my_net_write(net, buf, bytes))
+ {
+ errmsg = "while writing data to client";
+ goto err;
+ }
+ }
+
+ end:
+ if (my_net_write(net, (uchar*) "", 0) || net_flush(net) ||
+ (my_net_read(net) == packet_error))
+ {
+ errmsg = "while negotiating file transfer close";
+ goto err;
+ }
+ error = 0;
+
+ err:
+ my_net_set_read_timeout(net, old_timeout);
+ if (fd >= 0)
+ mysql_file_close(fd, MYF(0));
+ if (errmsg)
+ {
+ sql_print_error("Failed in send_file() %s", errmsg);
+ DBUG_PRINT("error", ("%s", errmsg));
+ }
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Internal to mysql_binlog_send() routine that recalculates checksum for
+ a FD event (asserted) that needs additional arranment prior sending to slave.
+*/
+inline void fix_checksum(String *packet, ulong ev_offset)
+{
+ /* recalculate the crc for this event */
+ uint data_len = uint4korr(packet->ptr() + ev_offset + EVENT_LEN_OFFSET);
+ ha_checksum crc= my_checksum(0L, NULL, 0);
+ DBUG_ASSERT(data_len ==
+ LOG_EVENT_MINIMAL_HEADER_LEN + FORMAT_DESCRIPTION_HEADER_LEN +
+ BINLOG_CHECKSUM_ALG_DESC_LEN + BINLOG_CHECKSUM_LEN);
+ crc= my_checksum(crc, (uchar *)packet->ptr() + ev_offset, data_len -
+ BINLOG_CHECKSUM_LEN);
+ int4store(packet->ptr() + ev_offset + data_len - BINLOG_CHECKSUM_LEN, crc);
+}
+
+
+static user_var_entry * get_binlog_checksum_uservar(THD * thd)
+{
+ LEX_STRING name= { C_STRING_WITH_LEN("master_binlog_checksum")};
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry;
+}
+
+/**
+ Function for calling in mysql_binlog_send
+ to check if slave initiated checksum-handshake.
+
+ @param[in] thd THD to access a user variable
+
+ @return TRUE if handshake took place, FALSE otherwise
+*/
+
+static bool is_slave_checksum_aware(THD * thd)
+{
+ DBUG_ENTER("is_slave_checksum_aware");
+ user_var_entry *entry= get_binlog_checksum_uservar(thd);
+ DBUG_RETURN(entry? true : false);
+}
+
+/**
+ Function for calling in mysql_binlog_send
+ to get the value of @@binlog_checksum of the master at
+ time of checksum-handshake.
+
+ The value tells the master whether to compute or not, and the slave
+ to verify or not the first artificial Rotate event's checksum.
+
+ @param[in] thd THD to access a user variable
+
+ @return value of @@binlog_checksum alg according to
+ @c enum enum_binlog_checksum_alg
+*/
+
+static uint8 get_binlog_checksum_value_at_connect(THD * thd)
+{
+ uint8 ret;
+
+ DBUG_ENTER("get_binlog_checksum_value_at_connect");
+ user_var_entry *entry= get_binlog_checksum_uservar(thd);
+ if (!entry)
+ {
+ ret= BINLOG_CHECKSUM_ALG_UNDEF;
+ }
+ else
+ {
+ DBUG_ASSERT(entry->type == STRING_RESULT);
+ String str;
+ uint dummy_errors;
+ str.copy(entry->value, entry->length, &my_charset_bin, &my_charset_bin,
+ &dummy_errors);
+ ret= (uint8) find_type ((char*) str.ptr(), &binlog_checksum_typelib, 1) - 1;
+ DBUG_ASSERT(ret <= BINLOG_CHECKSUM_ALG_CRC32); // while it's just on CRC32 alg
+ }
+ DBUG_RETURN(ret);
+}
+
+/*
+ Adjust the position pointer in the binary log file for all running slaves
+
+ SYNOPSIS
+ adjust_linfo_offsets()
+ purge_offset Number of bytes removed from start of log index file
+
+ NOTES
+ - This is called when doing a PURGE when we delete lines from the
+ index log file
+
+ REQUIREMENTS
+ - Before calling this function, we have to ensure that no threads are
+ using any binary log file before purge_offset.a
+
+ TODO
+ - Inform the slave threads that they should sync the position
+ in the binary log file with flush_relay_log_info.
+ Now they sync is done for next read.
+*/
+
+void adjust_linfo_offsets(my_off_t purge_offset)
+{
+ THD *tmp;
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ I_List_iterator<THD> it(threads);
+
+ while ((tmp=it++))
+ {
+ LOG_INFO* linfo;
+ if ((linfo = tmp->current_linfo))
+ {
+ mysql_mutex_lock(&linfo->lock);
+ /*
+ Index file offset can be less that purge offset only if
+ we just started reading the index file. In that case
+ we have nothing to adjust
+ */
+ if (linfo->index_file_offset < purge_offset)
+ linfo->fatal = (linfo->index_file_offset != 0);
+ else
+ linfo->index_file_offset -= purge_offset;
+ mysql_mutex_unlock(&linfo->lock);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+}
+
+
+bool log_in_use(const char* log_name)
+{
+ size_t log_name_len = strlen(log_name) + 1;
+ THD *tmp;
+ bool result = 0;
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ I_List_iterator<THD> it(threads);
+
+ while ((tmp=it++))
+ {
+ LOG_INFO* linfo;
+ if ((linfo = tmp->current_linfo))
+ {
+ mysql_mutex_lock(&linfo->lock);
+ result = !memcmp(log_name, linfo->log_file_name, log_name_len);
+ mysql_mutex_unlock(&linfo->lock);
+ if (result)
+ break;
+ }
+ }
+
+ mysql_mutex_unlock(&LOCK_thread_count);
+ return result;
+}
+
+bool purge_error_message(THD* thd, int res)
+{
+ uint errcode;
+
+ if ((errcode= purge_log_get_error_code(res)) != 0)
+ {
+ my_message(errcode, ER(errcode), MYF(0));
+ return TRUE;
+ }
+ my_ok(thd);
+ return FALSE;
+}
+
+
+/**
+ Execute a PURGE BINARY LOGS TO <log> command.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @param to_log Name of the last log to purge.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+bool purge_master_logs(THD* thd, const char* to_log)
+{
+ char search_file_name[FN_REFLEN];
+ if (!mysql_bin_log.is_open())
+ {
+ my_ok(thd);
+ return FALSE;
+ }
+
+ mysql_bin_log.make_log_name(search_file_name, to_log);
+ return purge_error_message(thd,
+ mysql_bin_log.purge_logs(search_file_name, 0, 1,
+ 1, NULL));
+}
+
+
+/**
+ Execute a PURGE BINARY LOGS BEFORE <date> command.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @param purge_time Date before which logs should be purged.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+bool purge_master_logs_before_date(THD* thd, time_t purge_time)
+{
+ if (!mysql_bin_log.is_open())
+ {
+ my_ok(thd);
+ return 0;
+ }
+ return purge_error_message(thd,
+ mysql_bin_log.purge_logs_before_date(purge_time));
+}
+
+int test_for_non_eof_log_read_errors(int error, const char **errmsg)
+{
+ if (error == LOG_READ_EOF)
+ return 0;
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ switch (error) {
+ case LOG_READ_BOGUS:
+ *errmsg = "bogus data in log event";
+ break;
+ case LOG_READ_TOO_LARGE:
+ *errmsg = "log event entry exceeded max_allowed_packet; \
+Increase max_allowed_packet on master";
+ break;
+ case LOG_READ_IO:
+ *errmsg = "I/O error reading log event";
+ break;
+ case LOG_READ_MEM:
+ *errmsg = "memory allocation failed reading log event";
+ break;
+ case LOG_READ_TRUNC:
+ *errmsg = "binlog truncated in the middle of event; consider out of disk space on master";
+ break;
+ case LOG_READ_CHECKSUM_FAILURE:
+ *errmsg = "event read from binlog did not pass crc check";
+ break;
+ default:
+ *errmsg = "unknown error reading log event on the master";
+ break;
+ }
+ return error;
+}
+
+
+/**
+ An auxiliary function for calling in mysql_binlog_send
+ to initialize the heartbeat timeout in waiting for a binlogged event.
+
+ @param[in] thd THD to access a user variable
+
+ @return heartbeat period an ulonglong of nanoseconds
+ or zero if heartbeat was not demanded by slave
+*/
+static ulonglong get_heartbeat_period(THD * thd)
+{
+ bool null_value;
+ LEX_STRING name= { C_STRING_WITH_LEN("master_heartbeat_period")};
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry? entry->val_int(&null_value) : 0;
+}
+
+/*
+ Lookup the capabilities of the slave, which it announces by setting a value
+ MARIA_SLAVE_CAPABILITY_XXX in @mariadb_slave_capability.
+
+ Older MariaDB slaves, and other MySQL slaves, do not set
+ @mariadb_slave_capability, corresponding to a capability of
+ MARIA_SLAVE_CAPABILITY_UNKNOWN (0).
+*/
+static int
+get_mariadb_slave_capability(THD *thd)
+{
+ bool null_value;
+ const LEX_STRING name= { C_STRING_WITH_LEN("mariadb_slave_capability") };
+ const user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry ?
+ (int)(entry->val_int(&null_value)) : MARIA_SLAVE_CAPABILITY_UNKNOWN;
+}
+
+
+/*
+ Get the value of the @slave_connect_state user variable into the supplied
+ String (this is the GTID connect state requested by the connecting slave).
+
+ Returns false if error (ie. slave did not set the variable and does not
+ want to use GTID to set start position), true if success.
+*/
+static bool
+get_slave_connect_state(THD *thd, String *out_str)
+{
+ bool null_value;
+
+ const LEX_STRING name= { C_STRING_WITH_LEN("slave_connect_state") };
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry && entry->val_str(&null_value, out_str, 0) && !null_value;
+}
+
+
+static bool
+get_slave_gtid_strict_mode(THD *thd)
+{
+ bool null_value;
+
+ const LEX_STRING name= { C_STRING_WITH_LEN("slave_gtid_strict_mode") };
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry && entry->val_int(&null_value) && !null_value;
+}
+
+
+static bool
+get_slave_gtid_ignore_duplicates(THD *thd)
+{
+ bool null_value;
+
+ const LEX_STRING name= { C_STRING_WITH_LEN("slave_gtid_ignore_duplicates") };
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry && entry->val_int(&null_value) && !null_value;
+}
+
+
+/*
+ Get the value of the @slave_until_gtid user variable into the supplied
+ String (this is the GTID position specified for START SLAVE UNTIL
+ master_gtid_pos='xxx').
+
+ Returns false if error (ie. slave did not set the variable and is not doing
+ START SLAVE UNTIL mater_gtid_pos='xxx'), true if success.
+*/
+static bool
+get_slave_until_gtid(THD *thd, String *out_str)
+{
+ bool null_value;
+
+ const LEX_STRING name= { C_STRING_WITH_LEN("slave_until_gtid") };
+ user_var_entry *entry=
+ (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry && entry->val_str(&null_value, out_str, 0) && !null_value;
+}
+
+
+/*
+ Function prepares and sends repliation heartbeat event.
+
+ @param net net object of THD
+ @param packet buffer to store the heartbeat instance
+ @param event_coordinates binlog file name and position of the last
+ real event master sent from binlog
+
+ @note
+ Among three essential pieces of heartbeat data Log_event::when
+ is computed locally.
+ The error to send is serious and should force terminating
+ the dump thread.
+*/
+static int send_heartbeat_event(NET* net, String* packet,
+ const struct event_coordinates *coord,
+ uint8 checksum_alg_arg)
+{
+ DBUG_ENTER("send_heartbeat_event");
+ char header[LOG_EVENT_HEADER_LEN];
+ my_bool do_checksum= checksum_alg_arg != BINLOG_CHECKSUM_ALG_OFF &&
+ checksum_alg_arg != BINLOG_CHECKSUM_ALG_UNDEF;
+ /*
+ 'when' (the timestamp) is set to 0 so that slave could distinguish between
+ real and fake Rotate events (if necessary)
+ */
+ memset(header, 0, 4); // when
+
+ header[EVENT_TYPE_OFFSET] = HEARTBEAT_LOG_EVENT;
+
+ char* p= coord->file_name + dirname_length(coord->file_name);
+
+ uint ident_len = strlen(p);
+ ulong event_len = ident_len + LOG_EVENT_HEADER_LEN +
+ (do_checksum ? BINLOG_CHECKSUM_LEN : 0);
+ int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id);
+ int4store(header + EVENT_LEN_OFFSET, event_len);
+ int2store(header + FLAGS_OFFSET, 0);
+
+ int4store(header + LOG_POS_OFFSET, coord->pos); // log_pos
+
+ packet->append(header, sizeof(header));
+ packet->append(p, ident_len); // log_file_name
+
+ if (do_checksum)
+ {
+ char b[BINLOG_CHECKSUM_LEN];
+ ha_checksum crc= my_checksum(0L, NULL, 0);
+ crc= my_checksum(crc, (uchar*) header, sizeof(header));
+ crc= my_checksum(crc, (uchar*) p, ident_len);
+ int4store(b, crc);
+ packet->append(b, sizeof(b));
+ }
+
+ if (my_net_write(net, (uchar*) packet->ptr(), packet->length()) ||
+ net_flush(net))
+ {
+ DBUG_RETURN(-1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+struct binlog_file_entry
+{
+ binlog_file_entry *next;
+ char *name;
+};
+
+static binlog_file_entry *
+get_binlog_list(MEM_ROOT *memroot)
+{
+ IO_CACHE *index_file;
+ char fname[FN_REFLEN];
+ size_t length;
+ binlog_file_entry *current_list= NULL, *e;
+ DBUG_ENTER("get_binlog_list");
+
+ if (!mysql_bin_log.is_open())
+ {
+ my_error(ER_NO_BINARY_LOGGING, MYF(0));
+ DBUG_RETURN(NULL);
+ }
+
+ mysql_bin_log.lock_index();
+ index_file=mysql_bin_log.get_index_file();
+ reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0);
+
+ /* The file ends with EOF or empty line */
+ while ((length=my_b_gets(index_file, fname, sizeof(fname))) > 1)
+ {
+ --length; /* Remove the newline */
+ if (!(e= (binlog_file_entry *)alloc_root(memroot, sizeof(*e))) ||
+ !(e->name= strmake_root(memroot, fname, length)))
+ {
+ mysql_bin_log.unlock_index();
+ my_error(ER_OUTOFMEMORY, MYF(0), length + 1 + sizeof(*e));
+ DBUG_RETURN(NULL);
+ }
+ e->next= current_list;
+ current_list= e;
+ }
+ mysql_bin_log.unlock_index();
+
+ DBUG_RETURN(current_list);
+}
+
+/*
+ Find the Gtid_list_log_event at the start of a binlog.
+
+ NULL for ok, non-NULL error message for error.
+
+ If ok, then the event is returned in *out_gtid_list. This can be NULL if we
+ get back to binlogs written by old server version without GTID support. If
+ so, it means we have reached the point to start from, as no GTID events can
+ exist in earlier binlogs.
+*/
+static const char *
+get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list)
+{
+ Format_description_log_event init_fdle(BINLOG_VERSION);
+ Format_description_log_event *fdle;
+ Log_event *ev;
+ const char *errormsg = NULL;
+
+ *out_gtid_list= NULL;
+
+ if (!(ev= Log_event::read_log_event(cache, 0, &init_fdle,
+ opt_master_verify_checksum)) ||
+ ev->get_type_code() != FORMAT_DESCRIPTION_EVENT)
+ {
+ if (ev)
+ delete ev;
+ return "Could not read format description log event while looking for "
+ "GTID position in binlog";
+ }
+
+ fdle= static_cast<Format_description_log_event *>(ev);
+
+ for (;;)
+ {
+ Log_event_type typ;
+
+ ev= Log_event::read_log_event(cache, 0, fdle, opt_master_verify_checksum);
+ if (!ev)
+ {
+ errormsg= "Could not read GTID list event while looking for GTID "
+ "position in binlog";
+ break;
+ }
+ typ= ev->get_type_code();
+ if (typ == GTID_LIST_EVENT)
+ break; /* Done, found it */
+ delete ev;
+ if (typ == ROTATE_EVENT || typ == STOP_EVENT ||
+ typ == FORMAT_DESCRIPTION_EVENT)
+ continue; /* Continue looking */
+
+ /* We did not find any Gtid_list_log_event, must be old binlog. */
+ ev= NULL;
+ break;
+ }
+
+ delete fdle;
+ *out_gtid_list= static_cast<Gtid_list_log_event *>(ev);
+ return errormsg;
+}
+
+
+/*
+ Check if every GTID requested by the slave is contained in this (or a later)
+ binlog file. Return true if so, false if not.
+
+ We do the check with a single scan of the list of GTIDs, avoiding the need
+ to build an in-memory hash or stuff like that.
+
+ We need to check that slave did not request GTID D-S-N1, when the
+ Gtid_list_log_event for this binlog file has D-S-N2 with N2 >= N1.
+ (Because this means that requested GTID is in an earlier binlog).
+ However, if the Gtid_list_log_event indicates that D-S-N1 is the very last
+ GTID for domain D in prior binlog files, then it is ok to start from the
+ very start of this binlog file. This special case is important, as it
+ allows to purge old logs even if some domain is unused for long.
+
+ In addition, we need to check that we do not have a GTID D-S-N3 in the
+ Gtid_list_log_event where D is not present in the requested slave state at
+ all. Since if D is not in requested slave state, it means that slave needs
+ to start at the very first GTID in domain D.
+*/
+static bool
+contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev)
+{
+ uint32 i;
+
+ for (i= 0; i < glev->count; ++i)
+ {
+ uint32 gl_domain_id= glev->list[i].domain_id;
+ const rpl_gtid *gtid= st->find(gl_domain_id);
+ if (!gtid)
+ {
+ /*
+ The slave needs to start from the very beginning of this domain, which
+ is in an earlier binlog file. So we need to search back further.
+ */
+ return false;
+ }
+ if (gtid->server_id == glev->list[i].server_id &&
+ gtid->seq_no <= glev->list[i].seq_no)
+ {
+ /*
+ The slave needs to start after gtid, but it is contained in an earlier
+ binlog file. So we need to search back further, unless it was the very
+ last gtid logged for the domain in earlier binlog files.
+ */
+ if (gtid->seq_no < glev->list[i].seq_no)
+ return false;
+
+ /*
+ The slave requested D-S-N1, which happens to be the last GTID logged
+ in prior binlog files with same domain id D and server id S.
+
+ The Gtid_list is kept sorted on domain_id, with the last GTID in each
+ domain_id group being the last one logged. So if this is the last GTID
+ within the domain_id group, then it is ok to start from the very
+ beginning of this group, per the special case explained in comment at
+ the start of this function. If not, then we need to search back further.
+ */
+ if (i+1 < glev->count && gl_domain_id == glev->list[i+1].domain_id)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+static void
+give_error_start_pos_missing_in_binlog(int *err, const char **errormsg,
+ rpl_gtid *error_gtid)
+{
+ rpl_gtid binlog_gtid;
+
+ if (mysql_bin_log.lookup_domain_in_binlog_state(error_gtid->domain_id,
+ &binlog_gtid) &&
+ binlog_gtid.seq_no >= error_gtid->seq_no)
+ {
+ *errormsg= "Requested slave GTID state not found in binlog. The slave has "
+ "probably diverged due to executing erroneous transactions";
+ *err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2;
+ }
+ else
+ {
+ *errormsg= "Requested slave GTID state not found in binlog";
+ *err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG;
+ }
+}
+
+
+/*
+ Check the start GTID state requested by the slave against our binlog state.
+
+ Give an error if the slave requests something that we do not have in our
+ binlog.
+*/
+
+static int
+check_slave_start_position(binlog_send_info *info, const char **errormsg,
+ rpl_gtid *error_gtid)
+{
+ uint32 i;
+ int err;
+ slave_connection_state::entry **delete_list= NULL;
+ uint32 delete_idx= 0;
+ slave_connection_state *st= &info->gtid_state;
+
+ if (rpl_load_gtid_slave_state(info->thd))
+ {
+ *errormsg= "Failed to load replication slave GTID state";
+ err= ER_CANNOT_LOAD_SLAVE_GTID_STATE;
+ goto end;
+ }
+
+ for (i= 0; i < st->hash.records; ++i)
+ {
+ slave_connection_state::entry *slave_gtid_entry=
+ (slave_connection_state::entry *)my_hash_element(&st->hash, i);
+ rpl_gtid *slave_gtid= &slave_gtid_entry->gtid;
+ rpl_gtid master_gtid;
+ rpl_gtid master_replication_gtid;
+ rpl_gtid start_gtid;
+ bool start_at_own_slave_pos=
+ rpl_global_gtid_slave_state.domain_to_gtid(slave_gtid->domain_id,
+ &master_replication_gtid) &&
+ slave_gtid->server_id == master_replication_gtid.server_id &&
+ slave_gtid->seq_no == master_replication_gtid.seq_no;
+
+ if (mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id,
+ slave_gtid->server_id,
+ &master_gtid) &&
+ master_gtid.seq_no >= slave_gtid->seq_no)
+ {
+ /*
+ If connecting slave requests to start at the GTID we last applied when
+ we were ourselves a slave, then this GTID may not exist in our binlog
+ (in case of --log-slave-updates=0). So set the flag to disable the
+ error about missing GTID in the binlog in this case.
+ */
+ if (start_at_own_slave_pos)
+ slave_gtid_entry->flags|= slave_connection_state::START_OWN_SLAVE_POS;
+ continue;
+ }
+
+ if (!start_at_own_slave_pos)
+ {
+ rpl_gtid domain_gtid;
+ slave_connection_state *until_gtid_state= info->until_gtid_state;
+ rpl_gtid *until_gtid;
+
+ if (!mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id,
+ &domain_gtid))
+ {
+ /*
+ We do not have anything in this domain, neither in the binlog nor
+ in the slave state. So we are probably one master in a multi-master
+ setup, and this domain is served by a different master.
+
+ But set a flag so that if we then ever _do_ happen to encounter
+ anything in this domain, then we will re-check that the requested
+ slave position exists, and give the error at that time if not.
+ */
+ slave_gtid_entry->flags|= slave_connection_state::START_ON_EMPTY_DOMAIN;
+ continue;
+ }
+
+ if (info->slave_gtid_ignore_duplicates &&
+ domain_gtid.seq_no < slave_gtid->seq_no)
+ {
+ /*
+ When --gtid-ignore-duplicates, it is ok for the slave to request
+ something that we do not have (yet) - they might already have gotten
+ it through another path in a multi-path replication hierarchy.
+ */
+ continue;
+ }
+
+ if (until_gtid_state &&
+ ( !(until_gtid= until_gtid_state->find(slave_gtid->domain_id)) ||
+ (mysql_bin_log.find_in_binlog_state(until_gtid->domain_id,
+ until_gtid->server_id,
+ &master_gtid) &&
+ master_gtid.seq_no >= until_gtid->seq_no)))
+ {
+ /*
+ The slave requested to start from a position that is not (yet) in
+ our binlog, but it also specified an UNTIL condition that _is_ in
+ our binlog (or a missing UNTIL, which means stop at the very
+ beginning). So the stop position is before the start position, and
+ we just delete the entry from the UNTIL hash to mark that this
+ domain has already reached the UNTIL condition.
+ */
+ if(until_gtid)
+ until_gtid_state->remove(until_gtid);
+ continue;
+ }
+
+ *error_gtid= *slave_gtid;
+ give_error_start_pos_missing_in_binlog(&err, errormsg, error_gtid);
+ goto end;
+ }
+
+ /*
+ Ok, so connecting slave asked to start at a GTID that we do not have in
+ our binlog, but it was in fact the last GTID we applied earlier, when we
+ were acting as a replication slave.
+
+ So this means that we were running as a replication slave without
+ --log-slave-updates, but now we switched to be a master. It is worth it
+ to handle this special case, as it allows users to run a simple
+ master -> slave without --log-slave-updates, and then exchange slave and
+ master, as long as they make sure the slave is caught up before switching.
+ */
+
+ /*
+ First check if we logged something ourselves as a master after being a
+ slave. This will be seen as a GTID with our own server_id and bigger
+ seq_no than what is in the slave state.
+
+ If we did not log anything ourselves, then start the connecting slave
+ replicating from the current binlog end position, which in this case
+ corresponds to our replication slave state and hence what the connecting
+ slave is requesting.
+ */
+ if (mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id,
+ global_system_variables.server_id,
+ &start_gtid) &&
+ start_gtid.seq_no > slave_gtid->seq_no)
+ {
+ /*
+ Start replication within this domain at the first GTID that we logged
+ ourselves after becoming a master.
+
+ Remember that this starting point is in fact a "fake" GTID which may
+ not exists in the binlog, so that we do not complain about it in
+ --gtid-strict-mode.
+ */
+ slave_gtid->server_id= global_system_variables.server_id;
+ slave_gtid_entry->flags|= slave_connection_state::START_OWN_SLAVE_POS;
+ }
+ else if (mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id,
+ &start_gtid))
+ {
+ slave_gtid->server_id= start_gtid.server_id;
+ slave_gtid->seq_no= start_gtid.seq_no;
+ }
+ else
+ {
+ /*
+ We do not have _anything_ in our own binlog for this domain. Just
+ delete the entry in the slave connection state, then it will pick up
+ anything new that arrives.
+
+ We just queue up the deletion and do it later, after the loop, so that
+ we do not mess up the iteration over the hash.
+ */
+ if (!delete_list)
+ {
+ if (!(delete_list= (slave_connection_state::entry **)
+ my_malloc(sizeof(*delete_list) * st->hash.records, MYF(MY_WME))))
+ {
+ *errormsg= "Out of memory while checking slave start position";
+ err= ER_OUT_OF_RESOURCES;
+ goto end;
+ }
+ }
+ delete_list[delete_idx++]= slave_gtid_entry;
+ }
+ }
+
+ /* Do any delayed deletes from the hash. */
+ if (delete_list)
+ {
+ for (i= 0; i < delete_idx; ++i)
+ st->remove(&(delete_list[i]->gtid));
+ }
+ err= 0;
+
+end:
+ if (delete_list)
+ my_free(delete_list);
+ return err;
+}
+
+/*
+ Find the name of the binlog file to start reading for a slave that connects
+ using GTID state.
+
+ Returns the file name in out_name, which must be of size at least FN_REFLEN.
+
+ Returns NULL on ok, error message on error.
+
+ In case of non-error return, the returned binlog file is guaranteed to
+ contain the first event to be transmitted to the slave for every domain
+ present in our binlogs. It is still necessary to skip all GTIDs up to
+ and including the GTID requested by slave within each domain.
+
+ However, as a special case, if the event to be sent to the slave is the very
+ first event (within that domain) in the returned binlog, then nothing should
+ be skipped, so that domain is deleted from the passed in slave connection
+ state.
+
+ This is necessary in case the slave requests a GTID within a replication
+ domain that has long been inactive. The binlog file containing that GTID may
+ have been long since purged. However, as long as no GTIDs after that have
+ been purged, we have the GTID requested by slave in the Gtid_list_log_event
+ of the latest binlog. So we can start from there, as long as we delete the
+ corresponding entry in the slave state so we do not wrongly skip any events
+ that might turn up if that domain becomes active again, vainly looking for
+ the requested GTID that was already purged.
+*/
+static const char *
+gtid_find_binlog_file(slave_connection_state *state, char *out_name,
+ slave_connection_state *until_gtid_state)
+{
+ MEM_ROOT memroot;
+ binlog_file_entry *list;
+ Gtid_list_log_event *glev= NULL;
+ const char *errormsg= NULL;
+ char buf[FN_REFLEN];
+
+ init_alloc_root(&memroot, 10*(FN_REFLEN+sizeof(binlog_file_entry)), 0,
+ MYF(MY_THREAD_SPECIFIC));
+ if (!(list= get_binlog_list(&memroot)))
+ {
+ errormsg= "Out of memory while looking for GTID position in binlog";
+ goto end;
+ }
+
+ while (list)
+ {
+ File file;
+ IO_CACHE cache;
+
+ if (!list->next)
+ {
+ /*
+ It should be safe to read the currently used binlog, as we will only
+ read the header part that is already written.
+
+ But if that does not work on windows, then we will need to cache the
+ event somewhere in memory I suppose - that could work too.
+ */
+ }
+ /*
+ Read the Gtid_list_log_event at the start of the binlog file to
+ get the binlog state.
+ */
+ if (normalize_binlog_name(buf, list->name, false))
+ {
+ errormsg= "Failed to determine binlog file name while looking for "
+ "GTID position in binlog";
+ goto end;
+ }
+ bzero((char*) &cache, sizeof(cache));
+ if ((file= open_binlog(&cache, buf, &errormsg)) == (File)-1)
+ goto end;
+ errormsg= get_gtid_list_event(&cache, &glev);
+ end_io_cache(&cache);
+ mysql_file_close(file, MYF(MY_WME));
+ if (errormsg)
+ goto end;
+
+ if (!glev || contains_all_slave_gtid(state, glev))
+ {
+ strmake(out_name, buf, FN_REFLEN);
+
+ if (glev)
+ {
+ uint32 i;
+
+ /*
+ As a special case, we allow to start from binlog file N if the
+ requested GTID is the last event (in the corresponding domain) in
+ binlog file (N-1), but then we need to remove that GTID from the slave
+ state, rather than skipping events waiting for it to turn up.
+
+ If slave is doing START SLAVE UNTIL, check for any UNTIL conditions
+ that are already included in a previous binlog file. Delete any such
+ from the UNTIL hash, to mark that such domains have already reached
+ their UNTIL condition.
+ */
+ for (i= 0; i < glev->count; ++i)
+ {
+ const rpl_gtid *gtid= state->find(glev->list[i].domain_id);
+ if (!gtid)
+ {
+ /*
+ Contains_all_slave_gtid() returns false if there is any domain in
+ Gtid_list_event which is not in the requested slave position.
+
+ We may delete a domain from the slave state inside this loop, but
+ we only do this when it is the very last GTID logged for that
+ domain in earlier binlogs, and then we can not encounter it in any
+ further GTIDs in the Gtid_list.
+ */
+ DBUG_ASSERT(0);
+ } else if (gtid->server_id == glev->list[i].server_id &&
+ gtid->seq_no == glev->list[i].seq_no)
+ {
+ /*
+ The slave requested to start from the very beginning of this
+ domain in this binlog file. So delete the entry from the state,
+ we do not need to skip anything.
+ */
+ state->remove(gtid);
+ }
+
+ if (until_gtid_state &&
+ (gtid= until_gtid_state->find(glev->list[i].domain_id)) &&
+ gtid->server_id == glev->list[i].server_id &&
+ gtid->seq_no <= glev->list[i].seq_no)
+ {
+ /*
+ We've already reached the stop position in UNTIL for this domain,
+ since it is before the start position.
+ */
+ until_gtid_state->remove(gtid);
+ }
+ }
+ }
+
+ goto end;
+ }
+ delete glev;
+ glev= NULL;
+ list= list->next;
+ }
+
+ /* We reached the end without finding anything. */
+ errormsg= "Could not find GTID state requested by slave in any binlog "
+ "files. Probably the slave state is too old and required binlog files "
+ "have been purged.";
+
+end:
+ if (glev)
+ delete glev;
+
+ free_root(&memroot, MYF(0));
+ return errormsg;
+}
+
+
+/*
+ Given an old-style binlog position with file name and file offset, find the
+ corresponding gtid position. If the offset is not at an event boundary, give
+ an error.
+
+ Return NULL on ok, error message string on error.
+
+ ToDo: Improve the performance of this by using binlog index files.
+*/
+static const char *
+gtid_state_from_pos(const char *name, uint32 offset,
+ slave_connection_state *gtid_state)
+{
+ IO_CACHE cache;
+ File file;
+ const char *errormsg= NULL;
+ bool found_gtid_list_event= false;
+ bool found_format_description_event= false;
+ bool valid_pos= false;
+ uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF;
+ int err;
+ String packet;
+ Format_description_log_event *fdev= NULL;
+
+ if (gtid_state->load((const rpl_gtid *)NULL, 0))
+ {
+ errormsg= "Internal error (out of memory?) initializing slave state "
+ "while scanning binlog to find start position";
+ return errormsg;
+ }
+
+ if ((file= open_binlog(&cache, name, &errormsg)) == (File)-1)
+ return errormsg;
+
+ if (!(fdev= new Format_description_log_event(3)))
+ {
+ errormsg= "Out of memory initializing format_description event "
+ "while scanning binlog to find start position";
+ goto end;
+ }
+
+ /*
+ First we need to find the initial GTID_LIST_EVENT. We need this even
+ if the offset is at the very start of the binlog file.
+
+ But if we do not find any GTID_LIST_EVENT, then this is an old binlog
+ with no GTID information, so we return empty GTID state.
+ */
+ for (;;)
+ {
+ Log_event_type typ;
+ uint32 cur_pos;
+
+ cur_pos= (uint32)my_b_tell(&cache);
+ if (cur_pos == offset)
+ valid_pos= true;
+ if (found_format_description_event && found_gtid_list_event &&
+ cur_pos >= offset)
+ break;
+
+ packet.length(0);
+ err= Log_event::read_log_event(&cache, &packet, NULL,
+ current_checksum_alg);
+ if (err)
+ {
+ errormsg= "Could not read binlog while searching for slave start "
+ "position on master";
+ goto end;
+ }
+ /*
+ The cast to uchar is needed to avoid a signed char being converted to a
+ negative number.
+ */
+ typ= (Log_event_type)(uchar)packet[EVENT_TYPE_OFFSET];
+ if (typ == FORMAT_DESCRIPTION_EVENT)
+ {
+ Format_description_log_event *tmp;
+
+ if (found_format_description_event)
+ {
+ errormsg= "Duplicate format description log event found while "
+ "searching for old-style position in binlog";
+ goto end;
+ }
+
+ current_checksum_alg= get_checksum_alg(packet.ptr(), packet.length());
+ found_format_description_event= true;
+ if (!(tmp= new Format_description_log_event(packet.ptr(), packet.length(),
+ fdev)))
+ {
+ errormsg= "Corrupt Format_description event found or out-of-memory "
+ "while searching for old-style position in binlog";
+ goto end;
+ }
+ delete fdev;
+ fdev= tmp;
+ }
+ else if (typ != FORMAT_DESCRIPTION_EVENT && !found_format_description_event)
+ {
+ errormsg= "Did not find format description log event while searching "
+ "for old-style position in binlog";
+ goto end;
+ }
+ else if (typ == ROTATE_EVENT || typ == STOP_EVENT ||
+ typ == BINLOG_CHECKPOINT_EVENT)
+ continue; /* Continue looking */
+ else if (typ == GTID_LIST_EVENT)
+ {
+ rpl_gtid *gtid_list;
+ bool status;
+ uint32 list_len;
+
+ if (found_gtid_list_event)
+ {
+ errormsg= "Found duplicate Gtid_list_log_event while scanning binlog "
+ "to find slave start position";
+ goto end;
+ }
+ status= Gtid_list_log_event::peek(packet.ptr(), packet.length(),
+ current_checksum_alg,
+ &gtid_list, &list_len, fdev);
+ if (status)
+ {
+ errormsg= "Error reading Gtid_list_log_event while searching "
+ "for old-style position in binlog";
+ goto end;
+ }
+ err= gtid_state->load(gtid_list, list_len);
+ my_free(gtid_list);
+ if (err)
+ {
+ errormsg= "Internal error (out of memory?) initialising slave state "
+ "while scanning binlog to find start position";
+ goto end;
+ }
+ found_gtid_list_event= true;
+ }
+ else if (!found_gtid_list_event)
+ {
+ /* We did not find any Gtid_list_log_event, must be old binlog. */
+ goto end;
+ }
+ else if (typ == GTID_EVENT)
+ {
+ rpl_gtid gtid;
+ uchar flags2;
+ if (Gtid_log_event::peek(packet.ptr(), packet.length(),
+ current_checksum_alg, &gtid.domain_id,
+ &gtid.server_id, &gtid.seq_no, &flags2, fdev))
+ {
+ errormsg= "Corrupt gtid_log_event found while scanning binlog to find "
+ "initial slave position";
+ goto end;
+ }
+ if (gtid_state->update(&gtid))
+ {
+ errormsg= "Internal error (out of memory?) updating slave state while "
+ "scanning binlog to find start position";
+ goto end;
+ }
+ }
+ }
+
+ if (!valid_pos)
+ {
+ errormsg= "Slave requested incorrect position in master binlog. "
+ "Requested position %u in file '%s', but this position does not "
+ "correspond to the location of any binlog event.";
+ }
+
+end:
+ delete fdev;
+ end_io_cache(&cache);
+ mysql_file_close(file, MYF(MY_WME));
+
+ return errormsg;
+}
+
+
+int
+gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str)
+{
+ slave_connection_state gtid_state;
+ const char *lookup_name;
+ char name_buf[FN_REFLEN];
+ LOG_INFO linfo;
+
+ if (!mysql_bin_log.is_open())
+ {
+ my_error(ER_NO_BINARY_LOGGING, MYF(0));
+ return 1;
+ }
+
+ if (in_name && in_name[0])
+ {
+ mysql_bin_log.make_log_name(name_buf, in_name);
+ lookup_name= name_buf;
+ }
+ else
+ lookup_name= NULL;
+ linfo.index_file_offset= 0;
+ if (mysql_bin_log.find_log_pos(&linfo, lookup_name, 1))
+ return 1;
+
+ if (pos < 4)
+ pos= 4;
+
+ if (gtid_state_from_pos(linfo.log_file_name, pos, &gtid_state) ||
+ gtid_state.to_string(out_str))
+ return 1;
+ return 0;
+}
+
+
+static bool
+is_until_reached(binlog_send_info *info, ulong *ev_offset,
+ Log_event_type event_type, const char **errmsg,
+ uint32 current_pos)
+{
+ switch (info->gtid_until_group)
+ {
+ case GTID_UNTIL_NOT_DONE:
+ return false;
+ case GTID_UNTIL_STOP_AFTER_STANDALONE:
+ if (Log_event::is_part_of_group(event_type))
+ return false;
+ break;
+ case GTID_UNTIL_STOP_AFTER_TRANSACTION:
+ if (event_type != XID_EVENT &&
+ (event_type != QUERY_EVENT ||
+ !Query_log_event::peek_is_commit_rollback
+ (info->packet->ptr()+*ev_offset,
+ info->packet->length()-*ev_offset,
+ info->current_checksum_alg)))
+ return false;
+ break;
+ }
+
+ /*
+ The last event group has been sent, now the START SLAVE UNTIL condition
+ has been reached.
+
+ Send a last fake Gtid_list_log_event with a flag set to mark that we
+ stop due to UNTIL condition.
+ */
+ if (reset_transmit_packet(info->thd, info->flags, ev_offset, errmsg))
+ return true;
+ Gtid_list_log_event glev(&info->until_binlog_state,
+ Gtid_list_log_event::FLAG_UNTIL_REACHED);
+ if (fake_gtid_list_event(info, &glev, errmsg, current_pos))
+ return true;
+ *errmsg= NULL;
+ return true;
+}
+
+
+/*
+ Helper function for mysql_binlog_send() to write an event down the slave
+ connection.
+
+ Returns NULL on success, error message string on error.
+*/
+static const char *
+send_event_to_slave(binlog_send_info *info, Log_event_type event_type,
+ IO_CACHE *log, ulong ev_offset, rpl_gtid *error_gtid)
+{
+ my_off_t pos;
+ String* const packet= info->packet;
+ size_t len= packet->length();
+ int mariadb_slave_capability= info->mariadb_slave_capability;
+ uint8 current_checksum_alg= info->current_checksum_alg;
+ slave_connection_state *gtid_state= &info->gtid_state;
+ slave_connection_state *until_gtid_state= info->until_gtid_state;
+
+ if (event_type == GTID_LIST_EVENT &&
+ info->using_gtid_state && until_gtid_state)
+ {
+ rpl_gtid *gtid_list;
+ uint32 list_len;
+ bool err;
+
+ if (ev_offset > len ||
+ Gtid_list_log_event::peek(packet->ptr()+ev_offset, len - ev_offset,
+ current_checksum_alg,
+ &gtid_list, &list_len, info->fdev))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ return "Failed to read Gtid_list_log_event: corrupt binlog";
+ }
+ err= info->until_binlog_state.load(gtid_list, list_len);
+ my_free(gtid_list);
+ if (err)
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ return "Failed in internal GTID book-keeping: Out of memory";
+ }
+ }
+
+ /* Skip GTID event groups until we reach slave position within a domain_id. */
+ if (event_type == GTID_EVENT && info->using_gtid_state)
+ {
+ uchar flags2;
+ slave_connection_state::entry *gtid_entry;
+ rpl_gtid *gtid;
+
+ if (gtid_state->count() > 0 || until_gtid_state)
+ {
+ rpl_gtid event_gtid;
+
+ if (ev_offset > len ||
+ Gtid_log_event::peek(packet->ptr()+ev_offset, len - ev_offset,
+ current_checksum_alg,
+ &event_gtid.domain_id, &event_gtid.server_id,
+ &event_gtid.seq_no, &flags2, info->fdev))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ return "Failed to read Gtid_log_event: corrupt binlog";
+ }
+
+ DBUG_EXECUTE_IF("gtid_force_reconnect_at_10_1_100",
+ {
+ rpl_gtid *dbug_gtid;
+ if ((dbug_gtid= info->until_binlog_state.find_nolock(10,1)) &&
+ dbug_gtid->seq_no == 100)
+ {
+ DBUG_SET("-d,gtid_force_reconnect_at_10_1_100");
+ DBUG_SET_INITIAL("-d,gtid_force_reconnect_at_10_1_100");
+ my_errno= ER_UNKNOWN_ERROR;
+ return "DBUG-injected forced reconnect";
+ }
+ });
+
+ if (info->until_binlog_state.update_nolock(&event_gtid, false))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ return "Failed in internal GTID book-keeping: Out of memory";
+ }
+
+ if (gtid_state->count() > 0)
+ {
+ gtid_entry= gtid_state->find_entry(event_gtid.domain_id);
+ if (gtid_entry != NULL)
+ {
+ gtid= &gtid_entry->gtid;
+ if (gtid_entry->flags & slave_connection_state::START_ON_EMPTY_DOMAIN)
+ {
+ rpl_gtid master_gtid;
+ if (!mysql_bin_log.find_in_binlog_state(gtid->domain_id,
+ gtid->server_id,
+ &master_gtid) ||
+ master_gtid.seq_no < gtid->seq_no)
+ {
+ int err;
+ const char *errormsg;
+ *error_gtid= *gtid;
+ give_error_start_pos_missing_in_binlog(&err, &errormsg, error_gtid);
+ my_errno= err;
+ return errormsg;
+ }
+ gtid_entry->flags&= ~(uint32)slave_connection_state::START_ON_EMPTY_DOMAIN;
+ }
+
+ /* Skip this event group if we have not yet reached slave start pos. */
+ if (event_gtid.server_id != gtid->server_id ||
+ event_gtid.seq_no <= gtid->seq_no)
+ info->gtid_skip_group= (flags2 & Gtid_log_event::FL_STANDALONE ?
+ GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION);
+ if (event_gtid.server_id == gtid->server_id &&
+ event_gtid.seq_no >= gtid->seq_no)
+ {
+ if (info->slave_gtid_strict_mode &&
+ event_gtid.seq_no > gtid->seq_no &&
+ !(gtid_entry->flags & slave_connection_state::START_OWN_SLAVE_POS))
+ {
+ /*
+ In strict mode, it is an error if the slave requests to start
+ in a "hole" in the master's binlog: a GTID that does not
+ exist, even though both the prior and subsequent seq_no exists
+ for same domain_id and server_id.
+ */
+ my_errno= ER_GTID_START_FROM_BINLOG_HOLE;
+ *error_gtid= *gtid;
+ return "The binlog on the master is missing the GTID requested "
+ "by the slave (even though both a prior and a subsequent "
+ "sequence number does exist), and GTID strict mode is enabled.";
+ }
+
+ /*
+ Send a fake Gtid_list event to the slave.
+ This allows the slave to update its current binlog position
+ so MASTER_POS_WAIT() and MASTER_GTID_WAIT() can work.
+ The fake event will be sent at the end of this event group.
+ */
+ info->send_fake_gtid_list= true;
+
+ /*
+ Delete this entry if we have reached slave start position (so we
+ will not skip subsequent events and won't have to look them up
+ and check).
+ */
+ gtid_state->remove(gtid);
+ }
+ }
+ }
+
+ if (until_gtid_state)
+ {
+ gtid= until_gtid_state->find(event_gtid.domain_id);
+ if (gtid == NULL)
+ {
+ /*
+ This domain already reached the START SLAVE UNTIL stop condition,
+ so skip this event group.
+ */
+ info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ?
+ GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION);
+ }
+ else if (event_gtid.server_id == gtid->server_id &&
+ event_gtid.seq_no >= gtid->seq_no)
+ {
+ /*
+ We have reached the stop condition.
+ Delete this domain_id from the hash, so we will skip all further
+ events in this domain and eventually stop when all domains are
+ done.
+ */
+ uint64 until_seq_no= gtid->seq_no;
+ until_gtid_state->remove(gtid);
+ if (until_gtid_state->count() == 0)
+ info->gtid_until_group= (flags2 & Gtid_log_event::FL_STANDALONE ?
+ GTID_UNTIL_STOP_AFTER_STANDALONE :
+ GTID_UNTIL_STOP_AFTER_TRANSACTION);
+ if (event_gtid.seq_no > until_seq_no)
+ {
+ /*
+ The GTID in START SLAVE UNTIL condition is missing in our binlog.
+ This should normally not happen (user error), but since we can be
+ sure that we are now beyond the position that the UNTIL condition
+ should be in, we can just stop now. And we also need to skip this
+ event group (as it is beyond the UNTIL condition).
+ */
+ info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ?
+ GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ Skip event group if we have not yet reached the correct slave GTID position.
+
+ Note that slave that understands GTID can also tolerate holes, so there is
+ no need to supply dummy event.
+ */
+ switch (info->gtid_skip_group)
+ {
+ case GTID_SKIP_STANDALONE:
+ if (!Log_event::is_part_of_group(event_type))
+ info->gtid_skip_group= GTID_SKIP_NOT;
+ return NULL;
+ case GTID_SKIP_TRANSACTION:
+ if (event_type == XID_EVENT ||
+ (event_type == QUERY_EVENT &&
+ Query_log_event::peek_is_commit_rollback(packet->ptr() + ev_offset,
+ len - ev_offset,
+ current_checksum_alg)))
+ info->gtid_skip_group= GTID_SKIP_NOT;
+ return NULL;
+ case GTID_SKIP_NOT:
+ break;
+ }
+
+ /* Do not send annotate_rows events unless slave requested it. */
+ if (event_type == ANNOTATE_ROWS_EVENT &&
+ !(info->flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT))
+ {
+ if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES)
+ {
+ /* This slave can tolerate events omitted from the binlog stream. */
+ return NULL;
+ }
+ else if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_ANNOTATE)
+ {
+ /*
+ The slave did not request ANNOTATE_ROWS_EVENT (it does not need them as
+ it will not log them in its own binary log). However, it understands the
+ event and will just ignore it, and it would break if we omitted it,
+ leaving a hole in the binlog stream. So just send the event as-is.
+ */
+ }
+ else
+ {
+ /*
+ The slave does not understand ANNOTATE_ROWS_EVENT.
+
+ Older MariaDB slaves (and MySQL slaves) will break replication if there
+ are holes in the binlog stream (they will miscompute the binlog offset
+ and request the wrong position when reconnecting).
+
+ So replace the event with a dummy event of the same size that will be
+ a no-operation on the slave.
+ */
+ if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ return "Failed to replace row annotate event with dummy: too small event.";
+ }
+ }
+ }
+
+ /*
+ Replace GTID events with old-style BEGIN events for slaves that do not
+ understand global transaction IDs. For stand-alone events, where there is
+ no terminating COMMIT query event, omit the GTID event or replace it with
+ a dummy event, as appropriate.
+ */
+ if (event_type == GTID_EVENT &&
+ mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID)
+ {
+ bool need_dummy=
+ mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES;
+ bool err= Gtid_log_event::make_compatible_event(packet, &need_dummy,
+ ev_offset,
+ current_checksum_alg);
+ if (err)
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ return "Failed to replace GTID event with backwards-compatible event: "
+ "currupt event.";
+ }
+ if (!need_dummy)
+ return NULL;
+ }
+
+ /*
+ Do not send binlog checkpoint or gtid list events to a slave that does not
+ understand it.
+ */
+ if ((unlikely(event_type == BINLOG_CHECKPOINT_EVENT) &&
+ mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT) ||
+ (unlikely(event_type == GTID_LIST_EVENT) &&
+ mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID))
+ {
+ if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES)
+ {
+ /* This slave can tolerate events omitted from the binlog stream. */
+ return NULL;
+ }
+ else
+ {
+ /*
+ The slave does not understand BINLOG_CHECKPOINT_EVENT. Send a dummy
+ event instead, with same length so slave does not get confused about
+ binlog positions.
+ */
+ if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ return "Failed to replace binlog checkpoint or gtid list event with "
+ "dummy: too small event.";
+ }
+ }
+ }
+
+ /*
+ Skip events with the @@skip_replication flag set, if slave requested
+ skipping of such events.
+ */
+ if (info->thd->variables.option_bits & OPTION_SKIP_REPLICATION)
+ {
+ /*
+ The first byte of the packet is a '\0' to distinguish it from an error
+ packet. So the actual event starts at offset +1.
+ */
+ uint16 event_flags= uint2korr(&((*packet)[FLAGS_OFFSET+1]));
+ if (event_flags & LOG_EVENT_SKIP_REPLICATION_F)
+ return NULL;
+ }
+
+ THD_STAGE_INFO(info->thd, stage_sending_binlog_event_to_slave);
+
+ pos= my_b_tell(log);
+ if (RUN_HOOK(binlog_transmit, before_send_event,
+ (info->thd, info->flags, packet, info->log_file_name, pos)))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ return "run 'before_send_event' hook failed";
+ }
+
+ if (my_net_write(info->net, (uchar*) packet->ptr(), len))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ return "Failed on my_net_write()";
+ }
+
+ DBUG_PRINT("info", ("log event code %d", (*packet)[LOG_EVENT_OFFSET+1] ));
+ if (event_type == LOAD_EVENT)
+ {
+ if (send_file(info->thd))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ return "failed in send_file()";
+ }
+ }
+
+ if (RUN_HOOK(binlog_transmit, after_send_event,
+ (info->thd, info->flags, packet)))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ return "Failed to run hook 'after_send_event'";
+ }
+
+ return NULL; /* Success */
+}
+
+
+void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos,
+ ushort flags)
+{
+ LOG_INFO linfo;
+ char *log_file_name = linfo.log_file_name;
+ char search_file_name[FN_REFLEN], *name;
+
+ ulong ev_offset;
+
+ IO_CACHE log;
+ File file = -1;
+ String* const packet= &thd->packet;
+ int error;
+ const char *errmsg = "Unknown error", *tmp_msg;
+ char error_text[MAX_SLAVE_ERRMSG]; // to be send to slave via my_message()
+ mysql_mutex_t *log_lock;
+ mysql_cond_t *log_cond;
+ char str_buf[128];
+ String connect_gtid_state(str_buf, sizeof(str_buf), system_charset_info);
+ char str_buf2[128];
+ String slave_until_gtid_str(str_buf2, sizeof(str_buf2), system_charset_info);
+ slave_connection_state until_gtid_state_obj;
+ rpl_gtid error_gtid;
+ binlog_send_info info(thd, packet, flags, log_file_name);
+ bool has_transmit_started= false;
+
+ int old_max_allowed_packet= thd->variables.max_allowed_packet;
+
+#ifndef DBUG_OFF
+ int left_events = max_binlog_dump_events;
+ uint dbug_reconnect_counter= 0;
+#endif
+ DBUG_ENTER("mysql_binlog_send");
+ DBUG_PRINT("enter",("log_ident: '%s' pos: %ld", log_ident, (long) pos));
+
+ bzero((char*) &log,sizeof(log));
+ bzero(&error_gtid, sizeof(error_gtid));
+ /*
+ heartbeat_period from @master_heartbeat_period user variable
+ */
+ ulonglong heartbeat_period= get_heartbeat_period(thd);
+ struct timespec heartbeat_buf;
+ struct timespec *heartbeat_ts= NULL;
+ const LOG_POS_COORD start_coord= { log_ident, pos },
+ *p_start_coord= &start_coord;
+ LOG_POS_COORD coord_buf= { log_file_name, BIN_LOG_HEADER_SIZE },
+ *p_coord= &coord_buf;
+ if (heartbeat_period != 0)
+ {
+ heartbeat_ts= &heartbeat_buf;
+ set_timespec_nsec(*heartbeat_ts, 0);
+ }
+ info.mariadb_slave_capability= get_mariadb_slave_capability(thd);
+
+ connect_gtid_state.length(0);
+ info.using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state);
+ DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", info.using_gtid_state= false;);
+ if (info.using_gtid_state)
+ {
+ info.slave_gtid_strict_mode= get_slave_gtid_strict_mode(thd);
+ info.slave_gtid_ignore_duplicates= get_slave_gtid_ignore_duplicates(thd);
+ if(get_slave_until_gtid(thd, &slave_until_gtid_str))
+ info.until_gtid_state= &until_gtid_state_obj;
+ }
+
+ DBUG_EXECUTE_IF("binlog_force_reconnect_after_22_events",
+ {
+ DBUG_SET("-d,binlog_force_reconnect_after_22_events");
+ DBUG_SET_INITIAL("-d,binlog_force_reconnect_after_22_events");
+ dbug_reconnect_counter= 22;
+ });
+
+ /*
+ We want to corrupt the first event, in Log_event::read_log_event().
+ But we do not want the corruption to happen early, eg. when client does
+ BINLOG_GTID_POS(). So test case sets a DBUG trigger which causes us to
+ set the real DBUG injection here.
+ */
+ DBUG_EXECUTE_IF("corrupt_read_log_event2_set",
+ {
+ DBUG_SET("-d,corrupt_read_log_event2_set");
+ DBUG_SET("+d,corrupt_read_log_event2");
+ });
+
+#ifndef DBUG_OFF
+ if (opt_sporadic_binlog_dump_fail && (binlog_dump_count++ % 2))
+ {
+ errmsg = "Master failed COM_BINLOG_DUMP to test if slave can recover";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+#endif
+
+ if (!(info.fdev= new Format_description_log_event(3)))
+ {
+ errmsg= "Out of memory initializing format_description event";
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ if (!mysql_bin_log.is_open())
+ {
+ errmsg = "Binary log is not open";
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+ if (!server_id_supplied)
+ {
+ errmsg = "Misconfigured master - server id was not set";
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ name=search_file_name;
+ if (info.using_gtid_state)
+ {
+ if (info.gtid_state.load(connect_gtid_state.c_ptr_quick(),
+ connect_gtid_state.length()))
+ {
+ errmsg= "Out of memory or malformed slave request when obtaining start "
+ "position from GTID state";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ if (info.until_gtid_state &&
+ info.until_gtid_state->load(slave_until_gtid_str.c_ptr_quick(),
+ slave_until_gtid_str.length()))
+ {
+ errmsg= "Out of memory or malformed slave request when obtaining UNTIL "
+ "position sent from slave";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ if ((error= check_slave_start_position(&info, &errmsg, &error_gtid)))
+ {
+ my_errno= error;
+ goto err;
+ }
+ if ((errmsg= gtid_find_binlog_file(&info.gtid_state, search_file_name,
+ info.until_gtid_state)))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+ pos= 4;
+ }
+ else
+ {
+ if (log_ident[0])
+ mysql_bin_log.make_log_name(search_file_name, log_ident);
+ else
+ name=0; // Find first log
+ }
+
+ linfo.index_file_offset = 0;
+
+ if (mysql_bin_log.find_log_pos(&linfo, name, 1))
+ {
+ errmsg = "Could not find first log file name in binary log index file";
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = &linfo;
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ if ((file=open_binlog(&log, log_file_name, &errmsg)) < 0)
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+ if (pos < BIN_LOG_HEADER_SIZE || pos > my_b_filelength(&log))
+ {
+ errmsg= "Client requested master to start replication from \
+impossible position";
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ if (global_system_variables.log_warnings > 1)
+ sql_print_information("Start binlog_dump to slave_server(%lu), pos(%s, %lu)",
+ thd->variables.server_id, log_ident, (ulong)pos);
+ if (RUN_HOOK(binlog_transmit, transmit_start, (thd, flags, log_ident, pos)))
+ {
+ errmsg= "Failed to run hook 'transmit_start'";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ has_transmit_started= true;
+
+ /* reset transmit packet for the fake rotate event below */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+
+ /*
+ Tell the client about the log name with a fake Rotate event;
+ this is needed even if we also send a Format_description_log_event
+ just after, because that event does not contain the binlog's name.
+ Note that as this Rotate event is sent before
+ Format_description_log_event, the slave cannot have any info to
+ understand this event's format, so the header len of
+ Rotate_log_event is FROZEN (so in 5.0 it will have a header shorter
+ than other events except FORMAT_DESCRIPTION_EVENT).
+ Before 4.0.14 we called fake_rotate_event below only if (pos ==
+ BIN_LOG_HEADER_SIZE), because if this is false then the slave
+ already knows the binlog's name.
+ Since, we always call fake_rotate_event; if the slave already knew
+ the log's name (ex: CHANGE MASTER TO MASTER_LOG_FILE=...) this is
+ useless but does not harm much. It is nice for 3.23 (>=.58) slaves
+ which test Rotate events to see if the master is 4.0 (then they
+ choose to stop because they can't replicate 4.0); by always calling
+ fake_rotate_event we are sure that 3.23.58 and newer will detect the
+ problem as soon as replication starts (BUG#198).
+ Always calling fake_rotate_event makes sending of normal
+ (=from-binlog) Rotate events a priori unneeded, but it is not so
+ simple: the 2 Rotate events are not equivalent, the normal one is
+ before the Stop event, the fake one is after. If we don't send the
+ normal one, then the Stop event will be interpreted (by existing 4.0
+ slaves) as "the master stopped", which is wrong. So for safety,
+ given that we want minimum modification of 4.0, we send the normal
+ and fake Rotates.
+ */
+ if (fake_rotate_event(&info, pos, &errmsg,
+ get_binlog_checksum_value_at_connect(thd)))
+ {
+ /*
+ This error code is not perfect, as fake_rotate_event() does not
+ read anything from the binlog; if it fails it's because of an
+ error in my_net_write(), fortunately it will say so in errmsg.
+ */
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ /*
+ Adding MAX_LOG_EVENT_HEADER_LEN, since a binlog event can become
+ this larger than the corresponding packet (query) sent
+ from client to master.
+ */
+ thd->variables.max_allowed_packet= MAX_MAX_ALLOWED_PACKET;
+
+ /*
+ We can set log_lock now, it does not move (it's a member of
+ mysql_bin_log, and it's already inited, and it will be destroyed
+ only at shutdown).
+ */
+ p_coord->pos= pos; // the first hb matches the slave's last seen value
+ log_lock= mysql_bin_log.get_log_lock();
+ log_cond= mysql_bin_log.get_log_cond();
+ if (pos > BIN_LOG_HEADER_SIZE)
+ {
+ /* reset transmit packet for the event read from binary log
+ file */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+
+ /*
+ Try to find a Format_description_log_event at the beginning of
+ the binlog
+ */
+ if (!(error = Log_event::read_log_event(&log, packet, log_lock, 0)))
+ {
+ /*
+ The packet has offsets equal to the normal offsets in a
+ binlog event + ev_offset (the first ev_offset characters are
+ the header (default \0)).
+ */
+ DBUG_PRINT("info",
+ ("Looked for a Format_description_log_event, found event type %d",
+ (*packet)[EVENT_TYPE_OFFSET+ev_offset]));
+ if ((*packet)[EVENT_TYPE_OFFSET+ev_offset] == FORMAT_DESCRIPTION_EVENT)
+ {
+ Format_description_log_event *tmp;
+
+ info.current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset,
+ packet->length() - ev_offset);
+ DBUG_ASSERT(info.current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF ||
+ info.current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ info.current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32);
+ if (!is_slave_checksum_aware(thd) &&
+ info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ errmsg= "Slave can not handle replication events with the checksum "
+ "that master is configured to log";
+ sql_print_warning("Master is configured to log replication events "
+ "with checksum, but will not send such events to "
+ "slaves that cannot process them");
+ goto err;
+ }
+
+ if (!(tmp= new Format_description_log_event(packet->ptr()+ev_offset,
+ packet->length()-ev_offset,
+ info.fdev)))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ errmsg= "Corrupt Format_description event found or out-of-memory";
+ goto err;
+ }
+ delete info.fdev;
+ info.fdev= tmp;
+
+ (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F;
+ /*
+ mark that this event with "log_pos=0", so the slave
+ should not increment master's binlog position
+ (rli->group_master_log_pos)
+ */
+ int4store((char*) packet->ptr()+LOG_POS_OFFSET+ev_offset, 0);
+ /*
+ if reconnect master sends FD event with `created' as 0
+ to avoid destroying temp tables.
+ */
+ int4store((char*) packet->ptr()+LOG_EVENT_MINIMAL_HEADER_LEN+
+ ST_CREATED_OFFSET+ev_offset, (ulong) 0);
+
+ /* fix the checksum due to latest changes in header */
+ if (info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ fix_checksum(packet, ev_offset);
+
+ /* send it */
+ if (my_net_write(info.net, (uchar*) packet->ptr(), packet->length()))
+ {
+ errmsg = "Failed on my_net_write()";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+
+ /*
+ No need to save this event. We are only doing simple reads
+ (no real parsing of the events) so we don't need it. And so
+ we don't need the artificial Format_description_log_event of
+ 3.23&4.x.
+ */
+ }
+ }
+ else
+ {
+ if (test_for_non_eof_log_read_errors(error, &errmsg))
+ goto err;
+ /*
+ It's EOF, nothing to do, go on reading next events, the
+ Format_description_log_event will be found naturally if it is written.
+ */
+ }
+ } /* end of if (pos > BIN_LOG_HEADER_SIZE); */
+ else
+ {
+ /* The Format_description_log_event event will be found naturally. */
+ }
+
+ /*
+ Handle the case of START SLAVE UNTIL with an UNTIL condition already
+ fulfilled at the start position.
+
+ We will send one event, the format_description, and then stop.
+ */
+ if (info.until_gtid_state && info.until_gtid_state->count() == 0)
+ info.gtid_until_group= GTID_UNTIL_STOP_AFTER_STANDALONE;
+
+ /* seek to the requested position, to start the requested dump */
+ my_b_seek(&log, pos); // Seek will done on next read
+
+ while (!info.net->error && info.net->vio != 0 && !thd->killed)
+ {
+ Log_event_type event_type= UNKNOWN_EVENT;
+ killed_state killed;
+
+ /* reset the transmit packet for the event read from binary log
+ file */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+
+ bool is_active_binlog= false;
+ while (!(killed= thd->killed) &&
+ !(error = Log_event::read_log_event(&log, packet, log_lock,
+ info.current_checksum_alg,
+ log_file_name,
+ &is_active_binlog)))
+ {
+#ifndef DBUG_OFF
+ if (max_binlog_dump_events && !left_events--)
+ {
+ net_flush(info.net);
+ errmsg = "Debugging binlog dump abort";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+#endif
+ /*
+ log's filename does not change while it's active
+ */
+ p_coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET);
+
+ event_type=
+ (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]);
+#ifdef ENABLED_DEBUG_SYNC
+ DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid",
+ {
+ if (event_type == XID_EVENT)
+ {
+ net_flush(info.net);
+ const char act[]=
+ "now "
+ "wait_for signal.continue";
+ DBUG_ASSERT(debug_sync_service);
+ DBUG_ASSERT(!debug_sync_set_action(thd,
+ STRING_WITH_LEN(act)));
+ const char act2[]=
+ "now "
+ "signal signal.continued";
+ DBUG_ASSERT(!debug_sync_set_action(current_thd,
+ STRING_WITH_LEN(act2)));
+ }
+ });
+#endif
+ if (event_type == FORMAT_DESCRIPTION_EVENT)
+ {
+ Format_description_log_event *tmp;
+
+ info.current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset,
+ packet->length() - ev_offset);
+ DBUG_ASSERT(info.current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF ||
+ info.current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ info.current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32);
+ if (!is_slave_checksum_aware(thd) &&
+ info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ errmsg= "Slave can not handle replication events with the checksum "
+ "that master is configured to log";
+ sql_print_warning("Master is configured to log replication events "
+ "with checksum, but will not send such events to "
+ "slaves that cannot process them");
+ goto err;
+ }
+
+ if (!(tmp= new Format_description_log_event(packet->ptr()+ev_offset,
+ packet->length()-ev_offset,
+ info.fdev)))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ errmsg= "Corrupt Format_description event found or out-of-memory";
+ goto err;
+ }
+ delete info.fdev;
+ info.fdev= tmp;
+
+ (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F;
+
+ if (info.using_gtid_state)
+ {
+ /*
+ If this event has the field `created' set, then it will cause the
+ slave to delete all active temporary tables. This must not happen
+ if the slave received any later GTIDs in a previous connect, as
+ those GTIDs might have created new temporary tables that are still
+ needed.
+
+ So here, we check if the starting GTID position was already
+ reached before this format description event. If not, we clear the
+ `created' flag to preserve temporary tables on the slave. (If the
+ slave connects at a position past this event, it means that it
+ already received and handled it in a previous connect).
+ */
+ if (!info.gtid_state.is_pos_reached())
+ {
+ int4store((char*) packet->ptr()+LOG_EVENT_MINIMAL_HEADER_LEN+
+ ST_CREATED_OFFSET+ev_offset, (ulong) 0);
+ if (info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ fix_checksum(packet, ev_offset);
+ }
+ }
+ }
+
+#ifndef DBUG_OFF
+ if (dbug_reconnect_counter > 0)
+ {
+ --dbug_reconnect_counter;
+ if (dbug_reconnect_counter == 0)
+ {
+ errmsg= "DBUG-injected forced reconnect";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ }
+#endif
+
+ if ((tmp_msg= send_event_to_slave(&info, event_type, &log,
+ ev_offset, &error_gtid)))
+ {
+ errmsg= tmp_msg;
+ goto err;
+ }
+ if (unlikely(info.send_fake_gtid_list) &&
+ info.gtid_skip_group == GTID_SKIP_NOT)
+ {
+ Gtid_list_log_event glev(&info.until_binlog_state, 0);
+
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg) ||
+ fake_gtid_list_event(&info, &glev, &errmsg, my_b_tell(&log)))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ info.send_fake_gtid_list= false;
+ }
+ if (info.until_gtid_state &&
+ is_until_reached(&info, &ev_offset, event_type, &errmsg,
+ my_b_tell(&log)))
+ {
+ if (errmsg)
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ goto end;
+ }
+
+ DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid",
+ {
+ if (event_type == XID_EVENT)
+ {
+ net_flush(info.net);
+ }
+ });
+
+ /* reset transmit packet for next loop */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+ }
+ if (killed)
+ goto end;
+
+ DBUG_EXECUTE_IF("wait_after_binlog_EOF",
+ {
+ const char act[]= "now wait_for signal.rotate_finished";
+ DBUG_ASSERT(!debug_sync_set_action(current_thd,
+ STRING_WITH_LEN(act)));
+ };);
+
+ /*
+ TODO: now that we are logging the offset, check to make sure
+ the recorded offset and the actual match.
+ Guilhem 2003-06: this is not true if this master is a slave
+ <4.0.15 running with --log-slave-updates, because then log_pos may
+ be the offset in the-master-of-this-master's binlog.
+ */
+ if (test_for_non_eof_log_read_errors(error, &errmsg))
+ goto err;
+
+ /*
+ We should only move to the next binlog when the last read event
+ came from a already deactivated binlog.
+ */
+ if (!(flags & BINLOG_DUMP_NON_BLOCK) && is_active_binlog)
+ {
+ /*
+ Block until there is more data in the log
+ */
+ if (net_flush(info.net))
+ {
+ errmsg = "failed on net_flush()";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+
+ /*
+ We may have missed the update broadcast from the log
+ that has just happened, let's try to catch it if it did.
+ If we did not miss anything, we just wait for other threads
+ to signal us.
+ */
+ {
+ log.error=0;
+ bool read_packet = 0;
+
+#ifndef DBUG_OFF
+ if (max_binlog_dump_events && !left_events--)
+ {
+ errmsg = "Debugging binlog dump abort";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+#endif
+
+ /* reset the transmit packet for the event read from binary log
+ file */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+
+ /*
+ No one will update the log while we are reading
+ now, but we'll be quick and just read one record
+
+ TODO:
+ Add an counter that is incremented for each time we update the
+ binary log. We can avoid the following read if the counter
+ has not been updated since last read.
+ */
+
+ mysql_mutex_lock(log_lock);
+ switch (error= Log_event::read_log_event(&log, packet, (mysql_mutex_t*) 0,
+ info.current_checksum_alg)) {
+ case 0:
+ /* we read successfully, so we'll need to send it to the slave */
+ mysql_mutex_unlock(log_lock);
+ read_packet = 1;
+ p_coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET);
+ event_type=
+ (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]);
+ break;
+
+ case LOG_READ_EOF:
+ {
+ int ret;
+ ulong signal_cnt;
+ DBUG_PRINT("wait",("waiting for data in binary log"));
+ /* For mysqlbinlog (mysqlbinlog.server_id==0). */
+ if (thd->variables.server_id==0)
+ {
+ mysql_mutex_unlock(log_lock);
+ goto end;
+ }
+
+#ifndef DBUG_OFF
+ ulong hb_info_counter= 0;
+#endif
+ PSI_stage_info old_stage;
+ signal_cnt= mysql_bin_log.signal_cnt;
+ do
+ {
+ if (heartbeat_period != 0)
+ {
+ DBUG_ASSERT(heartbeat_ts);
+ set_timespec_nsec(*heartbeat_ts, heartbeat_period);
+ }
+ thd->ENTER_COND(log_cond, log_lock,
+ &stage_master_has_sent_all_binlog_to_slave,
+ &old_stage);
+ if (thd->killed)
+ break;
+ ret= mysql_bin_log.wait_for_update_bin_log(thd, heartbeat_ts);
+ DBUG_ASSERT(ret == 0 || (heartbeat_period != 0));
+ if (ret == ETIMEDOUT || ret == ETIME)
+ {
+#ifndef DBUG_OFF
+ if (hb_info_counter < 3)
+ {
+ sql_print_information("master sends heartbeat message");
+ hb_info_counter++;
+ if (hb_info_counter == 3)
+ sql_print_information("the rest of heartbeat info skipped ...");
+ }
+#endif
+ /* reset transmit packet for the heartbeat event */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ {
+ thd->EXIT_COND(&old_stage);
+ goto err;
+ }
+ if (send_heartbeat_event(info.net, packet, p_coord,
+ info.current_checksum_alg))
+ {
+ errmsg = "Failed on my_net_write()";
+ my_errno= ER_UNKNOWN_ERROR;
+ thd->EXIT_COND(&old_stage);
+ goto err;
+ }
+ }
+ else
+ {
+ DBUG_PRINT("wait",("binary log received update or a broadcast signal caught"));
+ }
+ } while (signal_cnt == mysql_bin_log.signal_cnt);
+ thd->EXIT_COND(&old_stage);
+ }
+ break;
+
+ default:
+ mysql_mutex_unlock(log_lock);
+ test_for_non_eof_log_read_errors(error, &errmsg);
+ goto err;
+ }
+
+ if (read_packet)
+ {
+ if ((tmp_msg= send_event_to_slave(&info, event_type, &log,
+ ev_offset, &error_gtid)))
+ {
+ errmsg= tmp_msg;
+ goto err;
+ }
+ if (unlikely(info.send_fake_gtid_list)
+ && info.gtid_skip_group == GTID_SKIP_NOT)
+ {
+ Gtid_list_log_event glev(&info.until_binlog_state, 0);
+
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg) ||
+ fake_gtid_list_event(&info, &glev, &errmsg, my_b_tell(&log)))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ info.send_fake_gtid_list= false;
+ }
+ if (info.until_gtid_state &&
+ is_until_reached(&info, &ev_offset, event_type, &errmsg,
+ my_b_tell(&log)))
+ {
+ if (errmsg)
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ goto end;
+ }
+ }
+
+ log.error=0;
+ }
+ }
+ else
+ {
+ bool loop_breaker = 0;
+ /* need this to break out of the for loop from switch */
+
+ THD_STAGE_INFO(thd, stage_finished_reading_one_binlog_switching_to_next_binlog);
+ switch (mysql_bin_log.find_next_log(&linfo, 1)) {
+ case 0:
+ break;
+ case LOG_INFO_EOF:
+ if (mysql_bin_log.is_active(log_file_name))
+ {
+ loop_breaker = (flags & BINLOG_DUMP_NON_BLOCK);
+ break;
+ }
+ default:
+ errmsg = "could not find next log";
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ if (loop_breaker)
+ break;
+
+ end_io_cache(&log);
+ mysql_file_close(file, MYF(MY_WME));
+
+ /* reset transmit packet for the possible fake rotate event */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+
+ /*
+ Call fake_rotate_event() in case the previous log (the one which
+ we have just finished reading) did not contain a Rotate event
+ (for example (I don't know any other example) the previous log
+ was the last one before the master was shutdown & restarted).
+ This way we tell the slave about the new log's name and
+ position. If the binlog is 5.0, the next event we are going to
+ read and send is Format_description_log_event.
+ */
+ if ((file=open_binlog(&log, log_file_name, &errmsg)) < 0 ||
+ fake_rotate_event(&info, BIN_LOG_HEADER_SIZE, &errmsg,
+ info.current_checksum_alg))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ p_coord->file_name= log_file_name; // reset to the next
+ }
+ }
+
+end:
+ end_io_cache(&log);
+ mysql_file_close(file, MYF(MY_WME));
+
+ if (has_transmit_started)
+ RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags));
+ my_eof(thd);
+ THD_STAGE_INFO(thd, stage_waiting_to_finalize_termination);
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = 0;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ thd->variables.max_allowed_packet= old_max_allowed_packet;
+ delete info.fdev;
+ DBUG_VOID_RETURN;
+
+err:
+ THD_STAGE_INFO(thd, stage_waiting_to_finalize_termination);
+ if (my_errno == ER_MASTER_FATAL_ERROR_READING_BINLOG && my_b_inited(&log))
+ {
+ /*
+ detailing the fatal error message with coordinates
+ of the last position read.
+ */
+ my_snprintf(error_text, sizeof(error_text),
+ "%s; the first event '%s' at %lld, "
+ "the last event read from '%s' at %lld, "
+ "the last byte read from '%s' at %lld.",
+ errmsg,
+ my_basename(p_start_coord->file_name), p_start_coord->pos,
+ my_basename(p_coord->file_name), p_coord->pos,
+ my_basename(log_file_name), my_b_tell(&log));
+ }
+ else if (my_errno == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG)
+ {
+ my_snprintf(error_text, sizeof(error_text),
+ "Error: connecting slave requested to start from GTID "
+ "%u-%u-%llu, which is not in the master's binlog",
+ error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no);
+ /* Use this error code so slave will know not to try reconnect. */
+ my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ }
+ else if (my_errno == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2)
+ {
+ my_snprintf(error_text, sizeof(error_text),
+ "Error: connecting slave requested to start from GTID "
+ "%u-%u-%llu, which is not in the master's binlog. Since the "
+ "master's binlog contains GTIDs with higher sequence numbers, "
+ "it probably means that the slave has diverged due to "
+ "executing extra erroneous transactions",
+ error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no);
+ /* Use this error code so slave will know not to try reconnect. */
+ my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ }
+ else if (my_errno == ER_GTID_START_FROM_BINLOG_HOLE)
+ {
+ my_snprintf(error_text, sizeof(error_text),
+ "The binlog on the master is missing the GTID %u-%u-%llu "
+ "requested by the slave (even though both a prior and a "
+ "subsequent sequence number does exist), and GTID strict mode "
+ "is enabled",
+ error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no);
+ /* Use this error code so slave will know not to try reconnect. */
+ my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ }
+ else if (my_errno == ER_CANNOT_LOAD_SLAVE_GTID_STATE)
+ {
+ my_snprintf(error_text, sizeof(error_text),
+ "Failed to load replication slave GTID state from table %s.%s",
+ "mysql", rpl_gtid_slave_state_table_name.str);
+ my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ }
+ else
+ strcpy(error_text, errmsg);
+ end_io_cache(&log);
+ if (has_transmit_started)
+ RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags));
+ /*
+ Exclude iteration through thread list
+ this is needed for purge_logs() - it will iterate through
+ thread list and update thd->current_linfo->index_file_offset
+ this mutex will make sure that it never tried to update our linfo
+ after we return from this stack frame
+ */
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = 0;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ if (file >= 0)
+ mysql_file_close(file, MYF(MY_WME));
+ thd->variables.max_allowed_packet= old_max_allowed_packet;
+ delete info.fdev;
+
+ my_message(my_errno, error_text, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Execute a START SLAVE statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @param mi Pointer to Master_info object for the slave's IO thread.
+
+ @param net_report If true, saves the exit status into thd->stmt_da.
+
+ @retval 0 success
+ @retval 1 error
+ @retval -1 fatal error
+*/
+
+int start_slave(THD* thd , Master_info* mi, bool net_report)
+{
+ int slave_errno= 0;
+ int thread_mask;
+ char master_info_file_tmp[FN_REFLEN];
+ char relay_log_info_file_tmp[FN_REFLEN];
+ DBUG_ENTER("start_slave");
+
+ if (check_access(thd, SUPER_ACL, any_db, NULL, NULL, 0, 0))
+ DBUG_RETURN(-1);
+
+ create_logfile_name_with_suffix(master_info_file_tmp,
+ sizeof(master_info_file_tmp),
+ master_info_file, 0,
+ &mi->cmp_connection_name);
+ create_logfile_name_with_suffix(relay_log_info_file_tmp,
+ sizeof(relay_log_info_file_tmp),
+ relay_log_info_file, 0,
+ &mi->cmp_connection_name);
+
+ lock_slave_threads(mi); // this allows us to cleanly read slave_running
+ // Get a mask of _stopped_ threads
+ init_thread_mask(&thread_mask,mi,1 /* inverse */);
+
+ if (thd->lex->mi.gtid_pos_str.str)
+ {
+ if (thread_mask != (SLAVE_IO|SLAVE_SQL))
+ {
+ slave_errno= ER_SLAVE_WAS_RUNNING;
+ goto err;
+ }
+ if (thd->lex->slave_thd_opt)
+ {
+ slave_errno= ER_BAD_SLAVE_UNTIL_COND;
+ goto err;
+ }
+ if (mi->using_gtid == Master_info::USE_GTID_NO)
+ {
+ slave_errno= ER_UNTIL_REQUIRES_USING_GTID;
+ goto err;
+ }
+ }
+
+ /*
+ Below we will start all stopped threads. But if the user wants to
+ start only one thread, do as if the other thread was running (as we
+ don't wan't to touch the other thread), so set the bit to 0 for the
+ other thread
+ */
+ if (thd->lex->slave_thd_opt)
+ thread_mask&= thd->lex->slave_thd_opt;
+ if (thread_mask) //some threads are stopped, start them
+ {
+ if (init_master_info(mi,master_info_file_tmp,relay_log_info_file_tmp, 0,
+ thread_mask))
+ slave_errno=ER_MASTER_INFO;
+ else if (server_id_supplied && *mi->host)
+ {
+ /*
+ If we will start SQL thread we will care about UNTIL options If
+ not and they are specified we will ignore them and warn user
+ about this fact.
+ */
+ if (thread_mask & SLAVE_SQL)
+ {
+ mysql_mutex_lock(&mi->rli.data_lock);
+
+ if (thd->lex->mi.pos)
+ {
+ if (thd->lex->mi.relay_log_pos)
+ slave_errno=ER_BAD_SLAVE_UNTIL_COND;
+ mi->rli.until_condition= Relay_log_info::UNTIL_MASTER_POS;
+ mi->rli.until_log_pos= thd->lex->mi.pos;
+ /*
+ We don't check thd->lex->mi.log_file_name for NULL here
+ since it is checked in sql_yacc.yy
+ */
+ strmake_buf(mi->rli.until_log_name, thd->lex->mi.log_file_name);
+ }
+ else if (thd->lex->mi.relay_log_pos)
+ {
+ if (thd->lex->mi.pos)
+ slave_errno=ER_BAD_SLAVE_UNTIL_COND;
+ mi->rli.until_condition= Relay_log_info::UNTIL_RELAY_POS;
+ mi->rli.until_log_pos= thd->lex->mi.relay_log_pos;
+ strmake_buf(mi->rli.until_log_name, thd->lex->mi.relay_log_name);
+ }
+ else if (thd->lex->mi.gtid_pos_str.str)
+ {
+ if (mi->rli.until_gtid_pos.load(thd->lex->mi.gtid_pos_str.str,
+ thd->lex->mi.gtid_pos_str.length))
+ {
+ slave_errno= ER_INCORRECT_GTID_STATE;
+ mysql_mutex_unlock(&mi->rli.data_lock);
+ goto err;
+ }
+ mi->rli.until_condition= Relay_log_info::UNTIL_GTID;
+ }
+ else
+ mi->rli.clear_until_condition();
+
+ if (mi->rli.until_condition == Relay_log_info::UNTIL_MASTER_POS ||
+ mi->rli.until_condition == Relay_log_info::UNTIL_RELAY_POS)
+ {
+ /* Preparing members for effective until condition checking */
+ const char *p= fn_ext(mi->rli.until_log_name);
+ char *p_end;
+ if (*p)
+ {
+ //p points to '.'
+ mi->rli.until_log_name_extension= strtoul(++p,&p_end, 10);
+ /*
+ p_end points to the first invalid character. If it equals
+ to p, no digits were found, error. If it contains '\0' it
+ means conversion went ok.
+ */
+ if (p_end==p || *p_end)
+ slave_errno=ER_BAD_SLAVE_UNTIL_COND;
+ }
+ else
+ slave_errno=ER_BAD_SLAVE_UNTIL_COND;
+
+ /* mark the cached result of the UNTIL comparison as "undefined" */
+ mi->rli.until_log_names_cmp_result=
+ Relay_log_info::UNTIL_LOG_NAMES_CMP_UNKNOWN;
+ }
+
+ if (mi->rli.until_condition != Relay_log_info::UNTIL_NONE)
+ {
+ /* Issuing warning then started without --skip-slave-start */
+ if (!opt_skip_slave_start)
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_MISSING_SKIP_SLAVE,
+ ER(ER_MISSING_SKIP_SLAVE));
+ }
+
+ mysql_mutex_unlock(&mi->rli.data_lock);
+ }
+ else if (thd->lex->mi.pos || thd->lex->mi.relay_log_pos)
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNTIL_COND_IGNORED,
+ ER(ER_UNTIL_COND_IGNORED));
+
+ if (!slave_errno)
+ slave_errno = start_slave_threads(0 /*no mutex */,
+ 1 /* wait for start */,
+ mi,
+ master_info_file_tmp,
+ relay_log_info_file_tmp,
+ thread_mask);
+ }
+ else
+ slave_errno = ER_BAD_SLAVE;
+ }
+ else
+ {
+ /* no error if all threads are already started, only a warning */
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SLAVE_WAS_RUNNING,
+ ER(ER_SLAVE_WAS_RUNNING));
+ }
+
+err:
+ unlock_slave_threads(mi);
+
+ if (slave_errno)
+ {
+ if (net_report)
+ my_error(slave_errno, MYF(0),
+ (int) mi->connection_name.length,
+ mi->connection_name.str);
+ DBUG_RETURN(slave_errno == ER_BAD_SLAVE ? -1 : 1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Execute a STOP SLAVE statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @param mi Pointer to Master_info object for the slave's IO thread.
+
+ @param net_report If true, saves the exit status into thd->stmt_da.
+
+ @retval 0 success
+ @retval 1 error
+ @retval -1 error
+*/
+
+int stop_slave(THD* thd, Master_info* mi, bool net_report )
+{
+ int slave_errno;
+ DBUG_ENTER("stop_slave");
+ DBUG_PRINT("enter",("Connection: %s", mi->connection_name.str));
+
+ if (check_access(thd, SUPER_ACL, any_db, NULL, NULL, 0, 0))
+ DBUG_RETURN(-1);
+ THD_STAGE_INFO(thd, stage_killing_slave);
+ int thread_mask;
+ lock_slave_threads(mi);
+ // Get a mask of _running_ threads
+ init_thread_mask(&thread_mask,mi,0 /* not inverse*/);
+ /*
+ Below we will stop all running threads.
+ But if the user wants to stop only one thread, do as if the other thread
+ was stopped (as we don't wan't to touch the other thread), so set the
+ bit to 0 for the other thread
+ */
+ if (thd->lex->slave_thd_opt)
+ thread_mask &= thd->lex->slave_thd_opt;
+
+ if (thread_mask)
+ {
+ slave_errno= terminate_slave_threads(mi,thread_mask,
+ 1 /*skip lock */);
+ }
+ else
+ {
+ //no error if both threads are already stopped, only a warning
+ slave_errno= 0;
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SLAVE_WAS_NOT_RUNNING,
+ ER(ER_SLAVE_WAS_NOT_RUNNING));
+ }
+ unlock_slave_threads(mi);
+
+ if (slave_errno)
+ {
+ if (net_report)
+ my_message(slave_errno, ER(slave_errno), MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Execute a RESET SLAVE statement.
+
+ @param thd Pointer to THD object of the client thread executing the
+ statement.
+
+ @param mi Pointer to Master_info object for the slave.
+
+ @retval 0 success
+ @retval 1 error
+*/
+int reset_slave(THD *thd, Master_info* mi)
+{
+ MY_STAT stat_area;
+ char fname[FN_REFLEN];
+ int thread_mask= 0, error= 0;
+ uint sql_errno=ER_UNKNOWN_ERROR;
+ const char* errmsg= "Unknown error occured while reseting slave";
+ char master_info_file_tmp[FN_REFLEN];
+ char relay_log_info_file_tmp[FN_REFLEN];
+ DBUG_ENTER("reset_slave");
+
+ lock_slave_threads(mi);
+ init_thread_mask(&thread_mask,mi,0 /* not inverse */);
+ if (thread_mask) // We refuse if any slave thread is running
+ {
+ unlock_slave_threads(mi);
+ my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length,
+ mi->connection_name.str);
+ DBUG_RETURN(ER_SLAVE_MUST_STOP);
+ }
+
+ ha_reset_slave(thd);
+
+ // delete relay logs, clear relay log coordinates
+ if ((error= purge_relay_logs(&mi->rli, thd,
+ 1 /* just reset */,
+ &errmsg)))
+ {
+ sql_errno= ER_RELAY_LOG_FAIL;
+ goto err;
+ }
+
+ /* Clear master's log coordinates and associated information */
+ mi->clear_in_memory_info(thd->lex->reset_slave_info.all);
+
+ /*
+ Reset errors (the idea is that we forget about the
+ old master).
+ */
+ mi->clear_error();
+ mi->rli.clear_error();
+ mi->rli.clear_until_condition();
+ mi->rli.slave_skip_counter= 0;
+
+ // close master_info_file, relay_log_info_file, set mi->inited=rli->inited=0
+ end_master_info(mi);
+
+ // and delete these two files
+ create_logfile_name_with_suffix(master_info_file_tmp,
+ sizeof(master_info_file_tmp),
+ master_info_file, 0,
+ &mi->cmp_connection_name);
+ create_logfile_name_with_suffix(relay_log_info_file_tmp,
+ sizeof(relay_log_info_file_tmp),
+ relay_log_info_file, 0,
+ &mi->cmp_connection_name);
+
+ fn_format(fname, master_info_file_tmp, mysql_data_home, "", 4+32);
+ if (mysql_file_stat(key_file_master_info, fname, &stat_area, MYF(0)) &&
+ mysql_file_delete(key_file_master_info, fname, MYF(MY_WME)))
+ {
+ error=1;
+ goto err;
+ }
+ else if (global_system_variables.log_warnings > 1)
+ sql_print_information("Deleted Master_info file '%s'.", fname);
+
+ // delete relay_log_info_file
+ fn_format(fname, relay_log_info_file_tmp, mysql_data_home, "", 4+32);
+ if (mysql_file_stat(key_file_relay_log_info, fname, &stat_area, MYF(0)) &&
+ mysql_file_delete(key_file_relay_log_info, fname, MYF(MY_WME)))
+ {
+ error=1;
+ goto err;
+ }
+ else if (global_system_variables.log_warnings > 1)
+ sql_print_information("Deleted Master_info file '%s'.", fname);
+
+ RUN_HOOK(binlog_relay_io, after_reset_slave, (thd, mi));
+err:
+ unlock_slave_threads(mi);
+ if (error)
+ my_error(sql_errno, MYF(0), errmsg);
+ DBUG_RETURN(error);
+}
+
+/*
+
+ Kill all Binlog_dump threads which previously talked to the same slave
+ ("same" means with the same server id). Indeed, if the slave stops, if the
+ Binlog_dump thread is waiting (mysql_cond_wait) for binlog update, then it
+ will keep existing until a query is written to the binlog. If the master is
+ idle, then this could last long, and if the slave reconnects, we could have 2
+ Binlog_dump threads in SHOW PROCESSLIST, until a query is written to the
+ binlog. To avoid this, when the slave reconnects and sends COM_BINLOG_DUMP,
+ the master kills any existing thread with the slave's server id (if this id
+ is not zero; it will be true for real slaves, but false for mysqlbinlog when
+ it sends COM_BINLOG_DUMP to get a remote binlog dump).
+
+ SYNOPSIS
+ kill_zombie_dump_threads()
+ slave_server_id the slave's server id
+
+*/
+
+
+void kill_zombie_dump_threads(uint32 slave_server_id)
+{
+ mysql_mutex_lock(&LOCK_thread_count);
+ I_List_iterator<THD> it(threads);
+ THD *tmp;
+
+ while ((tmp=it++))
+ {
+ if (tmp->get_command() == COM_BINLOG_DUMP &&
+ tmp->variables.server_id == slave_server_id)
+ {
+ mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
+ break;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+ if (tmp)
+ {
+ /*
+ Here we do not call kill_one_thread() as
+ it will be slow because it will iterate through the list
+ again. We just to do kill the thread ourselves.
+ */
+ tmp->awake(KILL_QUERY);
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+ }
+}
+
+/**
+ Get value for a string parameter with error checking
+
+ Note that in case of error the original string should not be updated!
+
+ @ret 0 ok
+ @ret 1 error
+*/
+
+static bool get_string_parameter(char *to, const char *from, size_t length,
+ const char *name, CHARSET_INFO *cs)
+{
+ if (from) // Empty paramaters allowed
+ {
+ size_t from_length= strlen(from);
+ uint from_numchars= cs->cset->numchars(cs, from, from + from_length);
+ if (from_numchars > length / cs->mbmaxlen)
+ {
+ my_error(ER_WRONG_STRING_LENGTH, MYF(0), from, name, length / cs->mbmaxlen);
+ return 1;
+ }
+ memcpy(to, from, from_length+1);
+ }
+ return 0;
+}
+
+
+/**
+ Execute a CHANGE MASTER statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @param mi Pointer to Master_info object belonging to the slave's IO
+ thread.
+
+ @param master_info_added Out parameter saying if the Master_info *mi was
+ added to the global list of masters. This is useful in error conditions
+ to know if caller should free Master_info *mi.
+
+ @retval FALSE success
+ @retval TRUE error
+*/
+bool change_master(THD* thd, Master_info* mi, bool *master_info_added)
+{
+ int thread_mask;
+ const char* errmsg= 0;
+ bool need_relay_log_purge= 1;
+ bool ret= FALSE;
+ char saved_host[HOSTNAME_LENGTH + 1];
+ uint saved_port;
+ char saved_log_name[FN_REFLEN];
+ Master_info::enum_using_gtid saved_using_gtid;
+ char master_info_file_tmp[FN_REFLEN];
+ char relay_log_info_file_tmp[FN_REFLEN];
+ my_off_t saved_log_pos;
+ LEX_MASTER_INFO* lex_mi= &thd->lex->mi;
+ DBUG_ENTER("change_master");
+
+ mysql_mutex_assert_owner(&LOCK_active_mi);
+ DBUG_ASSERT(master_info_index);
+
+ *master_info_added= false;
+ /*
+ We need to check if there is an empty master_host. Otherwise
+ change master succeeds, a master.info file is created containing
+ empty master_host string and when issuing: start slave; an error
+ is thrown stating that the server is not configured as slave.
+ (See BUG#28796).
+ */
+ if (lex_mi->host && !*lex_mi->host)
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "MASTER_HOST");
+ DBUG_RETURN(TRUE);
+ }
+ if (master_info_index->check_duplicate_master_info(&lex_mi->connection_name,
+ lex_mi->host,
+ lex_mi->port))
+ DBUG_RETURN(TRUE);
+
+ lock_slave_threads(mi);
+ init_thread_mask(&thread_mask,mi,0 /*not inverse*/);
+ if (thread_mask) // We refuse if any slave thread is running
+ {
+ my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length,
+ mi->connection_name.str);
+ ret= TRUE;
+ goto err;
+ }
+
+ THD_STAGE_INFO(thd, stage_changing_master);
+
+ create_logfile_name_with_suffix(master_info_file_tmp,
+ sizeof(master_info_file_tmp),
+ master_info_file, 0,
+ &mi->cmp_connection_name);
+ create_logfile_name_with_suffix(relay_log_info_file_tmp,
+ sizeof(relay_log_info_file_tmp),
+ relay_log_info_file, 0,
+ &mi->cmp_connection_name);
+
+ /* if new Master_info doesn't exists, add it */
+ if (!master_info_index->get_master_info(&mi->connection_name,
+ Sql_condition::WARN_LEVEL_NOTE))
+ {
+ if (master_info_index->add_master_info(mi, TRUE))
+ {
+ my_error(ER_MASTER_INFO, MYF(0),
+ (int) lex_mi->connection_name.length,
+ lex_mi->connection_name.str);
+ ret= TRUE;
+ goto err;
+ }
+ *master_info_added= true;
+ }
+ if (global_system_variables.log_warnings > 1)
+ sql_print_information("Master: '%.*s' Master_info_file: '%s' "
+ "Relay_info_file: '%s'",
+ (int) mi->connection_name.length,
+ mi->connection_name.str,
+ master_info_file_tmp, relay_log_info_file_tmp);
+
+ if (init_master_info(mi, master_info_file_tmp, relay_log_info_file_tmp, 0,
+ thread_mask))
+ {
+ my_error(ER_MASTER_INFO, MYF(0),
+ (int) lex_mi->connection_name.length,
+ lex_mi->connection_name.str);
+ ret= TRUE;
+ goto err;
+ }
+
+ /*
+ Data lock not needed since we have already stopped the running threads,
+ and we have the hold on the run locks which will keep all threads that
+ could possibly modify the data structures from running
+ */
+
+ /*
+ Before processing the command, save the previous state.
+ */
+ strmake_buf(saved_host, mi->host);
+ saved_port= mi->port;
+ strmake_buf(saved_log_name, mi->master_log_name);
+ saved_log_pos= mi->master_log_pos;
+ saved_using_gtid= mi->using_gtid;
+
+ /*
+ If the user specified host or port without binlog or position,
+ reset binlog's name to FIRST and position to 4.
+ */
+
+ if ((lex_mi->host || lex_mi->port) && !lex_mi->log_file_name && !lex_mi->pos)
+ {
+ mi->master_log_name[0] = 0;
+ mi->master_log_pos= BIN_LOG_HEADER_SIZE;
+ }
+
+ if (lex_mi->log_file_name)
+ strmake_buf(mi->master_log_name, lex_mi->log_file_name);
+ if (lex_mi->pos)
+ {
+ mi->master_log_pos= lex_mi->pos;
+ }
+ DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
+
+ if (get_string_parameter(mi->host, lex_mi->host, sizeof(mi->host)-1,
+ "MASTER_HOST", system_charset_info) ||
+ get_string_parameter(mi->user, lex_mi->user, sizeof(mi->user)-1,
+ "MASTER_USER", system_charset_info) ||
+ get_string_parameter(mi->password, lex_mi->password,
+ sizeof(mi->password)-1, "MASTER_PASSWORD",
+ &my_charset_bin))
+ {
+ ret= TRUE;
+ goto err;
+ }
+
+ if (lex_mi->port)
+ mi->port = lex_mi->port;
+ if (lex_mi->connect_retry)
+ mi->connect_retry = lex_mi->connect_retry;
+ if (lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
+ mi->heartbeat_period = lex_mi->heartbeat_period;
+ else
+ mi->heartbeat_period= (float) MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD,
+ (slave_net_timeout/2.0));
+ mi->received_heartbeats= 0; // counter lives until master is CHANGEd
+ /*
+ reset the last time server_id list if the current CHANGE MASTER
+ is mentioning IGNORE_SERVER_IDS= (...)
+ */
+ if (lex_mi->repl_ignore_server_ids_opt == LEX_MASTER_INFO::LEX_MI_ENABLE)
+ reset_dynamic(&mi->ignore_server_ids);
+ for (uint i= 0; i < lex_mi->repl_ignore_server_ids.elements; i++)
+ {
+ ulong s_id;
+ get_dynamic(&lex_mi->repl_ignore_server_ids, (uchar*) &s_id, i);
+ if (s_id == global_system_variables.server_id && replicate_same_server_id)
+ {
+ my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), static_cast<int>(s_id));
+ ret= TRUE;
+ goto err;
+ }
+ else
+ {
+ if (bsearch((const ulong *) &s_id,
+ mi->ignore_server_ids.buffer,
+ mi->ignore_server_ids.elements, sizeof(ulong),
+ (int (*) (const void*, const void*))
+ change_master_server_id_cmp) == NULL)
+ insert_dynamic(&mi->ignore_server_ids, (uchar*) &s_id);
+ }
+ }
+ sort_dynamic(&mi->ignore_server_ids, (qsort_cmp) change_master_server_id_cmp);
+
+ if (lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
+ mi->ssl= (lex_mi->ssl == LEX_MASTER_INFO::LEX_MI_ENABLE);
+
+ if (lex_mi->ssl_verify_server_cert != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
+ mi->ssl_verify_server_cert=
+ (lex_mi->ssl_verify_server_cert == LEX_MASTER_INFO::LEX_MI_ENABLE);
+
+ if (lex_mi->ssl_ca)
+ strmake_buf(mi->ssl_ca, lex_mi->ssl_ca);
+ if (lex_mi->ssl_capath)
+ strmake_buf(mi->ssl_capath, lex_mi->ssl_capath);
+ if (lex_mi->ssl_cert)
+ strmake_buf(mi->ssl_cert, lex_mi->ssl_cert);
+ if (lex_mi->ssl_cipher)
+ strmake_buf(mi->ssl_cipher, lex_mi->ssl_cipher);
+ if (lex_mi->ssl_key)
+ strmake_buf(mi->ssl_key, lex_mi->ssl_key);
+ if (lex_mi->ssl_crl)
+ strmake_buf(mi->ssl_crl, lex_mi->ssl_crl);
+ if (lex_mi->ssl_crlpath)
+ strmake_buf(mi->ssl_crlpath, lex_mi->ssl_crlpath);
+
+#ifndef HAVE_OPENSSL
+ if (lex_mi->ssl || lex_mi->ssl_ca || lex_mi->ssl_capath ||
+ lex_mi->ssl_cert || lex_mi->ssl_cipher || lex_mi->ssl_key ||
+ lex_mi->ssl_verify_server_cert || lex_mi->ssl_crl || lex_mi->ssl_crlpath)
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SLAVE_IGNORED_SSL_PARAMS, ER(ER_SLAVE_IGNORED_SSL_PARAMS));
+#endif
+
+ if (lex_mi->relay_log_name)
+ {
+ need_relay_log_purge= 0;
+ char relay_log_name[FN_REFLEN];
+ mi->rli.relay_log.make_log_name(relay_log_name, lex_mi->relay_log_name);
+ strmake_buf(mi->rli.group_relay_log_name, relay_log_name);
+ strmake_buf(mi->rli.event_relay_log_name, relay_log_name);
+ }
+
+ if (lex_mi->relay_log_pos)
+ {
+ need_relay_log_purge= 0;
+ mi->rli.group_relay_log_pos= mi->rli.event_relay_log_pos= lex_mi->relay_log_pos;
+ }
+
+ if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_SLAVE_POS)
+ mi->using_gtid= Master_info::USE_GTID_SLAVE_POS;
+ else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_CURRENT_POS)
+ mi->using_gtid= Master_info::USE_GTID_CURRENT_POS;
+ else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_NO ||
+ lex_mi->log_file_name || lex_mi->pos ||
+ lex_mi->relay_log_name || lex_mi->relay_log_pos)
+ mi->using_gtid= Master_info::USE_GTID_NO;
+
+ /*
+ If user did specify neither host nor port nor any log name nor any log
+ pos, i.e. he specified only user/password/master_connect_retry, he probably
+ wants replication to resume from where it had left, i.e. from the
+ coordinates of the **SQL** thread (imagine the case where the I/O is ahead
+ of the SQL; restarting from the coordinates of the I/O would lose some
+ events which is probably unwanted when you are just doing minor changes
+ like changing master_connect_retry).
+ A side-effect is that if only the I/O thread was started, this thread may
+ restart from ''/4 after the CHANGE MASTER. That's a minor problem (it is a
+ much more unlikely situation than the one we are fixing here).
+ Note: coordinates of the SQL thread must be read here, before the
+ 'if (need_relay_log_purge)' block which resets them.
+ */
+ if (!lex_mi->host && !lex_mi->port &&
+ !lex_mi->log_file_name && !lex_mi->pos &&
+ need_relay_log_purge)
+ {
+ /*
+ Sometimes mi->rli.master_log_pos == 0 (it happens when the SQL thread is
+ not initialized), so we use a MY_MAX().
+ What happens to mi->rli.master_log_pos during the initialization stages
+ of replication is not 100% clear, so we guard against problems using
+ MY_MAX().
+ */
+ mi->master_log_pos = MY_MAX(BIN_LOG_HEADER_SIZE,
+ mi->rli.group_master_log_pos);
+ strmake_buf(mi->master_log_name, mi->rli.group_master_log_name);
+ }
+
+ /*
+ Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never
+ a slave before).
+ */
+ if (flush_master_info(mi, FALSE, FALSE))
+ {
+ my_error(ER_RELAY_LOG_INIT, MYF(0), "Failed to flush master info file");
+ ret= TRUE;
+ goto err;
+ }
+ if (need_relay_log_purge)
+ {
+ THD_STAGE_INFO(thd, stage_purging_old_relay_logs);
+ if (purge_relay_logs(&mi->rli, thd,
+ 0 /* not only reset, but also reinit */,
+ &errmsg))
+ {
+ my_error(ER_RELAY_LOG_FAIL, MYF(0), errmsg);
+ ret= TRUE;
+ goto err;
+ }
+ }
+ else
+ {
+ const char* msg;
+ /* Relay log is already initialized */
+ if (init_relay_log_pos(&mi->rli,
+ mi->rli.group_relay_log_name,
+ mi->rli.group_relay_log_pos,
+ 0 /*no data lock*/,
+ &msg, 0))
+ {
+ my_error(ER_RELAY_LOG_INIT, MYF(0), msg);
+ ret= TRUE;
+ goto err;
+ }
+ }
+ /*
+ Coordinates in rli were spoilt by the 'if (need_relay_log_purge)' block,
+ so restore them to good values. If we left them to ''/0, that would work;
+ but that would fail in the case of 2 successive CHANGE MASTER (without a
+ START SLAVE in between): because first one would set the coords in mi to
+ the good values of those in rli, the set those in rli to ''/0, then
+ second CHANGE MASTER would set the coords in mi to those of rli, i.e. to
+ ''/0: we have lost all copies of the original good coordinates.
+ That's why we always save good coords in rli.
+ */
+ mi->rli.group_master_log_pos= mi->master_log_pos;
+ DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
+ strmake_buf(mi->rli.group_master_log_name,mi->master_log_name);
+
+ if (!mi->rli.group_master_log_name[0]) // uninitialized case
+ mi->rli.group_master_log_pos=0;
+
+ mysql_mutex_lock(&mi->rli.data_lock);
+ mi->rli.abort_pos_wait++; /* for MASTER_POS_WAIT() to abort */
+ /* Clear the errors, for a clean start */
+ mi->rli.clear_error();
+ mi->rli.clear_until_condition();
+ mi->rli.slave_skip_counter= 0;
+
+ sql_print_information("'CHANGE MASTER TO executed'. "
+ "Previous state master_host='%s', master_port='%u', master_log_file='%s', "
+ "master_log_pos='%ld'. "
+ "New state master_host='%s', master_port='%u', master_log_file='%s', "
+ "master_log_pos='%ld'.", saved_host, saved_port, saved_log_name,
+ (ulong) saved_log_pos, mi->host, mi->port, mi->master_log_name,
+ (ulong) mi->master_log_pos);
+ if (saved_using_gtid != Master_info::USE_GTID_NO ||
+ mi->using_gtid != Master_info::USE_GTID_NO)
+ sql_print_information("Previous Using_Gtid=%s. New Using_Gtid=%s",
+ mi->using_gtid_astext(saved_using_gtid),
+ mi->using_gtid_astext(mi->using_gtid));
+
+ /*
+ If we don't write new coordinates to disk now, then old will remain in
+ relay-log.info until START SLAVE is issued; but if mysqld is shutdown
+ before START SLAVE, then old will remain in relay-log.info, and will be the
+ in-memory value at restart (thus causing errors, as the old relay log does
+ not exist anymore).
+ */
+ flush_relay_log_info(&mi->rli);
+ mysql_cond_broadcast(&mi->data_cond);
+ mysql_mutex_unlock(&mi->rli.data_lock);
+
+err:
+ unlock_slave_threads(mi);
+ if (ret == FALSE)
+ my_ok(thd);
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Execute a RESET MASTER statement.
+
+ @param thd Pointer to THD object of the client thread executing the
+ statement.
+
+ @retval 0 success
+ @retval 1 error
+*/
+int reset_master(THD* thd, rpl_gtid *init_state, uint32 init_state_len)
+{
+ if (!mysql_bin_log.is_open())
+ {
+ my_message(ER_FLUSH_MASTER_BINLOG_CLOSED,
+ ER(ER_FLUSH_MASTER_BINLOG_CLOSED), MYF(ME_BELL+ME_WAITTANG));
+ return 1;
+ }
+
+ if (mysql_bin_log.reset_logs(thd, 1, init_state, init_state_len))
+ return 1;
+ RUN_HOOK(binlog_transmit, after_reset_master, (thd, 0 /* flags */));
+ return 0;
+}
+
+
+/**
+ Execute a SHOW BINLOG EVENTS statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+bool mysql_show_binlog_events(THD* thd)
+{
+ Protocol *protocol= thd->protocol;
+ List<Item> field_list;
+ const char *errmsg = 0;
+ bool ret = TRUE;
+ IO_CACHE log;
+ File file = -1;
+ MYSQL_BIN_LOG *binary_log= NULL;
+ int old_max_allowed_packet= thd->variables.max_allowed_packet;
+ Master_info *mi= 0;
+ LOG_INFO linfo;
+
+ DBUG_ENTER("mysql_show_binlog_events");
+
+ Log_event::init_show_field_list(&field_list);
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ Format_description_log_event *description_event= new
+ Format_description_log_event(3); /* MySQL 4.0 by default */
+
+ DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS ||
+ thd->lex->sql_command == SQLCOM_SHOW_RELAYLOG_EVENTS);
+
+ /* select wich binary log to use: binlog or relay */
+ if ( thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS )
+ {
+ /*
+ Wait for handlers to insert any pending information
+ into the binlog. For e.g. ndb which updates the binlog asynchronously
+ this is needed so that the uses sees all its own commands in the binlog
+ */
+ ha_binlog_wait(thd);
+
+ binary_log= &mysql_bin_log;
+ }
+ else /* showing relay log contents */
+ {
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (!master_info_index ||
+ !(mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_ERROR)))
+ {
+ mysql_mutex_unlock(&LOCK_active_mi);
+ DBUG_RETURN(TRUE);
+ }
+ binary_log= &(mi->rli.relay_log);
+ }
+
+ if (binary_log->is_open())
+ {
+ LEX_MASTER_INFO *lex_mi= &thd->lex->mi;
+ SELECT_LEX_UNIT *unit= &thd->lex->unit;
+ ha_rows event_count, limit_start, limit_end;
+ my_off_t pos = MY_MAX(BIN_LOG_HEADER_SIZE, lex_mi->pos); // user-friendly
+ char search_file_name[FN_REFLEN], *name;
+ const char *log_file_name = lex_mi->log_file_name;
+ mysql_mutex_t *log_lock = binary_log->get_log_lock();
+ Log_event* ev;
+
+ if (mi)
+ {
+ /* We can unlock the mutex as we have a lock on the file */
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mi= 0;
+ }
+
+ unit->set_limit(thd->lex->current_select);
+ limit_start= unit->offset_limit_cnt;
+ limit_end= unit->select_limit_cnt;
+
+ name= search_file_name;
+ if (log_file_name)
+ binary_log->make_log_name(search_file_name, log_file_name);
+ else
+ name=0; // Find first log
+
+ linfo.index_file_offset = 0;
+
+ if (binary_log->find_log_pos(&linfo, name, 1))
+ {
+ errmsg = "Could not find target log";
+ goto err;
+ }
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = &linfo;
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ if ((file=open_binlog(&log, linfo.log_file_name, &errmsg)) < 0)
+ goto err;
+
+ /*
+ to account binlog event header size
+ */
+ thd->variables.max_allowed_packet += MAX_LOG_EVENT_HEADER;
+
+ mysql_mutex_lock(log_lock);
+
+ /*
+ open_binlog() sought to position 4.
+ Read the first event in case it's a Format_description_log_event, to
+ know the format. If there's no such event, we are 3.23 or 4.x. This
+ code, like before, can't read 3.23 binlogs.
+ This code will fail on a mixed relay log (one which has Format_desc then
+ Rotate then Format_desc).
+ */
+ ev= Log_event::read_log_event(&log, (mysql_mutex_t*)0, description_event,
+ opt_master_verify_checksum);
+ if (ev)
+ {
+ if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT)
+ {
+ delete description_event;
+ description_event= (Format_description_log_event*) ev;
+ }
+ else
+ delete ev;
+ }
+
+ my_b_seek(&log, pos);
+
+ if (!description_event->is_valid())
+ {
+ errmsg="Invalid Format_description event; could be out of memory";
+ goto err;
+ }
+
+ for (event_count = 0;
+ (ev = Log_event::read_log_event(&log, (mysql_mutex_t*) 0,
+ description_event,
+ opt_master_verify_checksum)); )
+ {
+ if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT)
+ description_event->checksum_alg= ev->checksum_alg;
+
+ if (event_count >= limit_start &&
+ ev->net_send(thd, protocol, linfo.log_file_name, pos))
+ {
+ errmsg = "Net error";
+ delete ev;
+ mysql_mutex_unlock(log_lock);
+ goto err;
+ }
+
+ pos = my_b_tell(&log);
+ delete ev;
+
+ if (++event_count >= limit_end)
+ break;
+ }
+
+ if (event_count < limit_end && log.error)
+ {
+ errmsg = "Wrong offset or I/O error";
+ mysql_mutex_unlock(log_lock);
+ goto err;
+ }
+
+ mysql_mutex_unlock(log_lock);
+ }
+ else if (mi)
+ mysql_mutex_unlock(&LOCK_active_mi);
+
+ // Check that linfo is still on the function scope.
+ DEBUG_SYNC(thd, "after_show_binlog_events");
+
+ ret= FALSE;
+
+err:
+ delete description_event;
+ if (file >= 0)
+ {
+ end_io_cache(&log);
+ mysql_file_close(file, MYF(MY_WME));
+ }
+
+ if (errmsg)
+ my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0),
+ "SHOW BINLOG EVENTS", errmsg);
+ else
+ my_eof(thd);
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = 0;
+ mysql_mutex_unlock(&LOCK_thread_count);
+ thd->variables.max_allowed_packet= old_max_allowed_packet;
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Execute a SHOW MASTER STATUS statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+bool show_binlog_info(THD* thd)
+{
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("show_binlog_info");
+ List<Item> field_list;
+ field_list.push_back(new Item_empty_string("File", FN_REFLEN));
+ field_list.push_back(new Item_return_int("Position",20,
+ MYSQL_TYPE_LONGLONG));
+ field_list.push_back(new Item_empty_string("Binlog_Do_DB",255));
+ field_list.push_back(new Item_empty_string("Binlog_Ignore_DB",255));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+ protocol->prepare_for_resend();
+
+ if (mysql_bin_log.is_open())
+ {
+ LOG_INFO li;
+ mysql_bin_log.get_current_log(&li);
+ int dir_len = dirname_length(li.log_file_name);
+ protocol->store(li.log_file_name + dir_len, &my_charset_bin);
+ protocol->store((ulonglong) li.pos);
+ protocol->store(binlog_filter->get_do_db());
+ protocol->store(binlog_filter->get_ignore_db());
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+ }
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Execute a SHOW BINARY LOGS statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+bool show_binlogs(THD* thd)
+{
+ IO_CACHE *index_file;
+ LOG_INFO cur;
+ File file;
+ char fname[FN_REFLEN];
+ List<Item> field_list;
+ uint length;
+ int cur_dir_len;
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("show_binlogs");
+
+ if (!mysql_bin_log.is_open())
+ {
+ my_error(ER_NO_BINARY_LOGGING, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ field_list.push_back(new Item_empty_string("Log_name", 255));
+ field_list.push_back(new Item_return_int("File_size", 20,
+ MYSQL_TYPE_LONGLONG));
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ mysql_mutex_lock(mysql_bin_log.get_log_lock());
+ mysql_bin_log.lock_index();
+ index_file=mysql_bin_log.get_index_file();
+
+ mysql_bin_log.raw_get_current_log(&cur); // dont take mutex
+ mysql_mutex_unlock(mysql_bin_log.get_log_lock()); // lockdep, OK
+
+ cur_dir_len= dirname_length(cur.log_file_name);
+
+ reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0);
+
+ /* The file ends with EOF or empty line */
+ while ((length=my_b_gets(index_file, fname, sizeof(fname))) > 1)
+ {
+ int dir_len;
+ ulonglong file_length= 0; // Length if open fails
+ fname[--length] = '\0'; // remove the newline
+
+ protocol->prepare_for_resend();
+ dir_len= dirname_length(fname);
+ length-= dir_len;
+ protocol->store(fname + dir_len, length, &my_charset_bin);
+
+ if (!(strncmp(fname+dir_len, cur.log_file_name+cur_dir_len, length)))
+ file_length= cur.pos; /* The active log, use the active position */
+ else
+ {
+ /* this is an old log, open it and find the size */
+ if ((file= mysql_file_open(key_file_binlog,
+ fname, O_RDONLY | O_SHARE | O_BINARY,
+ MYF(0))) >= 0)
+ {
+ file_length= (ulonglong) mysql_file_seek(file, 0L, MY_SEEK_END, MYF(0));
+ mysql_file_close(file, MYF(0));
+ }
+ }
+ protocol->store(file_length);
+ if (protocol->write())
+ goto err;
+ }
+ if(index_file->error == -1)
+ goto err;
+ mysql_bin_log.unlock_index();
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+
+err:
+ mysql_bin_log.unlock_index();
+ DBUG_RETURN(TRUE);
+}
+
+/**
+ Load data's io cache specific hook to be executed
+ before a chunk of data is being read into the cache's buffer
+ The fuction instantianates and writes into the binlog
+ replication events along LOAD DATA processing.
+
+ @param file pointer to io-cache
+ @retval 0 success
+ @retval 1 failure
+*/
+int log_loaded_block(IO_CACHE* file)
+{
+ DBUG_ENTER("log_loaded_block");
+ LOAD_FILE_INFO *lf_info;
+ uint block_len;
+ /* buffer contains position where we started last read */
+ uchar* buffer= (uchar*) my_b_get_buffer_start(file);
+ uint max_event_size= current_thd->variables.max_allowed_packet;
+ lf_info= (LOAD_FILE_INFO*) file->arg;
+ if (lf_info->thd->is_current_stmt_binlog_format_row())
+ DBUG_RETURN(0);
+ if (lf_info->last_pos_in_file != HA_POS_ERROR &&
+ lf_info->last_pos_in_file >= my_b_get_pos_in_file(file))
+ DBUG_RETURN(0);
+
+ for (block_len= (uint) (my_b_get_bytes_in_buffer(file)); block_len > 0;
+ buffer += MY_MIN(block_len, max_event_size),
+ block_len -= MY_MIN(block_len, max_event_size))
+ {
+ lf_info->last_pos_in_file= my_b_get_pos_in_file(file);
+ if (lf_info->wrote_create_file)
+ {
+ Append_block_log_event a(lf_info->thd, lf_info->thd->db, buffer,
+ MY_MIN(block_len, max_event_size),
+ lf_info->log_delayed);
+ if (mysql_bin_log.write(&a))
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ Begin_load_query_log_event b(lf_info->thd, lf_info->thd->db,
+ buffer,
+ MY_MIN(block_len, max_event_size),
+ lf_info->log_delayed);
+ if (mysql_bin_log.write(&b))
+ DBUG_RETURN(1);
+ lf_info->wrote_create_file= 1;
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Initialise the slave replication state from the mysql.gtid_slave_pos table.
+
+ This is called each time an SQL thread starts, but the data is only actually
+ loaded on the first call.
+
+ The slave state is the last GTID applied on the slave within each
+ replication domain.
+
+ To avoid row lock contention, there are multiple rows for each domain_id.
+ The one containing the current slave state is the one with the maximal
+ sub_id value, within each domain_id.
+
+ CREATE TABLE mysql.gtid_slave_pos (
+ domain_id INT UNSIGNED NOT NULL,
+ sub_id BIGINT UNSIGNED NOT NULL,
+ server_id INT UNSIGNED NOT NULL,
+ seq_no BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (domain_id, sub_id))
+*/
+
+void
+rpl_init_gtid_slave_state()
+{
+ rpl_global_gtid_slave_state.init();
+}
+
+
+void
+rpl_deinit_gtid_slave_state()
+{
+ rpl_global_gtid_slave_state.deinit();
+}
+
+
+void
+rpl_init_gtid_waiting()
+{
+ rpl_global_gtid_waiting.init();
+}
+
+
+void
+rpl_deinit_gtid_waiting()
+{
+ rpl_global_gtid_waiting.destroy();
+}
+
+
+/*
+ Format the current GTID state as a string, for returning the value of
+ @@global.gtid_slave_pos.
+
+ If the flag use_binlog is true, then the contents of the binary log (if
+ enabled) is merged into the current GTID state (@@global.gtid_current_pos).
+*/
+int
+rpl_append_gtid_state(String *dest, bool use_binlog)
+{
+ int err;
+ rpl_gtid *gtid_list= NULL;
+ uint32 num_gtids= 0;
+
+ if (use_binlog && opt_bin_log &&
+ (err= mysql_bin_log.get_most_recent_gtid_list(&gtid_list, &num_gtids)))
+ return err;
+
+ err= rpl_global_gtid_slave_state.tostring(dest, gtid_list, num_gtids);
+ my_free(gtid_list);
+
+ return err;
+}
+
+
+/*
+ Load the current GTID position into a slave_connection_state, for use when
+ connecting to a master server with GTID.
+
+ If the flag use_binlog is true, then the contents of the binary log (if
+ enabled) is merged into the current GTID state (master_use_gtid=current_pos).
+*/
+int
+rpl_load_gtid_state(slave_connection_state *state, bool use_binlog)
+{
+ int err;
+ rpl_gtid *gtid_list= NULL;
+ uint32 num_gtids= 0;
+
+ if (use_binlog && opt_bin_log &&
+ (err= mysql_bin_log.get_most_recent_gtid_list(&gtid_list, &num_gtids)))
+ return err;
+
+ err= state->load(&rpl_global_gtid_slave_state, gtid_list, num_gtids);
+ my_free(gtid_list);
+
+ return err;
+}
+
+
+bool
+rpl_gtid_pos_check(THD *thd, char *str, size_t len)
+{
+ slave_connection_state tmp_slave_state;
+ bool gave_conflict_warning= false, gave_missing_warning= false;
+
+ /* Check that we can parse the supplied string. */
+ if (tmp_slave_state.load(str, len))
+ return true;
+
+ /*
+ Check our own binlog for any of our own transactions that are newer
+ than the GTID state the user is requesting. Any such transactions would
+ result in an out-of-order binlog, which could break anyone replicating
+ with us as master.
+
+ So give an error if this is found, requesting the user to do a
+ RESET MASTER (to clean up the binlog) if they really want this.
+ */
+ if (mysql_bin_log.is_open())
+ {
+ rpl_gtid *binlog_gtid_list= NULL;
+ uint32 num_binlog_gtids= 0;
+ uint32 i;
+
+ if (mysql_bin_log.get_most_recent_gtid_list(&binlog_gtid_list,
+ &num_binlog_gtids))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME));
+ return true;
+ }
+ for (i= 0; i < num_binlog_gtids; ++i)
+ {
+ rpl_gtid *binlog_gtid= &binlog_gtid_list[i];
+ rpl_gtid *slave_gtid;
+ if (binlog_gtid->server_id != global_system_variables.server_id)
+ continue;
+ if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id)))
+ {
+ if (opt_gtid_strict_mode)
+ {
+ my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0),
+ binlog_gtid->domain_id, binlog_gtid->domain_id,
+ binlog_gtid->server_id, binlog_gtid->seq_no);
+ break;
+ }
+ else if (!gave_missing_warning)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_MASTER_GTID_POS_MISSING_DOMAIN,
+ ER(ER_MASTER_GTID_POS_MISSING_DOMAIN),
+ binlog_gtid->domain_id, binlog_gtid->domain_id,
+ binlog_gtid->server_id, binlog_gtid->seq_no);
+ gave_missing_warning= true;
+ }
+ }
+ else if (slave_gtid->seq_no < binlog_gtid->seq_no)
+ {
+ if (opt_gtid_strict_mode)
+ {
+ my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0),
+ slave_gtid->domain_id, slave_gtid->server_id,
+ slave_gtid->seq_no, binlog_gtid->domain_id,
+ binlog_gtid->server_id, binlog_gtid->seq_no);
+ break;
+ }
+ else if (!gave_conflict_warning)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG,
+ ER(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG),
+ slave_gtid->domain_id, slave_gtid->server_id,
+ slave_gtid->seq_no, binlog_gtid->domain_id,
+ binlog_gtid->server_id, binlog_gtid->seq_no);
+ gave_conflict_warning= true;
+ }
+ }
+ }
+ my_free(binlog_gtid_list);
+ if (i != num_binlog_gtids)
+ return true;
+ }
+
+ return false;
+}
+
+
+bool
+rpl_gtid_pos_update(THD *thd, char *str, size_t len)
+{
+ if (rpl_global_gtid_slave_state.load(thd, str, len, true, true))
+ {
+ my_error(ER_FAILED_GTID_STATE_INIT, MYF(0));
+ return true;
+ }
+ else
+ return false;
+}
+
+
+#endif /* HAVE_REPLICATION */
diff --git a/sql/sql_repl.h b/sql/sql_repl.h
new file mode 100644
index 00000000000..7f7751b8f44
--- /dev/null
+++ b/sql/sql_repl.h
@@ -0,0 +1,85 @@
+/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_REPL_INCLUDED
+#define SQL_REPL_INCLUDED
+
+#include "rpl_filter.h"
+
+#ifdef HAVE_REPLICATION
+#include "slave.h"
+
+typedef struct st_slave_info
+{
+ uint32 server_id;
+ uint32 rpl_recovery_rank, master_id;
+ char host[HOSTNAME_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1];
+ char user[USERNAME_LENGTH+1];
+ char password[MAX_PASSWORD_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1];
+ uint16 port;
+ THD* thd;
+} SLAVE_INFO;
+
+struct slave_connection_state;
+
+extern my_bool opt_show_slave_auth_info;
+extern char *master_host, *master_info_file;
+extern bool server_id_supplied;
+
+extern int max_binlog_dump_events;
+extern my_bool opt_sporadic_binlog_dump_fail;
+
+int start_slave(THD* thd, Master_info* mi, bool net_report);
+int stop_slave(THD* thd, Master_info* mi, bool net_report);
+bool change_master(THD* thd, Master_info* mi, bool *master_info_added);
+bool mysql_show_binlog_events(THD* thd);
+int reset_slave(THD *thd, Master_info* mi);
+int reset_master(THD* thd, rpl_gtid *init_state, uint32 init_state_len);
+bool purge_master_logs(THD* thd, const char* to_log);
+bool purge_master_logs_before_date(THD* thd, time_t purge_time);
+bool log_in_use(const char* log_name);
+void adjust_linfo_offsets(my_off_t purge_offset);
+bool show_binlogs(THD* thd);
+extern int init_master_info(Master_info* mi);
+void kill_zombie_dump_threads(uint32 slave_server_id);
+int check_binlog_magic(IO_CACHE* log, const char** errmsg);
+
+typedef struct st_load_file_info
+{
+ THD* thd;
+ my_off_t last_pos_in_file;
+ bool wrote_create_file, log_delayed;
+} LOAD_FILE_INFO;
+
+int log_loaded_block(IO_CACHE* file);
+int init_replication_sys_vars();
+void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, ushort flags);
+
+#ifdef HAVE_PSI_INTERFACE
+extern PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state;
+#endif
+void rpl_init_gtid_slave_state();
+void rpl_deinit_gtid_slave_state();
+void rpl_init_gtid_waiting();
+void rpl_deinit_gtid_waiting();
+int gtid_state_from_binlog_pos(const char *name, uint32 pos, String *out_str);
+int rpl_append_gtid_state(String *dest, bool use_binlog);
+int rpl_load_gtid_state(slave_connection_state *state, bool use_binlog);
+bool rpl_gtid_pos_check(THD *thd, char *str, size_t len);
+bool rpl_gtid_pos_update(THD *thd, char *str, size_t len);
+
+#endif /* HAVE_REPLICATION */
+
+#endif /* SQL_REPL_INCLUDED */
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
new file mode 100644
index 00000000000..2aeddf2415d
--- /dev/null
+++ b/sql/sql_select.cc
@@ -0,0 +1,25270 @@
+/* Copyright (c) 2000, 2015 Oracle and/or its affiliates.
+ Copyright (c) 2009, 2015 MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/**
+ @file
+
+ @brief
+ mysql_select and join optimization
+
+
+ @defgroup Query_Optimizer Query Optimizer
+ @{
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_select.h"
+#include "sql_cache.h" // query_cache_*
+#include "sql_table.h" // primary_key_name
+#include "probes_mysql.h"
+#include "key.h" // key_copy, key_cmp, key_cmp_if_same
+#include "lock.h" // mysql_unlock_some_tables,
+ // mysql_unlock_read_tables
+#include "sql_show.h" // append_identifier
+#include "sql_base.h" // setup_wild, setup_fields, fill_record
+#include "sql_parse.h" // check_stack_overrun
+#include "sql_partition.h" // make_used_partitions_str
+#include "sql_acl.h" // *_ACL
+#include "sql_test.h" // print_where, print_keyuse_array,
+ // print_sjm, print_plan, TEST_join
+#include "records.h" // init_read_record, end_read_record
+#include "filesort.h" // filesort_free_buffers
+#include "sql_union.h" // mysql_union
+#include "opt_subselect.h"
+#include "log_slow.h"
+#include "sql_derived.h"
+#include "sql_statistics.h"
+
+#include "debug_sync.h" // DEBUG_SYNC
+#include <m_ctype.h>
+#include <my_bit.h>
+#include <hash.h>
+#include <ft_global.h>
+
+const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref",
+ "MAYBE_REF","ALL","range","index","fulltext",
+ "ref_or_null","unique_subquery","index_subquery",
+ "index_merge", "hash_ALL", "hash_range",
+ "hash_index", "hash_index_merge" };
+
+struct st_sargable_param;
+
+static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array);
+static bool make_join_statistics(JOIN *join, List<TABLE_LIST> &leaves,
+ COND *conds, DYNAMIC_ARRAY *keyuse);
+static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,
+ JOIN_TAB *join_tab,
+ uint tables, COND *conds,
+ table_map table_map, SELECT_LEX *select_lex,
+ st_sargable_param **sargables);
+static bool sort_and_filter_keyuse(THD *thd, DYNAMIC_ARRAY *keyuse,
+ bool skip_unprefixed_keyparts);
+static int sort_keyuse(KEYUSE *a,KEYUSE *b);
+static bool are_tables_local(JOIN_TAB *jtab, table_map used_tables);
+static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
+ bool allow_full_scan, table_map used_tables);
+void best_access_path(JOIN *join, JOIN_TAB *s,
+ table_map remaining_tables, uint idx,
+ bool disable_jbuf, double record_count,
+ POSITION *pos, POSITION *loose_scan_pos);
+static void optimize_straight_join(JOIN *join, table_map join_tables);
+static bool greedy_search(JOIN *join, table_map remaining_tables,
+ uint depth, uint prune_level,
+ uint use_cond_selectivity);
+static bool best_extension_by_limited_search(JOIN *join,
+ table_map remaining_tables,
+ uint idx, double record_count,
+ double read_time, uint depth,
+ uint prune_level,
+ uint use_cond_selectivity);
+static uint determine_search_depth(JOIN* join);
+C_MODE_START
+static int join_tab_cmp(const void *dummy, const void* ptr1, const void* ptr2);
+static int join_tab_cmp_straight(const void *dummy, const void* ptr1, const void* ptr2);
+static int join_tab_cmp_embedded_first(const void *emb, const void* ptr1, const void *ptr2);
+C_MODE_END
+/*
+ TODO: 'find_best' is here only temporarily until 'greedy_search' is
+ tested and approved.
+*/
+static bool find_best(JOIN *join,table_map rest_tables,uint index,
+ double record_count,double read_time, uint use_cond_selectivity);
+static uint cache_record_length(JOIN *join,uint index);
+bool get_best_combination(JOIN *join);
+static store_key *get_store_key(THD *thd,
+ KEYUSE *keyuse, table_map used_tables,
+ KEY_PART_INFO *key_part, uchar *key_buff,
+ uint maybe_null);
+static bool make_outerjoin_info(JOIN *join);
+static Item*
+make_cond_after_sjm(Item *root_cond, Item *cond, table_map tables,
+ table_map sjm_tables, bool inside_or_clause);
+static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *item);
+static void revise_cache_usage(JOIN_TAB *join_tab);
+static bool make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after);
+static bool only_eq_ref_tables(JOIN *join, ORDER *order, table_map tables);
+static void update_depend_map(JOIN *join);
+static void update_depend_map_for_order(JOIN *join, ORDER *order);
+static ORDER *remove_const(JOIN *join,ORDER *first_order,COND *cond,
+ bool change_list, bool *simple_order);
+static int return_zero_rows(JOIN *join, select_result *res,
+ List<TABLE_LIST> &tables,
+ List<Item> &fields, bool send_row,
+ ulonglong select_options, const char *info,
+ Item *having, List<Item> &all_fields);
+static COND *build_equal_items(JOIN *join, COND *cond,
+ COND_EQUAL *inherited,
+ List<TABLE_LIST> *join_list,
+ bool ignore_on_conds,
+ COND_EQUAL **cond_equal_ref,
+ bool link_equal_fields= FALSE);
+static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab,
+ COND *cond,
+ COND_EQUAL *cond_equal,
+ void *table_join_idx);
+static COND *simplify_joins(JOIN *join, List<TABLE_LIST> *join_list,
+ COND *conds, bool top, bool in_sj);
+static bool check_interleaving_with_nj(JOIN_TAB *next);
+static void restore_prev_nj_state(JOIN_TAB *last);
+static uint reset_nj_counters(JOIN *join, List<TABLE_LIST> *join_list);
+static uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list,
+ uint first_unused);
+
+static COND *optimize_cond(JOIN *join, COND *conds,
+ List<TABLE_LIST> *join_list,
+ bool ignore_on_conds,
+ Item::cond_result *cond_value,
+ COND_EQUAL **cond_equal,
+ int flags= 0);
+bool const_expression_in_where(COND *conds,Item *item, Item **comp_item);
+static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table,
+ Procedure *proc);
+
+static enum_nested_loop_state evaluate_join_record(JOIN *, JOIN_TAB *, int);
+static enum_nested_loop_state
+evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab);
+static enum_nested_loop_state
+end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
+static enum_nested_loop_state
+end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
+static enum_nested_loop_state
+end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
+static enum_nested_loop_state
+end_unique_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
+
+static int test_if_group_changed(List<Cached_item> &list);
+static int join_read_const_table(JOIN_TAB *tab, POSITION *pos);
+static int join_read_system(JOIN_TAB *tab);
+static int join_read_const(JOIN_TAB *tab);
+static int join_read_key(JOIN_TAB *tab);
+static void join_read_key_unlock_row(st_join_table *tab);
+static int join_read_always_key(JOIN_TAB *tab);
+static int join_read_last_key(JOIN_TAB *tab);
+static int join_no_more_records(READ_RECORD *info);
+static int join_read_next(READ_RECORD *info);
+static int join_init_quick_read_record(JOIN_TAB *tab);
+static int test_if_quick_select(JOIN_TAB *tab);
+static bool test_if_use_dynamic_range_scan(JOIN_TAB *join_tab);
+static int join_read_first(JOIN_TAB *tab);
+static int join_read_next(READ_RECORD *info);
+static int join_read_next_same(READ_RECORD *info);
+static int join_read_last(JOIN_TAB *tab);
+static int join_read_prev_same(READ_RECORD *info);
+static int join_read_prev(READ_RECORD *info);
+static int join_ft_read_first(JOIN_TAB *tab);
+static int join_ft_read_next(READ_RECORD *info);
+int join_read_always_key_or_null(JOIN_TAB *tab);
+int join_read_next_same_or_null(READ_RECORD *info);
+static COND *make_cond_for_table(THD *thd, Item *cond,table_map table,
+ table_map used_table,
+ int join_tab_idx_arg,
+ bool exclude_expensive_cond,
+ bool retain_ref_cond);
+static COND *make_cond_for_table_from_pred(THD *thd, Item *root_cond,
+ Item *cond,
+ table_map tables,
+ table_map used_table,
+ int join_tab_idx_arg,
+ bool exclude_expensive_cond,
+ bool retain_ref_cond);
+
+static Item* part_of_refkey(TABLE *form,Field *field);
+uint find_shortest_key(TABLE *table, const key_map *usable_keys);
+static bool test_if_cheaper_ordering(const JOIN_TAB *tab,
+ ORDER *order, TABLE *table,
+ key_map usable_keys, int key,
+ ha_rows select_limit,
+ int *new_key, int *new_key_direction,
+ ha_rows *new_select_limit,
+ uint *new_used_key_parts= NULL,
+ uint *saved_best_key_parts= NULL);
+static bool test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,
+ ha_rows select_limit, bool no_changes,
+ const key_map *map);
+static bool list_contains_unique_index(TABLE *table,
+ bool (*find_func) (Field *, void *), void *data);
+static bool find_field_in_item_list (Field *field, void *data);
+static bool find_field_in_order_list (Field *field, void *data);
+static int create_sort_index(THD *thd, JOIN *join, ORDER *order,
+ ha_rows filesort_limit, ha_rows select_limit,
+ bool is_order_by);
+static int remove_duplicates(JOIN *join,TABLE *entry,List<Item> &fields,
+ Item *having);
+static int remove_dup_with_compare(THD *thd, TABLE *entry, Field **field,
+ Item *having);
+static int remove_dup_with_hash_index(THD *thd,TABLE *table,
+ uint field_count, Field **first_field,
+ ulong key_length,Item *having);
+static bool cmp_buffer_with_ref(THD *thd, TABLE *table, TABLE_REF *tab_ref);
+static bool setup_new_fields(THD *thd, List<Item> &fields,
+ List<Item> &all_fields, ORDER *new_order);
+static ORDER *create_distinct_group(THD *thd, Item **ref_pointer_array,
+ ORDER *order, List<Item> &fields,
+ List<Item> &all_fields,
+ bool *all_order_by_fields_used);
+static bool test_if_subpart(ORDER *a,ORDER *b);
+static TABLE *get_sort_by_table(ORDER *a,ORDER *b,List<TABLE_LIST> &tables,
+ table_map const_tables);
+static void calc_group_buffer(JOIN *join,ORDER *group);
+static bool make_group_fields(JOIN *main_join, JOIN *curr_join);
+static bool alloc_group_fields(JOIN *join,ORDER *group);
+// Create list for using with tempory table
+static bool change_to_use_tmp_fields(THD *thd, Item **ref_pointer_array,
+ List<Item> &new_list1,
+ List<Item> &new_list2,
+ uint elements, List<Item> &items);
+// Create list for using with tempory table
+static bool change_refs_to_tmp_fields(THD *thd, Item **ref_pointer_array,
+ List<Item> &new_list1,
+ List<Item> &new_list2,
+ uint elements, List<Item> &items);
+static void init_tmptable_sum_functions(Item_sum **func);
+static void update_tmptable_sum_func(Item_sum **func,TABLE *tmp_table);
+static void copy_sum_funcs(Item_sum **func_ptr, Item_sum **end);
+static bool add_ref_to_table_cond(THD *thd, JOIN_TAB *join_tab);
+static bool setup_sum_funcs(THD *thd, Item_sum **func_ptr);
+static bool prepare_sum_aggregators(Item_sum **func_ptr, bool need_distinct);
+static bool init_sum_functions(Item_sum **func, Item_sum **end);
+static bool update_sum_func(Item_sum **func);
+static void select_describe(JOIN *join, bool need_tmp_table,bool need_order,
+ bool distinct, const char *message=NullS);
+static void add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab);
+static uint make_join_orderinfo(JOIN *join);
+static bool generate_derived_keys(DYNAMIC_ARRAY *keyuse_array);
+
+Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field,
+ bool *inherited_fl);
+JOIN_TAB *first_depth_first_tab(JOIN* join);
+JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab);
+
+enum enum_exec_or_opt {WALK_OPTIMIZATION_TABS , WALK_EXECUTION_TABS};
+JOIN_TAB *first_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind);
+JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind,
+ JOIN_TAB *tab);
+static double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
+ table_map rem_tables);
+
+#ifndef DBUG_OFF
+
+/*
+ SHOW EXPLAIN testing: wait for, and serve n_calls APC requests.
+*/
+void dbug_serve_apcs(THD *thd, int n_calls)
+{
+ const char *save_proc_info= thd->proc_info;
+
+ /* Busy-wait for n_calls APC requests to arrive and be processed */
+ int n_apcs= thd->apc_target.n_calls_processed + n_calls;
+ while (thd->apc_target.n_calls_processed < n_apcs)
+ {
+ /* This is so that mysqltest knows we're ready to serve requests: */
+ thd_proc_info(thd, "show_explain_trap");
+ my_sleep(30000);
+ thd_proc_info(thd, save_proc_info);
+ if (thd->check_killed())
+ break;
+ }
+}
+
+
+/*
+ Debugging: check if @name=value, comparing as integer
+
+ Intended usage:
+
+ DBUG_EXECUTE_IF("show_explain_probe_2",
+ if (dbug_user_var_equals_int(thd, "select_id", select_id))
+ dbug_serve_apcs(thd, 1);
+ );
+
+*/
+
+bool dbug_user_var_equals_int(THD *thd, const char *name, int value)
+{
+ user_var_entry *var;
+ LEX_STRING varname= {(char*)name, strlen(name)};
+ if ((var= get_variable(&thd->user_vars, varname, FALSE)))
+ {
+ bool null_value;
+ longlong var_value= var->val_int(&null_value);
+ if (!null_value && var_value == value)
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
+
+
+/**
+ This handles SELECT with and without UNION.
+*/
+
+bool handle_select(THD *thd, LEX *lex, select_result *result,
+ ulong setup_tables_done_option)
+{
+ bool res;
+ register SELECT_LEX *select_lex = &lex->select_lex;
+ DBUG_ENTER("handle_select");
+ MYSQL_SELECT_START(thd->query());
+
+ if (select_lex->master_unit()->is_union() ||
+ select_lex->master_unit()->fake_select_lex)
+ res= mysql_union(thd, lex, result, &lex->unit, setup_tables_done_option);
+ else
+ {
+ SELECT_LEX_UNIT *unit= &lex->unit;
+ unit->set_limit(unit->global_parameters);
+ /*
+ 'options' of mysql_select will be set in JOIN, as far as JOIN for
+ every PS/SP execution new, we will not need reset this flag if
+ setup_tables_done_option changed for next rexecution
+ */
+ res= mysql_select(thd, &select_lex->ref_pointer_array,
+ select_lex->table_list.first,
+ select_lex->with_wild, select_lex->item_list,
+ select_lex->where,
+ select_lex->order_list.elements +
+ select_lex->group_list.elements,
+ select_lex->order_list.first,
+ select_lex->group_list.first,
+ select_lex->having,
+ lex->proc_list.first,
+ select_lex->options | thd->variables.option_bits |
+ setup_tables_done_option,
+ result, unit, select_lex);
+ }
+ DBUG_PRINT("info",("res: %d report_error: %d", res,
+ thd->is_error()));
+ res|= thd->is_error();
+ if (unlikely(res))
+ result->abort_result_set();
+ if (thd->killed == ABORT_QUERY)
+ {
+ /*
+ If LIMIT ROWS EXAMINED interrupted query execution, issue a warning,
+ continue with normal processing and produce an incomplete query result.
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT,
+ ER(ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT),
+ thd->accessed_rows_and_keys,
+ thd->lex->limit_rows_examined->val_uint());
+ thd->reset_killed();
+ }
+ /* Disable LIMIT ROWS EXAMINED after query execution. */
+ thd->lex->limit_rows_examined_cnt= ULONGLONG_MAX;
+
+ MYSQL_SELECT_DONE((int) res, (ulong) thd->limit_found_rows);
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Fix fields referenced from inner selects.
+
+ @param thd Thread handle
+ @param all_fields List of all fields used in select
+ @param select Current select
+ @param ref_pointer_array Array of references to Items used in current select
+ @param group_list GROUP BY list (is NULL by default)
+
+ @details
+ The function serves 3 purposes
+
+ - adds fields referenced from inner query blocks to the current select list
+
+ - Decides which class to use to reference the items (Item_ref or
+ Item_direct_ref)
+
+ - fixes references (Item_ref objects) to these fields.
+
+ If a field isn't already on the select list and the ref_pointer_array
+ is provided then it is added to the all_fields list and the pointer to
+ it is saved in the ref_pointer_array.
+
+ The class to access the outer field is determined by the following rules:
+
+ -#. If the outer field isn't used under an aggregate function then the
+ Item_ref class should be used.
+
+ -#. If the outer field is used under an aggregate function and this
+ function is, in turn, aggregated in the query block where the outer
+ field was resolved or some query nested therein, then the
+ Item_direct_ref class should be used. Also it should be used if we are
+ grouping by a subquery that references this outer field.
+
+ The resolution is done here and not at the fix_fields() stage as
+ it can be done only after aggregate functions are fixed and pulled up to
+ selects where they are to be aggregated.
+
+ When the class is chosen it substitutes the original field in the
+ Item_outer_ref object.
+
+ After this we proceed with fixing references (Item_outer_ref objects) to
+ this field from inner subqueries.
+
+ @return Status
+ @retval true An error occured.
+ @retval false OK.
+ */
+
+bool
+fix_inner_refs(THD *thd, List<Item> &all_fields, SELECT_LEX *select,
+ Item **ref_pointer_array)
+{
+ Item_outer_ref *ref;
+
+ /*
+ Mark the references from the inner_refs_list that are occurred in
+ the group by expressions. Those references will contain direct
+ references to the referred fields. The markers are set in
+ the found_in_group_by field of the references from the list.
+ */
+ List_iterator_fast <Item_outer_ref> ref_it(select->inner_refs_list);
+ for (ORDER *group= select->join->group_list; group; group= group->next)
+ {
+ (*group->item)->walk(&Item::check_inner_refs_processor,
+ TRUE, (uchar *) &ref_it);
+ }
+
+ while ((ref= ref_it++))
+ {
+ bool direct_ref= false;
+ Item *item= ref->outer_ref;
+ Item **item_ref= ref->ref;
+ Item_ref *new_ref;
+ /*
+ TODO: this field item already might be present in the select list.
+ In this case instead of adding new field item we could use an
+ existing one. The change will lead to less operations for copying fields,
+ smaller temporary tables and less data passed through filesort.
+ */
+ if (ref_pointer_array && !ref->found_in_select_list)
+ {
+ int el= all_fields.elements;
+ ref_pointer_array[el]= item;
+ /* Add the field item to the select list of the current select. */
+ all_fields.push_front(item);
+ /*
+ If it's needed reset each Item_ref item that refers this field with
+ a new reference taken from ref_pointer_array.
+ */
+ item_ref= ref_pointer_array + el;
+ }
+
+ if (ref->in_sum_func)
+ {
+ Item_sum *sum_func;
+ if (ref->in_sum_func->nest_level > select->nest_level)
+ direct_ref= TRUE;
+ else
+ {
+ for (sum_func= ref->in_sum_func; sum_func &&
+ sum_func->aggr_level >= select->nest_level;
+ sum_func= sum_func->in_sum_func)
+ {
+ if (sum_func->aggr_level == select->nest_level)
+ {
+ direct_ref= TRUE;
+ break;
+ }
+ }
+ }
+ }
+ else if (ref->found_in_group_by)
+ direct_ref= TRUE;
+
+ new_ref= direct_ref ?
+ new Item_direct_ref(ref->context, item_ref, ref->table_name,
+ ref->field_name, ref->alias_name_used) :
+ new Item_ref(ref->context, item_ref, ref->table_name,
+ ref->field_name, ref->alias_name_used);
+ if (!new_ref)
+ return TRUE;
+ ref->outer_ref= new_ref;
+ ref->ref= &ref->outer_ref;
+
+ if (!ref->fixed && ref->fix_fields(thd, 0))
+ return TRUE;
+ thd->lex->used_tables|= item->used_tables();
+ }
+ return false;
+}
+
+/**
+ The following clauses are redundant for subqueries:
+
+ DISTINCT
+ GROUP BY if there are no aggregate functions and no HAVING
+ clause
+
+ Because redundant clauses are removed both from JOIN and
+ select_lex, the removal is permanent. Thus, it only makes sense to
+ call this function for normal queries and on first execution of
+ SP/PS
+
+ @param subq_select_lex select_lex that is part of a subquery
+ predicate. This object and the associated
+ join is modified.
+*/
+
+static
+void remove_redundant_subquery_clauses(st_select_lex *subq_select_lex)
+{
+ DBUG_ENTER("remove_redundant_subquery_clauses");
+ Item_subselect *subq_predicate= subq_select_lex->master_unit()->item;
+ /*
+ The removal should happen for IN, ALL, ANY and EXISTS subqueries,
+ which means all but single row subqueries. Example single row
+ subqueries:
+ a) SELECT * FROM t1 WHERE t1.a = (<single row subquery>)
+ b) SELECT a, (<single row subquery) FROM t1
+ */
+ if (subq_predicate->substype() == Item_subselect::SINGLEROW_SUBS)
+ DBUG_VOID_RETURN;
+
+ /* A subquery that is not single row should be one of IN/ALL/ANY/EXISTS. */
+ DBUG_ASSERT (subq_predicate->substype() == Item_subselect::EXISTS_SUBS ||
+ subq_predicate->is_in_predicate());
+
+ if (subq_select_lex->options & SELECT_DISTINCT)
+ {
+ subq_select_lex->join->select_distinct= false;
+ subq_select_lex->options&= ~SELECT_DISTINCT;
+ DBUG_PRINT("info", ("DISTINCT removed"));
+ }
+
+ /*
+ Remove GROUP BY if there are no aggregate functions and no HAVING
+ clause
+ */
+ if (subq_select_lex->group_list.elements &&
+ !subq_select_lex->with_sum_func && !subq_select_lex->join->having)
+ {
+ for (ORDER *ord= subq_select_lex->group_list.first; ord; ord= ord->next)
+ {
+ (*ord->item)->walk(&Item::eliminate_subselect_processor, FALSE, NULL);
+ }
+ subq_select_lex->join->group_list= NULL;
+ subq_select_lex->group_list.empty();
+ DBUG_PRINT("info", ("GROUP BY removed"));
+ }
+
+ /*
+ TODO: This would prevent processing quries with ORDER BY ... LIMIT
+ therefore we disable this optimization for now.
+ Remove GROUP BY if there are no aggregate functions and no HAVING
+ clause
+ if (subq_select_lex->group_list.elements &&
+ !subq_select_lex->with_sum_func && !subq_select_lex->join->having)
+ {
+ subq_select_lex->join->group_list= NULL;
+ subq_select_lex->group_list.empty();
+ }
+ */
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Function to setup clauses without sum functions.
+*/
+inline int setup_without_group(THD *thd, Item **ref_pointer_array,
+ TABLE_LIST *tables,
+ List<TABLE_LIST> &leaves,
+ List<Item> &fields,
+ List<Item> &all_fields,
+ COND **conds,
+ ORDER *order,
+ ORDER *group,
+ bool *hidden_group_fields,
+ uint *reserved)
+{
+ int res;
+ st_select_lex *const select= thd->lex->current_select;
+ nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
+ /*
+ Need to save the value, so we can turn off only any new non_agg_field_used
+ additions coming from the WHERE
+ */
+ const bool saved_non_agg_field_used= select->non_agg_field_used();
+ DBUG_ENTER("setup_without_group");
+
+ thd->lex->allow_sum_func&= ~((nesting_map)1 << select->nest_level);
+ res= setup_conds(thd, tables, leaves, conds);
+ if (thd->lex->current_select->first_cond_optimization)
+ {
+ if (!res && *conds && ! thd->lex->current_select->merged_into)
+ (*reserved)= (*conds)->exists2in_reserved_items();
+ else
+ (*reserved)= 0;
+ }
+
+ /* it's not wrong to have non-aggregated columns in a WHERE */
+ select->set_non_agg_field_used(saved_non_agg_field_used);
+
+ thd->lex->allow_sum_func|= (nesting_map)1 << select->nest_level;
+ res= res || setup_order(thd, ref_pointer_array, tables, fields, all_fields,
+ order);
+ thd->lex->allow_sum_func&= ~((nesting_map)1 << select->nest_level);
+ res= res || setup_group(thd, ref_pointer_array, tables, fields, all_fields,
+ group, hidden_group_fields);
+ thd->lex->allow_sum_func= save_allow_sum_func;
+ DBUG_RETURN(res);
+}
+
+/*****************************************************************************
+ Check fields, find best join, do the select and output fields.
+ mysql_select assumes that all tables are already opened
+*****************************************************************************/
+
+
+/**
+ Prepare of whole select (including sub queries in future).
+
+ @todo
+ Add check of calculation of GROUP functions and fields:
+ SELECT COUNT(*)+table.col1 from table1;
+
+ @retval
+ -1 on error
+ @retval
+ 0 on success
+*/
+int
+JOIN::prepare(Item ***rref_pointer_array,
+ TABLE_LIST *tables_init,
+ uint wild_num, COND *conds_init, uint og_num,
+ ORDER *order_init, bool skip_order_by,
+ ORDER *group_init, Item *having_init,
+ ORDER *proc_param_init, SELECT_LEX *select_lex_arg,
+ SELECT_LEX_UNIT *unit_arg)
+{
+ DBUG_ENTER("JOIN::prepare");
+
+ // to prevent double initialization on EXPLAIN
+ if (optimized)
+ DBUG_RETURN(0);
+
+ conds= conds_init;
+ order= order_init;
+ group_list= group_init;
+ having= having_init;
+ proc_param= proc_param_init;
+ tables_list= tables_init;
+ select_lex= select_lex_arg;
+ select_lex->join= this;
+ join_list= &select_lex->top_join_list;
+ union_part= unit_arg->is_union();
+
+ if (select_lex->handle_derived(thd->lex, DT_PREPARE))
+ DBUG_RETURN(1);
+
+ thd->lex->current_select->is_item_list_lookup= 1;
+ /*
+ If we have already executed SELECT, then it have not sense to prevent
+ its table from update (see unique_table())
+ Affects only materialized derived tables.
+ */
+ /* Check that all tables, fields, conds and order are ok */
+ if (!(select_options & OPTION_SETUP_TABLES_DONE) &&
+ setup_tables_and_check_access(thd, &select_lex->context, join_list,
+ tables_list, select_lex->leaf_tables,
+ FALSE, SELECT_ACL, SELECT_ACL, FALSE))
+ DBUG_RETURN(-1);
+
+ /*
+ Permanently remove redundant parts from the query if
+ 1) This is a subquery
+ 2) This is the first time this query is optimized (since the
+ transformation is permanent
+ 3) Not normalizing a view. Removal should take place when a
+ query involving a view is optimized, not when the view
+ is created
+ */
+ if (select_lex->master_unit()->item && // 1)
+ select_lex->first_cond_optimization && // 2)
+ !thd->lex->is_view_context_analysis()) // 3)
+ {
+ remove_redundant_subquery_clauses(select_lex);
+ }
+
+ /*
+ TRUE if the SELECT list mixes elements with and without grouping,
+ and there is no GROUP BY clause. Mixing non-aggregated fields with
+ aggregate functions in the SELECT list is a MySQL exptenstion that
+ is allowed only if the ONLY_FULL_GROUP_BY sql mode is not set.
+ */
+ mixed_implicit_grouping= false;
+ if ((~thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY) &&
+ select_lex->with_sum_func && !group_list)
+ {
+ List_iterator_fast <Item> select_it(fields_list);
+ Item *select_el; /* Element of the SELECT clause, can be an expression. */
+ bool found_field_elem= false;
+ bool found_sum_func_elem= false;
+
+ while ((select_el= select_it++))
+ {
+ if (select_el->with_sum_func)
+ found_sum_func_elem= true;
+ if (select_el->with_field)
+ found_field_elem= true;
+ if (found_sum_func_elem && found_field_elem)
+ {
+ mixed_implicit_grouping= true;
+ break;
+ }
+ }
+ }
+
+ table_count= select_lex->leaf_tables.elements;
+
+ TABLE_LIST *tbl;
+ List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
+ while ((tbl= li++))
+ {
+ //table_count++; /* Count the number of tables in the join. */
+ /*
+ If the query uses implicit grouping where the select list contains both
+ aggregate functions and non-aggregate fields, any non-aggregated field
+ may produce a NULL value. Set all fields of each table as nullable before
+ semantic analysis to take into account this change of nullability.
+
+ Note: this loop doesn't touch tables inside merged semi-joins, because
+ subquery-to-semijoin conversion has not been done yet. This is intended.
+ */
+ if (mixed_implicit_grouping && tbl->table)
+ tbl->table->maybe_null= 1;
+ }
+
+ if ((wild_num && setup_wild(thd, tables_list, fields_list, &all_fields,
+ wild_num)) ||
+ select_lex->setup_ref_array(thd, og_num) ||
+ setup_fields(thd, (*rref_pointer_array), fields_list, MARK_COLUMNS_READ,
+ &all_fields, 1) ||
+ setup_without_group(thd, (*rref_pointer_array), tables_list,
+ select_lex->leaf_tables, fields_list,
+ all_fields, &conds, order, group_list,
+ &hidden_group_fields, &select_lex->select_n_reserved))
+ DBUG_RETURN(-1); /* purecov: inspected */
+
+ ref_pointer_array= *rref_pointer_array;
+
+ /* Resolve the ORDER BY that was skipped, then remove it. */
+ if (skip_order_by && select_lex != select_lex->master_unit()->global_parameters)
+ {
+ if (setup_order(thd, (*rref_pointer_array), tables_list, fields_list,
+ all_fields, select_lex->order_list.first))
+ DBUG_RETURN(-1);
+ select_lex->order_list.empty();
+ }
+
+ if (having)
+ {
+ nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
+ thd->where="having clause";
+ thd->lex->allow_sum_func|= (nesting_map)1 << select_lex_arg->nest_level;
+ select_lex->having_fix_field= 1;
+ /*
+ Wrap alone field in HAVING clause in case it will be outer field of subquery
+ which need persistent pointer on it, but having could be changed by optimizer
+ */
+ if (having->type() == Item::REF_ITEM &&
+ ((Item_ref *)having)->ref_type() == Item_ref::REF)
+ wrap_ident(thd, &having);
+ bool having_fix_rc= (!having->fixed &&
+ (having->fix_fields(thd, &having) ||
+ having->check_cols(1)));
+ select_lex->having_fix_field= 0;
+
+ if (having_fix_rc || thd->is_error())
+ DBUG_RETURN(-1); /* purecov: inspected */
+ thd->lex->allow_sum_func= save_allow_sum_func;
+ }
+
+ int res= check_and_do_in_subquery_rewrites(this);
+
+ select_lex->fix_prepare_information(thd, &conds, &having);
+
+ if (res)
+ DBUG_RETURN(res);
+
+ if (order)
+ {
+ bool real_order= FALSE;
+ ORDER *ord;
+ for (ord= order; ord; ord= ord->next)
+ {
+ Item *item= *ord->item;
+ /*
+ Disregard sort order if there's only
+ zero length NOT NULL fields (e.g. {VAR}CHAR(0) NOT NULL") or
+ zero length NOT NULL string functions there.
+ Such tuples don't contain any data to sort.
+ */
+ if (!real_order &&
+ /* Not a zero length NOT NULL field */
+ ((item->type() != Item::FIELD_ITEM ||
+ ((Item_field *) item)->field->maybe_null() ||
+ ((Item_field *) item)->field->sort_length()) &&
+ /* AND not a zero length NOT NULL string function. */
+ (item->type() != Item::FUNC_ITEM ||
+ item->maybe_null ||
+ item->result_type() != STRING_RESULT ||
+ item->max_length)))
+ real_order= TRUE;
+
+ if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM)
+ item->split_sum_func(thd, ref_pointer_array, all_fields);
+ }
+ if (!real_order)
+ order= NULL;
+ }
+
+ if (having && having->with_sum_func)
+ having->split_sum_func2(thd, ref_pointer_array, all_fields,
+ &having, TRUE);
+ if (select_lex->inner_sum_func_list)
+ {
+ Item_sum *end=select_lex->inner_sum_func_list;
+ Item_sum *item_sum= end;
+ do
+ {
+ item_sum= item_sum->next;
+ item_sum->split_sum_func2(thd, ref_pointer_array,
+ all_fields, item_sum->ref_by, FALSE);
+ } while (item_sum != end);
+ }
+
+ if (select_lex->inner_refs_list.elements &&
+ fix_inner_refs(thd, all_fields, select_lex, ref_pointer_array))
+ DBUG_RETURN(-1);
+
+ if (group_list)
+ {
+ /*
+ Because HEAP tables can't index BIT fields we need to use an
+ additional hidden field for grouping because later it will be
+ converted to a LONG field. Original field will remain of the
+ BIT type and will be returned to a client.
+ */
+ for (ORDER *ord= group_list; ord; ord= ord->next)
+ {
+ if ((*ord->item)->type() == Item::FIELD_ITEM &&
+ (*ord->item)->field_type() == MYSQL_TYPE_BIT)
+ {
+ Item_field *field= new Item_field(thd, *(Item_field**)ord->item);
+ int el= all_fields.elements;
+ ref_pointer_array[el]= field;
+ all_fields.push_front(field);
+ ord->item= ref_pointer_array + el;
+ }
+ }
+ }
+
+ /*
+ Check if there are references to un-aggregated columns when computing
+ aggregate functions with implicit grouping (there is no GROUP BY).
+ */
+ if (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY && !group_list &&
+ !(select_lex->master_unit()->item &&
+ select_lex->master_unit()->item->is_in_predicate() &&
+ ((Item_in_subselect*)select_lex->master_unit()->item)->
+ test_set_strategy(SUBS_MAXMIN_INJECTED)) &&
+ select_lex->non_agg_field_used() &&
+ select_lex->agg_func_used())
+ {
+ my_message(ER_MIX_OF_GROUP_FUNC_AND_FIELDS,
+ ER(ER_MIX_OF_GROUP_FUNC_AND_FIELDS), MYF(0));
+ DBUG_RETURN(-1);
+ }
+ {
+ /* Caclulate the number of groups */
+ send_group_parts= 0;
+ for (ORDER *group_tmp= group_list ; group_tmp ; group_tmp= group_tmp->next)
+ send_group_parts++;
+ }
+
+ procedure= setup_procedure(thd, proc_param, result, fields_list, &error);
+ if (error)
+ goto err; /* purecov: inspected */
+ if (procedure)
+ {
+ if (setup_new_fields(thd, fields_list, all_fields,
+ procedure->param_fields))
+ goto err; /* purecov: inspected */
+ if (procedure->group)
+ {
+ if (!test_if_subpart(procedure->group,group_list))
+ { /* purecov: inspected */
+ my_message(ER_DIFF_GROUPS_PROC, ER(ER_DIFF_GROUPS_PROC),
+ MYF(0)); /* purecov: inspected */
+ goto err; /* purecov: inspected */
+ }
+ }
+ if (order && (procedure->flags & PROC_NO_SORT))
+ { /* purecov: inspected */
+ my_message(ER_ORDER_WITH_PROC, ER(ER_ORDER_WITH_PROC),
+ MYF(0)); /* purecov: inspected */
+ goto err; /* purecov: inspected */
+ }
+ if (thd->lex->derived_tables)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "PROCEDURE",
+ thd->lex->derived_tables & DERIVED_VIEW ?
+ "view" : "subquery");
+ goto err;
+ }
+ if (thd->lex->sql_command != SQLCOM_SELECT)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "PROCEDURE", "non-SELECT");
+ goto err;
+ }
+ }
+
+ if (!procedure && result && result->prepare(fields_list, unit_arg))
+ goto err; /* purecov: inspected */
+
+ unit= unit_arg;
+ if (prepare_stage2())
+ goto err;
+
+ DBUG_RETURN(0); // All OK
+
+err:
+ delete procedure; /* purecov: inspected */
+ procedure= 0;
+ DBUG_RETURN(-1); /* purecov: inspected */
+}
+
+
+/**
+ Second phase of prepare where we collect some statistic.
+
+ @details
+ We made this part separate to be able recalculate some statistic after
+ transforming subquery on optimization phase.
+*/
+
+bool JOIN::prepare_stage2()
+{
+ bool res= TRUE;
+ DBUG_ENTER("JOIN::prepare_stage2");
+
+ /* Init join struct */
+ count_field_types(select_lex, &tmp_table_param, all_fields, 0);
+ ref_pointer_array_size= all_fields.elements*sizeof(Item*);
+ this->group= group_list != 0;
+
+ if (tmp_table_param.sum_func_count && !group_list)
+ implicit_grouping= TRUE;
+
+#ifdef RESTRICTED_GROUP
+ if (implicit_grouping)
+ {
+ my_message(ER_WRONG_SUM_SELECT,ER(ER_WRONG_SUM_SELECT),MYF(0));
+ goto err;
+ }
+#endif
+ if (select_lex->olap == ROLLUP_TYPE && rollup_init())
+ goto err;
+ if (alloc_func_list())
+ goto err;
+
+ res= FALSE;
+err:
+ DBUG_RETURN(res); /* purecov: inspected */
+}
+
+int JOIN::optimize()
+{
+ bool was_optimized= optimized;
+ int res= optimize_inner();
+ /*
+ If we're inside a non-correlated subquery, this function may be
+ called for the second time after the subquery has been executed
+ and deleted. The second call will not produce a valid query plan, it will
+ short-circuit because optimized==TRUE.
+
+ "was_optimized != optimized" is here to handle this case:
+ - first optimization starts, gets an error (from a const. cheap
+ subquery), returns 1
+ - another JOIN::optimize() call made, and now join->optimize() will
+ return 0, even though we never had a query plan.
+ */
+ if (was_optimized != optimized && !res && have_query_plan != QEP_DELETED)
+ {
+ create_explain_query_if_not_exists(thd->lex, thd->mem_root);
+ have_query_plan= QEP_AVAILABLE;
+ save_explain_data(thd->lex->explain, false /* can overwrite */,
+ need_tmp,
+ !skip_sort_order && !no_order && (order || group_list),
+ select_distinct);
+ }
+ return res;
+}
+
+
+/**
+ global select optimisation.
+
+ @note
+ error code saved in field 'error'
+
+ @retval
+ 0 success
+ @retval
+ 1 error
+*/
+
+int
+JOIN::optimize_inner()
+{
+ ulonglong select_opts_for_readinfo;
+ uint no_jbuf_after;
+ DBUG_ENTER("JOIN::optimize");
+
+ do_send_rows = (unit->select_limit_cnt) ? 1 : 0;
+ // to prevent double initialization on EXPLAIN
+ if (optimized)
+ DBUG_RETURN(0);
+ optimized= 1;
+ DEBUG_SYNC(thd, "before_join_optimize");
+
+ THD_STAGE_INFO(thd, stage_optimizing);
+
+ set_allowed_join_cache_types();
+ need_distinct= TRUE;
+
+ /* Run optimize phase for all derived tables/views used in this SELECT. */
+ if (select_lex->handle_derived(thd->lex, DT_OPTIMIZE))
+ DBUG_RETURN(1);
+
+ if (select_lex->first_cond_optimization)
+ {
+ //Do it only for the first execution
+ /* Merge all mergeable derived tables/views in this SELECT. */
+ if (select_lex->handle_derived(thd->lex, DT_MERGE))
+ DBUG_RETURN(TRUE);
+ table_count= select_lex->leaf_tables.elements;
+ }
+ // Update used tables after all handling derived table procedures
+ select_lex->update_used_tables();
+
+ /*
+ In fact we transform underlying subqueries after their 'prepare' phase and
+ before 'optimize' from upper query 'optimize' to allow semijoin
+ conversion happened (which done in the same way.
+ */
+ if(select_lex->first_cond_optimization &&
+ conds && conds->walk(&Item::exists2in_processor, 0, (uchar *)thd))
+ DBUG_RETURN(1);
+ /*
+TODO: make view to decide if it is possible to write to WHERE directly or make Semi-Joins able to process ON condition if it is possible
+ for (TABLE_LIST *tbl= tables_list; tbl; tbl= tbl->next_local)
+ {
+ if (tbl->on_expr &&
+ tbl->on_expr->walk(&Item::exists2in_processor, 0, (uchar *)thd))
+ DBUG_RETURN(1);
+ }
+ */
+
+ if (transform_max_min_subquery())
+ DBUG_RETURN(1); /* purecov: inspected */
+
+ if (select_lex->first_cond_optimization)
+ {
+ /* dump_TABLE_LIST_graph(select_lex, select_lex->leaf_tables); */
+ if (convert_join_subqueries_to_semijoins(this))
+ DBUG_RETURN(1); /* purecov: inspected */
+ /* dump_TABLE_LIST_graph(select_lex, select_lex->leaf_tables); */
+ select_lex->update_used_tables();
+
+ }
+
+ eval_select_list_used_tables();
+
+ if (optimize_constant_subqueries())
+ DBUG_RETURN(1);
+
+ table_count= select_lex->leaf_tables.elements;
+
+ if (setup_ftfuncs(select_lex)) /* should be after having->fix_fields */
+ DBUG_RETURN(-1);
+
+ row_limit= ((select_distinct || order || group_list) ? HA_POS_ERROR :
+ unit->select_limit_cnt);
+ /* select_limit is used to decide if we are likely to scan the whole table */
+ select_limit= unit->select_limit_cnt;
+ if (having || (select_options & OPTION_FOUND_ROWS))
+ select_limit= HA_POS_ERROR;
+#ifdef HAVE_REF_TO_FIELDS // Not done yet
+ /* Add HAVING to WHERE if possible */
+ if (having && !group_list && !sum_func_count)
+ {
+ if (!conds)
+ {
+ conds= having;
+ having= 0;
+ }
+ else if ((conds=new Item_cond_and(conds,having)))
+ {
+ /*
+ Item_cond_and can't be fixed after creation, so we do not check
+ conds->fixed
+ */
+ conds->fix_fields(thd, &conds);
+ conds->change_ref_to_fields(thd, tables_list);
+ conds->top_level_item();
+ having= 0;
+ }
+ }
+#endif
+
+ SELECT_LEX *sel= select_lex;
+ if (sel->first_cond_optimization)
+ {
+ /*
+ The following code will allocate the new items in a permanent
+ MEMROOT for prepared statements and stored procedures.
+ */
+
+ Query_arena *arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ sel->first_cond_optimization= 0;
+
+ /* Convert all outer joins to inner joins if possible */
+ conds= simplify_joins(this, join_list, conds, TRUE, FALSE);
+ if (select_lex->save_leaf_tables(thd))
+ DBUG_RETURN(1);
+ build_bitmap_for_nested_joins(join_list, 0);
+
+ sel->prep_where= conds ? conds->copy_andor_structure(thd) : 0;
+
+ sel->where= conds;
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ }
+
+ if (setup_jtbm_semi_joins(this, join_list, &conds))
+ DBUG_RETURN(1);
+
+ conds= optimize_cond(this, conds, join_list, FALSE,
+ &cond_value, &cond_equal, OPT_LINK_EQUAL_FIELDS);
+
+ if (thd->is_error())
+ {
+ error= 1;
+ DBUG_PRINT("error",("Error from optimize_cond"));
+ DBUG_RETURN(1);
+ }
+
+ {
+ having= optimize_cond(this, having, join_list, TRUE,
+ &having_value, &having_equal);
+
+ if (thd->is_error())
+ {
+ error= 1;
+ DBUG_PRINT("error",("Error from optimize_cond"));
+ DBUG_RETURN(1);
+ }
+ if (select_lex->where)
+ {
+ select_lex->cond_value= cond_value;
+ if (sel->where != conds && cond_value == Item::COND_OK)
+ thd->change_item_tree(&sel->where, conds);
+ }
+ if (select_lex->having)
+ {
+ select_lex->having_value= having_value;
+ if (sel->having != having && having_value == Item::COND_OK)
+ thd->change_item_tree(&sel->having, having);
+ }
+ if (cond_value == Item::COND_FALSE || having_value == Item::COND_FALSE ||
+ (!unit->select_limit_cnt && !(select_options & OPTION_FOUND_ROWS)))
+ { /* Impossible cond */
+ DBUG_PRINT("info", (having_value == Item::COND_FALSE ?
+ "Impossible HAVING" : "Impossible WHERE"));
+ zero_result_cause= having_value == Item::COND_FALSE ?
+ "Impossible HAVING" : "Impossible WHERE";
+ table_count= top_join_tab_count= 0;
+ error= 0;
+ goto setup_subq_exit;
+ }
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ {
+ TABLE_LIST *tbl;
+ List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
+ while ((tbl= li++))
+ {
+ /*
+ If tbl->embedding!=NULL that means that this table is in the inner
+ part of the nested outer join, and we can't do partition pruning
+ (TODO: check if this limitation can be lifted)
+ */
+ if (!tbl->embedding ||
+ (tbl->embedding && tbl->embedding->sj_on_expr))
+ {
+ Item *prune_cond= tbl->on_expr? tbl->on_expr : conds;
+ tbl->table->all_partitions_pruned_away= prune_partitions(thd,
+ tbl->table,
+ prune_cond);
+ }
+ }
+ }
+#endif
+
+ /*
+ Try to optimize count(*), MY_MIN() and MY_MAX() to const fields if
+ there is implicit grouping (aggregate functions but no
+ group_list). In this case, the result set shall only contain one
+ row.
+ */
+ if (tables_list && implicit_grouping)
+ {
+ int res;
+ /*
+ opt_sum_query() returns HA_ERR_KEY_NOT_FOUND if no rows match
+ to the WHERE conditions,
+ or 1 if all items were resolved (optimized away),
+ or 0, or an error number HA_ERR_...
+
+ If all items were resolved by opt_sum_query, there is no need to
+ open any tables.
+ */
+ if ((res=opt_sum_query(thd, select_lex->leaf_tables, all_fields, conds)))
+ {
+ DBUG_ASSERT(res >= 0);
+ if (res == HA_ERR_KEY_NOT_FOUND)
+ {
+ DBUG_PRINT("info",("No matching min/max row"));
+ zero_result_cause= "No matching min/max row";
+ table_count= top_join_tab_count= 0;
+ error=0;
+ goto setup_subq_exit;
+ }
+ if (res > 1)
+ {
+ error= res;
+ DBUG_PRINT("error",("Error from opt_sum_query"));
+ DBUG_RETURN(1);
+ }
+
+ DBUG_PRINT("info",("Select tables optimized away"));
+ zero_result_cause= "Select tables optimized away";
+ tables_list= 0; // All tables resolved
+ const_tables= top_join_tab_count= table_count;
+ /*
+ Extract all table-independent conditions and replace the WHERE
+ clause with them. All other conditions were computed by opt_sum_query
+ and the MIN/MAX/COUNT function(s) have been replaced by constants,
+ so there is no need to compute the whole WHERE clause again.
+ Notice that make_cond_for_table() will always succeed to remove all
+ computed conditions, because opt_sum_query() is applicable only to
+ conjunctions.
+ Preserve conditions for EXPLAIN.
+ */
+ if (conds && !(thd->lex->describe & DESCRIBE_EXTENDED))
+ {
+ COND *table_independent_conds=
+ make_cond_for_table(thd, conds, PSEUDO_TABLE_BITS, 0, -1,
+ FALSE, FALSE);
+ DBUG_EXECUTE("where",
+ print_where(table_independent_conds,
+ "where after opt_sum_query()",
+ QT_ORDINARY););
+ conds= table_independent_conds;
+ }
+ }
+ }
+ if (!tables_list)
+ {
+ DBUG_PRINT("info",("No tables"));
+ error= 0;
+ goto setup_subq_exit;
+ }
+ error= -1; // Error is sent to client
+ /* get_sort_by_table() call used to be here: */
+ MEM_UNDEFINED(&sort_by_table, sizeof(sort_by_table));
+
+ /* Calculate how to do the join */
+ THD_STAGE_INFO(thd, stage_statistics);
+ if (make_join_statistics(this, select_lex->leaf_tables, conds, &keyuse) ||
+ thd->is_fatal_error)
+ {
+ DBUG_PRINT("error",("Error: make_join_statistics() failed"));
+ DBUG_RETURN(1);
+ }
+
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_DERIVED_WITH_KEYS))
+ drop_unused_derived_keys();
+
+ if (rollup.state != ROLLUP::STATE_NONE)
+ {
+ if (rollup_process_const_fields())
+ {
+ DBUG_PRINT("error", ("Error: rollup_process_fields() failed"));
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ /* Remove distinct if only const tables */
+ select_distinct= select_distinct && (const_tables != table_count);
+ }
+
+ THD_STAGE_INFO(thd, stage_preparing);
+ if (result->initialize_tables(this))
+ {
+ DBUG_PRINT("error",("Error: initialize_tables() failed"));
+ DBUG_RETURN(1); // error == -1
+ }
+ if (const_table_map != found_const_table_map &&
+ !(select_options & SELECT_DESCRIBE))
+ {
+ // There is at least one empty const table
+ zero_result_cause= "no matching row in const table";
+ DBUG_PRINT("error",("Error: %s", zero_result_cause));
+ error= 0;
+ goto setup_subq_exit;
+ }
+ if (!(thd->variables.option_bits & OPTION_BIG_SELECTS) &&
+ best_read > (double) thd->variables.max_join_size &&
+ !(select_options & SELECT_DESCRIBE))
+ { /* purecov: inspected */
+ my_message(ER_TOO_BIG_SELECT, ER(ER_TOO_BIG_SELECT), MYF(0));
+ error= -1;
+ DBUG_RETURN(1);
+ }
+ if (const_tables && !thd->locked_tables_mode &&
+ !(select_options & SELECT_NO_UNLOCK))
+ mysql_unlock_some_tables(thd, table, const_tables);
+ if (!conds && outer_join)
+ {
+ /* Handle the case where we have an OUTER JOIN without a WHERE */
+ conds=new Item_int((longlong) 1,1); // Always true
+ }
+
+ if (impossible_where)
+ {
+ zero_result_cause=
+ "Impossible WHERE noticed after reading const tables";
+ select_lex->mark_const_derived(zero_result_cause);
+ goto setup_subq_exit;
+ }
+
+ select= make_select(*table, const_table_map,
+ const_table_map, conds, 1, &error);
+ if (error)
+ { /* purecov: inspected */
+ error= -1; /* purecov: inspected */
+ DBUG_PRINT("error",("Error: make_select() failed"));
+ DBUG_RETURN(1);
+ }
+
+ reset_nj_counters(this, join_list);
+ if (make_outerjoin_info(this))
+ {
+ DBUG_RETURN(1);
+ }
+
+ /*
+ Among the equal fields belonging to the same multiple equality
+ choose the one that is to be retrieved first and substitute
+ all references to these in where condition for a reference for
+ the selected field.
+ */
+ if (conds)
+ {
+ conds= substitute_for_best_equal_field(NO_PARTICULAR_TAB, conds,
+ cond_equal, map2table);
+ if (thd->is_error())
+ {
+ error= 1;
+ DBUG_PRINT("error",("Error from substitute_for_best_equal"));
+ DBUG_RETURN(1);
+ }
+ conds->update_used_tables();
+ DBUG_EXECUTE("where",
+ print_where(conds,
+ "after substitute_best_equal",
+ QT_ORDINARY););
+ }
+
+ /*
+ Perform the optimization on fields evaluation mentioned above
+ for all on expressions.
+ */
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab;
+ tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS))
+ {
+ if (*tab->on_expr_ref)
+ {
+ *tab->on_expr_ref= substitute_for_best_equal_field(NO_PARTICULAR_TAB,
+ *tab->on_expr_ref,
+ tab->cond_equal,
+ map2table);
+ if (thd->is_error())
+ {
+ error= 1;
+ DBUG_PRINT("error",("Error from substitute_for_best_equal"));
+ DBUG_RETURN(1);
+ }
+ (*tab->on_expr_ref)->update_used_tables();
+ }
+ }
+
+ /*
+ Perform the optimization on fields evaliation mentioned above
+ for all used ref items.
+ */
+ for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab;
+ tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS))
+ {
+ uint key_copy_index=0;
+ for (uint i=0; i < tab->ref.key_parts; i++)
+ {
+ Item **ref_item_ptr= tab->ref.items+i;
+ Item *ref_item= *ref_item_ptr;
+ if (!ref_item->used_tables() && !(select_options & SELECT_DESCRIBE))
+ continue;
+ COND_EQUAL *equals= cond_equal;
+ JOIN_TAB *first_inner= tab->first_inner;
+ while (equals)
+ {
+ ref_item= substitute_for_best_equal_field(tab, ref_item,
+ equals, map2table);
+ if (first_inner)
+ {
+ equals= first_inner->cond_equal;
+ first_inner= first_inner->first_upper;
+ }
+ else
+ equals= 0;
+ }
+ ref_item->update_used_tables();
+ if (*ref_item_ptr != ref_item)
+ {
+ *ref_item_ptr= ref_item;
+ Item *item= ref_item->real_item();
+ store_key *key_copy= tab->ref.key_copy[key_copy_index];
+ if (key_copy->type() == store_key::FIELD_STORE_KEY)
+ {
+ if (item->basic_const_item())
+ {
+ /* It is constant propagated here */
+ tab->ref.key_copy[key_copy_index]=
+ new store_key_const_item(*tab->ref.key_copy[key_copy_index],
+ item);
+ }
+ else if (item->const_item())
+ {
+ tab->ref.key_copy[key_copy_index]=
+ new store_key_item(*tab->ref.key_copy[key_copy_index],
+ item, TRUE);
+ }
+ else
+ {
+ store_key_field *field_copy= ((store_key_field *)key_copy);
+ DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
+ field_copy->change_source_field((Item_field *) item);
+ }
+ }
+ }
+ key_copy_index++;
+ }
+ }
+
+ if (conds && const_table_map != found_const_table_map &&
+ (select_options & SELECT_DESCRIBE))
+ {
+ conds=new Item_int((longlong) 0,1); // Always false
+ }
+
+ /* Cache constant expressions in WHERE, HAVING, ON clauses. */
+ cache_const_exprs();
+
+ if (make_join_select(this, select, conds))
+ {
+ zero_result_cause=
+ "Impossible WHERE noticed after reading const tables";
+ select_lex->mark_const_derived(zero_result_cause);
+ goto setup_subq_exit;
+ }
+
+ error= -1; /* if goto err */
+
+ /* Optimize distinct away if possible */
+ {
+ ORDER *org_order= order;
+ order=remove_const(this, order,conds,1, &simple_order);
+ if (thd->is_error())
+ {
+ error= 1;
+ DBUG_PRINT("error",("Error from remove_const"));
+ DBUG_RETURN(1);
+ }
+
+ /*
+ If we are using ORDER BY NULL or ORDER BY const_expression,
+ return result in any order (even if we are using a GROUP BY)
+ */
+ if (!order && org_order)
+ skip_sort_order= 1;
+ }
+ /*
+ Check if we can optimize away GROUP BY/DISTINCT.
+ We can do that if there are no aggregate functions, the
+ fields in DISTINCT clause (if present) and/or columns in GROUP BY
+ (if present) contain direct references to all key parts of
+ an unique index (in whatever order) and if the key parts of the
+ unique index cannot contain NULLs.
+ Note that the unique keys for DISTINCT and GROUP BY should not
+ be the same (as long as they are unique).
+
+ The FROM clause must contain a single non-constant table.
+ */
+ if (table_count - const_tables == 1 && (group_list || select_distinct) &&
+ !tmp_table_param.sum_func_count &&
+ (!join_tab[const_tables].select ||
+ !join_tab[const_tables].select->quick ||
+ join_tab[const_tables].select->quick->get_type() !=
+ QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX))
+ {
+ if (group_list && rollup.state == ROLLUP::STATE_NONE &&
+ list_contains_unique_index(join_tab[const_tables].table,
+ find_field_in_order_list,
+ (void *) group_list))
+ {
+ /*
+ We have found that grouping can be removed since groups correspond to
+ only one row anyway, but we still have to guarantee correct result
+ order. The line below effectively rewrites the query from GROUP BY
+ <fields> to ORDER BY <fields>. There are three exceptions:
+ - if skip_sort_order is set (see above), then we can simply skip
+ GROUP BY;
+ - if we are in a subquery, we don't have to maintain order
+ - we can only rewrite ORDER BY if the ORDER BY fields are 'compatible'
+ with the GROUP BY ones, i.e. either one is a prefix of another.
+ We only check if the ORDER BY is a prefix of GROUP BY. In this case
+ test_if_subpart() copies the ASC/DESC attributes from the original
+ ORDER BY fields.
+ If GROUP BY is a prefix of ORDER BY, then it is safe to leave
+ 'order' as is.
+ */
+ if (!order || test_if_subpart(group_list, order))
+ {
+ if (skip_sort_order ||
+ select_lex->master_unit()->item) // This is a subquery
+ order= NULL;
+ else
+ order= group_list;
+ }
+ /*
+ If we have an IGNORE INDEX FOR GROUP BY(fields) clause, this must be
+ rewritten to IGNORE INDEX FOR ORDER BY(fields).
+ */
+ join_tab->table->keys_in_use_for_order_by=
+ join_tab->table->keys_in_use_for_group_by;
+ group_list= 0;
+ group= 0;
+ }
+ if (select_distinct &&
+ list_contains_unique_index(join_tab[const_tables].table,
+ find_field_in_item_list,
+ (void *) &fields_list))
+ {
+ select_distinct= 0;
+ }
+ }
+ if (group_list || tmp_table_param.sum_func_count)
+ {
+ if (! hidden_group_fields && rollup.state == ROLLUP::STATE_NONE)
+ select_distinct=0;
+ }
+ else if (select_distinct && table_count - const_tables == 1 &&
+ rollup.state == ROLLUP::STATE_NONE)
+ {
+ /*
+ We are only using one table. In this case we change DISTINCT to a
+ GROUP BY query if:
+ - The GROUP BY can be done through indexes (no sort) and the ORDER
+ BY only uses selected fields.
+ (In this case we can later optimize away GROUP BY and ORDER BY)
+ - We are scanning the whole table without LIMIT
+ This can happen if:
+ - We are using CALC_FOUND_ROWS
+ - We are using an ORDER BY that can't be optimized away.
+
+ We don't want to use this optimization when we are using LIMIT
+ because in this case we can just create a temporary table that
+ holds LIMIT rows and stop when this table is full.
+ */
+ JOIN_TAB *tab= &join_tab[const_tables];
+ bool all_order_fields_used;
+ if (order)
+ {
+ skip_sort_order= test_if_skip_sort_order(tab, order, select_limit, 1,
+ &tab->table->keys_in_use_for_order_by);
+ }
+ if ((group_list=create_distinct_group(thd, select_lex->ref_pointer_array,
+ order, fields_list, all_fields,
+ &all_order_fields_used)))
+ {
+ bool skip_group= (skip_sort_order &&
+ test_if_skip_sort_order(tab, group_list, select_limit, 1,
+ &tab->table->keys_in_use_for_group_by) != 0);
+ count_field_types(select_lex, &tmp_table_param, all_fields, 0);
+ if ((skip_group && all_order_fields_used) ||
+ select_limit == HA_POS_ERROR ||
+ (order && !skip_sort_order))
+ {
+ /* Change DISTINCT to GROUP BY */
+ select_distinct= 0;
+ no_order= !order;
+ if (all_order_fields_used)
+ {
+ if (order && skip_sort_order)
+ {
+ /*
+ Force MySQL to read the table in sorted order to get result in
+ ORDER BY order.
+ */
+ tmp_table_param.quick_group=0;
+ }
+ order=0;
+ }
+ group=1; // For end_write_group
+ }
+ else
+ group_list= 0;
+ }
+ else if (thd->is_fatal_error) // End of memory
+ DBUG_RETURN(1);
+ }
+ simple_group= 0;
+ {
+ ORDER *old_group_list;
+ group_list= remove_const(this, (old_group_list= group_list), conds,
+ rollup.state == ROLLUP::STATE_NONE,
+ &simple_group);
+ if (thd->is_error())
+ {
+ error= 1;
+ DBUG_PRINT("error",("Error from remove_const"));
+ DBUG_RETURN(1);
+ }
+ if (old_group_list && !group_list)
+ {
+ DBUG_ASSERT(group);
+ select_distinct= 0;
+ }
+ }
+ if (!group_list && group)
+ {
+ order=0; // The output has only one row
+ simple_order=1;
+ select_distinct= 0; // No need in distinct for 1 row
+ group_optimized_away= 1;
+ }
+
+ calc_group_buffer(this, group_list);
+ send_group_parts= tmp_table_param.group_parts; /* Save org parts */
+ if (procedure && procedure->group)
+ {
+ group_list= procedure->group= remove_const(this, procedure->group, conds,
+ 1, &simple_group);
+ if (thd->is_error())
+ {
+ error= 1;
+ DBUG_PRINT("error",("Error from remove_const"));
+ DBUG_RETURN(1);
+ }
+ calc_group_buffer(this, group_list);
+ }
+
+ if (test_if_subpart(group_list, order) ||
+ (!group_list && tmp_table_param.sum_func_count))
+ {
+ order=0;
+ if (is_indexed_agg_distinct(this, NULL))
+ sort_and_group= 0;
+ }
+
+ // Can't use sort on head table if using join buffering
+ if (full_join || hash_join)
+ {
+ TABLE *stable= (sort_by_table == (TABLE *) 1 ?
+ join_tab[const_tables].table : sort_by_table);
+ /*
+ FORCE INDEX FOR ORDER BY can be used to prevent join buffering when
+ sorting on the first table.
+ */
+ if (!stable || !stable->force_index_order)
+ {
+ if (group_list)
+ simple_group= 0;
+ if (order)
+ simple_order= 0;
+ }
+ }
+
+ need_tmp= test_if_need_tmp_table();
+
+ /*
+ If the hint FORCE INDEX FOR ORDER BY/GROUP BY is used for the table
+ whose columns are required to be returned in a sorted order, then
+ the proper value for no_jbuf_after should be yielded by a call to
+ the make_join_orderinfo function.
+ Yet the current implementation of FORCE INDEX hints does not
+ allow us to do it in a clean manner.
+ */
+ no_jbuf_after= 1 ? table_count : make_join_orderinfo(this);
+
+ // Don't use join buffering when we use MATCH
+ select_opts_for_readinfo=
+ (select_options & (SELECT_DESCRIBE | SELECT_NO_JOIN_CACHE)) |
+ (select_lex->ftfunc_list->elements ? SELECT_NO_JOIN_CACHE : 0);
+
+ if (make_join_readinfo(this, select_opts_for_readinfo, no_jbuf_after))
+ DBUG_RETURN(1);
+
+ /* Perform FULLTEXT search before all regular searches */
+ if (!(select_options & SELECT_DESCRIBE))
+ init_ftfuncs(thd, select_lex, MY_TEST(order));
+
+ if (optimize_unflattened_subqueries())
+ DBUG_RETURN(1);
+
+ int res;
+ if ((res= rewrite_to_index_subquery_engine(this)) != -1)
+ DBUG_RETURN(res);
+ if (setup_subquery_caches())
+ DBUG_RETURN(-1);
+
+ /*
+ Need to tell handlers that to play it safe, it should fetch all
+ columns of the primary key of the tables: this is because MySQL may
+ build row pointers for the rows, and for all columns of the primary key
+ the read set has not necessarily been set by the server code.
+ */
+ if (need_tmp || select_distinct || group_list || order)
+ {
+ for (uint i= 0; i < table_count; i++)
+ {
+ if (!(table[i]->map & const_table_map))
+ table[i]->prepare_for_position();
+ }
+ }
+
+ DBUG_EXECUTE("info",TEST_join(this););
+
+ if (const_tables != table_count)
+ {
+ /*
+ Because filesort always does a full table scan or a quick range scan
+ we must add the removed reference to the select for the table.
+ We only need to do this when we have a simple_order or simple_group
+ as in other cases the join is done before the sort.
+ */
+ if ((order || group_list) &&
+ join_tab[const_tables].type != JT_ALL &&
+ join_tab[const_tables].type != JT_FT &&
+ join_tab[const_tables].type != JT_REF_OR_NULL &&
+ ((order && simple_order) || (group_list && simple_group)))
+ {
+ if (add_ref_to_table_cond(thd,&join_tab[const_tables])) {
+ DBUG_RETURN(1);
+ }
+ }
+ /*
+ Calculate a possible 'limit' of table rows for 'GROUP BY': 'need_tmp'
+ implies that there will be more postprocessing so the specified
+ 'limit' should not be enforced yet in the call to
+ 'test_if_skip_sort_order'.
+ */
+ const ha_rows limit = need_tmp ? HA_POS_ERROR : unit->select_limit_cnt;
+
+ if (!(select_options & SELECT_BIG_RESULT) &&
+ ((group_list &&
+ (!simple_group ||
+ !test_if_skip_sort_order(&join_tab[const_tables], group_list,
+ limit, 0,
+ &join_tab[const_tables].table->
+ keys_in_use_for_group_by))) ||
+ select_distinct) &&
+ tmp_table_param.quick_group && !procedure)
+ {
+ need_tmp=1; simple_order=simple_group=0; // Force tmp table without sort
+ }
+ if (order)
+ {
+ /*
+ Do we need a temporary table due to the ORDER BY not being equal to
+ the GROUP BY? The call to test_if_skip_sort_order above tests for the
+ GROUP BY clause only and hence is not valid in this case. So the
+ estimated number of rows to be read from the first table is not valid.
+ We clear it here so that it doesn't show up in EXPLAIN.
+ */
+ if (need_tmp && (select_options & SELECT_DESCRIBE) != 0)
+ join_tab[const_tables].limit= 0;
+ /*
+ Force using of tmp table if sorting by a SP or UDF function due to
+ their expensive and probably non-deterministic nature.
+ */
+ for (ORDER *tmp_order= order; tmp_order ; tmp_order=tmp_order->next)
+ {
+ Item *item= *tmp_order->item;
+ if (item->is_expensive())
+ {
+ /* Force tmp table without sort */
+ need_tmp=1; simple_order=simple_group=0;
+ break;
+ }
+ }
+ }
+ }
+
+ tmp_having= having;
+ if (select_options & SELECT_DESCRIBE)
+ {
+ error= 0;
+ goto derived_exit;
+ }
+ having= 0;
+
+ /*
+ The loose index scan access method guarantees that all grouping or
+ duplicate row elimination (for distinct) is already performed
+ during data retrieval, and that all MIN/MAX functions are already
+ computed for each group. Thus all MIN/MAX functions should be
+ treated as regular functions, and there is no need to perform
+ grouping in the main execution loop.
+ Notice that currently loose index scan is applicable only for
+ single table queries, thus it is sufficient to test only the first
+ join_tab element of the plan for its access method.
+ */
+ if (join_tab->is_using_loose_index_scan())
+ {
+ tmp_table_param.precomputed_group_by= TRUE;
+ if (join_tab->is_using_agg_loose_index_scan())
+ {
+ need_distinct= FALSE;
+ tmp_table_param.precomputed_group_by= FALSE;
+ }
+ }
+
+ error= 0;
+
+ DBUG_RETURN(0);
+
+setup_subq_exit:
+ /* Choose an execution strategy for this JOIN. */
+ if (!tables_list || !table_count)
+ choose_tableless_subquery_plan();
+ /*
+ Even with zero matching rows, subqueries in the HAVING clause may
+ need to be evaluated if there are aggregate functions in the query.
+ */
+ if (optimize_unflattened_subqueries())
+ DBUG_RETURN(1);
+ error= 0;
+
+derived_exit:
+ select_lex->mark_const_derived(zero_result_cause);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Create and initialize objects neeed for the execution of a query plan.
+ Evaluate constant expressions not evaluated during optimization.
+*/
+
+int JOIN::init_execution()
+{
+ DBUG_ENTER("JOIN::init_execution");
+
+ DBUG_ASSERT(optimized);
+ DBUG_ASSERT(!(select_options & SELECT_DESCRIBE));
+ initialized= true;
+
+ /*
+ Enable LIMIT ROWS EXAMINED during query execution if:
+ (1) This JOIN is the outermost query (not a subquery or derived table)
+ This ensures that the limit is enabled when actual execution begins, and
+ not if a subquery is evaluated during optimization of the outer query.
+ (2) This JOIN is not the result of a UNION. In this case do not apply the
+ limit in order to produce the partial query result stored in the
+ UNION temp table.
+ */
+ if (!select_lex->outer_select() && // (1)
+ select_lex != select_lex->master_unit()->fake_select_lex) // (2)
+ thd->lex->set_limit_rows_examined();
+
+ /* Create a tmp table if distinct or if the sort is too complicated */
+ if (need_tmp)
+ {
+ DBUG_PRINT("info",("Creating tmp table"));
+ THD_STAGE_INFO(thd, stage_copying_to_tmp_table);
+
+ init_items_ref_array();
+
+ tmp_table_param.hidden_field_count= (all_fields.elements -
+ fields_list.elements);
+ ORDER *tmp_group= ((!simple_group && !procedure &&
+ !(test_flags & TEST_NO_KEY_GROUP)) ? group_list :
+ (ORDER*) 0);
+ /*
+ Pushing LIMIT to the temporary table creation is not applicable
+ when there is ORDER BY or GROUP BY or there is no GROUP BY, but
+ there are aggregate functions, because in all these cases we need
+ all result rows.
+ */
+ ha_rows tmp_rows_limit= ((order == 0 || skip_sort_order) &&
+ !tmp_group &&
+ !thd->lex->current_select->with_sum_func) ?
+ select_limit : HA_POS_ERROR;
+
+ if (!(exec_tmp_table1=
+ create_tmp_table(thd, &tmp_table_param, all_fields,
+ tmp_group, group_list ? 0 : select_distinct,
+ group_list && simple_group,
+ select_options, tmp_rows_limit, "")))
+ DBUG_RETURN(1);
+
+ /*
+ We don't have to store rows in temp table that doesn't match HAVING if:
+ - we are sorting the table and writing complete group rows to the
+ temp table.
+ - We are using DISTINCT without resolving the distinct as a GROUP BY
+ on all columns.
+
+ If having is not handled here, it will be checked before the row
+ is sent to the client.
+ */
+ if (tmp_having &&
+ (sort_and_group || (exec_tmp_table1->distinct && !group_list)))
+ having= tmp_having;
+
+ /* if group or order on first table, sort first */
+ if (group_list && simple_group)
+ {
+ DBUG_PRINT("info",("Sorting for group"));
+ THD_STAGE_INFO(thd, stage_sorting_for_group);
+ if (create_sort_index(thd, this, group_list,
+ HA_POS_ERROR, HA_POS_ERROR, FALSE) ||
+ alloc_group_fields(this, group_list) ||
+ make_sum_func_list(all_fields, fields_list, 1) ||
+ prepare_sum_aggregators(sum_funcs, need_distinct) ||
+ setup_sum_funcs(thd, sum_funcs))
+ {
+ DBUG_RETURN(1);
+ }
+ group_list=0;
+ }
+ else
+ {
+ if (make_sum_func_list(all_fields, fields_list, 0) ||
+ prepare_sum_aggregators(sum_funcs, need_distinct) ||
+ setup_sum_funcs(thd, sum_funcs))
+ {
+ DBUG_RETURN(1);
+ }
+
+ if (!group_list && ! exec_tmp_table1->distinct && order && simple_order)
+ {
+ DBUG_PRINT("info",("Sorting for order"));
+ THD_STAGE_INFO(thd, stage_sorting_for_order);
+ if (create_sort_index(thd, this, order,
+ HA_POS_ERROR, HA_POS_ERROR, TRUE))
+ {
+ DBUG_RETURN(1);
+ }
+ order=0;
+ }
+ }
+
+ /*
+ Optimize distinct when used on some of the tables
+ SELECT DISTINCT t1.a FROM t1,t2 WHERE t1.b=t2.b
+ In this case we can stop scanning t2 when we have found one t1.a
+ */
+
+ if (exec_tmp_table1->distinct)
+ {
+ table_map used_tables= select_list_used_tables;
+ JOIN_TAB *last_join_tab= join_tab + top_join_tab_count - 1;
+ do
+ {
+ if (used_tables & last_join_tab->table->map ||
+ last_join_tab->use_join_cache)
+ break;
+ last_join_tab->shortcut_for_distinct= true;
+ } while (last_join_tab-- != join_tab);
+ /* Optimize "select distinct b from t1 order by key_part_1 limit #" */
+ if (order && skip_sort_order)
+ {
+ /* Should always succeed */
+ if (test_if_skip_sort_order(&join_tab[const_tables],
+ order, unit->select_limit_cnt, 0,
+ &join_tab[const_tables].table->
+ keys_in_use_for_order_by))
+ order=0;
+ }
+ }
+
+ /* If this join belongs to an uncacheable query save the original join */
+ if (select_lex->uncacheable && init_save_join_tab())
+ DBUG_RETURN(-1); /* purecov: inspected */
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Setup expression caches for subqueries that need them
+
+ @details
+ The function wraps correlated subquery expressions that return one value
+ into objects of the class Item_cache_wrapper setting up an expression
+ cache for each of them. The result values of the subqueries are to be
+ cached together with the corresponding sets of the parameters - outer
+ references of the subqueries.
+
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool JOIN::setup_subquery_caches()
+{
+ DBUG_ENTER("JOIN::setup_subquery_caches");
+
+ /*
+ We have to check all this condition together because items created in
+ one of this clauses can be moved to another one by optimizer
+ */
+ if (select_lex->expr_cache_may_be_used[IN_WHERE] ||
+ select_lex->expr_cache_may_be_used[IN_HAVING] ||
+ select_lex->expr_cache_may_be_used[IN_ON] ||
+ select_lex->expr_cache_may_be_used[NO_MATTER])
+ {
+ if (conds)
+ conds= conds->transform(&Item::expr_cache_insert_transformer,
+ (uchar*) thd);
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS))
+ {
+ if (tab->select_cond)
+ tab->select_cond=
+ tab->select_cond->transform(&Item::expr_cache_insert_transformer,
+ (uchar*) thd);
+ if (tab->cache_select && tab->cache_select->cond)
+ tab->cache_select->cond=
+ tab->cache_select->
+ cond->transform(&Item::expr_cache_insert_transformer,
+ (uchar*) thd);
+
+ }
+
+ if (having)
+ having= having->transform(&Item::expr_cache_insert_transformer,
+ (uchar*) thd);
+ if (tmp_having)
+ {
+ DBUG_ASSERT(having == NULL);
+ tmp_having= tmp_having->transform(&Item::expr_cache_insert_transformer,
+ (uchar*) thd);
+ }
+ }
+ if (select_lex->expr_cache_may_be_used[SELECT_LIST] ||
+ select_lex->expr_cache_may_be_used[IN_GROUP_BY] ||
+ select_lex->expr_cache_may_be_used[NO_MATTER])
+ {
+ List_iterator<Item> li(all_fields);
+ Item *item;
+ while ((item= li++))
+ {
+ Item *new_item=
+ item->transform(&Item::expr_cache_insert_transformer, (uchar*) thd);
+ if (new_item != item)
+ {
+ thd->change_item_tree(li.ref(), new_item);
+ }
+ }
+ for (ORDER *group= group_list; group ; group= group->next)
+ {
+ *group->item=
+ (*group->item)->transform(&Item::expr_cache_insert_transformer,
+ (uchar*) thd);
+ }
+ }
+ if (select_lex->expr_cache_may_be_used[NO_MATTER])
+ {
+ for (ORDER *ord= order; ord; ord= ord->next)
+ {
+ *ord->item=
+ (*ord->item)->transform(&Item::expr_cache_insert_transformer,
+ (uchar*) thd);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Restore values in temporary join.
+*/
+void JOIN::restore_tmp()
+{
+ DBUG_PRINT("info", ("restore_tmp this %p tmp_join %p", this, tmp_join));
+ DBUG_ASSERT(tmp_join != this);
+ memcpy(tmp_join, this, (size_t) sizeof(JOIN));
+}
+
+
+/*
+ Shrink join buffers used for preceding tables to reduce the occupied space
+
+ SYNOPSIS
+ shrink_join_buffers()
+ jt table up to which the buffers are to be shrunk
+ curr_space the size of the space used by the buffers for tables 1..jt
+ needed_space the size of the space that has to be used by these buffers
+
+ DESCRIPTION
+ The function makes an attempt to shrink all join buffers used for the
+ tables starting from the first up to jt to reduce the total size of the
+ space occupied by the buffers used for tables 1,...,jt from curr_space
+ to needed_space.
+ The function assumes that the buffer for the table jt has not been
+ allocated yet.
+
+ RETURN
+ FALSE if all buffer have been successfully shrunk
+ TRUE otherwise
+*/
+
+bool JOIN::shrink_join_buffers(JOIN_TAB *jt,
+ ulonglong curr_space,
+ ulonglong needed_space)
+{
+ JOIN_CACHE *cache;
+ for (JOIN_TAB *tab= join_tab+const_tables; tab < jt; tab++)
+ {
+ cache= tab->cache;
+ if (cache)
+ {
+ size_t buff_size;
+ if (needed_space < cache->get_min_join_buffer_size())
+ return TRUE;
+ if (cache->shrink_join_buffer_in_ratio(curr_space, needed_space))
+ {
+ revise_cache_usage(tab);
+ return TRUE;
+ }
+ buff_size= cache->get_join_buffer_size();
+ curr_space-= buff_size;
+ needed_space-= buff_size;
+ }
+ }
+
+ cache= jt->cache;
+ DBUG_ASSERT(cache);
+ if (needed_space < cache->get_min_join_buffer_size())
+ return TRUE;
+ cache->set_join_buffer_size((size_t)needed_space);
+
+ return FALSE;
+}
+
+
+int
+JOIN::reinit()
+{
+ DBUG_ENTER("JOIN::reinit");
+
+ unit->offset_limit_cnt= (ha_rows)(select_lex->offset_limit ?
+ select_lex->offset_limit->val_uint() : 0);
+
+ first_record= 0;
+ cleaned= false;
+
+ if (exec_tmp_table1)
+ {
+ exec_tmp_table1->file->extra(HA_EXTRA_RESET_STATE);
+ exec_tmp_table1->file->ha_delete_all_rows();
+ free_io_cache(exec_tmp_table1);
+ filesort_free_buffers(exec_tmp_table1,0);
+ }
+ if (exec_tmp_table2)
+ {
+ exec_tmp_table2->file->extra(HA_EXTRA_RESET_STATE);
+ exec_tmp_table2->file->ha_delete_all_rows();
+ free_io_cache(exec_tmp_table2);
+ filesort_free_buffers(exec_tmp_table2,0);
+ }
+ clear_sj_tmp_tables(this);
+ if (items0)
+ set_items_ref_array(items0);
+
+ if (join_tab_save)
+ memcpy(join_tab, join_tab_save, sizeof(JOIN_TAB) * table_count);
+
+ /* need to reset ref access state (see join_read_key) */
+ if (join_tab)
+ {
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITH_CONST_TABLES); tab;
+ tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS))
+ {
+ tab->ref.key_err= TRUE;
+ }
+ }
+
+ if (tmp_join)
+ restore_tmp();
+
+ /* Reset of sum functions */
+ if (sum_funcs)
+ {
+ Item_sum *func, **func_ptr= sum_funcs;
+ while ((func= *(func_ptr++)))
+ func->clear();
+ }
+
+ if (no_rows_in_result_called)
+ {
+ /* Reset effect of possible no_rows_in_result() */
+ List_iterator_fast<Item> it(fields_list);
+ Item *item;
+ no_rows_in_result_called= 0;
+ while ((item= it++))
+ item->restore_to_before_no_rows_in_result();
+ }
+
+ if (!(select_options & SELECT_DESCRIBE))
+ init_ftfuncs(thd, select_lex, MY_TEST(order));
+
+ DBUG_RETURN(0);
+}
+
+/**
+ @brief Save the original join layout
+
+ @details Saves the original join layout so it can be reused in
+ re-execution and for EXPLAIN.
+
+ @return Operation status
+ @retval 0 success.
+ @retval 1 error occurred.
+*/
+
+bool
+JOIN::init_save_join_tab()
+{
+ if (!(tmp_join= (JOIN*)thd->alloc(sizeof(JOIN))))
+ return 1; /* purecov: inspected */
+ error= 0; // Ensure that tmp_join.error= 0
+ restore_tmp();
+ return 0;
+}
+
+
+bool
+JOIN::save_join_tab()
+{
+ if (!join_tab_save && select_lex->master_unit()->uncacheable)
+ {
+ if (!(join_tab_save= (JOIN_TAB*)thd->memdup((uchar*) join_tab,
+ sizeof(JOIN_TAB) * table_count)))
+ return 1;
+ }
+ return 0;
+}
+
+
+void JOIN::save_explain_data(Explain_query *output, bool can_overwrite,
+ bool need_tmp_table, bool need_order,
+ bool distinct)
+{
+ if (select_lex->select_number != UINT_MAX &&
+ select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ &&
+ have_query_plan != JOIN::QEP_NOT_PRESENT_YET &&
+ have_query_plan != JOIN::QEP_DELETED && // this happens when there was
+ // no QEP ever, but then
+ //cleanup() is called multiple times
+ output && // for "SET" command in SPs.
+ (can_overwrite? true: !output->get_select(select_lex->select_number)))
+ {
+ const char *message= NULL;
+ if (!table_count || !tables_list || zero_result_cause)
+ {
+ /* It's a degenerate join */
+ message= zero_result_cause ? zero_result_cause : "No tables used";
+ }
+ save_explain_data_intern(thd->lex->explain, need_tmp_table, need_order,
+ distinct, message);
+ }
+}
+
+
+void JOIN::exec()
+{
+ DBUG_EXECUTE_IF("show_explain_probe_join_exec_start",
+ if (dbug_user_var_equals_int(thd,
+ "show_explain_probe_select_id",
+ select_lex->select_number))
+ dbug_serve_apcs(thd, 1);
+ );
+ exec_inner();
+
+ if (!exec_saved_explain)
+ {
+ save_explain_data(thd->lex->explain, true /* can overwrite */,
+ need_tmp,
+ order != 0 && !skip_sort_order,
+ select_distinct);
+ exec_saved_explain= true;
+ }
+
+ DBUG_EXECUTE_IF("show_explain_probe_join_exec_end",
+ if (dbug_user_var_equals_int(thd,
+ "show_explain_probe_select_id",
+ select_lex->select_number))
+ dbug_serve_apcs(thd, 1);
+ );
+}
+
+
+/**
+ Exec select.
+
+ @todo
+ Note, that create_sort_index calls test_if_skip_sort_order and may
+ finally replace sorting with index scan if there is a LIMIT clause in
+ the query. It's never shown in EXPLAIN!
+
+ @todo
+ When can we have here thd->net.report_error not zero?
+*/
+
+void JOIN::exec_inner()
+{
+ List<Item> *columns_list= &fields_list;
+ int tmp_error;
+
+ DBUG_ENTER("JOIN::exec");
+
+ const bool has_group_by= this->group;
+
+ THD_STAGE_INFO(thd, stage_executing);
+ error= 0;
+ if (procedure)
+ {
+ procedure_fields_list= fields_list;
+ if (procedure->change_columns(procedure_fields_list) ||
+ result->prepare(procedure_fields_list, unit))
+ {
+ thd->set_examined_row_count(0);
+ thd->limit_found_rows= 0;
+ DBUG_VOID_RETURN;
+ }
+ columns_list= &procedure_fields_list;
+ }
+ (void) result->prepare2(); // Currently, this cannot fail.
+
+ if (!tables_list && (table_count || !select_lex->with_sum_func))
+ { // Only test of functions
+ if (select_options & SELECT_DESCRIBE)
+ select_describe(this, FALSE, FALSE, FALSE,
+ (zero_result_cause?zero_result_cause:"No tables used"));
+ else
+ {
+ if (result->send_result_set_metadata(*columns_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ {
+ DBUG_VOID_RETURN;
+ }
+ /*
+ We have to test for 'conds' here as the WHERE may not be constant
+ even if we don't have any tables for prepared statements or if
+ conds uses something like 'rand()'.
+ If the HAVING clause is either impossible or always true, then
+ JOIN::having is set to NULL by optimize_cond.
+ In this case JOIN::exec must check for JOIN::having_value, in the
+ same way it checks for JOIN::cond_value.
+ */
+ DBUG_ASSERT(error == 0);
+ if (cond_value != Item::COND_FALSE &&
+ having_value != Item::COND_FALSE &&
+ (!conds || conds->val_int()) &&
+ (!having || having->val_int()))
+ {
+ if (do_send_rows &&
+ (procedure ? (procedure->send_row(procedure_fields_list) ||
+ procedure->end_of_records()) : result->send_data(fields_list)> 0))
+ error= 1;
+ else
+ send_records= ((select_options & OPTION_FOUND_ROWS) ? 1 :
+ thd->get_sent_row_count());
+ }
+ else
+ send_records= 0;
+ if (!error)
+ {
+ join_free(); // Unlock all cursors
+ error= (int) result->send_eof();
+ }
+ }
+ /* Single select (without union) always returns 0 or 1 row */
+ thd->limit_found_rows= send_records;
+ thd->set_examined_row_count(0);
+ DBUG_VOID_RETURN;
+ }
+ /*
+ Don't reset the found rows count if there're no tables as
+ FOUND_ROWS() may be called. Never reset the examined row count here.
+ It must be accumulated from all join iterations of all join parts.
+ */
+ if (table_count)
+ thd->limit_found_rows= 0;
+
+ /*
+ Evaluate expensive constant conditions that were not evaluated during
+ optimization. Do not evaluate them for EXPLAIN statements as these
+ condtions may be arbitrarily costly, and because the optimize phase
+ might not have produced a complete executable plan for EXPLAINs.
+ */
+ if (exec_const_cond && !(select_options & SELECT_DESCRIBE) &&
+ !exec_const_cond->val_int())
+ zero_result_cause= "Impossible WHERE noticed after reading const tables";
+
+ /*
+ We've called exec_const_cond->val_int(). This may have caused an error.
+ */
+ if (thd->is_error())
+ {
+ error= thd->is_error();
+ DBUG_VOID_RETURN;
+ }
+
+ if (zero_result_cause)
+ {
+ (void) return_zero_rows(this, result, select_lex->leaf_tables,
+ *columns_list,
+ send_row_on_empty_set(),
+ select_options,
+ zero_result_cause,
+ having ? having : tmp_having, all_fields);
+ DBUG_VOID_RETURN;
+ }
+
+ /*
+ Evaluate all constant expressions with subqueries in the ORDER/GROUP clauses
+ to make sure that all subqueries return a single row. The evaluation itself
+ will trigger an error if that is not the case.
+ */
+ if (exec_const_order_group_cond.elements &&
+ !(select_options & SELECT_DESCRIBE))
+ {
+ List_iterator_fast<Item> const_item_it(exec_const_order_group_cond);
+ Item *cur_const_item;
+ while ((cur_const_item= const_item_it++))
+ {
+ cur_const_item->val_str(); // This caches val_str() to Item::str_value
+ if (thd->is_error())
+ {
+ error= thd->is_error();
+ DBUG_VOID_RETURN;
+ }
+ }
+ }
+
+ if ((this->select_lex->options & OPTION_SCHEMA_TABLE) &&
+ get_schema_tables_result(this, PROCESSED_BY_JOIN_EXEC))
+ DBUG_VOID_RETURN;
+
+ if (select_options & SELECT_DESCRIBE)
+ {
+ /*
+ Check if we managed to optimize ORDER BY away and don't use temporary
+ table to resolve ORDER BY: in that case, we only may need to do
+ filesort for GROUP BY.
+ */
+ if (!order && !no_order && (!skip_sort_order || !need_tmp))
+ {
+ /*
+ Reset 'order' to 'group_list' and reinit variables describing
+ 'order'
+ */
+ order= group_list;
+ simple_order= simple_group;
+ skip_sort_order= 0;
+ }
+ if (order &&
+ (order != group_list || !(select_options & SELECT_BIG_RESULT)) &&
+ (const_tables == table_count ||
+ ((simple_order || skip_sort_order) &&
+ test_if_skip_sort_order(&join_tab[const_tables], order,
+ select_limit, 0,
+ &join_tab[const_tables].table->
+ keys_in_use_for_query))))
+ order=0;
+ having= tmp_having;
+ select_describe(this, need_tmp,
+ order != 0 && !skip_sort_order,
+ select_distinct,
+ !table_count ? "No tables used" : NullS);
+ DBUG_VOID_RETURN;
+ }
+ else
+ {
+ /* it's a const select, materialize it. */
+ select_lex->mark_const_derived(zero_result_cause);
+ }
+
+ if (!initialized && init_execution())
+ DBUG_VOID_RETURN;
+
+ JOIN *curr_join= this;
+ List<Item> *curr_all_fields= &all_fields;
+ List<Item> *curr_fields_list= &fields_list;
+ TABLE *curr_tmp_table= 0;
+ /*
+ curr_join->join_free() will call JOIN::cleanup(full=TRUE). It will not
+ be safe to call update_used_tables() after that.
+ */
+ if (curr_join->tmp_having)
+ curr_join->tmp_having->update_used_tables();
+
+ /*
+ Initialize examined rows here because the values from all join parts
+ must be accumulated in examined_row_count. Hence every join
+ iteration must count from zero.
+ */
+ curr_join->examined_rows= 0;
+
+ /* Create a tmp table if distinct or if the sort is too complicated */
+ if (need_tmp)
+ {
+ if (tmp_join)
+ {
+ /*
+ We are in a non cacheable sub query. Get the saved join structure
+ after optimization.
+ (curr_join may have been modified during last exection and we need
+ to reset it)
+ */
+ curr_join= tmp_join;
+ }
+ curr_tmp_table= exec_tmp_table1;
+
+ /* Copy data to the temporary table */
+ THD_STAGE_INFO(thd, stage_copying_to_tmp_table);
+ DBUG_PRINT("info", ("%s", thd->proc_info));
+ if (!curr_join->sort_and_group &&
+ curr_join->const_tables != curr_join->table_count)
+ {
+ JOIN_TAB *first_tab= curr_join->join_tab + curr_join->const_tables;
+ first_tab->sorted= MY_TEST(first_tab->loosescan_match_tab);
+ }
+
+ Procedure *save_proc= curr_join->procedure;
+ tmp_error= do_select(curr_join, (List<Item> *) 0, curr_tmp_table, 0);
+ curr_join->procedure= save_proc;
+ if (tmp_error)
+ {
+ error= tmp_error;
+ DBUG_VOID_RETURN;
+ }
+ curr_tmp_table->file->info(HA_STATUS_VARIABLE);
+
+ if (curr_join->having)
+ curr_join->having= curr_join->tmp_having= 0; // Allready done
+
+ /* Change sum_fields reference to calculated fields in tmp_table */
+#ifdef HAVE_valgrind
+ if (curr_join != this)
+#endif
+ curr_join->all_fields= *curr_all_fields;
+ if (!items1)
+ {
+ items1= items0 + all_fields.elements;
+ if (sort_and_group || curr_tmp_table->group ||
+ tmp_table_param.precomputed_group_by)
+ {
+ if (change_to_use_tmp_fields(thd, items1,
+ tmp_fields_list1, tmp_all_fields1,
+ fields_list.elements, all_fields))
+ DBUG_VOID_RETURN;
+ }
+ else
+ {
+ if (change_refs_to_tmp_fields(thd, items1,
+ tmp_fields_list1, tmp_all_fields1,
+ fields_list.elements, all_fields))
+ DBUG_VOID_RETURN;
+ }
+#ifdef HAVE_valgrind
+ if (curr_join != this)
+#endif
+ {
+ curr_join->tmp_all_fields1= tmp_all_fields1;
+ curr_join->tmp_fields_list1= tmp_fields_list1;
+ }
+ curr_join->items1= items1;
+ }
+ curr_all_fields= &tmp_all_fields1;
+ curr_fields_list= &tmp_fields_list1;
+ curr_join->set_items_ref_array(items1);
+
+ if (sort_and_group || curr_tmp_table->group)
+ {
+ curr_join->tmp_table_param.field_count+=
+ curr_join->tmp_table_param.sum_func_count+
+ curr_join->tmp_table_param.func_count;
+ curr_join->tmp_table_param.sum_func_count=
+ curr_join->tmp_table_param.func_count= 0;
+ }
+ else
+ {
+ curr_join->tmp_table_param.field_count+=
+ curr_join->tmp_table_param.func_count;
+ curr_join->tmp_table_param.func_count= 0;
+ }
+
+ // procedure can't be used inside subselect => we do nothing special for it
+ if (procedure)
+ procedure->update_refs();
+
+ if (curr_tmp_table->group)
+ { // Already grouped
+ if (!curr_join->order && !curr_join->no_order && !skip_sort_order)
+ curr_join->order= curr_join->group_list; /* order by group */
+ curr_join->group_list= 0;
+ }
+
+ /*
+ If we have different sort & group then we must sort the data by group
+ and copy it to another tmp table
+ This code is also used if we are using distinct something
+ we haven't been able to store in the temporary table yet
+ like SEC_TO_TIME(SUM(...)).
+ */
+
+ if ((curr_join->group_list && (!test_if_subpart(curr_join->group_list,
+ curr_join->order) ||
+ curr_join->select_distinct)) ||
+ (curr_join->select_distinct &&
+ curr_join->tmp_table_param.using_indirect_summary_function))
+ { /* Must copy to another table */
+ DBUG_PRINT("info",("Creating group table"));
+
+ /* Free first data from old join */
+
+ /*
+ psergey-todo: this is the place of pre-mature JOIN::free call.
+ */
+ curr_join->join_free();
+ if (curr_join->make_simple_join(this, curr_tmp_table))
+ DBUG_VOID_RETURN;
+ calc_group_buffer(curr_join, group_list);
+ count_field_types(select_lex, &curr_join->tmp_table_param,
+ curr_join->tmp_all_fields1,
+ curr_join->select_distinct && !curr_join->group_list);
+ curr_join->tmp_table_param.hidden_field_count=
+ (curr_join->tmp_all_fields1.elements-
+ curr_join->tmp_fields_list1.elements);
+
+
+ if (exec_tmp_table2)
+ curr_tmp_table= exec_tmp_table2;
+ else
+ {
+ /* group data to new table */
+
+ /*
+ If the access method is loose index scan then all MIN/MAX
+ functions are precomputed, and should be treated as regular
+ functions. See extended comment in JOIN::exec.
+ */
+ if (curr_join->join_tab->is_using_loose_index_scan())
+ curr_join->tmp_table_param.precomputed_group_by= TRUE;
+
+ if (!(curr_tmp_table=
+ exec_tmp_table2= create_tmp_table(thd,
+ &curr_join->tmp_table_param,
+ *curr_all_fields,
+ (ORDER*) 0,
+ curr_join->select_distinct &&
+ !curr_join->group_list,
+ 1, curr_join->select_options,
+ HA_POS_ERROR, "")))
+ DBUG_VOID_RETURN;
+ curr_join->exec_tmp_table2= exec_tmp_table2;
+ }
+ if (curr_join->group_list)
+ {
+ if (curr_join->join_tab == join_tab && save_join_tab())
+ {
+ DBUG_VOID_RETURN;
+ }
+ DBUG_PRINT("info",("Sorting for index"));
+ THD_STAGE_INFO(thd, stage_creating_sort_index);
+ if (create_sort_index(thd, curr_join, curr_join->group_list,
+ HA_POS_ERROR, HA_POS_ERROR, FALSE) ||
+ make_group_fields(this, curr_join))
+ {
+ DBUG_VOID_RETURN;
+ }
+ sortorder= curr_join->sortorder;
+ }
+
+ THD_STAGE_INFO(thd, stage_copying_to_group_table);
+ DBUG_PRINT("info", ("%s", thd->proc_info));
+ if (curr_join != this)
+ {
+ if (sum_funcs2)
+ {
+ curr_join->sum_funcs= sum_funcs2;
+ curr_join->sum_funcs_end= sum_funcs_end2;
+ }
+ else
+ {
+ curr_join->alloc_func_list();
+ sum_funcs2= curr_join->sum_funcs;
+ sum_funcs_end2= curr_join->sum_funcs_end;
+ }
+ }
+ if (curr_join->make_sum_func_list(*curr_all_fields, *curr_fields_list,
+ 1, TRUE) ||
+ prepare_sum_aggregators(curr_join->sum_funcs,
+ !curr_join->join_tab->is_using_agg_loose_index_scan()))
+ DBUG_VOID_RETURN;
+ curr_join->group_list= 0;
+ if (!curr_join->sort_and_group &&
+ curr_join->const_tables != curr_join->table_count)
+ {
+ JOIN_TAB *first_tab= curr_join->join_tab + curr_join->const_tables;
+ first_tab->sorted= MY_TEST(first_tab->loosescan_match_tab);
+ }
+ tmp_error= -1;
+ if (setup_sum_funcs(curr_join->thd, curr_join->sum_funcs) ||
+ (tmp_error= do_select(curr_join, (List<Item> *) 0, curr_tmp_table,
+ 0)))
+ {
+ error= tmp_error;
+ DBUG_VOID_RETURN;
+ }
+ end_read_record(&curr_join->join_tab->read_record);
+ curr_join->const_tables= curr_join->table_count; // Mark free for cleanup()
+ curr_join->join_tab[0].table= 0; // Table is freed
+
+ // No sum funcs anymore
+ if (!items2)
+ {
+ items2= items1 + all_fields.elements;
+ if (change_to_use_tmp_fields(thd, items2,
+ tmp_fields_list2, tmp_all_fields2,
+ fields_list.elements, tmp_all_fields1))
+ DBUG_VOID_RETURN;
+#ifdef HAVE_valgrind
+ /*
+ Some GCCs use memcpy() for struct assignment, even for x=x.
+ GCC bug 19410: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=19410
+ */
+ if (curr_join != this)
+#endif
+ {
+ curr_join->tmp_fields_list2= tmp_fields_list2;
+ curr_join->tmp_all_fields2= tmp_all_fields2;
+ }
+ }
+ curr_fields_list= &curr_join->tmp_fields_list2;
+ curr_all_fields= &curr_join->tmp_all_fields2;
+ curr_join->set_items_ref_array(items2);
+ curr_join->tmp_table_param.field_count+=
+ curr_join->tmp_table_param.sum_func_count;
+ curr_join->tmp_table_param.sum_func_count= 0;
+ }
+ if (curr_tmp_table->distinct)
+ curr_join->select_distinct=0; /* Each row is unique */
+
+
+ curr_join->join_free(); /* Free quick selects */
+
+ if (curr_join->select_distinct && ! curr_join->group_list)
+ {
+ THD_STAGE_INFO(thd, stage_removing_duplicates);
+ if (remove_duplicates(curr_join, curr_tmp_table,
+ *curr_fields_list, curr_join->tmp_having))
+ DBUG_VOID_RETURN;
+ curr_join->tmp_having=0;
+ curr_join->select_distinct=0;
+ }
+ curr_tmp_table->reginfo.lock_type= TL_UNLOCK;
+ if (curr_join->make_simple_join(this, curr_tmp_table))
+ DBUG_VOID_RETURN;
+ calc_group_buffer(curr_join, curr_join->group_list);
+ count_field_types(select_lex, &curr_join->tmp_table_param,
+ *curr_all_fields, 0);
+
+ }
+ if (procedure)
+ count_field_types(select_lex, &curr_join->tmp_table_param,
+ *curr_all_fields, 0);
+
+ if (curr_join->group || curr_join->implicit_grouping ||
+ curr_join->tmp_table_param.sum_func_count ||
+ (procedure && (procedure->flags & PROC_GROUP)))
+ {
+ if (make_group_fields(this, curr_join))
+ {
+ DBUG_VOID_RETURN;
+ }
+ if (!items3)
+ {
+ if (!items0)
+ init_items_ref_array();
+ items3= ref_pointer_array + (all_fields.elements*4);
+ setup_copy_fields(thd, &curr_join->tmp_table_param,
+ items3, tmp_fields_list3, tmp_all_fields3,
+ curr_fields_list->elements, *curr_all_fields);
+ tmp_table_param.save_copy_funcs= curr_join->tmp_table_param.copy_funcs;
+ tmp_table_param.save_copy_field= curr_join->tmp_table_param.copy_field;
+ tmp_table_param.save_copy_field_end=
+ curr_join->tmp_table_param.copy_field_end;
+#ifdef HAVE_valgrind
+ if (curr_join != this)
+#endif
+ {
+ curr_join->tmp_all_fields3= tmp_all_fields3;
+ curr_join->tmp_fields_list3= tmp_fields_list3;
+ }
+ }
+ else
+ {
+ curr_join->tmp_table_param.copy_funcs= tmp_table_param.save_copy_funcs;
+ curr_join->tmp_table_param.copy_field= tmp_table_param.save_copy_field;
+ curr_join->tmp_table_param.copy_field_end=
+ tmp_table_param.save_copy_field_end;
+ }
+ curr_fields_list= &tmp_fields_list3;
+ curr_all_fields= &tmp_all_fields3;
+ curr_join->set_items_ref_array(items3);
+
+ if (curr_join->make_sum_func_list(*curr_all_fields, *curr_fields_list,
+ 1, TRUE) ||
+ prepare_sum_aggregators(curr_join->sum_funcs,
+ !curr_join->join_tab ||
+ !curr_join->join_tab->
+ is_using_agg_loose_index_scan()) ||
+ setup_sum_funcs(curr_join->thd, curr_join->sum_funcs) ||
+ thd->is_fatal_error)
+ DBUG_VOID_RETURN;
+ }
+ if (curr_join->group_list || curr_join->order)
+ {
+ DBUG_PRINT("info",("Sorting for send_result_set_metadata"));
+ THD_STAGE_INFO(thd, stage_sorting_result);
+ /* If we have already done the group, add HAVING to sorted table */
+ if (curr_join->tmp_having && ! curr_join->group_list &&
+ ! curr_join->sort_and_group)
+ {
+ JOIN_TAB *curr_table= &curr_join->join_tab[curr_join->const_tables];
+ table_map used_tables= (curr_join->const_table_map |
+ curr_table->table->map);
+ curr_join->tmp_having->update_used_tables();
+
+ Item* sort_table_cond= make_cond_for_table(thd, curr_join->tmp_having,
+ used_tables,
+ (table_map)0, -1,
+ FALSE, FALSE);
+ if (sort_table_cond)
+ {
+ if (!curr_table->select)
+ if (!(curr_table->select= new SQL_SELECT))
+ DBUG_VOID_RETURN;
+ if (!curr_table->select->cond)
+ curr_table->select->cond= sort_table_cond;
+ else
+ {
+ if (!(curr_table->select->cond=
+ new Item_cond_and(curr_table->select->cond,
+ sort_table_cond)))
+ DBUG_VOID_RETURN;
+ }
+ if (curr_table->pre_idx_push_select_cond)
+ {
+ if (sort_table_cond->type() == Item::COND_ITEM)
+ sort_table_cond= sort_table_cond->copy_andor_structure(thd);
+ if (!(curr_table->pre_idx_push_select_cond=
+ new Item_cond_and(curr_table->pre_idx_push_select_cond,
+ sort_table_cond)))
+ DBUG_VOID_RETURN;
+ }
+ if (curr_table->select->cond && !curr_table->select->cond->fixed)
+ curr_table->select->cond->fix_fields(thd, 0);
+ if (curr_table->pre_idx_push_select_cond &&
+ !curr_table->pre_idx_push_select_cond->fixed)
+ curr_table->pre_idx_push_select_cond->fix_fields(thd, 0);
+
+ curr_table->select->pre_idx_push_select_cond=
+ curr_table->pre_idx_push_select_cond;
+ curr_table->set_select_cond(curr_table->select->cond, __LINE__);
+ curr_table->select_cond->top_level_item();
+ DBUG_EXECUTE("where",print_where(curr_table->select->cond,
+ "select and having",
+ QT_ORDINARY););
+ curr_join->tmp_having= make_cond_for_table(thd, curr_join->tmp_having,
+ ~ (table_map) 0,
+ ~used_tables, -1,
+ FALSE, FALSE);
+ DBUG_EXECUTE("where",print_where(curr_join->tmp_having,
+ "having after sort",
+ QT_ORDINARY););
+ }
+ }
+ {
+ if (group)
+ curr_join->select_limit= HA_POS_ERROR;
+ else
+ {
+ /*
+ We can abort sorting after thd->select_limit rows if we there is no
+ WHERE clause for any tables after the sorted one.
+ */
+ JOIN_TAB *curr_table= &curr_join->join_tab[curr_join->const_tables+1];
+ JOIN_TAB *end_table= &curr_join->join_tab[curr_join->top_join_tab_count];
+ for (; curr_table < end_table ; curr_table++)
+ {
+ /*
+ table->keyuse is set in the case there was an original WHERE clause
+ on the table that was optimized away.
+ */
+ if (curr_table->select_cond ||
+ (curr_table->keyuse && !curr_table->first_inner))
+ {
+ /* We have to sort all rows */
+ curr_join->select_limit= HA_POS_ERROR;
+ break;
+ }
+ }
+ }
+ if (curr_join->join_tab == join_tab && save_join_tab())
+ {
+ DBUG_VOID_RETURN;
+ }
+ /*
+ Here we sort rows for ORDER BY/GROUP BY clause, if the optimiser
+ chose FILESORT to be faster than INDEX SCAN or there is no
+ suitable index present.
+ Note, that create_sort_index calls test_if_skip_sort_order and may
+ finally replace sorting with index scan if there is a LIMIT clause in
+ the query. XXX: it's never shown in EXPLAIN!
+ OPTION_FOUND_ROWS supersedes LIMIT and is taken into account.
+ */
+ DBUG_PRINT("info",("Sorting for order by/group by"));
+ ORDER *order_arg=
+ curr_join->group_list ? curr_join->group_list : curr_join->order;
+ /*
+ filesort_limit: Return only this many rows from filesort().
+ We can use select_limit_cnt only if we have no group_by and 1 table.
+ This allows us to use Bounded_queue for queries like:
+ "select SQL_CALC_FOUND_ROWS * from t1 order by b desc limit 1;"
+ select_limit == HA_POS_ERROR (we need a full table scan)
+ unit->select_limit_cnt == 1 (we only need one row in the result set)
+ */
+ const ha_rows filesort_limit_arg=
+ (has_group_by || curr_join->table_count > 1)
+ ? curr_join->select_limit : unit->select_limit_cnt;
+ const ha_rows select_limit_arg=
+ select_options & OPTION_FOUND_ROWS
+ ? HA_POS_ERROR : unit->select_limit_cnt;
+ curr_join->filesort_found_rows= filesort_limit_arg != HA_POS_ERROR;
+
+ DBUG_PRINT("info", ("has_group_by %d "
+ "curr_join->table_count %d "
+ "curr_join->m_select_limit %d "
+ "unit->select_limit_cnt %d",
+ has_group_by,
+ curr_join->table_count,
+ (int) curr_join->select_limit,
+ (int) unit->select_limit_cnt));
+
+ if (create_sort_index(thd,
+ curr_join,
+ order_arg,
+ filesort_limit_arg,
+ select_limit_arg,
+ curr_join->group_list ? FALSE : TRUE))
+ DBUG_VOID_RETURN;
+ sortorder= curr_join->sortorder;
+ if (curr_join->const_tables != curr_join->table_count &&
+ !curr_join->join_tab[curr_join->const_tables].table->sort.io_cache)
+ {
+ /*
+ If no IO cache exists for the first table then we are using an
+ INDEX SCAN and no filesort. Thus we should not remove the sorted
+ attribute on the INDEX SCAN.
+ */
+ skip_sort_order= 1;
+ }
+ }
+ }
+ /* XXX: When can we have here thd->is_error() not zero? */
+ if (thd->is_error())
+ {
+ error= thd->is_error();
+ DBUG_VOID_RETURN;
+ }
+ curr_join->having= curr_join->tmp_having;
+ curr_join->fields= curr_fields_list;
+ curr_join->procedure= procedure;
+
+ THD_STAGE_INFO(thd, stage_sending_data);
+ DBUG_PRINT("info", ("%s", thd->proc_info));
+ result->send_result_set_metadata((procedure ? curr_join->procedure_fields_list :
+ *curr_fields_list),
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
+ error= do_select(curr_join, curr_fields_list, NULL, procedure);
+ thd->limit_found_rows= curr_join->send_records;
+ if (curr_join->order && curr_join->sortorder &&
+ curr_join->filesort_found_rows)
+ {
+ /* Use info provided by filesort. */
+ DBUG_ASSERT(curr_join->table_count > curr_join->const_tables);
+ JOIN_TAB *tab= curr_join->join_tab + curr_join->const_tables;
+ thd->limit_found_rows= tab->records;
+ }
+
+ /* Accumulate the counts from all join iterations of all join parts. */
+ thd->inc_examined_row_count(curr_join->examined_rows);
+ DBUG_PRINT("counts", ("thd->examined_row_count: %lu",
+ (ulong) thd->get_examined_row_count()));
+
+ /*
+ With EXPLAIN EXTENDED we have to restore original ref_array
+ for a derived table which is always materialized.
+ We also need to do this when we have temp table(s).
+ Otherwise we would not be able to print the query correctly.
+ */
+ if (items0 && (thd->lex->describe & DESCRIBE_EXTENDED) &&
+ (select_lex->linkage == DERIVED_TABLE_TYPE ||
+ exec_tmp_table1 || exec_tmp_table2))
+ set_items_ref_array(items0);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Clean up join.
+
+ @return
+ Return error that hold JOIN.
+*/
+
+int
+JOIN::destroy()
+{
+ DBUG_ENTER("JOIN::destroy");
+ select_lex->join= 0;
+
+ if (tmp_join)
+ {
+ if (join_tab != tmp_join->join_tab)
+ {
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITH_CONST_TABLES);
+ tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS))
+ {
+ tab->cleanup();
+ }
+ }
+ tmp_join->tmp_join= 0;
+ /*
+ We need to clean up tmp_table_param for reusable JOINs (having non-zero
+ and different from self tmp_join) because it's not being cleaned up
+ anywhere else (as we need to keep the join is reusable).
+ */
+ tmp_table_param.cleanup();
+ tmp_join->tmp_table_param.copy_field= 0;
+ DBUG_RETURN(tmp_join->destroy());
+ }
+ cond_equal= 0;
+ having_equal= 0;
+
+ cleanup(1);
+ /* Cleanup items referencing temporary table columns */
+ cleanup_item_list(tmp_all_fields1);
+ cleanup_item_list(tmp_all_fields3);
+ if (exec_tmp_table1)
+ free_tmp_table(thd, exec_tmp_table1);
+ if (exec_tmp_table2)
+ free_tmp_table(thd, exec_tmp_table2);
+ delete select;
+ destroy_sj_tmp_tables(this);
+ delete_dynamic(&keyuse);
+ delete procedure;
+ DBUG_RETURN(error);
+}
+
+
+void JOIN::cleanup_item_list(List<Item> &items) const
+{
+ DBUG_ENTER("JOIN::cleanup_item_list");
+ if (!items.is_empty())
+ {
+ List_iterator_fast<Item> it(items);
+ Item *item;
+ while ((item= it++))
+ item->cleanup();
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ An entry point to single-unit select (a select without UNION).
+
+ @param thd thread handler
+ @param rref_pointer_array a reference to ref_pointer_array of
+ the top-level select_lex for this query
+ @param tables list of all tables used in this query.
+ The tables have been pre-opened.
+ @param wild_num number of wildcards used in the top level
+ select of this query.
+ For example statement
+ SELECT *, t1.*, catalog.t2.* FROM t0, t1, t2;
+ has 3 wildcards.
+ @param fields list of items in SELECT list of the top-level
+ select
+ e.g. SELECT a, b, c FROM t1 will have Item_field
+ for a, b and c in this list.
+ @param conds top level item of an expression representing
+ WHERE clause of the top level select
+ @param og_num total number of ORDER BY and GROUP BY clauses
+ arguments
+ @param order linked list of ORDER BY agruments
+ @param group linked list of GROUP BY arguments
+ @param having top level item of HAVING expression
+ @param proc_param list of PROCEDUREs
+ @param select_options select options (BIG_RESULT, etc)
+ @param result an instance of result set handling class.
+ This object is responsible for send result
+ set rows to the client or inserting them
+ into a table.
+ @param select_lex the only SELECT_LEX of this query
+ @param unit top-level UNIT of this query
+ UNIT is an artificial object created by the
+ parser for every SELECT clause.
+ e.g.
+ SELECT * FROM t1 WHERE a1 IN (SELECT * FROM t2)
+ has 2 unions.
+
+ @retval
+ FALSE success
+ @retval
+ TRUE an error
+*/
+
+bool
+mysql_select(THD *thd, Item ***rref_pointer_array,
+ TABLE_LIST *tables, uint wild_num, List<Item> &fields,
+ COND *conds, uint og_num, ORDER *order, ORDER *group,
+ Item *having, ORDER *proc_param, ulonglong select_options,
+ select_result *result, SELECT_LEX_UNIT *unit,
+ SELECT_LEX *select_lex)
+{
+ int err= 0;
+ bool free_join= 1;
+ DBUG_ENTER("mysql_select");
+
+ select_lex->context.resolve_in_select_list= TRUE;
+ JOIN *join;
+ if (select_lex->join != 0)
+ {
+ join= select_lex->join;
+ /*
+ is it single SELECT in derived table, called in derived table
+ creation
+ */
+ if (select_lex->linkage != DERIVED_TABLE_TYPE ||
+ (select_options & SELECT_DESCRIBE))
+ {
+ if (select_lex->linkage != GLOBAL_OPTIONS_TYPE)
+ {
+ //here is EXPLAIN of subselect or derived table
+ if (join->change_result(result))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Original join tabs might be overwritten at first
+ subselect execution. So we need to restore them.
+ */
+ Item_subselect *subselect= select_lex->master_unit()->item;
+ if (subselect && subselect->is_uncacheable() && join->reinit())
+ DBUG_RETURN(TRUE);
+ }
+ else
+ {
+ if ((err= join->prepare(rref_pointer_array, tables, wild_num,
+ conds, og_num, order, false, group, having,
+ proc_param, select_lex, unit)))
+ {
+ goto err;
+ }
+ }
+ }
+ free_join= 0;
+ join->select_options= select_options;
+ }
+ else
+ {
+ /*
+ When in EXPLAIN, delay deleting the joins so that they are still
+ available when we're producing EXPLAIN EXTENDED warning text.
+ */
+ if (select_options & SELECT_DESCRIBE)
+ free_join= 0;
+
+ if (!(join= new JOIN(thd, fields, select_options, result)))
+ DBUG_RETURN(TRUE);
+ THD_STAGE_INFO(thd, stage_init);
+ thd->lex->used_tables=0;
+ if ((err= join->prepare(rref_pointer_array, tables, wild_num,
+ conds, og_num, order, false, group, having, proc_param,
+ select_lex, unit)))
+ {
+ goto err;
+ }
+ }
+
+ if ((err= join->optimize()))
+ {
+ goto err; // 1
+ }
+
+ if (thd->lex->describe & DESCRIBE_EXTENDED)
+ {
+ join->conds_history= join->conds;
+ join->having_history= (join->having?join->having:join->tmp_having);
+ }
+
+ if (thd->is_error())
+ goto err;
+
+ join->exec();
+
+ if (thd->lex->describe & DESCRIBE_EXTENDED)
+ {
+ select_lex->where= join->conds_history;
+ select_lex->having= join->having_history;
+ }
+
+err:
+ if (free_join)
+ {
+ THD_STAGE_INFO(thd, stage_end);
+ err|= select_lex->cleanup();
+ DBUG_RETURN(err || thd->is_error());
+ }
+ DBUG_RETURN(join->error ? join->error: err);
+}
+
+
+/*****************************************************************************
+ Create JOIN_TABS, make a guess about the table types,
+ Approximate how many records will be used in each table
+*****************************************************************************/
+
+static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select,
+ TABLE *table,
+ const key_map *keys,ha_rows limit)
+{
+ int error;
+ DBUG_ENTER("get_quick_record_count");
+ uchar buff[STACK_BUFF_ALLOC];
+ if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
+ DBUG_RETURN(0); // Fatal error flag is set
+ if (select)
+ {
+ select->head=table;
+ table->reginfo.impossible_range=0;
+ if ((error= select->test_quick_select(thd, *(key_map *)keys,(table_map) 0,
+ limit, 0, FALSE)) == 1)
+ DBUG_RETURN(select->quick->records);
+ if (error == -1)
+ {
+ table->reginfo.impossible_range=1;
+ DBUG_RETURN(0);
+ }
+ DBUG_PRINT("warning",("Couldn't use record count on const keypart"));
+ }
+ DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */
+}
+
+/*
+ This structure is used to collect info on potentially sargable
+ predicates in order to check whether they become sargable after
+ reading const tables.
+ We form a bitmap of indexes that can be used for sargable predicates.
+ Only such indexes are involved in range analysis.
+*/
+typedef struct st_sargable_param
+{
+ Field *field; /* field against which to check sargability */
+ Item **arg_value; /* values of potential keys for lookups */
+ uint num_values; /* number of values in the above array */
+} SARGABLE_PARAM;
+
+
+/**
+ Calculate the best possible join and initialize the join structure.
+
+ @retval
+ 0 ok
+ @retval
+ 1 Fatal error
+*/
+
+static bool
+make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
+ COND *conds, DYNAMIC_ARRAY *keyuse_array)
+{
+ int error= 0;
+ TABLE *table;
+ uint i,table_count,const_count,key;
+ table_map found_const_table_map, all_table_map, found_ref, refs;
+ key_map const_ref, eq_part;
+ bool has_expensive_keyparts;
+ TABLE **table_vector;
+ JOIN_TAB *stat,*stat_end,*s,**stat_ref, **stat_vector;
+ KEYUSE *keyuse,*start_keyuse;
+ table_map outer_join=0;
+ table_map no_rows_const_tables= 0;
+ SARGABLE_PARAM *sargables= 0;
+ List_iterator<TABLE_LIST> ti(tables_list);
+ TABLE_LIST *tables;
+ DBUG_ENTER("make_join_statistics");
+
+ LINT_INIT(table); /* inited in all loops */
+ table_count=join->table_count;
+
+ stat=(JOIN_TAB*) join->thd->calloc(sizeof(JOIN_TAB)*(table_count));
+ stat_ref=(JOIN_TAB**) join->thd->alloc(sizeof(JOIN_TAB*)*
+ (MAX_TABLES + table_count + 1));
+ stat_vector= stat_ref + MAX_TABLES;
+ table_vector=(TABLE**) join->thd->calloc(sizeof(TABLE*)*(table_count*2));
+ join->positions= new (join->thd->mem_root) POSITION[(table_count+1)];
+ /*
+ best_positions is ok to allocate with alloc() as we copy things to it with
+ memcpy()
+ */
+ join->best_positions= (POSITION*) join->thd->alloc(sizeof(POSITION)*
+ (table_count +1));
+
+ if (join->thd->is_fatal_error)
+ DBUG_RETURN(1); // Eom /* purecov: inspected */
+
+ join->best_ref=stat_vector;
+
+ stat_end=stat+table_count;
+ found_const_table_map= all_table_map=0;
+ const_count=0;
+
+ for (s= stat, i= 0; (tables= ti++); s++, i++)
+ {
+ TABLE_LIST *embedding= tables->embedding;
+ stat_vector[i]=s;
+ s->keys.init();
+ s->const_keys.init();
+ s->checked_keys.init();
+ s->needed_reg.init();
+ table_vector[i]=s->table=table=tables->table;
+ table->pos_in_table_list= tables;
+ error= tables->fetch_number_of_rows();
+ set_statistics_for_table(join->thd, table);
+ bitmap_clear_all(&table->cond_set);
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ const bool all_partitions_pruned_away= table->all_partitions_pruned_away;
+#else
+ const bool all_partitions_pruned_away= FALSE;
+#endif
+
+ DBUG_EXECUTE_IF("bug11747970_raise_error",
+ { join->thd->killed= KILL_QUERY_HARD; });
+ if (error)
+ {
+ table->file->print_error(error, MYF(0));
+ goto error;
+ }
+ table->quick_keys.clear_all();
+ table->intersect_keys.clear_all();
+ table->reginfo.join_tab=s;
+ table->reginfo.not_exists_optimize=0;
+ bzero((char*) table->const_key_parts, sizeof(key_part_map)*table->s->keys);
+ all_table_map|= table->map;
+ s->preread_init_done= FALSE;
+ s->join=join;
+
+ s->dependent= tables->dep_tables;
+ if (tables->schema_table)
+ table->file->stats.records= table->used_stat_records= 2;
+ table->quick_condition_rows= table->stat_records();
+
+ s->on_expr_ref= &tables->on_expr;
+ if (*s->on_expr_ref)
+ {
+ /* s is the only inner table of an outer join */
+ if (!table->is_filled_at_execution() &&
+ ((!table->file->stats.records &&
+ (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) ||
+ all_partitions_pruned_away) && !embedding)
+ { // Empty table
+ s->dependent= 0; // Ignore LEFT JOIN depend.
+ no_rows_const_tables |= table->map;
+ set_position(join,const_count++,s,(KEYUSE*) 0);
+ continue;
+ }
+ outer_join|= table->map;
+ s->embedding_map= 0;
+ for (;embedding; embedding= embedding->embedding)
+ s->embedding_map|= embedding->nested_join->nj_map;
+ continue;
+ }
+ if (embedding)
+ {
+ /* s belongs to a nested join, maybe to several embedded joins */
+ s->embedding_map= 0;
+ bool inside_an_outer_join= FALSE;
+ do
+ {
+ /*
+ If this is a semi-join nest, skip it, and proceed upwards. Maybe
+ we're in some outer join nest
+ */
+ if (embedding->sj_on_expr)
+ {
+ embedding= embedding->embedding;
+ continue;
+ }
+ inside_an_outer_join= TRUE;
+ NESTED_JOIN *nested_join= embedding->nested_join;
+ s->embedding_map|=nested_join->nj_map;
+ s->dependent|= embedding->dep_tables;
+ embedding= embedding->embedding;
+ outer_join|= nested_join->used_tables;
+ }
+ while (embedding);
+ if (inside_an_outer_join)
+ continue;
+ }
+ if (!table->is_filled_at_execution() &&
+ (table->s->system ||
+ (table->file->stats.records <= 1 &&
+ (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) ||
+ all_partitions_pruned_away) &&
+ !s->dependent &&
+ !table->fulltext_searched && !join->no_const_tables)
+ {
+ set_position(join,const_count++,s,(KEYUSE*) 0);
+ no_rows_const_tables |= table->map;
+ }
+
+ /* SJ-Materialization handling: */
+ if (table->pos_in_table_list->jtbm_subselect &&
+ table->pos_in_table_list->jtbm_subselect->is_jtbm_const_tab)
+ {
+ set_position(join,const_count++,s,(KEYUSE*) 0);
+ no_rows_const_tables |= table->map;
+ }
+ }
+
+ stat_vector[i]=0;
+ join->outer_join=outer_join;
+
+ if (join->outer_join)
+ {
+ /*
+ Build transitive closure for relation 'to be dependent on'.
+ This will speed up the plan search for many cases with outer joins,
+ as well as allow us to catch illegal cross references/
+ Warshall's algorithm is used to build the transitive closure.
+ As we use bitmaps to represent the relation the complexity
+ of the algorithm is O((number of tables)^2).
+
+ The classic form of the Warshall's algorithm would look like:
+ for (i= 0; i < table_count; i++)
+ {
+ for (j= 0; j < table_count; j++)
+ {
+ for (k= 0; k < table_count; k++)
+ {
+ if (bitmap_is_set(stat[j].dependent, i) &&
+ bitmap_is_set(stat[i].dependent, k))
+ bitmap_set_bit(stat[j].dependent, k);
+ }
+ }
+ }
+ */
+
+ for (s= stat ; s < stat_end ; s++)
+ {
+ table= s->table;
+ for (JOIN_TAB *t= stat ; t < stat_end ; t++)
+ {
+ if (t->dependent & table->map)
+ t->dependent |= table->reginfo.join_tab->dependent;
+ }
+ if (outer_join & s->table->map)
+ s->table->maybe_null= 1;
+ }
+ /* Catch illegal cross references for outer joins */
+ for (i= 0, s= stat ; i < table_count ; i++, s++)
+ {
+ if (s->dependent & s->table->map)
+ {
+ join->table_count=0; // Don't use join->table
+ my_message(ER_WRONG_OUTER_JOIN, ER(ER_WRONG_OUTER_JOIN), MYF(0));
+ goto error;
+ }
+ s->key_dependent= s->dependent;
+ }
+ }
+
+ if (conds || outer_join)
+ {
+ if (update_ref_and_keys(join->thd, keyuse_array, stat, join->table_count,
+ conds, ~outer_join, join->select_lex, &sargables))
+ goto error;
+ /*
+ Keyparts without prefixes may be useful if this JOIN is a subquery, and
+ if the subquery may be executed via the IN-EXISTS strategy.
+ */
+ bool skip_unprefixed_keyparts=
+ !(join->is_in_subquery() &&
+ ((Item_in_subselect*)join->unit->item)->test_strategy(SUBS_IN_TO_EXISTS));
+
+ if (keyuse_array->elements &&
+ sort_and_filter_keyuse(join->thd, keyuse_array,
+ skip_unprefixed_keyparts))
+ goto error;
+ DBUG_EXECUTE("opt", print_keyuse_array(keyuse_array););
+ }
+
+ join->const_table_map= no_rows_const_tables;
+ join->const_tables= const_count;
+ eliminate_tables(join);
+ join->const_table_map &= ~no_rows_const_tables;
+ const_count= join->const_tables;
+ found_const_table_map= join->const_table_map;
+
+ /* Read tables with 0 or 1 rows (system tables) */
+ for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count;
+ p_pos < p_end ;
+ p_pos++)
+ {
+ s= p_pos->table;
+ if (! (s->table->map & join->eliminated_tables))
+ {
+ int tmp;
+ s->type=JT_SYSTEM;
+ join->const_table_map|=s->table->map;
+ if ((tmp=join_read_const_table(s, p_pos)))
+ {
+ if (tmp > 0)
+ goto error; // Fatal error
+ }
+ else
+ {
+ found_const_table_map|= s->table->map;
+ s->table->pos_in_table_list->optimized_away= TRUE;
+ }
+ }
+ }
+
+ /* loop until no more const tables are found */
+ int ref_changed;
+ do
+ {
+ more_const_tables_found:
+ ref_changed = 0;
+ found_ref=0;
+
+ /*
+ We only have to loop from stat_vector + const_count as
+ set_position() will move all const_tables first in stat_vector
+ */
+
+ for (JOIN_TAB **pos=stat_vector+const_count ; (s= *pos) ; pos++)
+ {
+ table=s->table;
+
+ if (table->is_filled_at_execution())
+ continue;
+
+ /*
+ If equi-join condition by a key is null rejecting and after a
+ substitution of a const table the key value happens to be null
+ then we can state that there are no matches for this equi-join.
+ */
+ if ((keyuse= s->keyuse) && *s->on_expr_ref && !s->embedding_map &&
+ !(table->map & join->eliminated_tables))
+ {
+ /*
+ When performing an outer join operation if there are no matching rows
+ for the single row of the outer table all the inner tables are to be
+ null complemented and thus considered as constant tables.
+ Here we apply this consideration to the case of outer join operations
+ with a single inner table only because the case with nested tables
+ would require a more thorough analysis.
+ TODO. Apply single row substitution to null complemented inner tables
+ for nested outer join operations.
+ */
+ while (keyuse->table == table)
+ {
+ if (!keyuse->is_for_hash_join() &&
+ !(keyuse->val->used_tables() & ~join->const_table_map) &&
+ keyuse->val->is_null() && keyuse->null_rejecting)
+ {
+ s->type= JT_CONST;
+ mark_as_null_row(table);
+ found_const_table_map|= table->map;
+ join->const_table_map|= table->map;
+ set_position(join,const_count++,s,(KEYUSE*) 0);
+ goto more_const_tables_found;
+ }
+ keyuse++;
+ }
+ }
+
+ if (s->dependent) // If dependent on some table
+ {
+ // All dep. must be constants
+ if (s->dependent & ~(found_const_table_map))
+ continue;
+ if (table->file->stats.records <= 1L &&
+ (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT) &&
+ !table->pos_in_table_list->embedding &&
+ !((outer_join & table->map) &&
+ (*s->on_expr_ref)->is_expensive()))
+ { // system table
+ int tmp= 0;
+ s->type=JT_SYSTEM;
+ join->const_table_map|=table->map;
+ set_position(join,const_count++,s,(KEYUSE*) 0);
+ if ((tmp= join_read_const_table(s, join->positions+const_count-1)))
+ {
+ if (tmp > 0)
+ goto error; // Fatal error
+ }
+ else
+ found_const_table_map|= table->map;
+ continue;
+ }
+ }
+ /* check if table can be read by key or table only uses const refs */
+ if ((keyuse=s->keyuse))
+ {
+ s->type= JT_REF;
+ while (keyuse->table == table)
+ {
+ if (keyuse->is_for_hash_join())
+ {
+ keyuse++;
+ continue;
+ }
+ start_keyuse=keyuse;
+ key=keyuse->key;
+ s->keys.set_bit(key); // TODO: remove this ?
+
+ refs=0;
+ const_ref.clear_all();
+ eq_part.clear_all();
+ has_expensive_keyparts= false;
+ do
+ {
+ if (keyuse->val->type() != Item::NULL_ITEM && !keyuse->optimize)
+ {
+ if (!((~found_const_table_map) & keyuse->used_tables))
+ {
+ const_ref.set_bit(keyuse->keypart);
+ if (keyuse->val->is_expensive())
+ has_expensive_keyparts= true;
+ }
+ else
+ refs|=keyuse->used_tables;
+ eq_part.set_bit(keyuse->keypart);
+ }
+ keyuse++;
+ } while (keyuse->table == table && keyuse->key == key);
+
+ TABLE_LIST *embedding= table->pos_in_table_list->embedding;
+ /*
+ TODO (low priority): currently we ignore the const tables that
+ are within a semi-join nest which is within an outer join nest.
+ The effect of this is that we don't do const substitution for
+ such tables.
+ */
+ KEY *keyinfo= table->key_info + key;
+ uint key_parts= table->actual_n_key_parts(keyinfo);
+ if (eq_part.is_prefix(key_parts) &&
+ !table->fulltext_searched &&
+ (!embedding || (embedding->sj_on_expr && !embedding->embedding)))
+ {
+ key_map base_part, base_const_ref, base_eq_part;
+ base_part.set_prefix(keyinfo->user_defined_key_parts);
+ base_const_ref= const_ref;
+ base_const_ref.intersect(base_part);
+ base_eq_part= eq_part;
+ base_eq_part.intersect(base_part);
+ if (table->actual_key_flags(keyinfo) & HA_NOSAME)
+ {
+
+ if (base_const_ref == base_eq_part &&
+ !has_expensive_keyparts &&
+ !((outer_join & table->map) &&
+ (*s->on_expr_ref)->is_expensive()))
+ { // Found everything for ref.
+ int tmp;
+ ref_changed = 1;
+ s->type= JT_CONST;
+ join->const_table_map|=table->map;
+ set_position(join,const_count++,s,start_keyuse);
+ if (create_ref_for_key(join, s, start_keyuse, FALSE,
+ found_const_table_map))
+ goto error;
+ if ((tmp=join_read_const_table(s,
+ join->positions+const_count-1)))
+ {
+ if (tmp > 0)
+ goto error; // Fatal error
+ }
+ else
+ found_const_table_map|= table->map;
+ break;
+ }
+ else
+ found_ref|= refs; // Table is const if all refs are const
+ }
+ else if (base_const_ref == base_eq_part)
+ s->const_keys.set_bit(key);
+ }
+ }
+ }
+ }
+ } while (join->const_table_map & found_ref && ref_changed);
+
+ join->sort_by_table= get_sort_by_table(join->order, join->group_list,
+ join->select_lex->leaf_tables,
+ join->const_table_map);
+ /*
+ Update info on indexes that can be used for search lookups as
+ reading const tables may has added new sargable predicates.
+ */
+ if (const_count && sargables)
+ {
+ for( ; sargables->field ; sargables++)
+ {
+ Field *field= sargables->field;
+ JOIN_TAB *join_tab= field->table->reginfo.join_tab;
+ key_map possible_keys= field->key_start;
+ possible_keys.intersect(field->table->keys_in_use_for_query);
+ bool is_const= 1;
+ for (uint j=0; j < sargables->num_values; j++)
+ is_const&= sargables->arg_value[j]->const_item();
+ if (is_const)
+ join_tab[0].const_keys.merge(possible_keys);
+ }
+ }
+
+ join->impossible_where= false;
+ if (conds && const_count)
+ {
+ COND_EQUAL *orig_cond_equal = join->cond_equal;
+ conds->update_used_tables();
+ conds= remove_eq_conds(join->thd, conds, &join->cond_value);
+ if (conds && conds->type() == Item::COND_ITEM &&
+ ((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC)
+ join->cond_equal= &((Item_cond_and*) conds)->cond_equal;
+ join->select_lex->where= conds;
+ if (join->cond_value == Item::COND_FALSE)
+ {
+ join->impossible_where= true;
+ conds=new Item_int((longlong) 0,1);
+ }
+ join->conds= conds;
+ join->cond_equal= NULL;
+ if (conds)
+ {
+ if (conds->type() == Item::COND_ITEM &&
+ ((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC)
+ join->cond_equal= (&((Item_cond_and *) conds)->cond_equal);
+ else if (conds->type() == Item::FUNC_ITEM &&
+ ((Item_func*) conds)->functype() == Item_func::MULT_EQUAL_FUNC)
+ {
+ if (!join->cond_equal)
+ join->cond_equal= new COND_EQUAL;
+ join->cond_equal->current_level.empty();
+ join->cond_equal->current_level.push_back((Item_equal*) conds);
+ }
+ }
+
+ if (orig_cond_equal != join->cond_equal)
+ {
+ /*
+ If join->cond_equal has changed all references to it from COND_EQUAL
+ objects associated with ON expressions must be updated.
+ */
+ for (JOIN_TAB **pos=stat_vector+const_count ; (s= *pos) ; pos++)
+ {
+ if (*s->on_expr_ref && s->cond_equal &&
+ s->cond_equal->upper_levels == orig_cond_equal)
+ s->cond_equal->upper_levels= join->cond_equal;
+ }
+ }
+ }
+
+ /* Calc how many (possible) matched records in each table */
+
+ for (s=stat ; s < stat_end ; s++)
+ {
+ s->startup_cost= 0;
+ if (s->type == JT_SYSTEM || s->type == JT_CONST)
+ {
+ /* Only one matching row */
+ s->found_records= s->records= 1;
+ s->read_time=1.0;
+ s->worst_seeks=1.0;
+ continue;
+ }
+ /* Approximate found rows and time to read them */
+ if (s->table->is_filled_at_execution())
+ {
+ get_delayed_table_estimates(s->table, &s->records, &s->read_time,
+ &s->startup_cost);
+ s->found_records= s->records;
+ table->quick_condition_rows=s->records;
+ }
+ else
+ {
+ s->scan_time();
+ }
+
+ /*
+ Set a max range of how many seeks we can expect when using keys
+ This is can't be to high as otherwise we are likely to use
+ table scan.
+ */
+ s->worst_seeks= MY_MIN((double) s->found_records / 10,
+ (double) s->read_time*3);
+ if (s->worst_seeks < 2.0) // Fix for small tables
+ s->worst_seeks=2.0;
+
+ /*
+ Add to stat->const_keys those indexes for which all group fields or
+ all select distinct fields participate in one index.
+ */
+ add_group_and_distinct_keys(join, s);
+
+ s->table->cond_selectivity= 1.0;
+
+ /*
+ Perform range analysis if there are keys it could use (1).
+ Don't do range analysis if we're on the inner side of an outer join (2).
+ Do range analysis if we're on the inner side of a semi-join (3).
+ Don't do range analysis for materialized subqueries (4).
+ Don't do range analysis for materialized derived tables (5)
+ */
+ if ((!s->const_keys.is_clear_all() ||
+ !bitmap_is_clear_all(&s->table->cond_set)) && // (1)
+ (!s->table->pos_in_table_list->embedding || // (2)
+ (s->table->pos_in_table_list->embedding && // (3)
+ s->table->pos_in_table_list->embedding->sj_on_expr)) && // (3)
+ !s->table->is_filled_at_execution() && // (4)
+ !(s->table->pos_in_table_list->derived && // (5)
+ s->table->pos_in_table_list->is_materialized_derived())) // (5)
+ {
+ bool impossible_range= FALSE;
+ ha_rows records= HA_POS_ERROR;
+ SQL_SELECT *select= 0;
+ if (!s->const_keys.is_clear_all())
+ {
+ select= make_select(s->table, found_const_table_map,
+ found_const_table_map,
+ *s->on_expr_ref ? *s->on_expr_ref : conds,
+ 1, &error);
+ if (!select)
+ goto error;
+ records= get_quick_record_count(join->thd, select, s->table,
+ &s->const_keys, join->row_limit);
+ s->quick=select->quick;
+ s->needed_reg=select->needed_reg;
+ select->quick=0;
+ impossible_range= records == 0 && s->table->reginfo.impossible_range;
+ }
+ if (!impossible_range)
+ {
+ if (join->thd->variables.optimizer_use_condition_selectivity > 1)
+ calculate_cond_selectivity_for_table(join->thd, s->table,
+ *s->on_expr_ref ?
+ *s->on_expr_ref : conds);
+ if (s->table->reginfo.impossible_range)
+ {
+ impossible_range= TRUE;
+ records= 0;
+ }
+ }
+ if (impossible_range)
+ {
+ /*
+ Impossible WHERE or ON expression
+ In case of ON, we mark that the we match one empty NULL row.
+ In case of WHERE, don't set found_const_table_map to get the
+ caller to abort with a zero row result.
+ */
+ join->const_table_map|= s->table->map;
+ set_position(join,const_count++,s,(KEYUSE*) 0);
+ s->type= JT_CONST;
+ if (*s->on_expr_ref)
+ {
+ /* Generate empty row */
+ s->info= ET_IMPOSSIBLE_ON_CONDITION;
+ found_const_table_map|= s->table->map;
+ s->type= JT_CONST;
+ mark_as_null_row(s->table); // All fields are NULL
+ }
+ }
+ if (records != HA_POS_ERROR)
+ {
+ s->found_records=records;
+ s->read_time= s->quick ? s->quick->read_time : 0.0;
+ }
+ if (select)
+ delete select;
+ }
+
+ }
+
+ if (pull_out_semijoin_tables(join))
+ DBUG_RETURN(TRUE);
+
+ join->join_tab=stat;
+ join->top_join_tab_count= table_count;
+ join->map2table=stat_ref;
+ join->table= table_vector;
+ join->const_tables=const_count;
+ join->found_const_table_map=found_const_table_map;
+
+ if (join->const_tables != join->table_count)
+ optimize_keyuse(join, keyuse_array);
+
+ DBUG_ASSERT(!join->conds || !join->cond_equal ||
+ !join->cond_equal->current_level.elements ||
+ (join->conds->type() == Item::COND_ITEM &&
+ ((Item_cond*) (join->conds))->functype() ==
+ Item_func::COND_AND_FUNC &&
+ join->cond_equal ==
+ &((Item_cond_and *) (join->conds))->cond_equal) ||
+ (join->conds->type() == Item::FUNC_ITEM &&
+ ((Item_func*) (join->conds))->functype() ==
+ Item_func::MULT_EQUAL_FUNC &&
+ join->cond_equal->current_level.elements == 1 &&
+ join->cond_equal->current_level.head() == join->conds));
+
+ if (optimize_semijoin_nests(join, all_table_map))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ {
+ ha_rows records= 1;
+ SELECT_LEX_UNIT *unit= join->select_lex->master_unit();
+
+ /* Find an optimal join order of the non-constant tables. */
+ if (join->const_tables != join->table_count)
+ {
+ if (choose_plan(join, all_table_map & ~join->const_table_map))
+ goto error;
+ }
+ else
+ {
+ memcpy((uchar*) join->best_positions,(uchar*) join->positions,
+ sizeof(POSITION)*join->const_tables);
+ join->record_count= 1.0;
+ join->best_read=1.0;
+ }
+
+ if (!(join->select_options & SELECT_DESCRIBE) &&
+ unit->derived && unit->derived->is_materialized_derived())
+ {
+ /*
+ Calculate estimated number of rows for materialized derived
+ table/view.
+ */
+ for (i= 0; i < join->table_count ; i++)
+ records*= join->best_positions[i].records_read ?
+ (ha_rows)join->best_positions[i].records_read : 1;
+ set_if_smaller(records, unit->select_limit_cnt);
+ join->select_lex->increase_derived_records(records);
+ }
+ }
+
+ if (join->choose_subquery_plan(all_table_map & ~join->const_table_map))
+ goto error;
+
+ DEBUG_SYNC(join->thd, "inside_make_join_statistics");
+
+ /* Generate an execution plan from the found optimal join order. */
+ DBUG_RETURN(join->thd->check_killed() || get_best_combination(join));
+
+error:
+ /*
+ Need to clean up join_tab from TABLEs in case of error.
+ They won't get cleaned up by JOIN::cleanup() because JOIN::join_tab
+ may not be assigned yet by this function (which is building join_tab).
+ Dangling TABLE::reginfo.join_tab may cause part_of_refkey to choke.
+ */
+ {
+ TABLE_LIST *table;
+ List_iterator<TABLE_LIST> ti(tables_list);
+ while ((table= ti++))
+ table->table->reginfo.join_tab= NULL;
+ }
+ DBUG_RETURN (1);
+}
+
+
+/*****************************************************************************
+ Check with keys are used and with tables references with tables
+ Updates in stat:
+ keys Bitmap of all used keys
+ const_keys Bitmap of all keys with may be used with quick_select
+ keyuse Pointer to possible keys
+*****************************************************************************/
+
+/// Used when finding key fields
+typedef struct key_field_t {
+ Field *field;
+ Item *val; ///< May be empty if diff constant
+ uint level;
+ uint optimize;
+ bool eq_func;
+ /**
+ If true, the condition this struct represents will not be satisfied
+ when val IS NULL.
+ */
+ bool null_rejecting;
+ bool *cond_guard; /* See KEYUSE::cond_guard */
+ uint sj_pred_no; /* See KEYUSE::sj_pred_no */
+} KEY_FIELD;
+
+/**
+ Merge new key definitions to old ones, remove those not used in both.
+
+ This is called for OR between different levels.
+
+ That is, the function operates on an array of KEY_FIELD elements which has
+ two parts:
+
+ $LEFT_PART $RIGHT_PART
+ +-----------------------+-----------------------+
+ start new_fields end
+
+ $LEFT_PART and $RIGHT_PART are arrays that have KEY_FIELD elements for two
+ parts of the OR condition. Our task is to produce an array of KEY_FIELD
+ elements that would correspond to "$LEFT_PART OR $RIGHT_PART".
+
+ The rules for combining elements are as follows:
+
+ (keyfieldA1 AND keyfieldA2 AND ...) OR (keyfieldB1 AND keyfieldB2 AND ...)=
+
+ = AND_ij (keyfieldA_i OR keyfieldB_j)
+
+ We discard all (keyfieldA_i OR keyfieldB_j) that refer to different
+ fields. For those referring to the same field, the logic is as follows:
+
+ t.keycol=expr1 OR t.keycol=expr2 -> (since expr1 and expr2 are different
+ we can't produce a single equality,
+ so produce nothing)
+
+ t.keycol=expr1 OR t.keycol=expr1 -> t.keycol=expr1
+
+ t.keycol=expr1 OR t.keycol IS NULL -> t.keycol=expr1, and also set
+ KEY_OPTIMIZE_REF_OR_NULL flag
+
+ The last one is for ref_or_null access. We have handling for this special
+ because it's needed for evaluating IN subqueries that are internally
+ transformed into
+
+ @code
+ EXISTS(SELECT * FROM t1 WHERE t1.key=outer_ref_field or t1.key IS NULL)
+ @endcode
+
+ See add_key_fields() for discussion of what is and_level.
+
+ KEY_FIELD::null_rejecting is processed as follows: @n
+ result has null_rejecting=true if it is set for both ORed references.
+ for example:
+ - (t2.key = t1.field OR t2.key = t1.field) -> null_rejecting=true
+ - (t2.key = t1.field OR t2.key <=> t1.field) -> null_rejecting=false
+
+ @todo
+ The result of this is that we're missing some 'ref' accesses.
+ OptimizerTeam: Fix this
+*/
+
+static KEY_FIELD *
+merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
+ uint and_level)
+{
+ if (start == new_fields)
+ return start; // Impossible or
+ if (new_fields == end)
+ return start; // No new fields, skip all
+
+ KEY_FIELD *first_free=new_fields;
+
+ /* Mark all found fields in old array */
+ for (; new_fields != end ; new_fields++)
+ {
+ for (KEY_FIELD *old=start ; old != first_free ; old++)
+ {
+ if (old->field == new_fields->field)
+ {
+ /*
+ NOTE: below const_item() call really works as "!used_tables()", i.e.
+ it can return FALSE where it is feasible to make it return TRUE.
+
+ The cause is as follows: Some of the tables are already known to be
+ const tables (the detection code is in make_join_statistics(),
+ above the update_ref_and_keys() call), but we didn't propagate
+ information about this: TABLE::const_table is not set to TRUE, and
+ Item::update_used_tables() hasn't been called for each item.
+ The result of this is that we're missing some 'ref' accesses.
+ TODO: OptimizerTeam: Fix this
+ */
+ if (!new_fields->val->const_item())
+ {
+ /*
+ If the value matches, we can use the key reference.
+ If not, we keep it until we have examined all new values
+ */
+ if (old->val->eq(new_fields->val, old->field->binary()))
+ {
+ old->level= and_level;
+ old->optimize= ((old->optimize & new_fields->optimize &
+ KEY_OPTIMIZE_EXISTS) |
+ ((old->optimize | new_fields->optimize) &
+ KEY_OPTIMIZE_REF_OR_NULL));
+ old->null_rejecting= (old->null_rejecting &&
+ new_fields->null_rejecting);
+ }
+ }
+ else if (old->eq_func && new_fields->eq_func &&
+ old->val->eq_by_collation(new_fields->val,
+ old->field->binary(),
+ old->field->charset()))
+
+ {
+ old->level= and_level;
+ old->optimize= ((old->optimize & new_fields->optimize &
+ KEY_OPTIMIZE_EXISTS) |
+ ((old->optimize | new_fields->optimize) &
+ KEY_OPTIMIZE_REF_OR_NULL));
+ old->null_rejecting= (old->null_rejecting &&
+ new_fields->null_rejecting);
+ }
+ else if (old->eq_func && new_fields->eq_func &&
+ ((old->val->const_item() && !old->val->is_expensive() &&
+ old->val->is_null()) ||
+ (!new_fields->val->is_expensive() &&
+ new_fields->val->is_null())))
+ {
+ /* field = expression OR field IS NULL */
+ old->level= and_level;
+ if (old->field->maybe_null())
+ {
+ old->optimize= KEY_OPTIMIZE_REF_OR_NULL;
+ /* The referred expression can be NULL: */
+ old->null_rejecting= 0;
+ }
+ /*
+ Remember the NOT NULL value unless the value does not depend
+ on other tables.
+ */
+ if (!old->val->used_tables() && !old->val->is_expensive() &&
+ old->val->is_null())
+ old->val= new_fields->val;
+ }
+ else
+ {
+ /*
+ We are comparing two different const. In this case we can't
+ use a key-lookup on this so it's better to remove the value
+ and let the range optimzier handle it
+ */
+ if (old == --first_free) // If last item
+ break;
+ *old= *first_free; // Remove old value
+ old--; // Retry this value
+ }
+ }
+ }
+ }
+ /* Remove all not used items */
+ for (KEY_FIELD *old=start ; old != first_free ;)
+ {
+ if (old->level != and_level)
+ { // Not used in all levels
+ if (old == --first_free)
+ break;
+ *old= *first_free; // Remove old value
+ continue;
+ }
+ old++;
+ }
+ return first_free;
+}
+
+
+/*
+ Given a field, return its index in semi-join's select list, or UINT_MAX
+
+ DESCRIPTION
+ Given a field, we find its table; then see if the table is within a
+ semi-join nest and if the field was in select list of the subselect.
+ If it was, we return field's index in the select list. The value is used
+ by LooseScan strategy.
+*/
+
+static uint get_semi_join_select_list_index(Field *field)
+{
+ uint res= UINT_MAX;
+ TABLE_LIST *emb_sj_nest;
+ if ((emb_sj_nest= field->table->pos_in_table_list->embedding) &&
+ emb_sj_nest->sj_on_expr)
+ {
+ Item_in_subselect *subq_pred= emb_sj_nest->sj_subq_pred;
+ st_select_lex *subq_lex= subq_pred->unit->first_select();
+ if (subq_pred->left_expr->cols() == 1)
+ {
+ Item *sel_item= subq_lex->ref_pointer_array[0];
+ if (sel_item->type() == Item::FIELD_ITEM &&
+ ((Item_field*)sel_item)->field->eq(field))
+ {
+ res= 0;
+ }
+ }
+ else
+ {
+ for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
+ {
+ Item *sel_item= subq_lex->ref_pointer_array[i];
+ if (sel_item->type() == Item::FIELD_ITEM &&
+ ((Item_field*)sel_item)->field->eq(field))
+ {
+ res= i;
+ break;
+ }
+ }
+ }
+ }
+ return res;
+}
+
+
+/**
+ Add a possible key to array of possible keys if it's usable as a key
+
+ @param key_fields Pointer to add key, if usable
+ @param and_level And level, to be stored in KEY_FIELD
+ @param cond Condition predicate
+ @param field Field used in comparision
+ @param eq_func True if we used =, <=> or IS NULL
+ @param value Value used for comparison with field
+ @param num_values Number of values[] that we are comparing against
+ @param usable_tables Tables which can be used for key optimization
+ @param sargables IN/OUT Array of found sargable candidates
+
+ @note
+ If we are doing a NOT NULL comparison on a NOT NULL field in a outer join
+ table, we store this to be able to do not exists optimization later.
+
+ @returns
+ *key_fields is incremented if we stored a key in the array
+*/
+
+static void
+add_key_field(JOIN *join,
+ KEY_FIELD **key_fields,uint and_level, Item_func *cond,
+ Field *field, bool eq_func, Item **value, uint num_values,
+ table_map usable_tables, SARGABLE_PARAM **sargables)
+{
+ uint optimize= 0;
+ if (eq_func &&
+ ((join->is_allowed_hash_join_access() &&
+ field->hash_join_is_possible() &&
+ !(field->table->pos_in_table_list->is_materialized_derived() &&
+ field->table->created)) ||
+ (field->table->pos_in_table_list->is_materialized_derived() &&
+ !field->table->created && !(field->flags & BLOB_FLAG))))
+ {
+ optimize= KEY_OPTIMIZE_EQ;
+ }
+ else if (!(field->flags & PART_KEY_FLAG))
+ {
+ // Don't remove column IS NULL on a LEFT JOIN table
+ if (eq_func && (*value)->type() == Item::NULL_ITEM &&
+ field->table->maybe_null && !field->null_ptr)
+ {
+ optimize= KEY_OPTIMIZE_EXISTS;
+ DBUG_ASSERT(num_values == 1);
+ }
+ }
+ if (optimize != KEY_OPTIMIZE_EXISTS)
+ {
+ table_map used_tables=0;
+ bool optimizable=0;
+ for (uint i=0; i<num_values; i++)
+ {
+ used_tables|=(value[i])->used_tables();
+ if (!((value[i])->used_tables() & (field->table->map | RAND_TABLE_BIT)))
+ optimizable=1;
+ }
+ if (!optimizable)
+ return;
+ if (!(usable_tables & field->table->map))
+ {
+ if (!eq_func || (*value)->type() != Item::NULL_ITEM ||
+ !field->table->maybe_null || field->null_ptr)
+ return; // Can't use left join optimize
+ optimize= KEY_OPTIMIZE_EXISTS;
+ }
+ else
+ {
+ JOIN_TAB *stat=field->table->reginfo.join_tab;
+ key_map possible_keys=field->get_possible_keys();
+ possible_keys.intersect(field->table->keys_in_use_for_query);
+ stat[0].keys.merge(possible_keys); // Add possible keys
+
+ /*
+ Save the following cases:
+ Field op constant
+ Field LIKE constant where constant doesn't start with a wildcard
+ Field = field2 where field2 is in a different table
+ Field op formula
+ Field IS NULL
+ Field IS NOT NULL
+ Field BETWEEN ...
+ Field IN ...
+ */
+ if (field->flags & PART_KEY_FLAG)
+ stat[0].key_dependent|=used_tables;
+
+ bool is_const=1;
+ for (uint i=0; i<num_values; i++)
+ {
+ if (!(is_const&= value[i]->const_item()))
+ break;
+ }
+ if (is_const)
+ {
+ stat[0].const_keys.merge(possible_keys);
+ bitmap_set_bit(&field->table->cond_set, field->field_index);
+ }
+ else if (!eq_func)
+ {
+ /*
+ Save info to be able check whether this predicate can be
+ considered as sargable for range analisis after reading const tables.
+ We do not save info about equalities as update_const_equal_items
+ will take care of updating info on keys from sargable equalities.
+ */
+ (*sargables)--;
+ (*sargables)->field= field;
+ (*sargables)->arg_value= value;
+ (*sargables)->num_values= num_values;
+ }
+ if (!eq_func) // eq_func is NEVER true when num_values > 1
+ return;
+
+ if ((*value)->cmp_type() == TIME_RESULT &&
+ field->cmp_type() != TIME_RESULT)
+ return;
+
+ /*
+ Note, for ITEM/ENUM columns:
+ - field->cmp_type() returns INT_RESULT
+ - field->result_type() returns STRING_RESULT
+ - field->type() returns MYSQL_TYPE_STRING
+
+ Using field->real_type() to detect ENUM/SET,
+ as they need a special handling:
+ - Conditions between a ENUM/SET filter and a TIME expression
+ cannot be optimized. They were filtered out in the previous if block.
+ - It's Ok to use ref access for an ENUM/SET field compared to an
+ INT/REAL/DECIMAL expression.
+ - It's Ok to use ref for an ENUM/SET field compared to a STRING
+ expression if the collation of the field and the collation of
+ the condition match.
+ */
+ if ((field->real_type() == MYSQL_TYPE_ENUM ||
+ field->real_type() == MYSQL_TYPE_SET) &&
+ (*value)->cmp_type () == STRING_RESULT &&
+ field->charset() != cond->compare_collation())
+ return;
+
+ /*
+ We can't use indexes when comparing a string index to a
+ number or two strings if the effective collation
+ of the operation differ from the field collation.
+ */
+
+ if (field->cmp_type() == STRING_RESULT)
+ {
+ if ((*value)->cmp_type() != STRING_RESULT)
+ return;
+ if (field->charset() != cond->compare_collation())
+ return;
+ }
+ }
+ }
+ /*
+ For the moment eq_func is always true. This slot is reserved for future
+ extensions where we want to remembers other things than just eq comparisons
+ */
+ DBUG_ASSERT(eq_func);
+ /* Store possible eq field */
+ (*key_fields)->field= field;
+ (*key_fields)->eq_func= eq_func;
+ (*key_fields)->val= *value;
+ (*key_fields)->level= and_level;
+ (*key_fields)->optimize= optimize;
+ /*
+ If the condition has form "tbl.keypart = othertbl.field" and
+ othertbl.field can be NULL, there will be no matches if othertbl.field
+ has NULL value.
+ We use null_rejecting in add_not_null_conds() to add
+ 'othertbl.field IS NOT NULL' to tab->select_cond.
+ */
+ {
+ Item *real= (*value)->real_item();
+ if (((cond->functype() == Item_func::EQ_FUNC) ||
+ (cond->functype() == Item_func::MULT_EQUAL_FUNC)) &&
+ (real->type() == Item::FIELD_ITEM) &&
+ ((Item_field*)real)->field->maybe_null())
+ (*key_fields)->null_rejecting= true;
+ else
+ (*key_fields)->null_rejecting= false;
+ }
+ (*key_fields)->cond_guard= NULL;
+
+ (*key_fields)->sj_pred_no= get_semi_join_select_list_index(field);
+ (*key_fields)++;
+}
+
+/**
+ Add possible keys to array of possible keys originated from a simple
+ predicate.
+
+ @param key_fields Pointer to add key, if usable
+ @param and_level And level, to be stored in KEY_FIELD
+ @param cond Condition predicate
+ @param field Field used in comparision
+ @param eq_func True if we used =, <=> or IS NULL
+ @param value Value used for comparison with field
+ Is NULL for BETWEEN and IN
+ @param usable_tables Tables which can be used for key optimization
+ @param sargables IN/OUT Array of found sargable candidates
+
+ @note
+ If field items f1 and f2 belong to the same multiple equality and
+ a key is added for f1, the the same key is added for f2.
+
+ @returns
+ *key_fields is incremented if we stored a key in the array
+*/
+
+static void
+add_key_equal_fields(JOIN *join, KEY_FIELD **key_fields, uint and_level,
+ Item_func *cond, Item *field_item,
+ bool eq_func, Item **val,
+ uint num_values, table_map usable_tables,
+ SARGABLE_PARAM **sargables)
+{
+ Field *field= ((Item_field *) (field_item->real_item()))->field;
+ add_key_field(join, key_fields, and_level, cond, field,
+ eq_func, val, num_values, usable_tables, sargables);
+ Item_equal *item_equal= field_item->get_item_equal();
+ if (item_equal)
+ {
+ /*
+ Add to the set of possible key values every substitution of
+ the field for an equal field included into item_equal
+ */
+ Item_equal_fields_iterator it(*item_equal);
+ while (it++)
+ {
+ Field *equal_field= it.get_curr_field();
+ if (!field->eq(equal_field))
+ {
+ add_key_field(join, key_fields, and_level, cond, equal_field,
+ eq_func, val, num_values, usable_tables,
+ sargables);
+ }
+ }
+ }
+}
+
+
+/**
+ Check if an expression is a non-outer field.
+
+ Checks if an expression is a field and belongs to the current select.
+
+ @param field Item expression to check
+
+ @return boolean
+ @retval TRUE the expression is a local field
+ @retval FALSE it's something else
+*/
+
+static bool
+is_local_field (Item *field)
+{
+ return field->real_item()->type() == Item::FIELD_ITEM
+ && !(field->used_tables() & OUTER_REF_TABLE_BIT)
+ && !((Item_field *)field->real_item())->get_depended_from();
+}
+
+
+/*
+ In this and other functions, and_level is a number that is ever-growing
+ and is different for the contents of every AND or OR clause. For example,
+ when processing clause
+
+ (a AND b AND c) OR (x AND y)
+
+ we'll have
+ * KEY_FIELD elements for (a AND b AND c) are assigned and_level=1
+ * KEY_FIELD elements for (x AND y) are assigned and_level=2
+ * OR operation is performed, and whatever elements are left after it are
+ assigned and_level=3.
+
+ The primary reason for having and_level attribute is the OR operation which
+ uses and_level to mark KEY_FIELDs that should get into the result of the OR
+ operation
+*/
+
+static void
+add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level,
+ COND *cond, table_map usable_tables,
+ SARGABLE_PARAM **sargables)
+{
+ if (cond->type() == Item_func::COND_ITEM)
+ {
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+ KEY_FIELD *org_key_fields= *key_fields;
+
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ Item *item;
+ while ((item=li++))
+ add_key_fields(join, key_fields, and_level, item, usable_tables,
+ sargables);
+ for (; org_key_fields != *key_fields ; org_key_fields++)
+ org_key_fields->level= *and_level;
+ }
+ else
+ {
+ (*and_level)++;
+ add_key_fields(join, key_fields, and_level, li++, usable_tables,
+ sargables);
+ Item *item;
+ while ((item=li++))
+ {
+ KEY_FIELD *start_key_fields= *key_fields;
+ (*and_level)++;
+ add_key_fields(join, key_fields, and_level, item, usable_tables,
+ sargables);
+ *key_fields=merge_key_fields(org_key_fields,start_key_fields,
+ *key_fields,++(*and_level));
+ }
+ }
+ return;
+ }
+
+ /*
+ Subquery optimization: Conditions that are pushed down into subqueries
+ are wrapped into Item_func_trig_cond. We process the wrapped condition
+ but need to set cond_guard for KEYUSE elements generated from it.
+ */
+ {
+ if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_func*)cond)->functype() == Item_func::TRIG_COND_FUNC)
+ {
+ Item *cond_arg= ((Item_func*)cond)->arguments()[0];
+ if (!join->group_list && !join->order &&
+ join->unit->item &&
+ join->unit->item->substype() == Item_subselect::IN_SUBS &&
+ !join->unit->is_union())
+ {
+ KEY_FIELD *save= *key_fields;
+ add_key_fields(join, key_fields, and_level, cond_arg, usable_tables,
+ sargables);
+ // Indicate that this ref access candidate is for subquery lookup:
+ for (; save != *key_fields; save++)
+ save->cond_guard= ((Item_func_trig_cond*)cond)->get_trig_var();
+ }
+ return;
+ }
+ }
+
+ /* If item is of type 'field op field/constant' add it to key_fields */
+ if (cond->type() != Item::FUNC_ITEM)
+ return;
+ Item_func *cond_func= (Item_func*) cond;
+ switch (cond_func->select_optimize()) {
+ case Item_func::OPTIMIZE_NONE:
+ break;
+ case Item_func::OPTIMIZE_KEY:
+ {
+ Item **values;
+ /*
+ Build list of possible keys for 'a BETWEEN low AND high'.
+ It is handled similar to the equivalent condition
+ 'a >= low AND a <= high':
+ */
+ if (cond_func->functype() == Item_func::BETWEEN)
+ {
+ Item_field *field_item;
+ bool equal_func= FALSE;
+ uint num_values= 2;
+ values= cond_func->arguments();
+
+ bool binary_cmp= (values[0]->real_item()->type() == Item::FIELD_ITEM)
+ ? ((Item_field*)values[0]->real_item())->field->binary()
+ : TRUE;
+
+ /*
+ Additional optimization: If 'low = high':
+ Handle as if the condition was "t.key = low".
+ */
+ if (!((Item_func_between*)cond_func)->negated &&
+ values[1]->eq(values[2], binary_cmp))
+ {
+ equal_func= TRUE;
+ num_values= 1;
+ }
+
+ /*
+ Append keys for 'field <cmp> value[]' if the
+ condition is of the form::
+ '<field> BETWEEN value[1] AND value[2]'
+ */
+ if (is_local_field(values[0]))
+ {
+ field_item= (Item_field *) (values[0]->real_item());
+ add_key_equal_fields(join, key_fields, *and_level, cond_func,
+ field_item, equal_func, &values[1],
+ num_values, usable_tables, sargables);
+ }
+ /*
+ Append keys for 'value[0] <cmp> field' if the
+ condition is of the form:
+ 'value[0] BETWEEN field1 AND field2'
+ */
+ for (uint i= 1; i <= num_values; i++)
+ {
+ if (is_local_field(values[i]))
+ {
+ field_item= (Item_field *) (values[i]->real_item());
+ add_key_equal_fields(join, key_fields, *and_level, cond_func,
+ field_item, equal_func, values,
+ 1, usable_tables, sargables);
+ }
+ }
+ } // if ( ... Item_func::BETWEEN)
+
+ // IN, NE
+ else if (is_local_field (cond_func->key_item()) &&
+ !(cond_func->used_tables() & OUTER_REF_TABLE_BIT))
+ {
+ values= cond_func->arguments()+1;
+ if (cond_func->functype() == Item_func::NE_FUNC &&
+ is_local_field (cond_func->arguments()[1]))
+ values--;
+ DBUG_ASSERT(cond_func->functype() != Item_func::IN_FUNC ||
+ cond_func->argument_count() != 2);
+ add_key_equal_fields(join, key_fields, *and_level, cond_func,
+ (Item_field*) (cond_func->key_item()->real_item()),
+ 0, values,
+ cond_func->argument_count()-1,
+ usable_tables, sargables);
+ }
+ break;
+ }
+ case Item_func::OPTIMIZE_OP:
+ {
+ bool equal_func=(cond_func->functype() == Item_func::EQ_FUNC ||
+ cond_func->functype() == Item_func::EQUAL_FUNC);
+
+ if (is_local_field (cond_func->arguments()[0]))
+ {
+ add_key_equal_fields(join, key_fields, *and_level, cond_func,
+ (Item_field*) (cond_func->arguments()[0])->
+ real_item(),
+ equal_func,
+ cond_func->arguments()+1, 1, usable_tables,
+ sargables);
+ }
+ if (is_local_field (cond_func->arguments()[1]) &&
+ cond_func->functype() != Item_func::LIKE_FUNC)
+ {
+ add_key_equal_fields(join, key_fields, *and_level, cond_func,
+ (Item_field*) (cond_func->arguments()[1])->
+ real_item(),
+ equal_func,
+ cond_func->arguments(),1,usable_tables,
+ sargables);
+ }
+ break;
+ }
+ case Item_func::OPTIMIZE_NULL:
+ /* column_name IS [NOT] NULL */
+ if (is_local_field (cond_func->arguments()[0]) &&
+ !(cond_func->used_tables() & OUTER_REF_TABLE_BIT))
+ {
+ Item *tmp=new Item_null;
+ if (unlikely(!tmp)) // Should never be true
+ return;
+ add_key_equal_fields(join, key_fields, *and_level, cond_func,
+ (Item_field*) (cond_func->arguments()[0])->
+ real_item(),
+ cond_func->functype() == Item_func::ISNULL_FUNC,
+ &tmp, 1, usable_tables, sargables);
+ }
+ break;
+ case Item_func::OPTIMIZE_EQUAL:
+ Item_equal *item_equal= (Item_equal *) cond;
+ Item *const_item= item_equal->get_const();
+ Item_equal_fields_iterator it(*item_equal);
+ if (const_item)
+ {
+ /*
+ For each field field1 from item_equal consider the equality
+ field1=const_item as a condition allowing an index access of the table
+ with field1 by the keys value of field1.
+ */
+ while (it++)
+ {
+ Field *equal_field= it.get_curr_field();
+ add_key_field(join, key_fields, *and_level, cond_func, equal_field,
+ TRUE, &const_item, 1, usable_tables, sargables);
+ }
+ }
+ else
+ {
+ /*
+ Consider all pairs of different fields included into item_equal.
+ For each of them (field1, field1) consider the equality
+ field1=field2 as a condition allowing an index access of the table
+ with field1 by the keys value of field2.
+ */
+ Item_equal_fields_iterator fi(*item_equal);
+ while (fi++)
+ {
+ Field *field= fi.get_curr_field();
+ Item *item;
+ while ((item= it++))
+ {
+ Field *equal_field= it.get_curr_field();
+ if (!field->eq(equal_field))
+ {
+ add_key_field(join, key_fields, *and_level, cond_func, field,
+ TRUE, &item, 1, usable_tables,
+ sargables);
+ }
+ }
+ it.rewind();
+ }
+ }
+ break;
+ }
+}
+
+
+static uint
+max_part_bit(key_part_map bits)
+{
+ uint found;
+ for (found=0; bits & 1 ; found++,bits>>=1) ;
+ return found;
+}
+
+
+/**
+ Add a new keuse to the specified array of KEYUSE objects
+
+ @param[in,out] keyuse_array array of keyuses to be extended
+ @param[in] key_field info on the key use occurrence
+ @param[in] key key number for the keyse to be added
+ @param[in] part key part for the keyuse to be added
+
+ @note
+ The function builds a new KEYUSE object for a key use utilizing the info
+ on the left and right parts of the given key use extracted from the
+ structure key_field, the key number and key part for this key use.
+ The built object is added to the dynamic array keyuse_array.
+
+ @retval 0 the built object is succesfully added
+ @retval 1 otherwise
+*/
+
+static bool
+add_keyuse(DYNAMIC_ARRAY *keyuse_array, KEY_FIELD *key_field,
+ uint key, uint part)
+{
+ KEYUSE keyuse;
+ Field *field= key_field->field;
+
+ keyuse.table= field->table;
+ keyuse.val= key_field->val;
+ keyuse.key= key;
+ if (!is_hash_join_key_no(key))
+ {
+ keyuse.keypart=part;
+ keyuse.keypart_map= (key_part_map) 1 << part;
+ }
+ else
+ {
+ keyuse.keypart= field->field_index;
+ keyuse.keypart_map= (key_part_map) 0;
+ }
+ keyuse.used_tables= key_field->val->used_tables();
+ keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL;
+ keyuse.ref_table_rows= 0;
+ keyuse.null_rejecting= key_field->null_rejecting;
+ keyuse.cond_guard= key_field->cond_guard;
+ keyuse.sj_pred_no= key_field->sj_pred_no;
+ return (insert_dynamic(keyuse_array,(uchar*) &keyuse));
+}
+
+
+/*
+ Add all keys with uses 'field' for some keypart
+ If field->and_level != and_level then only mark key_part as const_part
+
+ RETURN
+ 0 - OK
+ 1 - Out of memory.
+*/
+
+static bool
+add_key_part(DYNAMIC_ARRAY *keyuse_array, KEY_FIELD *key_field)
+{
+ Field *field=key_field->field;
+ TABLE *form= field->table;
+
+ if (key_field->eq_func && !(key_field->optimize & KEY_OPTIMIZE_EXISTS))
+ {
+ for (uint key=0 ; key < form->s->keys ; key++)
+ {
+ if (!(form->keys_in_use_for_query.is_set(key)))
+ continue;
+ if (form->key_info[key].flags & (HA_FULLTEXT | HA_SPATIAL))
+ continue; // ToDo: ft-keys in non-ft queries. SerG
+
+ KEY *keyinfo= form->key_info+key;
+ uint key_parts= form->actual_n_key_parts(keyinfo);
+ for (uint part=0 ; part < key_parts ; part++)
+ {
+ if (field->eq(form->key_info[key].key_part[part].field))
+ {
+ if (add_keyuse(keyuse_array, key_field, key, part))
+ return TRUE;
+ }
+ }
+ }
+ if (field->hash_join_is_possible() &&
+ (key_field->optimize & KEY_OPTIMIZE_EQ) &&
+ key_field->val->used_tables())
+ {
+ /*
+ If a key use is extracted from an equi-join predicate then it is
+ added not only as a key use for every index whose component can
+ be evalusted utilizing this key use, but also as a key use for
+ hash join. Such key uses are marked with a special key number.
+ */
+ if (add_keyuse(keyuse_array, key_field, get_hash_join_key_no(), 0))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+/*
+ A key part number that means we're using a fulltext scan.
+
+ In order not to confuse it with regular equalities, we need to pick
+ a number that's greater than MAX_REF_PARTS.
+
+ Hash Join code stores field->field_index in KEYUSE::keypart, so the
+ number needs to be bigger than MAX_FIELDS, also.
+
+ CAUTION: sql_test.cc has its own definition of FT_KEYPART.
+*/
+#define FT_KEYPART (MAX_FIELDS+10)
+
+static bool
+add_ft_keys(DYNAMIC_ARRAY *keyuse_array,
+ JOIN_TAB *stat,COND *cond,table_map usable_tables)
+{
+ Item_func_match *cond_func=NULL;
+
+ if (!cond)
+ return FALSE;
+
+ if (cond->type() == Item::FUNC_ITEM)
+ {
+ Item_func *func=(Item_func *)cond;
+ Item_func::Functype functype= func->functype();
+ if (functype == Item_func::FT_FUNC)
+ cond_func=(Item_func_match *)cond;
+ else if (func->arg_count == 2)
+ {
+ Item *arg0=(Item *)(func->arguments()[0]),
+ *arg1=(Item *)(func->arguments()[1]);
+ if (arg1->const_item() && arg1->cols() == 1 &&
+ arg0->type() == Item::FUNC_ITEM &&
+ ((Item_func *) arg0)->functype() == Item_func::FT_FUNC &&
+ ((functype == Item_func::GE_FUNC && arg1->val_real() > 0) ||
+ (functype == Item_func::GT_FUNC && arg1->val_real() >=0)))
+ cond_func= (Item_func_match *) arg0;
+ else if (arg0->const_item() && arg0->cols() == 1 &&
+ arg1->type() == Item::FUNC_ITEM &&
+ ((Item_func *) arg1)->functype() == Item_func::FT_FUNC &&
+ ((functype == Item_func::LE_FUNC && arg0->val_real() > 0) ||
+ (functype == Item_func::LT_FUNC && arg0->val_real() >=0)))
+ cond_func= (Item_func_match *) arg1;
+ }
+ }
+ else if (cond->type() == Item::COND_ITEM)
+ {
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ Item *item;
+ while ((item=li++))
+ {
+ if (add_ft_keys(keyuse_array,stat,item,usable_tables))
+ return TRUE;
+ }
+ }
+ }
+
+ if (!cond_func || cond_func->key == NO_SUCH_KEY ||
+ !(usable_tables & cond_func->table->map))
+ return FALSE;
+
+ KEYUSE keyuse;
+ keyuse.table= cond_func->table;
+ keyuse.val = cond_func;
+ keyuse.key = cond_func->key;
+ keyuse.keypart= FT_KEYPART;
+ keyuse.used_tables=cond_func->key_item()->used_tables();
+ keyuse.optimize= 0;
+ keyuse.keypart_map= 0;
+ keyuse.sj_pred_no= UINT_MAX;
+ return insert_dynamic(keyuse_array,(uchar*) &keyuse);
+}
+
+
+static int
+sort_keyuse(KEYUSE *a,KEYUSE *b)
+{
+ int res;
+ if (a->table->tablenr != b->table->tablenr)
+ return (int) (a->table->tablenr - b->table->tablenr);
+ if (a->key != b->key)
+ return (int) (a->key - b->key);
+ if (a->key == MAX_KEY && b->key == MAX_KEY &&
+ a->used_tables != b->used_tables)
+ return (int) ((ulong) a->used_tables - (ulong) b->used_tables);
+ if (a->keypart != b->keypart)
+ return (int) (a->keypart - b->keypart);
+ // Place const values before other ones
+ if ((res= MY_TEST((a->used_tables & ~OUTER_REF_TABLE_BIT)) -
+ MY_TEST((b->used_tables & ~OUTER_REF_TABLE_BIT))))
+ return res;
+ /* Place rows that are not 'OPTIMIZE_REF_OR_NULL' first */
+ return (int) ((a->optimize & KEY_OPTIMIZE_REF_OR_NULL) -
+ (b->optimize & KEY_OPTIMIZE_REF_OR_NULL));
+}
+
+
+/*
+ Add to KEY_FIELD array all 'ref' access candidates within nested join.
+
+ This function populates KEY_FIELD array with entries generated from the
+ ON condition of the given nested join, and does the same for nested joins
+ contained within this nested join.
+
+ @param[in] nested_join_table Nested join pseudo-table to process
+ @param[in,out] end End of the key field array
+ @param[in,out] and_level And-level
+ @param[in,out] sargables Array of found sargable candidates
+
+
+ @note
+ We can add accesses to the tables that are direct children of this nested
+ join (1), and are not inner tables w.r.t their neighbours (2).
+
+ Example for #1 (outer brackets pair denotes nested join this function is
+ invoked for):
+ @code
+ ... LEFT JOIN (t1 LEFT JOIN (t2 ... ) ) ON cond
+ @endcode
+ Example for #2:
+ @code
+ ... LEFT JOIN (t1 LEFT JOIN t2 ) ON cond
+ @endcode
+ In examples 1-2 for condition cond, we can add 'ref' access candidates to
+ t1 only.
+ Example #3:
+ @code
+ ... LEFT JOIN (t1, t2 LEFT JOIN t3 ON inner_cond) ON cond
+ @endcode
+ Here we can add 'ref' access candidates for t1 and t2, but not for t3.
+*/
+
+static void add_key_fields_for_nj(JOIN *join, TABLE_LIST *nested_join_table,
+ KEY_FIELD **end, uint *and_level,
+ SARGABLE_PARAM **sargables)
+{
+ List_iterator<TABLE_LIST> li(nested_join_table->nested_join->join_list);
+ List_iterator<TABLE_LIST> li2(nested_join_table->nested_join->join_list);
+ bool have_another = FALSE;
+ table_map tables= 0;
+ TABLE_LIST *table;
+ DBUG_ASSERT(nested_join_table->nested_join);
+
+ while ((table= li++) || (have_another && (li=li2, have_another=FALSE,
+ (table= li++))))
+ {
+ if (table->nested_join)
+ {
+ if (!table->on_expr)
+ {
+ /* It's a semi-join nest. Walk into it as if it wasn't a nest */
+ have_another= TRUE;
+ li2= li;
+ li= List_iterator<TABLE_LIST>(table->nested_join->join_list);
+ }
+ else
+ add_key_fields_for_nj(join, table, end, and_level, sargables);
+ }
+ else
+ if (!table->on_expr)
+ tables |= table->table->map;
+ }
+ if (nested_join_table->on_expr)
+ add_key_fields(join, end, and_level, nested_join_table->on_expr, tables,
+ sargables);
+}
+
+
+void count_cond_for_nj(SELECT_LEX *sel, TABLE_LIST *nested_join_table)
+{
+ List_iterator<TABLE_LIST> li(nested_join_table->nested_join->join_list);
+ List_iterator<TABLE_LIST> li2(nested_join_table->nested_join->join_list);
+ bool have_another = FALSE;
+ TABLE_LIST *table;
+
+ while ((table= li++) || (have_another && (li=li2, have_another=FALSE,
+ (table= li++))))
+ if (table->nested_join)
+ {
+ if (!table->on_expr)
+ {
+ /* It's a semi-join nest. Walk into it as if it wasn't a nest */
+ have_another= TRUE;
+ li2= li;
+ li= List_iterator<TABLE_LIST>(table->nested_join->join_list);
+ }
+ else
+ count_cond_for_nj(sel, table);
+ }
+ if (nested_join_table->on_expr)
+ nested_join_table->on_expr->walk(&Item::count_sargable_conds,
+ 0, (uchar*) sel);
+
+}
+
+/**
+ Update keyuse array with all possible keys we can use to fetch rows.
+
+ @param thd
+ @param[out] keyuse Put here ordered array of KEYUSE structures
+ @param join_tab Array in tablenr_order
+ @param tables Number of tables in join
+ @param cond WHERE condition (note that the function analyzes
+ join_tab[i]->on_expr too)
+ @param normal_tables Tables not inner w.r.t some outer join (ones
+ for which we can make ref access based the WHERE
+ clause)
+ @param select_lex current SELECT
+ @param[out] sargables Array of found sargable candidates
+
+ @retval
+ 0 OK
+ @retval
+ 1 Out of memory.
+*/
+
+static bool
+update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
+ uint tables, COND *cond, table_map normal_tables,
+ SELECT_LEX *select_lex, SARGABLE_PARAM **sargables)
+{
+ uint and_level,i;
+ KEY_FIELD *key_fields, *end, *field;
+ uint sz;
+ uint m= MY_MAX(select_lex->max_equal_elems,1);
+
+ SELECT_LEX *sel=thd->lex->current_select;
+ sel->cond_count= 0;
+ sel->between_count= 0;
+ if (cond)
+ cond->walk(&Item::count_sargable_conds, 0, (uchar*) sel);
+ for (i=0 ; i < tables ; i++)
+ {
+ if (*join_tab[i].on_expr_ref)
+ (*join_tab[i].on_expr_ref)->walk(&Item::count_sargable_conds,
+ 0, (uchar*) sel);
+ }
+ {
+ List_iterator<TABLE_LIST> li(*join_tab->join->join_list);
+ TABLE_LIST *table;
+ while ((table= li++))
+ {
+ if (table->nested_join)
+ count_cond_for_nj(sel, table);
+ }
+ }
+
+ /*
+ We use the same piece of memory to store both KEY_FIELD
+ and SARGABLE_PARAM structure.
+ KEY_FIELD values are placed at the beginning this memory
+ while SARGABLE_PARAM values are put at the end.
+ All predicates that are used to fill arrays of KEY_FIELD
+ and SARGABLE_PARAM structures have at most 2 arguments
+ except BETWEEN predicates that have 3 arguments and
+ IN predicates.
+ This any predicate if it's not BETWEEN/IN can be used
+ directly to fill at most 2 array elements, either of KEY_FIELD
+ or SARGABLE_PARAM type. For a BETWEEN predicate 3 elements
+ can be filled as this predicate is considered as
+ saragable with respect to each of its argument.
+ An IN predicate can require at most 1 element as currently
+ it is considered as sargable only for its first argument.
+ Multiple equality can add elements that are filled after
+ substitution of field arguments by equal fields. There
+ can be not more than select_lex->max_equal_elems such
+ substitutions.
+ */
+ sz= MY_MAX(sizeof(KEY_FIELD),sizeof(SARGABLE_PARAM))*
+ ((sel->cond_count*2 + sel->between_count)*m+1);
+ if (!(key_fields=(KEY_FIELD*) thd->alloc(sz)))
+ return TRUE; /* purecov: inspected */
+ and_level= 0;
+ field= end= key_fields;
+ *sargables= (SARGABLE_PARAM *) key_fields +
+ (sz - sizeof((*sargables)[0].field))/sizeof(SARGABLE_PARAM);
+ /* set a barrier for the array of SARGABLE_PARAM */
+ (*sargables)[0].field= 0;
+
+ if (my_init_dynamic_array(keyuse,sizeof(KEYUSE),20,64,
+ MYF(MY_THREAD_SPECIFIC)))
+ return TRUE;
+
+ if (cond)
+ {
+ KEY_FIELD *saved_field= field;
+ add_key_fields(join_tab->join, &end, &and_level, cond, normal_tables,
+ sargables);
+ for (; field != end ; field++)
+ {
+
+ /* Mark that we can optimize LEFT JOIN */
+ if (field->val->type() == Item::NULL_ITEM &&
+ !field->field->real_maybe_null())
+ field->field->table->reginfo.not_exists_optimize=1;
+ }
+ field= saved_field;
+ }
+ for (i=0 ; i < tables ; i++)
+ {
+ /*
+ Block the creation of keys for inner tables of outer joins.
+ Here only the outer joins that can not be converted to
+ inner joins are left and all nests that can be eliminated
+ are flattened.
+ In the future when we introduce conditional accesses
+ for inner tables in outer joins these keys will be taken
+ into account as well.
+ */
+ if (*join_tab[i].on_expr_ref)
+ add_key_fields(join_tab->join, &end, &and_level,
+ *join_tab[i].on_expr_ref,
+ join_tab[i].table->map, sargables);
+ }
+
+ /* Process ON conditions for the nested joins */
+ {
+ List_iterator<TABLE_LIST> li(*join_tab->join->join_list);
+ TABLE_LIST *table;
+ while ((table= li++))
+ {
+ if (table->nested_join)
+ add_key_fields_for_nj(join_tab->join, table, &end, &and_level,
+ sargables);
+ }
+ }
+
+ /* fill keyuse with found key parts */
+ for ( ; field != end ; field++)
+ {
+ if (add_key_part(keyuse,field))
+ return TRUE;
+ }
+
+ if (select_lex->ftfunc_list->elements)
+ {
+ if (add_ft_keys(keyuse,join_tab,cond,normal_tables))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/**
+ Sort the array of possible keys and remove the following key parts:
+ - ref if there is a keypart which is a ref and a const.
+ (e.g. if there is a key(a,b) and the clause is a=3 and b=7 and b=t2.d,
+ then we skip the key part corresponding to b=t2.d)
+ - keyparts without previous keyparts
+ (e.g. if there is a key(a,b,c) but only b < 5 (or a=2 and c < 3) is
+ used in the query, we drop the partial key parts from consideration).
+ Special treatment for ft-keys.
+*/
+
+static bool sort_and_filter_keyuse(THD *thd, DYNAMIC_ARRAY *keyuse,
+ bool skip_unprefixed_keyparts)
+{
+ KEYUSE key_end, *prev, *save_pos, *use;
+ uint found_eq_constant, i;
+
+ DBUG_ASSERT(keyuse->elements);
+
+ my_qsort(keyuse->buffer, keyuse->elements, sizeof(KEYUSE),
+ (qsort_cmp) sort_keyuse);
+
+ bzero((char*) &key_end, sizeof(key_end)); /* Add for easy testing */
+ if (insert_dynamic(keyuse, (uchar*) &key_end))
+ return TRUE;
+
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_DERIVED_WITH_KEYS))
+ generate_derived_keys(keyuse);
+
+ use= save_pos= dynamic_element(keyuse,0,KEYUSE*);
+ prev= &key_end;
+ found_eq_constant= 0;
+ for (i=0 ; i < keyuse->elements-1 ; i++,use++)
+ {
+ if (!use->is_for_hash_join())
+ {
+ if (!(use->used_tables & ~OUTER_REF_TABLE_BIT) &&
+ use->optimize != KEY_OPTIMIZE_REF_OR_NULL)
+ use->table->const_key_parts[use->key]|= use->keypart_map;
+ if (use->keypart != FT_KEYPART)
+ {
+ if (use->key == prev->key && use->table == prev->table)
+ {
+ if ((prev->keypart+1 < use->keypart && skip_unprefixed_keyparts) ||
+ (prev->keypart == use->keypart && found_eq_constant))
+ continue; /* remove */
+ }
+ else if (use->keypart != 0 && skip_unprefixed_keyparts)
+ continue; /* remove - first found must be 0 */
+ }
+
+ prev= use;
+ found_eq_constant= !use->used_tables;
+ use->table->reginfo.join_tab->checked_keys.set_bit(use->key);
+ }
+ /*
+ Old gcc used a memcpy(), which is undefined if save_pos==use:
+ http://gcc.gnu.org/bugzilla/show_bug.cgi?id=19410
+ http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39480
+ This also disables a valgrind warning, so better to have the test.
+ */
+ if (save_pos != use)
+ *save_pos= *use;
+ /* Save ptr to first use */
+ if (!use->table->reginfo.join_tab->keyuse)
+ use->table->reginfo.join_tab->keyuse= save_pos;
+ save_pos++;
+ }
+ i= (uint) (save_pos-(KEYUSE*) keyuse->buffer);
+ (void) set_dynamic(keyuse,(uchar*) &key_end,i);
+ keyuse->elements= i;
+
+ return FALSE;
+}
+
+
+/**
+ Update some values in keyuse for faster choose_plan() loop.
+*/
+
+static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array)
+{
+ KEYUSE *end,*keyuse= dynamic_element(keyuse_array, 0, KEYUSE*);
+
+ for (end= keyuse+ keyuse_array->elements ; keyuse < end ; keyuse++)
+ {
+ table_map map;
+ /*
+ If we find a ref, assume this table matches a proportional
+ part of this table.
+ For example 100 records matching a table with 5000 records
+ gives 5000/100 = 50 records per key
+ Constant tables are ignored.
+ To avoid bad matches, we don't make ref_table_rows less than 100.
+ */
+ keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref
+ if (keyuse->used_tables &
+ (map= (keyuse->used_tables & ~join->const_table_map &
+ ~OUTER_REF_TABLE_BIT)))
+ {
+ uint n_tables= my_count_bits(map);
+ if (n_tables == 1) // Only one table
+ {
+ Table_map_iterator it(map);
+ int tablenr= it.next_bit();
+ DBUG_ASSERT(tablenr != Table_map_iterator::BITMAP_END);
+ TABLE *tmp_table=join->table[tablenr];
+ if (tmp_table) // already created
+ keyuse->ref_table_rows= MY_MAX(tmp_table->file->stats.records, 100);
+ }
+ }
+ /*
+ Outer reference (external field) is constant for single executing
+ of subquery
+ */
+ if (keyuse->used_tables == OUTER_REF_TABLE_BIT)
+ keyuse->ref_table_rows= 1;
+ }
+}
+
+
+
+/**
+ Check for the presence of AGGFN(DISTINCT a) queries that may be subject
+ to loose index scan.
+
+
+ Check if the query is a subject to AGGFN(DISTINCT) using loose index scan
+ (QUICK_GROUP_MIN_MAX_SELECT).
+ Optionally (if out_args is supplied) will push the arguments of
+ AGGFN(DISTINCT) to the list
+
+ Check for every COUNT(DISTINCT), AVG(DISTINCT) or
+ SUM(DISTINCT). These can be resolved by Loose Index Scan as long
+ as all the aggregate distinct functions refer to the same
+ fields. Thus:
+
+ SELECT AGGFN(DISTINCT a, b), AGGFN(DISTINCT b, a)... => can use LIS
+ SELECT AGGFN(DISTINCT a), AGGFN(DISTINCT a) ... => can use LIS
+ SELECT AGGFN(DISTINCT a, b), AGGFN(DISTINCT a) ... => cannot use LIS
+ SELECT AGGFN(DISTINCT a), AGGFN(DISTINCT b) ... => cannot use LIS
+ etc.
+
+ @param join the join to check
+ @param[out] out_args Collect the arguments of the aggregate functions
+ to a list. We don't worry about duplicates as
+ these will be sorted out later in
+ get_best_group_min_max.
+
+ @return does the query qualify for indexed AGGFN(DISTINCT)
+ @retval true it does
+ @retval false AGGFN(DISTINCT) must apply distinct in it.
+*/
+
+bool
+is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args)
+{
+ Item_sum **sum_item_ptr;
+ bool result= false;
+ Field_map first_aggdistinct_fields;
+
+ if (join->table_count != 1 || /* reference more than 1 table */
+ join->select_distinct || /* or a DISTINCT */
+ join->select_lex->olap == ROLLUP_TYPE) /* Check (B3) for ROLLUP */
+ return false;
+
+ if (join->make_sum_func_list(join->all_fields, join->fields_list, true))
+ return false;
+
+ for (sum_item_ptr= join->sum_funcs; *sum_item_ptr; sum_item_ptr++)
+ {
+ Item_sum *sum_item= *sum_item_ptr;
+ Field_map cur_aggdistinct_fields;
+ Item *expr;
+ /* aggregate is not AGGFN(DISTINCT) or more than 1 argument to it */
+ switch (sum_item->sum_func())
+ {
+ case Item_sum::MIN_FUNC:
+ case Item_sum::MAX_FUNC:
+ continue;
+ case Item_sum::COUNT_DISTINCT_FUNC:
+ break;
+ case Item_sum::AVG_DISTINCT_FUNC:
+ case Item_sum::SUM_DISTINCT_FUNC:
+ if (sum_item->get_arg_count() == 1)
+ break;
+ /* fall through */
+ default: return false;
+ }
+ /*
+ We arrive here for every COUNT(DISTINCT),AVG(DISTINCT) or SUM(DISTINCT).
+ Collect the arguments of the aggregate functions to a list.
+ We don't worry about duplicates as these will be sorted out later in
+ get_best_group_min_max
+ */
+ for (uint i= 0; i < sum_item->get_arg_count(); i++)
+ {
+ expr= sum_item->get_arg(i);
+ /* The AGGFN(DISTINCT) arg is not an attribute? */
+ if (expr->real_item()->type() != Item::FIELD_ITEM)
+ return false;
+
+ Item_field* item= static_cast<Item_field*>(expr->real_item());
+ if (out_args)
+ out_args->push_back(item);
+
+ cur_aggdistinct_fields.set_bit(item->field->field_index);
+ result= true;
+ }
+ /*
+ If there are multiple aggregate functions, make sure that they all
+ refer to exactly the same set of columns.
+ */
+ if (first_aggdistinct_fields.is_clear_all())
+ first_aggdistinct_fields.merge(cur_aggdistinct_fields);
+ else if (first_aggdistinct_fields != cur_aggdistinct_fields)
+ return false;
+ }
+
+ return result;
+}
+
+
+/**
+ Discover the indexes that can be used for GROUP BY or DISTINCT queries.
+
+ If the query has a GROUP BY clause, find all indexes that contain all
+ GROUP BY fields, and add those indexes to join->const_keys.
+
+ If the query has a DISTINCT clause, find all indexes that contain all
+ SELECT fields, and add those indexes to join->const_keys.
+ This allows later on such queries to be processed by a
+ QUICK_GROUP_MIN_MAX_SELECT.
+
+ @param join
+ @param join_tab
+
+ @return
+ None
+*/
+
+static void
+add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab)
+{
+ List<Item_field> indexed_fields;
+ List_iterator<Item_field> indexed_fields_it(indexed_fields);
+ ORDER *cur_group;
+ Item_field *cur_item;
+ key_map possible_keys(0);
+
+ if (join->group_list)
+ { /* Collect all query fields referenced in the GROUP clause. */
+ for (cur_group= join->group_list; cur_group; cur_group= cur_group->next)
+ (*cur_group->item)->walk(&Item::collect_item_field_processor, 0,
+ (uchar*) &indexed_fields);
+ }
+ else if (join->select_distinct)
+ { /* Collect all query fields referenced in the SELECT clause. */
+ List<Item> &select_items= join->fields_list;
+ List_iterator<Item> select_items_it(select_items);
+ Item *item;
+ while ((item= select_items_it++))
+ item->walk(&Item::collect_item_field_processor, 0,
+ (uchar*) &indexed_fields);
+ }
+ else if (is_indexed_agg_distinct(join, &indexed_fields))
+ {
+ join->sort_and_group= 1;
+ }
+ else
+ return;
+
+ if (indexed_fields.elements == 0)
+ return;
+
+ /* Intersect the keys of all group fields. */
+ cur_item= indexed_fields_it++;
+ possible_keys.merge(cur_item->field->part_of_key);
+ while ((cur_item= indexed_fields_it++))
+ {
+ possible_keys.intersect(cur_item->field->part_of_key);
+ }
+
+ if (!possible_keys.is_clear_all())
+ join_tab->const_keys.merge(possible_keys);
+}
+
+
+/*****************************************************************************
+ Go through all combinations of not marked tables and find the one
+ which uses least records
+*****************************************************************************/
+
+/** Save const tables first as used tables. */
+
+void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
+{
+ join->positions[idx].table= table;
+ join->positions[idx].key=key;
+ join->positions[idx].records_read=1.0; /* This is a const table */
+ join->positions[idx].cond_selectivity= 1.0;
+ join->positions[idx].ref_depend_map= 0;
+
+// join->positions[idx].loosescan_key= MAX_KEY; /* Not a LooseScan */
+ join->positions[idx].sj_strategy= SJ_OPT_NONE;
+ join->positions[idx].use_join_buffer= FALSE;
+
+ /* Move the const table as down as possible in best_ref */
+ JOIN_TAB **pos=join->best_ref+idx+1;
+ JOIN_TAB *next=join->best_ref[idx];
+ for (;next != table ; pos++)
+ {
+ JOIN_TAB *tmp=pos[0];
+ pos[0]=next;
+ next=tmp;
+ }
+ join->best_ref[idx]=table;
+}
+
+
+/*
+ Estimate how many records we will get if we read just this table and apply
+ a part of WHERE that can be checked for it.
+
+ @detail
+ Estimate how many records we will get if we
+ - read the given table with its "independent" access method (either quick
+ select or full table/index scan),
+ - apply the part of WHERE that refers only to this table.
+
+ @seealso
+ table_cond_selectivity() produces selectivity of condition that is checked
+ after joining rows from this table to rows from preceding tables.
+*/
+
+inline
+double matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint,
+ uint use_cond_selectivity)
+{
+ ha_rows records;
+ double dbl_records;
+
+ if (use_cond_selectivity > 1)
+ {
+ TABLE *table= s->table;
+ double sel= table->cond_selectivity;
+ double table_records= table->stat_records();
+ dbl_records= table_records * sel;
+ return dbl_records;
+ }
+
+ records = s->found_records;
+
+ /*
+ If there is a filtering condition on the table (i.e. ref analyzer found
+ at least one "table.keyXpartY= exprZ", where exprZ refers only to tables
+ preceding this table in the join order we're now considering), then
+ assume that 25% of the rows will be filtered out by this condition.
+
+ This heuristic is supposed to force tables used in exprZ to be before
+ this table in join order.
+ */
+ if (with_found_constraint)
+ records-= records/4;
+
+ /*
+ If applicable, get a more accurate estimate. Don't use the two
+ heuristics at once.
+ */
+ if (s->table->quick_condition_rows != s->found_records)
+ records= s->table->quick_condition_rows;
+
+ dbl_records= records;
+ return dbl_records;
+}
+
+
+/**
+ Find the best access path for an extension of a partial execution
+ plan and add this path to the plan.
+
+ The function finds the best access path to table 's' from the passed
+ partial plan where an access path is the general term for any means to
+ access the data in 's'. An access path may use either an index or a scan,
+ whichever is cheaper. The input partial plan is passed via the array
+ 'join->positions' of length 'idx'. The chosen access method for 's' and its
+ cost are stored in 'join->positions[idx]'.
+
+ @param join pointer to the structure providing all context info
+ for the query
+ @param s the table to be joined by the function
+ @param thd thread for the connection that submitted the query
+ @param remaining_tables set of tables not included into the partial plan yet
+ @param idx the length of the partial plan
+ @param disable_jbuf TRUE<=> Don't use join buffering
+ @param record_count estimate for the number of records returned by the
+ partial plan
+ @param pos OUT Table access plan
+ @param loose_scan_pos OUT Table plan that uses loosescan, or set cost to
+ DBL_MAX if not possible.
+
+ @return
+ None
+*/
+
+void
+best_access_path(JOIN *join,
+ JOIN_TAB *s,
+ table_map remaining_tables,
+ uint idx,
+ bool disable_jbuf,
+ double record_count,
+ POSITION *pos,
+ POSITION *loose_scan_pos)
+{
+ THD *thd= join->thd;
+ uint use_cond_selectivity= thd->variables.optimizer_use_condition_selectivity;
+ KEYUSE *best_key= 0;
+ uint best_max_key_part= 0;
+ my_bool found_constraint= 0;
+ double best= DBL_MAX;
+ double best_time= DBL_MAX;
+ double records= DBL_MAX;
+ table_map best_ref_depends_map= 0;
+ double tmp;
+ ha_rows rec;
+ bool best_uses_jbuf= FALSE;
+ MY_BITMAP *eq_join_set= &s->table->eq_join_set;
+ KEYUSE *hj_start_key= 0;
+
+ disable_jbuf= disable_jbuf || idx == join->const_tables;
+
+ Loose_scan_opt loose_scan_opt;
+ DBUG_ENTER("best_access_path");
+
+ bitmap_clear_all(eq_join_set);
+
+ loose_scan_opt.init(join, s, remaining_tables);
+
+ if (s->keyuse)
+ { /* Use key if possible */
+ KEYUSE *keyuse;
+ KEYUSE *start_key=0;
+ TABLE *table= s->table;
+ double best_records= DBL_MAX;
+ uint max_key_part=0;
+
+ /* Test how we can use keys */
+ rec= s->records/MATCHING_ROWS_IN_OTHER_TABLE; // Assumed records/key
+ for (keyuse=s->keyuse ; keyuse->table == table ;)
+ {
+ KEY *keyinfo;
+ ulong key_flags;
+ uint key_parts;
+ key_part_map found_part= 0;
+ table_map found_ref= 0;
+ uint key= keyuse->key;
+ bool ft_key= (keyuse->keypart == FT_KEYPART);
+ /* Bitmap of keyparts where the ref access is over 'keypart=const': */
+ key_part_map const_part= 0;
+ /* The or-null keypart in ref-or-null access: */
+ key_part_map ref_or_null_part= 0;
+ if (is_hash_join_key_no(key))
+ {
+ /*
+ Hash join as any join employing join buffer can be used to join
+ only those tables that are joined after the first non const table
+ */
+ if (!(remaining_tables & keyuse->used_tables) &&
+ idx > join->const_tables)
+ {
+ if (!hj_start_key)
+ hj_start_key= keyuse;
+ bitmap_set_bit(eq_join_set, keyuse->keypart);
+ }
+ keyuse++;
+ continue;
+ }
+
+ keyinfo= table->key_info+key;
+ key_parts= table->actual_n_key_parts(keyinfo);
+ key_flags= table->actual_key_flags(keyinfo);
+
+ /* Calculate how many key segments of the current key we can use */
+ start_key= keyuse;
+
+ loose_scan_opt.next_ref_key();
+ DBUG_PRINT("info", ("Considering ref access on key %s",
+ keyuse->table->key_info[keyuse->key].name));
+
+ do /* For each keypart */
+ {
+ uint keypart= keyuse->keypart;
+ table_map best_part_found_ref= 0;
+ double best_prev_record_reads= DBL_MAX;
+
+ do /* For each way to access the keypart */
+ {
+ /*
+ if 1. expression doesn't refer to forward tables
+ 2. we won't get two ref-or-null's
+ */
+ if (!(remaining_tables & keyuse->used_tables) &&
+ s->access_from_tables_is_allowed(keyuse->used_tables,
+ join->sjm_lookup_tables) &&
+ !(ref_or_null_part && (keyuse->optimize &
+ KEY_OPTIMIZE_REF_OR_NULL)))
+ {
+ found_part|= keyuse->keypart_map;
+ if (!(keyuse->used_tables & ~join->const_table_map))
+ const_part|= keyuse->keypart_map;
+
+ double tmp2= prev_record_reads(join->positions, idx,
+ (found_ref | keyuse->used_tables));
+ if (tmp2 < best_prev_record_reads)
+ {
+ best_part_found_ref= keyuse->used_tables & ~join->const_table_map;
+ best_prev_record_reads= tmp2;
+ }
+ if (rec > keyuse->ref_table_rows)
+ rec= keyuse->ref_table_rows;
+ /*
+ If there is one 'key_column IS NULL' expression, we can
+ use this ref_or_null optimisation of this field
+ */
+ if (keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)
+ ref_or_null_part |= keyuse->keypart_map;
+ }
+ loose_scan_opt.add_keyuse(remaining_tables, keyuse);
+ keyuse++;
+ } while (keyuse->table == table && keyuse->key == key &&
+ keyuse->keypart == keypart);
+ found_ref|= best_part_found_ref;
+ } while (keyuse->table == table && keyuse->key == key);
+
+ /*
+ Assume that that each key matches a proportional part of table.
+ */
+ if (!found_part && !ft_key && !loose_scan_opt.have_a_case())
+ continue; // Nothing usable found
+
+ if (rec < MATCHING_ROWS_IN_OTHER_TABLE)
+ rec= MATCHING_ROWS_IN_OTHER_TABLE; // Fix for small tables
+
+ /*
+ ft-keys require special treatment
+ */
+ if (ft_key)
+ {
+ /*
+ Really, there should be records=0.0 (yes!)
+ but 1.0 would be probably safer
+ */
+ tmp= prev_record_reads(join->positions, idx, found_ref);
+ records= 1.0;
+ }
+ else
+ {
+ found_constraint= MY_TEST(found_part);
+ loose_scan_opt.check_ref_access_part1(s, key, start_key, found_part);
+
+ /* Check if we found full key */
+ if (found_part == PREV_BITS(uint, key_parts) &&
+ !ref_or_null_part)
+ { /* use eq key */
+ max_key_part= (uint) ~0;
+ if ((key_flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME ||
+ MY_TEST(key_flags & HA_EXT_NOSAME))
+ {
+ tmp = prev_record_reads(join->positions, idx, found_ref);
+ records=1.0;
+ }
+ else
+ {
+ if (!found_ref)
+ { /* We found a const key */
+ /*
+ ReuseRangeEstimateForRef-1:
+ We get here if we've found a ref(const) (c_i are constants):
+ "(keypart1=c1) AND ... AND (keypartN=cN)" [ref_const_cond]
+
+ If range optimizer was able to construct a "range"
+ access on this index, then its condition "quick_cond" was
+ eqivalent to ref_const_cond (*), and we can re-use E(#rows)
+ from the range optimizer.
+
+ Proof of (*): By properties of range and ref optimizers
+ quick_cond will be equal or tighther than ref_const_cond.
+ ref_const_cond already covers "smallest" possible interval -
+ a singlepoint interval over all keyparts. Therefore,
+ quick_cond is equivalent to ref_const_cond (if it was an
+ empty interval we wouldn't have got here).
+ */
+ if (table->quick_keys.is_set(key))
+ records= (double) table->quick_rows[key];
+ else
+ {
+ /* quick_range couldn't use key! */
+ records= (double) s->records/rec;
+ }
+ }
+ else
+ {
+ uint key_parts= table->actual_n_key_parts(keyinfo);
+ if (!(records= keyinfo->actual_rec_per_key(key_parts-1)))
+ { /* Prefer longer keys */
+ records=
+ ((double) s->records / (double) rec *
+ (1.0 +
+ ((double) (table->s->max_key_length-keyinfo->key_length) /
+ (double) table->s->max_key_length)));
+ if (records < 2.0)
+ records=2.0; /* Can't be as good as a unique */
+ }
+ /*
+ ReuseRangeEstimateForRef-2: We get here if we could not reuse
+ E(#rows) from range optimizer. Make another try:
+
+ If range optimizer produced E(#rows) for a prefix of the ref
+ access we're considering, and that E(#rows) is lower then our
+ current estimate, make an adjustment. The criteria of when we
+ can make an adjustment is a special case of the criteria used
+ in ReuseRangeEstimateForRef-3.
+ */
+ if (table->quick_keys.is_set(key) &&
+ (const_part &
+ (((key_part_map)1 << table->quick_key_parts[key])-1)) ==
+ (((key_part_map)1 << table->quick_key_parts[key])-1) &&
+ table->quick_n_ranges[key] == 1 &&
+ records > (double) table->quick_rows[key])
+ {
+ records= (double) table->quick_rows[key];
+ }
+ }
+ /* Limit the number of matched rows */
+ tmp= records;
+ set_if_smaller(tmp, (double) thd->variables.max_seeks_for_key);
+ if (table->covering_keys.is_set(key))
+ tmp= table->file->keyread_time(key, 1, (ha_rows) tmp);
+ else
+ tmp= table->file->read_time(key, 1,
+ (ha_rows) MY_MIN(tmp,s->worst_seeks));
+ tmp*= record_count;
+ }
+ }
+ else
+ {
+ /*
+ Use as much key-parts as possible and a uniq key is better
+ than a not unique key
+ Set tmp to (previous record count) * (records / combination)
+ */
+ if ((found_part & 1) &&
+ (!(table->file->index_flags(key, 0, 0) & HA_ONLY_WHOLE_INDEX) ||
+ found_part == PREV_BITS(uint,keyinfo->user_defined_key_parts)))
+ {
+ max_key_part= max_part_bit(found_part);
+ /*
+ ReuseRangeEstimateForRef-3:
+ We're now considering a ref[or_null] access via
+ (t.keypart1=e1 AND ... AND t.keypartK=eK) [ OR
+ (same-as-above but with one cond replaced
+ with "t.keypart_i IS NULL")] (**)
+
+ Try re-using E(#rows) from "range" optimizer:
+ We can do so if "range" optimizer used the same intervals as
+ in (**). The intervals used by range optimizer may be not
+ available at this point (as "range" access might have choosen to
+ create quick select over another index), so we can't compare
+ them to (**). We'll make indirect judgements instead.
+ The sufficient conditions for re-use are:
+ (C1) All e_i in (**) are constants, i.e. found_ref==FALSE. (if
+ this is not satisfied we have no way to know which ranges
+ will be actually scanned by 'ref' until we execute the
+ join)
+ (C2) max #key parts in 'range' access == K == max_key_part (this
+ is apparently a necessary requirement)
+
+ We also have a property that "range optimizer produces equal or
+ tighter set of scan intervals than ref(const) optimizer". Each
+ of the intervals in (**) are "tightest possible" intervals when
+ one limits itself to using keyparts 1..K (which we do in #2).
+ From here it follows that range access used either one, or
+ both of the (I1) and (I2) intervals:
+
+ (t.keypart1=c1 AND ... AND t.keypartK=eK) (I1)
+ (same-as-above but with one cond replaced
+ with "t.keypart_i IS NULL") (I2)
+
+ The remaining part is to exclude the situation where range
+ optimizer used one interval while we're considering
+ ref-or-null and looking for estimate for two intervals. This
+ is done by last limitation:
+
+ (C3) "range optimizer used (have ref_or_null?2:1) intervals"
+ */
+ if (table->quick_keys.is_set(key) && !found_ref && //(C1)
+ table->quick_key_parts[key] == max_key_part && //(C2)
+ table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part)) //(C3)
+ {
+ tmp= records= (double) table->quick_rows[key];
+ }
+ else
+ {
+ /* Check if we have statistic about the distribution */
+ if ((records= keyinfo->actual_rec_per_key(max_key_part-1)))
+ {
+ /*
+ Fix for the case where the index statistics is too
+ optimistic: If
+ (1) We're considering ref(const) and there is quick select
+ on the same index,
+ (2) and that quick select uses more keyparts (i.e. it will
+ scan equal/smaller interval then this ref(const))
+ (3) and E(#rows) for quick select is higher then our
+ estimate,
+ Then
+ We'll use E(#rows) from quick select.
+
+ Q: Why do we choose to use 'ref'? Won't quick select be
+ cheaper in some cases ?
+ TODO: figure this out and adjust the plan choice if needed.
+ */
+ if (!found_ref && table->quick_keys.is_set(key) && // (1)
+ table->quick_key_parts[key] > max_key_part && // (2)
+ records < (double)table->quick_rows[key]) // (3)
+ records= (double)table->quick_rows[key];
+
+ tmp= records;
+ }
+ else
+ {
+ /*
+ Assume that the first key part matches 1% of the file
+ and that the whole key matches 10 (duplicates) or 1
+ (unique) records.
+ Assume also that more key matches proportionally more
+ records
+ This gives the formula:
+ records = (x * (b-a) + a*c-b)/(c-1)
+
+ b = records matched by whole key
+ a = records matched by first key part (1% of all records?)
+ c = number of key parts in key
+ x = used key parts (1 <= x <= c)
+ */
+ double rec_per_key;
+ if (!(rec_per_key=(double)
+ keyinfo->rec_per_key[keyinfo->user_defined_key_parts-1]))
+ rec_per_key=(double) s->records/rec+1;
+
+ if (!s->records)
+ tmp = 0;
+ else if (rec_per_key/(double) s->records >= 0.01)
+ tmp = rec_per_key;
+ else
+ {
+ double a=s->records*0.01;
+ if (keyinfo->user_defined_key_parts > 1)
+ tmp= (max_key_part * (rec_per_key - a) +
+ a*keyinfo->user_defined_key_parts - rec_per_key)/
+ (keyinfo->user_defined_key_parts-1);
+ else
+ tmp= a;
+ set_if_bigger(tmp,1.0);
+ }
+ records = (ulong) tmp;
+ }
+
+ if (ref_or_null_part)
+ {
+ /* We need to do two key searches to find key */
+ tmp *= 2.0;
+ records *= 2.0;
+ }
+
+ /*
+ ReuseRangeEstimateForRef-4: We get here if we could not reuse
+ E(#rows) from range optimizer. Make another try:
+
+ If range optimizer produced E(#rows) for a prefix of the ref
+ access we're considering, and that E(#rows) is lower then our
+ current estimate, make the adjustment.
+
+ The decision whether we can re-use the estimate from the range
+ optimizer is the same as in ReuseRangeEstimateForRef-3,
+ applied to first table->quick_key_parts[key] key parts.
+ */
+ if (table->quick_keys.is_set(key) &&
+ table->quick_key_parts[key] <= max_key_part &&
+ const_part &
+ ((key_part_map)1 << table->quick_key_parts[key]) &&
+ table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part &
+ const_part) &&
+ records > (double) table->quick_rows[key])
+ {
+ tmp= records= (double) table->quick_rows[key];
+ }
+ }
+
+ /* Limit the number of matched rows */
+ set_if_smaller(tmp, (double) thd->variables.max_seeks_for_key);
+ if (table->covering_keys.is_set(key))
+ tmp= table->file->keyread_time(key, 1, (ha_rows) tmp);
+ else
+ tmp= table->file->read_time(key, 1,
+ (ha_rows) MY_MIN(tmp,s->worst_seeks));
+ tmp*= record_count;
+ }
+ else
+ tmp= best_time; // Do nothing
+ }
+
+ tmp += s->startup_cost;
+ loose_scan_opt.check_ref_access_part2(key, start_key, records, tmp);
+ } /* not ft_key */
+ if (tmp + 0.0001 < best_time - records/(double) TIME_FOR_COMPARE)
+ {
+ best_time= tmp + records/(double) TIME_FOR_COMPARE;
+ best= tmp;
+ best_records= records;
+ best_key= start_key;
+ best_max_key_part= max_key_part;
+ best_ref_depends_map= found_ref;
+ }
+ } /* for each key */
+ records= best_records;
+ }
+
+ /*
+ If there is no key to access the table, but there is an equi-join
+ predicate connecting the table with the privious tables then we
+ consider the possibility of using hash join.
+ We need also to check that:
+ (1) s is inner table of semi-join -> join cache is allowed for semijoins
+ (2) s is inner table of outer join -> join cache is allowed for outer joins
+ */
+ if (idx > join->const_tables && best_key == 0 &&
+ (join->allowed_join_cache_types & JOIN_CACHE_HASHED_BIT) &&
+ join->max_allowed_join_cache_level > 2 &&
+ !bitmap_is_clear_all(eq_join_set) && !disable_jbuf &&
+ (!s->emb_sj_nest ||
+ join->allowed_semijoin_with_cache) && // (1)
+ (!(s->table->map & join->outer_join) ||
+ join->allowed_outer_join_with_cache)) // (2)
+ {
+ double join_sel= 0.1;
+ /* Estimate the cost of the hash join access to the table */
+ double rnd_records= matching_candidates_in_table(s, found_constraint,
+ use_cond_selectivity);
+
+ tmp= s->quick ? s->quick->read_time : s->scan_time();
+ tmp+= (s->records - rnd_records)/(double) TIME_FOR_COMPARE;
+
+ /* We read the table as many times as join buffer becomes full. */
+ tmp*= (1.0 + floor((double) cache_record_length(join,idx) *
+ record_count /
+ (double) thd->variables.join_buff_size));
+ best_time= tmp +
+ (record_count*join_sel) / TIME_FOR_COMPARE * rnd_records;
+ best= tmp;
+ records= rnd_records;
+ best_key= hj_start_key;
+ best_ref_depends_map= 0;
+ best_uses_jbuf= TRUE;
+ }
+
+ /*
+ Don't test table scan if it can't be better.
+ Prefer key lookup if we would use the same key for scanning.
+
+ Don't do a table scan on InnoDB tables, if we can read the used
+ parts of the row from any of the used index.
+ This is because table scans uses index and we would not win
+ anything by using a table scan.
+
+ A word for word translation of the below if-statement in sergefp's
+ understanding: we check if we should use table scan if:
+ (1) The found 'ref' access produces more records than a table scan
+ (or index scan, or quick select), or 'ref' is more expensive than
+ any of them.
+ (2) This doesn't hold: the best way to perform table scan is to to perform
+ 'range' access using index IDX, and the best way to perform 'ref'
+ access is to use the same index IDX, with the same or more key parts.
+ (note: it is not clear how this rule is/should be extended to
+ index_merge quick selects)
+ (3) See above note about InnoDB.
+ (4) NOT ("FORCE INDEX(...)" is used for table and there is 'ref' access
+ path, but there is no quick select)
+ If the condition in the above brackets holds, then the only possible
+ "table scan" access method is ALL/index (there is no quick select).
+ Since we have a 'ref' access path, and FORCE INDEX instructs us to
+ choose it over ALL/index, there is no need to consider a full table
+ scan.
+ (5) Non-flattenable semi-joins: don't consider doing a scan of temporary
+ table if we had an option to make lookups into it. In real-world cases,
+ lookups are cheaper than full scans, but when the table is small, they
+ can be [considered to be] more expensive, which causes lookups not to
+ be used for cases with small datasets, which is annoying.
+ */
+ if ((records >= s->found_records || best > s->read_time) && // (1)
+ !(s->quick && best_key && s->quick->index == best_key->key && // (2)
+ best_max_key_part >= s->table->quick_key_parts[best_key->key]) &&// (2)
+ !((s->table->file->ha_table_flags() & HA_TABLE_SCAN_ON_INDEX) && // (3)
+ ! s->table->covering_keys.is_clear_all() && best_key && !s->quick) &&// (3)
+ !(s->table->force_index && best_key && !s->quick) && // (4)
+ !(best_key && s->table->pos_in_table_list->jtbm_subselect)) // (5)
+ { // Check full join
+ double rnd_records= matching_candidates_in_table(s, found_constraint,
+ use_cond_selectivity);
+
+ /*
+ Range optimizer never proposes a RANGE if it isn't better
+ than FULL: so if RANGE is present, it's always preferred to FULL.
+ Here we estimate its cost.
+ */
+
+ if (s->quick)
+ {
+ /*
+ For each record we:
+ - read record range through 'quick'
+ - skip rows which does not satisfy WHERE constraints
+ TODO:
+ We take into account possible use of join cache for ALL/index
+ access (see first else-branch below), but we don't take it into
+ account here for range/index_merge access. Find out why this is so.
+ */
+ tmp= record_count *
+ (s->quick->read_time +
+ (s->found_records - rnd_records)/(double) TIME_FOR_COMPARE);
+
+ loose_scan_opt.check_range_access(join, idx, s->quick);
+ }
+ else
+ {
+ /* Estimate cost of reading table. */
+ if (s->table->force_index && !best_key) // index scan
+ tmp= s->table->file->read_time(s->ref.key, 1, s->records);
+ else // table scan
+ tmp= s->scan_time();
+
+ if ((s->table->map & join->outer_join) || disable_jbuf) // Can't use join cache
+ {
+ /*
+ For each record we have to:
+ - read the whole table record
+ - skip rows which does not satisfy join condition
+ */
+ tmp= record_count *
+ (tmp +
+ (s->records - rnd_records)/(double) TIME_FOR_COMPARE);
+ }
+ else
+ {
+ /* We read the table as many times as join buffer becomes full. */
+ tmp*= (1.0 + floor((double) cache_record_length(join,idx) *
+ record_count /
+ (double) thd->variables.join_buff_size));
+ /*
+ We don't make full cartesian product between rows in the scanned
+ table and existing records because we skip all rows from the
+ scanned table, which does not satisfy join condition when
+ we read the table (see flush_cached_records for details). Here we
+ take into account cost to read and skip these records.
+ */
+ tmp+= (s->records - rnd_records)/(double) TIME_FOR_COMPARE;
+ }
+ }
+
+ tmp += s->startup_cost;
+ /*
+ We estimate the cost of evaluating WHERE clause for found records
+ as record_count * rnd_records / TIME_FOR_COMPARE. This cost plus
+ tmp give us total cost of using TABLE SCAN
+ */
+ if (best == DBL_MAX ||
+ (tmp + record_count/(double) TIME_FOR_COMPARE*rnd_records <
+ (best_key->is_for_hash_join() ? best_time :
+ best + record_count/(double) TIME_FOR_COMPARE*records)))
+ {
+ /*
+ If the table has a range (s->quick is set) make_join_select()
+ will ensure that this will be used
+ */
+ best= tmp;
+ records= rnd_records;
+ best_key= 0;
+ /* range/index_merge/ALL/index access method are "independent", so: */
+ best_ref_depends_map= 0;
+ best_uses_jbuf= MY_TEST(!disable_jbuf && !((s->table->map &
+ join->outer_join)));
+ }
+ }
+
+ /* Update the cost information for the current partial plan */
+ pos->records_read= records;
+ pos->read_time= best;
+ pos->key= best_key;
+ pos->table= s;
+ pos->ref_depend_map= best_ref_depends_map;
+ pos->loosescan_picker.loosescan_key= MAX_KEY;
+ pos->use_join_buffer= best_uses_jbuf;
+
+ loose_scan_opt.save_to_position(s, loose_scan_pos);
+
+ if (!best_key &&
+ idx == join->const_tables &&
+ s->table == join->sort_by_table &&
+ join->unit->select_limit_cnt >= records)
+ join->sort_by_table= (TABLE*) 1; // Must use temporary table
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Find JOIN_TAB's embedding (i.e, parent) subquery.
+ - For merged semi-joins, tables inside the semi-join nest have their
+ semi-join nest as parent. We intentionally ignore results of table
+ pullout action here.
+ - For non-merged semi-joins (JTBM tabs), the embedding subquery is the
+ JTBM join tab itself.
+*/
+
+static TABLE_LIST* get_emb_subq(JOIN_TAB *tab)
+{
+ TABLE_LIST *tlist= tab->table->pos_in_table_list;
+ if (tlist->jtbm_subselect)
+ return tlist;
+ TABLE_LIST *embedding= tlist->embedding;
+ if (!embedding || !embedding->sj_subq_pred)
+ return NULL;
+ return embedding;
+}
+
+
+/*
+ Choose initial table order that "helps" semi-join optimizations.
+
+ The idea is that we should start with the order that is the same as the one
+ we would have had if we had semijoin=off:
+ - Top-level tables go first
+ - subquery tables are grouped together by the subquery they are in,
+ - subquery tables are attached where the subquery predicate would have been
+ attached if we had semi-join off.
+
+ This function relies on join_tab_cmp()/join_tab_cmp_straight() to produce
+ certain pre-liminary ordering, see compare_embedding_subqueries() for its
+ description.
+*/
+
+static void choose_initial_table_order(JOIN *join)
+{
+ TABLE_LIST *emb_subq;
+ JOIN_TAB **tab= join->best_ref + join->const_tables;
+ JOIN_TAB **tabs_end= tab + join->table_count - join->const_tables;
+ DBUG_ENTER("choose_initial_table_order");
+ /* Find where the top-level JOIN_TABs end and subquery JOIN_TABs start */
+ for (; tab != tabs_end; tab++)
+ {
+ if ((emb_subq= get_emb_subq(*tab)))
+ break;
+ }
+ uint n_subquery_tabs= tabs_end - tab;
+
+ if (!n_subquery_tabs)
+ DBUG_VOID_RETURN;
+
+ /* Copy the subquery JOIN_TABs to a separate array */
+ JOIN_TAB *subquery_tabs[MAX_TABLES];
+ memcpy(subquery_tabs, tab, sizeof(JOIN_TAB*) * n_subquery_tabs);
+
+ JOIN_TAB **last_top_level_tab= tab;
+ JOIN_TAB **subq_tab= subquery_tabs;
+ JOIN_TAB **subq_tabs_end= subquery_tabs + n_subquery_tabs;
+ TABLE_LIST *cur_subq_nest= NULL;
+ for (; subq_tab < subq_tabs_end; subq_tab++)
+ {
+ if (get_emb_subq(*subq_tab)!= cur_subq_nest)
+ {
+ /*
+ Reached the part of subquery_tabs that covers tables in some subquery.
+ */
+ cur_subq_nest= get_emb_subq(*subq_tab);
+
+ /* Determine how many tables the subquery has */
+ JOIN_TAB **last_tab_for_subq;
+ for (last_tab_for_subq= subq_tab;
+ last_tab_for_subq < subq_tabs_end &&
+ get_emb_subq(*last_tab_for_subq) == cur_subq_nest;
+ last_tab_for_subq++) {}
+ uint n_subquery_tables= last_tab_for_subq - subq_tab;
+
+ /*
+ Walk the original array and find where this subquery would have been
+ attached to
+ */
+ table_map need_tables= cur_subq_nest->original_subq_pred_used_tables;
+ need_tables &= ~(join->const_table_map | PSEUDO_TABLE_BITS);
+ for (JOIN_TAB **top_level_tab= join->best_ref + join->const_tables;
+ top_level_tab < last_top_level_tab;
+ //top_level_tab < join->best_ref + join->table_count;
+ top_level_tab++)
+ {
+ need_tables &= ~(*top_level_tab)->table->map;
+ /* Check if this is the place where subquery should be attached */
+ if (!need_tables)
+ {
+ /* Move away the top-level tables that are after top_level_tab */
+ uint top_tail_len= last_top_level_tab - top_level_tab - 1;
+ memmove(top_level_tab + 1 + n_subquery_tables, top_level_tab + 1,
+ sizeof(JOIN_TAB*)*top_tail_len);
+ last_top_level_tab += n_subquery_tables;
+ memcpy(top_level_tab + 1, subq_tab, sizeof(JOIN_TAB*)*n_subquery_tables);
+ break;
+ }
+ }
+ DBUG_ASSERT(!need_tables);
+ subq_tab += n_subquery_tables - 1;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Selects and invokes a search strategy for an optimal query plan.
+
+ The function checks user-configurable parameters that control the search
+ strategy for an optimal plan, selects the search method and then invokes
+ it. Each specific optimization procedure stores the final optimal plan in
+ the array 'join->best_positions', and the cost of the plan in
+ 'join->best_read'.
+
+ @param join pointer to the structure providing all context info for
+ the query
+ @param join_tables set of the tables in the query
+
+ @todo
+ 'MAX_TABLES+2' denotes the old implementation of find_best before
+ the greedy version. Will be removed when greedy_search is approved.
+
+ @retval
+ FALSE ok
+ @retval
+ TRUE Fatal error
+*/
+
+bool
+choose_plan(JOIN *join, table_map join_tables)
+{
+ uint search_depth= join->thd->variables.optimizer_search_depth;
+ uint prune_level= join->thd->variables.optimizer_prune_level;
+ uint use_cond_selectivity=
+ join->thd->variables.optimizer_use_condition_selectivity;
+ bool straight_join= MY_TEST(join->select_options & SELECT_STRAIGHT_JOIN);
+ DBUG_ENTER("choose_plan");
+
+ join->cur_embedding_map= 0;
+ reset_nj_counters(join, join->join_list);
+ qsort2_cmp jtab_sort_func;
+
+ if (join->emb_sjm_nest)
+ {
+ /* We're optimizing semi-join materialization nest, so put the
+ tables from this semi-join as first
+ */
+ jtab_sort_func= join_tab_cmp_embedded_first;
+ }
+ else
+ {
+ /*
+ if (SELECT_STRAIGHT_JOIN option is set)
+ reorder tables so dependent tables come after tables they depend
+ on, otherwise keep tables in the order they were specified in the query
+ else
+ Apply heuristic: pre-sort all access plans with respect to the number of
+ records accessed.
+ */
+ jtab_sort_func= straight_join ? join_tab_cmp_straight : join_tab_cmp;
+ }
+
+ /*
+ psergey-todo: if we're not optimizing an SJM nest,
+ - sort that outer tables are first, and each sjm nest follows
+ - then, put each [sjm_table1, ... sjm_tableN] sub-array right where
+ WHERE clause pushdown would have put it.
+ */
+ my_qsort2(join->best_ref + join->const_tables,
+ join->table_count - join->const_tables, sizeof(JOIN_TAB*),
+ jtab_sort_func, (void*)join->emb_sjm_nest);
+
+ if (!join->emb_sjm_nest)
+ {
+ choose_initial_table_order(join);
+ }
+ join->cur_sj_inner_tables= 0;
+
+ if (straight_join)
+ {
+ optimize_straight_join(join, join_tables);
+ }
+ else
+ {
+ if (search_depth == MAX_TABLES+2)
+ { /*
+ TODO: 'MAX_TABLES+2' denotes the old implementation of find_best before
+ the greedy version. Will be removed when greedy_search is approved.
+ */
+ join->best_read= DBL_MAX;
+ if (find_best(join, join_tables, join->const_tables, 1.0, 0.0,
+ use_cond_selectivity))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else
+ {
+ if (search_depth == 0)
+ /* Automatically determine a reasonable value for 'search_depth' */
+ search_depth= determine_search_depth(join);
+ if (greedy_search(join, join_tables, search_depth, prune_level,
+ use_cond_selectivity))
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ /*
+ Store the cost of this query into a user variable
+ Don't update last_query_cost for statements that are not "flat joins" :
+ i.e. they have subqueries, unions or call stored procedures.
+ TODO: calculate a correct cost for a query with subqueries and UNIONs.
+ */
+ if (join->thd->lex->is_single_level_stmt())
+ join->thd->status_var.last_query_cost= join->best_read;
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Compare two join tabs based on the subqueries they are from.
+ - top-level join tabs go first
+ - then subqueries are ordered by their select_id (we're using this
+ criteria because we need a cross-platform, deterministic ordering)
+
+ @return
+ 0 - equal
+ -1 - jt1 < jt2
+ 1 - jt1 > jt2
+*/
+
+static int compare_embedding_subqueries(JOIN_TAB *jt1, JOIN_TAB *jt2)
+{
+ /* Determine if the first table is originally from a subquery */
+ TABLE_LIST *tbl1= jt1->table->pos_in_table_list;
+ uint tbl1_select_no;
+ if (tbl1->jtbm_subselect)
+ {
+ tbl1_select_no=
+ tbl1->jtbm_subselect->unit->first_select()->select_number;
+ }
+ else if (tbl1->embedding && tbl1->embedding->sj_subq_pred)
+ {
+ tbl1_select_no=
+ tbl1->embedding->sj_subq_pred->unit->first_select()->select_number;
+ }
+ else
+ tbl1_select_no= 1; /* Top-level */
+
+ /* Same for the second table */
+ TABLE_LIST *tbl2= jt2->table->pos_in_table_list;
+ uint tbl2_select_no;
+ if (tbl2->jtbm_subselect)
+ {
+ tbl2_select_no=
+ tbl2->jtbm_subselect->unit->first_select()->select_number;
+ }
+ else if (tbl2->embedding && tbl2->embedding->sj_subq_pred)
+ {
+ tbl2_select_no=
+ tbl2->embedding->sj_subq_pred->unit->first_select()->select_number;
+ }
+ else
+ tbl2_select_no= 1; /* Top-level */
+
+ /*
+ Put top-level tables in front. Tables from within subqueries must follow,
+ grouped by their owner subquery. We don't care about the order that
+ subquery groups are in, because choose_initial_table_order() will re-order
+ the groups.
+ */
+ if (tbl1_select_no != tbl2_select_no)
+ return tbl1_select_no > tbl2_select_no ? 1 : -1;
+ return 0;
+}
+
+
+/**
+ Compare two JOIN_TAB objects based on the number of accessed records.
+
+ @param ptr1 pointer to first JOIN_TAB object
+ @param ptr2 pointer to second JOIN_TAB object
+
+ NOTES
+ The order relation implemented by join_tab_cmp() is not transitive,
+ i.e. it is possible to choose such a, b and c that (a < b) && (b < c)
+ but (c < a). This implies that result of a sort using the relation
+ implemented by join_tab_cmp() depends on the order in which
+ elements are compared, i.e. the result is implementation-specific.
+ Example:
+ a: dependent = 0x0 table->map = 0x1 found_records = 3 ptr = 0x907e6b0
+ b: dependent = 0x0 table->map = 0x2 found_records = 3 ptr = 0x907e838
+ c: dependent = 0x6 table->map = 0x10 found_records = 2 ptr = 0x907ecd0
+
+ As for subuqueries, this function must produce order that can be fed to
+ choose_initial_table_order().
+
+ @retval
+ 1 if first is bigger
+ @retval
+ -1 if second is bigger
+ @retval
+ 0 if equal
+*/
+
+static int
+join_tab_cmp(const void *dummy, const void* ptr1, const void* ptr2)
+{
+ JOIN_TAB *jt1= *(JOIN_TAB**) ptr1;
+ JOIN_TAB *jt2= *(JOIN_TAB**) ptr2;
+ int cmp;
+
+ if ((cmp= compare_embedding_subqueries(jt1, jt2)) != 0)
+ return cmp;
+ /*
+ After that,
+ take care about ordering imposed by LEFT JOIN constraints,
+ possible [eq]ref accesses, and numbers of matching records in the table.
+ */
+ if (jt1->dependent & jt2->table->map)
+ return 1;
+ if (jt2->dependent & jt1->table->map)
+ return -1;
+ if (jt1->found_records > jt2->found_records)
+ return 1;
+ if (jt1->found_records < jt2->found_records)
+ return -1;
+ return jt1 > jt2 ? 1 : (jt1 < jt2 ? -1 : 0);
+}
+
+
+/**
+ Same as join_tab_cmp, but for use with SELECT_STRAIGHT_JOIN.
+*/
+
+static int
+join_tab_cmp_straight(const void *dummy, const void* ptr1, const void* ptr2)
+{
+ JOIN_TAB *jt1= *(JOIN_TAB**) ptr1;
+ JOIN_TAB *jt2= *(JOIN_TAB**) ptr2;
+
+ /*
+ We don't do subquery flattening if the parent or child select has
+ STRAIGHT_JOIN modifier. It is complicated to implement and the semantics
+ is hardly useful.
+ */
+ DBUG_ASSERT(!jt1->emb_sj_nest);
+ DBUG_ASSERT(!jt2->emb_sj_nest);
+
+ int cmp;
+ if ((cmp= compare_embedding_subqueries(jt1, jt2)) != 0)
+ return cmp;
+
+ if (jt1->dependent & jt2->table->map)
+ return 1;
+ if (jt2->dependent & jt1->table->map)
+ return -1;
+ return jt1 > jt2 ? 1 : (jt1 < jt2 ? -1 : 0);
+}
+
+
+/*
+ Same as join_tab_cmp but tables from within the given semi-join nest go
+ first. Used when the optimizing semi-join materialization nests.
+*/
+
+static int
+join_tab_cmp_embedded_first(const void *emb, const void* ptr1, const void* ptr2)
+{
+ const TABLE_LIST *emb_nest= (TABLE_LIST*) emb;
+ JOIN_TAB *jt1= *(JOIN_TAB**) ptr1;
+ JOIN_TAB *jt2= *(JOIN_TAB**) ptr2;
+
+ if (jt1->emb_sj_nest == emb_nest && jt2->emb_sj_nest != emb_nest)
+ return -1;
+ if (jt1->emb_sj_nest != emb_nest && jt2->emb_sj_nest == emb_nest)
+ return 1;
+
+ if (jt1->dependent & jt2->table->map)
+ return 1;
+ if (jt2->dependent & jt1->table->map)
+ return -1;
+
+ if (jt1->found_records > jt2->found_records)
+ return 1;
+ if (jt1->found_records < jt2->found_records)
+ return -1;
+
+ return jt1 > jt2 ? 1 : (jt1 < jt2 ? -1 : 0);
+}
+
+
+/**
+ Heuristic procedure to automatically guess a reasonable degree of
+ exhaustiveness for the greedy search procedure.
+
+ The procedure estimates the optimization time and selects a search depth
+ big enough to result in a near-optimal QEP, that doesn't take too long to
+ find. If the number of tables in the query exceeds some constant, then
+ search_depth is set to this constant.
+
+ @param join pointer to the structure providing all context info for
+ the query
+
+ @note
+ This is an extremely simplistic implementation that serves as a stub for a
+ more advanced analysis of the join. Ideally the search depth should be
+ determined by learning from previous query optimizations, because it will
+ depend on the CPU power (and other factors).
+
+ @todo
+ this value should be determined dynamically, based on statistics:
+ uint max_tables_for_exhaustive_opt= 7;
+
+ @todo
+ this value could be determined by some mapping of the form:
+ depth : table_count -> [max_tables_for_exhaustive_opt..MAX_EXHAUSTIVE]
+
+ @return
+ A positive integer that specifies the search depth (and thus the
+ exhaustiveness) of the depth-first search algorithm used by
+ 'greedy_search'.
+*/
+
+static uint
+determine_search_depth(JOIN *join)
+{
+ uint table_count= join->table_count - join->const_tables;
+ uint search_depth;
+ /* TODO: this value should be determined dynamically, based on statistics: */
+ uint max_tables_for_exhaustive_opt= 7;
+
+ if (table_count <= max_tables_for_exhaustive_opt)
+ search_depth= table_count+1; // use exhaustive for small number of tables
+ else
+ /*
+ TODO: this value could be determined by some mapping of the form:
+ depth : table_count -> [max_tables_for_exhaustive_opt..MAX_EXHAUSTIVE]
+ */
+ search_depth= max_tables_for_exhaustive_opt; // use greedy search
+
+ return search_depth;
+}
+
+
+/**
+ Select the best ways to access the tables in a query without reordering them.
+
+ Find the best access paths for each query table and compute their costs
+ according to their order in the array 'join->best_ref' (thus without
+ reordering the join tables). The function calls sequentially
+ 'best_access_path' for each table in the query to select the best table
+ access method. The final optimal plan is stored in the array
+ 'join->best_positions', and the corresponding cost in 'join->best_read'.
+
+ @param join pointer to the structure providing all context info for
+ the query
+ @param join_tables set of the tables in the query
+
+ @note
+ This function can be applied to:
+ - queries with STRAIGHT_JOIN
+ - internally to compute the cost of an arbitrary QEP
+ @par
+ Thus 'optimize_straight_join' can be used at any stage of the query
+ optimization process to finalize a QEP as it is.
+*/
+
+static void
+optimize_straight_join(JOIN *join, table_map join_tables)
+{
+ JOIN_TAB *s;
+ uint idx= join->const_tables;
+ bool disable_jbuf= join->thd->variables.join_cache_level == 0;
+ double record_count= 1.0;
+ double read_time= 0.0;
+ uint use_cond_selectivity=
+ join->thd->variables.optimizer_use_condition_selectivity;
+ POSITION loose_scan_pos;
+
+ for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++)
+ {
+ /* Find the best access method from 's' to the current partial plan */
+ best_access_path(join, s, join_tables, idx, disable_jbuf, record_count,
+ join->positions + idx, &loose_scan_pos);
+
+ /* compute the cost of the new plan extended with 's' */
+ record_count*= join->positions[idx].records_read;
+ read_time+= join->positions[idx].read_time +
+ record_count / (double) TIME_FOR_COMPARE;
+ advance_sj_state(join, join_tables, idx, &record_count, &read_time,
+ &loose_scan_pos);
+
+ join_tables&= ~(s->table->map);
+ double pushdown_cond_selectivity= 1.0;
+ if (use_cond_selectivity > 1)
+ pushdown_cond_selectivity= table_cond_selectivity(join, idx, s,
+ join_tables);
+ join->positions[idx].cond_selectivity= pushdown_cond_selectivity;
+ ++idx;
+ }
+
+ if (join->sort_by_table &&
+ join->sort_by_table != join->positions[join->const_tables].table->table)
+ read_time+= record_count; // We have to make a temp table
+ memcpy((uchar*) join->best_positions, (uchar*) join->positions,
+ sizeof(POSITION)*idx);
+ join->record_count= record_count;
+ join->best_read= read_time - 0.001;
+}
+
+
+/**
+ Find a good, possibly optimal, query execution plan (QEP) by a greedy search.
+
+ The search procedure uses a hybrid greedy/exhaustive search with controlled
+ exhaustiveness. The search is performed in N = card(remaining_tables)
+ steps. Each step evaluates how promising is each of the unoptimized tables,
+ selects the most promising table, and extends the current partial QEP with
+ that table. Currenly the most 'promising' table is the one with least
+ expensive extension.\
+
+ There are two extreme cases:
+ -# When (card(remaining_tables) < search_depth), the estimate finds the
+ best complete continuation of the partial QEP. This continuation can be
+ used directly as a result of the search.
+ -# When (search_depth == 1) the 'best_extension_by_limited_search'
+ consideres the extension of the current QEP with each of the remaining
+ unoptimized tables.
+
+ All other cases are in-between these two extremes. Thus the parameter
+ 'search_depth' controlls the exhaustiveness of the search. The higher the
+ value, the longer the optimization time and possibly the better the
+ resulting plan. The lower the value, the fewer alternative plans are
+ estimated, but the more likely to get a bad QEP.
+
+ All intermediate and final results of the procedure are stored in 'join':
+ - join->positions : modified for every partial QEP that is explored
+ - join->best_positions: modified for the current best complete QEP
+ - join->best_read : modified for the current best complete QEP
+ - join->best_ref : might be partially reordered
+
+ The final optimal plan is stored in 'join->best_positions', and its
+ corresponding cost in 'join->best_read'.
+
+ @note
+ The following pseudocode describes the algorithm of 'greedy_search':
+
+ @code
+ procedure greedy_search
+ input: remaining_tables
+ output: pplan;
+ {
+ pplan = <>;
+ do {
+ (t, a) = best_extension(pplan, remaining_tables);
+ pplan = concat(pplan, (t, a));
+ remaining_tables = remaining_tables - t;
+ } while (remaining_tables != {})
+ return pplan;
+ }
+
+ @endcode
+ where 'best_extension' is a placeholder for a procedure that selects the
+ most "promising" of all tables in 'remaining_tables'.
+ Currently this estimate is performed by calling
+ 'best_extension_by_limited_search' to evaluate all extensions of the
+ current QEP of size 'search_depth', thus the complexity of 'greedy_search'
+ mainly depends on that of 'best_extension_by_limited_search'.
+
+ @par
+ If 'best_extension()' == 'best_extension_by_limited_search()', then the
+ worst-case complexity of this algorithm is <=
+ O(N*N^search_depth/search_depth). When serch_depth >= N, then the
+ complexity of greedy_search is O(N!).
+
+ @par
+ In the future, 'greedy_search' might be extended to support other
+ implementations of 'best_extension', e.g. some simpler quadratic procedure.
+
+ @param join pointer to the structure providing all context info
+ for the query
+ @param remaining_tables set of tables not included into the partial plan yet
+ @param search_depth controlls the exhaustiveness of the search
+ @param prune_level the pruning heuristics that should be applied during
+ search
+ @param use_cond_selectivity specifies how the selectivity of the conditions
+ pushed to a table should be taken into account
+
+ @retval
+ FALSE ok
+ @retval
+ TRUE Fatal error
+*/
+
+static bool
+greedy_search(JOIN *join,
+ table_map remaining_tables,
+ uint search_depth,
+ uint prune_level,
+ uint use_cond_selectivity)
+{
+ double record_count= 1.0;
+ double read_time= 0.0;
+ uint idx= join->const_tables; // index into 'join->best_ref'
+ uint best_idx;
+ uint size_remain; // cardinality of remaining_tables
+ POSITION best_pos;
+ JOIN_TAB *best_table; // the next plan node to be added to the curr QEP
+ // ==join->tables or # tables in the sj-mat nest we're optimizing
+ uint n_tables __attribute__((unused));
+ DBUG_ENTER("greedy_search");
+
+ /* number of tables that remain to be optimized */
+ n_tables= size_remain= my_count_bits(remaining_tables &
+ (join->emb_sjm_nest?
+ (join->emb_sjm_nest->sj_inner_tables &
+ ~join->const_table_map)
+ :
+ ~(table_map)0));
+
+ do {
+ /* Find the extension of the current QEP with the lowest cost */
+ join->best_read= DBL_MAX;
+ if (best_extension_by_limited_search(join, remaining_tables, idx, record_count,
+ read_time, search_depth, prune_level,
+ use_cond_selectivity))
+ DBUG_RETURN(TRUE);
+ /*
+ 'best_read < DBL_MAX' means that optimizer managed to find
+ some plan and updated 'best_positions' array accordingly.
+ */
+ DBUG_ASSERT(join->best_read < DBL_MAX);
+
+ if (size_remain <= search_depth)
+ {
+ /*
+ 'join->best_positions' contains a complete optimal extension of the
+ current partial QEP.
+ */
+ DBUG_EXECUTE("opt", print_plan(join, n_tables,
+ record_count, read_time, read_time,
+ "optimal"););
+ DBUG_RETURN(FALSE);
+ }
+
+ /* select the first table in the optimal extension as most promising */
+ best_pos= join->best_positions[idx];
+ best_table= best_pos.table;
+ /*
+ Each subsequent loop of 'best_extension_by_limited_search' uses
+ 'join->positions' for cost estimates, therefore we have to update its
+ value.
+ */
+ join->positions[idx]= best_pos;
+
+ /*
+ Update the interleaving state after extending the current partial plan
+ with a new table.
+ We are doing this here because best_extension_by_limited_search reverts
+ the interleaving state to the one of the non-extended partial plan
+ on exit.
+ */
+ bool is_interleave_error __attribute__((unused))=
+ check_interleaving_with_nj (best_table);
+ /* This has been already checked by best_extension_by_limited_search */
+ DBUG_ASSERT(!is_interleave_error);
+
+
+ /* find the position of 'best_table' in 'join->best_ref' */
+ best_idx= idx;
+ JOIN_TAB *pos= join->best_ref[best_idx];
+ while (pos && best_table != pos)
+ pos= join->best_ref[++best_idx];
+ DBUG_ASSERT((pos != NULL)); // should always find 'best_table'
+ /* move 'best_table' at the first free position in the array of joins */
+ swap_variables(JOIN_TAB*, join->best_ref[idx], join->best_ref[best_idx]);
+
+ /* compute the cost of the new plan extended with 'best_table' */
+ record_count*= join->positions[idx].records_read;
+ read_time+= join->positions[idx].read_time +
+ record_count / (double) TIME_FOR_COMPARE;
+
+ remaining_tables&= ~(best_table->table->map);
+ --size_remain;
+ ++idx;
+
+ DBUG_EXECUTE("opt", print_plan(join, idx,
+ record_count, read_time, read_time,
+ "extended"););
+ } while (TRUE);
+}
+
+
+/**
+ Get cost of execution and fanout produced by selected tables in the join
+ prefix (where prefix is defined as prefix in depth-first traversal)
+
+ @param end_tab_idx The number of last tab to be taken into
+ account (in depth-first traversal prefix)
+ @param filter_map Bitmap of tables whose cost/fanout are to
+ be taken into account.
+ @param read_time_arg [out] store read time here
+ @param record_count_arg [out] store record count here
+
+ @note
+
+ @returns
+ read_time_arg and record_count_arg contain the computed cost and fanout
+*/
+
+void JOIN::get_partial_cost_and_fanout(int end_tab_idx,
+ table_map filter_map,
+ double *read_time_arg,
+ double *record_count_arg)
+{
+ double record_count= 1;
+ double read_time= 0.0;
+ double sj_inner_fanout= 1.0;
+ JOIN_TAB *end_tab= NULL;
+ JOIN_TAB *tab;
+ int i;
+ int last_sj_table= MAX_TABLES;
+
+ /*
+ Handle a special case where the join is degenerate, and produces no
+ records
+ */
+ if (table_count == const_tables)
+ {
+ *read_time_arg= 0.0;
+ /*
+ We return 1, because
+ - it is the pessimistic estimate (there might be grouping)
+ - it's safer, as we're less likely to hit the edge cases in
+ calculations.
+ */
+ *record_count_arg=1.0;
+ return;
+ }
+
+ for (tab= first_depth_first_tab(this), i= const_tables;
+ tab;
+ tab= next_depth_first_tab(this, tab), i++)
+ {
+ end_tab= tab;
+ if (i == end_tab_idx)
+ break;
+ }
+
+ for (tab= first_depth_first_tab(this), i= const_tables;
+ ;
+ tab= next_depth_first_tab(this, tab), i++)
+ {
+ if (end_tab->bush_root_tab && end_tab->bush_root_tab == tab)
+ {
+ /*
+ We've entered the SJM nest that contains the end_tab. The caller is
+ - interested in fanout inside the nest (because that's how many times
+ we'll invoke the attached WHERE conditions)
+ - not interested in cost
+ */
+ record_count= 1.0;
+ read_time= 0.0;
+ }
+
+ /*
+ Ignore fanout (but not cost) from sj-inner tables, as long as
+ the range that processes them finishes before the end_tab
+ */
+ if (tab->sj_strategy != SJ_OPT_NONE)
+ {
+ sj_inner_fanout= 1.0;
+ last_sj_table= i + tab->n_sj_tables;
+ }
+
+ table_map cur_table_map;
+ if (tab->table)
+ cur_table_map= tab->table->map;
+ else
+ {
+ /* This is a SJ-Materialization nest. Check all of its tables */
+ TABLE *first_child= tab->bush_children->start->table;
+ TABLE_LIST *sjm_nest= first_child->pos_in_table_list->embedding;
+ cur_table_map= sjm_nest->nested_join->used_tables;
+ }
+ if (tab->records_read && (cur_table_map & filter_map))
+ {
+ record_count *= tab->records_read;
+ read_time += tab->read_time + record_count / (double) TIME_FOR_COMPARE;
+ if (tab->emb_sj_nest)
+ sj_inner_fanout *= tab->records_read;
+ }
+
+ if (i == last_sj_table)
+ {
+ record_count /= sj_inner_fanout;
+ sj_inner_fanout= 1.0;
+ last_sj_table= MAX_TABLES;
+ }
+
+ if (tab == end_tab)
+ break;
+ }
+ *read_time_arg= read_time;// + record_count / TIME_FOR_COMPARE;
+ *record_count_arg= record_count;
+}
+
+
+/*
+ Get prefix cost and fanout. This function is different from
+ get_partial_cost_and_fanout:
+ - it operates on a JOIN that haven't yet finished its optimization phase (in
+ particular, fix_semijoin_strategies_for_picked_join_order() and
+ get_best_combination() haven't been called)
+ - it assumes the the join prefix doesn't have any semi-join plans
+
+ These assumptions are met by the caller of the function.
+*/
+
+void JOIN::get_prefix_cost_and_fanout(uint n_tables,
+ double *read_time_arg,
+ double *record_count_arg)
+{
+ double record_count= 1;
+ double read_time= 0.0;
+ for (uint i= const_tables; i < n_tables + const_tables ; i++)
+ {
+ if (best_positions[i].records_read)
+ {
+ record_count *= best_positions[i].records_read;
+ read_time += best_positions[i].read_time;
+ }
+ }
+ *read_time_arg= read_time;// + record_count / TIME_FOR_COMPARE;
+ *record_count_arg= record_count;
+}
+
+
+/**
+ Estimate the number of rows that query execution will read.
+
+ @todo This is a very pessimistic upper bound. Use join selectivity
+ when available to produce a more realistic number.
+*/
+
+double JOIN::get_examined_rows()
+{
+ double examined_rows;
+ double prev_fanout= 1;
+ JOIN_TAB *tab= first_breadth_first_tab(this, WALK_OPTIMIZATION_TABS);
+ JOIN_TAB *prev_tab= tab;
+
+ examined_rows= tab->get_examined_rows();
+
+ while ((tab= next_breadth_first_tab(this, WALK_OPTIMIZATION_TABS, tab)))
+ {
+ prev_fanout *= prev_tab->records_read;
+ examined_rows+= tab->get_examined_rows() * prev_fanout;
+ prev_tab= tab;
+ }
+ return examined_rows;
+}
+
+
+/**
+ @brief
+ Get the selectivity of equalities between columns when joining a table
+
+ @param join The optimized join
+ @param idx The number of tables in the evaluated partual join
+ @param s The table to be joined for evaluation
+ @param rem_tables The bitmap of tables to be joined later
+ @param keyparts The number of key parts to used when joining s
+ @param ref_keyuse_steps Array of references to keyuses employed to join s
+*/
+
+static
+double table_multi_eq_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
+ table_map rem_tables, uint keyparts,
+ uint16 *ref_keyuse_steps)
+{
+ double sel= 1.0;
+ COND_EQUAL *cond_equal= join->cond_equal;
+
+ if (!cond_equal || !cond_equal->current_level.elements)
+ return sel;
+
+ if (!s->keyuse)
+ return sel;
+
+ Item_equal *item_equal;
+ List_iterator_fast<Item_equal> it(cond_equal->current_level);
+ TABLE *table= s->table;
+ table_map table_bit= table->map;
+ POSITION *pos= &join->positions[idx];
+
+ while ((item_equal= it++))
+ {
+ /*
+ Check whether we need to take into account the selectivity of
+ multiple equality item_equal. If this is the case multiply
+ the current value of sel by this selectivity
+ */
+ table_map used_tables= item_equal->used_tables();
+ if (!(used_tables & table_bit))
+ continue;
+ if (item_equal->get_const())
+ continue;
+
+ Field *fld;
+ bool adjust_sel= FALSE;
+ Item_equal_fields_iterator fi(*item_equal);
+ while((fi++) && !adjust_sel)
+ {
+ Field *fld= fi.get_curr_field();
+ if (fld->table->map != table_bit)
+ continue;
+ if (pos->key == 0)
+ adjust_sel= TRUE;
+ else
+ {
+ uint i;
+ KEYUSE *keyuse= pos->key;
+ uint key= keyuse->key;
+ for (i= 0; i < keyparts; i++)
+ {
+ if (i > 0)
+ keyuse+= ref_keyuse_steps[i-1];
+ uint fldno;
+ if (is_hash_join_key_no(key))
+ fldno= keyuse->keypart;
+ else
+ fldno= table->key_info[key].key_part[i].fieldnr - 1;
+ if (fld->field_index == fldno)
+ break;
+ }
+ keyuse= pos->key;
+
+ if (i == keyparts)
+ {
+ /*
+ Field fld is included in multiple equality item_equal
+ and is not a part of the ref key.
+ The selectivity of the multiple equality must be taken
+ into account unless one of the ref arguments is
+ equal to fld.
+ */
+ adjust_sel= TRUE;
+ for (uint j= 0; j < keyparts && adjust_sel; j++)
+ {
+ if (j > 0)
+ keyuse+= ref_keyuse_steps[j-1];
+ Item *ref_item= keyuse->val;
+ if (ref_item->real_item()->type() == Item::FIELD_ITEM)
+ {
+ Item_field *field_item= (Item_field *) (ref_item->real_item());
+ if (item_equal->contains(field_item->field))
+ adjust_sel= FALSE;
+ }
+ }
+ }
+ }
+ }
+ if (adjust_sel)
+ {
+ /*
+ If ref == 0 and there are no fields in the multiple equality
+ item_equal that belong to the tables joined prior to s
+ then the selectivity of multiple equality will be set to 1.0.
+ */
+ double eq_fld_sel= 1.0;
+ fi.rewind();
+ while ((fi++))
+ {
+ double curr_eq_fld_sel;
+ fld= fi.get_curr_field();
+ if (!fld->table->map & ~(table_bit | rem_tables))
+ continue;
+ curr_eq_fld_sel= get_column_avg_frequency(fld) /
+ fld->table->stat_records();
+ if (curr_eq_fld_sel < 1.0)
+ set_if_bigger(eq_fld_sel, curr_eq_fld_sel);
+ }
+ sel*= eq_fld_sel;
+ }
+ }
+ return sel;
+}
+
+
+/**
+ @brief
+ Get the selectivity of conditions when joining a table
+
+ @param join The optimized join
+ @param s The table to be joined for evaluation
+ @param rem_tables The bitmap of tables to be joined later
+
+ @detail
+ Get selectivity of conditions that can be applied when joining this table
+ with previous tables.
+
+ For quick selects and full table scans, selectivity of COND(this_table)
+ is accounted for in matching_candidates_in_table(). Here, we only count
+ selectivity of COND(this_table, previous_tables).
+
+ For other access methods, we need to calculate selectivity of the whole
+ condition, "COND(this_table) AND COND(this_table, previous_tables)".
+
+ @retval
+ selectivity of the conditions imposed on the rows of s
+*/
+
+static
+double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
+ table_map rem_tables)
+{
+ uint16 ref_keyuse_steps[MAX_REF_PARTS - 1];
+ Field *field;
+ TABLE *table= s->table;
+ MY_BITMAP *read_set= table->read_set;
+ double sel= s->table->cond_selectivity;
+ POSITION *pos= &join->positions[idx];
+ uint keyparts= 0;
+ uint found_part_ref_or_null= 0;
+
+ if (pos->key != 0)
+ {
+ /*
+ A ref access or hash join is used for this table. ref access is created
+ from
+
+ tbl.keypart1=expr1 AND tbl.keypart2=expr2 AND ...
+
+ and it will only return rows for which this condition is satisified.
+ Suppose, certain expr{i} is a constant. Since ref access only returns
+ rows that satisfy
+
+ tbl.keypart{i}=const (*)
+
+ then selectivity of this equality should not be counted in return value
+ of this function. This function uses the value of
+
+ table->cond_selectivity=selectivity(COND(tbl)) (**)
+
+ as a starting point. This value includes selectivity of equality (*). We
+ should somehow discount it.
+
+ Looking at calculate_cond_selectivity_for_table(), one can see that that
+ the value is not necessarily a direct multiplicand in
+ table->cond_selectivity
+
+ There are three possible ways to discount
+ 1. There is a potential range access on t.keypart{i}=const.
+ (an important special case: the used ref access has a const prefix for
+ which a range estimate is available)
+
+ 2. The field has a histogram. field[x]->cond_selectivity has the data.
+
+ 3. Use index stats on this index:
+ rec_per_key[key_part+1]/rec_per_key[key_part]
+
+ (TODO: more details about the "t.key=othertable.col" case)
+ */
+ KEYUSE *keyuse= pos->key;
+ KEYUSE *prev_ref_keyuse= keyuse;
+ uint key= keyuse->key;
+
+ /*
+ Check if we have a prefix of key=const that matches a quick select.
+ */
+ if (!is_hash_join_key_no(key))
+ {
+ table_map quick_key_map= (table_map(1) << table->quick_key_parts[key]) - 1;
+ if (table->quick_rows[key] &&
+ !(quick_key_map & ~table->const_key_parts[key]))
+ {
+ /*
+ Ok, there is an equality for each of the key parts used by the
+ quick select. This means, quick select's estimate can be reused to
+ discount the selectivity of a prefix of a ref access.
+ */
+ for (; quick_key_map & 1 ; quick_key_map>>= 1)
+ {
+ while (keyuse->table == table && keyuse->key == key &&
+ keyuse->keypart == keyparts)
+ {
+ keyuse++;
+ }
+ keyparts++;
+ }
+ sel /= (double)table->quick_rows[key] / (double) table->stat_records();
+ }
+ }
+
+ /*
+ Go through the "keypart{N}=..." equalities and find those that were
+ already taken into account in table->cond_selectivity.
+ */
+ keyuse= pos->key;
+ keyparts=0;
+ while (keyuse->table == table && keyuse->key == key)
+ {
+ if (!(keyuse->used_tables & (rem_tables | table->map)))
+ {
+ if (are_tables_local(s, keyuse->val->used_tables()))
+ {
+ if (is_hash_join_key_no(key))
+ {
+ if (keyparts == keyuse->keypart)
+ keyparts++;
+ }
+ else
+ {
+ if (keyparts == keyuse->keypart &&
+ !((keyuse->val->used_tables()) & ~pos->ref_depend_map) &&
+ !(found_part_ref_or_null & keyuse->optimize))
+ {
+ /* Found a KEYUSE object that will be used by ref access */
+ keyparts++;
+ found_part_ref_or_null|= keyuse->optimize & ~KEY_OPTIMIZE_EQ;
+ }
+ }
+
+ if (keyparts > keyuse->keypart)
+ {
+ /* Ok this is the keyuse that will be used for ref access */
+ uint fldno;
+ if (is_hash_join_key_no(key))
+ fldno= keyuse->keypart;
+ else
+ fldno= table->key_info[key].key_part[keyparts-1].fieldnr - 1;
+ if (keyuse->val->const_item())
+ {
+ if (table->field[fldno]->cond_selectivity > 0)
+ {
+ sel /= table->field[fldno]->cond_selectivity;
+ set_if_smaller(sel, 1.0);
+ }
+ /*
+ TODO: we could do better here:
+ 1. cond_selectivity might be =1 (the default) because quick
+ select on some index prevented us from analyzing
+ histogram for this column.
+ 2. we could get an estimate through this?
+ rec_per_key[key_part-1] / rec_per_key[key_part]
+ */
+ }
+ if (keyparts > 1)
+ {
+ ref_keyuse_steps[keyparts-2]= keyuse - prev_ref_keyuse;
+ prev_ref_keyuse= keyuse;
+ }
+ }
+ }
+ }
+ keyuse++;
+ }
+ }
+ else
+ {
+ /*
+ The table is accessed with full table scan, or quick select.
+ Selectivity of COND(table) is already accounted for in
+ matching_candidates_in_table().
+ */
+ sel= 1;
+ }
+
+ /*
+ If the field f from the table is equal to a field from one the
+ earlier joined tables then the selectivity of the range conditions
+ over the field f must be discounted.
+
+ We need to discount selectivity only if we're using ref-based
+ access method (and have sel!=1).
+ If we use ALL/range/index_merge, then sel==1, and no need to discount.
+ */
+ if (pos->key != NULL)
+ {
+ for (Field **f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
+ {
+ if (!bitmap_is_set(read_set, field->field_index) ||
+ !field->next_equal_field)
+ continue;
+ for (Field *next_field= field->next_equal_field;
+ next_field != field;
+ next_field= next_field->next_equal_field)
+ {
+ if (!(next_field->table->map & rem_tables) && next_field->table != table)
+ {
+ if (field->cond_selectivity > 0)
+ {
+ sel/= field->cond_selectivity;
+ set_if_smaller(sel, 1.0);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ sel*= table_multi_eq_cond_selectivity(join, idx, s, rem_tables,
+ keyparts, ref_keyuse_steps);
+
+ return sel;
+}
+
+
+/**
+ Find a good, possibly optimal, query execution plan (QEP) by a possibly
+ exhaustive search.
+
+ The procedure searches for the optimal ordering of the query tables in set
+ 'remaining_tables' of size N, and the corresponding optimal access paths to
+ each table. The choice of a table order and an access path for each table
+ constitutes a query execution plan (QEP) that fully specifies how to
+ execute the query.
+
+ The maximal size of the found plan is controlled by the parameter
+ 'search_depth'. When search_depth == N, the resulting plan is complete and
+ can be used directly as a QEP. If search_depth < N, the found plan consists
+ of only some of the query tables. Such "partial" optimal plans are useful
+ only as input to query optimization procedures, and cannot be used directly
+ to execute a query.
+
+ The algorithm begins with an empty partial plan stored in 'join->positions'
+ and a set of N tables - 'remaining_tables'. Each step of the algorithm
+ evaluates the cost of the partial plan extended by all access plans for
+ each of the relations in 'remaining_tables', expands the current partial
+ plan with the access plan that results in lowest cost of the expanded
+ partial plan, and removes the corresponding relation from
+ 'remaining_tables'. The algorithm continues until it either constructs a
+ complete optimal plan, or constructs an optimal plartial plan with size =
+ search_depth.
+
+ The final optimal plan is stored in 'join->best_positions'. The
+ corresponding cost of the optimal plan is in 'join->best_read'.
+
+ @note
+ The procedure uses a recursive depth-first search where the depth of the
+ recursion (and thus the exhaustiveness of the search) is controlled by the
+ parameter 'search_depth'.
+
+ @note
+ The pseudocode below describes the algorithm of
+ 'best_extension_by_limited_search'. The worst-case complexity of this
+ algorithm is O(N*N^search_depth/search_depth). When serch_depth >= N, then
+ the complexity of greedy_search is O(N!).
+
+ @code
+ procedure best_extension_by_limited_search(
+ pplan in, // in, partial plan of tables-joined-so-far
+ pplan_cost, // in, cost of pplan
+ remaining_tables, // in, set of tables not referenced in pplan
+ best_plan_so_far, // in/out, best plan found so far
+ best_plan_so_far_cost,// in/out, cost of best_plan_so_far
+ search_depth) // in, maximum size of the plans being considered
+ {
+ for each table T from remaining_tables
+ {
+ // Calculate the cost of using table T as above
+ cost = complex-series-of-calculations;
+
+ // Add the cost to the cost so far.
+ pplan_cost+= cost;
+
+ if (pplan_cost >= best_plan_so_far_cost)
+ // pplan_cost already too great, stop search
+ continue;
+
+ pplan= expand pplan by best_access_method;
+ remaining_tables= remaining_tables - table T;
+ if (remaining_tables is not an empty set
+ and
+ search_depth > 1)
+ {
+ best_extension_by_limited_search(pplan, pplan_cost,
+ remaining_tables,
+ best_plan_so_far,
+ best_plan_so_far_cost,
+ search_depth - 1);
+ }
+ else
+ {
+ best_plan_so_far_cost= pplan_cost;
+ best_plan_so_far= pplan;
+ }
+ }
+ }
+ @endcode
+
+ @note
+ When 'best_extension_by_limited_search' is called for the first time,
+ 'join->best_read' must be set to the largest possible value (e.g. DBL_MAX).
+ The actual implementation provides a way to optionally use pruning
+ heuristic (controlled by the parameter 'prune_level') to reduce the search
+ space by skipping some partial plans.
+
+ @note
+ The parameter 'search_depth' provides control over the recursion
+ depth, and thus the size of the resulting optimal plan.
+
+ @param join pointer to the structure providing all context info
+ for the query
+ @param remaining_tables set of tables not included into the partial plan yet
+ @param idx length of the partial QEP in 'join->positions';
+ since a depth-first search is used, also corresponds
+ to the current depth of the search tree;
+ also an index in the array 'join->best_ref';
+ @param record_count estimate for the number of records returned by the
+ best partial plan
+ @param read_time the cost of the best partial plan
+ @param search_depth maximum depth of the recursion and thus size of the
+ found optimal plan
+ (0 < search_depth <= join->tables+1).
+ @param prune_level pruning heuristics that should be applied during
+ optimization
+ (values: 0 = EXHAUSTIVE, 1 = PRUNE_BY_TIME_OR_ROWS)
+ @param use_cond_selectivity specifies how the selectivity of the conditions
+ pushed to a table should be taken into account
+
+ @retval
+ FALSE ok
+ @retval
+ TRUE Fatal error
+*/
+
+static bool
+best_extension_by_limited_search(JOIN *join,
+ table_map remaining_tables,
+ uint idx,
+ double record_count,
+ double read_time,
+ uint search_depth,
+ uint prune_level,
+ uint use_cond_selectivity)
+{
+ DBUG_ENTER("best_extension_by_limited_search");
+
+ THD *thd= join->thd;
+
+ DBUG_EXECUTE_IF("show_explain_probe_best_ext_lim_search",
+ if (dbug_user_var_equals_int(thd,
+ "show_explain_probe_select_id",
+ join->select_lex->select_number))
+ dbug_serve_apcs(thd, 1);
+ );
+
+ if (thd->check_killed()) // Abort
+ DBUG_RETURN(TRUE);
+
+ DBUG_EXECUTE("opt", print_plan(join, idx, read_time, record_count, idx,
+ "SOFAR:"););
+
+ /*
+ 'join' is a partial plan with lower cost than the best plan so far,
+ so continue expanding it further with the tables in 'remaining_tables'.
+ */
+ JOIN_TAB *s;
+ double best_record_count= DBL_MAX;
+ double best_read_time= DBL_MAX;
+ bool disable_jbuf= join->thd->variables.join_cache_level == 0;
+
+ DBUG_EXECUTE("opt", print_plan(join, idx, record_count, read_time, read_time,
+ "part_plan"););
+
+ /*
+ If we are searching for the execution plan of a materialized semi-join nest
+ then allowed_tables contains bits only for the tables from this nest.
+ */
+ table_map allowed_tables= ~(table_map)0;
+ if (join->emb_sjm_nest)
+ allowed_tables= join->emb_sjm_nest->sj_inner_tables & ~join->const_table_map;
+
+ for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++)
+ {
+ table_map real_table_bit= s->table->map;
+ if ((remaining_tables & real_table_bit) &&
+ (allowed_tables & real_table_bit) &&
+ !(remaining_tables & s->dependent) &&
+ (!idx || !check_interleaving_with_nj(s)))
+ {
+ double current_record_count, current_read_time;
+ POSITION *position= join->positions + idx;
+
+ /* Find the best access method from 's' to the current partial plan */
+ POSITION loose_scan_pos;
+ best_access_path(join, s, remaining_tables, idx, disable_jbuf,
+ record_count, join->positions + idx, &loose_scan_pos);
+
+ /* Compute the cost of extending the plan with 's' */
+
+ current_record_count= record_count * position->records_read;
+ current_read_time=read_time + position->read_time +
+ current_record_count / (double) TIME_FOR_COMPARE;
+
+ advance_sj_state(join, remaining_tables, idx, &current_record_count,
+ &current_read_time, &loose_scan_pos);
+
+ /* Expand only partial plans with lower cost than the best QEP so far */
+ if (current_read_time >= join->best_read)
+ {
+ DBUG_EXECUTE("opt", print_plan(join, idx+1,
+ current_record_count,
+ read_time,
+ current_read_time,
+ "prune_by_cost"););
+ restore_prev_nj_state(s);
+ restore_prev_sj_state(remaining_tables, s, idx);
+ continue;
+ }
+
+ /*
+ Prune some less promising partial plans. This heuristic may miss
+ the optimal QEPs, thus it results in a non-exhaustive search.
+ */
+ if (prune_level == 1)
+ {
+ if (best_record_count > current_record_count ||
+ best_read_time > current_read_time ||
+ (idx == join->const_tables && // 's' is the first table in the QEP
+ s->table == join->sort_by_table))
+ {
+ if (best_record_count >= current_record_count &&
+ best_read_time >= current_read_time &&
+ /* TODO: What is the reasoning behind this condition? */
+ (!(s->key_dependent & allowed_tables & remaining_tables) ||
+ join->positions[idx].records_read < 2.0))
+ {
+ best_record_count= current_record_count;
+ best_read_time= current_read_time;
+ }
+ }
+ else
+ {
+ DBUG_EXECUTE("opt", print_plan(join, idx+1,
+ current_record_count,
+ read_time,
+ current_read_time,
+ "pruned_by_heuristic"););
+ restore_prev_nj_state(s);
+ restore_prev_sj_state(remaining_tables, s, idx);
+ continue;
+ }
+ }
+
+ double pushdown_cond_selectivity= 1.0;
+ if (use_cond_selectivity > 1)
+ pushdown_cond_selectivity= table_cond_selectivity(join, idx, s,
+ remaining_tables &
+ ~real_table_bit);
+ join->positions[idx].cond_selectivity= pushdown_cond_selectivity;
+ double partial_join_cardinality= current_record_count *
+ pushdown_cond_selectivity;
+ if ( (search_depth > 1) && (remaining_tables & ~real_table_bit) & allowed_tables )
+ { /* Recursively expand the current partial plan */
+ swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
+ if (best_extension_by_limited_search(join,
+ remaining_tables & ~real_table_bit,
+ idx + 1,
+ partial_join_cardinality,
+ current_read_time,
+ search_depth - 1,
+ prune_level,
+ use_cond_selectivity))
+ DBUG_RETURN(TRUE);
+ swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
+ }
+ else
+ { /*
+ 'join' is either the best partial QEP with 'search_depth' relations,
+ or the best complete QEP so far, whichever is smaller.
+ */
+ if (join->sort_by_table &&
+ join->sort_by_table !=
+ join->positions[join->const_tables].table->table)
+ /*
+ We may have to make a temp table, note that this is only a
+ heuristic since we cannot know for sure at this point.
+ Hence it may be wrong.
+ */
+ current_read_time+= current_record_count;
+ if (current_read_time < join->best_read)
+ {
+ memcpy((uchar*) join->best_positions, (uchar*) join->positions,
+ sizeof(POSITION) * (idx + 1));
+ join->record_count= partial_join_cardinality;
+ join->best_read= current_read_time - 0.001;
+ }
+ DBUG_EXECUTE("opt", print_plan(join, idx+1,
+ current_record_count,
+ read_time,
+ current_read_time,
+ "full_plan"););
+ }
+ restore_prev_nj_state(s);
+ restore_prev_sj_state(remaining_tables, s, idx);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @todo
+ - TODO: this function is here only temporarily until 'greedy_search' is
+ tested and accepted.
+
+ RETURN VALUES
+ FALSE ok
+ TRUE Fatal error
+*/
+static bool
+find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
+ double read_time, uint use_cond_selectivity)
+{
+ DBUG_ENTER("find_best");
+ THD *thd= join->thd;
+ if (thd->check_killed())
+ DBUG_RETURN(TRUE);
+ if (!rest_tables)
+ {
+ DBUG_PRINT("best",("read_time: %g record_count: %g",read_time,
+ record_count));
+
+ read_time+=record_count/(double) TIME_FOR_COMPARE;
+ if (join->sort_by_table &&
+ join->sort_by_table !=
+ join->positions[join->const_tables].table->table)
+ read_time+=record_count; // We have to make a temp table
+ if (read_time < join->best_read)
+ {
+ memcpy((uchar*) join->best_positions,(uchar*) join->positions,
+ sizeof(POSITION)*idx);
+ join->best_read= read_time - 0.001;
+ }
+ DBUG_RETURN(FALSE);
+ }
+ if (read_time+record_count/(double) TIME_FOR_COMPARE >= join->best_read)
+ DBUG_RETURN(FALSE); /* Found better before */
+
+ JOIN_TAB *s;
+ double best_record_count=DBL_MAX,best_read_time=DBL_MAX;
+ bool disable_jbuf= join->thd->variables.join_cache_level == 0;
+ for (JOIN_TAB **pos=join->best_ref+idx ; (s=*pos) ; pos++)
+ {
+ table_map real_table_bit=s->table->map;
+ if ((rest_tables & real_table_bit) && !(rest_tables & s->dependent) &&
+ (!idx|| !check_interleaving_with_nj(s)))
+ {
+ double records, best;
+ POSITION loose_scan_pos;
+ best_access_path(join, s, rest_tables, idx, disable_jbuf, record_count,
+ join->positions + idx, &loose_scan_pos);
+ records= join->positions[idx].records_read;
+ best= join->positions[idx].read_time;
+ /*
+ Go to the next level only if there hasn't been a better key on
+ this level! This will cut down the search for a lot simple cases!
+ */
+ double current_record_count=record_count*records;
+ double current_read_time=read_time+best;
+ advance_sj_state(join, rest_tables, idx, &current_record_count,
+ &current_read_time, &loose_scan_pos);
+
+ double pushdown_cond_selectivity= 1.0;
+ if (use_cond_selectivity > 1)
+ pushdown_cond_selectivity= table_cond_selectivity(join, idx, s,
+ rest_tables &
+ ~real_table_bit);
+ join->positions[idx].cond_selectivity= pushdown_cond_selectivity;
+ double partial_join_cardinality= current_record_count *
+ pushdown_cond_selectivity;
+
+ if (best_record_count > partial_join_cardinality ||
+ best_read_time > current_read_time ||
+ (idx == join->const_tables && s->table == join->sort_by_table))
+ {
+ if (best_record_count >= partial_join_cardinality &&
+ best_read_time >= current_read_time &&
+ (!(s->key_dependent & rest_tables) || records < 2.0))
+ {
+ best_record_count= partial_join_cardinality;
+ best_read_time=current_read_time;
+ }
+ swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
+ if (find_best(join,rest_tables & ~real_table_bit,idx+1,
+ partial_join_cardinality,current_read_time,
+ use_cond_selectivity))
+ DBUG_RETURN(TRUE);
+ swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
+ }
+ restore_prev_nj_state(s);
+ restore_prev_sj_state(rest_tables, s, idx);
+ if (join->select_options & SELECT_STRAIGHT_JOIN)
+ break; // Don't test all combinations
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Find how much space the prevous read not const tables takes in cache.
+*/
+
+void JOIN_TAB::calc_used_field_length(bool max_fl)
+{
+ uint null_fields,blobs,fields;
+ ulong rec_length;
+ Field **f_ptr,*field;
+ uint uneven_bit_fields;
+ MY_BITMAP *read_set= table->read_set;
+
+ uneven_bit_fields= null_fields= blobs= fields= rec_length=0;
+ for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
+ {
+ if (bitmap_is_set(read_set, field->field_index))
+ {
+ uint flags=field->flags;
+ fields++;
+ rec_length+=field->pack_length();
+ if (flags & BLOB_FLAG)
+ blobs++;
+ if (!(flags & NOT_NULL_FLAG))
+ null_fields++;
+ if (field->type() == MYSQL_TYPE_BIT &&
+ ((Field_bit*)field)->bit_len)
+ uneven_bit_fields++;
+ }
+ }
+ if (null_fields || uneven_bit_fields)
+ rec_length+=(table->s->null_fields+7)/8;
+ if (table->maybe_null)
+ rec_length+=sizeof(my_bool);
+
+ /* Take into account that DuplicateElimination may need to store rowid */
+ uint rowid_add_size= 0;
+ if (keep_current_rowid)
+ {
+ rowid_add_size= table->file->ref_length;
+ rec_length += rowid_add_size;
+ fields++;
+ }
+
+ if (max_fl)
+ {
+ // TODO: to improve this estimate for max expected length
+ if (blobs)
+ {
+ ulong blob_length= table->file->stats.mean_rec_length;
+ if (ULONG_MAX - rec_length > blob_length)
+ rec_length+= blob_length;
+ else
+ rec_length= ULONG_MAX;
+ }
+ max_used_fieldlength= rec_length;
+ }
+ else if (table->file->stats.mean_rec_length)
+ set_if_smaller(rec_length, table->file->stats.mean_rec_length + rowid_add_size);
+
+ used_fields=fields;
+ used_fieldlength=rec_length;
+ used_blobs=blobs;
+ used_null_fields= null_fields;
+ used_uneven_bit_fields= uneven_bit_fields;
+}
+
+
+/*
+ @brief
+ Extract pushdown conditions for a table scan
+
+ @details
+ This functions extracts pushdown conditions usable when this table is scanned.
+ The conditions are extracted either from WHERE or from ON expressions.
+ The conditions are attached to the field cache_select of this table.
+
+ @note
+ Currently the extracted conditions are used only by BNL and BNLH join.
+ algorithms.
+
+ @retval 0 on success
+ 1 otherwise
+*/
+
+int JOIN_TAB::make_scan_filter()
+{
+ COND *tmp;
+ DBUG_ENTER("make_scan_filter");
+
+ Item *cond= is_inner_table_of_outer_join() ?
+ *get_first_inner_table()->on_expr_ref : join->conds;
+
+ if (cond &&
+ (tmp= make_cond_for_table(join->thd, cond,
+ join->const_table_map | table->map,
+ table->map, -1, FALSE, TRUE)))
+ {
+ DBUG_EXECUTE("where",print_where(tmp,"cache", QT_ORDINARY););
+ if (!(cache_select=
+ (SQL_SELECT*) join->thd->memdup((uchar*) select, sizeof(SQL_SELECT))))
+ DBUG_RETURN(1);
+ cache_select->cond= tmp;
+ cache_select->read_tables=join->const_table_map;
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Check whether hash join algorithm can be used to join this table
+
+ @details
+ This function finds out whether the ref items that have been chosen
+ by the planner to access this table can be used for hash join algorithms.
+ The answer depends on a certain property of the the fields of the
+ joined tables on which the hash join key is built.
+
+ @note
+ At present the function is supposed to be called only after the function
+ get_best_combination has been called.
+
+ @retval TRUE it's possible to use hash join to join this table
+ @retval FALSE otherwise
+*/
+
+bool JOIN_TAB::hash_join_is_possible()
+{
+ if (type != JT_REF && type != JT_EQ_REF)
+ return FALSE;
+ if (!is_ref_for_hash_join())
+ {
+ KEY *keyinfo= table->key_info + ref.key;
+ return keyinfo->key_part[0].field->hash_join_is_possible();
+ }
+ return TRUE;
+}
+
+
+static uint
+cache_record_length(JOIN *join,uint idx)
+{
+ uint length=0;
+ JOIN_TAB **pos,**end;
+
+ for (pos=join->best_ref+join->const_tables,end=join->best_ref+idx ;
+ pos != end ;
+ pos++)
+ {
+ JOIN_TAB *join_tab= *pos;
+ length+= join_tab->get_used_fieldlength();
+ }
+ return length;
+}
+
+
+/*
+ Get the number of different row combinations for subset of partial join
+
+ SYNOPSIS
+ prev_record_reads()
+ join The join structure
+ idx Number of tables in the partial join order (i.e. the
+ partial join order is in join->positions[0..idx-1])
+ found_ref Bitmap of tables for which we need to find # of distinct
+ row combinations.
+
+ DESCRIPTION
+ Given a partial join order (in join->positions[0..idx-1]) and a subset of
+ tables within that join order (specified in found_ref), find out how many
+ distinct row combinations of subset tables will be in the result of the
+ partial join order.
+
+ This is used as follows: Suppose we have a table accessed with a ref-based
+ method. The ref access depends on current rows of tables in found_ref.
+ We want to count # of different ref accesses. We assume two ref accesses
+ will be different if at least one of access parameters is different.
+ Example: consider a query
+
+ SELECT * FROM t1, t2, t3 WHERE t1.key=c1 AND t2.key=c2 AND t3.key=t1.field
+
+ and a join order:
+ t1, ref access on t1.key=c1
+ t2, ref access on t2.key=c2
+ t3, ref access on t3.key=t1.field
+
+ For t1: n_ref_scans = 1, n_distinct_ref_scans = 1
+ For t2: n_ref_scans = records_read(t1), n_distinct_ref_scans=1
+ For t3: n_ref_scans = records_read(t1)*records_read(t2)
+ n_distinct_ref_scans = #records_read(t1)
+
+ The reason for having this function (at least the latest version of it)
+ is that we need to account for buffering in join execution.
+
+ An edge-case example: if we have a non-first table in join accessed via
+ ref(const) or ref(param) where there is a small number of different
+ values of param, then the access will likely hit the disk cache and will
+ not require any disk seeks.
+
+ The proper solution would be to assume an LRU disk cache of some size,
+ calculate probability of cache hits, etc. For now we just count
+ identical ref accesses as one.
+
+ RETURN
+ Expected number of row combinations
+*/
+
+double
+prev_record_reads(POSITION *positions, uint idx, table_map found_ref)
+{
+ double found=1.0;
+ POSITION *pos_end= positions - 1;
+ for (POSITION *pos= positions + idx - 1; pos != pos_end; pos--)
+ {
+ if (pos->table->table->map & found_ref)
+ {
+ found_ref|= pos->ref_depend_map;
+ /*
+ For the case of "t1 LEFT JOIN t2 ON ..." where t2 is a const table
+ with no matching row we will get position[t2].records_read==0.
+ Actually the size of output is one null-complemented row, therefore
+ we will use value of 1 whenever we get records_read==0.
+
+ Note
+ - the above case can't occur if inner part of outer join has more
+ than one table: table with no matches will not be marked as const.
+
+ - Ideally we should add 1 to records_read for every possible null-
+ complemented row. We're not doing it because: 1. it will require
+ non-trivial code and add overhead. 2. The value of records_read
+ is an inprecise estimate and adding 1 (or, in the worst case,
+ #max_nested_outer_joins=64-1) will not make it any more precise.
+ */
+ if (pos->records_read)
+ found*= pos->records_read;
+ }
+ }
+ return found;
+}
+
+
+/*
+ Enumerate join tabs in breadth-first fashion, including const tables.
+*/
+
+JOIN_TAB *first_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind)
+{
+ /* There's always one (i.e. first) table */
+ return (tabs_kind == WALK_EXECUTION_TABS)? join->join_tab:
+ join->table_access_tabs;
+}
+
+
+JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind,
+ JOIN_TAB *tab)
+{
+ JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, tabs_kind);
+ const uint n_top_tabs_count= (tabs_kind == WALK_EXECUTION_TABS)?
+ join->top_join_tab_count:
+ join->top_table_access_tabs_count;
+ if (!tab->bush_root_tab)
+ {
+ /* We're at top level. Get the next top-level tab */
+ tab++;
+ if (tab < first_top_tab + n_top_tabs_count)
+ return tab;
+
+ /* No more top-level tabs. Switch to enumerating SJM nest children */
+ tab= first_top_tab;
+ }
+ else
+ {
+ /* We're inside of an SJM nest */
+ if (!tab->last_leaf_in_bush)
+ {
+ /* There's one more table in the nest, return it. */
+ return ++tab;
+ }
+ else
+ {
+ /*
+ There are no more tables in this nest. Get out of it and then we'll
+ proceed to the next nest.
+ */
+ tab= tab->bush_root_tab + 1;
+ }
+ }
+
+ /*
+ Ok, "tab" points to a top-level table, and we need to find the next SJM
+ nest and enter it.
+ */
+ for (; tab < first_top_tab + n_top_tabs_count; tab++)
+ {
+ if (tab->bush_children)
+ return tab->bush_children->start;
+ }
+ return NULL;
+}
+
+
+JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables const_tbls)
+{
+ JOIN_TAB *tab= join->join_tab;
+ if (const_tbls == WITHOUT_CONST_TABLES)
+ {
+ if (join->const_tables == join->table_count)
+ return NULL;
+ tab += join->const_tables;
+ }
+ return tab;
+}
+
+
+JOIN_TAB *next_top_level_tab(JOIN *join, JOIN_TAB *tab)
+{
+ tab= next_breadth_first_tab(join, WALK_EXECUTION_TABS, tab);
+ if (tab && tab->bush_root_tab)
+ tab= NULL;
+ return tab;
+}
+
+
+JOIN_TAB *first_linear_tab(JOIN *join,
+ enum enum_with_bush_roots include_bush_roots,
+ enum enum_with_const_tables const_tbls)
+{
+ JOIN_TAB *first= join->join_tab;
+ if (const_tbls == WITHOUT_CONST_TABLES)
+ first+= join->const_tables;
+
+ if (first >= join->join_tab + join->top_join_tab_count)
+ return NULL; /* All are const tables */
+
+ if (first->bush_children && include_bush_roots == WITHOUT_BUSH_ROOTS)
+ {
+ /* This JOIN_TAB is a SJM nest; Start from first table in nest */
+ return first->bush_children->start;
+ }
+
+ return first;
+}
+
+
+/*
+ A helper function to loop over all join's join_tab in sequential fashion
+
+ DESCRIPTION
+ Depending on include_bush_roots parameter, JOIN_TABs that represent
+ SJM-scan/lookups are either returned or omitted.
+
+ SJM-Bush children are returned right after (or in place of) their container
+ join tab (TODO: does anybody depend on this? A: make_join_readinfo() seems
+ to)
+
+ For example, if we have this structure:
+
+ ot1--ot2--sjm1----------------ot3-...
+ |
+ +--it1--it2--it3
+
+ calls to next_linear_tab( include_bush_roots=TRUE) will return:
+
+ ot1 ot2 sjm1 it1 it2 it3 ot3 ...
+
+ while calls to next_linear_tab( include_bush_roots=FALSE) will return:
+
+ ot1 ot2 it1 it2 it3 ot3 ...
+
+ (note that sjm1 won't be returned).
+*/
+
+JOIN_TAB *next_linear_tab(JOIN* join, JOIN_TAB* tab,
+ enum enum_with_bush_roots include_bush_roots)
+{
+ if (include_bush_roots == WITH_BUSH_ROOTS && tab->bush_children)
+ {
+ /* This JOIN_TAB is a SJM nest; Start from first table in nest */
+ return tab->bush_children->start;
+ }
+
+ DBUG_ASSERT(!tab->last_leaf_in_bush || tab->bush_root_tab);
+
+ if (tab->bush_root_tab) /* Are we inside an SJM nest */
+ {
+ /* Inside SJM nest */
+ if (!tab->last_leaf_in_bush)
+ return tab+1; /* Return next in nest */
+ /* Continue from the sjm on the top level */
+ tab= tab->bush_root_tab;
+ }
+
+ /* If no more JOIN_TAB's on the top level */
+ if (++tab == join->join_tab + join->top_join_tab_count)
+ return NULL;
+
+ if (include_bush_roots == WITHOUT_BUSH_ROOTS && tab->bush_children)
+ {
+ /* This JOIN_TAB is a SJM nest; Start from first table in nest */
+ tab= tab->bush_children->start;
+ }
+ return tab;
+}
+
+
+/*
+ Start to iterate over all join tables in bush-children-first order, excluding
+ the const tables (see next_depth_first_tab() comment for details)
+*/
+
+JOIN_TAB *first_depth_first_tab(JOIN* join)
+{
+ JOIN_TAB* tab;
+ /* This means we're starting the enumeration */
+ if (join->const_tables == join->top_join_tab_count)
+ return NULL;
+
+ tab= join->join_tab + join->const_tables;
+
+ return (tab->bush_children) ? tab->bush_children->start : tab;
+}
+
+
+/*
+ A helper function to iterate over all join tables in bush-children-first order
+
+ DESCRIPTION
+
+ For example, for this join plan
+
+ ot1--ot2--sjm1------------ot3-...
+ |
+ |
+ it1--it2--it3
+
+ call to first_depth_first_tab() will return ot1, and subsequent calls to
+ next_depth_first_tab() will return:
+
+ ot2 it1 it2 it3 sjm ot3 ...
+*/
+
+JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab)
+{
+ /* If we're inside SJM nest and have reached its end, get out */
+ if (tab->last_leaf_in_bush)
+ return tab->bush_root_tab;
+
+ /* Move to next tab in the array we're traversing */
+ tab++;
+
+ if (tab == join->join_tab +join->top_join_tab_count)
+ return NULL; /* Outside SJM nest and reached EOF */
+
+ if (tab->bush_children)
+ return tab->bush_children->start;
+
+ return tab;
+}
+
+
+static Item * const null_ptr= NULL;
+
+/*
+ Set up join struct according to the picked join order in
+
+ SYNOPSIS
+ get_best_combination()
+ join The join to process (the picked join order is mainly in
+ join->best_positions)
+
+ DESCRIPTION
+ Setup join structures according the picked join order
+ - finalize semi-join strategy choices (see
+ fix_semijoin_strategies_for_picked_join_order)
+ - create join->join_tab array and put there the JOIN_TABs in the join order
+ - create data structures describing ref access methods.
+
+ NOTE
+ In this function we switch from pre-join-optimization JOIN_TABs to
+ post-join-optimization JOIN_TABs. This is achieved by copying the entire
+ JOIN_TAB objects.
+
+ RETURN
+ FALSE OK
+ TRUE Out of memory
+*/
+
+bool
+get_best_combination(JOIN *join)
+{
+ uint tablenr;
+ table_map used_tables;
+ JOIN_TAB *join_tab,*j;
+ KEYUSE *keyuse;
+ uint table_count;
+ THD *thd=join->thd;
+ DBUG_ENTER("get_best_combination");
+
+ table_count=join->table_count;
+ if (!(join->join_tab=join_tab=
+ (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB)*table_count)))
+ DBUG_RETURN(TRUE);
+
+ join->full_join=0;
+ join->hash_join= FALSE;
+
+ used_tables= OUTER_REF_TABLE_BIT; // Outer row is already read
+
+ fix_semijoin_strategies_for_picked_join_order(join);
+
+ JOIN_TAB_RANGE *root_range;
+ if (!(root_range= new JOIN_TAB_RANGE))
+ DBUG_RETURN(TRUE);
+ root_range->start= join->join_tab;
+ /* root_range->end will be set later */
+ join->join_tab_ranges.empty();
+
+ if (join->join_tab_ranges.push_back(root_range))
+ DBUG_RETURN(TRUE);
+
+ JOIN_TAB *sjm_nest_end= NULL;
+ JOIN_TAB *sjm_nest_root= NULL;
+
+ for (j=join_tab, tablenr=0 ; tablenr < table_count ; tablenr++,j++)
+ {
+ TABLE *form;
+ POSITION *cur_pos= &join->best_positions[tablenr];
+ if (cur_pos->sj_strategy == SJ_OPT_MATERIALIZE ||
+ cur_pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
+ {
+ /*
+ Ok, we've entered an SJ-Materialization semi-join (note that this can't
+ be done recursively, semi-joins are not allowed to be nested).
+ 1. Put into main join order a JOIN_TAB that represents a lookup or scan
+ in the temptable.
+ */
+ bzero(j, sizeof(JOIN_TAB));
+ j->join= join;
+ j->table= NULL; //temporary way to tell SJM tables from others.
+ j->ref.key = -1;
+ j->on_expr_ref= (Item**) &null_ptr;
+ j->keys= key_map(1); /* The unique index is always in 'possible keys' in EXPLAIN */
+
+ /*
+ 2. Proceed with processing SJM nest's join tabs, putting them into the
+ sub-order
+ */
+ SJ_MATERIALIZATION_INFO *sjm= cur_pos->table->emb_sj_nest->sj_mat_info;
+ j->records_read= (sjm->is_sj_scan? sjm->rows : 1);
+ j->records= (ha_rows) j->records_read;
+ j->cond_selectivity= 1.0;
+ JOIN_TAB *jt;
+ JOIN_TAB_RANGE *jt_range;
+ if (!(jt= (JOIN_TAB*)join->thd->alloc(sizeof(JOIN_TAB)*sjm->tables)) ||
+ !(jt_range= new JOIN_TAB_RANGE))
+ DBUG_RETURN(TRUE);
+ jt_range->start= jt;
+ jt_range->end= jt + sjm->tables;
+ join->join_tab_ranges.push_back(jt_range);
+ j->bush_children= jt_range;
+ sjm_nest_end= jt + sjm->tables;
+ sjm_nest_root= j;
+
+ j= jt;
+ }
+
+ *j= *join->best_positions[tablenr].table;
+
+ j->bush_root_tab= sjm_nest_root;
+
+ form=join->table[tablenr]=j->table;
+ used_tables|= form->map;
+ form->reginfo.join_tab=j;
+ if (!*j->on_expr_ref)
+ form->reginfo.not_exists_optimize=0; // Only with LEFT JOIN
+ DBUG_PRINT("info",("type: %d", j->type));
+ if (j->type == JT_CONST)
+ goto loop_end; // Handled in make_join_stat..
+
+ j->loosescan_match_tab= NULL; //non-nulls will be set later
+ j->inside_loosescan_range= FALSE;
+ j->ref.key = -1;
+ j->ref.key_parts=0;
+
+ if (j->type == JT_SYSTEM)
+ goto loop_end;
+ if ( !(keyuse= join->best_positions[tablenr].key))
+ {
+ j->type=JT_ALL;
+ if (join->best_positions[tablenr].use_join_buffer &&
+ tablenr != join->const_tables)
+ join->full_join= 1;
+ }
+
+ /*if (join->best_positions[tablenr].sj_strategy == SJ_OPT_LOOSE_SCAN)
+ {
+ DBUG_ASSERT(!keyuse || keyuse->key ==
+ join->best_positions[tablenr].loosescan_picker.loosescan_key);
+ j->index= join->best_positions[tablenr].loosescan_picker.loosescan_key;
+ }*/
+
+ if (keyuse && create_ref_for_key(join, j, keyuse, TRUE, used_tables))
+ DBUG_RETURN(TRUE); // Something went wrong
+
+ if ((j->type == JT_REF || j->type == JT_EQ_REF) &&
+ is_hash_join_key_no(j->ref.key))
+ join->hash_join= TRUE;
+
+ loop_end:
+ /*
+ Save records_read in JOIN_TAB so that select_describe()/etc don't have
+ to access join->best_positions[].
+ */
+ j->records_read= join->best_positions[tablenr].records_read;
+ j->cond_selectivity= join->best_positions[tablenr].cond_selectivity;
+ join->map2table[j->table->tablenr]= j;
+
+ /* If we've reached the end of sjm nest, switch back to main sequence */
+ if (j + 1 == sjm_nest_end)
+ {
+ j->last_leaf_in_bush= TRUE;
+ j= sjm_nest_root;
+ sjm_nest_root= NULL;
+ sjm_nest_end= NULL;
+ }
+ }
+ root_range->end= j;
+
+ join->top_join_tab_count= join->join_tab_ranges.head()->end -
+ join->join_tab_ranges.head()->start;
+ /*
+ Save pointers to select join tabs for SHOW EXPLAIN
+ */
+ join->table_access_tabs= join->join_tab;
+ join->top_table_access_tabs_count= join->top_join_tab_count;
+
+
+ update_depend_map(join);
+ DBUG_RETURN(0);
+}
+
+/**
+ Create a descriptor of hash join key to access a given join table
+
+ @param join join which the join table belongs to
+ @param join_tab the join table to access
+ @param org_keyuse beginning of the key uses to join this table
+ @param used_tables bitmap of the previous tables
+
+ @details
+ This function first finds key uses that can be utilized by the hash join
+ algorithm to join join_tab to the previous tables marked in the bitmap
+ used_tables. The tested key uses are taken from the array of all key uses
+ for 'join' starting from the position org_keyuse. After all interesting key
+ uses have been found the function builds a descriptor of the corresponding
+ key that is used by the hash join algorithm would it be chosen to join
+ the table join_tab.
+
+ @retval FALSE the descriptor for a hash join key is successfully created
+ @retval TRUE otherwise
+*/
+
+static bool create_hj_key_for_table(JOIN *join, JOIN_TAB *join_tab,
+ KEYUSE *org_keyuse, table_map used_tables)
+{
+ KEY *keyinfo;
+ KEY_PART_INFO *key_part_info;
+ KEYUSE *keyuse= org_keyuse;
+ uint key_parts= 0;
+ THD *thd= join->thd;
+ TABLE *table= join_tab->table;
+ bool first_keyuse= TRUE;
+ DBUG_ENTER("create_hj_key_for_table");
+
+ do
+ {
+ if (!(~used_tables & keyuse->used_tables) &&
+ are_tables_local(join_tab, keyuse->used_tables))
+ {
+ if (first_keyuse)
+ {
+ key_parts++;
+ first_keyuse= FALSE;
+ }
+ else
+ {
+ KEYUSE *curr= org_keyuse;
+ for( ; curr < keyuse; curr++)
+ {
+ if (curr->keypart == keyuse->keypart &&
+ !(~used_tables & curr->used_tables) &&
+ are_tables_local(join_tab, curr->used_tables))
+ break;
+ }
+ if (curr == keyuse)
+ key_parts++;
+ }
+ }
+ keyuse++;
+ } while (keyuse->table == table && keyuse->is_for_hash_join());
+ if (!key_parts)
+ DBUG_RETURN(TRUE);
+ /* This memory is allocated only once for the joined table join_tab */
+ if (!(keyinfo= (KEY *) thd->alloc(sizeof(KEY))) ||
+ !(key_part_info = (KEY_PART_INFO *) thd->alloc(sizeof(KEY_PART_INFO)*
+ key_parts)))
+ DBUG_RETURN(TRUE);
+ keyinfo->usable_key_parts= keyinfo->user_defined_key_parts = key_parts;
+ keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
+ keyinfo->key_part= key_part_info;
+ keyinfo->key_length=0;
+ keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+ keyinfo->flags= HA_GENERATED_KEY;
+ keyinfo->is_statistics_from_stat_tables= FALSE;
+ keyinfo->name= (char *) "$hj";
+ keyinfo->rec_per_key= (ulong*) thd->calloc(sizeof(ulong)*key_parts);
+ if (!keyinfo->rec_per_key)
+ DBUG_RETURN(TRUE);
+ keyinfo->key_part= key_part_info;
+
+ first_keyuse= TRUE;
+ keyuse= org_keyuse;
+ do
+ {
+ if (!(~used_tables & keyuse->used_tables) &&
+ are_tables_local(join_tab, keyuse->used_tables))
+ {
+ bool add_key_part= TRUE;
+ if (!first_keyuse)
+ {
+ for(KEYUSE *curr= org_keyuse; curr < keyuse; curr++)
+ {
+ if (curr->keypart == keyuse->keypart &&
+ !(~used_tables & curr->used_tables) &&
+ are_tables_local(join_tab, curr->used_tables))
+ {
+ keyuse->keypart= NO_KEYPART;
+ add_key_part= FALSE;
+ break;
+ }
+ }
+ }
+ if (add_key_part)
+ {
+ Field *field= table->field[keyuse->keypart];
+ uint fieldnr= keyuse->keypart+1;
+ table->create_key_part_by_field(key_part_info, field, fieldnr);
+ keyinfo->key_length += key_part_info->store_length;
+ key_part_info++;
+ }
+ }
+ first_keyuse= FALSE;
+ keyuse++;
+ } while (keyuse->table == table && keyuse->is_for_hash_join());
+
+ keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
+ keyinfo->ext_key_flags= keyinfo->flags;
+ keyinfo->ext_key_part_map= 0;
+
+ join_tab->hj_key= keyinfo;
+
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ Check if a set of tables specified by used_tables can be accessed when
+ we're doing scan on join_tab jtab.
+*/
+static bool are_tables_local(JOIN_TAB *jtab, table_map used_tables)
+{
+ if (jtab->bush_root_tab)
+ {
+ /*
+ jtab is inside execution join nest. We may not refer to outside tables,
+ except the const tables.
+ */
+ table_map local_tables= jtab->emb_sj_nest->nested_join->used_tables |
+ jtab->join->const_table_map |
+ OUTER_REF_TABLE_BIT;
+ return !MY_TEST(used_tables & ~local_tables);
+ }
+
+ /*
+ If we got here then jtab is at top level.
+ - all other tables at top level are accessible,
+ - tables in join nests are accessible too, because all their columns that
+ are needed at top level will be unpacked when scanning the
+ materialization table.
+ */
+ return TRUE;
+}
+
+static bool create_ref_for_key(JOIN *join, JOIN_TAB *j,
+ KEYUSE *org_keyuse, bool allow_full_scan,
+ table_map used_tables)
+{
+ uint keyparts, length, key;
+ TABLE *table;
+ KEY *keyinfo;
+ KEYUSE *keyuse= org_keyuse;
+ bool ftkey= (keyuse->keypart == FT_KEYPART);
+ THD *thd= join->thd;
+ DBUG_ENTER("create_ref_for_key");
+
+ /* Use best key from find_best */
+ table= j->table;
+ key= keyuse->key;
+ if (!is_hash_join_key_no(key))
+ keyinfo= table->key_info+key;
+ else
+ {
+ if (create_hj_key_for_table(join, j, org_keyuse, used_tables))
+ DBUG_RETURN(TRUE);
+ keyinfo= j->hj_key;
+ }
+
+ if (ftkey)
+ {
+ Item_func_match *ifm=(Item_func_match *)keyuse->val;
+
+ length=0;
+ keyparts=1;
+ ifm->join_key=1;
+ }
+ else
+ {
+ keyparts=length=0;
+ uint found_part_ref_or_null= 0;
+ /*
+ Calculate length for the used key
+ Stop if there is a missing key part or when we find second key_part
+ with KEY_OPTIMIZE_REF_OR_NULL
+ */
+ do
+ {
+ if (!(~used_tables & keyuse->used_tables) &&
+ j->access_from_tables_is_allowed(keyuse->used_tables,
+ join->sjm_lookup_tables))
+ {
+ if (are_tables_local(j, keyuse->val->used_tables()))
+ {
+ if ((is_hash_join_key_no(key) && keyuse->keypart != NO_KEYPART) ||
+ (!is_hash_join_key_no(key) && keyparts == keyuse->keypart &&
+ !(found_part_ref_or_null & keyuse->optimize)))
+ {
+ length+= keyinfo->key_part[keyparts].store_length;
+ keyparts++;
+ found_part_ref_or_null|= keyuse->optimize & ~KEY_OPTIMIZE_EQ;
+ }
+ }
+ }
+ keyuse++;
+ } while (keyuse->table == table && keyuse->key == key);
+
+ if (!keyparts && allow_full_scan)
+ {
+ /* It's a LooseIndexScan strategy scanning whole index */
+ j->type= JT_ALL;
+ j->index= key;
+ DBUG_RETURN(FALSE);
+ }
+
+ DBUG_ASSERT(length > 0);
+ DBUG_ASSERT(keyparts != 0);
+ } /* not ftkey */
+
+ /* set up fieldref */
+ j->ref.key_parts= keyparts;
+ j->ref.key_length= length;
+ j->ref.key= (int) key;
+ if (!(j->ref.key_buff= (uchar*) thd->calloc(ALIGN_SIZE(length)*2)) ||
+ !(j->ref.key_copy= (store_key**) thd->alloc((sizeof(store_key*) *
+ (keyparts+1)))) ||
+ !(j->ref.items=(Item**) thd->alloc(sizeof(Item*)*keyparts)) ||
+ !(j->ref.cond_guards= (bool**) thd->alloc(sizeof(uint*)*keyparts)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ j->ref.key_buff2=j->ref.key_buff+ALIGN_SIZE(length);
+ j->ref.key_err=1;
+ j->ref.has_record= FALSE;
+ j->ref.null_rejecting= 0;
+ j->ref.disable_cache= FALSE;
+ j->ref.null_ref_part= NO_REF_PART;
+ j->ref.const_ref_part_map= 0;
+ keyuse=org_keyuse;
+
+ store_key **ref_key= j->ref.key_copy;
+ uchar *key_buff=j->ref.key_buff, *null_ref_key= 0;
+ uint null_ref_part= NO_REF_PART;
+ bool keyuse_uses_no_tables= TRUE;
+ if (ftkey)
+ {
+ j->ref.items[0]=((Item_func*)(keyuse->val))->key_item();
+ /* Predicates pushed down into subquery can't be used FT access */
+ j->ref.cond_guards[0]= NULL;
+ if (keyuse->used_tables)
+ DBUG_RETURN(TRUE); // not supported yet. SerG
+
+ j->type=JT_FT;
+ }
+ else
+ {
+ uint i;
+ for (i=0 ; i < keyparts ; keyuse++,i++)
+ {
+ while (((~used_tables) & keyuse->used_tables) ||
+ !j->access_from_tables_is_allowed(keyuse->used_tables,
+ join->sjm_lookup_tables) ||
+ keyuse->keypart == NO_KEYPART ||
+ (keyuse->keypart !=
+ (is_hash_join_key_no(key) ?
+ keyinfo->key_part[i].field->field_index : i)) ||
+ !are_tables_local(j, keyuse->val->used_tables()))
+ keyuse++; /* Skip other parts */
+
+ uint maybe_null= MY_TEST(keyinfo->key_part[i].null_bit);
+ j->ref.items[i]=keyuse->val; // Save for cond removal
+ j->ref.cond_guards[i]= keyuse->cond_guard;
+ if (keyuse->null_rejecting)
+ j->ref.null_rejecting|= (key_part_map)1 << i;
+ keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables;
+ /*
+ Todo: we should remove this check for thd->lex->describe on the next
+ line. With SHOW EXPLAIN code, EXPLAIN printout code no longer depends
+ on it. However, removing the check caused change in lots of query
+ plans! Does the optimizer depend on the contents of
+ table_ref->key_copy ? If yes, do we produce incorrect EXPLAINs?
+ */
+ if (!keyuse->val->used_tables() && !thd->lex->describe)
+ { // Compare against constant
+ store_key_item tmp(thd,
+ keyinfo->key_part[i].field,
+ key_buff + maybe_null,
+ maybe_null ? key_buff : 0,
+ keyinfo->key_part[i].length,
+ keyuse->val,
+ FALSE);
+ if (thd->is_fatal_error)
+ DBUG_RETURN(TRUE);
+ tmp.copy();
+ j->ref.const_ref_part_map |= key_part_map(1) << i ;
+ }
+ else
+ *ref_key++= get_store_key(thd,
+ keyuse,join->const_table_map,
+ &keyinfo->key_part[i],
+ key_buff, maybe_null);
+ /*
+ Remember if we are going to use REF_OR_NULL
+ But only if field _really_ can be null i.e. we force JT_REF
+ instead of JT_REF_OR_NULL in case if field can't be null
+ */
+ if ((keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL) && maybe_null)
+ {
+ null_ref_key= key_buff;
+ null_ref_part= i;
+ }
+ key_buff+= keyinfo->key_part[i].store_length;
+ }
+ } /* not ftkey */
+ *ref_key=0; // end_marker
+ if (j->type == JT_FT)
+ DBUG_RETURN(0);
+ ulong key_flags= j->table->actual_key_flags(keyinfo);
+ if (j->type == JT_CONST)
+ j->table->const_table= 1;
+ else if (!((keyparts == keyinfo->user_defined_key_parts &&
+ ((key_flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME)) ||
+ (keyparts > keyinfo->user_defined_key_parts && // true only for extended keys
+ MY_TEST(key_flags & HA_EXT_NOSAME) &&
+ keyparts == keyinfo->ext_key_parts)) ||
+ null_ref_key)
+ {
+ /* Must read with repeat */
+ j->type= null_ref_key ? JT_REF_OR_NULL : JT_REF;
+ j->ref.null_ref_key= null_ref_key;
+ j->ref.null_ref_part= null_ref_part;
+ }
+ else if (keyuse_uses_no_tables)
+ {
+ /*
+ This happen if we are using a constant expression in the ON part
+ of an LEFT JOIN.
+ SELECT * FROM a LEFT JOIN b ON b.key=30
+ Here we should not mark the table as a 'const' as a field may
+ have a 'normal' value or a NULL value.
+ */
+ j->type=JT_CONST;
+ }
+ else
+ j->type=JT_EQ_REF;
+
+ j->read_record.unlock_row= (j->type == JT_EQ_REF)?
+ join_read_key_unlock_row : rr_unlock_row;
+ DBUG_RETURN(0);
+}
+
+
+
+static store_key *
+get_store_key(THD *thd, KEYUSE *keyuse, table_map used_tables,
+ KEY_PART_INFO *key_part, uchar *key_buff, uint maybe_null)
+{
+ if (!((~used_tables) & keyuse->used_tables)) // if const item
+ {
+ return new store_key_const_item(thd,
+ key_part->field,
+ key_buff + maybe_null,
+ maybe_null ? key_buff : 0,
+ key_part->length,
+ keyuse->val);
+ }
+ else if (keyuse->val->type() == Item::FIELD_ITEM ||
+ (keyuse->val->type() == Item::REF_ITEM &&
+ ((((Item_ref*)keyuse->val)->ref_type() == Item_ref::OUTER_REF &&
+ (*(Item_ref**)((Item_ref*)keyuse->val)->ref)->ref_type() ==
+ Item_ref::DIRECT_REF) ||
+ ((Item_ref*)keyuse->val)->ref_type() == Item_ref::VIEW_REF) &&
+ keyuse->val->real_item()->type() == Item::FIELD_ITEM))
+ return new store_key_field(thd,
+ key_part->field,
+ key_buff + maybe_null,
+ maybe_null ? key_buff : 0,
+ key_part->length,
+ ((Item_field*) keyuse->val->real_item())->field,
+ keyuse->val->real_item()->full_name());
+
+ return new store_key_item(thd,
+ key_part->field,
+ key_buff + maybe_null,
+ maybe_null ? key_buff : 0,
+ key_part->length,
+ keyuse->val, FALSE);
+}
+
+/**
+ @details Initialize a JOIN as a query execution plan
+ that accesses a single table via a table scan.
+
+ @param parent contains JOIN_TAB and TABLE object buffers for this join
+ @param tmp_table temporary table
+
+ @retval FALSE success
+ @retval TRUE error occurred
+*/
+bool
+JOIN::make_simple_join(JOIN *parent, TABLE *temp_table)
+{
+ DBUG_ENTER("JOIN::make_simple_join");
+
+ /*
+ Reuse TABLE * and JOIN_TAB if already allocated by a previous call
+ to this function through JOIN::exec (may happen for sub-queries).
+ */
+ if (!parent->join_tab_reexec &&
+ !(parent->join_tab_reexec= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB))))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ // psergey-todo: here, save the pointer for original join_tabs.
+ join_tab= parent->join_tab_reexec;
+ table= &parent->table_reexec[0]; parent->table_reexec[0]= temp_table;
+ table_count= top_join_tab_count= 1;
+
+ const_tables= 0;
+ const_table_map= 0;
+ eliminated_tables= 0;
+ tmp_table_param.field_count= tmp_table_param.sum_func_count=
+ tmp_table_param.func_count= 0;
+ /*
+ We need to destruct the copy_field (allocated in create_tmp_table())
+ before setting it to 0 if the join is not "reusable".
+ */
+ if (!tmp_join || tmp_join != this)
+ tmp_table_param.cleanup();
+ tmp_table_param.copy_field= tmp_table_param.copy_field_end=0;
+ first_record= sort_and_group=0;
+ send_records= (ha_rows) 0;
+
+ if (group_optimized_away && !tmp_table_param.precomputed_group_by)
+ {
+ /*
+ If grouping has been optimized away, a temporary table is
+ normally not needed unless we're explicitly requested to create
+ one (e.g. due to a SQL_BUFFER_RESULT hint or INSERT ... SELECT).
+
+ In this case (grouping was optimized away), temp_table was
+ created without a grouping expression and JOIN::exec() will not
+ perform the necessary grouping (by the use of end_send_group()
+ or end_write_group()) if JOIN::group is set to false.
+
+ There is one exception: if the loose index scan access method is
+ used to read into the temporary table, grouping and aggregate
+ functions are handled.
+ */
+ // the temporary table was explicitly requested
+ DBUG_ASSERT(MY_TEST(select_options & OPTION_BUFFER_RESULT));
+ // the temporary table does not have a grouping expression
+ DBUG_ASSERT(!temp_table->group);
+ }
+ else
+ group= false;
+
+ row_limit= unit->select_limit_cnt;
+ do_send_rows= row_limit ? 1 : 0;
+
+ bzero(join_tab, sizeof(JOIN_TAB));
+ join_tab->table=temp_table;
+ join_tab->set_select_cond(NULL, __LINE__);
+ join_tab->type= JT_ALL; /* Map through all records */
+ join_tab->keys.init();
+ join_tab->keys.set_all(); /* test everything in quick */
+ join_tab->ref.key = -1;
+ join_tab->shortcut_for_distinct= false;
+ join_tab->read_first_record= join_init_read_record;
+ join_tab->join= this;
+ join_tab->ref.key_parts= 0;
+ bzero((char*) &join_tab->read_record,sizeof(join_tab->read_record));
+ temp_table->status=0;
+ temp_table->null_row=0;
+ DBUG_RETURN(FALSE);
+}
+
+
+inline void add_cond_and_fix(THD *thd, Item **e1, Item *e2)
+{
+ if (*e1)
+ {
+ if (!e2)
+ return;
+ Item *res;
+ if ((res= new Item_cond_and(*e1, e2)))
+ {
+ res->fix_fields(thd, 0);
+ res->update_used_tables();
+ *e1= res;
+ }
+ }
+ else
+ *e1= e2;
+}
+
+
+/**
+ Add to join_tab->select_cond[i] "table.field IS NOT NULL" conditions
+ we've inferred from ref/eq_ref access performed.
+
+ This function is a part of "Early NULL-values filtering for ref access"
+ optimization.
+
+ Example of this optimization:
+ For query SELECT * FROM t1,t2 WHERE t2.key=t1.field @n
+ and plan " any-access(t1), ref(t2.key=t1.field) " @n
+ add "t1.field IS NOT NULL" to t1's table condition. @n
+
+ Description of the optimization:
+
+ We look through equalities choosen to perform ref/eq_ref access,
+ pick equalities that have form "tbl.part_of_key = othertbl.field"
+ (where othertbl is a non-const table and othertbl.field may be NULL)
+ and add them to conditions on correspoding tables (othertbl in this
+ example).
+
+ Exception from that is the case when referred_tab->join != join.
+ I.e. don't add NOT NULL constraints from any embedded subquery.
+ Consider this query:
+ @code
+ SELECT A.f2 FROM t1 LEFT JOIN t2 A ON A.f2 = f1
+ WHERE A.f3=(SELECT MIN(f3) FROM t2 C WHERE A.f4 = C.f4) OR A.f3 IS NULL;
+ @endocde
+ Here condition A.f3 IS NOT NULL is going to be added to the WHERE
+ condition of the embedding query.
+ Another example:
+ SELECT * FROM t10, t11 WHERE (t10.a < 10 OR t10.a IS NULL)
+ AND t11.b <=> t10.b AND (t11.a = (SELECT MAX(a) FROM t12
+ WHERE t12.b = t10.a ));
+ Here condition t10.a IS NOT NULL is going to be added.
+ In both cases addition of NOT NULL condition will erroneously reject
+ some rows of the result set.
+ referred_tab->join != join constraint would disallow such additions.
+
+ This optimization doesn't affect the choices that ref, range, or join
+ optimizer make. This was intentional because this was added after 4.1
+ was GA.
+
+ Implementation overview
+ 1. update_ref_and_keys() accumulates info about null-rejecting
+ predicates in in KEY_FIELD::null_rejecting
+ 1.1 add_key_part saves these to KEYUSE.
+ 2. create_ref_for_key copies them to TABLE_REF.
+ 3. add_not_null_conds adds "x IS NOT NULL" to join_tab->select_cond of
+ appropiate JOIN_TAB members.
+*/
+
+static void add_not_null_conds(JOIN *join)
+{
+ JOIN_TAB *tab;
+ DBUG_ENTER("add_not_null_conds");
+
+ for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+ if (tab->type == JT_REF || tab->type == JT_EQ_REF ||
+ tab->type == JT_REF_OR_NULL)
+ {
+ for (uint keypart= 0; keypart < tab->ref.key_parts; keypart++)
+ {
+ if (tab->ref.null_rejecting & ((key_part_map)1 << keypart))
+ {
+ Item *item= tab->ref.items[keypart];
+ Item *notnull;
+ Item *real= item->real_item();
+ if (real->const_item() && real->type() != Item::FIELD_ITEM &&
+ !real->is_expensive())
+ {
+ /*
+ It could be constant instead of field after constant
+ propagation.
+ */
+ continue;
+ }
+ DBUG_ASSERT(real->type() == Item::FIELD_ITEM);
+ Item_field *not_null_item= (Item_field*)real;
+ JOIN_TAB *referred_tab= not_null_item->field->table->reginfo.join_tab;
+ /*
+ For UPDATE queries such as:
+ UPDATE t1 SET t1.f2=(SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1);
+ not_null_item is the t1.f1, but it's referred_tab is 0.
+ */
+ if (!referred_tab)
+ continue;
+ if (!(notnull= new Item_func_isnotnull(not_null_item)))
+ DBUG_VOID_RETURN;
+ /*
+ We need to do full fix_fields() call here in order to have correct
+ notnull->const_item(). This is needed e.g. by test_quick_select
+ when it is called from make_join_select after this function is
+ called.
+ */
+ if (notnull->fix_fields(join->thd, &notnull))
+ DBUG_VOID_RETURN;
+ DBUG_EXECUTE("where",print_where(notnull,
+ referred_tab->table->alias.c_ptr(),
+ QT_ORDINARY););
+ if (!tab->first_inner)
+ {
+ COND *new_cond= referred_tab->join == join ?
+ referred_tab->select_cond :
+ join->outer_ref_cond;
+ add_cond_and_fix(join->thd, &new_cond, notnull);
+ if (referred_tab->join == join)
+ referred_tab->set_select_cond(new_cond, __LINE__);
+ else
+ join->outer_ref_cond= new_cond;
+ }
+ else
+ add_cond_and_fix(join->thd, tab->first_inner->on_expr_ref, notnull);
+ }
+ }
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Build a predicate guarded by match variables for embedding outer joins.
+ The function recursively adds guards for predicate cond
+ assending from tab to the first inner table next embedding
+ nested outer join and so on until it reaches root_tab
+ (root_tab can be 0).
+
+ In other words:
+ add_found_match_trig_cond(tab->first_inner_tab, y, 0) is the way one should
+ wrap parts of WHERE. The idea is that the part of WHERE should be only
+ evaluated after we've finished figuring out whether outer joins.
+ ^^^ is the above correct?
+
+ @param tab the first inner table for most nested outer join
+ @param cond the predicate to be guarded (must be set)
+ @param root_tab the first inner table to stop
+
+ @return
+ - pointer to the guarded predicate, if success
+ - 0, otherwise
+*/
+
+static COND*
+add_found_match_trig_cond(JOIN_TAB *tab, COND *cond, JOIN_TAB *root_tab)
+{
+ COND *tmp;
+ DBUG_ASSERT(cond != 0);
+ if (tab == root_tab)
+ return cond;
+ if ((tmp= add_found_match_trig_cond(tab->first_upper, cond, root_tab)))
+ tmp= new Item_func_trig_cond(tmp, &tab->found);
+ if (tmp)
+ {
+ tmp->quick_fix_field();
+ tmp->update_used_tables();
+ }
+ return tmp;
+}
+
+
+bool TABLE_LIST::is_active_sjm()
+{
+ return sj_mat_info && sj_mat_info->is_used;
+}
+
+
+/**
+ Fill in outer join related info for the execution plan structure.
+
+ For each outer join operation left after simplification of the
+ original query the function set up the following pointers in the linear
+ structure join->join_tab representing the selected execution plan.
+ The first inner table t0 for the operation is set to refer to the last
+ inner table tk through the field t0->last_inner.
+ Any inner table ti for the operation are set to refer to the first
+ inner table ti->first_inner.
+ The first inner table t0 for the operation is set to refer to the
+ first inner table of the embedding outer join operation, if there is any,
+ through the field t0->first_upper.
+ The on expression for the outer join operation is attached to the
+ corresponding first inner table through the field t0->on_expr_ref.
+ Here ti are structures of the JOIN_TAB type.
+
+ In other words, for each join tab, set
+ - first_inner
+ - last_inner
+ - first_upper
+ - on_expr_ref, cond_equal
+
+ EXAMPLE. For the query:
+ @code
+ SELECT * FROM t1
+ LEFT JOIN
+ (t2, t3 LEFT JOIN t4 ON t3.a=t4.a)
+ ON (t1.a=t2.a AND t1.b=t3.b)
+ WHERE t1.c > 5,
+ @endcode
+
+ given the execution plan with the table order t1,t2,t3,t4
+ is selected, the following references will be set;
+ t4->last_inner=[t4], t4->first_inner=[t4], t4->first_upper=[t2]
+ t2->last_inner=[t4], t2->first_inner=t3->first_inner=[t2],
+ on expression (t1.a=t2.a AND t1.b=t3.b) will be attached to
+ *t2->on_expr_ref, while t3.a=t4.a will be attached to *t4->on_expr_ref.
+
+ @param join reference to the info fully describing the query
+
+ @note
+ The function assumes that the simplification procedure has been
+ already applied to the join query (see simplify_joins).
+ This function can be called only after the execution plan
+ has been chosen.
+*/
+
+static bool
+make_outerjoin_info(JOIN *join)
+{
+ DBUG_ENTER("make_outerjoin_info");
+
+ /*
+ Create temp. tables for merged SJ-Materialization nests. We need to do
+ this now, because further code relies on tab->table and
+ tab->table->pos_in_table_list being set.
+ */
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+ if (tab->bush_children)
+ {
+ if (setup_sj_materialization_part1(tab))
+ DBUG_RETURN(TRUE);
+ tab->table->reginfo.join_tab= tab;
+ }
+ }
+
+ for (JOIN_TAB *tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab;
+ tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+ TABLE *table= tab->table;
+ TABLE_LIST *tbl= table->pos_in_table_list;
+ TABLE_LIST *embedding= tbl->embedding;
+
+ if (tbl->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT))
+ {
+ /*
+ Table tab is the only one inner table for outer join.
+ (Like table t4 for the table reference t3 LEFT JOIN t4 ON t3.a=t4.a
+ is in the query above.)
+ */
+ tab->last_inner= tab->first_inner= tab;
+ tab->on_expr_ref= &tbl->on_expr;
+ tab->cond_equal= tbl->cond_equal;
+ if (embedding && !embedding->is_active_sjm())
+ tab->first_upper= embedding->nested_join->first_nested;
+ }
+ for ( ; embedding ; embedding= embedding->embedding)
+ {
+ if (embedding->is_active_sjm())
+ {
+ /* We're trying to walk out of an SJ-Materialization nest. Don't do this. */
+ break;
+ }
+ /* Ignore sj-nests: */
+ if (!(embedding->on_expr && embedding->outer_join))
+ continue;
+ NESTED_JOIN *nested_join= embedding->nested_join;
+ if (!nested_join->counter)
+ {
+ /*
+ Table tab is the first inner table for nested_join.
+ Save reference to it in the nested join structure.
+ */
+ nested_join->first_nested= tab;
+ tab->on_expr_ref= &embedding->on_expr;
+ tab->cond_equal= tbl->cond_equal;
+ if (embedding->embedding)
+ tab->first_upper= embedding->embedding->nested_join->first_nested;
+ }
+ if (!tab->first_inner)
+ tab->first_inner= nested_join->first_nested;
+ if (tab->table->reginfo.not_exists_optimize)
+ tab->first_inner->table->reginfo.not_exists_optimize= 1;
+ if (++nested_join->counter < nested_join->n_tables)
+ break;
+ /* Table tab is the last inner table for nested join. */
+ nested_join->first_nested->last_inner= tab;
+ if (tab->first_inner->table->reginfo.not_exists_optimize)
+ {
+ for (JOIN_TAB *join_tab= tab->first_inner; join_tab <= tab; join_tab++)
+ join_tab->table->reginfo.not_exists_optimize= 1;
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+static bool
+make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
+{
+ THD *thd= join->thd;
+ DBUG_ENTER("make_join_select");
+ if (select)
+ {
+ add_not_null_conds(join);
+ table_map used_tables;
+ /*
+ Step #1: Extract constant condition
+ - Extract and check the constant part of the WHERE
+ - Extract constant parts of ON expressions from outer
+ joins and attach them appropriately.
+ */
+ if (cond) /* Because of QUICK_GROUP_MIN_MAX_SELECT */
+ { /* there may be a select without a cond. */
+ if (join->table_count > 1)
+ cond->update_used_tables(); // Tablenr may have changed
+
+ /*
+ Extract expressions that depend on constant tables
+ 1. Const part of the join's WHERE clause can be checked immediately
+ and if it is not satisfied then the join has empty result
+ 2. Constant parts of outer joins' ON expressions must be attached
+ there inside the triggers.
+ */
+ { // Check const tables
+ join->exec_const_cond=
+ make_cond_for_table(thd, cond,
+ join->const_table_map,
+ (table_map) 0, -1, FALSE, FALSE);
+ /* Add conditions added by add_not_null_conds(). */
+ for (uint i= 0 ; i < join->const_tables ; i++)
+ add_cond_and_fix(thd, &join->exec_const_cond,
+ join->join_tab[i].select_cond);
+
+ DBUG_EXECUTE("where",print_where(join->exec_const_cond,"constants",
+ QT_ORDINARY););
+ if (join->exec_const_cond && !join->exec_const_cond->is_expensive() &&
+ !join->exec_const_cond->val_int())
+ {
+ DBUG_PRINT("info",("Found impossible WHERE condition"));
+ join->exec_const_cond= NULL;
+ DBUG_RETURN(1); // Impossible const condition
+ }
+
+ if (join->table_count != join->const_tables)
+ {
+ COND *outer_ref_cond= make_cond_for_table(thd, cond,
+ join->const_table_map |
+ OUTER_REF_TABLE_BIT,
+ OUTER_REF_TABLE_BIT,
+ -1, FALSE, FALSE);
+ if (outer_ref_cond)
+ {
+ add_cond_and_fix(thd, &outer_ref_cond, join->outer_ref_cond);
+ join->outer_ref_cond= outer_ref_cond;
+ }
+ }
+ else
+ {
+ COND *pseudo_bits_cond=
+ make_cond_for_table(thd, cond,
+ join->const_table_map |
+ PSEUDO_TABLE_BITS,
+ PSEUDO_TABLE_BITS,
+ -1, FALSE, FALSE);
+ if (pseudo_bits_cond)
+ {
+ add_cond_and_fix(thd, &pseudo_bits_cond,
+ join->pseudo_bits_cond);
+ join->pseudo_bits_cond= pseudo_bits_cond;
+ }
+ }
+ }
+ }
+
+ /*
+ Step #2: Extract WHERE/ON parts
+ */
+ table_map save_used_tables= 0;
+ used_tables=((select->const_tables=join->const_table_map) |
+ OUTER_REF_TABLE_BIT | RAND_TABLE_BIT);
+ JOIN_TAB *tab;
+ table_map current_map;
+ uint i= join->const_tables;
+ for (tab= first_depth_first_tab(join); tab;
+ tab= next_depth_first_tab(join, tab), i++)
+ {
+ bool is_hj;
+ /*
+ first_inner is the X in queries like:
+ SELECT * FROM t1 LEFT OUTER JOIN (t2 JOIN t3) ON X
+ */
+ JOIN_TAB *first_inner_tab= tab->first_inner;
+
+ if (!tab->bush_children)
+ current_map= tab->table->map;
+ else
+ current_map= tab->bush_children->start->emb_sj_nest->sj_inner_tables;
+
+ bool use_quick_range=0;
+ COND *tmp;
+
+ /*
+ Tables that are within SJ-Materialization nests cannot have their
+ conditions referring to preceding non-const tables.
+ - If we're looking at the first SJM table, reset used_tables
+ to refer to only allowed tables
+ */
+ if (tab->emb_sj_nest && tab->emb_sj_nest->sj_mat_info &&
+ tab->emb_sj_nest->sj_mat_info->is_used &&
+ !(used_tables & tab->emb_sj_nest->sj_inner_tables))
+ {
+ save_used_tables= used_tables;
+ used_tables= join->const_table_map | OUTER_REF_TABLE_BIT |
+ RAND_TABLE_BIT;
+ }
+
+ /*
+ Following force including random expression in last table condition.
+ It solve problem with select like SELECT * FROM t1 WHERE rand() > 0.5
+ */
+ if (tab == join->join_tab + join->top_join_tab_count - 1)
+ current_map|= OUTER_REF_TABLE_BIT | RAND_TABLE_BIT;
+ used_tables|=current_map;
+
+ if (tab->type == JT_REF && tab->quick &&
+ (((uint) tab->ref.key == tab->quick->index &&
+ tab->ref.key_length < tab->quick->max_used_key_length) ||
+ tab->table->intersect_keys.is_set(tab->ref.key)))
+ {
+ /* Range uses longer key; Use this instead of ref on key */
+ tab->type=JT_ALL;
+ use_quick_range=1;
+ tab->use_quick=1;
+ tab->ref.key= -1;
+ tab->ref.key_parts=0; // Don't use ref key.
+ join->best_positions[i].records_read= rows2double(tab->quick->records);
+ /*
+ We will use join cache here : prevent sorting of the first
+ table only and sort at the end.
+ */
+ if (i != join->const_tables &&
+ join->table_count > join->const_tables + 1 &&
+ join->best_positions[i].use_join_buffer)
+ join->full_join= 1;
+ }
+
+ tmp= NULL;
+
+ if (cond)
+ {
+ if (tab->bush_children)
+ {
+ // Reached the materialization tab
+ tmp= make_cond_after_sjm(cond, cond, save_used_tables, used_tables,
+ /*inside_or_clause=*/FALSE);
+ used_tables= save_used_tables | used_tables;
+ save_used_tables= 0;
+ }
+ else
+ {
+ tmp= make_cond_for_table(thd, cond, used_tables, current_map, i,
+ FALSE, FALSE);
+ }
+ /* Add conditions added by add_not_null_conds(). */
+ if (tab->select_cond)
+ add_cond_and_fix(thd, &tmp, tab->select_cond);
+ }
+
+ is_hj= (tab->type == JT_REF || tab->type == JT_EQ_REF) &&
+ (join->allowed_join_cache_types & JOIN_CACHE_HASHED_BIT) &&
+ ((join->max_allowed_join_cache_level+1)/2 == 2 ||
+ ((join->max_allowed_join_cache_level+1)/2 > 2 &&
+ is_hash_join_key_no(tab->ref.key))) &&
+ (!tab->emb_sj_nest ||
+ join->allowed_semijoin_with_cache) &&
+ (!(tab->table->map & join->outer_join) ||
+ join->allowed_outer_join_with_cache);
+
+ if (cond && !tmp && tab->quick)
+ { // Outer join
+ if (tab->type != JT_ALL && !is_hj)
+ {
+ /*
+ Don't use the quick method
+ We come here in the case where we have 'key=constant' and
+ the test is removed by make_cond_for_table()
+ */
+ delete tab->quick;
+ tab->quick= 0;
+ }
+ else
+ {
+ /*
+ Hack to handle the case where we only refer to a table
+ in the ON part of an OUTER JOIN. In this case we want the code
+ below to check if we should use 'quick' instead.
+ */
+ DBUG_PRINT("info", ("Item_int"));
+ tmp= new Item_int((longlong) 1,1); // Always true
+ }
+
+ }
+ if (tmp || !cond || tab->type == JT_REF || tab->type == JT_REF_OR_NULL ||
+ tab->type == JT_EQ_REF || first_inner_tab)
+ {
+ DBUG_EXECUTE("where",print_where(tmp,
+ tab->table? tab->table->alias.c_ptr() :"sjm-nest",
+ QT_ORDINARY););
+ SQL_SELECT *sel= tab->select= ((SQL_SELECT*)
+ thd->memdup((uchar*) select,
+ sizeof(*select)));
+ if (!sel)
+ DBUG_RETURN(1); // End of memory
+ /*
+ If tab is an inner table of an outer join operation,
+ add a match guard to the pushed down predicate.
+ The guard will turn the predicate on only after
+ the first match for outer tables is encountered.
+ */
+ if (cond && tmp)
+ {
+ /*
+ Because of QUICK_GROUP_MIN_MAX_SELECT there may be a select without
+ a cond, so neutralize the hack above.
+ */
+ if (!(tmp= add_found_match_trig_cond(first_inner_tab, tmp, 0)))
+ DBUG_RETURN(1);
+ sel->cond= tmp;
+ tab->set_select_cond(tmp, __LINE__);
+ /* Push condition to storage engine if this is enabled
+ and the condition is not guarded */
+ if (tab->table)
+ {
+ tab->table->file->pushed_cond= NULL;
+ if (thd->use_cond_push(tab->table->file) && !first_inner_tab)
+ {
+ COND *push_cond=
+ make_cond_for_table(thd, tmp, current_map, current_map,
+ -1, FALSE, FALSE);
+ if (push_cond)
+ {
+ /* Push condition to handler */
+ if (!tab->table->file->cond_push(push_cond))
+ tab->table->file->pushed_cond= push_cond;
+ }
+ }
+ }
+ }
+ else
+ {
+ sel->cond= NULL;
+ tab->set_select_cond(NULL, __LINE__);
+ }
+
+ sel->head=tab->table;
+ DBUG_EXECUTE("where",
+ print_where(tmp,
+ tab->table ? tab->table->alias.c_ptr() :
+ "(sjm-nest)",
+ QT_ORDINARY););
+ if (tab->quick)
+ {
+ /* Use quick key read if it's a constant and it's not used
+ with key reading */
+ if ((tab->needed_reg.is_clear_all() && tab->type != JT_EQ_REF &&
+ tab->type != JT_FT &&
+ ((tab->type != JT_CONST && tab->type != JT_REF) ||
+ (uint) tab->ref.key == tab->quick->index)) || is_hj)
+ {
+ DBUG_ASSERT(tab->quick->is_valid());
+ sel->quick=tab->quick; // Use value from get_quick_...
+ sel->quick_keys.clear_all();
+ sel->needed_reg.clear_all();
+ }
+ else
+ {
+ delete tab->quick;
+ }
+ tab->quick=0;
+ }
+ uint ref_key= sel->head? (uint) sel->head->reginfo.join_tab->ref.key+1 : 0;
+ if (i == join->const_tables && ref_key)
+ {
+ if (!tab->const_keys.is_clear_all() &&
+ tab->table->reginfo.impossible_range)
+ DBUG_RETURN(1);
+ }
+ else if (tab->type == JT_ALL && ! use_quick_range)
+ {
+ if (!tab->const_keys.is_clear_all() &&
+ tab->table->reginfo.impossible_range)
+ DBUG_RETURN(1); // Impossible range
+ /*
+ We plan to scan all rows.
+ Check again if we should use an index.
+ We could have used an column from a previous table in
+ the index if we are using limit and this is the first table
+ */
+
+ if (!tab->table->is_filled_at_execution() &&
+ ((cond && (!tab->keys.is_subset(tab->const_keys) && i > 0)) ||
+ (!tab->const_keys.is_clear_all() && i == join->const_tables &&
+ join->unit->select_limit_cnt <
+ join->best_positions[i].records_read &&
+ !(join->select_options & OPTION_FOUND_ROWS))))
+ {
+ /* Join with outer join condition */
+ COND *orig_cond=sel->cond;
+ sel->cond= and_conds(sel->cond, *tab->on_expr_ref);
+
+ /*
+ We can't call sel->cond->fix_fields,
+ as it will break tab->on_expr if it's AND condition
+ (fix_fields currently removes extra AND/OR levels).
+ Yet attributes of the just built condition are not needed.
+ Thus we call sel->cond->quick_fix_field for safety.
+ */
+ if (sel->cond && !sel->cond->fixed)
+ sel->cond->quick_fix_field();
+
+ if (sel->test_quick_select(thd, tab->keys,
+ ((used_tables & ~ current_map) |
+ OUTER_REF_TABLE_BIT),
+ (join->select_options &
+ OPTION_FOUND_ROWS ?
+ HA_POS_ERROR :
+ join->unit->select_limit_cnt), 0,
+ FALSE) < 0)
+ {
+ /*
+ Before reporting "Impossible WHERE" for the whole query
+ we have to check isn't it only "impossible ON" instead
+ */
+ sel->cond=orig_cond;
+ if (!*tab->on_expr_ref ||
+ sel->test_quick_select(thd, tab->keys,
+ used_tables & ~ current_map,
+ (join->select_options &
+ OPTION_FOUND_ROWS ?
+ HA_POS_ERROR :
+ join->unit->select_limit_cnt),0,
+ FALSE) < 0)
+ DBUG_RETURN(1); // Impossible WHERE
+ }
+ else
+ sel->cond=orig_cond;
+
+ /* Fix for EXPLAIN */
+ if (sel->quick)
+ join->best_positions[i].records_read= (double)sel->quick->records;
+ }
+ else
+ {
+ sel->needed_reg=tab->needed_reg;
+ }
+ sel->quick_keys= tab->table->quick_keys;
+ if (!sel->quick_keys.is_subset(tab->checked_keys) ||
+ !sel->needed_reg.is_subset(tab->checked_keys))
+ {
+ tab->use_quick= (!sel->needed_reg.is_clear_all() &&
+ (sel->quick_keys.is_clear_all() ||
+ (sel->quick &&
+ (sel->quick->records >= 100L)))) ?
+ 2 : 1;
+ sel->read_tables= used_tables & ~current_map;
+ sel->quick_keys.clear_all();
+ }
+ if (i != join->const_tables && tab->use_quick != 2 &&
+ !tab->first_inner)
+ { /* Read with cache */
+ if (tab->make_scan_filter())
+ DBUG_RETURN(1);
+ }
+ }
+ }
+
+ /*
+ Push down conditions from all ON expressions.
+ Each of these conditions are guarded by a variable
+ that turns if off just before null complemented row for
+ outer joins is formed. Thus, the condition from an
+ 'on expression' are guaranteed not to be checked for
+ the null complemented row.
+ */
+
+ /*
+ First push down constant conditions from ON expressions.
+ - Each pushed-down condition is wrapped into trigger which is
+ enabled only for non-NULL-complemented record
+ - The condition is attached to the first_inner_table.
+
+ With regards to join nests:
+ - if we start at top level, don't walk into nests
+ - if we start inside a nest, stay within that nest.
+ */
+ JOIN_TAB *start_from= tab->bush_root_tab?
+ tab->bush_root_tab->bush_children->start :
+ join->join_tab + join->const_tables;
+ JOIN_TAB *end_with= tab->bush_root_tab?
+ tab->bush_root_tab->bush_children->end :
+ join->join_tab + join->top_join_tab_count;
+ for (JOIN_TAB *join_tab= start_from;
+ join_tab != end_with;
+ join_tab++)
+ {
+ if (*join_tab->on_expr_ref)
+ {
+ JOIN_TAB *cond_tab= join_tab->first_inner;
+ COND *tmp= make_cond_for_table(thd, *join_tab->on_expr_ref,
+ join->const_table_map,
+ (table_map) 0, -1, FALSE, FALSE);
+ if (!tmp)
+ continue;
+ tmp= new Item_func_trig_cond(tmp, &cond_tab->not_null_compl);
+ if (!tmp)
+ DBUG_RETURN(1);
+ tmp->quick_fix_field();
+ cond_tab->select_cond= !cond_tab->select_cond ? tmp :
+ new Item_cond_and(cond_tab->select_cond,tmp);
+ if (!cond_tab->select_cond)
+ DBUG_RETURN(1);
+ cond_tab->select_cond->quick_fix_field();
+ cond_tab->select_cond->update_used_tables();
+ if (cond_tab->select)
+ cond_tab->select->cond= cond_tab->select_cond;
+ }
+ }
+
+
+ /* Push down non-constant conditions from ON expressions */
+ JOIN_TAB *last_tab= tab;
+
+ /*
+ while we're inside of an outer join and last_tab is
+ the last of its tables ...
+ */
+ while (first_inner_tab && first_inner_tab->last_inner == last_tab)
+ {
+ /*
+ Table tab is the last inner table of an outer join.
+ An on expression is always attached to it.
+ */
+ COND *on_expr= *first_inner_tab->on_expr_ref;
+
+ table_map used_tables2= (join->const_table_map |
+ OUTER_REF_TABLE_BIT | RAND_TABLE_BIT);
+
+ start_from= tab->bush_root_tab?
+ tab->bush_root_tab->bush_children->start :
+ join->join_tab + join->const_tables;
+ for (JOIN_TAB *tab= start_from; tab <= last_tab; tab++)
+ {
+ DBUG_ASSERT(tab->table);
+ current_map= tab->table->map;
+ used_tables2|= current_map;
+ /*
+ psergey: have put the -1 below. It's bad, will need to fix it.
+ */
+ COND *tmp_cond= make_cond_for_table(thd, on_expr, used_tables2,
+ current_map, /*(tab - first_tab)*/ -1,
+ FALSE, FALSE);
+ bool is_sjm_lookup_tab= FALSE;
+ if (tab->bush_children)
+ {
+ /*
+ 'tab' is an SJ-Materialization tab, i.e. we have a join order
+ like this:
+
+ ot1 sjm_tab LEFT JOIN ot2 ot3
+ ^ ^
+ 'tab'-+ +--- left join we're adding triggers for
+
+ LEFT JOIN's ON expression may not have references to subquery
+ columns. The subquery was in the WHERE clause, so IN-equality
+ is in the WHERE clause, also.
+ However, equality propagation code may have propagated the
+ IN-equality into ON expression, and we may get things like
+
+ subquery_inner_table=const
+
+ in the ON expression. We must not check such conditions during
+ SJM-lookup, because 1) subquery_inner_table has no valid current
+ row (materialization temp.table has it instead), and 2) they
+ would be true anyway.
+ */
+ SJ_MATERIALIZATION_INFO *sjm=
+ tab->bush_children->start->emb_sj_nest->sj_mat_info;
+ if (sjm->is_used && !sjm->is_sj_scan)
+ is_sjm_lookup_tab= TRUE;
+ }
+
+ if (tab == first_inner_tab && tab->on_precond && !is_sjm_lookup_tab)
+ add_cond_and_fix(thd, &tmp_cond, tab->on_precond);
+ if (tmp_cond && !is_sjm_lookup_tab)
+ {
+ JOIN_TAB *cond_tab= tab < first_inner_tab ? first_inner_tab : tab;
+ Item **sel_cond_ref= tab < first_inner_tab ?
+ &first_inner_tab->on_precond :
+ &tab->select_cond;
+ /*
+ First add the guards for match variables of
+ all embedding outer join operations.
+ */
+ if (!(tmp_cond= add_found_match_trig_cond(cond_tab->first_inner,
+ tmp_cond,
+ first_inner_tab)))
+ DBUG_RETURN(1);
+ /*
+ Now add the guard turning the predicate off for
+ the null complemented row.
+ */
+ DBUG_PRINT("info", ("Item_func_trig_cond"));
+ tmp_cond= new Item_func_trig_cond(tmp_cond,
+ &first_inner_tab->
+ not_null_compl);
+ DBUG_PRINT("info", ("Item_func_trig_cond 0x%lx",
+ (ulong) tmp_cond));
+ if (tmp_cond)
+ tmp_cond->quick_fix_field();
+ /* Add the predicate to other pushed down predicates */
+ DBUG_PRINT("info", ("Item_cond_and"));
+ *sel_cond_ref= !(*sel_cond_ref) ?
+ tmp_cond :
+ new Item_cond_and(*sel_cond_ref, tmp_cond);
+ DBUG_PRINT("info", ("Item_cond_and 0x%lx",
+ (ulong)(*sel_cond_ref)));
+ if (!(*sel_cond_ref))
+ DBUG_RETURN(1);
+ (*sel_cond_ref)->quick_fix_field();
+ (*sel_cond_ref)->update_used_tables();
+ if (cond_tab->select)
+ cond_tab->select->cond= cond_tab->select_cond;
+ }
+ }
+ first_inner_tab= first_inner_tab->first_upper;
+ }
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+static
+uint get_next_field_for_derived_key(uchar *arg)
+{
+ KEYUSE *keyuse= *(KEYUSE **) arg;
+ if (!keyuse)
+ return (uint) (-1);
+ TABLE *table= keyuse->table;
+ uint key= keyuse->key;
+ uint fldno= keyuse->keypart;
+ uint keypart= keyuse->keypart_map == (key_part_map) 1 ?
+ 0 : (keyuse-1)->keypart+1;
+ for ( ;
+ keyuse->table == table && keyuse->key == key && keyuse->keypart == fldno;
+ keyuse++)
+ keyuse->keypart= keypart;
+ if (keyuse->key != key)
+ keyuse= 0;
+ *((KEYUSE **) arg)= keyuse;
+ return fldno;
+}
+
+
+static
+uint get_next_field_for_derived_key_simple(uchar *arg)
+{
+ KEYUSE *keyuse= *(KEYUSE **) arg;
+ if (!keyuse)
+ return (uint) (-1);
+ TABLE *table= keyuse->table;
+ uint key= keyuse->key;
+ uint fldno= keyuse->keypart;
+ for ( ;
+ keyuse->table == table && keyuse->key == key && keyuse->keypart == fldno;
+ keyuse++)
+ ;
+ if (keyuse->key != key)
+ keyuse= 0;
+ *((KEYUSE **) arg)= keyuse;
+ return fldno;
+}
+
+static
+bool generate_derived_keys_for_table(KEYUSE *keyuse, uint count, uint keys)
+{
+ TABLE *table= keyuse->table;
+ if (table->alloc_keys(keys))
+ return TRUE;
+ uint key_count= 0;
+ KEYUSE *first_keyuse= keyuse;
+ uint prev_part= keyuse->keypart;
+ uint parts= 0;
+ uint i= 0;
+
+ for ( ; i < count && key_count < keys; )
+ {
+ do
+ {
+ keyuse->key= table->s->keys;
+ keyuse->keypart_map= (key_part_map) (1 << parts);
+ keyuse++;
+ i++;
+ }
+ while (i < count && keyuse->used_tables == first_keyuse->used_tables &&
+ keyuse->keypart == prev_part);
+ parts++;
+ if (i < count && keyuse->used_tables == first_keyuse->used_tables)
+ {
+ prev_part= keyuse->keypart;
+ }
+ else
+ {
+ KEYUSE *save_first_keyuse= first_keyuse;
+ if (table->check_tmp_key(table->s->keys, parts,
+ get_next_field_for_derived_key_simple,
+ (uchar *) &first_keyuse))
+
+ {
+ first_keyuse= save_first_keyuse;
+ if (table->add_tmp_key(table->s->keys, parts,
+ get_next_field_for_derived_key,
+ (uchar *) &first_keyuse,
+ FALSE))
+ return TRUE;
+ table->reginfo.join_tab->keys.set_bit(table->s->keys);
+ }
+ else
+ {
+ /* Mark keyuses for this key to be excluded */
+ for (KEYUSE *curr=save_first_keyuse; curr < keyuse; curr++)
+ {
+ curr->key= MAX_KEY;
+ }
+ }
+ first_keyuse= keyuse;
+ key_count++;
+ parts= 0;
+ prev_part= keyuse->keypart;
+ }
+ }
+
+ return FALSE;
+}
+
+
+static
+bool generate_derived_keys(DYNAMIC_ARRAY *keyuse_array)
+{
+ KEYUSE *keyuse= dynamic_element(keyuse_array, 0, KEYUSE*);
+ uint elements= keyuse_array->elements;
+ TABLE *prev_table= 0;
+ for (uint i= 0; i < elements; i++, keyuse++)
+ {
+ if (!keyuse->table)
+ break;
+ KEYUSE *first_table_keyuse= NULL;
+ table_map last_used_tables= 0;
+ uint count= 0;
+ uint keys= 0;
+ TABLE_LIST *derived= NULL;
+ if (keyuse->table != prev_table)
+ derived= keyuse->table->pos_in_table_list;
+ while (derived && derived->is_materialized_derived())
+ {
+ if (keyuse->table != prev_table)
+ {
+ prev_table= keyuse->table;
+ while (keyuse->table == prev_table && keyuse->key != MAX_KEY)
+ {
+ keyuse++;
+ i++;
+ }
+ if (keyuse->table != prev_table)
+ {
+ keyuse--;
+ i--;
+ derived= NULL;
+ continue;
+ }
+ first_table_keyuse= keyuse;
+ last_used_tables= keyuse->used_tables;
+ count= 0;
+ keys= 0;
+ }
+ else if (keyuse->used_tables != last_used_tables)
+ {
+ keys++;
+ last_used_tables= keyuse->used_tables;
+ }
+ count++;
+ keyuse++;
+ i++;
+ if (keyuse->table != prev_table)
+ {
+ if (generate_derived_keys_for_table(first_table_keyuse, count, ++keys))
+ return TRUE;
+ keyuse--;
+ i--;
+ derived= NULL;
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/*
+ @brief
+ Drops unused keys for each materialized derived table/view
+
+ @details
+ For materialized derived tables only ref access can be used, it employs
+ only one index, thus we don't need the rest. For each materialized derived
+ table/view call TABLE::use_index to save one index chosen by the optimizer
+ and free others. No key is chosen then all keys will be dropped.
+*/
+
+void JOIN::drop_unused_derived_keys()
+{
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS))
+ {
+
+ TABLE *table=tab->table;
+ if (!table)
+ continue;
+ if (!table->pos_in_table_list->is_materialized_derived())
+ continue;
+ if (table->max_keys > 1)
+ table->use_index(tab->ref.key);
+ if (table->s->keys)
+ {
+ if (tab->ref.key >= 0)
+ tab->ref.key= 0;
+ else
+ table->s->keys= 0;
+ }
+ tab->keys= (key_map) (table->s->keys ? 1 : 0);
+ }
+}
+
+
+/*
+ Evaluate the bitmap of used tables for items from the select list
+*/
+
+inline void JOIN::eval_select_list_used_tables()
+{
+ select_list_used_tables= 0;
+ Item *item;
+ List_iterator_fast<Item> it(fields_list);
+ while ((item= it++))
+ {
+ select_list_used_tables|= item->used_tables();
+ }
+ Item_outer_ref *ref;
+ List_iterator_fast<Item_outer_ref> ref_it(select_lex->inner_refs_list);
+ while ((ref= ref_it++))
+ {
+ item= ref->outer_ref;
+ select_list_used_tables|= item->used_tables();
+ }
+}
+
+
+/*
+ Determine {after which table we'll produce ordered set}
+
+ SYNOPSIS
+ make_join_orderinfo()
+ join
+
+
+ DESCRIPTION
+ Determine if the set is already ordered for ORDER BY, so it can
+ disable join cache because it will change the ordering of the results.
+ Code handles sort table that is at any location (not only first after
+ the const tables) despite the fact that it's currently prohibited.
+ We must disable join cache if the first non-const table alone is
+ ordered. If there is a temp table the ordering is done as a last
+ operation and doesn't prevent join cache usage.
+
+ RETURN
+ Number of table after which the set will be ordered
+ join->tables if we don't need an ordered set
+*/
+
+static uint make_join_orderinfo(JOIN *join)
+{
+ /*
+ This function needs to be fixed to take into account that we now have SJM
+ nests.
+ */
+ DBUG_ASSERT(0);
+
+ JOIN_TAB *tab;
+ if (join->need_tmp)
+ return join->table_count;
+ tab= join->get_sort_by_join_tab();
+ return tab ? tab-join->join_tab : join->table_count;
+}
+
+/*
+ Deny usage of join buffer for the specified table
+
+ SYNOPSIS
+ set_join_cache_denial()
+ tab join table for which join buffer usage is to be denied
+
+ DESCRIPTION
+ The function denies usage of join buffer when joining the table 'tab'.
+ The table is marked as not employing any join buffer. If a join cache
+ object has been already allocated for the table this object is destroyed.
+
+ RETURN
+ none
+*/
+
+static
+void set_join_cache_denial(JOIN_TAB *join_tab)
+{
+ if (join_tab->cache)
+ {
+ /*
+ If there is a previous cache linked to this cache through the
+ next_cache pointer: remove the link.
+ */
+ if (join_tab->cache->prev_cache)
+ join_tab->cache->prev_cache->next_cache= 0;
+ /*
+ No need to do the same for next_cache since cache denial is done
+ backwards starting from the latest cache in the linked list (see
+ revise_cache_usage()).
+ */
+ DBUG_ASSERT(!join_tab->cache->next_cache);
+
+ join_tab->cache->free();
+ join_tab->cache= 0;
+ }
+ if (join_tab->use_join_cache)
+ {
+ join_tab->use_join_cache= FALSE;
+ join_tab->used_join_cache_level= 0;
+ /*
+ It could be only sub_select(). It could not be sub_seject_sjm because we
+ don't do join buffering for the first table in sjm nest.
+ */
+ join_tab[-1].next_select= sub_select;
+ if (join_tab->type == JT_REF && join_tab->is_ref_for_hash_join())
+ {
+ join_tab->type= JT_ALL;
+ join_tab->ref.key_parts= 0;
+ }
+ join_tab->join->return_tab= join_tab;
+ }
+}
+
+
+/**
+ The default implementation of unlock-row method of READ_RECORD,
+ used in all access methods.
+*/
+
+void rr_unlock_row(st_join_table *tab)
+{
+ READ_RECORD *info= &tab->read_record;
+ info->table->file->unlock_row();
+}
+
+
+/**
+ Pick the appropriate access method functions
+
+ Sets the functions for the selected table access method
+
+ @param tab Table reference to put access method
+*/
+
+static void
+pick_table_access_method(JOIN_TAB *tab)
+{
+ switch (tab->type)
+ {
+ case JT_REF:
+ tab->read_first_record= join_read_always_key;
+ tab->read_record.read_record= join_read_next_same;
+ break;
+
+ case JT_REF_OR_NULL:
+ tab->read_first_record= join_read_always_key_or_null;
+ tab->read_record.read_record= join_read_next_same_or_null;
+ break;
+
+ case JT_CONST:
+ tab->read_first_record= join_read_const;
+ tab->read_record.read_record= join_no_more_records;
+ break;
+
+ case JT_EQ_REF:
+ tab->read_first_record= join_read_key;
+ tab->read_record.read_record= join_no_more_records;
+ break;
+
+ case JT_FT:
+ tab->read_first_record= join_ft_read_first;
+ tab->read_record.read_record= join_ft_read_next;
+ break;
+
+ case JT_SYSTEM:
+ tab->read_first_record= join_read_system;
+ tab->read_record.read_record= join_no_more_records;
+ break;
+
+ /* keep gcc happy */
+ default:
+ break;
+ }
+}
+
+
+/*
+ Revise usage of join buffer for the specified table and the whole nest
+
+ SYNOPSIS
+ revise_cache_usage()
+ tab join table for which join buffer usage is to be revised
+
+ DESCRIPTION
+ The function revise the decision to use a join buffer for the table 'tab'.
+ If this table happened to be among the inner tables of a nested outer join/
+ semi-join the functions denies usage of join buffers for all of them
+
+ RETURN
+ none
+*/
+
+static
+void revise_cache_usage(JOIN_TAB *join_tab)
+{
+ JOIN_TAB *tab;
+ JOIN_TAB *first_inner;
+
+ if (join_tab->first_inner)
+ {
+ JOIN_TAB *end_tab= join_tab;
+ for (first_inner= join_tab->first_inner;
+ first_inner;
+ first_inner= first_inner->first_upper)
+ {
+ for (tab= end_tab; tab >= first_inner; tab--)
+ set_join_cache_denial(tab);
+ end_tab= first_inner;
+ }
+ }
+ else if (join_tab->first_sj_inner_tab)
+ {
+ first_inner= join_tab->first_sj_inner_tab;
+ for (tab= join_tab; tab >= first_inner; tab--)
+ {
+ set_join_cache_denial(tab);
+ }
+ }
+ else set_join_cache_denial(join_tab);
+}
+
+
+/*
+ end_select-compatible function that writes the record into a sjm temptable
+
+ SYNOPSIS
+ end_sj_materialize()
+ join The join
+ join_tab Points to right after the last join_tab in materialization bush
+ end_of_records FALSE <=> This call is made to pass another record
+ combination
+ TRUE <=> EOF (no action)
+
+ DESCRIPTION
+ This function is used by semi-join materialization to capture suquery's
+ resultset and write it into the temptable (that is, materialize it).
+
+ NOTE
+ This function is used only for semi-join materialization. Non-semijoin
+ materialization uses different mechanism.
+
+ RETURN
+ NESTED_LOOP_OK
+ NESTED_LOOP_ERROR
+*/
+
+enum_nested_loop_state
+end_sj_materialize(JOIN *join, JOIN_TAB *join_tab, bool end_of_records)
+{
+ int error;
+ THD *thd= join->thd;
+ SJ_MATERIALIZATION_INFO *sjm= join_tab[-1].emb_sj_nest->sj_mat_info;
+ DBUG_ENTER("end_sj_materialize");
+ if (!end_of_records)
+ {
+ TABLE *table= sjm->table;
+
+ List_iterator<Item> it(sjm->sjm_table_cols);
+ Item *item;
+ while ((item= it++))
+ {
+ if (item->is_null())
+ DBUG_RETURN(NESTED_LOOP_OK);
+ }
+ fill_record(thd, table, table->field, sjm->sjm_table_cols, TRUE, FALSE);
+ if (thd->is_error())
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ if ((error= table->file->ha_write_tmp_row(table->record[0])))
+ {
+ /* create_myisam_from_heap will generate error if needed */
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP) &&
+ create_internal_tmp_table_from_heap(thd, table,
+ sjm->sjm_table_param.start_recinfo,
+ &sjm->sjm_table_param.recinfo, error, 1, NULL))
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ }
+ }
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+
+/*
+ Check whether a join buffer can be used to join the specified table
+
+ SYNOPSIS
+ check_join_cache_usage()
+ tab joined table to check join buffer usage for
+ options options of the join
+ no_jbuf_after don't use join buffering after table with this number
+ prev_tab previous join table
+
+ DESCRIPTION
+ The function finds out whether the table 'tab' can be joined using a join
+ buffer. This check is performed after the best execution plan for 'join'
+ has been chosen. If the function decides that a join buffer can be employed
+ then it selects the most appropriate join cache object that contains this
+ join buffer.
+ The result of the check and the type of the the join buffer to be used
+ depend on:
+ - the access method to access rows of the joined table
+ - whether the join table is an inner table of an outer join or semi-join
+ - whether the optimizer switches
+ outer_join_with_cache, semijoin_with_cache, join_cache_incremental,
+ join_cache_hashed, join_cache_bka,
+ are set on or off
+ - the join cache level set for the query
+ - the join 'options'.
+
+ In any case join buffer is not used if the number of the joined table is
+ greater than 'no_jbuf_after'. It's also never used if the value of
+ join_cache_level is equal to 0.
+ If the optimizer switch outer_join_with_cache is off no join buffer is
+ used for outer join operations.
+ If the optimizer switch semijoin_with_cache is off no join buffer is used
+ for semi-join operations.
+ If the optimizer switch join_cache_incremental is off no incremental join
+ buffers are used.
+ If the optimizer switch join_cache_hashed is off then the optimizer uses
+ neither BNLH algorithm, nor BKAH algorithm to perform join operations.
+
+ If the optimizer switch join_cache_bka is off then the optimizer uses
+ neither BKA algorithm, nor BKAH algorithm to perform join operation.
+ The valid settings for join_cache_level lay in the interval 0..8.
+ If it set to 0 no join buffers are used to perform join operations.
+ Currently we differentiate between join caches of 8 levels:
+ 1 : non-incremental join cache used for BNL join algorithm
+ 2 : incremental join cache used for BNL join algorithm
+ 3 : non-incremental join cache used for BNLH join algorithm
+ 4 : incremental join cache used for BNLH join algorithm
+ 5 : non-incremental join cache used for BKA join algorithm
+ 6 : incremental join cache used for BKA join algorithm
+ 7 : non-incremental join cache used for BKAH join algorithm
+ 8 : incremental join cache used for BKAH join algorithm
+ If the value of join_cache_level is set to n then no join caches of
+ levels higher than n can be employed.
+
+ If the optimizer switches outer_join_with_cache, semijoin_with_cache,
+ join_cache_incremental, join_cache_hashed, join_cache_bka are all on
+ the following rules are applied.
+ If join_cache_level==1|2 then join buffer is used for inner joins, outer
+ joins and semi-joins with 'JT_ALL' access method. In this case a
+ JOIN_CACHE_BNL object is employed.
+ If join_cache_level==3|4 and then join buffer is used for a join operation
+ (inner join, outer join, semi-join) with 'JT_REF'/'JT_EQREF' access method
+ then a JOIN_CACHE_BNLH object is employed.
+ If an index is used to access rows of the joined table and the value of
+ join_cache_level==5|6 then a JOIN_CACHE_BKA object is employed.
+ If an index is used to access rows of the joined table and the value of
+ join_cache_level==7|8 then a JOIN_CACHE_BKAH object is employed.
+ If the value of join_cache_level is odd then creation of a non-linked
+ join cache is forced.
+
+ Currently for any join operation a join cache of the level of the
+ highest allowed and applicable level is used.
+ For example, if join_cache_level is set to 6 and the optimizer switch
+ join_cache_bka is off, while the optimizer switch join_cache_hashed is
+ on then for any inner join operation with JT_REF/JT_EQREF access method
+ to the joined table the BNLH join algorithm will be used, while for
+ the table accessed by the JT_ALL methods the BNL algorithm will be used.
+
+ If the function decides that a join buffer can be used to join the table
+ 'tab' then it sets the value of tab->use_join_buffer to TRUE and assigns
+ the selected join cache object to the field 'cache' of the previous
+ join table.
+ If the function creates a join cache object it tries to initialize it. The
+ failure to do this results in an invocation of the function that destructs
+ the created object.
+ If the function decides that but some reasons no join buffer can be used
+ for a table it calls the function revise_cache_usage that checks
+ whether join cache should be denied for some previous tables. In this case
+ a pointer to the first table for which join cache usage has been denied
+ is passed in join->return_val (see the function set_join_cache_denial).
+
+ The functions changes the value the fields tab->icp_other_tables_ok and
+ tab->idx_cond_fact_out to FALSE if the chosen join cache algorithm
+ requires it.
+
+ NOTES
+ An inner table of a nested outer join or a nested semi-join can be currently
+ joined only when a linked cache object is employed. In these cases setting
+ join_cache_incremental to 'off' results in denial of usage of any join
+ buffer when joining the table.
+ For a nested outer join/semi-join, currently, we either use join buffers for
+ all inner tables or for none of them.
+ Some engines (e.g. Falcon) currently allow to use only a join cache
+ of the type JOIN_CACHE_BKAH when the joined table is accessed through
+ an index. For these engines setting the value of join_cache_level to 5 or 6
+ results in that no join buffer is used to join the table.
+
+ RETURN VALUE
+ cache level if cache is used, otherwise returns 0
+
+ TODO
+ Support BKA inside SJ-Materialization nests. When doing this, we'll need
+ to only store sj-inner tables in the join buffer.
+#if 0
+ JOIN_TAB *first_tab= join->join_tab+join->const_tables;
+ uint n_tables= i-join->const_tables;
+ / *
+ We normally put all preceding tables into the join buffer, except
+ for the constant tables.
+ If we're inside a semi-join materialization nest, e.g.
+
+ outer_tbl1 outer_tbl2 ( inner_tbl1, inner_tbl2 ) ...
+ ^-- we're here
+
+ then we need to put into the join buffer only the tables from
+ within the nest.
+ * /
+ if (i >= first_sjm_table && i < last_sjm_table)
+ {
+ n_tables= i - first_sjm_table; // will be >0 if we got here
+ first_tab= join->join_tab + first_sjm_table;
+ }
+#endif
+*/
+
+static
+uint check_join_cache_usage(JOIN_TAB *tab,
+ ulonglong options,
+ uint no_jbuf_after,
+ uint table_index,
+ JOIN_TAB *prev_tab)
+{
+ Cost_estimate cost;
+ uint flags= 0;
+ ha_rows rows= 0;
+ uint bufsz= 4096;
+ JOIN_CACHE *prev_cache=0;
+ JOIN *join= tab->join;
+ uint cache_level= tab->used_join_cache_level;
+ bool force_unlinked_cache=
+ !(join->allowed_join_cache_types & JOIN_CACHE_INCREMENTAL_BIT);
+ bool no_hashed_cache=
+ !(join->allowed_join_cache_types & JOIN_CACHE_HASHED_BIT);
+ bool no_bka_cache=
+ !(join->allowed_join_cache_types & JOIN_CACHE_BKA_BIT);
+
+ join->return_tab= 0;
+
+ /*
+ Don't use join cache if @@join_cache_level==0 or this table is the first
+ one join suborder (either at top level or inside a bush)
+ */
+ if (cache_level == 0 || !prev_tab)
+ return 0;
+
+ if (force_unlinked_cache && (cache_level%2 == 0))
+ cache_level--;
+
+ if (options & SELECT_NO_JOIN_CACHE)
+ goto no_join_cache;
+
+ if (tab->use_quick == 2)
+ goto no_join_cache;
+
+ if (tab->table->map & join->complex_firstmatch_tables)
+ goto no_join_cache;
+
+ /*
+ Don't use join cache if we're inside a join tab range covered by LooseScan
+ strategy (TODO: LooseScan is very similar to FirstMatch so theoretically it
+ should be possible to use join buffering in the same way we're using it for
+ multi-table firstmatch ranges).
+ */
+ if (tab->inside_loosescan_range)
+ goto no_join_cache;
+
+ if (tab->is_inner_table_of_semijoin() &&
+ !join->allowed_semijoin_with_cache)
+ goto no_join_cache;
+ if (tab->is_inner_table_of_outer_join() &&
+ !join->allowed_outer_join_with_cache)
+ goto no_join_cache;
+
+ /*
+ Non-linked join buffers can't guarantee one match
+ */
+ if (tab->is_nested_inner())
+ {
+ if (force_unlinked_cache || cache_level == 1)
+ goto no_join_cache;
+ if (cache_level & 1)
+ cache_level--;
+ }
+
+ /*
+ Don't use BKA for materialized tables. We could actually have a
+ meaningful use of BKA when linked join buffers are used.
+
+ The problem is, the temp.table is not filled (actually not even opened
+ properly) yet, and this doesn't let us call
+ handler->multi_range_read_info(). It is possible to come up with
+ estimates, etc. without acessing the table, but it seems not to worth the
+ effort now.
+ */
+ if (tab->table->pos_in_table_list->is_materialized_derived())
+ no_bka_cache= true;
+
+ /*
+ Don't use join buffering if we're dictated not to by no_jbuf_after
+ (This is not meaningfully used currently)
+ */
+ if (table_index > no_jbuf_after)
+ goto no_join_cache;
+
+ /*
+ TODO: BNL join buffer should be perfectly ok with tab->bush_children.
+ */
+ if (tab->loosescan_match_tab || tab->bush_children)
+ goto no_join_cache;
+
+ for (JOIN_TAB *first_inner= tab->first_inner; first_inner;
+ first_inner= first_inner->first_upper)
+ {
+ if (first_inner != tab &&
+ (!first_inner->use_join_cache || !(tab-1)->use_join_cache))
+ goto no_join_cache;
+ }
+ if (tab->first_sj_inner_tab && tab->first_sj_inner_tab != tab &&
+ (!tab->first_sj_inner_tab->use_join_cache || !(tab-1)->use_join_cache))
+ goto no_join_cache;
+ if (!prev_tab->use_join_cache)
+ {
+ /*
+ Check whether table tab and the previous one belong to the same nest of
+ inner tables and if so do not use join buffer when joining table tab.
+ */
+ if (tab->first_inner && tab != tab->first_inner)
+ {
+ for (JOIN_TAB *first_inner= tab[-1].first_inner;
+ first_inner;
+ first_inner= first_inner->first_upper)
+ {
+ if (first_inner == tab->first_inner)
+ goto no_join_cache;
+ }
+ }
+ else if (tab->first_sj_inner_tab && tab != tab->first_sj_inner_tab &&
+ tab->first_sj_inner_tab == tab[-1].first_sj_inner_tab)
+ goto no_join_cache;
+ }
+
+ prev_cache= prev_tab->cache;
+
+ switch (tab->type) {
+ case JT_ALL:
+ if (cache_level == 1)
+ prev_cache= 0;
+ if ((tab->cache= new JOIN_CACHE_BNL(join, tab, prev_cache)) &&
+ !tab->cache->init(options & SELECT_DESCRIBE))
+ {
+ tab->icp_other_tables_ok= FALSE;
+ return (2 - MY_TEST(!prev_cache));
+ }
+ goto no_join_cache;
+ case JT_SYSTEM:
+ case JT_CONST:
+ case JT_REF:
+ case JT_EQ_REF:
+ if (cache_level <=2 || (no_hashed_cache && no_bka_cache))
+ goto no_join_cache;
+ if (tab->ref.is_access_triggered())
+ goto no_join_cache;
+
+ if (!tab->is_ref_for_hash_join() && !no_bka_cache)
+ {
+ flags= HA_MRR_NO_NULL_ENDPOINTS | HA_MRR_SINGLE_POINT;
+ if (tab->table->covering_keys.is_set(tab->ref.key))
+ flags|= HA_MRR_INDEX_ONLY;
+ rows= tab->table->file->multi_range_read_info(tab->ref.key, 10, 20,
+ tab->ref.key_parts,
+ &bufsz, &flags, &cost);
+ }
+
+ if ((cache_level <=4 && !no_hashed_cache) || no_bka_cache ||
+ tab->is_ref_for_hash_join() ||
+ ((flags & HA_MRR_NO_ASSOCIATION) && cache_level <=6))
+ {
+ if (!tab->hash_join_is_possible() ||
+ tab->make_scan_filter())
+ goto no_join_cache;
+ if (cache_level == 3)
+ prev_cache= 0;
+ if ((tab->cache= new JOIN_CACHE_BNLH(join, tab, prev_cache)) &&
+ !tab->cache->init(options & SELECT_DESCRIBE))
+ {
+ tab->icp_other_tables_ok= FALSE;
+ return (4 - MY_TEST(!prev_cache));
+ }
+ goto no_join_cache;
+ }
+ if (cache_level > 4 && no_bka_cache)
+ goto no_join_cache;
+
+ if ((flags & HA_MRR_NO_ASSOCIATION) &&
+ (cache_level <= 6 || no_hashed_cache))
+ goto no_join_cache;
+
+ if ((rows != HA_POS_ERROR) && !(flags & HA_MRR_USE_DEFAULT_IMPL))
+ {
+ if (cache_level <= 6 || no_hashed_cache)
+ {
+ if (cache_level == 5)
+ prev_cache= 0;
+ if ((tab->cache= new JOIN_CACHE_BKA(join, tab, flags, prev_cache)) &&
+ !tab->cache->init(options & SELECT_DESCRIBE))
+ return (6 - MY_TEST(!prev_cache));
+ goto no_join_cache;
+ }
+ else
+ {
+ if (cache_level == 7)
+ prev_cache= 0;
+ if ((tab->cache= new JOIN_CACHE_BKAH(join, tab, flags, prev_cache)) &&
+ !tab->cache->init(options & SELECT_DESCRIBE))
+ {
+ tab->idx_cond_fact_out= FALSE;
+ return (8 - MY_TEST(!prev_cache));
+ }
+ goto no_join_cache;
+ }
+ }
+ goto no_join_cache;
+ default : ;
+ }
+
+no_join_cache:
+ if (tab->type != JT_ALL && tab->is_ref_for_hash_join())
+ {
+ tab->type= JT_ALL;
+ tab->ref.key_parts= 0;
+ }
+ revise_cache_usage(tab);
+ return 0;
+}
+
+
+/*
+ Check whether join buffers can be used to join tables of a join
+
+ SYNOPSIS
+ check_join_cache_usage()
+ join join whose tables are to be checked
+ options options of the join
+ no_jbuf_after don't use join buffering after table with this number
+ (The tables are assumed to be numbered in
+ first_linear_tab(join, WITHOUT_CONST_TABLES),
+ next_linear_tab(join, WITH_CONST_TABLES) order).
+
+ DESCRIPTION
+ For each table after the first non-constant table the function checks
+ whether the table can be joined using a join buffer. If the function decides
+ that a join buffer can be employed then it selects the most appropriate join
+ cache object that contains this join buffer whose level is not greater
+ than join_cache_level set for the join. To make this check the function
+ calls the function check_join_cache_usage for every non-constant table.
+
+ NOTES
+ In some situations (e.g. for nested outer joins, for nested semi-joins) only
+ incremental buffers can be used. If it turns out that for some inner table
+ no join buffer can be used then any inner table of an outer/semi-join nest
+ cannot use join buffer. In the case when already chosen buffer must be
+ denied for a table the function recalls check_join_cache_usage()
+ starting from this table. The pointer to the table from which the check
+ has to be restarted is returned in join->return_val (see the description
+ of check_join_cache_usage).
+*/
+
+void check_join_cache_usage_for_tables(JOIN *join, ulonglong options,
+ uint no_jbuf_after)
+{
+ JOIN_TAB *tab;
+ JOIN_TAB *prev_tab;
+
+ for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+ tab->used_join_cache_level= join->max_allowed_join_cache_level;
+ }
+
+ uint idx= join->const_tables;
+ for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+restart:
+ tab->icp_other_tables_ok= TRUE;
+ tab->idx_cond_fact_out= TRUE;
+
+ /*
+ Check if we have a preceding join_tab, as something that will feed us
+ records that we could buffer. We don't have it, if
+ - this is the first non-const table in the join order,
+ - this is the first table inside an SJM nest.
+ */
+ prev_tab= tab - 1;
+ if (tab == join->join_tab + join->const_tables ||
+ (tab->bush_root_tab && tab->bush_root_tab->bush_children->start == tab))
+ prev_tab= NULL;
+
+ switch (tab->type) {
+ case JT_SYSTEM:
+ case JT_CONST:
+ case JT_EQ_REF:
+ case JT_REF:
+ case JT_REF_OR_NULL:
+ case JT_ALL:
+ tab->used_join_cache_level= check_join_cache_usage(tab, options,
+ no_jbuf_after,
+ idx,
+ prev_tab);
+ tab->use_join_cache= MY_TEST(tab->used_join_cache_level);
+ /*
+ psergey-merge: todo: raise the question that this is really stupid that
+ we can first allocate a join buffer, then decide not to use it and free
+ it.
+ */
+ if (join->return_tab)
+ {
+ tab= join->return_tab;
+ goto restart;
+ }
+ break;
+ default:
+ tab->used_join_cache_level= 0;
+ }
+ if (!tab->bush_children)
+ idx++;
+ }
+}
+
+/**
+ Remove pushdown conditions that are already checked by the scan phase
+ of BNL/BNLH joins.
+
+ @note
+ If the single-table condition for this table will be used by a
+ blocked join to pre-filter this table's rows, there is no need
+ to re-check the same single-table condition for each joined record.
+
+ This method removes from JOIN_TAB::select_cond and JOIN_TAB::select::cond
+ all top-level conjuncts that also appear in in JOIN_TAB::cache_select::cond.
+*/
+
+void JOIN_TAB::remove_redundant_bnl_scan_conds()
+{
+ if (!(select_cond && cache_select && cache &&
+ (cache->get_join_alg() == JOIN_CACHE::BNL_JOIN_ALG ||
+ cache->get_join_alg() == JOIN_CACHE::BNLH_JOIN_ALG)))
+ return;
+
+ /*
+ select->cond is not processed separately. This method assumes it is always
+ the same as select_cond.
+ */
+ DBUG_ASSERT(!select || !select->cond ||
+ (select->cond == select_cond));
+
+ if (is_cond_and(select_cond))
+ {
+ List_iterator<Item> pushed_cond_li(*((Item_cond*) select_cond)->argument_list());
+ Item *pushed_item;
+ Item_cond_and *reduced_select_cond= new Item_cond_and;
+
+ if (is_cond_and(cache_select->cond))
+ {
+ List_iterator<Item> scan_cond_li(*((Item_cond*) cache_select->cond)->argument_list());
+ Item *scan_item;
+ while ((pushed_item= pushed_cond_li++))
+ {
+ bool found= false;
+ scan_cond_li.rewind();
+ while ((scan_item= scan_cond_li++))
+ {
+ if (pushed_item->eq(scan_item, 0))
+ {
+ found= true;
+ break;
+ }
+ }
+ if (!found)
+ reduced_select_cond->add(pushed_item);
+ }
+ }
+ else
+ {
+ while ((pushed_item= pushed_cond_li++))
+ {
+ if (!pushed_item->eq(cache_select->cond, 0))
+ reduced_select_cond->add(pushed_item);
+ }
+ }
+
+ /*
+ JOIN_CACHE::check_match uses JOIN_TAB::select->cond instead of
+ JOIN_TAB::select_cond. set_cond() sets both pointers.
+ */
+ if (reduced_select_cond->argument_list()->is_empty())
+ set_cond(NULL);
+ else if (reduced_select_cond->argument_list()->elements == 1)
+ set_cond(reduced_select_cond->argument_list()->head());
+ else
+ {
+ reduced_select_cond->quick_fix_field();
+ set_cond(reduced_select_cond);
+ }
+ }
+ else if (select_cond->eq(cache_select->cond, 0))
+ set_cond(NULL);
+}
+
+
+/*
+ Plan refinement stage: do various setup things for the executor
+
+ SYNOPSIS
+ make_join_readinfo()
+ join Join being processed
+ options Join's options (checking for SELECT_DESCRIBE,
+ SELECT_NO_JOIN_CACHE)
+ no_jbuf_after Don't use join buffering after table with this number.
+
+ DESCRIPTION
+ Plan refinement stage: do various set ups for the executioner
+ - set up use of join buffering
+ - push index conditions
+ - increment relevant counters
+ - etc
+
+ RETURN
+ FALSE - OK
+ TRUE - Out of memory
+*/
+
+static bool
+make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after)
+{
+ JOIN_TAB *tab;
+ uint i;
+ DBUG_ENTER("make_join_readinfo");
+
+ bool statistics= MY_TEST(!(join->select_options & SELECT_DESCRIBE));
+ bool sorted= 1;
+
+ join->complex_firstmatch_tables= table_map(0);
+
+ if (!join->select_lex->sj_nests.is_empty() &&
+ setup_semijoin_dups_elimination(join, options, no_jbuf_after))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ /* For const tables, set partial_join_cardinality to 1. */
+ for (tab= join->join_tab; tab != join->join_tab + join->const_tables; tab++)
+ tab->partial_join_cardinality= 1;
+
+ JOIN_TAB *prev_tab= NULL;
+ i= join->const_tables;
+ for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab;
+ prev_tab=tab, tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+ /*
+ The approximation below for partial join cardinality is not good because
+ - it does not take into account some pushdown predicates
+ - it does not differentiate between inner joins, outer joins and
+ semi-joins.
+ Later it should be improved.
+ */
+
+ if (tab->bush_root_tab && tab->bush_root_tab->bush_children->start == tab)
+ prev_tab= NULL;
+ DBUG_ASSERT(tab->bush_children || tab->table == join->best_positions[i].table->table);
+
+ tab->partial_join_cardinality= join->best_positions[i].records_read *
+ (prev_tab? prev_tab->partial_join_cardinality : 1);
+ if (!tab->bush_children)
+ i++;
+ }
+
+ check_join_cache_usage_for_tables(join, options, no_jbuf_after);
+
+ JOIN_TAB *first_tab;
+ for (tab= first_tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
+ {
+ if (tab->bush_children)
+ {
+ if (setup_sj_materialization_part2(tab))
+ return TRUE;
+ }
+
+ TABLE *table=tab->table;
+ uint jcl= tab->used_join_cache_level;
+ tab->read_record.table= table;
+ tab->read_record.unlock_row= rr_unlock_row;
+ tab->sorted= sorted;
+ sorted= 0; // only first must be sorted
+
+
+ /*
+ We should not set tab->next_select for the last table in the
+ SMJ-nest, as setup_sj_materialization() has already set it to
+ end_sj_materialize.
+ */
+ if (!(tab->bush_root_tab &&
+ tab->bush_root_tab->bush_children->end == tab + 1))
+ {
+ tab->next_select=sub_select; /* normal select */
+ }
+
+
+ if (tab->loosescan_match_tab)
+ {
+ if (!(tab->loosescan_buf= (uchar*)join->thd->alloc(tab->
+ loosescan_key_len)))
+ return TRUE; /* purecov: inspected */
+ tab->sorted= TRUE;
+ }
+ table->status=STATUS_NO_RECORD;
+ pick_table_access_method (tab);
+
+ if (jcl)
+ tab[-1].next_select=sub_select_cache;
+
+ if (tab->cache && tab->cache->get_join_alg() == JOIN_CACHE::BNLH_JOIN_ALG)
+ tab->type= JT_HASH;
+
+ switch (tab->type) {
+ case JT_SYSTEM: // Only happens with left join
+ case JT_CONST: // Only happens with left join
+ /* Only happens with outer joins */
+ tab->read_first_record= tab->type == JT_SYSTEM ?
+ join_read_system :join_read_const;
+ if (table->covering_keys.is_set(tab->ref.key) &&
+ !table->no_keyread)
+ table->enable_keyread();
+ else if ((!jcl || jcl > 4) && !tab->ref.is_access_triggered())
+ push_index_cond(tab, tab->ref.key);
+ break;
+ case JT_EQ_REF:
+ tab->read_record.unlock_row= join_read_key_unlock_row;
+ /* fall through */
+ if (table->covering_keys.is_set(tab->ref.key) &&
+ !table->no_keyread)
+ table->enable_keyread();
+ else if ((!jcl || jcl > 4) && !tab->ref.is_access_triggered())
+ push_index_cond(tab, tab->ref.key);
+ break;
+ case JT_REF_OR_NULL:
+ case JT_REF:
+ if (tab->select)
+ {
+ delete tab->select->quick;
+ tab->select->quick=0;
+ }
+ delete tab->quick;
+ tab->quick=0;
+ if (table->covering_keys.is_set(tab->ref.key) &&
+ !table->no_keyread)
+ table->enable_keyread();
+ else if ((!jcl || jcl > 4) && !tab->ref.is_access_triggered())
+ push_index_cond(tab, tab->ref.key);
+ break;
+ case JT_ALL:
+ case JT_HASH:
+ /*
+ If previous table use cache
+ If the incoming data set is already sorted don't use cache.
+ Also don't use cache if this is the first table in semi-join
+ materialization nest.
+ */
+ /* These init changes read_record */
+ if (tab->use_quick == 2)
+ {
+ join->thd->set_status_no_good_index_used();
+ tab->read_first_record= join_init_quick_read_record;
+ if (statistics)
+ join->thd->inc_status_select_range_check();
+ }
+ else
+ {
+ if (!tab->bush_children)
+ tab->read_first_record= join_init_read_record;
+ if (tab == first_tab)
+ {
+ if (tab->select && tab->select->quick)
+ {
+ if (statistics)
+ join->thd->inc_status_select_range();
+ }
+ else
+ {
+ join->thd->set_status_no_index_used();
+ if (statistics)
+ {
+ join->thd->inc_status_select_scan();
+ join->thd->query_plan_flags|= QPLAN_FULL_SCAN;
+ }
+ }
+ }
+ else
+ {
+ if (tab->select && tab->select->quick)
+ {
+ if (statistics)
+ join->thd->inc_status_select_full_range_join();
+ }
+ else
+ {
+ join->thd->set_status_no_index_used();
+ if (statistics)
+ {
+ join->thd->inc_status_select_full_join();
+ join->thd->query_plan_flags|= QPLAN_FULL_JOIN;
+ }
+ }
+ }
+ if (!table->no_keyread)
+ {
+ if (tab->select && tab->select->quick &&
+ tab->select->quick->index != MAX_KEY && //not index_merge
+ table->covering_keys.is_set(tab->select->quick->index))
+ table->enable_keyread();
+ else if (!table->covering_keys.is_clear_all() &&
+ !(tab->select && tab->select->quick))
+ { // Only read index tree
+ if (tab->loosescan_match_tab)
+ tab->index= tab->loosescan_key;
+ else
+ {
+#ifdef BAD_OPTIMIZATION
+ /*
+ It has turned out that the below change, while speeding things
+ up for disk-bound loads, slows them down for cases when the data
+ is in disk cache (see BUG#35850):
+ See bug #26447: "Using the clustered index for a table scan
+ is always faster than using a secondary index".
+ */
+ if (table->s->primary_key != MAX_KEY &&
+ table->file->primary_key_is_clustered())
+ tab->index= table->s->primary_key;
+ else
+#endif
+ tab->index=find_shortest_key(table, & table->covering_keys);
+ }
+ tab->read_first_record= join_read_first;
+ /* Read with index_first / index_next */
+ tab->type= tab->type == JT_ALL ? JT_NEXT : JT_HASH_NEXT;
+ }
+ }
+ if (tab->select && tab->select->quick &&
+ tab->select->quick->index != MAX_KEY && ! tab->table->key_read)
+ push_index_cond(tab, tab->select->quick->index);
+ }
+ break;
+ case JT_FT:
+ break;
+ /* purecov: begin deadcode */
+ default:
+ DBUG_PRINT("error",("Table type %d found",tab->type));
+ break;
+ case JT_UNKNOWN:
+ case JT_MAYBE_REF:
+ abort();
+ /* purecov: end */
+ }
+
+ tab->remove_redundant_bnl_scan_conds();
+ DBUG_EXECUTE("where",
+ char buff[256];
+ String str(buff,sizeof(buff),system_charset_info);
+ str.length(0);
+ str.append(tab->table? tab->table->alias.c_ptr() :"<no_table_name>");
+ str.append(" final_pushdown_cond");
+ print_where(tab->select_cond, str.c_ptr_safe(), QT_ORDINARY););
+ }
+ uint n_top_tables= join->join_tab_ranges.head()->end -
+ join->join_tab_ranges.head()->start;
+
+ join->join_tab[n_top_tables - 1].next_select=0; /* Set by do_select */
+
+ /*
+ If a join buffer is used to join a table the ordering by an index
+ for the first non-constant table cannot be employed anymore.
+ */
+ for (tab= join->join_tab + join->const_tables ;
+ tab != join->join_tab + n_top_tables ; tab++)
+ {
+ if (tab->use_join_cache)
+ {
+ JOIN_TAB *sort_by_tab= join->group && join->simple_group &&
+ join->group_list ?
+ join->join_tab+join->const_tables :
+ join->get_sort_by_join_tab();
+ /*
+ It could be that sort_by_tab==NULL, and the plan is to use filesort()
+ on the first table.
+ */
+ if (join->order)
+ {
+ join->simple_order= 0;
+ join->need_tmp= 1;
+ }
+
+ if (join->group && !join->group_optimized_away)
+ {
+ join->need_tmp= 1;
+ join->simple_group= 0;
+ }
+
+ if (sort_by_tab)
+ {
+ join->need_tmp= 1;
+ join->simple_order= join->simple_group= 0;
+ if (sort_by_tab->type == JT_NEXT &&
+ !sort_by_tab->table->covering_keys.is_set(sort_by_tab->index))
+ {
+ sort_by_tab->type= JT_ALL;
+ sort_by_tab->read_first_record= join_init_read_record;
+ }
+ else if (sort_by_tab->type == JT_HASH_NEXT &&
+ !sort_by_tab->table->covering_keys.is_set(sort_by_tab->index))
+ {
+ sort_by_tab->type= JT_HASH;
+ sort_by_tab->read_first_record= join_init_read_record;
+ }
+ }
+ break;
+ }
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Give error if we some tables are done with a full join.
+
+ This is used by multi_table_update and multi_table_delete when running
+ in safe mode.
+
+ @param join Join condition
+
+ @retval
+ 0 ok
+ @retval
+ 1 Error (full join used)
+*/
+
+bool error_if_full_join(JOIN *join)
+{
+ for (JOIN_TAB *tab=first_top_level_tab(join, WITH_CONST_TABLES); tab;
+ tab= next_top_level_tab(join, tab))
+ {
+ if (tab->type == JT_ALL && (!tab->select || !tab->select->quick))
+ {
+ my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE,
+ ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0));
+ return(1);
+ }
+ }
+ return(0);
+}
+
+
+/**
+ cleanup JOIN_TAB.
+
+ DESCRIPTION
+ This is invoked when we've finished all join executions.
+*/
+
+void JOIN_TAB::cleanup()
+{
+ DBUG_ENTER("JOIN_TAB::cleanup");
+ DBUG_PRINT("enter", ("table %s.%s",
+ (table ? table->s->db.str : "?"),
+ (table ? table->s->table_name.str : "?")));
+ delete select;
+ select= 0;
+ delete quick;
+ quick= 0;
+ if (cache)
+ {
+ cache->free();
+ cache= 0;
+ }
+ limit= 0;
+ if (table)
+ {
+ table->disable_keyread();
+ table->file->ha_index_or_rnd_end();
+ preread_init_done= FALSE;
+ if (table->pos_in_table_list &&
+ table->pos_in_table_list->jtbm_subselect)
+ {
+ if (table->pos_in_table_list->jtbm_subselect->is_jtbm_const_tab)
+ {
+ /*
+ Set this to NULL so that cleanup_empty_jtbm_semi_joins() doesn't
+ attempt to make another free_tmp_table call.
+ */
+ table->pos_in_table_list->table= NULL;
+ free_tmp_table(join->thd, table);
+ table= NULL;
+ }
+ else
+ {
+ end_read_record(&read_record);
+ table->pos_in_table_list->jtbm_subselect->cleanup();
+ /*
+ The above call freed the materializedd temptable. Set it to NULL so
+ that we don't attempt to touch it if JOIN_TAB::cleanup() is invoked
+ multiple times (it may be)
+ */
+ table=NULL;
+ }
+ DBUG_VOID_RETURN;
+ }
+ /*
+ We need to reset this for next select
+ (Tested in part_of_refkey)
+ */
+ table->reginfo.join_tab= 0;
+ }
+ end_read_record(&read_record);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Estimate the time to get rows of the joined table
+*/
+
+double JOIN_TAB::scan_time()
+{
+ double res;
+ if (table->created)
+ {
+ if (table->is_filled_at_execution())
+ {
+ get_delayed_table_estimates(table, &records, &read_time,
+ &startup_cost);
+ found_records= records;
+ table->quick_condition_rows= records;
+ }
+ else
+ {
+ found_records= records= table->stat_records();
+ read_time= table->file->scan_time();
+ /*
+ table->quick_condition_rows has already been set to
+ table->file->stats.records
+ */
+ }
+ res= read_time;
+ }
+ else
+ {
+ found_records= records=table->stat_records();
+ read_time= found_records ? (double)found_records: 10.0;// TODO:fix this stub
+ res= read_time;
+ }
+ return res;
+}
+
+
+/**
+ Estimate the number of rows that a an access method will read from a table.
+
+ @todo: why not use JOIN_TAB::found_records
+*/
+
+ha_rows JOIN_TAB::get_examined_rows()
+{
+ double examined_rows;
+
+ if (select && select->quick && use_quick != 2)
+ examined_rows= select->quick->records;
+ else if (type == JT_NEXT || type == JT_ALL ||
+ type == JT_HASH || type ==JT_HASH_NEXT)
+ {
+ if (limit)
+ {
+ /*
+ @todo This estimate is wrong, a LIMIT query may examine much more rows
+ than the LIMIT itself.
+ */
+ examined_rows= limit;
+ }
+ else
+ {
+ if (table->is_filled_at_execution())
+ examined_rows= records;
+ else
+ {
+ /*
+ handler->info(HA_STATUS_VARIABLE) has been called in
+ make_join_statistics()
+ */
+ examined_rows= table->stat_records();
+ }
+ }
+ }
+ else
+ examined_rows= records_read;
+
+ return (ha_rows) examined_rows;
+}
+
+
+/**
+ Initialize the join_tab before reading.
+ Currently only derived table/view materialization is done here.
+
+ TODO: consider moving this together with join_tab_execution_startup
+*/
+bool JOIN_TAB::preread_init()
+{
+ TABLE_LIST *derived= table->pos_in_table_list;
+ if (!derived || !derived->is_materialized_derived())
+ {
+ preread_init_done= TRUE;
+ return FALSE;
+ }
+
+ /* Materialize derived table/view. */
+ if (!derived->get_unit()->executed &&
+ mysql_handle_single_derived(join->thd->lex,
+ derived, DT_CREATE | DT_FILL))
+ return TRUE;
+
+ preread_init_done= TRUE;
+ if (select && select->quick)
+ select->quick->replace_handler(table->file);
+
+ DBUG_EXECUTE_IF("show_explain_probe_join_tab_preread",
+ if (dbug_user_var_equals_int(join->thd,
+ "show_explain_probe_select_id",
+ join->select_lex->select_number))
+ dbug_serve_apcs(join->thd, 1);
+ );
+
+ /* init ftfuns for just initialized derived table */
+ if (table->fulltext_searched)
+ init_ftfuncs(join->thd, join->select_lex, MY_TEST(join->order));
+
+ return FALSE;
+}
+
+
+/**
+ Build a TABLE_REF structure for index lookup in the temporary table
+
+ @param thd Thread handle
+ @param tmp_key The temporary table key
+ @param it The iterator of items for lookup in the key
+ @param skip Number of fields from the beginning to skip
+
+ @details
+ Build TABLE_REF object for lookup in the key 'tmp_key' using items
+ accessible via item iterator 'it'.
+
+ @retval TRUE Error
+ @retval FALSE OK
+*/
+
+bool TABLE_REF::tmp_table_index_lookup_init(THD *thd,
+ KEY *tmp_key,
+ Item_iterator &it,
+ bool value,
+ uint skip)
+{
+ uint tmp_key_parts= tmp_key->user_defined_key_parts;
+ uint i;
+ DBUG_ENTER("TABLE_REF::tmp_table_index_lookup_init");
+
+ key= 0; /* The only temp table index. */
+ key_length= tmp_key->key_length;
+ if (!(key_buff=
+ (uchar*) thd->calloc(ALIGN_SIZE(tmp_key->key_length) * 2)) ||
+ !(key_copy=
+ (store_key**) thd->alloc((sizeof(store_key*) *
+ (tmp_key_parts + 1)))) ||
+ !(items=
+ (Item**) thd->alloc(sizeof(Item*) * tmp_key_parts)))
+ DBUG_RETURN(TRUE);
+
+ key_buff2= key_buff + ALIGN_SIZE(tmp_key->key_length);
+
+ KEY_PART_INFO *cur_key_part= tmp_key->key_part;
+ store_key **ref_key= key_copy;
+ uchar *cur_ref_buff= key_buff;
+
+ it.open();
+ for (i= 0; i < skip; i++) it.next();
+ for (i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++)
+ {
+ Item *item= it.next();
+ DBUG_ASSERT(item);
+ items[i]= item;
+ int null_count= MY_TEST(cur_key_part->field->real_maybe_null());
+ *ref_key= new store_key_item(thd, cur_key_part->field,
+ /* TIMOUR:
+ the NULL byte is taken into account in
+ cur_key_part->store_length, so instead of
+ cur_ref_buff + MY_TEST(maybe_null), we could
+ use that information instead.
+ */
+ cur_ref_buff + null_count,
+ null_count ? cur_ref_buff : 0,
+ cur_key_part->length, items[i], value);
+ cur_ref_buff+= cur_key_part->store_length;
+ }
+ *ref_key= NULL; /* End marker. */
+ key_err= 1;
+ key_parts= tmp_key_parts;
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Check if ref access uses "Full scan on NULL key" (i.e. it actually alternates
+ between ref access and full table scan)
+*/
+
+bool TABLE_REF::is_access_triggered()
+{
+ for (uint i = 0; i < key_parts; i++)
+ {
+ if (cond_guards[i])
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Partially cleanup JOIN after it has executed: close index or rnd read
+ (table cursors), free quick selects.
+
+ This function is called in the end of execution of a JOIN, before the used
+ tables are unlocked and closed.
+
+ For a join that is resolved using a temporary table, the first sweep is
+ performed against actual tables and an intermediate result is inserted
+ into the temprorary table.
+ The last sweep is performed against the temporary table. Therefore,
+ the base tables and associated buffers used to fill the temporary table
+ are no longer needed, and this function is called to free them.
+
+ For a join that is performed without a temporary table, this function
+ is called after all rows are sent, but before EOF packet is sent.
+
+ For a simple SELECT with no subqueries this function performs a full
+ cleanup of the JOIN and calls mysql_unlock_read_tables to free used base
+ tables.
+
+ If a JOIN is executed for a subquery or if it has a subquery, we can't
+ do the full cleanup and need to do a partial cleanup only.
+ - If a JOIN is not the top level join, we must not unlock the tables
+ because the outer select may not have been evaluated yet, and we
+ can't unlock only selected tables of a query.
+ - Additionally, if this JOIN corresponds to a correlated subquery, we
+ should not free quick selects and join buffers because they will be
+ needed for the next execution of the correlated subquery.
+ - However, if this is a JOIN for a [sub]select, which is not
+ a correlated subquery itself, but has subqueries, we can free it
+ fully and also free JOINs of all its subqueries. The exception
+ is a subquery in SELECT list, e.g: @n
+ SELECT a, (select MY_MAX(b) from t1) group by c @n
+ This subquery will not be evaluated at first sweep and its value will
+ not be inserted into the temporary table. Instead, it's evaluated
+ when selecting from the temporary table. Therefore, it can't be freed
+ here even though it's not correlated.
+
+ @todo
+ Unlock tables even if the join isn't top level select in the tree
+*/
+
+void JOIN::join_free()
+{
+ SELECT_LEX_UNIT *tmp_unit;
+ SELECT_LEX *sl;
+ /*
+ Optimization: if not EXPLAIN and we are done with the JOIN,
+ free all tables.
+ */
+ bool full= !(select_lex->uncacheable) && !(thd->lex->describe);
+ bool can_unlock= full;
+ DBUG_ENTER("JOIN::join_free");
+
+ cleanup(full);
+
+ for (tmp_unit= select_lex->first_inner_unit();
+ tmp_unit;
+ tmp_unit= tmp_unit->next_unit())
+ for (sl= tmp_unit->first_select(); sl; sl= sl->next_select())
+ {
+ Item_subselect *subselect= sl->master_unit()->item;
+ bool full_local= full && (!subselect || subselect->is_evaluated());
+ /*
+ If this join is evaluated, we can fully clean it up and clean up all
+ its underlying joins even if they are correlated -- they will not be
+ used any more anyway.
+ If this join is not yet evaluated, we still must clean it up to
+ close its table cursors -- it may never get evaluated, as in case of
+ ... HAVING FALSE OR a IN (SELECT ...))
+ but all table cursors must be closed before the unlock.
+ */
+ sl->cleanup_all_joins(full_local);
+ /* Can't unlock if at least one JOIN is still needed */
+ can_unlock= can_unlock && full_local;
+ }
+
+ /*
+ We are not using tables anymore
+ Unlock all tables. We may be in an INSERT .... SELECT statement.
+ */
+ if (can_unlock && lock && thd->lock && ! thd->locked_tables_mode &&
+ !(select_options & SELECT_NO_UNLOCK) &&
+ !select_lex->subquery_in_having &&
+ (select_lex == (thd->lex->unit.fake_select_lex ?
+ thd->lex->unit.fake_select_lex : &thd->lex->select_lex)))
+ {
+ /*
+ TODO: unlock tables even if the join isn't top level select in the
+ tree.
+ */
+ mysql_unlock_read_tables(thd, lock); // Don't free join->lock
+ lock= 0;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Free resources of given join.
+
+ @param fill true if we should free all resources, call with full==1
+ should be last, before it this function can be called with
+ full==0
+
+ @note
+ With subquery this function definitely will be called several times,
+ but even for simple query it can be called several times.
+*/
+
+void JOIN::cleanup(bool full)
+{
+ DBUG_ENTER("JOIN::cleanup");
+ DBUG_PRINT("enter", ("full %u", (uint) full));
+
+ if (full)
+ have_query_plan= QEP_DELETED;
+
+ if (table)
+ {
+ JOIN_TAB *tab;
+ /*
+ Only a sorted table may be cached. This sorted table is always the
+ first non const table in join->table
+ */
+ if (table_count > const_tables) // Test for not-const tables
+ {
+ JOIN_TAB *first_tab= first_top_level_tab(this, WITHOUT_CONST_TABLES);
+ if (first_tab->table)
+ {
+ free_io_cache(first_tab->table);
+ filesort_free_buffers(first_tab->table, full);
+ }
+ }
+ if (full)
+ {
+ JOIN_TAB *sort_tab= first_linear_tab(this, WITH_BUSH_ROOTS,
+ WITHOUT_CONST_TABLES);
+ if (pre_sort_join_tab)
+ {
+ if (sort_tab && sort_tab->select == pre_sort_join_tab->select)
+ {
+ pre_sort_join_tab->select= NULL;
+ }
+ else
+ clean_pre_sort_join_tab();
+ }
+ /*
+ Call cleanup() on join tabs used by the join optimization
+ (join->join_tab may now be pointing to result of make_simple_join
+ reading from the temporary table)
+
+ We also need to check table_count to handle various degenerate joins
+ w/o tables: they don't have some members initialized and
+ WALK_OPTIMIZATION_TABS may not work correctly for them.
+ */
+ enum enum_exec_or_opt tabs_kind;
+ if (first_breadth_first_tab(this, WALK_OPTIMIZATION_TABS))
+ tabs_kind= WALK_OPTIMIZATION_TABS;
+ else
+ tabs_kind= WALK_EXECUTION_TABS;
+ if (table_count)
+ {
+ for (tab= first_breadth_first_tab(this, tabs_kind); tab;
+ tab= next_breadth_first_tab(this, tabs_kind, tab))
+ {
+ tab->cleanup();
+ }
+
+ if (tabs_kind == WALK_OPTIMIZATION_TABS &&
+ first_breadth_first_tab(this, WALK_OPTIMIZATION_TABS) !=
+ first_breadth_first_tab(this, WALK_EXECUTION_TABS))
+ {
+ JOIN_TAB *jt= first_breadth_first_tab(this, WALK_EXECUTION_TABS);
+ /* We've walked optimization tabs. do execution ones too */
+ if (jt)
+ jt->cleanup();
+ }
+ }
+ cleaned= true;
+
+ }
+ else
+ {
+ for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITH_CONST_TABLES); tab;
+ tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS))
+ {
+ if (tab->table)
+ {
+ DBUG_PRINT("info", ("close index: %s.%s alias: %s",
+ tab->table->s->db.str,
+ tab->table->s->table_name.str,
+ tab->table->alias.c_ptr()));
+ tab->table->file->ha_index_or_rnd_end();
+ }
+ }
+ }
+ }
+ if (full)
+ {
+ cleanup_empty_jtbm_semi_joins(this, join_list);
+ /*
+ Ensure that the following delete_elements() would not be called
+ twice for the same list.
+ */
+ if (tmp_join && tmp_join != this &&
+ tmp_join->group_fields == this->group_fields)
+ tmp_join->group_fields.empty();
+
+ // Run Cached_item DTORs!
+ group_fields.delete_elements();
+
+ /*
+ We can't call delete_elements() on copy_funcs as this will cause
+ problems in free_elements() as some of the elements are then deleted.
+ */
+ tmp_table_param.copy_funcs.empty();
+ /*
+ If we have tmp_join and 'this' JOIN is not tmp_join and
+ tmp_table_param.copy_field's of them are equal then we have to remove
+ pointer to tmp_table_param.copy_field from tmp_join, because it qill
+ be removed in tmp_table_param.cleanup().
+ */
+ if (tmp_join &&
+ tmp_join != this &&
+ tmp_join->tmp_table_param.copy_field ==
+ tmp_table_param.copy_field)
+ {
+ tmp_join->tmp_table_param.copy_field=
+ tmp_join->tmp_table_param.save_copy_field= 0;
+ }
+ tmp_table_param.cleanup();
+
+ if (!join_tab)
+ {
+ List_iterator<TABLE_LIST> li(*join_list);
+ TABLE_LIST *table_ref;
+ while ((table_ref= li++))
+ {
+ if (table_ref->table &&
+ table_ref->jtbm_subselect &&
+ table_ref->jtbm_subselect->is_jtbm_const_tab)
+ {
+ free_tmp_table(thd, table_ref->table);
+ table_ref->table= NULL;
+ }
+ }
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Remove the following expressions from ORDER BY and GROUP BY:
+ Constant expressions @n
+ Expression that only uses tables that are of type EQ_REF and the reference
+ is in the ORDER list or if all refereed tables are of the above type.
+
+ In the following, the X field can be removed:
+ @code
+ SELECT * FROM t1,t2 WHERE t1.a=t2.a ORDER BY t1.a,t2.X
+ SELECT * FROM t1,t2,t3 WHERE t1.a=t2.a AND t2.b=t3.b ORDER BY t1.a,t3.X
+ @endcode
+
+ These can't be optimized:
+ @code
+ SELECT * FROM t1,t2 WHERE t1.a=t2.a ORDER BY t2.X,t1.a
+ SELECT * FROM t1,t2 WHERE t1.a=t2.a AND t1.b=t2.b ORDER BY t1.a,t2.c
+ SELECT * FROM t1,t2 WHERE t1.a=t2.a ORDER BY t2.b,t1.a
+ @endcode
+
+ TODO: this function checks ORDER::used, which can only have a value of 0.
+*/
+
+static bool
+eq_ref_table(JOIN *join, ORDER *start_order, JOIN_TAB *tab)
+{
+ if (tab->cached_eq_ref_table) // If cached
+ return tab->eq_ref_table;
+ tab->cached_eq_ref_table=1;
+ /* We can skip const tables only if not an outer table */
+ if (tab->type == JT_CONST && !tab->first_inner)
+ return (tab->eq_ref_table=1); /* purecov: inspected */
+ if (tab->type != JT_EQ_REF || tab->table->maybe_null)
+ return (tab->eq_ref_table=0); // We must use this
+ Item **ref_item=tab->ref.items;
+ Item **end=ref_item+tab->ref.key_parts;
+ uint found=0;
+ table_map map=tab->table->map;
+
+ for (; ref_item != end ; ref_item++)
+ {
+ if (! (*ref_item)->const_item())
+ { // Not a const ref
+ ORDER *order;
+ for (order=start_order ; order ; order=order->next)
+ {
+ if ((*ref_item)->eq(order->item[0],0))
+ break;
+ }
+ if (order)
+ {
+ if (!(order->used & map))
+ {
+ found++;
+ order->used|= map;
+ }
+ continue; // Used in ORDER BY
+ }
+ if (!only_eq_ref_tables(join,start_order, (*ref_item)->used_tables()))
+ return (tab->eq_ref_table=0);
+ }
+ }
+ /* Check that there was no reference to table before sort order */
+ for (; found && start_order ; start_order=start_order->next)
+ {
+ if (start_order->used & map)
+ {
+ found--;
+ continue;
+ }
+ if (start_order->depend_map & map)
+ return (tab->eq_ref_table=0);
+ }
+ return tab->eq_ref_table=1;
+}
+
+
+static bool
+only_eq_ref_tables(JOIN *join,ORDER *order,table_map tables)
+{
+ tables&= ~PSEUDO_TABLE_BITS;
+ for (JOIN_TAB **tab=join->map2table ; tables ; tab++, tables>>=1)
+ {
+ if (tables & 1 && !eq_ref_table(join, order, *tab))
+ return 0;
+ }
+ return 1;
+}
+
+
+/** Update the dependency map for the tables. */
+
+static void update_depend_map(JOIN *join)
+{
+ JOIN_TAB *join_tab;
+ for (join_tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITH_CONST_TABLES);
+ join_tab;
+ join_tab= next_linear_tab(join, join_tab, WITH_BUSH_ROOTS))
+ {
+ TABLE_REF *ref= &join_tab->ref;
+ table_map depend_map=0;
+ Item **item=ref->items;
+ uint i;
+ for (i=0 ; i < ref->key_parts ; i++,item++)
+ depend_map|=(*item)->used_tables();
+ ref->depend_map=depend_map & ~OUTER_REF_TABLE_BIT;
+ depend_map&= ~OUTER_REF_TABLE_BIT;
+ for (JOIN_TAB **tab=join->map2table;
+ depend_map ;
+ tab++,depend_map>>=1 )
+ {
+ if (depend_map & 1)
+ ref->depend_map|=(*tab)->ref.depend_map;
+ }
+ }
+}
+
+
+/** Update the dependency map for the sort order. */
+
+static void update_depend_map_for_order(JOIN *join, ORDER *order)
+{
+ for (; order ; order=order->next)
+ {
+ table_map depend_map;
+ order->item[0]->update_used_tables();
+ order->depend_map=depend_map=order->item[0]->used_tables();
+ order->used= 0;
+ // Not item_sum(), RAND() and no reference to table outside of sub select
+ if (!(order->depend_map & (OUTER_REF_TABLE_BIT | RAND_TABLE_BIT))
+ && !order->item[0]->with_sum_func)
+ {
+ for (JOIN_TAB **tab=join->map2table;
+ depend_map ;
+ tab++, depend_map>>=1)
+ {
+ if (depend_map & 1)
+ order->depend_map|=(*tab)->ref.depend_map;
+ }
+ }
+ }
+}
+
+
+/**
+ Remove all constants and check if ORDER only contains simple
+ expressions.
+
+ We also remove all duplicate expressions, keeping only the first one.
+
+ simple_order is set to 1 if sort_order only uses fields from head table
+ and the head table is not a LEFT JOIN table.
+
+ @param join Join handler
+ @param first_order List of SORT or GROUP order
+ @param cond WHERE statement
+ @param change_list Set to 1 if we should remove things from list.
+ If this is not set, then only simple_order is
+ calculated.
+ @param simple_order Set to 1 if we are only using simple
+ expressions.
+
+ @return
+ Returns new sort order
+*/
+
+static ORDER *
+remove_const(JOIN *join,ORDER *first_order, COND *cond,
+ bool change_list, bool *simple_order)
+{
+ if (join->table_count == join->const_tables)
+ return change_list ? 0 : first_order; // No need to sort
+
+ ORDER *order,**prev_ptr, *tmp_order;
+ table_map first_table;
+ table_map not_const_tables= ~join->const_table_map;
+ table_map ref;
+ bool first_is_base_table= FALSE;
+ DBUG_ENTER("remove_const");
+
+ LINT_INIT(first_table); /* protected by first_is_base_table */
+ if (join->join_tab[join->const_tables].table)
+ {
+ first_table= join->join_tab[join->const_tables].table->map;
+ first_is_base_table= TRUE;
+ }
+
+ /*
+ Cleanup to avoid interference of calls of this function for
+ ORDER BY and GROUP BY
+ */
+ for (JOIN_TAB *tab= join->join_tab + join->const_tables;
+ tab < join->join_tab + join->table_count;
+ tab++)
+ tab->cached_eq_ref_table= FALSE;
+
+ prev_ptr= &first_order;
+ *simple_order= *join->join_tab[join->const_tables].on_expr_ref ? 0 : 1;
+
+ /* NOTE: A variable of not_const_tables ^ first_table; breaks gcc 2.7 */
+
+ update_depend_map_for_order(join, first_order);
+ for (order=first_order; order ; order=order->next)
+ {
+ table_map order_tables=order->item[0]->used_tables();
+ if (order->item[0]->with_sum_func ||
+ /*
+ If the outer table of an outer join is const (either by itself or
+ after applying WHERE condition), grouping on a field from such a
+ table will be optimized away and filesort without temporary table
+ will be used unless we prevent that now. Filesort is not fit to
+ handle joins and the join condition is not applied. We can't detect
+ the case without an expensive test, however, so we force temporary
+ table for all queries containing more than one table, ROLLUP, and an
+ outer join.
+ */
+ (join->table_count > 1 && join->rollup.state == ROLLUP::STATE_INITED &&
+ join->outer_join))
+ *simple_order=0; // Must do a temp table to sort
+ else if (!(order_tables & not_const_tables))
+ {
+ if (order->item[0]->has_subquery())
+ {
+ /*
+ Delay the evaluation of constant ORDER and/or GROUP expressions that
+ contain subqueries until the execution phase.
+ */
+ join->exec_const_order_group_cond.push_back(order->item[0]);
+ }
+ DBUG_PRINT("info",("removing: %s", order->item[0]->full_name()));
+ continue;
+ }
+ else
+ {
+ if (order_tables & (RAND_TABLE_BIT | OUTER_REF_TABLE_BIT))
+ *simple_order=0;
+ else
+ {
+ if (cond && const_expression_in_where(cond,order->item[0]))
+ {
+ DBUG_PRINT("info",("removing: %s", order->item[0]->full_name()));
+ continue;
+ }
+ if (first_is_base_table && (ref=order_tables & (not_const_tables ^ first_table)))
+ {
+ if (!(order_tables & first_table) &&
+ only_eq_ref_tables(join,first_order, ref))
+ {
+ DBUG_PRINT("info",("removing: %s", order->item[0]->full_name()));
+ continue;
+ }
+ *simple_order=0; // Must do a temp table to sort
+ }
+ }
+ }
+ /* Remove ORDER BY entries that we have seen before */
+ for (tmp_order= first_order;
+ tmp_order != order;
+ tmp_order= tmp_order->next)
+ {
+ if (tmp_order->item[0]->eq(order->item[0],1))
+ break;
+ }
+ if (tmp_order != order)
+ continue; // Duplicate order by. Remove
+
+ if (change_list)
+ *prev_ptr= order; // use this entry
+ prev_ptr= &order->next;
+ }
+ if (change_list)
+ *prev_ptr=0;
+ if (prev_ptr == &first_order) // Nothing to sort/group
+ *simple_order=1;
+ DBUG_PRINT("exit",("simple_order: %d",(int) *simple_order));
+ DBUG_RETURN(first_order);
+}
+
+
+/**
+ Filter out ORDER items those are equal to constants in WHERE
+
+ This function is a limited version of remove_const() for use
+ with non-JOIN statements (i.e. single-table UPDATE and DELETE).
+
+
+ @param order Linked list of ORDER BY arguments
+ @param cond WHERE expression
+
+ @return pointer to new filtered ORDER list or NULL if whole list eliminated
+
+ @note
+ This function overwrites input order list.
+*/
+
+ORDER *simple_remove_const(ORDER *order, COND *where)
+{
+ if (!order || !where)
+ return order;
+
+ ORDER *first= NULL, *prev= NULL;
+ for (; order; order= order->next)
+ {
+ DBUG_ASSERT(!order->item[0]->with_sum_func); // should never happen
+ if (!const_expression_in_where(where, order->item[0]))
+ {
+ if (!first)
+ first= order;
+ if (prev)
+ prev->next= order;
+ prev= order;
+ }
+ }
+ if (prev)
+ prev->next= NULL;
+ return first;
+}
+
+
+static int
+return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
+ List<Item> &fields, bool send_row, ulonglong select_options,
+ const char *info, Item *having, List<Item> &all_fields)
+{
+ DBUG_ENTER("return_zero_rows");
+
+ if (select_options & SELECT_DESCRIBE)
+ {
+ select_describe(join, FALSE, FALSE, FALSE, info);
+ DBUG_RETURN(0);
+ }
+
+ join->join_free();
+
+ if (send_row)
+ {
+ /*
+ Set all tables to have NULL row. This is needed as we will be evaluating
+ HAVING condition.
+ */
+ List_iterator<TABLE_LIST> ti(tables);
+ TABLE_LIST *table;
+ while ((table= ti++))
+ {
+ /*
+ Don't touch semi-join materialization tables, as the above join_free()
+ call has freed them (and HAVING clause can't have references to them
+ anyway).
+ */
+ if (!table->is_jtbm())
+ mark_as_null_row(table->table); // All fields are NULL
+ }
+ List_iterator_fast<Item> it(all_fields);
+ Item *item;
+ /*
+ Inform all items (especially aggregating) to calculate HAVING correctly,
+ also we will need it for sending results.
+ */
+ while ((item= it++))
+ item->no_rows_in_result();
+ if (having && having->val_int() == 0)
+ send_row=0;
+ }
+ if (!(result->send_result_set_metadata(fields,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)))
+ {
+ bool send_error= FALSE;
+ if (send_row)
+ send_error= result->send_data(fields) > 0;
+ if (!send_error)
+ result->send_eof(); // Should be safe
+ }
+ /* Update results for FOUND_ROWS */
+ join->thd->limit_found_rows= 0;
+ join->thd->set_examined_row_count(0);
+ DBUG_RETURN(0);
+}
+
+/*
+ used only in JOIN::clear
+*/
+static void clear_tables(JOIN *join)
+{
+ /*
+ must clear only the non-const tables, as const tables
+ are not re-calculated.
+ */
+ for (uint i= 0 ; i < join->table_count ; i++)
+ {
+ if (!(join->table[i]->map & join->const_table_map))
+ mark_as_null_row(join->table[i]); // All fields are NULL
+ }
+}
+
+/*****************************************************************************
+ Make som simple condition optimization:
+ If there is a test 'field = const' change all refs to 'field' to 'const'
+ Remove all dummy tests 'item = item', 'const op const'.
+ Remove all 'item is NULL', when item can never be null!
+ item->marker should be 0 for all items on entry
+ Return in cond_value FALSE if condition is impossible (1 = 2)
+*****************************************************************************/
+
+class COND_CMP :public ilink {
+public:
+ static void *operator new(size_t size)
+ {
+ return (void*) sql_alloc((uint) size);
+ }
+ static void operator delete(void *ptr __attribute__((unused)),
+ size_t size __attribute__((unused)))
+ { TRASH(ptr, size); }
+
+ Item *and_level;
+ Item_bool_func2 *cmp_func;
+ COND_CMP(Item *a,Item_bool_func2 *b) :and_level(a),cmp_func(b) {}
+};
+
+/**
+ Find the multiple equality predicate containing a field.
+
+ The function retrieves the multiple equalities accessed through
+ the con_equal structure from current level and up looking for
+ an equality containing field. It stops retrieval as soon as the equality
+ is found and set up inherited_fl to TRUE if it's found on upper levels.
+
+ @param cond_equal multiple equalities to search in
+ @param field field to look for
+ @param[out] inherited_fl set up to TRUE if multiple equality is found
+ on upper levels (not on current level of
+ cond_equal)
+
+ @return
+ - Item_equal for the found multiple equality predicate if a success;
+ - NULL otherwise.
+*/
+
+Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field,
+ bool *inherited_fl)
+{
+ Item_equal *item= 0;
+ bool in_upper_level= FALSE;
+ while (cond_equal)
+ {
+ List_iterator_fast<Item_equal> li(cond_equal->current_level);
+ while ((item= li++))
+ {
+ if (item->contains(field))
+ goto finish;
+ }
+ in_upper_level= TRUE;
+ cond_equal= cond_equal->upper_levels;
+ }
+ in_upper_level= FALSE;
+finish:
+ *inherited_fl= in_upper_level;
+ return item;
+}
+
+
+/**
+ Check whether an equality can be used to build multiple equalities.
+
+ This function first checks whether the equality (left_item=right_item)
+ is a simple equality i.e. the one that equates a field with another field
+ or a constant (field=field_item or field=const_item).
+ If this is the case the function looks for a multiple equality
+ in the lists referenced directly or indirectly by cond_equal inferring
+ the given simple equality. If it doesn't find any, it builds a multiple
+ equality that covers the predicate, i.e. the predicate can be inferred
+ from this multiple equality.
+ The built multiple equality could be obtained in such a way:
+ create a binary multiple equality equivalent to the predicate, then
+ merge it, if possible, with one of old multiple equalities.
+ This guarantees that the set of multiple equalities covering equality
+ predicates will be minimal.
+
+ EXAMPLE:
+ For the where condition
+ @code
+ WHERE a=b AND b=c AND
+ (b=2 OR f=e)
+ @endcode
+ the check_equality will be called for the following equality
+ predicates a=b, b=c, b=2 and f=e.
+ - For a=b it will be called with *cond_equal=(0,[]) and will transform
+ *cond_equal into (0,[Item_equal(a,b)]).
+ - For b=c it will be called with *cond_equal=(0,[Item_equal(a,b)])
+ and will transform *cond_equal into CE=(0,[Item_equal(a,b,c)]).
+ - For b=2 it will be called with *cond_equal=(ptr(CE),[])
+ and will transform *cond_equal into (ptr(CE),[Item_equal(2,a,b,c)]).
+ - For f=e it will be called with *cond_equal=(ptr(CE), [])
+ and will transform *cond_equal into (ptr(CE),[Item_equal(f,e)]).
+
+ @note
+ Now only fields that have the same type definitions (verified by
+ the Field::eq_def method) are placed to the same multiple equalities.
+ Because of this some equality predicates are not eliminated and
+ can be used in the constant propagation procedure.
+ We could weeken the equlity test as soon as at least one of the
+ equal fields is to be equal to a constant. It would require a
+ more complicated implementation: we would have to store, in
+ general case, its own constant for each fields from the multiple
+ equality. But at the same time it would allow us to get rid
+ of constant propagation completely: it would be done by the call
+ to build_equal_items_for_cond.
+
+
+ The implementation does not follow exactly the above rules to
+ build a new multiple equality for the equality predicate.
+ If it processes the equality of the form field1=field2, it
+ looks for multiple equalities me1 containig field1 and me2 containing
+ field2. If only one of them is found the fuction expands it with
+ the lacking field. If multiple equalities for both fields are
+ found they are merged. If both searches fail a new multiple equality
+ containing just field1 and field2 is added to the existing
+ multiple equalities.
+ If the function processes the predicate of the form field1=const,
+ it looks for a multiple equality containing field1. If found, the
+ function checks the constant of the multiple equality. If the value
+ is unknown, it is setup to const. Otherwise the value is compared with
+ const and the evaluation of the equality predicate is performed.
+ When expanding/merging equality predicates from the upper levels
+ the function first copies them for the current level. It looks
+ acceptable, as this happens rarely. The implementation without
+ copying would be much more complicated.
+
+ For description of how equality propagation works with SJM nests, grep
+ for EqualityPropagationAndSjmNests.
+
+ @param left_item left term of the quality to be checked
+ @param right_item right term of the equality to be checked
+ @param item equality item if the equality originates from a condition
+ predicate, 0 if the equality is the result of row
+ elimination
+ @param cond_equal multiple equalities that must hold together with the
+ equality
+
+ @retval
+ TRUE if the predicate is a simple equality predicate to be used
+ for building multiple equalities
+ @retval
+ FALSE otherwise
+*/
+
+static bool check_simple_equality(Item *left_item, Item *right_item,
+ Item *item, COND_EQUAL *cond_equal)
+{
+ Item *orig_left_item= left_item;
+ Item *orig_right_item= right_item;
+ if (left_item->type() == Item::REF_ITEM &&
+ ((Item_ref*)left_item)->ref_type() == Item_ref::VIEW_REF)
+ {
+ if (((Item_ref*)left_item)->get_depended_from())
+ return FALSE;
+ left_item= left_item->real_item();
+ }
+ if (right_item->type() == Item::REF_ITEM &&
+ ((Item_ref*)right_item)->ref_type() == Item_ref::VIEW_REF)
+ {
+ if (((Item_ref*)right_item)->get_depended_from())
+ return FALSE;
+ right_item= right_item->real_item();
+ }
+ if (left_item->type() == Item::FIELD_ITEM &&
+ right_item->type() == Item::FIELD_ITEM &&
+ !((Item_field*)left_item)->get_depended_from() &&
+ !((Item_field*)right_item)->get_depended_from())
+ {
+ /* The predicate the form field1=field2 is processed */
+
+ Field *left_field= ((Item_field*) left_item)->field;
+ Field *right_field= ((Item_field*) right_item)->field;
+
+ if (!left_field->eq_def(right_field))
+ return FALSE;
+
+ /* Search for multiple equalities containing field1 and/or field2 */
+ bool left_copyfl, right_copyfl;
+ Item_equal *left_item_equal=
+ find_item_equal(cond_equal, left_field, &left_copyfl);
+ Item_equal *right_item_equal=
+ find_item_equal(cond_equal, right_field, &right_copyfl);
+
+ /* As (NULL=NULL) != TRUE we can't just remove the predicate f=f */
+ if (left_field->eq(right_field)) /* f = f */
+ return (!(left_field->maybe_null() && !left_item_equal));
+
+ if (left_item_equal && left_item_equal == right_item_equal)
+ {
+ /*
+ The equality predicate is inference of one of the existing
+ multiple equalities, i.e the condition is already covered
+ by upper level equalities
+ */
+ return TRUE;
+ }
+
+ /* Copy the found multiple equalities at the current level if needed */
+ if (left_copyfl)
+ {
+ /* left_item_equal of an upper level contains left_item */
+ left_item_equal= new Item_equal(left_item_equal);
+ left_item_equal->set_context_field(((Item_field*) left_item));
+ cond_equal->current_level.push_back(left_item_equal);
+ }
+ if (right_copyfl)
+ {
+ /* right_item_equal of an upper level contains right_item */
+ right_item_equal= new Item_equal(right_item_equal);
+ right_item_equal->set_context_field(((Item_field*) right_item));
+ cond_equal->current_level.push_back(right_item_equal);
+ }
+
+ if (left_item_equal)
+ {
+ /* left item was found in the current or one of the upper levels */
+ if (! right_item_equal)
+ left_item_equal->add(orig_right_item);
+ else
+ {
+ /* Merge two multiple equalities forming a new one */
+ left_item_equal->merge(right_item_equal);
+ /* Remove the merged multiple equality from the list */
+ List_iterator<Item_equal> li(cond_equal->current_level);
+ while ((li++) != right_item_equal) ;
+ li.remove();
+ }
+ }
+ else
+ {
+ /* left item was not found neither the current nor in upper levels */
+ if (right_item_equal)
+ right_item_equal->add(orig_left_item);
+ else
+ {
+ /* None of the fields was found in multiple equalities */
+ Item_equal *item_equal= new Item_equal(orig_left_item,
+ orig_right_item,
+ FALSE);
+ item_equal->set_context_field((Item_field*)left_item);
+ cond_equal->current_level.push_back(item_equal);
+ }
+ }
+ return TRUE;
+ }
+
+ {
+ /* The predicate of the form field=const/const=field is processed */
+ Item *const_item= 0;
+ Item_field *field_item= 0;
+ Item *orig_field_item= 0;
+ if (left_item->type() == Item::FIELD_ITEM &&
+ !((Item_field*)left_item)->get_depended_from() &&
+ right_item->const_item() && !right_item->is_expensive())
+ {
+ orig_field_item= orig_left_item;
+ field_item= (Item_field *) left_item;
+ const_item= right_item;
+ }
+ else if (right_item->type() == Item::FIELD_ITEM &&
+ !((Item_field*)right_item)->get_depended_from() &&
+ left_item->const_item() && !left_item->is_expensive())
+ {
+ orig_field_item= orig_right_item;
+ field_item= (Item_field *) right_item;
+ const_item= left_item;
+ }
+
+ if (const_item &&
+ field_item->result_type() == const_item->result_type())
+ {
+ bool copyfl;
+
+ if (field_item->cmp_type() == STRING_RESULT)
+ {
+ CHARSET_INFO *cs= field_item->field->charset();
+ if (!item)
+ {
+ Item_func_eq *eq_item;
+ if (!(eq_item= new Item_func_eq(orig_left_item, orig_right_item)) ||
+ eq_item->set_cmp_func())
+ return FALSE;
+ eq_item->quick_fix_field();
+ item= eq_item;
+ }
+ if ((cs != ((Item_func *) item)->compare_collation()) ||
+ !cs->coll->propagate(cs, 0, 0))
+ return FALSE;
+ }
+
+ Item_equal *item_equal = find_item_equal(cond_equal,
+ field_item->field, &copyfl);
+ if (copyfl)
+ {
+ item_equal= new Item_equal(item_equal);
+ cond_equal->current_level.push_back(item_equal);
+ item_equal->set_context_field(field_item);
+ }
+ if (item_equal)
+ {
+ /*
+ The flag cond_false will be set to 1 after this, if item_equal
+ already contains a constant and its value is not equal to
+ the value of const_item.
+ */
+ item_equal->add_const(const_item, orig_field_item);
+ }
+ else
+ {
+ item_equal= new Item_equal(const_item, orig_field_item, TRUE);
+ item_equal->set_context_field(field_item);
+ cond_equal->current_level.push_back(item_equal);
+ }
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ Convert row equalities into a conjunction of regular equalities.
+
+ The function converts a row equality of the form (E1,...,En)=(E'1,...,E'n)
+ into a list of equalities E1=E'1,...,En=E'n. For each of these equalities
+ Ei=E'i the function checks whether it is a simple equality or a row
+ equality. If it is a simple equality it is used to expand multiple
+ equalities of cond_equal. If it is a row equality it converted to a
+ sequence of equalities between row elements. If Ei=E'i is neither a
+ simple equality nor a row equality the item for this predicate is added
+ to eq_list.
+
+ @param thd thread handle
+ @param left_row left term of the row equality to be processed
+ @param right_row right term of the row equality to be processed
+ @param cond_equal multiple equalities that must hold together with the
+ predicate
+ @param eq_list results of conversions of row equalities that are not
+ simple enough to form multiple equalities
+
+ @retval
+ TRUE if conversion has succeeded (no fatal error)
+ @retval
+ FALSE otherwise
+*/
+
+static bool check_row_equality(THD *thd, Item *left_row, Item_row *right_row,
+ COND_EQUAL *cond_equal, List<Item>* eq_list)
+{
+ uint n= left_row->cols();
+ for (uint i= 0 ; i < n; i++)
+ {
+ bool is_converted;
+ Item *left_item= left_row->element_index(i);
+ Item *right_item= right_row->element_index(i);
+ if (left_item->type() == Item::ROW_ITEM &&
+ right_item->type() == Item::ROW_ITEM)
+ {
+ is_converted= check_row_equality(thd,
+ (Item_row *) left_item,
+ (Item_row *) right_item,
+ cond_equal, eq_list);
+ }
+ else
+ {
+ is_converted= check_simple_equality(left_item, right_item, 0, cond_equal);
+ }
+
+ if (!is_converted)
+ {
+ Item_func_eq *eq_item;
+ if (!(eq_item= new Item_func_eq(left_item, right_item)) ||
+ eq_item->set_cmp_func())
+ return FALSE;
+ eq_item->quick_fix_field();
+ eq_list->push_back(eq_item);
+ }
+ }
+ return TRUE;
+}
+
+
+/**
+ Eliminate row equalities and form multiple equalities predicates.
+
+ This function checks whether the item is a simple equality
+ i.e. the one that equates a field with another field or a constant
+ (field=field_item or field=constant_item), or, a row equality.
+ For a simple equality the function looks for a multiple equality
+ in the lists referenced directly or indirectly by cond_equal inferring
+ the given simple equality. If it doesn't find any, it builds/expands
+ multiple equality that covers the predicate.
+ Row equalities are eliminated substituted for conjunctive regular
+ equalities which are treated in the same way as original equality
+ predicates.
+
+ @param thd thread handle
+ @param item predicate to process
+ @param cond_equal multiple equalities that must hold together with the
+ predicate
+ @param eq_list results of conversions of row equalities that are not
+ simple enough to form multiple equalities
+
+ @retval
+ TRUE if re-writing rules have been applied
+ @retval
+ FALSE otherwise, i.e.
+ if the predicate is not an equality,
+ or, if the equality is neither a simple one nor a row equality,
+ or, if the procedure fails by a fatal error.
+*/
+
+static bool check_equality(THD *thd, Item *item, COND_EQUAL *cond_equal,
+ List<Item> *eq_list)
+{
+ if (item->type() == Item::FUNC_ITEM &&
+ ((Item_func*) item)->functype() == Item_func::EQ_FUNC)
+ {
+ Item *left_item= ((Item_func*) item)->arguments()[0];
+ Item *right_item= ((Item_func*) item)->arguments()[1];
+
+ if (left_item->type() == Item::ROW_ITEM &&
+ right_item->type() == Item::ROW_ITEM)
+ {
+ return check_row_equality(thd,
+ (Item_row *) left_item,
+ (Item_row *) right_item,
+ cond_equal, eq_list);
+ }
+ else
+ return check_simple_equality(left_item, right_item, item, cond_equal);
+ }
+ return FALSE;
+}
+
+
+/**
+ Replace all equality predicates in a condition by multiple equality items.
+
+ At each 'and' level the function detects items for equality predicates
+ and replaced them by a set of multiple equality items of class Item_equal,
+ taking into account inherited equalities from upper levels.
+ If an equality predicate is used not in a conjunction it's just
+ replaced by a multiple equality predicate.
+ For each 'and' level the function set a pointer to the inherited
+ multiple equalities in the cond_equal field of the associated
+ object of the type Item_cond_and.
+ The function also traverses the cond tree and and for each field reference
+ sets a pointer to the multiple equality item containing the field, if there
+ is any. If this multiple equality equates fields to a constant the
+ function replaces the field reference by the constant in the cases
+ when the field is not of a string type or when the field reference is
+ just an argument of a comparison predicate.
+ The function also determines the maximum number of members in
+ equality lists of each Item_cond_and object assigning it to
+ thd->lex->current_select->max_equal_elems.
+
+ @note
+ Multiple equality predicate =(f1,..fn) is equivalent to the conjuction of
+ f1=f2, .., fn-1=fn. It substitutes any inference from these
+ equality predicates that is equivalent to the conjunction.
+ Thus, =(a1,a2,a3) can substitute for ((a1=a3) AND (a2=a3) AND (a2=a1)) as
+ it is equivalent to ((a1=a2) AND (a2=a3)).
+ The function always makes a substitution of all equality predicates occured
+ in a conjuction for a minimal set of multiple equality predicates.
+ This set can be considered as a canonical representation of the
+ sub-conjunction of the equality predicates.
+ E.g. (t1.a=t2.b AND t2.b>5 AND t1.a=t3.c) is replaced by
+ (=(t1.a,t2.b,t3.c) AND t2.b>5), not by
+ (=(t1.a,t2.b) AND =(t1.a,t3.c) AND t2.b>5);
+ while (t1.a=t2.b AND t2.b>5 AND t3.c=t4.d) is replaced by
+ (=(t1.a,t2.b) AND =(t3.c=t4.d) AND t2.b>5),
+ but if additionally =(t4.d,t2.b) is inherited, it
+ will be replaced by (=(t1.a,t2.b,t3.c,t4.d) AND t2.b>5)
+
+ The function performs the substitution in a recursive descent by
+ the condtion tree, passing to the next AND level a chain of multiple
+ equality predicates which have been built at the upper levels.
+ The Item_equal items built at the level are attached to other
+ non-equality conjucts as a sublist. The pointer to the inherited
+ multiple equalities is saved in the and condition object (Item_cond_and).
+ This chain allows us for any field reference occurence easyly to find a
+ multiple equality that must be held for this occurence.
+ For each AND level we do the following:
+ - scan it for all equality predicate (=) items
+ - join them into disjoint Item_equal() groups
+ - process the included OR conditions recursively to do the same for
+ lower AND levels.
+
+ We need to do things in this order as lower AND levels need to know about
+ all possible Item_equal objects in upper levels.
+
+ @param thd thread handle
+ @param cond condition(expression) where to make replacement
+ @param inherited path to all inherited multiple equality items
+
+ @return
+ pointer to the transformed condition
+*/
+
+static COND *build_equal_items_for_cond(THD *thd, COND *cond,
+ COND_EQUAL *inherited,
+ bool link_item_fields)
+{
+ Item_equal *item_equal;
+ COND_EQUAL cond_equal;
+ cond_equal.upper_levels= inherited;
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ List<Item> eq_list;
+ bool and_level= ((Item_cond*) cond)->functype() ==
+ Item_func::COND_AND_FUNC;
+ List<Item> *args= ((Item_cond*) cond)->argument_list();
+
+ List_iterator<Item> li(*args);
+ Item *item;
+
+ if (and_level)
+ {
+ /*
+ Retrieve all conjuncts of this level detecting the equality
+ that are subject to substitution by multiple equality items and
+ removing each such predicate from the conjunction after having
+ found/created a multiple equality whose inference the predicate is.
+ */
+ while ((item= li++))
+ {
+ /*
+ PS/SP note: we can safely remove a node from AND-OR
+ structure here because it's restored before each
+ re-execution of any prepared statement/stored procedure.
+ */
+ if (check_equality(thd, item, &cond_equal, &eq_list))
+ li.remove();
+ }
+
+ /*
+ Check if we eliminated all the predicates of the level, e.g.
+ (a=a AND b=b AND a=a).
+ */
+ if (!args->elements &&
+ !cond_equal.current_level.elements &&
+ !eq_list.elements)
+ return new Item_int((longlong) 1, 1);
+
+ List_iterator_fast<Item_equal> it(cond_equal.current_level);
+ while ((item_equal= it++))
+ {
+ item_equal->set_link_equal_fields(link_item_fields);
+ item_equal->fix_fields(thd, NULL);
+ item_equal->update_used_tables();
+ set_if_bigger(thd->lex->current_select->max_equal_elems,
+ item_equal->n_field_items());
+ }
+
+ ((Item_cond_and*)cond)->cond_equal.copy(cond_equal);
+ cond_equal.current_level=
+ ((Item_cond_and*)cond)->cond_equal.current_level;
+ inherited= &(((Item_cond_and*)cond)->cond_equal);
+ }
+ /*
+ Make replacement of equality predicates for lower levels
+ of the condition expression.
+ */
+ li.rewind();
+ while ((item= li++))
+ {
+ Item *new_item;
+ if ((new_item= build_equal_items_for_cond(thd, item, inherited, FALSE))
+ != item)
+ {
+ /* This replacement happens only for standalone equalities */
+ /*
+ This is ok with PS/SP as the replacement is done for
+ arguments of an AND/OR item, which are restored for each
+ execution of PS/SP.
+ */
+ li.replace(new_item);
+ }
+ }
+ if (and_level)
+ {
+ args->concat(&eq_list);
+ args->concat((List<Item> *)&cond_equal.current_level);
+ }
+ }
+ else if (cond->type() == Item::FUNC_ITEM ||
+ cond->real_item()->type() == Item::FIELD_ITEM)
+ {
+ List<Item> eq_list;
+ /*
+ If an equality predicate forms the whole and level,
+ we call it standalone equality and it's processed here.
+ E.g. in the following where condition
+ WHERE a=5 AND (b=5 or a=c)
+ (b=5) and (a=c) are standalone equalities.
+ In general we can't leave alone standalone eqalities:
+ for WHERE a=b AND c=d AND (b=c OR d=5)
+ b=c is replaced by =(a,b,c,d).
+ */
+ if (check_equality(thd, cond, &cond_equal, &eq_list))
+ {
+ int n= cond_equal.current_level.elements + eq_list.elements;
+ if (n == 0)
+ return new Item_int((longlong) 1,1);
+ else if (n == 1)
+ {
+ if ((item_equal= cond_equal.current_level.pop()))
+ {
+ item_equal->fix_fields(thd, NULL);
+ item_equal->update_used_tables();
+ set_if_bigger(thd->lex->current_select->max_equal_elems,
+ item_equal->n_field_items());
+ item_equal->upper_levels= inherited;
+ return item_equal;
+ }
+
+ return eq_list.pop();
+ }
+ else
+ {
+ /*
+ Here a new AND level must be created. It can happen only
+ when a row equality is processed as a standalone predicate.
+ */
+ Item_cond_and *and_cond= new Item_cond_and(eq_list);
+ and_cond->quick_fix_field();
+ List<Item> *args= and_cond->argument_list();
+ List_iterator_fast<Item_equal> it(cond_equal.current_level);
+ while ((item_equal= it++))
+ {
+ item_equal->fix_length_and_dec();
+ item_equal->update_used_tables();
+ set_if_bigger(thd->lex->current_select->max_equal_elems,
+ item_equal->n_field_items());
+ }
+ and_cond->cond_equal.copy(cond_equal);
+ cond_equal.current_level= and_cond->cond_equal.current_level;
+ args->concat((List<Item> *)&cond_equal.current_level);
+
+ return and_cond;
+ }
+ }
+ /*
+ For each field reference in cond, not from equal item predicates,
+ set a pointer to the multiple equality it belongs to (if there is any)
+ as soon the field is not of a string type or the field reference is
+ an argument of a comparison predicate.
+ */
+ uchar* is_subst_valid= (uchar *) Item::ANY_SUBST;
+ cond= cond->compile(&Item::subst_argument_checker,
+ &is_subst_valid,
+ &Item::equal_fields_propagator,
+ (uchar *) inherited);
+ cond->update_used_tables();
+ }
+ return cond;
+}
+
+
+/**
+ Build multiple equalities for a condition and all on expressions that
+ inherit these multiple equalities.
+
+ The function first applies the build_equal_items_for_cond function
+ to build all multiple equalities for condition cond utilizing equalities
+ referred through the parameter inherited. The extended set of
+ equalities is returned in the structure referred by the cond_equal_ref
+ parameter. After this the function calls itself recursively for
+ all on expressions whose direct references can be found in join_list
+ and who inherit directly the multiple equalities just having built.
+
+ @note
+ The on expression used in an outer join operation inherits all equalities
+ from the on expression of the embedding join, if there is any, or
+ otherwise - from the where condition.
+ This fact is not obvious, but presumably can be proved.
+ Consider the following query:
+ @code
+ SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t1.a=t3.a AND t2.a=t4.a
+ WHERE t1.a=t2.a;
+ @endcode
+ If the on expression in the query inherits =(t1.a,t2.a), then we
+ can build the multiple equality =(t1.a,t2.a,t3.a,t4.a) that infers
+ the equality t3.a=t4.a. Although the on expression
+ t1.a=t3.a AND t2.a=t4.a AND t3.a=t4.a is not equivalent to the one
+ in the query the latter can be replaced by the former: the new query
+ will return the same result set as the original one.
+
+ Interesting that multiple equality =(t1.a,t2.a,t3.a,t4.a) allows us
+ to use t1.a=t3.a AND t3.a=t4.a under the on condition:
+ @code
+ SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t1.a=t3.a AND t3.a=t4.a
+ WHERE t1.a=t2.a
+ @endcode
+ This query equivalent to:
+ @code
+ SELECT * FROM (t1 LEFT JOIN (t3,t4) ON t1.a=t3.a AND t3.a=t4.a),t2
+ WHERE t1.a=t2.a
+ @endcode
+ Similarly the original query can be rewritten to the query:
+ @code
+ SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t2.a=t4.a AND t3.a=t4.a
+ WHERE t1.a=t2.a
+ @endcode
+ that is equivalent to:
+ @code
+ SELECT * FROM (t2 LEFT JOIN (t3,t4)ON t2.a=t4.a AND t3.a=t4.a), t1
+ WHERE t1.a=t2.a
+ @endcode
+ Thus, applying equalities from the where condition we basically
+ can get more freedom in performing join operations.
+ Although we don't use this property now, it probably makes sense to use
+ it in the future.
+ @param thd Thread handler
+ @param cond condition to build the multiple equalities for
+ @param inherited path to all inherited multiple equality items
+ @param join_list list of join tables to which the condition
+ refers to
+ @ignore_on_conds TRUE <-> do not build multiple equalities
+ for on expressions
+ @param[out] cond_equal_ref pointer to the structure to place built
+ equalities in
+ @param link_equal_items equal fields are to be linked
+
+ @return
+ pointer to the transformed condition containing multiple equalities
+*/
+
+static COND *build_equal_items(JOIN *join, COND *cond,
+ COND_EQUAL *inherited,
+ List<TABLE_LIST> *join_list,
+ bool ignore_on_conds,
+ COND_EQUAL **cond_equal_ref,
+ bool link_equal_fields)
+{
+ THD *thd= join->thd;
+ COND_EQUAL *cond_equal= 0;
+
+ if (cond)
+ {
+ cond= build_equal_items_for_cond(thd, cond, inherited, link_equal_fields);
+ cond->update_used_tables();
+ if (cond->type() == Item::COND_ITEM &&
+ ((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ cond_equal= &((Item_cond_and*) cond)->cond_equal;
+
+ else if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC)
+ {
+ cond_equal= new COND_EQUAL;
+ cond_equal->current_level.push_back((Item_equal *) cond);
+ }
+ }
+ if (cond_equal)
+ {
+ cond_equal->upper_levels= inherited;
+ inherited= cond_equal;
+ }
+ *cond_equal_ref= cond_equal;
+
+ if (join_list && !ignore_on_conds)
+ {
+ TABLE_LIST *table;
+ List_iterator<TABLE_LIST> li(*join_list);
+
+ while ((table= li++))
+ {
+ if (table->on_expr)
+ {
+ List<TABLE_LIST> *nested_join_list= table->nested_join ?
+ &table->nested_join->join_list : NULL;
+ /*
+ We can modify table->on_expr because its old value will
+ be restored before re-execution of PS/SP.
+ */
+ table->on_expr= build_equal_items(join, table->on_expr, inherited,
+ nested_join_list, ignore_on_conds,
+ &table->cond_equal);
+ }
+ }
+ }
+
+ return cond;
+}
+
+
+/**
+ Compare field items by table order in the execution plan.
+
+ If field1 and field2 belong to different tables then
+ field1 considered as better than field2 if the table containing
+ field1 is accessed earlier than the table containing field2.
+ The function finds out what of two fields is better according
+ this criteria.
+ If field1 and field2 belong to the same table then the result
+ of comparison depends on whether the fields are parts of
+ the key that are used to access this table.
+
+ @param field1 first field item to compare
+ @param field2 second field item to compare
+ @param table_join_idx index to tables determining table order
+
+ @retval
+ 1 if field1 is better than field2
+ @retval
+ -1 if field2 is better than field1
+ @retval
+ 0 otherwise
+*/
+
+static int compare_fields_by_table_order(Item *field1,
+ Item *field2,
+ void *table_join_idx)
+{
+ int cmp= 0;
+ bool outer_ref= 0;
+ Item_field *f1= (Item_field *) (field1->real_item());
+ Item_field *f2= (Item_field *) (field2->real_item());
+ if (field1->const_item() || f1->const_item())
+ return 1;
+ if (field2->const_item() || f2->const_item())
+ return -1;
+ if (f2->used_tables() & OUTER_REF_TABLE_BIT)
+ {
+ outer_ref= 1;
+ cmp= -1;
+ }
+ if (f1->used_tables() & OUTER_REF_TABLE_BIT)
+ {
+ outer_ref= 1;
+ cmp++;
+ }
+ if (outer_ref)
+ return cmp;
+ JOIN_TAB **idx= (JOIN_TAB **) table_join_idx;
+
+ JOIN_TAB *tab1= idx[f1->field->table->tablenr];
+ JOIN_TAB *tab2= idx[f2->field->table->tablenr];
+
+ /*
+ if one of the table is inside a merged SJM nest and another one isn't,
+ compare SJM bush roots of the tables.
+ */
+ if (tab1->bush_root_tab != tab2->bush_root_tab)
+ {
+ if (tab1->bush_root_tab)
+ tab1= tab1->bush_root_tab;
+
+ if (tab2->bush_root_tab)
+ tab2= tab2->bush_root_tab;
+ }
+
+ cmp= tab2 - tab1;
+
+ if (!cmp)
+ {
+ JOIN_TAB *tab= idx[f1->field->table->tablenr];
+ uint keyno= MAX_KEY;
+ if (tab->ref.key_parts)
+ keyno= tab->ref.key;
+ else if (tab->select && tab->select->quick)
+ keyno = tab->select->quick->index;
+ if (keyno != MAX_KEY)
+ {
+ if (f2->field->part_of_key.is_set(keyno))
+ cmp= -1;
+ if (f1->field->part_of_key.is_set(keyno))
+ cmp++;
+ if (!cmp)
+ {
+ KEY *key_info= tab->table->key_info + keyno;
+ for (uint i= 0; i < key_info->user_defined_key_parts; i++)
+ {
+ Field *fld= key_info->key_part[i].field;
+ if (fld->eq(f2->field))
+ {
+ cmp= -1;
+ break;
+ }
+ if (fld->eq(f1->field))
+ {
+ cmp= 1;
+ break;
+ }
+ }
+ }
+ }
+ else
+ cmp= f2->field->field_index-f1->field->field_index;
+ }
+ return cmp < 0 ? -1 : (cmp ? 1 : 0);
+}
+
+
+static TABLE_LIST* embedding_sjm(Item *item)
+{
+ Item_field *item_field= (Item_field *) (item->real_item());
+ TABLE_LIST *nest= item_field->field->table->pos_in_table_list->embedding;
+ if (nest && nest->sj_mat_info && nest->sj_mat_info->is_used)
+ return nest;
+ else
+ return NULL;
+}
+
+/**
+ Generate minimal set of simple equalities equivalent to a multiple equality.
+
+ The function retrieves the fields of the multiple equality item
+ item_equal and for each field f:
+ - if item_equal contains const it generates the equality f=const_item;
+ - otherwise, if f is not the first field, generates the equality
+ f=item_equal->get_first().
+ All generated equality are added to the cond conjunction.
+
+ @param cond condition to add the generated equality to
+ @param upper_levels structure to access multiple equality of upper levels
+ @param item_equal multiple equality to generate simple equality from
+
+ @note
+ Before generating an equality function checks that it has not
+ been generated for multiple equalities of the upper levels.
+ E.g. for the following where condition
+ WHERE a=5 AND ((a=b AND b=c) OR c>4)
+ the upper level AND condition will contain =(5,a),
+ while the lower level AND condition will contain =(5,a,b,c).
+ When splitting =(5,a,b,c) into a separate equality predicates
+ we should omit 5=a, as we have it already in the upper level.
+ The following where condition gives us a more complicated case:
+ WHERE t1.a=t2.b AND t3.c=t4.d AND (t2.b=t3.c OR t4.e>5 ...) AND ...
+ Given the tables are accessed in the order t1->t2->t3->t4 for
+ the selected query execution plan the lower level multiple
+ equality =(t1.a,t2.b,t3.c,t4.d) formally should be converted to
+ t1.a=t2.b AND t1.a=t3.c AND t1.a=t4.d. But t1.a=t2.a will be
+ generated for the upper level. Also t3.c=t4.d will be generated there.
+ So only t1.a=t3.c should be left in the lower level.
+ If cond is equal to 0, then not more then one equality is generated
+ and a pointer to it is returned as the result of the function.
+
+ Equality substutution and semi-join materialization nests:
+
+ In case join order looks like this:
+
+ outer_tbl1 outer_tbl2 SJM (inner_tbl1 inner_tbl2) outer_tbl3
+
+ We must not construct equalities like
+
+ outer_tbl1.col = inner_tbl1.col
+
+ because they would get attached to inner_tbl1 and will get evaluated
+ during materialization phase, when we don't have current value of
+ outer_tbl1.col.
+
+ Item_equal::get_first() also takes similar measures for dealing with
+ equality substitution in presense of SJM nests.
+
+ Grep for EqualityPropagationAndSjmNests for a more verbose description.
+
+ @return
+ - The condition with generated simple equalities or
+ a pointer to the simple generated equality, if success.
+ - 0, otherwise.
+*/
+
+Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels,
+ Item_equal *item_equal)
+{
+ List<Item> eq_list;
+ Item_func_eq *eq_item= 0;
+ if (((Item *) item_equal)->const_item() && !item_equal->val_int())
+ return new Item_int((longlong) 0,1);
+ Item *item_const= item_equal->get_const();
+ Item_equal_fields_iterator it(*item_equal);
+ Item *head;
+ TABLE_LIST *current_sjm= NULL;
+ Item *current_sjm_head= NULL;
+
+ DBUG_ASSERT(!cond ||
+ cond->type() == Item::INT_ITEM ||
+ (cond->type() == Item::FUNC_ITEM &&
+ ((Item_func *) cond)->functype() == Item_func::EQ_FUNC) ||
+ (cond->type() == Item::COND_ITEM &&
+ ((Item_func *) cond)->functype() == Item_func::COND_AND_FUNC));
+
+ /*
+ Pick the "head" item: the constant one or the first in the join order
+ (if the first in the join order happends to be inside an SJM nest, that's
+ ok, because this is where the value will be unpacked after
+ materialization).
+ */
+ if (item_const)
+ head= item_const;
+ else
+ {
+ TABLE_LIST *emb_nest;
+ head= item_equal->get_first(NO_PARTICULAR_TAB, NULL);
+ it++;
+ if ((emb_nest= embedding_sjm(head)))
+ {
+ current_sjm= emb_nest;
+ current_sjm_head= head;
+ }
+ }
+
+ Item *field_item;
+ /*
+ For each other item, generate "item=head" equality (except the tables that
+ are within SJ-Materialization nests, for those "head" is defined
+ differently)
+ */
+ while ((field_item= it++))
+ {
+ Item_equal *upper= field_item->find_item_equal(upper_levels);
+ Item *item= field_item;
+ TABLE_LIST *field_sjm= embedding_sjm(field_item);
+ if (!field_sjm)
+ {
+ current_sjm= NULL;
+ current_sjm_head= NULL;
+ }
+
+ /*
+ Check if "field_item=head" equality is already guaranteed to be true
+ on upper AND-levels.
+ */
+ if (upper)
+ {
+ TABLE_LIST *native_sjm= embedding_sjm(item_equal->context_field);
+ Item *upper_const= upper->get_const();
+ if (item_const && upper_const)
+ {
+ /*
+ Upper item also has "field_item=const".
+ Don't produce equality if const is equal to item_const.
+ */
+ Item_func_eq *func= new Item_func_eq(item_const, upper_const);
+ func->set_cmp_func();
+ func->quick_fix_field();
+ if (func->val_int())
+ item= 0;
+ }
+ else
+ {
+ Item_equal_fields_iterator li(*item_equal);
+ while ((item= li++) != field_item)
+ {
+ if (embedding_sjm(item) == field_sjm &&
+ item->find_item_equal(upper_levels) == upper)
+ break;
+ }
+ }
+ if (embedding_sjm(field_item) != native_sjm)
+ item= NULL; /* Don't produce equality */
+ }
+
+ bool produce_equality= MY_TEST(item == field_item);
+ if (!item_const && field_sjm && field_sjm != current_sjm)
+ {
+ /* Entering an SJM nest */
+ current_sjm_head= field_item;
+ if (!field_sjm->sj_mat_info->is_sj_scan)
+ produce_equality= FALSE;
+ }
+
+ if (produce_equality)
+ {
+ if (eq_item && eq_list.push_back(eq_item))
+ return 0;
+
+ /*
+ If we're inside an SJM-nest (current_sjm!=NULL), and the multi-equality
+ doesn't include a constant, we should produce equality with the first
+ of the equal items in this SJM (except for the first element inside the
+ SJM. For that, we produce the equality with the "head" item).
+
+ In other cases, get the "head" item, which is either first of the
+ equals on top level, or the constant.
+ */
+ Item *head_item= (!item_const && current_sjm &&
+ current_sjm_head != field_item) ? current_sjm_head: head;
+ Item *head_real_item= head_item->real_item();
+ if (head_real_item->type() == Item::FIELD_ITEM)
+ head_item= head_real_item;
+
+ eq_item= new Item_func_eq(field_item->real_item(), head_item);
+
+ if (!eq_item || eq_item->set_cmp_func())
+ return 0;
+ eq_item->quick_fix_field();
+ }
+ current_sjm= field_sjm;
+ }
+
+ /*
+ We have produced zero, one, or more pair-wise equalities eq_i. We want to
+ return an expression in form:
+
+ cond AND eq_1 AND eq_2 AND eq_3 AND ...
+
+ 'cond' is a parameter for this function, which may be NULL, an Item_int(1),
+ or an Item_func_eq or an Item_cond_and.
+
+ We want to return a well-formed condition: no nested Item_cond_and objects,
+ or Item_cond_and with a single child:
+ - if 'cond' is an Item_cond_and, we add eq_i as its tail
+ - if 'cond' is Item_int(1), we return eq_i
+ - otherwise, we create our own Item_cond_and and put 'cond' at the front of
+ it.
+ - if we have only one condition to return, we don't create an Item_cond_and
+ */
+
+ if (eq_item && eq_list.push_back(eq_item))
+ return 0;
+ COND *res= 0;
+ switch (eq_list.elements)
+ {
+ case 0:
+ res= cond ? cond : new Item_int((longlong) 1, 1);
+ break;
+ case 1:
+ if (!cond || cond->type() == Item::INT_ITEM)
+ res= eq_item;
+ break;
+ default:
+ break;
+ }
+ if (!res)
+ {
+ if (cond)
+ {
+ if (cond->type() == Item::COND_ITEM)
+ {
+ res= cond;
+ ((Item_cond *) res)->add_at_end(&eq_list);
+ }
+ else if (eq_list.push_front(cond))
+ return 0;
+ }
+ }
+ if (!res)
+ res= new Item_cond_and(eq_list);
+ if (res)
+ {
+ res->quick_fix_field();
+ res->update_used_tables();
+ }
+
+ return res;
+}
+
+
+/**
+ Substitute every field reference in a condition by the best equal field
+ and eliminate all multiple equality predicates.
+
+ The function retrieves the cond condition and for each encountered
+ multiple equality predicate it sorts the field references in it
+ according to the order of tables specified by the table_join_idx
+ parameter. Then it eliminates the multiple equality predicate it
+ replacing it by the conjunction of simple equality predicates
+ equating every field from the multiple equality to the first
+ field in it, or to the constant, if there is any.
+ After this the function retrieves all other conjuncted
+ predicates substitute every field reference by the field reference
+ to the first equal field or equal constant if there are any.
+
+ @param context_tab Join tab that 'cond' will be attached to, or
+ NO_PARTICULAR_TAB. See notes above.
+ @param cond condition to process
+ @param cond_equal multiple equalities to take into consideration
+ @param table_join_idx index to tables determining field preference
+
+ @note
+ At the first glance full sort of fields in multiple equality
+ seems to be an overkill. Yet it's not the case due to possible
+ new fields in multiple equality item of lower levels. We want
+ the order in them to comply with the order of upper levels.
+
+ context_tab may be used to specify which join tab `cond` will be
+ attached to. There are two possible cases:
+
+ 1. context_tab != NO_PARTICULAR_TAB
+ We're doing substitution for an Item which will be evaluated in the
+ context of a particular item. For example, if the optimizer does a
+ ref access on "tbl1.key= expr" then
+ = equality substitution will be perfomed on 'expr'
+ = it is known in advance that 'expr' will be evaluated when
+ table t1 is accessed.
+ Note that in this kind of substution we never have to replace Item_equal
+ objects. For example, for
+
+ t.key= func(col1=col2 AND col2=const)
+
+ we will not build Item_equal or do equality substution (if we decide to,
+ this function will need to be fixed to handle it)
+
+ 2. context_tab == NO_PARTICULAR_TAB
+ We're doing substitution in WHERE/ON condition, which is not yet
+ attached to any particular join_tab. We will use information about the
+ chosen join order to make "optimal" substitions, i.e. those that allow
+ to apply filtering as soon as possible. See eliminate_item_equal() and
+ Item_equal::get_first() for details.
+
+ @return
+ The transformed condition, or NULL in case of error
+*/
+
+static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab,
+ COND *cond,
+ COND_EQUAL *cond_equal,
+ void *table_join_idx)
+{
+ Item_equal *item_equal;
+ COND *org_cond= cond; // Return this in case of fatal error
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ List<Item> *cond_list= ((Item_cond*) cond)->argument_list();
+
+ bool and_level= ((Item_cond*) cond)->functype() ==
+ Item_func::COND_AND_FUNC;
+ if (and_level)
+ {
+ cond_equal= &((Item_cond_and *) cond)->cond_equal;
+ cond_list->disjoin((List<Item> *) &cond_equal->current_level);/* remove Item_equal objects from the AND. */
+
+ List_iterator_fast<Item_equal> it(cond_equal->current_level);
+ while ((item_equal= it++))
+ {
+ item_equal->sort(&compare_fields_by_table_order, table_join_idx);
+ }
+ }
+
+ List_iterator<Item> li(*cond_list);
+ Item *item;
+ while ((item= li++))
+ {
+ Item *new_item= substitute_for_best_equal_field(context_tab,
+ item, cond_equal,
+ table_join_idx);
+ /*
+ This works OK with PS/SP re-execution as changes are made to
+ the arguments of AND/OR items only
+ */
+ if (new_item != item)
+ li.replace(new_item);
+ }
+
+ if (and_level)
+ {
+ COND *eq_cond= 0;
+ List_iterator_fast<Item_equal> it(cond_equal->current_level);
+ bool false_eq_cond= FALSE;
+ while ((item_equal= it++))
+ {
+ eq_cond= eliminate_item_equal(eq_cond, cond_equal->upper_levels,
+ item_equal);
+ if (!eq_cond)
+ {
+ eq_cond= 0;
+ break;
+ }
+ else if (eq_cond->type() == Item::INT_ITEM && !eq_cond->val_bool())
+ {
+ /*
+ This occurs when eliminate_item_equal() founds that cond is
+ always false and substitutes it with Item_int 0.
+ Due to this, value of item_equal will be 0, so just return it.
+ */
+ cond= eq_cond;
+ false_eq_cond= TRUE;
+ break;
+ }
+ }
+ if (eq_cond && !false_eq_cond)
+ {
+ /* Insert the generated equalities before all other conditions */
+ if (eq_cond->type() == Item::COND_ITEM)
+ ((Item_cond *) cond)->add_at_head(
+ ((Item_cond *) eq_cond)->argument_list());
+ else
+ {
+ if (cond_list->is_empty())
+ cond= eq_cond;
+ else
+ {
+ /* Do not add an equality condition if it's always true */
+ if (eq_cond->type() != Item::INT_ITEM &&
+ cond_list->push_front(eq_cond))
+ eq_cond= 0;
+ }
+ }
+ }
+ if (!eq_cond)
+ {
+ /*
+ We are out of memory doing the transformation.
+ This is a fatal error now. However we bail out by returning the
+ original condition that we had before we started the transformation.
+ */
+ cond_list->concat((List<Item> *) &cond_equal->current_level);
+ }
+ }
+ }
+ else if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC)
+ {
+ item_equal= (Item_equal *) cond;
+ item_equal->sort(&compare_fields_by_table_order, table_join_idx);
+ cond_equal= item_equal->upper_levels;
+ if (cond_equal && cond_equal->current_level.head() == item_equal)
+ cond_equal= cond_equal->upper_levels;
+ cond= eliminate_item_equal(0, cond_equal, item_equal);
+ return cond ? cond : org_cond;
+ }
+ else
+ {
+ while (cond_equal)
+ {
+ List_iterator_fast<Item_equal> it(cond_equal->current_level);
+ while((item_equal= it++))
+ {
+ REPLACE_EQUAL_FIELD_ARG arg= {item_equal, context_tab};
+ cond= cond->transform(&Item::replace_equal_field, (uchar *) &arg);
+ }
+ cond_equal= cond_equal->upper_levels;
+ }
+ }
+ return cond;
+}
+
+
+/**
+ Check appearance of new constant items in multiple equalities
+ of a condition after reading a constant table.
+
+ The function retrieves the cond condition and for each encountered
+ multiple equality checks whether new constants have appeared after
+ reading the constant (single row) table tab. If so it adjusts
+ the multiple equality appropriately.
+
+ @param cond condition whose multiple equalities are to be checked
+ @param table constant table that has been read
+ @param const_key mark key parts as constant
+*/
+
+static void update_const_equal_items(COND *cond, JOIN_TAB *tab, bool const_key)
+{
+ if (!(cond->used_tables() & tab->table->map))
+ return;
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ List<Item> *cond_list= ((Item_cond*) cond)->argument_list();
+ List_iterator_fast<Item> li(*cond_list);
+ Item *item;
+ while ((item= li++))
+ update_const_equal_items(item, tab,
+ (((Item_cond*) cond)->top_level() &&
+ ((Item_cond*) cond)->functype() ==
+ Item_func::COND_AND_FUNC));
+ }
+ else if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC)
+ {
+ Item_equal *item_equal= (Item_equal *) cond;
+ bool contained_const= item_equal->get_const() != NULL;
+ item_equal->update_const();
+ if (!contained_const && item_equal->get_const())
+ {
+ /* Update keys for range analysis */
+ Item_equal_fields_iterator it(*item_equal);
+ while (it++)
+ {
+ Field *field= it.get_curr_field();
+ JOIN_TAB *stat= field->table->reginfo.join_tab;
+ key_map possible_keys= field->key_start;
+ possible_keys.intersect(field->table->keys_in_use_for_query);
+ stat[0].const_keys.merge(possible_keys);
+
+ /*
+ For each field in the multiple equality (for which we know that it
+ is a constant) we have to find its corresponding key part, and set
+ that key part in const_key_parts.
+ */
+ if (!possible_keys.is_clear_all())
+ {
+ TABLE *tab= field->table;
+ KEYUSE *use;
+ for (use= stat->keyuse; use && use->table == tab; use++)
+ if (const_key &&
+ !use->is_for_hash_join() && possible_keys.is_set(use->key) &&
+ tab->key_info[use->key].key_part[use->keypart].field ==
+ field)
+ tab->const_key_parts[use->key]|= use->keypart_map;
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ Check if
+ WHERE expr=value AND expr=const
+ can be rewritten as:
+ WHERE const=value AND expr=const
+
+ @param target - the target operator whose "expr" argument will be
+ replaced to "const".
+ @param target_expr - the target's "expr" which will be replaced to "const".
+ @param target_value - the target's second argument, it will remain unchanged.
+ @param source - the equality expression ("=" or "<=>") that
+ can be used to rewrite the "target" part
+ (under certain conditions, see the code).
+ @param source_expr - the source's "expr". It should be exactly equal to
+ the target's "expr" to make condition rewrite possible.
+ @param source_const - the source's "const" argument, it will be inserted
+ into "target" instead of "expr".
+*/
+static bool
+can_change_cond_ref_to_const(Item_bool_func2 *target,
+ Item *target_expr, Item *target_value,
+ Item_bool_func2 *source,
+ Item *source_expr, Item *source_const)
+{
+ if (!target_expr->eq(source_expr,0) ||
+ target_value == source_const ||
+ target_expr->cmp_context != source_expr->cmp_context)
+ return false;
+ if (target_expr->cmp_context == STRING_RESULT)
+ {
+ /*
+ In this example:
+ SET NAMES utf8 COLLATE utf8_german2_ci;
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1 (a CHAR(10) CHARACTER SET utf8);
+ INSERT INTO t1 VALUES ('o-umlaut'),('oe');
+ SELECT * FROM t1 WHERE a='oe' COLLATE utf8_german2_ci AND a='oe';
+
+ the query should return only the row with 'oe'.
+ It should not return 'o-umlaut', because 'o-umlaut' does not match
+ the right part of the condition: a='oe'
+ ('o-umlaut' is not equal to 'oe' in utf8_general_ci,
+ which is the collation of the field "a").
+
+ If we change the right part from:
+ ... AND a='oe'
+ to
+ ... AND 'oe' COLLATE utf8_german2_ci='oe'
+ it will be evalulated to TRUE and removed from the condition,
+ so the overall query will be simplified to:
+
+ SELECT * FROM t1 WHERE a='oe' COLLATE utf8_german2_ci;
+
+ which will erroneously start to return both 'oe' and 'o-umlaut'.
+ So changing "expr" to "const" is not possible if the effective
+ collations of "target" and "source" are not exactly the same.
+
+ Note, the code before the fix for MDEV-7152 only checked that
+ collations of "source_const" and "target_value" are the same.
+ This was not enough, as the bug report demonstrated.
+ */
+ return
+ target->compare_collation() == source->compare_collation() &&
+ target_value->collation.collation == source_const->collation.collation;
+ }
+ return true; // Non-string comparison
+}
+
+
+/*
+ change field = field to field = const for each found field = const in the
+ and_level
+*/
+
+static void
+change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list,
+ Item *and_father, Item *cond,
+ Item_bool_func2 *field_value_owner,
+ Item *field, Item *value)
+{
+ if (cond->type() == Item::COND_ITEM)
+ {
+ bool and_level= ((Item_cond*) cond)->functype() ==
+ Item_func::COND_AND_FUNC;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ change_cond_ref_to_const(thd, save_list,and_level ? cond : item, item,
+ field_value_owner, field, value);
+ return;
+ }
+ if (cond->eq_cmp_result() == Item::COND_OK)
+ return; // Not a boolean function
+
+ Item_bool_func2 *func= (Item_bool_func2*) cond;
+ Item **args= func->arguments();
+ Item *left_item= args[0];
+ Item *right_item= args[1];
+ Item_func::Functype functype= func->functype();
+
+ if (can_change_cond_ref_to_const(func, right_item, left_item,
+ field_value_owner, field, value))
+ {
+ Item *tmp=value->clone_item();
+ if (tmp)
+ {
+ tmp->collation.set(right_item->collation);
+ thd->change_item_tree(args + 1, tmp);
+ func->update_used_tables();
+ if ((functype == Item_func::EQ_FUNC || functype == Item_func::EQUAL_FUNC)
+ && and_father != cond && !left_item->const_item())
+ {
+ cond->marker=1;
+ COND_CMP *tmp2;
+ if ((tmp2=new COND_CMP(and_father,func)))
+ save_list->push_back(tmp2);
+ }
+ func->set_cmp_func();
+ }
+ }
+ else if (can_change_cond_ref_to_const(func, left_item, right_item,
+ field_value_owner, field, value))
+ {
+ Item *tmp= value->clone_item();
+ if (tmp)
+ {
+ tmp->collation.set(left_item->collation);
+ thd->change_item_tree(args, tmp);
+ value= tmp;
+ func->update_used_tables();
+ if ((functype == Item_func::EQ_FUNC || functype == Item_func::EQUAL_FUNC)
+ && and_father != cond && !right_item->const_item())
+ {
+ args[0]= args[1]; // For easy check
+ thd->change_item_tree(args + 1, value);
+ cond->marker=1;
+ COND_CMP *tmp2;
+ if ((tmp2=new COND_CMP(and_father,func)))
+ save_list->push_back(tmp2);
+ }
+ func->set_cmp_func();
+ }
+ }
+}
+
+
+static void
+propagate_cond_constants(THD *thd, I_List<COND_CMP> *save_list,
+ COND *and_father, COND *cond)
+{
+ if (cond->type() == Item::COND_ITEM)
+ {
+ bool and_level= ((Item_cond*) cond)->functype() ==
+ Item_func::COND_AND_FUNC;
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ I_List<COND_CMP> save;
+ while ((item=li++))
+ {
+ propagate_cond_constants(thd, &save,and_level ? cond : item, item);
+ }
+ if (and_level)
+ { // Handle other found items
+ I_List_iterator<COND_CMP> cond_itr(save);
+ COND_CMP *cond_cmp;
+ while ((cond_cmp=cond_itr++))
+ {
+ Item **args= cond_cmp->cmp_func->arguments();
+ if (!args[0]->const_item())
+ change_cond_ref_to_const(thd, &save,cond_cmp->and_level,
+ cond_cmp->and_level,
+ cond_cmp->cmp_func, args[0], args[1]);
+ }
+ }
+ }
+ else if (and_father != cond && !cond->marker) // In a AND group
+ {
+ if (cond->type() == Item::FUNC_ITEM &&
+ (((Item_func*) cond)->functype() == Item_func::EQ_FUNC ||
+ ((Item_func*) cond)->functype() == Item_func::EQUAL_FUNC))
+ {
+ Item_func_eq *func=(Item_func_eq*) cond;
+ Item **args= func->arguments();
+ bool left_const= args[0]->const_item() && !args[0]->is_expensive();
+ bool right_const= args[1]->const_item() && !args[1]->is_expensive();
+ if (!(left_const && right_const) &&
+ args[0]->cmp_type() == args[1]->cmp_type())
+ {
+ if (right_const)
+ {
+ resolve_const_item(thd, &args[1], args[0]);
+ func->update_used_tables();
+ change_cond_ref_to_const(thd, save_list, and_father, and_father,
+ func, args[0], args[1]);
+ }
+ else if (left_const)
+ {
+ resolve_const_item(thd, &args[0], args[1]);
+ func->update_used_tables();
+ change_cond_ref_to_const(thd, save_list, and_father, and_father,
+ func, args[1], args[0]);
+ }
+ }
+ }
+ }
+}
+
+/**
+ Simplify joins replacing outer joins by inner joins whenever it's
+ possible.
+
+ The function, during a retrieval of join_list, eliminates those
+ outer joins that can be converted into inner join, possibly nested.
+ It also moves the on expressions for the converted outer joins
+ and from inner joins to conds.
+ The function also calculates some attributes for nested joins:
+ - used_tables
+ - not_null_tables
+ - dep_tables.
+ - on_expr_dep_tables
+ The first two attributes are used to test whether an outer join can
+ be substituted for an inner join. The third attribute represents the
+ relation 'to be dependent on' for tables. If table t2 is dependent
+ on table t1, then in any evaluated execution plan table access to
+ table t2 must precede access to table t2. This relation is used also
+ to check whether the query contains invalid cross-references.
+ The forth attribute is an auxiliary one and is used to calculate
+ dep_tables.
+ As the attribute dep_tables qualifies possibles orders of tables in the
+ execution plan, the dependencies required by the straight join
+ modifiers are reflected in this attribute as well.
+ The function also removes all braces that can be removed from the join
+ expression without changing its meaning.
+
+ @note
+ An outer join can be replaced by an inner join if the where condition
+ or the on expression for an embedding nested join contains a conjunctive
+ predicate rejecting null values for some attribute of the inner tables.
+
+ E.g. in the query:
+ @code
+ SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a WHERE t2.b < 5
+ @endcode
+ the predicate t2.b < 5 rejects nulls.
+ The query is converted first to:
+ @code
+ SELECT * FROM t1 INNER JOIN t2 ON t2.a=t1.a WHERE t2.b < 5
+ @endcode
+ then to the equivalent form:
+ @code
+ SELECT * FROM t1, t2 ON t2.a=t1.a WHERE t2.b < 5 AND t2.a=t1.a
+ @endcode
+
+
+ Similarly the following query:
+ @code
+ SELECT * from t1 LEFT JOIN (t2, t3) ON t2.a=t1.a t3.b=t1.b
+ WHERE t2.c < 5
+ @endcode
+ is converted to:
+ @code
+ SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a t3.b=t1.b
+
+ @endcode
+
+ One conversion might trigger another:
+ @code
+ SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a
+ LEFT JOIN t3 ON t3.b=t2.b
+ WHERE t3 IS NOT NULL =>
+ SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a, t3
+ WHERE t3 IS NOT NULL AND t3.b=t2.b =>
+ SELECT * FROM t1, t2, t3
+ WHERE t3 IS NOT NULL AND t3.b=t2.b AND t2.a=t1.a
+ @endcode
+
+ The function removes all unnecessary braces from the expression
+ produced by the conversions.
+ E.g.
+ @code
+ SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a AND t3.b=t1.b
+ @endcode
+ finally is converted to:
+ @code
+ SELECT * FROM t1, t2, t3 WHERE t2.c < 5 AND t2.a=t1.a AND t3.b=t1.b
+
+ @endcode
+
+
+ It also will remove braces from the following queries:
+ @code
+ SELECT * from (t1 LEFT JOIN t2 ON t2.a=t1.a) LEFT JOIN t3 ON t3.b=t2.b
+ SELECT * from (t1, (t2,t3)) WHERE t1.a=t2.a AND t2.b=t3.b.
+ @endcode
+
+ The benefit of this simplification procedure is that it might return
+ a query for which the optimizer can evaluate execution plan with more
+ join orders. With a left join operation the optimizer does not
+ consider any plan where one of the inner tables is before some of outer
+ tables.
+
+ IMPLEMENTATION
+ The function is implemented by a recursive procedure. On the recursive
+ ascent all attributes are calculated, all outer joins that can be
+ converted are replaced and then all unnecessary braces are removed.
+ As join list contains join tables in the reverse order sequential
+ elimination of outer joins does not require extra recursive calls.
+
+ SEMI-JOIN NOTES
+ Remove all semi-joins that have are within another semi-join (i.e. have
+ an "ancestor" semi-join nest)
+
+ EXAMPLES
+ Here is an example of a join query with invalid cross references:
+ @code
+ SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN t3 ON t3.b=t1.b
+ @endcode
+
+ @param join reference to the query info
+ @param join_list list representation of the join to be converted
+ @param conds conditions to add on expressions for converted joins
+ @param top true <=> conds is the where condition
+ @param in_sj TRUE <=> processing semi-join nest's children
+ @return
+ - The new condition, if success
+ - 0, otherwise
+*/
+
+static COND *
+simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top,
+ bool in_sj)
+{
+ TABLE_LIST *table;
+ NESTED_JOIN *nested_join;
+ TABLE_LIST *prev_table= 0;
+ List_iterator<TABLE_LIST> li(*join_list);
+ bool straight_join= MY_TEST(join->select_options & SELECT_STRAIGHT_JOIN);
+ DBUG_ENTER("simplify_joins");
+
+ /*
+ Try to simplify join operations from join_list.
+ The most outer join operation is checked for conversion first.
+ */
+ while ((table= li++))
+ {
+ table_map used_tables;
+ table_map not_null_tables= (table_map) 0;
+
+ if ((nested_join= table->nested_join))
+ {
+ /*
+ If the element of join_list is a nested join apply
+ the procedure to its nested join list first.
+ */
+ if (table->on_expr)
+ {
+ Item *expr= table->on_expr;
+ /*
+ If an on expression E is attached to the table,
+ check all null rejected predicates in this expression.
+ If such a predicate over an attribute belonging to
+ an inner table of an embedded outer join is found,
+ the outer join is converted to an inner join and
+ the corresponding on expression is added to E.
+ */
+ expr= simplify_joins(join, &nested_join->join_list,
+ expr, FALSE, in_sj || table->sj_on_expr);
+
+ if (!table->prep_on_expr || expr != table->on_expr)
+ {
+ DBUG_ASSERT(expr);
+
+ table->on_expr= expr;
+ table->prep_on_expr= expr->copy_andor_structure(join->thd);
+ }
+ }
+ nested_join->used_tables= (table_map) 0;
+ nested_join->not_null_tables=(table_map) 0;
+ conds= simplify_joins(join, &nested_join->join_list, conds, top,
+ in_sj || table->sj_on_expr);
+ used_tables= nested_join->used_tables;
+ not_null_tables= nested_join->not_null_tables;
+ /* The following two might become unequal after table elimination: */
+ nested_join->n_tables= nested_join->join_list.elements;
+ }
+ else
+ {
+ if (!table->prep_on_expr)
+ table->prep_on_expr= table->on_expr;
+ used_tables= table->get_map();
+ if (conds)
+ not_null_tables= conds->not_null_tables();
+ }
+
+ if (table->embedding)
+ {
+ table->embedding->nested_join->used_tables|= used_tables;
+ table->embedding->nested_join->not_null_tables|= not_null_tables;
+ }
+
+ if (!(table->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT)) ||
+ (used_tables & not_null_tables))
+ {
+ /*
+ For some of the inner tables there are conjunctive predicates
+ that reject nulls => the outer join can be replaced by an inner join.
+ */
+ if (table->outer_join && !table->embedding && table->table)
+ table->table->maybe_null= FALSE;
+ table->outer_join= 0;
+ if (table->on_expr)
+ {
+ /* Add ON expression to the WHERE or upper-level ON condition. */
+ if (conds)
+ {
+ conds= and_conds(conds, table->on_expr);
+ conds->top_level_item();
+ /* conds is always a new item as both cond and on_expr existed */
+ DBUG_ASSERT(!conds->fixed);
+ conds->fix_fields(join->thd, &conds);
+ }
+ else
+ conds= table->on_expr;
+ table->prep_on_expr= table->on_expr= 0;
+ }
+ }
+
+ /*
+ Only inner tables of non-convertible outer joins
+ remain with on_expr.
+ */
+ if (table->on_expr)
+ {
+ table->dep_tables|= table->on_expr->used_tables();
+ if (table->embedding)
+ {
+ table->dep_tables&= ~table->embedding->nested_join->used_tables;
+ /*
+ Embedding table depends on tables used
+ in embedded on expressions.
+ */
+ table->embedding->on_expr_dep_tables|= table->on_expr->used_tables();
+ }
+ else
+ table->dep_tables&= ~table->get_map();
+ }
+
+ if (prev_table)
+ {
+ /* The order of tables is reverse: prev_table follows table */
+ if (prev_table->straight || straight_join)
+ prev_table->dep_tables|= used_tables;
+ if (prev_table->on_expr)
+ {
+ prev_table->dep_tables|= table->on_expr_dep_tables;
+ table_map prev_used_tables= prev_table->nested_join ?
+ prev_table->nested_join->used_tables :
+ prev_table->get_map();
+ /*
+ If on expression contains only references to inner tables
+ we still make the inner tables dependent on the outer tables.
+ It would be enough to set dependency only on one outer table
+ for them. Yet this is really a rare case.
+ Note:
+ RAND_TABLE_BIT mask should not be counted as it
+ prevents update of inner table dependences.
+ For example it might happen if RAND() function
+ is used in JOIN ON clause.
+ */
+ if (!((prev_table->on_expr->used_tables() &
+ ~(OUTER_REF_TABLE_BIT | RAND_TABLE_BIT)) &
+ ~prev_used_tables))
+ prev_table->dep_tables|= used_tables;
+ }
+ }
+ prev_table= table;
+ }
+
+ /*
+ Flatten nested joins that can be flattened.
+ no ON expression and not a semi-join => can be flattened.
+ */
+ li.rewind();
+ while ((table= li++))
+ {
+ nested_join= table->nested_join;
+ if (table->sj_on_expr && !in_sj)
+ {
+ /*
+ If this is a semi-join that is not contained within another semi-join,
+ leave it intact (otherwise it is flattened)
+ */
+ join->select_lex->sj_nests.push_back(table);
+
+ /*
+ Also, walk through semi-join children and mark those that are now
+ top-level
+ */
+ TABLE_LIST *tbl;
+ List_iterator<TABLE_LIST> it(nested_join->join_list);
+ while ((tbl= it++))
+ {
+ if (!tbl->on_expr && tbl->table)
+ tbl->table->maybe_null= FALSE;
+ }
+ }
+ else if (nested_join && !table->on_expr)
+ {
+ TABLE_LIST *tbl;
+ List_iterator<TABLE_LIST> it(nested_join->join_list);
+ List<TABLE_LIST> repl_list;
+ while ((tbl= it++))
+ {
+ tbl->embedding= table->embedding;
+ if (!tbl->embedding && !tbl->on_expr && tbl->table)
+ tbl->table->maybe_null= FALSE;
+ tbl->join_list= table->join_list;
+ repl_list.push_back(tbl);
+ tbl->dep_tables|= table->dep_tables;
+ }
+ li.replace(repl_list);
+ }
+ }
+ DBUG_RETURN(conds);
+}
+
+
+/**
+ Assign each nested join structure a bit in nested_join_map.
+
+ Assign each nested join structure (except ones that embed only one element
+ and so are redundant) a bit in nested_join_map.
+
+ @param join Join being processed
+ @param join_list List of tables
+ @param first_unused Number of first unused bit in nested_join_map before the
+ call
+
+ @note
+ This function is called after simplify_joins(), when there are no
+ redundant nested joins, #non_redundant_nested_joins <= #tables_in_join so
+ we will not run out of bits in nested_join_map.
+
+ @return
+ First unused bit in nested_join_map after the call.
+*/
+
+static uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list,
+ uint first_unused)
+{
+ List_iterator<TABLE_LIST> li(*join_list);
+ TABLE_LIST *table;
+ DBUG_ENTER("build_bitmap_for_nested_joins");
+ while ((table= li++))
+ {
+ NESTED_JOIN *nested_join;
+ if ((nested_join= table->nested_join))
+ {
+ /*
+ It is guaranteed by simplify_joins() function that a nested join
+ that has only one child represents a single table VIEW (and the child
+ is an underlying table). We don't assign bits to such nested join
+ structures because
+ 1. it is redundant (a "sequence" of one table cannot be interleaved
+ with anything)
+ 2. we could run out bits in nested_join_map otherwise.
+ */
+ if (nested_join->n_tables != 1)
+ {
+ /* Don't assign bits to sj-nests */
+ if (table->on_expr)
+ nested_join->nj_map= (nested_join_map) 1 << first_unused++;
+ first_unused= build_bitmap_for_nested_joins(&nested_join->join_list,
+ first_unused);
+ }
+ }
+ }
+ DBUG_RETURN(first_unused);
+}
+
+
+/**
+ Set NESTED_JOIN::counter=0 in all nested joins in passed list.
+
+ Recursively set NESTED_JOIN::counter=0 for all nested joins contained in
+ the passed join_list.
+
+ @param join_list List of nested joins to process. It may also contain base
+ tables which will be ignored.
+*/
+
+static uint reset_nj_counters(JOIN *join, List<TABLE_LIST> *join_list)
+{
+ List_iterator<TABLE_LIST> li(*join_list);
+ TABLE_LIST *table;
+ DBUG_ENTER("reset_nj_counters");
+ uint n=0;
+ while ((table= li++))
+ {
+ NESTED_JOIN *nested_join;
+ bool is_eliminated_nest= FALSE;
+ if ((nested_join= table->nested_join))
+ {
+ nested_join->counter= 0;
+ nested_join->n_tables= reset_nj_counters(join, &nested_join->join_list);
+ if (!nested_join->n_tables)
+ is_eliminated_nest= TRUE;
+ }
+ if ((table->nested_join && !is_eliminated_nest) ||
+ (!table->nested_join && (table->table->map & ~join->eliminated_tables)))
+ n++;
+ }
+ DBUG_RETURN(n);
+}
+
+
+/**
+ Check interleaving with an inner tables of an outer join for
+ extension table.
+
+ Check if table next_tab can be added to current partial join order, and
+ if yes, record that it has been added.
+
+ The function assumes that both current partial join order and its
+ extension with next_tab are valid wrt table dependencies.
+
+ @verbatim
+ IMPLEMENTATION
+ LIMITATIONS ON JOIN ORDER
+ The nested [outer] joins executioner algorithm imposes these limitations
+ on join order:
+ 1. "Outer tables first" - any "outer" table must be before any
+ corresponding "inner" table.
+ 2. "No interleaving" - tables inside a nested join must form a continuous
+ sequence in join order (i.e. the sequence must not be interrupted by
+ tables that are outside of this nested join).
+
+ #1 is checked elsewhere, this function checks #2 provided that #1 has
+ been already checked.
+
+ WHY NEED NON-INTERLEAVING
+ Consider an example:
+
+ select * from t0 join t1 left join (t2 join t3) on cond1
+
+ The join order "t1 t2 t0 t3" is invalid:
+
+ table t0 is outside of the nested join, so WHERE condition for t0 is
+ attached directly to t0 (without triggers, and it may be used to access
+ t0). Applying WHERE(t0) to (t2,t0,t3) record is invalid as we may miss
+ combinations of (t1, t2, t3) that satisfy condition cond1, and produce a
+ null-complemented (t1, t2.NULLs, t3.NULLs) row, which should not have
+ been produced.
+
+ If table t0 is not between t2 and t3, the problem doesn't exist:
+ If t0 is located after (t2,t3), WHERE(t0) is applied after nested join
+ processing has finished.
+ If t0 is located before (t2,t3), predicates like WHERE_cond(t0, t2) are
+ wrapped into condition triggers, which takes care of correct nested
+ join processing.
+
+ HOW IT IS IMPLEMENTED
+ The limitations on join order can be rephrased as follows: for valid
+ join order one must be able to:
+ 1. write down the used tables in the join order on one line.
+ 2. for each nested join, put one '(' and one ')' on the said line
+ 3. write "LEFT JOIN" and "ON (...)" where appropriate
+ 4. get a query equivalent to the query we're trying to execute.
+
+ Calls to check_interleaving_with_nj() are equivalent to writing the
+ above described line from left to right.
+ A single check_interleaving_with_nj(A,B) call is equivalent to writing
+ table B and appropriate brackets on condition that table A and
+ appropriate brackets is the last what was written. Graphically the
+ transition is as follows:
+
+ +---- current position
+ |
+ ... last_tab ))) | ( next_tab ) )..) | ...
+ X Y Z |
+ +- need to move to this
+ position.
+
+ Notes about the position:
+ The caller guarantees that there is no more then one X-bracket by
+ checking "!(remaining_tables & s->dependent)" before calling this
+ function. X-bracket may have a pair in Y-bracket.
+
+ When "writing" we store/update this auxilary info about the current
+ position:
+ 1. join->cur_embedding_map - bitmap of pairs of brackets (aka nested
+ joins) we've opened but didn't close.
+ 2. {each NESTED_JOIN structure not simplified away}->counter - number
+ of this nested join's children that have already been added to to
+ the partial join order.
+ @endverbatim
+
+ @param next_tab Table we're going to extend the current partial join with
+
+ @retval
+ FALSE Join order extended, nested joins info about current join
+ order (see NOTE section) updated.
+ @retval
+ TRUE Requested join order extension not allowed.
+*/
+
+static bool check_interleaving_with_nj(JOIN_TAB *next_tab)
+{
+ TABLE_LIST *next_emb= next_tab->table->pos_in_table_list->embedding;
+ JOIN *join= next_tab->join;
+
+ if (join->cur_embedding_map & ~next_tab->embedding_map)
+ {
+ /*
+ next_tab is outside of the "pair of brackets" we're currently in.
+ Cannot add it.
+ */
+ return TRUE;
+ }
+
+ /*
+ Do update counters for "pairs of brackets" that we've left (marked as
+ X,Y,Z in the above picture)
+ */
+ for (;next_emb && next_emb != join->emb_sjm_nest; next_emb= next_emb->embedding)
+ {
+ if (!next_emb->sj_on_expr)
+ {
+ next_emb->nested_join->counter++;
+ if (next_emb->nested_join->counter == 1)
+ {
+ /*
+ next_emb is the first table inside a nested join we've "entered". In
+ the picture above, we're looking at the 'X' bracket. Don't exit yet as
+ X bracket might have Y pair bracket.
+ */
+ join->cur_embedding_map |= next_emb->nested_join->nj_map;
+ }
+
+ if (next_emb->nested_join->n_tables !=
+ next_emb->nested_join->counter)
+ break;
+
+ /*
+ We're currently at Y or Z-bracket as depicted in the above picture.
+ Mark that we've left it and continue walking up the brackets hierarchy.
+ */
+ join->cur_embedding_map &= ~next_emb->nested_join->nj_map;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ Nested joins perspective: Remove the last table from the join order.
+
+ The algorithm is the reciprocal of check_interleaving_with_nj(), hence
+ parent join nest nodes are updated only when the last table in its child
+ node is removed. The ASCII graphic below will clarify.
+
+ %A table nesting such as <tt> t1 x [ ( t2 x t3 ) x ( t4 x t5 ) ] </tt>is
+ represented by the below join nest tree.
+
+ @verbatim
+ NJ1
+ _/ / \
+ _/ / NJ2
+ _/ / / \
+ / / / \
+ t1 x [ (t2 x t3) x (t4 x t5) ]
+ @endverbatim
+
+ At the point in time when check_interleaving_with_nj() adds the table t5 to
+ the query execution plan, QEP, it also directs the node named NJ2 to mark
+ the table as covered. NJ2 does so by incrementing its @c counter
+ member. Since all of NJ2's tables are now covered by the QEP, the algorithm
+ proceeds up the tree to NJ1, incrementing its counter as well. All join
+ nests are now completely covered by the QEP.
+
+ restore_prev_nj_state() does the above in reverse. As seen above, the node
+ NJ1 contains the nodes t2, t3, and NJ2. Its counter being equal to 3 means
+ that the plan covers t2, t3, and NJ2, @e and that the sub-plan (t4 x t5)
+ completely covers NJ2. The removal of t5 from the partial plan will first
+ decrement NJ2's counter to 1. It will then detect that NJ2 went from being
+ completely to partially covered, and hence the algorithm must continue
+ upwards to NJ1 and decrement its counter to 2. %A subsequent removal of t4
+ will however not influence NJ1 since it did not un-cover the last table in
+ NJ2.
+
+ SYNOPSIS
+ restore_prev_nj_state()
+ last join table to remove, it is assumed to be the last in current
+ partial join order.
+
+ DESCRIPTION
+
+ Remove the last table from the partial join order and update the nested
+ joins counters and join->cur_embedding_map. It is ok to call this
+ function for the first table in join order (for which
+ check_interleaving_with_nj has not been called)
+
+ @param last join table to remove, it is assumed to be the last in current
+ partial join order.
+*/
+
+static void restore_prev_nj_state(JOIN_TAB *last)
+{
+ TABLE_LIST *last_emb= last->table->pos_in_table_list->embedding;
+ JOIN *join= last->join;
+ for (;last_emb != NULL && last_emb != join->emb_sjm_nest;
+ last_emb= last_emb->embedding)
+ {
+ if (!last_emb->sj_on_expr)
+ {
+ NESTED_JOIN *nest= last_emb->nested_join;
+ DBUG_ASSERT(nest->counter > 0);
+
+ bool was_fully_covered= nest->is_fully_covered();
+
+ join->cur_embedding_map|= nest->nj_map;
+
+ if (--nest->counter == 0)
+ join->cur_embedding_map&= ~nest->nj_map;
+
+ if (!was_fully_covered)
+ break;
+ }
+ }
+}
+
+
+
+/*
+ Change access methods not to use join buffering and adjust costs accordingly
+
+ SYNOPSIS
+ optimize_wo_join_buffering()
+ join
+ first_tab The first tab to do re-optimization for
+ last_tab The last tab to do re-optimization for
+ last_remaining_tables Bitmap of tables that are not in the
+ [0...last_tab] join prefix
+ first_alt TRUE <=> Use the LooseScan plan for the first_tab
+ no_jbuf_before Don't allow to use join buffering before this
+ table
+ reopt_rec_count OUT New output record count
+ reopt_cost OUT New join prefix cost
+
+ DESCRIPTION
+ Given a join prefix [0; ... first_tab], change the access to the tables
+ in the [first_tab; last_tab] not to use join buffering. This is needed
+ because some semi-join strategies cannot be used together with the join
+ buffering.
+ In general case the best table order in [first_tab; last_tab] range with
+ join buffering is different from the best order without join buffering but
+ we don't try finding a better join order. (TODO ask Igor why did we
+ chose not to do this in the end. that's actually the difference from the
+ forking approach)
+*/
+
+void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab,
+ table_map last_remaining_tables,
+ bool first_alt, uint no_jbuf_before,
+ double *outer_rec_count, double *reopt_cost)
+{
+ double cost, rec_count;
+ table_map reopt_remaining_tables= last_remaining_tables;
+ uint i;
+
+ if (first_tab > join->const_tables)
+ {
+ cost= join->positions[first_tab - 1].prefix_cost.total_cost();
+ rec_count= join->positions[first_tab - 1].prefix_record_count;
+ }
+ else
+ {
+ cost= 0.0;
+ rec_count= 1;
+ }
+
+ *outer_rec_count= rec_count;
+ for (i= first_tab; i <= last_tab; i++)
+ reopt_remaining_tables |= join->positions[i].table->table->map;
+
+ /*
+ best_access_path() optimization depends on the value of
+ join->cur_sj_inner_tables. Our goal in this function is to do a
+ re-optimization with disabled join buffering, but no other changes.
+ In order to achieve this, cur_sj_inner_tables needs have the same
+ value it had during the original invocations of best_access_path.
+
+ We know that this function, optimize_wo_join_buffering() is called to
+ re-optimize semi-join join order range, which allows to conclude that
+ the "original" value of cur_sj_inner_tables was 0.
+ */
+ table_map save_cur_sj_inner_tables= join->cur_sj_inner_tables;
+ join->cur_sj_inner_tables= 0;
+
+ for (i= first_tab; i <= last_tab; i++)
+ {
+ JOIN_TAB *rs= join->positions[i].table;
+ POSITION pos, loose_scan_pos;
+
+ if ((i == first_tab && first_alt) || join->positions[i].use_join_buffer)
+ {
+ /* Find the best access method that would not use join buffering */
+ best_access_path(join, rs, reopt_remaining_tables, i,
+ TRUE, rec_count,
+ &pos, &loose_scan_pos);
+ }
+ else
+ pos= join->positions[i];
+
+ if ((i == first_tab && first_alt))
+ pos= loose_scan_pos;
+
+ reopt_remaining_tables &= ~rs->table->map;
+ rec_count *= pos.records_read;
+ cost += pos.read_time;
+
+ if (!rs->emb_sj_nest)
+ *outer_rec_count *= pos.records_read;
+ }
+ join->cur_sj_inner_tables= save_cur_sj_inner_tables;
+
+ *reopt_cost= cost;
+}
+
+
+static COND *
+optimize_cond(JOIN *join, COND *conds,
+ List<TABLE_LIST> *join_list, bool ignore_on_conds,
+ Item::cond_result *cond_value, COND_EQUAL **cond_equal,
+ int flags)
+{
+ THD *thd= join->thd;
+ DBUG_ENTER("optimize_cond");
+
+ if (!conds)
+ {
+ *cond_value= Item::COND_TRUE;
+ if (!ignore_on_conds)
+ build_equal_items(join, NULL, NULL, join_list, ignore_on_conds,
+ cond_equal);
+ }
+ else
+ {
+ /*
+ Build all multiple equality predicates and eliminate equality
+ predicates that can be inferred from these multiple equalities.
+ For each reference of a field included into a multiple equality
+ that occurs in a function set a pointer to the multiple equality
+ predicate. Substitute a constant instead of this field if the
+ multiple equality contains a constant.
+ */
+ DBUG_EXECUTE("where", print_where(conds, "original", QT_ORDINARY););
+ conds= build_equal_items(join, conds, NULL, join_list,
+ ignore_on_conds, cond_equal,
+ MY_TEST(flags & OPT_LINK_EQUAL_FIELDS));
+ DBUG_EXECUTE("where",print_where(conds,"after equal_items", QT_ORDINARY););
+
+ /* change field = field to field = const for each found field = const */
+ propagate_cond_constants(thd, (I_List<COND_CMP> *) 0, conds, conds);
+ /*
+ Remove all instances of item == item
+ Remove all and-levels where CONST item != CONST item
+ */
+ DBUG_EXECUTE("where",print_where(conds,"after const change", QT_ORDINARY););
+ conds= remove_eq_conds(thd, conds, cond_value);
+ if (conds && conds->type() == Item::COND_ITEM &&
+ ((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC)
+ *cond_equal= &((Item_cond_and*) conds)->cond_equal;
+ DBUG_EXECUTE("info",print_where(conds,"after remove", QT_ORDINARY););
+ }
+ DBUG_RETURN(conds);
+}
+
+
+/**
+ @brief
+ Propagate multiple equalities to the sub-expressions of a condition
+
+ @param thd thread handle
+ @param cond the condition where equalities are to be propagated
+ @param *new_equalities the multiple equalities to be propagated
+ @param inherited path to all inherited multiple equality items
+ @param[out] is_simplifiable_cond 'cond' may be simplified after the
+ propagation of the equalities
+
+ @details
+ The function recursively traverses the tree of the condition 'cond' and
+ for each its AND sub-level of any depth the function merges the multiple
+ equalities from the list 'new_equalities' into the multiple equalities
+ attached to the AND item created for this sub-level.
+ The function also [re]sets references to the equalities formed by the
+ merges of multiple equalities in all field items occurred in 'cond'
+ that are encountered in the equalities.
+ If the result of any merge of multiple equalities is an impossible
+ condition the function returns TRUE in the parameter is_simplifiable_cond.
+*/
+
+void propagate_new_equalities(THD *thd, Item *cond,
+ List<Item_equal> *new_equalities,
+ COND_EQUAL *inherited,
+ bool *is_simplifiable_cond)
+{
+ if (cond->type() == Item::COND_ITEM)
+ {
+ bool and_level= ((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC;
+ if (and_level)
+ {
+ Item_cond_and *cond_and= (Item_cond_and *) cond;
+ List<Item_equal> *cond_equalities= &cond_and->cond_equal.current_level;
+ cond_and->cond_equal.upper_levels= inherited;
+ if (!cond_equalities->is_empty() && cond_equalities != new_equalities)
+ {
+ Item_equal *equal_item;
+ List_iterator<Item_equal> it(*new_equalities);
+ while ((equal_item= it++))
+ {
+ equal_item->merge_into_list(cond_equalities, true, true);
+ }
+ List_iterator<Item_equal> ei(*cond_equalities);
+ while ((equal_item= ei++))
+ {
+ if (equal_item->const_item() && !equal_item->val_int())
+ {
+ *is_simplifiable_cond= true;
+ return;
+ }
+ }
+ }
+ }
+
+ Item *item;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ while ((item= li++))
+ {
+ COND_EQUAL *new_inherited= and_level && item->type() == Item::COND_ITEM ?
+ &((Item_cond_and *) cond)->cond_equal :
+ inherited;
+ propagate_new_equalities(thd, item, new_equalities, new_inherited,
+ is_simplifiable_cond);
+ }
+ }
+ else if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC)
+ {
+ Item_equal *equal_item;
+ List_iterator<Item_equal> it(*new_equalities);
+ Item_equal *equality= (Item_equal *) cond;
+ equality->upper_levels= inherited;
+ while ((equal_item= it++))
+ {
+ equality->merge_with_check(equal_item, true);
+ }
+ if (equality->const_item() && !equality->val_int())
+ *is_simplifiable_cond= true;
+ }
+ else
+ {
+ uchar* is_subst_valid= (uchar *) Item::ANY_SUBST;
+ cond= cond->compile(&Item::subst_argument_checker,
+ &is_subst_valid,
+ &Item::equal_fields_propagator,
+ (uchar *) inherited);
+ cond->update_used_tables();
+ }
+}
+
+/*
+ Check if cond_is_datetime_is_null() is true for the condition cond, or
+ for any of its AND/OR-children
+*/
+bool cond_has_datetime_is_null(Item *cond)
+{
+ if (cond_is_datetime_is_null(cond))
+ return true;
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ List<Item> *cond_arg_list= ((Item_cond*) cond)->argument_list();
+ List_iterator<Item> li(*cond_arg_list);
+ Item *item;
+ while ((item= li++))
+ {
+ if (cond_has_datetime_is_null(item))
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ Check if passed condtition has for of
+
+ not_null_date_col IS NULL
+
+ where not_null_date_col has a datte or datetime type
+*/
+
+bool cond_is_datetime_is_null(Item *cond)
+{
+ if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_func*) cond)->functype() == Item_func::ISNULL_FUNC)
+ {
+ Item **args= ((Item_func_isnull*) cond)->arguments();
+ if (args[0]->type() == Item::FIELD_ITEM)
+ {
+ Field *field=((Item_field*) args[0])->field;
+
+ if (((field->type() == MYSQL_TYPE_DATE) ||
+ (field->type() == MYSQL_TYPE_DATETIME)) &&
+ (field->flags & NOT_NULL_FLAG))
+ {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ @brief
+ Evaluate all constant boolean sub-expressions in a condition
+
+ @param thd thread handle
+ @param cond condition where where to evaluate constant sub-expressions
+ @param[out] cond_value : the returned value of the condition
+ (TRUE/FALSE/UNKNOWN:
+ Item::COND_TRUE/Item::COND_FALSE/Item::COND_OK)
+ @return
+ the item that is the result of the substitution of all inexpensive constant
+ boolean sub-expressions into cond, or,
+ NULL if the condition is constant and is evaluated to FALSE.
+
+ @details
+ This function looks for all inexpensive constant boolean sub-expressions in
+ the given condition 'cond' and substitutes them for their values.
+ For example, the condition 2 > (5 + 1) or a < (10 / 2)
+ will be transformed to the condition a < (10 / 2).
+ Note that a constant sub-expression is evaluated only if it is constant and
+ inexpensive. A sub-expression with an uncorrelated subquery may be evaluated
+ only if the subquery is considered as inexpensive.
+ The function does not evaluate a constant sub-expression if it is not on one
+ of AND/OR levels of the condition 'cond'. For example, the subquery in the
+ condition a > (select max(b) from t1 where b > 5) will never be evaluated
+ by this function.
+ If a constant boolean sub-expression is evaluated to TRUE then:
+ - when the sub-expression is a conjunct of an AND formula it is simply
+ removed from this formula
+ - when the sub-expression is a disjunct of an OR formula the whole OR
+ formula is converted to TRUE
+ If a constant boolean sub-expression is evaluated to FALSE then:
+ - when the sub-expression is a disjunct of an OR formula it is simply
+ removed from this formula
+ - when the sub-expression is a conjuct of an AND formula the whole AND
+ formula is converted to FALSE
+ When a disjunct/conjunct is removed from an OR/AND formula it might happen
+ that there is only one conjunct/disjunct remaining. In this case this
+ remaining disjunct/conjunct must be merged into underlying AND/OR formula,
+ because AND/OR levels must alternate in the same way as they alternate
+ after fix_fields() is called for the original condition.
+ The specifics of merging a formula f into an AND formula A appears
+ when A contains multiple equalities and f contains multiple equalities.
+ In this case the multiple equalities from f and A have to be merged.
+ After this the resulting multiple equalities have to be propagated into
+ the all AND/OR levels of the formula A (see propagate_new_equalities()).
+ The propagation of multiple equalities might result in forming multiple
+ equalities that are always FALSE. This, in its turn, might trigger further
+ simplification of the condition.
+
+ @note
+ EXAMPLE 1:
+ SELECT * FROM t1 WHERE (b = 1 OR a = 1) AND (b = 5 AND a = 5 OR 1 != 1);
+ First 1 != 1 will be removed from the second conjunct:
+ => SELECT * FROM t1 WHERE (b = 1 OR a = 1) AND (b = 5 AND a = 5);
+ Then (b = 5 AND a = 5) will be merged into the top level condition:
+ => SELECT * FROM t1 WHERE (b = 1 OR a = 1) AND (b = 5) AND (a = 5);
+ Then (b = 5), (a = 5) will be propagated into the disjuncs of
+ (b = 1 OR a = 1):
+ => SELECT * FROM t1 WHERE ((b = 1) AND (b = 5) AND (a = 5) OR
+ (a = 1) AND (b = 5) AND (a = 5)) AND
+ (b = 5) AND (a = 5)
+ => SELECT * FROM t1 WHERE ((FALSE AND (a = 5)) OR
+ (FALSE AND (b = 5))) AND
+ (b = 5) AND (a = 5)
+ After this an additional call of remove_eq_conds() converts it
+ to FALSE
+
+ EXAMPLE 2:
+ SELECT * FROM t1 WHERE (b = 1 OR a = 5) AND (b = 5 AND a = 5 OR 1 != 1);
+ => SELECT * FROM t1 WHERE (b = 1 OR a = 5) AND (b = 5 AND a = 5);
+ => SELECT * FROM t1 WHERE (b = 1 OR a = 5) AND (b = 5) AND (a = 5);
+ => SELECT * FROM t1 WHERE ((b = 1) AND (b = 5) AND (a = 5) OR
+ (a = 5) AND (b = 5) AND (a = 5)) AND
+ (b = 5) AND (a = 5)
+ => SELECT * FROM t1 WHERE ((FALSE AND (a = 5)) OR
+ ((b = 5) AND (a = 5))) AND
+ (b = 5) AND (a = 5)
+ After this an additional call of remove_eq_conds() converts it to
+ => SELECT * FROM t1 WHERE (b = 5) AND (a = 5)
+*/
+
+static COND *
+internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value)
+{
+ if (cond->type() == Item::COND_ITEM)
+ {
+ bool and_level= ((Item_cond*) cond)->functype()
+ == Item_func::COND_AND_FUNC;
+ List<Item> *cond_arg_list= ((Item_cond*) cond)->argument_list();
+
+ if (and_level)
+ {
+ /*
+ Remove multiple equalities that became always true (e.g. after
+ constant row substitution).
+ They would be removed later in the function anyway, but the list of
+ them cond_equal.current_level also must be adjusted correspondingly.
+ So it's easier to do it at one pass through the list of the equalities.
+ */
+ List<Item_equal> *cond_equalities=
+ &((Item_cond_and *) cond)->cond_equal.current_level;
+ cond_arg_list->disjoin((List<Item> *) cond_equalities);
+ List_iterator<Item_equal> it(*cond_equalities);
+ Item_equal *eq_item;
+ while ((eq_item= it++))
+ {
+ if (eq_item->const_item() && eq_item->val_int())
+ it.remove();
+ }
+ cond_arg_list->concat((List<Item> *) cond_equalities);
+ }
+
+ List<Item_equal> new_equalities;
+ List_iterator<Item> li(*cond_arg_list);
+ bool should_fix_fields= 0;
+ Item::cond_result tmp_cond_value;
+ Item *item;
+
+ /*
+ If the list cond_arg_list became empty then it consisted only
+ of always true multiple equalities.
+ */
+ *cond_value= cond_arg_list->elements ? Item::COND_UNDEF : Item::COND_TRUE;
+
+ while ((item=li++))
+ {
+ Item *new_item=internal_remove_eq_conds(thd, item, &tmp_cond_value);
+ if (!new_item)
+ {
+ /* This can happen only when item is converted to TRUE or FALSE */
+ li.remove();
+ }
+ else if (item != new_item)
+ {
+ /*
+ This can happen when:
+ - item was an OR formula converted to one disjunct
+ - item was an AND formula converted to one conjunct
+ In these cases the disjunct/conjunct must be merged into the
+ argument list of cond.
+ */
+ if (new_item->type() == Item::COND_ITEM &&
+ item->type() == Item::COND_ITEM)
+ {
+ DBUG_ASSERT(((Item_cond *) cond)->functype() ==
+ ((Item_cond *) new_item)->functype());
+ List<Item> *new_item_arg_list=
+ ((Item_cond *) new_item)->argument_list();
+ if (and_level)
+ {
+ /*
+ If new_item is an AND formula then multiple equalities
+ of new_item_arg_list must merged into multiple equalities
+ of cond_arg_list.
+ */
+ List<Item_equal> *new_item_equalities=
+ &((Item_cond_and *) new_item)->cond_equal.current_level;
+ if (!new_item_equalities->is_empty())
+ {
+ /*
+ Cut the multiple equalities from the new_item_arg_list and
+ append them on the list new_equalities. Later the equalities
+ from this list will be merged into the multiple equalities
+ of cond_arg_list all together.
+ */
+ new_item_arg_list->disjoin((List<Item> *) new_item_equalities);
+ new_equalities.concat(new_item_equalities);
+ }
+ }
+ if (new_item_arg_list->is_empty())
+ li.remove();
+ else
+ {
+ uint cnt= new_item_arg_list->elements;
+ li.replace(*new_item_arg_list);
+ /* Make iterator li ignore new items */
+ for (cnt--; cnt; cnt--)
+ li++;
+ should_fix_fields= 1;
+ }
+ }
+ else if (and_level &&
+ new_item->type() == Item::FUNC_ITEM &&
+ ((Item_cond*) new_item)->functype() ==
+ Item_func::MULT_EQUAL_FUNC)
+ {
+ li.remove();
+ new_equalities.push_back((Item_equal *) new_item);
+ }
+ else
+ {
+ if (new_item->type() == Item::COND_ITEM &&
+ ((Item_cond*) new_item)->functype() ==
+ ((Item_cond*) cond)->functype())
+ {
+ List<Item> *new_item_arg_list=
+ ((Item_cond *) new_item)->argument_list();
+ uint cnt= new_item_arg_list->elements;
+ li.replace(*new_item_arg_list);
+ /* Make iterator li ignore new items */
+ for (cnt--; cnt; cnt--)
+ li++;
+ }
+ else
+ li.replace(new_item);
+ should_fix_fields= 1;
+ }
+ }
+ if (*cond_value == Item::COND_UNDEF)
+ *cond_value=tmp_cond_value;
+ switch (tmp_cond_value) {
+ case Item::COND_OK: // Not TRUE or FALSE
+ if (and_level || *cond_value == Item::COND_FALSE)
+ *cond_value=tmp_cond_value;
+ break;
+ case Item::COND_FALSE:
+ if (and_level)
+ {
+ *cond_value=tmp_cond_value;
+ return (COND*) 0; // Always false
+ }
+ break;
+ case Item::COND_TRUE:
+ if (!and_level)
+ {
+ *cond_value= tmp_cond_value;
+ return (COND*) 0; // Always true
+ }
+ break;
+ case Item::COND_UNDEF: // Impossible
+ break; /* purecov: deadcode */
+ }
+ }
+ if (!new_equalities.is_empty())
+ {
+ DBUG_ASSERT(and_level);
+ /*
+ Merge multiple equalities that were cut from the results of
+ simplification of OR formulas converted into AND formulas.
+ These multiple equalities are to be merged into the
+ multiple equalities of cond_arg_list.
+ */
+ COND_EQUAL *cond_equal= &((Item_cond_and *) cond)->cond_equal;
+ List<Item_equal> *cond_equalities= &cond_equal->current_level;
+ cond_arg_list->disjoin((List<Item> *) cond_equalities);
+ Item_equal *equality;
+ List_iterator_fast<Item_equal> it(new_equalities);
+ while ((equality= it++))
+ {
+ equality->upper_levels= cond_equal->upper_levels;
+ equality->merge_into_list(cond_equalities, false, false);
+ List_iterator_fast<Item_equal> ei(*cond_equalities);
+ while ((equality= ei++))
+ {
+ if (equality->const_item() && !equality->val_int())
+ {
+ *cond_value= Item::COND_FALSE;
+ return (COND*) 0;
+ }
+ }
+ }
+ cond_arg_list->concat((List<Item> *) cond_equalities);
+ /*
+ Propagate the newly formed multiple equalities to
+ the all AND/OR levels of cond
+ */
+ bool is_simplifiable_cond= false;
+ propagate_new_equalities(thd, cond, cond_equalities,
+ cond_equal->upper_levels,
+ &is_simplifiable_cond);
+ /*
+ If the above propagation of multiple equalities brings us
+ to multiple equalities that are always FALSE then try to
+ simplify the condition with remove_eq_cond() again.
+ */
+ if (is_simplifiable_cond)
+ {
+ if (!(cond= internal_remove_eq_conds(thd, cond, cond_value)))
+ return cond;
+ }
+ should_fix_fields= 1;
+ }
+ if (should_fix_fields)
+ cond->update_used_tables();
+
+ if (!((Item_cond*) cond)->argument_list()->elements ||
+ *cond_value != Item::COND_OK)
+ return (COND*) 0;
+ if (((Item_cond*) cond)->argument_list()->elements == 1)
+ { // Remove list
+ item= ((Item_cond*) cond)->argument_list()->head();
+ ((Item_cond*) cond)->argument_list()->empty();
+ return item;
+ }
+ }
+ else if (cond_is_datetime_is_null(cond))
+ {
+ /* fix to replace 'NULL' dates with '0' (shreeve@uci.edu) */
+ /*
+ See BUG#12594011
+ Documentation says that
+ SELECT datetime_notnull d FROM t1 WHERE d IS NULL
+ shall return rows where d=='0000-00-00'
+
+ Thus, for DATE and DATETIME columns defined as NOT NULL,
+ "date_notnull IS NULL" has to be modified to
+ "date_notnull IS NULL OR date_notnull == 0" (if outer join)
+ "date_notnull == 0" (otherwise)
+
+ */
+ Item **args= ((Item_func_isnull*) cond)->arguments();
+ Field *field=((Item_field*) args[0])->field;
+
+ Item *item0= new(thd->mem_root) Item_int((longlong)0, 1);
+ Item *eq_cond= new(thd->mem_root) Item_func_eq(args[0], item0);
+ if (!eq_cond)
+ return cond;
+
+ if (field->table->pos_in_table_list->is_inner_table_of_outer_join())
+ {
+ // outer join: transform "col IS NULL" to "col IS NULL or col=0"
+ Item *or_cond= new(thd->mem_root) Item_cond_or(eq_cond, cond);
+ if (!or_cond)
+ return cond;
+ cond= or_cond;
+ }
+ else
+ {
+ // not outer join: transform "col IS NULL" to "col=0"
+ cond= eq_cond;
+ }
+
+ cond->fix_fields(thd, &cond);
+
+ if (cond->const_item() && !cond->is_expensive())
+ {
+ *cond_value= eval_const_cond(cond) ? Item::COND_TRUE : Item::COND_FALSE;
+ return (COND*) 0;
+ }
+ }
+ else if (cond->const_item() && !cond->is_expensive())
+ {
+ *cond_value= eval_const_cond(cond) ? Item::COND_TRUE : Item::COND_FALSE;
+ return (COND*) 0;
+ }
+ else if ((*cond_value= cond->eq_cmp_result()) != Item::COND_OK)
+ { // boolan compare function
+ Item *left_item= ((Item_func*) cond)->arguments()[0];
+ Item *right_item= ((Item_func*) cond)->arguments()[1];
+ if (left_item->eq(right_item,1))
+ {
+ if (!left_item->maybe_null ||
+ ((Item_func*) cond)->functype() == Item_func::EQUAL_FUNC)
+ return (COND*) 0; // Compare of identical items
+ }
+ }
+ *cond_value=Item::COND_OK;
+ return cond; // Point at next and level
+}
+
+/**
+ Remove const and eq items. Return new item, or NULL if no condition
+ cond_value is set to according:
+ COND_OK query is possible (field = constant)
+ COND_TRUE always true ( 1 = 1 )
+ COND_FALSE always false ( 1 = 2 )
+
+ SYNPOSIS
+ remove_eq_conds()
+ thd THD environment
+ cond the condition to handle
+ cond_value the resulting value of the condition
+
+ NOTES
+ calls the inner_remove_eq_conds to check all the tree reqursively
+
+ RETURN
+ *COND with the simplified condition
+*/
+
+COND *
+remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value)
+{
+ if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_func*) cond)->functype() == Item_func::ISNULL_FUNC)
+ {
+ /*
+ Handles this special case for some ODBC applications:
+ The are requesting the row that was just updated with a auto_increment
+ value with this construct:
+
+ SELECT * from table_name where auto_increment_column IS NULL
+ This will be changed to:
+ SELECT * from table_name where auto_increment_column = LAST_INSERT_ID
+ */
+
+ Item_func_isnull *func=(Item_func_isnull*) cond;
+ Item **args= func->arguments();
+ if (args[0]->type() == Item::FIELD_ITEM)
+ {
+ Field *field=((Item_field*) args[0])->field;
+ if (field->flags & AUTO_INCREMENT_FLAG && !field->table->maybe_null &&
+ (thd->variables.option_bits & OPTION_AUTO_IS_NULL) &&
+ (thd->first_successful_insert_id_in_prev_stmt > 0 &&
+ thd->substitute_null_with_insert_id))
+ {
+#ifdef HAVE_QUERY_CACHE
+ query_cache_abort(&thd->query_cache_tls);
+#endif
+ COND *new_cond;
+ if ((new_cond= new Item_func_eq(args[0],
+ new Item_int("last_insert_id()",
+ thd->read_first_successful_insert_id_in_prev_stmt(),
+ MY_INT64_NUM_DECIMAL_DIGITS))))
+ {
+ cond=new_cond;
+ /*
+ Item_func_eq can't be fixed after creation so we do not check
+ cond->fixed, also it do not need tables so we use 0 as second
+ argument.
+ */
+ cond->fix_fields(thd, &cond);
+ }
+ /*
+ IS NULL should be mapped to LAST_INSERT_ID only for first row, so
+ clear for next row
+ */
+ thd->substitute_null_with_insert_id= FALSE;
+
+ *cond_value= Item::COND_OK;
+ return cond;
+ }
+ }
+ }
+ return internal_remove_eq_conds(thd, cond, cond_value); // Scan all the condition
+}
+
+
+/**
+ Check if equality can be used in removing components of GROUP BY/DISTINCT
+
+ @param l the left comparison argument (a field if any)
+ @param r the right comparison argument (a const of any)
+
+ @details
+ Checks if an equality predicate can be used to take away
+ DISTINCT/GROUP BY because it is known to be true for exactly one
+ distinct value (e.g. <expr> == <const>).
+ Arguments must be compared in the native type of the left argument
+ and (for strings) in the native collation of the left argument.
+ Otherwise, for example,
+ <string_field> = <int_const> may match more than 1 distinct value or
+ the <string_field>.
+
+ @note We don't need to aggregate l and r collations here, because r -
+ the constant item - has already been converted to a proper collation
+ for comparison. We only need to compare this collation with field's collation.
+
+ @retval true can be used
+ @retval false cannot be used
+*/
+static bool
+test_if_equality_guarantees_uniqueness(Item *l, Item *r)
+{
+ return (r->const_item() || !(r->used_tables() & ~OUTER_REF_TABLE_BIT)) &&
+ item_cmp_type(l->cmp_type(), r->cmp_type()) == l->cmp_type() &&
+ (l->cmp_type() != STRING_RESULT ||
+ l->collation.collation == r->collation.collation);
+}
+
+
+/*
+ Return TRUE if i1 and i2 (if any) are equal items,
+ or if i1 is a wrapper item around the f2 field.
+*/
+
+static bool equal(Item *i1, Item *i2, Field *f2)
+{
+ DBUG_ASSERT((i2 == NULL) ^ (f2 == NULL));
+
+ if (i2 != NULL)
+ return i1->eq(i2, 1);
+ else if (i1->type() == Item::FIELD_ITEM)
+ return f2->eq(((Item_field *) i1)->field);
+ else
+ return FALSE;
+}
+
+
+/**
+ Test if a field or an item is equal to a constant value in WHERE
+
+ @param cond WHERE clause expression
+ @param comp_item Item to find in WHERE expression
+ (if comp_field != NULL)
+ @param comp_field Field to find in WHERE expression
+ (if comp_item != NULL)
+ @param[out] const_item intermediate arg, set to Item pointer to NULL
+
+ @return TRUE if the field is a constant value in WHERE
+
+ @note
+ comp_item and comp_field parameters are mutually exclusive.
+*/
+bool
+const_expression_in_where(COND *cond, Item *comp_item, Field *comp_field,
+ Item **const_item)
+{
+ DBUG_ASSERT((comp_item == NULL) ^ (comp_field == NULL));
+
+ Item *intermediate= NULL;
+ if (const_item == NULL)
+ const_item= &intermediate;
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ bool and_level= (((Item_cond*) cond)->functype()
+ == Item_func::COND_AND_FUNC);
+ List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ bool res=const_expression_in_where(item, comp_item, comp_field,
+ const_item);
+ if (res) // Is a const value
+ {
+ if (and_level)
+ return 1;
+ }
+ else if (!and_level)
+ return 0;
+ }
+ return and_level ? 0 : 1;
+ }
+ else if (cond->eq_cmp_result() != Item::COND_OK)
+ { // boolean compare function
+ Item_func* func= (Item_func*) cond;
+ if (func->functype() != Item_func::EQUAL_FUNC &&
+ func->functype() != Item_func::EQ_FUNC)
+ return 0;
+ Item *left_item= ((Item_func*) cond)->arguments()[0];
+ Item *right_item= ((Item_func*) cond)->arguments()[1];
+ if (equal(left_item, comp_item, comp_field))
+ {
+ if (test_if_equality_guarantees_uniqueness (left_item, right_item))
+ {
+ if (*const_item)
+ return right_item->eq(*const_item, 1);
+ *const_item=right_item;
+ return 1;
+ }
+ }
+ else if (equal(right_item, comp_item, comp_field))
+ {
+ if (test_if_equality_guarantees_uniqueness (right_item, left_item))
+ {
+ if (*const_item)
+ return left_item->eq(*const_item, 1);
+ *const_item=left_item;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/****************************************************************************
+ Create internal temporary table
+****************************************************************************/
+
+/**
+ Create field for temporary table from given field.
+
+ @param thd Thread handler
+ @param org_field field from which new field will be created
+ @param name New field name
+ @param table Temporary table
+ @param item !=NULL if item->result_field should point to new field.
+ This is relevant for how fill_record() is going to work:
+ If item != NULL then fill_record() will update
+ the record in the original table.
+ If item == NULL then fill_record() will update
+ the temporary table
+ @param convert_blob_length If >0 create a varstring(convert_blob_length)
+ field instead of blob.
+
+ @retval
+ NULL on error
+ @retval
+ new_created field
+*/
+
+Field *create_tmp_field_from_field(THD *thd, Field *org_field,
+ const char *name, TABLE *table,
+ Item_field *item, uint convert_blob_length)
+{
+ Field *new_field;
+
+ /*
+ Make sure that the blob fits into a Field_varstring which has
+ 2-byte lenght.
+ */
+ if (convert_blob_length && convert_blob_length <= Field_varstring::MAX_SIZE &&
+ (org_field->flags & BLOB_FLAG))
+ new_field= new Field_varstring(convert_blob_length,
+ org_field->maybe_null(),
+ org_field->field_name, table->s,
+ org_field->charset());
+ else
+ new_field= org_field->new_field(thd->mem_root, table,
+ table == org_field->table);
+ if (new_field)
+ {
+ new_field->init(table);
+ new_field->orig_table= org_field->orig_table;
+ if (item)
+ item->result_field= new_field;
+ else
+ new_field->field_name= name;
+ new_field->flags|= (org_field->flags & NO_DEFAULT_VALUE_FLAG);
+ if (org_field->maybe_null() || (item && item->maybe_null))
+ new_field->flags&= ~NOT_NULL_FLAG; // Because of outer join
+ if (org_field->type() == MYSQL_TYPE_VAR_STRING ||
+ org_field->type() == MYSQL_TYPE_VARCHAR)
+ table->s->db_create_options|= HA_OPTION_PACK_RECORD;
+ else if (org_field->type() == FIELD_TYPE_DOUBLE)
+ ((Field_double *) new_field)->not_fixed= TRUE;
+ new_field->vcol_info= 0;
+ new_field->stored_in_db= TRUE;
+ new_field->cond_selectivity= 1.0;
+ new_field->next_equal_field= NULL;
+ new_field->option_list= NULL;
+ new_field->option_struct= NULL;
+ }
+ return new_field;
+}
+
+/**
+ Create field for temporary table using type of given item.
+
+ @param thd Thread handler
+ @param item Item to create a field for
+ @param table Temporary table
+ @param copy_func If set and item is a function, store copy of
+ item in this array
+ @param modify_item 1 if item->result_field should point to new
+ item. This is relevent for how fill_record()
+ is going to work:
+ If modify_item is 1 then fill_record() will
+ update the record in the original table.
+ If modify_item is 0 then fill_record() will
+ update the temporary table
+ @param convert_blob_length If >0 create a varstring(convert_blob_length)
+ field instead of blob.
+
+ @retval
+ 0 on error
+ @retval
+ new_created field
+*/
+
+static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table,
+ Item ***copy_func, bool modify_item,
+ uint convert_blob_length)
+{
+ bool maybe_null= item->maybe_null;
+ Field *new_field;
+ LINT_INIT(new_field);
+
+ switch (item->result_type()) {
+ case REAL_RESULT:
+ new_field= new Field_double(item->max_length, maybe_null,
+ item->name, item->decimals, TRUE);
+ break;
+ case INT_RESULT:
+ /*
+ Select an integer type with the minimal fit precision.
+ MY_INT32_NUM_DECIMAL_DIGITS is sign inclusive, don't consider the sign.
+ Values with MY_INT32_NUM_DECIMAL_DIGITS digits may or may not fit into
+ Field_long : make them Field_longlong.
+ */
+ if (item->max_length >= (MY_INT32_NUM_DECIMAL_DIGITS - 1))
+ new_field=new Field_longlong(item->max_length, maybe_null,
+ item->name, item->unsigned_flag);
+ else
+ new_field=new Field_long(item->max_length, maybe_null,
+ item->name, item->unsigned_flag);
+ break;
+ case STRING_RESULT:
+ DBUG_ASSERT(item->collation.collation);
+
+ /*
+ DATE/TIME and GEOMETRY fields have STRING_RESULT result type.
+ To preserve type they needed to be handled separately.
+ */
+ if (item->cmp_type() == TIME_RESULT ||
+ item->field_type() == MYSQL_TYPE_GEOMETRY)
+ new_field= item->tmp_table_field_from_field_type(table, 1);
+ /*
+ Make sure that the blob fits into a Field_varstring which has
+ 2-byte lenght.
+ */
+ else if (item->max_length/item->collation.collation->mbmaxlen > 255 &&
+ convert_blob_length <= Field_varstring::MAX_SIZE &&
+ convert_blob_length)
+ new_field= new Field_varstring(convert_blob_length, maybe_null,
+ item->name, table->s,
+ item->collation.collation);
+ else
+ new_field= item->make_string_field(table);
+ new_field->set_derivation(item->collation.derivation);
+ break;
+ case DECIMAL_RESULT:
+ new_field= Field_new_decimal::create_from_item(item);
+ break;
+ case ROW_RESULT:
+ default:
+ // This case should never be choosen
+ DBUG_ASSERT(0);
+ new_field= 0;
+ break;
+ }
+ if (new_field)
+ new_field->init(table);
+
+ if (copy_func && item->real_item()->is_result_field())
+ *((*copy_func)++) = item; // Save for copy_funcs
+ if (modify_item)
+ item->set_result_field(new_field);
+ if (item->type() == Item::NULL_ITEM)
+ new_field->is_created_from_null_item= TRUE;
+ return new_field;
+}
+
+
+/**
+ Create field for information schema table.
+
+ @param thd Thread handler
+ @param table Temporary table
+ @param item Item to create a field for
+
+ @retval
+ 0 on error
+ @retval
+ new_created field
+*/
+
+Field *create_tmp_field_for_schema(THD *thd, Item *item, TABLE *table)
+{
+ if (item->field_type() == MYSQL_TYPE_VARCHAR)
+ {
+ Field *field;
+ if (item->max_length > MAX_FIELD_VARCHARLENGTH)
+ field= new Field_blob(item->max_length, item->maybe_null,
+ item->name, item->collation.collation);
+ else
+ field= new Field_varstring(item->max_length, item->maybe_null,
+ item->name,
+ table->s, item->collation.collation);
+ if (field)
+ field->init(table);
+ return field;
+ }
+ return item->tmp_table_field_from_field_type(table, 0);
+}
+
+
+/**
+ Create field for temporary table.
+
+ @param thd Thread handler
+ @param table Temporary table
+ @param item Item to create a field for
+ @param type Type of item (normally item->type)
+ @param copy_func If set and item is a function, store copy of item
+ in this array
+ @param from_field if field will be created using other field as example,
+ pointer example field will be written here
+ @param default_field If field has a default value field, store it here
+ @param group 1 if we are going to do a relative group by on result
+ @param modify_item 1 if item->result_field should point to new item.
+ This is relevent for how fill_record() is going to
+ work:
+ If modify_item is 1 then fill_record() will update
+ the record in the original table.
+ If modify_item is 0 then fill_record() will update
+ the temporary table
+ @param convert_blob_length If >0 create a varstring(convert_blob_length)
+ field instead of blob.
+
+ @retval
+ 0 on error
+ @retval
+ new_created field
+*/
+
+Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type,
+ Item ***copy_func, Field **from_field,
+ Field **default_field,
+ bool group, bool modify_item,
+ bool table_cant_handle_bit_fields,
+ bool make_copy_field,
+ uint convert_blob_length)
+{
+ Field *result;
+ Item::Type orig_type= type;
+ Item *orig_item= 0;
+
+ if (type != Item::FIELD_ITEM &&
+ item->real_item()->type() == Item::FIELD_ITEM)
+ {
+ orig_item= item;
+ item= item->real_item();
+ type= Item::FIELD_ITEM;
+ }
+
+ switch (type) {
+ case Item::SUM_FUNC_ITEM:
+ {
+ Item_sum *item_sum=(Item_sum*) item;
+ result= item_sum->create_tmp_field(group, table, convert_blob_length);
+ if (!result)
+ my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+ return result;
+ }
+ case Item::FIELD_ITEM:
+ case Item::DEFAULT_VALUE_ITEM:
+ case Item::INSERT_VALUE_ITEM:
+ {
+ Item_field *field= (Item_field*) item;
+ bool orig_modify= modify_item;
+ if (orig_type == Item::REF_ITEM)
+ modify_item= 0;
+ /*
+ If item have to be able to store NULLs but underlaid field can't do it,
+ create_tmp_field_from_field() can't be used for tmp field creation.
+ */
+ if (((field->maybe_null && field->in_rollup) ||
+ (thd->create_tmp_table_for_derived && /* for mat. view/dt */
+ orig_item && orig_item->maybe_null)) &&
+ !field->field->maybe_null())
+ {
+ bool save_maybe_null= FALSE;
+ /*
+ The item the ref points to may have maybe_null flag set while
+ the ref doesn't have it. This may happen for outer fields
+ when the outer query decided at some point after name resolution phase
+ that this field might be null. Take this into account here.
+ */
+ if (orig_item)
+ {
+ save_maybe_null= item->maybe_null;
+ item->maybe_null= orig_item->maybe_null;
+ }
+ result= create_tmp_field_from_item(thd, item, table, NULL,
+ modify_item, convert_blob_length);
+ *from_field= field->field;
+ if (result && modify_item)
+ field->result_field= result;
+ if (orig_item)
+ item->maybe_null= save_maybe_null;
+ }
+ else if (table_cant_handle_bit_fields && field->field->type() ==
+ MYSQL_TYPE_BIT)
+ {
+ *from_field= field->field;
+ result= create_tmp_field_from_item(thd, item, table, copy_func,
+ modify_item, convert_blob_length);
+ if (result && modify_item)
+ field->result_field= result;
+ }
+ else
+ result= create_tmp_field_from_field(thd, (*from_field= field->field),
+ orig_item ? orig_item->name :
+ item->name,
+ table,
+ modify_item ? field :
+ NULL,
+ convert_blob_length);
+ if (orig_type == Item::REF_ITEM && orig_modify)
+ ((Item_ref*)orig_item)->set_result_field(result);
+ /*
+ Fields that are used as arguments to the DEFAULT() function already have
+ their data pointers set to the default value during name resolution. See
+ Item_default_value::fix_fields.
+ */
+ if (orig_type != Item::DEFAULT_VALUE_ITEM && field->field->eq_def(result))
+ *default_field= field->field;
+ return result;
+ }
+ /* Fall through */
+ case Item::FUNC_ITEM:
+ if (((Item_func *) item)->functype() == Item_func::FUNC_SP)
+ {
+ Item_func_sp *item_func_sp= (Item_func_sp *) item;
+ Field *sp_result_field= item_func_sp->get_sp_result_field();
+
+ if (make_copy_field)
+ {
+ DBUG_ASSERT(item_func_sp->result_field);
+ *from_field= item_func_sp->result_field;
+ }
+ else
+ {
+ *((*copy_func)++)= item;
+ }
+
+ Field *result_field=
+ create_tmp_field_from_field(thd,
+ sp_result_field,
+ item_func_sp->name,
+ table,
+ NULL,
+ convert_blob_length);
+
+ if (modify_item)
+ item->set_result_field(result_field);
+
+ return result_field;
+ }
+
+ /* Fall through */
+ case Item::COND_ITEM:
+ case Item::FIELD_AVG_ITEM:
+ case Item::FIELD_STD_ITEM:
+ case Item::SUBSELECT_ITEM:
+ /* The following can only happen with 'CREATE TABLE ... SELECT' */
+ case Item::PROC_ITEM:
+ case Item::INT_ITEM:
+ case Item::REAL_ITEM:
+ case Item::DECIMAL_ITEM:
+ case Item::STRING_ITEM:
+ case Item::DATE_ITEM:
+ case Item::REF_ITEM:
+ case Item::NULL_ITEM:
+ case Item::VARBIN_ITEM:
+ case Item::CACHE_ITEM:
+ case Item::EXPR_CACHE_ITEM:
+ if (make_copy_field)
+ {
+ DBUG_ASSERT(((Item_result_field*)item)->result_field);
+ *from_field= ((Item_result_field*)item)->result_field;
+ }
+ return create_tmp_field_from_item(thd, item, table,
+ (make_copy_field ? 0 : copy_func),
+ modify_item, convert_blob_length);
+ case Item::TYPE_HOLDER:
+ result= ((Item_type_holder *)item)->make_field_by_type(table);
+ result->set_derivation(item->collation.derivation);
+ return result;
+ default: // Dosen't have to be stored
+ return 0;
+ }
+}
+
+/*
+ Set up column usage bitmaps for a temporary table
+
+ IMPLEMENTATION
+ For temporary tables, we need one bitmap with all columns set and
+ a tmp_set bitmap to be used by things like filesort.
+*/
+
+void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps)
+{
+ uint field_count= table->s->fields;
+ my_bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count,
+ FALSE);
+ my_bitmap_init(&table->def_vcol_set,
+ (my_bitmap_map*) (bitmaps+ bitmap_buffer_size(field_count)),
+ field_count, FALSE);
+ my_bitmap_init(&table->tmp_set,
+ (my_bitmap_map*) (bitmaps+ 2*bitmap_buffer_size(field_count)),
+ field_count, FALSE);
+ my_bitmap_init(&table->eq_join_set,
+ (my_bitmap_map*) (bitmaps+ 3*bitmap_buffer_size(field_count)),
+ field_count, FALSE);
+ my_bitmap_init(&table->cond_set,
+ (my_bitmap_map*) (bitmaps+ 4*bitmap_buffer_size(field_count)),
+ field_count, FALSE);
+ /* write_set and all_set are copies of read_set */
+ table->def_write_set= table->def_read_set;
+ table->s->all_set= table->def_read_set;
+ bitmap_set_all(&table->s->all_set);
+ table->default_column_bitmaps();
+}
+
+
+/**
+ Create a temp table according to a field list.
+
+ Given field pointers are changed to point at tmp_table for
+ send_result_set_metadata. The table object is self contained: it's
+ allocated in its own memory root, as well as Field objects
+ created for table columns.
+ This function will replace Item_sum items in 'fields' list with
+ corresponding Item_field items, pointing at the fields in the
+ temporary table, unless this was prohibited by TRUE
+ value of argument save_sum_fields. The Item_field objects
+ are created in THD memory root.
+
+ @param thd thread handle
+ @param param a description used as input to create the table
+ @param fields list of items that will be used to define
+ column types of the table (also see NOTES)
+ @param group TODO document
+ @param distinct should table rows be distinct
+ @param save_sum_fields see NOTES
+ @param select_options
+ @param rows_limit
+ @param table_alias possible name of the temporary table that can
+ be used for name resolving; can be "".
+*/
+
+TABLE *
+create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields,
+ ORDER *group, bool distinct, bool save_sum_fields,
+ ulonglong select_options, ha_rows rows_limit,
+ const char *table_alias, bool do_not_open,
+ bool keep_row_order)
+{
+ MEM_ROOT *mem_root_save, own_root;
+ TABLE *table;
+ TABLE_SHARE *share;
+ uint i,field_count,null_count,null_pack_length;
+ uint copy_func_count= param->func_count;
+ uint hidden_null_count, hidden_null_pack_length, hidden_field_count;
+ uint blob_count,group_null_items, string_count;
+ uint temp_pool_slot=MY_BIT_NONE;
+ uint fieldnr= 0;
+ ulong reclength, string_total_length;
+ bool using_unique_constraint= 0;
+ bool use_packed_rows= 0;
+ bool not_all_columns= !(select_options & TMP_TABLE_ALL_COLUMNS);
+ char *tmpname,path[FN_REFLEN];
+ uchar *pos, *group_buff, *bitmaps;
+ uchar *null_flags;
+ Field **reg_field, **from_field, **default_field;
+ uint *blob_field;
+ Copy_field *copy=0;
+ KEY *keyinfo;
+ KEY_PART_INFO *key_part_info;
+ Item **copy_func;
+ TMP_ENGINE_COLUMNDEF *recinfo;
+ /*
+ total_uneven_bit_length is uneven bit length for visible fields
+ hidden_uneven_bit_length is uneven bit length for hidden fields
+ */
+ uint total_uneven_bit_length= 0, hidden_uneven_bit_length= 0;
+ bool force_copy_fields= param->force_copy_fields;
+ /* Treat sum functions as normal ones when loose index scan is used. */
+ save_sum_fields|= param->precomputed_group_by;
+ DBUG_ENTER("create_tmp_table");
+ DBUG_PRINT("enter",
+ ("table_alias: '%s' distinct: %d save_sum_fields: %d "
+ "rows_limit: %lu group: %d", table_alias,
+ (int) distinct, (int) save_sum_fields,
+ (ulong) rows_limit, MY_TEST(group)));
+
+ thd->query_plan_flags|= QPLAN_TMP_TABLE;
+
+ if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES))
+ temp_pool_slot = bitmap_lock_set_next(&temp_pool);
+
+ if (temp_pool_slot != MY_BIT_NONE) // we got a slot
+ sprintf(path, "%s_%lx_%i", tmp_file_prefix,
+ current_pid, temp_pool_slot);
+ else
+ {
+ /* if we run out of slots or we are not using tempool */
+ sprintf(path, "%s%lx_%lx_%x", tmp_file_prefix,current_pid,
+ thd->thread_id, thd->tmp_table++);
+ }
+
+ /*
+ No need to change table name to lower case as we are only creating
+ MyISAM, Aria or HEAP tables here
+ */
+ fn_format(path, path, mysql_tmpdir, "",
+ MY_REPLACE_EXT|MY_UNPACK_FILENAME);
+
+ if (group)
+ {
+ ORDER **prev= &group;
+ if (!param->quick_group)
+ group=0; // Can't use group key
+ else for (ORDER *tmp=group ; tmp ; tmp=tmp->next)
+ {
+ /* Exclude found constant from the list */
+ if ((*tmp->item)->const_item())
+ {
+ *prev= tmp->next;
+ param->group_parts--;
+ continue;
+ }
+ else
+ prev= &(tmp->next);
+ /*
+ marker == 4 means two things:
+ - store NULLs in the key, and
+ - convert BIT fields to 64-bit long, needed because MEMORY tables
+ can't index BIT fields.
+ */
+ (*tmp->item)->marker=4; // Store null in key
+ if ((*tmp->item)->too_big_for_varchar())
+ using_unique_constraint=1;
+ }
+ if (param->group_length >= MAX_BLOB_WIDTH)
+ using_unique_constraint=1;
+ if (group)
+ distinct=0; // Can't use distinct
+ }
+
+ field_count=param->field_count+param->func_count+param->sum_func_count;
+ hidden_field_count=param->hidden_field_count;
+
+ /*
+ When loose index scan is employed as access method, it already
+ computes all groups and the result of all aggregate functions. We
+ make space for the items of the aggregate function in the list of
+ functions TMP_TABLE_PARAM::items_to_copy, so that the values of
+ these items are stored in the temporary table.
+ */
+ if (param->precomputed_group_by)
+ copy_func_count+= param->sum_func_count;
+
+ init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC));
+
+ if (!multi_alloc_root(&own_root,
+ &table, sizeof(*table),
+ &share, sizeof(*share),
+ &reg_field, sizeof(Field*) * (field_count+1),
+ &default_field, sizeof(Field*) * (field_count),
+ &blob_field, sizeof(uint)*(field_count+1),
+ &from_field, sizeof(Field*)*field_count,
+ &copy_func, sizeof(*copy_func)*(copy_func_count+1),
+ &param->keyinfo, sizeof(*param->keyinfo),
+ &key_part_info,
+ sizeof(*key_part_info)*(param->group_parts+1),
+ &param->start_recinfo,
+ sizeof(*param->recinfo)*(field_count*2+4),
+ &tmpname, (uint) strlen(path)+1,
+ &group_buff, (group && ! using_unique_constraint ?
+ param->group_length : 0),
+ &bitmaps, bitmap_buffer_size(field_count)*5,
+ NullS))
+ {
+ if (temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+ DBUG_RETURN(NULL); /* purecov: inspected */
+ }
+ /* Copy_field belongs to TMP_TABLE_PARAM, allocate it in THD mem_root */
+ if (!(param->copy_field= copy= new (thd->mem_root) Copy_field[field_count]))
+ {
+ if (temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+ free_root(&own_root, MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(NULL); /* purecov: inspected */
+ }
+ param->items_to_copy= copy_func;
+ strmov(tmpname, path);
+ /* make table according to fields */
+
+ bzero((char*) table,sizeof(*table));
+ bzero((char*) reg_field,sizeof(Field*)*(field_count+1));
+ bzero((char*) default_field, sizeof(Field*) * (field_count));
+ bzero((char*) from_field,sizeof(Field*)*field_count);
+
+ table->mem_root= own_root;
+ mem_root_save= thd->mem_root;
+ thd->mem_root= &table->mem_root;
+
+ table->field=reg_field;
+ table->alias.set(table_alias, strlen(table_alias), table_alias_charset);
+
+ table->reginfo.lock_type=TL_WRITE; /* Will be updated */
+ table->map=1;
+ table->temp_pool_slot = temp_pool_slot;
+ table->copy_blobs= 1;
+ table->in_use= thd;
+ table->quick_keys.init();
+ table->covering_keys.init();
+ table->merge_keys.init();
+ table->intersect_keys.init();
+ table->keys_in_use_for_query.init();
+ table->no_rows_with_nulls= param->force_not_null_cols;
+
+ table->s= share;
+ init_tmp_table_share(thd, share, "", 0, tmpname, tmpname);
+ share->blob_field= blob_field;
+ share->table_charset= param->table_charset;
+ share->primary_key= MAX_KEY; // Indicate no primary key
+ share->keys_for_keyread.init();
+ share->keys_in_use.init();
+ if (param->schema_table)
+ share->db= INFORMATION_SCHEMA_NAME;
+
+ /* Calculate which type of fields we will store in the temporary table */
+
+ reclength= string_total_length= 0;
+ blob_count= string_count= null_count= hidden_null_count= group_null_items= 0;
+ param->using_indirect_summary_function=0;
+
+ List_iterator_fast<Item> li(fields);
+ Item *item;
+ Field **tmp_from_field=from_field;
+ while ((item=li++))
+ {
+ Item::Type type=item->type();
+ if (not_all_columns)
+ {
+ if (item->with_sum_func && type != Item::SUM_FUNC_ITEM)
+ {
+ if (item->used_tables() & OUTER_REF_TABLE_BIT)
+ item->update_used_tables();
+ if ((item->real_type() == Item::SUBSELECT_ITEM) ||
+ (item->used_tables() & ~OUTER_REF_TABLE_BIT))
+ {
+ /*
+ Mark that the we have ignored an item that refers to a summary
+ function. We need to know this if someone is going to use
+ DISTINCT on the result.
+ */
+ param->using_indirect_summary_function=1;
+ continue;
+ }
+ }
+ if (item->const_item() && (int) hidden_field_count <= 0)
+ continue; // We don't have to store this
+ }
+ if (type == Item::SUM_FUNC_ITEM && !group && !save_sum_fields)
+ { /* Can't calc group yet */
+ Item_sum *sum_item= (Item_sum *) item;
+ sum_item->result_field=0;
+ for (i=0 ; i < sum_item->get_arg_count() ; i++)
+ {
+ Item *arg= sum_item->get_arg(i);
+ if (!arg->const_item())
+ {
+ Field *new_field=
+ create_tmp_field(thd, table, arg, arg->type(), &copy_func,
+ tmp_from_field, &default_field[fieldnr],
+ group != 0,not_all_columns,
+ distinct, 0,
+ param->convert_blob_length);
+ if (!new_field)
+ goto err; // Should be OOM
+ tmp_from_field++;
+ reclength+=new_field->pack_length();
+ if (new_field->flags & BLOB_FLAG)
+ {
+ *blob_field++= fieldnr;
+ blob_count++;
+ }
+ if (new_field->type() == MYSQL_TYPE_BIT)
+ total_uneven_bit_length+= new_field->field_length & 7;
+ *(reg_field++)= new_field;
+ if (new_field->real_type() == MYSQL_TYPE_STRING ||
+ new_field->real_type() == MYSQL_TYPE_VARCHAR)
+ {
+ string_count++;
+ string_total_length+= new_field->pack_length();
+ }
+ thd->mem_root= mem_root_save;
+ arg= sum_item->set_arg(i, thd, new Item_field(new_field));
+ thd->mem_root= &table->mem_root;
+ if (param->force_not_null_cols)
+ {
+ new_field->flags|= NOT_NULL_FLAG;
+ new_field->null_ptr= NULL;
+ }
+ if (!(new_field->flags & NOT_NULL_FLAG))
+ {
+ null_count++;
+ /*
+ new_field->maybe_null() is still false, it will be
+ changed below. But we have to setup Item_field correctly
+ */
+ arg->maybe_null=1;
+ }
+ new_field->field_index= fieldnr++;
+ }
+ }
+ }
+ else
+ {
+ /*
+ The last parameter to create_tmp_field() is a bit tricky:
+
+ We need to set it to 0 in union, to get fill_record() to modify the
+ temporary table.
+ We need to set it to 1 on multi-table-update and in select to
+ write rows to the temporary table.
+ We here distinguish between UNION and multi-table-updates by the fact
+ that in the later case group is set to the row pointer.
+
+ The test for item->marker == 4 is ensure we don't create a group-by
+ key over a bit field as heap tables can't handle that.
+ */
+ Field *new_field= (param->schema_table) ?
+ create_tmp_field_for_schema(thd, item, table) :
+ create_tmp_field(thd, table, item, type, &copy_func,
+ tmp_from_field, &default_field[fieldnr],
+ group != 0,
+ !force_copy_fields &&
+ (not_all_columns || group !=0),
+ /*
+ If item->marker == 4 then we force create_tmp_field
+ to create a 64-bit longs for BIT fields because HEAP
+ tables can't index BIT fields directly. We do the same
+ for distinct, as we want the distinct index to be
+ usable in this case too.
+ */
+ item->marker == 4 || param->bit_fields_as_long,
+ force_copy_fields,
+ param->convert_blob_length);
+
+ if (!new_field)
+ {
+ if (thd->is_fatal_error)
+ goto err; // Got OOM
+ continue; // Some kind of const item
+ }
+ if (type == Item::SUM_FUNC_ITEM)
+ {
+ Item_sum *agg_item= (Item_sum *) item;
+ /*
+ Update the result field only if it has never been set, or if the
+ created temporary table is not to be used for subquery
+ materialization.
+
+ The reason is that for subqueries that require materialization as part
+ of their plan, we create the 'external' temporary table needed for IN
+ execution, after the 'internal' temporary table needed for grouping.
+ Since both the external and the internal temporary tables are created
+ for the same list of SELECT fields of the subquery, setting
+ 'result_field' for each invocation of create_tmp_table overrides the
+ previous value of 'result_field'.
+
+ The condition below prevents the creation of the external temp table
+ to override the 'result_field' that was set for the internal temp table.
+ */
+ if (!agg_item->result_field || !param->materialized_subquery)
+ agg_item->result_field= new_field;
+ }
+ tmp_from_field++;
+ if (param->force_not_null_cols)
+ {
+ new_field->flags|= NOT_NULL_FLAG;
+ new_field->null_ptr= NULL;
+ }
+ reclength+=new_field->pack_length();
+ if (!(new_field->flags & NOT_NULL_FLAG))
+ null_count++;
+ if (new_field->type() == MYSQL_TYPE_BIT)
+ total_uneven_bit_length+= new_field->field_length & 7;
+ if (new_field->flags & BLOB_FLAG)
+ {
+ *blob_field++= fieldnr;
+ blob_count++;
+ }
+ if (new_field->real_type() == MYSQL_TYPE_STRING ||
+ new_field->real_type() == MYSQL_TYPE_VARCHAR)
+ {
+ string_count++;
+ string_total_length+= new_field->pack_length();
+ }
+ if (item->marker == 4 && item->maybe_null)
+ {
+ group_null_items++;
+ new_field->flags|= GROUP_FLAG;
+ }
+ new_field->field_index= fieldnr++;
+ *(reg_field++)= new_field;
+ }
+ if (!--hidden_field_count)
+ {
+ /*
+ This was the last hidden field; Remember how many hidden fields could
+ have null
+ */
+ hidden_null_count=null_count;
+ /*
+ We need to update hidden_field_count as we may have stored group
+ functions with constant arguments
+ */
+ param->hidden_field_count= fieldnr;
+ null_count= 0;
+ /*
+ On last hidden field we store uneven bit length in
+ hidden_uneven_bit_length and proceed calculation of
+ uneven bits for visible fields into
+ total_uneven_bit_length variable.
+ */
+ hidden_uneven_bit_length= total_uneven_bit_length;
+ total_uneven_bit_length= 0;
+ }
+ }
+ DBUG_ASSERT(fieldnr == (uint) (reg_field - table->field));
+ DBUG_ASSERT(field_count >= (uint) (reg_field - table->field));
+ field_count= fieldnr;
+ *reg_field= 0;
+ *blob_field= 0; // End marker
+ share->fields= field_count;
+ share->column_bitmap_size= bitmap_buffer_size(share->fields);
+
+ /* If result table is small; use a heap */
+ /* future: storage engine selection can be made dynamic? */
+ if (blob_count || using_unique_constraint
+ || (thd->variables.big_tables && !(select_options & SELECT_SMALL_RESULT))
+ || (select_options & TMP_TABLE_FORCE_MYISAM)
+ || thd->variables.tmp_table_size == 0)
+ {
+ share->db_plugin= ha_lock_engine(0, TMP_ENGINE_HTON);
+ table->file= get_new_handler(share, &table->mem_root,
+ share->db_type());
+ if (group &&
+ (param->group_parts > table->file->max_key_parts() ||
+ param->group_length > table->file->max_key_length()))
+ using_unique_constraint=1;
+ }
+ else
+ {
+ share->db_plugin= ha_lock_engine(0, heap_hton);
+ table->file= get_new_handler(share, &table->mem_root,
+ share->db_type());
+ }
+ if (!table->file)
+ goto err;
+
+ if (table->file->set_ha_share_ref(&share->ha_share))
+ {
+ delete table->file;
+ goto err;
+ }
+
+ if (!using_unique_constraint)
+ reclength+= group_null_items; // null flag is stored separately
+
+ share->blob_fields= blob_count;
+ if (blob_count == 0)
+ {
+ /* We need to ensure that first byte is not 0 for the delete link */
+ if (param->hidden_field_count)
+ hidden_null_count++;
+ else
+ null_count++;
+ }
+ hidden_null_pack_length= (hidden_null_count + 7 +
+ hidden_uneven_bit_length) / 8;
+ null_pack_length= (hidden_null_pack_length +
+ (null_count + total_uneven_bit_length + 7) / 8);
+ reclength+=null_pack_length;
+ if (!reclength)
+ reclength=1; // Dummy select
+ /* Use packed rows if there is blobs or a lot of space to gain */
+ if (blob_count ||
+ (string_total_length >= STRING_TOTAL_LENGTH_TO_PACK_ROWS &&
+ (reclength / string_total_length <= RATIO_TO_PACK_ROWS ||
+ string_total_length / string_count >= AVG_STRING_LENGTH_TO_PACK_ROWS)))
+ use_packed_rows= 1;
+
+ share->reclength= reclength;
+ {
+ uint alloc_length=ALIGN_SIZE(reclength+MI_UNIQUE_HASH_LENGTH+1);
+ share->rec_buff_length= alloc_length;
+ if (!(table->record[0]= (uchar*)
+ alloc_root(&table->mem_root, alloc_length*3)))
+ goto err;
+ table->record[1]= table->record[0]+alloc_length;
+ share->default_values= table->record[1]+alloc_length;
+ }
+ copy_func[0]=0; // End marker
+ param->func_count= copy_func - param->items_to_copy;
+
+ setup_tmp_table_column_bitmaps(table, bitmaps);
+
+ recinfo=param->start_recinfo;
+ null_flags=(uchar*) table->record[0];
+ pos=table->record[0]+ null_pack_length;
+ if (null_pack_length)
+ {
+ bzero((uchar*) recinfo,sizeof(*recinfo));
+ recinfo->type=FIELD_NORMAL;
+ recinfo->length=null_pack_length;
+ recinfo++;
+ bfill(null_flags,null_pack_length,255); // Set null fields
+
+ table->null_flags= (uchar*) table->record[0];
+ share->null_fields= null_count+ hidden_null_count;
+ share->null_bytes= share->null_bytes_for_compare= null_pack_length;
+ }
+ null_count= (blob_count == 0) ? 1 : 0;
+ hidden_field_count=param->hidden_field_count;
+ for (i=0,reg_field=table->field; i < field_count; i++,reg_field++,recinfo++)
+ {
+ Field *field= *reg_field;
+ uint length;
+ bzero((uchar*) recinfo,sizeof(*recinfo));
+
+ if (!(field->flags & NOT_NULL_FLAG))
+ {
+ recinfo->null_bit= (uint8)1 << (null_count & 7);
+ recinfo->null_pos= null_count/8;
+ field->move_field(pos,null_flags+null_count/8,
+ (uint8)1 << (null_count & 7));
+ null_count++;
+ }
+ else
+ field->move_field(pos,(uchar*) 0,0);
+ if (field->type() == MYSQL_TYPE_BIT)
+ {
+ /* We have to reserve place for extra bits among null bits */
+ ((Field_bit*) field)->set_bit_ptr(null_flags + null_count / 8,
+ null_count & 7);
+ null_count+= (field->field_length & 7);
+ }
+ field->reset();
+
+ /*
+ Test if there is a default field value. The test for ->ptr is to skip
+ 'offset' fields generated by initalize_tables
+ */
+ if (default_field[i] && default_field[i]->ptr)
+ {
+ /*
+ default_field[i] is set only in the cases when 'field' can
+ inherit the default value that is defined for the field referred
+ by the Item_field object from which 'field' has been created.
+ */
+ my_ptrdiff_t diff;
+ Field *orig_field= default_field[i];
+ /* Get the value from default_values */
+ diff= (my_ptrdiff_t) (orig_field->table->s->default_values-
+ orig_field->table->record[0]);
+ orig_field->move_field_offset(diff); // Points now at default_values
+ if (orig_field->is_real_null())
+ field->set_null();
+ else
+ {
+ field->set_notnull();
+ memcpy(field->ptr, orig_field->ptr, field->pack_length());
+ }
+ orig_field->move_field_offset(-diff); // Back to record[0]
+ }
+
+ if (from_field[i])
+ { /* Not a table Item */
+ copy->set(field,from_field[i],save_sum_fields);
+ copy++;
+ }
+ length=field->pack_length();
+ pos+= length;
+
+ /* Make entry for create table */
+ recinfo->length=length;
+ if (field->flags & BLOB_FLAG)
+ recinfo->type= FIELD_BLOB;
+ else if (use_packed_rows &&
+ field->real_type() == MYSQL_TYPE_STRING &&
+ length >= MIN_STRING_LENGTH_TO_PACK_ROWS)
+ recinfo->type= FIELD_SKIP_ENDSPACE;
+ else if (field->real_type() == MYSQL_TYPE_VARCHAR)
+ recinfo->type= FIELD_VARCHAR;
+ else
+ recinfo->type= FIELD_NORMAL;
+
+ if (!--hidden_field_count)
+ null_count=(null_count+7) & ~7; // move to next byte
+
+ // fix table name in field entry
+ field->set_table_name(&table->alias);
+ }
+
+ param->copy_field_end=copy;
+ param->recinfo= recinfo; // Pointer to after last field
+ store_record(table,s->default_values); // Make empty default record
+
+ if (thd->variables.tmp_table_size == ~ (ulonglong) 0) // No limit
+ share->max_rows= ~(ha_rows) 0;
+ else
+ share->max_rows= (ha_rows) (((share->db_type() == heap_hton) ?
+ MY_MIN(thd->variables.tmp_table_size,
+ thd->variables.max_heap_table_size) :
+ thd->variables.tmp_table_size) /
+ share->reclength);
+ set_if_bigger(share->max_rows,1); // For dummy start options
+ /*
+ Push the LIMIT clause to the temporary table creation, so that we
+ materialize only up to 'rows_limit' records instead of all result records.
+ */
+ set_if_smaller(share->max_rows, rows_limit);
+ param->end_write_records= rows_limit;
+
+ keyinfo= param->keyinfo;
+
+ if (group)
+ {
+ DBUG_PRINT("info",("Creating group key in temporary table"));
+ table->group=group; /* Table is grouped by key */
+ param->group_buff=group_buff;
+ share->keys=1;
+ share->uniques= MY_TEST(using_unique_constraint);
+ table->key_info= table->s->key_info= keyinfo;
+ table->keys_in_use_for_query.set_bit(0);
+ share->keys_in_use.set_bit(0);
+ keyinfo->key_part=key_part_info;
+ keyinfo->flags=HA_NOSAME | HA_BINARY_PACK_KEY | HA_PACK_KEY;
+ keyinfo->ext_key_flags= keyinfo->flags;
+ keyinfo->usable_key_parts=keyinfo->user_defined_key_parts= param->group_parts;
+ keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
+ keyinfo->key_length=0;
+ keyinfo->rec_per_key=NULL;
+ keyinfo->read_stats= NULL;
+ keyinfo->collected_stats= NULL;
+ keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+ keyinfo->is_statistics_from_stat_tables= FALSE;
+ keyinfo->name= (char*) "group_key";
+ ORDER *cur_group= group;
+ for (; cur_group ; cur_group= cur_group->next, key_part_info++)
+ {
+ Field *field=(*cur_group->item)->get_tmp_table_field();
+ DBUG_ASSERT(field->table == table);
+ bool maybe_null=(*cur_group->item)->maybe_null;
+ key_part_info->null_bit=0;
+ key_part_info->field= field;
+ key_part_info->fieldnr= field->field_index + 1;
+ if (cur_group == group)
+ field->key_start.set_bit(0);
+ key_part_info->offset= field->offset(table->record[0]);
+ key_part_info->length= (uint16) field->key_length();
+ key_part_info->type= (uint8) field->key_type();
+ key_part_info->key_type =
+ ((ha_base_keytype) key_part_info->type == HA_KEYTYPE_TEXT ||
+ (ha_base_keytype) key_part_info->type == HA_KEYTYPE_VARTEXT1 ||
+ (ha_base_keytype) key_part_info->type == HA_KEYTYPE_VARTEXT2) ?
+ 0 : FIELDFLAG_BINARY;
+ key_part_info->key_part_flag= 0;
+ if (!using_unique_constraint)
+ {
+ cur_group->buff=(char*) group_buff;
+
+ if (maybe_null && !field->null_bit)
+ {
+ /*
+ This can only happen in the unusual case where an outer join
+ table was found to be not-nullable by the optimizer and we
+ the item can't really be null.
+ We solve this by marking the item as !maybe_null to ensure
+ that the key,field and item definition match.
+ */
+ (*cur_group->item)->maybe_null= maybe_null= 0;
+ }
+
+ if (!(cur_group->field= field->new_key_field(thd->mem_root,table,
+ group_buff +
+ MY_TEST(maybe_null),
+ key_part_info->length,
+ field->null_ptr,
+ field->null_bit)))
+ goto err; /* purecov: inspected */
+
+ if (maybe_null)
+ {
+ /*
+ To be able to group on NULL, we reserved place in group_buff
+ for the NULL flag just before the column. (see above).
+ The field data is after this flag.
+ The NULL flag is updated in 'end_update()' and 'end_write()'
+ */
+ keyinfo->flags|= HA_NULL_ARE_EQUAL; // def. that NULL == NULL
+ key_part_info->null_bit=field->null_bit;
+ key_part_info->null_offset= (uint) (field->null_ptr -
+ (uchar*) table->record[0]);
+ cur_group->buff++; // Pointer to field data
+ group_buff++; // Skipp null flag
+ }
+ /* In GROUP BY 'a' and 'a ' are equal for VARCHAR fields */
+ key_part_info->key_part_flag|= HA_END_SPACE_ARE_EQUAL;
+ group_buff+= cur_group->field->pack_length();
+ }
+ keyinfo->key_length+= key_part_info->length;
+ }
+ /*
+ Ensure we didn't overrun the group buffer. The < is only true when
+ some maybe_null fields was changed to be not null fields.
+ */
+ DBUG_ASSERT(using_unique_constraint ||
+ group_buff <= param->group_buff + param->group_length);
+ }
+
+ if (distinct && field_count != param->hidden_field_count)
+ {
+ /*
+ Create an unique key or an unique constraint over all columns
+ that should be in the result. In the temporary table, there are
+ 'param->hidden_field_count' extra columns, whose null bits are stored
+ in the first 'hidden_null_pack_length' bytes of the row.
+ */
+ DBUG_PRINT("info",("hidden_field_count: %d", param->hidden_field_count));
+
+ if (blob_count)
+ {
+ /*
+ Special mode for index creation in MyISAM used to support unique
+ indexes on blobs with arbitrary length. Such indexes cannot be
+ used for lookups.
+ */
+ share->uniques= 1;
+ }
+ null_pack_length-=hidden_null_pack_length;
+ keyinfo->user_defined_key_parts=
+ ((field_count-param->hidden_field_count)+
+ (share->uniques ? MY_TEST(null_pack_length) : 0));
+ keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
+ table->distinct= 1;
+ share->keys= 1;
+ if (!(key_part_info= (KEY_PART_INFO*)
+ alloc_root(&table->mem_root,
+ keyinfo->user_defined_key_parts * sizeof(KEY_PART_INFO))))
+ goto err;
+ bzero((void*) key_part_info, keyinfo->user_defined_key_parts * sizeof(KEY_PART_INFO));
+ table->keys_in_use_for_query.set_bit(0);
+ share->keys_in_use.set_bit(0);
+ table->key_info= table->s->key_info= keyinfo;
+ keyinfo->key_part=key_part_info;
+ keyinfo->flags=HA_NOSAME | HA_NULL_ARE_EQUAL | HA_BINARY_PACK_KEY | HA_PACK_KEY;
+ keyinfo->ext_key_flags= keyinfo->flags;
+ keyinfo->key_length= 0; // Will compute the sum of the parts below.
+ keyinfo->name= (char*) "distinct_key";
+ keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+ keyinfo->is_statistics_from_stat_tables= FALSE;
+ keyinfo->read_stats= NULL;
+ keyinfo->collected_stats= NULL;
+
+ /*
+ Needed by non-merged semi-joins: SJ-Materialized table must have a valid
+ rec_per_key array, because it participates in join optimization. Since
+ the table has no data, the only statistics we can provide is "unknown",
+ i.e. zero values.
+
+ (For table record count, we calculate and set JOIN_TAB::found_records,
+ see get_delayed_table_estimates()).
+ */
+ size_t rpk_size= keyinfo->user_defined_key_parts * sizeof(keyinfo->rec_per_key[0]);
+ if (!(keyinfo->rec_per_key= (ulong*) alloc_root(&table->mem_root,
+ rpk_size)))
+ goto err;
+ bzero(keyinfo->rec_per_key, rpk_size);
+
+ /*
+ Create an extra field to hold NULL bits so that unique indexes on
+ blobs can distinguish NULL from 0. This extra field is not needed
+ when we do not use UNIQUE indexes for blobs.
+ */
+ if (null_pack_length && share->uniques)
+ {
+ key_part_info->null_bit=0;
+ key_part_info->offset=hidden_null_pack_length;
+ key_part_info->length=null_pack_length;
+ key_part_info->field= new Field_string(table->record[0],
+ (uint32) key_part_info->length,
+ (uchar*) 0,
+ (uint) 0,
+ Field::NONE,
+ NullS, &my_charset_bin);
+ if (!key_part_info->field)
+ goto err;
+ key_part_info->field->init(table);
+ key_part_info->key_type=FIELDFLAG_BINARY;
+ key_part_info->type= HA_KEYTYPE_BINARY;
+ key_part_info->fieldnr= key_part_info->field->field_index + 1;
+ key_part_info++;
+ }
+ /* Create a distinct key over the columns we are going to return */
+ for (i=param->hidden_field_count, reg_field=table->field + i ;
+ i < field_count;
+ i++, reg_field++, key_part_info++)
+ {
+ key_part_info->field= *reg_field;
+ (*reg_field)->flags |= PART_KEY_FLAG;
+ if (key_part_info == keyinfo->key_part)
+ (*reg_field)->key_start.set_bit(0);
+ key_part_info->null_bit= (*reg_field)->null_bit;
+ key_part_info->null_offset= (uint) ((*reg_field)->null_ptr -
+ (uchar*) table->record[0]);
+
+ key_part_info->offset= (*reg_field)->offset(table->record[0]);
+ key_part_info->length= (uint16) (*reg_field)->pack_length();
+ key_part_info->fieldnr= (*reg_field)->field_index + 1;
+ /* TODO:
+ The below method of computing the key format length of the
+ key part is a copy/paste from opt_range.cc, and table.cc.
+ This should be factored out, e.g. as a method of Field.
+ In addition it is not clear if any of the Field::*_length
+ methods is supposed to compute the same length. If so, it
+ might be reused.
+ */
+ key_part_info->store_length= key_part_info->length;
+
+ if ((*reg_field)->real_maybe_null())
+ {
+ key_part_info->store_length+= HA_KEY_NULL_LENGTH;
+ key_part_info->key_part_flag |= HA_NULL_PART;
+ }
+ if ((*reg_field)->type() == MYSQL_TYPE_BLOB ||
+ (*reg_field)->real_type() == MYSQL_TYPE_VARCHAR ||
+ (*reg_field)->type() == MYSQL_TYPE_GEOMETRY)
+ {
+ if ((*reg_field)->type() == MYSQL_TYPE_BLOB ||
+ (*reg_field)->type() == MYSQL_TYPE_GEOMETRY)
+ key_part_info->key_part_flag|= HA_BLOB_PART;
+ else
+ key_part_info->key_part_flag|= HA_VAR_LENGTH_PART;
+
+ key_part_info->store_length+=HA_KEY_BLOB_LENGTH;
+ }
+
+ keyinfo->key_length+= key_part_info->store_length;
+
+ key_part_info->type= (uint8) (*reg_field)->key_type();
+ key_part_info->key_type =
+ ((ha_base_keytype) key_part_info->type == HA_KEYTYPE_TEXT ||
+ (ha_base_keytype) key_part_info->type == HA_KEYTYPE_VARTEXT1 ||
+ (ha_base_keytype) key_part_info->type == HA_KEYTYPE_VARTEXT2) ?
+ 0 : FIELDFLAG_BINARY;
+ }
+ }
+
+ if (thd->is_fatal_error) // If end of memory
+ goto err; /* purecov: inspected */
+ share->db_record_offset= 1;
+ table->used_for_duplicate_elimination= (param->sum_func_count == 0 &&
+ (table->group || table->distinct));
+ table->keep_row_order= keep_row_order;
+
+ if (!do_not_open)
+ {
+ if (share->db_type() == TMP_ENGINE_HTON)
+ {
+ if (create_internal_tmp_table(table, param->keyinfo, param->start_recinfo,
+ &param->recinfo, select_options))
+ goto err;
+ }
+ if (open_tmp_table(table))
+ goto err;
+ }
+
+ // Make empty record so random data is not written to disk
+ empty_record(table);
+
+ thd->mem_root= mem_root_save;
+
+ DBUG_RETURN(table);
+
+err:
+ thd->mem_root= mem_root_save;
+ free_tmp_table(thd,table); /* purecov: inspected */
+ if (temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+ DBUG_RETURN(NULL); /* purecov: inspected */
+}
+
+
+
+/****************************************************************************/
+
+/**
+ Create a reduced TABLE object with properly set up Field list from a
+ list of field definitions.
+
+ The created table doesn't have a table handler associated with
+ it, has no keys, no group/distinct, no copy_funcs array.
+ The sole purpose of this TABLE object is to use the power of Field
+ class to read/write data to/from table->record[0]. Then one can store
+ the record in any container (RB tree, hash, etc).
+ The table is created in THD mem_root, so are the table's fields.
+ Consequently, if you don't BLOB fields, you don't need to free it.
+
+ @param thd connection handle
+ @param field_list list of column definitions
+
+ @return
+ 0 if out of memory, TABLE object in case of success
+*/
+
+TABLE *create_virtual_tmp_table(THD *thd, List<Create_field> &field_list)
+{
+ uint field_count= field_list.elements;
+ uint blob_count= 0;
+ Field **field;
+ Create_field *cdef; /* column definition */
+ uint record_length= 0;
+ uint null_count= 0; /* number of columns which may be null */
+ uint null_pack_length; /* NULL representation array length */
+ uint *blob_field;
+ uchar *bitmaps;
+ TABLE *table;
+ TABLE_SHARE *share;
+
+ if (!multi_alloc_root(thd->mem_root,
+ &table, sizeof(*table),
+ &share, sizeof(*share),
+ &field, (field_count + 1) * sizeof(Field*),
+ &blob_field, (field_count+1) *sizeof(uint),
+ &bitmaps, bitmap_buffer_size(field_count)*5,
+ NullS))
+ return 0;
+
+ bzero(table, sizeof(*table));
+ bzero(share, sizeof(*share));
+ table->field= field;
+ table->s= share;
+ table->temp_pool_slot= MY_BIT_NONE;
+ share->blob_field= blob_field;
+ share->fields= field_count;
+ setup_tmp_table_column_bitmaps(table, bitmaps);
+
+ /* Create all fields and calculate the total length of record */
+ List_iterator_fast<Create_field> it(field_list);
+ while ((cdef= it++))
+ {
+ *field= make_field(share, 0, cdef->length,
+ (uchar*) (f_maybe_null(cdef->pack_flag) ? "" : 0),
+ f_maybe_null(cdef->pack_flag) ? 1 : 0,
+ cdef->pack_flag, cdef->sql_type, cdef->charset,
+ cdef->geom_type, cdef->unireg_check,
+ cdef->interval, cdef->field_name);
+ if (!*field)
+ goto error;
+ (*field)->init(table);
+ record_length+= (*field)->pack_length();
+ if (! ((*field)->flags & NOT_NULL_FLAG))
+ null_count++;
+
+ if ((*field)->flags & BLOB_FLAG)
+ share->blob_field[blob_count++]= (uint) (field - table->field);
+
+ field++;
+ }
+ *field= NULL; /* mark the end of the list */
+ share->blob_field[blob_count]= 0; /* mark the end of the list */
+ share->blob_fields= blob_count;
+
+ null_pack_length= (null_count + 7)/8;
+ share->reclength= record_length + null_pack_length;
+ share->rec_buff_length= ALIGN_SIZE(share->reclength + 1);
+ table->record[0]= (uchar*) thd->alloc(share->rec_buff_length);
+ if (!table->record[0])
+ goto error;
+
+ if (null_pack_length)
+ {
+ table->null_flags= (uchar*) table->record[0];
+ share->null_fields= null_count;
+ share->null_bytes= share->null_bytes_for_compare= null_pack_length;
+ }
+
+ table->in_use= thd; /* field->reset() may access table->in_use */
+ {
+ /* Set up field pointers */
+ uchar *null_pos= table->record[0];
+ uchar *field_pos= null_pos + share->null_bytes;
+ uint null_bit= 1;
+
+ for (field= table->field; *field; ++field)
+ {
+ Field *cur_field= *field;
+ if ((cur_field->flags & NOT_NULL_FLAG))
+ cur_field->move_field(field_pos);
+ else
+ {
+ cur_field->move_field(field_pos, (uchar*) null_pos, null_bit);
+ null_bit<<= 1;
+ if (null_bit == (uint)1 << 8)
+ {
+ ++null_pos;
+ null_bit= 1;
+ }
+ }
+ if (cur_field->type() == MYSQL_TYPE_BIT &&
+ cur_field->key_type() == HA_KEYTYPE_BIT)
+ {
+ /* This is a Field_bit since key_type is HA_KEYTYPE_BIT */
+ static_cast<Field_bit*>(cur_field)->set_bit_ptr(null_pos, null_bit);
+ null_bit+= cur_field->field_length & 7;
+ if (null_bit > 7)
+ {
+ null_pos++;
+ null_bit-= 8;
+ }
+ }
+ cur_field->reset();
+
+ field_pos+= cur_field->pack_length();
+ }
+ }
+ return table;
+error:
+ for (field= table->field; *field; ++field)
+ delete *field; /* just invokes field destructor */
+ return 0;
+}
+
+
+bool open_tmp_table(TABLE *table)
+{
+ int error;
+ if ((error= table->file->ha_open(table, table->s->table_name.str, O_RDWR,
+ HA_OPEN_TMP_TABLE |
+ HA_OPEN_INTERNAL_TABLE)))
+ {
+ table->file->print_error(error, MYF(0)); /* purecov: inspected */
+ table->db_stat= 0;
+ return 1;
+ }
+ table->db_stat= HA_OPEN_KEYFILE+HA_OPEN_RNDFILE;
+ (void) table->file->extra(HA_EXTRA_QUICK); /* Faster */
+ if (!table->created)
+ {
+ table->created= TRUE;
+ table->in_use->inc_status_created_tmp_tables();
+ }
+
+ return 0;
+}
+
+
+#ifdef USE_ARIA_FOR_TMP_TABLES
+/*
+ Create internal (MyISAM or Maria) temporary table
+
+ SYNOPSIS
+ create_internal_tmp_table()
+ table Table object that descrimes the table to be created
+ keyinfo Description of the index (there is always one index)
+ start_recinfo engine's column descriptions
+ recinfo INOUT End of engine's column descriptions
+ options Option bits
+
+ DESCRIPTION
+ Create an internal emporary table according to passed description. The is
+ assumed to have one unique index or constraint.
+
+ The passed array or TMP_ENGINE_COLUMNDEF structures must have this form:
+
+ 1. 1-byte column (afaiu for 'deleted' flag) (note maybe not 1-byte
+ when there are many nullable columns)
+ 2. Table columns
+ 3. One free TMP_ENGINE_COLUMNDEF element (*recinfo points here)
+
+ This function may use the free element to create hash column for unique
+ constraint.
+
+ RETURN
+ FALSE - OK
+ TRUE - Error
+*/
+
+
+bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
+ TMP_ENGINE_COLUMNDEF *start_recinfo,
+ TMP_ENGINE_COLUMNDEF **recinfo,
+ ulonglong options)
+{
+ int error;
+ MARIA_KEYDEF keydef;
+ MARIA_UNIQUEDEF uniquedef;
+ TABLE_SHARE *share= table->s;
+ MARIA_CREATE_INFO create_info;
+ DBUG_ENTER("create_internal_tmp_table");
+
+ if (share->keys)
+ { // Get keys for ni_create
+ bool using_unique_constraint=0;
+ HA_KEYSEG *seg= (HA_KEYSEG*) alloc_root(&table->mem_root,
+ sizeof(*seg) * keyinfo->user_defined_key_parts);
+ if (!seg)
+ goto err;
+
+ bzero(seg, sizeof(*seg) * keyinfo->user_defined_key_parts);
+ if (keyinfo->key_length > table->file->max_key_length() ||
+ keyinfo->user_defined_key_parts > table->file->max_key_parts() ||
+ share->uniques)
+ {
+ if (!share->uniques && !(keyinfo->flags & HA_NOSAME))
+ {
+ my_error(ER_INTERNAL_ERROR, MYF(0),
+ "Using too big key for internal temp tables");
+ DBUG_RETURN(1);
+ }
+
+ /* Can't create a key; Make a unique constraint instead of a key */
+ share->keys= 0;
+ share->uniques= 1;
+ using_unique_constraint=1;
+ bzero((char*) &uniquedef,sizeof(uniquedef));
+ uniquedef.keysegs=keyinfo->user_defined_key_parts;
+ uniquedef.seg=seg;
+ uniquedef.null_are_equal=1;
+
+ /* Create extra column for hash value */
+ bzero((uchar*) *recinfo,sizeof(**recinfo));
+ (*recinfo)->type= FIELD_CHECK;
+ (*recinfo)->length= MARIA_UNIQUE_HASH_LENGTH;
+ (*recinfo)++;
+ share->reclength+= MARIA_UNIQUE_HASH_LENGTH;
+ }
+ else
+ {
+ /* Create a key */
+ bzero((char*) &keydef,sizeof(keydef));
+ keydef.flag= keyinfo->flags & HA_NOSAME;
+ keydef.keysegs= keyinfo->user_defined_key_parts;
+ keydef.seg= seg;
+ }
+ for (uint i=0; i < keyinfo->user_defined_key_parts ; i++,seg++)
+ {
+ Field *field=keyinfo->key_part[i].field;
+ seg->flag= 0;
+ seg->language= field->charset()->number;
+ seg->length= keyinfo->key_part[i].length;
+ seg->start= keyinfo->key_part[i].offset;
+ if (field->flags & BLOB_FLAG)
+ {
+ seg->type=
+ ((keyinfo->key_part[i].key_type & FIELDFLAG_BINARY) ?
+ HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2);
+ seg->bit_start= (uint8)(field->pack_length() -
+ portable_sizeof_char_ptr);
+ seg->flag= HA_BLOB_PART;
+ seg->length=0; // Whole blob in unique constraint
+ }
+ else
+ {
+ seg->type= keyinfo->key_part[i].type;
+ /* Tell handler if it can do suffic space compression */
+ if (field->real_type() == MYSQL_TYPE_STRING &&
+ keyinfo->key_part[i].length > 32)
+ seg->flag|= HA_SPACE_PACK;
+ }
+ if (!(field->flags & NOT_NULL_FLAG))
+ {
+ seg->null_bit= field->null_bit;
+ seg->null_pos= (uint) (field->null_ptr - (uchar*) table->record[0]);
+ /*
+ We are using a GROUP BY on something that contains NULL
+ In this case we have to tell Aria that two NULL should
+ on INSERT be regarded at the same value
+ */
+ if (!using_unique_constraint)
+ keydef.flag|= HA_NULL_ARE_EQUAL;
+ }
+ }
+ }
+ bzero((char*) &create_info,sizeof(create_info));
+
+ /* Use long data format, to ensure we never get a 'table is full' error */
+ if (!(options & SELECT_SMALL_RESULT))
+ create_info.data_file_length= ~(ulonglong) 0;
+
+ /*
+ The logic for choosing the record format:
+ The STATIC_RECORD format is the fastest one, because it's so simple,
+ so we use this by default for short rows.
+ BLOCK_RECORD caches both row and data, so this is generally faster than
+ DYNAMIC_RECORD. The one exception is when we write to tmp table and
+ want to use keys for duplicate elimination as with BLOCK RECORD
+ we first write the row, then check for key conflicts and then we have to
+ delete the row. The cases when this can happen is when there is
+ a group by and no sum functions or if distinct is used.
+ */
+ if ((error= maria_create(share->table_name.str,
+ table->no_rows ? NO_RECORD :
+ (share->reclength < 64 &&
+ !share->blob_fields ? STATIC_RECORD :
+ table->used_for_duplicate_elimination ||
+ table->keep_row_order ?
+ DYNAMIC_RECORD : BLOCK_RECORD),
+ share->keys, &keydef,
+ (uint) (*recinfo-start_recinfo),
+ start_recinfo,
+ share->uniques, &uniquedef,
+ &create_info,
+ HA_CREATE_TMP_TABLE | HA_CREATE_INTERNAL_TABLE)))
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ table->db_stat=0;
+ goto err;
+ }
+ table->in_use->inc_status_created_tmp_disk_tables();
+ table->in_use->inc_status_created_tmp_tables();
+ table->in_use->query_plan_flags|= QPLAN_TMP_DISK;
+ share->db_record_offset= 1;
+ table->created= TRUE;
+ DBUG_RETURN(0);
+ err:
+ DBUG_RETURN(1);
+}
+
+#else
+
+/*
+ Create internal (MyISAM or Maria) temporary table
+
+ SYNOPSIS
+ create_internal_tmp_table()
+ table Table object that descrimes the table to be created
+ keyinfo Description of the index (there is always one index)
+ start_recinfo engine's column descriptions
+ recinfo INOUT End of engine's column descriptions
+ options Option bits
+
+ DESCRIPTION
+ Create an internal emporary table according to passed description. The is
+ assumed to have one unique index or constraint.
+
+ The passed array or TMP_ENGINE_COLUMNDEF structures must have this form:
+
+ 1. 1-byte column (afaiu for 'deleted' flag) (note maybe not 1-byte
+ when there are many nullable columns)
+ 2. Table columns
+ 3. One free TMP_ENGINE_COLUMNDEF element (*recinfo points here)
+
+ This function may use the free element to create hash column for unique
+ constraint.
+
+ RETURN
+ FALSE - OK
+ TRUE - Error
+*/
+
+/* Create internal MyISAM temporary table */
+
+bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
+ TMP_ENGINE_COLUMNDEF *start_recinfo,
+ TMP_ENGINE_COLUMNDEF **recinfo,
+ ulonglong options)
+{
+ int error;
+ MI_KEYDEF keydef;
+ MI_UNIQUEDEF uniquedef;
+ TABLE_SHARE *share= table->s;
+ DBUG_ENTER("create_internal_tmp_table");
+
+ if (share->keys)
+ { // Get keys for ni_create
+ bool using_unique_constraint=0;
+ HA_KEYSEG *seg= (HA_KEYSEG*) alloc_root(&table->mem_root,
+ sizeof(*seg) * keyinfo->user_defined_key_parts);
+ if (!seg)
+ goto err;
+
+ bzero(seg, sizeof(*seg) * keyinfo->user_defined_key_parts);
+ if (keyinfo->key_length > table->file->max_key_length() ||
+ keyinfo->user_defined_key_parts > table->file->max_key_parts() ||
+ share->uniques)
+ {
+ /* Can't create a key; Make a unique constraint instead of a key */
+ share->keys= 0;
+ share->uniques= 1;
+ using_unique_constraint=1;
+ bzero((char*) &uniquedef,sizeof(uniquedef));
+ uniquedef.keysegs=keyinfo->user_defined_key_parts;
+ uniquedef.seg=seg;
+ uniquedef.null_are_equal=1;
+
+ /* Create extra column for hash value */
+ bzero((uchar*) *recinfo,sizeof(**recinfo));
+ (*recinfo)->type= FIELD_CHECK;
+ (*recinfo)->length=MI_UNIQUE_HASH_LENGTH;
+ (*recinfo)++;
+ share->reclength+=MI_UNIQUE_HASH_LENGTH;
+ }
+ else
+ {
+ /* Create an unique key */
+ bzero((char*) &keydef,sizeof(keydef));
+ keydef.flag= ((keyinfo->flags & HA_NOSAME) | HA_BINARY_PACK_KEY |
+ HA_PACK_KEY);
+ keydef.keysegs= keyinfo->user_defined_key_parts;
+ keydef.seg= seg;
+ }
+ for (uint i=0; i < keyinfo->user_defined_key_parts ; i++,seg++)
+ {
+ Field *field=keyinfo->key_part[i].field;
+ seg->flag= 0;
+ seg->language= field->charset()->number;
+ seg->length= keyinfo->key_part[i].length;
+ seg->start= keyinfo->key_part[i].offset;
+ if (field->flags & BLOB_FLAG)
+ {
+ seg->type=
+ ((keyinfo->key_part[i].key_type & FIELDFLAG_BINARY) ?
+ HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2);
+ seg->bit_start= (uint8)(field->pack_length() - portable_sizeof_char_ptr);
+ seg->flag= HA_BLOB_PART;
+ seg->length=0; // Whole blob in unique constraint
+ }
+ else
+ {
+ seg->type= keyinfo->key_part[i].type;
+ /* Tell handler if it can do suffic space compression */
+ if (field->real_type() == MYSQL_TYPE_STRING &&
+ keyinfo->key_part[i].length > 4)
+ seg->flag|= HA_SPACE_PACK;
+ }
+ if (!(field->flags & NOT_NULL_FLAG))
+ {
+ seg->null_bit= field->null_bit;
+ seg->null_pos= (uint) (field->null_ptr - (uchar*) table->record[0]);
+ /*
+ We are using a GROUP BY on something that contains NULL
+ In this case we have to tell MyISAM that two NULL should
+ on INSERT be regarded at the same value
+ */
+ if (!using_unique_constraint)
+ keydef.flag|= HA_NULL_ARE_EQUAL;
+ }
+ }
+ }
+ MI_CREATE_INFO create_info;
+ bzero((char*) &create_info,sizeof(create_info));
+
+ if (!(options & SELECT_SMALL_RESULT))
+ create_info.data_file_length= ~(ulonglong) 0;
+
+ if ((error=mi_create(share->table_name.str, share->keys, &keydef,
+ (uint) (*recinfo-start_recinfo),
+ start_recinfo,
+ share->uniques, &uniquedef,
+ &create_info,
+ HA_CREATE_TMP_TABLE | HA_CREATE_INTERNAL_TABLE)))
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ table->db_stat=0;
+ goto err;
+ }
+ table->in_use->inc_status_created_tmp_disk_tables();
+ table->in_use->inc_status_created_tmp_tables();
+ table->in_use->query_plan_flags|= QPLAN_TMP_DISK;
+ share->db_record_offset= 1;
+ table->created= TRUE;
+ DBUG_RETURN(0);
+ err:
+ DBUG_RETURN(1);
+}
+
+#endif /* USE_ARIA_FOR_TMP_TABLES */
+
+
+/*
+ If a HEAP table gets full, create a internal table in MyISAM or Maria
+ and copy all rows to this
+*/
+
+
+bool
+create_internal_tmp_table_from_heap(THD *thd, TABLE *table,
+ TMP_ENGINE_COLUMNDEF *start_recinfo,
+ TMP_ENGINE_COLUMNDEF **recinfo,
+ int error,
+ bool ignore_last_dupp_key_error,
+ bool *is_duplicate)
+{
+ TABLE new_table;
+ TABLE_SHARE share;
+ const char *save_proc_info;
+ int write_err= 0;
+ DBUG_ENTER("create_internal_tmp_table_from_heap");
+ if (is_duplicate)
+ *is_duplicate= FALSE;
+
+ if (table->s->db_type() != heap_hton ||
+ error != HA_ERR_RECORD_FILE_FULL)
+ {
+ /*
+ We don't want this error to be converted to a warning, e.g. in case of
+ INSERT IGNORE ... SELECT.
+ */
+ table->file->print_error(error, MYF(ME_FATALERROR));
+ DBUG_RETURN(1);
+ }
+ new_table= *table;
+ share= *table->s;
+ new_table.s= &share;
+ new_table.s->db_plugin= ha_lock_engine(thd, TMP_ENGINE_HTON);
+ if (!(new_table.file= get_new_handler(&share, &new_table.mem_root,
+ new_table.s->db_type())))
+ DBUG_RETURN(1); // End of memory
+
+ if (new_table.file->set_ha_share_ref(&share.ha_share))
+ {
+ delete new_table.file;
+ DBUG_RETURN(1);
+ }
+
+ save_proc_info=thd->proc_info;
+ THD_STAGE_INFO(thd, stage_converting_heap_to_myisam);
+
+ new_table.no_rows= table->no_rows;
+ if (create_internal_tmp_table(&new_table, table->key_info, start_recinfo,
+ recinfo,
+ thd->lex->select_lex.options |
+ thd->variables.option_bits))
+ goto err2;
+ if (open_tmp_table(&new_table))
+ goto err1;
+ if (table->file->indexes_are_disabled())
+ new_table.file->ha_disable_indexes(HA_KEY_SWITCH_ALL);
+ table->file->ha_index_or_rnd_end();
+ if (table->file->ha_rnd_init_with_error(1))
+ DBUG_RETURN(1);
+ if (new_table.no_rows)
+ new_table.file->extra(HA_EXTRA_NO_ROWS);
+ else
+ {
+ /* update table->file->stats.records */
+ table->file->info(HA_STATUS_VARIABLE);
+ new_table.file->ha_start_bulk_insert(table->file->stats.records);
+ }
+
+ /*
+ copy all old rows from heap table to MyISAM table
+ This is the only code that uses record[1] to read/write but this
+ is safe as this is a temporary MyISAM table without timestamp/autoincrement
+ or partitioning.
+ */
+ while (!table->file->ha_rnd_next(new_table.record[1]))
+ {
+ write_err= new_table.file->ha_write_tmp_row(new_table.record[1]);
+ DBUG_EXECUTE_IF("raise_error", write_err= HA_ERR_FOUND_DUPP_KEY ;);
+ if (write_err)
+ goto err;
+ if (thd->check_killed())
+ {
+ thd->send_kill_message();
+ goto err_killed;
+ }
+ }
+ if (!new_table.no_rows && new_table.file->ha_end_bulk_insert())
+ goto err;
+ /* copy row that filled HEAP table */
+ if ((write_err=new_table.file->ha_write_tmp_row(table->record[0])))
+ {
+ if (new_table.file->is_fatal_error(write_err, HA_CHECK_DUP) ||
+ !ignore_last_dupp_key_error)
+ goto err;
+ if (is_duplicate)
+ *is_duplicate= TRUE;
+ }
+ else
+ {
+ if (is_duplicate)
+ *is_duplicate= FALSE;
+ }
+
+ /* remove heap table and change to use myisam table */
+ (void) table->file->ha_rnd_end();
+ (void) table->file->ha_close(); // This deletes the table !
+ delete table->file;
+ table->file=0;
+ plugin_unlock(0, table->s->db_plugin);
+ share.db_plugin= my_plugin_lock(0, share.db_plugin);
+ new_table.s= table->s; // Keep old share
+ *table= new_table;
+ *table->s= share;
+
+ table->file->change_table_ptr(table, table->s);
+ table->use_all_columns();
+ if (save_proc_info)
+ thd_proc_info(thd, (!strcmp(save_proc_info,"Copying to tmp table") ?
+ "Copying to tmp table on disk" : save_proc_info));
+ DBUG_RETURN(0);
+
+ err:
+ DBUG_PRINT("error",("Got error: %d",write_err));
+ table->file->print_error(write_err, MYF(0));
+err_killed:
+ (void) table->file->ha_rnd_end();
+ (void) new_table.file->ha_close();
+ err1:
+ new_table.file->ha_delete_table(new_table.s->table_name.str);
+ err2:
+ delete new_table.file;
+ thd_proc_info(thd, save_proc_info);
+ table->mem_root= new_table.mem_root;
+ DBUG_RETURN(1);
+}
+
+
+void
+free_tmp_table(THD *thd, TABLE *entry)
+{
+ MEM_ROOT own_root= entry->mem_root;
+ const char *save_proc_info;
+ DBUG_ENTER("free_tmp_table");
+ DBUG_PRINT("enter",("table: %s alias: %s",entry->s->table_name.str,
+ entry->alias.c_ptr()));
+
+ save_proc_info=thd->proc_info;
+ THD_STAGE_INFO(thd, stage_removing_tmp_table);
+
+ if (entry->file && entry->created)
+ {
+ entry->file->ha_index_or_rnd_end();
+ if (entry->db_stat)
+ entry->file->ha_drop_table(entry->s->table_name.str);
+ else
+ entry->file->ha_delete_table(entry->s->table_name.str);
+ delete entry->file;
+ }
+
+ /* free blobs */
+ for (Field **ptr=entry->field ; *ptr ; ptr++)
+ (*ptr)->free();
+ free_io_cache(entry);
+
+ if (entry->temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, entry->temp_pool_slot);
+
+ plugin_unlock(0, entry->s->db_plugin);
+ entry->alias.free();
+
+ free_root(&own_root, MYF(0)); /* the table is allocated in its own root */
+ thd_proc_info(thd, save_proc_info);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @details
+ Rows produced by a join sweep may end up in a temporary table or be sent
+ to a client. Setup the function of the nested loop join algorithm which
+ handles final fully constructed and matched records.
+
+ @param join join to setup the function for.
+
+ @return
+ end_select function to use. This function can't fail.
+*/
+
+Next_select_func setup_end_select_func(JOIN *join)
+{
+ TABLE *table= join->tmp_table;
+ TMP_TABLE_PARAM *tmp_tbl= &join->tmp_table_param;
+ Next_select_func end_select;
+
+ /* Set up select_end */
+ if (table)
+ {
+ if (table->group && tmp_tbl->sum_func_count &&
+ !tmp_tbl->precomputed_group_by)
+ {
+ if (table->s->keys)
+ {
+ DBUG_PRINT("info",("Using end_update"));
+ end_select=end_update;
+ }
+ else
+ {
+ DBUG_PRINT("info",("Using end_unique_update"));
+ end_select=end_unique_update;
+ }
+ }
+ else if (join->sort_and_group && !tmp_tbl->precomputed_group_by)
+ {
+ DBUG_PRINT("info",("Using end_write_group"));
+ end_select=end_write_group;
+ }
+ else
+ {
+ DBUG_PRINT("info",("Using end_write"));
+ end_select=end_write;
+ if (tmp_tbl->precomputed_group_by)
+ {
+ /*
+ A preceding call to create_tmp_table in the case when loose
+ index scan is used guarantees that
+ TMP_TABLE_PARAM::items_to_copy has enough space for the group
+ by functions. It is OK here to use memcpy since we copy
+ Item_sum pointers into an array of Item pointers.
+ */
+ memcpy(tmp_tbl->items_to_copy + tmp_tbl->func_count,
+ join->sum_funcs,
+ sizeof(Item*)*tmp_tbl->sum_func_count);
+ tmp_tbl->items_to_copy[tmp_tbl->func_count+tmp_tbl->sum_func_count]= 0;
+ }
+ }
+ }
+ else
+ {
+ /*
+ Choose method for presenting result to user. Use end_send_group
+ if the query requires grouping (has a GROUP BY clause and/or one or
+ more aggregate functions). Use end_send if the query should not
+ be grouped.
+ */
+ if ((join->sort_and_group ||
+ (join->procedure && join->procedure->flags & PROC_GROUP)) &&
+ !tmp_tbl->precomputed_group_by)
+ end_select= end_send_group;
+ else
+ end_select= end_send;
+ }
+ return end_select;
+}
+
+
+/**
+ Make a join of all tables and write it on socket or to table.
+
+ @retval
+ 0 if ok
+ @retval
+ 1 if error is sent
+ @retval
+ -1 if error should be sent
+*/
+static int
+do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
+{
+ int rc= 0;
+ enum_nested_loop_state error= NESTED_LOOP_OK;
+ JOIN_TAB *join_tab;
+ DBUG_ENTER("do_select");
+ LINT_INIT(join_tab);
+
+ join->procedure=procedure;
+ join->tmp_table= table; /* Save for easy recursion */
+ join->fields= fields;
+
+ if (table)
+ {
+ (void) table->file->extra(HA_EXTRA_WRITE_CACHE);
+ empty_record(table);
+ if (table->group && join->tmp_table_param.sum_func_count &&
+ table->s->keys && !table->file->inited)
+ {
+ rc= table->file->ha_index_init(0, 0);
+ if (rc)
+ {
+ table->file->print_error(rc, MYF(0));
+ DBUG_RETURN(-1);
+ }
+ }
+ }
+ /* Set up select_end */
+ Next_select_func end_select= setup_end_select_func(join);
+ if (join->table_count)
+ {
+ join->join_tab[join->top_join_tab_count - 1].next_select= end_select;
+ join_tab=join->join_tab+join->const_tables;
+ }
+ join->send_records=0;
+ if (join->table_count == join->const_tables)
+ {
+ /*
+ HAVING will be checked after processing aggregate functions,
+ But WHERE should checked here (we alredy have read tables).
+ Notice that make_join_select() splits all conditions in this case
+ into two groups exec_const_cond and outer_ref_cond.
+ If join->table_count == join->const_tables then it is
+ sufficient to check only the condition pseudo_bits_cond.
+ */
+ DBUG_ASSERT(join->outer_ref_cond == NULL);
+ if (!join->pseudo_bits_cond || join->pseudo_bits_cond->val_int())
+ {
+ error= (*end_select)(join, 0, 0);
+ if (error == NESTED_LOOP_OK || error == NESTED_LOOP_QUERY_LIMIT)
+ error= (*end_select)(join, 0, 1);
+
+ /*
+ If we don't go through evaluate_join_record(), do the counting
+ here. join->send_records is increased on success in end_send(),
+ so we don't touch it here.
+ */
+ join->examined_rows++;
+ DBUG_ASSERT(join->examined_rows <= 1);
+ }
+ else if (join->send_row_on_empty_set())
+ {
+ if (!join->having || join->having->val_int())
+ {
+ List<Item> *columns_list= (procedure ? &join->procedure_fields_list :
+ fields);
+ rc= join->result->send_data(*columns_list) > 0;
+ }
+ }
+ /*
+ An error can happen when evaluating the conds
+ (the join condition and piece of where clause
+ relevant to this join table).
+ */
+ if (join->thd->is_error())
+ error= NESTED_LOOP_ERROR;
+ }
+ else
+ {
+ DBUG_ASSERT(join->table_count);
+
+ DBUG_EXECUTE_IF("show_explain_probe_do_select",
+ if (dbug_user_var_equals_int(join->thd,
+ "show_explain_probe_select_id",
+ join->select_lex->select_number))
+ dbug_serve_apcs(join->thd, 1);
+ );
+
+ if (join->outer_ref_cond && !join->outer_ref_cond->val_int())
+ error= NESTED_LOOP_NO_MORE_ROWS;
+ else
+ error= sub_select(join,join_tab,0);
+ if ((error == NESTED_LOOP_OK || error == NESTED_LOOP_NO_MORE_ROWS) &&
+ join->thd->killed != ABORT_QUERY)
+ error= sub_select(join,join_tab,1);
+ if (error == NESTED_LOOP_QUERY_LIMIT)
+ error= NESTED_LOOP_OK; /* select_limit used */
+ }
+ if (error == NESTED_LOOP_NO_MORE_ROWS || join->thd->killed == ABORT_QUERY)
+ error= NESTED_LOOP_OK;
+
+ if (table)
+ {
+ int tmp, new_errno= 0;
+ if ((tmp=table->file->extra(HA_EXTRA_NO_CACHE)))
+ {
+ DBUG_PRINT("error",("extra(HA_EXTRA_NO_CACHE) failed"));
+ new_errno= tmp;
+ }
+ if ((tmp=table->file->ha_index_or_rnd_end()))
+ {
+ DBUG_PRINT("error",("ha_index_or_rnd_end() failed"));
+ new_errno= tmp;
+ }
+ if (new_errno)
+ table->file->print_error(new_errno,MYF(0));
+ }
+ else
+ {
+ /*
+ The following will unlock all cursors if the command wasn't an
+ update command
+ */
+ join->join_free(); // Unlock all cursors
+ }
+ if (error == NESTED_LOOP_OK)
+ {
+ /*
+ Sic: this branch works even if rc != 0, e.g. when
+ send_data above returns an error.
+ */
+ if (!table) // If sending data to client
+ {
+ if (join->result->send_eof())
+ rc= 1; // Don't send error
+ }
+ DBUG_PRINT("info",("%ld records output", (long) join->send_records));
+ }
+ else
+ rc= -1;
+#ifndef DBUG_OFF
+ if (rc)
+ {
+ DBUG_PRINT("error",("Error: do_select() failed"));
+ }
+#endif
+ DBUG_RETURN(join->thd->is_error() ? -1 : rc);
+}
+
+
+int rr_sequential_and_unpack(READ_RECORD *info)
+{
+ int error;
+ if ((error= rr_sequential(info)))
+ return error;
+
+ for (Copy_field *cp= info->copy_field; cp != info->copy_field_end; cp++)
+ (*cp->do_copy)(cp);
+
+ return error;
+}
+
+
+/*
+ Fill the join buffer with partial records, retrieve all full matches for them
+
+ SYNOPSIS
+ sub_select_cache()
+ join pointer to the structure providing all context info for the query
+ join_tab the first next table of the execution plan to be retrieved
+ end_records true when we need to perform final steps of the retrieval
+
+ DESCRIPTION
+ For a given table Ti= join_tab from the sequence of tables of the chosen
+ execution plan T1,...,Ti,...,Tn the function just put the partial record
+ t1,...,t[i-1] into the join buffer associated with table Ti unless this
+ is the last record added into the buffer. In this case, the function
+ additionally finds all matching full records for all partial
+ records accumulated in the buffer, after which it cleans the buffer up.
+ If a partial join record t1,...,ti is extended utilizing a dynamic
+ range scan then it is not put into the join buffer. Rather all matching
+ records are found for it at once by the function sub_select.
+
+ NOTES
+ The function implements the algorithmic schema for both Blocked Nested
+ Loop Join and Batched Key Access Join. The difference can be seen only at
+ the level of of the implementation of the put_record and join_records
+ virtual methods for the cache object associated with the join_tab.
+ The put_record method accumulates records in the cache, while the
+ join_records method builds all matching join records and send them into
+ the output stream.
+
+ RETURN
+ return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS.
+*/
+
+enum_nested_loop_state
+sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records)
+{
+ enum_nested_loop_state rc;
+ JOIN_CACHE *cache= join_tab->cache;
+ DBUG_ENTER("sub_select_cache");
+
+ /*
+ This function cannot be called if join_tab has no associated join
+ buffer
+ */
+ DBUG_ASSERT(cache != NULL);
+
+ join_tab->cache->reset_join(join);
+
+ if (end_of_records)
+ {
+ rc= cache->join_records(FALSE);
+ if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS)
+ rc= sub_select(join, join_tab, end_of_records);
+ DBUG_RETURN(rc);
+ }
+ if (join->thd->check_killed())
+ {
+ /* The user has aborted the execution of the query */
+ join->thd->send_kill_message();
+ DBUG_RETURN(NESTED_LOOP_KILLED);
+ }
+ if (!test_if_use_dynamic_range_scan(join_tab))
+ {
+ if (!cache->put_record())
+ DBUG_RETURN(NESTED_LOOP_OK);
+ /*
+ We has decided that after the record we've just put into the buffer
+ won't add any more records. Now try to find all the matching
+ extensions for all records in the buffer.
+ */
+ rc= cache->join_records(FALSE);
+ DBUG_RETURN(rc);
+ }
+ /*
+ TODO: Check whether we really need the call below and we can't do
+ without it. If it's not the case remove it.
+ */
+ rc= cache->join_records(TRUE);
+ if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS)
+ rc= sub_select(join, join_tab, end_of_records);
+ DBUG_RETURN(rc);
+}
+
+/**
+ Retrieve records ends with a given beginning from the result of a join.
+
+ For a given partial join record consisting of records from the tables
+ preceding the table join_tab in the execution plan, the function
+ retrieves all matching full records from the result set and
+ send them to the result set stream.
+
+ @note
+ The function effectively implements the final (n-k) nested loops
+ of nested loops join algorithm, where k is the ordinal number of
+ the join_tab table and n is the total number of tables in the join query.
+ It performs nested loops joins with all conjunctive predicates from
+ the where condition pushed as low to the tables as possible.
+ E.g. for the query
+ @code
+ SELECT * FROM t1,t2,t3
+ WHERE t1.a=t2.a AND t2.b=t3.b AND t1.a BETWEEN 5 AND 9
+ @endcode
+ the predicate (t1.a BETWEEN 5 AND 9) will be pushed to table t1,
+ given the selected plan prescribes to nest retrievals of the
+ joined tables in the following order: t1,t2,t3.
+ A pushed down predicate are attached to the table which it pushed to,
+ at the field join_tab->select_cond.
+ When executing a nested loop of level k the function runs through
+ the rows of 'join_tab' and for each row checks the pushed condition
+ attached to the table.
+ If it is false the function moves to the next row of the
+ table. If the condition is true the function recursively executes (n-k-1)
+ remaining embedded nested loops.
+ The situation becomes more complicated if outer joins are involved in
+ the execution plan. In this case the pushed down predicates can be
+ checked only at certain conditions.
+ Suppose for the query
+ @code
+ SELECT * FROM t1 LEFT JOIN (t2,t3) ON t3.a=t1.a
+ WHERE t1>2 AND (t2.b>5 OR t2.b IS NULL)
+ @endcode
+ the optimizer has chosen a plan with the table order t1,t2,t3.
+ The predicate P1=t1>2 will be pushed down to the table t1, while the
+ predicate P2=(t2.b>5 OR t2.b IS NULL) will be attached to the table
+ t2. But the second predicate can not be unconditionally tested right
+ after a row from t2 has been read. This can be done only after the
+ first row with t3.a=t1.a has been encountered.
+ Thus, the second predicate P2 is supplied with a guarded value that are
+ stored in the field 'found' of the first inner table for the outer join
+ (table t2). When the first row with t3.a=t1.a for the current row
+ of table t1 appears, the value becomes true. For now on the predicate
+ is evaluated immediately after the row of table t2 has been read.
+ When the first row with t3.a=t1.a has been encountered all
+ conditions attached to the inner tables t2,t3 must be evaluated.
+ Only when all of them are true the row is sent to the output stream.
+ If not, the function returns to the lowest nest level that has a false
+ attached condition.
+ The predicates from on expressions are also pushed down. If in the
+ the above example the on expression were (t3.a=t1.a AND t2.a=t1.a),
+ then t1.a=t2.a would be pushed down to table t2, and without any
+ guard.
+ If after the run through all rows of table t2, the first inner table
+ for the outer join operation, it turns out that no matches are
+ found for the current row of t1, then current row from table t1
+ is complemented by nulls for t2 and t3. Then the pushed down predicates
+ are checked for the composed row almost in the same way as it had
+ been done for the first row with a match. The only difference is
+ the predicates from on expressions are not checked.
+
+ @par
+ @b IMPLEMENTATION
+ @par
+ The function forms output rows for a current partial join of k
+ tables tables recursively.
+ For each partial join record ending with a certain row from
+ join_tab it calls sub_select that builds all possible matching
+ tails from the result set.
+ To be able check predicates conditionally items of the class
+ Item_func_trig_cond are employed.
+ An object of this class is constructed from an item of class COND
+ and a pointer to a guarding boolean variable.
+ When the value of the guard variable is true the value of the object
+ is the same as the value of the predicate, otherwise it's just returns
+ true.
+ To carry out a return to a nested loop level of join table t the pointer
+ to t is remembered in the field 'return_tab' of the join structure.
+ Consider the following query:
+ @code
+ SELECT * FROM t1,
+ LEFT JOIN
+ (t2, t3 LEFT JOIN (t4,t5) ON t5.a=t3.a)
+ ON t4.a=t2.a
+ WHERE (t2.b=5 OR t2.b IS NULL) AND (t4.b=2 OR t4.b IS NULL)
+ @endcode
+ Suppose the chosen execution plan dictates the order t1,t2,t3,t4,t5
+ and suppose for a given joined rows from tables t1,t2,t3 there are
+ no rows in the result set yet.
+ When first row from t5 that satisfies the on condition
+ t5.a=t3.a is found, the pushed down predicate t4.b=2 OR t4.b IS NULL
+ becomes 'activated', as well the predicate t4.a=t2.a. But
+ the predicate (t2.b=5 OR t2.b IS NULL) can not be checked until
+ t4.a=t2.a becomes true.
+ In order not to re-evaluate the predicates that were already evaluated
+ as attached pushed down predicates, a pointer to the the first
+ most inner unmatched table is maintained in join_tab->first_unmatched.
+ Thus, when the first row from t5 with t5.a=t3.a is found
+ this pointer for t5 is changed from t4 to t2.
+
+ @par
+ @b STRUCTURE @b NOTES
+ @par
+ join_tab->first_unmatched points always backwards to the first inner
+ table of the embedding nested join, if any.
+
+ @param join pointer to the structure providing all context info for
+ the query
+ @param join_tab the first next table of the execution plan to be retrieved
+ @param end_records true when we need to perform final steps of retrival
+
+ @return
+ return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS.
+*/
+
+enum_nested_loop_state
+sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
+{
+ DBUG_ENTER("sub_select");
+
+ if (join_tab->last_inner)
+ {
+ JOIN_TAB *last_inner_tab= join_tab->last_inner;
+ for (JOIN_TAB *jt= join_tab; jt <= last_inner_tab; jt++)
+ jt->table->null_row= 0;
+ }
+ else
+ join_tab->table->null_row=0;
+
+ if (end_of_records)
+ {
+ enum_nested_loop_state nls=
+ (*join_tab->next_select)(join,join_tab+1,end_of_records);
+ DBUG_RETURN(nls);
+ }
+ int error;
+ enum_nested_loop_state rc= NESTED_LOOP_OK;
+ READ_RECORD *info= &join_tab->read_record;
+
+ for (SJ_TMP_TABLE *flush_dups_table= join_tab->flush_weedout_table;
+ flush_dups_table;
+ flush_dups_table= flush_dups_table->next_flush_table)
+ {
+ flush_dups_table->sj_weedout_delete_rows();
+ }
+
+ if (!join_tab->preread_init_done && join_tab->preread_init())
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+
+ join->return_tab= join_tab;
+
+ if (join_tab->last_inner)
+ {
+ /* join_tab is the first inner table for an outer join operation. */
+
+ /* Set initial state of guard variables for this table.*/
+ join_tab->found=0;
+ join_tab->not_null_compl= 1;
+
+ /* Set first_unmatched for the last inner table of this group */
+ join_tab->last_inner->first_unmatched= join_tab;
+ if (join_tab->on_precond && !join_tab->on_precond->val_int())
+ rc= NESTED_LOOP_NO_MORE_ROWS;
+ }
+ join->thd->get_stmt_da()->reset_current_row_for_warning();
+
+ if (rc != NESTED_LOOP_NO_MORE_ROWS &&
+ (rc= join_tab_execution_startup(join_tab)) < 0)
+ DBUG_RETURN(rc);
+
+ if (join_tab->loosescan_match_tab)
+ join_tab->loosescan_match_tab->found_match= FALSE;
+
+ if (rc != NESTED_LOOP_NO_MORE_ROWS)
+ {
+ error= (*join_tab->read_first_record)(join_tab);
+ if (!error && join_tab->keep_current_rowid)
+ join_tab->table->file->position(join_tab->table->record[0]);
+ rc= evaluate_join_record(join, join_tab, error);
+ }
+
+ /*
+ Note: psergey has added the 2nd part of the following condition; the
+ change should probably be made in 5.1, too.
+ */
+ bool skip_over= FALSE;
+ while (rc == NESTED_LOOP_OK && join->return_tab >= join_tab)
+ {
+ if (join_tab->loosescan_match_tab &&
+ join_tab->loosescan_match_tab->found_match)
+ {
+ KEY *key= join_tab->table->key_info + join_tab->loosescan_key;
+ key_copy(join_tab->loosescan_buf, join_tab->table->record[0], key,
+ join_tab->loosescan_key_len);
+ skip_over= TRUE;
+ }
+
+ error= info->read_record(info);
+
+ if (skip_over && !error)
+ {
+ if(!key_cmp(join_tab->table->key_info[join_tab->loosescan_key].key_part,
+ join_tab->loosescan_buf, join_tab->loosescan_key_len))
+ {
+ /*
+ This is the LooseScan action: skip over records with the same key
+ value if we already had a match for them.
+ */
+ continue;
+ }
+ join_tab->loosescan_match_tab->found_match= FALSE;
+ skip_over= FALSE;
+ }
+
+ if (join_tab->keep_current_rowid)
+ join_tab->table->file->position(join_tab->table->record[0]);
+
+ rc= evaluate_join_record(join, join_tab, error);
+ }
+
+ if (rc == NESTED_LOOP_NO_MORE_ROWS &&
+ join_tab->last_inner && !join_tab->found)
+ rc= evaluate_null_complemented_join_record(join, join_tab);
+
+ if (rc == NESTED_LOOP_NO_MORE_ROWS)
+ rc= NESTED_LOOP_OK;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief Process one row of the nested loop join.
+
+ This function will evaluate parts of WHERE/ON clauses that are
+ applicable to the partial row on hand and in case of success
+ submit this row to the next level of the nested loop.
+
+ @param join - The join object
+ @param join_tab - The most inner join_tab being processed
+ @param error > 0: Error, terminate processing
+ = 0: (Partial) row is available
+ < 0: No more rows available at this level
+ @return Nested loop state (Ok, No_more_rows, Error, Killed)
+*/
+
+static enum_nested_loop_state
+evaluate_join_record(JOIN *join, JOIN_TAB *join_tab,
+ int error)
+{
+ bool shortcut_for_distinct= join_tab->shortcut_for_distinct;
+ ha_rows found_records=join->found_records;
+ COND *select_cond= join_tab->select_cond;
+ bool select_cond_result= TRUE;
+
+ DBUG_ENTER("evaluate_join_record");
+ DBUG_PRINT("enter",
+ ("evaluate_join_record join: %p join_tab: %p"
+ " cond: %p error: %d alias %s",
+ join, join_tab, select_cond, error,
+ join_tab->table->alias.ptr()));
+ if (error > 0 || (join->thd->is_error())) // Fatal error
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ if (error < 0)
+ DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS);
+ if (join->thd->check_killed()) // Aborted by user
+ {
+ join->thd->send_kill_message();
+ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
+ }
+
+ if (join_tab->table->vfield)
+ update_virtual_fields(join->thd, join_tab->table);
+
+ if (select_cond)
+ {
+ select_cond_result= MY_TEST(select_cond->val_int());
+
+ /* check for errors evaluating the condition */
+ if (join->thd->is_error())
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ }
+
+ if (!select_cond || select_cond_result)
+ {
+ /*
+ There is no select condition or the attached pushed down
+ condition is true => a match is found.
+ */
+ bool found= 1;
+ while (join_tab->first_unmatched && found)
+ {
+ /*
+ The while condition is always false if join_tab is not
+ the last inner join table of an outer join operation.
+ */
+ JOIN_TAB *first_unmatched= join_tab->first_unmatched;
+ /*
+ Mark that a match for current outer table is found.
+ This activates push down conditional predicates attached
+ to the all inner tables of the outer join.
+ */
+ first_unmatched->found= 1;
+ for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++)
+ {
+ /* Check all predicates that has just been activated. */
+ /*
+ Actually all predicates non-guarded by first_unmatched->found
+ will be re-evaluated again. It could be fixed, but, probably,
+ it's not worth doing now.
+ */
+ /*
+ not_exists_optimize has been created from a
+ select_cond containing 'is_null'. This 'is_null'
+ predicate is still present on any 'tab' with
+ 'not_exists_optimize'. Furthermore, the usual rules
+ for condition guards also applies for
+ 'not_exists_optimize' -> When 'is_null==false' we
+ know all cond. guards are open and we can apply
+ the 'not_exists_optimize'.
+ */
+ DBUG_ASSERT(!(tab->table->reginfo.not_exists_optimize &&
+ !tab->select_cond));
+
+ if (tab->select_cond && !tab->select_cond->val_int())
+ {
+ /* The condition attached to table tab is false */
+
+ if (tab == join_tab)
+ {
+ found= 0;
+ }
+ else
+ {
+ /*
+ Set a return point if rejected predicate is attached
+ not to the last table of the current nest level.
+ */
+ join->return_tab= tab;
+ }
+
+ if (tab->table->reginfo.not_exists_optimize)
+ {
+ /*
+ When not_exists_optimize is set: No need to further
+ explore more rows of 'tab' for this partial result.
+ Any found 'tab' matches are known to evaluate to 'false'.
+ Returning .._NO_MORE_ROWS will skip rem. 'tab' rows.
+ */
+ DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS);
+ }
+ else if (tab != join_tab)
+ {
+ DBUG_RETURN(NESTED_LOOP_OK);
+ }
+ }
+ }
+ /*
+ Check whether join_tab is not the last inner table
+ for another embedding outer join.
+ */
+ if ((first_unmatched= first_unmatched->first_upper) &&
+ first_unmatched->last_inner != join_tab)
+ first_unmatched= 0;
+ join_tab->first_unmatched= first_unmatched;
+ }
+
+ JOIN_TAB *return_tab= join->return_tab;
+ join_tab->found_match= TRUE;
+
+ if (join_tab->check_weed_out_table && found)
+ {
+ int res= join_tab->check_weed_out_table->sj_weedout_check_row(join->thd);
+ DBUG_PRINT("info", ("weedout_check: %d", res));
+ if (res == -1)
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ else if (res == 1)
+ found= FALSE;
+ }
+ else if (join_tab->do_firstmatch)
+ {
+ /*
+ We should return to the join_tab->do_firstmatch after we have
+ enumerated all the suffixes for current prefix row combination
+ */
+ return_tab= join_tab->do_firstmatch;
+ }
+
+ /*
+ It was not just a return to lower loop level when one
+ of the newly activated predicates is evaluated as false
+ (See above join->return_tab= tab).
+ */
+ join->examined_rows++;
+ DBUG_PRINT("counts", ("join->examined_rows++: %lu found: %d",
+ (ulong) join->examined_rows, (int) found));
+
+ if (found)
+ {
+ enum enum_nested_loop_state rc;
+ /* A match from join_tab is found for the current partial join. */
+ rc= (*join_tab->next_select)(join, join_tab+1, 0);
+ join->thd->get_stmt_da()->inc_current_row_for_warning();
+ if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+ DBUG_RETURN(rc);
+ if (return_tab < join->return_tab)
+ join->return_tab= return_tab;
+
+ if (join->return_tab < join_tab)
+ DBUG_RETURN(NESTED_LOOP_OK);
+ /*
+ Test if this was a SELECT DISTINCT query on a table that
+ was not in the field list; In this case we can abort if
+ we found a row, as no new rows can be added to the result.
+ */
+ if (shortcut_for_distinct && found_records != join->found_records)
+ DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS);
+ }
+ else
+ {
+ join->thd->get_stmt_da()->inc_current_row_for_warning();
+ join_tab->read_record.unlock_row(join_tab);
+ }
+ }
+ else
+ {
+ /*
+ The condition pushed down to the table join_tab rejects all rows
+ with the beginning coinciding with the current partial join.
+ */
+ join->examined_rows++;
+ join->thd->get_stmt_da()->inc_current_row_for_warning();
+ join_tab->read_record.unlock_row(join_tab);
+ }
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+/**
+
+ @details
+ Construct a NULL complimented partial join record and feed it to the next
+ level of the nested loop. This function is used in case we have
+ an OUTER join and no matching record was found.
+*/
+
+static enum_nested_loop_state
+evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab)
+{
+ /*
+ The table join_tab is the first inner table of a outer join operation
+ and no matches has been found for the current outer row.
+ */
+ JOIN_TAB *last_inner_tab= join_tab->last_inner;
+ /* Cache variables for faster loop */
+ COND *select_cond;
+ for ( ; join_tab <= last_inner_tab ; join_tab++)
+ {
+ /* Change the the values of guard predicate variables. */
+ join_tab->found= 1;
+ join_tab->not_null_compl= 0;
+ /* The outer row is complemented by nulls for each inner tables */
+ restore_record(join_tab->table,s->default_values); // Make empty record
+ mark_as_null_row(join_tab->table); // For group by without error
+ select_cond= join_tab->select_cond;
+ /* Check all attached conditions for inner table rows. */
+ if (select_cond && !select_cond->val_int())
+ return NESTED_LOOP_OK;
+ }
+ join_tab--;
+ /*
+ The row complemented by nulls might be the first row
+ of embedding outer joins.
+ If so, perform the same actions as in the code
+ for the first regular outer join row above.
+ */
+ for ( ; ; )
+ {
+ JOIN_TAB *first_unmatched= join_tab->first_unmatched;
+ if ((first_unmatched= first_unmatched->first_upper) &&
+ first_unmatched->last_inner != join_tab)
+ first_unmatched= 0;
+ join_tab->first_unmatched= first_unmatched;
+ if (!first_unmatched)
+ break;
+ first_unmatched->found= 1;
+ for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++)
+ {
+ if (tab->select_cond && !tab->select_cond->val_int())
+ {
+ join->return_tab= tab;
+ return NESTED_LOOP_OK;
+ }
+ }
+ }
+ /*
+ The row complemented by nulls satisfies all conditions
+ attached to inner tables.
+ */
+ if (join_tab->check_weed_out_table)
+ {
+ int res= join_tab->check_weed_out_table->sj_weedout_check_row(join->thd);
+ if (res == -1)
+ return NESTED_LOOP_ERROR;
+ else if (res == 1)
+ return NESTED_LOOP_OK;
+ }
+ else if (join_tab->do_firstmatch)
+ {
+ /*
+ We should return to the join_tab->do_firstmatch after we have
+ enumerated all the suffixes for current prefix row combination
+ */
+ if (join_tab->do_firstmatch < join->return_tab)
+ join->return_tab= join_tab->do_firstmatch;
+ }
+
+ /*
+ Send the row complemented by nulls to be joined with the
+ remaining tables.
+ */
+ return (*join_tab->next_select)(join, join_tab+1, 0);
+}
+
+/*****************************************************************************
+ The different ways to read a record
+ Returns -1 if row was not found, 0 if row was found and 1 on errors
+*****************************************************************************/
+
+/** Help function when we get some an error from the table handler. */
+
+int report_error(TABLE *table, int error)
+{
+ if (error == HA_ERR_END_OF_FILE || error == HA_ERR_KEY_NOT_FOUND)
+ {
+ table->status= STATUS_GARBAGE;
+ return -1; // key not found; ok
+ }
+ /*
+ Locking reads can legally return also these errors, do not
+ print them to the .err log
+ */
+ if (error != HA_ERR_LOCK_DEADLOCK && error != HA_ERR_LOCK_WAIT_TIMEOUT
+ && error != HA_ERR_TABLE_DEF_CHANGED && !table->in_use->killed)
+ sql_print_error("Got error %d when reading table '%s'",
+ error, table->s->path.str);
+ table->file->print_error(error,MYF(0));
+ return 1;
+}
+
+
+int safe_index_read(JOIN_TAB *tab)
+{
+ int error;
+ TABLE *table= tab->table;
+ if ((error= table->file->ha_index_read_map(table->record[0],
+ tab->ref.key_buff,
+ make_prev_keypart_map(tab->ref.key_parts),
+ HA_READ_KEY_EXACT)))
+ return report_error(table, error);
+ return 0;
+}
+
+
+/**
+ Reads content of constant table
+
+ @param tab table
+ @param pos position of table in query plan
+
+ @retval 0 ok, one row was found or one NULL-complemented row was created
+ @retval -1 ok, no row was found and no NULL-complemented row was created
+ @retval 1 error
+*/
+
+static int
+join_read_const_table(JOIN_TAB *tab, POSITION *pos)
+{
+ int error;
+ TABLE_LIST *tbl;
+ DBUG_ENTER("join_read_const_table");
+ TABLE *table=tab->table;
+ table->const_table=1;
+ table->null_row=0;
+ table->status=STATUS_NO_RECORD;
+
+ if (tab->table->pos_in_table_list->is_materialized_derived() &&
+ !tab->table->pos_in_table_list->fill_me)
+ {
+ //TODO: don't get here at all
+ /* Skip materialized derived tables/views. */
+ DBUG_RETURN(0);
+ }
+ else if (tab->table->pos_in_table_list->jtbm_subselect &&
+ tab->table->pos_in_table_list->jtbm_subselect->is_jtbm_const_tab)
+ {
+ /* Row will not be found */
+ int res;
+ if (tab->table->pos_in_table_list->jtbm_subselect->jtbm_const_row_found)
+ res= 0;
+ else
+ res= -1;
+ DBUG_RETURN(res);
+ }
+ else if (tab->type == JT_SYSTEM)
+ {
+ if ((error=join_read_system(tab)))
+ { // Info for DESCRIBE
+ tab->info= ET_CONST_ROW_NOT_FOUND;
+ /* Mark for EXPLAIN that the row was not found */
+ pos->records_read=0.0;
+ pos->ref_depend_map= 0;
+ if (!table->pos_in_table_list->outer_join || error > 0)
+ DBUG_RETURN(error);
+ }
+ /*
+ The optimizer trust the engine that when stats.records is 0, there
+ was no found rows
+ */
+ DBUG_ASSERT(table->file->stats.records > 0 || error);
+ }
+ else
+ {
+ if (!table->key_read && table->covering_keys.is_set(tab->ref.key) &&
+ !table->no_keyread &&
+ (int) table->reginfo.lock_type <= (int) TL_READ_HIGH_PRIORITY)
+ {
+ table->enable_keyread();
+ tab->index= tab->ref.key;
+ }
+ error=join_read_const(tab);
+ table->disable_keyread();
+ if (error)
+ {
+ tab->info= ET_UNIQUE_ROW_NOT_FOUND;
+ /* Mark for EXPLAIN that the row was not found */
+ pos->records_read=0.0;
+ pos->ref_depend_map= 0;
+ if (!table->pos_in_table_list->outer_join || error > 0)
+ DBUG_RETURN(error);
+ }
+ }
+ /*
+ Evaluate an on-expression only if it is not considered expensive.
+ This mainly prevents executing subqueries in optimization phase.
+ This is necessary since proper setup for such execution has not been
+ done at this stage.
+ */
+ if (*tab->on_expr_ref && !table->null_row &&
+ !(*tab->on_expr_ref)->is_expensive())
+ {
+#if !defined(DBUG_OFF) && defined(NOT_USING_ITEM_EQUAL)
+ /*
+ This test could be very useful to find bugs in the optimizer
+ where we would call this function with an expression that can't be
+ evaluated yet. We can't have this enabled by default as long as
+ have items like Item_equal, that doesn't report they are const but
+ they can still be called even if they contain not const items.
+ */
+ (*tab->on_expr_ref)->update_used_tables();
+ DBUG_ASSERT((*tab->on_expr_ref)->const_item());
+#endif
+ if ((table->null_row= MY_TEST((*tab->on_expr_ref)->val_int() == 0)))
+ mark_as_null_row(table);
+ }
+ if (!table->null_row)
+ table->maybe_null=0;
+
+ {
+ JOIN *join= tab->join;
+ List_iterator<TABLE_LIST> ti(join->select_lex->leaf_tables);
+ /* Check appearance of new constant items in Item_equal objects */
+ if (join->conds)
+ update_const_equal_items(join->conds, tab, TRUE);
+ while ((tbl= ti++))
+ {
+ TABLE_LIST *embedded;
+ TABLE_LIST *embedding= tbl;
+ do
+ {
+ embedded= embedding;
+ if (embedded->on_expr)
+ update_const_equal_items(embedded->on_expr, tab, TRUE);
+ embedding= embedded->embedding;
+ }
+ while (embedding &&
+ embedding->nested_join->join_list.head() == embedded);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Read a constant table when there is at most one matching row, using a table
+ scan.
+
+ @param tab Table to read
+
+ @retval 0 Row was found
+ @retval -1 Row was not found
+ @retval 1 Got an error (other than row not found) during read
+*/
+static int
+join_read_system(JOIN_TAB *tab)
+{
+ TABLE *table= tab->table;
+ int error;
+ if (table->status & STATUS_GARBAGE) // If first read
+ {
+ if ((error= table->file->ha_read_first_row(table->record[0],
+ table->s->primary_key)))
+ {
+ if (error != HA_ERR_END_OF_FILE)
+ return report_error(table, error);
+ mark_as_null_row(tab->table);
+ empty_record(table); // Make empty record
+ return -1;
+ }
+ if (table->vfield)
+ update_virtual_fields(tab->join->thd, table);
+ store_record(table,record[1]);
+ }
+ else if (!table->status) // Only happens with left join
+ restore_record(table,record[1]); // restore old record
+ table->null_row=0;
+ return table->status ? -1 : 0;
+}
+
+
+/**
+ Read a table when there is at most one matching row.
+
+ @param tab Table to read
+
+ @retval 0 Row was found
+ @retval -1 Row was not found
+ @retval 1 Got an error (other than row not found) during read
+*/
+
+static int
+join_read_const(JOIN_TAB *tab)
+{
+ int error;
+ TABLE *table= tab->table;
+ if (table->status & STATUS_GARBAGE) // If first read
+ {
+ table->status= 0;
+ if (cp_buffer_from_ref(tab->join->thd, table, &tab->ref))
+ error=HA_ERR_KEY_NOT_FOUND;
+ else
+ {
+ error= table->file->ha_index_read_idx_map(table->record[0],tab->ref.key,
+ (uchar*) tab->ref.key_buff,
+ make_prev_keypart_map(tab->ref.key_parts),
+ HA_READ_KEY_EXACT);
+ }
+ if (error)
+ {
+ table->status= STATUS_NOT_FOUND;
+ mark_as_null_row(tab->table);
+ empty_record(table);
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ return report_error(table, error);
+ return -1;
+ }
+ if (table->vfield)
+ update_virtual_fields(tab->join->thd, table);
+ store_record(table,record[1]);
+ }
+ else if (!(table->status & ~STATUS_NULL_ROW)) // Only happens with left join
+ {
+ table->status=0;
+ restore_record(table,record[1]); // restore old record
+ }
+ table->null_row=0;
+ return table->status ? -1 : 0;
+}
+
+/*
+ eq_ref access method implementation: "read_first" function
+
+ SYNOPSIS
+ join_read_key()
+ tab JOIN_TAB of the accessed table
+
+ DESCRIPTION
+ This is "read_fist" function for the eq_ref access method. The difference
+ from ref access function is that is that it has a one-element lookup
+ cache (see cmp_buffer_with_ref)
+
+ RETURN
+ 0 - Ok
+ -1 - Row not found
+ 1 - Error
+*/
+
+
+static int
+join_read_key(JOIN_TAB *tab)
+{
+ return join_read_key2(tab->join->thd, tab, tab->table, &tab->ref);
+}
+
+
+/*
+ eq_ref access handler but generalized a bit to support TABLE and TABLE_REF
+ not from the join_tab. See join_read_key for detailed synopsis.
+*/
+int join_read_key2(THD *thd, JOIN_TAB *tab, TABLE *table, TABLE_REF *table_ref)
+{
+ int error;
+ if (!table->file->inited)
+ {
+ error= table->file->ha_index_init(table_ref->key, tab ? tab->sorted : TRUE);
+ if (error)
+ {
+ (void) report_error(table, error);
+ return 1;
+ }
+ }
+
+ /* TODO: Why don't we do "Late NULLs Filtering" here? */
+ if (cmp_buffer_with_ref(thd, table, table_ref) ||
+ (table->status & (STATUS_GARBAGE | STATUS_NO_PARENT | STATUS_NULL_ROW)))
+ {
+ if (table_ref->key_err)
+ {
+ table->status=STATUS_NOT_FOUND;
+ return -1;
+ }
+ /*
+ Moving away from the current record. Unlock the row
+ in the handler if it did not match the partial WHERE.
+ */
+ if (tab && tab->ref.has_record && tab->ref.use_count == 0)
+ {
+ tab->read_record.table->file->unlock_row();
+ table_ref->has_record= FALSE;
+ }
+ error=table->file->ha_index_read_map(table->record[0],
+ table_ref->key_buff,
+ make_prev_keypart_map(table_ref->key_parts),
+ HA_READ_KEY_EXACT);
+ if (error && error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ return report_error(table, error);
+
+ if (! error)
+ {
+ table_ref->has_record= TRUE;
+ table_ref->use_count= 1;
+ }
+ }
+ else if (table->status == 0)
+ {
+ DBUG_ASSERT(table_ref->has_record);
+ table_ref->use_count++;
+ }
+ table->null_row=0;
+ return table->status ? -1 : 0;
+}
+
+
+/**
+ Since join_read_key may buffer a record, do not unlock
+ it if it was not used in this invocation of join_read_key().
+ Only count locks, thus remembering if the record was left unused,
+ and unlock already when pruning the current value of
+ TABLE_REF buffer.
+ @sa join_read_key()
+*/
+
+static void
+join_read_key_unlock_row(st_join_table *tab)
+{
+ DBUG_ASSERT(tab->ref.use_count);
+ if (tab->ref.use_count)
+ tab->ref.use_count--;
+}
+
+/*
+ ref access method implementation: "read_first" function
+
+ SYNOPSIS
+ join_read_always_key()
+ tab JOIN_TAB of the accessed table
+
+ DESCRIPTION
+ This is "read_fist" function for the "ref" access method.
+
+ The functon must leave the index initialized when it returns.
+ ref_or_null access implementation depends on that.
+
+ RETURN
+ 0 - Ok
+ -1 - Row not found
+ 1 - Error
+*/
+
+static int
+join_read_always_key(JOIN_TAB *tab)
+{
+ int error;
+ TABLE *table= tab->table;
+
+ /* Initialize the index first */
+ if (!table->file->inited)
+ {
+ if ((error= table->file->ha_index_init(tab->ref.key, tab->sorted)))
+ {
+ (void) report_error(table, error);
+ return 1;
+ }
+ }
+
+ if (cp_buffer_from_ref(tab->join->thd, table, &tab->ref))
+ return -1;
+ if ((error= table->file->prepare_index_key_scan_map(tab->ref.key_buff, make_prev_keypart_map(tab->ref.key_parts))))
+ {
+ report_error(table,error);
+ return -1;
+ }
+ if ((error= table->file->ha_index_read_map(table->record[0],
+ tab->ref.key_buff,
+ make_prev_keypart_map(tab->ref.key_parts),
+ HA_READ_KEY_EXACT)))
+ {
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ return report_error(table, error);
+ return -1; /* purecov: inspected */
+ }
+ return 0;
+}
+
+
+/**
+ This function is used when optimizing away ORDER BY in
+ SELECT * FROM t1 WHERE a=1 ORDER BY a DESC,b DESC.
+*/
+
+static int
+join_read_last_key(JOIN_TAB *tab)
+{
+ int error;
+ TABLE *table= tab->table;
+
+ if (!table->file->inited &&
+ (error= table->file->ha_index_init(tab->ref.key, tab->sorted)))
+ {
+ (void) report_error(table, error);
+ return 1;
+ }
+
+ if (cp_buffer_from_ref(tab->join->thd, table, &tab->ref))
+ return -1;
+ if ((error= table->file->prepare_index_key_scan_map(tab->ref.key_buff, make_prev_keypart_map(tab->ref.key_parts))))
+ {
+ report_error(table,error);
+ return -1;
+ }
+ if ((error= table->file->ha_index_read_map(table->record[0],
+ tab->ref.key_buff,
+ make_prev_keypart_map(tab->ref.key_parts),
+ HA_READ_PREFIX_LAST)))
+ {
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ return report_error(table, error);
+ return -1; /* purecov: inspected */
+ }
+ return 0;
+}
+
+
+ /* ARGSUSED */
+static int
+join_no_more_records(READ_RECORD *info __attribute__((unused)))
+{
+ return -1;
+}
+
+
+static int
+join_read_next_same(READ_RECORD *info)
+{
+ int error;
+ TABLE *table= info->table;
+ JOIN_TAB *tab=table->reginfo.join_tab;
+
+ if ((error= table->file->ha_index_next_same(table->record[0],
+ tab->ref.key_buff,
+ tab->ref.key_length)))
+ {
+ if (error != HA_ERR_END_OF_FILE)
+ return report_error(table, error);
+ table->status= STATUS_GARBAGE;
+ return -1;
+ }
+ return 0;
+}
+
+
+static int
+join_read_prev_same(READ_RECORD *info)
+{
+ int error;
+ TABLE *table= info->table;
+ JOIN_TAB *tab=table->reginfo.join_tab;
+
+ if ((error= table->file->ha_index_prev(table->record[0])))
+ return report_error(table, error);
+ if (key_cmp_if_same(table, tab->ref.key_buff, tab->ref.key,
+ tab->ref.key_length))
+ {
+ table->status=STATUS_NOT_FOUND;
+ error= -1;
+ }
+ return error;
+}
+
+
+static int
+join_init_quick_read_record(JOIN_TAB *tab)
+{
+ if (test_if_quick_select(tab) == -1)
+ return -1; /* No possible records */
+ return join_init_read_record(tab);
+}
+
+
+int read_first_record_seq(JOIN_TAB *tab)
+{
+ if (tab->read_record.table->file->ha_rnd_init_with_error(1))
+ return 1;
+ return (*tab->read_record.read_record)(&tab->read_record);
+}
+
+static int
+test_if_quick_select(JOIN_TAB *tab)
+{
+ DBUG_EXECUTE_IF("show_explain_probe_test_if_quick_select",
+ if (dbug_user_var_equals_int(tab->join->thd,
+ "show_explain_probe_select_id",
+ tab->join->select_lex->select_number))
+ dbug_serve_apcs(tab->join->thd, 1);
+ );
+
+
+ delete tab->select->quick;
+ tab->select->quick=0;
+ return tab->select->test_quick_select(tab->join->thd, tab->keys,
+ (table_map) 0, HA_POS_ERROR, 0,
+ FALSE);
+}
+
+
+static
+bool test_if_use_dynamic_range_scan(JOIN_TAB *join_tab)
+{
+ return (join_tab->use_quick == 2 && test_if_quick_select(join_tab) > 0);
+}
+
+int join_init_read_record(JOIN_TAB *tab)
+{
+ if (tab->select && tab->select->quick && tab->select->quick->reset())
+ return 1;
+ if (!tab->preread_init_done && tab->preread_init())
+ return 1;
+ if (init_read_record(&tab->read_record, tab->join->thd, tab->table,
+ tab->select,1,1, FALSE))
+ return 1;
+ return (*tab->read_record.read_record)(&tab->read_record);
+}
+
+int
+join_read_record_no_init(JOIN_TAB *tab)
+{
+ Copy_field *save_copy, *save_copy_end;
+
+ /*
+ init_read_record resets all elements of tab->read_record().
+ Remember things that we don't want to have reset.
+ */
+ save_copy= tab->read_record.copy_field;
+ save_copy_end= tab->read_record.copy_field_end;
+
+ init_read_record(&tab->read_record, tab->join->thd, tab->table,
+ tab->select,1,1, FALSE);
+
+ tab->read_record.copy_field= save_copy;
+ tab->read_record.copy_field_end= save_copy_end;
+ tab->read_record.read_record= rr_sequential_and_unpack;
+
+ return (*tab->read_record.read_record)(&tab->read_record);
+}
+
+static int
+join_read_first(JOIN_TAB *tab)
+{
+ int error= 0;
+ TABLE *table=tab->table;
+ DBUG_ENTER("join_read_first");
+
+ if (table->covering_keys.is_set(tab->index) && !table->no_keyread &&
+ !table->key_read)
+ table->enable_keyread();
+ tab->table->status=0;
+ tab->read_record.read_record=join_read_next;
+ tab->read_record.table=table;
+ tab->read_record.index=tab->index;
+ tab->read_record.record=table->record[0];
+ if (!table->file->inited)
+ error= table->file->ha_index_init(tab->index, tab->sorted);
+ if (!error)
+ error= table->file->prepare_index_scan();
+ if (error || (error=tab->table->file->ha_index_first(tab->table->record[0])))
+ {
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ report_error(table, error);
+ DBUG_RETURN(-1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+static int
+join_read_next(READ_RECORD *info)
+{
+ int error;
+ if ((error= info->table->file->ha_index_next(info->record)))
+ return report_error(info->table, error);
+
+ return 0;
+}
+
+
+static int
+join_read_last(JOIN_TAB *tab)
+{
+ TABLE *table=tab->table;
+ int error= 0;
+ DBUG_ENTER("join_read_first");
+
+ if (table->covering_keys.is_set(tab->index) && !table->no_keyread &&
+ !table->key_read)
+ table->enable_keyread();
+ tab->table->status=0;
+ tab->read_record.read_record=join_read_prev;
+ tab->read_record.table=table;
+ tab->read_record.index=tab->index;
+ tab->read_record.record=table->record[0];
+ if (!table->file->inited)
+ error= table->file->ha_index_init(tab->index, 1);
+ if (!error)
+ error= table->file->prepare_index_scan();
+ if (error || (error= tab->table->file->ha_index_last(tab->table->record[0])))
+ DBUG_RETURN(report_error(table, error));
+
+ DBUG_RETURN(0);
+}
+
+
+static int
+join_read_prev(READ_RECORD *info)
+{
+ int error;
+ if ((error= info->table->file->ha_index_prev(info->record)))
+ return report_error(info->table, error);
+ return 0;
+}
+
+
+static int
+join_ft_read_first(JOIN_TAB *tab)
+{
+ int error;
+ TABLE *table= tab->table;
+
+ if (!table->file->inited &&
+ (error= table->file->ha_index_init(tab->ref.key, 1)))
+ {
+ (void) report_error(table, error);
+ return 1;
+ }
+
+ table->file->ft_init();
+
+ if ((error= table->file->ha_ft_read(table->record[0])))
+ return report_error(table, error);
+ return 0;
+}
+
+static int
+join_ft_read_next(READ_RECORD *info)
+{
+ int error;
+ if ((error= info->table->file->ha_ft_read(info->table->record[0])))
+ return report_error(info->table, error);
+ return 0;
+}
+
+
+/**
+ Reading of key with key reference and one part that may be NULL.
+*/
+
+int
+join_read_always_key_or_null(JOIN_TAB *tab)
+{
+ int res;
+
+ /* First read according to key which is NOT NULL */
+ *tab->ref.null_ref_key= 0; // Clear null byte
+ if ((res= join_read_always_key(tab)) >= 0)
+ return res;
+
+ /* Then read key with null value */
+ *tab->ref.null_ref_key= 1; // Set null byte
+ return safe_index_read(tab);
+}
+
+
+int
+join_read_next_same_or_null(READ_RECORD *info)
+{
+ int error;
+ if ((error= join_read_next_same(info)) >= 0)
+ return error;
+ JOIN_TAB *tab= info->table->reginfo.join_tab;
+
+ /* Test if we have already done a read after null key */
+ if (*tab->ref.null_ref_key)
+ return -1; // All keys read
+ *tab->ref.null_ref_key= 1; // Set null byte
+ return safe_index_read(tab); // then read null keys
+}
+
+
+/*****************************************************************************
+ DESCRIPTION
+ Functions that end one nested loop iteration. Different functions
+ are used to support GROUP BY clause and to redirect records
+ to a table (e.g. in case of SELECT into a temporary table) or to the
+ network client.
+
+ RETURN VALUES
+ NESTED_LOOP_OK - the record has been successfully handled
+ NESTED_LOOP_ERROR - a fatal error (like table corruption)
+ was detected
+ NESTED_LOOP_KILLED - thread shutdown was requested while processing
+ the record
+ NESTED_LOOP_QUERY_LIMIT - the record has been successfully handled;
+ additionally, the nested loop produced the
+ number of rows specified in the LIMIT clause
+ for the query
+ NESTED_LOOP_CURSOR_LIMIT - the record has been successfully handled;
+ additionally, there is a cursor and the nested
+ loop algorithm produced the number of rows
+ that is specified for current cursor fetch
+ operation.
+ All return values except NESTED_LOOP_OK abort the nested loop.
+*****************************************************************************/
+
+/* ARGSUSED */
+static enum_nested_loop_state
+end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records)
+{
+ DBUG_ENTER("end_send");
+ if (!end_of_records)
+ {
+ if (join->table_count &&
+ (join->join_tab->is_using_loose_index_scan() ||
+ /*
+ When order by used a loose scan as its input, the quick select may
+ be attached to pre_sort_join_tab.
+ */
+ (join->pre_sort_join_tab &&
+ join->pre_sort_join_tab->is_using_loose_index_scan())))
+ {
+ /* Copy non-aggregated fields when loose index scan is used. */
+ copy_fields(&join->tmp_table_param);
+ }
+ if (join->having && join->having->val_int() == 0)
+ DBUG_RETURN(NESTED_LOOP_OK); // Didn't match having
+ if (join->procedure)
+ {
+ if (join->procedure->send_row(join->procedure_fields_list))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ DBUG_RETURN(NESTED_LOOP_OK);
+ }
+ if (join->do_send_rows)
+ {
+ int error;
+ /* result < 0 if row was not accepted and should not be counted */
+ if ((error= join->result->send_data(*join->fields)))
+ DBUG_RETURN(error < 0 ? NESTED_LOOP_OK : NESTED_LOOP_ERROR);
+ }
+
+ ++join->send_records;
+ if (join->send_records >= join->unit->select_limit_cnt &&
+ !join->do_send_rows)
+ {
+ /*
+ If filesort is used for sorting, stop after select_limit_cnt+1
+ records are read. Because of optimization in some cases it can
+ provide only select_limit_cnt+1 records.
+ */
+ if (join->order && join->sortorder &&
+ join->filesort_found_rows &&
+ join->select_options & OPTION_FOUND_ROWS)
+ {
+ DBUG_PRINT("info", ("filesort NESTED_LOOP_QUERY_LIMIT"));
+ DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT);
+ }
+ }
+ if (join->send_records >= join->unit->select_limit_cnt &&
+ join->do_send_rows)
+ {
+ if (join->select_options & OPTION_FOUND_ROWS)
+ {
+ JOIN_TAB *jt=join->join_tab;
+ if ((join->table_count == 1) && !join->tmp_table && !join->sort_and_group
+ && !join->send_group_parts && !join->having && !jt->select_cond &&
+ !(jt->select && jt->select->quick) &&
+ (jt->table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT) &&
+ (jt->ref.key < 0))
+ {
+ /* Join over all rows in table; Return number of found rows */
+ TABLE *table=jt->table;
+
+ join->select_options ^= OPTION_FOUND_ROWS;
+ if (table->sort.record_pointers ||
+ (table->sort.io_cache && my_b_inited(table->sort.io_cache)))
+ {
+ /* Using filesort */
+ join->send_records= table->sort.found_records;
+ }
+ else
+ {
+ table->file->info(HA_STATUS_VARIABLE);
+ join->send_records= table->file->stats.records;
+ }
+ }
+ else
+ {
+ join->do_send_rows= 0;
+ if (join->unit->fake_select_lex)
+ join->unit->fake_select_lex->select_limit= 0;
+ DBUG_RETURN(NESTED_LOOP_OK);
+ }
+ }
+ DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); // Abort nicely
+ }
+ else if (join->send_records >= join->fetch_limit)
+ {
+ /*
+ There is a server side cursor and all rows for
+ this fetch request are sent.
+ */
+ DBUG_RETURN(NESTED_LOOP_CURSOR_LIMIT);
+ }
+ }
+ else
+ {
+ if (join->procedure && join->procedure->end_of_records())
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ }
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+
+ /* ARGSUSED */
+enum_nested_loop_state
+end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records)
+{
+ int idx= -1;
+ enum_nested_loop_state ok_code= NESTED_LOOP_OK;
+ DBUG_ENTER("end_send_group");
+
+ if (!join->first_record || end_of_records ||
+ (idx=test_if_group_changed(join->group_fields)) >= 0)
+ {
+ if (join->first_record ||
+ (end_of_records && !join->group && !join->group_optimized_away))
+ {
+ if (join->procedure)
+ join->procedure->end_group();
+ if (idx < (int) join->send_group_parts)
+ {
+ int error=0;
+ if (join->procedure)
+ {
+ if (join->having && join->having->val_int() == 0)
+ error= -1; // Didn't satisfy having
+ else
+ {
+ if (join->do_send_rows)
+ error=join->procedure->send_row(*join->fields) ? 1 : 0;
+ join->send_records++;
+ }
+ if (end_of_records && join->procedure->end_of_records())
+ error= 1; // Fatal error
+ }
+ else
+ {
+ if (!join->first_record)
+ {
+ List_iterator_fast<Item> it(*join->fields);
+ Item *item;
+ DBUG_PRINT("info", ("no matching rows"));
+
+ /* No matching rows for group function */
+ join->clear();
+ join->no_rows_in_result_called= 1;
+
+ while ((item= it++))
+ item->no_rows_in_result();
+ }
+ if (join->having && join->having->val_int() == 0)
+ error= -1; // Didn't satisfy having
+ else
+ {
+ if (join->do_send_rows)
+ {
+ error= join->result->send_data(*join->fields);
+ if (error < 0)
+ {
+ /* Duplicate row, don't count */
+ join->send_records--;
+ error= 0;
+ }
+ }
+ join->send_records++;
+ }
+ if (join->rollup.state != ROLLUP::STATE_NONE && error <= 0)
+ {
+ if (join->rollup_send_data((uint) (idx+1)))
+ error= 1;
+ }
+ }
+ if (error > 0)
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ if (end_of_records)
+ DBUG_RETURN(NESTED_LOOP_OK);
+ if (join->send_records >= join->unit->select_limit_cnt &&
+ join->do_send_rows)
+ {
+ if (!(join->select_options & OPTION_FOUND_ROWS))
+ DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); // Abort nicely
+ join->do_send_rows=0;
+ join->unit->select_limit_cnt = HA_POS_ERROR;
+ }
+ else if (join->send_records >= join->fetch_limit)
+ {
+ /*
+ There is a server side cursor and all rows
+ for this fetch request are sent.
+ */
+ /*
+ Preventing code duplication. When finished with the group reset
+ the group functions and copy_fields. We fall through. bug #11904
+ */
+ ok_code= NESTED_LOOP_CURSOR_LIMIT;
+ }
+ }
+ }
+ else
+ {
+ if (end_of_records)
+ DBUG_RETURN(NESTED_LOOP_OK);
+ join->first_record=1;
+ (void) test_if_group_changed(join->group_fields);
+ }
+ if (idx < (int) join->send_group_parts)
+ {
+ /*
+ This branch is executed also for cursors which have finished their
+ fetch limit - the reason for ok_code.
+ */
+ copy_fields(&join->tmp_table_param);
+ if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1]))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ if (join->procedure)
+ join->procedure->add();
+ DBUG_RETURN(ok_code);
+ }
+ }
+ if (update_sum_func(join->sum_funcs))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ if (join->procedure)
+ join->procedure->add();
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+
+ /* ARGSUSED */
+static enum_nested_loop_state
+end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records)
+{
+ TABLE *table=join->tmp_table;
+ DBUG_ENTER("end_write");
+
+ if (!end_of_records)
+ {
+ copy_fields(&join->tmp_table_param);
+ if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd))
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+
+ if (!join->having || join->having->val_int())
+ {
+ int error;
+ join->found_records++;
+ if ((error= table->file->ha_write_tmp_row(table->record[0])))
+ {
+ if (!table->file->is_fatal_error(error, HA_CHECK_DUP))
+ goto end;
+ bool is_duplicate;
+ if (create_internal_tmp_table_from_heap(join->thd, table,
+ join->tmp_table_param.start_recinfo,
+ &join->tmp_table_param.recinfo,
+ error, 1, &is_duplicate))
+ DBUG_RETURN(NESTED_LOOP_ERROR); // Not a table_is_full error
+ if (is_duplicate)
+ goto end;
+ table->s->uniques=0; // To ensure rows are the same
+ }
+ if (++join->send_records >= join->tmp_table_param.end_write_records &&
+ join->do_send_rows)
+ {
+ if (!(join->select_options & OPTION_FOUND_ROWS))
+ DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT);
+ join->do_send_rows=0;
+ join->unit->select_limit_cnt = HA_POS_ERROR;
+ }
+ }
+ }
+end:
+ if (join->thd->check_killed())
+ {
+ join->thd->send_kill_message();
+ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
+ }
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+/* ARGSUSED */
+/** Group by searching after group record and updating it if possible. */
+
+static enum_nested_loop_state
+end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records)
+{
+ TABLE *table=join->tmp_table;
+ ORDER *group;
+ int error;
+ DBUG_ENTER("end_update");
+
+ if (end_of_records)
+ DBUG_RETURN(NESTED_LOOP_OK);
+
+ join->found_records++;
+ copy_fields(&join->tmp_table_param); // Groups are copied twice.
+ /* Make a key of group index */
+ for (group=table->group ; group ; group=group->next)
+ {
+ Item *item= *group->item;
+ if (group->fast_field_copier_setup != group->field)
+ {
+ DBUG_PRINT("info", ("new setup 0x%lx -> 0x%lx",
+ (ulong)group->fast_field_copier_setup,
+ (ulong)group->field));
+ group->fast_field_copier_setup= group->field;
+ group->fast_field_copier_func=
+ item->setup_fast_field_copier(group->field);
+ }
+ item->save_org_in_field(group->field, group->fast_field_copier_func);
+ /* Store in the used key if the field was 0 */
+ if (item->maybe_null)
+ group->buff[-1]= (char) group->field->is_null();
+ }
+ if (!table->file->ha_index_read_map(table->record[1],
+ join->tmp_table_param.group_buff,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ { /* Update old record */
+ restore_record(table,record[1]);
+ update_tmptable_sum_func(join->sum_funcs,table);
+ if ((error= table->file->ha_update_tmp_row(table->record[1],
+ table->record[0])))
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ }
+ goto end;
+ }
+
+ init_tmptable_sum_functions(join->sum_funcs);
+ if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd))
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ if ((error= table->file->ha_write_tmp_row(table->record[0])))
+ {
+ if (create_internal_tmp_table_from_heap(join->thd, table,
+ join->tmp_table_param.start_recinfo,
+ &join->tmp_table_param.recinfo,
+ error, 0, NULL))
+ DBUG_RETURN(NESTED_LOOP_ERROR); // Not a table_is_full error
+ /* Change method to update rows */
+ if ((error= table->file->ha_index_init(0, 0)))
+ {
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ }
+
+ join->join_tab[join->top_join_tab_count-1].next_select=end_unique_update;
+ }
+ join->send_records++;
+end:
+ if (join->thd->check_killed())
+ {
+ join->thd->send_kill_message();
+ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
+ }
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+
+/** Like end_update, but this is done with unique constraints instead of keys. */
+
+static enum_nested_loop_state
+end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records)
+{
+ TABLE *table=join->tmp_table;
+ int error;
+ DBUG_ENTER("end_unique_update");
+
+ if (end_of_records)
+ DBUG_RETURN(NESTED_LOOP_OK);
+
+ init_tmptable_sum_functions(join->sum_funcs);
+ copy_fields(&join->tmp_table_param); // Groups are copied twice.
+ if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd))
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+
+ if (!(error= table->file->ha_write_tmp_row(table->record[0])))
+ join->send_records++; // New group
+ else
+ {
+ if ((int) table->file->get_dup_key(error) < 0)
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ }
+ if (table->file->ha_rnd_pos(table->record[1],table->file->dup_ref))
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ }
+ restore_record(table,record[1]);
+ update_tmptable_sum_func(join->sum_funcs,table);
+ if ((error= table->file->ha_update_tmp_row(table->record[1],
+ table->record[0])))
+ {
+ table->file->print_error(error,MYF(0)); /* purecov: inspected */
+ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
+ }
+ }
+ if (join->thd->check_killed())
+ {
+ join->thd->send_kill_message();
+ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
+ }
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+
+ /* ARGSUSED */
+enum_nested_loop_state
+end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records)
+{
+ TABLE *table=join->tmp_table;
+ int idx= -1;
+ DBUG_ENTER("end_write_group");
+
+ if (!join->first_record || end_of_records ||
+ (idx=test_if_group_changed(join->group_fields)) >= 0)
+ {
+ if (join->first_record || (end_of_records && !join->group))
+ {
+ if (join->procedure)
+ join->procedure->end_group();
+ int send_group_parts= join->send_group_parts;
+ if (idx < send_group_parts)
+ {
+ if (!join->first_record)
+ {
+ /* No matching rows for group function */
+ join->clear();
+ }
+ copy_sum_funcs(join->sum_funcs,
+ join->sum_funcs_end[send_group_parts]);
+ if (!join->having || join->having->val_int())
+ {
+ int error= table->file->ha_write_tmp_row(table->record[0]);
+ if (error &&
+ create_internal_tmp_table_from_heap(join->thd, table,
+ join->tmp_table_param.start_recinfo,
+ &join->tmp_table_param.recinfo,
+ error, 0, NULL))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ }
+ if (join->rollup.state != ROLLUP::STATE_NONE)
+ {
+ if (join->rollup_write_data((uint) (idx+1), table))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ }
+ if (end_of_records)
+ goto end;
+ }
+ }
+ else
+ {
+ if (end_of_records)
+ goto end;
+ join->first_record=1;
+ (void) test_if_group_changed(join->group_fields);
+ }
+ if (idx < (int) join->send_group_parts)
+ {
+ copy_fields(&join->tmp_table_param);
+ if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1]))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ if (join->procedure)
+ join->procedure->add();
+ goto end;
+ }
+ }
+ if (update_sum_func(join->sum_funcs))
+ DBUG_RETURN(NESTED_LOOP_ERROR);
+ if (join->procedure)
+ join->procedure->add();
+end:
+ if (join->thd->check_killed())
+ {
+ join->thd->send_kill_message();
+ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
+ }
+ DBUG_RETURN(NESTED_LOOP_OK);
+}
+
+
+/*****************************************************************************
+ Remove calculation with tables that aren't yet read. Remove also tests
+ against fields that are read through key where the table is not a
+ outer join table.
+ We can't remove tests that are made against columns which are stored
+ in sorted order.
+*****************************************************************************/
+
+/**
+ Check if "left_item=right_item" equality is guaranteed to be true by use of
+ [eq]ref access on left_item->field->table.
+
+ SYNOPSIS
+ test_if_ref()
+ root_cond
+ left_item
+ right_item
+
+ DESCRIPTION
+ Check if the given "left_item = right_item" equality is guaranteed to be
+ true by use of [eq_]ref access method.
+
+ We need root_cond as we can't remove ON expressions even if employed ref
+ access guarantees that they are true. This is because TODO
+
+ RETURN
+ TRUE if right_item is used removable reference key on left_item
+ FALSE Otherwise
+
+*/
+
+bool test_if_ref(Item *root_cond, Item_field *left_item,Item *right_item)
+{
+ Field *field=left_item->field;
+ JOIN_TAB *join_tab= field->table->reginfo.join_tab;
+ // No need to change const test
+ if (!field->table->const_table && join_tab &&
+ !join_tab->is_ref_for_hash_join() &&
+ (!join_tab->first_inner ||
+ *join_tab->first_inner->on_expr_ref == root_cond))
+ {
+ /*
+ If ref access uses "Full scan on NULL key" (i.e. it actually alternates
+ between ref access and full table scan), then no equality can be
+ guaranteed to be true.
+ */
+ if (join_tab->ref.is_access_triggered())
+ return FALSE;
+
+ Item *ref_item=part_of_refkey(field->table,field);
+ if (ref_item && (ref_item->eq(right_item,1) ||
+ ref_item->real_item()->eq(right_item,1)))
+ {
+ right_item= right_item->real_item();
+ if (right_item->type() == Item::FIELD_ITEM)
+ return (field->eq_def(((Item_field *) right_item)->field));
+ /* remove equalities injected by IN->EXISTS transformation */
+ else if (right_item->type() == Item::CACHE_ITEM)
+ return ((Item_cache *)right_item)->eq_def (field);
+ if (right_item->const_item() && !(right_item->is_null()))
+ {
+ /*
+ We can remove binary fields and numerical fields except float,
+ as float comparison isn't 100 % safe
+ We have to keep normal strings to be able to check for end spaces
+ */
+ if (field->binary() &&
+ field->real_type() != MYSQL_TYPE_STRING &&
+ field->real_type() != MYSQL_TYPE_VARCHAR &&
+ (field->type() != MYSQL_TYPE_FLOAT || field->decimals() == 0))
+ {
+ return !right_item->save_in_field_no_warnings(field, 1);
+ }
+ }
+ }
+ }
+ return 0; // keep test
+}
+
+
+/**
+ Extract a condition that can be checked after reading given table
+ @fn make_cond_for_table()
+
+ @param cond Condition to analyze
+ @param tables Tables for which "current field values" are available
+ @param used_table Table that we're extracting the condition for
+ tables Tables for which "current field values" are available (this
+ includes used_table)
+ (may also include PSEUDO_TABLE_BITS, and may be zero)
+ @param join_tab_idx_arg
+ The index of the JOIN_TAB this Item is being extracted
+ for. MAX_TABLES if there is no corresponding JOIN_TAB.
+ @param exclude_expensive_cond
+ Do not push expensive conditions
+ @param retain_ref_cond
+ Retain ref conditions
+
+ @retval <>NULL Generated condition
+ @retval =NULL Already checked, OR error
+
+ @details
+ Extract the condition that can be checked after reading the table
+ specified in 'used_table', given that current-field values for tables
+ specified in 'tables' bitmap are available.
+ If 'used_table' is 0
+ - extract conditions for all tables in 'tables'.
+ - extract conditions are unrelated to any tables
+ in the same query block/level(i.e. conditions
+ which have used_tables == 0).
+
+ The function assumes that
+ - Constant parts of the condition has already been checked.
+ - Condition that could be checked for tables in 'tables' has already
+ been checked.
+
+ The function takes into account that some parts of the condition are
+ guaranteed to be true by employed 'ref' access methods (the code that
+ does this is located at the end, search down for "EQ_FUNC").
+
+ @note
+ Make sure to keep the implementations of make_cond_for_table() and
+ make_cond_after_sjm() synchronized.
+ make_cond_for_info_schema() uses similar algorithm as well.
+*/
+
+static Item *
+make_cond_for_table(THD *thd, Item *cond, table_map tables,
+ table_map used_table,
+ int join_tab_idx_arg,
+ bool exclude_expensive_cond __attribute__((unused)),
+ bool retain_ref_cond)
+{
+ return make_cond_for_table_from_pred(thd, cond, cond, tables, used_table,
+ join_tab_idx_arg,
+ exclude_expensive_cond,
+ retain_ref_cond);
+}
+
+
+static Item *
+make_cond_for_table_from_pred(THD *thd, Item *root_cond, Item *cond,
+ table_map tables, table_map used_table,
+ int join_tab_idx_arg,
+ bool exclude_expensive_cond __attribute__
+ ((unused)),
+ bool retain_ref_cond)
+
+{
+ if (used_table && !(cond->used_tables() & used_table))
+ return (COND*) 0; // Already checked
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ /* Create new top level AND item */
+ Item_cond_and *new_cond=new Item_cond_and;
+ if (!new_cond)
+ return (COND*) 0; // OOM /* purecov: inspected */
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix=make_cond_for_table_from_pred(thd, root_cond, item,
+ tables, used_table,
+ join_tab_idx_arg,
+ exclude_expensive_cond,
+ retain_ref_cond);
+ if (fix)
+ new_cond->argument_list()->push_back(fix);
+ }
+ switch (new_cond->argument_list()->elements) {
+ case 0:
+ return (COND*) 0; // Always true
+ case 1:
+ return new_cond->argument_list()->head();
+ default:
+ /*
+ Call fix_fields to propagate all properties of the children to
+ the new parent Item. This should not be expensive because all
+ children of Item_cond_and should be fixed by now.
+ */
+ new_cond->fix_fields(thd, 0);
+ new_cond->used_tables_cache=
+ ((Item_cond_and*) cond)->used_tables_cache &
+ tables;
+ return new_cond;
+ }
+ }
+ else
+ { // Or list
+ Item_cond_or *new_cond=new Item_cond_or;
+ if (!new_cond)
+ return (COND*) 0; // OOM /* purecov: inspected */
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix=make_cond_for_table_from_pred(thd, root_cond, item,
+ tables, 0L,
+ join_tab_idx_arg,
+ exclude_expensive_cond,
+ retain_ref_cond);
+ if (!fix)
+ return (COND*) 0; // Always true
+ new_cond->argument_list()->push_back(fix);
+ }
+ /*
+ Call fix_fields to propagate all properties of the children to
+ the new parent Item. This should not be expensive because all
+ children of Item_cond_and should be fixed by now.
+ */
+ new_cond->fix_fields(thd, 0);
+ new_cond->used_tables_cache= ((Item_cond_or*) cond)->used_tables_cache;
+ new_cond->top_level_item();
+ return new_cond;
+ }
+ }
+
+ /*
+ Because the following test takes a while and it can be done
+ table_count times, we mark each item that we have examined with the result
+ of the test
+ */
+ if ((cond->marker == 3 && !retain_ref_cond) ||
+ (cond->used_tables() & ~tables))
+ return (COND*) 0; // Can't check this yet
+
+ if (cond->marker == 2 || cond->eq_cmp_result() == Item::COND_OK)
+ {
+ cond->set_join_tab_idx(join_tab_idx_arg);
+ return cond; // Not boolean op
+ }
+
+ if (cond->type() == Item::FUNC_ITEM &&
+ ((Item_func*) cond)->functype() == Item_func::EQ_FUNC)
+ {
+ Item *left_item= ((Item_func*) cond)->arguments()[0]->real_item();
+ Item *right_item= ((Item_func*) cond)->arguments()[1]->real_item();
+ if (left_item->type() == Item::FIELD_ITEM && !retain_ref_cond &&
+ test_if_ref(root_cond, (Item_field*) left_item,right_item))
+ {
+ cond->marker=3; // Checked when read
+ return (COND*) 0;
+ }
+ if (right_item->type() == Item::FIELD_ITEM && !retain_ref_cond &&
+ test_if_ref(root_cond, (Item_field*) right_item,left_item))
+ {
+ cond->marker=3; // Checked when read
+ return (COND*) 0;
+ }
+ }
+ cond->marker=2;
+ cond->set_join_tab_idx(join_tab_idx_arg);
+ return cond;
+}
+
+
+/*
+ The difference of this from make_cond_for_table() is that we're in the
+ following state:
+ 1. conditions referring to 'tables' have been checked
+ 2. conditions referring to sjm_tables have been checked, too
+ 3. We need condition that couldn't be checked in #1 or #2 but
+ can be checked when we get both (tables | sjm_tables).
+
+*/
+static COND *
+make_cond_after_sjm(Item *root_cond, Item *cond, table_map tables,
+ table_map sjm_tables, bool inside_or_clause)
+{
+ /*
+ We assume that conditions that refer to only join prefix tables or
+ sjm_tables have already been checked.
+ */
+ if (!inside_or_clause &&
+ (!(cond->used_tables() & ~tables) ||
+ !(cond->used_tables() & ~sjm_tables)))
+ return (COND*) 0; // Already checked
+
+ /* AND/OR recursive descent */
+ if (cond->type() == Item::COND_ITEM)
+ {
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ /* Create new top level AND item */
+ Item_cond_and *new_cond=new Item_cond_and;
+ if (!new_cond)
+ return (COND*) 0; // OOM /* purecov: inspected */
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix=make_cond_after_sjm(root_cond, item, tables, sjm_tables,
+ inside_or_clause);
+ if (fix)
+ new_cond->argument_list()->push_back(fix);
+ }
+ switch (new_cond->argument_list()->elements) {
+ case 0:
+ return (COND*) 0; // Always true
+ case 1:
+ return new_cond->argument_list()->head();
+ default:
+ /*
+ Item_cond_and do not need fix_fields for execution, its parameters
+ are fixed or do not need fix_fields, too
+ */
+ new_cond->quick_fix_field();
+ new_cond->used_tables_cache=
+ ((Item_cond_and*) cond)->used_tables_cache &
+ tables;
+ return new_cond;
+ }
+ }
+ else
+ { // Or list
+ Item_cond_or *new_cond=new Item_cond_or;
+ if (!new_cond)
+ return (COND*) 0; // OOM /* purecov: inspected */
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix= make_cond_after_sjm(root_cond, item, tables, sjm_tables,
+ /*inside_or_clause= */TRUE);
+ if (!fix)
+ return (COND*) 0; // Always true
+ new_cond->argument_list()->push_back(fix);
+ }
+ /*
+ Item_cond_or do not need fix_fields for execution, its parameters
+ are fixed or do not need fix_fields, too
+ */
+ new_cond->quick_fix_field();
+ new_cond->used_tables_cache= ((Item_cond_or*) cond)->used_tables_cache;
+ new_cond->top_level_item();
+ return new_cond;
+ }
+ }
+
+ /*
+ Because the following test takes a while and it can be done
+ table_count times, we mark each item that we have examined with the result
+ of the test
+ */
+
+ if (cond->marker == 3 || (cond->used_tables() & ~(tables | sjm_tables)))
+ return (COND*) 0; // Can't check this yet
+ if (cond->marker == 2 || cond->eq_cmp_result() == Item::COND_OK)
+ return cond; // Not boolean op
+
+ /*
+ Remove equalities that are guaranteed to be true by use of 'ref' access
+ method
+ */
+ if (((Item_func*) cond)->functype() == Item_func::EQ_FUNC)
+ {
+ Item *left_item= ((Item_func*) cond)->arguments()[0]->real_item();
+ Item *right_item= ((Item_func*) cond)->arguments()[1]->real_item();
+ if (left_item->type() == Item::FIELD_ITEM &&
+ test_if_ref(root_cond, (Item_field*) left_item,right_item))
+ {
+ cond->marker=3; // Checked when read
+ return (COND*) 0;
+ }
+ if (right_item->type() == Item::FIELD_ITEM &&
+ test_if_ref(root_cond, (Item_field*) right_item,left_item))
+ {
+ cond->marker=3; // Checked when read
+ return (COND*) 0;
+ }
+ }
+ cond->marker=2;
+ return cond;
+}
+
+
+/*
+ @brief
+
+ Check if
+ - @table uses "ref"-like access
+ - it is based on "@field=certain_item" equality
+ - the equality will be true for any record returned by the access method
+ and return the certain_item if yes.
+
+ @detail
+
+ Equality won't necessarily hold if:
+ - the used index covers only part of the @field.
+ Suppose, we have a CHAR(5) field and INDEX(field(3)). if you make a lookup
+ for 'abc', you will get both record with 'abc' and with 'abcde'.
+ - The type of access is actually ref_or_null, and so @field can be either
+ a value or NULL.
+
+ @return
+ Item that the field will be equal to
+ NULL if no such item
+*/
+
+static Item *
+part_of_refkey(TABLE *table,Field *field)
+{
+ JOIN_TAB *join_tab= table->reginfo.join_tab;
+ if (!join_tab)
+ return (Item*) 0; // field from outer non-select (UPDATE,...)
+
+ uint ref_parts= join_tab->ref.key_parts;
+ if (ref_parts) /* if it's ref/eq_ref/ref_or_null */
+ {
+ uint key= join_tab->ref.key;
+ KEY *key_info= join_tab->get_keyinfo_by_key_no(key);
+ KEY_PART_INFO *key_part= key_info->key_part;
+
+ for (uint part=0 ; part < ref_parts ; part++,key_part++)
+ {
+ if (field->eq(key_part->field))
+ {
+ /*
+ Found the field in the key. Check that
+ 1. ref_or_null doesn't alternate this component between a value and
+ a NULL
+ 2. index fully covers the key
+ */
+ if (part != join_tab->ref.null_ref_part && // (1)
+ !(key_part->key_part_flag & HA_PART_KEY_SEG)) // (2)
+ {
+ return join_tab->ref.items[part];
+ }
+ break;
+ }
+ }
+ }
+ return (Item*) 0;
+}
+
+
+/**
+ Test if one can use the key to resolve ORDER BY.
+
+ @param order Sort order
+ @param table Table to sort
+ @param idx Index to check
+ @param used_key_parts [out] NULL by default, otherwise return value for
+ used key parts.
+
+
+ @note
+ used_key_parts is set to correct key parts used if return value != 0
+ (On other cases, used_key_part may be changed)
+ Note that the value may actually be greater than the number of index
+ key parts. This can happen for storage engines that have the primary
+ key parts as a suffix for every secondary key.
+
+ @retval
+ 1 key is ok.
+ @retval
+ 0 Key can't be used
+ @retval
+ -1 Reverse key can be used
+*/
+
+static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx,
+ uint *used_key_parts= NULL)
+{
+ KEY_PART_INFO *key_part,*key_part_end;
+ key_part=table->key_info[idx].key_part;
+ key_part_end=key_part+table->key_info[idx].user_defined_key_parts;
+ key_part_map const_key_parts=table->const_key_parts[idx];
+ int reverse=0;
+ uint key_parts;
+ my_bool on_pk_suffix= FALSE;
+ DBUG_ENTER("test_if_order_by_key");
+
+ for (; order ; order=order->next, const_key_parts>>=1)
+ {
+ Field *field=((Item_field*) (*order->item)->real_item())->field;
+ int flag;
+
+ /*
+ Skip key parts that are constants in the WHERE clause.
+ These are already skipped in the ORDER BY by const_expression_in_where()
+ */
+ for (; const_key_parts & 1 ; const_key_parts>>= 1)
+ key_part++;
+
+ if (key_part >= key_part_end)
+ {
+ /*
+ We are at the end of the key. Check if the engine has the primary
+ key as a suffix to the secondary keys. If it has continue to check
+ the primary key as a suffix.
+ */
+ if (!on_pk_suffix && (table->key_info[idx].ext_key_part_map & 1) &&
+ (table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
+ table->s->primary_key != MAX_KEY &&
+ table->s->primary_key != idx)
+ {
+ KEY_PART_INFO *start,*end;
+ uint pk_part_idx= 0;
+ on_pk_suffix= TRUE;
+ start= key_part= table->key_info[table->s->primary_key].key_part;
+ const_key_parts=table->const_key_parts[table->s->primary_key];
+
+ /*
+ Calculate true key_part_end and const_key_parts
+ (we have to stop as first not continous primary key part)
+ */
+ for (key_part_end= key_part,
+ end= key_part+table->key_info[table->s->primary_key].user_defined_key_parts;
+ key_part_end < end; key_part_end++, pk_part_idx++)
+ {
+ /* Found hole in the pk_parts; Abort */
+ if (!(table->key_info[idx].ext_key_part_map &
+ (((key_part_map) 1) << pk_part_idx)))
+ break;
+ }
+
+ /* Adjust const_key_parts */
+ const_key_parts&= (((key_part_map) 1) << pk_part_idx) -1;
+
+ for (; const_key_parts & 1 ; const_key_parts>>= 1)
+ key_part++;
+ /*
+ Test if the primary key parts were all const (i.e. there's one row).
+ The sorting doesn't matter.
+ */
+ if (key_part == start+table->key_info[table->s->primary_key].user_defined_key_parts &&
+ reverse == 0)
+ {
+ key_parts= 0;
+ reverse= 1; // Key is ok to use
+ goto ok;
+ }
+ }
+ else
+ DBUG_RETURN(0);
+ }
+
+ if (key_part->field != field || !field->part_of_sortkey.is_set(idx))
+ DBUG_RETURN(0);
+
+ /* set flag to 1 if we can use read-next on key, else to -1 */
+ flag= ((order->asc == !(key_part->key_part_flag & HA_REVERSE_SORT)) ?
+ 1 : -1);
+ if (reverse && flag != reverse)
+ DBUG_RETURN(0);
+ reverse=flag; // Remember if reverse
+ if (key_part < key_part_end)
+ key_part++;
+ }
+ if (on_pk_suffix)
+ {
+ uint used_key_parts_secondary= table->key_info[idx].user_defined_key_parts;
+ uint used_key_parts_pk=
+ (uint) (key_part - table->key_info[table->s->primary_key].key_part);
+ key_parts= used_key_parts_pk + used_key_parts_secondary;
+
+ if (reverse == -1 &&
+ (!(table->file->index_flags(idx, used_key_parts_secondary - 1, 1) &
+ HA_READ_PREV) ||
+ !(table->file->index_flags(table->s->primary_key,
+ used_key_parts_pk - 1, 1) & HA_READ_PREV)))
+ reverse= 0; // Index can't be used
+ }
+ else
+ {
+ key_parts= (uint) (key_part - table->key_info[idx].key_part);
+ if (reverse == -1 &&
+ !(table->file->index_flags(idx, key_parts-1, 1) & HA_READ_PREV))
+ reverse= 0; // Index can't be used
+ }
+ok:
+ if (used_key_parts != NULL)
+ *used_key_parts= key_parts;
+ DBUG_RETURN(reverse);
+}
+
+
+/**
+ Find shortest key suitable for full table scan.
+
+ @param table Table to scan
+ @param usable_keys Allowed keys
+
+ @return
+ MAX_KEY no suitable key found
+ key index otherwise
+*/
+
+uint find_shortest_key(TABLE *table, const key_map *usable_keys)
+{
+ double min_cost= DBL_MAX;
+ uint best= MAX_KEY;
+ if (!usable_keys->is_clear_all())
+ {
+ for (uint nr=0; nr < table->s->keys ; nr++)
+ {
+ if (usable_keys->is_set(nr))
+ {
+ double cost= table->file->keyread_time(nr, 1, table->file->records());
+ if (cost < min_cost)
+ {
+ min_cost= cost;
+ best=nr;
+ }
+ }
+ }
+ }
+ return best;
+}
+
+/**
+ Test if a second key is the subkey of the first one.
+
+ @param key_part First key parts
+ @param ref_key_part Second key parts
+ @param ref_key_part_end Last+1 part of the second key
+
+ @note
+ Second key MUST be shorter than the first one.
+
+ @retval
+ 1 is a subkey
+ @retval
+ 0 no sub key
+*/
+
+inline bool
+is_subkey(KEY_PART_INFO *key_part, KEY_PART_INFO *ref_key_part,
+ KEY_PART_INFO *ref_key_part_end)
+{
+ for (; ref_key_part < ref_key_part_end; key_part++, ref_key_part++)
+ if (!key_part->field->eq(ref_key_part->field))
+ return 0;
+ return 1;
+}
+
+/**
+ Test if we can use one of the 'usable_keys' instead of 'ref' key
+ for sorting.
+
+ @param ref Number of key, used for WHERE clause
+ @param usable_keys Keys for testing
+
+ @return
+ - MAX_KEY If we can't use other key
+ - the number of found key Otherwise
+*/
+
+static uint
+test_if_subkey(ORDER *order, TABLE *table, uint ref, uint ref_key_parts,
+ const key_map *usable_keys)
+{
+ uint nr;
+ uint min_length= (uint) ~0;
+ uint best= MAX_KEY;
+ KEY_PART_INFO *ref_key_part= table->key_info[ref].key_part;
+ KEY_PART_INFO *ref_key_part_end= ref_key_part + ref_key_parts;
+
+ for (nr= 0 ; nr < table->s->keys ; nr++)
+ {
+ if (usable_keys->is_set(nr) &&
+ table->key_info[nr].key_length < min_length &&
+ table->key_info[nr].user_defined_key_parts >= ref_key_parts &&
+ is_subkey(table->key_info[nr].key_part, ref_key_part,
+ ref_key_part_end) &&
+ test_if_order_by_key(order, table, nr))
+ {
+ min_length= table->key_info[nr].key_length;
+ best= nr;
+ }
+ }
+ return best;
+}
+
+
+/**
+ Check if GROUP BY/DISTINCT can be optimized away because the set is
+ already known to be distinct.
+
+ Used in removing the GROUP BY/DISTINCT of the following types of
+ statements:
+ @code
+ SELECT [DISTINCT] <unique_key_cols>... FROM <single_table_ref>
+ [GROUP BY <unique_key_cols>,...]
+ @endcode
+
+ If (a,b,c is distinct)
+ then <any combination of a,b,c>,{whatever} is also distinct
+
+ This function checks if all the key parts of any of the unique keys
+ of the table are referenced by a list : either the select list
+ through find_field_in_item_list or GROUP BY list through
+ find_field_in_order_list.
+ If the above holds and the key parts cannot contain NULLs then we
+ can safely remove the GROUP BY/DISTINCT,
+ as no result set can be more distinct than an unique key.
+
+ @param table The table to operate on.
+ @param find_func function to iterate over the list and search
+ for a field
+
+ @retval
+ 1 found
+ @retval
+ 0 not found.
+*/
+
+static bool
+list_contains_unique_index(TABLE *table,
+ bool (*find_func) (Field *, void *), void *data)
+{
+ for (uint keynr= 0; keynr < table->s->keys; keynr++)
+ {
+ if (keynr == table->s->primary_key ||
+ (table->key_info[keynr].flags & HA_NOSAME))
+ {
+ KEY *keyinfo= table->key_info + keynr;
+ KEY_PART_INFO *key_part, *key_part_end;
+
+ for (key_part=keyinfo->key_part,
+ key_part_end=key_part+ keyinfo->user_defined_key_parts;
+ key_part < key_part_end;
+ key_part++)
+ {
+ if (key_part->field->maybe_null() ||
+ !find_func(key_part->field, data))
+ break;
+ }
+ if (key_part == key_part_end)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ Helper function for list_contains_unique_index.
+ Find a field reference in a list of ORDER structures.
+ Finds a direct reference of the Field in the list.
+
+ @param field The field to search for.
+ @param data ORDER *.The list to search in
+
+ @retval
+ 1 found
+ @retval
+ 0 not found.
+*/
+
+static bool
+find_field_in_order_list (Field *field, void *data)
+{
+ ORDER *group= (ORDER *) data;
+ bool part_found= 0;
+ for (ORDER *tmp_group= group; tmp_group; tmp_group=tmp_group->next)
+ {
+ Item *item= (*tmp_group->item)->real_item();
+ if (item->type() == Item::FIELD_ITEM &&
+ ((Item_field*) item)->field->eq(field))
+ {
+ part_found= 1;
+ break;
+ }
+ }
+ return part_found;
+}
+
+
+/**
+ Helper function for list_contains_unique_index.
+ Find a field reference in a dynamic list of Items.
+ Finds a direct reference of the Field in the list.
+
+ @param[in] field The field to search for.
+ @param[in] data List<Item> *.The list to search in
+
+ @retval
+ 1 found
+ @retval
+ 0 not found.
+*/
+
+static bool
+find_field_in_item_list (Field *field, void *data)
+{
+ List<Item> *fields= (List<Item> *) data;
+ bool part_found= 0;
+ List_iterator<Item> li(*fields);
+ Item *item;
+
+ while ((item= li++))
+ {
+ if (item->real_item()->type() == Item::FIELD_ITEM &&
+ ((Item_field*) (item->real_item()))->field->eq(field))
+ {
+ part_found= 1;
+ break;
+ }
+ }
+ return part_found;
+}
+
+
+/**
+ Test if we can skip the ORDER BY by using an index.
+
+ If we can use an index, the JOIN_TAB / tab->select struct
+ is changed to use the index.
+
+ The index must cover all fields in <order>, or it will not be considered.
+
+ @param no_changes No changes will be made to the query plan.
+
+ @todo
+ - sergeyp: Results of all index merge selects actually are ordered
+ by clustered PK values.
+
+ @retval
+ 0 We have to use filesort to do the sorting
+ @retval
+ 1 We can use an index.
+*/
+
+static bool
+test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
+ bool no_changes, const key_map *map)
+{
+ int ref_key;
+ uint UNINIT_VAR(ref_key_parts);
+ int order_direction= 0;
+ uint used_key_parts= 0;
+ TABLE *table=tab->table;
+ SQL_SELECT *select=tab->select;
+ key_map usable_keys;
+ QUICK_SELECT_I *save_quick= select ? select->quick : 0;
+ Item *orig_cond= 0;
+ bool orig_cond_saved= false;
+ int best_key= -1;
+ bool changed_key= false;
+ DBUG_ENTER("test_if_skip_sort_order");
+
+ /* Check that we are always called with first non-const table */
+ DBUG_ASSERT(tab == tab->join->join_tab + tab->join->const_tables);
+
+ /*
+ Keys disabled by ALTER TABLE ... DISABLE KEYS should have already
+ been taken into account.
+ */
+ usable_keys= *map;
+
+ for (ORDER *tmp_order=order; tmp_order ; tmp_order=tmp_order->next)
+ {
+ Item *item= (*tmp_order->item)->real_item();
+ if (item->type() != Item::FIELD_ITEM)
+ {
+ usable_keys.clear_all();
+ DBUG_RETURN(0);
+ }
+ usable_keys.intersect(((Item_field*) item)->field->part_of_sortkey);
+ if (usable_keys.is_clear_all())
+ goto use_filesort; // No usable keys
+ }
+
+ ref_key= -1;
+ /* Test if constant range in WHERE */
+ if (tab->ref.key >= 0 && tab->ref.key_parts)
+ {
+ ref_key= tab->ref.key;
+ ref_key_parts= tab->ref.key_parts;
+ if (tab->type == JT_REF_OR_NULL || tab->type == JT_FT)
+ goto use_filesort;
+ }
+ else if (select && select->quick) // Range found by opt_range
+ {
+ int quick_type= select->quick->get_type();
+ /*
+ assume results are not ordered when index merge is used
+ TODO: sergeyp: Results of all index merge selects actually are ordered
+ by clustered PK values.
+ */
+
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT)
+ ref_key= MAX_KEY;
+ else
+ {
+ ref_key= select->quick->index;
+ ref_key_parts= select->quick->used_key_parts;
+ }
+ }
+
+ if (ref_key >= 0 && ref_key != MAX_KEY)
+ {
+ /*
+ We come here when there is a REF key.
+ */
+ if (!usable_keys.is_set(ref_key))
+ {
+ /*
+ We come here when ref_key is not among usable_keys
+ */
+ uint new_ref_key;
+ /*
+ If using index only read, only consider other possible index only
+ keys
+ */
+ if (table->covering_keys.is_set(ref_key))
+ usable_keys.intersect(table->covering_keys);
+ if (tab->pre_idx_push_select_cond)
+ {
+ orig_cond= tab->set_cond(tab->pre_idx_push_select_cond);
+ orig_cond_saved= true;
+ }
+
+ if ((new_ref_key= test_if_subkey(order, table, ref_key, ref_key_parts,
+ &usable_keys)) < MAX_KEY)
+ {
+ if (tab->ref.key >= 0)
+ {
+ /*
+ We'll use ref access method on key new_ref_key. In general case
+ the index search tuple for new_ref_key will be different (e.g.
+ when one index is defined as (part1, part2, ...) and another as
+ (part1, part2(N), ...) and the WHERE clause contains
+ "part1 = const1 AND part2=const2".
+ So we build tab->ref from scratch here.
+ */
+ KEYUSE *keyuse= tab->keyuse;
+ while (keyuse->key != new_ref_key && keyuse->table == tab->table)
+ keyuse++;
+ if (create_ref_for_key(tab->join, tab, keyuse, FALSE,
+ (tab->join->const_table_map |
+ OUTER_REF_TABLE_BIT)))
+ goto use_filesort;
+
+ pick_table_access_method(tab);
+ }
+ else
+ {
+ /*
+ The range optimizer constructed QUICK_RANGE for ref_key, and
+ we want to use instead new_ref_key as the index. We can't
+ just change the index of the quick select, because this may
+ result in an inconsistent QUICK_SELECT object. Below we
+ create a new QUICK_SELECT from scratch so that all its
+ parameters are set correctly by the range optimizer.
+ */
+ key_map new_ref_key_map;
+ COND *save_cond;
+ bool res;
+ new_ref_key_map.clear_all(); // Force the creation of quick select
+ new_ref_key_map.set_bit(new_ref_key); // only for new_ref_key.
+
+ /* Reset quick; This will be restored in 'use_filesort' if needed */
+ select->quick= 0;
+ save_cond= select->cond;
+ if (select->pre_idx_push_select_cond)
+ select->cond= select->pre_idx_push_select_cond;
+ res= select->test_quick_select(tab->join->thd, new_ref_key_map, 0,
+ (tab->join->select_options &
+ OPTION_FOUND_ROWS) ?
+ HA_POS_ERROR :
+ tab->join->unit->select_limit_cnt,0,
+ TRUE) <= 0;
+ if (res)
+ {
+ select->cond= save_cond;
+ goto use_filesort;
+ }
+ /*
+ We don't restore select->cond as we want to use the
+ original condition as index condition pushdown is not
+ active for the new index.
+ */
+ }
+ ref_key= new_ref_key;
+ changed_key= true;
+ }
+ }
+ /* Check if we get the rows in requested sorted order by using the key */
+ if (usable_keys.is_set(ref_key) &&
+ (order_direction= test_if_order_by_key(order,table,ref_key,
+ &used_key_parts)))
+ goto check_reverse_order;
+ }
+ {
+ uint UNINIT_VAR(best_key_parts);
+ uint saved_best_key_parts= 0;
+ int best_key_direction= 0;
+ JOIN *join= tab->join;
+ ha_rows table_records= table->stat_records();
+
+ test_if_cheaper_ordering(tab, order, table, usable_keys,
+ ref_key, select_limit,
+ &best_key, &best_key_direction,
+ &select_limit, &best_key_parts,
+ &saved_best_key_parts);
+
+ /*
+ filesort() and join cache are usually faster than reading in
+ index order and not using join cache, except in case that chosen
+ index is clustered key.
+ */
+ if (best_key < 0 ||
+ ((select_limit >= table_records) &&
+ (tab->type == JT_ALL &&
+ tab->join->table_count > tab->join->const_tables + 1) &&
+ !(table->file->index_flags(best_key, 0, 1) & HA_CLUSTERED_INDEX)))
+ goto use_filesort;
+
+ if (select &&
+ table->quick_keys.is_set(best_key) && best_key != ref_key)
+ {
+ key_map map;
+ map.clear_all(); // Force the creation of quick select
+ map.set_bit(best_key); // only best_key.
+ select->quick= 0;
+ select->test_quick_select(join->thd, map, 0,
+ join->select_options & OPTION_FOUND_ROWS ?
+ HA_POS_ERROR :
+ join->unit->select_limit_cnt,
+ TRUE, FALSE);
+ }
+ order_direction= best_key_direction;
+ /*
+ saved_best_key_parts is actual number of used keyparts found by the
+ test_if_order_by_key function. It could differ from keyinfo->user_defined_key_parts,
+ thus we have to restore it in case of desc order as it affects
+ QUICK_SELECT_DESC behaviour.
+ */
+ used_key_parts= (order_direction == -1) ?
+ saved_best_key_parts : best_key_parts;
+ changed_key= true;
+ }
+
+check_reverse_order:
+ DBUG_ASSERT(order_direction != 0);
+
+ if (order_direction == -1) // If ORDER BY ... DESC
+ {
+ int quick_type;
+ if (select && select->quick)
+ {
+ /*
+ Don't reverse the sort order, if it's already done.
+ (In some cases test_if_order_by_key() can be called multiple times
+ */
+ if (select->quick->reverse_sorted())
+ goto skipped_filesort;
+
+ quick_type= select->quick->get_type();
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
+ {
+ tab->limit= 0;
+ goto use_filesort; // Use filesort
+ }
+ }
+ }
+
+ /*
+ Update query plan with access pattern for doing ordered access
+ according to what we have decided above.
+ */
+ if (!no_changes) // We are allowed to update QEP
+ {
+ if (best_key >= 0)
+ {
+ bool quick_created=
+ (select && select->quick && select->quick!=save_quick);
+
+ /*
+ If ref_key used index tree reading only ('Using index' in EXPLAIN),
+ and best_key doesn't, then revert the decision.
+ */
+ if (!table->covering_keys.is_set(best_key))
+ table->disable_keyread();
+ if (!quick_created)
+ {
+ if (select) // Throw any existing quick select
+ select->quick= 0; // Cleanup either reset to save_quick,
+ // or 'delete save_quick'
+ tab->index= best_key;
+ tab->read_first_record= order_direction > 0 ?
+ join_read_first:join_read_last;
+ tab->type=JT_NEXT; // Read with index_first(), index_next()
+
+ if (tab->pre_idx_push_select_cond)
+ {
+ tab->set_cond(tab->pre_idx_push_select_cond);
+ /*
+ orig_cond is a part of pre_idx_push_cond,
+ no need to restore it.
+ */
+ orig_cond= 0;
+ orig_cond_saved= false;
+ }
+
+ table->file->ha_index_or_rnd_end();
+ if (tab->join->select_options & SELECT_DESCRIBE)
+ {
+ tab->ref.key= -1;
+ tab->ref.key_parts= 0;
+ if (select_limit < table->stat_records())
+ tab->limit= select_limit;
+ table->disable_keyread();
+ }
+ }
+ else if (tab->type != JT_ALL || tab->select->quick)
+ {
+ /*
+ We're about to use a quick access to the table.
+ We need to change the access method so as the quick access
+ method is actually used.
+ */
+ DBUG_ASSERT(tab->select->quick);
+ tab->type=JT_ALL;
+ tab->use_quick=1;
+ tab->ref.key= -1;
+ tab->ref.key_parts=0; // Don't use ref key.
+ tab->read_first_record= join_init_read_record;
+ if (tab->is_using_loose_index_scan())
+ tab->join->tmp_table_param.precomputed_group_by= TRUE;
+
+ /*
+ Restore the original condition as changes done by pushdown
+ condition are not relevant anymore
+ */
+ if (tab->select && tab->select->pre_idx_push_select_cond)
+ {
+ tab->set_cond(tab->select->pre_idx_push_select_cond);
+ tab->table->file->cancel_pushed_idx_cond();
+ }
+ /*
+ TODO: update the number of records in join->best_positions[tablenr]
+ */
+ }
+ } // best_key >= 0
+
+ if (order_direction == -1) // If ORDER BY ... DESC
+ {
+ if (select && select->quick)
+ {
+ /* ORDER BY range_key DESC */
+ QUICK_SELECT_I *tmp= select->quick->make_reverse(used_key_parts);
+ if (!tmp)
+ {
+ tab->limit= 0;
+ goto use_filesort; // Reverse sort failed -> filesort
+ }
+ /*
+ Cancel Pushed Index Condition, as it doesn't work for reverse scans.
+ */
+ if (tab->select && tab->select->pre_idx_push_select_cond)
+ {
+ tab->set_cond(tab->select->pre_idx_push_select_cond);
+ tab->table->file->cancel_pushed_idx_cond();
+ }
+ if (select->quick == save_quick)
+ save_quick= 0; // make_reverse() consumed it
+ select->set_quick(tmp);
+ }
+ else if (tab->type != JT_NEXT && tab->type != JT_REF_OR_NULL &&
+ tab->ref.key >= 0 && tab->ref.key_parts <= used_key_parts)
+ {
+ /*
+ SELECT * FROM t1 WHERE a=1 ORDER BY a DESC,b DESC
+
+ Use a traversal function that starts by reading the last row
+ with key part (A) and then traverse the index backwards.
+ */
+ tab->read_first_record= join_read_last_key;
+ tab->read_record.read_record= join_read_prev_same;
+ /*
+ Cancel Pushed Index Condition, as it doesn't work for reverse scans.
+ */
+ if (tab->select && tab->select->pre_idx_push_select_cond)
+ {
+ tab->set_cond(tab->select->pre_idx_push_select_cond);
+ tab->table->file->cancel_pushed_idx_cond();
+ }
+ }
+ }
+ else if (select && select->quick)
+ select->quick->need_sorted_output();
+
+ } // QEP has been modified
+
+ /*
+ Cleanup:
+ We may have both a 'select->quick' and 'save_quick' (original)
+ at this point. Delete the one that we wan't use.
+ */
+
+skipped_filesort:
+ // Keep current (ordered) select->quick
+ if (select && save_quick != select->quick)
+ {
+ delete save_quick;
+ save_quick= NULL;
+ }
+ if (orig_cond_saved && !changed_key)
+ tab->set_cond(orig_cond);
+ if (!no_changes && changed_key && table->file->pushed_idx_cond)
+ table->file->cancel_pushed_idx_cond();
+
+ DBUG_RETURN(1);
+
+use_filesort:
+ // Restore original save_quick
+ if (select && select->quick != save_quick)
+ {
+ delete select->quick;
+ select->quick= save_quick;
+ }
+ if (orig_cond_saved)
+ tab->set_cond(orig_cond);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ If not selecting by given key, create an index how records should be read
+
+ SYNOPSIS
+ create_sort_index()
+ thd Thread handler
+ join Join with table to sort
+ order How table should be sorted
+ filesort_limit Max number of rows that needs to be sorted
+ select_limit Max number of rows in final output
+ Used to decide if we should use index or not
+ is_order_by true if we are sorting on ORDER BY, false if GROUP BY
+ Used to decide if we should use index or not
+
+
+ IMPLEMENTATION
+ - If there is an index that can be used, the first non-const join_tab in
+ 'join' is modified to use this index.
+ - If no index, create with filesort() an index file that can be used to
+ retrieve rows in order (should be done with 'read_record').
+ The sorted data is stored in tab->table and will be freed when calling
+ free_io_cache(tab->table).
+
+ RETURN VALUES
+ 0 ok
+ -1 Some fatal error
+ 1 No records
+*/
+
+static int
+create_sort_index(THD *thd, JOIN *join, ORDER *order,
+ ha_rows filesort_limit, ha_rows select_limit,
+ bool is_order_by)
+{
+ uint length= 0;
+ ha_rows examined_rows;
+ ha_rows found_rows;
+ ha_rows filesort_retval= HA_POS_ERROR;
+ TABLE *table;
+ SQL_SELECT *select;
+ JOIN_TAB *tab;
+ int err= 0;
+ bool quick_created= FALSE;
+ DBUG_ENTER("create_sort_index");
+
+ if (join->table_count == join->const_tables)
+ DBUG_RETURN(0); // One row, no need to sort
+ tab= join->join_tab + join->const_tables;
+ table= tab->table;
+ select= tab->select;
+
+ JOIN_TAB *save_pre_sort_join_tab= NULL;
+ if (join->pre_sort_join_tab)
+ {
+ /*
+ we've already been in this function, and stashed away the original access
+ method in join->pre_sort_join_tab, restore it now.
+ */
+
+ /* First, restore state of the handler */
+ if (join->pre_sort_index != MAX_KEY)
+ {
+ if (table->file->ha_index_or_rnd_end())
+ goto err;
+ if (join->pre_sort_idx_pushed_cond)
+ {
+ table->file->idx_cond_push(join->pre_sort_index,
+ join->pre_sort_idx_pushed_cond);
+ }
+ }
+ else
+ {
+ if (table->file->ha_index_or_rnd_end() ||
+ table->file->ha_rnd_init(TRUE))
+ goto err;
+ }
+
+ /* Second, restore access method parameters */
+ tab->records= join->pre_sort_join_tab->records;
+ tab->select= join->pre_sort_join_tab->select;
+ tab->select_cond= join->pre_sort_join_tab->select_cond;
+ tab->type= join->pre_sort_join_tab->type;
+ tab->read_first_record= join->pre_sort_join_tab->read_first_record;
+
+ save_pre_sort_join_tab= join->pre_sort_join_tab;
+ join->pre_sort_join_tab= NULL;
+ }
+ else
+ {
+ /*
+ Save index #, save index condition. Do it right now, because MRR may
+ */
+ if (table->file->inited == handler::INDEX)
+ {
+ join->pre_sort_index= table->file->active_index;
+ join->pre_sort_idx_pushed_cond= table->file->pushed_idx_cond;
+ // no need to save key_read
+ }
+ else
+ join->pre_sort_index= MAX_KEY;
+ }
+
+ /* Currently ORDER BY ... LIMIT is not supported in subqueries. */
+ DBUG_ASSERT(join->group_list || !join->is_in_subquery());
+
+ /*
+ When there is SQL_BIG_RESULT do not sort using index for GROUP BY,
+ and thus force sorting on disk unless a group min-max optimization
+ is going to be used as it is applied now only for one table queries
+ with covering indexes.
+ */
+ if ((order != join->group_list ||
+ !(join->select_options & SELECT_BIG_RESULT) ||
+ (select && select->quick &&
+ select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)) &&
+ test_if_skip_sort_order(tab,order,select_limit,0,
+ is_order_by ? &table->keys_in_use_for_order_by :
+ &table->keys_in_use_for_group_by))
+ DBUG_RETURN(0);
+ for (ORDER *ord= join->order; ord; ord= ord->next)
+ length++;
+ if (!(join->sortorder=
+ make_unireg_sortorder(order, &length, join->sortorder)))
+ goto err; /* purecov: inspected */
+
+ table->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE),
+ MYF(MY_WME | MY_ZEROFILL|
+ MY_THREAD_SPECIFIC));
+ table->status=0; // May be wrong if quick_select
+
+ if (!tab->preread_init_done && tab->preread_init())
+ goto err;
+
+ // If table has a range, move it to select
+ if (select && !select->quick && tab->ref.key >= 0)
+ {
+ if (tab->quick)
+ {
+ select->quick=tab->quick;
+ tab->quick=0;
+ /*
+ We can only use 'Only index' if quick key is same as ref_key
+ and in index_merge 'Only index' cannot be used
+ */
+ if (((uint) tab->ref.key != select->quick->index))
+ table->disable_keyread();
+ }
+ else
+ {
+ /*
+ We have a ref on a const; Change this to a range that filesort
+ can use.
+ For impossible ranges (like when doing a lookup on NULL on a NOT NULL
+ field, quick will contain an empty record set.
+ */
+ if (!(select->quick= (tab->type == JT_FT ?
+ get_ft_select(thd, table, tab->ref.key) :
+ get_quick_select_for_ref(thd, table, &tab->ref,
+ tab->found_records))))
+ goto err;
+ quick_created= TRUE;
+ }
+ }
+
+ /* Fill schema tables with data before filesort if it's necessary */
+ if ((join->select_lex->options & OPTION_SCHEMA_TABLE) &&
+ get_schema_tables_result(join, PROCESSED_BY_CREATE_SORT_INDEX))
+ goto err;
+
+ if (table->s->tmp_table)
+ table->file->info(HA_STATUS_VARIABLE); // Get record count
+ filesort_retval= filesort(thd, table, join->sortorder, length,
+ select, filesort_limit, 0,
+ &examined_rows, &found_rows);
+ table->sort.found_records= filesort_retval;
+ tab->records= found_rows; // For SQL_CALC_ROWS
+
+ if (quick_created)
+ {
+ /* This will delete the quick select. */
+ select->cleanup();
+ }
+
+ if (!join->pre_sort_join_tab)
+ {
+ if (save_pre_sort_join_tab)
+ join->pre_sort_join_tab= save_pre_sort_join_tab;
+ else if (!(join->pre_sort_join_tab= (JOIN_TAB*)thd->alloc(sizeof(JOIN_TAB))))
+ goto err;
+ }
+
+ *(join->pre_sort_join_tab)= *tab;
+
+ tab->select=NULL;
+ tab->set_select_cond(NULL, __LINE__);
+ tab->type=JT_ALL; // Read with normal read_record
+ tab->read_first_record= join_init_read_record;
+ tab->table->file->ha_index_or_rnd_end();
+
+ if (err)
+ goto err;
+
+ tab->join->examined_rows+=examined_rows;
+ DBUG_RETURN(filesort_retval == HA_POS_ERROR);
+err:
+ DBUG_RETURN(-1);
+}
+
+void JOIN::clean_pre_sort_join_tab()
+{
+ //TABLE *table= pre_sort_join_tab->table;
+ /*
+ Note: we can come here for fake_select_lex object. That object will have
+ the table already deleted by st_select_lex_unit::cleanup().
+ We rely on that fake_select_lex didn't have quick select.
+ */
+ if (pre_sort_join_tab->select && pre_sort_join_tab->select->quick)
+ {
+ pre_sort_join_tab->select->cleanup();
+ }
+}
+
+
+/**
+ Compare fields from table->record[0] and table->record[1],
+ possibly skipping few first fields.
+
+ @param table
+ @param ptr field to start the comparison from,
+ somewhere in the table->field[] array
+
+ @retval 1 different
+ @retval 0 identical
+*/
+static bool compare_record(TABLE *table, Field **ptr)
+{
+ for (; *ptr ; ptr++)
+ {
+ Field *f= *ptr;
+ if (f->is_null() != f->is_null(table->s->rec_buff_length) ||
+ (!f->is_null() && f->cmp_offset(table->s->rec_buff_length)))
+ return 1;
+ }
+ return 0;
+}
+
+static bool copy_blobs(Field **ptr)
+{
+ for (; *ptr ; ptr++)
+ {
+ if ((*ptr)->flags & BLOB_FLAG)
+ if (((Field_blob *) (*ptr))->copy())
+ return 1; // Error
+ }
+ return 0;
+}
+
+static void free_blobs(Field **ptr)
+{
+ for (; *ptr ; ptr++)
+ {
+ if ((*ptr)->flags & BLOB_FLAG)
+ ((Field_blob *) (*ptr))->free();
+ }
+}
+
+
+static int
+remove_duplicates(JOIN *join, TABLE *table, List<Item> &fields, Item *having)
+{
+ int error;
+ ulong keylength= 0;
+ uint field_count;
+ THD *thd= join->thd;
+
+ DBUG_ENTER("remove_duplicates");
+
+ table->reginfo.lock_type=TL_WRITE;
+
+ /* Calculate how many saved fields there is in list */
+ field_count=0;
+ List_iterator<Item> it(fields);
+ Item *item;
+ while ((item=it++))
+ {
+ if (item->get_tmp_table_field() && ! item->const_item())
+ field_count++;
+ }
+
+ if (!field_count && !(join->select_options & OPTION_FOUND_ROWS) && !having)
+ { // only const items with no OPTION_FOUND_ROWS
+ join->unit->select_limit_cnt= 1; // Only send first row
+ DBUG_RETURN(0);
+ }
+
+ Field **first_field=table->field+table->s->fields - field_count;
+ for (Field **ptr=first_field; *ptr; ptr++)
+ keylength+= (*ptr)->sort_length() + (*ptr)->maybe_null();
+
+ /*
+ Disable LIMIT ROWS EXAMINED in order to avoid interrupting prematurely
+ duplicate removal, and produce a possibly incomplete query result.
+ */
+ thd->lex->limit_rows_examined_cnt= ULONGLONG_MAX;
+ if (thd->killed == ABORT_QUERY)
+ thd->reset_killed();
+
+ free_io_cache(table); // Safety
+ table->file->info(HA_STATUS_VARIABLE);
+ if (table->s->db_type() == heap_hton ||
+ (!table->s->blob_fields &&
+ ((ALIGN_SIZE(keylength) + HASH_OVERHEAD) * table->file->stats.records <
+ thd->variables.sortbuff_size)))
+ error=remove_dup_with_hash_index(join->thd, table, field_count, first_field,
+ keylength, having);
+ else
+ error=remove_dup_with_compare(join->thd, table, first_field, having);
+
+ if (join->select_lex != join->select_lex->master_unit()->fake_select_lex)
+ thd->lex->set_limit_rows_examined();
+ free_blobs(first_field);
+ DBUG_RETURN(error);
+}
+
+
+static int remove_dup_with_compare(THD *thd, TABLE *table, Field **first_field,
+ Item *having)
+{
+ handler *file=table->file;
+ uchar *record=table->record[0];
+ int error;
+ DBUG_ENTER("remove_dup_with_compare");
+
+ if (file->ha_rnd_init_with_error(1))
+ DBUG_RETURN(1);
+
+ error= file->ha_rnd_next(record);
+ for (;;)
+ {
+ if (thd->check_killed())
+ {
+ thd->send_kill_message();
+ error=0;
+ goto err;
+ }
+ if (error)
+ {
+ if (error == HA_ERR_RECORD_DELETED)
+ {
+ error= file->ha_rnd_next(record);
+ continue;
+ }
+ if (error == HA_ERR_END_OF_FILE)
+ break;
+ goto err;
+ }
+ if (having && !having->val_int())
+ {
+ if ((error= file->ha_delete_row(record)))
+ goto err;
+ error= file->ha_rnd_next(record);
+ continue;
+ }
+ if (copy_blobs(first_field))
+ {
+ my_message(ER_OUTOFMEMORY, ER(ER_OUTOFMEMORY), MYF(ME_FATALERROR));
+ error=0;
+ goto err;
+ }
+ store_record(table,record[1]);
+
+ /* Read through rest of file and mark duplicated rows deleted */
+ bool found=0;
+ for (;;)
+ {
+ if ((error= file->ha_rnd_next(record)))
+ {
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ if (error == HA_ERR_END_OF_FILE)
+ break;
+ goto err;
+ }
+ if (compare_record(table, first_field) == 0)
+ {
+ if ((error= file->ha_delete_row(record)))
+ goto err;
+ }
+ else if (!found)
+ {
+ found=1;
+ if ((error= file->remember_rnd_pos()))
+ goto err;
+ }
+ }
+ if (!found)
+ break; // End of file
+ /* Restart search on saved row */
+ if ((error= file->restart_rnd_next(record)))
+ goto err;
+ }
+
+ file->extra(HA_EXTRA_NO_CACHE);
+ DBUG_RETURN(0);
+err:
+ file->extra(HA_EXTRA_NO_CACHE);
+ if (error)
+ file->print_error(error,MYF(0));
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Generate a hash index for each row to quickly find duplicate rows.
+
+ @note
+ Note that this will not work on tables with blobs!
+*/
+
+static int remove_dup_with_hash_index(THD *thd, TABLE *table,
+ uint field_count,
+ Field **first_field,
+ ulong key_length,
+ Item *having)
+{
+ uchar *key_buffer, *key_pos, *record=table->record[0];
+ int error;
+ handler *file= table->file;
+ ulong extra_length= ALIGN_SIZE(key_length)-key_length;
+ uint *field_lengths, *field_length;
+ HASH hash;
+ Field **ptr;
+ DBUG_ENTER("remove_dup_with_hash_index");
+
+ if (!my_multi_malloc(MYF(MY_WME),
+ &key_buffer,
+ (uint) ((key_length + extra_length) *
+ (long) file->stats.records),
+ &field_lengths,
+ (uint) (field_count*sizeof(*field_lengths)),
+ NullS))
+ DBUG_RETURN(1);
+
+ for (ptr= first_field, field_length=field_lengths ; *ptr ; ptr++)
+ (*field_length++)= (*ptr)->sort_length();
+
+ if (my_hash_init(&hash, &my_charset_bin, (uint) file->stats.records, 0,
+ key_length, (my_hash_get_key) 0, 0, 0))
+ {
+ my_free(key_buffer);
+ DBUG_RETURN(1);
+ }
+
+ if ((error= file->ha_rnd_init(1)))
+ goto err;
+
+ key_pos=key_buffer;
+ for (;;)
+ {
+ uchar *org_key_pos;
+ if (thd->check_killed())
+ {
+ thd->send_kill_message();
+ error=0;
+ goto err;
+ }
+ if ((error= file->ha_rnd_next(record)))
+ {
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ if (error == HA_ERR_END_OF_FILE)
+ break;
+ goto err;
+ }
+ if (having && !having->val_int())
+ {
+ if ((error= file->ha_delete_row(record)))
+ goto err;
+ continue;
+ }
+
+ /* copy fields to key buffer */
+ org_key_pos= key_pos;
+ field_length=field_lengths;
+ for (ptr= first_field ; *ptr ; ptr++)
+ {
+ (*ptr)->make_sort_key(key_pos, *field_length);
+ key_pos+= (*ptr)->maybe_null() + *field_length++;
+ }
+ /* Check if it exists before */
+ if (my_hash_search(&hash, org_key_pos, key_length))
+ {
+ /* Duplicated found ; Remove the row */
+ if ((error= file->ha_delete_row(record)))
+ goto err;
+ }
+ else
+ {
+ if (my_hash_insert(&hash, org_key_pos))
+ goto err;
+ }
+ key_pos+=extra_length;
+ }
+ my_free(key_buffer);
+ my_hash_free(&hash);
+ file->extra(HA_EXTRA_NO_CACHE);
+ (void) file->ha_rnd_end();
+ DBUG_RETURN(0);
+
+err:
+ my_free(key_buffer);
+ my_hash_free(&hash);
+ file->extra(HA_EXTRA_NO_CACHE);
+ (void) file->ha_rnd_end();
+ if (error)
+ file->print_error(error,MYF(0));
+ DBUG_RETURN(1);
+}
+
+
+SORT_FIELD *make_unireg_sortorder(ORDER *order, uint *length,
+ SORT_FIELD *sortorder)
+{
+ uint count;
+ SORT_FIELD *sort,*pos;
+ DBUG_ENTER("make_unireg_sortorder");
+
+ count=0;
+ for (ORDER *tmp = order; tmp; tmp=tmp->next)
+ count++;
+ if (!sortorder)
+ sortorder= (SORT_FIELD*) sql_alloc(sizeof(SORT_FIELD) *
+ (MY_MAX(count, *length) + 1));
+ pos= sort= sortorder;
+
+ if (!pos)
+ DBUG_RETURN(0);
+
+ for (;order;order=order->next,pos++)
+ {
+ Item *const item= order->item[0], *const real_item= item->real_item();
+ pos->field= 0; pos->item= 0;
+ if (real_item->type() == Item::FIELD_ITEM)
+ {
+ // Could be a field, or Item_direct_view_ref wrapping a field
+ DBUG_ASSERT(item->type() == Item::FIELD_ITEM ||
+ (item->type() == Item::REF_ITEM &&
+ static_cast<Item_ref*>(item)->ref_type() ==
+ Item_ref::VIEW_REF));
+ pos->field= static_cast<Item_field*>(real_item)->field;
+ }
+ else if (real_item->type() == Item::SUM_FUNC_ITEM &&
+ !real_item->const_item())
+ {
+ // Aggregate, or Item_aggregate_ref
+ DBUG_ASSERT(item->type() == Item::SUM_FUNC_ITEM ||
+ (item->type() == Item::REF_ITEM &&
+ static_cast<Item_ref*>(item)->ref_type() ==
+ Item_ref::AGGREGATE_REF));
+ pos->field= item->get_tmp_table_field();
+ }
+ else if (real_item->type() == Item::COPY_STR_ITEM)
+ { // Blob patch
+ pos->item= static_cast<Item_copy*>(real_item)->get_item();
+ }
+ else
+ pos->item= item;
+ pos->reverse=! order->asc;
+ DBUG_ASSERT(pos->field != NULL || pos->item != NULL);
+ }
+ *length=count;
+ DBUG_RETURN(sort);
+}
+
+
+/*
+ eq_ref: Create the lookup key and check if it is the same as saved key
+
+
+
+
+ SYNOPSIS
+ cmp_buffer_with_ref()
+ tab Join tab of the accessed table
+ table The table to read. This is usually tab->table, except for
+ semi-join when we might need to make a lookup in a temptable
+ instead.
+ tab_ref The structure with methods to collect index lookup tuple.
+ This is usually table->ref, except for the case of when we're
+ doing lookup into semi-join materialization table.
+
+ DESCRIPTION
+ Used by eq_ref access method: create the index lookup key and check if
+ we've used this key at previous lookup (If yes, we don't need to repeat
+ the lookup - the record has been already fetched)
+
+ RETURN
+ TRUE No cached record for the key, or failed to create the key (due to
+ out-of-domain error)
+ FALSE The created key is the same as the previous one (and the record
+ is already in table->record)
+*/
+
+static bool
+cmp_buffer_with_ref(THD *thd, TABLE *table, TABLE_REF *tab_ref)
+{
+ bool no_prev_key;
+ if (!tab_ref->disable_cache)
+ {
+ if (!(no_prev_key= tab_ref->key_err))
+ {
+ /* Previous access found a row. Copy its key */
+ memcpy(tab_ref->key_buff2, tab_ref->key_buff, tab_ref->key_length);
+ }
+ }
+ else
+ no_prev_key= TRUE;
+ if ((tab_ref->key_err= cp_buffer_from_ref(thd, table, tab_ref)) ||
+ no_prev_key)
+ return 1;
+ return memcmp(tab_ref->key_buff2, tab_ref->key_buff, tab_ref->key_length)
+ != 0;
+}
+
+
+bool
+cp_buffer_from_ref(THD *thd, TABLE *table, TABLE_REF *ref)
+{
+ enum enum_check_fields save_count_cuted_fields= thd->count_cuted_fields;
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->write_set);
+ bool result= 0;
+
+ for (store_key **copy=ref->key_copy ; *copy ; copy++)
+ {
+ if ((*copy)->copy() & 1)
+ {
+ result= 1;
+ break;
+ }
+ }
+ thd->count_cuted_fields= save_count_cuted_fields;
+ dbug_tmp_restore_column_map(table->write_set, old_map);
+ return result;
+}
+
+
+/*****************************************************************************
+ Group and order functions
+*****************************************************************************/
+
+/**
+ Resolve an ORDER BY or GROUP BY column reference.
+
+ Given a column reference (represented by 'order') from a GROUP BY or ORDER
+ BY clause, find the actual column it represents. If the column being
+ resolved is from the GROUP BY clause, the procedure searches the SELECT
+ list 'fields' and the columns in the FROM list 'tables'. If 'order' is from
+ the ORDER BY clause, only the SELECT list is being searched.
+
+ If 'order' is resolved to an Item, then order->item is set to the found
+ Item. If there is no item for the found column (that is, it was resolved
+ into a table field), order->item is 'fixed' and is added to all_fields and
+ ref_pointer_array.
+
+ ref_pointer_array and all_fields are updated.
+
+ @param[in] thd Pointer to current thread structure
+ @param[in,out] ref_pointer_array All select, group and order by fields
+ @param[in] tables List of tables to search in (usually
+ FROM clause)
+ @param[in] order Column reference to be resolved
+ @param[in] fields List of fields to search in (usually
+ SELECT list)
+ @param[in,out] all_fields All select, group and order by fields
+ @param[in] is_group_field True if order is a GROUP field, false if
+ ORDER by field
+
+ @retval
+ FALSE if OK
+ @retval
+ TRUE if error occurred
+*/
+
+static bool
+find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
+ ORDER *order, List<Item> &fields, List<Item> &all_fields,
+ bool is_group_field)
+{
+ Item *order_item= *order->item; /* The item from the GROUP/ORDER caluse. */
+ Item::Type order_item_type;
+ Item **select_item; /* The corresponding item from the SELECT clause. */
+ Field *from_field; /* The corresponding field from the FROM clause. */
+ uint counter;
+ enum_resolution_type resolution;
+
+ /*
+ Local SP variables may be int but are expressions, not positions.
+ (And they can't be used before fix_fields is called for them).
+ */
+ if (order_item->type() == Item::INT_ITEM && order_item->basic_const_item())
+ { /* Order by position */
+ uint count= (uint) order_item->val_int();
+ if (!count || count > fields.elements)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0),
+ order_item->full_name(), thd->where);
+ return TRUE;
+ }
+ thd->change_item_tree((Item**)&order->item, (Item*)(ref_pointer_array + count - 1));
+ order->in_field_list= 1;
+ order->counter= count;
+ order->counter_used= 1;
+ return FALSE;
+ }
+ /* Lookup the current GROUP/ORDER field in the SELECT clause. */
+ select_item= find_item_in_list(order_item, fields, &counter,
+ REPORT_EXCEPT_NOT_FOUND, &resolution);
+ if (!select_item)
+ return TRUE; /* The item is not unique, or some other error occured. */
+
+
+ /* Check whether the resolved field is not ambiguos. */
+ if (select_item != not_found_item)
+ {
+ Item *view_ref= NULL;
+ /*
+ If we have found field not by its alias in select list but by its
+ original field name, we should additionally check if we have conflict
+ for this name (in case if we would perform lookup in all tables).
+ */
+ if (resolution == RESOLVED_BEHIND_ALIAS && !order_item->fixed &&
+ order_item->fix_fields(thd, order->item))
+ return TRUE;
+
+ /* Lookup the current GROUP field in the FROM clause. */
+ order_item_type= order_item->type();
+ from_field= (Field*) not_found_field;
+ if ((is_group_field && order_item_type == Item::FIELD_ITEM) ||
+ order_item_type == Item::REF_ITEM)
+ {
+ from_field= find_field_in_tables(thd, (Item_ident*) order_item, tables,
+ NULL, &view_ref, IGNORE_ERRORS, FALSE,
+ FALSE);
+ if (!from_field)
+ from_field= (Field*) not_found_field;
+ }
+
+ if (from_field == not_found_field ||
+ (from_field != view_ref_found ?
+ /* it is field of base table => check that fields are same */
+ ((*select_item)->type() == Item::FIELD_ITEM &&
+ ((Item_field*) (*select_item))->field->eq(from_field)) :
+ /*
+ in is field of view table => check that references on translation
+ table are same
+ */
+ ((*select_item)->type() == Item::REF_ITEM &&
+ view_ref->type() == Item::REF_ITEM &&
+ ((Item_ref *) (*select_item))->ref ==
+ ((Item_ref *) view_ref)->ref)))
+ {
+ /*
+ If there is no such field in the FROM clause, or it is the same field
+ as the one found in the SELECT clause, then use the Item created for
+ the SELECT field. As a result if there was a derived field that
+ 'shadowed' a table field with the same name, the table field will be
+ chosen over the derived field.
+ */
+ order->item= ref_pointer_array + counter;
+ order->in_field_list=1;
+ return FALSE;
+ }
+ else
+ {
+ /*
+ There is a field with the same name in the FROM clause. This
+ is the field that will be chosen. In this case we issue a
+ warning so the user knows that the field from the FROM clause
+ overshadows the column reference from the SELECT list.
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_NON_UNIQ_ERROR,
+ ER(ER_NON_UNIQ_ERROR),
+ ((Item_ident*) order_item)->field_name,
+ current_thd->where);
+ }
+ }
+
+ order->in_field_list=0;
+ /*
+ The call to order_item->fix_fields() means that here we resolve
+ 'order_item' to a column from a table in the list 'tables', or to
+ a column in some outer query. Exactly because of the second case
+ we come to this point even if (select_item == not_found_item),
+ inspite of that fix_fields() calls find_item_in_list() one more
+ time.
+
+ We check order_item->fixed because Item_func_group_concat can put
+ arguments for which fix_fields already was called.
+ */
+ if (!order_item->fixed &&
+ (order_item->fix_fields(thd, order->item) ||
+ (order_item= *order->item)->check_cols(1) ||
+ thd->is_error()))
+ return TRUE; /* Wrong field. */
+
+ uint el= all_fields.elements;
+ all_fields.push_front(order_item); /* Add new field to field list. */
+ ref_pointer_array[el]= order_item;
+ /*
+ If the order_item is a SUM_FUNC_ITEM, when fix_fields is called
+ ref_by is set to order->item which is the address of order_item.
+ But this needs to be address of order_item in the all_fields list.
+ As a result, when it gets replaced with Item_aggregate_ref
+ object in Item::split_sum_func2, we will be able to retrieve the
+ newly created object.
+ */
+ if (order_item->type() == Item::SUM_FUNC_ITEM)
+ ((Item_sum *)order_item)->ref_by= all_fields.head_ref();
+
+ order->item= ref_pointer_array + el;
+ return FALSE;
+}
+
+
+/**
+ Change order to point at item in select list.
+
+ If item isn't a number and doesn't exits in the select list, add it the
+ the field list.
+*/
+
+int setup_order(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
+ List<Item> &fields, List<Item> &all_fields, ORDER *order)
+{
+ thd->where="order clause";
+ for (; order; order=order->next)
+ {
+ if (find_order_in_list(thd, ref_pointer_array, tables, order, fields,
+ all_fields, FALSE))
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Intitialize the GROUP BY list.
+
+ @param thd Thread handler
+ @param ref_pointer_array We store references to all fields that was
+ not in 'fields' here.
+ @param fields All fields in the select part. Any item in
+ 'order' that is part of these list is replaced
+ by a pointer to this fields.
+ @param all_fields Total list of all unique fields used by the
+ select. All items in 'order' that was not part
+ of fields will be added first to this list.
+ @param order The fields we should do GROUP BY on.
+ @param hidden_group_fields Pointer to flag that is set to 1 if we added
+ any fields to all_fields.
+
+ @todo
+ change ER_WRONG_FIELD_WITH_GROUP to more detailed
+ ER_NON_GROUPING_FIELD_USED
+
+ @retval
+ 0 ok
+ @retval
+ 1 error (probably out of memory)
+*/
+
+int
+setup_group(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
+ List<Item> &fields, List<Item> &all_fields, ORDER *order,
+ bool *hidden_group_fields)
+{
+ *hidden_group_fields=0;
+ ORDER *ord;
+
+ if (!order)
+ return 0; /* Everything is ok */
+
+ uint org_fields=all_fields.elements;
+
+ thd->where="group statement";
+ enum_parsing_place save_place= thd->lex->current_select->parsing_place;
+ thd->lex->current_select->parsing_place= IN_GROUP_BY;
+ for (ord= order; ord; ord= ord->next)
+ {
+ if (find_order_in_list(thd, ref_pointer_array, tables, ord, fields,
+ all_fields, TRUE))
+ return 1;
+ (*ord->item)->marker= UNDEF_POS; /* Mark found */
+ if ((*ord->item)->with_sum_func)
+ {
+ my_error(ER_WRONG_GROUP_FIELD, MYF(0), (*ord->item)->full_name());
+ return 1;
+ }
+ }
+ thd->lex->current_select->parsing_place= save_place;
+
+ if (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY)
+ {
+ /*
+ Don't allow one to use fields that is not used in GROUP BY
+ For each select a list of field references that aren't under an
+ aggregate function is created. Each field in this list keeps the
+ position of the select list expression which it belongs to.
+
+ First we check an expression from the select list against the GROUP BY
+ list. If it's found there then it's ok. It's also ok if this expression
+ is a constant or an aggregate function. Otherwise we scan the list
+ of non-aggregated fields and if we'll find at least one field reference
+ that belongs to this expression and doesn't occur in the GROUP BY list
+ we throw an error. If there are no fields in the created list for a
+ select list expression this means that all fields in it are used under
+ aggregate functions.
+ */
+ Item *item;
+ Item_field *field;
+ int cur_pos_in_select_list= 0;
+ List_iterator<Item> li(fields);
+ List_iterator<Item_field> naf_it(thd->lex->current_select->non_agg_fields);
+
+ field= naf_it++;
+ while (field && (item=li++))
+ {
+ if (item->type() != Item::SUM_FUNC_ITEM && item->marker >= 0 &&
+ !item->const_item() &&
+ !(item->real_item()->type() == Item::FIELD_ITEM &&
+ item->used_tables() & OUTER_REF_TABLE_BIT))
+ {
+ while (field)
+ {
+ /* Skip fields from previous expressions. */
+ if (field->marker < cur_pos_in_select_list)
+ goto next_field;
+ /* Found a field from the next expression. */
+ if (field->marker > cur_pos_in_select_list)
+ break;
+ /*
+ Check whether the field occur in the GROUP BY list.
+ Throw the error later if the field isn't found.
+ */
+ for (ord= order; ord; ord= ord->next)
+ if ((*ord->item)->eq((Item*)field, 0))
+ goto next_field;
+ /*
+ TODO: change ER_WRONG_FIELD_WITH_GROUP to more detailed
+ ER_NON_GROUPING_FIELD_USED
+ */
+ my_error(ER_WRONG_FIELD_WITH_GROUP, MYF(0), field->full_name());
+ return 1;
+next_field:
+ field= naf_it++;
+ }
+ }
+ cur_pos_in_select_list++;
+ }
+ }
+ if (org_fields != all_fields.elements)
+ *hidden_group_fields=1; // group fields is not used
+ return 0;
+}
+
+/**
+ Add fields with aren't used at start of field list.
+
+ @return
+ FALSE if ok
+*/
+
+static bool
+setup_new_fields(THD *thd, List<Item> &fields,
+ List<Item> &all_fields, ORDER *new_field)
+{
+ Item **item;
+ uint counter;
+ enum_resolution_type not_used;
+ DBUG_ENTER("setup_new_fields");
+
+ thd->mark_used_columns= MARK_COLUMNS_READ; // Not really needed, but...
+ for (; new_field ; new_field= new_field->next)
+ {
+ if ((item= find_item_in_list(*new_field->item, fields, &counter,
+ IGNORE_ERRORS, &not_used)))
+ new_field->item=item; /* Change to shared Item */
+ else
+ {
+ thd->where="procedure list";
+ if ((*new_field->item)->fix_fields(thd, new_field->item))
+ DBUG_RETURN(1); /* purecov: inspected */
+ all_fields.push_front(*new_field->item);
+ new_field->item=all_fields.head_ref();
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+/**
+ Create a group by that consist of all non const fields.
+
+ Try to use the fields in the order given by 'order' to allow one to
+ optimize away 'order by'.
+*/
+
+ORDER *
+create_distinct_group(THD *thd, Item **ref_pointer_array,
+ ORDER *order_list, List<Item> &fields,
+ List<Item> &all_fields,
+ bool *all_order_by_fields_used)
+{
+ List_iterator<Item> li(fields);
+ Item *item, **orig_ref_pointer_array= ref_pointer_array;
+ ORDER *order,*group,**prev;
+
+ *all_order_by_fields_used= 1;
+ while ((item=li++))
+ item->marker=0; /* Marker that field is not used */
+
+ prev= &group; group=0;
+ for (order=order_list ; order; order=order->next)
+ {
+ if (order->in_field_list)
+ {
+ ORDER *ord=(ORDER*) thd->memdup((char*) order,sizeof(ORDER));
+ if (!ord)
+ return 0;
+ *prev=ord;
+ prev= &ord->next;
+ (*ord->item)->marker=1;
+ }
+ else
+ *all_order_by_fields_used= 0;
+ }
+
+ li.rewind();
+ while ((item=li++))
+ {
+ if (!item->const_item() && !item->with_sum_func && !item->marker)
+ {
+ /*
+ Don't put duplicate columns from the SELECT list into the
+ GROUP BY list.
+ */
+ ORDER *ord_iter;
+ for (ord_iter= group; ord_iter; ord_iter= ord_iter->next)
+ if ((*ord_iter->item)->eq(item, 1))
+ goto next_item;
+
+ ORDER *ord=(ORDER*) thd->calloc(sizeof(ORDER));
+ if (!ord)
+ return 0;
+
+ if (item->type() == Item::FIELD_ITEM &&
+ item->field_type() == MYSQL_TYPE_BIT)
+ {
+ /*
+ Because HEAP tables can't index BIT fields we need to use an
+ additional hidden field for grouping because later it will be
+ converted to a LONG field. Original field will remain of the
+ BIT type and will be returned to a client.
+ */
+ Item_field *new_item= new Item_field(thd, (Item_field*)item);
+ int el= all_fields.elements;
+ orig_ref_pointer_array[el]= new_item;
+ all_fields.push_front(new_item);
+ ord->item= orig_ref_pointer_array + el;
+ }
+ else
+ {
+ /*
+ We have here only field_list (not all_field_list), so we can use
+ simple indexing of ref_pointer_array (order in the array and in the
+ list are same)
+ */
+ ord->item= ref_pointer_array;
+ }
+ ord->asc=1;
+ *prev=ord;
+ prev= &ord->next;
+ }
+next_item:
+ ref_pointer_array++;
+ }
+ *prev=0;
+ return group;
+}
+
+
+/**
+ Update join with count of the different type of fields.
+*/
+
+void
+count_field_types(SELECT_LEX *select_lex, TMP_TABLE_PARAM *param,
+ List<Item> &fields, bool reset_with_sum_func)
+{
+ List_iterator<Item> li(fields);
+ Item *field;
+
+ param->field_count=param->sum_func_count=param->func_count=
+ param->hidden_field_count=0;
+ param->quick_group=1;
+ while ((field=li++))
+ {
+ Item::Type real_type= field->real_item()->type();
+ if (real_type == Item::FIELD_ITEM)
+ param->field_count++;
+ else if (real_type == Item::SUM_FUNC_ITEM)
+ {
+ if (! field->const_item())
+ {
+ Item_sum *sum_item=(Item_sum*) field->real_item();
+ if (!sum_item->depended_from() ||
+ sum_item->depended_from() == select_lex)
+ {
+ if (!sum_item->quick_group)
+ param->quick_group=0; // UDF SUM function
+ param->sum_func_count++;
+
+ for (uint i=0 ; i < sum_item->get_arg_count() ; i++)
+ {
+ if (sum_item->get_arg(i)->real_item()->type() == Item::FIELD_ITEM)
+ param->field_count++;
+ else
+ param->func_count++;
+ }
+ }
+ param->func_count++;
+ }
+ }
+ else
+ {
+ param->func_count++;
+ if (reset_with_sum_func)
+ field->with_sum_func=0;
+ }
+ }
+}
+
+
+/**
+ Return 1 if second is a subpart of first argument.
+
+ If first parts has different direction, change it to second part
+ (group is sorted like order)
+*/
+
+static bool
+test_if_subpart(ORDER *a,ORDER *b)
+{
+ for (; a && b; a=a->next,b=b->next)
+ {
+ if ((*a->item)->eq(*b->item,1))
+ a->asc=b->asc;
+ else
+ return 0;
+ }
+ return MY_TEST(!b);
+}
+
+/**
+ Return table number if there is only one table in sort order
+ and group and order is compatible, else return 0.
+*/
+
+static TABLE *
+get_sort_by_table(ORDER *a,ORDER *b, List<TABLE_LIST> &tables,
+ table_map const_tables)
+{
+ TABLE_LIST *table;
+ List_iterator<TABLE_LIST> ti(tables);
+ table_map map= (table_map) 0;
+ DBUG_ENTER("get_sort_by_table");
+
+ if (!a)
+ a=b; // Only one need to be given
+ else if (!b)
+ b=a;
+
+ for (; a && b; a=a->next,b=b->next)
+ {
+ /* Skip elements of a that are constant */
+ while (!((*a->item)->used_tables() & ~const_tables))
+ {
+ if (!(a= a->next))
+ break;
+ }
+
+ /* Skip elements of b that are constant */
+ while (!((*b->item)->used_tables() & ~const_tables))
+ {
+ if (!(b= b->next))
+ break;
+ }
+
+ if (!a || !b)
+ break;
+
+ if (!(*a->item)->eq(*b->item,1))
+ DBUG_RETURN(0);
+ map|=a->item[0]->used_tables();
+ }
+ if (!map || (map & (RAND_TABLE_BIT | OUTER_REF_TABLE_BIT)))
+ DBUG_RETURN(0);
+
+ while ((table= ti++) && !(map & table->table->map)) ;
+ if (map != table->table->map)
+ DBUG_RETURN(0); // More than one table
+ DBUG_PRINT("exit",("sort by table: %d",table->table->tablenr));
+ DBUG_RETURN(table->table);
+}
+
+
+/**
+ calc how big buffer we need for comparing group entries.
+*/
+
+static void
+calc_group_buffer(JOIN *join,ORDER *group)
+{
+ uint key_length=0, parts=0, null_parts=0;
+
+ if (group)
+ join->group= 1;
+ for (; group ; group=group->next)
+ {
+ Item *group_item= *group->item;
+ Field *field= group_item->get_tmp_table_field();
+ if (field)
+ {
+ enum_field_types type;
+ if ((type= field->type()) == MYSQL_TYPE_BLOB)
+ key_length+=MAX_BLOB_WIDTH; // Can't be used as a key
+ else if (type == MYSQL_TYPE_VARCHAR || type == MYSQL_TYPE_VAR_STRING)
+ key_length+= field->field_length + HA_KEY_BLOB_LENGTH;
+ else if (type == MYSQL_TYPE_BIT)
+ {
+ /* Bit is usually stored as a longlong key for group fields */
+ key_length+= 8; // Big enough
+ }
+ else
+ key_length+= field->pack_length();
+ }
+ else
+ {
+ switch (group_item->result_type()) {
+ case REAL_RESULT:
+ key_length+= sizeof(double);
+ break;
+ case INT_RESULT:
+ key_length+= sizeof(longlong);
+ break;
+ case DECIMAL_RESULT:
+ key_length+= my_decimal_get_binary_size(group_item->max_length -
+ (group_item->decimals ? 1 : 0),
+ group_item->decimals);
+ break;
+ case STRING_RESULT:
+ {
+ enum enum_field_types type= group_item->field_type();
+ /*
+ As items represented as DATE/TIME fields in the group buffer
+ have STRING_RESULT result type, we increase the length
+ by 8 as maximum pack length of such fields.
+ */
+ if (type == MYSQL_TYPE_TIME ||
+ type == MYSQL_TYPE_DATE ||
+ type == MYSQL_TYPE_DATETIME ||
+ type == MYSQL_TYPE_TIMESTAMP)
+ {
+ key_length+= 8;
+ }
+ else if (type == MYSQL_TYPE_BLOB)
+ key_length+= MAX_BLOB_WIDTH; // Can't be used as a key
+ else
+ {
+ /*
+ Group strings are taken as varstrings and require an length field.
+ A field is not yet created by create_tmp_field()
+ and the sizes should match up.
+ */
+ key_length+= group_item->max_length + HA_KEY_BLOB_LENGTH;
+ }
+ break;
+ }
+ default:
+ /* This case should never be choosen */
+ DBUG_ASSERT(0);
+ my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+ }
+ }
+ parts++;
+ if (group_item->maybe_null)
+ null_parts++;
+ }
+ join->tmp_table_param.group_length=key_length+null_parts;
+ join->tmp_table_param.group_parts=parts;
+ join->tmp_table_param.group_null_parts=null_parts;
+}
+
+
+/**
+ allocate group fields or take prepared (cached).
+
+ @param main_join join of current select
+ @param curr_join current join (join of current select or temporary copy
+ of it)
+
+ @retval
+ 0 ok
+ @retval
+ 1 failed
+*/
+
+static bool
+make_group_fields(JOIN *main_join, JOIN *curr_join)
+{
+ if (main_join->group_fields_cache.elements)
+ {
+ curr_join->group_fields= main_join->group_fields_cache;
+ curr_join->sort_and_group= 1;
+ }
+ else
+ {
+ if (alloc_group_fields(curr_join, curr_join->group_list))
+ return (1);
+ main_join->group_fields_cache= curr_join->group_fields;
+ }
+ return (0);
+}
+
+
+/**
+ Get a list of buffers for saveing last group.
+
+ Groups are saved in reverse order for easyer check loop.
+*/
+
+static bool
+alloc_group_fields(JOIN *join,ORDER *group)
+{
+ if (group)
+ {
+ for (; group ; group=group->next)
+ {
+ Cached_item *tmp=new_Cached_item(join->thd, *group->item, TRUE);
+ if (!tmp || join->group_fields.push_front(tmp))
+ return TRUE;
+ }
+ }
+ join->sort_and_group=1; /* Mark for do_select */
+ return FALSE;
+}
+
+
+
+/*
+ Test if a single-row cache of items changed, and update the cache.
+
+ @details Test if a list of items that typically represents a result
+ row has changed. If the value of some item changed, update the cached
+ value for this item.
+
+ @param list list of <item, cached_value> pairs stored as Cached_item.
+
+ @return -1 if no item changed
+ @return index of the first item that changed
+*/
+
+int test_if_item_cache_changed(List<Cached_item> &list)
+{
+ DBUG_ENTER("test_if_item_cache_changed");
+ List_iterator<Cached_item> li(list);
+ int idx= -1,i;
+ Cached_item *buff;
+
+ for (i=(int) list.elements-1 ; (buff=li++) ; i--)
+ {
+ if (buff->cmp())
+ idx=i;
+ }
+ DBUG_PRINT("info", ("idx: %d", idx));
+ DBUG_RETURN(idx);
+}
+
+
+
+static int
+test_if_group_changed(List<Cached_item> &list)
+{
+ DBUG_ENTER("test_if_group_changed");
+ List_iterator<Cached_item> li(list);
+ int idx= -1,i;
+ Cached_item *buff;
+
+ for (i=(int) list.elements-1 ; (buff=li++) ; i--)
+ {
+ if (buff->cmp())
+ idx=i;
+ }
+ DBUG_PRINT("info", ("idx: %d", idx));
+ DBUG_RETURN(idx);
+}
+
+
+/**
+ Setup copy_fields to save fields at start of new group.
+
+ Setup copy_fields to save fields at start of new group
+
+ Only FIELD_ITEM:s and FUNC_ITEM:s needs to be saved between groups.
+ Change old item_field to use a new field with points at saved fieldvalue
+ This function is only called before use of send_result_set_metadata.
+
+ @param thd THD pointer
+ @param param temporary table parameters
+ @param ref_pointer_array array of pointers to top elements of filed list
+ @param res_selected_fields new list of items of select item list
+ @param res_all_fields new list of all items
+ @param elements number of elements in select item list
+ @param all_fields all fields list
+
+ @todo
+ In most cases this result will be sent to the user.
+ This should be changed to use copy_int or copy_real depending
+ on how the value is to be used: In some cases this may be an
+ argument in a group function, like: IF(ISNULL(col),0,COUNT(*))
+
+ @retval
+ 0 ok
+ @retval
+ !=0 error
+*/
+
+bool
+setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param,
+ Item **ref_pointer_array,
+ List<Item> &res_selected_fields, List<Item> &res_all_fields,
+ uint elements, List<Item> &all_fields)
+{
+ Item *pos;
+ List_iterator_fast<Item> li(all_fields);
+ Copy_field *copy= NULL;
+ Copy_field *copy_start __attribute__((unused));
+ res_selected_fields.empty();
+ res_all_fields.empty();
+ List_iterator_fast<Item> itr(res_all_fields);
+ List<Item> extra_funcs;
+ uint i, border= all_fields.elements - elements;
+ DBUG_ENTER("setup_copy_fields");
+
+ if (param->field_count &&
+ !(copy=param->copy_field= new Copy_field[param->field_count]))
+ goto err2;
+
+ param->copy_funcs.empty();
+ copy_start= copy;
+ for (i= 0; (pos= li++); i++)
+ {
+ Field *field;
+ uchar *tmp;
+ Item *real_pos= pos->real_item();
+ /*
+ Aggregate functions can be substituted for fields (by e.g. temp tables).
+ We need to filter those substituted fields out.
+ */
+ if (real_pos->type() == Item::FIELD_ITEM &&
+ !(real_pos != pos &&
+ ((Item_ref *)pos)->ref_type() == Item_ref::AGGREGATE_REF))
+ {
+ Item_field *item;
+ if (!(item= new Item_field(thd, ((Item_field*) real_pos))))
+ goto err;
+ if (pos->type() == Item::REF_ITEM)
+ {
+ /* preserve the names of the ref when dereferncing */
+ Item_ref *ref= (Item_ref *) pos;
+ item->db_name= ref->db_name;
+ item->table_name= ref->table_name;
+ item->name= ref->name;
+ }
+ pos= item;
+ if (item->field->flags & BLOB_FLAG)
+ {
+ if (!(pos= new Item_copy_string(pos)))
+ goto err;
+ /*
+ Item_copy_string::copy for function can call
+ Item_copy_string::val_int for blob via Item_ref.
+ But if Item_copy_string::copy for blob isn't called before,
+ it's value will be wrong
+ so let's insert Item_copy_string for blobs in the beginning of
+ copy_funcs
+ (to see full test case look at having.test, BUG #4358)
+ */
+ if (param->copy_funcs.push_front(pos))
+ goto err;
+ }
+ else
+ {
+ /*
+ set up save buffer and change result_field to point at
+ saved value
+ */
+ field= item->field;
+ item->result_field=field->new_field(thd->mem_root,field->table, 1);
+ /*
+ We need to allocate one extra byte for null handling and
+ another extra byte to not get warnings from purify in
+ Field_string::val_int
+ */
+ if (!(tmp= (uchar*) sql_alloc(field->pack_length()+2)))
+ goto err;
+ if (copy)
+ {
+ DBUG_ASSERT (param->field_count > (uint) (copy - copy_start));
+ copy->set(tmp, item->result_field);
+ item->result_field->move_field(copy->to_ptr,copy->to_null_ptr,1);
+#ifdef HAVE_valgrind
+ copy->to_ptr[copy->from_length]= 0;
+#endif
+ copy++;
+ }
+ }
+ }
+ else if ((real_pos->type() == Item::FUNC_ITEM ||
+ real_pos->real_type() == Item::SUBSELECT_ITEM ||
+ real_pos->type() == Item::CACHE_ITEM ||
+ real_pos->type() == Item::COND_ITEM) &&
+ !real_pos->with_sum_func)
+ { // Save for send fields
+ pos= real_pos;
+ /* TODO:
+ In most cases this result will be sent to the user.
+ This should be changed to use copy_int or copy_real depending
+ on how the value is to be used: In some cases this may be an
+ argument in a group function, like: IF(ISNULL(col),0,COUNT(*))
+ */
+ if (!(pos=new Item_copy_string(pos)))
+ goto err;
+ if (i < border) // HAVING, ORDER and GROUP BY
+ {
+ if (extra_funcs.push_back(pos))
+ goto err;
+ }
+ else if (param->copy_funcs.push_back(pos))
+ goto err;
+ }
+ res_all_fields.push_back(pos);
+ ref_pointer_array[((i < border)? all_fields.elements-i-1 : i-border)]=
+ pos;
+ }
+ param->copy_field_end= copy;
+
+ for (i= 0; i < border; i++)
+ itr++;
+ itr.sublist(res_selected_fields, elements);
+ /*
+ Put elements from HAVING, ORDER BY and GROUP BY last to ensure that any
+ reference used in these will resolve to a item that is already calculated
+ */
+ param->copy_funcs.concat(&extra_funcs);
+
+ DBUG_RETURN(0);
+
+ err:
+ if (copy)
+ delete [] param->copy_field; // This is never 0
+ param->copy_field=0;
+err2:
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Make a copy of all simple SELECT'ed items.
+
+ This is done at the start of a new group so that we can retrieve
+ these later when the group changes.
+*/
+
+void
+copy_fields(TMP_TABLE_PARAM *param)
+{
+ Copy_field *ptr=param->copy_field;
+ Copy_field *end=param->copy_field_end;
+
+ DBUG_ASSERT((ptr != NULL && end >= ptr) || (ptr == NULL && end == NULL));
+
+ for (; ptr != end; ptr++)
+ (*ptr->do_copy)(ptr);
+
+ List_iterator_fast<Item> it(param->copy_funcs);
+ Item_copy_string *item;
+ while ((item = (Item_copy_string*) it++))
+ item->copy();
+}
+
+
+/**
+ Make an array of pointers to sum_functions to speed up
+ sum_func calculation.
+
+ @retval
+ 0 ok
+ @retval
+ 1 Error
+*/
+
+bool JOIN::alloc_func_list()
+{
+ uint func_count, group_parts;
+ DBUG_ENTER("alloc_func_list");
+
+ func_count= tmp_table_param.sum_func_count;
+ /*
+ If we are using rollup, we need a copy of the summary functions for
+ each level
+ */
+ if (rollup.state != ROLLUP::STATE_NONE)
+ func_count*= (send_group_parts+1);
+
+ group_parts= send_group_parts;
+ /*
+ If distinct, reserve memory for possible
+ disctinct->group_by optimization
+ */
+ if (select_distinct)
+ {
+ group_parts+= fields_list.elements;
+ /*
+ If the ORDER clause is specified then it's possible that
+ it also will be optimized, so reserve space for it too
+ */
+ if (order)
+ {
+ ORDER *ord;
+ for (ord= order; ord; ord= ord->next)
+ group_parts++;
+ }
+ }
+
+ /* This must use calloc() as rollup_make_fields depends on this */
+ sum_funcs= (Item_sum**) thd->calloc(sizeof(Item_sum**) * (func_count+1) +
+ sizeof(Item_sum***) * (group_parts+1));
+ sum_funcs_end= (Item_sum***) (sum_funcs+func_count+1);
+ DBUG_RETURN(sum_funcs == 0);
+}
+
+
+/**
+ Initialize 'sum_funcs' array with all Item_sum objects.
+
+ @param field_list All items
+ @param send_result_set_metadata Items in select list
+ @param before_group_by Set to 1 if this is called before GROUP BY handling
+ @param recompute Set to TRUE if sum_funcs must be recomputed
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool JOIN::make_sum_func_list(List<Item> &field_list, List<Item> &send_result_set_metadata,
+ bool before_group_by, bool recompute)
+{
+ List_iterator_fast<Item> it(field_list);
+ Item_sum **func;
+ Item *item;
+ DBUG_ENTER("make_sum_func_list");
+
+ if (*sum_funcs && !recompute)
+ DBUG_RETURN(FALSE); /* We have already initialized sum_funcs. */
+
+ func= sum_funcs;
+ while ((item=it++))
+ {
+ if (item->type() == Item::SUM_FUNC_ITEM && !item->const_item() &&
+ (!((Item_sum*) item)->depended_from() ||
+ ((Item_sum *)item)->depended_from() == select_lex))
+ *func++= (Item_sum*) item;
+ }
+ if (before_group_by && rollup.state == ROLLUP::STATE_INITED)
+ {
+ rollup.state= ROLLUP::STATE_READY;
+ if (rollup_make_fields(field_list, send_result_set_metadata, &func))
+ DBUG_RETURN(TRUE); // Should never happen
+ }
+ else if (rollup.state == ROLLUP::STATE_NONE)
+ {
+ for (uint i=0 ; i <= send_group_parts ;i++)
+ sum_funcs_end[i]= func;
+ }
+ else if (rollup.state == ROLLUP::STATE_READY)
+ DBUG_RETURN(FALSE); // Don't put end marker
+ *func=0; // End marker
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Change all funcs and sum_funcs to fields in tmp table, and create
+ new list of all items.
+
+ @param thd THD pointer
+ @param ref_pointer_array array of pointers to top elements of filed list
+ @param res_selected_fields new list of items of select item list
+ @param res_all_fields new list of all items
+ @param elements number of elements in select item list
+ @param all_fields all fields list
+
+ @retval
+ 0 ok
+ @retval
+ !=0 error
+*/
+
+static bool
+change_to_use_tmp_fields(THD *thd, Item **ref_pointer_array,
+ List<Item> &res_selected_fields,
+ List<Item> &res_all_fields,
+ uint elements, List<Item> &all_fields)
+{
+ List_iterator_fast<Item> it(all_fields);
+ Item *item_field,*item;
+ DBUG_ENTER("change_to_use_tmp_fields");
+
+ res_selected_fields.empty();
+ res_all_fields.empty();
+
+ uint border= all_fields.elements - elements;
+ for (uint i= 0; (item= it++); i++)
+ {
+ Field *field;
+ if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM)
+ item_field= item;
+ else if (item->type() == Item::FIELD_ITEM)
+ item_field= item->get_tmp_table_item(thd);
+ else if (item->type() == Item::FUNC_ITEM &&
+ ((Item_func*)item)->functype() == Item_func::SUSERVAR_FUNC)
+ {
+ field= item->get_tmp_table_field();
+ if (field != NULL)
+ {
+ /*
+ Replace "@:=<expression>" with "@:=<tmp table column>". Otherwise, we
+ would re-evaluate <expression>, and if expression were a subquery, this
+ would access already-unlocked tables.
+ */
+ Item_func_set_user_var* suv=
+ new Item_func_set_user_var(thd, (Item_func_set_user_var*) item);
+ Item_field *new_field= new Item_field(field);
+ if (!suv || !new_field)
+ DBUG_RETURN(true); // Fatal error
+ /*
+ We are replacing the argument of Item_func_set_user_var after its value
+ has been read. The argument's null_value should be set by now, so we
+ must set it explicitly for the replacement argument since the null_value
+ may be read without any preceding call to val_*().
+ */
+ new_field->update_null_value();
+ List<Item> list;
+ list.push_back(new_field);
+ suv->set_arguments(list);
+ item_field= suv;
+ }
+ else
+ item_field= item;
+ }
+ else if ((field= item->get_tmp_table_field()))
+ {
+ if (item->type() == Item::SUM_FUNC_ITEM && field->table->group)
+ item_field= ((Item_sum*) item)->result_item(field);
+ else
+ item_field= (Item*) new Item_field(field);
+ if (!item_field)
+ DBUG_RETURN(true); // Fatal error
+
+ if (item->real_item()->type() != Item::FIELD_ITEM)
+ field->orig_table= 0;
+ item_field->name= item->name;
+ if (item->type() == Item::REF_ITEM)
+ {
+ Item_field *ifield= (Item_field *) item_field;
+ Item_ref *iref= (Item_ref *) item;
+ ifield->table_name= iref->table_name;
+ ifield->db_name= iref->db_name;
+ }
+#ifndef DBUG_OFF
+ if (!item_field->name)
+ {
+ char buff[256];
+ String str(buff,sizeof(buff),&my_charset_bin);
+ str.length(0);
+ str.extra_allocation(1024);
+ item->print(&str, QT_ORDINARY);
+ item_field->name= sql_strmake(str.ptr(),str.length());
+ }
+#endif
+ }
+ else
+ item_field= item;
+
+ res_all_fields.push_back(item_field);
+ ref_pointer_array[((i < border)? all_fields.elements-i-1 : i-border)]=
+ item_field;
+ }
+
+ List_iterator_fast<Item> itr(res_all_fields);
+ for (uint i= 0; i < border; i++)
+ itr++;
+ itr.sublist(res_selected_fields, elements);
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Change all sum_func refs to fields to point at fields in tmp table.
+ Change all funcs to be fields in tmp table.
+
+ @param thd THD pointer
+ @param ref_pointer_array array of pointers to top elements of filed list
+ @param res_selected_fields new list of items of select item list
+ @param res_all_fields new list of all items
+ @param elements number of elements in select item list
+ @param all_fields all fields list
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+static bool
+change_refs_to_tmp_fields(THD *thd, Item **ref_pointer_array,
+ List<Item> &res_selected_fields,
+ List<Item> &res_all_fields, uint elements,
+ List<Item> &all_fields)
+{
+ List_iterator_fast<Item> it(all_fields);
+ Item *item, *new_item;
+ res_selected_fields.empty();
+ res_all_fields.empty();
+
+ uint i, border= all_fields.elements - elements;
+ for (i= 0; (item= it++); i++)
+ {
+ res_all_fields.push_back(new_item= item->get_tmp_table_item(thd));
+ ref_pointer_array[((i < border)? all_fields.elements-i-1 : i-border)]=
+ new_item;
+ }
+
+ List_iterator_fast<Item> itr(res_all_fields);
+ for (i= 0; i < border; i++)
+ itr++;
+ itr.sublist(res_selected_fields, elements);
+
+ return thd->is_fatal_error;
+}
+
+
+
+/******************************************************************************
+ Code for calculating functions
+******************************************************************************/
+
+
+/**
+ Call ::setup for all sum functions.
+
+ @param thd thread handler
+ @param func_ptr sum function list
+
+ @retval
+ FALSE ok
+ @retval
+ TRUE error
+*/
+
+static bool setup_sum_funcs(THD *thd, Item_sum **func_ptr)
+{
+ Item_sum *func;
+ DBUG_ENTER("setup_sum_funcs");
+ while ((func= *(func_ptr++)))
+ {
+ if (func->aggregator_setup(thd))
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+static bool prepare_sum_aggregators(Item_sum **func_ptr, bool need_distinct)
+{
+ Item_sum *func;
+ DBUG_ENTER("prepare_sum_aggregators");
+ while ((func= *(func_ptr++)))
+ {
+ if (func->set_aggregator(need_distinct && func->has_with_distinct() ?
+ Aggregator::DISTINCT_AGGREGATOR :
+ Aggregator::SIMPLE_AGGREGATOR))
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+static void
+init_tmptable_sum_functions(Item_sum **func_ptr)
+{
+ Item_sum *func;
+ while ((func= *(func_ptr++)))
+ func->reset_field();
+}
+
+
+/** Update record 0 in tmp_table from record 1. */
+
+static void
+update_tmptable_sum_func(Item_sum **func_ptr,
+ TABLE *tmp_table __attribute__((unused)))
+{
+ Item_sum *func;
+ while ((func= *(func_ptr++)))
+ func->update_field();
+}
+
+
+/** Copy result of sum functions to record in tmp_table. */
+
+static void
+copy_sum_funcs(Item_sum **func_ptr, Item_sum **end_ptr)
+{
+ for (; func_ptr != end_ptr ; func_ptr++)
+ (void) (*func_ptr)->save_in_result_field(1);
+ return;
+}
+
+
+static bool
+init_sum_functions(Item_sum **func_ptr, Item_sum **end_ptr)
+{
+ for (; func_ptr != end_ptr ;func_ptr++)
+ {
+ if ((*func_ptr)->reset_and_add())
+ return 1;
+ }
+ /* If rollup, calculate the upper sum levels */
+ for ( ; *func_ptr ; func_ptr++)
+ {
+ if ((*func_ptr)->aggregator_add())
+ return 1;
+ }
+ return 0;
+}
+
+
+static bool
+update_sum_func(Item_sum **func_ptr)
+{
+ Item_sum *func;
+ for (; (func= (Item_sum*) *func_ptr) ; func_ptr++)
+ if (func->aggregator_add())
+ return 1;
+ return 0;
+}
+
+/**
+ Copy result of functions to record in tmp_table.
+
+ Uses the thread pointer to check for errors in
+ some of the val_xxx() methods called by the
+ save_in_result_field() function.
+ TODO: make the Item::val_xxx() return error code
+
+ @param func_ptr array of the function Items to copy to the tmp table
+ @param thd pointer to the current thread for error checking
+ @retval
+ FALSE if OK
+ @retval
+ TRUE on error
+*/
+
+bool
+copy_funcs(Item **func_ptr, const THD *thd)
+{
+ Item *func;
+ for (; (func = *func_ptr) ; func_ptr++)
+ {
+ func->save_in_result_field(1);
+ /*
+ Need to check the THD error state because Item::val_xxx() don't
+ return error code, but can generate errors
+ TODO: change it for a real status check when Item::val_xxx()
+ are extended to return status code.
+ */
+ if (thd->is_error())
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Create a condition for a const reference and add this to the
+ currenct select for the table.
+*/
+
+static bool add_ref_to_table_cond(THD *thd, JOIN_TAB *join_tab)
+{
+ DBUG_ENTER("add_ref_to_table_cond");
+ if (!join_tab->ref.key_parts)
+ DBUG_RETURN(FALSE);
+
+ Item_cond_and *cond=new Item_cond_and();
+ TABLE *table=join_tab->table;
+ int error= 0;
+ if (!cond)
+ DBUG_RETURN(TRUE);
+
+ for (uint i=0 ; i < join_tab->ref.key_parts ; i++)
+ {
+ Field *field=table->field[table->key_info[join_tab->ref.key].key_part[i].
+ fieldnr-1];
+ Item *value=join_tab->ref.items[i];
+ cond->add(new Item_func_equal(new Item_field(field), value));
+ }
+ if (thd->is_fatal_error)
+ DBUG_RETURN(TRUE);
+ if (!cond->fixed)
+ {
+ Item *tmp_item= (Item*) cond;
+ cond->fix_fields(thd, &tmp_item);
+ DBUG_ASSERT(cond == tmp_item);
+ }
+ if (join_tab->select)
+ {
+ Item *cond_copy;
+ UNINIT_VAR(cond_copy); // used when pre_idx_push_select_cond!=NULL
+ if (join_tab->select->pre_idx_push_select_cond)
+ cond_copy= cond->copy_andor_structure(thd);
+ if (join_tab->select->cond)
+ error=(int) cond->add(join_tab->select->cond);
+ join_tab->select->cond= cond;
+ if (join_tab->select->pre_idx_push_select_cond)
+ {
+ Item *new_cond= and_conds(cond_copy, join_tab->select->pre_idx_push_select_cond);
+ if (!new_cond->fixed && new_cond->fix_fields(thd, &new_cond))
+ error= 1;
+ join_tab->pre_idx_push_select_cond=
+ join_tab->select->pre_idx_push_select_cond= new_cond;
+ }
+ join_tab->set_select_cond(cond, __LINE__);
+ }
+ else if ((join_tab->select= make_select(join_tab->table, 0, 0, cond, 0,
+ &error)))
+ join_tab->set_select_cond(cond, __LINE__);
+
+ DBUG_RETURN(error ? TRUE : FALSE);
+}
+
+
+/**
+ Free joins of subselect of this select.
+
+ @param thd THD pointer
+ @param select pointer to st_select_lex which subselects joins we will free
+*/
+
+void free_underlaid_joins(THD *thd, SELECT_LEX *select)
+{
+ for (SELECT_LEX_UNIT *unit= select->first_inner_unit();
+ unit;
+ unit= unit->next_unit())
+ unit->cleanup();
+}
+
+/****************************************************************************
+ ROLLUP handling
+****************************************************************************/
+
+/**
+ Replace occurences of group by fields in an expression by ref items.
+
+ The function replaces occurrences of group by fields in expr
+ by ref objects for these fields unless they are under aggregate
+ functions.
+ The function also corrects value of the the maybe_null attribute
+ for the items of all subexpressions containing group by fields.
+
+ @b EXAMPLES
+ @code
+ SELECT a+1 FROM t1 GROUP BY a WITH ROLLUP
+ SELECT SUM(a)+a FROM t1 GROUP BY a WITH ROLLUP
+ @endcode
+
+ @b IMPLEMENTATION
+
+ The function recursively traverses the tree of the expr expression,
+ looks for occurrences of the group by fields that are not under
+ aggregate functions and replaces them for the corresponding ref items.
+
+ @note
+ This substitution is needed GROUP BY queries with ROLLUP if
+ SELECT list contains expressions over group by attributes.
+
+ @param thd reference to the context
+ @param expr expression to make replacement
+ @param group_list list of references to group by items
+ @param changed out: returns 1 if item contains a replaced field item
+
+ @todo
+ - TODO: Some functions are not null-preserving. For those functions
+ updating of the maybe_null attribute is an overkill.
+
+ @retval
+ 0 if ok
+ @retval
+ 1 on error
+*/
+
+static bool change_group_ref(THD *thd, Item_func *expr, ORDER *group_list,
+ bool *changed)
+{
+ if (expr->arg_count)
+ {
+ Name_resolution_context *context= &thd->lex->current_select->context;
+ Item **arg,**arg_end;
+ bool arg_changed= FALSE;
+ for (arg= expr->arguments(),
+ arg_end= expr->arguments()+expr->arg_count;
+ arg != arg_end; arg++)
+ {
+ Item *item= *arg;
+ if (item->type() == Item::FIELD_ITEM || item->type() == Item::REF_ITEM)
+ {
+ ORDER *group_tmp;
+ for (group_tmp= group_list; group_tmp; group_tmp= group_tmp->next)
+ {
+ if (item->eq(*group_tmp->item,0))
+ {
+ Item *new_item;
+ if (!(new_item= new Item_ref(context, group_tmp->item, 0,
+ item->name)))
+ return 1; // fatal_error is set
+ thd->change_item_tree(arg, new_item);
+ arg_changed= TRUE;
+ }
+ }
+ }
+ else if (item->type() == Item::FUNC_ITEM)
+ {
+ if (change_group_ref(thd, (Item_func *) item, group_list, &arg_changed))
+ return 1;
+ }
+ }
+ if (arg_changed)
+ {
+ expr->maybe_null= 1;
+ expr->in_rollup= 1;
+ *changed= TRUE;
+ }
+ }
+ return 0;
+}
+
+
+/** Allocate memory needed for other rollup functions. */
+
+bool JOIN::rollup_init()
+{
+ uint i,j;
+ Item **ref_array;
+
+ tmp_table_param.quick_group= 0; // Can't create groups in tmp table
+ rollup.state= ROLLUP::STATE_INITED;
+
+ /*
+ Create pointers to the different sum function groups
+ These are updated by rollup_make_fields()
+ */
+ tmp_table_param.group_parts= send_group_parts;
+
+ if (!(rollup.null_items= (Item_null_result**) thd->alloc((sizeof(Item*) +
+ sizeof(Item**) +
+ sizeof(List<Item>) +
+ ref_pointer_array_size)
+ * send_group_parts )))
+ return 1;
+
+ rollup.fields= (List<Item>*) (rollup.null_items + send_group_parts);
+ rollup.ref_pointer_arrays= (Item***) (rollup.fields + send_group_parts);
+ ref_array= (Item**) (rollup.ref_pointer_arrays+send_group_parts);
+
+ /*
+ Prepare space for field list for the different levels
+ These will be filled up in rollup_make_fields()
+ */
+ for (i= 0 ; i < send_group_parts ; i++)
+ {
+ rollup.null_items[i]= new (thd->mem_root) Item_null_result();
+ List<Item> *rollup_fields= &rollup.fields[i];
+ rollup_fields->empty();
+ rollup.ref_pointer_arrays[i]= ref_array;
+ ref_array+= all_fields.elements;
+ }
+ for (i= 0 ; i < send_group_parts; i++)
+ {
+ for (j=0 ; j < fields_list.elements ; j++)
+ rollup.fields[i].push_back(rollup.null_items[i]);
+ }
+ List_iterator<Item> it(all_fields);
+ Item *item;
+ while ((item= it++))
+ {
+ ORDER *group_tmp;
+ bool found_in_group= 0;
+
+ for (group_tmp= group_list; group_tmp; group_tmp= group_tmp->next)
+ {
+ if (*group_tmp->item == item)
+ {
+ item->maybe_null= 1;
+ item->in_rollup= 1;
+ found_in_group= 1;
+ break;
+ }
+ }
+ if (item->type() == Item::FUNC_ITEM && !found_in_group)
+ {
+ bool changed= FALSE;
+ if (change_group_ref(thd, (Item_func *) item, group_list, &changed))
+ return 1;
+ /*
+ We have to prevent creation of a field in a temporary table for
+ an expression that contains GROUP BY attributes.
+ Marking the expression item as 'with_sum_func' will ensure this.
+ */
+ if (changed)
+ item->with_sum_func= 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ Wrap all constant Items in GROUP BY list.
+
+ For ROLLUP queries each constant item referenced in GROUP BY list
+ is wrapped up into an Item_func object yielding the same value
+ as the constant item. The objects of the wrapper class are never
+ considered as constant items and besides they inherit all
+ properties of the Item_result_field class.
+ This wrapping allows us to ensure writing constant items
+ into temporary tables whenever the result of the ROLLUP
+ operation has to be written into a temporary table, e.g. when
+ ROLLUP is used together with DISTINCT in the SELECT list.
+ Usually when creating temporary tables for a intermidiate
+ result we do not include fields for constant expressions.
+
+ @retval
+ 0 if ok
+ @retval
+ 1 on error
+*/
+
+bool JOIN::rollup_process_const_fields()
+{
+ ORDER *group_tmp;
+ Item *item;
+ List_iterator<Item> it(all_fields);
+
+ for (group_tmp= group_list; group_tmp; group_tmp= group_tmp->next)
+ {
+ if (!(*group_tmp->item)->const_item())
+ continue;
+ while ((item= it++))
+ {
+ if (*group_tmp->item == item)
+ {
+ Item* new_item= new Item_func_rollup_const(item);
+ if (!new_item)
+ return 1;
+ new_item->fix_fields(thd, (Item **) 0);
+ thd->change_item_tree(it.ref(), new_item);
+ for (ORDER *tmp= group_tmp; tmp; tmp= tmp->next)
+ {
+ if (*tmp->item == item)
+ thd->change_item_tree(tmp->item, new_item);
+ }
+ break;
+ }
+ }
+ it.rewind();
+ }
+ return 0;
+}
+
+
+/**
+ Fill up rollup structures with pointers to fields to use.
+
+ Creates copies of item_sum items for each sum level.
+
+ @param fields_arg List of all fields (hidden and real ones)
+ @param sel_fields Pointer to selected fields
+ @param func Store here a pointer to all fields
+
+ @retval
+ 0 if ok;
+ In this case func is pointing to next not used element.
+ @retval
+ 1 on error
+*/
+
+bool JOIN::rollup_make_fields(List<Item> &fields_arg, List<Item> &sel_fields,
+ Item_sum ***func)
+{
+ List_iterator_fast<Item> it(fields_arg);
+ Item *first_field= sel_fields.head();
+ uint level;
+
+ /*
+ Create field lists for the different levels
+
+ The idea here is to have a separate field list for each rollup level to
+ avoid all runtime checks of which columns should be NULL.
+
+ The list is stored in reverse order to get sum function in such an order
+ in func that it makes it easy to reset them with init_sum_functions()
+
+ Assuming: SELECT a, b, c SUM(b) FROM t1 GROUP BY a,b WITH ROLLUP
+
+ rollup.fields[0] will contain list where a,b,c is NULL
+ rollup.fields[1] will contain list where b,c is NULL
+ ...
+ rollup.ref_pointer_array[#] points to fields for rollup.fields[#]
+ ...
+ sum_funcs_end[0] points to all sum functions
+ sum_funcs_end[1] points to all sum functions, except grand totals
+ ...
+ */
+
+ for (level=0 ; level < send_group_parts ; level++)
+ {
+ uint i;
+ uint pos= send_group_parts - level -1;
+ bool real_fields= 0;
+ Item *item;
+ List_iterator<Item> new_it(rollup.fields[pos]);
+ Item **ref_array_start= rollup.ref_pointer_arrays[pos];
+ ORDER *start_group;
+
+ /* Point to first hidden field */
+ Item **ref_array= ref_array_start + fields_arg.elements-1;
+
+ /* Remember where the sum functions ends for the previous level */
+ sum_funcs_end[pos+1]= *func;
+
+ /* Find the start of the group for this level */
+ for (i= 0, start_group= group_list ;
+ i++ < pos ;
+ start_group= start_group->next)
+ ;
+
+ it.rewind();
+ while ((item= it++))
+ {
+ if (item == first_field)
+ {
+ real_fields= 1; // End of hidden fields
+ ref_array= ref_array_start;
+ }
+
+ if (item->type() == Item::SUM_FUNC_ITEM && !item->const_item() &&
+ (!((Item_sum*) item)->depended_from() ||
+ ((Item_sum *)item)->depended_from() == select_lex))
+
+ {
+ /*
+ This is a top level summary function that must be replaced with
+ a sum function that is reset for this level.
+
+ NOTE: This code creates an object which is not that nice in a
+ sub select. Fortunately it's not common to have rollup in
+ sub selects.
+ */
+ item= item->copy_or_same(thd);
+ ((Item_sum*) item)->make_unique();
+ *(*func)= (Item_sum*) item;
+ (*func)++;
+ }
+ else
+ {
+ /* Check if this is something that is part of this group by */
+ ORDER *group_tmp;
+ for (group_tmp= start_group, i= pos ;
+ group_tmp ; group_tmp= group_tmp->next, i++)
+ {
+ if (*group_tmp->item == item)
+ {
+ /*
+ This is an element that is used by the GROUP BY and should be
+ set to NULL in this level
+ */
+ Item_null_result *null_item= new (thd->mem_root) Item_null_result();
+ if (!null_item)
+ return 1;
+ item->maybe_null= 1; // Value will be null sometimes
+ null_item->result_field= item->get_tmp_table_field();
+ item= null_item;
+ break;
+ }
+ }
+ }
+ *ref_array= item;
+ if (real_fields)
+ {
+ (void) new_it++; // Point to next item
+ new_it.replace(item); // Replace previous
+ ref_array++;
+ }
+ else
+ ref_array--;
+ }
+ }
+ sum_funcs_end[0]= *func; // Point to last function
+ return 0;
+}
+
+/**
+ Send all rollup levels higher than the current one to the client.
+
+ @b SAMPLE
+ @code
+ SELECT a, b, c SUM(b) FROM t1 GROUP BY a,b WITH ROLLUP
+ @endcode
+
+ @param idx Level we are on:
+ - 0 = Total sum level
+ - 1 = First group changed (a)
+ - 2 = Second group changed (a,b)
+
+ @retval
+ 0 ok
+ @retval
+ 1 If send_data_failed()
+*/
+
+int JOIN::rollup_send_data(uint idx)
+{
+ uint i;
+ for (i= send_group_parts ; i-- > idx ; )
+ {
+ int res= 0;
+ /* Get reference pointers to sum functions in place */
+ memcpy((char*) ref_pointer_array,
+ (char*) rollup.ref_pointer_arrays[i],
+ ref_pointer_array_size);
+ if ((!having || having->val_int()))
+ {
+ if (send_records < unit->select_limit_cnt && do_send_rows &&
+ (res= result->send_data(rollup.fields[i])) > 0)
+ return 1;
+ if (!res)
+ send_records++;
+ }
+ }
+ /* Restore ref_pointer_array */
+ set_items_ref_array(current_ref_pointer_array);
+ return 0;
+}
+
+/**
+ Write all rollup levels higher than the current one to a temp table.
+
+ @b SAMPLE
+ @code
+ SELECT a, b, SUM(c) FROM t1 GROUP BY a,b WITH ROLLUP
+ @endcode
+
+ @param idx Level we are on:
+ - 0 = Total sum level
+ - 1 = First group changed (a)
+ - 2 = Second group changed (a,b)
+ @param table reference to temp table
+
+ @retval
+ 0 ok
+ @retval
+ 1 if write_data_failed()
+*/
+
+int JOIN::rollup_write_data(uint idx, TABLE *table_arg)
+{
+ uint i;
+ for (i= send_group_parts ; i-- > idx ; )
+ {
+ /* Get reference pointers to sum functions in place */
+ memcpy((char*) ref_pointer_array,
+ (char*) rollup.ref_pointer_arrays[i],
+ ref_pointer_array_size);
+ if ((!having || having->val_int()))
+ {
+ int write_error;
+ Item *item;
+ List_iterator_fast<Item> it(rollup.fields[i]);
+ while ((item= it++))
+ {
+ if (item->type() == Item::NULL_ITEM && item->is_result_field())
+ item->save_in_result_field(1);
+ }
+ copy_sum_funcs(sum_funcs_end[i+1], sum_funcs_end[i]);
+ if ((write_error= table_arg->file->ha_write_tmp_row(table_arg->record[0])))
+ {
+ if (create_internal_tmp_table_from_heap(thd, table_arg,
+ tmp_table_param.start_recinfo,
+ &tmp_table_param.recinfo,
+ write_error, 0, NULL))
+ return 1;
+ }
+ }
+ }
+ /* Restore ref_pointer_array */
+ set_items_ref_array(current_ref_pointer_array);
+ return 0;
+}
+
+/**
+ clear results if there are not rows found for group
+ (end_send_group/end_write_group)
+*/
+
+void JOIN::clear()
+{
+ clear_tables(this);
+ copy_fields(&tmp_table_param);
+
+ if (sum_funcs)
+ {
+ Item_sum *func, **func_ptr= sum_funcs;
+ while ((func= *(func_ptr++)))
+ func->clear();
+ }
+}
+
+
+/*
+ Print an EXPLAIN line with all NULLs and given message in the 'Extra' column
+*/
+
+int print_explain_message_line(select_result_sink *result,
+ uint8 options,
+ uint select_number,
+ const char *select_type,
+ ha_rows *rows,
+ const char *message)
+{
+ Item *item_null= new Item_null();
+ List<Item> item_list;
+
+ item_list.push_back(new Item_int((int32) select_number));
+ item_list.push_back(new Item_string_sys(select_type));
+ /* `table` */
+ item_list.push_back(item_null);
+
+ /* `partitions` */
+ if (options & DESCRIBE_PARTITIONS)
+ item_list.push_back(item_null);
+
+ /* type, possible_keys, key, key_len, ref */
+ for (uint i=0 ; i < 5; i++)
+ item_list.push_back(item_null);
+
+ /* `rows` */
+ if (rows)
+ {
+ item_list.push_back(new Item_int(*rows,
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ }
+ else
+ item_list.push_back(item_null);
+
+ /* `filtered` */
+ if (options & DESCRIBE_EXTENDED)
+ item_list.push_back(item_null);
+
+ /* `Extra` */
+ if (message)
+ item_list.push_back(new Item_string_sys(message));
+ else
+ item_list.push_back(item_null);
+
+ if (result->send_data(item_list))
+ return 1;
+ return 0;
+}
+
+
+/*
+ Make a comma-separated list of possible_keys names and add it into the string
+*/
+
+void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line)
+{
+ if (!possible_keys.is_clear_all())
+ {
+ uint j;
+ for (j=0 ; j < table->s->keys ; j++)
+ {
+ if (possible_keys.is_set(j))
+ {
+ if (line->length())
+ line->append(',');
+ line->append(table->key_info[j].name,
+ strlen(table->key_info[j].name),
+ system_charset_info);
+ }
+ }
+ }
+}
+
+/*
+ Print an EXPLAIN output row, based on information provided in the parameters
+
+ @note
+ Parameters that may have NULL value in EXPLAIN output, should be passed
+ (char*)NULL.
+
+ @return
+ 0 - OK
+ 1 - OOM Error
+*/
+
+int print_explain_row(select_result_sink *result,
+ uint8 options,
+ uint select_number,
+ const char *select_type,
+ const char *table_name,
+ const char *partitions,
+ enum join_type jtype,
+ const char *possible_keys,
+ const char *index,
+ const char *key_len,
+ const char *ref,
+ ha_rows *rows,
+ const char *extra)
+{
+ Item *item_null= new Item_null();
+ List<Item> item_list;
+ Item *item;
+
+ item_list.push_back(new Item_int((int32) select_number));
+ item_list.push_back(new Item_string_sys(select_type));
+ item_list.push_back(new Item_string_sys(table_name));
+ if (options & DESCRIBE_PARTITIONS)
+ {
+ if (partitions)
+ {
+ item_list.push_back(new Item_string_sys(partitions));
+ }
+ else
+ item_list.push_back(item_null);
+ }
+
+ const char *jtype_str= join_type_str[jtype];
+ item_list.push_back(new Item_string_sys(jtype_str));
+
+ item= possible_keys? new Item_string_sys(possible_keys) : item_null;
+ item_list.push_back(item);
+
+ /* 'index */
+ item= index ? new Item_string_sys(index) : item_null;
+ item_list.push_back(item);
+
+ /* 'key_len */
+ item= key_len ? new Item_string_sys(key_len) : item_null;
+ item_list.push_back(item);
+
+ /* 'ref' */
+ item= ref ? new Item_string_sys(ref) : item_null;
+ item_list.push_back(item);
+
+ /* 'rows' */
+ if (rows)
+ {
+ item_list.push_back(new Item_int(*rows,
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ }
+ else
+ item_list.push_back(item_null);
+
+ /* 'filtered' */
+ const double filtered=100.0;
+ if (options & DESCRIBE_EXTENDED)
+ item_list.push_back(new Item_float(filtered, 2));
+
+ /* 'Extra' */
+ if (extra)
+ item_list.push_back(new Item_string_sys(extra));
+ else
+ item_list.push_back(item_null);
+
+ if (result->send_data(item_list))
+ return 1;
+ return 0;
+}
+
+
+int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly,
+ SELECT_LEX *select_lex, uint8 explain_flags)
+{
+ Item *item_null= new Item_null();
+ List<Item> item_list;
+ if (on_the_fly)
+ select_lex->set_explain_type(on_the_fly);
+ /*
+ here we assume that the query will return at least two rows, so we
+ show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong
+ and no filesort will be actually done, but executing all selects in
+ the UNION to provide precise EXPLAIN information will hardly be
+ appreciated :)
+ */
+ char table_name_buffer[SAFE_NAME_LEN];
+ item_list.empty();
+ /* id */
+ item_list.push_back(new Item_null);
+ /* select_type */
+ item_list.push_back(new Item_string_sys(select_lex->type));
+ /* table */
+ {
+ SELECT_LEX *sl= select_lex->master_unit()->first_select();
+ uint len= 6, lastop= 0;
+ memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
+ for (; sl && len + lastop + 5 < NAME_LEN; sl= sl->next_select())
+ {
+ len+= lastop;
+ lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len,
+ "%u,", sl->select_number);
+ }
+ if (sl || len + lastop >= NAME_LEN)
+ {
+ memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1);
+ len+= 4;
+ }
+ else
+ {
+ len+= lastop;
+ table_name_buffer[len - 1]= '>'; // change ',' to '>'
+ }
+ item_list.push_back(new Item_string_sys(table_name_buffer, len));
+ }
+ /* partitions */
+ if (explain_flags & DESCRIBE_PARTITIONS)
+ item_list.push_back(item_null);
+ /* type */
+ item_list.push_back(new Item_string_sys(join_type_str[JT_ALL]));
+
+ /* possible_keys */
+ item_list.push_back(item_null);
+ /* key*/
+ item_list.push_back(item_null);
+ /* key_len */
+ item_list.push_back(item_null);
+ /* ref */
+ item_list.push_back(item_null);
+ /* in_rows */
+ if (explain_flags & DESCRIBE_EXTENDED)
+ item_list.push_back(item_null);
+ /* rows */
+ item_list.push_back(item_null);
+ /* extra */
+ if (select_lex->master_unit()->global_parameters->order_list.first)
+ item_list.push_back(new Item_string_sys("Using filesort", 14));
+ else
+ item_list.push_back(new Item_string_sys("", 0));
+
+ if (result->send_data(item_list))
+ return 1;
+ return 0;
+}
+
+
+/*
+ Append MRR information from quick select to the given string
+*/
+
+void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res)
+{
+ char mrr_str_buf[128];
+ mrr_str_buf[0]=0;
+ int len;
+ handler *h= quick->head->file;
+ len= h->multi_range_read_explain_info(quick->mrr_flags, mrr_str_buf,
+ sizeof(mrr_str_buf));
+ if (len > 0)
+ {
+ //res->append(STRING_WITH_LEN("; "));
+ res->append(mrr_str_buf, len);
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TODO: join with make_possible_keys_line ?
+void append_possible_keys(String *str, TABLE *table, key_map possible_keys)
+{
+ uint j;
+ for (j=0 ; j < table->s->keys ; j++)
+ {
+ if (possible_keys.is_set(j))
+ {
+ if (str->length())
+ str->append(',');
+ str->append(table->key_info[j].name,
+ strlen(table->key_info[j].name),
+ system_charset_info);
+ }
+ }
+}
+
+
+/*
+ Save Query Plan Footprint
+
+ @note
+ Currently, this function may be called multiple times
+*/
+
+int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table,
+ bool need_order, bool distinct,
+ const char *message)
+{
+ Explain_node *explain_node;
+ JOIN *join= this; /* Legacy: this code used to be a non-member function */
+ THD *thd=join->thd;
+ const CHARSET_INFO *cs= system_charset_info;
+ int quick_type;
+ int error= 0;
+ DBUG_ENTER("JOIN::save_explain_data_intern");
+ DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s",
+ (ulong)join->select_lex, join->select_lex->type,
+ message ? message : "NULL"));
+ DBUG_ASSERT(have_query_plan == QEP_AVAILABLE);
+ /* Don't log this into the slow query log */
+
+ if (message)
+ {
+ Explain_select *xpl_sel;
+ explain_node= xpl_sel= new (output->mem_root) Explain_select;
+ join->select_lex->set_explain_type(true);
+
+ xpl_sel->select_id= join->select_lex->select_number;
+ xpl_sel->select_type= join->select_lex->type;
+ xpl_sel->message= message;
+ /* Setting xpl_sel->message means that all other members are invalid */
+ output->add_node(xpl_sel);
+ }
+ else if (join->select_lex == join->unit->fake_select_lex)
+ {
+ /* Do nothing, Explain_union will create and print fake_select_lex */
+ }
+ else if (!join->select_lex->master_unit()->derived ||
+ join->select_lex->master_unit()->derived->is_materialized_derived())
+ {
+ Explain_select *xpl_sel;
+ explain_node= xpl_sel= new (output->mem_root) Explain_select;
+ table_map used_tables=0;
+
+ join->select_lex->set_explain_type(true);
+ xpl_sel->select_id= join->select_lex->select_number;
+ xpl_sel->select_type= join->select_lex->type;
+
+ JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS);
+
+ for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab;
+ tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab))
+ {
+ uint select_id;
+ if (tab->bush_root_tab)
+ {
+ JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start;
+ select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier();
+ }
+ else
+ select_id= join->select_lex->select_number;
+
+ TABLE *table=tab->table;
+ TABLE_LIST *table_list= tab->table->pos_in_table_list;
+ char buff4[512];
+ my_bool key_read;
+ char table_name_buffer[SAFE_NAME_LEN];
+ String tmp4(buff4,sizeof(buff4),cs);
+ KEY *key_info= 0;
+ uint key_len= 0;
+ tmp4.length(0);
+ quick_type= -1;
+ QUICK_SELECT_I *quick= NULL;
+ JOIN_TAB *saved_join_tab= NULL;
+
+ /* Don't show eliminated tables */
+ if (table->map & join->eliminated_tables)
+ {
+ used_tables|=table->map;
+ continue;
+ }
+
+ if (join->table_access_tabs == join->join_tab &&
+ tab == (first_top_tab + join->const_tables) && pre_sort_join_tab)
+ {
+ saved_join_tab= tab;
+ tab= pre_sort_join_tab;
+ }
+
+ Explain_table_access *eta= new (output->mem_root) Explain_table_access;
+ xpl_sel->add_table(eta);
+ eta->key.set(thd->mem_root, NULL, (uint)-1);
+ eta->quick_info= NULL;
+
+ /* id */
+ if (tab->bush_root_tab)
+ eta->sjm_nest_select_id= select_id;
+ else
+ eta->sjm_nest_select_id= 0;
+
+ /* select_type */
+ xpl_sel->select_type= join->select_lex->type;
+
+ /* table */
+ if (table->derived_select_number)
+ {
+ /* Derived table name generation */
+ int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1,
+ "<derived%u>",
+ table->derived_select_number);
+ eta->table_name.copy(table_name_buffer, len, cs);
+ }
+ else if (tab->bush_children)
+ {
+ JOIN_TAB *ctab= tab->bush_children->start;
+ /* table */
+ int len= my_snprintf(table_name_buffer,
+ sizeof(table_name_buffer)-1,
+ "<subquery%d>",
+ ctab->emb_sj_nest->sj_subq_pred->get_identifier());
+ eta->table_name.copy(table_name_buffer, len, cs);
+ }
+ else
+ {
+ TABLE_LIST *real_table= table->pos_in_table_list;
+ eta->table_name.copy(real_table->alias, strlen(real_table->alias), cs);
+ }
+
+ /* "partitions" column */
+ {
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info;
+ if (!table->derived_select_number &&
+ (part_info= table->part_info))
+ {
+ make_used_partitions_str(part_info, &eta->used_partitions);
+ eta->used_partitions_set= true;
+ }
+ else
+ eta->used_partitions_set= false;
+#else
+ /* just produce empty column if partitioning is not compiled in */
+ eta->used_partitions_set= false;
+#endif
+ }
+
+ /* "type" column */
+ enum join_type tab_type= tab->type;
+ if ((tab->type == JT_ALL || tab->type == JT_HASH) &&
+ tab->select && tab->select->quick && tab->use_quick != 2)
+ {
+ quick= tab->select->quick;
+ quick_type= tab->select->quick->get_type();
+ if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) ||
+ (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) ||
+ (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
+ (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
+ tab_type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE;
+ else
+ tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE;
+ }
+ eta->type= tab_type;
+
+ /* Build "possible_keys" value */
+ append_possible_keys(&eta->possible_keys_str, table, tab->keys);
+
+ /* Build "key", "key_len", and "ref" */
+ if (tab_type == JT_NEXT)
+ {
+ key_info= table->key_info+tab->index;
+ key_len= key_info->key_length;
+ }
+ else if (tab->ref.key_parts)
+ {
+ key_info= tab->get_keyinfo_by_key_no(tab->ref.key);
+ key_len= tab->ref.key_length;
+ }
+
+ /*
+ In STRAIGHT_JOIN queries, there can be join tabs with JT_CONST type
+ that still have quick selects.
+ */
+ if (tab->select && tab->select->quick && tab_type != JT_CONST)
+ {
+ eta->quick_info= tab->select->quick->get_explain(thd->mem_root);
+ }
+
+ if (key_info) /* 'index' or 'ref' access */
+ {
+ eta->key.set(thd->mem_root, key_info->name, key_len);
+
+ if (tab->ref.key_parts && tab_type != JT_FT)
+ {
+ store_key **ref=tab->ref.key_copy;
+ for (uint kp= 0; kp < tab->ref.key_parts; kp++)
+ {
+ if (tmp4.length())
+ tmp4.append(',');
+
+ if ((key_part_map(1) << kp) & tab->ref.const_ref_part_map)
+ tmp4.append("const");
+ else
+ {
+ tmp4.append((*ref)->name(), strlen((*ref)->name()), cs);
+ ref++;
+ }
+ }
+ }
+ }
+
+ if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */
+ {
+ eta->hash_next_key.set(thd->mem_root,
+ table->key_info[tab->index].name,
+ table->key_info[tab->index].key_length);
+ }
+
+ if (key_info)
+ {
+ if (key_info && tab_type != JT_NEXT)
+ {
+ eta->ref.copy(tmp4);
+ eta->ref_set= true;
+ }
+ else
+ eta->ref_set= false;
+ }
+ else
+ {
+ if (table_list && /* SJM bushes don't have table_list */
+ table_list->schema_table &&
+ table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE)
+ {
+ const char *tmp_buff;
+ int f_idx;
+ StringBuffer<64> key_name_buf;
+ if (table_list->has_db_lookup_value)
+ {
+ /* The "key" has the name of the column referring to the database */
+ f_idx= table_list->schema_table->idx_field1;
+ tmp_buff= table_list->schema_table->fields_info[f_idx].field_name;
+ key_name_buf.append(tmp_buff, strlen(tmp_buff), cs);
+ }
+ if (table_list->has_table_lookup_value)
+ {
+ if (table_list->has_db_lookup_value)
+ key_name_buf.append(',');
+
+ f_idx= table_list->schema_table->idx_field2;
+ tmp_buff= table_list->schema_table->fields_info[f_idx].field_name;
+ key_name_buf.append(tmp_buff, strlen(tmp_buff), cs);
+ }
+
+ if (key_name_buf.length())
+ eta->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1);
+ }
+ eta->ref_set= false;
+ }
+
+ /* "rows" */
+ if (table_list /* SJM bushes don't have table_list */ &&
+ table_list->schema_table)
+ {
+ /* I_S tables have rows=extra=NULL */
+ eta->rows_set= false;
+ eta->filtered_set= false;
+ }
+ else
+ {
+ double examined_rows= tab->get_examined_rows();
+
+ eta->rows_set= true;
+ eta->rows= (ha_rows) examined_rows;
+
+ /* "filtered" */
+ float f= 0.0;
+ if (examined_rows)
+ {
+ double pushdown_cond_selectivity= tab->cond_selectivity;
+ if (pushdown_cond_selectivity == 1.0)
+ f= (float) (100.0 * tab->records_read / examined_rows);
+ else
+ f= (float) (100.0 * pushdown_cond_selectivity);
+ }
+ set_if_smaller(f, 100.0);
+ eta->filtered_set= true;
+ eta->filtered= f;
+ }
+
+ /* Build "Extra" field and save it */
+ key_read=table->key_read;
+ if ((tab_type == JT_NEXT || tab_type == JT_CONST) &&
+ table->covering_keys.is_set(tab->index))
+ key_read=1;
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT &&
+ !((QUICK_ROR_INTERSECT_SELECT*)quick)->need_to_fetch_row)
+ key_read=1;
+
+ if (tab->info)
+ {
+ eta->push_extra(tab->info);
+ }
+ else if (tab->packed_info & TAB_INFO_HAVE_VALUE)
+ {
+ if (tab->packed_info & TAB_INFO_USING_INDEX)
+ eta->push_extra(ET_USING_INDEX);
+ if (tab->packed_info & TAB_INFO_USING_WHERE)
+ eta->push_extra(ET_USING_WHERE);
+ if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL)
+ eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY);
+ }
+ else
+ {
+ uint keyno= MAX_KEY;
+ if (tab->ref.key_parts)
+ keyno= tab->ref.key;
+ else if (tab->select && quick)
+ keyno = quick->index;
+
+ if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno &&
+ table->file->pushed_idx_cond)
+ eta->push_extra(ET_USING_INDEX_CONDITION);
+ else if (tab->cache_idx_cond)
+ eta->push_extra(ET_USING_INDEX_CONDITION_BKA);
+
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT ||
+ quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
+ {
+ eta->push_extra(ET_USING);
+ }
+ if (tab->select)
+ {
+ if (tab->use_quick == 2)
+ {
+ eta->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD);
+ eta->range_checked_map= tab->keys;
+ }
+ else if (tab->select->cond ||
+ (tab->cache_select && tab->cache_select->cond))
+ {
+ const COND *pushed_cond= tab->table->file->pushed_cond;
+
+ if (thd->use_cond_push(tab->table->file) && pushed_cond)
+ {
+ eta->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION);
+ /*
+ psergey-todo: what to do? This was useful with NDB only.
+
+ if (explain_flags & DESCRIBE_EXTENDED)
+ {
+ extra.append(STRING_WITH_LEN(": "));
+ ((COND *)pushed_cond)->print(&extra, QT_ORDINARY);
+ }
+ */
+ }
+ else
+ eta->push_extra(ET_USING_WHERE);
+ }
+ }
+ if (table_list /* SJM bushes don't have table_list */ &&
+ table_list->schema_table &&
+ table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE)
+ {
+ if (!table_list->table_open_method)
+ eta->push_extra(ET_SKIP_OPEN_TABLE);
+ else if (table_list->table_open_method == OPEN_FRM_ONLY)
+ eta->push_extra(ET_OPEN_FRM_ONLY);
+ else
+ eta->push_extra(ET_OPEN_FULL_TABLE);
+ /* psergey-note: the following has a bug.*/
+ if (table_list->has_db_lookup_value &&
+ table_list->has_table_lookup_value)
+ eta->push_extra(ET_SCANNED_0_DATABASES);
+ else if (table_list->has_db_lookup_value ||
+ table_list->has_table_lookup_value)
+ eta->push_extra(ET_SCANNED_1_DATABASE);
+ else
+ eta->push_extra(ET_SCANNED_ALL_DATABASES);
+ }
+ if (key_read)
+ {
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
+ {
+ QUICK_GROUP_MIN_MAX_SELECT *qgs=
+ (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick;
+ eta->push_extra(ET_USING_INDEX_FOR_GROUP_BY);
+ eta->loose_scan_is_scanning= qgs->loose_scan_is_scanning();
+ }
+ else
+ eta->push_extra(ET_USING_INDEX);
+ }
+ if (table->reginfo.not_exists_optimize)
+ eta->push_extra(ET_NOT_EXISTS);
+
+ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE)
+ {
+ explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick),
+ &eta->mrr_type);
+ if (eta->mrr_type.length() > 0)
+ eta->push_extra(ET_USING_MRR);
+ }
+
+ if (need_tmp_table)
+ {
+ need_tmp_table=0;
+ xpl_sel->using_temporary= true;
+ }
+ if (need_order)
+ {
+ need_order=0;
+ xpl_sel->using_filesort= true;
+ }
+ if (distinct & test_all_bits(used_tables,
+ join->select_list_used_tables))
+ eta->push_extra(ET_DISTINCT);
+ if (tab->loosescan_match_tab)
+ {
+ eta->push_extra(ET_LOOSESCAN);
+ }
+
+ if (tab->first_weedout_table)
+ eta->push_extra(ET_START_TEMPORARY);
+ if (tab->check_weed_out_table)
+ eta->push_extra(ET_END_TEMPORARY);
+ else if (tab->do_firstmatch)
+ {
+ if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1)
+ eta->push_extra(ET_FIRST_MATCH);
+ else
+ {
+ eta->push_extra(ET_FIRST_MATCH);
+ TABLE *prev_table=tab->do_firstmatch->table;
+ if (prev_table->derived_select_number)
+ {
+ char namebuf[NAME_LEN];
+ /* Derived table name generation */
+ int len= my_snprintf(namebuf, sizeof(namebuf)-1,
+ "<derived%u>",
+ prev_table->derived_select_number);
+ eta->firstmatch_table_name.append(namebuf, len);
+ }
+ else
+ eta->firstmatch_table_name.append(prev_table->pos_in_table_list->alias);
+ }
+ }
+
+ for (uint part= 0; part < tab->ref.key_parts; part++)
+ {
+ if (tab->ref.cond_guards[part])
+ {
+ eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY);
+ break;
+ }
+ }
+
+ if (tab->cache)
+ {
+ eta->push_extra(ET_USING_JOIN_BUFFER);
+ tab->cache->save_explain_data(&eta->bka_type);
+ }
+ }
+
+ if (saved_join_tab)
+ tab= saved_join_tab;
+
+ // For next iteration
+ used_tables|=table->map;
+ }
+ output->add_node(xpl_sel);
+ }
+
+ for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
+ unit;
+ unit= unit->next_unit())
+ {
+ /*
+ Display subqueries only if
+ (1) they are not parts of ON clauses that were eliminated by table
+ elimination.
+ (2) they are not merged derived tables
+ */
+ if (!(unit->item && unit->item->eliminated) && // (1)
+ (!unit->derived || unit->derived->is_materialized_derived())) // (2)
+ {
+ explain_node->add_child(unit->first_select()->select_number);
+ }
+ }
+
+ if (!error && select_lex->is_top_level_node())
+ output->query_plan_ready();
+
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+ This function serves as "shortcut point" for EXPLAIN queries.
+
+ The EXPLAIN statement executes just like its SELECT counterpart would
+ execute, except that JOIN::exec() will call select_describe() instead of
+ actually executing the query.
+
+ Inside select_describe():
+ - Query plan is updated with latest QEP choices made at the start of
+ JOIN::exec().
+ - the proces of "almost execution" is invoked for the children subqueries.
+
+ Overall, select_describe() is a legacy of old EXPLAIN implementation and
+ should be removed.
+*/
+
+static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
+ bool distinct,const char *message)
+{
+ THD *thd=join->thd;
+ select_result *result=join->result;
+ DBUG_ENTER("select_describe");
+
+ /* Update the QPF with latest values of using_temporary, using_filesort */
+ Explain_select *explain_sel;
+ uint select_nr= join->select_lex->select_number;
+ if ((explain_sel= thd->lex->explain->get_select(select_nr)))
+ {
+ explain_sel->using_temporary= need_tmp_table;
+ explain_sel->using_filesort= need_order;
+ }
+
+ for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
+ unit;
+ unit= unit->next_unit())
+ {
+ /*
+ This fix_fields() call is to handle an edge case like this:
+
+ SELECT ... UNION SELECT ... ORDER BY (SELECT ...)
+
+ for such queries, we'll get here before having called
+ subquery_expr->fix_fields(), which will cause failure to
+ */
+ if (unit->item && !unit->item->fixed)
+ {
+ Item *ref= unit->item;
+ if (unit->item->fix_fields(thd, &ref))
+ DBUG_VOID_RETURN;
+ DBUG_ASSERT(ref == unit->item);
+ }
+
+ /*
+ Display subqueries only if they are not parts of eliminated WHERE/ON
+ clauses.
+ */
+ if (!(unit->item && unit->item->eliminated))
+ {
+ if (mysql_explain_union(thd, unit, result))
+ DBUG_VOID_RETURN;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result)
+{
+ DBUG_ENTER("mysql_explain_union");
+ bool res= 0;
+ SELECT_LEX *first= unit->first_select();
+
+ for (SELECT_LEX *sl= first; sl; sl= sl->next_select())
+ {
+ sl->set_explain_type(FALSE);
+ sl->options|= SELECT_DESCRIBE;
+ }
+
+ if (unit->is_union())
+ {
+ unit->fake_select_lex->select_number= FAKE_SELECT_LEX_ID; // jost for initialization
+ unit->fake_select_lex->type= "UNION RESULT";
+ unit->fake_select_lex->options|= SELECT_DESCRIBE;
+ if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE)))
+ res= unit->exec();
+ }
+ else
+ {
+ thd->lex->current_select= first;
+ unit->set_limit(unit->global_parameters);
+ res= mysql_select(thd, &first->ref_pointer_array,
+ first->table_list.first,
+ first->with_wild, first->item_list,
+ first->where,
+ first->order_list.elements +
+ first->group_list.elements,
+ first->order_list.first,
+ first->group_list.first,
+ first->having,
+ thd->lex->proc_list.first,
+ first->options | thd->variables.option_bits | SELECT_DESCRIBE,
+ result, unit, first);
+ }
+ DBUG_RETURN(res || thd->is_error());
+}
+
+
+static void print_table_array(THD *thd,
+ table_map eliminated_tables,
+ String *str, TABLE_LIST **table,
+ TABLE_LIST **end,
+ enum_query_type query_type)
+{
+ (*table)->print(thd, eliminated_tables, str, query_type);
+
+ for (TABLE_LIST **tbl= table + 1; tbl < end; tbl++)
+ {
+ TABLE_LIST *curr= *tbl;
+
+ /*
+ The "eliminated_tables &&" check guards againist the case of
+ printing the query for CREATE VIEW. We do that without having run
+ JOIN::optimize() and so will have nested_join->used_tables==0.
+ */
+ if (eliminated_tables &&
+ ((curr->table && (curr->table->map & eliminated_tables)) ||
+ (curr->nested_join && !(curr->nested_join->used_tables &
+ ~eliminated_tables))))
+ {
+ /* as of 5.5, print_join doesnt put eliminated elements into array */
+ DBUG_ASSERT(0);
+ continue;
+ }
+
+ if (curr->outer_join)
+ {
+ /* MySQL converts right to left joins */
+ str->append(STRING_WITH_LEN(" left join "));
+ }
+ else if (curr->straight)
+ str->append(STRING_WITH_LEN(" straight_join "));
+ else if (curr->sj_inner_tables)
+ str->append(STRING_WITH_LEN(" semi join "));
+ else
+ str->append(STRING_WITH_LEN(" join "));
+ curr->print(thd, eliminated_tables, str, query_type);
+ if (curr->on_expr)
+ {
+ str->append(STRING_WITH_LEN(" on("));
+ curr->on_expr->print(str, query_type);
+ str->append(')');
+ }
+ }
+}
+
+
+/*
+ Check if the passed table is
+ - a base table which was eliminated, or
+ - a join nest which only contained eliminated tables (and so was eliminated,
+ too)
+*/
+
+static bool is_eliminated_table(table_map eliminated_tables, TABLE_LIST *tbl)
+{
+ return eliminated_tables &&
+ ((tbl->table && (tbl->table->map & eliminated_tables)) ||
+ (tbl->nested_join && !(tbl->nested_join->used_tables &
+ ~eliminated_tables)));
+}
+
+/**
+ Print joins from the FROM clause.
+
+ @param thd thread handler
+ @param str string where table should be printed
+ @param tables list of tables in join
+ @query_type type of the query is being generated
+*/
+
+static void print_join(THD *thd,
+ table_map eliminated_tables,
+ String *str,
+ List<TABLE_LIST> *tables,
+ enum_query_type query_type)
+{
+ /* List is reversed => we should reverse it before using */
+ List_iterator_fast<TABLE_LIST> ti(*tables);
+ TABLE_LIST **table;
+ uint non_const_tables= 0;
+ DBUG_ENTER("print_join");
+
+ for (TABLE_LIST *t= ti++; t ; t= ti++)
+ {
+ /*
+ See comment in print_table_array() about the second part of the
+ condition
+ */
+ if (!t->optimized_away && !is_eliminated_table(eliminated_tables, t))
+ non_const_tables++;
+ }
+ if (!non_const_tables)
+ {
+ str->append(STRING_WITH_LEN("dual"));
+ DBUG_VOID_RETURN; // all tables were optimized away
+ }
+ ti.rewind();
+
+ if (!(table= (TABLE_LIST **)thd->alloc(sizeof(TABLE_LIST*) *
+ non_const_tables)))
+ DBUG_VOID_RETURN; // out of memory
+
+ TABLE_LIST *tmp, **t= table + (non_const_tables - 1);
+ while ((tmp= ti++))
+ {
+ if (tmp->optimized_away || is_eliminated_table(eliminated_tables, tmp))
+ continue;
+ *t--= tmp;
+ }
+
+ DBUG_ASSERT(tables->elements >= 1);
+ /*
+ Assert that the first table in the list isn't eliminated. This comes from
+ the fact that the first table can't be inner table of an outer join.
+ */
+ DBUG_ASSERT(!eliminated_tables ||
+ !(((*table)->table && ((*table)->table->map & eliminated_tables)) ||
+ ((*table)->nested_join && !((*table)->nested_join->used_tables &
+ ~eliminated_tables))));
+ /*
+ If the first table is a semi-join nest, swap it with something that is
+ not a semi-join nest.
+ */
+ if ((*table)->sj_inner_tables)
+ {
+ TABLE_LIST **end= table + non_const_tables;
+ for (TABLE_LIST **t2= table; t2!=end; t2++)
+ {
+ if (!(*t2)->sj_inner_tables)
+ {
+ TABLE_LIST *tmp= *t2;
+ *t2= *table;
+ *table= tmp;
+ break;
+ }
+ }
+ }
+ print_table_array(thd, eliminated_tables, str, table,
+ table + non_const_tables, query_type);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ @brief Print an index hint
+
+ @details Prints out the USE|FORCE|IGNORE index hint.
+
+ @param thd the current thread
+ @param[out] str appends the index hint here
+ @param hint what the hint is (as string : "USE INDEX"|
+ "FORCE INDEX"|"IGNORE INDEX")
+ @param hint_length the length of the string in 'hint'
+ @param indexes a list of index names for the hint
+*/
+
+void
+Index_hint::print(THD *thd, String *str)
+{
+ switch (type)
+ {
+ case INDEX_HINT_IGNORE: str->append(STRING_WITH_LEN("IGNORE INDEX")); break;
+ case INDEX_HINT_USE: str->append(STRING_WITH_LEN("USE INDEX")); break;
+ case INDEX_HINT_FORCE: str->append(STRING_WITH_LEN("FORCE INDEX")); break;
+ }
+ str->append (STRING_WITH_LEN(" ("));
+ if (key_name.length)
+ {
+ if (thd && !my_strnncoll(system_charset_info,
+ (const uchar *)key_name.str, key_name.length,
+ (const uchar *)primary_key_name,
+ strlen(primary_key_name)))
+ str->append(primary_key_name);
+ else
+ append_identifier(thd, str, key_name.str, key_name.length);
+ }
+ str->append(')');
+}
+
+
+/**
+ Print table as it should be in join list.
+
+ @param str string where table should be printed
+*/
+
+void TABLE_LIST::print(THD *thd, table_map eliminated_tables, String *str,
+ enum_query_type query_type)
+{
+ if (nested_join)
+ {
+ str->append('(');
+ print_join(thd, eliminated_tables, str, &nested_join->join_list, query_type);
+ str->append(')');
+ }
+ else if (jtbm_subselect)
+ {
+ if (jtbm_subselect->engine->engine_type() ==
+ subselect_engine::SINGLE_SELECT_ENGINE)
+ {
+ /*
+ We get here when conversion into materialization didn't finish (this
+ happens when
+ - The subquery is a degenerate case which produces 0 or 1 record
+ - subquery's optimization didn't finish because of @@max_join_size
+ limits
+ - ... maybe some other cases like this
+ */
+ str->append(STRING_WITH_LEN(" <materialize> ("));
+ jtbm_subselect->engine->print(str, query_type);
+ str->append(')');
+ }
+ else
+ {
+ str->append(STRING_WITH_LEN(" <materialize> ("));
+ subselect_hash_sj_engine *hash_engine;
+ hash_engine= (subselect_hash_sj_engine*)jtbm_subselect->engine;
+ hash_engine->materialize_engine->print(str, query_type);
+ str->append(')');
+ }
+ }
+ else
+ {
+ const char *cmp_name; // Name to compare with alias
+ if (view_name.str)
+ {
+ // A view
+
+ if (!(belong_to_view &&
+ belong_to_view->compact_view_format))
+ {
+ append_identifier(thd, str, view_db.str, view_db.length);
+ str->append('.');
+ }
+ append_identifier(thd, str, view_name.str, view_name.length);
+ cmp_name= view_name.str;
+ }
+ else if (derived)
+ {
+ // A derived table
+ str->append('(');
+ derived->print(str, query_type);
+ str->append(')');
+ cmp_name= ""; // Force printing of alias
+ }
+ else
+ {
+ // A normal table
+
+ if (!(belong_to_view &&
+ belong_to_view->compact_view_format))
+ {
+ append_identifier(thd, str, db, db_length);
+ str->append('.');
+ }
+ if (schema_table)
+ {
+ append_identifier(thd, str, schema_table_name,
+ strlen(schema_table_name));
+ cmp_name= schema_table_name;
+ }
+ else
+ {
+ append_identifier(thd, str, table_name, table_name_length);
+ cmp_name= table_name;
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (partition_names && partition_names->elements)
+ {
+ int i, num_parts= partition_names->elements;
+ List_iterator<String> name_it(*(partition_names));
+ str->append(STRING_WITH_LEN(" PARTITION ("));
+ for (i= 1; i <= num_parts; i++)
+ {
+ String *name= name_it++;
+ append_identifier(thd, str, name->c_ptr(), name->length());
+ if (i != num_parts)
+ str->append(',');
+ }
+ str->append(')');
+ }
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
+ }
+ if (my_strcasecmp(table_alias_charset, cmp_name, alias))
+ {
+ char t_alias_buff[MAX_ALIAS_NAME];
+ const char *t_alias= alias;
+
+ str->append(' ');
+ if (lower_case_table_names== 1)
+ {
+ if (alias && alias[0])
+ {
+ strmov(t_alias_buff, alias);
+ my_casedn_str(files_charset_info, t_alias_buff);
+ t_alias= t_alias_buff;
+ }
+ }
+
+ append_identifier(thd, str, t_alias, strlen(t_alias));
+ }
+
+ if (index_hints)
+ {
+ List_iterator<Index_hint> it(*index_hints);
+ Index_hint *hint;
+
+ while ((hint= it++))
+ {
+ str->append (STRING_WITH_LEN(" "));
+ hint->print (thd, str);
+ }
+ }
+ }
+}
+
+
+void st_select_lex::print(THD *thd, String *str, enum_query_type query_type)
+{
+ DBUG_ASSERT(thd);
+
+ str->append(STRING_WITH_LEN("select "));
+
+ if (join && join->cleaned)
+ {
+ /*
+ JOIN already cleaned up so it is dangerous to print items
+ because temporary tables they pointed on could be freed.
+ */
+ str->append('#');
+ str->append(select_number);
+ return;
+ }
+
+ /* First add options */
+ if (options & SELECT_STRAIGHT_JOIN)
+ str->append(STRING_WITH_LEN("straight_join "));
+ if (options & SELECT_HIGH_PRIORITY)
+ str->append(STRING_WITH_LEN("high_priority "));
+ if (options & SELECT_DISTINCT)
+ str->append(STRING_WITH_LEN("distinct "));
+ if (options & SELECT_SMALL_RESULT)
+ str->append(STRING_WITH_LEN("sql_small_result "));
+ if (options & SELECT_BIG_RESULT)
+ str->append(STRING_WITH_LEN("sql_big_result "));
+ if (options & OPTION_BUFFER_RESULT)
+ str->append(STRING_WITH_LEN("sql_buffer_result "));
+ if (options & OPTION_FOUND_ROWS)
+ str->append(STRING_WITH_LEN("sql_calc_found_rows "));
+ switch (sql_cache)
+ {
+ case SQL_NO_CACHE:
+ str->append(STRING_WITH_LEN("sql_no_cache "));
+ break;
+ case SQL_CACHE:
+ str->append(STRING_WITH_LEN("sql_cache "));
+ break;
+ case SQL_CACHE_UNSPECIFIED:
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+
+ //Item List
+ bool first= 1;
+ List_iterator_fast<Item> it(item_list);
+ Item *item;
+ while ((item= it++))
+ {
+ if (first)
+ first= 0;
+ else
+ str->append(',');
+
+ if (is_subquery_function() && item->is_autogenerated_name)
+ {
+ /*
+ Do not print auto-generated aliases in subqueries. It has no purpose
+ in a view definition or other contexts where the query is printed.
+ */
+ item->print(str, query_type);
+ }
+ else
+ item->print_item_w_name(str, query_type);
+ }
+
+ /*
+ from clause
+ TODO: support USING/FORCE/IGNORE index
+ */
+ if (table_list.elements)
+ {
+ str->append(STRING_WITH_LEN(" from "));
+ /* go through join tree */
+ print_join(thd, join? join->eliminated_tables: 0, str, &top_join_list, query_type);
+ }
+ else if (where)
+ {
+ /*
+ "SELECT 1 FROM DUAL WHERE 2" should not be printed as
+ "SELECT 1 WHERE 2": the 1st syntax is valid, but the 2nd is not.
+ */
+ str->append(STRING_WITH_LEN(" from DUAL "));
+ }
+
+ // Where
+ Item *cur_where= where;
+ if (join)
+ cur_where= join->conds;
+ if (cur_where || cond_value != Item::COND_UNDEF)
+ {
+ str->append(STRING_WITH_LEN(" where "));
+ if (cur_where)
+ cur_where->print(str, query_type);
+ else
+ str->append(cond_value != Item::COND_FALSE ? "1" : "0");
+ }
+
+ // group by & olap
+ if (group_list.elements)
+ {
+ str->append(STRING_WITH_LEN(" group by "));
+ print_order(str, group_list.first, query_type);
+ switch (olap)
+ {
+ case CUBE_TYPE:
+ str->append(STRING_WITH_LEN(" with cube"));
+ break;
+ case ROLLUP_TYPE:
+ str->append(STRING_WITH_LEN(" with rollup"));
+ break;
+ default:
+ ; //satisfy compiler
+ }
+ }
+
+ // having
+ Item *cur_having= having;
+ if (join)
+ cur_having= join->having;
+
+ if (cur_having || having_value != Item::COND_UNDEF)
+ {
+ str->append(STRING_WITH_LEN(" having "));
+ if (cur_having)
+ cur_having->print(str, query_type);
+ else
+ str->append(having_value != Item::COND_FALSE ? "1" : "0");
+ }
+
+ if (order_list.elements)
+ {
+ str->append(STRING_WITH_LEN(" order by "));
+ print_order(str, order_list.first, query_type);
+ }
+
+ // limit
+ print_limit(thd, str, query_type);
+
+ // PROCEDURE unsupported here
+}
+
+
+/**
+ change select_result object of JOIN.
+
+ @param res new select_result object
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE error
+*/
+
+bool JOIN::change_result(select_result *res)
+{
+ DBUG_ENTER("JOIN::change_result");
+ result= res;
+ if (tmp_join)
+ tmp_join->result= res;
+ if (!procedure && (result->prepare(fields_list, select_lex->master_unit()) ||
+ result->prepare2()))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief
+ Set allowed types of join caches that can be used for join operations
+
+ @details
+ The function sets a bitmap of allowed join buffers types in the field
+ allowed_join_cache_types of this JOIN structure:
+ bit 1 is set if tjoin buffers are allowed to be incremental
+ bit 2 is set if the join buffers are allowed to be hashed
+ but 3 is set if the join buffers are allowed to be used for BKA
+ join algorithms.
+ The allowed types are read from system variables.
+ Besides the function sets maximum allowed join cache level that is
+ also read from a system variable.
+*/
+
+void JOIN::set_allowed_join_cache_types()
+{
+ allowed_join_cache_types= 0;
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_JOIN_CACHE_INCREMENTAL))
+ allowed_join_cache_types|= JOIN_CACHE_INCREMENTAL_BIT;
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_JOIN_CACHE_HASHED))
+ allowed_join_cache_types|= JOIN_CACHE_HASHED_BIT;
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_JOIN_CACHE_BKA))
+ allowed_join_cache_types|= JOIN_CACHE_BKA_BIT;
+ allowed_semijoin_with_cache=
+ optimizer_flag(thd, OPTIMIZER_SWITCH_SEMIJOIN_WITH_CACHE);
+ allowed_outer_join_with_cache=
+ optimizer_flag(thd, OPTIMIZER_SWITCH_OUTER_JOIN_WITH_CACHE);
+ max_allowed_join_cache_level= thd->variables.join_cache_level;
+}
+
+
+/**
+ Save a query execution plan so that the caller can revert to it if needed,
+ and reset the current query plan so that it can be reoptimized.
+
+ @param save_to The object into which the current query plan state is saved
+*/
+
+void JOIN::save_query_plan(Join_plan_state *save_to)
+{
+ if (keyuse.elements)
+ {
+ DYNAMIC_ARRAY tmp_keyuse;
+ /* Swap the current and the backup keyuse internal arrays. */
+ tmp_keyuse= keyuse;
+ keyuse= save_to->keyuse; /* keyuse is reset to an empty array. */
+ save_to->keyuse= tmp_keyuse;
+
+ for (uint i= 0; i < table_count; i++)
+ {
+ save_to->join_tab_keyuse[i]= join_tab[i].keyuse;
+ join_tab[i].keyuse= NULL;
+ save_to->join_tab_checked_keys[i]= join_tab[i].checked_keys;
+ join_tab[i].checked_keys.clear_all();
+ }
+ }
+ memcpy((uchar*) save_to->best_positions, (uchar*) best_positions,
+ sizeof(POSITION) * (table_count + 1));
+ memset(best_positions, 0, sizeof(POSITION) * (table_count + 1));
+
+ /* Save SJM nests */
+ List_iterator<TABLE_LIST> it(select_lex->sj_nests);
+ TABLE_LIST *tlist;
+ SJ_MATERIALIZATION_INFO **p_info= save_to->sj_mat_info;
+ while ((tlist= it++))
+ {
+ *(p_info++)= tlist->sj_mat_info;
+ }
+}
+
+
+/**
+ Reset a query execution plan so that it can be reoptimized in-place.
+*/
+void JOIN::reset_query_plan()
+{
+ for (uint i= 0; i < table_count; i++)
+ {
+ join_tab[i].keyuse= NULL;
+ join_tab[i].checked_keys.clear_all();
+ }
+}
+
+
+/**
+ Restore a query execution plan previously saved by the caller.
+
+ @param The object from which the current query plan state is restored.
+*/
+
+void JOIN::restore_query_plan(Join_plan_state *restore_from)
+{
+ if (restore_from->keyuse.elements)
+ {
+ DYNAMIC_ARRAY tmp_keyuse;
+ tmp_keyuse= keyuse;
+ keyuse= restore_from->keyuse;
+ restore_from->keyuse= tmp_keyuse;
+
+ for (uint i= 0; i < table_count; i++)
+ {
+ join_tab[i].keyuse= restore_from->join_tab_keyuse[i];
+ join_tab[i].checked_keys= restore_from->join_tab_checked_keys[i];
+ }
+
+ }
+ memcpy((uchar*) best_positions, (uchar*) restore_from->best_positions,
+ sizeof(POSITION) * (table_count + 1));
+ /* Restore SJM nests */
+ List_iterator<TABLE_LIST> it(select_lex->sj_nests);
+ TABLE_LIST *tlist;
+ SJ_MATERIALIZATION_INFO **p_info= restore_from->sj_mat_info;
+ while ((tlist= it++))
+ {
+ tlist->sj_mat_info= *(p_info++);
+ }
+}
+
+
+/**
+ Reoptimize a query plan taking into account an additional conjunct to the
+ WHERE clause.
+
+ @param added_where An extra conjunct to the WHERE clause to reoptimize with
+ @param join_tables The set of tables to reoptimize
+ @param save_to If != NULL, save here the state of the current query plan,
+ otherwise reuse the existing query plan structures.
+
+ @notes
+ Given a query plan that was already optimized taking into account some WHERE
+ clause 'C', reoptimize this plan with a new WHERE clause 'C AND added_where'.
+ The reoptimization works as follows:
+
+ 1. Call update_ref_and_keys *only* for the new conditions 'added_where'
+ that are about to be injected into the query.
+ 2. Expand if necessary the original KEYUSE array JOIN::keyuse to
+ accommodate the new REF accesses computed for the 'added_where' condition.
+ 3. Add the new KEYUSEs into JOIN::keyuse.
+ 4. Re-sort and re-filter the JOIN::keyuse array with the newly added
+ KEYUSE elements.
+
+ @retval REOPT_NEW_PLAN there is a new plan.
+ @retval REOPT_OLD_PLAN no new improved plan was produced, use the old one.
+ @retval REOPT_ERROR an irrecovarable error occured during reoptimization.
+*/
+
+JOIN::enum_reopt_result
+JOIN::reoptimize(Item *added_where, table_map join_tables,
+ Join_plan_state *save_to)
+{
+ DYNAMIC_ARRAY added_keyuse;
+ SARGABLE_PARAM *sargables= 0; /* Used only as a dummy parameter. */
+ uint org_keyuse_elements;
+
+ /* Re-run the REF optimizer to take into account the new conditions. */
+ if (update_ref_and_keys(thd, &added_keyuse, join_tab, table_count, added_where,
+ ~outer_join, select_lex, &sargables))
+ {
+ delete_dynamic(&added_keyuse);
+ return REOPT_ERROR;
+ }
+
+ if (!added_keyuse.elements)
+ {
+ delete_dynamic(&added_keyuse);
+ return REOPT_OLD_PLAN;
+ }
+
+ if (save_to)
+ save_query_plan(save_to);
+ else
+ reset_query_plan();
+
+ if (!keyuse.buffer &&
+ my_init_dynamic_array(&keyuse, sizeof(KEYUSE), 20, 64,
+ MYF(MY_THREAD_SPECIFIC)))
+ {
+ delete_dynamic(&added_keyuse);
+ return REOPT_ERROR;
+ }
+
+ org_keyuse_elements= save_to ? save_to->keyuse.elements : keyuse.elements;
+ allocate_dynamic(&keyuse, org_keyuse_elements + added_keyuse.elements);
+
+ /* If needed, add the access methods from the original query plan. */
+ if (save_to)
+ {
+ DBUG_ASSERT(!keyuse.elements);
+ memcpy(keyuse.buffer,
+ save_to->keyuse.buffer,
+ (size_t) save_to->keyuse.elements * keyuse.size_of_element);
+ keyuse.elements= save_to->keyuse.elements;
+ }
+
+ /* Add the new access methods to the keyuse array. */
+ memcpy(keyuse.buffer + keyuse.elements * keyuse.size_of_element,
+ added_keyuse.buffer,
+ (size_t) added_keyuse.elements * added_keyuse.size_of_element);
+ keyuse.elements+= added_keyuse.elements;
+ /* added_keyuse contents is copied, and it is no longer needed. */
+ delete_dynamic(&added_keyuse);
+
+ if (sort_and_filter_keyuse(thd, &keyuse, true))
+ return REOPT_ERROR;
+ optimize_keyuse(this, &keyuse);
+
+ if (optimize_semijoin_nests(this, join_tables))
+ return REOPT_ERROR;
+
+ /* Re-run the join optimizer to compute a new query plan. */
+ if (choose_plan(this, join_tables))
+ return REOPT_ERROR;
+
+ return REOPT_NEW_PLAN;
+}
+
+
+/**
+ Cache constant expressions in WHERE, HAVING, ON conditions.
+*/
+
+void JOIN::cache_const_exprs()
+{
+ bool cache_flag= FALSE;
+ bool *analyzer_arg= &cache_flag;
+
+ /* No need in cache if all tables are constant. */
+ if (const_tables == table_count)
+ return;
+
+ if (conds)
+ conds->compile(&Item::cache_const_expr_analyzer, (uchar **)&analyzer_arg,
+ &Item::cache_const_expr_transformer, (uchar *)&cache_flag);
+ cache_flag= FALSE;
+ if (having)
+ having->compile(&Item::cache_const_expr_analyzer, (uchar **)&analyzer_arg,
+ &Item::cache_const_expr_transformer, (uchar *)&cache_flag);
+
+ for (JOIN_TAB *tab= first_depth_first_tab(this); tab;
+ tab= next_depth_first_tab(this, tab))
+ {
+ if (*tab->on_expr_ref)
+ {
+ cache_flag= FALSE;
+ (*tab->on_expr_ref)->compile(&Item::cache_const_expr_analyzer,
+ (uchar **)&analyzer_arg,
+ &Item::cache_const_expr_transformer,
+ (uchar *)&cache_flag);
+ }
+ }
+}
+
+
+/**
+ Find a cheaper access key than a given @a key
+
+ @param tab NULL or JOIN_TAB of the accessed table
+ @param order Linked list of ORDER BY arguments
+ @param table Table if tab == NULL or tab->table
+ @param usable_keys Key map to find a cheaper key in
+ @param ref_key
+ * 0 <= key < MAX_KEY - key number (hint) to start the search
+ * -1 - no key number provided
+ @param select_limit LIMIT value
+ @param [out] new_key Key number if success, otherwise undefined
+ @param [out] new_key_direction Return -1 (reverse) or +1 if success,
+ otherwise undefined
+ @param [out] new_select_limit Return adjusted LIMIT
+ @param [out] new_used_key_parts NULL by default, otherwise return number
+ of new_key prefix columns if success
+ or undefined if the function fails
+ @param [out] saved_best_key_parts NULL by default, otherwise preserve the
+ value for further use in QUICK_SELECT_DESC
+
+ @note
+ This function takes into account table->quick_condition_rows statistic
+ (that is calculated by the make_join_statistics function).
+ However, single table procedures such as mysql_update() and mysql_delete()
+ never call make_join_statistics, so they have to update it manually
+ (@see get_index_for_order()).
+*/
+
+static bool
+test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
+ key_map usable_keys, int ref_key,
+ ha_rows select_limit_arg,
+ int *new_key, int *new_key_direction,
+ ha_rows *new_select_limit, uint *new_used_key_parts,
+ uint *saved_best_key_parts)
+{
+ DBUG_ENTER("test_if_cheaper_ordering");
+ /*
+ Check whether there is an index compatible with the given order
+ usage of which is cheaper than usage of the ref_key index (ref_key>=0)
+ or a table scan.
+ It may be the case if ORDER/GROUP BY is used with LIMIT.
+ */
+ ha_rows best_select_limit= HA_POS_ERROR;
+ JOIN *join= tab ? tab->join : NULL;
+ uint nr;
+ key_map keys;
+ uint best_key_parts= 0;
+ int best_key_direction= 0;
+ ha_rows best_records= 0;
+ double read_time;
+ int best_key= -1;
+ bool is_best_covering= FALSE;
+ double fanout= 1;
+ ha_rows table_records= table->stat_records();
+ bool group= join && join->group && order == join->group_list;
+ ha_rows refkey_rows_estimate= table->quick_condition_rows;
+ const bool has_limit= (select_limit_arg != HA_POS_ERROR);
+
+ /*
+ If not used with LIMIT, only use keys if the whole query can be
+ resolved with a key; This is because filesort() is usually faster than
+ retrieving all rows through an index.
+ */
+ if (select_limit_arg >= table_records)
+ {
+ keys= *table->file->keys_to_use_for_scanning();
+ keys.merge(table->covering_keys);
+
+ /*
+ We are adding here also the index specified in FORCE INDEX clause,
+ if any.
+ This is to allow users to use index in ORDER BY.
+ */
+ if (table->force_index)
+ keys.merge(group ? table->keys_in_use_for_group_by :
+ table->keys_in_use_for_order_by);
+ keys.intersect(usable_keys);
+ }
+ else
+ keys= usable_keys;
+
+ if (join)
+ {
+ uint tablenr= tab - join->join_tab;
+ read_time= join->best_positions[tablenr].read_time;
+ for (uint i= tablenr+1; i < join->table_count; i++)
+ fanout*= join->best_positions[i].records_read; // fanout is always >= 1
+ }
+ else
+ read_time= table->file->scan_time();
+
+ /*
+ Calculate the selectivity of the ref_key for REF_ACCESS. For
+ RANGE_ACCESS we use table->quick_condition_rows.
+ */
+ if (ref_key >= 0 && tab->type == JT_REF)
+ {
+ if (table->quick_keys.is_set(ref_key))
+ refkey_rows_estimate= table->quick_rows[ref_key];
+ else
+ {
+ const KEY *ref_keyinfo= table->key_info + ref_key;
+ refkey_rows_estimate= ref_keyinfo->rec_per_key[tab->ref.key_parts - 1];
+ }
+ set_if_bigger(refkey_rows_estimate, 1);
+ }
+
+ for (nr=0; nr < table->s->keys ; nr++)
+ {
+ int direction;
+ ha_rows select_limit= select_limit_arg;
+ uint used_key_parts= 0;
+
+ if (keys.is_set(nr) &&
+ (direction= test_if_order_by_key(order, table, nr, &used_key_parts)))
+ {
+ /*
+ At this point we are sure that ref_key is a non-ordering
+ key (where "ordering key" is a key that will return rows
+ in the order required by ORDER BY).
+ */
+ DBUG_ASSERT (ref_key != (int) nr);
+
+ bool is_covering= (table->covering_keys.is_set(nr) ||
+ (table->file->index_flags(nr, 0, 1) &
+ HA_CLUSTERED_INDEX));
+ /*
+ Don't use an index scan with ORDER BY without limit.
+ For GROUP BY without limit always use index scan
+ if there is a suitable index.
+ Why we hold to this asymmetry hardly can be explained
+ rationally. It's easy to demonstrate that using
+ temporary table + filesort could be cheaper for grouping
+ queries too.
+ */
+ if (is_covering ||
+ select_limit != HA_POS_ERROR ||
+ (ref_key < 0 && (group || table->force_index)))
+ {
+ double rec_per_key;
+ double index_scan_time;
+ KEY *keyinfo= table->key_info+nr;
+ if (select_limit == HA_POS_ERROR)
+ select_limit= table_records;
+ if (group)
+ {
+ /*
+ Used_key_parts can be larger than keyinfo->user_defined_key_parts
+ when using a secondary index clustered with a primary
+ key (e.g. as in Innodb).
+ See Bug #28591 for details.
+ */
+ uint used_index_parts= keyinfo->user_defined_key_parts;
+ uint used_pk_parts= 0;
+ if (used_key_parts > used_index_parts)
+ used_pk_parts= used_key_parts-used_index_parts;
+ rec_per_key= used_key_parts ?
+ keyinfo->actual_rec_per_key(used_key_parts-1) : 1;
+ /* Take into account the selectivity of the used pk prefix */
+ if (used_pk_parts)
+ {
+ KEY *pkinfo=tab->table->key_info+table->s->primary_key;
+ /*
+ If the values of of records per key for the prefixes
+ of the primary key are considered unknown we assume
+ they are equal to 1.
+ */
+ if (used_key_parts == pkinfo->user_defined_key_parts ||
+ pkinfo->rec_per_key[0] == 0)
+ rec_per_key= 1;
+ if (rec_per_key > 1)
+ {
+ rec_per_key*= pkinfo->actual_rec_per_key(used_pk_parts-1);
+ rec_per_key/= pkinfo->actual_rec_per_key(0);
+ /*
+ The value of rec_per_key for the extended key has
+ to be adjusted accordingly if some components of
+ the secondary key are included in the primary key.
+ */
+ for(uint i= 1; i < used_pk_parts; i++)
+ {
+ if (pkinfo->key_part[i].field->key_start.is_set(nr))
+ {
+ /*
+ We presume here that for any index rec_per_key[i] != 0
+ if rec_per_key[0] != 0.
+ */
+ DBUG_ASSERT(pkinfo->actual_rec_per_key(i));
+ rec_per_key*= pkinfo->actual_rec_per_key(i-1);
+ rec_per_key/= pkinfo->actual_rec_per_key(i);
+ }
+ }
+ }
+ }
+ set_if_bigger(rec_per_key, 1);
+ /*
+ With a grouping query each group containing on average
+ rec_per_key records produces only one row that will
+ be included into the result set.
+ */
+ if (select_limit > table_records/rec_per_key)
+ select_limit= table_records;
+ else
+ select_limit= (ha_rows) (select_limit*rec_per_key);
+ } /* group */
+
+ /*
+ If tab=tk is not the last joined table tn then to get first
+ L records from the result set we can expect to retrieve
+ only L/fanout(tk,tn) where fanout(tk,tn) says how many
+ rows in the record set on average will match each row tk.
+ Usually our estimates for fanouts are too pessimistic.
+ So the estimate for L/fanout(tk,tn) will be too optimistic
+ and as result we'll choose an index scan when using ref/range
+ access + filesort will be cheaper.
+ */
+ select_limit= (ha_rows) (select_limit < fanout ?
+ 1 : select_limit/fanout);
+ /*
+ We assume that each of the tested indexes is not correlated
+ with ref_key. Thus, to select first N records we have to scan
+ N/selectivity(ref_key) index entries.
+ selectivity(ref_key) = #scanned_records/#table_records =
+ refkey_rows_estimate/table_records.
+ In any case we can't select more than #table_records.
+ N/(refkey_rows_estimate/table_records) > table_records
+ <=> N > refkey_rows_estimate.
+ */
+ if (select_limit > refkey_rows_estimate)
+ select_limit= table_records;
+ else
+ select_limit= (ha_rows) (select_limit *
+ (double) table_records /
+ refkey_rows_estimate);
+ rec_per_key= keyinfo->actual_rec_per_key(keyinfo->user_defined_key_parts-1);
+ set_if_bigger(rec_per_key, 1);
+ /*
+ Here we take into account the fact that rows are
+ accessed in sequences rec_per_key records in each.
+ Rows in such a sequence are supposed to be ordered
+ by rowid/primary key. When reading the data
+ in a sequence we'll touch not more pages than the
+ table file contains.
+ TODO. Use the formula for a disk sweep sequential access
+ to calculate the cost of accessing data rows for one
+ index entry.
+ */
+ index_scan_time= select_limit/rec_per_key *
+ MY_MIN(rec_per_key, table->file->scan_time());
+ if ((ref_key < 0 && (group || table->force_index || is_covering)) ||
+ index_scan_time < read_time)
+ {
+ ha_rows quick_records= table_records;
+ ha_rows refkey_select_limit= (ref_key >= 0 &&
+ table->covering_keys.is_set(ref_key)) ?
+ refkey_rows_estimate :
+ HA_POS_ERROR;
+ if ((is_best_covering && !is_covering) ||
+ (is_covering && refkey_select_limit < select_limit))
+ continue;
+ if (table->quick_keys.is_set(nr))
+ quick_records= table->quick_rows[nr];
+ if (best_key < 0 ||
+ (select_limit <= MY_MIN(quick_records,best_records) ?
+ keyinfo->user_defined_key_parts < best_key_parts :
+ quick_records < best_records) ||
+ (!is_best_covering && is_covering))
+ {
+ best_key= nr;
+ best_key_parts= keyinfo->user_defined_key_parts;
+ if (saved_best_key_parts)
+ *saved_best_key_parts= used_key_parts;
+ best_records= quick_records;
+ is_best_covering= is_covering;
+ best_key_direction= direction;
+ best_select_limit= select_limit;
+ }
+ }
+ }
+ }
+ }
+
+ if (best_key < 0 || best_key == ref_key)
+ DBUG_RETURN(FALSE);
+
+ *new_key= best_key;
+ *new_key_direction= best_key_direction;
+ *new_select_limit= has_limit ? best_select_limit : table_records;
+ if (new_used_key_parts != NULL)
+ *new_used_key_parts= best_key_parts;
+
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Find a key to apply single table UPDATE/DELETE by a given ORDER
+
+ @param order Linked list of ORDER BY arguments
+ @param table Table to find a key
+ @param select Pointer to access/update select->quick (if any)
+ @param limit LIMIT clause parameter
+ @param [out] scanned_limit How many records we expect to scan
+ Valid if *need_sort=FALSE.
+ @param [out] need_sort TRUE if filesort needed
+ @param [out] reverse
+ TRUE if the key is reversed again given ORDER (undefined if key == MAX_KEY)
+
+ @return
+ - MAX_KEY if no key found (need_sort == TRUE)
+ - MAX_KEY if quick select result order is OK (need_sort == FALSE)
+ - key number (either index scan or quick select) (need_sort == FALSE)
+
+ @note
+ Side effects:
+ - may deallocate or deallocate and replace select->quick;
+ - may set table->quick_condition_rows and table->quick_rows[...]
+ to table->file->stats.records.
+*/
+
+uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select,
+ ha_rows limit, ha_rows *scanned_limit,
+ bool *need_sort, bool *reverse)
+{
+ if (!order)
+ {
+ *need_sort= FALSE;
+ if (select && select->quick)
+ return select->quick->index; // index or MAX_KEY, use quick select as is
+ else
+ return table->file->key_used_on_scan; // MAX_KEY or index for some engines
+ }
+
+ if (!is_simple_order(order)) // just to cut further expensive checks
+ {
+ *need_sort= TRUE;
+ return MAX_KEY;
+ }
+
+ if (select && select->quick)
+ {
+ if (select->quick->index == MAX_KEY)
+ {
+ *need_sort= TRUE;
+ return MAX_KEY;
+ }
+
+ uint used_key_parts;
+ switch (test_if_order_by_key(order, table, select->quick->index,
+ &used_key_parts)) {
+ case 1: // desired order
+ *need_sort= FALSE;
+ *scanned_limit= MY_MIN(limit, select->quick->records);
+ return select->quick->index;
+ case 0: // unacceptable order
+ *need_sort= TRUE;
+ return MAX_KEY;
+ case -1: // desired order, but opposite direction
+ {
+ QUICK_SELECT_I *reverse_quick;
+ if ((reverse_quick=
+ select->quick->make_reverse(used_key_parts)))
+ {
+ select->set_quick(reverse_quick);
+ *need_sort= FALSE;
+ *scanned_limit= MY_MIN(limit, select->quick->records);
+ return select->quick->index;
+ }
+ else
+ {
+ *need_sort= TRUE;
+ return MAX_KEY;
+ }
+ }
+ }
+ DBUG_ASSERT(0);
+ }
+ else if (limit != HA_POS_ERROR)
+ { // check if some index scan & LIMIT is more efficient than filesort
+
+ /*
+ Update quick_condition_rows since single table UPDATE/DELETE procedures
+ don't call make_join_statistics() and leave this variable uninitialized.
+ */
+ table->quick_condition_rows= table->stat_records();
+
+ int key, direction;
+ if (test_if_cheaper_ordering(NULL, order, table,
+ table->keys_in_use_for_order_by, -1,
+ limit,
+ &key, &direction, &limit) &&
+ !is_key_used(table, key, table->write_set))
+ {
+ *need_sort= FALSE;
+ *scanned_limit= limit;
+ *reverse= (direction < 0);
+ return key;
+ }
+ }
+ *need_sort= TRUE;
+ return MAX_KEY;
+}
+
+/*
+ Count how much times conditions are true for several first rows of the table
+
+ @param thd thread handle
+ @param rows_to_read how much rows to check
+ @param table table which should be checked
+ @conds conds list of conditions and countars for them
+
+ @return number of really checked rows or 0 in case of error or empty table
+*/
+
+ulong check_selectivity(THD *thd,
+ ulong rows_to_read,
+ TABLE *table,
+ List<COND_STATISTIC> *conds)
+{
+ ulong count= 0;
+ COND_STATISTIC *cond;
+ List_iterator_fast<COND_STATISTIC> it(*conds);
+ handler *file= table->file;
+ uchar *record= table->record[0];
+ int error= 0;
+ DBUG_ENTER("check_selectivity");
+
+ DBUG_ASSERT(rows_to_read > 0);
+ while ((cond= it++))
+ {
+ DBUG_ASSERT(cond->cond);
+ DBUG_ASSERT(cond->cond->used_tables() == table->map);
+ cond->positive= 0;
+ }
+ it.rewind();
+
+ if (file->ha_rnd_init_with_error(1))
+ DBUG_RETURN(0);
+ do
+ {
+ error= file->ha_rnd_next(record);
+
+ if (thd->killed)
+ {
+ thd->send_kill_message();
+ count= 0;
+ goto err;
+ }
+ if (error)
+ {
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ if (error == HA_ERR_END_OF_FILE)
+ break;
+ goto err;
+ }
+
+ count++;
+ while ((cond= it++))
+ {
+ if (cond->cond->val_bool())
+ cond->positive++;
+ }
+ it.rewind();
+
+ } while (count < rows_to_read);
+
+ file->ha_rnd_end();
+ DBUG_RETURN(count);
+
+err:
+ DBUG_PRINT("error", ("error %d", error));
+ file->ha_rnd_end();
+ DBUG_RETURN(0);
+}
+
+/**
+ @} (end of group Query_Optimizer)
+*/
diff --git a/sql/sql_select.h b/sql/sql_select.h
new file mode 100644
index 00000000000..5aa29715dc3
--- /dev/null
+++ b/sql/sql_select.h
@@ -0,0 +1,1909 @@
+#ifndef SQL_SELECT_INCLUDED
+#define SQL_SELECT_INCLUDED
+
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/**
+ @file
+
+ @brief
+ classes to use when handling where clause
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "procedure.h"
+#include "sql_array.h" /* Array */
+#include "records.h" /* READ_RECORD */
+#include "opt_range.h" /* SQL_SELECT, QUICK_SELECT_I */
+
+
+/* Values in optimize */
+#define KEY_OPTIMIZE_EXISTS 1
+#define KEY_OPTIMIZE_REF_OR_NULL 2
+#define KEY_OPTIMIZE_EQ 4
+
+inline uint get_hash_join_key_no() { return MAX_KEY; }
+
+inline bool is_hash_join_key_no(uint key) { return key == MAX_KEY; }
+
+typedef struct keyuse_t {
+ TABLE *table;
+ Item *val; /**< or value if no field */
+ table_map used_tables;
+ uint key, keypart, optimize;
+ key_part_map keypart_map;
+ ha_rows ref_table_rows;
+ /**
+ If true, the comparison this value was created from will not be
+ satisfied if val has NULL 'value'.
+ */
+ bool null_rejecting;
+ /*
+ !NULL - This KEYUSE was created from an equality that was wrapped into
+ an Item_func_trig_cond. This means the equality (and validity of
+ this KEYUSE element) can be turned on and off. The on/off state
+ is indicted by the pointed value:
+ *cond_guard == TRUE <=> equality condition is on
+ *cond_guard == FALSE <=> equality condition is off
+
+ NULL - Otherwise (the source equality can't be turned off)
+ */
+ bool *cond_guard;
+ /*
+ 0..64 <=> This was created from semi-join IN-equality # sj_pred_no.
+ MAX_UINT Otherwise
+ */
+ uint sj_pred_no;
+
+ bool is_for_hash_join() { return is_hash_join_key_no(key); }
+} KEYUSE;
+
+#define NO_KEYPART ((uint)(-1))
+
+class store_key;
+
+const int NO_REF_PART= uint(-1);
+
+typedef struct st_table_ref
+{
+ bool key_err;
+ /** True if something was read into buffer in join_read_key. */
+ bool has_record;
+ uint key_parts; ///< num of ...
+ uint key_length; ///< length of key_buff
+ int key; ///< key no
+ uchar *key_buff; ///< value to look for with key
+ uchar *key_buff2; ///< key_buff+key_length
+ store_key **key_copy; //
+
+ /*
+ Bitmap of key parts which refer to constants. key_copy only has copiers for
+ non-const key parts.
+ */
+ key_part_map const_ref_part_map;
+
+ Item **items; ///< val()'s for each keypart
+ /*
+ Array of pointers to trigger variables. Some/all of the pointers may be
+ NULL. The ref access can be used iff
+
+ for each used key part i, (!cond_guards[i] || *cond_guards[i])
+
+ This array is used by subquery code. The subquery code may inject
+ triggered conditions, i.e. conditions that can be 'switched off'. A ref
+ access created from such condition is not valid when at least one of the
+ underlying conditions is switched off (see subquery code for more details)
+ */
+ bool **cond_guards;
+ /**
+ (null_rejecting & (1<<i)) means the condition is '=' and no matching
+ rows will be produced if items[i] IS NULL (see add_not_null_conds())
+ */
+ key_part_map null_rejecting;
+ table_map depend_map; ///< Table depends on these tables.
+
+ /* null byte position in the key_buf. Used for REF_OR_NULL optimization */
+ uchar *null_ref_key;
+ /*
+ ref_or_null optimization: number of key part that alternates between
+ the lookup value or NULL (there's only one such part).
+ If we're not using ref_or_null, the value is NO_REF_PART
+ */
+ uint null_ref_part;
+
+ /*
+ The number of times the record associated with this key was used
+ in the join.
+ */
+ ha_rows use_count;
+
+ /*
+ TRUE <=> disable the "cache" as doing lookup with the same key value may
+ produce different results (because of Index Condition Pushdown)
+
+ */
+ bool disable_cache;
+
+ bool tmp_table_index_lookup_init(THD *thd, KEY *tmp_key, Item_iterator &it,
+ bool value, uint skip= 0);
+ bool is_access_triggered();
+} TABLE_REF;
+
+
+/*
+ The structs which holds the join connections and join states
+*/
+enum join_type { JT_UNKNOWN,JT_SYSTEM,JT_CONST,JT_EQ_REF,JT_REF,JT_MAYBE_REF,
+ JT_ALL, JT_RANGE, JT_NEXT, JT_FT, JT_REF_OR_NULL,
+ JT_UNIQUE_SUBQUERY, JT_INDEX_SUBQUERY, JT_INDEX_MERGE,
+ JT_HASH, JT_HASH_RANGE, JT_HASH_NEXT, JT_HASH_INDEX_MERGE};
+
+class JOIN;
+
+enum enum_nested_loop_state
+{
+ NESTED_LOOP_KILLED= -2, NESTED_LOOP_ERROR= -1,
+ NESTED_LOOP_OK= 0, NESTED_LOOP_NO_MORE_ROWS= 1,
+ NESTED_LOOP_QUERY_LIMIT= 3, NESTED_LOOP_CURSOR_LIMIT= 4
+};
+
+
+/* Possible sj_strategy values */
+enum sj_strategy_enum
+{
+ SJ_OPT_NONE=0,
+ SJ_OPT_DUPS_WEEDOUT=1,
+ SJ_OPT_LOOSE_SCAN =2,
+ SJ_OPT_FIRST_MATCH =3,
+ SJ_OPT_MATERIALIZE =4,
+ SJ_OPT_MATERIALIZE_SCAN=5
+};
+
+/* Values for JOIN_TAB::packed_info */
+#define TAB_INFO_HAVE_VALUE 1
+#define TAB_INFO_USING_INDEX 2
+#define TAB_INFO_USING_WHERE 4
+#define TAB_INFO_FULL_SCAN_ON_NULL 8
+
+typedef enum_nested_loop_state
+(*Next_select_func)(JOIN *, struct st_join_table *, bool);
+Next_select_func setup_end_select_func(JOIN *join);
+int rr_sequential(READ_RECORD *info);
+int rr_sequential_and_unpack(READ_RECORD *info);
+
+
+#include "sql_explain.h"
+
+/**************************************************************************************
+ * New EXPLAIN structures END
+ *************************************************************************************/
+
+class JOIN_CACHE;
+class SJ_TMP_TABLE;
+class JOIN_TAB_RANGE;
+
+typedef struct st_join_table {
+ st_join_table() {} /* Remove gcc warning */
+ TABLE *table;
+ KEYUSE *keyuse; /**< pointer to first used key */
+ KEY *hj_key; /**< descriptor of the used best hash join key
+ not supported by any index */
+ SQL_SELECT *select;
+ COND *select_cond;
+ COND *on_precond; /**< part of on condition to check before
+ accessing the first inner table */
+ QUICK_SELECT_I *quick;
+ /*
+ The value of select_cond before we've attempted to do Index Condition
+ Pushdown. We may need to restore everything back if we first choose one
+ index but then reconsider (see test_if_skip_sort_order() for such
+ scenarios).
+ NULL means no index condition pushdown was performed.
+ */
+ Item *pre_idx_push_select_cond;
+ /*
+ Pointer to the associated ON expression. on_expr_ref=!NULL except for
+ degenerate joins.
+ *on_expr_ref!=NULL for tables that are first inner tables within an outer
+ join.
+ */
+ Item **on_expr_ref;
+ COND_EQUAL *cond_equal; /**< multiple equalities for the on expression */
+ st_join_table *first_inner; /**< first inner table for including outerjoin */
+ bool found; /**< true after all matches or null complement */
+ bool not_null_compl;/**< true before null complement is added */
+ st_join_table *last_inner; /**< last table table for embedding outer join */
+ st_join_table *first_upper; /**< first inner table for embedding outer join */
+ st_join_table *first_unmatched; /**< used for optimization purposes only */
+
+ /*
+ For join tabs that are inside an SJM bush: root of the bush
+ */
+ st_join_table *bush_root_tab;
+
+ /* TRUE <=> This join_tab is inside an SJM bush and is the last leaf tab here */
+ bool last_leaf_in_bush;
+
+ /*
+ ptr - this is a bush, and ptr points to description of child join_tab
+ range
+ NULL - this join tab has no bush children
+ */
+ JOIN_TAB_RANGE *bush_children;
+
+ /* Special content for EXPLAIN 'Extra' column or NULL if none */
+ enum explain_extra_tag info;
+
+ /*
+ Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra'
+ column, or 0 if there is no info.
+ */
+ uint packed_info;
+
+ READ_RECORD::Setup_func read_first_record;
+ Next_select_func next_select;
+ READ_RECORD read_record;
+ /*
+ Currently the following two fields are used only for a [NOT] IN subquery
+ if it is executed by an alternative full table scan when the left operand of
+ the subquery predicate is evaluated to NULL.
+ */
+ READ_RECORD::Setup_func save_read_first_record;/* to save read_first_record */
+ READ_RECORD::Read_func save_read_record;/* to save read_record.read_record */
+ double worst_seeks;
+ key_map const_keys; /**< Keys with constant part */
+ key_map checked_keys; /**< Keys checked in find_best */
+ key_map needed_reg;
+ key_map keys; /**< all keys with can be used */
+
+ /* Either #rows in the table or 1 for const table. */
+ ha_rows records;
+ /*
+ Number of records that will be scanned (yes scanned, not returned) by the
+ best 'independent' access method, i.e. table scan or QUICK_*_SELECT)
+ */
+ ha_rows found_records;
+ /*
+ Cost of accessing the table using "ALL" or range/index_merge access
+ method (but not 'index' for some reason), i.e. this matches method which
+ E(#records) is in found_records.
+ */
+ double read_time;
+
+ /* Copy of POSITION::records_read, set by get_best_combination() */
+ double records_read;
+
+ /* The selectivity of the conditions that can be pushed to the table */
+ double cond_selectivity;
+
+ /* Startup cost for execution */
+ double startup_cost;
+
+ double partial_join_cardinality;
+
+ table_map dependent,key_dependent;
+ /*
+ 1 - use quick select
+ 2 - use "Range checked for each record"
+ */
+ uint use_quick;
+ /*
+ Index to use. Note: this is valid only for 'index' access, but not range or
+ ref access.
+ */
+ uint index;
+ uint status; ///< Save status for cache
+ uint used_fields;
+ ulong used_fieldlength;
+ ulong max_used_fieldlength;
+ uint used_blobs;
+ uint used_null_fields;
+ uint used_uneven_bit_fields;
+ enum join_type type;
+ bool cached_eq_ref_table,eq_ref_table;
+ bool shortcut_for_distinct;
+ bool sorted;
+ /*
+ If it's not 0 the number stored this field indicates that the index
+ scan has been chosen to access the table data and we expect to scan
+ this number of rows for the table.
+ */
+ ha_rows limit;
+ TABLE_REF ref;
+ /* TRUE <=> condition pushdown supports other tables presence */
+ bool icp_other_tables_ok;
+ /*
+ TRUE <=> condition pushed to the index has to be factored out of
+ the condition pushed to the table
+ */
+ bool idx_cond_fact_out;
+ bool use_join_cache;
+ uint used_join_cache_level;
+ ulong join_buffer_size_limit;
+ JOIN_CACHE *cache;
+ /*
+ Index condition for BKA access join
+ */
+ Item *cache_idx_cond;
+ SQL_SELECT *cache_select;
+ JOIN *join;
+ /*
+ Embedding SJ-nest (may be not the direct parent), or NULL if none.
+ This variable holds the result of table pullout.
+ */
+ TABLE_LIST *emb_sj_nest;
+
+ /* FirstMatch variables (final QEP) */
+ struct st_join_table *first_sj_inner_tab;
+ struct st_join_table *last_sj_inner_tab;
+
+ /* Variables for semi-join duplicate elimination */
+ SJ_TMP_TABLE *flush_weedout_table;
+ SJ_TMP_TABLE *check_weed_out_table;
+ /* for EXPLAIN only: */
+ SJ_TMP_TABLE *first_weedout_table;
+
+ /*
+ If set, means we should stop join enumeration after we've got the first
+ match and return to the specified join tab. May point to
+ join->join_tab[-1] which means stop join execution after the first
+ match.
+ */
+ struct st_join_table *do_firstmatch;
+
+ /*
+ ptr - We're doing a LooseScan, this join tab is the first (i.e.
+ "driving") join tab), and ptr points to the last join tab
+ handled by the strategy. loosescan_match_tab->found_match
+ should be checked to see if the current value group had a match.
+ NULL - Not doing a loose scan on this join tab.
+ */
+ struct st_join_table *loosescan_match_tab;
+
+ /* TRUE <=> we are inside LooseScan range */
+ bool inside_loosescan_range;
+
+ /* Buffer to save index tuple to be able to skip duplicates */
+ uchar *loosescan_buf;
+
+ /*
+ Index used by LooseScan (we store it here separately because ref access
+ stores it in tab->ref.key, while range scan stores it in tab->index, etc)
+ */
+ uint loosescan_key;
+
+ /* Length of key tuple (depends on #keyparts used) to store in the above */
+ uint loosescan_key_len;
+
+ /* Used by LooseScan. TRUE<=> there has been a matching record combination */
+ bool found_match;
+
+ /*
+ Used by DuplicateElimination. tab->table->ref must have the rowid
+ whenever we have a current record.
+ */
+ int keep_current_rowid;
+
+ /* NestedOuterJoins: Bitmap of nested joins this table is part of */
+ nested_join_map embedding_map;
+
+ /*
+ Semi-join strategy to be used for this join table. This is a copy of
+ POSITION::sj_strategy field. This field is set up by the
+ fix_semijoin_strategies_for_picked_join_order.
+ */
+ enum sj_strategy_enum sj_strategy;
+
+ uint n_sj_tables;
+
+ bool preread_init_done;
+
+ void cleanup();
+ inline bool is_using_loose_index_scan()
+ {
+ return (select && select->quick &&
+ (select->quick->get_type() ==
+ QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX));
+ }
+ bool is_using_agg_loose_index_scan ()
+ {
+ return (is_using_loose_index_scan() &&
+ ((QUICK_GROUP_MIN_MAX_SELECT *)select->quick)->is_agg_distinct());
+ }
+ bool is_inner_table_of_semi_join_with_first_match()
+ {
+ return first_sj_inner_tab != NULL;
+ }
+ bool is_inner_table_of_semijoin()
+ {
+ return emb_sj_nest != NULL;
+ }
+ bool is_inner_table_of_outer_join()
+ {
+ return first_inner != NULL;
+ }
+ bool is_single_inner_of_semi_join_with_first_match()
+ {
+ return first_sj_inner_tab == this && last_sj_inner_tab == this;
+ }
+ bool is_single_inner_of_outer_join()
+ {
+ return first_inner == this && first_inner->last_inner == this;
+ }
+ bool is_first_inner_for_outer_join()
+ {
+ return first_inner && first_inner == this;
+ }
+ bool use_match_flag()
+ {
+ return is_first_inner_for_outer_join() || first_sj_inner_tab == this ;
+ }
+ bool check_only_first_match()
+ {
+ return is_inner_table_of_semi_join_with_first_match() ||
+ (is_inner_table_of_outer_join() &&
+ table->reginfo.not_exists_optimize);
+ }
+ bool is_last_inner_table()
+ {
+ return (first_inner && first_inner->last_inner == this) ||
+ last_sj_inner_tab == this;
+ }
+ /*
+ Check whether the table belongs to a nest of inner tables of an
+ outer join or to a nest of inner tables of a semi-join
+ */
+ bool is_nested_inner()
+ {
+ if (first_inner &&
+ (first_inner != first_inner->last_inner || first_inner->first_upper))
+ return TRUE;
+ if (first_sj_inner_tab && first_sj_inner_tab != last_sj_inner_tab)
+ return TRUE;
+ return FALSE;
+ }
+ struct st_join_table *get_first_inner_table()
+ {
+ if (first_inner)
+ return first_inner;
+ return first_sj_inner_tab;
+ }
+ void set_select_cond(COND *to, uint line)
+ {
+ DBUG_PRINT("info", ("select_cond changes %p -> %p at line %u tab %p",
+ select_cond, to, line, this));
+ select_cond= to;
+ }
+ COND *set_cond(COND *new_cond)
+ {
+ COND *tmp_select_cond= select_cond;
+ set_select_cond(new_cond, __LINE__);
+ if (select)
+ select->cond= new_cond;
+ return tmp_select_cond;
+ }
+ void calc_used_field_length(bool max_fl);
+ ulong get_used_fieldlength()
+ {
+ if (!used_fieldlength)
+ calc_used_field_length(FALSE);
+ return used_fieldlength;
+ }
+ ulong get_max_used_fieldlength()
+ {
+ if (!max_used_fieldlength)
+ calc_used_field_length(TRUE);
+ return max_used_fieldlength;
+ }
+ double get_partial_join_cardinality() { return partial_join_cardinality; }
+ bool hash_join_is_possible();
+ int make_scan_filter();
+ bool is_ref_for_hash_join() { return is_hash_join_key_no(ref.key); }
+ KEY *get_keyinfo_by_key_no(uint key)
+ {
+ return (is_hash_join_key_no(key) ? hj_key : table->key_info+key);
+ }
+ double scan_time();
+ ha_rows get_examined_rows();
+ bool preread_init();
+
+ bool is_sjm_nest() { return MY_TEST(bush_children); }
+
+ bool access_from_tables_is_allowed(table_map used_tables,
+ table_map sjm_lookup_tables)
+ {
+ table_map used_sjm_lookup_tables= used_tables & sjm_lookup_tables;
+ return !used_sjm_lookup_tables ||
+ (emb_sj_nest &&
+ !(used_sjm_lookup_tables & ~emb_sj_nest->sj_inner_tables));
+ }
+
+ void remove_redundant_bnl_scan_conds();
+} JOIN_TAB;
+
+
+#include "sql_join_cache.h"
+
+enum_nested_loop_state sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool
+ end_of_records);
+enum_nested_loop_state sub_select(JOIN *join,JOIN_TAB *join_tab, bool
+ end_of_records);
+enum_nested_loop_state
+end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records);
+enum_nested_loop_state
+end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
+ bool end_of_records);
+
+
+struct st_position;
+
+class Semi_join_strategy_picker
+{
+public:
+ /* Called when starting to build a new join prefix */
+ virtual void set_empty() = 0;
+
+ /*
+ Update internal state after another table has been added to the join
+ prefix
+ */
+ virtual void set_from_prev(struct st_position *prev) = 0;
+
+ virtual bool check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ struct st_position *loose_scan_pos) = 0;
+
+ virtual void mark_used() = 0;
+
+ virtual ~Semi_join_strategy_picker() {}
+};
+
+
+/*
+ Duplicate Weedout strategy optimization state
+*/
+
+class Duplicate_weedout_picker : public Semi_join_strategy_picker
+{
+ /* The first table that the strategy will need to handle */
+ uint first_dupsweedout_table;
+
+ /*
+ Tables that we will need to have in the prefix to do the weedout step
+ (all inner and all outer that the involved semi-joins are correlated with)
+ */
+ table_map dupsweedout_tables;
+
+ bool is_used;
+public:
+ void set_empty()
+ {
+ dupsweedout_tables= 0;
+ first_dupsweedout_table= MAX_TABLES;
+ is_used= FALSE;
+ }
+ void set_from_prev(struct st_position *prev);
+
+ bool check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *stratey,
+ struct st_position *loose_scan_pos);
+
+ void mark_used() { is_used= TRUE; }
+ friend void fix_semijoin_strategies_for_picked_join_order(JOIN *join);
+};
+
+
+class Firstmatch_picker : public Semi_join_strategy_picker
+{
+ /*
+ Index of the first inner table that we intend to handle with this
+ strategy
+ */
+ uint first_firstmatch_table;
+ /*
+ Tables that were not in the join prefix when we've started considering
+ FirstMatch strategy.
+ */
+ table_map first_firstmatch_rtbl;
+ /*
+ Tables that need to be in the prefix before we can calculate the cost
+ of using FirstMatch strategy.
+ */
+ table_map firstmatch_need_tables;
+
+ bool is_used;
+
+ bool in_firstmatch_prefix() { return (first_firstmatch_table != MAX_TABLES); }
+ void invalidate_firstmatch_prefix() { first_firstmatch_table= MAX_TABLES; }
+public:
+ void set_empty()
+ {
+ invalidate_firstmatch_prefix();
+ is_used= FALSE;
+ }
+
+ void set_from_prev(struct st_position *prev);
+ bool check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ struct st_position *loose_scan_pos);
+
+ void mark_used() { is_used= TRUE; }
+ friend void fix_semijoin_strategies_for_picked_join_order(JOIN *join);
+};
+
+
+class LooseScan_picker : public Semi_join_strategy_picker
+{
+ /* The first (i.e. driving) table we're doing loose scan for */
+ uint first_loosescan_table;
+ /*
+ Tables that need to be in the prefix before we can calculate the cost
+ of using LooseScan strategy.
+ */
+ table_map loosescan_need_tables;
+
+ /*
+ keyno - Planning to do LooseScan on this key. If keyuse is NULL then
+ this is a full index scan, otherwise this is a ref+loosescan
+ scan (and keyno matches the KEUSE's)
+ MAX_KEY - Not doing a LooseScan
+ */
+ uint loosescan_key; // final (one for strategy instance )
+ uint loosescan_parts; /* Number of keyparts to be kept distinct */
+
+ bool is_used;
+public:
+ void set_empty()
+ {
+ first_loosescan_table= MAX_TABLES;
+ is_used= FALSE;
+ }
+
+ void set_from_prev(struct st_position *prev);
+ bool check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ struct st_position *loose_scan_pos);
+ void mark_used() { is_used= TRUE; }
+
+ friend class Loose_scan_opt;
+ friend void best_access_path(JOIN *join,
+ JOIN_TAB *s,
+ table_map remaining_tables,
+ uint idx,
+ bool disable_jbuf,
+ double record_count,
+ struct st_position *pos,
+ struct st_position *loose_scan_pos);
+ friend bool get_best_combination(JOIN *join);
+ friend int setup_semijoin_dups_elimination(JOIN *join, ulonglong options,
+ uint no_jbuf_after);
+ friend void fix_semijoin_strategies_for_picked_join_order(JOIN *join);
+};
+
+
+class Sj_materialization_picker : public Semi_join_strategy_picker
+{
+ bool is_used;
+
+ /* The last inner table (valid once we're after it) */
+ uint sjm_scan_last_inner;
+ /*
+ Tables that we need to have in the prefix to calculate the correct cost.
+ Basically, we need all inner tables and outer tables mentioned in the
+ semi-join's ON expression so we can correctly account for fanout.
+ */
+ table_map sjm_scan_need_tables;
+
+public:
+ void set_empty()
+ {
+ sjm_scan_need_tables= 0;
+ LINT_INIT(sjm_scan_last_inner);
+ is_used= FALSE;
+ }
+ void set_from_prev(struct st_position *prev);
+ bool check_qep(JOIN *join,
+ uint idx,
+ table_map remaining_tables,
+ const JOIN_TAB *new_join_tab,
+ double *record_count,
+ double *read_time,
+ table_map *handled_fanout,
+ sj_strategy_enum *strategy,
+ struct st_position *loose_scan_pos);
+ void mark_used() { is_used= TRUE; }
+
+ friend void fix_semijoin_strategies_for_picked_join_order(JOIN *join);
+};
+
+
+/**
+ Information about a position of table within a join order. Used in join
+ optimization.
+*/
+typedef struct st_position :public Sql_alloc
+{
+ /* The table that's put into join order */
+ JOIN_TAB *table;
+
+ /*
+ The "fanout": number of output rows that will be produced (after
+ pushed down selection condition is applied) per each row combination of
+ previous tables.
+ */
+ double records_read;
+
+ /* The selectivity of the pushed down conditions */
+ double cond_selectivity;
+
+ /*
+ Cost accessing the table in course of the entire complete join execution,
+ i.e. cost of one access method use (e.g. 'range' or 'ref' scan ) times
+ number the access method will be invoked.
+ */
+ double read_time;
+
+ /* Cumulative cost and record count for the join prefix */
+ Cost_estimate prefix_cost;
+ double prefix_record_count;
+
+ /*
+ NULL - 'index' or 'range' or 'index_merge' or 'ALL' access is used.
+ Other - [eq_]ref[_or_null] access is used. Pointer to {t.keypart1 = expr}
+ */
+ KEYUSE *key;
+
+ /* If ref-based access is used: bitmap of tables this table depends on */
+ table_map ref_depend_map;
+
+ /*
+ TRUE <=> join buffering will be used. At the moment this is based on
+ *very* imprecise guesses made in best_access_path().
+ */
+ bool use_join_buffer;
+
+ /*
+ Current optimization state: Semi-join strategy to be used for this
+ and preceding join tables.
+
+ Join optimizer sets this for the *last* join_tab in the
+ duplicate-generating range. That is, in order to interpret this field,
+ one needs to traverse join->[best_]positions array from right to left.
+ When you see a join table with sj_strategy!= SJ_OPT_NONE, some other
+ field (depending on the strategy) tells how many preceding positions
+ this applies to. The values of covered_preceding_positions->sj_strategy
+ must be ignored.
+ */
+ enum sj_strategy_enum sj_strategy;
+
+ /*
+ Valid only after fix_semijoin_strategies_for_picked_join_order() call:
+ if sj_strategy!=SJ_OPT_NONE, this is the number of subsequent tables that
+ are covered by the specified semi-join strategy
+ */
+ uint n_sj_tables;
+
+ /*
+ Bitmap of semi-join inner tables that are in the join prefix and for
+ which there's no provision for how to eliminate semi-join duplicates
+ they produce.
+ */
+ table_map dups_producing_tables;
+
+ table_map inner_tables_handled_with_other_sjs;
+
+ Duplicate_weedout_picker dups_weedout_picker;
+ Firstmatch_picker firstmatch_picker;
+ LooseScan_picker loosescan_picker;
+ Sj_materialization_picker sjmat_picker;
+} POSITION;
+
+typedef struct st_rollup
+{
+ enum State { STATE_NONE, STATE_INITED, STATE_READY };
+ State state;
+ Item_null_result **null_items;
+ Item ***ref_pointer_arrays;
+ List<Item> *fields;
+} ROLLUP;
+
+
+class JOIN_TAB_RANGE: public Sql_alloc
+{
+public:
+ JOIN_TAB *start;
+ JOIN_TAB *end;
+};
+
+
+class JOIN :public Sql_alloc
+{
+private:
+ JOIN(const JOIN &rhs); /**< not implemented */
+ JOIN& operator=(const JOIN &rhs); /**< not implemented */
+
+protected:
+
+ /**
+ The subset of the state of a JOIN that represents an optimized query
+ execution plan. Allows saving/restoring different JOIN plans for the same
+ query.
+ */
+ class Join_plan_state {
+ public:
+ DYNAMIC_ARRAY keyuse; /* Copy of the JOIN::keyuse array. */
+ POSITION *best_positions; /* Copy of JOIN::best_positions */
+ /* Copies of the JOIN_TAB::keyuse pointers for each JOIN_TAB. */
+ KEYUSE **join_tab_keyuse;
+ /* Copies of JOIN_TAB::checked_keys for each JOIN_TAB. */
+ key_map *join_tab_checked_keys;
+ SJ_MATERIALIZATION_INFO **sj_mat_info;
+ my_bool error;
+ public:
+ Join_plan_state(uint tables) : error(0)
+ {
+ keyuse.elements= 0;
+ keyuse.buffer= NULL;
+ best_positions= 0; /* To detect errors */
+ error= my_multi_malloc(MYF(MY_WME),
+ &best_positions,
+ sizeof(*best_positions) * (tables + 1),
+ &join_tab_keyuse,
+ sizeof(*join_tab_keyuse) * tables,
+ &join_tab_checked_keys,
+ sizeof(*join_tab_checked_keys) * tables,
+ &sj_mat_info,
+ sizeof(sj_mat_info) * tables,
+ NullS) == 0;
+ }
+ Join_plan_state(JOIN *join);
+ ~Join_plan_state()
+ {
+ delete_dynamic(&keyuse);
+ my_free(best_positions);
+ }
+ };
+
+ /* Results of reoptimizing a JOIN via JOIN::reoptimize(). */
+ enum enum_reopt_result {
+ REOPT_NEW_PLAN, /* there is a new reoptimized plan */
+ REOPT_OLD_PLAN, /* no new improved plan can be found, use the old one */
+ REOPT_ERROR, /* an irrecovarable error occured during reoptimization */
+ REOPT_NONE /* not yet reoptimized */
+ };
+
+ /* Support for plan reoptimization with rewritten conditions. */
+ enum_reopt_result reoptimize(Item *added_where, table_map join_tables,
+ Join_plan_state *save_to);
+ void save_query_plan(Join_plan_state *save_to);
+ void reset_query_plan();
+ void restore_query_plan(Join_plan_state *restore_from);
+ /* Choose a subquery plan for a table-less subquery. */
+ bool choose_tableless_subquery_plan();
+
+public:
+ JOIN_TAB *join_tab, **best_ref;
+
+ /*
+ Saved join_tab for pre_sorting. create_sort_index() will save here..
+ */
+ JOIN_TAB *pre_sort_join_tab;
+ uint pre_sort_index;
+ Item *pre_sort_idx_pushed_cond;
+ void clean_pre_sort_join_tab();
+
+ /*
+ For "Using temporary+Using filesort" queries, JOIN::join_tab can point to
+ either:
+ 1. array of join tabs describing how to run the select, or
+ 2. array of single join tab describing read from the temporary table.
+
+ SHOW EXPLAIN code needs to read/show #1. This is why two next members are
+ there for saving it.
+ */
+ JOIN_TAB *table_access_tabs;
+ uint top_table_access_tabs_count;
+
+ JOIN_TAB **map2table; ///< mapping between table indexes and JOIN_TABs
+ JOIN_TAB *join_tab_save; ///< saved join_tab for subquery reexecution
+
+ List<JOIN_TAB_RANGE> join_tab_ranges;
+
+ /*
+ Base tables participating in the join. After join optimization is done, the
+ tables are stored in the join order (but the only really important part is
+ that const tables are first).
+ */
+ TABLE **table;
+ /**
+ The table which has an index that allows to produce the requried ordering.
+ A special value of 0x1 means that the ordering will be produced by
+ passing 1st non-const table to filesort(). NULL means no such table exists.
+ */
+ TABLE *sort_by_table;
+ /*
+ Number of tables in the join.
+ (In MySQL, it is named 'tables' and is also the number of elements in
+ join->join_tab array. In MariaDB, the latter is not true, so we've renamed
+ the variable)
+ */
+ uint table_count;
+ uint outer_tables; /**< Number of tables that are not inside semijoin */
+ uint const_tables;
+ /*
+ Number of tables in the top join_tab array. Normally this matches
+ (join_tab_ranges.head()->end - join_tab_ranges.head()->start).
+
+ We keep it here so that it is saved/restored with JOIN::restore_tmp.
+ */
+ uint top_join_tab_count;
+ uint send_group_parts;
+ bool group; /**< If query contains GROUP BY clause */
+ bool need_distinct;
+
+ /**
+ Indicates that grouping will be performed on the result set during
+ query execution. This field belongs to query execution.
+
+ @see make_group_fields, alloc_group_fields, JOIN::exec
+ */
+ bool sort_and_group;
+ bool first_record,full_join, no_field_update;
+ bool hash_join;
+ bool do_send_rows;
+ table_map const_table_map;
+ /**
+ Bitmap of semijoin tables that the current partial plan decided
+ to materialize and access by lookups
+ */
+ table_map sjm_lookup_tables;
+ /*
+ Constant tables for which we have found a row (as opposed to those for
+ which we didn't).
+ */
+ table_map found_const_table_map;
+
+ /* Tables removed by table elimination. Set to 0 before the elimination. */
+ table_map eliminated_tables;
+ /*
+ Bitmap of all inner tables from outer joins (set at start of
+ make_join_statistics)
+ */
+ table_map outer_join;
+ /* Bitmap of tables used in the select list items */
+ table_map select_list_used_tables;
+ ha_rows send_records,found_records,examined_rows,row_limit, select_limit;
+ /**
+ Used to fetch no more than given amount of rows per one
+ fetch operation of server side cursor.
+ The value is checked in end_send and end_send_group in fashion, similar
+ to offset_limit_cnt:
+ - fetch_limit= HA_POS_ERROR if there is no cursor.
+ - when we open a cursor, we set fetch_limit to 0,
+ - on each fetch iteration we add num_rows to fetch to fetch_limit
+ */
+ ha_rows fetch_limit;
+ /* Finally picked QEP. This is result of join optimization */
+ POSITION *best_positions;
+
+/******* Join optimization state members start *******/
+ /*
+ pointer - we're doing optimization for a semi-join materialization nest.
+ NULL - otherwise
+ */
+ TABLE_LIST *emb_sjm_nest;
+
+ /* Current join optimization state */
+ POSITION *positions;
+
+ /*
+ Bitmap of nested joins embedding the position at the end of the current
+ partial join (valid only during join optimizer run).
+ */
+ nested_join_map cur_embedding_map;
+
+ /*
+ Bitmap of inner tables of semi-join nests that have a proper subset of
+ their tables in the current join prefix. That is, of those semi-join
+ nests that have their tables both in and outside of the join prefix.
+ */
+ table_map cur_sj_inner_tables;
+
+ /* We also maintain a stack of join optimization states in * join->positions[] */
+/******* Join optimization state members end *******/
+
+ /*
+ Tables within complex firstmatch ranges (i.e. those where inner tables are
+ interleaved with outer tables). Join buffering cannot be used for these.
+ */
+ table_map complex_firstmatch_tables;
+
+ /*
+ The cost of best complete join plan found so far during optimization,
+ after optimization phase - cost of picked join order (not taking into
+ account the changes made by test_if_skip_sort_order()).
+ */
+ double best_read;
+ /*
+ Estimated result rows (fanout) of the join operation. If this is a subquery
+ that is reexecuted multiple times, this value includes the estiamted # of
+ reexecutions. This value is equal to the multiplication of all
+ join->positions[i].records_read of a JOIN.
+ */
+ double record_count;
+ List<Item> *fields;
+ List<Cached_item> group_fields, group_fields_cache;
+ TABLE *tmp_table;
+ /// used to store 2 possible tmp table of SELECT
+ TABLE *exec_tmp_table1, *exec_tmp_table2;
+ THD *thd;
+ Item_sum **sum_funcs, ***sum_funcs_end;
+ /** second copy of sumfuncs (for queries with 2 temporary tables */
+ Item_sum **sum_funcs2, ***sum_funcs_end2;
+ Procedure *procedure;
+ Item *having;
+ Item *tmp_having; ///< To store having when processed temporary table
+ Item *having_history; ///< Store having for explain
+ ulonglong select_options;
+ /*
+ Bitmap of allowed types of the join caches that
+ can be used for join operations
+ */
+ uint allowed_join_cache_types;
+ bool allowed_semijoin_with_cache;
+ bool allowed_outer_join_with_cache;
+ /* Maximum level of the join caches that can be used for join operations */
+ uint max_allowed_join_cache_level;
+ select_result *result;
+ TMP_TABLE_PARAM tmp_table_param;
+ MYSQL_LOCK *lock;
+ /// unit structure (with global parameters) for this select
+ SELECT_LEX_UNIT *unit;
+ /// select that processed
+ SELECT_LEX *select_lex;
+ /**
+ TRUE <=> optimizer must not mark any table as a constant table.
+ This is needed for subqueries in form "a IN (SELECT .. UNION SELECT ..):
+ when we optimize the select that reads the results of the union from a
+ temporary table, we must not mark the temp. table as constant because
+ the number of rows in it may vary from one subquery execution to another.
+ */
+ bool no_const_tables;
+ /*
+ This flag is set if we call no_rows_in_result() as par of end_group().
+ This is used as a simple speed optimization to avoiding calling
+ restore_no_rows_in_result() in ::reinit()
+ */
+ bool no_rows_in_result_called;
+
+ /**
+ This is set if SQL_CALC_ROWS was calculated by filesort()
+ and should be taken from the appropriate JOIN_TAB
+ */
+ bool filesort_found_rows;
+
+ /**
+ Copy of this JOIN to be used with temporary tables.
+
+ tmp_join is used when the JOIN needs to be "reusable" (e.g. in a
+ subquery that gets re-executed several times) and we know will use
+ temporary tables for materialization. The materialization to a
+ temporary table overwrites the JOIN structure to point to the
+ temporary table after the materialization is done. This is where
+ tmp_join is used : it's a copy of the JOIN before the
+ materialization and is used in restoring before re-execution by
+ overwriting the current JOIN structure with the saved copy.
+ Because of this we should pay extra care of not freeing up helper
+ structures that are referenced by the original contents of the
+ JOIN. We can check for this by making sure the "current" join is
+ not the temporary copy, e.g. !tmp_join || tmp_join != join
+
+ We should free these sub-structures at JOIN::destroy() if the
+ "current" join has a copy is not that copy.
+ */
+ JOIN *tmp_join;
+ ROLLUP rollup; ///< Used with rollup
+
+ bool mixed_implicit_grouping;
+ bool select_distinct; ///< Set if SELECT DISTINCT
+ /**
+ If we have the GROUP BY statement in the query,
+ but the group_list was emptied by optimizer, this
+ flag is TRUE.
+ It happens when fields in the GROUP BY are from
+ constant table
+ */
+ bool group_optimized_away;
+
+ /*
+ simple_xxxxx is set if ORDER/GROUP BY doesn't include any references
+ to other tables than the first non-constant table in the JOIN.
+ It's also set if ORDER/GROUP BY is empty.
+ Used for deciding for or against using a temporary table to compute
+ GROUP/ORDER BY.
+ */
+ bool simple_order, simple_group;
+ /**
+ Is set only in case if we have a GROUP BY clause
+ and no ORDER BY after constant elimination of 'order'.
+ */
+ bool no_order;
+ /** Is set if we have a GROUP BY and we have ORDER BY on a constant. */
+ bool skip_sort_order;
+
+ bool need_tmp, hidden_group_fields;
+ /* TRUE if there was full cleunap of the JOIN */
+ bool cleaned;
+ DYNAMIC_ARRAY keyuse;
+ Item::cond_result cond_value, having_value;
+ /**
+ Impossible where after reading const tables
+ (set in make_join_statistics())
+ */
+ bool impossible_where;
+ List<Item> all_fields; ///< to store all fields that used in query
+ ///Above list changed to use temporary table
+ List<Item> tmp_all_fields1, tmp_all_fields2, tmp_all_fields3;
+ ///Part, shared with list above, emulate following list
+ List<Item> tmp_fields_list1, tmp_fields_list2, tmp_fields_list3;
+ List<Item> &fields_list; ///< hold field list passed to mysql_select
+ List<Item> procedure_fields_list;
+ int error;
+
+ ORDER *order, *group_list, *proc_param; //hold parameters of mysql_select
+ COND *conds; // ---"---
+ Item *conds_history; // store WHERE for explain
+ COND *outer_ref_cond; ///<part of conds containing only outer references
+ COND *pseudo_bits_cond; // part of conds containing special bita
+ TABLE_LIST *tables_list; ///<hold 'tables' parameter of mysql_select
+ List<TABLE_LIST> *join_list; ///< list of joined tables in reverse order
+ COND_EQUAL *cond_equal;
+ COND_EQUAL *having_equal;
+ /*
+ Constant codition computed during optimization, but evaluated during
+ join execution. Typically expensive conditions that should not be
+ evaluated at optimization time.
+ */
+ Item *exec_const_cond;
+ /*
+ Constant ORDER and/or GROUP expressions that contain subqueries. Such
+ expressions need to evaluated to verify that the subquery indeed
+ returns a single row. The evaluation of such expressions is delayed
+ until query execution.
+ */
+ List<Item> exec_const_order_group_cond;
+ SQL_SELECT *select; ///<created in optimisation phase
+ JOIN_TAB *return_tab; ///<used only for outer joins
+ Item **ref_pointer_array; ///<used pointer reference for this select
+ // Copy of above to be used with different lists
+ Item **items0, **items1, **items2, **items3, **current_ref_pointer_array;
+ uint ref_pointer_array_size; ///< size of above in bytes
+ const char *zero_result_cause; ///< not 0 if exec must return zero result
+
+ bool union_part; ///< this subselect is part of union
+
+ enum join_optimization_state { NOT_OPTIMIZED=0,
+ OPTIMIZATION_IN_PROGRESS=1,
+ OPTIMIZATION_DONE=2};
+ bool optimized; ///< flag to avoid double optimization in EXPLAIN
+ bool initialized; ///< flag to avoid double init_execution calls
+
+ enum { QEP_NOT_PRESENT_YET, QEP_AVAILABLE, QEP_DELETED} have_query_plan;
+
+ /*
+ Additional WHERE and HAVING predicates to be considered for IN=>EXISTS
+ subquery transformation of a JOIN object.
+ */
+ Item *in_to_exists_where;
+ Item *in_to_exists_having;
+
+ /* Temporary tables used to weed-out semi-join duplicates */
+ List<TABLE> sj_tmp_tables;
+ /* SJM nests that are executed with SJ-Materialization strategy */
+ List<SJ_MATERIALIZATION_INFO> sjm_info_list;
+
+ /*
+ storage for caching buffers allocated during query execution.
+ These buffers allocations need to be cached as the thread memory pool is
+ cleared only at the end of the execution of the whole query and not caching
+ allocations that occur in repetition at execution time will result in
+ excessive memory usage.
+ Note: make_simple_join always creates an execution plan that accesses
+ a single table, thus it is sufficient to have a one-element array for
+ table_reexec.
+ */
+ SORT_FIELD *sortorder; // make_unireg_sortorder()
+ TABLE *table_reexec[1]; // make_simple_join()
+ JOIN_TAB *join_tab_reexec; // make_simple_join()
+ /* end of allocation caching storage */
+
+ JOIN(THD *thd_arg, List<Item> &fields_arg, ulonglong select_options_arg,
+ select_result *result_arg)
+ :fields_list(fields_arg)
+ {
+ init(thd_arg, fields_arg, select_options_arg, result_arg);
+ }
+
+ void init(THD *thd_arg, List<Item> &fields_arg, ulonglong select_options_arg,
+ select_result *result_arg)
+ {
+ join_tab= join_tab_save= 0;
+ table= 0;
+ table_count= 0;
+ top_join_tab_count= 0;
+ const_tables= 0;
+ eliminated_tables= 0;
+ join_list= 0;
+ implicit_grouping= FALSE;
+ sort_and_group= 0;
+ first_record= 0;
+ do_send_rows= 1;
+ send_records= 0;
+ found_records= 0;
+ fetch_limit= HA_POS_ERROR;
+ examined_rows= 0;
+ exec_tmp_table1= 0;
+ exec_tmp_table2= 0;
+ sortorder= 0;
+ table_reexec[0]= 0;
+ join_tab_reexec= 0;
+ thd= thd_arg;
+ sum_funcs= sum_funcs2= 0;
+ procedure= 0;
+ having= tmp_having= having_history= 0;
+ select_options= select_options_arg;
+ result= result_arg;
+ lock= thd_arg->lock;
+ select_lex= 0; //for safety
+ tmp_join= 0;
+ select_distinct= MY_TEST(select_options & SELECT_DISTINCT);
+ no_order= 0;
+ simple_order= 0;
+ simple_group= 0;
+ need_distinct= 0;
+ skip_sort_order= 0;
+ need_tmp= 0;
+ hidden_group_fields= 0; /*safety*/
+ error= 0;
+ select= 0;
+ return_tab= 0;
+ ref_pointer_array= items0= items1= items2= items3= 0;
+ ref_pointer_array_size= 0;
+ zero_result_cause= 0;
+ optimized= 0;
+ have_query_plan= QEP_NOT_PRESENT_YET;
+ initialized= 0;
+ cleaned= 0;
+ cond_equal= 0;
+ having_equal= 0;
+ exec_const_cond= 0;
+ group_optimized_away= 0;
+ no_rows_in_result_called= 0;
+ positions= best_positions= 0;
+
+ all_fields= fields_arg;
+ if (&fields_list != &fields_arg) /* Avoid valgrind-warning */
+ fields_list= fields_arg;
+ bzero((char*) &keyuse,sizeof(keyuse));
+ tmp_table_param.init();
+ tmp_table_param.end_write_records= HA_POS_ERROR;
+ rollup.state= ROLLUP::STATE_NONE;
+
+ no_const_tables= FALSE;
+ outer_ref_cond= pseudo_bits_cond= NULL;
+ in_to_exists_where= NULL;
+ in_to_exists_having= NULL;
+ pre_sort_join_tab= NULL;
+ emb_sjm_nest= NULL;
+ sjm_lookup_tables= 0;
+
+ exec_saved_explain= false;
+ /*
+ The following is needed because JOIN::cleanup(true) may be called for
+ joins for which JOIN::optimize was aborted with an error before a proper
+ query plan was produced
+ */
+ table_access_tabs= NULL;
+ }
+
+ /*
+ TRUE <=> There was a JOIN::exec() call, which saved this JOIN's EXPLAIN.
+ The idea is that we also save at the end of JOIN::optimize(), but that
+ might not be the final plan.
+ */
+ bool exec_saved_explain;
+
+ int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num,
+ COND *conds, uint og_num, ORDER *order, bool skip_order_by,
+ ORDER *group, Item *having, ORDER *proc_param, SELECT_LEX *select,
+ SELECT_LEX_UNIT *unit);
+ bool prepare_stage2();
+ int optimize();
+ int optimize_inner();
+ int reinit();
+ int init_execution();
+ void exec();
+ void exec_inner();
+ int destroy();
+ void restore_tmp();
+ bool alloc_func_list();
+ bool flatten_subqueries();
+ bool optimize_unflattened_subqueries();
+ bool optimize_constant_subqueries();
+ bool make_sum_func_list(List<Item> &all_fields, List<Item> &send_fields,
+ bool before_group_by, bool recompute= FALSE);
+
+ inline void set_items_ref_array(Item **ptr)
+ {
+ memcpy((char*) ref_pointer_array, (char*) ptr, ref_pointer_array_size);
+ current_ref_pointer_array= ptr;
+ }
+ inline void init_items_ref_array()
+ {
+ items0= ref_pointer_array + all_fields.elements;
+ memcpy(items0, ref_pointer_array, ref_pointer_array_size);
+ current_ref_pointer_array= items0;
+ }
+
+ bool rollup_init();
+ bool rollup_process_const_fields();
+ bool rollup_make_fields(List<Item> &all_fields, List<Item> &fields,
+ Item_sum ***func);
+ int rollup_send_data(uint idx);
+ int rollup_write_data(uint idx, TABLE *table);
+ /**
+ Release memory and, if possible, the open tables held by this execution
+ plan (and nested plans). It's used to release some tables before
+ the end of execution in order to increase concurrency and reduce
+ memory consumption.
+ */
+ void join_free();
+ /** Cleanup this JOIN, possibly for reuse */
+ void cleanup(bool full);
+ void clear();
+ bool save_join_tab();
+ bool init_save_join_tab();
+ bool send_row_on_empty_set()
+ {
+ return (do_send_rows && implicit_grouping && !group_optimized_away &&
+ having_value != Item::COND_FALSE);
+ }
+ bool empty_result() { return (zero_result_cause && !implicit_grouping); }
+ bool change_result(select_result *result);
+ bool is_top_level_join() const
+ {
+ return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 ||
+ select_lex == unit->fake_select_lex));
+ }
+ void cache_const_exprs();
+ inline table_map all_tables_map()
+ {
+ return (table_map(1) << table_count) - 1;
+ }
+ void drop_unused_derived_keys();
+ inline void eval_select_list_used_tables();
+ /*
+ Return the table for which an index scan can be used to satisfy
+ the sort order needed by the ORDER BY/(implicit) GROUP BY clause
+ */
+ JOIN_TAB *get_sort_by_join_tab()
+ {
+ return (need_tmp || !sort_by_table || skip_sort_order ||
+ ((group || tmp_table_param.sum_func_count) && !group_list)) ?
+ NULL : join_tab+const_tables;
+ }
+ bool setup_subquery_caches();
+ bool shrink_join_buffers(JOIN_TAB *jt,
+ ulonglong curr_space,
+ ulonglong needed_space);
+ void set_allowed_join_cache_types();
+ bool is_allowed_hash_join_access()
+ {
+ return MY_TEST(allowed_join_cache_types & JOIN_CACHE_HASHED_BIT) &&
+ max_allowed_join_cache_level > JOIN_CACHE_HASHED_BIT;
+ }
+ /*
+ Check if we need to create a temporary table.
+ This has to be done if all tables are not already read (const tables)
+ and one of the following conditions holds:
+ - We are using DISTINCT (simple distinct's are already optimized away)
+ - We are using an ORDER BY or GROUP BY on fields not in the first table
+ - We are using different ORDER BY and GROUP BY orders
+ - The user wants us to buffer the result.
+ When the WITH ROLLUP modifier is present, we cannot skip temporary table
+ creation for the DISTINCT clause just because there are only const tables.
+ */
+ bool test_if_need_tmp_table()
+ {
+ return ((const_tables != table_count &&
+ ((select_distinct || !simple_order || !simple_group) ||
+ (group_list && order) ||
+ MY_TEST(select_options & OPTION_BUFFER_RESULT))) ||
+ (rollup.state != ROLLUP::STATE_NONE && select_distinct));
+ }
+ bool choose_subquery_plan(table_map join_tables);
+ void get_partial_cost_and_fanout(int end_tab_idx,
+ table_map filter_map,
+ double *read_time_arg,
+ double *record_count_arg);
+ void get_prefix_cost_and_fanout(uint n_tables,
+ double *read_time_arg,
+ double *record_count_arg);
+ double get_examined_rows();
+ /* defined in opt_subselect.cc */
+ bool transform_max_min_subquery();
+ /* True if this JOIN is a subquery under an IN predicate. */
+ bool is_in_subquery()
+ {
+ return (unit->item && unit->item->is_in_predicate());
+ }
+ void save_explain_data(Explain_query *output, bool can_overwrite,
+ bool need_tmp_table, bool need_order, bool distinct);
+ int save_explain_data_intern(Explain_query *output, bool need_tmp_table,
+ bool need_order, bool distinct,
+ const char *message);
+private:
+ /**
+ TRUE if the query contains an aggregate function but has no GROUP
+ BY clause.
+ */
+ bool implicit_grouping;
+ bool make_simple_join(JOIN *join, TABLE *tmp_table);
+ void cleanup_item_list(List<Item> &items) const;
+};
+
+enum enum_with_bush_roots { WITH_BUSH_ROOTS, WITHOUT_BUSH_ROOTS};
+enum enum_with_const_tables { WITH_CONST_TABLES, WITHOUT_CONST_TABLES};
+
+JOIN_TAB *first_linear_tab(JOIN *join,
+ enum enum_with_bush_roots include_bush_roots,
+ enum enum_with_const_tables const_tbls);
+JOIN_TAB *next_linear_tab(JOIN* join, JOIN_TAB* tab,
+ enum enum_with_bush_roots include_bush_roots);
+
+JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables with_const);
+JOIN_TAB *next_top_level_tab(JOIN *join, JOIN_TAB *tab);
+
+typedef struct st_select_check {
+ uint const_ref,reg_ref;
+} SELECT_CHECK;
+
+extern const char *join_type_str[];
+
+/* Extern functions in sql_select.cc */
+void count_field_types(SELECT_LEX *select_lex, TMP_TABLE_PARAM *param,
+ List<Item> &fields, bool reset_with_sum_func);
+bool setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param,
+ Item **ref_pointer_array,
+ List<Item> &new_list1, List<Item> &new_list2,
+ uint elements, List<Item> &fields);
+void copy_fields(TMP_TABLE_PARAM *param);
+bool copy_funcs(Item **func_ptr, const THD *thd);
+uint find_shortest_key(TABLE *table, const key_map *usable_keys);
+Field* create_tmp_field_from_field(THD *thd, Field* org_field,
+ const char *name, TABLE *table,
+ Item_field *item, uint convert_blob_length);
+
+bool is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args);
+
+/* functions from opt_sum.cc */
+bool simple_pred(Item_func *func_item, Item **args, bool *inv_order);
+int opt_sum_query(THD* thd,
+ List<TABLE_LIST> &tables, List<Item> &all_fields, COND *conds);
+
+/* from sql_delete.cc, used by opt_range.cc */
+extern "C" int refpos_order_cmp(void* arg, const void *a,const void *b);
+
+/** class to copying an field/item to a key struct */
+
+class store_key :public Sql_alloc
+{
+public:
+ bool null_key; /* TRUE <=> the value of the key has a null part */
+ enum store_key_result { STORE_KEY_OK, STORE_KEY_FATAL, STORE_KEY_CONV };
+ enum Type { FIELD_STORE_KEY, ITEM_STORE_KEY, CONST_ITEM_STORE_KEY };
+ store_key(THD *thd, Field *field_arg, uchar *ptr, uchar *null, uint length)
+ :null_key(0), null_ptr(null), err(0)
+ {
+ to_field=field_arg->new_key_field(thd->mem_root, field_arg->table,
+ ptr, length, null, 1);
+ }
+ store_key(store_key &arg)
+ :Sql_alloc(), null_key(arg.null_key), to_field(arg.to_field),
+ null_ptr(arg.null_ptr), err(arg.err)
+
+ {}
+ virtual ~store_key() {} /** Not actually needed */
+ virtual enum Type type() const=0;
+ virtual const char *name() const=0;
+ virtual bool store_key_is_const() { return false; }
+
+ /**
+ @brief sets ignore truncation warnings mode and calls the real copy method
+
+ @details this function makes sure truncation warnings when preparing the
+ key buffers don't end up as errors (because of an enclosing INSERT/UPDATE).
+ */
+ enum store_key_result copy()
+ {
+ enum store_key_result result;
+ THD *thd= to_field->table->in_use;
+ enum_check_fields saved_count_cuted_fields= thd->count_cuted_fields;
+ ulonglong sql_mode= thd->variables.sql_mode;
+ thd->variables.sql_mode&= ~(MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE);
+ thd->variables.sql_mode|= MODE_INVALID_DATES;
+
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+
+ result= copy_inner();
+
+ thd->count_cuted_fields= saved_count_cuted_fields;
+ thd->variables.sql_mode= sql_mode;
+
+ return result;
+ }
+
+ protected:
+ Field *to_field; // Store data here
+ uchar *null_ptr;
+ uchar err;
+
+ virtual enum store_key_result copy_inner()=0;
+};
+
+
+class store_key_field: public store_key
+{
+ Copy_field copy_field;
+ const char *field_name;
+ public:
+ store_key_field(THD *thd, Field *to_field_arg, uchar *ptr,
+ uchar *null_ptr_arg,
+ uint length, Field *from_field, const char *name_arg)
+ :store_key(thd, to_field_arg,ptr,
+ null_ptr_arg ? null_ptr_arg : from_field->maybe_null() ? &err
+ : (uchar*) 0, length), field_name(name_arg)
+ {
+ if (to_field)
+ {
+ copy_field.set(to_field,from_field,0);
+ }
+ }
+
+ enum Type type() const { return FIELD_STORE_KEY; }
+ const char *name() const { return field_name; }
+
+ void change_source_field(Item_field *fld_item)
+ {
+ copy_field.set(to_field, fld_item->field, 0);
+ field_name= fld_item->full_name();
+ }
+
+ protected:
+ enum store_key_result copy_inner()
+ {
+ TABLE *table= copy_field.to_field->table;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table,
+ table->write_set);
+
+ /*
+ It looks like the next statement is needed only for a simplified
+ hash function over key values used now in BNLH join.
+ When the implementation of this function will be replaced for a proper
+ full version this statement probably should be removed.
+ */
+ bzero(copy_field.to_ptr,copy_field.to_length);
+
+ copy_field.do_copy(&copy_field);
+ dbug_tmp_restore_column_map(table->write_set, old_map);
+ null_key= to_field->is_null();
+ return err != 0 ? STORE_KEY_FATAL : STORE_KEY_OK;
+ }
+};
+
+
+class store_key_item :public store_key
+{
+ protected:
+ Item *item;
+ /*
+ Flag that forces usage of save_val() method which save value of the
+ item instead of save_in_field() method which saves result.
+ */
+ bool use_value;
+public:
+ store_key_item(THD *thd, Field *to_field_arg, uchar *ptr,
+ uchar *null_ptr_arg, uint length, Item *item_arg, bool val)
+ :store_key(thd, to_field_arg, ptr,
+ null_ptr_arg ? null_ptr_arg : item_arg->maybe_null ?
+ &err : (uchar*) 0, length), item(item_arg), use_value(val)
+ {}
+ store_key_item(store_key &arg, Item *new_item, bool val)
+ :store_key(arg), item(new_item), use_value(val)
+ {}
+
+
+ enum Type type() const { return ITEM_STORE_KEY; }
+ const char *name() const { return "func"; }
+
+ protected:
+ enum store_key_result copy_inner()
+ {
+ TABLE *table= to_field->table;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table,
+ table->write_set);
+ int res= FALSE;
+
+ /*
+ It looks like the next statement is needed only for a simplified
+ hash function over key values used now in BNLH join.
+ When the implementation of this function will be replaced for a proper
+ full version this statement probably should be removed.
+ */
+ to_field->reset();
+
+ if (use_value)
+ item->save_val(to_field);
+ else
+ res= item->save_in_field(to_field, 1);
+ /*
+ Item::save_in_field() may call Item::val_xxx(). And if this is a subquery
+ we need to check for errors executing it and react accordingly
+ */
+ if (!res && table->in_use->is_error())
+ res= 1; /* STORE_KEY_FATAL */
+ dbug_tmp_restore_column_map(table->write_set, old_map);
+ null_key= to_field->is_null() || item->null_value;
+ return ((err != 0 || res < 0 || res > 2) ? STORE_KEY_FATAL :
+ (store_key_result) res);
+ }
+};
+
+
+class store_key_const_item :public store_key_item
+{
+ bool inited;
+public:
+ store_key_const_item(THD *thd, Field *to_field_arg, uchar *ptr,
+ uchar *null_ptr_arg, uint length,
+ Item *item_arg)
+ :store_key_item(thd, to_field_arg, ptr,
+ null_ptr_arg ? null_ptr_arg : item_arg->maybe_null ?
+ &err : (uchar*) 0, length, item_arg, FALSE), inited(0)
+ {
+ }
+ store_key_const_item(store_key &arg, Item *new_item)
+ :store_key_item(arg, new_item, FALSE), inited(0)
+ {}
+
+ enum Type type() const { return CONST_ITEM_STORE_KEY; }
+ const char *name() const { return "const"; }
+ bool store_key_is_const() { return true; }
+
+protected:
+ enum store_key_result copy_inner()
+ {
+ int res;
+ if (!inited)
+ {
+ inited=1;
+ TABLE *table= to_field->table;
+ my_bitmap_map *old_map= dbug_tmp_use_all_columns(table,
+ table->write_set);
+ if ((res= item->save_in_field(to_field, 1)))
+ {
+ if (!err)
+ err= res < 0 ? 1 : res; /* 1=STORE_KEY_FATAL */
+ }
+ /*
+ Item::save_in_field() may call Item::val_xxx(). And if this is a subquery
+ we need to check for errors executing it and react accordingly
+ */
+ if (!err && to_field->table->in_use->is_error())
+ err= 1; /* STORE_KEY_FATAL */
+ dbug_tmp_restore_column_map(table->write_set, old_map);
+ }
+ null_key= to_field->is_null() || item->null_value;
+ return (err > 2 ? STORE_KEY_FATAL : (store_key_result) err);
+ }
+};
+
+bool cp_buffer_from_ref(THD *thd, TABLE *table, TABLE_REF *ref);
+bool error_if_full_join(JOIN *join);
+int report_error(TABLE *table, int error);
+int safe_index_read(JOIN_TAB *tab);
+COND *remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value);
+int get_quick_record(SQL_SELECT *select);
+SORT_FIELD * make_unireg_sortorder(ORDER *order, uint *length,
+ SORT_FIELD *sortorder);
+int setup_order(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
+ List<Item> &fields, List <Item> &all_fields, ORDER *order);
+int setup_group(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
+ List<Item> &fields, List<Item> &all_fields, ORDER *order,
+ bool *hidden_group_fields);
+bool fix_inner_refs(THD *thd, List<Item> &all_fields, SELECT_LEX *select,
+ Item **ref_pointer_array);
+int join_read_key2(THD *thd, struct st_join_table *tab, TABLE *table,
+ struct st_table_ref *table_ref);
+
+bool handle_select(THD *thd, LEX *lex, select_result *result,
+ ulong setup_tables_done_option);
+bool mysql_select(THD *thd, Item ***rref_pointer_array,
+ TABLE_LIST *tables, uint wild_num, List<Item> &list,
+ COND *conds, uint og_num, ORDER *order, ORDER *group,
+ Item *having, ORDER *proc_param, ulonglong select_type,
+ select_result *result, SELECT_LEX_UNIT *unit,
+ SELECT_LEX *select_lex);
+void free_underlaid_joins(THD *thd, SELECT_LEX *select);
+bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit,
+ select_result *result);
+Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type,
+ Item ***copy_func, Field **from_field,
+ Field **def_field,
+ bool group, bool modify_item,
+ bool table_cant_handle_bit_fields,
+ bool make_copy_field,
+ uint convert_blob_length);
+
+/*
+ General routine to change field->ptr of a NULL-terminated array of Field
+ objects. Useful when needed to call val_int, val_str or similar and the
+ field data is not in table->record[0] but in some other structure.
+ set_key_field_ptr changes all fields of an index using a key_info object.
+ All methods presume that there is at least one field to change.
+*/
+
+TABLE *create_virtual_tmp_table(THD *thd, List<Create_field> &field_list);
+
+int test_if_item_cache_changed(List<Cached_item> &list);
+int join_init_read_record(JOIN_TAB *tab);
+int join_read_record_no_init(JOIN_TAB *tab);
+void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key);
+inline Item * and_items(Item* cond, Item *item)
+{
+ return (cond? (new Item_cond_and(cond, item)) : item);
+}
+bool choose_plan(JOIN *join, table_map join_tables);
+void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab,
+ table_map last_remaining_tables,
+ bool first_alt, uint no_jbuf_before,
+ double *outer_rec_count, double *reopt_cost);
+Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field,
+ bool *inherited_fl);
+extern bool test_if_ref(Item *,
+ Item_field *left_item,Item *right_item);
+
+inline bool optimizer_flag(THD *thd, uint flag)
+{
+ return (thd->variables.optimizer_switch & flag);
+}
+
+int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly,
+ SELECT_LEX *select_lex, uint8 select_options);
+
+uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select,
+ ha_rows limit, ha_rows *scanned_limit,
+ bool *need_sort, bool *reverse);
+ORDER *simple_remove_const(ORDER *order, COND *where);
+bool const_expression_in_where(COND *cond, Item *comp_item,
+ Field *comp_field= NULL,
+ Item **const_item= NULL);
+bool cond_is_datetime_is_null(Item *cond);
+bool cond_has_datetime_is_null(Item *cond);
+
+/* Table elimination entry point function */
+void eliminate_tables(JOIN *join);
+
+/* Index Condition Pushdown entry point function */
+void push_index_cond(JOIN_TAB *tab, uint keyno);
+
+#define OPT_LINK_EQUAL_FIELDS 1
+
+/* EXPLAIN-related utility functions */
+int print_explain_message_line(select_result_sink *result,
+ uint8 options,
+ uint select_number,
+ const char *select_type,
+ ha_rows *rows,
+ const char *message);
+void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res);
+int print_explain_row(select_result_sink *result,
+ uint8 options,
+ uint select_number,
+ const char *select_type,
+ const char *table_name,
+ const char *partitions,
+ enum join_type jtype,
+ const char *possible_keys,
+ const char *index,
+ const char *key_len,
+ const char *ref,
+ ha_rows *rows,
+ const char *extra);
+void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line);
+
+/****************************************************************************
+ Temporary table support for SQL Runtime
+ ***************************************************************************/
+
+#define STRING_TOTAL_LENGTH_TO_PACK_ROWS 128
+#define AVG_STRING_LENGTH_TO_PACK_ROWS 64
+#define RATIO_TO_PACK_ROWS 2
+#define MIN_STRING_LENGTH_TO_PACK_ROWS 10
+
+TABLE *create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields,
+ ORDER *group, bool distinct, bool save_sum_fields,
+ ulonglong select_options, ha_rows rows_limit,
+ const char* alias, bool do_not_open=FALSE,
+ bool keep_row_order= FALSE);
+void free_tmp_table(THD *thd, TABLE *entry);
+bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table,
+ TMP_ENGINE_COLUMNDEF *start_recinfo,
+ TMP_ENGINE_COLUMNDEF **recinfo,
+ int error, bool ignore_last_dupp_key_error,
+ bool *is_duplicate);
+bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
+ TMP_ENGINE_COLUMNDEF *start_recinfo,
+ TMP_ENGINE_COLUMNDEF **recinfo,
+ ulonglong options);
+bool open_tmp_table(TABLE *table);
+void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps);
+double prev_record_reads(POSITION *positions, uint idx, table_map found_ref);
+void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist);
+
+struct st_cond_statistic
+{
+ Item *cond;
+ Field *field_arg;
+ ulong positive;
+};
+typedef struct st_cond_statistic COND_STATISTIC;
+
+ulong check_selectivity(THD *thd,
+ ulong rows_to_read,
+ TABLE *table,
+ List<COND_STATISTIC> *conds);
+
+#endif /* SQL_SELECT_INCLUDED */
diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc
new file mode 100644
index 00000000000..2b0576ffba9
--- /dev/null
+++ b/sql/sql_servers.cc
@@ -0,0 +1,1334 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/*
+ The servers are saved in the system table "servers"
+
+ Currently, when the user performs an ALTER SERVER or a DROP SERVER
+ operation, it will cause all open tables which refer to the named
+ server connection to be flushed. This may cause some undesirable
+ behaviour with regard to currently running transactions. It is
+ expected that the DBA knows what s/he is doing when s/he performs
+ the ALTER SERVER or DROP SERVER operation.
+
+ TODO:
+ It is desirable for us to implement a callback mechanism instead where
+ callbacks can be registered for specific server protocols. The callback
+ will be fired when such a server name has been created/altered/dropped
+ or when statistics are to be gathered such as how many actual connections.
+ Storage engines etc will be able to make use of the callback so that
+ currently running transactions etc will not be disrupted.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "sql_servers.h"
+#include "unireg.h"
+#include "sql_base.h" // close_mysql_tables
+#include "records.h" // init_read_record, end_read_record
+#include <m_ctype.h>
+#include <stdarg.h>
+#include "sp_head.h"
+#include "sp.h"
+#include "transaction.h"
+#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
+
+/*
+ We only use 1 mutex to guard the data structures - THR_LOCK_servers.
+ Read locked when only reading data and write-locked for all other access.
+*/
+
+static HASH servers_cache;
+static MEM_ROOT mem;
+static mysql_rwlock_t THR_LOCK_servers;
+
+static bool get_server_from_table_to_cache(TABLE *table);
+
+/* insert functions */
+static int insert_server(THD *thd, FOREIGN_SERVER *server_options);
+static int insert_server_record(TABLE *table, FOREIGN_SERVER *server);
+static int insert_server_record_into_cache(FOREIGN_SERVER *server);
+static FOREIGN_SERVER *
+prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options);
+/* drop functions */
+static int delete_server_record(TABLE *table,
+ char *server_name,
+ size_t server_name_length);
+static int delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options);
+
+/* update functions */
+static void prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options,
+ FOREIGN_SERVER *existing,
+ FOREIGN_SERVER *altered);
+static int update_server(THD *thd, FOREIGN_SERVER *existing,
+ FOREIGN_SERVER *altered);
+static int update_server_record(TABLE *table, FOREIGN_SERVER *server);
+static int update_server_record_in_cache(FOREIGN_SERVER *existing,
+ FOREIGN_SERVER *altered);
+/* utility functions */
+static void merge_server_struct(FOREIGN_SERVER *from, FOREIGN_SERVER *to);
+
+
+
+static uchar *servers_cache_get_key(FOREIGN_SERVER *server, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ DBUG_ENTER("servers_cache_get_key");
+ DBUG_PRINT("info", ("server_name_length %d server_name %s",
+ server->server_name_length,
+ server->server_name));
+
+ *length= (uint) server->server_name_length;
+ DBUG_RETURN((uchar*) server->server_name);
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_rwlock_key key_rwlock_THR_LOCK_servers;
+
+static PSI_rwlock_info all_servers_cache_rwlocks[]=
+{
+ { &key_rwlock_THR_LOCK_servers, "THR_LOCK_servers", PSI_FLAG_GLOBAL}
+};
+
+static void init_servers_cache_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_servers_cache_rwlocks);
+ PSI_server->register_rwlock(category, all_servers_cache_rwlocks, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+/*
+ Initialize structures responsible for servers used in federated
+ server scheme information for them from the server
+ table in the 'mysql' database.
+
+ SYNOPSIS
+ servers_init()
+ dont_read_server_table TRUE if we want to skip loading data from
+ server table and disable privilege checking.
+
+ NOTES
+ This function is mostly responsible for preparatory steps, main work
+ on initialization and grants loading is done in servers_reload().
+
+ RETURN VALUES
+ 0 ok
+ 1 Could not initialize servers
+*/
+
+bool servers_init(bool dont_read_servers_table)
+{
+ THD *thd;
+ bool return_val= FALSE;
+ DBUG_ENTER("servers_init");
+
+#ifdef HAVE_PSI_INTERFACE
+ init_servers_cache_psi_keys();
+#endif
+
+ /* init the mutex */
+ if (mysql_rwlock_init(key_rwlock_THR_LOCK_servers, &THR_LOCK_servers))
+ DBUG_RETURN(TRUE);
+
+ /* initialise our servers cache */
+ if (my_hash_init(&servers_cache, system_charset_info, 32, 0, 0,
+ (my_hash_get_key) servers_cache_get_key, 0, 0))
+ {
+ return_val= TRUE; /* we failed, out of memory? */
+ goto end;
+ }
+
+ /* Initialize the mem root for data */
+ init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC));
+
+ if (dont_read_servers_table)
+ goto end;
+
+ /*
+ To be able to run this from boot, we allocate a temporary THD
+ */
+ if (!(thd=new THD))
+ DBUG_RETURN(TRUE);
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+ /*
+ It is safe to call servers_reload() since servers_* arrays and hashes which
+ will be freed there are global static objects and thus are initialized
+ by zeros at startup.
+ */
+ return_val= servers_reload(thd);
+ delete thd;
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+
+end:
+ DBUG_RETURN(return_val);
+}
+
+/*
+ Initialize server structures
+
+ SYNOPSIS
+ servers_load()
+ thd Current thread
+ tables List containing open "mysql.servers"
+
+ RETURN VALUES
+ FALSE Success
+ TRUE Error
+
+ TODO
+ Revert back to old list if we failed to load new one.
+*/
+
+static bool servers_load(THD *thd, TABLE_LIST *tables)
+{
+ TABLE *table;
+ READ_RECORD read_record_info;
+ bool return_val= TRUE;
+ DBUG_ENTER("servers_load");
+
+ my_hash_reset(&servers_cache);
+ free_root(&mem, MYF(0));
+ init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
+
+ if (init_read_record(&read_record_info,thd,table=tables[0].table,NULL,1,0,
+ FALSE))
+ DBUG_RETURN(1);
+ while (!(read_record_info.read_record(&read_record_info)))
+ {
+ /* return_val is already TRUE, so no need to set */
+ if ((get_server_from_table_to_cache(table)))
+ goto end;
+ }
+
+ return_val= FALSE;
+
+end:
+ end_read_record(&read_record_info);
+ DBUG_RETURN(return_val);
+}
+
+
+/*
+ Forget current servers cache and read new servers
+ from the conneciton table.
+
+ SYNOPSIS
+ servers_reload()
+ thd Current thread
+
+ NOTE
+ All tables of calling thread which were open and locked by LOCK TABLES
+ statement will be unlocked and closed.
+ This function is also used for initialization of structures responsible
+ for user/db-level privilege checking.
+
+ RETURN VALUE
+ FALSE Success
+ TRUE Failure
+*/
+
+bool servers_reload(THD *thd)
+{
+ TABLE_LIST tables[1];
+ bool return_val= TRUE;
+ DBUG_ENTER("servers_reload");
+
+ DBUG_PRINT("info", ("locking servers_cache"));
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+
+ tables[0].init_one_table("mysql", 5, "servers", 7, "servers", TL_READ);
+
+ if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ /*
+ Execution might have been interrupted; only print the error message
+ if an error condition has been raised.
+ */
+ if (thd->get_stmt_da()->is_error())
+ sql_print_error("Can't open and lock privilege tables: %s",
+ thd->get_stmt_da()->message());
+ return_val= FALSE;
+ goto end;
+ }
+
+ if ((return_val= servers_load(thd, tables)))
+ { // Error. Revert to old list
+ /* blast, for now, we have no servers, discuss later way to preserve */
+
+ DBUG_PRINT("error",("Reverting to old privileges"));
+ servers_free();
+ }
+
+end:
+ close_mysql_tables(thd);
+ DBUG_PRINT("info", ("unlocking servers_cache"));
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ DBUG_RETURN(return_val);
+}
+
+
+/*
+ Initialize structures responsible for servers used in federated
+ server scheme information for them from the server
+ table in the 'mysql' database.
+
+ SYNOPSIS
+ get_server_from_table_to_cache()
+ TABLE *table open table pointer
+
+
+ NOTES
+ This function takes a TABLE pointer (pointing to an opened
+ table). With this open table, a FOREIGN_SERVER struct pointer
+ is allocated into root memory, then each member of the FOREIGN_SERVER
+ struct is populated. A char pointer takes the return value of get_field
+ for each column we're interested in obtaining, and if that pointer
+ isn't 0x0, the FOREIGN_SERVER member is set to that value, otherwise,
+ is set to the value of an empty string, since get_field would set it to
+ 0x0 if the column's value is empty, even if the default value for that
+ column is NOT NULL.
+
+ RETURN VALUES
+ 0 ok
+ 1 could not insert server struct into global servers cache
+*/
+
+static bool
+get_server_from_table_to_cache(TABLE *table)
+{
+ /* alloc a server struct */
+ char *ptr;
+ char * const blank= (char*)"";
+ FOREIGN_SERVER *server= (FOREIGN_SERVER *)alloc_root(&mem,
+ sizeof(FOREIGN_SERVER));
+ DBUG_ENTER("get_server_from_table_to_cache");
+ table->use_all_columns();
+
+ /* get each field into the server struct ptr */
+ server->server_name= get_field(&mem, table->field[0]);
+ server->server_name_length= (uint) strlen(server->server_name);
+ ptr= get_field(&mem, table->field[1]);
+ server->host= ptr ? ptr : blank;
+ ptr= get_field(&mem, table->field[2]);
+ server->db= ptr ? ptr : blank;
+ ptr= get_field(&mem, table->field[3]);
+ server->username= ptr ? ptr : blank;
+ ptr= get_field(&mem, table->field[4]);
+ server->password= ptr ? ptr : blank;
+ ptr= get_field(&mem, table->field[5]);
+ server->sport= ptr ? ptr : blank;
+
+ server->port= server->sport ? atoi(server->sport) : 0;
+
+ ptr= get_field(&mem, table->field[6]);
+ server->socket= ptr && strlen(ptr) ? ptr : blank;
+ ptr= get_field(&mem, table->field[7]);
+ server->scheme= ptr ? ptr : blank;
+ ptr= get_field(&mem, table->field[8]);
+ server->owner= ptr ? ptr : blank;
+ DBUG_PRINT("info", ("server->server_name %s", server->server_name));
+ DBUG_PRINT("info", ("server->host %s", server->host));
+ DBUG_PRINT("info", ("server->db %s", server->db));
+ DBUG_PRINT("info", ("server->username %s", server->username));
+ DBUG_PRINT("info", ("server->password %s", server->password));
+ DBUG_PRINT("info", ("server->socket %s", server->socket));
+ if (my_hash_insert(&servers_cache, (uchar*) server))
+ {
+ DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
+ server->server_name, (long unsigned int) server));
+ // error handling needed here
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ SYNOPSIS
+ insert_server()
+ THD *thd - thread pointer
+ FOREIGN_SERVER *server - pointer to prepared FOREIGN_SERVER struct
+
+ NOTES
+ This function takes a server object that is has all members properly
+ prepared, ready to be inserted both into the mysql.servers table and
+ the servers cache.
+
+ THR_LOCK_servers must be write locked.
+
+ RETURN VALUES
+ 0 - no error
+ other - error code
+*/
+
+static int
+insert_server(THD *thd, FOREIGN_SERVER *server)
+{
+ int error= -1;
+ TABLE_LIST tables;
+ TABLE *table;
+
+ DBUG_ENTER("insert_server");
+
+ tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE);
+
+ /* need to open before acquiring THR_LOCK_plugin or it will deadlock */
+ if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ goto end;
+
+ /* insert the server into the table */
+ if ((error= insert_server_record(table, server)))
+ goto end;
+
+ /* insert the server into the cache */
+ if ((error= insert_server_record_into_cache(server)))
+ goto end;
+
+end:
+ DBUG_RETURN(error);
+}
+
+
+/*
+ SYNOPSIS
+ int insert_server_record_into_cache()
+ FOREIGN_SERVER *server
+
+ NOTES
+ This function takes a FOREIGN_SERVER pointer to an allocated (root mem)
+ and inserts it into the global servers cache
+
+ THR_LOCK_servers must be write locked.
+
+ RETURN VALUE
+ 0 - no error
+ >0 - error code
+
+*/
+
+static int
+insert_server_record_into_cache(FOREIGN_SERVER *server)
+{
+ int error=0;
+ DBUG_ENTER("insert_server_record_into_cache");
+ /*
+ We succeded in insertion of the server to the table, now insert
+ the server to the cache
+ */
+ DBUG_PRINT("info", ("inserting server %s at %lx, length %d",
+ server->server_name, (long unsigned int) server,
+ server->server_name_length));
+ if (my_hash_insert(&servers_cache, (uchar*) server))
+ {
+ DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
+ server->server_name, (long unsigned int) server));
+ // error handling needed here
+ error= 1;
+ }
+ DBUG_RETURN(error);
+}
+
+
+/*
+ SYNOPSIS
+ store_server_fields()
+ TABLE *table
+ FOREIGN_SERVER *server
+
+ NOTES
+ This function takes an opened table object, and a pointer to an
+ allocated FOREIGN_SERVER struct, and then stores each member of
+ the FOREIGN_SERVER to the appropriate fields in the table, in
+ advance of insertion into the mysql.servers table
+
+ RETURN VALUE
+ VOID
+
+*/
+
+static void
+store_server_fields(TABLE *table, FOREIGN_SERVER *server)
+{
+
+ table->use_all_columns();
+ /*
+ "server" has already been prepped by prepare_server_struct_for_<>
+ so, all we need to do is check if the value is set (> -1 for port)
+
+ If this happens to be an update, only the server members that
+ have changed will be set. If an insert, then all will be set,
+ even if with empty strings
+ */
+ if (server->host)
+ table->field[1]->store(server->host,
+ (uint) strlen(server->host), system_charset_info);
+ if (server->db)
+ table->field[2]->store(server->db,
+ (uint) strlen(server->db), system_charset_info);
+ if (server->username)
+ table->field[3]->store(server->username,
+ (uint) strlen(server->username), system_charset_info);
+ if (server->password)
+ table->field[4]->store(server->password,
+ (uint) strlen(server->password), system_charset_info);
+ if (server->port > -1)
+ table->field[5]->store(server->port);
+
+ if (server->socket)
+ table->field[6]->store(server->socket,
+ (uint) strlen(server->socket), system_charset_info);
+ if (server->scheme)
+ table->field[7]->store(server->scheme,
+ (uint) strlen(server->scheme), system_charset_info);
+ if (server->owner)
+ table->field[8]->store(server->owner,
+ (uint) strlen(server->owner), system_charset_info);
+}
+
+/*
+ SYNOPSIS
+ insert_server_record()
+ TABLE *table
+ FOREIGN_SERVER *server
+
+ NOTES
+ This function takes the arguments of an open table object and a pointer
+ to an allocated FOREIGN_SERVER struct. It stores the server_name into
+ the first field of the table (the primary key, server_name column). With
+ this, index_read_idx is called, if the record is found, an error is set
+ to ER_FOREIGN_SERVER_EXISTS (the server with that server name exists in the
+ table), if not, then store_server_fields stores all fields of the
+ FOREIGN_SERVER to the table, then ha_write_row is inserted. If an error
+ is encountered in either index_read_idx or ha_write_row, then that error
+ is returned
+
+ RETURN VALUE
+ 0 - no errors
+ >0 - error code
+
+ */
+
+static
+int insert_server_record(TABLE *table, FOREIGN_SERVER *server)
+{
+ int error;
+ DBUG_ENTER("insert_server_record");
+ tmp_disable_binlog(table->in_use);
+ table->use_all_columns();
+
+ empty_record(table);
+
+ /* set the field that's the PK to the value we're looking for */
+ table->field[0]->store(server->server_name,
+ server->server_name_length,
+ system_charset_info);
+
+ /* read index until record is that specified in server_name */
+ if ((error= table->file->ha_index_read_idx_map(table->record[0], 0,
+ (uchar *)table->field[0]->ptr,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT)))
+ {
+ /* if not found, err */
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ {
+ table->file->print_error(error, MYF(0));
+ error= 1;
+ }
+ /* store each field to be inserted */
+ store_server_fields(table, server);
+
+ DBUG_PRINT("info",("record for server '%s' not found!",
+ server->server_name));
+ /* write/insert the new server */
+ if ((error=table->file->ha_write_row(table->record[0])))
+ {
+ table->file->print_error(error, MYF(0));
+ }
+ else
+ error= 0;
+ }
+ else
+ error= ER_FOREIGN_SERVER_EXISTS;
+
+ reenable_binlog(table->in_use);
+ DBUG_RETURN(error);
+}
+
+/*
+ SYNOPSIS
+ drop_server()
+ THD *thd
+ LEX_SERVER_OPTIONS *server_options
+
+ NOTES
+ This function takes as its arguments a THD object pointer and a pointer
+ to a LEX_SERVER_OPTIONS struct from the parser. The member 'server_name'
+ of this LEX_SERVER_OPTIONS struct contains the value of the server to be
+ deleted. The mysql.servers table is opened via open_ltable, a table object
+ returned, the servers cache mutex locked, then delete_server_record is
+ called with this table object and LEX_SERVER_OPTIONS server_name and
+ server_name_length passed, containing the name of the server to be
+ dropped/deleted, then delete_server_record_in_cache is called to delete
+ the server from the servers cache.
+
+ RETURN VALUE
+ 0 - no error
+ > 0 - error code
+*/
+
+int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
+{
+ int error;
+ TABLE_LIST tables;
+ TABLE *table;
+ LEX_STRING name= { server_options->server_name,
+ server_options->server_name_length };
+
+ DBUG_ENTER("drop_server");
+ DBUG_PRINT("info", ("server name server->server_name %s",
+ server_options->server_name));
+
+ tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE);
+
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+
+ /* hit the memory hit first */
+ if ((error= delete_server_record_in_cache(server_options)))
+ goto end;
+
+ if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ {
+ error= my_errno;
+ goto end;
+ }
+
+ error= delete_server_record(table, name.str, name.length);
+
+ /* close the servers table before we call closed_cached_connection_tables */
+ close_mysql_tables(thd);
+
+ if (close_cached_connection_tables(thd, &name))
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR, "Server connection in use");
+ }
+
+end:
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ delete_server_record_in_cache()
+ LEX_SERVER_OPTIONS *server_options
+
+ NOTES
+ This function's argument is a LEX_SERVER_OPTIONS struct pointer. This
+ function uses the "server_name" and "server_name_length" members of the
+ lex->server_options to search for the server in the servers_cache. Upon
+ returned the server (pointer to a FOREIGN_SERVER struct), it then deletes
+ that server from the servers_cache hash.
+
+ RETURN VALUE
+ 0 - no error
+
+*/
+
+static int
+delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options)
+{
+ int error= ER_FOREIGN_SERVER_DOESNT_EXIST;
+ FOREIGN_SERVER *server;
+ DBUG_ENTER("delete_server_record_in_cache");
+
+ DBUG_PRINT("info",("trying to obtain server name %s length %d",
+ server_options->server_name,
+ server_options->server_name_length));
+
+
+ if (!(server= (FOREIGN_SERVER *)
+ my_hash_search(&servers_cache,
+ (uchar*) server_options->server_name,
+ server_options->server_name_length)))
+ {
+ DBUG_PRINT("info", ("server_name %s length %d not found!",
+ server_options->server_name,
+ server_options->server_name_length));
+ goto end;
+ }
+ /*
+ We succeded in deletion of the server to the table, now delete
+ the server from the cache
+ */
+ DBUG_PRINT("info",("deleting server %s length %d",
+ server->server_name,
+ server->server_name_length));
+
+ my_hash_delete(&servers_cache, (uchar*) server);
+
+ error= 0;
+
+end:
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ update_server()
+ THD *thd
+ FOREIGN_SERVER *existing
+ FOREIGN_SERVER *altered
+
+ NOTES
+ This function takes as arguments a THD object pointer, and two pointers,
+ one pointing to the existing FOREIGN_SERVER struct "existing" (which is
+ the current record as it is) and another pointer pointing to the
+ FOREIGN_SERVER struct with the members containing the modified/altered
+ values that need to be updated in both the mysql.servers table and the
+ servers_cache. It opens a table, passes the table and the altered
+ FOREIGN_SERVER pointer, which will be used to update the mysql.servers
+ table for the particular server via the call to update_server_record,
+ and in the servers_cache via update_server_record_in_cache.
+
+ THR_LOCK_servers must be write locked.
+
+ RETURN VALUE
+ 0 - no error
+ >0 - error code
+
+*/
+
+int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered)
+{
+ int error;
+ TABLE *table;
+ TABLE_LIST tables;
+ DBUG_ENTER("update_server");
+
+ tables.init_one_table("mysql", 5, "servers", 7, "servers",
+ TL_WRITE);
+
+ if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ {
+ error= my_errno;
+ goto end;
+ }
+
+ if ((error= update_server_record(table, altered)))
+ goto end;
+
+ error= update_server_record_in_cache(existing, altered);
+
+ /*
+ Perform a reload so we don't have a 'hole' in our mem_root
+ */
+ servers_load(thd, &tables);
+
+end:
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ update_server_record_in_cache()
+ FOREIGN_SERVER *existing
+ FOREIGN_SERVER *altered
+
+ NOTES
+ This function takes as an argument the FOREIGN_SERVER structi pointer
+ for the existing server and the FOREIGN_SERVER struct populated with only
+ the members which have been updated. It then "merges" the "altered" struct
+ members to the existing server, the existing server then represents an
+ updated server. Then, the existing record is deleted from the servers_cache
+ HASH, then the updated record inserted, in essence replacing the old
+ record.
+
+ THR_LOCK_servers must be write locked.
+
+ RETURN VALUE
+ 0 - no error
+ 1 - error
+
+*/
+
+int update_server_record_in_cache(FOREIGN_SERVER *existing,
+ FOREIGN_SERVER *altered)
+{
+ int error= 0;
+ DBUG_ENTER("update_server_record_in_cache");
+
+ /*
+ update the members that haven't been change in the altered server struct
+ with the values of the existing server struct
+ */
+ merge_server_struct(existing, altered);
+
+ /*
+ delete the existing server struct from the server cache
+ */
+ my_hash_delete(&servers_cache, (uchar*)existing);
+
+ /*
+ Insert the altered server struct into the server cache
+ */
+ if (my_hash_insert(&servers_cache, (uchar*)altered))
+ {
+ DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
+ altered->server_name, (long unsigned int) altered));
+ error= ER_OUT_OF_RESOURCES;
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ merge_server_struct()
+ FOREIGN_SERVER *from
+ FOREIGN_SERVER *to
+
+ NOTES
+ This function takes as its arguments two pointers each to an allocated
+ FOREIGN_SERVER struct. The first FOREIGN_SERVER struct represents the struct
+ that we will obtain values from (hence the name "from"), the second
+ FOREIGN_SERVER struct represents which FOREIGN_SERVER struct we will be
+ "copying" any members that have a value to (hence the name "to")
+
+ RETURN VALUE
+ VOID
+
+*/
+
+void merge_server_struct(FOREIGN_SERVER *from, FOREIGN_SERVER *to)
+{
+ DBUG_ENTER("merge_server_struct");
+ if (!to->host)
+ to->host= strdup_root(&mem, from->host);
+ if (!to->db)
+ to->db= strdup_root(&mem, from->db);
+ if (!to->username)
+ to->username= strdup_root(&mem, from->username);
+ if (!to->password)
+ to->password= strdup_root(&mem, from->password);
+ if (to->port == -1)
+ to->port= from->port;
+ if (!to->socket && from->socket)
+ to->socket= strdup_root(&mem, from->socket);
+ if (!to->scheme && from->scheme)
+ to->scheme= strdup_root(&mem, from->scheme);
+ if (!to->owner)
+ to->owner= strdup_root(&mem, from->owner);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+
+ SYNOPSIS
+ update_server_record()
+ TABLE *table
+ FOREIGN_SERVER *server
+
+ NOTES
+ This function takes as its arguments an open TABLE pointer, and a pointer
+ to an allocated FOREIGN_SERVER structure representing an updated record
+ which needs to be inserted. The primary key, server_name is stored to field
+ 0, then index_read_idx is called to read the index to that record, the
+ record then being ready to be updated, if found. If not found an error is
+ set and error message printed. If the record is found, store_record is
+ called, then store_server_fields stores each field from the the members of
+ the updated FOREIGN_SERVER struct.
+
+ RETURN VALUE
+ 0 - no error
+
+*/
+
+
+static int
+update_server_record(TABLE *table, FOREIGN_SERVER *server)
+{
+ int error=0;
+ DBUG_ENTER("update_server_record");
+ tmp_disable_binlog(table->in_use);
+ table->use_all_columns();
+ /* set the field that's the PK to the value we're looking for */
+ table->field[0]->store(server->server_name,
+ server->server_name_length,
+ system_charset_info);
+
+ if ((error= table->file->ha_index_read_idx_map(table->record[0], 0,
+ (uchar *)table->field[0]->ptr,
+ ~(longlong)0,
+ HA_READ_KEY_EXACT)))
+ {
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ table->file->print_error(error, MYF(0));
+ DBUG_PRINT("info",("server not found!"));
+ error= ER_FOREIGN_SERVER_DOESNT_EXIST;
+ }
+ else
+ {
+ /* ok, so we can update since the record exists in the table */
+ store_record(table,record[1]);
+ store_server_fields(table, server);
+ if ((error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ {
+ DBUG_PRINT("info",("problems with ha_update_row %d", error));
+ goto end;
+ }
+ else
+ error= 0;
+ }
+
+end:
+ reenable_binlog(table->in_use);
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ delete_server_record()
+ TABLE *table
+ char *server_name
+ int server_name_length
+
+ NOTES
+
+ RETURN VALUE
+ 0 - no error
+
+*/
+
+static int
+delete_server_record(TABLE *table,
+ char *server_name, size_t server_name_length)
+{
+ int error;
+ DBUG_ENTER("delete_server_record");
+ tmp_disable_binlog(table->in_use);
+ table->use_all_columns();
+
+ /* set the field that's the PK to the value we're looking for */
+ table->field[0]->store(server_name, server_name_length, system_charset_info);
+
+ if ((error= table->file->ha_index_read_idx_map(table->record[0], 0,
+ (uchar *)table->field[0]->ptr,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT)))
+ {
+ if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ table->file->print_error(error, MYF(0));
+ DBUG_PRINT("info",("server not found!"));
+ error= ER_FOREIGN_SERVER_DOESNT_EXIST;
+ }
+ else
+ {
+ if ((error= table->file->ha_delete_row(table->record[0])))
+ table->file->print_error(error, MYF(0));
+ }
+
+ reenable_binlog(table->in_use);
+ DBUG_RETURN(error);
+}
+
+/*
+
+ SYNOPSIS
+ create_server()
+ THD *thd
+ LEX_SERVER_OPTIONS *server_options
+
+ NOTES
+
+ RETURN VALUE
+ 0 - no error
+
+*/
+
+int create_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
+{
+ int error= ER_FOREIGN_SERVER_EXISTS;
+ FOREIGN_SERVER *server;
+
+ DBUG_ENTER("create_server");
+ DBUG_PRINT("info", ("server_options->server_name %s",
+ server_options->server_name));
+
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+
+ /* hit the memory first */
+ if (my_hash_search(&servers_cache, (uchar*) server_options->server_name,
+ server_options->server_name_length))
+ goto end;
+
+
+ if (!(server= prepare_server_struct_for_insert(server_options)))
+ {
+ /* purecov: begin inspected */
+ error= ER_OUT_OF_RESOURCES;
+ goto end;
+ /* purecov: end */
+ }
+
+ error= insert_server(thd, server);
+
+ DBUG_PRINT("info", ("error returned %d", error));
+
+end:
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ alter_server()
+ THD *thd
+ LEX_SERVER_OPTIONS *server_options
+
+ NOTES
+
+ RETURN VALUE
+ 0 - no error
+
+*/
+
+int alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
+{
+ int error= ER_FOREIGN_SERVER_DOESNT_EXIST;
+ FOREIGN_SERVER *altered, *existing;
+ LEX_STRING name= { server_options->server_name,
+ server_options->server_name_length };
+ DBUG_ENTER("alter_server");
+ DBUG_PRINT("info", ("server_options->server_name %s",
+ server_options->server_name));
+
+ mysql_rwlock_wrlock(&THR_LOCK_servers);
+
+ if (!(existing= (FOREIGN_SERVER *) my_hash_search(&servers_cache,
+ (uchar*) name.str,
+ name.length)))
+ goto end;
+
+ altered= (FOREIGN_SERVER *)alloc_root(&mem,
+ sizeof(FOREIGN_SERVER));
+
+ prepare_server_struct_for_update(server_options, existing, altered);
+
+ error= update_server(thd, existing, altered);
+
+ /* close the servers table before we call closed_cached_connection_tables */
+ close_mysql_tables(thd);
+
+ if (close_cached_connection_tables(thd, &name))
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_ERROR, "Server connection in use");
+ }
+
+end:
+ DBUG_PRINT("info", ("error returned %d", error));
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ DBUG_RETURN(error);
+}
+
+
+/*
+
+ SYNOPSIS
+ prepare_server_struct_for_insert()
+ LEX_SERVER_OPTIONS *server_options
+
+ NOTES
+ As FOREIGN_SERVER members are allocated on mem_root, we do not need to
+ free them in case of error.
+
+ RETURN VALUE
+ On success filled FOREIGN_SERVER, or NULL in case out of memory.
+
+*/
+
+static FOREIGN_SERVER *
+prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options)
+{
+ char *unset_ptr= (char*)"";
+ FOREIGN_SERVER *server;
+ DBUG_ENTER("prepare_server_struct");
+
+ if (!(server= (FOREIGN_SERVER *)alloc_root(&mem, sizeof(FOREIGN_SERVER))))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ /* these two MUST be set */
+ if (!(server->server_name= strdup_root(&mem, server_options->server_name)))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+ server->server_name_length= server_options->server_name_length;
+
+ if (!(server->host= server_options->host ?
+ strdup_root(&mem, server_options->host) : unset_ptr))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ if (!(server->db= server_options->db ?
+ strdup_root(&mem, server_options->db) : unset_ptr))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ if (!(server->username= server_options->username ?
+ strdup_root(&mem, server_options->username) : unset_ptr))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ if (!(server->password= server_options->password ?
+ strdup_root(&mem, server_options->password) : unset_ptr))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ /* set to 0 if not specified */
+ server->port= server_options->port > -1 ?
+ server_options->port : 0;
+
+ if (!(server->socket= server_options->socket ?
+ strdup_root(&mem, server_options->socket) : unset_ptr))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ if (!(server->scheme= server_options->scheme ?
+ strdup_root(&mem, server_options->scheme) : unset_ptr))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ if (!(server->owner= server_options->owner ?
+ strdup_root(&mem, server_options->owner) : unset_ptr))
+ DBUG_RETURN(NULL); /* purecov: inspected */
+
+ DBUG_RETURN(server);
+}
+
+/*
+
+ SYNOPSIS
+ prepare_server_struct_for_update()
+ LEX_SERVER_OPTIONS *server_options
+
+ NOTES
+
+ RETURN VALUE
+ 0 - no error
+
+*/
+
+static void
+prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options,
+ FOREIGN_SERVER *existing,
+ FOREIGN_SERVER *altered)
+{
+ DBUG_ENTER("prepare_server_struct_for_update");
+
+ altered->server_name= strdup_root(&mem, server_options->server_name);
+ altered->server_name_length= server_options->server_name_length;
+ DBUG_PRINT("info", ("existing name %s altered name %s",
+ existing->server_name, altered->server_name));
+
+ /*
+ The logic here is this: is this value set AND is it different
+ than the existing value?
+ */
+ altered->host=
+ (server_options->host && (strcmp(server_options->host, existing->host))) ?
+ strdup_root(&mem, server_options->host) : 0;
+
+ altered->db=
+ (server_options->db && (strcmp(server_options->db, existing->db))) ?
+ strdup_root(&mem, server_options->db) : 0;
+
+ altered->username=
+ (server_options->username &&
+ (strcmp(server_options->username, existing->username))) ?
+ strdup_root(&mem, server_options->username) : 0;
+
+ altered->password=
+ (server_options->password &&
+ (strcmp(server_options->password, existing->password))) ?
+ strdup_root(&mem, server_options->password) : 0;
+
+ /*
+ port is initialised to -1, so if unset, it will be -1
+ */
+ altered->port= (server_options->port > -1 &&
+ server_options->port != existing->port) ?
+ server_options->port : -1;
+
+ altered->socket=
+ (server_options->socket &&
+ (strcmp(server_options->socket, existing->socket))) ?
+ strdup_root(&mem, server_options->socket) : 0;
+
+ altered->scheme=
+ (server_options->scheme &&
+ (strcmp(server_options->scheme, existing->scheme))) ?
+ strdup_root(&mem, server_options->scheme) : 0;
+
+ altered->owner=
+ (server_options->owner &&
+ (strcmp(server_options->owner, existing->owner))) ?
+ strdup_root(&mem, server_options->owner) : 0;
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+
+ SYNOPSIS
+ servers_free()
+ bool end
+
+ NOTES
+
+ RETURN VALUE
+ void
+
+*/
+
+void servers_free(bool end)
+{
+ DBUG_ENTER("servers_free");
+ if (!my_hash_inited(&servers_cache))
+ DBUG_VOID_RETURN;
+ if (!end)
+ {
+ free_root(&mem, MYF(MY_MARK_BLOCKS_FREE));
+ my_hash_reset(&servers_cache);
+ DBUG_VOID_RETURN;
+ }
+ mysql_rwlock_destroy(&THR_LOCK_servers);
+ free_root(&mem,MYF(0));
+ my_hash_free(&servers_cache);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ SYNOPSIS
+
+ clone_server(MEM_ROOT *mem_root, FOREIGN_SERVER *orig, FOREIGN_SERVER *buff)
+
+ Create a clone of FOREIGN_SERVER. If the supplied mem_root is of
+ thd->mem_root then the copy is automatically disposed at end of statement.
+
+ NOTES
+
+ ARGS
+ MEM_ROOT pointer (strings are copied into this mem root)
+ FOREIGN_SERVER pointer (made a copy of)
+ FOREIGN_SERVER buffer (if not-NULL, this pointer is returned)
+
+ RETURN VALUE
+ FOREIGN_SEVER pointer (copy of one supplied FOREIGN_SERVER)
+*/
+
+static FOREIGN_SERVER *clone_server(MEM_ROOT *mem, const FOREIGN_SERVER *server,
+ FOREIGN_SERVER *buffer)
+{
+ DBUG_ENTER("sql_server.cc:clone_server");
+
+ if (!buffer)
+ buffer= (FOREIGN_SERVER *) alloc_root(mem, sizeof(FOREIGN_SERVER));
+
+ buffer->server_name= strmake_root(mem, server->server_name,
+ server->server_name_length);
+ buffer->port= server->port;
+ buffer->server_name_length= server->server_name_length;
+
+ /* TODO: We need to examine which of these can really be NULL */
+ buffer->db= server->db ? strdup_root(mem, server->db) : NULL;
+ buffer->scheme= server->scheme ? strdup_root(mem, server->scheme) : NULL;
+ buffer->username= server->username? strdup_root(mem, server->username): NULL;
+ buffer->password= server->password? strdup_root(mem, server->password): NULL;
+ buffer->socket= server->socket ? strdup_root(mem, server->socket) : NULL;
+ buffer->owner= server->owner ? strdup_root(mem, server->owner) : NULL;
+ buffer->host= server->host ? strdup_root(mem, server->host) : NULL;
+
+ DBUG_RETURN(buffer);
+}
+
+
+/*
+
+ SYNOPSIS
+ get_server_by_name()
+ const char *server_name
+
+ NOTES
+
+ RETURN VALUE
+ FOREIGN_SERVER *
+
+*/
+
+FOREIGN_SERVER *get_server_by_name(MEM_ROOT *mem, const char *server_name,
+ FOREIGN_SERVER *buff)
+{
+ size_t server_name_length;
+ FOREIGN_SERVER *server;
+ DBUG_ENTER("get_server_by_name");
+ DBUG_PRINT("info", ("server_name %s", server_name));
+
+ server_name_length= strlen(server_name);
+
+ if (! server_name || !strlen(server_name))
+ {
+ DBUG_PRINT("info", ("server_name not defined!"));
+ DBUG_RETURN((FOREIGN_SERVER *)NULL);
+ }
+
+ DBUG_PRINT("info", ("locking servers_cache"));
+ mysql_rwlock_rdlock(&THR_LOCK_servers);
+ if (!(server= (FOREIGN_SERVER *) my_hash_search(&servers_cache,
+ (uchar*) server_name,
+ server_name_length)))
+ {
+ DBUG_PRINT("info", ("server_name %s length %u not found!",
+ server_name, (unsigned) server_name_length));
+ server= (FOREIGN_SERVER *) NULL;
+ }
+ /* otherwise, make copy of server */
+ else
+ server= clone_server(mem, server, buff);
+
+ DBUG_PRINT("info", ("unlocking servers_cache"));
+ mysql_rwlock_unlock(&THR_LOCK_servers);
+ DBUG_RETURN(server);
+
+}
diff --git a/sql/sql_servers.h b/sql/sql_servers.h
new file mode 100644
index 00000000000..a6186a85ae2
--- /dev/null
+++ b/sql/sql_servers.h
@@ -0,0 +1,53 @@
+#ifndef SQL_SERVERS_INCLUDED
+#define SQL_SERVERS_INCLUDED
+
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "my_global.h" /* uint */
+#include "slave.h" // for tables_ok(), rpl_filter
+
+class THD;
+typedef struct st_lex_server_options LEX_SERVER_OPTIONS;
+typedef struct st_mem_root MEM_ROOT;
+
+/* structs */
+typedef struct st_federated_server
+{
+ char *server_name;
+ long port;
+ uint server_name_length;
+ char *db, *scheme, *username, *password, *socket, *owner, *host, *sport;
+} FOREIGN_SERVER;
+
+/* cache handlers */
+bool servers_init(bool dont_read_server_table);
+bool servers_reload(THD *thd);
+void servers_free(bool end=0);
+
+/* insert functions */
+int create_server(THD *thd, LEX_SERVER_OPTIONS *server_options);
+
+/* drop functions */
+int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options);
+
+/* update functions */
+int alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options);
+
+/* lookup functions */
+FOREIGN_SERVER *get_server_by_name(MEM_ROOT *mem, const char *server_name,
+ FOREIGN_SERVER *server_buffer);
+
+#endif /* SQL_SERVERS_INCLUDED */
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
new file mode 100644
index 00000000000..b566029740a
--- /dev/null
+++ b/sql/sql_show.cc
@@ -0,0 +1,9626 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, SkySQL Ab.
+
+ 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 */
+
+
+/* Function with list databases, tables or fields */
+
+#include "sql_plugin.h" // Includes my_global.h
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_acl.h" // fill_schema_*_privileges
+#include "sql_select.h" // For select_describe
+#include "sql_base.h" // close_tables_for_reopen
+#include "create_options.h"
+#include "sql_show.h"
+#include "sql_table.h" // filename_to_tablename,
+ // primary_key_name,
+ // build_table_filename
+#include "repl_failsafe.h"
+#include "sql_parse.h" // check_access, check_table_access
+#include "sql_partition.h" // partition_element
+#include "sql_derived.h" // mysql_derived_prepare,
+ // mysql_handle_derived,
+#include "sql_db.h" // check_db_dir_existence, load_db_opt_by_name
+#include "sql_time.h" // interval_type_to_name
+#include "tztime.h" // struct Time_zone
+#include "sql_acl.h" // TABLE_ACLS, check_grant, DB_ACLS, acl_get,
+ // check_grant_db
+#include "filesort.h" // filesort_free_buffers
+#include "sp.h"
+#include "sp_head.h"
+#include "sp_pcontext.h"
+#include "set_var.h"
+#include "sql_trigger.h"
+#include "sql_derived.h"
+#include "sql_statistics.h"
+#include "sql_connect.h"
+#include "authors.h"
+#include "contributors.h"
+#include "sql_partition.h"
+#ifdef HAVE_EVENT_SCHEDULER
+#include "events.h"
+#include "event_data_objects.h"
+#endif
+#include <my_dir.h>
+#include "lock.h" // MYSQL_OPEN_IGNORE_FLUSH
+#include "debug_sync.h"
+#include "keycaches.h"
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+#include "ha_partition.h"
+#endif
+enum enum_i_s_events_fields
+{
+ ISE_EVENT_CATALOG= 0,
+ ISE_EVENT_SCHEMA,
+ ISE_EVENT_NAME,
+ ISE_DEFINER,
+ ISE_TIME_ZONE,
+ ISE_EVENT_BODY,
+ ISE_EVENT_DEFINITION,
+ ISE_EVENT_TYPE,
+ ISE_EXECUTE_AT,
+ ISE_INTERVAL_VALUE,
+ ISE_INTERVAL_FIELD,
+ ISE_SQL_MODE,
+ ISE_STARTS,
+ ISE_ENDS,
+ ISE_STATUS,
+ ISE_ON_COMPLETION,
+ ISE_CREATED,
+ ISE_LAST_ALTERED,
+ ISE_LAST_EXECUTED,
+ ISE_EVENT_COMMENT,
+ ISE_ORIGINATOR,
+ ISE_CLIENT_CS,
+ ISE_CONNECTION_CL,
+ ISE_DB_CL
+};
+
+#define USERNAME_WITH_HOST_CHAR_LENGTH (USERNAME_CHAR_LENGTH + HOSTNAME_LENGTH + 2)
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+static const char *grant_names[]={
+ "select","insert","update","delete","create","drop","reload","shutdown",
+ "process","file","grant","references","index","alter"};
+
+static TYPELIB grant_types = { sizeof(grant_names)/sizeof(char **),
+ "grant_types",
+ grant_names, NULL};
+#endif
+
+/* Match the values of enum ha_choice */
+static const char *ha_choice_values[] = {"", "0", "1"};
+
+static void store_key_options(THD *thd, String *packet, TABLE *table,
+ KEY *key_info);
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+static void get_cs_converted_string_value(THD *thd,
+ String *input_str,
+ String *output_str,
+ CHARSET_INFO *cs,
+ bool use_hex);
+#endif
+
+static int show_create_view(THD *thd, TABLE_LIST *table, String *buff);
+
+static void append_algorithm(TABLE_LIST *table, String *buff);
+
+static COND * make_cond_for_info_schema(COND *cond, TABLE_LIST *table);
+
+/**
+ Condition pushdown used for INFORMATION_SCHEMA / SHOW queries.
+ This structure is to implement an optimization when
+ accessing data dictionary data in the INFORMATION_SCHEMA
+ or SHOW commands.
+ When the query contain a TABLE_SCHEMA or TABLE_NAME clause,
+ narrow the search for data based on the constraints given.
+*/
+typedef struct st_lookup_field_values
+{
+ /**
+ Value of a TABLE_SCHEMA clause.
+ Note that this value length may exceed @c NAME_LEN.
+ @sa wild_db_value
+ */
+ LEX_STRING db_value;
+ /**
+ Value of a TABLE_NAME clause.
+ Note that this value length may exceed @c NAME_LEN.
+ @sa wild_table_value
+ */
+ LEX_STRING table_value;
+ /**
+ True when @c db_value is a LIKE clause,
+ false when @c db_value is an '=' clause.
+ */
+ bool wild_db_value;
+ /**
+ True when @c table_value is a LIKE clause,
+ false when @c table_value is an '=' clause.
+ */
+ bool wild_table_value;
+} LOOKUP_FIELD_VALUES;
+
+
+bool get_lookup_field_values(THD *, COND *, TABLE_LIST *, LOOKUP_FIELD_VALUES *);
+
+/***************************************************************************
+** List all table types supported
+***************************************************************************/
+
+static int make_version_string(char *buf, int buf_length, uint version)
+{
+ return my_snprintf(buf, buf_length, "%d.%d", version>>8,version&0xff);
+}
+
+
+static const LEX_STRING maturity_name[]={
+ { C_STRING_WITH_LEN("Unknown") },
+ { C_STRING_WITH_LEN("Experimental") },
+ { C_STRING_WITH_LEN("Alpha") },
+ { C_STRING_WITH_LEN("Beta") },
+ { C_STRING_WITH_LEN("Gamma") },
+ { C_STRING_WITH_LEN("Stable") }};
+
+
+static my_bool show_plugins(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ TABLE *table= (TABLE*) arg;
+ struct st_maria_plugin *plug= plugin_decl(plugin);
+ struct st_plugin_dl *plugin_dl= plugin_dlib(plugin);
+ CHARSET_INFO *cs= system_charset_info;
+ char version_buf[20];
+
+ restore_record(table, s->default_values);
+
+ table->field[0]->store(plugin_name(plugin)->str,
+ plugin_name(plugin)->length, cs);
+
+ table->field[1]->store(version_buf,
+ make_version_string(version_buf, sizeof(version_buf), plug->version),
+ cs);
+
+ switch (plugin_state(plugin)) {
+ case PLUGIN_IS_DELETED:
+ table->field[2]->store(STRING_WITH_LEN("DELETED"), cs);
+ break;
+ case PLUGIN_IS_UNINITIALIZED:
+ table->field[2]->store(STRING_WITH_LEN("INACTIVE"), cs);
+ break;
+ case PLUGIN_IS_READY:
+ table->field[2]->store(STRING_WITH_LEN("ACTIVE"), cs);
+ break;
+ case PLUGIN_IS_DISABLED:
+ table->field[2]->store(STRING_WITH_LEN("DISABLED"), cs);
+ break;
+ case PLUGIN_IS_FREED: // filtered in fill_plugins, used in fill_all_plugins
+ table->field[2]->store(STRING_WITH_LEN("NOT INSTALLED"), cs);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+
+ table->field[3]->store(plugin_type_names[plug->type].str,
+ plugin_type_names[plug->type].length,
+ cs);
+ table->field[4]->store(version_buf,
+ make_version_string(version_buf, sizeof(version_buf),
+ *(uint *)plug->info), cs);
+
+ if (plugin_dl)
+ {
+ table->field[5]->store(plugin_dl->dl.str, plugin_dl->dl.length, cs);
+ table->field[5]->set_notnull();
+ table->field[6]->store(version_buf,
+ make_version_string(version_buf, sizeof(version_buf),
+ plugin_dl->mariaversion),
+ cs);
+ table->field[6]->set_notnull();
+ }
+ else
+ {
+ table->field[5]->set_null();
+ table->field[6]->set_null();
+ }
+
+
+ if (plug->author)
+ {
+ table->field[7]->store(plug->author, strlen(plug->author), cs);
+ table->field[7]->set_notnull();
+ }
+ else
+ table->field[7]->set_null();
+
+ if (plug->descr)
+ {
+ table->field[8]->store(plug->descr, strlen(plug->descr), cs);
+ table->field[8]->set_notnull();
+ }
+ else
+ table->field[8]->set_null();
+
+ switch (plug->license) {
+ case PLUGIN_LICENSE_GPL:
+ table->field[9]->store(PLUGIN_LICENSE_GPL_STRING,
+ strlen(PLUGIN_LICENSE_GPL_STRING), cs);
+ break;
+ case PLUGIN_LICENSE_BSD:
+ table->field[9]->store(PLUGIN_LICENSE_BSD_STRING,
+ strlen(PLUGIN_LICENSE_BSD_STRING), cs);
+ break;
+ default:
+ table->field[9]->store(PLUGIN_LICENSE_PROPRIETARY_STRING,
+ strlen(PLUGIN_LICENSE_PROPRIETARY_STRING), cs);
+ break;
+ }
+
+ table->field[10]->store(
+ global_plugin_typelib_names[plugin_load_option(plugin)],
+ strlen(global_plugin_typelib_names[plugin_load_option(plugin)]),
+ cs);
+
+ if (plug->maturity <= MariaDB_PLUGIN_MATURITY_STABLE)
+ table->field[11]->store(maturity_name[plug->maturity].str,
+ maturity_name[plug->maturity].length,
+ cs);
+ else
+ table->field[11]->store("Unknown", 7, cs);
+
+ if (plug->version_info)
+ {
+ table->field[12]->store(plug->version_info,
+ strlen(plug->version_info), cs);
+ table->field[12]->set_notnull();
+ }
+ else
+ table->field[12]->set_null();
+
+ return schema_table_store_record(thd, table);
+}
+
+
+int fill_plugins(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("fill_plugins");
+ TABLE *table= tables->table;
+
+ if (plugin_foreach_with_mask(thd, show_plugins, MYSQL_ANY_PLUGIN,
+ ~PLUGIN_IS_FREED, table))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+
+int fill_all_plugins(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("fill_all_plugins");
+ TABLE *table= tables->table;
+ LOOKUP_FIELD_VALUES lookup;
+
+ if (get_lookup_field_values(thd, cond, tables, &lookup))
+ DBUG_RETURN(0);
+
+ if (lookup.db_value.str && !lookup.db_value.str[0])
+ DBUG_RETURN(0); // empty string never matches a valid SONAME
+
+ MY_DIR *dirp= my_dir(opt_plugin_dir, MY_THREAD_SPECIFIC);
+ if (!dirp)
+ {
+ my_error(ER_CANT_READ_DIR, MYF(0), opt_plugin_dir, my_errno);
+ DBUG_RETURN(1);
+ }
+
+ if (!lookup.db_value.str)
+ plugin_dl_foreach(thd, 0, show_plugins, table);
+
+ const char *wstr= lookup.db_value.str, *wend= wstr + lookup.db_value.length;
+ for (uint i=0; i < (uint) dirp->number_of_files; i++)
+ {
+ FILEINFO *file= dirp->dir_entry+i;
+ LEX_STRING dl= { file->name, strlen(file->name) };
+ const char *dlend= dl.str + dl.length;
+ const size_t so_ext_len= sizeof(SO_EXT) - 1;
+
+ if (strcasecmp(dlend - so_ext_len, SO_EXT))
+ continue;
+
+ if (lookup.db_value.str)
+ {
+ if (lookup.wild_db_value)
+ {
+ if (my_wildcmp(files_charset_info, dl.str, dlend, wstr, wend,
+ wild_prefix, wild_one, wild_many))
+ continue;
+ }
+ else
+ {
+ if (my_strnncoll(files_charset_info,
+ (uchar*)dl.str, dl.length,
+ (uchar*)lookup.db_value.str, lookup.db_value.length))
+ continue;
+ }
+ }
+
+ plugin_dl_foreach(thd, &dl, show_plugins, table);
+ thd->clear_error();
+ }
+
+ my_dirend(dirp);
+ DBUG_RETURN(0);
+}
+
+
+/***************************************************************************
+** List all Authors.
+** If you can update it, you get to be in it :)
+***************************************************************************/
+
+bool mysqld_show_authors(THD *thd)
+{
+ List<Item> field_list;
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("mysqld_show_authors");
+
+ field_list.push_back(new Item_empty_string("Name",40));
+ field_list.push_back(new Item_empty_string("Location",40));
+ field_list.push_back(new Item_empty_string("Comment",512));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ show_table_authors_st *authors;
+ for (authors= show_table_authors; authors->name; authors++)
+ {
+ protocol->prepare_for_resend();
+ protocol->store(authors->name, system_charset_info);
+ protocol->store(authors->location, system_charset_info);
+ protocol->store(authors->comment, system_charset_info);
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+ }
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+/***************************************************************************
+** List all Contributors.
+** Please get permission before updating
+***************************************************************************/
+
+bool mysqld_show_contributors(THD *thd)
+{
+ List<Item> field_list;
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("mysqld_show_contributors");
+
+ field_list.push_back(new Item_empty_string("Name",40));
+ field_list.push_back(new Item_empty_string("Location",40));
+ field_list.push_back(new Item_empty_string("Comment", 512));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ show_table_contributors_st *contributors;
+ for (contributors= show_table_contributors; contributors->name; contributors++)
+ {
+ protocol->prepare_for_resend();
+ protocol->store(contributors->name, system_charset_info);
+ protocol->store(contributors->location, system_charset_info);
+ protocol->store(contributors->comment, system_charset_info);
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+ }
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+/***************************************************************************
+ List all privileges supported
+***************************************************************************/
+
+struct show_privileges_st {
+ const char *privilege;
+ const char *context;
+ const char *comment;
+};
+
+static struct show_privileges_st sys_privileges[]=
+{
+ {"Alter", "Tables", "To alter the table"},
+ {"Alter routine", "Functions,Procedures", "To alter or drop stored functions/procedures"},
+ {"Create", "Databases,Tables,Indexes", "To create new databases and tables"},
+ {"Create routine","Databases","To use CREATE FUNCTION/PROCEDURE"},
+ {"Create temporary tables","Databases","To use CREATE TEMPORARY TABLE"},
+ {"Create view", "Tables", "To create new views"},
+ {"Create user", "Server Admin", "To create new users"},
+ {"Delete", "Tables", "To delete existing rows"},
+ {"Drop", "Databases,Tables", "To drop databases, tables, and views"},
+#ifdef HAVE_EVENT_SCHEDULER
+ {"Event","Server Admin","To create, alter, drop and execute events"},
+#endif
+ {"Execute", "Functions,Procedures", "To execute stored routines"},
+ {"File", "File access on server", "To read and write files on the server"},
+ {"Grant option", "Databases,Tables,Functions,Procedures", "To give to other users those privileges you possess"},
+ {"Index", "Tables", "To create or drop indexes"},
+ {"Insert", "Tables", "To insert data into tables"},
+ {"Lock tables","Databases","To use LOCK TABLES (together with SELECT privilege)"},
+ {"Process", "Server Admin", "To view the plain text of currently executing queries"},
+ {"Proxy", "Server Admin", "To make proxy user possible"},
+ {"References", "Databases,Tables", "To have references on tables"},
+ {"Reload", "Server Admin", "To reload or refresh tables, logs and privileges"},
+ {"Replication client","Server Admin","To ask where the slave or master servers are"},
+ {"Replication slave","Server Admin","To read binary log events from the master"},
+ {"Select", "Tables", "To retrieve rows from table"},
+ {"Show databases","Server Admin","To see all databases with SHOW DATABASES"},
+ {"Show view","Tables","To see views with SHOW CREATE VIEW"},
+ {"Shutdown","Server Admin", "To shut down the server"},
+ {"Super","Server Admin","To use KILL thread, SET GLOBAL, CHANGE MASTER, etc."},
+ {"Trigger","Tables", "To use triggers"},
+ {"Create tablespace", "Server Admin", "To create/alter/drop tablespaces"},
+ {"Update", "Tables", "To update existing rows"},
+ {"Usage","Server Admin","No privileges - allow connect only"},
+ {NullS, NullS, NullS}
+};
+
+bool mysqld_show_privileges(THD *thd)
+{
+ List<Item> field_list;
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("mysqld_show_privileges");
+
+ field_list.push_back(new Item_empty_string("Privilege",10));
+ field_list.push_back(new Item_empty_string("Context",15));
+ field_list.push_back(new Item_empty_string("Comment",NAME_CHAR_LEN));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ show_privileges_st *privilege= sys_privileges;
+ for (privilege= sys_privileges; privilege->privilege ; privilege++)
+ {
+ protocol->prepare_for_resend();
+ protocol->store(privilege->privilege, system_charset_info);
+ protocol->store(privilege->context, system_charset_info);
+ protocol->store(privilege->comment, system_charset_info);
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+ }
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+/** Hash of LEX_STRINGs used to search for ignored db directories. */
+static HASH ignore_db_dirs_hash;
+
+/**
+ An array of LEX_STRING pointers to collect the options at
+ option parsing time.
+*/
+static DYNAMIC_ARRAY ignore_db_dirs_array;
+
+/**
+ A value for the read only system variable to show a list of
+ ignored directories.
+*/
+char *opt_ignore_db_dirs= NULL;
+
+/**
+ This flag is ON if:
+ - the list of ignored directories is not empty
+
+ - and some of the ignored directory names
+ need no tablename-to-filename conversion.
+ Otherwise, if the name of the directory contains
+ unconditional characters like '+' or '.', they
+ never can match the database directory name. So the
+ db_name_is_in_ignore_db_dirs_list() can just return at once.
+*/
+static bool skip_ignored_dir_check= TRUE;
+
+/**
+ Sets up the data structures for collection of directories at option
+ processing time.
+ We need to collect the directories in an array first, because
+ we need the character sets initialized before setting up the hash.
+
+ @return state
+ @retval TRUE failed
+ @retval FALSE success
+*/
+
+bool
+ignore_db_dirs_init()
+{
+ return my_init_dynamic_array(&ignore_db_dirs_array, sizeof(LEX_STRING *),
+ 0, 0, MYF(0));
+}
+
+
+/**
+ Retrieves the key (the string itself) from the LEX_STRING hash members.
+
+ Needed by hash_init().
+
+ @param data the data element from the hash
+ @param out len_ret Placeholder to return the length of the key
+ @param unused
+ @return a pointer to the key
+*/
+
+static uchar *
+db_dirs_hash_get_key(const uchar *data, size_t *len_ret,
+ my_bool __attribute__((unused)))
+{
+ LEX_STRING *e= (LEX_STRING *) data;
+
+ *len_ret= e->length;
+ return (uchar *) e->str;
+}
+
+
+/**
+ Wrap a directory name into a LEX_STRING and push it to the array.
+
+ Called at option processing time for each --ignore-db-dir option.
+
+ @param path the name of the directory to push
+ @return state
+ @retval TRUE failed
+ @retval FALSE success
+*/
+
+bool
+push_ignored_db_dir(char *path)
+{
+ LEX_STRING *new_elt;
+ char *new_elt_buffer;
+ size_t path_len= strlen(path);
+
+ if (!path_len || path_len >= FN_REFLEN)
+ return true;
+
+ // No need to normalize, it's only a directory name, not a path.
+ if (!my_multi_malloc(0,
+ &new_elt, sizeof(LEX_STRING),
+ &new_elt_buffer, path_len + 1,
+ NullS))
+ return true;
+ new_elt->str= new_elt_buffer;
+ memcpy(new_elt_buffer, path, path_len);
+ new_elt_buffer[path_len]= 0;
+ new_elt->length= path_len;
+ return insert_dynamic(&ignore_db_dirs_array, (uchar*) &new_elt);
+}
+
+
+/**
+ Clean up the directory ignore options accumulated so far.
+
+ Called at option processing time for each --ignore-db-dir option
+ with an empty argument.
+*/
+
+void
+ignore_db_dirs_reset()
+{
+ LEX_STRING **elt;
+ while (NULL!= (elt= (LEX_STRING **) pop_dynamic(&ignore_db_dirs_array)))
+ if (elt && *elt)
+ my_free(*elt);
+}
+
+
+/**
+ Free the directory ignore option variables.
+
+ Called at server shutdown.
+*/
+
+void
+ignore_db_dirs_free()
+{
+ if (opt_ignore_db_dirs)
+ {
+ my_free(opt_ignore_db_dirs);
+ opt_ignore_db_dirs= NULL;
+ }
+ ignore_db_dirs_reset();
+ delete_dynamic(&ignore_db_dirs_array);
+ my_hash_free(&ignore_db_dirs_hash);
+}
+
+
+/**
+ Initialize the ignore db directories hash and status variable from
+ the options collected in the array.
+
+ Called when option processing is over and the server's in-memory
+ structures are fully initialized.
+
+ @return state
+ @retval TRUE failed
+ @retval FALSE success
+*/
+
+static void dispose_db_dir(void *ptr)
+{
+ my_free(ptr);
+}
+
+
+bool
+ignore_db_dirs_process_additions()
+{
+ ulong i;
+ size_t len;
+ char *ptr;
+ LEX_STRING *dir;
+
+
+ skip_ignored_dir_check= TRUE;
+
+ if (my_hash_init(&ignore_db_dirs_hash,
+ lower_case_table_names ?
+ character_set_filesystem : &my_charset_bin,
+ 0, 0, 0, db_dirs_hash_get_key,
+ dispose_db_dir,
+ HASH_UNIQUE))
+ return true;
+
+ /* len starts from 1 because of the terminating zero. */
+ len= 1;
+ for (i= 0; i < ignore_db_dirs_array.elements; i++)
+ {
+ get_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i);
+ len+= dir->length + 1; // +1 for the comma
+ if (skip_ignored_dir_check)
+ {
+ char buff[FN_REFLEN];
+ (void) tablename_to_filename(dir->str, buff, sizeof(buff));
+ skip_ignored_dir_check= strcmp(dir->str, buff) != 0;
+ }
+ }
+
+ /* No delimiter for the last directory. */
+ if (len > 1)
+ len--;
+
+ /* +1 the terminating zero */
+ ptr= opt_ignore_db_dirs= (char *) my_malloc(len + 1, MYF(0));
+ if (!ptr)
+ return true;
+
+ /* Make sure we have an empty string to start with. */
+ *ptr= 0;
+
+ for (i= 0; i < ignore_db_dirs_array.elements; i++)
+ {
+ get_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i);
+ if (my_hash_insert(&ignore_db_dirs_hash, (uchar *) dir))
+ return true;
+ ptr= strnmov(ptr, dir->str, dir->length);
+ if (i + 1 < ignore_db_dirs_array.elements)
+ ptr= strmov(ptr, ",");
+
+ /*
+ Set the transferred array element to NULL to avoid double free
+ in case of error.
+ */
+ dir= NULL;
+ set_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i);
+ }
+
+ /* make sure the string is terminated */
+ DBUG_ASSERT(ptr - opt_ignore_db_dirs <= (ptrdiff_t) len);
+ *ptr= 0;
+
+ /*
+ It's OK to empty the array here as the allocated elements are
+ referenced through the hash now.
+ */
+ reset_dynamic(&ignore_db_dirs_array);
+
+ return false;
+}
+
+
+/**
+ Check if a directory name is in the hash of ignored directories.
+
+ @return search result
+ @retval TRUE found
+ @retval FALSE not found
+*/
+
+static inline bool
+is_in_ignore_db_dirs_list(const char *directory)
+{
+ return ignore_db_dirs_hash.records &&
+ NULL != my_hash_search(&ignore_db_dirs_hash, (const uchar *) directory,
+ strlen(directory));
+}
+
+
+/**
+ Check if a database name is in the hash of ignored directories.
+
+ @return search result
+ @retval TRUE found
+ @retval FALSE not found
+*/
+
+bool
+db_name_is_in_ignore_db_dirs_list(const char *directory)
+{
+ char buff[FN_REFLEN];
+ uint buff_len;
+
+ if (skip_ignored_dir_check)
+ return 0;
+
+ buff_len= tablename_to_filename(directory, buff, sizeof(buff));
+
+ return my_hash_search(&ignore_db_dirs_hash, (uchar *) buff, buff_len)!=NULL;
+}
+
+enum find_files_result {
+ FIND_FILES_OK,
+ FIND_FILES_OOM,
+ FIND_FILES_DIR
+};
+
+/*
+ find_files() - find files in a given directory.
+
+ SYNOPSIS
+ find_files()
+ thd thread handler
+ files put found files in this list
+ db database name to search tables in
+ or NULL to search for databases
+ path path to database
+ wild filter for found files
+
+ RETURN
+ FIND_FILES_OK success
+ FIND_FILES_OOM out of memory error
+ FIND_FILES_DIR no such directory, or directory can't be read
+*/
+
+
+static find_files_result
+find_files(THD *thd, Dynamic_array<LEX_STRING*> *files, LEX_STRING *db,
+ const char *path, const LEX_STRING *wild)
+{
+ MY_DIR *dirp;
+ Discovered_table_list tl(thd, files, wild);
+ DBUG_ENTER("find_files");
+
+ if (!(dirp = my_dir(path, MY_THREAD_SPECIFIC | (db ? 0 : MY_WANT_STAT))))
+ {
+ if (my_errno == ENOENT)
+ my_error(ER_BAD_DB_ERROR, MYF(ME_BELL | ME_WAITTANG), db->str);
+ else
+ my_error(ER_CANT_READ_DIR, MYF(ME_BELL | ME_WAITTANG), path, my_errno);
+ DBUG_RETURN(FIND_FILES_DIR);
+ }
+
+ if (!db) /* Return databases */
+ {
+ for (uint i=0; i < (uint) dirp->number_of_files; i++)
+ {
+ FILEINFO *file= dirp->dir_entry+i;
+#ifdef USE_SYMDIR
+ char *ext;
+ char buff[FN_REFLEN];
+ if (my_use_symdir && !strcmp(ext=fn_ext(file->name), ".sym"))
+ {
+ /* Only show the sym file if it points to a directory */
+ char *end;
+ *ext=0; /* Remove extension */
+ unpack_dirname(buff, file->name);
+ end= strend(buff);
+ if (end != buff && end[-1] == FN_LIBCHAR)
+ end[-1]= 0; // Remove end FN_LIBCHAR
+ if (!mysql_file_stat(key_file_misc, buff, file->mystat, MYF(0)))
+ continue;
+ }
+#endif
+ if (!MY_S_ISDIR(file->mystat->st_mode))
+ continue;
+
+ if (is_in_ignore_db_dirs_list(file->name))
+ continue;
+
+ if (tl.add_file(file->name))
+ goto err;
+ }
+ tl.sort();
+ }
+ else
+ {
+ if (ha_discover_table_names(thd, db, dirp, &tl, false))
+ goto err;
+ }
+
+ DBUG_PRINT("info",("found: %zu files", files->elements()));
+ my_dirend(dirp);
+
+ DBUG_RETURN(FIND_FILES_OK);
+
+err:
+ my_dirend(dirp);
+ DBUG_RETURN(FIND_FILES_OOM);
+}
+
+
+/**
+ An Internal_error_handler that suppresses errors regarding views'
+ underlying tables that occur during privilege checking within SHOW CREATE
+ VIEW commands. This happens in the cases when
+
+ - A view's underlying table (e.g. referenced in its SELECT list) does not
+ exist. There should not be an error as no attempt was made to access it
+ per se.
+
+ - Access is denied for some table, column, function or stored procedure
+ such as mentioned above. This error gets raised automatically, since we
+ can't untangle its access checking from that of the view itself.
+ */
+class Show_create_error_handler : public Internal_error_handler {
+
+ TABLE_LIST *m_top_view;
+ bool m_handling;
+ Security_context *m_sctx;
+
+ char m_view_access_denied_message[MYSQL_ERRMSG_SIZE];
+ char *m_view_access_denied_message_ptr;
+
+public:
+
+ /**
+ Creates a new Show_create_error_handler for the particular security
+ context and view.
+
+ @thd Thread context, used for security context information if needed.
+ @top_view The view. We do not verify at this point that top_view is in
+ fact a view since, alas, these things do not stay constant.
+ */
+ explicit Show_create_error_handler(THD *thd, TABLE_LIST *top_view) :
+ m_top_view(top_view), m_handling(FALSE),
+ m_view_access_denied_message_ptr(NULL)
+ {
+
+ m_sctx= MY_TEST(m_top_view->security_ctx) ?
+ m_top_view->security_ctx : thd->security_ctx;
+ }
+
+ /**
+ Lazy instantiation of 'view access denied' message. The purpose of the
+ Show_create_error_handler is to hide details of underlying tables for
+ which we have no privileges behind ER_VIEW_INVALID messages. But this
+ obviously does not apply if we lack privileges on the view itself.
+ Unfortunately the information about for which table privilege checking
+ failed is not available at this point. The only way for us to check is by
+ reconstructing the actual error message and see if it's the same.
+ */
+ char* get_view_access_denied_message()
+ {
+ if (!m_view_access_denied_message_ptr)
+ {
+ m_view_access_denied_message_ptr= m_view_access_denied_message;
+ my_snprintf(m_view_access_denied_message, MYSQL_ERRMSG_SIZE,
+ ER(ER_TABLEACCESS_DENIED_ERROR), "SHOW VIEW",
+ m_sctx->priv_user,
+ m_sctx->host_or_ip, m_top_view->get_table_name());
+ }
+ return m_view_access_denied_message_ptr;
+ }
+
+ bool handle_condition(THD *thd, uint sql_errno, const char * /* sqlstate */,
+ Sql_condition::enum_warning_level level,
+ const char *message, Sql_condition ** /* cond_hdl */)
+ {
+ /*
+ The handler does not handle the errors raised by itself.
+ At this point we know if top_view is really a view.
+ */
+ if (m_handling || !m_top_view->view)
+ return FALSE;
+
+ m_handling= TRUE;
+
+ bool is_handled;
+
+ switch (sql_errno)
+ {
+ case ER_TABLEACCESS_DENIED_ERROR:
+ if (!strcmp(get_view_access_denied_message(), message))
+ {
+ /* Access to top view is not granted, don't interfere. */
+ is_handled= FALSE;
+ break;
+ }
+ case ER_COLUMNACCESS_DENIED_ERROR:
+ case ER_VIEW_NO_EXPLAIN: /* Error was anonymized, ignore all the same. */
+ case ER_PROCACCESS_DENIED_ERROR:
+ is_handled= TRUE;
+ break;
+
+ case ER_BAD_FIELD_ERROR:
+ case ER_SP_DOES_NOT_EXIST:
+ case ER_NO_SUCH_TABLE:
+ case ER_NO_SUCH_TABLE_IN_ENGINE:
+ /* Established behavior: warn if underlying tables, columns, or functions
+ are missing. */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_VIEW_INVALID,
+ ER(ER_VIEW_INVALID),
+ m_top_view->get_db_name(),
+ m_top_view->get_table_name());
+ is_handled= TRUE;
+ break;
+
+ default:
+ is_handled= FALSE;
+ }
+
+ m_handling= FALSE;
+ return is_handled;
+ }
+};
+
+
+/*
+ Return CREATE command for table or view
+
+ @param thd Thread handler
+ @param table_list Table / view
+
+ @return
+ @retval 0 OK
+ @retval 1 Error
+
+ @notes
+ table_list->db and table_list->table_name are kept unchanged to
+ not cause problems with SP.
+*/
+
+bool
+mysqld_show_create(THD *thd, TABLE_LIST *table_list)
+{
+ Protocol *protocol= thd->protocol;
+ char buff[2048];
+ String buffer(buff, sizeof(buff), system_charset_info);
+ List<Item> field_list;
+ bool error= TRUE;
+ DBUG_ENTER("mysqld_show_create");
+ DBUG_PRINT("enter",("db: %s table: %s",table_list->db,
+ table_list->table_name));
+
+ /*
+ Metadata locks taken during SHOW CREATE should be released when
+ the statmement completes as it is an information statement.
+ */
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+
+ /* We want to preserve the tree for views. */
+ thd->lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VIEW;
+
+ {
+ /*
+ Use open_tables() directly rather than
+ open_normal_and_derived_tables(). This ensures that
+ close_thread_tables() is not called if open tables fails and the
+ error is ignored. This allows us to handle broken views nicely.
+ */
+ uint counter;
+ Show_create_error_handler view_error_suppressor(thd, table_list);
+ thd->push_internal_handler(&view_error_suppressor);
+ bool open_error=
+ open_tables(thd, &table_list, &counter,
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL) ||
+ mysql_handle_derived(thd->lex, DT_PREPARE);
+ thd->pop_internal_handler();
+ if (open_error && (thd->killed || thd->is_error()))
+ goto exit;
+ }
+
+ /* TODO: add environment variables show when it become possible */
+ if (thd->lex->only_view && !table_list->view)
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0),
+ table_list->db, table_list->table_name, "VIEW");
+ goto exit;
+ }
+
+ buffer.length(0);
+
+ if (table_list->view)
+ buffer.set_charset(table_list->view_creation_ctx->get_client_cs());
+
+ if ((table_list->view ?
+ show_create_view(thd, table_list, &buffer) :
+ show_create_table(thd, table_list, &buffer, NULL, WITHOUT_DB_NAME)))
+ goto exit;
+
+ if (table_list->view)
+ {
+ field_list.push_back(new Item_empty_string("View",NAME_CHAR_LEN));
+ field_list.push_back(new Item_empty_string("Create View",
+ MY_MAX(buffer.length(),1024)));
+ field_list.push_back(new Item_empty_string("character_set_client",
+ MY_CS_NAME_SIZE));
+ field_list.push_back(new Item_empty_string("collation_connection",
+ MY_CS_NAME_SIZE));
+ }
+ else
+ {
+ field_list.push_back(new Item_empty_string("Table",NAME_CHAR_LEN));
+ // 1024 is for not to confuse old clients
+ field_list.push_back(new Item_empty_string("Create Table",
+ MY_MAX(buffer.length(),1024)));
+ }
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ goto exit;
+
+ protocol->prepare_for_resend();
+ if (table_list->view)
+ protocol->store(table_list->view_name.str, system_charset_info);
+ else
+ {
+ if (table_list->schema_table)
+ protocol->store(table_list->schema_table->table_name,
+ system_charset_info);
+ else
+ protocol->store(table_list->table->alias.c_ptr(), system_charset_info);
+ }
+
+ if (table_list->view)
+ {
+ protocol->store(buffer.ptr(), buffer.length(),
+ table_list->view_creation_ctx->get_client_cs());
+
+ protocol->store(table_list->view_creation_ctx->get_client_cs()->csname,
+ system_charset_info);
+
+ protocol->store(table_list->view_creation_ctx->get_connection_cl()->name,
+ system_charset_info);
+ }
+ else
+ protocol->store(buffer.ptr(), buffer.length(), buffer.charset());
+
+ if (protocol->write())
+ goto exit;
+
+ error= FALSE;
+ my_eof(thd);
+
+exit:
+ close_thread_tables(thd);
+ /* Release any metadata locks taken during SHOW CREATE. */
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+ DBUG_RETURN(error);
+}
+
+bool mysqld_show_create_db(THD *thd, LEX_STRING *dbname,
+ LEX_STRING *orig_dbname,
+ HA_CREATE_INFO *create_info)
+{
+ char buff[2048];
+ String buffer(buff, sizeof(buff), system_charset_info);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *sctx= thd->security_ctx;
+ uint db_access;
+#endif
+ HA_CREATE_INFO create;
+ uint create_options = create_info ? create_info->options : 0;
+ Protocol *protocol=thd->protocol;
+ DBUG_ENTER("mysql_show_create_db");
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (test_all_bits(sctx->master_access, DB_ACLS))
+ db_access=DB_ACLS;
+ else
+ db_access= (acl_get(sctx->host, sctx->ip, sctx->priv_user, dbname->str, 0) |
+ sctx->master_access);
+ if (!(db_access & DB_ACLS) && check_grant_db(thd,dbname->str))
+ {
+ status_var_increment(thd->status_var.access_denied_errors);
+ my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
+ sctx->priv_user, sctx->host_or_ip, dbname->str);
+ general_log_print(thd,COM_INIT_DB,ER(ER_DBACCESS_DENIED_ERROR),
+ sctx->priv_user, sctx->host_or_ip, orig_dbname->str);
+ DBUG_RETURN(TRUE);
+ }
+#endif
+ if (is_infoschema_db(dbname->str))
+ {
+ *dbname= INFORMATION_SCHEMA_NAME;
+ create.default_table_charset= system_charset_info;
+ }
+ else
+ {
+ if (check_db_dir_existence(dbname->str))
+ {
+ my_error(ER_BAD_DB_ERROR, MYF(0), dbname->str);
+ DBUG_RETURN(TRUE);
+ }
+
+ load_db_opt_by_name(thd, dbname->str, &create);
+ }
+ List<Item> field_list;
+ field_list.push_back(new Item_empty_string("Database",NAME_CHAR_LEN));
+ field_list.push_back(new Item_empty_string("Create Database",1024));
+
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ protocol->prepare_for_resend();
+ protocol->store(orig_dbname->str, orig_dbname->length, system_charset_info);
+ buffer.length(0);
+ buffer.append(STRING_WITH_LEN("CREATE DATABASE "));
+ if (create_options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ buffer.append(STRING_WITH_LEN("/*!32312 IF NOT EXISTS*/ "));
+ append_identifier(thd, &buffer, dbname->str, dbname->length);
+
+ if (create.default_table_charset)
+ {
+ buffer.append(STRING_WITH_LEN(" /*!40100"));
+ buffer.append(STRING_WITH_LEN(" DEFAULT CHARACTER SET "));
+ buffer.append(create.default_table_charset->csname);
+ if (!(create.default_table_charset->state & MY_CS_PRIMARY))
+ {
+ buffer.append(STRING_WITH_LEN(" COLLATE "));
+ buffer.append(create.default_table_charset->name);
+ }
+ buffer.append(STRING_WITH_LEN(" */"));
+ }
+ protocol->store(buffer.ptr(), buffer.length(), buffer.charset());
+
+ if (protocol->write())
+ DBUG_RETURN(TRUE);
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+
+/****************************************************************************
+ Return only fields for API mysql_list_fields
+ Use "show table wildcard" in mysql instead of this
+****************************************************************************/
+
+void
+mysqld_list_fields(THD *thd, TABLE_LIST *table_list, const char *wild)
+{
+ TABLE *table;
+ DBUG_ENTER("mysqld_list_fields");
+ DBUG_PRINT("enter",("table: %s",table_list->table_name));
+
+ if (open_normal_and_derived_tables(thd, table_list,
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL,
+ DT_PREPARE | DT_CREATE))
+ DBUG_VOID_RETURN;
+ table= table_list->table;
+
+ List<Item> field_list;
+
+ Field **ptr,*field;
+ for (ptr=table->field ; (field= *ptr); ptr++)
+ {
+ if (!wild || !wild[0] ||
+ !wild_case_compare(system_charset_info, field->field_name,wild))
+ {
+ if (table_list->view)
+ field_list.push_back(new Item_ident_for_show(field,
+ table_list->view_db.str,
+ table_list->view_name.str));
+ else
+ field_list.push_back(new Item_field(field));
+ }
+ }
+ restore_record(table, s->default_values); // Get empty record
+ table->use_all_columns();
+ if (thd->protocol->send_result_set_metadata(&field_list, Protocol::SEND_DEFAULTS))
+ DBUG_VOID_RETURN;
+ my_eof(thd);
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Go through all character combinations and ensure that sql_lex.cc can
+ parse it as an identifier.
+
+ SYNOPSIS
+ require_quotes()
+ name attribute name
+ name_length length of name
+
+ RETURN
+ # Pointer to conflicting character
+ 0 No conflicting character
+*/
+
+static const char *require_quotes(const char *name, uint name_length)
+{
+ uint length;
+ bool pure_digit= TRUE;
+ const char *end= name + name_length;
+
+ for (; name < end ; name++)
+ {
+ uchar chr= (uchar) *name;
+ length= my_mbcharlen(system_charset_info, chr);
+ if (length == 1 && !system_charset_info->ident_map[chr])
+ return name;
+ if (length == 1 && (chr < '0' || chr > '9'))
+ pure_digit= FALSE;
+ }
+ if (pure_digit)
+ return name;
+ return 0;
+}
+
+
+/*
+ Quote the given identifier if needed and append it to the target string.
+ If the given identifier is empty, it will be quoted.
+
+ SYNOPSIS
+ append_identifier()
+ thd thread handler
+ packet target string
+ name the identifier to be appended
+ name_length length of the appending identifier
+
+ RETURN VALUES
+ true Error
+ false Ok
+*/
+
+bool
+append_identifier(THD *thd, String *packet, const char *name, uint length)
+{
+ const char *name_end;
+ char quote_char;
+ int q= get_quote_char_for_identifier(thd, name, length);
+
+ if (q == EOF)
+ return packet->append(name, length, packet->charset());
+
+ /*
+ The identifier must be quoted as it includes a quote character or
+ it's a keyword
+ */
+
+ /*
+ Special code for swe7. It encodes the letter "E WITH ACUTE" on
+ the position 0x60, where backtick normally resides.
+ In swe7 we cannot append 0x60 using system_charset_info,
+ because it cannot be converted to swe7 and will be replaced to
+ question mark '?'. Use &my_charset_bin to avoid this.
+ It will prevent conversion and will append the backtick as is.
+ */
+ CHARSET_INFO *quote_charset= q == 0x60 &&
+ (packet->charset()->state & MY_CS_NONASCII) &&
+ packet->charset()->mbmaxlen == 1 ?
+ &my_charset_bin : system_charset_info;
+
+ (void) packet->reserve(length*2 + 2);
+ quote_char= (char) q;
+ if (packet->append(&quote_char, 1, quote_charset))
+ return true;
+
+ for (name_end= name+length ; name < name_end ; name+= length)
+ {
+ uchar chr= (uchar) *name;
+ length= my_mbcharlen(system_charset_info, chr);
+ /*
+ my_mbcharlen can return 0 on a wrong multibyte
+ sequence. It is possible when upgrading from 4.0,
+ and identifier contains some accented characters.
+ The manual says it does not work. So we'll just
+ change length to 1 not to hang in the endless loop.
+ */
+ if (!length)
+ length= 1;
+ if (length == 1 && chr == (uchar) quote_char &&
+ packet->append(&quote_char, 1, quote_charset))
+ return true;
+ if (packet->append(name, length, system_charset_info))
+ return true;
+ }
+ return packet->append(&quote_char, 1, quote_charset);
+}
+
+
+/*
+ Get the quote character for displaying an identifier.
+
+ SYNOPSIS
+ get_quote_char_for_identifier()
+ thd Thread handler
+ name name to quote
+ length length of name
+
+ IMPLEMENTATION
+ Force quoting in the following cases:
+ - name is empty (for one, it is possible when we use this function for
+ quoting user and host names for DEFINER clause);
+ - name is a keyword;
+ - name includes a special character;
+ Otherwise identifier is quoted only if the option OPTION_QUOTE_SHOW_CREATE
+ is set.
+
+ RETURN
+ EOF No quote character is needed
+ # Quote character
+*/
+
+int get_quote_char_for_identifier(THD *thd, const char *name, uint length)
+{
+ if (length &&
+ !is_keyword(name,length) &&
+ !require_quotes(name, length) &&
+ !(thd->variables.option_bits & OPTION_QUOTE_SHOW_CREATE))
+ return EOF;
+ if (thd->variables.sql_mode & MODE_ANSI_QUOTES)
+ return '"';
+ return '`';
+}
+
+
+/* Append directory name (if exists) to CREATE INFO */
+
+static void append_directory(THD *thd, String *packet, const char *dir_type,
+ const char *filename)
+{
+ if (filename && !(thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE))
+ {
+ uint length= dirname_length(filename);
+ packet->append(' ');
+ packet->append(dir_type);
+ packet->append(STRING_WITH_LEN(" DIRECTORY='"));
+#ifdef __WIN__
+ /* Convert \ to / to be able to create table on unix */
+ char *winfilename= (char*) thd->memdup(filename, length);
+ char *pos, *end;
+ for (pos= winfilename, end= pos+length ; pos < end ; pos++)
+ {
+ if (*pos == '\\')
+ *pos = '/';
+ }
+ filename= winfilename;
+#endif
+ packet->append(filename, length);
+ packet->append('\'');
+ }
+}
+
+
+#define LIST_PROCESS_HOST_LEN 64
+
+
+/**
+ Print "ON UPDATE" clause of a field into a string.
+
+ @param timestamp_field Pointer to timestamp field of a table.
+ @param field The field to generate ON UPDATE clause for.
+ @bool lcase Whether to print in lower case.
+ @return false on success, true on error.
+*/
+static bool print_on_update_clause(Field *field, String *val, bool lcase)
+{
+ DBUG_ASSERT(val->charset()->mbminlen == 1);
+ val->length(0);
+ if (field->has_update_default_function())
+ {
+ if (lcase)
+ val->append(STRING_WITH_LEN("on update "));
+ else
+ val->append(STRING_WITH_LEN("ON UPDATE "));
+ val->append(STRING_WITH_LEN("CURRENT_TIMESTAMP"));
+ if (field->decimals() > 0)
+ val->append_parenthesized(field->decimals());
+ return true;
+ }
+ return false;
+}
+
+
+static bool get_field_default_value(THD *thd, Field *field, String *def_value,
+ bool quoted)
+{
+ bool has_default;
+ bool has_now_default;
+ enum enum_field_types field_type= field->type();
+
+ /*
+ We are using CURRENT_TIMESTAMP instead of NOW because it is
+ more standard
+ */
+ has_now_default= field->has_insert_default_function();
+
+ has_default= (field_type != FIELD_TYPE_BLOB &&
+ !(field->flags & NO_DEFAULT_VALUE_FLAG) &&
+ field->unireg_check != Field::NEXT_NUMBER &&
+ !((thd->variables.sql_mode & (MODE_MYSQL323 | MODE_MYSQL40))
+ && has_now_default));
+
+ def_value->length(0);
+ if (has_default)
+ {
+ if (has_now_default)
+ {
+ def_value->append(STRING_WITH_LEN("CURRENT_TIMESTAMP"));
+ if (field->decimals() > 0)
+ def_value->append_parenthesized(field->decimals());
+ }
+ else if (!field->is_null())
+ { // Not null by default
+ char tmp[MAX_FIELD_WIDTH];
+ String type(tmp, sizeof(tmp), field->charset());
+ if (field_type == MYSQL_TYPE_BIT)
+ {
+ longlong dec= field->val_int();
+ char *ptr= longlong2str(dec, tmp + 2, 2);
+ uint32 length= (uint32) (ptr - tmp);
+ tmp[0]= 'b';
+ tmp[1]= '\'';
+ tmp[length]= '\'';
+ type.length(length + 1);
+ quoted= 0;
+ }
+ else
+ field->val_str(&type);
+ if (type.length())
+ {
+ String def_val;
+ uint dummy_errors;
+ /* convert to system_charset_info == utf8 */
+ def_val.copy(type.ptr(), type.length(), field->charset(),
+ system_charset_info, &dummy_errors);
+ if (quoted)
+ append_unescaped(def_value, def_val.ptr(), def_val.length());
+ else
+ def_value->append(def_val.ptr(), def_val.length());
+ }
+ else if (quoted)
+ def_value->append(STRING_WITH_LEN("''"));
+ }
+ else if (field->maybe_null() && quoted)
+ def_value->append(STRING_WITH_LEN("NULL")); // Null as default
+ else
+ return 0;
+
+ }
+ return has_default;
+}
+
+
+/**
+ Appends list of options to string
+
+ @param thd thread handler
+ @param packet string to append
+ @param opt list of options
+ @param check_options only print known options
+ @param rules list of known options
+*/
+
+static void append_create_options(THD *thd, String *packet,
+ engine_option_value *opt,
+ bool check_options,
+ ha_create_table_option *rules)
+{
+ bool in_comment= false;
+ for(; opt; opt= opt->next)
+ {
+ if (check_options)
+ {
+ if (is_engine_option_known(opt, rules))
+ {
+ if (in_comment)
+ packet->append(STRING_WITH_LEN(" */"));
+ in_comment= false;
+ }
+ else
+ {
+ if (!in_comment)
+ packet->append(STRING_WITH_LEN(" /*"));
+ in_comment= true;
+ }
+ }
+
+ DBUG_ASSERT(opt->value.str);
+ packet->append(' ');
+ append_identifier(thd, packet, opt->name.str, opt->name.length);
+ packet->append('=');
+ if (opt->quoted_value)
+ append_unescaped(packet, opt->value.str, opt->value.length);
+ else
+ packet->append(opt->value.str, opt->value.length);
+ }
+ if (in_comment)
+ packet->append(STRING_WITH_LEN(" */"));
+}
+
+/*
+ Build a CREATE TABLE statement for a table.
+
+ SYNOPSIS
+ show_create_table()
+ thd The thread
+ table_list A list containing one table to write statement
+ for.
+ packet Pointer to a string where statement will be
+ written.
+ create_info_arg Pointer to create information that can be used
+ to tailor the format of the statement. Can be
+ NULL, in which case only SQL_MODE is considered
+ when building the statement.
+ with_db_name Add database name to table name
+
+ NOTE
+ Currently always return 0, but might return error code in the
+ future.
+
+ RETURN
+ 0 OK
+ */
+
+int show_create_table(THD *thd, TABLE_LIST *table_list, String *packet,
+ HA_CREATE_INFO *create_info_arg,
+ enum_with_db_name with_db_name)
+{
+ List<Item> field_list;
+ char tmp[MAX_FIELD_WIDTH], *for_str, buff[128], def_value_buf[MAX_FIELD_WIDTH];
+ const char *alias;
+ String type(tmp, sizeof(tmp), system_charset_info);
+ String def_value(def_value_buf, sizeof(def_value_buf), system_charset_info);
+ Field **ptr,*field;
+ uint primary_key;
+ KEY *key_info;
+ TABLE *table= table_list->table;
+ handler *file= table->file;
+ TABLE_SHARE *share= table->s;
+ HA_CREATE_INFO create_info;
+ sql_mode_t sql_mode= thd->variables.sql_mode;
+ bool foreign_db_mode= sql_mode & (MODE_POSTGRESQL | MODE_ORACLE |
+ MODE_MSSQL | MODE_DB2 |
+ MODE_MAXDB | MODE_ANSI);
+ bool limited_mysql_mode= sql_mode & (MODE_NO_FIELD_OPTIONS | MODE_MYSQL323 |
+ MODE_MYSQL40);
+ bool show_table_options= !(sql_mode & MODE_NO_TABLE_OPTIONS) &&
+ !foreign_db_mode;
+ bool check_options= !(sql_mode & MODE_IGNORE_BAD_TABLE_OPTIONS) &&
+ !create_info_arg;
+ handlerton *hton;
+ my_bitmap_map *old_map;
+ int error= 0;
+ DBUG_ENTER("show_create_table");
+ DBUG_PRINT("enter",("table: %s", table->s->table_name.str));
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (table->part_info)
+ hton= table->part_info->default_engine_type;
+ else
+#endif
+ hton= file->ht;
+
+ restore_record(table, s->default_values); // Get empty record
+
+ packet->append(STRING_WITH_LEN("CREATE "));
+ if (create_info_arg &&
+ (create_info_arg->org_options & HA_LEX_CREATE_REPLACE ||
+ create_info_arg->table_was_deleted))
+ packet->append(STRING_WITH_LEN("OR REPLACE "));
+ if (share->tmp_table)
+ packet->append(STRING_WITH_LEN("TEMPORARY "));
+ packet->append(STRING_WITH_LEN("TABLE "));
+ if (create_info_arg &&
+ (create_info_arg->options & HA_LEX_CREATE_IF_NOT_EXISTS))
+ packet->append(STRING_WITH_LEN("IF NOT EXISTS "));
+ if (table_list->schema_table)
+ alias= table_list->schema_table->table_name;
+ else
+ {
+ if (lower_case_table_names == 2)
+ alias= table->alias.c_ptr();
+ else
+ {
+ alias= share->table_name.str;
+ }
+ }
+
+ /*
+ Print the database before the table name if told to do that. The
+ database name is only printed in the event that it is different
+ from the current database. The main reason for doing this is to
+ avoid having to update gazillions of tests and result files, but
+ it also saves a few bytes of the binary log.
+ */
+ if (with_db_name == WITH_DB_NAME)
+ {
+ const LEX_STRING *const db=
+ table_list->schema_table ? &INFORMATION_SCHEMA_NAME : &table->s->db;
+ if (!thd->db || strcmp(db->str, thd->db))
+ {
+ append_identifier(thd, packet, db->str, db->length);
+ packet->append(STRING_WITH_LEN("."));
+ }
+ }
+
+ append_identifier(thd, packet, alias, strlen(alias));
+ packet->append(STRING_WITH_LEN(" (\n"));
+ /*
+ We need this to get default values from the table
+ We have to restore the read_set if we are called from insert in case
+ of row based replication.
+ */
+ old_map= tmp_use_all_columns(table, table->read_set);
+
+ for (ptr=table->field ; (field= *ptr); ptr++)
+ {
+ uint flags = field->flags;
+
+ if (ptr != table->field)
+ packet->append(STRING_WITH_LEN(",\n"));
+
+ packet->append(STRING_WITH_LEN(" "));
+ append_identifier(thd,packet,field->field_name, strlen(field->field_name));
+ packet->append(' ');
+ // check for surprises from the previous call to Field::sql_type()
+ if (type.ptr() != tmp)
+ type.set(tmp, sizeof(tmp), system_charset_info);
+ else
+ type.set_charset(system_charset_info);
+
+ field->sql_type(type);
+ packet->append(type.ptr(), type.length(), system_charset_info);
+
+ if (field->has_charset() && !(sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)))
+ {
+ if (field->charset() != share->table_charset)
+ {
+ packet->append(STRING_WITH_LEN(" CHARACTER SET "));
+ packet->append(field->charset()->csname);
+ }
+ /*
+ For string types dump collation name only if
+ collation is not primary for the given charset
+ */
+ if (!(field->charset()->state & MY_CS_PRIMARY))
+ {
+ packet->append(STRING_WITH_LEN(" COLLATE "));
+ packet->append(field->charset()->name);
+ }
+ }
+
+ if (field->vcol_info)
+ {
+ packet->append(STRING_WITH_LEN(" AS ("));
+ packet->append(field->vcol_info->expr_str.str,
+ field->vcol_info->expr_str.length,
+ system_charset_info);
+ packet->append(STRING_WITH_LEN(")"));
+ if (field->stored_in_db)
+ packet->append(STRING_WITH_LEN(" PERSISTENT"));
+ else
+ packet->append(STRING_WITH_LEN(" VIRTUAL"));
+ }
+
+ if (flags & NOT_NULL_FLAG)
+ packet->append(STRING_WITH_LEN(" NOT NULL"));
+ else if (field->type() == MYSQL_TYPE_TIMESTAMP)
+ {
+ /*
+ TIMESTAMP field require explicit NULL flag, because unlike
+ all other fields they are treated as NOT NULL by default.
+ */
+ packet->append(STRING_WITH_LEN(" NULL"));
+ }
+
+ if (!field->vcol_info &&
+ get_field_default_value(thd, field, &def_value, 1))
+ {
+ packet->append(STRING_WITH_LEN(" DEFAULT "));
+ packet->append(def_value.ptr(), def_value.length(), system_charset_info);
+ }
+
+ if (!limited_mysql_mode && print_on_update_clause(field, &def_value, false))
+ {
+ packet->append(STRING_WITH_LEN(" "));
+ packet->append(def_value);
+ }
+
+
+ if (field->unireg_check == Field::NEXT_NUMBER &&
+ !(sql_mode & MODE_NO_FIELD_OPTIONS))
+ packet->append(STRING_WITH_LEN(" AUTO_INCREMENT"));
+
+ if (field->comment.length)
+ {
+ packet->append(STRING_WITH_LEN(" COMMENT "));
+ append_unescaped(packet, field->comment.str, field->comment.length);
+ }
+ append_create_options(thd, packet, field->option_list, check_options,
+ hton->field_options);
+ }
+
+ key_info= table->key_info;
+ bzero((char*) &create_info, sizeof(create_info));
+ /* Allow update_create_info to update row type, page checksums and options */
+ create_info.row_type= share->row_type;
+ create_info.page_checksum= share->page_checksum;
+ create_info.options= share->db_create_options;
+ file->update_create_info(&create_info);
+ primary_key= share->primary_key;
+
+ for (uint i=0 ; i < share->keys ; i++,key_info++)
+ {
+ KEY_PART_INFO *key_part= key_info->key_part;
+ bool found_primary=0;
+ packet->append(STRING_WITH_LEN(",\n "));
+
+ if (i == primary_key && !strcmp(key_info->name, primary_key_name))
+ {
+ found_primary=1;
+ /*
+ No space at end, because a space will be added after where the
+ identifier would go, but that is not added for primary key.
+ */
+ packet->append(STRING_WITH_LEN("PRIMARY KEY"));
+ }
+ else if (key_info->flags & HA_NOSAME)
+ packet->append(STRING_WITH_LEN("UNIQUE KEY "));
+ else if (key_info->flags & HA_FULLTEXT)
+ packet->append(STRING_WITH_LEN("FULLTEXT KEY "));
+ else if (key_info->flags & HA_SPATIAL)
+ packet->append(STRING_WITH_LEN("SPATIAL KEY "));
+ else
+ packet->append(STRING_WITH_LEN("KEY "));
+
+ if (!found_primary)
+ append_identifier(thd, packet, key_info->name, strlen(key_info->name));
+
+ packet->append(STRING_WITH_LEN(" ("));
+
+ for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
+ {
+ if (j)
+ packet->append(',');
+
+ if (key_part->field)
+ append_identifier(thd,packet,key_part->field->field_name,
+ strlen(key_part->field->field_name));
+ if (key_part->field &&
+ (key_part->length !=
+ table->field[key_part->fieldnr-1]->key_length() &&
+ !(key_info->flags & (HA_FULLTEXT | HA_SPATIAL))))
+ {
+ packet->append_parenthesized((long) key_part->length /
+ key_part->field->charset()->mbmaxlen);
+ }
+ }
+ packet->append(')');
+ store_key_options(thd, packet, table, key_info);
+ if (key_info->parser)
+ {
+ LEX_STRING *parser_name= plugin_name(key_info->parser);
+ packet->append(STRING_WITH_LEN(" /*!50100 WITH PARSER "));
+ append_identifier(thd, packet, parser_name->str, parser_name->length);
+ packet->append(STRING_WITH_LEN(" */ "));
+ }
+ append_create_options(thd, packet, key_info->option_list, check_options,
+ hton->index_options);
+ }
+
+ /*
+ Get possible foreign key definitions stored in InnoDB and append them
+ to the CREATE TABLE statement
+ */
+
+ if ((for_str= file->get_foreign_key_create_info()))
+ {
+ packet->append(for_str, strlen(for_str));
+ file->free_foreign_key_create_info(for_str);
+ }
+
+ packet->append(STRING_WITH_LEN("\n)"));
+ if (show_table_options)
+ {
+ /*
+ IF check_create_info
+ THEN add ENGINE only if it was used when creating the table
+ */
+ if (!create_info_arg ||
+ (create_info_arg->used_fields & HA_CREATE_USED_ENGINE))
+ {
+ if (sql_mode & (MODE_MYSQL323 | MODE_MYSQL40))
+ packet->append(STRING_WITH_LEN(" TYPE="));
+ else
+ packet->append(STRING_WITH_LEN(" ENGINE="));
+ packet->append(hton_name(hton));
+ }
+
+ /*
+ Add AUTO_INCREMENT=... if there is an AUTO_INCREMENT column,
+ and NEXT_ID > 1 (the default). We must not print the clause
+ for engines that do not support this as it would break the
+ import of dumps, but as of this writing, the test for whether
+ AUTO_INCREMENT columns are allowed and wether AUTO_INCREMENT=...
+ is supported is identical, !(file->table_flags() & HA_NO_AUTO_INCREMENT))
+ Because of that, we do not explicitly test for the feature,
+ but may extrapolate its existence from that of an AUTO_INCREMENT column.
+ */
+
+ if (create_info.auto_increment_value > 1)
+ {
+ char *end;
+ packet->append(STRING_WITH_LEN(" AUTO_INCREMENT="));
+ end= longlong10_to_str(create_info.auto_increment_value, buff,10);
+ packet->append(buff, (uint) (end - buff));
+ }
+
+ if (share->table_charset && !(sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)))
+ {
+ /*
+ IF check_create_info
+ THEN add DEFAULT CHARSET only if it was used when creating the table
+ */
+ if (!create_info_arg ||
+ (create_info_arg->used_fields & HA_CREATE_USED_DEFAULT_CHARSET))
+ {
+ packet->append(STRING_WITH_LEN(" DEFAULT CHARSET="));
+ packet->append(share->table_charset->csname);
+ if (!(share->table_charset->state & MY_CS_PRIMARY))
+ {
+ packet->append(STRING_WITH_LEN(" COLLATE="));
+ packet->append(table->s->table_charset->name);
+ }
+ }
+ }
+
+ if (share->min_rows)
+ {
+ char *end;
+ packet->append(STRING_WITH_LEN(" MIN_ROWS="));
+ end= longlong10_to_str(share->min_rows, buff, 10);
+ packet->append(buff, (uint) (end- buff));
+ }
+
+ if (share->max_rows && !table_list->schema_table)
+ {
+ char *end;
+ packet->append(STRING_WITH_LEN(" MAX_ROWS="));
+ end= longlong10_to_str(share->max_rows, buff, 10);
+ packet->append(buff, (uint) (end - buff));
+ }
+
+ if (share->avg_row_length)
+ {
+ char *end;
+ packet->append(STRING_WITH_LEN(" AVG_ROW_LENGTH="));
+ end= longlong10_to_str(share->avg_row_length, buff,10);
+ packet->append(buff, (uint) (end - buff));
+ }
+
+ if (create_info.options & HA_OPTION_PACK_KEYS)
+ packet->append(STRING_WITH_LEN(" PACK_KEYS=1"));
+ if (create_info.options & HA_OPTION_NO_PACK_KEYS)
+ packet->append(STRING_WITH_LEN(" PACK_KEYS=0"));
+ if (share->db_create_options & HA_OPTION_STATS_PERSISTENT)
+ packet->append(STRING_WITH_LEN(" STATS_PERSISTENT=1"));
+ if (share->db_create_options & HA_OPTION_NO_STATS_PERSISTENT)
+ packet->append(STRING_WITH_LEN(" STATS_PERSISTENT=0"));
+ if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON)
+ packet->append(STRING_WITH_LEN(" STATS_AUTO_RECALC=1"));
+ else if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF)
+ packet->append(STRING_WITH_LEN(" STATS_AUTO_RECALC=0"));
+ if (share->stats_sample_pages != 0)
+ {
+ char *end;
+ packet->append(STRING_WITH_LEN(" STATS_SAMPLE_PAGES="));
+ end= longlong10_to_str(share->stats_sample_pages, buff, 10);
+ packet->append(buff, (uint) (end - buff));
+ }
+
+ /* We use CHECKSUM, instead of TABLE_CHECKSUM, for backward compability */
+ if (create_info.options & HA_OPTION_CHECKSUM)
+ packet->append(STRING_WITH_LEN(" CHECKSUM=1"));
+ if (create_info.page_checksum != HA_CHOICE_UNDEF)
+ {
+ packet->append(STRING_WITH_LEN(" PAGE_CHECKSUM="));
+ packet->append(ha_choice_values[create_info.page_checksum], 1);
+ }
+ if (create_info.options & HA_OPTION_DELAY_KEY_WRITE)
+ packet->append(STRING_WITH_LEN(" DELAY_KEY_WRITE=1"));
+ if (create_info.row_type != ROW_TYPE_DEFAULT)
+ {
+ packet->append(STRING_WITH_LEN(" ROW_FORMAT="));
+ packet->append(ha_row_type[(uint) create_info.row_type]);
+ }
+ if (share->transactional != HA_CHOICE_UNDEF)
+ {
+ packet->append(STRING_WITH_LEN(" TRANSACTIONAL="));
+ packet->append(ha_choice_values[(uint) share->transactional], 1);
+ }
+ if (table->s->key_block_size)
+ {
+ char *end;
+ packet->append(STRING_WITH_LEN(" KEY_BLOCK_SIZE="));
+ end= longlong10_to_str(table->s->key_block_size, buff, 10);
+ packet->append(buff, (uint) (end - buff));
+ }
+ table->file->append_create_info(packet);
+ if (share->comment.length)
+ {
+ packet->append(STRING_WITH_LEN(" COMMENT="));
+ append_unescaped(packet, share->comment.str, share->comment.length);
+ }
+ if (share->connect_string.length)
+ {
+ packet->append(STRING_WITH_LEN(" CONNECTION="));
+ append_unescaped(packet, share->connect_string.str, share->connect_string.length);
+ }
+ append_create_options(thd, packet, share->option_list, check_options,
+ hton->table_options);
+ append_directory(thd, packet, "DATA", create_info.data_file_name);
+ append_directory(thd, packet, "INDEX", create_info.index_file_name);
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ {
+ if (table->part_info &&
+ !((table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) &&
+ table->part_info->is_auto_partitioned))
+ {
+ /*
+ Partition syntax for CREATE TABLE is at the end of the syntax.
+ */
+ uint part_syntax_len;
+ char *part_syntax;
+ String comment_start;
+ table->part_info->set_show_version_string(&comment_start);
+ if ((part_syntax= generate_partition_syntax(table->part_info,
+ &part_syntax_len,
+ FALSE,
+ show_table_options,
+ NULL, NULL,
+ comment_start.c_ptr())))
+ {
+ packet->append(comment_start);
+ if (packet->append(part_syntax, part_syntax_len) ||
+ packet->append(STRING_WITH_LEN(" */")))
+ error= 1;
+ my_free(part_syntax);
+ }
+ }
+ }
+#endif
+ tmp_restore_column_map(table->read_set, old_map);
+ DBUG_RETURN(error);
+}
+
+
+static void store_key_options(THD *thd, String *packet, TABLE *table,
+ KEY *key_info)
+{
+ bool limited_mysql_mode= (thd->variables.sql_mode &
+ (MODE_NO_FIELD_OPTIONS | MODE_MYSQL323 |
+ MODE_MYSQL40)) != 0;
+ bool foreign_db_mode= (thd->variables.sql_mode & (MODE_POSTGRESQL |
+ MODE_ORACLE |
+ MODE_MSSQL |
+ MODE_DB2 |
+ MODE_MAXDB |
+ MODE_ANSI)) != 0;
+ char *end, buff[32];
+
+ if (!(thd->variables.sql_mode & MODE_NO_KEY_OPTIONS) &&
+ !limited_mysql_mode && !foreign_db_mode)
+ {
+
+ if (key_info->algorithm == HA_KEY_ALG_BTREE)
+ packet->append(STRING_WITH_LEN(" USING BTREE"));
+
+ if (key_info->algorithm == HA_KEY_ALG_HASH)
+ packet->append(STRING_WITH_LEN(" USING HASH"));
+
+ /* send USING only in non-default case: non-spatial rtree */
+ if ((key_info->algorithm == HA_KEY_ALG_RTREE) &&
+ !(key_info->flags & HA_SPATIAL))
+ packet->append(STRING_WITH_LEN(" USING RTREE"));
+
+ if ((key_info->flags & HA_USES_BLOCK_SIZE) &&
+ table->s->key_block_size != key_info->block_size)
+ {
+ packet->append(STRING_WITH_LEN(" KEY_BLOCK_SIZE="));
+ end= longlong10_to_str(key_info->block_size, buff, 10);
+ packet->append(buff, (uint) (end - buff));
+ }
+ DBUG_ASSERT(MY_TEST(key_info->flags & HA_USES_COMMENT) ==
+ (key_info->comment.length > 0));
+ if (key_info->flags & HA_USES_COMMENT)
+ {
+ packet->append(STRING_WITH_LEN(" COMMENT "));
+ append_unescaped(packet, key_info->comment.str,
+ key_info->comment.length);
+ }
+ }
+}
+
+
+void
+view_store_options(THD *thd, TABLE_LIST *table, String *buff)
+{
+ append_algorithm(table, buff);
+ append_definer(thd, buff, &table->definer.user, &table->definer.host);
+ if (table->view_suid)
+ buff->append(STRING_WITH_LEN("SQL SECURITY DEFINER "));
+ else
+ buff->append(STRING_WITH_LEN("SQL SECURITY INVOKER "));
+}
+
+
+/*
+ Append DEFINER clause to the given buffer.
+
+ SYNOPSIS
+ append_definer()
+ thd [in] thread handle
+ buffer [inout] buffer to hold DEFINER clause
+ definer_user [in] user name part of definer
+ definer_host [in] host name part of definer
+*/
+
+static void append_algorithm(TABLE_LIST *table, String *buff)
+{
+ buff->append(STRING_WITH_LEN("ALGORITHM="));
+ switch ((int16)table->algorithm) {
+ case VIEW_ALGORITHM_UNDEFINED:
+ buff->append(STRING_WITH_LEN("UNDEFINED "));
+ break;
+ case VIEW_ALGORITHM_TMPTABLE:
+ buff->append(STRING_WITH_LEN("TEMPTABLE "));
+ break;
+ case VIEW_ALGORITHM_MERGE:
+ buff->append(STRING_WITH_LEN("MERGE "));
+ break;
+ default:
+ DBUG_ASSERT(0); // never should happen
+ }
+}
+
+/*
+ Append DEFINER clause to the given buffer.
+
+ SYNOPSIS
+ append_definer()
+ thd [in] thread handle
+ buffer [inout] buffer to hold DEFINER clause
+ definer_user [in] user name part of definer
+ definer_host [in] host name part of definer
+*/
+
+void append_definer(THD *thd, String *buffer, const LEX_STRING *definer_user,
+ const LEX_STRING *definer_host)
+{
+ buffer->append(STRING_WITH_LEN("DEFINER="));
+ append_identifier(thd, buffer, definer_user->str, definer_user->length);
+ if (definer_host->str[0])
+ {
+ buffer->append('@');
+ append_identifier(thd, buffer, definer_host->str, definer_host->length);
+ }
+ buffer->append(' ');
+}
+
+
+static int show_create_view(THD *thd, TABLE_LIST *table, String *buff)
+{
+ my_bool compact_view_name= TRUE;
+ my_bool foreign_db_mode= (thd->variables.sql_mode & (MODE_POSTGRESQL |
+ MODE_ORACLE |
+ MODE_MSSQL |
+ MODE_DB2 |
+ MODE_MAXDB |
+ MODE_ANSI)) != 0;
+
+ if (!thd->db || strcmp(thd->db, table->view_db.str))
+ /*
+ print compact view name if the view belongs to the current database
+ */
+ compact_view_name= table->compact_view_format= FALSE;
+ else
+ {
+ /*
+ Compact output format for view body can be used
+ if this view only references table inside it's own db
+ */
+ TABLE_LIST *tbl;
+ table->compact_view_format= TRUE;
+ for (tbl= thd->lex->query_tables;
+ tbl;
+ tbl= tbl->next_global)
+ {
+ if (strcmp(table->view_db.str, tbl->view ? tbl->view_db.str :tbl->db)!= 0)
+ {
+ table->compact_view_format= FALSE;
+ break;
+ }
+ }
+ }
+
+ buff->append(STRING_WITH_LEN("CREATE "));
+ if (!foreign_db_mode)
+ {
+ view_store_options(thd, table, buff);
+ }
+ buff->append(STRING_WITH_LEN("VIEW "));
+ if (!compact_view_name)
+ {
+ append_identifier(thd, buff, table->view_db.str, table->view_db.length);
+ buff->append('.');
+ }
+ append_identifier(thd, buff, table->view_name.str, table->view_name.length);
+ buff->append(STRING_WITH_LEN(" AS "));
+
+ /*
+ We can't just use table->query, because our SQL_MODE may trigger
+ a different syntax, like when ANSI_QUOTES is defined.
+ */
+ table->view->unit.print(buff, QT_ORDINARY);
+
+ if (table->with_check != VIEW_CHECK_NONE)
+ {
+ if (table->with_check == VIEW_CHECK_LOCAL)
+ buff->append(STRING_WITH_LEN(" WITH LOCAL CHECK OPTION"));
+ else
+ buff->append(STRING_WITH_LEN(" WITH CASCADED CHECK OPTION"));
+ }
+ return 0;
+}
+
+
+/****************************************************************************
+ Return info about all processes
+ returns for each thread: thread id, user, host, db, command, info
+****************************************************************************/
+
+class thread_info :public ilink {
+public:
+ static void *operator new(size_t size)
+ {
+ return (void*) sql_alloc((uint) size);
+ }
+ static void operator delete(void *ptr __attribute__((unused)),
+ size_t size __attribute__((unused)))
+ { TRASH(ptr, size); }
+
+ ulong thread_id;
+ time_t start_time;
+ uint command;
+ const char *user,*host,*db,*proc_info,*state_info;
+ CSET_STRING query_string;
+ double progress;
+};
+
+static const char *thread_state_info(THD *tmp)
+{
+#ifndef EMBEDDED_LIBRARY
+ if (tmp->net.reading_or_writing)
+ {
+ if (tmp->net.reading_or_writing == 2)
+ return "Writing to net";
+ else if (tmp->get_command() == COM_SLEEP)
+ return "";
+ else
+ return "Reading from net";
+ }
+ else
+#endif
+ {
+ if (tmp->proc_info)
+ return tmp->proc_info;
+ else if (tmp->mysys_var && tmp->mysys_var->current_cond)
+ return "Waiting on cond";
+ else
+ return NULL;
+ }
+}
+
+void mysqld_list_processes(THD *thd,const char *user, bool verbose)
+{
+ Item *field;
+ List<Item> field_list;
+ I_List<thread_info> thread_infos;
+ ulong max_query_length= (verbose ? thd->variables.max_allowed_packet :
+ PROCESS_LIST_WIDTH);
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("mysqld_list_processes");
+
+ field_list.push_back(new Item_int("Id", 0, MY_INT32_NUM_DECIMAL_DIGITS));
+ field_list.push_back(new Item_empty_string("User", USERNAME_CHAR_LENGTH));
+ field_list.push_back(new Item_empty_string("Host",LIST_PROCESS_HOST_LEN));
+ field_list.push_back(field=new Item_empty_string("db",NAME_CHAR_LEN));
+ field->maybe_null=1;
+ field_list.push_back(new Item_empty_string("Command",16));
+ field_list.push_back(field= new Item_return_int("Time",7, MYSQL_TYPE_LONG));
+ field->unsigned_flag= 0;
+ field_list.push_back(field=new Item_empty_string("State",30));
+ field->maybe_null=1;
+ field_list.push_back(field=new Item_empty_string("Info",max_query_length));
+ field->maybe_null=1;
+ if (!thd->variables.old_mode &&
+ !(thd->variables.old_behavior & OLD_MODE_NO_PROGRESS_INFO))
+ {
+ field_list.push_back(field= new Item_float("Progress", 0.0, 3, 7));
+ field->maybe_null= 0;
+ }
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_VOID_RETURN;
+
+ if (thd->killed)
+ DBUG_VOID_RETURN;
+
+ mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
+ I_List_iterator<THD> it(threads);
+ THD *tmp;
+ while ((tmp=it++))
+ {
+ Security_context *tmp_sctx= tmp->security_ctx;
+ struct st_my_thread_var *mysys_var;
+ if ((tmp->vio_ok() || tmp->system_thread) &&
+ (!user || (tmp_sctx->user && !strcmp(tmp_sctx->user, user))))
+ {
+ thread_info *thd_info= new thread_info;
+
+ thd_info->thread_id=tmp->thread_id;
+ thd_info->user= thd->strdup(tmp_sctx->user ? tmp_sctx->user :
+ (tmp->system_thread ?
+ "system user" : "unauthenticated user"));
+ if (tmp->peer_port && (tmp_sctx->host || tmp_sctx->ip) &&
+ thd->security_ctx->host_or_ip[0])
+ {
+ if ((thd_info->host= (char*) thd->alloc(LIST_PROCESS_HOST_LEN+1)))
+ my_snprintf((char *) thd_info->host, LIST_PROCESS_HOST_LEN,
+ "%s:%u", tmp_sctx->host_or_ip, tmp->peer_port);
+ }
+ else
+ thd_info->host= thd->strdup(tmp_sctx->host_or_ip[0] ?
+ tmp_sctx->host_or_ip :
+ tmp_sctx->host ? tmp_sctx->host : "");
+ thd_info->command=(int) tmp->get_command();
+ mysql_mutex_lock(&tmp->LOCK_thd_data);
+ if ((thd_info->db= tmp->db)) // Safe test
+ thd_info->db= thd->strdup(thd_info->db);
+ if ((mysys_var= tmp->mysys_var))
+ mysql_mutex_lock(&mysys_var->mutex);
+ thd_info->proc_info= (char*) (tmp->killed >= KILL_QUERY ?
+ "Killed" : 0);
+ thd_info->state_info= thread_state_info(tmp);
+ if (mysys_var)
+ mysql_mutex_unlock(&mysys_var->mutex);
+
+ /* Lock THD mutex that protects its data when looking at it. */
+ if (tmp->query())
+ {
+ uint length= MY_MIN(max_query_length, tmp->query_length());
+ char *q= thd->strmake(tmp->query(),length);
+ /* Safety: in case strmake failed, we set length to 0. */
+ thd_info->query_string=
+ CSET_STRING(q, q ? length : 0, tmp->query_charset());
+ }
+
+ /*
+ Progress report. We need to do this under a lock to ensure that all
+ is from the same stage.
+ */
+ if (tmp->progress.max_counter)
+ {
+ uint max_stage= MY_MAX(tmp->progress.max_stage, 1);
+ thd_info->progress= (((tmp->progress.stage / (double) max_stage) +
+ ((tmp->progress.counter /
+ (double) tmp->progress.max_counter) /
+ (double) max_stage)) *
+ 100.0);
+ set_if_smaller(thd_info->progress, 100);
+ }
+ else
+ thd_info->progress= 0.0;
+ thd_info->start_time= tmp->start_time;
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+ thread_infos.append(thd_info);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ thread_info *thd_info;
+ time_t now= my_time(0);
+ char buff[20]; // For progress
+ String store_buffer(buff, sizeof(buff), system_charset_info);
+
+ while ((thd_info=thread_infos.get()))
+ {
+ protocol->prepare_for_resend();
+ protocol->store((ulonglong) thd_info->thread_id);
+ protocol->store(thd_info->user, system_charset_info);
+ protocol->store(thd_info->host, system_charset_info);
+ protocol->store(thd_info->db, system_charset_info);
+ if (thd_info->proc_info)
+ protocol->store(thd_info->proc_info, system_charset_info);
+ else
+ protocol->store(command_name[thd_info->command].str, system_charset_info);
+ if (thd_info->start_time)
+ protocol->store_long ((longlong) (now - thd_info->start_time));
+ else
+ protocol->store_null();
+ protocol->store(thd_info->state_info, system_charset_info);
+ protocol->store(thd_info->query_string.str(),
+ thd_info->query_string.charset());
+ if (!thd->variables.old_mode &&
+ !(thd->variables.old_behavior & OLD_MODE_NO_PROGRESS_INFO))
+ protocol->store(thd_info->progress, 3, &store_buffer);
+ if (protocol->write())
+ break; /* purecov: inspected */
+ }
+ my_eof(thd);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Produce EXPLAIN data.
+
+ This function is APC-scheduled to be run in the context of the thread that
+ we're producing EXPLAIN for.
+*/
+
+void Show_explain_request::call_in_target_thread()
+{
+ Query_arena backup_arena;
+ bool printed_anything= FALSE;
+
+ /*
+ Change the arena because JOIN::print_explain and co. are going to allocate
+ items. Let them allocate them on our arena.
+ */
+ target_thd->set_n_backup_active_arena((Query_arena*)request_thd,
+ &backup_arena);
+
+ query_str.copy(target_thd->query(),
+ target_thd->query_length(),
+ target_thd->query_charset());
+
+ DBUG_ASSERT(current_thd == target_thd);
+ set_current_thd(request_thd);
+ if (target_thd->lex->print_explain(explain_buf, 0 /* explain flags*/,
+ &printed_anything))
+ {
+ failed_to_produce= TRUE;
+ }
+ set_current_thd(target_thd);
+
+ if (!printed_anything)
+ failed_to_produce= TRUE;
+
+ target_thd->restore_active_arena((Query_arena*)request_thd, &backup_arena);
+}
+
+
+int select_result_explain_buffer::send_data(List<Item> &items)
+{
+ int res;
+ THD *cur_thd= current_thd;
+ DBUG_ENTER("select_result_explain_buffer::send_data");
+
+ /*
+ Switch to the recieveing thread, so that we correctly count memory used
+ by it. This is needed as it's the receiving thread that will free the
+ memory.
+ */
+ set_current_thd(thd);
+ fill_record(thd, dst_table, dst_table->field, items, TRUE, FALSE);
+ res= dst_table->file->ha_write_tmp_row(dst_table->record[0]);
+ set_current_thd(cur_thd);
+ DBUG_RETURN(MY_TEST(res));
+}
+
+bool select_result_text_buffer::send_result_set_metadata(List<Item> &fields, uint flag)
+{
+ n_columns= fields.elements;
+ return append_row(fields, true /*send item names */);
+ return send_data(fields);
+}
+
+
+int select_result_text_buffer::send_data(List<Item> &items)
+{
+ return append_row(items, false /*send item values */);
+}
+
+int select_result_text_buffer::append_row(List<Item> &items, bool send_names)
+{
+ List_iterator<Item> it(items);
+ Item *item;
+ char **row;
+ int column= 0;
+
+ if (!(row= (char**) thd->alloc(sizeof(char*) * n_columns)))
+ return true;
+ rows.push_back(row);
+
+ while ((item= it++))
+ {
+ DBUG_ASSERT(column < n_columns);
+ StringBuffer<32> buf;
+ const char *data_ptr;
+ size_t data_len;
+ if (send_names)
+ {
+ data_ptr= item->name;
+ data_len= strlen(item->name);
+ }
+ else
+ {
+ String *res;
+ res= item->val_str(&buf);
+ if (item->null_value)
+ {
+ data_ptr= "NULL";
+ data_len=4;
+ }
+ else
+ {
+ data_ptr= res->c_ptr_safe();
+ data_len= res->length();
+ }
+ }
+
+ char *ptr= (char*)thd->alloc(data_len + 1);
+ memcpy(ptr, data_ptr, data_len + 1);
+ row[column]= ptr;
+
+ column++;
+ }
+ return false;
+}
+
+
+void select_result_text_buffer::save_to(String *res)
+{
+ List_iterator<char*> it(rows);
+ char **row;
+ res->append("#\n");
+ while ((row= it++))
+ {
+ res->append("# explain: ");
+ for (int i=0; i < n_columns; i++)
+ {
+ if (i)
+ res->append('\t');
+ res->append(row[i]);
+ }
+ res->append("\n");
+ }
+ res->append("#\n");
+}
+
+
+/*
+ Store the SHOW EXPLAIN output in the temporary table.
+*/
+
+int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond)
+{
+ const char *calling_user;
+ THD *tmp;
+ my_thread_id thread_id;
+ DBUG_ENTER("fill_show_explain");
+
+ DBUG_ASSERT(cond==NULL);
+ thread_id= thd->lex->value_list.head()->val_int();
+ calling_user= (thd->security_ctx->master_access & PROCESS_ACL) ? NullS :
+ thd->security_ctx->priv_user;
+
+ if ((tmp= find_thread_by_id(thread_id)))
+ {
+ Security_context *tmp_sctx= tmp->security_ctx;
+ /*
+ If calling_user==NULL, calling thread has SUPER or PROCESS
+ privilege, and so can do SHOW EXPLAIN on any user.
+
+ if calling_user!=NULL, he's only allowed to view SHOW EXPLAIN on
+ his own threads.
+ */
+ if (calling_user && (!tmp_sctx->user || strcmp(calling_user,
+ tmp_sctx->user)))
+ {
+ my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESS");
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+ DBUG_RETURN(1);
+ }
+
+ if (tmp == thd)
+ {
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+ my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ bool bres;
+ /*
+ Ok we've found the thread of interest and it won't go away because
+ we're holding its LOCK_thd data. Post it a SHOW EXPLAIN request.
+ */
+ bool timed_out;
+ int timeout_sec= 30;
+ Show_explain_request explain_req;
+ select_result_explain_buffer *explain_buf;
+
+ explain_buf= new select_result_explain_buffer(thd, table->table);
+
+ explain_req.explain_buf= explain_buf;
+ explain_req.target_thd= tmp;
+ explain_req.request_thd= thd;
+ explain_req.failed_to_produce= FALSE;
+
+ /* Ok, we have a lock on target->LOCK_thd_data, can call: */
+ bres= tmp->apc_target.make_apc_call(thd, &explain_req, timeout_sec, &timed_out);
+
+ if (bres || explain_req.failed_to_produce)
+ {
+ if (thd->killed)
+ thd->send_kill_message();
+ else if (timed_out)
+ my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
+ else
+ my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0));
+
+ bres= TRUE;
+ }
+ else
+ {
+ /*
+ Push the query string as a warning. The query may be in a different
+ charset than the charset that's used for error messages, so, convert it
+ if needed.
+ */
+ CHARSET_INFO *fromcs= explain_req.query_str.charset();
+ CHARSET_INFO *tocs= error_message_charset_info;
+ char *warning_text;
+ if (!my_charset_same(fromcs, tocs))
+ {
+ uint conv_length= 1 + tocs->mbmaxlen * explain_req.query_str.length() /
+ fromcs->mbminlen;
+ uint dummy_errors;
+ char *to, *p;
+ if (!(to= (char*)thd->alloc(conv_length + 1)))
+ DBUG_RETURN(1);
+ p= to;
+ p+= copy_and_convert(to, conv_length, tocs,
+ explain_req.query_str.c_ptr(),
+ explain_req.query_str.length(), fromcs,
+ &dummy_errors);
+ *p= 0;
+ warning_text= to;
+ }
+ else
+ warning_text= explain_req.query_str.c_ptr_safe();
+
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_YES, warning_text);
+ }
+ DBUG_RETURN(bres);
+ }
+ else
+ {
+ my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id);
+ DBUG_RETURN(1);
+ }
+}
+
+
+int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond)
+{
+ TABLE *table= tables->table;
+ CHARSET_INFO *cs= system_charset_info;
+ char *user;
+ my_hrtime_t unow= my_hrtime();
+ DBUG_ENTER("fill_schema_processlist");
+
+ DEBUG_SYNC(thd,"fill_schema_processlist_after_unow");
+
+ user= thd->security_ctx->master_access & PROCESS_ACL ?
+ NullS : thd->security_ctx->priv_user;
+
+ mysql_mutex_lock(&LOCK_thread_count);
+
+ if (!thd->killed)
+ {
+ I_List_iterator<THD> it(threads);
+ THD* tmp;
+
+ while ((tmp= it++))
+ {
+ Security_context *tmp_sctx= tmp->security_ctx;
+ struct st_my_thread_var *mysys_var;
+ const char *val, *db;
+ ulonglong max_counter;
+
+ if ((!tmp->vio_ok() && !tmp->system_thread) ||
+ (user && (!tmp_sctx->user || strcmp(tmp_sctx->user, user))))
+ continue;
+
+ restore_record(table, s->default_values);
+ /* ID */
+ table->field[0]->store((longlong) tmp->thread_id, TRUE);
+ /* USER */
+ val= tmp_sctx->user ? tmp_sctx->user :
+ (tmp->system_thread ? "system user" : "unauthenticated user");
+ table->field[1]->store(val, strlen(val), cs);
+ /* HOST */
+ if (tmp->peer_port && (tmp_sctx->host || tmp_sctx->ip) &&
+ thd->security_ctx->host_or_ip[0])
+ {
+ char host[LIST_PROCESS_HOST_LEN + 1];
+ my_snprintf(host, LIST_PROCESS_HOST_LEN, "%s:%u",
+ tmp_sctx->host_or_ip, tmp->peer_port);
+ table->field[2]->store(host, strlen(host), cs);
+ }
+ else
+ table->field[2]->store(tmp_sctx->host_or_ip,
+ strlen(tmp_sctx->host_or_ip), cs);
+ /* DB */
+ mysql_mutex_lock(&tmp->LOCK_thd_data);
+ if ((db= tmp->db))
+ {
+ table->field[3]->store(db, strlen(db), cs);
+ table->field[3]->set_notnull();
+ }
+
+ if ((mysys_var= tmp->mysys_var))
+ mysql_mutex_lock(&mysys_var->mutex);
+ /* COMMAND */
+ if ((val= (char *) ((tmp->killed >= KILL_QUERY ?
+ "Killed" : 0))))
+ table->field[4]->store(val, strlen(val), cs);
+ else
+ table->field[4]->store(command_name[tmp->get_command()].str,
+ command_name[tmp->get_command()].length, cs);
+ /* MYSQL_TIME */
+ ulonglong start_utime= tmp->start_time * HRTIME_RESOLUTION + tmp->start_time_sec_part;
+ ulonglong utime= start_utime && start_utime < unow.val
+ ? unow.val - start_utime : 0;
+ table->field[5]->store(utime / HRTIME_RESOLUTION, TRUE);
+ /* STATE */
+ if ((val= thread_state_info(tmp)))
+ {
+ table->field[6]->store(val, strlen(val), cs);
+ table->field[6]->set_notnull();
+ }
+
+ if (mysys_var)
+ mysql_mutex_unlock(&mysys_var->mutex);
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+
+ /* TIME_MS */
+ table->field[8]->store((double)(utime / (HRTIME_RESOLUTION / 1000.0)));
+
+ /* INFO */
+ /* Lock THD mutex that protects its data when looking at it. */
+ mysql_mutex_lock(&tmp->LOCK_thd_data);
+ if (tmp->query())
+ {
+ table->field[7]->store(tmp->query(),
+ MY_MIN(PROCESS_LIST_INFO_WIDTH,
+ tmp->query_length()), cs);
+ table->field[7]->set_notnull();
+ }
+
+ /*
+ Progress report. We need to do this under a lock to ensure that all
+ is from the same stage.
+ */
+ if ((max_counter= tmp->progress.max_counter))
+ {
+ table->field[9]->store((longlong) tmp->progress.stage + 1, 1);
+ table->field[10]->store((longlong) tmp->progress.max_stage, 1);
+ table->field[11]->store((double) tmp->progress.counter /
+ (double) max_counter*100.0);
+ }
+ mysql_mutex_unlock(&tmp->LOCK_thd_data);
+
+ /*
+ This may become negative if we free a memory allocated by another
+ thread in this thread. However it's better that we notice it eventually
+ than hide it.
+ */
+ table->field[12]->store((longlong) (tmp->status_var.memory_used +
+ sizeof(THD)),
+ FALSE);
+ table->field[12]->set_notnull();
+ table->field[13]->store((longlong) tmp->get_examined_row_count(), TRUE);
+ table->field[13]->set_notnull();
+
+ /* QUERY_ID */
+ table->field[14]->store(tmp->query_id, TRUE);
+
+ if (schema_table_store_record(thd, table))
+ {
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_RETURN(1);
+ }
+ }
+ }
+
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_RETURN(0);
+}
+
+/*****************************************************************************
+ Status functions
+*****************************************************************************/
+
+static DYNAMIC_ARRAY all_status_vars;
+static bool status_vars_inited= 0;
+
+C_MODE_START
+static int show_var_cmp(const void *var1, const void *var2)
+{
+ return strcasecmp(((SHOW_VAR*)var1)->name, ((SHOW_VAR*)var2)->name);
+}
+C_MODE_END
+
+/*
+ deletes all the SHOW_UNDEF elements from the array and calls
+ delete_dynamic() if it's completely empty.
+*/
+static void shrink_var_array(DYNAMIC_ARRAY *array)
+{
+ uint a,b;
+ SHOW_VAR *all= dynamic_element(array, 0, SHOW_VAR *);
+
+ for (a= b= 0; b < array->elements; b++)
+ if (all[b].type != SHOW_UNDEF)
+ all[a++]= all[b];
+ if (a)
+ {
+ bzero(all+a, sizeof(SHOW_VAR)); // writing NULL-element to the end
+ array->elements= a;
+ }
+ else // array is completely empty - delete it
+ delete_dynamic(array);
+}
+
+/*
+ Adds an array of SHOW_VAR entries to the output of SHOW STATUS
+
+ SYNOPSIS
+ add_status_vars(SHOW_VAR *list)
+ list - an array of SHOW_VAR entries to add to all_status_vars
+ the last entry must be {0,0,SHOW_UNDEF}
+
+ NOTE
+ The handling of all_status_vars[] is completely internal, it's allocated
+ automatically when something is added to it, and deleted completely when
+ the last entry is removed.
+
+ As a special optimization, if add_status_vars() is called before
+ init_status_vars(), it assumes "startup mode" - neither concurrent access
+ to the array nor SHOW STATUS are possible (thus it skips locks and qsort)
+
+ The last entry of the all_status_vars[] should always be {0,0,SHOW_UNDEF}
+*/
+int add_status_vars(SHOW_VAR *list)
+{
+ int res= 0;
+ if (status_vars_inited)
+ mysql_mutex_lock(&LOCK_show_status);
+ if (!all_status_vars.buffer && // array is not allocated yet - do it now
+ my_init_dynamic_array(&all_status_vars, sizeof(SHOW_VAR), 200, 20, MYF(0)))
+ {
+ res= 1;
+ goto err;
+ }
+ while (list->name)
+ res|= insert_dynamic(&all_status_vars, (uchar*)list++);
+ res|= insert_dynamic(&all_status_vars, (uchar*)list); // appending NULL-element
+ all_status_vars.elements--; // but next insert_dynamic should overwite it
+ if (status_vars_inited)
+ sort_dynamic(&all_status_vars, show_var_cmp);
+err:
+ if (status_vars_inited)
+ mysql_mutex_unlock(&LOCK_show_status);
+ return res;
+}
+
+/*
+ Make all_status_vars[] usable for SHOW STATUS
+
+ NOTE
+ See add_status_vars(). Before init_status_vars() call, add_status_vars()
+ works in a special fast "startup" mode. Thus init_status_vars()
+ should be called as late as possible but before enabling multi-threading.
+*/
+void init_status_vars()
+{
+ status_vars_inited=1;
+ sort_dynamic(&all_status_vars, show_var_cmp);
+}
+
+void reset_status_vars()
+{
+ SHOW_VAR *ptr= (SHOW_VAR*) all_status_vars.buffer;
+ SHOW_VAR *last= ptr + all_status_vars.elements;
+ for (; ptr < last; ptr++)
+ {
+ /* Note that SHOW_LONG_NOFLUSH variables are not reset */
+ if (ptr->type == SHOW_LONG)
+ *(ulong*) ptr->value= 0;
+ }
+}
+
+/*
+ catch-all cleanup function, cleans up everything no matter what
+
+ DESCRIPTION
+ This function is not strictly required if all add_status_vars/
+ remove_status_vars are properly paired, but it's a safety measure that
+ deletes everything from the all_status_vars[] even if some
+ remove_status_vars were forgotten
+*/
+void free_status_vars()
+{
+ delete_dynamic(&all_status_vars);
+}
+
+/*
+ Removes an array of SHOW_VAR entries from the output of SHOW STATUS
+
+ SYNOPSIS
+ remove_status_vars(SHOW_VAR *list)
+ list - an array of SHOW_VAR entries to remove to all_status_vars
+ the last entry must be {0,0,SHOW_UNDEF}
+
+ NOTE
+ there's lots of room for optimizing this, especially in non-sorted mode,
+ but nobody cares - it may be called only in case of failed plugin
+ initialization in the mysqld startup.
+*/
+
+void remove_status_vars(SHOW_VAR *list)
+{
+ if (status_vars_inited)
+ {
+ mysql_mutex_lock(&LOCK_show_status);
+ SHOW_VAR *all= dynamic_element(&all_status_vars, 0, SHOW_VAR *);
+
+ for (; list->name; list++)
+ {
+ int first= 0, last= ((int) all_status_vars.elements) - 1;
+ for ( ; first <= last; )
+ {
+ int res, middle= (first + last) / 2;
+ if ((res= show_var_cmp(list, all + middle)) < 0)
+ last= middle - 1;
+ else if (res > 0)
+ first= middle + 1;
+ else
+ {
+ all[middle].type= SHOW_UNDEF;
+ break;
+ }
+ }
+ }
+ shrink_var_array(&all_status_vars);
+ mysql_mutex_unlock(&LOCK_show_status);
+ }
+ else
+ {
+ SHOW_VAR *all= dynamic_element(&all_status_vars, 0, SHOW_VAR *);
+ uint i;
+ for (; list->name; list++)
+ {
+ for (i= 0; i < all_status_vars.elements; i++)
+ {
+ if (show_var_cmp(list, all+i))
+ continue;
+ all[i].type= SHOW_UNDEF;
+ break;
+ }
+ }
+ shrink_var_array(&all_status_vars);
+ }
+}
+
+
+
+static bool show_status_array(THD *thd, const char *wild,
+ SHOW_VAR *variables,
+ enum enum_var_type value_type,
+ struct system_status_var *status_var,
+ const char *prefix, TABLE *table,
+ bool ucase_names,
+ COND *cond)
+{
+ my_aligned_storage<SHOW_VAR_FUNC_BUFF_SIZE, MY_ALIGNOF(long)> buffer;
+ char * const buff= buffer.data;
+ char *prefix_end;
+ /* the variable name should not be longer than 64 characters */
+ char name_buffer[64];
+ int len;
+ LEX_STRING null_lex_str;
+ SHOW_VAR tmp, *var;
+ enum_check_fields save_count_cuted_fields= thd->count_cuted_fields;
+ bool res= FALSE;
+ CHARSET_INFO *charset= system_charset_info;
+ DBUG_ENTER("show_status_array");
+
+ thd->count_cuted_fields= CHECK_FIELD_WARN;
+ null_lex_str.str= 0; // For sys_var->value_ptr()
+ null_lex_str.length= 0;
+
+ prefix_end=strnmov(name_buffer, prefix, sizeof(name_buffer)-1);
+ if (*prefix)
+ *prefix_end++= '_';
+ len=name_buffer + sizeof(name_buffer) - prefix_end;
+
+ for (; variables->name; variables++)
+ {
+ bool wild_checked;
+ strnmov(prefix_end, variables->name, len);
+ name_buffer[sizeof(name_buffer)-1]=0; /* Safety */
+ if (ucase_names)
+ my_caseup_str(system_charset_info, name_buffer);
+ else
+ {
+ my_casedn_str(system_charset_info, name_buffer);
+ DBUG_ASSERT(name_buffer[0] >= 'a');
+ DBUG_ASSERT(name_buffer[0] <= 'z');
+
+ /* traditionally status variables have a first letter uppercased */
+ if (status_var)
+ name_buffer[0]-= 'a' - 'A';
+ }
+
+
+ restore_record(table, s->default_values);
+ table->field[0]->store(name_buffer, strlen(name_buffer),
+ system_charset_info);
+
+ /*
+ Compare name for types that can't return arrays. We do this to not
+ calculate the value for function variables that we will not access
+ */
+ if ((variables->type != SHOW_FUNC && variables->type != SHOW_ARRAY))
+ {
+ if (wild && wild[0] && wild_case_compare(system_charset_info,
+ name_buffer, wild))
+ continue;
+ wild_checked= 1; // Avoid checking it again
+ }
+
+ /*
+ if var->type is SHOW_FUNC or SHOW_SIMPLE_FUNC, call the function.
+ Repeat as necessary, if new var is again one of the above
+ */
+ for (var=variables; var->type == SHOW_FUNC ||
+ var->type == SHOW_SIMPLE_FUNC; var= &tmp)
+ ((mysql_show_var_func)(var->value))(thd, &tmp, buff);
+
+ SHOW_TYPE show_type=var->type;
+ if (show_type == SHOW_ARRAY)
+ {
+ show_status_array(thd, wild, (SHOW_VAR *) var->value, value_type,
+ status_var, name_buffer, table, ucase_names, cond);
+ }
+ else
+ {
+ if ((wild_checked ||
+ (wild && wild[0] && wild_case_compare(system_charset_info,
+ name_buffer, wild))) &&
+ (!cond || cond->val_int()))
+ {
+ char *value=var->value;
+ const char *pos, *end; // We assign a lot of const's
+
+ if (show_type == SHOW_SYS)
+ {
+ sys_var *var= ((sys_var *) value);
+ show_type= var->show_type();
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ value= (char*) var->value_ptr(thd, value_type, &null_lex_str);
+ charset= var->charset(thd);
+ }
+
+ pos= end= buff;
+ /*
+ note that value may be == buff. All SHOW_xxx code below
+ should still work in this case
+ */
+ switch (show_type) {
+ case SHOW_DOUBLE_STATUS:
+ value= ((char *) status_var + (intptr) value);
+ /* fall through */
+ case SHOW_DOUBLE:
+ /* 6 is the default precision for '%f' in sprintf() */
+ end= buff + my_fcvt(*(double *) value, 6, buff, NULL);
+ break;
+ case SHOW_LONG_STATUS:
+ value= ((char *) status_var + (intptr) value);
+ /* fall through */
+ case SHOW_ULONG:
+ case SHOW_LONG_NOFLUSH: // the difference lies in refresh_status()
+ end= int10_to_str(*(long*) value, buff, 10);
+ break;
+ case SHOW_LONGLONG_STATUS:
+ value= ((char *) status_var + (intptr) value);
+ /* fall through */
+ case SHOW_ULONGLONG:
+ end= longlong10_to_str(*(longlong*) value, buff, 10);
+ break;
+ case SHOW_HA_ROWS:
+ end= longlong10_to_str((longlong) *(ha_rows*) value, buff, 10);
+ break;
+ case SHOW_BOOL:
+ end= strmov(buff, *(bool*) value ? "ON" : "OFF");
+ break;
+ case SHOW_MY_BOOL:
+ end= strmov(buff, *(my_bool*) value ? "ON" : "OFF");
+ break;
+ case SHOW_UINT:
+ end= int10_to_str((long) *(uint*) value, buff, 10);
+ break;
+ case SHOW_SINT:
+ end= int10_to_str((long) *(int*) value, buff, -10);
+ break;
+ case SHOW_SLONG:
+ end= int10_to_str(*(long*) value, buff, -10);
+ break;
+ case SHOW_SLONGLONG:
+ end= longlong10_to_str(*(longlong*) value, buff, -10);
+ break;
+ case SHOW_HAVE:
+ {
+ SHOW_COMP_OPTION tmp= *(SHOW_COMP_OPTION*) value;
+ pos= show_comp_option_name[(int) tmp];
+ end= strend(pos);
+ break;
+ }
+ case SHOW_CHAR:
+ {
+ if (!(pos= value))
+ pos= "";
+ end= strend(pos);
+ break;
+ }
+ case SHOW_CHAR_PTR:
+ {
+ if (!(pos= *(char**) value))
+ pos= "";
+
+ DBUG_EXECUTE_IF("alter_server_version_str",
+ if (!my_strcasecmp(system_charset_info,
+ variables->name,
+ "version")) {
+ pos= "some-other-version";
+ });
+
+ end= strend(pos);
+ break;
+ }
+ case SHOW_LEX_STRING:
+ {
+ LEX_STRING *ls=(LEX_STRING*)value;
+ if (!(pos= ls->str))
+ end= pos= "";
+ else
+ end= pos + ls->length;
+ break;
+ }
+ case SHOW_UNDEF:
+ break; // Return empty string
+ case SHOW_SYS: // Cannot happen
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ table->field[1]->store(pos, (uint32) (end - pos), charset);
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+ table->field[1]->set_notnull();
+
+ if (var->type == SHOW_SYS)
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ if (schema_table_store_record(thd, table))
+ {
+ res= TRUE;
+ goto end;
+ }
+ }
+ }
+ }
+end:
+ thd->count_cuted_fields= save_count_cuted_fields;
+ DBUG_RETURN(res);
+}
+
+#ifdef COMPLETE_PATCH_NOT_ADDED_YET
+/*
+ Aggregate values for mapped_user entries by their role.
+
+ SYNOPSIS
+ aggregate_user_stats
+ all_user_stats - input to aggregate
+ agg_user_stats - returns aggregated values
+
+ RETURN
+ 0 - OK
+ 1 - error
+*/
+
+static int aggregate_user_stats(HASH *all_user_stats, HASH *agg_user_stats)
+{
+ DBUG_ENTER("aggregate_user_stats");
+ if (my_hash_init(agg_user_stats, system_charset_info,
+ MY_MAX(all_user_stats->records, 1),
+ 0, 0, (my_hash_get_key)get_key_user_stats,
+ (my_hash_free_key)free_user_stats, 0))
+ {
+ sql_print_error("Malloc in aggregate_user_stats failed");
+ DBUG_RETURN(1);
+ }
+
+ for (uint i= 0; i < all_user_stats->records; i++)
+ {
+ USER_STATS *user= (USER_STATS*)my_hash_element(all_user_stats, i);
+ USER_STATS *agg_user;
+ uint name_length= strlen(user->priv_user);
+
+ if (!(agg_user= (USER_STATS*) my_hash_search(agg_user_stats,
+ (uchar*)user->priv_user,
+ name_length)))
+ {
+ // First entry for this role.
+ if (!(agg_user= (USER_STATS*) my_malloc(sizeof(USER_STATS),
+ MYF(MY_WME | MY_ZEROFILL|
+ MY_THREAD_SPECIFIC))))
+ {
+ sql_print_error("Malloc in aggregate_user_stats failed");
+ DBUG_RETURN(1);
+ }
+
+ init_user_stats(agg_user, user->priv_user, name_length,
+ user->priv_user,
+ user->total_connections, user->concurrent_connections,
+ user->connected_time, user->busy_time, user->cpu_time,
+ user->bytes_received, user->bytes_sent,
+ user->binlog_bytes_written,
+ user->rows_sent, user->rows_read,
+ user->rows_inserted, user->rows_deleted,
+ user->rows_updated,
+ user->select_commands, user->update_commands,
+ user->other_commands,
+ user->commit_trans, user->rollback_trans,
+ user->denied_connections, user->lost_connections,
+ user->access_denied_errors, user->empty_queries);
+
+ if (my_hash_insert(agg_user_stats, (uchar*) agg_user))
+ {
+ /* Out of memory */
+ my_free(agg_user, 0);
+ sql_print_error("Malloc in aggregate_user_stats failed");
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ /* Aggregate with existing values for this role. */
+ add_user_stats(agg_user,
+ user->total_connections, user->concurrent_connections,
+ user->connected_time, user->busy_time, user->cpu_time,
+ user->bytes_received, user->bytes_sent,
+ user->binlog_bytes_written,
+ user->rows_sent, user->rows_read,
+ user->rows_inserted, user->rows_deleted,
+ user->rows_updated,
+ user->select_commands, user->update_commands,
+ user->other_commands,
+ user->commit_trans, user->rollback_trans,
+ user->denied_connections, user->lost_connections,
+ user->access_denied_errors, user->empty_queries);
+ }
+ }
+ DBUG_PRINT("exit", ("aggregated %lu input into %lu output entries",
+ all_user_stats->records, agg_user_stats->records));
+ DBUG_RETURN(0);
+}
+#endif
+
+/*
+ Write result to network for SHOW USER_STATISTICS
+
+ SYNOPSIS
+ send_user_stats
+ all_user_stats - values to return
+ table - I_S table
+
+ RETURN
+ 0 - OK
+ 1 - error
+*/
+
+int send_user_stats(THD* thd, HASH *all_user_stats, TABLE *table)
+{
+ DBUG_ENTER("send_user_stats");
+
+ for (uint i= 0; i < all_user_stats->records; i++)
+ {
+ uint j= 0;
+ USER_STATS *user_stats= (USER_STATS*) my_hash_element(all_user_stats, i);
+
+ table->field[j++]->store(user_stats->user, user_stats->user_name_length,
+ system_charset_info);
+ table->field[j++]->store((longlong)user_stats->total_connections,TRUE);
+ table->field[j++]->store((longlong)user_stats->concurrent_connections, TRUE);
+ table->field[j++]->store((longlong)user_stats->connected_time, TRUE);
+ table->field[j++]->store((double)user_stats->busy_time);
+ table->field[j++]->store((double)user_stats->cpu_time);
+ table->field[j++]->store((longlong)user_stats->bytes_received, TRUE);
+ table->field[j++]->store((longlong)user_stats->bytes_sent, TRUE);
+ table->field[j++]->store((longlong)user_stats->binlog_bytes_written, TRUE);
+ table->field[j++]->store((longlong)user_stats->rows_read, TRUE);
+ table->field[j++]->store((longlong)user_stats->rows_sent, TRUE);
+ table->field[j++]->store((longlong)user_stats->rows_deleted, TRUE);
+ table->field[j++]->store((longlong)user_stats->rows_inserted, TRUE);
+ table->field[j++]->store((longlong)user_stats->rows_updated, TRUE);
+ table->field[j++]->store((longlong)user_stats->select_commands, TRUE);
+ table->field[j++]->store((longlong)user_stats->update_commands, TRUE);
+ table->field[j++]->store((longlong)user_stats->other_commands, TRUE);
+ table->field[j++]->store((longlong)user_stats->commit_trans, TRUE);
+ table->field[j++]->store((longlong)user_stats->rollback_trans, TRUE);
+ table->field[j++]->store((longlong)user_stats->denied_connections, TRUE);
+ table->field[j++]->store((longlong)user_stats->lost_connections, TRUE);
+ table->field[j++]->store((longlong)user_stats->access_denied_errors, TRUE);
+ table->field[j++]->store((longlong)user_stats->empty_queries, TRUE);
+ if (schema_table_store_record(thd, table))
+ {
+ DBUG_PRINT("error", ("store record error"));
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+/*
+ Process SHOW USER_STATISTICS
+
+ SYNOPSIS
+ mysqld_show_user_stats
+ thd - current thread
+ wild - limit results to the entry for this user
+ with_roles - when true, display role for mapped users
+
+ RETURN
+ 0 - OK
+ 1 - error
+*/
+
+int fill_schema_user_stats(THD* thd, TABLE_LIST* tables, COND* cond)
+{
+ TABLE *table= tables->table;
+ int result;
+ DBUG_ENTER("fill_schema_user_stats");
+
+ if (check_global_access(thd, SUPER_ACL | PROCESS_ACL, true))
+ DBUG_RETURN(0);
+
+ /*
+ Iterates through all the global stats and sends them to the client.
+ Pattern matching on the client IP is supported.
+ */
+
+ mysql_mutex_lock(&LOCK_global_user_client_stats);
+ result= send_user_stats(thd, &global_user_stats, table) != 0;
+ mysql_mutex_unlock(&LOCK_global_user_client_stats);
+
+ DBUG_PRINT("exit", ("result: %d", result));
+ DBUG_RETURN(result);
+}
+
+/*
+ Process SHOW CLIENT_STATISTICS
+
+ SYNOPSIS
+ mysqld_show_client_stats
+ thd - current thread
+ wild - limit results to the entry for this client
+
+ RETURN
+ 0 - OK
+ 1 - error
+*/
+
+int fill_schema_client_stats(THD* thd, TABLE_LIST* tables, COND* cond)
+{
+ TABLE *table= tables->table;
+ int result;
+ DBUG_ENTER("fill_schema_client_stats");
+
+ if (check_global_access(thd, SUPER_ACL | PROCESS_ACL, true))
+ DBUG_RETURN(0);
+
+ /*
+ Iterates through all the global stats and sends them to the client.
+ Pattern matching on the client IP is supported.
+ */
+
+ mysql_mutex_lock(&LOCK_global_user_client_stats);
+ result= send_user_stats(thd, &global_client_stats, table) != 0;
+ mysql_mutex_unlock(&LOCK_global_user_client_stats);
+
+ DBUG_PRINT("exit", ("result: %d", result));
+ DBUG_RETURN(result);
+}
+
+
+/* Fill information schema table with table statistics */
+
+int fill_schema_table_stats(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ TABLE *table= tables->table;
+ DBUG_ENTER("fill_schema_table_stats");
+
+ mysql_mutex_lock(&LOCK_global_table_stats);
+ for (uint i= 0; i < global_table_stats.records; i++)
+ {
+ char *end_of_schema;
+ TABLE_STATS *table_stats=
+ (TABLE_STATS*)my_hash_element(&global_table_stats, i);
+ TABLE_LIST tmp_table;
+ size_t schema_length, table_name_length;
+
+ end_of_schema= strend(table_stats->table);
+ schema_length= (size_t) (end_of_schema - table_stats->table);
+ table_name_length= strlen(table_stats->table + schema_length + 1);
+
+ bzero((char*) &tmp_table,sizeof(tmp_table));
+ tmp_table.db= table_stats->table;
+ tmp_table.table_name= end_of_schema+1;
+ tmp_table.grant.privilege= 0;
+ if (check_access(thd, SELECT_ACL, tmp_table.db,
+ &tmp_table.grant.privilege, NULL, 0, 1) ||
+ check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX,
+ 1))
+ continue;
+
+ table->field[0]->store(table_stats->table, schema_length,
+ system_charset_info);
+ table->field[1]->store(table_stats->table + schema_length+1,
+ table_name_length, system_charset_info);
+ table->field[2]->store((longlong)table_stats->rows_read, TRUE);
+ table->field[3]->store((longlong)table_stats->rows_changed, TRUE);
+ table->field[4]->store((longlong)table_stats->rows_changed_x_indexes,
+ TRUE);
+ if (schema_table_store_record(thd, table))
+ {
+ mysql_mutex_unlock(&LOCK_global_table_stats);
+ DBUG_RETURN(1);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_global_table_stats);
+ DBUG_RETURN(0);
+}
+
+
+/* Fill information schema table with index statistics */
+
+int fill_schema_index_stats(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ TABLE *table= tables->table;
+ DBUG_ENTER("fill_schema_index_stats");
+
+ mysql_mutex_lock(&LOCK_global_index_stats);
+ for (uint i= 0; i < global_index_stats.records; i++)
+ {
+ INDEX_STATS *index_stats =
+ (INDEX_STATS*) my_hash_element(&global_index_stats, i);
+ TABLE_LIST tmp_table;
+ char *index_name;
+ size_t schema_name_length, table_name_length, index_name_length;
+
+ bzero((char*) &tmp_table,sizeof(tmp_table));
+ tmp_table.db= index_stats->index;
+ tmp_table.table_name= strend(index_stats->index)+1;
+ tmp_table.grant.privilege= 0;
+ if (check_access(thd, SELECT_ACL, tmp_table.db,
+ &tmp_table.grant.privilege, NULL, 0, 1) ||
+ check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX, 1))
+ continue;
+
+ index_name= strend(tmp_table.table_name)+1;
+ schema_name_length= (tmp_table.table_name - index_stats->index) -1;
+ table_name_length= (index_name - tmp_table.table_name)-1;
+ index_name_length= (index_stats->index_name_length - schema_name_length -
+ table_name_length - 3);
+
+ table->field[0]->store(tmp_table.db, schema_name_length,
+ system_charset_info);
+ table->field[1]->store(tmp_table.table_name, table_name_length,
+ system_charset_info);
+ table->field[2]->store(index_name, index_name_length, system_charset_info);
+ table->field[3]->store((longlong)index_stats->rows_read, TRUE);
+
+ if (schema_table_store_record(thd, table))
+ {
+ mysql_mutex_unlock(&LOCK_global_index_stats);
+ DBUG_RETURN(1);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_global_index_stats);
+ DBUG_RETURN(0);
+}
+
+
+/* collect status for all running threads */
+
+void calc_sum_of_all_status(STATUS_VAR *to)
+{
+ DBUG_ENTER("calc_sum_of_all_status");
+
+ /* Ensure that thread id not killed during loop */
+ mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
+
+ I_List_iterator<THD> it(threads);
+ THD *tmp;
+
+ /* Get global values as base */
+ *to= global_status_var;
+
+ /* Add to this status from existing threads */
+ while ((tmp= it++))
+ add_to_status(to, &tmp->status_var);
+
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_VOID_RETURN;
+}
+
+
+/* This is only used internally, but we need it here as a forward reference */
+extern ST_SCHEMA_TABLE schema_tables[];
+
+/*
+ Store record to I_S table, convert HEAP table
+ to MyISAM if necessary
+
+ SYNOPSIS
+ schema_table_store_record()
+ thd thread handler
+ table Information schema table to be updated
+
+ RETURN
+ 0 success
+ 1 error
+*/
+
+bool schema_table_store_record(THD *thd, TABLE *table)
+{
+ int error;
+ if ((error= table->file->ha_write_tmp_row(table->record[0])))
+ {
+ TMP_TABLE_PARAM *param= table->pos_in_table_list->schema_table_param;
+ if (create_internal_tmp_table_from_heap(thd, table, param->start_recinfo,
+ &param->recinfo, error, 0, NULL))
+
+ return 1;
+ }
+ return 0;
+}
+
+
+static int make_table_list(THD *thd, SELECT_LEX *sel,
+ LEX_STRING *db_name, LEX_STRING *table_name)
+{
+ Table_ident *table_ident;
+ table_ident= new Table_ident(thd, *db_name, *table_name, 1);
+ if (!sel->add_table_to_list(thd, table_ident, 0, 0, TL_READ, MDL_SHARED_READ))
+ return 1;
+ return 0;
+}
+
+
+/**
+ @brief Get lookup value from the part of 'WHERE' condition
+
+ @details This function gets lookup value from
+ the part of 'WHERE' condition if it's possible and
+ fill appropriate lookup_field_vals struct field
+ with this value.
+
+ @param[in] thd thread handler
+ @param[in] item_func part of WHERE condition
+ @param[in] table I_S table
+ @param[in, out] lookup_field_vals Struct which holds lookup values
+
+ @return
+ 0 success
+ 1 error, there can be no matching records for the condition
+*/
+
+bool get_lookup_value(THD *thd, Item_func *item_func,
+ TABLE_LIST *table,
+ LOOKUP_FIELD_VALUES *lookup_field_vals)
+{
+ ST_SCHEMA_TABLE *schema_table= table->schema_table;
+ ST_FIELD_INFO *field_info= schema_table->fields_info;
+ const char *field_name1= schema_table->idx_field1 >= 0 ?
+ field_info[schema_table->idx_field1].field_name : "";
+ const char *field_name2= schema_table->idx_field2 >= 0 ?
+ field_info[schema_table->idx_field2].field_name : "";
+
+ if (item_func->functype() == Item_func::EQ_FUNC ||
+ item_func->functype() == Item_func::EQUAL_FUNC)
+ {
+ int idx_field, idx_val;
+ char tmp[MAX_FIELD_WIDTH];
+ String *tmp_str, str_buff(tmp, sizeof(tmp), system_charset_info);
+ Item_field *item_field;
+ CHARSET_INFO *cs= system_charset_info;
+
+ if (item_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM &&
+ item_func->arguments()[1]->const_item())
+ {
+ idx_field= 0;
+ idx_val= 1;
+ }
+ else if (item_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM &&
+ item_func->arguments()[0]->const_item())
+ {
+ idx_field= 1;
+ idx_val= 0;
+ }
+ else
+ return 0;
+
+ item_field= (Item_field*) item_func->arguments()[idx_field]->real_item();
+ if (table->table != item_field->field->table)
+ return 0;
+ tmp_str= item_func->arguments()[idx_val]->val_str(&str_buff);
+
+ /* impossible value */
+ if (!tmp_str)
+ return 1;
+
+ /* Lookup value is database name */
+ if (!cs->coll->strnncollsp(cs, (uchar *) field_name1, strlen(field_name1),
+ (uchar *) item_field->field_name,
+ strlen(item_field->field_name), 0))
+ {
+ thd->make_lex_string(&lookup_field_vals->db_value,
+ tmp_str->ptr(), tmp_str->length());
+ }
+ /* Lookup value is table name */
+ else if (!cs->coll->strnncollsp(cs, (uchar *) field_name2,
+ strlen(field_name2),
+ (uchar *) item_field->field_name,
+ strlen(item_field->field_name), 0))
+ {
+ thd->make_lex_string(&lookup_field_vals->table_value,
+ tmp_str->ptr(), tmp_str->length());
+ }
+ }
+ return 0;
+}
+
+
+/**
+ @brief Calculates lookup values from 'WHERE' condition
+
+ @details This function calculates lookup value(database name, table name)
+ from 'WHERE' condition if it's possible and
+ fill lookup_field_vals struct fields with these values.
+
+ @param[in] thd thread handler
+ @param[in] cond WHERE condition
+ @param[in] table I_S table
+ @param[in, out] lookup_field_vals Struct which holds lookup values
+
+ @return
+ 0 success
+ 1 error, there can be no matching records for the condition
+*/
+
+bool calc_lookup_values_from_cond(THD *thd, COND *cond, TABLE_LIST *table,
+ LOOKUP_FIELD_VALUES *lookup_field_vals)
+{
+ if (!cond)
+ return 0;
+
+ if (cond->type() == Item::COND_ITEM)
+ {
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item= li++))
+ {
+ if (item->type() == Item::FUNC_ITEM)
+ {
+ if (get_lookup_value(thd, (Item_func*)item, table, lookup_field_vals))
+ return 1;
+ }
+ else
+ {
+ if (calc_lookup_values_from_cond(thd, item, table, lookup_field_vals))
+ return 1;
+ }
+ }
+ }
+ return 0;
+ }
+ else if (cond->type() == Item::FUNC_ITEM &&
+ get_lookup_value(thd, (Item_func*) cond, table, lookup_field_vals))
+ return 1;
+ return 0;
+}
+
+
+bool uses_only_table_name_fields(Item *item, TABLE_LIST *table)
+{
+ if (item->type() == Item::FUNC_ITEM)
+ {
+ Item_func *item_func= (Item_func*)item;
+ for (uint i=0; i<item_func->argument_count(); i++)
+ {
+ if (!uses_only_table_name_fields(item_func->arguments()[i], table))
+ return 0;
+ }
+ }
+ else if (item->type() == Item::FIELD_ITEM)
+ {
+ Item_field *item_field= (Item_field*)item;
+ CHARSET_INFO *cs= system_charset_info;
+ ST_SCHEMA_TABLE *schema_table= table->schema_table;
+ ST_FIELD_INFO *field_info= schema_table->fields_info;
+ const char *field_name1= schema_table->idx_field1 >= 0 ?
+ field_info[schema_table->idx_field1].field_name : "";
+ const char *field_name2= schema_table->idx_field2 >= 0 ?
+ field_info[schema_table->idx_field2].field_name : "";
+ if (table->table != item_field->field->table ||
+ (cs->coll->strnncollsp(cs, (uchar *) field_name1, strlen(field_name1),
+ (uchar *) item_field->field_name,
+ strlen(item_field->field_name), 0) &&
+ cs->coll->strnncollsp(cs, (uchar *) field_name2, strlen(field_name2),
+ (uchar *) item_field->field_name,
+ strlen(item_field->field_name), 0)))
+ return 0;
+ }
+ else if (item->type() == Item::REF_ITEM)
+ return uses_only_table_name_fields(item->real_item(), table);
+
+ if (item->real_type() == Item::SUBSELECT_ITEM && !item->const_item())
+ return 0;
+
+ return 1;
+}
+
+
+static COND * make_cond_for_info_schema(COND *cond, TABLE_LIST *table)
+{
+ if (!cond)
+ return (COND*) 0;
+ if (cond->type() == Item::COND_ITEM)
+ {
+ if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
+ {
+ /* Create new top level AND item */
+ Item_cond_and *new_cond=new Item_cond_and;
+ if (!new_cond)
+ return (COND*) 0;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix= make_cond_for_info_schema(item, table);
+ if (fix)
+ new_cond->argument_list()->push_back(fix);
+ }
+ switch (new_cond->argument_list()->elements) {
+ case 0:
+ return (COND*) 0;
+ case 1:
+ return new_cond->argument_list()->head();
+ default:
+ new_cond->quick_fix_field();
+ return new_cond;
+ }
+ }
+ else
+ { // Or list
+ Item_cond_or *new_cond=new Item_cond_or;
+ if (!new_cond)
+ return (COND*) 0;
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *fix=make_cond_for_info_schema(item, table);
+ if (!fix)
+ return (COND*) 0;
+ new_cond->argument_list()->push_back(fix);
+ }
+ new_cond->quick_fix_field();
+ new_cond->top_level_item();
+ return new_cond;
+ }
+ }
+
+ if (!uses_only_table_name_fields(cond, table))
+ return (COND*) 0;
+ return cond;
+}
+
+
+/**
+ @brief Calculate lookup values(database name, table name)
+
+ @details This function calculates lookup values(database name, table name)
+ from 'WHERE' condition or wild values (for 'SHOW' commands only)
+ from LEX struct and fill lookup_field_vals struct field
+ with these values.
+
+ @param[in] thd thread handler
+ @param[in] cond WHERE condition
+ @param[in] tables I_S table
+ @param[in, out] lookup_field_values Struct which holds lookup values
+
+ @return
+ 0 success
+ 1 error, there can be no matching records for the condition
+*/
+
+bool get_lookup_field_values(THD *thd, COND *cond, TABLE_LIST *tables,
+ LOOKUP_FIELD_VALUES *lookup_field_values)
+{
+ LEX *lex= thd->lex;
+ String *wild= lex->wild;
+ bool rc= 0;
+
+ bzero((char*) lookup_field_values, sizeof(LOOKUP_FIELD_VALUES));
+ switch (lex->sql_command) {
+ case SQLCOM_SHOW_DATABASES:
+ if (wild)
+ {
+ thd->make_lex_string(&lookup_field_values->db_value,
+ wild->ptr(), wild->length());
+ lookup_field_values->wild_db_value= 1;
+ }
+ break;
+ case SQLCOM_SHOW_TABLES:
+ case SQLCOM_SHOW_TABLE_STATUS:
+ case SQLCOM_SHOW_TRIGGERS:
+ case SQLCOM_SHOW_EVENTS:
+ thd->make_lex_string(&lookup_field_values->db_value,
+ lex->select_lex.db, strlen(lex->select_lex.db));
+ if (wild)
+ {
+ thd->make_lex_string(&lookup_field_values->table_value,
+ wild->ptr(), wild->length());
+ lookup_field_values->wild_table_value= 1;
+ }
+ break;
+ case SQLCOM_SHOW_PLUGINS:
+ if (lex->ident.str)
+ thd->make_lex_string(&lookup_field_values->db_value,
+ lex->ident.str, lex->ident.length);
+ else if (lex->wild)
+ {
+ thd->make_lex_string(&lookup_field_values->db_value,
+ lex->wild->ptr(), lex->wild->length());
+ lookup_field_values->wild_db_value= 1;
+ }
+ break;
+ default:
+ /*
+ The "default" is for queries over I_S.
+ All previous cases handle SHOW commands.
+ */
+ rc= calc_lookup_values_from_cond(thd, cond, tables, lookup_field_values);
+ break;
+ }
+
+ if (lower_case_table_names && !rc)
+ {
+ /*
+ We can safely do in-place upgrades here since all of the above cases
+ are allocating a new memory buffer for these strings.
+ */
+ if (lookup_field_values->db_value.str && lookup_field_values->db_value.str[0])
+ my_casedn_str(system_charset_info, lookup_field_values->db_value.str);
+ if (lookup_field_values->table_value.str &&
+ lookup_field_values->table_value.str[0])
+ my_casedn_str(system_charset_info, lookup_field_values->table_value.str);
+ }
+
+ return rc;
+}
+
+
+enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table)
+{
+ return (enum enum_schema_tables) (schema_table - &schema_tables[0]);
+}
+
+
+/*
+ Create db names list. Information schema name always is first in list
+
+ SYNOPSIS
+ make_db_list()
+ thd thread handler
+ files list of db names
+ wild wild string
+ idx_field_vals idx_field_vals->db_name contains db name or
+ wild string
+
+ RETURN
+ zero success
+ non-zero error
+*/
+
+int make_db_list(THD *thd, Dynamic_array<LEX_STRING*> *files,
+ LOOKUP_FIELD_VALUES *lookup_field_vals)
+{
+ if (lookup_field_vals->wild_db_value)
+ {
+ /*
+ This part of code is only for SHOW DATABASES command.
+ idx_field_vals->db_value can be 0 when we don't use
+ LIKE clause (see also get_index_field_values() function)
+ */
+ if (!lookup_field_vals->db_value.str ||
+ !wild_case_compare(system_charset_info,
+ INFORMATION_SCHEMA_NAME.str,
+ lookup_field_vals->db_value.str))
+ {
+ if (files->append_val(&INFORMATION_SCHEMA_NAME))
+ return 1;
+ }
+ return find_files(thd, files, 0, mysql_data_home,
+ &lookup_field_vals->db_value);
+ }
+
+
+ /*
+ If we have db lookup value we just add it to list and
+ exit from the function.
+ We don't do this for database names longer than the maximum
+ name length.
+ */
+ if (lookup_field_vals->db_value.str)
+ {
+ if (lookup_field_vals->db_value.length > NAME_LEN)
+ {
+ /*
+ Impossible value for a database name,
+ found in a WHERE DATABASE_NAME = 'xxx' clause.
+ */
+ return 0;
+ }
+
+ if (is_infoschema_db(lookup_field_vals->db_value.str,
+ lookup_field_vals->db_value.length))
+ {
+ if (files->append_val(&INFORMATION_SCHEMA_NAME))
+ return 1;
+ return 0;
+ }
+ if (files->append_val(&lookup_field_vals->db_value))
+ return 1;
+ return 0;
+ }
+
+ /*
+ Create list of existing databases. It is used in case
+ of select from information schema table
+ */
+ if (files->append_val(&INFORMATION_SCHEMA_NAME))
+ return 1;
+ return find_files(thd, files, 0, mysql_data_home, &null_lex_str);
+}
+
+
+struct st_add_schema_table
+{
+ Dynamic_array<LEX_STRING*> *files;
+ const char *wild;
+};
+
+
+static my_bool add_schema_table(THD *thd, plugin_ref plugin,
+ void* p_data)
+{
+ LEX_STRING *file_name= 0;
+ st_add_schema_table *data= (st_add_schema_table *)p_data;
+ Dynamic_array<LEX_STRING*> *file_list= data->files;
+ const char *wild= data->wild;
+ ST_SCHEMA_TABLE *schema_table= plugin_data(plugin, ST_SCHEMA_TABLE *);
+ DBUG_ENTER("add_schema_table");
+
+ if (schema_table->hidden)
+ DBUG_RETURN(0);
+ if (wild)
+ {
+ if (lower_case_table_names)
+ {
+ if (wild_case_compare(files_charset_info,
+ schema_table->table_name,
+ wild))
+ DBUG_RETURN(0);
+ }
+ else if (wild_compare(schema_table->table_name, wild, 0))
+ DBUG_RETURN(0);
+ }
+
+ if ((file_name= thd->make_lex_string(schema_table->table_name,
+ strlen(schema_table->table_name))) &&
+ !file_list->append(file_name))
+ DBUG_RETURN(0);
+ DBUG_RETURN(1);
+}
+
+
+int schema_tables_add(THD *thd, Dynamic_array<LEX_STRING*> *files,
+ const char *wild)
+{
+ LEX_STRING *file_name= 0;
+ ST_SCHEMA_TABLE *tmp_schema_table= schema_tables;
+ st_add_schema_table add_data;
+ DBUG_ENTER("schema_tables_add");
+
+ for (; tmp_schema_table->table_name; tmp_schema_table++)
+ {
+ if (tmp_schema_table->hidden)
+ continue;
+ if (wild)
+ {
+ if (lower_case_table_names)
+ {
+ if (wild_case_compare(files_charset_info,
+ tmp_schema_table->table_name,
+ wild))
+ continue;
+ }
+ else if (wild_compare(tmp_schema_table->table_name, wild, 0))
+ continue;
+ }
+ if ((file_name=
+ thd->make_lex_string(tmp_schema_table->table_name,
+ strlen(tmp_schema_table->table_name))) &&
+ !files->append(file_name))
+ continue;
+ DBUG_RETURN(1);
+ }
+
+ add_data.files= files;
+ add_data.wild= wild;
+ if (plugin_foreach(thd, add_schema_table,
+ MYSQL_INFORMATION_SCHEMA_PLUGIN, &add_data))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Create table names list
+
+ @details The function creates the list of table names in
+ database
+
+ @param[in] thd thread handler
+ @param[in] table_names List of table names in database
+ @param[in] lex pointer to LEX struct
+ @param[in] lookup_field_vals pointer to LOOKUP_FIELD_VALUE struct
+ @param[in] db_name database name
+
+ @return Operation status
+ @retval 0 ok
+ @retval 1 fatal error
+ @retval 2 Not fatal error; Safe to ignore this file list
+*/
+
+static int
+make_table_name_list(THD *thd, Dynamic_array<LEX_STRING*> *table_names,
+ LEX *lex, LOOKUP_FIELD_VALUES *lookup_field_vals,
+ LEX_STRING *db_name)
+{
+ char path[FN_REFLEN + 1];
+ build_table_filename(path, sizeof(path) - 1, db_name->str, "", "", 0);
+ if (!lookup_field_vals->wild_table_value &&
+ lookup_field_vals->table_value.str)
+ {
+ if (lookup_field_vals->table_value.length > NAME_LEN)
+ {
+ /*
+ Impossible value for a table name,
+ found in a WHERE TABLE_NAME = 'xxx' clause.
+ */
+ return 0;
+ }
+ if (db_name == &INFORMATION_SCHEMA_NAME)
+ {
+ LEX_STRING *name;
+ ST_SCHEMA_TABLE *schema_table=
+ find_schema_table(thd, lookup_field_vals->table_value.str);
+ if (schema_table && !schema_table->hidden)
+ {
+ if (!(name= thd->make_lex_string(schema_table->table_name,
+ strlen(schema_table->table_name))) ||
+ table_names->append(name))
+ return 1;
+ }
+ }
+ else
+ {
+ if (table_names->append_val(&lookup_field_vals->table_value))
+ return 1;
+ }
+ return 0;
+ }
+
+ /*
+ This call will add all matching the wildcards (if specified) IS tables
+ to the list
+ */
+ if (db_name == &INFORMATION_SCHEMA_NAME)
+ return (schema_tables_add(thd, table_names,
+ lookup_field_vals->table_value.str));
+
+ find_files_result res= find_files(thd, table_names, db_name, path,
+ &lookup_field_vals->table_value);
+ if (res != FIND_FILES_OK)
+ {
+ /*
+ Downgrade errors about problems with database directory to
+ warnings if this is not a 'SHOW' command. Another thread
+ may have dropped database, and we may still have a name
+ for that directory.
+ */
+ if (res == FIND_FILES_DIR)
+ {
+ if (sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND)
+ return 1;
+ thd->clear_error();
+ return 2;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ Fill I_S table with data obtained by performing full-blown table open.
+
+ @param thd Thread handler.
+ @param is_show_fields_or_keys Indicates whether it is a legacy SHOW
+ COLUMNS or SHOW KEYS statement.
+ @param table TABLE object for I_S table to be filled.
+ @param schema_table I_S table description structure.
+ @param orig_db_name Database name.
+ @param orig_table_name Table name.
+ @param open_tables_state_backup Open_tables_state object which is used
+ to save/restore original status of
+ variables related to open tables state.
+ @param can_deadlock Indicates that deadlocks are possible
+ due to metadata locks, so to avoid
+ them we should not wait in case if
+ conflicting lock is present.
+
+ @retval FALSE - Success.
+ @retval TRUE - Failure.
+*/
+static bool
+fill_schema_table_by_open(THD *thd, bool is_show_fields_or_keys,
+ TABLE *table, ST_SCHEMA_TABLE *schema_table,
+ LEX_STRING *orig_db_name,
+ LEX_STRING *orig_table_name,
+ Open_tables_backup *open_tables_state_backup,
+ bool can_deadlock)
+{
+ Query_arena i_s_arena(thd->mem_root,
+ Query_arena::STMT_CONVENTIONAL_EXECUTION),
+ backup_arena, *old_arena;
+ LEX *old_lex= thd->lex, temp_lex, *lex;
+ LEX_STRING db_name, table_name;
+ TABLE_LIST *table_list;
+ bool result= true;
+ DBUG_ENTER("fill_schema_table_by_open");
+
+ /*
+ When a view is opened its structures are allocated on a permanent
+ statement arena and linked into the LEX tree for the current statement
+ (this happens even in cases when view is handled through TEMPTABLE
+ algorithm).
+
+ To prevent this process from unnecessary hogging of memory in the permanent
+ arena of our I_S query and to avoid damaging its LEX we use temporary
+ arena and LEX for table/view opening.
+
+ Use temporary arena instead of statement permanent arena. Also make
+ it active arena and save original one for successive restoring.
+ */
+ old_arena= thd->stmt_arena;
+ thd->stmt_arena= &i_s_arena;
+ thd->set_n_backup_active_arena(&i_s_arena, &backup_arena);
+
+ /* Prepare temporary LEX. */
+ thd->lex= lex= &temp_lex;
+ lex_start(thd);
+
+ /* Disable constant subquery evaluation as we won't be locking tables. */
+ lex->context_analysis_only= CONTEXT_ANALYSIS_ONLY_VIEW;
+
+ /*
+ Some of process_table() functions rely on wildcard being passed from
+ old LEX (or at least being initialized).
+ */
+ lex->wild= old_lex->wild;
+
+ /*
+ Since make_table_list() might change database and table name passed
+ to it we create copies of orig_db_name and orig_table_name here.
+ These copies are used for make_table_list() while unaltered values
+ are passed to process_table() functions.
+ */
+ if (!thd->make_lex_string(&db_name,
+ orig_db_name->str, orig_db_name->length) ||
+ !thd->make_lex_string(&table_name,
+ orig_table_name->str, orig_table_name->length))
+ goto end;
+
+ /*
+ Create table list element for table to be open. Link it with the
+ temporary LEX. The latter is required to correctly open views and
+ produce table describing their structure.
+ */
+ if (make_table_list(thd, &lex->select_lex, &db_name, &table_name))
+ goto end;
+
+ table_list= lex->select_lex.table_list.first;
+
+ if (is_show_fields_or_keys)
+ {
+ /*
+ Restore thd->temporary_tables to be able to process
+ temporary tables (only for 'show index' & 'show columns').
+ This should be changed when processing of temporary tables for
+ I_S tables will be done.
+ */
+ thd->temporary_tables= open_tables_state_backup->temporary_tables;
+ }
+ else
+ {
+ /*
+ Apply optimization flags for table opening which are relevant for
+ this I_S table. We can't do this for SHOW COLUMNS/KEYS because of
+ backward compatibility.
+ */
+ table_list->i_s_requested_object= schema_table->i_s_requested_object;
+ }
+
+ /*
+ Let us set fake sql_command so views won't try to merge
+ themselves into main statement. If we don't do this,
+ SELECT * from information_schema.xxxx will cause problems.
+ SQLCOM_SHOW_FIELDS is used because it satisfies
+ 'only_view_structure()'.
+ */
+ lex->sql_command= SQLCOM_SHOW_FIELDS;
+ result= (open_temporary_tables(thd, table_list) ||
+ open_normal_and_derived_tables(thd, table_list,
+ (MYSQL_OPEN_IGNORE_FLUSH |
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL |
+ (can_deadlock ?
+ MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0)),
+ DT_PREPARE | DT_CREATE));
+ /*
+ Restore old value of sql_command back as it is being looked at in
+ process_table() function.
+ */
+ lex->sql_command= old_lex->sql_command;
+
+ DEBUG_SYNC(thd, "after_open_table_ignore_flush");
+
+ /*
+ XXX: show_table_list has a flag i_is_requested,
+ and when it's set, open_normal_and_derived_tables()
+ can return an error without setting an error message
+ in THD, which is a hack. This is why we have to
+ check for res, then for thd->is_error() and only then
+ for thd->main_da.sql_errno().
+
+ Again we don't do this for SHOW COLUMNS/KEYS because
+ of backward compatibility.
+ */
+ if (!is_show_fields_or_keys && result &&
+ (thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE ||
+ thd->get_stmt_da()->sql_errno() == ER_WRONG_OBJECT))
+ {
+ /*
+ Hide error for a non-existing table.
+ For example, this error can occur when we use a where condition
+ with a db name and table, but the table does not exist or
+ there is a view with the same name.
+ */
+ result= false;
+ thd->clear_error();
+ }
+ else
+ {
+ result= schema_table->process_table(thd, table_list,
+ table, result,
+ orig_db_name,
+ orig_table_name);
+ }
+
+
+end:
+ lex->unit.cleanup();
+
+ /* Restore original LEX value, statement's arena and THD arena values. */
+ lex_end(thd->lex);
+
+ // Free items, before restoring backup_arena below.
+ DBUG_ASSERT(i_s_arena.free_list == NULL);
+ thd->free_items();
+
+ /*
+ For safety reset list of open temporary tables before closing
+ all tables open within this Open_tables_state.
+ */
+ thd->temporary_tables= NULL;
+ close_thread_tables(thd);
+ /*
+ Release metadata lock we might have acquired.
+ See comment in fill_schema_table_from_frm() for details.
+ */
+ thd->mdl_context.rollback_to_savepoint(open_tables_state_backup->mdl_system_tables_svp);
+
+ thd->lex= old_lex;
+
+ thd->stmt_arena= old_arena;
+ thd->restore_active_arena(&i_s_arena, &backup_arena);
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ @brief Fill I_S table for SHOW TABLE NAMES commands
+
+ @param[in] thd thread handler
+ @param[in] table TABLE struct for I_S table
+ @param[in] db_name database name
+ @param[in] table_name table name
+
+ @return Operation status
+ @retval 0 success
+ @retval 1 error
+*/
+
+static int fill_schema_table_names(THD *thd, TABLE_LIST *tables,
+ LEX_STRING *db_name, LEX_STRING *table_name)
+{
+ TABLE *table= tables->table;
+ if (db_name == &INFORMATION_SCHEMA_NAME)
+ {
+ table->field[3]->store(STRING_WITH_LEN("SYSTEM VIEW"),
+ system_charset_info);
+ }
+ else if (tables->table_open_method != SKIP_OPEN_TABLE)
+ {
+ CHARSET_INFO *cs= system_charset_info;
+ handlerton *hton;
+ if (ha_table_exists(thd, db_name->str, table_name->str, &hton))
+ {
+ if (hton == view_pseudo_hton)
+ table->field[3]->store(STRING_WITH_LEN("VIEW"), cs);
+ else
+ table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs);
+ }
+ else
+ table->field[3]->store(STRING_WITH_LEN("ERROR"), cs);
+
+ if (thd->is_error() && thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE)
+ {
+ thd->clear_error();
+ return 0;
+ }
+ }
+ if (schema_table_store_record(thd, table))
+ return 1;
+ return 0;
+}
+
+
+/**
+ @brief Get open table method
+
+ @details The function calculates the method which will be used
+ for table opening:
+ SKIP_OPEN_TABLE - do not open table
+ OPEN_FRM_ONLY - open FRM file only
+ OPEN_FULL_TABLE - open FRM, data, index files
+ @param[in] tables I_S table table_list
+ @param[in] schema_table I_S table struct
+ @param[in] schema_table_idx I_S table index
+
+ @return return a set of flags
+ @retval SKIP_OPEN_TABLE | OPEN_FRM_ONLY | OPEN_FULL_TABLE
+*/
+
+uint get_table_open_method(TABLE_LIST *tables,
+ ST_SCHEMA_TABLE *schema_table,
+ enum enum_schema_tables schema_table_idx)
+{
+ /*
+ determine which method will be used for table opening
+ */
+ if (schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE)
+ {
+ Field **ptr, *field;
+ int table_open_method= 0, field_indx= 0;
+ uint star_table_open_method= OPEN_FULL_TABLE;
+ bool used_star= true; // true if '*' is used in select
+ for (ptr=tables->table->field; (field= *ptr) ; ptr++)
+ {
+ star_table_open_method=
+ MY_MIN(star_table_open_method,
+ schema_table->fields_info[field_indx].open_method);
+ if (bitmap_is_set(tables->table->read_set, field->field_index))
+ {
+ used_star= false;
+ table_open_method|= schema_table->fields_info[field_indx].open_method;
+ }
+ field_indx++;
+ }
+ if (used_star)
+ return star_table_open_method;
+ return table_open_method;
+ }
+ /* I_S tables which use get_all_tables but can not be optimized */
+ return (uint) OPEN_FULL_TABLE;
+}
+
+
+/**
+ Try acquire high priority share metadata lock on a table (with
+ optional wait for conflicting locks to go away).
+
+ @param thd Thread context.
+ @param mdl_request Pointer to memory to be used for MDL_request
+ object for a lock request.
+ @param table Table list element for the table
+ @param can_deadlock Indicates that deadlocks are possible due to
+ metadata locks, so to avoid them we should not
+ wait in case if conflicting lock is present.
+
+ @note This is an auxiliary function to be used in cases when we want to
+ access table's description by looking up info in TABLE_SHARE without
+ going through full-blown table open.
+ @note This function assumes that there are no other metadata lock requests
+ in the current metadata locking context.
+
+ @retval FALSE No error, if lock was obtained TABLE_LIST::mdl_request::ticket
+ is set to non-NULL value.
+ @retval TRUE Some error occured (probably thread was killed).
+*/
+
+static bool
+try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table,
+ bool can_deadlock)
+{
+ bool error;
+ table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
+ MDL_SHARED_HIGH_PRIO, MDL_TRANSACTION);
+
+ if (can_deadlock)
+ {
+ /*
+ When .FRM is being open in order to get data for an I_S table,
+ we might have some tables not only open but also locked.
+ E.g. this happens when a SHOW or I_S statement is run
+ under LOCK TABLES or inside a stored function.
+ By waiting for the conflicting metadata lock to go away we
+ might create a deadlock which won't entirely belong to the
+ MDL subsystem and thus won't be detectable by this subsystem's
+ deadlock detector. To avoid such situation, when there are
+ other locked tables, we prefer not to wait on a conflicting
+ lock.
+ */
+ error= thd->mdl_context.try_acquire_lock(&table->mdl_request);
+ }
+ else
+ error= thd->mdl_context.acquire_lock(&table->mdl_request,
+ thd->variables.lock_wait_timeout);
+
+ return error;
+}
+
+
+/**
+ @brief Fill I_S table with data from FRM file only
+
+ @param[in] thd thread handler
+ @param[in] table TABLE struct for I_S table
+ @param[in] schema_table I_S table struct
+ @param[in] db_name database name
+ @param[in] table_name table name
+ @param[in] schema_table_idx I_S table index
+ @param[in] open_tables_state_backup Open_tables_state object which is used
+ to save/restore original state of metadata
+ locks.
+ @param[in] can_deadlock Indicates that deadlocks are possible
+ due to metadata locks, so to avoid
+ them we should not wait in case if
+ conflicting lock is present.
+
+ @return Operation status
+ @retval 0 Table is processed and we can continue
+ with new table
+ @retval 1 It's view and we have to use
+ open_tables function for this table
+*/
+
+static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables,
+ ST_SCHEMA_TABLE *schema_table,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name,
+ enum enum_schema_tables schema_table_idx,
+ Open_tables_backup *open_tables_state_backup,
+ bool can_deadlock)
+{
+ TABLE *table= tables->table;
+ TABLE_SHARE *share;
+ TABLE tbl;
+ TABLE_LIST table_list;
+ uint res= 0;
+ char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1];
+
+ bzero((char*) &table_list, sizeof(TABLE_LIST));
+ bzero((char*) &tbl, sizeof(TABLE));
+
+ DBUG_ASSERT(db_name->length <= NAME_LEN);
+ DBUG_ASSERT(table_name->length <= NAME_LEN);
+
+ if (lower_case_table_names)
+ {
+ /*
+ In lower_case_table_names > 0 metadata locking and table definition
+ cache subsystems require normalized (lowercased) database and table
+ names as input.
+ */
+ strmov(db_name_buff, db_name->str);
+ strmov(table_name_buff, table_name->str);
+ my_casedn_str(files_charset_info, db_name_buff);
+ my_casedn_str(files_charset_info, table_name_buff);
+ table_list.db= db_name_buff;
+ table_list.table_name= table_name_buff;
+ }
+ else
+ {
+ table_list.table_name= table_name->str;
+ table_list.db= db_name->str;
+ }
+
+ /*
+ TODO: investigate if in this particular situation we can get by
+ simply obtaining internal lock of the data-dictionary
+ instead of obtaining full-blown metadata lock.
+ */
+ if (try_acquire_high_prio_shared_mdl_lock(thd, &table_list, can_deadlock))
+ {
+ /*
+ Some error occured (most probably we have been killed while
+ waiting for conflicting locks to go away), let the caller to
+ handle the situation.
+ */
+ return 1;
+ }
+
+ if (! table_list.mdl_request.ticket)
+ {
+ /*
+ We are in situation when we have encountered conflicting metadata
+ lock and deadlocks can occur due to waiting for it to go away.
+ So instead of waiting skip this table with an appropriate warning.
+ */
+ DBUG_ASSERT(can_deadlock);
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_I_S_SKIPPED_TABLE,
+ ER(ER_WARN_I_S_SKIPPED_TABLE),
+ table_list.db, table_list.table_name);
+ return 0;
+ }
+
+ if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY)
+ {
+ init_sql_alloc(&tbl.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0));
+ if (!Table_triggers_list::check_n_load(thd, db_name->str,
+ table_name->str, &tbl, 1))
+ {
+ table_list.table= &tbl;
+ res= schema_table->process_table(thd, &table_list, table,
+ res, db_name, table_name);
+ delete tbl.triggers;
+ }
+ free_root(&tbl.mem_root, MYF(0));
+ goto end;
+ }
+
+ share= tdc_acquire_share_shortlived(thd, &table_list, GTS_TABLE | GTS_VIEW);
+ if (!share)
+ {
+ res= 0;
+ goto end;
+ }
+
+ if (share->is_view)
+ {
+ if (schema_table->i_s_requested_object & OPEN_TABLE_ONLY)
+ {
+ /* skip view processing */
+ res= 0;
+ goto end_share;
+ }
+ else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL)
+ {
+ /*
+ tell get_all_tables() to fall back to
+ open_normal_and_derived_tables()
+ */
+ res= 1;
+ goto end_share;
+ }
+
+ if (open_new_frm(thd, share, table_name->str,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
+ OPEN_VIEW_NO_PARSE,
+ thd->open_options, &tbl, &table_list, thd->mem_root))
+ goto end_share;
+ table_list.view= (LEX*) share->is_view;
+ res= schema_table->process_table(thd, &table_list, table,
+ res, db_name, table_name);
+ goto end_share;
+ }
+
+ if (!open_table_from_share(thd, share, table_name->str, 0,
+ (EXTRA_RECORD | OPEN_FRM_FILE_ONLY),
+ thd->open_options, &tbl, FALSE))
+ {
+ tbl.s= share;
+ table_list.table= &tbl;
+ table_list.view= (LEX*) share->is_view;
+ res= schema_table->process_table(thd, &table_list, table,
+ res, db_name, table_name);
+ free_root(&tbl.mem_root, MYF(0));
+ }
+
+
+end_share:
+ tdc_release_share(share);
+
+end:
+ /*
+ Release metadata lock we might have acquired.
+
+ Without this step metadata locks acquired for each table processed
+ will be accumulated. In situation when a lot of tables are processed
+ by I_S query this will result in transaction with too many metadata
+ locks. As result performance of acquisition of new lock will suffer.
+
+ Of course, the fact that we don't hold metadata lock on tables which
+ were processed till the end of I_S query makes execution less isolated
+ from concurrent DDL. Consequently one might get 'dirty' results from
+ such a query. But we have never promised serializability of I_S queries
+ anyway.
+
+ We don't have any tables open since we took backup, so rolling back to
+ savepoint is safe.
+ */
+ DBUG_ASSERT(thd->open_tables == NULL);
+ thd->mdl_context.rollback_to_savepoint(open_tables_state_backup->mdl_system_tables_svp);
+ thd->clear_error();
+ return res;
+}
+
+
+class Warnings_only_error_handler : public Internal_error_handler
+{
+public:
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* msg,
+ Sql_condition ** cond_hdl)
+ {
+ if (sql_errno == ER_PARSE_ERROR ||
+ sql_errno == ER_TRG_NO_DEFINER ||
+ sql_errno == ER_TRG_NO_CREATION_CTX)
+ return true;
+
+ if (level != Sql_condition::WARN_LEVEL_ERROR)
+ return false;
+
+ if (!thd->get_stmt_da()->is_error())
+ thd->get_stmt_da()->set_error_status(sql_errno, msg, sqlstate, *cond_hdl);
+ return true; // handled!
+ }
+};
+
+
+/**
+ @brief Fill I_S tables whose data are retrieved
+ from frm files and storage engine
+
+ @details The information schema tables are internally represented as
+ temporary tables that are filled at query execution time.
+ Those I_S tables whose data are retrieved
+ from frm files and storage engine are filled by the function
+ get_all_tables().
+
+ @param[in] thd thread handler
+ @param[in] tables I_S table
+ @param[in] cond 'WHERE' condition
+
+ @return Operation status
+ @retval 0 success
+ @retval 1 error
+*/
+
+int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ LEX *lex= thd->lex;
+ TABLE *table= tables->table;
+ TABLE_LIST table_acl_check;
+ SELECT_LEX *lsel= tables->schema_select_lex;
+ ST_SCHEMA_TABLE *schema_table= tables->schema_table;
+ LOOKUP_FIELD_VALUES lookup_field_vals;
+ enum enum_schema_tables schema_table_idx;
+ Dynamic_array<LEX_STRING*> db_names;
+ COND *partial_cond= 0;
+ int error= 1;
+ Open_tables_backup open_tables_state_backup;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *sctx= thd->security_ctx;
+#endif
+ uint table_open_method;
+ bool can_deadlock;
+ DBUG_ENTER("get_all_tables");
+
+ /*
+ In cases when SELECT from I_S table being filled by this call is
+ part of statement which also uses other tables or is being executed
+ under LOCK TABLES or is part of transaction which also uses other
+ tables waiting for metadata locks which happens below might result
+ in deadlocks.
+ To avoid them we don't wait if conflicting metadata lock is
+ encountered and skip table with emitting an appropriate warning.
+ */
+ can_deadlock= thd->mdl_context.has_locks();
+
+ /*
+ We should not introduce deadlocks even if we already have some
+ tables open and locked, since we won't lock tables which we will
+ open and will ignore pending exclusive metadata locks for these
+ tables by using high-priority requests for shared metadata locks.
+ */
+ thd->reset_n_backup_open_tables_state(&open_tables_state_backup);
+
+ schema_table_idx= get_schema_table_idx(schema_table);
+ tables->table_open_method= table_open_method=
+ get_table_open_method(tables, schema_table, schema_table_idx);
+ DBUG_PRINT("open_method", ("%d", tables->table_open_method));
+ /*
+ this branch processes SHOW FIELDS, SHOW INDEXES commands.
+ see sql_parse.cc, prepare_schema_table() function where
+ this values are initialized
+ */
+ if (lsel && lsel->table_list.first)
+ {
+ LEX_STRING db_name, table_name;
+
+ db_name.str= lsel->table_list.first->db;
+ db_name.length= lsel->table_list.first->db_length;
+
+ table_name.str= lsel->table_list.first->table_name;
+ table_name.length= lsel->table_list.first->table_name_length;
+
+ error= fill_schema_table_by_open(thd, TRUE,
+ table, schema_table,
+ &db_name, &table_name,
+ &open_tables_state_backup,
+ can_deadlock);
+ goto err;
+ }
+
+ if (get_lookup_field_values(thd, cond, tables, &lookup_field_vals))
+ {
+ error= 0;
+ goto err;
+ }
+
+ DBUG_PRINT("info",("db_name='%s', table_name='%s'",
+ lookup_field_vals.db_value.str,
+ lookup_field_vals.table_value.str));
+
+ if (!lookup_field_vals.wild_db_value && !lookup_field_vals.wild_table_value)
+ {
+ /*
+ if lookup value is empty string then
+ it's impossible table name or db name
+ */
+ if ((lookup_field_vals.db_value.str &&
+ !lookup_field_vals.db_value.str[0]) ||
+ (lookup_field_vals.table_value.str &&
+ !lookup_field_vals.table_value.str[0]))
+ {
+ error= 0;
+ goto err;
+ }
+ }
+
+ if (lookup_field_vals.db_value.length &&
+ !lookup_field_vals.wild_db_value)
+ tables->has_db_lookup_value= TRUE;
+ if (lookup_field_vals.table_value.length &&
+ !lookup_field_vals.wild_table_value)
+ tables->has_table_lookup_value= TRUE;
+
+ if (tables->has_db_lookup_value && tables->has_table_lookup_value)
+ partial_cond= 0;
+ else
+ partial_cond= make_cond_for_info_schema(cond, tables);
+
+ if (lex->describe)
+ {
+ /* EXPLAIN SELECT */
+ error= 0;
+ goto err;
+ }
+
+ bzero((char*) &table_acl_check, sizeof(table_acl_check));
+
+ if (make_db_list(thd, &db_names, &lookup_field_vals))
+ goto err;
+ for (size_t i=0; i < db_names.elements(); i++)
+ {
+ LEX_STRING *db_name= db_names.at(i);
+ DBUG_ASSERT(db_name->length <= NAME_LEN);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (!(check_access(thd, SELECT_ACL, db_name->str,
+ &thd->col_access, NULL, 0, 1) ||
+ (!thd->col_access && check_grant_db(thd, db_name->str))) ||
+ sctx->master_access & (DB_ACLS | SHOW_DB_ACL) ||
+ acl_get(sctx->host, sctx->ip, sctx->priv_user, db_name->str, 0))
+#endif
+ {
+ Dynamic_array<LEX_STRING*> table_names;
+ int res= make_table_name_list(thd, &table_names, lex,
+ &lookup_field_vals, db_name);
+ if (res == 2) /* Not fatal error, continue */
+ continue;
+ if (res)
+ goto err;
+
+ for (size_t i=0; i < table_names.elements(); i++)
+ {
+ LEX_STRING *table_name= table_names.at(i);
+ DBUG_ASSERT(table_name->length <= NAME_LEN);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (!(thd->col_access & TABLE_ACLS))
+ {
+ table_acl_check.db= db_name->str;
+ table_acl_check.db_length= db_name->length;
+ table_acl_check.table_name= table_name->str;
+ table_acl_check.table_name_length= table_name->length;
+ table_acl_check.grant.privilege= thd->col_access;
+ if (check_grant(thd, TABLE_ACLS, &table_acl_check, TRUE, 1, TRUE))
+ continue;
+ }
+#endif
+ restore_record(table, s->default_values);
+ table->field[schema_table->idx_field1]->
+ store(db_name->str, db_name->length, system_charset_info);
+ table->field[schema_table->idx_field2]->
+ store(table_name->str, table_name->length, system_charset_info);
+
+ if (!partial_cond || partial_cond->val_int())
+ {
+ /*
+ If table is I_S.tables and open_table_method is 0 (eg SKIP_OPEN)
+ we can skip table opening and we don't have lookup value for
+ table name or lookup value is wild string(table name list is
+ already created by make_table_name_list() function).
+ */
+ if (!table_open_method && schema_table_idx == SCH_TABLES &&
+ (!lookup_field_vals.table_value.length ||
+ lookup_field_vals.wild_table_value))
+ {
+ table->field[0]->store(STRING_WITH_LEN("def"), system_charset_info);
+ if (schema_table_store_record(thd, table))
+ goto err; /* Out of space in temporary table */
+ continue;
+ }
+
+ /* SHOW TABLE NAMES command */
+ if (schema_table_idx == SCH_TABLE_NAMES)
+ {
+ if (fill_schema_table_names(thd, tables, db_name, table_name))
+ continue;
+ }
+ else if (schema_table_idx == SCH_TRIGGERS &&
+ db_name == &INFORMATION_SCHEMA_NAME)
+ {
+ continue;
+ }
+ else
+ {
+ if (!(table_open_method & ~OPEN_FRM_ONLY) &&
+ db_name != &INFORMATION_SCHEMA_NAME)
+ {
+ if (!fill_schema_table_from_frm(thd, tables, schema_table,
+ db_name, table_name,
+ schema_table_idx,
+ &open_tables_state_backup,
+ can_deadlock))
+ continue;
+ }
+
+ DEBUG_SYNC(thd, "before_open_in_get_all_tables");
+ if (fill_schema_table_by_open(thd, FALSE,
+ table, schema_table,
+ db_name, table_name,
+ &open_tables_state_backup,
+ can_deadlock))
+ goto err;
+ }
+ }
+ }
+ }
+ }
+
+ error= 0;
+err:
+ thd->restore_backup_open_tables_state(&open_tables_state_backup);
+
+ DBUG_RETURN(error);
+}
+
+
+bool store_schema_shemata(THD* thd, TABLE *table, LEX_STRING *db_name,
+ CHARSET_INFO *cs)
+{
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), system_charset_info);
+ table->field[1]->store(db_name->str, db_name->length, system_charset_info);
+ table->field[2]->store(cs->csname, strlen(cs->csname), system_charset_info);
+ table->field[3]->store(cs->name, strlen(cs->name), system_charset_info);
+ return schema_table_store_record(thd, table);
+}
+
+
+int fill_schema_schemata(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ /*
+ TODO: fill_schema_shemata() is called when new client is connected.
+ Returning error status in this case leads to client hangup.
+ */
+
+ LOOKUP_FIELD_VALUES lookup_field_vals;
+ Dynamic_array<LEX_STRING*> db_names;
+ HA_CREATE_INFO create;
+ TABLE *table= tables->table;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *sctx= thd->security_ctx;
+#endif
+ DBUG_ENTER("fill_schema_shemata");
+
+ if (get_lookup_field_values(thd, cond, tables, &lookup_field_vals))
+ DBUG_RETURN(0);
+ DBUG_PRINT("INDEX VALUES",("db_name: %s table_name: %s",
+ lookup_field_vals.db_value.str,
+ lookup_field_vals.table_value.str));
+ if (make_db_list(thd, &db_names, &lookup_field_vals))
+ DBUG_RETURN(1);
+
+ /*
+ If we have lookup db value we should check that the database exists
+ */
+ if(lookup_field_vals.db_value.str && !lookup_field_vals.wild_db_value &&
+ db_names.at(0) != &INFORMATION_SCHEMA_NAME)
+ {
+ char path[FN_REFLEN+16];
+ uint path_len;
+ MY_STAT stat_info;
+ if (!lookup_field_vals.db_value.str[0])
+ DBUG_RETURN(0);
+ path_len= build_table_filename(path, sizeof(path) - 1,
+ lookup_field_vals.db_value.str, "", "", 0);
+ path[path_len-1]= 0;
+ if (!mysql_file_stat(key_file_misc, path, &stat_info, MYF(0)))
+ DBUG_RETURN(0);
+ }
+
+ for (size_t i=0; i < db_names.elements(); i++)
+ {
+ LEX_STRING *db_name= db_names.at(i);
+ DBUG_ASSERT(db_name->length <= NAME_LEN);
+ if (db_name == &INFORMATION_SCHEMA_NAME)
+ {
+ if (store_schema_shemata(thd, table, db_name,
+ system_charset_info))
+ DBUG_RETURN(1);
+ continue;
+ }
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (sctx->master_access & (DB_ACLS | SHOW_DB_ACL) ||
+ acl_get(sctx->host, sctx->ip, sctx->priv_user, db_name->str, 0) ||
+ !check_grant_db(thd, db_name->str))
+#endif
+ {
+ load_db_opt_by_name(thd, db_name->str, &create);
+ if (store_schema_shemata(thd, table, db_name,
+ create.default_table_charset))
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+static int get_schema_tables_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ const char *tmp_buff;
+ MYSQL_TIME time;
+ int info_error= 0;
+ CHARSET_INFO *cs= system_charset_info;
+ DBUG_ENTER("get_schema_tables_record");
+
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(table_name->str, table_name->length, cs);
+
+ if (res)
+ {
+ /* There was a table open error, so set the table type and return */
+ if (tables->view)
+ table->field[3]->store(STRING_WITH_LEN("VIEW"), cs);
+ else if (tables->schema_table)
+ table->field[3]->store(STRING_WITH_LEN("SYSTEM VIEW"), cs);
+ else
+ table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs);
+
+ goto err;
+ }
+
+ if (tables->view)
+ {
+ table->field[3]->store(STRING_WITH_LEN("VIEW"), cs);
+ table->field[20]->store(STRING_WITH_LEN("VIEW"), cs);
+ }
+ else
+ {
+ char option_buff[512];
+ String str(option_buff,sizeof(option_buff), system_charset_info);
+ TABLE *show_table= tables->table;
+ TABLE_SHARE *share= show_table->s;
+ handler *file= show_table->file;
+ handlerton *tmp_db_type= share->db_type();
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ bool is_partitioned= FALSE;
+#endif
+
+ if (share->tmp_table == SYSTEM_TMP_TABLE)
+ table->field[3]->store(STRING_WITH_LEN("SYSTEM VIEW"), cs);
+ else if (share->tmp_table)
+ table->field[3]->store(STRING_WITH_LEN("LOCAL TEMPORARY"), cs);
+ else
+ table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs);
+
+ for (int i= 4; i < 20; i++)
+ {
+ if (i == 7 || (i > 12 && i < 17) || i == 18)
+ continue;
+ table->field[i]->set_notnull();
+ }
+
+ /* Collect table info from the table share */
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (share->db_type() == partition_hton &&
+ share->partition_info_str_len)
+ {
+ tmp_db_type= plugin_hton(share->default_part_plugin);
+ is_partitioned= TRUE;
+ }
+#endif
+
+ tmp_buff= (char *) ha_resolve_storage_engine_name(tmp_db_type);
+ table->field[4]->store(tmp_buff, strlen(tmp_buff), cs);
+ table->field[5]->store((longlong) share->frm_version, TRUE);
+
+ str.length(0);
+
+ if (share->min_rows)
+ {
+ str.qs_append(STRING_WITH_LEN(" min_rows="));
+ str.qs_append(share->min_rows);
+ }
+
+ if (share->max_rows)
+ {
+ str.qs_append(STRING_WITH_LEN(" max_rows="));
+ str.qs_append(share->max_rows);
+ }
+
+ if (share->avg_row_length)
+ {
+ str.qs_append(STRING_WITH_LEN(" avg_row_length="));
+ str.qs_append(share->avg_row_length);
+ }
+
+ if (share->db_create_options & HA_OPTION_PACK_KEYS)
+ str.qs_append(STRING_WITH_LEN(" pack_keys=1"));
+
+ if (share->db_create_options & HA_OPTION_NO_PACK_KEYS)
+ str.qs_append(STRING_WITH_LEN(" pack_keys=0"));
+
+ if (share->db_create_options & HA_OPTION_STATS_PERSISTENT)
+ str.qs_append(STRING_WITH_LEN(" stats_persistent=1"));
+
+ if (share->db_create_options & HA_OPTION_NO_STATS_PERSISTENT)
+ str.qs_append(STRING_WITH_LEN(" stats_persistent=0"));
+
+ if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON)
+ str.qs_append(STRING_WITH_LEN(" stats_auto_recalc=1"));
+ else if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF)
+ str.qs_append(STRING_WITH_LEN(" stats_auto_recalc=0"));
+
+ if (share->stats_sample_pages != 0)
+ {
+ str.qs_append(STRING_WITH_LEN(" stats_sample_pages="));
+ str.qs_append(share->stats_sample_pages);
+ }
+
+ /* We use CHECKSUM, instead of TABLE_CHECKSUM, for backward compability */
+ if (share->db_create_options & HA_OPTION_CHECKSUM)
+ str.qs_append(STRING_WITH_LEN(" checksum=1"));
+
+ if (share->page_checksum != HA_CHOICE_UNDEF)
+ {
+ str.qs_append(STRING_WITH_LEN(" page_checksum="));
+ str.qs_append(ha_choice_values[(uint) share->page_checksum]);
+ }
+
+ if (share->db_create_options & HA_OPTION_DELAY_KEY_WRITE)
+ str.qs_append(STRING_WITH_LEN(" delay_key_write=1"));
+
+ if (share->row_type != ROW_TYPE_DEFAULT)
+ {
+ str.qs_append(STRING_WITH_LEN(" row_format="));
+ str.qs_append(ha_row_type[(uint) share->row_type]);
+ }
+
+ if (share->key_block_size)
+ {
+ str.qs_append(STRING_WITH_LEN(" key_block_size="));
+ str.qs_append(share->key_block_size);
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (is_partitioned)
+ str.qs_append(STRING_WITH_LEN(" partitioned"));
+#endif
+
+ if (share->transactional != HA_CHOICE_UNDEF)
+ {
+ str.qs_append(STRING_WITH_LEN(" transactional="));
+ str.qs_append(ha_choice_values[(uint) share->transactional]);
+ }
+ append_create_options(thd, &str, share->option_list, false, 0);
+
+ if (str.length())
+ table->field[19]->store(str.ptr()+1, str.length()-1, cs);
+
+ tmp_buff= (share->table_charset ?
+ share->table_charset->name : "default");
+
+ table->field[17]->store(tmp_buff, strlen(tmp_buff), cs);
+
+ if (share->comment.str)
+ table->field[20]->store(share->comment.str, share->comment.length, cs);
+
+ /* Collect table info from the storage engine */
+
+ if(file)
+ {
+ /* If info() fails, then there's nothing else to do */
+ if ((info_error= file->info(HA_STATUS_VARIABLE |
+ HA_STATUS_TIME |
+ HA_STATUS_VARIABLE_EXTRA |
+ HA_STATUS_AUTO)) != 0)
+ goto err;
+
+ enum row_type row_type = file->get_row_type();
+ switch (row_type) {
+ case ROW_TYPE_NOT_USED:
+ case ROW_TYPE_DEFAULT:
+ tmp_buff= ((share->db_options_in_use &
+ HA_OPTION_COMPRESS_RECORD) ? "Compressed" :
+ (share->db_options_in_use & HA_OPTION_PACK_RECORD) ?
+ "Dynamic" : "Fixed");
+ break;
+ case ROW_TYPE_FIXED:
+ tmp_buff= "Fixed";
+ break;
+ case ROW_TYPE_DYNAMIC:
+ tmp_buff= "Dynamic";
+ break;
+ case ROW_TYPE_COMPRESSED:
+ tmp_buff= "Compressed";
+ break;
+ case ROW_TYPE_REDUNDANT:
+ tmp_buff= "Redundant";
+ break;
+ case ROW_TYPE_COMPACT:
+ tmp_buff= "Compact";
+ break;
+ case ROW_TYPE_PAGE:
+ tmp_buff= "Page";
+ break;
+ }
+
+ table->field[6]->store(tmp_buff, strlen(tmp_buff), cs);
+
+ if (!tables->schema_table)
+ {
+ table->field[7]->store((longlong) file->stats.records, TRUE);
+ table->field[7]->set_notnull();
+ }
+ table->field[8]->store((longlong) file->stats.mean_rec_length, TRUE);
+ table->field[9]->store((longlong) file->stats.data_file_length, TRUE);
+ if (file->stats.max_data_file_length)
+ {
+ table->field[10]->store((longlong) file->stats.max_data_file_length,
+ TRUE);
+ }
+ table->field[11]->store((longlong) file->stats.index_file_length, TRUE);
+ table->field[12]->store((longlong) file->stats.delete_length, TRUE);
+ if (show_table->found_next_number_field)
+ {
+ table->field[13]->store((longlong) file->stats.auto_increment_value,
+ TRUE);
+ table->field[13]->set_notnull();
+ }
+ if (file->stats.create_time)
+ {
+ thd->variables.time_zone->gmt_sec_to_TIME(&time,
+ (my_time_t) file->stats.create_time);
+ table->field[14]->store_time(&time);
+ table->field[14]->set_notnull();
+ }
+ if (file->stats.update_time)
+ {
+ thd->variables.time_zone->gmt_sec_to_TIME(&time,
+ (my_time_t) file->stats.update_time);
+ table->field[15]->store_time(&time);
+ table->field[15]->set_notnull();
+ }
+ if (file->stats.check_time)
+ {
+ thd->variables.time_zone->gmt_sec_to_TIME(&time,
+ (my_time_t) file->stats.check_time);
+ table->field[16]->store_time(&time);
+ table->field[16]->set_notnull();
+ }
+ if (file->ha_table_flags() & (HA_HAS_OLD_CHECKSUM | HA_HAS_NEW_CHECKSUM))
+ {
+ table->field[18]->store((longlong) file->checksum(), TRUE);
+ table->field[18]->set_notnull();
+ }
+ }
+ }
+
+err:
+ if (res || info_error)
+ {
+ /*
+ If an error was encountered, push a warning, set the TABLE COMMENT
+ column with the error text, and clear the error so that the operation
+ can continue.
+ */
+ const char *error= thd->get_stmt_da()->message();
+ table->field[20]->store(error, strlen(error), cs);
+
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(), error);
+ thd->clear_error();
+ }
+
+ DBUG_RETURN(schema_table_store_record(thd, table));
+}
+
+
+/**
+ @brief Store field characteristics into appropriate I_S table columns
+
+ @param[in] table I_S table
+ @param[in] field processed field
+ @param[in] cs I_S table charset
+ @param[in] offset offset from beginning of table
+ to DATE_TYPE column in I_S table
+
+ @return void
+*/
+
+static void store_column_type(TABLE *table, Field *field, CHARSET_INFO *cs,
+ uint offset)
+{
+ bool is_blob;
+ int decimals, field_length;
+ const char *tmp_buff;
+ char column_type_buff[MAX_FIELD_WIDTH];
+ String column_type(column_type_buff, sizeof(column_type_buff), cs);
+
+ field->sql_type(column_type);
+ /* DTD_IDENTIFIER column */
+ table->field[offset + 8]->store(column_type.ptr(), column_type.length(), cs);
+ table->field[offset + 8]->set_notnull();
+ /*
+ DATA_TYPE column:
+ MySQL column type has the following format:
+ base_type [(dimension)] [unsigned] [zerofill].
+ For DATA_TYPE column we extract only base type.
+ */
+ tmp_buff= strchr(column_type.c_ptr_safe(), '(');
+ if (!tmp_buff)
+ /*
+ if there is no dimention part then check the presence of
+ [unsigned] [zerofill] attributes and cut them of if exist.
+ */
+ tmp_buff= strchr(column_type.c_ptr_safe(), ' ');
+ table->field[offset]->store(column_type.ptr(),
+ (tmp_buff ? tmp_buff - column_type.ptr() :
+ column_type.length()), cs);
+
+ is_blob= (field->type() == MYSQL_TYPE_BLOB);
+ if (field->has_charset() || is_blob ||
+ field->real_type() == MYSQL_TYPE_VARCHAR || // For varbinary type
+ field->real_type() == MYSQL_TYPE_STRING) // For binary type
+ {
+ uint32 octet_max_length= field->max_display_length();
+ if (is_blob && octet_max_length != (uint32) 4294967295U)
+ octet_max_length /= field->charset()->mbmaxlen;
+ longlong char_max_len= is_blob ?
+ (longlong) octet_max_length / field->charset()->mbminlen :
+ (longlong) octet_max_length / field->charset()->mbmaxlen;
+ /* CHARACTER_MAXIMUM_LENGTH column*/
+ table->field[offset + 1]->store(char_max_len, TRUE);
+ table->field[offset + 1]->set_notnull();
+ /* CHARACTER_OCTET_LENGTH column */
+ table->field[offset + 2]->store((longlong) octet_max_length, TRUE);
+ table->field[offset + 2]->set_notnull();
+ }
+
+ /*
+ Calculate field_length and decimals.
+ They are set to -1 if they should not be set (we should return NULL)
+ */
+
+ field_length= -1;
+ decimals= field->decimals();
+ switch (field->type()) {
+ case MYSQL_TYPE_NEWDECIMAL:
+ field_length= ((Field_new_decimal*) field)->precision;
+ break;
+ case MYSQL_TYPE_DECIMAL:
+ field_length= field->field_length - (decimals ? 2 : 1);
+ break;
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_INT24:
+ field_length= field->max_display_length() - 1;
+ break;
+ case MYSQL_TYPE_LONGLONG:
+ field_length= field->max_display_length() -
+ ((field->flags & UNSIGNED_FLAG) ? 0 : 1);
+ break;
+ case MYSQL_TYPE_BIT:
+ field_length= field->max_display_length();
+ decimals= -1; // return NULL
+ break;
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ field_length= field->field_length;
+ if (decimals == NOT_FIXED_DEC)
+ decimals= -1; // return NULL
+ break;
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_DATETIME:
+ /* DATETIME_PRECISION column */
+ table->field[offset + 5]->store((longlong) field->decimals(), TRUE);
+ table->field[offset + 5]->set_notnull();
+ break;
+ default:
+ break;
+ }
+
+ /* NUMERIC_PRECISION column */
+ if (field_length >= 0)
+ {
+ table->field[offset + 3]->store((longlong) field_length, TRUE);
+ table->field[offset + 3]->set_notnull();
+
+ /* NUMERIC_SCALE column */
+ if (decimals >= 0)
+ {
+ table->field[offset + 4]->store((longlong) decimals, TRUE);
+ table->field[offset + 4]->set_notnull();
+ }
+ }
+ if (field->has_charset())
+ {
+ /* CHARACTER_SET_NAME column*/
+ tmp_buff= field->charset()->csname;
+ table->field[offset + 6]->store(tmp_buff, strlen(tmp_buff), cs);
+ table->field[offset + 6]->set_notnull();
+ /* COLLATION_NAME column */
+ tmp_buff= field->charset()->name;
+ table->field[offset + 7]->store(tmp_buff, strlen(tmp_buff), cs);
+ table->field[offset + 7]->set_notnull();
+ }
+}
+
+
+static int get_schema_column_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ LEX *lex= thd->lex;
+ const char *wild= lex->wild ? lex->wild->ptr() : NullS;
+ CHARSET_INFO *cs= system_charset_info;
+ TABLE *show_table;
+ Field **ptr, *field;
+ int count;
+ DBUG_ENTER("get_schema_column_record");
+
+ if (res)
+ {
+ if (lex->sql_command != SQLCOM_SHOW_FIELDS)
+ {
+ /*
+ I.e. we are in SELECT FROM INFORMATION_SCHEMA.COLUMS
+ rather than in SHOW COLUMNS
+ */
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->clear_error();
+ res= 0;
+ }
+ DBUG_RETURN(res);
+ }
+
+ show_table= tables->table;
+ count= 0;
+ ptr= show_table->field;
+ show_table->use_all_columns(); // Required for default
+ restore_record(show_table, s->default_values);
+
+ for (; (field= *ptr) ; ptr++)
+ {
+ uchar *pos;
+ char tmp[MAX_FIELD_WIDTH];
+ String type(tmp,sizeof(tmp), system_charset_info);
+
+ DEBUG_SYNC(thd, "get_schema_column");
+
+ if (wild && wild[0] &&
+ wild_case_compare(system_charset_info, field->field_name,wild))
+ continue;
+
+ count++;
+ /* Get default row, with all NULL fields set to NULL */
+ restore_record(table, s->default_values);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ uint col_access;
+ check_access(thd,SELECT_ACL, db_name->str,
+ &tables->grant.privilege, 0, 0, MY_TEST(tables->schema_table));
+ col_access= get_column_grant(thd, &tables->grant,
+ db_name->str, table_name->str,
+ field->field_name) & COL_ACLS;
+ if (!tables->schema_table && !col_access)
+ continue;
+ char *end= tmp;
+ for (uint bitnr=0; col_access ; col_access>>=1,bitnr++)
+ {
+ if (col_access & 1)
+ {
+ *end++=',';
+ end=strmov(end,grant_types.type_names[bitnr]);
+ }
+ }
+ table->field[18]->store(tmp+1,end == tmp ? 0 : (uint) (end-tmp-1), cs);
+
+#endif
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(table_name->str, table_name->length, cs);
+ table->field[3]->store(field->field_name, strlen(field->field_name),
+ cs);
+ table->field[4]->store((longlong) count, TRUE);
+
+ if (get_field_default_value(thd, field, &type, 0))
+ {
+ table->field[5]->store(type.ptr(), type.length(), cs);
+ table->field[5]->set_notnull();
+ }
+ pos=(uchar*) ((field->flags & NOT_NULL_FLAG) ? "NO" : "YES");
+ table->field[6]->store((const char*) pos,
+ strlen((const char*) pos), cs);
+ store_column_type(table, field, cs, 7);
+ pos=(uchar*) ((field->flags & PRI_KEY_FLAG) ? "PRI" :
+ (field->flags & UNIQUE_KEY_FLAG) ? "UNI" :
+ (field->flags & MULTIPLE_KEY_FLAG) ? "MUL":"");
+ table->field[16]->store((const char*) pos,
+ strlen((const char*) pos), cs);
+
+ if (field->unireg_check == Field::NEXT_NUMBER)
+ table->field[17]->store(STRING_WITH_LEN("auto_increment"), cs);
+ if (print_on_update_clause(field, &type, true))
+ table->field[17]->store(type.ptr(), type.length(), cs);
+ if (field->vcol_info)
+ {
+ if (field->stored_in_db)
+ table->field[17]->store(STRING_WITH_LEN("PERSISTENT"), cs);
+ else
+ table->field[17]->store(STRING_WITH_LEN("VIRTUAL"), cs);
+ }
+ table->field[19]->store(field->comment.str, field->comment.length, cs);
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+int fill_schema_charsets(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ CHARSET_INFO **cs;
+ const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
+ TABLE *table= tables->table;
+ CHARSET_INFO *scs= system_charset_info;
+
+ for (cs= all_charsets ;
+ cs < all_charsets + array_elements(all_charsets) ;
+ cs++)
+ {
+ CHARSET_INFO *tmp_cs= cs[0];
+ if (tmp_cs && (tmp_cs->state & MY_CS_PRIMARY) &&
+ (tmp_cs->state & MY_CS_AVAILABLE) &&
+ !(tmp_cs->state & MY_CS_HIDDEN) &&
+ !(wild && wild[0] &&
+ wild_case_compare(scs, tmp_cs->csname,wild)))
+ {
+ const char *comment;
+ restore_record(table, s->default_values);
+ table->field[0]->store(tmp_cs->csname, strlen(tmp_cs->csname), scs);
+ table->field[1]->store(tmp_cs->name, strlen(tmp_cs->name), scs);
+ comment= tmp_cs->comment ? tmp_cs->comment : "";
+ table->field[2]->store(comment, strlen(comment), scs);
+ table->field[3]->store((longlong) tmp_cs->mbmaxlen, TRUE);
+ if (schema_table_store_record(thd, table))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static my_bool iter_schema_engines(THD *thd, plugin_ref plugin,
+ void *ptable)
+{
+ TABLE *table= (TABLE *) ptable;
+ handlerton *hton= plugin_hton(plugin);
+ const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
+ CHARSET_INFO *scs= system_charset_info;
+ handlerton *default_type= ha_default_handlerton(thd);
+ DBUG_ENTER("iter_schema_engines");
+
+
+ /* Disabled plugins */
+ if (plugin_state(plugin) != PLUGIN_IS_READY)
+ {
+
+ struct st_maria_plugin *plug= plugin_decl(plugin);
+ if (!(wild && wild[0] &&
+ wild_case_compare(scs, plug->name,wild)))
+ {
+ restore_record(table, s->default_values);
+ table->field[0]->store(plug->name, strlen(plug->name), scs);
+ table->field[1]->store(C_STRING_WITH_LEN("NO"), scs);
+ table->field[2]->store(plug->descr, strlen(plug->descr), scs);
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+ }
+
+ if (!(hton->flags & HTON_HIDDEN))
+ {
+ LEX_STRING *name= plugin_name(plugin);
+ if (!(wild && wild[0] &&
+ wild_case_compare(scs, name->str,wild)))
+ {
+ LEX_STRING yesno[2]= {{ C_STRING_WITH_LEN("NO") },
+ { C_STRING_WITH_LEN("YES") }};
+ LEX_STRING *tmp;
+ const char *option_name= show_comp_option_name[(int) hton->state];
+ restore_record(table, s->default_values);
+
+ table->field[0]->store(name->str, name->length, scs);
+ if (hton->state == SHOW_OPTION_YES && default_type == hton)
+ option_name= "DEFAULT";
+ table->field[1]->store(option_name, strlen(option_name), scs);
+ table->field[2]->store(plugin_decl(plugin)->descr,
+ strlen(plugin_decl(plugin)->descr), scs);
+ tmp= &yesno[MY_TEST(hton->commit)];
+ table->field[3]->store(tmp->str, tmp->length, scs);
+ table->field[3]->set_notnull();
+ tmp= &yesno[MY_TEST(hton->prepare)];
+ table->field[4]->store(tmp->str, tmp->length, scs);
+ table->field[4]->set_notnull();
+ tmp= &yesno[MY_TEST(hton->savepoint_set)];
+ table->field[5]->store(tmp->str, tmp->length, scs);
+ table->field[5]->set_notnull();
+
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+int fill_schema_engines(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("fill_schema_engines");
+ if (plugin_foreach_with_mask(thd, iter_schema_engines,
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ ~PLUGIN_IS_FREED, tables->table))
+ DBUG_RETURN(1);
+ DBUG_RETURN(0);
+}
+
+
+int fill_schema_collation(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ CHARSET_INFO **cs;
+ const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
+ TABLE *table= tables->table;
+ CHARSET_INFO *scs= system_charset_info;
+ for (cs= all_charsets ;
+ cs < all_charsets + array_elements(all_charsets) ;
+ cs++ )
+ {
+ CHARSET_INFO **cl;
+ CHARSET_INFO *tmp_cs= cs[0];
+ if (!tmp_cs || !(tmp_cs->state & MY_CS_AVAILABLE) ||
+ (tmp_cs->state & MY_CS_HIDDEN) ||
+ !(tmp_cs->state & MY_CS_PRIMARY))
+ continue;
+ for (cl= all_charsets;
+ cl < all_charsets + array_elements(all_charsets) ;
+ cl ++)
+ {
+ CHARSET_INFO *tmp_cl= cl[0];
+ if (!tmp_cl || !(tmp_cl->state & MY_CS_AVAILABLE) ||
+ !my_charset_same(tmp_cs, tmp_cl))
+ continue;
+ if (!(wild && wild[0] &&
+ wild_case_compare(scs, tmp_cl->name,wild)))
+ {
+ const char *tmp_buff;
+ restore_record(table, s->default_values);
+ table->field[0]->store(tmp_cl->name, strlen(tmp_cl->name), scs);
+ table->field[1]->store(tmp_cl->csname , strlen(tmp_cl->csname), scs);
+ table->field[2]->store((longlong) tmp_cl->number, TRUE);
+ tmp_buff= (tmp_cl->state & MY_CS_PRIMARY) ? "Yes" : "";
+ table->field[3]->store(tmp_buff, strlen(tmp_buff), scs);
+ tmp_buff= (tmp_cl->state & MY_CS_COMPILED)? "Yes" : "";
+ table->field[4]->store(tmp_buff, strlen(tmp_buff), scs);
+ table->field[5]->store((longlong) tmp_cl->strxfrm_multiply, TRUE);
+ if (schema_table_store_record(thd, table))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+int fill_schema_coll_charset_app(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ CHARSET_INFO **cs;
+ TABLE *table= tables->table;
+ CHARSET_INFO *scs= system_charset_info;
+ for (cs= all_charsets ;
+ cs < all_charsets + array_elements(all_charsets) ;
+ cs++ )
+ {
+ CHARSET_INFO **cl;
+ CHARSET_INFO *tmp_cs= cs[0];
+ if (!tmp_cs || !(tmp_cs->state & MY_CS_AVAILABLE) ||
+ !(tmp_cs->state & MY_CS_PRIMARY))
+ continue;
+ for (cl= all_charsets;
+ cl < all_charsets + array_elements(all_charsets) ;
+ cl ++)
+ {
+ CHARSET_INFO *tmp_cl= cl[0];
+ if (!tmp_cl || !(tmp_cl->state & MY_CS_AVAILABLE) ||
+ (tmp_cl->state & MY_CS_HIDDEN) ||
+ !my_charset_same(tmp_cs,tmp_cl))
+ continue;
+ restore_record(table, s->default_values);
+ table->field[0]->store(tmp_cl->name, strlen(tmp_cl->name), scs);
+ table->field[1]->store(tmp_cl->csname , strlen(tmp_cl->csname), scs);
+ if (schema_table_store_record(thd, table))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static inline void copy_field_as_string(Field *to_field, Field *from_field)
+{
+ char buff[MAX_FIELD_WIDTH];
+ String tmp_str(buff, sizeof(buff), system_charset_info);
+ from_field->val_str(&tmp_str);
+ to_field->store(tmp_str.ptr(), tmp_str.length(), system_charset_info);
+}
+
+
+/**
+ @brief Store record into I_S.PARAMETERS table
+
+ @param[in] thd thread handler
+ @param[in] table I_S table
+ @param[in] proc_table 'mysql.proc' table
+ @param[in] wild wild string, not used for now,
+ will be useful
+ if we add 'SHOW PARAMETERs'
+ @param[in] full_access if 1 user has privileges on the routine
+ @param[in] sp_user user in 'user@host' format
+
+ @return Operation status
+ @retval 0 ok
+ @retval 1 error
+*/
+
+bool store_schema_params(THD *thd, TABLE *table, TABLE *proc_table,
+ const char *wild, bool full_access,
+ const char *sp_user)
+{
+ TABLE_SHARE share;
+ TABLE tbl;
+ CHARSET_INFO *cs= system_charset_info;
+ char params_buff[MAX_FIELD_WIDTH], returns_buff[MAX_FIELD_WIDTH],
+ sp_db_buff[NAME_LEN], sp_name_buff[NAME_LEN], path[FN_REFLEN],
+ definer_buff[DEFINER_LENGTH + 1];
+ String params(params_buff, sizeof(params_buff), cs);
+ String returns(returns_buff, sizeof(returns_buff), cs);
+ String sp_db(sp_db_buff, sizeof(sp_db_buff), cs);
+ String sp_name(sp_name_buff, sizeof(sp_name_buff), cs);
+ String definer(definer_buff, sizeof(definer_buff), cs);
+ sp_head *sp;
+ stored_procedure_type routine_type;
+ bool free_sp_head;
+ DBUG_ENTER("store_schema_params");
+
+ bzero((char*) &tbl, sizeof(TABLE));
+ (void) build_table_filename(path, sizeof(path), "", "", "", 0);
+ init_tmp_table_share(thd, &share, "", 0, "", path);
+
+ get_field(thd->mem_root, proc_table->field[MYSQL_PROC_FIELD_DB], &sp_db);
+ get_field(thd->mem_root, proc_table->field[MYSQL_PROC_FIELD_NAME], &sp_name);
+ get_field(thd->mem_root,proc_table->field[MYSQL_PROC_FIELD_DEFINER],&definer);
+ routine_type= (stored_procedure_type) proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_int();
+
+ if (!full_access)
+ full_access= !strcmp(sp_user, definer.ptr());
+ if (!full_access &&
+ check_some_routine_access(thd, sp_db.ptr(),sp_name.ptr(),
+ routine_type == TYPE_ENUM_PROCEDURE))
+ DBUG_RETURN(0);
+
+ params.length(0);
+ get_field(thd->mem_root, proc_table->field[MYSQL_PROC_FIELD_PARAM_LIST],
+ &params);
+ returns.length(0);
+ if (routine_type == TYPE_ENUM_FUNCTION)
+ get_field(thd->mem_root, proc_table->field[MYSQL_PROC_FIELD_RETURNS],
+ &returns);
+
+ sp= sp_load_for_information_schema(thd, proc_table, &sp_db, &sp_name,
+ (ulong) proc_table->
+ field[MYSQL_PROC_FIELD_SQL_MODE]->val_int(),
+ routine_type,
+ returns.c_ptr_safe(),
+ params.c_ptr_safe(),
+ &free_sp_head);
+
+ if (sp)
+ {
+ Field *field;
+ Create_field *field_def;
+ String tmp_string;
+ if (routine_type == TYPE_ENUM_FUNCTION)
+ {
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(sp_db.ptr(), sp_db.length(), cs);
+ table->field[2]->store(sp_name.ptr(), sp_name.length(), cs);
+ table->field[3]->store((longlong) 0, TRUE);
+ get_field(thd->mem_root, proc_table->field[MYSQL_PROC_MYSQL_TYPE],
+ &tmp_string);
+ table->field[15]->store(tmp_string.ptr(), tmp_string.length(), cs);
+ field_def= &sp->m_return_field_def;
+ field= make_field(&share, (uchar*) 0, field_def->length,
+ (uchar*) "", 0, field_def->pack_flag,
+ field_def->sql_type, field_def->charset,
+ field_def->geom_type, Field::NONE,
+ field_def->interval, "");
+
+ field->table= &tbl;
+ tbl.in_use= thd;
+ store_column_type(table, field, cs, 6);
+ if (schema_table_store_record(thd, table))
+ {
+ free_table_share(&share);
+ if (free_sp_head)
+ delete sp;
+ DBUG_RETURN(1);
+ }
+ }
+
+ sp_pcontext *spcont= sp->get_parse_context();
+ uint params= spcont->context_var_count();
+ for (uint i= 0 ; i < params ; i++)
+ {
+ const char *tmp_buff;
+ sp_variable *spvar= spcont->find_variable(i);
+ field_def= &spvar->field_def;
+ switch (spvar->mode) {
+ case sp_variable::MODE_IN:
+ tmp_buff= "IN";
+ break;
+ case sp_variable::MODE_OUT:
+ tmp_buff= "OUT";
+ break;
+ case sp_variable::MODE_INOUT:
+ tmp_buff= "INOUT";
+ break;
+ default:
+ tmp_buff= "";
+ break;
+ }
+
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(sp_db.ptr(), sp_db.length(), cs);
+ table->field[2]->store(sp_name.ptr(), sp_name.length(), cs);
+ table->field[3]->store((longlong) i + 1, TRUE);
+ table->field[4]->store(tmp_buff, strlen(tmp_buff), cs);
+ table->field[4]->set_notnull();
+ table->field[5]->store(spvar->name.str, spvar->name.length, cs);
+ table->field[5]->set_notnull();
+ get_field(thd->mem_root, proc_table->field[MYSQL_PROC_MYSQL_TYPE],
+ &tmp_string);
+ table->field[15]->store(tmp_string.ptr(), tmp_string.length(), cs);
+
+ field= make_field(&share, (uchar*) 0, field_def->length,
+ (uchar*) "", 0, field_def->pack_flag,
+ field_def->sql_type, field_def->charset,
+ field_def->geom_type, Field::NONE,
+ field_def->interval, spvar->name.str);
+
+ field->table= &tbl;
+ tbl.in_use= thd;
+ store_column_type(table, field, cs, 6);
+ if (schema_table_store_record(thd, table))
+ {
+ free_table_share(&share);
+ if (free_sp_head)
+ delete sp;
+ DBUG_RETURN(1);
+ }
+ }
+ if (free_sp_head)
+ delete sp;
+ }
+ free_table_share(&share);
+ DBUG_RETURN(0);
+}
+
+
+bool store_schema_proc(THD *thd, TABLE *table, TABLE *proc_table,
+ const char *wild, bool full_access, const char *sp_user)
+{
+ MYSQL_TIME time;
+ LEX *lex= thd->lex;
+ CHARSET_INFO *cs= system_charset_info;
+ char sp_db_buff[SAFE_NAME_LEN + 1], sp_name_buff[NAME_LEN + 1],
+ definer_buff[DEFINER_LENGTH + 1],
+ returns_buff[MAX_FIELD_WIDTH];
+
+ String sp_db(sp_db_buff, sizeof(sp_db_buff), cs);
+ String sp_name(sp_name_buff, sizeof(sp_name_buff), cs);
+ String definer(definer_buff, sizeof(definer_buff), cs);
+ String returns(returns_buff, sizeof(returns_buff), cs);
+
+ proc_table->field[MYSQL_PROC_FIELD_DB]->val_str(&sp_db);
+ proc_table->field[MYSQL_PROC_FIELD_NAME]->val_str(&sp_name);
+ proc_table->field[MYSQL_PROC_FIELD_DEFINER]->val_str(&definer);
+
+ if (!full_access)
+ full_access= !strcmp(sp_user, definer.c_ptr_safe());
+ if (!full_access &&
+ check_some_routine_access(thd, sp_db.c_ptr_safe(), sp_name.c_ptr_safe(),
+ proc_table->field[MYSQL_PROC_MYSQL_TYPE]->
+ val_int() == TYPE_ENUM_PROCEDURE))
+ return 0;
+
+ if ((lex->sql_command == SQLCOM_SHOW_STATUS_PROC &&
+ proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_int() ==
+ TYPE_ENUM_PROCEDURE) ||
+ (lex->sql_command == SQLCOM_SHOW_STATUS_FUNC &&
+ proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_int() ==
+ TYPE_ENUM_FUNCTION) ||
+ (sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) == 0)
+ {
+ restore_record(table, s->default_values);
+ if (!wild || !wild[0] || !wild_case_compare(system_charset_info,
+ sp_name.c_ptr_safe(), wild))
+ {
+ int enum_idx= (int) proc_table->field[MYSQL_PROC_FIELD_ACCESS]->val_int();
+ table->field[3]->store(sp_name.ptr(), sp_name.length(), cs);
+
+ copy_field_as_string(table->field[0],
+ proc_table->field[MYSQL_PROC_FIELD_SPECIFIC_NAME]);
+ table->field[1]->store(STRING_WITH_LEN("def"), cs);
+ table->field[2]->store(sp_db.ptr(), sp_db.length(), cs);
+ copy_field_as_string(table->field[4],
+ proc_table->field[MYSQL_PROC_MYSQL_TYPE]);
+
+ if (proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_int() ==
+ TYPE_ENUM_FUNCTION)
+ {
+ sp_head *sp;
+ bool free_sp_head;
+ proc_table->field[MYSQL_PROC_FIELD_RETURNS]->val_str(&returns);
+ sp= sp_load_for_information_schema(thd, proc_table, &sp_db, &sp_name,
+ (ulong) proc_table->
+ field[MYSQL_PROC_FIELD_SQL_MODE]->
+ val_int(),
+ TYPE_ENUM_FUNCTION,
+ returns.c_ptr_safe(),
+ "", &free_sp_head);
+
+ if (sp)
+ {
+ char path[FN_REFLEN];
+ TABLE_SHARE share;
+ TABLE tbl;
+ Field *field;
+ Create_field *field_def= &sp->m_return_field_def;
+
+ bzero((char*) &tbl, sizeof(TABLE));
+ (void) build_table_filename(path, sizeof(path), "", "", "", 0);
+ init_tmp_table_share(thd, &share, "", 0, "", path);
+ field= make_field(&share, (uchar*) 0, field_def->length,
+ (uchar*) "", 0, field_def->pack_flag,
+ field_def->sql_type, field_def->charset,
+ field_def->geom_type, Field::NONE,
+ field_def->interval, "");
+
+ field->table= &tbl;
+ tbl.in_use= thd;
+ store_column_type(table, field, cs, 5);
+ free_table_share(&share);
+ if (free_sp_head)
+ delete sp;
+ }
+ }
+
+ if (full_access)
+ {
+ copy_field_as_string(table->field[15],
+ proc_table->field[MYSQL_PROC_FIELD_BODY_UTF8]);
+ table->field[15]->set_notnull();
+ }
+ table->field[14]->store(STRING_WITH_LEN("SQL"), cs);
+ table->field[18]->store(STRING_WITH_LEN("SQL"), cs);
+ copy_field_as_string(table->field[19],
+ proc_table->field[MYSQL_PROC_FIELD_DETERMINISTIC]);
+ table->field[20]->store(sp_data_access_name[enum_idx].str,
+ sp_data_access_name[enum_idx].length , cs);
+ copy_field_as_string(table->field[22],
+ proc_table->field[MYSQL_PROC_FIELD_SECURITY_TYPE]);
+
+ bzero((char *)&time, sizeof(time));
+ ((Field_timestamp *) proc_table->field[MYSQL_PROC_FIELD_CREATED])->
+ get_time(&time);
+ table->field[23]->store_time(&time);
+ bzero((char *)&time, sizeof(time));
+ ((Field_timestamp *) proc_table->field[MYSQL_PROC_FIELD_MODIFIED])->
+ get_time(&time);
+ table->field[24]->store_time(&time);
+ copy_field_as_string(table->field[25],
+ proc_table->field[MYSQL_PROC_FIELD_SQL_MODE]);
+ copy_field_as_string(table->field[26],
+ proc_table->field[MYSQL_PROC_FIELD_COMMENT]);
+
+ table->field[27]->store(definer.ptr(), definer.length(), cs);
+ copy_field_as_string(table->field[28],
+ proc_table->
+ field[MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT]);
+ copy_field_as_string(table->field[29],
+ proc_table->
+ field[MYSQL_PROC_FIELD_COLLATION_CONNECTION]);
+ copy_field_as_string(table->field[30],
+ proc_table->field[MYSQL_PROC_FIELD_DB_COLLATION]);
+
+ return schema_table_store_record(thd, table);
+ }
+ }
+ return 0;
+}
+
+
+int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ TABLE *proc_table;
+ TABLE_LIST proc_tables;
+ const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
+ int res= 0;
+ TABLE *table= tables->table;
+ bool full_access;
+ char definer[USER_HOST_BUFF_SIZE];
+ Open_tables_backup open_tables_state_backup;
+ enum enum_schema_tables schema_table_idx=
+ get_schema_table_idx(tables->schema_table);
+ DBUG_ENTER("fill_schema_proc");
+
+ strxmov(definer, thd->security_ctx->priv_user, "@",
+ thd->security_ctx->priv_host, NullS);
+ /* We use this TABLE_LIST instance only for checking of privileges. */
+ bzero((char*) &proc_tables,sizeof(proc_tables));
+ proc_tables.db= (char*) "mysql";
+ proc_tables.db_length= 5;
+ proc_tables.table_name= proc_tables.alias= (char*) "proc";
+ proc_tables.table_name_length= 4;
+ proc_tables.lock_type= TL_READ;
+ full_access= !check_table_access(thd, SELECT_ACL, &proc_tables, FALSE,
+ 1, TRUE);
+ if (!(proc_table= open_proc_table_for_read(thd, &open_tables_state_backup)))
+ {
+ DBUG_RETURN(1);
+ }
+
+ if (proc_table->file->ha_index_init(0, 1))
+ {
+ res= 1;
+ goto err;
+ }
+
+ if ((res= proc_table->file->ha_index_first(proc_table->record[0])))
+ {
+ res= (res == HA_ERR_END_OF_FILE) ? 0 : 1;
+ goto err;
+ }
+
+ if (schema_table_idx == SCH_PROCEDURES ?
+ store_schema_proc(thd, table, proc_table, wild, full_access, definer) :
+ store_schema_params(thd, table, proc_table, wild, full_access, definer))
+ {
+ res= 1;
+ goto err;
+ }
+ while (!proc_table->file->ha_index_next(proc_table->record[0]))
+ {
+ if (schema_table_idx == SCH_PROCEDURES ?
+ store_schema_proc(thd, table, proc_table, wild, full_access, definer):
+ store_schema_params(thd, table, proc_table, wild, full_access, definer))
+ {
+ res= 1;
+ goto err;
+ }
+ }
+
+err:
+ if (proc_table->file->inited)
+ (void) proc_table->file->ha_index_end();
+
+ close_system_tables(thd, &open_tables_state_backup);
+ DBUG_RETURN(res);
+}
+
+
+static int get_schema_stat_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ CHARSET_INFO *cs= system_charset_info;
+ DBUG_ENTER("get_schema_stat_record");
+ if (res)
+ {
+ if (thd->lex->sql_command != SQLCOM_SHOW_KEYS)
+ {
+ /*
+ I.e. we are in SELECT FROM INFORMATION_SCHEMA.STATISTICS
+ rather than in SHOW KEYS
+ */
+ if (thd->is_error())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->clear_error();
+ res= 0;
+ }
+ DBUG_RETURN(res);
+ }
+ else if (!tables->view)
+ {
+ TABLE *show_table= tables->table;
+ KEY *key_info=show_table->s->key_info;
+ if (show_table->file)
+ {
+ show_table->file->info(HA_STATUS_VARIABLE |
+ HA_STATUS_NO_LOCK |
+ HA_STATUS_TIME);
+ set_statistics_for_table(thd, show_table);
+ }
+ for (uint i=0 ; i < show_table->s->keys ; i++,key_info++)
+ {
+ KEY_PART_INFO *key_part= key_info->key_part;
+ const char *str;
+ for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
+ {
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(table_name->str, table_name->length, cs);
+ table->field[3]->store((longlong) ((key_info->flags &
+ HA_NOSAME) ? 0 : 1), TRUE);
+ table->field[4]->store(db_name->str, db_name->length, cs);
+ table->field[5]->store(key_info->name, strlen(key_info->name), cs);
+ table->field[6]->store((longlong) (j+1), TRUE);
+ str=(key_part->field ? key_part->field->field_name :
+ "?unknown field?");
+ table->field[7]->store(str, strlen(str), cs);
+ if (show_table->file)
+ {
+ if (show_table->file->index_flags(i, j, 0) & HA_READ_ORDER)
+ {
+ table->field[8]->store(((key_part->key_part_flag &
+ HA_REVERSE_SORT) ?
+ "D" : "A"), 1, cs);
+ table->field[8]->set_notnull();
+ }
+ KEY *key=show_table->key_info+i;
+ if (key->rec_per_key[j])
+ {
+ ha_rows records= (ha_rows) ((double) show_table->stat_records() /
+ key->actual_rec_per_key(j));
+ table->field[9]->store((longlong) records, TRUE);
+ table->field[9]->set_notnull();
+ }
+ str= show_table->file->index_type(i);
+ table->field[13]->store(str, strlen(str), cs);
+ }
+ if (!(key_info->flags & HA_FULLTEXT) &&
+ (key_part->field &&
+ key_part->length !=
+ show_table->s->field[key_part->fieldnr-1]->key_length()))
+ {
+ table->field[10]->store((longlong) key_part->length /
+ key_part->field->charset()->mbmaxlen, TRUE);
+ table->field[10]->set_notnull();
+ }
+ uint flags= key_part->field ? key_part->field->flags : 0;
+ const char *pos=(char*) ((flags & NOT_NULL_FLAG) ? "" : "YES");
+ table->field[12]->store(pos, strlen(pos), cs);
+ if (!show_table->s->keys_in_use.is_set(i))
+ table->field[14]->store(STRING_WITH_LEN("disabled"), cs);
+ else
+ table->field[14]->store("", 0, cs);
+ table->field[14]->set_notnull();
+ DBUG_ASSERT(MY_TEST(key_info->flags & HA_USES_COMMENT) ==
+ (key_info->comment.length > 0));
+ if (key_info->flags & HA_USES_COMMENT)
+ table->field[15]->store(key_info->comment.str,
+ key_info->comment.length, cs);
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ }
+ }
+ DBUG_RETURN(res);
+}
+
+
+static int get_schema_views_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ CHARSET_INFO *cs= system_charset_info;
+ char definer[USER_HOST_BUFF_SIZE];
+ uint definer_len;
+ bool updatable_view;
+ DBUG_ENTER("get_schema_views_record");
+
+ if (tables->view)
+ {
+ Security_context *sctx= thd->security_ctx;
+ if (!tables->allowed_show)
+ {
+ if (!my_strcasecmp(system_charset_info, tables->definer.user.str,
+ sctx->priv_user) &&
+ !my_strcasecmp(system_charset_info, tables->definer.host.str,
+ sctx->priv_host))
+ tables->allowed_show= TRUE;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ else
+ {
+ if ((thd->col_access & (SHOW_VIEW_ACL|SELECT_ACL)) ==
+ (SHOW_VIEW_ACL|SELECT_ACL))
+ tables->allowed_show= TRUE;
+ else
+ {
+ TABLE_LIST table_list;
+ uint view_access;
+ memset(&table_list, 0, sizeof(table_list));
+ table_list.db= tables->db;
+ table_list.table_name= tables->table_name;
+ table_list.grant.privilege= thd->col_access;
+ view_access= get_table_grant(thd, &table_list);
+ if ((view_access & (SHOW_VIEW_ACL|SELECT_ACL)) ==
+ (SHOW_VIEW_ACL|SELECT_ACL))
+ tables->allowed_show= TRUE;
+ }
+ }
+#endif
+ }
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(table_name->str, table_name->length, cs);
+
+ if (tables->allowed_show)
+ {
+ table->field[3]->store(tables->view_body_utf8.str,
+ tables->view_body_utf8.length,
+ cs);
+ }
+
+ if (tables->with_check != VIEW_CHECK_NONE)
+ {
+ if (tables->with_check == VIEW_CHECK_LOCAL)
+ table->field[4]->store(STRING_WITH_LEN("LOCAL"), cs);
+ else
+ table->field[4]->store(STRING_WITH_LEN("CASCADED"), cs);
+ }
+ else
+ table->field[4]->store(STRING_WITH_LEN("NONE"), cs);
+
+ /*
+ Only try to fill in the information about view updatability
+ if it is requested as part of the top-level query (i.e.
+ it's select * from i_s.views, as opposed to, say, select
+ security_type from i_s.views). Do not try to access the
+ underlying tables if there was an error when opening the
+ view: all underlying tables are released back to the table
+ definition cache on error inside open_normal_and_derived_tables().
+ If a field is not assigned explicitly, it defaults to NULL.
+ */
+ if (res == FALSE &&
+ table->pos_in_table_list->table_open_method & OPEN_FULL_TABLE)
+ {
+ updatable_view= 0;
+ if (tables->algorithm != VIEW_ALGORITHM_TMPTABLE)
+ {
+ /*
+ We should use tables->view->select_lex.item_list here
+ and can not use Field_iterator_view because the view
+ always uses temporary algorithm during opening for I_S
+ and TABLE_LIST fields 'field_translation'
+ & 'field_translation_end' are uninitialized is this
+ case.
+ */
+ List<Item> *fields= &tables->view->select_lex.item_list;
+ List_iterator<Item> it(*fields);
+ Item *item;
+ Item_field *field;
+ /*
+ check that at least one column in view is updatable
+ */
+ while ((item= it++))
+ {
+ if ((field= item->field_for_view_update()) && field->field &&
+ !field->field->table->pos_in_table_list->schema_table)
+ {
+ updatable_view= 1;
+ break;
+ }
+ }
+ if (updatable_view && !tables->view->can_be_merged())
+ updatable_view= 0;
+ }
+ if (updatable_view)
+ table->field[5]->store(STRING_WITH_LEN("YES"), cs);
+ else
+ table->field[5]->store(STRING_WITH_LEN("NO"), cs);
+ }
+
+ definer_len= (strxmov(definer, tables->definer.user.str, "@",
+ tables->definer.host.str, NullS) - definer);
+ table->field[6]->store(definer, definer_len, cs);
+ if (tables->view_suid)
+ table->field[7]->store(STRING_WITH_LEN("DEFINER"), cs);
+ else
+ table->field[7]->store(STRING_WITH_LEN("INVOKER"), cs);
+
+ table->field[8]->store(tables->view_creation_ctx->get_client_cs()->csname,
+ strlen(tables->view_creation_ctx->
+ get_client_cs()->csname), cs);
+
+ table->field[9]->store(tables->view_creation_ctx->
+ get_connection_cl()->name,
+ strlen(tables->view_creation_ctx->
+ get_connection_cl()->name), cs);
+
+
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ if (res && thd->is_error())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ }
+ if (res)
+ thd->clear_error();
+ DBUG_RETURN(0);
+}
+
+
+bool store_constraints(THD *thd, TABLE *table, LEX_STRING *db_name,
+ LEX_STRING *table_name, const char *key_name,
+ uint key_len, const char *con_type, uint con_len)
+{
+ CHARSET_INFO *cs= system_charset_info;
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(key_name, key_len, cs);
+ table->field[3]->store(db_name->str, db_name->length, cs);
+ table->field[4]->store(table_name->str, table_name->length, cs);
+ table->field[5]->store(con_type, con_len, cs);
+ return schema_table_store_record(thd, table);
+}
+
+
+static int get_schema_constraints_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ DBUG_ENTER("get_schema_constraints_record");
+ if (res)
+ {
+ if (thd->is_error())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->clear_error();
+ DBUG_RETURN(0);
+ }
+ else if (!tables->view)
+ {
+ List<FOREIGN_KEY_INFO> f_key_list;
+ TABLE *show_table= tables->table;
+ KEY *key_info=show_table->key_info;
+ uint primary_key= show_table->s->primary_key;
+ show_table->file->info(HA_STATUS_VARIABLE |
+ HA_STATUS_NO_LOCK |
+ HA_STATUS_TIME);
+ for (uint i=0 ; i < show_table->s->keys ; i++, key_info++)
+ {
+ if (i != primary_key && !(key_info->flags & HA_NOSAME))
+ continue;
+
+ if (i == primary_key && !strcmp(key_info->name, primary_key_name))
+ {
+ if (store_constraints(thd, table, db_name, table_name, key_info->name,
+ strlen(key_info->name),
+ STRING_WITH_LEN("PRIMARY KEY")))
+ DBUG_RETURN(1);
+ }
+ else if (key_info->flags & HA_NOSAME)
+ {
+ if (store_constraints(thd, table, db_name, table_name, key_info->name,
+ strlen(key_info->name),
+ STRING_WITH_LEN("UNIQUE")))
+ DBUG_RETURN(1);
+ }
+ }
+
+ show_table->file->get_foreign_key_list(thd, &f_key_list);
+ FOREIGN_KEY_INFO *f_key_info;
+ List_iterator_fast<FOREIGN_KEY_INFO> it(f_key_list);
+ while ((f_key_info=it++))
+ {
+ if (store_constraints(thd, table, db_name, table_name,
+ f_key_info->foreign_id->str,
+ strlen(f_key_info->foreign_id->str),
+ "FOREIGN KEY", 11))
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(res);
+}
+
+
+static bool store_trigger(THD *thd, TABLE *table, LEX_STRING *db_name,
+ LEX_STRING *table_name, LEX_STRING *trigger_name,
+ enum trg_event_type event,
+ enum trg_action_time_type timing,
+ LEX_STRING *trigger_stmt,
+ ulong sql_mode,
+ LEX_STRING *definer_buffer,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name)
+{
+ CHARSET_INFO *cs= system_charset_info;
+ LEX_STRING sql_mode_rep;
+
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(trigger_name->str, trigger_name->length, cs);
+ table->field[3]->store(trg_event_type_names[event].str,
+ trg_event_type_names[event].length, cs);
+ table->field[4]->store(STRING_WITH_LEN("def"), cs);
+ table->field[5]->store(db_name->str, db_name->length, cs);
+ table->field[6]->store(table_name->str, table_name->length, cs);
+ table->field[9]->store(trigger_stmt->str, trigger_stmt->length, cs);
+ table->field[10]->store(STRING_WITH_LEN("ROW"), cs);
+ table->field[11]->store(trg_action_time_type_names[timing].str,
+ trg_action_time_type_names[timing].length, cs);
+ table->field[14]->store(STRING_WITH_LEN("OLD"), cs);
+ table->field[15]->store(STRING_WITH_LEN("NEW"), cs);
+
+ sql_mode_string_representation(thd, sql_mode, &sql_mode_rep);
+ table->field[17]->store(sql_mode_rep.str, sql_mode_rep.length, cs);
+ table->field[18]->store(definer_buffer->str, definer_buffer->length, cs);
+ table->field[19]->store(client_cs_name->str, client_cs_name->length, cs);
+ table->field[20]->store(connection_cl_name->str,
+ connection_cl_name->length, cs);
+ table->field[21]->store(db_cl_name->str, db_cl_name->length, cs);
+
+ return schema_table_store_record(thd, table);
+}
+
+
+static int get_schema_triggers_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ DBUG_ENTER("get_schema_triggers_record");
+ /*
+ res can be non zero value when processed table is a view or
+ error happened during opening of processed table.
+ */
+ if (res)
+ {
+ if (thd->is_error())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->clear_error();
+ DBUG_RETURN(0);
+ }
+ if (!tables->view && tables->table->triggers)
+ {
+ Table_triggers_list *triggers= tables->table->triggers;
+ int event, timing;
+
+ if (check_table_access(thd, TRIGGER_ACL, tables, FALSE, 1, TRUE))
+ goto ret;
+
+ for (event= 0; event < (int)TRG_EVENT_MAX; event++)
+ {
+ for (timing= 0; timing < (int)TRG_ACTION_MAX; timing++)
+ {
+ LEX_STRING trigger_name;
+ LEX_STRING trigger_stmt;
+ ulong sql_mode;
+ char definer_holder[USER_HOST_BUFF_SIZE];
+ LEX_STRING definer_buffer;
+ LEX_STRING client_cs_name;
+ LEX_STRING connection_cl_name;
+ LEX_STRING db_cl_name;
+
+ definer_buffer.str= definer_holder;
+ if (triggers->get_trigger_info(thd, (enum trg_event_type) event,
+ (enum trg_action_time_type)timing,
+ &trigger_name, &trigger_stmt,
+ &sql_mode,
+ &definer_buffer,
+ &client_cs_name,
+ &connection_cl_name,
+ &db_cl_name))
+ continue;
+
+ if (store_trigger(thd, table, db_name, table_name, &trigger_name,
+ (enum trg_event_type) event,
+ (enum trg_action_time_type) timing, &trigger_stmt,
+ sql_mode,
+ &definer_buffer,
+ &client_cs_name,
+ &connection_cl_name,
+ &db_cl_name))
+ DBUG_RETURN(1);
+ }
+ }
+ }
+ret:
+ DBUG_RETURN(0);
+}
+
+
+void store_key_column_usage(TABLE *table, LEX_STRING *db_name,
+ LEX_STRING *table_name, const char *key_name,
+ uint key_len, const char *con_type, uint con_len,
+ longlong idx)
+{
+ CHARSET_INFO *cs= system_charset_info;
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(key_name, key_len, cs);
+ table->field[3]->store(STRING_WITH_LEN("def"), cs);
+ table->field[4]->store(db_name->str, db_name->length, cs);
+ table->field[5]->store(table_name->str, table_name->length, cs);
+ table->field[6]->store(con_type, con_len, cs);
+ table->field[7]->store((longlong) idx, TRUE);
+}
+
+
+static int get_schema_key_column_usage_record(THD *thd,
+ TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ DBUG_ENTER("get_schema_key_column_usage_record");
+ if (res)
+ {
+ if (thd->is_error())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->clear_error();
+ DBUG_RETURN(0);
+ }
+ else if (!tables->view)
+ {
+ List<FOREIGN_KEY_INFO> f_key_list;
+ TABLE *show_table= tables->table;
+ KEY *key_info=show_table->key_info;
+ uint primary_key= show_table->s->primary_key;
+ show_table->file->info(HA_STATUS_VARIABLE |
+ HA_STATUS_NO_LOCK |
+ HA_STATUS_TIME);
+ for (uint i=0 ; i < show_table->s->keys ; i++, key_info++)
+ {
+ if (i != primary_key && !(key_info->flags & HA_NOSAME))
+ continue;
+ uint f_idx= 0;
+ KEY_PART_INFO *key_part= key_info->key_part;
+ for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
+ {
+ if (key_part->field)
+ {
+ f_idx++;
+ restore_record(table, s->default_values);
+ store_key_column_usage(table, db_name, table_name,
+ key_info->name,
+ strlen(key_info->name),
+ key_part->field->field_name,
+ strlen(key_part->field->field_name),
+ (longlong) f_idx);
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ }
+ }
+
+ show_table->file->get_foreign_key_list(thd, &f_key_list);
+ FOREIGN_KEY_INFO *f_key_info;
+ List_iterator_fast<FOREIGN_KEY_INFO> fkey_it(f_key_list);
+ while ((f_key_info= fkey_it++))
+ {
+ LEX_STRING *f_info;
+ LEX_STRING *r_info;
+ List_iterator_fast<LEX_STRING> it(f_key_info->foreign_fields),
+ it1(f_key_info->referenced_fields);
+ uint f_idx= 0;
+ while ((f_info= it++))
+ {
+ r_info= it1++;
+ f_idx++;
+ restore_record(table, s->default_values);
+ store_key_column_usage(table, db_name, table_name,
+ f_key_info->foreign_id->str,
+ f_key_info->foreign_id->length,
+ f_info->str, f_info->length,
+ (longlong) f_idx);
+ table->field[8]->store((longlong) f_idx, TRUE);
+ table->field[8]->set_notnull();
+ table->field[9]->store(f_key_info->referenced_db->str,
+ f_key_info->referenced_db->length,
+ system_charset_info);
+ table->field[9]->set_notnull();
+ table->field[10]->store(f_key_info->referenced_table->str,
+ f_key_info->referenced_table->length,
+ system_charset_info);
+ table->field[10]->set_notnull();
+ table->field[11]->store(r_info->str, r_info->length,
+ system_charset_info);
+ table->field[11]->set_notnull();
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ }
+ }
+ DBUG_RETURN(res);
+}
+
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+static void collect_partition_expr(THD *thd, List<char> &field_list,
+ String *str)
+{
+ List_iterator<char> part_it(field_list);
+ ulong no_fields= field_list.elements;
+ const char *field_str;
+ str->length(0);
+ while ((field_str= part_it++))
+ {
+ append_identifier(thd, str, field_str, strlen(field_str));
+ if (--no_fields != 0)
+ str->append(",");
+ }
+ return;
+}
+
+
+/*
+ Convert a string in a given character set to a string which can be
+ used for FRM file storage in which case use_hex is TRUE and we store
+ the character constants as hex strings in the character set encoding
+ their field have. In the case of SHOW CREATE TABLE and the
+ PARTITIONS information schema table we instead provide utf8 strings
+ to the user and convert to the utf8 character set.
+
+ SYNOPSIS
+ get_cs_converted_part_value_from_string()
+ item Item from which constant comes
+ input_str String as provided by val_str after
+ conversion to character set
+ output_str Out value: The string created
+ cs Character set string is encoded in
+ NULL for INT_RESULT's here
+ use_hex TRUE => hex string created
+ FALSE => utf8 constant string created
+
+ RETURN VALUES
+ TRUE Error
+ FALSE Ok
+*/
+
+int get_cs_converted_part_value_from_string(THD *thd,
+ Item *item,
+ String *input_str,
+ String *output_str,
+ CHARSET_INFO *cs,
+ bool use_hex)
+{
+ if (item->result_type() == INT_RESULT)
+ {
+ longlong value= item->val_int();
+ output_str->set(value, system_charset_info);
+ return FALSE;
+ }
+ if (!input_str)
+ {
+ my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
+ return TRUE;
+ }
+ get_cs_converted_string_value(thd,
+ input_str,
+ output_str,
+ cs,
+ use_hex);
+ return FALSE;
+}
+#endif
+
+
+static void store_schema_partitions_record(THD *thd, TABLE *schema_table,
+ TABLE *showing_table,
+ partition_element *part_elem,
+ handler *file, uint part_id)
+{
+ TABLE* table= schema_table;
+ CHARSET_INFO *cs= system_charset_info;
+ PARTITION_STATS stat_info;
+ MYSQL_TIME time;
+ file->get_dynamic_partition_info(&stat_info, part_id);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[12]->store((longlong) stat_info.records, TRUE);
+ table->field[13]->store((longlong) stat_info.mean_rec_length, TRUE);
+ table->field[14]->store((longlong) stat_info.data_file_length, TRUE);
+ if (stat_info.max_data_file_length)
+ {
+ table->field[15]->store((longlong) stat_info.max_data_file_length, TRUE);
+ table->field[15]->set_notnull();
+ }
+ table->field[16]->store((longlong) stat_info.index_file_length, TRUE);
+ table->field[17]->store((longlong) stat_info.delete_length, TRUE);
+ if (stat_info.create_time)
+ {
+ thd->variables.time_zone->gmt_sec_to_TIME(&time,
+ (my_time_t)stat_info.create_time);
+ table->field[18]->store_time(&time);
+ table->field[18]->set_notnull();
+ }
+ if (stat_info.update_time)
+ {
+ thd->variables.time_zone->gmt_sec_to_TIME(&time,
+ (my_time_t)stat_info.update_time);
+ table->field[19]->store_time(&time);
+ table->field[19]->set_notnull();
+ }
+ if (stat_info.check_time)
+ {
+ thd->variables.time_zone->gmt_sec_to_TIME(&time,
+ (my_time_t)stat_info.check_time);
+ table->field[20]->store_time(&time);
+ table->field[20]->set_notnull();
+ }
+ if (file->ha_table_flags() & (HA_HAS_OLD_CHECKSUM | HA_HAS_NEW_CHECKSUM))
+ {
+ table->field[21]->store((longlong) stat_info.check_sum, TRUE);
+ table->field[21]->set_notnull();
+ }
+ if (part_elem)
+ {
+ if (part_elem->part_comment)
+ table->field[22]->store(part_elem->part_comment,
+ strlen(part_elem->part_comment), cs);
+ else
+ table->field[22]->store(STRING_WITH_LEN(""), cs);
+ if (part_elem->nodegroup_id != UNDEF_NODEGROUP)
+ table->field[23]->store((longlong) part_elem->nodegroup_id, TRUE);
+ else
+ table->field[23]->store(STRING_WITH_LEN("default"), cs);
+
+ table->field[24]->set_notnull();
+ if (part_elem->tablespace_name)
+ table->field[24]->store(part_elem->tablespace_name,
+ strlen(part_elem->tablespace_name), cs);
+ else
+ {
+ char *ts= showing_table->s->tablespace;
+ if(ts)
+ table->field[24]->store(ts, strlen(ts), cs);
+ else
+ table->field[24]->set_null();
+ }
+ }
+ return;
+}
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+static int
+get_partition_column_description(THD *thd,
+ partition_info *part_info,
+ part_elem_value *list_value,
+ String &tmp_str)
+{
+ uint num_elements= part_info->part_field_list.elements;
+ uint i;
+ DBUG_ENTER("get_partition_column_description");
+
+ for (i= 0; i < num_elements; i++)
+ {
+ part_column_list_val *col_val= &list_value->col_val_array[i];
+ if (col_val->max_value)
+ tmp_str.append(partition_keywords[PKW_MAXVALUE].str);
+ else if (col_val->null_value)
+ tmp_str.append("NULL");
+ else
+ {
+ char buffer[MAX_KEY_LENGTH];
+ String str(buffer, sizeof(buffer), &my_charset_bin);
+ String val_conv;
+ Item *item= col_val->item_expression;
+
+ if (!(item= part_info->get_column_item(item,
+ part_info->part_field_array[i])))
+ {
+ DBUG_RETURN(1);
+ }
+ String *res= item->val_str(&str);
+ if (get_cs_converted_part_value_from_string(thd, item, res, &val_conv,
+ part_info->part_field_array[i]->charset(),
+ FALSE))
+ {
+ DBUG_RETURN(1);
+ }
+ tmp_str.append(val_conv);
+ }
+ if (i != num_elements - 1)
+ tmp_str.append(",");
+ }
+ DBUG_RETURN(0);
+}
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
+
+static int get_schema_partitions_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name,
+ LEX_STRING *table_name)
+{
+ CHARSET_INFO *cs= system_charset_info;
+ char buff[61];
+ String tmp_res(buff, sizeof(buff), cs);
+ String tmp_str;
+ TABLE *show_table= tables->table;
+ handler *file;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info;
+#endif
+ DBUG_ENTER("get_schema_partitions_record");
+
+ if (res)
+ {
+ if (thd->is_error())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->clear_error();
+ DBUG_RETURN(0);
+ }
+ file= show_table->file;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ part_info= show_table->part_info;
+ if (part_info)
+ {
+ partition_element *part_elem;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ uint part_pos= 0, part_id= 0;
+
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[2]->store(table_name->str, table_name->length, cs);
+
+
+ /* Partition method*/
+ switch (part_info->part_type) {
+ case RANGE_PARTITION:
+ case LIST_PARTITION:
+ tmp_res.length(0);
+ if (part_info->part_type == RANGE_PARTITION)
+ tmp_res.append(partition_keywords[PKW_RANGE].str,
+ partition_keywords[PKW_RANGE].length);
+ else
+ tmp_res.append(partition_keywords[PKW_LIST].str,
+ partition_keywords[PKW_LIST].length);
+ if (part_info->column_list)
+ tmp_res.append(partition_keywords[PKW_COLUMNS].str,
+ partition_keywords[PKW_COLUMNS].length);
+ table->field[7]->store(tmp_res.ptr(), tmp_res.length(), cs);
+ break;
+ case HASH_PARTITION:
+ tmp_res.length(0);
+ if (part_info->linear_hash_ind)
+ tmp_res.append(partition_keywords[PKW_LINEAR].str,
+ partition_keywords[PKW_LINEAR].length);
+ if (part_info->list_of_part_fields)
+ tmp_res.append(partition_keywords[PKW_KEY].str,
+ partition_keywords[PKW_KEY].length);
+ else
+ tmp_res.append(partition_keywords[PKW_HASH].str,
+ partition_keywords[PKW_HASH].length);
+ table->field[7]->store(tmp_res.ptr(), tmp_res.length(), cs);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+ DBUG_RETURN(1);
+ }
+ table->field[7]->set_notnull();
+
+ /* Partition expression */
+ if (part_info->part_expr)
+ {
+ table->field[9]->store(part_info->part_func_string,
+ part_info->part_func_len, cs);
+ }
+ else if (part_info->list_of_part_fields)
+ {
+ collect_partition_expr(thd, part_info->part_field_list, &tmp_str);
+ table->field[9]->store(tmp_str.ptr(), tmp_str.length(), cs);
+ }
+ table->field[9]->set_notnull();
+
+ if (part_info->is_sub_partitioned())
+ {
+ /* Subpartition method */
+ tmp_res.length(0);
+ if (part_info->linear_hash_ind)
+ tmp_res.append(partition_keywords[PKW_LINEAR].str,
+ partition_keywords[PKW_LINEAR].length);
+ if (part_info->list_of_subpart_fields)
+ tmp_res.append(partition_keywords[PKW_KEY].str,
+ partition_keywords[PKW_KEY].length);
+ else
+ tmp_res.append(partition_keywords[PKW_HASH].str,
+ partition_keywords[PKW_HASH].length);
+ table->field[8]->store(tmp_res.ptr(), tmp_res.length(), cs);
+ table->field[8]->set_notnull();
+
+ /* Subpartition expression */
+ if (part_info->subpart_expr)
+ {
+ table->field[10]->store(part_info->subpart_func_string,
+ part_info->subpart_func_len, cs);
+ }
+ else if (part_info->list_of_subpart_fields)
+ {
+ collect_partition_expr(thd, part_info->subpart_field_list, &tmp_str);
+ table->field[10]->store(tmp_str.ptr(), tmp_str.length(), cs);
+ }
+ table->field[10]->set_notnull();
+ }
+
+ while ((part_elem= part_it++))
+ {
+ table->field[3]->store(part_elem->partition_name,
+ strlen(part_elem->partition_name), cs);
+ table->field[3]->set_notnull();
+ /* PARTITION_ORDINAL_POSITION */
+ table->field[5]->store((longlong) ++part_pos, TRUE);
+ table->field[5]->set_notnull();
+
+ /* Partition description */
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ if (part_info->column_list)
+ {
+ List_iterator<part_elem_value> list_val_it(part_elem->list_val_list);
+ part_elem_value *list_value= list_val_it++;
+ tmp_str.length(0);
+ if (get_partition_column_description(thd,
+ part_info,
+ list_value,
+ tmp_str))
+ {
+ DBUG_RETURN(1);
+ }
+ table->field[11]->store(tmp_str.ptr(), tmp_str.length(), cs);
+ }
+ else
+ {
+ if (part_elem->range_value != LONGLONG_MAX)
+ table->field[11]->store((longlong) part_elem->range_value, FALSE);
+ else
+ table->field[11]->store(partition_keywords[PKW_MAXVALUE].str,
+ partition_keywords[PKW_MAXVALUE].length, cs);
+ }
+ table->field[11]->set_notnull();
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+ List_iterator<part_elem_value> list_val_it(part_elem->list_val_list);
+ part_elem_value *list_value;
+ uint num_items= part_elem->list_val_list.elements;
+ tmp_str.length(0);
+ tmp_res.length(0);
+ if (part_elem->has_null_value)
+ {
+ tmp_str.append("NULL");
+ if (num_items > 0)
+ tmp_str.append(",");
+ }
+ while ((list_value= list_val_it++))
+ {
+ if (part_info->column_list)
+ {
+ if (part_info->part_field_list.elements > 1U)
+ tmp_str.append("(");
+ if (get_partition_column_description(thd,
+ part_info,
+ list_value,
+ tmp_str))
+ {
+ DBUG_RETURN(1);
+ }
+ if (part_info->part_field_list.elements > 1U)
+ tmp_str.append(")");
+ }
+ else
+ {
+ if (!list_value->unsigned_flag)
+ tmp_res.set(list_value->value, cs);
+ else
+ tmp_res.set((ulonglong)list_value->value, cs);
+ tmp_str.append(tmp_res);
+ }
+ if (--num_items != 0)
+ tmp_str.append(",");
+ }
+ table->field[11]->store(tmp_str.ptr(), tmp_str.length(), cs);
+ table->field[11]->set_notnull();
+ }
+
+ if (part_elem->subpartitions.elements)
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ partition_element *subpart_elem;
+ uint subpart_pos= 0;
+
+ while ((subpart_elem= sub_it++))
+ {
+ table->field[4]->store(subpart_elem->partition_name,
+ strlen(subpart_elem->partition_name), cs);
+ table->field[4]->set_notnull();
+ /* SUBPARTITION_ORDINAL_POSITION */
+ table->field[6]->store((longlong) ++subpart_pos, TRUE);
+ table->field[6]->set_notnull();
+
+ store_schema_partitions_record(thd, table, show_table, subpart_elem,
+ file, part_id);
+ part_id++;
+ if(schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ store_schema_partitions_record(thd, table, show_table, part_elem,
+ file, part_id);
+ part_id++;
+ if(schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+ }
+ else
+#endif
+ {
+ store_schema_partitions_record(thd, table, show_table, 0, file, 0);
+ if(schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+#ifdef HAVE_EVENT_SCHEDULER
+/*
+ Loads an event from mysql.event and copies it's data to a row of
+ I_S.EVENTS
+
+ Synopsis
+ copy_event_to_schema_table()
+ thd Thread
+ sch_table The schema table (information_schema.event)
+ event_table The event table to use for loading (mysql.event).
+
+ Returns
+ 0 OK
+ 1 Error
+*/
+
+int
+copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
+{
+ const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
+ CHARSET_INFO *scs= system_charset_info;
+ MYSQL_TIME time;
+ Event_timed et;
+ DBUG_ENTER("copy_event_to_schema_table");
+
+ restore_record(sch_table, s->default_values);
+
+ if (et.load_from_row(thd, event_table))
+ {
+ my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "event");
+ DBUG_RETURN(1);
+ }
+
+ if (!(!wild || !wild[0] || !wild_case_compare(scs, et.name.str, wild)))
+ DBUG_RETURN(0);
+
+ /*
+ Skip events in schemas one does not have access to. The check is
+ optimized. It's guaranteed in case of SHOW EVENTS that the user
+ has access.
+ */
+ if (thd->lex->sql_command != SQLCOM_SHOW_EVENTS &&
+ check_access(thd, EVENT_ACL, et.dbname.str, NULL, NULL, 0, 1))
+ DBUG_RETURN(0);
+
+ sch_table->field[ISE_EVENT_CATALOG]->store(STRING_WITH_LEN("def"), scs);
+ sch_table->field[ISE_EVENT_SCHEMA]->
+ store(et.dbname.str, et.dbname.length,scs);
+ sch_table->field[ISE_EVENT_NAME]->
+ store(et.name.str, et.name.length, scs);
+ sch_table->field[ISE_DEFINER]->
+ store(et.definer.str, et.definer.length, scs);
+ const String *tz_name= et.time_zone->get_name();
+ sch_table->field[ISE_TIME_ZONE]->
+ store(tz_name->ptr(), tz_name->length(), scs);
+ sch_table->field[ISE_EVENT_BODY]->
+ store(STRING_WITH_LEN("SQL"), scs);
+ sch_table->field[ISE_EVENT_DEFINITION]->store(
+ et.body_utf8.str, et.body_utf8.length, scs);
+
+ /* SQL_MODE */
+ {
+ LEX_STRING sql_mode;
+ sql_mode_string_representation(thd, et.sql_mode, &sql_mode);
+ sch_table->field[ISE_SQL_MODE]->
+ store(sql_mode.str, sql_mode.length, scs);
+ }
+
+ int not_used=0;
+
+ if (et.expression)
+ {
+ String show_str;
+ /* type */
+ sch_table->field[ISE_EVENT_TYPE]->store(STRING_WITH_LEN("RECURRING"), scs);
+
+ if (Events::reconstruct_interval_expression(&show_str, et.interval,
+ et.expression))
+ DBUG_RETURN(1);
+
+ sch_table->field[ISE_INTERVAL_VALUE]->set_notnull();
+ sch_table->field[ISE_INTERVAL_VALUE]->
+ store(show_str.ptr(), show_str.length(), scs);
+
+ LEX_STRING *ival= &interval_type_to_name[et.interval];
+ sch_table->field[ISE_INTERVAL_FIELD]->set_notnull();
+ sch_table->field[ISE_INTERVAL_FIELD]->store(ival->str, ival->length, scs);
+
+ /* starts & ends . STARTS is always set - see sql_yacc.yy */
+ et.time_zone->gmt_sec_to_TIME(&time, et.starts);
+ sch_table->field[ISE_STARTS]->set_notnull();
+ sch_table->field[ISE_STARTS]->store_time(&time);
+
+ if (!et.ends_null)
+ {
+ et.time_zone->gmt_sec_to_TIME(&time, et.ends);
+ sch_table->field[ISE_ENDS]->set_notnull();
+ sch_table->field[ISE_ENDS]->store_time(&time);
+ }
+ }
+ else
+ {
+ /* type */
+ sch_table->field[ISE_EVENT_TYPE]->store(STRING_WITH_LEN("ONE TIME"), scs);
+
+ et.time_zone->gmt_sec_to_TIME(&time, et.execute_at);
+ sch_table->field[ISE_EXECUTE_AT]->set_notnull();
+ sch_table->field[ISE_EXECUTE_AT]->store_time(&time);
+ }
+
+ /* status */
+
+ switch (et.status)
+ {
+ case Event_parse_data::ENABLED:
+ sch_table->field[ISE_STATUS]->store(STRING_WITH_LEN("ENABLED"), scs);
+ break;
+ case Event_parse_data::SLAVESIDE_DISABLED:
+ sch_table->field[ISE_STATUS]->store(STRING_WITH_LEN("SLAVESIDE_DISABLED"),
+ scs);
+ break;
+ case Event_parse_data::DISABLED:
+ sch_table->field[ISE_STATUS]->store(STRING_WITH_LEN("DISABLED"), scs);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ sch_table->field[ISE_ORIGINATOR]->store(et.originator, TRUE);
+
+ /* on_completion */
+ if (et.on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ sch_table->field[ISE_ON_COMPLETION]->
+ store(STRING_WITH_LEN("NOT PRESERVE"), scs);
+ else
+ sch_table->field[ISE_ON_COMPLETION]->
+ store(STRING_WITH_LEN("PRESERVE"), scs);
+
+ number_to_datetime(et.created, 0, &time, 0, &not_used);
+ DBUG_ASSERT(not_used==0);
+ sch_table->field[ISE_CREATED]->store_time(&time);
+
+ number_to_datetime(et.modified, 0, &time, 0, &not_used);
+ DBUG_ASSERT(not_used==0);
+ sch_table->field[ISE_LAST_ALTERED]->store_time(&time);
+
+ if (et.last_executed)
+ {
+ et.time_zone->gmt_sec_to_TIME(&time, et.last_executed);
+ sch_table->field[ISE_LAST_EXECUTED]->set_notnull();
+ sch_table->field[ISE_LAST_EXECUTED]->store_time(&time);
+ }
+
+ sch_table->field[ISE_EVENT_COMMENT]->
+ store(et.comment.str, et.comment.length, scs);
+
+ sch_table->field[ISE_CLIENT_CS]->set_notnull();
+ sch_table->field[ISE_CLIENT_CS]->store(
+ et.creation_ctx->get_client_cs()->csname,
+ strlen(et.creation_ctx->get_client_cs()->csname),
+ scs);
+
+ sch_table->field[ISE_CONNECTION_CL]->set_notnull();
+ sch_table->field[ISE_CONNECTION_CL]->store(
+ et.creation_ctx->get_connection_cl()->name,
+ strlen(et.creation_ctx->get_connection_cl()->name),
+ scs);
+
+ sch_table->field[ISE_DB_CL]->set_notnull();
+ sch_table->field[ISE_DB_CL]->store(
+ et.creation_ctx->get_db_cl()->name,
+ strlen(et.creation_ctx->get_db_cl()->name),
+ scs);
+
+ if (schema_table_store_record(thd, sch_table))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+#endif
+
+int fill_open_tables(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("fill_open_tables");
+ const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
+ TABLE *table= tables->table;
+ CHARSET_INFO *cs= system_charset_info;
+ OPEN_TABLE_LIST *open_list;
+ if (!(open_list=list_open_tables(thd,thd->lex->select_lex.db, wild))
+ && thd->is_fatal_error)
+ DBUG_RETURN(1);
+
+ for (; open_list ; open_list=open_list->next)
+ {
+ restore_record(table, s->default_values);
+ table->field[0]->store(open_list->db, strlen(open_list->db), cs);
+ table->field[1]->store(open_list->table, strlen(open_list->table), cs);
+ table->field[2]->store((longlong) open_list->in_use, TRUE);
+ table->field[3]->store((longlong) open_list->locked, TRUE);
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("fill_variables");
+ int res= 0;
+ LEX *lex= thd->lex;
+ const char *wild= lex->wild ? lex->wild->ptr() : NullS;
+ enum enum_schema_tables schema_table_idx=
+ get_schema_table_idx(tables->schema_table);
+ enum enum_var_type option_type= OPT_SESSION;
+ bool upper_case_names= (schema_table_idx != SCH_VARIABLES);
+ bool sorted_vars= (schema_table_idx == SCH_VARIABLES);
+
+ if ((sorted_vars && lex->option_type == OPT_GLOBAL) ||
+ schema_table_idx == SCH_GLOBAL_VARIABLES)
+ option_type= OPT_GLOBAL;
+
+ COND *partial_cond= make_cond_for_info_schema(cond, tables);
+
+ mysql_rwlock_rdlock(&LOCK_system_variables_hash);
+ res= show_status_array(thd, wild, enumerate_sys_vars(thd, sorted_vars, option_type),
+ option_type, NULL, "", tables->table,
+ upper_case_names, partial_cond);
+ mysql_rwlock_unlock(&LOCK_system_variables_hash);
+ DBUG_RETURN(res);
+}
+
+
+int fill_status(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("fill_status");
+ LEX *lex= thd->lex;
+ const char *wild= lex->wild ? lex->wild->ptr() : NullS;
+ int res= 0;
+ STATUS_VAR *tmp1, tmp;
+ enum enum_schema_tables schema_table_idx=
+ get_schema_table_idx(tables->schema_table);
+ enum enum_var_type option_type;
+ bool upper_case_names= (schema_table_idx != SCH_STATUS);
+
+ if (schema_table_idx == SCH_STATUS)
+ {
+ option_type= lex->option_type;
+ if (option_type == OPT_GLOBAL)
+ tmp1= &tmp;
+ else
+ tmp1= thd->initial_status_var;
+ }
+ else if (schema_table_idx == SCH_GLOBAL_STATUS)
+ {
+ option_type= OPT_GLOBAL;
+ tmp1= &tmp;
+ }
+ else
+ {
+ option_type= OPT_SESSION;
+ tmp1= &thd->status_var;
+ }
+
+ COND *partial_cond= make_cond_for_info_schema(cond, tables);
+ // Evaluate and cache const subqueries now, before the mutex.
+ if (partial_cond)
+ partial_cond->val_int();
+
+ if (option_type == OPT_GLOBAL)
+ {
+ /* We only hold LOCK_status for summary status vars */
+ mysql_mutex_lock(&LOCK_status);
+ calc_sum_of_all_status(&tmp);
+ mysql_mutex_unlock(&LOCK_status);
+ }
+
+ mysql_mutex_lock(&LOCK_show_status);
+ res= show_status_array(thd, wild,
+ (SHOW_VAR *)all_status_vars.buffer,
+ option_type, tmp1, "", tables->table,
+ upper_case_names, partial_cond);
+ mysql_mutex_unlock(&LOCK_show_status);
+ DBUG_RETURN(res);
+}
+
+
+/*
+ Fill and store records into I_S.referential_constraints table
+
+ SYNOPSIS
+ get_referential_constraints_record()
+ thd thread handle
+ tables table list struct(processed table)
+ table I_S table
+ res 1 means the error during opening of the processed table
+ 0 means processed table is opened without error
+ base_name db name
+ file_name table name
+
+ RETURN
+ 0 ok
+ # error
+*/
+
+static int
+get_referential_constraints_record(THD *thd, TABLE_LIST *tables,
+ TABLE *table, bool res,
+ LEX_STRING *db_name, LEX_STRING *table_name)
+{
+ CHARSET_INFO *cs= system_charset_info;
+ DBUG_ENTER("get_referential_constraints_record");
+
+ if (res)
+ {
+ if (thd->is_error())
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->message());
+ thd->clear_error();
+ DBUG_RETURN(0);
+ }
+ if (!tables->view)
+ {
+ List<FOREIGN_KEY_INFO> f_key_list;
+ TABLE *show_table= tables->table;
+ show_table->file->info(HA_STATUS_VARIABLE |
+ HA_STATUS_NO_LOCK |
+ HA_STATUS_TIME);
+
+ show_table->file->get_foreign_key_list(thd, &f_key_list);
+ FOREIGN_KEY_INFO *f_key_info;
+ List_iterator_fast<FOREIGN_KEY_INFO> it(f_key_list);
+ while ((f_key_info= it++))
+ {
+ restore_record(table, s->default_values);
+ table->field[0]->store(STRING_WITH_LEN("def"), cs);
+ table->field[1]->store(db_name->str, db_name->length, cs);
+ table->field[9]->store(table_name->str, table_name->length, cs);
+ table->field[2]->store(f_key_info->foreign_id->str,
+ f_key_info->foreign_id->length, cs);
+ table->field[3]->store(STRING_WITH_LEN("def"), cs);
+ table->field[4]->store(f_key_info->referenced_db->str,
+ f_key_info->referenced_db->length, cs);
+ table->field[10]->store(f_key_info->referenced_table->str,
+ f_key_info->referenced_table->length, cs);
+ if (f_key_info->referenced_key_name)
+ {
+ table->field[5]->store(f_key_info->referenced_key_name->str,
+ f_key_info->referenced_key_name->length, cs);
+ table->field[5]->set_notnull();
+ }
+ else
+ table->field[5]->set_null();
+ table->field[6]->store(STRING_WITH_LEN("NONE"), cs);
+ table->field[7]->store(f_key_info->update_method->str,
+ f_key_info->update_method->length, cs);
+ table->field[8]->store(f_key_info->delete_method->str,
+ f_key_info->delete_method->length, cs);
+ if (schema_table_store_record(thd, table))
+ DBUG_RETURN(1);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+struct schema_table_ref
+{
+ const char *table_name;
+ ST_SCHEMA_TABLE *schema_table;
+};
+
+ST_FIELD_INFO user_stats_fields_info[]=
+{
+ {"USER", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "User", SKIP_OPEN_TABLE},
+ {"TOTAL_CONNECTIONS", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Total_connections",SKIP_OPEN_TABLE},
+ {"CONCURRENT_CONNECTIONS", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Concurrent_connections",SKIP_OPEN_TABLE},
+ {"CONNECTED_TIME", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Connected_time",SKIP_OPEN_TABLE},
+ {"BUSY_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Busy_time",SKIP_OPEN_TABLE},
+ {"CPU_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Cpu_time",SKIP_OPEN_TABLE},
+ {"BYTES_RECEIVED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_received",SKIP_OPEN_TABLE},
+ {"BYTES_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_sent",SKIP_OPEN_TABLE},
+ {"BINLOG_BYTES_WRITTEN", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Binlog_bytes_written",SKIP_OPEN_TABLE},
+ {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE},
+ {"ROWS_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_sent",SKIP_OPEN_TABLE},
+ {"ROWS_DELETED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_deleted",SKIP_OPEN_TABLE},
+ {"ROWS_INSERTED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_inserted",SKIP_OPEN_TABLE},
+ {"ROWS_UPDATED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_updated",SKIP_OPEN_TABLE},
+ {"SELECT_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Select_commands",SKIP_OPEN_TABLE},
+ {"UPDATE_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Update_commands",SKIP_OPEN_TABLE},
+ {"OTHER_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Other_commands",SKIP_OPEN_TABLE},
+ {"COMMIT_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Commit_transactions",SKIP_OPEN_TABLE},
+ {"ROLLBACK_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rollback_transactions",SKIP_OPEN_TABLE},
+ {"DENIED_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Denied_connections",SKIP_OPEN_TABLE},
+ {"LOST_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Lost_connections",SKIP_OPEN_TABLE},
+ {"ACCESS_DENIED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Access_denied",SKIP_OPEN_TABLE},
+ {"EMPTY_QUERIES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Empty_queries",SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}
+};
+
+ST_FIELD_INFO client_stats_fields_info[]=
+{
+ {"CLIENT", LIST_PROCESS_HOST_LEN, MYSQL_TYPE_STRING, 0, 0, "Client",SKIP_OPEN_TABLE},
+ {"TOTAL_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Total_connections",SKIP_OPEN_TABLE},
+ {"CONCURRENT_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Concurrent_connections",SKIP_OPEN_TABLE},
+ {"CONNECTED_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Connected_time",SKIP_OPEN_TABLE},
+ {"BUSY_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Busy_time",SKIP_OPEN_TABLE},
+ {"CPU_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Cpu_time",SKIP_OPEN_TABLE},
+ {"BYTES_RECEIVED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_received",SKIP_OPEN_TABLE},
+ {"BYTES_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_sent",SKIP_OPEN_TABLE},
+ {"BINLOG_BYTES_WRITTEN", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Binlog_bytes_written",SKIP_OPEN_TABLE},
+ {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE},
+ {"ROWS_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_sent",SKIP_OPEN_TABLE},
+ {"ROWS_DELETED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_deleted",SKIP_OPEN_TABLE},
+ {"ROWS_INSERTED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_inserted",SKIP_OPEN_TABLE},
+ {"ROWS_UPDATED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_updated",SKIP_OPEN_TABLE},
+ {"SELECT_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Select_commands",SKIP_OPEN_TABLE},
+ {"UPDATE_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Update_commands",SKIP_OPEN_TABLE},
+ {"OTHER_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Other_commands",SKIP_OPEN_TABLE},
+ {"COMMIT_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Commit_transactions",SKIP_OPEN_TABLE},
+ {"ROLLBACK_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rollback_transactions",SKIP_OPEN_TABLE},
+ {"DENIED_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Denied_connections",SKIP_OPEN_TABLE},
+ {"LOST_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Lost_connections",SKIP_OPEN_TABLE},
+ {"ACCESS_DENIED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Access_denied",SKIP_OPEN_TABLE},
+ {"EMPTY_QUERIES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Empty_queries",SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}
+};
+
+
+ST_FIELD_INFO table_stats_fields_info[]=
+{
+ {"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema",SKIP_OPEN_TABLE},
+ {"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name",SKIP_OPEN_TABLE},
+ {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE},
+ {"ROWS_CHANGED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_changed",SKIP_OPEN_TABLE},
+ {"ROWS_CHANGED_X_INDEXES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_changed_x_#indexes",SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}
+};
+
+ST_FIELD_INFO index_stats_fields_info[]=
+{
+ {"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema",SKIP_OPEN_TABLE},
+ {"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name",SKIP_OPEN_TABLE},
+ {"INDEX_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Index_name",SKIP_OPEN_TABLE},
+ {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0,0}
+};
+
+/*
+ Find schema_tables elment by name
+
+ SYNOPSIS
+ find_schema_table_in_plugin()
+ thd thread handler
+ plugin plugin
+ table_name table name
+
+ RETURN
+ 0 table not found
+ 1 found the schema table
+*/
+static my_bool find_schema_table_in_plugin(THD *thd, plugin_ref plugin,
+ void* p_table)
+{
+ schema_table_ref *p_schema_table= (schema_table_ref *)p_table;
+ const char* table_name= p_schema_table->table_name;
+ ST_SCHEMA_TABLE *schema_table= plugin_data(plugin, ST_SCHEMA_TABLE *);
+ DBUG_ENTER("find_schema_table_in_plugin");
+
+ if (!my_strcasecmp(system_charset_info,
+ schema_table->table_name,
+ table_name)) {
+ my_plugin_lock(thd, plugin);
+ p_schema_table->schema_table= schema_table;
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Find schema_tables elment by name
+
+ SYNOPSIS
+ find_schema_table()
+ thd thread handler
+ table_name table name
+
+ RETURN
+ 0 table not found
+ # pointer to 'schema_tables' element
+*/
+
+ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name)
+{
+ schema_table_ref schema_table_a;
+ ST_SCHEMA_TABLE *schema_table= schema_tables;
+ DBUG_ENTER("find_schema_table");
+
+ for (; schema_table->table_name; schema_table++)
+ {
+ if (!my_strcasecmp(system_charset_info,
+ schema_table->table_name,
+ table_name))
+ DBUG_RETURN(schema_table);
+ }
+
+ schema_table_a.table_name= table_name;
+ if (plugin_foreach(thd, find_schema_table_in_plugin,
+ MYSQL_INFORMATION_SCHEMA_PLUGIN, &schema_table_a))
+ DBUG_RETURN(schema_table_a.schema_table);
+
+ DBUG_RETURN(NULL);
+}
+
+
+ST_SCHEMA_TABLE *get_schema_table(enum enum_schema_tables schema_table_idx)
+{
+ return &schema_tables[schema_table_idx];
+}
+
+
+/**
+ Create information_schema table using schema_table data.
+
+ @note
+ For MYSQL_TYPE_DECIMAL fields only, the field_length member has encoded
+ into it two numbers, based on modulus of base-10 numbers. In the ones
+ position is the number of decimals. Tens position is unused. In the
+ hundreds and thousands position is a two-digit decimal number representing
+ length. Encode this value with (length*100)+decimals , where
+ 0<decimals<10 and 0<=length<100 .
+
+ @param
+ thd thread handler
+
+ @param table_list Used to pass I_S table information(fields info, tables
+ parameters etc) and table name.
+
+ @retval \# Pointer to created table
+ @retval NULL Can't create table
+*/
+
+TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list)
+{
+ int field_count= 0;
+ Item *item;
+ TABLE *table;
+ List<Item> field_list;
+ ST_SCHEMA_TABLE *schema_table= table_list->schema_table;
+ ST_FIELD_INFO *fields_info= schema_table->fields_info;
+ CHARSET_INFO *cs= system_charset_info;
+ DBUG_ENTER("create_schema_table");
+
+ for (; fields_info->field_name; fields_info++)
+ {
+ switch (fields_info->field_type) {
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_INT24:
+ if (!(item= new Item_return_int(fields_info->field_name,
+ fields_info->field_length,
+ fields_info->field_type,
+ fields_info->value)))
+ {
+ DBUG_RETURN(0);
+ }
+ item->unsigned_flag= (fields_info->field_flags & MY_I_S_UNSIGNED);
+ break;
+ case MYSQL_TYPE_DATE:
+ if (!(item=new Item_return_date_time(fields_info->field_name,
+ strlen(fields_info->field_name),
+ fields_info->field_type)))
+ DBUG_RETURN(0);
+ break;
+ case MYSQL_TYPE_TIME:
+ if (!(item=new Item_return_date_time(fields_info->field_name,
+ strlen(fields_info->field_name),
+ fields_info->field_type)))
+ DBUG_RETURN(0);
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_DATETIME:
+ if (!(item=new Item_return_date_time(fields_info->field_name,
+ strlen(fields_info->field_name),
+ fields_info->field_type)))
+ DBUG_RETURN(0);
+ break;
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ if ((item= new Item_float(fields_info->field_name, 0.0, NOT_FIXED_DEC,
+ fields_info->field_length)) == NULL)
+ DBUG_RETURN(NULL);
+ break;
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ if (!(item= new Item_decimal((longlong) fields_info->value, false)))
+ {
+ DBUG_RETURN(0);
+ }
+ /*
+ Create a type holder, as we want the type of the item to defined
+ the type of the object, not the value
+ */
+ if (!(item= new Item_type_holder(thd, item)))
+ DBUG_RETURN(0);
+ item->unsigned_flag= (fields_info->field_flags & MY_I_S_UNSIGNED);
+ item->decimals= fields_info->field_length%10;
+ item->max_length= (fields_info->field_length/100)%100;
+ if (item->unsigned_flag == 0)
+ item->max_length+= 1;
+ if (item->decimals > 0)
+ item->max_length+= 1;
+ item->set_name(fields_info->field_name,
+ strlen(fields_info->field_name), cs);
+ break;
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ if (!(item= new Item_blob(fields_info->field_name,
+ fields_info->field_length)))
+ {
+ DBUG_RETURN(0);
+ }
+ break;
+ default:
+ /* Don't let unimplemented types pass through. Could be a grave error. */
+ DBUG_ASSERT(fields_info->field_type == MYSQL_TYPE_STRING);
+
+ if (!(item= new Item_empty_string("", fields_info->field_length, cs)))
+ {
+ DBUG_RETURN(0);
+ }
+ item->set_name(fields_info->field_name,
+ strlen(fields_info->field_name), cs);
+ break;
+ }
+ field_list.push_back(item);
+ item->maybe_null= (fields_info->field_flags & MY_I_S_MAYBE_NULL);
+ field_count++;
+ }
+ TMP_TABLE_PARAM *tmp_table_param =
+ (TMP_TABLE_PARAM*) (thd->alloc(sizeof(TMP_TABLE_PARAM)));
+ tmp_table_param->init();
+ tmp_table_param->table_charset= cs;
+ tmp_table_param->field_count= field_count;
+ tmp_table_param->schema_table= 1;
+ SELECT_LEX *select_lex= thd->lex->current_select;
+ if (!(table= create_tmp_table(thd, tmp_table_param,
+ field_list, (ORDER*) 0, 0, 0,
+ (select_lex->options | thd->variables.option_bits |
+ TMP_TABLE_ALL_COLUMNS),
+ HA_POS_ERROR, table_list->alias)))
+ DBUG_RETURN(0);
+ my_bitmap_map* bitmaps=
+ (my_bitmap_map*) thd->alloc(bitmap_buffer_size(field_count));
+ my_bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count,
+ FALSE);
+ table->read_set= &table->def_read_set;
+ bitmap_clear_all(table->read_set);
+ table_list->schema_table_param= tmp_table_param;
+ DBUG_RETURN(table);
+}
+
+
+/*
+ For old SHOW compatibility. It is used when
+ old SHOW doesn't have generated column names
+ Make list of fields for SHOW
+
+ SYNOPSIS
+ make_old_format()
+ thd thread handler
+ schema_table pointer to 'schema_tables' element
+
+ RETURN
+ 1 error
+ 0 success
+*/
+
+int make_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table)
+{
+ ST_FIELD_INFO *field_info= schema_table->fields_info;
+ Name_resolution_context *context= &thd->lex->select_lex.context;
+ for (; field_info->field_name; field_info++)
+ {
+ if (field_info->old_name)
+ {
+ Item_field *field= new Item_field(context,
+ NullS, NullS, field_info->field_name);
+ if (field)
+ {
+ field->set_name(field_info->old_name,
+ strlen(field_info->old_name),
+ system_charset_info);
+ if (add_item_to_list(thd, field))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+int make_schemata_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table)
+{
+ char tmp[128];
+ LEX *lex= thd->lex;
+ SELECT_LEX *sel= lex->current_select;
+ Name_resolution_context *context= &sel->context;
+
+ if (!sel->item_list.elements)
+ {
+ ST_FIELD_INFO *field_info= &schema_table->fields_info[1];
+ String buffer(tmp,sizeof(tmp), system_charset_info);
+ Item_field *field= new Item_field(context,
+ NullS, NullS, field_info->field_name);
+ if (!field || add_item_to_list(thd, field))
+ return 1;
+ buffer.length(0);
+ buffer.append(field_info->old_name);
+ if (lex->wild && lex->wild->ptr())
+ {
+ buffer.append(STRING_WITH_LEN(" ("));
+ buffer.append(lex->wild->ptr());
+ buffer.append(')');
+ }
+ field->set_name(buffer.ptr(), buffer.length(), system_charset_info);
+ }
+ return 0;
+}
+
+
+int make_table_names_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table)
+{
+ char tmp[128];
+ String buffer(tmp,sizeof(tmp), thd->charset());
+ LEX *lex= thd->lex;
+ Name_resolution_context *context= &lex->select_lex.context;
+
+ ST_FIELD_INFO *field_info= &schema_table->fields_info[2];
+ buffer.length(0);
+ buffer.append(field_info->old_name);
+ buffer.append(lex->select_lex.db);
+ if (lex->wild && lex->wild->ptr())
+ {
+ buffer.append(STRING_WITH_LEN(" ("));
+ buffer.append(lex->wild->ptr());
+ buffer.append(')');
+ }
+ Item_field *field= new Item_field(context,
+ NullS, NullS, field_info->field_name);
+ if (add_item_to_list(thd, field))
+ return 1;
+ field->set_name(buffer.ptr(), buffer.length(), system_charset_info);
+ if (thd->lex->verbose)
+ {
+ field->set_name(buffer.ptr(), buffer.length(), system_charset_info);
+ field_info= &schema_table->fields_info[3];
+ field= new Item_field(context, NullS, NullS, field_info->field_name);
+ if (add_item_to_list(thd, field))
+ return 1;
+ field->set_name(field_info->old_name, strlen(field_info->old_name),
+ system_charset_info);
+ }
+ return 0;
+}
+
+
+int make_columns_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table)
+{
+ int fields_arr[]= {3, 15, 14, 6, 16, 5, 17, 18, 19, -1};
+ int *field_num= fields_arr;
+ ST_FIELD_INFO *field_info;
+ Name_resolution_context *context= &thd->lex->select_lex.context;
+
+ for (; *field_num >= 0; field_num++)
+ {
+ field_info= &schema_table->fields_info[*field_num];
+ if (!thd->lex->verbose && (*field_num == 14 ||
+ *field_num == 18 ||
+ *field_num == 19))
+ continue;
+ Item_field *field= new Item_field(context,
+ NullS, NullS, field_info->field_name);
+ if (field)
+ {
+ field->set_name(field_info->old_name,
+ strlen(field_info->old_name),
+ system_charset_info);
+ if (add_item_to_list(thd, field))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int make_character_sets_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table)
+{
+ int fields_arr[]= {0, 2, 1, 3, -1};
+ int *field_num= fields_arr;
+ ST_FIELD_INFO *field_info;
+ Name_resolution_context *context= &thd->lex->select_lex.context;
+
+ for (; *field_num >= 0; field_num++)
+ {
+ field_info= &schema_table->fields_info[*field_num];
+ Item_field *field= new Item_field(context,
+ NullS, NullS, field_info->field_name);
+ if (field)
+ {
+ field->set_name(field_info->old_name,
+ strlen(field_info->old_name),
+ system_charset_info);
+ if (add_item_to_list(thd, field))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int make_proc_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table)
+{
+ int fields_arr[]= {2, 3, 4, 27, 24, 23, 22, 26, 28, 29, 30, -1};
+ int *field_num= fields_arr;
+ ST_FIELD_INFO *field_info;
+ Name_resolution_context *context= &thd->lex->select_lex.context;
+
+ for (; *field_num >= 0; field_num++)
+ {
+ field_info= &schema_table->fields_info[*field_num];
+ Item_field *field= new Item_field(context,
+ NullS, NullS, field_info->field_name);
+ if (field)
+ {
+ field->set_name(field_info->old_name,
+ strlen(field_info->old_name),
+ system_charset_info);
+ if (add_item_to_list(thd, field))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ Create information_schema table
+
+ SYNOPSIS
+ mysql_schema_table()
+ thd thread handler
+ lex pointer to LEX
+ table_list pointer to table_list
+
+ RETURN
+ 0 success
+ 1 error
+*/
+
+int mysql_schema_table(THD *thd, LEX *lex, TABLE_LIST *table_list)
+{
+ TABLE *table;
+ DBUG_ENTER("mysql_schema_table");
+ if (!(table= table_list->schema_table->create_table(thd, table_list)))
+ DBUG_RETURN(1);
+ table->s->tmp_table= SYSTEM_TMP_TABLE;
+ table->grant.privilege= SELECT_ACL;
+ /*
+ This test is necessary to make
+ case insensitive file systems +
+ upper case table names(information schema tables) +
+ views
+ working correctly
+ */
+ if (table_list->schema_table_name)
+ table->alias_name_used= my_strcasecmp(table_alias_charset,
+ table_list->schema_table_name,
+ table_list->alias);
+ table_list->table_name= table->s->table_name.str;
+ table_list->table_name_length= table->s->table_name.length;
+ table_list->table= table;
+ table->next= thd->derived_tables;
+ thd->derived_tables= table;
+ table_list->select_lex->options |= OPTION_SCHEMA_TABLE;
+ lex->safe_to_cache_query= 0;
+
+ if (table_list->schema_table_reformed) // show command
+ {
+ SELECT_LEX *sel= lex->current_select;
+ Item *item;
+ Field_translator *transl, *org_transl;
+
+ if (table_list->field_translation)
+ {
+ Field_translator *end= table_list->field_translation_end;
+ for (transl= table_list->field_translation; transl < end; transl++)
+ {
+ if (!transl->item->fixed &&
+ transl->item->fix_fields(thd, &transl->item))
+ DBUG_RETURN(1);
+ }
+ DBUG_RETURN(0);
+ }
+ List_iterator_fast<Item> it(sel->item_list);
+ if (!(transl=
+ (Field_translator*)(thd->stmt_arena->
+ alloc(sel->item_list.elements *
+ sizeof(Field_translator)))))
+ {
+ DBUG_RETURN(1);
+ }
+ for (org_transl= transl; (item= it++); transl++)
+ {
+ transl->item= item;
+ transl->name= item->name;
+ if (!item->fixed && item->fix_fields(thd, &transl->item))
+ {
+ DBUG_RETURN(1);
+ }
+ }
+ table_list->field_translation= org_transl;
+ table_list->field_translation_end= transl;
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Generate select from information_schema table
+
+ SYNOPSIS
+ make_schema_select()
+ thd thread handler
+ sel pointer to SELECT_LEX
+ schema_table_idx index of 'schema_tables' element
+
+ RETURN
+ 0 success
+ 1 error
+*/
+
+int make_schema_select(THD *thd, SELECT_LEX *sel,
+ enum enum_schema_tables schema_table_idx)
+{
+ ST_SCHEMA_TABLE *schema_table= get_schema_table(schema_table_idx);
+ LEX_STRING db, table;
+ DBUG_ENTER("make_schema_select");
+ DBUG_PRINT("enter", ("mysql_schema_select: %s", schema_table->table_name));
+ /*
+ We have to make non const db_name & table_name
+ because of lower_case_table_names
+ */
+ if (!thd->make_lex_string(&db, INFORMATION_SCHEMA_NAME.str,
+ INFORMATION_SCHEMA_NAME.length))
+ DBUG_RETURN(1);
+
+ if (!thd->make_lex_string(&table, schema_table->table_name,
+ strlen(schema_table->table_name)))
+ DBUG_RETURN(1);
+
+ if (schema_table->old_format(thd, schema_table))
+
+ DBUG_RETURN(1);
+
+ if (!sel->add_table_to_list(thd, new Table_ident(thd, db, table, 0),
+ 0, 0, TL_READ, MDL_SHARED_READ))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Fill temporary schema tables before SELECT
+
+ SYNOPSIS
+ get_schema_tables_result()
+ join join which use schema tables
+ executed_place place where I_S table processed
+
+ RETURN
+ FALSE success
+ TRUE error
+*/
+
+bool get_schema_tables_result(JOIN *join,
+ enum enum_schema_table_state executed_place)
+{
+ THD *thd= join->thd;
+ LEX *lex= thd->lex;
+ bool result= 0;
+ PSI_stage_info org_stage;
+ DBUG_ENTER("get_schema_tables_result");
+
+ Warnings_only_error_handler err_handler;
+ thd->push_internal_handler(&err_handler);
+ thd->enter_stage(&stage_filling_schema_table, &org_stage, __func__, __FILE__,
+ __LINE__);
+
+ JOIN_TAB *tab;
+ for (tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS, WITH_CONST_TABLES);
+ tab;
+ tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
+ {
+ if (!tab->table || !tab->table->pos_in_table_list)
+ break;
+
+ TABLE_LIST *table_list= tab->table->pos_in_table_list;
+ if (table_list->schema_table && thd->fill_information_schema_tables())
+ {
+ bool is_subselect= (&lex->unit != lex->current_select->master_unit() &&
+ lex->current_select->master_unit()->item);
+
+ /* A value of 0 indicates a dummy implementation */
+ if (table_list->schema_table->fill_table == 0)
+ continue;
+
+ /* skip I_S optimizations specific to get_all_tables */
+ if (thd->lex->describe &&
+ (table_list->schema_table->fill_table != get_all_tables))
+ continue;
+
+ /*
+ If schema table is already processed and
+ the statement is not a subselect then
+ we don't need to fill this table again.
+ If schema table is already processed and
+ schema_table_state != executed_place then
+ table is already processed and
+ we should skip second data processing.
+ */
+ if (table_list->schema_table_state &&
+ (!is_subselect || table_list->schema_table_state != executed_place))
+ continue;
+
+ /*
+ if table is used in a subselect and
+ table has been processed earlier with the same
+ 'executed_place' value then we should refresh the table.
+ */
+ if (table_list->schema_table_state && is_subselect)
+ {
+ table_list->table->file->extra(HA_EXTRA_NO_CACHE);
+ table_list->table->file->extra(HA_EXTRA_RESET_STATE);
+ table_list->table->file->ha_delete_all_rows();
+ free_io_cache(table_list->table);
+ filesort_free_buffers(table_list->table,1);
+ table_list->table->null_row= 0;
+ }
+ else
+ table_list->table->file->stats.records= 0;
+
+
+ Item *cond= tab->select_cond;
+ if (tab->cache_select && tab->cache_select->cond)
+ {
+ /*
+ If join buffering is used, we should use the condition that is
+ attached to the join cache. Cache condition has a part of WHERE that
+ can be checked when we're populating this table.
+ join_tab->select_cond is of no interest, because it only has
+ conditions that depend on both this table and previous tables in the
+ join order.
+ */
+ cond= tab->cache_select->cond;
+ }
+
+ if (table_list->schema_table->fill_table(thd, table_list, cond))
+ {
+ result= 1;
+ join->error= 1;
+ tab->read_record.table->file= table_list->table->file;
+ table_list->schema_table_state= executed_place;
+ break;
+ }
+ tab->read_record.table->file= table_list->table->file;
+ table_list->schema_table_state= executed_place;
+ }
+ }
+ thd->pop_internal_handler();
+ if (thd->is_error())
+ {
+ /*
+ This hack is here, because I_S code uses thd->clear_error() a lot.
+ Which means, a Warnings_only_error_handler cannot handle the error
+ corectly as it does not know whether an error is real (e.g. caused
+ by tab->select_cond->val_int()) or will be cleared later.
+ Thus it ignores all errors, and the real one (that is, the error
+ that was not cleared) is pushed now.
+
+ It also means that an audit plugin cannot process the error correctly
+ either. See also thd->clear_error()
+ */
+ thd->get_stmt_da()->push_warning(thd,
+ thd->get_stmt_da()->sql_errno(),
+ thd->get_stmt_da()->get_sqlstate(),
+ Sql_condition::WARN_LEVEL_ERROR,
+ thd->get_stmt_da()->message());
+ }
+ else if (result)
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ THD_STAGE_INFO(thd, org_stage);
+ DBUG_RETURN(result);
+}
+
+struct run_hton_fill_schema_table_args
+{
+ TABLE_LIST *tables;
+ COND *cond;
+};
+
+static my_bool run_hton_fill_schema_table(THD *thd, plugin_ref plugin,
+ void *arg)
+{
+ struct run_hton_fill_schema_table_args *args=
+ (run_hton_fill_schema_table_args *) arg;
+ handlerton *hton= plugin_hton(plugin);
+ if (hton->fill_is_table && hton->state == SHOW_OPTION_YES)
+ hton->fill_is_table(hton, thd, args->tables, args->cond,
+ get_schema_table_idx(args->tables->schema_table));
+ return false;
+}
+
+int hton_fill_schema_table(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("hton_fill_schema_table");
+
+ struct run_hton_fill_schema_table_args args;
+ args.tables= tables;
+ args.cond= cond;
+
+ plugin_foreach(thd, run_hton_fill_schema_table,
+ MYSQL_STORAGE_ENGINE_PLUGIN, &args);
+
+ DBUG_RETURN(0);
+}
+
+
+static
+int store_key_cache_table_record(THD *thd, TABLE *table,
+ const char *name, uint name_length,
+ KEY_CACHE *key_cache,
+ uint partitions, uint partition_no)
+{
+ KEY_CACHE_STATISTICS keycache_stats;
+ uint err;
+ DBUG_ENTER("store_key_cache_table_record");
+
+ get_key_cache_statistics(key_cache, partition_no, &keycache_stats);
+
+ if (!key_cache->key_cache_inited || keycache_stats.mem_size == 0)
+ DBUG_RETURN(0);
+
+ restore_record(table, s->default_values);
+ table->field[0]->store(name, name_length, system_charset_info);
+ if (partitions == 0)
+ table->field[1]->set_null();
+ else
+ {
+ table->field[1]->set_notnull();
+ table->field[1]->store((long) partitions, TRUE);
+ }
+
+ if (partition_no == 0)
+ table->field[2]->set_null();
+ else
+ {
+ table->field[2]->set_notnull();
+ table->field[2]->store((long) partition_no, TRUE);
+ }
+ table->field[3]->store(keycache_stats.mem_size, TRUE);
+ table->field[4]->store(keycache_stats.block_size, TRUE);
+ table->field[5]->store(keycache_stats.blocks_used, TRUE);
+ table->field[6]->store(keycache_stats.blocks_unused, TRUE);
+ table->field[7]->store(keycache_stats.blocks_changed, TRUE);
+ table->field[8]->store(keycache_stats.read_requests, TRUE);
+ table->field[9]->store(keycache_stats.reads, TRUE);
+ table->field[10]->store(keycache_stats.write_requests, TRUE);
+ table->field[11]->store(keycache_stats.writes, TRUE);
+
+ err= schema_table_store_record(thd, table);
+ DBUG_RETURN(err);
+}
+
+int run_fill_key_cache_tables(const char *name, KEY_CACHE *key_cache, void *p)
+{
+ DBUG_ENTER("run_fill_key_cache_tables");
+
+ if (!key_cache->key_cache_inited)
+ DBUG_RETURN(0);
+
+ TABLE *table= (TABLE *)p;
+ THD *thd= table->in_use;
+ uint partitions= key_cache->partitions;
+ size_t namelen= strlen(name);
+ DBUG_ASSERT(partitions <= MAX_KEY_CACHE_PARTITIONS);
+
+ if (partitions)
+ {
+ for (uint i= 0; i < partitions; i++)
+ {
+ if (store_key_cache_table_record(thd, table, name, namelen,
+ key_cache, partitions, i+1))
+ DBUG_RETURN(1);
+ }
+ }
+
+ if (store_key_cache_table_record(thd, table, name, namelen,
+ key_cache, partitions, 0))
+ DBUG_RETURN(1);
+ DBUG_RETURN(0);
+}
+
+int fill_key_cache_tables(THD *thd, TABLE_LIST *tables, COND *cond)
+{
+ DBUG_ENTER("fill_key_cache_tables");
+
+ int res= process_key_caches(run_fill_key_cache_tables, tables->table);
+
+ DBUG_RETURN(res);
+}
+
+
+ST_FIELD_INFO schema_fields_info[]=
+{
+ {"CATALOG_NAME", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"SCHEMA_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Database",
+ SKIP_OPEN_TABLE},
+ {"DEFAULT_CHARACTER_SET_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0,
+ SKIP_OPEN_TABLE},
+ {"DEFAULT_COLLATION_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0,
+ SKIP_OPEN_TABLE},
+ {"SQL_PATH", FN_REFLEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO tables_fields_info[]=
+{
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name",
+ SKIP_OPEN_TABLE},
+ {"TABLE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"ENGINE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, "Engine", OPEN_FRM_ONLY},
+ {"VERSION", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Version", OPEN_FRM_ONLY},
+ {"ROW_FORMAT", 10, MYSQL_TYPE_STRING, 0, 1, "Row_format", OPEN_FULL_TABLE},
+ {"TABLE_ROWS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Rows", OPEN_FULL_TABLE},
+ {"AVG_ROW_LENGTH", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Avg_row_length", OPEN_FULL_TABLE},
+ {"DATA_LENGTH", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Data_length", OPEN_FULL_TABLE},
+ {"MAX_DATA_LENGTH", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Max_data_length", OPEN_FULL_TABLE},
+ {"INDEX_LENGTH", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Index_length", OPEN_FULL_TABLE},
+ {"DATA_FREE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Data_free", OPEN_FULL_TABLE},
+ {"AUTO_INCREMENT", MY_INT64_NUM_DECIMAL_DIGITS , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Auto_increment", OPEN_FULL_TABLE},
+ {"CREATE_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, "Create_time", OPEN_FULL_TABLE},
+ {"UPDATE_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, "Update_time", OPEN_FULL_TABLE},
+ {"CHECK_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, "Check_time", OPEN_FULL_TABLE},
+ {"TABLE_COLLATION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 1, "Collation",
+ OPEN_FRM_ONLY},
+ {"CHECKSUM", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Checksum", OPEN_FULL_TABLE},
+ {"CREATE_OPTIONS", 255, MYSQL_TYPE_STRING, 0, 1, "Create_options",
+ OPEN_FRM_ONLY},
+ {"TABLE_COMMENT", TABLE_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0,
+ "Comment", OPEN_FRM_ONLY},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO columns_fields_info[]=
+{
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"COLUMN_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Field",
+ OPEN_FRM_ONLY},
+ {"ORDINAL_POSITION", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ MY_I_S_UNSIGNED, 0, OPEN_FRM_ONLY},
+ {"COLUMN_DEFAULT", MAX_FIELD_VARCHARLENGTH, MYSQL_TYPE_STRING, 0,
+ 1, "Default", OPEN_FRM_ONLY},
+ {"IS_NULLABLE", 3, MYSQL_TYPE_STRING, 0, 0, "Null", OPEN_FRM_ONLY},
+ {"DATA_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"CHARACTER_MAXIMUM_LENGTH", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
+ 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FRM_ONLY},
+ {"CHARACTER_OCTET_LENGTH", MY_INT64_NUM_DECIMAL_DIGITS , MYSQL_TYPE_LONGLONG,
+ 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FRM_ONLY},
+ {"NUMERIC_PRECISION", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
+ 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FRM_ONLY},
+ {"NUMERIC_SCALE", MY_INT64_NUM_DECIMAL_DIGITS , MYSQL_TYPE_LONGLONG,
+ 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FRM_ONLY},
+ {"DATETIME_PRECISION", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
+ 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FRM_ONLY},
+ {"CHARACTER_SET_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FRM_ONLY},
+ {"COLLATION_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 1, "Collation",
+ OPEN_FRM_ONLY},
+ {"COLUMN_TYPE", 65535, MYSQL_TYPE_STRING, 0, 0, "Type", OPEN_FRM_ONLY},
+ {"COLUMN_KEY", 3, MYSQL_TYPE_STRING, 0, 0, "Key", OPEN_FRM_ONLY},
+ {"EXTRA", 27, MYSQL_TYPE_STRING, 0, 0, "Extra", OPEN_FRM_ONLY},
+ {"PRIVILEGES", 80, MYSQL_TYPE_STRING, 0, 0, "Privileges", OPEN_FRM_ONLY},
+ {"COLUMN_COMMENT", COLUMN_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0,
+ "Comment", OPEN_FRM_ONLY},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO charsets_fields_info[]=
+{
+ {"CHARACTER_SET_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, "Charset",
+ SKIP_OPEN_TABLE},
+ {"DEFAULT_COLLATE_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "Default collation", SKIP_OPEN_TABLE},
+ {"DESCRIPTION", 60, MYSQL_TYPE_STRING, 0, 0, "Description",
+ SKIP_OPEN_TABLE},
+ {"MAXLEN", 3, MYSQL_TYPE_LONGLONG, 0, 0, "Maxlen", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO collation_fields_info[]=
+{
+ {"COLLATION_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, "Collation",
+ SKIP_OPEN_TABLE},
+ {"CHARACTER_SET_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, "Charset",
+ SKIP_OPEN_TABLE},
+ {"ID", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Id",
+ SKIP_OPEN_TABLE},
+ {"IS_DEFAULT", 3, MYSQL_TYPE_STRING, 0, 0, "Default", SKIP_OPEN_TABLE},
+ {"IS_COMPILED", 3, MYSQL_TYPE_STRING, 0, 0, "Compiled", SKIP_OPEN_TABLE},
+ {"SORTLEN", 3, MYSQL_TYPE_LONGLONG, 0, 0, "Sortlen", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO applicable_roles_fields_info[]=
+{
+ {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"ROLE_NAME", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"IS_GRANTABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO enabled_roles_fields_info[]=
+{
+ {"ROLE_NAME", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO engines_fields_info[]=
+{
+ {"ENGINE", 64, MYSQL_TYPE_STRING, 0, 0, "Engine", SKIP_OPEN_TABLE},
+ {"SUPPORT", 8, MYSQL_TYPE_STRING, 0, 0, "Support", SKIP_OPEN_TABLE},
+ {"COMMENT", 160, MYSQL_TYPE_STRING, 0, 0, "Comment", SKIP_OPEN_TABLE},
+ {"TRANSACTIONS", 3, MYSQL_TYPE_STRING, 0, 1, "Transactions", SKIP_OPEN_TABLE},
+ {"XA", 3, MYSQL_TYPE_STRING, 0, 1, "XA", SKIP_OPEN_TABLE},
+ {"SAVEPOINTS", 3 ,MYSQL_TYPE_STRING, 0, 1, "Savepoints", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO events_fields_info[]=
+{
+ {"EVENT_CATALOG", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"EVENT_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Db",
+ SKIP_OPEN_TABLE},
+ {"EVENT_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name",
+ SKIP_OPEN_TABLE},
+ {"DEFINER", DEFINER_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "Definer", SKIP_OPEN_TABLE},
+ {"TIME_ZONE", 64, MYSQL_TYPE_STRING, 0, 0, "Time zone", SKIP_OPEN_TABLE},
+ {"EVENT_BODY", 8, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"EVENT_DEFINITION", 65535, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"EVENT_TYPE", 9, MYSQL_TYPE_STRING, 0, 0, "Type", SKIP_OPEN_TABLE},
+ {"EXECUTE_AT", 0, MYSQL_TYPE_DATETIME, 0, 1, "Execute at", SKIP_OPEN_TABLE},
+ {"INTERVAL_VALUE", 256, MYSQL_TYPE_STRING, 0, 1, "Interval value",
+ SKIP_OPEN_TABLE},
+ {"INTERVAL_FIELD", 18, MYSQL_TYPE_STRING, 0, 1, "Interval field",
+ SKIP_OPEN_TABLE},
+ {"SQL_MODE", 32*256, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"STARTS", 0, MYSQL_TYPE_DATETIME, 0, 1, "Starts", SKIP_OPEN_TABLE},
+ {"ENDS", 0, MYSQL_TYPE_DATETIME, 0, 1, "Ends", SKIP_OPEN_TABLE},
+ {"STATUS", 18, MYSQL_TYPE_STRING, 0, 0, "Status", SKIP_OPEN_TABLE},
+ {"ON_COMPLETION", 12, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"CREATED", 0, MYSQL_TYPE_DATETIME, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"LAST_ALTERED", 0, MYSQL_TYPE_DATETIME, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"LAST_EXECUTED", 0, MYSQL_TYPE_DATETIME, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"EVENT_COMMENT", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"ORIGINATOR", 10, MYSQL_TYPE_LONGLONG, 0, 0, "Originator", SKIP_OPEN_TABLE},
+ {"CHARACTER_SET_CLIENT", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "character_set_client", SKIP_OPEN_TABLE},
+ {"COLLATION_CONNECTION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "collation_connection", SKIP_OPEN_TABLE},
+ {"DATABASE_COLLATION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "Database Collation", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+
+ST_FIELD_INFO coll_charset_app_fields_info[]=
+{
+ {"COLLATION_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0,
+ SKIP_OPEN_TABLE},
+ {"CHARACTER_SET_NAME", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0,
+ SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO proc_fields_info[]=
+{
+ {"SPECIFIC_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"ROUTINE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"ROUTINE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Db",
+ SKIP_OPEN_TABLE},
+ {"ROUTINE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name",
+ SKIP_OPEN_TABLE},
+ {"ROUTINE_TYPE", 9, MYSQL_TYPE_STRING, 0, 0, "Type", SKIP_OPEN_TABLE},
+ {"DATA_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"CHARACTER_MAXIMUM_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"CHARACTER_OCTET_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"NUMERIC_PRECISION", 21 , MYSQL_TYPE_LONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"NUMERIC_SCALE", 21 , MYSQL_TYPE_LONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"DATETIME_PRECISION", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
+ 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FRM_ONLY},
+ {"CHARACTER_SET_NAME", 64, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"COLLATION_NAME", 64, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"DTD_IDENTIFIER", 65535, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"ROUTINE_BODY", 8, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"ROUTINE_DEFINITION", 65535, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"EXTERNAL_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"EXTERNAL_LANGUAGE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ SKIP_OPEN_TABLE},
+ {"PARAMETER_STYLE", 8, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"IS_DETERMINISTIC", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"SQL_DATA_ACCESS", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ SKIP_OPEN_TABLE},
+ {"SQL_PATH", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"SECURITY_TYPE", 7, MYSQL_TYPE_STRING, 0, 0, "Security_type",
+ SKIP_OPEN_TABLE},
+ {"CREATED", 0, MYSQL_TYPE_DATETIME, 0, 0, "Created", SKIP_OPEN_TABLE},
+ {"LAST_ALTERED", 0, MYSQL_TYPE_DATETIME, 0, 0, "Modified", SKIP_OPEN_TABLE},
+ {"SQL_MODE", 32*256, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"ROUTINE_COMMENT", 65535, MYSQL_TYPE_STRING, 0, 0, "Comment",
+ SKIP_OPEN_TABLE},
+ {"DEFINER", DEFINER_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "Definer", SKIP_OPEN_TABLE},
+ {"CHARACTER_SET_CLIENT", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "character_set_client", SKIP_OPEN_TABLE},
+ {"COLLATION_CONNECTION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "collation_connection", SKIP_OPEN_TABLE},
+ {"DATABASE_COLLATION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "Database Collation", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO stat_fields_info[]=
+{
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Table", OPEN_FRM_ONLY},
+ {"NON_UNIQUE", 1, MYSQL_TYPE_LONGLONG, 0, 0, "Non_unique", OPEN_FRM_ONLY},
+ {"INDEX_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"INDEX_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Key_name",
+ OPEN_FRM_ONLY},
+ {"SEQ_IN_INDEX", 2, MYSQL_TYPE_LONGLONG, 0, 0, "Seq_in_index", OPEN_FRM_ONLY},
+ {"COLUMN_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Column_name",
+ OPEN_FRM_ONLY},
+ {"COLLATION", 1, MYSQL_TYPE_STRING, 0, 1, "Collation", OPEN_FRM_ONLY},
+ {"CARDINALITY", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 1,
+ "Cardinality", OPEN_FULL_TABLE},
+ {"SUB_PART", 3, MYSQL_TYPE_LONGLONG, 0, 1, "Sub_part", OPEN_FRM_ONLY},
+ {"PACKED", 10, MYSQL_TYPE_STRING, 0, 1, "Packed", OPEN_FRM_ONLY},
+ {"NULLABLE", 3, MYSQL_TYPE_STRING, 0, 0, "Null", OPEN_FRM_ONLY},
+ {"INDEX_TYPE", 16, MYSQL_TYPE_STRING, 0, 0, "Index_type", OPEN_FULL_TABLE},
+ {"COMMENT", 16, MYSQL_TYPE_STRING, 0, 1, "Comment", OPEN_FRM_ONLY},
+ {"INDEX_COMMENT", INDEX_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0,
+ "Index_comment", OPEN_FRM_ONLY},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO view_fields_info[]=
+{
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"VIEW_DEFINITION", 65535, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"CHECK_OPTION", 8, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"IS_UPDATABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"DEFINER", DEFINER_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"SECURITY_TYPE", 7, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"CHARACTER_SET_CLIENT", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FRM_ONLY},
+ {"COLLATION_CONNECTION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FRM_ONLY},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO user_privileges_fields_info[]=
+{
+ {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PRIVILEGE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"IS_GRANTABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO schema_privileges_fields_info[]=
+{
+ {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PRIVILEGE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"IS_GRANTABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO table_privileges_fields_info[]=
+{
+ {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PRIVILEGE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"IS_GRANTABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO column_privileges_fields_info[]=
+{
+ {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"COLUMN_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PRIVILEGE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"IS_GRANTABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO table_constraints_fields_info[]=
+{
+ {"CONSTRAINT_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"CONSTRAINT_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"CONSTRAINT_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"CONSTRAINT_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO key_column_usage_fields_info[]=
+{
+ {"CONSTRAINT_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"CONSTRAINT_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"CONSTRAINT_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"COLUMN_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"ORDINAL_POSITION", 10 ,MYSQL_TYPE_LONGLONG, 0, 0, 0, OPEN_FULL_TABLE},
+ {"POSITION_IN_UNIQUE_CONSTRAINT", 10 ,MYSQL_TYPE_LONGLONG, 0, 1, 0,
+ OPEN_FULL_TABLE},
+ {"REFERENCED_TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FULL_TABLE},
+ {"REFERENCED_TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FULL_TABLE},
+ {"REFERENCED_COLUMN_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FULL_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO table_names_fields_info[]=
+{
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_SCHEMA",NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN + MYSQL50_TABLE_NAME_PREFIX_LENGTH,
+ MYSQL_TYPE_STRING, 0, 0, "Tables_in_", SKIP_OPEN_TABLE},
+ {"TABLE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_type",
+ OPEN_FRM_ONLY},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO open_tables_fields_info[]=
+{
+ {"Database", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Database",
+ SKIP_OPEN_TABLE},
+ {"Table",NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Table", SKIP_OPEN_TABLE},
+ {"In_use", 1, MYSQL_TYPE_LONGLONG, 0, 0, "In_use", SKIP_OPEN_TABLE},
+ {"Name_locked", 4, MYSQL_TYPE_LONGLONG, 0, 0, "Name_locked", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO triggers_fields_info[]=
+{
+ {"TRIGGER_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TRIGGER_SCHEMA",NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"TRIGGER_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Trigger",
+ OPEN_FRM_ONLY},
+ {"EVENT_MANIPULATION", 6, MYSQL_TYPE_STRING, 0, 0, "Event", OPEN_FRM_ONLY},
+ {"EVENT_OBJECT_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FRM_ONLY},
+ {"EVENT_OBJECT_SCHEMA",NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FRM_ONLY},
+ {"EVENT_OBJECT_TABLE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Table",
+ OPEN_FRM_ONLY},
+ {"ACTION_ORDER", 4, MYSQL_TYPE_LONGLONG, 0, 0, 0, OPEN_FRM_ONLY},
+ {"ACTION_CONDITION", 65535, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FRM_ONLY},
+ {"ACTION_STATEMENT", 65535, MYSQL_TYPE_STRING, 0, 0, "Statement",
+ OPEN_FRM_ONLY},
+ {"ACTION_ORIENTATION", 9, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"ACTION_TIMING", 6, MYSQL_TYPE_STRING, 0, 0, "Timing", OPEN_FRM_ONLY},
+ {"ACTION_REFERENCE_OLD_TABLE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FRM_ONLY},
+ {"ACTION_REFERENCE_NEW_TABLE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FRM_ONLY},
+ {"ACTION_REFERENCE_OLD_ROW", 3, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"ACTION_REFERENCE_NEW_ROW", 3, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
+ {"CREATED", 0, MYSQL_TYPE_DATETIME, 0, 1, "Created", OPEN_FRM_ONLY},
+ {"SQL_MODE", 32*256, MYSQL_TYPE_STRING, 0, 0, "sql_mode", OPEN_FRM_ONLY},
+ {"DEFINER", DEFINER_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "Definer", OPEN_FRM_ONLY},
+ {"CHARACTER_SET_CLIENT", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "character_set_client", OPEN_FRM_ONLY},
+ {"COLLATION_CONNECTION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "collation_connection", OPEN_FRM_ONLY},
+ {"DATABASE_COLLATION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0,
+ "Database Collation", OPEN_FRM_ONLY},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO partitions_fields_info[]=
+{
+ {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"TABLE_SCHEMA",NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"PARTITION_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"SUBPARTITION_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FULL_TABLE},
+ {"PARTITION_ORDINAL_POSITION", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FULL_TABLE},
+ {"SUBPARTITION_ORDINAL_POSITION", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FULL_TABLE},
+ {"PARTITION_METHOD", 18, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"SUBPARTITION_METHOD", 12, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"PARTITION_EXPRESSION", 65535, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"SUBPARTITION_EXPRESSION", 65535, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FULL_TABLE},
+ {"PARTITION_DESCRIPTION", 65535, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"TABLE_ROWS", 21 , MYSQL_TYPE_LONGLONG, 0, MY_I_S_UNSIGNED, 0,
+ OPEN_FULL_TABLE},
+ {"AVG_ROW_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0, MY_I_S_UNSIGNED, 0,
+ OPEN_FULL_TABLE},
+ {"DATA_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0, MY_I_S_UNSIGNED, 0,
+ OPEN_FULL_TABLE},
+ {"MAX_DATA_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FULL_TABLE},
+ {"INDEX_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0, MY_I_S_UNSIGNED, 0,
+ OPEN_FULL_TABLE},
+ {"DATA_FREE", 21 , MYSQL_TYPE_LONGLONG, 0, MY_I_S_UNSIGNED, 0,
+ OPEN_FULL_TABLE},
+ {"CREATE_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, 0, OPEN_FULL_TABLE},
+ {"UPDATE_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, 0, OPEN_FULL_TABLE},
+ {"CHECK_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, 0, OPEN_FULL_TABLE},
+ {"CHECKSUM", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FULL_TABLE},
+ {"PARTITION_COMMENT", 80, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"NODEGROUP", 12 , MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"TABLESPACE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ OPEN_FULL_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO variables_fields_info[]=
+{
+ {"VARIABLE_NAME", 64, MYSQL_TYPE_STRING, 0, 0, "Variable_name",
+ SKIP_OPEN_TABLE},
+ {"VARIABLE_VALUE", 1024, MYSQL_TYPE_STRING, 0, 1, "Value", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO processlist_fields_info[]=
+{
+ {"ID", 4, MYSQL_TYPE_LONGLONG, 0, 0, "Id", SKIP_OPEN_TABLE},
+ {"USER", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "User",
+ SKIP_OPEN_TABLE},
+ {"HOST", LIST_PROCESS_HOST_LEN, MYSQL_TYPE_STRING, 0, 0, "Host",
+ SKIP_OPEN_TABLE},
+ {"DB", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, "Db", SKIP_OPEN_TABLE},
+ {"COMMAND", 16, MYSQL_TYPE_STRING, 0, 0, "Command", SKIP_OPEN_TABLE},
+ {"TIME", 7, MYSQL_TYPE_LONG, 0, 0, "Time", SKIP_OPEN_TABLE},
+ {"STATE", 64, MYSQL_TYPE_STRING, 0, 1, "State", SKIP_OPEN_TABLE},
+ {"INFO", PROCESS_LIST_INFO_WIDTH, MYSQL_TYPE_STRING, 0, 1, "Info",
+ SKIP_OPEN_TABLE},
+ {"TIME_MS", 100 * (MY_INT64_NUM_DECIMAL_DIGITS + 1) + 3, MYSQL_TYPE_DECIMAL,
+ 0, 0, "Time_ms", SKIP_OPEN_TABLE},
+ {"STAGE", 2, MYSQL_TYPE_TINY, 0, 0, "Stage", SKIP_OPEN_TABLE},
+ {"MAX_STAGE", 2, MYSQL_TYPE_TINY, 0, 0, "Max_stage", SKIP_OPEN_TABLE},
+ {"PROGRESS", 703, MYSQL_TYPE_DECIMAL, 0, 0, "Progress",
+ SKIP_OPEN_TABLE},
+ {"MEMORY_USED", 7, MYSQL_TYPE_LONG, 0, 0, "Memory_used", SKIP_OPEN_TABLE},
+ {"EXAMINED_ROWS", 7, MYSQL_TYPE_LONG, 0, 0, "Examined_rows", SKIP_OPEN_TABLE},
+ {"QUERY_ID", 4, MYSQL_TYPE_LONGLONG, 0, 0, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO plugin_fields_info[]=
+{
+ {"PLUGIN_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name",
+ SKIP_OPEN_TABLE},
+ {"PLUGIN_VERSION", 20, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PLUGIN_STATUS", 16, MYSQL_TYPE_STRING, 0, 0, "Status", SKIP_OPEN_TABLE},
+ {"PLUGIN_TYPE", 80, MYSQL_TYPE_STRING, 0, 0, "Type", SKIP_OPEN_TABLE},
+ {"PLUGIN_TYPE_VERSION", 20, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PLUGIN_LIBRARY", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, "Library",
+ SKIP_OPEN_TABLE},
+ {"PLUGIN_LIBRARY_VERSION", 20, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"PLUGIN_AUTHOR", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"PLUGIN_DESCRIPTION", 65535, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"PLUGIN_LICENSE", 80, MYSQL_TYPE_STRING, 0, 0, "License", SKIP_OPEN_TABLE},
+ {"LOAD_OPTION", 64, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PLUGIN_MATURITY", 12, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"PLUGIN_AUTH_VERSION", 80, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+ST_FIELD_INFO files_fields_info[]=
+{
+ {"FILE_ID", 4, MYSQL_TYPE_LONGLONG, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"FILE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"FILE_TYPE", 20, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLESPACE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ SKIP_OPEN_TABLE},
+ {"TABLE_CATALOG", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"LOGFILE_GROUP_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0,
+ SKIP_OPEN_TABLE},
+ {"LOGFILE_GROUP_NUMBER", 4, MYSQL_TYPE_LONGLONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"ENGINE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"FULLTEXT_KEYS", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"DELETED_ROWS", 4, MYSQL_TYPE_LONGLONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"UPDATE_COUNT", 4, MYSQL_TYPE_LONGLONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"FREE_EXTENTS", 4, MYSQL_TYPE_LONGLONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"TOTAL_EXTENTS", 4, MYSQL_TYPE_LONGLONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"EXTENT_SIZE", 4, MYSQL_TYPE_LONGLONG, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"INITIAL_SIZE", 21, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, SKIP_OPEN_TABLE},
+ {"MAXIMUM_SIZE", 21, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, SKIP_OPEN_TABLE},
+ {"AUTOEXTEND_SIZE", 21, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, SKIP_OPEN_TABLE},
+ {"CREATION_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"LAST_UPDATE_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"LAST_ACCESS_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"RECOVER_TIME", 4, MYSQL_TYPE_LONGLONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"TRANSACTION_COUNTER", 4, MYSQL_TYPE_LONGLONG, 0, 1, 0, SKIP_OPEN_TABLE},
+ {"VERSION", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Version", SKIP_OPEN_TABLE},
+ {"ROW_FORMAT", 10, MYSQL_TYPE_STRING, 0, 1, "Row_format", SKIP_OPEN_TABLE},
+ {"TABLE_ROWS", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Rows", SKIP_OPEN_TABLE},
+ {"AVG_ROW_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Avg_row_length", SKIP_OPEN_TABLE},
+ {"DATA_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Data_length", SKIP_OPEN_TABLE},
+ {"MAX_DATA_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Max_data_length", SKIP_OPEN_TABLE},
+ {"INDEX_LENGTH", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Index_length", SKIP_OPEN_TABLE},
+ {"DATA_FREE", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Data_free", SKIP_OPEN_TABLE},
+ {"CREATE_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, "Create_time", SKIP_OPEN_TABLE},
+ {"UPDATE_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, "Update_time", SKIP_OPEN_TABLE},
+ {"CHECK_TIME", 0, MYSQL_TYPE_DATETIME, 0, 1, "Check_time", SKIP_OPEN_TABLE},
+ {"CHECKSUM", 21 , MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Checksum", SKIP_OPEN_TABLE},
+ {"STATUS", 20, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"EXTRA", 255, MYSQL_TYPE_STRING, 0, 1, 0, SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+void init_fill_schema_files_row(TABLE* table)
+{
+ int i;
+ for(i=0; files_fields_info[i].field_name!=NULL; i++)
+ table->field[i]->set_null();
+
+ table->field[IS_FILES_STATUS]->set_notnull();
+ table->field[IS_FILES_STATUS]->store("NORMAL", 6, system_charset_info);
+}
+
+ST_FIELD_INFO referential_constraints_fields_info[]=
+{
+ {"CONSTRAINT_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"CONSTRAINT_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"CONSTRAINT_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"UNIQUE_CONSTRAINT_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"UNIQUE_CONSTRAINT_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"UNIQUE_CONSTRAINT_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0,
+ MY_I_S_MAYBE_NULL, 0, OPEN_FULL_TABLE},
+ {"MATCH_OPTION", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"UPDATE_RULE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"DELETE_RULE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"REFERENCED_TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO parameters_fields_info[]=
+{
+ {"SPECIFIC_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"SPECIFIC_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ OPEN_FULL_TABLE},
+ {"SPECIFIC_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"ORDINAL_POSITION", 21 , MYSQL_TYPE_LONG, 0, 0, 0, OPEN_FULL_TABLE},
+ {"PARAMETER_MODE", 5, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"PARAMETER_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"DATA_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"CHARACTER_MAXIMUM_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, 0, OPEN_FULL_TABLE},
+ {"CHARACTER_OCTET_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, 0, OPEN_FULL_TABLE},
+ {"NUMERIC_PRECISION", 21 , MYSQL_TYPE_LONG, 0, 1, 0, OPEN_FULL_TABLE},
+ {"NUMERIC_SCALE", 21 , MYSQL_TYPE_LONG, 0, 1, 0, OPEN_FULL_TABLE},
+ {"DATETIME_PRECISION", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
+ 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, OPEN_FRM_ONLY},
+ {"CHARACTER_SET_NAME", 64, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"COLLATION_NAME", 64, MYSQL_TYPE_STRING, 0, 1, 0, OPEN_FULL_TABLE},
+ {"DTD_IDENTIFIER", 65535, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {"ROUTINE_TYPE", 9, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE}
+};
+
+
+ST_FIELD_INFO tablespaces_fields_info[]=
+{
+ {"TABLESPACE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
+ SKIP_OPEN_TABLE},
+ {"ENGINE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"TABLESPACE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL,
+ 0, SKIP_OPEN_TABLE},
+ {"LOGFILE_GROUP_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL,
+ 0, SKIP_OPEN_TABLE},
+ {"EXTENT_SIZE", 21, MYSQL_TYPE_LONGLONG, 0,
+ MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED, 0, SKIP_OPEN_TABLE},
+ {"AUTOEXTEND_SIZE", 21, MYSQL_TYPE_LONGLONG, 0,
+ MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED, 0, SKIP_OPEN_TABLE},
+ {"MAXIMUM_SIZE", 21, MYSQL_TYPE_LONGLONG, 0,
+ MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED, 0, SKIP_OPEN_TABLE},
+ {"NODEGROUP_ID", 21, MYSQL_TYPE_LONGLONG, 0,
+ MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED, 0, SKIP_OPEN_TABLE},
+ {"TABLESPACE_COMMENT", 2048, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0,
+ SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO keycache_fields_info[]=
+{
+ {"KEY_CACHE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE},
+ {"SEGMENTS", 3, MYSQL_TYPE_LONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED) , 0, SKIP_OPEN_TABLE},
+ {"SEGMENT_NUMBER", 3, MYSQL_TYPE_LONG, 0,
+ (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), 0, SKIP_OPEN_TABLE},
+ {"FULL_SIZE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), 0, SKIP_OPEN_TABLE},
+ {"BLOCK_SIZE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), 0, SKIP_OPEN_TABLE },
+ {"USED_BLOCKS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), "Key_blocks_used", SKIP_OPEN_TABLE},
+ {"UNUSED_BLOCKS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), "Key_blocks_unused", SKIP_OPEN_TABLE},
+ {"DIRTY_BLOCKS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), "Key_blocks_not_flushed", SKIP_OPEN_TABLE},
+ {"READ_REQUESTS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), "Key_read_requests", SKIP_OPEN_TABLE},
+ {"READS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), "Key_reads", SKIP_OPEN_TABLE},
+ {"WRITE_REQUESTS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), "Key_write_requests", SKIP_OPEN_TABLE},
+ {"WRITES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
+ (MY_I_S_UNSIGNED), "Key_writes", SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+ST_FIELD_INFO show_explain_fields_info[]=
+{
+ /* field_name, length, type, value, field_flags, old_name*/
+ {"id", 3, MYSQL_TYPE_LONGLONG, 0 /*value*/, MY_I_S_MAYBE_NULL, "id",
+ SKIP_OPEN_TABLE},
+ {"select_type", 19, MYSQL_TYPE_STRING, 0 /*value*/, 0, "select_type",
+ SKIP_OPEN_TABLE},
+ {"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL,
+ "table", SKIP_OPEN_TABLE},
+ {"type", 15, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE},
+ {"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/,
+ MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE},
+ {"key", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/,
+ MY_I_S_MAYBE_NULL, "key", SKIP_OPEN_TABLE},
+ {"key_len", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/,
+ MY_I_S_MAYBE_NULL, "key_len", SKIP_OPEN_TABLE},
+ {"ref", NAME_CHAR_LEN*MAX_REF_PARTS, MYSQL_TYPE_STRING, 0/*value*/,
+ MY_I_S_MAYBE_NULL, "ref", SKIP_OPEN_TABLE},
+ {"rows", 10, MYSQL_TYPE_LONGLONG, 0/*value*/, MY_I_S_MAYBE_NULL, "rows",
+ SKIP_OPEN_TABLE},
+ {"Extra", 255, MYSQL_TYPE_STRING, 0/*value*/, 0 /*flags*/, "Extra",
+ SKIP_OPEN_TABLE},
+ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
+};
+
+
+/*
+ Description of ST_FIELD_INFO in table.h
+
+ Make sure that the order of schema_tables and enum_schema_tables are the same.
+
+*/
+
+ST_SCHEMA_TABLE schema_tables[]=
+{
+ {"ALL_PLUGINS", plugin_fields_info, create_schema_table,
+ fill_all_plugins, make_old_format, 0, 5, -1, 0, 0},
+ {"APPLICABLE_ROLES", applicable_roles_fields_info, create_schema_table,
+ fill_schema_applicable_roles, 0, 0, -1, -1, 0, 0},
+ {"CHARACTER_SETS", charsets_fields_info, create_schema_table,
+ fill_schema_charsets, make_character_sets_old_format, 0, -1, -1, 0, 0},
+ {"CLIENT_STATISTICS", client_stats_fields_info, create_schema_table,
+ fill_schema_client_stats, make_old_format, 0, -1, -1, 0, 0},
+ {"COLLATIONS", collation_fields_info, create_schema_table,
+ fill_schema_collation, make_old_format, 0, -1, -1, 0, 0},
+ {"COLLATION_CHARACTER_SET_APPLICABILITY", coll_charset_app_fields_info,
+ create_schema_table, fill_schema_coll_charset_app, 0, 0, -1, -1, 0, 0},
+ {"COLUMNS", columns_fields_info, create_schema_table,
+ get_all_tables, make_columns_old_format, get_schema_column_record, 1, 2, 0,
+ OPTIMIZE_I_S_TABLE|OPEN_VIEW_FULL},
+ {"COLUMN_PRIVILEGES", column_privileges_fields_info, create_schema_table,
+ fill_schema_column_privileges, 0, 0, -1, -1, 0, 0},
+ {"ENABLED_ROLES", enabled_roles_fields_info, create_schema_table,
+ fill_schema_enabled_roles, 0, 0, -1, -1, 0, 0},
+ {"ENGINES", engines_fields_info, create_schema_table,
+ fill_schema_engines, make_old_format, 0, -1, -1, 0, 0},
+#ifdef HAVE_EVENT_SCHEDULER
+ {"EVENTS", events_fields_info, create_schema_table,
+ Events::fill_schema_events, make_old_format, 0, -1, -1, 0, 0},
+#else
+ {"EVENTS", events_fields_info, create_schema_table,
+ 0, make_old_format, 0, -1, -1, 0, 0},
+#endif
+ {"EXPLAIN", show_explain_fields_info, create_schema_table, fill_show_explain,
+ make_old_format, 0, -1, -1, TRUE /*hidden*/ , 0},
+ {"FILES", files_fields_info, create_schema_table,
+ hton_fill_schema_table, 0, 0, -1, -1, 0, 0},
+ {"GLOBAL_STATUS", variables_fields_info, create_schema_table,
+ fill_status, make_old_format, 0, 0, -1, 0, 0},
+ {"GLOBAL_VARIABLES", variables_fields_info, create_schema_table,
+ fill_variables, make_old_format, 0, 0, -1, 0, 0},
+ {"INDEX_STATISTICS", index_stats_fields_info, create_schema_table,
+ fill_schema_index_stats, make_old_format, 0, -1, -1, 0, 0},
+ {"KEY_CACHES", keycache_fields_info, create_schema_table,
+ fill_key_cache_tables, make_old_format, 0, -1,-1, 0, 0},
+ {"KEY_COLUMN_USAGE", key_column_usage_fields_info, create_schema_table,
+ get_all_tables, 0, get_schema_key_column_usage_record, 4, 5, 0,
+ OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY},
+ {"OPEN_TABLES", open_tables_fields_info, create_schema_table,
+ fill_open_tables, make_old_format, 0, -1, -1, 1, 0},
+ {"PARAMETERS", parameters_fields_info, create_schema_table,
+ fill_schema_proc, 0, 0, -1, -1, 0, 0},
+ {"PARTITIONS", partitions_fields_info, create_schema_table,
+ get_all_tables, 0, get_schema_partitions_record, 1, 2, 0,
+ OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY},
+ {"PLUGINS", plugin_fields_info, create_schema_table,
+ fill_plugins, make_old_format, 0, -1, -1, 0, 0},
+ {"PROCESSLIST", processlist_fields_info, create_schema_table,
+ fill_schema_processlist, make_old_format, 0, -1, -1, 0, 0},
+ {"PROFILING", query_profile_statistics_info, create_schema_table,
+ fill_query_profile_statistics_info, make_profile_table_for_show,
+ NULL, -1, -1, false, 0},
+ {"REFERENTIAL_CONSTRAINTS", referential_constraints_fields_info,
+ create_schema_table, get_all_tables, 0, get_referential_constraints_record,
+ 1, 9, 0, OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY},
+ {"ROUTINES", proc_fields_info, create_schema_table,
+ fill_schema_proc, make_proc_old_format, 0, -1, -1, 0, 0},
+ {"SCHEMATA", schema_fields_info, create_schema_table,
+ fill_schema_schemata, make_schemata_old_format, 0, 1, -1, 0, 0},
+ {"SCHEMA_PRIVILEGES", schema_privileges_fields_info, create_schema_table,
+ fill_schema_schema_privileges, 0, 0, -1, -1, 0, 0},
+ {"SESSION_STATUS", variables_fields_info, create_schema_table,
+ fill_status, make_old_format, 0, 0, -1, 0, 0},
+ {"SESSION_VARIABLES", variables_fields_info, create_schema_table,
+ fill_variables, make_old_format, 0, 0, -1, 0, 0},
+ {"STATISTICS", stat_fields_info, create_schema_table,
+ get_all_tables, make_old_format, get_schema_stat_record, 1, 2, 0,
+ OPEN_TABLE_ONLY|OPTIMIZE_I_S_TABLE},
+ {"STATUS", variables_fields_info, create_schema_table, fill_status,
+ make_old_format, 0, 0, -1, 1, 0},
+ {"TABLES", tables_fields_info, create_schema_table,
+ get_all_tables, make_old_format, get_schema_tables_record, 1, 2, 0,
+ OPTIMIZE_I_S_TABLE},
+ {"TABLESPACES", tablespaces_fields_info, create_schema_table,
+ hton_fill_schema_table, 0, 0, -1, -1, 0, 0},
+ {"TABLE_CONSTRAINTS", table_constraints_fields_info, create_schema_table,
+ get_all_tables, 0, get_schema_constraints_record, 3, 4, 0,
+ OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY},
+ {"TABLE_NAMES", table_names_fields_info, create_schema_table,
+ get_all_tables, make_table_names_old_format, 0, 1, 2, 1, OPTIMIZE_I_S_TABLE},
+ {"TABLE_PRIVILEGES", table_privileges_fields_info, create_schema_table,
+ fill_schema_table_privileges, 0, 0, -1, -1, 0, 0},
+ {"TABLE_STATISTICS", table_stats_fields_info, create_schema_table,
+ fill_schema_table_stats, make_old_format, 0, -1, -1, 0, 0},
+ {"TRIGGERS", triggers_fields_info, create_schema_table,
+ get_all_tables, make_old_format, get_schema_triggers_record, 5, 6, 0,
+ OPEN_TRIGGER_ONLY|OPTIMIZE_I_S_TABLE},
+ {"USER_PRIVILEGES", user_privileges_fields_info, create_schema_table,
+ fill_schema_user_privileges, 0, 0, -1, -1, 0, 0},
+ {"USER_STATISTICS", user_stats_fields_info, create_schema_table,
+ fill_schema_user_stats, make_old_format, 0, -1, -1, 0, 0},
+ {"VARIABLES", variables_fields_info, create_schema_table, fill_variables,
+ make_old_format, 0, 0, -1, 1, 0},
+ {"VIEWS", view_fields_info, create_schema_table,
+ get_all_tables, 0, get_schema_views_record, 1, 2, 0,
+ OPEN_VIEW_ONLY|OPTIMIZE_I_S_TABLE},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+};
+
+
+int initialize_schema_table(st_plugin_int *plugin)
+{
+ ST_SCHEMA_TABLE *schema_table;
+ DBUG_ENTER("initialize_schema_table");
+
+ if (!(schema_table= (ST_SCHEMA_TABLE *)my_malloc(sizeof(ST_SCHEMA_TABLE),
+ MYF(MY_WME | MY_ZEROFILL))))
+ DBUG_RETURN(1);
+ /* Historical Requirement */
+ plugin->data= schema_table; // shortcut for the future
+ if (plugin->plugin->init)
+ {
+ schema_table->create_table= create_schema_table;
+ schema_table->old_format= make_old_format;
+ schema_table->idx_field1= -1,
+ schema_table->idx_field2= -1;
+
+ /* Make the name available to the init() function. */
+ schema_table->table_name= plugin->name.str;
+
+ if (plugin->plugin->init(schema_table))
+ {
+ sql_print_error("Plugin '%s' init function returned error.",
+ plugin->name.str);
+ plugin->data= NULL;
+ my_free(schema_table);
+ DBUG_RETURN(1);
+ }
+
+ /* Make sure the plugin name is not set inside the init() function. */
+ schema_table->table_name= plugin->name.str;
+ }
+ DBUG_RETURN(0);
+}
+
+int finalize_schema_table(st_plugin_int *plugin)
+{
+ ST_SCHEMA_TABLE *schema_table= (ST_SCHEMA_TABLE *)plugin->data;
+ DBUG_ENTER("finalize_schema_table");
+
+ if (schema_table)
+ {
+ if (plugin->plugin->deinit)
+ {
+ DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str));
+ if (plugin->plugin->deinit(NULL))
+ {
+ DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
+ plugin->name.str));
+ }
+ }
+ my_free(schema_table);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Output trigger information (SHOW CREATE TRIGGER) to the client.
+
+ @param thd Thread context.
+ @param triggers List of triggers for the table.
+ @param trigger_idx Index of the trigger to dump.
+
+ @return Operation status
+ @retval TRUE Error.
+ @retval FALSE Success.
+*/
+
+static bool show_create_trigger_impl(THD *thd,
+ Table_triggers_list *triggers,
+ int trigger_idx)
+{
+ int ret_code;
+
+ Protocol *p= thd->protocol;
+ List<Item> fields;
+
+ LEX_STRING trg_name;
+ ulonglong trg_sql_mode;
+ LEX_STRING trg_sql_mode_str;
+ LEX_STRING trg_sql_original_stmt;
+ LEX_STRING trg_client_cs_name;
+ LEX_STRING trg_connection_cl_name;
+ LEX_STRING trg_db_cl_name;
+
+ CHARSET_INFO *trg_client_cs;
+
+ /*
+ TODO: Check privileges here. This functionality will be added by
+ implementation of the following WL items:
+ - WL#2227: New privileges for new objects
+ - WL#3482: Protect SHOW CREATE PROCEDURE | FUNCTION | VIEW | TRIGGER
+ properly
+
+ SHOW TRIGGERS and I_S.TRIGGERS will be affected too.
+ */
+
+ /* Prepare trigger "object". */
+
+ triggers->get_trigger_info(thd,
+ trigger_idx,
+ &trg_name,
+ &trg_sql_mode,
+ &trg_sql_original_stmt,
+ &trg_client_cs_name,
+ &trg_connection_cl_name,
+ &trg_db_cl_name);
+
+ sql_mode_string_representation(thd, trg_sql_mode, &trg_sql_mode_str);
+
+ /* Resolve trigger client character set. */
+
+ if (resolve_charset(trg_client_cs_name.str, NULL, &trg_client_cs))
+ return TRUE;
+
+ /* Send header. */
+
+ fields.push_back(new Item_empty_string("Trigger", NAME_LEN));
+ fields.push_back(new Item_empty_string("sql_mode", trg_sql_mode_str.length));
+
+ {
+ /*
+ NOTE: SQL statement field must be not less than 1024 in order not to
+ confuse old clients.
+ */
+
+ Item_empty_string *stmt_fld=
+ new Item_empty_string("SQL Original Statement",
+ MY_MAX(trg_sql_original_stmt.length, 1024));
+
+ stmt_fld->maybe_null= TRUE;
+
+ fields.push_back(stmt_fld);
+ }
+
+ fields.push_back(new Item_empty_string("character_set_client",
+ MY_CS_NAME_SIZE));
+
+ fields.push_back(new Item_empty_string("collation_connection",
+ MY_CS_NAME_SIZE));
+
+ fields.push_back(new Item_empty_string("Database Collation",
+ MY_CS_NAME_SIZE));
+
+ if (p->send_result_set_metadata(&fields, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ return TRUE;
+
+ /* Send data. */
+
+ p->prepare_for_resend();
+
+ p->store(trg_name.str,
+ trg_name.length,
+ system_charset_info);
+
+ p->store(trg_sql_mode_str.str,
+ trg_sql_mode_str.length,
+ system_charset_info);
+
+ p->store(trg_sql_original_stmt.str,
+ trg_sql_original_stmt.length,
+ trg_client_cs);
+
+ p->store(trg_client_cs_name.str,
+ trg_client_cs_name.length,
+ system_charset_info);
+
+ p->store(trg_connection_cl_name.str,
+ trg_connection_cl_name.length,
+ system_charset_info);
+
+ p->store(trg_db_cl_name.str,
+ trg_db_cl_name.length,
+ system_charset_info);
+
+ ret_code= p->write();
+
+ if (!ret_code)
+ my_eof(thd);
+
+ return ret_code != 0;
+}
+
+
+/**
+ Read TRN and TRG files to obtain base table name for the specified
+ trigger name and construct TABE_LIST object for the base table.
+
+ @param thd Thread context.
+ @param trg_name Trigger name.
+
+ @return TABLE_LIST object corresponding to the base table.
+
+ TODO: This function is a copy&paste from add_table_to_list() and
+ sp_add_to_query_tables(). The problem is that in order to be compatible
+ with Stored Programs (Prepared Statements), we should not touch thd->lex.
+ The "source" functions also add created TABLE_LIST object to the
+ thd->lex->query_tables.
+
+ The plan to eliminate this copy&paste is to:
+
+ - get rid of sp_add_to_query_tables() and use Lex::add_table_to_list().
+ Only add_table_to_list() must be used to add tables from the parser
+ into Lex::query_tables list.
+
+ - do not update Lex::query_tables in add_table_to_list().
+*/
+
+static
+TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name)
+{
+ char trn_path_buff[FN_REFLEN];
+ LEX_STRING trn_path= { trn_path_buff, 0 };
+ LEX_STRING db;
+ LEX_STRING tbl_name;
+ TABLE_LIST *table;
+
+ build_trn_path(thd, trg_name, &trn_path);
+
+ if (check_trn_exists(&trn_path))
+ {
+ my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
+ return NULL;
+ }
+
+ if (load_table_name_for_trigger(thd, trg_name, &trn_path, &tbl_name))
+ return NULL;
+
+ /* We need to reset statement table list to be PS/SP friendly. */
+ if (!(table= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST))))
+ return NULL;
+
+ db= trg_name->m_db;
+
+ db.str= thd->strmake(db.str, db.length);
+ if (lower_case_table_names)
+ db.length= my_casedn_str(files_charset_info, db.str);
+
+ tbl_name.str= thd->strmake(tbl_name.str, tbl_name.length);
+
+ if (db.str == NULL || tbl_name.str == NULL)
+ return NULL;
+
+ table->init_one_table(db.str, db.length, tbl_name.str, tbl_name.length,
+ tbl_name.str, TL_IGNORE);
+
+ return table;
+}
+
+
+/**
+ SHOW CREATE TRIGGER high-level implementation.
+
+ @param thd Thread context.
+ @param trg_name Trigger name.
+
+ @return Operation status
+ @retval TRUE Error.
+ @retval FALSE Success.
+*/
+
+bool show_create_trigger(THD *thd, const sp_name *trg_name)
+{
+ TABLE_LIST *lst= get_trigger_table(thd, trg_name);
+ uint num_tables; /* NOTE: unused, only to pass to open_tables(). */
+ Table_triggers_list *triggers;
+ int trigger_idx;
+ bool error= TRUE;
+
+ if (!lst)
+ return TRUE;
+
+ if (check_table_access(thd, TRIGGER_ACL, lst, FALSE, 1, TRUE))
+ {
+ my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "TRIGGER");
+ return TRUE;
+ }
+
+ /*
+ Metadata locks taken during SHOW CREATE TRIGGER should be released when
+ the statement completes as it is an information statement.
+ */
+ MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
+
+ /*
+ Open the table by name in order to load Table_triggers_list object.
+ */
+ if (open_tables(thd, &lst, &num_tables,
+ MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))
+ {
+ my_error(ER_TRG_CANT_OPEN_TABLE, MYF(0),
+ (const char *) trg_name->m_db.str,
+ (const char *) lst->table_name);
+
+ goto exit;
+
+ /* Perform closing actions and return error status. */
+ }
+
+ triggers= lst->table->triggers;
+
+ if (!triggers)
+ {
+ my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
+ goto exit;
+ }
+
+ trigger_idx= triggers->find_trigger_by_name(&trg_name->m_name);
+
+ if (trigger_idx < 0)
+ {
+ my_error(ER_TRG_CORRUPTED_FILE, MYF(0),
+ (const char *) trg_name->m_db.str,
+ (const char *) lst->table_name);
+
+ goto exit;
+ }
+
+ error= show_create_trigger_impl(thd, triggers, trigger_idx);
+
+ /*
+ NOTE: if show_create_trigger_impl() failed, that means we could not
+ send data to the client. In this case we simply raise the error
+ status and client connection will be closed.
+ */
+
+exit:
+ close_thread_tables(thd);
+ /* Release any metadata locks taken during SHOW CREATE TRIGGER. */
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+ return error;
+}
+
+class IS_internal_schema_access : public ACL_internal_schema_access
+{
+public:
+ IS_internal_schema_access()
+ {}
+
+ ~IS_internal_schema_access()
+ {}
+
+ ACL_internal_access_result check(ulong want_access,
+ ulong *save_priv) const;
+
+ const ACL_internal_table_access *lookup(const char *name) const;
+};
+
+ACL_internal_access_result
+IS_internal_schema_access::check(ulong want_access,
+ ulong *save_priv) const
+{
+ want_access &= ~SELECT_ACL;
+
+ /*
+ We don't allow any simple privileges but SELECT_ACL on
+ the information_schema database.
+ */
+ if (unlikely(want_access & DB_ACLS))
+ return ACL_INTERNAL_ACCESS_DENIED;
+
+ /* Always grant SELECT for the information schema. */
+ *save_priv|= SELECT_ACL;
+
+ return want_access ? ACL_INTERNAL_ACCESS_CHECK_GRANT :
+ ACL_INTERNAL_ACCESS_GRANTED;
+}
+
+const ACL_internal_table_access *
+IS_internal_schema_access::lookup(const char *name) const
+{
+ /* There are no per table rules for the information schema. */
+ return NULL;
+}
+
+static IS_internal_schema_access is_internal_schema_access;
+
+void initialize_information_schema_acl()
+{
+ ACL_internal_schema_registry::register_schema(&INFORMATION_SCHEMA_NAME,
+ &is_internal_schema_access);
+}
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+/*
+ Convert a string in character set in column character set format
+ to utf8 character set if possible, the utf8 character set string
+ will later possibly be converted to character set used by client.
+ Thus we attempt conversion from column character set to both
+ utf8 and to character set client.
+
+ Examples of strings that should fail conversion to utf8 are unassigned
+ characters as e.g. 0x81 in cp1250 (Windows character set for for countries
+ like Czech and Poland). Example of string that should fail conversion to
+ character set on client (e.g. if this is latin1) is 0x2020 (daggger) in
+ ucs2.
+
+ If the conversion fails we will as a fall back convert the string to
+ hex encoded format. The caller of the function can also ask for hex
+ encoded format of output string unconditionally.
+
+ SYNOPSIS
+ get_cs_converted_string_value()
+ thd Thread object
+ input_str Input string in cs character set
+ output_str Output string to be produced in utf8
+ cs Character set of input string
+ use_hex Use hex string unconditionally
+
+
+ RETURN VALUES
+ No return value
+*/
+
+static void get_cs_converted_string_value(THD *thd,
+ String *input_str,
+ String *output_str,
+ CHARSET_INFO *cs,
+ bool use_hex)
+{
+
+ output_str->length(0);
+ if (input_str->length() == 0)
+ {
+ output_str->append("''");
+ return;
+ }
+ if (!use_hex)
+ {
+ String try_val;
+ uint try_conv_error= 0;
+
+ try_val.copy(input_str->ptr(), input_str->length(), cs,
+ thd->variables.character_set_client, &try_conv_error);
+ if (!try_conv_error)
+ {
+ String val;
+ uint conv_error= 0;
+
+ val.copy(input_str->ptr(), input_str->length(), cs,
+ system_charset_info, &conv_error);
+ if (!conv_error)
+ {
+ append_unescaped(output_str, val.ptr(), val.length());
+ return;
+ }
+ }
+ /* We had a conversion error, use hex encoded string for safety */
+ }
+ {
+ const uchar *ptr;
+ uint i, len;
+ char buf[3];
+
+ output_str->append("_");
+ output_str->append(cs->csname);
+ output_str->append(" ");
+ output_str->append("0x");
+ len= input_str->length();
+ ptr= (uchar*)input_str->ptr();
+ for (i= 0; i < len; i++)
+ {
+ uint high, low;
+
+ high= (*ptr) >> 4;
+ low= (*ptr) & 0x0F;
+ buf[0]= _dig_vec_upper[high];
+ buf[1]= _dig_vec_upper[low];
+ buf[2]= 0;
+ output_str->append((const char*)buf);
+ ptr++;
+ }
+ }
+ return;
+}
+#endif
diff --git a/sql/sql_show.h b/sql/sql_show.h
new file mode 100644
index 00000000000..ce7a9110cca
--- /dev/null
+++ b/sql/sql_show.h
@@ -0,0 +1,161 @@
+/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_SHOW_H
+#define SQL_SHOW_H
+
+#include "sql_list.h" /* List */
+#include "handler.h" /* enum_schema_tables */
+#include "table.h" /* enum_schema_table_state */
+#include "my_apc.h"
+
+/* Forward declarations */
+class JOIN;
+class String;
+class THD;
+class sp_name;
+struct TABLE_LIST;
+typedef class st_select_lex SELECT_LEX;
+struct LEX;
+typedef struct st_mysql_show_var SHOW_VAR;
+typedef struct st_schema_table ST_SCHEMA_TABLE;
+struct TABLE;
+typedef struct system_status_var STATUS_VAR;
+
+/* Used by handlers to store things in schema tables */
+#define IS_FILES_FILE_ID 0
+#define IS_FILES_FILE_NAME 1
+#define IS_FILES_FILE_TYPE 2
+#define IS_FILES_TABLESPACE_NAME 3
+#define IS_FILES_TABLE_CATALOG 4
+#define IS_FILES_TABLE_SCHEMA 5
+#define IS_FILES_TABLE_NAME 6
+#define IS_FILES_LOGFILE_GROUP_NAME 7
+#define IS_FILES_LOGFILE_GROUP_NUMBER 8
+#define IS_FILES_ENGINE 9
+#define IS_FILES_FULLTEXT_KEYS 10
+#define IS_FILES_DELETED_ROWS 11
+#define IS_FILES_UPDATE_COUNT 12
+#define IS_FILES_FREE_EXTENTS 13
+#define IS_FILES_TOTAL_EXTENTS 14
+#define IS_FILES_EXTENT_SIZE 15
+#define IS_FILES_INITIAL_SIZE 16
+#define IS_FILES_MAXIMUM_SIZE 17
+#define IS_FILES_AUTOEXTEND_SIZE 18
+#define IS_FILES_CREATION_TIME 19
+#define IS_FILES_LAST_UPDATE_TIME 20
+#define IS_FILES_LAST_ACCESS_TIME 21
+#define IS_FILES_RECOVER_TIME 22
+#define IS_FILES_TRANSACTION_COUNTER 23
+#define IS_FILES_VERSION 24
+#define IS_FILES_ROW_FORMAT 25
+#define IS_FILES_TABLE_ROWS 26
+#define IS_FILES_AVG_ROW_LENGTH 27
+#define IS_FILES_DATA_LENGTH 28
+#define IS_FILES_MAX_DATA_LENGTH 29
+#define IS_FILES_INDEX_LENGTH 30
+#define IS_FILES_DATA_FREE 31
+#define IS_FILES_CREATE_TIME 32
+#define IS_FILES_UPDATE_TIME 33
+#define IS_FILES_CHECK_TIME 34
+#define IS_FILES_CHECKSUM 35
+#define IS_FILES_STATUS 36
+#define IS_FILES_EXTRA 37
+
+typedef enum { WITHOUT_DB_NAME, WITH_DB_NAME } enum_with_db_name;
+int show_create_table(THD *thd, TABLE_LIST *table_list, String *packet,
+ HA_CREATE_INFO *create_info_arg,
+ enum_with_db_name with_db_name);
+
+int copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table);
+
+bool append_identifier(THD *thd, String *packet, const char *name,
+ uint length);
+void mysqld_list_fields(THD *thd,TABLE_LIST *table, const char *wild);
+int mysqld_dump_create_info(THD *thd, TABLE_LIST *table_list, int fd);
+bool mysqld_show_create(THD *thd, TABLE_LIST *table_list);
+bool mysqld_show_create_db(THD *thd, LEX_STRING *db_name,
+ LEX_STRING *orig_db_name,
+ HA_CREATE_INFO *create);
+
+void mysqld_list_processes(THD *thd,const char *user,bool verbose);
+int mysqld_show_status(THD *thd);
+int mysqld_show_variables(THD *thd,const char *wild);
+bool mysqld_show_storage_engines(THD *thd);
+bool mysqld_show_authors(THD *thd);
+bool mysqld_show_contributors(THD *thd);
+bool mysqld_show_privileges(THD *thd);
+char *make_backup_log_name(char *buff, const char *name, const char* log_ext);
+void calc_sum_of_all_status(STATUS_VAR *to);
+void append_definer(THD *thd, String *buffer, const LEX_STRING *definer_user,
+ const LEX_STRING *definer_host);
+int add_status_vars(SHOW_VAR *list);
+void remove_status_vars(SHOW_VAR *list);
+void init_status_vars();
+void free_status_vars();
+void reset_status_vars();
+bool show_create_trigger(THD *thd, const sp_name *trg_name);
+void view_store_options(THD *thd, TABLE_LIST *table, String *buff);
+
+void init_fill_schema_files_row(TABLE* table);
+bool schema_table_store_record(THD *thd, TABLE *table);
+void initialize_information_schema_acl();
+
+ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name);
+ST_SCHEMA_TABLE *get_schema_table(enum enum_schema_tables schema_table_idx);
+int make_schema_select(THD *thd, SELECT_LEX *sel,
+ enum enum_schema_tables schema_table_idx);
+int mysql_schema_table(THD *thd, LEX *lex, TABLE_LIST *table_list);
+bool get_schema_tables_result(JOIN *join,
+ enum enum_schema_table_state executed_place);
+enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table);
+
+/* These functions were under INNODB_COMPATIBILITY_HOOKS */
+int get_quote_char_for_identifier(THD *thd, const char *name, uint length);
+THD *find_thread_by_id(longlong id, bool query_id= false);
+
+class select_result_explain_buffer;
+/*
+ SHOW EXPLAIN request object.
+*/
+
+class Show_explain_request : public Apc_target::Apc_call
+{
+public:
+ THD *target_thd; /* thd that we're running SHOW EXPLAIN for */
+ THD *request_thd; /* thd that run SHOW EXPLAIN command */
+
+ /* If true, there was some error when producing EXPLAIN output. */
+ bool failed_to_produce;
+
+ /* SHOW EXPLAIN will be stored here */
+ select_result_explain_buffer *explain_buf;
+
+ /* Query that we've got SHOW EXPLAIN for */
+ String query_str;
+
+ /* Overloaded virtual function */
+ void call_in_target_thread();
+};
+
+/* Handle the ignored database directories list for SHOW/I_S. */
+bool ignore_db_dirs_init();
+void ignore_db_dirs_free();
+void ignore_db_dirs_reset();
+bool ignore_db_dirs_process_additions();
+bool push_ignored_db_dir(char *path);
+extern char *opt_ignore_db_dirs;
+
+#endif /* SQL_SHOW_H */
diff --git a/sql/sql_signal.cc b/sql/sql_signal.cc
new file mode 100644
index 00000000000..374a24f75e5
--- /dev/null
+++ b/sql/sql_signal.cc
@@ -0,0 +1,542 @@
+/* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 <my_global.h>
+#include "sql_priv.h"
+#include "sp_head.h"
+#include "sp_pcontext.h"
+#include "sp_rcontext.h"
+#include "sql_signal.h"
+
+/*
+ The parser accepts any error code (desired)
+ The runtime internally supports any error code (desired)
+ The client server protocol is limited to 16 bits error codes (restriction),
+ and the value of 65535 is reserved for progress reporting.
+ Enforcing the 65534 limit in the runtime until the protocol can change.
+*/
+#define MAX_MYSQL_ERRNO 65534
+
+const LEX_STRING Diag_condition_item_names[]=
+{
+ { C_STRING_WITH_LEN("CLASS_ORIGIN") },
+ { C_STRING_WITH_LEN("SUBCLASS_ORIGIN") },
+ { C_STRING_WITH_LEN("CONSTRAINT_CATALOG") },
+ { C_STRING_WITH_LEN("CONSTRAINT_SCHEMA") },
+ { C_STRING_WITH_LEN("CONSTRAINT_NAME") },
+ { C_STRING_WITH_LEN("CATALOG_NAME") },
+ { C_STRING_WITH_LEN("SCHEMA_NAME") },
+ { C_STRING_WITH_LEN("TABLE_NAME") },
+ { C_STRING_WITH_LEN("COLUMN_NAME") },
+ { C_STRING_WITH_LEN("CURSOR_NAME") },
+ { C_STRING_WITH_LEN("MESSAGE_TEXT") },
+ { C_STRING_WITH_LEN("MYSQL_ERRNO") },
+
+ { C_STRING_WITH_LEN("CONDITION_IDENTIFIER") },
+ { C_STRING_WITH_LEN("CONDITION_NUMBER") },
+ { C_STRING_WITH_LEN("CONNECTION_NAME") },
+ { C_STRING_WITH_LEN("MESSAGE_LENGTH") },
+ { C_STRING_WITH_LEN("MESSAGE_OCTET_LENGTH") },
+ { C_STRING_WITH_LEN("PARAMETER_MODE") },
+ { C_STRING_WITH_LEN("PARAMETER_NAME") },
+ { C_STRING_WITH_LEN("PARAMETER_ORDINAL_POSITION") },
+ { C_STRING_WITH_LEN("RETURNED_SQLSTATE") },
+ { C_STRING_WITH_LEN("ROUTINE_CATALOG") },
+ { C_STRING_WITH_LEN("ROUTINE_NAME") },
+ { C_STRING_WITH_LEN("ROUTINE_SCHEMA") },
+ { C_STRING_WITH_LEN("SERVER_NAME") },
+ { C_STRING_WITH_LEN("SPECIFIC_NAME") },
+ { C_STRING_WITH_LEN("TRIGGER_CATALOG") },
+ { C_STRING_WITH_LEN("TRIGGER_NAME") },
+ { C_STRING_WITH_LEN("TRIGGER_SCHEMA") }
+};
+
+const LEX_STRING Diag_statement_item_names[]=
+{
+ { C_STRING_WITH_LEN("NUMBER") },
+ { C_STRING_WITH_LEN("MORE") },
+ { C_STRING_WITH_LEN("COMMAND_FUNCTION") },
+ { C_STRING_WITH_LEN("COMMAND_FUNCTION_CODE") },
+ { C_STRING_WITH_LEN("DYNAMIC_FUNCTION") },
+ { C_STRING_WITH_LEN("DYNAMIC_FUNCTION_CODE") },
+ { C_STRING_WITH_LEN("ROW_COUNT") },
+ { C_STRING_WITH_LEN("TRANSACTIONS_COMMITTED") },
+ { C_STRING_WITH_LEN("TRANSACTIONS_ROLLED_BACK") },
+ { C_STRING_WITH_LEN("TRANSACTION_ACTIVE") }
+};
+
+
+Set_signal_information::Set_signal_information(
+ const Set_signal_information& set)
+{
+ memcpy(m_item, set.m_item, sizeof(m_item));
+}
+
+void Set_signal_information::clear()
+{
+ memset(m_item, 0, sizeof(m_item));
+}
+
+void Sql_cmd_common_signal::assign_defaults(
+ Sql_condition *cond,
+ bool set_level_code,
+ Sql_condition::enum_warning_level level,
+ int sqlcode)
+{
+ if (set_level_code)
+ {
+ cond->m_level= level;
+ cond->m_sql_errno= sqlcode;
+ }
+ if (! cond->get_message_text())
+ cond->set_builtin_message_text(ER(sqlcode));
+}
+
+void Sql_cmd_common_signal::eval_defaults(THD *thd, Sql_condition *cond)
+{
+ DBUG_ASSERT(cond);
+
+ const char* sqlstate;
+ bool set_defaults= (m_cond != 0);
+
+ if (set_defaults)
+ {
+ /*
+ SIGNAL is restricted in sql_yacc.yy to only signal SQLSTATE conditions.
+ */
+ DBUG_ASSERT(m_cond->type == sp_condition_value::SQLSTATE);
+ sqlstate= m_cond->sql_state;
+ cond->set_sqlstate(sqlstate);
+ }
+ else
+ sqlstate= cond->get_sqlstate();
+
+ DBUG_ASSERT(sqlstate);
+ /* SQLSTATE class "00": illegal, rejected in the parser. */
+ DBUG_ASSERT((sqlstate[0] != '0') || (sqlstate[1] != '0'));
+
+ if ((sqlstate[0] == '0') && (sqlstate[1] == '1'))
+ {
+ /* SQLSTATE class "01": warning. */
+ assign_defaults(cond, set_defaults,
+ Sql_condition::WARN_LEVEL_WARN, ER_SIGNAL_WARN);
+ }
+ else if ((sqlstate[0] == '0') && (sqlstate[1] == '2'))
+ {
+ /* SQLSTATE class "02": not found. */
+ assign_defaults(cond, set_defaults,
+ Sql_condition::WARN_LEVEL_ERROR, ER_SIGNAL_NOT_FOUND);
+ }
+ else
+ {
+ /* other SQLSTATE classes : error. */
+ assign_defaults(cond, set_defaults,
+ Sql_condition::WARN_LEVEL_ERROR, ER_SIGNAL_EXCEPTION);
+ }
+}
+
+static bool assign_fixed_string(MEM_ROOT *mem_root,
+ CHARSET_INFO *dst_cs,
+ size_t max_char,
+ String *dst,
+ const String* src)
+{
+ bool truncated;
+ size_t numchars;
+ CHARSET_INFO *src_cs;
+ const char* src_str;
+ const char* src_end;
+ size_t src_len;
+ size_t to_copy;
+ char* dst_str;
+ size_t dst_len;
+ size_t dst_copied;
+ uint32 dummy_offset;
+
+ src_str= src->ptr();
+ if (src_str == NULL)
+ {
+ dst->set((const char*) NULL, 0, dst_cs);
+ return false;
+ }
+
+ src_cs= src->charset();
+ src_len= src->length();
+ src_end= src_str + src_len;
+ numchars= src_cs->cset->numchars(src_cs, src_str, src_end);
+
+ if (numchars <= max_char)
+ {
+ to_copy= src->length();
+ truncated= false;
+ }
+ else
+ {
+ numchars= max_char;
+ to_copy= dst_cs->cset->charpos(dst_cs, src_str, src_end, numchars);
+ truncated= true;
+ }
+
+ if (String::needs_conversion(to_copy, src_cs, dst_cs, & dummy_offset))
+ {
+ dst_len= numchars * dst_cs->mbmaxlen;
+ dst_str= (char*) alloc_root(mem_root, dst_len + 1);
+ if (dst_str)
+ {
+ const char* well_formed_error_pos;
+ const char* cannot_convert_error_pos;
+ const char* from_end_pos;
+
+ dst_copied= well_formed_copy_nchars(dst_cs, dst_str, dst_len,
+ src_cs, src_str, src_len,
+ numchars,
+ & well_formed_error_pos,
+ & cannot_convert_error_pos,
+ & from_end_pos);
+ DBUG_ASSERT(dst_copied <= dst_len);
+ dst_len= dst_copied; /* In case the copy truncated the data */
+ dst_str[dst_copied]= '\0';
+ }
+ }
+ else
+ {
+ dst_len= to_copy;
+ dst_str= (char*) alloc_root(mem_root, dst_len + 1);
+ if (dst_str)
+ {
+ memcpy(dst_str, src_str, to_copy);
+ dst_str[to_copy]= '\0';
+ }
+ }
+ dst->set(dst_str, dst_len, dst_cs);
+
+ return truncated;
+}
+
+static int assign_condition_item(MEM_ROOT *mem_root, const char* name, THD *thd,
+ Item *set, String *ci)
+{
+ char str_buff[(64+1)*4]; /* Room for a null terminated UTF8 String 64 */
+ String str_value(str_buff, sizeof(str_buff), & my_charset_utf8_bin);
+ String *str;
+ bool truncated;
+
+ DBUG_ENTER("assign_condition_item");
+
+ if (set->is_null())
+ {
+ thd->raise_error_printf(ER_WRONG_VALUE_FOR_VAR, name, "NULL");
+ DBUG_RETURN(1);
+ }
+
+ str= set->val_str(& str_value);
+ truncated= assign_fixed_string(mem_root, & my_charset_utf8_bin, 64, ci, str);
+ if (truncated)
+ {
+ if (thd->is_strict_mode())
+ {
+ thd->raise_error_printf(ER_COND_ITEM_TOO_LONG, name);
+ DBUG_RETURN(1);
+ }
+
+ thd->raise_warning_printf(WARN_COND_ITEM_TRUNCATED, name);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+int Sql_cmd_common_signal::eval_signal_informations(THD *thd, Sql_condition *cond)
+{
+ struct cond_item_map
+ {
+ enum enum_diag_condition_item_name m_item;
+ String Sql_condition::*m_member;
+ };
+
+ static cond_item_map map[]=
+ {
+ { DIAG_CLASS_ORIGIN, & Sql_condition::m_class_origin },
+ { DIAG_SUBCLASS_ORIGIN, & Sql_condition::m_subclass_origin },
+ { DIAG_CONSTRAINT_CATALOG, & Sql_condition::m_constraint_catalog },
+ { DIAG_CONSTRAINT_SCHEMA, & Sql_condition::m_constraint_schema },
+ { DIAG_CONSTRAINT_NAME, & Sql_condition::m_constraint_name },
+ { DIAG_CATALOG_NAME, & Sql_condition::m_catalog_name },
+ { DIAG_SCHEMA_NAME, & Sql_condition::m_schema_name },
+ { DIAG_TABLE_NAME, & Sql_condition::m_table_name },
+ { DIAG_COLUMN_NAME, & Sql_condition::m_column_name },
+ { DIAG_CURSOR_NAME, & Sql_condition::m_cursor_name }
+ };
+
+ Item *set;
+ String str_value;
+ String *str;
+ int i;
+ uint j;
+ int result= 1;
+ enum enum_diag_condition_item_name item_enum;
+ String *member;
+ const LEX_STRING *name;
+
+ DBUG_ENTER("Sql_cmd_common_signal::eval_signal_informations");
+
+ for (i= FIRST_DIAG_SET_PROPERTY;
+ i <= LAST_DIAG_SET_PROPERTY;
+ i++)
+ {
+ set= m_set_signal_information.m_item[i];
+ if (set)
+ {
+ if (! set->fixed)
+ {
+ if (set->fix_fields(thd, & set))
+ goto end;
+ m_set_signal_information.m_item[i]= set;
+ }
+ }
+ }
+
+ /*
+ Generically assign all the UTF8 String 64 condition items
+ described in the map.
+ */
+ for (j= 0; j < array_elements(map); j++)
+ {
+ item_enum= map[j].m_item;
+ set= m_set_signal_information.m_item[item_enum];
+ if (set != NULL)
+ {
+ member= & (cond->* map[j].m_member);
+ name= & Diag_condition_item_names[item_enum];
+ if (assign_condition_item(cond->m_mem_root, name->str, thd, set, member))
+ goto end;
+ }
+ }
+
+ /*
+ Assign the remaining attributes.
+ */
+
+ set= m_set_signal_information.m_item[DIAG_MESSAGE_TEXT];
+ if (set != NULL)
+ {
+ if (set->is_null())
+ {
+ thd->raise_error_printf(ER_WRONG_VALUE_FOR_VAR,
+ "MESSAGE_TEXT", "NULL");
+ goto end;
+ }
+ /*
+ Enforce that SET MESSAGE_TEXT = <value> evaluates the value
+ as VARCHAR(128) CHARACTER SET UTF8.
+ */
+ bool truncated;
+ String utf8_text;
+ str= set->val_str(& str_value);
+ truncated= assign_fixed_string(thd->mem_root, & my_charset_utf8_bin, 128,
+ & utf8_text, str);
+ if (truncated)
+ {
+ if (thd->is_strict_mode())
+ {
+ thd->raise_error_printf(ER_COND_ITEM_TOO_LONG,
+ "MESSAGE_TEXT");
+ goto end;
+ }
+
+ thd->raise_warning_printf(WARN_COND_ITEM_TRUNCATED,
+ "MESSAGE_TEXT");
+ }
+
+ /*
+ See the comments
+ "Design notes about Sql_condition::m_message_text."
+ in file sql_error.cc
+ */
+ String converted_text;
+ converted_text.set_charset(error_message_charset_info);
+ converted_text.append(utf8_text.ptr(), utf8_text.length(),
+ utf8_text.charset());
+ cond->set_builtin_message_text(converted_text.c_ptr_safe());
+ }
+
+ set= m_set_signal_information.m_item[DIAG_MYSQL_ERRNO];
+ if (set != NULL)
+ {
+ if (set->is_null())
+ {
+ thd->raise_error_printf(ER_WRONG_VALUE_FOR_VAR,
+ "MYSQL_ERRNO", "NULL");
+ goto end;
+ }
+ longlong code= set->val_int();
+ if ((code <= 0) || (code > MAX_MYSQL_ERRNO))
+ {
+ str= set->val_str(& str_value);
+ thd->raise_error_printf(ER_WRONG_VALUE_FOR_VAR,
+ "MYSQL_ERRNO", str->c_ptr_safe());
+ goto end;
+ }
+ cond->m_sql_errno= (int) code;
+ }
+
+ /*
+ The various item->val_xxx() methods don't return an error code,
+ but flag thd in case of failure.
+ */
+ if (! thd->is_error())
+ result= 0;
+
+end:
+ for (i= FIRST_DIAG_SET_PROPERTY;
+ i <= LAST_DIAG_SET_PROPERTY;
+ i++)
+ {
+ set= m_set_signal_information.m_item[i];
+ if (set)
+ {
+ if (set->fixed)
+ set->cleanup();
+ }
+ }
+
+ DBUG_RETURN(result);
+}
+
+bool Sql_cmd_common_signal::raise_condition(THD *thd, Sql_condition *cond)
+{
+ bool result= TRUE;
+
+ DBUG_ENTER("Sql_cmd_common_signal::raise_condition");
+
+ DBUG_ASSERT(thd->lex->query_tables == NULL);
+
+ eval_defaults(thd, cond);
+ if (eval_signal_informations(thd, cond))
+ DBUG_RETURN(result);
+
+ /* SIGNAL should not signal WARN_LEVEL_NOTE */
+ DBUG_ASSERT((cond->m_level == Sql_condition::WARN_LEVEL_WARN) ||
+ (cond->m_level == Sql_condition::WARN_LEVEL_ERROR));
+
+ Sql_condition *raised= NULL;
+ raised= thd->raise_condition(cond->get_sql_errno(),
+ cond->get_sqlstate(),
+ cond->get_level(),
+ cond->get_message_text());
+ if (raised)
+ raised->copy_opt_attributes(cond);
+
+ if (cond->m_level == Sql_condition::WARN_LEVEL_WARN)
+ {
+ my_ok(thd);
+ result= FALSE;
+ }
+
+ DBUG_RETURN(result);
+}
+
+bool Sql_cmd_signal::execute(THD *thd)
+{
+ bool result= TRUE;
+ Sql_condition cond(thd->mem_root);
+
+ DBUG_ENTER("Sql_cmd_signal::execute");
+
+ /*
+ WL#2110 SIGNAL specification says:
+
+ When SIGNAL is executed, it has five effects, in the following order:
+
+ (1) First, the diagnostics area is completely cleared. So if the
+ SIGNAL is in a DECLARE HANDLER then any pending errors or warnings
+ are gone. So is 'row count'.
+
+ This has roots in the SQL standard specification for SIGNAL.
+ */
+
+ thd->get_stmt_da()->reset_diagnostics_area();
+ thd->set_row_count_func(0);
+ thd->get_stmt_da()->clear_warning_info(thd->query_id);
+
+ result= raise_condition(thd, &cond);
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Execute RESIGNAL SQL-statement.
+
+ @param thd Thread context.
+
+ @return Error status
+ @retval true in case of error
+ @retval false on success
+*/
+
+bool Sql_cmd_resignal::execute(THD *thd)
+{
+ Diagnostics_area *da= thd->get_stmt_da();
+ const sp_rcontext::Sql_condition_info *signaled;
+ int result= TRUE;
+
+ DBUG_ENTER("Resignal_statement::execute");
+
+ // This is a way to force sql_conditions from the current Warning_info to be
+ // passed to the caller's Warning_info.
+ da->set_warning_info_id(thd->query_id);
+
+ if (! thd->spcont || ! (signaled= thd->spcont->raised_condition()))
+ {
+ thd->raise_error(ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER);
+ DBUG_RETURN(result);
+ }
+
+ Sql_condition signaled_err(thd->mem_root);
+ signaled_err.set(signaled->sql_errno,
+ signaled->sql_state,
+ signaled->level,
+ signaled->message);
+
+ if (m_cond)
+ {
+ query_cache_abort(&thd->query_cache_tls);
+
+ /* Keep handled conditions. */
+ da->unmark_sql_conditions_from_removal();
+
+ /* Check if the old condition still exists. */
+ if (da->has_sql_condition(signaled->message, strlen(signaled->message)))
+ {
+ /* Make room for the new RESIGNAL condition. */
+ da->reserve_space(thd, 1);
+ }
+ else
+ {
+ /* Make room for old condition + the new RESIGNAL condition. */
+ da->reserve_space(thd, 2);
+
+ da->push_warning(thd, &signaled_err);
+ }
+ }
+
+ /* RESIGNAL with signal_value */
+ result= raise_condition(thd, &signaled_err);
+
+ DBUG_RETURN(result);
+
+}
+
diff --git a/sql/sql_signal.h b/sql/sql_signal.h
new file mode 100644
index 00000000000..2a508eed5bf
--- /dev/null
+++ b/sql/sql_signal.h
@@ -0,0 +1,147 @@
+/* Copyright (c) 2008 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+#ifndef SQL_SIGNAL_H
+#define SQL_SIGNAL_H
+
+/**
+ Sql_cmd_common_signal represents the common properties of the
+ SIGNAL and RESIGNAL statements.
+*/
+class Sql_cmd_common_signal : public Sql_cmd
+{
+protected:
+ /**
+ Constructor.
+ @param cond the condition signaled if any, or NULL.
+ @param set collection of signal condition item assignments.
+ */
+ Sql_cmd_common_signal(const sp_condition_value *cond,
+ const Set_signal_information& set)
+ : Sql_cmd(),
+ m_cond(cond),
+ m_set_signal_information(set)
+ {}
+
+ virtual ~Sql_cmd_common_signal()
+ {}
+
+ /**
+ Assign the condition items 'MYSQL_ERRNO', 'level' and 'MESSAGE_TEXT'
+ default values of a condition.
+ @param cond the condition to update.
+ @param set_level_code true if 'level' and 'MYSQL_ERRNO' needs to be overwritten
+ @param level the level to assign
+ @param sqlcode the sql code to assign
+ */
+ static void assign_defaults(Sql_condition *cond,
+ bool set_level_code,
+ Sql_condition::enum_warning_level level,
+ int sqlcode);
+
+ /**
+ Evaluate the condition items 'SQLSTATE', 'MYSQL_ERRNO', 'level' and 'MESSAGE_TEXT'
+ default values for this statement.
+ @param thd the current thread.
+ @param cond the condition to update.
+ */
+ void eval_defaults(THD *thd, Sql_condition *cond);
+
+ /**
+ Evaluate each signal condition items for this statement.
+ @param thd the current thread.
+ @param cond the condition to update.
+ @return 0 on success.
+ */
+ int eval_signal_informations(THD *thd, Sql_condition *cond);
+
+ /**
+ Raise a SQL condition.
+ @param thd the current thread.
+ @param cond the condition to raise.
+ @return false on success.
+ */
+ bool raise_condition(THD *thd, Sql_condition *cond);
+
+ /**
+ The condition to signal or resignal.
+ This member is optional and can be NULL (RESIGNAL).
+ */
+ const sp_condition_value *m_cond;
+
+ /**
+ Collection of 'SET item = value' assignments in the
+ SIGNAL/RESIGNAL statement.
+ */
+ Set_signal_information m_set_signal_information;
+};
+
+/**
+ Sql_cmd_signal represents a SIGNAL statement.
+*/
+class Sql_cmd_signal : public Sql_cmd_common_signal
+{
+public:
+ /**
+ Constructor, used to represent a SIGNAL statement.
+ @param cond the SQL condition to signal (required).
+ @param set the collection of signal informations to signal.
+ */
+ Sql_cmd_signal(const sp_condition_value *cond,
+ const Set_signal_information& set)
+ : Sql_cmd_common_signal(cond, set)
+ {}
+
+ virtual ~Sql_cmd_signal()
+ {}
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_SIGNAL;
+ }
+
+ virtual bool execute(THD *thd);
+};
+
+/**
+ Sql_cmd_resignal represents a RESIGNAL statement.
+*/
+class Sql_cmd_resignal : public Sql_cmd_common_signal
+{
+public:
+ /**
+ Constructor, used to represent a RESIGNAL statement.
+ @param cond the SQL condition to resignal (optional, may be NULL).
+ @param set the collection of signal informations to resignal.
+ */
+ Sql_cmd_resignal(const sp_condition_value *cond,
+ const Set_signal_information& set)
+ : Sql_cmd_common_signal(cond, set)
+ {}
+
+ virtual ~Sql_cmd_resignal()
+ {}
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_RESIGNAL;
+ }
+
+ virtual bool execute(THD *thd);
+};
+
+#endif
+
diff --git a/sql/sql_sort.h b/sql/sql_sort.h
new file mode 100644
index 00000000000..d30ddfb6eec
--- /dev/null
+++ b/sql/sql_sort.h
@@ -0,0 +1,115 @@
+#ifndef SQL_SORT_INCLUDED
+#define SQL_SORT_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 "m_string.h" /* memset */
+#include "my_global.h" /* uchar */
+#include "my_base.h" /* ha_rows */
+#include "my_sys.h" /* qsort2_cmp */
+#include "queues.h"
+
+typedef struct st_buffpek BUFFPEK;
+typedef struct st_sort_field SORT_FIELD;
+
+class Field;
+struct TABLE;
+
+/* Defines used by filesort and uniques */
+
+#define MERGEBUFF 7
+#define MERGEBUFF2 15
+
+/*
+ The structure SORT_ADDON_FIELD describes a fixed layout
+ for field values appended to sorted values in records to be sorted
+ in the sort buffer.
+ Only fixed layout is supported now.
+ Null bit maps for the appended values is placed before the values
+ themselves. Offsets are from the last sorted field, that is from the
+ record referefence, which is still last component of sorted records.
+ It is preserved for backward compatiblility.
+ The structure is used tp store values of the additional fields
+ in the sort buffer. It is used also when these values are read
+ from a temporary file/buffer. As the reading procedures are beyond the
+ scope of the 'filesort' code the values have to be retrieved via
+ the callback function 'unpack_addon_fields'.
+*/
+
+typedef struct st_sort_addon_field
+{
+ /* Sort addon packed field */
+ Field *field; /* Original field */
+ uint offset; /* Offset from the last sorted field */
+ uint null_offset; /* Offset to to null bit from the last sorted field */
+ uint length; /* Length in the sort buffer */
+ uint8 null_bit; /* Null bit mask for the field */
+} SORT_ADDON_FIELD;
+
+struct BUFFPEK_COMPARE_CONTEXT
+{
+ qsort_cmp2 key_compare;
+ void *key_compare_arg;
+};
+
+
+class Sort_param {
+public:
+ uint rec_length; // Length of sorted records.
+ uint sort_length; // Length of sorted columns.
+ uint ref_length; // Length of record ref.
+ uint addon_length; // Length of added packed fields.
+ uint res_length; // Length of records in final sorted file/buffer.
+ uint max_keys_per_buffer; // Max keys / buffer.
+ uint min_dupl_count;
+ ha_rows max_rows; // Select limit, or HA_POS_ERROR if unlimited.
+ ha_rows examined_rows; // Number of examined rows.
+ TABLE *sort_form; // For quicker make_sortkey.
+ SORT_FIELD *local_sortorder;
+ SORT_FIELD *end;
+ SORT_ADDON_FIELD *addon_field; // Descriptors for companion fields.
+ uchar *unique_buff;
+ bool not_killable;
+ char* tmp_buffer;
+ // The fields below are used only by Unique class.
+ qsort2_cmp compare;
+ BUFFPEK_COMPARE_CONTEXT cmp_context;
+
+ Sort_param()
+ {
+ memset(this, 0, sizeof(*this));
+ }
+ void init_for_filesort(uint sortlen, TABLE *table,
+ ulong max_length_for_sort_data,
+ ha_rows maxrows, bool sort_positions);
+};
+
+
+int merge_many_buff(Sort_param *param, uchar *sort_buffer,
+ BUFFPEK *buffpek,
+ uint *maxbuffer, IO_CACHE *t_file);
+uint read_to_buffer(IO_CACHE *fromfile,BUFFPEK *buffpek,
+ uint sort_length);
+int merge_buffers(Sort_param *param,IO_CACHE *from_file,
+ IO_CACHE *to_file, uchar *sort_buffer,
+ BUFFPEK *lastbuff,BUFFPEK *Fb,
+ BUFFPEK *Tb,int flag);
+int merge_index(Sort_param *param, uchar *sort_buffer,
+ BUFFPEK *buffpek, uint maxbuffer,
+ IO_CACHE *tempfile, IO_CACHE *outfile);
+void reuse_freed_buff(QUEUE *queue, BUFFPEK *reuse, uint key_length);
+
+#endif /* SQL_SORT_INCLUDED */
diff --git a/sql/sql_state.c b/sql/sql_state.c
new file mode 100644
index 00000000000..2bfd61d6696
--- /dev/null
+++ b/sql/sql_state.c
@@ -0,0 +1,55 @@
+/* Copyright (C) 2000-2003 MySQL AB
+ Use is subject to license terms
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/* Functions to map mysqld errno to sql_state */
+
+#include <my_global.h>
+#include <mysqld_error.h>
+#include <my_base.h>
+
+struct st_map_errno_to_sqlstate
+{
+ uint mysql_errno;
+ const char *odbc_state;
+ const char *jdbc_state;
+};
+
+struct st_map_errno_to_sqlstate sqlstate_map[]=
+{
+#include <handler_state.h>
+#include <sql_state.h>
+};
+
+const char *mysql_errno_to_sqlstate(uint mysql_errno)
+{
+ uint first=0, end= array_elements(sqlstate_map)-1;
+ struct st_map_errno_to_sqlstate *map;
+
+ /* Do binary search in the sorted array */
+ while (first != end)
+ {
+ uint mid= (first+end)/2;
+ map= sqlstate_map+mid;
+ if (map->mysql_errno < mysql_errno)
+ first= mid+1;
+ else
+ end= mid;
+ }
+ map= sqlstate_map+first;
+ if (map->mysql_errno == mysql_errno)
+ return map->odbc_state;
+ return "HY000"; /* General error */
+}
diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc
new file mode 100644
index 00000000000..4ce1f3ec22a
--- /dev/null
+++ b/sql/sql_statistics.cc
@@ -0,0 +1,3718 @@
+/* Copyright (C) 2009 MySQL AB
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @file
+
+ @brief
+ functions to update persitent statistical tables and to read from them
+
+ @defgroup Query_Optimizer Query Optimizer
+ @{
+*/
+
+#include <my_global.h>
+#include "sql_base.h"
+#include "key.h"
+#include "sql_statistics.h"
+#include "opt_range.h"
+#include "my_atomic.h"
+
+/*
+ The system variable 'use_stat_tables' can take one of the
+ following values:
+ "never", "complementary", "preferably".
+ If the values of the variable 'use_stat_tables' is set to
+ "never then any statistical data from the persistent statistical tables
+ is ignored by the optimizer.
+ If the value of the variable 'use_stat_tables' is set to
+ "complementary" then a particular statistical characteristic is used
+ by the optimizer only if the database engine does not provide similar
+ statistics. For example, 'nulls_ratio' for table columns currently
+ are not provided by any engine. So optimizer uses this statistical data
+ from the statistical tables. At the same time it does not use
+ 'avg_frequency' for any index prefix from the statistical tables since
+ the a similar statistical characteristic 'records_per_key' can be
+ requested from the database engine.
+ If the value the variable 'use_stat_tables' is set to
+ "preferably" the optimizer uses a particular statistical data only if
+ it can't be found in the statistical data.
+ If an ANALYZE command is executed then it results in collecting
+ statistical data for the tables specified by the command and storing
+ the collected statistics in the persistent statistical tables only
+ when the value of the variable 'use_stat_tables' is not
+ equal to "never".
+*/
+
+/* Currently there are only 3 persistent statistical tables */
+static const uint STATISTICS_TABLES= 3;
+
+/*
+ The names of the statistical tables in this array must correspond the
+ definitions of the tables in the file ../scripts/mysql_system_tables.sql
+*/
+static const LEX_STRING stat_table_name[STATISTICS_TABLES]=
+{
+ { C_STRING_WITH_LEN("table_stats") },
+ { C_STRING_WITH_LEN("column_stats") },
+ { C_STRING_WITH_LEN("index_stats") }
+};
+
+/* Name of database to which the statistical tables belong */
+static const LEX_STRING stat_tables_db_name= { C_STRING_WITH_LEN("mysql") };
+
+
+/**
+ @details
+ The function builds a list of TABLE_LIST elements for system statistical
+ tables using array of TABLE_LIST passed as a parameter.
+ The lock type of each element is set to TL_READ if for_write = FALSE,
+ otherwise it is set to TL_WRITE.
+*/
+
+static
+inline void init_table_list_for_stat_tables(TABLE_LIST *tables, bool for_write)
+{
+ uint i;
+
+ memset((char *) &tables[0], 0, sizeof(TABLE_LIST) * STATISTICS_TABLES);
+
+ for (i= 0; i < STATISTICS_TABLES; i++)
+ {
+ tables[i].db= stat_tables_db_name.str;
+ tables[i].db_length= stat_tables_db_name.length;
+ tables[i].alias= tables[i].table_name= stat_table_name[i].str;
+ tables[i].table_name_length= stat_table_name[i].length;
+ tables[i].lock_type= for_write ? TL_WRITE : TL_READ;
+ if (i < STATISTICS_TABLES - 1)
+ tables[i].next_global= tables[i].next_local=
+ tables[i].next_name_resolution_table= &tables[i+1];
+ if (i != 0)
+ tables[i].prev_global= &tables[i-1].next_global;
+ }
+}
+
+
+/**
+ @details
+ The function builds a TABLE_LIST containing only one element 'tbl' for
+ the statistical table called 'stat_tab_name'.
+ The lock type of the element is set to TL_READ if for_write = FALSE,
+ otherwise it is set to TL_WRITE.
+*/
+
+static
+inline void init_table_list_for_single_stat_table(TABLE_LIST *tbl,
+ const LEX_STRING *stat_tab_name,
+ bool for_write)
+{
+ memset((char *) tbl, 0, sizeof(TABLE_LIST));
+
+ tbl->db= stat_tables_db_name.str;
+ tbl->db_length= stat_tables_db_name.length;
+ tbl->alias= tbl->table_name= stat_tab_name->str;
+ tbl->table_name_length= stat_tab_name->length;
+ tbl->lock_type= for_write ? TL_WRITE : TL_READ;
+}
+
+
+/**
+ @brief
+ Open all statistical tables and lock them
+*/
+
+static
+inline int open_stat_tables(THD *thd, TABLE_LIST *tables,
+ Open_tables_backup *backup,
+ bool for_write)
+{
+ init_table_list_for_stat_tables(tables, for_write);
+ init_mdl_requests(tables);
+ return open_system_tables_for_read(thd, tables, backup);
+}
+
+
+/**
+ @brief
+ Open a statistical table and lock it
+*/
+static
+inline int open_single_stat_table(THD *thd, TABLE_LIST *table,
+ const LEX_STRING *stat_tab_name,
+ Open_tables_backup *backup,
+ bool for_write)
+{
+ init_table_list_for_single_stat_table(table, stat_tab_name, for_write);
+ init_mdl_requests(table);
+ return open_system_tables_for_read(thd, table, backup);
+}
+
+
+/*
+ The class Column_statistics_collected is a helper class used to collect
+ statistics on a table column. The class is derived directly from
+ the class Column_statistics, and, additionally to the fields of the
+ latter, it contains the fields to accumulate the results of aggregation
+ for the number of nulls in the column and for the size of the column
+ values. There is also a container for distinct column values used
+ to calculate the average number of records per distinct column value.
+*/
+
+class Column_statistics_collected :public Column_statistics
+{
+
+private:
+ Field *column; /* The column to collect statistics on */
+ ha_rows nulls; /* To accumulate the number of nulls in the column */
+ ulonglong column_total_length; /* To accumulate the size of column values */
+ Count_distinct_field *count_distinct; /* The container for distinct
+ column values */
+
+ bool is_single_pk_col; /* TRUE <-> the only column of the primary key */
+
+public:
+
+ inline void init(THD *thd, Field * table_field);
+ inline bool add(ha_rows rowno);
+ inline void finish(ha_rows rows);
+ inline void cleanup();
+};
+
+
+/**
+ Stat_table is the base class for classes Table_stat, Column_stat and
+ Index_stat. The methods of these classes allow us to read statistical
+ data from statistical tables, write collected statistical data into
+ statistical tables and update statistical data in these tables
+ as well as update access fields belonging to the primary key and
+ delete records by prefixes of the primary key.
+ Objects of the classes Table_stat, Column_stat and Index stat are used
+ for reading/writing statistics from/into persistent tables table_stats,
+ column_stats and index_stats correspondingly. These tables are stored in
+ the system database 'mysql'.
+
+ Statistics is read and written always for a given database table t. When
+ an object of any of these classes is created a pointer to the TABLE
+ structure for this database table is passed as a parameter to the constructor
+ of the object. The other parameter is a pointer to the TABLE structure for
+ the corresponding statistical table st. So construction of an object to
+ read/write statistical data on table t from/into statistical table st
+ requires both table t and st to be opened.
+ In some cases the TABLE structure for table t may be undefined. Then
+ the objects of the classes Table_stat, Column_stat and Index stat are
+ created by the alternative constructor that require only the name
+ of the table t and the name of the database it belongs to. Currently the
+ alternative constructors are used only in the cases when some records
+ belonging to the table are to be deleted, or its keys are to be updated
+
+ Reading/writing statistical data from/into a statistical table is always
+ performed by a key. At the moment there is only one key defined for each
+ statistical table and this key is primary.
+ The primary key for the table table_stats is built as (db_name, table_name).
+ The primary key for the table column_stats is built as (db_name, table_name,
+ column_name).
+ The primary key for the table index_stats is built as (db_name, table_name,
+ index_name, prefix_arity).
+
+ Reading statistical data from a statistical table is performed by the
+ following pattern. First a table dependent method sets the values of the
+ the fields that comprise the lookup key. Then an implementation of the
+ method get_stat_values() declared in Stat_table as a pure virtual method
+ finds the row from the statistical table by the set key. If the row is
+ found the values of statistical fields are read from this row and are
+ distributed in the internal structures.
+
+ Let's assume the statistical data is read for table t from database db.
+
+ When statistical data is searched in the table table_stats first
+ Table_stat::set_key_fields() should set the fields of db_name and
+ table_name. Then get_stat_values looks for a row by the set key value,
+ and, if the row is found, reads the value from the column
+ table_stats.cardinality into the field read_stat.cardinality of the TABLE
+ structure for table t and sets the value of read_stat.cardinality_is_null
+ from this structure to FALSE. If the value of the 'cardinality' column
+ in the row is null or if no row is found read_stat.cardinality_is_null
+ is set to TRUE.
+
+ When statistical data is searched in the table column_stats first
+ Column_stat::set_key_fields() should set the fields of db_name, table_name
+ and column_name with column_name taken out of the only parameter f of the
+ Field* type passed to this method. After this get_stat_values looks
+ for a row by the set key value. If the row is found the values of statistical
+ data columns min_value, max_value, nulls_ratio, avg_length, avg_frequency,
+ hist_size, hist_type, histogram are read into internal structures. Values
+ of nulls_ratio, avg_length, avg_frequency, hist_size, hist_type, histogram
+ are read into the corresponding fields of the read_stat structure from
+ the Field object f, while values from min_value and max_value are copied
+ into the min_value and max_value record buffers attached to the TABLE
+ structure for table t.
+ If the value of a statistical column in the found row is null, then the
+ corresponding flag in the f->read_stat.column_stat_nulls bitmap is set off.
+ Otherwise the flag is set on. If no row is found for the column the all flags
+ in f->column_stat_nulls are set off.
+
+ When statistical data is searched in the table index_stats first
+ Index_stat::set_key_fields() has to be called to set the fields of db_name,
+ table_name, index_name and prefix_arity. The value of index_name is extracted
+ from the first parameter key_info of the KEY* type passed to the method.
+ This parameter specifies the index of interest idx. The second parameter
+ passed to the method specifies the arity k of the index prefix for which
+ statistical data is to be read. E.g. if the index idx consists of 3
+ components (p1,p2,p3) the table index_stats usually will contain 3 rows for
+ this index: the first - for the prefix (p1), the second - for the prefix
+ (p1,p2), and the third - for the the prefix (p1,p2,p3). After the key fields
+ has been set a call of get_stat_value looks for a row by the set key value.
+ If the row is found and the value of the avg_frequency column is not null
+ then this value is assigned to key_info->read_stat.avg_frequency[k].
+ Otherwise 0 is assigned to this element.
+
+ The method Stat_table::update_stat is used to write statistical data
+ collected in the internal structures into a statistical table st.
+ It is assumed that before any invocation of this method a call of the
+ function st.set_key_fields has set the values of the primary key fields
+ that serve to locate the row from the statistical table st where the
+ the collected statistical data from internal structures are to be written
+ to. The statistical data is written from the counterparts of the
+ statistical fields of internal structures into which it would be read
+ by the functions get_stat_values. The counterpart fields are used
+ only when statistics is collected
+ When updating/inserting a row from the statistical table st the method
+ Stat_table::update_stat calls the implementation of the pure virtual
+ method store_field_values to transfer statistical data from the fields
+ of internal structures to the fields of record buffer used for updates
+ of the statistical table st.
+*/
+
+class Stat_table
+{
+
+private:
+
+ /* Handler used for the retrieval of the statistical table stat_table */
+ handler *stat_file;
+
+ uint stat_key_length; /* Length of the key to access stat_table */
+ uchar *record[2]; /* Record buffers used to access/update stat_table */
+ uint stat_key_idx; /* The number of the key to access stat_table */
+
+ /* This is a helper function used only by the Stat_table constructors */
+ void common_init_stat_table()
+ {
+ stat_file= stat_table->file;
+ /* Currently any statistical table has only one key */
+ stat_key_idx= 0;
+ stat_key_info= &stat_table->key_info[stat_key_idx];
+ stat_key_length= stat_key_info->key_length;
+ record[0]= stat_table->record[0];
+ record[1]= stat_table->record[1];
+ }
+
+protected:
+
+ /* Statistical table to read statistics from or to update/delete */
+ TABLE *stat_table;
+ KEY *stat_key_info; /* Structure for the index to access stat_table */
+
+ /* Table for which statistical data is read / updated */
+ TABLE *table;
+ TABLE_SHARE *table_share; /* Table share for 'table */
+ LEX_STRING *db_name; /* Name of the database containing 'table' */
+ LEX_STRING *table_name; /* Name of the table 'table' */
+
+ void store_record_for_update()
+ {
+ store_record(stat_table, record[1]);
+ }
+
+ void store_record_for_lookup()
+ {
+ DBUG_ASSERT(record[0] == stat_table->record[0]);
+ }
+
+ bool update_record()
+ {
+ int err;
+ if ((err= stat_file->ha_update_row(record[1], record[0])) &&
+ err != HA_ERR_RECORD_IS_THE_SAME)
+ return TRUE;
+ /* Make change permanent and avoid 'table is marked as crashed' errors */
+ stat_file->extra(HA_EXTRA_FLUSH);
+ return FALSE;
+ }
+
+public:
+
+
+ /**
+ @details
+ This constructor has to be called by any constructor of the derived
+ classes. The constructor 'tunes' the private and protected members of
+ the constructed object to the statistical table 'stat_table' with the
+ statistical data of our interest and to the table 'tab' for which this
+ statistics has been collected.
+ */
+
+ Stat_table(TABLE *stat, TABLE *tab)
+ :stat_table(stat), table(tab)
+ {
+ table_share= tab->s;
+ common_init_stat_table();
+ db_name= &table_share->db;
+ table_name= &table_share->table_name;
+ }
+
+
+ /**
+ @details
+ This constructor has to be called by any constructor of the derived
+ classes. The constructor 'tunes' the private and protected members of
+ the constructed object to the statistical table 'stat_table' with the
+ statistical data of our interest and to the table t for which this
+ statistics has been collected. The table t is uniquely specified
+ by the database name 'db' and the table name 'tab'.
+ */
+
+ Stat_table(TABLE *stat, LEX_STRING *db, LEX_STRING *tab)
+ :stat_table(stat), table_share(NULL)
+ {
+ common_init_stat_table();
+ db_name= db;
+ table_name= tab;
+ }
+
+
+ virtual ~Stat_table() {}
+
+ /**
+ @brief
+ Store the given values of fields for database name and table name
+
+ @details
+ This is a purely virtual method.
+ The implementation for any derived class shall store the given
+ values of the database name and table name in the corresponding
+ fields of stat_table.
+
+ @note
+ The method is called by the update_table_name_key_parts function.
+ */
+
+ virtual void change_full_table_name(LEX_STRING *db, LEX_STRING *tab)= 0;
+
+
+ /**
+ @brief
+ Store statistical data into fields of the statistical table
+
+ @details
+ This is a purely virtual method.
+ The implementation for any derived class shall put the appropriate
+ statistical data into the corresponding fields of stat_table.
+
+ @note
+ The method is called by the update_stat function.
+ */
+
+ virtual void store_stat_fields()= 0;
+
+
+ /**
+ @brief
+ Read statistical data from fields of the statistical table
+
+ @details
+ This is a purely virtual method.
+ The implementation for any derived read shall read the appropriate
+ statistical data from the corresponding fields of stat_table.
+ */
+
+ virtual void get_stat_values()= 0;
+
+
+ /**
+ @brief
+ Find a record in the statistical table by a primary key
+
+ @details
+ The function looks for a record in stat_table by its primary key.
+ It assumes that the key fields have been already stored in the record
+ buffer of stat_table.
+
+ @retval
+ FALSE the record is not found
+ @retval
+ TRUE the record is found
+ */
+
+ bool find_stat()
+ {
+ uchar key[MAX_KEY_LENGTH];
+ key_copy(key, record[0], stat_key_info, stat_key_length);
+ return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, key,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT);
+ }
+
+
+ /**
+ @brief
+ Find a record in the statistical table by a key prefix value
+
+ @details
+ The function looks for a record in stat_table by the key value consisting
+ of 'prefix_parts' major components for the primary index.
+ It assumes that the key prefix fields have been already stored in the record
+ buffer of stat_table.
+
+ @retval
+ FALSE the record is not found
+ @retval
+ TRUE the record is found
+ */
+
+ bool find_next_stat_for_prefix(uint prefix_parts)
+ {
+ uchar key[MAX_KEY_LENGTH];
+ uint prefix_key_length= 0;
+ for (uint i= 0; i < prefix_parts; i++)
+ prefix_key_length+= stat_key_info->key_part[i].store_length;
+ key_copy(key, record[0], stat_key_info, prefix_key_length);
+ key_part_map prefix_map= (key_part_map) ((1 << prefix_parts) - 1);
+ return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, key,
+ prefix_map, HA_READ_KEY_EXACT);
+ }
+
+
+ /**
+ @brief
+ Update/insert a record in the statistical table with new statistics
+
+ @details
+ The function first looks for a record by its primary key in the statistical
+ table stat_table. If the record is found the function updates statistical
+ fields of the records. The data for these fields are taken from internal
+ structures containing info on the table 'table'. If the record is not
+ found the function inserts a new record with the primary key set to the
+ search key and the statistical data taken from the internal structures.
+ The function assumes that the key fields have been already stored in
+ the record buffer of stat_table.
+
+ @retval
+ FALSE success with the update/insert of the record
+ @retval
+ TRUE failure with the update/insert of the record
+
+ @note
+ The function calls the virtual method store_stat_fields to populate the
+ statistical fields of the updated/inserted row with new statistics.
+ */
+
+ bool update_stat()
+ {
+ if (find_stat())
+ {
+ store_record_for_update();
+ store_stat_fields();
+ return update_record();
+ }
+ else
+ {
+ int err;
+ store_stat_fields();
+ if ((err= stat_file->ha_write_row(record[0])))
+ return TRUE;
+ /* Make change permanent and avoid 'table is marked as crashed' errors */
+ stat_file->extra(HA_EXTRA_FLUSH);
+ }
+ return FALSE;
+ }
+
+
+ /**
+ @brief
+ Update the table name fields in the current record of stat_table
+
+ @details
+ The function updates the fields containing database name and table name
+ for the last found record in the statistical table stat_table.
+ The corresponding names for update is taken from the parameters
+ db and tab.
+
+ @retval
+ FALSE success with the update of the record
+ @retval
+ TRUE failure with the update of the record
+
+ @note
+ The function calls the virtual method change_full_table_name
+ to store the new names in the record buffer used for updates.
+ */
+
+ bool update_table_name_key_parts(LEX_STRING *db, LEX_STRING *tab)
+ {
+ store_record_for_update();
+ change_full_table_name(db, tab);
+ bool rc= update_record();
+ store_record_for_lookup();
+ return rc;
+ }
+
+
+ /**
+ @brief
+ Delete the current record of the statistical table stat_table
+
+ @details
+ The function deletes the last found record from the statistical
+ table stat_table.
+
+ @retval
+ FALSE success with the deletion of the record
+ @retval
+ TRUE failure with the deletion of the record
+ */
+
+ bool delete_stat()
+ {
+ int err;
+ if ((err= stat_file->ha_delete_row(record[0])))
+ return TRUE;
+ /* Make change permanent and avoid 'table is marked as crashed' errors */
+ stat_file->extra(HA_EXTRA_FLUSH);
+ return FALSE;
+ }
+};
+
+
+/*
+ An object of the class Table_stat is created to read statistical
+ data on tables from the statistical table table_stats, to update
+ table_stats with such statistical data, or to update columns
+ of the primary key, or to delete the record by its primary key or
+ its prefix.
+ Rows from the statistical table are read and updated always by
+ primary key.
+*/
+
+class Table_stat: public Stat_table
+{
+
+private:
+
+ Field *db_name_field; /* Field for the column table_stats.db_name */
+ Field *table_name_field; /* Field for the column table_stats.table_name */
+
+ void common_init_table_stat()
+ {
+ db_name_field= stat_table->field[TABLE_STAT_DB_NAME];
+ table_name_field= stat_table->field[TABLE_STAT_TABLE_NAME];
+ }
+
+ void change_full_table_name(LEX_STRING *db, LEX_STRING *tab)
+ {
+ db_name_field->store(db->str, db->length, system_charset_info);
+ table_name_field->store(tab->str, tab->length, system_charset_info);
+ }
+
+public:
+
+ /**
+ @details
+ The constructor 'tunes' the private and protected members of the
+ constructed object for the statistical table table_stats to read/update
+ statistics on table 'tab'. The TABLE structure for the table table_stat
+ must be passed as a value for the parameter 'stat'.
+ */
+
+ Table_stat(TABLE *stat, TABLE *tab) :Stat_table(stat, tab)
+ {
+ common_init_table_stat();
+ }
+
+
+ /**
+ @details
+ The constructor 'tunes' the private and protected members of the
+ object constructed for the statistical table table_stat for
+ the future updates/deletes of the record concerning the table 'tab'
+ from the database 'db'.
+ */
+
+ Table_stat(TABLE *stat, LEX_STRING *db, LEX_STRING *tab)
+ :Stat_table(stat, db, tab)
+ {
+ common_init_table_stat();
+ }
+
+
+ /**
+ @brief
+ Set the key fields for the statistical table table_stat
+
+ @details
+ The function sets the values of the fields db_name and table_name
+ in the record buffer for the statistical table table_stat.
+ These fields comprise the primary key for the table.
+
+ @note
+ The function is supposed to be called before any use of the
+ method find_stat for an object of the Table_stat class.
+ */
+
+ void set_key_fields()
+ {
+ db_name_field->store(db_name->str, db_name->length, system_charset_info);
+ table_name_field->store(table_name->str, table_name->length,
+ system_charset_info);
+ }
+
+
+ /**
+ @brief
+ Store statistical data into statistical fields of table_stat
+
+ @details
+ This implementation of a purely virtual method sets the value of the
+ column 'cardinality' of the statistical table table_stat according to
+ the value of the flag write_stat.cardinality_is_null and the value of
+ the field write_stat.cardinality' from the TABLE structure for 'table'.
+ */
+
+ void store_stat_fields()
+ {
+ Field *stat_field= stat_table->field[TABLE_STAT_CARDINALITY];
+ if (table->collected_stats->cardinality_is_null)
+ stat_field->set_null();
+ else
+ {
+ stat_field->set_notnull();
+ stat_field->store(table->collected_stats->cardinality);
+ }
+ }
+
+
+ /**
+ @brief
+ Read statistical data from statistical fields of table_stat
+
+ @details
+ This implementation of a purely virtual method first looks for a record
+ the statistical table table_stat by its primary key set the record
+ buffer with the help of Table_stat::set_key_fields. Then, if the row is
+ found the function reads the value of the column 'cardinality' of the table
+ table_stat and sets the value of the flag read_stat.cardinality_is_null
+ and the value of the field read_stat.cardinality' from the TABLE structure
+ for 'table' accordingly.
+ */
+
+ void get_stat_values()
+ {
+ Table_statistics *read_stats= table_share->stats_cb.table_stats;
+ read_stats->cardinality_is_null= TRUE;
+ read_stats->cardinality= 0;
+ if (find_stat())
+ {
+ Field *stat_field= stat_table->field[TABLE_STAT_CARDINALITY];
+ if (!stat_field->is_null())
+ {
+ read_stats->cardinality_is_null= FALSE;
+ read_stats->cardinality= stat_field->val_int();
+ }
+ }
+ }
+
+};
+
+
+/*
+ An object of the class Column_stat is created to read statistical data
+ on table columns from the statistical table column_stats, to update
+ column_stats with such statistical data, or to update columns
+ of the primary key, or to delete the record by its primary key or
+ its prefix.
+ Rows from the statistical table are read and updated always by
+ primary key.
+*/
+
+class Column_stat: public Stat_table
+{
+
+private:
+
+ Field *db_name_field; /* Field for the column column_stats.db_name */
+ Field *table_name_field; /* Field for the column column_stats.table_name */
+ Field *column_name_field; /* Field for the column column_stats.column_name */
+
+ Field *table_field; /* Field from 'table' to read /update statistics on */
+
+ void common_init_column_stat_table()
+ {
+ db_name_field= stat_table->field[COLUMN_STAT_DB_NAME];
+ table_name_field= stat_table->field[COLUMN_STAT_TABLE_NAME];
+ column_name_field= stat_table->field[COLUMN_STAT_COLUMN_NAME];
+ }
+
+ void change_full_table_name(LEX_STRING *db, LEX_STRING *tab)
+ {
+ db_name_field->store(db->str, db->length, system_charset_info);
+ table_name_field->store(tab->str, tab->length, system_charset_info);
+ }
+
+public:
+
+ /**
+ @details
+ The constructor 'tunes' the private and protected members of the
+ constructed object for the statistical table column_stats to read/update
+ statistics on fields of the table 'tab'. The TABLE structure for the table
+ column_stats must be passed as a value for the parameter 'stat'.
+ */
+
+ Column_stat(TABLE *stat, TABLE *tab) :Stat_table(stat, tab)
+ {
+ common_init_column_stat_table();
+ }
+
+
+ /**
+ @details
+ The constructor 'tunes' the private and protected members of the
+ object constructed for the statistical table column_stats for
+ the future updates/deletes of the record concerning the table 'tab'
+ from the database 'db'.
+ */
+
+ Column_stat(TABLE *stat, LEX_STRING *db, LEX_STRING *tab)
+ :Stat_table(stat, db, tab)
+ {
+ common_init_column_stat_table();
+ }
+
+ /**
+ @brief
+ Set table name fields for the statistical table column_stats
+
+ @details
+ The function stores the values of the fields db_name and table_name
+ of the statistical table column_stats in the record buffer.
+ */
+
+ void set_full_table_name()
+ {
+ db_name_field->store(db_name->str, db_name->length, system_charset_info);
+ table_name_field->store(table_name->str, table_name->length,
+ system_charset_info);
+ }
+
+
+ /**
+ @brief
+ Set the key fields for the statistical table column_stats
+
+ @param
+ col Field for the 'table' column to read/update statistics on
+
+ @details
+ The function stores the values of the fields db_name, table_name and
+ column_name in the record buffer for the statistical table column_stats.
+ These fields comprise the primary key for the table.
+ It also sets table_field to the passed parameter.
+
+ @note
+ The function is supposed to be called before any use of the
+ method find_stat for an object of the Column_stat class.
+ */
+
+ void set_key_fields(Field *col)
+ {
+ set_full_table_name();
+ const char *column_name= col->field_name;
+ column_name_field->store(column_name, strlen(column_name),
+ system_charset_info);
+ table_field= col;
+ }
+
+
+ /**
+ @brief
+ Update the table name fields in the current record of stat_table
+
+ @details
+ The function updates the primary key fields containing database name,
+ table name, and column name for the last found record in the statistical
+ table column_stats.
+
+ @retval
+ FALSE success with the update of the record
+ @retval
+ TRUE failure with the update of the record
+ */
+
+ bool update_column_key_part(const char *col)
+ {
+ store_record_for_update();
+ set_full_table_name();
+ column_name_field->store(col, strlen(col), system_charset_info);
+ bool rc= update_record();
+ store_record_for_lookup();
+ return rc;
+ }
+
+
+ /**
+ @brief
+ Store statistical data into statistical fields of column_stats
+
+ @details
+ This implementation of a purely virtual method sets the value of the
+ columns 'min_value', 'max_value', 'nulls_ratio', 'avg_length',
+ 'avg_frequency', 'hist_size', 'hist_type' and 'histogram' of the
+ stistical table columns_stat according to the contents of the bitmap
+ write_stat.column_stat_nulls and the values of the fields min_value,
+ max_value, nulls_ratio, avg_length, avg_frequency, hist_size, hist_type
+ and histogram of the structure write_stat from the Field structure
+ for the field 'table_field'.
+ The value of the k-th column in the table columns_stat is set to NULL
+ if the k-th bit in the bitmap 'column_stat_nulls' is set to 1.
+
+ @note
+ A value from the field min_value/max_value is always converted
+ into a utf8 string. If the length of the column 'min_value'/'max_value'
+ is less than the length of the string the string is trimmed to fit the
+ length of the column.
+ */
+
+ void store_stat_fields()
+ {
+ char buff[MAX_FIELD_WIDTH];
+ String val(buff, sizeof(buff), &my_charset_utf8_bin);
+
+ for (uint i= COLUMN_STAT_MIN_VALUE; i <= COLUMN_STAT_HISTOGRAM; i++)
+ {
+ Field *stat_field= stat_table->field[i];
+ if (table_field->collected_stats->is_null(i))
+ stat_field->set_null();
+ else
+ {
+ stat_field->set_notnull();
+ switch (i) {
+ case COLUMN_STAT_MIN_VALUE:
+ if (table_field->type() == MYSQL_TYPE_BIT)
+ stat_field->store(table_field->collected_stats->min_value->val_int());
+ else
+ {
+ table_field->collected_stats->min_value->val_str(&val);
+ stat_field->store(val.ptr(), val.length(), &my_charset_utf8_bin);
+ }
+ break;
+ case COLUMN_STAT_MAX_VALUE:
+ if (table_field->type() == MYSQL_TYPE_BIT)
+ stat_field->store(table_field->collected_stats->max_value->val_int());
+ else
+ {
+ table_field->collected_stats->max_value->val_str(&val);
+ stat_field->store(val.ptr(), val.length(), &my_charset_utf8_bin);
+ }
+ break;
+ case COLUMN_STAT_NULLS_RATIO:
+ stat_field->store(table_field->collected_stats->get_nulls_ratio());
+ break;
+ case COLUMN_STAT_AVG_LENGTH:
+ stat_field->store(table_field->collected_stats->get_avg_length());
+ break;
+ case COLUMN_STAT_AVG_FREQUENCY:
+ stat_field->store(table_field->collected_stats->get_avg_frequency());
+ break;
+ case COLUMN_STAT_HIST_SIZE:
+ stat_field->store(table_field->collected_stats->histogram.get_size());
+ break;
+ case COLUMN_STAT_HIST_TYPE:
+ stat_field->store(table_field->collected_stats->histogram.get_type() +
+ 1);
+ break;
+ case COLUMN_STAT_HISTOGRAM:
+ const char * col_histogram=
+ (const char *) (table_field->collected_stats->histogram.get_values());
+ stat_field->store(col_histogram,
+ table_field->collected_stats->histogram.get_size(),
+ &my_charset_bin);
+ break;
+ }
+ }
+ }
+ }
+
+
+ /**
+ @brief
+ Read statistical data from statistical fields of column_stats
+
+ @details
+ This implementation of a purely virtual method first looks for a record
+ in the statistical table column_stats by its primary key set in the record
+ buffer with the help of Column_stat::set_key_fields. Then, if the row is
+ found, the function reads the values of the columns 'min_value',
+ 'max_value', 'nulls_ratio', 'avg_length', 'avg_frequency', 'hist_size' and
+ 'hist_type" of the table column_stat and sets accordingly the value of
+ the bitmap read_stat.column_stat_nulls' and the values of the fields
+ min_value, max_value, nulls_ratio, avg_length, avg_frequency, hist_size and
+ hist_type of the structure read_stat from the Field structure for the field
+ 'table_field'.
+ */
+
+ void get_stat_values()
+ {
+ table_field->read_stats->set_all_nulls();
+
+ if (table_field->read_stats->min_value)
+ table_field->read_stats->min_value->set_null();
+ if (table_field->read_stats->max_value)
+ table_field->read_stats->max_value->set_null();
+
+ if (find_stat())
+ {
+ char buff[MAX_FIELD_WIDTH];
+ String val(buff, sizeof(buff), &my_charset_utf8_bin);
+
+ for (uint i= COLUMN_STAT_MIN_VALUE; i <= COLUMN_STAT_HIST_TYPE; i++)
+ {
+ Field *stat_field= stat_table->field[i];
+
+ if (!stat_field->is_null() &&
+ (i > COLUMN_STAT_MAX_VALUE ||
+ (i == COLUMN_STAT_MIN_VALUE &&
+ table_field->read_stats->min_value) ||
+ (i == COLUMN_STAT_MAX_VALUE &&
+ table_field->read_stats->max_value)))
+ {
+ table_field->read_stats->set_not_null(i);
+
+ switch (i) {
+ case COLUMN_STAT_MIN_VALUE:
+ stat_field->val_str(&val);
+ table_field->read_stats->min_value->store(val.ptr(), val.length(),
+ &my_charset_utf8_bin);
+ break;
+ case COLUMN_STAT_MAX_VALUE:
+ stat_field->val_str(&val);
+ table_field->read_stats->max_value->store(val.ptr(), val.length(),
+ &my_charset_utf8_bin);
+ break;
+ case COLUMN_STAT_NULLS_RATIO:
+ table_field->read_stats->set_nulls_ratio(stat_field->val_real());
+ break;
+ case COLUMN_STAT_AVG_LENGTH:
+ table_field->read_stats->set_avg_length(stat_field->val_real());
+ break;
+ case COLUMN_STAT_AVG_FREQUENCY:
+ table_field->read_stats->set_avg_frequency(stat_field->val_real());
+ break;
+ case COLUMN_STAT_HIST_SIZE:
+ table_field->read_stats->histogram.set_size(stat_field->val_int());
+ break;
+ case COLUMN_STAT_HIST_TYPE:
+ Histogram_type hist_type= (Histogram_type) (stat_field->val_int() -
+ 1);
+ table_field->read_stats->histogram.set_type(hist_type);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ @brief
+ Read histogram from of column_stats
+
+ @details
+ This method first looks for a record in the statistical table column_stats
+ by its primary key set the record buffer with the help of
+ Column_stat::set_key_fields. Then, if the row is found, the function reads
+ the value of the column 'histogram' of the table column_stat and sets
+ accordingly the corresponding bit in the bitmap read_stat.column_stat_nulls.
+ The method assumes that the value of histogram size and the pointer to
+ the histogram location has been already set in the fields size and values
+ of read_stats->histogram.
+ */
+
+ void get_histogram_value()
+ {
+ if (find_stat())
+ {
+ char buff[MAX_FIELD_WIDTH];
+ String val(buff, sizeof(buff), &my_charset_utf8_bin);
+ uint fldno= COLUMN_STAT_HISTOGRAM;
+ Field *stat_field= stat_table->field[fldno];
+ table_field->read_stats->set_not_null(fldno);
+ stat_field->val_str(&val);
+ memcpy(table_field->read_stats->histogram.get_values(),
+ val.ptr(), table_field->read_stats->histogram.get_size());
+ }
+ }
+
+};
+
+
+/*
+ An object of the class Index_stat is created to read statistical
+ data on tables from the statistical table table_stat, to update
+ index_stats with such statistical data, or to update columns
+ of the primary key, or to delete the record by its primary key or
+ its prefix.
+ Rows from the statistical table are read and updated always by
+ primary key.
+*/
+
+class Index_stat: public Stat_table
+{
+
+private:
+
+ Field *db_name_field; /* Field for the column index_stats.db_name */
+ Field *table_name_field; /* Field for the column index_stats.table_name */
+ Field *index_name_field; /* Field for the column index_stats.table_name */
+ Field *prefix_arity_field; /* Field for the column index_stats.prefix_arity */
+
+ KEY *table_key_info; /* Info on the index to read/update statistics on */
+ uint prefix_arity; /* Number of components of the index prefix of interest */
+
+ void common_init_index_stat_table()
+ {
+ db_name_field= stat_table->field[INDEX_STAT_DB_NAME];
+ table_name_field= stat_table->field[INDEX_STAT_TABLE_NAME];
+ index_name_field= stat_table->field[INDEX_STAT_INDEX_NAME];
+ prefix_arity_field= stat_table->field[INDEX_STAT_PREFIX_ARITY];
+ }
+
+ void change_full_table_name(LEX_STRING *db, LEX_STRING *tab)
+ {
+ db_name_field->store(db->str, db->length, system_charset_info);
+ table_name_field->store(tab->str, tab->length, system_charset_info);
+ }
+
+public:
+
+
+ /**
+ @details
+ The constructor 'tunes' the private and protected members of the
+ constructed object for the statistical table index_stats to read/update
+ statistics on prefixes of different indexes of the table 'tab'.
+ The TABLE structure for the table index_stats must be passed as a value
+ for the parameter 'stat'.
+ */
+
+ Index_stat(TABLE *stat, TABLE*tab) :Stat_table(stat, tab)
+ {
+ common_init_index_stat_table();
+ }
+
+
+ /**
+ @details
+ The constructor 'tunes' the private and protected members of the
+ object constructed for the statistical table index_stats for
+ the future updates/deletes of the record concerning the table 'tab'
+ from the database 'db'.
+ */
+
+ Index_stat(TABLE *stat, LEX_STRING *db, LEX_STRING *tab)
+ :Stat_table(stat, db, tab)
+ {
+ common_init_index_stat_table();
+ }
+
+
+ /**
+ @brief
+ Set table name fields for the statistical table index_stats
+
+ @details
+ The function stores the values of the fields db_name and table_name
+ of the statistical table index_stats in the record buffer.
+ */
+
+ void set_full_table_name()
+ {
+ db_name_field->store(db_name->str, db_name->length, system_charset_info);
+ table_name_field->store(table_name->str, table_name->length,
+ system_charset_info);
+ }
+
+ /**
+ @brief
+ Set the key fields of index_stats used to access records for index prefixes
+
+ @param
+ index_info Info for the index of 'table' to read/update statistics on
+
+ @details
+ The function sets the values of the fields db_name, table_name and
+ index_name in the record buffer for the statistical table index_stats.
+ It also sets table_key_info to the passed parameter.
+
+ @note
+ The function is supposed to be called before any use of the method
+ find_next_stat_for_prefix for an object of the Index_stat class.
+ */
+
+ void set_index_prefix_key_fields(KEY *index_info)
+ {
+ set_full_table_name();
+ char *index_name= index_info->name;
+ index_name_field->store(index_name, strlen(index_name),
+ system_charset_info);
+ table_key_info= index_info;
+ }
+
+
+ /**
+ @brief
+ Set the key fields for the statistical table index_stats
+
+ @param
+ index_info Info for the index of 'table' to read/update statistics on
+ @param
+ index_prefix_arity Number of components in the index prefix of interest
+
+ @details
+ The function sets the values of the fields db_name, table_name and
+ index_name, prefix_arity in the record buffer for the statistical
+ table index_stats. These fields comprise the primary key for the table.
+
+ @note
+ The function is supposed to be called before any use of the
+ method find_stat for an object of the Index_stat class.
+ */
+
+ void set_key_fields(KEY *index_info, uint index_prefix_arity)
+ {
+ set_index_prefix_key_fields(index_info);
+ prefix_arity= index_prefix_arity;
+ prefix_arity_field->store(index_prefix_arity, TRUE);
+ }
+
+
+ /**
+ @brief
+ Store statistical data into statistical fields of table index_stats
+
+ @details
+ This implementation of a purely virtual method sets the value of the
+ column 'avg_frequency' of the statistical table index_stats according to
+ the value of write_stat.avg_frequency[Index_stat::prefix_arity]
+ from the KEY_INFO structure 'table_key_info'.
+ If the value of write_stat. avg_frequency[Index_stat::prefix_arity] is
+ equal to 0, the value of the column is set to NULL.
+ */
+
+ void store_stat_fields()
+ {
+ Field *stat_field= stat_table->field[INDEX_STAT_AVG_FREQUENCY];
+ double avg_frequency=
+ table_key_info->collected_stats->get_avg_frequency(prefix_arity-1);
+ if (avg_frequency == 0)
+ stat_field->set_null();
+ else
+ {
+ stat_field->set_notnull();
+ stat_field->store(avg_frequency);
+ }
+ }
+
+
+ /**
+ @brief
+ Read statistical data from statistical fields of index_stats
+
+ @details
+ This implementation of a purely virtual method first looks for a record the
+ statistical table index_stats by its primary key set the record buffer with
+ the help of Index_stat::set_key_fields. If the row is found the function
+ reads the value of the column 'avg_freguency' of the table index_stat and
+ sets the value of read_stat.avg_frequency[Index_stat::prefix_arity]
+ from the KEY_INFO structure 'table_key_info' accordingly. If the value of
+ the column is NULL, read_stat.avg_frequency[Index_stat::prefix_arity] is
+ set to 0. Otherwise, read_stat.avg_frequency[Index_stat::prefix_arity] is
+ set to the value of the column.
+ */
+
+ void get_stat_values()
+ {
+ double avg_frequency= 0;
+ if(find_stat())
+ {
+ Field *stat_field= stat_table->field[INDEX_STAT_AVG_FREQUENCY];
+ if (!stat_field->is_null())
+ avg_frequency= stat_field->val_real();
+ }
+ table_key_info->read_stats->set_avg_frequency(prefix_arity-1, avg_frequency);
+ }
+
+};
+
+/*
+ Histogram_builder is a helper class that is used to build histograms
+ for columns
+*/
+
+class Histogram_builder
+{
+ Field *column; /* table field for which the histogram is built */
+ uint col_length; /* size of this field */
+ ha_rows records; /* number of records the histogram is built for */
+ Field *min_value; /* pointer to the minimal value for the field */
+ Field *max_value; /* pointer to the maximal value for the field */
+ Histogram *histogram; /* the histogram location */
+ uint hist_width; /* the number of points in the histogram */
+ double bucket_capacity; /* number of rows in a bucket of the histogram */
+ uint curr_bucket; /* number of the current bucket to be built */
+ ulonglong count; /* number of values retrieved */
+ ulonglong count_distinct; /* number of distinct values retrieved */
+
+public:
+ Histogram_builder(Field *col, uint col_len, ha_rows rows)
+ : column(col), col_length(col_len), records(rows)
+ {
+ Column_statistics *col_stats= col->collected_stats;
+ min_value= col_stats->min_value;
+ max_value= col_stats->max_value;
+ histogram= &col_stats->histogram;
+ hist_width= histogram->get_width();
+ bucket_capacity= (double) records / (hist_width + 1);
+ curr_bucket= 0;
+ count= 0;
+ count_distinct= 0;
+ }
+
+ ulonglong get_count_distinct() { return count_distinct; }
+
+ int next(void *elem, element_count elem_cnt)
+ {
+ count_distinct++;
+ count+= elem_cnt;
+ if (curr_bucket == hist_width)
+ return 0;
+ if (count > bucket_capacity * (curr_bucket + 1))
+ {
+ column->store_field_value((uchar *) elem, col_length);
+ histogram->set_value(curr_bucket,
+ column->pos_in_interval(min_value, max_value));
+ curr_bucket++;
+ while (curr_bucket != hist_width &&
+ count > bucket_capacity * (curr_bucket + 1))
+ {
+ histogram->set_prev_value(curr_bucket);
+ curr_bucket++;
+ }
+ }
+ return 0;
+ }
+};
+
+
+C_MODE_START
+
+int histogram_build_walk(void *elem, element_count elem_cnt, void *arg)
+{
+ Histogram_builder *hist_builder= (Histogram_builder *) arg;
+ return hist_builder->next(elem, elem_cnt);
+}
+
+C_MODE_END
+
+
+/*
+ The class Count_distinct_field is a helper class used to calculate
+ the number of distinct values for a column. The class employs the
+ Unique class for this purpose.
+ The class Count_distinct_field is used only by the function
+ collect_statistics_for_table to calculate the values for
+ column avg_frequency of the statistical table column_stats.
+*/
+
+class Count_distinct_field: public Sql_alloc
+{
+protected:
+
+ /* Field for which the number of distinct values is to be find out */
+ Field *table_field;
+ Unique *tree; /* The helper object to contain distinct values */
+ uint tree_key_length; /* The length of the keys for the elements of 'tree */
+
+public:
+
+ Count_distinct_field() {}
+
+ /**
+ @param
+ field Field for which the number of distinct values is
+ to be find out
+ @param
+ max_heap_table_size The limit for the memory used by the RB tree container
+ of the constructed Unique object 'tree'
+
+ @details
+ The constructor sets the values of 'table_field' and 'tree_key_length',
+ and then calls the 'new' operation to create a Unique object for 'tree'.
+ The type of 'field' and the value max_heap_table_size of determine the set
+ of the parameters to be passed to the constructor of the Unique object.
+ */
+
+ Count_distinct_field(Field *field, uint max_heap_table_size)
+ {
+ table_field= field;
+ tree_key_length= field->pack_length();
+
+ tree= new Unique((qsort_cmp2) simple_str_key_cmp, (void*) field,
+ tree_key_length, max_heap_table_size, 1);
+ }
+
+ virtual ~Count_distinct_field()
+ {
+ delete tree;
+ tree= NULL;
+ }
+
+ /*
+ @brief
+ Check whether the Unique object tree has been successfully created
+ */
+ bool exists()
+ {
+ return (tree != NULL);
+ }
+
+ /*
+ @brief
+ Add the value of 'field' to the container of the Unique object 'tree'
+ */
+ virtual bool add()
+ {
+ return tree->unique_add(table_field->ptr);
+ }
+
+ /*
+ @brief
+ Calculate the number of elements accumulated in the container of 'tree'
+ */
+ ulonglong get_value()
+ {
+ ulonglong count;
+ if (tree->elements == 0)
+ return (ulonglong) tree->elements_in_tree();
+ count= 0;
+ tree->walk(table_field->table, count_distinct_walk, (void*) &count);
+ return count;
+ }
+
+ /*
+ @brief
+ Build the histogram for the elements accumulated in the container of 'tree'
+ */
+ ulonglong get_value_with_histogram(ha_rows rows)
+ {
+ Histogram_builder hist_builder(table_field, tree_key_length, rows);
+ tree->walk(table_field->table, histogram_build_walk, (void *) &hist_builder);
+ return hist_builder.get_count_distinct();
+ }
+
+ /*
+ @brief
+ Get the size of the histogram in bytes built for table_field
+ */
+ uint get_hist_size()
+ {
+ return table_field->collected_stats->histogram.get_size();
+ }
+
+ /*
+ @brief
+ Get the pointer to the histogram built for table_field
+ */
+ uchar *get_histogram()
+ {
+ return table_field->collected_stats->histogram.get_values();
+ }
+
+};
+
+
+static
+int simple_ulonglong_key_cmp(void* arg, uchar* key1, uchar* key2)
+{
+ ulonglong *val1= (ulonglong *) key1;
+ ulonglong *val2= (ulonglong *) key2;
+ return *val1 > *val2 ? 1 : *val1 == *val2 ? 0 : -1;
+}
+
+
+/*
+ The class Count_distinct_field_bit is derived from the class
+ Count_distinct_field to be used only for fields of the MYSQL_TYPE_BIT type.
+ The class provides a different implementation for the method add
+*/
+
+class Count_distinct_field_bit: public Count_distinct_field
+{
+public:
+
+ Count_distinct_field_bit(Field *field, uint max_heap_table_size)
+ {
+ table_field= field;
+ tree_key_length= sizeof(ulonglong);
+
+ tree= new Unique((qsort_cmp2) simple_ulonglong_key_cmp,
+ (void*) &tree_key_length,
+ tree_key_length, max_heap_table_size, 1);
+ }
+
+ bool add()
+ {
+ longlong val= table_field->val_int();
+ return tree->unique_add(&val);
+ }
+};
+
+
+/*
+ The class Index_prefix_calc is a helper class used to calculate the values
+ for the column 'avg_frequency' of the statistical table index_stats.
+ For any table t from the database db and any k-component prefix of the
+ index i for this table the row from index_stats with the primary key
+ (db,t,i,k) must contain in the column 'avg_frequency' either NULL or
+ the number that is the ratio of N and V, where N is the number of index
+ entries without NULL values in the first k components of the index i,
+ and V is the number of distinct tuples composed of the first k components
+ encountered among these index entries.
+ Currently the objects of this class are used only by the function
+ collect_statistics_for_index.
+*/
+
+class Index_prefix_calc: public Sql_alloc
+{
+
+private:
+
+ /* Table containing index specified by index_info */
+ TABLE *index_table;
+ /* Info for the index i for whose prefix 'avg_frequency' is calculated */
+ KEY *index_info;
+ /* The maximum number of the components in the prefixes of interest */
+ uint prefixes;
+ bool empty;
+
+ /* This structure is created for every k components of the index i */
+ class Prefix_calc_state
+ {
+ public:
+ /*
+ The number of the scanned index entries without nulls
+ in the first k components
+ */
+ ulonglong entry_count;
+ /*
+ The number if the scanned index entries without nulls with
+ the last encountered k-component prefix
+ */
+ ulonglong prefix_count;
+ /* The values of the last encountered k-component prefix */
+ Cached_item *last_prefix;
+ };
+
+ /*
+ Array of structures used to calculate 'avg_frequency' for different
+ prefixes of the index i
+ */
+ Prefix_calc_state *calc_state;
+
+public:
+
+ bool is_single_comp_pk;
+
+ Index_prefix_calc(TABLE *table, KEY *key_info)
+ : index_table(table), index_info(key_info)
+ {
+ uint i;
+ Prefix_calc_state *state;
+ uint key_parts= table->actual_n_key_parts(key_info);
+ empty= TRUE;
+ prefixes= 0;
+ LINT_INIT(calc_state);
+
+ is_single_comp_pk= FALSE;
+ uint pk= table->s->primary_key;
+ if ((uint) (table->key_info - key_info) == pk &&
+ table->key_info[pk].user_defined_key_parts == 1)
+ {
+ prefixes= 1;
+ is_single_comp_pk= TRUE;
+ return;
+ }
+
+ if ((calc_state=
+ (Prefix_calc_state *) sql_alloc(sizeof(Prefix_calc_state)*key_parts)))
+ {
+ uint keyno= key_info-table->key_info;
+ for (i= 0, state= calc_state; i < key_parts; i++, state++)
+ {
+ /*
+ Do not consider prefixes containing a component that is only part
+ of the field. This limitation is set to avoid fetching data when
+ calculating the values of 'avg_frequency' for prefixes.
+ */
+ if (!key_info->key_part[i].field->part_of_key.is_set(keyno))
+ break;
+
+ if (!(state->last_prefix=
+ new Cached_item_field(key_info->key_part[i].field)))
+ break;
+ state->entry_count= state->prefix_count= 0;
+ prefixes++;
+ }
+ }
+ }
+
+
+ /**
+ @breif
+ Change the elements of calc_state after reading the next index entry
+
+ @details
+ This function is to be called at the index scan each time the next
+ index entry has been read into the record buffer.
+ For each of the index prefixes the function checks whether nulls
+ are encountered in any of the k components of the prefix.
+ If this is not the case the value of calc_state[k-1].entry_count
+ is incremented by 1. Then the function checks whether the value of
+ any of these k components has changed. If so, the value of
+ calc_state[k-1].prefix_count is incremented by 1.
+ */
+
+ void add()
+ {
+ uint i;
+ Prefix_calc_state *state;
+ uint first_changed= prefixes;
+ for (i= prefixes, state= calc_state+prefixes-1; i; i--, state--)
+ {
+ if (state->last_prefix->cmp())
+ first_changed= i-1;
+ }
+ if (empty)
+ {
+ first_changed= 0;
+ empty= FALSE;
+ }
+ for (i= 0, state= calc_state; i < prefixes; i++, state++)
+ {
+ if (state->last_prefix->null_value)
+ break;
+ if (i >= first_changed)
+ state->prefix_count++;
+ state->entry_count++;
+ }
+ }
+
+ /**
+ @brief
+ Calculate the values of avg_frequency for all prefixes of an index
+
+ @details
+ This function is to be called after the index scan to count the number
+ of distinct index prefixes has been done. The function calculates
+ the value of avg_frequency for the index prefix with k components
+ as calc_state[k-1].entry_count/calc_state[k-1].prefix_count.
+ If calc_state[k-1].prefix_count happens to be 0, the value of
+ avg_frequency[k-1] is set to 0, i.e. is considered as unknown.
+ */
+
+ void get_avg_frequency()
+ {
+ uint i;
+ Prefix_calc_state *state;
+
+ if (is_single_comp_pk)
+ {
+ index_info->collected_stats->set_avg_frequency(0, 1.0);
+ return;
+ }
+
+ for (i= 0, state= calc_state; i < prefixes; i++, state++)
+ {
+ if (i < prefixes)
+ {
+ double val= state->prefix_count == 0 ?
+ 0 : (double) state->entry_count / state->prefix_count;
+ index_info->collected_stats->set_avg_frequency(i, val);
+ }
+ }
+ }
+};
+
+
+/**
+ @brief
+ Create fields for min/max values to collect column statistics
+
+ @param
+ table Table the fields are created for
+
+ @details
+ The function first allocates record buffers to store min/max values
+ for 'table's fields. Then for each table field f it creates Field structures
+ that points to these buffers rather that to the record buffer as the
+ Field object for f does. The pointers of the created fields are placed
+ in the collected_stats structure of the Field object for f.
+ The function allocates the buffers for min/max values in the table
+ memory.
+
+ @note
+ The buffers allocated when min/max values are used to read statistics
+ from the persistent statistical tables differ from those buffers that
+ are used when statistics on min/max values for column is collected
+ as they are allocated in different mem_roots.
+ The same is true for the fields created for min/max values.
+*/
+
+static
+void create_min_max_statistical_fields_for_table(TABLE *table)
+{
+ uint rec_buff_length= table->s->rec_buff_length;
+
+ if ((table->collected_stats->min_max_record_buffers=
+ (uchar *) alloc_root(&table->mem_root, 2*rec_buff_length)))
+ {
+ uchar *record= table->collected_stats->min_max_record_buffers;
+ memset(record, 0, 2*rec_buff_length);
+
+ for (uint i=0; i < 2; i++, record+= rec_buff_length)
+ {
+ for (Field **field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ Field *fld;
+ Field *table_field= *field_ptr;
+ my_ptrdiff_t diff= record-table->record[0];
+ if (!bitmap_is_set(table->read_set, table_field->field_index))
+ continue;
+ if (!(fld= table_field->clone(&table->mem_root, table, diff, TRUE)))
+ continue;
+ if (i == 0)
+ table_field->collected_stats->min_value= fld;
+ else
+ table_field->collected_stats->max_value= fld;
+ }
+ }
+ }
+}
+
+
+/**
+ @brief
+ Create fields for min/max values to read column statistics
+
+ @param
+ thd Thread handler
+ @param
+ table_share Table share the fields are created for
+ @param
+ is_safe TRUE <-> at any time only one thread can perform the function
+
+ @details
+ The function first allocates record buffers to store min/max values
+ for 'table_share's fields. Then for each field f it creates Field structures
+ that points to these buffers rather that to the record buffer as the
+ Field object for f does. The pointers of the created fields are placed
+ in the read_stats structure of the Field object for f.
+ The function allocates the buffers for min/max values in the table share
+ memory.
+ If the parameter is_safe is TRUE then it is guaranteed that at any given time
+ only one thread is executed the code of the function.
+
+ @note
+ The buffers allocated when min/max values are used to collect statistics
+ from the persistent statistical tables differ from those buffers that
+ are used when statistics on min/max values for column is read as they
+ are allocated in different mem_roots.
+ The same is true for the fields created for min/max values.
+*/
+
+static
+void create_min_max_statistical_fields_for_table_share(THD *thd,
+ TABLE_SHARE *table_share)
+{
+ TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb;
+ Table_statistics *stats= stats_cb->table_stats;
+
+ if (stats->min_max_record_buffers)
+ return;
+
+ uint rec_buff_length= table_share->rec_buff_length;
+
+ if ((stats->min_max_record_buffers=
+ (uchar *) alloc_root(&stats_cb->mem_root, 2*rec_buff_length)))
+ {
+ uchar *record= stats->min_max_record_buffers;
+ memset(record, 0, 2*rec_buff_length);
+
+ for (uint i=0; i < 2; i++, record+= rec_buff_length)
+ {
+ for (Field **field_ptr= table_share->field; *field_ptr; field_ptr++)
+ {
+ Field *fld;
+ Field *table_field= *field_ptr;
+ my_ptrdiff_t diff= record - table_share->default_values;
+ if (!(fld= table_field->clone(&stats_cb->mem_root, diff)))
+ continue;
+ if (i == 0)
+ table_field->read_stats->min_value= fld;
+ else
+ table_field->read_stats->max_value= fld;
+ }
+ }
+ }
+
+}
+
+
+/**
+ @brief
+ Allocate memory for the table's statistical data to be collected
+
+ @param
+ table Table for which the memory for statistical data is allocated
+
+ @note
+ The function allocates the memory for the statistical data on 'table' with
+ the intention to collect the data there. The memory is allocated for
+ the statistics on the table, on the table's columns, and on the table's
+ indexes. The memory is allocated in the table's mem_root.
+
+ @retval
+ 0 If the memory for all statistical data has been successfully allocated
+ @retval
+ 1 Otherwise
+
+ @note
+ Each thread allocates its own memory to collect statistics on the table
+ It allows us, for example, to collect statistics on the different indexes
+ of the same table in parallel.
+*/
+
+int alloc_statistics_for_table(THD* thd, TABLE *table)
+{
+ Field **field_ptr;
+ uint fields;
+
+ DBUG_ENTER("alloc_statistics_for_table");
+
+
+ Table_statistics *table_stats=
+ (Table_statistics *) alloc_root(&table->mem_root,
+ sizeof(Table_statistics));
+
+ fields= table->s->fields ;
+ Column_statistics_collected *column_stats=
+ (Column_statistics_collected *) alloc_root(&table->mem_root,
+ sizeof(Column_statistics_collected) *
+ (fields+1));
+
+ uint keys= table->s->keys;
+ Index_statistics *index_stats=
+ (Index_statistics *) alloc_root(&table->mem_root,
+ sizeof(Index_statistics) * keys);
+
+ uint key_parts= table->s->ext_key_parts;
+ ulong *idx_avg_frequency= (ulong*) alloc_root(&table->mem_root,
+ sizeof(ulong) * key_parts);
+
+ uint columns= 0;
+ for (field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ if (bitmap_is_set(table->read_set, (*field_ptr)->field_index))
+ columns++;
+ }
+ uint hist_size= thd->variables.histogram_size;
+ Histogram_type hist_type= (Histogram_type) (thd->variables.histogram_type);
+ uchar *histogram= NULL;
+ if (hist_size > 0)
+ histogram= (uchar *) alloc_root(&table->mem_root, hist_size * columns);
+
+ if (!table_stats || !column_stats || !index_stats || !idx_avg_frequency ||
+ (hist_size && !histogram))
+ DBUG_RETURN(1);
+
+ table->collected_stats= table_stats;
+ table_stats->column_stats= column_stats;
+ table_stats->index_stats= index_stats;
+ table_stats->idx_avg_frequency= idx_avg_frequency;
+ table_stats->histograms= histogram;
+
+ memset(column_stats, 0, sizeof(Column_statistics) * (fields+1));
+
+ for (field_ptr= table->field; *field_ptr; field_ptr++, column_stats++)
+ {
+ (*field_ptr)->collected_stats= column_stats;
+ (*field_ptr)->collected_stats->max_value= NULL;
+ (*field_ptr)->collected_stats->min_value= NULL;
+ if (bitmap_is_set(table->read_set, (*field_ptr)->field_index))
+ {
+ column_stats->histogram.set_size(hist_size);
+ column_stats->histogram.set_type(hist_type);
+ column_stats->histogram.set_values(histogram);
+ histogram+= hist_size;
+ }
+ }
+
+ memset(idx_avg_frequency, 0, sizeof(ulong) * key_parts);
+
+ KEY *key_info, *end;
+ for (key_info= table->key_info, end= key_info + table->s->keys;
+ key_info < end;
+ key_info++, index_stats++)
+ {
+ key_info->collected_stats= index_stats;
+ key_info->collected_stats->init_avg_frequency(idx_avg_frequency);
+ idx_avg_frequency+= key_info->ext_key_parts;
+ }
+
+ create_min_max_statistical_fields_for_table(table);
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Check whether any persistent statistics for the processed command is needed
+
+ @param
+ thd The thread handle
+
+ @details
+ The function checks whether any persitent statistics for the processed
+ command is needed to be read.
+
+ @retval
+ TRUE statistics is needed to be read
+ @retval
+ FALSE Otherwise
+*/
+
+static
+inline bool statistics_for_command_is_needed(THD *thd)
+{
+ if (thd->bootstrap || thd->variables.use_stat_tables == NEVER)
+ return FALSE;
+
+ switch(thd->lex->sql_command) {
+ case SQLCOM_SELECT:
+ case SQLCOM_INSERT:
+ case SQLCOM_INSERT_SELECT:
+ case SQLCOM_UPDATE:
+ case SQLCOM_UPDATE_MULTI:
+ case SQLCOM_DELETE:
+ case SQLCOM_DELETE_MULTI:
+ case SQLCOM_REPLACE:
+ case SQLCOM_REPLACE_SELECT:
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/**
+ @brief
+ Allocate memory for the statistical data used by a table share
+
+ @param
+ thd Thread handler
+ @param
+ table_share Table share for which the memory for statistical data is allocated
+ @param
+ is_safe TRUE <-> at any time only one thread can perform the function
+
+ @note
+ The function allocates the memory for the statistical data on a table in the
+ table's share memory with the intention to read the statistics there from
+ the system persistent statistical tables mysql.table_stat, mysql.column_stats,
+ mysql.index_stats. The memory is allocated for the statistics on the table,
+ on the tables's columns, and on the table's indexes. The memory is allocated
+ in the table_share's mem_root.
+ If the parameter is_safe is TRUE then it is guaranteed that at any given time
+ only one thread is executed the code of the function.
+
+ @retval
+ 0 If the memory for all statistical data has been successfully allocated
+ @retval
+ 1 Otherwise
+
+ @note
+ The situation when more than one thread try to allocate memory for
+ statistical data is rare. It happens under the following scenario:
+ 1. One thread executes a query over table t with the system variable
+ 'use_stat_tables' set to 'never'.
+ 2. After this the second thread sets 'use_stat_tables' to 'preferably'
+ and executes a query over table t.
+ 3. Simultaneously the third thread sets 'use_stat_tables' to 'preferably'
+ and executes a query over table t.
+ Here the second and the third threads try to allocate the memory for
+ statistical data at the same time. The precautions are taken to
+ guarantee the correctness of the allocation.
+
+ @note
+ Currently the function always is called with the parameter is_safe set
+ to FALSE.
+*/
+
+int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *table_share,
+ bool is_safe)
+{
+
+ Field **field_ptr;
+ KEY *key_info, *end;
+ TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb;
+
+ DBUG_ENTER("alloc_statistics_for_table_share");
+
+ DEBUG_SYNC(thd, "statistics_mem_alloc_start1");
+ DEBUG_SYNC(thd, "statistics_mem_alloc_start2");
+
+ if (!statistics_for_command_is_needed(thd))
+ DBUG_RETURN(1);
+
+ if (!is_safe)
+ mysql_mutex_lock(&table_share->LOCK_share);
+
+ if (stats_cb->stats_can_be_read)
+ {
+ if (!is_safe)
+ mysql_mutex_unlock(&table_share->LOCK_share);
+ DBUG_RETURN(0);
+ }
+
+ Table_statistics *table_stats= stats_cb->table_stats;
+ if (!table_stats)
+ {
+ table_stats= (Table_statistics *) alloc_root(&stats_cb->mem_root,
+ sizeof(Table_statistics));
+ if (!table_stats)
+ {
+ if (!is_safe)
+ mysql_mutex_unlock(&table_share->LOCK_share);
+ DBUG_RETURN(1);
+ }
+ memset(table_stats, 0, sizeof(Table_statistics));
+ stats_cb->table_stats= table_stats;
+ }
+
+ uint fields= table_share->fields;
+ Column_statistics *column_stats= table_stats->column_stats;
+ if (!column_stats)
+ {
+ column_stats= (Column_statistics *) alloc_root(&stats_cb->mem_root,
+ sizeof(Column_statistics) *
+ (fields+1));
+ if (column_stats)
+ {
+ memset(column_stats, 0, sizeof(Column_statistics) * (fields+1));
+ table_stats->column_stats= column_stats;
+ for (field_ptr= table_share->field;
+ *field_ptr;
+ field_ptr++, column_stats++)
+ {
+ (*field_ptr)->read_stats= column_stats;
+ (*field_ptr)->read_stats->min_value= NULL;
+ (*field_ptr)->read_stats->max_value= NULL;
+ }
+ create_min_max_statistical_fields_for_table_share(thd, table_share);
+ }
+ }
+
+ uint keys= table_share->keys;
+ Index_statistics *index_stats= table_stats->index_stats;
+ if (!index_stats)
+ {
+ index_stats= (Index_statistics *) alloc_root(&stats_cb->mem_root,
+ sizeof(Index_statistics) *
+ keys);
+ if (index_stats)
+ {
+ table_stats->index_stats= index_stats;
+ for (key_info= table_share->key_info, end= key_info + keys;
+ key_info < end;
+ key_info++, index_stats++)
+ {
+ key_info->read_stats= index_stats;
+ }
+ }
+ }
+
+ uint key_parts= table_share->ext_key_parts;
+ ulong *idx_avg_frequency= table_stats->idx_avg_frequency;
+ if (!idx_avg_frequency)
+ {
+ idx_avg_frequency= (ulong*) alloc_root(&stats_cb->mem_root,
+ sizeof(ulong) * key_parts);
+ if (idx_avg_frequency)
+ {
+ memset(idx_avg_frequency, 0, sizeof(ulong) * key_parts);
+ table_stats->idx_avg_frequency= idx_avg_frequency;
+ for (key_info= table_share->key_info, end= key_info + keys;
+ key_info < end;
+ key_info++)
+ {
+ key_info->read_stats->init_avg_frequency(idx_avg_frequency);
+ idx_avg_frequency+= key_info->ext_key_parts;
+ }
+ }
+ }
+
+ if (column_stats && index_stats && idx_avg_frequency)
+ stats_cb->stats_can_be_read= TRUE;
+
+ if (!is_safe)
+ mysql_mutex_unlock(&table_share->LOCK_share);
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Allocate memory for the histogram used by a table share
+
+ @param
+ thd Thread handler
+ @param
+ table_share Table share for which the memory for histogram data is allocated
+ @param
+ is_safe TRUE <-> at any time only one thread can perform the function
+
+ @note
+ The function allocates the memory for the histogram built for a table in the
+ table's share memory with the intention to read the data there from the
+ system persistent statistical table mysql.column_stats,
+ The memory is allocated in the table_share's mem_root.
+ If the parameter is_safe is TRUE then it is guaranteed that at any given time
+ only one thread is executed the code of the function.
+
+ @retval
+ 0 If the memory for all statistical data has been successfully allocated
+ @retval
+ 1 Otherwise
+
+ @note
+ Currently the function always is called with the parameter is_safe set
+ to FALSE.
+*/
+
+static
+int alloc_histograms_for_table_share(THD* thd, TABLE_SHARE *table_share,
+ bool is_safe)
+{
+ TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb;
+
+ DBUG_ENTER("alloc_histograms_for_table_share");
+
+ if (!is_safe)
+ mysql_mutex_lock(&table_share->LOCK_share);
+
+ if (stats_cb->histograms_can_be_read)
+ {
+ if (!is_safe)
+ mysql_mutex_unlock(&table_share->LOCK_share);
+ DBUG_RETURN(0);
+ }
+
+ Table_statistics *table_stats= stats_cb->table_stats;
+ ulong total_hist_size= table_stats->total_hist_size;
+
+ if (total_hist_size && !table_stats->histograms)
+ {
+ uchar *histograms= (uchar *) alloc_root(&stats_cb->mem_root,
+ total_hist_size);
+ if (!histograms)
+ {
+ if (!is_safe)
+ mysql_mutex_unlock(&table_share->LOCK_share);
+ DBUG_RETURN(1);
+ }
+ memset(histograms, 0, total_hist_size);
+ table_stats->histograms= histograms;
+ stats_cb->histograms_can_be_read= TRUE;
+ }
+
+ if (!is_safe)
+ mysql_mutex_unlock(&table_share->LOCK_share);
+
+ DBUG_RETURN(0);
+
+}
+
+/**
+ @brief
+ Initialize the aggregation fields to collect statistics on a column
+
+ @param
+ thd Thread handler
+ @param
+ table_field Column to collect statistics for
+*/
+
+inline
+void Column_statistics_collected::init(THD *thd, Field *table_field)
+{
+ uint max_heap_table_size= thd->variables.max_heap_table_size;
+ TABLE *table= table_field->table;
+ uint pk= table->s->primary_key;
+
+ is_single_pk_col= FALSE;
+
+ if (pk != MAX_KEY && table->key_info[pk].user_defined_key_parts == 1 &&
+ table->key_info[pk].key_part[0].fieldnr == table_field->field_index + 1)
+ is_single_pk_col= TRUE;
+
+ column= table_field;
+
+ set_all_nulls();
+
+ nulls= 0;
+ column_total_length= 0;
+ if (is_single_pk_col)
+ count_distinct= NULL;
+ if (table_field->flags & BLOB_FLAG)
+ count_distinct= NULL;
+ else
+ {
+ count_distinct=
+ table_field->type() == MYSQL_TYPE_BIT ?
+ new Count_distinct_field_bit(table_field, max_heap_table_size) :
+ new Count_distinct_field(table_field, max_heap_table_size);
+ }
+ if (count_distinct && !count_distinct->exists())
+ count_distinct= NULL;
+}
+
+
+/**
+ @brief
+ Perform aggregation for a row when collecting statistics on a column
+
+ @param
+ rowno The order number of the row
+*/
+
+inline
+bool Column_statistics_collected::add(ha_rows rowno)
+{
+
+ bool err= 0;
+ if (column->is_null())
+ nulls++;
+ else
+ {
+ column_total_length+= column->value_length();
+ if (min_value && column->update_min(min_value, rowno == nulls))
+ set_not_null(COLUMN_STAT_MIN_VALUE);
+ if (max_value && column->update_max(max_value, rowno == nulls))
+ set_not_null(COLUMN_STAT_MAX_VALUE);
+ if (count_distinct)
+ err= count_distinct->add();
+ }
+ return err;
+}
+
+
+/**
+ @brief
+ Get the results of aggregation when collecting the statistics on a column
+
+ @param
+ rows The total number of rows in the table
+*/
+
+inline
+void Column_statistics_collected::finish(ha_rows rows)
+{
+ double val;
+
+ if (rows)
+ {
+ val= (double) nulls / rows;
+ set_nulls_ratio(val);
+ set_not_null(COLUMN_STAT_NULLS_RATIO);
+ }
+ if (rows - nulls)
+ {
+ val= (double) column_total_length / (rows - nulls);
+ set_avg_length(val);
+ set_not_null(COLUMN_STAT_AVG_LENGTH);
+ }
+ if (count_distinct)
+ {
+ ulonglong distincts;
+ uint hist_size= count_distinct->get_hist_size();
+ if (hist_size == 0)
+ distincts= count_distinct->get_value();
+ else
+ distincts= count_distinct->get_value_with_histogram(rows - nulls);
+ if (distincts)
+ {
+ val= (double) (rows - nulls) / distincts;
+ set_avg_frequency(val);
+ set_not_null(COLUMN_STAT_AVG_FREQUENCY);
+ }
+ else
+ hist_size= 0;
+ histogram.set_size(hist_size);
+ set_not_null(COLUMN_STAT_HIST_SIZE);
+ if (hist_size && distincts)
+ {
+ set_not_null(COLUMN_STAT_HIST_TYPE);
+ histogram.set_values(count_distinct->get_histogram());
+ set_not_null(COLUMN_STAT_HISTOGRAM);
+ }
+ delete count_distinct;
+ count_distinct= NULL;
+ }
+ else if (is_single_pk_col)
+ {
+ val= 1.0;
+ set_avg_frequency(val);
+ set_not_null(COLUMN_STAT_AVG_FREQUENCY);
+ }
+}
+
+
+/**
+ @brief
+ Clean up auxiliary structures used for aggregation
+*/
+
+inline
+void Column_statistics_collected::cleanup()
+{
+ if (count_distinct)
+ {
+ delete count_distinct;
+ count_distinct= NULL;
+ }
+}
+
+
+/**
+ @brief
+ Collect statistical data on an index
+
+ @param
+ table The table the index belongs to
+ index The number of this index in the table
+
+ @details
+ The function collects the value of 'avg_frequency' for the prefixes
+ on an index from 'table'. The index is specified by its number.
+ If the scan is successful the calculated statistics is saved in the
+ elements of the array write_stat.avg_frequency of the KEY_INFO structure
+ for the index. The statistics for the prefix with k components is saved
+ in the element number k-1.
+
+ @retval
+ 0 If the statistics has been successfully collected
+ @retval
+ 1 Otherwise
+
+ @note
+ The function collects statistics for the index prefixes for one index
+ scan during which no data is fetched from the table records. That's why
+ statistical data for prefixes that contain part of a field is not
+ collected.
+ The function employs an object of the helper class Index_prefix_calc to
+ count for each index prefix the number of index entries without nulls and
+ the number of distinct entries among them.
+
+*/
+
+static
+int collect_statistics_for_index(THD *thd, TABLE *table, uint index)
+{
+ int rc= 0;
+ KEY *key_info= &table->key_info[index];
+ ha_rows rows= 0;
+
+ DBUG_ENTER("collect_statistics_for_index");
+
+ /* No statistics for FULLTEXT indexes. */
+ if (key_info->flags & HA_FULLTEXT)
+ DBUG_RETURN(rc);
+
+ Index_prefix_calc index_prefix_calc(table, key_info);
+
+ DEBUG_SYNC(table->in_use, "statistics_collection_start1");
+ DEBUG_SYNC(table->in_use, "statistics_collection_start2");
+
+ if (index_prefix_calc.is_single_comp_pk)
+ {
+ index_prefix_calc.get_avg_frequency();
+ DBUG_RETURN(rc);
+ }
+
+ table->key_read= 1;
+ table->file->extra(HA_EXTRA_KEYREAD);
+
+ table->file->ha_index_init(index, TRUE);
+ rc= table->file->ha_index_first(table->record[0]);
+ while (rc != HA_ERR_END_OF_FILE)
+ {
+ if (thd->killed)
+ break;
+
+ if (rc)
+ break;
+ rows++;
+ index_prefix_calc.add();
+ rc= table->file->ha_index_next(table->record[0]);
+ }
+ table->key_read= 0;
+ table->file->ha_index_end();
+
+ rc= (rc == HA_ERR_END_OF_FILE && !thd->killed) ? 0 : 1;
+
+ if (!rc)
+ index_prefix_calc.get_avg_frequency();
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Collect statistical data for a table
+
+ @param
+ thd The thread handle
+ @param
+ table The table to collect statistics on
+
+ @details
+ The function collects data for various statistical characteristics on
+ the table 'table'. These data is saved in the internal fields that could
+ be reached from 'table'. The data is prepared to be saved in the persistent
+ statistical table by the function update_statistics_for_table.
+ The collected statistical values are not placed in the same fields that
+ keep the statistical data used by the optimizer. Therefore, at any time,
+ there is no collision between the statistics being collected and the one
+ used by the optimizer to look for optimal query execution plans for other
+ clients.
+
+ @retval
+ 0 If the statistics has been successfully collected
+ @retval
+ 1 Otherwise
+
+ @note
+ The function first collects statistical data for statistical characteristics
+ to be saved in the statistical tables table_stat and column_stats. To do this
+ it performs a full table scan of 'table'. At this scan the function collects
+ statistics on each column of the table and count the total number of the
+ scanned rows. To calculate the value of 'avg_frequency' for a column the
+ function constructs an object of the helper class Count_distinct_field
+ (or its derivation). Currently this class cannot count the number of
+ distinct values for blob columns. So the value of 'avg_frequency' for
+ blob columns is always null.
+ After the full table scan the function calls collect_statistics_for_index
+ for each table index. The latter performs full index scan for each index.
+
+ @note
+ Currently the statistical data is collected indiscriminately for all
+ columns/indexes of 'table', for all statistical characteristics.
+ TODO. Collect only specified statistical characteristics for specified
+ columns/indexes.
+
+ @note
+ Currently the process of collecting statistical data is not optimized.
+ For example, 'avg_frequency' for a column could be copied from the
+ 'avg_frequency' collected for an index if this column is used as the
+ first component of the index. Min and min values for this column could
+ be extracted from the index as well.
+*/
+
+int collect_statistics_for_table(THD *thd, TABLE *table)
+{
+ int rc;
+ Field **field_ptr;
+ Field *table_field;
+ ha_rows rows= 0;
+ handler *file=table->file;
+
+ DBUG_ENTER("collect_statistics_for_table");
+
+ table->collected_stats->cardinality_is_null= TRUE;
+ table->collected_stats->cardinality= 0;
+
+ for (field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ table_field= *field_ptr;
+ if (!bitmap_is_set(table->read_set, table_field->field_index))
+ continue;
+ table_field->collected_stats->init(thd, table_field);
+ }
+
+ restore_record(table, s->default_values);
+
+ /* Perform a full table scan to collect statistics on 'table's columns */
+ if (!(rc= file->ha_rnd_init(TRUE)))
+ {
+ DEBUG_SYNC(table->in_use, "statistics_collection_start");
+
+ while ((rc= file->ha_rnd_next(table->record[0])) != HA_ERR_END_OF_FILE)
+ {
+ if (thd->killed)
+ break;
+
+ if (rc)
+ {
+ if (rc == HA_ERR_RECORD_DELETED)
+ continue;
+ break;
+ }
+
+ for (field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ table_field= *field_ptr;
+ if (!bitmap_is_set(table->read_set, table_field->field_index))
+ continue;
+ if ((rc= table_field->collected_stats->add(rows)))
+ break;
+ }
+ if (rc)
+ break;
+ rows++;
+ }
+ file->ha_rnd_end();
+ }
+ rc= (rc == HA_ERR_END_OF_FILE && !thd->killed) ? 0 : 1;
+
+ /*
+ Calculate values for all statistical characteristics on columns and
+ and for each field f of 'table' save them in the write_stat structure
+ from the Field object for f.
+ */
+ if (!rc)
+ {
+ table->collected_stats->cardinality_is_null= FALSE;
+ table->collected_stats->cardinality= rows;
+ }
+
+ bitmap_clear_all(table->write_set);
+ for (field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ table_field= *field_ptr;
+ if (!bitmap_is_set(table->read_set, table_field->field_index))
+ continue;
+ bitmap_set_bit(table->write_set, table_field->field_index);
+ if (!rc)
+ table_field->collected_stats->finish(rows);
+ else
+ table_field->collected_stats->cleanup();
+ }
+ bitmap_clear_all(table->write_set);
+
+ if (!rc)
+ {
+ uint key;
+ key_map::Iterator it(table->keys_in_use_for_query);
+
+ MY_BITMAP *save_read_set= table->read_set;
+ table->read_set= &table->tmp_set;
+ bitmap_set_all(table->read_set);
+
+ /* Collect statistics for indexes */
+ while ((key= it++) != key_map::Iterator::BITMAP_END)
+ {
+ if ((rc= collect_statistics_for_index(thd, table, key)))
+ break;
+ }
+
+ table->read_set= save_read_set;
+ }
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Update statistics for a table in the persistent statistical tables
+
+ @param
+ thd The thread handle
+ @param
+ table The table to collect statistics on
+
+ @details
+ For each statistical table st the function looks for the rows from this
+ table that contain statistical data on 'table'. If rows with given
+ statistical characteristics exist they are updated with the new statistical
+ values taken from internal structures for 'table'. Otherwise new rows
+ with these statistical characteristics are added into st.
+ It is assumed that values stored in the statistical tables are found and
+ saved by the function collect_statistics_for_table.
+
+ @retval
+ 0 If all statistical tables has been successfully updated
+ @retval
+ 1 Otherwise
+
+ @note
+ The function is called when executing the ANALYZE actions for 'table'.
+ The function first unlocks the opened table the statistics on which has
+ been collected, but does not closes it, so all collected statistical data
+ remains in internal structures for 'table'. Then the function opens the
+ statistical tables and writes the statistical data for 'table'into them.
+ It is not allowed just to open statistical tables for writing when some
+ other tables are locked for reading.
+ After the statistical tables have been opened they are updated one by one
+ with the new statistics on 'table'. Objects of the helper classes
+ Table_stat, Column_stat and Index_stat are employed for this.
+ After having been updated the statistical system tables are closed.
+*/
+
+int update_statistics_for_table(THD *thd, TABLE *table)
+{
+ TABLE_LIST tables[STATISTICS_TABLES];
+ Open_tables_backup open_tables_backup;
+ uint i;
+ int err;
+ enum_binlog_format save_binlog_format;
+ int rc= 0;
+ TABLE *stat_table;
+
+ DBUG_ENTER("update_statistics_for_table");
+
+ DEBUG_SYNC(thd, "statistics_update_start");
+
+ if (open_stat_tables(thd, tables, &open_tables_backup, TRUE))
+ {
+ thd->clear_error();
+ DBUG_RETURN(rc);
+ }
+
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ /* Update the statistical table table_stats */
+ stat_table= tables[TABLE_STAT].table;
+ Table_stat table_stat(stat_table, table);
+ restore_record(stat_table, s->default_values);
+ table_stat.set_key_fields();
+ err= table_stat.update_stat();
+ if (err)
+ rc= 1;
+
+ /* Update the statistical table colum_stats */
+ stat_table= tables[COLUMN_STAT].table;
+ Column_stat column_stat(stat_table, table);
+ for (Field **field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ Field *table_field= *field_ptr;
+ if (!bitmap_is_set(table->read_set, table_field->field_index))
+ continue;
+ restore_record(stat_table, s->default_values);
+ column_stat.set_key_fields(table_field);
+ err= column_stat.update_stat();
+ if (err && !rc)
+ rc= 1;
+ }
+
+ /* Update the statistical table index_stats */
+ stat_table= tables[INDEX_STAT].table;
+ uint key;
+ key_map::Iterator it(table->keys_in_use_for_query);
+ Index_stat index_stat(stat_table, table);
+
+ while ((key= it++) != key_map::Iterator::BITMAP_END)
+ {
+ KEY *key_info= table->key_info+key;
+ uint key_parts= table->actual_n_key_parts(key_info);
+ for (i= 0; i < key_parts; i++)
+ {
+ restore_record(stat_table, s->default_values);
+ index_stat.set_key_fields(key_info, i+1);
+ err= index_stat.update_stat();
+ if (err && !rc)
+ rc= 1;
+ }
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Read statistics for a table from the persistent statistical tables
+
+ @param
+ thd The thread handle
+ @param
+ table The table to read statistics on
+ @param
+ stat_tables The array of TABLE_LIST objects for statistical tables
+
+ @details
+ For each statistical table the function looks for the rows from this
+ table that contain statistical data on 'table'. If such rows is found
+ the data from statistical columns of it is read into the appropriate
+ fields of internal structures for 'table'. Later at the query processing
+ this data are supposed to be used by the optimizer.
+ The parameter stat_tables should point to an array of TABLE_LIST
+ objects for all statistical tables linked into a list. All statistical
+ tables are supposed to be opened.
+ The function is called by read_statistics_for_tables_if_needed().
+
+ @retval
+ 0 If data has been successfully read for the table
+ @retval
+ 1 Otherwise
+
+ @note
+ Objects of the helper classes Table_stat, Column_stat and Index_stat
+ are employed to read statistical data from the statistical tables.
+ now.
+*/
+
+static
+int read_statistics_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables)
+{
+ uint i;
+ TABLE *stat_table;
+ Field *table_field;
+ Field **field_ptr;
+ KEY *key_info, *key_info_end;
+ TABLE_SHARE *table_share= table->s;
+ Table_statistics *read_stats= table_share->stats_cb.table_stats;
+
+ DBUG_ENTER("read_statistics_for_table");
+
+ /* Read statistics from the statistical table table_stats */
+ stat_table= stat_tables[TABLE_STAT].table;
+ Table_stat table_stat(stat_table, table);
+ table_stat.set_key_fields();
+ table_stat.get_stat_values();
+
+ /* Read statistics from the statistical table column_stats */
+ stat_table= stat_tables[COLUMN_STAT].table;
+ ulong total_hist_size= 0;
+ Column_stat column_stat(stat_table, table);
+ for (field_ptr= table_share->field; *field_ptr; field_ptr++)
+ {
+ table_field= *field_ptr;
+ column_stat.set_key_fields(table_field);
+ column_stat.get_stat_values();
+ total_hist_size+= table_field->read_stats->histogram.get_size();
+ }
+ read_stats->total_hist_size= total_hist_size;
+
+ /* Read statistics from the statistical table index_stats */
+ stat_table= stat_tables[INDEX_STAT].table;
+ Index_stat index_stat(stat_table, table);
+ for (key_info= table_share->key_info,
+ key_info_end= key_info + table_share->keys;
+ key_info < key_info_end; key_info++)
+ {
+ uint key_parts= key_info->ext_key_parts;
+ for (i= 0; i < key_parts; i++)
+ {
+ index_stat.set_key_fields(key_info, i+1);
+ index_stat.get_stat_values();
+ }
+
+ key_part_map ext_key_part_map= key_info->ext_key_part_map;
+ if (key_info->user_defined_key_parts != key_info->ext_key_parts &&
+ key_info->read_stats->get_avg_frequency(key_info->user_defined_key_parts) == 0)
+ {
+ KEY *pk_key_info= table_share->key_info + table_share->primary_key;
+ uint k= key_info->user_defined_key_parts;
+ uint pk_parts= pk_key_info->user_defined_key_parts;
+ ha_rows n_rows= read_stats->cardinality;
+ double k_dist= n_rows / key_info->read_stats->get_avg_frequency(k-1);
+ uint m= 0;
+ for (uint j= 0; j < pk_parts; j++)
+ {
+ if (!(ext_key_part_map & 1 << j))
+ {
+ for (uint l= k; l < k + m; l++)
+ {
+ double avg_frequency=
+ pk_key_info->read_stats->get_avg_frequency(j-1);
+ set_if_smaller(avg_frequency, 1);
+ double val= pk_key_info->read_stats->get_avg_frequency(j) /
+ avg_frequency;
+ key_info->read_stats->set_avg_frequency (l, val);
+ }
+ }
+ else
+ {
+ double avg_frequency= pk_key_info->read_stats->get_avg_frequency(j);
+ key_info->read_stats->set_avg_frequency(k + m, avg_frequency);
+ m++;
+ }
+ }
+ for (uint l= k; l < k + m; l++)
+ {
+ double avg_frequency= key_info->read_stats->get_avg_frequency(l);
+ if (avg_frequency == 0 || read_stats->cardinality_is_null)
+ avg_frequency= 1;
+ else if (avg_frequency > 1)
+ {
+ avg_frequency/= k_dist;
+ set_if_bigger(avg_frequency, 1);
+ }
+ key_info->read_stats->set_avg_frequency(l, avg_frequency);
+ }
+ }
+ }
+
+ table->stats_is_read= TRUE;
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Check whether any statistics is to be read for tables from a table list
+
+ @param
+ thd The thread handle
+ @param
+ tables The tables list for whose tables the check is to be done
+
+ @details
+ The function checks whether for any of the tables opened and locked for
+ a statement statistics from statistical tables is needed to be read.
+
+ @retval
+ TRUE statistics for any of the tables is needed to be read
+ @retval
+ FALSE Otherwise
+*/
+
+static
+bool statistics_for_tables_is_needed(THD *thd, TABLE_LIST *tables)
+{
+ if (!tables)
+ return FALSE;
+
+ if (!statistics_for_command_is_needed(thd))
+ return FALSE;
+
+ /*
+ Do not read statistics for any query over non-user tables.
+ If the query references some statistical tables, but not all
+ of them, reading the statistics may lead to a deadlock
+ */
+ for (TABLE_LIST *tl= tables; tl; tl= tl->next_global)
+ {
+ if (!tl->is_view_or_derived() && tl->table)
+ {
+ TABLE_SHARE *table_share= tl->table->s;
+ if (table_share &&
+ (table_share->table_category != TABLE_CATEGORY_USER ||
+ table_share->tmp_table != NO_TMP_TABLE))
+ return FALSE;
+ }
+ }
+
+ for (TABLE_LIST *tl= tables; tl; tl= tl->next_global)
+ {
+ if (!tl->is_view_or_derived() && tl->table)
+ {
+ TABLE_SHARE *table_share= tl->table->s;
+ if (table_share &&
+ table_share->stats_cb.stats_can_be_read &&
+ (!table_share->stats_cb.stats_is_read ||
+ (!table_share->stats_cb.histograms_are_read &&
+ thd->variables.optimizer_use_condition_selectivity > 3)))
+ return TRUE;
+ if (table_share->stats_cb.stats_is_read)
+ tl->table->stats_is_read= TRUE;
+ if (table_share->stats_cb.histograms_are_read)
+ tl->table->histograms_are_read= TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/**
+ @brief
+ Read histogram for a table from the persistent statistical tables
+
+ @param
+ thd The thread handle
+ @param
+ table The table to read histograms for
+ @param
+ stat_tables The array of TABLE_LIST objects for statistical tables
+
+ @details
+ For the statistical table columns_stats the function looks for the rows
+ from this table that contain statistical data on 'table'. If such rows
+ are found the histograms from them are read into the memory allocated
+ for histograms of 'table'. Later at the query processing these histogram
+ are supposed to be used by the optimizer.
+ The parameter stat_tables should point to an array of TABLE_LIST
+ objects for all statistical tables linked into a list. All statistical
+ tables are supposed to be opened.
+ The function is called by read_statistics_for_tables_if_needed().
+
+ @retval
+ 0 If data has been successfully read for the table
+ @retval
+ 1 Otherwise
+
+ @note
+ Objects of the helper Column_stat are employed read histogram
+ from the statistical table column_stats now.
+*/
+
+static
+int read_histograms_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables)
+{
+ TABLE_SHARE *table_share= table->s;
+
+ DBUG_ENTER("read_histograms_for_table");
+
+ if (!table_share->stats_cb.histograms_can_be_read)
+ {
+ (void) alloc_histograms_for_table_share(thd, table_share, FALSE);
+ }
+ if (table_share->stats_cb.histograms_can_be_read &&
+ !table_share->stats_cb.histograms_are_read)
+ {
+ Field **field_ptr;
+ uchar *histogram= table_share->stats_cb.table_stats->histograms;
+ TABLE *stat_table= stat_tables[COLUMN_STAT].table;
+ Column_stat column_stat(stat_table, table);
+ for (field_ptr= table_share->field; *field_ptr; field_ptr++)
+ {
+ Field *table_field= *field_ptr;
+ uint hist_size= table_field->read_stats->histogram.get_size();
+ if (hist_size)
+ {
+ column_stat.set_key_fields(table_field);
+ table_field->read_stats->histogram.set_values(histogram);
+ column_stat.get_histogram_value();
+ histogram+= hist_size;
+ }
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+/**
+ @brief
+ Read statistics for tables from a table list if it is needed
+
+ @param
+ thd The thread handle
+ @param
+ tables The tables list for whose tables to read statistics
+
+ @details
+ The function first checks whether for any of the tables opened and locked
+ for a statement statistics from statistical tables is needed to be read.
+ Then, if so, it opens system statistical tables for read and reads
+ the statistical data from them for those tables from the list for which it
+ makes sense. Then the function closes system statistical tables.
+
+ @retval
+ 0 Statistics for tables was successfully read
+ @retval
+ 1 Otherwise
+*/
+
+int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables)
+{
+ TABLE_LIST stat_tables[STATISTICS_TABLES];
+ Open_tables_backup open_tables_backup;
+
+ DBUG_ENTER("read_statistics_for_tables_if_needed");
+
+ DEBUG_SYNC(thd, "statistics_read_start");
+
+ if (!statistics_for_tables_is_needed(thd, tables))
+ DBUG_RETURN(0);
+
+ if (open_stat_tables(thd, stat_tables, &open_tables_backup, FALSE))
+ {
+ thd->clear_error();
+ DBUG_RETURN(1);
+ }
+
+ for (TABLE_LIST *tl= tables; tl; tl= tl->next_global)
+ {
+ if (!tl->is_view_or_derived() && tl->table)
+ {
+ TABLE_SHARE *table_share= tl->table->s;
+ if (table_share &&
+ table_share->stats_cb.stats_can_be_read &&
+ !table_share->stats_cb.stats_is_read)
+ {
+ (void) read_statistics_for_table(thd, tl->table, stat_tables);
+ table_share->stats_cb.stats_is_read= TRUE;
+ }
+ if (table_share->stats_cb.stats_is_read)
+ tl->table->stats_is_read= TRUE;
+ if (thd->variables.optimizer_use_condition_selectivity > 3 &&
+ table_share && !table_share->stats_cb.histograms_are_read)
+ {
+ (void) read_histograms_for_table(thd, tl->table, stat_tables);
+ table_share->stats_cb.histograms_are_read= TRUE;
+ }
+ if (table_share->stats_cb.stats_is_read)
+ tl->table->histograms_are_read= TRUE;
+ }
+ }
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Delete statistics on a table from all statistical tables
+
+ @param
+ thd The thread handle
+ @param
+ db The name of the database the table belongs to
+ @param
+ tab The name of the table whose statistics is to be deleted
+
+ @details
+ The function delete statistics on the table called 'tab' of the database
+ 'db' from all statistical tables: table_stats, column_stats, index_stats.
+
+ @retval
+ 0 If all deletions are successful
+ @retval
+ 1 Otherwise
+
+ @note
+ The function is called when executing the statement DROP TABLE 'tab'.
+*/
+
+int delete_statistics_for_table(THD *thd, LEX_STRING *db, LEX_STRING *tab)
+{
+ int err;
+ enum_binlog_format save_binlog_format;
+ TABLE *stat_table;
+ TABLE_LIST tables[STATISTICS_TABLES];
+ Open_tables_backup open_tables_backup;
+ int rc= 0;
+
+ DBUG_ENTER("delete_statistics_for_table");
+
+ if (open_stat_tables(thd, tables, &open_tables_backup, TRUE))
+ {
+ thd->clear_error();
+ DBUG_RETURN(rc);
+ }
+
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ /* Delete statistics on table from the statistical table index_stats */
+ stat_table= tables[INDEX_STAT].table;
+ Index_stat index_stat(stat_table, db, tab);
+ index_stat.set_full_table_name();
+ while (index_stat.find_next_stat_for_prefix(2))
+ {
+ err= index_stat.delete_stat();
+ if (err & !rc)
+ rc= 1;
+ }
+
+ /* Delete statistics on table from the statistical table column_stats */
+ stat_table= tables[COLUMN_STAT].table;
+ Column_stat column_stat(stat_table, db, tab);
+ column_stat.set_full_table_name();
+ while (column_stat.find_next_stat_for_prefix(2))
+ {
+ err= column_stat.delete_stat();
+ if (err & !rc)
+ rc= 1;
+ }
+
+ /* Delete statistics on table from the statistical table table_stats */
+ stat_table= tables[TABLE_STAT].table;
+ Table_stat table_stat(stat_table, db, tab);
+ table_stat.set_key_fields();
+ if (table_stat.find_stat())
+ {
+ err= table_stat.delete_stat();
+ if (err & !rc)
+ rc= 1;
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Delete statistics on a column of the specified table
+
+ @param
+ thd The thread handle
+ @param
+ tab The table the column belongs to
+ @param
+ col The field of the column whose statistics is to be deleted
+
+ @details
+ The function delete statistics on the column 'col' belonging to the table
+ 'tab' from the statistical table column_stats.
+
+ @retval
+ 0 If the deletion is successful
+ @retval
+ 1 Otherwise
+
+ @note
+ The function is called when dropping a table column or when changing
+ the definition of this column.
+*/
+
+int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col)
+{
+ int err;
+ enum_binlog_format save_binlog_format;
+ TABLE *stat_table;
+ TABLE_LIST tables;
+ Open_tables_backup open_tables_backup;
+ int rc= 0;
+
+ DBUG_ENTER("delete_statistics_for_column");
+
+ if (open_single_stat_table(thd, &tables, &stat_table_name[1],
+ &open_tables_backup, TRUE))
+ {
+ thd->clear_error();
+ DBUG_RETURN(rc);
+ }
+
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ stat_table= tables.table;
+ Column_stat column_stat(stat_table, tab);
+ column_stat.set_key_fields(col);
+ if (column_stat.find_stat())
+ {
+ err= column_stat.delete_stat();
+ if (err)
+ rc= 1;
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Delete statistics on an index of the specified table
+
+ @param
+ thd The thread handle
+ @param
+ tab The table the index belongs to
+ @param
+ key_info The descriptor of the index whose statistics is to be deleted
+ @param
+ ext_prefixes_only Delete statistics only on the index prefixes extended by
+ the components of the primary key
+
+ @details
+ The function delete statistics on the index specified by 'key_info'
+ defined on the table 'tab' from the statistical table index_stats.
+
+ @retval
+ 0 If the deletion is successful
+ @retval
+ 1 Otherwise
+
+ @note
+ The function is called when dropping an index, or dropping/changing the
+ definition of a column used in the definition of the index.
+*/
+
+int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info,
+ bool ext_prefixes_only)
+{
+ int err;
+ enum_binlog_format save_binlog_format;
+ TABLE *stat_table;
+ TABLE_LIST tables;
+ Open_tables_backup open_tables_backup;
+ int rc= 0;
+
+ DBUG_ENTER("delete_statistics_for_index");
+
+ if (open_single_stat_table(thd, &tables, &stat_table_name[2],
+ &open_tables_backup, TRUE))
+ {
+ thd->clear_error();
+ DBUG_RETURN(rc);
+ }
+
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ stat_table= tables.table;
+ Index_stat index_stat(stat_table, tab);
+ if (!ext_prefixes_only)
+ {
+ index_stat.set_index_prefix_key_fields(key_info);
+ while (index_stat.find_next_stat_for_prefix(3))
+ {
+ err= index_stat.delete_stat();
+ if (err && !rc)
+ rc= 1;
+ }
+ }
+ else
+ {
+ for (uint i= key_info->user_defined_key_parts; i < key_info->ext_key_parts; i++)
+ {
+ index_stat.set_key_fields(key_info, i+1);
+ if (index_stat.find_next_stat_for_prefix(4))
+ {
+ err= index_stat.delete_stat();
+ if (err && !rc)
+ rc= 1;
+ }
+ }
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Rename a table in all statistical tables
+
+ @param
+ thd The thread handle
+ @param
+ db The name of the database the table belongs to
+ @param
+ tab The name of the table to be renamed in statistical tables
+ @param
+ new_tab The new name of the table
+
+ @details
+ The function replaces the name of the table 'tab' from the database 'db'
+ for 'new_tab' in all all statistical tables: table_stats, column_stats,
+ index_stats.
+
+ @retval
+ 0 If all updates of the table name are successful
+ @retval
+ 1 Otherwise
+
+ @note
+ The function is called when executing any statement that renames a table
+*/
+
+int rename_table_in_stat_tables(THD *thd, LEX_STRING *db, LEX_STRING *tab,
+ LEX_STRING *new_db, LEX_STRING *new_tab)
+{
+ int err;
+ enum_binlog_format save_binlog_format;
+ TABLE *stat_table;
+ TABLE_LIST tables[STATISTICS_TABLES];
+ Open_tables_backup open_tables_backup;
+ int rc= 0;
+
+ DBUG_ENTER("rename_table_in_stat_tables");
+
+ if (open_stat_tables(thd, tables, &open_tables_backup, TRUE))
+ {
+ thd->clear_error();
+ DBUG_RETURN(rc);
+ }
+
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ /* Rename table in the statistical table index_stats */
+ stat_table= tables[INDEX_STAT].table;
+ Index_stat index_stat(stat_table, db, tab);
+ index_stat.set_full_table_name();
+ while (index_stat.find_next_stat_for_prefix(2))
+ {
+ err= index_stat.update_table_name_key_parts(new_db, new_tab);
+ if (err & !rc)
+ rc= 1;
+ index_stat.set_full_table_name();
+ }
+
+ /* Rename table in the statistical table column_stats */
+ stat_table= tables[COLUMN_STAT].table;
+ Column_stat column_stat(stat_table, db, tab);
+ column_stat.set_full_table_name();
+ while (column_stat.find_next_stat_for_prefix(2))
+ {
+ err= column_stat.update_table_name_key_parts(new_db, new_tab);
+ if (err & !rc)
+ rc= 1;
+ column_stat.set_full_table_name();
+ }
+
+ /* Rename table in the statistical table table_stats */
+ stat_table= tables[TABLE_STAT].table;
+ Table_stat table_stat(stat_table, db, tab);
+ table_stat.set_key_fields();
+ if (table_stat.find_stat())
+ {
+ err= table_stat.update_table_name_key_parts(new_db, new_tab);
+ if (err & !rc)
+ rc= 1;
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Rename a column in the statistical table column_stats
+
+ @param
+ thd The thread handle
+ @param
+ tab The table the column belongs to
+ @param
+ col The column to be renamed
+ @param
+ new_name The new column name
+
+ @details
+ The function replaces the name of the column 'col' belonging to the table
+ 'tab' for 'new_name' in the statistical table column_stats.
+
+ @retval
+ 0 If all updates of the table name are successful
+ @retval
+ 1 Otherwise
+
+ @note
+ The function is called when executing any statement that renames a column,
+ but does not change the column definition.
+*/
+
+int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col,
+ const char *new_name)
+{
+ int err;
+ enum_binlog_format save_binlog_format;
+ TABLE *stat_table;
+ TABLE_LIST tables;
+ Open_tables_backup open_tables_backup;
+ int rc= 0;
+
+ DBUG_ENTER("rename_column_in_stat_tables");
+
+ if (tab->s->tmp_table != NO_TMP_TABLE)
+ DBUG_RETURN(0);
+
+ if (open_single_stat_table(thd, &tables, &stat_table_name[1],
+ &open_tables_backup, TRUE))
+ {
+ thd->clear_error();
+ DBUG_RETURN(rc);
+ }
+
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ /* Rename column in the statistical table table_stat */
+ stat_table= tables.table;
+ Column_stat column_stat(stat_table, tab);
+ column_stat.set_key_fields(col);
+ if (column_stat.find_stat())
+ {
+ err= column_stat.update_column_key_part(new_name);
+ if (err & !rc)
+ rc= 1;
+ }
+
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ close_system_tables(thd, &open_tables_backup);
+
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Set statistics for a table that will be used by the optimizer
+
+ @param
+ thd The thread handle
+ @param
+ table The table to set statistics for
+
+ @details
+ Depending on the value of thd->variables.use_stat_tables
+ the function performs the settings for the table that will control
+ from where the statistical data used by the optimizer will be taken.
+*/
+
+void set_statistics_for_table(THD *thd, TABLE *table)
+{
+ TABLE_STATISTICS_CB *stats_cb= &table->s->stats_cb;
+ Table_statistics *read_stats= stats_cb->table_stats;
+ Use_stat_tables_mode use_stat_table_mode= get_use_stat_tables_mode(thd);
+ table->used_stat_records=
+ (use_stat_table_mode <= COMPLEMENTARY ||
+ !table->stats_is_read || read_stats->cardinality_is_null) ?
+ table->file->stats.records : read_stats->cardinality;
+ KEY *key_info, *key_info_end;
+ for (key_info= table->key_info, key_info_end= key_info+table->s->keys;
+ key_info < key_info_end; key_info++)
+ {
+ key_info->is_statistics_from_stat_tables=
+ (use_stat_table_mode > COMPLEMENTARY &&
+ table->stats_is_read &&
+ key_info->read_stats->avg_frequency_is_inited() &&
+ key_info->read_stats->get_avg_frequency(0) > 0.5);
+ }
+}
+
+
+/**
+ @brief
+ Get the average frequency for a column
+
+ @param
+ field The column whose average frequency is required
+
+ @retval
+ The required average frequency
+*/
+
+double get_column_avg_frequency(Field * field)
+{
+ double res;
+ TABLE *table= field->table;
+
+ /*
+ Statistics is shared by table instances and is accessed through
+ the table share. If table->s->field is not set for 'table', then
+ no column statistics is available for the table .
+ */
+ if (!table->s->field)
+ {
+ res= table->stat_records();
+ return res;
+ }
+
+ Column_statistics *col_stats= table->s->field[field->field_index]->read_stats;
+
+ if (!col_stats)
+ res= table->stat_records();
+ else
+ res= col_stats->get_avg_frequency();
+ return res;
+}
+
+
+/**
+ @brief
+ Estimate the number of rows in a column range using data from stat tables
+
+ @param
+ field The column whose range cardinality is to be estimated
+ @param
+ min_endp The left end of the range whose cardinality is required
+ @param
+ max_endp The right end of the range whose cardinality is required
+ @param
+ range_flag The range flags
+
+ @details
+ The function gets an estimate of the number of rows in a column range
+ using the statistical data from the table column_stats.
+
+ @retval
+ The required estimate of the rows in the column range
+*/
+
+double get_column_range_cardinality(Field *field,
+ key_range *min_endp,
+ key_range *max_endp,
+ uint range_flag)
+{
+ double res;
+ TABLE *table= field->table;
+ Column_statistics *col_stats= table->field[field->field_index]->read_stats;
+ double tab_records= table->stat_records();
+
+ if (!col_stats)
+ return tab_records;
+
+ double col_nulls= tab_records * col_stats->get_nulls_ratio();
+
+ double col_non_nulls= tab_records - col_nulls;
+
+ bool nulls_incl= field->null_ptr && min_endp && min_endp->key[0] &&
+ !(range_flag & NEAR_MIN);
+
+ if (col_non_nulls < 1)
+ {
+ if (nulls_incl)
+ res= col_nulls;
+ else
+ res= 0;
+ }
+ else if (min_endp && max_endp && min_endp->length == max_endp->length &&
+ !memcmp(min_endp->key, max_endp->key, min_endp->length))
+ {
+ if (nulls_incl)
+ {
+ /* This is null single point range */
+ res= col_nulls;
+ }
+ else
+ {
+ double avg_frequency= col_stats->get_avg_frequency();
+ res= avg_frequency;
+ /*
+ psergey-todo: what does check for min_value, max_value mean?
+ min/max_value are set to NULL in alloc_statistics_for_table() and
+ alloc_statistics_for_table_share(). Both functions will immediately
+ call create_min_max_statistical_fields_for_table and
+ create_min_max_statistical_fields_for_table_share() respectively,
+ which will set min/max_value to be valid pointers, unless OOM
+ occurs.
+ */
+ if (avg_frequency > 1.0 + 0.000001 &&
+ col_stats->min_value && col_stats->max_value)
+ {
+ Histogram *hist= &col_stats->histogram;
+ if (hist->is_available())
+ {
+ store_key_image_to_rec(field, (uchar *) min_endp->key,
+ field->key_length());
+ double pos= field->pos_in_interval(col_stats->min_value,
+ col_stats->max_value);
+ res= col_non_nulls *
+ hist->point_selectivity(pos,
+ avg_frequency / col_non_nulls);
+ }
+ }
+ else if (avg_frequency == 0.0)
+ {
+ /* This actually means there is no statistics data */
+ res= tab_records;
+ }
+ }
+ }
+ else
+ {
+ if (col_stats->min_value && col_stats->max_value)
+ {
+ double sel, min_mp_pos, max_mp_pos;
+
+ if (min_endp && !(field->null_ptr && min_endp->key[0]))
+ {
+ store_key_image_to_rec(field, (uchar *) min_endp->key,
+ field->key_length());
+ min_mp_pos= field->pos_in_interval(col_stats->min_value,
+ col_stats->max_value);
+ }
+ else
+ min_mp_pos= 0.0;
+ if (max_endp)
+ {
+ store_key_image_to_rec(field, (uchar *) max_endp->key,
+ field->key_length());
+ max_mp_pos= field->pos_in_interval(col_stats->min_value,
+ col_stats->max_value);
+ }
+ else
+ max_mp_pos= 1.0;
+
+ Histogram *hist= &col_stats->histogram;
+ if (!hist->is_available())
+ sel= (max_mp_pos - min_mp_pos);
+ else
+ sel= hist->range_selectivity(min_mp_pos, max_mp_pos);
+ res= col_non_nulls * sel;
+ set_if_bigger(res, col_stats->get_avg_frequency());
+ }
+ else
+ res= col_non_nulls;
+ if (nulls_incl)
+ res+= col_nulls;
+ }
+ return res;
+}
+
+
+
+/*
+ Estimate selectivity of "col=const" using a histogram
+
+ @param pos Position of the "const" between column's min_value and
+ max_value. This is a number in [0..1] range.
+ @param avg_sel Average selectivity of condition "col=const" in this table.
+ It is calcuated as (#non_null_values / #distinct_values).
+
+ @return
+ Expected condition selectivity (a number between 0 and 1)
+
+ @notes
+ [re_zero_length_buckets] If a bucket with zero value-length is in the
+ middle of the histogram, we will not have min==max. Example: suppose,
+ pos_value=0x12, and the histogram is:
+
+ #n #n+1 #n+2
+ ... 0x10 0x12 0x12 0x14 ...
+ |
+ +------------- bucket with zero value-length
+
+ Here, we will get min=#n+1, max=#n+2, and use the multi-bucket formula.
+
+ The problem happens at the histogram ends. if pos_value=0, and the
+ histogram is:
+
+ 0x00 0x10 ...
+
+ then min=0, max=0. This means pos_value is contained within bucket #0,
+ but on the other hand, histogram data says that the bucket has only one
+ value.
+*/
+
+double Histogram::point_selectivity(double pos, double avg_sel)
+{
+ double sel;
+ /* Find the bucket that contains the value 'pos'. */
+ uint min= find_bucket(pos, TRUE);
+ uint pos_value= (uint) (pos * prec_factor());
+
+ /* Find how many buckets this value occupies */
+ uint max= min;
+ while (max + 1 < get_width() && get_value(max + 1) == pos_value)
+ max++;
+
+ /*
+ A special case: we're looking at a single bucket, and that bucket has
+ zero value-length. Use the multi-bucket formula (attempt to use
+ single-bucket formula will cause divison by zero).
+
+ For more details see [re_zero_length_buckets] above.
+ */
+ if (max == min && get_value(max) == ((max==0)? 0 : get_value(max-1)))
+ max++;
+
+ if (max > min)
+ {
+ /*
+ The value occupies multiple buckets. Use start_bucket ... end_bucket as
+ selectivity.
+ */
+ double bucket_sel= 1.0/(get_width() + 1);
+ sel= bucket_sel * (max - min + 1);
+ }
+ else
+ {
+ /*
+ The value 'pos' fits within one single histogram bucket.
+
+ Histogram buckets have the same numbers of rows, but they cover
+ different ranges of values.
+
+ We assume that values are uniformly distributed across the [0..1] value
+ range.
+ */
+
+ /*
+ If all buckets covered value ranges of the same size, the width of
+ value range would be:
+ */
+ double avg_bucket_width= 1.0 / (get_width() + 1);
+
+ /*
+ Let's see what is the width of value range that our bucket is covering.
+ (min==max currently. they are kept in the formula just in case we
+ will want to extend it to handle multi-bucket case)
+ */
+ double inv_prec_factor= (double) 1.0 / prec_factor();
+ double current_bucket_width=
+ (max + 1 == get_width() ? 1.0 : (get_value(max) * inv_prec_factor)) -
+ (min == 0 ? 0.0 : (get_value(min-1) * inv_prec_factor));
+
+ DBUG_ASSERT(current_bucket_width); /* We shouldn't get a one zero-width bucket */
+
+ /*
+ So:
+ - each bucket has the same #rows
+ - values are unformly distributed across the [min_value,max_value] domain.
+
+ If a bucket has value range that's N times bigger then average, than
+ each value will have to have N times fewer rows than average.
+ */
+ sel= avg_sel * avg_bucket_width / current_bucket_width;
+
+ /*
+ (Q: if we just follow this proportion we may end up in a situation
+ where number of different values we expect to find in this bucket
+ exceeds the number of rows that this histogram has in a bucket. Are
+ we ok with this or we would want to have certain caps?)
+ */
+ }
+ return sel;
+}
+
diff --git a/sql/sql_statistics.h b/sql/sql_statistics.h
new file mode 100644
index 00000000000..46e5cef22d1
--- /dev/null
+++ b/sql/sql_statistics.h
@@ -0,0 +1,428 @@
+/* Copyright 2006-2008 MySQL AB, 2008 Sun Microsystems, Inc.
+
+ 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 */
+
+#ifndef SQL_STATISTICS_H
+#define SQL_STATISTICS_H
+
+typedef
+enum enum_use_stat_tables_mode
+{
+ NEVER,
+ COMPLEMENTARY,
+ PEFERABLY,
+} Use_stat_tables_mode;
+
+typedef
+enum enum_histogram_type
+{
+ SINGLE_PREC_HB,
+ DOUBLE_PREC_HB
+} Histogram_type;
+
+enum enum_stat_tables
+{
+ TABLE_STAT,
+ COLUMN_STAT,
+ INDEX_STAT,
+};
+
+
+/*
+ These enumeration types comprise the dictionary of three
+ statistical tables table_stat, column_stat and index_stat
+ as they defined in ../scripts/mysql_system_tables.sql.
+
+ It would be nice if the declarations of these types were
+ generated automatically by the table definitions.
+*/
+
+enum enum_table_stat_col
+{
+ TABLE_STAT_DB_NAME,
+ TABLE_STAT_TABLE_NAME,
+ TABLE_STAT_CARDINALITY
+};
+
+enum enum_column_stat_col
+{
+ COLUMN_STAT_DB_NAME,
+ COLUMN_STAT_TABLE_NAME,
+ COLUMN_STAT_COLUMN_NAME,
+ COLUMN_STAT_MIN_VALUE,
+ COLUMN_STAT_MAX_VALUE,
+ COLUMN_STAT_NULLS_RATIO,
+ COLUMN_STAT_AVG_LENGTH,
+ COLUMN_STAT_AVG_FREQUENCY,
+ COLUMN_STAT_HIST_SIZE,
+ COLUMN_STAT_HIST_TYPE,
+ COLUMN_STAT_HISTOGRAM
+};
+
+enum enum_index_stat_col
+{
+ INDEX_STAT_DB_NAME,
+ INDEX_STAT_TABLE_NAME,
+ INDEX_STAT_INDEX_NAME,
+ INDEX_STAT_PREFIX_ARITY,
+ INDEX_STAT_AVG_FREQUENCY
+};
+
+inline
+Use_stat_tables_mode get_use_stat_tables_mode(THD *thd)
+{
+ return (Use_stat_tables_mode) (thd->variables.use_stat_tables);
+}
+
+int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables);
+int collect_statistics_for_table(THD *thd, TABLE *table);
+int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *share,
+ bool is_safe);
+int alloc_statistics_for_table(THD *thd, TABLE *table);
+int update_statistics_for_table(THD *thd, TABLE *table);
+int delete_statistics_for_table(THD *thd, LEX_STRING *db, LEX_STRING *tab);
+int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col);
+int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info,
+ bool ext_prefixes_only);
+int rename_table_in_stat_tables(THD *thd, LEX_STRING *db, LEX_STRING *tab,
+ LEX_STRING *new_db, LEX_STRING *new_tab);
+int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col,
+ const char *new_name);
+void set_statistics_for_table(THD *thd, TABLE *table);
+
+double get_column_avg_frequency(Field * field);
+
+double get_column_range_cardinality(Field *field,
+ key_range *min_endp,
+ key_range *max_endp,
+ uint range_flag);
+
+class Histogram
+{
+
+private:
+ Histogram_type type;
+ uint8 size; /* Size of values array, in bytes */
+ uchar *values;
+
+ uint prec_factor()
+ {
+ switch (type) {
+ case SINGLE_PREC_HB:
+ return ((uint) (1 << 8) - 1);
+ case DOUBLE_PREC_HB:
+ return ((uint) (1 << 16) - 1);
+ }
+ return 1;
+ }
+
+public:
+ uint get_width()
+ {
+ switch (type) {
+ case SINGLE_PREC_HB:
+ return size;
+ case DOUBLE_PREC_HB:
+ return size / 2;
+ }
+ return 0;
+ }
+
+private:
+ uint get_value(uint i)
+ {
+ DBUG_ASSERT(i < get_width());
+ switch (type) {
+ case SINGLE_PREC_HB:
+ return (uint) (((uint8 *) values)[i]);
+ case DOUBLE_PREC_HB:
+ return (uint) uint2korr(values + i * 2);
+ }
+ return 0;
+ }
+
+ /* Find the bucket which value 'pos' falls into. */
+ uint find_bucket(double pos, bool first)
+ {
+ uint val= (uint) (pos * prec_factor());
+ int lp= 0;
+ int rp= get_width() - 1;
+ int d= get_width() / 2;
+ uint i= lp + d;
+ for ( ; d; d= (rp - lp) / 2, i= lp + d)
+ {
+ if (val == get_value(i))
+ break;
+ if (val < get_value(i))
+ rp= i;
+ else if (val > get_value(i + 1))
+ lp= i + 1;
+ else
+ break;
+ }
+
+ if (val > get_value(i) && i < (get_width() - 1))
+ i++;
+
+ if (val == get_value(i))
+ {
+ if (first)
+ {
+ while(i && val == get_value(i - 1))
+ i--;
+ }
+ else
+ {
+ while(i + 1 < get_width() && val == get_value(i + 1))
+ i++;
+ }
+ }
+ return i;
+ }
+
+public:
+
+ uint get_size() { return (uint) size; }
+
+ Histogram_type get_type() { return type; }
+
+ uchar *get_values() { return (uchar *) values; }
+
+ void set_size (ulonglong sz) { size= (uint8) sz; }
+
+ void set_type (Histogram_type t) { type= t; }
+
+ void set_values (uchar *vals) { values= (uchar *) vals; }
+
+ bool is_available() { return get_size() > 0 && get_values(); }
+
+ void set_value(uint i, double val)
+ {
+ switch (type) {
+ case SINGLE_PREC_HB:
+ ((uint8 *) values)[i]= (uint8) (val * prec_factor());
+ return;
+ case DOUBLE_PREC_HB:
+ int2store(values + i * 2, val * prec_factor());
+ return;
+ }
+ }
+
+ void set_prev_value(uint i)
+ {
+ switch (type) {
+ case SINGLE_PREC_HB:
+ ((uint8 *) values)[i]= ((uint8 *) values)[i-1];
+ return;
+ case DOUBLE_PREC_HB:
+ int2store(values + i * 2, uint2korr(values + i * 2 - 2));
+ return;
+ }
+ }
+
+ double range_selectivity(double min_pos, double max_pos)
+ {
+ double sel;
+ double bucket_sel= 1.0/(get_width() + 1);
+ uint min= find_bucket(min_pos, TRUE);
+ uint max= find_bucket(max_pos, FALSE);
+ sel= bucket_sel * (max - min + 1);
+ return sel;
+ }
+
+ /*
+ Estimate selectivity of "col=const" using a histogram
+ */
+ double point_selectivity(double pos, double avg_sel);
+};
+
+
+class Columns_statistics;
+class Index_statistics;
+
+static inline
+int rename_table_in_stat_tables(THD *thd, const char *db, const char *tab,
+ const char *new_db, const char *new_tab)
+{
+ LEX_STRING od= { const_cast<char*>(db), strlen(db) };
+ LEX_STRING ot= { const_cast<char*>(tab), strlen(tab) };
+ LEX_STRING nd= { const_cast<char*>(new_db), strlen(new_db) };
+ LEX_STRING nt= { const_cast<char*>(new_tab), strlen(new_tab) };
+ return rename_table_in_stat_tables(thd, &od, &ot, &nd, &nt);
+}
+
+
+/* Statistical data on a table */
+
+class Table_statistics
+{
+
+public:
+ my_bool cardinality_is_null; /* TRUE if the cardinality is unknown */
+ ha_rows cardinality; /* Number of rows in the table */
+ uchar *min_max_record_buffers; /* Record buffers for min/max values */
+ Column_statistics *column_stats; /* Array of statistical data for columns */
+ Index_statistics *index_stats; /* Array of statistical data for indexes */
+ ulong *idx_avg_frequency; /* Array of records per key for index prefixes */
+ ulong total_hist_size; /* Total size of all histograms */
+ uchar *histograms; /* Sequence of histograms */
+};
+
+
+/*
+ Statistical data on a column
+
+ Note: objects of this class may be "empty", where they have almost all fields
+ as zeros, for example, get_avg_frequency() will return 0.
+
+ objects are allocated in alloc_statistics_for_table[_share].
+*/
+
+class Column_statistics
+{
+
+private:
+ static const uint Scale_factor_nulls_ratio= 100000;
+ static const uint Scale_factor_avg_length= 100000;
+ static const uint Scale_factor_avg_frequency= 100000;
+
+public:
+ /*
+ Bitmap indicating what statistical characteristics
+ are available for the column
+ */
+ uint32 column_stat_nulls;
+
+ /* For the below two, see comments in get_column_range_cardinality() */
+ /* Minimum value for the column */
+ Field *min_value;
+ /* Maximum value for the column */
+ Field *max_value;
+
+private:
+
+ /*
+ The ratio Z/N multiplied by the scale factor Scale_factor_nulls_ratio,
+ where
+ N is the total number of rows,
+ Z is the number of nulls in the column
+ */
+ ulong nulls_ratio;
+
+ /*
+ Average number of bytes occupied by the representation of a
+ value of the column in memory buffers such as join buffer
+ multiplied by the scale factor Scale_factor_avg_length.
+ CHAR values are stripped of trailing spaces.
+ Flexible values are stripped of their length prefixes.
+ */
+ ulong avg_length;
+
+ /*
+ The ratio N/D multiplied by the scale factor Scale_factor_avg_frequency,
+ where
+ N is the number of rows with not null value in the column,
+ D the number of distinct values among them
+ */
+ ulong avg_frequency;
+
+public:
+
+ Histogram histogram;
+
+ void set_all_nulls()
+ {
+ column_stat_nulls=
+ ((1 << (COLUMN_STAT_HISTOGRAM-COLUMN_STAT_COLUMN_NAME))-1) <<
+ (COLUMN_STAT_COLUMN_NAME+1);
+ }
+
+ void set_not_null(uint stat_field_no)
+ {
+ column_stat_nulls&= ~(1 << stat_field_no);
+ }
+
+ bool is_null(uint stat_field_no)
+ {
+ return MY_TEST(column_stat_nulls & (1 << stat_field_no));
+ }
+
+ double get_nulls_ratio()
+ {
+ return (double) nulls_ratio / Scale_factor_nulls_ratio;
+ }
+
+ double get_avg_length()
+ {
+ return (double) avg_length / Scale_factor_avg_length;
+ }
+
+ double get_avg_frequency()
+ {
+ return (double) avg_frequency / Scale_factor_avg_frequency;
+ }
+
+ void set_nulls_ratio (double val)
+ {
+ nulls_ratio= (ulong) (val * Scale_factor_nulls_ratio);
+ }
+
+ void set_avg_length (double val)
+ {
+ avg_length= (ulong) (val * Scale_factor_avg_length);
+ }
+
+ void set_avg_frequency (double val)
+ {
+ avg_frequency= (ulong) (val * Scale_factor_avg_frequency);
+ }
+
+};
+
+
+/* Statistical data on an index prefixes */
+
+class Index_statistics
+{
+
+private:
+ static const uint Scale_factor_avg_frequency= 100000;
+ /*
+ The k-th element of this array contains the ratio N/D
+ multiplied by the scale factor Scale_factor_avg_frequency,
+ where N is the number of index entries without nulls
+ in the first k components, and D is the number of distinct
+ k-component prefixes among them
+ */
+ ulong *avg_frequency;
+
+public:
+
+ void init_avg_frequency(ulong *ptr) { avg_frequency= ptr; }
+
+ bool avg_frequency_is_inited() { return avg_frequency != NULL; }
+
+ double get_avg_frequency(uint i)
+ {
+ return (double) avg_frequency[i] / Scale_factor_avg_frequency;
+ }
+
+ void set_avg_frequency(uint i, double val)
+ {
+ avg_frequency[i]= (ulong) (val * Scale_factor_avg_frequency);
+ }
+
+};
+
+#endif /* SQL_STATISTICS_H */
diff --git a/sql/sql_string.cc b/sql/sql_string.cc
new file mode 100644
index 00000000000..a7bfa6c1455
--- /dev/null
+++ b/sql/sql_string.cc
@@ -0,0 +1,1179 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+
+ 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 */
+
+/* This file is originally from the mysql distribution. Coded by monty */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <m_string.h>
+#include <m_ctype.h>
+#include <mysql_com.h>
+
+#include "sql_string.h"
+
+/*****************************************************************************
+** String functions
+*****************************************************************************/
+
+bool String::real_alloc(uint32 length)
+{
+ uint32 arg_length= ALIGN_SIZE(length + 1);
+ DBUG_ASSERT(arg_length > length);
+ if (arg_length <= length)
+ return TRUE; /* Overflow */
+ str_length=0;
+ if (Alloced_length < arg_length)
+ {
+ free();
+ if (!(Ptr=(char*) my_malloc(arg_length,MYF(MY_WME |
+ (thread_specific ?
+ MY_THREAD_SPECIFIC : 0)))))
+ return TRUE;
+ Alloced_length=arg_length;
+ alloced=1;
+ }
+ Ptr[0]=0;
+ return FALSE;
+}
+
+
+/**
+ Allocates a new buffer on the heap for this String.
+
+ - If the String's internal buffer is privately owned and heap allocated,
+ one of the following is performed.
+
+ - If the requested length is greater than what fits in the buffer, a new
+ buffer is allocated, data moved and the old buffer freed.
+
+ - If the requested length is less or equal to what fits in the buffer, a
+ null character is inserted at the appropriate position.
+
+ - If the String does not keep a private buffer on the heap, such a buffer
+ will be allocated and the string copied accoring to its length, as found
+ in String::length().
+
+ For C compatibility, the new string buffer is null terminated.
+
+ @param alloc_length The requested string size in characters, excluding any
+ null terminator.
+
+ @retval false Either the copy operation is complete or, if the size of the
+ new buffer is smaller than the currently allocated buffer (if one exists),
+ no allocation occured.
+
+ @retval true An error occured when attempting to allocate memory.
+*/
+bool String::realloc_raw(uint32 alloc_length)
+{
+ if (Alloced_length <= alloc_length)
+ {
+ char *new_ptr;
+ uint32 len= ALIGN_SIZE(alloc_length+1);
+ DBUG_ASSERT(len > alloc_length);
+ if (len <= alloc_length)
+ return TRUE; /* Overflow */
+ if (alloced)
+ {
+ if (!(new_ptr= (char*) my_realloc(Ptr,len,
+ MYF(MY_WME |
+ (thread_specific ?
+ MY_THREAD_SPECIFIC : 0)))))
+ return TRUE; // Signal error
+ }
+ else if ((new_ptr= (char*) my_malloc(len,
+ MYF(MY_WME |
+ (thread_specific ?
+ MY_THREAD_SPECIFIC : 0)))))
+ {
+ if (str_length > len - 1)
+ str_length= 0;
+ if (str_length) // Avoid bugs in memcpy on AIX
+ memcpy(new_ptr,Ptr,str_length);
+ new_ptr[str_length]=0;
+ alloced=1;
+ }
+ else
+ return TRUE; // Signal error
+ Ptr= new_ptr;
+ Alloced_length= len;
+ }
+ return FALSE;
+}
+
+bool String::set_int(longlong num, bool unsigned_flag, CHARSET_INFO *cs)
+{
+ uint l=20*cs->mbmaxlen+1;
+ int base= unsigned_flag ? 10 : -10;
+
+ if (alloc(l))
+ return TRUE;
+ str_length=(uint32) (cs->cset->longlong10_to_str)(cs,Ptr,l,base,num);
+ str_charset=cs;
+ return FALSE;
+}
+
+bool String::set_real(double num,uint decimals, CHARSET_INFO *cs)
+{
+ char buff[FLOATING_POINT_BUFFER];
+ uint dummy_errors;
+ size_t len;
+
+ str_charset=cs;
+ if (decimals >= NOT_FIXED_DEC)
+ {
+ len= my_gcvt(num, MY_GCVT_ARG_DOUBLE, sizeof(buff) - 1, buff, NULL);
+ return copy(buff, len, &my_charset_latin1, cs, &dummy_errors);
+ }
+ len= my_fcvt(num, decimals, buff, NULL);
+ return copy(buff, (uint32) len, &my_charset_latin1, cs,
+ &dummy_errors);
+}
+
+
+bool String::copy()
+{
+ if (!alloced)
+ {
+ Alloced_length=0; // Force realloc
+ return realloc(str_length);
+ }
+ return FALSE;
+}
+
+/**
+ Copies the internal buffer from str. If this String has a private heap
+ allocated buffer where new data does not fit, a new buffer is allocated
+ before copying and the old buffer freed. Character set information is also
+ copied.
+
+ @param str The string whose internal buffer is to be copied.
+
+ @retval false Success.
+ @retval true Memory allocation failed.
+*/
+bool String::copy(const String &str)
+{
+ if (alloc(str.str_length))
+ return TRUE;
+ str_length=str.str_length;
+ bmove(Ptr,str.Ptr,str_length); // May be overlapping
+ Ptr[str_length]=0;
+ str_charset=str.str_charset;
+ return FALSE;
+}
+
+bool String::copy(const char *str,uint32 arg_length, CHARSET_INFO *cs)
+{
+ if (alloc(arg_length))
+ return TRUE;
+ if ((str_length=arg_length))
+ memcpy(Ptr,str,arg_length);
+ Ptr[arg_length]=0;
+ str_charset=cs;
+ return FALSE;
+}
+
+
+/*
+ Checks that the source string can be just copied to the destination string
+ without conversion.
+
+ SYNPOSIS
+
+ needs_conversion()
+ arg_length Length of string to copy.
+ from_cs Character set to copy from
+ to_cs Character set to copy to
+ uint32 *offset Returns number of unaligned characters.
+
+ RETURN
+ 0 No conversion needed
+ 1 Either character set conversion or adding leading zeros
+ (e.g. for UCS-2) must be done
+
+ NOTE
+ to_cs may be NULL for "no conversion" if the system variable
+ character_set_results is NULL.
+*/
+
+bool String::needs_conversion(uint32 arg_length,
+ CHARSET_INFO *from_cs,
+ CHARSET_INFO *to_cs,
+ uint32 *offset)
+{
+ *offset= 0;
+ if (!to_cs ||
+ (to_cs == &my_charset_bin) ||
+ (to_cs == from_cs) ||
+ my_charset_same(from_cs, to_cs) ||
+ ((from_cs == &my_charset_bin) &&
+ (!(*offset=(arg_length % to_cs->mbminlen)))))
+ return FALSE;
+ return TRUE;
+}
+
+
+/*
+ Checks that the source string can just be copied to the destination string
+ without conversion.
+ Unlike needs_conversion it will require conversion on incoming binary data
+ to ensure the data are verified for vailidity first.
+
+ @param arg_length Length of string to copy.
+ @param from_cs Character set to copy from
+ @param to_cs Character set to copy to
+
+ @return conversion needed
+*/
+bool String::needs_conversion_on_storage(uint32 arg_length,
+ CHARSET_INFO *cs_from,
+ CHARSET_INFO *cs_to)
+{
+ uint32 offset;
+ return (needs_conversion(arg_length, cs_from, cs_to, &offset) ||
+ /* force conversion when storing a binary string */
+ (cs_from == &my_charset_bin &&
+ /* into a non-binary destination */
+ cs_to != &my_charset_bin &&
+ /* and any of the following is true :*/
+ (
+ /* it's a variable length encoding */
+ cs_to->mbminlen != cs_to->mbmaxlen ||
+ /* longer than 2 bytes : neither 1 byte nor ucs2 */
+ cs_to->mbminlen > 2 ||
+ /* and is not a multiple of the char byte size */
+ 0 != (arg_length % cs_to->mbmaxlen)
+ )
+ )
+ );
+}
+
+
+/*
+ Copy a multi-byte character sets with adding leading zeros.
+
+ SYNOPSIS
+
+ copy_aligned()
+ str String to copy
+ arg_length Length of string. This should NOT be dividable with
+ cs->mbminlen.
+ offset arg_length % cs->mb_minlength
+ cs Character set for 'str'
+
+ NOTES
+ For real multi-byte, ascii incompatible charactser sets,
+ like UCS-2, add leading zeros if we have an incomplete character.
+ Thus,
+ SELECT _ucs2 0xAA
+ will automatically be converted into
+ SELECT _ucs2 0x00AA
+
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+bool String::copy_aligned(const char *str,uint32 arg_length, uint32 offset,
+ CHARSET_INFO *cs)
+{
+ /* How many bytes are in incomplete character */
+ offset= cs->mbminlen - offset; /* How many zeros we should prepend */
+ DBUG_ASSERT(offset && offset != cs->mbminlen);
+
+ uint32 aligned_length= arg_length + offset;
+ if (alloc(aligned_length))
+ return TRUE;
+
+ /*
+ Note, this is only safe for big-endian UCS-2.
+ If we add little-endian UCS-2 sometimes, this code
+ will be more complicated. But it's OK for now.
+ */
+ bzero((char*) Ptr, offset);
+ memcpy(Ptr + offset, str, arg_length);
+ Ptr[aligned_length]=0;
+ /* str_length is always >= 0 as arg_length is != 0 */
+ str_length= aligned_length;
+ str_charset= cs;
+ return FALSE;
+}
+
+
+bool String::set_or_copy_aligned(const char *str,uint32 arg_length,
+ CHARSET_INFO *cs)
+{
+ /* How many bytes are in incomplete character */
+ uint32 offset= (arg_length % cs->mbminlen);
+
+ if (!offset) /* All characters are complete, just copy */
+ {
+ set(str, arg_length, cs);
+ return FALSE;
+ }
+ return copy_aligned(str, arg_length, offset, cs);
+}
+
+
+/**
+ Copies the character data into this String, with optional character set
+ conversion.
+
+ @return
+ FALSE ok
+ TRUE Could not allocate result buffer
+
+*/
+
+bool String::copy(const char *str, uint32 arg_length,
+ CHARSET_INFO *from_cs, CHARSET_INFO *to_cs, uint *errors)
+{
+ uint32 offset;
+
+ DBUG_ASSERT(!str || str != Ptr);
+
+ if (!needs_conversion(arg_length, from_cs, to_cs, &offset))
+ {
+ *errors= 0;
+ return copy(str, arg_length, to_cs);
+ }
+ if ((from_cs == &my_charset_bin) && offset)
+ {
+ *errors= 0;
+ return copy_aligned(str, arg_length, offset, to_cs);
+ }
+ uint32 new_length= to_cs->mbmaxlen*arg_length;
+ if (alloc(new_length))
+ return TRUE;
+ str_length=copy_and_convert((char*) Ptr, new_length, to_cs,
+ str, arg_length, from_cs, errors);
+ str_charset=to_cs;
+ return FALSE;
+}
+
+
+/*
+ Set a string to the value of a latin1-string, keeping the original charset
+
+ SYNOPSIS
+ copy_or_set()
+ str String of a simple charset (latin1)
+ arg_length Length of string
+
+ IMPLEMENTATION
+ If string object is of a simple character set, set it to point to the
+ given string.
+ If not, make a copy and convert it to the new character set.
+
+ RETURN
+ 0 ok
+ 1 Could not allocate result buffer
+
+*/
+
+bool String::set_ascii(const char *str, uint32 arg_length)
+{
+ if (str_charset->mbminlen == 1)
+ {
+ set(str, arg_length, str_charset);
+ return 0;
+ }
+ uint dummy_errors;
+ return copy(str, arg_length, &my_charset_latin1, str_charset, &dummy_errors);
+}
+
+
+/* This is used by mysql.cc */
+
+bool String::fill(uint32 max_length,char fill_char)
+{
+ if (str_length > max_length)
+ Ptr[str_length=max_length]=0;
+ else
+ {
+ if (realloc(max_length))
+ return TRUE;
+ bfill(Ptr+str_length,max_length-str_length,fill_char);
+ str_length=max_length;
+ }
+ return FALSE;
+}
+
+void String::strip_sp()
+{
+ while (str_length && my_isspace(str_charset,Ptr[str_length-1]))
+ str_length--;
+}
+
+bool String::append(const String &s)
+{
+ if (s.length())
+ {
+ if (realloc_with_extra_if_needed(str_length+s.length()))
+ return TRUE;
+ memcpy(Ptr+str_length,s.ptr(),s.length());
+ str_length+=s.length();
+ }
+ return FALSE;
+}
+
+
+/*
+ Append an ASCII string to the a string of the current character set
+*/
+
+bool String::append(const char *s,uint32 arg_length)
+{
+ if (!arg_length)
+ return FALSE;
+
+ /*
+ For an ASCII incompatible string, e.g. UCS-2, we need to convert
+ */
+ if (str_charset->mbminlen > 1)
+ {
+ uint32 add_length=arg_length * str_charset->mbmaxlen;
+ uint dummy_errors;
+ if (realloc_with_extra_if_needed(str_length+ add_length))
+ return TRUE;
+ str_length+= copy_and_convert(Ptr+str_length, add_length, str_charset,
+ s, arg_length, &my_charset_latin1,
+ &dummy_errors);
+ return FALSE;
+ }
+
+ /*
+ For an ASCII compatinble string we can just append.
+ */
+ if (realloc_with_extra_if_needed(str_length+arg_length))
+ return TRUE;
+ memcpy(Ptr+str_length,s,arg_length);
+ str_length+=arg_length;
+ return FALSE;
+}
+
+
+/*
+ Append a 0-terminated ASCII string
+*/
+
+bool String::append(const char *s)
+{
+ return append(s, (uint) strlen(s));
+}
+
+
+
+bool String::append_ulonglong(ulonglong val)
+{
+ if (realloc(str_length+MAX_BIGINT_WIDTH+2))
+ return TRUE;
+ char *end= (char*) longlong10_to_str(val, (char*) Ptr + str_length, 10);
+ str_length= end - Ptr;
+ return FALSE;
+}
+
+/*
+ Append a string in the given charset to the string
+ with character set recoding
+*/
+
+bool String::append(const char *s,uint32 arg_length, CHARSET_INFO *cs)
+{
+ uint32 offset;
+
+ if (needs_conversion(arg_length, cs, str_charset, &offset))
+ {
+ uint32 add_length;
+ if ((cs == &my_charset_bin) && offset)
+ {
+ DBUG_ASSERT(str_charset->mbminlen > offset);
+ offset= str_charset->mbminlen - offset; // How many characters to pad
+ add_length= arg_length + offset;
+ if (realloc(str_length + add_length))
+ return TRUE;
+ bzero((char*) Ptr + str_length, offset);
+ memcpy(Ptr + str_length + offset, s, arg_length);
+ str_length+= add_length;
+ return FALSE;
+ }
+
+ add_length= arg_length / cs->mbminlen * str_charset->mbmaxlen;
+ uint dummy_errors;
+ if (realloc_with_extra_if_needed(str_length + add_length))
+ return TRUE;
+ str_length+= copy_and_convert(Ptr+str_length, add_length, str_charset,
+ s, arg_length, cs, &dummy_errors);
+ }
+ else
+ {
+ if (realloc_with_extra_if_needed(str_length + arg_length))
+ return TRUE;
+ memcpy(Ptr + str_length, s, arg_length);
+ str_length+= arg_length;
+ }
+ return FALSE;
+}
+
+bool String::append(IO_CACHE* file, uint32 arg_length)
+{
+ if (realloc_with_extra_if_needed(str_length+arg_length))
+ return TRUE;
+ if (my_b_read(file, (uchar*) Ptr + str_length, arg_length))
+ {
+ shrink(str_length);
+ return TRUE;
+ }
+ str_length+=arg_length;
+ return FALSE;
+}
+
+
+/**
+ Append a parenthesized number to String.
+ Used in various pieces of SHOW related code.
+
+ @param nr Number
+ @param radix Radix, optional parameter, 10 by default.
+*/
+bool String::append_parenthesized(long nr, int radix)
+{
+ char buff[64], *end;
+ buff[0]= '(';
+ end= int10_to_str(nr, buff + 1, radix);
+ *end++ = ')';
+ return append(buff, (uint) (end - buff));
+}
+
+
+bool String::append_with_prefill(const char *s,uint32 arg_length,
+ uint32 full_length, char fill_char)
+{
+ int t_length= arg_length > full_length ? arg_length : full_length;
+
+ if (realloc_with_extra_if_needed(str_length + t_length))
+ return TRUE;
+ t_length= full_length - arg_length;
+ if (t_length > 0)
+ {
+ bfill(Ptr+str_length, t_length, fill_char);
+ str_length=str_length + t_length;
+ }
+ append(s, arg_length);
+ return FALSE;
+}
+
+uint32 String::numchars() const
+{
+ return str_charset->cset->numchars(str_charset, Ptr, Ptr+str_length);
+}
+
+int String::charpos(longlong i,uint32 offset)
+{
+ if (i <= 0)
+ return (int)i;
+ return (int)str_charset->cset->charpos(str_charset,Ptr+offset,Ptr+str_length,(size_t)i);
+}
+
+int String::strstr(const String &s,uint32 offset)
+{
+ if (s.length()+offset <= str_length)
+ {
+ if (!s.length())
+ return ((int) offset); // Empty string is always found
+
+ register const char *str = Ptr+offset;
+ register const char *search=s.ptr();
+ const char *end=Ptr+str_length-s.length()+1;
+ const char *search_end=s.ptr()+s.length();
+skip:
+ while (str != end)
+ {
+ if (*str++ == *search)
+ {
+ register char *i,*j;
+ i=(char*) str; j=(char*) search+1;
+ while (j != search_end)
+ if (*i++ != *j++) goto skip;
+ return (int) (str-Ptr) -1;
+ }
+ }
+ }
+ return -1;
+}
+
+/*
+** Search string from end. Offset is offset to the end of string
+*/
+
+int String::strrstr(const String &s,uint32 offset)
+{
+ if (s.length() <= offset && offset <= str_length)
+ {
+ if (!s.length())
+ return offset; // Empty string is always found
+ register const char *str = Ptr+offset-1;
+ register const char *search=s.ptr()+s.length()-1;
+
+ const char *end=Ptr+s.length()-2;
+ const char *search_end=s.ptr()-1;
+skip:
+ while (str != end)
+ {
+ if (*str-- == *search)
+ {
+ register char *i,*j;
+ i=(char*) str; j=(char*) search-1;
+ while (j != search_end)
+ if (*i-- != *j--) goto skip;
+ return (int) (i-Ptr) +1;
+ }
+ }
+ }
+ return -1;
+}
+
+/*
+ Replace substring with string
+ If wrong parameter or not enough memory, do nothing
+*/
+
+bool String::replace(uint32 offset,uint32 arg_length,const String &to)
+{
+ return replace(offset,arg_length,to.ptr(),to.length());
+}
+
+bool String::replace(uint32 offset,uint32 arg_length,
+ const char *to, uint32 to_length)
+{
+ long diff = (long) to_length-(long) arg_length;
+ if (offset+arg_length <= str_length)
+ {
+ if (diff < 0)
+ {
+ if (to_length)
+ memcpy(Ptr+offset,to,to_length);
+ bmove(Ptr+offset+to_length,Ptr+offset+arg_length,
+ str_length-offset-arg_length);
+ }
+ else
+ {
+ if (diff)
+ {
+ if (realloc_with_extra_if_needed(str_length+(uint32) diff))
+ return TRUE;
+ bmove_upp((uchar*) Ptr+str_length+diff, (uchar*) Ptr+str_length,
+ str_length-offset-arg_length);
+ }
+ if (to_length)
+ memcpy(Ptr+offset,to,to_length);
+ }
+ str_length+=(uint32) diff;
+ }
+ return FALSE;
+}
+
+
+// added by Holyfoot for "geometry" needs
+int String::reserve(uint32 space_needed, uint32 grow_by)
+{
+ if (Alloced_length < str_length + space_needed)
+ {
+ if (realloc(Alloced_length + MY_MAX(space_needed, grow_by) - 1))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void String::qs_append(const char *str, uint32 len)
+{
+ memcpy(Ptr + str_length, str, len + 1);
+ str_length += len;
+}
+
+void String::qs_append(double d)
+{
+ char *buff = Ptr + str_length;
+ str_length+= my_gcvt(d, MY_GCVT_ARG_DOUBLE, FLOATING_POINT_BUFFER - 1, buff,
+ NULL);
+}
+
+void String::qs_append(double *d)
+{
+ double ld;
+ float8get(ld, (char*) d);
+ qs_append(ld);
+}
+
+void String::qs_append(int i)
+{
+ char *buff= Ptr + str_length;
+ char *end= int10_to_str(i, buff, -10);
+ str_length+= (int) (end-buff);
+}
+
+void String::qs_append(ulonglong i)
+{
+ char *buff= Ptr + str_length;
+ char *end= longlong10_to_str(i, buff,10);
+ str_length+= (int) (end-buff);
+}
+
+/*
+ Compare strings according to collation, without end space.
+
+ SYNOPSIS
+ sortcmp()
+ s First string
+ t Second string
+ cs Collation
+
+ NOTE:
+ Normally this is case sensitive comparison
+
+ RETURN
+ < 0 s < t
+ 0 s == t
+ > 0 s > t
+*/
+
+
+int sortcmp(const String *s,const String *t, CHARSET_INFO *cs)
+{
+ return cs->coll->strnncollsp(cs,
+ (uchar *) s->ptr(),s->length(),
+ (uchar *) t->ptr(),t->length(), 0);
+}
+
+
+/*
+ Compare strings byte by byte. End spaces are also compared.
+
+ SYNOPSIS
+ stringcmp()
+ s First string
+ t Second string
+
+ NOTE:
+ Strings are compared as a stream of uchars
+
+ RETURN
+ < 0 s < t
+ 0 s == t
+ > 0 s > t
+*/
+
+
+int stringcmp(const String *s,const String *t)
+{
+ uint32 s_len=s->length(),t_len=t->length(),len=MY_MIN(s_len,t_len);
+ int cmp= memcmp(s->ptr(), t->ptr(), len);
+ return (cmp) ? cmp : (int) (s_len - t_len);
+}
+
+
+String *copy_if_not_alloced(String *to,String *from,uint32 from_length)
+{
+ if (from->Alloced_length >= from_length)
+ return from;
+ if ((from->alloced && (from->Alloced_length != 0)) || !to || from == to)
+ {
+ (void) from->realloc(from_length);
+ return from;
+ }
+ if (to->realloc(from_length))
+ return from; // Actually an error
+ if ((to->str_length=MY_MIN(from->str_length,from_length)))
+ memcpy(to->Ptr,from->Ptr,to->str_length);
+ to->str_charset=from->str_charset;
+ return to;
+}
+
+
+/****************************************************************************
+ Help functions
+****************************************************************************/
+
+/**
+ Copy string with HEX-encoding of "bad" characters.
+
+ @details This functions copies the string pointed by "src"
+ to the string pointed by "dst". Not more than "srclen" bytes
+ are read from "src". Any sequences of bytes representing
+ a not-well-formed substring (according to cs) are hex-encoded,
+ and all well-formed substrings (according to cs) are copied as is.
+ Not more than "dstlen" bytes are written to "dst". The number
+ of bytes written to "dst" is returned.
+
+ @param cs character set pointer of the destination string
+ @param[out] dst destination string
+ @param dstlen size of dst
+ @param src source string
+ @param srclen length of src
+
+ @retval result length
+*/
+
+size_t
+my_copy_with_hex_escaping(CHARSET_INFO *cs,
+ char *dst, size_t dstlen,
+ const char *src, size_t srclen)
+{
+ const char *srcend= src + srclen;
+ char *dst0= dst;
+
+ for ( ; src < srcend ; )
+ {
+ size_t chlen;
+ if ((chlen= my_ismbchar(cs, src, srcend)))
+ {
+ if (dstlen < chlen)
+ break; /* purecov: inspected */
+ memcpy(dst, src, chlen);
+ src+= chlen;
+ dst+= chlen;
+ dstlen-= chlen;
+ }
+ else if (*src & 0x80)
+ {
+ if (dstlen < 4)
+ break; /* purecov: inspected */
+ *dst++= '\\';
+ *dst++= 'x';
+ *dst++= _dig_vec_upper[((unsigned char) *src) >> 4];
+ *dst++= _dig_vec_upper[((unsigned char) *src) & 15];
+ src++;
+ dstlen-= 4;
+ }
+ else
+ {
+ if (dstlen < 1)
+ break; /* purecov: inspected */
+ *dst++= *src++;
+ dstlen--;
+ }
+ }
+ return dst - dst0;
+}
+
+
+/*
+ copy a string,
+ with optional character set conversion,
+ with optional left padding (for binary -> UCS2 conversion)
+
+ SYNOPSIS
+ well_formed_copy_nchars()
+ to Store result here
+ to_length Maxinum length of "to" string
+ to_cs Character set of "to" string
+ from Copy from here
+ from_length Length of from string
+ from_cs From character set
+ nchars Copy not more that nchars characters
+ well_formed_error_pos Return position when "from" is not well formed
+ or NULL otherwise.
+ cannot_convert_error_pos Return position where a not convertable
+ character met, or NULL otherwise.
+ from_end_pos Return position where scanning of "from"
+ string stopped.
+ NOTES
+
+ RETURN
+ length of bytes copied to 'to'
+*/
+
+
+uint32
+well_formed_copy_nchars(CHARSET_INFO *to_cs,
+ char *to, uint to_length,
+ CHARSET_INFO *from_cs,
+ const char *from, uint from_length,
+ uint nchars,
+ const char **well_formed_error_pos,
+ const char **cannot_convert_error_pos,
+ const char **from_end_pos)
+{
+ uint res;
+
+ if ((to_cs == &my_charset_bin) ||
+ (from_cs == &my_charset_bin) ||
+ (to_cs == from_cs) ||
+ my_charset_same(from_cs, to_cs))
+ {
+ if (to_length < to_cs->mbminlen || !nchars)
+ {
+ *from_end_pos= from;
+ *cannot_convert_error_pos= NULL;
+ *well_formed_error_pos= NULL;
+ return 0;
+ }
+
+ if (to_cs == &my_charset_bin)
+ {
+ res= MY_MIN(MY_MIN(nchars, to_length), from_length);
+ memmove(to, from, res);
+ *from_end_pos= from + res;
+ *well_formed_error_pos= NULL;
+ *cannot_convert_error_pos= NULL;
+ }
+ else
+ {
+ int well_formed_error;
+ uint from_offset;
+
+ if ((from_offset= (from_length % to_cs->mbminlen)) &&
+ (from_cs == &my_charset_bin))
+ {
+ /*
+ Copying from BINARY to UCS2 needs to prepend zeros sometimes:
+ INSERT INTO t1 (ucs2_column) VALUES (0x01);
+ 0x01 -> 0x0001
+ */
+ uint pad_length= to_cs->mbminlen - from_offset;
+ bzero(to, pad_length);
+ memmove(to + pad_length, from, from_offset);
+ /*
+ In some cases left zero-padding can create an incorrect character.
+ For example:
+ INSERT INTO t1 (utf32_column) VALUES (0x110000);
+ We'll pad the value to 0x00110000, which is a wrong UTF32 sequence!
+ The valid characters range is limited to 0x00000000..0x0010FFFF.
+
+ Make sure we didn't pad to an incorrect character.
+ */
+ if (to_cs->cset->well_formed_len(to_cs,
+ to, to + to_cs->mbminlen, 1,
+ &well_formed_error) !=
+ to_cs->mbminlen)
+ {
+ *from_end_pos= *well_formed_error_pos= from;
+ *cannot_convert_error_pos= NULL;
+ return 0;
+ }
+ nchars--;
+ from+= from_offset;
+ from_length-= from_offset;
+ to+= to_cs->mbminlen;
+ to_length-= to_cs->mbminlen;
+ }
+
+ set_if_smaller(from_length, to_length);
+ res= to_cs->cset->well_formed_len(to_cs, from, from + from_length,
+ nchars, &well_formed_error);
+ memmove(to, from, res);
+ *from_end_pos= from + res;
+ *well_formed_error_pos= well_formed_error ? from + res : NULL;
+ *cannot_convert_error_pos= NULL;
+ if (from_offset)
+ res+= to_cs->mbminlen;
+ }
+ }
+ else
+ {
+ int cnvres;
+ my_wc_t wc;
+ my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc;
+ my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb;
+ const uchar *from_end= (const uchar*) from + from_length;
+ uchar *to_end= (uchar*) to + to_length;
+ char *to_start= to;
+ *well_formed_error_pos= NULL;
+ *cannot_convert_error_pos= NULL;
+
+ for ( ; nchars; nchars--)
+ {
+ const char *from_prev= from;
+ if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, from_end)) > 0)
+ from+= cnvres;
+ else if (cnvres == MY_CS_ILSEQ)
+ {
+ if (!*well_formed_error_pos)
+ *well_formed_error_pos= from;
+ from++;
+ wc= '?';
+ }
+ else if (cnvres > MY_CS_TOOSMALL)
+ {
+ /*
+ A correct multibyte sequence detected
+ But it doesn't have Unicode mapping.
+ */
+ if (!*cannot_convert_error_pos)
+ *cannot_convert_error_pos= from;
+ from+= (-cnvres);
+ wc= '?';
+ }
+ else
+ {
+ if ((uchar *) from >= from_end)
+ break; // End of line
+ // Incomplete byte sequence
+ if (!*well_formed_error_pos)
+ *well_formed_error_pos= from;
+ from++;
+ wc= '?';
+ }
+outp:
+ if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0)
+ to+= cnvres;
+ else if (cnvres == MY_CS_ILUNI && wc != '?')
+ {
+ if (!*cannot_convert_error_pos)
+ *cannot_convert_error_pos= from_prev;
+ wc= '?';
+ goto outp;
+ }
+ else
+ {
+ from= from_prev;
+ break;
+ }
+ }
+ *from_end_pos= from;
+ res= (uint) (to - to_start);
+ }
+ return (uint32) res;
+}
+
+
+
+/*
+ Append characters to a single-quoted string '...', escaping special
+ characters with backslashes as necessary.
+ Does not add the enclosing quotes, this is left up to caller.
+*/
+#define APPEND(X) if (append(X)) return 1; else break
+bool String::append_for_single_quote(const char *st, uint len)
+{
+ const char *end= st+len;
+ for (; st < end; st++)
+ {
+ uchar c= *st;
+ switch (c)
+ {
+ case '\\': APPEND(STRING_WITH_LEN("\\\\"));
+ case '\0': APPEND(STRING_WITH_LEN("\\0"));
+ case '\'': APPEND(STRING_WITH_LEN("\\'"));
+ case '\n': APPEND(STRING_WITH_LEN("\\n"));
+ case '\r': APPEND(STRING_WITH_LEN("\\r"));
+ case '\032': APPEND(STRING_WITH_LEN("\\Z"));
+ default: APPEND(c);
+ }
+ }
+ return 0;
+}
+
+void String::print(String *str) const
+{
+ str->append_for_single_quote(Ptr, str_length);
+}
+
+/*
+ Exchange state of this object and argument.
+
+ SYNOPSIS
+ String::swap()
+
+ RETURN
+ Target string will contain state of this object and vice versa.
+*/
+
+void String::swap(String &s)
+{
+ swap_variables(char *, Ptr, s.Ptr);
+ swap_variables(uint32, str_length, s.str_length);
+ swap_variables(uint32, Alloced_length, s.Alloced_length);
+ swap_variables(bool, alloced, s.alloced);
+ swap_variables(CHARSET_INFO*, str_charset, s.str_charset);
+}
+
+
+/**
+ Convert string to printable ASCII string
+
+ @details This function converts input string "from" replacing non-ASCII bytes
+ with hexadecimal sequences ("\xXX") optionally appending "..." to the end of
+ the resulting string.
+ This function used in the ER_TRUNCATED_WRONG_VALUE_FOR_FIELD error messages,
+ e.g. when a string cannot be converted to a result charset.
+
+
+ @param to output buffer
+ @param to_len size of the output buffer (8 bytes or greater)
+ @param from input string
+ @param from_len size of the input string
+ @param from_cs input charset
+ @param nbytes maximal number of bytes to convert (from_len if 0)
+
+ @return number of bytes in the output string
+*/
+
+uint convert_to_printable(char *to, size_t to_len,
+ const char *from, size_t from_len,
+ CHARSET_INFO *from_cs, size_t nbytes /*= 0*/)
+{
+ /* needs at least 8 bytes for '\xXX...' and zero byte */
+ DBUG_ASSERT(to_len >= 8);
+
+ char *t= to;
+ char *t_end= to + to_len - 1; // '- 1' is for the '\0' at the end
+ const char *f= from;
+ const char *f_end= from + (nbytes ? MY_MIN(from_len, nbytes) : from_len);
+ char *dots= to; // last safe place to append '...'
+
+ if (!f || t == t_end)
+ return 0;
+
+ for (; t < t_end && f < f_end; f++)
+ {
+ /*
+ If the source string is ASCII compatible (mbminlen==1)
+ and the source character is in ASCII printable range (0x20..0x7F),
+ then display the character as is.
+
+ Otherwise, if the source string is not ASCII compatible (e.g. UCS2),
+ or the source character is not in the printable range,
+ then print the character using HEX notation.
+ */
+ if (((unsigned char) *f) >= 0x20 &&
+ ((unsigned char) *f) <= 0x7F &&
+ from_cs->mbminlen == 1)
+ {
+ *t++= *f;
+ }
+ else
+ {
+ if (t_end - t < 4) // \xXX
+ break;
+ *t++= '\\';
+ *t++= 'x';
+ *t++= _dig_vec_upper[((unsigned char) *f) >> 4];
+ *t++= _dig_vec_upper[((unsigned char) *f) & 0x0F];
+ }
+ if (t_end - t >= 3) // '...'
+ dots= t;
+ }
+ if (f < from + from_len)
+ memcpy(dots, STRING_WITH_LEN("...\0"));
+ else
+ *t= '\0';
+ return t - to;
+}
diff --git a/sql/sql_string.h b/sql/sql_string.h
new file mode 100644
index 00000000000..c287f051d98
--- /dev/null
+++ b/sql/sql_string.h
@@ -0,0 +1,598 @@
+#ifndef SQL_STRING_INCLUDED
+#define SQL_STRING_INCLUDED
+
+/*
+ Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2013, Monty Program Ab.
+
+ 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 */
+
+/* This file is originally from the mysql distribution. Coded by monty */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "m_ctype.h" /* my_charset_bin */
+#include "my_sys.h" /* alloc_root, my_free, my_realloc */
+#include "m_string.h" /* TRASH */
+
+class String;
+typedef struct st_io_cache IO_CACHE;
+typedef struct st_mem_root MEM_ROOT;
+
+int sortcmp(const String *a,const String *b, CHARSET_INFO *cs);
+String *copy_if_not_alloced(String *a,String *b,uint32 arg_length);
+inline uint32 copy_and_convert(char *to, uint32 to_length,
+ const CHARSET_INFO *to_cs,
+ const char *from, uint32 from_length,
+ const CHARSET_INFO *from_cs, uint *errors)
+{
+ return my_convert(to, to_length, to_cs, from, from_length, from_cs, errors);
+}
+uint32 well_formed_copy_nchars(CHARSET_INFO *to_cs,
+ char *to, uint to_length,
+ CHARSET_INFO *from_cs,
+ const char *from, uint from_length,
+ uint nchars,
+ const char **well_formed_error_pos,
+ const char **cannot_convert_error_pos,
+ const char **from_end_pos);
+size_t my_copy_with_hex_escaping(CHARSET_INFO *cs,
+ char *dst, size_t dstlen,
+ const char *src, size_t srclen);
+uint convert_to_printable(char *to, size_t to_len,
+ const char *from, size_t from_len,
+ CHARSET_INFO *from_cs, size_t nbytes= 0);
+
+class String
+{
+ char *Ptr;
+ uint32 str_length,Alloced_length, extra_alloc;
+ bool alloced,thread_specific;
+ CHARSET_INFO *str_charset;
+public:
+ String()
+ {
+ Ptr=0; str_length=Alloced_length=extra_alloc=0;
+ alloced= thread_specific= 0;
+ str_charset= &my_charset_bin;
+ }
+ String(uint32 length_arg)
+ {
+ alloced= thread_specific= 0;
+ Alloced_length= extra_alloc= 0; (void) real_alloc(length_arg);
+ str_charset= &my_charset_bin;
+ }
+ String(const char *str, CHARSET_INFO *cs)
+ {
+ Ptr=(char*) str; str_length= (uint32) strlen(str);
+ Alloced_length= extra_alloc= 0;
+ alloced= thread_specific= 0;
+ str_charset=cs;
+ }
+ /*
+ NOTE: If one intend to use the c_ptr() method, the following two
+ contructors need the size of memory for STR to be at least LEN+1 (to make
+ room for zero termination).
+ */
+ String(const char *str,uint32 len, CHARSET_INFO *cs)
+ {
+ Ptr=(char*) str; str_length=len; Alloced_length= extra_alloc=0;
+ alloced= thread_specific= 0;
+ str_charset=cs;
+ }
+ String(char *str,uint32 len, CHARSET_INFO *cs)
+ {
+ Ptr=(char*) str; Alloced_length=str_length=len; extra_alloc= 0;
+ alloced= thread_specific= 0;
+ str_charset=cs;
+ }
+ String(const String &str)
+ {
+ Ptr=str.Ptr ; str_length=str.str_length ;
+ Alloced_length=str.Alloced_length; extra_alloc= 0;
+ alloced= thread_specific= 0;
+ str_charset=str.str_charset;
+ }
+ static void *operator new(size_t size, MEM_ROOT *mem_root) throw ()
+ { return (void*) alloc_root(mem_root, (uint) size); }
+ static void operator delete(void *ptr_arg, size_t size)
+ {
+ (void) ptr_arg;
+ (void) size;
+ TRASH(ptr_arg, size);
+ }
+ static void operator delete(void *, MEM_ROOT *)
+ { /* never called */ }
+ ~String() { free(); }
+
+ /* Mark variable thread specific it it's not allocated already */
+ inline void set_thread_specific()
+ {
+ if (!alloced)
+ thread_specific= 1;
+ }
+ inline void set_charset(CHARSET_INFO *charset_arg)
+ { str_charset= charset_arg; }
+ inline CHARSET_INFO *charset() const { return str_charset; }
+ inline uint32 length() const { return str_length;}
+ inline uint32 alloced_length() const { return Alloced_length;}
+ inline uint32 extra_allocation() const { return extra_alloc;}
+ inline char& operator [] (uint32 i) const { return Ptr[i]; }
+ inline void length(uint32 len) { str_length=len ; }
+ inline void extra_allocation(uint32 len) { extra_alloc= len; }
+ inline bool is_empty() const { return (str_length == 0); }
+ inline void mark_as_const() { Alloced_length= 0;}
+ inline const char *ptr() const { return Ptr; }
+ inline char *c_ptr()
+ {
+ DBUG_ASSERT(!alloced || !Ptr || !Alloced_length ||
+ (Alloced_length >= (str_length + 1)));
+
+ if (!Ptr || Ptr[str_length]) /* Should be safe */
+ (void) realloc(str_length);
+ return Ptr;
+ }
+ inline char *c_ptr_quick()
+ {
+ if (Ptr && str_length < Alloced_length)
+ Ptr[str_length]=0;
+ return Ptr;
+ }
+ inline char *c_ptr_safe()
+ {
+ if (Ptr && str_length < Alloced_length)
+ Ptr[str_length]=0;
+ else
+ (void) realloc(str_length);
+ return Ptr;
+ }
+ LEX_STRING lex_string() const
+ {
+ LEX_STRING lex_string = { (char*) ptr(), length() };
+ return lex_string;
+ }
+ LEX_CSTRING lex_cstring() const
+ {
+ LEX_CSTRING lex_cstring = { ptr(), length() };
+ return lex_cstring;
+ }
+
+ void set(String &str,uint32 offset,uint32 arg_length)
+ {
+ DBUG_ASSERT(&str != this);
+ free();
+ Ptr=(char*) str.ptr()+offset; str_length=arg_length;
+ if (str.Alloced_length)
+ Alloced_length=str.Alloced_length-offset;
+ str_charset=str.str_charset;
+ }
+
+
+ /**
+ Points the internal buffer to the supplied one. The old buffer is freed.
+ @param str Pointer to the new buffer.
+ @param arg_length Length of the new buffer in characters, excluding any
+ null character.
+ @param cs Character set to use for interpreting string data.
+ @note The new buffer will not be null terminated.
+ */
+ inline void set(char *str,uint32 arg_length, CHARSET_INFO *cs)
+ {
+ free();
+ Ptr=(char*) str; str_length=Alloced_length=arg_length;
+ str_charset=cs;
+ }
+ inline void set(const char *str,uint32 arg_length, CHARSET_INFO *cs)
+ {
+ free();
+ Ptr=(char*) str; str_length=arg_length;
+ str_charset=cs;
+ }
+ bool set_ascii(const char *str, uint32 arg_length);
+ inline void set_quick(char *str,uint32 arg_length, CHARSET_INFO *cs)
+ {
+ if (!alloced)
+ {
+ Ptr=(char*) str; str_length=Alloced_length=arg_length;
+ }
+ str_charset=cs;
+ }
+ bool set_int(longlong num, bool unsigned_flag, CHARSET_INFO *cs);
+ bool set(longlong num, CHARSET_INFO *cs)
+ { return set_int(num, false, cs); }
+ bool set(ulonglong num, CHARSET_INFO *cs)
+ { return set_int((longlong)num, true, cs); }
+ bool set_real(double num,uint decimals, CHARSET_INFO *cs);
+
+ /* Move handling of buffer from some other object to String */
+ void reassociate(char *ptr, uint32 length, uint32 alloced_length,
+ CHARSET_INFO *cs)
+ {
+ free();
+ Ptr= ptr;
+ str_length= length;
+ Alloced_length= alloced_length;
+ str_charset= cs;
+ alloced= ptr != 0;
+ }
+
+ /*
+ PMG 2004.11.12
+ This is a method that works the same as perl's "chop". It simply
+ drops the last character of a string. This is useful in the case
+ of the federated storage handler where I'm building a unknown
+ number, list of values and fields to be used in a sql insert
+ statement to be run on the remote server, and have a comma after each.
+ When the list is complete, I "chop" off the trailing comma
+
+ ex.
+ String stringobj;
+ stringobj.append("VALUES ('foo', 'fi', 'fo',");
+ stringobj.chop();
+ stringobj.append(")");
+
+ In this case, the value of string was:
+
+ VALUES ('foo', 'fi', 'fo',
+ VALUES ('foo', 'fi', 'fo'
+ VALUES ('foo', 'fi', 'fo')
+
+ */
+ inline void chop()
+ {
+ str_length--;
+ Ptr[str_length]= '\0';
+ DBUG_ASSERT(strlen(Ptr) == str_length);
+ }
+
+ inline void free()
+ {
+ if (alloced)
+ {
+ alloced=0;
+ my_free(Ptr);
+ }
+ Alloced_length= extra_alloc= 0;
+ Ptr=0;
+ str_length=0; /* Safety */
+ }
+ inline bool alloc(uint32 arg_length)
+ {
+ if (arg_length < Alloced_length)
+ return 0;
+ return real_alloc(arg_length);
+ }
+ bool real_alloc(uint32 arg_length); // Empties old string
+ bool realloc_raw(uint32 arg_length);
+ bool realloc(uint32 arg_length)
+ {
+ if (realloc_raw(arg_length))
+ return TRUE;
+ Ptr[arg_length]=0; // This make other funcs shorter
+ return FALSE;
+ }
+ bool realloc_with_extra(uint32 arg_length)
+ {
+ if (extra_alloc < 4096)
+ extra_alloc= extra_alloc*2+128;
+ if (realloc_raw(arg_length + extra_alloc))
+ return TRUE;
+ Ptr[arg_length]=0; // This make other funcs shorter
+ return FALSE;
+ }
+ bool realloc_with_extra_if_needed(uint32 arg_length)
+ {
+ if (arg_length < Alloced_length)
+ {
+ Ptr[arg_length]=0; // behave as if realloc was called.
+ return 0;
+ }
+ return realloc_with_extra(arg_length);
+ }
+ // Shrink the buffer, but only if it is allocated on the heap.
+ inline void shrink(uint32 arg_length)
+ {
+ if (!is_alloced())
+ return;
+ if (ALIGN_SIZE(arg_length+1) < Alloced_length)
+ {
+ char *new_ptr;
+ if (!(new_ptr=(char*) my_realloc(Ptr,arg_length,MYF(0))))
+ {
+ Alloced_length = 0;
+ real_alloc(arg_length);
+ }
+ else
+ {
+ Ptr=new_ptr;
+ Alloced_length=arg_length;
+ }
+ }
+ }
+ bool is_alloced() const { return alloced; }
+ inline String& operator = (const String &s)
+ {
+ if (&s != this)
+ {
+ /*
+ It is forbidden to do assignments like
+ some_string = substring_of_that_string
+ */
+ DBUG_ASSERT(!s.uses_buffer_owned_by(this));
+ free();
+ Ptr=s.Ptr ; str_length=s.str_length ; Alloced_length=s.Alloced_length;
+ str_charset=s.str_charset;
+ }
+ return *this;
+ }
+
+ bool copy(); // Alloc string if not alloced
+ bool copy(const String &s); // Allocate new string
+ bool copy(const char *s,uint32 arg_length, CHARSET_INFO *cs); // Allocate new string
+ static bool needs_conversion(uint32 arg_length,
+ CHARSET_INFO *cs_from, CHARSET_INFO *cs_to,
+ uint32 *offset);
+ static bool needs_conversion_on_storage(uint32 arg_length,
+ CHARSET_INFO *cs_from,
+ CHARSET_INFO *cs_to);
+ bool copy_aligned(const char *s, uint32 arg_length, uint32 offset,
+ CHARSET_INFO *cs);
+ bool set_or_copy_aligned(const char *s, uint32 arg_length, CHARSET_INFO *cs);
+ bool copy(const char*s,uint32 arg_length, CHARSET_INFO *csfrom,
+ CHARSET_INFO *csto, uint *errors);
+ bool copy(const String *str, CHARSET_INFO *tocs, uint *errors)
+ {
+ return copy(str->ptr(), str->length(), str->charset(), tocs, errors);
+ }
+ void move(String &s)
+ {
+ free();
+ Ptr=s.Ptr ; str_length=s.str_length ; Alloced_length=s.Alloced_length;
+ extra_alloc= s.extra_alloc;
+ alloced= s.alloced;
+ thread_specific= s.thread_specific;
+ s.alloced= 0;
+ }
+ bool append(const String &s);
+ bool append(const char *s);
+ bool append(const LEX_STRING *ls)
+ {
+ return append(ls->str, ls->length);
+ }
+ bool append(const char *s, uint32 arg_length);
+ bool append(const char *s, uint32 arg_length, CHARSET_INFO *cs);
+ bool append_ulonglong(ulonglong val);
+ bool append(IO_CACHE* file, uint32 arg_length);
+ bool append_with_prefill(const char *s, uint32 arg_length,
+ uint32 full_length, char fill_char);
+ bool append_parenthesized(long nr, int radix= 10);
+ int strstr(const String &search,uint32 offset=0); // Returns offset to substring or -1
+ int strrstr(const String &search,uint32 offset=0); // Returns offset to substring or -1
+ bool replace(uint32 offset,uint32 arg_length,const char *to,uint32 length);
+ bool replace(uint32 offset,uint32 arg_length,const String &to);
+ inline bool append(char chr)
+ {
+ if (str_length < Alloced_length)
+ {
+ Ptr[str_length++]=chr;
+ }
+ else
+ {
+ if (realloc_with_extra(str_length + 1))
+ return 1;
+ Ptr[str_length++]=chr;
+ }
+ return 0;
+ }
+ bool append_hex(const char *src, uint32 srclen)
+ {
+ for (const char *end= src + srclen ; src != end ; src++)
+ {
+ if (append(_dig_vec_lower[((uchar) *src) >> 4]) ||
+ append(_dig_vec_lower[((uchar) *src) & 0x0F]))
+ return true;
+ }
+ return false;
+ }
+ bool fill(uint32 max_length,char fill);
+ void strip_sp();
+ friend int sortcmp(const String *a,const String *b, CHARSET_INFO *cs);
+ friend int stringcmp(const String *a,const String *b);
+ friend String *copy_if_not_alloced(String *a,String *b,uint32 arg_length);
+ friend class Field;
+ uint32 numchars() const;
+ int charpos(longlong i,uint32 offset=0);
+
+ int reserve(uint32 space_needed)
+ {
+ return realloc(str_length + space_needed);
+ }
+ int reserve(uint32 space_needed, uint32 grow_by);
+
+ /*
+ The following append operations do NOT check alloced memory
+ q_*** methods writes values of parameters itself
+ qs_*** methods writes string representation of value
+ */
+ void q_append(const char c)
+ {
+ Ptr[str_length++] = c;
+ }
+ void q_append(const uint32 n)
+ {
+ int4store(Ptr + str_length, n);
+ str_length += 4;
+ }
+ void q_append(double d)
+ {
+ float8store(Ptr + str_length, d);
+ str_length += 8;
+ }
+ void q_append(double *d)
+ {
+ float8store(Ptr + str_length, *d);
+ str_length += 8;
+ }
+ void q_append(const char *data, uint32 data_len)
+ {
+ memcpy(Ptr + str_length, data, data_len);
+ str_length += data_len;
+ }
+
+ void write_at_position(int position, uint32 value)
+ {
+ int4store(Ptr + position,value);
+ }
+
+ void qs_append(const char *str)
+ {
+ qs_append(str, (uint32)strlen(str));
+ }
+ void qs_append(const char *str, uint32 len);
+ void qs_append(double d);
+ void qs_append(double *d);
+ inline void qs_append(const char c)
+ {
+ Ptr[str_length]= c;
+ str_length++;
+ }
+ void qs_append(int i);
+ void qs_append(uint i)
+ {
+ qs_append((ulonglong)i);
+ }
+ void qs_append(ulong i)
+ {
+ qs_append((ulonglong)i);
+ }
+ void qs_append(ulonglong i);
+
+ /* Inline (general) functions used by the protocol functions */
+
+ inline char *prep_append(uint32 arg_length, uint32 step_alloc)
+ {
+ uint32 new_length= arg_length + str_length;
+ if (new_length > Alloced_length)
+ {
+ if (realloc(new_length + step_alloc))
+ return 0;
+ }
+ uint32 old_length= str_length;
+ str_length+= arg_length;
+ return Ptr+ old_length; /* Area to use */
+ }
+
+ inline bool append(const char *s, uint32 arg_length, uint32 step_alloc)
+ {
+ uint32 new_length= arg_length + str_length;
+ if (new_length > Alloced_length && realloc(new_length + step_alloc))
+ return TRUE;
+ memcpy(Ptr+str_length, s, arg_length);
+ str_length+= arg_length;
+ return FALSE;
+ }
+ void print(String *print) const;
+
+ bool append_for_single_quote(const char *st, uint len);
+ bool append_for_single_quote(const String *s)
+ {
+ return append_for_single_quote(s->ptr(), s->length());
+ }
+ bool append_for_single_quote(const char *st)
+ {
+ return append_for_single_quote(st, strlen(st));
+ }
+
+ /* Swap two string objects. Efficient way to exchange data without memcpy. */
+ void swap(String &s);
+
+ inline bool uses_buffer_owned_by(const String *s) const
+ {
+ return (s->alloced && Ptr >= s->Ptr && Ptr < s->Ptr + s->str_length);
+ }
+ uint well_formed_length() const
+ {
+ int dummy_error;
+ return charset()->cset->well_formed_len(charset(), ptr(), ptr() + length(),
+ length(), &dummy_error);
+ }
+ bool is_ascii() const
+ {
+ if (length() == 0)
+ return TRUE;
+ if (charset()->mbminlen > 1)
+ return FALSE;
+ for (const char *c= ptr(), *end= c + length(); c < end; c++)
+ {
+ if (!my_isascii(*c))
+ return FALSE;
+ }
+ return TRUE;
+ }
+ bool bin_eq(const String *other) const
+ {
+ return length() == other->length() &&
+ !memcmp(ptr(), other->ptr(), length());
+ }
+ bool eq(const String *other, CHARSET_INFO *cs) const
+ {
+ return !sortcmp(this, other, cs);
+ }
+};
+
+
+// The following class is a backport from MySQL 5.6:
+/**
+ String class wrapper with a preallocated buffer of size buff_sz
+
+ This class allows to replace sequences of:
+ char buff[12345];
+ String str(buff, sizeof(buff));
+ str.length(0);
+ with a simple equivalent declaration:
+ StringBuffer<12345> str;
+*/
+
+template<size_t buff_sz>
+class StringBuffer : public String
+{
+ char buff[buff_sz];
+
+public:
+ StringBuffer() : String(buff, buff_sz, &my_charset_bin) { length(0); }
+ explicit StringBuffer(const CHARSET_INFO *cs) : String(buff, buff_sz, cs)
+ {
+ length(0);
+ }
+ StringBuffer(const char *str, size_t length, const CHARSET_INFO *cs)
+ : String(buff, buff_sz, cs)
+ {
+ set(str, length, cs);
+ }
+};
+
+
+static inline bool check_if_only_end_space(CHARSET_INFO *cs,
+ const char *str,
+ const char *end)
+{
+ return str+ cs->cset->scan(cs, str, end, MY_SEQ_SPACES) == end;
+}
+
+int append_query_string(CHARSET_INFO *csinfo, String *to,
+ const char *str, size_t len, bool no_backslash);
+
+#endif /* SQL_STRING_INCLUDED */
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
new file mode 100644
index 00000000000..817ab6e8967
--- /dev/null
+++ b/sql/sql_table.cc
@@ -0,0 +1,9814 @@
+/*
+ Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/* drop and alter of tables */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "debug_sync.h"
+#include "sql_table.h"
+#include "sql_parse.h" // test_if_data_home_dir
+#include "sql_cache.h" // query_cache_*
+#include "sql_base.h" // open_table_uncached, lock_table_names
+#include "lock.h" // mysql_unlock_tables
+#include "strfunc.h" // find_type2, find_set
+#include "sql_truncate.h" // regenerate_locked_table
+#include "sql_partition.h" // mem_alloc_error,
+ // generate_partition_syntax,
+ // partition_info
+ // NOT_A_PARTITION_ID
+#include "sql_db.h" // load_db_opt_by_name
+#include "sql_time.h" // make_truncated_value_warning
+#include "records.h" // init_read_record, end_read_record
+#include "filesort.h" // filesort_free_buffers
+#include "sql_select.h" // setup_order,
+ // make_unireg_sortorder
+#include "sql_handler.h" // mysql_ha_rm_tables
+#include "discover.h" // readfrm
+#include "my_pthread.h" // pthread_mutex_t
+#include "log_event.h" // Query_log_event
+#include "sql_statistics.h"
+#include <hash.h>
+#include <myisam.h>
+#include <my_dir.h>
+#include "create_options.h"
+#include "sp_head.h"
+#include "sp.h"
+#include "sql_trigger.h"
+#include "sql_parse.h"
+#include "sql_show.h"
+#include "transaction.h"
+#include "sql_audit.h"
+
+#ifdef __WIN__
+#include <io.h>
+#endif
+
+const char *primary_key_name="PRIMARY";
+
+static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end);
+static char *make_unique_key_name(const char *field_name,KEY *start,KEY *end);
+static int copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
+ List<Create_field> &create, bool ignore,
+ uint order_num, ORDER *order,
+ ha_rows *copied,ha_rows *deleted,
+ Alter_info::enum_enable_or_disable keys_onoff,
+ Alter_table_ctx *alter_ctx);
+
+static bool prepare_blob_field(THD *thd, Create_field *sql_field);
+static bool check_engine(THD *, const char *, const char *, HA_CREATE_INFO *);
+static int mysql_prepare_create_table(THD *, HA_CREATE_INFO *, Alter_info *,
+ uint *, handler *, KEY **, uint *, int);
+static uint blob_length_by_type(enum_field_types type);
+
+/**
+ @brief Helper function for explain_filename
+ @param thd Thread handle
+ @param to_p Explained name in system_charset_info
+ @param end_p End of the to_p buffer
+ @param name Name to be converted
+ @param name_len Length of the name, in bytes
+*/
+static char* add_identifier(THD* thd, char *to_p, const char * end_p,
+ const char* name, uint name_len)
+{
+ uint res;
+ uint errors;
+ const char *conv_name;
+ char tmp_name[FN_REFLEN];
+ char conv_string[FN_REFLEN];
+ int quote;
+
+ DBUG_ENTER("add_identifier");
+ if (!name[name_len])
+ conv_name= name;
+ else
+ {
+ strnmov(tmp_name, name, name_len);
+ tmp_name[name_len]= 0;
+ conv_name= tmp_name;
+ }
+ res= strconvert(&my_charset_filename, conv_name, name_len,
+ system_charset_info,
+ conv_string, FN_REFLEN, &errors);
+ if (!res || errors)
+ {
+ DBUG_PRINT("error", ("strconvert of '%s' failed with %u (errors: %u)", conv_name, res, errors));
+ conv_name= name;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("conv '%s' -> '%s'", conv_name, conv_string));
+ conv_name= conv_string;
+ }
+
+ quote = thd ? get_quote_char_for_identifier(thd, conv_name, res - 1) : '"';
+
+ if (quote != EOF && (end_p - to_p > 2))
+ {
+ *(to_p++)= (char) quote;
+ while (*conv_name && (end_p - to_p - 1) > 0)
+ {
+ uint length= my_mbcharlen(system_charset_info, *conv_name);
+ if (!length)
+ length= 1;
+ if (length == 1 && *conv_name == (char) quote)
+ {
+ if ((end_p - to_p) < 3)
+ break;
+ *(to_p++)= (char) quote;
+ *(to_p++)= *(conv_name++);
+ }
+ else if (((long) length) < (end_p - to_p))
+ {
+ to_p= strnmov(to_p, conv_name, length);
+ conv_name+= length;
+ }
+ else
+ break; /* string already filled */
+ }
+ if (end_p > to_p) {
+ *(to_p++)= (char) quote;
+ if (end_p > to_p)
+ *to_p= 0; /* terminate by NUL, but do not include it in the count */
+ }
+ }
+ else
+ to_p= strnmov(to_p, conv_name, end_p - to_p);
+ DBUG_RETURN(to_p);
+}
+
+
+/**
+ @brief Explain a path name by split it to database, table etc.
+
+ @details Break down the path name to its logic parts
+ (database, table, partition, subpartition).
+ filename_to_tablename cannot be used on partitions, due to the #P# part.
+ There can be up to 6 '#', #P# for partition, #SP# for subpartition
+ and #TMP# or #REN# for temporary or renamed partitions.
+ This should be used when something should be presented to a user in a
+ diagnostic, error etc. when it would be useful to know what a particular
+ file [and directory] means. Such as SHOW ENGINE STATUS, error messages etc.
+
+ Examples:
+
+ t1#P#p1 table t1 partition p1
+ t1#P#p1#SP#sp1 table t1 partition p1 subpartition sp1
+ t1#P#p1#SP#sp1#TMP# table t1 partition p1 subpartition sp1 temporary
+ t1#P#p1#SP#sp1#REN# table t1 partition p1 subpartition sp1 renamed
+
+ @param thd Thread handle
+ @param from Path name in my_charset_filename
+ Null terminated in my_charset_filename, normalized
+ to use '/' as directory separation character.
+ @param to Explained name in system_charset_info
+ @param to_length Size of to buffer
+ @param explain_mode Requested output format.
+ EXPLAIN_ALL_VERBOSE ->
+ [Database `db`, ]Table `tbl`[,[ Temporary| Renamed]
+ Partition `p` [, Subpartition `sp`]]
+ EXPLAIN_PARTITIONS_VERBOSE -> `db`.`tbl`
+ [[ Temporary| Renamed] Partition `p`
+ [, Subpartition `sp`]]
+ EXPLAIN_PARTITIONS_AS_COMMENT -> `db`.`tbl` |*
+ [,[ Temporary| Renamed] Partition `p`
+ [, Subpartition `sp`]] *|
+ (| is really a /, and it is all in one line)
+
+ @retval Length of returned string
+*/
+
+uint explain_filename(THD* thd,
+ const char *from,
+ char *to,
+ uint to_length,
+ enum_explain_filename_mode explain_mode)
+{
+ char *to_p= to;
+ char *end_p= to_p + to_length;
+ const char *db_name= NULL;
+ int db_name_len= 0;
+ const char *table_name;
+ int table_name_len= 0;
+ const char *part_name= NULL;
+ int part_name_len= 0;
+ const char *subpart_name= NULL;
+ int subpart_name_len= 0;
+ uint part_type= NORMAL_PART_NAME;
+
+ const char *tmp_p;
+ DBUG_ENTER("explain_filename");
+ DBUG_PRINT("enter", ("from '%s'", from));
+ tmp_p= from;
+ table_name= from;
+ /*
+ If '/' then take last directory part as database.
+ '/' is the directory separator, not FN_LIB_CHAR
+ */
+ while ((tmp_p= strchr(tmp_p, '/')))
+ {
+ db_name= table_name;
+ /* calculate the length */
+ db_name_len= tmp_p - db_name;
+ tmp_p++;
+ table_name= tmp_p;
+ }
+ tmp_p= table_name;
+ /* Look if there are partition tokens in the table name. */
+ while ((tmp_p= strchr(tmp_p, '#')))
+ {
+ tmp_p++;
+ switch (tmp_p[0]) {
+ case 'P':
+ case 'p':
+ if (tmp_p[1] == '#')
+ {
+ part_name= tmp_p + 2;
+ tmp_p+= 2;
+ }
+ break;
+ case 'S':
+ case 's':
+ if ((tmp_p[1] == 'P' || tmp_p[1] == 'p') && tmp_p[2] == '#')
+ {
+ part_name_len= tmp_p - part_name - 1;
+ subpart_name= tmp_p + 3;
+ tmp_p+= 3;
+ }
+ break;
+ case 'T':
+ case 't':
+ if ((tmp_p[1] == 'M' || tmp_p[1] == 'm') &&
+ (tmp_p[2] == 'P' || tmp_p[2] == 'p') &&
+ tmp_p[3] == '#' && !tmp_p[4])
+ {
+ part_type= TEMP_PART_NAME;
+ tmp_p+= 4;
+ }
+ break;
+ case 'R':
+ case 'r':
+ if ((tmp_p[1] == 'E' || tmp_p[1] == 'e') &&
+ (tmp_p[2] == 'N' || tmp_p[2] == 'n') &&
+ tmp_p[3] == '#' && !tmp_p[4])
+ {
+ part_type= RENAMED_PART_NAME;
+ tmp_p+= 4;
+ }
+ break;
+ default:
+ /* Not partition name part. */
+ ;
+ }
+ }
+ if (part_name)
+ {
+ table_name_len= part_name - table_name - 3;
+ if (subpart_name)
+ subpart_name_len= strlen(subpart_name);
+ else
+ part_name_len= strlen(part_name);
+ if (part_type != NORMAL_PART_NAME)
+ {
+ if (subpart_name)
+ subpart_name_len-= 5;
+ else
+ part_name_len-= 5;
+ }
+ }
+ else
+ table_name_len= strlen(table_name);
+ if (db_name)
+ {
+ if (explain_mode == EXPLAIN_ALL_VERBOSE)
+ {
+ to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_DATABASE_NAME),
+ end_p - to_p);
+ *(to_p++)= ' ';
+ to_p= add_identifier(thd, to_p, end_p, db_name, db_name_len);
+ to_p= strnmov(to_p, ", ", end_p - to_p);
+ }
+ else
+ {
+ to_p= add_identifier(thd, to_p, end_p, db_name, db_name_len);
+ to_p= strnmov(to_p, ".", end_p - to_p);
+ }
+ }
+ if (explain_mode == EXPLAIN_ALL_VERBOSE)
+ {
+ to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_TABLE_NAME), end_p - to_p);
+ *(to_p++)= ' ';
+ to_p= add_identifier(thd, to_p, end_p, table_name, table_name_len);
+ }
+ else
+ to_p= add_identifier(thd, to_p, end_p, table_name, table_name_len);
+ if (part_name)
+ {
+ if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT)
+ to_p= strnmov(to_p, " /* ", end_p - to_p);
+ else if (explain_mode == EXPLAIN_PARTITIONS_VERBOSE)
+ to_p= strnmov(to_p, " ", end_p - to_p);
+ else
+ to_p= strnmov(to_p, ", ", end_p - to_p);
+ if (part_type != NORMAL_PART_NAME)
+ {
+ if (part_type == TEMP_PART_NAME)
+ to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_TEMPORARY_NAME),
+ end_p - to_p);
+ else
+ to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_RENAMED_NAME),
+ end_p - to_p);
+ to_p= strnmov(to_p, " ", end_p - to_p);
+ }
+ to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_PARTITION_NAME),
+ end_p - to_p);
+ *(to_p++)= ' ';
+ to_p= add_identifier(thd, to_p, end_p, part_name, part_name_len);
+ if (subpart_name)
+ {
+ to_p= strnmov(to_p, ", ", end_p - to_p);
+ to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_SUBPARTITION_NAME),
+ end_p - to_p);
+ *(to_p++)= ' ';
+ to_p= add_identifier(thd, to_p, end_p, subpart_name, subpart_name_len);
+ }
+ if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT)
+ to_p= strnmov(to_p, " */", end_p - to_p);
+ }
+ DBUG_PRINT("exit", ("to '%s'", to));
+ DBUG_RETURN(to_p - to);
+}
+
+
+/*
+ Translate a file name to a table name (WL #1324).
+
+ SYNOPSIS
+ filename_to_tablename()
+ from The file name in my_charset_filename.
+ to OUT The table name in system_charset_info.
+ to_length The size of the table name buffer.
+
+ RETURN
+ Table name length.
+*/
+
+uint filename_to_tablename(const char *from, char *to, uint to_length,
+ bool stay_quiet)
+{
+ uint errors;
+ size_t res;
+ DBUG_ENTER("filename_to_tablename");
+ DBUG_PRINT("enter", ("from '%s'", from));
+
+ res= strconvert(&my_charset_filename, from, FN_REFLEN,
+ system_charset_info, to, to_length, &errors);
+ if (errors) // Old 5.0 name
+ {
+ res= (strxnmov(to, to_length, MYSQL50_TABLE_NAME_PREFIX, from, NullS) -
+ to);
+ if (!stay_quiet)
+ sql_print_error("Invalid (old?) table or database name '%s'", from);
+ }
+
+ DBUG_PRINT("exit", ("to '%s'", to));
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Check if given string begins with "#mysql50#" prefix
+
+ @param name string to check cut
+
+ @retval
+ FALSE no prefix found
+ @retval
+ TRUE prefix found
+*/
+
+bool check_mysql50_prefix(const char *name)
+{
+ return (name[0] == '#' &&
+ !strncmp(name, MYSQL50_TABLE_NAME_PREFIX,
+ MYSQL50_TABLE_NAME_PREFIX_LENGTH));
+}
+
+
+/**
+ Check if given string begins with "#mysql50#" prefix, cut it if so.
+
+ @param from string to check and cut
+ @param to[out] buffer for result string
+ @param to_length its size
+
+ @retval
+ 0 no prefix found
+ @retval
+ non-0 result string length
+*/
+
+uint check_n_cut_mysql50_prefix(const char *from, char *to, uint to_length)
+{
+ if (check_mysql50_prefix(from))
+ return (uint) (strmake(to, from + MYSQL50_TABLE_NAME_PREFIX_LENGTH,
+ to_length - 1) - to);
+ return 0;
+}
+
+
+/*
+ Translate a table name to a file name (WL #1324).
+
+ SYNOPSIS
+ tablename_to_filename()
+ from The table name in system_charset_info.
+ to OUT The file name in my_charset_filename.
+ to_length The size of the file name buffer.
+
+ RETURN
+ File name length.
+*/
+
+uint tablename_to_filename(const char *from, char *to, uint to_length)
+{
+ uint errors, length;
+ DBUG_ENTER("tablename_to_filename");
+ DBUG_PRINT("enter", ("from '%s'", from));
+
+ if ((length= check_n_cut_mysql50_prefix(from, to, to_length)))
+ {
+ /*
+ Check if the name supplied is a valid mysql 5.0 name and
+ make the name a zero length string if it's not.
+ Note that just returning zero length is not enough :
+ a lot of places don't check the return value and expect
+ a zero terminated string.
+ */
+ if (check_table_name(to, length, TRUE))
+ {
+ to[0]= 0;
+ length= 0;
+ }
+ DBUG_RETURN(length);
+ }
+ length= strconvert(system_charset_info, from, FN_REFLEN,
+ &my_charset_filename, to, to_length, &errors);
+ if (check_if_legal_tablename(to) &&
+ length + 4 < to_length)
+ {
+ memcpy(to + length, "@@@", 4);
+ length+= 3;
+ }
+ DBUG_PRINT("exit", ("to '%s'", to));
+ DBUG_RETURN(length);
+}
+
+
+/*
+ Creates path to a file: mysql_data_dir/db/table.ext
+
+ SYNOPSIS
+ build_table_filename()
+ buff Where to write result in my_charset_filename.
+ This may be the same as table_name.
+ bufflen buff size
+ db Database name in system_charset_info.
+ table_name Table name in system_charset_info.
+ ext File extension.
+ flags FN_FROM_IS_TMP or FN_TO_IS_TMP or FN_IS_TMP
+ table_name is temporary, do not change.
+
+ NOTES
+
+ Uses database and table name, and extension to create
+ a file name in mysql_data_dir. Database and table
+ names are converted from system_charset_info into "fscs".
+ Unless flags indicate a temporary table name.
+ 'db' is always converted.
+ 'ext' is not converted.
+
+ The conversion suppression is required for ALTER TABLE. This
+ statement creates intermediate tables. These are regular
+ (non-temporary) tables with a temporary name. Their path names must
+ be derivable from the table name. So we cannot use
+ build_tmptable_filename() for them.
+
+ RETURN
+ path length
+*/
+
+uint build_table_filename(char *buff, size_t bufflen, const char *db,
+ const char *table_name, const char *ext, uint flags)
+{
+ char dbbuff[FN_REFLEN];
+ char tbbuff[FN_REFLEN];
+ DBUG_ENTER("build_table_filename");
+ DBUG_PRINT("enter", ("db: '%s' table_name: '%s' ext: '%s' flags: %x",
+ db, table_name, ext, flags));
+
+ if (flags & FN_IS_TMP) // FN_FROM_IS_TMP | FN_TO_IS_TMP
+ strmake(tbbuff, table_name, sizeof(tbbuff)-1);
+ else
+ (void) tablename_to_filename(table_name, tbbuff, sizeof(tbbuff));
+
+ (void) tablename_to_filename(db, dbbuff, sizeof(dbbuff));
+
+ char *end = buff + bufflen;
+ /* Don't add FN_ROOTDIR if mysql_data_home already includes it */
+ char *pos = strnmov(buff, mysql_data_home, bufflen);
+ size_t rootdir_len= strlen(FN_ROOTDIR);
+ if (pos - rootdir_len >= buff &&
+ memcmp(pos - rootdir_len, FN_ROOTDIR, rootdir_len) != 0)
+ pos= strnmov(pos, FN_ROOTDIR, end - pos);
+ pos= strxnmov(pos, end - pos, dbbuff, FN_ROOTDIR, NullS);
+#ifdef USE_SYMDIR
+ if (!(flags & SKIP_SYMDIR_ACCESS))
+ {
+ unpack_dirname(buff, buff);
+ pos= strend(buff);
+ }
+#endif
+ pos= strxnmov(pos, end - pos, tbbuff, ext, NullS);
+
+ DBUG_PRINT("exit", ("buff: '%s'", buff));
+ DBUG_RETURN(pos - buff);
+}
+
+
+/**
+ Create path to a temporary table mysql_tmpdir/#sql1234_12_1
+ (i.e. to its .FRM file but without an extension).
+
+ @param thd The thread handle.
+ @param buff Where to write result in my_charset_filename.
+ @param bufflen buff size
+
+ @note
+ Uses current_pid, thread_id, and tmp_table counter to create
+ a file name in mysql_tmpdir.
+
+ @return Path length.
+*/
+
+uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen)
+{
+ DBUG_ENTER("build_tmptable_filename");
+
+ char *p= strnmov(buff, mysql_tmpdir, bufflen);
+ my_snprintf(p, bufflen - (p - buff), "/%s%lx_%lx_%x",
+ tmp_file_prefix, current_pid,
+ thd->thread_id, thd->tmp_table++);
+
+ if (lower_case_table_names)
+ {
+ /* Convert all except tmpdir to lower case */
+ my_casedn_str(files_charset_info, p);
+ }
+
+ size_t length= unpack_filename(buff, buff);
+ DBUG_PRINT("exit", ("buff: '%s'", buff));
+ DBUG_RETURN(length);
+}
+
+/*
+--------------------------------------------------------------------------
+
+ MODULE: DDL log
+ -----------------
+
+ This module is used to ensure that we can recover from crashes that occur
+ in the middle of a meta-data operation in MySQL. E.g. DROP TABLE t1, t2;
+ We need to ensure that both t1 and t2 are dropped and not only t1 and
+ also that each table drop is entirely done and not "half-baked".
+
+ To support this we create log entries for each meta-data statement in the
+ ddl log while we are executing. These entries are dropped when the
+ operation is completed.
+
+ At recovery those entries that were not completed will be executed.
+
+ There is only one ddl log in the system and it is protected by a mutex
+ and there is a global struct that contains information about its current
+ state.
+
+ History:
+ First version written in 2006 by Mikael Ronstrom
+--------------------------------------------------------------------------
+*/
+
+struct st_global_ddl_log
+{
+ /*
+ We need to adjust buffer size to be able to handle downgrades/upgrades
+ where IO_SIZE has changed. We'll set the buffer size such that we can
+ handle that the buffer size was upto 4 times bigger in the version
+ that wrote the DDL log.
+ */
+ char file_entry_buf[4*IO_SIZE];
+ char file_name_str[FN_REFLEN];
+ char *file_name;
+ DDL_LOG_MEMORY_ENTRY *first_free;
+ DDL_LOG_MEMORY_ENTRY *first_used;
+ uint num_entries;
+ File file_id;
+ uint name_len;
+ uint io_size;
+ bool inited;
+ bool do_release;
+ bool recovery_phase;
+ st_global_ddl_log() : inited(false), do_release(false) {}
+};
+
+st_global_ddl_log global_ddl_log;
+
+mysql_mutex_t LOCK_gdl;
+
+#define DDL_LOG_ENTRY_TYPE_POS 0
+#define DDL_LOG_ACTION_TYPE_POS 1
+#define DDL_LOG_PHASE_POS 2
+#define DDL_LOG_NEXT_ENTRY_POS 4
+#define DDL_LOG_NAME_POS 8
+
+#define DDL_LOG_NUM_ENTRY_POS 0
+#define DDL_LOG_NAME_LEN_POS 4
+#define DDL_LOG_IO_SIZE_POS 8
+
+/**
+ Read one entry from ddl log file.
+
+ @param entry_no Entry number to read
+
+ @return Operation status
+ @retval true Error
+ @retval false Success
+*/
+
+static bool read_ddl_log_file_entry(uint entry_no)
+{
+ bool error= FALSE;
+ File file_id= global_ddl_log.file_id;
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
+ uint io_size= global_ddl_log.io_size;
+ DBUG_ENTER("read_ddl_log_file_entry");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if (mysql_file_pread(file_id, file_entry_buf, io_size, io_size * entry_no,
+ MYF(MY_WME)) != io_size)
+ error= TRUE;
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Write one entry to ddl log file.
+
+ @param entry_no Entry number to write
+
+ @return Operation status
+ @retval true Error
+ @retval false Success
+*/
+
+static bool write_ddl_log_file_entry(uint entry_no)
+{
+ bool error= FALSE;
+ File file_id= global_ddl_log.file_id;
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
+ DBUG_ENTER("write_ddl_log_file_entry");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if (mysql_file_pwrite(file_id, file_entry_buf,
+ IO_SIZE, IO_SIZE * entry_no, MYF(MY_WME)) != IO_SIZE)
+ error= TRUE;
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Sync the ddl log file.
+
+ @return Operation status
+ @retval FALSE Success
+ @retval TRUE Error
+*/
+
+
+static bool sync_ddl_log_file()
+{
+ DBUG_ENTER("sync_ddl_log_file");
+ DBUG_RETURN(mysql_file_sync(global_ddl_log.file_id, MYF(MY_WME)));
+}
+
+
+/**
+ Write ddl log header.
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool write_ddl_log_header()
+{
+ uint16 const_var;
+ DBUG_ENTER("write_ddl_log_header");
+
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NUM_ENTRY_POS],
+ global_ddl_log.num_entries);
+ const_var= FN_REFLEN;
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_LEN_POS],
+ (ulong) const_var);
+ const_var= IO_SIZE;
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_IO_SIZE_POS],
+ (ulong) const_var);
+ if (write_ddl_log_file_entry(0UL))
+ {
+ sql_print_error("Error writing ddl log header");
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(sync_ddl_log_file());
+}
+
+
+/**
+ Create ddl log file name.
+ @param file_name Filename setup
+*/
+
+static inline void create_ddl_log_file_name(char *file_name)
+{
+ strxmov(file_name, mysql_data_home, "/", "ddl_log.log", NullS);
+}
+
+
+/**
+ Read header of ddl log file.
+
+ When we read the ddl log header we get information about maximum sizes
+ of names in the ddl log and we also get information about the number
+ of entries in the ddl log.
+
+ @return Last entry in ddl log (0 if no entries)
+*/
+
+static uint read_ddl_log_header()
+{
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
+ char file_name[FN_REFLEN];
+ uint entry_no;
+ bool successful_open= FALSE;
+ DBUG_ENTER("read_ddl_log_header");
+
+ mysql_mutex_init(key_LOCK_gdl, &LOCK_gdl, MY_MUTEX_INIT_SLOW);
+ mysql_mutex_lock(&LOCK_gdl);
+ create_ddl_log_file_name(file_name);
+ if ((global_ddl_log.file_id= mysql_file_open(key_file_global_ddl_log,
+ file_name,
+ O_RDWR | O_BINARY, MYF(0))) >= 0)
+ {
+ if (read_ddl_log_file_entry(0UL))
+ {
+ /* Write message into error log */
+ sql_print_error("Failed to read ddl log file in recovery");
+ }
+ else
+ successful_open= TRUE;
+ }
+ if (successful_open)
+ {
+ entry_no= uint4korr(&file_entry_buf[DDL_LOG_NUM_ENTRY_POS]);
+ global_ddl_log.name_len= uint4korr(&file_entry_buf[DDL_LOG_NAME_LEN_POS]);
+ global_ddl_log.io_size= uint4korr(&file_entry_buf[DDL_LOG_IO_SIZE_POS]);
+ DBUG_ASSERT(global_ddl_log.io_size <=
+ sizeof(global_ddl_log.file_entry_buf));
+ }
+ else
+ {
+ entry_no= 0;
+ }
+ global_ddl_log.first_free= NULL;
+ global_ddl_log.first_used= NULL;
+ global_ddl_log.num_entries= 0;
+ global_ddl_log.do_release= true;
+ mysql_mutex_unlock(&LOCK_gdl);
+ DBUG_RETURN(entry_no);
+}
+
+
+/**
+ Convert from ddl_log_entry struct to file_entry_buf binary blob.
+
+ @param ddl_log_entry filled in ddl_log_entry struct.
+*/
+
+static void set_global_from_ddl_log_entry(const DDL_LOG_ENTRY *ddl_log_entry)
+{
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ global_ddl_log.file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]=
+ (char)DDL_LOG_ENTRY_CODE;
+ global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS]=
+ (char)ddl_log_entry->action_type;
+ global_ddl_log.file_entry_buf[DDL_LOG_PHASE_POS]= 0;
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NEXT_ENTRY_POS],
+ ddl_log_entry->next_entry);
+ DBUG_ASSERT(strlen(ddl_log_entry->name) < FN_REFLEN);
+ strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS],
+ ddl_log_entry->name, FN_REFLEN - 1);
+ if (ddl_log_entry->action_type == DDL_LOG_RENAME_ACTION ||
+ ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION ||
+ ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION)
+ {
+ DBUG_ASSERT(strlen(ddl_log_entry->from_name) < FN_REFLEN);
+ strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN],
+ ddl_log_entry->from_name, FN_REFLEN - 1);
+ }
+ else
+ global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN]= 0;
+ DBUG_ASSERT(strlen(ddl_log_entry->handler_name) < FN_REFLEN);
+ strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (2*FN_REFLEN)],
+ ddl_log_entry->handler_name, FN_REFLEN - 1);
+ if (ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION)
+ {
+ DBUG_ASSERT(strlen(ddl_log_entry->tmp_name) < FN_REFLEN);
+ strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (3*FN_REFLEN)],
+ ddl_log_entry->tmp_name, FN_REFLEN - 1);
+ }
+ else
+ global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (3*FN_REFLEN)]= 0;
+}
+
+
+/**
+ Convert from file_entry_buf binary blob to ddl_log_entry struct.
+
+ @param[out] ddl_log_entry struct to fill in.
+
+ @note Strings (names) are pointing to the global_ddl_log structure,
+ so LOCK_gdl needs to be hold until they are read or copied.
+*/
+
+static void set_ddl_log_entry_from_global(DDL_LOG_ENTRY *ddl_log_entry,
+ const uint read_entry)
+{
+ char *file_entry_buf= (char*) global_ddl_log.file_entry_buf;
+ uint inx;
+ uchar single_char;
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ ddl_log_entry->entry_pos= read_entry;
+ single_char= file_entry_buf[DDL_LOG_ENTRY_TYPE_POS];
+ ddl_log_entry->entry_type= (enum ddl_log_entry_code)single_char;
+ single_char= file_entry_buf[DDL_LOG_ACTION_TYPE_POS];
+ ddl_log_entry->action_type= (enum ddl_log_action_code)single_char;
+ ddl_log_entry->phase= file_entry_buf[DDL_LOG_PHASE_POS];
+ ddl_log_entry->next_entry= uint4korr(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS]);
+ ddl_log_entry->name= &file_entry_buf[DDL_LOG_NAME_POS];
+ inx= DDL_LOG_NAME_POS + global_ddl_log.name_len;
+ ddl_log_entry->from_name= &file_entry_buf[inx];
+ inx+= global_ddl_log.name_len;
+ ddl_log_entry->handler_name= &file_entry_buf[inx];
+ if (ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION)
+ {
+ inx+= global_ddl_log.name_len;
+ ddl_log_entry->tmp_name= &file_entry_buf[inx];
+ }
+ else
+ ddl_log_entry->tmp_name= NULL;
+}
+
+
+/**
+ Read a ddl log entry.
+
+ Read a specified entry in the ddl log.
+
+ @param read_entry Number of entry to read
+ @param[out] entry_info Information from entry
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool read_ddl_log_entry(uint read_entry, DDL_LOG_ENTRY *ddl_log_entry)
+{
+ DBUG_ENTER("read_ddl_log_entry");
+
+ if (read_ddl_log_file_entry(read_entry))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ set_ddl_log_entry_from_global(ddl_log_entry, read_entry);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Initialise ddl log.
+
+ Write the header of the ddl log file and length of names. Also set
+ number of entries to zero.
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool init_ddl_log()
+{
+ char file_name[FN_REFLEN];
+ DBUG_ENTER("init_ddl_log");
+
+ if (global_ddl_log.inited)
+ goto end;
+
+ global_ddl_log.io_size= IO_SIZE;
+ global_ddl_log.name_len= FN_REFLEN;
+ create_ddl_log_file_name(file_name);
+ if ((global_ddl_log.file_id= mysql_file_create(key_file_global_ddl_log,
+ file_name, CREATE_MODE,
+ O_RDWR | O_TRUNC | O_BINARY,
+ MYF(MY_WME))) < 0)
+ {
+ /* Couldn't create ddl log file, this is serious error */
+ sql_print_error("Failed to open ddl log file");
+ DBUG_RETURN(TRUE);
+ }
+ global_ddl_log.inited= TRUE;
+ if (write_ddl_log_header())
+ {
+ (void) mysql_file_close(global_ddl_log.file_id, MYF(MY_WME));
+ global_ddl_log.inited= FALSE;
+ DBUG_RETURN(TRUE);
+ }
+
+end:
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Sync ddl log file.
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool sync_ddl_log_no_lock()
+{
+ DBUG_ENTER("sync_ddl_log_no_lock");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if ((!global_ddl_log.recovery_phase) &&
+ init_ddl_log())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(sync_ddl_log_file());
+}
+
+
+/**
+ @brief Deactivate an individual entry.
+
+ @details For complex rename operations we need to deactivate individual
+ entries.
+
+ During replace operations where we start with an existing table called
+ t1 and a replacement table called t1#temp or something else and where
+ we want to delete t1 and rename t1#temp to t1 this is not possible to
+ do in a safe manner unless the ddl log is informed of the phases in
+ the change.
+
+ Delete actions are 1-phase actions that can be ignored immediately after
+ being executed.
+ Rename actions from x to y is also a 1-phase action since there is no
+ interaction with any other handlers named x and y.
+ Replace action where drop y and x -> y happens needs to be a two-phase
+ action. Thus the first phase will drop y and the second phase will
+ rename x -> y.
+
+ @param entry_no Entry position of record to change
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool deactivate_ddl_log_entry_no_lock(uint entry_no)
+{
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
+ DBUG_ENTER("deactivate_ddl_log_entry_no_lock");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if (!read_ddl_log_file_entry(entry_no))
+ {
+ if (file_entry_buf[DDL_LOG_ENTRY_TYPE_POS] == DDL_LOG_ENTRY_CODE)
+ {
+ /*
+ Log entry, if complete mark it done (IGNORE).
+ Otherwise increase the phase by one.
+ */
+ if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_DELETE_ACTION ||
+ file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_RENAME_ACTION ||
+ (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION &&
+ file_entry_buf[DDL_LOG_PHASE_POS] == 1) ||
+ (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION &&
+ file_entry_buf[DDL_LOG_PHASE_POS] >= EXCH_PHASE_TEMP_TO_FROM))
+ file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE;
+ else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION)
+ {
+ DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] == 0);
+ file_entry_buf[DDL_LOG_PHASE_POS]= 1;
+ }
+ else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION)
+ {
+ DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] <=
+ EXCH_PHASE_FROM_TO_NAME);
+ file_entry_buf[DDL_LOG_PHASE_POS]++;
+ }
+ else
+ {
+ DBUG_ASSERT(0);
+ }
+ if (write_ddl_log_file_entry(entry_no))
+ {
+ sql_print_error("Error in deactivating log entry. Position = %u",
+ entry_no);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ else
+ {
+ sql_print_error("Failed in reading entry before deactivating it");
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Execute one action in a ddl log entry
+
+ @param ddl_log_entry Information in action entry to execute
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry)
+{
+ bool frm_action= FALSE;
+ LEX_STRING handler_name;
+ handler *file= NULL;
+ MEM_ROOT mem_root;
+ int error= TRUE;
+ char to_path[FN_REFLEN];
+ char from_path[FN_REFLEN];
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ char *par_ext= (char*)".par";
+#endif
+ handlerton *hton;
+ DBUG_ENTER("execute_ddl_log_action");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if (ddl_log_entry->entry_type == DDL_IGNORE_LOG_ENTRY_CODE)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ DBUG_PRINT("ddl_log",
+ ("execute type %c next %u name '%s' from_name '%s' handler '%s'"
+ " tmp_name '%s'",
+ ddl_log_entry->action_type,
+ ddl_log_entry->next_entry,
+ ddl_log_entry->name,
+ ddl_log_entry->from_name,
+ ddl_log_entry->handler_name,
+ ddl_log_entry->tmp_name));
+ handler_name.str= (char*)ddl_log_entry->handler_name;
+ handler_name.length= strlen(ddl_log_entry->handler_name);
+ init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC));
+ if (!strcmp(ddl_log_entry->handler_name, reg_ext))
+ frm_action= TRUE;
+ else
+ {
+ plugin_ref plugin= ha_resolve_by_name(thd, &handler_name);
+ if (!plugin)
+ {
+ my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), ddl_log_entry->handler_name);
+ goto error;
+ }
+ hton= plugin_data(plugin, handlerton*);
+ file= get_new_handler((TABLE_SHARE*)0, &mem_root, hton);
+ if (!file)
+ {
+ mem_alloc_error(sizeof(handler));
+ goto error;
+ }
+ }
+ switch (ddl_log_entry->action_type)
+ {
+ case DDL_LOG_REPLACE_ACTION:
+ case DDL_LOG_DELETE_ACTION:
+ {
+ if (ddl_log_entry->phase == 0)
+ {
+ if (frm_action)
+ {
+ strxmov(to_path, ddl_log_entry->name, reg_ext, NullS);
+ if ((error= mysql_file_delete(key_file_frm, to_path, MYF(MY_WME))))
+ {
+ if (my_errno != ENOENT)
+ break;
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ strxmov(to_path, ddl_log_entry->name, par_ext, NullS);
+ (void) mysql_file_delete(key_file_partition, to_path, MYF(MY_WME));
+#endif
+ }
+ else
+ {
+ if ((error= file->ha_delete_table(ddl_log_entry->name)))
+ {
+ if (error != ENOENT && error != HA_ERR_NO_SUCH_TABLE)
+ break;
+ }
+ }
+ if ((deactivate_ddl_log_entry_no_lock(ddl_log_entry->entry_pos)))
+ break;
+ (void) sync_ddl_log_no_lock();
+ error= FALSE;
+ if (ddl_log_entry->action_type == DDL_LOG_DELETE_ACTION)
+ break;
+ }
+ DBUG_ASSERT(ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION);
+ /*
+ Fall through and perform the rename action of the replace
+ action. We have already indicated the success of the delete
+ action in the log entry by stepping up the phase.
+ */
+ }
+ case DDL_LOG_RENAME_ACTION:
+ {
+ error= TRUE;
+ if (frm_action)
+ {
+ strxmov(to_path, ddl_log_entry->name, reg_ext, NullS);
+ strxmov(from_path, ddl_log_entry->from_name, reg_ext, NullS);
+ if (mysql_file_rename(key_file_frm, from_path, to_path, MYF(MY_WME)))
+ break;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ strxmov(to_path, ddl_log_entry->name, par_ext, NullS);
+ strxmov(from_path, ddl_log_entry->from_name, par_ext, NullS);
+ (void) mysql_file_rename(key_file_partition, from_path, to_path, MYF(MY_WME));
+#endif
+ }
+ else
+ {
+ if (file->ha_rename_table(ddl_log_entry->from_name,
+ ddl_log_entry->name))
+ break;
+ }
+ if ((deactivate_ddl_log_entry_no_lock(ddl_log_entry->entry_pos)))
+ break;
+ (void) sync_ddl_log_no_lock();
+ error= FALSE;
+ break;
+ }
+ case DDL_LOG_EXCHANGE_ACTION:
+ {
+ /* We hold LOCK_gdl, so we can alter global_ddl_log.file_entry_buf */
+ char *file_entry_buf= (char*)&global_ddl_log.file_entry_buf;
+ /* not yet implemented for frm */
+ DBUG_ASSERT(!frm_action);
+ /*
+ Using a case-switch here to revert all currently done phases,
+ since it will fall through until the first phase is undone.
+ */
+ switch (ddl_log_entry->phase) {
+ case EXCH_PHASE_TEMP_TO_FROM:
+ /* tmp_name -> from_name possibly done */
+ (void) file->ha_rename_table(ddl_log_entry->from_name,
+ ddl_log_entry->tmp_name);
+ /* decrease the phase and sync */
+ file_entry_buf[DDL_LOG_PHASE_POS]--;
+ if (write_ddl_log_file_entry(ddl_log_entry->entry_pos))
+ break;
+ if (sync_ddl_log_no_lock())
+ break;
+ /* fall through */
+ case EXCH_PHASE_FROM_TO_NAME:
+ /* from_name -> name possibly done */
+ (void) file->ha_rename_table(ddl_log_entry->name,
+ ddl_log_entry->from_name);
+ /* decrease the phase and sync */
+ file_entry_buf[DDL_LOG_PHASE_POS]--;
+ if (write_ddl_log_file_entry(ddl_log_entry->entry_pos))
+ break;
+ if (sync_ddl_log_no_lock())
+ break;
+ /* fall through */
+ case EXCH_PHASE_NAME_TO_TEMP:
+ /* name -> tmp_name possibly done */
+ (void) file->ha_rename_table(ddl_log_entry->tmp_name,
+ ddl_log_entry->name);
+ /* disable the entry and sync */
+ file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE;
+ if (write_ddl_log_file_entry(ddl_log_entry->entry_pos))
+ break;
+ if (sync_ddl_log_no_lock())
+ break;
+ error= FALSE;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+
+ break;
+ }
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ delete file;
+error:
+ free_root(&mem_root, MYF(0));
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Get a free entry in the ddl log
+
+ @param[out] active_entry A ddl log memory entry returned
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry,
+ bool *write_header)
+{
+ DDL_LOG_MEMORY_ENTRY *used_entry;
+ DDL_LOG_MEMORY_ENTRY *first_used= global_ddl_log.first_used;
+ DBUG_ENTER("get_free_ddl_log_entry");
+
+ if (global_ddl_log.first_free == NULL)
+ {
+ if (!(used_entry= (DDL_LOG_MEMORY_ENTRY*)my_malloc(
+ sizeof(DDL_LOG_MEMORY_ENTRY), MYF(MY_WME))))
+ {
+ sql_print_error("Failed to allocate memory for ddl log free list");
+ DBUG_RETURN(TRUE);
+ }
+ global_ddl_log.num_entries++;
+ used_entry->entry_pos= global_ddl_log.num_entries;
+ *write_header= TRUE;
+ }
+ else
+ {
+ used_entry= global_ddl_log.first_free;
+ global_ddl_log.first_free= used_entry->next_log_entry;
+ *write_header= FALSE;
+ }
+ /*
+ Move from free list to used list
+ */
+ used_entry->next_log_entry= first_used;
+ used_entry->prev_log_entry= NULL;
+ used_entry->next_active_log_entry= NULL;
+ global_ddl_log.first_used= used_entry;
+ if (first_used)
+ first_used->prev_log_entry= used_entry;
+
+ *active_entry= used_entry;
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Execute one entry in the ddl log.
+
+ Executing an entry means executing a linked list of actions.
+
+ @param first_entry Reference to first action in entry
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool execute_ddl_log_entry_no_lock(THD *thd, uint first_entry)
+{
+ DDL_LOG_ENTRY ddl_log_entry;
+ uint read_entry= first_entry;
+ DBUG_ENTER("execute_ddl_log_entry_no_lock");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ do
+ {
+ if (read_ddl_log_entry(read_entry, &ddl_log_entry))
+ {
+ /* Write to error log and continue with next log entry */
+ sql_print_error("Failed to read entry = %u from ddl log",
+ read_entry);
+ break;
+ }
+ DBUG_ASSERT(ddl_log_entry.entry_type == DDL_LOG_ENTRY_CODE ||
+ ddl_log_entry.entry_type == DDL_IGNORE_LOG_ENTRY_CODE);
+
+ if (execute_ddl_log_action(thd, &ddl_log_entry))
+ {
+ /* Write to error log and continue with next log entry */
+ sql_print_error("Failed to execute action for entry = %u from ddl log",
+ read_entry);
+ break;
+ }
+ read_entry= ddl_log_entry.next_entry;
+ } while (read_entry);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ External interface methods for the DDL log Module
+ ---------------------------------------------------
+*/
+
+/**
+ Write a ddl log entry.
+
+ A careful write of the ddl log is performed to ensure that we can
+ handle crashes occurring during CREATE and ALTER TABLE processing.
+
+ @param ddl_log_entry Information about log entry
+ @param[out] entry_written Entry information written into
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+bool write_ddl_log_entry(DDL_LOG_ENTRY *ddl_log_entry,
+ DDL_LOG_MEMORY_ENTRY **active_entry)
+{
+ bool error, write_header;
+ DBUG_ENTER("write_ddl_log_entry");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if (init_ddl_log())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ set_global_from_ddl_log_entry(ddl_log_entry);
+ if (get_free_ddl_log_entry(active_entry, &write_header))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ error= FALSE;
+ DBUG_PRINT("ddl_log",
+ ("write type %c next %u name '%s' from_name '%s' handler '%s'"
+ " tmp_name '%s'",
+ (char) global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS],
+ ddl_log_entry->next_entry,
+ (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS],
+ (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS
+ + FN_REFLEN],
+ (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS
+ + (2*FN_REFLEN)],
+ (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS
+ + (3*FN_REFLEN)]));
+ if (write_ddl_log_file_entry((*active_entry)->entry_pos))
+ {
+ error= TRUE;
+ sql_print_error("Failed to write entry_no = %u",
+ (*active_entry)->entry_pos);
+ }
+ if (write_header && !error)
+ {
+ (void) sync_ddl_log_no_lock();
+ if (write_ddl_log_header())
+ error= TRUE;
+ }
+ if (error)
+ release_ddl_log_memory_entry(*active_entry);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ @brief Write final entry in the ddl log.
+
+ @details This is the last write in the ddl log. The previous log entries
+ have already been written but not yet synched to disk.
+ We write a couple of log entries that describes action to perform.
+ This entries are set-up in a linked list, however only when a first
+ execute entry is put as the first entry these will be executed.
+ This routine writes this first.
+
+ @param first_entry First entry in linked list of entries
+ to execute, if 0 = NULL it means that
+ the entry is removed and the entries
+ are put into the free list.
+ @param complete Flag indicating we are simply writing
+ info about that entry has been completed
+ @param[in,out] active_entry Entry to execute, 0 = NULL if the entry
+ is written first time and needs to be
+ returned. In this case the entry written
+ is returned in this parameter
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+bool write_execute_ddl_log_entry(uint first_entry,
+ bool complete,
+ DDL_LOG_MEMORY_ENTRY **active_entry)
+{
+ bool write_header= FALSE;
+ char *file_entry_buf= (char*)global_ddl_log.file_entry_buf;
+ DBUG_ENTER("write_execute_ddl_log_entry");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if (init_ddl_log())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ if (!complete)
+ {
+ /*
+ We haven't synched the log entries yet, we synch them now before
+ writing the execute entry. If complete is true we haven't written
+ any log entries before, we are only here to write the execute
+ entry to indicate it is done.
+ */
+ (void) sync_ddl_log_no_lock();
+ file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_LOG_EXECUTE_CODE;
+ }
+ else
+ file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_IGNORE_LOG_ENTRY_CODE;
+ file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= 0; /* Ignored for execute entries */
+ file_entry_buf[DDL_LOG_PHASE_POS]= 0;
+ int4store(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], first_entry);
+ file_entry_buf[DDL_LOG_NAME_POS]= 0;
+ file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN]= 0;
+ file_entry_buf[DDL_LOG_NAME_POS + 2*FN_REFLEN]= 0;
+ if (!(*active_entry))
+ {
+ if (get_free_ddl_log_entry(active_entry, &write_header))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ write_header= TRUE;
+ }
+ if (write_ddl_log_file_entry((*active_entry)->entry_pos))
+ {
+ sql_print_error("Error writing execute entry in ddl log");
+ release_ddl_log_memory_entry(*active_entry);
+ DBUG_RETURN(TRUE);
+ }
+ (void) sync_ddl_log_no_lock();
+ if (write_header)
+ {
+ if (write_ddl_log_header())
+ {
+ release_ddl_log_memory_entry(*active_entry);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Deactivate an individual entry.
+
+ @details see deactivate_ddl_log_entry_no_lock.
+
+ @param entry_no Entry position of record to change
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+bool deactivate_ddl_log_entry(uint entry_no)
+{
+ bool error;
+ DBUG_ENTER("deactivate_ddl_log_entry");
+
+ mysql_mutex_lock(&LOCK_gdl);
+ error= deactivate_ddl_log_entry_no_lock(entry_no);
+ mysql_mutex_unlock(&LOCK_gdl);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Sync ddl log file.
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+bool sync_ddl_log()
+{
+ bool error;
+ DBUG_ENTER("sync_ddl_log");
+
+ mysql_mutex_lock(&LOCK_gdl);
+ error= sync_ddl_log_no_lock();
+ mysql_mutex_unlock(&LOCK_gdl);
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Release a log memory entry.
+ @param log_memory_entry Log memory entry to release
+*/
+
+void release_ddl_log_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry)
+{
+ DDL_LOG_MEMORY_ENTRY *first_free= global_ddl_log.first_free;
+ DDL_LOG_MEMORY_ENTRY *next_log_entry= log_entry->next_log_entry;
+ DDL_LOG_MEMORY_ENTRY *prev_log_entry= log_entry->prev_log_entry;
+ DBUG_ENTER("release_ddl_log_memory_entry");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ global_ddl_log.first_free= log_entry;
+ log_entry->next_log_entry= first_free;
+
+ if (prev_log_entry)
+ prev_log_entry->next_log_entry= next_log_entry;
+ else
+ global_ddl_log.first_used= next_log_entry;
+ if (next_log_entry)
+ next_log_entry->prev_log_entry= prev_log_entry;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Execute one entry in the ddl log.
+
+ Executing an entry means executing a linked list of actions.
+
+ @param first_entry Reference to first action in entry
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+bool execute_ddl_log_entry(THD *thd, uint first_entry)
+{
+ bool error;
+ DBUG_ENTER("execute_ddl_log_entry");
+
+ mysql_mutex_lock(&LOCK_gdl);
+ error= execute_ddl_log_entry_no_lock(thd, first_entry);
+ mysql_mutex_unlock(&LOCK_gdl);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Close the ddl log.
+*/
+
+static void close_ddl_log()
+{
+ DBUG_ENTER("close_ddl_log");
+ if (global_ddl_log.file_id >= 0)
+ {
+ (void) mysql_file_close(global_ddl_log.file_id, MYF(MY_WME));
+ global_ddl_log.file_id= (File) -1;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Execute the ddl log at recovery of MySQL Server.
+*/
+
+void execute_ddl_log_recovery()
+{
+ uint num_entries, i;
+ THD *thd;
+ DDL_LOG_ENTRY ddl_log_entry;
+ char file_name[FN_REFLEN];
+ static char recover_query_string[]= "INTERNAL DDL LOG RECOVER IN PROGRESS";
+ DBUG_ENTER("execute_ddl_log_recovery");
+
+ /*
+ Initialise global_ddl_log struct
+ */
+ bzero(global_ddl_log.file_entry_buf, sizeof(global_ddl_log.file_entry_buf));
+ global_ddl_log.inited= FALSE;
+ global_ddl_log.recovery_phase= TRUE;
+ global_ddl_log.io_size= IO_SIZE;
+ global_ddl_log.file_id= (File) -1;
+
+ /*
+ To be able to run this from boot, we allocate a temporary THD
+ */
+ if (!(thd=new THD))
+ DBUG_VOID_RETURN;
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+
+ thd->set_query(recover_query_string, strlen(recover_query_string));
+
+ /* this also initialize LOCK_gdl */
+ num_entries= read_ddl_log_header();
+ mysql_mutex_lock(&LOCK_gdl);
+ for (i= 1; i < num_entries + 1; i++)
+ {
+ if (read_ddl_log_entry(i, &ddl_log_entry))
+ {
+ sql_print_error("Failed to read entry no = %u from ddl log",
+ i);
+ continue;
+ }
+ if (ddl_log_entry.entry_type == DDL_LOG_EXECUTE_CODE)
+ {
+ if (execute_ddl_log_entry_no_lock(thd, ddl_log_entry.next_entry))
+ {
+ /* Real unpleasant scenario but we continue anyways. */
+ continue;
+ }
+ }
+ }
+ close_ddl_log();
+ create_ddl_log_file_name(file_name);
+ (void) mysql_file_delete(key_file_global_ddl_log, file_name, MYF(0));
+ global_ddl_log.recovery_phase= FALSE;
+ mysql_mutex_unlock(&LOCK_gdl);
+ delete thd;
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release all memory allocated to the ddl log.
+*/
+
+void release_ddl_log()
+{
+ DDL_LOG_MEMORY_ENTRY *free_list;
+ DDL_LOG_MEMORY_ENTRY *used_list;
+ DBUG_ENTER("release_ddl_log");
+
+ if (!global_ddl_log.do_release)
+ DBUG_VOID_RETURN;
+
+ mysql_mutex_lock(&LOCK_gdl);
+ free_list= global_ddl_log.first_free;
+ used_list= global_ddl_log.first_used;
+ while (used_list)
+ {
+ DDL_LOG_MEMORY_ENTRY *tmp= used_list->next_log_entry;
+ my_free(used_list);
+ used_list= tmp;
+ }
+ while (free_list)
+ {
+ DDL_LOG_MEMORY_ENTRY *tmp= free_list->next_log_entry;
+ my_free(free_list);
+ free_list= tmp;
+ }
+ close_ddl_log();
+ global_ddl_log.inited= 0;
+ mysql_mutex_unlock(&LOCK_gdl);
+ mysql_mutex_destroy(&LOCK_gdl);
+ global_ddl_log.do_release= false;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+---------------------------------------------------------------------------
+
+ END MODULE DDL log
+ --------------------
+
+---------------------------------------------------------------------------
+*/
+
+
+/**
+ @brief construct a temporary shadow file name.
+
+ @details Make a shadow file name used by ALTER TABLE to construct the
+ modified table (with keeping the original). The modified table is then
+ moved back as original table. The name must start with the temp file
+ prefix so it gets filtered out by table files listing routines.
+
+ @param[out] buff buffer to receive the constructed name
+ @param bufflen size of buff
+ @param lpt alter table data structure
+
+ @retval path length
+*/
+
+uint build_table_shadow_filename(char *buff, size_t bufflen,
+ ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+ char tmp_name[FN_REFLEN];
+ my_snprintf (tmp_name, sizeof (tmp_name), "%s-%s", tmp_file_prefix,
+ lpt->table_name);
+ return build_table_filename(buff, bufflen, lpt->db, tmp_name, "", FN_IS_TMP);
+}
+
+
+/*
+ SYNOPSIS
+ mysql_write_frm()
+ lpt Struct carrying many parameters needed for this
+ method
+ flags Flags as defined below
+ WFRM_INITIAL_WRITE If set we need to prepare table before
+ creating the frm file
+ WFRM_INSTALL_SHADOW If set we should install the new frm
+ WFRM_KEEP_SHARE If set we know that the share is to be
+ retained and thus we should ensure share
+ object is correct, if not set we don't
+ set the new partition syntax string since
+ we know the share object is destroyed.
+ WFRM_PACK_FRM If set we should pack the frm file and delete
+ the frm file
+
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+
+ DESCRIPTION
+ A support method that creates a new frm file and in this process it
+ regenerates the partition data. It works fine also for non-partitioned
+ tables since it only handles partitioned data if it exists.
+*/
+
+bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags)
+{
+ /*
+ Prepare table to prepare for writing a new frm file where the
+ partitions in add/drop state have temporarily changed their state
+ We set tmp_table to avoid get errors on naming of primary key index.
+ */
+ int error= 0;
+ char path[FN_REFLEN+1];
+ char shadow_path[FN_REFLEN+1];
+ char shadow_frm_name[FN_REFLEN+1];
+ char frm_name[FN_REFLEN+1];
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ char *part_syntax_buf;
+ uint syntax_len;
+#endif
+ DBUG_ENTER("mysql_write_frm");
+
+ /*
+ Build shadow frm file name
+ */
+ build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
+ strxmov(shadow_frm_name, shadow_path, reg_ext, NullS);
+ if (flags & WFRM_WRITE_SHADOW)
+ {
+ if (mysql_prepare_create_table(lpt->thd, lpt->create_info, lpt->alter_info,
+ &lpt->db_options, lpt->table->file,
+ &lpt->key_info_buffer, &lpt->key_count,
+ C_ALTER_TABLE))
+ {
+ DBUG_RETURN(TRUE);
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ {
+ partition_info *part_info= lpt->table->part_info;
+ if (part_info)
+ {
+ if (!(part_syntax_buf= generate_partition_syntax(part_info,
+ &syntax_len,
+ TRUE, TRUE,
+ lpt->create_info,
+ lpt->alter_info,
+ NULL)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ part_info->part_info_string= part_syntax_buf;
+ part_info->part_info_len= syntax_len;
+ }
+ }
+#endif
+ /* Write shadow frm file */
+ lpt->create_info->table_options= lpt->db_options;
+ LEX_CUSTRING frm= build_frm_image(lpt->thd, lpt->table_name,
+ lpt->create_info,
+ lpt->alter_info->create_list,
+ lpt->key_count, lpt->key_info_buffer,
+ lpt->table->file);
+ if (!frm.str)
+ {
+ error= 1;
+ goto end;
+ }
+
+ int error= writefrm(shadow_path, lpt->db, lpt->table_name,
+ lpt->create_info->tmp_table(), frm.str, frm.length);
+ my_free(const_cast<uchar*>(frm.str));
+
+ if (error || lpt->table->file->ha_create_partitioning_metadata(shadow_path,
+ NULL, CHF_CREATE_FLAG))
+ {
+ mysql_file_delete(key_file_frm, shadow_frm_name, MYF(0));
+ error= 1;
+ goto end;
+ }
+ }
+ if (flags & WFRM_PACK_FRM)
+ {
+ /*
+ We need to pack the frm file and after packing it we delete the
+ frm file to ensure it doesn't get used. This is only used for
+ handlers that have the main version of the frm file stored in the
+ handler.
+ */
+ const uchar *data;
+ size_t length;
+ if (readfrm(shadow_path, &data, &length) ||
+ packfrm(data, length, &lpt->pack_frm_data, &lpt->pack_frm_len))
+ {
+ my_free(const_cast<uchar*>(data));
+ my_free(lpt->pack_frm_data);
+ mem_alloc_error(length);
+ error= 1;
+ goto end;
+ }
+ error= mysql_file_delete(key_file_frm, shadow_frm_name, MYF(MY_WME));
+ }
+ if (flags & WFRM_INSTALL_SHADOW)
+ {
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info= lpt->part_info;
+#endif
+ /*
+ Build frm file name
+ */
+ build_table_filename(path, sizeof(path) - 1, lpt->db,
+ lpt->table_name, "", 0);
+ strxnmov(frm_name, sizeof(frm_name), path, reg_ext, NullS);
+ /*
+ When we are changing to use new frm file we need to ensure that we
+ don't collide with another thread in process to open the frm file.
+ We start by deleting the .frm file and possible .par file. Then we
+ write to the DDL log that we have completed the delete phase by
+ increasing the phase of the log entry. Next step is to rename the
+ new .frm file and the new .par file to the real name. After
+ completing this we write a new phase to the log entry that will
+ deactivate it.
+ */
+ if (mysql_file_delete(key_file_frm, frm_name, MYF(MY_WME)) ||
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ lpt->table->file->ha_create_partitioning_metadata(path, shadow_path,
+ CHF_DELETE_FLAG) ||
+ deactivate_ddl_log_entry(part_info->frm_log_entry->entry_pos) ||
+ (sync_ddl_log(), FALSE) ||
+ mysql_file_rename(key_file_frm,
+ shadow_frm_name, frm_name, MYF(MY_WME)) ||
+ lpt->table->file->ha_create_partitioning_metadata(path, shadow_path,
+ CHF_RENAME_FLAG))
+#else
+ mysql_file_rename(key_file_frm,
+ shadow_frm_name, frm_name, MYF(MY_WME)))
+#endif
+ {
+ error= 1;
+ goto err;
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (part_info && (flags & WFRM_KEEP_SHARE))
+ {
+ TABLE_SHARE *share= lpt->table->s;
+ char *tmp_part_syntax_str;
+ if (!(part_syntax_buf= generate_partition_syntax(part_info,
+ &syntax_len,
+ TRUE, TRUE,
+ lpt->create_info,
+ lpt->alter_info,
+ NULL)))
+ {
+ error= 1;
+ goto err;
+ }
+ if (share->partition_info_buffer_size < syntax_len + 1)
+ {
+ share->partition_info_buffer_size= syntax_len+1;
+ if (!(tmp_part_syntax_str= (char*) strmake_root(&share->mem_root,
+ part_syntax_buf,
+ syntax_len)))
+ {
+ error= 1;
+ goto err;
+ }
+ share->partition_info_str= tmp_part_syntax_str;
+ }
+ else
+ memcpy((char*) share->partition_info_str, part_syntax_buf,
+ syntax_len + 1);
+ share->partition_info_str_len= part_info->part_info_len= syntax_len;
+ part_info->part_info_string= part_syntax_buf;
+ }
+#endif
+
+err:
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ deactivate_ddl_log_entry(part_info->frm_log_entry->entry_pos);
+ part_info->frm_log_entry= NULL;
+ (void) sync_ddl_log();
+#endif
+ ;
+ }
+
+end:
+ DBUG_RETURN(error);
+}
+
+
+/*
+ SYNOPSIS
+ write_bin_log()
+ thd Thread object
+ clear_error is clear_error to be called
+ query Query to log
+ query_length Length of query
+ is_trans if the event changes either
+ a trans or non-trans engine.
+
+ RETURN VALUES
+ NONE
+
+ DESCRIPTION
+ Write the binlog if open, routine used in multiple places in this
+ file
+*/
+
+int write_bin_log(THD *thd, bool clear_error,
+ char const *query, ulong query_length, bool is_trans)
+{
+ int error= 0;
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= 0;
+ thd_proc_info(thd, "Writing to binlog");
+ if (clear_error)
+ thd->clear_error();
+ else
+ errcode= query_error_code(thd, TRUE);
+ error= thd->binlog_query(THD::STMT_QUERY_TYPE,
+ query, query_length, is_trans, FALSE, FALSE,
+ errcode);
+ thd_proc_info(thd, 0);
+ }
+ return error;
+}
+
+
+/*
+ delete (drop) tables.
+
+ SYNOPSIS
+ mysql_rm_table()
+ thd Thread handle
+ tables List of tables to delete
+ if_exists If 1, don't give error if one table doesn't exists
+
+ NOTES
+ Will delete all tables that can be deleted and give a compact error
+ messages for tables that could not be deleted.
+ If a table is in use, we will wait for all users to free the table
+ before dropping it
+
+ Wait if global_read_lock (FLUSH TABLES WITH READ LOCK) is set, but
+ not if under LOCK TABLES.
+
+ RETURN
+ FALSE OK. In this case ok packet is sent to user
+ TRUE Error
+
+*/
+
+bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
+ my_bool drop_temporary)
+{
+ bool error;
+ Drop_table_error_handler err_handler;
+ TABLE_LIST *table;
+ DBUG_ENTER("mysql_rm_table");
+
+ /* Disable drop of enabled log tables, must be done before name locking */
+ for (table= tables; table; table= table->next_local)
+ {
+ if (check_if_log_table(table, TRUE, "DROP"))
+ DBUG_RETURN(true);
+ }
+
+ if (!drop_temporary)
+ {
+ if (!in_bootstrap)
+ {
+ for (table= tables; table; table= table->next_local)
+ {
+ LEX_STRING db_name= { table->db, table->db_length };
+ LEX_STRING table_name= { table->table_name, table->table_name_length };
+ if (table->open_type == OT_BASE_ONLY ||
+ !find_temporary_table(thd, table))
+ (void) delete_statistics_for_table(thd, &db_name, &table_name);
+ }
+ }
+
+ if (!thd->locked_tables_mode)
+ {
+ if (lock_table_names(thd, tables, NULL,
+ thd->variables.lock_wait_timeout, 0))
+ DBUG_RETURN(true);
+ }
+ else
+ {
+ for (table= tables; table; table= table->next_local)
+ {
+ if (is_temporary_table(table))
+ {
+ /*
+ A temporary table.
+
+ Don't try to find a corresponding MDL lock or assign it
+ to table->mdl_request.ticket. There can't be metadata
+ locks for temporary tables: they are local to the session.
+
+ Later in this function we release the MDL lock only if
+ table->mdl_requeset.ticket is not NULL. Thus here we
+ ensure that we won't release the metadata lock on the base
+ table locked with LOCK TABLES as a side effect of temporary
+ table drop.
+ */
+ DBUG_ASSERT(table->mdl_request.ticket == NULL);
+ }
+ else
+ {
+ /*
+ Not a temporary table.
+
+ Since 'tables' list can't contain duplicates (this is ensured
+ by parser) it is safe to cache pointer to the TABLE instances
+ in its elements.
+ */
+ table->table= find_table_for_mdl_upgrade(thd, table->db,
+ table->table_name, false);
+ if (!table->table)
+ DBUG_RETURN(true);
+ table->mdl_request.ticket= table->table->mdl_ticket;
+ }
+ }
+ }
+ }
+
+ /* mark for close and remove all cached entries */
+ thd->push_internal_handler(&err_handler);
+ error= mysql_rm_table_no_locks(thd, tables, if_exists, drop_temporary,
+ false, false, false);
+ thd->pop_internal_handler();
+
+ if (error)
+ DBUG_RETURN(TRUE);
+ my_ok(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Find the comment in the query.
+ That's auxiliary function to be used handling DROP TABLE [comment].
+
+ @param thd Thread handler
+ @param comment_pos How many characters to skip before the comment.
+ Can be either 9 for DROP TABLE or
+ 17 for DROP TABLE IF EXISTS
+ @param comment_start returns the beginning of the comment if found.
+
+ @retval 0 no comment found
+ @retval >0 the lenght of the comment found
+
+*/
+static uint32 comment_length(THD *thd, uint32 comment_pos,
+ const char **comment_start)
+{
+ /* We use uchar * here to make array indexing portable */
+ const uchar *query= (uchar*) thd->query();
+ const uchar *query_end= (uchar*) query + thd->query_length();
+ const uchar *const state_map= thd->charset()->state_map;
+
+ for (; query < query_end; query++)
+ {
+ if (state_map[static_cast<uchar>(*query)] == MY_LEX_SKIP)
+ continue;
+ if (comment_pos-- == 0)
+ break;
+ }
+ if (query > query_end - 3 /* comment can't be shorter than 4 */ ||
+ state_map[static_cast<uchar>(*query)] != MY_LEX_LONG_COMMENT || query[1] != '*')
+ return 0;
+
+ *comment_start= (char*) query;
+
+ for (query+= 3; query < query_end; query++)
+ {
+ if (query[-1] == '*' && query[0] == '/')
+ return (char*) query - *comment_start + 1;
+ }
+ return 0;
+}
+
+
+/**
+ Execute the drop of a normal or temporary table.
+
+ @param thd Thread handler
+ @param tables Tables to drop
+ @param if_exists If set, don't give an error if table doesn't exists.
+ In this case we give an warning of level 'NOTE'
+ @param drop_temporary Only drop temporary tables
+ @param drop_view Allow to delete VIEW .frm
+ @param dont_log_query Don't write query to log files. This will also not
+ generate warnings if the handler files doesn't exists
+ @param dont_free_locks Don't do automatic UNLOCK TABLE if no more locked
+ tables
+
+ @retval 0 ok
+ @retval 1 Error
+ @retval -1 Thread was killed
+
+ @note This function assumes that metadata locks have already been taken.
+ It is also assumed that the tables have been removed from TDC.
+
+ @note This function assumes that temporary tables to be dropped have
+ been pre-opened using corresponding table list elements.
+
+ @todo When logging to the binary log, we should log
+ tmp_tables and transactional tables as separate statements if we
+ are in a transaction; This is needed to get these tables into the
+ cached binary log that is only written on COMMIT.
+ The current code only writes DROP statements that only uses temporary
+ tables to the cache binary log. This should be ok on most cases, but
+ not all.
+*/
+
+int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists,
+ bool drop_temporary, bool drop_view,
+ bool dont_log_query,
+ bool dont_free_locks)
+{
+ TABLE_LIST *table;
+ char path[FN_REFLEN + 1], wrong_tables_buff[160], *alias= NULL;
+ String wrong_tables(wrong_tables_buff, sizeof(wrong_tables_buff)-1,
+ system_charset_info);
+ uint path_length= 0;
+ int error= 0;
+ int non_temp_tables_count= 0;
+ bool foreign_key_error=0;
+ bool non_tmp_error= 0;
+ bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0;
+ bool non_tmp_table_deleted= 0;
+ bool is_drop_tmp_if_exists_added= 0;
+ bool one_table= tables->next_local == 0;
+ bool was_view= 0;
+ String built_query;
+ String built_trans_tmp_query, built_non_trans_tmp_query;
+ DBUG_ENTER("mysql_rm_table_no_locks");
+
+ wrong_tables.length(0);
+ /*
+ Prepares the drop statements that will be written into the binary
+ log as follows:
+
+ 1 - If we are not processing a "DROP TEMPORARY" it prepares a
+ "DROP".
+
+ 2 - A "DROP" may result in a "DROP TEMPORARY" but the opposite is
+ not true.
+
+ 3 - If the current format is row, the IF EXISTS token needs to be
+ appended because one does not know if CREATE TEMPORARY was previously
+ written to the binary log.
+
+ 4 - Add the IF_EXISTS token if necessary, i.e. if_exists is TRUE.
+
+ 5 - For temporary tables, there is a need to differentiate tables
+ in transactional and non-transactional storage engines. For that,
+ reason, two types of drop statements are prepared.
+
+ The need to different the type of tables when dropping a temporary
+ table stems from the fact that such drop does not commit an ongoing
+ transaction and changes to non-transactional tables must be written
+ ahead of the transaction in some circumstances.
+
+ 6- Slave SQL thread ignores all replicate-* filter rules
+ for temporary tables with 'IF EXISTS' clause. (See sql/sql_parse.cc:
+ mysql_execute_command() for details). These commands will be binlogged
+ as they are, even if the default database (from USE `db`) is not present
+ on the Slave. This can cause point in time recovery failures later
+ when user uses the slave's binlog to re-apply. Hence at the time of binary
+ logging, these commands will be written with fully qualified table names
+ and use `db` will be suppressed.
+ */
+ if (!dont_log_query)
+ {
+ if (!drop_temporary)
+ {
+ const char *comment_start;
+ uint32 comment_len;
+
+ built_query.set_charset(system_charset_info);
+ if (if_exists)
+ built_query.append("DROP TABLE IF EXISTS ");
+ else
+ built_query.append("DROP TABLE ");
+
+ if ((comment_len= comment_length(thd, if_exists ? 17:9, &comment_start)))
+ {
+ built_query.append(comment_start, comment_len);
+ built_query.append(" ");
+ }
+ }
+
+ if (thd->is_current_stmt_binlog_format_row() || if_exists)
+ {
+ is_drop_tmp_if_exists_added= true;
+ built_trans_tmp_query.set_charset(system_charset_info);
+ built_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS ");
+ built_non_trans_tmp_query.set_charset(system_charset_info);
+ built_non_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS ");
+ }
+ else
+ {
+ built_trans_tmp_query.set_charset(system_charset_info);
+ built_trans_tmp_query.append("DROP TEMPORARY TABLE ");
+ built_non_trans_tmp_query.set_charset(system_charset_info);
+ built_non_trans_tmp_query.append("DROP TEMPORARY TABLE ");
+ }
+ }
+
+ for (table= tables; table; table= table->next_local)
+ {
+ bool is_trans= 0;
+ bool table_creation_was_logged= 1;
+ char *db=table->db;
+ size_t db_length= table->db_length;
+ handlerton *table_type= 0;
+
+ DBUG_PRINT("table", ("table_l: '%s'.'%s' table: 0x%lx s: 0x%lx",
+ table->db, table->table_name, (long) table->table,
+ table->table ? (long) table->table->s : (long) -1));
+
+ /*
+ If we are in locked tables mode and are dropping a temporary table,
+ the ticket should be NULL to ensure that we don't release a lock
+ on a base table later.
+ */
+ DBUG_ASSERT(!(thd->locked_tables_mode &&
+ table->open_type != OT_BASE_ONLY &&
+ find_temporary_table(thd, table) &&
+ table->mdl_request.ticket != NULL));
+
+ /*
+ drop_temporary_table may return one of the following error codes:
+ . 0 - a temporary table was successfully dropped.
+ . 1 - a temporary table was not found.
+ . -1 - a temporary table is used by an outer statement.
+ */
+ if (table->open_type == OT_BASE_ONLY || !is_temporary_table(table))
+ error= 1;
+ else
+ {
+ table_creation_was_logged= table->table->s->table_creation_was_logged;
+ if ((error= drop_temporary_table(thd, table->table, &is_trans)) == -1)
+ {
+ DBUG_ASSERT(thd->in_sub_stmt);
+ goto err;
+ }
+ table->table= 0;
+ }
+
+ if ((drop_temporary && if_exists) || !error)
+ {
+ /*
+ This handles the case of temporary tables. We have the following cases:
+
+ . "DROP TEMPORARY" was executed and a temporary table was affected
+ (i.e. drop_temporary && !error) or the if_exists was specified (i.e.
+ drop_temporary && if_exists).
+
+ . "DROP" was executed but a temporary table was affected (.i.e
+ !error).
+ */
+#ifndef DONT_LOG_DROP_OF_TEMPORARY_TABLES
+ table_creation_was_logged= 1;
+#endif
+ if (!dont_log_query && table_creation_was_logged)
+ {
+ /*
+ If there is an error, we don't know the type of the engine
+ at this point. So, we keep it in the trx-cache.
+ */
+ is_trans= error ? TRUE : is_trans;
+ if (is_trans)
+ trans_tmp_table_deleted= TRUE;
+ else
+ non_trans_tmp_table_deleted= TRUE;
+
+ String *built_ptr_query=
+ (is_trans ? &built_trans_tmp_query : &built_non_trans_tmp_query);
+ /*
+ Write the database name if it is not the current one or if
+ thd->db is NULL or 'IF EXISTS' clause is present in 'DROP TEMPORARY'
+ query.
+ */
+ if (thd->db == NULL || strcmp(db,thd->db) != 0
+ || is_drop_tmp_if_exists_added )
+ {
+ append_identifier(thd, built_ptr_query, db, db_length);
+ built_ptr_query->append(".");
+ }
+ append_identifier(thd, built_ptr_query, table->table_name,
+ table->table_name_length);
+ built_ptr_query->append(",");
+ }
+ /*
+ This means that a temporary table was droped and as such there
+ is no need to proceed with the code that tries to drop a regular
+ table.
+ */
+ if (!error) continue;
+ }
+ else if (!drop_temporary)
+ {
+ non_temp_tables_count++;
+
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db,
+ table->table_name,
+ MDL_SHARED));
+
+ alias= (lower_case_table_names == 2) ? table->alias : table->table_name;
+ /* remove .frm file and engine files */
+ path_length= build_table_filename(path, sizeof(path) - 1, db, alias,
+ reg_ext, 0);
+
+ /*
+ This handles the case where a "DROP" was executed and a regular
+ table "may be" dropped as drop_temporary is FALSE and error is
+ TRUE. If the error was FALSE a temporary table was dropped and
+ regardless of the status of drop_temporary a "DROP TEMPORARY"
+ must be used.
+ */
+ if (!dont_log_query)
+ {
+ /*
+ Note that unless if_exists is TRUE or a temporary table was deleted,
+ there is no means to know if the statement should be written to the
+ binary log. See further information on this variable in what follows.
+ */
+ non_tmp_table_deleted= (if_exists ? TRUE : non_tmp_table_deleted);
+ /*
+ Don't write the database name if it is the current one (or if
+ thd->db is NULL).
+ */
+ if (thd->db == NULL || strcmp(db,thd->db) != 0)
+ {
+ append_identifier(thd, &built_query, db, db_length);
+ built_query.append(".");
+ }
+
+ append_identifier(thd, &built_query, table->table_name,
+ table->table_name_length);
+ built_query.append(",");
+ }
+ }
+ DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table");
+ error= 0;
+ if (drop_temporary ||
+ (ha_table_exists(thd, db, alias, &table_type) == 0 && table_type == 0) ||
+ (!drop_view && (was_view= (table_type == view_pseudo_hton))))
+ {
+ /*
+ One of the following cases happened:
+ . "DROP TEMPORARY" but a temporary table was not found.
+ . "DROP" but table was not found
+ . "DROP TABLE" statement, but it's a view.
+ */
+ if (if_exists)
+ {
+ char buff[FN_REFLEN];
+ String tbl_name(buff, sizeof(buff), system_charset_info);
+ tbl_name.length(0);
+ tbl_name.append(db);
+ tbl_name.append('.');
+ tbl_name.append(table->table_name);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR),
+ tbl_name.c_ptr_safe());
+ }
+ else
+ {
+ non_tmp_error = (drop_temporary ? non_tmp_error : TRUE);
+ error= 1;
+ }
+ }
+ else
+ {
+ char *end;
+ /*
+ It could happen that table's share in the table definition cache
+ is the only thing that keeps the engine plugin loaded
+ (if it is uninstalled and waits for the ref counter to drop to 0).
+
+ In this case, the tdc_remove_table() below will release and unload
+ the plugin. And ha_delete_table() will get a dangling pointer.
+
+ Let's lock the plugin till the end of the statement.
+ */
+ if (table_type && table_type != view_pseudo_hton)
+ ha_lock_engine(thd, table_type);
+
+ if (thd->locked_tables_mode)
+ {
+ if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED))
+ {
+ error= -1;
+ goto err;
+ }
+ /* the following internally does TDC_RT_REMOVE_ALL */
+ close_all_tables_for_name(thd, table->table->s,
+ HA_EXTRA_PREPARE_FOR_DROP, NULL);
+ table->table= 0;
+ }
+ else
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name,
+ false);
+
+ /* Check that we have an exclusive lock on the table to be dropped. */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db,
+ table->table_name,
+ MDL_EXCLUSIVE));
+
+ // Remove extension for delete
+ *(end= path + path_length - reg_ext_length)= '\0';
+
+ error= ha_delete_table(thd, table_type, path, db, table->table_name,
+ !dont_log_query);
+
+ if (error == HA_ERR_ROW_IS_REFERENCED)
+ {
+ /* the table is referenced by a foreign key constraint */
+ foreign_key_error= 1;
+ }
+ if (!error || error == ENOENT || error == HA_ERR_NO_SUCH_TABLE)
+ {
+ int frm_delete_error, trigger_drop_error= 0;
+ /* Delete the table definition file */
+ strmov(end,reg_ext);
+ frm_delete_error= mysql_file_delete(key_file_frm, path, MYF(MY_WME));
+ if (frm_delete_error)
+ frm_delete_error= my_errno;
+ else
+ {
+ non_tmp_table_deleted= TRUE;
+ trigger_drop_error=
+ Table_triggers_list::drop_all_triggers(thd, db, table->table_name);
+ }
+
+ if (trigger_drop_error ||
+ (frm_delete_error && frm_delete_error != ENOENT))
+ error= 1;
+ else if (!frm_delete_error || !error || if_exists)
+ {
+ error= 0;
+ thd->clear_error();
+ }
+ }
+ non_tmp_error= error ? TRUE : non_tmp_error;
+ }
+ if (error)
+ {
+ if (wrong_tables.length())
+ wrong_tables.append(',');
+ wrong_tables.append(db);
+ wrong_tables.append('.');
+ wrong_tables.append(table->table_name);
+ }
+ else
+ {
+ PSI_CALL_drop_table_share(false, table->db, table->db_length,
+ table->table_name, table->table_name_length);
+ mysql_audit_drop_table(thd, table);
+ }
+
+ DBUG_PRINT("table", ("table: 0x%lx s: 0x%lx", (long) table->table,
+ table->table ? (long) table->table->s : (long) -1));
+
+ DBUG_EXECUTE_IF("bug43138",
+ my_printf_error(ER_BAD_TABLE_ERROR,
+ ER(ER_BAD_TABLE_ERROR), MYF(0),
+ table->table_name););
+ }
+ DEBUG_SYNC(thd, "rm_table_no_locks_before_binlog");
+ thd->thread_specific_used|= (trans_tmp_table_deleted ||
+ non_trans_tmp_table_deleted);
+ error= 0;
+err:
+ if (wrong_tables.length())
+ {
+ if (one_table && was_view)
+ my_printf_error(ER_IT_IS_A_VIEW, ER(ER_IT_IS_A_VIEW), MYF(0),
+ wrong_tables.c_ptr_safe());
+ else if (!foreign_key_error)
+ my_printf_error(ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), MYF(0),
+ wrong_tables.c_ptr_safe());
+ else
+ my_message(ER_ROW_IS_REFERENCED, ER(ER_ROW_IS_REFERENCED), MYF(0));
+ error= 1;
+ }
+
+ /*
+ We are always logging drop of temporary tables.
+ The reason is to handle the following case:
+ - Use statement based replication
+ - CREATE TEMPORARY TABLE foo (logged)
+ - set row based replication
+ - DROP TEMPORAY TABLE foo (needs to be logged)
+ This should be fixed so that we remember if creation of the
+ temporary table was logged and only log it if the creation was
+ logged.
+ */
+
+ if (non_trans_tmp_table_deleted ||
+ trans_tmp_table_deleted || non_tmp_table_deleted)
+ {
+ query_cache_invalidate3(thd, tables, 0);
+ if (!dont_log_query && mysql_bin_log.is_open())
+ {
+ if (non_trans_tmp_table_deleted)
+ {
+ /* Chop of the last comma */
+ built_non_trans_tmp_query.chop();
+ built_non_trans_tmp_query.append(" /* generated by server */");
+ error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
+ built_non_trans_tmp_query.ptr(),
+ built_non_trans_tmp_query.length(),
+ FALSE, FALSE,
+ is_drop_tmp_if_exists_added,
+ 0);
+ }
+ if (trans_tmp_table_deleted)
+ {
+ /* Chop of the last comma */
+ built_trans_tmp_query.chop();
+ built_trans_tmp_query.append(" /* generated by server */");
+ error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
+ built_trans_tmp_query.ptr(),
+ built_trans_tmp_query.length(),
+ TRUE, FALSE,
+ is_drop_tmp_if_exists_added,
+ 0);
+ }
+ if (non_tmp_table_deleted)
+ {
+ /* Chop of the last comma */
+ built_query.chop();
+ built_query.append(" /* generated by server */");
+ int error_code = (non_tmp_error ?
+ (foreign_key_error ? ER_ROW_IS_REFERENCED : ER_BAD_TABLE_ERROR) : 0);
+ error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
+ built_query.ptr(),
+ built_query.length(),
+ TRUE, FALSE, FALSE,
+ error_code);
+ }
+ }
+ }
+
+ if (!drop_temporary)
+ {
+ /*
+ Under LOCK TABLES we should release meta-data locks on the tables
+ which were dropped.
+
+ Leave LOCK TABLES mode if we managed to drop all tables which were
+ locked. Additional check for 'non_temp_tables_count' is to avoid
+ leaving LOCK TABLES mode if we have dropped only temporary tables.
+ */
+ if (thd->locked_tables_mode)
+ {
+ if (thd->lock && thd->lock->table_count == 0 &&
+ non_temp_tables_count > 0 && !dont_free_locks)
+ {
+ thd->locked_tables_list.unlock_locked_tables(thd);
+ goto end;
+ }
+ for (table= tables; table; table= table->next_local)
+ {
+ /* Drop locks for all successfully dropped tables. */
+ if (table->table == NULL && table->mdl_request.ticket)
+ {
+ /*
+ Under LOCK TABLES we may have several instances of table open
+ and locked and therefore have to remove several metadata lock
+ requests associated with them.
+ */
+ thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket);
+ }
+ }
+ }
+ /*
+ Rely on the caller to implicitly commit the transaction
+ and release metadata locks.
+ */
+ }
+
+end:
+ DBUG_RETURN(error);
+}
+
+/**
+ Log the drop of a table.
+
+ @param thd Thread handler
+ @param db_name Database name
+ @param table_name Table name
+ @param temporary_table 1 if table was a temporary table
+
+ This code is only used in the case of failed CREATE OR REPLACE TABLE
+ when the original table was dropped but we could not create the new one.
+*/
+
+bool log_drop_table(THD *thd, const char *db_name, size_t db_name_length,
+ const char *table_name, size_t table_name_length,
+ bool temporary_table)
+{
+ char buff[NAME_LEN*2 + 80];
+ String query(buff, sizeof(buff), system_charset_info);
+ bool error;
+ DBUG_ENTER("log_drop_table");
+
+ if (!mysql_bin_log.is_open())
+ DBUG_RETURN(0);
+
+ query.length(0);
+ query.append(STRING_WITH_LEN("DROP "));
+ if (temporary_table)
+ query.append(STRING_WITH_LEN("TEMPORARY "));
+ query.append(STRING_WITH_LEN("TABLE IF EXISTS "));
+ append_identifier(thd, &query, db_name, db_name_length);
+ query.append(".");
+ append_identifier(thd, &query, table_name, table_name_length);
+ query.append(STRING_WITH_LEN("/* Generated to handle "
+ "failed CREATE OR REPLACE */"));
+ error= thd->binlog_query(THD::STMT_QUERY_TYPE,
+ query.ptr(), query.length(),
+ FALSE, FALSE, temporary_table, 0);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Quickly remove a table.
+
+ @param thd Thread context.
+ @param base The handlerton handle.
+ @param db The database name.
+ @param table_name The table name.
+ @param flags Flags for build_table_filename() as well as describing
+ if handler files / .FRM should be deleted as well.
+
+ @return False in case of success, True otherwise.
+*/
+
+bool quick_rm_table(THD *thd, handlerton *base, const char *db,
+ const char *table_name, uint flags)
+{
+ char path[FN_REFLEN + 1];
+ bool error= 0;
+ DBUG_ENTER("quick_rm_table");
+
+ uint path_length= build_table_filename(path, sizeof(path) - 1,
+ db, table_name, reg_ext, flags);
+ if (mysql_file_delete(key_file_frm, path, MYF(0)))
+ error= 1; /* purecov: inspected */
+ path[path_length - reg_ext_length]= '\0'; // Remove reg_ext
+ if (flags & NO_HA_TABLE)
+ {
+ handler *file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base);
+ if (!file)
+ DBUG_RETURN(true);
+ (void) file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG);
+ delete file;
+ }
+ if (!(flags & (FRM_ONLY|NO_HA_TABLE)))
+ error|= ha_delete_table(current_thd, base, path, db, table_name, 0);
+
+ if (likely(error == 0))
+ {
+ PSI_CALL_drop_table_share(flags & FN_IS_TMP, db, strlen(db),
+ table_name, strlen(table_name));
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Sort keys in the following order:
+ - PRIMARY KEY
+ - UNIQUE keys where all column are NOT NULL
+ - UNIQUE keys that don't contain partial segments
+ - Other UNIQUE keys
+ - Normal keys
+ - Fulltext keys
+
+ This will make checking for duplicated keys faster and ensure that
+ PRIMARY keys are prioritized.
+*/
+
+static int sort_keys(KEY *a, KEY *b)
+{
+ ulong a_flags= a->flags, b_flags= b->flags;
+
+ if (a_flags & HA_NOSAME)
+ {
+ if (!(b_flags & HA_NOSAME))
+ return -1;
+ if ((a_flags ^ b_flags) & HA_NULL_PART_KEY)
+ {
+ /* Sort NOT NULL keys before other keys */
+ return (a_flags & HA_NULL_PART_KEY) ? 1 : -1;
+ }
+ if (a->name == primary_key_name)
+ return -1;
+ if (b->name == primary_key_name)
+ return 1;
+ /* Sort keys don't containing partial segments before others */
+ if ((a_flags ^ b_flags) & HA_KEY_HAS_PART_KEY_SEG)
+ return (a_flags & HA_KEY_HAS_PART_KEY_SEG) ? 1 : -1;
+ }
+ else if (b_flags & HA_NOSAME)
+ return 1; // Prefer b
+
+ if ((a_flags ^ b_flags) & HA_FULLTEXT)
+ {
+ return (a_flags & HA_FULLTEXT) ? 1 : -1;
+ }
+ /*
+ Prefer original key order. usable_key_parts contains here
+ the original key position.
+ */
+ return ((a->usable_key_parts < b->usable_key_parts) ? -1 :
+ (a->usable_key_parts > b->usable_key_parts) ? 1 :
+ 0);
+}
+
+/*
+ Check TYPELIB (set or enum) for duplicates
+
+ SYNOPSIS
+ check_duplicates_in_interval()
+ set_or_name "SET" or "ENUM" string for warning message
+ name name of the checked column
+ typelib list of values for the column
+ dup_val_count returns count of duplicate elements
+
+ DESCRIPTION
+ This function prints an warning for each value in list
+ which has some duplicates on its right
+
+ RETURN VALUES
+ 0 ok
+ 1 Error
+*/
+
+bool check_duplicates_in_interval(const char *set_or_name,
+ const char *name, TYPELIB *typelib,
+ CHARSET_INFO *cs, unsigned int *dup_val_count)
+{
+ TYPELIB tmp= *typelib;
+ const char **cur_value= typelib->type_names;
+ unsigned int *cur_length= typelib->type_lengths;
+ *dup_val_count= 0;
+
+ for ( ; tmp.count > 1; cur_value++, cur_length++)
+ {
+ tmp.type_names++;
+ tmp.type_lengths++;
+ tmp.count--;
+ if (find_type2(&tmp, (const char*)*cur_value, *cur_length, cs))
+ {
+ THD *thd= current_thd;
+ ErrConvString err(*cur_value, *cur_length, cs);
+ if (current_thd->is_strict_mode())
+ {
+ my_error(ER_DUPLICATED_VALUE_IN_TYPE, MYF(0),
+ name, err.ptr(), set_or_name);
+ return 1;
+ }
+ push_warning_printf(thd,Sql_condition::WARN_LEVEL_NOTE,
+ ER_DUPLICATED_VALUE_IN_TYPE,
+ ER(ER_DUPLICATED_VALUE_IN_TYPE),
+ name, err.ptr(), set_or_name);
+ (*dup_val_count)++;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ Check TYPELIB (set or enum) max and total lengths
+
+ SYNOPSIS
+ calculate_interval_lengths()
+ cs charset+collation pair of the interval
+ typelib list of values for the column
+ max_length length of the longest item
+ tot_length sum of the item lengths
+
+ DESCRIPTION
+ After this function call:
+ - ENUM uses max_length
+ - SET uses tot_length.
+
+ RETURN VALUES
+ void
+*/
+void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval,
+ uint32 *max_length, uint32 *tot_length)
+{
+ const char **pos;
+ uint *len;
+ *max_length= *tot_length= 0;
+ for (pos= interval->type_names, len= interval->type_lengths;
+ *pos ; pos++, len++)
+ {
+ size_t length= cs->cset->numchars(cs, *pos, *pos + *len);
+ *tot_length+= length;
+ set_if_bigger(*max_length, (uint32)length);
+ }
+}
+
+
+/*
+ Prepare a create_table instance for packing
+
+ SYNOPSIS
+ prepare_create_field()
+ sql_field field to prepare for packing
+ blob_columns count for BLOBs
+ table_flags table flags
+
+ DESCRIPTION
+ This function prepares a Create_field instance.
+ Fields such as pack_flag are valid after this call.
+
+ RETURN VALUES
+ 0 ok
+ 1 Error
+*/
+
+int prepare_create_field(Create_field *sql_field,
+ uint *blob_columns,
+ longlong table_flags)
+{
+ unsigned int dup_val_count;
+ DBUG_ENTER("prepare_create_field");
+
+ /*
+ This code came from mysql_prepare_create_table.
+ Indent preserved to make patching easier
+ */
+ DBUG_ASSERT(sql_field->charset);
+
+ switch (sql_field->sql_type) {
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ sql_field->pack_flag=FIELDFLAG_BLOB |
+ pack_length_to_packflag(sql_field->pack_length -
+ portable_sizeof_char_ptr);
+ if (sql_field->charset->state & MY_CS_BINSORT)
+ sql_field->pack_flag|=FIELDFLAG_BINARY;
+ sql_field->length=8; // Unireg field length
+ sql_field->unireg_check=Field::BLOB_FIELD;
+ (*blob_columns)++;
+ break;
+ case MYSQL_TYPE_GEOMETRY:
+#ifdef HAVE_SPATIAL
+ if (!(table_flags & HA_CAN_GEOMETRY))
+ {
+ my_printf_error(ER_CHECK_NOT_IMPLEMENTED, ER(ER_CHECK_NOT_IMPLEMENTED),
+ MYF(0), "GEOMETRY");
+ DBUG_RETURN(1);
+ }
+ sql_field->pack_flag=FIELDFLAG_GEOM |
+ pack_length_to_packflag(sql_field->pack_length -
+ portable_sizeof_char_ptr);
+ if (sql_field->charset->state & MY_CS_BINSORT)
+ sql_field->pack_flag|=FIELDFLAG_BINARY;
+ sql_field->length=8; // Unireg field length
+ sql_field->unireg_check=Field::BLOB_FIELD;
+ (*blob_columns)++;
+ break;
+#else
+ my_printf_error(ER_FEATURE_DISABLED,ER(ER_FEATURE_DISABLED), MYF(0),
+ sym_group_geom.name, sym_group_geom.needed_define);
+ DBUG_RETURN(1);
+#endif /*HAVE_SPATIAL*/
+ case MYSQL_TYPE_VARCHAR:
+#ifndef QQ_ALL_HANDLERS_SUPPORT_VARCHAR
+ if (table_flags & HA_NO_VARCHAR)
+ {
+ /* convert VARCHAR to CHAR because handler is not yet up to date */
+ sql_field->sql_type= MYSQL_TYPE_VAR_STRING;
+ sql_field->pack_length= calc_pack_length(sql_field->sql_type,
+ (uint) sql_field->length);
+ if ((sql_field->length / sql_field->charset->mbmaxlen) >
+ MAX_FIELD_CHARLENGTH)
+ {
+ my_printf_error(ER_TOO_BIG_FIELDLENGTH, ER(ER_TOO_BIG_FIELDLENGTH),
+ MYF(0), sql_field->field_name,
+ static_cast<ulong>(MAX_FIELD_CHARLENGTH));
+ DBUG_RETURN(1);
+ }
+ }
+#endif
+ /* fall through */
+ case MYSQL_TYPE_STRING:
+ sql_field->pack_flag=0;
+ if (sql_field->charset->state & MY_CS_BINSORT)
+ sql_field->pack_flag|=FIELDFLAG_BINARY;
+ break;
+ case MYSQL_TYPE_ENUM:
+ sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) |
+ FIELDFLAG_INTERVAL;
+ if (sql_field->charset->state & MY_CS_BINSORT)
+ sql_field->pack_flag|=FIELDFLAG_BINARY;
+ sql_field->unireg_check=Field::INTERVAL_FIELD;
+ if (check_duplicates_in_interval("ENUM",sql_field->field_name,
+ sql_field->interval,
+ sql_field->charset, &dup_val_count))
+ DBUG_RETURN(1);
+ break;
+ case MYSQL_TYPE_SET:
+ sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) |
+ FIELDFLAG_BITFIELD;
+ if (sql_field->charset->state & MY_CS_BINSORT)
+ sql_field->pack_flag|=FIELDFLAG_BINARY;
+ sql_field->unireg_check=Field::BIT_FIELD;
+ if (check_duplicates_in_interval("SET",sql_field->field_name,
+ sql_field->interval,
+ sql_field->charset, &dup_val_count))
+ DBUG_RETURN(1);
+ /* Check that count of unique members is not more then 64 */
+ if (sql_field->interval->count - dup_val_count > sizeof(longlong)*8)
+ {
+ my_error(ER_TOO_BIG_SET, MYF(0), sql_field->field_name);
+ DBUG_RETURN(1);
+ }
+ break;
+ case MYSQL_TYPE_DATE: // Rest of string types
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIME2:
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_NULL:
+ sql_field->pack_flag=f_settype((uint) sql_field->sql_type);
+ break;
+ case MYSQL_TYPE_BIT:
+ /*
+ We have sql_field->pack_flag already set here, see
+ mysql_prepare_create_table().
+ */
+ break;
+ case MYSQL_TYPE_NEWDECIMAL:
+ sql_field->pack_flag=(FIELDFLAG_NUMBER |
+ (sql_field->flags & UNSIGNED_FLAG ? 0 :
+ FIELDFLAG_DECIMAL) |
+ (sql_field->flags & ZEROFILL_FLAG ?
+ FIELDFLAG_ZEROFILL : 0) |
+ (sql_field->decimals << FIELDFLAG_DEC_SHIFT));
+ break;
+ case MYSQL_TYPE_TIMESTAMP:
+ case MYSQL_TYPE_TIMESTAMP2:
+ /* fall-through */
+ default:
+ sql_field->pack_flag=(FIELDFLAG_NUMBER |
+ (sql_field->flags & UNSIGNED_FLAG ? 0 :
+ FIELDFLAG_DECIMAL) |
+ (sql_field->flags & ZEROFILL_FLAG ?
+ FIELDFLAG_ZEROFILL : 0) |
+ f_settype((uint) sql_field->sql_type) |
+ (sql_field->decimals << FIELDFLAG_DEC_SHIFT));
+ break;
+ }
+ if (!(sql_field->flags & NOT_NULL_FLAG) ||
+ (sql_field->vcol_info)) /* Make virtual columns allow NULL values */
+ sql_field->pack_flag|= FIELDFLAG_MAYBE_NULL;
+ if (sql_field->flags & NO_DEFAULT_VALUE_FLAG)
+ sql_field->pack_flag|= FIELDFLAG_NO_DEFAULT;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Get character set from field object generated by parser using
+ default values when not set.
+
+ SYNOPSIS
+ get_sql_field_charset()
+ sql_field The sql_field object
+ create_info Info generated by parser
+
+ RETURN VALUES
+ cs Character set
+*/
+
+CHARSET_INFO* get_sql_field_charset(Create_field *sql_field,
+ HA_CREATE_INFO *create_info)
+{
+ CHARSET_INFO *cs= sql_field->charset;
+
+ if (!cs)
+ cs= create_info->default_table_charset;
+ /*
+ table_charset is set only in ALTER TABLE t1 CONVERT TO CHARACTER SET csname
+ if we want change character set for all varchar/char columns.
+ But the table charset must not affect the BLOB fields, so don't
+ allow to change my_charset_bin to somethig else.
+ */
+ if (create_info->table_charset && cs != &my_charset_bin)
+ cs= create_info->table_charset;
+ return cs;
+}
+
+
+/**
+ Modifies the first column definition whose SQL type is TIMESTAMP
+ by adding the features DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.
+
+ @param column_definitions The list of column definitions, in the physical
+ order in which they appear in the table.
+ */
+void promote_first_timestamp_column(List<Create_field> *column_definitions)
+{
+ List_iterator<Create_field> it(*column_definitions);
+ Create_field *column_definition;
+
+ while ((column_definition= it++) != NULL)
+ {
+ if (is_timestamp_type(column_definition->sql_type) || // TIMESTAMP
+ column_definition->unireg_check == Field::TIMESTAMP_OLD_FIELD) // Legacy
+ {
+ if ((column_definition->flags & NOT_NULL_FLAG) != 0 && // NOT NULL,
+ column_definition->def == NULL && // no constant default,
+ column_definition->unireg_check == Field::NONE) // no function default
+ {
+ DBUG_PRINT("info", ("First TIMESTAMP column '%s' was promoted to "
+ "DEFAULT CURRENT_TIMESTAMP ON UPDATE "
+ "CURRENT_TIMESTAMP",
+ column_definition->field_name
+ ));
+ column_definition->unireg_check= Field::TIMESTAMP_DNUN_FIELD;
+ }
+ return;
+ }
+ }
+}
+
+
+/**
+ Check if there is a duplicate key. Report a warning for every duplicate key.
+
+ @param thd Thread context.
+ @param key Key to be checked.
+ @param key_info Key meta-data info.
+ @param key_list List of existing keys.
+*/
+static void check_duplicate_key(THD *thd,
+ Key *key, KEY *key_info,
+ List<Key> *key_list)
+{
+ /*
+ We only check for duplicate indexes if it is requested and the
+ key is not auto-generated.
+
+ Check is requested if the key was explicitly created or altered
+ by the user (unless it's a foreign key).
+ */
+ if (!key->key_create_info.check_for_duplicate_indexes || key->generated)
+ return;
+
+ List_iterator<Key> key_list_iterator(*key_list);
+ List_iterator<Key_part_spec> key_column_iterator(key->columns);
+ Key *k;
+
+ while ((k= key_list_iterator++))
+ {
+ // Looking for a similar key...
+
+ if (k == key)
+ break;
+
+ if (k->generated ||
+ (key->type != k->type) ||
+ (key->key_create_info.algorithm != k->key_create_info.algorithm) ||
+ (key->columns.elements != k->columns.elements))
+ {
+ // Keys are different.
+ continue;
+ }
+
+ /*
+ Keys 'key' and 'k' might be identical.
+ Check that the keys have identical columns in the same order.
+ */
+
+ List_iterator<Key_part_spec> k_column_iterator(k->columns);
+
+ bool all_columns_are_identical= true;
+
+ key_column_iterator.rewind();
+
+ for (uint i= 0; i < key->columns.elements; ++i)
+ {
+ Key_part_spec *c1= key_column_iterator++;
+ Key_part_spec *c2= k_column_iterator++;
+
+ DBUG_ASSERT(c1 && c2);
+
+ if (my_strcasecmp(system_charset_info,
+ c1->field_name.str, c2->field_name.str) ||
+ (c1->length != c2->length))
+ {
+ all_columns_are_identical= false;
+ break;
+ }
+ }
+
+ // Report a warning if we have two identical keys.
+
+ if (all_columns_are_identical)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_DUP_INDEX, ER(ER_DUP_INDEX),
+ key_info->name,
+ thd->lex->query_tables->db,
+ thd->lex->query_tables->table_name);
+ break;
+ }
+ }
+}
+
+
+/*
+ Preparation for table creation
+
+ SYNOPSIS
+ mysql_prepare_create_table()
+ thd Thread object.
+ create_info Create information (like MAX_ROWS).
+ alter_info List of columns and indexes to create
+ db_options INOUT Table options (like HA_OPTION_PACK_RECORD).
+ file The handler for the new table.
+ key_info_buffer OUT An array of KEY structs for the indexes.
+ key_count OUT The number of elements in the array.
+ create_table_mode C_ORDINARY_CREATE, C_ALTER_TABLE,
+ C_CREATE_SELECT, C_ASSISTED_DISCOVERY
+
+ DESCRIPTION
+ Prepares the table and key structures for table creation.
+
+ NOTES
+ sets create_info->varchar if the table has a varchar
+
+ RETURN VALUES
+ FALSE OK
+ TRUE error
+*/
+
+static int
+mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
+ Alter_info *alter_info, uint *db_options,
+ handler *file, KEY **key_info_buffer,
+ uint *key_count, int create_table_mode)
+{
+ const char *key_name;
+ Create_field *sql_field,*dup_field;
+ uint field,null_fields,blob_columns,max_key_length;
+ ulong record_offset= 0;
+ KEY *key_info;
+ KEY_PART_INFO *key_part_info;
+ int field_no,dup_no;
+ int select_field_pos,auto_increment=0;
+ List_iterator<Create_field> it(alter_info->create_list);
+ List_iterator<Create_field> it2(alter_info->create_list);
+ uint total_uneven_bit_length= 0;
+ int select_field_count= C_CREATE_SELECT(create_table_mode);
+ bool tmp_table= create_table_mode == C_ALTER_TABLE;
+ DBUG_ENTER("mysql_prepare_create_table");
+
+ select_field_pos= alter_info->create_list.elements - select_field_count;
+ null_fields=blob_columns=0;
+ create_info->varchar= 0;
+ max_key_length= file->max_key_length();
+
+ for (field_no=0; (sql_field=it++) ; field_no++)
+ {
+ CHARSET_INFO *save_cs;
+
+ /*
+ Initialize length from its original value (number of characters),
+ which was set in the parser. This is necessary if we're
+ executing a prepared statement for the second time.
+ */
+ sql_field->length= sql_field->char_length;
+ /* Set field charset. */
+ save_cs= sql_field->charset= get_sql_field_charset(sql_field,
+ create_info);
+ if ((sql_field->flags & BINCMP_FLAG) &&
+ !(sql_field->charset= get_charset_by_csname(sql_field->charset->csname,
+ MY_CS_BINSORT,MYF(0))))
+ {
+ char tmp[65];
+ strmake(strmake(tmp, save_cs->csname, sizeof(tmp)-4),
+ STRING_WITH_LEN("_bin"));
+ my_error(ER_UNKNOWN_COLLATION, MYF(0), tmp);
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Convert the default value from client character
+ set into the column character set if necessary.
+ */
+ if (sql_field->def &&
+ save_cs != sql_field->def->collation.collation &&
+ (sql_field->sql_type == MYSQL_TYPE_VAR_STRING ||
+ sql_field->sql_type == MYSQL_TYPE_STRING ||
+ sql_field->sql_type == MYSQL_TYPE_SET ||
+ sql_field->sql_type == MYSQL_TYPE_ENUM))
+ {
+ /*
+ Starting from 5.1 we work here with a copy of Create_field
+ created by the caller, not with the instance that was
+ originally created during parsing. It's OK to create
+ a temporary item and initialize with it a member of the
+ copy -- this item will be thrown away along with the copy
+ at the end of execution, and thus not introduce a dangling
+ pointer in the parsed tree of a prepared statement or a
+ stored procedure statement.
+ */
+ sql_field->def= sql_field->def->safe_charset_converter(save_cs);
+
+ if (sql_field->def == NULL)
+ {
+ /* Could not convert */
+ my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (sql_field->sql_type == MYSQL_TYPE_SET ||
+ sql_field->sql_type == MYSQL_TYPE_ENUM)
+ {
+ uint32 dummy;
+ CHARSET_INFO *cs= sql_field->charset;
+ TYPELIB *interval= sql_field->interval;
+
+ /*
+ Create typelib from interval_list, and if necessary
+ convert strings from client character set to the
+ column character set.
+ */
+ if (!interval)
+ {
+ /*
+ Create the typelib in runtime memory - we will free the
+ occupied memory at the same time when we free this
+ sql_field -- at the end of execution.
+ */
+ interval= sql_field->interval= typelib(thd->mem_root,
+ sql_field->interval_list);
+ List_iterator<String> int_it(sql_field->interval_list);
+ String conv, *tmp;
+ char comma_buf[4]; /* 4 bytes for utf32 */
+ int comma_length= cs->cset->wc_mb(cs, ',', (uchar*) comma_buf,
+ (uchar*) comma_buf +
+ sizeof(comma_buf));
+ DBUG_ASSERT(comma_length > 0);
+ for (uint i= 0; (tmp= int_it++); i++)
+ {
+ size_t lengthsp;
+ if (String::needs_conversion(tmp->length(), tmp->charset(),
+ cs, &dummy))
+ {
+ uint cnv_errs;
+ conv.copy(tmp->ptr(), tmp->length(), tmp->charset(), cs, &cnv_errs);
+ interval->type_names[i]= strmake_root(thd->mem_root, conv.ptr(),
+ conv.length());
+ interval->type_lengths[i]= conv.length();
+ }
+
+ // Strip trailing spaces.
+ lengthsp= cs->cset->lengthsp(cs, interval->type_names[i],
+ interval->type_lengths[i]);
+ interval->type_lengths[i]= lengthsp;
+ ((uchar *)interval->type_names[i])[lengthsp]= '\0';
+ if (sql_field->sql_type == MYSQL_TYPE_SET)
+ {
+ if (cs->coll->instr(cs, interval->type_names[i],
+ interval->type_lengths[i],
+ comma_buf, comma_length, NULL, 0))
+ {
+ ErrConvString err(tmp->ptr(), tmp->length(), cs);
+ my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "set", err.ptr());
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ sql_field->interval_list.empty(); // Don't need interval_list anymore
+ }
+
+ if (sql_field->sql_type == MYSQL_TYPE_SET)
+ {
+ uint32 field_length;
+ if (sql_field->def != NULL)
+ {
+ char *not_used;
+ uint not_used2;
+ bool not_found= 0;
+ String str, *def= sql_field->def->val_str(&str);
+ if (def == NULL) /* SQL "NULL" maps to NULL */
+ {
+ if ((sql_field->flags & NOT_NULL_FLAG) != 0)
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* else, NULL is an allowed value */
+ (void) find_set(interval, NULL, 0,
+ cs, &not_used, &not_used2, &not_found);
+ }
+ else /* not NULL */
+ {
+ (void) find_set(interval, def->ptr(), def->length(),
+ cs, &not_used, &not_used2, &not_found);
+ }
+
+ if (not_found)
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ calculate_interval_lengths(cs, interval, &dummy, &field_length);
+ sql_field->length= field_length + (interval->count - 1);
+ }
+ else /* MYSQL_TYPE_ENUM */
+ {
+ uint32 field_length;
+ DBUG_ASSERT(sql_field->sql_type == MYSQL_TYPE_ENUM);
+ if (sql_field->def != NULL)
+ {
+ String str, *def= sql_field->def->val_str(&str);
+ if (def == NULL) /* SQL "NULL" maps to NULL */
+ {
+ if ((sql_field->flags & NOT_NULL_FLAG) != 0)
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* else, the defaults yield the correct length for NULLs. */
+ }
+ else /* not NULL */
+ {
+ def->length(cs->cset->lengthsp(cs, def->ptr(), def->length()));
+ if (find_type2(interval, def->ptr(), def->length(), cs) == 0) /* not found */
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ calculate_interval_lengths(cs, interval, &field_length, &dummy);
+ sql_field->length= field_length;
+ }
+ set_if_smaller(sql_field->length, MAX_FIELD_WIDTH-1);
+ }
+
+ if (sql_field->sql_type == MYSQL_TYPE_BIT)
+ {
+ sql_field->pack_flag= FIELDFLAG_NUMBER;
+ if (file->ha_table_flags() & HA_CAN_BIT_FIELD)
+ total_uneven_bit_length+= sql_field->length & 7;
+ else
+ sql_field->pack_flag|= FIELDFLAG_TREAT_BIT_AS_CHAR;
+ }
+
+ sql_field->create_length_to_internal_length();
+ if (prepare_blob_field(thd, sql_field))
+ DBUG_RETURN(TRUE);
+
+ if (!(sql_field->flags & NOT_NULL_FLAG))
+ null_fields++;
+
+ if (check_column_name(sql_field->field_name))
+ {
+ my_error(ER_WRONG_COLUMN_NAME, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Check if we have used the same field name before */
+ for (dup_no=0; (dup_field=it2++) != sql_field; dup_no++)
+ {
+ if (my_strcasecmp(system_charset_info,
+ sql_field->field_name,
+ dup_field->field_name) == 0)
+ {
+ /*
+ If this was a CREATE ... SELECT statement, accept a field
+ redefinition if we are changing a field in the SELECT part
+ */
+ if (field_no < select_field_pos || dup_no >= select_field_pos)
+ {
+ my_error(ER_DUP_FIELDNAME, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+ else
+ {
+ /* Field redefined */
+ sql_field->def= dup_field->def;
+ sql_field->sql_type= dup_field->sql_type;
+ sql_field->charset= (dup_field->charset ?
+ dup_field->charset :
+ create_info->default_table_charset);
+ sql_field->length= dup_field->char_length;
+ sql_field->pack_length= dup_field->pack_length;
+ sql_field->key_length= dup_field->key_length;
+ sql_field->decimals= dup_field->decimals;
+ sql_field->create_length_to_internal_length();
+ sql_field->unireg_check= dup_field->unireg_check;
+ /*
+ We're making one field from two, the result field will have
+ dup_field->flags as flags. If we've incremented null_fields
+ because of sql_field->flags, decrement it back.
+ */
+ if (!(sql_field->flags & NOT_NULL_FLAG))
+ null_fields--;
+ sql_field->flags= dup_field->flags;
+ sql_field->interval= dup_field->interval;
+ sql_field->vcol_info= dup_field->vcol_info;
+ sql_field->stored_in_db= dup_field->stored_in_db;
+ it2.remove(); // Remove first (create) definition
+ select_field_pos--;
+ break;
+ }
+ }
+ }
+ /* Don't pack rows in old tables if the user has requested this */
+ if ((sql_field->flags & BLOB_FLAG) ||
+ (sql_field->sql_type == MYSQL_TYPE_VARCHAR &&
+ create_info->row_type != ROW_TYPE_FIXED))
+ (*db_options)|= HA_OPTION_PACK_RECORD;
+ it2.rewind();
+ }
+
+ /* record_offset will be increased with 'length-of-null-bits' later */
+ record_offset= 0;
+ null_fields+= total_uneven_bit_length;
+
+ it.rewind();
+ while ((sql_field=it++))
+ {
+ DBUG_ASSERT(sql_field->charset != 0);
+
+ if (prepare_create_field(sql_field, &blob_columns,
+ file->ha_table_flags()))
+ DBUG_RETURN(TRUE);
+ if (sql_field->sql_type == MYSQL_TYPE_VARCHAR)
+ create_info->varchar= TRUE;
+ sql_field->offset= record_offset;
+ if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
+ auto_increment++;
+ if (parse_option_list(thd, create_info->db_type, &sql_field->option_struct,
+ &sql_field->option_list,
+ create_info->db_type->field_options, FALSE,
+ thd->mem_root))
+ DBUG_RETURN(TRUE);
+ /*
+ For now skip fields that are not physically stored in the database
+ (virtual fields) and update their offset later
+ (see the next loop).
+ */
+ if (sql_field->stored_in_db)
+ record_offset+= sql_field->pack_length;
+ }
+ /* Update virtual fields' offset*/
+ it.rewind();
+ while ((sql_field=it++))
+ {
+ if (!sql_field->stored_in_db)
+ {
+ sql_field->offset= record_offset;
+ record_offset+= sql_field->pack_length;
+ }
+ }
+ if (auto_increment > 1)
+ {
+ my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (auto_increment &&
+ (file->ha_table_flags() & HA_NO_AUTO_INCREMENT))
+ {
+ my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, MYF(0), file->table_type());
+ DBUG_RETURN(TRUE);
+ }
+
+ if (blob_columns && (file->ha_table_flags() & HA_NO_BLOBS))
+ {
+ my_error(ER_TABLE_CANT_HANDLE_BLOB, MYF(0), file->table_type());
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ CREATE TABLE[with auto_increment column] SELECT is unsafe as the rows
+ inserted in the created table depends on the order of the rows fetched
+ from the select tables. This order may differ on master and slave. We
+ therefore mark it as unsafe.
+ */
+ if (select_field_count > 0 && auto_increment)
+ thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_SELECT_AUTOINC);
+
+ /* Create keys */
+
+ List_iterator<Key> key_iterator(alter_info->key_list);
+ List_iterator<Key> key_iterator2(alter_info->key_list);
+ uint key_parts=0, fk_key_count=0;
+ bool primary_key=0,unique_key=0;
+ Key *key, *key2;
+ uint tmp, key_number;
+ /* special marker for keys to be ignored */
+ static char ignore_key[1];
+
+ /* Calculate number of key segements */
+ *key_count= 0;
+
+ while ((key=key_iterator++))
+ {
+ DBUG_PRINT("info", ("key name: '%s' type: %d", key->name.str ? key->name.str :
+ "(none)" , key->type));
+ if (key->type == Key::FOREIGN_KEY)
+ {
+ fk_key_count++;
+ if (((Foreign_key *)key)->validate(alter_info->create_list))
+ DBUG_RETURN(TRUE);
+ Foreign_key *fk_key= (Foreign_key*) key;
+ if (fk_key->ref_columns.elements &&
+ fk_key->ref_columns.elements != fk_key->columns.elements)
+ {
+ my_error(ER_WRONG_FK_DEF, MYF(0),
+ (fk_key->name.str ? fk_key->name.str :
+ "foreign key without name"),
+ ER(ER_KEY_REF_DO_NOT_MATCH_TABLE_REF));
+ DBUG_RETURN(TRUE);
+ }
+ continue;
+ }
+ (*key_count)++;
+ tmp=file->max_key_parts();
+ if (key->columns.elements > tmp)
+ {
+ my_error(ER_TOO_MANY_KEY_PARTS,MYF(0),tmp);
+ DBUG_RETURN(TRUE);
+ }
+ if (check_string_char_length(&key->name, "", NAME_CHAR_LEN,
+ system_charset_info, 1))
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), key->name.str);
+ DBUG_RETURN(TRUE);
+ }
+ key_iterator2.rewind ();
+ if (key->type != Key::FOREIGN_KEY)
+ {
+ while ((key2 = key_iterator2++) != key)
+ {
+ /*
+ foreign_key_prefix(key, key2) returns 0 if key or key2, or both, is
+ 'generated', and a generated key is a prefix of the other key.
+ Then we do not need the generated shorter key.
+ */
+ if ((key2->type != Key::FOREIGN_KEY &&
+ key2->name.str != ignore_key &&
+ !foreign_key_prefix(key, key2)))
+ {
+ /* TODO: issue warning message */
+ /* mark that the generated key should be ignored */
+ if (!key2->generated ||
+ (key->generated && key->columns.elements <
+ key2->columns.elements))
+ key->name.str= ignore_key;
+ else
+ {
+ key2->name.str= ignore_key;
+ key_parts-= key2->columns.elements;
+ (*key_count)--;
+ }
+ break;
+ }
+ }
+ }
+ if (key->name.str != ignore_key)
+ key_parts+=key->columns.elements;
+ else
+ (*key_count)--;
+ if (key->name.str && !tmp_table && (key->type != Key::PRIMARY) &&
+ !my_strcasecmp(system_charset_info, key->name.str, primary_key_name))
+ {
+ my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name.str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ tmp=file->max_keys();
+ if (*key_count > tmp)
+ {
+ my_error(ER_TOO_MANY_KEYS,MYF(0),tmp);
+ DBUG_RETURN(TRUE);
+ }
+
+ (*key_info_buffer)= key_info= (KEY*) sql_calloc(sizeof(KEY) * (*key_count));
+ key_part_info=(KEY_PART_INFO*) sql_calloc(sizeof(KEY_PART_INFO)*key_parts);
+ if (!*key_info_buffer || ! key_part_info)
+ DBUG_RETURN(TRUE); // Out of memory
+
+ key_iterator.rewind();
+ key_number=0;
+ for (; (key=key_iterator++) ; key_number++)
+ {
+ uint key_length=0;
+ Key_part_spec *column;
+
+ if (key->name.str == ignore_key)
+ {
+ /* ignore redundant keys */
+ do
+ key=key_iterator++;
+ while (key && key->name.str == ignore_key);
+ if (!key)
+ break;
+ }
+
+ switch (key->type) {
+ case Key::MULTIPLE:
+ key_info->flags= 0;
+ break;
+ case Key::FULLTEXT:
+ key_info->flags= HA_FULLTEXT;
+ if ((key_info->parser_name= &key->key_create_info.parser_name)->str)
+ key_info->flags|= HA_USES_PARSER;
+ else
+ key_info->parser_name= 0;
+ break;
+ case Key::SPATIAL:
+#ifdef HAVE_SPATIAL
+ key_info->flags= HA_SPATIAL;
+ break;
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0),
+ sym_group_geom.name, sym_group_geom.needed_define);
+ DBUG_RETURN(TRUE);
+#endif
+ case Key::FOREIGN_KEY:
+ key_number--; // Skip this key
+ continue;
+ default:
+ key_info->flags = HA_NOSAME;
+ break;
+ }
+ if (key->generated)
+ key_info->flags|= HA_GENERATED_KEY;
+
+ key_info->user_defined_key_parts=(uint8) key->columns.elements;
+ key_info->key_part=key_part_info;
+ key_info->usable_key_parts= key_number;
+ key_info->algorithm= key->key_create_info.algorithm;
+ key_info->option_list= key->option_list;
+ if (parse_option_list(thd, create_info->db_type, &key_info->option_struct,
+ &key_info->option_list,
+ create_info->db_type->index_options, FALSE,
+ thd->mem_root))
+ DBUG_RETURN(TRUE);
+
+ if (key->type == Key::FULLTEXT)
+ {
+ if (!(file->ha_table_flags() & HA_CAN_FULLTEXT))
+ {
+ my_error(ER_TABLE_CANT_HANDLE_FT, MYF(0), file->table_type());
+ DBUG_RETURN(TRUE);
+ }
+ }
+ /*
+ Make SPATIAL to be RTREE by default
+ SPATIAL only on BLOB or at least BINARY, this
+ actually should be replaced by special GEOM type
+ in near future when new frm file is ready
+ checking for proper key parts number:
+ */
+
+ /* TODO: Add proper checks if handler supports key_type and algorithm */
+ if (key_info->flags & HA_SPATIAL)
+ {
+ if (!(file->ha_table_flags() & HA_CAN_RTREEKEYS))
+ {
+ my_error(ER_TABLE_CANT_HANDLE_SPKEYS, MYF(0), file->table_type());
+ DBUG_RETURN(TRUE);
+ }
+ if (key_info->user_defined_key_parts != 1)
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "SPATIAL INDEX");
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else if (key_info->algorithm == HA_KEY_ALG_RTREE)
+ {
+#ifdef HAVE_RTREE_KEYS
+ if ((key_info->user_defined_key_parts & 1) == 1)
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "RTREE INDEX");
+ DBUG_RETURN(TRUE);
+ }
+ /* TODO: To be deleted */
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "RTREE INDEX");
+ DBUG_RETURN(TRUE);
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0),
+ sym_group_rtree.name, sym_group_rtree.needed_define);
+ DBUG_RETURN(TRUE);
+#endif
+ }
+
+ /* Take block size from key part or table part */
+ /*
+ TODO: Add warning if block size changes. We can't do it here, as
+ this may depend on the size of the key
+ */
+ key_info->block_size= (key->key_create_info.block_size ?
+ key->key_create_info.block_size :
+ create_info->key_block_size);
+
+ if (key_info->block_size)
+ key_info->flags|= HA_USES_BLOCK_SIZE;
+
+ List_iterator<Key_part_spec> cols(key->columns), cols2(key->columns);
+ CHARSET_INFO *ft_key_charset=0; // for FULLTEXT
+ for (uint column_nr=0 ; (column=cols++) ; column_nr++)
+ {
+ Key_part_spec *dup_column;
+
+ it.rewind();
+ field=0;
+ while ((sql_field=it++) &&
+ my_strcasecmp(system_charset_info,
+ column->field_name.str,
+ sql_field->field_name))
+ field++;
+ if (!sql_field)
+ {
+ my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ while ((dup_column= cols2++) != column)
+ {
+ if (!my_strcasecmp(system_charset_info,
+ column->field_name.str, dup_column->field_name.str))
+ {
+ my_printf_error(ER_DUP_FIELDNAME,
+ ER(ER_DUP_FIELDNAME),MYF(0),
+ column->field_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ cols2.rewind();
+ if (key->type == Key::FULLTEXT)
+ {
+ if ((sql_field->sql_type != MYSQL_TYPE_STRING &&
+ sql_field->sql_type != MYSQL_TYPE_VARCHAR &&
+ !f_is_blob(sql_field->pack_flag)) ||
+ sql_field->charset == &my_charset_bin ||
+ sql_field->charset->mbminlen > 1 || // ucs2 doesn't work yet
+ (ft_key_charset && sql_field->charset != ft_key_charset))
+ {
+ my_error(ER_BAD_FT_COLUMN, MYF(0), column->field_name.str);
+ DBUG_RETURN(-1);
+ }
+ ft_key_charset=sql_field->charset;
+ /*
+ for fulltext keys keyseg length is 1 for blobs (it's ignored in ft
+ code anyway, and 0 (set to column width later) for char's. it has
+ to be correct col width for char's, as char data are not prefixed
+ with length (unlike blobs, where ft code takes data length from a
+ data prefix, ignoring column->length).
+ */
+ column->length= MY_TEST(f_is_blob(sql_field->pack_flag));
+ }
+ else
+ {
+ column->length*= sql_field->charset->mbmaxlen;
+
+ if (key->type == Key::SPATIAL)
+ {
+ if (column->length)
+ {
+ my_error(ER_WRONG_SUB_KEY, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (!f_is_geom(sql_field->pack_flag))
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "SPATIAL INDEX");
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (f_is_blob(sql_field->pack_flag) ||
+ (f_is_geom(sql_field->pack_flag) && key->type != Key::SPATIAL))
+ {
+ if (!(file->ha_table_flags() & HA_CAN_INDEX_BLOBS))
+ {
+ my_error(ER_BLOB_USED_AS_KEY, MYF(0), column->field_name.str,
+ file->table_type());
+ DBUG_RETURN(TRUE);
+ }
+ if (f_is_geom(sql_field->pack_flag) && sql_field->geom_type ==
+ Field::GEOM_POINT)
+ column->length= MAX_LEN_GEOM_POINT_FIELD;
+ if (!column->length)
+ {
+ my_error(ER_BLOB_KEY_WITHOUT_LENGTH, MYF(0), column->field_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+#ifdef HAVE_SPATIAL
+ if (key->type == Key::SPATIAL)
+ {
+ if (!column->length)
+ {
+ /*
+ 4 is: (Xmin,Xmax,Ymin,Ymax), this is for 2D case
+ Lately we'll extend this code to support more dimensions
+ */
+ column->length= 4*sizeof(double);
+ }
+ }
+#endif
+ if (!sql_field->stored_in_db)
+ {
+ /* Key fields must always be physically stored. */
+ my_error(ER_KEY_BASED_ON_GENERATED_VIRTUAL_COLUMN, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (key->type == Key::PRIMARY && sql_field->vcol_info)
+ {
+ my_error(ER_PRIMARY_KEY_BASED_ON_VIRTUAL_COLUMN, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (!(sql_field->flags & NOT_NULL_FLAG))
+ {
+ if (key->type == Key::PRIMARY)
+ {
+ /* Implicitly set primary key fields to NOT NULL for ISO conf. */
+ sql_field->flags|= NOT_NULL_FLAG;
+ sql_field->pack_flag&= ~FIELDFLAG_MAYBE_NULL;
+ null_fields--;
+ }
+ else
+ {
+ key_info->flags|= HA_NULL_PART_KEY;
+ if (!(file->ha_table_flags() & HA_NULL_IN_KEY))
+ {
+ my_error(ER_NULL_COLUMN_IN_INDEX, MYF(0), column->field_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ if (key->type == Key::SPATIAL)
+ {
+ my_message(ER_SPATIAL_CANT_HAVE_NULL,
+ ER(ER_SPATIAL_CANT_HAVE_NULL), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
+ {
+ if (column_nr == 0 || (file->ha_table_flags() & HA_AUTO_PART_KEY))
+ auto_increment--; // Field is used
+ }
+ }
+
+ key_part_info->fieldnr= field;
+ key_part_info->offset= (uint16) sql_field->offset;
+ key_part_info->key_type=sql_field->pack_flag;
+ uint key_part_length= sql_field->key_length;
+
+ if (column->length)
+ {
+ if (f_is_blob(sql_field->pack_flag))
+ {
+ key_part_length= MY_MIN(column->length,
+ blob_length_by_type(sql_field->sql_type)
+ * sql_field->charset->mbmaxlen);
+ if (key_part_length > max_key_length ||
+ key_part_length > file->max_key_part_length())
+ {
+ key_part_length= MY_MIN(max_key_length, file->max_key_part_length());
+ if (key->type == Key::MULTIPLE)
+ {
+ /* not a critical problem */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TOO_LONG_KEY, ER(ER_TOO_LONG_KEY),
+ key_part_length);
+ /* Align key length to multibyte char boundary */
+ key_part_length-= key_part_length % sql_field->charset->mbmaxlen;
+ }
+ else
+ {
+ my_error(ER_TOO_LONG_KEY, MYF(0), key_part_length);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ // Catch invalid use of partial keys
+ else if (!f_is_geom(sql_field->pack_flag) &&
+ // is the key partial?
+ column->length != key_part_length &&
+ // is prefix length bigger than field length?
+ (column->length > key_part_length ||
+ // can the field have a partial key?
+ !Field::type_can_have_key_part (sql_field->sql_type) ||
+ // a packed field can't be used in a partial key
+ f_is_packed(sql_field->pack_flag) ||
+ // does the storage engine allow prefixed search?
+ ((file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS) &&
+ // and is this a 'unique' key?
+ (key_info->flags & HA_NOSAME))))
+ {
+ my_message(ER_WRONG_SUB_KEY, ER(ER_WRONG_SUB_KEY), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ else if (!(file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS))
+ key_part_length= column->length;
+ }
+ else if (key_part_length == 0 && (sql_field->flags & NOT_NULL_FLAG))
+ {
+ my_error(ER_WRONG_KEY_COLUMN, MYF(0), file->table_type(),
+ column->field_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ if (key_part_length > file->max_key_part_length() &&
+ key->type != Key::FULLTEXT)
+ {
+ key_part_length= file->max_key_part_length();
+ if (key->type == Key::MULTIPLE)
+ {
+ /* not a critical problem */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TOO_LONG_KEY, ER(ER_TOO_LONG_KEY),
+ key_part_length);
+ /* Align key length to multibyte char boundary */
+ key_part_length-= key_part_length % sql_field->charset->mbmaxlen;
+ }
+ else
+ {
+ my_error(ER_TOO_LONG_KEY, MYF(0), key_part_length);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ key_part_info->length= (uint16) key_part_length;
+ /* Use packed keys for long strings on the first column */
+ if (!((*db_options) & HA_OPTION_NO_PACK_KEYS) &&
+ !((create_info->table_options & HA_OPTION_NO_PACK_KEYS)) &&
+ (key_part_length >= KEY_DEFAULT_PACK_LENGTH &&
+ (sql_field->sql_type == MYSQL_TYPE_STRING ||
+ sql_field->sql_type == MYSQL_TYPE_VARCHAR ||
+ sql_field->pack_flag & FIELDFLAG_BLOB)))
+ {
+ if ((column_nr == 0 && (sql_field->pack_flag & FIELDFLAG_BLOB)) ||
+ sql_field->sql_type == MYSQL_TYPE_VARCHAR)
+ key_info->flags|= HA_BINARY_PACK_KEY | HA_VAR_LENGTH_KEY;
+ else
+ key_info->flags|= HA_PACK_KEY;
+ }
+ /* Check if the key segment is partial, set the key flag accordingly */
+ if (key_part_length != sql_field->key_length)
+ key_info->flags|= HA_KEY_HAS_PART_KEY_SEG;
+
+ key_length+= key_part_length;
+ key_part_info++;
+
+ /* Create the key name based on the first column (if not given) */
+ if (column_nr == 0)
+ {
+ if (key->type == Key::PRIMARY)
+ {
+ if (primary_key)
+ {
+ my_message(ER_MULTIPLE_PRI_KEY, ER(ER_MULTIPLE_PRI_KEY),
+ MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ key_name=primary_key_name;
+ primary_key=1;
+ }
+ else if (!(key_name= key->name.str))
+ key_name=make_unique_key_name(sql_field->field_name,
+ *key_info_buffer, key_info);
+ if (check_if_keyname_exists(key_name, *key_info_buffer, key_info))
+ {
+ my_error(ER_DUP_KEYNAME, MYF(0), key_name);
+ DBUG_RETURN(TRUE);
+ }
+ key_info->name=(char*) key_name;
+ }
+ }
+ if (!key_info->name || check_column_name(key_info->name))
+ {
+ my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key_info->name);
+ DBUG_RETURN(TRUE);
+ }
+ if (!(key_info->flags & HA_NULL_PART_KEY))
+ unique_key=1;
+ key_info->key_length=(uint16) key_length;
+ if (key_length > max_key_length && key->type != Key::FULLTEXT)
+ {
+ my_error(ER_TOO_LONG_KEY,MYF(0),max_key_length);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (validate_comment_length(thd, &key->key_create_info.comment,
+ INDEX_COMMENT_MAXLEN, ER_TOO_LONG_INDEX_COMMENT,
+ key_info->name))
+ DBUG_RETURN(TRUE);
+
+ key_info->comment.length= key->key_create_info.comment.length;
+ if (key_info->comment.length > 0)
+ {
+ key_info->flags|= HA_USES_COMMENT;
+ key_info->comment.str= key->key_create_info.comment.str;
+ }
+
+ // Check if a duplicate index is defined.
+ check_duplicate_key(thd, key, key_info, &alter_info->key_list);
+
+ key_info++;
+ }
+
+ if (!unique_key && !primary_key &&
+ (file->ha_table_flags() & HA_REQUIRE_PRIMARY_KEY))
+ {
+ my_message(ER_REQUIRES_PRIMARY_KEY, ER(ER_REQUIRES_PRIMARY_KEY), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ if (auto_increment > 0)
+ {
+ my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ /* Sort keys in optimized order */
+ my_qsort((uchar*) *key_info_buffer, *key_count, sizeof(KEY),
+ (qsort_cmp) sort_keys);
+ create_info->null_bits= null_fields;
+
+ /* Check fields. */
+ it.rewind();
+ while ((sql_field=it++))
+ {
+ Field::utype type= (Field::utype) MTYP_TYPENR(sql_field->unireg_check);
+
+ if (thd->variables.sql_mode & MODE_NO_ZERO_DATE &&
+ !sql_field->def &&
+ is_timestamp_type(sql_field->sql_type) &&
+ (sql_field->flags & NOT_NULL_FLAG) &&
+ (type == Field::NONE || type == Field::TIMESTAMP_UN_FIELD))
+ {
+ /*
+ An error should be reported if:
+ - NO_ZERO_DATE SQL mode is active;
+ - there is no explicit DEFAULT clause (default column value);
+ - this is a TIMESTAMP column;
+ - the column is not NULL;
+ - this is not the DEFAULT CURRENT_TIMESTAMP column.
+
+ In other words, an error should be reported if
+ - NO_ZERO_DATE SQL mode is active;
+ - the column definition is equivalent to
+ 'column_name TIMESTAMP DEFAULT 0'.
+ */
+
+ my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ /* Give warnings for not supported table options */
+#if defined(WITH_ARIA_STORAGE_ENGINE)
+ extern handlerton *maria_hton;
+ if (file->ht != maria_hton)
+#endif
+ if (create_info->transactional)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ILLEGAL_HA_CREATE_OPTION,
+ ER(ER_ILLEGAL_HA_CREATE_OPTION),
+ file->engine_name()->str,
+ "TRANSACTIONAL=1");
+
+ if (parse_option_list(thd, file->partition_ht(), &create_info->option_struct,
+ &create_info->option_list,
+ file->partition_ht()->table_options, FALSE,
+ thd->mem_root))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ check comment length of table, column, index and partition
+
+ If comment lenght is more than the standard length
+ truncate it and store the comment lenght upto the standard
+ comment length size
+
+ @param thd Thread handle
+ @param[in,out] comment Comment
+ @param max_len Maximum allowed comment length
+ @param err_code Error message
+ @param name Name of commented object
+
+ @return Operation status
+ @retval true Error found
+ @retval false On Success
+*/
+bool validate_comment_length(THD *thd, LEX_STRING *comment, size_t max_len,
+ uint err_code, const char *name)
+{
+ DBUG_ENTER("validate_comment_length");
+ uint tmp_len= my_charpos(system_charset_info, comment->str,
+ comment->str + comment->length, max_len);
+ if (tmp_len < comment->length)
+ {
+ if (thd->is_strict_mode())
+ {
+ my_error(err_code, MYF(0), name, static_cast<ulong>(max_len));
+ DBUG_RETURN(true);
+ }
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, err_code,
+ ER(err_code), name, static_cast<ulong>(max_len));
+ comment->length= tmp_len;
+ }
+ DBUG_RETURN(false);
+}
+
+
+/*
+ Set table default charset, if not set
+
+ SYNOPSIS
+ set_table_default_charset()
+ create_info Table create information
+
+ DESCRIPTION
+ If the table character set was not given explicitely,
+ let's fetch the database default character set and
+ apply it to the table.
+*/
+
+static void set_table_default_charset(THD *thd,
+ HA_CREATE_INFO *create_info, char *db)
+{
+ /*
+ If the table character set was not given explicitly,
+ let's fetch the database default character set and
+ apply it to the table.
+ */
+ if (!create_info->default_table_charset)
+ {
+ HA_CREATE_INFO db_info;
+
+ load_db_opt_by_name(thd, db, &db_info);
+
+ create_info->default_table_charset= db_info.default_table_charset;
+ }
+}
+
+
+/*
+ Extend long VARCHAR fields to blob & prepare field if it's a blob
+
+ SYNOPSIS
+ prepare_blob_field()
+ sql_field Field to check
+
+ RETURN
+ 0 ok
+ 1 Error (sql_field can't be converted to blob)
+ In this case the error is given
+*/
+
+static bool prepare_blob_field(THD *thd, Create_field *sql_field)
+{
+ DBUG_ENTER("prepare_blob_field");
+
+ if (sql_field->length > MAX_FIELD_VARCHARLENGTH &&
+ !(sql_field->flags & BLOB_FLAG))
+ {
+ /* Convert long VARCHAR columns to TEXT or BLOB */
+ char warn_buff[MYSQL_ERRMSG_SIZE];
+
+ if (sql_field->def || thd->is_strict_mode())
+ {
+ my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), sql_field->field_name,
+ static_cast<ulong>(MAX_FIELD_VARCHARLENGTH /
+ sql_field->charset->mbmaxlen));
+ DBUG_RETURN(1);
+ }
+ sql_field->sql_type= MYSQL_TYPE_BLOB;
+ sql_field->flags|= BLOB_FLAG;
+ my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_AUTO_CONVERT), sql_field->field_name,
+ (sql_field->charset == &my_charset_bin) ? "VARBINARY" : "VARCHAR",
+ (sql_field->charset == &my_charset_bin) ? "BLOB" : "TEXT");
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_AUTO_CONVERT,
+ warn_buff);
+ }
+
+ if ((sql_field->flags & BLOB_FLAG) && sql_field->length)
+ {
+ if (sql_field->sql_type == FIELD_TYPE_BLOB ||
+ sql_field->sql_type == FIELD_TYPE_TINY_BLOB ||
+ sql_field->sql_type == FIELD_TYPE_MEDIUM_BLOB)
+ {
+ /* The user has given a length to the blob column */
+ sql_field->sql_type= get_blob_type_from_length(sql_field->length);
+ sql_field->pack_length= calc_pack_length(sql_field->sql_type, 0);
+ }
+ sql_field->length= 0;
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Preparation of Create_field for SP function return values.
+ Based on code used in the inner loop of mysql_prepare_create_table()
+ above.
+
+ SYNOPSIS
+ sp_prepare_create_field()
+ thd Thread object
+ sql_field Field to prepare
+
+ DESCRIPTION
+ Prepares the field structures for field creation.
+
+*/
+
+void sp_prepare_create_field(THD *thd, Create_field *sql_field)
+{
+ if (sql_field->sql_type == MYSQL_TYPE_SET ||
+ sql_field->sql_type == MYSQL_TYPE_ENUM)
+ {
+ uint32 field_length, dummy;
+ if (sql_field->sql_type == MYSQL_TYPE_SET)
+ {
+ calculate_interval_lengths(sql_field->charset,
+ sql_field->interval, &dummy,
+ &field_length);
+ sql_field->length= field_length +
+ (sql_field->interval->count - 1);
+ }
+ else /* MYSQL_TYPE_ENUM */
+ {
+ calculate_interval_lengths(sql_field->charset,
+ sql_field->interval,
+ &field_length, &dummy);
+ sql_field->length= field_length;
+ }
+ set_if_smaller(sql_field->length, MAX_FIELD_WIDTH-1);
+ }
+
+ if (sql_field->sql_type == MYSQL_TYPE_BIT)
+ {
+ sql_field->pack_flag= FIELDFLAG_NUMBER |
+ FIELDFLAG_TREAT_BIT_AS_CHAR;
+ }
+ sql_field->create_length_to_internal_length();
+ DBUG_ASSERT(sql_field->def == 0);
+ /* Can't go wrong as sql_field->def is not defined */
+ (void) prepare_blob_field(thd, sql_field);
+}
+
+
+handler *mysql_create_frm_image(THD *thd,
+ const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info, int create_table_mode,
+ KEY **key_info,
+ uint *key_count,
+ LEX_CUSTRING *frm)
+{
+ uint db_options;
+ handler *file;
+ DBUG_ENTER("mysql_create_frm_image");
+
+ if (!alter_info->create_list.elements)
+ {
+ my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0));
+ DBUG_RETURN(NULL);
+ }
+
+ set_table_default_charset(thd, create_info, (char*) db);
+
+ db_options= create_info->table_options;
+ if (create_info->row_type == ROW_TYPE_DYNAMIC ||
+ create_info->row_type == ROW_TYPE_PAGE)
+ db_options|= HA_OPTION_PACK_RECORD;
+
+ if (!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root,
+ create_info->db_type)))
+ {
+ mem_alloc_error(sizeof(handler));
+ DBUG_RETURN(NULL);
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info= thd->work_part_info;
+
+ if (!part_info && create_info->db_type->partition_flags &&
+ (create_info->db_type->partition_flags() & HA_USE_AUTO_PARTITION))
+ {
+ /*
+ Table is not defined as a partitioned table but the engine handles
+ all tables as partitioned. The handler will set up the partition info
+ object with the default settings.
+ */
+ thd->work_part_info= part_info= new partition_info();
+ if (!part_info)
+ {
+ mem_alloc_error(sizeof(partition_info));
+ goto err;
+ }
+ file->set_auto_partitions(part_info);
+ part_info->default_engine_type= create_info->db_type;
+ part_info->is_auto_partitioned= TRUE;
+ }
+ if (part_info)
+ {
+ /*
+ The table has been specified as a partitioned table.
+ If this is part of an ALTER TABLE the handler will be the partition
+ handler but we need to specify the default handler to use for
+ partitions also in the call to check_partition_info. We transport
+ this information in the default_db_type variable, it is either
+ DB_TYPE_DEFAULT or the engine set in the ALTER TABLE command.
+ */
+ handlerton *part_engine_type= create_info->db_type;
+ char *part_syntax_buf;
+ uint syntax_len;
+ handlerton *engine_type;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ partition_element *part_elem;
+
+ while ((part_elem= part_it++))
+ {
+ if (part_elem->part_comment)
+ {
+ LEX_STRING comment= {
+ part_elem->part_comment, strlen(part_elem->part_comment)
+ };
+ if (validate_comment_length(thd, &comment,
+ TABLE_PARTITION_COMMENT_MAXLEN,
+ ER_TOO_LONG_TABLE_PARTITION_COMMENT,
+ part_elem->partition_name))
+ DBUG_RETURN(NULL);
+ part_elem->part_comment[comment.length]= '\0';
+ }
+ if (part_elem->subpartitions.elements)
+ {
+ List_iterator<partition_element> sub_it(part_elem->subpartitions);
+ partition_element *subpart_elem;
+ while ((subpart_elem= sub_it++))
+ {
+ if (subpart_elem->part_comment)
+ {
+ LEX_STRING comment= {
+ subpart_elem->part_comment, strlen(subpart_elem->part_comment)
+ };
+ if (validate_comment_length(thd, &comment,
+ TABLE_PARTITION_COMMENT_MAXLEN,
+ ER_TOO_LONG_TABLE_PARTITION_COMMENT,
+ subpart_elem->partition_name))
+ DBUG_RETURN(NULL);
+ subpart_elem->part_comment[comment.length]= '\0';
+ }
+ }
+ }
+ }
+
+ if (create_info->tmp_table())
+ {
+ my_error(ER_PARTITION_NO_TEMPORARY, MYF(0));
+ goto err;
+ }
+ if ((part_engine_type == partition_hton) &&
+ part_info->default_engine_type)
+ {
+ /*
+ This only happens at ALTER TABLE.
+ default_engine_type was assigned from the engine set in the ALTER
+ TABLE command.
+ */
+ ;
+ }
+ else
+ {
+ if (create_info->used_fields & HA_CREATE_USED_ENGINE)
+ {
+ part_info->default_engine_type= create_info->db_type;
+ }
+ else
+ {
+ if (part_info->default_engine_type == NULL)
+ {
+ part_info->default_engine_type= ha_checktype(thd,
+ DB_TYPE_DEFAULT, 0, 0);
+ }
+ }
+ }
+ DBUG_PRINT("info", ("db_type = %s create_info->db_type = %s",
+ ha_resolve_storage_engine_name(part_info->default_engine_type),
+ ha_resolve_storage_engine_name(create_info->db_type)));
+ if (part_info->check_partition_info(thd, &engine_type, file,
+ create_info, FALSE))
+ goto err;
+ part_info->default_engine_type= engine_type;
+
+ /*
+ We reverse the partitioning parser and generate a standard format
+ for syntax stored in frm file.
+ */
+ if (!(part_syntax_buf= generate_partition_syntax(part_info,
+ &syntax_len,
+ TRUE, TRUE,
+ create_info,
+ alter_info,
+ NULL)))
+ goto err;
+ part_info->part_info_string= part_syntax_buf;
+ part_info->part_info_len= syntax_len;
+ if ((!(engine_type->partition_flags &&
+ engine_type->partition_flags() & HA_CAN_PARTITION)) ||
+ create_info->db_type == partition_hton)
+ {
+ /*
+ The handler assigned to the table cannot handle partitioning.
+ Assign the partition handler as the handler of the table.
+ */
+ DBUG_PRINT("info", ("db_type: %s",
+ ha_resolve_storage_engine_name(create_info->db_type)));
+ delete file;
+ create_info->db_type= partition_hton;
+ if (!(file= get_ha_partition(part_info)))
+ DBUG_RETURN(NULL);
+
+ /*
+ If we have default number of partitions or subpartitions we
+ might require to set-up the part_info object such that it
+ creates a proper .par file. The current part_info object is
+ only used to create the frm-file and .par-file.
+ */
+ if (part_info->use_default_num_partitions &&
+ part_info->num_parts &&
+ (int)part_info->num_parts !=
+ file->get_default_no_partitions(create_info))
+ {
+ uint i;
+ List_iterator<partition_element> part_it(part_info->partitions);
+ part_it++;
+ DBUG_ASSERT(thd->lex->sql_command != SQLCOM_CREATE_TABLE);
+ for (i= 1; i < part_info->partitions.elements; i++)
+ (part_it++)->part_state= PART_TO_BE_DROPPED;
+ }
+ else if (part_info->is_sub_partitioned() &&
+ part_info->use_default_num_subpartitions &&
+ part_info->num_subparts &&
+ (int)part_info->num_subparts !=
+ file->get_default_no_partitions(create_info))
+ {
+ DBUG_ASSERT(thd->lex->sql_command != SQLCOM_CREATE_TABLE);
+ part_info->num_subparts= file->get_default_no_partitions(create_info);
+ }
+ }
+ else if (create_info->db_type != engine_type)
+ {
+ /*
+ We come here when we don't use a partitioned handler.
+ Since we use a partitioned table it must be "native partitioned".
+ We have switched engine from defaults, most likely only specified
+ engines in partition clauses.
+ */
+ delete file;
+ if (!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root,
+ engine_type)))
+ {
+ mem_alloc_error(sizeof(handler));
+ DBUG_RETURN(NULL);
+ }
+ }
+ }
+ /*
+ Unless table's storage engine supports partitioning natively
+ don't allow foreign keys on partitioned tables (they won't
+ work work even with InnoDB beneath of partitioning engine).
+ If storage engine handles partitioning natively (like NDB)
+ foreign keys support is possible, so we let the engine decide.
+ */
+ if (create_info->db_type == partition_hton)
+ {
+ List_iterator_fast<Key> key_iterator(alter_info->key_list);
+ Key *key;
+ while ((key= key_iterator++))
+ {
+ if (key->type == Key::FOREIGN_KEY)
+ {
+ my_error(ER_FOREIGN_KEY_ON_PARTITIONED, MYF(0));
+ goto err;
+ }
+ }
+ }
+#endif
+
+ if (mysql_prepare_create_table(thd, create_info, alter_info, &db_options,
+ file, key_info, key_count,
+ create_table_mode))
+ goto err;
+ create_info->table_options=db_options;
+
+ *frm= build_frm_image(thd, table_name, create_info,
+ alter_info->create_list, *key_count,
+ *key_info, file);
+
+ if (frm->str)
+ DBUG_RETURN(file);
+
+err:
+ delete file;
+ DBUG_RETURN(NULL);
+}
+
+
+/**
+ Create a table
+
+ @param thd Thread object
+ @param orig_db Database for error messages
+ @param orig_table_name Table name for error messages
+ (it's different from table_name for ALTER TABLE)
+ @param db Database
+ @param table_name Table name
+ @param path Path to table (i.e. to its .FRM file without
+ the extension).
+ @param create_info Create information (like MAX_ROWS)
+ @param alter_info Description of fields and keys for new table
+ @param create_table_mode C_ORDINARY_CREATE, C_ALTER_TABLE, C_ASSISTED_DISCOVERY
+ or any positive number (for C_CREATE_SELECT).
+ @param[out] is_trans Identifies the type of engine where the table
+ was created: either trans or non-trans.
+ @param[out] key_info Array of KEY objects describing keys in table
+ which was created.
+ @param[out] key_count Number of keys in table which was created.
+
+ If one creates a temporary table, this is automatically opened
+
+ Note that this function assumes that caller already have taken
+ exclusive metadata lock on table being created or used some other
+ way to ensure that concurrent operations won't intervene.
+ mysql_create_table() is a wrapper that can be used for this.
+
+ @retval 0 OK
+ @retval 1 error
+ @retval -1 table existed but IF EXISTS was used
+*/
+
+static
+int create_table_impl(THD *thd,
+ const char *orig_db, const char *orig_table_name,
+ const char *db, const char *table_name,
+ const char *path,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ int create_table_mode,
+ bool *is_trans,
+ KEY **key_info,
+ uint *key_count,
+ LEX_CUSTRING *frm)
+{
+ const char *alias;
+ handler *file= 0;
+ int error= 1;
+ bool frm_only= create_table_mode == C_ALTER_TABLE_FRM_ONLY;
+ bool internal_tmp_table= create_table_mode == C_ALTER_TABLE || frm_only;
+ DBUG_ENTER("mysql_create_table_no_lock");
+ DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d",
+ db, table_name, internal_tmp_table));
+
+ if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)
+ {
+ if (create_info->data_file_name)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
+ "DATA DIRECTORY");
+ if (create_info->index_file_name)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
+ "INDEX DIRECTORY");
+ create_info->data_file_name= create_info->index_file_name= 0;
+ }
+ else
+ if (error_if_data_home_dir(create_info->data_file_name, "DATA DIRECTORY") ||
+ error_if_data_home_dir(create_info->index_file_name, "INDEX DIRECTORY")||
+ check_partition_dirs(thd->lex->part_info))
+ goto err;
+
+ alias= table_case_name(create_info, table_name);
+
+ /* Check if table exists */
+ if (create_info->tmp_table())
+ {
+ TABLE *tmp_table;
+ if (find_and_use_temporary_table(thd, db, table_name, &tmp_table))
+ goto err;
+ if (tmp_table)
+ {
+ bool table_creation_was_logged= tmp_table->s->table_creation_was_logged;
+ if (create_info->options & HA_LEX_CREATE_REPLACE)
+ {
+ bool is_trans;
+ /*
+ We are using CREATE OR REPLACE on an existing temporary table
+ Remove the old table so that we can re-create it.
+ */
+ if (drop_temporary_table(thd, tmp_table, &is_trans))
+ goto err;
+ }
+ else if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ goto warn;
+ else
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias);
+ goto err;
+ }
+ /*
+ We have to log this query, even if it failed later to ensure the
+ drop is done.
+ */
+#ifndef DONT_LOG_DROP_OF_TEMPORARY_TABLES
+ table_creation_was_logged= 1;
+#endif
+ if (table_creation_was_logged)
+ {
+ thd->variables.option_bits|= OPTION_KEEP_LOG;
+ thd->log_current_statement= 1;
+ create_info->table_was_deleted= 1;
+ }
+ }
+ }
+ else
+ {
+ if (!internal_tmp_table && ha_table_exists(thd, db, table_name))
+ {
+ if (create_info->options & HA_LEX_CREATE_REPLACE)
+ {
+ TABLE_LIST table_list;
+ table_list.init_one_table(db, strlen(db), table_name,
+ strlen(table_name), table_name,
+ TL_WRITE_ALLOW_WRITE);
+ table_list.table= create_info->table;
+
+ if (check_if_log_table(&table_list, TRUE, "CREATE OR REPLACE"))
+ goto err;
+
+ /*
+ Rollback the empty transaction started in mysql_create_table()
+ call to open_and_lock_tables() when we are using LOCK TABLES.
+ */
+ (void) trans_rollback_stmt(thd);
+ /* Remove normal table without logging. Keep tables locked */
+ if (mysql_rm_table_no_locks(thd, &table_list, 0, 0, 0, 1, 1))
+ goto err;
+
+ /*
+ We have to log this query, even if it failed later to ensure the
+ drop is done.
+ */
+ thd->variables.option_bits|= OPTION_KEEP_LOG;
+ thd->log_current_statement= 1;
+ create_info->table_was_deleted= 1;
+ DBUG_EXECUTE_IF("send_kill_after_delete", thd->killed= KILL_QUERY; );
+
+ /*
+ Restart statement transactions for the case of CREATE ... SELECT.
+ */
+ if (thd->lex->select_lex.item_list.elements &&
+ restart_trans_for_tables(thd, thd->lex->query_tables))
+ goto err;
+ }
+ else if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ goto warn;
+ else
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
+ goto err;
+ }
+ }
+ }
+
+ THD_STAGE_INFO(thd, stage_creating_table);
+
+ if (check_engine(thd, orig_db, orig_table_name, create_info))
+ goto err;
+
+ if (create_table_mode == C_ASSISTED_DISCOVERY)
+ {
+ /* check that it's used correctly */
+ DBUG_ASSERT(alter_info->create_list.elements == 0);
+ DBUG_ASSERT(alter_info->key_list.elements == 0);
+
+ TABLE_SHARE share;
+ handlerton *hton= create_info->db_type;
+ int ha_err;
+ Field *no_fields= 0;
+
+ if (!hton->discover_table_structure)
+ {
+ my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0));
+ goto err;
+ }
+
+ init_tmp_table_share(thd, &share, db, 0, table_name, path);
+
+ /* prepare everything for discovery */
+ share.field= &no_fields;
+ share.db_plugin= ha_lock_engine(thd, hton);
+ share.option_list= create_info->option_list;
+ share.connect_string= create_info->connect_string;
+
+ if (parse_engine_table_options(thd, hton, &share))
+ goto err;
+
+ ha_err= hton->discover_table_structure(hton, thd, &share, create_info);
+
+ /*
+ if discovery failed, the plugin will be auto-unlocked, as it
+ was locked on the THD, see above.
+ if discovery succeeded, the plugin was replaced by a globally
+ locked plugin, that will be unlocked by free_table_share()
+ */
+ if (ha_err)
+ share.db_plugin= 0; // will be auto-freed, locked above on the THD
+
+ free_table_share(&share);
+
+ if (ha_err)
+ {
+ my_error(ER_GET_ERRNO, MYF(0), ha_err, hton_name(hton)->str);
+ goto err;
+ }
+ }
+ else
+ {
+ file= mysql_create_frm_image(thd, orig_db, orig_table_name, create_info,
+ alter_info, create_table_mode, key_info,
+ key_count, frm);
+ if (!file)
+ goto err;
+ if (rea_create_table(thd, frm, path, db, table_name, create_info,
+ file, frm_only))
+ goto err;
+ }
+
+ create_info->table= 0;
+ if (!frm_only && create_info->tmp_table())
+ {
+ /*
+ Open a table (skipping table cache) and add it into
+ THD::temporary_tables list.
+ */
+
+ TABLE *table= open_table_uncached(thd, create_info->db_type, path,
+ db, table_name, true, true);
+
+ if (!table)
+ {
+ (void) rm_temporary_table(create_info->db_type, path);
+ goto err;
+ }
+
+ if (is_trans != NULL)
+ *is_trans= table->file->has_transactions();
+
+ thd->thread_specific_used= TRUE;
+ create_info->table= table; // Store pointer to table
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ else if (thd->work_part_info && frm_only)
+ {
+ /*
+ For partitioned tables we can't find some problems with table
+ until table is opened. Therefore in order to disallow creation
+ of corrupted tables we have to try to open table as the part
+ of its creation process.
+ In cases when both .FRM and SE part of table are created table
+ is implicitly open in ha_create_table() call.
+ In cases when we create .FRM without SE part we have to open
+ table explicitly.
+ */
+ TABLE table;
+ TABLE_SHARE share;
+
+ init_tmp_table_share(thd, &share, db, 0, table_name, path);
+
+ bool result= (open_table_def(thd, &share, GTS_TABLE) ||
+ open_table_from_share(thd, &share, "", 0, (uint) READ_ALL,
+ 0, &table, true));
+ if (!result)
+ (void) closefrm(&table, 0);
+
+ free_table_share(&share);
+
+ if (result)
+ {
+ char frm_name[FN_REFLEN];
+ strxnmov(frm_name, sizeof(frm_name), path, reg_ext, NullS);
+ (void) mysql_file_delete(key_file_frm, frm_name, MYF(0));
+ (void) file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG);
+ goto err;
+ }
+ }
+#endif
+
+ error= 0;
+err:
+ THD_STAGE_INFO(thd, stage_after_create);
+ delete file;
+ DBUG_PRINT("exit", ("return: %d", error));
+ DBUG_RETURN(error);
+
+warn:
+ error= -1;
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
+ alias);
+ goto err;
+}
+
+/**
+ Simple wrapper around create_table_impl() to be used
+ in various version of CREATE TABLE statement.
+*/
+
+int mysql_create_table_no_lock(THD *thd,
+ const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info, bool *is_trans,
+ int create_table_mode)
+{
+ KEY *not_used_1;
+ uint not_used_2;
+ int res;
+ char path[FN_REFLEN + 1];
+ LEX_CUSTRING frm= {0,0};
+
+ if (create_info->tmp_table())
+ build_tmptable_filename(thd, path, sizeof(path));
+ else
+ {
+ int length;
+ const char *alias= table_case_name(create_info, table_name);
+ length= build_table_filename(path, sizeof(path) - 1, db, alias,
+ "", 0);
+ // Check if we hit FN_REFLEN bytes along with file extension.
+ if (length+reg_ext_length > FN_REFLEN)
+ {
+ my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(path)-1, path);
+ return true;
+ }
+ }
+
+ res= create_table_impl(thd, db, table_name, db, table_name, path,
+ create_info, alter_info, create_table_mode,
+ is_trans, &not_used_1, &not_used_2, &frm);
+ my_free(const_cast<uchar*>(frm.str));
+ return res;
+}
+
+/**
+ Implementation of SQLCOM_CREATE_TABLE.
+
+ Take the metadata locks (including a shared lock on the affected
+ schema) and create the table. Is written to be called from
+ mysql_execute_command(), to which it delegates the common parts
+ with other commands (i.e. implicit commit before and after,
+ close of thread tables.
+*/
+
+bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info)
+{
+ const char *db= create_table->db;
+ const char *table_name= create_table->table_name;
+ bool is_trans= FALSE;
+ bool result;
+ int create_table_mode;
+ TABLE_LIST *pos_in_locked_tables= 0;
+ MDL_ticket *mdl_ticket= 0;
+ DBUG_ENTER("mysql_create_table");
+
+ DBUG_ASSERT(create_table == thd->lex->query_tables);
+
+ /* Copy temporarily the statement flags to thd for lock_table_names() */
+ uint save_thd_create_info_options= thd->lex->create_info.options;
+ thd->lex->create_info.options|= create_info->options;
+
+ /* Open or obtain an exclusive metadata lock on table being created */
+ result= open_and_lock_tables(thd, create_table, FALSE, 0);
+
+ thd->lex->create_info.options= save_thd_create_info_options;
+
+ if (result)
+ {
+ /* is_error() may be 0 if table existed and we generated a warning */
+ DBUG_RETURN(thd->is_error());
+ }
+ /* The following is needed only in case of lock tables */
+ if ((create_info->table= create_table->table))
+ {
+ pos_in_locked_tables= create_info->table->pos_in_locked_tables;
+ mdl_ticket= create_table->table->mdl_ticket;
+ }
+
+ /* Got lock. */
+ DEBUG_SYNC(thd, "locked_table_name");
+
+ if (alter_info->create_list.elements || alter_info->key_list.elements)
+ create_table_mode= C_ORDINARY_CREATE;
+ else
+ create_table_mode= C_ASSISTED_DISCOVERY;
+
+ promote_first_timestamp_column(&alter_info->create_list);
+ if (mysql_create_table_no_lock(thd, db, table_name, create_info, alter_info,
+ &is_trans, create_table_mode) > 0)
+ {
+ result= 1;
+ goto err;
+ }
+
+ /*
+ Check if we are doing CREATE OR REPLACE TABLE under LOCK TABLES
+ on a non temporary table
+ */
+ if (thd->locked_tables_mode && pos_in_locked_tables &&
+ (create_info->options & HA_LEX_CREATE_REPLACE))
+ {
+ /*
+ Add back the deleted table and re-created table as a locked table
+ This should always work as we have a meta lock on the table.
+ */
+ thd->locked_tables_list.add_back_last_deleted_lock(pos_in_locked_tables);
+ if (thd->locked_tables_list.reopen_tables(thd))
+ {
+ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
+ result= 1;
+ }
+ else
+ {
+ TABLE *table= pos_in_locked_tables->table;
+ table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ }
+ }
+
+err:
+ /* In RBR we don't need to log CREATE TEMPORARY TABLE */
+ if (thd->is_current_stmt_binlog_format_row() && create_info->tmp_table())
+ DBUG_RETURN(result);
+
+ /* Write log if no error or if we already deleted a table */
+ if (!result || thd->log_current_statement)
+ {
+ if (result && create_info->table_was_deleted)
+ {
+ /*
+ Possible locked table was dropped. We should remove meta data locks
+ associated with it and do UNLOCK_TABLES if no more locked tables.
+ */
+ thd->locked_tables_list.unlock_locked_table(thd, mdl_ticket);
+ }
+ else if (!result && create_info->tmp_table() && create_info->table)
+ {
+ /*
+ Remember that tmp table creation was logged so that we know if
+ we should log a delete of it.
+ */
+ create_info->table->s->table_creation_was_logged= 1;
+ }
+ if (write_bin_log(thd, result ? FALSE : TRUE, thd->query(),
+ thd->query_length(), is_trans))
+ result= 1;
+ }
+ DBUG_RETURN(result);
+}
+
+
+/*
+** Give the key name after the first field with an optional '_#' after
+**/
+
+static bool
+check_if_keyname_exists(const char *name, KEY *start, KEY *end)
+{
+ for (KEY *key=start ; key != end ; key++)
+ if (!my_strcasecmp(system_charset_info,name,key->name))
+ return 1;
+ return 0;
+}
+
+
+static char *
+make_unique_key_name(const char *field_name,KEY *start,KEY *end)
+{
+ char buff[MAX_FIELD_NAME],*buff_end;
+
+ if (!check_if_keyname_exists(field_name,start,end) &&
+ my_strcasecmp(system_charset_info,field_name,primary_key_name))
+ return (char*) field_name; // Use fieldname
+ buff_end=strmake(buff,field_name, sizeof(buff)-4);
+
+ /*
+ Only 3 chars + '\0' left, so need to limit to 2 digit
+ This is ok as we can't have more than 100 keys anyway
+ */
+ for (uint i=2 ; i< 100; i++)
+ {
+ *buff_end= '_';
+ int10_to_str(i, buff_end+1, 10);
+ if (!check_if_keyname_exists(buff,start,end))
+ return sql_strdup(buff);
+ }
+ return (char*) "not_specified"; // Should never happen
+}
+
+
+/****************************************************************************
+** Alter a table definition
+****************************************************************************/
+
+
+/**
+ Rename a table.
+
+ @param base The handlerton handle.
+ @param old_db The old database name.
+ @param old_name The old table name.
+ @param new_db The new database name.
+ @param new_name The new table name.
+ @param flags flags
+ FN_FROM_IS_TMP old_name is temporary.
+ FN_TO_IS_TMP new_name is temporary.
+ NO_FRM_RENAME Don't rename the FRM file
+ but only the table in the storage engine.
+ NO_HA_TABLE Don't rename table in engine.
+ NO_FK_CHECKS Don't check FK constraints during rename.
+
+ @return false OK
+ @return true Error
+*/
+
+bool
+mysql_rename_table(handlerton *base, const char *old_db,
+ const char *old_name, const char *new_db,
+ const char *new_name, uint flags)
+{
+ THD *thd= current_thd;
+ char from[FN_REFLEN + 1], to[FN_REFLEN + 1],
+ lc_from[FN_REFLEN + 1], lc_to[FN_REFLEN + 1];
+ char *from_base= from, *to_base= to;
+ char tmp_name[SAFE_NAME_LEN+1], tmp_db_name[SAFE_NAME_LEN+1];
+ handler *file;
+ int error=0;
+ ulonglong save_bits= thd->variables.option_bits;
+ int length;
+ DBUG_ENTER("mysql_rename_table");
+ DBUG_PRINT("enter", ("old: '%s'.'%s' new: '%s'.'%s'",
+ old_db, old_name, new_db, new_name));
+
+ // Temporarily disable foreign key checks
+ if (flags & NO_FK_CHECKS)
+ thd->variables.option_bits|= OPTION_NO_FOREIGN_KEY_CHECKS;
+
+ file= (base == NULL ? 0 :
+ get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base));
+
+ build_table_filename(from, sizeof(from) - 1, old_db, old_name, "",
+ flags & FN_FROM_IS_TMP);
+ length= build_table_filename(to, sizeof(to) - 1, new_db, new_name, "",
+ flags & FN_TO_IS_TMP);
+ // Check if we hit FN_REFLEN bytes along with file extension.
+ if (length+reg_ext_length > FN_REFLEN)
+ {
+ my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(to)-1, to);
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ If lower_case_table_names == 2 (case-preserving but case-insensitive
+ file system) and the storage is not HA_FILE_BASED, we need to provide
+ a lowercase file name, but we leave the .frm in mixed case.
+ */
+ if (lower_case_table_names == 2 && file &&
+ !(file->ha_table_flags() & HA_FILE_BASED))
+ {
+ strmov(tmp_name, old_name);
+ my_casedn_str(files_charset_info, tmp_name);
+ strmov(tmp_db_name, old_db);
+ my_casedn_str(files_charset_info, tmp_db_name);
+
+ build_table_filename(lc_from, sizeof(lc_from) - 1, tmp_db_name, tmp_name,
+ "", flags & FN_FROM_IS_TMP);
+ from_base= lc_from;
+
+ strmov(tmp_name, new_name);
+ my_casedn_str(files_charset_info, tmp_name);
+ strmov(tmp_db_name, new_db);
+ my_casedn_str(files_charset_info, tmp_db_name);
+
+ build_table_filename(lc_to, sizeof(lc_to) - 1, tmp_db_name, tmp_name, "",
+ flags & FN_TO_IS_TMP);
+ to_base= lc_to;
+ }
+
+ if (flags & NO_HA_TABLE)
+ {
+ if (rename_file_ext(from,to,reg_ext))
+ error= my_errno;
+ (void) file->ha_create_partitioning_metadata(to, from, CHF_RENAME_FLAG);
+ }
+ else if (!file || !(error=file->ha_rename_table(from_base, to_base)))
+ {
+ if (!(flags & NO_FRM_RENAME) && rename_file_ext(from,to,reg_ext))
+ {
+ error=my_errno;
+ if (file)
+ {
+ if (error == ENOENT)
+ error= 0; // this is ok if file->ha_rename_table() succeeded
+ else
+ file->ha_rename_table(to_base, from_base); // Restore old file name
+ }
+ }
+ }
+ delete file;
+ if (error == HA_ERR_WRONG_COMMAND)
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "ALTER TABLE");
+ else if (error)
+ my_error(ER_ERROR_ON_RENAME, MYF(0), from, to, error);
+ else if (!(flags & FN_IS_TMP))
+ mysql_audit_rename_table(thd, old_db, old_name, new_db, new_name);
+
+ /*
+ Remove the old table share from the pfs table share array. The new table
+ share will be created when the renamed table is first accessed.
+ */
+ if (likely(error == 0))
+ {
+ PSI_CALL_drop_table_share(flags & FN_FROM_IS_TMP,
+ old_db, strlen(old_db),
+ old_name, strlen(old_name));
+ }
+
+ // Restore options bits to the original value
+ thd->variables.option_bits= save_bits;
+
+ DBUG_RETURN(error != 0);
+}
+
+
+/*
+ Create a table identical to the specified table
+
+ SYNOPSIS
+ mysql_create_like_table()
+ thd Thread object
+ table Table list element for target table
+ src_table Table list element for source table
+ create_info Create info
+
+ RETURN VALUES
+ FALSE OK
+ TRUE error
+*/
+
+bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
+ TABLE_LIST* src_table,
+ HA_CREATE_INFO *create_info)
+{
+ HA_CREATE_INFO local_create_info;
+ TABLE_LIST *pos_in_locked_tables= 0;
+ Alter_info local_alter_info;
+ Alter_table_ctx local_alter_ctx; // Not used
+ bool res= TRUE;
+ bool is_trans= FALSE;
+ bool do_logging= FALSE;
+ uint not_used;
+ int create_res;
+ DBUG_ENTER("mysql_create_like_table");
+
+ /*
+ We the open source table to get its description in HA_CREATE_INFO
+ and Alter_info objects. This also acquires a shared metadata lock
+ on this table which ensures that no concurrent DDL operation will
+ mess with it.
+ Also in case when we create non-temporary table open_tables()
+ call obtains an exclusive metadata lock on target table ensuring
+ that we can safely perform table creation.
+ Thus by holding both these locks we ensure that our statement is
+ properly isolated from all concurrent operations which matter.
+ */
+
+ /* Copy temporarily the statement flags to thd for lock_table_names() */
+ uint save_thd_create_info_options= thd->lex->create_info.options;
+ thd->lex->create_info.options|= create_info->options;
+ res= open_tables(thd, &thd->lex->query_tables, &not_used, 0);
+ thd->lex->create_info.options= save_thd_create_info_options;
+
+ if (res)
+ {
+ /* is_error() may be 0 if table existed and we generated a warning */
+ res= thd->is_error();
+ goto err;
+ }
+ /* Ensure we don't try to create something from which we select from */
+ if ((create_info->options & HA_LEX_CREATE_REPLACE) &&
+ !create_info->tmp_table())
+ {
+ TABLE_LIST *duplicate;
+ if ((duplicate= unique_table(thd, table, src_table, 0)))
+ {
+ update_non_unique_table_error(src_table, "CREATE", duplicate);
+ goto err;
+ }
+ }
+
+ src_table->table->use_all_columns();
+
+ DEBUG_SYNC(thd, "create_table_like_after_open");
+
+ /* Fill HA_CREATE_INFO and Alter_info with description of source table. */
+ bzero((char*) &local_create_info, sizeof(local_create_info));
+ local_create_info.db_type= src_table->table->s->db_type();
+ local_create_info.row_type= src_table->table->s->row_type;
+ if (mysql_prepare_alter_table(thd, src_table->table, &local_create_info,
+ &local_alter_info, &local_alter_ctx))
+ goto err;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ /* Partition info is not handled by mysql_prepare_alter_table() call. */
+ if (src_table->table->part_info)
+ thd->work_part_info= src_table->table->part_info->get_clone();
+#endif
+
+ /*
+ Adjust description of source table before using it for creation of
+ target table.
+
+ Similarly to SHOW CREATE TABLE we ignore MAX_ROWS attribute of
+ temporary table which represents I_S table.
+ */
+ if (src_table->schema_table)
+ local_create_info.max_rows= 0;
+ /* Set IF NOT EXISTS option as in the CREATE TABLE LIKE statement. */
+ local_create_info.options|= (create_info->options &
+ (HA_LEX_CREATE_IF_NOT_EXISTS |
+ HA_LEX_CREATE_REPLACE));
+ /* Replace type of source table with one specified in the statement. */
+ local_create_info.options&= ~HA_LEX_CREATE_TMP_TABLE;
+ local_create_info.options|= create_info->tmp_table();
+ /* Reset auto-increment counter for the new table. */
+ local_create_info.auto_increment_value= 0;
+ /*
+ Do not inherit values of DATA and INDEX DIRECTORY options from
+ the original table. This is documented behavior.
+ */
+ local_create_info.data_file_name= local_create_info.index_file_name= NULL;
+
+ /* The following is needed only in case of lock tables */
+ if ((local_create_info.table= thd->lex->query_tables->table))
+ pos_in_locked_tables= local_create_info.table->pos_in_locked_tables;
+
+ res= ((create_res=
+ mysql_create_table_no_lock(thd, table->db, table->table_name,
+ &local_create_info, &local_alter_info,
+ &is_trans, C_ORDINARY_CREATE)) > 0);
+ /* Remember to log if we deleted something */
+ do_logging= thd->log_current_statement;
+ if (res)
+ goto err;
+
+ /*
+ Check if we are doing CREATE OR REPLACE TABLE under LOCK TABLES
+ on a non temporary table
+ */
+ if (thd->locked_tables_mode && pos_in_locked_tables &&
+ (create_info->options & HA_LEX_CREATE_REPLACE))
+ {
+ /*
+ Add back the deleted table and re-created table as a locked table
+ This should always work as we have a meta lock on the table.
+ */
+ thd->locked_tables_list.add_back_last_deleted_lock(pos_in_locked_tables);
+ if (thd->locked_tables_list.reopen_tables(thd))
+ {
+ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
+ res= 1; // We got an error
+ }
+ else
+ {
+ /*
+ Get pointer to the newly opened table. We need this to ensure we
+ don't reopen the table when doing statment logging below.
+ */
+ table->table= pos_in_locked_tables->table;
+ table->table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ }
+ }
+ else
+ {
+ /*
+ Ensure that we have an exclusive lock on target table if we are creating
+ non-temporary table.
+ */
+ DBUG_ASSERT((create_info->tmp_table()) ||
+ thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db,
+ table->table_name,
+ MDL_EXCLUSIVE));
+ }
+
+ DEBUG_SYNC(thd, "create_table_like_before_binlog");
+
+ /*
+ We have to write the query before we unlock the tables.
+ */
+ if (thd->is_current_stmt_binlog_format_row())
+ {
+ /*
+ Since temporary tables are not replicated under row-based
+ replication, CREATE TABLE ... LIKE ... needs special
+ treatement. We have four cases to consider, according to the
+ following decision table:
+
+ ==== ========= ========= ==============================
+ Case Target Source Write to binary log
+ ==== ========= ========= ==============================
+ 1 normal normal Original statement
+ 2 normal temporary Generated statement if the table
+ was created.
+ 3 temporary normal Nothing
+ 4 temporary temporary Nothing
+ ==== ========= ========= ==============================
+ */
+ if (!(create_info->tmp_table()))
+ {
+ if (src_table->table->s->tmp_table) // Case 2
+ {
+ char buf[2048];
+ String query(buf, sizeof(buf), system_charset_info);
+ query.length(0); // Have to zero it since constructor doesn't
+ Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN |
+ MYSQL_OPEN_IGNORE_KILLED);
+ bool new_table= FALSE; // Whether newly created table is open.
+
+ if (create_res != 0)
+ {
+ /*
+ Table or view with same name already existed and we where using
+ IF EXISTS. Continue without logging anything.
+ */
+ do_logging= 0;
+ goto err;
+ }
+ if (!table->table)
+ {
+ TABLE_LIST::enum_open_strategy save_open_strategy;
+ int open_res;
+ /* Force the newly created table to be opened */
+ save_open_strategy= table->open_strategy;
+ table->open_strategy= TABLE_LIST::OPEN_NORMAL;
+
+ /*
+ In order for show_create_table() to work we need to open
+ destination table if it is not already open (i.e. if it
+ has not existed before). We don't need acquire metadata
+ lock in order to do this as we already hold exclusive
+ lock on this table. The table will be closed by
+ close_thread_table() at the end of this branch.
+ */
+ open_res= open_table(thd, table, thd->mem_root, &ot_ctx);
+ /* Restore */
+ table->open_strategy= save_open_strategy;
+ if (open_res)
+ {
+ res= 1;
+ goto err;
+ }
+ new_table= TRUE;
+ }
+ /*
+ We have to re-test if the table was a view as the view may not
+ have been opened until just above.
+ */
+ if (!table->view)
+ {
+ int result __attribute__((unused))=
+ show_create_table(thd, table, &query, create_info, WITHOUT_DB_NAME);
+
+ DBUG_ASSERT(result == 0); // show_create_table() always return 0
+ do_logging= FALSE;
+ if (write_bin_log(thd, TRUE, query.ptr(), query.length()))
+ {
+ res= 1;
+ do_logging= 0;
+ goto err;
+ }
+
+ if (new_table)
+ {
+ DBUG_ASSERT(thd->open_tables == table->table);
+ /*
+ When opening the table, we ignored the locked tables
+ (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table
+ without risking to close some locked table.
+ */
+ close_thread_table(thd, &thd->open_tables);
+ }
+ }
+ }
+ else // Case 1
+ do_logging= TRUE;
+ }
+ /*
+ Case 3 and 4 does nothing under RBR
+ */
+ }
+ else
+ {
+ DBUG_PRINT("info",
+ ("res: %d tmp_table: %d create_info->table: %p",
+ res, create_info->tmp_table(), local_create_info.table));
+ if (!res && create_info->tmp_table() && local_create_info.table)
+ {
+ /*
+ Remember that tmp table creation was logged so that we know if
+ we should log a delete of it.
+ */
+ local_create_info.table->s->table_creation_was_logged= 1;
+ }
+ do_logging= TRUE;
+ }
+
+err:
+ if (do_logging)
+ {
+ if (res && create_info->table_was_deleted)
+ {
+ /*
+ Table was not deleted. Original table was deleted.
+ We have to log it.
+ */
+ log_drop_table(thd, table->db, table->db_length,
+ table->table_name, table->table_name_length,
+ create_info->tmp_table());
+ }
+ else if (write_bin_log(thd, res ? FALSE : TRUE, thd->query(),
+ thd->query_length(), is_trans))
+ res= 1;
+ }
+ DBUG_RETURN(res);
+}
+
+
+/* table_list should contain just one table */
+int mysql_discard_or_import_tablespace(THD *thd,
+ TABLE_LIST *table_list,
+ bool discard)
+{
+ Alter_table_prelocking_strategy alter_prelocking_strategy;
+ int error;
+ DBUG_ENTER("mysql_discard_or_import_tablespace");
+
+ mysql_audit_alter_table(thd, table_list);
+
+ /*
+ Note that DISCARD/IMPORT TABLESPACE always is the only operation in an
+ ALTER TABLE
+ */
+
+ THD_STAGE_INFO(thd, stage_discard_or_import_tablespace);
+
+ /*
+ We set this flag so that ha_innobase::open and ::external_lock() do
+ not complain when we lock the table
+ */
+ thd->tablespace_op= TRUE;
+ /*
+ Adjust values of table-level and metadata which was set in parser
+ for the case general ALTER TABLE.
+ */
+ table_list->mdl_request.set_type(MDL_EXCLUSIVE);
+ table_list->lock_type= TL_WRITE;
+ /* Do not open views. */
+ table_list->required_type= FRMTYPE_TABLE;
+
+ if (open_and_lock_tables(thd, table_list, FALSE, 0,
+ &alter_prelocking_strategy))
+ {
+ thd->tablespace_op=FALSE;
+ DBUG_RETURN(-1);
+ }
+
+ error= table_list->table->file->ha_discard_or_import_tablespace(discard);
+
+ THD_STAGE_INFO(thd, stage_end);
+
+ if (error)
+ goto err;
+
+ /*
+ The 0 in the call below means 'not in a transaction', which means
+ immediate invalidation; that is probably what we wish here
+ */
+ query_cache_invalidate3(thd, table_list, 0);
+
+ /* The ALTER TABLE is always in its own transaction */
+ error= trans_commit_stmt(thd);
+ if (trans_commit_implicit(thd))
+ error=1;
+ if (error)
+ goto err;
+ error= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
+
+err:
+ thd->tablespace_op=FALSE;
+
+ if (error == 0)
+ {
+ my_ok(thd);
+ DBUG_RETURN(0);
+ }
+
+ table_list->table->file->print_error(error, MYF(0));
+
+ DBUG_RETURN(-1);
+}
+
+
+/**
+ Check if key is a candidate key, i.e. a unique index with no index
+ fields partial or nullable.
+*/
+
+static bool is_candidate_key(KEY *key)
+{
+ KEY_PART_INFO *key_part;
+ KEY_PART_INFO *key_part_end= key->key_part + key->user_defined_key_parts;
+
+ if (!(key->flags & HA_NOSAME) || (key->flags & HA_NULL_PART_KEY))
+ return false;
+
+ for (key_part= key->key_part; key_part < key_part_end; key_part++)
+ {
+ if (key_part->key_part_flag & HA_PART_KEY_SEG)
+ return false;
+ }
+ return true;
+}
+
+
+/*
+ Preparation for table creation
+
+ SYNOPSIS
+ handle_if_exists_option()
+ thd Thread object.
+ table The altered table.
+ alter_info List of columns and indexes to create
+
+ DESCRIPTION
+ Looks for the IF [NOT] EXISTS options, checks the states and remove items
+ from the list if existing found.
+
+ RETURN VALUES
+ NONE
+*/
+
+static void
+handle_if_exists_options(THD *thd, TABLE *table, Alter_info *alter_info)
+{
+ Field **f_ptr;
+ DBUG_ENTER("handle_if_exists_option");
+
+ /* Handle ADD COLUMN IF NOT EXISTS. */
+ {
+ List_iterator<Create_field> it(alter_info->create_list);
+ Create_field *sql_field;
+
+ while ((sql_field=it++))
+ {
+ if (!sql_field->create_if_not_exists || sql_field->change)
+ continue;
+ /*
+ If there is a field with the same name in the table already,
+ remove the sql_field from the list.
+ */
+ for (f_ptr=table->field; *f_ptr; f_ptr++)
+ {
+ if (my_strcasecmp(system_charset_info,
+ sql_field->field_name, (*f_ptr)->field_name) == 0)
+ goto drop_create_field;
+ }
+ {
+ /*
+ If in the ADD list there is a field with the same name,
+ remove the sql_field from the list.
+ */
+ List_iterator<Create_field> chk_it(alter_info->create_list);
+ Create_field *chk_field;
+ while ((chk_field= chk_it++) && chk_field != sql_field)
+ {
+ if (my_strcasecmp(system_charset_info,
+ sql_field->field_name, chk_field->field_name) == 0)
+ goto drop_create_field;
+ }
+ }
+ continue;
+drop_create_field:
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_DUP_FIELDNAME, ER(ER_DUP_FIELDNAME),
+ sql_field->field_name);
+ it.remove();
+ if (alter_info->create_list.is_empty())
+ {
+ alter_info->flags&= ~Alter_info::ALTER_ADD_COLUMN;
+ if (alter_info->key_list.is_empty())
+ alter_info->flags&= ~(Alter_info::ALTER_ADD_INDEX |
+ Alter_info::ADD_FOREIGN_KEY);
+ }
+ }
+ }
+
+ /* Handle MODIFY COLUMN IF EXISTS. */
+ {
+ List_iterator<Create_field> it(alter_info->create_list);
+ Create_field *sql_field;
+
+ while ((sql_field=it++))
+ {
+ if (!sql_field->create_if_not_exists || !sql_field->change)
+ continue;
+ /*
+ If there is NO field with the same name in the table already,
+ remove the sql_field from the list.
+ */
+ for (f_ptr=table->field; *f_ptr; f_ptr++)
+ {
+ if (my_strcasecmp(system_charset_info,
+ sql_field->change, (*f_ptr)->field_name) == 0)
+ {
+ break;
+ }
+ }
+ if (*f_ptr == NULL)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR),
+ sql_field->change, table->s->table_name.str);
+ it.remove();
+ if (alter_info->create_list.is_empty())
+ {
+ alter_info->flags&= ~(Alter_info::ALTER_ADD_COLUMN |
+ Alter_info::ALTER_CHANGE_COLUMN);
+ if (alter_info->key_list.is_empty())
+ alter_info->flags&= ~Alter_info::ALTER_ADD_INDEX;
+ }
+ }
+ }
+ }
+
+ /* Handle DROP COLUMN/KEY IF EXISTS. */
+ {
+ List_iterator<Alter_drop> drop_it(alter_info->drop_list);
+ Alter_drop *drop;
+ bool remove_drop;
+ while ((drop= drop_it++))
+ {
+ if (!drop->drop_if_exists)
+ continue;
+ remove_drop= TRUE;
+ if (drop->type == Alter_drop::COLUMN)
+ {
+ /*
+ If there is NO field with that name in the table,
+ remove the 'drop' from the list.
+ */
+ for (f_ptr=table->field; *f_ptr; f_ptr++)
+ {
+ if (my_strcasecmp(system_charset_info,
+ drop->name, (*f_ptr)->field_name) == 0)
+ {
+ remove_drop= FALSE;
+ break;
+ }
+ }
+ }
+ else /* Alter_drop::KEY */
+ {
+ uint n_key;
+ if (drop->type != Alter_drop::FOREIGN_KEY)
+ {
+ for (n_key=0; n_key < table->s->keys; n_key++)
+ {
+ if (my_strcasecmp(system_charset_info,
+ drop->name, table->key_info[n_key].name) == 0)
+ {
+ remove_drop= FALSE;
+ break;
+ }
+ }
+ }
+ else
+ {
+ List <FOREIGN_KEY_INFO> fk_child_key_list;
+ FOREIGN_KEY_INFO *f_key;
+ table->file->get_foreign_key_list(thd, &fk_child_key_list);
+ List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
+ while ((f_key= fk_key_it++))
+ {
+ if (my_strcasecmp(system_charset_info, f_key->foreign_id->str,
+ drop->name) == 0)
+ {
+ remove_drop= FALSE;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!remove_drop)
+ {
+ /*
+ Check if the name appears twice in the DROP list.
+ */
+ List_iterator<Alter_drop> chk_it(alter_info->drop_list);
+ Alter_drop *chk_drop;
+ while ((chk_drop= chk_it++) && chk_drop != drop)
+ {
+ if (drop->type == chk_drop->type &&
+ my_strcasecmp(system_charset_info,
+ drop->name, chk_drop->name) == 0)
+ {
+ remove_drop= TRUE;
+ break;
+ }
+ }
+ }
+
+ if (remove_drop)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_CANT_DROP_FIELD_OR_KEY, ER(ER_CANT_DROP_FIELD_OR_KEY),
+ drop->name);
+ drop_it.remove();
+ if (alter_info->drop_list.is_empty())
+ alter_info->flags&= ~(Alter_info::ALTER_DROP_COLUMN |
+ Alter_info::ALTER_DROP_INDEX |
+ Alter_info::DROP_FOREIGN_KEY);
+ }
+ }
+ }
+
+ /* ALTER TABLE ADD KEY IF NOT EXISTS */
+ /* ALTER TABLE ADD FOREIGN KEY IF NOT EXISTS */
+ {
+ Key *key;
+ List_iterator<Key> key_it(alter_info->key_list);
+ uint n_key;
+ const char *keyname;
+ while ((key=key_it++))
+ {
+ if (!key->create_if_not_exists)
+ continue;
+ /* If the name of the key is not specified, */
+ /* let us check the name of the first key part. */
+ if ((keyname= key->name.str) == NULL)
+ {
+ List_iterator<Key_part_spec> part_it(key->columns);
+ Key_part_spec *kp;
+ if ((kp= part_it++))
+ keyname= kp->field_name.str;
+ if (keyname == NULL)
+ continue;
+ }
+ if (key->type != Key::FOREIGN_KEY)
+ {
+ for (n_key=0; n_key < table->s->keys; n_key++)
+ {
+ if (my_strcasecmp(system_charset_info,
+ keyname, table->key_info[n_key].name) == 0)
+ {
+ goto remove_key;
+ }
+ }
+ }
+ else
+ {
+ List <FOREIGN_KEY_INFO> fk_child_key_list;
+ FOREIGN_KEY_INFO *f_key;
+ table->file->get_foreign_key_list(thd, &fk_child_key_list);
+ List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
+ while ((f_key= fk_key_it++))
+ {
+ if (my_strcasecmp(system_charset_info, f_key->foreign_id->str,
+ key->name.str) == 0)
+ goto remove_key;
+ }
+ }
+
+ {
+ Key *chk_key;
+ List_iterator<Key> chk_it(alter_info->key_list);
+ const char *chkname;
+ while ((chk_key=chk_it++) && chk_key != key)
+ {
+ if ((chkname= chk_key->name.str) == NULL)
+ {
+ List_iterator<Key_part_spec> part_it(chk_key->columns);
+ Key_part_spec *kp;
+ if ((kp= part_it++))
+ chkname= kp->field_name.str;
+ if (keyname == NULL)
+ continue;
+ }
+ if (key->type == chk_key->type &&
+ my_strcasecmp(system_charset_info, keyname, chkname) == 0)
+ goto remove_key;
+ }
+ }
+ continue;
+
+remove_key:
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_DUP_KEYNAME, ER(ER_DUP_KEYNAME), keyname);
+ key_it.remove();
+ if (key->type == Key::FOREIGN_KEY)
+ {
+ /* ADD FOREIGN KEY appends two items. */
+ key_it.remove();
+ }
+ if (alter_info->key_list.is_empty())
+ alter_info->flags&= ~(Alter_info::ALTER_ADD_INDEX |
+ Alter_info::ADD_FOREIGN_KEY);
+ }
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *tab_part_info= table->part_info;
+ if (tab_part_info && thd->lex->check_exists)
+ {
+ /* ALTER TABLE ADD PARTITION IF NOT EXISTS */
+ if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION)
+ {
+ partition_info *alt_part_info= thd->lex->part_info;
+ if (alt_part_info)
+ {
+ List_iterator<partition_element> new_part_it(alt_part_info->partitions);
+ partition_element *pe;
+ while ((pe= new_part_it++))
+ {
+ if (!tab_part_info->has_unique_name(pe))
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_SAME_NAME_PARTITION, ER(ER_SAME_NAME_PARTITION),
+ pe->partition_name);
+ alter_info->flags&= ~Alter_info::ALTER_ADD_PARTITION;
+ thd->lex->part_info= NULL;
+ break;
+ }
+ }
+ }
+ }
+ /* ALTER TABLE DROP PARTITION IF EXISTS */
+ if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION)
+ {
+ List_iterator<char> names_it(alter_info->partition_names);
+ char *name;
+
+ while ((name= names_it++))
+ {
+ List_iterator<partition_element> part_it(tab_part_info->partitions);
+ partition_element *part_elem;
+ while ((part_elem= part_it++))
+ {
+ if (my_strcasecmp(system_charset_info,
+ part_elem->partition_name, name) == 0)
+ break;
+ }
+ if (!part_elem)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_DROP_PARTITION_NON_EXISTENT,
+ ER(ER_DROP_PARTITION_NON_EXISTENT), "DROP");
+ names_it.remove();
+ }
+ }
+ if (alter_info->partition_names.elements == 0)
+ alter_info->flags&= ~Alter_info::ALTER_DROP_PARTITION;
+ }
+ }
+#endif /*WITH_PARTITION_STORAGE_ENGINE*/
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get Create_field object for newly created table by field index.
+
+ @param alter_info Alter_info describing newly created table.
+ @param idx Field index.
+*/
+
+static Create_field *get_field_by_index(Alter_info *alter_info, uint idx)
+{
+ List_iterator_fast<Create_field> field_it(alter_info->create_list);
+ uint field_idx= 0;
+ Create_field *field;
+
+ while ((field= field_it++) && field_idx < idx)
+ { field_idx++; }
+
+ return field;
+}
+
+
+static int compare_uint(const uint *s, const uint *t)
+{
+ return (*s < *t) ? -1 : ((*s > *t) ? 1 : 0);
+}
+
+
+/**
+ Compare original and new versions of a table and fill Alter_inplace_info
+ describing differences between those versions.
+
+ @param thd Thread
+ @param table The original table.
+ @param varchar Indicates that new definition has new
+ VARCHAR column.
+ @param[in/out] ha_alter_info Data structure which already contains
+ basic information about create options,
+ field and keys for the new version of
+ table and which should be completed with
+ more detailed information needed for
+ in-place ALTER.
+
+ First argument 'table' contains information of the original
+ table, which includes all corresponding parts that the new
+ table has in arguments create_list, key_list and create_info.
+
+ Compare the changes between the original and new table definitions.
+ The result of this comparison is then passed to SE which determines
+ whether it can carry out these changes in-place.
+
+ Mark any changes detected in the ha_alter_flags.
+ We generally try to specify handler flags only if there are real
+ changes. But in cases when it is cumbersome to determine if some
+ attribute has really changed we might choose to set flag
+ pessimistically, for example, relying on parser output only.
+
+ If there are no data changes, but index changes, 'index_drop_buffer'
+ and/or 'index_add_buffer' are populated with offsets into
+ table->key_info or key_info_buffer respectively for the indexes
+ that need to be dropped and/or (re-)created.
+
+ Note that this function assumes that it is OK to change Alter_info
+ and HA_CREATE_INFO which it gets. It is caller who is responsible
+ for creating copies for this structures if he needs them unchanged.
+
+ @retval true error
+ @retval false success
+*/
+
+static bool fill_alter_inplace_info(THD *thd,
+ TABLE *table,
+ bool varchar,
+ Alter_inplace_info *ha_alter_info)
+{
+ Field **f_ptr, *field;
+ List_iterator_fast<Create_field> new_field_it;
+ Create_field *new_field;
+ KEY_PART_INFO *key_part, *new_part;
+ KEY_PART_INFO *end;
+ uint candidate_key_count= 0;
+ Alter_info *alter_info= ha_alter_info->alter_info;
+ DBUG_ENTER("fill_alter_inplace_info");
+
+ /* Allocate result buffers. */
+ if (! (ha_alter_info->index_drop_buffer=
+ (KEY**) thd->alloc(sizeof(KEY*) * table->s->keys)) ||
+ ! (ha_alter_info->index_add_buffer=
+ (uint*) thd->alloc(sizeof(uint) *
+ alter_info->key_list.elements)))
+ DBUG_RETURN(true);
+
+ /* First we setup ha_alter_flags based on what was detected by parser. */
+ if (alter_info->flags & Alter_info::ALTER_ADD_COLUMN)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_COLUMN;
+ if (alter_info->flags & Alter_info::ALTER_DROP_COLUMN)
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_COLUMN;
+ /*
+ Comparing new and old default values of column is cumbersome.
+ So instead of using such a comparison for detecting if default
+ has really changed we rely on flags set by parser to get an
+ approximate value for storage engine flag.
+ */
+ if (alter_info->flags & (Alter_info::ALTER_CHANGE_COLUMN |
+ Alter_info::ALTER_CHANGE_COLUMN_DEFAULT))
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_DEFAULT;
+ if (alter_info->flags & Alter_info::ADD_FOREIGN_KEY)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_FOREIGN_KEY;
+ if (alter_info->flags & Alter_info::DROP_FOREIGN_KEY)
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_FOREIGN_KEY;
+ if (alter_info->flags & Alter_info::ALTER_OPTIONS)
+ ha_alter_info->handler_flags|= Alter_inplace_info::CHANGE_CREATE_OPTION;
+ if (alter_info->flags & Alter_info::ALTER_RENAME)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_RENAME;
+ /* Check partition changes */
+ if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_COALESCE_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::COALESCE_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::REORGANIZE_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_TABLE_REORG)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_TABLE_REORG;
+ if (alter_info->flags & Alter_info::ALTER_REMOVE_PARTITIONING)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_REMOVE_PARTITIONING;
+ if (alter_info->flags & Alter_info::ALTER_ALL_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_ALL_PARTITION;
+ /* Check for: ALTER TABLE FORCE, ALTER TABLE ENGINE and OPTIMIZE TABLE. */
+ if (alter_info->flags & Alter_info::ALTER_RECREATE)
+ ha_alter_info->handler_flags|= Alter_inplace_info::RECREATE_TABLE;
+
+ /*
+ If we altering table with old VARCHAR fields we will be automatically
+ upgrading VARCHAR column types.
+ */
+ if (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE;
+
+ /*
+ Go through fields in old version of table and detect changes to them.
+ We don't want to rely solely on Alter_info flags for this since:
+ a) new definition of column can be fully identical to the old one
+ despite the fact that this column is mentioned in MODIFY clause.
+ b) even if new column type differs from its old column from metadata
+ point of view, it might be identical from storage engine point
+ of view (e.g. when ENUM('a','b') is changed to ENUM('a','b',c')).
+ c) flags passed to storage engine contain more detailed information
+ about nature of changes than those provided from parser.
+ */
+ for (f_ptr= table->field; (field= *f_ptr); f_ptr++)
+ {
+ /* Clear marker for renamed or dropped field
+ which we are going to set later. */
+ field->flags&= ~(FIELD_IS_RENAMED | FIELD_IS_DROPPED);
+
+ /* Use transformed info to evaluate flags for storage engine. */
+ uint new_field_index= 0;
+ new_field_it.init(alter_info->create_list);
+ while ((new_field= new_field_it++))
+ {
+ if (new_field->field == field)
+ break;
+ new_field_index++;
+ }
+
+ if (new_field)
+ {
+ /* Field is not dropped. Evaluate changes bitmap for it. */
+
+ /*
+ Check if type of column has changed to some incompatible type.
+ */
+ switch (field->is_equal(new_field))
+ {
+ case IS_EQUAL_NO:
+ /* New column type is incompatible with old one. */
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE;
+ if (table->s->tmp_table == NO_TMP_TABLE)
+ {
+ delete_statistics_for_column(thd, table, field);
+ KEY *key_info= table->key_info;
+ for (uint i=0; i < table->s->keys; i++, key_info++)
+ {
+ if (field->part_of_key.is_set(i))
+ {
+ uint key_parts= table->actual_n_key_parts(key_info);
+ for (uint j= 0; j < key_parts; j++)
+ {
+ if (key_info->key_part[j].fieldnr-1 == field->field_index)
+ {
+ delete_statistics_for_index(thd, table, key_info,
+ j >= key_info->user_defined_key_parts);
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+ case IS_EQUAL_YES:
+ /*
+ New column is the same as the old one or the fully compatible with
+ it (for example, ENUM('a','b') was changed to ENUM('a','b','c')).
+ Such a change if any can ALWAYS be carried out by simply updating
+ data-dictionary without even informing storage engine.
+ No flag is set in this case.
+ */
+ break;
+ case IS_EQUAL_PACK_LENGTH:
+ /*
+ New column type differs from the old one, but has compatible packed
+ data representation. Depending on storage engine, such a change can
+ be carried out by simply updating data dictionary without changing
+ actual data (for example, VARCHAR(300) is changed to VARCHAR(400)).
+ */
+ ha_alter_info->handler_flags|= Alter_inplace_info::
+ ALTER_COLUMN_EQUAL_PACK_LENGTH;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ /* Safety. */
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE;
+ }
+
+ /*
+ Check if the altered column is computed and either
+ is stored or is used in the partitioning expression.
+ TODO: Mark such a column with an alter flag only if
+ the defining expression has changed.
+ */
+ if (field->vcol_info &&
+ (field->stored_in_db || field->vcol_info->is_in_partitioning_expr()))
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_VCOL;
+ }
+
+ /* Check if field was renamed */
+ if (my_strcasecmp(system_charset_info, field->field_name,
+ new_field->field_name))
+ {
+ field->flags|= FIELD_IS_RENAMED;
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_NAME;
+ rename_column_in_stat_tables(thd, table, field,
+ new_field->field_name);
+ }
+
+ /* Check that NULL behavior is same for old and new fields */
+ if ((new_field->flags & NOT_NULL_FLAG) !=
+ (uint) (field->flags & NOT_NULL_FLAG))
+ {
+ if (new_field->flags & NOT_NULL_FLAG)
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_NOT_NULLABLE;
+ else
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_NULLABLE;
+ }
+
+ /*
+ We do not detect changes to default values in this loop.
+ See comment above for more details.
+ */
+
+ /*
+ Detect changes in column order.
+ */
+ if (field->field_index != new_field_index)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_ORDER;
+
+ /* Detect changes in storage type of column */
+ if (new_field->field_storage_type() != field->field_storage_type())
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_STORAGE_TYPE;
+
+ /* Detect changes in column format of column */
+ if (new_field->column_format() != field->column_format())
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_COLUMN_FORMAT;
+
+ if (engine_options_differ(field->option_struct, new_field->option_struct,
+ table->file->ht->field_options))
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_OPTION;
+ ha_alter_info->create_info->fields_option_struct[f_ptr - table->field]=
+ new_field->option_struct;
+ }
+
+ }
+ else
+ {
+ /*
+ Field is not present in new version of table and therefore was dropped.
+ Corresponding storage engine flag should be already set.
+ */
+ DBUG_ASSERT(ha_alter_info->handler_flags & Alter_inplace_info::DROP_COLUMN);
+ field->flags|= FIELD_IS_DROPPED;
+ }
+ }
+
+ new_field_it.init(alter_info->create_list);
+ while ((new_field= new_field_it++))
+ {
+ if (! new_field->field)
+ {
+ /*
+ Field is not present in old version of table and therefore was added.
+ Again corresponding storage engine flag should be already set.
+ */
+ DBUG_ASSERT(ha_alter_info->handler_flags & Alter_inplace_info::ADD_COLUMN);
+
+ if (new_field->vcol_info &&
+ (new_field->stored_in_db || new_field->vcol_info->is_in_partitioning_expr()))
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_VCOL;
+ }
+ break;
+ }
+ }
+
+ /*
+ Go through keys and check if the original ones are compatible
+ with new table.
+ */
+ KEY *table_key;
+ KEY *table_key_end= table->key_info + table->s->keys;
+ KEY *new_key;
+ KEY *new_key_end=
+ ha_alter_info->key_info_buffer + ha_alter_info->key_count;
+
+ DBUG_PRINT("info", ("index count old: %d new: %d",
+ table->s->keys, ha_alter_info->key_count));
+
+ /*
+ Step through all keys of the old table and search matching new keys.
+ */
+ ha_alter_info->index_drop_count= 0;
+ ha_alter_info->index_add_count= 0;
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
+ {
+ /* Search a new key with the same name. */
+ for (new_key= ha_alter_info->key_info_buffer;
+ new_key < new_key_end;
+ new_key++)
+ {
+ if (! strcmp(table_key->name, new_key->name))
+ break;
+ }
+ if (new_key >= new_key_end)
+ {
+ /* Key not found. Add the key to the drop buffer. */
+ ha_alter_info->index_drop_buffer
+ [ha_alter_info->index_drop_count++]=
+ table_key;
+ DBUG_PRINT("info", ("index dropped: '%s'", table_key->name));
+ continue;
+ }
+
+ /* Check that the key types are compatible between old and new tables. */
+ if ((table_key->algorithm != new_key->algorithm) ||
+ ((table_key->flags & HA_KEYFLAG_MASK) !=
+ (new_key->flags & HA_KEYFLAG_MASK)) ||
+ (table_key->user_defined_key_parts !=
+ new_key->user_defined_key_parts))
+ goto index_changed;
+
+ if (engine_options_differ(table_key->option_struct, new_key->option_struct,
+ table->file->ht->index_options))
+ goto index_changed;
+
+ /*
+ Check that the key parts remain compatible between the old and
+ new tables.
+ */
+ end= table_key->key_part + table_key->user_defined_key_parts;
+ for (key_part= table_key->key_part, new_part= new_key->key_part;
+ key_part < end;
+ key_part++, new_part++)
+ {
+ /*
+ Key definition has changed if we are using a different field or
+ if the used key part length is different. It makes sense to
+ check lengths first as in case when fields differ it is likely
+ that lengths differ too and checking fields is more expensive
+ in general case.
+ */
+ if (key_part->length != new_part->length)
+ goto index_changed;
+
+ new_field= get_field_by_index(alter_info, new_part->fieldnr);
+
+ /*
+ For prefix keys KEY_PART_INFO::field points to cloned Field
+ object with adjusted length. So below we have to check field
+ indexes instead of simply comparing pointers to Field objects.
+ */
+ if (! new_field->field ||
+ new_field->field->field_index != key_part->fieldnr - 1)
+ goto index_changed;
+ }
+ continue;
+
+ index_changed:
+ /* Key modified. Add the key / key offset to both buffers. */
+ ha_alter_info->index_drop_buffer
+ [ha_alter_info->index_drop_count++]=
+ table_key;
+ ha_alter_info->index_add_buffer
+ [ha_alter_info->index_add_count++]=
+ new_key - ha_alter_info->key_info_buffer;
+ /* Mark all old fields which are used in newly created index. */
+ DBUG_PRINT("info", ("index changed: '%s'", table_key->name));
+ }
+ /*end of for (; table_key < table_key_end;) */
+
+ /*
+ Step through all keys of the new table and find matching old keys.
+ */
+ for (new_key= ha_alter_info->key_info_buffer;
+ new_key < new_key_end;
+ new_key++)
+ {
+ /* Search an old key with the same name. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
+ {
+ if (! strcmp(table_key->name, new_key->name))
+ break;
+ }
+ if (table_key >= table_key_end)
+ {
+ /* Key not found. Add the offset of the key to the add buffer. */
+ ha_alter_info->index_add_buffer
+ [ha_alter_info->index_add_count++]=
+ new_key - ha_alter_info->key_info_buffer;
+ DBUG_PRINT("info", ("index added: '%s'", new_key->name));
+ }
+ else
+ ha_alter_info->create_info->indexes_option_struct[table_key - table->key_info]=
+ new_key->option_struct;
+ }
+
+ /*
+ Sort index_add_buffer according to how key_info_buffer is sorted.
+ I.e. with primary keys first - see sort_keys().
+ */
+ my_qsort(ha_alter_info->index_add_buffer,
+ ha_alter_info->index_add_count,
+ sizeof(uint), (qsort_cmp) compare_uint);
+
+ /* Now let us calculate flags for storage engine API. */
+
+ /* Count all existing candidate keys. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
+ {
+ /*
+ Check if key is a candidate key, This key is either already primary key
+ or could be promoted to primary key if the original primary key is
+ dropped.
+ In MySQL one is allowed to create primary key with partial fields (i.e.
+ primary key which is not considered candidate). For simplicity we count
+ such key as a candidate key here.
+ */
+ if (((uint) (table_key - table->key_info) == table->s->primary_key) ||
+ is_candidate_key(table_key))
+ candidate_key_count++;
+ }
+
+ /* Figure out what kind of indexes we are dropping. */
+ KEY **dropped_key;
+ KEY **dropped_key_end= ha_alter_info->index_drop_buffer +
+ ha_alter_info->index_drop_count;
+
+ for (dropped_key= ha_alter_info->index_drop_buffer;
+ dropped_key < dropped_key_end; dropped_key++)
+ {
+ table_key= *dropped_key;
+
+ if (table_key->flags & HA_NOSAME)
+ {
+ /*
+ Unique key. Check for PRIMARY KEY. Also see comment about primary
+ and candidate keys above.
+ */
+ if ((uint) (table_key - table->key_info) == table->s->primary_key)
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_PK_INDEX;
+ candidate_key_count--;
+ }
+ else
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_UNIQUE_INDEX;
+ if (is_candidate_key(table_key))
+ candidate_key_count--;
+ }
+ }
+ else
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_INDEX;
+ }
+
+ /* Now figure out what kind of indexes we are adding. */
+ for (uint add_key_idx= 0; add_key_idx < ha_alter_info->index_add_count; add_key_idx++)
+ {
+ new_key= ha_alter_info->key_info_buffer + ha_alter_info->index_add_buffer[add_key_idx];
+
+ if (new_key->flags & HA_NOSAME)
+ {
+ bool is_pk= !my_strcasecmp(system_charset_info, new_key->name, primary_key_name);
+
+ if ((!(new_key->flags & HA_KEY_HAS_PART_KEY_SEG) &&
+ !(new_key->flags & HA_NULL_PART_KEY)) ||
+ is_pk)
+ {
+ /* Candidate key or primary key! */
+ if (candidate_key_count == 0 || is_pk)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PK_INDEX;
+ else
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_UNIQUE_INDEX;
+ candidate_key_count++;
+ }
+ else
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_UNIQUE_INDEX;
+ }
+ }
+ else
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_INDEX;
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Mark fields participating in newly added indexes in TABLE object which
+ corresponds to new version of altered table.
+
+ @param ha_alter_info Alter_inplace_info describing in-place ALTER.
+ @param altered_table TABLE object for new version of TABLE in which
+ fields should be marked.
+*/
+
+static void update_altered_table(const Alter_inplace_info &ha_alter_info,
+ TABLE *altered_table)
+{
+ uint field_idx, add_key_idx;
+ KEY *key;
+ KEY_PART_INFO *end, *key_part;
+
+ /*
+ Clear marker for all fields, as we are going to set it only
+ for fields which participate in new indexes.
+ */
+ for (field_idx= 0; field_idx < altered_table->s->fields; ++field_idx)
+ altered_table->field[field_idx]->flags&= ~FIELD_IN_ADD_INDEX;
+
+ /*
+ Go through array of newly added indexes and mark fields
+ participating in them.
+ */
+ for (add_key_idx= 0; add_key_idx < ha_alter_info.index_add_count;
+ add_key_idx++)
+ {
+ key= ha_alter_info.key_info_buffer +
+ ha_alter_info.index_add_buffer[add_key_idx];
+
+ end= key->key_part + key->user_defined_key_parts;
+ for (key_part= key->key_part; key_part < end; key_part++)
+ altered_table->field[key_part->fieldnr]->flags|= FIELD_IN_ADD_INDEX;
+ }
+}
+
+
+/**
+ Compare two tables to see if their metadata are compatible.
+ One table specified by a TABLE instance, the other using Alter_info
+ and HA_CREATE_INFO.
+
+ @param[in] table The first table.
+ @param[in] alter_info Alter options, fields and keys for the
+ second table.
+ @param[in] create_info Create options for the second table.
+ @param[out] metadata_equal Result of comparison.
+
+ @retval true error
+ @retval false success
+*/
+
+bool mysql_compare_tables(TABLE *table,
+ Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ bool *metadata_equal)
+{
+ DBUG_ENTER("mysql_compare_tables");
+
+ uint changes= IS_EQUAL_NO;
+ uint key_count;
+ List_iterator_fast<Create_field> tmp_new_field_it;
+ THD *thd= table->in_use;
+ *metadata_equal= false;
+
+ /*
+ Create a copy of alter_info.
+ To compare definitions, we need to "prepare" the definition - transform it
+ from parser output to a format that describes the table layout (all column
+ defaults are initialized, duplicate columns are removed). This is done by
+ mysql_prepare_create_table. Unfortunately, mysql_prepare_create_table
+ performs its transformations "in-place", that is, modifies the argument.
+ Since we would like to keep mysql_compare_tables() idempotent (not altering
+ any of the arguments) we create a copy of alter_info here and pass it to
+ mysql_prepare_create_table, then use the result to compare the tables, and
+ then destroy the copy.
+ */
+ Alter_info tmp_alter_info(*alter_info, thd->mem_root);
+ uint db_options= 0; /* not used */
+ KEY *key_info_buffer= NULL;
+
+ /* Create the prepared information. */
+ int create_table_mode= table->s->tmp_table == NO_TMP_TABLE ?
+ C_ORDINARY_CREATE : C_ALTER_TABLE;
+ if (mysql_prepare_create_table(thd, create_info, &tmp_alter_info,
+ &db_options, table->file, &key_info_buffer,
+ &key_count, create_table_mode))
+ DBUG_RETURN(1);
+
+ /* Some very basic checks. */
+ if (table->s->fields != alter_info->create_list.elements ||
+ table->s->db_type() != create_info->db_type ||
+ table->s->tmp_table ||
+ (table->s->row_type != create_info->row_type))
+ DBUG_RETURN(false);
+
+ /* Go through fields and check if they are compatible. */
+ tmp_new_field_it.init(tmp_alter_info.create_list);
+ for (Field **f_ptr= table->field; *f_ptr; f_ptr++)
+ {
+ Field *field= *f_ptr;
+ Create_field *tmp_new_field= tmp_new_field_it++;
+
+ /* Check that NULL behavior is the same. */
+ if ((tmp_new_field->flags & NOT_NULL_FLAG) !=
+ (uint) (field->flags & NOT_NULL_FLAG))
+ DBUG_RETURN(false);
+
+ /*
+ mysql_prepare_alter_table() clears HA_OPTION_PACK_RECORD bit when
+ preparing description of existing table. In ALTER TABLE it is later
+ updated to correct value by create_table_impl() call.
+ So to get correct value of this bit in this function we have to
+ mimic behavior of create_table_impl().
+ */
+ if (create_info->row_type == ROW_TYPE_DYNAMIC ||
+ create_info->row_type == ROW_TYPE_PAGE ||
+ (tmp_new_field->flags & BLOB_FLAG) ||
+ (tmp_new_field->sql_type == MYSQL_TYPE_VARCHAR &&
+ create_info->row_type != ROW_TYPE_FIXED))
+ create_info->table_options|= HA_OPTION_PACK_RECORD;
+
+ /* Check if field was renamed */
+ if (my_strcasecmp(system_charset_info,
+ field->field_name,
+ tmp_new_field->field_name))
+ DBUG_RETURN(false);
+
+ /* Evaluate changes bitmap and send to check_if_incompatible_data() */
+ uint field_changes= field->is_equal(tmp_new_field);
+ if (field_changes != IS_EQUAL_YES)
+ DBUG_RETURN(false);
+
+ changes|= field_changes;
+ }
+
+ /* Check if changes are compatible with current handler. */
+ if (table->file->check_if_incompatible_data(create_info, changes))
+ DBUG_RETURN(false);
+
+ /* Go through keys and check if they are compatible. */
+ KEY *table_key;
+ KEY *table_key_end= table->key_info + table->s->keys;
+ KEY *new_key;
+ KEY *new_key_end= key_info_buffer + key_count;
+
+ /* Step through all keys of the first table and search matching keys. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
+ {
+ /* Search a key with the same name. */
+ for (new_key= key_info_buffer; new_key < new_key_end; new_key++)
+ {
+ if (! strcmp(table_key->name, new_key->name))
+ break;
+ }
+ if (new_key >= new_key_end)
+ DBUG_RETURN(false);
+
+ /* Check that the key types are compatible. */
+ if ((table_key->algorithm != new_key->algorithm) ||
+ ((table_key->flags & HA_KEYFLAG_MASK) !=
+ (new_key->flags & HA_KEYFLAG_MASK)) ||
+ (table_key->user_defined_key_parts !=
+ new_key->user_defined_key_parts))
+ DBUG_RETURN(false);
+
+ /* Check that the key parts remain compatible. */
+ KEY_PART_INFO *table_part;
+ KEY_PART_INFO *table_part_end= table_key->key_part + table_key->user_defined_key_parts;
+ KEY_PART_INFO *new_part;
+ for (table_part= table_key->key_part, new_part= new_key->key_part;
+ table_part < table_part_end;
+ table_part++, new_part++)
+ {
+ /*
+ Key definition is different if we are using a different field or
+ if the used key part length is different. We know that the fields
+ are equal. Comparing field numbers is sufficient.
+ */
+ if ((table_part->length != new_part->length) ||
+ (table_part->fieldnr - 1 != new_part->fieldnr))
+ DBUG_RETURN(false);
+ }
+ }
+
+ /* Step through all keys of the second table and find matching keys. */
+ for (new_key= key_info_buffer; new_key < new_key_end; new_key++)
+ {
+ /* Search a key with the same name. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
+ {
+ if (! strcmp(table_key->name, new_key->name))
+ break;
+ }
+ if (table_key >= table_key_end)
+ DBUG_RETURN(false);
+ }
+
+ *metadata_equal= true; // Tables are compatible
+ DBUG_RETURN(false);
+}
+
+
+/*
+ Manages enabling/disabling of indexes for ALTER TABLE
+
+ SYNOPSIS
+ alter_table_manage_keys()
+ table Target table
+ indexes_were_disabled Whether the indexes of the from table
+ were disabled
+ keys_onoff ENABLE | DISABLE | LEAVE_AS_IS
+
+ RETURN VALUES
+ FALSE OK
+ TRUE Error
+*/
+
+static
+bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled,
+ Alter_info::enum_enable_or_disable keys_onoff)
+{
+ int error= 0;
+ DBUG_ENTER("alter_table_manage_keys");
+ DBUG_PRINT("enter", ("table=%p were_disabled=%d on_off=%d",
+ table, indexes_were_disabled, keys_onoff));
+
+ switch (keys_onoff) {
+ case Alter_info::ENABLE:
+ DEBUG_SYNC(table->in_use, "alter_table_enable_indexes");
+ error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+ break;
+ case Alter_info::LEAVE_AS_IS:
+ if (!indexes_were_disabled)
+ break;
+ /* fall-through: disabled indexes */
+ case Alter_info::DISABLE:
+ error= table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+ }
+
+ if (error == HA_ERR_WRONG_COMMAND)
+ {
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
+ table->file->table_type(),
+ table->s->db.str, table->s->table_name.str);
+ error= 0;
+ } else if (error)
+ table->file->print_error(error, MYF(0));
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Check if the pending ALTER TABLE operations support the in-place
+ algorithm based on restrictions in the SQL layer or given the
+ nature of the operations themselves. If in-place isn't supported,
+ it won't be necessary to check with the storage engine.
+
+ @param table The original TABLE.
+ @param create_info Information from the parsing phase about new
+ table properties.
+ @param alter_info Data related to detected changes.
+
+ @return false In-place is possible, check with storage engine.
+ @return true Incompatible operations, must use table copy.
+*/
+
+static bool is_inplace_alter_impossible(TABLE *table,
+ HA_CREATE_INFO *create_info,
+ const Alter_info *alter_info)
+{
+ DBUG_ENTER("is_inplace_alter_impossible");
+
+ /* At the moment we can't handle altering temporary tables without a copy. */
+ if (table->s->tmp_table)
+ DBUG_RETURN(true);
+
+ /*
+ For the ALTER TABLE tbl_name ORDER BY ... we always use copy
+ algorithm. In theory, this operation can be done in-place by some
+ engine, but since a) no current engine does this and b) our current
+ API lacks infrastructure for passing information about table ordering
+ to storage engine we simply always do copy now.
+
+ ENABLE/DISABLE KEYS is a MyISAM/Heap specific operation that is
+ not supported for in-place in combination with other operations.
+ Alone, it will be done by simple_rename_or_index_change().
+ */
+ if (alter_info->flags & (Alter_info::ALTER_ORDER |
+ Alter_info::ALTER_KEYS_ONOFF))
+ DBUG_RETURN(true);
+
+ /*
+ If the table engine is changed explicitly (using ENGINE clause)
+ or implicitly (e.g. when non-partitioned table becomes
+ partitioned) a regular alter table (copy) needs to be
+ performed.
+ */
+ if (create_info->db_type != table->s->db_type())
+ DBUG_RETURN(true);
+
+ /*
+ There was a bug prior to mysql-4.0.25. Number of null fields was
+ calculated incorrectly. As a result frm and data files gets out of
+ sync after fast alter table. There is no way to determine by which
+ mysql version (in 4.0 and 4.1 branches) table was created, thus we
+ disable fast alter table for all tables created by mysql versions
+ prior to 5.0 branch.
+ See BUG#6236.
+ */
+ if (!table->s->mysql_version)
+ DBUG_RETURN(true);
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Perform in-place alter table.
+
+ @param thd Thread handle.
+ @param table_list TABLE_LIST for the table to change.
+ @param table The original TABLE.
+ @param altered_table TABLE object for new version of the table.
+ @param ha_alter_info Structure describing ALTER TABLE to be carried
+ out and serving as a storage place for data
+ used during different phases.
+ @param inplace_supported Enum describing the locking requirements.
+ @param target_mdl_request Metadata request/lock on the target table name.
+ @param alter_ctx ALTER TABLE runtime context.
+
+ @retval true Error
+ @retval false Success
+
+ @note
+ If mysql_alter_table does not need to copy the table, it is
+ either an alter table where the storage engine does not
+ need to know about the change, only the frm will change,
+ or the storage engine supports performing the alter table
+ operation directly, in-place without mysql having to copy
+ the table.
+
+ @note This function frees the TABLE object associated with the new version of
+ the table and removes the .FRM file for it in case of both success and
+ failure.
+*/
+
+static bool mysql_inplace_alter_table(THD *thd,
+ TABLE_LIST *table_list,
+ TABLE *table,
+ TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info,
+ enum_alter_inplace_result inplace_supported,
+ MDL_request *target_mdl_request,
+ Alter_table_ctx *alter_ctx)
+{
+ Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
+ handlerton *db_type= table->s->db_type();
+ MDL_ticket *mdl_ticket= table->mdl_ticket;
+ HA_CREATE_INFO *create_info= ha_alter_info->create_info;
+ Alter_info *alter_info= ha_alter_info->alter_info;
+ bool reopen_tables= false;
+
+ DBUG_ENTER("mysql_inplace_alter_table");
+
+ /*
+ Upgrade to EXCLUSIVE lock if:
+ - This is requested by the storage engine
+ - Or the storage engine needs exclusive lock for just the prepare
+ phase
+ - Or requested by the user
+
+ Note that we handle situation when storage engine needs exclusive
+ lock for prepare phase under LOCK TABLES in the same way as when
+ exclusive lock is required for duration of the whole statement.
+ */
+ if (inplace_supported == HA_ALTER_INPLACE_EXCLUSIVE_LOCK ||
+ ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) &&
+ (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) ||
+ alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)
+ {
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto cleanup;
+ /*
+ Get rid of all TABLE instances belonging to this thread
+ except one to be used for in-place ALTER TABLE.
+
+ This is mostly needed to satisfy InnoDB assumptions/asserts.
+ */
+ close_all_tables_for_name(thd, table->s,
+ alter_ctx->is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME :
+ HA_EXTRA_NOT_USED,
+ table);
+ /*
+ If we are under LOCK TABLES we will need to reopen tables which we
+ just have closed in case of error.
+ */
+ reopen_tables= true;
+ }
+ else if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE)
+ {
+ /*
+ Storage engine has requested exclusive lock only for prepare phase
+ and we are not under LOCK TABLES.
+ Don't mark TABLE_SHARE as old in this case, as this won't allow opening
+ of table by other threads during main phase of in-place ALTER TABLE.
+ */
+ if (thd->mdl_context.upgrade_shared_lock(table->mdl_ticket, MDL_EXCLUSIVE,
+ thd->variables.lock_wait_timeout))
+ goto cleanup;
+
+ tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE,
+ table->s->db.str, table->s->table_name.str,
+ false);
+ }
+
+ /*
+ Upgrade to SHARED_NO_WRITE lock if:
+ - The storage engine needs writes blocked for the whole duration
+ - Or this is requested by the user
+ Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE.
+ */
+ if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK ||
+ alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED) &&
+ thd->mdl_context.upgrade_shared_lock(table->mdl_ticket,
+ MDL_SHARED_NO_WRITE,
+ thd->variables.lock_wait_timeout))
+ {
+ goto cleanup;
+ }
+
+ // It's now safe to take the table level lock.
+ if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0))
+ goto cleanup;
+
+ DEBUG_SYNC(thd, "alter_table_inplace_after_lock_upgrade");
+ THD_STAGE_INFO(thd, stage_alter_inplace_prepare);
+
+ switch (inplace_supported) {
+ case HA_ALTER_ERROR:
+ case HA_ALTER_INPLACE_NOT_SUPPORTED:
+ DBUG_ASSERT(0);
+ // fall through
+ case HA_ALTER_INPLACE_NO_LOCK:
+ case HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE:
+ switch (alter_info->requested_lock) {
+ case Alter_info::ALTER_TABLE_LOCK_DEFAULT:
+ case Alter_info::ALTER_TABLE_LOCK_NONE:
+ ha_alter_info->online= true;
+ break;
+ case Alter_info::ALTER_TABLE_LOCK_SHARED:
+ case Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE:
+ break;
+ }
+ break;
+ case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
+ case HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE:
+ case HA_ALTER_INPLACE_SHARED_LOCK:
+ break;
+ }
+
+ if (table->file->ha_prepare_inplace_alter_table(altered_table,
+ ha_alter_info))
+ {
+ goto rollback;
+ }
+
+ /*
+ Downgrade the lock if storage engine has told us that exclusive lock was
+ necessary only for prepare phase (unless we are not under LOCK TABLES) and
+ user has not explicitly requested exclusive lock.
+ */
+ if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) &&
+ !(thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
+ (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE))
+ {
+ /* If storage engine or user requested shared lock downgrade to SNW. */
+ if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED)
+ table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_WRITE);
+ else
+ {
+ DBUG_ASSERT(inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE);
+ table->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
+ }
+ }
+
+ DEBUG_SYNC(thd, "alter_table_inplace_after_lock_downgrade");
+ THD_STAGE_INFO(thd, stage_alter_inplace);
+
+ if (table->file->ha_inplace_alter_table(altered_table,
+ ha_alter_info))
+ {
+ goto rollback;
+ }
+
+ // Upgrade to EXCLUSIVE before commit.
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+ goto rollback;
+
+ /*
+ If we are killed after this point, we should ignore and continue.
+ We have mostly completed the operation at this point, there should
+ be no long waits left.
+ */
+
+ DBUG_EXECUTE_IF("alter_table_rollback_new_index", {
+ table->file->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info,
+ false);
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ goto cleanup;
+ });
+
+ DEBUG_SYNC(thd, "alter_table_inplace_before_commit");
+ THD_STAGE_INFO(thd, stage_alter_inplace_commit);
+
+ if (table->file->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info,
+ true))
+ {
+ goto rollback;
+ }
+
+ close_all_tables_for_name(thd, table->s,
+ alter_ctx->is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME :
+ HA_EXTRA_NOT_USED,
+ NULL);
+ table_list->table= table= NULL;
+ close_temporary_table(thd, altered_table, true, false);
+
+ /*
+ Replace the old .FRM with the new .FRM, but keep the old name for now.
+ Rename to the new name (if needed) will be handled separately below.
+ */
+ if (mysql_rename_table(db_type, alter_ctx->new_db, alter_ctx->tmp_name,
+ alter_ctx->db, alter_ctx->alias,
+ FN_FROM_IS_TMP | NO_HA_TABLE))
+ {
+ // Since changes were done in-place, we can't revert them.
+ (void) quick_rm_table(thd, db_type,
+ alter_ctx->new_db, alter_ctx->tmp_name,
+ FN_IS_TMP | NO_HA_TABLE);
+ DBUG_RETURN(true);
+ }
+
+ table_list->mdl_request.ticket= mdl_ticket;
+ if (open_table(thd, table_list, thd->mem_root, &ot_ctx))
+ DBUG_RETURN(true);
+
+ /*
+ Tell the handler that the changed frm is on disk and table
+ has been re-opened
+ */
+ table_list->table->file->ha_notify_table_changed();
+
+ /*
+ We might be going to reopen table down on the road, so we have to
+ restore state of the TABLE object which we used for obtaining of
+ handler object to make it usable for later reopening.
+ */
+ close_thread_table(thd, &thd->open_tables);
+ table_list->table= NULL;
+
+ // Rename altered table if requested.
+ if (alter_ctx->is_table_renamed())
+ {
+ // Remove TABLE and TABLE_SHARE for old name from TDC.
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
+ alter_ctx->db, alter_ctx->table_name, false);
+
+ if (mysql_rename_table(db_type, alter_ctx->db, alter_ctx->table_name,
+ alter_ctx->new_db, alter_ctx->new_alias, 0))
+ {
+ /*
+ If the rename fails we will still have a working table
+ with the old name, but with other changes applied.
+ */
+ DBUG_RETURN(true);
+ }
+ if (Table_triggers_list::change_table_name(thd,
+ alter_ctx->db,
+ alter_ctx->alias,
+ alter_ctx->table_name,
+ alter_ctx->new_db,
+ alter_ctx->new_alias))
+ {
+ /*
+ If the rename of trigger files fails, try to rename the table
+ back so we at least have matching table and trigger files.
+ */
+ (void) mysql_rename_table(db_type,
+ alter_ctx->new_db, alter_ctx->new_alias,
+ alter_ctx->db, alter_ctx->alias, NO_FK_CHECKS);
+ DBUG_RETURN(true);
+ }
+ rename_table_in_stat_tables(thd, alter_ctx->db,alter_ctx->alias,
+ alter_ctx->new_db, alter_ctx->new_alias);
+ }
+
+ DBUG_RETURN(false);
+
+ rollback:
+ table->file->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info,
+ false);
+ cleanup:
+ if (reopen_tables)
+ {
+ /* Close the only table instance which is still around. */
+ close_all_tables_for_name(thd, table->s,
+ alter_ctx->is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME :
+ HA_EXTRA_NOT_USED,
+ NULL);
+ if (thd->locked_tables_list.reopen_tables(thd))
+ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
+ /* QQ; do something about metadata locks ? */
+ }
+ close_temporary_table(thd, altered_table, true, false);
+ // Delete temporary .frm/.par
+ (void) quick_rm_table(thd, create_info->db_type, alter_ctx->new_db,
+ alter_ctx->tmp_name, FN_IS_TMP | NO_HA_TABLE);
+ DBUG_RETURN(true);
+}
+
+/**
+ maximum possible length for certain blob types.
+
+ @param[in] type Blob type (e.g. MYSQL_TYPE_TINY_BLOB)
+
+ @return
+ length
+*/
+
+static uint
+blob_length_by_type(enum_field_types type)
+{
+ switch (type)
+ {
+ case MYSQL_TYPE_TINY_BLOB:
+ return 255;
+ case MYSQL_TYPE_BLOB:
+ return 65535;
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ return 16777215;
+ case MYSQL_TYPE_LONG_BLOB:
+ return 4294967295U;
+ default:
+ DBUG_ASSERT(0); // we should never go here
+ return 0;
+ }
+}
+
+
+/**
+ Prepare column and key definitions for CREATE TABLE in ALTER TABLE.
+
+ This function transforms parse output of ALTER TABLE - lists of
+ columns and keys to add, drop or modify into, essentially,
+ CREATE TABLE definition - a list of columns and keys of the new
+ table. While doing so, it also performs some (bug not all)
+ semantic checks.
+
+ This function is invoked when we know that we're going to
+ perform ALTER TABLE via a temporary table -- i.e. in-place ALTER TABLE
+ is not possible, perhaps because the ALTER statement contains
+ instructions that require change in table data, not only in
+ table definition or indexes.
+
+ @param[in,out] thd thread handle. Used as a memory pool
+ and source of environment information.
+ @param[in] table the source table, open and locked
+ Used as an interface to the storage engine
+ to acquire additional information about
+ the original table.
+ @param[in,out] create_info A blob with CREATE/ALTER TABLE
+ parameters
+ @param[in,out] alter_info Another blob with ALTER/CREATE parameters.
+ Originally create_info was used only in
+ CREATE TABLE and alter_info only in ALTER TABLE.
+ But since ALTER might end-up doing CREATE,
+ this distinction is gone and we just carry
+ around two structures.
+ @param[in,out] alter_ctx Runtime context for ALTER TABLE.
+
+ @return
+ Fills various create_info members based on information retrieved
+ from the storage engine.
+ Sets create_info->varchar if the table has a VARCHAR column.
+ Prepares alter_info->create_list and alter_info->key_list with
+ columns and keys of the new table.
+
+ @retval TRUE error, out of memory or a semantical error in ALTER
+ TABLE instructions
+ @retval FALSE success
+*/
+
+bool
+mysql_prepare_alter_table(THD *thd, TABLE *table,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ Alter_table_ctx *alter_ctx)
+{
+ /* New column definitions are added here */
+ List<Create_field> new_create_list;
+ /* New key definitions are added here */
+ List<Key> new_key_list;
+ List_iterator<Alter_drop> drop_it(alter_info->drop_list);
+ List_iterator<Create_field> def_it(alter_info->create_list);
+ List_iterator<Alter_column> alter_it(alter_info->alter_list);
+ List_iterator<Key> key_it(alter_info->key_list);
+ List_iterator<Create_field> find_it(new_create_list);
+ List_iterator<Create_field> field_it(new_create_list);
+ List<Key_part_spec> key_parts;
+ uint db_create_options= (table->s->db_create_options
+ & ~(HA_OPTION_PACK_RECORD));
+ uint used_fields;
+ KEY *key_info=table->key_info;
+ bool rc= TRUE;
+ bool modified_primary_key= FALSE;
+ Create_field *def;
+ Field **f_ptr,*field;
+ DBUG_ENTER("mysql_prepare_alter_table");
+
+ /*
+ Merge incompatible changes flag in case of upgrade of a table from an
+ old MariaDB or MySQL version. This ensures that we don't try to do an
+ online alter table if field packing or character set changes are required.
+ */
+ create_info->used_fields|= table->s->incompatible_version;
+ used_fields= create_info->used_fields;
+
+ create_info->varchar= FALSE;
+ /* Let new create options override the old ones */
+ if (!(used_fields & HA_CREATE_USED_MIN_ROWS))
+ create_info->min_rows= table->s->min_rows;
+ if (!(used_fields & HA_CREATE_USED_MAX_ROWS))
+ create_info->max_rows= table->s->max_rows;
+ if (!(used_fields & HA_CREATE_USED_AVG_ROW_LENGTH))
+ create_info->avg_row_length= table->s->avg_row_length;
+ if (!(used_fields & HA_CREATE_USED_DEFAULT_CHARSET))
+ create_info->default_table_charset= table->s->table_charset;
+ if (!(used_fields & HA_CREATE_USED_AUTO) && table->found_next_number_field)
+ {
+ /* Table has an autoincrement, copy value to new table */
+ table->file->info(HA_STATUS_AUTO);
+ create_info->auto_increment_value= table->file->stats.auto_increment_value;
+ }
+
+ if (!(used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE))
+ create_info->key_block_size= table->s->key_block_size;
+
+ if (!(used_fields & HA_CREATE_USED_STATS_SAMPLE_PAGES))
+ create_info->stats_sample_pages= table->s->stats_sample_pages;
+
+ if (!(used_fields & HA_CREATE_USED_STATS_AUTO_RECALC))
+ create_info->stats_auto_recalc= table->s->stats_auto_recalc;
+
+ if (!(used_fields & HA_CREATE_USED_TRANSACTIONAL))
+ create_info->transactional= table->s->transactional;
+
+ if (!(used_fields & HA_CREATE_USED_CONNECTION))
+ create_info->connect_string= table->s->connect_string;
+
+ restore_record(table, s->default_values); // Empty record for DEFAULT
+
+ if ((create_info->fields_option_struct= (ha_field_option_struct**)
+ thd->calloc(sizeof(void*) * table->s->fields)) == NULL ||
+ (create_info->indexes_option_struct= (ha_index_option_struct**)
+ thd->calloc(sizeof(void*) * table->s->keys)) == NULL)
+ DBUG_RETURN(1);
+
+ create_info->option_list= merge_engine_table_options(table->s->option_list,
+ create_info->option_list, thd->mem_root);
+
+ /*
+ First collect all fields from table which isn't in drop_list
+ */
+ for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
+ {
+ Alter_drop *drop;
+ if (field->type() == MYSQL_TYPE_VARCHAR)
+ create_info->varchar= TRUE;
+ /* Check if field should be dropped */
+ drop_it.rewind();
+ while ((drop=drop_it++))
+ {
+ if (drop->type == Alter_drop::COLUMN &&
+ !my_strcasecmp(system_charset_info,field->field_name, drop->name))
+ {
+ /* Reset auto_increment value if it was dropped */
+ if (MTYP_TYPENR(field->unireg_check) == Field::NEXT_NUMBER &&
+ !(used_fields & HA_CREATE_USED_AUTO))
+ {
+ create_info->auto_increment_value=0;
+ create_info->used_fields|=HA_CREATE_USED_AUTO;
+ }
+ break;
+ }
+ }
+ if (drop)
+ {
+ if (table->s->tmp_table == NO_TMP_TABLE)
+ (void) delete_statistics_for_column(thd, table, field);
+ drop_it.remove();
+ continue;
+ }
+ /* Check if field is changed */
+ def_it.rewind();
+ while ((def=def_it++))
+ {
+ if (def->change &&
+ !my_strcasecmp(system_charset_info,field->field_name, def->change))
+ break;
+ }
+ if (def)
+ { // Field is changed
+ def->field=field;
+ /*
+ Add column being updated to the list of new columns.
+ Note that columns with AFTER clauses are added to the end
+ of the list for now. Their positions will be corrected later.
+ */
+ new_create_list.push_back(def);
+ if (field->stored_in_db != def->stored_in_db)
+ {
+ my_error(ER_UNSUPPORTED_ACTION_ON_VIRTUAL_COLUMN, MYF(0));
+ goto err;
+ }
+ if (!def->after)
+ {
+ /*
+ If this ALTER TABLE doesn't have an AFTER clause for the modified
+ column then remove this column from the list of columns to be
+ processed. So later we can iterate over the columns remaining
+ in this list and process modified columns with AFTER clause or
+ add new columns.
+ */
+ def_it.remove();
+ }
+ }
+ else
+ {
+ /*
+ This field was not dropped and not changed, add it to the list
+ for the new table.
+ */
+ def= new Create_field(field, field);
+ new_create_list.push_back(def);
+ alter_it.rewind(); // Change default if ALTER
+ Alter_column *alter;
+ while ((alter=alter_it++))
+ {
+ if (!my_strcasecmp(system_charset_info,field->field_name, alter->name))
+ break;
+ }
+ if (alter)
+ {
+ if (def->sql_type == MYSQL_TYPE_BLOB)
+ {
+ my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0), def->change);
+ goto err;
+ }
+ if ((def->def=alter->def)) // Use new default
+ def->flags&= ~NO_DEFAULT_VALUE_FLAG;
+ else
+ def->flags|= NO_DEFAULT_VALUE_FLAG;
+ alter_it.remove();
+ }
+ }
+ }
+ def_it.rewind();
+ while ((def=def_it++)) // Add new columns
+ {
+ if (def->change && ! def->field)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change,
+ table->s->table_name.str);
+ goto err;
+ }
+ /*
+ Check that the DATE/DATETIME not null field we are going to add is
+ either has a default value or the '0000-00-00' is allowed by the
+ set sql mode.
+ If the '0000-00-00' value isn't allowed then raise the error_if_not_empty
+ flag to allow ALTER TABLE only if the table to be altered is empty.
+ */
+ if ((def->sql_type == MYSQL_TYPE_DATE ||
+ def->sql_type == MYSQL_TYPE_NEWDATE ||
+ def->sql_type == MYSQL_TYPE_DATETIME ||
+ def->sql_type == MYSQL_TYPE_DATETIME2) &&
+ !alter_ctx->datetime_field &&
+ !(~def->flags & (NO_DEFAULT_VALUE_FLAG | NOT_NULL_FLAG)) &&
+ thd->variables.sql_mode & MODE_NO_ZERO_DATE)
+ {
+ alter_ctx->datetime_field= def;
+ alter_ctx->error_if_not_empty= TRUE;
+ }
+ if (!def->after)
+ new_create_list.push_back(def);
+ else
+ {
+ Create_field *find;
+ if (def->change)
+ {
+ find_it.rewind();
+ /*
+ For columns being modified with AFTER clause we should first remove
+ these columns from the list and then add them back at their correct
+ positions.
+ */
+ while ((find=find_it++))
+ {
+ /*
+ Create_fields representing changed columns are added directly
+ from Alter_info::create_list to new_create_list. We can therefore
+ safely use pointer equality rather than name matching here.
+ This prevents removing the wrong column in case of column rename.
+ */
+ if (find == def)
+ {
+ find_it.remove();
+ break;
+ }
+ }
+ }
+ if (def->after == first_keyword)
+ new_create_list.push_front(def);
+ else
+ {
+ find_it.rewind();
+ while ((find=find_it++))
+ {
+ if (!my_strcasecmp(system_charset_info, def->after, find->field_name))
+ break;
+ }
+ if (!find)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table->s->table_name.str);
+ goto err;
+ }
+ find_it.after(def); // Put column after this
+ }
+ }
+ }
+ if (alter_info->alter_list.elements)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0),
+ alter_info->alter_list.head()->name, table->s->table_name.str);
+ goto err;
+ }
+ if (!new_create_list.elements)
+ {
+ my_message(ER_CANT_REMOVE_ALL_FIELDS, ER(ER_CANT_REMOVE_ALL_FIELDS),
+ MYF(0));
+ goto err;
+ }
+
+ /*
+ Collect all keys which isn't in drop list. Add only those
+ for which some fields exists.
+ */
+
+ for (uint i=0 ; i < table->s->keys ; i++,key_info++)
+ {
+ char *key_name= key_info->name;
+ Alter_drop *drop;
+ drop_it.rewind();
+ while ((drop=drop_it++))
+ {
+ if (drop->type == Alter_drop::KEY &&
+ !my_strcasecmp(system_charset_info,key_name, drop->name))
+ break;
+ }
+ if (drop)
+ {
+ if (table->s->tmp_table == NO_TMP_TABLE)
+ {
+ (void) delete_statistics_for_index(thd, table, key_info, FALSE);
+ if (i == table->s->primary_key)
+ {
+ KEY *tab_key_info= table->key_info;
+ for (uint j=0; j < table->s->keys; j++, tab_key_info++)
+ {
+ if (tab_key_info->user_defined_key_parts !=
+ tab_key_info->ext_key_parts)
+ (void) delete_statistics_for_index(thd, table, tab_key_info,
+ TRUE);
+ }
+ }
+ }
+ drop_it.remove();
+ continue;
+ }
+
+ KEY_PART_INFO *key_part= key_info->key_part;
+ key_parts.empty();
+ bool delete_index_stat= FALSE;
+ for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
+ {
+ if (!key_part->field)
+ continue; // Wrong field (from UNIREG)
+ const char *key_part_name=key_part->field->field_name;
+ Create_field *cfield;
+ uint key_part_length;
+
+ field_it.rewind();
+ while ((cfield=field_it++))
+ {
+ if (cfield->change)
+ {
+ if (!my_strcasecmp(system_charset_info, key_part_name,
+ cfield->change))
+ break;
+ }
+ else if (!my_strcasecmp(system_charset_info,
+ key_part_name, cfield->field_name))
+ break;
+ }
+ if (!cfield)
+ {
+ if (table->s->primary_key == i)
+ modified_primary_key= TRUE;
+ delete_index_stat= TRUE;
+ continue; // Field is removed
+ }
+ key_part_length= key_part->length;
+ if (cfield->field) // Not new field
+ {
+ /*
+ If the field can't have only a part used in a key according to its
+ new type, or should not be used partially according to its
+ previous type, or the field length is less than the key part
+ length, unset the key part length.
+
+ We also unset the key part length if it is the same as the
+ old field's length, so the whole new field will be used.
+
+ BLOBs may have cfield->length == 0, which is why we test it before
+ checking whether cfield->length < key_part_length (in chars).
+
+ In case of TEXTs we check the data type maximum length *in bytes*
+ to key part length measured *in characters* (i.e. key_part_length
+ devided to mbmaxlen). This is because it's OK to have:
+ CREATE TABLE t1 (a tinytext, key(a(254)) character set utf8);
+ In case of this example:
+ - data type maximum length is 255.
+ - key_part_length is 1016 (=254*4, where 4 is mbmaxlen)
+ */
+ if (!Field::type_can_have_key_part(cfield->field->type()) ||
+ !Field::type_can_have_key_part(cfield->sql_type) ||
+ /* spatial keys can't have sub-key length */
+ (key_info->flags & HA_SPATIAL) ||
+ (cfield->field->field_length == key_part_length &&
+ !f_is_blob(key_part->key_type)) ||
+ (cfield->length && (((cfield->sql_type >= MYSQL_TYPE_TINY_BLOB &&
+ cfield->sql_type <= MYSQL_TYPE_BLOB) ?
+ blob_length_by_type(cfield->sql_type) :
+ cfield->length) <
+ key_part_length / key_part->field->charset()->mbmaxlen)))
+ key_part_length= 0; // Use whole field
+ }
+ key_part_length /= key_part->field->charset()->mbmaxlen;
+ key_parts.push_back(new Key_part_spec(cfield->field_name,
+ strlen(cfield->field_name),
+ key_part_length));
+ }
+ if (table->s->tmp_table == NO_TMP_TABLE)
+ {
+ if (delete_index_stat)
+ (void) delete_statistics_for_index(thd, table, key_info, FALSE);
+ else if (modified_primary_key &&
+ key_info->user_defined_key_parts != key_info->ext_key_parts)
+ (void) delete_statistics_for_index(thd, table, key_info, TRUE);
+ }
+
+ if (key_parts.elements)
+ {
+ KEY_CREATE_INFO key_create_info;
+ Key *key;
+ enum Key::Keytype key_type;
+ bzero((char*) &key_create_info, sizeof(key_create_info));
+
+ key_create_info.algorithm= key_info->algorithm;
+ if (key_info->flags & HA_USES_BLOCK_SIZE)
+ key_create_info.block_size= key_info->block_size;
+ if (key_info->flags & HA_USES_PARSER)
+ key_create_info.parser_name= *plugin_name(key_info->parser);
+ if (key_info->flags & HA_USES_COMMENT)
+ key_create_info.comment= key_info->comment;
+
+ /*
+ We're refreshing an already existing index. Since the index is not
+ modified, there is no need to check for duplicate indexes again.
+ */
+ key_create_info.check_for_duplicate_indexes= false;
+
+ if (key_info->flags & HA_SPATIAL)
+ key_type= Key::SPATIAL;
+ else if (key_info->flags & HA_NOSAME)
+ {
+ if (! my_strcasecmp(system_charset_info, key_name, primary_key_name))
+ key_type= Key::PRIMARY;
+ else
+ key_type= Key::UNIQUE;
+ }
+ else if (key_info->flags & HA_FULLTEXT)
+ key_type= Key::FULLTEXT;
+ else
+ key_type= Key::MULTIPLE;
+
+ key= new Key(key_type, key_name, strlen(key_name),
+ &key_create_info,
+ MY_TEST(key_info->flags & HA_GENERATED_KEY),
+ key_parts, key_info->option_list, FALSE);
+ new_key_list.push_back(key);
+ }
+ }
+ {
+ Key *key;
+ while ((key=key_it++)) // Add new keys
+ {
+ if (key->type == Key::FOREIGN_KEY &&
+ ((Foreign_key *)key)->validate(new_create_list))
+ goto err;
+ new_key_list.push_back(key);
+ if (key->name.str &&
+ !my_strcasecmp(system_charset_info, key->name.str, primary_key_name))
+ {
+ my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name.str);
+ goto err;
+ }
+ }
+ }
+
+ if (alter_info->drop_list.elements)
+ {
+ Alter_drop *drop;
+ drop_it.rewind();
+ while ((drop=drop_it++)) {
+ switch (drop->type) {
+ case Alter_drop::KEY:
+ case Alter_drop::COLUMN:
+ my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
+ alter_info->drop_list.head()->name);
+ goto err;
+ case Alter_drop::FOREIGN_KEY:
+ // Leave the DROP FOREIGN KEY names in the alter_info->drop_list.
+ break;
+ }
+ }
+ }
+ if (alter_info->alter_list.elements)
+ {
+ my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
+ alter_info->alter_list.head()->name);
+ goto err;
+ }
+
+ if (!create_info->comment.str)
+ {
+ create_info->comment.str= table->s->comment.str;
+ create_info->comment.length= table->s->comment.length;
+ }
+
+ table->file->update_create_info(create_info);
+ if ((create_info->table_options &
+ (HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS)) ||
+ (used_fields & HA_CREATE_USED_PACK_KEYS))
+ db_create_options&= ~(HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS);
+ if ((create_info->table_options &
+ (HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT)) ||
+ (used_fields & HA_CREATE_USED_STATS_PERSISTENT))
+ db_create_options&= ~(HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT);
+
+ if (create_info->table_options &
+ (HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM))
+ db_create_options&= ~(HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM);
+ if (create_info->table_options &
+ (HA_OPTION_DELAY_KEY_WRITE | HA_OPTION_NO_DELAY_KEY_WRITE))
+ db_create_options&= ~(HA_OPTION_DELAY_KEY_WRITE |
+ HA_OPTION_NO_DELAY_KEY_WRITE);
+ create_info->table_options|= db_create_options;
+
+ if (table->s->tmp_table)
+ create_info->options|=HA_LEX_CREATE_TMP_TABLE;
+
+ rc= FALSE;
+ alter_info->create_list.swap(new_create_list);
+ alter_info->key_list.swap(new_key_list);
+err:
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ Get Create_field object for newly created table by its name
+ in the old version of table.
+
+ @param alter_info Alter_info describing newly created table.
+ @param old_name Name of field in old table.
+
+ @returns Pointer to Create_field object, NULL - if field is
+ not present in new version of table.
+*/
+
+static Create_field *get_field_by_old_name(Alter_info *alter_info,
+ const char *old_name)
+{
+ List_iterator_fast<Create_field> new_field_it(alter_info->create_list);
+ Create_field *new_field;
+
+ while ((new_field= new_field_it++))
+ {
+ if (new_field->field &&
+ (my_strcasecmp(system_charset_info,
+ new_field->field->field_name,
+ old_name) == 0))
+ break;
+ }
+ return new_field;
+}
+
+
+/** Type of change to foreign key column, */
+
+enum fk_column_change_type
+{
+ FK_COLUMN_NO_CHANGE, FK_COLUMN_DATA_CHANGE,
+ FK_COLUMN_RENAMED, FK_COLUMN_DROPPED
+};
+
+/**
+ Check that ALTER TABLE's changes on columns of a foreign key are allowed.
+
+ @param[in] thd Thread context.
+ @param[in] alter_info Alter_info describing changes to be done
+ by ALTER TABLE.
+ @param[in] fk_columns List of columns of the foreign key to check.
+ @param[out] bad_column_name Name of field on which ALTER TABLE tries to
+ do prohibited operation.
+
+ @note This function takes into account value of @@foreign_key_checks
+ setting.
+
+ @retval FK_COLUMN_NO_CHANGE No significant changes are to be done on
+ foreign key columns.
+ @retval FK_COLUMN_DATA_CHANGE ALTER TABLE might result in value
+ change in foreign key column (and
+ foreign_key_checks is on).
+ @retval FK_COLUMN_RENAMED Foreign key column is renamed.
+ @retval FK_COLUMN_DROPPED Foreign key column is dropped.
+*/
+
+static enum fk_column_change_type
+fk_check_column_changes(THD *thd, Alter_info *alter_info,
+ List<LEX_STRING> &fk_columns,
+ const char **bad_column_name)
+{
+ List_iterator_fast<LEX_STRING> column_it(fk_columns);
+ LEX_STRING *column;
+
+ *bad_column_name= NULL;
+
+ while ((column= column_it++))
+ {
+ Create_field *new_field= get_field_by_old_name(alter_info, column->str);
+
+ if (new_field)
+ {
+ Field *old_field= new_field->field;
+
+ if (my_strcasecmp(system_charset_info, old_field->field_name,
+ new_field->field_name))
+ {
+ /*
+ Copy algorithm doesn't support proper renaming of columns in
+ the foreign key yet. At the moment we lack API which will tell
+ SE that foreign keys should be updated to use new name of column
+ like it happens in case of in-place algorithm.
+ */
+ *bad_column_name= column->str;
+ return FK_COLUMN_RENAMED;
+ }
+
+ if ((old_field->is_equal(new_field) == IS_EQUAL_NO) ||
+ ((new_field->flags & NOT_NULL_FLAG) &&
+ !(old_field->flags & NOT_NULL_FLAG)))
+ {
+ if (!(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
+ {
+ /*
+ Column in a FK has changed significantly. Unless
+ foreign_key_checks are off we prohibit this since this
+ means values in this column might be changed by ALTER
+ and thus referential integrity might be broken,
+ */
+ *bad_column_name= column->str;
+ return FK_COLUMN_DATA_CHANGE;
+ }
+ }
+ }
+ else
+ {
+ /*
+ Column in FK was dropped. Most likely this will break
+ integrity constraints of InnoDB data-dictionary (and thus
+ InnoDB will emit an error), so we prohibit this right away
+ even if foreign_key_checks are off.
+ This also includes a rare case when another field replaces
+ field being dropped since it is easy to break referential
+ integrity in this case.
+ */
+ *bad_column_name= column->str;
+ return FK_COLUMN_DROPPED;
+ }
+ }
+
+ return FK_COLUMN_NO_CHANGE;
+}
+
+
+/**
+ Check if ALTER TABLE we are about to execute using COPY algorithm
+ is not supported as it might break referential integrity.
+
+ @note If foreign_key_checks is disabled (=0), we allow to break
+ referential integrity. But we still disallow some operations
+ like dropping or renaming columns in foreign key since they
+ are likely to break consistency of InnoDB data-dictionary
+ and thus will end-up in error anyway.
+
+ @param[in] thd Thread context.
+ @param[in] table Table to be altered.
+ @param[in] alter_info Lists of fields, keys to be changed, added
+ or dropped.
+ @param[out] alter_ctx ALTER TABLE runtime context.
+ Alter_table_ctx::fk_error_if_delete flag
+ is set if deletion during alter can break
+ foreign key integrity.
+
+ @retval false Success.
+ @retval true Error, ALTER - tries to do change which is not compatible
+ with foreign key definitions on the table.
+*/
+
+static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
+ Alter_info *alter_info,
+ Alter_table_ctx *alter_ctx)
+{
+ List <FOREIGN_KEY_INFO> fk_parent_key_list;
+ List <FOREIGN_KEY_INFO> fk_child_key_list;
+ FOREIGN_KEY_INFO *f_key;
+
+ DBUG_ENTER("fk_prepare_copy_alter_table");
+
+ table->file->get_parent_foreign_key_list(thd, &fk_parent_key_list);
+
+ /* OOM when building list. */
+ if (thd->is_error())
+ DBUG_RETURN(true);
+
+ /*
+ Remove from the list all foreign keys in which table participates as
+ parent which are to be dropped by this ALTER TABLE. This is possible
+ when a foreign key has the same table as child and parent.
+ */
+ List_iterator<FOREIGN_KEY_INFO> fk_parent_key_it(fk_parent_key_list);
+
+ while ((f_key= fk_parent_key_it++))
+ {
+ Alter_drop *drop;
+ List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list);
+
+ while ((drop= drop_it++))
+ {
+ /*
+ InnoDB treats foreign key names in case-insensitive fashion.
+ So we do it here too. For database and table name type of
+ comparison used depends on lower-case-table-names setting.
+ For l_c_t_n = 0 we use case-sensitive comparison, for
+ l_c_t_n > 0 modes case-insensitive comparison is used.
+ */
+ if ((drop->type == Alter_drop::FOREIGN_KEY) &&
+ (my_strcasecmp(system_charset_info, f_key->foreign_id->str,
+ drop->name) == 0) &&
+ (my_strcasecmp(table_alias_charset, f_key->foreign_db->str,
+ table->s->db.str) == 0) &&
+ (my_strcasecmp(table_alias_charset, f_key->foreign_table->str,
+ table->s->table_name.str) == 0))
+ fk_parent_key_it.remove();
+ }
+ }
+
+ /*
+ If there are FKs in which this table is parent which were not
+ dropped we need to prevent ALTER deleting rows from the table,
+ as it might break referential integrity. OTOH it is OK to do
+ so if foreign_key_checks are disabled.
+ */
+ if (!fk_parent_key_list.is_empty() &&
+ !(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
+ alter_ctx->set_fk_error_if_delete_row(fk_parent_key_list.head());
+
+ fk_parent_key_it.rewind();
+ while ((f_key= fk_parent_key_it++))
+ {
+ enum fk_column_change_type changes;
+ const char *bad_column_name;
+
+ changes= fk_check_column_changes(thd, alter_info,
+ f_key->referenced_fields,
+ &bad_column_name);
+
+ switch(changes)
+ {
+ case FK_COLUMN_NO_CHANGE:
+ /* No significant changes. We can proceed with ALTER! */
+ break;
+ case FK_COLUMN_DATA_CHANGE:
+ {
+ char buff[NAME_LEN*2+2];
+ strxnmov(buff, sizeof(buff)-1, f_key->foreign_db->str, ".",
+ f_key->foreign_table->str, NullS);
+ my_error(ER_FK_COLUMN_CANNOT_CHANGE_CHILD, MYF(0), bad_column_name,
+ f_key->foreign_id->str, buff);
+ DBUG_RETURN(true);
+ }
+ case FK_COLUMN_RENAMED:
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "ALGORITHM=COPY",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME),
+ "ALGORITHM=INPLACE");
+ DBUG_RETURN(true);
+ case FK_COLUMN_DROPPED:
+ {
+ char buff[NAME_LEN*2+2];
+ strxnmov(buff, sizeof(buff)-1, f_key->foreign_db->str, ".",
+ f_key->foreign_table->str, NullS);
+ my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, MYF(0), bad_column_name,
+ f_key->foreign_id->str, buff);
+ DBUG_RETURN(true);
+ }
+ default:
+ DBUG_ASSERT(0);
+ }
+ }
+
+ table->file->get_foreign_key_list(thd, &fk_child_key_list);
+
+ /* OOM when building list. */
+ if (thd->is_error())
+ DBUG_RETURN(true);
+
+ /*
+ Remove from the list all foreign keys which are to be dropped
+ by this ALTER TABLE.
+ */
+ List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
+
+ while ((f_key= fk_key_it++))
+ {
+ Alter_drop *drop;
+ List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list);
+
+ while ((drop= drop_it++))
+ {
+ /* Names of foreign keys in InnoDB are case-insensitive. */
+ if ((drop->type == Alter_drop::FOREIGN_KEY) &&
+ (my_strcasecmp(system_charset_info, f_key->foreign_id->str,
+ drop->name) == 0))
+ fk_key_it.remove();
+ }
+ }
+
+ fk_key_it.rewind();
+ while ((f_key= fk_key_it++))
+ {
+ enum fk_column_change_type changes;
+ const char *bad_column_name;
+
+ changes= fk_check_column_changes(thd, alter_info,
+ f_key->foreign_fields,
+ &bad_column_name);
+
+ switch(changes)
+ {
+ case FK_COLUMN_NO_CHANGE:
+ /* No significant changes. We can proceed with ALTER! */
+ break;
+ case FK_COLUMN_DATA_CHANGE:
+ my_error(ER_FK_COLUMN_CANNOT_CHANGE, MYF(0), bad_column_name,
+ f_key->foreign_id->str);
+ DBUG_RETURN(true);
+ case FK_COLUMN_RENAMED:
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "ALGORITHM=COPY",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME),
+ "ALGORITHM=INPLACE");
+ DBUG_RETURN(true);
+ case FK_COLUMN_DROPPED:
+ my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), bad_column_name,
+ f_key->foreign_id->str);
+ DBUG_RETURN(true);
+ default:
+ DBUG_ASSERT(0);
+ }
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Rename table and/or turn indexes on/off without touching .FRM
+
+ @param thd Thread handler
+ @param table_list TABLE_LIST for the table to change
+ @param keys_onoff ENABLE or DISABLE KEYS?
+ @param alter_ctx ALTER TABLE runtime context.
+
+ @return Operation status
+ @retval false Success
+ @retval true Failure
+*/
+
+static bool
+simple_rename_or_index_change(THD *thd, TABLE_LIST *table_list,
+ Alter_info::enum_enable_or_disable keys_onoff,
+ Alter_table_ctx *alter_ctx)
+{
+ TABLE *table= table_list->table;
+ MDL_ticket *mdl_ticket= table->mdl_ticket;
+ int error= 0;
+ enum ha_extra_function extra_func= thd->locked_tables_mode
+ ? HA_EXTRA_NOT_USED
+ : HA_EXTRA_FORCE_REOPEN;
+ DBUG_ENTER("simple_rename_or_index_change");
+
+ if (keys_onoff != Alter_info::LEAVE_AS_IS)
+ {
+ if (wait_while_table_is_used(thd, table, extra_func))
+ DBUG_RETURN(true);
+
+ // It's now safe to take the table level lock.
+ if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0))
+ DBUG_RETURN(true);
+
+ error= alter_table_manage_keys(table,
+ table->file->indexes_are_disabled(),
+ keys_onoff);
+ }
+
+ if (!error && alter_ctx->is_table_renamed())
+ {
+ THD_STAGE_INFO(thd, stage_rename);
+ handlerton *old_db_type= table->s->db_type();
+ /*
+ Then do a 'simple' rename of the table. First we need to close all
+ instances of 'source' table.
+ Note that if wait_while_table_is_used() returns error here (i.e. if
+ this thread was killed) then it must be that previous step of
+ simple rename did nothing and therefore we can safely return
+ without additional clean-up.
+ */
+ if (wait_while_table_is_used(thd, table, extra_func))
+ DBUG_RETURN(true);
+ close_all_tables_for_name(thd, table->s, HA_EXTRA_PREPARE_FOR_RENAME, NULL);
+
+ LEX_STRING old_db_name= { alter_ctx->db, strlen(alter_ctx->db) };
+ LEX_STRING old_table_name=
+ { alter_ctx->table_name, strlen(alter_ctx->table_name) };
+ LEX_STRING new_db_name= { alter_ctx->new_db, strlen(alter_ctx->new_db) };
+ LEX_STRING new_table_name=
+ { alter_ctx->new_alias, strlen(alter_ctx->new_alias) };
+ (void) rename_table_in_stat_tables(thd, &old_db_name, &old_table_name,
+ &new_db_name, &new_table_name);
+
+ if (mysql_rename_table(old_db_type, alter_ctx->db, alter_ctx->table_name,
+ alter_ctx->new_db, alter_ctx->new_alias, 0))
+ error= -1;
+ else if (Table_triggers_list::change_table_name(thd,
+ alter_ctx->db,
+ alter_ctx->alias,
+ alter_ctx->table_name,
+ alter_ctx->new_db,
+ alter_ctx->new_alias))
+ {
+ (void) mysql_rename_table(old_db_type,
+ alter_ctx->new_db, alter_ctx->new_alias,
+ alter_ctx->db, alter_ctx->table_name,
+ NO_FK_CHECKS);
+ error= -1;
+ }
+ }
+
+ if (!error)
+ {
+ error= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ if (!error)
+ my_ok(thd);
+ }
+ table_list->table= NULL; // For query cache
+ query_cache_invalidate3(thd, table_list, 0);
+
+ if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
+ {
+ /*
+ Under LOCK TABLES we should adjust meta-data locks before finishing
+ statement. Otherwise we can rely on them being released
+ along with the implicit commit.
+ */
+ if (alter_ctx->is_table_renamed())
+ thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+ else
+ mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ }
+ DBUG_RETURN(error != 0);
+}
+
+
+/**
+ Alter table
+
+ @param thd Thread handle
+ @param new_db If there is a RENAME clause
+ @param new_name If there is a RENAME clause
+ @param create_info Information from the parsing phase about new
+ table properties.
+ @param table_list The table to change.
+ @param alter_info Lists of fields, keys to be changed, added
+ or dropped.
+ @param order_num How many ORDER BY fields has been specified.
+ @param order List of fields to ORDER BY.
+ @param ignore Whether we have ALTER IGNORE TABLE
+
+ @retval true Error
+ @retval false Success
+
+ This is a veery long function and is everything but the kitchen sink :)
+ It is used to alter a table and not only by ALTER TABLE but also
+ CREATE|DROP INDEX are mapped on this function.
+
+ When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS,
+ or both, then this function short cuts its operation by renaming
+ the table and/or enabling/disabling the keys. In this case, the FRM is
+ not changed, directly by mysql_alter_table. However, if there is a
+ RENAME + change of a field, or an index, the short cut is not used.
+ See how `create_list` is used to generate the new FRM regarding the
+ structure of the fields. The same is done for the indices of the table.
+
+ Altering a table can be done in two ways. The table can be modified
+ directly using an in-place algorithm, or the changes can be done using
+ an intermediate temporary table (copy). In-place is the preferred
+ algorithm as it avoids copying table data. The storage engine
+ selects which algorithm to use in check_if_supported_inplace_alter()
+ based on information about the table changes from fill_alter_inplace_info().
+*/
+
+bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
+ HA_CREATE_INFO *create_info,
+ TABLE_LIST *table_list,
+ Alter_info *alter_info,
+ uint order_num, ORDER *order, bool ignore)
+{
+ DBUG_ENTER("mysql_alter_table");
+
+ /*
+ Check if we attempt to alter mysql.slow_log or
+ mysql.general_log table and return an error if
+ it is the case.
+ TODO: this design is obsolete and will be removed.
+ */
+ int table_kind= check_if_log_table(table_list, FALSE, NullS);
+
+ if (table_kind)
+ {
+ /* Disable alter of enabled log tables */
+ if (logger.is_log_table_enabled(table_kind))
+ {
+ my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
+ DBUG_RETURN(true);
+ }
+
+ /* Disable alter of log tables to unsupported engine */
+ if ((create_info->used_fields & HA_CREATE_USED_ENGINE) &&
+ (!create_info->db_type || /* unknown engine */
+ !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES)))
+ {
+ my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0),
+ hton_name(create_info->db_type)->str);
+ DBUG_RETURN(true);
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (alter_info->flags & Alter_info::ALTER_PARTITION)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table");
+ DBUG_RETURN(true);
+ }
+#endif
+ }
+
+ THD_STAGE_INFO(thd, stage_init);
+
+ /*
+ Code below can handle only base tables so ensure that we won't open a view.
+ Note that RENAME TABLE the only ALTER clause which is supported for views
+ has been already processed.
+ */
+ table_list->required_type= FRMTYPE_TABLE;
+
+ Alter_table_prelocking_strategy alter_prelocking_strategy;
+
+ DEBUG_SYNC(thd, "alter_table_before_open_tables");
+ uint tables_opened;
+
+ thd->open_options|= HA_OPEN_FOR_ALTER;
+ bool error= open_tables(thd, &table_list, &tables_opened, 0,
+ &alter_prelocking_strategy);
+ thd->open_options&= ~HA_OPEN_FOR_ALTER;
+
+ DEBUG_SYNC(thd, "alter_opened_table");
+
+ if (error)
+ DBUG_RETURN(true);
+
+ TABLE *table= table_list->table;
+ table->use_all_columns();
+ MDL_ticket *mdl_ticket= table->mdl_ticket;
+
+ /*
+ Prohibit changing of the UNION list of a non-temporary MERGE table
+ under LOCK tables. It would be quite difficult to reuse a shrinked
+ set of tables from the old table or to open a new TABLE object for
+ an extended list and verify that they belong to locked tables.
+ */
+ if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
+ (create_info->used_fields & HA_CREATE_USED_UNION) &&
+ (table->s->tmp_table == NO_TMP_TABLE))
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ Alter_table_ctx alter_ctx(thd, table_list, tables_opened, new_db, new_name);
+
+ MDL_request target_mdl_request;
+
+ /* Check that we are not trying to rename to an existing table */
+ if (alter_ctx.is_table_renamed())
+ {
+ if (table->s->tmp_table != NO_TMP_TABLE)
+ {
+ if (find_temporary_table(thd, alter_ctx.new_db, alter_ctx.new_name))
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias);
+ DBUG_RETURN(true);
+ }
+ }
+ else
+ {
+ MDL_request_list mdl_requests;
+ MDL_request target_db_mdl_request;
+
+ target_mdl_request.init(MDL_key::TABLE,
+ alter_ctx.new_db, alter_ctx.new_name,
+ MDL_EXCLUSIVE, MDL_TRANSACTION);
+ mdl_requests.push_front(&target_mdl_request);
+
+ /*
+ If we are moving the table to a different database, we also
+ need IX lock on the database name so that the target database
+ is protected by MDL while the table is moved.
+ */
+ if (alter_ctx.is_database_changed())
+ {
+ target_db_mdl_request.init(MDL_key::SCHEMA, alter_ctx.new_db, "",
+ MDL_INTENTION_EXCLUSIVE,
+ MDL_TRANSACTION);
+ mdl_requests.push_front(&target_db_mdl_request);
+ }
+
+ /*
+ Global intention exclusive lock must have been already acquired when
+ table to be altered was open, so there is no need to do it here.
+ */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL,
+ "", "",
+ MDL_INTENTION_EXCLUSIVE));
+
+ if (thd->mdl_context.acquire_locks(&mdl_requests,
+ thd->variables.lock_wait_timeout))
+ DBUG_RETURN(true);
+
+ DEBUG_SYNC(thd, "locked_table_name");
+ /*
+ Table maybe does not exist, but we got an exclusive lock
+ on the name, now we can safely try to find out for sure.
+ */
+ if (ha_table_exists(thd, alter_ctx.new_db, alter_ctx.new_name, 0))
+ {
+ /* Table will be closed in do_command() */
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias);
+ DBUG_RETURN(true);
+ }
+ }
+ }
+
+ if (!create_info->db_type)
+ {
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (table->part_info &&
+ create_info->used_fields & HA_CREATE_USED_ENGINE)
+ {
+ /*
+ This case happens when the user specified
+ ENGINE = x where x is a non-existing storage engine
+ We set create_info->db_type to default_engine_type
+ to ensure we don't change underlying engine type
+ due to a erroneously given engine name.
+ */
+ create_info->db_type= table->part_info->default_engine_type;
+ }
+ else
+#endif
+ create_info->db_type= table->s->db_type();
+ }
+
+ if (check_engine(thd, alter_ctx.new_db, alter_ctx.new_name, create_info))
+ DBUG_RETURN(true);
+
+ if ((create_info->db_type != table->s->db_type() ||
+ alter_info->flags & Alter_info::ALTER_PARTITION) &&
+ !table->file->can_switch_engines())
+ {
+ my_error(ER_ROW_IS_REFERENCED, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ /*
+ If foreign key is added then check permission to access parent table.
+
+ In function "check_fk_parent_table_access", create_info->db_type is used
+ to identify whether engine supports FK constraint or not. Since
+ create_info->db_type is set here, check to parent table access is delayed
+ till this point for the alter operation.
+ */
+ if ((alter_info->flags & Alter_info::ADD_FOREIGN_KEY) &&
+ check_fk_parent_table_access(thd, create_info, alter_info))
+ DBUG_RETURN(true);
+
+ /*
+ If this is an ALTER TABLE and no explicit row type specified reuse
+ the table's row type.
+ Note: this is the same as if the row type was specified explicitly.
+ */
+ if (create_info->row_type == ROW_TYPE_NOT_USED)
+ {
+ /* ALTER TABLE without explicit row type */
+ create_info->row_type= table->s->row_type;
+ }
+ else
+ {
+ /* ALTER TABLE with specific row type */
+ create_info->used_fields |= HA_CREATE_USED_ROW_FORMAT;
+ }
+
+ DBUG_PRINT("info", ("old type: %s new type: %s",
+ ha_resolve_storage_engine_name(table->s->db_type()),
+ ha_resolve_storage_engine_name(create_info->db_type)));
+ if (ha_check_storage_engine_flag(table->s->db_type(), HTON_ALTER_NOT_SUPPORTED))
+ {
+ DBUG_PRINT("info", ("doesn't support alter"));
+ my_error(ER_ILLEGAL_HA, MYF(0), hton_name(table->s->db_type())->str,
+ alter_ctx.db, alter_ctx.table_name);
+ DBUG_RETURN(true);
+ }
+
+ if (ha_check_storage_engine_flag(create_info->db_type,
+ HTON_ALTER_NOT_SUPPORTED))
+ {
+ DBUG_PRINT("info", ("doesn't support alter"));
+ my_error(ER_ILLEGAL_HA, MYF(0), hton_name(create_info->db_type)->str,
+ alter_ctx.new_db, alter_ctx.new_name);
+ DBUG_RETURN(true);
+ }
+
+ if (table->s->tmp_table == NO_TMP_TABLE)
+ mysql_audit_alter_table(thd, table_list);
+
+ THD_STAGE_INFO(thd, stage_setup);
+
+ handle_if_exists_options(thd, table, alter_info);
+
+ /*
+ Look if we have to do anything at all.
+ ALTER can become NOOP after handling
+ the IF (NOT) EXISTS options.
+ */
+ if (alter_info->flags == 0)
+ {
+ my_snprintf(alter_ctx.tmp_name, sizeof(alter_ctx.tmp_name),
+ ER(ER_INSERT_INFO), 0L, 0L,
+ thd->get_stmt_da()->current_statement_warn_count());
+ my_ok(thd, 0L, 0L, alter_ctx.tmp_name);
+ DBUG_RETURN(false);
+ }
+
+ if (!(alter_info->flags & ~(Alter_info::ALTER_RENAME |
+ Alter_info::ALTER_KEYS_ONOFF)) &&
+ alter_info->requested_algorithm !=
+ Alter_info::ALTER_TABLE_ALGORITHM_COPY &&
+ !table->s->tmp_table) // no need to touch frm
+ {
+ // This requires X-lock, no other lock levels supported.
+ if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_DEFAULT &&
+ alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
+ "LOCK=NONE/SHARED", "LOCK=EXCLUSIVE");
+ DBUG_RETURN(true);
+ }
+ bool res= simple_rename_or_index_change(thd, table_list,
+ alter_info->keys_onoff,
+ &alter_ctx);
+ DBUG_RETURN(res);
+ }
+
+ /* We have to do full alter table. */
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ bool partition_changed= false;
+ bool fast_alter_partition= false;
+ {
+ if (prep_alter_part_table(thd, table, alter_info, create_info,
+ &alter_ctx, &partition_changed,
+ &fast_alter_partition))
+ {
+ DBUG_RETURN(true);
+ }
+ }
+#endif
+
+ if (mysql_prepare_alter_table(thd, table, create_info, alter_info,
+ &alter_ctx))
+ {
+ DBUG_RETURN(true);
+ }
+
+ set_table_default_charset(thd, create_info, alter_ctx.db);
+ promote_first_timestamp_column(&alter_info->create_list);
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (fast_alter_partition)
+ {
+ /*
+ ALGORITHM and LOCK clauses are generally not allowed by the
+ parser for operations related to partitioning.
+ The exceptions are ALTER_PARTITION and ALTER_REMOVE_PARTITIONING.
+ For consistency, we report ER_ALTER_OPERATION_NOT_SUPPORTED here.
+ */
+ if (alter_info->requested_lock !=
+ Alter_info::ALTER_TABLE_LOCK_DEFAULT)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "LOCK=NONE/SHARED/EXCLUSIVE",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION),
+ "LOCK=DEFAULT");
+ DBUG_RETURN(true);
+ }
+ else if (alter_info->requested_algorithm !=
+ Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "ALGORITHM=COPY/INPLACE",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION),
+ "ALGORITHM=DEFAULT");
+ DBUG_RETURN(true);
+ }
+
+ /*
+ Upgrade from MDL_SHARED_UPGRADABLE to MDL_SHARED_NO_WRITE.
+ Afterwards it's safe to take the table level lock.
+ */
+ if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
+ thd->variables.lock_wait_timeout)
+ || lock_tables(thd, table_list, alter_ctx.tables_opened, 0))
+ {
+ DBUG_RETURN(true);
+ }
+
+ // In-place execution of ALTER TABLE for partitioning.
+ DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info,
+ create_info, table_list,
+ alter_ctx.db,
+ alter_ctx.table_name));
+ }
+#endif
+
+ /*
+ Use copy algorithm if:
+ - old_alter_table system variable is set without in-place requested using
+ the ALGORITHM clause.
+ - Or if in-place is impossible for given operation.
+ - Changes to partitioning which were not handled by fast_alter_part_table()
+ needs to be handled using table copying algorithm unless the engine
+ supports auto-partitioning as such engines can do some changes
+ using in-place API.
+ */
+ if ((thd->variables.old_alter_table &&
+ alter_info->requested_algorithm !=
+ Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
+ || is_inplace_alter_impossible(table, create_info, alter_info)
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ || (partition_changed &&
+ !(table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION))
+#endif
+ )
+ {
+ if (alter_info->requested_algorithm ==
+ Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
+ "ALGORITHM=INPLACE", "ALGORITHM=COPY");
+ DBUG_RETURN(true);
+ }
+ alter_info->requested_algorithm= Alter_info::ALTER_TABLE_ALGORITHM_COPY;
+ }
+
+ /*
+ ALTER TABLE ... ENGINE to the same engine is a common way to
+ request table rebuild. Set ALTER_RECREATE flag to force table
+ rebuild.
+ */
+ if (create_info->db_type == table->s->db_type() &&
+ create_info->used_fields & HA_CREATE_USED_ENGINE)
+ alter_info->flags|= Alter_info::ALTER_RECREATE;
+
+ /*
+ If the old table had partitions and we are doing ALTER TABLE ...
+ engine= <new_engine>, the new table must preserve the original
+ partitioning. This means that the new engine is still the
+ partitioning engine, not the engine specified in the parser.
+ This is discovered in prep_alter_part_table, which in such case
+ updates create_info->db_type.
+ It's therefore important that the assignment below is done
+ after prep_alter_part_table.
+ */
+ handlerton *new_db_type= create_info->db_type;
+ handlerton *old_db_type= table->s->db_type();
+ TABLE *new_table= NULL;
+ ha_rows copied=0,deleted=0;
+
+ /*
+ Handling of symlinked tables:
+ If no rename:
+ Create new data file and index file on the same disk as the
+ old data and index files.
+ Copy data.
+ Rename new data file over old data file and new index file over
+ old index file.
+ Symlinks are not changed.
+
+ If rename:
+ Create new data file and index file on the same disk as the
+ old data and index files. Create also symlinks to point at
+ the new tables.
+ Copy data.
+ At end, rename intermediate tables, and symlinks to intermediate
+ table, to final table name.
+ Remove old table and old symlinks
+
+ If rename is made to another database:
+ Create new tables in new database.
+ Copy data.
+ Remove old table and symlinks.
+ */
+ char index_file[FN_REFLEN], data_file[FN_REFLEN];
+
+ if (!alter_ctx.is_database_changed())
+ {
+ if (create_info->index_file_name)
+ {
+ /* Fix index_file_name to have 'tmp_name' as basename */
+ strmov(index_file, alter_ctx.tmp_name);
+ create_info->index_file_name=fn_same(index_file,
+ create_info->index_file_name,
+ 1);
+ }
+ if (create_info->data_file_name)
+ {
+ /* Fix data_file_name to have 'tmp_name' as basename */
+ strmov(data_file, alter_ctx.tmp_name);
+ create_info->data_file_name=fn_same(data_file,
+ create_info->data_file_name,
+ 1);
+ }
+ }
+ else
+ {
+ /* Ignore symlink if db is changed. */
+ create_info->data_file_name=create_info->index_file_name=0;
+ }
+
+ DEBUG_SYNC(thd, "alter_table_before_create_table_no_lock");
+ /* We can abort alter table for any table type */
+ thd->abort_on_warning= !ignore && thd->is_strict_mode();
+
+ /*
+ Create .FRM for new version of table with a temporary name.
+ We don't log the statement, it will be logged later.
+
+ Keep information about keys in newly created table as it
+ will be used later to construct Alter_inplace_info object
+ and by fill_alter_inplace_info() call.
+ */
+ KEY *key_info;
+ uint key_count;
+ /*
+ Remember if the new definition has new VARCHAR column;
+ create_info->varchar will be reset in create_table_impl()/
+ mysql_prepare_create_table().
+ */
+ bool varchar= create_info->varchar;
+ LEX_CUSTRING frm= {0,0};
+
+ tmp_disable_binlog(thd);
+ create_info->options|=HA_CREATE_TMP_ALTER;
+ error= create_table_impl(thd,
+ alter_ctx.db, alter_ctx.table_name,
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ alter_ctx.get_tmp_path(),
+ create_info, alter_info,
+ C_ALTER_TABLE_FRM_ONLY, NULL,
+ &key_info, &key_count, &frm);
+ reenable_binlog(thd);
+ thd->abort_on_warning= false;
+ if (error)
+ {
+ my_free(const_cast<uchar*>(frm.str));
+ DBUG_RETURN(true);
+ }
+
+ /* Remember that we have not created table in storage engine yet. */
+ bool no_ha_table= true;
+
+ if (alter_info->requested_algorithm != Alter_info::ALTER_TABLE_ALGORITHM_COPY)
+ {
+ Alter_inplace_info ha_alter_info(create_info, alter_info,
+ key_info, key_count,
+ IF_PARTITIONING(thd->work_part_info, NULL),
+ ignore);
+ TABLE *altered_table= NULL;
+ bool use_inplace= true;
+
+ /* Fill the Alter_inplace_info structure. */
+ if (fill_alter_inplace_info(thd, table, varchar, &ha_alter_info))
+ goto err_new_table_cleanup;
+
+ if (ha_alter_info.handler_flags == 0)
+ {
+ /*
+ No-op ALTER, no need to call handler API functions.
+
+ If this code path is entered for an ALTER statement that
+ should not be a real no-op, new handler flags should be added
+ and fill_alter_inplace_info() adjusted.
+
+ Note that we can end up here if an ALTER statement has clauses
+ that cancel each other out (e.g. ADD/DROP identically index).
+
+ Also note that we ignore the LOCK clause here.
+
+ TODO don't create the frm in the first place
+ */
+ deletefrm(alter_ctx.get_tmp_path());
+ my_free(const_cast<uchar*>(frm.str));
+ goto end_inplace;
+ }
+
+ // We assume that the table is non-temporary.
+ DBUG_ASSERT(!table->s->tmp_table);
+
+ if (!(altered_table= open_table_uncached(thd, new_db_type,
+ alter_ctx.get_tmp_path(),
+ alter_ctx.new_db,
+ alter_ctx.tmp_name,
+ true, false)))
+ goto err_new_table_cleanup;
+
+ /* Set markers for fields in TABLE object for altered table. */
+ update_altered_table(ha_alter_info, altered_table);
+
+ /*
+ Mark all columns in 'altered_table' as used to allow usage
+ of its record[0] buffer and Field objects during in-place
+ ALTER TABLE.
+ */
+ altered_table->column_bitmaps_set_no_signal(&altered_table->s->all_set,
+ &altered_table->s->all_set);
+ restore_record(altered_table, s->default_values); // Create empty record
+ if (altered_table->default_field && altered_table->update_default_fields())
+ goto err_new_table_cleanup;
+
+ // Ask storage engine whether to use copy or in-place
+ enum_alter_inplace_result inplace_supported=
+ table->file->check_if_supported_inplace_alter(altered_table,
+ &ha_alter_info);
+
+ switch (inplace_supported) {
+ case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
+ // If SHARED lock and no particular algorithm was requested, use COPY.
+ if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_SHARED &&
+ alter_info->requested_algorithm ==
+ Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT)
+ {
+ use_inplace= false;
+ }
+ // Otherwise, if weaker lock was requested, report errror.
+ else if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_NONE ||
+ alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_SHARED)
+ {
+ ha_alter_info.report_unsupported_error("LOCK=NONE/SHARED",
+ "LOCK=EXCLUSIVE");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ break;
+ case HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE:
+ case HA_ALTER_INPLACE_SHARED_LOCK:
+ // If weaker lock was requested, report errror.
+ if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_NONE)
+ {
+ ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ break;
+ case HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE:
+ case HA_ALTER_INPLACE_NO_LOCK:
+ break;
+ case HA_ALTER_INPLACE_NOT_SUPPORTED:
+ // If INPLACE was requested, report error.
+ if (alter_info->requested_algorithm ==
+ Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
+ {
+ ha_alter_info.report_unsupported_error("ALGORITHM=INPLACE",
+ "ALGORITHM=COPY");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ // COPY with LOCK=NONE is not supported, no point in trying.
+ if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_NONE)
+ {
+ ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ // Otherwise use COPY
+ use_inplace= false;
+ break;
+ case HA_ALTER_ERROR:
+ default:
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+
+ if (use_inplace)
+ {
+ table->s->frm_image= &frm;
+ int res= mysql_inplace_alter_table(thd, table_list, table, altered_table,
+ &ha_alter_info, inplace_supported,
+ &target_mdl_request, &alter_ctx);
+ my_free(const_cast<uchar*>(frm.str));
+
+ if (res)
+ DBUG_RETURN(true);
+
+ goto end_inplace;
+ }
+ else
+ {
+ close_temporary_table(thd, altered_table, true, false);
+ }
+ }
+
+ /* ALTER TABLE using copy algorithm. */
+
+ /* Check if ALTER TABLE is compatible with foreign key definitions. */
+ if (fk_prepare_copy_alter_table(thd, table, alter_info, &alter_ctx))
+ goto err_new_table_cleanup;
+
+ if (!table->s->tmp_table)
+ {
+ // COPY algorithm doesn't work with concurrent writes.
+ if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "LOCK=NONE",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY),
+ "LOCK=SHARED");
+ goto err_new_table_cleanup;
+ }
+
+ // If EXCLUSIVE lock is requested, upgrade already.
+ if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE &&
+ wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err_new_table_cleanup;
+
+ /*
+ Otherwise upgrade to SHARED_NO_WRITE.
+ Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE.
+ */
+ if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE &&
+ thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
+ thd->variables.lock_wait_timeout))
+ goto err_new_table_cleanup;
+
+ DEBUG_SYNC(thd, "alter_table_copy_after_lock_upgrade");
+ }
+
+ // It's now safe to take the table level lock.
+ if (lock_tables(thd, table_list, alter_ctx.tables_opened, 0))
+ goto err_new_table_cleanup;
+
+ if (ha_create_table(thd, alter_ctx.get_tmp_path(),
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ create_info, &frm))
+ goto err_new_table_cleanup;
+
+ /* Mark that we have created table in storage engine. */
+ no_ha_table= false;
+
+ if (create_info->tmp_table())
+ {
+ if (!open_table_uncached(thd, new_db_type,
+ alter_ctx.get_tmp_path(),
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ true, true))
+ goto err_new_table_cleanup;
+ }
+
+ /* Open the table since we need to copy the data. */
+ if (table->s->tmp_table != NO_TMP_TABLE)
+ {
+ TABLE_LIST tbl;
+ tbl.init_one_table(alter_ctx.new_db, strlen(alter_ctx.new_db),
+ alter_ctx.tmp_name, strlen(alter_ctx.tmp_name),
+ alter_ctx.tmp_name, TL_READ_NO_INSERT);
+ /* Table is in thd->temporary_tables */
+ (void) open_temporary_table(thd, &tbl);
+ new_table= tbl.table;
+ }
+ else
+ {
+ /* table is a normal table: Create temporary table in same directory */
+ /* Open our intermediate table. */
+ new_table= open_table_uncached(thd, new_db_type, alter_ctx.get_tmp_path(),
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ true, true);
+ }
+ if (!new_table)
+ goto err_new_table_cleanup;
+ /*
+ Note: In case of MERGE table, we do not attach children. We do not
+ copy data for MERGE tables. Only the children have data.
+ */
+
+ /* Copy the data if necessary. */
+ thd->count_cuted_fields= CHECK_FIELD_WARN; // calc cuted fields
+ thd->cuted_fields=0L;
+
+ /*
+ We do not copy data for MERGE tables. Only the children have data.
+ MERGE tables have HA_NO_COPY_ON_ALTER set.
+ */
+ if (!(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))
+ {
+ new_table->next_number_field=new_table->found_next_number_field;
+ THD_STAGE_INFO(thd, stage_copy_to_tmp_table);
+ DBUG_EXECUTE_IF("abort_copy_table", {
+ my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
+ goto err_new_table_cleanup;
+ });
+ if (copy_data_between_tables(thd, table, new_table,
+ alter_info->create_list, ignore,
+ order_num, order, &copied, &deleted,
+ alter_info->keys_onoff,
+ &alter_ctx))
+ goto err_new_table_cleanup;
+ }
+ else
+ {
+ if (!table->s->tmp_table &&
+ wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err_new_table_cleanup;
+ THD_STAGE_INFO(thd, stage_manage_keys);
+ alter_table_manage_keys(table, table->file->indexes_are_disabled(),
+ alter_info->keys_onoff);
+ if (trans_commit_stmt(thd) || trans_commit_implicit(thd))
+ goto err_new_table_cleanup;
+ }
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
+
+ if (table->s->tmp_table != NO_TMP_TABLE)
+ {
+ /* Close lock if this is a transactional table */
+ if (thd->lock)
+ {
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES &&
+ thd->locked_tables_mode != LTM_PRELOCKED_UNDER_LOCK_TABLES)
+ {
+ mysql_unlock_tables(thd, thd->lock);
+ thd->lock= NULL;
+ }
+ else
+ {
+ /*
+ If LOCK TABLES list is not empty and contains this table,
+ unlock the table and remove the table from this list.
+ */
+ mysql_lock_remove(thd, thd->lock, table);
+ }
+ }
+ new_table->s->table_creation_was_logged=
+ table->s->table_creation_was_logged;
+ /* Remove link to old table and rename the new one */
+ close_temporary_table(thd, table, true, true);
+ /* Should pass the 'new_name' as we store table name in the cache */
+ if (rename_temporary_table(thd, new_table,
+ alter_ctx.new_db, alter_ctx.new_name))
+ goto err_new_table_cleanup;
+ /* We don't replicate alter table statement on temporary tables */
+ if (!thd->is_current_stmt_binlog_format_row() &&
+ write_bin_log(thd, true, thd->query(), thd->query_length()))
+ DBUG_RETURN(true);
+ my_free(const_cast<uchar*>(frm.str));
+ goto end_temporary;
+ }
+
+ /*
+ Close the intermediate table that will be the new table, but do
+ not delete it! Even altough MERGE tables do not have their children
+ attached here it is safe to call close_temporary_table().
+ */
+ close_temporary_table(thd, new_table, true, false);
+ new_table= NULL;
+
+ DEBUG_SYNC(thd, "alter_table_before_rename_result_table");
+
+ /*
+ Data is copied. Now we:
+ 1) Wait until all other threads will stop using old version of table
+ by upgrading shared metadata lock to exclusive one.
+ 2) Close instances of table open by this thread and replace them
+ with placeholders to simplify reopen process.
+ 3) Rename the old table to a temp name, rename the new one to the
+ old name.
+ 4) If we are under LOCK TABLES and don't do ALTER TABLE ... RENAME
+ we reopen new version of table.
+ 5) Write statement to the binary log.
+ 6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we
+ remove placeholders and release metadata locks.
+ 7) If we are not not under LOCK TABLES we rely on the caller
+ (mysql_execute_command()) to release metadata locks.
+ */
+
+ THD_STAGE_INFO(thd, stage_rename_result_table);
+
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+ goto err_new_table_cleanup;
+
+ close_all_tables_for_name(thd, table->s,
+ alter_ctx.is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME:
+ HA_EXTRA_NOT_USED,
+ NULL);
+ table_list->table= table= NULL; /* Safety */
+ my_free(const_cast<uchar*>(frm.str));
+
+ /*
+ Rename the old table to temporary name to have a backup in case
+ anything goes wrong while renaming the new table.
+ */
+ char backup_name[32];
+ my_snprintf(backup_name, sizeof(backup_name), "%s2-%lx-%lx", tmp_file_prefix,
+ current_pid, thd->thread_id);
+ if (lower_case_table_names)
+ my_casedn_str(files_charset_info, backup_name);
+ if (mysql_rename_table(old_db_type, alter_ctx.db, alter_ctx.table_name,
+ alter_ctx.db, backup_name, FN_TO_IS_TMP))
+ {
+ // Rename to temporary name failed, delete the new table, abort ALTER.
+ (void) quick_rm_table(thd, new_db_type, alter_ctx.new_db,
+ alter_ctx.tmp_name, FN_IS_TMP);
+ goto err_with_mdl;
+ }
+
+ // Rename the new table to the correct name.
+ if (mysql_rename_table(new_db_type, alter_ctx.new_db, alter_ctx.tmp_name,
+ alter_ctx.new_db, alter_ctx.new_alias,
+ FN_FROM_IS_TMP))
+ {
+ // Rename failed, delete the temporary table.
+ (void) quick_rm_table(thd, new_db_type, alter_ctx.new_db,
+ alter_ctx.tmp_name, FN_IS_TMP);
+
+ // Restore the backup of the original table to the old name.
+ (void) mysql_rename_table(old_db_type, alter_ctx.db, backup_name,
+ alter_ctx.db, alter_ctx.alias,
+ FN_FROM_IS_TMP | NO_FK_CHECKS);
+ goto err_with_mdl;
+ }
+
+ // Check if we renamed the table and if so update trigger files.
+ if (alter_ctx.is_table_renamed())
+ {
+ if (Table_triggers_list::change_table_name(thd,
+ alter_ctx.db,
+ alter_ctx.alias,
+ alter_ctx.table_name,
+ alter_ctx.new_db,
+ alter_ctx.new_alias))
+ {
+ // Rename succeeded, delete the new table.
+ (void) quick_rm_table(thd, new_db_type,
+ alter_ctx.new_db, alter_ctx.new_alias, 0);
+ // Restore the backup of the original table to the old name.
+ (void) mysql_rename_table(old_db_type, alter_ctx.db, backup_name,
+ alter_ctx.db, alter_ctx.alias,
+ FN_FROM_IS_TMP | NO_FK_CHECKS);
+ goto err_with_mdl;
+ }
+ rename_table_in_stat_tables(thd, alter_ctx.db,alter_ctx.alias,
+ alter_ctx.new_db, alter_ctx.new_alias);
+ }
+
+ // ALTER TABLE succeeded, delete the backup of the old table.
+ if (quick_rm_table(thd, old_db_type, alter_ctx.db, backup_name, FN_IS_TMP))
+ {
+ /*
+ The fact that deletion of the backup failed is not critical
+ error, but still worth reporting as it might indicate serious
+ problem with server.
+ */
+ goto err_with_mdl;
+ }
+
+end_inplace:
+
+ if (thd->locked_tables_list.reopen_tables(thd))
+ goto err_with_mdl;
+
+ THD_STAGE_INFO(thd, stage_end);
+
+ DEBUG_SYNC(thd, "alter_table_before_main_binlog");
+
+ DBUG_ASSERT(!(mysql_bin_log.is_open() &&
+ thd->is_current_stmt_binlog_format_row() &&
+ (create_info->tmp_table())));
+ if (write_bin_log(thd, true, thd->query(), thd->query_length()))
+ DBUG_RETURN(true);
+
+ if (ha_check_storage_engine_flag(old_db_type, HTON_FLUSH_AFTER_RENAME))
+ {
+ /*
+ For the alter table to be properly flushed to the logs, we
+ have to open the new table. If not, we get a problem on server
+ shutdown. But we do not need to attach MERGE children.
+ */
+ TABLE *t_table;
+ t_table= open_table_uncached(thd, new_db_type, alter_ctx.get_new_path(),
+ alter_ctx.new_db, alter_ctx.new_name,
+ false, true);
+ if (t_table)
+ intern_close_table(t_table);
+ else
+ sql_print_warning("Could not open table %s.%s after rename\n",
+ alter_ctx.new_db, alter_ctx.table_name);
+ ha_flush_logs(old_db_type);
+ }
+ table_list->table= NULL; // For query cache
+ query_cache_invalidate3(thd, table_list, false);
+
+ if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
+ {
+ if (alter_ctx.is_table_renamed())
+ thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+ else
+ mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ }
+
+end_temporary:
+ my_snprintf(alter_ctx.tmp_name, sizeof(alter_ctx.tmp_name),
+ ER(ER_INSERT_INFO),
+ (ulong) (copied + deleted), (ulong) deleted,
+ (ulong) thd->get_stmt_da()->current_statement_warn_count());
+ my_ok(thd, copied + deleted, 0L, alter_ctx.tmp_name);
+ DBUG_RETURN(false);
+
+err_new_table_cleanup:
+ my_free(const_cast<uchar*>(frm.str));
+ if (new_table)
+ {
+ /* close_temporary_table() frees the new_table pointer. */
+ close_temporary_table(thd, new_table, true, true);
+ }
+ else
+ (void) quick_rm_table(thd, new_db_type,
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ (FN_IS_TMP | (no_ha_table ? NO_HA_TABLE : 0)));
+
+ /*
+ No default value was provided for a DATE/DATETIME field, the
+ current sql_mode doesn't allow the '0000-00-00' value and
+ the table to be altered isn't empty.
+ Report error here.
+ */
+ if (alter_ctx.error_if_not_empty &&
+ thd->get_stmt_da()->current_row_for_warning())
+ {
+ const char *f_val= 0;
+ enum enum_mysql_timestamp_type t_type= MYSQL_TIMESTAMP_DATE;
+ switch (alter_ctx.datetime_field->sql_type)
+ {
+ case MYSQL_TYPE_DATE:
+ case MYSQL_TYPE_NEWDATE:
+ f_val= "0000-00-00";
+ t_type= MYSQL_TIMESTAMP_DATE;
+ break;
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_DATETIME2:
+ f_val= "0000-00-00 00:00:00";
+ t_type= MYSQL_TIMESTAMP_DATETIME;
+ break;
+ default:
+ /* Shouldn't get here. */
+ DBUG_ASSERT(0);
+ }
+ bool save_abort_on_warning= thd->abort_on_warning;
+ thd->abort_on_warning= true;
+ make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ f_val, strlength(f_val), t_type,
+ alter_ctx.datetime_field->field_name);
+ thd->abort_on_warning= save_abort_on_warning;
+ }
+
+ DBUG_RETURN(true);
+
+err_with_mdl:
+ /*
+ An error happened while we were holding exclusive name metadata lock
+ on table being altered. To be safe under LOCK TABLES we should
+ remove all references to the altered table from the list of locked
+ tables and release the exclusive metadata lock.
+ */
+ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
+ thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+ DBUG_RETURN(true);
+}
+
+
+
+/**
+ Prepare the transaction for the alter table's copy phase.
+*/
+
+bool mysql_trans_prepare_alter_copy_data(THD *thd)
+{
+ DBUG_ENTER("mysql_trans_prepare_alter_copy_data");
+ /*
+ Turn off recovery logging since rollback of an alter table is to
+ delete the new table so there is no need to log the changes to it.
+
+ This needs to be done before external_lock.
+ */
+ if (ha_enable_transaction(thd, FALSE))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Commit the copy phase of the alter table.
+*/
+
+bool mysql_trans_commit_alter_copy_data(THD *thd)
+{
+ bool error= FALSE;
+ DBUG_ENTER("mysql_trans_commit_alter_copy_data");
+
+ if (ha_enable_transaction(thd, TRUE))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Ensure that the new table is saved properly to disk before installing
+ the new .frm.
+ And that InnoDB's internal latches are released, to avoid deadlock
+ when waiting on other instances of the table before rename (Bug#54747).
+ */
+ if (trans_commit_stmt(thd))
+ error= TRUE;
+ if (trans_commit_implicit(thd))
+ error= TRUE;
+
+ DBUG_RETURN(error);
+}
+
+
+static int
+copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
+ List<Create_field> &create, bool ignore,
+ uint order_num, ORDER *order,
+ ha_rows *copied, ha_rows *deleted,
+ Alter_info::enum_enable_or_disable keys_onoff,
+ Alter_table_ctx *alter_ctx)
+{
+ int error= 1;
+ Copy_field *copy= NULL, *copy_end;
+ ha_rows found_count= 0, delete_count= 0;
+ uint length= 0;
+ SORT_FIELD *sortorder;
+ READ_RECORD info;
+ TABLE_LIST tables;
+ List<Item> fields;
+ List<Item> all_fields;
+ ha_rows examined_rows;
+ ha_rows found_rows;
+ bool auto_increment_field_copied= 0;
+ ulonglong save_sql_mode= thd->variables.sql_mode;
+ ulonglong prev_insert_id, time_to_report_progress;
+ Field **dfield_ptr= to->default_field;
+ DBUG_ENTER("copy_data_between_tables");
+
+ /* Two or 3 stages; Sorting, copying data and update indexes */
+ thd_progress_init(thd, 2 + MY_TEST(order));
+
+ if (mysql_trans_prepare_alter_copy_data(thd))
+ DBUG_RETURN(-1);
+
+ if (!(copy= new Copy_field[to->s->fields]))
+ DBUG_RETURN(-1); /* purecov: inspected */
+
+ /* We need external lock before we can disable/enable keys */
+ if (to->file->ha_external_lock(thd, F_WRLCK))
+ DBUG_RETURN(-1);
+
+ alter_table_manage_keys(to, from->file->indexes_are_disabled(), keys_onoff);
+
+ /* We can abort alter table for any table type */
+ thd->abort_on_warning= !ignore && thd->is_strict_mode();
+
+ from->file->info(HA_STATUS_VARIABLE);
+ to->file->ha_start_bulk_insert(from->file->stats.records,
+ ignore ? 0 : HA_CREATE_UNIQUE_INDEX_BY_SORT);
+
+ List_iterator<Create_field> it(create);
+ Create_field *def;
+ copy_end=copy;
+ to->s->default_fields= 0;
+ for (Field **ptr=to->field ; *ptr ; ptr++)
+ {
+ def=it++;
+ if (def->field)
+ {
+ if (*ptr == to->next_number_field)
+ {
+ auto_increment_field_copied= TRUE;
+ /*
+ If we are going to copy contents of one auto_increment column to
+ another auto_increment column it is sensible to preserve zeroes.
+ This condition also covers case when we are don't actually alter
+ auto_increment column.
+ */
+ if (def->field == from->found_next_number_field)
+ thd->variables.sql_mode|= MODE_NO_AUTO_VALUE_ON_ZERO;
+ }
+ (copy_end++)->set(*ptr,def->field,0);
+ }
+ else
+ {
+ /*
+ Update the set of auto-update fields to contain only the new fields
+ added to the table. Only these fields should be updated automatically.
+ Old fields keep their current values, and therefore should not be
+ present in the set of autoupdate fields.
+ */
+ if ((*ptr)->has_insert_default_function())
+ {
+ *(dfield_ptr++)= *ptr;
+ ++to->s->default_fields;
+ }
+ }
+ }
+ if (dfield_ptr)
+ *dfield_ptr= NULL;
+
+ if (order)
+ {
+ if (to->s->primary_key != MAX_KEY &&
+ to->file->ha_table_flags() & HA_TABLE_SCAN_ON_INDEX)
+ {
+ char warn_buff[MYSQL_ERRMSG_SIZE];
+ my_snprintf(warn_buff, sizeof(warn_buff),
+ "ORDER BY ignored as there is a user-defined clustered index"
+ " in the table '%-.192s'", from->s->table_name.str);
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
+ warn_buff);
+ }
+ else
+ {
+ from->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE),
+ MYF(MY_FAE | MY_ZEROFILL |
+ MY_THREAD_SPECIFIC));
+ bzero((char *) &tables, sizeof(tables));
+ tables.table= from;
+ tables.alias= tables.table_name= from->s->table_name.str;
+ tables.db= from->s->db.str;
+
+ THD_STAGE_INFO(thd, stage_sorting);
+ if (thd->lex->select_lex.setup_ref_array(thd, order_num) ||
+ setup_order(thd, thd->lex->select_lex.ref_pointer_array,
+ &tables, fields, all_fields, order) ||
+ !(sortorder= make_unireg_sortorder(order, &length, NULL)) ||
+ (from->sort.found_records= filesort(thd, from, sortorder, length,
+ NULL, HA_POS_ERROR,
+ true,
+ &examined_rows, &found_rows)) ==
+ HA_POS_ERROR)
+ goto err;
+ }
+ thd_progress_next_stage(thd);
+ }
+
+ THD_STAGE_INFO(thd, stage_copy_to_tmp_table);
+ /* Tell handler that we have values for all columns in the to table */
+ to->use_all_columns();
+ to->mark_virtual_columns_for_write(TRUE);
+ if (init_read_record(&info, thd, from, (SQL_SELECT *) 0, 1, 1, FALSE))
+ goto err;
+
+ if (ignore && !alter_ctx->fk_error_if_delete_row)
+ to->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ thd->get_stmt_da()->reset_current_row_for_warning();
+ restore_record(to, s->default_values); // Create empty record
+ if (to->default_field && to->update_default_fields())
+ goto err;
+
+ thd->progress.max_counter= from->file->records();
+ time_to_report_progress= MY_HOW_OFTEN_TO_WRITE/10;
+
+ while (!(error=info.read_record(&info)))
+ {
+ if (thd->killed)
+ {
+ thd->send_kill_message();
+ error= 1;
+ break;
+ }
+ if (from->vfield)
+ update_virtual_fields(thd, from);
+ if (++thd->progress.counter >= time_to_report_progress)
+ {
+ time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10;
+ thd_progress_report(thd, thd->progress.counter,
+ thd->progress.max_counter);
+ }
+
+ /* Return error if source table isn't empty. */
+ if (alter_ctx->error_if_not_empty)
+ {
+ error= 1;
+ break;
+ }
+ if (to->next_number_field)
+ {
+ if (auto_increment_field_copied)
+ to->auto_increment_field_not_null= TRUE;
+ else
+ to->next_number_field->reset();
+ }
+
+ for (Copy_field *copy_ptr=copy ; copy_ptr != copy_end ; copy_ptr++)
+ {
+ copy_ptr->do_copy(copy_ptr);
+ }
+ prev_insert_id= to->file->next_insert_id;
+ if (to->vfield)
+ update_virtual_fields(thd, to, VCOL_UPDATE_FOR_WRITE);
+ if (thd->is_error())
+ {
+ error= 1;
+ break;
+ }
+ error=to->file->ha_write_row(to->record[0]);
+ to->auto_increment_field_not_null= FALSE;
+ if (error)
+ {
+ if (to->file->is_fatal_error(error, HA_CHECK_DUP))
+ {
+ /* Not a duplicate key error. */
+ to->file->print_error(error, MYF(0));
+ error= 1;
+ break;
+ }
+ else
+ {
+ /* Duplicate key error. */
+ if (alter_ctx->fk_error_if_delete_row)
+ {
+ /*
+ We are trying to omit a row from the table which serves as parent
+ in a foreign key. This might have broken referential integrity so
+ emit an error. Note that we can't ignore this error even if we are
+ executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but
+ doesn't allow to break unique or foreign key constraints,
+ */
+ my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0),
+ alter_ctx->fk_error_id,
+ alter_ctx->fk_error_table);
+ break;
+ }
+
+ if (ignore)
+ {
+ /* This ALTER IGNORE TABLE. Simply skip row and continue. */
+ to->file->restore_auto_increment(prev_insert_id);
+ delete_count++;
+ }
+ else
+ {
+ /* Ordinary ALTER TABLE. Report duplicate key error. */
+ uint key_nr= to->file->get_dup_key(error);
+ if ((int) key_nr >= 0)
+ {
+ const char *err_msg= ER(ER_DUP_ENTRY_WITH_KEY_NAME);
+ if (key_nr == 0 &&
+ (to->key_info[0].key_part[0].field->flags &
+ AUTO_INCREMENT_FLAG))
+ err_msg= ER(ER_DUP_ENTRY_AUTOINCREMENT_CASE);
+ print_keydup_error(to, key_nr == MAX_KEY ? NULL :
+ &to->key_info[key_nr],
+ err_msg, MYF(0));
+ }
+ else
+ to->file->print_error(error, MYF(0));
+ break;
+ }
+ }
+ }
+ else
+ found_count++;
+ thd->get_stmt_da()->inc_current_row_for_warning();
+ }
+ end_read_record(&info);
+ free_io_cache(from);
+ delete [] copy;
+
+ THD_STAGE_INFO(thd, stage_enabling_keys);
+ thd_progress_next_stage(thd);
+
+ if (error > 0)
+ {
+ /* We are going to drop the temporary table */
+ to->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
+ }
+ if (to->file->ha_end_bulk_insert() && error <= 0)
+ {
+ to->file->print_error(my_errno,MYF(0));
+ error= 1;
+ }
+ to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+
+ if (mysql_trans_commit_alter_copy_data(thd))
+ error= 1;
+
+ err:
+ thd->variables.sql_mode= save_sql_mode;
+ thd->abort_on_warning= 0;
+ *copied= found_count;
+ *deleted=delete_count;
+ to->file->ha_release_auto_increment();
+ if (to->file->ha_external_lock(thd,F_UNLCK))
+ error=1;
+ if (error < 0 && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME))
+ error= 1;
+ thd_progress_end(thd);
+ DBUG_RETURN(error > 0 ? -1 : 0);
+}
+
+
+/*
+ Recreates one table by calling mysql_alter_table().
+
+ SYNOPSIS
+ mysql_recreate_table()
+ thd Thread handler
+ table_list Table to recreate
+ table_copy Recreate the table by using
+ ALTER TABLE COPY algorithm
+
+ RETURN
+ Like mysql_alter_table().
+*/
+
+bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list, bool table_copy)
+{
+ HA_CREATE_INFO create_info;
+ Alter_info alter_info;
+ TABLE_LIST *next_table= table_list->next_global;
+
+ DBUG_ENTER("mysql_recreate_table");
+ /* Set lock type which is appropriate for ALTER TABLE. */
+ table_list->lock_type= TL_READ_NO_INSERT;
+ /* Same applies to MDL request. */
+ table_list->mdl_request.set_type(MDL_SHARED_NO_WRITE);
+ /* hide following tables from open_tables() */
+ table_list->next_global= NULL;
+
+ bzero((char*) &create_info, sizeof(create_info));
+ create_info.row_type=ROW_TYPE_NOT_USED;
+ create_info.default_table_charset=default_charset_info;
+ /* Force alter table to recreate table */
+ alter_info.flags= (Alter_info::ALTER_CHANGE_COLUMN |
+ Alter_info::ALTER_RECREATE);
+
+ if (table_copy)
+ alter_info.requested_algorithm= Alter_info::ALTER_TABLE_ALGORITHM_COPY;
+
+ bool res= mysql_alter_table(thd, NullS, NullS, &create_info,
+ table_list, &alter_info, 0,
+ (ORDER *) 0, 0);
+ table_list->next_global= next_table;
+ DBUG_RETURN(res);
+}
+
+
+bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
+ HA_CHECK_OPT *check_opt)
+{
+ TABLE_LIST *table;
+ List<Item> field_list;
+ Item *item;
+ Protocol *protocol= thd->protocol;
+ DBUG_ENTER("mysql_checksum_table");
+
+ /*
+ CHECKSUM TABLE returns results and rollbacks statement transaction,
+ so it should not be used in stored function or trigger.
+ */
+ DBUG_ASSERT(! thd->in_sub_stmt);
+
+ field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2));
+ item->maybe_null= 1;
+ field_list.push_back(item= new Item_int("Checksum",
+ (longlong) 1,
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ item->maybe_null= 1;
+ if (protocol->send_result_set_metadata(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ /*
+ Close all temporary tables which were pre-open to simplify
+ privilege checking. Clear all references to closed tables.
+ */
+ close_thread_tables(thd);
+ for (table= tables; table; table= table->next_local)
+ table->table= NULL;
+
+ /* Open one table after the other to keep lock time as short as possible. */
+ for (table= tables; table; table= table->next_local)
+ {
+ char table_name[SAFE_NAME_LEN*2+2];
+ TABLE *t;
+ TABLE_LIST *save_next_global;
+
+ strxmov(table_name, table->db ,".", table->table_name, NullS);
+
+ /* Remember old 'next' pointer and break the list. */
+ save_next_global= table->next_global;
+ table->next_global= NULL;
+ table->lock_type= TL_READ;
+ /* Allow to open real tables only. */
+ table->required_type= FRMTYPE_TABLE;
+
+ if (open_temporary_tables(thd, table) ||
+ open_and_lock_tables(thd, table, FALSE, 0))
+ {
+ t= NULL;
+ }
+ else
+ t= table->table;
+
+ table->next_global= save_next_global;
+
+ protocol->prepare_for_resend();
+ protocol->store(table_name, system_charset_info);
+
+ if (!t)
+ {
+ /* Table didn't exist */
+ protocol->store_null();
+ }
+ else
+ {
+ /* Call ->checksum() if the table checksum matches 'old_mode' settings */
+ if (!(check_opt->flags & T_EXTEND) &&
+ (((t->file->ha_table_flags() & HA_HAS_OLD_CHECKSUM) && thd->variables.old_mode) ||
+ ((t->file->ha_table_flags() & HA_HAS_NEW_CHECKSUM) && !thd->variables.old_mode)))
+ protocol->store((ulonglong)t->file->checksum());
+ else if (check_opt->flags & T_QUICK)
+ protocol->store_null();
+ else
+ {
+ /* calculating table's checksum */
+ ha_checksum crc= 0;
+ uchar null_mask=256 - (1 << t->s->last_null_bit_pos);
+
+ t->use_all_columns();
+
+ if (t->file->ha_rnd_init(1))
+ protocol->store_null();
+ else
+ {
+ for (;;)
+ {
+ if (thd->killed)
+ {
+ /*
+ we've been killed; let handler clean up, and remove the
+ partial current row from the recordset (embedded lib)
+ */
+ t->file->ha_rnd_end();
+ thd->protocol->remove_last_row();
+ goto err;
+ }
+ ha_checksum row_crc= 0;
+ int error= t->file->ha_rnd_next(t->record[0]);
+ if (unlikely(error))
+ {
+ if (error == HA_ERR_RECORD_DELETED)
+ continue;
+ break;
+ }
+ if (t->s->null_bytes)
+ {
+ /* fix undefined null bits */
+ t->record[0][t->s->null_bytes-1] |= null_mask;
+ if (!(t->s->db_create_options & HA_OPTION_PACK_RECORD))
+ t->record[0][0] |= 1;
+
+ row_crc= my_checksum(row_crc, t->record[0], t->s->null_bytes);
+ }
+
+ for (uint i= 0; i < t->s->fields; i++ )
+ {
+ Field *f= t->field[i];
+
+ if (! thd->variables.old_mode && f->is_real_null(0))
+ continue;
+ /*
+ BLOB and VARCHAR have pointers in their field, we must convert
+ to string; GEOMETRY is implemented on top of BLOB.
+ BIT may store its data among NULL bits, convert as well.
+ */
+ switch (f->type()) {
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_GEOMETRY:
+ case MYSQL_TYPE_BIT:
+ {
+ String tmp;
+ f->val_str(&tmp);
+ row_crc= my_checksum(row_crc, (uchar*) tmp.ptr(),
+ tmp.length());
+ break;
+ }
+ default:
+ row_crc= my_checksum(row_crc, f->ptr, f->pack_length());
+ break;
+ }
+ }
+
+ crc+= row_crc;
+ }
+ protocol->store((ulonglong)crc);
+ t->file->ha_rnd_end();
+ }
+ }
+ trans_rollback_stmt(thd);
+ close_thread_tables(thd);
+ }
+
+ if (thd->transaction_rollback_request)
+ {
+ /*
+ If transaction rollback was requested we honor it. To do this we
+ abort statement and return error as not only CHECKSUM TABLE is
+ rolled back but the whole transaction in which it was used.
+ */
+ thd->protocol->remove_last_row();
+ goto err;
+ }
+
+ /* Hide errors from client. Return NULL for problematic tables instead. */
+ thd->clear_error();
+
+ if (protocol->write())
+ goto err;
+ }
+
+ my_eof(thd);
+ DBUG_RETURN(FALSE);
+
+err:
+ DBUG_RETURN(TRUE);
+}
+
+/**
+ @brief Check if the table can be created in the specified storage engine.
+
+ Checks if the storage engine is enabled and supports the given table
+ type (e.g. normal, temporary, system). May do engine substitution
+ if the requested engine is disabled.
+
+ @param thd Thread descriptor.
+ @param db_name Database name.
+ @param table_name Name of table to be created.
+ @param create_info Create info from parser, including engine.
+
+ @retval true Engine not available/supported, error has been reported.
+ @retval false Engine available/supported.
+*/
+static bool check_engine(THD *thd, const char *db_name,
+ const char *table_name, HA_CREATE_INFO *create_info)
+{
+ DBUG_ENTER("check_engine");
+ handlerton **new_engine= &create_info->db_type;
+ handlerton *req_engine= *new_engine;
+ bool no_substitution=
+ MY_TEST(thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION);
+ if (!(*new_engine= ha_checktype(thd, ha_legacy_type(req_engine),
+ no_substitution, 1)))
+ DBUG_RETURN(true);
+
+ if (req_engine && req_engine != *new_engine)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_WARN_USING_OTHER_HANDLER,
+ ER(ER_WARN_USING_OTHER_HANDLER),
+ ha_resolve_storage_engine_name(*new_engine),
+ table_name);
+ }
+ if (create_info->tmp_table() &&
+ ha_check_storage_engine_flag(*new_engine, HTON_TEMPORARY_NOT_SUPPORTED))
+ {
+ if (create_info->used_fields & HA_CREATE_USED_ENGINE)
+ {
+ my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0),
+ hton_name(*new_engine)->str, "TEMPORARY");
+ *new_engine= 0;
+ DBUG_RETURN(true);
+ }
+ *new_engine= myisam_hton;
+ }
+
+ DBUG_RETURN(false);
+}
diff --git a/sql/sql_table.h b/sql/sql_table.h
new file mode 100644
index 00000000000..2b383623873
--- /dev/null
+++ b/sql/sql_table.h
@@ -0,0 +1,287 @@
+/* Copyright (c) 2006, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2014, Monty Program Ab.
+
+ 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 */
+
+#ifndef SQL_TABLE_INCLUDED
+#define SQL_TABLE_INCLUDED
+
+#include "my_global.h" /* my_bool */
+#include "my_sys.h" // pthread_mutex_t
+#include "m_string.h" // LEX_CUSTRING
+
+class Alter_info;
+class Alter_table_ctx;
+class Create_field;
+struct TABLE_LIST;
+class THD;
+struct TABLE;
+struct handlerton;
+class handler;
+typedef struct st_ha_check_opt HA_CHECK_OPT;
+struct HA_CREATE_INFO;
+typedef struct st_key KEY;
+typedef struct st_key_cache KEY_CACHE;
+typedef struct st_lock_param_type ALTER_PARTITION_PARAM_TYPE;
+typedef struct st_order ORDER;
+
+enum ddl_log_entry_code
+{
+ /*
+ DDL_LOG_EXECUTE_CODE:
+ This is a code that indicates that this is a log entry to
+ be executed, from this entry a linked list of log entries
+ can be found and executed.
+ DDL_LOG_ENTRY_CODE:
+ An entry to be executed in a linked list from an execute log
+ entry.
+ DDL_IGNORE_LOG_ENTRY_CODE:
+ An entry that is to be ignored
+ */
+ DDL_LOG_EXECUTE_CODE = 'e',
+ DDL_LOG_ENTRY_CODE = 'l',
+ DDL_IGNORE_LOG_ENTRY_CODE = 'i'
+};
+
+enum ddl_log_action_code
+{
+ /*
+ The type of action that a DDL_LOG_ENTRY_CODE entry is to
+ perform.
+ DDL_LOG_DELETE_ACTION:
+ Delete an entity
+ DDL_LOG_RENAME_ACTION:
+ Rename an entity
+ DDL_LOG_REPLACE_ACTION:
+ Rename an entity after removing the previous entry with the
+ new name, that is replace this entry.
+ DDL_LOG_EXCHANGE_ACTION:
+ Exchange two entities by renaming them a -> tmp, b -> a, tmp -> b.
+ */
+ DDL_LOG_DELETE_ACTION = 'd',
+ DDL_LOG_RENAME_ACTION = 'r',
+ DDL_LOG_REPLACE_ACTION = 's',
+ DDL_LOG_EXCHANGE_ACTION = 'e'
+};
+
+enum enum_ddl_log_exchange_phase {
+ EXCH_PHASE_NAME_TO_TEMP= 0,
+ EXCH_PHASE_FROM_TO_NAME= 1,
+ EXCH_PHASE_TEMP_TO_FROM= 2
+};
+
+
+typedef struct st_ddl_log_entry
+{
+ const char *name;
+ const char *from_name;
+ const char *handler_name;
+ const char *tmp_name;
+ uint next_entry;
+ uint entry_pos;
+ enum ddl_log_entry_code entry_type;
+ enum ddl_log_action_code action_type;
+ /*
+ Most actions have only one phase. REPLACE does however have two
+ phases. The first phase removes the file with the new name if
+ there was one there before and the second phase renames the
+ old name to the new name.
+ */
+ char phase;
+} DDL_LOG_ENTRY;
+
+typedef struct st_ddl_log_memory_entry
+{
+ uint entry_pos;
+ struct st_ddl_log_memory_entry *next_log_entry;
+ struct st_ddl_log_memory_entry *prev_log_entry;
+ struct st_ddl_log_memory_entry *next_active_log_entry;
+} DDL_LOG_MEMORY_ENTRY;
+
+
+enum enum_explain_filename_mode
+{
+ EXPLAIN_ALL_VERBOSE= 0,
+ EXPLAIN_PARTITIONS_VERBOSE,
+ EXPLAIN_PARTITIONS_AS_COMMENT
+};
+
+/* Maximum length of GEOM_POINT Field */
+#define MAX_LEN_GEOM_POINT_FIELD 25
+
+/* depends on errmsg.txt Database `db`, Table `t` ... */
+#define EXPLAIN_FILENAME_MAX_EXTRA_LENGTH 63
+
+#define WFRM_WRITE_SHADOW 1
+#define WFRM_INSTALL_SHADOW 2
+#define WFRM_PACK_FRM 4
+#define WFRM_KEEP_SHARE 8
+
+/* Flags for conversion functions. */
+static const uint FN_FROM_IS_TMP= 1 << 0;
+static const uint FN_TO_IS_TMP= 1 << 1;
+static const uint FN_IS_TMP= FN_FROM_IS_TMP | FN_TO_IS_TMP;
+static const uint NO_FRM_RENAME= 1 << 2;
+static const uint FRM_ONLY= 1 << 3;
+/** Don't remove table in engine. Remove only .FRM and maybe .PAR files. */
+static const uint NO_HA_TABLE= 1 << 4;
+/** Don't resolve MySQL's fake "foo.sym" symbolic directory names. */
+static const uint SKIP_SYMDIR_ACCESS= 1 << 5;
+/** Don't check foreign key constraints while renaming table */
+static const uint NO_FK_CHECKS= 1 << 6;
+
+uint filename_to_tablename(const char *from, char *to, uint to_length,
+ bool stay_quiet = false);
+uint tablename_to_filename(const char *from, char *to, uint to_length);
+uint check_n_cut_mysql50_prefix(const char *from, char *to, uint to_length);
+bool check_mysql50_prefix(const char *name);
+uint build_table_filename(char *buff, size_t bufflen, const char *db,
+ const char *table, const char *ext, uint flags);
+uint build_table_shadow_filename(char *buff, size_t bufflen,
+ ALTER_PARTITION_PARAM_TYPE *lpt);
+uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen);
+bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info);
+
+/*
+ mysql_create_table_no_lock can be called in one of the following
+ mutually exclusive situations:
+
+ - Just a normal ordinary CREATE TABLE statement that explicitly
+ defines the table structure.
+
+ - CREATE TABLE ... SELECT. It is special, because only in this case,
+ the list of fields is allowed to have duplicates, as long as one of the
+ duplicates comes from the select list, and the other doesn't. For
+ example in
+
+ CREATE TABLE t1 (a int(5) NOT NUL) SELECT b+10 as a FROM t2;
+
+ the list in alter_info->create_list will have two fields `a`.
+
+ - ALTER TABLE, that creates a temporary table #sql-xxx, which will be later
+ renamed to replace the original table.
+
+ - ALTER TABLE as above, but which only modifies the frm file, it only
+ creates an frm file for the #sql-xxx, the table in the engine is not
+ created.
+
+ - Assisted discovery, CREATE TABLE statement without the table structure.
+
+ These situations are distinguished by the following "create table mode"
+ values, where a CREATE ... SELECT is denoted by any non-negative number
+ (which should be the number of fields in the SELECT ... part), and other
+ cases use constants as defined below.
+*/
+#define C_CREATE_SELECT(X) ((X) > 0 ? (X) : 0)
+#define C_ORDINARY_CREATE 0
+#define C_ALTER_TABLE -1
+#define C_ALTER_TABLE_FRM_ONLY -2
+#define C_ASSISTED_DISCOVERY -3
+
+int mysql_create_table_no_lock(THD *thd, const char *db,
+ const char *table_name,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info, bool *is_trans,
+ int create_table_mode);
+
+handler *mysql_create_frm_image(THD *thd,
+ const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ int create_table_mode,
+ KEY **key_info,
+ uint *key_count,
+ LEX_CUSTRING *frm);
+
+int mysql_discard_or_import_tablespace(THD *thd,
+ TABLE_LIST *table_list,
+ bool discard);
+
+bool mysql_prepare_alter_table(THD *thd, TABLE *table,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ Alter_table_ctx *alter_ctx);
+bool mysql_trans_prepare_alter_copy_data(THD *thd);
+bool mysql_trans_commit_alter_copy_data(THD *thd);
+bool mysql_alter_table(THD *thd, char *new_db, char *new_name,
+ HA_CREATE_INFO *create_info,
+ TABLE_LIST *table_list,
+ Alter_info *alter_info,
+ uint order_num, ORDER *order, bool ignore);
+bool mysql_compare_tables(TABLE *table,
+ Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ bool *metadata_equal);
+bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list, bool table_copy);
+bool mysql_create_like_table(THD *thd, TABLE_LIST *table,
+ TABLE_LIST *src_table,
+ HA_CREATE_INFO *create_info);
+bool mysql_rename_table(handlerton *base, const char *old_db,
+ const char * old_name, const char *new_db,
+ const char * new_name, uint flags);
+
+bool mysql_backup_table(THD* thd, TABLE_LIST* table_list);
+bool mysql_restore_table(THD* thd, TABLE_LIST* table_list);
+
+bool mysql_checksum_table(THD* thd, TABLE_LIST* table_list,
+ HA_CHECK_OPT* check_opt);
+bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
+ my_bool drop_temporary);
+int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists,
+ bool drop_temporary, bool drop_view,
+ bool log_query, bool dont_free_locks);
+bool log_drop_table(THD *thd, const char *db_name, size_t db_name_length,
+ const char *table_name, size_t table_name_length,
+ bool temporary_table);
+bool quick_rm_table(THD *thd, handlerton *base, const char *db,
+ const char *table_name, uint flags);
+void close_cached_table(THD *thd, TABLE *table);
+void sp_prepare_create_field(THD *thd, Create_field *sql_field);
+int prepare_create_field(Create_field *sql_field,
+ uint *blob_columns,
+ longlong table_flags);
+CHARSET_INFO* get_sql_field_charset(Create_field *sql_field,
+ HA_CREATE_INFO *create_info);
+bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags);
+int write_bin_log(THD *thd, bool clear_error,
+ char const *query, ulong query_length,
+ bool is_trans= FALSE);
+bool write_ddl_log_entry(DDL_LOG_ENTRY *ddl_log_entry,
+ DDL_LOG_MEMORY_ENTRY **active_entry);
+bool write_execute_ddl_log_entry(uint first_entry,
+ bool complete,
+ DDL_LOG_MEMORY_ENTRY **active_entry);
+bool deactivate_ddl_log_entry(uint entry_no);
+void release_ddl_log_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry);
+bool sync_ddl_log();
+void release_ddl_log();
+void execute_ddl_log_recovery();
+bool execute_ddl_log_entry(THD *thd, uint first_entry);
+
+template<typename T> class List;
+void promote_first_timestamp_column(List<Create_field> *column_definitions);
+
+/*
+ These prototypes where under INNODB_COMPATIBILITY_HOOKS.
+*/
+uint explain_filename(THD* thd, const char *from, char *to, uint to_length,
+ enum_explain_filename_mode explain_mode);
+
+
+extern MYSQL_PLUGIN_IMPORT const char *primary_key_name;
+extern mysql_mutex_t LOCK_gdl;
+
+#endif /* SQL_TABLE_INCLUDED */
diff --git a/sql/sql_tablespace.cc b/sql/sql_tablespace.cc
new file mode 100644
index 00000000000..2991b16350c
--- /dev/null
+++ b/sql/sql_tablespace.cc
@@ -0,0 +1,77 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/* drop and alter of tablespaces */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_tablespace.h"
+#include "sql_table.h" // write_bin_log
+#include "sql_class.h" // THD
+
+int mysql_alter_tablespace(THD *thd, st_alter_tablespace *ts_info)
+{
+ int error= HA_ADMIN_NOT_IMPLEMENTED;
+ handlerton *hton= ts_info->storage_engine;
+
+ DBUG_ENTER("mysql_alter_tablespace");
+ /*
+ If the user haven't defined an engine, this will fallback to using the
+ default storage engine.
+ */
+ if (hton == NULL || hton->state != SHOW_OPTION_YES)
+ {
+ hton= ha_default_handlerton(thd);
+ if (ts_info->storage_engine != 0)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_USING_OTHER_HANDLER,
+ ER(ER_WARN_USING_OTHER_HANDLER),
+ hton_name(hton)->str,
+ ts_info->tablespace_name ? ts_info->tablespace_name
+ : ts_info->logfile_group_name);
+ }
+
+ if (hton->alter_tablespace)
+ {
+ if ((error= hton->alter_tablespace(hton, thd, ts_info)))
+ {
+ if (error == 1)
+ {
+ DBUG_RETURN(1);
+ }
+
+ if (error == HA_ADMIN_NOT_IMPLEMENTED)
+ {
+ my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "");
+ }
+ else
+ {
+ my_error(error, MYF(0));
+ }
+ DBUG_RETURN(error);
+ }
+ }
+ else
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_ILLEGAL_HA_CREATE_OPTION,
+ ER(ER_ILLEGAL_HA_CREATE_OPTION),
+ hton_name(hton)->str,
+ "TABLESPACE or LOGFILE GROUP");
+ }
+ error= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
+ DBUG_RETURN(error);
+}
diff --git a/sql/sql_tablespace.h b/sql/sql_tablespace.h
new file mode 100644
index 00000000000..ae77d15cbcb
--- /dev/null
+++ b/sql/sql_tablespace.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_TABLESPACE_INCLUDED
+#define SQL_TABLESPACE_INCLUDED
+
+class THD;
+class st_alter_tablespace;
+
+int mysql_alter_tablespace(THD* thd, st_alter_tablespace *ts_info);
+
+#endif /* SQL_TABLESPACE_INCLUDED */
diff --git a/sql/sql_test.cc b/sql/sql_test.cc
new file mode 100644
index 00000000000..82abc861ec4
--- /dev/null
+++ b/sql/sql_test.cc
@@ -0,0 +1,647 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+/* Write some debug info */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_test.h"
+#include "sql_base.h"
+#include "sql_show.h" // calc_sum_of_all_status
+#include "sql_select.h"
+#include "keycaches.h"
+#include <hash.h>
+#include <thr_alarm.h>
+#if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_H)
+#include <malloc.h>
+#elif defined(HAVE_MALLINFO) && defined(HAVE_SYS_MALLOC_H)
+#include <sys/malloc.h>
+#endif
+
+#ifdef HAVE_EVENT_SCHEDULER
+#include "events.h"
+#endif
+
+static const char *lock_descriptions[] =
+{
+ /* TL_UNLOCK */ "No lock",
+ /* TL_READ_DEFAULT */ NULL,
+ /* TL_READ */ "Low priority read lock",
+ /* TL_READ_WITH_SHARED_LOCKS */ "Shared read lock",
+ /* TL_READ_HIGH_PRIORITY */ "High priority read lock",
+ /* TL_READ_NO_INSERT */ "Read lock without concurrent inserts",
+ /* TL_WRITE_ALLOW_WRITE */ "Write lock that allows other writers",
+ /* TL_WRITE_CONCURRENT_INSERT */ "Concurrent insert lock",
+ /* TL_WRITE_DELAYED */ "Lock used by delayed insert",
+ /* TL_WRITE_DEFAULT */ NULL,
+ /* TL_WRITE_LOW_PRIORITY */ "Low priority write lock",
+ /* TL_WRITE */ "High priority write lock",
+ /* TL_WRITE_ONLY */ "Highest priority write lock"
+};
+
+
+#ifndef DBUG_OFF
+
+void
+print_where(COND *cond,const char *info, enum_query_type query_type)
+{
+ char buff[1024];
+ String str(buff,(uint32) sizeof(buff), system_charset_info);
+ str.length(0);
+ str.extra_allocation(1024);
+ if (cond)
+ cond->print(&str, query_type);
+
+ DBUG_LOCK_FILE;
+ (void) fprintf(DBUG_FILE,"\nWHERE:(%s) %p ", info, cond);
+ (void) fputs(str.c_ptr_safe(),DBUG_FILE);
+ (void) fputc('\n',DBUG_FILE);
+ DBUG_UNLOCK_FILE;
+}
+
+ /* This is for debugging purposes */
+
+
+static void print_cached_tables(void)
+{
+ TABLE_SHARE *share;
+ TABLE *entry;
+ TDC_iterator tdc_it;
+
+ compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
+
+ /* purecov: begin tested */
+ puts("DB Table Version Thread Open Lock");
+
+ tdc_it.init();
+ while ((share= tdc_it.next()))
+ {
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
+ while ((entry= it++))
+ {
+ THD *in_use= entry->in_use;
+ printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
+ entry->s->db.str, entry->s->table_name.str, entry->s->tdc.version,
+ in_use ? in_use->thread_id : 0,
+ entry->db_stat ? 1 : 0,
+ in_use ? lock_descriptions[(int)entry->reginfo.lock_type] :
+ "Not in use");
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ }
+ tdc_it.deinit();
+ printf("\nCurrent refresh version: %ld\n", tdc_refresh_version());
+ fflush(stdout);
+ /* purecov: end */
+ return;
+}
+
+
+void TEST_filesort(SORT_FIELD *sortorder,uint s_length)
+{
+ char buff[256],buff2[256];
+ String str(buff,sizeof(buff),system_charset_info);
+ String out(buff2,sizeof(buff2),system_charset_info);
+ const char *sep;
+ DBUG_ENTER("TEST_filesort");
+
+ out.length(0);
+ for (sep=""; s_length-- ; sortorder++, sep=" ")
+ {
+ out.append(sep);
+ if (sortorder->reverse)
+ out.append('-');
+ if (sortorder->field)
+ {
+ if (sortorder->field->table_name)
+ {
+ out.append(*sortorder->field->table_name);
+ out.append('.');
+ }
+ out.append(sortorder->field->field_name ? sortorder->field->field_name:
+ "tmp_table_column");
+ }
+ else
+ {
+ str.length(0);
+ sortorder->item->print(&str, QT_ORDINARY);
+ out.append(str);
+ }
+ }
+ DBUG_LOCK_FILE;
+ (void) fputs("\nInfo about FILESORT\n",DBUG_FILE);
+ fprintf(DBUG_FILE,"Sortorder: %s\n",out.c_ptr_safe());
+ DBUG_UNLOCK_FILE;
+ DBUG_VOID_RETURN;
+}
+
+
+void
+TEST_join(JOIN *join)
+{
+ uint ref;
+ int i;
+ List_iterator<JOIN_TAB_RANGE> it(join->join_tab_ranges);
+ JOIN_TAB_RANGE *jt_range;
+ DBUG_ENTER("TEST_join");
+
+ DBUG_LOCK_FILE;
+ (void) fputs("\nInfo about JOIN\n",DBUG_FILE);
+ while ((jt_range= it++))
+ {
+ /*
+ Assemble results of all the calls to full_name() first,
+ in order not to garble the tabular output below.
+ */
+ String ref_key_parts[MAX_TABLES];
+ int tables_in_range= jt_range->end - jt_range->start;
+ for (i= 0; i < tables_in_range; i++)
+ {
+ JOIN_TAB *tab= jt_range->start + i;
+ for (ref= 0; ref < tab->ref.key_parts; ref++)
+ {
+ ref_key_parts[i].append(tab->ref.items[ref]->full_name());
+ ref_key_parts[i].append(" ");
+ }
+ }
+
+ for (i= 0; i < tables_in_range; i++)
+ {
+ JOIN_TAB *tab= jt_range->start + i;
+ TABLE *form=tab->table;
+ char key_map_buff[128];
+ fprintf(DBUG_FILE,"%-16.16s type: %-7s q_keys: %s refs: %d key: %d len: %d\n",
+ form->alias.c_ptr(),
+ join_type_str[tab->type],
+ tab->keys.print(key_map_buff),
+ tab->ref.key_parts,
+ tab->ref.key,
+ tab->ref.key_length);
+ if (tab->select)
+ {
+ char buf[MAX_KEY/8+1];
+ if (tab->use_quick == 2)
+ fprintf(DBUG_FILE,
+ " quick select checked for each record (keys: %s)\n",
+ tab->select->quick_keys.print(buf));
+ else if (tab->select->quick)
+ {
+ fprintf(DBUG_FILE, " quick select used:\n");
+ tab->select->quick->dbug_dump(18, FALSE);
+ }
+ else
+ (void)fputs(" select used\n",DBUG_FILE);
+ }
+ if (tab->ref.key_parts)
+ {
+ fprintf(DBUG_FILE,
+ " refs: %s\n", ref_key_parts[i].c_ptr_safe());
+ }
+ }
+ (void)fputs("\n",DBUG_FILE);
+ }
+ DBUG_UNLOCK_FILE;
+ DBUG_VOID_RETURN;
+}
+
+
+#define FT_KEYPART (MAX_FIELDS+10)
+
+void print_keyuse(KEYUSE *keyuse)
+{
+ char buff[256];
+ char buf2[64];
+ const char *fieldname;
+ JOIN_TAB *join_tab= keyuse->table->reginfo.join_tab;
+ KEY *key_info= join_tab->get_keyinfo_by_key_no(keyuse->key);
+ String str(buff,(uint32) sizeof(buff), system_charset_info);
+ str.length(0);
+ keyuse->val->print(&str, QT_ORDINARY);
+ str.append('\0');
+ if (keyuse->is_for_hash_join())
+ fieldname= keyuse->table->field[keyuse->keypart]->field_name;
+ else if (keyuse->keypart == FT_KEYPART)
+ fieldname= "FT_KEYPART";
+ else
+ fieldname= key_info->key_part[keyuse->keypart].field->field_name;
+ ll2str(keyuse->used_tables, buf2, 16, 0);
+ DBUG_LOCK_FILE;
+ fprintf(DBUG_FILE, "KEYUSE: %s.%s=%s optimize: %u used_tables: %s "
+ "ref_table_rows: %lu keypart_map: %0lx\n",
+ keyuse->table->alias.c_ptr(), fieldname, str.ptr(),
+ (uint) keyuse->optimize, buf2, (ulong) keyuse->ref_table_rows,
+ (ulong) keyuse->keypart_map);
+ DBUG_UNLOCK_FILE;
+ //key_part_map keypart_map; --?? there can be several?
+}
+
+
+/* purecov: begin inspected */
+void print_keyuse_array(DYNAMIC_ARRAY *keyuse_array)
+{
+ DBUG_LOCK_FILE;
+ fprintf(DBUG_FILE, "KEYUSE array (%d elements)\n", keyuse_array->elements);
+ DBUG_UNLOCK_FILE;
+ for(uint i=0; i < keyuse_array->elements; i++)
+ print_keyuse((KEYUSE*)dynamic_array_ptr(keyuse_array, i));
+}
+
+
+/*
+ Print the current state during query optimization.
+
+ SYNOPSIS
+ print_plan()
+ join pointer to the structure providing all context info for
+ the query
+ read_time the cost of the best partial plan
+ record_count estimate for the number of records returned by the best
+ partial plan
+ idx length of the partial QEP in 'join->positions';
+ also an index in the array 'join->best_ref';
+ info comment string to appear above the printout
+
+ DESCRIPTION
+ This function prints to the log file DBUG_FILE the members of 'join' that
+ are used during query optimization (join->positions, join->best_positions,
+ and join->best_ref) and few other related variables (read_time,
+ record_count).
+ Useful to trace query optimizer functions.
+
+ RETURN
+ None
+*/
+
+void
+print_plan(JOIN* join, uint idx, double record_count, double read_time,
+ double current_read_time, const char *info)
+{
+ uint i;
+ POSITION pos;
+ JOIN_TAB *join_table;
+ JOIN_TAB **plan_nodes;
+ TABLE* table;
+
+ if (info == 0)
+ info= "";
+
+ DBUG_LOCK_FILE;
+ if (join->best_read == DBL_MAX)
+ {
+ fprintf(DBUG_FILE,
+ "%s; idx: %u best: DBL_MAX atime: %g itime: %g count: %g\n",
+ info, idx, current_read_time, read_time, record_count);
+ }
+ else
+ {
+ fprintf(DBUG_FILE,
+ "%s; idx :%u best: %g accumulated: %g increment: %g count: %g\n",
+ info, idx, join->best_read, current_read_time, read_time,
+ record_count);
+ }
+
+ /* Print the tables in JOIN->positions */
+ fputs(" POSITIONS: ", DBUG_FILE);
+ for (i= 0; i < idx ; i++)
+ {
+ pos = join->positions[i];
+ table= pos.table->table;
+ if (table)
+ fputs(table->s->table_name.str, DBUG_FILE);
+ fputc(' ', DBUG_FILE);
+ }
+ fputc('\n', DBUG_FILE);
+
+ /*
+ Print the tables in JOIN->best_positions only if at least one complete plan
+ has been found. An indicator for this is the value of 'join->best_read'.
+ */
+ if (join->best_read < DBL_MAX)
+ {
+ fputs("BEST_POSITIONS: ", DBUG_FILE);
+ for (i= 0; i < idx ; i++)
+ {
+ pos= join->best_positions[i];
+ table= pos.table->table;
+ if (table)
+ fputs(table->s->table_name.str, DBUG_FILE);
+ fputc(' ', DBUG_FILE);
+ }
+ }
+ fputc('\n', DBUG_FILE);
+
+ /* Print the tables in JOIN->best_ref */
+ fputs(" BEST_REF: ", DBUG_FILE);
+ for (plan_nodes= join->best_ref ; *plan_nodes ; plan_nodes++)
+ {
+ join_table= (*plan_nodes);
+ fputs(join_table->table->s->table_name.str, DBUG_FILE);
+ fprintf(DBUG_FILE, "(%lu,%lu,%lu)",
+ (ulong) join_table->found_records,
+ (ulong) join_table->records,
+ (ulong) join_table->read_time);
+ fputc(' ', DBUG_FILE);
+ }
+ fputc('\n', DBUG_FILE);
+
+ DBUG_UNLOCK_FILE;
+}
+
+
+void print_sjm(SJ_MATERIALIZATION_INFO *sjm)
+{
+ DBUG_LOCK_FILE;
+ fprintf(DBUG_FILE, "\nsemi-join nest{\n");
+ fprintf(DBUG_FILE, " tables { \n");
+ for (uint i= 0;i < sjm->tables; i++)
+ {
+ fprintf(DBUG_FILE, " %s%s\n",
+ sjm->positions[i].table->table->alias.c_ptr(),
+ (i == sjm->tables -1)? "": ",");
+ }
+ fprintf(DBUG_FILE, " }\n");
+ fprintf(DBUG_FILE, " materialize_cost= %g\n",
+ sjm->materialization_cost.total_cost());
+ fprintf(DBUG_FILE, " rows= %g\n", sjm->rows);
+ fprintf(DBUG_FILE, "}\n");
+ DBUG_UNLOCK_FILE;
+}
+/* purecov: end */
+
+/*
+ Debugging help: force List<...>::elem function not be removed as unused.
+*/
+Item* (List<Item>:: *dbug_list_item_elem_ptr)(int)= &List<Item>::elem;
+Item_equal* (List<Item_equal>:: *dbug_list_item_equal_elem_ptr)(int)=
+ &List<Item_equal>::elem;
+TABLE_LIST* (List<TABLE_LIST>:: *dbug_list_table_list_elem_ptr)(int) =
+ &List<TABLE_LIST>::elem;
+
+#endif
+
+typedef struct st_debug_lock
+{
+ ulong thread_id;
+ char table_name[FN_REFLEN];
+ bool waiting;
+ const char *lock_text;
+ enum thr_lock_type type;
+} TABLE_LOCK_INFO;
+
+C_MODE_START
+static int dl_compare(const void *p1, const void *p2)
+{
+ TABLE_LOCK_INFO *a, *b;
+
+ a= (TABLE_LOCK_INFO *) p1;
+ b= (TABLE_LOCK_INFO *) p2;
+
+ if (a->thread_id > b->thread_id)
+ return 1;
+ if (a->thread_id < b->thread_id)
+ return -1;
+ if (a->waiting == b->waiting)
+ return 0;
+ else if (a->waiting)
+ return -1;
+ return 1;
+}
+C_MODE_END
+
+
+static void push_locks_into_array(DYNAMIC_ARRAY *ar, THR_LOCK_DATA *data,
+ bool wait, const char *text)
+{
+ if (data)
+ {
+ TABLE *table=(TABLE *)data->debug_print_param;
+ if (table && table->s->tmp_table == NO_TMP_TABLE)
+ {
+ TABLE_LOCK_INFO table_lock_info;
+ table_lock_info.thread_id= table->in_use->thread_id;
+ memcpy(table_lock_info.table_name, table->s->table_cache_key.str,
+ table->s->table_cache_key.length);
+ table_lock_info.table_name[strlen(table_lock_info.table_name)]='.';
+ table_lock_info.waiting=wait;
+ table_lock_info.lock_text=text;
+ // lock_type is also obtainable from THR_LOCK_DATA
+ table_lock_info.type=table->reginfo.lock_type;
+ (void) push_dynamic(ar,(uchar*) &table_lock_info);
+ }
+ }
+}
+
+
+/*
+ Regarding MERGE tables:
+
+ For now, the best option is to use the common TABLE *pointer for all
+ cases; The drawback is that for MERGE tables we will see many locks
+ for the merge tables even if some of them are for individual tables.
+
+ The way to solve this is to add to 'THR_LOCK' structure a pointer to
+ the filename and use this when printing the data.
+ (We can for now ignore this and just print the same name for all merge
+ table parts; Please add the above as a comment to the display_lock
+ function so that we can easily add this if we ever need this.
+*/
+
+static void display_table_locks(void)
+{
+ LIST *list;
+ void *saved_base;
+ DYNAMIC_ARRAY saved_table_locks;
+
+ (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO),
+ tc_records() + 20, 50, MYF(0));
+ mysql_mutex_lock(&THR_LOCK_lock);
+ for (list= thr_lock_thread_list; list; list= list_rest(list))
+ {
+ THR_LOCK *lock=(THR_LOCK*) list->data;
+
+ mysql_mutex_lock(&lock->mutex);
+ push_locks_into_array(&saved_table_locks, lock->write.data, FALSE,
+ "Locked - write");
+ push_locks_into_array(&saved_table_locks, lock->write_wait.data, TRUE,
+ "Waiting - write");
+ push_locks_into_array(&saved_table_locks, lock->read.data, FALSE,
+ "Locked - read");
+ push_locks_into_array(&saved_table_locks, lock->read_wait.data, TRUE,
+ "Waiting - read");
+ mysql_mutex_unlock(&lock->mutex);
+ }
+ mysql_mutex_unlock(&THR_LOCK_lock);
+
+ if (!saved_table_locks.elements)
+ goto end;
+
+ saved_base= dynamic_element(&saved_table_locks, 0, TABLE_LOCK_INFO *);
+ my_qsort(saved_base, saved_table_locks.elements, sizeof(TABLE_LOCK_INFO),
+ dl_compare);
+ freeze_size(&saved_table_locks);
+
+ puts("\nThread database.table_name Locked/Waiting Lock_type\n");
+
+ unsigned int i;
+ for (i=0 ; i < saved_table_locks.elements ; i++)
+ {
+ TABLE_LOCK_INFO *dl_ptr=dynamic_element(&saved_table_locks,i,TABLE_LOCK_INFO*);
+ printf("%-8ld%-28.28s%-22s%s\n",
+ dl_ptr->thread_id,dl_ptr->table_name,dl_ptr->lock_text,lock_descriptions[(int)dl_ptr->type]);
+ }
+ puts("\n\n");
+end:
+ delete_dynamic(&saved_table_locks);
+}
+
+C_MODE_START
+static int print_key_cache_status(const char *name, KEY_CACHE *key_cache,
+ void *unused __attribute__((unused)))
+{
+ char llbuff1[22];
+ char llbuff2[22];
+ char llbuff3[22];
+ char llbuff4[22];
+
+ if (!key_cache->key_cache_inited)
+ {
+ printf("%s: Not in use\n", name);
+ }
+ else
+ {
+ KEY_CACHE_STATISTICS stats;
+ get_key_cache_statistics(key_cache, 0, &stats);
+
+ printf("%s\n\
+Buffer_size: %10lu\n\
+Block_size: %10lu\n\
+Division_limit: %10lu\n\
+Age_threshold: %10lu\n\
+Partitions: %10lu\n\
+blocks used: %10lu\n\
+not flushed: %10lu\n\
+w_requests: %10s\n\
+writes: %10s\n\
+r_requests: %10s\n\
+reads: %10s\n\n",
+ name,
+ (ulong)key_cache->param_buff_size,
+ (ulong)key_cache->param_block_size,
+ (ulong)key_cache->param_division_limit,
+ (ulong)key_cache->param_age_threshold,
+ (ulong)key_cache->param_partitions,
+ (ulong)stats.blocks_used,
+ (ulong)stats.blocks_changed,
+ llstr(stats.write_requests,llbuff1),
+ llstr(stats.writes,llbuff2),
+ llstr(stats.read_requests,llbuff3),
+ llstr(stats.reads,llbuff4));
+ }
+ return 0;
+}
+C_MODE_END
+
+
+void mysql_print_status()
+{
+ char current_dir[FN_REFLEN];
+ STATUS_VAR tmp;
+
+ calc_sum_of_all_status(&tmp);
+ printf("\nStatus information:\n\n");
+ (void) my_getwd(current_dir, sizeof(current_dir),MYF(0));
+ printf("Current dir: %s\n", current_dir);
+ printf("Running threads: %d Stack size: %ld\n", thread_count,
+ (long) my_thread_stack_size);
+ thr_print_locks(); // Write some debug info
+#ifndef DBUG_OFF
+ print_cached_tables();
+#endif
+ /* Print key cache status */
+ puts("\nKey caches:");
+ process_key_caches(print_key_cache_status, 0);
+ printf("\nhandler status:\n\
+read_key: %10lu\n\
+read_next: %10lu\n\
+read_rnd %10lu\n\
+read_first: %10lu\n\
+write: %10lu\n\
+delete %10lu\n\
+update: %10lu\n",
+ tmp.ha_read_key_count,
+ tmp.ha_read_next_count,
+ tmp.ha_read_rnd_count,
+ tmp.ha_read_first_count,
+ tmp.ha_write_count,
+ tmp.ha_delete_count,
+ tmp.ha_update_count);
+ printf("\nTable status:\n\
+Opened tables: %10lu\n\
+Open tables: %10lu\n\
+Open files: %10lu\n\
+Open streams: %10lu\n",
+ tmp.opened_tables,
+ (ulong) tc_records(),
+ (ulong) my_file_opened,
+ (ulong) my_stream_opened);
+
+#ifndef DONT_USE_THR_ALARM
+ ALARM_INFO alarm_info;
+ thr_alarm_info(&alarm_info);
+ printf("\nAlarm status:\n\
+Active alarms: %u\n\
+Max used alarms: %u\n\
+Next alarm time: %lu\n",
+ alarm_info.active_alarms,
+ alarm_info.max_used_alarms,
+ (ulong)alarm_info.next_alarm_time);
+#endif
+ display_table_locks();
+#ifdef HAVE_MALLINFO
+ struct mallinfo info= mallinfo();
+ printf("\nMemory status:\n\
+Non-mmapped space allocated from system: %d\n\
+Number of free chunks: %d\n\
+Number of fastbin blocks: %d\n\
+Number of mmapped regions: %d\n\
+Space in mmapped regions: %d\n\
+Maximum total allocated space: %d\n\
+Space available in freed fastbin blocks: %d\n\
+Total allocated space: %d\n\
+Total free space: %d\n\
+Top-most, releasable space: %d\n\
+Estimated memory (with thread stack): %ld\n",
+ (int) info.arena ,
+ (int) info.ordblks,
+ (int) info.smblks,
+ (int) info.hblks,
+ (int) info.hblkhd,
+ (int) info.usmblks,
+ (int) info.fsmblks,
+ (int) info.uordblks,
+ (int) info.fordblks,
+ (int) info.keepcost,
+ (long) (thread_count * my_thread_stack_size + info.hblkhd + info.arena));
+#endif
+
+#ifdef HAVE_EVENT_SCHEDULER
+ Events::dump_internal_status();
+#endif
+ puts("");
+ fflush(stdout);
+}
diff --git a/sql/sql_test.h b/sql/sql_test.h
new file mode 100644
index 00000000000..3c1ee188eeb
--- /dev/null
+++ b/sql/sql_test.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_TEST_INCLUDED
+#define SQL_TEST_INCLUDED
+
+#include "mysqld.h"
+
+class JOIN;
+struct TABLE_LIST;
+typedef class Item COND;
+typedef class st_select_lex SELECT_LEX;
+typedef struct st_sort_field SORT_FIELD;
+
+#ifndef DBUG_OFF
+void print_where(COND *cond,const char *info, enum_query_type query_type);
+void TEST_filesort(SORT_FIELD *sortorder,uint s_length);
+void TEST_join(JOIN *join);
+void print_plan(JOIN* join,uint idx, double record_count, double read_time,
+ double current_read_time, const char *info);
+void print_keyuse_array(DYNAMIC_ARRAY *keyuse_array);
+void print_sjm(SJ_MATERIALIZATION_INFO *sjm);
+void dump_TABLE_LIST_graph(SELECT_LEX *select_lex, TABLE_LIST* tl);
+#endif
+void mysql_print_status();
+
+#endif /* SQL_TEST_INCLUDED */
diff --git a/sql/sql_time.cc b/sql/sql_time.cc
new file mode 100644
index 00000000000..ca689d55a2b
--- /dev/null
+++ b/sql/sql_time.cc
@@ -0,0 +1,1341 @@
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013 Monty Program Ab.
+
+ 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 */
+
+
+/* Functions to handle date and time */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED by other includes
+#include "sql_time.h"
+#include "tztime.h" // struct Time_zone
+#include "sql_class.h" // THD
+#include <m_ctype.h>
+
+
+#define MAX_DAY_NUMBER 3652424L
+
+ /* Some functions to calculate dates */
+
+/*
+ Name description of interval names used in statements.
+
+ 'interval_type_to_name' is ordered and sorted on interval size and
+ interval complexity.
+ Order of elements in 'interval_type_to_name' should correspond to
+ the order of elements in 'interval_type' enum
+
+ See also interval_type, interval_names
+*/
+
+LEX_STRING interval_type_to_name[INTERVAL_LAST] = {
+ { C_STRING_WITH_LEN("YEAR")},
+ { C_STRING_WITH_LEN("QUARTER")},
+ { C_STRING_WITH_LEN("MONTH")},
+ { C_STRING_WITH_LEN("WEEK")},
+ { C_STRING_WITH_LEN("DAY")},
+ { C_STRING_WITH_LEN("HOUR")},
+ { C_STRING_WITH_LEN("MINUTE")},
+ { C_STRING_WITH_LEN("SECOND")},
+ { C_STRING_WITH_LEN("MICROSECOND")},
+ { C_STRING_WITH_LEN("YEAR_MONTH")},
+ { C_STRING_WITH_LEN("DAY_HOUR")},
+ { C_STRING_WITH_LEN("DAY_MINUTE")},
+ { C_STRING_WITH_LEN("DAY_SECOND")},
+ { C_STRING_WITH_LEN("HOUR_MINUTE")},
+ { C_STRING_WITH_LEN("HOUR_SECOND")},
+ { C_STRING_WITH_LEN("MINUTE_SECOND")},
+ { C_STRING_WITH_LEN("DAY_MICROSECOND")},
+ { C_STRING_WITH_LEN("HOUR_MICROSECOND")},
+ { C_STRING_WITH_LEN("MINUTE_MICROSECOND")},
+ { C_STRING_WITH_LEN("SECOND_MICROSECOND")}
+};
+
+ /* Calc weekday from daynr */
+ /* Returns 0 for monday, 1 for tuesday .... */
+
+int calc_weekday(long daynr,bool sunday_first_day_of_week)
+{
+ DBUG_ENTER("calc_weekday");
+ DBUG_RETURN ((int) ((daynr + 5L + (sunday_first_day_of_week ? 1L : 0L)) % 7));
+}
+
+/*
+ The bits in week_format has the following meaning:
+ WEEK_MONDAY_FIRST (0) If not set Sunday is first day of week
+ If set Monday is first day of week
+ WEEK_YEAR (1) If not set Week is in range 0-53
+
+ Week 0 is returned for the the last week of the previous year (for
+ a date at start of january) In this case one can get 53 for the
+ first week of next year. This flag ensures that the week is
+ relevant for the given year. Note that this flag is only
+ releveant if WEEK_JANUARY is not set.
+
+ If set Week is in range 1-53.
+
+ In this case one may get week 53 for a date in January (when
+ the week is that last week of previous year) and week 1 for a
+ date in December.
+
+ WEEK_FIRST_WEEKDAY (2) If not set Weeks are numbered according
+ to ISO 8601:1988
+ If set The week that contains the first
+ 'first-day-of-week' is week 1.
+
+ ISO 8601:1988 means that if the week containing January 1 has
+ four or more days in the new year, then it is week 1;
+ Otherwise it is the last week of the previous year, and the
+ next week is week 1.
+*/
+
+uint calc_week(MYSQL_TIME *l_time, uint week_behaviour, uint *year)
+{
+ uint days;
+ ulong daynr=calc_daynr(l_time->year,l_time->month,l_time->day);
+ ulong first_daynr=calc_daynr(l_time->year,1,1);
+ bool monday_first= MY_TEST(week_behaviour & WEEK_MONDAY_FIRST);
+ bool week_year= MY_TEST(week_behaviour & WEEK_YEAR);
+ bool first_weekday= MY_TEST(week_behaviour & WEEK_FIRST_WEEKDAY);
+
+ uint weekday=calc_weekday(first_daynr, !monday_first);
+ *year=l_time->year;
+
+ if (l_time->month == 1 && l_time->day <= 7-weekday)
+ {
+ if (!week_year &&
+ ((first_weekday && weekday != 0) ||
+ (!first_weekday && weekday >= 4)))
+ return 0;
+ week_year= 1;
+ (*year)--;
+ first_daynr-= (days=calc_days_in_year(*year));
+ weekday= (weekday + 53*7- days) % 7;
+ }
+
+ if ((first_weekday && weekday != 0) ||
+ (!first_weekday && weekday >= 4))
+ days= daynr - (first_daynr+ (7-weekday));
+ else
+ days= daynr - (first_daynr - weekday);
+
+ if (week_year && days >= 52*7)
+ {
+ weekday= (weekday + calc_days_in_year(*year)) % 7;
+ if ((!first_weekday && weekday < 4) ||
+ (first_weekday && weekday == 0))
+ {
+ (*year)++;
+ return 1;
+ }
+ }
+ return days/7+1;
+}
+
+ /* Change a daynr to year, month and day */
+ /* Daynr 0 is returned as date 00.00.00 */
+
+bool get_date_from_daynr(long daynr,uint *ret_year,uint *ret_month,
+ uint *ret_day)
+{
+ uint year,temp,leap_day,day_of_year,days_in_year;
+ uchar *month_pos;
+ DBUG_ENTER("get_date_from_daynr");
+
+ if (daynr < 366 || daynr > MAX_DAY_NUMBER)
+ DBUG_RETURN(1);
+
+ year= (uint) (daynr*100 / 36525L);
+ temp=(((year-1)/100+1)*3)/4;
+ day_of_year=(uint) (daynr - (long) year * 365L) - (year-1)/4 +temp;
+ while (day_of_year > (days_in_year= calc_days_in_year(year)))
+ {
+ day_of_year-=days_in_year;
+ (year)++;
+ }
+ leap_day=0;
+ if (days_in_year == 366)
+ {
+ if (day_of_year > 31+28)
+ {
+ day_of_year--;
+ if (day_of_year == 31+28)
+ leap_day=1; /* Handle leapyears leapday */
+ }
+ }
+ *ret_month=1;
+ for (month_pos= days_in_month ;
+ day_of_year > (uint) *month_pos ;
+ day_of_year-= *(month_pos++), (*ret_month)++)
+ ;
+ *ret_year=year;
+ *ret_day=day_of_year+leap_day;
+ DBUG_RETURN(0);
+}
+
+ /* Functions to handle periods */
+
+ulong convert_period_to_month(ulong period)
+{
+ ulong a,b;
+ if (period == 0)
+ return 0L;
+ if ((a=period/100) < YY_PART_YEAR)
+ a+=2000;
+ else if (a < 100)
+ a+=1900;
+ b=period%100;
+ return a*12+b-1;
+}
+
+
+ulong convert_month_to_period(ulong month)
+{
+ ulong year;
+ if (month == 0L)
+ return 0L;
+ if ((year=month/12) < 100)
+ {
+ year+=(year < YY_PART_YEAR) ? 2000 : 1900;
+ }
+ return year*100+month%12+1;
+}
+
+
+bool
+check_date_with_warn(const MYSQL_TIME *ltime, ulonglong fuzzy_date,
+ timestamp_type ts_type)
+{
+ int unused;
+ if (check_date(ltime, fuzzy_date, &unused))
+ {
+ ErrConvTime str(ltime);
+ make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ &str, ts_type, 0);
+ return true;
+ }
+ return false;
+}
+
+
+bool
+adjust_time_range_with_warn(MYSQL_TIME *ltime, uint dec)
+{
+ MYSQL_TIME copy= *ltime;
+ ErrConvTime str(&copy);
+ int warnings= 0;
+ if (check_time_range(ltime, dec, &warnings))
+ return true;
+ if (warnings)
+ make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ &str, MYSQL_TIMESTAMP_TIME, NullS);
+ return false;
+}
+
+/*
+ Convert a string to 8-bit representation,
+ for use in str_to_time/str_to_date/str_to_date.
+
+ In the future to_ascii() can be extended to convert
+ non-ASCII digits to ASCII digits
+ (for example, ARABIC-INDIC, DEVANAGARI, BENGALI, and so on)
+ so DATE/TIME/DATETIME values understand digits in the
+ respected scripts.
+*/
+static uint
+to_ascii(CHARSET_INFO *cs,
+ const char *src, uint src_length,
+ char *dst, uint dst_length)
+
+{
+ int cnvres;
+ my_wc_t wc;
+ const char *srcend= src + src_length;
+ char *dst0= dst, *dstend= dst + dst_length - 1;
+ while (dst < dstend &&
+ (cnvres= (cs->cset->mb_wc)(cs, &wc,
+ (const uchar*) src,
+ (const uchar*) srcend)) > 0 &&
+ wc < 128)
+ {
+ src+= cnvres;
+ *dst++= static_cast<char>(wc);
+ }
+ *dst= '\0';
+ return dst - dst0;
+}
+
+
+/* Character set-aware version of str_to_time() */
+bool
+str_to_time(CHARSET_INFO *cs, const char *str,uint length,
+ MYSQL_TIME *l_time, ulonglong fuzzydate, MYSQL_TIME_STATUS *status)
+{
+ char cnv[32];
+ if ((cs->state & MY_CS_NONASCII) != 0)
+ {
+ length= to_ascii(cs, str, length, cnv, sizeof(cnv));
+ str= cnv;
+ }
+ return str_to_time(str, length, l_time, fuzzydate, status);
+}
+
+
+/* Character set-aware version of str_to_datetime() */
+bool str_to_datetime(CHARSET_INFO *cs, const char *str, uint length,
+ MYSQL_TIME *l_time, ulonglong flags,
+ MYSQL_TIME_STATUS *status)
+{
+ char cnv[32];
+ if ((cs->state & MY_CS_NONASCII) != 0)
+ {
+ length= to_ascii(cs, str, length, cnv, sizeof(cnv));
+ str= cnv;
+ }
+ return str_to_datetime(str, length, l_time, flags, status);
+}
+
+
+/*
+ Convert a timestamp string to a MYSQL_TIME value and produce a warning
+ if string was truncated during conversion.
+
+ NOTE
+ See description of str_to_datetime() for more information.
+*/
+
+bool
+str_to_datetime_with_warn(CHARSET_INFO *cs,
+ const char *str, uint length, MYSQL_TIME *l_time,
+ ulonglong flags)
+{
+ MYSQL_TIME_STATUS status;
+ THD *thd= current_thd;
+ bool ret_val= str_to_datetime(cs, str, length, l_time, flags, &status);
+ if (ret_val || status.warnings)
+ make_truncated_value_warning(thd,
+ ret_val ? Sql_condition::WARN_LEVEL_WARN :
+ Sql_condition::time_warn_level(status.warnings),
+ str, length, flags & TIME_TIME_ONLY ?
+ MYSQL_TIMESTAMP_TIME : l_time->time_type, NullS);
+ DBUG_EXECUTE_IF("str_to_datetime_warn",
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_YES, str););
+ return ret_val;
+}
+
+
+/**
+ converts a pair of numbers (integer part, microseconds) to MYSQL_TIME
+
+ @param neg sign of the time value
+ @param nr integer part of the number to convert
+ @param sec_part microsecond part of the number
+ @param ltime converted value will be written here
+ @param fuzzydate conversion flags (TIME_INVALID_DATE, etc)
+ @param str original number, as an ErrConv. For the warning
+ @param field_name field name or NULL if not a field. For the warning
+
+ @returns 0 for success, 1 for a failure
+*/
+static bool number_to_time_with_warn(bool neg, ulonglong nr, ulong sec_part,
+ MYSQL_TIME *ltime, ulonglong fuzzydate,
+ const ErrConv *str,
+ const char *field_name)
+{
+ int was_cut;
+ longlong res;
+ enum_field_types f_type;
+ bool have_warnings;
+
+ if (fuzzydate & TIME_TIME_ONLY)
+ {
+ fuzzydate= TIME_TIME_ONLY; // clear other flags
+ f_type= MYSQL_TYPE_TIME;
+ res= number_to_time(neg, nr, sec_part, ltime, &was_cut);
+ have_warnings= MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut);
+ }
+ else
+ {
+ f_type= MYSQL_TYPE_DATETIME;
+ if (neg)
+ {
+ res= -1;
+ }
+ else
+ {
+ res= number_to_datetime(nr, sec_part, ltime, fuzzydate, &was_cut);
+ have_warnings= was_cut && (fuzzydate & TIME_NO_ZERO_IN_DATE);
+ }
+ }
+
+ if (res < 0 || have_warnings)
+ {
+ make_truncated_value_warning(current_thd,
+ Sql_condition::WARN_LEVEL_WARN, str,
+ res < 0 ? MYSQL_TIMESTAMP_ERROR
+ : mysql_type_to_time_type(f_type),
+ field_name);
+ }
+ return res < 0;
+}
+
+
+bool double_to_datetime_with_warn(double value, MYSQL_TIME *ltime,
+ ulonglong fuzzydate, const char *field_name)
+{
+ const ErrConvDouble str(value);
+ bool neg= value < 0;
+
+ if (neg)
+ value= -value;
+
+ if (value > LONGLONG_MAX)
+ value= static_cast<double>(LONGLONG_MAX);
+
+ longlong nr= static_cast<ulonglong>(floor(value));
+ uint sec_part= static_cast<ulong>((value - floor(value))*TIME_SECOND_PART_FACTOR);
+ return number_to_time_with_warn(neg, nr, sec_part, ltime, fuzzydate, &str,
+ field_name);
+}
+
+
+bool decimal_to_datetime_with_warn(const my_decimal *value, MYSQL_TIME *ltime,
+ ulonglong fuzzydate, const char *field_name)
+{
+ const ErrConvDecimal str(value);
+ ulonglong nr;
+ ulong sec_part;
+ bool neg= my_decimal2seconds(value, &nr, &sec_part);
+ return number_to_time_with_warn(neg, nr, sec_part, ltime, fuzzydate, &str,
+ field_name);
+}
+
+
+bool int_to_datetime_with_warn(bool neg, ulonglong value, MYSQL_TIME *ltime,
+ ulonglong fuzzydate, const char *field_name)
+{
+ const ErrConvInteger str(neg ? -value : value, !neg);
+ return number_to_time_with_warn(neg, value, 0, ltime,
+ fuzzydate, &str, field_name);
+}
+
+
+/*
+ Convert a datetime from broken-down MYSQL_TIME representation to
+ corresponding TIMESTAMP value.
+
+ SYNOPSIS
+ TIME_to_timestamp()
+ thd - current thread
+ t - datetime in broken-down representation,
+ error_code - 0, if the conversion was successful;
+ ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
+ which is out of TIMESTAMP range;
+ ER_WARN_INVALID_TIMESTAMP, if t represents value which
+ doesn't exists (falls into the spring time-gap).
+
+ RETURN
+ Number seconds in UTC since start of Unix Epoch corresponding to t.
+ 0 - in case of ER_WARN_DATA_OUT_OF_RANGE
+*/
+
+my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, uint *error_code)
+{
+ thd->time_zone_used= 1;
+ return thd->variables.time_zone->TIME_to_gmt_sec(t, error_code);
+}
+
+
+/*
+ Convert a system time structure to TIME
+*/
+
+void localtime_to_TIME(MYSQL_TIME *to, struct tm *from)
+{
+ to->neg=0;
+ to->second_part=0;
+ to->year= (int) ((from->tm_year+1900) % 10000);
+ to->month= (int) from->tm_mon+1;
+ to->day= (int) from->tm_mday;
+ to->hour= (int) from->tm_hour;
+ to->minute= (int) from->tm_min;
+ to->second= (int) from->tm_sec;
+}
+
+void calc_time_from_sec(MYSQL_TIME *to, long seconds, long microseconds)
+{
+ long t_seconds;
+ // to->neg is not cleared, it may already be set to a useful value
+ to->time_type= MYSQL_TIMESTAMP_TIME;
+ to->year= 0;
+ to->month= 0;
+ to->day= 0;
+ to->hour= seconds/3600L;
+ t_seconds= seconds%3600L;
+ to->minute= t_seconds/60L;
+ to->second= t_seconds%60L;
+ to->second_part= microseconds;
+}
+
+
+/*
+ Parse a format string specification
+
+ SYNOPSIS
+ parse_date_time_format()
+ format_type Format of string (time, date or datetime)
+ format_str String to parse
+ format_length Length of string
+ date_time_format Format to fill in
+
+ NOTES
+ Fills in date_time_format->positions for all date time parts.
+
+ positions marks the position for a datetime element in the format string.
+ The position array elements are in the following order:
+ YYYY-DD-MM HH-MM-DD.FFFFFF AM
+ 0 1 2 3 4 5 6 7
+
+ If positions[0]= 5, it means that year will be the forth element to
+ read from the parsed date string.
+
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+bool parse_date_time_format(timestamp_type format_type,
+ const char *format, uint format_length,
+ DATE_TIME_FORMAT *date_time_format)
+{
+ uint offset= 0, separators= 0;
+ const char *ptr= format, *format_str;
+ const char *end= ptr+format_length;
+ uchar *dt_pos= date_time_format->positions;
+ /* need_p is set if we are using AM/PM format */
+ bool need_p= 0, allow_separator= 0;
+ ulong part_map= 0, separator_map= 0;
+ const char *parts[16];
+
+ date_time_format->time_separator= 0;
+ date_time_format->flag= 0; // For future
+
+ /*
+ Fill position with 'dummy' arguments to found out if a format tag is
+ used twice (This limit's the format to 255 characters, but this is ok)
+ */
+ dt_pos[0]= dt_pos[1]= dt_pos[2]= dt_pos[3]=
+ dt_pos[4]= dt_pos[5]= dt_pos[6]= dt_pos[7]= 255;
+
+ for (; ptr != end; ptr++)
+ {
+ if (*ptr == '%' && ptr+1 != end)
+ {
+ uint position;
+ LINT_INIT(position);
+ switch (*++ptr) {
+ case 'y': // Year
+ case 'Y':
+ position= 0;
+ break;
+ case 'c': // Month
+ case 'm':
+ position= 1;
+ break;
+ case 'd':
+ case 'e':
+ position= 2;
+ break;
+ case 'h':
+ case 'I':
+ case 'l':
+ need_p= 1; // Need AM/PM
+ /* Fall through */
+ case 'k':
+ case 'H':
+ position= 3;
+ break;
+ case 'i':
+ position= 4;
+ break;
+ case 's':
+ case 'S':
+ position= 5;
+ break;
+ case 'f':
+ position= 6;
+ if (dt_pos[5] != offset-1 || ptr[-2] != '.')
+ return 1; // Wrong usage of %f
+ break;
+ case 'p': // AM/PM
+ if (offset == 0) // Can't be first
+ return 0;
+ position= 7;
+ break;
+ default:
+ return 1; // Unknown controll char
+ }
+ if (dt_pos[position] != 255) // Don't allow same tag twice
+ return 1;
+ parts[position]= ptr-1;
+
+ /*
+ If switching from time to date, ensure that all time parts
+ are used
+ */
+ if (part_map && position <= 2 && !(part_map & (1 | 2 | 4)))
+ offset=5;
+ part_map|= (ulong) 1 << position;
+ dt_pos[position]= offset++;
+ allow_separator= 1;
+ }
+ else
+ {
+ /*
+ Don't allow any characters in format as this could easily confuse
+ the date reader
+ */
+ if (!allow_separator)
+ return 1; // No separator here
+ allow_separator= 0; // Don't allow two separators
+ separators++;
+ /* Store in separator_map which parts are punct characters */
+ if (my_ispunct(&my_charset_latin1, *ptr))
+ separator_map|= (ulong) 1 << (offset-1);
+ else if (!my_isspace(&my_charset_latin1, *ptr))
+ return 1;
+ }
+ }
+
+ /* If no %f, specify it after seconds. Move %p up, if necessary */
+ if ((part_map & 32) && !(part_map & 64))
+ {
+ dt_pos[6]= dt_pos[5] +1;
+ parts[6]= parts[5]; // For later test in (need_p)
+ if (dt_pos[6] == dt_pos[7]) // Move %p one step up if used
+ dt_pos[7]++;
+ }
+
+ /*
+ Check that we have not used a non legal format specifier and that all
+ format specifiers have been used
+
+ The last test is to ensure that %p is used if and only if
+ it's needed.
+ */
+ if ((format_type == MYSQL_TIMESTAMP_DATETIME &&
+ !test_all_bits(part_map, (1 | 2 | 4 | 8 | 16 | 32))) ||
+ (format_type == MYSQL_TIMESTAMP_DATE && part_map != (1 | 2 | 4)) ||
+ (format_type == MYSQL_TIMESTAMP_TIME &&
+ !test_all_bits(part_map, 8 | 16 | 32)) ||
+ !allow_separator || // %option should be last
+ (need_p && dt_pos[6] +1 != dt_pos[7]) ||
+ (need_p ^ (dt_pos[7] != 255)))
+ return 1;
+
+ if (dt_pos[6] != 255) // If fractional seconds
+ {
+ /* remove fractional seconds from later tests */
+ uint pos= dt_pos[6] -1;
+ /* Remove separator before %f from sep map */
+ separator_map= ((separator_map & ((ulong) (1 << pos)-1)) |
+ ((separator_map & ~((ulong) (1 << pos)-1)) >> 1));
+ if (part_map & 64)
+ {
+ separators--; // There is always a separator
+ need_p= 1; // force use of separators
+ }
+ }
+
+ /*
+ Remove possible separator before %p from sep_map
+ (This can either be at position 3, 4, 6 or 7) h.m.d.%f %p
+ */
+ if (dt_pos[7] != 255)
+ {
+ if (need_p && parts[7] != parts[6]+2)
+ separators--;
+ }
+ /*
+ Calculate if %p is in first or last part of the datetime field
+
+ At this point we have either %H-%i-%s %p 'year parts' or
+ 'year parts' &H-%i-%s %p" as %f was removed above
+ */
+ offset= dt_pos[6] <= 3 ? 3 : 6;
+ /* Remove separator before %p from sep map */
+ separator_map= ((separator_map & ((ulong) (1 << offset)-1)) |
+ ((separator_map & ~((ulong) (1 << offset)-1)) >> 1));
+
+ format_str= 0;
+ switch (format_type) {
+ case MYSQL_TIMESTAMP_DATE:
+ format_str= known_date_time_formats[INTERNAL_FORMAT].date_format;
+ /* fall through */
+ case MYSQL_TIMESTAMP_TIME:
+ if (!format_str)
+ format_str=known_date_time_formats[INTERNAL_FORMAT].time_format;
+
+ /*
+ If there is no separators, allow the internal format as we can read
+ this. If separators are used, they must be between each part
+ */
+ if (format_length == 6 && !need_p &&
+ !my_strnncoll(&my_charset_bin,
+ (const uchar *) format, 6,
+ (const uchar *) format_str, 6))
+ return 0;
+ if (separator_map == (1 | 2))
+ {
+ if (format_type == MYSQL_TIMESTAMP_TIME)
+ {
+ if (*(format+2) != *(format+5))
+ break; // Error
+ /* Store the character used for time formats */
+ date_time_format->time_separator= *(format+2);
+ }
+ return 0;
+ }
+ break;
+ case MYSQL_TIMESTAMP_DATETIME:
+ /*
+ If there is no separators, allow the internal format as we can read
+ this. If separators are used, they must be between each part.
+ Between DATE and TIME we also allow space as separator
+ */
+ if ((format_length == 12 && !need_p &&
+ !my_strnncoll(&my_charset_bin,
+ (const uchar *) format, 12,
+ (const uchar*) known_date_time_formats[INTERNAL_FORMAT].datetime_format,
+ 12)) ||
+ (separators == 5 && separator_map == (1 | 2 | 8 | 16)))
+ return 0;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ return 1; // Error
+}
+
+
+/*
+ Create a DATE_TIME_FORMAT object from a format string specification
+
+ SYNOPSIS
+ date_time_format_make()
+ format_type Format to parse (time, date or datetime)
+ format_str String to parse
+ format_length Length of string
+
+ NOTES
+ The returned object should be freed with my_free()
+
+ RETURN
+ NULL ponter: Error
+ new object
+*/
+
+DATE_TIME_FORMAT
+*date_time_format_make(timestamp_type format_type,
+ const char *format_str, uint format_length)
+{
+ DATE_TIME_FORMAT tmp;
+
+ if (format_length && format_length < 255 &&
+ !parse_date_time_format(format_type, format_str,
+ format_length, &tmp))
+ {
+ tmp.format.str= (char*) format_str;
+ tmp.format.length= format_length;
+ return date_time_format_copy((THD *)0, &tmp);
+ }
+ return 0;
+}
+
+
+/*
+ Create a copy of a DATE_TIME_FORMAT object
+
+ SYNOPSIS
+ date_and_time_format_copy()
+ thd Set if variable should be allocated in thread mem
+ format format to copy
+
+ NOTES
+ The returned object should be freed with my_free()
+
+ RETURN
+ NULL ponter: Error
+ new object
+*/
+
+DATE_TIME_FORMAT *date_time_format_copy(THD *thd, DATE_TIME_FORMAT *format)
+{
+ DATE_TIME_FORMAT *new_format;
+ ulong length= sizeof(*format) + format->format.length + 1;
+
+ if (thd)
+ new_format= (DATE_TIME_FORMAT *) thd->alloc(length);
+ else
+ new_format= (DATE_TIME_FORMAT *) my_malloc(length, MYF(MY_WME));
+ if (new_format)
+ {
+ /* Put format string after current pos */
+ new_format->format.str= (char*) (new_format+1);
+ memcpy((char*) new_format->positions, (char*) format->positions,
+ sizeof(format->positions));
+ new_format->time_separator= format->time_separator;
+ /* We make the string null terminated for easy printf in SHOW VARIABLES */
+ memcpy((char*) new_format->format.str, format->format.str,
+ format->format.length);
+ new_format->format.str[format->format.length]= 0;
+ new_format->format.length= format->format.length;
+ }
+ return new_format;
+}
+
+
+KNOWN_DATE_TIME_FORMAT known_date_time_formats[6]=
+{
+ {"USA", "%m.%d.%Y", "%Y-%m-%d %H.%i.%s", "%h:%i:%s %p" },
+ {"JIS", "%Y-%m-%d", "%Y-%m-%d %H:%i:%s", "%H:%i:%s" },
+ {"ISO", "%Y-%m-%d", "%Y-%m-%d %H:%i:%s", "%H:%i:%s" },
+ {"EUR", "%d.%m.%Y", "%Y-%m-%d %H.%i.%s", "%H.%i.%s" },
+ {"INTERNAL", "%Y%m%d", "%Y%m%d%H%i%s", "%H%i%s" },
+ { 0, 0, 0, 0 }
+};
+
+
+const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format,
+ timestamp_type type)
+{
+ switch (type) {
+ case MYSQL_TIMESTAMP_DATE:
+ return format->date_format;
+ case MYSQL_TIMESTAMP_DATETIME:
+ return format->datetime_format;
+ case MYSQL_TIMESTAMP_TIME:
+ return format->time_format;
+ default:
+ DBUG_ASSERT(0); // Impossible
+ return 0;
+ }
+}
+
+
+/**
+ Convert TIME/DATE/DATETIME value to String.
+ @param l_time DATE value
+ @param OUT str String to convert to
+ @param dec Number of fractional digits.
+*/
+bool my_TIME_to_str(const MYSQL_TIME *ltime, String *str, uint dec)
+{
+ if (str->alloc(MAX_DATE_STRING_REP_LENGTH))
+ return true;
+ str->set_charset(&my_charset_numeric);
+ str->length(my_TIME_to_str(ltime, const_cast<char*>(str->ptr()), dec));
+ return false;
+}
+
+
+void make_truncated_value_warning(THD *thd,
+ Sql_condition::enum_warning_level level,
+ const ErrConv *sval,
+ timestamp_type time_type,
+ const char *field_name)
+{
+ char warn_buff[MYSQL_ERRMSG_SIZE];
+ const char *type_str;
+ CHARSET_INFO *cs= &my_charset_latin1;
+
+ switch (time_type) {
+ case MYSQL_TIMESTAMP_DATE:
+ type_str= "date";
+ break;
+ case MYSQL_TIMESTAMP_TIME:
+ type_str= "time";
+ break;
+ case MYSQL_TIMESTAMP_DATETIME: // FALLTHROUGH
+ default:
+ type_str= "datetime";
+ break;
+ }
+ if (field_name)
+ cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff),
+ ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
+ type_str, sval->ptr(), field_name,
+ (ulong) thd->get_stmt_da()->current_row_for_warning());
+ else
+ {
+ if (time_type > MYSQL_TIMESTAMP_ERROR)
+ cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff),
+ ER(ER_TRUNCATED_WRONG_VALUE),
+ type_str, sval->ptr());
+ else
+ cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff),
+ ER(ER_WRONG_VALUE), type_str, sval->ptr());
+ }
+ push_warning(thd, level,
+ ER_TRUNCATED_WRONG_VALUE, warn_buff);
+}
+
+
+/* Daynumber from year 0 to 9999-12-31 */
+#define COMBINE(X) \
+ (((((X)->day * 24LL + (X)->hour) * 60LL + \
+ (X)->minute) * 60LL + (X)->second)*1000000LL + \
+ (X)->second_part)
+#define GET_PART(X, N) X % N ## LL; X/= N ## LL
+
+bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type,
+ INTERVAL interval)
+{
+ long period, sign;
+
+ sign= (interval.neg == ltime->neg ? 1 : -1);
+
+ switch (int_type) {
+ case INTERVAL_SECOND:
+ case INTERVAL_SECOND_MICROSECOND:
+ case INTERVAL_MICROSECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_HOUR:
+ case INTERVAL_MINUTE_MICROSECOND:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_HOUR_MICROSECOND:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_DAY_MICROSECOND:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY:
+ {
+ longlong usec, daynr;
+ my_bool neg= 0;
+ enum enum_mysql_timestamp_type time_type= ltime->time_type;
+
+ if ((ulong) interval.day > MAX_DAY_NUMBER)
+ goto invalid_date;
+
+ if (time_type != MYSQL_TIMESTAMP_TIME)
+ ltime->day+= calc_daynr(ltime->year, ltime->month, 1) - 1;
+
+ usec= COMBINE(ltime) + sign*COMBINE(&interval);
+
+ if (usec < 0)
+ {
+ neg= 1;
+ usec= -usec;
+ }
+
+ ltime->second_part= GET_PART(usec, 1000000);
+ ltime->second= GET_PART(usec, 60);
+ ltime->minute= GET_PART(usec, 60);
+ ltime->neg^= neg;
+
+ if (time_type == MYSQL_TIMESTAMP_TIME)
+ {
+ if (usec > TIME_MAX_HOUR)
+ goto invalid_date;
+ ltime->hour= static_cast<uint>(usec);
+ ltime->day= 0;
+ return 0;
+ }
+
+ if (int_type != INTERVAL_DAY)
+ ltime->time_type= MYSQL_TIMESTAMP_DATETIME; // Return full date
+
+ ltime->hour= GET_PART(usec, 24);
+ daynr= usec;
+
+ /* Day number from year 0 to 9999-12-31 */
+ if (get_date_from_daynr((long) daynr, &ltime->year, &ltime->month,
+ &ltime->day))
+ goto invalid_date;
+ break;
+ }
+ case INTERVAL_WEEK:
+ period= (calc_daynr(ltime->year,ltime->month,ltime->day) +
+ sign * (long) interval.day);
+ /* Daynumber from year 0 to 9999-12-31 */
+ if (get_date_from_daynr((long) period,&ltime->year,&ltime->month,
+ &ltime->day))
+ goto invalid_date;
+ break;
+ case INTERVAL_YEAR:
+ ltime->year+= sign * (long) interval.year;
+ if ((ulong) ltime->year >= 10000L)
+ goto invalid_date;
+ if (ltime->month == 2 && ltime->day == 29 &&
+ calc_days_in_year(ltime->year) != 366)
+ ltime->day=28; // Was leap-year
+ break;
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_QUARTER:
+ case INTERVAL_MONTH:
+ period= (ltime->year*12 + sign * (long) interval.year*12 +
+ ltime->month-1 + sign * (long) interval.month);
+ if ((ulong) period >= 120000L)
+ goto invalid_date;
+ ltime->year= (uint) (period / 12);
+ ltime->month= (uint) (period % 12L)+1;
+ /* Adjust day if the new month doesn't have enough days */
+ if (ltime->day > days_in_month[ltime->month-1])
+ {
+ ltime->day = days_in_month[ltime->month-1];
+ if (ltime->month == 2 && calc_days_in_year(ltime->year) == 366)
+ ltime->day++; // Leap-year
+ }
+ break;
+ default:
+ goto null_date;
+ }
+
+ if (ltime->time_type != MYSQL_TIMESTAMP_TIME)
+ return 0; // Ok
+
+invalid_date:
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_DATETIME_FUNCTION_OVERFLOW,
+ ER(ER_DATETIME_FUNCTION_OVERFLOW),
+ ltime->time_type == MYSQL_TIMESTAMP_TIME ?
+ "time" : "datetime");
+null_date:
+ return 1;
+}
+
+
+/*
+ Calculate difference between two datetime values as seconds + microseconds.
+
+ SYNOPSIS
+ calc_time_diff()
+ l_time1 - TIME/DATE/DATETIME value
+ l_time2 - TIME/DATE/DATETIME value
+ l_sign - 1 absolute values are substracted,
+ -1 absolute values are added.
+ seconds_out - Out parameter where difference between
+ l_time1 and l_time2 in seconds is stored.
+ microseconds_out- Out parameter where microsecond part of difference
+ between l_time1 and l_time2 is stored.
+
+ NOTE
+ This function calculates difference between l_time1 and l_time2 absolute
+ values. So one should set l_sign and correct result if he want to take
+ signs into account (i.e. for MYSQL_TIME values).
+
+ RETURN VALUES
+ Returns sign of difference.
+ 1 means negative result
+ 0 means positive result
+
+*/
+
+bool
+calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2,
+ int l_sign, longlong *seconds_out, long *microseconds_out)
+{
+ long days;
+ bool neg;
+ longlong microseconds;
+
+ /*
+ We suppose that if first argument is MYSQL_TIMESTAMP_TIME
+ the second argument should be TIMESTAMP_TIME also.
+ We should check it before calc_time_diff call.
+ */
+ if (l_time1->time_type == MYSQL_TIMESTAMP_TIME) // Time value
+ days= (long)l_time1->day - l_sign * (long)l_time2->day;
+ else
+ {
+ days= calc_daynr((uint) l_time1->year,
+ (uint) l_time1->month,
+ (uint) l_time1->day);
+ if (l_time2->time_type == MYSQL_TIMESTAMP_TIME)
+ days-= l_sign * (long)l_time2->day;
+ else
+ days-= l_sign*calc_daynr((uint) l_time2->year,
+ (uint) l_time2->month,
+ (uint) l_time2->day);
+ }
+
+ microseconds= ((longlong)days * SECONDS_IN_24H +
+ (longlong)(l_time1->hour*3600L +
+ l_time1->minute*60L +
+ l_time1->second) -
+ l_sign*(longlong)(l_time2->hour*3600L +
+ l_time2->minute*60L +
+ l_time2->second)) * 1000000LL +
+ (longlong)l_time1->second_part -
+ l_sign*(longlong)l_time2->second_part;
+
+ neg= 0;
+ if (microseconds < 0)
+ {
+ microseconds= -microseconds;
+ neg= 1;
+ }
+ *seconds_out= microseconds/1000000L;
+ *microseconds_out= (long) (microseconds%1000000L);
+ return neg;
+}
+
+
+/*
+ Compares 2 MYSQL_TIME structures
+
+ SYNOPSIS
+ my_time_compare()
+
+ a - first time
+ b - second time
+
+ RETURN VALUE
+ -1 - a < b
+ 0 - a == b
+ 1 - a > b
+
+*/
+
+int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b)
+{
+ ulonglong a_t= pack_time(a);
+ ulonglong b_t= pack_time(b);
+
+ if (a_t < b_t)
+ return -1;
+ if (a_t > b_t)
+ return 1;
+
+ return 0;
+}
+
+
+/**
+ Convert TIME to DATETIME.
+ @param ltime The value to convert.
+ @return false on success, true of error (negative time).
+*/
+bool time_to_datetime(MYSQL_TIME *ltime)
+{
+ DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_TIME);
+ DBUG_ASSERT(ltime->year == 0);
+ DBUG_ASSERT(ltime->month == 0);
+ DBUG_ASSERT(ltime->day == 0);
+ if (ltime->neg)
+ return true;
+ uint day= ltime->hour / 24;
+ ltime->hour%= 24;
+ ltime->month= day / 31;
+ ltime->day= day % 31;
+ return false;
+}
+
+
+/**
+ Return a valid DATE or DATETIME value from an arbitrary MYSQL_TIME.
+ If ltime is TIME, it's first converted to DATETIME.
+ If ts_type is DATE, hhmmss is set to zero.
+ The date part of the result is checked against fuzzy_date.
+
+ @param ltime The value to convert.
+ @param fuzzy_date Flags to check date.
+ @param ts_type The type to convert to.
+ @return false on success, true of error (negative time).*/
+bool
+make_date_with_warn(MYSQL_TIME *ltime, ulonglong fuzzy_date,
+ timestamp_type ts_type)
+{
+ DBUG_ASSERT(ts_type == MYSQL_TIMESTAMP_DATE ||
+ ts_type == MYSQL_TIMESTAMP_DATETIME);
+ if (ltime->time_type == MYSQL_TIMESTAMP_TIME && time_to_datetime(ltime))
+ {
+ /* e.g. negative time */
+ ErrConvTime str(ltime);
+ make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ &str, ts_type, 0);
+ return true;
+ }
+ if ((ltime->time_type= ts_type) == MYSQL_TIMESTAMP_DATE)
+ ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0;
+ return check_date_with_warn(ltime, fuzzy_date, ts_type);
+}
+
+
+/*
+ Convert a TIME value to DAY-TIME interval, e.g. for extraction:
+ EXTRACT(DAY FROM x), EXTRACT(HOUR FROM x), etc.
+ Moves full days from ltime->hour to ltime->day.
+ Note, time_type is set to MYSQL_TIMESTAMP_NONE, to make sure that
+ the structure is not used for anything else other than extraction:
+ non-extraction TIME functions expect zero day value!
+*/
+void time_to_daytime_interval(MYSQL_TIME *ltime)
+{
+ DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_TIME);
+ DBUG_ASSERT(ltime->year == 0);
+ DBUG_ASSERT(ltime->month == 0);
+ DBUG_ASSERT(ltime->day == 0);
+ ltime->day= ltime->hour / 24;
+ ltime->hour%= 24;
+ ltime->time_type= MYSQL_TIMESTAMP_NONE;
+}
+
+
+/*** Conversion from TIME to DATETIME ***/
+
+/*
+ Simple case: TIME is within normal 24 hours internal.
+ Mix DATE part of ldate and TIME part of ltime together.
+*/
+static void
+mix_date_and_time_simple(MYSQL_TIME *ldate, const MYSQL_TIME *ltime)
+{
+ DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE ||
+ ldate->time_type == MYSQL_TIMESTAMP_DATETIME);
+ ldate->hour= ltime->hour;
+ ldate->minute= ltime->minute;
+ ldate->second= ltime->second;
+ ldate->second_part= ltime->second_part;
+ ldate->time_type= MYSQL_TIMESTAMP_DATETIME;
+}
+
+
+/*
+ Complex case: TIME is negative or outside of the 24 hour interval.
+*/
+static void
+mix_date_and_time_complex(MYSQL_TIME *ldate, const MYSQL_TIME *ltime)
+{
+ DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE ||
+ ldate->time_type == MYSQL_TIMESTAMP_DATETIME);
+ longlong seconds;
+ long days, useconds;
+ int sign= ltime->neg ? 1 : -1;
+ ldate->neg= calc_time_diff(ldate, ltime, sign, &seconds, &useconds);
+
+ DBUG_ASSERT(!ldate->neg);
+ DBUG_ASSERT(ldate->year > 0);
+
+ days= (long) (seconds / SECONDS_IN_24H);
+ calc_time_from_sec(ldate, seconds % SECONDS_IN_24H, useconds);
+ get_date_from_daynr(days, &ldate->year, &ldate->month, &ldate->day);
+ ldate->time_type= MYSQL_TIMESTAMP_DATETIME;
+}
+
+
+/**
+ Mix a date value and a time value.
+
+ @param IN/OUT ldate Date value.
+ @param ltime Time value.
+*/
+static void
+mix_date_and_time(MYSQL_TIME *to, const MYSQL_TIME *from)
+{
+ if (!from->neg && from->hour < 24)
+ mix_date_and_time_simple(to, from);
+ else
+ mix_date_and_time_complex(to, from);
+}
+
+
+/**
+ Get current date in DATE format
+*/
+void set_current_date(THD *thd, MYSQL_TIME *to)
+{
+ thd->variables.time_zone->gmt_sec_to_TIME(to, thd->query_start());
+ thd->time_zone_used= 1;
+ datetime_to_date(to);
+}
+
+
+/**
+ 5.5 compatible conversion from TIME to DATETIME
+*/
+static bool
+time_to_datetime_old(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to)
+{
+ DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME);
+
+ if (from->neg)
+ return true;
+
+ /* Set the date part */
+ uint day= from->hour / 24;
+ to->day= day % 31;
+ to->month= day / 31;
+ to->year= 0;
+ /* Set the time part */
+ to->hour= from->hour % 24;
+ to->minute= from->minute;
+ to->second= from->second;
+ to->second_part= from->second_part;
+ /* set sign and type */
+ to->neg= 0;
+ to->time_type= MYSQL_TIMESTAMP_DATETIME;
+ return false;
+}
+
+
+/**
+ Convert time to datetime.
+
+ The time value is added to the current datetime value.
+ @param IN ltime Time value to convert from.
+ @param OUT ltime2 Datetime value to convert to.
+*/
+bool
+time_to_datetime(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to)
+{
+ if (thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST)
+ return time_to_datetime_old(thd, from, to);
+ set_current_date(thd, to);
+ mix_date_and_time(to, from);
+ return false;
+}
+
+
+bool
+time_to_datetime_with_warn(THD *thd,
+ const MYSQL_TIME *from, MYSQL_TIME *to,
+ ulonglong fuzzydate)
+{
+ int warn= 0;
+ DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME);
+ /*
+ After time_to_datetime() we need to do check_date(), as
+ the caller may want TIME_NO_ZERO_DATE or TIME_NO_ZERO_IN_DATE.
+ Note, the SQL standard time->datetime conversion mode always returns
+ a valid date based on CURRENT_DATE. So we need to do check_date()
+ only in the old mode.
+ */
+ if (time_to_datetime(thd, from, to) ||
+ ((thd->variables.old_behavior && OLD_MODE_ZERO_DATE_TIME_CAST) &&
+ check_date(to, fuzzydate, &warn)))
+ {
+ ErrConvTime str(from);
+ make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ &str, MYSQL_TIMESTAMP_DATETIME, 0);
+ return true;
+ }
+ return false;
+}
diff --git a/sql/sql_time.h b/sql/sql_time.h
new file mode 100644
index 00000000000..dc8e4668e1e
--- /dev/null
+++ b/sql/sql_time.h
@@ -0,0 +1,163 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_TIME_INCLUDED
+#define SQL_TIME_INCLUDED
+
+#include "my_global.h" /* ulong */
+#include "my_time.h"
+#include "mysql_time.h" /* timestamp_type */
+#include "sql_error.h" /* Sql_condition */
+#include "structs.h" /* INTERVAL */
+
+typedef enum enum_mysql_timestamp_type timestamp_type;
+typedef struct st_date_time_format DATE_TIME_FORMAT;
+typedef struct st_known_date_time_format KNOWN_DATE_TIME_FORMAT;
+
+/* Flags for calc_week() function. */
+#define WEEK_MONDAY_FIRST 1
+#define WEEK_YEAR 2
+#define WEEK_FIRST_WEEKDAY 4
+
+ulong convert_period_to_month(ulong period);
+ulong convert_month_to_period(ulong month);
+void set_current_date(THD *thd, MYSQL_TIME *to);
+bool time_to_datetime(MYSQL_TIME *ltime);
+void time_to_daytime_interval(MYSQL_TIME *l_time);
+bool get_date_from_daynr(long daynr,uint *year, uint *month, uint *day);
+my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, uint *error_code);
+bool str_to_datetime_with_warn(CHARSET_INFO *cs, const char *str,
+ uint length, MYSQL_TIME *l_time,
+ ulonglong flags);
+bool double_to_datetime_with_warn(double value, MYSQL_TIME *ltime,
+ ulonglong fuzzydate,
+ const char *name);
+bool decimal_to_datetime_with_warn(const my_decimal *value, MYSQL_TIME *ltime,
+ ulonglong fuzzydate,
+ const char *name);
+bool int_to_datetime_with_warn(bool neg, ulonglong value, MYSQL_TIME *ltime,
+ ulonglong fuzzydate,
+ const char *name);
+
+bool time_to_datetime(THD *thd, const MYSQL_TIME *tm, MYSQL_TIME *dt);
+bool time_to_datetime_with_warn(THD *thd,
+ const MYSQL_TIME *tm, MYSQL_TIME *dt,
+ ulonglong fuzzydate);
+inline void datetime_to_time(MYSQL_TIME *ltime)
+{
+ DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATE ||
+ ltime->time_type == MYSQL_TIMESTAMP_DATETIME);
+ DBUG_ASSERT(ltime->neg == 0);
+ ltime->year= ltime->month= ltime->day= 0;
+ ltime->time_type= MYSQL_TIMESTAMP_TIME;
+}
+inline void datetime_to_date(MYSQL_TIME *ltime)
+{
+ DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATE ||
+ ltime->time_type == MYSQL_TIMESTAMP_DATETIME);
+ DBUG_ASSERT(ltime->neg == 0);
+ ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0;
+ ltime->time_type= MYSQL_TIMESTAMP_DATE;
+}
+inline void date_to_datetime(MYSQL_TIME *ltime)
+{
+ DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATE ||
+ ltime->time_type == MYSQL_TIMESTAMP_DATETIME);
+ DBUG_ASSERT(ltime->neg == 0);
+ ltime->time_type= MYSQL_TIMESTAMP_DATETIME;
+}
+void make_truncated_value_warning(THD *thd,
+ Sql_condition::enum_warning_level level,
+ const ErrConv *str_val,
+ timestamp_type time_type,
+ const char *field_name);
+
+static inline void make_truncated_value_warning(THD *thd,
+ Sql_condition::enum_warning_level level, const char *str_val,
+ uint str_length, timestamp_type time_type,
+ const char *field_name)
+{
+ const ErrConvString str(str_val, str_length, &my_charset_bin);
+ make_truncated_value_warning(thd, level, &str, time_type, field_name);
+}
+
+extern DATE_TIME_FORMAT *date_time_format_make(timestamp_type format_type,
+ const char *format_str,
+ uint format_length);
+extern DATE_TIME_FORMAT *date_time_format_copy(THD *thd,
+ DATE_TIME_FORMAT *format);
+const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format,
+ timestamp_type type);
+bool my_TIME_to_str(const MYSQL_TIME *ltime, String *str, uint dec);
+
+/* MYSQL_TIME operations */
+bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type,
+ INTERVAL interval);
+bool calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2,
+ int l_sign, longlong *seconds_out, long *microseconds_out);
+int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b);
+void localtime_to_TIME(MYSQL_TIME *to, struct tm *from);
+void calc_time_from_sec(MYSQL_TIME *to, long seconds, long microseconds);
+uint calc_week(MYSQL_TIME *l_time, uint week_behaviour, uint *year);
+
+int calc_weekday(long daynr,bool sunday_first_day_of_week);
+bool parse_date_time_format(timestamp_type format_type,
+ const char *format, uint format_length,
+ DATE_TIME_FORMAT *date_time_format);
+/* Character set-aware version of str_to_time() */
+bool str_to_time(CHARSET_INFO *cs, const char *str,uint length,
+ MYSQL_TIME *l_time, ulonglong fuzzydate,
+ MYSQL_TIME_STATUS *status);
+/* Character set-aware version of str_to_datetime() */
+bool str_to_datetime(CHARSET_INFO *cs,
+ const char *str, uint length,
+ MYSQL_TIME *l_time, ulonglong flags,
+ MYSQL_TIME_STATUS *status);
+
+/* convenience wrapper */
+inline bool parse_date_time_format(timestamp_type format_type,
+ DATE_TIME_FORMAT *date_time_format)
+{
+ return parse_date_time_format(format_type,
+ date_time_format->format.str,
+ date_time_format->format.length,
+ date_time_format);
+}
+
+
+extern DATE_TIME_FORMAT global_date_format;
+extern DATE_TIME_FORMAT global_datetime_format;
+extern DATE_TIME_FORMAT global_time_format;
+extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[];
+extern LEX_STRING interval_type_to_name[];
+
+
+static inline bool
+non_zero_date(const MYSQL_TIME *ltime)
+{
+ return ltime->year || ltime->month || ltime->day;
+}
+static inline bool
+check_date(const MYSQL_TIME *ltime, ulonglong flags, int *was_cut)
+{
+ return check_date(ltime, non_zero_date(ltime), flags, was_cut);
+}
+bool check_date_with_warn(const MYSQL_TIME *ltime, ulonglong fuzzy_date,
+ timestamp_type ts_type);
+bool make_date_with_warn(MYSQL_TIME *ltime,
+ ulonglong fuzzy_date, timestamp_type ts_type);
+bool adjust_time_range_with_warn(MYSQL_TIME *ltime, uint dec);
+
+#endif /* SQL_TIME_INCLUDED */
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
new file mode 100644
index 00000000000..a59bbb5bc61
--- /dev/null
+++ b/sql/sql_trigger.cc
@@ -0,0 +1,2439 @@
+/*
+ Copyright (c) 2004, 2012, Oracle and/or its affiliates.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+#define MYSQL_LEX 1
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sp_head.h"
+#include "sql_trigger.h"
+#include "sql_parse.h" // parse_sql
+#include "parse_file.h"
+#include "sp.h"
+#include "sql_base.h" // find_temporary_table
+#include "sql_show.h" // append_definer, append_identifier
+#include "sql_table.h" // build_table_filename,
+ // check_n_cut_mysql50_prefix
+#include "sql_db.h" // get_default_db_collation
+#include "sql_acl.h" // *_ACL, is_acl_user
+#include "sql_handler.h" // mysql_ha_rm_tables
+#include "sp_cache.h" // sp_invalidate_cache
+#include <mysys_err.h>
+
+/*************************************************************************/
+
+template <class T>
+inline T *alloc_type(MEM_ROOT *m)
+{
+ return (T *) alloc_root(m, sizeof (T));
+}
+
+/*
+ NOTE: Since alloc_type() is declared as inline, alloc_root() calls should
+ be inlined by the compiler. So, implementation of alloc_root() is not
+ needed. However, let's put the implementation in object file just in case
+ of stupid MS or other old compilers.
+*/
+
+template LEX_STRING *alloc_type<LEX_STRING>(MEM_ROOT *m);
+template ulonglong *alloc_type<ulonglong>(MEM_ROOT *m);
+
+inline LEX_STRING *alloc_lex_string(MEM_ROOT *m)
+{
+ return alloc_type<LEX_STRING>(m);
+}
+
+/*************************************************************************/
+/**
+ Trigger_creation_ctx -- creation context of triggers.
+*/
+
+class Trigger_creation_ctx : public Stored_program_creation_ctx,
+ public Sql_alloc
+{
+public:
+ static Trigger_creation_ctx *create(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ const LEX_STRING *client_cs_name,
+ const LEX_STRING *connection_cl_name,
+ const LEX_STRING *db_cl_name);
+
+public:
+ virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root)
+ {
+ return new (mem_root) Trigger_creation_ctx(m_client_cs,
+ m_connection_cl,
+ m_db_cl);
+ }
+
+protected:
+ virtual Object_creation_ctx *create_backup_ctx(THD *thd) const
+ {
+ return new Trigger_creation_ctx(thd);
+ }
+
+private:
+ Trigger_creation_ctx(THD *thd)
+ :Stored_program_creation_ctx(thd)
+ { }
+
+ Trigger_creation_ctx(CHARSET_INFO *client_cs,
+ CHARSET_INFO *connection_cl,
+ CHARSET_INFO *db_cl)
+ :Stored_program_creation_ctx(client_cs, connection_cl, db_cl)
+ { }
+};
+
+/**************************************************************************
+ Trigger_creation_ctx implementation.
+**************************************************************************/
+
+Trigger_creation_ctx *
+Trigger_creation_ctx::create(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ const LEX_STRING *client_cs_name,
+ const LEX_STRING *connection_cl_name,
+ const LEX_STRING *db_cl_name)
+{
+ CHARSET_INFO *client_cs;
+ CHARSET_INFO *connection_cl;
+ CHARSET_INFO *db_cl;
+
+ bool invalid_creation_ctx= FALSE;
+
+ if (resolve_charset(client_cs_name->str,
+ thd->variables.character_set_client,
+ &client_cs))
+ {
+ sql_print_warning("Trigger for table '%s'.'%s': "
+ "invalid character_set_client value (%s).",
+ (const char *) db_name,
+ (const char *) table_name,
+ (const char *) client_cs_name->str);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (resolve_collation(connection_cl_name->str,
+ thd->variables.collation_connection,
+ &connection_cl))
+ {
+ sql_print_warning("Trigger for table '%s'.'%s': "
+ "invalid collation_connection value (%s).",
+ (const char *) db_name,
+ (const char *) table_name,
+ (const char *) connection_cl_name->str);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (resolve_collation(db_cl_name->str, NULL, &db_cl))
+ {
+ sql_print_warning("Trigger for table '%s'.'%s': "
+ "invalid database_collation value (%s).",
+ (const char *) db_name,
+ (const char *) table_name,
+ (const char *) db_cl_name->str);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (invalid_creation_ctx)
+ {
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_WARN,
+ ER_TRG_INVALID_CREATION_CTX,
+ ER(ER_TRG_INVALID_CREATION_CTX),
+ (const char *) db_name,
+ (const char *) table_name);
+ }
+
+ /*
+ If we failed to resolve the database collation, load the default one
+ from the disk.
+ */
+
+ if (!db_cl)
+ db_cl= get_default_db_collation(thd, db_name);
+
+ return new Trigger_creation_ctx(client_cs, connection_cl, db_cl);
+}
+
+/*************************************************************************/
+
+static const LEX_STRING triggers_file_type=
+ { C_STRING_WITH_LEN("TRIGGERS") };
+
+const char * const TRG_EXT= ".TRG";
+
+/**
+ Table of .TRG file field descriptors.
+ We have here only one field now because in nearest future .TRG
+ files will be merged into .FRM files (so we don't need something
+ like md5 or created fields).
+*/
+static File_option triggers_file_parameters[]=
+{
+ {
+ { C_STRING_WITH_LEN("triggers") },
+ my_offsetof(class Table_triggers_list, definitions_list),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("sql_modes") },
+ my_offsetof(class Table_triggers_list, definition_modes_list),
+ FILE_OPTIONS_ULLLIST
+ },
+ {
+ { C_STRING_WITH_LEN("definers") },
+ my_offsetof(class Table_triggers_list, definers_list),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("client_cs_names") },
+ my_offsetof(class Table_triggers_list, client_cs_names),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("connection_cl_names") },
+ my_offsetof(class Table_triggers_list, connection_cl_names),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("db_cl_names") },
+ my_offsetof(class Table_triggers_list, db_cl_names),
+ FILE_OPTIONS_STRLIST
+ },
+ { { 0, 0 }, 0, FILE_OPTIONS_STRING }
+};
+
+File_option sql_modes_parameters=
+{
+ { C_STRING_WITH_LEN("sql_modes") },
+ my_offsetof(class Table_triggers_list, definition_modes_list),
+ FILE_OPTIONS_ULLLIST
+};
+
+/**
+ This must be kept up to date whenever a new option is added to the list
+ above, as it specifies the number of required parameters of the trigger in
+ .trg file.
+*/
+
+static const int TRG_NUM_REQUIRED_PARAMETERS= 6;
+
+/*
+ Structure representing contents of .TRN file which are used to support
+ database wide trigger namespace.
+*/
+
+struct st_trigname
+{
+ LEX_STRING trigger_table;
+};
+
+static const LEX_STRING trigname_file_type=
+ { C_STRING_WITH_LEN("TRIGGERNAME") };
+
+const char * const TRN_EXT= ".TRN";
+
+static File_option trigname_file_parameters[]=
+{
+ {
+ { C_STRING_WITH_LEN("trigger_table")},
+ offsetof(struct st_trigname, trigger_table),
+ FILE_OPTIONS_ESTRING
+ },
+ { { 0, 0 }, 0, FILE_OPTIONS_STRING }
+};
+
+
+const LEX_STRING trg_action_time_type_names[]=
+{
+ { C_STRING_WITH_LEN("BEFORE") },
+ { C_STRING_WITH_LEN("AFTER") }
+};
+
+const LEX_STRING trg_event_type_names[]=
+{
+ { C_STRING_WITH_LEN("INSERT") },
+ { C_STRING_WITH_LEN("UPDATE") },
+ { C_STRING_WITH_LEN("DELETE") }
+};
+
+
+class Handle_old_incorrect_sql_modes_hook: public Unknown_key_hook
+{
+private:
+ char *path;
+public:
+ Handle_old_incorrect_sql_modes_hook(char *file_path)
+ :path(file_path)
+ {};
+ virtual bool process_unknown_string(char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, char *end);
+};
+
+
+class Handle_old_incorrect_trigger_table_hook: public Unknown_key_hook
+{
+public:
+ Handle_old_incorrect_trigger_table_hook(char *file_path,
+ LEX_STRING *trigger_table_arg)
+ :path(file_path), trigger_table_value(trigger_table_arg)
+ {};
+ virtual bool process_unknown_string(char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, char *end);
+private:
+ char *path;
+ LEX_STRING *trigger_table_value;
+};
+
+
+/**
+ An error handler that catches all non-OOM errors which can occur during
+ parsing of trigger body. Such errors are ignored and corresponding error
+ message is used to construct a more verbose error message which contains
+ name of problematic trigger. This error message is later emitted when
+ one tries to perform DML or some of DDL on this table.
+ Also, if possible, grabs name of the trigger being parsed so it can be
+ used to correctly drop problematic trigger.
+*/
+class Deprecated_trigger_syntax_handler : public Internal_error_handler
+{
+private:
+
+ char m_message[MYSQL_ERRMSG_SIZE];
+ LEX_STRING *m_trigger_name;
+
+public:
+
+ Deprecated_trigger_syntax_handler() : m_trigger_name(NULL) {}
+
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_warning_level level,
+ const char* message,
+ Sql_condition ** cond_hdl)
+ {
+ if (sql_errno != EE_OUTOFMEMORY &&
+ sql_errno != ER_OUT_OF_RESOURCES)
+ {
+ if(thd->lex->spname)
+ m_trigger_name= &thd->lex->spname->m_name;
+ if (m_trigger_name)
+ my_snprintf(m_message, sizeof(m_message),
+ ER(ER_ERROR_IN_TRIGGER_BODY),
+ m_trigger_name->str, message);
+ else
+ my_snprintf(m_message, sizeof(m_message),
+ ER(ER_ERROR_IN_UNKNOWN_TRIGGER_BODY), message);
+ return true;
+ }
+ return false;
+ }
+
+ LEX_STRING *get_trigger_name() { return m_trigger_name; }
+ char *get_error_message() { return m_message; }
+};
+
+
+/**
+ Create or drop trigger for table.
+
+ @param thd current thread context (including trigger definition in LEX)
+ @param tables table list containing one table for which trigger is created.
+ @param create whenever we create (TRUE) or drop (FALSE) trigger
+
+ @note
+ This function is mainly responsible for opening and locking of table and
+ invalidation of all its instances in table cache after trigger creation.
+ Real work on trigger creation/dropping is done inside Table_triggers_list
+ methods.
+
+ @todo
+ TODO: We should check if user has TRIGGER privilege for table here.
+ Now we just require SUPER privilege for creating/dropping because
+ we don't have proper privilege checking for triggers in place yet.
+
+ @retval
+ FALSE Success
+ @retval
+ TRUE error
+*/
+bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
+{
+ /*
+ FIXME: The code below takes too many different paths depending on the
+ 'create' flag, so that the justification for a single function
+ 'mysql_create_or_drop_trigger', compared to two separate functions
+ 'mysql_create_trigger' and 'mysql_drop_trigger' is not apparent.
+ This is a good candidate for a minor refactoring.
+ */
+ TABLE *table;
+ bool result= TRUE;
+ String stmt_query;
+ bool lock_upgrade_done= FALSE;
+ MDL_ticket *mdl_ticket= NULL;
+ Query_tables_list backup;
+
+ DBUG_ENTER("mysql_create_or_drop_trigger");
+
+ /* Charset of the buffer for statement must be system one. */
+ stmt_query.set_charset(system_charset_info);
+
+ /*
+ QQ: This function could be merged in mysql_alter_table() function
+ But do we want this ?
+ */
+
+ /*
+ Note that once we will have check for TRIGGER privilege in place we won't
+ need second part of condition below, since check_access() function also
+ checks that db is specified.
+ */
+ if (!thd->lex->spname->m_db.length || (create && !tables->db_length))
+ {
+ my_error(ER_NO_DB_ERROR, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ We don't allow creating triggers on tables in the 'mysql' schema
+ */
+ if (create && !my_strcasecmp(system_charset_info, "mysql", tables->db))
+ {
+ my_error(ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ There is no DETERMINISTIC clause for triggers, so can't check it.
+ But a trigger can in theory be used to do nasty things (if it supported
+ DROP for example) so we do the check for privileges. For now there is
+ already a stronger test right above; but when this stronger test will
+ be removed, the test below will hold. Because triggers have the same
+ nature as functions regarding binlogging: their body is implicitly
+ binlogged, so they share the same danger, so trust_function_creators
+ applies to them too.
+ */
+ if (!trust_function_creators && mysql_bin_log.is_open() &&
+ !(thd->security_ctx->master_access & SUPER_ACL))
+ {
+ my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (!create)
+ {
+ bool if_exists= thd->lex->check_exists;
+
+ /*
+ Protect the query table list from the temporary and potentially
+ destructive changes necessary to open the trigger's table.
+ */
+ thd->lex->reset_n_backup_query_tables_list(&backup);
+ /*
+ Restore Query_tables_list::sql_command, which was
+ reset above, as the code that writes the query to the
+ binary log assumes that this value corresponds to the
+ statement that is being executed.
+ */
+ thd->lex->sql_command= backup.sql_command;
+
+ if (opt_readonly && !(thd->security_ctx->master_access & SUPER_ACL) &&
+ !thd->slave_thread)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
+ goto end;
+ }
+
+ if (add_table_for_trigger(thd, thd->lex->spname, if_exists, & tables))
+ goto end;
+
+ if (!tables)
+ {
+ DBUG_ASSERT(if_exists);
+ /*
+ Since the trigger does not exist, there is no associated table,
+ and therefore :
+ - no TRIGGER privileges to check,
+ - no trigger to drop,
+ - no table to lock/modify,
+ so the drop statement is successful.
+ */
+ result= FALSE;
+ /* Still, we need to log the query ... */
+ stmt_query.append(thd->query(), thd->query_length());
+ goto end;
+ }
+ }
+
+ /*
+ Check that the user has TRIGGER privilege on the subject table.
+ */
+ {
+ bool err_status;
+ TABLE_LIST **save_query_tables_own_last= thd->lex->query_tables_own_last;
+ thd->lex->query_tables_own_last= 0;
+
+ err_status= check_table_access(thd, TRIGGER_ACL, tables, FALSE, 1, FALSE);
+
+ thd->lex->query_tables_own_last= save_query_tables_own_last;
+
+ if (err_status)
+ goto end;
+ }
+
+ /* We should have only one table in table list. */
+ DBUG_ASSERT(tables->next_global == 0);
+
+ /* We do not allow creation of triggers on temporary tables. */
+ if (create && find_temporary_table(thd, tables))
+ {
+ my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias);
+ goto end;
+ }
+
+ /* We also don't allow creation of triggers on views. */
+ tables->required_type= FRMTYPE_TABLE;
+ /*
+ Also prevent DROP TRIGGER from opening temporary table which might
+ shadow base table on which trigger to be dropped is defined.
+ */
+ tables->open_type= OT_BASE_ONLY;
+
+ /* Keep consistent with respect to other DDL statements */
+ mysql_ha_rm_tables(thd, tables);
+
+ if (thd->locked_tables_mode)
+ {
+ /* Under LOCK TABLES we must only accept write locked tables. */
+ if (!(tables->table= find_table_for_mdl_upgrade(thd, tables->db,
+ tables->table_name,
+ FALSE)))
+ goto end;
+ }
+ else
+ {
+ tables->table= open_n_lock_single_table(thd, tables,
+ TL_READ_NO_INSERT, 0);
+ if (! tables->table)
+ goto end;
+ tables->table->use_all_columns();
+ }
+ table= tables->table;
+
+ /* Later on we will need it to downgrade the lock */
+ mdl_ticket= table->mdl_ticket;
+
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto end;
+
+ lock_upgrade_done= TRUE;
+
+ if (!table->triggers)
+ {
+ if (!create)
+ {
+ my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
+ goto end;
+ }
+
+ if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table)))
+ goto end;
+ }
+
+ result= (create ?
+ table->triggers->create_trigger(thd, tables, &stmt_query):
+ table->triggers->drop_trigger(thd, tables, &stmt_query));
+
+ if (result)
+ goto end;
+
+ close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
+ /*
+ Reopen the table if we were under LOCK TABLES.
+ Ignore the return value for now. It's better to
+ keep master/slave in consistent state.
+ */
+ if (thd->locked_tables_list.reopen_tables(thd))
+ thd->clear_error();
+
+ /*
+ Invalidate SP-cache. That's needed because triggers may change list of
+ pre-locking tables.
+ */
+ sp_cache_invalidate();
+
+end:
+ if (!result)
+ {
+ result= write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length());
+ }
+
+ /*
+ If we are under LOCK TABLES we should restore original state of
+ meta-data locks. Otherwise all locks will be released along
+ with the implicit commit.
+ */
+ if (thd->locked_tables_mode && tables && lock_upgrade_done)
+ mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+
+ /* Restore the query table list. Used only for drop trigger. */
+ if (!create)
+ thd->lex->restore_backup_query_tables_list(&backup);
+
+ if (!result)
+ my_ok(thd);
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Create trigger for table.
+
+ @param thd current thread context (including trigger definition in
+ LEX)
+ @param tables table list containing one open table for which the
+ trigger is created.
+ @param[out] stmt_query after successful return, this string contains
+ well-formed statement for creation this trigger.
+
+ @note
+ - Assumes that trigger name is fully qualified.
+ - NULL-string means the following LEX_STRING instance:
+ { str = 0; length = 0 }.
+ - In other words, definer_user and definer_host should contain
+ simultaneously NULL-strings (non-SUID/old trigger) or valid strings
+ (SUID/new trigger).
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
+ String *stmt_query)
+{
+ LEX *lex= thd->lex;
+ TABLE *table= tables->table;
+ char file_buff[FN_REFLEN], trigname_buff[FN_REFLEN];
+ LEX_STRING file, trigname_file;
+ LEX_STRING *trg_def;
+ LEX_STRING definer_user;
+ LEX_STRING definer_host;
+ ulonglong *trg_sql_mode;
+ char trg_definer_holder[USER_HOST_BUFF_SIZE];
+ LEX_STRING *trg_definer;
+ Item_trigger_field *trg_field;
+ struct st_trigname trigname;
+ LEX_STRING *trg_client_cs_name;
+ LEX_STRING *trg_connection_cl_name;
+ LEX_STRING *trg_db_cl_name;
+
+ if (check_for_broken_triggers())
+ return true;
+
+ /* Trigger must be in the same schema as target table. */
+ if (my_strcasecmp(table_alias_charset, table->s->db.str,
+ lex->spname->m_db.str))
+ {
+ my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
+ return 1;
+ }
+
+ /* We don't allow creation of several triggers of the same type yet */
+ if (bodies[lex->trg_chistics.event][lex->trg_chistics.action_time] != 0)
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "multiple triggers with the same action time"
+ " and event for one table");
+ return 1;
+ }
+
+ if (sp_process_definer(thd))
+ return 1;
+
+ /*
+ Let us check if all references to fields in old/new versions of row in
+ this trigger are ok.
+
+ NOTE: We do it here more from ease of use standpoint. We still have to
+ do some checks on each execution. E.g. we can catch privilege changes
+ only during execution. Also in near future, when we will allow access
+ to other tables from trigger we won't be able to catch changes in other
+ tables...
+
+ Since we don't plan to access to contents of the fields it does not
+ matter that we choose for both OLD and NEW values the same versions
+ of Field objects here.
+ */
+ old_field= new_field= table->field;
+
+ for (trg_field= lex->trg_table_fields.first;
+ trg_field; trg_field= trg_field->next_trg_field)
+ {
+ /*
+ NOTE: now we do not check privileges at CREATE TRIGGER time. This will
+ be changed in the future.
+ */
+ trg_field->setup_field(thd, table, NULL);
+
+ if (!trg_field->fixed &&
+ trg_field->fix_fields(thd, (Item **)0))
+ return 1;
+ }
+
+ /*
+ Here we are creating file with triggers and save all triggers in it.
+ sql_create_definition_file() files handles renaming and backup of older
+ versions
+ */
+ file.length= build_table_filename(file_buff, FN_REFLEN - 1,
+ tables->db, tables->table_name,
+ TRG_EXT, 0);
+ file.str= file_buff;
+ trigname_file.length= build_table_filename(trigname_buff, FN_REFLEN-1,
+ tables->db,
+ lex->spname->m_name.str,
+ TRN_EXT, 0);
+ trigname_file.str= trigname_buff;
+
+ /* Use the filesystem to enforce trigger namespace constraints. */
+ if (!access(trigname_buff, F_OK))
+ {
+ my_error(ER_TRG_ALREADY_EXISTS, MYF(0));
+ return 1;
+ }
+
+ trigname.trigger_table.str= tables->table_name;
+ trigname.trigger_table.length= tables->table_name_length;
+
+ if (sql_create_definition_file(NULL, &trigname_file, &trigname_file_type,
+ (uchar*)&trigname, trigname_file_parameters))
+ return 1;
+
+ /*
+ Soon we will invalidate table object and thus Table_triggers_list object
+ so don't care about place to which trg_def->ptr points and other
+ invariants (e.g. we don't bother to update names_list)
+
+ QQ: Hmm... probably we should not care about setting up active thread
+ mem_root too.
+ */
+ if (!(trg_def= alloc_lex_string(&table->mem_root)) ||
+ definitions_list.push_back(trg_def, &table->mem_root) ||
+
+ !(trg_sql_mode= alloc_type<ulonglong>(&table->mem_root)) ||
+ definition_modes_list.push_back(trg_sql_mode, &table->mem_root) ||
+
+ !(trg_definer= alloc_lex_string(&table->mem_root)) ||
+ definers_list.push_back(trg_definer, &table->mem_root) ||
+
+ !(trg_client_cs_name= alloc_lex_string(&table->mem_root)) ||
+ client_cs_names.push_back(trg_client_cs_name, &table->mem_root) ||
+
+ !(trg_connection_cl_name= alloc_lex_string(&table->mem_root)) ||
+ connection_cl_names.push_back(trg_connection_cl_name, &table->mem_root) ||
+
+ !(trg_db_cl_name= alloc_lex_string(&table->mem_root)) ||
+ db_cl_names.push_back(trg_db_cl_name, &table->mem_root))
+ {
+ goto err_with_cleanup;
+ }
+
+ *trg_sql_mode= thd->variables.sql_mode;
+
+ if (lex->sphead->m_chistics->suid != SP_IS_NOT_SUID)
+ {
+ /* SUID trigger. */
+
+ definer_user= lex->definer->user;
+ definer_host= lex->definer->host;
+
+ lex->definer->set_lex_string(trg_definer, trg_definer_holder);
+ }
+ else
+ {
+ /* non-SUID trigger. */
+
+ definer_user.str= 0;
+ definer_user.length= 0;
+
+ definer_host.str= 0;
+ definer_host.length= 0;
+
+ trg_definer->str= (char*) "";
+ trg_definer->length= 0;
+ }
+
+ /*
+ Fill character set information:
+ - client character set contains charset info only;
+ - connection collation contains pair {character set, collation};
+ - database collation contains pair {character set, collation};
+ */
+
+ lex_string_set(trg_client_cs_name, thd->charset()->csname);
+
+ lex_string_set(trg_connection_cl_name,
+ thd->variables.collation_connection->name);
+
+ lex_string_set(trg_db_cl_name,
+ get_default_db_collation(thd, tables->db)->name);
+
+ /*
+ Create well-formed trigger definition query. Original query is not
+ appropriated, because definer-clause can be not truncated.
+ */
+
+ stmt_query->append(STRING_WITH_LEN("CREATE "));
+
+ if (lex->sphead->m_chistics->suid != SP_IS_NOT_SUID)
+ {
+ /*
+ Append definer-clause if the trigger is SUID (a usual trigger in
+ new MySQL versions).
+ */
+
+ append_definer(thd, stmt_query, &definer_user, &definer_host);
+ }
+
+ LEX_STRING stmt_definition;
+ stmt_definition.str= (char*) thd->lex->stmt_definition_begin;
+ stmt_definition.length= thd->lex->stmt_definition_end
+ - thd->lex->stmt_definition_begin;
+ trim_whitespace(thd->charset(), & stmt_definition);
+
+ stmt_query->append(stmt_definition.str, stmt_definition.length);
+
+ trg_def->str= stmt_query->c_ptr_safe();
+ trg_def->length= stmt_query->length();
+
+ /* Create trigger definition file. */
+
+ if (!sql_create_definition_file(NULL, &file, &triggers_file_type,
+ (uchar*)this, triggers_file_parameters))
+ return 0;
+
+err_with_cleanup:
+ mysql_file_delete(key_file_trn, trigname_buff, MYF(MY_WME));
+ return 1;
+}
+
+
+/**
+ Deletes the .TRG file for a table.
+
+ @param path char buffer of size FN_REFLEN to be used
+ for constructing path to .TRG file.
+ @param db table's database name
+ @param table_name table's name
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+
+static bool rm_trigger_file(char *path, const char *db,
+ const char *table_name)
+{
+ build_table_filename(path, FN_REFLEN-1, db, table_name, TRG_EXT, 0);
+ return mysql_file_delete(key_file_trg, path, MYF(MY_WME));
+}
+
+
+/**
+ Deletes the .TRN file for a trigger.
+
+ @param path char buffer of size FN_REFLEN to be used
+ for constructing path to .TRN file.
+ @param db trigger's database name
+ @param trigger_name trigger's name
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+
+static bool rm_trigname_file(char *path, const char *db,
+ const char *trigger_name)
+{
+ build_table_filename(path, FN_REFLEN - 1, db, trigger_name, TRN_EXT, 0);
+ return mysql_file_delete(key_file_trn, path, MYF(MY_WME));
+}
+
+
+/**
+ Helper function that saves .TRG file for Table_triggers_list object.
+
+ @param triggers Table_triggers_list object for which file should be saved
+ @param db Name of database for subject table
+ @param table_name Name of subject table
+
+ @retval
+ FALSE Success
+ @retval
+ TRUE Error
+*/
+
+static bool save_trigger_file(Table_triggers_list *triggers, const char *db,
+ const char *table_name)
+{
+ char file_buff[FN_REFLEN];
+ LEX_STRING file;
+
+ file.length= build_table_filename(file_buff, FN_REFLEN - 1, db, table_name,
+ TRG_EXT, 0);
+ file.str= file_buff;
+ return sql_create_definition_file(NULL, &file, &triggers_file_type,
+ (uchar*)triggers, triggers_file_parameters);
+}
+
+
+/**
+ Drop trigger for table.
+
+ @param thd current thread context
+ (including trigger definition in LEX)
+ @param tables table list containing one open table for which trigger
+ is dropped.
+ @param[out] stmt_query after successful return, this string contains
+ well-formed statement for creation this trigger.
+
+ @todo
+ Probably instead of removing .TRG file we should move
+ to archive directory but this should be done as part of
+ parse_file.cc functionality (because we will need it
+ elsewhere).
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables,
+ String *stmt_query)
+{
+ const char *sp_name= thd->lex->spname->m_name.str; // alias
+
+ LEX_STRING *name;
+ char path[FN_REFLEN];
+
+ List_iterator_fast<LEX_STRING> it_name(names_list);
+
+ List_iterator<ulonglong> it_mod(definition_modes_list);
+ List_iterator<LEX_STRING> it_def(definitions_list);
+ List_iterator<LEX_STRING> it_definer(definers_list);
+ List_iterator<LEX_STRING> it_client_cs_name(client_cs_names);
+ List_iterator<LEX_STRING> it_connection_cl_name(connection_cl_names);
+ List_iterator<LEX_STRING> it_db_cl_name(db_cl_names);
+
+ stmt_query->append(thd->query(), thd->query_length());
+
+ while ((name= it_name++))
+ {
+ it_def++;
+ it_mod++;
+ it_definer++;
+ it_client_cs_name++;
+ it_connection_cl_name++;
+ it_db_cl_name++;
+
+ if (my_strcasecmp(table_alias_charset, sp_name, name->str) == 0)
+ {
+ /*
+ Again we don't care much about other things required for
+ clean trigger removing since table will be reopened anyway.
+ */
+ it_def.remove();
+ it_mod.remove();
+ it_definer.remove();
+ it_client_cs_name.remove();
+ it_connection_cl_name.remove();
+ it_db_cl_name.remove();
+
+ if (definitions_list.is_empty())
+ {
+ /*
+ TODO: Probably instead of removing .TRG file we should move
+ to archive directory but this should be done as part of
+ parse_file.cc functionality (because we will need it
+ elsewhere).
+ */
+ if (rm_trigger_file(path, tables->db, tables->table_name))
+ return 1;
+ }
+ else
+ {
+ if (save_trigger_file(this, tables->db, tables->table_name))
+ return 1;
+ }
+
+ if (rm_trigname_file(path, tables->db, sp_name))
+ return 1;
+ return 0;
+ }
+ }
+
+ my_message(ER_TRG_DOES_NOT_EXIST, ER(ER_TRG_DOES_NOT_EXIST), MYF(0));
+ return 1;
+}
+
+
+Table_triggers_list::~Table_triggers_list()
+{
+ for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
+ for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
+ delete bodies[i][j];
+
+ if (record1_field)
+ for (Field **fld_ptr= record1_field; *fld_ptr; fld_ptr++)
+ delete *fld_ptr;
+}
+
+
+/**
+ Prepare array of Field objects referencing to TABLE::record[1] instead
+ of record[0] (they will represent OLD.* row values in ON UPDATE trigger
+ and in ON DELETE trigger which will be called during REPLACE execution).
+
+ @param table pointer to TABLE object for which we are creating fields.
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+bool Table_triggers_list::prepare_record1_accessors(TABLE *table)
+{
+ Field **fld, **old_fld;
+
+ if (!(record1_field= (Field **)alloc_root(&table->mem_root,
+ (table->s->fields + 1) *
+ sizeof(Field*))))
+ return 1;
+
+ for (fld= table->field, old_fld= record1_field; *fld; fld++, old_fld++)
+ {
+ /*
+ QQ: it is supposed that it is ok to use this function for field
+ cloning...
+ */
+ if (!(*old_fld= (*fld)->new_field(&table->mem_root, table,
+ table == (*fld)->table)))
+ return 1;
+ (*old_fld)->move_field_offset((my_ptrdiff_t)(table->record[1] -
+ table->record[0]));
+ }
+ *old_fld= 0;
+
+ return 0;
+}
+
+
+/**
+ Adjust Table_triggers_list with new TABLE pointer.
+
+ @param new_table new pointer to TABLE instance
+*/
+
+void Table_triggers_list::set_table(TABLE *new_table)
+{
+ trigger_table= new_table;
+ for (Field **field= new_table->triggers->record1_field ; *field ; field++)
+ (*field)->init(new_table);
+}
+
+
+/**
+ Check whenever .TRG file for table exist and load all triggers it contains.
+
+ @param thd current thread context
+ @param db table's database name
+ @param table_name table's name
+ @param table pointer to table object
+ @param names_only stop after loading trigger names
+
+ @todo
+ A lot of things to do here e.g. how about other funcs and being
+ more paranoical ?
+
+ @todo
+ This could be avoided if there is no triggers for UPDATE and DELETE.
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+
+bool Table_triggers_list::check_n_load(THD *thd, const char *db,
+ const char *table_name, TABLE *table,
+ bool names_only)
+{
+ char path_buff[FN_REFLEN];
+ LEX_STRING path;
+ File_parser *parser;
+ LEX_STRING save_db;
+
+ DBUG_ENTER("Table_triggers_list::check_n_load");
+
+ path.length= build_table_filename(path_buff, FN_REFLEN - 1,
+ db, table_name, TRG_EXT, 0);
+ path.str= path_buff;
+
+ // QQ: should we analyze errno somehow ?
+ if (access(path_buff, F_OK))
+ DBUG_RETURN(0);
+
+ /*
+ File exists so we got to load triggers.
+ FIXME: A lot of things to do here e.g. how about other funcs and being
+ more paranoical ?
+ */
+
+ if ((parser= sql_parse_prepare(&path, &table->mem_root, 1)))
+ {
+ if (is_equal(&triggers_file_type, parser->type()))
+ {
+ Table_triggers_list *triggers=
+ new (&table->mem_root) Table_triggers_list(table);
+ Handle_old_incorrect_sql_modes_hook sql_modes_hook(path.str);
+
+ if (!triggers)
+ DBUG_RETURN(1);
+
+ /*
+ We don't have the following attributes in old versions of .TRG file, so
+ we should initialize the list for safety:
+ - sql_modes;
+ - definers;
+ - character sets (client, connection, database);
+ */
+ triggers->definition_modes_list.empty();
+ triggers->definers_list.empty();
+ triggers->client_cs_names.empty();
+ triggers->connection_cl_names.empty();
+ triggers->db_cl_names.empty();
+
+ if (parser->parse((uchar*)triggers, &table->mem_root,
+ triggers_file_parameters,
+ TRG_NUM_REQUIRED_PARAMETERS,
+ &sql_modes_hook))
+ DBUG_RETURN(1);
+
+ List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
+ LEX_STRING *trg_create_str;
+ ulonglong *trg_sql_mode;
+
+ if (triggers->definition_modes_list.is_empty() &&
+ !triggers->definitions_list.is_empty())
+ {
+ /*
+ It is old file format => we should fill list of sql_modes.
+
+ We use one mode (current) for all triggers, because we have not
+ information about mode in old format.
+ */
+ if (!(trg_sql_mode= alloc_type<ulonglong>(&table->mem_root)))
+ {
+ DBUG_RETURN(1); // EOM
+ }
+ *trg_sql_mode= global_system_variables.sql_mode;
+ while (it++)
+ {
+ if (triggers->definition_modes_list.push_back(trg_sql_mode,
+ &table->mem_root))
+ {
+ DBUG_RETURN(1); // EOM
+ }
+ }
+ it.rewind();
+ }
+
+ if (triggers->definers_list.is_empty() &&
+ !triggers->definitions_list.is_empty())
+ {
+ /*
+ It is old file format => we should fill list of definers.
+
+ If there is no definer information, we should not switch context to
+ definer when checking privileges. I.e. privileges for such triggers
+ are checked for "invoker" rather than for "definer".
+ */
+
+ LEX_STRING *trg_definer;
+
+ if (!(trg_definer= alloc_lex_string(&table->mem_root)))
+ DBUG_RETURN(1); // EOM
+
+ trg_definer->str= (char*) "";
+ trg_definer->length= 0;
+
+ while (it++)
+ {
+ if (triggers->definers_list.push_back(trg_definer,
+ &table->mem_root))
+ {
+ DBUG_RETURN(1); // EOM
+ }
+ }
+
+ it.rewind();
+ }
+
+ if (!triggers->definitions_list.is_empty() &&
+ (triggers->client_cs_names.is_empty() ||
+ triggers->connection_cl_names.is_empty() ||
+ triggers->db_cl_names.is_empty()))
+ {
+ /*
+ It is old file format => we should fill lists of character sets.
+ */
+
+ LEX_STRING *trg_client_cs_name;
+ LEX_STRING *trg_connection_cl_name;
+ LEX_STRING *trg_db_cl_name;
+
+ if (!triggers->client_cs_names.is_empty() ||
+ !triggers->connection_cl_names.is_empty() ||
+ !triggers->db_cl_names.is_empty())
+ {
+ my_error(ER_TRG_CORRUPTED_FILE, MYF(0),
+ (const char *) db,
+ (const char *) table_name);
+
+ DBUG_RETURN(1); // EOM
+ }
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRG_NO_CREATION_CTX,
+ ER(ER_TRG_NO_CREATION_CTX),
+ (const char*) db,
+ (const char*) table_name);
+
+ if (!(trg_client_cs_name= alloc_lex_string(&table->mem_root)) ||
+ !(trg_connection_cl_name= alloc_lex_string(&table->mem_root)) ||
+ !(trg_db_cl_name= alloc_lex_string(&table->mem_root)))
+ {
+ DBUG_RETURN(1); // EOM
+ }
+
+ /*
+ Backward compatibility: assume that the query is in the current
+ character set.
+ */
+
+ lex_string_set(trg_client_cs_name,
+ thd->variables.character_set_client->csname);
+
+ lex_string_set(trg_connection_cl_name,
+ thd->variables.collation_connection->name);
+
+ lex_string_set(trg_db_cl_name,
+ thd->variables.collation_database->name);
+
+ while (it++)
+ {
+ if (triggers->client_cs_names.push_back(trg_client_cs_name,
+ &table->mem_root) ||
+
+ triggers->connection_cl_names.push_back(trg_connection_cl_name,
+ &table->mem_root) ||
+
+ triggers->db_cl_names.push_back(trg_db_cl_name,
+ &table->mem_root))
+ {
+ DBUG_RETURN(1); // EOM
+ }
+ }
+
+ it.rewind();
+ }
+
+ DBUG_ASSERT(triggers->definition_modes_list.elements ==
+ triggers->definitions_list.elements);
+ DBUG_ASSERT(triggers->definers_list.elements ==
+ triggers->definitions_list.elements);
+ DBUG_ASSERT(triggers->client_cs_names.elements ==
+ triggers->definitions_list.elements);
+ DBUG_ASSERT(triggers->connection_cl_names.elements ==
+ triggers->definitions_list.elements);
+ DBUG_ASSERT(triggers->db_cl_names.elements ==
+ triggers->definitions_list.elements);
+
+ table->triggers= triggers;
+ status_var_increment(thd->status_var.feature_trigger);
+
+ /*
+ TODO: This could be avoided if there is no triggers
+ for UPDATE and DELETE.
+ */
+ if (!names_only && triggers->prepare_record1_accessors(table))
+ DBUG_RETURN(1);
+
+ List_iterator_fast<ulonglong> itm(triggers->definition_modes_list);
+ List_iterator_fast<LEX_STRING> it_definer(triggers->definers_list);
+ List_iterator_fast<LEX_STRING> it_client_cs_name(triggers->client_cs_names);
+ List_iterator_fast<LEX_STRING> it_connection_cl_name(triggers->connection_cl_names);
+ List_iterator_fast<LEX_STRING> it_db_cl_name(triggers->db_cl_names);
+ LEX *old_lex= thd->lex, lex;
+ sp_rcontext *save_spcont= thd->spcont;
+ ulonglong save_sql_mode= thd->variables.sql_mode;
+ LEX_STRING *on_table_name;
+
+ thd->lex= &lex;
+
+ save_db.str= thd->db;
+ save_db.length= thd->db_length;
+ thd->reset_db((char*) db, strlen(db));
+ while ((trg_create_str= it++))
+ {
+ sp_head *sp;
+ trg_sql_mode= itm++;
+ LEX_STRING *trg_definer= it_definer++;
+
+ thd->variables.sql_mode= (ulong)*trg_sql_mode;
+
+ Parser_state parser_state;
+ if (parser_state.init(thd, trg_create_str->str, trg_create_str->length))
+ goto err_with_lex_cleanup;
+
+ Trigger_creation_ctx *creation_ctx=
+ Trigger_creation_ctx::create(thd,
+ db,
+ table_name,
+ it_client_cs_name++,
+ it_connection_cl_name++,
+ it_db_cl_name++);
+
+ lex_start(thd);
+ thd->spcont= NULL;
+
+ Deprecated_trigger_syntax_handler error_handler;
+ thd->push_internal_handler(&error_handler);
+ bool parse_error= parse_sql(thd, & parser_state, creation_ctx);
+ thd->pop_internal_handler();
+
+ /*
+ Not strictly necessary to invoke this method here, since we know
+ that we've parsed CREATE TRIGGER and not an
+ UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
+ maintain the invariant that this method is called for each
+ distinct statement, in case its logic is extended with other
+ types of analyses in future.
+ */
+ lex.set_trg_event_type_for_tables();
+
+ if (parse_error)
+ {
+ if (!triggers->m_has_unparseable_trigger)
+ triggers->set_parse_error_message(error_handler.get_error_message());
+ /* Currently sphead is always set to NULL in case of a parse error */
+ DBUG_ASSERT(lex.sphead == 0);
+ if (error_handler.get_trigger_name())
+ {
+ LEX_STRING *trigger_name;
+ const LEX_STRING *orig_trigger_name= error_handler.get_trigger_name();
+
+ if (!(trigger_name= alloc_lex_string(&table->mem_root)) ||
+ !(trigger_name->str= strmake_root(&table->mem_root,
+ orig_trigger_name->str,
+ orig_trigger_name->length)))
+ goto err_with_lex_cleanup;
+
+ trigger_name->length= orig_trigger_name->length;
+
+ if (triggers->names_list.push_back(trigger_name,
+ &table->mem_root))
+ goto err_with_lex_cleanup;
+ }
+ else
+ {
+ /*
+ The Table_triggers_list is not constructed as a list of
+ trigger objects as one would expect, but rather of lists of
+ properties of equal length. Thus, even if we don't get the
+ trigger name, we still fill all in all the lists with
+ placeholders as we might otherwise create a skew in the
+ lists. Obviously, this has to be refactored.
+ */
+ LEX_STRING *empty= alloc_lex_string(&table->mem_root);
+ if (!empty)
+ goto err_with_lex_cleanup;
+
+ empty->str= const_cast<char*>("");
+ empty->length= 0;
+ if (triggers->names_list.push_back(empty, &table->mem_root))
+ goto err_with_lex_cleanup;
+ }
+ lex_end(&lex);
+ continue;
+ }
+
+ lex.sphead->set_info(0, 0, &lex.sp_chistics, (ulong) *trg_sql_mode);
+
+ int event= lex.trg_chistics.event;
+ int action_time= lex.trg_chistics.action_time;
+
+ sp= triggers->bodies[event][action_time]= lex.sphead;
+ lex.sphead= NULL; /* Prevent double cleanup. */
+
+ sp->set_info(0, 0, &lex.sp_chistics, (ulong) *trg_sql_mode);
+ sp->set_creation_ctx(creation_ctx);
+
+ if (!trg_definer->length)
+ {
+ /*
+ This trigger was created/imported from the previous version of
+ MySQL, which does not support triggers definers. We should emit
+ warning here.
+ */
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER),
+ (const char*) db,
+ (const char*) sp->m_name.str);
+
+ /*
+ Set definer to the '' to correct displaying in the information
+ schema.
+ */
+
+ sp->set_definer((char*) "", 0);
+
+ /*
+ Triggers without definer information are executed under the
+ authorization of the invoker.
+ */
+
+ sp->m_chistics->suid= SP_IS_NOT_SUID;
+ }
+ else
+ sp->set_definer(trg_definer->str, trg_definer->length);
+
+ if (triggers->names_list.push_back(&sp->m_name, &table->mem_root))
+ goto err_with_lex_cleanup;
+
+ if (!(on_table_name= alloc_lex_string(&table->mem_root)))
+ goto err_with_lex_cleanup;
+
+ on_table_name->str= (char*) lex.raw_trg_on_table_name_begin;
+ on_table_name->length= lex.raw_trg_on_table_name_end
+ - lex.raw_trg_on_table_name_begin;
+
+ if (triggers->on_table_names_list.push_back(on_table_name, &table->mem_root))
+ goto err_with_lex_cleanup;
+#ifndef DBUG_OFF
+ /*
+ Let us check that we correctly update trigger definitions when we
+ rename tables with triggers.
+
+ In special cases like "RENAME TABLE `#mysql50#somename` TO `somename`"
+ or "ALTER DATABASE `#mysql50#somename` UPGRADE DATA DIRECTORY NAME"
+ we might be given table or database name with "#mysql50#" prefix (and
+ trigger's definiton contains un-prefixed version of the same name).
+ To remove this prefix we use check_n_cut_mysql50_prefix().
+ */
+
+ char fname[SAFE_NAME_LEN + 1];
+ DBUG_ASSERT((!my_strcasecmp(table_alias_charset, lex.query_tables->db, db) ||
+ (check_n_cut_mysql50_prefix(db, fname, sizeof(fname)) &&
+ !my_strcasecmp(table_alias_charset, lex.query_tables->db, fname))));
+ DBUG_ASSERT((!my_strcasecmp(table_alias_charset, lex.query_tables->table_name, table_name) ||
+ (check_n_cut_mysql50_prefix(table_name, fname, sizeof(fname)) &&
+ !my_strcasecmp(table_alias_charset, lex.query_tables->table_name, fname))));
+#endif
+ if (names_only)
+ {
+ lex_end(&lex);
+ continue;
+ }
+
+ /*
+ Gather all Item_trigger_field objects representing access to fields
+ in old/new versions of row in trigger into lists containing all such
+ objects for the triggers with same action and timing.
+ */
+ triggers->trigger_fields[lex.trg_chistics.event]
+ [lex.trg_chistics.action_time]=
+ lex.trg_table_fields.first;
+ /*
+ Also let us bind these objects to Field objects in table being
+ opened.
+
+ We ignore errors here, because if even something is wrong we still
+ will be willing to open table to perform some operations (e.g.
+ SELECT)...
+ Anyway some things can be checked only during trigger execution.
+ */
+ for (Item_trigger_field *trg_field= lex.trg_table_fields.first;
+ trg_field;
+ trg_field= trg_field->next_trg_field)
+ {
+ trg_field->setup_field(thd, table,
+ &triggers->subject_table_grants[lex.trg_chistics.event]
+ [lex.trg_chistics.action_time]);
+ }
+
+ lex_end(&lex);
+ }
+ thd->reset_db(save_db.str, save_db.length);
+ thd->lex= old_lex;
+ thd->spcont= save_spcont;
+ thd->variables.sql_mode= save_sql_mode;
+
+ DBUG_RETURN(0);
+
+err_with_lex_cleanup:
+ // QQ: anything else ?
+ lex_end(&lex);
+ thd->lex= old_lex;
+ thd->spcont= save_spcont;
+ thd->variables.sql_mode= save_sql_mode;
+ thd->reset_db(save_db.str, save_db.length);
+ DBUG_RETURN(1);
+ }
+
+ /*
+ We don't care about this error message much because .TRG files will
+ be merged into .FRM anyway.
+ */
+ my_error(ER_WRONG_OBJECT, MYF(0),
+ table_name, TRG_EXT + 1, "TRIGGER");
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Obtains and returns trigger metadata.
+
+ @param thd current thread context
+ @param event trigger event type
+ @param time_type trigger action time
+ @param trigger_name returns name of trigger
+ @param trigger_stmt returns statement of trigger
+ @param sql_mode returns sql_mode of trigger
+ @param definer returns definer/creator of trigger. The caller is
+ responsible to allocate enough space for storing
+ definer information.
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+
+bool Table_triggers_list::get_trigger_info(THD *thd, trg_event_type event,
+ trg_action_time_type time_type,
+ LEX_STRING *trigger_name,
+ LEX_STRING *trigger_stmt,
+ ulong *sql_mode,
+ LEX_STRING *definer,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name)
+{
+ sp_head *body;
+ DBUG_ENTER("get_trigger_info");
+ if ((body= bodies[event][time_type]))
+ {
+ Stored_program_creation_ctx *creation_ctx=
+ bodies[event][time_type]->get_creation_ctx();
+
+ *trigger_name= body->m_name;
+ *trigger_stmt= body->m_body_utf8;
+ *sql_mode= body->m_sql_mode;
+
+ if (body->m_chistics->suid == SP_IS_NOT_SUID)
+ {
+ definer->str[0]= 0;
+ definer->length= 0;
+ }
+ else
+ {
+ definer->length= strxmov(definer->str, body->m_definer_user.str, "@",
+ body->m_definer_host.str, NullS) - definer->str;
+ }
+
+ lex_string_set(client_cs_name,
+ creation_ctx->get_client_cs()->csname);
+
+ lex_string_set(connection_cl_name,
+ creation_ctx->get_connection_cl()->name);
+
+ lex_string_set(db_cl_name,
+ creation_ctx->get_db_cl()->name);
+
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(1);
+}
+
+
+void Table_triggers_list::get_trigger_info(THD *thd,
+ int trigger_idx,
+ LEX_STRING *trigger_name,
+ ulonglong *sql_mode,
+ LEX_STRING *sql_original_stmt,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name)
+{
+ List_iterator_fast<LEX_STRING> it_trigger_name(names_list);
+ List_iterator_fast<ulonglong> it_sql_mode(definition_modes_list);
+ List_iterator_fast<LEX_STRING> it_sql_orig_stmt(definitions_list);
+ List_iterator_fast<LEX_STRING> it_client_cs_name(client_cs_names);
+ List_iterator_fast<LEX_STRING> it_connection_cl_name(connection_cl_names);
+ List_iterator_fast<LEX_STRING> it_db_cl_name(db_cl_names);
+
+ for (int i = 0; i < trigger_idx; ++i)
+ {
+ it_trigger_name.next_fast();
+ it_sql_mode.next_fast();
+ it_sql_orig_stmt.next_fast();
+
+ it_client_cs_name.next_fast();
+ it_connection_cl_name.next_fast();
+ it_db_cl_name.next_fast();
+ }
+
+ *trigger_name= *(it_trigger_name++);
+ *sql_mode= *(it_sql_mode++);
+ *sql_original_stmt= *(it_sql_orig_stmt++);
+
+ *client_cs_name= *(it_client_cs_name++);
+ *connection_cl_name= *(it_connection_cl_name++);
+ *db_cl_name= *(it_db_cl_name++);
+}
+
+
+int Table_triggers_list::find_trigger_by_name(const LEX_STRING *trg_name)
+{
+ List_iterator_fast<LEX_STRING> it(names_list);
+
+ for (int i = 0; ; ++i)
+ {
+ LEX_STRING *cur_name= it++;
+
+ if (!cur_name)
+ return -1;
+
+ if (strcmp(cur_name->str, trg_name->str) == 0)
+ return i;
+ }
+}
+
+/**
+ Find trigger's table from trigger identifier and add it to
+ the statement table list.
+
+ @param[in] thd Thread context.
+ @param[in] trg_name Trigger name.
+ @param[in] if_exists TRUE if SQL statement contains "IF EXISTS" clause.
+ That means a warning instead of error should be
+ thrown if trigger with given name does not exist.
+ @param[out] table Pointer to TABLE_LIST object for the
+ table trigger.
+
+ @return Operation status
+ @retval FALSE On success.
+ @retval TRUE Otherwise.
+*/
+
+bool add_table_for_trigger(THD *thd,
+ const sp_name *trg_name,
+ bool if_exists,
+ TABLE_LIST **table)
+{
+ LEX *lex= thd->lex;
+ char trn_path_buff[FN_REFLEN];
+ LEX_STRING trn_path= { trn_path_buff, 0 };
+ LEX_STRING tbl_name= null_lex_str;
+
+ DBUG_ENTER("add_table_for_trigger");
+
+ build_trn_path(thd, trg_name, &trn_path);
+
+ if (check_trn_exists(&trn_path))
+ {
+ if (if_exists)
+ {
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_NOTE,
+ ER_TRG_DOES_NOT_EXIST,
+ ER(ER_TRG_DOES_NOT_EXIST));
+
+ *table= NULL;
+
+ DBUG_RETURN(FALSE);
+ }
+
+ my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (load_table_name_for_trigger(thd, trg_name, &trn_path, &tbl_name))
+ DBUG_RETURN(TRUE);
+
+ *table= sp_add_to_query_tables(thd, lex, trg_name->m_db.str,
+ tbl_name.str, TL_IGNORE,
+ MDL_SHARED_NO_WRITE);
+
+ DBUG_RETURN(*table ? FALSE : TRUE);
+}
+
+
+/**
+ Drop all triggers for table.
+
+ @param thd current thread context
+ @param db schema for table
+ @param name name for table
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+
+bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name)
+{
+ TABLE table;
+ char path[FN_REFLEN];
+ bool result= 0;
+ DBUG_ENTER("drop_all_triggers");
+
+ bzero(&table, sizeof(table));
+ init_sql_alloc(&table.mem_root, 8192, 0, MYF(0));
+
+ if (Table_triggers_list::check_n_load(thd, db, name, &table, 1))
+ {
+ result= 1;
+ goto end;
+ }
+ if (table.triggers)
+ {
+ LEX_STRING *trigger;
+ List_iterator_fast<LEX_STRING> it_name(table.triggers->names_list);
+
+ while ((trigger= it_name++))
+ {
+ /*
+ Trigger, which body we failed to parse during call
+ Table_triggers_list::check_n_load(), might be missing name.
+ Such triggers have zero-length name and are skipped here.
+ */
+ if (trigger->length == 0)
+ continue;
+ if (rm_trigname_file(path, db, trigger->str))
+ {
+ /*
+ Instead of immediately bailing out with error if we were unable
+ to remove .TRN file we will try to drop other files.
+ */
+ result= 1;
+ continue;
+ }
+ }
+
+ if (rm_trigger_file(path, db, name))
+ {
+ result= 1;
+ goto end;
+ }
+ }
+end:
+ if (table.triggers)
+ delete table.triggers;
+ free_root(&table.mem_root, MYF(0));
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Update .TRG file after renaming triggers' subject table
+ (change name of table in triggers' definitions).
+
+ @param thd Thread context
+ @param old_db_name Old database of subject table
+ @param new_db_name New database of subject table
+ @param old_table_name Old subject table's name
+ @param new_table_name New subject table's name
+
+ @retval
+ FALSE Success
+ @retval
+ TRUE Failure
+*/
+
+bool
+Table_triggers_list::change_table_name_in_triggers(THD *thd,
+ const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *old_table_name,
+ LEX_STRING *new_table_name)
+{
+ char path_buff[FN_REFLEN];
+ LEX_STRING *def, *on_table_name, new_def;
+ ulonglong save_sql_mode= thd->variables.sql_mode;
+ List_iterator_fast<LEX_STRING> it_def(definitions_list);
+ List_iterator_fast<LEX_STRING> it_on_table_name(on_table_names_list);
+ List_iterator_fast<ulonglong> it_mode(definition_modes_list);
+ size_t on_q_table_name_len, before_on_len;
+ String buff;
+
+ DBUG_ASSERT(definitions_list.elements == on_table_names_list.elements &&
+ definitions_list.elements == definition_modes_list.elements);
+
+ while ((def= it_def++))
+ {
+ on_table_name= it_on_table_name++;
+ thd->variables.sql_mode= (ulong) *(it_mode++);
+
+ /* Construct CREATE TRIGGER statement with new table name. */
+ buff.length(0);
+
+ /* WARNING: 'on_table_name' is supposed to point inside 'def' */
+ DBUG_ASSERT(on_table_name->str > def->str);
+ DBUG_ASSERT(on_table_name->str < (def->str + def->length));
+ before_on_len= on_table_name->str - def->str;
+
+ buff.append(def->str, before_on_len);
+ buff.append(STRING_WITH_LEN("ON "));
+ append_identifier(thd, &buff, new_table_name->str, new_table_name->length);
+ buff.append(STRING_WITH_LEN(" "));
+ on_q_table_name_len= buff.length() - before_on_len;
+ buff.append(on_table_name->str + on_table_name->length,
+ def->length - (before_on_len + on_table_name->length));
+ /*
+ It is OK to allocate some memory on table's MEM_ROOT since this
+ table instance will be thrown out at the end of rename anyway.
+ */
+ new_def.str= (char*) memdup_root(&trigger_table->mem_root, buff.ptr(),
+ buff.length());
+ new_def.length= buff.length();
+ on_table_name->str= new_def.str + before_on_len;
+ on_table_name->length= on_q_table_name_len;
+ *def= new_def;
+ }
+
+ thd->variables.sql_mode= save_sql_mode;
+
+ if (thd->is_fatal_error)
+ return TRUE; /* OOM */
+
+ if (save_trigger_file(this, new_db_name, new_table_name->str))
+ return TRUE;
+ if (rm_trigger_file(path_buff, old_db_name, old_table_name->str))
+ {
+ (void) rm_trigger_file(path_buff, new_db_name, new_table_name->str);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Iterate though Table_triggers_list::names_list list and update
+ .TRN files after renaming triggers' subject table.
+
+ @param old_db_name Old database of subject table
+ @param new_db_name New database of subject table
+ @param new_table_name New subject table's name
+ @param stopper Pointer to Table_triggers_list::names_list at
+ which we should stop updating.
+
+ @retval
+ 0 Success
+ @retval
+ non-0 Failure, pointer to Table_triggers_list::names_list element
+ for which update failed.
+*/
+
+LEX_STRING*
+Table_triggers_list::change_table_name_in_trignames(const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *new_table_name,
+ LEX_STRING *stopper)
+{
+ char trigname_buff[FN_REFLEN];
+ struct st_trigname trigname;
+ LEX_STRING trigname_file;
+ LEX_STRING *trigger;
+ List_iterator_fast<LEX_STRING> it_name(names_list);
+
+ while ((trigger= it_name++) != stopper)
+ {
+ trigname_file.length= build_table_filename(trigname_buff, FN_REFLEN-1,
+ new_db_name, trigger->str,
+ TRN_EXT, 0);
+ trigname_file.str= trigname_buff;
+
+ trigname.trigger_table= *new_table_name;
+
+ if (sql_create_definition_file(NULL, &trigname_file, &trigname_file_type,
+ (uchar*)&trigname, trigname_file_parameters))
+ return trigger;
+
+ /* Remove stale .TRN file in case of database upgrade */
+ if (old_db_name)
+ {
+ if (rm_trigname_file(trigname_buff, old_db_name, trigger->str))
+ {
+ (void) rm_trigname_file(trigname_buff, new_db_name, trigger->str);
+ return trigger;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ Update .TRG and .TRN files after renaming triggers' subject table.
+
+ @param[in,out] thd Thread context
+ @param[in] db Old database of subject table
+ @param[in] old_alias Old alias of subject table
+ @param[in] old_table Old name of subject table
+ @param[in] new_db New database for subject table
+ @param[in] new_table New name of subject table
+
+ @note
+ This method tries to leave trigger related files in consistent state,
+ i.e. it either will complete successfully, or will fail leaving files
+ in their initial state.
+ Also this method assumes that subject table is not renamed to itself.
+ This method needs to be called under an exclusive table metadata lock.
+
+ @retval FALSE Success
+ @retval TRUE Error
+*/
+
+bool Table_triggers_list::change_table_name(THD *thd, const char *db,
+ const char *old_alias,
+ const char *old_table,
+ const char *new_db,
+ const char *new_table)
+{
+ TABLE table;
+ bool result= 0;
+ bool upgrading50to51= FALSE;
+ LEX_STRING *err_trigname;
+ DBUG_ENTER("change_table_name");
+
+ bzero(&table, sizeof(table));
+ init_sql_alloc(&table.mem_root, 8192, 0, MYF(0));
+
+ /*
+ This method interfaces the mysql server code protected by
+ an exclusive metadata lock.
+ */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table,
+ MDL_EXCLUSIVE));
+
+ DBUG_ASSERT(my_strcasecmp(table_alias_charset, db, new_db) ||
+ my_strcasecmp(table_alias_charset, old_alias, new_table));
+
+ if (Table_triggers_list::check_n_load(thd, db, old_table, &table, TRUE))
+ {
+ result= 1;
+ goto end;
+ }
+ if (table.triggers)
+ {
+ if (table.triggers->check_for_broken_triggers())
+ {
+ result= 1;
+ goto end;
+ }
+ LEX_STRING old_table_name= { (char *) old_alias, strlen(old_alias) };
+ LEX_STRING new_table_name= { (char *) new_table, strlen(new_table) };
+ /*
+ Since triggers should be in the same schema as their subject tables
+ moving table with them between two schemas raises too many questions.
+ (E.g. what should happen if in new schema we already have trigger
+ with same name ?).
+
+ In case of "ALTER DATABASE `#mysql50#db1` UPGRADE DATA DIRECTORY NAME"
+ we will be given table name with "#mysql50#" prefix
+ To remove this prefix we use check_n_cut_mysql50_prefix().
+ */
+ if (my_strcasecmp(table_alias_charset, db, new_db))
+ {
+ char dbname[SAFE_NAME_LEN + 1];
+ if (check_n_cut_mysql50_prefix(db, dbname, sizeof(dbname)) &&
+ !my_strcasecmp(table_alias_charset, dbname, new_db))
+ {
+ upgrading50to51= TRUE;
+ }
+ else
+ {
+ my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
+ result= 1;
+ goto end;
+ }
+ }
+ if (table.triggers->change_table_name_in_triggers(thd, db, new_db,
+ &old_table_name,
+ &new_table_name))
+ {
+ result= 1;
+ goto end;
+ }
+ if ((err_trigname= table.triggers->change_table_name_in_trignames(
+ upgrading50to51 ? db : NULL,
+ new_db, &new_table_name, 0)))
+ {
+ /*
+ If we were unable to update one of .TRN files properly we will
+ revert all changes that we have done and report about error.
+ We assume that we will be able to undo our changes without errors
+ (we can't do much if there will be an error anyway).
+ */
+ (void) table.triggers->change_table_name_in_trignames(
+ upgrading50to51 ? new_db : NULL, db,
+ &old_table_name, err_trigname);
+ (void) table.triggers->change_table_name_in_triggers(
+ thd, db, new_db,
+ &new_table_name, &old_table_name);
+ result= 1;
+ goto end;
+ }
+ }
+
+end:
+ delete table.triggers;
+ free_root(&table.mem_root, MYF(0));
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Execute trigger for given (event, time) pair.
+
+ The operation executes trigger for the specified event (insert, update,
+ delete) and time (after, before) if it is set.
+
+ @param thd
+ @param event
+ @param time_type
+ @param old_row_is_record1
+
+ @return Error status.
+ @retval FALSE on success.
+ @retval TRUE on error.
+*/
+
+bool Table_triggers_list::process_triggers(THD *thd,
+ trg_event_type event,
+ trg_action_time_type time_type,
+ bool old_row_is_record1)
+{
+ bool err_status;
+ Sub_statement_state statement_state;
+ sp_head *sp_trigger= bodies[event][time_type];
+ SELECT_LEX *save_current_select;
+
+ if (check_for_broken_triggers())
+ return true;
+
+ if (sp_trigger == NULL)
+ return FALSE;
+
+ status_var_increment(thd->status_var.executed_triggers);
+
+ if (old_row_is_record1)
+ {
+ old_field= record1_field;
+ new_field= trigger_table->field;
+ }
+ else
+ {
+ new_field= record1_field;
+ old_field= trigger_table->field;
+ }
+ /*
+ This trigger must have been processed by the pre-locking
+ algorithm.
+ */
+ DBUG_ASSERT(trigger_table->pos_in_table_list->trg_event_map &
+ static_cast<uint>(1 << static_cast<int>(event)));
+
+ thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
+
+ /*
+ Reset current_select before call execute_trigger() and
+ restore it after return from one. This way error is set
+ in case of failure during trigger execution.
+ */
+ save_current_select= thd->lex->current_select;
+ thd->lex->current_select= NULL;
+ err_status=
+ sp_trigger->execute_trigger(thd,
+ &trigger_table->s->db,
+ &trigger_table->s->table_name,
+ &subject_table_grants[event][time_type]);
+ thd->lex->current_select= save_current_select;
+
+ thd->restore_sub_statement_state(&statement_state);
+
+ return err_status;
+}
+
+
+/**
+ Add triggers for table to the set of routines used by statement.
+ Add tables used by them to statement table list. Do the same for
+ routines used by triggers.
+
+ @param thd Thread context.
+ @param prelocking_ctx Prelocking context of the statement.
+ @param table_list Table list element for table with trigger.
+
+ @retval FALSE Success.
+ @retval TRUE Failure.
+*/
+
+bool
+Table_triggers_list::
+add_tables_and_routines_for_triggers(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list)
+{
+ DBUG_ASSERT(static_cast<int>(table_list->lock_type) >=
+ static_cast<int>(TL_WRITE_ALLOW_WRITE));
+
+ for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
+ {
+ if (table_list->trg_event_map &
+ static_cast<uint8>(1 << static_cast<int>(i)))
+ {
+ for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
+ {
+ /* We can have only one trigger per action type currently */
+ sp_head *trigger= table_list->table->triggers->bodies[i][j];
+
+ if (trigger)
+ {
+ MDL_key key(MDL_key::TRIGGER, trigger->m_db.str, trigger->m_name.str);
+
+ if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
+ &key, table_list->belong_to_view))
+ {
+ trigger->add_used_tables_to_table_list(thd,
+ &prelocking_ctx->query_tables_last,
+ table_list->belong_to_view);
+ sp_update_stmt_used_routines(thd, prelocking_ctx,
+ &trigger->m_sroutines,
+ table_list->belong_to_view);
+ trigger->propagate_attributes(prelocking_ctx);
+ }
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ Check if any of the marked fields are used in the trigger.
+
+ @param used_fields Bitmap over fields to check
+ @param event_type Type of event triggers for which we are going to inspect
+ @param action_time Type of trigger action time we are going to inspect
+*/
+
+bool Table_triggers_list::is_fields_updated_in_trigger(MY_BITMAP *used_fields,
+ trg_event_type event_type,
+ trg_action_time_type action_time)
+{
+ Item_trigger_field *trg_field;
+ sp_head *sp= bodies[event_type][action_time];
+ DBUG_ASSERT(used_fields->n_bits == trigger_table->s->fields);
+
+ for (trg_field= sp->m_trg_table_fields.first; trg_field;
+ trg_field= trg_field->next_trg_field)
+ {
+ /* We cannot check fields which does not present in table. */
+ if (trg_field->field_idx != (uint)-1)
+ {
+ if (bitmap_is_set(used_fields, trg_field->field_idx) &&
+ trg_field->get_settable_routine_parameter())
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ Mark fields of subject table which we read/set in its triggers
+ as such.
+
+ This method marks fields of subject table which are read/set in its
+ triggers as such (by properly updating TABLE::read_set/write_set)
+ and thus informs handler that values for these fields should be
+ retrieved/stored during execution of statement.
+
+ @param thd Current thread context
+ @param event Type of event triggers for which we are going to inspect
+*/
+
+void Table_triggers_list::mark_fields_used(trg_event_type event)
+{
+ int action_time;
+ Item_trigger_field *trg_field;
+
+ for (action_time= 0; action_time < (int)TRG_ACTION_MAX; action_time++)
+ {
+ for (trg_field= trigger_fields[event][action_time]; trg_field;
+ trg_field= trg_field->next_trg_field)
+ {
+ /* We cannot mark fields which does not present in table. */
+ if (trg_field->field_idx != (uint)-1)
+ {
+ bitmap_set_bit(trigger_table->read_set, trg_field->field_idx);
+ if (trg_field->get_settable_routine_parameter())
+ bitmap_set_bit(trigger_table->write_set, trg_field->field_idx);
+ }
+ }
+ }
+ trigger_table->file->column_bitmaps_signal();
+}
+
+
+/**
+ Signals to the Table_triggers_list that a parse error has occured when
+ reading a trigger from file. This makes the Table_triggers_list enter an
+ error state flagged by m_has_unparseable_trigger == true. The error message
+ will be used whenever a statement invoking or manipulating triggers is
+ issued against the Table_triggers_list's table.
+
+ @param error_message The error message thrown by the parser.
+ */
+void Table_triggers_list::set_parse_error_message(char *error_message)
+{
+ m_has_unparseable_trigger= true;
+ strnmov(m_parse_error_message, error_message,
+ sizeof(m_parse_error_message)-1);
+}
+
+
+/**
+ Trigger BUG#14090 compatibility hook.
+
+ @param[in,out] unknown_key reference on the line with unknown
+ parameter and the parsing point
+ @param[in] base base address for parameter writing
+ (structure like TABLE)
+ @param[in] mem_root MEM_ROOT for parameters allocation
+ @param[in] end the end of the configuration
+
+ @note
+ NOTE: this hook process back compatibility for incorrectly written
+ sql_modes parameter (see BUG#14090).
+
+ @retval
+ FALSE OK
+ @retval
+ TRUE Error
+*/
+
+#define INVALID_SQL_MODES_LENGTH 13
+
+bool
+Handle_old_incorrect_sql_modes_hook::process_unknown_string(char *&unknown_key,
+ uchar* base,
+ MEM_ROOT *mem_root,
+ char *end)
+{
+ DBUG_ENTER("Handle_old_incorrect_sql_modes_hook::process_unknown_string");
+ DBUG_PRINT("info", ("unknown key: %60s", unknown_key));
+
+ if (unknown_key + INVALID_SQL_MODES_LENGTH + 1 < end &&
+ unknown_key[INVALID_SQL_MODES_LENGTH] == '=' &&
+ !memcmp(unknown_key, STRING_WITH_LEN("sql_modes")))
+ {
+ char *ptr= unknown_key + INVALID_SQL_MODES_LENGTH + 1;
+
+ DBUG_PRINT("info", ("sql_modes affected by BUG#14090 detected"));
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_NOTE,
+ ER_OLD_FILE_FORMAT,
+ ER(ER_OLD_FILE_FORMAT),
+ (char *)path, "TRIGGER");
+ if (get_file_options_ulllist(ptr, end, unknown_key, base,
+ &sql_modes_parameters, mem_root))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Set parsing pointer to the last symbol of string (\n)
+ 1) to avoid problem with \0 in the junk after sql_modes
+ 2) to speed up skipping this line by parser.
+ */
+ unknown_key= ptr-1;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+#define INVALID_TRIGGER_TABLE_LENGTH 15
+
+/**
+ Trigger BUG#15921 compatibility hook. For details see
+ Handle_old_incorrect_sql_modes_hook::process_unknown_string().
+*/
+bool
+Handle_old_incorrect_trigger_table_hook::
+process_unknown_string(char *&unknown_key, uchar* base, MEM_ROOT *mem_root,
+ char *end)
+{
+ DBUG_ENTER("Handle_old_incorrect_trigger_table_hook::process_unknown_string");
+ DBUG_PRINT("info", ("unknown key: %60s", unknown_key));
+
+ if (unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1 < end &&
+ unknown_key[INVALID_TRIGGER_TABLE_LENGTH] == '=' &&
+ !memcmp(unknown_key, STRING_WITH_LEN("trigger_table")))
+ {
+ char *ptr= unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1;
+
+ DBUG_PRINT("info", ("trigger_table affected by BUG#15921 detected"));
+ push_warning_printf(current_thd,
+ Sql_condition::WARN_LEVEL_NOTE,
+ ER_OLD_FILE_FORMAT,
+ ER(ER_OLD_FILE_FORMAT),
+ (char *)path, "TRIGGER");
+
+ if (!(ptr= parse_escaped_string(ptr, end, mem_root, trigger_table_value)))
+ {
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), "trigger_table",
+ unknown_key);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Set parsing pointer to the last symbol of string (\n). */
+ unknown_key= ptr-1;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Contruct path to TRN-file.
+
+ @param thd[in] Thread context.
+ @param trg_name[in] Trigger name.
+ @param trn_path[out] Variable to store constructed path
+*/
+
+void build_trn_path(THD *thd, const sp_name *trg_name, LEX_STRING *trn_path)
+{
+ /* Construct path to the TRN-file. */
+
+ trn_path->length= build_table_filename(trn_path->str,
+ FN_REFLEN - 1,
+ trg_name->m_db.str,
+ trg_name->m_name.str,
+ TRN_EXT,
+ 0);
+}
+
+
+/**
+ Check if TRN-file exists.
+
+ @return
+ @retval TRUE if TRN-file does not exist.
+ @retval FALSE if TRN-file exists.
+*/
+
+bool check_trn_exists(const LEX_STRING *trn_path)
+{
+ return access(trn_path->str, F_OK) != 0;
+}
+
+
+/**
+ Retrieve table name for given trigger.
+
+ @param thd[in] Thread context.
+ @param trg_name[in] Trigger name.
+ @param trn_path[in] Path to the corresponding TRN-file.
+ @param tbl_name[out] Variable to store retrieved table name.
+
+ @return Error status.
+ @retval FALSE on success.
+ @retval TRUE if table name could not be retrieved.
+*/
+
+bool load_table_name_for_trigger(THD *thd,
+ const sp_name *trg_name,
+ const LEX_STRING *trn_path,
+ LEX_STRING *tbl_name)
+{
+ File_parser *parser;
+ struct st_trigname trn_data;
+
+ Handle_old_incorrect_trigger_table_hook trigger_table_hook(
+ trn_path->str,
+ &trn_data.trigger_table);
+
+ DBUG_ENTER("load_table_name_for_trigger");
+
+ /* Parse the TRN-file. */
+
+ if (!(parser= sql_parse_prepare(trn_path, thd->mem_root, TRUE)))
+ DBUG_RETURN(TRUE);
+
+ if (!is_equal(&trigname_file_type, parser->type()))
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0),
+ trg_name->m_name.str,
+ TRN_EXT + 1,
+ "TRIGGERNAME");
+
+ DBUG_RETURN(TRUE);
+ }
+
+ if (parser->parse((uchar*) &trn_data, thd->mem_root,
+ trigname_file_parameters, 1,
+ &trigger_table_hook))
+ DBUG_RETURN(TRUE);
+
+ /* Copy trigger table name. */
+
+ *tbl_name= trn_data.trigger_table;
+
+ /* That's all. */
+
+ DBUG_RETURN(FALSE);
+}
diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h
new file mode 100644
index 00000000000..52892550d35
--- /dev/null
+++ b/sql/sql_trigger.h
@@ -0,0 +1,258 @@
+#ifndef SQL_TRIGGER_INCLUDED
+#define SQL_TRIGGER_INCLUDED
+
+/*
+ Copyright (c) 2004, 2011, Oracle and/or its affiliates.
+
+ 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 */
+
+/* Forward declarations */
+
+class Item_trigger_field;
+class sp_head;
+class sp_name;
+class Query_tables_list;
+struct TABLE_LIST;
+class Query_tables_list;
+
+/** Event on which trigger is invoked. */
+enum trg_event_type
+{
+ TRG_EVENT_INSERT= 0,
+ TRG_EVENT_UPDATE= 1,
+ TRG_EVENT_DELETE= 2,
+ TRG_EVENT_MAX
+};
+
+#include "table.h" /* GRANT_INFO */
+
+/*
+ We need this two enums here instead of sql_lex.h because
+ at least one of them is used by Item_trigger_field interface.
+
+ Time when trigger is invoked (i.e. before or after row actually
+ inserted/updated/deleted).
+*/
+enum trg_action_time_type
+{
+ TRG_ACTION_BEFORE= 0, TRG_ACTION_AFTER= 1, TRG_ACTION_MAX
+};
+
+
+/**
+ This class holds all information about triggers of table.
+
+ TODO: Will it be merged into TABLE in the future ?
+*/
+
+class Table_triggers_list: public Sql_alloc
+{
+ /** Triggers as SPs grouped by event, action_time */
+ sp_head *bodies[TRG_EVENT_MAX][TRG_ACTION_MAX];
+ /**
+ Heads of the lists linking items for all fields used in triggers
+ grouped by event and action_time.
+ */
+ Item_trigger_field *trigger_fields[TRG_EVENT_MAX][TRG_ACTION_MAX];
+ /**
+ Copy of TABLE::Field array with field pointers set to TABLE::record[1]
+ buffer instead of TABLE::record[0] (used for OLD values in on UPDATE
+ trigger and DELETE trigger when it is called for REPLACE).
+ */
+ Field **record1_field;
+ /**
+ During execution of trigger new_field and old_field should point to the
+ array of fields representing new or old version of row correspondingly
+ (so it can point to TABLE::field or to Tale_triggers_list::record1_field)
+ */
+ Field **new_field;
+ Field **old_field;
+
+ /* TABLE instance for which this triggers list object was created */
+ TABLE *trigger_table;
+ /**
+ Names of triggers.
+ Should correspond to order of triggers on definitions_list,
+ used in CREATE/DROP TRIGGER for looking up trigger by name.
+ */
+ List<LEX_STRING> names_list;
+ /**
+ List of "ON table_name" parts in trigger definitions, used for
+ updating trigger definitions during RENAME TABLE.
+ */
+ List<LEX_STRING> on_table_names_list;
+
+ /**
+ Grant information for each trigger (pair: subject table, trigger definer).
+ */
+ GRANT_INFO subject_table_grants[TRG_EVENT_MAX][TRG_ACTION_MAX];
+
+ /**
+ This flag indicates that one of the triggers was not parsed successfully,
+ and as a precaution the object has entered a state where all trigger
+ access results in errors until all such triggers are dropped. It is not
+ safe to add triggers since we don't know if the broken trigger has the
+ same name or event type. Nor is it safe to invoke any trigger for the
+ aforementioned reasons. The only safe operations are drop_trigger and
+ drop_all_triggers.
+
+ @see Table_triggers_list::set_parse_error
+ */
+ bool m_has_unparseable_trigger;
+
+ /**
+ This error will be displayed when the user tries to manipulate or invoke
+ triggers on a table that has broken triggers. It will get set only once
+ per statement and thus will contain the first parse error encountered in
+ the trigger file.
+ */
+ char m_parse_error_message[MYSQL_ERRMSG_SIZE];
+
+public:
+ /**
+ Field responsible for storing triggers definitions in file.
+ It have to be public because we are using it directly from parser.
+ */
+ List<LEX_STRING> definitions_list;
+ /**
+ List of sql modes for triggers
+ */
+ List<ulonglong> definition_modes_list;
+
+ List<LEX_STRING> definers_list;
+
+ /* Character set context, used for parsing and executing triggers. */
+
+ List<LEX_STRING> client_cs_names;
+ List<LEX_STRING> connection_cl_names;
+ List<LEX_STRING> db_cl_names;
+
+ /* End of character ser context. */
+
+ Table_triggers_list(TABLE *table_arg)
+ :record1_field(0), trigger_table(table_arg),
+ m_has_unparseable_trigger(false)
+ {
+ bzero((char *)bodies, sizeof(bodies));
+ bzero((char *)trigger_fields, sizeof(trigger_fields));
+ bzero((char *)&subject_table_grants, sizeof(subject_table_grants));
+ }
+ ~Table_triggers_list();
+
+ bool create_trigger(THD *thd, TABLE_LIST *table, String *stmt_query);
+ bool drop_trigger(THD *thd, TABLE_LIST *table, String *stmt_query);
+ bool process_triggers(THD *thd, trg_event_type event,
+ trg_action_time_type time_type,
+ bool old_row_is_record1);
+
+ bool get_trigger_info(THD *thd, trg_event_type event,
+ trg_action_time_type time_type,
+ LEX_STRING *trigger_name, LEX_STRING *trigger_stmt,
+ ulong *sql_mode,
+ LEX_STRING *definer,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name);
+
+ void get_trigger_info(THD *thd,
+ int trigger_idx,
+ LEX_STRING *trigger_name,
+ ulonglong *sql_mode,
+ LEX_STRING *sql_original_stmt,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name);
+
+ int find_trigger_by_name(const LEX_STRING *trigger_name);
+
+ static bool check_n_load(THD *thd, const char *db, const char *table_name,
+ TABLE *table, bool names_only);
+ static bool drop_all_triggers(THD *thd, char *db, char *table_name);
+ static bool change_table_name(THD *thd, const char *db,
+ const char *old_alias,
+ const char *old_table,
+ const char *new_db,
+ const char *new_table);
+ bool has_triggers(trg_event_type event_type,
+ trg_action_time_type action_time)
+ {
+ return (bodies[event_type][action_time] != NULL);
+ }
+ bool has_delete_triggers()
+ {
+ return (bodies[TRG_EVENT_DELETE][TRG_ACTION_BEFORE] ||
+ bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]);
+ }
+
+ void set_table(TABLE *new_table);
+
+ void mark_fields_used(trg_event_type event);
+
+ void set_parse_error_message(char *error_message);
+
+ friend class Item_trigger_field;
+
+ bool add_tables_and_routines_for_triggers(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list);
+
+ bool is_fields_updated_in_trigger(MY_BITMAP *used_fields,
+ trg_event_type event_type,
+ trg_action_time_type action_time);
+
+private:
+ bool prepare_record1_accessors(TABLE *table);
+ LEX_STRING* change_table_name_in_trignames(const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *new_table_name,
+ LEX_STRING *stopper);
+ bool change_table_name_in_triggers(THD *thd,
+ const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *old_table_name,
+ LEX_STRING *new_table_name);
+
+ bool check_for_broken_triggers()
+ {
+ if (m_has_unparseable_trigger)
+ {
+ my_message(ER_PARSE_ERROR, m_parse_error_message, MYF(0));
+ return true;
+ }
+ return false;
+ }
+};
+
+extern const LEX_STRING trg_action_time_type_names[];
+extern const LEX_STRING trg_event_type_names[];
+
+bool add_table_for_trigger(THD *thd,
+ const sp_name *trg_name,
+ bool continue_if_not_exist,
+ TABLE_LIST **table);
+
+void build_trn_path(THD *thd, const sp_name *trg_name, LEX_STRING *trn_path);
+
+bool check_trn_exists(const LEX_STRING *trn_path);
+
+bool load_table_name_for_trigger(THD *thd,
+ const sp_name *trg_name,
+ const LEX_STRING *trn_path,
+ LEX_STRING *tbl_name);
+bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create);
+
+extern const char * const TRG_EXT;
+extern const char * const TRN_EXT;
+
+#endif /* SQL_TRIGGER_INCLUDED */
diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc
new file mode 100644
index 00000000000..05869b70c8f
--- /dev/null
+++ b/sql/sql_truncate.cc
@@ -0,0 +1,571 @@
+/* Copyright (c) 2010, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2013, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#include "debug_sync.h" // DEBUG_SYNC
+#include "table.h" // TABLE, FOREIGN_KEY_INFO
+#include "sql_class.h" // THD
+#include "sql_base.h" // open_and_lock_tables
+#include "sql_table.h" // write_bin_log
+#include "datadict.h" // dd_recreate_table()
+#include "lock.h" // MYSQL_OPEN_* flags
+#include "sql_acl.h" // DROP_ACL
+#include "sql_parse.h" // check_one_table_access()
+#include "sql_truncate.h"
+#include "sql_show.h" //append_identifier()
+
+
+/**
+ Append a list of field names to a string.
+
+ @param str The string.
+ @param fields The list of field names.
+
+ @return TRUE on failure, FALSE otherwise.
+*/
+
+static bool fk_info_append_fields(THD *thd, String *str,
+ List<LEX_STRING> *fields)
+{
+ bool res= FALSE;
+ LEX_STRING *field;
+ List_iterator_fast<LEX_STRING> it(*fields);
+
+ while ((field= it++))
+ {
+ res|= append_identifier(thd, str, field->str, field->length);
+ res|= str->append(", ");
+ }
+
+ str->chop();
+ str->chop();
+
+ return res;
+}
+
+
+/**
+ Generate a foreign key description suitable for a error message.
+
+ @param thd Thread context.
+ @param fk_info The foreign key information.
+
+ @return A human-readable string describing the foreign key.
+*/
+
+static const char *fk_info_str(THD *thd, FOREIGN_KEY_INFO *fk_info)
+{
+ bool res= FALSE;
+ char buffer[STRING_BUFFER_USUAL_SIZE*2];
+ String str(buffer, sizeof(buffer), system_charset_info);
+
+ str.length(0);
+
+ /*
+ `db`.`tbl`, CONSTRAINT `id` FOREIGN KEY (`fk`) REFERENCES `db`.`tbl` (`fk`)
+ */
+
+ res|= append_identifier(thd, &str, fk_info->foreign_db->str,
+ fk_info->foreign_db->length);
+ res|= str.append(".");
+ res|= append_identifier(thd, &str, fk_info->foreign_table->str,
+ fk_info->foreign_table->length);
+ res|= str.append(", CONSTRAINT ");
+ res|= append_identifier(thd, &str, fk_info->foreign_id->str,
+ fk_info->foreign_id->length);
+ res|= str.append(" FOREIGN KEY (");
+ res|= fk_info_append_fields(thd, &str, &fk_info->foreign_fields);
+ res|= str.append(") REFERENCES ");
+ res|= append_identifier(thd, &str, fk_info->referenced_db->str,
+ fk_info->referenced_db->length);
+ res|= str.append(".");
+ res|= append_identifier(thd, &str, fk_info->referenced_table->str,
+ fk_info->referenced_table->length);
+ res|= str.append(" (");
+ res|= fk_info_append_fields(thd, &str, &fk_info->referenced_fields);
+ res|= str.append(')');
+
+ return res ? NULL : thd->strmake(str.ptr(), str.length());
+}
+
+
+/**
+ Check and emit a fatal error if the table which is going to be
+ affected by TRUNCATE TABLE is a parent table in some non-self-
+ referencing foreign key.
+
+ @remark The intention is to allow truncate only for tables that
+ are not dependent on other tables.
+
+ @param thd Thread context.
+ @param table Table handle.
+
+ @retval FALSE This table is not parent in a non-self-referencing foreign
+ key. Statement can proceed.
+ @retval TRUE This table is parent in a non-self-referencing foreign key,
+ error was emitted.
+*/
+
+static bool
+fk_truncate_illegal_if_parent(THD *thd, TABLE *table)
+{
+ FOREIGN_KEY_INFO *fk_info;
+ List<FOREIGN_KEY_INFO> fk_list;
+ List_iterator_fast<FOREIGN_KEY_INFO> it;
+
+ /*
+ Bail out early if the table is not referenced by a foreign key.
+ In this case, the table could only be, if at all, a child table.
+ */
+ if (! table->file->referenced_by_foreign_key())
+ return FALSE;
+
+ /*
+ This table _is_ referenced by a foreign key. At this point, only
+ self-referencing keys are acceptable. For this reason, get the list
+ of foreign keys referencing this table in order to check the name
+ of the child (dependent) tables.
+ */
+ table->file->get_parent_foreign_key_list(thd, &fk_list);
+
+ /* Out of memory when building list. */
+ if (thd->is_error())
+ return TRUE;
+
+ it.init(fk_list);
+
+ /* Loop over the set of foreign keys for which this table is a parent. */
+ while ((fk_info= it++))
+ {
+ DBUG_ASSERT(!my_strcasecmp(system_charset_info,
+ fk_info->referenced_db->str,
+ table->s->db.str));
+
+ DBUG_ASSERT(!my_strcasecmp(system_charset_info,
+ fk_info->referenced_table->str,
+ table->s->table_name.str));
+
+ if (my_strcasecmp(system_charset_info, fk_info->foreign_db->str,
+ table->s->db.str) ||
+ my_strcasecmp(system_charset_info, fk_info->foreign_table->str,
+ table->s->table_name.str))
+ break;
+ }
+
+ /* Table is parent in a non-self-referencing foreign key. */
+ if (fk_info)
+ {
+ my_error(ER_TRUNCATE_ILLEGAL_FK, MYF(0), fk_info_str(thd, fk_info));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/*
+ Open and truncate a locked table.
+
+ @param thd Thread context.
+ @param table_ref Table list element for the table to be truncated.
+ @param is_tmp_table True if element refers to a temp table.
+
+ @retval TRUNCATE_OK Truncate was successful and statement can be safely
+ binlogged.
+ @retval TRUNCATE_FAILED_BUT_BINLOG Truncate failed but still go ahead with
+ binlogging as in case of non transactional tables
+ partial truncation is possible.
+
+ @retval TRUNCATE_FAILED_SKIP_BINLOG Truncate was not successful hence donot
+ binlong the statement.
+*/
+
+enum Sql_cmd_truncate_table::truncate_result
+Sql_cmd_truncate_table::handler_truncate(THD *thd, TABLE_LIST *table_ref,
+ bool is_tmp_table)
+{
+ int error= 0;
+ uint flags= 0;
+ DBUG_ENTER("Sql_cmd_truncate_table::handler_truncate");
+
+ /*
+ Can't recreate, the engine must mechanically delete all rows
+ in the table. Use open_and_lock_tables() to open a write cursor.
+ */
+
+ /* If it is a temporary table, no need to take locks. */
+ if (!is_tmp_table)
+ {
+ /* We don't need to load triggers. */
+ DBUG_ASSERT(table_ref->trg_event_map == 0);
+ /*
+ Our metadata lock guarantees that no transaction is reading
+ or writing into the table. Yet, to open a write cursor we need
+ a thr_lock lock. Allow to open base tables only.
+ */
+ table_ref->required_type= FRMTYPE_TABLE;
+ /*
+ Ignore pending FLUSH TABLES since we don't want to release
+ the MDL lock taken above and otherwise there is no way to
+ wait for FLUSH TABLES in deadlock-free fashion.
+ */
+ flags= MYSQL_OPEN_IGNORE_FLUSH;
+ /*
+ Even though we have an MDL lock on the table here, we don't
+ pass MYSQL_OPEN_HAS_MDL_LOCK to open_and_lock_tables
+ since to truncate a MERGE table, we must open and lock
+ merge children, and on those we don't have an MDL lock.
+ Thus clear the ticket to satisfy MDL asserts.
+ */
+ table_ref->mdl_request.ticket= NULL;
+ }
+
+ /* Open the table as it will handle some required preparations. */
+ if (open_and_lock_tables(thd, table_ref, FALSE, flags))
+ DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
+
+ /* Whether to truncate regardless of foreign keys. */
+ if (! (thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
+ if (fk_truncate_illegal_if_parent(thd, table_ref->table))
+ DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
+
+ error= table_ref->table->file->ha_truncate();
+ if (error)
+ {
+ table_ref->table->file->print_error(error, MYF(0));
+ /*
+ If truncate method is not implemented then we don't binlog the
+ statement. If truncation has failed in a transactional engine then also we
+ donot binlog the statment. Only in non transactional engine we binlog
+ inspite of errors.
+ */
+ if (error == HA_ERR_WRONG_COMMAND ||
+ table_ref->table->file->has_transactions())
+ DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
+ else
+ DBUG_RETURN(TRUNCATE_FAILED_BUT_BINLOG);
+ }
+ DBUG_RETURN(TRUNCATE_OK);
+}
+
+
+/*
+ Close and recreate a temporary table. In case of success,
+ write truncate statement into the binary log if in statement
+ mode.
+
+ @param thd Thread context.
+ @param table The temporary table.
+
+ @retval FALSE Success.
+ @retval TRUE Error.
+*/
+
+static bool recreate_temporary_table(THD *thd, TABLE *table)
+{
+ bool error= TRUE;
+ TABLE_SHARE *share= table->s;
+ handlerton *table_type= table->s->db_type();
+ TABLE *new_table;
+ DBUG_ENTER("recreate_temporary_table");
+
+ table->file->info(HA_STATUS_AUTO | HA_STATUS_NO_LOCK);
+
+ /*
+ If LOCK TABLES list is not empty and contains this table
+ then unlock the table and remove it from this list.
+ */
+ mysql_lock_remove(thd, thd->lock, table);
+
+ /* Don't free share. */
+ close_temporary_table(thd, table, FALSE, FALSE);
+
+ dd_recreate_table(thd, share->db.str, share->table_name.str,
+ share->normalized_path.str);
+
+ if ((new_table= open_table_uncached(thd, table_type, share->path.str,
+ share->db.str,
+ share->table_name.str, true, true)))
+ {
+ error= FALSE;
+ thd->thread_specific_used= TRUE;
+ new_table->s->table_creation_was_logged= share->table_creation_was_logged;
+ }
+ else
+ rm_temporary_table(table_type, share->path.str);
+
+ free_table_share(share);
+ my_free(table);
+
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Handle locking a base table for truncate.
+
+ @param[in] thd Thread context.
+ @param[in] table_ref Table list element for the table to
+ be truncated.
+ @param[out] hton_can_recreate Set to TRUE if table can be dropped
+ and recreated.
+
+ @retval FALSE Success.
+ @retval TRUE Error.
+*/
+
+bool Sql_cmd_truncate_table::lock_table(THD *thd, TABLE_LIST *table_ref,
+ bool *hton_can_recreate)
+{
+ TABLE *table= NULL;
+ DBUG_ENTER("Sql_cmd_truncate_table::lock_table");
+
+ /* Lock types are set in the parser. */
+ DBUG_ASSERT(table_ref->lock_type == TL_WRITE);
+ /* The handler truncate protocol dictates a exclusive lock. */
+ DBUG_ASSERT(table_ref->mdl_request.type == MDL_EXCLUSIVE);
+
+ /*
+ Before doing anything else, acquire a metadata lock on the table,
+ or ensure we have one. We don't use open_and_lock_tables()
+ right away because we want to be able to truncate (and recreate)
+ corrupted tables, those that we can't fully open.
+
+ MySQL manual documents that TRUNCATE can be used to repair a
+ damaged table, i.e. a table that can not be fully "opened".
+ In particular MySQL manual says: As long as the table format
+ file tbl_name.frm is valid, the table can be re-created as
+ an empty table with TRUNCATE TABLE, even if the data or index
+ files have become corrupted.
+ */
+ if (thd->locked_tables_mode)
+ {
+ if (!(table= find_table_for_mdl_upgrade(thd, table_ref->db,
+ table_ref->table_name, FALSE)))
+ DBUG_RETURN(TRUE);
+
+ *hton_can_recreate= ha_check_storage_engine_flag(table->s->db_type(),
+ HTON_CAN_RECREATE);
+ table_ref->mdl_request.ticket= table->mdl_ticket;
+ }
+ else
+ {
+ /* Acquire an exclusive lock. */
+ DBUG_ASSERT(table_ref->next_global == NULL);
+ if (lock_table_names(thd, table_ref, NULL,
+ thd->variables.lock_wait_timeout, 0))
+ DBUG_RETURN(TRUE);
+
+ handlerton *hton;
+ if (!ha_table_exists(thd, table_ref->db, table_ref->table_name, &hton) ||
+ hton == view_pseudo_hton)
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), table_ref->db, table_ref->table_name);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (!hton)
+ {
+ /*
+ The table exists, but its storage engine is unknown, perhaps not
+ loaded at the moment. We need to open and parse the frm to know the
+ storage engine in question, so let's proceed with the truncation and
+ try to open the table. This will produce the correct error message
+ about unknown engine.
+ */
+ *hton_can_recreate= false;
+ }
+ else
+ *hton_can_recreate= hton->flags & HTON_CAN_RECREATE;
+ }
+
+ /*
+ A storage engine can recreate or truncate the table only if there
+ are no references to it from anywhere, i.e. no cached TABLE in the
+ table cache.
+ */
+ if (thd->locked_tables_mode)
+ {
+ DEBUG_SYNC(thd, "upgrade_lock_for_truncate");
+ /* To remove the table from the cache we need an exclusive lock. */
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DROP))
+ DBUG_RETURN(TRUE);
+ m_ticket_downgrade= table->mdl_ticket;
+ /* Close if table is going to be recreated. */
+ if (*hton_can_recreate)
+ close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
+ }
+ else
+ {
+ /* Table is already locked exclusively. Remove cached instances. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_ref->db,
+ table_ref->table_name, FALSE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Optimized delete of all rows by doing a full generate of the table.
+
+ @remark Will work even if the .MYI and .MYD files are destroyed.
+ In other words, it works as long as the .FRM is intact and
+ the engine supports re-create.
+
+ @param thd Thread context.
+ @param table_ref Table list element for the table to be truncated.
+
+ @retval FALSE Success.
+ @retval TRUE Error.
+*/
+
+bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref)
+{
+ int error;
+ bool binlog_stmt;
+ DBUG_ENTER("Sql_cmd_truncate_table::truncate_table");
+
+ DBUG_ASSERT((!table_ref->table) ||
+ (table_ref->table && table_ref->table->s));
+
+ /* Initialize, or reinitialize in case of reexecution (SP). */
+ m_ticket_downgrade= NULL;
+
+ /* If it is a temporary table, no need to take locks. */
+ if (is_temporary_table(table_ref))
+ {
+ TABLE *tmp_table= table_ref->table;
+
+ /* In RBR, the statement is not binlogged if the table is temporary. */
+ binlog_stmt= !thd->is_current_stmt_binlog_format_row();
+
+ /* Note that a temporary table cannot be partitioned. */
+ if (ha_check_storage_engine_flag(tmp_table->s->db_type(),
+ HTON_CAN_RECREATE))
+ {
+ if ((error= recreate_temporary_table(thd, tmp_table)))
+ binlog_stmt= FALSE; /* No need to binlog failed truncate-by-recreate. */
+
+ DBUG_ASSERT(! thd->transaction.stmt.modified_non_trans_table);
+ }
+ else
+ {
+ /*
+ The engine does not support truncate-by-recreate. Open the
+ table and invoke the handler truncate. In such a manner this
+ can in fact open several tables if it's a temporary MyISAMMRG
+ table.
+ */
+ error= handler_truncate(thd, table_ref, TRUE);
+ }
+
+ /*
+ No need to invalidate the query cache, queries with temporary
+ tables are not in the cache. No need to write to the binary
+ log a failed row-by-row delete even if under RBR as the table
+ might not exist on the slave.
+ */
+ }
+ else /* It's not a temporary table. */
+ {
+ bool hton_can_recreate;
+
+ if (lock_table(thd, table_ref, &hton_can_recreate))
+ DBUG_RETURN(TRUE);
+
+ if (hton_can_recreate)
+ {
+ /*
+ The storage engine can truncate the table by creating an
+ empty table with the same structure.
+ */
+ error= dd_recreate_table(thd, table_ref->db, table_ref->table_name);
+
+ if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd))
+ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
+
+ /* No need to binlog a failed truncate-by-recreate. */
+ binlog_stmt= !error;
+ }
+ else
+ {
+ /*
+ The engine does not support truncate-by-recreate.
+ Attempt to use the handler truncate method.
+ */
+ error= handler_truncate(thd, table_ref, FALSE);
+
+ /*
+ All effects of a TRUNCATE TABLE operation are committed even if
+ truncation fails in the case of non transactional tables. Thus, the
+ query must be written to the binary log. The only exception is a
+ unimplemented truncate method.
+ */
+ if (error == TRUNCATE_OK || error == TRUNCATE_FAILED_BUT_BINLOG)
+ binlog_stmt= true;
+ else
+ binlog_stmt= false;
+ }
+
+ /*
+ If we tried to open a MERGE table and failed due to problems with the
+ children tables, the table will have been closed and table_ref->table
+ will be invalid. Reset the pointer here in any case as
+ query_cache_invalidate does not need a valid TABLE object.
+ */
+ table_ref->table= NULL;
+ query_cache_invalidate3(thd, table_ref, FALSE);
+ }
+
+ /* DDL is logged in statement format, regardless of binlog format. */
+ if (binlog_stmt)
+ error|= write_bin_log(thd, !error, thd->query(), thd->query_length());
+
+ /*
+ A locked table ticket was upgraded to a exclusive lock. After the
+ the query has been written to the binary log, downgrade the lock
+ to a shared one.
+ */
+ if (m_ticket_downgrade)
+ m_ticket_downgrade->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Execute a TRUNCATE statement at runtime.
+
+ @param thd The current thread.
+
+ @return FALSE on success.
+*/
+
+bool Sql_cmd_truncate_table::execute(THD *thd)
+{
+ bool res= TRUE;
+ TABLE_LIST *first_table= thd->lex->select_lex.table_list.first;
+ DBUG_ENTER("Sql_cmd_truncate_table::execute");
+
+ if (check_one_table_access(thd, DROP_ACL, first_table))
+ DBUG_RETURN(res);
+
+ if (! (res= truncate_table(thd, first_table)))
+ my_ok(thd);
+
+ DBUG_RETURN(res);
+}
+
diff --git a/sql/sql_truncate.h b/sql/sql_truncate.h
new file mode 100644
index 00000000000..b8525fd6abb
--- /dev/null
+++ b/sql/sql_truncate.h
@@ -0,0 +1,73 @@
+#ifndef SQL_TRUNCATE_INCLUDED
+#define SQL_TRUNCATE_INCLUDED
+/* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+class THD;
+struct TABLE_LIST;
+
+/**
+ Sql_cmd_truncate_table represents the TRUNCATE statement.
+*/
+class Sql_cmd_truncate_table : public Sql_cmd
+{
+private:
+ /* Set if a lock must be downgraded after truncate is done. */
+ MDL_ticket *m_ticket_downgrade;
+
+public:
+ /**
+ Constructor, used to represent a TRUNCATE statement.
+ */
+ Sql_cmd_truncate_table()
+ {}
+
+ virtual ~Sql_cmd_truncate_table()
+ {}
+
+ /**
+ Execute a TRUNCATE statement at runtime.
+ @param thd the current thread.
+ @return false on success.
+ */
+ bool execute(THD *thd);
+
+ virtual enum_sql_command sql_command_code() const
+ {
+ return SQLCOM_TRUNCATE;
+ }
+
+protected:
+ enum truncate_result{
+ TRUNCATE_OK=0,
+ TRUNCATE_FAILED_BUT_BINLOG,
+ TRUNCATE_FAILED_SKIP_BINLOG
+ };
+
+ /** Handle locking a base table for truncate. */
+ bool lock_table(THD *, TABLE_LIST *, bool *);
+
+ /** Truncate table via the handler method. */
+ enum truncate_result handler_truncate(THD *, TABLE_LIST *, bool);
+
+ /**
+ Optimized delete of all rows by doing a full regenerate of the table.
+ Depending on the storage engine, it can be accomplished through a
+ drop and recreate or via the handler truncate method.
+ */
+ bool truncate_table(THD *, TABLE_LIST *);
+};
+
+#endif
diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc
new file mode 100644
index 00000000000..bd5732c3696
--- /dev/null
+++ b/sql/sql_udf.cc
@@ -0,0 +1,615 @@
+/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+
+ 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 */
+
+/* This implements 'user defined functions' */
+
+/*
+ Known bugs:
+
+ Memory for functions is never freed!
+ Shared libraries are not closed before mysqld exits;
+ - This is because we can't be sure if some threads are using
+ a function.
+
+ The bugs only affect applications that create and free a lot of
+ dynamic functions, so this shouldn't be a real problem.
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_base.h" // close_mysql_tables
+#include "sql_parse.h" // check_identifier_name
+#include "sql_table.h" // write_bin_log
+#include "records.h" // init_read_record, end_read_record
+#include <my_pthread.h>
+#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
+
+#ifdef HAVE_DLOPEN
+extern "C"
+{
+#include <stdarg.h>
+#include <hash.h>
+}
+
+static bool initialized = 0;
+static MEM_ROOT mem;
+static HASH udf_hash;
+static mysql_rwlock_t THR_LOCK_udf;
+
+
+static udf_func *add_udf(LEX_STRING *name, Item_result ret,
+ char *dl, Item_udftype typ);
+static void del_udf(udf_func *udf);
+static void *find_udf_dl(const char *dl);
+
+static char *init_syms(udf_func *tmp, char *nm)
+{
+ char *end;
+
+ if (!((tmp->func= (Udf_func_any) dlsym(tmp->dlhandle, tmp->name.str))))
+ return tmp->name.str;
+
+ end=strmov(nm,tmp->name.str);
+
+ if (tmp->type == UDFTYPE_AGGREGATE)
+ {
+ (void)strmov(end, "_clear");
+ if (!((tmp->func_clear= (Udf_func_clear) dlsym(tmp->dlhandle, nm))))
+ return nm;
+ (void)strmov(end, "_add");
+ if (!((tmp->func_add= (Udf_func_add) dlsym(tmp->dlhandle, nm))))
+ return nm;
+ }
+
+ (void) strmov(end,"_deinit");
+ tmp->func_deinit= (Udf_func_deinit) dlsym(tmp->dlhandle, nm);
+
+ (void) strmov(end,"_init");
+ tmp->func_init= (Udf_func_init) dlsym(tmp->dlhandle, nm);
+
+ /*
+ to prefent loading "udf" from, e.g. libc.so
+ let's ensure that at least one auxiliary symbol is defined
+ */
+ if (!tmp->func_init && !tmp->func_deinit && tmp->type != UDFTYPE_AGGREGATE)
+ {
+ if (!opt_allow_suspicious_udfs)
+ return nm;
+ if (current_thd->variables.log_warnings)
+ sql_print_warning(ER(ER_CANT_FIND_DL_ENTRY), nm);
+ }
+ return 0;
+}
+
+
+extern "C" uchar* get_hash_key(const uchar *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ udf_func *udf=(udf_func*) buff;
+ *length=(uint) udf->name.length;
+ return (uchar*) udf->name.str;
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_rwlock_key key_rwlock_THR_LOCK_udf;
+
+static PSI_rwlock_info all_udf_rwlocks[]=
+{
+ { &key_rwlock_THR_LOCK_udf, "THR_LOCK_udf", PSI_FLAG_GLOBAL}
+};
+
+static void init_udf_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_udf_rwlocks);
+ PSI_server->register_rwlock(category, all_udf_rwlocks, count);
+}
+#endif
+
+/*
+ Read all predeclared functions from mysql.func and accept all that
+ can be used.
+*/
+
+void udf_init()
+{
+ udf_func *tmp;
+ TABLE_LIST tables;
+ READ_RECORD read_record_info;
+ TABLE *table;
+ int error;
+ DBUG_ENTER("ufd_init");
+ char db[]= "mysql"; /* A subject to casednstr, can't be constant */
+
+ if (initialized)
+ DBUG_VOID_RETURN;
+
+#ifdef HAVE_PSI_INTERFACE
+ init_udf_psi_keys();
+#endif
+
+ mysql_rwlock_init(key_rwlock_THR_LOCK_udf, &THR_LOCK_udf);
+
+ init_sql_alloc(&mem, UDF_ALLOC_BLOCK_SIZE, 0, MYF(0));
+ THD *new_thd = new THD;
+ if (!new_thd ||
+ my_hash_init(&udf_hash,system_charset_info,32,0,0,get_hash_key, NULL, 0))
+ {
+ sql_print_error("Can't allocate memory for udf structures");
+ my_hash_free(&udf_hash);
+ free_root(&mem,MYF(0));
+ delete new_thd;
+ DBUG_VOID_RETURN;
+ }
+ initialized = 1;
+ new_thd->thread_stack= (char*) &new_thd;
+ new_thd->store_globals();
+ new_thd->set_db(db, sizeof(db)-1);
+
+ tables.init_one_table(db, sizeof(db)-1, "func", 4, "func", TL_READ);
+
+ if (open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ DBUG_PRINT("error",("Can't open udf table"));
+ sql_print_error("Can't open the mysql.func table. Please "
+ "run mysql_upgrade to create it.");
+ goto end;
+ }
+
+ table= tables.table;
+ if (init_read_record(&read_record_info, new_thd, table, NULL,1,0,FALSE))
+ {
+ sql_print_error("Could not initialize init_read_record; udf's not "
+ "loaded");
+ goto end;
+ }
+
+ table->use_all_columns();
+ while (!(error= read_record_info.read_record(&read_record_info)))
+ {
+ DBUG_PRINT("info",("init udf record"));
+ LEX_STRING name;
+ name.str=get_field(&mem, table->field[0]);
+ name.length = (uint) strlen(name.str);
+ char *dl_name= get_field(&mem, table->field[2]);
+ bool new_dl=0;
+ Item_udftype udftype=UDFTYPE_FUNCTION;
+ if (table->s->fields >= 4) // New func table
+ udftype=(Item_udftype) table->field[3]->val_int();
+
+ /*
+ Ensure that the .dll doesn't have a path
+ This is done to ensure that only approved dll from the system
+ directories are used (to make this even remotely secure).
+
+ On windows we must check both FN_LIBCHAR and '/'.
+ */
+ if (check_valid_path(dl_name, strlen(dl_name)) ||
+ check_string_char_length(&name, "", NAME_CHAR_LEN,
+ system_charset_info, 1))
+ {
+ sql_print_error("Invalid row in mysql.func table for function '%.64s'",
+ name.str);
+ continue;
+ }
+
+ if (!(tmp= add_udf(&name,(Item_result) table->field[1]->val_int(),
+ dl_name, udftype)))
+ {
+ sql_print_error("Can't alloc memory for udf function: '%.64s'", name.str);
+ continue;
+ }
+
+ void *dl = find_udf_dl(tmp->dl);
+ if (dl == NULL)
+ {
+ char dlpath[FN_REFLEN];
+ strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", tmp->dl,
+ NullS);
+ (void) unpack_filename(dlpath, dlpath);
+ if (!(dl= dlopen(dlpath, RTLD_NOW)))
+ {
+ /* Print warning to log */
+ sql_print_error(ER(ER_CANT_OPEN_LIBRARY), tmp->dl, errno, dlerror());
+ /* Keep the udf in the hash so that we can remove it later */
+ continue;
+ }
+ new_dl=1;
+ }
+ tmp->dlhandle = dl;
+ {
+ char buf[SAFE_NAME_LEN+16], *missing;
+ if ((missing= init_syms(tmp, buf)))
+ {
+ sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), missing);
+ del_udf(tmp);
+ if (new_dl)
+ dlclose(dl);
+ }
+ }
+ }
+ if (error > 0)
+ sql_print_error("Got unknown error: %d", my_errno);
+ end_read_record(&read_record_info);
+ table->m_needs_reopen= TRUE; // Force close to free memory
+
+end:
+ close_mysql_tables(new_thd);
+ delete new_thd;
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+ DBUG_VOID_RETURN;
+}
+
+
+void udf_free()
+{
+ /* close all shared libraries */
+ DBUG_ENTER("udf_free");
+ for (uint idx=0 ; idx < udf_hash.records ; idx++)
+ {
+ udf_func *udf=(udf_func*) my_hash_element(&udf_hash,idx);
+ if (udf->dlhandle) // Not closed before
+ {
+ /* Mark all versions using the same handler as closed */
+ for (uint j=idx+1 ; j < udf_hash.records ; j++)
+ {
+ udf_func *tmp=(udf_func*) my_hash_element(&udf_hash,j);
+ if (udf->dlhandle == tmp->dlhandle)
+ tmp->dlhandle=0; // Already closed
+ }
+ dlclose(udf->dlhandle);
+ }
+ }
+ my_hash_free(&udf_hash);
+ free_root(&mem,MYF(0));
+ if (initialized)
+ {
+ initialized= 0;
+ mysql_rwlock_destroy(&THR_LOCK_udf);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+static void del_udf(udf_func *udf)
+{
+ DBUG_ENTER("del_udf");
+ if (!--udf->usage_count)
+ {
+ my_hash_delete(&udf_hash,(uchar*) udf);
+ using_udf_functions=udf_hash.records != 0;
+ }
+ else
+ {
+ /*
+ The functions is in use ; Rename the functions instead of removing it.
+ The functions will be automaticly removed when the least threads
+ doesn't use it anymore
+ */
+ char *name= udf->name.str;
+ uint name_length=udf->name.length;
+ udf->name.str=(char*) "*";
+ udf->name.length=1;
+ my_hash_update(&udf_hash,(uchar*) udf,(uchar*) name,name_length);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+void free_udf(udf_func *udf)
+{
+ DBUG_ENTER("free_udf");
+
+ if (!initialized)
+ DBUG_VOID_RETURN;
+
+ mysql_rwlock_wrlock(&THR_LOCK_udf);
+ if (!--udf->usage_count)
+ {
+ /*
+ We come here when someone has deleted the udf function
+ while another thread still was using the udf
+ */
+ my_hash_delete(&udf_hash,(uchar*) udf);
+ using_udf_functions=udf_hash.records != 0;
+ if (!find_udf_dl(udf->dl))
+ dlclose(udf->dlhandle);
+ }
+ mysql_rwlock_unlock(&THR_LOCK_udf);
+ DBUG_VOID_RETURN;
+}
+
+
+/* This is only called if using_udf_functions != 0 */
+
+udf_func *find_udf(const char *name,uint length,bool mark_used)
+{
+ udf_func *udf=0;
+ DBUG_ENTER("find_udf");
+
+ if (!initialized)
+ DBUG_RETURN(NULL);
+
+ DEBUG_SYNC(current_thd, "find_udf_before_lock");
+ /* TODO: This should be changed to reader locks someday! */
+ if (mark_used)
+ mysql_rwlock_wrlock(&THR_LOCK_udf); /* Called during fix_fields */
+ else
+ mysql_rwlock_rdlock(&THR_LOCK_udf); /* Called during parsing */
+
+ if ((udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) name,
+ length ? length : (uint) strlen(name))))
+ {
+ if (!udf->dlhandle)
+ udf=0; // Could not be opened
+ else if (mark_used)
+ udf->usage_count++;
+ }
+ mysql_rwlock_unlock(&THR_LOCK_udf);
+ DBUG_RETURN(udf);
+}
+
+
+static void *find_udf_dl(const char *dl)
+{
+ DBUG_ENTER("find_udf_dl");
+
+ /*
+ Because only the function name is hashed, we have to search trough
+ all rows to find the dl.
+ */
+ for (uint idx=0 ; idx < udf_hash.records ; idx++)
+ {
+ udf_func *udf=(udf_func*) my_hash_element(&udf_hash,idx);
+ if (!strcmp(dl, udf->dl) && udf->dlhandle != NULL)
+ DBUG_RETURN(udf->dlhandle);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/* Assume that name && dl is already allocated */
+
+static udf_func *add_udf(LEX_STRING *name, Item_result ret, char *dl,
+ Item_udftype type)
+{
+ if (!name || !dl || !(uint) type || (uint) type > (uint) UDFTYPE_AGGREGATE)
+ return 0;
+ udf_func *tmp= (udf_func*) alloc_root(&mem, sizeof(udf_func));
+ if (!tmp)
+ return 0;
+ bzero((char*) tmp,sizeof(*tmp));
+ tmp->name = *name; //dup !!
+ tmp->dl = dl;
+ tmp->returns = ret;
+ tmp->type = type;
+ tmp->usage_count=1;
+ if (my_hash_insert(&udf_hash,(uchar*) tmp))
+ return 0;
+ using_udf_functions=1;
+ return tmp;
+}
+
+
+/**
+ Create a user defined function.
+
+ @note Like implementations of other DDL/DML in MySQL, this function
+ relies on the caller to close the thread tables. This is done in the
+ end of dispatch_command().
+*/
+
+int mysql_create_function(THD *thd,udf_func *udf)
+{
+ int error;
+ void *dl=0;
+ bool new_dl=0;
+ TABLE *table;
+ TABLE_LIST tables;
+ udf_func *u_d;
+ DBUG_ENTER("mysql_create_function");
+
+ if (!initialized)
+ {
+ if (opt_noacl)
+ my_error(ER_CANT_INITIALIZE_UDF, MYF(0),
+ udf->name.str,
+ "UDFs are unavailable with the --skip-grant-tables option");
+ else
+ my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ /*
+ Ensure that the .dll doesn't have a path
+ This is done to ensure that only approved dll from the system
+ directories are used (to make this even remotely secure).
+ */
+ if (check_valid_path(udf->dl, strlen(udf->dl)))
+ {
+ my_message(ER_UDF_NO_PATHS, ER(ER_UDF_NO_PATHS), MYF(0));
+ DBUG_RETURN(1);
+ }
+ if (check_string_char_length(&udf->name, "", NAME_CHAR_LEN,
+ system_charset_info, 1))
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), udf->name.str);
+ DBUG_RETURN(1);
+ }
+
+ tables.init_one_table(STRING_WITH_LEN("mysql"), STRING_WITH_LEN("func"),
+ "func", TL_WRITE);
+ table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT);
+
+ mysql_rwlock_wrlock(&THR_LOCK_udf);
+ DEBUG_SYNC(current_thd, "mysql_create_function_after_lock");
+ if ((my_hash_search(&udf_hash,(uchar*) udf->name.str, udf->name.length)))
+ {
+ my_error(ER_UDF_EXISTS, MYF(0), udf->name.str);
+ goto err;
+ }
+ if (!(dl = find_udf_dl(udf->dl)))
+ {
+ char dlpath[FN_REFLEN];
+ strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", udf->dl, NullS);
+ (void) unpack_filename(dlpath, dlpath);
+
+ if (!(dl = dlopen(dlpath, RTLD_NOW)))
+ {
+ DBUG_PRINT("error",("dlopen of %s failed, error: %d (%s)",
+ udf->dl, errno, dlerror()));
+ my_error(ER_CANT_OPEN_LIBRARY, MYF(0),
+ udf->dl, errno, dlerror());
+ goto err;
+ }
+ new_dl=1;
+ }
+ udf->dlhandle=dl;
+ {
+ char buf[SAFE_NAME_LEN+16], *missing;
+ if ((missing= init_syms(udf, buf)))
+ {
+ my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), missing);
+ goto err;
+ }
+ }
+ udf->name.str=strdup_root(&mem,udf->name.str);
+ udf->dl=strdup_root(&mem,udf->dl);
+ if (!(u_d=add_udf(&udf->name,udf->returns,udf->dl,udf->type)))
+ goto err;
+ u_d->dlhandle = dl;
+ u_d->func=udf->func;
+ u_d->func_init=udf->func_init;
+ u_d->func_deinit=udf->func_deinit;
+ u_d->func_clear=udf->func_clear;
+ u_d->func_add=udf->func_add;
+
+ /* create entry in mysql.func table */
+
+ /* Allow creation of functions even if we can't open func table */
+ if (!table)
+ goto err;
+ table->use_all_columns();
+ restore_record(table, s->default_values); // Default values for fields
+ table->field[0]->store(u_d->name.str, u_d->name.length, system_charset_info);
+ table->field[1]->store((longlong) u_d->returns, TRUE);
+ table->field[2]->store(u_d->dl,(uint) strlen(u_d->dl), system_charset_info);
+ if (table->s->fields >= 4) // If not old func format
+ table->field[3]->store((longlong) u_d->type, TRUE);
+ error = table->file->ha_write_row(table->record[0]);
+
+ if (error)
+ {
+ my_error(ER_ERROR_ON_WRITE, MYF(0), "mysql.func", error);
+ del_udf(u_d);
+ goto err;
+ }
+ mysql_rwlock_unlock(&THR_LOCK_udf);
+
+ /* Binlog the create function. */
+ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length()))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+
+ err:
+ if (new_dl)
+ dlclose(dl);
+ mysql_rwlock_unlock(&THR_LOCK_udf);
+ DBUG_RETURN(1);
+}
+
+
+int mysql_drop_function(THD *thd,const LEX_STRING *udf_name)
+{
+ TABLE *table;
+ TABLE_LIST tables;
+ udf_func *udf;
+ char *exact_name_str;
+ uint exact_name_len;
+ DBUG_ENTER("mysql_drop_function");
+
+ if (!initialized)
+ {
+ if (opt_noacl)
+ my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str);
+ else
+ my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ tables.init_one_table(STRING_WITH_LEN("mysql"), STRING_WITH_LEN("func"),
+ "func", TL_WRITE);
+ table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT);
+
+ mysql_rwlock_wrlock(&THR_LOCK_udf);
+ DEBUG_SYNC(current_thd, "mysql_drop_function_after_lock");
+ if (!(udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) udf_name->str,
+ (uint) udf_name->length)))
+ {
+ my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str);
+ goto err;
+ }
+ exact_name_str= udf->name.str;
+ exact_name_len= udf->name.length;
+ del_udf(udf);
+ /*
+ Close the handle if this was function that was found during boot or
+ CREATE FUNCTION and it's not in use by any other udf function
+ */
+ if (udf->dlhandle && !find_udf_dl(udf->dl))
+ dlclose(udf->dlhandle);
+
+ if (!table)
+ goto err;
+ table->use_all_columns();
+ table->field[0]->store(exact_name_str, exact_name_len, &my_charset_bin);
+ if (!table->file->ha_index_read_idx_map(table->record[0], 0,
+ (uchar*) table->field[0]->ptr,
+ HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ int error;
+ if ((error = table->file->ha_delete_row(table->record[0])))
+ table->file->print_error(error, MYF(0));
+ }
+ mysql_rwlock_unlock(&THR_LOCK_udf);
+
+ /*
+ Binlog the drop function. Keep the table open and locked
+ while binlogging, to avoid binlog inconsistency.
+ */
+ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length()))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+
+err:
+ mysql_rwlock_unlock(&THR_LOCK_udf);
+ DBUG_RETURN(1);
+}
+
+#endif /* HAVE_DLOPEN */
diff --git a/sql/sql_udf.h b/sql/sql_udf.h
new file mode 100644
index 00000000000..4aa055b9858
--- /dev/null
+++ b/sql/sql_udf.h
@@ -0,0 +1,147 @@
+#ifndef SQL_UDF_INCLUDED
+#define SQL_UDF_INCLUDED
+
+/* Copyright (c) 2000, 2003-2007 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+
+/* This file defines structures needed by udf functions */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface
+#endif
+
+enum Item_udftype {UDFTYPE_FUNCTION=1,UDFTYPE_AGGREGATE};
+
+typedef void (*Udf_func_clear)(UDF_INIT *, uchar *, uchar *);
+typedef void (*Udf_func_add)(UDF_INIT *, UDF_ARGS *, uchar *, uchar *);
+typedef void (*Udf_func_deinit)(UDF_INIT*);
+typedef my_bool (*Udf_func_init)(UDF_INIT *, UDF_ARGS *, char *);
+typedef void (*Udf_func_any)();
+typedef double (*Udf_func_double)(UDF_INIT *, UDF_ARGS *, uchar *, uchar *);
+typedef longlong (*Udf_func_longlong)(UDF_INIT *, UDF_ARGS *, uchar *,
+ uchar *);
+
+typedef struct st_udf_func
+{
+ LEX_STRING name;
+ Item_result returns;
+ Item_udftype type;
+ char *dl;
+ void *dlhandle;
+ Udf_func_any func;
+ Udf_func_init func_init;
+ Udf_func_deinit func_deinit;
+ Udf_func_clear func_clear;
+ Udf_func_add func_add;
+ ulong usage_count;
+} udf_func;
+
+class Item_result_field;
+
+class udf_handler :public Sql_alloc
+{
+ protected:
+ udf_func *u_d;
+ String *buffers;
+ UDF_ARGS f_args;
+ UDF_INIT initid;
+ char *num_buffer;
+ uchar error, is_null;
+ bool initialized;
+ Item **args;
+
+ public:
+ table_map used_tables_cache;
+ bool const_item_cache;
+ bool not_original;
+ udf_handler(udf_func *udf_arg) :u_d(udf_arg), buffers(0), error(0),
+ is_null(0), initialized(0), not_original(0)
+ {}
+ ~udf_handler();
+ const char *name() const { return u_d ? u_d->name.str : "?"; }
+ Item_result result_type () const
+ { return u_d ? u_d->returns : STRING_RESULT;}
+ bool get_arguments();
+ bool fix_fields(THD *thd, Item_result_field *item,
+ uint arg_count, Item **args);
+ void cleanup();
+ double val(my_bool *null_value)
+ {
+ is_null= 0;
+ if (get_arguments())
+ {
+ *null_value=1;
+ return 0.0;
+ }
+ Udf_func_double func= (Udf_func_double) u_d->func;
+ double tmp=func(&initid, &f_args, &is_null, &error);
+ if (is_null || error)
+ {
+ *null_value=1;
+ return 0.0;
+ }
+ *null_value=0;
+ return tmp;
+ }
+ longlong val_int(my_bool *null_value)
+ {
+ is_null= 0;
+ if (get_arguments())
+ {
+ *null_value=1;
+ return 0;
+ }
+ Udf_func_longlong func= (Udf_func_longlong) u_d->func;
+ longlong tmp=func(&initid, &f_args, &is_null, &error);
+ if (is_null || error)
+ {
+ *null_value=1;
+ return 0;
+ }
+ *null_value=0;
+ return tmp;
+ }
+ my_decimal *val_decimal(my_bool *null_value, my_decimal *dec_buf);
+ void clear()
+ {
+ is_null= 0;
+ Udf_func_clear func= u_d->func_clear;
+ func(&initid, &is_null, &error);
+ }
+ void add(my_bool *null_value)
+ {
+ if (get_arguments())
+ {
+ *null_value=1;
+ return;
+ }
+ Udf_func_add func= u_d->func_add;
+ func(&initid, &f_args, &is_null, &error);
+ *null_value= (my_bool) (is_null || error);
+ }
+ String *val_str(String *str,String *save_str);
+};
+
+
+#ifdef HAVE_DLOPEN
+void udf_init(void),udf_free(void);
+udf_func *find_udf(const char *name, uint len=0,bool mark_used=0);
+void free_udf(udf_func *udf);
+int mysql_create_function(THD *thd,udf_func *udf);
+int mysql_drop_function(THD *thd,const LEX_STRING *name);
+#endif
+#endif /* SQL_UDF_INCLUDED */
diff --git a/sql/sql_union.cc b/sql/sql_union.cc
new file mode 100644
index 00000000000..77a3b1eec8f
--- /dev/null
+++ b/sql/sql_union.cc
@@ -0,0 +1,1074 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2014, SkySQL Ab.
+
+ 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 */
+
+
+/*
+ UNION of select's
+ UNION's were introduced by Monty and Sinisa <sinisa@mysql.com>
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_union.h"
+#include "sql_select.h"
+#include "sql_cursor.h"
+#include "sql_base.h" // fill_record
+#include "filesort.h" // filesort_free_buffers
+
+bool mysql_union(THD *thd, LEX *lex, select_result *result,
+ SELECT_LEX_UNIT *unit, ulong setup_tables_done_option)
+{
+ DBUG_ENTER("mysql_union");
+ bool res;
+ if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK |
+ setup_tables_done_option)))
+ res= unit->exec();
+ res|= unit->cleanup();
+ DBUG_RETURN(res);
+}
+
+
+/***************************************************************************
+** store records in temporary table for UNION
+***************************************************************************/
+
+int select_union::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
+{
+ unit= u;
+ return 0;
+}
+
+
+int select_union::send_data(List<Item> &values)
+{
+ if (unit->offset_limit_cnt)
+ { // using limit offset,count
+ unit->offset_limit_cnt--;
+ return 0;
+ }
+ if (thd->killed == ABORT_QUERY)
+ return 0;
+ if (table->no_rows_with_nulls)
+ table->null_catch_flags= CHECK_ROW_FOR_NULLS_TO_REJECT;
+ fill_record(thd, table, table->field, values, TRUE, FALSE);
+ if (thd->is_error())
+ return 1;
+ if (table->no_rows_with_nulls)
+ {
+ table->null_catch_flags&= ~CHECK_ROW_FOR_NULLS_TO_REJECT;
+ if (table->null_catch_flags)
+ return 0;
+ }
+
+ if ((write_err= table->file->ha_write_tmp_row(table->record[0])))
+ {
+ if (write_err == HA_ERR_FOUND_DUPP_KEY)
+ {
+ /*
+ Inform upper level that we found a duplicate key, that should not
+ be counted as part of limit
+ */
+ return -1;
+ }
+ bool is_duplicate= FALSE;
+ /* create_internal_tmp_table_from_heap will generate error if needed */
+ if (table->file->is_fatal_error(write_err, HA_CHECK_DUP) &&
+ create_internal_tmp_table_from_heap(thd, table,
+ tmp_table_param.start_recinfo,
+ &tmp_table_param.recinfo,
+ write_err, 1, &is_duplicate))
+ return 1;
+ if (is_duplicate)
+ return -1;
+ }
+ return 0;
+}
+
+
+bool select_union::send_eof()
+{
+ return 0;
+}
+
+
+bool select_union::flush()
+{
+ int error;
+ if ((error=table->file->extra(HA_EXTRA_NO_CACHE)))
+ {
+ table->file->print_error(error, MYF(0));
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ Create a temporary table to store the result of select_union.
+
+ SYNOPSIS
+ select_union::create_result_table()
+ thd thread handle
+ column_types a list of items used to define columns of the
+ temporary table
+ is_union_distinct if set, the temporary table will eliminate
+ duplicates on insert
+ options create options
+ table_alias name of the temporary table
+ bit_fields_as_long convert bit fields to ulonglong
+ create_table whether to physically create result table
+ keep_row_order keep rows in order as they were inserted
+
+ DESCRIPTION
+ Create a temporary table that is used to store the result of a UNION,
+ derived table, or a materialized cursor.
+
+ RETURN VALUE
+ 0 The table has been created successfully.
+ 1 create_tmp_table failed.
+*/
+
+bool
+select_union::create_result_table(THD *thd_arg, List<Item> *column_types,
+ bool is_union_distinct, ulonglong options,
+ const char *alias,
+ bool bit_fields_as_long, bool create_table,
+ bool keep_row_order)
+{
+ DBUG_ASSERT(table == 0);
+ tmp_table_param.init();
+ tmp_table_param.field_count= column_types->elements;
+ tmp_table_param.bit_fields_as_long= bit_fields_as_long;
+
+ if (! (table= create_tmp_table(thd_arg, &tmp_table_param, *column_types,
+ (ORDER*) 0, is_union_distinct, 1,
+ options, HA_POS_ERROR, alias,
+ !create_table, keep_row_order)))
+ return TRUE;
+
+ table->keys_in_use_for_query.clear_all();
+ for (uint i=0; i < table->s->fields; i++)
+ table->field[i]->flags &= ~PART_KEY_FLAG;
+
+ if (create_table)
+ {
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ }
+ return FALSE;
+}
+
+
+/**
+ Reset and empty the temporary table that stores the materialized query result.
+
+ @note The cleanup performed here is exactly the same as for the two temp
+ tables of JOIN - exec_tmp_table_[1 | 2].
+*/
+
+void select_union::cleanup()
+{
+ table->file->extra(HA_EXTRA_RESET_STATE);
+ table->file->ha_delete_all_rows();
+ free_io_cache(table);
+ filesort_free_buffers(table,0);
+}
+
+
+/*
+ initialization procedures before fake_select_lex preparation()
+
+ SYNOPSIS
+ st_select_lex_unit::init_prepare_fake_select_lex()
+ thd - thread handler
+ first_execution - TRUE at the first execution of the union
+
+ RETURN
+ options of SELECT
+*/
+
+void
+st_select_lex_unit::init_prepare_fake_select_lex(THD *thd_arg,
+ bool first_execution)
+{
+ thd_arg->lex->current_select= fake_select_lex;
+ fake_select_lex->table_list.link_in_list(&result_table_list,
+ &result_table_list.next_local);
+ fake_select_lex->context.table_list=
+ fake_select_lex->context.first_name_resolution_table=
+ fake_select_lex->get_table_list();
+ /*
+ The flag fake_select_lex->first_execution indicates whether this is
+ called at the first execution of the statement, while first_execution
+ shows whether this is called at the first execution of the union that
+ may form just a subselect.
+ */
+ if (!fake_select_lex->first_execution && first_execution)
+ {
+ for (ORDER *order= global_parameters->order_list.first;
+ order;
+ order= order->next)
+ order->item= &order->item_ptr;
+ }
+ for (ORDER *order= global_parameters->order_list.first;
+ order;
+ order=order->next)
+ {
+ (*order->item)->walk(&Item::change_context_processor, 0,
+ (uchar*) &fake_select_lex->context);
+ (*order->item)->walk(&Item::set_fake_select_as_master_processor, 0,
+ (uchar*) fake_select_lex);
+ }
+}
+
+
+bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
+ ulong additional_options)
+{
+ SELECT_LEX *lex_select_save= thd_arg->lex->current_select;
+ SELECT_LEX *sl, *first_sl= first_select();
+ select_result *tmp_result;
+ bool is_union_select;
+ DBUG_ENTER("st_select_lex_unit::prepare");
+
+ describe= MY_TEST(additional_options & SELECT_DESCRIBE);
+
+ /*
+ result object should be reassigned even if preparing already done for
+ max/min subquery (ALL/ANY optimization)
+ */
+ result= sel_result;
+
+ if (prepared)
+ {
+ if (describe)
+ {
+ /* fast reinit for EXPLAIN */
+ for (sl= first_sl; sl; sl= sl->next_select())
+ {
+ sl->join->result= result;
+ select_limit_cnt= HA_POS_ERROR;
+ offset_limit_cnt= 0;
+ if (!sl->join->procedure &&
+ result->prepare(sl->join->fields_list, this))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ sl->join->select_options|= SELECT_DESCRIBE;
+ sl->join->reinit();
+ }
+ }
+ DBUG_RETURN(FALSE);
+ }
+ prepared= 1;
+ saved_error= FALSE;
+
+ thd_arg->lex->current_select= sl= first_sl;
+ found_rows_for_union= first_sl->options & OPTION_FOUND_ROWS;
+ is_union_select= is_union() || fake_select_lex;
+
+ /* Global option */
+
+ if (is_union_select)
+ {
+ if (!(tmp_result= union_result= new select_union))
+ goto err;
+ if (describe)
+ tmp_result= sel_result;
+ }
+ else
+ tmp_result= sel_result;
+
+ sl->context.resolve_in_select_list= TRUE;
+
+ for (;sl; sl= sl->next_select())
+ {
+ bool can_skip_order_by;
+ sl->options|= SELECT_NO_UNLOCK;
+ JOIN *join= new JOIN(thd_arg, sl->item_list,
+ sl->options | thd_arg->variables.option_bits | additional_options,
+ tmp_result);
+ /*
+ setup_tables_done_option should be set only for very first SELECT,
+ because it protect from secont setup_tables call for select-like non
+ select commands (DELETE/INSERT/...) and they use only very first
+ SELECT (for union it can be only INSERT ... SELECT).
+ */
+ additional_options&= ~OPTION_SETUP_TABLES_DONE;
+ if (!join)
+ goto err;
+
+ thd_arg->lex->current_select= sl;
+
+ can_skip_order_by= is_union_select && !(sl->braces && sl->explicit_limit);
+
+ saved_error= join->prepare(&sl->ref_pointer_array,
+ sl->table_list.first,
+ sl->with_wild,
+ sl->where,
+ (can_skip_order_by ? 0 :
+ sl->order_list.elements) +
+ sl->group_list.elements,
+ can_skip_order_by ?
+ NULL : sl->order_list.first,
+ can_skip_order_by,
+ sl->group_list.first,
+ sl->having,
+ (is_union_select ? NULL :
+ thd_arg->lex->proc_list.first),
+ sl, this);
+
+ /* There are no * in the statement anymore (for PS) */
+ sl->with_wild= 0;
+ last_procedure= join->procedure;
+
+ if (saved_error || (saved_error= thd_arg->is_fatal_error))
+ goto err;
+ /*
+ Remove all references from the select_lex_units to the subqueries that
+ are inside the ORDER BY clause.
+ */
+ if (can_skip_order_by)
+ {
+ for (ORDER *ord= (ORDER *)sl->order_list.first; ord; ord= ord->next)
+ {
+ (*ord->item)->walk(&Item::eliminate_subselect_processor, FALSE, NULL);
+ }
+ }
+
+ /*
+ Use items list of underlaid select for derived tables to preserve
+ information about fields lengths and exact types
+ */
+ if (!is_union_select)
+ types= first_sl->item_list;
+ else if (sl == first_sl)
+ {
+ types.empty();
+ List_iterator_fast<Item> it(sl->item_list);
+ Item *item_tmp;
+ while ((item_tmp= it++))
+ {
+ /* Error's in 'new' will be detected after loop */
+ types.push_back(new Item_type_holder(thd_arg, item_tmp));
+ }
+
+ if (thd_arg->is_fatal_error)
+ goto err; // out of memory
+ }
+ else
+ {
+ if (types.elements != sl->item_list.elements)
+ {
+ my_message(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT,
+ ER(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT),MYF(0));
+ goto err;
+ }
+ List_iterator_fast<Item> it(sl->item_list);
+ List_iterator_fast<Item> tp(types);
+ Item *type, *item_tmp;
+ while ((type= tp++, item_tmp= it++))
+ {
+ if (((Item_type_holder*)type)->join_types(thd_arg, item_tmp))
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+
+ if (is_union_select)
+ {
+ /*
+ Check that it was possible to aggregate
+ all collations together for UNION.
+ */
+ List_iterator_fast<Item> tp(types);
+ Item *type;
+ ulonglong create_options;
+ uint save_tablenr= 0;
+ table_map save_map= 0;
+ uint save_maybe_null= 0;
+
+ while ((type= tp++))
+ {
+ if (type->result_type() == STRING_RESULT &&
+ type->collation.derivation == DERIVATION_NONE)
+ {
+ my_error(ER_CANT_AGGREGATE_NCOLLATIONS, MYF(0), "UNION");
+ goto err;
+ }
+ }
+
+ /*
+ Disable the usage of fulltext searches in the last union branch.
+ This is a temporary 5.x limitation because of the way the fulltext
+ search functions are handled by the optimizer.
+ This is manifestation of the more general problems of "taking away"
+ parts of a SELECT statement post-fix_fields(). This is generally not
+ doable since various flags are collected in various places (e.g.
+ SELECT_LEX) that carry information about the presence of certain
+ expressions or constructs in the parts of the query.
+ When part of the query is taken away it's not clear how to "divide"
+ the meaning of these accumulated flags and what to carry over to the
+ recipient query (SELECT_LEX).
+ */
+ if (global_parameters->ftfunc_list->elements &&
+ global_parameters->order_list.elements &&
+ global_parameters != fake_select_lex)
+ {
+ ORDER *ord;
+ Item_func::Functype ft= Item_func::FT_FUNC;
+ for (ord= global_parameters->order_list.first; ord; ord= ord->next)
+ if ((*ord->item)->walk (&Item::find_function_processor, FALSE,
+ (uchar *) &ft))
+ {
+ my_error (ER_CANT_USE_OPTION_HERE, MYF(0), "MATCH()");
+ goto err;
+ }
+ }
+
+
+ create_options= (first_sl->options | thd_arg->variables.option_bits |
+ TMP_TABLE_ALL_COLUMNS);
+ /*
+ Force the temporary table to be a MyISAM table if we're going to use
+ fullext functions (MATCH ... AGAINST .. IN BOOLEAN MODE) when reading
+ from it (this should be removed in 5.2 when fulltext search is moved
+ out of MyISAM).
+ */
+ if (global_parameters->ftfunc_list->elements)
+ create_options= create_options | TMP_TABLE_FORCE_MYISAM;
+
+ if (union_result->create_result_table(thd, &types, MY_TEST(union_distinct),
+ create_options, "", FALSE, TRUE))
+ goto err;
+ if (fake_select_lex && !fake_select_lex->first_cond_optimization)
+ {
+ save_tablenr= result_table_list.tablenr_exec;
+ save_map= result_table_list.map_exec;
+ save_maybe_null= result_table_list.maybe_null_exec;
+ }
+ bzero((char*) &result_table_list, sizeof(result_table_list));
+ result_table_list.db= (char*) "";
+ result_table_list.table_name= result_table_list.alias= (char*) "union";
+ result_table_list.table= table= union_result->table;
+ if (fake_select_lex && !fake_select_lex->first_cond_optimization)
+ {
+ result_table_list.tablenr_exec= save_tablenr;
+ result_table_list.map_exec= save_map;
+ result_table_list.maybe_null_exec= save_maybe_null;
+ }
+
+ thd_arg->lex->current_select= lex_select_save;
+ if (!item_list.elements)
+ {
+ Query_arena *arena, backup_arena;
+
+ arena= thd->activate_stmt_arena_if_needed(&backup_arena);
+
+ saved_error= table->fill_item_list(&item_list);
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup_arena);
+
+ if (saved_error)
+ goto err;
+
+ if (thd->stmt_arena->is_stmt_prepare())
+ {
+ /* Validate the global parameters of this union */
+
+ init_prepare_fake_select_lex(thd, TRUE);
+ /* Should be done only once (the only item_list per statement) */
+ DBUG_ASSERT(fake_select_lex->join == 0);
+ if (!(fake_select_lex->join= new JOIN(thd, item_list, thd->variables.option_bits,
+ result)))
+ {
+ fake_select_lex->table_list.empty();
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Fake st_select_lex should have item list for correct ref_array
+ allocation.
+ */
+ fake_select_lex->item_list= item_list;
+
+ thd_arg->lex->current_select= fake_select_lex;
+
+ /*
+ We need to add up n_sum_items in order to make the correct
+ allocation in setup_ref_array().
+ */
+ fake_select_lex->n_child_sum_items+= global_parameters->n_sum_items;
+
+ saved_error= fake_select_lex->join->
+ prepare(&fake_select_lex->ref_pointer_array,
+ fake_select_lex->table_list.first,
+ 0, 0,
+ global_parameters->order_list.elements, // og_num
+ global_parameters->order_list.first, // order
+ false, NULL, NULL, NULL,
+ fake_select_lex, this);
+ fake_select_lex->table_list.empty();
+ }
+ }
+ else
+ {
+ /*
+ We're in execution of a prepared statement or stored procedure:
+ reset field items to point at fields from the created temporary table.
+ */
+ table->reset_item_list(&item_list);
+ }
+ }
+
+ thd_arg->lex->current_select= lex_select_save;
+
+ DBUG_RETURN(saved_error || thd_arg->is_fatal_error);
+
+err:
+ thd_arg->lex->current_select= lex_select_save;
+ (void) cleanup();
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Run optimization phase.
+
+ @return FALSE unit successfully passed optimization phase.
+ @return TRUE an error occur.
+*/
+bool st_select_lex_unit::optimize()
+{
+ SELECT_LEX *lex_select_save= thd->lex->current_select;
+ SELECT_LEX *select_cursor=first_select();
+ DBUG_ENTER("st_select_lex_unit::optimize");
+
+ if (optimized && !uncacheable && !describe)
+ DBUG_RETURN(FALSE);
+
+ if (uncacheable || !item || !item->assigned() || describe)
+ {
+ if (item)
+ item->reset_value_registration();
+ if (optimized && item)
+ {
+ if (item->assigned())
+ {
+ item->assigned(0); // We will reinit & rexecute unit
+ item->reset();
+ table->file->ha_delete_all_rows();
+ }
+ /* re-enabling indexes for next subselect iteration */
+ if (union_distinct && table->file->ha_enable_indexes(HA_KEY_SWITCH_ALL))
+ {
+ DBUG_ASSERT(0);
+ }
+ }
+ for (SELECT_LEX *sl= select_cursor; sl; sl= sl->next_select())
+ {
+ thd->lex->current_select= sl;
+
+ if (optimized)
+ saved_error= sl->join->reinit();
+ else
+ {
+ set_limit(sl);
+ if (sl == global_parameters || describe)
+ {
+ offset_limit_cnt= 0;
+ /*
+ We can't use LIMIT at this stage if we are using ORDER BY for the
+ whole query
+ */
+ if (sl->order_list.first || describe)
+ select_limit_cnt= HA_POS_ERROR;
+ }
+
+ /*
+ When using braces, SQL_CALC_FOUND_ROWS affects the whole query:
+ we don't calculate found_rows() per union part.
+ Otherwise, SQL_CALC_FOUND_ROWS should be done on all sub parts.
+ */
+ sl->join->select_options=
+ (select_limit_cnt == HA_POS_ERROR || sl->braces) ?
+ sl->options & ~OPTION_FOUND_ROWS : sl->options | found_rows_for_union;
+
+ saved_error= sl->join->optimize();
+ }
+
+ if (saved_error)
+ {
+ thd->lex->current_select= lex_select_save;
+ DBUG_RETURN(saved_error);
+ }
+ }
+ }
+ optimized= 1;
+
+ thd->lex->current_select= lex_select_save;
+ DBUG_RETURN(saved_error);
+}
+
+
+bool st_select_lex_unit::exec()
+{
+ SELECT_LEX *lex_select_save= thd->lex->current_select;
+ SELECT_LEX *select_cursor=first_select();
+ ulonglong add_rows=0;
+ ha_rows examined_rows= 0;
+ bool first_execution= !executed;
+ DBUG_ENTER("st_select_lex_unit::exec");
+ bool was_executed= executed;
+
+ if (executed && !uncacheable && !describe)
+ DBUG_RETURN(FALSE);
+ executed= 1;
+ if (!(uncacheable & ~UNCACHEABLE_EXPLAIN) && item)
+ item->make_const();
+
+ saved_error= optimize();
+
+ create_explain_query_if_not_exists(thd->lex, thd->mem_root);
+
+ if (!saved_error && !was_executed)
+ save_union_explain(thd->lex->explain);
+
+ if (saved_error)
+ DBUG_RETURN(saved_error);
+
+ if (uncacheable || !item || !item->assigned() || describe)
+ {
+ for (SELECT_LEX *sl= select_cursor; sl; sl= sl->next_select())
+ {
+ ha_rows records_at_start= 0;
+ thd->lex->current_select= sl;
+ if (sl != &thd->lex->select_lex)
+ fake_select_lex->uncacheable|= sl->uncacheable;
+ else
+ fake_select_lex->uncacheable= 0;
+
+ {
+ set_limit(sl);
+ if (sl == global_parameters || describe)
+ {
+ offset_limit_cnt= 0;
+ /*
+ We can't use LIMIT at this stage if we are using ORDER BY for the
+ whole query
+ */
+ if (sl->order_list.first || describe)
+ select_limit_cnt= HA_POS_ERROR;
+ }
+
+ /*
+ When using braces, SQL_CALC_FOUND_ROWS affects the whole query:
+ we don't calculate found_rows() per union part.
+ Otherwise, SQL_CALC_FOUND_ROWS should be done on all sub parts.
+ */
+ sl->join->select_options=
+ (select_limit_cnt == HA_POS_ERROR || sl->braces) ?
+ sl->options & ~OPTION_FOUND_ROWS : sl->options | found_rows_for_union;
+ saved_error= sl->join->optimize();
+ }
+ if (!saved_error)
+ {
+ records_at_start= table->file->stats.records;
+ sl->join->exec();
+ if (sl == union_distinct)
+ {
+ if (table->file->ha_disable_indexes(HA_KEY_SWITCH_ALL))
+ DBUG_RETURN(TRUE);
+ table->no_keyread=1;
+ }
+ saved_error= sl->join->error;
+ offset_limit_cnt= (ha_rows)(sl->offset_limit ?
+ sl->offset_limit->val_uint() :
+ 0);
+ if (!saved_error)
+ {
+ examined_rows+= thd->get_examined_row_count();
+ thd->set_examined_row_count(0);
+ if (union_result->flush())
+ {
+ thd->lex->current_select= lex_select_save;
+ DBUG_RETURN(1);
+ }
+ }
+ }
+ if (saved_error)
+ {
+ thd->lex->current_select= lex_select_save;
+ DBUG_RETURN(saved_error);
+ }
+ /* Needed for the following test and for records_at_start in next loop */
+ int error= table->file->info(HA_STATUS_VARIABLE);
+ if(error)
+ {
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(1);
+ }
+ if (found_rows_for_union && !sl->braces &&
+ select_limit_cnt != HA_POS_ERROR)
+ {
+ /*
+ This is a union without braces. Remember the number of rows that
+ could also have been part of the result set.
+ We get this from the difference of between total number of possible
+ rows and actual rows added to the temporary table.
+ */
+ add_rows+= (ulonglong) (thd->limit_found_rows - (ulonglong)
+ ((table->file->stats.records - records_at_start)));
+ }
+ if (thd->killed == ABORT_QUERY)
+ {
+ /*
+ Stop execution of the remaining queries in the UNIONS, and produce
+ the current result.
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT,
+ ER(ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT),
+ thd->accessed_rows_and_keys,
+ thd->lex->limit_rows_examined->val_uint());
+ thd->reset_killed();
+ break;
+ }
+ }
+ }
+
+ DBUG_EXECUTE_IF("show_explain_probe_union_read",
+ dbug_serve_apcs(thd, 1););
+ /* Send result to 'result' */
+ saved_error= TRUE;
+ {
+ List<Item_func_match> empty_list;
+ empty_list.empty();
+ /*
+ Disable LIMIT ROWS EXAMINED in order to produce the possibly incomplete
+ result of the UNION without interruption due to exceeding the limit.
+ */
+ thd->lex->limit_rows_examined_cnt= ULONGLONG_MAX;
+
+ if (!thd->is_fatal_error) // Check if EOM
+ {
+ set_limit(global_parameters);
+ init_prepare_fake_select_lex(thd, first_execution);
+ JOIN *join= fake_select_lex->join;
+ if (!join)
+ {
+ /*
+ allocate JOIN for fake select only once (prevent
+ mysql_select automatic allocation)
+ TODO: The above is nonsense. mysql_select() will not allocate the
+ join if one already exists. There must be some other reason why we
+ don't let it allocate the join. Perhaps this is because we need
+ some special parameter values passed to join constructor?
+ */
+ if (!(fake_select_lex->join= new JOIN(thd, item_list,
+ fake_select_lex->options, result)))
+ {
+ fake_select_lex->table_list.empty();
+ goto err;
+ }
+ fake_select_lex->join->no_const_tables= TRUE;
+
+ /*
+ Fake st_select_lex should have item list for correct ref_array
+ allocation.
+ */
+ fake_select_lex->item_list= item_list;
+
+ /*
+ We need to add up n_sum_items in order to make the correct
+ allocation in setup_ref_array().
+ Don't add more sum_items if we have already done JOIN::prepare
+ for this (with a different join object)
+ */
+ if (!fake_select_lex->ref_pointer_array)
+ fake_select_lex->n_child_sum_items+= global_parameters->n_sum_items;
+
+ if (!was_executed)
+ save_union_explain_part2(thd->lex->explain);
+
+ saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array,
+ &result_table_list,
+ 0, item_list, NULL,
+ global_parameters->order_list.elements,
+ global_parameters->order_list.first,
+ NULL, NULL, NULL,
+ fake_select_lex->options | SELECT_NO_UNLOCK,
+ result, this, fake_select_lex);
+ }
+ else
+ {
+ if (describe)
+ {
+ /*
+ In EXPLAIN command, constant subqueries that do not use any
+ tables are executed two times:
+ - 1st time is a real evaluation to get the subquery value
+ - 2nd time is to produce EXPLAIN output rows.
+ 1st execution sets certain members (e.g. select_result) to perform
+ subquery execution rather than EXPLAIN line production. In order
+ to reset them back, we re-do all of the actions (yes it is ugly):
+ */
+ join->init(thd, item_list, fake_select_lex->options, result);
+ saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array,
+ &result_table_list,
+ 0, item_list, NULL,
+ global_parameters->order_list.elements,
+ global_parameters->order_list.first,
+ NULL, NULL, NULL,
+ fake_select_lex->options | SELECT_NO_UNLOCK,
+ result, this, fake_select_lex);
+ }
+ else
+ {
+ join->examined_rows= 0;
+ saved_error= join->reinit();
+ join->exec();
+ }
+ }
+
+ fake_select_lex->table_list.empty();
+ if (!saved_error)
+ {
+ thd->limit_found_rows = (ulonglong)table->file->stats.records + add_rows;
+ thd->inc_examined_row_count(examined_rows);
+ }
+ /*
+ Mark for slow query log if any of the union parts didn't use
+ indexes efficiently
+ */
+ }
+ }
+ thd->lex->current_select= lex_select_save;
+err:
+ thd->lex->set_limit_rows_examined();
+ DBUG_RETURN(saved_error);
+}
+
+
+bool st_select_lex_unit::cleanup()
+{
+ int error= 0;
+ DBUG_ENTER("st_select_lex_unit::cleanup");
+
+ if (cleaned)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ cleaned= 1;
+
+ if (union_result)
+ {
+ delete union_result;
+ union_result=0; // Safety
+ if (table)
+ free_tmp_table(thd, table);
+ table= 0; // Safety
+ }
+
+ for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select())
+ error|= sl->cleanup();
+
+ if (fake_select_lex)
+ {
+ JOIN *join;
+ if ((join= fake_select_lex->join))
+ {
+ join->tables_list= 0;
+ join->table_count= 0;
+ join->top_join_tab_count= 0;
+ }
+ error|= fake_select_lex->cleanup();
+ /*
+ There are two cases when we should clean order items:
+ 1. UNION with SELECTs which all enclosed into braces
+ in this case global_parameters == fake_select_lex
+ 2. UNION where last SELECT is not enclosed into braces
+ in this case global_parameters == 'last select'
+ So we should use global_parameters->order_list for
+ proper order list clean up.
+ Note: global_parameters and fake_select_lex are always
+ initialized for UNION
+ */
+ DBUG_ASSERT(global_parameters);
+ if (global_parameters->order_list.elements)
+ {
+ ORDER *ord;
+ for (ord= global_parameters->order_list.first; ord; ord= ord->next)
+ (*ord->item)->walk (&Item::cleanup_processor, 0, 0);
+ }
+ }
+
+ DBUG_RETURN(error);
+}
+
+
+void st_select_lex_unit::reinit_exec_mechanism()
+{
+ prepared= optimized= executed= 0;
+#ifndef DBUG_OFF
+ if (is_union())
+ {
+ List_iterator_fast<Item> it(item_list);
+ Item *field;
+ while ((field= it++))
+ {
+ /*
+ we can't cleanup here, because it broke link to temporary table field,
+ but have to drop fixed flag to allow next fix_field of this field
+ during re-executing
+ */
+ field->fixed= 0;
+ }
+ }
+#endif
+}
+
+
+/*
+ change select_result object of unit
+
+ SYNOPSIS
+ st_select_lex_unit::change_result()
+ result new select_result object
+ old_result old select_result object
+
+ RETURN
+ FALSE - OK
+ TRUE - error
+*/
+
+bool st_select_lex_unit::change_result(select_result_interceptor *new_result,
+ select_result_interceptor *old_result)
+{
+ bool res= FALSE;
+ for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select())
+ {
+ if (sl->join && sl->join->result == old_result)
+ if (sl->join->change_result(new_result))
+ return TRUE;
+ }
+ if (fake_select_lex && fake_select_lex->join)
+ res= fake_select_lex->join->change_result(new_result);
+ return (res);
+}
+
+/*
+ Get column type information for this unit.
+
+ SYNOPSIS
+ st_select_lex_unit::get_unit_column_types()
+
+ DESCRIPTION
+ For a single-select the column types are taken
+ from the list of selected items. For a union this function
+ assumes that st_select_lex_unit::prepare has been called
+ and returns the type holders that were created for unioned
+ column types of all selects.
+
+ NOTES
+ The implementation of this function should be in sync with
+ st_select_lex_unit::prepare()
+*/
+
+List<Item> *st_select_lex_unit::get_unit_column_types()
+{
+ SELECT_LEX *sl= first_select();
+ bool is_procedure= MY_TEST(sl->join->procedure);
+
+ if (is_procedure)
+ {
+ /* Types for "SELECT * FROM t1 procedure analyse()"
+ are generated during execute */
+ return &sl->join->procedure_fields_list;
+ }
+
+
+ if (is_union())
+ {
+ DBUG_ASSERT(prepared);
+ /* Types are generated during prepare */
+ return &types;
+ }
+
+ return &sl->item_list;
+}
+
+bool st_select_lex::cleanup()
+{
+ bool error= FALSE;
+ DBUG_ENTER("st_select_lex::cleanup()");
+
+ if (join)
+ {
+ DBUG_ASSERT((st_select_lex*)join->select_lex == this);
+ error= join->destroy();
+ delete join;
+ join= 0;
+ }
+ for (SELECT_LEX_UNIT *lex_unit= first_inner_unit(); lex_unit ;
+ lex_unit= lex_unit->next_unit())
+ {
+ error= (bool) ((uint) error | (uint) lex_unit->cleanup());
+ }
+ non_agg_fields.empty();
+ inner_refs_list.empty();
+ exclude_from_table_unique_test= FALSE;
+ DBUG_RETURN(error);
+}
+
+
+void st_select_lex::cleanup_all_joins(bool full)
+{
+ SELECT_LEX_UNIT *unit;
+ SELECT_LEX *sl;
+
+ if (join)
+ join->cleanup(full);
+
+ for (unit= first_inner_unit(); unit; unit= unit->next_unit())
+ for (sl= unit->first_select(); sl; sl= sl->next_select())
+ sl->cleanup_all_joins(full);
+}
+
+
+/**
+ Set exclude_from_table_unique_test for selects of this unit and all
+ underlying selects.
+
+ @note used to exclude materialized derived tables (views) from unique
+ table check.
+*/
+
+void st_select_lex_unit::set_unique_exclude()
+{
+ for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select())
+ {
+ sl->exclude_from_table_unique_test= TRUE;
+ for (SELECT_LEX_UNIT *unit= sl->first_inner_unit();
+ unit;
+ unit= unit->next_unit())
+ {
+ unit->set_unique_exclude();
+ }
+ }
+}
+
diff --git a/sql/sql_union.h b/sql/sql_union.h
new file mode 100644
index 00000000000..171f607fba7
--- /dev/null
+++ b/sql/sql_union.h
@@ -0,0 +1,31 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_UNION_INCLUDED
+#define SQL_UNION_INCLUDED
+
+#include "my_global.h" /* ulong */
+
+class THD;
+class select_result;
+struct LEX;
+
+typedef class st_select_lex_unit SELECT_LEX_UNIT;
+
+bool mysql_union(THD *thd, LEX *lex, select_result *result,
+ SELECT_LEX_UNIT *unit, ulong setup_tables_done_option);
+
+
+#endif /* SQL_UNION_INCLUDED */
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
new file mode 100644
index 00000000000..f616549097b
--- /dev/null
+++ b/sql/sql_update.cc
@@ -0,0 +1,2525 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+
+/*
+ Single table and multi table updates of tables.
+ Multi-table updates were introduced by Sinisa & Monty
+*/
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_update.h"
+#include "sql_cache.h" // query_cache_*
+#include "sql_base.h" // close_tables_for_reopen
+#include "sql_parse.h" // cleanup_items
+#include "sql_partition.h" // partition_key_modified
+#include "sql_select.h"
+#include "sql_view.h" // check_key_in_view
+#include "sp_head.h"
+#include "sql_trigger.h"
+#include "sql_statistics.h"
+#include "probes_mysql.h"
+#include "debug_sync.h"
+#include "key.h" // is_key_used
+#include "sql_acl.h" // *_ACL, check_grant
+#include "records.h" // init_read_record,
+ // end_read_record
+#include "filesort.h" // filesort
+#include "sql_derived.h" // mysql_derived_prepare,
+ // mysql_handle_derived,
+ // mysql_derived_filling
+
+
+/**
+ True if the table's input and output record buffers are comparable using
+ compare_record(TABLE*).
+ */
+bool records_are_comparable(const TABLE *table) {
+ return ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) == 0) ||
+ bitmap_is_subset(table->write_set, table->read_set);
+}
+
+
+/**
+ Compares the input and outbut record buffers of the table to see if a row
+ has changed.
+
+ @return true if row has changed.
+ @return false otherwise.
+*/
+
+bool compare_record(const TABLE *table)
+{
+ DBUG_ASSERT(records_are_comparable(table));
+
+ if ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) != 0)
+ {
+ /*
+ Storage engine may not have read all columns of the record. Fields
+ (including NULL bits) not in the write_set may not have been read and
+ can therefore not be compared.
+ */
+ for (Field **ptr= table->field ; *ptr != NULL; ptr++)
+ {
+ Field *field= *ptr;
+ if (bitmap_is_set(table->write_set, field->field_index))
+ {
+ if (field->real_maybe_null())
+ {
+ uchar null_byte_index= field->null_ptr - table->record[0];
+
+ if (((table->record[0][null_byte_index]) & field->null_bit) !=
+ ((table->record[1][null_byte_index]) & field->null_bit))
+ return TRUE;
+ }
+ if (field->cmp_binary_offset(table->s->rec_buff_length))
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /*
+ The storage engine has read all columns, so it's safe to compare all bits
+ including those not in the write_set. This is cheaper than the field-by-field
+ comparison done above.
+ */
+ if (table->s->can_cmp_whole_record)
+ return cmp_record(table,record[1]);
+ /* Compare null bits */
+ if (memcmp(table->null_flags,
+ table->null_flags+table->s->rec_buff_length,
+ table->s->null_bytes_for_compare))
+ return TRUE; // Diff in NULL value
+ /* Compare updated fields */
+ for (Field **ptr= table->field ; *ptr ; ptr++)
+ {
+ if (bitmap_is_set(table->write_set, (*ptr)->field_index) &&
+ (*ptr)->cmp_binary_offset(table->s->rec_buff_length))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ check that all fields are real fields
+
+ SYNOPSIS
+ check_fields()
+ thd thread handler
+ items Items for check
+
+ RETURN
+ TRUE Items can't be used in UPDATE
+ FALSE Items are OK
+*/
+
+static bool check_fields(THD *thd, List<Item> &items)
+{
+ List_iterator<Item> it(items);
+ Item *item;
+ Item_field *field;
+
+ while ((item= it++))
+ {
+ if (!(field= item->field_for_view_update()))
+ {
+ /* item has name, because it comes from VIEW SELECT list */
+ my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), item->name);
+ return TRUE;
+ }
+ /*
+ we make temporary copy of Item_field, to avoid influence of changing
+ result_field on Item_ref which refer on this field
+ */
+ thd->change_item_tree(it.ref(), new Item_field(thd, field));
+ }
+ return FALSE;
+}
+
+
+/**
+ Re-read record if more columns are needed for error message.
+
+ If we got a duplicate key error, we want to write an error
+ message containing the value of the duplicate key. If we do not have
+ all fields of the key value in record[0], we need to re-read the
+ record with a proper read_set.
+
+ @param[in] error error number
+ @param[in] table table
+*/
+
+static void prepare_record_for_error_message(int error, TABLE *table)
+{
+ Field **field_p;
+ Field *field;
+ uint keynr;
+ MY_BITMAP unique_map; /* Fields in offended unique. */
+ my_bitmap_map unique_map_buf[bitmap_buffer_size(MAX_FIELDS)];
+ DBUG_ENTER("prepare_record_for_error_message");
+
+ /*
+ Only duplicate key errors print the key value.
+ If storage engine does always read all columns, we have the value alraedy.
+ */
+ if ((error != HA_ERR_FOUND_DUPP_KEY) ||
+ !(table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ))
+ DBUG_VOID_RETURN;
+
+ /*
+ Get the number of the offended index.
+ We will see MAX_KEY if the engine cannot determine the affected index.
+ */
+ if ((keynr= table->file->get_dup_key(error)) >= MAX_KEY)
+ DBUG_VOID_RETURN;
+
+ /* Create unique_map with all fields used by that index. */
+ my_bitmap_init(&unique_map, unique_map_buf, table->s->fields, FALSE);
+ table->mark_columns_used_by_index_no_reset(keynr, &unique_map);
+
+ /* Subtract read_set and write_set. */
+ bitmap_subtract(&unique_map, table->read_set);
+ bitmap_subtract(&unique_map, table->write_set);
+
+ /*
+ If the unique index uses columns that are neither in read_set
+ nor in write_set, we must re-read the record.
+ Otherwise no need to do anything.
+ */
+ if (bitmap_is_clear_all(&unique_map))
+ DBUG_VOID_RETURN;
+
+ /* Get identifier of last read record into table->file->ref. */
+ table->file->position(table->record[0]);
+ /* Add all fields used by unique index to read_set. */
+ bitmap_union(table->read_set, &unique_map);
+ /* Tell the engine about the new set. */
+ table->file->column_bitmaps_signal();
+ /* Read record that is identified by table->file->ref. */
+ (void) table->file->ha_rnd_pos(table->record[1], table->file->ref);
+ /* Copy the newly read columns into the new record. */
+ for (field_p= table->field; (field= *field_p); field_p++)
+ if (bitmap_is_set(&unique_map, field->field_index))
+ field->copy_from_tmp(table->s->rec_buff_length);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Process usual UPDATE
+
+ SYNOPSIS
+ mysql_update()
+ thd thread handler
+ fields fields for update
+ values values of fields for update
+ conds WHERE clause expression
+ order_num number of elemen in ORDER BY clause
+ order ORDER BY clause list
+ limit limit clause
+ handle_duplicates how to handle duplicates
+
+ RETURN
+ 0 - OK
+ 2 - privilege check and openning table passed, but we need to convert to
+ multi-update because of view substitution
+ 1 - error
+*/
+
+int mysql_update(THD *thd,
+ TABLE_LIST *table_list,
+ List<Item> &fields,
+ List<Item> &values,
+ COND *conds,
+ uint order_num, ORDER *order,
+ ha_rows limit,
+ enum enum_duplicates handle_duplicates, bool ignore,
+ ha_rows *found_return, ha_rows *updated_return)
+{
+ bool using_limit= limit != HA_POS_ERROR;
+ bool safe_update= MY_TEST(thd->variables.option_bits & OPTION_SAFE_UPDATES);
+ bool used_key_is_modified= FALSE, transactional_table, will_batch;
+ bool can_compare_record;
+ int res;
+ int error, loc_error;
+ uint dup_key_found;
+ bool need_sort= TRUE;
+ bool reverse= FALSE;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ uint want_privilege;
+#endif
+ uint table_count= 0;
+ ha_rows updated, found;
+ key_map old_covering_keys;
+ TABLE *table;
+ SQL_SELECT *select= NULL;
+ READ_RECORD info;
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ ulonglong id;
+ List<Item> all_fields;
+ killed_state killed_status= NOT_KILLED;
+ Update_plan query_plan(thd->mem_root);
+ query_plan.index= MAX_KEY;
+ query_plan.using_filesort= FALSE;
+ DBUG_ENTER("mysql_update");
+
+ create_explain_query(thd->lex, thd->mem_root);
+ if (open_tables(thd, &table_list, &table_count, 0))
+ DBUG_RETURN(1);
+
+ //Prepare views so they are handled correctly.
+ if (mysql_handle_derived(thd->lex, DT_INIT))
+ DBUG_RETURN(1);
+
+ if (table_list->is_multitable())
+ {
+ DBUG_ASSERT(table_list->view != 0);
+ DBUG_PRINT("info", ("Switch to multi-update"));
+ /* pass counter value */
+ thd->lex->table_count= table_count;
+ /* convert to multiupdate */
+ DBUG_RETURN(2);
+ }
+ if (lock_tables(thd, table_list, table_count, 0))
+ DBUG_RETURN(1);
+
+ if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT))
+ DBUG_RETURN(1);
+ if (table_list->handle_derived(thd->lex, DT_PREPARE))
+ DBUG_RETURN(1);
+
+ THD_STAGE_INFO(thd, stage_init);
+ table= table_list->table;
+
+ if (!table_list->single_table_updatable())
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE");
+ DBUG_RETURN(1);
+ }
+ query_plan.updating_a_view= MY_TEST(table_list->view);
+
+ /* Calculate "table->covering_keys" based on the WHERE */
+ table->covering_keys= table->s->keys_in_use;
+ table->quick_keys.clear_all();
+
+ query_plan.select_lex= &thd->lex->select_lex;
+ query_plan.table= table;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* Force privilege re-checking for views after they have been opened. */
+ want_privilege= (table_list->view ? UPDATE_ACL :
+ table_list->grant.want_privilege);
+#endif
+ if (mysql_prepare_update(thd, table_list, &conds, order_num, order))
+ DBUG_RETURN(1);
+
+ old_covering_keys= table->covering_keys; // Keys used in WHERE
+ /* Check the fields we are going to modify */
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ table_list->grant.want_privilege= table->grant.want_privilege= want_privilege;
+ table_list->register_want_access(want_privilege);
+#endif
+ /* 'Unfix' fields to allow correct marking by the setup_fields function. */
+ if (table_list->is_view())
+ unfix_fields(fields);
+
+ if (setup_fields_with_no_wrap(thd, 0, fields, MARK_COLUMNS_WRITE, 0, 0))
+ DBUG_RETURN(1); /* purecov: inspected */
+ if (table_list->view && check_fields(thd, fields))
+ {
+ DBUG_RETURN(1);
+ }
+ if (check_key_in_view(thd, table_list))
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE");
+ DBUG_RETURN(1);
+ }
+ if (table->default_field)
+ table->mark_default_fields_for_write();
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /* Check values */
+ table_list->grant.want_privilege= table->grant.want_privilege=
+ (SELECT_ACL & ~table->grant.privilege);
+#endif
+ if (setup_fields(thd, 0, values, MARK_COLUMNS_READ, 0, 0))
+ {
+ free_underlaid_joins(thd, select_lex);
+ DBUG_RETURN(1); /* purecov: inspected */
+ }
+
+ /* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */
+ if (select_lex->optimize_unflattened_subqueries(false))
+ DBUG_RETURN(TRUE);
+
+ if (select_lex->inner_refs_list.elements &&
+ fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array))
+ DBUG_RETURN(1);
+
+ if (conds)
+ {
+ Item::cond_result cond_value;
+ conds= remove_eq_conds(thd, conds, &cond_value);
+ if (cond_value == Item::COND_FALSE)
+ {
+ limit= 0; // Impossible WHERE
+ query_plan.set_impossible_where();
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+ }
+ }
+
+ /*
+ If a timestamp field settable on UPDATE is present then to avoid wrong
+ update force the table handler to retrieve write-only fields to be able
+ to compare records and detect data change.
+ */
+ if ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) &&
+ table->default_field && table->has_default_function(true))
+ bitmap_union(table->read_set, table->write_set);
+ // Don't count on usage of 'only index' when calculating which key to use
+ table->covering_keys.clear_all();
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (prune_partitions(thd, table, conds))
+ {
+ free_underlaid_joins(thd, select_lex);
+
+ query_plan.set_no_partitions();
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+
+ my_ok(thd); // No matching records
+ DBUG_RETURN(0);
+ }
+#endif
+ /* Update the table->file->stats.records number */
+ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
+ set_statistics_for_table(thd, table);
+
+ select= make_select(table, 0, 0, conds, 0, &error);
+ if (error || !limit || thd->is_error() ||
+ (select && select->check_quick(thd, safe_update, limit)))
+ {
+ query_plan.set_impossible_where();
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ /*
+ There was an error or the error was already sent by
+ the quick select evaluation.
+ TODO: Add error code output parameter to Item::val_xxx() methods.
+ Currently they rely on the user checking DA for
+ errors when unwinding the stack after calling Item::val_xxx().
+ */
+ if (error || thd->is_error())
+ {
+ DBUG_RETURN(1); // Error in where
+ }
+ my_ok(thd); // No matching records
+ DBUG_RETURN(0);
+ }
+
+ /* If running in safe sql mode, don't allow updates without keys */
+ if (table->quick_keys.is_clear_all())
+ {
+ thd->set_status_no_index_used();
+ if (safe_update && !using_limit)
+ {
+ my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE,
+ ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0));
+ goto err;
+ }
+ }
+ init_ftfuncs(thd, select_lex, 1);
+
+ table->mark_columns_needed_for_update();
+
+ table->update_const_key_parts(conds);
+ order= simple_remove_const(order, conds);
+ query_plan.scanned_rows= select? select->records: table->file->stats.records;
+
+ if (select && select->quick && select->quick->unique_key_range())
+ { // Single row select (always "ordered"): Ok to use with key field UPDATE
+ need_sort= FALSE;
+ query_plan.index= MAX_KEY;
+ used_key_is_modified= FALSE;
+ }
+ else
+ {
+ ha_rows scanned_limit= query_plan.scanned_rows;
+ query_plan.index= get_index_for_order(order, table, select, limit,
+ &scanned_limit, &need_sort, &reverse);
+ if (!need_sort)
+ query_plan.scanned_rows= scanned_limit;
+
+ if (select && select->quick)
+ {
+ DBUG_ASSERT(need_sort || query_plan.index == select->quick->index);
+ used_key_is_modified= (!select->quick->unique_key_range() &&
+ select->quick->is_keys_used(table->write_set));
+ }
+ else
+ {
+ if (need_sort)
+ { // Assign table scan index to check below for modified key fields:
+ query_plan.index= table->file->key_used_on_scan;
+ }
+ if (query_plan.index != MAX_KEY)
+ { // Check if we are modifying a key that we are used to search with:
+ used_key_is_modified= is_key_used(table, query_plan.index, table->write_set);
+ }
+ }
+ }
+
+ /*
+ Query optimization is finished at this point.
+ - Save the decisions in the query plan
+ - if we're running EXPLAIN UPDATE, get out
+ */
+ query_plan.select= select;
+ query_plan.possible_keys= select? select->possible_keys: key_map(0);
+
+ if (used_key_is_modified || order ||
+ partition_key_modified(table, table->write_set))
+ {
+ if (order && need_sort)
+ query_plan.using_filesort= true;
+ else
+ query_plan.using_io_buffer= true;
+ }
+
+
+ /*
+ Ok, we have generated a query plan for the UPDATE.
+ - if we're running EXPLAIN UPDATE, goto produce explain output
+ - otherwise, execute the query plan
+ */
+ if (thd->lex->describe)
+ goto exit_without_my_ok;
+ query_plan.save_explain_data(thd->lex->explain);
+
+ DBUG_EXECUTE_IF("show_explain_probe_update_exec_start",
+ dbug_serve_apcs(thd, 1););
+
+ if (query_plan.using_filesort || query_plan.using_io_buffer)
+ {
+ /*
+ We can't update table directly; We must first search after all
+ matching rows before updating the table!
+ */
+ MY_BITMAP *save_read_set= table->read_set;
+ MY_BITMAP *save_write_set= table->write_set;
+
+ if (query_plan.index < MAX_KEY && old_covering_keys.is_set(query_plan.index))
+ table->add_read_columns_used_by_index(query_plan.index);
+ else
+ table->use_all_columns();
+
+ /* note: We avoid sorting if we sort on the used index */
+ if (query_plan.using_filesort)
+ {
+ /*
+ Doing an ORDER BY; Let filesort find and sort the rows we are going
+ to update
+ NOTE: filesort will call table->prepare_for_position()
+ */
+ uint length= 0;
+ SORT_FIELD *sortorder;
+ ha_rows examined_rows;
+ ha_rows found_rows;
+
+ table->sort.io_cache = (IO_CACHE *) my_malloc(sizeof(IO_CACHE),
+ MYF(MY_FAE | MY_ZEROFILL |
+ MY_THREAD_SPECIFIC));
+ if (!(sortorder=make_unireg_sortorder(order, &length, NULL)) ||
+ (table->sort.found_records= filesort(thd, table, sortorder, length,
+ select, limit,
+ true,
+ &examined_rows, &found_rows))
+ == HA_POS_ERROR)
+ {
+ goto err;
+ }
+ thd->inc_examined_row_count(examined_rows);
+ /*
+ Filesort has already found and selected the rows we want to update,
+ so we don't need the where clause
+ */
+ delete select;
+ select= 0;
+ }
+ else
+ {
+ /*
+ We are doing a search on a key that is updated. In this case
+ we go trough the matching rows, save a pointer to them and
+ update these in a separate loop based on the pointer.
+ */
+
+ IO_CACHE tempfile;
+ if (open_cached_file(&tempfile, mysql_tmpdir,TEMP_PREFIX,
+ DISK_BUFFER_SIZE, MYF(MY_WME)))
+ goto err;
+
+ /* If quick select is used, initialize it before retrieving rows. */
+ if (select && select->quick && select->quick->reset())
+ {
+ close_cached_file(&tempfile);
+ goto err;
+ }
+ table->file->try_semi_consistent_read(1);
+
+ /*
+ When we get here, we have one of the following options:
+ A. query_plan.index == MAX_KEY
+ This means we should use full table scan, and start it with
+ init_read_record call
+ B. query_plan.index != MAX_KEY
+ B.1 quick select is used, start the scan with init_read_record
+ B.2 quick select is not used, this is full index scan (with LIMIT)
+ Full index scan must be started with init_read_record_idx
+ */
+
+ if (query_plan.index == MAX_KEY || (select && select->quick))
+ {
+ if (init_read_record(&info, thd, table, select, 0, 1, FALSE))
+ {
+ close_cached_file(&tempfile);
+ goto err;
+ }
+ }
+ else
+ init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse);
+
+ THD_STAGE_INFO(thd, stage_searching_rows_for_update);
+ ha_rows tmp_limit= limit;
+
+ while (!(error=info.read_record(&info)) && !thd->killed)
+ {
+ if (table->vfield)
+ update_virtual_fields(thd, table,
+ table->triggers ? VCOL_UPDATE_ALL :
+ VCOL_UPDATE_FOR_READ);
+ thd->inc_examined_row_count(1);
+ if (!select || (error= select->skip_record(thd)) > 0)
+ {
+ if (table->file->was_semi_consistent_read())
+ continue; /* repeat the read of the same row if it still exists */
+
+ table->file->position(table->record[0]);
+ if (my_b_write(&tempfile,table->file->ref,
+ table->file->ref_length))
+ {
+ error=1; /* purecov: inspected */
+ break; /* purecov: inspected */
+ }
+ if (!--limit && using_limit)
+ {
+ error= -1;
+ break;
+ }
+ }
+ else
+ {
+ /*
+ Don't try unlocking the row if skip_record reported an error since in
+ this case the transaction might have been rolled back already.
+ */
+ if (error < 0)
+ {
+ /* Fatal error from select->skip_record() */
+ error= 1;
+ break;
+ }
+ else
+ table->file->unlock_row();
+ }
+ }
+ if (thd->killed && !error)
+ error= 1; // Aborted
+ limit= tmp_limit;
+ table->file->try_semi_consistent_read(0);
+ end_read_record(&info);
+
+ /* Change select to use tempfile */
+ if (select)
+ {
+ delete select->quick;
+ if (select->free_cond)
+ delete select->cond;
+ select->quick=0;
+ select->cond=0;
+ }
+ else
+ {
+ select= new SQL_SELECT;
+ select->head=table;
+ }
+ //psergey-todo: disable SHOW EXPLAIN because the plan was deleted?
+ if (reinit_io_cache(&tempfile,READ_CACHE,0L,0,0))
+ error=1; /* purecov: inspected */
+ select->file=tempfile; // Read row ptrs from this file
+ if (error >= 0)
+ goto err;
+ }
+ table->disable_keyread();
+ table->column_bitmaps_set(save_read_set, save_write_set);
+ }
+
+ if (ignore)
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+
+ if (select && select->quick && select->quick->reset())
+ goto err;
+ table->file->try_semi_consistent_read(1);
+ if (init_read_record(&info, thd, table, select, 0, 1, FALSE))
+ goto err;
+
+ updated= found= 0;
+ /*
+ Generate an error (in TRADITIONAL mode) or warning
+ when trying to set a NOT NULL field to NULL.
+ */
+ thd->count_cuted_fields= CHECK_FIELD_WARN;
+ thd->cuted_fields=0L;
+ THD_STAGE_INFO(thd, stage_updating);
+
+ transactional_table= table->file->has_transactions();
+ thd->abort_on_warning= !ignore && thd->is_strict_mode();
+ if (table->prepare_triggers_for_update_stmt_or_event())
+ {
+ will_batch= FALSE;
+ }
+ else
+ will_batch= !table->file->start_bulk_update();
+
+ /*
+ Assure that we can use position()
+ if we need to create an error message.
+ */
+ if (table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ)
+ table->prepare_for_position();
+
+ table->reset_default_fields();
+
+ /*
+ We can use compare_record() to optimize away updates if
+ the table handler is returning all columns OR if
+ if all updated columns are read
+ */
+ can_compare_record= records_are_comparable(table);
+
+ while (!(error=info.read_record(&info)) && !thd->killed)
+ {
+ if (table->vfield)
+ update_virtual_fields(thd, table,
+ table->triggers ? VCOL_UPDATE_ALL :
+ VCOL_UPDATE_FOR_READ);
+ thd->inc_examined_row_count(1);
+ if (!select || select->skip_record(thd) > 0)
+ {
+ if (table->file->was_semi_consistent_read())
+ continue; /* repeat the read of the same row if it still exists */
+
+ store_record(table,record[1]);
+ if (fill_record_n_invoke_before_triggers(thd, table, fields, values, 0,
+ TRG_EVENT_UPDATE))
+ break; /* purecov: inspected */
+
+ found++;
+
+ if (!can_compare_record || compare_record(table))
+ {
+ if (table->default_field && table->update_default_fields())
+ {
+ error= 1;
+ break;
+ }
+ if ((res= table_list->view_check_option(thd, ignore)) !=
+ VIEW_CHECK_OK)
+ {
+ found--;
+ if (res == VIEW_CHECK_SKIP)
+ continue;
+ else if (res == VIEW_CHECK_ERROR)
+ {
+ error= 1;
+ break;
+ }
+ }
+ if (will_batch)
+ {
+ /*
+ Typically a batched handler can execute the batched jobs when:
+ 1) When specifically told to do so
+ 2) When it is not a good idea to batch anymore
+ 3) When it is necessary to send batch for other reasons
+ (One such reason is when READ's must be performed)
+
+ 1) is covered by exec_bulk_update calls.
+ 2) and 3) is handled by the bulk_update_row method.
+
+ bulk_update_row can execute the updates including the one
+ defined in the bulk_update_row or not including the row
+ in the call. This is up to the handler implementation and can
+ vary from call to call.
+
+ The dup_key_found reports the number of duplicate keys found
+ in those updates actually executed. It only reports those if
+ the extra call with HA_EXTRA_IGNORE_DUP_KEY have been issued.
+ If this hasn't been issued it returns an error code and can
+ ignore this number. Thus any handler that implements batching
+ for UPDATE IGNORE must also handle this extra call properly.
+
+ If a duplicate key is found on the record included in this
+ call then it should be included in the count of dup_key_found
+ and error should be set to 0 (only if these errors are ignored).
+ */
+ error= table->file->ha_bulk_update_row(table->record[1],
+ table->record[0],
+ &dup_key_found);
+ limit+= dup_key_found;
+ updated-= dup_key_found;
+ }
+ else
+ {
+ /* Non-batched update */
+ error= table->file->ha_update_row(table->record[1],
+ table->record[0]);
+ }
+ if (!error || error == HA_ERR_RECORD_IS_THE_SAME)
+ {
+ if (error != HA_ERR_RECORD_IS_THE_SAME)
+ updated++;
+ else
+ error= 0;
+ }
+ else if (!ignore ||
+ table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ {
+ /*
+ If (ignore && error is ignorable) we don't have to
+ do anything; otherwise...
+ */
+ myf flags= 0;
+
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ flags|= ME_FATALERROR; /* Other handler errors are fatal */
+
+ prepare_record_for_error_message(error, table);
+ table->file->print_error(error,MYF(flags));
+ error= 1;
+ break;
+ }
+ }
+
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
+ TRG_ACTION_AFTER, TRUE))
+ {
+ error= 1;
+ break;
+ }
+
+ if (!--limit && using_limit)
+ {
+ /*
+ We have reached end-of-file in most common situations where no
+ batching has occurred and if batching was supposed to occur but
+ no updates were made and finally when the batch execution was
+ performed without error and without finding any duplicate keys.
+ If the batched updates were performed with errors we need to
+ check and if no error but duplicate key's found we need to
+ continue since those are not counted for in limit.
+ */
+ if (will_batch &&
+ ((error= table->file->exec_bulk_update(&dup_key_found)) ||
+ dup_key_found))
+ {
+ if (error)
+ {
+ /* purecov: begin inspected */
+ /*
+ The handler should not report error of duplicate keys if they
+ are ignored. This is a requirement on batching handlers.
+ */
+ prepare_record_for_error_message(error, table);
+ table->file->print_error(error,MYF(0));
+ error= 1;
+ break;
+ /* purecov: end */
+ }
+ /*
+ Either an error was found and we are ignoring errors or there
+ were duplicate keys found. In both cases we need to correct
+ the counters and continue the loop.
+ */
+ limit= dup_key_found; //limit is 0 when we get here so need to +
+ updated-= dup_key_found;
+ }
+ else
+ {
+ error= -1; // Simulate end of file
+ break;
+ }
+ }
+ }
+ /*
+ Don't try unlocking the row if skip_record reported an error since in
+ this case the transaction might have been rolled back already.
+ */
+ else if (!thd->is_error())
+ table->file->unlock_row();
+ else
+ {
+ error= 1;
+ break;
+ }
+ thd->get_stmt_da()->inc_current_row_for_warning();
+ if (thd->is_error())
+ {
+ error= 1;
+ break;
+ }
+ }
+ table->auto_increment_field_not_null= FALSE;
+ dup_key_found= 0;
+ /*
+ Caching the killed status to pass as the arg to query event constuctor;
+ The cached value can not change whereas the killed status can
+ (externally) since this point and change of the latter won't affect
+ binlogging.
+ It's assumed that if an error was set in combination with an effective
+ killed status then the error is due to killing.
+ */
+ killed_status= thd->killed; // get the status of the volatile
+ // simulated killing after the loop must be ineffective for binlogging
+ DBUG_EXECUTE_IF("simulate_kill_bug27571",
+ {
+ thd->killed= KILL_QUERY;
+ };);
+ error= (killed_status == NOT_KILLED)? error : 1;
+
+ if (error &&
+ will_batch &&
+ (loc_error= table->file->exec_bulk_update(&dup_key_found)))
+ /*
+ An error has occurred when a batched update was performed and returned
+ an error indication. It cannot be an allowed duplicate key error since
+ we require the batching handler to treat this as a normal behavior.
+
+ Otherwise we simply remove the number of duplicate keys records found
+ in the batched update.
+ */
+ {
+ /* purecov: begin inspected */
+ prepare_record_for_error_message(loc_error, table);
+ table->file->print_error(loc_error,MYF(ME_FATALERROR));
+ error= 1;
+ /* purecov: end */
+ }
+ else
+ updated-= dup_key_found;
+ if (will_batch)
+ table->file->end_bulk_update();
+ table->file->try_semi_consistent_read(0);
+
+ if (!transactional_table && updated > 0)
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+
+ end_read_record(&info);
+ delete select;
+ THD_STAGE_INFO(thd, stage_end);
+ (void) table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+
+ /*
+ Invalidate the table in the query cache if something changed.
+ This must be before binlog writing and ha_autocommit_...
+ */
+ if (updated)
+ {
+ query_cache_invalidate3(thd, table_list, 1);
+ }
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ /*
+ error < 0 means really no error at all: we processed all rows until the
+ last one without error. error > 0 means an error (e.g. unique key
+ violation and no IGNORE or REPLACE). error == 0 is also an error (if
+ preparing the record or invoking before triggers fails). See
+ ha_autocommit_or_rollback(error>=0) and DBUG_RETURN(error>=0) below.
+ Sometimes we want to binlog even if we updated no rows, in case user used
+ it to be sure master and slave are in same state.
+ */
+ if ((error < 0) || thd->transaction.stmt.modified_non_trans_table)
+ {
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= 0;
+ if (error < 0)
+ thd->clear_error();
+ else
+ errcode= query_error_code(thd, killed_status == NOT_KILLED);
+
+ if (thd->binlog_query(THD::ROW_QUERY_TYPE,
+ thd->query(), thd->query_length(),
+ transactional_table, FALSE, FALSE, errcode))
+ {
+ error=1; // Rollback update
+ }
+ }
+ }
+ DBUG_ASSERT(transactional_table || !updated || thd->transaction.stmt.modified_non_trans_table);
+ free_underlaid_joins(thd, select_lex);
+
+ /* If LAST_INSERT_ID(X) was used, report X */
+ id= thd->arg_of_last_insert_id_function ?
+ thd->first_successful_insert_id_in_prev_stmt : 0;
+
+ if (error < 0)
+ {
+ char buff[MYSQL_ERRMSG_SIZE];
+ my_snprintf(buff, sizeof(buff), ER(ER_UPDATE_INFO), (ulong) found,
+ (ulong) updated,
+ (ulong) thd->get_stmt_da()->current_statement_warn_count());
+ my_ok(thd, (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated,
+ id, buff);
+ DBUG_PRINT("info",("%ld records updated", (long) updated));
+ }
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE; /* calc cuted fields */
+ thd->abort_on_warning= 0;
+ if (thd->lex->current_select->first_cond_optimization)
+ {
+ thd->lex->current_select->save_leaf_tables(thd);
+ thd->lex->current_select->first_cond_optimization= 0;
+ }
+ *found_return= found;
+ *updated_return= updated;
+ DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0);
+
+err:
+
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ table->disable_keyread();
+ thd->abort_on_warning= 0;
+ DBUG_RETURN(1);
+
+exit_without_my_ok:
+ query_plan.save_explain_data(thd->lex->explain);
+
+ int err2= thd->lex->explain->send_explain(thd);
+
+ delete select;
+ free_underlaid_joins(thd, select_lex);
+ DBUG_RETURN((err2 || thd->is_error()) ? 1 : 0);
+}
+
+/*
+ Prepare items in UPDATE statement
+
+ SYNOPSIS
+ mysql_prepare_update()
+ thd - thread handler
+ table_list - global/local table list
+ conds - conditions
+ order_num - number of ORDER BY list entries
+ order - ORDER BY clause list
+
+ RETURN VALUE
+ FALSE OK
+ TRUE error
+*/
+bool mysql_prepare_update(THD *thd, TABLE_LIST *table_list,
+ Item **conds, uint order_num, ORDER *order)
+{
+ Item *fake_conds= 0;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ TABLE *table= table_list->table;
+#endif
+ List<Item> all_fields;
+ SELECT_LEX *select_lex= &thd->lex->select_lex;
+ DBUG_ENTER("mysql_prepare_update");
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ table_list->grant.want_privilege= table->grant.want_privilege=
+ (SELECT_ACL & ~table->grant.privilege);
+ table_list->register_want_access(SELECT_ACL);
+#endif
+
+ thd->lex->allow_sum_func= 0;
+
+ /*
+ We do not call DT_MERGE_FOR_INSERT because it has no sense for simple
+ (not multi-) update
+ */
+ if (mysql_handle_derived(thd->lex, DT_PREPARE))
+ DBUG_RETURN(TRUE);
+
+ if (setup_tables_and_check_access(thd, &select_lex->context,
+ &select_lex->top_join_list,
+ table_list,
+ select_lex->leaf_tables,
+ FALSE, UPDATE_ACL, SELECT_ACL, TRUE) ||
+ setup_conds(thd, table_list, select_lex->leaf_tables, conds) ||
+ select_lex->setup_ref_array(thd, order_num) ||
+ setup_order(thd, select_lex->ref_pointer_array,
+ table_list, all_fields, all_fields, order) ||
+ setup_ftfuncs(select_lex))
+ DBUG_RETURN(TRUE);
+
+ /* Check that we are not using table that we are updating in a sub select */
+ {
+ TABLE_LIST *duplicate;
+ if ((duplicate= unique_table(thd, table_list, table_list->next_global, 0)))
+ {
+ update_non_unique_table_error(table_list, "UPDATE", duplicate);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ select_lex->fix_prepare_information(thd, conds, &fake_conds);
+ DBUG_RETURN(FALSE);
+}
+
+
+/***************************************************************************
+ Update multiple tables from join
+***************************************************************************/
+
+/*
+ Get table map for list of Item_field
+*/
+
+static table_map get_table_map(List<Item> *items)
+{
+ List_iterator_fast<Item> item_it(*items);
+ Item_field *item;
+ table_map map= 0;
+
+ while ((item= (Item_field *) item_it++))
+ map|= item->all_used_tables();
+ DBUG_PRINT("info", ("table_map: 0x%08lx", (long) map));
+ return map;
+}
+
+/**
+ If one row is updated through two different aliases and the first
+ update physically moves the row, the second update will error
+ because the row is no longer located where expected. This function
+ checks if the multiple-table update is about to do that and if so
+ returns with an error.
+
+ The following update operations physically moves rows:
+ 1) Update of a column in a clustered primary key
+ 2) Update of a column used to calculate which partition the row belongs to
+
+ This function returns with an error if both of the following are
+ true:
+
+ a) A table in the multiple-table update statement is updated
+ through multiple aliases (including views)
+ b) At least one of the updates on the table from a) may physically
+ moves the row. Note: Updating a column used to calculate which
+ partition a row belongs to does not necessarily mean that the
+ row is moved. The new value may or may not belong to the same
+ partition.
+
+ @param leaves First leaf table
+ @param tables_for_update Map of tables that are updated
+
+ @return
+ true if the update is unsafe, in which case an error message is also set,
+ false otherwise.
+*/
+static
+bool unsafe_key_update(List<TABLE_LIST> leaves, table_map tables_for_update)
+{
+ List_iterator_fast<TABLE_LIST> it(leaves), it2(leaves);
+ TABLE_LIST *tl, *tl2;
+
+ while ((tl= it++))
+ {
+ if (tl->table->map & tables_for_update)
+ {
+ TABLE *table1= tl->table;
+ bool primkey_clustered= (table1->file->primary_key_is_clustered() &&
+ table1->s->primary_key != MAX_KEY);
+
+ bool table_partitioned= false;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ table_partitioned= (table1->part_info != NULL);
+#endif
+
+ if (!table_partitioned && !primkey_clustered)
+ continue;
+
+ it2.rewind();
+ while ((tl2= it2++))
+ {
+ /*
+ Look at "next" tables only since all previous tables have
+ already been checked
+ */
+ TABLE *table2= tl2->table;
+ if (tl2 != tl &&
+ table2->map & tables_for_update && table1->s == table2->s)
+ {
+ // A table is updated through two aliases
+ if (table_partitioned &&
+ (partition_key_modified(table1, table1->write_set) ||
+ partition_key_modified(table2, table2->write_set)))
+ {
+ // Partitioned key is updated
+ my_error(ER_MULTI_UPDATE_KEY_CONFLICT, MYF(0),
+ tl->belong_to_view ? tl->belong_to_view->alias
+ : tl->alias,
+ tl2->belong_to_view ? tl2->belong_to_view->alias
+ : tl2->alias);
+ return true;
+ }
+
+ if (primkey_clustered)
+ {
+ // The primary key can cover multiple columns
+ KEY key_info= table1->key_info[table1->s->primary_key];
+ KEY_PART_INFO *key_part= key_info.key_part;
+ KEY_PART_INFO *key_part_end= key_part + key_info.user_defined_key_parts;
+
+ for (;key_part != key_part_end; ++key_part)
+ {
+ if (bitmap_is_set(table1->write_set, key_part->fieldnr-1) ||
+ bitmap_is_set(table2->write_set, key_part->fieldnr-1))
+ {
+ // Clustered primary key is updated
+ my_error(ER_MULTI_UPDATE_KEY_CONFLICT, MYF(0),
+ tl->belong_to_view ? tl->belong_to_view->alias
+ : tl->alias,
+ tl2->belong_to_view ? tl2->belong_to_view->alias
+ : tl2->alias);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ Check if there is enough privilege on specific table used by the
+ main select list of multi-update directly or indirectly (through
+ a view).
+
+ @param[in] thd Thread context.
+ @param[in] table Table list element for the table.
+ @param[in] tables_for_update Bitmap with tables being updated.
+ @param[in/out] updated_arg Set to true if table in question is
+ updated, also set to true if it is
+ a view and one of its underlying
+ tables is updated. Should be
+ initialized to false by the caller
+ before a sequence of calls to this
+ function.
+
+ @note To determine which tables/views are updated we have to go from
+ leaves to root since tables_for_update contains map of leaf
+ tables being updated and doesn't include non-leaf tables
+ (fields are already resolved to leaf tables).
+
+ @retval false - Success, all necessary privileges on all tables are
+ present or might be present on column-level.
+ @retval true - Failure, some necessary privilege on some table is
+ missing.
+*/
+
+static bool multi_update_check_table_access(THD *thd, TABLE_LIST *table,
+ table_map tables_for_update,
+ bool *updated_arg)
+{
+ if (table->view)
+ {
+ bool updated= false;
+ /*
+ If it is a mergeable view then we need to check privileges on its
+ underlying tables being merged (including views). We also need to
+ check if any of them is updated in order to find if this view is
+ updated.
+ If it is a non-mergeable view then it can't be updated.
+ */
+ DBUG_ASSERT(table->merge_underlying_list ||
+ (!table->updatable &&
+ !(table->table->map & tables_for_update)));
+
+ for (TABLE_LIST *tbl= table->merge_underlying_list; tbl;
+ tbl= tbl->next_local)
+ {
+ if (multi_update_check_table_access(thd, tbl, tables_for_update,
+ &updated))
+ {
+ tbl->hide_view_error(thd);
+ return true;
+ }
+ }
+ if (check_table_access(thd, updated ? UPDATE_ACL: SELECT_ACL, table,
+ FALSE, 1, FALSE))
+ return true;
+ *updated_arg|= updated;
+ /* We only need SELECT privilege for columns in the values list. */
+ table->grant.want_privilege= SELECT_ACL & ~table->grant.privilege;
+ }
+ else
+ {
+ /* Must be a base or derived table. */
+ const bool updated= table->table->map & tables_for_update;
+ if (check_table_access(thd, updated ? UPDATE_ACL : SELECT_ACL, table,
+ FALSE, 1, FALSE))
+ return true;
+ *updated_arg|= updated;
+ /* We only need SELECT privilege for columns in the values list. */
+ if (!table->derived)
+ {
+ table->grant.want_privilege= SELECT_ACL & ~table->grant.privilege;
+ table->table->grant.want_privilege= (SELECT_ACL &
+ ~table->table->grant.privilege);
+ }
+ }
+ return false;
+}
+
+
+/*
+ make update specific preparation and checks after opening tables
+
+ SYNOPSIS
+ mysql_multi_update_prepare()
+ thd thread handler
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+int mysql_multi_update_prepare(THD *thd)
+{
+ LEX *lex= thd->lex;
+ TABLE_LIST *table_list= lex->query_tables;
+ TABLE_LIST *tl;
+ List<Item> *fields= &lex->select_lex.item_list;
+ table_map tables_for_update;
+ bool update_view= 0;
+ /*
+ if this multi-update was converted from usual update, here is table
+ counter else junk will be assigned here, but then replaced with real
+ count in open_tables()
+ */
+ uint table_count= lex->table_count;
+ const bool using_lock_tables= thd->locked_tables_mode != LTM_NONE;
+ bool original_multiupdate= (thd->lex->sql_command == SQLCOM_UPDATE_MULTI);
+ DBUG_ENTER("mysql_multi_update_prepare");
+
+ /* following need for prepared statements, to run next time multi-update */
+ thd->lex->sql_command= SQLCOM_UPDATE_MULTI;
+
+ /*
+ Open tables and create derived ones, but do not lock and fill them yet.
+
+ During prepare phase acquire only S metadata locks instead of SW locks to
+ keep prepare of multi-UPDATE compatible with concurrent LOCK TABLES WRITE
+ and global read lock.
+ */
+ if ((original_multiupdate &&
+ open_tables(thd, &table_list, &table_count,
+ (thd->stmt_arena->is_stmt_prepare() ?
+ MYSQL_OPEN_FORCE_SHARED_MDL : 0))) ||
+ mysql_handle_derived(lex, DT_INIT))
+ DBUG_RETURN(TRUE);
+ /*
+ setup_tables() need for VIEWs. JOIN::prepare() will call setup_tables()
+ second time, but this call will do nothing (there are check for second
+ call in setup_tables()).
+ */
+
+ //We need to merge for insert prior to prepare.
+ if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT))
+ DBUG_RETURN(TRUE);
+ if (mysql_handle_derived(lex, DT_PREPARE))
+ DBUG_RETURN(TRUE);
+
+ if (setup_tables_and_check_access(thd, &lex->select_lex.context,
+ &lex->select_lex.top_join_list,
+ table_list,
+ lex->select_lex.leaf_tables, FALSE,
+ UPDATE_ACL, SELECT_ACL, FALSE))
+ DBUG_RETURN(TRUE);
+
+ if (lex->select_lex.handle_derived(thd->lex, DT_MERGE))
+ DBUG_RETURN(TRUE);
+
+ if (setup_fields_with_no_wrap(thd, 0, *fields, MARK_COLUMNS_WRITE, 0, 0))
+ DBUG_RETURN(TRUE);
+
+ for (tl= table_list; tl ; tl= tl->next_local)
+ {
+ if (tl->view)
+ {
+ update_view= 1;
+ break;
+ }
+ }
+
+ if (update_view && check_fields(thd, *fields))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ thd->table_map_for_update= tables_for_update= get_table_map(fields);
+
+ if (unsafe_key_update(lex->select_lex.leaf_tables, tables_for_update))
+ DBUG_RETURN(true);
+
+ /*
+ Setup timestamp handling and locking mode
+ */
+ List_iterator<TABLE_LIST> ti(lex->select_lex.leaf_tables);
+ while ((tl= ti++))
+ {
+ TABLE *table= tl->table;
+
+ /* if table will be updated then check that it is unique */
+ if (table->map & tables_for_update)
+ {
+ if (!tl->single_table_updatable() || check_key_in_view(thd, tl))
+ {
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), tl->alias, "UPDATE");
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_PRINT("info",("setting table `%s` for update", tl->alias));
+ /*
+ If table will be updated we should not downgrade lock for it and
+ leave it as is.
+ */
+ }
+ else
+ {
+ DBUG_PRINT("info",("setting table `%s` for read-only", tl->alias));
+ /*
+ If we are using the binary log, we need TL_READ_NO_INSERT to get
+ correct order of statements. Otherwise, we use a TL_READ lock to
+ improve performance.
+ We don't downgrade metadata lock from SW to SR in this case as
+ there is no guarantee that the same ticket is not used by
+ another table instance used by this statement which is going to
+ be write-locked (for example, trigger to be invoked might try
+ to update this table).
+ Last argument routine_modifies_data for read_lock_type_for_table()
+ is ignored, as prelocking placeholder will never be set here.
+ */
+ DBUG_ASSERT(tl->prelocking_placeholder == false);
+ thr_lock_type lock_type= read_lock_type_for_table(thd, lex, tl, true);
+ if (using_lock_tables)
+ tl->lock_type= lock_type;
+ else
+ tl->set_lock_type(thd, lock_type);
+ tl->updating= 0;
+ }
+ }
+
+ /*
+ Check access privileges for tables being updated or read.
+ Note that unlike in the above loop we need to iterate here not only
+ through all leaf tables but also through all view hierarchy.
+ */
+ for (tl= table_list; tl; tl= tl->next_local)
+ {
+ bool not_used= false;
+ if (multi_update_check_table_access(thd, tl, tables_for_update, &not_used))
+ DBUG_RETURN(TRUE);
+ }
+
+ /* check single table update for view compound from several tables */
+ for (tl= table_list; tl; tl= tl->next_local)
+ {
+ if (tl->is_merged_derived())
+ {
+ TABLE_LIST *for_update= 0;
+ if (tl->check_single_table(&for_update, tables_for_update, tl))
+ {
+ my_error(ER_VIEW_MULTIUPDATE, MYF(0),
+ tl->view_db.str, tl->view_name.str);
+ DBUG_RETURN(-1);
+ }
+ }
+ }
+
+ /* now lock and fill tables */
+ if (!thd->stmt_arena->is_stmt_prepare() &&
+ lock_tables(thd, table_list, table_count, 0))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ /* @todo: downgrade the metadata locks here. */
+
+ /*
+ Check that we are not using table that we are updating, but we should
+ skip all tables of UPDATE SELECT itself
+ */
+ lex->select_lex.exclude_from_table_unique_test= TRUE;
+ /* We only need SELECT privilege for columns in the values list */
+ ti.rewind();
+ while ((tl= ti++))
+ {
+ TABLE *table= tl->table;
+ TABLE_LIST *tlist;
+ if (!(tlist= tl->top_table())->derived)
+ {
+ tlist->grant.want_privilege=
+ (SELECT_ACL & ~tlist->grant.privilege);
+ table->grant.want_privilege= (SELECT_ACL & ~table->grant.privilege);
+ }
+ DBUG_PRINT("info", ("table: %s want_privilege: %u", tl->alias,
+ (uint) table->grant.want_privilege));
+ if (tl->lock_type != TL_READ &&
+ tl->lock_type != TL_READ_NO_INSERT)
+ {
+ TABLE_LIST *duplicate;
+ if ((duplicate= unique_table(thd, tl, table_list, 0)))
+ {
+ update_non_unique_table_error(table_list, "UPDATE", duplicate);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ /*
+ Set exclude_from_table_unique_test value back to FALSE. It is needed for
+ further check in multi_update::prepare whether to use record cache.
+ */
+ lex->select_lex.exclude_from_table_unique_test= FALSE;
+
+ if (lex->save_prep_leaf_tables())
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN (FALSE);
+}
+
+
+/*
+ Setup multi-update handling and call SELECT to do the join
+*/
+
+bool mysql_multi_update(THD *thd,
+ TABLE_LIST *table_list,
+ List<Item> *fields,
+ List<Item> *values,
+ COND *conds,
+ ulonglong options,
+ enum enum_duplicates handle_duplicates,
+ bool ignore,
+ SELECT_LEX_UNIT *unit,
+ SELECT_LEX *select_lex,
+ multi_update **result)
+{
+ bool res;
+ DBUG_ENTER("mysql_multi_update");
+
+ if (!(*result= new multi_update(table_list,
+ &thd->lex->select_lex.leaf_tables,
+ fields, values,
+ handle_duplicates, ignore)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ thd->abort_on_warning= thd->is_strict_mode();
+ List<Item> total_list;
+
+ res= mysql_select(thd, &select_lex->ref_pointer_array,
+ table_list, select_lex->with_wild,
+ total_list,
+ conds, 0, (ORDER *) NULL, (ORDER *)NULL, (Item *) NULL,
+ (ORDER *)NULL,
+ options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
+ OPTION_SETUP_TABLES_DONE,
+ *result, unit, select_lex);
+
+ DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error()));
+ res|= thd->is_error();
+ if (unlikely(res))
+ (*result)->abort_result_set();
+ else
+ {
+ if (thd->lex->describe)
+ res= thd->lex->explain->send_explain(thd);
+ }
+ thd->abort_on_warning= 0;
+ DBUG_RETURN(res);
+}
+
+
+multi_update::multi_update(TABLE_LIST *table_list,
+ List<TABLE_LIST> *leaves_list,
+ List<Item> *field_list, List<Item> *value_list,
+ enum enum_duplicates handle_duplicates_arg,
+ bool ignore_arg)
+ :all_tables(table_list), leaves(leaves_list), update_tables(0),
+ tmp_tables(0), updated(0), found(0), fields(field_list),
+ values(value_list), table_count(0), copy_field(0),
+ handle_duplicates(handle_duplicates_arg), do_update(1), trans_safe(1),
+ transactional_tables(0), ignore(ignore_arg), error_handled(0), prepared(0)
+{}
+
+
+/*
+ Connect fields with tables and create list of tables that are updated
+*/
+
+int multi_update::prepare(List<Item> &not_used_values,
+ SELECT_LEX_UNIT *lex_unit)
+
+{
+ TABLE_LIST *table_ref;
+ SQL_I_List<TABLE_LIST> update;
+ table_map tables_to_update;
+ Item_field *item;
+ List_iterator_fast<Item> field_it(*fields);
+ List_iterator_fast<Item> value_it(*values);
+ uint i, max_fields;
+ uint leaf_table_count= 0;
+ List_iterator<TABLE_LIST> ti(*leaves);
+ DBUG_ENTER("multi_update::prepare");
+
+ if (prepared)
+ DBUG_RETURN(0);
+ prepared= true;
+
+ thd->count_cuted_fields= CHECK_FIELD_WARN;
+ thd->cuted_fields=0L;
+ THD_STAGE_INFO(thd, stage_updating_main_table);
+
+ tables_to_update= get_table_map(fields);
+
+ if (!tables_to_update)
+ {
+ my_message(ER_NO_TABLES_USED, ER(ER_NO_TABLES_USED), MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ /*
+ We gather the set of columns read during evaluation of SET expression in
+ TABLE::tmp_set by pointing TABLE::read_set to it and then restore it after
+ setup_fields().
+ */
+ while ((table_ref= ti++))
+ {
+ TABLE *table= table_ref->table;
+ if (tables_to_update & table->map)
+ {
+ DBUG_ASSERT(table->read_set == &table->def_read_set);
+ table->read_set= &table->tmp_set;
+ bitmap_clear_all(table->read_set);
+ }
+ }
+
+ /*
+ We have to check values after setup_tables to get covering_keys right in
+ reference tables
+ */
+
+ int error= setup_fields(thd, 0, *values, MARK_COLUMNS_READ, 0, 0);
+
+ ti.rewind();
+ while ((table_ref= ti++))
+ {
+ TABLE *table= table_ref->table;
+ if (tables_to_update & table->map)
+ {
+ table->read_set= &table->def_read_set;
+ bitmap_union(table->read_set, &table->tmp_set);
+ /*
+ If a timestamp field settable on UPDATE is present then to avoid wrong
+ update force the table handler to retrieve write-only fields to be able
+ to compare records and detect data change.
+ */
+ if ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) &&
+ table->default_field && table->has_default_function(true))
+ bitmap_union(table->read_set, table->write_set);
+ }
+ }
+
+ if (error)
+ DBUG_RETURN(1);
+
+ /*
+ Save tables beeing updated in update_tables
+ update_table->shared is position for table
+ Don't use key read on tables that are updated
+ */
+
+ update.empty();
+ ti.rewind();
+ while ((table_ref= ti++))
+ {
+ /* TODO: add support of view of join support */
+ TABLE *table=table_ref->table;
+ leaf_table_count++;
+ if (tables_to_update & table->map)
+ {
+ TABLE_LIST *tl= (TABLE_LIST*) thd->memdup(table_ref,
+ sizeof(*tl));
+ if (!tl)
+ DBUG_RETURN(1);
+ update.link_in_list(tl, &tl->next_local);
+ tl->shared= table_count++;
+ table->no_keyread=1;
+ table->covering_keys.clear_all();
+ table->pos_in_table_list= tl;
+ table->prepare_triggers_for_update_stmt_or_event();
+ table->reset_default_fields();
+ }
+ }
+
+
+ table_count= update.elements;
+ update_tables= update.first;
+
+ tmp_tables = (TABLE**) thd->calloc(sizeof(TABLE *) * table_count);
+ tmp_table_param = (TMP_TABLE_PARAM*) thd->calloc(sizeof(TMP_TABLE_PARAM) *
+ table_count);
+ fields_for_table= (List_item **) thd->alloc(sizeof(List_item *) *
+ table_count);
+ values_for_table= (List_item **) thd->alloc(sizeof(List_item *) *
+ table_count);
+ if (thd->is_fatal_error)
+ DBUG_RETURN(1);
+ for (i=0 ; i < table_count ; i++)
+ {
+ fields_for_table[i]= new List_item;
+ values_for_table[i]= new List_item;
+ }
+ if (thd->is_fatal_error)
+ DBUG_RETURN(1);
+
+ /* Split fields into fields_for_table[] and values_by_table[] */
+
+ while ((item= (Item_field *) field_it++))
+ {
+ Item *value= value_it++;
+ uint offset= item->field->table->pos_in_table_list->shared;
+ fields_for_table[offset]->push_back(item);
+ values_for_table[offset]->push_back(value);
+ }
+ if (thd->is_fatal_error)
+ DBUG_RETURN(1);
+
+ /* Allocate copy fields */
+ max_fields=0;
+ for (i=0 ; i < table_count ; i++)
+ set_if_bigger(max_fields, fields_for_table[i]->elements + leaf_table_count);
+ copy_field= new Copy_field[max_fields];
+ DBUG_RETURN(thd->is_fatal_error != 0);
+}
+
+void multi_update::update_used_tables()
+{
+ Item *item;
+ List_iterator_fast<Item> it(*values);
+ while ((item= it++))
+ {
+ item->update_used_tables();
+ }
+}
+
+/*
+ Check if table is safe to update on fly
+
+ SYNOPSIS
+ safe_update_on_fly()
+ thd Thread handler
+ join_tab How table is used in join
+ all_tables List of tables
+
+ NOTES
+ We can update the first table in join on the fly if we know that
+ a row in this table will never be read twice. This is true under
+ the following conditions:
+
+ - No column is both written to and read in SET expressions.
+
+ - We are doing a table scan and the data is in a separate file (MyISAM) or
+ if we don't update a clustered key.
+
+ - We are doing a range scan and we don't update the scan key or
+ the primary key for a clustered table handler.
+
+ - Table is not joined to itself.
+
+ This function gets information about fields to be updated from
+ the TABLE::write_set bitmap.
+
+ WARNING
+ This code is a bit dependent of how make_join_readinfo() works.
+
+ The field table->tmp_set is used for keeping track of which fields are
+ read during evaluation of the SET expression. See multi_update::prepare.
+
+ RETURN
+ 0 Not safe to update
+ 1 Safe to update
+*/
+
+static bool safe_update_on_fly(THD *thd, JOIN_TAB *join_tab,
+ TABLE_LIST *table_ref, TABLE_LIST *all_tables)
+{
+ TABLE *table= join_tab->table;
+ if (unique_table(thd, table_ref, all_tables, 0))
+ return 0;
+ switch (join_tab->type) {
+ case JT_SYSTEM:
+ case JT_CONST:
+ case JT_EQ_REF:
+ return TRUE; // At most one matching row
+ case JT_REF:
+ case JT_REF_OR_NULL:
+ return !is_key_used(table, join_tab->ref.key, table->write_set);
+ case JT_ALL:
+ if (bitmap_is_overlapping(&table->tmp_set, table->write_set))
+ return FALSE;
+ /* If range search on index */
+ if (join_tab->quick)
+ return !join_tab->quick->is_keys_used(table->write_set);
+ /* If scanning in clustered key */
+ if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
+ table->s->primary_key < MAX_KEY)
+ return !is_key_used(table, table->s->primary_key, table->write_set);
+ return TRUE;
+ default:
+ break; // Avoid compler warning
+ }
+ return FALSE;
+
+}
+
+
+/*
+ Initialize table for multi table
+
+ IMPLEMENTATION
+ - Update first table in join on the fly, if possible
+ - Create temporary tables to store changed values for all other tables
+ that are updated (and main_table if the above doesn't hold).
+*/
+
+bool
+multi_update::initialize_tables(JOIN *join)
+{
+ TABLE_LIST *table_ref;
+ DBUG_ENTER("initialize_tables");
+
+ if ((thd->variables.option_bits & OPTION_SAFE_UPDATES) && error_if_full_join(join))
+ DBUG_RETURN(1);
+ main_table=join->join_tab->table;
+ table_to_update= 0;
+
+ /* Any update has at least one pair (field, value) */
+ DBUG_ASSERT(fields->elements);
+ /*
+ Only one table may be modified by UPDATE of an updatable view.
+ For an updatable view first_table_for_update indicates this
+ table.
+ For a regular multi-update it refers to some updated table.
+ */
+ TABLE *first_table_for_update= ((Item_field *) fields->head())->field->table;
+
+ /* Create a temporary table for keys to all tables, except main table */
+ for (table_ref= update_tables; table_ref; table_ref= table_ref->next_local)
+ {
+ TABLE *table=table_ref->table;
+ uint cnt= table_ref->shared;
+ List<Item> temp_fields;
+ ORDER group;
+ TMP_TABLE_PARAM *tmp_param;
+
+ if (ignore)
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ if (table == main_table) // First table in join
+ {
+ if (safe_update_on_fly(thd, join->join_tab, table_ref, all_tables))
+ {
+ table->mark_columns_needed_for_update();
+ table_to_update= table; // Update table on the fly
+ continue;
+ }
+ }
+ table->mark_columns_needed_for_update();
+ table->prepare_for_position();
+
+ /*
+ enable uncacheable flag if we update a view with check option
+ and check option has a subselect, otherwise, the check option
+ can be evaluated after the subselect was freed as independent
+ (See full_local in JOIN::join_free()).
+ */
+ if (table_ref->check_option && !join->select_lex->uncacheable)
+ {
+ SELECT_LEX_UNIT *tmp_unit;
+ SELECT_LEX *sl;
+ for (tmp_unit= join->select_lex->first_inner_unit();
+ tmp_unit;
+ tmp_unit= tmp_unit->next_unit())
+ {
+ for (sl= tmp_unit->first_select(); sl; sl= sl->next_select())
+ {
+ if (sl->master_unit()->item)
+ {
+ join->select_lex->uncacheable|= UNCACHEABLE_CHECKOPTION;
+ goto loop_end;
+ }
+ }
+ }
+ }
+loop_end:
+
+ if (table == first_table_for_update && table_ref->check_option)
+ {
+ table_map unupdated_tables= table_ref->check_option->used_tables() &
+ ~first_table_for_update->map;
+ List_iterator<TABLE_LIST> ti(*leaves);
+ TABLE_LIST *tbl_ref;
+ while ((tbl_ref= ti++) && unupdated_tables)
+ {
+ if (unupdated_tables & tbl_ref->table->map)
+ unupdated_tables&= ~tbl_ref->table->map;
+ else
+ continue;
+ if (unupdated_check_opt_tables.push_back(tbl_ref->table))
+ DBUG_RETURN(1);
+ }
+ }
+
+ tmp_param= tmp_table_param+cnt;
+
+ /*
+ Create a temporary table to store all fields that are changed for this
+ table. The first field in the temporary table is a pointer to the
+ original row so that we can find and update it. For the updatable
+ VIEW a few following fields are rowids of tables used in the CHECK
+ OPTION condition.
+ */
+
+ List_iterator_fast<TABLE> tbl_it(unupdated_check_opt_tables);
+ TABLE *tbl= table;
+ do
+ {
+ /*
+ Signal each table (including tables referenced by WITH CHECK OPTION
+ clause) for which we will store row position in the temporary table
+ that we need a position to be read first.
+ */
+ tbl->prepare_for_position();
+
+ Field_string *field= new Field_string(tbl->file->ref_length, 0,
+ tbl->alias.c_ptr(),
+ &my_charset_bin);
+ if (!field)
+ DBUG_RETURN(1);
+ field->init(tbl);
+ /*
+ The field will be converted to varstring when creating tmp table if
+ table to be updated was created by mysql 4.1. Deny this.
+ */
+ field->can_alter_field_type= 0;
+ Item_field *ifield= new Item_field((Field *) field);
+ if (!ifield)
+ DBUG_RETURN(1);
+ ifield->maybe_null= 0;
+ if (temp_fields.push_back(ifield))
+ DBUG_RETURN(1);
+ } while ((tbl= tbl_it++));
+
+ temp_fields.concat(fields_for_table[cnt]);
+
+ /* Make an unique key over the first field to avoid duplicated updates */
+ bzero((char*) &group, sizeof(group));
+ group.asc= 1;
+ group.item= (Item**) temp_fields.head_ref();
+
+ tmp_param->quick_group=1;
+ tmp_param->field_count=temp_fields.elements;
+ tmp_param->group_parts=1;
+ tmp_param->group_length= table->file->ref_length;
+ /* small table, ignore SQL_BIG_TABLES */
+ my_bool save_big_tables= thd->variables.big_tables;
+ thd->variables.big_tables= FALSE;
+ tmp_tables[cnt]=create_tmp_table(thd, tmp_param, temp_fields,
+ (ORDER*) &group, 0, 0,
+ TMP_TABLE_ALL_COLUMNS, HA_POS_ERROR, "");
+ thd->variables.big_tables= save_big_tables;
+ if (!tmp_tables[cnt])
+ DBUG_RETURN(1);
+ tmp_tables[cnt]->file->extra(HA_EXTRA_WRITE_CACHE);
+ }
+ DBUG_RETURN(0);
+}
+
+
+multi_update::~multi_update()
+{
+ TABLE_LIST *table;
+ for (table= update_tables ; table; table= table->next_local)
+ {
+ table->table->no_keyread= table->table->no_cache= 0;
+ if (ignore)
+ table->table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
+ }
+
+ if (tmp_tables)
+ {
+ for (uint cnt = 0; cnt < table_count; cnt++)
+ {
+ if (tmp_tables[cnt])
+ {
+ free_tmp_table(thd, tmp_tables[cnt]);
+ tmp_table_param[cnt].cleanup();
+ }
+ }
+ }
+ if (copy_field)
+ delete [] copy_field;
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE; // Restore this setting
+ DBUG_ASSERT(trans_safe || !updated ||
+ thd->transaction.all.modified_non_trans_table);
+}
+
+
+int multi_update::send_data(List<Item> &not_used_values)
+{
+ TABLE_LIST *cur_table;
+ DBUG_ENTER("multi_update::send_data");
+
+ for (cur_table= update_tables; cur_table; cur_table= cur_table->next_local)
+ {
+ TABLE *table= cur_table->table;
+ uint offset= cur_table->shared;
+ /*
+ Check if we are using outer join and we didn't find the row
+ or if we have already updated this row in the previous call to this
+ function.
+
+ The same row may be presented here several times in a join of type
+ UPDATE t1 FROM t1,t2 SET t1.a=t2.a
+
+ In this case we will do the update for the first found row combination.
+ The join algorithm guarantees that we will not find the a row in
+ t1 several times.
+ */
+ if (table->status & (STATUS_NULL_ROW | STATUS_UPDATED))
+ continue;
+
+ if (table == table_to_update)
+ {
+ /*
+ We can use compare_record() to optimize away updates if
+ the table handler is returning all columns OR if
+ if all updated columns are read
+ */
+ bool can_compare_record;
+ can_compare_record= records_are_comparable(table);
+
+ table->status|= STATUS_UPDATED;
+ store_record(table,record[1]);
+ if (fill_record_n_invoke_before_triggers(thd, table, *fields_for_table[offset],
+ *values_for_table[offset], 0,
+ TRG_EVENT_UPDATE))
+ DBUG_RETURN(1);
+
+ /*
+ Reset the table->auto_increment_field_not_null as it is valid for
+ only one row.
+ */
+ table->auto_increment_field_not_null= FALSE;
+ found++;
+ if (!can_compare_record || compare_record(table))
+ {
+ int error;
+
+ if (table->default_field && table->update_default_fields())
+ DBUG_RETURN(1);
+
+ if ((error= cur_table->view_check_option(thd, ignore)) !=
+ VIEW_CHECK_OK)
+ {
+ found--;
+ if (error == VIEW_CHECK_SKIP)
+ continue;
+ else if (error == VIEW_CHECK_ERROR)
+ DBUG_RETURN(1);
+ }
+ if (!updated++)
+ {
+ /*
+ Inform the main table that we are going to update the table even
+ while we may be scanning it. This will flush the read cache
+ if it's used.
+ */
+ main_table->file->extra(HA_EXTRA_PREPARE_FOR_UPDATE);
+ }
+ if ((error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ {
+ updated--;
+ if (!ignore ||
+ table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ {
+ /*
+ If (ignore && error == is ignorable) we don't have to
+ do anything; otherwise...
+ */
+ myf flags= 0;
+
+ if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
+ flags|= ME_FATALERROR; /* Other handler errors are fatal */
+
+ prepare_record_for_error_message(error, table);
+ table->file->print_error(error,MYF(flags));
+ DBUG_RETURN(1);
+ }
+ }
+ else
+ {
+ if (error == HA_ERR_RECORD_IS_THE_SAME)
+ {
+ error= 0;
+ updated--;
+ }
+ /* non-transactional or transactional table got modified */
+ /* either multi_update class' flag is raised in its branch */
+ if (table->file->has_transactions())
+ transactional_tables= TRUE;
+ else
+ {
+ trans_safe= FALSE;
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ }
+ }
+ }
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
+ TRG_ACTION_AFTER, TRUE))
+ DBUG_RETURN(1);
+ }
+ else
+ {
+ int error;
+ TABLE *tmp_table= tmp_tables[offset];
+ /*
+ For updatable VIEW store rowid of the updated table and
+ rowids of tables used in the CHECK OPTION condition.
+ */
+ uint field_num= 0;
+ List_iterator_fast<TABLE> tbl_it(unupdated_check_opt_tables);
+ TABLE *tbl= table;
+ do
+ {
+ tbl->file->position(tbl->record[0]);
+ memcpy((char*) tmp_table->field[field_num]->ptr,
+ (char*) tbl->file->ref, tbl->file->ref_length);
+ /*
+ For outer joins a rowid field may have no NOT_NULL_FLAG,
+ so we have to reset NULL bit for this field.
+ (set_notnull() resets NULL bit only if available).
+ */
+ tmp_table->field[field_num]->set_notnull();
+ field_num++;
+ } while ((tbl= tbl_it++));
+
+ /* Store regular updated fields in the row. */
+ fill_record(thd, tmp_table,
+ tmp_table->field + 1 + unupdated_check_opt_tables.elements,
+ *values_for_table[offset], TRUE, FALSE);
+
+ /* Write row, ignoring duplicated updates to a row */
+ error= tmp_table->file->ha_write_tmp_row(tmp_table->record[0]);
+ if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE)
+ {
+ if (error &&
+ create_internal_tmp_table_from_heap(thd, tmp_table,
+ tmp_table_param[offset].start_recinfo,
+ &tmp_table_param[offset].recinfo,
+ error, 1, NULL))
+ {
+ do_update= 0;
+ DBUG_RETURN(1); // Not a table_is_full error
+ }
+ found++;
+ }
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+void multi_update::abort_result_set()
+{
+ /* the error was handled or nothing deleted and no side effects return */
+ if (error_handled ||
+ (!thd->transaction.stmt.modified_non_trans_table && !updated))
+ return;
+
+ /* Something already updated so we have to invalidate cache */
+ if (updated)
+ query_cache_invalidate3(thd, update_tables, 1);
+ /*
+ If all tables that has been updated are trans safe then just do rollback.
+ If not attempt to do remaining updates.
+ */
+
+ if (! trans_safe)
+ {
+ DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table);
+ if (do_update && table_count > 1)
+ {
+ /* Add warning here */
+ /*
+ todo/fixme: do_update() is never called with the arg 1.
+ should it change the signature to become argless?
+ */
+ (void) do_updates();
+ }
+ }
+ if (thd->transaction.stmt.modified_non_trans_table)
+ {
+ /*
+ The query has to binlog because there's a modified non-transactional table
+ either from the query's list or via a stored routine: bug#13270,23333
+ */
+ if (mysql_bin_log.is_open())
+ {
+ /*
+ THD::killed status might not have been set ON at time of an error
+ got caught and if happens later the killed error is written
+ into repl event.
+ */
+ int errcode= query_error_code(thd, thd->killed == NOT_KILLED);
+ /* the error of binary logging is ignored */
+ (void)thd->binlog_query(THD::ROW_QUERY_TYPE,
+ thd->query(), thd->query_length(),
+ transactional_tables, FALSE, FALSE, errcode);
+ }
+ thd->transaction.all.modified_non_trans_table= TRUE;
+ }
+ DBUG_ASSERT(trans_safe || !updated || thd->transaction.stmt.modified_non_trans_table);
+}
+
+
+int multi_update::do_updates()
+{
+ TABLE_LIST *cur_table;
+ int local_error= 0;
+ ha_rows org_updated;
+ TABLE *table, *tmp_table, *err_table;
+ List_iterator_fast<TABLE> check_opt_it(unupdated_check_opt_tables);
+ DBUG_ENTER("multi_update::do_updates");
+
+ do_update= 0; // Don't retry this function
+ if (!found)
+ DBUG_RETURN(0);
+ for (cur_table= update_tables; cur_table; cur_table= cur_table->next_local)
+ {
+ bool can_compare_record;
+ uint offset= cur_table->shared;
+
+ table = cur_table->table;
+ if (table == table_to_update)
+ continue; // Already updated
+ org_updated= updated;
+ tmp_table= tmp_tables[cur_table->shared];
+ tmp_table->file->extra(HA_EXTRA_CACHE); // Change to read cache
+ if ((local_error= table->file->ha_rnd_init(0)))
+ {
+ err_table= table;
+ goto err;
+ }
+ table->file->extra(HA_EXTRA_NO_CACHE);
+
+ check_opt_it.rewind();
+ while(TABLE *tbl= check_opt_it++)
+ {
+ if ((local_error= tbl->file->ha_rnd_init(1)))
+ {
+ err_table= tbl;
+ goto err;
+ }
+ tbl->file->extra(HA_EXTRA_CACHE);
+ }
+
+ /*
+ Setup copy functions to copy fields from temporary table
+ */
+ List_iterator_fast<Item> field_it(*fields_for_table[offset]);
+ Field **field;
+ Copy_field *copy_field_ptr= copy_field, *copy_field_end;
+
+ /* Skip row pointers */
+ field= tmp_table->field + 1 + unupdated_check_opt_tables.elements;
+ for ( ; *field ; field++)
+ {
+ Item_field *item= (Item_field* ) field_it++;
+ (copy_field_ptr++)->set(item->field, *field, 0);
+ }
+ copy_field_end=copy_field_ptr;
+
+ if ((local_error= tmp_table->file->ha_rnd_init(1)))
+ {
+ err_table= tmp_table;
+ goto err;
+ }
+
+ can_compare_record= records_are_comparable(table);
+
+ for (;;)
+ {
+ if (thd->killed && trans_safe)
+ {
+ thd->fatal_error();
+ goto err2;
+ }
+ if ((local_error= tmp_table->file->ha_rnd_next(tmp_table->record[0])))
+ {
+ if (local_error == HA_ERR_END_OF_FILE)
+ break;
+ if (local_error == HA_ERR_RECORD_DELETED)
+ continue; // May happen on dup key
+ err_table= tmp_table;
+ goto err;
+ }
+
+ /* call rnd_pos() using rowids from temporary table */
+ check_opt_it.rewind();
+ TABLE *tbl= table;
+ uint field_num= 0;
+ do
+ {
+ if ((local_error=
+ tbl->file->ha_rnd_pos(tbl->record[0],
+ (uchar *) tmp_table->field[field_num]->ptr)))
+ {
+ err_table= tbl;
+ goto err;
+ }
+ field_num++;
+ } while ((tbl= check_opt_it++));
+
+ table->status|= STATUS_UPDATED;
+ store_record(table,record[1]);
+
+ /* Copy data from temporary table to current table */
+ for (copy_field_ptr=copy_field;
+ copy_field_ptr != copy_field_end;
+ copy_field_ptr++)
+ {
+ (*copy_field_ptr->do_copy)(copy_field_ptr);
+ copy_field_ptr->to_field->set_has_explicit_value();
+ }
+
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
+ TRG_ACTION_BEFORE, TRUE))
+ goto err2;
+
+ if (!can_compare_record || compare_record(table))
+ {
+ int error;
+ if (table->default_field && (error= table->update_default_fields()))
+ goto err2;
+ if ((error= cur_table->view_check_option(thd, ignore)) !=
+ VIEW_CHECK_OK)
+ {
+ if (error == VIEW_CHECK_SKIP)
+ continue;
+ else if (error == VIEW_CHECK_ERROR)
+ {
+ thd->fatal_error();
+ goto err2;
+ }
+ }
+ if ((local_error=table->file->ha_update_row(table->record[1],
+ table->record[0])) &&
+ local_error != HA_ERR_RECORD_IS_THE_SAME)
+ {
+ if (!ignore ||
+ table->file->is_fatal_error(local_error, HA_CHECK_DUP_KEY))
+ {
+ err_table= table;
+ goto err;
+ }
+ }
+ if (local_error != HA_ERR_RECORD_IS_THE_SAME)
+ updated++;
+ else
+ local_error= 0;
+ }
+
+ if (table->triggers &&
+ table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
+ TRG_ACTION_AFTER, TRUE))
+ goto err2;
+ }
+
+ if (updated != org_updated)
+ {
+ if (table->file->has_transactions())
+ transactional_tables= TRUE;
+ else
+ {
+ trans_safe= FALSE; // Can't do safe rollback
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ }
+ }
+ (void) table->file->ha_rnd_end();
+ (void) tmp_table->file->ha_rnd_end();
+ check_opt_it.rewind();
+ while (TABLE *tbl= check_opt_it++)
+ tbl->file->ha_rnd_end();
+
+ }
+ DBUG_RETURN(0);
+
+err:
+ {
+ prepare_record_for_error_message(local_error, err_table);
+ err_table->file->print_error(local_error,MYF(ME_FATALERROR));
+ }
+
+err2:
+ if (table->file->inited)
+ (void) table->file->ha_rnd_end();
+ if (tmp_table->file->inited)
+ (void) tmp_table->file->ha_rnd_end();
+ check_opt_it.rewind();
+ while (TABLE *tbl= check_opt_it++)
+ {
+ if (tbl->file->inited)
+ (void) tbl->file->ha_rnd_end();
+ }
+
+ if (updated != org_updated)
+ {
+ if (table->file->has_transactions())
+ transactional_tables= TRUE;
+ else
+ {
+ trans_safe= FALSE;
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+ }
+ }
+ DBUG_RETURN(1);
+}
+
+
+/* out: 1 if error, 0 if success */
+
+bool multi_update::send_eof()
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ ulonglong id;
+ killed_state killed_status= NOT_KILLED;
+ DBUG_ENTER("multi_update::send_eof");
+ THD_STAGE_INFO(thd, stage_updating_reference_tables);
+
+ /*
+ Does updates for the last n - 1 tables, returns 0 if ok;
+ error takes into account killed status gained in do_updates()
+ */
+ int local_error= thd->is_error();
+ if (!local_error)
+ local_error = (table_count) ? do_updates() : 0;
+ /*
+ if local_error is not set ON until after do_updates() then
+ later carried out killing should not affect binlogging.
+ */
+ killed_status= (local_error == 0) ? NOT_KILLED : thd->killed;
+ THD_STAGE_INFO(thd, stage_end);
+
+ /* We must invalidate the query cache before binlog writing and
+ ha_autocommit_... */
+
+ if (updated)
+ {
+ query_cache_invalidate3(thd, update_tables, 1);
+ }
+ /*
+ Write the SQL statement to the binlog if we updated
+ rows and we succeeded or if we updated some non
+ transactional tables.
+
+ The query has to binlog because there's a modified non-transactional table
+ either from the query's list or via a stored routine: bug#13270,23333
+ */
+
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
+
+ if (local_error == 0 || thd->transaction.stmt.modified_non_trans_table)
+ {
+ if (mysql_bin_log.is_open())
+ {
+ int errcode= 0;
+ if (local_error == 0)
+ thd->clear_error();
+ else
+ errcode= query_error_code(thd, killed_status == NOT_KILLED);
+ if (thd->binlog_query(THD::ROW_QUERY_TYPE,
+ thd->query(), thd->query_length(),
+ transactional_tables, FALSE, FALSE, errcode))
+ {
+ local_error= 1; // Rollback update
+ }
+ }
+ }
+ DBUG_ASSERT(trans_safe || !updated ||
+ thd->transaction.stmt.modified_non_trans_table);
+
+ if (local_error != 0)
+ error_handled= TRUE; // to force early leave from ::abort_result_set()
+
+ if (local_error > 0) // if the above log write did not fail ...
+ {
+ /* Safety: If we haven't got an error before (can happen in do_updates) */
+ my_message(ER_UNKNOWN_ERROR, "An error occured in multi-table update",
+ MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ id= thd->arg_of_last_insert_id_function ?
+ thd->first_successful_insert_id_in_prev_stmt : 0;
+ my_snprintf(buff, sizeof(buff), ER(ER_UPDATE_INFO),
+ (ulong) found, (ulong) updated, (ulong) thd->cuted_fields);
+ ::my_ok(thd, (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated,
+ id, buff);
+ DBUG_RETURN(FALSE);
+}
diff --git a/sql/sql_update.h b/sql/sql_update.h
new file mode 100644
index 00000000000..64029c5d634
--- /dev/null
+++ b/sql/sql_update.h
@@ -0,0 +1,44 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef SQL_UPDATE_INCLUDED
+#define SQL_UPDATE_INCLUDED
+
+#include "sql_class.h" /* enum_duplicates */
+
+class Item;
+struct TABLE_LIST;
+class THD;
+
+typedef class st_select_lex SELECT_LEX;
+typedef class st_select_lex_unit SELECT_LEX_UNIT;
+
+bool mysql_prepare_update(THD *thd, TABLE_LIST *table_list,
+ Item **conds, uint order_num, ORDER *order);
+int mysql_update(THD *thd,TABLE_LIST *tables,List<Item> &fields,
+ List<Item> &values,COND *conds,
+ uint order_num, ORDER *order, ha_rows limit,
+ enum enum_duplicates handle_duplicates, bool ignore,
+ ha_rows *found_return, ha_rows *updated_return);
+bool mysql_multi_update(THD *thd, TABLE_LIST *table_list,
+ List<Item> *fields, List<Item> *values,
+ COND *conds, ulonglong options,
+ enum enum_duplicates handle_duplicates, bool ignore,
+ SELECT_LEX_UNIT *unit, SELECT_LEX *select_lex,
+ multi_update **result);
+bool records_are_comparable(const TABLE *table);
+bool compare_record(const TABLE *table);
+
+#endif /* SQL_UPDATE_INCLUDED */
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
new file mode 100644
index 00000000000..41647a7262f
--- /dev/null
+++ b/sql/sql_view.cc
@@ -0,0 +1,2128 @@
+/* Copyright (c) 2004, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+*/
+
+#define MYSQL_LEX 1
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_view.h"
+#include "sql_base.h" // find_table_in_global_list, lock_table_names
+#include "sql_parse.h" // sql_parse
+#include "sql_cache.h" // query_cache_*
+#include "lock.h" // MYSQL_OPEN_SKIP_TEMPORARY
+#include "sql_show.h" // append_identifier
+#include "sql_table.h" // build_table_filename
+#include "sql_db.h" // mysql_opt_change_db, mysql_change_db
+#include "sql_acl.h" // *_ACL, check_grant
+#include "sql_select.h"
+#include "parse_file.h"
+#include "sp_head.h"
+#include "sp.h"
+#include "sp_cache.h"
+#include "datadict.h" // dd_frm_is_view()
+#include "sql_derived.h"
+
+#define MD5_BUFF_LENGTH 33
+
+const LEX_STRING view_type= { C_STRING_WITH_LEN("VIEW") };
+
+static int mysql_register_view(THD *, TABLE_LIST *, enum_view_create_mode);
+
+/*
+ Make a unique name for an anonymous view column
+ SYNOPSIS
+ target reference to the item for which a new name has to be made
+ item_list list of items within which we should check uniqueness of
+ the created name
+ last_element the last element of the list above
+
+ NOTE
+ Unique names are generated by adding 'My_exp_' to the old name of the
+ column. In case the name that was created this way already exists, we
+ add a numeric postfix to its end (i.e. "1") and increase the number
+ until the name becomes unique. If the generated name is longer than
+ NAME_LEN, it is truncated.
+*/
+
+static void make_unique_view_field_name(Item *target,
+ List<Item> &item_list,
+ Item *last_element)
+{
+ char *name= (target->orig_name ?
+ target->orig_name :
+ target->name);
+ size_t name_len;
+ uint attempt;
+ char buff[NAME_LEN+1];
+ List_iterator_fast<Item> itc(item_list);
+
+ for (attempt= 0;; attempt++)
+ {
+ Item *check;
+ bool ok= TRUE;
+
+ if (attempt)
+ name_len= my_snprintf(buff, NAME_LEN, "My_exp_%d_%s", attempt, name);
+ else
+ name_len= my_snprintf(buff, NAME_LEN, "My_exp_%s", name);
+
+ do
+ {
+ check= itc++;
+ if (check != target &&
+ my_strcasecmp(system_charset_info, buff, check->name) == 0)
+ {
+ ok= FALSE;
+ break;
+ }
+ } while (check != last_element);
+ if (ok)
+ break;
+ itc.rewind();
+ }
+
+ target->orig_name= target->name;
+ target->set_name(buff, name_len, system_charset_info);
+}
+
+
+/*
+ Check if items with same names are present in list and possibly
+ generate unique names for them.
+
+ SYNOPSIS
+ item_list list of Items which should be checked for duplicates
+ gen_unique_view_name flag: generate unique name or return with error when
+ duplicate names are found.
+
+ DESCRIPTION
+ This function is used on view creation and preparation of derived tables.
+ It checks item_list for items with duplicate names. If it founds two
+ items with same name and conversion to unique names isn't allowed, or
+ names for both items are set by user - function fails.
+ Otherwise it generates unique name for one item with autogenerated name
+ using make_unique_view_field_name()
+
+ RETURN VALUE
+ FALSE no duplicate names found, or they are converted to unique ones
+ TRUE duplicate names are found and they can't be converted or conversion
+ isn't allowed
+*/
+
+bool check_duplicate_names(List<Item> &item_list, bool gen_unique_view_name)
+{
+ Item *item;
+ List_iterator_fast<Item> it(item_list);
+ List_iterator_fast<Item> itc(item_list);
+ DBUG_ENTER("check_duplicate_names");
+
+ while ((item= it++))
+ {
+ Item *check;
+ /* treat underlying fields like set by user names */
+ if (item->real_item()->type() == Item::FIELD_ITEM)
+ item->is_autogenerated_name= FALSE;
+ itc.rewind();
+ while ((check= itc++) && check != item)
+ {
+ if (my_strcasecmp(system_charset_info, item->name, check->name) == 0)
+ {
+ if (!gen_unique_view_name)
+ goto err;
+ if (item->is_autogenerated_name)
+ make_unique_view_field_name(item, item_list, item);
+ else if (check->is_autogenerated_name)
+ make_unique_view_field_name(check, item_list, item);
+ else
+ goto err;
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+
+err:
+ my_error(ER_DUP_FIELDNAME, MYF(0), item->name);
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Check if auto generated column names are conforming and
+ possibly generate a conforming name for them if not.
+
+ @param item_list List of Items which should be checked
+*/
+
+static void make_valid_column_names(List<Item> &item_list)
+{
+ Item *item;
+ uint name_len;
+ List_iterator_fast<Item> it(item_list);
+ char buff[NAME_LEN];
+ DBUG_ENTER("make_valid_column_names");
+
+ for (uint column_no= 1; (item= it++); column_no++)
+ {
+ if (!item->is_autogenerated_name || !check_column_name(item->name))
+ continue;
+ name_len= my_snprintf(buff, NAME_LEN, "Name_exp_%u", column_no);
+ item->orig_name= item->name;
+ item->set_name(buff, name_len, system_charset_info);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Fill defined view parts
+
+ SYNOPSIS
+ fill_defined_view_parts()
+ thd current thread.
+ view view to operate on
+
+ DESCRIPTION
+ This function will initialize the parts of the view
+ definition that are not specified in ALTER VIEW
+ to their values from CREATE VIEW.
+ The view must be opened to get its definition.
+ We use a copy of the view when opening because we want
+ to preserve the original view instance.
+
+ RETURN VALUE
+ TRUE can't open table
+ FALSE success
+*/
+static bool
+fill_defined_view_parts (THD *thd, TABLE_LIST *view)
+{
+ LEX *lex= thd->lex;
+ TABLE_LIST decoy;
+
+ memcpy (&decoy, view, sizeof (TABLE_LIST));
+ if (tdc_open_view(thd, &decoy, decoy.alias, thd->mem_root,
+ OPEN_VIEW_NO_PARSE))
+ return TRUE;
+
+ if (!lex->definer)
+ {
+ view->definer.host= decoy.definer.host;
+ view->definer.user= decoy.definer.user;
+ lex->definer= &view->definer;
+ }
+ if (lex->create_view_algorithm == DTYPE_ALGORITHM_UNDEFINED)
+ lex->create_view_algorithm= (uint8) decoy.algorithm;
+ if (lex->create_view_suid == VIEW_SUID_DEFAULT)
+ lex->create_view_suid= decoy.view_suid ?
+ VIEW_SUID_DEFINER : VIEW_SUID_INVOKER;
+
+ return FALSE;
+}
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+
+/**
+ @brief CREATE VIEW privileges pre-check.
+
+ @param thd thread handler
+ @param tables tables used in the view
+ @param views views to create
+ @param mode VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
+
+ @retval FALSE Operation was a success.
+ @retval TRUE An error occured.
+*/
+
+bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view,
+ enum_view_create_mode mode)
+{
+ LEX *lex= thd->lex;
+ /* first table in list is target VIEW name => cut off it */
+ TABLE_LIST *tbl;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ SELECT_LEX *sl;
+ bool res= TRUE;
+ DBUG_ENTER("create_view_precheck");
+
+ /*
+ Privilege check for view creation:
+ - user has CREATE VIEW privilege on view table
+ - user has DROP privilege in case of ALTER VIEW or CREATE OR REPLACE
+ VIEW
+ - user has some (SELECT/UPDATE/INSERT/DELETE) privileges on columns of
+ underlying tables used on top of SELECT list (because it can be
+ (theoretically) updated, so it is enough to have UPDATE privilege on
+ them, for example)
+ - user has SELECT privilege on columns used in expressions of VIEW select
+ - for columns of underly tables used on top of SELECT list also will be
+ checked that we have not more privileges on correspondent column of view
+ table (i.e. user will not get some privileges by view creation)
+ */
+ if ((check_access(thd, CREATE_VIEW_ACL, view->db,
+ &view->grant.privilege,
+ &view->grant.m_internal,
+ 0, 0) ||
+ check_grant(thd, CREATE_VIEW_ACL, view, FALSE, 1, FALSE)) ||
+ (mode != VIEW_CREATE_NEW &&
+ (check_access(thd, DROP_ACL, view->db,
+ &view->grant.privilege,
+ &view->grant.m_internal,
+ 0, 0) ||
+ check_grant(thd, DROP_ACL, view, FALSE, 1, FALSE))))
+ goto err;
+
+ for (sl= select_lex; sl; sl= sl->next_select())
+ {
+ for (tbl= sl->get_table_list(); tbl; tbl= tbl->next_local)
+ {
+ /*
+ Ensure that we have some privileges on this table, more strict check
+ will be done on column level after preparation,
+ */
+ if (check_some_access(thd, VIEW_ANY_ACL, tbl))
+ {
+ my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
+ "ANY", thd->security_ctx->priv_user,
+ thd->security_ctx->priv_host, tbl->table_name);
+ goto err;
+ }
+ /*
+ Mark this table as a table which will be checked after the prepare
+ phase
+ */
+ tbl->table_in_first_from_clause= 1;
+
+ /*
+ We need to check only SELECT_ACL for all normal fields, fields for
+ which we need "any" (SELECT/UPDATE/INSERT/DELETE) privilege will be
+ checked later
+ */
+ tbl->grant.want_privilege= SELECT_ACL;
+ /*
+ Make sure that all rights are loaded to the TABLE::grant field.
+
+ tbl->table_name will be correct name of table because VIEWs are
+ not opened yet.
+ */
+ fill_effective_table_privileges(thd, &tbl->grant, tbl->db,
+ tbl->table_name);
+ }
+ }
+
+ if (&lex->select_lex != lex->all_selects_list)
+ {
+ /* check tables of subqueries */
+ for (tbl= tables; tbl; tbl= tbl->next_global)
+ {
+ if (!tbl->table_in_first_from_clause)
+ {
+ if (check_access(thd, SELECT_ACL, tbl->db,
+ &tbl->grant.privilege,
+ &tbl->grant.m_internal,
+ 0, 0) ||
+ check_grant(thd, SELECT_ACL, tbl, FALSE, 1, FALSE))
+ goto err;
+ }
+ }
+ }
+ /*
+ Mark fields for special privilege check ("any" privilege)
+ */
+ for (sl= select_lex; sl; sl= sl->next_select())
+ {
+ List_iterator_fast<Item> it(sl->item_list);
+ Item *item;
+ while ((item= it++))
+ {
+ Item_field *field;
+ if ((field= item->field_for_view_update()))
+ {
+ /*
+ any_privileges may be reset later by the Item_field::set_field
+ method in case of a system temporary table.
+ */
+ field->any_privileges= 1;
+ }
+ }
+ }
+
+ res= FALSE;
+
+err:
+ DBUG_RETURN(res || thd->is_error());
+}
+
+#else
+
+bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view,
+ enum_view_create_mode mode)
+{
+ return FALSE;
+}
+
+#endif
+
+
+/**
+ @brief Creating/altering VIEW procedure
+
+ @param thd thread handler
+ @param views views to create
+ @param mode VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
+
+ @note This function handles both create and alter view commands.
+
+ @retval FALSE Operation was a success.
+ @retval TRUE An error occured.
+*/
+
+bool mysql_create_view(THD *thd, TABLE_LIST *views,
+ enum_view_create_mode mode)
+{
+ LEX *lex= thd->lex;
+ bool link_to_local;
+ /* first table in list is target VIEW name => cut off it */
+ TABLE_LIST *view= lex->unlink_first_table(&link_to_local);
+ TABLE_LIST *tables= lex->query_tables;
+ TABLE_LIST *tbl;
+ SELECT_LEX *select_lex= &lex->select_lex;
+ SELECT_LEX *sl;
+ SELECT_LEX_UNIT *unit= &lex->unit;
+ bool res= FALSE;
+ DBUG_ENTER("mysql_create_view");
+
+ /* This is ensured in the parser. */
+ DBUG_ASSERT(!lex->proc_list.first && !lex->result &&
+ !lex->param_list.elements);
+
+ /*
+ We can't allow taking exclusive meta-data locks of unlocked view under
+ LOCK TABLES since this might lead to deadlock. Since at the moment we
+ can't really lock view with LOCK TABLES we simply prohibit creation/
+ alteration of views under LOCK TABLES.
+ */
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ res= TRUE;
+ goto err;
+ }
+
+ if ((res= create_view_precheck(thd, tables, view, mode)))
+ goto err;
+
+ lex->link_first_table_back(view, link_to_local);
+ view->open_type= OT_BASE_ONLY;
+
+ if (open_temporary_tables(thd, lex->query_tables) ||
+ open_and_lock_tables(thd, lex->query_tables, TRUE, 0))
+ {
+ view= lex->unlink_first_table(&link_to_local);
+ res= TRUE;
+ goto err;
+ }
+
+ view= lex->unlink_first_table(&link_to_local);
+
+ if (check_db_dir_existence(view->db))
+ {
+ my_error(ER_BAD_DB_ERROR, MYF(0), view->db);
+ res= TRUE;
+ goto err;
+ }
+
+ if (mode == VIEW_ALTER && fill_defined_view_parts(thd, view))
+ {
+ res= TRUE;
+ goto err;
+ }
+
+ if (lex->limit_rows_examined)
+ {
+ /*
+ LIMIT ROWS EXAMINED is not supported inside views to avoid complicated
+ side-effects and semantics of the clause.
+ */
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "LIMIT ROWS EXAMINED inside views");
+ res= TRUE;
+ goto err;
+ }
+
+ sp_cache_invalidate();
+ if (sp_process_definer(thd))
+ goto err;
+
+ /*
+ check that tables are not temporary and this VIEW do not used in query
+ (it is possible with ALTERing VIEW).
+ open_and_lock_tables can change the value of tables,
+ e.g. it may happen if before the function call tables was equal to 0.
+ */
+ for (tbl= lex->query_tables; tbl; tbl= tbl->next_global)
+ {
+ /* is this table view and the same view which we creates now? */
+ if (tbl->view &&
+ strcmp(tbl->view_db.str, view->db) == 0 &&
+ strcmp(tbl->view_name.str, view->table_name) == 0)
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), tbl->view_db.str, tbl->view_name.str);
+ res= TRUE;
+ goto err;
+ }
+
+ /*
+ tbl->table can be NULL when tbl is a placeholder for a view
+ that is indirectly referenced via a stored function from the
+ view being created. We don't check these indirectly
+ referenced views in CREATE VIEW so they don't have table
+ object.
+ */
+ if (tbl->table)
+ {
+ /* is this table temporary and is not view? */
+ if (tbl->table->s->tmp_table != NO_TMP_TABLE && !tbl->view &&
+ !tbl->schema_table)
+ {
+ my_error(ER_VIEW_SELECT_TMPTABLE, MYF(0), tbl->alias);
+ res= TRUE;
+ goto err;
+ }
+ /*
+ Copy the privileges of the underlying VIEWs which were filled by
+ fill_effective_table_privileges
+ (they were not copied at derived tables processing)
+ */
+ tbl->table->grant.privilege= tbl->grant.privilege;
+ }
+ }
+
+ /* prepare select to resolve all fields */
+ lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VIEW;
+ if (unit->prepare(thd, 0, 0))
+ {
+ /*
+ some errors from prepare are reported to user, if is not then
+ it will be checked after err: label
+ */
+ res= TRUE;
+ goto err;
+ }
+
+ /* view list (list of view fields names) */
+ if (lex->view_list.elements)
+ {
+ List_iterator_fast<Item> it(select_lex->item_list);
+ List_iterator_fast<LEX_STRING> nm(lex->view_list);
+ Item *item;
+ LEX_STRING *name;
+
+ if (lex->view_list.elements != select_lex->item_list.elements)
+ {
+ my_message(ER_VIEW_WRONG_LIST, ER(ER_VIEW_WRONG_LIST), MYF(0));
+ res= TRUE;
+ goto err;
+ }
+ while ((item= it++, name= nm++))
+ {
+ item->set_name(name->str, (uint) name->length, system_charset_info);
+ item->is_autogenerated_name= FALSE;
+ }
+ }
+
+ /* Check if the auto generated column names are conforming. */
+ for (sl= select_lex; sl; sl= sl->next_select())
+ make_valid_column_names(sl->item_list);
+
+ if (check_duplicate_names(select_lex->item_list, 1))
+ {
+ res= TRUE;
+ goto err;
+ }
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ Compare/check grants on view with grants of underlying tables
+ */
+
+ fill_effective_table_privileges(thd, &view->grant, view->db,
+ view->table_name);
+
+ /*
+ Make sure that the current user does not have more column-level privileges
+ on the newly created view than he/she does on the underlying
+ tables. E.g. it must not be so that the user has UPDATE privileges on a
+ view column of he/she doesn't have it on the underlying table's
+ corresponding column. In that case, return an error for CREATE VIEW.
+ */
+ {
+ Item *report_item= NULL;
+ /*
+ This will hold the intersection of the priviliges on all columns in the
+ view.
+ */
+ uint final_priv= VIEW_ANY_ACL;
+
+ for (sl= select_lex; sl; sl= sl->next_select())
+ {
+ DBUG_ASSERT(view->db); /* Must be set in the parser */
+ List_iterator_fast<Item> it(sl->item_list);
+ Item *item;
+ while ((item= it++))
+ {
+ Item_field *fld= item->field_for_view_update();
+ uint priv= (get_column_grant(thd, &view->grant, view->db,
+ view->table_name, item->name) &
+ VIEW_ANY_ACL);
+
+ if (fld && !fld->field->table->s->tmp_table)
+ {
+
+ final_priv&= fld->have_privileges;
+
+ if (~fld->have_privileges & priv)
+ report_item= item;
+ }
+ }
+ }
+
+ if (!final_priv && report_item)
+ {
+ my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
+ "create view", thd->security_ctx->priv_user,
+ thd->security_ctx->priv_host, report_item->name,
+ view->table_name);
+ res= TRUE;
+ goto err;
+ }
+ }
+#endif
+
+ res= mysql_register_view(thd, view, mode);
+
+ /*
+ View TABLE_SHARE must be removed from the table definition cache in order to
+ make ALTER VIEW work properly. Otherwise, we would not be able to detect
+ meta-data changes after ALTER VIEW.
+ */
+
+ if (!res)
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, view->db, view->table_name, false);
+
+ if (!res && mysql_bin_log.is_open())
+ {
+ String buff;
+ const LEX_STRING command[3]=
+ {{ C_STRING_WITH_LEN("CREATE ") },
+ { C_STRING_WITH_LEN("ALTER ") },
+ { C_STRING_WITH_LEN("CREATE OR REPLACE ") }};
+
+ buff.append(command[thd->lex->create_view_mode].str,
+ command[thd->lex->create_view_mode].length);
+ view_store_options(thd, views, &buff);
+ buff.append(STRING_WITH_LEN("VIEW "));
+ /* Test if user supplied a db (ie: we did not use thd->db) */
+ if (views->db && views->db[0] &&
+ (thd->db == NULL || strcmp(views->db, thd->db)))
+ {
+ append_identifier(thd, &buff, views->db,
+ views->db_length);
+ buff.append('.');
+ }
+ append_identifier(thd, &buff, views->table_name,
+ views->table_name_length);
+ if (lex->view_list.elements)
+ {
+ List_iterator_fast<LEX_STRING> names(lex->view_list);
+ LEX_STRING *name;
+ int i;
+
+ for (i= 0; (name= names++); i++)
+ {
+ buff.append(i ? ", " : "(");
+ append_identifier(thd, &buff, name->str, name->length);
+ }
+ buff.append(')');
+ }
+ buff.append(STRING_WITH_LEN(" AS "));
+ buff.append(views->source.str, views->source.length);
+
+ int errcode= query_error_code(thd, TRUE);
+ if (thd->binlog_query(THD::STMT_QUERY_TYPE,
+ buff.ptr(), buff.length(), FALSE, FALSE, FALSE, errcode))
+ res= TRUE;
+ }
+
+ if (mode != VIEW_CREATE_NEW)
+ query_cache_invalidate3(thd, view, 0);
+ if (res)
+ goto err;
+
+ my_ok(thd);
+ lex->link_first_table_back(view, link_to_local);
+ DBUG_RETURN(0);
+
+err:
+ THD_STAGE_INFO(thd, stage_end);
+ lex->link_first_table_back(view, link_to_local);
+ unit->cleanup();
+ DBUG_RETURN(res || thd->is_error());
+}
+
+
+static void make_view_filename(LEX_STRING *dir, char *dir_buff,
+ size_t dir_buff_len,
+ LEX_STRING *path, char *path_buff,
+ size_t path_buff_len,
+ LEX_STRING *file,
+ TABLE_LIST *view)
+{
+ /* print file name */
+ dir->length= build_table_filename(dir_buff, dir_buff_len - 1,
+ view->db, "", "", 0);
+ dir->str= dir_buff;
+
+ path->length= build_table_filename(path_buff, path_buff_len - 1,
+ view->db, view->table_name, reg_ext, 0);
+ path->str= path_buff;
+
+ file->str= path->str + dir->length;
+ file->length= path->length - dir->length;
+}
+
+/* number of required parameters for making view */
+static const int required_view_parameters= 15;
+
+/*
+ table of VIEW .frm field descriptors
+
+ Note that one should NOT change the order for this, as it's used by
+ parse()
+*/
+static File_option view_parameters[]=
+{{{ C_STRING_WITH_LEN("query")},
+ my_offsetof(TABLE_LIST, select_stmt),
+ FILE_OPTIONS_ESTRING},
+ {{ C_STRING_WITH_LEN("md5")},
+ my_offsetof(TABLE_LIST, md5),
+ FILE_OPTIONS_STRING},
+ {{ C_STRING_WITH_LEN("updatable")},
+ my_offsetof(TABLE_LIST, updatable_view),
+ FILE_OPTIONS_ULONGLONG},
+ {{ C_STRING_WITH_LEN("algorithm")},
+ my_offsetof(TABLE_LIST, algorithm),
+ FILE_OPTIONS_VIEW_ALGO},
+ {{ C_STRING_WITH_LEN("definer_user")},
+ my_offsetof(TABLE_LIST, definer.user),
+ FILE_OPTIONS_STRING},
+ {{ C_STRING_WITH_LEN("definer_host")},
+ my_offsetof(TABLE_LIST, definer.host),
+ FILE_OPTIONS_STRING},
+ {{ C_STRING_WITH_LEN("suid")},
+ my_offsetof(TABLE_LIST, view_suid),
+ FILE_OPTIONS_ULONGLONG},
+ {{ C_STRING_WITH_LEN("with_check_option")},
+ my_offsetof(TABLE_LIST, with_check),
+ FILE_OPTIONS_ULONGLONG},
+ {{ C_STRING_WITH_LEN("timestamp")},
+ my_offsetof(TABLE_LIST, timestamp),
+ FILE_OPTIONS_TIMESTAMP},
+ {{ C_STRING_WITH_LEN("create-version")},
+ my_offsetof(TABLE_LIST, file_version),
+ FILE_OPTIONS_ULONGLONG},
+ {{ C_STRING_WITH_LEN("source")},
+ my_offsetof(TABLE_LIST, source),
+ FILE_OPTIONS_ESTRING},
+ {{(char*) STRING_WITH_LEN("client_cs_name")},
+ my_offsetof(TABLE_LIST, view_client_cs_name),
+ FILE_OPTIONS_STRING},
+ {{(char*) STRING_WITH_LEN("connection_cl_name")},
+ my_offsetof(TABLE_LIST, view_connection_cl_name),
+ FILE_OPTIONS_STRING},
+ {{(char*) STRING_WITH_LEN("view_body_utf8")},
+ my_offsetof(TABLE_LIST, view_body_utf8),
+ FILE_OPTIONS_ESTRING},
+ {{ C_STRING_WITH_LEN("mariadb-version")},
+ my_offsetof(TABLE_LIST, mariadb_version),
+ FILE_OPTIONS_ULONGLONG},
+ {{NullS, 0}, 0,
+ FILE_OPTIONS_STRING}
+};
+
+static LEX_STRING view_file_type[]= {{(char*) STRING_WITH_LEN("VIEW") }};
+
+
+int mariadb_fix_view(THD *thd, TABLE_LIST *view, bool wrong_checksum,
+ bool swap_alg)
+{
+ char dir_buff[FN_REFLEN + 1], path_buff[FN_REFLEN + 1];
+ LEX_STRING dir, file, path;
+ DBUG_ENTER("mariadb_fix_view");
+
+ if (!wrong_checksum && view->mariadb_version)
+ DBUG_RETURN(HA_ADMIN_OK);
+
+ make_view_filename(&dir, dir_buff, sizeof(dir_buff),
+ &path, path_buff, sizeof(path_buff),
+ &file, view);
+ /* init timestamp */
+ if (!view->timestamp.str)
+ view->timestamp.str= view->timestamp_buffer;
+
+ if (swap_alg && view->algorithm != VIEW_ALGORITHM_UNDEFINED)
+ {
+ DBUG_ASSERT(view->algorithm == VIEW_ALGORITHM_MERGE ||
+ view->algorithm == VIEW_ALGORITHM_TMPTABLE);
+ if (view->algorithm == VIEW_ALGORITHM_MERGE)
+ view->algorithm= VIEW_ALGORITHM_TMPTABLE;
+ else
+ view->algorithm= VIEW_ALGORITHM_MERGE;
+ }
+ else
+ swap_alg= 0;
+ if (wrong_checksum)
+ {
+ if (view->md5.length != 32)
+ {
+ if ((view->md5.str= (char *)thd->alloc(32 + 1)) == NULL)
+ DBUG_RETURN(HA_ADMIN_FAILED);
+ }
+ view->calc_md5(view->md5.str);
+ view->md5.length= 32;
+ }
+ view->mariadb_version= MYSQL_VERSION_ID;
+
+ if (sql_create_definition_file(&dir, &file, view_file_type,
+ (uchar*)view, view_parameters))
+ {
+ sql_print_error("View '%-.192s'.'%-.192s': algorithm swap error.",
+ view->db, view->table_name);
+ DBUG_RETURN(HA_ADMIN_INTERNAL_ERROR);
+ }
+ sql_print_information("View %`s.%`s: the version is set to %llu%s%s",
+ view->db, view->table_name, view->mariadb_version,
+ (wrong_checksum ? ", checksum corrected" : ""),
+ (swap_alg ?
+ ((view->algorithm == VIEW_ALGORITHM_MERGE) ?
+ ", algorithm restored to be MERGE"
+ : ", algorithm restored to be TEMPTABLE")
+ : ""));
+
+
+ DBUG_RETURN(HA_ADMIN_OK);
+}
+
+
+/*
+ Register VIEW (write .frm & process .frm's history backups)
+
+ SYNOPSIS
+ mysql_register_view()
+ thd - thread handler
+ view - view description
+ mode - VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
+
+ RETURN
+ 0 OK
+ -1 Error
+ 1 Error and error message given
+*/
+
+static int mysql_register_view(THD *thd, TABLE_LIST *view,
+ enum_view_create_mode mode)
+{
+ LEX *lex= thd->lex;
+
+ /*
+ View definition query -- a SELECT statement that fully defines view. It
+ is generated from the Item-tree built from the original (specified by
+ the user) query. The idea is that generated query should eliminates all
+ ambiguities and fix view structure at CREATE-time (once for all).
+ Item::print() virtual operation is used to generate view definition
+ query.
+
+ INFORMATION_SCHEMA query (IS query) -- a SQL statement describing a
+ view that is shown in INFORMATION_SCHEMA. Basically, it is 'view
+ definition query' with text literals converted to UTF8 and without
+ character set introducers.
+
+ For example:
+ Let's suppose we have:
+ CREATE TABLE t1(a INT, b INT);
+ User specified query:
+ CREATE VIEW v1(x, y) AS SELECT * FROM t1;
+ Generated query:
+ SELECT a AS x, b AS y FROM t1;
+ IS query:
+ SELECT a AS x, b AS y FROM t1;
+
+ View definition query is stored in the client character set.
+ */
+ char view_query_buff[4096];
+ String view_query(view_query_buff,
+ sizeof (view_query_buff),
+ thd->charset());
+
+ char is_query_buff[4096];
+ String is_query(is_query_buff,
+ sizeof (is_query_buff),
+ system_charset_info);
+
+ char md5[MD5_BUFF_LENGTH];
+ bool can_be_merged;
+ char dir_buff[FN_REFLEN + 1], path_buff[FN_REFLEN + 1];
+ LEX_STRING dir, file, path;
+ int error= 0;
+ DBUG_ENTER("mysql_register_view");
+
+ /* Generate view definition and IS queries. */
+ view_query.length(0);
+ is_query.length(0);
+ {
+ ulong sql_mode= thd->variables.sql_mode & MODE_ANSI_QUOTES;
+ thd->variables.sql_mode&= ~MODE_ANSI_QUOTES;
+
+ lex->unit.print(&view_query, QT_VIEW_INTERNAL);
+ lex->unit.print(&is_query,
+ enum_query_type(QT_TO_SYSTEM_CHARSET | QT_WITHOUT_INTRODUCERS));
+
+ thd->variables.sql_mode|= sql_mode;
+ }
+ DBUG_PRINT("info", ("View: %.*s", view_query.length(), view_query.ptr()));
+
+ /* fill structure */
+ view->source= thd->lex->create_view_select;
+
+ if (!thd->make_lex_string(&view->select_stmt, view_query.ptr(),
+ view_query.length()))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ error= -1;
+ goto err;
+ }
+
+ /*
+ version 1 - before 10.0.5
+ version 2 - empty definer_host means a role
+ */
+ view->file_version= 2;
+ view->mariadb_version= MYSQL_VERSION_ID;
+ view->calc_md5(md5);
+ if (!(view->md5.str= (char*) thd->memdup(md5, 32)))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ error= -1;
+ goto err;
+ }
+ view->md5.length= 32;
+ can_be_merged= lex->can_be_merged();
+ if (lex->create_view_algorithm == VIEW_ALGORITHM_MERGE &&
+ !lex->can_be_merged())
+ {
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_VIEW_MERGE,
+ ER(ER_WARN_VIEW_MERGE));
+ lex->create_view_algorithm= DTYPE_ALGORITHM_UNDEFINED;
+ }
+ view->algorithm= lex->create_view_algorithm;
+ view->definer.user= lex->definer->user;
+ view->definer.host= lex->definer->host;
+ view->view_suid= lex->create_view_suid;
+ view->with_check= lex->create_view_check;
+ if ((view->updatable_view= (can_be_merged &&
+ view->algorithm != VIEW_ALGORITHM_TMPTABLE)))
+ {
+ /* TODO: change here when we will support UNIONs */
+ for (TABLE_LIST *tbl= lex->select_lex.table_list.first;
+ tbl;
+ tbl= tbl->next_local)
+ {
+ if ((tbl->view && !tbl->updatable_view) || tbl->schema_table)
+ {
+ view->updatable_view= 0;
+ break;
+ }
+ for (TABLE_LIST *up= tbl; up; up= up->embedding)
+ {
+ if (up->outer_join)
+ {
+ view->updatable_view= 0;
+ goto loop_out;
+ }
+ }
+ }
+ }
+loop_out:
+ /* print file name */
+ make_view_filename(&dir, dir_buff, sizeof(dir_buff),
+ &path, path_buff, sizeof(path_buff),
+ &file, view);
+ /* init timestamp */
+ if (!view->timestamp.str)
+ view->timestamp.str= view->timestamp_buffer;
+
+ /* check old .frm */
+ {
+ char path_buff[FN_REFLEN];
+ LEX_STRING path;
+ File_parser *parser;
+
+ path.str= path_buff;
+ fn_format(path_buff, file.str, dir.str, "", MY_UNPACK_FILENAME);
+ path.length= strlen(path_buff);
+
+ if (ha_table_exists(thd, view->db, view->table_name, NULL))
+ {
+ if (mode == VIEW_CREATE_NEW)
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), view->alias);
+ error= -1;
+ goto err;
+ }
+
+ if (!(parser= sql_parse_prepare(&path, thd->mem_root, 0)))
+ {
+ error= 1;
+ goto err;
+ }
+
+ if (!parser->ok() || !is_equal(&view_type, parser->type()))
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW");
+ error= -1;
+ goto err;
+ }
+
+ /*
+ TODO: read dependence list, too, to process cascade/restrict
+ TODO: special cascade/restrict procedure for alter?
+ */
+ }
+ else
+ {
+ if (mode == VIEW_ALTER)
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), view->db, view->alias);
+ error= -1;
+ goto err;
+ }
+ }
+ }
+
+ /* Initialize view creation context from the environment. */
+
+ view->view_creation_ctx= View_creation_ctx::create(thd);
+
+ /*
+ Set LEX_STRING attributes in view-structure for parser to create
+ frm-file.
+ */
+
+ lex_string_set(&view->view_client_cs_name,
+ view->view_creation_ctx->get_client_cs()->csname);
+
+ lex_string_set(&view->view_connection_cl_name,
+ view->view_creation_ctx->get_connection_cl()->name);
+
+ if (!thd->make_lex_string(&view->view_body_utf8, is_query.ptr(),
+ is_query.length()))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ error= -1;
+ goto err;
+ }
+
+ /*
+ Check that table of main select do not used in subqueries.
+
+ This test can catch only very simple cases of such non-updateable views,
+ all other will be detected before updating commands execution.
+ (it is more optimisation then real check)
+
+ NOTE: this skip cases of using table via VIEWs, joined VIEWs, VIEWs with
+ UNION
+ */
+ if (view->updatable_view &&
+ !lex->select_lex.master_unit()->is_union() &&
+ !(lex->select_lex.table_list.first)->next_local &&
+ find_table_in_global_list(lex->query_tables->next_global,
+ lex->query_tables->db,
+ lex->query_tables->table_name))
+ {
+ view->updatable_view= 0;
+ }
+
+ if (view->with_check != VIEW_CHECK_NONE &&
+ !view->updatable_view)
+ {
+ my_error(ER_VIEW_NONUPD_CHECK, MYF(0), view->db, view->table_name);
+ error= -1;
+ goto err;
+ }
+
+ if (sql_create_definition_file(&dir, &file, view_file_type,
+ (uchar*)view, view_parameters))
+ {
+ error= thd->is_error() ? -1 : 1;
+ goto err;
+ }
+ DBUG_RETURN(0);
+err:
+ view->select_stmt.str= NULL;
+ view->select_stmt.length= 0;
+ view->md5.str= NULL;
+ view->md5.length= 0;
+ DBUG_RETURN(error);
+}
+
+
+
+/*
+ read VIEW .frm and create structures
+
+ SYNOPSIS
+ mysql_make_view()
+ thd Thread handle
+ parser parser object
+ table TABLE_LIST structure for filling
+ flags flags
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table,
+ uint flags)
+{
+ SELECT_LEX *end, *UNINIT_VAR(view_select);
+ LEX *old_lex, *lex;
+ Query_arena *arena, backup;
+ TABLE_LIST *top_view= table->top_table();
+ bool UNINIT_VAR(parse_status);
+ bool result, view_is_mergeable;
+ TABLE_LIST *UNINIT_VAR(view_main_select_tables);
+ DBUG_ENTER("mysql_make_view");
+ DBUG_PRINT("info", ("table: 0x%lx (%s)", (ulong) table, table->table_name));
+
+ if (table->view)
+ {
+ /*
+ It's an execution of a PS/SP and the view has already been unfolded
+ into a list of used tables. Now we only need to update the information
+ about granted privileges in the view tables with the actual data
+ stored in MySQL privilege system. We don't need to restore the
+ required privileges (by calling register_want_access) because they has
+ not changed since PREPARE or the previous execution: the only case
+ when this information is changed is execution of UPDATE on a view, but
+ the original want_access is restored in its end.
+ */
+ if (!table->prelocking_placeholder && table->prepare_security(thd))
+ {
+ DBUG_RETURN(1);
+ }
+ DBUG_PRINT("info",
+ ("VIEW %s.%s is already processed on previous PS/SP execution",
+ table->view_db.str, table->view_name.str));
+
+ /*
+ Clear old variables in the TABLE_LIST that could be left from an old view
+ This is only needed if there was an error at last usage of view,
+ in which case the reinit call wasn't done.
+ See MDEV-6668 for details.
+ */
+ mysql_derived_reinit(thd, NULL, table);
+
+ DBUG_RETURN(0);
+ }
+
+ if (table->index_hints && table->index_hints->elements)
+ {
+ my_error(ER_KEY_DOES_NOT_EXITS, MYF(0),
+ table->index_hints->head()->key_name.str, table->table_name);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* check loop via view definition */
+ for (TABLE_LIST *precedent= table->referencing_view;
+ precedent;
+ precedent= precedent->referencing_view)
+ {
+ if (precedent->view_name.length == table->table_name_length &&
+ precedent->view_db.length == table->db_length &&
+ my_strcasecmp(system_charset_info,
+ precedent->view_name.str, table->table_name) == 0 &&
+ my_strcasecmp(system_charset_info,
+ precedent->view_db.str, table->db) == 0)
+ {
+ my_error(ER_VIEW_RECURSIVE, MYF(0),
+ top_view->view_db.str, top_view->view_name.str);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ /*
+ For now we assume that tables will not be changed during PS life (it
+ will be TRUE as far as we make new table cache).
+ */
+ old_lex= thd->lex;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ /* init timestamp */
+ if (!table->timestamp.str)
+ table->timestamp.str= table->timestamp_buffer;
+ /* prepare default values for old format */
+ table->view_suid= TRUE;
+ table->definer.user.str= table->definer.host.str= 0;
+ table->definer.user.length= table->definer.host.length= 0;
+
+ /*
+ TODO: when VIEWs will be stored in cache, table mem_root should
+ be used here
+ */
+ if ((result= parser->parse((uchar*)table, thd->mem_root,
+ view_parameters, required_view_parameters,
+ &file_parser_dummy_hook)))
+ goto end;
+
+ /*
+ check old format view .frm
+ */
+ if (!table->definer.user.str)
+ {
+ DBUG_ASSERT(!table->definer.host.str &&
+ !table->definer.user.length &&
+ !table->definer.host.length);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_VIEW_FRM_NO_USER, ER(ER_VIEW_FRM_NO_USER),
+ table->db, table->table_name);
+ get_default_definer(thd, &table->definer, false);
+ }
+
+ /*
+ since 10.0.5 definer.host can never be "" for a User, but it's
+ always "" for a Role. Before 10.0.5 it could be "" for a User,
+ but roles didn't exist. file_version helps.
+ */
+ if (!table->definer.host.str[0] && table->file_version < 2)
+ table->definer.host= host_not_specified; // User, not Role
+
+ /*
+ Initialize view definition context by character set names loaded from
+ the view definition file. Use UTF8 character set if view definition
+ file is of old version and does not contain the character set names.
+ */
+ table->view_creation_ctx= View_creation_ctx::create(thd, table);
+
+ if (flags & OPEN_VIEW_NO_PARSE)
+ {
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ Save VIEW parameters, which will be wiped out by derived table
+ processing
+ */
+ table->view_db.str= table->db;
+ table->view_db.length= table->db_length;
+ table->view_name.str= table->table_name;
+ table->view_name.length= table->table_name_length;
+ /*
+ We don't invalidate a prepared statement when a view changes,
+ or when someone creates a temporary table.
+ Instead, the view is inlined into the body of the statement
+ upon the first execution. Below, make sure that on
+ re-execution of a prepared statement we don't prefer
+ a temporary table to the view, if the view name was shadowed
+ with a temporary table with the same name.
+ This assignment ensures that on re-execution open_table() will
+ not try to call find_temporary_table() for this TABLE_LIST,
+ but will invoke open_table_from_share(), which will
+ eventually call this function.
+ */
+ table->open_type= OT_BASE_ONLY;
+
+ /*TODO: md5 test here and warning if it is differ */
+
+
+ /*
+ TODO: TABLE mem root should be used here when VIEW will be stored in
+ TABLE cache
+
+ now Lex placed in statement memory
+ */
+ table->view= lex= thd->lex= (LEX*) new(thd->mem_root) st_lex_local;
+ if (!table->view)
+ {
+ result= true;
+ goto end;
+ }
+
+ {
+ char old_db_buf[SAFE_NAME_LEN+1];
+ LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
+ bool dbchanged;
+ Parser_state parser_state;
+ if (parser_state.init(thd, table->select_stmt.str,
+ table->select_stmt.length))
+ goto err;
+
+ /*
+ Use view db name as thread default database, in order to ensure
+ that the view is parsed and prepared correctly.
+ */
+ if ((result= mysql_opt_change_db(thd, &table->view_db, &old_db, 1,
+ &dbchanged)))
+ goto end;
+
+ lex_start(thd);
+ view_select= &lex->select_lex;
+ view_select->select_number= ++thd->select_number;
+
+ ulonglong saved_mode= thd->variables.sql_mode;
+ /* switch off modes which can prevent normal parsing of VIEW
+ - MODE_REAL_AS_FLOAT affect only CREATE TABLE parsing
+ + MODE_PIPES_AS_CONCAT affect expression parsing
+ + MODE_ANSI_QUOTES affect expression parsing
+ + MODE_IGNORE_SPACE affect expression parsing
+ - MODE_IGNORE_BAD_TABLE_OPTIONS affect only CREATE/ALTER TABLE parsing
+ * MODE_ONLY_FULL_GROUP_BY affect execution
+ * MODE_NO_UNSIGNED_SUBTRACTION affect execution
+ - MODE_NO_DIR_IN_CREATE affect table creation only
+ - MODE_POSTGRESQL compounded from other modes
+ - MODE_ORACLE compounded from other modes
+ - MODE_MSSQL compounded from other modes
+ - MODE_DB2 compounded from other modes
+ - MODE_MAXDB affect only CREATE TABLE parsing
+ - MODE_NO_KEY_OPTIONS affect only SHOW
+ - MODE_NO_TABLE_OPTIONS affect only SHOW
+ - MODE_NO_FIELD_OPTIONS affect only SHOW
+ - MODE_MYSQL323 affect only SHOW
+ - MODE_MYSQL40 affect only SHOW
+ - MODE_ANSI compounded from other modes
+ (+ transaction mode)
+ ? MODE_NO_AUTO_VALUE_ON_ZERO affect UPDATEs
+ + MODE_NO_BACKSLASH_ESCAPES affect expression parsing
+ */
+ thd->variables.sql_mode&= ~(MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
+ MODE_IGNORE_SPACE | MODE_NO_BACKSLASH_ESCAPES);
+
+ /* Parse the query. */
+
+ parse_status= parse_sql(thd, & parser_state, table->view_creation_ctx);
+
+ /* Restore environment. */
+
+ if ((old_lex->sql_command == SQLCOM_SHOW_FIELDS) ||
+ (old_lex->sql_command == SQLCOM_SHOW_CREATE))
+ lex->sql_command= old_lex->sql_command;
+
+ thd->variables.sql_mode= saved_mode;
+
+ if (dbchanged && mysql_change_db(thd, &old_db, TRUE))
+ goto err;
+ }
+ if (!parse_status)
+ {
+ TABLE_LIST *view_tables= lex->query_tables;
+ TABLE_LIST *view_tables_tail= 0;
+ TABLE_LIST *tbl;
+ Security_context *security_ctx= 0;
+
+ /*
+ Check rights to run commands (EXPLAIN SELECT & SHOW CREATE) which show
+ underlying tables.
+ Skip this step if we are opening view for prelocking only.
+ */
+ if (!table->prelocking_placeholder && (old_lex->describe))
+ {
+ /*
+ The user we run EXPLAIN as (either the connected user who issued
+ the EXPLAIN statement, or the definer of a SUID stored routine
+ which contains the EXPLAIN) should have both SHOW_VIEW_ACL and
+ SELECT_ACL on the view being opened as well as on all underlying
+ views since EXPLAIN will disclose their structure. This user also
+ should have SELECT_ACL on all underlying tables of the view since
+ this EXPLAIN will disclose information about the number of rows in it.
+
+ To perform this privilege check we create auxiliary TABLE_LIST object
+ for the view in order a) to avoid trashing "table->grant" member for
+ original table list element, which contents can be important at later
+ stage for column-level privilege checking b) get TABLE_LIST object
+ with "security_ctx" member set to 0, i.e. forcing check_table_access()
+ to use active user's security context.
+
+ There is no need for creating similar copies of TABLE_LIST elements
+ for underlying tables since they just have been constructed and thus
+ have TABLE_LIST::security_ctx == 0 and fresh TABLE_LIST::grant member.
+
+ Finally at this point making sure we have SHOW_VIEW_ACL on the views
+ will suffice as we implicitly require SELECT_ACL anyway.
+ */
+
+ TABLE_LIST view_no_suid;
+ bzero(static_cast<void *>(&view_no_suid), sizeof(TABLE_LIST));
+ view_no_suid.db= table->db;
+ view_no_suid.table_name= table->table_name;
+
+ DBUG_ASSERT(view_tables == NULL || view_tables->security_ctx == NULL);
+
+ if (check_table_access(thd, SELECT_ACL, view_tables,
+ FALSE, UINT_MAX, TRUE) ||
+ check_table_access(thd, SHOW_VIEW_ACL, &view_no_suid,
+ FALSE, UINT_MAX, TRUE))
+ {
+ my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0));
+ goto err;
+ }
+ }
+ else if (!table->prelocking_placeholder &&
+ (old_lex->sql_command == SQLCOM_SHOW_CREATE) &&
+ !table->belong_to_view)
+ {
+ if (check_table_access(thd, SHOW_VIEW_ACL, table, FALSE, UINT_MAX, FALSE))
+ goto err;
+ }
+
+ if (!(table->view_tables=
+ (List<TABLE_LIST>*) new(thd->mem_root) List<TABLE_LIST>))
+ goto err;
+ /*
+ mark to avoid temporary table using and put view reference and find
+ last view table
+ */
+ for (tbl= view_tables;
+ tbl;
+ tbl= (view_tables_tail= tbl)->next_global)
+ {
+ tbl->open_type= OT_BASE_ONLY;
+ tbl->belong_to_view= top_view;
+ tbl->referencing_view= table;
+ tbl->prelocking_placeholder= table->prelocking_placeholder;
+ /*
+ First we fill want_privilege with SELECT_ACL (this is needed for the
+ tables which belongs to view subqueries and temporary table views,
+ then for the merged view underlying tables we will set wanted
+ privileges of top_view
+ */
+ tbl->grant.want_privilege= SELECT_ACL;
+ /*
+ After unfolding the view we lose the list of tables referenced in it
+ (we will have only a list of underlying tables in case of MERGE
+ algorithm, which does not include the tables referenced from
+ subqueries used in view definition).
+ Let's build a list of all tables referenced in the view.
+ */
+ table->view_tables->push_back(tbl);
+ }
+
+ /*
+ Put tables of VIEW after VIEW TABLE_LIST
+
+ NOTE: It is important for UPDATE/INSERT/DELETE checks to have this
+ tables just after VIEW instead of tail of list, to be able check that
+ table is unique. Also we store old next table for the same purpose.
+ */
+ if (view_tables)
+ {
+ if (table->next_global)
+ {
+ view_tables_tail->next_global= table->next_global;
+ table->next_global->prev_global= &view_tables_tail->next_global;
+ }
+ else
+ {
+ old_lex->query_tables_last= &view_tables_tail->next_global;
+ }
+ view_tables->prev_global= &table->next_global;
+ table->next_global= view_tables;
+ }
+
+ /*
+ If the view's body needs row-based binlogging (e.g. the VIEW is created
+ from SELECT UUID()), the top statement also needs it.
+ */
+ old_lex->set_stmt_unsafe_flags(lex->get_stmt_unsafe_flags());
+
+ view_is_mergeable= (table->algorithm != VIEW_ALGORITHM_TMPTABLE &&
+ lex->can_be_merged());
+
+ if (view_is_mergeable)
+ {
+ /*
+ Currently 'view_main_select_tables' differs from 'view_tables'
+ only then view has CONVERT_TZ() function in its select list.
+ This may change in future, for example if we enable merging of
+ views with subqueries in select list.
+ */
+ view_main_select_tables= lex->select_lex.table_list.first;
+
+ /*
+ Let us set proper lock type for tables of the view's main
+ select since we may want to perform update or insert on
+ view. This won't work for view containing union. But this is
+ ok since we don't allow insert and update on such views
+ anyway.
+ */
+ for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local)
+ {
+ tbl->lock_type= table->lock_type;
+ tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
+ }
+ /*
+ If the view is mergeable, we might want to
+ INSERT/UPDATE/DELETE into tables of this view. Preserve the
+ original sql command and 'duplicates' of the outer lex.
+ This is used later in set_trg_event_type_for_command.
+ */
+ lex->sql_command= old_lex->sql_command;
+ lex->duplicates= old_lex->duplicates;
+ }
+ /*
+ This method has a dependency on the proper lock type being set,
+ so in case of views should be called here.
+ */
+ lex->set_trg_event_type_for_tables();
+
+ /*
+ If we are opening this view as part of implicit LOCK TABLES, then
+ this view serves as simple placeholder and we should not continue
+ further processing.
+ */
+ if (table->prelocking_placeholder)
+ goto ok2;
+
+ old_lex->derived_tables|= (DERIVED_VIEW | lex->derived_tables);
+
+ /* move SQL_NO_CACHE & Co to whole query */
+ old_lex->safe_to_cache_query= (old_lex->safe_to_cache_query &&
+ lex->safe_to_cache_query);
+ /* move SQL_CACHE to whole query */
+ if (view_select->options & OPTION_TO_QUERY_CACHE)
+ old_lex->select_lex.options|= OPTION_TO_QUERY_CACHE;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (table->view_suid)
+ {
+ /*
+ For suid views prepare a security context for checking underlying
+ objects of the view.
+ */
+ if (!(table->view_sctx= (Security_context *)
+ thd->stmt_arena->calloc(sizeof(Security_context))))
+ goto err;
+ security_ctx= table->view_sctx;
+ }
+ else
+ {
+ /*
+ For non-suid views inherit security context from view's table list.
+ This allows properly handle situation when non-suid view is used
+ from within suid view.
+ */
+ security_ctx= table->security_ctx;
+ }
+#endif
+
+ /* Assign the context to the tables referenced in the view */
+ if (view_tables)
+ {
+ DBUG_ASSERT(view_tables_tail);
+ for (tbl= view_tables; tbl != view_tables_tail->next_global;
+ tbl= tbl->next_global)
+ tbl->security_ctx= security_ctx;
+ }
+
+ /* assign security context to SELECT name resolution contexts of view */
+ for(SELECT_LEX *sl= lex->all_selects_list;
+ sl;
+ sl= sl->next_select_in_list())
+ sl->context.security_ctx= security_ctx;
+
+ /*
+ Setup an error processor to hide error messages issued by stored
+ routines referenced in the view
+ */
+ for (SELECT_LEX *sl= lex->all_selects_list;
+ sl;
+ sl= sl->next_select_in_list())
+ {
+ sl->context.error_processor= &view_error_processor;
+ sl->context.error_processor_data= (void *)table;
+ }
+
+ /*
+ check MERGE algorithm ability
+ - algorithm is not explicit TEMPORARY TABLE
+ - VIEW SELECT allow merging
+ - VIEW used in subquery or command support MERGE algorithm
+ */
+ if (view_is_mergeable &&
+ (table->select_lex->master_unit() != &old_lex->unit ||
+ old_lex->can_use_merged()) &&
+ !old_lex->can_not_use_merged())
+ {
+ /* lex should contain at least one table */
+ DBUG_ASSERT(view_main_select_tables != 0);
+
+ List_iterator_fast<TABLE_LIST> ti(view_select->top_join_list);
+
+ table->derived_type= VIEW_ALGORITHM_MERGE;
+ DBUG_PRINT("info", ("algorithm: MERGE"));
+ table->updatable= (table->updatable_view != 0);
+ table->effective_with_check=
+ old_lex->get_effective_with_check(table);
+ table->merge_underlying_list= view_main_select_tables;
+
+ /* Fill correct wanted privileges. */
+ for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local)
+ tbl->grant.want_privilege= top_view->grant.orig_want_privilege;
+
+ /* prepare view context */
+ lex->select_lex.context.resolve_in_table_list_only(view_main_select_tables);
+ lex->select_lex.context.outer_context= 0;
+ lex->select_lex.select_n_having_items+=
+ table->select_lex->select_n_having_items;
+
+ table->where= view_select->where;
+
+ /*
+ We can safely ignore the VIEW's ORDER BY if we merge into union
+ branch, as order is not important there.
+ */
+ if (!table->select_lex->master_unit()->is_union() &&
+ table->select_lex->order_list.elements == 0)
+ table->select_lex->order_list.push_back(&lex->select_lex.order_list);
+ else
+ {
+ if (old_lex->sql_command == SQLCOM_SELECT &&
+ (old_lex->describe & DESCRIBE_EXTENDED) &&
+ lex->select_lex.order_list.elements &&
+ !table->select_lex->master_unit()->is_union())
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_VIEW_ORDERBY_IGNORED,
+ ER(ER_VIEW_ORDERBY_IGNORED),
+ table->db, table->table_name);
+ }
+ }
+ /*
+ This SELECT_LEX will be linked in global SELECT_LEX list
+ to make it processed by mysql_handle_derived(),
+ but it will not be included to SELECT_LEX tree, because it
+ will not be executed
+ */
+ goto ok;
+ }
+
+ table->derived_type= VIEW_ALGORITHM_TMPTABLE;
+ DBUG_PRINT("info", ("algorithm: TEMPORARY TABLE"));
+ view_select->linkage= DERIVED_TABLE_TYPE;
+ table->updatable= 0;
+ table->effective_with_check= VIEW_CHECK_NONE;
+ old_lex->subqueries= TRUE;
+
+ table->derived= &lex->unit;
+ }
+ else
+ goto err;
+
+ok:
+ /* SELECT tree link */
+ lex->unit.include_down(table->select_lex);
+ lex->unit.slave= view_select; // fix include_down initialisation
+ /* global SELECT list linking */
+ end= view_select; // primary SELECT_LEX is always last
+ end->link_next= old_lex->all_selects_list;
+ old_lex->all_selects_list->link_prev= &end->link_next;
+ old_lex->all_selects_list= lex->all_selects_list;
+ lex->all_selects_list->link_prev=
+ (st_select_lex_node**)&old_lex->all_selects_list;
+
+ok2:
+ DBUG_ASSERT(lex == thd->lex);
+ thd->lex= old_lex; // Needed for prepare_security
+ result= !table->prelocking_placeholder && table->prepare_security(thd);
+
+ lex_end(lex);
+end:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ thd->lex= old_lex;
+ DBUG_RETURN(result);
+
+err:
+ DBUG_ASSERT(thd->lex == table->view);
+ lex_end(thd->lex);
+ delete table->view;
+ table->view= 0; // now it is not VIEW placeholder
+ result= 1;
+ goto end;
+}
+
+
+/*
+ drop view
+
+ SYNOPSIS
+ mysql_drop_view()
+ thd - thread handle
+ views - views to delete
+ drop_mode - cascade/check
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error
+*/
+
+bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
+{
+ char path[FN_REFLEN + 1];
+ TABLE_LIST *view;
+ String non_existant_views;
+ char *wrong_object_db= NULL, *wrong_object_name= NULL;
+ bool error= FALSE;
+ bool some_views_deleted= FALSE;
+ bool something_wrong= FALSE;
+ DBUG_ENTER("mysql_drop_view");
+
+ /*
+ We can't allow dropping of unlocked view under LOCK TABLES since this
+ might lead to deadlock. But since we can't really lock view with LOCK
+ TABLES we have to simply prohibit dropping of views.
+ */
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (lock_table_names(thd, views, 0, thd->variables.lock_wait_timeout, 0))
+ DBUG_RETURN(TRUE);
+
+ for (view= views; view; view= view->next_local)
+ {
+ bool not_exist;
+ build_table_filename(path, sizeof(path) - 1,
+ view->db, view->table_name, reg_ext, 0);
+
+ if ((not_exist= my_access(path, F_OK)) || !dd_frm_is_view(thd, path))
+ {
+ char name[FN_REFLEN];
+ my_snprintf(name, sizeof(name), "%s.%s", view->db, view->table_name);
+ if (thd->lex->check_exists)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR),
+ name);
+ continue;
+ }
+ if (not_exist)
+ {
+ if (non_existant_views.length())
+ non_existant_views.append(',');
+ non_existant_views.append(name);
+ }
+ else
+ {
+ if (!wrong_object_name)
+ {
+ wrong_object_db= view->db;
+ wrong_object_name= view->table_name;
+ }
+ }
+ continue;
+ }
+ if (mysql_file_delete(key_file_frm, path, MYF(MY_WME)))
+ error= TRUE;
+
+ some_views_deleted= TRUE;
+
+ /*
+ For a view, there is a TABLE_SHARE object.
+ Remove it from the table definition cache, in case the view was cached.
+ */
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, view->db, view->table_name,
+ FALSE);
+ query_cache_invalidate3(thd, view, 0);
+ sp_cache_invalidate();
+ }
+
+ if (wrong_object_name)
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0), wrong_object_db, wrong_object_name,
+ "VIEW");
+ }
+ if (non_existant_views.length())
+ {
+ my_error(ER_BAD_TABLE_ERROR, MYF(0), non_existant_views.c_ptr_safe());
+ }
+
+ something_wrong= error || wrong_object_name || non_existant_views.length();
+ if (some_views_deleted || !something_wrong)
+ {
+ /* if something goes wrong, bin-log with possible error code,
+ otherwise bin-log with error code cleared.
+ */
+ if (write_bin_log(thd, !something_wrong, thd->query(), thd->query_length()))
+ something_wrong= 1;
+ }
+
+ if (something_wrong)
+ {
+ DBUG_RETURN(TRUE);
+ }
+ my_ok(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ check of key (primary or unique) presence in updatable view
+
+ SYNOPSIS
+ check_key_in_view()
+ thd thread handle
+ view view for check with opened table
+
+ DESCRIPTION
+ If it is VIEW and query have LIMIT clause then check that underlying
+ table of view contain one of following:
+ 1) primary key of underlying table
+ 2) unique key underlying table with fields for which NULL value is
+ impossible
+ 3) all fields of underlying table
+
+ RETURN
+ FALSE OK
+ TRUE view do not contain key or all fields
+*/
+
+bool check_key_in_view(THD *thd, TABLE_LIST *view)
+{
+ TABLE *table;
+ Field_translator *trans, *end_of_trans;
+ KEY *key_info, *key_info_end;
+ DBUG_ENTER("check_key_in_view");
+
+ /*
+ we do not support updatable UNIONs in VIEW, so we can check just limit of
+ LEX::select_lex
+ */
+ if ((!view->view && !view->belong_to_view) ||
+ thd->lex->sql_command == SQLCOM_INSERT ||
+ thd->lex->select_lex.select_limit == 0)
+ DBUG_RETURN(FALSE); /* it is normal table or query without LIMIT */
+ table= view->table;
+ view= view->top_table();
+ trans= view->field_translation;
+ key_info_end= (key_info= table->key_info)+ table->s->keys;
+
+ end_of_trans= view->field_translation_end;
+ DBUG_ASSERT(table != 0 && view->field_translation != 0);
+
+ {
+ /*
+ We should be sure that all fields are ready to get keys from them, but
+ this operation should not have influence on Field::query_id, to avoid
+ marking as used fields which are not used
+ */
+ enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
+ thd->mark_used_columns= MARK_COLUMNS_NONE;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+ for (Field_translator *fld= trans; fld < end_of_trans; fld++)
+ {
+ if (!fld->item->fixed && fld->item->fix_fields(thd, &fld->item))
+ {
+ thd->mark_used_columns= save_mark_used_columns;
+ DBUG_RETURN(TRUE);
+ }
+ }
+ thd->mark_used_columns= save_mark_used_columns;
+ DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
+ }
+ /* Loop over all keys to see if a unique-not-null key is used */
+ for (;key_info != key_info_end ; key_info++)
+ {
+ if ((key_info->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME)
+ {
+ KEY_PART_INFO *key_part= key_info->key_part;
+ KEY_PART_INFO *key_part_end= key_part + key_info->user_defined_key_parts;
+
+ /* check that all key parts are used */
+ for (;;)
+ {
+ Field_translator *k;
+ for (k= trans; k < end_of_trans; k++)
+ {
+ Item_field *field;
+ if ((field= k->item->field_for_view_update()) &&
+ field->field == key_part->field)
+ break;
+ }
+ if (k == end_of_trans)
+ break; // Key is not possible
+ if (++key_part == key_part_end)
+ DBUG_RETURN(FALSE); // Found usable key
+ }
+ }
+ }
+
+ DBUG_PRINT("info", ("checking if all fields of table are used"));
+ /* check all fields presence */
+ {
+ Field **field_ptr;
+ Field_translator *fld;
+ for (field_ptr= table->field; *field_ptr; field_ptr++)
+ {
+ for (fld= trans; fld < end_of_trans; fld++)
+ {
+ Item_field *field;
+ if ((field= fld->item->field_for_view_update()) &&
+ field->field == *field_ptr)
+ break;
+ }
+ if (fld == end_of_trans) // If field didn't exists
+ {
+ /*
+ Keys or all fields of underlying tables are not found => we have
+ to check variable updatable_views_with_limit to decide should we
+ issue an error or just a warning
+ */
+ if (thd->variables.updatable_views_with_limit)
+ {
+ /* update allowed, but issue warning */
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_WARN_VIEW_WITHOUT_KEY, ER(ER_WARN_VIEW_WITHOUT_KEY));
+ DBUG_RETURN(FALSE);
+ }
+ /* prohibit update */
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ insert fields from VIEW (MERGE algorithm) into given list
+
+ SYNOPSIS
+ insert_view_fields()
+ thd thread handler
+ list list for insertion
+ view view for processing
+
+ RETURN
+ FALSE OK
+ TRUE error (is not sent to cliet)
+*/
+
+bool insert_view_fields(THD *thd, List<Item> *list, TABLE_LIST *view)
+{
+ Field_translator *trans_end;
+ Field_translator *trans;
+ DBUG_ENTER("insert_view_fields");
+
+ if (!(trans= view->field_translation))
+ DBUG_RETURN(FALSE);
+ trans_end= view->field_translation_end;
+
+ for (Field_translator *entry= trans; entry < trans_end; entry++)
+ {
+ Item_field *fld;
+ if ((fld= entry->item->field_for_view_update()))
+ list->push_back(fld);
+ else
+ {
+ my_error(ER_NON_INSERTABLE_TABLE, MYF(0), view->alias, "INSERT");
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ checking view md5 check suum
+
+ SINOPSYS
+ view_checksum()
+ thd threar handler
+ view view for check
+
+ RETUIRN
+ HA_ADMIN_OK OK
+ HA_ADMIN_NOT_IMPLEMENTED it is not VIEW
+ HA_ADMIN_WRONG_CHECKSUM check sum is wrong
+*/
+
+int view_checksum(THD *thd, TABLE_LIST *view)
+{
+ char md5[MD5_BUFF_LENGTH];
+ if (!view->view || view->md5.length != 32)
+ return HA_ADMIN_NOT_IMPLEMENTED;
+ view->calc_md5(md5);
+ return (strncmp(md5, view->md5.str, 32) ?
+ HA_ADMIN_WRONG_CHECKSUM :
+ HA_ADMIN_OK);
+}
+
+/**
+ Check view
+
+ @param thd thread handle
+ @param view view for check
+ @param check_opt check options
+
+ @retval HA_ADMIN_OK OK
+ @retval HA_ADMIN_NOT_IMPLEMENTED it is not VIEW
+ @retval HA_ADMIN_WRONG_CHECKSUM check sum is wrong
+*/
+int view_check(THD *thd, TABLE_LIST *view, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("view_check");
+
+ int res= view_checksum(thd, view);
+ if (res != HA_ADMIN_OK)
+ DBUG_RETURN(res);
+
+ if (((check_opt->sql_flags & TT_FOR_UPGRADE) && !view->mariadb_version))
+ DBUG_RETURN(HA_ADMIN_NEEDS_UPGRADE);
+
+ DBUG_RETURN(HA_ADMIN_OK);
+}
+
+
+/**
+ Repair view
+
+ @param thd thread handle
+ @param view view for check
+ @param check_opt check options
+
+ @retval HA_ADMIN_OK OK
+ @retval HA_ADMIN_NOT_IMPLEMENTED it is not VIEW
+ @retval HA_ADMIN_WRONG_CHECKSUM check sum is wrong
+*/
+
+int view_repair(THD *thd, TABLE_LIST *view, HA_CHECK_OPT *check_opt)
+{
+ DBUG_ENTER("view_repair");
+ bool swap_alg= (check_opt->sql_flags & TT_FROM_MYSQL);
+ bool wrong_checksum= view_checksum(thd, view) != HA_ADMIN_OK;
+ int ret;
+ if (wrong_checksum || swap_alg || (!view->mariadb_version))
+ {
+ ret= mariadb_fix_view(thd, view, wrong_checksum, swap_alg);
+ DBUG_RETURN(ret);
+ }
+ DBUG_RETURN(HA_ADMIN_OK);
+}
+
+/*
+ rename view
+
+ Synopsis:
+ renames a view
+
+ Parameters:
+ thd thread handler
+ new_db new name of database
+ new_name new name of view
+ view view
+
+ Return values:
+ FALSE Ok
+ TRUE Error
+*/
+bool
+mysql_rename_view(THD *thd,
+ const char *new_db,
+ const char *new_name,
+ TABLE_LIST *view)
+{
+ LEX_STRING pathstr;
+ File_parser *parser;
+ char path_buff[FN_REFLEN + 1];
+ bool error= TRUE;
+ DBUG_ENTER("mysql_rename_view");
+
+ pathstr.str= (char *) path_buff;
+ pathstr.length= build_table_filename(path_buff, sizeof(path_buff) - 1,
+ view->db, view->table_name,
+ reg_ext, 0);
+
+ if ((parser= sql_parse_prepare(&pathstr, thd->mem_root, 1)) &&
+ is_equal(&view_type, parser->type()))
+ {
+ TABLE_LIST view_def;
+ char dir_buff[FN_REFLEN + 1];
+ LEX_STRING dir, file;
+
+ /*
+ To be PS-friendly we should either to restore state of
+ TABLE_LIST object pointed by 'view' after using it for
+ view definition parsing or use temporary 'view_def'
+ object for it.
+ */
+ bzero(&view_def, sizeof(view_def));
+ view_def.timestamp.str= view_def.timestamp_buffer;
+ view_def.view_suid= TRUE;
+
+ /* get view definition and source */
+ if (parser->parse((uchar*)&view_def, thd->mem_root, view_parameters,
+ array_elements(view_parameters)-1,
+ &file_parser_dummy_hook))
+ goto err;
+
+ /* rename view and it's backups */
+ if (rename_in_schema_file(thd, view->db, view->table_name, new_db, new_name))
+ goto err;
+
+ dir.str= dir_buff;
+ dir.length= build_table_filename(dir_buff, sizeof(dir_buff) - 1,
+ new_db, "", "", 0);
+
+ pathstr.str= path_buff;
+ pathstr.length= build_table_filename(path_buff, sizeof(path_buff) - 1,
+ new_db, new_name, reg_ext, 0);
+
+ file.str= pathstr.str + dir.length;
+ file.length= pathstr.length - dir.length;
+
+ if (sql_create_definition_file(&dir, &file, view_file_type,
+ (uchar*)&view_def, view_parameters))
+ {
+ /* restore renamed view in case of error */
+ rename_in_schema_file(thd, new_db, new_name, view->db, view->table_name);
+ goto err;
+ }
+ } else
+ DBUG_RETURN(1);
+
+ /* remove cache entries */
+ query_cache_invalidate3(thd, view, 0);
+ sp_cache_invalidate();
+ error= FALSE;
+
+err:
+ DBUG_RETURN(error);
+}
diff --git a/sql/sql_view.h b/sql/sql_view.h
new file mode 100644
index 00000000000..8d733a1867c
--- /dev/null
+++ b/sql/sql_view.h
@@ -0,0 +1,62 @@
+#ifndef SQL_VIEW_INCLUDED
+#define SQL_VIEW_INCLUDED
+
+/* -*- C++ -*- */
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates.
+ Copyright (c) 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "sql_class.h" /* Required by sql_lex.h */
+#include "sql_lex.h" /* enum_view_create_mode, enum_drop_mode */
+
+/* Forward declarations */
+
+class File_parser;
+
+
+/* Function declarations */
+
+bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view,
+ enum_view_create_mode mode);
+
+bool mysql_create_view(THD *thd, TABLE_LIST *view,
+ enum_view_create_mode mode);
+
+bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table,
+ uint flags);
+
+
+bool mysql_drop_view(THD *thd, TABLE_LIST *view, enum_drop_mode drop_mode);
+
+bool check_key_in_view(THD *thd, TABLE_LIST * view);
+
+bool insert_view_fields(THD *thd, List<Item> *list, TABLE_LIST *view);
+
+int view_checksum(THD *thd, TABLE_LIST *view);
+int view_check(THD *thd, TABLE_LIST *view, HA_CHECK_OPT *check_opt);
+int view_repair(THD *thd, TABLE_LIST *view, HA_CHECK_OPT *check_opt);
+
+extern TYPELIB updatable_views_with_limit_typelib;
+
+bool check_duplicate_names(List<Item>& item_list, bool gen_unique_view_names);
+bool mysql_rename_view(THD *thd, const char *new_db, const char *new_name,
+ TABLE_LIST *view);
+
+#define VIEW_ANY_ACL (SELECT_ACL | UPDATE_ACL | INSERT_ACL | DELETE_ACL)
+
+extern const LEX_STRING view_type;
+
+#endif /* SQL_VIEW_INCLUDED */
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
new file mode 100644
index 00000000000..9c7898b1b02
--- /dev/null
+++ b/sql/sql_yacc.yy
@@ -0,0 +1,16420 @@
+/*
+ Copyright (c) 2000, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/* sql_yacc.yy */
+
+/**
+ @defgroup Parser Parser
+ @{
+*/
+
+%{
+#define YYLIP (& thd->m_parser_state->m_lip)
+#define YYPS (& thd->m_parser_state->m_yacc)
+#define YYCSCL (thd->variables.character_set_client)
+
+#define MYSQL_YACC
+#define YYINITDEPTH 100
+#define YYMAXDEPTH 3200 /* Because of 64K stack */
+#define Lex (thd->lex)
+
+#define Select Lex->current_select
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "sql_parse.h" /* comp_*_creator */
+#include "sql_table.h" /* primary_key_name */
+#include "sql_partition.h" /* mem_alloc_error, partition_info, HASH_PARTITION */
+#include "sql_acl.h" /* *_ACL */
+#include "password.h" /* my_make_scrambled_password_323, my_make_scrambled_password */
+#include "sql_class.h" /* Key_part_spec, enum_filetype, Diag_condition_item_name */
+#include "slave.h"
+#include "lex_symbol.h"
+#include "item_create.h"
+#include "sp_head.h"
+#include "sp_pcontext.h"
+#include "sp_rcontext.h"
+#include "sp.h"
+#include "sql_alter.h" // Sql_cmd_alter_table*
+#include "sql_truncate.h" // Sql_cmd_truncate_table
+#include "sql_admin.h" // Sql_cmd_analyze/Check..._table
+#include "sql_partition_admin.h" // Sql_cmd_alter_table_*_part.
+#include "sql_handler.h" // Sql_cmd_handler_*
+#include "sql_signal.h"
+#include "sql_get_diagnostics.h" // Sql_cmd_get_diagnostics
+#include "event_parse_data.h"
+#include "create_options.h"
+#include <myisam.h>
+#include <myisammrg.h>
+#include "keycaches.h"
+#include "set_var.h"
+#include "rpl_mi.h"
+#include "lex_token.h"
+
+/* this is to get the bison compilation windows warnings out */
+#ifdef _MSC_VER
+/* warning C4065: switch statement contains 'default' but no 'case' labels */
+#pragma warning (disable : 4065)
+#endif
+
+int yylex(void *yylval, void *yythd);
+
+#define yyoverflow(A,B,C,D,E,F) \
+ { \
+ ulong val= *(F); \
+ if (my_yyoverflow((B), (D), &val)) \
+ { \
+ yyerror(thd, (char*) (A)); \
+ return 2; \
+ } \
+ else \
+ { \
+ *(F)= (YYSIZE_T)val; \
+ } \
+ }
+
+#define MYSQL_YYABORT \
+ do \
+ { \
+ LEX::cleanup_lex_after_parse_error(thd); \
+ YYABORT; \
+ } while (0)
+
+#define MYSQL_YYABORT_UNLESS(A) \
+ if (!(A)) \
+ { \
+ my_parse_error(ER(ER_SYNTAX_ERROR));\
+ MYSQL_YYABORT; \
+ }
+
+/*
+ Work around for broken code generated by bison 1.875.
+
+ The code generated by bison 1.875a and later, bison 2.1 and bison 2.2 is ok.
+ With bison 1.875 however, the generated code contains:
+<pre>
+ yyerrlab1:
+ #if defined (__GNUC_MINOR__) && 2093 <= (__GNUC__ * 1000 + __GNUC_MINOR__)
+ __attribute__ ((__unused__))
+ #endif
+</pre>
+ This usage of __attribute__ is illegal, so we remove it.
+ See the following references for details:
+ http://lists.gnu.org/archive/html/bug-bison/2004-02/msg00014.html
+ http://gcc.gnu.org/bugzilla/show_bug.cgi?id=14273
+*/
+
+#if defined (__GNUC_MINOR__) && 2093 <= (__GNUC__ * 1000 + __GNUC_MINOR__)
+#undef __attribute__
+#define __attribute__(X)
+#endif
+
+
+#ifndef DBUG_OFF
+#define YYDEBUG 1
+#else
+#define YYDEBUG 0
+#endif
+
+/**
+ @brief Push an error message into MySQL error stack with line
+ and position information.
+
+ This function provides semantic action implementers with a way
+ to push the famous "You have a syntax error near..." error
+ message into the error stack, which is normally produced only if
+ a parse error is discovered internally by the Bison generated
+ parser.
+*/
+
+void my_parse_error(const char *s)
+{
+ THD *thd= current_thd;
+ Lex_input_stream *lip= & thd->m_parser_state->m_lip;
+
+ const char *yytext= lip->get_tok_start();
+ if (!yytext)
+ yytext= "";
+
+ /* Push an error into the error stack */
+ ErrConvString err(yytext, strlen(yytext), thd->variables.character_set_client);
+ my_printf_error(ER_PARSE_ERROR, ER(ER_PARSE_ERROR), MYF(0), s,
+ err.ptr(), lip->yylineno);
+}
+
+/**
+ @brief Bison callback to report a syntax/OOM error
+
+ This function is invoked by the bison-generated parser
+ when a syntax error, a parse error or an out-of-memory
+ condition occurs. This function is not invoked when the
+ parser is requested to abort by semantic action code
+ by means of YYABORT or YYACCEPT macros. This is why these
+ macros should not be used (use MYSQL_YYABORT/MYSQL_YYACCEPT
+ instead).
+
+ The parser will abort immediately after invoking this callback.
+
+ This function is not for use in semantic actions and is internal to
+ the parser, as it performs some pre-return cleanup.
+ In semantic actions, please use my_parse_error or my_error to
+ push an error into the error stack and MYSQL_YYABORT
+ to abort from the parser.
+*/
+
+void MYSQLerror(THD *thd, const char *s)
+{
+ /*
+ Restore the original LEX if it was replaced when parsing
+ a stored procedure. We must ensure that a parsing error
+ does not leave any side effects in the THD.
+ */
+ LEX::cleanup_lex_after_parse_error(thd);
+
+ /* "parse error" changed into "syntax error" between bison 1.75 and 1.875 */
+ if (strcmp(s,"parse error") == 0 || strcmp(s,"syntax error") == 0)
+ s= ER(ER_SYNTAX_ERROR);
+ my_parse_error(s);
+}
+
+
+#ifndef DBUG_OFF
+void turn_parser_debug_on()
+{
+ /*
+ MYSQLdebug is in sql/sql_yacc.cc, in bison generated code.
+ Turning this option on is **VERY** verbose, and should be
+ used when investigating a syntax error problem only.
+
+ The syntax to run with bison traces is as follows :
+ - Starting a server manually :
+ mysqld --debug-dbug="d,parser_debug" ...
+ - Running a test :
+ mysql-test-run.pl --mysqld="--debug-dbug=d,parser_debug" ...
+
+ The result will be in the process stderr (var/log/master.err)
+ */
+
+ extern int yydebug;
+ yydebug= 1;
+}
+#endif
+
+static bool is_native_function(THD *thd, const LEX_STRING *name)
+{
+ if (find_native_function_builder(thd, *name))
+ return true;
+
+ if (is_lex_native_function(name))
+ return true;
+
+ return false;
+}
+
+
+/**
+ Helper action for a case statement (entering the CASE).
+ This helper is used for both 'simple' and 'searched' cases.
+ This helper, with the other case_stmt_action_..., is executed when
+ the following SQL code is parsed:
+<pre>
+CREATE PROCEDURE proc_19194_simple(i int)
+BEGIN
+ DECLARE str CHAR(10);
+
+ CASE i
+ WHEN 1 THEN SET str="1";
+ WHEN 2 THEN SET str="2";
+ WHEN 3 THEN SET str="3";
+ ELSE SET str="unknown";
+ END CASE;
+
+ SELECT str;
+END
+</pre>
+ The actions are used to generate the following code:
+<pre>
+SHOW PROCEDURE CODE proc_19194_simple;
+Pos Instruction
+0 set str@1 NULL
+1 set_case_expr (12) 0 i@0
+2 jump_if_not 5(12) (case_expr@0 = 1)
+3 set str@1 _latin1'1'
+4 jump 12
+5 jump_if_not 8(12) (case_expr@0 = 2)
+6 set str@1 _latin1'2'
+7 jump 12
+8 jump_if_not 11(12) (case_expr@0 = 3)
+9 set str@1 _latin1'3'
+10 jump 12
+11 set str@1 _latin1'unknown'
+12 stmt 0 "SELECT str"
+</pre>
+
+ @param lex the parser lex context
+*/
+
+void case_stmt_action_case(LEX *lex)
+{
+ lex->sphead->new_cont_backpatch(NULL);
+
+ /*
+ BACKPATCH: Creating target label for the jump to
+ "case_stmt_action_end_case"
+ (Instruction 12 in the example)
+ */
+
+ lex->spcont->push_label(current_thd, empty_lex_str, lex->sphead->instructions());
+}
+
+/**
+ Helper action for a case expression statement (the expr in 'CASE expr').
+ This helper is used for 'searched' cases only.
+ @param lex the parser lex context
+ @param expr the parsed expression
+ @return 0 on success
+*/
+
+int case_stmt_action_expr(LEX *lex, Item* expr)
+{
+ sp_head *sp= lex->sphead;
+ sp_pcontext *parsing_ctx= lex->spcont;
+ int case_expr_id= parsing_ctx->register_case_expr();
+ sp_instr_set_case_expr *i;
+
+ if (parsing_ctx->push_case_expr_id(case_expr_id))
+ return 1;
+
+ i= new sp_instr_set_case_expr(sp->instructions(),
+ parsing_ctx, case_expr_id, expr, lex);
+
+ sp->add_cont_backpatch(i);
+ return sp->add_instr(i);
+}
+
+/**
+ Helper action for a case when condition.
+ This helper is used for both 'simple' and 'searched' cases.
+ @param lex the parser lex context
+ @param when the parsed expression for the WHEN clause
+ @param simple true for simple cases, false for searched cases
+*/
+
+int case_stmt_action_when(LEX *lex, Item *when, bool simple)
+{
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ uint ip= sp->instructions();
+ sp_instr_jump_if_not *i;
+ Item_case_expr *var;
+ Item *expr;
+
+ if (simple)
+ {
+ var= new Item_case_expr(ctx->get_current_case_expr_id());
+
+#ifndef DBUG_OFF
+ if (var)
+ {
+ var->m_sp= sp;
+ }
+#endif
+
+ expr= new Item_func_eq(var, when);
+ i= new sp_instr_jump_if_not(ip, ctx, expr, lex);
+ }
+ else
+ i= new sp_instr_jump_if_not(ip, ctx, when, lex);
+
+ /*
+ BACKPATCH: Registering forward jump from
+ "case_stmt_action_when" to "case_stmt_action_then"
+ (jump_if_not from instruction 2 to 5, 5 to 8 ... in the example)
+ */
+
+ return !MY_TEST(i) ||
+ sp->push_backpatch(i, ctx->push_label(current_thd, empty_lex_str, 0)) ||
+ sp->add_cont_backpatch(i) ||
+ sp->add_instr(i);
+}
+
+/**
+ Helper action for a case then statements.
+ This helper is used for both 'simple' and 'searched' cases.
+ @param lex the parser lex context
+*/
+
+int case_stmt_action_then(LEX *lex)
+{
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ uint ip= sp->instructions();
+ sp_instr_jump *i = new sp_instr_jump(ip, ctx);
+ if (!MY_TEST(i) || sp->add_instr(i))
+ return 1;
+
+ /*
+ BACKPATCH: Resolving forward jump from
+ "case_stmt_action_when" to "case_stmt_action_then"
+ (jump_if_not from instruction 2 to 5, 5 to 8 ... in the example)
+ */
+
+ sp->backpatch(ctx->pop_label());
+
+ /*
+ BACKPATCH: Registering forward jump from
+ "case_stmt_action_then" to "case_stmt_action_end_case"
+ (jump from instruction 4 to 12, 7 to 12 ... in the example)
+ */
+
+ return sp->push_backpatch(i, ctx->last_label());
+}
+
+/**
+ Helper action for an end case.
+ This helper is used for both 'simple' and 'searched' cases.
+ @param lex the parser lex context
+ @param simple true for simple cases, false for searched cases
+*/
+
+void case_stmt_action_end_case(LEX *lex, bool simple)
+{
+ /*
+ BACKPATCH: Resolving forward jump from
+ "case_stmt_action_then" to "case_stmt_action_end_case"
+ (jump from instruction 4 to 12, 7 to 12 ... in the example)
+ */
+ lex->sphead->backpatch(lex->spcont->pop_label());
+
+ if (simple)
+ lex->spcont->pop_case_expr_id();
+
+ lex->sphead->do_cont_backpatch();
+}
+
+
+static bool
+find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp)
+{
+ tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length);
+
+ if (tmp->var == NULL)
+ my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), tmp->base_name.str);
+ else
+ tmp->base_name= null_lex_str;
+
+ return thd->is_error();
+}
+
+
+/**
+ Helper action for a SET statement.
+ Used to push a system variable into the assignment list.
+
+ @param thd the current thread
+ @param tmp the system variable with base name
+ @param var_type the scope of the variable
+ @param val the value being assigned to the variable
+
+ @return TRUE if error, FALSE otherwise.
+*/
+
+static bool
+set_system_variable(THD *thd, struct sys_var_with_base *tmp,
+ enum enum_var_type var_type, Item *val)
+{
+ set_var *var;
+ LEX *lex= thd->lex;
+
+ /* No AUTOCOMMIT from a stored function or trigger. */
+ if (lex->spcont && tmp->var == Sys_autocommit_ptr)
+ lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT;
+
+ if (val && val->type() == Item::FIELD_ITEM &&
+ ((Item_field*)val)->table_name)
+ {
+ my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), tmp->var->name.str);
+ return TRUE;
+ }
+
+ if (! (var= new set_var(var_type, tmp->var, &tmp->base_name, val)))
+ return TRUE;
+
+ return lex->var_list.push_back(var);
+}
+
+
+/**
+ Helper action for a SET statement.
+ Used to push a SP local variable into the assignment list.
+
+ @param thd the current thread
+ @param var_type the SP local variable
+ @param val the value being assigned to the variable
+
+ @return TRUE if error, FALSE otherwise.
+*/
+
+static bool
+set_local_variable(THD *thd, sp_variable *spv, Item *val)
+{
+ Item *it;
+ LEX *lex= thd->lex;
+ sp_instr_set *sp_set;
+
+ if (val)
+ it= val;
+ else if (spv->default_value)
+ it= spv->default_value;
+ else
+ {
+ it= new (thd->mem_root) Item_null();
+ if (it == NULL)
+ return TRUE;
+ }
+
+ sp_set= new sp_instr_set(lex->sphead->instructions(), lex->spcont,
+ spv->offset, it, spv->type, lex, TRUE);
+
+ return (sp_set == NULL || lex->sphead->add_instr(sp_set));
+}
+
+
+/**
+ Helper action for a SET statement.
+ Used to SET a field of NEW row.
+
+ @param thd the current thread
+ @param name the field name
+ @param val the value being assigned to the row
+
+ @return TRUE if error, FALSE otherwise.
+*/
+
+static bool
+set_trigger_new_row(THD *thd, LEX_STRING *name, Item *val)
+{
+ LEX *lex= thd->lex;
+ Item_trigger_field *trg_fld;
+ sp_instr_set_trigger_field *sp_fld;
+
+ /* QQ: Shouldn't this be field's default value ? */
+ if (! val)
+ val= new Item_null();
+
+ DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE &&
+ (lex->trg_chistics.event == TRG_EVENT_INSERT ||
+ lex->trg_chistics.event == TRG_EVENT_UPDATE));
+
+ trg_fld= new (thd->mem_root)
+ Item_trigger_field(lex->current_context(),
+ Item_trigger_field::NEW_ROW,
+ name->str, UPDATE_ACL, FALSE);
+
+ if (trg_fld == NULL)
+ return TRUE;
+
+ sp_fld= new sp_instr_set_trigger_field(lex->sphead->instructions(),
+ lex->spcont, trg_fld, val, lex);
+
+ if (sp_fld == NULL)
+ return TRUE;
+
+ /*
+ Let us add this item to list of all Item_trigger_field
+ objects in trigger.
+ */
+ lex->trg_table_fields.link_in_list(trg_fld, &trg_fld->next_trg_field);
+
+ return lex->sphead->add_instr(sp_fld);
+}
+
+
+/**
+ Create an object to represent a SP variable in the Item-hierarchy.
+
+ @param thd The current thread.
+ @param name The SP variable name.
+ @param spvar The SP variable (optional).
+ @param start_in_q Start position of the SP variable name in the query.
+ @param end_in_q End position of the SP variable name in the query.
+
+ @remark If spvar is not specified, the name is used to search for the
+ variable in the parse-time context. If the variable does not
+ exist, a error is set and NULL is returned to the caller.
+
+ @return An Item_splocal object representing the SP variable, or NULL on error.
+*/
+static Item_splocal*
+create_item_for_sp_var(THD *thd, LEX_STRING name, sp_variable *spvar,
+ const char *start_in_q, const char *end_in_q)
+{
+ Item_splocal *item;
+ LEX *lex= thd->lex;
+ uint pos_in_q, len_in_q;
+ sp_pcontext *spc = lex->spcont;
+
+ /* If necessary, look for the variable. */
+ if (spc && !spvar)
+ spvar= spc->find_variable(name, false);
+
+ if (!spvar)
+ {
+ my_error(ER_SP_UNDECLARED_VAR, MYF(0), name.str);
+ return NULL;
+ }
+
+ DBUG_ASSERT(spc && spvar);
+
+ /* Position and length of the SP variable name in the query. */
+ pos_in_q= start_in_q - lex->sphead->m_tmp_query;
+ len_in_q= end_in_q - start_in_q;
+
+ item= new (thd->mem_root)
+ Item_splocal(name, spvar->offset, spvar->type, pos_in_q, len_in_q);
+
+#ifndef DBUG_OFF
+ if (item)
+ item->m_sp= lex->sphead;
+#endif
+
+ return item;
+}
+
+/**
+ Helper to resolve the SQL:2003 Syntax exception 1) in <in predicate>.
+ See SQL:2003, Part 2, section 8.4 <in predicate>, Note 184, page 383.
+ This function returns the proper item for the SQL expression
+ <code>left [NOT] IN ( expr )</code>
+ @param thd the current thread
+ @param left the in predicand
+ @param equal true for IN predicates, false for NOT IN predicates
+ @param expr first and only expression of the in value list
+ @return an expression representing the IN predicate.
+*/
+Item* handle_sql2003_note184_exception(THD *thd, Item* left, bool equal,
+ Item *expr)
+{
+ /*
+ Relevant references for this issue:
+ - SQL:2003, Part 2, section 8.4 <in predicate>, page 383,
+ - SQL:2003, Part 2, section 7.2 <row value expression>, page 296,
+ - SQL:2003, Part 2, section 6.3 <value expression primary>, page 174,
+ - SQL:2003, Part 2, section 7.15 <subquery>, page 370,
+ - SQL:2003 Feature F561, "Full value expressions".
+
+ The exception in SQL:2003 Note 184 means:
+ Item_singlerow_subselect, which corresponds to a <scalar subquery>,
+ should be re-interpreted as an Item_in_subselect, which corresponds
+ to a <table subquery> when used inside an <in predicate>.
+
+ Our reading of Note 184 is reccursive, so that all:
+ - IN (( <subquery> ))
+ - IN ((( <subquery> )))
+ - IN '('^N <subquery> ')'^N
+ - etc
+ should be interpreted as a <table subquery>, no matter how deep in the
+ expression the <subquery> is.
+ */
+
+ Item *result;
+
+ DBUG_ENTER("handle_sql2003_note184_exception");
+
+ if (expr->type() == Item::SUBSELECT_ITEM)
+ {
+ Item_subselect *expr2 = (Item_subselect*) expr;
+
+ if (expr2->substype() == Item_subselect::SINGLEROW_SUBS)
+ {
+ Item_singlerow_subselect *expr3 = (Item_singlerow_subselect*) expr2;
+ st_select_lex *subselect;
+
+ /*
+ Implement the mandated change, by altering the semantic tree:
+ left IN Item_singlerow_subselect(subselect)
+ is modified to
+ left IN (subselect)
+ which is represented as
+ Item_in_subselect(left, subselect)
+ */
+ subselect= expr3->invalidate_and_restore_select_lex();
+ result= new (thd->mem_root) Item_in_subselect(left, subselect);
+
+ if (! equal)
+ result = negate_expression(thd, result);
+
+ DBUG_RETURN(result);
+ }
+ }
+
+ if (equal)
+ result= new (thd->mem_root) Item_func_eq(left, expr);
+ else
+ result= new (thd->mem_root) Item_func_ne(left, expr);
+
+ DBUG_RETURN(result);
+}
+
+/**
+ @brief Creates a new SELECT_LEX for a UNION branch.
+
+ Sets up and initializes a SELECT_LEX structure for a query once the parser
+ discovers a UNION token. The current SELECT_LEX is pushed on the stack and
+ the new SELECT_LEX becomes the current one.
+
+ @param lex The parser state.
+
+ @param is_union_distinct True if the union preceding the new select statement
+ uses UNION DISTINCT.
+
+ @param is_top_level This should be @c TRUE if the newly created SELECT_LEX
+ is a non-nested statement.
+
+ @return <code>false</code> if successful, <code>true</code> if an error was
+ reported. In the latter case parsing should stop.
+ */
+bool add_select_to_union_list(LEX *lex, bool is_union_distinct,
+ bool is_top_level)
+{
+ /*
+ Only the last SELECT can have INTO. Since the grammar won't allow INTO in
+ a nested SELECT, we make this check only when creating a top-level SELECT.
+ */
+ if (is_top_level && lex->result)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO");
+ return TRUE;
+ }
+ if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ return TRUE;
+ }
+ /* This counter shouldn't be incremented for UNION parts */
+ lex->nest_level--;
+ if (mysql_new_select(lex, 0))
+ return TRUE;
+ mysql_init_select(lex);
+ lex->current_select->linkage=UNION_TYPE;
+ if (is_union_distinct) /* UNION DISTINCT - remember position */
+ lex->current_select->master_unit()->union_distinct=
+ lex->current_select;
+ return FALSE;
+}
+
+/**
+ @brief Initializes a SELECT_LEX for a query within parentheses (aka
+ braces).
+
+ @return false if successful, true if an error was reported. In the latter
+ case parsing should stop.
+ */
+bool setup_select_in_parentheses(LEX *lex)
+{
+ SELECT_LEX * sel= lex->current_select;
+ if (sel->set_braces(1))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ return TRUE;
+ }
+ if (sel->linkage == UNION_TYPE &&
+ !sel->master_unit()->first_select()->braces &&
+ sel->master_unit()->first_select()->linkage ==
+ UNION_TYPE)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ return TRUE;
+ }
+ if (sel->linkage == UNION_TYPE &&
+ sel->olap != UNSPECIFIED_OLAP_TYPE &&
+ sel->master_unit()->fake_select_lex)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "CUBE/ROLLUP", "ORDER BY");
+ return TRUE;
+ }
+ /* select in braces, can't contain global parameters */
+ if (sel->master_unit()->fake_select_lex)
+ sel->master_unit()->global_parameters=
+ sel->master_unit()->fake_select_lex;
+ return FALSE;
+}
+
+static bool add_create_index_prepare (LEX *lex, Table_ident *table)
+{
+ lex->sql_command= SQLCOM_CREATE_INDEX;
+ if (!lex->current_select->add_table_to_list(lex->thd, table, NULL,
+ TL_OPTION_UPDATING,
+ TL_READ_NO_INSERT,
+ MDL_SHARED_UPGRADABLE))
+ return TRUE;
+ lex->alter_info.reset();
+ lex->alter_info.flags= Alter_info::ALTER_ADD_INDEX;
+ lex->col_list.empty();
+ lex->change= NullS;
+ lex->option_list= NULL;
+ return FALSE;
+}
+
+static bool add_create_index (LEX *lex, Key::Keytype type,
+ const LEX_STRING &name,
+ KEY_CREATE_INFO *info= NULL, bool generated= 0)
+{
+ Key *key;
+ key= new Key(type, name, info ? info : &lex->key_create_info, generated,
+ lex->col_list, lex->option_list, lex->check_exists);
+ if (key == NULL)
+ return TRUE;
+
+ lex->alter_info.key_list.push_back(key);
+ lex->col_list.empty();
+ return FALSE;
+}
+
+
+/**
+ Create a separate LEX for each assignment if in SP.
+
+ If we are in SP we want have own LEX for each assignment.
+ This is mostly because it is hard for several sp_instr_set
+ and sp_instr_set_trigger instructions share one LEX.
+ (Well, it is theoretically possible but adds some extra
+ overhead on preparation for execution stage and IMO less
+ robust).
+
+ QQ: May be we should simply prohibit group assignments in SP?
+
+ @see sp_create_assignment_instr
+
+ @param thd Thread context
+ @param no_lookahead True if the parser has no lookahead
+*/
+
+static void sp_create_assignment_lex(THD *thd, bool no_lookahead)
+{
+ LEX *lex= thd->lex;
+
+ if (lex->sphead)
+ {
+ Lex_input_stream *lip= &thd->m_parser_state->m_lip;
+ LEX *old_lex= lex;
+ lex->sphead->reset_lex(thd);
+ lex= thd->lex;
+
+ /* Set new LEX as if we at start of set rule. */
+ lex->sql_command= SQLCOM_SET_OPTION;
+ mysql_init_select(lex);
+ lex->var_list.empty();
+ lex->autocommit= 0;
+ /* get_ptr() is only correct with no lookahead. */
+ DBUG_ASSERT(no_lookahead);
+ lex->sphead->m_tmp_query= lip->get_ptr();
+ /* Inherit from outer lex. */
+ lex->option_type= old_lex->option_type;
+ }
+}
+
+
+/**
+ Create a SP instruction for a SET assignment.
+
+ @see sp_create_assignment_lex
+
+ @param thd Thread context
+ @param no_lookahead True if the parser has no lookahead
+
+ @return false if success, true otherwise.
+*/
+
+static bool sp_create_assignment_instr(THD *thd, bool no_lookahead)
+{
+ LEX *lex= thd->lex;
+
+ if (lex->sphead)
+ {
+ sp_head *sp= lex->sphead;
+
+ if (!lex->var_list.is_empty())
+ {
+ /*
+ We have assignment to user or system variable or
+ option setting, so we should construct sp_instr_stmt
+ for it.
+ */
+ LEX_STRING qbuff;
+ sp_instr_stmt *i;
+ Lex_input_stream *lip= &thd->m_parser_state->m_lip;
+
+ if (!(i= new sp_instr_stmt(sp->instructions(), lex->spcont,
+ lex)))
+ return true;
+
+ /*
+ Extract the query statement from the tokenizer. The
+ end is either lip->ptr, if there was no lookahead,
+ lip->tok_end otherwise.
+ */
+ if (no_lookahead)
+ qbuff.length= lip->get_ptr() - sp->m_tmp_query;
+ else
+ qbuff.length= lip->get_tok_end() - sp->m_tmp_query;
+
+ if (!(qbuff.str= (char*) alloc_root(thd->mem_root,
+ qbuff.length + 5)))
+ return true;
+
+ strmake(strmake(qbuff.str, "SET ", 4), sp->m_tmp_query,
+ qbuff.length);
+ qbuff.length+= 4;
+ i->m_query= qbuff;
+ if (sp->add_instr(i))
+ return true;
+ }
+ enum_var_type inner_option_type= lex->option_type;
+ if (lex->sphead->restore_lex(thd))
+ return true;
+ /* Copy option_type to outer lex in case it has changed. */
+ thd->lex->option_type= inner_option_type;
+ }
+ return false;
+}
+
+
+%}
+%union {
+ int num;
+ ulong ulong_num;
+ ulonglong ulonglong_number;
+ longlong longlong_number;
+ LEX_STRING lex_str;
+ LEX_STRING *lex_str_ptr;
+ LEX_SYMBOL symbol;
+ LEX_TYPE lex_type;
+ Table_ident *table;
+ char *simple_string;
+ Item *item;
+ Item_num *item_num;
+ List<Item> *item_list;
+ List<String> *string_list;
+ String *string;
+ Key_part_spec *key_part;
+ TABLE_LIST *table_list;
+ udf_func *udf;
+ LEX_USER *lex_user;
+ struct sys_var_with_base variable;
+ enum enum_var_type var_type;
+ Key::Keytype key_type;
+ enum ha_key_alg key_alg;
+ handlerton *db_type;
+ enum row_type row_type;
+ enum ha_rkey_function ha_rkey_mode;
+ enum enum_tx_isolation tx_isolation;
+ enum Cast_target cast_type;
+ enum Item_udftype udf_type;
+ enum ha_choice choice;
+ CHARSET_INFO *charset;
+ thr_lock_type lock_type;
+ interval_type interval, interval_time_st;
+ timestamp_type date_time_type;
+ st_select_lex *select_lex;
+ chooser_compare_func_creator boolfunc2creator;
+ class sp_condition_value *spcondvalue;
+ struct { int vars, conds, hndlrs, curs; } spblock;
+ sp_name *spname;
+ LEX *lex;
+ sp_head *sphead;
+ struct p_elem_val *p_elem_value;
+ enum index_hint_type index_hint;
+ enum enum_filetype filetype;
+ enum Foreign_key::fk_option m_fk_option;
+ enum enum_yes_no_unknown m_yes_no_unk;
+ Diag_condition_item_name diag_condition_item_name;
+ Diagnostics_information::Which_area diag_area;
+ Diagnostics_information *diag_info;
+ Statement_information_item *stmt_info_item;
+ Statement_information_item::Name stmt_info_item_name;
+ List<Statement_information_item> *stmt_info_list;
+ Condition_information_item *cond_info_item;
+ Condition_information_item::Name cond_info_item_name;
+ List<Condition_information_item> *cond_info_list;
+ DYNCALL_CREATE_DEF *dyncol_def;
+ List<DYNCALL_CREATE_DEF> *dyncol_def_list;
+ bool is_not_empty;
+}
+
+%{
+bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
+%}
+
+%pure_parser /* We have threads */
+%parse-param { THD *thd }
+%lex-param { THD *thd }
+/*
+ Currently there are 163 shift/reduce conflicts.
+ We should not introduce new conflicts any more.
+*/
+%expect 163
+
+/*
+ Comments for TOKENS.
+ For each token, please include in the same line a comment that contains
+ the following tags:
+ SQL-2003-R : Reserved keyword as per SQL-2003
+ SQL-2003-N : Non Reserved keyword as per SQL-2003
+ SQL-1999-R : Reserved keyword as per SQL-1999
+ SQL-1999-N : Non Reserved keyword as per SQL-1999
+ MYSQL : MySQL extention (unspecified)
+ MYSQL-FUNC : MySQL extention, function
+ INTERNAL : Not a real token, lex optimization
+ OPERATOR : SQL operator
+ FUTURE-USE : Reserved for future use
+
+ This makes the code grep-able, and helps maintenance.
+*/
+
+%token ABORT_SYM /* INTERNAL (used in lex) */
+%token ACCESSIBLE_SYM
+%token ACTION /* SQL-2003-N */
+%token ADD /* SQL-2003-R */
+%token ADMIN_SYM /* SQL-2003-N */
+%token ADDDATE_SYM /* MYSQL-FUNC */
+%token AFTER_SYM /* SQL-2003-N */
+%token AGAINST
+%token AGGREGATE_SYM
+%token ALGORITHM_SYM
+%token ALL /* SQL-2003-R */
+%token ALTER /* SQL-2003-R */
+%token ALWAYS_SYM
+%token ANALYZE_SYM
+%token AND_AND_SYM /* OPERATOR */
+%token AND_SYM /* SQL-2003-R */
+%token ANY_SYM /* SQL-2003-R */
+%token AS /* SQL-2003-R */
+%token ASC /* SQL-2003-N */
+%token ASCII_SYM /* MYSQL-FUNC */
+%token ASENSITIVE_SYM /* FUTURE-USE */
+%token AT_SYM /* SQL-2003-R */
+%token AUTHORS_SYM
+%token AUTOEXTEND_SIZE_SYM
+%token AUTO_INC
+%token AUTO_SYM
+%token AVG_ROW_LENGTH
+%token AVG_SYM /* SQL-2003-N */
+%token BACKUP_SYM
+%token BEFORE_SYM /* SQL-2003-N */
+%token BEGIN_SYM /* SQL-2003-R */
+%token BETWEEN_SYM /* SQL-2003-R */
+%token BIGINT /* SQL-2003-R */
+%token BINARY /* SQL-2003-R */
+%token BINLOG_SYM
+%token BIN_NUM
+%token BIT_AND /* MYSQL-FUNC */
+%token BIT_OR /* MYSQL-FUNC */
+%token BIT_SYM /* MYSQL-FUNC */
+%token BIT_XOR /* MYSQL-FUNC */
+%token BLOB_SYM /* SQL-2003-R */
+%token BLOCK_SYM
+%token BOOLEAN_SYM /* SQL-2003-R */
+%token BOOL_SYM
+%token BOTH /* SQL-2003-R */
+%token BTREE_SYM
+%token BY /* SQL-2003-R */
+%token BYTE_SYM
+%token CACHE_SYM
+%token CALL_SYM /* SQL-2003-R */
+%token CASCADE /* SQL-2003-N */
+%token CASCADED /* SQL-2003-R */
+%token CASE_SYM /* SQL-2003-R */
+%token CAST_SYM /* SQL-2003-R */
+%token CATALOG_NAME_SYM /* SQL-2003-N */
+%token CHAIN_SYM /* SQL-2003-N */
+%token CHANGE
+%token CHANGED
+%token CHARSET
+%token CHAR_SYM /* SQL-2003-R */
+%token CHECKPOINT_SYM
+%token CHECKSUM_SYM
+%token CHECK_SYM /* SQL-2003-R */
+%token CIPHER_SYM
+%token CLASS_ORIGIN_SYM /* SQL-2003-N */
+%token CLIENT_SYM
+%token CLIENT_STATS_SYM
+%token CLOSE_SYM /* SQL-2003-R */
+%token COALESCE /* SQL-2003-N */
+%token CODE_SYM
+%token COLLATE_SYM /* SQL-2003-R */
+%token COLLATION_SYM /* SQL-2003-N */
+%token COLUMNS
+%token COLUMN_ADD_SYM
+%token COLUMN_CHECK_SYM
+%token COLUMN_CREATE_SYM
+%token COLUMN_DELETE_SYM
+%token COLUMN_GET_SYM
+%token COLUMN_SYM /* SQL-2003-R */
+%token COLUMN_NAME_SYM /* SQL-2003-N */
+%token COMMENT_SYM
+%token COMMITTED_SYM /* SQL-2003-N */
+%token COMMIT_SYM /* SQL-2003-R */
+%token COMPACT_SYM
+%token COMPLETION_SYM
+%token COMPRESSED_SYM
+%token CONCURRENT
+%token CONDITION_SYM /* SQL-2003-R, SQL-2008-R */
+%token CONNECTION_SYM
+%token CONSISTENT_SYM
+%token CONSTRAINT /* SQL-2003-R */
+%token CONSTRAINT_CATALOG_SYM /* SQL-2003-N */
+%token CONSTRAINT_NAME_SYM /* SQL-2003-N */
+%token CONSTRAINT_SCHEMA_SYM /* SQL-2003-N */
+%token CONTAINS_SYM /* SQL-2003-N */
+%token CONTEXT_SYM
+%token CONTINUE_SYM /* SQL-2003-R */
+%token CONTRIBUTORS_SYM
+%token CONVERT_SYM /* SQL-2003-N */
+%token COUNT_SYM /* SQL-2003-N */
+%token CPU_SYM
+%token CREATE /* SQL-2003-R */
+%token CROSS /* SQL-2003-R */
+%token CUBE_SYM /* SQL-2003-R */
+%token CURDATE /* MYSQL-FUNC */
+%token CURRENT_SYM /* SQL-2003-R */
+%token CURRENT_USER /* SQL-2003-R */
+%token CURRENT_ROLE /* SQL-2003-R */
+%token CURRENT_POS_SYM
+%token CURSOR_SYM /* SQL-2003-R */
+%token CURSOR_NAME_SYM /* SQL-2003-N */
+%token CURTIME /* MYSQL-FUNC */
+%token DATABASE
+%token DATABASES
+%token DATAFILE_SYM
+%token DATA_SYM /* SQL-2003-N */
+%token DATETIME
+%token DATE_ADD_INTERVAL /* MYSQL-FUNC */
+%token DATE_SUB_INTERVAL /* MYSQL-FUNC */
+%token DATE_SYM /* SQL-2003-R */
+%token DAY_HOUR_SYM
+%token DAY_MICROSECOND_SYM
+%token DAY_MINUTE_SYM
+%token DAY_SECOND_SYM
+%token DAY_SYM /* SQL-2003-R */
+%token DEALLOCATE_SYM /* SQL-2003-R */
+%token DECIMAL_NUM
+%token DECIMAL_SYM /* SQL-2003-R */
+%token DECLARE_SYM /* SQL-2003-R */
+%token DEFAULT /* SQL-2003-R */
+%token DEFINER_SYM
+%token DELAYED_SYM
+%token DELAY_KEY_WRITE_SYM
+%token DELETE_SYM /* SQL-2003-R */
+%token DESC /* SQL-2003-N */
+%token DESCRIBE /* SQL-2003-R */
+%token DES_KEY_FILE
+%token DETERMINISTIC_SYM /* SQL-2003-R */
+%token DIAGNOSTICS_SYM /* SQL-2003-N */
+%token DIRECTORY_SYM
+%token DISABLE_SYM
+%token DISCARD
+%token DISK_SYM
+%token DISTINCT /* SQL-2003-R */
+%token DIV_SYM
+%token DOUBLE_SYM /* SQL-2003-R */
+%token DO_SYM
+%token DROP /* SQL-2003-R */
+%token DUAL_SYM
+%token DUMPFILE
+%token DUPLICATE_SYM
+%token DYNAMIC_SYM /* SQL-2003-R */
+%token EACH_SYM /* SQL-2003-R */
+%token ELSE /* SQL-2003-R */
+%token ELSEIF_SYM
+%token ENABLE_SYM
+%token ENCLOSED
+%token END /* SQL-2003-R */
+%token ENDS_SYM
+%token END_OF_INPUT /* INTERNAL */
+%token ENGINES_SYM
+%token ENGINE_SYM
+%token ENUM
+%token EQ /* OPERATOR */
+%token EQUAL_SYM /* OPERATOR */
+%token ERROR_SYM
+%token ERRORS
+%token ESCAPED
+%token ESCAPE_SYM /* SQL-2003-R */
+%token EVENTS_SYM
+%token EVENT_SYM
+%token EVERY_SYM /* SQL-2003-N */
+%token EXCHANGE_SYM
+%token EXAMINED_SYM
+%token EXECUTE_SYM /* SQL-2003-R */
+%token EXISTS /* SQL-2003-R */
+%token EXIT_SYM
+%token EXPANSION_SYM
+%token EXPORT_SYM
+%token EXTENDED_SYM
+%token EXTENT_SIZE_SYM
+%token EXTRACT_SYM /* SQL-2003-N */
+%token FALSE_SYM /* SQL-2003-R */
+%token FAST_SYM
+%token FAULTS_SYM
+%token FETCH_SYM /* SQL-2003-R */
+%token FILE_SYM
+%token FIRST_SYM /* SQL-2003-N */
+%token FIXED_SYM
+%token FLOAT_NUM
+%token FLOAT_SYM /* SQL-2003-R */
+%token FLUSH_SYM
+%token FORCE_SYM
+%token FOREIGN /* SQL-2003-R */
+%token FOR_SYM /* SQL-2003-R */
+%token FOUND_SYM /* SQL-2003-R */
+%token FROM
+%token FULL /* SQL-2003-R */
+%token FULLTEXT_SYM
+%token FUNCTION_SYM /* SQL-2003-R */
+%token GE
+%token GENERAL
+%token GENERATED_SYM
+%token GEOMETRYCOLLECTION
+%token GEOMETRY_SYM
+%token GET_FORMAT /* MYSQL-FUNC */
+%token GET_SYM /* SQL-2003-R */
+%token GLOBAL_SYM /* SQL-2003-R */
+%token GRANT /* SQL-2003-R */
+%token GRANTS
+%token GROUP_SYM /* SQL-2003-R */
+%token GROUP_CONCAT_SYM
+%token GT_SYM /* OPERATOR */
+%token HANDLER_SYM
+%token HARD_SYM
+%token HASH_SYM
+%token HAVING /* SQL-2003-R */
+%token HELP_SYM
+%token HEX_NUM
+%token HEX_STRING
+%token HIGH_PRIORITY
+%token HOST_SYM
+%token HOSTS_SYM
+%token HOUR_MICROSECOND_SYM
+%token HOUR_MINUTE_SYM
+%token HOUR_SECOND_SYM
+%token HOUR_SYM /* SQL-2003-R */
+%token ID_SYM /* MYSQL */
+%token IDENT
+%token IDENTIFIED_SYM
+%token IDENT_QUOTED
+%token IF
+%token IGNORE_SYM
+%token IGNORE_SERVER_IDS_SYM
+%token IMPORT
+%token INDEXES
+%token INDEX_SYM
+%token INDEX_STATS_SYM
+%token INFILE
+%token INITIAL_SIZE_SYM
+%token INNER_SYM /* SQL-2003-R */
+%token INOUT_SYM /* SQL-2003-R */
+%token INSENSITIVE_SYM /* SQL-2003-R */
+%token INSERT /* SQL-2003-R */
+%token INSERT_METHOD
+%token INSTALL_SYM
+%token INTERVAL_SYM /* SQL-2003-R */
+%token INTO /* SQL-2003-R */
+%token INT_SYM /* SQL-2003-R */
+%token INVOKER_SYM
+%token IN_SYM /* SQL-2003-R */
+%token IO_SYM
+%token IPC_SYM
+%token IS /* SQL-2003-R */
+%token ISOLATION /* SQL-2003-R */
+%token ISSUER_SYM
+%token ITERATE_SYM
+%token JOIN_SYM /* SQL-2003-R */
+%token KEYS
+%token KEY_BLOCK_SIZE
+%token KEY_SYM /* SQL-2003-N */
+%token KILL_SYM
+%token LANGUAGE_SYM /* SQL-2003-R */
+%token LAST_SYM /* SQL-2003-N */
+%token LAST_VALUE
+%token LE /* OPERATOR */
+%token LEADING /* SQL-2003-R */
+%token LEAVES
+%token LEAVE_SYM
+%token LEFT /* SQL-2003-R */
+%token LESS_SYM
+%token LEVEL_SYM
+%token LEX_HOSTNAME
+%token LIKE /* SQL-2003-R */
+%token LIMIT
+%token LINEAR_SYM
+%token LINES
+%token LINESTRING
+%token LIST_SYM
+%token LOAD
+%token LOCAL_SYM /* SQL-2003-R */
+%token LOCATOR_SYM /* SQL-2003-N */
+%token LOCKS_SYM
+%token LOCK_SYM
+%token LOGFILE_SYM
+%token LOGS_SYM
+%token LONGBLOB
+%token LONGTEXT
+%token LONG_NUM
+%token LONG_SYM
+%token LOOP_SYM
+%token LOW_PRIORITY
+%token LT /* OPERATOR */
+%token MASTER_CONNECT_RETRY_SYM
+%token MASTER_GTID_POS_SYM
+%token MASTER_HOST_SYM
+%token MASTER_LOG_FILE_SYM
+%token MASTER_LOG_POS_SYM
+%token MASTER_PASSWORD_SYM
+%token MASTER_PORT_SYM
+%token MASTER_SERVER_ID_SYM
+%token MASTER_SSL_CAPATH_SYM
+%token MASTER_SSL_CA_SYM
+%token MASTER_SSL_CERT_SYM
+%token MASTER_SSL_CIPHER_SYM
+%token MASTER_SSL_CRL_SYM
+%token MASTER_SSL_CRLPATH_SYM
+%token MASTER_SSL_KEY_SYM
+%token MASTER_SSL_SYM
+%token MASTER_SSL_VERIFY_SERVER_CERT_SYM
+%token MASTER_SYM
+%token MASTER_USER_SYM
+%token MASTER_USE_GTID_SYM
+%token MASTER_HEARTBEAT_PERIOD_SYM
+%token MATCH /* SQL-2003-R */
+%token MAX_CONNECTIONS_PER_HOUR
+%token MAX_QUERIES_PER_HOUR
+%token MAX_ROWS
+%token MAX_SIZE_SYM
+%token MAX_SYM /* SQL-2003-N */
+%token MAX_UPDATES_PER_HOUR
+%token MAX_USER_CONNECTIONS_SYM
+%token MAX_VALUE_SYM /* SQL-2003-N */
+%token MEDIUMBLOB
+%token MEDIUMINT
+%token MEDIUMTEXT
+%token MEDIUM_SYM
+%token MEMORY_SYM
+%token MERGE_SYM /* SQL-2003-R */
+%token MESSAGE_TEXT_SYM /* SQL-2003-N */
+%token MICROSECOND_SYM /* MYSQL-FUNC */
+%token MIGRATE_SYM
+%token MINUTE_MICROSECOND_SYM
+%token MINUTE_SECOND_SYM
+%token MINUTE_SYM /* SQL-2003-R */
+%token MIN_ROWS
+%token MIN_SYM /* SQL-2003-N */
+%token MODE_SYM
+%token MODIFIES_SYM /* SQL-2003-R */
+%token MODIFY_SYM
+%token MOD_SYM /* SQL-2003-N */
+%token MONTH_SYM /* SQL-2003-R */
+%token MULTILINESTRING
+%token MULTIPOINT
+%token MULTIPOLYGON
+%token MUTEX_SYM
+%token MYSQL_SYM
+%token MYSQL_ERRNO_SYM
+%token NAMES_SYM /* SQL-2003-N */
+%token NAME_SYM /* SQL-2003-N */
+%token NATIONAL_SYM /* SQL-2003-R */
+%token NATURAL /* SQL-2003-R */
+%token NCHAR_STRING
+%token NCHAR_SYM /* SQL-2003-R */
+%token NDBCLUSTER_SYM
+%token NE /* OPERATOR */
+%token NEG
+%token NEW_SYM /* SQL-2003-R */
+%token NEXT_SYM /* SQL-2003-N */
+%token NODEGROUP_SYM
+%token NONE_SYM /* SQL-2003-R */
+%token NOT2_SYM
+%token NOT_SYM /* SQL-2003-R */
+%token NOW_SYM
+%token NO_SYM /* SQL-2003-R */
+%token NO_WAIT_SYM
+%token NO_WRITE_TO_BINLOG
+%token NULL_SYM /* SQL-2003-R */
+%token NUM
+%token NUMBER_SYM /* SQL-2003-N */
+%token NUMERIC_SYM /* SQL-2003-R */
+%token NVARCHAR_SYM
+%token OFFSET_SYM
+%token OLD_PASSWORD
+%token ON /* SQL-2003-R */
+%token ONE_SYM
+%token ONLY_SYM /* SQL-2003-R */
+%token ONLINE_SYM
+%token OPEN_SYM /* SQL-2003-R */
+%token OPTIMIZE
+%token OPTIONS_SYM
+%token OPTION /* SQL-2003-N */
+%token OPTIONALLY
+%token OR2_SYM
+%token ORDER_SYM /* SQL-2003-R */
+%token OR_OR_SYM /* OPERATOR */
+%token OR_SYM /* SQL-2003-R */
+%token OUTER
+%token OUTFILE
+%token OUT_SYM /* SQL-2003-R */
+%token OWNER_SYM
+%token PACK_KEYS_SYM
+%token PAGE_SYM
+%token PAGE_CHECKSUM_SYM
+%token PARAM_MARKER
+%token PARSER_SYM
+%token PARSE_VCOL_EXPR_SYM
+%token PARTIAL /* SQL-2003-N */
+%token PARTITION_SYM /* SQL-2003-R */
+%token PARTITIONS_SYM
+%token PARTITIONING_SYM
+%token PASSWORD
+%token PERSISTENT_SYM
+%token PHASE_SYM
+%token PLUGINS_SYM
+%token PLUGIN_SYM
+%token POINT_SYM
+%token POLYGON
+%token PORT_SYM
+%token POSITION_SYM /* SQL-2003-N */
+%token PRECISION /* SQL-2003-R */
+%token PREPARE_SYM /* SQL-2003-R */
+%token PRESERVE_SYM
+%token PREV_SYM
+%token PRIMARY_SYM /* SQL-2003-R */
+%token PRIVILEGES /* SQL-2003-N */
+%token PROCEDURE_SYM /* SQL-2003-R */
+%token PROCESS
+%token PROCESSLIST_SYM
+%token PROFILE_SYM
+%token PROFILES_SYM
+%token PROXY_SYM
+%token PURGE
+%token QUARTER_SYM
+%token QUERY_SYM
+%token QUICK
+%token RANGE_SYM /* SQL-2003-R */
+%token READS_SYM /* SQL-2003-R */
+%token READ_ONLY_SYM
+%token READ_SYM /* SQL-2003-N */
+%token READ_WRITE_SYM
+%token REAL /* SQL-2003-R */
+%token REBUILD_SYM
+%token RECOVER_SYM
+%token REDOFILE_SYM
+%token REDO_BUFFER_SIZE_SYM
+%token REDUNDANT_SYM
+%token REFERENCES /* SQL-2003-R */
+%token REGEXP
+%token RELAY
+%token RELAYLOG_SYM
+%token RELAY_LOG_FILE_SYM
+%token RELAY_LOG_POS_SYM
+%token RELAY_THREAD
+%token RELEASE_SYM /* SQL-2003-R */
+%token RELOAD
+%token REMOVE_SYM
+%token RENAME
+%token REORGANIZE_SYM
+%token REPAIR
+%token REPEATABLE_SYM /* SQL-2003-N */
+%token REPEAT_SYM /* MYSQL-FUNC */
+%token REPLACE /* MYSQL-FUNC */
+%token REPLICATION
+%token REQUIRE_SYM
+%token RESET_SYM
+%token RESIGNAL_SYM /* SQL-2003-R */
+%token RESOURCES
+%token RESTORE_SYM
+%token RESTRICT
+%token RESUME_SYM
+%token RETURNED_SQLSTATE_SYM /* SQL-2003-N */
+%token RETURNING_SYM
+%token RETURNS_SYM /* SQL-2003-R */
+%token RETURN_SYM /* SQL-2003-R */
+%token REVERSE_SYM
+%token REVOKE /* SQL-2003-R */
+%token RIGHT /* SQL-2003-R */
+%token ROLE_SYM
+%token ROLLBACK_SYM /* SQL-2003-R */
+%token ROLLUP_SYM /* SQL-2003-R */
+%token ROUTINE_SYM /* SQL-2003-N */
+%token ROWS_SYM /* SQL-2003-R */
+%token ROW_FORMAT_SYM
+%token ROW_SYM /* SQL-2003-R */
+%token ROW_COUNT_SYM /* SQL-2003-N */
+%token RTREE_SYM
+%token SAVEPOINT_SYM /* SQL-2003-R */
+%token SCHEDULE_SYM
+%token SCHEMA_NAME_SYM /* SQL-2003-N */
+%token SECOND_MICROSECOND_SYM
+%token SECOND_SYM /* SQL-2003-R */
+%token SECURITY_SYM /* SQL-2003-N */
+%token SELECT_SYM /* SQL-2003-R */
+%token SENSITIVE_SYM /* FUTURE-USE */
+%token SEPARATOR_SYM
+%token SERIALIZABLE_SYM /* SQL-2003-N */
+%token SERIAL_SYM
+%token SESSION_SYM /* SQL-2003-N */
+%token SERVER_SYM
+%token SERVER_OPTIONS
+%token SET /* SQL-2003-R */
+%token SET_VAR
+%token SHARE_SYM
+%token SHIFT_LEFT /* OPERATOR */
+%token SHIFT_RIGHT /* OPERATOR */
+%token SHOW
+%token SHUTDOWN
+%token SIGNAL_SYM /* SQL-2003-R */
+%token SIGNED_SYM
+%token SIMPLE_SYM /* SQL-2003-N */
+%token SLAVE
+%token SLAVES
+%token SLAVE_POS_SYM
+%token SLOW
+%token SMALLINT /* SQL-2003-R */
+%token SNAPSHOT_SYM
+%token SOCKET_SYM
+%token SOFT_SYM
+%token SONAME_SYM
+%token SOUNDS_SYM
+%token SOURCE_SYM
+%token SPATIAL_SYM
+%token SPECIFIC_SYM /* SQL-2003-R */
+%token SQLEXCEPTION_SYM /* SQL-2003-R */
+%token SQLSTATE_SYM /* SQL-2003-R */
+%token SQLWARNING_SYM /* SQL-2003-R */
+%token SQL_BIG_RESULT
+%token SQL_BUFFER_RESULT
+%token SQL_CACHE_SYM
+%token SQL_CALC_FOUND_ROWS
+%token SQL_NO_CACHE_SYM
+%token SQL_SMALL_RESULT
+%token SQL_SYM /* SQL-2003-R */
+%token SQL_THREAD
+%token SSL_SYM
+%token STARTING
+%token STARTS_SYM
+%token START_SYM /* SQL-2003-R */
+%token STATS_AUTO_RECALC_SYM
+%token STATS_PERSISTENT_SYM
+%token STATS_SAMPLE_PAGES_SYM
+%token STATUS_SYM
+%token STDDEV_SAMP_SYM /* SQL-2003-N */
+%token STD_SYM
+%token STOP_SYM
+%token STORAGE_SYM
+%token STRAIGHT_JOIN
+%token STRING_SYM
+%token SUBCLASS_ORIGIN_SYM /* SQL-2003-N */
+%token SUBDATE_SYM
+%token SUBJECT_SYM
+%token SUBPARTITIONS_SYM
+%token SUBPARTITION_SYM
+%token SUBSTRING /* SQL-2003-N */
+%token SUM_SYM /* SQL-2003-N */
+%token SUPER_SYM
+%token SUSPEND_SYM
+%token SWAPS_SYM
+%token SWITCHES_SYM
+%token SYSDATE
+%token TABLES
+%token TABLESPACE
+%token TABLE_REF_PRIORITY
+%token TABLE_STATS_SYM
+%token TABLE_SYM /* SQL-2003-R */
+%token TABLE_CHECKSUM_SYM
+%token TABLE_NAME_SYM /* SQL-2003-N */
+%token TEMPORARY /* SQL-2003-N */
+%token TEMPTABLE_SYM
+%token TERMINATED
+%token TEXT_STRING
+%token TEXT_SYM
+%token THAN_SYM
+%token THEN_SYM /* SQL-2003-R */
+%token TIMESTAMP /* SQL-2003-R */
+%token TIMESTAMP_ADD
+%token TIMESTAMP_DIFF
+%token TIME_SYM /* SQL-2003-R */
+%token TINYBLOB
+%token TINYINT
+%token TINYTEXT
+%token TO_SYM /* SQL-2003-R */
+%token TRAILING /* SQL-2003-R */
+%token TRANSACTION_SYM
+%token TRANSACTIONAL_SYM
+%token TRIGGERS_SYM
+%token TRIGGER_SYM /* SQL-2003-R */
+%token TRIM /* SQL-2003-N */
+%token TRUE_SYM /* SQL-2003-R */
+%token TRUNCATE_SYM
+%token TYPES_SYM
+%token TYPE_SYM /* SQL-2003-N */
+%token UDF_RETURNS_SYM
+%token ULONGLONG_NUM
+%token UNCOMMITTED_SYM /* SQL-2003-N */
+%token UNDEFINED_SYM
+%token UNDERSCORE_CHARSET
+%token UNDOFILE_SYM
+%token UNDO_BUFFER_SIZE_SYM
+%token UNDO_SYM /* FUTURE-USE */
+%token UNICODE_SYM
+%token UNINSTALL_SYM
+%token UNION_SYM /* SQL-2003-R */
+%token UNIQUE_SYM
+%token UNKNOWN_SYM /* SQL-2003-R */
+%token UNLOCK_SYM
+%token UNSIGNED
+%token UNTIL_SYM
+%token UPDATE_SYM /* SQL-2003-R */
+%token UPGRADE_SYM
+%token USAGE /* SQL-2003-N */
+%token USER /* SQL-2003-R */
+%token USER_STATS_SYM
+%token USE_FRM
+%token USE_SYM
+%token USING /* SQL-2003-R */
+%token UTC_DATE_SYM
+%token UTC_TIMESTAMP_SYM
+%token UTC_TIME_SYM
+%token VALUES /* SQL-2003-R */
+%token VALUE_SYM /* SQL-2003-R */
+%token VARBINARY
+%token VARCHAR /* SQL-2003-R */
+%token VARIABLES
+%token VARIANCE_SYM
+%token VARYING /* SQL-2003-R */
+%token VAR_SAMP_SYM
+%token VIA_SYM
+%token VIEW_SYM /* SQL-2003-N */
+%token VIRTUAL_SYM
+%token WAIT_SYM
+%token WARNINGS
+%token WEEK_SYM
+%token WEIGHT_STRING_SYM
+%token WHEN_SYM /* SQL-2003-R */
+%token WHERE /* SQL-2003-R */
+%token WHILE_SYM
+%token WITH /* SQL-2003-R */
+%token WITH_CUBE_SYM /* INTERNAL */
+%token WITH_ROLLUP_SYM /* INTERNAL */
+%token WORK_SYM /* SQL-2003-N */
+%token WRAPPER_SYM
+%token WRITE_SYM /* SQL-2003-N */
+%token X509_SYM
+%token XA_SYM
+%token XML_SYM
+%token XOR
+%token YEAR_MONTH_SYM
+%token YEAR_SYM /* SQL-2003-R */
+%token ZEROFILL
+
+%token IMPOSSIBLE_ACTION /* To avoid warning for yyerrlab1 */
+
+%left JOIN_SYM INNER_SYM STRAIGHT_JOIN CROSS LEFT RIGHT
+/* A dummy token to force the priority of table_ref production in a join. */
+%left TABLE_REF_PRIORITY
+%left SET_VAR
+%left OR_OR_SYM OR_SYM OR2_SYM
+%left XOR
+%left AND_SYM AND_AND_SYM
+%left BETWEEN_SYM CASE_SYM WHEN_SYM THEN_SYM ELSE
+%left EQ EQUAL_SYM GE GT_SYM LE LT NE IS LIKE REGEXP IN_SYM
+%left '|'
+%left '&'
+%left SHIFT_LEFT SHIFT_RIGHT
+%left '-' '+'
+%left '*' '/' '%' DIV_SYM MOD_SYM
+%left '^'
+%left NEG '~'
+%right NOT_SYM NOT2_SYM
+%right BINARY COLLATE_SYM
+%left INTERVAL_SYM
+
+%type <lex_str>
+ IDENT IDENT_QUOTED TEXT_STRING DECIMAL_NUM FLOAT_NUM NUM LONG_NUM
+ HEX_NUM HEX_STRING
+ LEX_HOSTNAME ULONGLONG_NUM field_ident select_alias ident ident_or_text
+ IDENT_sys TEXT_STRING_sys TEXT_STRING_literal
+ NCHAR_STRING opt_component key_cache_name
+ sp_opt_label BIN_NUM label_ident TEXT_STRING_filesystem ident_or_empty
+ opt_constraint constraint opt_ident opt_if_not_exists_ident
+
+%type <lex_str_ptr>
+ opt_table_alias
+
+%type <table>
+ table_ident table_ident_nodb references xid
+ table_ident_opt_wild create_like
+
+%type <simple_string>
+ remember_name remember_end opt_db text_or_password
+
+%type <string>
+ text_string hex_or_bin_String opt_gconcat_separator
+
+%type <lex_type> field_def
+
+%type <num>
+ type type_with_opt_collate int_type real_type order_dir lock_option
+ udf_type opt_if_exists opt_local opt_table_options table_options
+ table_option opt_if_not_exists create_or_replace opt_no_write_to_binlog
+ opt_temporary all_or_any opt_distinct
+ opt_ignore_leaves fulltext_options spatial_type union_option
+ union_opt select_derived_init transaction_access_mode_types
+ opt_natural_language_mode opt_query_expansion
+ opt_ev_status opt_ev_on_completion ev_on_completion opt_ev_comment
+ ev_alter_on_schedule_completion opt_ev_rename_to opt_ev_sql_stmt
+ optional_flush_tables_arguments opt_dyncol_type dyncol_type
+ opt_time_precision kill_type kill_option int_num
+ opt_default_time_precision
+
+/*
+ Bit field of MYSQL_START_TRANS_OPT_* flags.
+*/
+%type <num> opt_start_transaction_option_list
+%type <num> start_transaction_option_list
+%type <num> start_transaction_option
+
+%type <m_yes_no_unk>
+ opt_chain opt_release
+
+%type <m_fk_option>
+ delete_option
+
+%type <ulong_num>
+ ulong_num real_ulong_num merge_insert_types
+ ws_nweights
+ ws_level_flag_desc ws_level_flag_reverse ws_level_flags
+ opt_ws_levels ws_level_list ws_level_list_item ws_level_number
+ ws_level_range ws_level_list_or_range
+
+%type <ulonglong_number>
+ ulonglong_num real_ulonglong_num size_number
+
+%type <choice> choice
+
+%type <lock_type>
+ replace_lock_option opt_low_priority insert_lock_option load_data_lock
+
+%type <item>
+ literal text_literal insert_ident order_ident temporal_literal
+ simple_ident expr opt_expr opt_else sum_expr in_sum_expr
+ variable variable_aux bool_pri
+ predicate bit_expr
+ table_wild simple_expr udf_expr
+ expr_or_default set_expr_or_default
+ param_marker geometry_function
+ signed_literal now_or_signed_literal opt_escape
+ sp_opt_default
+ simple_ident_nospvar simple_ident_q
+ field_or_var limit_option
+ part_func_expr
+ function_call_keyword
+ function_call_nonkeyword
+ function_call_generic
+ function_call_conflict kill_expr
+ signal_allowed_expr
+ simple_target_specification
+ condition_number
+
+%type <item_num>
+ NUM_literal
+
+%type <item_list>
+ expr_list opt_udf_expr_list udf_expr_list when_list
+ ident_list ident_list_arg opt_expr_list
+
+%type <var_type>
+ option_type opt_var_type opt_var_ident_type
+
+%type <key_type>
+ normal_key_type opt_unique constraint_key_type fulltext spatial
+
+%type <key_alg>
+ btree_or_rtree
+
+%type <string_list>
+ using_list opt_use_partition use_partition
+
+%type <key_part>
+ key_part
+
+%type <table_list>
+ join_table_list join_table
+ table_factor table_ref esc_table_ref
+ select_derived derived_table_list
+ select_derived_union
+
+%type <date_time_type> date_time_type;
+%type <interval> interval
+
+%type <interval_time_st> interval_time_stamp
+
+%type <db_type> storage_engines known_storage_engines
+
+%type <row_type> row_types
+
+%type <tx_isolation> isolation_types
+
+%type <ha_rkey_mode> handler_rkey_mode
+
+%type <cast_type> cast_type
+
+%type <symbol> keyword keyword_sp
+
+%type <lex_user> user grant_user grant_role user_or_role current_role
+ admin_option_for_role user_maybe_role
+
+%type <charset>
+ opt_collate
+ charset_name
+ charset_name_or_default
+ old_or_new_charset_name
+ old_or_new_charset_name_or_default
+ collation_name
+ collation_name_or_default
+ opt_load_data_charset
+ UNDERSCORE_CHARSET
+
+%type <variable> internal_variable_name
+
+%type <select_lex> subselect
+ get_select_lex query_specification
+ query_expression_body
+
+%type <boolfunc2creator> comp_op
+
+%type <dyncol_def> dyncall_create_element
+
+%type <dyncol_def_list> dyncall_create_list
+
+%type <NONE>
+ query verb_clause create change select do drop insert replace insert2
+ insert_values update delete truncate rename
+ show describe load alter optimize keycache preload flush
+ reset purge begin commit rollback savepoint release
+ slave master_def master_defs master_file_def slave_until_opts
+ repair analyze opt_with_admin opt_with_admin_option
+ analyze_table_list analyze_table_elem_spec
+ opt_persistent_stat_clause persistent_stat_spec
+ persistent_column_stat_spec persistent_index_stat_spec
+ table_column_list table_index_list table_index_name
+ check start checksum
+ field_list field_list_item field_spec kill column_def key_def
+ keycache_list keycache_list_or_parts assign_to_keycache
+ assign_to_keycache_parts
+ preload_list preload_list_or_parts preload_keys preload_keys_parts
+ select_item_list select_item values_list no_braces
+ opt_limit_clause delete_limit_clause fields opt_values values
+ procedure_list procedure_list2 procedure_item
+ handler
+ opt_precision opt_ignore opt_column opt_restrict
+ grant revoke set lock unlock string_list field_options field_option
+ field_opt_list opt_binary ascii unicode table_lock_list table_lock
+ ref_list opt_match_clause opt_on_update_delete use
+ opt_delete_options opt_delete_option varchar nchar nvarchar
+ opt_outer table_list table_name table_alias_ref_list table_alias_ref
+ opt_place
+ opt_attribute opt_attribute_list attribute column_list column_list_id
+ opt_column_list grant_privileges grant_ident grant_list grant_option
+ object_privilege object_privilege_list user_list user_and_role_list
+ rename_list
+ clear_privileges flush_options flush_option table_or_tables
+ opt_flush_lock flush_lock flush_options_list
+ equal optional_braces
+ opt_mi_check_type opt_to mi_check_types
+ table_to_table_list table_to_table opt_table_list opt_as
+ handler_rkey_function handler_read_or_scan
+ single_multi table_wild_list table_wild_one opt_wild
+ union_clause union_list
+ precision subselect_start opt_and charset
+ subselect_end select_var_list select_var_list_init help
+ field_length opt_field_length
+ opt_extended_describe shutdown
+ prepare prepare_src execute deallocate
+ statement sp_suid
+ sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa
+ opt_field_or_var_spec fields_or_vars opt_load_data_set_spec
+ view_algorithm view_or_trigger_or_sp_or_event
+ definer_tail no_definer_tail
+ view_suid view_tail view_list_opt view_list view_select
+ view_check_option trigger_tail sp_tail sf_tail udf_tail event_tail
+ install uninstall partition_entry binlog_base64_event
+ init_key_options normal_key_options normal_key_opts all_key_opt
+ spatial_key_options fulltext_key_options normal_key_opt
+ fulltext_key_opt spatial_key_opt fulltext_key_opts spatial_key_opts
+ keep_gcc_happy
+ key_using_alg
+ part_column_list
+ server_def server_options_list server_option
+ definer_opt no_definer definer get_diagnostics
+ parse_vcol_expr vcol_opt_specifier vcol_opt_attribute
+ vcol_opt_attribute_list vcol_attribute
+ explainable_command
+END_OF_INPUT
+
+%type <NONE> call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt
+%type <NONE> sp_proc_stmt_statement sp_proc_stmt_return
+%type <NONE> sp_proc_stmt_if
+%type <NONE> sp_labeled_control sp_proc_stmt_unlabeled
+%type <NONE> sp_labeled_block sp_unlabeled_block
+%type <NONE> sp_proc_stmt_leave
+%type <NONE> sp_proc_stmt_iterate
+%type <NONE> sp_proc_stmt_open sp_proc_stmt_fetch sp_proc_stmt_close
+%type <NONE> case_stmt_specification simple_case_stmt searched_case_stmt
+
+%type <num> sp_decl_idents sp_opt_inout sp_handler_type sp_hcond_list
+%type <spcondvalue> sp_cond sp_hcond sqlstate signal_value opt_signal_value
+%type <spblock> sp_decls sp_decl
+%type <lex> sp_cursor_stmt
+%type <spname> sp_name
+%type <index_hint> index_hint_type
+%type <num> index_hint_clause normal_join inner_join
+%type <filetype> data_or_xml
+
+%type <NONE> signal_stmt resignal_stmt
+%type <diag_condition_item_name> signal_condition_information_item_name
+
+%type <diag_area> which_area;
+%type <diag_info> diagnostics_information;
+%type <stmt_info_item> statement_information_item;
+%type <stmt_info_item_name> statement_information_item_name;
+%type <stmt_info_list> statement_information;
+%type <cond_info_item> condition_information_item;
+%type <cond_info_item_name> condition_information_item_name;
+%type <cond_info_list> condition_information;
+
+%type <NONE>
+ '-' '+' '*' '/' '%' '(' ')'
+ ',' '!' '{' '}' '&' '|' AND_SYM OR_SYM OR_OR_SYM BETWEEN_SYM CASE_SYM
+ THEN_SYM WHEN_SYM DIV_SYM MOD_SYM OR2_SYM AND_AND_SYM DELETE_SYM
+
+%type <is_not_empty> opt_union_order_or_limit
+
+%type <NONE> ROLE_SYM
+
+%%
+
+
+/*
+ Indentation of grammar rules:
+
+rule: <-- starts at col 1
+ rule1a rule1b rule1c <-- starts at col 11
+ { <-- starts at col 11
+ code <-- starts at col 13, indentation is 2 spaces
+ }
+ | rule2a rule2b
+ {
+ code
+ }
+ ; <-- on a line by itself, starts at col 9
+
+ Also, please do not use any <TAB>, but spaces.
+ Having a uniform indentation in this file helps
+ code reviews, patches, merges, and make maintenance easier.
+ Tip: grep [[:cntrl:]] sql_yacc.yy
+ Thanks.
+*/
+
+query:
+ END_OF_INPUT
+ {
+ if (!thd->bootstrap &&
+ (!(thd->lex->select_lex.options & OPTION_FOUND_COMMENT)))
+ {
+ my_message(ER_EMPTY_QUERY, ER(ER_EMPTY_QUERY), MYF(0));
+ MYSQL_YYABORT;
+ }
+ thd->lex->sql_command= SQLCOM_EMPTY_QUERY;
+ YYLIP->found_semicolon= NULL;
+ }
+ | verb_clause
+ {
+ Lex_input_stream *lip = YYLIP;
+
+ if ((thd->client_capabilities & CLIENT_MULTI_QUERIES) &&
+ lip->multi_statements &&
+ ! lip->eof())
+ {
+ /*
+ We found a well formed query, and multi queries are allowed:
+ - force the parser to stop after the ';'
+ - mark the start of the next query for the next invocation
+ of the parser.
+ */
+ lip->next_state= MY_LEX_END;
+ lip->found_semicolon= lip->get_ptr();
+ }
+ else
+ {
+ /* Single query, terminated. */
+ lip->found_semicolon= NULL;
+ }
+ }
+ ';'
+ opt_end_of_input
+ | verb_clause END_OF_INPUT
+ {
+ /* Single query, not terminated. */
+ YYLIP->found_semicolon= NULL;
+ }
+ ;
+
+opt_end_of_input:
+ /* empty */
+ | END_OF_INPUT
+ ;
+
+verb_clause:
+ statement
+ | begin
+ ;
+
+/* Verb clauses, except begin */
+statement:
+ alter
+ | analyze
+ | binlog_base64_event
+ | call
+ | change
+ | check
+ | checksum
+ | commit
+ | create
+ | deallocate
+ | delete
+ | describe
+ | do
+ | drop
+ | execute
+ | flush
+ | get_diagnostics
+ | grant
+ | handler
+ | help
+ | insert
+ | install
+ | keep_gcc_happy
+ | keycache
+ | kill
+ | load
+ | lock
+ | optimize
+ | parse_vcol_expr
+ | partition_entry
+ | preload
+ | prepare
+ | purge
+ | release
+ | rename
+ | repair
+ | replace
+ | reset
+ | resignal_stmt
+ | revoke
+ | rollback
+ | savepoint
+ | select
+ | set
+ | signal_stmt
+ | show
+ | shutdown
+ | slave
+ | start
+ | truncate
+ | uninstall
+ | unlock
+ | update
+ | use
+ | xa
+ ;
+
+deallocate:
+ deallocate_or_drop PREPARE_SYM ident
+ {
+ LEX *lex= thd->lex;
+ lex->sql_command= SQLCOM_DEALLOCATE_PREPARE;
+ lex->prepared_stmt_name= $3;
+ }
+ ;
+
+deallocate_or_drop:
+ DEALLOCATE_SYM
+ | DROP
+ ;
+
+prepare:
+ PREPARE_SYM ident FROM prepare_src
+ {
+ LEX *lex= thd->lex;
+ lex->sql_command= SQLCOM_PREPARE;
+ lex->prepared_stmt_name= $2;
+ }
+ ;
+
+prepare_src:
+ TEXT_STRING_sys
+ {
+ LEX *lex= thd->lex;
+ lex->prepared_stmt_code= $1;
+ lex->prepared_stmt_code_is_varref= FALSE;
+ }
+ | '@' ident_or_text
+ {
+ LEX *lex= thd->lex;
+ lex->prepared_stmt_code= $2;
+ lex->prepared_stmt_code_is_varref= TRUE;
+ }
+ ;
+
+execute:
+ EXECUTE_SYM ident
+ {
+ LEX *lex= thd->lex;
+ lex->sql_command= SQLCOM_EXECUTE;
+ lex->prepared_stmt_name= $2;
+ }
+ execute_using
+ {}
+ ;
+
+execute_using:
+ /* nothing */
+ | USING execute_var_list
+ ;
+
+execute_var_list:
+ execute_var_list ',' execute_var_ident
+ | execute_var_ident
+ ;
+
+execute_var_ident:
+ '@' ident_or_text
+ {
+ LEX *lex=Lex;
+ LEX_STRING *lexstr= (LEX_STRING*)sql_memdup(&$2, sizeof(LEX_STRING));
+ if (!lexstr || lex->prepared_stmt_params.push_back(lexstr))
+ MYSQL_YYABORT;
+ }
+ ;
+
+/* help */
+
+help:
+ HELP_SYM
+ {
+ if (Lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "HELP");
+ MYSQL_YYABORT;
+ }
+ }
+ ident_or_text
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_HELP;
+ lex->help_arg= $3.str;
+ }
+ ;
+
+/* change master */
+
+change:
+ CHANGE MASTER_SYM optional_connection_name TO_SYM
+ {
+ Lex->sql_command = SQLCOM_CHANGE_MASTER;
+ }
+ master_defs
+ {}
+ ;
+
+master_defs:
+ master_def
+ | master_defs ',' master_def
+ ;
+
+master_def:
+ MASTER_HOST_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.host = $3.str;
+ }
+ | MASTER_USER_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.user = $3.str;
+ }
+ | MASTER_PASSWORD_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.password = $3.str;
+ }
+ | MASTER_PORT_SYM EQ ulong_num
+ {
+ Lex->mi.port = $3;
+ }
+ | MASTER_CONNECT_RETRY_SYM EQ ulong_num
+ {
+ Lex->mi.connect_retry = $3;
+ }
+ | MASTER_SSL_SYM EQ ulong_num
+ {
+ Lex->mi.ssl= $3 ?
+ LEX_MASTER_INFO::LEX_MI_ENABLE : LEX_MASTER_INFO::LEX_MI_DISABLE;
+ }
+ | MASTER_SSL_CA_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.ssl_ca= $3.str;
+ }
+ | MASTER_SSL_CAPATH_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.ssl_capath= $3.str;
+ }
+ | MASTER_SSL_CERT_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.ssl_cert= $3.str;
+ }
+ | MASTER_SSL_CIPHER_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.ssl_cipher= $3.str;
+ }
+ | MASTER_SSL_KEY_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.ssl_key= $3.str;
+ }
+ | MASTER_SSL_VERIFY_SERVER_CERT_SYM EQ ulong_num
+ {
+ Lex->mi.ssl_verify_server_cert= $3 ?
+ LEX_MASTER_INFO::LEX_MI_ENABLE : LEX_MASTER_INFO::LEX_MI_DISABLE;
+ }
+ | MASTER_SSL_CRL_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.ssl_crl= $3.str;
+ }
+ | MASTER_SSL_CRLPATH_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.ssl_crlpath= $3.str;
+ }
+
+ | MASTER_HEARTBEAT_PERIOD_SYM EQ NUM_literal
+ {
+ Lex->mi.heartbeat_period= (float) $3->val_real();
+ if (Lex->mi.heartbeat_period > SLAVE_MAX_HEARTBEAT_PERIOD ||
+ Lex->mi.heartbeat_period < 0.0)
+ {
+ const char format[]= "%d";
+ char buf[4*sizeof(SLAVE_MAX_HEARTBEAT_PERIOD) + sizeof(format)];
+ sprintf(buf, format, SLAVE_MAX_HEARTBEAT_PERIOD);
+ my_error(ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE, MYF(0), buf);
+ MYSQL_YYABORT;
+ }
+ if (Lex->mi.heartbeat_period > slave_net_timeout)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX,
+ ER(ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX));
+ }
+ if (Lex->mi.heartbeat_period < 0.001)
+ {
+ if (Lex->mi.heartbeat_period != 0.0)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN,
+ ER(ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN));
+ Lex->mi.heartbeat_period= 0.0;
+ }
+ Lex->mi.heartbeat_opt= LEX_MASTER_INFO::LEX_MI_DISABLE;
+ }
+ Lex->mi.heartbeat_opt= LEX_MASTER_INFO::LEX_MI_ENABLE;
+ }
+ | IGNORE_SERVER_IDS_SYM EQ '(' ignore_server_id_list ')'
+ {
+ Lex->mi.repl_ignore_server_ids_opt= LEX_MASTER_INFO::LEX_MI_ENABLE;
+ }
+ |
+ master_file_def
+ ;
+
+ignore_server_id_list:
+ /* Empty */
+ | ignore_server_id
+ | ignore_server_id_list ',' ignore_server_id
+ ;
+
+ignore_server_id:
+ ulong_num
+ {
+ insert_dynamic(&Lex->mi.repl_ignore_server_ids, (uchar*) &($1));
+ }
+
+master_file_def:
+ MASTER_LOG_FILE_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.log_file_name = $3.str;
+ }
+ | MASTER_LOG_POS_SYM EQ ulonglong_num
+ {
+ Lex->mi.pos = $3;
+ /*
+ If the user specified a value < BIN_LOG_HEADER_SIZE, adjust it
+ instead of causing subsequent errors.
+ We need to do it in this file, because only there we know that
+ MASTER_LOG_POS has been explicitely specified. On the contrary
+ in change_master() (sql_repl.cc) we cannot distinguish between 0
+ (MASTER_LOG_POS explicitely specified as 0) and 0 (unspecified),
+ whereas we want to distinguish (specified 0 means "read the binlog
+ from 0" (4 in fact), unspecified means "don't change the position
+ (keep the preceding value)").
+ */
+ Lex->mi.pos= MY_MAX(BIN_LOG_HEADER_SIZE, Lex->mi.pos);
+ }
+ | RELAY_LOG_FILE_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.relay_log_name = $3.str;
+ }
+ | RELAY_LOG_POS_SYM EQ ulong_num
+ {
+ Lex->mi.relay_log_pos = $3;
+ /* Adjust if < BIN_LOG_HEADER_SIZE (same comment as Lex->mi.pos) */
+ Lex->mi.relay_log_pos= MY_MAX(BIN_LOG_HEADER_SIZE, Lex->mi.relay_log_pos);
+ }
+ | MASTER_USE_GTID_SYM EQ CURRENT_POS_SYM
+ {
+ if (Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid");
+ MYSQL_YYABORT;
+ }
+ Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_CURRENT_POS;
+ }
+ ;
+ | MASTER_USE_GTID_SYM EQ SLAVE_POS_SYM
+ {
+ if (Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid");
+ MYSQL_YYABORT;
+ }
+ Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_SLAVE_POS;
+ }
+ ;
+ | MASTER_USE_GTID_SYM EQ NO_SYM
+ {
+ if (Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid");
+ MYSQL_YYABORT;
+ }
+ Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_NO;
+ }
+ ;
+
+optional_connection_name:
+ /* empty */
+ {
+ LEX *lex= thd->lex;
+ lex->mi.connection_name= thd->variables.default_master_connection;
+ }
+ | connection_name;
+ ;
+
+connection_name:
+ TEXT_STRING_sys
+ {
+ Lex->mi.connection_name= $1;
+#ifdef HAVE_REPLICATION
+ if (check_master_connection_name(&$1))
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "MASTER_CONNECTION_NAME");
+ MYSQL_YYABORT;
+ }
+#endif
+ }
+
+/* create a table */
+
+create:
+ create_or_replace opt_table_options TABLE_SYM opt_if_not_exists table_ident
+ {
+ LEX *lex= thd->lex;
+ lex->sql_command= SQLCOM_CREATE_TABLE;
+ if ($1 && $4)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "OR REPLACE", "IF NOT EXISTS");
+ MYSQL_YYABORT;
+ }
+ if (!lex->select_lex.add_table_to_list(thd, $5, NULL,
+ TL_OPTION_UPDATING,
+ TL_WRITE, MDL_EXCLUSIVE))
+ MYSQL_YYABORT;
+ lex->alter_info.reset();
+ lex->col_list.empty();
+ lex->change=NullS;
+ bzero((char*) &lex->create_info,sizeof(lex->create_info));
+ /*
+ For CREATE TABLE we should not open the table even if it exists.
+ If the table exists, we should either not create it or replace it
+ */
+ lex->query_tables->open_strategy= TABLE_LIST::OPEN_STUB;
+ lex->create_info.options= ($1 | $2 | $4);
+ lex->create_info.default_table_charset= NULL;
+ lex->name.str= 0;
+ lex->name.length= 0;
+ lex->create_last_non_select_table= lex->last_table();
+ }
+ create_body
+ {
+ LEX *lex= thd->lex;
+ lex->current_select= &lex->select_lex;
+ if ((lex->create_info.used_fields & HA_CREATE_USED_ENGINE) &&
+ !lex->create_info.db_type)
+ {
+ lex->create_info.db_type= ha_default_handlerton(thd);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_USING_OTHER_HANDLER,
+ ER(ER_WARN_USING_OTHER_HANDLER),
+ hton_name(lex->create_info.db_type)->str,
+ $5->table.str);
+ }
+ create_table_set_open_action_and_adjust_tables(lex);
+ }
+ | CREATE opt_unique INDEX_SYM opt_if_not_exists ident key_alg ON table_ident
+ {
+ if (add_create_index_prepare(Lex, $8))
+ MYSQL_YYABORT;
+ }
+ '(' key_list ')' normal_key_options
+ {
+ if (add_create_index(Lex, $2, $5))
+ MYSQL_YYABORT;
+ }
+ opt_index_lock_algorithm { }
+ | CREATE fulltext INDEX_SYM opt_if_not_exists ident init_key_options ON
+ table_ident
+ {
+ if (add_create_index_prepare(Lex, $8))
+ MYSQL_YYABORT;
+ }
+ '(' key_list ')' fulltext_key_options
+ {
+ if (add_create_index(Lex, $2, $5))
+ MYSQL_YYABORT;
+ }
+ opt_index_lock_algorithm { }
+ | CREATE spatial INDEX_SYM opt_if_not_exists ident init_key_options ON
+ table_ident
+ {
+ if (add_create_index_prepare(Lex, $8))
+ MYSQL_YYABORT;
+ }
+ '(' key_list ')' spatial_key_options
+ {
+ if (add_create_index(Lex, $2, $5))
+ MYSQL_YYABORT;
+ }
+ opt_index_lock_algorithm { }
+ | CREATE DATABASE opt_if_not_exists ident
+ {
+ Lex->create_info.default_table_charset= NULL;
+ Lex->create_info.used_fields= 0;
+ }
+ opt_create_database_options
+ {
+ LEX *lex=Lex;
+ lex->sql_command=SQLCOM_CREATE_DB;
+ lex->name= $4;
+ lex->create_info.options=$3;
+ }
+ | create_or_replace
+ {
+ Lex->create_view_mode= ($1 == 0 ? VIEW_CREATE_NEW :
+ VIEW_CREATE_OR_REPLACE);
+ Lex->create_view_algorithm= DTYPE_ALGORITHM_UNDEFINED;
+ Lex->create_view_suid= TRUE;
+ }
+ view_or_trigger_or_sp_or_event
+ {
+ if ($1 && Lex->sql_command != SQLCOM_CREATE_VIEW)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "OR REPLACE",
+ "TRIGGERS / SP / EVENT");
+ MYSQL_YYABORT;
+ }
+ }
+ | CREATE USER clear_privileges grant_list
+ {
+ Lex->sql_command = SQLCOM_CREATE_USER;
+ }
+ | CREATE ROLE_SYM clear_privileges role_list opt_with_admin
+ {
+ Lex->sql_command = SQLCOM_CREATE_ROLE;
+ }
+ | CREATE LOGFILE_SYM GROUP_SYM logfile_group_info
+ {
+ Lex->alter_tablespace_info->ts_cmd_type= CREATE_LOGFILE_GROUP;
+ }
+ | CREATE TABLESPACE tablespace_info
+ {
+ Lex->alter_tablespace_info->ts_cmd_type= CREATE_TABLESPACE;
+ }
+ | CREATE server_def
+ {
+ Lex->sql_command= SQLCOM_CREATE_SERVER;
+ }
+ ;
+
+server_def:
+ SERVER_SYM
+ ident_or_text
+ FOREIGN DATA_SYM WRAPPER_SYM
+ ident_or_text
+ OPTIONS_SYM '(' server_options_list ')'
+ {
+ Lex->server_options.server_name= $2.str;
+ Lex->server_options.server_name_length= $2.length;
+ Lex->server_options.scheme= $6.str;
+ }
+ ;
+
+server_options_list:
+ server_option
+ | server_options_list ',' server_option
+ ;
+
+server_option:
+ USER TEXT_STRING_sys
+ {
+ Lex->server_options.username= $2.str;
+ }
+ | HOST_SYM TEXT_STRING_sys
+ {
+ Lex->server_options.host= $2.str;
+ }
+ | DATABASE TEXT_STRING_sys
+ {
+ Lex->server_options.db= $2.str;
+ }
+ | OWNER_SYM TEXT_STRING_sys
+ {
+ Lex->server_options.owner= $2.str;
+ }
+ | PASSWORD TEXT_STRING_sys
+ {
+ Lex->server_options.password= $2.str;
+ }
+ | SOCKET_SYM TEXT_STRING_sys
+ {
+ Lex->server_options.socket= $2.str;
+ }
+ | PORT_SYM ulong_num
+ {
+ Lex->server_options.port= $2;
+ }
+ ;
+
+event_tail:
+ remember_name EVENT_SYM opt_if_not_exists sp_name
+ {
+ LEX *lex=Lex;
+
+ lex->stmt_definition_begin= $1;
+ lex->create_info.options= $3;
+ if (!(lex->event_parse_data= Event_parse_data::new_instance(thd)))
+ MYSQL_YYABORT;
+ lex->event_parse_data->identifier= $4;
+ lex->event_parse_data->on_completion=
+ Event_parse_data::ON_COMPLETION_DROP;
+
+ lex->sql_command= SQLCOM_CREATE_EVENT;
+ /* We need that for disallowing subqueries */
+ }
+ ON SCHEDULE_SYM ev_schedule_time
+ opt_ev_on_completion
+ opt_ev_status
+ opt_ev_comment
+ DO_SYM ev_sql_stmt
+ {
+ /*
+ sql_command is set here because some rules in ev_sql_stmt
+ can overwrite it
+ */
+ Lex->sql_command= SQLCOM_CREATE_EVENT;
+ }
+ ;
+
+ev_schedule_time:
+ EVERY_SYM expr interval
+ {
+ Lex->event_parse_data->item_expression= $2;
+ Lex->event_parse_data->interval= $3;
+ }
+ ev_starts
+ ev_ends
+ | AT_SYM expr
+ {
+ Lex->event_parse_data->item_execute_at= $2;
+ }
+ ;
+
+opt_ev_status:
+ /* empty */ { $$= 0; }
+ | ENABLE_SYM
+ {
+ Lex->event_parse_data->status= Event_parse_data::ENABLED;
+ Lex->event_parse_data->status_changed= true;
+ $$= 1;
+ }
+ | DISABLE_SYM ON SLAVE
+ {
+ Lex->event_parse_data->status= Event_parse_data::SLAVESIDE_DISABLED;
+ Lex->event_parse_data->status_changed= true;
+ $$= 1;
+ }
+ | DISABLE_SYM
+ {
+ Lex->event_parse_data->status= Event_parse_data::DISABLED;
+ Lex->event_parse_data->status_changed= true;
+ $$= 1;
+ }
+ ;
+
+ev_starts:
+ /* empty */
+ {
+ Item *item= new (thd->mem_root) Item_func_now_local(0);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ Lex->event_parse_data->item_starts= item;
+ }
+ | STARTS_SYM expr
+ {
+ Lex->event_parse_data->item_starts= $2;
+ }
+ ;
+
+ev_ends:
+ /* empty */
+ | ENDS_SYM expr
+ {
+ Lex->event_parse_data->item_ends= $2;
+ }
+ ;
+
+opt_ev_on_completion:
+ /* empty */ { $$= 0; }
+ | ev_on_completion
+ ;
+
+ev_on_completion:
+ ON COMPLETION_SYM PRESERVE_SYM
+ {
+ Lex->event_parse_data->on_completion=
+ Event_parse_data::ON_COMPLETION_PRESERVE;
+ $$= 1;
+ }
+ | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM
+ {
+ Lex->event_parse_data->on_completion=
+ Event_parse_data::ON_COMPLETION_DROP;
+ $$= 1;
+ }
+ ;
+
+opt_ev_comment:
+ /* empty */ { $$= 0; }
+ | COMMENT_SYM TEXT_STRING_sys
+ {
+ Lex->comment= Lex->event_parse_data->comment= $2;
+ $$= 1;
+ }
+ ;
+
+ev_sql_stmt:
+ {
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+
+ /*
+ This stops the following :
+ - CREATE EVENT ... DO CREATE EVENT ...;
+ - ALTER EVENT ... DO CREATE EVENT ...;
+ - CREATE EVENT ... DO ALTER EVENT DO ....;
+ - CREATE PROCEDURE ... BEGIN CREATE EVENT ... END|
+ This allows:
+ - CREATE EVENT ... DO DROP EVENT yyy;
+ - CREATE EVENT ... DO ALTER EVENT yyy;
+ (the nested ALTER EVENT can have anything but DO clause)
+ - ALTER EVENT ... DO ALTER EVENT yyy;
+ (the nested ALTER EVENT can have anything but DO clause)
+ - ALTER EVENT ... DO DROP EVENT yyy;
+ - CREATE PROCEDURE ... BEGIN ALTER EVENT ... END|
+ (the nested ALTER EVENT can have anything but DO clause)
+ - CREATE PROCEDURE ... BEGIN DROP EVENT ... END|
+ */
+ if (lex->sphead)
+ {
+ my_error(ER_EVENT_RECURSION_FORBIDDEN, MYF(0));
+ MYSQL_YYABORT;
+ }
+
+ if (!(lex->sphead= new sp_head()))
+ MYSQL_YYABORT;
+
+ lex->sphead->reset_thd_mem_root(thd);
+ lex->sphead->init(lex);
+ lex->sphead->init_sp_name(thd, lex->event_parse_data->identifier);
+
+ lex->sphead->m_type= TYPE_ENUM_PROCEDURE;
+
+ bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
+ lex->sphead->m_chistics= &lex->sp_chistics;
+
+ lex->sphead->set_body_start(thd, lip->get_cpp_ptr());
+ }
+ ev_sql_stmt_inner
+ {
+ LEX *lex= thd->lex;
+
+ /* return back to the original memory root ASAP */
+ lex->sphead->set_stmt_end(thd);
+ lex->sphead->restore_thd_mem_root(thd);
+
+ lex->sp_chistics.suid= SP_IS_SUID; //always the definer!
+
+ lex->event_parse_data->body_changed= TRUE;
+ }
+ ;
+
+ev_sql_stmt_inner:
+ sp_proc_stmt_statement
+ | sp_proc_stmt_return
+ | sp_proc_stmt_if
+ | case_stmt_specification
+ | sp_labeled_block
+ | sp_unlabeled_block
+ | sp_labeled_control
+ | sp_proc_stmt_unlabeled
+ | sp_proc_stmt_leave
+ | sp_proc_stmt_iterate
+ | sp_proc_stmt_open
+ | sp_proc_stmt_fetch
+ | sp_proc_stmt_close
+ ;
+
+clear_privileges:
+ /* Nothing */
+ {
+ LEX *lex=Lex;
+ lex->users_list.empty();
+ lex->columns.empty();
+ lex->grant= lex->grant_tot_col= 0;
+ lex->all_privileges= 0;
+ lex->select_lex.db= 0;
+ lex->ssl_type= SSL_TYPE_NOT_SPECIFIED;
+ lex->ssl_cipher= lex->x509_subject= lex->x509_issuer= 0;
+ bzero((char *)&(lex->mqh),sizeof(lex->mqh));
+ }
+ ;
+
+sp_name:
+ ident '.' ident
+ {
+ if (!$1.str || check_db_name(&$1))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ if (check_routine_name(&$3))
+ {
+ MYSQL_YYABORT;
+ }
+ $$= new sp_name($1, $3, true);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->init_qname(thd);
+ }
+ | ident
+ {
+ LEX *lex= thd->lex;
+ LEX_STRING db;
+ if (check_routine_name(&$1))
+ {
+ MYSQL_YYABORT;
+ }
+ if (lex->copy_db_to(&db.str, &db.length))
+ MYSQL_YYABORT;
+ $$= new sp_name(db, $1, false);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->init_qname(thd);
+ }
+ ;
+
+sp_a_chistics:
+ /* Empty */ {}
+ | sp_a_chistics sp_chistic {}
+ ;
+
+sp_c_chistics:
+ /* Empty */ {}
+ | sp_c_chistics sp_c_chistic {}
+ ;
+
+/* Characteristics for both create and alter */
+sp_chistic:
+ COMMENT_SYM TEXT_STRING_sys
+ { Lex->sp_chistics.comment= $2; }
+ | LANGUAGE_SYM SQL_SYM
+ { /* Just parse it, we only have one language for now. */ }
+ | NO_SYM SQL_SYM
+ { Lex->sp_chistics.daccess= SP_NO_SQL; }
+ | CONTAINS_SYM SQL_SYM
+ { Lex->sp_chistics.daccess= SP_CONTAINS_SQL; }
+ | READS_SYM SQL_SYM DATA_SYM
+ { Lex->sp_chistics.daccess= SP_READS_SQL_DATA; }
+ | MODIFIES_SYM SQL_SYM DATA_SYM
+ { Lex->sp_chistics.daccess= SP_MODIFIES_SQL_DATA; }
+ | sp_suid
+ {}
+ ;
+
+/* Create characteristics */
+sp_c_chistic:
+ sp_chistic { }
+ | DETERMINISTIC_SYM { Lex->sp_chistics.detistic= TRUE; }
+ | not DETERMINISTIC_SYM { Lex->sp_chistics.detistic= FALSE; }
+ ;
+
+sp_suid:
+ SQL_SYM SECURITY_SYM DEFINER_SYM
+ {
+ Lex->sp_chistics.suid= SP_IS_SUID;
+ }
+ | SQL_SYM SECURITY_SYM INVOKER_SYM
+ {
+ Lex->sp_chistics.suid= SP_IS_NOT_SUID;
+ }
+ ;
+
+call:
+ CALL_SYM sp_name
+ {
+ LEX *lex = Lex;
+
+ lex->sql_command= SQLCOM_CALL;
+ lex->spname= $2;
+ lex->value_list.empty();
+ sp_add_used_routine(lex, thd, $2, TYPE_ENUM_PROCEDURE);
+ }
+ opt_sp_cparam_list {}
+ ;
+
+/* CALL parameters */
+opt_sp_cparam_list:
+ /* Empty */
+ | '(' opt_sp_cparams ')'
+ ;
+
+opt_sp_cparams:
+ /* Empty */
+ | sp_cparams
+ ;
+
+sp_cparams:
+ sp_cparams ',' expr
+ {
+ Lex->value_list.push_back($3);
+ }
+ | expr
+ {
+ Lex->value_list.push_back($1);
+ }
+ ;
+
+/* Stored FUNCTION parameter declaration list */
+sp_fdparam_list:
+ /* Empty */
+ | sp_fdparams
+ ;
+
+sp_fdparams:
+ sp_fdparams ',' sp_fdparam
+ | sp_fdparam
+ ;
+
+sp_init_param:
+ /* Empty */
+ {
+ LEX *lex= Lex;
+
+ lex->length= 0;
+ lex->dec= 0;
+ lex->type= 0;
+
+ lex->default_value= 0;
+ lex->on_update_value= 0;
+
+ lex->comment= null_lex_str;
+ lex->charset= NULL;
+
+ lex->interval_list.empty();
+ lex->uint_geom_type= 0;
+ lex->vcol_info= 0;
+ }
+ ;
+
+sp_fdparam:
+ ident sp_init_param type_with_opt_collate
+ {
+ LEX *lex= Lex;
+ sp_pcontext *spc= lex->spcont;
+
+ if (spc->find_variable($1, TRUE))
+ {
+ my_error(ER_SP_DUP_PARAM, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+
+ sp_variable *spvar= spc->add_variable(thd,
+ $1,
+ (enum enum_field_types) $3,
+ sp_variable::MODE_IN);
+
+ if (lex->sphead->fill_field_definition(thd, lex,
+ (enum enum_field_types) $3,
+ &spvar->field_def))
+ {
+ MYSQL_YYABORT;
+ }
+ spvar->field_def.field_name= spvar->name.str;
+ spvar->field_def.pack_flag |= FIELDFLAG_MAYBE_NULL;
+ }
+ ;
+
+/* Stored PROCEDURE parameter declaration list */
+sp_pdparam_list:
+ /* Empty */
+ | sp_pdparams
+ ;
+
+sp_pdparams:
+ sp_pdparams ',' sp_pdparam
+ | sp_pdparam
+ ;
+
+sp_pdparam:
+ sp_opt_inout sp_init_param ident type_with_opt_collate
+ {
+ LEX *lex= Lex;
+ sp_pcontext *spc= lex->spcont;
+
+ if (spc->find_variable($3, TRUE))
+ {
+ my_error(ER_SP_DUP_PARAM, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ sp_variable *spvar= spc->add_variable(thd,
+ $3,
+ (enum enum_field_types) $4,
+ (sp_variable::enum_mode) $1);
+
+ if (lex->sphead->fill_field_definition(thd, lex,
+ (enum enum_field_types) $4,
+ &spvar->field_def))
+ {
+ MYSQL_YYABORT;
+ }
+ spvar->field_def.field_name= spvar->name.str;
+ spvar->field_def.pack_flag |= FIELDFLAG_MAYBE_NULL;
+ }
+ ;
+
+sp_opt_inout:
+ /* Empty */ { $$= sp_variable::MODE_IN; }
+ | IN_SYM { $$= sp_variable::MODE_IN; }
+ | OUT_SYM { $$= sp_variable::MODE_OUT; }
+ | INOUT_SYM { $$= sp_variable::MODE_INOUT; }
+ ;
+
+sp_proc_stmts:
+ /* Empty */ {}
+ | sp_proc_stmts sp_proc_stmt ';'
+ ;
+
+sp_proc_stmts1:
+ sp_proc_stmt ';' {}
+ | sp_proc_stmts1 sp_proc_stmt ';'
+ ;
+
+sp_decls:
+ /* Empty */
+ {
+ $$.vars= $$.conds= $$.hndlrs= $$.curs= 0;
+ }
+ | sp_decls sp_decl ';'
+ {
+ /* We check for declarations out of (standard) order this way
+ because letting the grammar rules reflect it caused tricky
+ shift/reduce conflicts with the wrong result. (And we get
+ better error handling this way.) */
+ if (($2.vars || $2.conds) && ($1.curs || $1.hndlrs))
+ { /* Variable or condition following cursor or handler */
+ my_message(ER_SP_VARCOND_AFTER_CURSHNDLR,
+ ER(ER_SP_VARCOND_AFTER_CURSHNDLR), MYF(0));
+ MYSQL_YYABORT;
+ }
+ if ($2.curs && $1.hndlrs)
+ { /* Cursor following handler */
+ my_message(ER_SP_CURSOR_AFTER_HANDLER,
+ ER(ER_SP_CURSOR_AFTER_HANDLER), MYF(0));
+ MYSQL_YYABORT;
+ }
+ $$.vars= $1.vars + $2.vars;
+ $$.conds= $1.conds + $2.conds;
+ $$.hndlrs= $1.hndlrs + $2.hndlrs;
+ $$.curs= $1.curs + $2.curs;
+ }
+ ;
+
+sp_decl:
+ DECLARE_SYM sp_decl_idents
+ {
+ LEX *lex= Lex;
+
+ lex->sphead->reset_lex(thd);
+ lex->spcont->declare_var_boundary($2);
+ }
+ type_with_opt_collate
+ sp_opt_default
+ {
+ LEX *lex= Lex;
+ sp_pcontext *pctx= lex->spcont;
+ uint num_vars= pctx->context_var_count();
+ enum enum_field_types var_type= (enum enum_field_types) $4;
+ Item *dflt_value_item= $5;
+
+ if (!dflt_value_item)
+ {
+ dflt_value_item= new (thd->mem_root) Item_null();
+ if (dflt_value_item == NULL)
+ MYSQL_YYABORT;
+ /* QQ Set to the var_type with null_value? */
+ }
+
+ for (uint i = num_vars-$2 ; i < num_vars ; i++)
+ {
+ uint var_idx= pctx->var_context2runtime(i);
+ sp_variable *spvar= pctx->find_variable(var_idx);
+
+ if (!spvar)
+ MYSQL_YYABORT;
+
+ spvar->type= var_type;
+ spvar->default_value= dflt_value_item;
+
+ if (lex->sphead->fill_field_definition(thd, lex, var_type,
+ &spvar->field_def))
+ {
+ MYSQL_YYABORT;
+ }
+
+ spvar->field_def.field_name= spvar->name.str;
+ spvar->field_def.pack_flag |= FIELDFLAG_MAYBE_NULL;
+
+ /* The last instruction is responsible for freeing LEX. */
+
+ sp_instr_set *is= new sp_instr_set(lex->sphead->instructions(),
+ pctx,
+ var_idx,
+ dflt_value_item,
+ var_type,
+ lex,
+ (i == num_vars - 1));
+ if (is == NULL ||
+ lex->sphead->add_instr(is))
+ MYSQL_YYABORT;
+ }
+
+ pctx->declare_var_boundary(0);
+ if (lex->sphead->restore_lex(thd))
+ MYSQL_YYABORT;
+ $$.vars= $2;
+ $$.conds= $$.hndlrs= $$.curs= 0;
+ }
+ | DECLARE_SYM ident CONDITION_SYM FOR_SYM sp_cond
+ {
+ LEX *lex= Lex;
+ sp_pcontext *spc= lex->spcont;
+
+ if (spc->find_condition($2, TRUE))
+ {
+ my_error(ER_SP_DUP_COND, MYF(0), $2.str);
+ MYSQL_YYABORT;
+ }
+ if(spc->add_condition(thd, $2, $5))
+ MYSQL_YYABORT;
+ $$.vars= $$.hndlrs= $$.curs= 0;
+ $$.conds= 1;
+ }
+ | DECLARE_SYM sp_handler_type HANDLER_SYM FOR_SYM
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+
+ sp_handler *h= lex->spcont->add_handler(thd,
+ (sp_handler::enum_type) $2);
+
+ lex->spcont= lex->spcont->push_context(thd,
+ sp_pcontext::HANDLER_SCOPE);
+
+ sp_pcontext *ctx= lex->spcont;
+ sp_instr_hpush_jump *i=
+ new sp_instr_hpush_jump(sp->instructions(), ctx, h);
+
+ if (i == NULL || sp->add_instr(i))
+ MYSQL_YYABORT;
+
+ /* For continue handlers, mark end of handler scope. */
+ if ($2 == sp_handler::CONTINUE &&
+ sp->push_backpatch(i, ctx->last_label()))
+ MYSQL_YYABORT;
+
+ if (sp->push_backpatch(i, ctx->push_label(thd, empty_lex_str, 0)))
+ MYSQL_YYABORT;
+ }
+ sp_hcond_list sp_proc_stmt
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ sp_label *hlab= lex->spcont->pop_label(); /* After this hdlr */
+ sp_instr_hreturn *i;
+
+ if ($2 == sp_handler::CONTINUE)
+ {
+ i= new sp_instr_hreturn(sp->instructions(), ctx);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ else
+ { /* EXIT or UNDO handler, just jump to the end of the block */
+ i= new sp_instr_hreturn(sp->instructions(), ctx);
+ if (i == NULL ||
+ sp->add_instr(i) ||
+ sp->push_backpatch(i, lex->spcont->last_label())) /* Block end */
+ MYSQL_YYABORT;
+ }
+ lex->sphead->backpatch(hlab);
+
+ lex->spcont= ctx->pop_context();
+
+ $$.vars= $$.conds= $$.curs= 0;
+ $$.hndlrs= 1;
+ }
+ | DECLARE_SYM ident CURSOR_SYM FOR_SYM sp_cursor_stmt
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ uint offp;
+ sp_instr_cpush *i;
+
+ if (ctx->find_cursor($2, &offp, TRUE))
+ {
+ my_error(ER_SP_DUP_CURS, MYF(0), $2.str);
+ delete $5;
+ MYSQL_YYABORT;
+ }
+ i= new sp_instr_cpush(sp->instructions(), ctx, $5,
+ ctx->current_cursor_count());
+ if (i == NULL ||
+ sp->add_instr(i) ||
+ ctx->add_cursor($2))
+ MYSQL_YYABORT;
+ $$.vars= $$.conds= $$.hndlrs= 0;
+ $$.curs= 1;
+ }
+ ;
+
+sp_cursor_stmt:
+ {
+ Lex->sphead->reset_lex(thd);
+ }
+ select
+ {
+ LEX *lex= Lex;
+
+ DBUG_ASSERT(lex->sql_command == SQLCOM_SELECT);
+
+ if (lex->result)
+ {
+ my_message(ER_SP_BAD_CURSOR_SELECT, ER(ER_SP_BAD_CURSOR_SELECT),
+ MYF(0));
+ MYSQL_YYABORT;
+ }
+ lex->sp_lex_in_use= TRUE;
+ $$= lex;
+ if (lex->sphead->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ ;
+
+sp_handler_type:
+ EXIT_SYM { $$= sp_handler::EXIT; }
+ | CONTINUE_SYM { $$= sp_handler::CONTINUE; }
+ /*| UNDO_SYM { QQ No yet } */
+ ;
+
+sp_hcond_list:
+ sp_hcond_element
+ { $$= 1; }
+ | sp_hcond_list ',' sp_hcond_element
+ { $$+= 1; }
+ ;
+
+sp_hcond_element:
+ sp_hcond
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont->parent_context();
+
+ if (ctx->check_duplicate_handler($1))
+ {
+ my_message(ER_SP_DUP_HANDLER, ER(ER_SP_DUP_HANDLER), MYF(0));
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ sp_instr_hpush_jump *i=
+ (sp_instr_hpush_jump *)sp->last_instruction();
+
+ i->add_condition($1);
+ }
+ }
+ ;
+
+sp_cond:
+ ulong_num
+ { /* mysql errno */
+ if ($1 == 0)
+ {
+ my_error(ER_WRONG_VALUE, MYF(0), "CONDITION", "0");
+ MYSQL_YYABORT;
+ }
+ $$= new (thd->mem_root) sp_condition_value($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | sqlstate
+ ;
+
+sqlstate:
+ SQLSTATE_SYM opt_value TEXT_STRING_literal
+ { /* SQLSTATE */
+
+ /*
+ An error is triggered:
+ - if the specified string is not a valid SQLSTATE,
+ - or if it represents the completion condition -- it is not
+ allowed to SIGNAL, or declare a handler for the completion
+ condition.
+ */
+ if (!is_sqlstate_valid(&$3) || is_sqlstate_completion($3.str))
+ {
+ my_error(ER_SP_BAD_SQLSTATE, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ $$= new (thd->mem_root) sp_condition_value($3.str);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_value:
+ /* Empty */ {}
+ | VALUE_SYM {}
+ ;
+
+sp_hcond:
+ sp_cond
+ {
+ $$= $1;
+ }
+ | ident /* CONDITION name */
+ {
+ $$= Lex->spcont->find_condition($1, false);
+ if ($$ == NULL)
+ {
+ my_error(ER_SP_COND_MISMATCH, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ }
+ | SQLWARNING_SYM /* SQLSTATEs 01??? */
+ {
+ $$= new (thd->mem_root) sp_condition_value(sp_condition_value::WARNING);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | not FOUND_SYM /* SQLSTATEs 02??? */
+ {
+ $$= new (thd->mem_root) sp_condition_value(sp_condition_value::NOT_FOUND);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SQLEXCEPTION_SYM /* All other SQLSTATEs */
+ {
+ $$= new (thd->mem_root) sp_condition_value(sp_condition_value::EXCEPTION);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+signal_stmt:
+ SIGNAL_SYM signal_value opt_set_signal_information
+ {
+ LEX *lex= thd->lex;
+ Yacc_state *state= & thd->m_parser_state->m_yacc;
+
+ lex->sql_command= SQLCOM_SIGNAL;
+ lex->m_sql_cmd=
+ new (thd->mem_root) Sql_cmd_signal($2, state->m_set_signal_info);
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+signal_value:
+ ident
+ {
+ LEX *lex= Lex;
+ sp_condition_value *cond;
+ if (lex->spcont == NULL)
+ {
+ /* SIGNAL foo cannot be used outside of stored programs */
+ my_error(ER_SP_COND_MISMATCH, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ cond= lex->spcont->find_condition($1, false);
+ if (cond == NULL)
+ {
+ my_error(ER_SP_COND_MISMATCH, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ if (cond->type != sp_condition_value::SQLSTATE)
+ {
+ my_error(ER_SIGNAL_BAD_CONDITION_TYPE, MYF(0));
+ MYSQL_YYABORT;
+ }
+ $$= cond;
+ }
+ | sqlstate
+ { $$= $1; }
+ ;
+
+opt_signal_value:
+ /* empty */
+ { $$= NULL; }
+ | signal_value
+ { $$= $1; }
+ ;
+
+opt_set_signal_information:
+ /* empty */
+ {
+ thd->m_parser_state->m_yacc.m_set_signal_info.clear();
+ }
+ | SET signal_information_item_list
+ ;
+
+signal_information_item_list:
+ signal_condition_information_item_name EQ signal_allowed_expr
+ {
+ Set_signal_information *info;
+ info= &thd->m_parser_state->m_yacc.m_set_signal_info;
+ int index= (int) $1;
+ info->clear();
+ info->m_item[index]= $3;
+ }
+ | signal_information_item_list ','
+ signal_condition_information_item_name EQ signal_allowed_expr
+ {
+ Set_signal_information *info;
+ info= &thd->m_parser_state->m_yacc.m_set_signal_info;
+ int index= (int) $3;
+ if (info->m_item[index] != NULL)
+ {
+ my_error(ER_DUP_SIGNAL_SET, MYF(0),
+ Diag_condition_item_names[index].str);
+ MYSQL_YYABORT;
+ }
+ info->m_item[index]= $5;
+ }
+ ;
+
+/*
+ Only a limited subset of <expr> are allowed in SIGNAL/RESIGNAL.
+*/
+signal_allowed_expr:
+ literal
+ { $$= $1; }
+ | variable
+ {
+ if ($1->type() == Item::FUNC_ITEM)
+ {
+ Item_func *item= (Item_func*) $1;
+ if (item->functype() == Item_func::SUSERVAR_FUNC)
+ {
+ /*
+ Don't allow the following syntax:
+ SIGNAL/RESIGNAL ...
+ SET <signal condition item name> = @foo := expr
+ */
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ $$= $1;
+ }
+ | simple_ident
+ { $$= $1; }
+ ;
+
+/* conditions that can be set in signal / resignal */
+signal_condition_information_item_name:
+ CLASS_ORIGIN_SYM
+ { $$= DIAG_CLASS_ORIGIN; }
+ | SUBCLASS_ORIGIN_SYM
+ { $$= DIAG_SUBCLASS_ORIGIN; }
+ | CONSTRAINT_CATALOG_SYM
+ { $$= DIAG_CONSTRAINT_CATALOG; }
+ | CONSTRAINT_SCHEMA_SYM
+ { $$= DIAG_CONSTRAINT_SCHEMA; }
+ | CONSTRAINT_NAME_SYM
+ { $$= DIAG_CONSTRAINT_NAME; }
+ | CATALOG_NAME_SYM
+ { $$= DIAG_CATALOG_NAME; }
+ | SCHEMA_NAME_SYM
+ { $$= DIAG_SCHEMA_NAME; }
+ | TABLE_NAME_SYM
+ { $$= DIAG_TABLE_NAME; }
+ | COLUMN_NAME_SYM
+ { $$= DIAG_COLUMN_NAME; }
+ | CURSOR_NAME_SYM
+ { $$= DIAG_CURSOR_NAME; }
+ | MESSAGE_TEXT_SYM
+ { $$= DIAG_MESSAGE_TEXT; }
+ | MYSQL_ERRNO_SYM
+ { $$= DIAG_MYSQL_ERRNO; }
+ ;
+
+resignal_stmt:
+ RESIGNAL_SYM opt_signal_value opt_set_signal_information
+ {
+ LEX *lex= thd->lex;
+ Yacc_state *state= & thd->m_parser_state->m_yacc;
+
+ lex->sql_command= SQLCOM_RESIGNAL;
+ lex->m_sql_cmd=
+ new (thd->mem_root) Sql_cmd_resignal($2,
+ state->m_set_signal_info);
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+get_diagnostics:
+ GET_SYM which_area DIAGNOSTICS_SYM diagnostics_information
+ {
+ Diagnostics_information *info= $4;
+
+ info->set_which_da($2);
+
+ Lex->sql_command= SQLCOM_GET_DIAGNOSTICS;
+ Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_get_diagnostics(info);
+
+ if (Lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+which_area:
+ /* If <which area> is not specified, then CURRENT is implicit. */
+ { $$= Diagnostics_information::CURRENT_AREA; }
+ | CURRENT_SYM
+ { $$= Diagnostics_information::CURRENT_AREA; }
+ ;
+
+diagnostics_information:
+ statement_information
+ {
+ $$= new (thd->mem_root) Statement_information($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CONDITION_SYM condition_number condition_information
+ {
+ $$= new (thd->mem_root) Condition_information($2, $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+statement_information:
+ statement_information_item
+ {
+ $$= new (thd->mem_root) List<Statement_information_item>;
+ if ($$ == NULL || $$->push_back($1))
+ MYSQL_YYABORT;
+ }
+ | statement_information ',' statement_information_item
+ {
+ if ($1->push_back($3))
+ MYSQL_YYABORT;
+ $$= $1;
+ }
+ ;
+
+statement_information_item:
+ simple_target_specification EQ statement_information_item_name
+ {
+ $$= new (thd->mem_root) Statement_information_item($3, $1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+
+simple_target_specification:
+ ident
+ {
+ Lex_input_stream *lip= &thd->m_parser_state->m_lip;
+ $$= create_item_for_sp_var(thd, $1, NULL,
+ lip->get_tok_start(), lip->get_ptr());
+
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | '@' ident_or_text
+ {
+ $$= new (thd->mem_root) Item_func_get_user_var($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+statement_information_item_name:
+ NUMBER_SYM
+ { $$= Statement_information_item::NUMBER; }
+ | ROW_COUNT_SYM
+ { $$= Statement_information_item::ROW_COUNT; }
+ ;
+
+/*
+ Only a limited subset of <expr> are allowed in GET DIAGNOSTICS
+ <condition number>, same subset as for SIGNAL/RESIGNAL.
+*/
+condition_number:
+ signal_allowed_expr
+ { $$= $1; }
+ ;
+
+condition_information:
+ condition_information_item
+ {
+ $$= new (thd->mem_root) List<Condition_information_item>;
+ if ($$ == NULL || $$->push_back($1))
+ MYSQL_YYABORT;
+ }
+ | condition_information ',' condition_information_item
+ {
+ if ($1->push_back($3))
+ MYSQL_YYABORT;
+ $$= $1;
+ }
+ ;
+
+condition_information_item:
+ simple_target_specification EQ condition_information_item_name
+ {
+ $$= new (thd->mem_root) Condition_information_item($3, $1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+
+condition_information_item_name:
+ CLASS_ORIGIN_SYM
+ { $$= Condition_information_item::CLASS_ORIGIN; }
+ | SUBCLASS_ORIGIN_SYM
+ { $$= Condition_information_item::SUBCLASS_ORIGIN; }
+ | CONSTRAINT_CATALOG_SYM
+ { $$= Condition_information_item::CONSTRAINT_CATALOG; }
+ | CONSTRAINT_SCHEMA_SYM
+ { $$= Condition_information_item::CONSTRAINT_SCHEMA; }
+ | CONSTRAINT_NAME_SYM
+ { $$= Condition_information_item::CONSTRAINT_NAME; }
+ | CATALOG_NAME_SYM
+ { $$= Condition_information_item::CATALOG_NAME; }
+ | SCHEMA_NAME_SYM
+ { $$= Condition_information_item::SCHEMA_NAME; }
+ | TABLE_NAME_SYM
+ { $$= Condition_information_item::TABLE_NAME; }
+ | COLUMN_NAME_SYM
+ { $$= Condition_information_item::COLUMN_NAME; }
+ | CURSOR_NAME_SYM
+ { $$= Condition_information_item::CURSOR_NAME; }
+ | MESSAGE_TEXT_SYM
+ { $$= Condition_information_item::MESSAGE_TEXT; }
+ | MYSQL_ERRNO_SYM
+ { $$= Condition_information_item::MYSQL_ERRNO; }
+ | RETURNED_SQLSTATE_SYM
+ { $$= Condition_information_item::RETURNED_SQLSTATE; }
+ ;
+
+sp_decl_idents:
+ ident
+ {
+ /* NOTE: field definition is filled in sp_decl section. */
+
+ LEX *lex= Lex;
+ sp_pcontext *spc= lex->spcont;
+
+ if (spc->find_variable($1, TRUE))
+ {
+ my_error(ER_SP_DUP_VAR, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ spc->add_variable(thd,
+ $1,
+ MYSQL_TYPE_DECIMAL,
+ sp_variable::MODE_IN);
+ $$= 1;
+ }
+ | sp_decl_idents ',' ident
+ {
+ /* NOTE: field definition is filled in sp_decl section. */
+
+ LEX *lex= Lex;
+ sp_pcontext *spc= lex->spcont;
+
+ if (spc->find_variable($3, TRUE))
+ {
+ my_error(ER_SP_DUP_VAR, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ spc->add_variable(thd,
+ $3,
+ MYSQL_TYPE_DECIMAL,
+ sp_variable::MODE_IN);
+ $$= $1 + 1;
+ }
+ ;
+
+sp_opt_default:
+ /* Empty */ { $$ = NULL; }
+ | DEFAULT expr { $$ = $2; }
+ ;
+
+sp_proc_stmt:
+ sp_proc_stmt_statement
+ | sp_proc_stmt_return
+ | sp_proc_stmt_if
+ | case_stmt_specification
+ | sp_labeled_block
+ | sp_unlabeled_block
+ | sp_labeled_control
+ | sp_proc_stmt_unlabeled
+ | sp_proc_stmt_leave
+ | sp_proc_stmt_iterate
+ | sp_proc_stmt_open
+ | sp_proc_stmt_fetch
+ | sp_proc_stmt_close
+ ;
+
+sp_proc_stmt_if:
+ IF
+ { Lex->sphead->new_cont_backpatch(NULL); }
+ sp_if END IF
+ { Lex->sphead->do_cont_backpatch(); }
+ ;
+
+sp_proc_stmt_statement:
+ {
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+
+ lex->sphead->reset_lex(thd);
+ lex->sphead->m_tmp_query= lip->get_tok_start();
+ }
+ statement
+ {
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+ sp_head *sp= lex->sphead;
+
+ sp->m_flags|= sp_get_flags_for_command(lex);
+ if (lex->sql_command == SQLCOM_CHANGE_DB)
+ { /* "USE db" doesn't work in a procedure */
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "USE");
+ MYSQL_YYABORT;
+ }
+ /*
+ Don't add an instruction for SET statements, since all
+ instructions for them were already added during processing
+ of "set" rule.
+ */
+ DBUG_ASSERT(lex->sql_command != SQLCOM_SET_OPTION ||
+ lex->var_list.is_empty());
+ if (lex->sql_command != SQLCOM_SET_OPTION)
+ {
+ sp_instr_stmt *i=new sp_instr_stmt(sp->instructions(),
+ lex->spcont, lex);
+ if (i == NULL)
+ MYSQL_YYABORT;
+
+ /*
+ Extract the query statement from the tokenizer. The
+ end is either lex->ptr, if there was no lookahead,
+ lex->tok_end otherwise.
+ */
+ if (yychar == YYEMPTY)
+ i->m_query.length= lip->get_ptr() - sp->m_tmp_query;
+ else
+ i->m_query.length= lip->get_tok_end() - sp->m_tmp_query;
+ if (!(i->m_query.str= strmake_root(thd->mem_root,
+ sp->m_tmp_query,
+ i->m_query.length)) ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ if (sp->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ ;
+
+sp_proc_stmt_return:
+ RETURN_SYM
+ { Lex->sphead->reset_lex(thd); }
+ expr
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+
+ if (sp->m_type != TYPE_ENUM_FUNCTION)
+ {
+ my_message(ER_SP_BADRETURN, ER(ER_SP_BADRETURN), MYF(0));
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ sp_instr_freturn *i;
+
+ i= new sp_instr_freturn(sp->instructions(), lex->spcont, $3,
+ sp->m_return_field_def.sql_type, lex);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ sp->m_flags|= sp_head::HAS_RETURN;
+ }
+ if (sp->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ ;
+
+sp_proc_stmt_unlabeled:
+ { /* Unlabeled controls get a secret label. */
+ LEX *lex= Lex;
+
+ lex->spcont->push_label(thd, empty_lex_str,
+ lex->sphead->instructions());
+ }
+ sp_unlabeled_control
+ {
+ LEX *lex= Lex;
+
+ lex->sphead->backpatch(lex->spcont->pop_label());
+ }
+ ;
+
+sp_proc_stmt_leave:
+ LEAVE_SYM label_ident
+ {
+ LEX *lex= Lex;
+ sp_head *sp = lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ sp_label *lab= ctx->find_label($2);
+
+ if (! lab)
+ {
+ my_error(ER_SP_LILABEL_MISMATCH, MYF(0), "LEAVE", $2.str);
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ sp_instr_jump *i;
+ uint ip= sp->instructions();
+ uint n;
+ /*
+ When jumping to a BEGIN-END block end, the target jump
+ points to the block hpop/cpop cleanup instructions,
+ so we should exclude the block context here.
+ When jumping to something else (i.e., SP_LAB_ITER),
+ there are no hpop/cpop at the jump destination,
+ so we should include the block context here for cleanup.
+ */
+ bool exclusive= (lab->type == sp_label::BEGIN);
+
+ n= ctx->diff_handlers(lab->ctx, exclusive);
+ if (n)
+ {
+ sp_instr_hpop *hpop= new sp_instr_hpop(ip++, ctx, n);
+ if (hpop == NULL)
+ MYSQL_YYABORT;
+ sp->add_instr(hpop);
+ }
+ n= ctx->diff_cursors(lab->ctx, exclusive);
+ if (n)
+ {
+ sp_instr_cpop *cpop= new sp_instr_cpop(ip++, ctx, n);
+ if (cpop == NULL)
+ MYSQL_YYABORT;
+ sp->add_instr(cpop);
+ }
+ i= new sp_instr_jump(ip, ctx);
+ if (i == NULL)
+ MYSQL_YYABORT;
+ sp->push_backpatch(i, lab); /* Jumping forward */
+ sp->add_instr(i);
+ }
+ }
+ ;
+
+sp_proc_stmt_iterate:
+ ITERATE_SYM label_ident
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ sp_label *lab= ctx->find_label($2);
+
+ if (! lab || lab->type != sp_label::ITERATION)
+ {
+ my_error(ER_SP_LILABEL_MISMATCH, MYF(0), "ITERATE", $2.str);
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ sp_instr_jump *i;
+ uint ip= sp->instructions();
+ uint n;
+
+ n= ctx->diff_handlers(lab->ctx, FALSE); /* Inclusive the dest. */
+ if (n)
+ {
+ sp_instr_hpop *hpop= new sp_instr_hpop(ip++, ctx, n);
+ if (hpop == NULL ||
+ sp->add_instr(hpop))
+ MYSQL_YYABORT;
+ }
+ n= ctx->diff_cursors(lab->ctx, FALSE); /* Inclusive the dest. */
+ if (n)
+ {
+ sp_instr_cpop *cpop= new sp_instr_cpop(ip++, ctx, n);
+ if (cpop == NULL ||
+ sp->add_instr(cpop))
+ MYSQL_YYABORT;
+ }
+ i= new sp_instr_jump(ip, ctx, lab->ip); /* Jump back */
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+sp_proc_stmt_open:
+ OPEN_SYM ident
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ uint offset;
+ sp_instr_copen *i;
+
+ if (! lex->spcont->find_cursor($2, &offset, false))
+ {
+ my_error(ER_SP_CURSOR_MISMATCH, MYF(0), $2.str);
+ MYSQL_YYABORT;
+ }
+ i= new sp_instr_copen(sp->instructions(), lex->spcont, offset);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ ;
+
+sp_proc_stmt_fetch:
+ FETCH_SYM sp_opt_fetch_noise ident INTO
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ uint offset;
+ sp_instr_cfetch *i;
+
+ if (! lex->spcont->find_cursor($3, &offset, false))
+ {
+ my_error(ER_SP_CURSOR_MISMATCH, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ i= new sp_instr_cfetch(sp->instructions(), lex->spcont, offset);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ sp_fetch_list
+ {}
+ ;
+
+sp_proc_stmt_close:
+ CLOSE_SYM ident
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ uint offset;
+ sp_instr_cclose *i;
+
+ if (! lex->spcont->find_cursor($2, &offset, false))
+ {
+ my_error(ER_SP_CURSOR_MISMATCH, MYF(0), $2.str);
+ MYSQL_YYABORT;
+ }
+ i= new sp_instr_cclose(sp->instructions(), lex->spcont, offset);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ ;
+
+sp_opt_fetch_noise:
+ /* Empty */
+ | NEXT_SYM FROM
+ | FROM
+ ;
+
+sp_fetch_list:
+ ident
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *spc= lex->spcont;
+ sp_variable *spv;
+
+ if (!spc || !(spv = spc->find_variable($1, false)))
+ {
+ my_error(ER_SP_UNDECLARED_VAR, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ /* An SP local variable */
+ sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction();
+
+ i->add_to_varlist(spv);
+ }
+ }
+ | sp_fetch_list ',' ident
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *spc= lex->spcont;
+ sp_variable *spv;
+
+ if (!spc || !(spv = spc->find_variable($3, false)))
+ {
+ my_error(ER_SP_UNDECLARED_VAR, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ /* An SP local variable */
+ sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction();
+
+ i->add_to_varlist(spv);
+ }
+ }
+ ;
+
+sp_if:
+ { Lex->sphead->reset_lex(thd); }
+ expr THEN_SYM
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ uint ip= sp->instructions();
+ sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx,
+ $2, lex);
+ if (i == NULL ||
+ sp->push_backpatch(i, ctx->push_label(thd, empty_lex_str, 0)) ||
+ sp->add_cont_backpatch(i) ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ if (sp->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ sp_proc_stmts1
+ {
+ sp_head *sp= Lex->sphead;
+ sp_pcontext *ctx= Lex->spcont;
+ uint ip= sp->instructions();
+ sp_instr_jump *i = new sp_instr_jump(ip, ctx);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ sp->backpatch(ctx->pop_label());
+ sp->push_backpatch(i, ctx->push_label(thd, empty_lex_str, 0));
+ }
+ sp_elseifs
+ {
+ LEX *lex= Lex;
+
+ lex->sphead->backpatch(lex->spcont->pop_label());
+ }
+ ;
+
+sp_elseifs:
+ /* Empty */
+ | ELSEIF_SYM sp_if
+ | ELSE sp_proc_stmts1
+ ;
+
+case_stmt_specification:
+ simple_case_stmt
+ | searched_case_stmt
+ ;
+
+simple_case_stmt:
+ CASE_SYM
+ {
+ LEX *lex= Lex;
+ case_stmt_action_case(lex);
+ lex->sphead->reset_lex(thd); /* For expr $3 */
+ }
+ expr
+ {
+ LEX *lex= Lex;
+ if (case_stmt_action_expr(lex, $3))
+ MYSQL_YYABORT;
+
+ /* For expr $3 */
+ if (lex->sphead->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ simple_when_clause_list
+ else_clause_opt
+ END
+ CASE_SYM
+ {
+ LEX *lex= Lex;
+ case_stmt_action_end_case(lex, true);
+ }
+ ;
+
+searched_case_stmt:
+ CASE_SYM
+ {
+ LEX *lex= Lex;
+ case_stmt_action_case(lex);
+ }
+ searched_when_clause_list
+ else_clause_opt
+ END
+ CASE_SYM
+ {
+ LEX *lex= Lex;
+ case_stmt_action_end_case(lex, false);
+ }
+ ;
+
+simple_when_clause_list:
+ simple_when_clause
+ | simple_when_clause_list simple_when_clause
+ ;
+
+searched_when_clause_list:
+ searched_when_clause
+ | searched_when_clause_list searched_when_clause
+ ;
+
+simple_when_clause:
+ WHEN_SYM
+ {
+ Lex->sphead->reset_lex(thd); /* For expr $3 */
+ }
+ expr
+ {
+ /* Simple case: <caseval> = <whenval> */
+
+ LEX *lex= Lex;
+ if (case_stmt_action_when(lex, $3, true))
+ MYSQL_YYABORT;
+ /* For expr $3 */
+ if (lex->sphead->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ THEN_SYM
+ sp_proc_stmts1
+ {
+ LEX *lex= Lex;
+ if (case_stmt_action_then(lex))
+ MYSQL_YYABORT;
+ }
+ ;
+
+searched_when_clause:
+ WHEN_SYM
+ {
+ Lex->sphead->reset_lex(thd); /* For expr $3 */
+ }
+ expr
+ {
+ LEX *lex= Lex;
+ if (case_stmt_action_when(lex, $3, false))
+ MYSQL_YYABORT;
+ /* For expr $3 */
+ if (lex->sphead->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ THEN_SYM
+ sp_proc_stmts1
+ {
+ LEX *lex= Lex;
+ if (case_stmt_action_then(lex))
+ MYSQL_YYABORT;
+ }
+ ;
+
+else_clause_opt:
+ /* empty */
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ uint ip= sp->instructions();
+ sp_instr_error *i= new sp_instr_error(ip, lex->spcont,
+ ER_SP_CASE_NOT_FOUND);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ | ELSE sp_proc_stmts1
+ ;
+
+sp_labeled_control:
+ label_ident ':'
+ {
+ LEX *lex= Lex;
+ sp_pcontext *ctx= lex->spcont;
+ sp_label *lab= ctx->find_label($1);
+
+ if (lab)
+ {
+ my_error(ER_SP_LABEL_REDEFINE, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ lab= lex->spcont->push_label(thd, $1, lex->sphead->instructions());
+ lab->type= sp_label::ITERATION;
+ }
+ }
+ sp_unlabeled_control sp_opt_label
+ {
+ LEX *lex= Lex;
+ sp_label *lab= lex->spcont->pop_label();
+
+ if ($5.str)
+ {
+ if (my_strcasecmp(system_charset_info, $5.str, lab->name.str) != 0)
+ {
+ my_error(ER_SP_LABEL_MISMATCH, MYF(0), $5.str);
+ MYSQL_YYABORT;
+ }
+ }
+ lex->sphead->backpatch(lab);
+ }
+ ;
+
+sp_opt_label:
+ /* Empty */ { $$= null_lex_str; }
+ | label_ident { $$= $1; }
+ ;
+
+sp_labeled_block:
+ label_ident ':'
+ {
+ LEX *lex= Lex;
+ sp_pcontext *ctx= lex->spcont;
+ sp_label *lab= ctx->find_label($1);
+
+ if (lab)
+ {
+ my_error(ER_SP_LABEL_REDEFINE, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+
+ lab= lex->spcont->push_label(thd, $1, lex->sphead->instructions());
+ lab->type= sp_label::BEGIN;
+ }
+ sp_block_content sp_opt_label
+ {
+ LEX *lex= Lex;
+ sp_label *lab= lex->spcont->pop_label();
+
+ if ($5.str)
+ {
+ if (my_strcasecmp(system_charset_info, $5.str, lab->name.str) != 0)
+ {
+ my_error(ER_SP_LABEL_MISMATCH, MYF(0), $5.str);
+ MYSQL_YYABORT;
+ }
+ }
+ }
+ ;
+
+sp_unlabeled_block:
+ { /* Unlabeled blocks get a secret label. */
+ LEX *lex= Lex;
+ uint ip= lex->sphead->instructions();
+ sp_label *lab= lex->spcont->push_label(thd, empty_lex_str, ip);
+ lab->type= sp_label::BEGIN;
+ }
+ sp_block_content
+ {
+ LEX *lex= Lex;
+ lex->spcont->pop_label();
+ }
+ ;
+
+sp_block_content:
+ BEGIN_SYM
+ { /* QQ This is just a dummy for grouping declarations and statements
+ together. No [[NOT] ATOMIC] yet, and we need to figure out how
+ make it coexist with the existing BEGIN COMMIT/ROLLBACK. */
+ LEX *lex= Lex;
+ lex->spcont= lex->spcont->push_context(thd,
+ sp_pcontext::REGULAR_SCOPE);
+ }
+ sp_decls
+ sp_proc_stmts
+ END
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ sp_pcontext *ctx= lex->spcont;
+ sp_instr *i;
+
+ sp->backpatch(ctx->last_label()); /* We always have a label */
+ if ($3.hndlrs)
+ {
+ i= new sp_instr_hpop(sp->instructions(), ctx, $3.hndlrs);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ if ($3.curs)
+ {
+ i= new sp_instr_cpop(sp->instructions(), ctx, $3.curs);
+ if (i == NULL ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ lex->spcont= ctx->pop_context();
+ }
+ ;
+
+sp_unlabeled_control:
+ LOOP_SYM
+ sp_proc_stmts1 END LOOP_SYM
+ {
+ LEX *lex= Lex;
+ uint ip= lex->sphead->instructions();
+ sp_label *lab= lex->spcont->last_label(); /* Jumping back */
+ sp_instr_jump *i = new sp_instr_jump(ip, lex->spcont, lab->ip);
+ if (i == NULL ||
+ lex->sphead->add_instr(i))
+ MYSQL_YYABORT;
+ }
+ | WHILE_SYM
+ { Lex->sphead->reset_lex(thd); }
+ expr DO_SYM
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ uint ip= sp->instructions();
+ sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont,
+ $3, lex);
+ if (i == NULL ||
+ /* Jumping forward */
+ sp->push_backpatch(i, lex->spcont->last_label()) ||
+ sp->new_cont_backpatch(i) ||
+ sp->add_instr(i))
+ MYSQL_YYABORT;
+ if (sp->restore_lex(thd))
+ MYSQL_YYABORT;
+ }
+ sp_proc_stmts1 END WHILE_SYM
+ {
+ LEX *lex= Lex;
+ uint ip= lex->sphead->instructions();
+ sp_label *lab= lex->spcont->last_label(); /* Jumping back */
+ sp_instr_jump *i = new sp_instr_jump(ip, lex->spcont, lab->ip);
+ if (i == NULL ||
+ lex->sphead->add_instr(i))
+ MYSQL_YYABORT;
+ lex->sphead->do_cont_backpatch();
+ }
+ | REPEAT_SYM sp_proc_stmts1 UNTIL_SYM
+ { Lex->sphead->reset_lex(thd); }
+ expr END REPEAT_SYM
+ {
+ LEX *lex= Lex;
+ uint ip= lex->sphead->instructions();
+ sp_label *lab= lex->spcont->last_label(); /* Jumping back */
+ sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont,
+ $5, lab->ip,
+ lex);
+ if (i == NULL ||
+ lex->sphead->add_instr(i))
+ MYSQL_YYABORT;
+ if (lex->sphead->restore_lex(thd))
+ MYSQL_YYABORT;
+ /* We can shortcut the cont_backpatch here */
+ i->m_cont_dest= ip+1;
+ }
+ ;
+
+trg_action_time:
+ BEFORE_SYM
+ { Lex->trg_chistics.action_time= TRG_ACTION_BEFORE; }
+ | AFTER_SYM
+ { Lex->trg_chistics.action_time= TRG_ACTION_AFTER; }
+ ;
+
+trg_event:
+ INSERT
+ { Lex->trg_chistics.event= TRG_EVENT_INSERT; }
+ | UPDATE_SYM
+ { Lex->trg_chistics.event= TRG_EVENT_UPDATE; }
+ | DELETE_SYM
+ { Lex->trg_chistics.event= TRG_EVENT_DELETE; }
+ ;
+/*
+ This part of the parser contains common code for all TABLESPACE
+ commands.
+ CREATE TABLESPACE name ...
+ ALTER TABLESPACE name CHANGE DATAFILE ...
+ ALTER TABLESPACE name ADD DATAFILE ...
+ ALTER TABLESPACE name access_mode
+ CREATE LOGFILE GROUP_SYM name ...
+ ALTER LOGFILE GROUP_SYM name ADD UNDOFILE ..
+ ALTER LOGFILE GROUP_SYM name ADD REDOFILE ..
+ DROP TABLESPACE name
+ DROP LOGFILE GROUP_SYM name
+*/
+change_tablespace_access:
+ tablespace_name
+ ts_access_mode
+ ;
+
+change_tablespace_info:
+ tablespace_name
+ CHANGE ts_datafile
+ change_ts_option_list
+ ;
+
+tablespace_info:
+ tablespace_name
+ ADD ts_datafile
+ opt_logfile_group_name
+ tablespace_option_list
+ ;
+
+opt_logfile_group_name:
+ /* empty */ {}
+ | USE_SYM LOGFILE_SYM GROUP_SYM ident
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->logfile_group_name= $4.str;
+ }
+ ;
+
+alter_tablespace_info:
+ tablespace_name
+ ADD ts_datafile
+ alter_tablespace_option_list
+ {
+ Lex->alter_tablespace_info->ts_alter_tablespace_type= ALTER_TABLESPACE_ADD_FILE;
+ }
+ | tablespace_name
+ DROP ts_datafile
+ alter_tablespace_option_list
+ {
+ Lex->alter_tablespace_info->ts_alter_tablespace_type= ALTER_TABLESPACE_DROP_FILE;
+ }
+ ;
+
+logfile_group_info:
+ logfile_group_name
+ add_log_file
+ logfile_group_option_list
+ ;
+
+alter_logfile_group_info:
+ logfile_group_name
+ add_log_file
+ alter_logfile_group_option_list
+ ;
+
+add_log_file:
+ ADD lg_undofile
+ | ADD lg_redofile
+ ;
+
+change_ts_option_list:
+ /* empty */ {}
+ change_ts_options
+ ;
+
+change_ts_options:
+ change_ts_option
+ | change_ts_options change_ts_option
+ | change_ts_options ',' change_ts_option
+ ;
+
+change_ts_option:
+ opt_ts_initial_size
+ | opt_ts_autoextend_size
+ | opt_ts_max_size
+ ;
+
+tablespace_option_list:
+ tablespace_options
+ ;
+
+tablespace_options:
+ tablespace_option
+ | tablespace_options tablespace_option
+ | tablespace_options ',' tablespace_option
+ ;
+
+tablespace_option:
+ opt_ts_initial_size
+ | opt_ts_autoextend_size
+ | opt_ts_max_size
+ | opt_ts_extent_size
+ | opt_ts_nodegroup
+ | opt_ts_engine
+ | ts_wait
+ | opt_ts_comment
+ ;
+
+alter_tablespace_option_list:
+ alter_tablespace_options
+ ;
+
+alter_tablespace_options:
+ alter_tablespace_option
+ | alter_tablespace_options alter_tablespace_option
+ | alter_tablespace_options ',' alter_tablespace_option
+ ;
+
+alter_tablespace_option:
+ opt_ts_initial_size
+ | opt_ts_autoextend_size
+ | opt_ts_max_size
+ | opt_ts_engine
+ | ts_wait
+ ;
+
+logfile_group_option_list:
+ logfile_group_options
+ ;
+
+logfile_group_options:
+ logfile_group_option
+ | logfile_group_options logfile_group_option
+ | logfile_group_options ',' logfile_group_option
+ ;
+
+logfile_group_option:
+ opt_ts_initial_size
+ | opt_ts_undo_buffer_size
+ | opt_ts_redo_buffer_size
+ | opt_ts_nodegroup
+ | opt_ts_engine
+ | ts_wait
+ | opt_ts_comment
+ ;
+
+alter_logfile_group_option_list:
+ alter_logfile_group_options
+ ;
+
+alter_logfile_group_options:
+ alter_logfile_group_option
+ | alter_logfile_group_options alter_logfile_group_option
+ | alter_logfile_group_options ',' alter_logfile_group_option
+ ;
+
+alter_logfile_group_option:
+ opt_ts_initial_size
+ | opt_ts_engine
+ | ts_wait
+ ;
+
+
+ts_datafile:
+ DATAFILE_SYM TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->data_file_name= $2.str;
+ }
+ ;
+
+lg_undofile:
+ UNDOFILE_SYM TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->undo_file_name= $2.str;
+ }
+ ;
+
+lg_redofile:
+ REDOFILE_SYM TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->redo_file_name= $2.str;
+ }
+ ;
+
+tablespace_name:
+ ident
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info= new st_alter_tablespace();
+ if (lex->alter_tablespace_info == NULL)
+ MYSQL_YYABORT;
+ lex->alter_tablespace_info->tablespace_name= $1.str;
+ lex->sql_command= SQLCOM_ALTER_TABLESPACE;
+ }
+ ;
+
+logfile_group_name:
+ ident
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info= new st_alter_tablespace();
+ if (lex->alter_tablespace_info == NULL)
+ MYSQL_YYABORT;
+ lex->alter_tablespace_info->logfile_group_name= $1.str;
+ lex->sql_command= SQLCOM_ALTER_TABLESPACE;
+ }
+ ;
+
+ts_access_mode:
+ READ_ONLY_SYM
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_access_mode= TS_READ_ONLY;
+ }
+ | READ_WRITE_SYM
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_access_mode= TS_READ_WRITE;
+ }
+ | NOT_SYM ACCESSIBLE_SYM
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_access_mode= TS_NOT_ACCESSIBLE;
+ }
+ ;
+
+opt_ts_initial_size:
+ INITIAL_SIZE_SYM opt_equal size_number
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->initial_size= $3;
+ }
+ ;
+
+opt_ts_autoextend_size:
+ AUTOEXTEND_SIZE_SYM opt_equal size_number
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->autoextend_size= $3;
+ }
+ ;
+
+opt_ts_max_size:
+ MAX_SIZE_SYM opt_equal size_number
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->max_size= $3;
+ }
+ ;
+
+opt_ts_extent_size:
+ EXTENT_SIZE_SYM opt_equal size_number
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->extent_size= $3;
+ }
+ ;
+
+opt_ts_undo_buffer_size:
+ UNDO_BUFFER_SIZE_SYM opt_equal size_number
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->undo_buffer_size= $3;
+ }
+ ;
+
+opt_ts_redo_buffer_size:
+ REDO_BUFFER_SIZE_SYM opt_equal size_number
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->redo_buffer_size= $3;
+ }
+ ;
+
+opt_ts_nodegroup:
+ NODEGROUP_SYM opt_equal real_ulong_num
+ {
+ LEX *lex= Lex;
+ if (lex->alter_tablespace_info->nodegroup_id != UNDEF_NODEGROUP)
+ {
+ my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"NODEGROUP");
+ MYSQL_YYABORT;
+ }
+ lex->alter_tablespace_info->nodegroup_id= $3;
+ }
+ ;
+
+opt_ts_comment:
+ COMMENT_SYM opt_equal TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ if (lex->alter_tablespace_info->ts_comment != NULL)
+ {
+ my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"COMMENT");
+ MYSQL_YYABORT;
+ }
+ lex->alter_tablespace_info->ts_comment= $3.str;
+ }
+ ;
+
+opt_ts_engine:
+ opt_storage ENGINE_SYM opt_equal storage_engines
+ {
+ LEX *lex= Lex;
+ if (lex->alter_tablespace_info->storage_engine != NULL)
+ {
+ my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),
+ "STORAGE ENGINE");
+ MYSQL_YYABORT;
+ }
+ lex->alter_tablespace_info->storage_engine= $4;
+ }
+ ;
+
+opt_ts_wait:
+ /* empty */
+ | ts_wait
+ ;
+
+ts_wait:
+ WAIT_SYM
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->wait_until_completed= TRUE;
+ }
+ | NO_WAIT_SYM
+ {
+ LEX *lex= Lex;
+ if (!(lex->alter_tablespace_info->wait_until_completed))
+ {
+ my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"NO_WAIT");
+ MYSQL_YYABORT;
+ }
+ lex->alter_tablespace_info->wait_until_completed= FALSE;
+ }
+ ;
+
+size_number:
+ real_ulonglong_num { $$= $1;}
+ | IDENT_sys
+ {
+ ulonglong number;
+ uint text_shift_number= 0;
+ longlong prefix_number;
+ char *start_ptr= $1.str;
+ uint str_len= $1.length;
+ char *end_ptr= start_ptr + str_len;
+ int error;
+ prefix_number= my_strtoll10(start_ptr, &end_ptr, &error);
+ if ((start_ptr + str_len - 1) == end_ptr)
+ {
+ switch (end_ptr[0])
+ {
+ case 'g':
+ case 'G':
+ text_shift_number+=10;
+ case 'm':
+ case 'M':
+ text_shift_number+=10;
+ case 'k':
+ case 'K':
+ text_shift_number+=10;
+ break;
+ default:
+ {
+ my_error(ER_WRONG_SIZE_NUMBER, MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ if (prefix_number >> 31)
+ {
+ my_error(ER_SIZE_OVERFLOW_ERROR, MYF(0));
+ MYSQL_YYABORT;
+ }
+ number= prefix_number << text_shift_number;
+ }
+ else
+ {
+ my_error(ER_WRONG_SIZE_NUMBER, MYF(0));
+ MYSQL_YYABORT;
+ }
+ $$= number;
+ }
+ ;
+
+/*
+ End tablespace part
+*/
+
+create_body:
+ '(' create_field_list ')'
+ { Lex->create_info.option_list= NULL; }
+ opt_create_table_options opt_create_partitioning opt_create_select {}
+ | opt_create_table_options opt_create_partitioning opt_create_select {}
+ /*
+ the following rule is redundant, but there's a shift/reduce
+ conflict that prevents the rule above from parsing a syntax like
+ CREATE TABLE t1 (SELECT 1);
+ */
+ | '(' create_select ')' { Select->set_braces(1);} union_opt {}
+ | create_like
+ {
+
+ Lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE;
+ TABLE_LIST *src_table= Lex->select_lex.add_table_to_list(thd,
+ $1, NULL, 0, TL_READ, MDL_SHARED_READ);
+ if (! src_table)
+ MYSQL_YYABORT;
+ /* CREATE TABLE ... LIKE is not allowed for views. */
+ src_table->required_type= FRMTYPE_TABLE;
+ }
+ ;
+
+create_like:
+ LIKE table_ident { $$= $2; }
+ | '(' LIKE table_ident ')' { $$= $3; }
+ ;
+
+opt_create_select:
+ /* empty */ {}
+ | opt_duplicate opt_as create_select
+ { Select->set_braces(0);}
+ union_clause {}
+ | opt_duplicate opt_as '(' create_select ')'
+ { Select->set_braces(1);}
+ union_opt {}
+ ;
+
+opt_create_partitioning:
+ opt_partitioning
+ {
+ /*
+ Remove all tables used in PARTITION clause from the global table
+ list. Partitioning with subqueries is not allowed anyway.
+ */
+ TABLE_LIST *last_non_sel_table= Lex->create_last_non_select_table;
+ last_non_sel_table->next_global= 0;
+ Lex->query_tables_last= &last_non_sel_table->next_global;
+ }
+ ;
+
+/*
+ This part of the parser is about handling of the partition information.
+
+ It's first version was written by Mikael Ronström with lots of answers to
+ questions provided by Antony Curtis.
+
+ The partition grammar can be called from three places.
+ 1) CREATE TABLE ... PARTITION ..
+ 2) ALTER TABLE table_name PARTITION ...
+ 3) PARTITION ...
+
+ The first place is called when a new table is created from a MySQL client.
+ The second place is called when a table is altered with the ALTER TABLE
+ command from a MySQL client.
+ The third place is called when opening an frm file and finding partition
+ info in the .frm file. It is necessary to avoid allowing PARTITION to be
+ an allowed entry point for SQL client queries. This is arranged by setting
+ some state variables before arriving here.
+
+ To be able to handle errors we will only set error code in this code
+ and handle the error condition in the function calling the parser. This
+ is necessary to ensure we can also handle errors when calling the parser
+ from the openfrm function.
+*/
+opt_partitioning:
+ /* empty */ {}
+ | partitioning
+ ;
+
+partitioning:
+ PARTITION_SYM have_partitioning
+ {
+ LEX *lex= Lex;
+ lex->part_info= new partition_info();
+ if (!lex->part_info)
+ {
+ mem_alloc_error(sizeof(partition_info));
+ MYSQL_YYABORT;
+ }
+ if (lex->sql_command == SQLCOM_ALTER_TABLE)
+ {
+ lex->alter_info.flags|= Alter_info::ALTER_PARTITION;
+ }
+ }
+ partition
+ ;
+
+have_partitioning:
+ /* empty */
+ {
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ LEX_STRING partition_name={C_STRING_WITH_LEN("partition")};
+ if (!plugin_is_ready(&partition_name, MYSQL_STORAGE_ENGINE_PLUGIN))
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
+ "--skip-partition");
+ MYSQL_YYABORT;
+ }
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0), "partitioning",
+ "--with-plugin-partition");
+ MYSQL_YYABORT;
+#endif
+ }
+ ;
+
+partition_entry:
+ PARTITION_SYM
+ {
+ LEX *lex= Lex;
+ if (!lex->part_info)
+ {
+ my_parse_error(ER(ER_PARTITION_ENTRY_ERROR));
+ MYSQL_YYABORT;
+ }
+ /*
+ We enter here when opening the frm file to translate
+ partition info string into part_info data structure.
+ */
+ }
+ partition {}
+ ;
+
+partition:
+ BY part_type_def opt_num_parts opt_sub_part part_defs
+ ;
+
+part_type_def:
+ opt_linear KEY_SYM opt_key_algo '(' part_field_list ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->list_of_part_fields= TRUE;
+ part_info->column_list= FALSE;
+ part_info->part_type= HASH_PARTITION;
+ }
+ | opt_linear HASH_SYM
+ { Lex->part_info->part_type= HASH_PARTITION; }
+ part_func {}
+ | RANGE_SYM part_func
+ { Lex->part_info->part_type= RANGE_PARTITION; }
+ | RANGE_SYM part_column_list
+ { Lex->part_info->part_type= RANGE_PARTITION; }
+ | LIST_SYM part_func
+ { Lex->part_info->part_type= LIST_PARTITION; }
+ | LIST_SYM part_column_list
+ { Lex->part_info->part_type= LIST_PARTITION; }
+ ;
+
+opt_linear:
+ /* empty */ {}
+ | LINEAR_SYM
+ { Lex->part_info->linear_hash_ind= TRUE;}
+ ;
+
+opt_key_algo:
+ /* empty */
+ { Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_NONE;}
+ | ALGORITHM_SYM EQ real_ulong_num
+ {
+ switch ($3) {
+ case 1:
+ Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_51;
+ break;
+ case 2:
+ Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_55;
+ break;
+ default:
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+part_field_list:
+ /* empty */ {}
+ | part_field_item_list {}
+ ;
+
+part_field_item_list:
+ part_field_item {}
+ | part_field_item_list ',' part_field_item {}
+ ;
+
+part_field_item:
+ ident
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->num_columns++;
+ if (part_info->part_field_list.push_back($1.str))
+ {
+ mem_alloc_error(1);
+ MYSQL_YYABORT;
+ }
+ if (part_info->num_columns > MAX_REF_PARTS)
+ {
+ my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0),
+ "list of partition fields");
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+part_column_list:
+ COLUMNS '(' part_field_list ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->column_list= TRUE;
+ part_info->list_of_part_fields= TRUE;
+ }
+ ;
+
+
+part_func:
+ '(' remember_name part_func_expr remember_end ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ if (part_info->set_part_expr($2+1, $3, $4, FALSE))
+ { MYSQL_YYABORT; }
+ part_info->num_columns= 1;
+ part_info->column_list= FALSE;
+ }
+ ;
+
+sub_part_func:
+ '(' remember_name part_func_expr remember_end ')'
+ {
+ if (Lex->part_info->set_part_expr($2+1, $3, $4, TRUE))
+ { MYSQL_YYABORT; }
+ }
+ ;
+
+
+opt_num_parts:
+ /* empty */ {}
+ | PARTITIONS_SYM real_ulong_num
+ {
+ uint num_parts= $2;
+ partition_info *part_info= Lex->part_info;
+ if (num_parts == 0)
+ {
+ my_error(ER_NO_PARTS_ERROR, MYF(0), "partitions");
+ MYSQL_YYABORT;
+ }
+
+ part_info->num_parts= num_parts;
+ part_info->use_default_num_partitions= FALSE;
+ }
+ ;
+
+opt_sub_part:
+ /* empty */ {}
+ | SUBPARTITION_SYM BY opt_linear HASH_SYM sub_part_func
+ { Lex->part_info->subpart_type= HASH_PARTITION; }
+ opt_num_subparts {}
+ | SUBPARTITION_SYM BY opt_linear KEY_SYM opt_key_algo
+ '(' sub_part_field_list ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->subpart_type= HASH_PARTITION;
+ part_info->list_of_subpart_fields= TRUE;
+ }
+ opt_num_subparts {}
+ ;
+
+sub_part_field_list:
+ sub_part_field_item {}
+ | sub_part_field_list ',' sub_part_field_item {}
+ ;
+
+sub_part_field_item:
+ ident
+ {
+ partition_info *part_info= Lex->part_info;
+ if (part_info->subpart_field_list.push_back($1.str))
+ {
+ mem_alloc_error(1);
+ MYSQL_YYABORT;
+ }
+ if (part_info->subpart_field_list.elements > MAX_REF_PARTS)
+ {
+ my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0),
+ "list of subpartition fields");
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+part_func_expr:
+ bit_expr
+ {
+ LEX *lex= Lex;
+ bool not_corr_func;
+ not_corr_func= !lex->safe_to_cache_query;
+ lex->safe_to_cache_query= 1;
+ if (not_corr_func)
+ {
+ my_parse_error(ER(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR));
+ MYSQL_YYABORT;
+ }
+ $$=$1;
+ }
+ ;
+
+opt_num_subparts:
+ /* empty */ {}
+ | SUBPARTITIONS_SYM real_ulong_num
+ {
+ uint num_parts= $2;
+ LEX *lex= Lex;
+ if (num_parts == 0)
+ {
+ my_error(ER_NO_PARTS_ERROR, MYF(0), "subpartitions");
+ MYSQL_YYABORT;
+ }
+ lex->part_info->num_subparts= num_parts;
+ lex->part_info->use_default_num_subpartitions= FALSE;
+ }
+ ;
+
+part_defs:
+ /* empty */
+ {
+ partition_info *part_info= Lex->part_info;
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0),
+ "RANGE");
+ MYSQL_YYABORT;
+ }
+ else if (part_info->part_type == LIST_PARTITION)
+ {
+ my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0),
+ "LIST");
+ MYSQL_YYABORT;
+ }
+ }
+ | '(' part_def_list ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ uint count_curr_parts= part_info->partitions.elements;
+ if (part_info->num_parts != 0)
+ {
+ if (part_info->num_parts !=
+ count_curr_parts)
+ {
+ my_parse_error(ER(ER_PARTITION_WRONG_NO_PART_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ else if (count_curr_parts > 0)
+ {
+ part_info->num_parts= count_curr_parts;
+ }
+ part_info->count_curr_subparts= 0;
+ }
+ ;
+
+part_def_list:
+ part_definition {}
+ | part_def_list ',' part_definition {}
+ ;
+
+part_definition:
+ PARTITION_SYM
+ {
+ partition_info *part_info= Lex->part_info;
+ partition_element *p_elem= new partition_element();
+
+ if (!p_elem || part_info->partitions.push_back(p_elem))
+ {
+ mem_alloc_error(sizeof(partition_element));
+ MYSQL_YYABORT;
+ }
+ p_elem->part_state= PART_NORMAL;
+ part_info->curr_part_elem= p_elem;
+ part_info->current_partition= p_elem;
+ part_info->use_default_partitions= FALSE;
+ part_info->use_default_num_partitions= FALSE;
+ }
+ part_name
+ opt_part_values
+ opt_part_options
+ opt_sub_partition
+ {}
+ ;
+
+part_name:
+ ident
+ {
+ partition_info *part_info= Lex->part_info;
+ partition_element *p_elem= part_info->curr_part_elem;
+ p_elem->partition_name= $1.str;
+ }
+ ;
+
+opt_part_values:
+ /* empty */
+ {
+ LEX *lex= Lex;
+ partition_info *part_info= lex->part_info;
+ if (! lex->is_partition_management())
+ {
+ if (part_info->part_type == RANGE_PARTITION)
+ {
+ my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0),
+ "RANGE", "LESS THAN");
+ MYSQL_YYABORT;
+ }
+ if (part_info->part_type == LIST_PARTITION)
+ {
+ my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0),
+ "LIST", "IN");
+ MYSQL_YYABORT;
+ }
+ }
+ else
+ part_info->part_type= HASH_PARTITION;
+ }
+ | VALUES LESS_SYM THAN_SYM
+ {
+ LEX *lex= Lex;
+ partition_info *part_info= lex->part_info;
+ if (! lex->is_partition_management())
+ {
+ if (part_info->part_type != RANGE_PARTITION)
+ {
+ my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0),
+ "RANGE", "LESS THAN");
+ MYSQL_YYABORT;
+ }
+ }
+ else
+ part_info->part_type= RANGE_PARTITION;
+ }
+ part_func_max {}
+ | VALUES IN_SYM
+ {
+ LEX *lex= Lex;
+ partition_info *part_info= lex->part_info;
+ if (! lex->is_partition_management())
+ {
+ if (part_info->part_type != LIST_PARTITION)
+ {
+ my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0),
+ "LIST", "IN");
+ MYSQL_YYABORT;
+ }
+ }
+ else
+ part_info->part_type= LIST_PARTITION;
+ }
+ part_values_in {}
+ ;
+
+part_func_max:
+ MAX_VALUE_SYM
+ {
+ partition_info *part_info= Lex->part_info;
+
+ if (part_info->num_columns &&
+ part_info->num_columns != 1U)
+ {
+ part_info->print_debug("Kilroy II", NULL);
+ my_parse_error(ER(ER_PARTITION_COLUMN_LIST_ERROR));
+ MYSQL_YYABORT;
+ }
+ else
+ part_info->num_columns= 1U;
+ if (part_info->init_column_part())
+ {
+ MYSQL_YYABORT;
+ }
+ if (part_info->add_max_value())
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ | part_value_item {}
+ ;
+
+part_values_in:
+ part_value_item
+ {
+ LEX *lex= Lex;
+ partition_info *part_info= lex->part_info;
+ part_info->print_debug("part_values_in: part_value_item", NULL);
+
+ if (part_info->num_columns != 1U)
+ {
+ if (!lex->is_partition_management() ||
+ part_info->num_columns == 0 ||
+ part_info->num_columns > MAX_REF_PARTS)
+ {
+ part_info->print_debug("Kilroy III", NULL);
+ my_parse_error(ER(ER_PARTITION_COLUMN_LIST_ERROR));
+ MYSQL_YYABORT;
+ }
+ /*
+ Reorganize the current large array into a list of small
+ arrays with one entry in each array. This can happen
+ in the first partition of an ALTER TABLE statement where
+ we ADD or REORGANIZE partitions. Also can only happen
+ for LIST partitions.
+ */
+ if (part_info->reorganize_into_single_field_col_val())
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ }
+ | '(' part_value_list ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ if (part_info->num_columns < 2U)
+ {
+ my_parse_error(ER(ER_ROW_SINGLE_PARTITION_FIELD_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+part_value_list:
+ part_value_item {}
+ | part_value_list ',' part_value_item {}
+ ;
+
+part_value_item:
+ '('
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->print_debug("( part_value_item", NULL);
+ /* Initialisation code needed for each list of value expressions */
+ if (!(part_info->part_type == LIST_PARTITION &&
+ part_info->num_columns == 1U) &&
+ part_info->init_column_part())
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ part_value_item_list {}
+ ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->print_debug(") part_value_item", NULL);
+ if (part_info->num_columns == 0)
+ part_info->num_columns= part_info->curr_list_object;
+ if (part_info->num_columns != part_info->curr_list_object)
+ {
+ /*
+ All value items lists must be of equal length, in some cases
+ which is covered by the above if-statement we don't know yet
+ how many columns is in the partition so the assignment above
+ ensures that we only report errors when we know we have an
+ error.
+ */
+ part_info->print_debug("Kilroy I", NULL);
+ my_parse_error(ER(ER_PARTITION_COLUMN_LIST_ERROR));
+ MYSQL_YYABORT;
+ }
+ part_info->curr_list_object= 0;
+ }
+ ;
+
+part_value_item_list:
+ part_value_expr_item {}
+ | part_value_item_list ',' part_value_expr_item {}
+ ;
+
+part_value_expr_item:
+ MAX_VALUE_SYM
+ {
+ partition_info *part_info= Lex->part_info;
+ if (part_info->part_type == LIST_PARTITION)
+ {
+ my_parse_error(ER(ER_MAXVALUE_IN_VALUES_IN));
+ MYSQL_YYABORT;
+ }
+ if (part_info->add_max_value())
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ | bit_expr
+ {
+ LEX *lex= Lex;
+ partition_info *part_info= lex->part_info;
+ Item *part_expr= $1;
+
+ if (!lex->safe_to_cache_query)
+ {
+ my_parse_error(ER(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR));
+ MYSQL_YYABORT;
+ }
+ if (part_info->add_column_list_value(thd, part_expr))
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+
+opt_sub_partition:
+ /* empty */
+ {
+ partition_info *part_info= Lex->part_info;
+ if (part_info->num_subparts != 0 &&
+ !part_info->use_default_subpartitions)
+ {
+ /*
+ We come here when we have defined subpartitions on the first
+ partition but not on all the subsequent partitions.
+ */
+ my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ | '(' sub_part_list ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ if (part_info->num_subparts != 0)
+ {
+ if (part_info->num_subparts !=
+ part_info->count_curr_subparts)
+ {
+ my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ else if (part_info->count_curr_subparts > 0)
+ {
+ if (part_info->partitions.elements > 1)
+ {
+ my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR));
+ MYSQL_YYABORT;
+ }
+ part_info->num_subparts= part_info->count_curr_subparts;
+ }
+ part_info->count_curr_subparts= 0;
+ }
+ ;
+
+sub_part_list:
+ sub_part_definition {}
+ | sub_part_list ',' sub_part_definition {}
+ ;
+
+sub_part_definition:
+ SUBPARTITION_SYM
+ {
+ partition_info *part_info= Lex->part_info;
+ partition_element *curr_part= part_info->current_partition;
+ partition_element *sub_p_elem= new partition_element(curr_part);
+ if (part_info->use_default_subpartitions &&
+ part_info->partitions.elements >= 2)
+ {
+ /*
+ create table t1 (a int)
+ partition by list (a) subpartition by hash (a)
+ (partition p0 values in (1),
+ partition p1 values in (2) subpartition sp11);
+ causes use to arrive since we are on the second
+ partition, but still use_default_subpartitions
+ is set. When we come here we're processing at least
+ the second partition (the current partition processed
+ have already been put into the partitions list.
+ */
+ my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR));
+ MYSQL_YYABORT;
+ }
+ if (!sub_p_elem ||
+ curr_part->subpartitions.push_back(sub_p_elem))
+ {
+ mem_alloc_error(sizeof(partition_element));
+ MYSQL_YYABORT;
+ }
+ part_info->curr_part_elem= sub_p_elem;
+ part_info->use_default_subpartitions= FALSE;
+ part_info->use_default_num_subpartitions= FALSE;
+ part_info->count_curr_subparts++;
+ }
+ sub_name opt_part_options {}
+ ;
+
+sub_name:
+ ident_or_text
+ { Lex->part_info->curr_part_elem->partition_name= $1.str; }
+ ;
+
+opt_part_options:
+ /* empty */ {}
+ | opt_part_option_list {}
+ ;
+
+opt_part_option_list:
+ opt_part_option_list opt_part_option {}
+ | opt_part_option {}
+ ;
+
+opt_part_option:
+ TABLESPACE opt_equal ident_or_text
+ { Lex->part_info->curr_part_elem->tablespace_name= $3.str; }
+ | opt_storage ENGINE_SYM opt_equal storage_engines
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->curr_part_elem->engine_type= $4;
+ part_info->default_engine_type= $4;
+ }
+ | CONNECTION_SYM opt_equal TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ lex->part_info->curr_part_elem->connect_string.str= $3.str;
+ lex->part_info->curr_part_elem->connect_string.length= $3.length;
+ }
+ | NODEGROUP_SYM opt_equal real_ulong_num
+ { Lex->part_info->curr_part_elem->nodegroup_id= (uint16) $3; }
+ | MAX_ROWS opt_equal real_ulonglong_num
+ { Lex->part_info->curr_part_elem->part_max_rows= (ha_rows) $3; }
+ | MIN_ROWS opt_equal real_ulonglong_num
+ { Lex->part_info->curr_part_elem->part_min_rows= (ha_rows) $3; }
+ | DATA_SYM DIRECTORY_SYM opt_equal TEXT_STRING_sys
+ { Lex->part_info->curr_part_elem->data_file_name= $4.str; }
+ | INDEX_SYM DIRECTORY_SYM opt_equal TEXT_STRING_sys
+ { Lex->part_info->curr_part_elem->index_file_name= $4.str; }
+ | COMMENT_SYM opt_equal TEXT_STRING_sys
+ { Lex->part_info->curr_part_elem->part_comment= $3.str; }
+ ;
+
+/*
+ End of partition parser part
+*/
+
+create_select:
+ SELECT_SYM
+ {
+ LEX *lex=Lex;
+ if (lex->sql_command == SQLCOM_INSERT)
+ lex->sql_command= SQLCOM_INSERT_SELECT;
+ else if (lex->sql_command == SQLCOM_REPLACE)
+ lex->sql_command= SQLCOM_REPLACE_SELECT;
+ /*
+ The following work only with the local list, the global list
+ is created correctly in this case
+ */
+ lex->current_select->table_list.save_and_clear(&lex->save_list);
+ mysql_init_select(lex);
+ lex->current_select->parsing_place= SELECT_LIST;
+ }
+ select_options select_item_list
+ {
+ Select->parsing_place= NO_MATTER;
+ }
+ opt_select_from
+ {
+ /*
+ The following work only with the local list, the global list
+ is created correctly in this case
+ */
+ Lex->current_select->table_list.push_front(&Lex->save_list);
+ }
+ ;
+
+opt_as:
+ /* empty */ {}
+ | AS {}
+ ;
+
+opt_create_database_options:
+ /* empty */ {}
+ | create_database_options {}
+ ;
+
+create_database_options:
+ create_database_option {}
+ | create_database_options create_database_option {}
+ ;
+
+create_database_option:
+ default_collation {}
+ | default_charset {}
+ ;
+
+opt_table_options:
+ /* empty */ { $$= 0; }
+ | table_options { $$= $1;}
+ ;
+
+table_options:
+ table_option { $$=$1; }
+ | table_option table_options { $$= $1 | $2; }
+ ;
+
+table_option:
+ TEMPORARY { $$=HA_LEX_CREATE_TMP_TABLE; }
+ ;
+
+opt_if_not_exists:
+ /* empty */
+ {
+ Lex->check_exists= FALSE;
+ $$= 0;
+ }
+ | IF not EXISTS
+ {
+ Lex->check_exists= TRUE;
+ $$=HA_LEX_CREATE_IF_NOT_EXISTS;
+ }
+ ;
+
+create_or_replace:
+ CREATE /* empty */
+ {
+ $$= 0;
+ }
+ | CREATE OR_SYM REPLACE
+ {
+ $$= HA_LEX_CREATE_REPLACE;
+ }
+ ;
+
+opt_create_table_options:
+ /* empty */
+ | create_table_options
+ ;
+
+create_table_options_space_separated:
+ create_table_option
+ | create_table_option create_table_options_space_separated
+ ;
+
+create_table_options:
+ create_table_option
+ | create_table_option create_table_options
+ | create_table_option ',' create_table_options
+ ;
+
+create_table_option:
+ ENGINE_SYM opt_equal storage_engines
+ {
+ Lex->create_info.db_type= $3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_ENGINE;
+ }
+ | MAX_ROWS opt_equal ulonglong_num
+ {
+ Lex->create_info.max_rows= $3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_MAX_ROWS;
+ }
+ | MIN_ROWS opt_equal ulonglong_num
+ {
+ Lex->create_info.min_rows= $3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_MIN_ROWS;
+ }
+ | AVG_ROW_LENGTH opt_equal ulong_num
+ {
+ Lex->create_info.avg_row_length=$3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_AVG_ROW_LENGTH;
+ }
+ | PASSWORD opt_equal TEXT_STRING_sys
+ {
+ Lex->create_info.password=$3.str;
+ Lex->create_info.used_fields|= HA_CREATE_USED_PASSWORD;
+ }
+ | COMMENT_SYM opt_equal TEXT_STRING_sys
+ {
+ Lex->create_info.comment=$3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_COMMENT;
+ }
+ | AUTO_INC opt_equal ulonglong_num
+ {
+ Lex->create_info.auto_increment_value=$3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_AUTO;
+ }
+ | PACK_KEYS_SYM opt_equal ulong_num
+ {
+ switch($3) {
+ case 0:
+ Lex->create_info.table_options|= HA_OPTION_NO_PACK_KEYS;
+ break;
+ case 1:
+ Lex->create_info.table_options|= HA_OPTION_PACK_KEYS;
+ break;
+ default:
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ Lex->create_info.used_fields|= HA_CREATE_USED_PACK_KEYS;
+ }
+ | PACK_KEYS_SYM opt_equal DEFAULT
+ {
+ Lex->create_info.table_options&=
+ ~(HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS);
+ Lex->create_info.used_fields|= HA_CREATE_USED_PACK_KEYS;
+ }
+ | STATS_AUTO_RECALC_SYM opt_equal ulong_num
+ {
+ switch($3) {
+ case 0:
+ Lex->create_info.stats_auto_recalc= HA_STATS_AUTO_RECALC_OFF;
+ break;
+ case 1:
+ Lex->create_info.stats_auto_recalc= HA_STATS_AUTO_RECALC_ON;
+ break;
+ default:
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ Lex->create_info.used_fields|= HA_CREATE_USED_STATS_AUTO_RECALC;
+ }
+ | STATS_AUTO_RECALC_SYM opt_equal DEFAULT
+ {
+ Lex->create_info.stats_auto_recalc= HA_STATS_AUTO_RECALC_DEFAULT;
+ Lex->create_info.used_fields|= HA_CREATE_USED_STATS_AUTO_RECALC;
+ }
+ | STATS_PERSISTENT_SYM opt_equal ulong_num
+ {
+ switch($3) {
+ case 0:
+ Lex->create_info.table_options|= HA_OPTION_NO_STATS_PERSISTENT;
+ break;
+ case 1:
+ Lex->create_info.table_options|= HA_OPTION_STATS_PERSISTENT;
+ break;
+ default:
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ Lex->create_info.used_fields|= HA_CREATE_USED_STATS_PERSISTENT;
+ }
+ | STATS_PERSISTENT_SYM opt_equal DEFAULT
+ {
+ Lex->create_info.table_options&=
+ ~(HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT);
+ Lex->create_info.used_fields|= HA_CREATE_USED_STATS_PERSISTENT;
+ }
+ | STATS_SAMPLE_PAGES_SYM opt_equal ulong_num
+ {
+ /* From user point of view STATS_SAMPLE_PAGES can be specified as
+ STATS_SAMPLE_PAGES=N (where 0<N<=65535, it does not make sense to
+ scan 0 pages) or STATS_SAMPLE_PAGES=default. Internally we record
+ =default as 0. See create_frm() in sql/table.cc, we use only two
+ bytes for stats_sample_pages and this is why we do not allow
+ larger values. 65535 pages, 16kb each means to sample 1GB, which
+ is impractical. If at some point this needs to be extended, then
+ we can store the higher bits from stats_sample_pages in .frm too. */
+ if ($3 == 0 || $3 > 0xffff)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ Lex->create_info.stats_sample_pages=$3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_STATS_SAMPLE_PAGES;
+ }
+ | STATS_SAMPLE_PAGES_SYM opt_equal DEFAULT
+ {
+ Lex->create_info.stats_sample_pages=0;
+ Lex->create_info.used_fields|= HA_CREATE_USED_STATS_SAMPLE_PAGES;
+ }
+ | CHECKSUM_SYM opt_equal ulong_num
+ {
+ Lex->create_info.table_options|= $3 ? HA_OPTION_CHECKSUM : HA_OPTION_NO_CHECKSUM;
+ Lex->create_info.used_fields|= HA_CREATE_USED_CHECKSUM;
+ }
+ | TABLE_CHECKSUM_SYM opt_equal ulong_num
+ {
+ Lex->create_info.table_options|= $3 ? HA_OPTION_CHECKSUM : HA_OPTION_NO_CHECKSUM;
+ Lex->create_info.used_fields|= HA_CREATE_USED_CHECKSUM;
+ }
+ | PAGE_CHECKSUM_SYM opt_equal choice
+ {
+ Lex->create_info.used_fields|= HA_CREATE_USED_PAGE_CHECKSUM;
+ Lex->create_info.page_checksum= $3;
+ }
+ | DELAY_KEY_WRITE_SYM opt_equal ulong_num
+ {
+ Lex->create_info.table_options|= $3 ? HA_OPTION_DELAY_KEY_WRITE : HA_OPTION_NO_DELAY_KEY_WRITE;
+ Lex->create_info.used_fields|= HA_CREATE_USED_DELAY_KEY_WRITE;
+ }
+ | ROW_FORMAT_SYM opt_equal row_types
+ {
+ Lex->create_info.row_type= $3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_ROW_FORMAT;
+ }
+ | UNION_SYM opt_equal
+ {
+ Lex->select_lex.table_list.save_and_clear(&Lex->save_list);
+ }
+ '(' opt_table_list ')'
+ {
+ /*
+ Move the union list to the merge_list and exclude its tables
+ from the global list.
+ */
+ LEX *lex=Lex;
+ lex->create_info.merge_list= lex->select_lex.table_list;
+ lex->select_lex.table_list= lex->save_list;
+ /*
+ When excluding union list from the global list we assume that
+ elements of the former immediately follow elements which represent
+ table being created/altered and parent tables.
+ */
+ TABLE_LIST *last_non_sel_table= lex->create_last_non_select_table;
+ DBUG_ASSERT(last_non_sel_table->next_global ==
+ lex->create_info.merge_list.first);
+ last_non_sel_table->next_global= 0;
+ Lex->query_tables_last= &last_non_sel_table->next_global;
+
+ lex->create_info.used_fields|= HA_CREATE_USED_UNION;
+ }
+ | default_charset
+ | default_collation
+ | INSERT_METHOD opt_equal merge_insert_types
+ {
+ Lex->create_info.merge_insert_method= $3;
+ Lex->create_info.used_fields|= HA_CREATE_USED_INSERT_METHOD;
+ }
+ | DATA_SYM DIRECTORY_SYM opt_equal TEXT_STRING_sys
+ {
+ Lex->create_info.data_file_name= $4.str;
+ Lex->create_info.used_fields|= HA_CREATE_USED_DATADIR;
+ }
+ | INDEX_SYM DIRECTORY_SYM opt_equal TEXT_STRING_sys
+ {
+ Lex->create_info.index_file_name= $4.str;
+ Lex->create_info.used_fields|= HA_CREATE_USED_INDEXDIR;
+ }
+ | TABLESPACE ident
+ {Lex->create_info.tablespace= $2.str;}
+ | STORAGE_SYM DISK_SYM
+ {Lex->create_info.storage_media= HA_SM_DISK;}
+ | STORAGE_SYM MEMORY_SYM
+ {Lex->create_info.storage_media= HA_SM_MEMORY;}
+ | CONNECTION_SYM opt_equal TEXT_STRING_sys
+ {
+ Lex->create_info.connect_string.str= $3.str;
+ Lex->create_info.connect_string.length= $3.length;
+ Lex->create_info.used_fields|= HA_CREATE_USED_CONNECTION;
+ }
+ | KEY_BLOCK_SIZE opt_equal ulong_num
+ {
+ Lex->create_info.used_fields|= HA_CREATE_USED_KEY_BLOCK_SIZE;
+ Lex->create_info.key_block_size= $3;
+ }
+ | TRANSACTIONAL_SYM opt_equal choice
+ {
+ Lex->create_info.used_fields|= HA_CREATE_USED_TRANSACTIONAL;
+ Lex->create_info.transactional= $3;
+ }
+ | IDENT_sys equal TEXT_STRING_sys
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, true, &Lex->create_info.option_list,
+ &Lex->option_list_last);
+ }
+ | IDENT_sys equal ident
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, false, &Lex->create_info.option_list,
+ &Lex->option_list_last);
+ }
+ | IDENT_sys equal real_ulonglong_num
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, &Lex->create_info.option_list,
+ &Lex->option_list_last, thd->mem_root);
+ }
+ | IDENT_sys equal DEFAULT
+ {
+ new (thd->mem_root)
+ engine_option_value($1, &Lex->create_info.option_list,
+ &Lex->option_list_last);
+ }
+ ;
+
+default_charset:
+ opt_default charset opt_equal charset_name_or_default
+ {
+ if (Lex->create_info.add_table_option_default_charset($4))
+ MYSQL_YYABORT;
+ }
+ ;
+
+default_collation:
+ opt_default COLLATE_SYM opt_equal collation_name_or_default
+ {
+ HA_CREATE_INFO *cinfo= &Lex->create_info;
+ if ((cinfo->used_fields & HA_CREATE_USED_DEFAULT_CHARSET) &&
+ cinfo->default_table_charset && $4 &&
+ !($4= merge_charset_and_collation(cinfo->default_table_charset,
+ $4)))
+ {
+ MYSQL_YYABORT;
+ }
+
+ Lex->create_info.default_table_charset= $4;
+ Lex->create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET;
+ }
+ ;
+
+storage_engines:
+ ident_or_text
+ {
+ plugin_ref plugin= ha_resolve_by_name(thd, &$1);
+
+ if (plugin)
+ $$= plugin_hton(plugin);
+ else
+ {
+ if (thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION)
+ {
+ my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ $$= 0;
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_UNKNOWN_STORAGE_ENGINE,
+ ER(ER_UNKNOWN_STORAGE_ENGINE),
+ $1.str);
+ }
+ }
+ ;
+
+known_storage_engines:
+ ident_or_text
+ {
+ plugin_ref plugin;
+ if ((plugin= ha_resolve_by_name(thd, &$1)))
+ $$= plugin_hton(plugin);
+ else
+ {
+ my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+row_types:
+ DEFAULT { $$= ROW_TYPE_DEFAULT; }
+ | FIXED_SYM { $$= ROW_TYPE_FIXED; }
+ | DYNAMIC_SYM { $$= ROW_TYPE_DYNAMIC; }
+ | COMPRESSED_SYM { $$= ROW_TYPE_COMPRESSED; }
+ | REDUNDANT_SYM { $$= ROW_TYPE_REDUNDANT; }
+ | COMPACT_SYM { $$= ROW_TYPE_COMPACT; }
+ | PAGE_SYM { $$= ROW_TYPE_PAGE; }
+ ;
+
+merge_insert_types:
+ NO_SYM { $$= MERGE_INSERT_DISABLED; }
+ | FIRST_SYM { $$= MERGE_INSERT_TO_FIRST; }
+ | LAST_SYM { $$= MERGE_INSERT_TO_LAST; }
+ ;
+
+opt_select_from:
+ opt_limit_clause {}
+ | select_from select_lock_type
+ ;
+
+udf_type:
+ STRING_SYM {$$ = (int) STRING_RESULT; }
+ | REAL {$$ = (int) REAL_RESULT; }
+ | DECIMAL_SYM {$$ = (int) DECIMAL_RESULT; }
+ | INT_SYM {$$ = (int) INT_RESULT; }
+ ;
+
+
+create_field_list:
+ field_list
+ {
+ Lex->create_last_non_select_table= Lex->last_table();
+ }
+ ;
+
+field_list:
+ field_list_item
+ | field_list ',' field_list_item
+ ;
+
+field_list_item:
+ column_def
+ | key_def
+ ;
+
+column_def:
+ field_spec opt_check_constraint
+ | field_spec references
+ {
+ Lex->col_list.empty(); /* Alloced by sql_alloc */
+ }
+ ;
+
+key_def:
+ normal_key_type opt_if_not_exists_ident key_alg '(' key_list ')'
+ { Lex->option_list= NULL; }
+ normal_key_options
+ {
+ if (add_create_index (Lex, $1, $2))
+ MYSQL_YYABORT;
+ }
+ | fulltext opt_key_or_index opt_if_not_exists_ident init_key_options
+ '(' key_list ')'
+ { Lex->option_list= NULL; }
+ fulltext_key_options
+ {
+ if (add_create_index (Lex, $1, $3))
+ MYSQL_YYABORT;
+ }
+ | spatial opt_key_or_index opt_if_not_exists_ident init_key_options
+ '(' key_list ')'
+ { Lex->option_list= NULL; }
+ spatial_key_options
+ {
+ if (add_create_index (Lex, $1, $3))
+ MYSQL_YYABORT;
+ }
+ | opt_constraint constraint_key_type opt_if_not_exists_ident key_alg
+ '(' key_list ')'
+ { Lex->option_list= NULL; }
+ normal_key_options
+ {
+ if (add_create_index (Lex, $2, $3.str ? $3 : $1))
+ MYSQL_YYABORT;
+ }
+ | opt_constraint FOREIGN KEY_SYM opt_if_not_exists_ident '(' key_list ')' references
+ {
+ LEX *lex=Lex;
+ Key *key= new Foreign_key($4.str ? $4 : $1, lex->col_list,
+ $8->db,
+ $8->table,
+ lex->ref_list,
+ lex->fk_delete_opt,
+ lex->fk_update_opt,
+ lex->fk_match_option,
+ lex->check_exists);
+ if (key == NULL)
+ MYSQL_YYABORT;
+ lex->alter_info.key_list.push_back(key);
+ lex->option_list= NULL;
+ if (add_create_index (lex, Key::MULTIPLE, $1.str ? $1 : $4,
+ &default_key_create_info, 1))
+ MYSQL_YYABORT;
+ /* Only used for ALTER TABLE. Ignored otherwise. */
+ lex->alter_info.flags|= Alter_info::ADD_FOREIGN_KEY;
+ }
+ | opt_constraint check_constraint
+ {
+ Lex->col_list.empty(); /* Alloced by sql_alloc */
+ }
+ ;
+
+opt_check_constraint:
+ /* empty */
+ | check_constraint
+ ;
+
+check_constraint:
+ CHECK_SYM '(' expr ')'
+ ;
+
+opt_constraint:
+ /* empty */ { $$= null_lex_str; }
+ | constraint { $$= $1; }
+ ;
+
+constraint:
+ CONSTRAINT opt_ident { $$=$2; }
+ ;
+
+field_spec:
+ field_ident
+ {
+ LEX *lex=Lex;
+ lex->length=lex->dec=0;
+ lex->type=0;
+ lex->default_value= lex->on_update_value= 0;
+ lex->comment=null_lex_str;
+ lex->charset=NULL;
+ lex->vcol_info= 0;
+ lex->option_list= NULL;
+ }
+ field_def
+ {
+ LEX *lex=Lex;
+ if (add_field_to_list(lex->thd, &$1, $3.type,
+ $3.length, $3.dec, lex->type,
+ lex->default_value, lex->on_update_value,
+ &lex->comment,
+ lex->change, &lex->interval_list, $3.charset,
+ lex->uint_geom_type,
+ lex->vcol_info, lex->option_list))
+ MYSQL_YYABORT;
+ }
+ ;
+
+field_def:
+ type opt_attribute
+ { $$.set($1, Lex->length, Lex->dec, Lex->charset); }
+ | type opt_generated_always AS
+ { $<lex_type>$.set($1, Lex->length, Lex->dec, Lex->charset); }
+ '(' virtual_column_func ')' vcol_opt_specifier vcol_opt_attribute
+ {
+ $$= $<lex_type>4;
+ Lex->vcol_info->set_field_type($$.type);
+ $$.type= (enum enum_field_types)MYSQL_TYPE_VIRTUAL;
+ }
+ ;
+
+opt_generated_always:
+ /* empty */
+ | GENERATED_SYM ALWAYS_SYM {}
+ ;
+
+vcol_opt_specifier:
+ /* empty */
+ {
+ Lex->vcol_info->set_stored_in_db_flag(FALSE);
+ }
+ | VIRTUAL_SYM
+ {
+ Lex->vcol_info->set_stored_in_db_flag(FALSE);
+ }
+ | PERSISTENT_SYM
+ {
+ Lex->vcol_info->set_stored_in_db_flag(TRUE);
+ }
+ ;
+
+vcol_opt_attribute:
+ /* empty */ {}
+ | vcol_opt_attribute_list {}
+ ;
+
+vcol_opt_attribute_list:
+ vcol_opt_attribute_list vcol_attribute {}
+ | vcol_attribute
+ ;
+
+vcol_attribute:
+ UNIQUE_SYM
+ {
+ LEX *lex=Lex;
+ lex->type|= UNIQUE_FLAG;
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX;
+ }
+ | UNIQUE_SYM KEY_SYM
+ {
+ LEX *lex=Lex;
+ lex->type|= UNIQUE_KEY_FLAG;
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX;
+ }
+ | COMMENT_SYM TEXT_STRING_sys { Lex->comment= $2; }
+ ;
+
+parse_vcol_expr:
+ PARSE_VCOL_EXPR_SYM '(' virtual_column_func ')'
+ {
+ /*
+ "PARSE_VCOL_EXPR" can only be used by the SQL server
+ when reading a '*.frm' file.
+ Prevent the end user from invoking this command.
+ */
+ if (!Lex->parse_vcol_expr)
+ {
+ my_message(ER_SYNTAX_ERROR, ER(ER_SYNTAX_ERROR), MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+virtual_column_func:
+ remember_name expr remember_end
+ {
+ Lex->vcol_info= new Virtual_column_info();
+ if (!Lex->vcol_info)
+ {
+ mem_alloc_error(sizeof(Virtual_column_info));
+ MYSQL_YYABORT;
+ }
+ uint expr_len= (uint)($3 - $1) - 1;
+ Lex->vcol_info->expr_str.str= (char* ) sql_memdup($1 + 1, expr_len);
+ Lex->vcol_info->expr_str.length= expr_len;
+ Lex->vcol_info->expr_item= $2;
+ }
+ ;
+
+type:
+ int_type opt_field_length field_options { $$=$1; }
+ | real_type opt_precision field_options { $$=$1; }
+ | FLOAT_SYM float_options field_options { $$=MYSQL_TYPE_FLOAT; }
+ | BIT_SYM
+ {
+ Lex->length= (char*) "1";
+ $$=MYSQL_TYPE_BIT;
+ }
+ | BIT_SYM field_length
+ {
+ $$=MYSQL_TYPE_BIT;
+ }
+ | BOOL_SYM
+ {
+ Lex->length= (char*) "1";
+ $$=MYSQL_TYPE_TINY;
+ }
+ | BOOLEAN_SYM
+ {
+ Lex->length= (char*) "1";
+ $$=MYSQL_TYPE_TINY;
+ }
+ | char field_length opt_binary
+ {
+ $$=MYSQL_TYPE_STRING;
+ }
+ | char opt_binary
+ {
+ Lex->length= (char*) "1";
+ $$=MYSQL_TYPE_STRING;
+ }
+ | nchar field_length opt_bin_mod
+ {
+ $$=MYSQL_TYPE_STRING;
+ Lex->charset=national_charset_info;
+ }
+ | nchar opt_bin_mod
+ {
+ Lex->length= (char*) "1";
+ $$=MYSQL_TYPE_STRING;
+ Lex->charset=national_charset_info;
+ }
+ | BINARY field_length
+ {
+ Lex->charset=&my_charset_bin;
+ $$=MYSQL_TYPE_STRING;
+ }
+ | BINARY
+ {
+ Lex->length= (char*) "1";
+ Lex->charset=&my_charset_bin;
+ $$=MYSQL_TYPE_STRING;
+ }
+ | varchar field_length opt_binary
+ {
+ $$= MYSQL_TYPE_VARCHAR;
+ }
+ | nvarchar field_length opt_bin_mod
+ {
+ $$= MYSQL_TYPE_VARCHAR;
+ Lex->charset=national_charset_info;
+ }
+ | VARBINARY field_length
+ {
+ Lex->charset=&my_charset_bin;
+ $$= MYSQL_TYPE_VARCHAR;
+ }
+ | YEAR_SYM opt_field_length field_options
+ {
+ if (Lex->length)
+ {
+ errno= 0;
+ ulong length= strtoul(Lex->length, NULL, 10);
+ if (errno == 0 && length <= MAX_FIELD_BLOBLENGTH && length != 4)
+ {
+ char buff[sizeof("YEAR()") + MY_INT64_NUM_DECIMAL_DIGITS + 1];
+ my_snprintf(buff, sizeof(buff), "YEAR(%lu)", length);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_WARN_DEPRECATED_SYNTAX,
+ ER(ER_WARN_DEPRECATED_SYNTAX),
+ buff, "YEAR(4)");
+ }
+ }
+ $$=MYSQL_TYPE_YEAR;
+ }
+ | DATE_SYM
+ { $$=MYSQL_TYPE_DATE; }
+ | TIME_SYM opt_field_length
+ { $$=MYSQL_TYPE_TIME; }
+ | TIMESTAMP opt_field_length
+ {
+ if (thd->variables.sql_mode & MODE_MAXDB)
+ $$=MYSQL_TYPE_DATETIME;
+ else
+ {
+ /*
+ Unlike other types TIMESTAMP fields are NOT NULL by default.
+ */
+ Lex->type|= NOT_NULL_FLAG;
+ $$=MYSQL_TYPE_TIMESTAMP;
+ }
+ }
+ | DATETIME opt_field_length
+ { $$=MYSQL_TYPE_DATETIME; }
+ | TINYBLOB
+ {
+ Lex->charset=&my_charset_bin;
+ $$=MYSQL_TYPE_TINY_BLOB;
+ }
+ | BLOB_SYM opt_field_length
+ {
+ Lex->charset=&my_charset_bin;
+ $$=MYSQL_TYPE_BLOB;
+ }
+ | spatial_type
+ {
+#ifdef HAVE_SPATIAL
+ Lex->charset=&my_charset_bin;
+ Lex->uint_geom_type= (uint)$1;
+ $$=MYSQL_TYPE_GEOMETRY;
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0),
+ sym_group_geom.name, sym_group_geom.needed_define);
+ MYSQL_YYABORT;
+#endif
+ }
+ | MEDIUMBLOB
+ {
+ Lex->charset=&my_charset_bin;
+ $$=MYSQL_TYPE_MEDIUM_BLOB;
+ }
+ | LONGBLOB
+ {
+ Lex->charset=&my_charset_bin;
+ $$=MYSQL_TYPE_LONG_BLOB;
+ }
+ | LONG_SYM VARBINARY
+ {
+ Lex->charset=&my_charset_bin;
+ $$=MYSQL_TYPE_MEDIUM_BLOB;
+ }
+ | LONG_SYM varchar opt_binary
+ { $$=MYSQL_TYPE_MEDIUM_BLOB; }
+ | TINYTEXT opt_binary
+ { $$=MYSQL_TYPE_TINY_BLOB; }
+ | TEXT_SYM opt_field_length opt_binary
+ { $$=MYSQL_TYPE_BLOB; }
+ | MEDIUMTEXT opt_binary
+ { $$=MYSQL_TYPE_MEDIUM_BLOB; }
+ | LONGTEXT opt_binary
+ { $$=MYSQL_TYPE_LONG_BLOB; }
+ | DECIMAL_SYM float_options field_options
+ { $$=MYSQL_TYPE_NEWDECIMAL;}
+ | NUMERIC_SYM float_options field_options
+ { $$=MYSQL_TYPE_NEWDECIMAL;}
+ | FIXED_SYM float_options field_options
+ { $$=MYSQL_TYPE_NEWDECIMAL;}
+ | ENUM
+ {Lex->interval_list.empty();}
+ '(' string_list ')' opt_binary
+ { $$=MYSQL_TYPE_ENUM; }
+ | SET
+ { Lex->interval_list.empty();}
+ '(' string_list ')' opt_binary
+ { $$=MYSQL_TYPE_SET; }
+ | LONG_SYM opt_binary
+ { $$=MYSQL_TYPE_MEDIUM_BLOB; }
+ | SERIAL_SYM
+ {
+ $$=MYSQL_TYPE_LONGLONG;
+ Lex->type|= (AUTO_INCREMENT_FLAG | NOT_NULL_FLAG | UNSIGNED_FLAG |
+ UNIQUE_FLAG);
+ }
+ ;
+
+spatial_type:
+ GEOMETRY_SYM { $$= Field::GEOM_GEOMETRY; }
+ | GEOMETRYCOLLECTION { $$= Field::GEOM_GEOMETRYCOLLECTION; }
+ | POINT_SYM
+ {
+ Lex->length= const_cast<char*>(STRINGIFY_ARG
+ (MAX_LEN_GEOM_POINT_FIELD));
+ $$= Field::GEOM_POINT;
+ }
+ | MULTIPOINT { $$= Field::GEOM_MULTIPOINT; }
+ | LINESTRING { $$= Field::GEOM_LINESTRING; }
+ | MULTILINESTRING { $$= Field::GEOM_MULTILINESTRING; }
+ | POLYGON { $$= Field::GEOM_POLYGON; }
+ | MULTIPOLYGON { $$= Field::GEOM_MULTIPOLYGON; }
+ ;
+
+char:
+ CHAR_SYM {}
+ ;
+
+nchar:
+ NCHAR_SYM {}
+ | NATIONAL_SYM CHAR_SYM {}
+ ;
+
+varchar:
+ char VARYING {}
+ | VARCHAR {}
+ ;
+
+nvarchar:
+ NATIONAL_SYM VARCHAR {}
+ | NVARCHAR_SYM {}
+ | NCHAR_SYM VARCHAR {}
+ | NATIONAL_SYM CHAR_SYM VARYING {}
+ | NCHAR_SYM VARYING {}
+ ;
+
+int_type:
+ INT_SYM { $$=MYSQL_TYPE_LONG; }
+ | TINYINT { $$=MYSQL_TYPE_TINY; }
+ | SMALLINT { $$=MYSQL_TYPE_SHORT; }
+ | MEDIUMINT { $$=MYSQL_TYPE_INT24; }
+ | BIGINT { $$=MYSQL_TYPE_LONGLONG; }
+ ;
+
+real_type:
+ REAL
+ {
+ $$= thd->variables.sql_mode & MODE_REAL_AS_FLOAT ?
+ MYSQL_TYPE_FLOAT : MYSQL_TYPE_DOUBLE;
+ }
+ | DOUBLE_SYM
+ { $$=MYSQL_TYPE_DOUBLE; }
+ | DOUBLE_SYM PRECISION
+ { $$=MYSQL_TYPE_DOUBLE; }
+ ;
+
+float_options:
+ /* empty */
+ { Lex->dec=Lex->length= (char*)0; }
+ | field_length
+ { Lex->dec= (char*)0; }
+ | precision
+ {}
+ ;
+
+precision:
+ '(' NUM ',' NUM ')'
+ {
+ LEX *lex=Lex;
+ lex->length=$2.str;
+ lex->dec=$4.str;
+ }
+ ;
+
+field_options:
+ /* empty */ {}
+ | field_opt_list {}
+ ;
+
+field_opt_list:
+ field_opt_list field_option {}
+ | field_option {}
+ ;
+
+field_option:
+ SIGNED_SYM {}
+ | UNSIGNED { Lex->type|= UNSIGNED_FLAG;}
+ | ZEROFILL { Lex->type|= UNSIGNED_FLAG | ZEROFILL_FLAG; }
+ ;
+
+field_length:
+ '(' LONG_NUM ')' { Lex->length= $2.str; }
+ | '(' ULONGLONG_NUM ')' { Lex->length= $2.str; }
+ | '(' DECIMAL_NUM ')' { Lex->length= $2.str; }
+ | '(' NUM ')' { Lex->length= $2.str; };
+
+opt_field_length:
+ /* empty */ { Lex->length=(char*) 0; /* use default length */ }
+ | field_length { }
+ ;
+
+opt_precision:
+ /* empty */ {}
+ | precision {}
+ ;
+
+opt_attribute:
+ /* empty */ {}
+ | opt_attribute_list {}
+ ;
+
+opt_attribute_list:
+ opt_attribute_list attribute {}
+ | attribute
+ ;
+
+attribute:
+ NULL_SYM { Lex->type&= ~ NOT_NULL_FLAG; }
+ | not NULL_SYM { Lex->type|= NOT_NULL_FLAG; }
+ | DEFAULT now_or_signed_literal { Lex->default_value=$2; }
+ | ON UPDATE_SYM NOW_SYM opt_default_time_precision
+ {
+ Item *item= new (thd->mem_root) Item_func_now_local($4);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ Lex->on_update_value= item;
+ }
+ | AUTO_INC { Lex->type|= AUTO_INCREMENT_FLAG | NOT_NULL_FLAG; }
+ | SERIAL_SYM DEFAULT VALUE_SYM
+ {
+ LEX *lex=Lex;
+ lex->type|= AUTO_INCREMENT_FLAG | NOT_NULL_FLAG | UNIQUE_FLAG;
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX;
+ }
+ | opt_primary KEY_SYM
+ {
+ LEX *lex=Lex;
+ lex->type|= PRI_KEY_FLAG | NOT_NULL_FLAG;
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX;
+ }
+ | UNIQUE_SYM
+ {
+ LEX *lex=Lex;
+ lex->type|= UNIQUE_FLAG;
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX;
+ }
+ | UNIQUE_SYM KEY_SYM
+ {
+ LEX *lex=Lex;
+ lex->type|= UNIQUE_KEY_FLAG;
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX;
+ }
+ | COMMENT_SYM TEXT_STRING_sys { Lex->comment= $2; }
+ | COLLATE_SYM collation_name
+ {
+ if (Lex->charset && !my_charset_same(Lex->charset,$2))
+ {
+ my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0),
+ $2->name,Lex->charset->csname);
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ Lex->charset=$2;
+ }
+ }
+ | IDENT_sys equal TEXT_STRING_sys
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, true, &Lex->option_list,
+ &Lex->option_list_last);
+ }
+ | IDENT_sys equal ident
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, false, &Lex->option_list,
+ &Lex->option_list_last);
+ }
+ | IDENT_sys equal real_ulonglong_num
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, &Lex->option_list,
+ &Lex->option_list_last, thd->mem_root);
+ }
+ | IDENT_sys equal DEFAULT
+ {
+ new (thd->mem_root)
+ engine_option_value($1, &Lex->option_list, &Lex->option_list_last);
+ }
+ ;
+
+
+type_with_opt_collate:
+ type opt_collate
+ {
+ $$= $1;
+
+ if (Lex->charset) /* Lex->charset is scanned in "type" */
+ {
+ if (!(Lex->charset= merge_charset_and_collation(Lex->charset, $2)))
+ MYSQL_YYABORT;
+ }
+ else if ($2)
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "COLLATE with no CHARACTER SET "
+ "in SP parameters, RETURNS, DECLARE");
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+
+now_or_signed_literal:
+ NOW_SYM opt_default_time_precision
+ {
+ $$= new (thd->mem_root) Item_func_now_local($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | signed_literal
+ { $$=$1; }
+ ;
+
+charset:
+ CHAR_SYM SET {}
+ | CHARSET {}
+ ;
+
+charset_name:
+ ident_or_text
+ {
+ if (!($$=get_charset_by_csname($1.str,MY_CS_PRIMARY,MYF(0))))
+ {
+ my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ }
+ | BINARY { $$= &my_charset_bin; }
+ ;
+
+charset_name_or_default:
+ charset_name { $$=$1; }
+ | DEFAULT { $$=NULL; }
+ ;
+
+opt_load_data_charset:
+ /* Empty */ { $$= NULL; }
+ | charset charset_name_or_default { $$= $2; }
+ ;
+
+old_or_new_charset_name:
+ ident_or_text
+ {
+ if (!($$=get_charset_by_csname($1.str,MY_CS_PRIMARY,MYF(0))) &&
+ !($$=get_old_charset_by_name($1.str)))
+ {
+ my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ }
+ | BINARY { $$= &my_charset_bin; }
+ ;
+
+old_or_new_charset_name_or_default:
+ old_or_new_charset_name { $$=$1; }
+ | DEFAULT { $$=NULL; }
+ ;
+
+collation_name:
+ ident_or_text
+ {
+ if (!($$= mysqld_collation_get_by_name($1.str)))
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_collate:
+ /* empty */ { $$=NULL; }
+ | COLLATE_SYM collation_name_or_default { $$=$2; }
+ ;
+
+collation_name_or_default:
+ collation_name { $$=$1; }
+ | DEFAULT { $$=NULL; }
+ ;
+
+opt_default:
+ /* empty */ {}
+ | DEFAULT {}
+ ;
+
+
+ascii:
+ ASCII_SYM { Lex->charset= &my_charset_latin1; }
+ | BINARY ASCII_SYM
+ {
+ Lex->charset= &my_charset_latin1_bin;
+ }
+ | ASCII_SYM BINARY
+ {
+ Lex->charset= &my_charset_latin1_bin;
+ }
+ ;
+
+unicode:
+ UNICODE_SYM
+ {
+ if (!(Lex->charset=get_charset_by_csname("ucs2",
+ MY_CS_PRIMARY,MYF(0))))
+ {
+ my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), "ucs2");
+ MYSQL_YYABORT;
+ }
+ }
+ | UNICODE_SYM BINARY
+ {
+ if (!(Lex->charset= mysqld_collation_get_by_name("ucs2_bin")))
+ MYSQL_YYABORT;
+ }
+ | BINARY UNICODE_SYM
+ {
+ if (!(Lex->charset= mysqld_collation_get_by_name("ucs2_bin")))
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_binary:
+ /* empty */ { Lex->charset=NULL; }
+ | ascii
+ | unicode
+ | BYTE_SYM { Lex->charset=&my_charset_bin; }
+ | charset charset_name opt_bin_mod { Lex->charset=$2; }
+ | BINARY
+ {
+ Lex->charset= NULL;
+ Lex->type|= BINCMP_FLAG;
+ }
+ | BINARY charset charset_name
+ {
+ Lex->charset= $3;
+ Lex->type|= BINCMP_FLAG;
+ }
+ ;
+
+opt_bin_mod:
+ /* empty */ { }
+ | BINARY { Lex->type|= BINCMP_FLAG; }
+ ;
+
+ws_nweights:
+ '(' real_ulong_num
+ {
+ if ($2 == 0)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ')'
+ { $$= $2; }
+ ;
+
+ws_level_flag_desc:
+ ASC { $$= 0; }
+ | DESC { $$= 1 << MY_STRXFRM_DESC_SHIFT; }
+ ;
+
+ws_level_flag_reverse:
+ REVERSE_SYM { $$= 1 << MY_STRXFRM_REVERSE_SHIFT; } ;
+
+ws_level_flags:
+ /* empty */ { $$= 0; }
+ | ws_level_flag_desc { $$= $1; }
+ | ws_level_flag_desc ws_level_flag_reverse { $$= $1 | $2; }
+ | ws_level_flag_reverse { $$= $1 ; }
+ ;
+
+ws_level_number:
+ real_ulong_num
+ {
+ $$= $1 < 1 ? 1 : ($1 > MY_STRXFRM_NLEVELS ? MY_STRXFRM_NLEVELS : $1);
+ $$--;
+ }
+ ;
+
+ws_level_list_item:
+ ws_level_number ws_level_flags
+ {
+ $$= (1 | $2) << $1;
+ }
+ ;
+
+ws_level_list:
+ ws_level_list_item { $$= $1; }
+ | ws_level_list ',' ws_level_list_item { $$|= $3; }
+ ;
+
+ws_level_range:
+ ws_level_number '-' ws_level_number
+ {
+ uint start= $1;
+ uint end= $3;
+ for ($$= 0; start <= end; start++)
+ $$|= (1 << start);
+ }
+ ;
+
+ws_level_list_or_range:
+ ws_level_list { $$= $1; }
+ | ws_level_range { $$= $1; }
+ ;
+
+opt_ws_levels:
+ /* empty*/ { $$= 0; }
+ | LEVEL_SYM ws_level_list_or_range { $$= $2; }
+ ;
+
+opt_primary:
+ /* empty */
+ | PRIMARY_SYM
+ ;
+
+references:
+ REFERENCES
+ table_ident
+ opt_ref_list
+ opt_match_clause
+ opt_on_update_delete
+ {
+ $$=$2;
+ }
+ ;
+
+opt_ref_list:
+ /* empty */
+ { Lex->ref_list.empty(); }
+ | '(' ref_list ')'
+ ;
+
+ref_list:
+ ref_list ',' ident
+ {
+ Key_part_spec *key= new Key_part_spec($3, 0);
+ if (key == NULL)
+ MYSQL_YYABORT;
+ Lex->ref_list.push_back(key);
+ }
+ | ident
+ {
+ Key_part_spec *key= new Key_part_spec($1, 0);
+ if (key == NULL)
+ MYSQL_YYABORT;
+ LEX *lex= Lex;
+ lex->ref_list.empty();
+ lex->ref_list.push_back(key);
+ }
+ ;
+
+opt_match_clause:
+ /* empty */
+ { Lex->fk_match_option= Foreign_key::FK_MATCH_UNDEF; }
+ | MATCH FULL
+ { Lex->fk_match_option= Foreign_key::FK_MATCH_FULL; }
+ | MATCH PARTIAL
+ { Lex->fk_match_option= Foreign_key::FK_MATCH_PARTIAL; }
+ | MATCH SIMPLE_SYM
+ { Lex->fk_match_option= Foreign_key::FK_MATCH_SIMPLE; }
+ ;
+
+opt_on_update_delete:
+ /* empty */
+ {
+ LEX *lex= Lex;
+ lex->fk_update_opt= Foreign_key::FK_OPTION_UNDEF;
+ lex->fk_delete_opt= Foreign_key::FK_OPTION_UNDEF;
+ }
+ | ON UPDATE_SYM delete_option
+ {
+ LEX *lex= Lex;
+ lex->fk_update_opt= $3;
+ lex->fk_delete_opt= Foreign_key::FK_OPTION_UNDEF;
+ }
+ | ON DELETE_SYM delete_option
+ {
+ LEX *lex= Lex;
+ lex->fk_update_opt= Foreign_key::FK_OPTION_UNDEF;
+ lex->fk_delete_opt= $3;
+ }
+ | ON UPDATE_SYM delete_option
+ ON DELETE_SYM delete_option
+ {
+ LEX *lex= Lex;
+ lex->fk_update_opt= $3;
+ lex->fk_delete_opt= $6;
+ }
+ | ON DELETE_SYM delete_option
+ ON UPDATE_SYM delete_option
+ {
+ LEX *lex= Lex;
+ lex->fk_update_opt= $6;
+ lex->fk_delete_opt= $3;
+ }
+ ;
+
+delete_option:
+ RESTRICT { $$= Foreign_key::FK_OPTION_RESTRICT; }
+ | CASCADE { $$= Foreign_key::FK_OPTION_CASCADE; }
+ | SET NULL_SYM { $$= Foreign_key::FK_OPTION_SET_NULL; }
+ | NO_SYM ACTION { $$= Foreign_key::FK_OPTION_NO_ACTION; }
+ | SET DEFAULT { $$= Foreign_key::FK_OPTION_DEFAULT; }
+ ;
+
+normal_key_type:
+ key_or_index { $$= Key::MULTIPLE; }
+ ;
+
+constraint_key_type:
+ PRIMARY_SYM KEY_SYM { $$= Key::PRIMARY; }
+ | UNIQUE_SYM opt_key_or_index { $$= Key::UNIQUE; }
+ ;
+
+key_or_index:
+ KEY_SYM {}
+ | INDEX_SYM {}
+ ;
+
+opt_key_or_index:
+ /* empty */ {}
+ | key_or_index
+ ;
+
+keys_or_index:
+ KEYS {}
+ | INDEX_SYM {}
+ | INDEXES {}
+ ;
+
+opt_unique:
+ /* empty */ { $$= Key::MULTIPLE; }
+ | UNIQUE_SYM { $$= Key::UNIQUE; }
+ ;
+
+fulltext:
+ FULLTEXT_SYM { $$= Key::FULLTEXT;}
+ ;
+
+spatial:
+ SPATIAL_SYM
+ {
+#ifdef HAVE_SPATIAL
+ $$= Key::SPATIAL;
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0),
+ sym_group_geom.name, sym_group_geom.needed_define);
+ MYSQL_YYABORT;
+#endif
+ }
+ ;
+
+init_key_options:
+ {
+ Lex->key_create_info= default_key_create_info;
+ }
+ ;
+
+/*
+ For now, key_alg initializies lex->key_create_info.
+ In the future, when all key options are after key definition,
+ we can remove key_alg and move init_key_options to key_options
+*/
+
+key_alg:
+ init_key_options
+ | init_key_options key_using_alg
+ ;
+
+normal_key_options:
+ /* empty */ {}
+ | normal_key_opts
+ ;
+
+fulltext_key_options:
+ /* empty */ {}
+ | fulltext_key_opts
+ ;
+
+spatial_key_options:
+ /* empty */ {}
+ | spatial_key_opts
+ ;
+
+normal_key_opts:
+ normal_key_opt
+ | normal_key_opts normal_key_opt
+ ;
+
+spatial_key_opts:
+ spatial_key_opt
+ | spatial_key_opts spatial_key_opt
+ ;
+
+fulltext_key_opts:
+ fulltext_key_opt
+ | fulltext_key_opts fulltext_key_opt
+ ;
+
+key_using_alg:
+ USING btree_or_rtree { Lex->key_create_info.algorithm= $2; }
+ | TYPE_SYM btree_or_rtree { Lex->key_create_info.algorithm= $2; }
+ ;
+
+all_key_opt:
+ KEY_BLOCK_SIZE opt_equal ulong_num
+ { Lex->key_create_info.block_size= $3; }
+ | COMMENT_SYM TEXT_STRING_sys { Lex->key_create_info.comment= $2; }
+ | IDENT_sys equal TEXT_STRING_sys
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, true, &Lex->option_list,
+ &Lex->option_list_last);
+ }
+ | IDENT_sys equal ident
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, false, &Lex->option_list,
+ &Lex->option_list_last);
+ }
+ | IDENT_sys equal real_ulonglong_num
+ {
+ new (thd->mem_root)
+ engine_option_value($1, $3, &Lex->option_list,
+ &Lex->option_list_last, thd->mem_root);
+ }
+ | IDENT_sys equal DEFAULT
+ {
+ new (thd->mem_root)
+ engine_option_value($1, &Lex->option_list, &Lex->option_list_last);
+ }
+ ;
+
+normal_key_opt:
+ all_key_opt
+ | key_using_alg
+ ;
+
+spatial_key_opt:
+ all_key_opt
+ ;
+
+fulltext_key_opt:
+ all_key_opt
+ | WITH PARSER_SYM IDENT_sys
+ {
+ if (plugin_is_ready(&$3, MYSQL_FTPARSER_PLUGIN))
+ Lex->key_create_info.parser_name= $3;
+ else
+ {
+ my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+btree_or_rtree:
+ BTREE_SYM { $$= HA_KEY_ALG_BTREE; }
+ | RTREE_SYM { $$= HA_KEY_ALG_RTREE; }
+ | HASH_SYM { $$= HA_KEY_ALG_HASH; }
+ ;
+
+key_list:
+ key_list ',' key_part order_dir { Lex->col_list.push_back($3); }
+ | key_part order_dir { Lex->col_list.push_back($1); }
+ ;
+
+key_part:
+ ident
+ {
+ $$= new Key_part_spec($1, 0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ident '(' NUM ')'
+ {
+ int key_part_len= atoi($3.str);
+ if (!key_part_len)
+ {
+ my_error(ER_KEY_PART_0, MYF(0), $1.str);
+ }
+ $$= new Key_part_spec($1, (uint) key_part_len);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_ident:
+ /* empty */ { $$= null_lex_str; }
+ | field_ident { $$= $1; }
+ ;
+
+opt_if_not_exists_ident:
+ opt_if_not_exists opt_ident
+ {
+ LEX *lex= Lex;
+ if (lex->check_exists && lex->sql_command != SQLCOM_ALTER_TABLE)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ $$= $2;
+ };
+
+opt_component:
+ /* empty */ { $$= null_lex_str; }
+ | '.' ident { $$= $2; }
+ ;
+
+string_list:
+ text_string { Lex->interval_list.push_back($1); }
+ | string_list ',' text_string { Lex->interval_list.push_back($3); };
+
+/*
+** Alter table
+*/
+
+alter:
+ ALTER
+ {
+ Lex->name.str= 0;
+ Lex->name.length= 0;
+ Lex->only_view= FALSE;
+ Lex->sql_command= SQLCOM_ALTER_TABLE;
+ Lex->duplicates= DUP_ERROR;
+ Lex->col_list.empty();
+ Lex->select_lex.init_order();
+ bzero(&Lex->create_info, sizeof(Lex->create_info));
+ Lex->create_info.db_type= 0;
+ Lex->create_info.default_table_charset= NULL;
+ Lex->create_info.row_type= ROW_TYPE_NOT_USED;
+ Lex->alter_info.reset();
+ Lex->no_write_to_binlog= 0;
+ Lex->create_info.storage_media= HA_SM_DEFAULT;
+ DBUG_ASSERT(!Lex->m_sql_cmd);
+ }
+ alter_options TABLE_SYM table_ident
+ {
+ if (!Lex->select_lex.add_table_to_list(thd, $5, NULL,
+ TL_OPTION_UPDATING,
+ TL_READ_NO_INSERT,
+ MDL_SHARED_UPGRADABLE))
+ MYSQL_YYABORT;
+ Lex->select_lex.db= (Lex->select_lex.table_list.first)->db;
+ Lex->create_last_non_select_table= Lex->last_table();
+ }
+ alter_commands
+ {
+ if (!Lex->m_sql_cmd)
+ {
+ /* Create a generic ALTER TABLE statment. */
+ Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_alter_table();
+ if (Lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ }
+ | ALTER DATABASE ident_or_empty
+ {
+ Lex->create_info.default_table_charset= NULL;
+ Lex->create_info.used_fields= 0;
+ }
+ create_database_options
+ {
+ LEX *lex=Lex;
+ lex->sql_command=SQLCOM_ALTER_DB;
+ lex->name= $3;
+ if (lex->name.str == NULL &&
+ lex->copy_db_to(&lex->name.str, &lex->name.length))
+ MYSQL_YYABORT;
+ }
+ | ALTER DATABASE ident UPGRADE_SYM DATA_SYM DIRECTORY_SYM NAME_SYM
+ {
+ LEX *lex= Lex;
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_DROP_SP, MYF(0), "DATABASE");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command= SQLCOM_ALTER_DB_UPGRADE;
+ lex->name= $3;
+ }
+ | ALTER PROCEDURE_SYM sp_name
+ {
+ LEX *lex= Lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_DROP_SP, MYF(0), "PROCEDURE");
+ MYSQL_YYABORT;
+ }
+ bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
+ }
+ sp_a_chistics
+ {
+ LEX *lex=Lex;
+
+ lex->sql_command= SQLCOM_ALTER_PROCEDURE;
+ lex->spname= $3;
+ }
+ | ALTER FUNCTION_SYM sp_name
+ {
+ LEX *lex= Lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION");
+ MYSQL_YYABORT;
+ }
+ bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
+ }
+ sp_a_chistics
+ {
+ LEX *lex=Lex;
+
+ lex->sql_command= SQLCOM_ALTER_FUNCTION;
+ lex->spname= $3;
+ }
+ | ALTER view_algorithm definer_opt
+ {
+ LEX *lex= Lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "ALTER VIEW");
+ MYSQL_YYABORT;
+ }
+ lex->create_view_mode= VIEW_ALTER;
+ }
+ view_tail
+ {}
+ | ALTER definer_opt
+ /*
+ We have two separate rules for ALTER VIEW rather that
+ optional view_algorithm above, to resolve the ambiguity
+ with the ALTER EVENT below.
+ */
+ {
+ LEX *lex= Lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "ALTER VIEW");
+ MYSQL_YYABORT;
+ }
+ lex->create_view_algorithm= DTYPE_ALGORITHM_UNDEFINED;
+ lex->create_view_mode= VIEW_ALTER;
+ }
+ view_tail
+ {}
+ | ALTER definer_opt EVENT_SYM sp_name
+ {
+ /*
+ It is safe to use Lex->spname because
+ ALTER EVENT xxx RENATE TO yyy DO ALTER EVENT RENAME TO
+ is not allowed. Lex->spname is used in the case of RENAME TO
+ If it had to be supported spname had to be added to
+ Event_parse_data.
+ */
+
+ if (!(Lex->event_parse_data= Event_parse_data::new_instance(thd)))
+ MYSQL_YYABORT;
+ Lex->event_parse_data->identifier= $4;
+
+ Lex->sql_command= SQLCOM_ALTER_EVENT;
+ }
+ ev_alter_on_schedule_completion
+ opt_ev_rename_to
+ opt_ev_status
+ opt_ev_comment
+ opt_ev_sql_stmt
+ {
+ if (!($6 || $7 || $8 || $9 || $10))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ /*
+ sql_command is set here because some rules in ev_sql_stmt
+ can overwrite it
+ */
+ Lex->sql_command= SQLCOM_ALTER_EVENT;
+ }
+ | ALTER TABLESPACE alter_tablespace_info
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_cmd_type= ALTER_TABLESPACE;
+ }
+ | ALTER LOGFILE_SYM GROUP_SYM alter_logfile_group_info
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_cmd_type= ALTER_LOGFILE_GROUP;
+ }
+ | ALTER TABLESPACE change_tablespace_info
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_cmd_type= CHANGE_FILE_TABLESPACE;
+ }
+ | ALTER TABLESPACE change_tablespace_access
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_cmd_type= ALTER_ACCESS_MODE_TABLESPACE;
+ }
+ | ALTER SERVER_SYM ident_or_text OPTIONS_SYM '(' server_options_list ')'
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_ALTER_SERVER;
+ lex->server_options.server_name= $3.str;
+ lex->server_options.server_name_length= $3.length;
+ }
+ ;
+
+ev_alter_on_schedule_completion:
+ /* empty */ { $$= 0;}
+ | ON SCHEDULE_SYM ev_schedule_time { $$= 1; }
+ | ev_on_completion { $$= 1; }
+ | ON SCHEDULE_SYM ev_schedule_time ev_on_completion { $$= 1; }
+ ;
+
+opt_ev_rename_to:
+ /* empty */ { $$= 0;}
+ | RENAME TO_SYM sp_name
+ {
+ /*
+ Use lex's spname to hold the new name.
+ The original name is in the Event_parse_data object
+ */
+ Lex->spname= $3;
+ $$= 1;
+ }
+ ;
+
+opt_ev_sql_stmt:
+ /* empty*/ { $$= 0;}
+ | DO_SYM ev_sql_stmt { $$= 1; }
+ ;
+
+ident_or_empty:
+ /* empty */ { $$.str= 0; $$.length= 0; }
+ | ident { $$= $1; }
+ ;
+
+alter_commands:
+ /* empty */
+ | DISCARD TABLESPACE
+ {
+ Lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_discard_import_tablespace(
+ Sql_cmd_discard_import_tablespace::DISCARD_TABLESPACE);
+ if (Lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ | IMPORT TABLESPACE
+ {
+ Lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_discard_import_tablespace(
+ Sql_cmd_discard_import_tablespace::IMPORT_TABLESPACE);
+ if (Lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ | alter_list
+ opt_partitioning
+ | alter_list
+ remove_partitioning
+ | remove_partitioning
+ | partitioning
+/*
+ This part was added for release 5.1 by Mikael Ronström.
+ From here we insert a number of commands to manage the partitions of a
+ partitioned table such as adding partitions, dropping partitions,
+ reorganising partitions in various manners. In future releases the list
+ will be longer.
+*/
+ | add_partition_rule
+ | DROP PARTITION_SYM opt_if_exists alt_part_name_list
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_DROP_PARTITION;
+ }
+ | REBUILD_SYM PARTITION_SYM opt_no_write_to_binlog
+ all_or_alt_part_name_list
+ {
+ LEX *lex= Lex;
+ lex->alter_info.flags|= Alter_info::ALTER_REBUILD_PARTITION;
+ lex->no_write_to_binlog= $3;
+ }
+ | OPTIMIZE PARTITION_SYM opt_no_write_to_binlog
+ all_or_alt_part_name_list
+ {
+ LEX *lex= thd->lex;
+ lex->no_write_to_binlog= $3;
+ lex->check_opt.init();
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_alter_table_optimize_partition();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ opt_no_write_to_binlog
+ | ANALYZE_SYM PARTITION_SYM opt_no_write_to_binlog
+ all_or_alt_part_name_list
+ {
+ LEX *lex= thd->lex;
+ lex->no_write_to_binlog= $3;
+ lex->check_opt.init();
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_alter_table_analyze_partition();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ | CHECK_SYM PARTITION_SYM all_or_alt_part_name_list
+ {
+ LEX *lex= thd->lex;
+ lex->check_opt.init();
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_alter_table_check_partition();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ opt_mi_check_type
+ | REPAIR PARTITION_SYM opt_no_write_to_binlog
+ all_or_alt_part_name_list
+ {
+ LEX *lex= thd->lex;
+ lex->no_write_to_binlog= $3;
+ lex->check_opt.init();
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_alter_table_repair_partition();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ opt_mi_repair_type
+ | COALESCE PARTITION_SYM opt_no_write_to_binlog real_ulong_num
+ {
+ LEX *lex= Lex;
+ lex->alter_info.flags|= Alter_info::ALTER_COALESCE_PARTITION;
+ lex->no_write_to_binlog= $3;
+ lex->alter_info.num_parts= $4;
+ }
+ | TRUNCATE_SYM PARTITION_SYM all_or_alt_part_name_list
+ {
+ LEX *lex= thd->lex;
+ lex->check_opt.init();
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_alter_table_truncate_partition();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ | reorg_partition_rule
+ | EXCHANGE_SYM PARTITION_SYM alt_part_name_item
+ WITH TABLE_SYM table_ident have_partitioning
+ {
+ LEX *lex= thd->lex;
+ size_t dummy;
+ lex->select_lex.db=$6->db.str;
+ if (lex->select_lex.db == NULL &&
+ lex->copy_db_to(&lex->select_lex.db, &dummy))
+ {
+ MYSQL_YYABORT;
+ }
+ lex->name= $6->table;
+ lex->alter_info.flags|= Alter_info::ALTER_EXCHANGE_PARTITION;
+ if (!lex->select_lex.add_table_to_list(thd, $6, NULL,
+ TL_OPTION_UPDATING,
+ TL_READ_NO_INSERT,
+ MDL_SHARED_NO_WRITE))
+ MYSQL_YYABORT;
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root)
+ Sql_cmd_alter_table_exchange_partition();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+remove_partitioning:
+ REMOVE_SYM PARTITIONING_SYM
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_REMOVE_PARTITIONING;
+ }
+ ;
+
+all_or_alt_part_name_list:
+ ALL
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_ALL_PARTITION;
+ }
+ | alt_part_name_list
+ ;
+
+add_partition_rule:
+ ADD PARTITION_SYM opt_if_not_exists opt_no_write_to_binlog
+ {
+ LEX *lex= Lex;
+ lex->part_info= new partition_info();
+ if (!lex->part_info)
+ {
+ mem_alloc_error(sizeof(partition_info));
+ MYSQL_YYABORT;
+ }
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_PARTITION;
+ lex->no_write_to_binlog= $4;
+ }
+ add_part_extra
+ {}
+ ;
+
+add_part_extra:
+ /* empty */
+ | '(' part_def_list ')'
+ {
+ LEX *lex= Lex;
+ lex->part_info->num_parts= lex->part_info->partitions.elements;
+ }
+ | PARTITIONS_SYM real_ulong_num
+ {
+ Lex->part_info->num_parts= $2;
+ }
+ ;
+
+reorg_partition_rule:
+ REORGANIZE_SYM PARTITION_SYM opt_no_write_to_binlog
+ {
+ LEX *lex= Lex;
+ lex->part_info= new partition_info();
+ if (!lex->part_info)
+ {
+ mem_alloc_error(sizeof(partition_info));
+ MYSQL_YYABORT;
+ }
+ lex->no_write_to_binlog= $3;
+ }
+ reorg_parts_rule
+ ;
+
+reorg_parts_rule:
+ /* empty */
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_TABLE_REORG;
+ }
+ | alt_part_name_list
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_REORGANIZE_PARTITION;
+ }
+ INTO '(' part_def_list ')'
+ {
+ partition_info *part_info= Lex->part_info;
+ part_info->num_parts= part_info->partitions.elements;
+ }
+ ;
+
+alt_part_name_list:
+ alt_part_name_item {}
+ | alt_part_name_list ',' alt_part_name_item {}
+ ;
+
+alt_part_name_item:
+ ident
+ {
+ if (Lex->alter_info.partition_names.push_back($1.str))
+ {
+ mem_alloc_error(1);
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+/*
+ End of management of partition commands
+*/
+
+alter_list:
+ alter_list_item
+ | alter_list ',' alter_list_item
+ ;
+
+add_column:
+ ADD opt_column opt_if_not_exists
+ {
+ LEX *lex=Lex;
+ lex->change=0;
+ lex->alter_info.flags|= Alter_info::ALTER_ADD_COLUMN;
+ }
+ ;
+
+alter_list_item:
+ add_column column_def opt_place
+ {
+ Lex->create_last_non_select_table= Lex->last_table();
+ }
+ | ADD key_def
+ {
+ Lex->create_last_non_select_table= Lex->last_table();
+ Lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX;
+ }
+ | add_column '(' create_field_list ')'
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_ADD_COLUMN |
+ Alter_info::ALTER_ADD_INDEX;
+ }
+ | CHANGE opt_column opt_if_exists field_ident
+ {
+ LEX *lex=Lex;
+ lex->change= $4.str;
+ lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN;
+ lex->option_list= NULL;
+ }
+ field_spec opt_place
+ {
+ Lex->create_last_non_select_table= Lex->last_table();
+ }
+ | MODIFY_SYM opt_column opt_if_exists field_ident
+ {
+ LEX *lex=Lex;
+ lex->length=lex->dec=0; lex->type=0;
+ lex->default_value= lex->on_update_value= 0;
+ lex->comment=null_lex_str;
+ lex->charset= NULL;
+ lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN;
+ lex->vcol_info= 0;
+ lex->option_list= NULL;
+ }
+ field_def
+ {
+ LEX *lex=Lex;
+ if (add_field_to_list(lex->thd,&$4,
+ $6.type,
+ $6.length, $6.dec, lex->type,
+ lex->default_value, lex->on_update_value,
+ &lex->comment,
+ $4.str, &lex->interval_list, $6.charset,
+ lex->uint_geom_type,
+ lex->vcol_info, lex->option_list))
+ MYSQL_YYABORT;
+ }
+ opt_place
+ {
+ Lex->create_last_non_select_table= Lex->last_table();
+ }
+ | DROP opt_column opt_if_exists field_ident opt_restrict
+ {
+ LEX *lex=Lex;
+ Alter_drop *ad= new Alter_drop(Alter_drop::COLUMN, $4.str, $3);
+ if (ad == NULL)
+ MYSQL_YYABORT;
+ lex->alter_info.drop_list.push_back(ad);
+ lex->alter_info.flags|= Alter_info::ALTER_DROP_COLUMN;
+ }
+ | DROP FOREIGN KEY_SYM opt_if_exists field_ident
+ {
+ LEX *lex=Lex;
+ Alter_drop *ad= new Alter_drop(Alter_drop::FOREIGN_KEY, $5.str, $4);
+ if (ad == NULL)
+ MYSQL_YYABORT;
+ lex->alter_info.drop_list.push_back(ad);
+ lex->alter_info.flags|= Alter_info::DROP_FOREIGN_KEY;
+ }
+ | DROP PRIMARY_SYM KEY_SYM
+ {
+ LEX *lex=Lex;
+ Alter_drop *ad= new Alter_drop(Alter_drop::KEY, primary_key_name,
+ FALSE);
+ if (ad == NULL)
+ MYSQL_YYABORT;
+ lex->alter_info.drop_list.push_back(ad);
+ lex->alter_info.flags|= Alter_info::ALTER_DROP_INDEX;
+ }
+ | DROP key_or_index opt_if_exists field_ident
+ {
+ LEX *lex=Lex;
+ Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $4.str, $3);
+ if (ad == NULL)
+ MYSQL_YYABORT;
+ lex->alter_info.drop_list.push_back(ad);
+ lex->alter_info.flags|= Alter_info::ALTER_DROP_INDEX;
+ }
+ | DISABLE_SYM KEYS
+ {
+ LEX *lex=Lex;
+ lex->alter_info.keys_onoff= Alter_info::DISABLE;
+ lex->alter_info.flags|= Alter_info::ALTER_KEYS_ONOFF;
+ }
+ | ENABLE_SYM KEYS
+ {
+ LEX *lex=Lex;
+ lex->alter_info.keys_onoff= Alter_info::ENABLE;
+ lex->alter_info.flags|= Alter_info::ALTER_KEYS_ONOFF;
+ }
+ | ALTER opt_column field_ident SET DEFAULT signed_literal
+ {
+ LEX *lex=Lex;
+ Alter_column *ac= new Alter_column($3.str,$6);
+ if (ac == NULL)
+ MYSQL_YYABORT;
+ lex->alter_info.alter_list.push_back(ac);
+ lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN_DEFAULT;
+ }
+ | ALTER opt_column field_ident DROP DEFAULT
+ {
+ LEX *lex=Lex;
+ Alter_column *ac= new Alter_column($3.str, (Item*) 0);
+ if (ac == NULL)
+ MYSQL_YYABORT;
+ lex->alter_info.alter_list.push_back(ac);
+ lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN_DEFAULT;
+ }
+ | RENAME opt_to table_ident
+ {
+ LEX *lex=Lex;
+ size_t dummy;
+ lex->select_lex.db=$3->db.str;
+ if (lex->select_lex.db == NULL &&
+ lex->copy_db_to(&lex->select_lex.db, &dummy))
+ {
+ MYSQL_YYABORT;
+ }
+ if (check_table_name($3->table.str,$3->table.length, FALSE) ||
+ ($3->db.str && check_db_name(&$3->db)))
+ {
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), $3->table.str);
+ MYSQL_YYABORT;
+ }
+ lex->name= $3->table;
+ lex->alter_info.flags|= Alter_info::ALTER_RENAME;
+ }
+ | CONVERT_SYM TO_SYM charset charset_name_or_default opt_collate
+ {
+ if (!$4)
+ {
+ $4= thd->variables.collation_database;
+ }
+ $5= $5 ? $5 : $4;
+ if (!my_charset_same($4,$5))
+ {
+ my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0),
+ $5->name, $4->csname);
+ MYSQL_YYABORT;
+ }
+ LEX *lex= Lex;
+ if (lex->create_info.add_alter_list_item_convert_to_charset($5))
+ MYSQL_YYABORT;
+ lex->alter_info.flags|= Alter_info::ALTER_CONVERT;
+ }
+ | create_table_options_space_separated
+ {
+ LEX *lex=Lex;
+ lex->alter_info.flags|= Alter_info::ALTER_OPTIONS;
+ if ((lex->create_info.used_fields & HA_CREATE_USED_ENGINE) &&
+ !lex->create_info.db_type)
+ {
+ lex->create_info.used_fields&= ~HA_CREATE_USED_ENGINE;
+ }
+ }
+ | FORCE_SYM
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_RECREATE;
+ }
+ | alter_order_clause
+ {
+ LEX *lex=Lex;
+ lex->alter_info.flags|= Alter_info::ALTER_ORDER;
+ }
+ | alter_algorithm_option
+ | alter_lock_option
+ ;
+
+opt_index_lock_algorithm:
+ /* empty */
+ | alter_lock_option
+ | alter_algorithm_option
+ | alter_lock_option alter_algorithm_option
+ | alter_algorithm_option alter_lock_option
+
+alter_algorithm_option:
+ ALGORITHM_SYM opt_equal DEFAULT
+ {
+ Lex->alter_info.requested_algorithm=
+ Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT;
+ }
+ | ALGORITHM_SYM opt_equal ident
+ {
+ if (Lex->alter_info.set_requested_algorithm(&$3))
+ {
+ my_error(ER_UNKNOWN_ALTER_ALGORITHM, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+alter_lock_option:
+ LOCK_SYM opt_equal DEFAULT
+ {
+ Lex->alter_info.requested_lock=
+ Alter_info::ALTER_TABLE_LOCK_DEFAULT;
+ }
+ | LOCK_SYM opt_equal ident
+ {
+ if (Lex->alter_info.set_requested_lock(&$3))
+ {
+ my_error(ER_UNKNOWN_ALTER_LOCK, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+opt_column:
+ /* empty */ {}
+ | COLUMN_SYM {}
+ ;
+
+opt_ignore:
+ /* empty */ { Lex->ignore= 0;}
+ | IGNORE_SYM { Lex->ignore= 1;}
+ ;
+
+alter_options:
+ { Lex->ignore= 0;} alter_options_part2
+ ;
+
+alter_options_part2:
+ /* empty */
+ | alter_option_list
+ ;
+
+alter_option_list:
+ alter_option_list alter_option
+ | alter_option
+ ;
+
+alter_option:
+ IGNORE_SYM { Lex->ignore= 1;}
+ | ONLINE_SYM
+ {
+ Lex->alter_info.requested_lock=
+ Alter_info::ALTER_TABLE_LOCK_NONE;
+ }
+
+
+opt_restrict:
+ /* empty */ { Lex->drop_mode= DROP_DEFAULT; }
+ | RESTRICT { Lex->drop_mode= DROP_RESTRICT; }
+ | CASCADE { Lex->drop_mode= DROP_CASCADE; }
+ ;
+
+opt_place:
+ /* empty */ {}
+ | AFTER_SYM ident
+ {
+ store_position_for_column($2.str);
+ Lex->alter_info.flags |= Alter_info::ALTER_COLUMN_ORDER;
+ }
+ | FIRST_SYM
+ {
+ store_position_for_column(first_keyword);
+ Lex->alter_info.flags |= Alter_info::ALTER_COLUMN_ORDER;
+ }
+ ;
+
+opt_to:
+ /* empty */ {}
+ | TO_SYM {}
+ | EQ {}
+ | AS {}
+ ;
+
+slave:
+ START_SYM SLAVE optional_connection_name slave_thread_opts
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_SLAVE_START;
+ lex->type = 0;
+ /* If you change this code don't forget to update SLAVE START too */
+ }
+ slave_until
+ {}
+ | START_SYM ALL SLAVES slave_thread_opts
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_SLAVE_ALL_START;
+ lex->type = 0;
+ }
+ {}
+ | STOP_SYM SLAVE optional_connection_name slave_thread_opts
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_SLAVE_STOP;
+ lex->type = 0;
+ /* If you change this code don't forget to update SLAVE STOP too */
+ }
+ | STOP_SYM ALL SLAVES slave_thread_opts
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_SLAVE_ALL_STOP;
+ lex->type = 0;
+ /* If you change this code don't forget to update SLAVE STOP too */
+ }
+ ;
+
+start:
+ START_SYM TRANSACTION_SYM opt_start_transaction_option_list
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_BEGIN;
+ /* READ ONLY and READ WRITE are mutually exclusive. */
+ if (($3 & MYSQL_START_TRANS_OPT_READ_WRITE) &&
+ ($3 & MYSQL_START_TRANS_OPT_READ_ONLY))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ lex->start_transaction_opt= $3;
+ }
+ ;
+
+opt_start_transaction_option_list:
+ /* empty */
+ {
+ $$= 0;
+ }
+ | start_transaction_option_list
+ {
+ $$= $1;
+ }
+ ;
+
+start_transaction_option_list:
+ start_transaction_option
+ {
+ $$= $1;
+ }
+ | start_transaction_option_list ',' start_transaction_option
+ {
+ $$= $1 | $3;
+ }
+ ;
+
+start_transaction_option:
+ WITH CONSISTENT_SYM SNAPSHOT_SYM
+ {
+ $$= MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT;
+ }
+ | READ_SYM ONLY_SYM
+ {
+ $$= MYSQL_START_TRANS_OPT_READ_ONLY;
+ }
+ | READ_SYM WRITE_SYM
+ {
+ $$= MYSQL_START_TRANS_OPT_READ_WRITE;
+ }
+ ;
+
+slave_thread_opts:
+ { Lex->slave_thd_opt= 0; }
+ slave_thread_opt_list
+ {}
+ ;
+
+slave_thread_opt_list:
+ slave_thread_opt
+ | slave_thread_opt_list ',' slave_thread_opt
+ ;
+
+slave_thread_opt:
+ /*empty*/ {}
+ | SQL_THREAD { Lex->slave_thd_opt|=SLAVE_SQL; }
+ | RELAY_THREAD { Lex->slave_thd_opt|=SLAVE_IO; }
+ ;
+
+slave_until:
+ /*empty*/ {}
+ | UNTIL_SYM slave_until_opts
+ {
+ LEX *lex=Lex;
+ if (((lex->mi.log_file_name || lex->mi.pos) &&
+ (lex->mi.relay_log_name || lex->mi.relay_log_pos)) ||
+ !((lex->mi.log_file_name && lex->mi.pos) ||
+ (lex->mi.relay_log_name && lex->mi.relay_log_pos)))
+ {
+ my_message(ER_BAD_SLAVE_UNTIL_COND,
+ ER(ER_BAD_SLAVE_UNTIL_COND), MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ | UNTIL_SYM MASTER_GTID_POS_SYM EQ TEXT_STRING_sys
+ {
+ Lex->mi.gtid_pos_str = $4;
+ }
+ ;
+
+slave_until_opts:
+ master_file_def
+ | slave_until_opts ',' master_file_def
+ ;
+
+checksum:
+ CHECKSUM_SYM table_or_tables
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_CHECKSUM;
+ /* Will be overriden during execution. */
+ YYPS->m_lock_type= TL_UNLOCK;
+ }
+ table_list opt_checksum_type
+ {}
+ ;
+
+opt_checksum_type:
+ /* nothing */ { Lex->check_opt.flags= 0; }
+ | QUICK { Lex->check_opt.flags= T_QUICK; }
+ | EXTENDED_SYM { Lex->check_opt.flags= T_EXTEND; }
+ ;
+
+repair:
+ REPAIR opt_no_write_to_binlog table_or_view
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_REPAIR;
+ lex->no_write_to_binlog= $2;
+ lex->check_opt.init();
+ lex->alter_info.reset();
+ /* Will be overriden during execution. */
+ YYPS->m_lock_type= TL_UNLOCK;
+ }
+ table_list opt_mi_repair_type
+ {
+ LEX* lex= thd->lex;
+ if ((lex->only_view &&
+ ((lex->check_opt.flags & (T_QUICK | T_EXTEND)) ||
+ (lex->check_opt.sql_flags & TT_USEFRM))) ||
+ (!lex->only_view &&
+ (lex->check_opt.sql_flags & TT_FROM_MYSQL)))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_repair_table();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_mi_repair_type:
+ /* empty */ { Lex->check_opt.flags = T_MEDIUM; }
+ | mi_repair_types {}
+ ;
+
+mi_repair_types:
+ mi_repair_type {}
+ | mi_repair_type mi_repair_types {}
+ ;
+
+mi_repair_type:
+ QUICK { Lex->check_opt.flags|= T_QUICK; }
+ | EXTENDED_SYM { Lex->check_opt.flags|= T_EXTEND; }
+ | USE_FRM { Lex->check_opt.sql_flags|= TT_USEFRM; }
+ | FROM MYSQL_SYM { Lex->check_opt.sql_flags|= TT_FROM_MYSQL; }
+ ;
+
+analyze:
+ ANALYZE_SYM opt_no_write_to_binlog table_or_tables
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_ANALYZE;
+ lex->no_write_to_binlog= $2;
+ lex->check_opt.init();
+ lex->alter_info.reset();
+ /* Will be overriden during execution. */
+ YYPS->m_lock_type= TL_UNLOCK;
+ }
+ analyze_table_list
+ {
+ LEX* lex= thd->lex;
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_analyze_table();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+analyze_table_list:
+ analyze_table_elem_spec
+ | analyze_table_list ',' analyze_table_elem_spec
+ ;
+
+analyze_table_elem_spec:
+ table_name opt_persistent_stat_clause
+ ;
+
+opt_persistent_stat_clause:
+ /* empty */
+ {}
+ | PERSISTENT_SYM FOR_SYM persistent_stat_spec
+ {
+ thd->lex->with_persistent_for_clause= TRUE;
+ }
+ ;
+
+persistent_stat_spec:
+ ALL
+ {}
+ | COLUMNS persistent_column_stat_spec INDEXES persistent_index_stat_spec
+ {}
+
+persistent_column_stat_spec:
+ ALL {}
+ | '('
+ {
+ LEX* lex= thd->lex;
+ lex->column_list= new List<LEX_STRING>;
+ if (lex->column_list == NULL)
+ MYSQL_YYABORT;
+ }
+ table_column_list
+ ')'
+ ;
+
+persistent_index_stat_spec:
+ ALL {}
+ | '('
+ {
+ LEX* lex= thd->lex;
+ lex->index_list= new List<LEX_STRING>;
+ if (lex->index_list == NULL)
+ MYSQL_YYABORT;
+ }
+ table_index_list
+ ')'
+ ;
+
+table_column_list:
+ /* empty */
+ {}
+ | ident
+ {
+ Lex->column_list->push_back((LEX_STRING*)
+ sql_memdup(&$1, sizeof(LEX_STRING)));
+ }
+ | table_column_list ',' ident
+ {
+ Lex->column_list->push_back((LEX_STRING*)
+ sql_memdup(&$3, sizeof(LEX_STRING)));
+ }
+ ;
+
+table_index_list:
+ /* empty */
+ {}
+ | table_index_name
+ | table_index_list ',' table_index_name
+ ;
+
+table_index_name:
+ ident
+ {
+ Lex->index_list->push_back(
+ (LEX_STRING*) sql_memdup(&$1, sizeof(LEX_STRING)));
+ }
+ |
+ PRIMARY_SYM
+ {
+ LEX_STRING str= {(char*) "PRIMARY", 7};
+ Lex->index_list->push_back(
+ (LEX_STRING*) sql_memdup(&str, sizeof(LEX_STRING)));
+ }
+ ;
+
+binlog_base64_event:
+ BINLOG_SYM TEXT_STRING_sys
+ {
+ Lex->sql_command = SQLCOM_BINLOG_BASE64_EVENT;
+ Lex->comment= $2;
+ }
+ ;
+
+check:
+ CHECK_SYM table_or_view
+ {
+ LEX *lex=Lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "CHECK");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command = SQLCOM_CHECK;
+ lex->check_opt.init();
+ lex->alter_info.reset();
+ /* Will be overriden during execution. */
+ YYPS->m_lock_type= TL_UNLOCK;
+ }
+ table_list opt_mi_check_type
+ {
+ LEX* lex= thd->lex;
+ if (lex->only_view &&
+ (lex->check_opt.flags & (T_QUICK | T_FAST | T_EXTEND |
+ T_CHECK_ONLY_CHANGED)))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_check_table();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_mi_check_type:
+ /* empty */ { Lex->check_opt.flags = T_MEDIUM; }
+ | mi_check_types {}
+ ;
+
+mi_check_types:
+ mi_check_type {}
+ | mi_check_type mi_check_types {}
+ ;
+
+mi_check_type:
+ QUICK { Lex->check_opt.flags|= T_QUICK; }
+ | FAST_SYM { Lex->check_opt.flags|= T_FAST; }
+ | MEDIUM_SYM { Lex->check_opt.flags|= T_MEDIUM; }
+ | EXTENDED_SYM { Lex->check_opt.flags|= T_EXTEND; }
+ | CHANGED { Lex->check_opt.flags|= T_CHECK_ONLY_CHANGED; }
+ | FOR_SYM UPGRADE_SYM { Lex->check_opt.sql_flags|= TT_FOR_UPGRADE; }
+ ;
+
+optimize:
+ OPTIMIZE opt_no_write_to_binlog table_or_tables
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_OPTIMIZE;
+ lex->no_write_to_binlog= $2;
+ lex->check_opt.init();
+ lex->alter_info.reset();
+ /* Will be overriden during execution. */
+ YYPS->m_lock_type= TL_UNLOCK;
+ }
+ table_list
+ {
+ LEX* lex= thd->lex;
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_optimize_table();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_no_write_to_binlog:
+ /* empty */ { $$= 0; }
+ | NO_WRITE_TO_BINLOG { $$= 1; }
+ | LOCAL_SYM { $$= 1; }
+ ;
+
+rename:
+ RENAME table_or_tables
+ {
+ Lex->sql_command= SQLCOM_RENAME_TABLE;
+ }
+ table_to_table_list
+ {}
+ | RENAME USER clear_privileges rename_list
+ {
+ Lex->sql_command = SQLCOM_RENAME_USER;
+ }
+ ;
+
+rename_list:
+ user TO_SYM user
+ {
+ if (Lex->users_list.push_back($1) || Lex->users_list.push_back($3))
+ MYSQL_YYABORT;
+ }
+ | rename_list ',' user TO_SYM user
+ {
+ if (Lex->users_list.push_back($3) || Lex->users_list.push_back($5))
+ MYSQL_YYABORT;
+ }
+ ;
+
+table_to_table_list:
+ table_to_table
+ | table_to_table_list ',' table_to_table
+ ;
+
+table_to_table:
+ table_ident TO_SYM table_ident
+ {
+ LEX *lex=Lex;
+ SELECT_LEX *sl= lex->current_select;
+ if (!sl->add_table_to_list(lex->thd, $1,NULL,TL_OPTION_UPDATING,
+ TL_IGNORE, MDL_EXCLUSIVE) ||
+ !sl->add_table_to_list(lex->thd, $3,NULL,TL_OPTION_UPDATING,
+ TL_IGNORE, MDL_EXCLUSIVE))
+ MYSQL_YYABORT;
+ }
+ ;
+
+keycache:
+ CACHE_SYM INDEX_SYM
+ {
+ Lex->alter_info.reset();
+ }
+ keycache_list_or_parts IN_SYM key_cache_name
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_ASSIGN_TO_KEYCACHE;
+ lex->ident= $6;
+ lex->only_view= FALSE;
+ }
+ ;
+
+keycache_list_or_parts:
+ keycache_list
+ | assign_to_keycache_parts
+ ;
+
+keycache_list:
+ assign_to_keycache
+ | keycache_list ',' assign_to_keycache
+ ;
+
+assign_to_keycache:
+ table_ident cache_keys_spec
+ {
+ if (!Select->add_table_to_list(thd, $1, NULL, 0, TL_READ,
+ MDL_SHARED_READ,
+ Select->pop_index_hints()))
+ MYSQL_YYABORT;
+ }
+ ;
+
+assign_to_keycache_parts:
+ table_ident adm_partition cache_keys_spec
+ {
+ if (!Select->add_table_to_list(thd, $1, NULL, 0, TL_READ,
+ MDL_SHARED_READ,
+ Select->pop_index_hints()))
+ MYSQL_YYABORT;
+ }
+ ;
+
+key_cache_name:
+ ident { $$= $1; }
+ | DEFAULT { $$ = default_key_cache_base; }
+ ;
+
+preload:
+ LOAD INDEX_SYM INTO CACHE_SYM
+ {
+ LEX *lex=Lex;
+ lex->sql_command=SQLCOM_PRELOAD_KEYS;
+ lex->alter_info.reset();
+ lex->only_view= FALSE;
+ }
+ preload_list_or_parts
+ {}
+ ;
+
+preload_list_or_parts:
+ preload_keys_parts
+ | preload_list
+ ;
+
+preload_list:
+ preload_keys
+ | preload_list ',' preload_keys
+ ;
+
+preload_keys:
+ table_ident cache_keys_spec opt_ignore_leaves
+ {
+ if (!Select->add_table_to_list(thd, $1, NULL, $3, TL_READ,
+ MDL_SHARED_READ,
+ Select->pop_index_hints()))
+ MYSQL_YYABORT;
+ }
+ ;
+
+preload_keys_parts:
+ table_ident adm_partition cache_keys_spec opt_ignore_leaves
+ {
+ if (!Select->add_table_to_list(thd, $1, NULL, $4, TL_READ,
+ MDL_SHARED_READ,
+ Select->pop_index_hints()))
+ MYSQL_YYABORT;
+ }
+ ;
+
+adm_partition:
+ PARTITION_SYM have_partitioning
+ {
+ Lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
+ }
+ '(' all_or_alt_part_name_list ')'
+ ;
+
+cache_keys_spec:
+ {
+ Lex->select_lex.alloc_index_hints(thd);
+ Select->set_index_hint_type(INDEX_HINT_USE,
+ INDEX_HINT_MASK_ALL);
+ }
+ cache_key_list_or_empty
+ ;
+
+cache_key_list_or_empty:
+ /* empty */ { }
+ | key_or_index '(' opt_key_usage_list ')'
+ ;
+
+opt_ignore_leaves:
+ /* empty */
+ { $$= 0; }
+ | IGNORE_SYM LEAVES { $$= TL_OPTION_IGNORE_LEAVES; }
+ ;
+
+/*
+ Select : retrieve data from table
+*/
+
+
+select:
+ select_init
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SELECT;
+ }
+ ;
+
+/* Need select_init2 for subselects. */
+select_init:
+ SELECT_SYM select_init2
+ | '(' select_paren ')' union_opt
+ ;
+
+select_paren:
+ SELECT_SYM select_part2
+ {
+ if (setup_select_in_parentheses(Lex))
+ MYSQL_YYABORT;
+ }
+ | '(' select_paren ')'
+ ;
+
+/* The equivalent of select_paren for nested queries. */
+select_paren_derived:
+ SELECT_SYM select_part2_derived
+ {
+ if (setup_select_in_parentheses(Lex))
+ MYSQL_YYABORT;
+ }
+ | '(' select_paren_derived ')'
+ ;
+
+select_init2:
+ select_part2
+ {
+ LEX *lex= Lex;
+ SELECT_LEX * sel= lex->current_select;
+ if (lex->current_select->set_braces(0))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ if (sel->linkage == UNION_TYPE &&
+ sel->master_unit()->first_select()->braces)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ union_clause
+ ;
+
+select_part2:
+ {
+ LEX *lex= Lex;
+ SELECT_LEX *sel= lex->current_select;
+ if (sel->linkage != UNION_TYPE)
+ mysql_init_select(lex);
+ lex->current_select->parsing_place= SELECT_LIST;
+ }
+ select_options select_item_list
+ {
+ Select->parsing_place= NO_MATTER;
+ }
+ select_into select_lock_type
+ ;
+
+select_into:
+ opt_order_clause opt_limit_clause {}
+ | into
+ | select_from
+ | into select_from
+ | select_from into
+ ;
+
+select_from:
+ FROM join_table_list where_clause group_clause having_clause
+ opt_order_clause opt_limit_clause procedure_clause
+ {
+ Select->context.table_list=
+ Select->context.first_name_resolution_table=
+ Select->table_list.first;
+ }
+ | FROM DUAL_SYM where_clause opt_limit_clause
+ /* oracle compatibility: oracle always requires FROM clause,
+ and DUAL is system table without fields.
+ Is "SELECT 1 FROM DUAL" any better than "SELECT 1" ?
+ Hmmm :) */
+ ;
+
+select_options:
+ /* empty*/
+ | select_option_list
+ {
+ if (Select->options & SELECT_DISTINCT && Select->options & SELECT_ALL)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "ALL", "DISTINCT");
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+select_option_list:
+ select_option_list select_option
+ | select_option
+ ;
+
+select_option:
+ query_expression_option
+ | SQL_NO_CACHE_SYM
+ {
+ /*
+ Allow this flag only on the first top-level SELECT statement, if
+ SQL_CACHE wasn't specified, and only once per query.
+ */
+ if (Lex->current_select != &Lex->select_lex)
+ {
+ my_error(ER_CANT_USE_OPTION_HERE, MYF(0), "SQL_NO_CACHE");
+ MYSQL_YYABORT;
+ }
+ else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_CACHE)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "SQL_CACHE", "SQL_NO_CACHE");
+ MYSQL_YYABORT;
+ }
+ else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_NO_CACHE)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "SQL_NO_CACHE");
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ Lex->safe_to_cache_query=0;
+ Lex->select_lex.options&= ~OPTION_TO_QUERY_CACHE;
+ Lex->select_lex.sql_cache= SELECT_LEX::SQL_NO_CACHE;
+ }
+ }
+ | SQL_CACHE_SYM
+ {
+ /*
+ Allow this flag only on the first top-level SELECT statement, if
+ SQL_NO_CACHE wasn't specified, and only once per query.
+ */
+ if (Lex->current_select != &Lex->select_lex)
+ {
+ my_error(ER_CANT_USE_OPTION_HERE, MYF(0), "SQL_CACHE");
+ MYSQL_YYABORT;
+ }
+ else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_NO_CACHE)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "SQL_NO_CACHE", "SQL_CACHE");
+ MYSQL_YYABORT;
+ }
+ else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_CACHE)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "SQL_CACHE");
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ Lex->safe_to_cache_query=1;
+ Lex->select_lex.options|= OPTION_TO_QUERY_CACHE;
+ Lex->select_lex.sql_cache= SELECT_LEX::SQL_CACHE;
+ }
+ }
+ ;
+
+select_lock_type:
+ /* empty */
+ | FOR_SYM UPDATE_SYM
+ {
+ LEX *lex=Lex;
+ lex->current_select->set_lock_for_tables(TL_WRITE);
+ lex->safe_to_cache_query=0;
+ }
+ | LOCK_SYM IN_SYM SHARE_SYM MODE_SYM
+ {
+ LEX *lex=Lex;
+ lex->current_select->
+ set_lock_for_tables(TL_READ_WITH_SHARED_LOCKS);
+ lex->safe_to_cache_query=0;
+ }
+ ;
+
+select_item_list:
+ select_item_list ',' select_item
+ | select_item
+ | '*'
+ {
+ Item *item= new (thd->mem_root)
+ Item_field(&thd->lex->current_select->context,
+ NULL, NULL, "*");
+ if (item == NULL)
+ MYSQL_YYABORT;
+ if (add_item_to_list(thd, item))
+ MYSQL_YYABORT;
+ (thd->lex->current_select->with_wild)++;
+ }
+ ;
+
+select_item:
+ remember_name table_wild remember_end
+ {
+ if (add_item_to_list(thd, $2))
+ MYSQL_YYABORT;
+ }
+ | remember_name expr remember_end select_alias
+ {
+ DBUG_ASSERT($1 < $3);
+
+ if (add_item_to_list(thd, $2))
+ MYSQL_YYABORT;
+ if ($4.str)
+ {
+ if (Lex->sql_command == SQLCOM_CREATE_VIEW &&
+ check_column_name($4.str))
+ {
+ my_error(ER_WRONG_COLUMN_NAME, MYF(0), $4.str);
+ MYSQL_YYABORT;
+ }
+ $2->is_autogenerated_name= FALSE;
+ $2->set_name($4.str, $4.length, system_charset_info);
+ }
+ else if (!$2->name)
+ {
+ $2->set_name($1, (uint) ($3 - $1), thd->charset());
+ }
+ }
+ ;
+
+remember_name:
+ {
+ $$= (char*) YYLIP->get_cpp_tok_start();
+ }
+ ;
+
+remember_end:
+ {
+ $$= (char*) YYLIP->get_cpp_tok_end();
+ }
+ ;
+
+select_alias:
+ /* empty */ { $$=null_lex_str;}
+ | AS ident { $$=$2; }
+ | AS TEXT_STRING_sys { $$=$2; }
+ | ident { $$=$1; }
+ | TEXT_STRING_sys { $$=$1; }
+ ;
+
+opt_default_time_precision:
+ /* empty */ { $$= NOT_FIXED_DEC; }
+ | '(' ')' { $$= NOT_FIXED_DEC; }
+ | '(' real_ulong_num ')' { $$= $2; };
+ ;
+
+opt_time_precision:
+ /* empty */ { $$= 0; }
+ | '(' ')' { $$= 0; }
+ | '(' real_ulong_num ')' { $$= $2; };
+ ;
+
+optional_braces:
+ /* empty */ {}
+ | '(' ')' {}
+ ;
+
+/* all possible expressions */
+expr:
+ expr or expr %prec OR_SYM
+ {
+ /*
+ Design notes:
+ Do not use a manually maintained stack like thd->lex->xxx_list,
+ but use the internal bison stack ($$, $1 and $3) instead.
+ Using the bison stack is:
+ - more robust to changes in the grammar,
+ - guaranteed to be in sync with the parser state,
+ - better for performances (no memory allocation).
+ */
+ Item_cond_or *item1;
+ Item_cond_or *item3;
+ if (is_cond_or($1))
+ {
+ item1= (Item_cond_or*) $1;
+ if (is_cond_or($3))
+ {
+ item3= (Item_cond_or*) $3;
+ /*
+ (X1 OR X2) OR (Y1 OR Y2) ==> OR (X1, X2, Y1, Y2)
+ */
+ item3->add_at_head(item1->argument_list());
+ $$ = $3;
+ }
+ else
+ {
+ /*
+ (X1 OR X2) OR Y ==> OR (X1, X2, Y)
+ */
+ item1->add($3);
+ $$ = $1;
+ }
+ }
+ else if (is_cond_or($3))
+ {
+ item3= (Item_cond_or*) $3;
+ /*
+ X OR (Y1 OR Y2) ==> OR (X, Y1, Y2)
+ */
+ item3->add_at_head($1);
+ $$ = $3;
+ }
+ else
+ {
+ /* X OR Y */
+ $$ = new (thd->mem_root) Item_cond_or($1, $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ }
+ | expr XOR expr %prec XOR
+ {
+ /* XOR is a proprietary extension */
+ $$ = new (thd->mem_root) Item_func_xor($1, $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | expr and expr %prec AND_SYM
+ {
+ /* See comments in rule expr: expr or expr */
+ Item_cond_and *item1;
+ Item_cond_and *item3;
+ if (is_cond_and($1))
+ {
+ item1= (Item_cond_and*) $1;
+ if (is_cond_and($3))
+ {
+ item3= (Item_cond_and*) $3;
+ /*
+ (X1 AND X2) AND (Y1 AND Y2) ==> AND (X1, X2, Y1, Y2)
+ */
+ item3->add_at_head(item1->argument_list());
+ $$ = $3;
+ }
+ else
+ {
+ /*
+ (X1 AND X2) AND Y ==> AND (X1, X2, Y)
+ */
+ item1->add($3);
+ $$ = $1;
+ }
+ }
+ else if (is_cond_and($3))
+ {
+ item3= (Item_cond_and*) $3;
+ /*
+ X AND (Y1 AND Y2) ==> AND (X, Y1, Y2)
+ */
+ item3->add_at_head($1);
+ $$ = $3;
+ }
+ else
+ {
+ /* X AND Y */
+ $$ = new (thd->mem_root) Item_cond_and($1, $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ }
+ | NOT_SYM expr %prec NOT_SYM
+ {
+ $$= negate_expression(thd, $2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri IS TRUE_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_istrue($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri IS not TRUE_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_isnottrue($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri IS FALSE_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_isfalse($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri IS not FALSE_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_isnotfalse($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri IS UNKNOWN_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_isnull($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri IS not UNKNOWN_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_isnotnull($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri
+ ;
+
+bool_pri:
+ bool_pri IS NULL_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_isnull($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri IS not NULL_SYM %prec IS
+ {
+ $$= new (thd->mem_root) Item_func_isnotnull($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri EQUAL_SYM predicate %prec EQUAL_SYM
+ {
+ $$= new (thd->mem_root) Item_func_equal($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri comp_op predicate %prec EQ
+ {
+ $$= (*$2)(0)->create($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bool_pri comp_op all_or_any '(' subselect ')' %prec EQ
+ {
+ $$= all_any_subquery_creator($1, $2, $3, $5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | predicate
+ ;
+
+predicate:
+ bit_expr IN_SYM '(' subselect ')'
+ {
+ $$= new (thd->mem_root) Item_in_subselect($1, $4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr not IN_SYM '(' subselect ')'
+ {
+ Item *item= new (thd->mem_root) Item_in_subselect($1, $5);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ $$= negate_expression(thd, item);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr IN_SYM '(' expr ')'
+ {
+ $$= handle_sql2003_note184_exception(thd, $1, true, $4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr IN_SYM '(' expr ',' expr_list ')'
+ {
+ $6->push_front($4);
+ $6->push_front($1);
+ $$= new (thd->mem_root) Item_func_in(*$6);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr not IN_SYM '(' expr ')'
+ {
+ $$= handle_sql2003_note184_exception(thd, $1, false, $5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr not IN_SYM '(' expr ',' expr_list ')'
+ {
+ $7->push_front($5);
+ $7->push_front($1);
+ Item_func_in *item = new (thd->mem_root) Item_func_in(*$7);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ item->negate();
+ $$= item;
+ }
+ | bit_expr BETWEEN_SYM bit_expr AND_SYM predicate
+ {
+ $$= new (thd->mem_root) Item_func_between($1,$3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr not BETWEEN_SYM bit_expr AND_SYM predicate
+ {
+ Item_func_between *item;
+ item= new (thd->mem_root) Item_func_between($1,$4,$6);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ item->negate();
+ $$= item;
+ }
+ | bit_expr SOUNDS_SYM LIKE bit_expr
+ {
+ Item *item1= new (thd->mem_root) Item_func_soundex($1);
+ Item *item4= new (thd->mem_root) Item_func_soundex($4);
+ if ((item1 == NULL) || (item4 == NULL))
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root) Item_func_eq(item1, item4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr LIKE simple_expr opt_escape
+ {
+ $$= new (thd->mem_root) Item_func_like($1,$3,$4,Lex->escape_used);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr not LIKE simple_expr opt_escape
+ {
+ Item *item= new (thd->mem_root) Item_func_like($1,$4,$5,
+ Lex->escape_used);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root) Item_func_not(item);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr REGEXP bit_expr
+ {
+ $$= new (thd->mem_root) Item_func_regex($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr not REGEXP bit_expr
+ {
+ Item *item= new (thd->mem_root) Item_func_regex($1,$4);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ $$= negate_expression(thd, item);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr
+ ;
+
+bit_expr:
+ bit_expr '|' bit_expr %prec '|'
+ {
+ $$= new (thd->mem_root) Item_func_bit_or($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '&' bit_expr %prec '&'
+ {
+ $$= new (thd->mem_root) Item_func_bit_and($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr SHIFT_LEFT bit_expr %prec SHIFT_LEFT
+ {
+ $$= new (thd->mem_root) Item_func_shift_left($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr SHIFT_RIGHT bit_expr %prec SHIFT_RIGHT
+ {
+ $$= new (thd->mem_root) Item_func_shift_right($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '+' bit_expr %prec '+'
+ {
+ $$= new (thd->mem_root) Item_func_plus($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '-' bit_expr %prec '-'
+ {
+ $$= new (thd->mem_root) Item_func_minus($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '+' INTERVAL_SYM expr interval %prec '+'
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($1,$4,$5,0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '-' INTERVAL_SYM expr interval %prec '-'
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($1,$4,$5,1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '*' bit_expr %prec '*'
+ {
+ $$= new (thd->mem_root) Item_func_mul($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '/' bit_expr %prec '/'
+ {
+ $$= new (thd->mem_root) Item_func_div($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '%' bit_expr %prec '%'
+ {
+ $$= new (thd->mem_root) Item_func_mod($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr DIV_SYM bit_expr %prec DIV_SYM
+ {
+ $$= new (thd->mem_root) Item_func_int_div($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr MOD_SYM bit_expr %prec MOD_SYM
+ {
+ $$= new (thd->mem_root) Item_func_mod($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | bit_expr '^' bit_expr
+ {
+ $$= new (thd->mem_root) Item_func_bit_xor($1,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | simple_expr
+ ;
+
+or:
+ OR_SYM
+ | OR2_SYM
+ ;
+
+and:
+ AND_SYM
+ | AND_AND_SYM
+ ;
+
+not:
+ NOT_SYM
+ | NOT2_SYM
+ ;
+
+not2:
+ '!'
+ | NOT2_SYM
+ ;
+
+comp_op:
+ EQ { $$ = &comp_eq_creator; }
+ | GE { $$ = &comp_ge_creator; }
+ | GT_SYM { $$ = &comp_gt_creator; }
+ | LE { $$ = &comp_le_creator; }
+ | LT { $$ = &comp_lt_creator; }
+ | NE { $$ = &comp_ne_creator; }
+ ;
+
+all_or_any:
+ ALL { $$ = 1; }
+ | ANY_SYM { $$ = 0; }
+ ;
+
+opt_dyncol_type:
+ /* empty */
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_NULL; /* automatic type */
+ lex->charset= NULL;
+ lex->length= lex->dec= 0;
+ }
+ | AS dyncol_type { $$= $2; }
+ ;
+
+dyncol_type:
+ INT_SYM
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_INT;
+ lex->charset= NULL;
+ lex->length= lex->dec= 0;
+ }
+ | UNSIGNED INT_SYM
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_UINT;
+ lex->charset= NULL;
+ lex->length= lex->dec= 0;
+ }
+ | DOUBLE_SYM
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_DOUBLE;
+ lex->charset= NULL;
+ lex->length= lex->dec= 0;
+ }
+ | REAL
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_DOUBLE;
+ lex->charset= NULL;
+ lex->length= lex->dec= 0;
+ }
+ | FLOAT_SYM
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_DOUBLE;
+ lex->charset= NULL;
+ lex->length= lex->dec= 0;
+ }
+ | DECIMAL_SYM float_options
+ {
+ $$= DYN_COL_DECIMAL;
+ Lex->charset= NULL;
+ }
+ | char opt_binary
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_STRING;
+ lex->length= lex->dec= 0;
+ }
+ | nchar
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_STRING;
+ lex->charset= national_charset_info;
+ lex->length= lex->dec= 0;
+ }
+ | DATE_SYM
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_DATE;
+ lex->charset= NULL;
+ lex->length= lex->dec= 0;
+ }
+ | TIME_SYM opt_field_length
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_TIME;
+ lex->charset= NULL;
+ lex->dec= lex->length;
+ lex->length= 0;
+ }
+ | DATETIME opt_field_length
+ {
+ LEX *lex= Lex;
+ $$= DYN_COL_DATETIME;
+ lex->charset= NULL;
+ lex->dec= lex->length;
+ lex->length= 0;
+ }
+ ;
+
+dyncall_create_element:
+ expr ',' expr opt_dyncol_type
+ {
+ LEX *lex= Lex;
+ $$= (DYNCALL_CREATE_DEF *)
+ alloc_root(thd->mem_root, sizeof(DYNCALL_CREATE_DEF));
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->key= $1;
+ $$->value= $3;
+ $$->type= (DYNAMIC_COLUMN_TYPE)$4;
+ $$->cs= lex->charset;
+ if (lex->length)
+ $$->len= strtoul(lex->length, NULL, 10);
+ else
+ $$->len= 0;
+ if (lex->dec)
+ $$->frac= strtoul(lex->dec, NULL, 10);
+ else
+ $$->len= 0;
+ }
+
+dyncall_create_list:
+ dyncall_create_element
+ {
+ $$= new (thd->mem_root) List<DYNCALL_CREATE_DEF>;
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->push_back($1);
+ }
+ | dyncall_create_list ',' dyncall_create_element
+ {
+ $1->push_back($3);
+ $$= $1;
+ }
+ ;
+
+simple_expr:
+ simple_ident
+ | function_call_keyword
+ | function_call_nonkeyword
+ | function_call_generic
+ | function_call_conflict
+ | simple_expr COLLATE_SYM ident_or_text %prec NEG
+ {
+ Item *i1= new (thd->mem_root) Item_string($3.str,
+ $3.length,
+ thd->charset());
+ if (i1 == NULL)
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root) Item_func_set_collation($1, i1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | literal
+ | param_marker
+ | variable
+ | sum_expr
+ | simple_expr OR_OR_SYM simple_expr
+ {
+ $$= new (thd->mem_root) Item_func_concat($1, $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | '+' simple_expr %prec NEG
+ {
+ $$= $2;
+ }
+ | '-' simple_expr %prec NEG
+ {
+ $$= new (thd->mem_root) Item_func_neg($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | '~' simple_expr %prec NEG
+ {
+ $$= new (thd->mem_root) Item_func_bit_neg($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | not2 simple_expr %prec NEG
+ {
+ $$= negate_expression(thd, $2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | '(' subselect ')'
+ {
+ $$= new (thd->mem_root) Item_singlerow_subselect($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | '(' expr ')'
+ { $$= $2; }
+ | '(' expr ',' expr_list ')'
+ {
+ $4->push_front($2);
+ $$= new (thd->mem_root) Item_row(*$4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ROW_SYM '(' expr ',' expr_list ')'
+ {
+ $5->push_front($3);
+ $$= new (thd->mem_root) Item_row(*$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | EXISTS '(' subselect ')'
+ {
+ $$= new (thd->mem_root) Item_exists_subselect($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | '{' ident expr '}'
+ {
+ $$= NULL;
+ /*
+ If "expr" is reasonably short pure ASCII string literal,
+ try to parse known ODBC style date, time or timestamp literals,
+ e.g:
+ SELECT {d'2001-01-01'};
+ SELECT {t'10:20:30'};
+ SELECT {ts'2001-01-01 10:20:30'};
+ */
+ if ($3->type() == Item::STRING_ITEM)
+ {
+ Item_string *item= (Item_string *) $3;
+ enum_field_types type= item->odbc_temporal_literal_type(&$2);
+ if (type != MYSQL_TYPE_STRING)
+ {
+ $$= create_temporal_literal(thd, item->val_str(NULL),
+ type, false);
+ }
+ }
+ if ($$ == NULL)
+ $$= $3;
+ }
+ | MATCH ident_list_arg AGAINST '(' bit_expr fulltext_options ')'
+ {
+ $2->push_front($5);
+ Item_func_match *i1= new (thd->mem_root) Item_func_match(*$2, $6);
+ if (i1 == NULL)
+ MYSQL_YYABORT;
+ Select->add_ftfunc_to_list(i1);
+ $$= i1;
+ }
+ | BINARY simple_expr %prec NEG
+ {
+ $$= create_func_cast(thd, $2, ITEM_CAST_CHAR, NULL, NULL,
+ &my_charset_bin);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CAST_SYM '(' expr AS cast_type ')'
+ {
+ LEX *lex= Lex;
+ $$= create_func_cast(thd, $3, $5, lex->length, lex->dec,
+ lex->charset);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CASE_SYM opt_expr when_list opt_else END
+ {
+ $$= new (thd->mem_root) Item_func_case(* $3, $2, $4 );
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CONVERT_SYM '(' expr ',' cast_type ')'
+ {
+ $$= create_func_cast(thd, $3, $5, Lex->length, Lex->dec,
+ Lex->charset);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CONVERT_SYM '(' expr USING charset_name ')'
+ {
+ $$= new (thd->mem_root) Item_func_conv_charset($3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | DEFAULT '(' simple_ident ')'
+ {
+ if ($3->is_splocal())
+ {
+ Item_splocal *il= static_cast<Item_splocal *>($3);
+
+ my_error(ER_WRONG_COLUMN_NAME, MYF(0), il->my_name()->str);
+ MYSQL_YYABORT;
+ }
+ $$= new (thd->mem_root) Item_default_value(Lex->current_context(),
+ $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | VALUES '(' simple_ident_nospvar ')'
+ {
+ $$= new (thd->mem_root) Item_insert_value(Lex->current_context(),
+ $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | INTERVAL_SYM expr interval '+' expr %prec INTERVAL_SYM
+ /* we cannot put interval before - */
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($5,$2,$3,0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+/*
+ Function call syntax using official SQL 2003 keywords.
+ Because the function name is an official token,
+ a dedicated grammar rule is needed in the parser.
+ There is no potential for conflicts
+*/
+function_call_keyword:
+ CHAR_SYM '(' expr_list ')'
+ {
+ $$= new (thd->mem_root) Item_func_char(*$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CHAR_SYM '(' expr_list USING charset_name ')'
+ {
+ $$= new (thd->mem_root) Item_func_char(*$3, $5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CURRENT_USER optional_braces
+ {
+ $$= new (thd->mem_root) Item_func_current_user(Lex->current_context());
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ Lex->safe_to_cache_query= 0;
+ }
+ | CURRENT_ROLE optional_braces
+ {
+ $$= new (thd->mem_root) Item_func_current_role(Lex->current_context());
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ Lex->safe_to_cache_query= 0;
+ }
+ | DATE_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_date_typecast($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | DAY_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_dayofmonth($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | HOUR_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_hour($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | INSERT '(' expr ',' expr ',' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_insert($3,$5,$7,$9);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | INTERVAL_SYM '(' expr ',' expr ')' %prec INTERVAL_SYM
+ {
+ List<Item> *list= new (thd->mem_root) List<Item>;
+ if (list == NULL)
+ MYSQL_YYABORT;
+ list->push_front($5);
+ list->push_front($3);
+ Item_row *item= new (thd->mem_root) Item_row(*list);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root) Item_func_interval(item);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | INTERVAL_SYM '(' expr ',' expr ',' expr_list ')' %prec INTERVAL_SYM
+ {
+ $7->push_front($5);
+ $7->push_front($3);
+ Item_row *item= new (thd->mem_root) Item_row(*$7);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root) Item_func_interval(item);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | LEFT '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_left($3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | MINUTE_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_minute($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | MONTH_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_month($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | RIGHT '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_right($3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SECOND_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_second($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TIME_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_time_typecast($3, AUTO_SEC_PART_DIGITS);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TIMESTAMP '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_datetime_typecast($3, AUTO_SEC_PART_DIGITS);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TIMESTAMP '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_add_time($3, $5, 1, 0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_trim($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' LEADING expr FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_ltrim($6,$4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' TRAILING expr FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_rtrim($6,$4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' BOTH expr FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_trim($6,$4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' LEADING FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_ltrim($5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' TRAILING FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_rtrim($5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' BOTH FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_trim($5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRIM '(' expr FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_trim($5,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | USER '(' ')'
+ {
+ $$= new (thd->mem_root) Item_func_user();
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ Lex->safe_to_cache_query=0;
+ }
+ | YEAR_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_year($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+/*
+ Function calls using non reserved keywords, with special syntaxic forms.
+ Dedicated grammar rules are needed because of the syntax,
+ but also have the potential to cause incompatibilities with other
+ parts of the language.
+ MAINTAINER:
+ The only reasons a function should be added here are:
+ - for compatibility reasons with another SQL syntax (CURDATE),
+ - for typing reasons (GET_FORMAT)
+ Any other 'Syntaxic sugar' enhancements should be *STRONGLY*
+ discouraged.
+*/
+function_call_nonkeyword:
+ ADDDATE_SYM '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($3, $5,
+ INTERVAL_DAY, 0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ADDDATE_SYM '(' expr ',' INTERVAL_SYM expr interval ')'
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($3, $6, $7, 0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CURDATE optional_braces
+ {
+ $$= new (thd->mem_root) Item_func_curdate_local();
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ | CURTIME opt_time_precision
+ {
+ $$= new (thd->mem_root) Item_func_curtime_local($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ | DATE_ADD_INTERVAL '(' expr ',' INTERVAL_SYM expr interval ')'
+ %prec INTERVAL_SYM
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($3,$6,$7,0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | DATE_SUB_INTERVAL '(' expr ',' INTERVAL_SYM expr interval ')'
+ %prec INTERVAL_SYM
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($3,$6,$7,1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | EXTRACT_SYM '(' interval FROM expr ')'
+ {
+ $$=new (thd->mem_root) Item_extract( $3, $5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | GET_FORMAT '(' date_time_type ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_get_format($3, $5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | NOW_SYM opt_time_precision
+ {
+ $$= new (thd->mem_root) Item_func_now_local($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ | POSITION_SYM '(' bit_expr IN_SYM expr ')'
+ {
+ $$ = new (thd->mem_root) Item_func_locate($5,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUBDATE_SYM '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($3, $5,
+ INTERVAL_DAY, 1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUBDATE_SYM '(' expr ',' INTERVAL_SYM expr interval ')'
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($3, $6, $7, 1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUBSTRING '(' expr ',' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_substr($3,$5,$7);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUBSTRING '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_substr($3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUBSTRING '(' expr FROM expr FOR_SYM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_substr($3,$5,$7);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUBSTRING '(' expr FROM expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_substr($3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SYSDATE opt_time_precision
+ {
+ /*
+ Unlike other time-related functions, SYSDATE() is
+ replication-unsafe because it is not affected by the
+ TIMESTAMP variable. It is unsafe even if
+ sysdate_is_now=1, because the slave may have
+ sysdate_is_now=0.
+ */
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ if (global_system_variables.sysdate_is_now == 0)
+ $$= new (thd->mem_root) Item_func_sysdate_local($2);
+ else
+ $$= new (thd->mem_root) Item_func_now_local($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ | TIMESTAMP_ADD '(' interval_time_stamp ',' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_date_add_interval($7,$5,$3,0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TIMESTAMP_DIFF '(' interval_time_stamp ',' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_timestamp_diff($5,$7,$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | UTC_DATE_SYM optional_braces
+ {
+ $$= new (thd->mem_root) Item_func_curdate_utc();
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ | UTC_TIME_SYM opt_time_precision
+ {
+ $$= new (thd->mem_root) Item_func_curtime_utc($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ | UTC_TIMESTAMP_SYM opt_time_precision
+ {
+ $$= new (thd->mem_root) Item_func_now_utc($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ |
+ COLUMN_ADD_SYM '(' expr ',' dyncall_create_list ')'
+ {
+ $$= create_func_dyncol_add(thd, $3, *$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ |
+ COLUMN_DELETE_SYM '(' expr ',' expr_list ')'
+ {
+ $$= create_func_dyncol_delete(thd, $3, *$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ |
+ COLUMN_CHECK_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_dyncol_check($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ |
+ COLUMN_CREATE_SYM '(' dyncall_create_list ')'
+ {
+ $$= create_func_dyncol_create(thd, *$3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ |
+ COLUMN_GET_SYM '(' expr ',' expr AS cast_type ')'
+ {
+ LEX *lex= Lex;
+ $$= create_func_dyncol_get(thd, $3, $5, $7,
+ lex->length, lex->dec,
+ lex->charset);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+/*
+ Functions calls using a non reserved keyword, and using a regular syntax.
+ Because the non reserved keyword is used in another part of the grammar,
+ a dedicated rule is needed here.
+*/
+function_call_conflict:
+ ASCII_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_ascii($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | CHARSET '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_charset($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | COALESCE '(' expr_list ')'
+ {
+ $$= new (thd->mem_root) Item_func_coalesce(* $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | COLLATION_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_collation($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | DATABASE '(' ')'
+ {
+ $$= new (thd->mem_root) Item_func_database();
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->safe_to_cache_query=0;
+ }
+ | IF '(' expr ',' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_if($3,$5,$7);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | LAST_VALUE '(' expr_list ')'
+ {
+ $$= new (thd->mem_root) Item_func_last_value(* $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | MICROSECOND_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_microsecond($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | MOD_SYM '(' expr ',' expr ')'
+ {
+ $$ = new (thd->mem_root) Item_func_mod($3, $5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | OLD_PASSWORD '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_old_password($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | PASSWORD '(' expr ')'
+ {
+ Item* i1;
+ if (thd->variables.old_passwords)
+ i1= new (thd->mem_root) Item_func_old_password($3);
+ else
+ i1= new (thd->mem_root) Item_func_password($3);
+ if (i1 == NULL)
+ MYSQL_YYABORT;
+ $$= i1;
+ }
+ | QUARTER_SYM '(' expr ')'
+ {
+ $$ = new (thd->mem_root) Item_func_quarter($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | REPEAT_SYM '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_repeat($3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | REPLACE '(' expr ',' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_replace($3,$5,$7);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | REVERSE_SYM '(' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_reverse($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ROW_COUNT_SYM '(' ')'
+ {
+ $$= new (thd->mem_root) Item_func_row_count();
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
+ Lex->safe_to_cache_query= 0;
+ }
+ | TRUNCATE_SYM '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_round($3,$5,1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | WEEK_SYM '(' expr ')'
+ {
+ Item *i1= new (thd->mem_root) Item_int((char*) "0",
+ thd->variables.default_week_format,
+ 1);
+ if (i1 == NULL)
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root) Item_func_week($3, i1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | WEEK_SYM '(' expr ',' expr ')'
+ {
+ $$= new (thd->mem_root) Item_func_week($3,$5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | WEIGHT_STRING_SYM '(' expr opt_ws_levels ')'
+ {
+ $$= new (thd->mem_root) Item_func_weight_string($3, 0, 0, $4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | WEIGHT_STRING_SYM '(' expr AS CHAR_SYM ws_nweights opt_ws_levels ')'
+ {
+ $$= new (thd->mem_root)
+ Item_func_weight_string($3, 0, $6,
+ $7 | MY_STRXFRM_PAD_WITH_SPACE);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | WEIGHT_STRING_SYM '(' expr AS BINARY ws_nweights ')'
+ {
+ Item *item= new (thd->mem_root) Item_char_typecast($3, $6, &my_charset_bin);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root)
+ Item_func_weight_string(item, 0, $6, MY_STRXFRM_PAD_WITH_SPACE);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | WEIGHT_STRING_SYM '(' expr ',' ulong_num ',' ulong_num ',' ulong_num ')'
+ {
+ $$= new (thd->mem_root) Item_func_weight_string($3, $5, $7, $9);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | geometry_function
+ {
+#ifdef HAVE_SPATIAL
+ $$= $1;
+ /* $1 may be NULL, GEOM_NEW not tested for out of memory */
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+#else
+ my_error(ER_FEATURE_DISABLED, MYF(0),
+ sym_group_geom.name, sym_group_geom.needed_define);
+ MYSQL_YYABORT;
+#endif
+ }
+ ;
+
+geometry_function:
+ CONTAINS_SYM '(' expr ',' expr ')'
+ {
+ $$= GEOM_NEW(thd,
+ Item_func_spatial_rel($3, $5,
+ Item_func::SP_CONTAINS_FUNC));
+ }
+ | GEOMETRYCOLLECTION '(' expr_list ')'
+ {
+ $$= GEOM_NEW(thd,
+ Item_func_spatial_collection(* $3,
+ Geometry::wkb_geometrycollection,
+ Geometry::wkb_point));
+ }
+ | LINESTRING '(' expr_list ')'
+ {
+ $$= GEOM_NEW(thd,
+ Item_func_spatial_collection(* $3,
+ Geometry::wkb_linestring,
+ Geometry::wkb_point));
+ }
+ | MULTILINESTRING '(' expr_list ')'
+ {
+ $$= GEOM_NEW(thd,
+ Item_func_spatial_collection(* $3,
+ Geometry::wkb_multilinestring,
+ Geometry::wkb_linestring));
+ }
+ | MULTIPOINT '(' expr_list ')'
+ {
+ $$= GEOM_NEW(thd,
+ Item_func_spatial_collection(* $3,
+ Geometry::wkb_multipoint,
+ Geometry::wkb_point));
+ }
+ | MULTIPOLYGON '(' expr_list ')'
+ {
+ $$= GEOM_NEW(thd,
+ Item_func_spatial_collection(* $3,
+ Geometry::wkb_multipolygon,
+ Geometry::wkb_polygon));
+ }
+ | POINT_SYM '(' expr ',' expr ')'
+ {
+ $$= GEOM_NEW(thd, Item_func_point($3,$5));
+ }
+ | POLYGON '(' expr_list ')'
+ {
+ $$= GEOM_NEW(thd,
+ Item_func_spatial_collection(* $3,
+ Geometry::wkb_polygon,
+ Geometry::wkb_linestring));
+ }
+ ;
+
+/*
+ Regular function calls.
+ The function name is *not* a token, and therefore is guaranteed to not
+ introduce side effects to the language in general.
+ MAINTAINER:
+ All the new functions implemented for new features should fit into
+ this category. The place to implement the function itself is
+ in sql/item_create.cc
+*/
+function_call_generic:
+ IDENT_sys '('
+ {
+#ifdef HAVE_DLOPEN
+ udf_func *udf= 0;
+ LEX *lex= Lex;
+ if (using_udf_functions &&
+ (udf= find_udf($1.str, $1.length)) &&
+ udf->type == UDFTYPE_AGGREGATE)
+ {
+ if (lex->current_select->inc_in_sum_expr())
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ /* Temporary placing the result of find_udf in $3 */
+ $<udf>$= udf;
+#endif
+ }
+ opt_udf_expr_list ')'
+ {
+ Create_func *builder;
+ Item *item= NULL;
+
+ if (check_routine_name(&$1))
+ {
+ MYSQL_YYABORT;
+ }
+
+ /*
+ Implementation note:
+ names are resolved with the following order:
+ - MySQL native functions,
+ - User Defined Functions,
+ - Stored Functions (assuming the current <use> database)
+
+ This will be revised with WL#2128 (SQL PATH)
+ */
+ builder= find_native_function_builder(thd, $1);
+ if (builder)
+ {
+ item= builder->create_func(thd, $1, $4);
+ }
+ else
+ {
+#ifdef HAVE_DLOPEN
+ /* Retrieving the result of find_udf */
+ udf_func *udf= $<udf>3;
+
+ if (udf)
+ {
+ if (udf->type == UDFTYPE_AGGREGATE)
+ {
+ Select->in_sum_expr--;
+ }
+
+ item= Create_udf_func::s_singleton.create(thd, udf, $4);
+ }
+ else
+#endif
+ {
+ builder= find_qualified_function_builder(thd);
+ DBUG_ASSERT(builder);
+ item= builder->create_func(thd, $1, $4);
+ }
+ }
+
+ if (! ($$= item))
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ | ident '.' ident '(' opt_expr_list ')'
+ {
+ Create_qfunc *builder;
+ Item *item= NULL;
+
+ /*
+ The following in practice calls:
+ <code>Create_sp_func::create()</code>
+ and builds a stored function.
+
+ However, it's important to maintain the interface between the
+ parser and the implementation in item_create.cc clean,
+ since this will change with WL#2128 (SQL PATH):
+ - INFORMATION_SCHEMA.version() is the SQL 99 syntax for the native
+ function version(),
+ - MySQL.version() is the SQL 2003 syntax for the native function
+ version() (a vendor can specify any schema).
+ */
+
+ if (!$1.str || check_db_name(&$1))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ if (check_routine_name(&$3))
+ {
+ MYSQL_YYABORT;
+ }
+
+ builder= find_qualified_function_builder(thd);
+ DBUG_ASSERT(builder);
+ item= builder->create_with_db(thd, $1, $3, true, $5);
+
+ if (! ($$= item))
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+fulltext_options:
+ opt_natural_language_mode opt_query_expansion
+ { $$= $1 | $2; }
+ | IN_SYM BOOLEAN_SYM MODE_SYM
+ { $$= FT_BOOL; }
+ ;
+
+opt_natural_language_mode:
+ /* nothing */ { $$= FT_NL; }
+ | IN_SYM NATURAL LANGUAGE_SYM MODE_SYM { $$= FT_NL; }
+ ;
+
+opt_query_expansion:
+ /* nothing */ { $$= 0; }
+ | WITH QUERY_SYM EXPANSION_SYM { $$= FT_EXPAND; }
+ ;
+
+opt_udf_expr_list:
+ /* empty */ { $$= NULL; }
+ | udf_expr_list { $$= $1; }
+ ;
+
+udf_expr_list:
+ udf_expr
+ {
+ $$= new (thd->mem_root) List<Item>;
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->push_back($1);
+ }
+ | udf_expr_list ',' udf_expr
+ {
+ $1->push_back($3);
+ $$= $1;
+ }
+ ;
+
+udf_expr:
+ remember_name expr remember_end select_alias
+ {
+ /*
+ Use Item::name as a storage for the attribute value of user
+ defined function argument. It is safe to use Item::name
+ because the syntax will not allow having an explicit name here.
+ See WL#1017 re. udf attributes.
+ */
+ if ($4.str)
+ {
+ $2->is_autogenerated_name= FALSE;
+ $2->set_name($4.str, $4.length, system_charset_info);
+ }
+ /*
+ A field has to have its proper name in order for name
+ resolution to work, something we are only guaranteed if we
+ parse it out. If we hijack the input stream with
+ remember_name we may get quoted or escaped names.
+ */
+ else if ($2->type() != Item::FIELD_ITEM &&
+ $2->type() != Item::REF_ITEM /* For HAVING */ )
+ $2->set_name($1, (uint) ($3 - $1), thd->charset());
+ $$= $2;
+ }
+ ;
+
+sum_expr:
+ AVG_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_avg($3, FALSE);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | AVG_SYM '(' DISTINCT in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_avg($4, TRUE);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | BIT_AND '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_and($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | BIT_OR '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_or($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | BIT_XOR '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_xor($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | COUNT_SYM '(' opt_all '*' ')'
+ {
+ Item *item= new (thd->mem_root) Item_int((int32) 0L,1);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ $$= new (thd->mem_root) Item_sum_count(item);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | COUNT_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_count($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | COUNT_SYM '(' DISTINCT
+ { Select->in_sum_expr++; }
+ expr_list
+ { Select->in_sum_expr--; }
+ ')'
+ {
+ $$= new (thd->mem_root) Item_sum_count(* $5);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | MIN_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_min($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ /*
+ According to ANSI SQL, DISTINCT is allowed and has
+ no sense inside MIN and MAX grouping functions; so MIN|MAX(DISTINCT ...)
+ is processed like an ordinary MIN | MAX()
+ */
+ | MIN_SYM '(' DISTINCT in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_min($4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | MAX_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_max($3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | MAX_SYM '(' DISTINCT in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_max($4);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | STD_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_std($3, 0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | VARIANCE_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_variance($3, 0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | STDDEV_SAMP_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_std($3, 1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | VAR_SAMP_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_variance($3, 1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUM_SYM '(' in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_sum($3, FALSE);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SUM_SYM '(' DISTINCT in_sum_expr ')'
+ {
+ $$= new (thd->mem_root) Item_sum_sum($4, TRUE);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | GROUP_CONCAT_SYM '(' opt_distinct
+ { Select->in_sum_expr++; }
+ expr_list opt_gorder_clause
+ opt_gconcat_separator
+ ')'
+ {
+ SELECT_LEX *sel= Select;
+ sel->in_sum_expr--;
+ $$= new (thd->mem_root)
+ Item_func_group_concat(Lex->current_context(), $3, $5,
+ sel->gorder_list, $7);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $5->empty();
+ sel->gorder_list.empty();
+ }
+ ;
+
+variable:
+ '@'
+ {
+ if (! Lex->parsing_options.allows_variable)
+ {
+ my_error(ER_VIEW_SELECT_VARIABLE, MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ variable_aux
+ {
+ $$= $3;
+ }
+ ;
+
+variable_aux:
+ ident_or_text SET_VAR expr
+ {
+ Item_func_set_user_var *item;
+ $$= item= new (thd->mem_root) Item_func_set_user_var($1, $3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ LEX *lex= Lex;
+ lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ lex->set_var_list.push_back(item);
+ }
+ | ident_or_text
+ {
+ $$= new (thd->mem_root) Item_func_get_user_var($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ LEX *lex= Lex;
+ lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ }
+ | '@' opt_var_ident_type ident_or_text opt_component
+ {
+ /* disallow "SELECT @@global.global.variable" */
+ if ($3.str && $4.str && check_reserved_words(&$3))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ if (!($$= get_system_var(thd, $2, $3, $4)))
+ MYSQL_YYABORT;
+ if (!((Item_func_get_system_var*) $$)->is_written_to_binlog())
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_VARIABLE);
+ }
+ ;
+
+opt_distinct:
+ /* empty */ { $$ = 0; }
+ | DISTINCT { $$ = 1; }
+ ;
+
+opt_gconcat_separator:
+ /* empty */
+ {
+ $$= new (thd->mem_root) String(",", 1, &my_charset_latin1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | SEPARATOR_SYM text_string { $$ = $2; }
+ ;
+
+opt_gorder_clause:
+ /* empty */
+ | ORDER_SYM BY
+ {
+ LEX *lex= Lex;
+ SELECT_LEX *sel= lex->current_select;
+ if (sel->linkage != GLOBAL_OPTIONS_TYPE &&
+ sel->olap != UNSPECIFIED_OLAP_TYPE &&
+ (sel->linkage != UNION_TYPE || sel->braces))
+ {
+ my_error(ER_WRONG_USAGE, MYF(0),
+ "CUBE/ROLLUP", "ORDER BY");
+ MYSQL_YYABORT;
+ }
+ }
+ gorder_list;
+ ;
+
+gorder_list:
+ gorder_list ',' order_ident order_dir
+ { if (add_gorder_to_list(thd, $3,(bool) $4)) MYSQL_YYABORT; }
+ | order_ident order_dir
+ { if (add_gorder_to_list(thd, $1,(bool) $2)) MYSQL_YYABORT; }
+ ;
+
+in_sum_expr:
+ opt_all
+ {
+ LEX *lex= Lex;
+ if (lex->current_select->inc_in_sum_expr())
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ expr
+ {
+ Select->in_sum_expr--;
+ $$= $3;
+ }
+ ;
+
+cast_type:
+ BINARY opt_field_length
+ { $$=ITEM_CAST_CHAR; Lex->charset= &my_charset_bin; Lex->dec= 0; }
+ | CHAR_SYM opt_field_length opt_binary
+ { $$=ITEM_CAST_CHAR; Lex->dec= 0; }
+ | NCHAR_SYM opt_field_length
+ { $$=ITEM_CAST_CHAR; Lex->charset= national_charset_info; Lex->dec=0; }
+ | INT_SYM
+ { $$=ITEM_CAST_SIGNED_INT; Lex->charset= NULL; Lex->dec=Lex->length= (char*)0; }
+ | SIGNED_SYM
+ { $$=ITEM_CAST_SIGNED_INT; Lex->charset= NULL; Lex->dec=Lex->length= (char*)0; }
+ | SIGNED_SYM INT_SYM
+ { $$=ITEM_CAST_SIGNED_INT; Lex->charset= NULL; Lex->dec=Lex->length= (char*)0; }
+ | UNSIGNED
+ { $$=ITEM_CAST_UNSIGNED_INT; Lex->charset= NULL; Lex->dec=Lex->length= (char*)0; }
+ | UNSIGNED INT_SYM
+ { $$=ITEM_CAST_UNSIGNED_INT; Lex->charset= NULL; Lex->dec=Lex->length= (char*)0; }
+ | DATE_SYM
+ { $$=ITEM_CAST_DATE; Lex->charset= NULL; Lex->dec=Lex->length= (char*)0; }
+ | TIME_SYM opt_field_length
+ {
+ $$=ITEM_CAST_TIME;
+ LEX *lex= Lex;
+ lex->charset= NULL; lex->dec= lex->length; lex->length= (char*)0;
+ }
+ | DATETIME opt_field_length
+ {
+ $$=ITEM_CAST_DATETIME;
+ LEX *lex= Lex;
+ lex->charset= NULL; lex->dec= lex->length; lex->length= (char*)0;
+ }
+ | DECIMAL_SYM float_options
+ { $$=ITEM_CAST_DECIMAL; Lex->charset= NULL; }
+ | DOUBLE_SYM
+ { Lex->charset= NULL; Lex->length= Lex->dec= 0;}
+ opt_precision
+ { $$=ITEM_CAST_DOUBLE; }
+
+opt_expr_list:
+ /* empty */ { $$= NULL; }
+ | expr_list { $$= $1;}
+ ;
+
+expr_list:
+ expr
+ {
+ $$= new (thd->mem_root) List<Item>;
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->push_back($1);
+ }
+ | expr_list ',' expr
+ {
+ $1->push_back($3);
+ $$= $1;
+ }
+ ;
+
+ident_list_arg:
+ ident_list { $$= $1; }
+ | '(' ident_list ')' { $$= $2; }
+ ;
+
+ident_list:
+ simple_ident
+ {
+ $$= new (thd->mem_root) List<Item>;
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->push_back($1);
+ }
+ | ident_list ',' simple_ident
+ {
+ $1->push_back($3);
+ $$= $1;
+ }
+ ;
+
+opt_expr:
+ /* empty */ { $$= NULL; }
+ | expr { $$= $1; }
+ ;
+
+opt_else:
+ /* empty */ { $$= NULL; }
+ | ELSE expr { $$= $2; }
+ ;
+
+when_list:
+ WHEN_SYM expr THEN_SYM expr
+ {
+ $$= new List<Item>;
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ $$->push_back($2);
+ $$->push_back($4);
+ }
+ | when_list WHEN_SYM expr THEN_SYM expr
+ {
+ $1->push_back($3);
+ $1->push_back($5);
+ $$= $1;
+ }
+ ;
+
+/* Equivalent to <table reference> in the SQL:2003 standard. */
+/* Warning - may return NULL in case of incomplete SELECT */
+table_ref:
+ table_factor { $$=$1; }
+ | join_table
+ {
+ LEX *lex= Lex;
+ if (!($$= lex->current_select->nest_last_join(lex->thd)))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+join_table_list:
+ derived_table_list { MYSQL_YYABORT_UNLESS($$=$1); }
+ ;
+
+/*
+ The ODBC escape syntax for Outer Join is: '{' OJ join_table '}'
+ The parser does not define OJ as a token, any ident is accepted
+ instead in $2 (ident). Also, all productions from table_ref can
+ be escaped, not only join_table. Both syntax extensions are safe
+ and are ignored.
+*/
+esc_table_ref:
+ table_ref { $$=$1; }
+ | '{' ident table_ref '}' { $$=$3; }
+ ;
+
+/* Equivalent to <table reference list> in the SQL:2003 standard. */
+/* Warning - may return NULL in case of incomplete SELECT */
+derived_table_list:
+ esc_table_ref { $$=$1; }
+ | derived_table_list ',' esc_table_ref
+ {
+ MYSQL_YYABORT_UNLESS($1 && ($$=$3));
+ }
+ ;
+
+/*
+ Notice that JOIN is a left-associative operation, and it must be parsed
+ as such, that is, the parser must process first the left join operand
+ then the right one. Such order of processing ensures that the parser
+ produces correct join trees which is essential for semantic analysis
+ and subsequent optimization phases.
+*/
+join_table:
+ /* INNER JOIN variants */
+ /*
+ Use %prec to evaluate production 'table_ref' before 'normal_join'
+ so that [INNER | CROSS] JOIN is properly nested as other
+ left-associative joins.
+ */
+ table_ref normal_join table_ref %prec TABLE_REF_PRIORITY
+ { MYSQL_YYABORT_UNLESS($1 && ($$=$3)); $3->straight=$2; }
+ | table_ref normal_join table_ref
+ ON
+ {
+ MYSQL_YYABORT_UNLESS($1 && $3);
+ /* Change the current name resolution context to a local context. */
+ if (push_new_name_resolution_context(thd, $1, $3))
+ MYSQL_YYABORT;
+ Select->parsing_place= IN_ON;
+ }
+ expr
+ {
+ $3->straight=$2;
+ add_join_on($3,$6);
+ Lex->pop_context();
+ Select->parsing_place= NO_MATTER;
+ }
+ | table_ref normal_join table_ref
+ USING
+ {
+ MYSQL_YYABORT_UNLESS($1 && $3);
+ }
+ '(' using_list ')'
+ {
+ $3->straight=$2;
+ add_join_natural($1,$3,$7,Select);
+ $$=$3;
+ }
+ | table_ref NATURAL inner_join table_factor
+ {
+ MYSQL_YYABORT_UNLESS($1 && ($$=$4));
+ $4->straight=$3;
+ add_join_natural($1,$4,NULL,Select);
+ }
+
+ /* LEFT JOIN variants */
+ | table_ref LEFT opt_outer JOIN_SYM table_ref
+ ON
+ {
+ MYSQL_YYABORT_UNLESS($1 && $5);
+ /* Change the current name resolution context to a local context. */
+ if (push_new_name_resolution_context(thd, $1, $5))
+ MYSQL_YYABORT;
+ Select->parsing_place= IN_ON;
+ }
+ expr
+ {
+ add_join_on($5,$8);
+ Lex->pop_context();
+ $5->outer_join|=JOIN_TYPE_LEFT;
+ $$=$5;
+ Select->parsing_place= NO_MATTER;
+ }
+ | table_ref LEFT opt_outer JOIN_SYM table_factor
+ {
+ MYSQL_YYABORT_UNLESS($1 && $5);
+ }
+ USING '(' using_list ')'
+ {
+ add_join_natural($1,$5,$9,Select);
+ $5->outer_join|=JOIN_TYPE_LEFT;
+ $$=$5;
+ }
+ | table_ref NATURAL LEFT opt_outer JOIN_SYM table_factor
+ {
+ MYSQL_YYABORT_UNLESS($1 && $6);
+ add_join_natural($1,$6,NULL,Select);
+ $6->outer_join|=JOIN_TYPE_LEFT;
+ $$=$6;
+ }
+
+ /* RIGHT JOIN variants */
+ | table_ref RIGHT opt_outer JOIN_SYM table_ref
+ ON
+ {
+ MYSQL_YYABORT_UNLESS($1 && $5);
+ /* Change the current name resolution context to a local context. */
+ if (push_new_name_resolution_context(thd, $1, $5))
+ MYSQL_YYABORT;
+ Select->parsing_place= IN_ON;
+ }
+ expr
+ {
+ LEX *lex= Lex;
+ if (!($$= lex->current_select->convert_right_join()))
+ MYSQL_YYABORT;
+ add_join_on($$, $8);
+ Lex->pop_context();
+ Select->parsing_place= NO_MATTER;
+ }
+ | table_ref RIGHT opt_outer JOIN_SYM table_factor
+ {
+ MYSQL_YYABORT_UNLESS($1 && $5);
+ }
+ USING '(' using_list ')'
+ {
+ LEX *lex= Lex;
+ if (!($$= lex->current_select->convert_right_join()))
+ MYSQL_YYABORT;
+ add_join_natural($$,$5,$9,Select);
+ }
+ | table_ref NATURAL RIGHT opt_outer JOIN_SYM table_factor
+ {
+ MYSQL_YYABORT_UNLESS($1 && $6);
+ add_join_natural($6,$1,NULL,Select);
+ LEX *lex= Lex;
+ if (!($$= lex->current_select->convert_right_join()))
+ MYSQL_YYABORT;
+ }
+ ;
+
+
+inner_join: /* $$ set if using STRAIGHT_JOIN, false otherwise */
+ JOIN_SYM { $$ = 0; }
+ | INNER_SYM JOIN_SYM { $$ = 0; }
+ | STRAIGHT_JOIN { $$ = 1; }
+ ;
+
+normal_join:
+ inner_join { $$ = $1; }
+ | CROSS JOIN_SYM { $$ = 0; }
+ ;
+
+/*
+ table PARTITION (list of partitions), reusing using_list instead of creating
+ a new rule for partition_list.
+*/
+opt_use_partition:
+ /* empty */ { $$= 0;}
+ | use_partition
+ ;
+
+use_partition:
+ PARTITION_SYM '(' using_list ')' have_partitioning
+ {
+ $$= $3;
+ }
+ ;
+
+/*
+ This is a flattening of the rules <table factor> and <table primary>
+ in the SQL:2003 standard, since we don't have <sample clause>
+
+ I.e.
+ <table factor> ::= <table primary> [ <sample clause> ]
+*/
+/* Warning - may return NULL in case of incomplete SELECT */
+table_factor:
+ {
+ SELECT_LEX *sel= Select;
+ sel->table_join_options= 0;
+ }
+ table_ident opt_use_partition opt_table_alias opt_key_definition
+ {
+ if (!($$= Select->add_table_to_list(thd, $2, $4,
+ Select->get_table_join_options(),
+ YYPS->m_lock_type,
+ YYPS->m_mdl_type,
+ Select->pop_index_hints(),
+ $3)))
+ MYSQL_YYABORT;
+ Select->add_joined_table($$);
+ }
+ | select_derived_init get_select_lex select_derived2
+ {
+ LEX *lex= Lex;
+ SELECT_LEX *sel= lex->current_select;
+ if ($1)
+ {
+ if (sel->set_braces(1))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ /* select in braces, can't contain global parameters */
+ if (sel->master_unit()->fake_select_lex)
+ sel->master_unit()->global_parameters=
+ sel->master_unit()->fake_select_lex;
+ }
+ if ($2->init_nested_join(lex->thd))
+ MYSQL_YYABORT;
+ $$= 0;
+ /* incomplete derived tables return NULL, we must be
+ nested in select_derived rule to be here. */
+ }
+ /*
+ Represents a flattening of the following rules from the SQL:2003
+ standard. This sub-rule corresponds to the sub-rule
+ <table primary> ::= ... | <derived table> [ AS ] <correlation name>
+
+ The following rules have been flattened into query_expression_body
+ (since we have no <with clause>).
+
+ <derived table> ::= <table subquery>
+ <table subquery> ::= <subquery>
+ <subquery> ::= <left paren> <query expression> <right paren>
+ <query expression> ::= [ <with clause> ] <query expression body>
+
+ For the time being we use the non-standard rule
+ select_derived_union which is a compromise between the standard
+ and our parser. Possibly this rule could be replaced by our
+ query_expression_body.
+ */
+ | '(' get_select_lex select_derived_union ')' opt_table_alias
+ {
+ /* Use $2 instead of Lex->current_select as derived table will
+ alter value of Lex->current_select. */
+ if (!($3 || $5) && $2->embedding &&
+ !$2->embedding->nested_join->join_list.elements)
+ {
+ /* we have a derived table ($3 == NULL) but no alias,
+ Since we are nested in further parentheses so we
+ can pass NULL to the outer level parentheses
+ Permits parsing of "((((select ...))) as xyz)" */
+ $$= 0;
+ }
+ else if (!$3)
+ {
+ /* Handle case of derived table, alias may be NULL if there
+ are no outer parentheses, add_table_to_list() will throw
+ error in this case */
+ LEX *lex=Lex;
+ SELECT_LEX *sel= lex->current_select;
+ SELECT_LEX_UNIT *unit= sel->master_unit();
+ lex->current_select= sel= unit->outer_select();
+ Table_ident *ti= new Table_ident(unit);
+ if (ti == NULL)
+ MYSQL_YYABORT;
+ if (!($$= sel->add_table_to_list(lex->thd,
+ ti, $5, 0,
+ TL_READ, MDL_SHARED_READ)))
+
+ MYSQL_YYABORT;
+ sel->add_joined_table($$);
+ lex->pop_context();
+ lex->nest_level--;
+ }
+ /*else if (($3->select_lex &&
+ $3->select_lex->master_unit()->is_union() &&
+ ($3->select_lex->master_unit()->first_select() ==
+ $3->select_lex || !$3->lifted)) || $5)*/
+ else if ($5 != NULL)
+ {
+ /*
+ Tables with or without joins within parentheses cannot
+ have aliases, and we ruled out derived tables above.
+ */
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ /* nested join: FROM (t1 JOIN t2 ...),
+ nest_level is the same as in the outer query */
+ $$= $3;
+ }
+ }
+ ;
+
+/*
+ This rule accepts just about anything. The reason is that we have
+ empty-producing rules in the beginning of rules, in this case
+ subselect_start. This forces bison to take a decision which rules to
+ reduce by long before it has seen any tokens. This approach ties us
+ to a very limited class of parseable languages, and unfortunately
+ SQL is not one of them. The chosen 'solution' was this rule, which
+ produces just about anything, even complete bogus statements, for
+ instance ( table UNION SELECT 1 ).
+ Fortunately, we know that the semantic value returned by
+ select_derived is NULL if it contained a derived table, and a pointer to
+ the base table's TABLE_LIST if it was a base table. So in the rule
+ regarding union's, we throw a parse error manually and pretend it
+ was bison that did it.
+
+ Also worth noting is that this rule concerns query expressions in
+ the from clause only. Top level select statements and other types of
+ subqueries have their own union rules.
+*/
+select_derived_union:
+ select_derived opt_union_order_or_limit
+ {
+ if ($1 && $2)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ | select_derived_union
+ UNION_SYM
+ union_option
+ {
+ if (add_select_to_union_list(Lex, (bool)$3, FALSE))
+ MYSQL_YYABORT;
+ }
+ query_specification
+ {
+ /*
+ Remove from the name resolution context stack the context of the
+ last select in the union.
+ */
+ Lex->pop_context();
+ }
+ opt_union_order_or_limit
+ {
+ if ($1 != NULL)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+/* The equivalent of select_init2 for nested queries. */
+select_init2_derived:
+ select_part2_derived
+ {
+ LEX *lex= Lex;
+ SELECT_LEX * sel= lex->current_select;
+ if (lex->current_select->set_braces(0))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ if (sel->linkage == UNION_TYPE &&
+ sel->master_unit()->first_select()->braces)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+/* The equivalent of select_part2 for nested queries. */
+select_part2_derived:
+ {
+ LEX *lex= Lex;
+ SELECT_LEX *sel= lex->current_select;
+ if (sel->linkage != UNION_TYPE)
+ mysql_init_select(lex);
+ lex->current_select->parsing_place= SELECT_LIST;
+ }
+ opt_query_expression_options select_item_list
+ {
+ Select->parsing_place= NO_MATTER;
+ }
+ opt_select_from select_lock_type
+ ;
+
+/* handle contents of parentheses in join expression */
+select_derived:
+ get_select_lex
+ {
+ LEX *lex= Lex;
+ if ($1->init_nested_join(lex->thd))
+ MYSQL_YYABORT;
+ }
+ derived_table_list
+ {
+ LEX *lex= Lex;
+ /* for normal joins, $3 != NULL and end_nested_join() != NULL,
+ for derived tables, both must equal NULL */
+
+ if (!($$= $1->end_nested_join(lex->thd)) && $3)
+ MYSQL_YYABORT;
+ if (!$3 && $$)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+select_derived2:
+ {
+ LEX *lex= Lex;
+ lex->derived_tables|= DERIVED_SUBQUERY;
+ if (!lex->expr_allows_subselect ||
+ lex->sql_command == (int)SQLCOM_PURGE)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE ||
+ mysql_new_select(lex, 1))
+ MYSQL_YYABORT;
+ mysql_init_select(lex);
+ lex->current_select->linkage= DERIVED_TABLE_TYPE;
+ lex->current_select->parsing_place= SELECT_LIST;
+ }
+ select_options select_item_list
+ {
+ Select->parsing_place= NO_MATTER;
+ }
+ opt_select_from
+ ;
+
+get_select_lex:
+ /* Empty */ { $$= Select; }
+ ;
+
+select_derived_init:
+ SELECT_SYM
+ {
+ LEX *lex= Lex;
+
+ if (! lex->parsing_options.allows_derived)
+ {
+ my_error(ER_VIEW_SELECT_DERIVED, MYF(0));
+ MYSQL_YYABORT;
+ }
+
+ SELECT_LEX *sel= lex->current_select;
+ TABLE_LIST *embedding;
+ if (!sel->embedding || sel->end_nested_join(lex->thd))
+ {
+ /* we are not in parentheses */
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ embedding= Select->embedding;
+ $$= embedding &&
+ !embedding->nested_join->join_list.elements;
+ /* return true if we are deeply nested */
+ }
+ ;
+
+opt_outer:
+ /* empty */ {}
+ | OUTER {}
+ ;
+
+index_hint_clause:
+ /* empty */
+ {
+ $$= thd->variables.old_mode ? INDEX_HINT_MASK_JOIN : INDEX_HINT_MASK_ALL;
+ }
+ | FOR_SYM JOIN_SYM { $$= INDEX_HINT_MASK_JOIN; }
+ | FOR_SYM ORDER_SYM BY { $$= INDEX_HINT_MASK_ORDER; }
+ | FOR_SYM GROUP_SYM BY { $$= INDEX_HINT_MASK_GROUP; }
+ ;
+
+index_hint_type:
+ FORCE_SYM { $$= INDEX_HINT_FORCE; }
+ | IGNORE_SYM { $$= INDEX_HINT_IGNORE; }
+ ;
+
+index_hint_definition:
+ index_hint_type key_or_index index_hint_clause
+ {
+ Select->set_index_hint_type($1, $3);
+ }
+ '(' key_usage_list ')'
+ | USE_SYM key_or_index index_hint_clause
+ {
+ Select->set_index_hint_type(INDEX_HINT_USE, $3);
+ }
+ '(' opt_key_usage_list ')'
+ ;
+
+index_hints_list:
+ index_hint_definition
+ | index_hints_list index_hint_definition
+ ;
+
+opt_index_hints_list:
+ /* empty */
+ | { Select->alloc_index_hints(thd); } index_hints_list
+ ;
+
+opt_key_definition:
+ { Select->clear_index_hints(); }
+ opt_index_hints_list
+ ;
+
+opt_key_usage_list:
+ /* empty */ { Select->add_index_hint(thd, NULL, 0); }
+ | key_usage_list {}
+ ;
+
+key_usage_element:
+ ident
+ { Select->add_index_hint(thd, $1.str, $1.length); }
+ | PRIMARY_SYM
+ { Select->add_index_hint(thd, (char *)"PRIMARY", 7); }
+ ;
+
+key_usage_list:
+ key_usage_element
+ | key_usage_list ',' key_usage_element
+ ;
+
+using_list:
+ ident
+ {
+ if (!($$= new List<String>))
+ MYSQL_YYABORT;
+ String *s= new (thd->mem_root) String((const char *) $1.str,
+ $1.length,
+ system_charset_info);
+ if (s == NULL)
+ MYSQL_YYABORT;
+ $$->push_back(s);
+ }
+ | using_list ',' ident
+ {
+ String *s= new (thd->mem_root) String((const char *) $3.str,
+ $3.length,
+ system_charset_info);
+ if (s == NULL)
+ MYSQL_YYABORT;
+ $1->push_back(s);
+ $$= $1;
+ }
+ ;
+
+interval:
+ interval_time_stamp {}
+ | DAY_HOUR_SYM { $$=INTERVAL_DAY_HOUR; }
+ | DAY_MICROSECOND_SYM { $$=INTERVAL_DAY_MICROSECOND; }
+ | DAY_MINUTE_SYM { $$=INTERVAL_DAY_MINUTE; }
+ | DAY_SECOND_SYM { $$=INTERVAL_DAY_SECOND; }
+ | HOUR_MICROSECOND_SYM { $$=INTERVAL_HOUR_MICROSECOND; }
+ | HOUR_MINUTE_SYM { $$=INTERVAL_HOUR_MINUTE; }
+ | HOUR_SECOND_SYM { $$=INTERVAL_HOUR_SECOND; }
+ | MINUTE_MICROSECOND_SYM { $$=INTERVAL_MINUTE_MICROSECOND; }
+ | MINUTE_SECOND_SYM { $$=INTERVAL_MINUTE_SECOND; }
+ | SECOND_MICROSECOND_SYM { $$=INTERVAL_SECOND_MICROSECOND; }
+ | YEAR_MONTH_SYM { $$=INTERVAL_YEAR_MONTH; }
+ ;
+
+interval_time_stamp:
+ DAY_SYM { $$=INTERVAL_DAY; }
+ | WEEK_SYM { $$=INTERVAL_WEEK; }
+ | HOUR_SYM { $$=INTERVAL_HOUR; }
+ | MINUTE_SYM { $$=INTERVAL_MINUTE; }
+ | MONTH_SYM { $$=INTERVAL_MONTH; }
+ | QUARTER_SYM { $$=INTERVAL_QUARTER; }
+ | SECOND_SYM { $$=INTERVAL_SECOND; }
+ | MICROSECOND_SYM { $$=INTERVAL_MICROSECOND; }
+ | YEAR_SYM { $$=INTERVAL_YEAR; }
+ ;
+
+date_time_type:
+ DATE_SYM {$$=MYSQL_TIMESTAMP_DATE;}
+ | TIME_SYM {$$=MYSQL_TIMESTAMP_TIME;}
+ | DATETIME {$$=MYSQL_TIMESTAMP_DATETIME;}
+ | TIMESTAMP {$$=MYSQL_TIMESTAMP_DATETIME;}
+ ;
+
+table_alias:
+ /* empty */
+ | AS
+ | EQ
+ ;
+
+opt_table_alias:
+ /* empty */ { $$=0; }
+ | table_alias ident
+ {
+ $$= (LEX_STRING*) sql_memdup(&$2,sizeof(LEX_STRING));
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_all:
+ /* empty */
+ | ALL
+ ;
+
+where_clause:
+ /* empty */ { Select->where= 0; }
+ | WHERE
+ {
+ Select->parsing_place= IN_WHERE;
+ }
+ expr
+ {
+ SELECT_LEX *select= Select;
+ select->where= normalize_cond($3);
+ select->parsing_place= NO_MATTER;
+ if ($3)
+ $3->top_level_item();
+ }
+ ;
+
+having_clause:
+ /* empty */
+ | HAVING
+ {
+ Select->parsing_place= IN_HAVING;
+ }
+ expr
+ {
+ SELECT_LEX *sel= Select;
+ sel->having= normalize_cond($3);
+ sel->parsing_place= NO_MATTER;
+ if ($3)
+ $3->top_level_item();
+ }
+ ;
+
+opt_escape:
+ ESCAPE_SYM simple_expr
+ {
+ Lex->escape_used= TRUE;
+ $$= $2;
+ }
+ | /* empty */
+ {
+ Lex->escape_used= FALSE;
+ $$= ((thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) ?
+ new (thd->mem_root) Item_string_ascii("", 0) :
+ new (thd->mem_root) Item_string_ascii("\\", 1));
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+/*
+ group by statement in select
+*/
+
+group_clause:
+ /* empty */
+ | GROUP_SYM BY group_list olap_opt
+ ;
+
+group_list:
+ group_list ',' order_ident order_dir
+ { if (add_group_to_list(thd, $3,(bool) $4)) MYSQL_YYABORT; }
+ | order_ident order_dir
+ { if (add_group_to_list(thd, $1,(bool) $2)) MYSQL_YYABORT; }
+ ;
+
+olap_opt:
+ /* empty */ {}
+ | WITH_CUBE_SYM
+ {
+ /*
+ 'WITH CUBE' is reserved in the MySQL syntax, but not implemented,
+ and cause LALR(2) conflicts.
+ This syntax is not standard.
+ MySQL syntax: GROUP BY col1, col2, col3 WITH CUBE
+ SQL-2003: GROUP BY ... CUBE(col1, col2, col3)
+ */
+ LEX *lex=Lex;
+ if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "WITH CUBE",
+ "global union parameters");
+ MYSQL_YYABORT;
+ }
+ lex->current_select->olap= CUBE_TYPE;
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "CUBE");
+ MYSQL_YYABORT;
+ }
+ | WITH_ROLLUP_SYM
+ {
+ /*
+ 'WITH ROLLUP' is needed for backward compatibility,
+ and cause LALR(2) conflicts.
+ This syntax is not standard.
+ MySQL syntax: GROUP BY col1, col2, col3 WITH ROLLUP
+ SQL-2003: GROUP BY ... ROLLUP(col1, col2, col3)
+ */
+ LEX *lex= Lex;
+ if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "WITH ROLLUP",
+ "global union parameters");
+ MYSQL_YYABORT;
+ }
+ lex->current_select->olap= ROLLUP_TYPE;
+ }
+ ;
+
+/*
+ Order by statement in ALTER TABLE
+*/
+
+alter_order_clause:
+ ORDER_SYM BY alter_order_list
+ ;
+
+alter_order_list:
+ alter_order_list ',' alter_order_item
+ | alter_order_item
+ ;
+
+alter_order_item:
+ simple_ident_nospvar order_dir
+ {
+ bool ascending= ($2 == 1) ? true : false;
+ if (add_order_to_list(thd, $1, ascending))
+ MYSQL_YYABORT;
+ }
+ ;
+
+/*
+ Order by statement in select
+*/
+
+opt_order_clause:
+ /* empty */
+ | order_clause
+ ;
+
+order_clause:
+ ORDER_SYM BY
+ {
+ LEX *lex=Lex;
+ SELECT_LEX *sel= lex->current_select;
+ SELECT_LEX_UNIT *unit= sel-> master_unit();
+ if (sel->linkage != GLOBAL_OPTIONS_TYPE &&
+ sel->olap != UNSPECIFIED_OLAP_TYPE &&
+ (sel->linkage != UNION_TYPE || sel->braces))
+ {
+ my_error(ER_WRONG_USAGE, MYF(0),
+ "CUBE/ROLLUP", "ORDER BY");
+ MYSQL_YYABORT;
+ }
+ if (lex->sql_command != SQLCOM_ALTER_TABLE && !unit->fake_select_lex)
+ {
+ /*
+ A query of the of the form (SELECT ...) ORDER BY order_list is
+ executed in the same way as the query
+ SELECT ... ORDER BY order_list
+ unless the SELECT construct contains ORDER BY or LIMIT clauses.
+ Otherwise we create a fake SELECT_LEX if it has not been created
+ yet.
+ */
+ SELECT_LEX *first_sl= unit->first_select();
+ if (!unit->is_union() &&
+ (first_sl->order_list.elements ||
+ first_sl->select_limit) &&
+ unit->add_fake_select_lex(lex->thd))
+ MYSQL_YYABORT;
+ }
+ }
+ order_list
+ ;
+
+order_list:
+ order_list ',' order_ident order_dir
+ { if (add_order_to_list(thd, $3,(bool) $4)) MYSQL_YYABORT; }
+ | order_ident order_dir
+ { if (add_order_to_list(thd, $1,(bool) $2)) MYSQL_YYABORT; }
+ ;
+
+order_dir:
+ /* empty */ { $$ = 1; }
+ | ASC { $$ =1; }
+ | DESC { $$ =0; }
+ ;
+
+opt_limit_clause_init:
+ /* empty */
+ {
+ LEX *lex= Lex;
+ SELECT_LEX *sel= lex->current_select;
+ sel->offset_limit= 0;
+ sel->select_limit= 0;
+ lex->limit_rows_examined= 0;
+ }
+ | limit_clause {}
+ ;
+
+opt_limit_clause:
+ /* empty */ {}
+ | limit_clause {}
+ ;
+
+limit_clause:
+ LIMIT limit_options
+ {
+ SELECT_LEX *sel= Select;
+ if (!sel->select_limit->basic_const_item() ||
+ sel->select_limit->val_int() > 0)
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT);
+ }
+ | LIMIT limit_options ROWS_SYM EXAMINED_SYM limit_rows_option
+ {
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT);
+ }
+ | LIMIT ROWS_SYM EXAMINED_SYM limit_rows_option
+ {
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT);
+ }
+ ;
+
+limit_options:
+ limit_option
+ {
+ SELECT_LEX *sel= Select;
+ sel->select_limit= $1;
+ sel->offset_limit= 0;
+ sel->explicit_limit= 1;
+ }
+ | limit_option ',' limit_option
+ {
+ SELECT_LEX *sel= Select;
+ sel->select_limit= $3;
+ sel->offset_limit= $1;
+ sel->explicit_limit= 1;
+ }
+ | limit_option OFFSET_SYM limit_option
+ {
+ SELECT_LEX *sel= Select;
+ sel->select_limit= $1;
+ sel->offset_limit= $3;
+ sel->explicit_limit= 1;
+ }
+ ;
+
+limit_option:
+ ident
+ {
+ Item_splocal *splocal;
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= & thd->m_parser_state->m_lip;
+ sp_variable *spv;
+ sp_pcontext *spc = lex->spcont;
+ if (spc && (spv = spc->find_variable($1, false)))
+ {
+ splocal= new (thd->mem_root)
+ Item_splocal($1, spv->offset, spv->type,
+ lip->get_tok_start() - lex->sphead->m_tmp_query,
+ lip->get_ptr() - lip->get_tok_start());
+ if (splocal == NULL)
+ MYSQL_YYABORT;
+#ifndef DBUG_OFF
+ splocal->m_sp= lex->sphead;
+#endif
+ lex->safe_to_cache_query=0;
+ }
+ else
+ {
+ my_error(ER_SP_UNDECLARED_VAR, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ if (splocal->type() != Item::INT_ITEM)
+ {
+ my_error(ER_WRONG_SPVAR_TYPE_IN_LIMIT, MYF(0));
+ MYSQL_YYABORT;
+ }
+ splocal->limit_clause_param= TRUE;
+ $$= splocal;
+ }
+ | param_marker
+ {
+ ((Item_param *) $1)->limit_clause_param= TRUE;
+ }
+ | ULONGLONG_NUM
+ {
+ $$= new (thd->mem_root) Item_uint($1.str, $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | LONG_NUM
+ {
+ $$= new (thd->mem_root) Item_uint($1.str, $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | NUM
+ {
+ $$= new (thd->mem_root) Item_uint($1.str, $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+limit_rows_option:
+ limit_option
+ {
+ LEX *lex=Lex;
+ lex->limit_rows_examined= $1;
+ }
+
+delete_limit_clause:
+ /* empty */
+ {
+ LEX *lex=Lex;
+ lex->current_select->select_limit= 0;
+ }
+ | LIMIT limit_option
+ {
+ SELECT_LEX *sel= Select;
+ sel->select_limit= $2;
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT);
+ sel->explicit_limit= 1;
+ }
+ | LIMIT ROWS_SYM EXAMINED_SYM { my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; }
+ | LIMIT limit_option ROWS_SYM EXAMINED_SYM { my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; }
+ ;
+
+int_num:
+ NUM { int error; $$= (int) my_strtoll10($1.str, (char**) 0, &error); }
+ | '-' NUM { int error; $$= -(int) my_strtoll10($2.str, (char**) 0, &error); }
+ | '-' LONG_NUM { int error; $$= -(int) my_strtoll10($2.str, (char**) 0, &error); }
+ ;
+
+ulong_num:
+ NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ | HEX_NUM { $$= (ulong) strtol($1.str, (char**) 0, 16); }
+ | LONG_NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ | ULONGLONG_NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ | DECIMAL_NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ | FLOAT_NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ ;
+
+real_ulong_num:
+ NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ | HEX_NUM { $$= (ulong) strtol($1.str, (char**) 0, 16); }
+ | LONG_NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ | ULONGLONG_NUM { int error; $$= (ulong) my_strtoll10($1.str, (char**) 0, &error); }
+ | dec_num_error { MYSQL_YYABORT; }
+ ;
+
+ulonglong_num:
+ NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ | ULONGLONG_NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ | LONG_NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ | DECIMAL_NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ | FLOAT_NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ ;
+
+real_ulonglong_num:
+ NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ | ULONGLONG_NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ | HEX_NUM { $$= strtoull($1.str, (char**) 0, 16); }
+ | LONG_NUM { int error; $$= (ulonglong) my_strtoll10($1.str, (char**) 0, &error); }
+ | dec_num_error { MYSQL_YYABORT; }
+ ;
+
+dec_num_error:
+ dec_num
+ { my_parse_error(ER(ER_ONLY_INTEGERS_ALLOWED)); }
+ ;
+
+dec_num:
+ DECIMAL_NUM
+ | FLOAT_NUM
+ ;
+
+choice:
+ ulong_num { $$= $1 != 0 ? HA_CHOICE_YES : HA_CHOICE_NO; }
+ | DEFAULT { $$= HA_CHOICE_UNDEF; }
+ ;
+
+procedure_clause:
+ /* empty */
+ | PROCEDURE_SYM ident /* Procedure name */
+ {
+ LEX *lex=Lex;
+
+ if (! lex->parsing_options.allows_select_procedure)
+ {
+ my_error(ER_VIEW_SELECT_CLAUSE, MYF(0), "PROCEDURE");
+ MYSQL_YYABORT;
+ }
+
+ if (&lex->select_lex != lex->current_select)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "PROCEDURE", "subquery");
+ MYSQL_YYABORT;
+ }
+ lex->proc_list.elements=0;
+ lex->proc_list.first=0;
+ lex->proc_list.next= &lex->proc_list.first;
+ Item_field *item= new (thd->mem_root)
+ Item_field(&lex->current_select->context,
+ NULL, NULL, $2.str);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ if (add_proc_to_list(lex->thd, item))
+ MYSQL_YYABORT;
+ Lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ }
+ '(' procedure_list ')'
+ ;
+
+procedure_list:
+ /* empty */ {}
+ | procedure_list2 {}
+ ;
+
+procedure_list2:
+ procedure_list2 ',' procedure_item
+ | procedure_item
+ ;
+
+procedure_item:
+ remember_name expr remember_end
+ {
+ if (add_proc_to_list(thd, $2))
+ MYSQL_YYABORT;
+ if (!$2->name)
+ $2->set_name($1, (uint) ($3 - $1), thd->charset());
+ }
+ ;
+
+select_var_list_init:
+ {
+ LEX *lex=Lex;
+ if (!lex->describe && (!(lex->result= new select_dumpvar())))
+ MYSQL_YYABORT;
+ }
+ select_var_list
+ {}
+ ;
+
+select_var_list:
+ select_var_list ',' select_var_ident
+ | select_var_ident {}
+ ;
+
+select_var_ident:
+ '@' ident_or_text
+ {
+ LEX *lex=Lex;
+ if (lex->result)
+ {
+ my_var *var= new my_var($2,0,0,(enum_field_types)0);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ ((select_dumpvar *)lex->result)->var_list.push_back(var);
+ }
+ else
+ {
+ /*
+ The parser won't create select_result instance only
+ if it's an EXPLAIN.
+ */
+ DBUG_ASSERT(lex->describe);
+ }
+ }
+ | ident_or_text
+ {
+ LEX *lex=Lex;
+ sp_variable *t;
+
+ if (!lex->spcont || !(t=lex->spcont->find_variable($1, false)))
+ {
+ my_error(ER_SP_UNDECLARED_VAR, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ if (lex->result)
+ {
+ my_var *var= new my_var($1,1,t->offset,t->type);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ ((select_dumpvar *)lex->result)->var_list.push_back(var);
+#ifndef DBUG_OFF
+ var->sp= lex->sphead;
+#endif
+ }
+ else
+ {
+ /*
+ The parser won't create select_result instance only
+ if it's an EXPLAIN.
+ */
+ DBUG_ASSERT(lex->describe);
+ }
+ }
+ ;
+
+into:
+ INTO
+ {
+ if (! Lex->parsing_options.allows_select_into)
+ {
+ my_error(ER_VIEW_SELECT_CLAUSE, MYF(0), "INTO");
+ MYSQL_YYABORT;
+ }
+ }
+ into_destination
+ ;
+
+into_destination:
+ OUTFILE TEXT_STRING_filesystem
+ {
+ LEX *lex= Lex;
+ lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ if (!(lex->exchange= new sql_exchange($2.str, 0)) ||
+ !(lex->result= new select_export(lex->exchange)))
+ MYSQL_YYABORT;
+ }
+ opt_load_data_charset
+ { Lex->exchange->cs= $4; }
+ opt_field_term opt_line_term
+ | DUMPFILE TEXT_STRING_filesystem
+ {
+ LEX *lex=Lex;
+ if (!lex->describe)
+ {
+ lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ if (!(lex->exchange= new sql_exchange($2.str,1)))
+ MYSQL_YYABORT;
+ if (!(lex->result= new select_dump(lex->exchange)))
+ MYSQL_YYABORT;
+ }
+ }
+ | select_var_list_init
+ {
+ Lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+ }
+ ;
+
+/*
+ DO statement
+*/
+
+do:
+ DO_SYM
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_DO;
+ mysql_init_select(lex);
+ }
+ expr_list
+ {
+ Lex->insert_list= $3;
+ }
+ ;
+
+/*
+ Drop : delete tables or index or user
+*/
+
+drop:
+ DROP opt_temporary table_or_tables opt_if_exists
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_DROP_TABLE;
+ lex->drop_temporary= $2;
+ lex->check_exists= $4;
+ YYPS->m_lock_type= TL_UNLOCK;
+ YYPS->m_mdl_type= MDL_EXCLUSIVE;
+ }
+ table_list opt_restrict
+ {}
+ | DROP INDEX_SYM opt_if_exists ident ON table_ident {}
+ {
+ LEX *lex=Lex;
+ Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $4.str, $3);
+ if (ad == NULL)
+ MYSQL_YYABORT;
+ lex->sql_command= SQLCOM_DROP_INDEX;
+ lex->alter_info.reset();
+ lex->alter_info.flags= Alter_info::ALTER_DROP_INDEX;
+ lex->alter_info.drop_list.push_back(ad);
+ if (!lex->current_select->add_table_to_list(lex->thd, $6, NULL,
+ TL_OPTION_UPDATING,
+ TL_READ_NO_INSERT,
+ MDL_SHARED_UPGRADABLE))
+ MYSQL_YYABORT;
+ }
+ | DROP DATABASE opt_if_exists ident
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_DROP_DB;
+ lex->check_exists=$3;
+ lex->name= $4;
+ }
+ | DROP FUNCTION_SYM opt_if_exists ident '.' ident
+ {
+ LEX *lex= thd->lex;
+ sp_name *spname;
+ if ($4.str && check_db_name(&$4))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), $4.str);
+ MYSQL_YYABORT;
+ }
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command = SQLCOM_DROP_FUNCTION;
+ lex->check_exists= $3;
+ spname= new sp_name($4, $6, true);
+ if (spname == NULL)
+ MYSQL_YYABORT;
+ spname->init_qname(thd);
+ lex->spname= spname;
+ }
+ | DROP FUNCTION_SYM opt_if_exists ident
+ {
+ LEX *lex= thd->lex;
+ LEX_STRING db= {0, 0};
+ sp_name *spname;
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION");
+ MYSQL_YYABORT;
+ }
+ if (thd->db && lex->copy_db_to(&db.str, &db.length))
+ MYSQL_YYABORT;
+ lex->sql_command = SQLCOM_DROP_FUNCTION;
+ lex->check_exists= $3;
+ spname= new sp_name(db, $4, false);
+ if (spname == NULL)
+ MYSQL_YYABORT;
+ spname->init_qname(thd);
+ lex->spname= spname;
+ }
+ | DROP PROCEDURE_SYM opt_if_exists sp_name
+ {
+ LEX *lex=Lex;
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_DROP_SP, MYF(0), "PROCEDURE");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command = SQLCOM_DROP_PROCEDURE;
+ lex->check_exists= $3;
+ lex->spname= $4;
+ }
+ | DROP USER clear_privileges user_list
+ {
+ Lex->sql_command = SQLCOM_DROP_USER;
+ }
+ | DROP ROLE_SYM clear_privileges role_list
+ {
+ Lex->sql_command = SQLCOM_DROP_ROLE;
+ }
+ | DROP VIEW_SYM opt_if_exists
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_DROP_VIEW;
+ lex->check_exists= $3;
+ YYPS->m_lock_type= TL_UNLOCK;
+ YYPS->m_mdl_type= MDL_EXCLUSIVE;
+ }
+ table_list opt_restrict
+ {}
+ | DROP EVENT_SYM opt_if_exists sp_name
+ {
+ Lex->check_exists= $3;
+ Lex->spname= $4;
+ Lex->sql_command = SQLCOM_DROP_EVENT;
+ }
+ | DROP TRIGGER_SYM opt_if_exists sp_name
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_DROP_TRIGGER;
+ lex->check_exists= $3;
+ lex->spname= $4;
+ }
+ | DROP TABLESPACE tablespace_name opt_ts_engine opt_ts_wait
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_cmd_type= DROP_TABLESPACE;
+ }
+ | DROP LOGFILE_SYM GROUP_SYM logfile_group_name opt_ts_engine opt_ts_wait
+ {
+ LEX *lex= Lex;
+ lex->alter_tablespace_info->ts_cmd_type= DROP_LOGFILE_GROUP;
+ }
+ | DROP SERVER_SYM opt_if_exists ident_or_text
+ {
+ Lex->sql_command = SQLCOM_DROP_SERVER;
+ Lex->check_exists= $3;
+ Lex->server_options.server_name= $4.str;
+ Lex->server_options.server_name_length= $4.length;
+ }
+ ;
+
+table_list:
+ table_name
+ | table_list ',' table_name
+ ;
+
+table_name:
+ table_ident
+ {
+ if (!Select->add_table_to_list(thd, $1, NULL,
+ TL_OPTION_UPDATING,
+ YYPS->m_lock_type,
+ YYPS->m_mdl_type))
+ MYSQL_YYABORT;
+ }
+ ;
+
+table_name_with_opt_use_partition:
+ table_ident opt_use_partition
+ {
+ if (!Select->add_table_to_list(thd, $1, NULL,
+ TL_OPTION_UPDATING,
+ YYPS->m_lock_type,
+ YYPS->m_mdl_type,
+ NULL,
+ $2))
+ MYSQL_YYABORT;
+ }
+ ;
+
+table_alias_ref_list:
+ table_alias_ref
+ | table_alias_ref_list ',' table_alias_ref
+ ;
+
+table_alias_ref:
+ table_ident_opt_wild
+ {
+ if (!Select->add_table_to_list(thd, $1, NULL,
+ TL_OPTION_UPDATING | TL_OPTION_ALIAS,
+ YYPS->m_lock_type,
+ YYPS->m_mdl_type))
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_if_exists:
+ /* empty */
+ {
+ Lex->check_exists= FALSE;
+ $$= 0;
+ }
+ | IF EXISTS
+ {
+ Lex->check_exists= TRUE;
+ $$= 1;
+ }
+ ;
+
+opt_temporary:
+ /* empty */ { $$= 0; }
+ | TEMPORARY { $$= 1; }
+ ;
+/*
+** Insert : add new data to table
+*/
+
+insert:
+ INSERT
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_INSERT;
+ lex->duplicates= DUP_ERROR;
+ mysql_init_select(lex);
+ }
+ insert_lock_option
+ opt_ignore insert2
+ {
+ Select->set_lock_for_tables($3);
+ Lex->current_select= &Lex->select_lex;
+ }
+ insert_field_spec opt_insert_update
+ {}
+ ;
+
+replace:
+ REPLACE
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_REPLACE;
+ lex->duplicates= DUP_REPLACE;
+ mysql_init_select(lex);
+ }
+ replace_lock_option insert2
+ {
+ Select->set_lock_for_tables($3);
+ Lex->current_select= &Lex->select_lex;
+ }
+ insert_field_spec
+ {}
+ ;
+
+insert_lock_option:
+ /* empty */
+ {
+ /*
+ If it is SP we do not allow insert optimisation when result of
+ insert visible only after the table unlocking but everyone can
+ read table.
+ */
+ $$= (Lex->sphead ? TL_WRITE_DEFAULT : TL_WRITE_CONCURRENT_INSERT);
+ }
+ | LOW_PRIORITY { $$= TL_WRITE_LOW_PRIORITY; }
+ | DELAYED_SYM
+ {
+ Lex->keyword_delayed_begin_offset= (uint)(YYLIP->get_tok_start() -
+ thd->query());
+ Lex->keyword_delayed_end_offset= Lex->keyword_delayed_begin_offset +
+ YYLIP->yyLength() + 1;
+ $$= TL_WRITE_DELAYED;
+ }
+ | HIGH_PRIORITY { $$= TL_WRITE; }
+ ;
+
+replace_lock_option:
+ opt_low_priority { $$= $1; }
+ | DELAYED_SYM
+ {
+ Lex->keyword_delayed_begin_offset= (uint)(YYLIP->get_tok_start() -
+ thd->query());
+ Lex->keyword_delayed_end_offset= Lex->keyword_delayed_begin_offset +
+ YYLIP->yyLength() + 1;
+ $$= TL_WRITE_DELAYED;
+ }
+ ;
+
+insert2:
+ INTO insert_table {}
+ | insert_table {}
+ ;
+
+insert_table:
+ table_name_with_opt_use_partition
+ {
+ LEX *lex=Lex;
+ lex->field_list.empty();
+ lex->many_values.empty();
+ lex->insert_list=0;
+ };
+
+insert_field_spec:
+ insert_values {}
+ | '(' ')' insert_values {}
+ | '(' fields ')' insert_values {}
+ | SET
+ {
+ LEX *lex=Lex;
+ if (!(lex->insert_list = new List_item) ||
+ lex->many_values.push_back(lex->insert_list))
+ MYSQL_YYABORT;
+ }
+ ident_eq_list
+ ;
+
+fields:
+ fields ',' insert_ident { Lex->field_list.push_back($3); }
+ | insert_ident { Lex->field_list.push_back($1); }
+ ;
+
+insert_values:
+ VALUES values_list {}
+ | VALUE_SYM values_list {}
+ | create_select
+ { Select->set_braces(0);}
+ union_clause {}
+ | '(' create_select ')'
+ { Select->set_braces(1);}
+ union_opt {}
+ ;
+
+values_list:
+ values_list ',' no_braces
+ | no_braces
+ ;
+
+ident_eq_list:
+ ident_eq_list ',' ident_eq_value
+ | ident_eq_value
+ ;
+
+ident_eq_value:
+ simple_ident_nospvar equal expr_or_default
+ {
+ LEX *lex=Lex;
+ if (lex->field_list.push_back($1) ||
+ lex->insert_list->push_back($3))
+ MYSQL_YYABORT;
+ }
+ ;
+
+equal:
+ EQ {}
+ | SET_VAR {}
+ ;
+
+opt_equal:
+ /* empty */ {}
+ | equal {}
+ ;
+
+no_braces:
+ '('
+ {
+ if (!(Lex->insert_list = new List_item))
+ MYSQL_YYABORT;
+ }
+ opt_values ')'
+ {
+ LEX *lex=Lex;
+ if (lex->many_values.push_back(lex->insert_list))
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_values:
+ /* empty */ {}
+ | values
+ ;
+
+values:
+ values ',' expr_or_default
+ {
+ if (Lex->insert_list->push_back($3))
+ MYSQL_YYABORT;
+ }
+ | expr_or_default
+ {
+ if (Lex->insert_list->push_back($1))
+ MYSQL_YYABORT;
+ }
+ ;
+
+expr_or_default:
+ expr { $$= $1;}
+ | DEFAULT
+ {
+ $$= new (thd->mem_root) Item_default_value(Lex->current_context());
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_insert_update:
+ /* empty */
+ | ON DUPLICATE_SYM { Lex->duplicates= DUP_UPDATE; }
+ KEY_SYM UPDATE_SYM insert_update_list
+ ;
+
+/* Update rows in a table */
+
+update:
+ UPDATE_SYM
+ {
+ LEX *lex= Lex;
+ mysql_init_select(lex);
+ lex->sql_command= SQLCOM_UPDATE;
+ lex->duplicates= DUP_ERROR;
+ }
+ opt_low_priority opt_ignore join_table_list
+ SET update_list
+ {
+ LEX *lex= Lex;
+ if (lex->select_lex.table_list.elements > 1)
+ lex->sql_command= SQLCOM_UPDATE_MULTI;
+ else if (lex->select_lex.get_table_list()->derived)
+ {
+ /* it is single table update and it is update of derived table */
+ my_error(ER_NON_UPDATABLE_TABLE, MYF(0),
+ lex->select_lex.get_table_list()->alias, "UPDATE");
+ MYSQL_YYABORT;
+ }
+ /*
+ In case of multi-update setting write lock for all tables may
+ be too pessimistic. We will decrease lock level if possible in
+ mysql_multi_update().
+ */
+ Select->set_lock_for_tables($3);
+ }
+ where_clause opt_order_clause delete_limit_clause {}
+ ;
+
+update_list:
+ update_list ',' update_elem
+ | update_elem
+ ;
+
+update_elem:
+ simple_ident_nospvar equal expr_or_default
+ {
+ if (add_item_to_list(thd, $1) || add_value_to_list(thd, $3))
+ MYSQL_YYABORT;
+ }
+ ;
+
+insert_update_list:
+ insert_update_list ',' insert_update_elem
+ | insert_update_elem
+ ;
+
+insert_update_elem:
+ simple_ident_nospvar equal expr_or_default
+ {
+ LEX *lex= Lex;
+ if (lex->update_list.push_back($1) ||
+ lex->value_list.push_back($3))
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_low_priority:
+ /* empty */ { $$= TL_WRITE_DEFAULT; }
+ | LOW_PRIORITY { $$= TL_WRITE_LOW_PRIORITY; }
+ ;
+
+/* Delete rows from a table */
+
+delete:
+ DELETE_SYM
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_DELETE;
+ mysql_init_select(lex);
+ YYPS->m_lock_type= TL_WRITE_DEFAULT;
+ YYPS->m_mdl_type= MDL_SHARED_WRITE;
+
+ lex->ignore= 0;
+ lex->select_lex.init_order();
+ }
+ opt_delete_options single_multi
+ ;
+
+single_multi:
+ FROM table_ident opt_use_partition
+ {
+ if (!Select->add_table_to_list(thd, $2, NULL, TL_OPTION_UPDATING,
+ YYPS->m_lock_type,
+ YYPS->m_mdl_type,
+ NULL,
+ $3))
+ MYSQL_YYABORT;
+ YYPS->m_lock_type= TL_READ_DEFAULT;
+ YYPS->m_mdl_type= MDL_SHARED_READ;
+ }
+ where_clause opt_order_clause
+ delete_limit_clause {}
+ opt_select_expressions {}
+ | table_wild_list
+ {
+ mysql_init_multi_delete(Lex);
+ YYPS->m_lock_type= TL_READ_DEFAULT;
+ YYPS->m_mdl_type= MDL_SHARED_READ;
+ }
+ FROM join_table_list where_clause
+ {
+ if (multi_delete_set_locks_and_link_aux_tables(Lex))
+ MYSQL_YYABORT;
+ }
+ | FROM table_alias_ref_list
+ {
+ mysql_init_multi_delete(Lex);
+ YYPS->m_lock_type= TL_READ_DEFAULT;
+ YYPS->m_mdl_type= MDL_SHARED_READ;
+ }
+ USING join_table_list where_clause
+ {
+ if (multi_delete_set_locks_and_link_aux_tables(Lex))
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_select_expressions:
+ /* empty */
+ | RETURNING_SYM select_item_list
+ ;
+
+table_wild_list:
+ table_wild_one
+ | table_wild_list ',' table_wild_one
+ ;
+
+table_wild_one:
+ ident opt_wild
+ {
+ Table_ident *ti= new Table_ident($1);
+ if (ti == NULL)
+ MYSQL_YYABORT;
+ if (!Select->add_table_to_list(thd,
+ ti,
+ NULL,
+ TL_OPTION_UPDATING | TL_OPTION_ALIAS,
+ YYPS->m_lock_type,
+ YYPS->m_mdl_type))
+ MYSQL_YYABORT;
+ }
+ | ident '.' ident opt_wild
+ {
+ Table_ident *ti= new Table_ident(thd, $1, $3, 0);
+ if (ti == NULL)
+ MYSQL_YYABORT;
+ if (!Select->add_table_to_list(thd,
+ ti,
+ NULL,
+ TL_OPTION_UPDATING | TL_OPTION_ALIAS,
+ YYPS->m_lock_type,
+ YYPS->m_mdl_type))
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_wild:
+ /* empty */ {}
+ | '.' '*' {}
+ ;
+
+opt_delete_options:
+ /* empty */ {}
+ | opt_delete_option opt_delete_options {}
+ ;
+
+opt_delete_option:
+ QUICK { Select->options|= OPTION_QUICK; }
+ | LOW_PRIORITY { YYPS->m_lock_type= TL_WRITE_LOW_PRIORITY; }
+ | IGNORE_SYM { Lex->ignore= 1; }
+ ;
+
+truncate:
+ TRUNCATE_SYM opt_table_sym
+ {
+ LEX* lex= Lex;
+ lex->sql_command= SQLCOM_TRUNCATE;
+ lex->alter_info.reset();
+ lex->select_lex.options= 0;
+ lex->select_lex.sql_cache= SELECT_LEX::SQL_CACHE_UNSPECIFIED;
+ lex->select_lex.init_order();
+ YYPS->m_lock_type= TL_WRITE;
+ YYPS->m_mdl_type= MDL_EXCLUSIVE;
+ }
+ table_name
+ {
+ LEX* lex= thd->lex;
+ DBUG_ASSERT(!lex->m_sql_cmd);
+ lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_truncate_table();
+ if (lex->m_sql_cmd == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_table_sym:
+ /* empty */
+ | TABLE_SYM
+ ;
+
+opt_profile_defs:
+ /* empty */
+ | profile_defs;
+
+profile_defs:
+ profile_def
+ | profile_defs ',' profile_def;
+
+profile_def:
+ CPU_SYM
+ {
+ Lex->profile_options|= PROFILE_CPU;
+ }
+ | MEMORY_SYM
+ {
+ Lex->profile_options|= PROFILE_MEMORY;
+ }
+ | BLOCK_SYM IO_SYM
+ {
+ Lex->profile_options|= PROFILE_BLOCK_IO;
+ }
+ | CONTEXT_SYM SWITCHES_SYM
+ {
+ Lex->profile_options|= PROFILE_CONTEXT;
+ }
+ | PAGE_SYM FAULTS_SYM
+ {
+ Lex->profile_options|= PROFILE_PAGE_FAULTS;
+ }
+ | IPC_SYM
+ {
+ Lex->profile_options|= PROFILE_IPC;
+ }
+ | SWAPS_SYM
+ {
+ Lex->profile_options|= PROFILE_SWAPS;
+ }
+ | SOURCE_SYM
+ {
+ Lex->profile_options|= PROFILE_SOURCE;
+ }
+ | ALL
+ {
+ Lex->profile_options|= PROFILE_ALL;
+ }
+ ;
+
+opt_profile_args:
+ /* empty */
+ {
+ Lex->profile_query_id= 0;
+ }
+ | FOR_SYM QUERY_SYM NUM
+ {
+ Lex->profile_query_id= atoi($3.str);
+ }
+ ;
+
+/* Show things */
+
+show:
+ SHOW
+ {
+ LEX *lex=Lex;
+ lex->wild=0;
+ lex->ident=null_lex_str;
+ mysql_init_select(lex);
+ lex->current_select->parsing_place= SELECT_LIST;
+ bzero((char*) &lex->create_info,sizeof(lex->create_info));
+ }
+ show_param
+ {
+ Select->parsing_place= NO_MATTER;
+ }
+ ;
+
+show_param:
+ DATABASES wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_DATABASES;
+ if (prepare_schema_table(thd, lex, 0, SCH_SCHEMATA))
+ MYSQL_YYABORT;
+ }
+ | opt_full TABLES opt_db wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_TABLES;
+ lex->select_lex.db= $3;
+ if (prepare_schema_table(thd, lex, 0, SCH_TABLE_NAMES))
+ MYSQL_YYABORT;
+ }
+ | opt_full TRIGGERS_SYM opt_db wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_TRIGGERS;
+ lex->select_lex.db= $3;
+ if (prepare_schema_table(thd, lex, 0, SCH_TRIGGERS))
+ MYSQL_YYABORT;
+ }
+ | EVENTS_SYM opt_db wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_EVENTS;
+ lex->select_lex.db= $2;
+ if (prepare_schema_table(thd, lex, 0, SCH_EVENTS))
+ MYSQL_YYABORT;
+ }
+ | TABLE_SYM STATUS_SYM opt_db wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_TABLE_STATUS;
+ lex->select_lex.db= $3;
+ if (prepare_schema_table(thd, lex, 0, SCH_TABLES))
+ MYSQL_YYABORT;
+ }
+ | OPEN_SYM TABLES opt_db wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_OPEN_TABLES;
+ lex->select_lex.db= $3;
+ if (prepare_schema_table(thd, lex, 0, SCH_OPEN_TABLES))
+ MYSQL_YYABORT;
+ }
+ | PLUGINS_SYM
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_PLUGINS;
+ if (prepare_schema_table(thd, lex, 0, SCH_PLUGINS))
+ MYSQL_YYABORT;
+ }
+ | PLUGINS_SYM SONAME_SYM TEXT_STRING_sys
+ {
+ Lex->ident= $3;
+ Lex->sql_command= SQLCOM_SHOW_PLUGINS;
+ if (prepare_schema_table(thd, Lex, 0, SCH_ALL_PLUGINS))
+ MYSQL_YYABORT;
+ }
+ | PLUGINS_SYM SONAME_SYM wild_and_where
+ {
+ Lex->sql_command= SQLCOM_SHOW_PLUGINS;
+ if (prepare_schema_table(thd, Lex, 0, SCH_ALL_PLUGINS))
+ MYSQL_YYABORT;
+ }
+ | ENGINE_SYM known_storage_engines show_engine_param
+ { Lex->create_info.db_type= $2; }
+ | ENGINE_SYM ALL show_engine_param
+ { Lex->create_info.db_type= NULL; }
+ | opt_full COLUMNS from_or_in table_ident opt_db wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_FIELDS;
+ if ($5)
+ $4->change_db($5);
+ if (prepare_schema_table(thd, lex, $4, SCH_COLUMNS))
+ MYSQL_YYABORT;
+ }
+ | master_or_binary LOGS_SYM
+ {
+ Lex->sql_command = SQLCOM_SHOW_BINLOGS;
+ }
+ | SLAVE HOSTS_SYM
+ {
+ Lex->sql_command = SQLCOM_SHOW_SLAVE_HOSTS;
+ }
+ | BINLOG_SYM EVENTS_SYM binlog_in binlog_from
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_BINLOG_EVENTS;
+ } opt_limit_clause_init
+ | RELAYLOG_SYM optional_connection_name EVENTS_SYM binlog_in binlog_from
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_RELAYLOG_EVENTS;
+ } opt_limit_clause_init
+ | keys_or_index from_or_in table_ident opt_db where_clause
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_KEYS;
+ if ($4)
+ $3->change_db($4);
+ if (prepare_schema_table(thd, lex, $3, SCH_STATISTICS))
+ MYSQL_YYABORT;
+ }
+ | opt_storage ENGINES_SYM
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_SHOW_STORAGE_ENGINES;
+ if (prepare_schema_table(thd, lex, 0, SCH_ENGINES))
+ MYSQL_YYABORT;
+ }
+ | AUTHORS_SYM
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_SHOW_AUTHORS;
+ }
+ | CONTRIBUTORS_SYM
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_SHOW_CONTRIBUTORS;
+ }
+ | PRIVILEGES
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_SHOW_PRIVILEGES;
+ }
+ | COUNT_SYM '(' '*' ')' WARNINGS
+ { (void) create_select_for_variable("warning_count"); }
+ | COUNT_SYM '(' '*' ')' ERRORS
+ { (void) create_select_for_variable("error_count"); }
+ | WARNINGS opt_limit_clause_init
+ { Lex->sql_command = SQLCOM_SHOW_WARNS;}
+ | ERRORS opt_limit_clause_init
+ { Lex->sql_command = SQLCOM_SHOW_ERRORS;}
+ | PROFILES_SYM
+ { Lex->sql_command = SQLCOM_SHOW_PROFILES; }
+ | PROFILE_SYM opt_profile_defs opt_profile_args opt_limit_clause_init
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_PROFILE;
+ if (prepare_schema_table(thd, lex, NULL, SCH_PROFILES) != 0)
+ YYABORT;
+ }
+ | opt_var_type STATUS_SYM wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_STATUS;
+ lex->option_type= $1;
+ if (prepare_schema_table(thd, lex, 0, SCH_STATUS))
+ MYSQL_YYABORT;
+ }
+ | opt_full PROCESSLIST_SYM
+ { Lex->sql_command= SQLCOM_SHOW_PROCESSLIST;}
+ | opt_var_type VARIABLES wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_VARIABLES;
+ lex->option_type= $1;
+ if (prepare_schema_table(thd, lex, 0, SCH_VARIABLES))
+ MYSQL_YYABORT;
+ }
+ | charset wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_CHARSETS;
+ if (prepare_schema_table(thd, lex, 0, SCH_CHARSETS))
+ MYSQL_YYABORT;
+ }
+ | COLLATION_SYM wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_COLLATIONS;
+ if (prepare_schema_table(thd, lex, 0, SCH_COLLATIONS))
+ MYSQL_YYABORT;
+ }
+ | GRANTS
+ {
+ Lex->sql_command= SQLCOM_SHOW_GRANTS;
+ if (!(Lex->grant_user= (LEX_USER*)thd->alloc(sizeof(LEX_USER))))
+ MYSQL_YYABORT;
+ Lex->grant_user->user= current_user_and_current_role;
+ }
+ | GRANTS FOR_SYM user_or_role
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_SHOW_GRANTS;
+ lex->grant_user=$3;
+ }
+ | CREATE DATABASE opt_if_not_exists ident
+ {
+ Lex->sql_command=SQLCOM_SHOW_CREATE_DB;
+ Lex->create_info.options=$3;
+ Lex->name= $4;
+ }
+ | CREATE TABLE_SYM table_ident
+ {
+ LEX *lex= Lex;
+ lex->sql_command = SQLCOM_SHOW_CREATE;
+ if (!lex->select_lex.add_table_to_list(thd, $3, NULL,0))
+ MYSQL_YYABORT;
+ lex->only_view= 0;
+ lex->create_info.storage_media= HA_SM_DEFAULT;
+ }
+ | CREATE VIEW_SYM table_ident
+ {
+ LEX *lex= Lex;
+ lex->sql_command = SQLCOM_SHOW_CREATE;
+ if (!lex->select_lex.add_table_to_list(thd, $3, NULL, 0))
+ MYSQL_YYABORT;
+ lex->only_view= 1;
+ }
+ | MASTER_SYM STATUS_SYM
+ {
+ Lex->sql_command = SQLCOM_SHOW_MASTER_STAT;
+ }
+ | ALL SLAVES STATUS_SYM
+ {
+ Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT;
+ Lex->verbose= 1;
+ }
+ | SLAVE STATUS_SYM
+ {
+ LEX *lex= thd->lex;
+ lex->mi.connection_name= thd->variables.default_master_connection;
+ lex->sql_command = SQLCOM_SHOW_SLAVE_STAT;
+ lex->verbose= 0;
+ }
+ | SLAVE connection_name STATUS_SYM
+ {
+ Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT;
+ Lex->verbose= 0;
+ }
+ | CLIENT_STATS_SYM
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_CLIENT_STATS;
+ if (prepare_schema_table(thd, lex, 0, SCH_CLIENT_STATS))
+ MYSQL_YYABORT;
+ }
+ | USER_STATS_SYM
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_USER_STATS;
+ if (prepare_schema_table(thd, lex, 0, SCH_USER_STATS))
+ MYSQL_YYABORT;
+ }
+ | TABLE_STATS_SYM
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_TABLE_STATS;
+ if (prepare_schema_table(thd, lex, 0, SCH_TABLE_STATS))
+ MYSQL_YYABORT;
+ }
+ | INDEX_STATS_SYM
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_INDEX_STATS;
+ if (prepare_schema_table(thd, lex, 0, SCH_INDEX_STATS))
+ MYSQL_YYABORT;
+ }
+ | CREATE PROCEDURE_SYM sp_name
+ {
+ LEX *lex= Lex;
+
+ lex->sql_command = SQLCOM_SHOW_CREATE_PROC;
+ lex->spname= $3;
+ }
+ | CREATE FUNCTION_SYM sp_name
+ {
+ LEX *lex= Lex;
+
+ lex->sql_command = SQLCOM_SHOW_CREATE_FUNC;
+ lex->spname= $3;
+ }
+ | CREATE TRIGGER_SYM sp_name
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_CREATE_TRIGGER;
+ lex->spname= $3;
+ }
+ | PROCEDURE_SYM STATUS_SYM wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_STATUS_PROC;
+ if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES))
+ MYSQL_YYABORT;
+ }
+ | FUNCTION_SYM STATUS_SYM wild_and_where
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_SHOW_STATUS_FUNC;
+ if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES))
+ MYSQL_YYABORT;
+ }
+ | PROCEDURE_SYM CODE_SYM sp_name
+ {
+ Lex->sql_command= SQLCOM_SHOW_PROC_CODE;
+ Lex->spname= $3;
+ }
+ | FUNCTION_SYM CODE_SYM sp_name
+ {
+ Lex->sql_command= SQLCOM_SHOW_FUNC_CODE;
+ Lex->spname= $3;
+ }
+ | CREATE EVENT_SYM sp_name
+ {
+ Lex->spname= $3;
+ Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT;
+ }
+ | describe_command FOR_SYM expr
+ {
+ Lex->sql_command= SQLCOM_SHOW_EXPLAIN;
+ if (prepare_schema_table(thd, Lex, 0, SCH_EXPLAIN))
+ MYSQL_YYABORT;
+ add_value_to_list(thd, $3);
+ }
+ ;
+
+show_engine_param:
+ STATUS_SYM
+ { Lex->sql_command= SQLCOM_SHOW_ENGINE_STATUS; }
+ | MUTEX_SYM
+ { Lex->sql_command= SQLCOM_SHOW_ENGINE_MUTEX; }
+ | LOGS_SYM
+ { Lex->sql_command= SQLCOM_SHOW_ENGINE_LOGS; }
+ ;
+
+master_or_binary:
+ MASTER_SYM
+ | BINARY
+ ;
+
+opt_storage:
+ /* empty */
+ | STORAGE_SYM
+ ;
+
+opt_db:
+ /* empty */ { $$= 0; }
+ | from_or_in ident { $$= $2.str; }
+ ;
+
+opt_full:
+ /* empty */ { Lex->verbose=0; }
+ | FULL { Lex->verbose=1; }
+ ;
+
+from_or_in:
+ FROM
+ | IN_SYM
+ ;
+
+binlog_in:
+ /* empty */ { Lex->mi.log_file_name = 0; }
+ | IN_SYM TEXT_STRING_sys { Lex->mi.log_file_name = $2.str; }
+ ;
+
+binlog_from:
+ /* empty */ { Lex->mi.pos = 4; /* skip magic number */ }
+ | FROM ulonglong_num { Lex->mi.pos = $2; }
+ ;
+
+wild_and_where:
+ /* empty */
+ | LIKE TEXT_STRING_sys
+ {
+ Lex->wild= new (thd->mem_root) String($2.str, $2.length,
+ system_charset_info);
+ if (Lex->wild == NULL)
+ MYSQL_YYABORT;
+ }
+ | WHERE expr
+ {
+ Select->where= normalize_cond($2);
+ if ($2)
+ $2->top_level_item();
+ }
+ ;
+
+/* A Oracle compatible synonym for show */
+describe:
+ describe_command table_ident
+ {
+ LEX *lex= Lex;
+ mysql_init_select(lex);
+ lex->current_select->parsing_place= SELECT_LIST;
+ lex->sql_command= SQLCOM_SHOW_FIELDS;
+ lex->select_lex.db= 0;
+ lex->verbose= 0;
+ if (prepare_schema_table(thd, lex, $2, SCH_COLUMNS))
+ MYSQL_YYABORT;
+ }
+ opt_describe_column
+ {
+ Select->parsing_place= NO_MATTER;
+ }
+ | describe_command opt_extended_describe
+ { Lex->describe|= DESCRIBE_NORMAL; }
+ explainable_command
+ {
+ LEX *lex=Lex;
+ lex->select_lex.options|= SELECT_DESCRIBE;
+ }
+ ;
+
+explainable_command:
+ select
+ | insert
+ | replace
+ | update
+ | delete
+ ;
+
+describe_command:
+ DESC
+ | DESCRIBE
+ ;
+
+opt_extended_describe:
+ /* empty */ {}
+ | EXTENDED_SYM { Lex->describe|= DESCRIBE_EXTENDED; }
+ | PARTITIONS_SYM { Lex->describe|= DESCRIBE_PARTITIONS; }
+ ;
+
+opt_describe_column:
+ /* empty */ {}
+ | text_string { Lex->wild= $1; }
+ | ident
+ {
+ Lex->wild= new (thd->mem_root) String((const char*) $1.str,
+ $1.length,
+ system_charset_info);
+ if (Lex->wild == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+
+/* flush things */
+
+flush:
+ FLUSH_SYM opt_no_write_to_binlog
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_FLUSH;
+ lex->type= 0;
+ lex->no_write_to_binlog= $2;
+ }
+ flush_options
+ {}
+ ;
+
+flush_options:
+ table_or_tables
+ {
+ Lex->type|= REFRESH_TABLES;
+ /*
+ Set type of metadata and table locks for
+ FLUSH TABLES table_list [WITH READ LOCK].
+ */
+ YYPS->m_lock_type= TL_READ_NO_INSERT;
+ YYPS->m_mdl_type= MDL_SHARED_HIGH_PRIO;
+ }
+ opt_table_list opt_flush_lock
+ | flush_options_list
+ ;
+
+opt_flush_lock:
+ /* empty */ {}
+ | flush_lock
+ {
+ TABLE_LIST *tables= Lex->query_tables;
+ for (; tables; tables= tables->next_global)
+ {
+ tables->mdl_request.set_type(MDL_SHARED_NO_WRITE);
+ tables->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */
+ tables->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */
+ }
+ }
+ ;
+
+flush_lock:
+ WITH READ_SYM LOCK_SYM optional_flush_tables_arguments
+ { Lex->type|= REFRESH_READ_LOCK | $4; }
+ | FOR_SYM
+ {
+ if (Lex->query_tables == NULL) // Table list can't be empty
+ {
+ my_parse_error(ER(ER_NO_TABLES_USED));
+ MYSQL_YYABORT;
+ }
+ Lex->type|= REFRESH_FOR_EXPORT;
+ } EXPORT_SYM {}
+ ;
+
+flush_options_list:
+ flush_options_list ',' flush_option
+ | flush_option
+ {}
+ ;
+
+flush_option:
+ ERROR_SYM LOGS_SYM
+ { Lex->type|= REFRESH_ERROR_LOG; }
+ | ENGINE_SYM LOGS_SYM
+ { Lex->type|= REFRESH_ENGINE_LOG; }
+ | GENERAL LOGS_SYM
+ { Lex->type|= REFRESH_GENERAL_LOG; }
+ | SLOW LOGS_SYM
+ { Lex->type|= REFRESH_SLOW_LOG; }
+ | BINARY LOGS_SYM
+ { Lex->type|= REFRESH_BINARY_LOG; }
+ | RELAY LOGS_SYM optional_connection_name
+ {
+ LEX *lex= Lex;
+ if (lex->type & REFRESH_RELAY_LOG)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "FLUSH", "RELAY LOGS");
+ MYSQL_YYABORT;
+ }
+ lex->type|= REFRESH_RELAY_LOG;
+ lex->relay_log_connection_name= lex->mi.connection_name;
+ }
+ | QUERY_SYM CACHE_SYM
+ { Lex->type|= REFRESH_QUERY_CACHE_FREE; }
+ | HOSTS_SYM
+ { Lex->type|= REFRESH_HOSTS; }
+ | PRIVILEGES
+ { Lex->type|= REFRESH_GRANT; }
+ | LOGS_SYM
+ {
+ Lex->type|= REFRESH_LOG;
+ Lex->relay_log_connection_name.str= (char*) "";
+ Lex->relay_log_connection_name.length= 0;
+ }
+ | STATUS_SYM
+ { Lex->type|= REFRESH_STATUS; }
+ | SLAVE optional_connection_name
+ {
+ LEX *lex= Lex;
+ if (lex->type & REFRESH_SLAVE)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "FLUSH","SLAVE");
+ MYSQL_YYABORT;
+ }
+ lex->type|= REFRESH_SLAVE;
+ lex->reset_slave_info.all= false;
+ }
+ | CLIENT_STATS_SYM
+ { Lex->type|= REFRESH_CLIENT_STATS; }
+ | USER_STATS_SYM
+ { Lex->type|= REFRESH_USER_STATS; }
+ | TABLE_STATS_SYM
+ { Lex->type|= REFRESH_TABLE_STATS; }
+ | INDEX_STATS_SYM
+ { Lex->type|= REFRESH_INDEX_STATS; }
+ | MASTER_SYM
+ { Lex->type|= REFRESH_MASTER; }
+ | DES_KEY_FILE
+ { Lex->type|= REFRESH_DES_KEY_FILE; }
+ | RESOURCES
+ { Lex->type|= REFRESH_USER_RESOURCES; }
+ ;
+
+opt_table_list:
+ /* empty */ {}
+ | table_list {}
+ ;
+
+optional_flush_tables_arguments:
+ /* empty */ {$$= 0;}
+ | AND_SYM DISABLE_SYM CHECKPOINT_SYM {$$= REFRESH_CHECKPOINT; }
+
+reset:
+ RESET_SYM
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_RESET; lex->type=0;
+ }
+ reset_options
+ {}
+ ;
+
+reset_options:
+ reset_options ',' reset_option
+ | reset_option
+ ;
+
+reset_option:
+ SLAVE { Lex->type|= REFRESH_SLAVE; }
+ optional_connection_name
+ slave_reset_options { }
+ | MASTER_SYM { Lex->type|= REFRESH_MASTER; }
+ | QUERY_SYM CACHE_SYM { Lex->type|= REFRESH_QUERY_CACHE;}
+ ;
+
+slave_reset_options:
+ /* empty */ { Lex->reset_slave_info.all= false; }
+ | ALL { Lex->reset_slave_info.all= true; }
+ ;
+
+purge:
+ PURGE
+ {
+ LEX *lex=Lex;
+ lex->type=0;
+ lex->sql_command = SQLCOM_PURGE;
+ }
+ purge_options
+ {}
+ ;
+
+purge_options:
+ master_or_binary LOGS_SYM purge_option
+ ;
+
+purge_option:
+ TO_SYM TEXT_STRING_sys
+ {
+ Lex->to_log = $2.str;
+ }
+ | BEFORE_SYM expr
+ {
+ LEX *lex= Lex;
+ lex->value_list.empty();
+ lex->value_list.push_front($2);
+ lex->sql_command= SQLCOM_PURGE_BEFORE;
+ }
+ ;
+
+/* kill threads */
+
+kill:
+ KILL_SYM
+ {
+ LEX *lex=Lex;
+ lex->value_list.empty();
+ lex->users_list.empty();
+ lex->sql_command= SQLCOM_KILL;
+ lex->kill_type= KILL_TYPE_ID;
+ }
+ kill_type kill_option kill_expr
+ {
+ Lex->kill_signal= (killed_state) ($3 | $4);
+ }
+ ;
+
+kill_type:
+ /* Empty */ { $$= (int) KILL_HARD_BIT; }
+ | HARD_SYM { $$= (int) KILL_HARD_BIT; }
+ | SOFT_SYM { $$= 0; }
+
+kill_option:
+ /* empty */ { $$= (int) KILL_CONNECTION; }
+ | CONNECTION_SYM { $$= (int) KILL_CONNECTION; }
+ | QUERY_SYM { $$= (int) KILL_QUERY; }
+ | QUERY_SYM ID_SYM
+ {
+ $$= (int) KILL_QUERY;
+ Lex->kill_type= KILL_TYPE_QUERY;
+ }
+ ;
+
+kill_expr:
+ expr
+ {
+ Lex->value_list.push_front($$);
+ }
+ | USER user
+ {
+ Lex->users_list.push_back($2);
+ Lex->kill_type= KILL_TYPE_USER;
+ }
+ ;
+
+
+shutdown:
+ SHUTDOWN { Lex->sql_command= SQLCOM_SHUTDOWN; }
+ ;
+
+/* change database */
+
+use:
+ USE_SYM ident
+ {
+ LEX *lex=Lex;
+ lex->sql_command=SQLCOM_CHANGE_DB;
+ lex->select_lex.db= $2.str;
+ }
+ ;
+
+/* import, export of files */
+
+load:
+ LOAD data_or_xml
+ {
+ LEX *lex= thd->lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0),
+ $2 == FILETYPE_CSV ? "LOAD DATA" : "LOAD XML");
+ MYSQL_YYABORT;
+ }
+ }
+ load_data_lock opt_local INFILE TEXT_STRING_filesystem
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_LOAD;
+ lex->local_file= $5;
+ lex->duplicates= DUP_ERROR;
+ lex->ignore= 0;
+ if (!(lex->exchange= new sql_exchange($7.str, 0, $2)))
+ MYSQL_YYABORT;
+ }
+ opt_duplicate INTO TABLE_SYM table_ident opt_use_partition
+ {
+ LEX *lex=Lex;
+ if (!Select->add_table_to_list(thd, $12, NULL, TL_OPTION_UPDATING,
+ $4, MDL_SHARED_WRITE, NULL, $13))
+ MYSQL_YYABORT;
+ lex->field_list.empty();
+ lex->update_list.empty();
+ lex->value_list.empty();
+ }
+ opt_load_data_charset
+ { Lex->exchange->cs= $15; }
+ opt_xml_rows_identified_by
+ opt_field_term opt_line_term opt_ignore_lines opt_field_or_var_spec
+ opt_load_data_set_spec
+ {}
+ ;
+
+data_or_xml:
+ DATA_SYM { $$= FILETYPE_CSV; }
+ | XML_SYM { $$= FILETYPE_XML; }
+ ;
+
+opt_local:
+ /* empty */ { $$=0;}
+ | LOCAL_SYM { $$=1;}
+ ;
+
+load_data_lock:
+ /* empty */ { $$= TL_WRITE_DEFAULT; }
+ | CONCURRENT
+ {
+ /*
+ Ignore this option in SP to avoid problem with query cache and
+ triggers with non default priority locks
+ */
+ $$= (Lex->sphead ? TL_WRITE_DEFAULT : TL_WRITE_CONCURRENT_INSERT);
+ }
+ | LOW_PRIORITY { $$= TL_WRITE_LOW_PRIORITY; }
+ ;
+
+opt_duplicate:
+ /* empty */ { Lex->duplicates=DUP_ERROR; }
+ | REPLACE { Lex->duplicates=DUP_REPLACE; }
+ | IGNORE_SYM { Lex->ignore= 1; }
+ ;
+
+opt_field_term:
+ /* empty */
+ | COLUMNS field_term_list
+ ;
+
+field_term_list:
+ field_term_list field_term
+ | field_term
+ ;
+
+field_term:
+ TERMINATED BY text_string
+ {
+ DBUG_ASSERT(Lex->exchange != 0);
+ Lex->exchange->field_term= $3;
+ }
+ | OPTIONALLY ENCLOSED BY text_string
+ {
+ LEX *lex= Lex;
+ DBUG_ASSERT(lex->exchange != 0);
+ lex->exchange->enclosed= $4;
+ lex->exchange->opt_enclosed= 1;
+ }
+ | ENCLOSED BY text_string
+ {
+ DBUG_ASSERT(Lex->exchange != 0);
+ Lex->exchange->enclosed= $3;
+ }
+ | ESCAPED BY text_string
+ {
+ DBUG_ASSERT(Lex->exchange != 0);
+ Lex->exchange->escaped= $3;
+ }
+ ;
+
+opt_line_term:
+ /* empty */
+ | LINES line_term_list
+ ;
+
+line_term_list:
+ line_term_list line_term
+ | line_term
+ ;
+
+line_term:
+ TERMINATED BY text_string
+ {
+ DBUG_ASSERT(Lex->exchange != 0);
+ Lex->exchange->line_term= $3;
+ }
+ | STARTING BY text_string
+ {
+ DBUG_ASSERT(Lex->exchange != 0);
+ Lex->exchange->line_start= $3;
+ }
+ ;
+
+opt_xml_rows_identified_by:
+ /* empty */ { }
+ | ROWS_SYM IDENTIFIED_SYM BY text_string
+ { Lex->exchange->line_term = $4; };
+
+opt_ignore_lines:
+ /* empty */
+ | IGNORE_SYM NUM lines_or_rows
+ {
+ DBUG_ASSERT(Lex->exchange != 0);
+ Lex->exchange->skip_lines= atol($2.str);
+ }
+ ;
+
+lines_or_rows:
+ LINES { }
+
+ | ROWS_SYM { }
+ ;
+
+opt_field_or_var_spec:
+ /* empty */ {}
+ | '(' fields_or_vars ')' {}
+ | '(' ')' {}
+ ;
+
+fields_or_vars:
+ fields_or_vars ',' field_or_var
+ { Lex->field_list.push_back($3); }
+ | field_or_var
+ { Lex->field_list.push_back($1); }
+ ;
+
+field_or_var:
+ simple_ident_nospvar {$$= $1;}
+ | '@' ident_or_text
+ {
+ $$= new (thd->mem_root) Item_user_var_as_out_param($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+opt_load_data_set_spec:
+ /* empty */ {}
+ | SET load_data_set_list {}
+ ;
+
+load_data_set_list:
+ load_data_set_list ',' load_data_set_elem
+ | load_data_set_elem
+ ;
+
+load_data_set_elem:
+ simple_ident_nospvar equal remember_name expr_or_default remember_end
+ {
+ LEX *lex= Lex;
+ if (lex->update_list.push_back($1) ||
+ lex->value_list.push_back($4))
+ MYSQL_YYABORT;
+ $4->set_name_no_truncate($3, (uint) ($5 - $3), thd->charset());
+ }
+ ;
+
+/* Common definitions */
+
+text_literal:
+ TEXT_STRING
+ {
+ LEX_STRING tmp;
+ CHARSET_INFO *cs_con= thd->variables.collation_connection;
+ CHARSET_INFO *cs_cli= thd->variables.character_set_client;
+ uint repertoire= thd->lex->text_string_is_7bit &&
+ my_charset_is_ascii_based(cs_cli) ?
+ MY_REPERTOIRE_ASCII : MY_REPERTOIRE_UNICODE30;
+ if (thd->charset_is_collation_connection ||
+ (repertoire == MY_REPERTOIRE_ASCII &&
+ my_charset_is_ascii_based(cs_con)))
+ tmp= $1;
+ else
+ {
+ if (thd->convert_string(&tmp, cs_con, $1.str, $1.length, cs_cli))
+ MYSQL_YYABORT;
+ }
+ $$= new (thd->mem_root) Item_string(tmp.str, tmp.length, cs_con,
+ DERIVATION_COERCIBLE,
+ repertoire);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | NCHAR_STRING
+ {
+ uint repertoire= Lex->text_string_is_7bit ?
+ MY_REPERTOIRE_ASCII : MY_REPERTOIRE_UNICODE30;
+ DBUG_ASSERT(my_charset_is_ascii_based(national_charset_info));
+ $$= new (thd->mem_root) Item_string($1.str, $1.length,
+ national_charset_info,
+ DERIVATION_COERCIBLE,
+ repertoire);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | UNDERSCORE_CHARSET TEXT_STRING
+ {
+ $$= new (thd->mem_root) Item_string_with_introducer($2.str,
+ $2.length, $1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | text_literal TEXT_STRING_literal
+ {
+ Item_string* item= (Item_string*) $1;
+ item->append($2.str, $2.length);
+ if (!(item->collation.repertoire & MY_REPERTOIRE_EXTENDED))
+ {
+ /*
+ If the string has been pure ASCII so far,
+ check the new part.
+ */
+ CHARSET_INFO *cs= thd->variables.collation_connection;
+ item->collation.repertoire|= my_string_repertoire(cs,
+ $2.str,
+ $2.length);
+ }
+ }
+ ;
+
+text_string:
+ TEXT_STRING_literal
+ {
+ $$= new (thd->mem_root) String($1.str,
+ $1.length,
+ thd->variables.collation_connection);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | hex_or_bin_String { $$= $1; }
+ ;
+
+
+hex_or_bin_String:
+ HEX_NUM
+ {
+ Item *tmp= new (thd->mem_root) Item_hex_hybrid($1.str, $1.length);
+ if (tmp == NULL)
+ MYSQL_YYABORT;
+ /*
+ it is OK only emulate fix_fields, because we need only
+ value of constant
+ */
+ tmp->quick_fix_field();
+ $$= tmp->val_str((String*) 0);
+ }
+ | HEX_STRING
+ {
+ Item *tmp= new (thd->mem_root) Item_hex_string($1.str, $1.length);
+ if (tmp == NULL)
+ MYSQL_YYABORT;
+ tmp->quick_fix_field();
+ $$= tmp->val_str((String*) 0);
+ }
+ | BIN_NUM
+ {
+ Item *tmp= new (thd->mem_root) Item_bin_string($1.str, $1.length);
+ if (tmp == NULL)
+ MYSQL_YYABORT;
+ /*
+ it is OK only emulate fix_fields, because we need only
+ value of constant
+ */
+ tmp->quick_fix_field();
+ $$= tmp->val_str((String*) 0);
+ }
+ ;
+
+param_marker:
+ PARAM_MARKER
+ {
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+ Item_param *item;
+ if (! lex->parsing_options.allows_variable)
+ {
+ my_error(ER_VIEW_SELECT_VARIABLE, MYF(0));
+ MYSQL_YYABORT;
+ }
+ item= new (thd->mem_root) Item_param((uint) (lip->get_tok_start() - thd->query()));
+ if (!($$= item) || lex->param_list.push_back(item))
+ {
+ my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+signed_literal:
+ literal { $$ = $1; }
+ | '+' NUM_literal { $$ = $2; }
+ | '-' NUM_literal
+ {
+ $2->max_length++;
+ $$= $2->neg();
+ }
+ ;
+
+literal:
+ text_literal { $$ = $1; }
+ | NUM_literal { $$ = $1; }
+ | temporal_literal { $$= $1; }
+ | NULL_SYM
+ {
+ /*
+ For the digest computation, in this context only,
+ NULL is considered a literal, hence reduced to '?'
+ REDUCE:
+ TOK_GENERIC_VALUE := NULL_SYM
+ */
+ YYLIP->reduce_digest_token(TOK_GENERIC_VALUE, NULL_SYM);
+ $$ = new (thd->mem_root) Item_null();
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ YYLIP->next_state= MY_LEX_OPERATOR_OR_IDENT;
+ }
+ | FALSE_SYM
+ {
+ $$= new (thd->mem_root) Item_int((char*) "FALSE",0,1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | TRUE_SYM
+ {
+ $$= new (thd->mem_root) Item_int((char*) "TRUE",1,1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | HEX_NUM
+ {
+ $$ = new (thd->mem_root) Item_hex_hybrid($1.str, $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | HEX_STRING
+ {
+ $$ = new (thd->mem_root) Item_hex_string($1.str, $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | BIN_NUM
+ {
+ $$= new (thd->mem_root) Item_bin_string($1.str, $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | UNDERSCORE_CHARSET hex_or_bin_String
+ {
+ Item_string_with_introducer *item_str;
+ /*
+ Pass NULL as name. Name will be set in the "select_item" rule and
+ will include the introducer and the original hex/bin notation.
+ */
+ item_str= new (thd->mem_root)
+ Item_string_with_introducer(NULL, $2->ptr(), $2->length(), $1);
+ if (!item_str || !item_str->check_well_formed_result(true))
+ MYSQL_YYABORT;
+
+ $$= item_str;
+ }
+ ;
+
+NUM_literal:
+ NUM
+ {
+ int error;
+ $$= new (thd->mem_root)
+ Item_int($1.str,
+ (longlong) my_strtoll10($1.str, NULL, &error),
+ $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | LONG_NUM
+ {
+ int error;
+ $$= new (thd->mem_root)
+ Item_int($1.str,
+ (longlong) my_strtoll10($1.str, NULL, &error),
+ $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ULONGLONG_NUM
+ {
+ $$= new (thd->mem_root) Item_uint($1.str, $1.length);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | DECIMAL_NUM
+ {
+ $$= new (thd->mem_root) Item_decimal($1.str, $1.length,
+ thd->charset());
+ if (($$ == NULL) || (thd->is_error()))
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ | FLOAT_NUM
+ {
+ $$= new (thd->mem_root) Item_float($1.str, $1.length);
+ if (($$ == NULL) || (thd->is_error()))
+ {
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+
+temporal_literal:
+ DATE_SYM TEXT_STRING
+ {
+ if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL,
+ MYSQL_TYPE_DATE, true)))
+ MYSQL_YYABORT;
+ }
+ | TIME_SYM TEXT_STRING
+ {
+ if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL,
+ MYSQL_TYPE_TIME, true)))
+ MYSQL_YYABORT;
+ }
+ | TIMESTAMP TEXT_STRING
+ {
+ if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL,
+ MYSQL_TYPE_DATETIME, true)))
+ MYSQL_YYABORT;
+ }
+ ;
+
+
+
+
+/**********************************************************************
+** Creating different items.
+**********************************************************************/
+
+insert_ident:
+ simple_ident_nospvar { $$=$1; }
+ | table_wild { $$=$1; }
+ ;
+
+table_wild:
+ ident '.' '*'
+ {
+ SELECT_LEX *sel= Select;
+ $$= new (thd->mem_root) Item_field(Lex->current_context(),
+ NullS, $1.str, "*");
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ sel->with_wild++;
+ }
+ | ident '.' ident '.' '*'
+ {
+ SELECT_LEX *sel= Select;
+ const char* schema= thd->client_capabilities & CLIENT_NO_SCHEMA ?
+ NullS : $1.str;
+ $$= new (thd->mem_root) Item_field(Lex->current_context(),
+ schema,
+ $3.str,"*");
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ sel->with_wild++;
+ }
+ ;
+
+order_ident:
+ expr { $$=$1; }
+ ;
+
+simple_ident:
+ ident
+ {
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+ sp_variable *spv;
+ sp_pcontext *spc = lex->spcont;
+ if (spc && (spv = spc->find_variable($1, false)))
+ {
+ /* We're compiling a stored procedure and found a variable */
+ if (! lex->parsing_options.allows_variable)
+ {
+ my_error(ER_VIEW_SELECT_VARIABLE, MYF(0));
+ MYSQL_YYABORT;
+ }
+
+ Item_splocal *splocal;
+ splocal= new (thd->mem_root)
+ Item_splocal($1, spv->offset, spv->type,
+ lip->get_tok_start_prev() - lex->sphead->m_tmp_query,
+ lip->get_tok_end() - lip->get_tok_start_prev());
+ if (splocal == NULL)
+ MYSQL_YYABORT;
+#ifndef DBUG_OFF
+ splocal->m_sp= lex->sphead;
+#endif
+ $$= splocal;
+ lex->safe_to_cache_query=0;
+ }
+ else
+ {
+ SELECT_LEX *sel=Select;
+ if ((sel->parsing_place != IN_HAVING) ||
+ (sel->get_in_sum_expr() > 0))
+ {
+ $$= new (thd->mem_root) Item_field(Lex->current_context(),
+ NullS, NullS, $1.str);
+ }
+ else
+ {
+ $$= new (thd->mem_root) Item_ref(Lex->current_context(),
+ NullS, NullS, $1.str);
+ }
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ }
+ | simple_ident_q { $$= $1; }
+ ;
+
+simple_ident_nospvar:
+ ident
+ {
+ SELECT_LEX *sel=Select;
+ if ((sel->parsing_place != IN_HAVING) ||
+ (sel->get_in_sum_expr() > 0))
+ {
+ $$= new (thd->mem_root) Item_field(Lex->current_context(),
+ NullS, NullS, $1.str);
+ }
+ else
+ {
+ $$= new (thd->mem_root) Item_ref(Lex->current_context(),
+ NullS, NullS, $1.str);
+ }
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | simple_ident_q { $$= $1; }
+ ;
+
+simple_ident_q:
+ ident '.' ident
+ {
+ LEX *lex= thd->lex;
+
+ /*
+ FIXME This will work ok in simple_ident_nospvar case because
+ we can't meet simple_ident_nospvar in trigger now. But it
+ should be changed in future.
+ */
+ if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
+ (!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
+ !my_strcasecmp(system_charset_info, $1.str, "OLD")))
+ {
+ Item_trigger_field *trg_fld;
+ bool new_row= ($1.str[0]=='N' || $1.str[0]=='n');
+
+ if (lex->trg_chistics.event == TRG_EVENT_INSERT &&
+ !new_row)
+ {
+ my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "OLD", "on INSERT");
+ MYSQL_YYABORT;
+ }
+
+ if (lex->trg_chistics.event == TRG_EVENT_DELETE &&
+ new_row)
+ {
+ my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE");
+ MYSQL_YYABORT;
+ }
+
+ DBUG_ASSERT(!new_row ||
+ (lex->trg_chistics.event == TRG_EVENT_INSERT ||
+ lex->trg_chistics.event == TRG_EVENT_UPDATE));
+ const bool read_only=
+ !(new_row && lex->trg_chistics.action_time == TRG_ACTION_BEFORE);
+ trg_fld= new (thd->mem_root)
+ Item_trigger_field(Lex->current_context(),
+ new_row ?
+ Item_trigger_field::NEW_ROW:
+ Item_trigger_field::OLD_ROW,
+ $3.str,
+ SELECT_ACL,
+ read_only);
+ if (trg_fld == NULL)
+ MYSQL_YYABORT;
+
+ /*
+ Let us add this item to list of all Item_trigger_field objects
+ in trigger.
+ */
+ lex->trg_table_fields.link_in_list(trg_fld,
+ &trg_fld->next_trg_field);
+
+ $$= trg_fld;
+ }
+ else
+ {
+ SELECT_LEX *sel= lex->current_select;
+ if (sel->no_table_names_allowed)
+ {
+ my_error(ER_TABLENAME_NOT_ALLOWED_HERE,
+ MYF(0), $1.str, thd->where);
+ }
+ if ((sel->parsing_place != IN_HAVING) ||
+ (sel->get_in_sum_expr() > 0))
+ {
+ $$= new (thd->mem_root) Item_field(Lex->current_context(),
+ NullS, $1.str, $3.str);
+ }
+ else
+ {
+ $$= new (thd->mem_root) Item_ref(Lex->current_context(),
+ NullS, $1.str, $3.str);
+ }
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ }
+ | '.' ident '.' ident
+ {
+ LEX *lex= thd->lex;
+ SELECT_LEX *sel= lex->current_select;
+ if (sel->no_table_names_allowed)
+ {
+ my_error(ER_TABLENAME_NOT_ALLOWED_HERE,
+ MYF(0), $2.str, thd->where);
+ }
+ if ((sel->parsing_place != IN_HAVING) ||
+ (sel->get_in_sum_expr() > 0))
+ {
+ $$= new (thd->mem_root) Item_field(Lex->current_context(),
+ NullS, $2.str, $4.str);
+
+ }
+ else
+ {
+ $$= new (thd->mem_root) Item_ref(Lex->current_context(),
+ NullS, $2.str, $4.str);
+ }
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ident '.' ident '.' ident
+ {
+ LEX *lex= thd->lex;
+ SELECT_LEX *sel= lex->current_select;
+ const char* schema= (thd->client_capabilities & CLIENT_NO_SCHEMA ?
+ NullS : $1.str);
+ if (sel->no_table_names_allowed)
+ {
+ my_error(ER_TABLENAME_NOT_ALLOWED_HERE,
+ MYF(0), $3.str, thd->where);
+ }
+ if ((sel->parsing_place != IN_HAVING) ||
+ (sel->get_in_sum_expr() > 0))
+ {
+ $$= new (thd->mem_root) Item_field(Lex->current_context(),
+ schema,
+ $3.str, $5.str);
+ }
+ else
+ {
+ $$= new (thd->mem_root) Item_ref(Lex->current_context(),
+ schema,
+ $3.str, $5.str);
+ }
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+field_ident:
+ ident { $$=$1;}
+ | ident '.' ident '.' ident
+ {
+ TABLE_LIST *table= Select->table_list.first;
+ if (my_strcasecmp(table_alias_charset, $1.str, table->db))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ if (my_strcasecmp(table_alias_charset, $3.str,
+ table->table_name))
+ {
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), $3.str);
+ MYSQL_YYABORT;
+ }
+ $$=$5;
+ }
+ | ident '.' ident
+ {
+ TABLE_LIST *table= Select->table_list.first;
+ if (my_strcasecmp(table_alias_charset, $1.str, table->alias))
+ {
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), $1.str);
+ MYSQL_YYABORT;
+ }
+ $$=$3;
+ }
+ | '.' ident { $$=$2;} /* For Delphi */
+ ;
+
+table_ident:
+ ident
+ {
+ $$= new Table_ident($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ident '.' ident
+ {
+ $$= new Table_ident(thd, $1,$3,0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | '.' ident
+ {
+ /* For Delphi */
+ $$= new Table_ident($2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+table_ident_opt_wild:
+ ident opt_wild
+ {
+ $$= new Table_ident($1);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ident '.' ident opt_wild
+ {
+ $$= new Table_ident(thd, $1,$3,0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+table_ident_nodb:
+ ident
+ {
+ LEX_STRING db={(char*) any_db,3};
+ $$= new Table_ident(thd, db,$1,0);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+IDENT_sys:
+ IDENT { $$= $1; }
+ | IDENT_QUOTED
+ {
+ if (thd->charset_is_system_charset)
+ {
+ CHARSET_INFO *cs= system_charset_info;
+ int dummy_error;
+ uint wlen= cs->cset->well_formed_len(cs, $1.str,
+ $1.str+$1.length,
+ $1.length, &dummy_error);
+ if (wlen < $1.length)
+ {
+ ErrConvString err($1.str, $1.length, &my_charset_bin);
+ my_error(ER_INVALID_CHARACTER_STRING, MYF(0),
+ cs->csname, err.ptr());
+ MYSQL_YYABORT;
+ }
+ $$= $1;
+ }
+ else
+ {
+ if (thd->convert_string(&$$, system_charset_info,
+ $1.str, $1.length, thd->charset()))
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+TEXT_STRING_sys:
+ TEXT_STRING
+ {
+ if (thd->charset_is_system_charset)
+ $$= $1;
+ else
+ {
+ if (thd->convert_string(&$$, system_charset_info,
+ $1.str, $1.length, thd->charset()))
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+TEXT_STRING_literal:
+ TEXT_STRING
+ {
+ if (thd->charset_is_collation_connection)
+ $$= $1;
+ else
+ {
+ if (thd->convert_string(&$$, thd->variables.collation_connection,
+ $1.str, $1.length, thd->charset()))
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+TEXT_STRING_filesystem:
+ TEXT_STRING
+ {
+ if (thd->charset_is_character_set_filesystem)
+ $$= $1;
+ else
+ {
+ if (thd->convert_string(&$$,
+ thd->variables.character_set_filesystem,
+ $1.str, $1.length, thd->charset()))
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+ident:
+ IDENT_sys { $$=$1; }
+ | keyword
+ {
+ $$.str= thd->strmake($1.str, $1.length);
+ if ($$.str == NULL)
+ MYSQL_YYABORT;
+ $$.length= $1.length;
+ }
+ ;
+
+label_ident:
+ IDENT_sys { $$=$1; }
+ | keyword_sp
+ {
+ $$.str= thd->strmake($1.str, $1.length);
+ if ($$.str == NULL)
+ MYSQL_YYABORT;
+ $$.length= $1.length;
+ }
+ ;
+
+ident_or_text:
+ ident { $$=$1;}
+ | TEXT_STRING_sys { $$=$1;}
+ | LEX_HOSTNAME { $$=$1;}
+ ;
+
+user_maybe_role:
+ ident_or_text
+ {
+ if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
+ MYSQL_YYABORT;
+ $$->user = $1;
+ $$->host= null_lex_str; // User or Role, see get_current_user()
+ $$->password= null_lex_str;
+ $$->plugin= empty_lex_str;
+ $$->auth= empty_lex_str;
+
+ if (check_string_char_length(&$$->user, ER(ER_USERNAME),
+ username_char_length,
+ system_charset_info, 0))
+ MYSQL_YYABORT;
+ }
+ | ident_or_text '@' ident_or_text
+ {
+ if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
+ MYSQL_YYABORT;
+ $$->user = $1; $$->host=$3;
+ $$->password= null_lex_str;
+ $$->plugin= empty_lex_str;
+ $$->auth= empty_lex_str;
+
+ if (check_string_char_length(&$$->user, ER(ER_USERNAME),
+ username_char_length,
+ system_charset_info, 0) ||
+ check_host_name(&$$->host))
+ MYSQL_YYABORT;
+ if ($$->host.str[0])
+ {
+ /*
+ Convert hostname part of username to lowercase.
+ It's OK to use in-place lowercase as long as
+ the character set is utf8.
+ */
+ my_casedn_str(system_charset_info, $$->host.str);
+ }
+ else
+ {
+ /*
+ fix historical undocumented convention that empty host is the
+ same as '%'
+ */
+ $$->host= host_not_specified;
+ }
+ }
+ | CURRENT_USER optional_braces
+ {
+ if (!($$=(LEX_USER*)thd->calloc(sizeof(LEX_USER))))
+ MYSQL_YYABORT;
+ $$->user= current_user;
+ $$->plugin= empty_lex_str;
+ $$->auth= empty_lex_str;
+ }
+ ;
+
+user_or_role: user_maybe_role | current_role;
+
+user: user_maybe_role
+ {
+ if ($1->user.str != current_user.str && $1->host.str == 0)
+ $1->host= host_not_specified;
+ $$= $1;
+ }
+ ;
+
+/* Keyword that we allow for identifiers (except SP labels) */
+keyword:
+ keyword_sp {}
+ | ASCII_SYM {}
+ | BACKUP_SYM {}
+ | BEGIN_SYM {}
+ | BYTE_SYM {}
+ | CACHE_SYM {}
+ | CHARSET {}
+ | CHECKSUM_SYM {}
+ | CHECKPOINT_SYM {}
+ | CLOSE_SYM {}
+ | COLUMN_ADD_SYM {}
+ | COLUMN_CHECK_SYM {}
+ | COLUMN_CREATE_SYM {}
+ | COLUMN_DELETE_SYM {}
+ | COLUMN_GET_SYM {}
+ | COMMENT_SYM {}
+ | COMMIT_SYM {}
+ | CONTAINS_SYM {}
+ | DEALLOCATE_SYM {}
+ | DO_SYM {}
+ | END {}
+ | EXAMINED_SYM {}
+ | EXECUTE_SYM {}
+ | FLUSH_SYM {}
+ | GET_SYM {}
+ | HANDLER_SYM {}
+ | HELP_SYM {}
+ | HOST_SYM {}
+ | INSTALL_SYM {}
+ | LANGUAGE_SYM {}
+ | NO_SYM {}
+ | OPEN_SYM {}
+ | OPTION {}
+ | OPTIONS_SYM {}
+ | OWNER_SYM {}
+ | PARSER_SYM {}
+ | PORT_SYM {}
+ | PREPARE_SYM {}
+ | REMOVE_SYM {}
+ | REPAIR {}
+ | RESET_SYM {}
+ | RESTORE_SYM {}
+ | ROLLBACK_SYM {}
+ | SAVEPOINT_SYM {}
+ | SECURITY_SYM {}
+ | SERVER_SYM {}
+ | SHUTDOWN {}
+ | SIGNED_SYM {}
+ | SOCKET_SYM {}
+ | SLAVE {}
+ | SLAVES {}
+ | SONAME_SYM {}
+ | START_SYM {}
+ | STOP_SYM {}
+ | TRUNCATE_SYM {}
+ | UNICODE_SYM {}
+ | UNINSTALL_SYM {}
+ | WRAPPER_SYM {}
+ | XA_SYM {}
+ | UPGRADE_SYM {}
+ ;
+
+/*
+ * Keywords that we allow for labels in SPs.
+ * Anything that's the beginning of a statement or characteristics
+ * must be in keyword above, otherwise we get (harmful) shift/reduce
+ * conflicts.
+ */
+keyword_sp:
+ ACTION {}
+ | ADDDATE_SYM {}
+ | ADMIN_SYM {}
+ | AFTER_SYM {}
+ | AGAINST {}
+ | AGGREGATE_SYM {}
+ | ALGORITHM_SYM {}
+ | ALWAYS_SYM {}
+ | ANY_SYM {}
+ | AT_SYM {}
+ | AUTHORS_SYM {}
+ | AUTO_INC {}
+ | AUTOEXTEND_SIZE_SYM {}
+ | AUTO_SYM {}
+ | AVG_ROW_LENGTH {}
+ | AVG_SYM {}
+ | BINLOG_SYM {}
+ | BIT_SYM {}
+ | BLOCK_SYM {}
+ | BOOL_SYM {}
+ | BOOLEAN_SYM {}
+ | BTREE_SYM {}
+ | CASCADED {}
+ | CATALOG_NAME_SYM {}
+ | CHAIN_SYM {}
+ | CHANGED {}
+ | CIPHER_SYM {}
+ | CLIENT_STATS_SYM {}
+ | CLIENT_SYM {}
+ | CLASS_ORIGIN_SYM {}
+ | COALESCE {}
+ | CODE_SYM {}
+ | COLLATION_SYM {}
+ | COLUMN_NAME_SYM {}
+ | COLUMNS {}
+ | COMMITTED_SYM {}
+ | COMPACT_SYM {}
+ | COMPLETION_SYM {}
+ | COMPRESSED_SYM {}
+ | CONCURRENT {}
+ | CONNECTION_SYM {}
+ | CONSISTENT_SYM {}
+ | CONSTRAINT_CATALOG_SYM {}
+ | CONSTRAINT_SCHEMA_SYM {}
+ | CONSTRAINT_NAME_SYM {}
+ | CONTEXT_SYM {}
+ | CONTRIBUTORS_SYM {}
+ | CURRENT_POS_SYM {}
+ | CPU_SYM {}
+ | CUBE_SYM {}
+ /*
+ Although a reserved keyword in SQL:2003 (and :2008),
+ not reserved in MySQL per WL#2111 specification.
+ */
+ | CURRENT_SYM {}
+ | CURSOR_NAME_SYM {}
+ | DATA_SYM {}
+ | DATAFILE_SYM {}
+ | DATETIME {}
+ | DATE_SYM {}
+ | DAY_SYM {}
+ | DEFINER_SYM {}
+ | DELAY_KEY_WRITE_SYM {}
+ | DES_KEY_FILE {}
+ | DIAGNOSTICS_SYM {}
+ | DIRECTORY_SYM {}
+ | DISABLE_SYM {}
+ | DISCARD {}
+ | DISK_SYM {}
+ | DUMPFILE {}
+ | DUPLICATE_SYM {}
+ | DYNAMIC_SYM {}
+ | ENDS_SYM {}
+ | ENUM {}
+ | ENGINE_SYM {}
+ | ENGINES_SYM {}
+ | ERROR_SYM {}
+ | ERRORS {}
+ | ESCAPE_SYM {}
+ | EVENT_SYM {}
+ | EVENTS_SYM {}
+ | EVERY_SYM {}
+ | EXCHANGE_SYM {}
+ | EXPANSION_SYM {}
+ | EXPORT_SYM {}
+ | EXTENDED_SYM {}
+ | EXTENT_SIZE_SYM {}
+ | FAULTS_SYM {}
+ | FAST_SYM {}
+ | FOUND_SYM {}
+ | ENABLE_SYM {}
+ | FULL {}
+ | FILE_SYM {}
+ | FIRST_SYM {}
+ | FIXED_SYM {}
+ | GENERAL {}
+ | GENERATED_SYM {}
+ | GEOMETRY_SYM {}
+ | GEOMETRYCOLLECTION {}
+ | GET_FORMAT {}
+ | GRANTS {}
+ | GLOBAL_SYM {}
+ | HASH_SYM {}
+ | HARD_SYM {}
+ | HOSTS_SYM {}
+ | HOUR_SYM {}
+ | ID_SYM {}
+ | IDENTIFIED_SYM {}
+ | IGNORE_SERVER_IDS_SYM {}
+ | INDEX_STATS_SYM {}
+ | INVOKER_SYM {}
+ | IMPORT {}
+ | INDEXES {}
+ | INITIAL_SIZE_SYM {}
+ | IO_SYM {}
+ | IPC_SYM {}
+ | ISOLATION {}
+ | ISSUER_SYM {}
+ | INSERT_METHOD {}
+ | KEY_BLOCK_SIZE {}
+ | LAST_VALUE {}
+ | LAST_SYM {}
+ | LEAVES {}
+ | LESS_SYM {}
+ | LEVEL_SYM {}
+ | LINESTRING {}
+ | LIST_SYM {}
+ | LOCAL_SYM {}
+ | LOCKS_SYM {}
+ | LOGFILE_SYM {}
+ | LOGS_SYM {}
+ | MAX_ROWS {}
+ | MASTER_SYM {}
+ | MASTER_HEARTBEAT_PERIOD_SYM {}
+ | MASTER_GTID_POS_SYM {}
+ | MASTER_HOST_SYM {}
+ | MASTER_PORT_SYM {}
+ | MASTER_LOG_FILE_SYM {}
+ | MASTER_LOG_POS_SYM {}
+ | MASTER_USER_SYM {}
+ | MASTER_USE_GTID_SYM {}
+ | MASTER_PASSWORD_SYM {}
+ | MASTER_SERVER_ID_SYM {}
+ | MASTER_CONNECT_RETRY_SYM {}
+ | MASTER_SSL_SYM {}
+ | MASTER_SSL_CA_SYM {}
+ | MASTER_SSL_CAPATH_SYM {}
+ | MASTER_SSL_CERT_SYM {}
+ | MASTER_SSL_CIPHER_SYM {}
+ | MASTER_SSL_CRL_SYM {}
+ | MASTER_SSL_CRLPATH_SYM {}
+ | MASTER_SSL_KEY_SYM {}
+ | MAX_CONNECTIONS_PER_HOUR {}
+ | MAX_QUERIES_PER_HOUR {}
+ | MAX_SIZE_SYM {}
+ | MAX_UPDATES_PER_HOUR {}
+ | MAX_USER_CONNECTIONS_SYM {}
+ | MEDIUM_SYM {}
+ | MEMORY_SYM {}
+ | MERGE_SYM {}
+ | MESSAGE_TEXT_SYM {}
+ | MICROSECOND_SYM {}
+ | MIGRATE_SYM {}
+ | MINUTE_SYM {}
+ | MIN_ROWS {}
+ | MODIFY_SYM {}
+ | MODE_SYM {}
+ | MONTH_SYM {}
+ | MULTILINESTRING {}
+ | MULTIPOINT {}
+ | MULTIPOLYGON {}
+ | MUTEX_SYM {}
+ | MYSQL_SYM {}
+ | MYSQL_ERRNO_SYM {}
+ | NAME_SYM {}
+ | NAMES_SYM {}
+ | NATIONAL_SYM {}
+ | NCHAR_SYM {}
+ | NDBCLUSTER_SYM {}
+ | NEXT_SYM {}
+ | NEW_SYM {}
+ | NO_WAIT_SYM {}
+ | NODEGROUP_SYM {}
+ | NONE_SYM {}
+ | NUMBER_SYM {}
+ | NVARCHAR_SYM {}
+ | OFFSET_SYM {}
+ | OLD_PASSWORD {}
+ | ONE_SYM {}
+ | ONLINE_SYM {}
+ | ONLY_SYM {}
+ | PACK_KEYS_SYM {}
+ | PAGE_SYM {}
+ | PARTIAL {}
+ | PARTITIONING_SYM {}
+ | PARTITIONS_SYM {}
+ | PASSWORD {}
+ | PERSISTENT_SYM {}
+ | PHASE_SYM {}
+ | PLUGIN_SYM {}
+ | PLUGINS_SYM {}
+ | POINT_SYM {}
+ | POLYGON {}
+ | PRESERVE_SYM {}
+ | PREV_SYM {}
+ | PRIVILEGES {}
+ | PROCESS {}
+ | PROCESSLIST_SYM {}
+ | PROFILE_SYM {}
+ | PROFILES_SYM {}
+ | PROXY_SYM {}
+ | QUARTER_SYM {}
+ | QUERY_SYM {}
+ | QUICK {}
+ | READ_ONLY_SYM {}
+ | REBUILD_SYM {}
+ | RECOVER_SYM {}
+ | REDO_BUFFER_SIZE_SYM {}
+ | REDOFILE_SYM {}
+ | REDUNDANT_SYM {}
+ | RELAY {}
+ | RELAYLOG_SYM {}
+ | RELAY_LOG_FILE_SYM {}
+ | RELAY_LOG_POS_SYM {}
+ | RELAY_THREAD {}
+ | RELOAD {}
+ | REORGANIZE_SYM {}
+ | REPEATABLE_SYM {}
+ | REPLICATION {}
+ | RESOURCES {}
+ | RESUME_SYM {}
+ | RETURNED_SQLSTATE_SYM {}
+ | RETURNS_SYM {}
+ | REVERSE_SYM {}
+ | ROLE_SYM {}
+ | ROLLUP_SYM {}
+ | ROUTINE_SYM {}
+ | ROWS_SYM {}
+ | ROW_COUNT_SYM {}
+ | ROW_FORMAT_SYM {}
+ | ROW_SYM {}
+ | RTREE_SYM {}
+ | SCHEDULE_SYM {}
+ | SCHEMA_NAME_SYM {}
+ | SECOND_SYM {}
+ | SERIAL_SYM {}
+ | SERIALIZABLE_SYM {}
+ | SESSION_SYM {}
+ | SIMPLE_SYM {}
+ | SHARE_SYM {}
+ | SLAVE_POS_SYM {}
+ | SLOW {}
+ | SNAPSHOT_SYM {}
+ | SOFT_SYM {}
+ | SOUNDS_SYM {}
+ | SOURCE_SYM {}
+ | SQL_CACHE_SYM {}
+ | SQL_BUFFER_RESULT {}
+ | SQL_NO_CACHE_SYM {}
+ | SQL_THREAD {}
+ | STARTS_SYM {}
+ | STATUS_SYM {}
+ | STORAGE_SYM {}
+ | STRING_SYM {}
+ | SUBCLASS_ORIGIN_SYM {}
+ | SUBDATE_SYM {}
+ | SUBJECT_SYM {}
+ | SUBPARTITION_SYM {}
+ | SUBPARTITIONS_SYM {}
+ | SUPER_SYM {}
+ | SUSPEND_SYM {}
+ | SWAPS_SYM {}
+ | SWITCHES_SYM {}
+ | TABLE_NAME_SYM {}
+ | TABLE_STATS_SYM {}
+ | TABLES {}
+ | TABLE_CHECKSUM_SYM {}
+ | TABLESPACE {}
+ | TEMPORARY {}
+ | TEMPTABLE_SYM {}
+ | TEXT_SYM {}
+ | THAN_SYM {}
+ | TRANSACTION_SYM {}
+ | TRANSACTIONAL_SYM {}
+ | TRIGGERS_SYM {}
+ | TIMESTAMP {}
+ | TIMESTAMP_ADD {}
+ | TIMESTAMP_DIFF {}
+ | TIME_SYM {}
+ | TYPES_SYM {}
+ | TYPE_SYM {}
+ | UDF_RETURNS_SYM {}
+ | FUNCTION_SYM {}
+ | UNCOMMITTED_SYM {}
+ | UNDEFINED_SYM {}
+ | UNDO_BUFFER_SIZE_SYM {}
+ | UNDOFILE_SYM {}
+ | UNKNOWN_SYM {}
+ | UNTIL_SYM {}
+ | USER {}
+ | USER_STATS_SYM {}
+ | USE_FRM {}
+ | VARIABLES {}
+ | VIEW_SYM {}
+ | VIRTUAL_SYM {}
+ | VALUE_SYM {}
+ | WARNINGS {}
+ | WAIT_SYM {}
+ | WEEK_SYM {}
+ | WEIGHT_STRING_SYM {}
+ | WORK_SYM {}
+ | X509_SYM {}
+ | XML_SYM {}
+ | YEAR_SYM {}
+ | VIA_SYM {}
+ ;
+
+/*
+ SQLCOM_SET_OPTION statement.
+
+ Note that to avoid shift/reduce conflicts, we have separate rules for the
+ first option listed in the statement.
+*/
+
+set:
+ SET
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_SET_OPTION;
+ mysql_init_select(lex);
+ lex->option_type=OPT_SESSION;
+ lex->var_list.empty();
+ lex->autocommit= 0;
+ sp_create_assignment_lex(thd, yychar == YYEMPTY);
+ }
+ start_option_value_list
+ {}
+ ;
+
+
+// Start of option value list
+start_option_value_list:
+ option_value_no_option_type
+ {
+ if (sp_create_assignment_instr(thd, yychar == YYEMPTY))
+ MYSQL_YYABORT;
+ }
+ option_value_list_continued
+ | TRANSACTION_SYM
+ {
+ Lex->option_type= OPT_DEFAULT;
+ }
+ transaction_characteristics
+ {
+ if (sp_create_assignment_instr(thd, yychar == YYEMPTY))
+ MYSQL_YYABORT;
+ }
+ | option_type
+ {
+ Lex->option_type= $1;
+ }
+ start_option_value_list_following_option_type
+ ;
+
+
+// Start of option value list, option_type was given
+start_option_value_list_following_option_type:
+ option_value_following_option_type
+ {
+ if (sp_create_assignment_instr(thd, yychar == YYEMPTY))
+ MYSQL_YYABORT;
+ }
+ option_value_list_continued
+ | TRANSACTION_SYM transaction_characteristics
+ {
+ if (sp_create_assignment_instr(thd, yychar == YYEMPTY))
+ MYSQL_YYABORT;
+ }
+ ;
+
+// Remainder of the option value list after first option value.
+option_value_list_continued:
+ /* empty */
+ | ',' option_value_list
+ ;
+
+// Repeating list of option values after first option value.
+option_value_list:
+ {
+ sp_create_assignment_lex(thd, yychar == YYEMPTY);
+ }
+ option_value
+ {
+ if (sp_create_assignment_instr(thd, yychar == YYEMPTY))
+ MYSQL_YYABORT;
+ }
+ | option_value_list ','
+ {
+ sp_create_assignment_lex(thd, yychar == YYEMPTY);
+ }
+ option_value
+ {
+ if (sp_create_assignment_instr(thd, yychar == YYEMPTY))
+ MYSQL_YYABORT;
+ }
+ ;
+
+// Wrapper around option values following the first option value in the stmt.
+option_value:
+ option_type
+ {
+ Lex->option_type= $1;
+ }
+ option_value_following_option_type
+ | option_value_no_option_type
+ ;
+
+option_type:
+ GLOBAL_SYM { $$=OPT_GLOBAL; }
+ | LOCAL_SYM { $$=OPT_SESSION; }
+ | SESSION_SYM { $$=OPT_SESSION; }
+ ;
+
+opt_var_type:
+ /* empty */ { $$=OPT_SESSION; }
+ | GLOBAL_SYM { $$=OPT_GLOBAL; }
+ | LOCAL_SYM { $$=OPT_SESSION; }
+ | SESSION_SYM { $$=OPT_SESSION; }
+ ;
+
+opt_var_ident_type:
+ /* empty */ { $$=OPT_DEFAULT; }
+ | GLOBAL_SYM '.' { $$=OPT_GLOBAL; }
+ | LOCAL_SYM '.' { $$=OPT_SESSION; }
+ | SESSION_SYM '.' { $$=OPT_SESSION; }
+ ;
+
+// Option values with preceding option_type.
+option_value_following_option_type:
+ internal_variable_name equal set_expr_or_default
+ {
+ LEX *lex= Lex;
+
+ if ($1.var && $1.var != trg_new_row_fake_var)
+ {
+ /* It is a system variable. */
+ if (set_system_variable(thd, &$1, lex->option_type, $3))
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ /*
+ Not in trigger assigning value to new row,
+ and option_type preceding local variable is illegal.
+ */
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+// Option values without preceding option_type.
+option_value_no_option_type:
+ internal_variable_name equal set_expr_or_default
+ {
+ LEX *lex= Lex;
+
+ if ($1.var == trg_new_row_fake_var)
+ {
+ /* We are in trigger and assigning value to field of new row */
+ if (set_trigger_new_row(thd, &$1.base_name, $3))
+ MYSQL_YYABORT;
+ }
+ else if ($1.var)
+ {
+ /* It is a system variable. */
+ if (set_system_variable(thd, &$1, lex->option_type, $3))
+ MYSQL_YYABORT;
+ }
+ else
+ {
+ sp_pcontext *spc= lex->spcont;
+ sp_variable *spv= spc->find_variable($1.base_name, false);
+
+ /* It is a local variable. */
+ if (set_local_variable(thd, spv, $3))
+ MYSQL_YYABORT;
+ }
+ }
+ | '@' ident_or_text equal expr
+ {
+ Item_func_set_user_var *item;
+ item= new (thd->mem_root) Item_func_set_user_var($2, $4);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ set_var_user *var= new set_var_user(item);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ Lex->var_list.push_back(var);
+ }
+ | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default
+ {
+ struct sys_var_with_base tmp= $4;
+ /* Lookup if necessary: must be a system variable. */
+ if (tmp.var == NULL)
+ {
+ if (find_sys_var_null_base(thd, &tmp))
+ MYSQL_YYABORT;
+ }
+ if (set_system_variable(thd, &tmp, $3, $6))
+ MYSQL_YYABORT;
+ }
+ | charset old_or_new_charset_name_or_default
+ {
+ LEX *lex= thd->lex;
+ CHARSET_INFO *cs2;
+ cs2= $2 ? $2: global_system_variables.character_set_client;
+ set_var_collation_client *var;
+ var= new set_var_collation_client(cs2,
+ thd->variables.collation_database,
+ cs2);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ lex->var_list.push_back(var);
+ }
+ | NAMES_SYM equal expr
+ {
+ LEX *lex= Lex;
+ sp_pcontext *spc= lex->spcont;
+ LEX_STRING names;
+
+ names.str= (char *)"names";
+ names.length= 5;
+ if (spc && spc->find_variable(names, false))
+ my_error(ER_SP_BAD_VAR_SHADOW, MYF(0), names.str);
+ else
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+
+ MYSQL_YYABORT;
+ }
+ | NAMES_SYM charset_name_or_default opt_collate
+ {
+ LEX *lex= Lex;
+ CHARSET_INFO *cs2;
+ CHARSET_INFO *cs3;
+ cs2= $2 ? $2 : global_system_variables.character_set_client;
+ cs3= $3 ? $3 : cs2;
+ if (!my_charset_same(cs2, cs3))
+ {
+ my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0),
+ cs3->name, cs2->csname);
+ MYSQL_YYABORT;
+ }
+ set_var_collation_client *var;
+ var= new set_var_collation_client(cs3, cs3, cs3);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ lex->var_list.push_back(var);
+ }
+ | ROLE_SYM ident_or_text
+ {
+ LEX *lex = Lex;
+ set_var_role *var= new set_var_role($2);
+ lex->var_list.push_back(var);
+ }
+ | PASSWORD equal text_or_password
+ {
+ LEX *lex= thd->lex;
+ LEX_USER *user;
+ sp_pcontext *spc= lex->spcont;
+ LEX_STRING pw;
+
+ pw.str= (char *)"password";
+ pw.length= 8;
+ if (spc && spc->find_variable(pw, false))
+ {
+ my_error(ER_SP_BAD_VAR_SHADOW, MYF(0), pw.str);
+ MYSQL_YYABORT;
+ }
+ if (!(user=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))
+ MYSQL_YYABORT;
+ user->user= current_user;
+ set_var_password *var= new set_var_password(user, $3);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ thd->lex->var_list.push_back(var);
+ thd->lex->autocommit= TRUE;
+ if (lex->sphead)
+ lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT;
+ }
+ | PASSWORD FOR_SYM user equal text_or_password
+ {
+ set_var_password *var= new set_var_password($3,$5);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ Lex->var_list.push_back(var);
+ Lex->autocommit= TRUE;
+ if (Lex->sphead)
+ Lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT;
+ }
+ ;
+
+internal_variable_name:
+ ident
+ {
+ sp_pcontext *spc= thd->lex->spcont;
+ sp_variable *spv;
+
+ /* Best effort lookup for system variable. */
+ if (!spc || !(spv = spc->find_variable($1, false)))
+ {
+ struct sys_var_with_base tmp= {NULL, $1};
+
+ /* Not an SP local variable */
+ if (find_sys_var_null_base(thd, &tmp))
+ MYSQL_YYABORT;
+
+ $$= tmp;
+ }
+ else
+ {
+ /*
+ Possibly an SP local variable (or a shadowed sysvar).
+ Will depend on the context of the SET statement.
+ */
+ $$.var= NULL;
+ $$.base_name= $1;
+ }
+ }
+ | ident '.' ident
+ {
+ LEX *lex= Lex;
+ if (check_reserved_words(&$1))
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
+ (!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
+ !my_strcasecmp(system_charset_info, $1.str, "OLD")))
+ {
+ if ($1.str[0]=='O' || $1.str[0]=='o')
+ {
+ my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", "");
+ MYSQL_YYABORT;
+ }
+ if (lex->trg_chistics.event == TRG_EVENT_DELETE)
+ {
+ my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0),
+ "NEW", "on DELETE");
+ MYSQL_YYABORT;
+ }
+ if (lex->trg_chistics.action_time == TRG_ACTION_AFTER)
+ {
+ my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "NEW", "after ");
+ MYSQL_YYABORT;
+ }
+ /* This special combination will denote field of NEW row */
+ $$.var= trg_new_row_fake_var;
+ $$.base_name= $3;
+ }
+ else
+ {
+ sys_var *tmp=find_sys_var(thd, $3.str, $3.length);
+ if (!tmp)
+ MYSQL_YYABORT;
+ if (!tmp->is_struct())
+ my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), $3.str);
+ $$.var= tmp;
+ $$.base_name= $1;
+ }
+ }
+ | DEFAULT '.' ident
+ {
+ sys_var *tmp=find_sys_var(thd, $3.str, $3.length);
+ if (!tmp)
+ MYSQL_YYABORT;
+ if (!tmp->is_struct())
+ my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), $3.str);
+ $$.var= tmp;
+ $$.base_name.str= (char*) "default";
+ $$.base_name.length= 7;
+ }
+ ;
+
+transaction_characteristics:
+ transaction_access_mode
+ | isolation_level
+ | transaction_access_mode ',' isolation_level
+ | isolation_level ',' transaction_access_mode
+ ;
+
+transaction_access_mode:
+ transaction_access_mode_types
+ {
+ LEX *lex=Lex;
+ Item *item= new (thd->mem_root) Item_int((int32) $1);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ set_var *var= new set_var(lex->option_type,
+ find_sys_var(thd, "tx_read_only"),
+ &null_lex_str,
+ item);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ lex->var_list.push_back(var);
+ }
+ ;
+
+isolation_level:
+ ISOLATION LEVEL_SYM isolation_types
+ {
+ LEX *lex=Lex;
+ Item *item= new (thd->mem_root) Item_int((int32) $3);
+ if (item == NULL)
+ MYSQL_YYABORT;
+ set_var *var= new set_var(lex->option_type,
+ find_sys_var(thd, "tx_isolation"),
+ &null_lex_str,
+ item);
+ if (var == NULL)
+ MYSQL_YYABORT;
+ lex->var_list.push_back(var);
+ }
+ ;
+
+transaction_access_mode_types:
+ READ_SYM ONLY_SYM { $$= true; }
+ | READ_SYM WRITE_SYM { $$= false; }
+ ;
+
+isolation_types:
+ READ_SYM UNCOMMITTED_SYM { $$= ISO_READ_UNCOMMITTED; }
+ | READ_SYM COMMITTED_SYM { $$= ISO_READ_COMMITTED; }
+ | REPEATABLE_SYM READ_SYM { $$= ISO_REPEATABLE_READ; }
+ | SERIALIZABLE_SYM { $$= ISO_SERIALIZABLE; }
+ ;
+
+text_or_password:
+ TEXT_STRING { $$=$1.str;}
+ | PASSWORD '(' TEXT_STRING ')'
+ {
+ $$= $3.length ? thd->variables.old_passwords ?
+ Item_func_old_password::alloc(thd, $3.str, $3.length) :
+ Item_func_password::alloc(thd, $3.str, $3.length) :
+ $3.str;
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | OLD_PASSWORD '(' TEXT_STRING ')'
+ {
+ $$= $3.length ? Item_func_old_password::
+ alloc(thd, $3.str, $3.length) :
+ $3.str;
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+set_expr_or_default:
+ expr { $$=$1; }
+ | DEFAULT { $$=0; }
+ | ON
+ {
+ $$=new (thd->mem_root) Item_string_sys("ON", 2);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | ALL
+ {
+ $$=new (thd->mem_root) Item_string_sys("ALL", 3);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ | BINARY
+ {
+ $$=new (thd->mem_root) Item_string_sys("binary", 6);
+ if ($$ == NULL)
+ MYSQL_YYABORT;
+ }
+ ;
+
+/* Lock function */
+
+lock:
+ LOCK_SYM table_or_tables
+ {
+ LEX *lex= Lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "LOCK");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command= SQLCOM_LOCK_TABLES;
+ }
+ table_lock_list
+ {}
+ ;
+
+table_or_tables:
+ TABLE_SYM { Lex->only_view= FALSE; }
+ | TABLES { Lex->only_view= FALSE; }
+ ;
+
+table_or_view:
+ table_or_tables
+ | VIEW_SYM { Lex->only_view= TRUE; }
+ ;
+
+table_lock_list:
+ table_lock
+ | table_lock_list ',' table_lock
+ ;
+
+table_lock:
+ table_ident opt_table_alias lock_option
+ {
+ thr_lock_type lock_type= (thr_lock_type) $3;
+ bool lock_for_write= (lock_type >= TL_WRITE_ALLOW_WRITE);
+ if (!Select->add_table_to_list(thd, $1, $2, 0, lock_type,
+ (lock_for_write ?
+ lock_type == TL_WRITE_CONCURRENT_INSERT ?
+ MDL_SHARED_WRITE :
+ MDL_SHARED_NO_READ_WRITE :
+ MDL_SHARED_READ)))
+ MYSQL_YYABORT;
+ }
+ ;
+
+lock_option:
+ READ_SYM { $$= TL_READ_NO_INSERT; }
+ | WRITE_SYM { $$= TL_WRITE_DEFAULT; }
+ | WRITE_SYM CONCURRENT
+ {
+ $$= (Lex->sphead ? TL_WRITE_DEFAULT : TL_WRITE_CONCURRENT_INSERT);
+ }
+
+ | LOW_PRIORITY WRITE_SYM { $$= TL_WRITE_LOW_PRIORITY; }
+ | READ_SYM LOCAL_SYM { $$= TL_READ; }
+ ;
+
+unlock:
+ UNLOCK_SYM
+ {
+ LEX *lex= Lex;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "UNLOCK");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command= SQLCOM_UNLOCK_TABLES;
+ }
+ table_or_tables
+ {}
+ ;
+
+/*
+** Handler: direct access to ISAM functions
+*/
+
+handler:
+ HANDLER_SYM table_ident OPEN_SYM opt_table_alias
+ {
+ LEX *lex= Lex;
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "HANDLER");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command = SQLCOM_HA_OPEN;
+ if (!lex->current_select->add_table_to_list(lex->thd, $2, $4, 0))
+ MYSQL_YYABORT;
+ }
+ | HANDLER_SYM table_ident_nodb CLOSE_SYM
+ {
+ LEX *lex= Lex;
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "HANDLER");
+ MYSQL_YYABORT;
+ }
+ lex->sql_command = SQLCOM_HA_CLOSE;
+ if (!lex->current_select->add_table_to_list(lex->thd, $2, 0, 0))
+ MYSQL_YYABORT;
+ }
+ | HANDLER_SYM table_ident_nodb READ_SYM
+ {
+ LEX *lex=Lex;
+ if (lex->sphead)
+ {
+ my_error(ER_SP_BADSTATEMENT, MYF(0), "HANDLER");
+ MYSQL_YYABORT;
+ }
+ lex->expr_allows_subselect= FALSE;
+ lex->sql_command = SQLCOM_HA_READ;
+ lex->ha_rkey_mode= HA_READ_KEY_EXACT; /* Avoid purify warnings */
+ Item *one= new (thd->mem_root) Item_int((int32) 1);
+ if (one == NULL)
+ MYSQL_YYABORT;
+ lex->current_select->select_limit= one;
+ lex->current_select->offset_limit= 0;
+ lex->limit_rows_examined= 0;
+ if (!lex->current_select->add_table_to_list(lex->thd, $2, 0, 0))
+ MYSQL_YYABORT;
+ }
+ handler_read_or_scan where_clause opt_limit_clause
+ {
+ Lex->expr_allows_subselect= TRUE;
+ /* Stored functions are not supported for HANDLER READ. */
+ if (Lex->uses_stored_routines())
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "stored functions in HANDLER ... READ");
+ MYSQL_YYABORT;
+ }
+ }
+ ;
+
+handler_read_or_scan:
+ handler_scan_function { Lex->ident= null_lex_str; }
+ | ident handler_rkey_function { Lex->ident= $1; }
+ ;
+
+handler_scan_function:
+ FIRST_SYM { Lex->ha_read_mode = RFIRST; }
+ | NEXT_SYM { Lex->ha_read_mode = RNEXT; }
+ ;
+
+handler_rkey_function:
+ FIRST_SYM { Lex->ha_read_mode = RFIRST; }
+ | NEXT_SYM { Lex->ha_read_mode = RNEXT; }
+ | PREV_SYM { Lex->ha_read_mode = RPREV; }
+ | LAST_SYM { Lex->ha_read_mode = RLAST; }
+ | handler_rkey_mode
+ {
+ LEX *lex=Lex;
+ lex->ha_read_mode = RKEY;
+ lex->ha_rkey_mode=$1;
+ if (!(lex->insert_list = new List_item))
+ MYSQL_YYABORT;
+ }
+ '(' values ')'
+ {}
+ ;
+
+handler_rkey_mode:
+ EQ { $$=HA_READ_KEY_EXACT; }
+ | GE { $$=HA_READ_KEY_OR_NEXT; }
+ | LE { $$=HA_READ_KEY_OR_PREV; }
+ | GT_SYM { $$=HA_READ_AFTER_KEY; }
+ | LT { $$=HA_READ_BEFORE_KEY; }
+ ;
+
+/* GRANT / REVOKE */
+
+revoke:
+ REVOKE clear_privileges revoke_command
+ {}
+ ;
+
+revoke_command:
+ grant_privileges ON opt_table grant_ident FROM user_and_role_list
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_REVOKE;
+ lex->type= 0;
+ }
+ | grant_privileges ON FUNCTION_SYM grant_ident FROM user_and_role_list
+ {
+ LEX *lex= Lex;
+ if (lex->columns.elements)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ lex->sql_command= SQLCOM_REVOKE;
+ lex->type= TYPE_ENUM_FUNCTION;
+ }
+ | grant_privileges ON PROCEDURE_SYM grant_ident FROM user_and_role_list
+ {
+ LEX *lex= Lex;
+ if (lex->columns.elements)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ lex->sql_command= SQLCOM_REVOKE;
+ lex->type= TYPE_ENUM_PROCEDURE;
+ }
+ | ALL opt_privileges ',' GRANT OPTION FROM user_and_role_list
+ {
+ Lex->sql_command = SQLCOM_REVOKE_ALL;
+ }
+ | PROXY_SYM ON user FROM user_list
+ {
+ LEX *lex= Lex;
+ lex->users_list.push_front ($3);
+ lex->sql_command= SQLCOM_REVOKE;
+ lex->type= TYPE_ENUM_PROXY;
+ }
+ | admin_option_for_role FROM user_and_role_list
+ {
+ Lex->sql_command= SQLCOM_REVOKE_ROLE;
+ if (Lex->users_list.push_front($1))
+ MYSQL_YYABORT;
+ }
+ ;
+
+admin_option_for_role:
+ ADMIN_SYM OPTION FOR_SYM grant_role
+ { Lex->with_admin_option= true; $$= $4; }
+ | grant_role
+ { Lex->with_admin_option= false; $$= $1; }
+ ;
+
+grant:
+ GRANT clear_privileges grant_command
+ {}
+ ;
+
+grant_command:
+ grant_privileges ON opt_table grant_ident TO_SYM grant_list
+ require_clause grant_options
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_GRANT;
+ lex->type= 0;
+ }
+ | grant_privileges ON FUNCTION_SYM grant_ident TO_SYM grant_list
+ require_clause grant_options
+ {
+ LEX *lex= Lex;
+ if (lex->columns.elements)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ lex->sql_command= SQLCOM_GRANT;
+ lex->type= TYPE_ENUM_FUNCTION;
+ }
+ | grant_privileges ON PROCEDURE_SYM grant_ident TO_SYM grant_list
+ require_clause grant_options
+ {
+ LEX *lex= Lex;
+ if (lex->columns.elements)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ lex->sql_command= SQLCOM_GRANT;
+ lex->type= TYPE_ENUM_PROCEDURE;
+ }
+ | PROXY_SYM ON user TO_SYM grant_list opt_grant_option
+ {
+ LEX *lex= Lex;
+ lex->users_list.push_front ($3);
+ lex->sql_command= SQLCOM_GRANT;
+ lex->type= TYPE_ENUM_PROXY;
+ }
+ | grant_role TO_SYM grant_list opt_with_admin_option
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_GRANT_ROLE;
+ /* The first role is the one that is granted */
+ if (Lex->users_list.push_front($1))
+ MYSQL_YYABORT;
+ }
+
+ ;
+
+opt_with_admin:
+ /* nothing */ { Lex->definer = 0; }
+ | WITH ADMIN_SYM user_or_role { Lex->definer = $3; }
+
+opt_with_admin_option:
+ /* nothing */ { Lex->with_admin_option= false; }
+ | WITH ADMIN_SYM OPTION { Lex->with_admin_option= true; }
+
+role_list:
+ grant_role
+ {
+ if (Lex->users_list.push_back($1))
+ MYSQL_YYABORT;
+ }
+ | role_list ',' grant_role
+ {
+ if (Lex->users_list.push_back($3))
+ MYSQL_YYABORT;
+ }
+ ;
+
+current_role:
+ CURRENT_ROLE optional_braces
+ {
+ if (!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))
+ MYSQL_YYABORT;
+ $$->user= current_role;
+ $$->plugin= empty_lex_str;
+ $$->auth= empty_lex_str;
+ }
+ ;
+
+grant_role:
+ ident_or_text
+ {
+ if ($1.length == 0)
+ {
+ my_error(ER_INVALID_ROLE, MYF(0), "");
+ MYSQL_YYABORT;
+ }
+ if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
+ MYSQL_YYABORT;
+ $$->user = $1;
+ $$->host= empty_lex_str;
+ $$->password= null_lex_str;
+ $$->plugin= empty_lex_str;
+ $$->auth= empty_lex_str;
+
+ if (check_string_char_length(&$$->user, ER(ER_USERNAME),
+ username_char_length,
+ system_charset_info, 0))
+ MYSQL_YYABORT;
+ }
+ | current_role
+ ;
+
+opt_table:
+ /* Empty */
+ | TABLE_SYM
+ ;
+
+grant_privileges:
+ object_privilege_list {}
+ | ALL opt_privileges
+ {
+ Lex->all_privileges= 1;
+ Lex->grant= GLOBAL_ACLS;
+ }
+ ;
+
+opt_privileges:
+ /* empty */
+ | PRIVILEGES
+ ;
+
+object_privilege_list:
+ object_privilege
+ | object_privilege_list ',' object_privilege
+ ;
+
+object_privilege:
+ SELECT_SYM
+ { Lex->which_columns = SELECT_ACL;}
+ opt_column_list {}
+ | INSERT
+ { Lex->which_columns = INSERT_ACL;}
+ opt_column_list {}
+ | UPDATE_SYM
+ { Lex->which_columns = UPDATE_ACL; }
+ opt_column_list {}
+ | REFERENCES
+ { Lex->which_columns = REFERENCES_ACL;}
+ opt_column_list {}
+ | DELETE_SYM { Lex->grant |= DELETE_ACL;}
+ | USAGE {}
+ | INDEX_SYM { Lex->grant |= INDEX_ACL;}
+ | ALTER { Lex->grant |= ALTER_ACL;}
+ | CREATE { Lex->grant |= CREATE_ACL;}
+ | DROP { Lex->grant |= DROP_ACL;}
+ | EXECUTE_SYM { Lex->grant |= EXECUTE_ACL;}
+ | RELOAD { Lex->grant |= RELOAD_ACL;}
+ | SHUTDOWN { Lex->grant |= SHUTDOWN_ACL;}
+ | PROCESS { Lex->grant |= PROCESS_ACL;}
+ | FILE_SYM { Lex->grant |= FILE_ACL;}
+ | GRANT OPTION { Lex->grant |= GRANT_ACL;}
+ | SHOW DATABASES { Lex->grant |= SHOW_DB_ACL;}
+ | SUPER_SYM { Lex->grant |= SUPER_ACL;}
+ | CREATE TEMPORARY TABLES { Lex->grant |= CREATE_TMP_ACL;}
+ | LOCK_SYM TABLES { Lex->grant |= LOCK_TABLES_ACL; }
+ | REPLICATION SLAVE { Lex->grant |= REPL_SLAVE_ACL; }
+ | REPLICATION CLIENT_SYM { Lex->grant |= REPL_CLIENT_ACL; }
+ | CREATE VIEW_SYM { Lex->grant |= CREATE_VIEW_ACL; }
+ | SHOW VIEW_SYM { Lex->grant |= SHOW_VIEW_ACL; }
+ | CREATE ROUTINE_SYM { Lex->grant |= CREATE_PROC_ACL; }
+ | ALTER ROUTINE_SYM { Lex->grant |= ALTER_PROC_ACL; }
+ | CREATE USER { Lex->grant |= CREATE_USER_ACL; }
+ | EVENT_SYM { Lex->grant |= EVENT_ACL;}
+ | TRIGGER_SYM { Lex->grant |= TRIGGER_ACL; }
+ | CREATE TABLESPACE { Lex->grant |= CREATE_TABLESPACE_ACL; }
+ ;
+
+opt_and:
+ /* empty */ {}
+ | AND_SYM {}
+ ;
+
+require_list:
+ require_list_element opt_and require_list
+ | require_list_element
+ ;
+
+require_list_element:
+ SUBJECT_SYM TEXT_STRING
+ {
+ LEX *lex=Lex;
+ if (lex->x509_subject)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "SUBJECT");
+ MYSQL_YYABORT;
+ }
+ lex->x509_subject=$2.str;
+ }
+ | ISSUER_SYM TEXT_STRING
+ {
+ LEX *lex=Lex;
+ if (lex->x509_issuer)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "ISSUER");
+ MYSQL_YYABORT;
+ }
+ lex->x509_issuer=$2.str;
+ }
+ | CIPHER_SYM TEXT_STRING
+ {
+ LEX *lex=Lex;
+ if (lex->ssl_cipher)
+ {
+ my_error(ER_DUP_ARGUMENT, MYF(0), "CIPHER");
+ MYSQL_YYABORT;
+ }
+ lex->ssl_cipher=$2.str;
+ }
+ ;
+
+grant_ident:
+ '*'
+ {
+ LEX *lex= Lex;
+ size_t dummy;
+ if (lex->copy_db_to(&lex->current_select->db, &dummy))
+ MYSQL_YYABORT;
+ if (lex->grant == GLOBAL_ACLS)
+ lex->grant = DB_ACLS & ~GRANT_ACL;
+ else if (lex->columns.elements)
+ {
+ my_message(ER_ILLEGAL_GRANT_FOR_TABLE,
+ ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ | ident '.' '*'
+ {
+ LEX *lex= Lex;
+ lex->current_select->db = $1.str;
+ if (lex->grant == GLOBAL_ACLS)
+ lex->grant = DB_ACLS & ~GRANT_ACL;
+ else if (lex->columns.elements)
+ {
+ my_message(ER_ILLEGAL_GRANT_FOR_TABLE,
+ ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ | '*' '.' '*'
+ {
+ LEX *lex= Lex;
+ lex->current_select->db = NULL;
+ if (lex->grant == GLOBAL_ACLS)
+ lex->grant= GLOBAL_ACLS & ~GRANT_ACL;
+ else if (lex->columns.elements)
+ {
+ my_message(ER_ILLEGAL_GRANT_FOR_TABLE,
+ ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0));
+ MYSQL_YYABORT;
+ }
+ }
+ | table_ident
+ {
+ LEX *lex=Lex;
+ if (!lex->current_select->add_table_to_list(lex->thd, $1,NULL,
+ TL_OPTION_UPDATING))
+ MYSQL_YYABORT;
+ if (lex->grant == GLOBAL_ACLS)
+ lex->grant = TABLE_ACLS & ~GRANT_ACL;
+ }
+ ;
+
+user_list:
+ user
+ {
+ if (Lex->users_list.push_back($1))
+ MYSQL_YYABORT;
+ }
+ | user_list ',' user
+ {
+ if (Lex->users_list.push_back($3))
+ MYSQL_YYABORT;
+ }
+ ;
+
+grant_list:
+ grant_user
+ {
+ if (Lex->users_list.push_back($1))
+ MYSQL_YYABORT;
+ }
+ | grant_list ',' grant_user
+ {
+ if (Lex->users_list.push_back($3))
+ MYSQL_YYABORT;
+ }
+ ;
+
+user_and_role_list:
+ user_or_role
+ {
+ if (Lex->users_list.push_back($1))
+ MYSQL_YYABORT;
+ }
+ | user_and_role_list ',' user_or_role
+ {
+ if (Lex->users_list.push_back($3))
+ MYSQL_YYABORT;
+ }
+ ;
+
+via_or_with: VIA_SYM | WITH ;
+using_or_as: USING | AS ;
+
+grant_user:
+ user IDENTIFIED_SYM BY TEXT_STRING
+ {
+ $$=$1; $1->password=$4;
+ if (Lex->sql_command == SQLCOM_REVOKE)
+ MYSQL_YYABORT;
+ if ($4.length)
+ {
+ if (thd->variables.old_passwords == 1)
+ {
+ char *buff=
+ (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1);
+ if (buff == NULL)
+ MYSQL_YYABORT;
+ my_make_scrambled_password_323(buff, $4.str, $4.length);
+ $1->password.str= buff;
+ $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323;
+ }
+ else
+ {
+ char *buff=
+ (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1);
+ if (buff == NULL)
+ MYSQL_YYABORT;
+ my_make_scrambled_password(buff, $4.str, $4.length);
+ $1->password.str= buff;
+ $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH;
+ }
+ }
+ }
+ | user IDENTIFIED_SYM BY PASSWORD TEXT_STRING
+ {
+ $$= $1;
+ $1->password= $5;
+ }
+ | user IDENTIFIED_SYM via_or_with ident_or_text
+ {
+ $$= $1;
+ $1->plugin= $4;
+ $1->auth= empty_lex_str;
+ }
+ | user IDENTIFIED_SYM via_or_with ident_or_text using_or_as TEXT_STRING_sys
+ {
+ $$= $1;
+ $1->plugin= $4;
+ $1->auth= $6;
+ }
+ | user_or_role
+ { $$= $1; $1->password= null_lex_str; }
+ ;
+
+opt_column_list:
+ /* empty */
+ {
+ LEX *lex=Lex;
+ lex->grant |= lex->which_columns;
+ }
+ | '(' column_list ')'
+ ;
+
+column_list:
+ column_list ',' column_list_id
+ | column_list_id
+ ;
+
+column_list_id:
+ ident
+ {
+ String *new_str = new (thd->mem_root) String((const char*) $1.str,$1.length,system_charset_info);
+ if (new_str == NULL)
+ MYSQL_YYABORT;
+ List_iterator <LEX_COLUMN> iter(Lex->columns);
+ class LEX_COLUMN *point;
+ LEX *lex=Lex;
+ while ((point=iter++))
+ {
+ if (!my_strcasecmp(system_charset_info,
+ point->column.c_ptr(), new_str->c_ptr()))
+ break;
+ }
+ lex->grant_tot_col|= lex->which_columns;
+ if (point)
+ point->rights |= lex->which_columns;
+ else
+ {
+ LEX_COLUMN *col= new LEX_COLUMN (*new_str,lex->which_columns);
+ if (col == NULL)
+ MYSQL_YYABORT;
+ lex->columns.push_back(col);
+ }
+ }
+ ;
+
+require_clause:
+ /* empty */
+ | REQUIRE_SYM require_list
+ {
+ Lex->ssl_type=SSL_TYPE_SPECIFIED;
+ }
+ | REQUIRE_SYM SSL_SYM
+ {
+ Lex->ssl_type=SSL_TYPE_ANY;
+ }
+ | REQUIRE_SYM X509_SYM
+ {
+ Lex->ssl_type=SSL_TYPE_X509;
+ }
+ | REQUIRE_SYM NONE_SYM
+ {
+ Lex->ssl_type=SSL_TYPE_NONE;
+ }
+ ;
+
+grant_options:
+ /* empty */ {}
+ | WITH grant_option_list
+ ;
+
+opt_grant_option:
+ /* empty */ {}
+ | WITH GRANT OPTION { Lex->grant |= GRANT_ACL;}
+ ;
+
+grant_option_list:
+ grant_option_list grant_option {}
+ | grant_option {}
+ ;
+
+grant_option:
+ GRANT OPTION { Lex->grant |= GRANT_ACL;}
+ | MAX_QUERIES_PER_HOUR ulong_num
+ {
+ LEX *lex=Lex;
+ lex->mqh.questions=$2;
+ lex->mqh.specified_limits|= USER_RESOURCES::QUERIES_PER_HOUR;
+ }
+ | MAX_UPDATES_PER_HOUR ulong_num
+ {
+ LEX *lex=Lex;
+ lex->mqh.updates=$2;
+ lex->mqh.specified_limits|= USER_RESOURCES::UPDATES_PER_HOUR;
+ }
+ | MAX_CONNECTIONS_PER_HOUR ulong_num
+ {
+ LEX *lex=Lex;
+ lex->mqh.conn_per_hour= $2;
+ lex->mqh.specified_limits|= USER_RESOURCES::CONNECTIONS_PER_HOUR;
+ }
+ | MAX_USER_CONNECTIONS_SYM int_num
+ {
+ LEX *lex=Lex;
+ lex->mqh.user_conn= $2;
+ lex->mqh.specified_limits|= USER_RESOURCES::USER_CONNECTIONS;
+ }
+ ;
+
+begin:
+ BEGIN_SYM
+ {
+ LEX *lex=Lex;
+ lex->sql_command = SQLCOM_BEGIN;
+ lex->start_transaction_opt= 0;
+ }
+ opt_work {}
+ ;
+
+opt_work:
+ /* empty */ {}
+ | WORK_SYM {}
+ ;
+
+opt_chain:
+ /* empty */
+ { $$= TVL_UNKNOWN; }
+ | AND_SYM NO_SYM CHAIN_SYM { $$= TVL_NO; }
+ | AND_SYM CHAIN_SYM { $$= TVL_YES; }
+ ;
+
+opt_release:
+ /* empty */
+ { $$= TVL_UNKNOWN; }
+ | RELEASE_SYM { $$= TVL_YES; }
+ | NO_SYM RELEASE_SYM { $$= TVL_NO; }
+;
+
+opt_savepoint:
+ /* empty */ {}
+ | SAVEPOINT_SYM {}
+ ;
+
+commit:
+ COMMIT_SYM opt_work opt_chain opt_release
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_COMMIT;
+ /* Don't allow AND CHAIN RELEASE. */
+ MYSQL_YYABORT_UNLESS($3 != TVL_YES || $4 != TVL_YES);
+ lex->tx_chain= $3;
+ lex->tx_release= $4;
+ }
+ ;
+
+rollback:
+ ROLLBACK_SYM opt_work opt_chain opt_release
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_ROLLBACK;
+ /* Don't allow AND CHAIN RELEASE. */
+ MYSQL_YYABORT_UNLESS($3 != TVL_YES || $4 != TVL_YES);
+ lex->tx_chain= $3;
+ lex->tx_release= $4;
+ }
+ | ROLLBACK_SYM opt_work
+ TO_SYM opt_savepoint ident
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_ROLLBACK_TO_SAVEPOINT;
+ lex->ident= $5;
+ }
+ ;
+
+savepoint:
+ SAVEPOINT_SYM ident
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_SAVEPOINT;
+ lex->ident= $2;
+ }
+ ;
+
+release:
+ RELEASE_SYM SAVEPOINT_SYM ident
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_RELEASE_SAVEPOINT;
+ lex->ident= $3;
+ }
+ ;
+
+/*
+ UNIONS : glue selects together
+*/
+
+
+union_clause:
+ /* empty */ {}
+ | union_list
+ ;
+
+union_list:
+ UNION_SYM union_option
+ {
+ if (add_select_to_union_list(Lex, (bool)$2, TRUE))
+ MYSQL_YYABORT;
+ }
+ select_init
+ {
+ /*
+ Remove from the name resolution context stack the context of the
+ last select in the union.
+ */
+ Lex->pop_context();
+ }
+ ;
+
+union_opt:
+ /* Empty */ { $$= 0; }
+ | union_list { $$= 1; }
+ | union_order_or_limit { $$= 1; }
+ ;
+
+opt_union_order_or_limit:
+ /* Empty */{ $$= false; }
+ | union_order_or_limit { $$= true; }
+ ;
+
+union_order_or_limit:
+ {
+ LEX *lex= thd->lex;
+ DBUG_ASSERT(lex->current_select->linkage != GLOBAL_OPTIONS_TYPE);
+ SELECT_LEX *sel= lex->current_select;
+ SELECT_LEX_UNIT *unit= sel->master_unit();
+ SELECT_LEX *fake= unit->fake_select_lex;
+ if (fake)
+ {
+ unit->global_parameters= fake;
+ fake->no_table_names_allowed= 1;
+ lex->current_select= fake;
+ }
+ thd->where= "global ORDER clause";
+ }
+ order_or_limit
+ {
+ thd->lex->current_select->no_table_names_allowed= 0;
+ thd->where= "";
+ }
+ ;
+
+order_or_limit:
+ order_clause opt_limit_clause_init
+ | limit_clause
+ ;
+
+union_option:
+ /* empty */ { $$=1; }
+ | DISTINCT { $$=1; }
+ | ALL { $$=0; }
+ ;
+
+query_specification:
+ SELECT_SYM select_init2_derived
+ {
+ $$= Lex->current_select->master_unit()->first_select();
+ }
+ | '(' select_paren_derived ')'
+ {
+ $$= Lex->current_select->master_unit()->first_select();
+ }
+ ;
+
+query_expression_body:
+ query_specification opt_union_order_or_limit
+ | query_expression_body
+ UNION_SYM union_option
+ {
+ if (add_select_to_union_list(Lex, (bool)$3, FALSE))
+ MYSQL_YYABORT;
+ }
+ query_specification
+ opt_union_order_or_limit
+ {
+ Lex->pop_context();
+ $$= $1;
+ }
+ ;
+
+/* Corresponds to <query expression> in the SQL:2003 standard. */
+subselect:
+ subselect_start query_expression_body subselect_end
+ {
+ $$= $2;
+ }
+ ;
+
+subselect_start:
+ {
+ LEX *lex=Lex;
+ if (!lex->expr_allows_subselect ||
+ lex->sql_command == (int)SQLCOM_PURGE)
+ {
+ my_parse_error(ER(ER_SYNTAX_ERROR));
+ MYSQL_YYABORT;
+ }
+ /*
+ we are making a "derived table" for the parenthesis
+ as we need to have a lex level to fit the union
+ after the parenthesis, e.g.
+ (SELECT .. ) UNION ... becomes
+ SELECT * FROM ((SELECT ...) UNION ...)
+ */
+ if (mysql_new_select(Lex, 1))
+ MYSQL_YYABORT;
+ }
+ ;
+
+subselect_end:
+ {
+ LEX *lex=Lex;
+
+ lex->pop_context();
+ SELECT_LEX *child= lex->current_select;
+ lex->current_select = lex->current_select->return_after_parsing();
+ lex->nest_level--;
+ lex->current_select->n_child_sum_items += child->n_sum_items;
+ /*
+ A subselect can add fields to an outer select. Reserve space for
+ them.
+ */
+ lex->current_select->select_n_where_fields+=
+ child->select_n_where_fields;
+
+ /*
+ Aggregate functions in having clause may add fields to an outer
+ select. Count them also.
+ */
+ lex->current_select->select_n_having_items+=
+ child->select_n_having_items;
+ }
+ ;
+
+opt_query_expression_options:
+ /* empty */
+ | query_expression_option_list
+ ;
+
+query_expression_option_list:
+ query_expression_option_list query_expression_option
+ | query_expression_option
+ ;
+
+query_expression_option:
+ STRAIGHT_JOIN { Select->options|= SELECT_STRAIGHT_JOIN; }
+ | HIGH_PRIORITY
+ {
+ if (check_simple_select())
+ MYSQL_YYABORT;
+ YYPS->m_lock_type= TL_READ_HIGH_PRIORITY;
+ YYPS->m_mdl_type= MDL_SHARED_READ;
+ Select->options|= SELECT_HIGH_PRIORITY;
+ }
+ | DISTINCT { Select->options|= SELECT_DISTINCT; }
+ | SQL_SMALL_RESULT { Select->options|= SELECT_SMALL_RESULT; }
+ | SQL_BIG_RESULT { Select->options|= SELECT_BIG_RESULT; }
+ | SQL_BUFFER_RESULT
+ {
+ if (check_simple_select())
+ MYSQL_YYABORT;
+ Select->options|= OPTION_BUFFER_RESULT;
+ }
+ | SQL_CALC_FOUND_ROWS
+ {
+ if (check_simple_select())
+ MYSQL_YYABORT;
+ Select->options|= OPTION_FOUND_ROWS;
+ }
+ | ALL { Select->options|= SELECT_ALL; }
+ ;
+
+/**************************************************************************
+
+ CREATE VIEW | TRIGGER | PROCEDURE statements.
+
+**************************************************************************/
+
+view_or_trigger_or_sp_or_event:
+ definer definer_tail
+ {}
+ | no_definer no_definer_tail
+ {}
+ | view_algorithm definer_opt view_tail
+ {}
+ ;
+
+definer_tail:
+ view_tail
+ | trigger_tail
+ | sp_tail
+ | sf_tail
+ | event_tail
+ ;
+
+no_definer_tail:
+ view_tail
+ | trigger_tail
+ | sp_tail
+ | sf_tail
+ | udf_tail
+ | event_tail
+ ;
+
+/**************************************************************************
+
+ DEFINER clause support.
+
+**************************************************************************/
+
+definer_opt:
+ no_definer
+ | definer
+ ;
+
+no_definer:
+ /* empty */
+ {
+ /*
+ We have to distinguish missing DEFINER-clause from case when
+ CURRENT_USER specified as definer explicitly in order to properly
+ handle CREATE TRIGGER statements which come to replication thread
+ from older master servers (i.e. to create non-suid trigger in this
+ case).
+ */
+ thd->lex->definer= 0;
+ }
+ ;
+
+definer:
+ DEFINER_SYM EQ user_or_role
+ {
+ thd->lex->definer= $3;
+ }
+ ;
+
+/**************************************************************************
+
+ CREATE VIEW statement parts.
+
+**************************************************************************/
+
+view_algorithm:
+ ALGORITHM_SYM EQ UNDEFINED_SYM
+ { Lex->create_view_algorithm= DTYPE_ALGORITHM_UNDEFINED; }
+ | ALGORITHM_SYM EQ MERGE_SYM
+ { Lex->create_view_algorithm= VIEW_ALGORITHM_MERGE; }
+ | ALGORITHM_SYM EQ TEMPTABLE_SYM
+ { Lex->create_view_algorithm= VIEW_ALGORITHM_TMPTABLE; }
+ ;
+
+view_suid:
+ /* empty */
+ { Lex->create_view_suid= VIEW_SUID_DEFAULT; }
+ | SQL_SYM SECURITY_SYM DEFINER_SYM
+ { Lex->create_view_suid= VIEW_SUID_DEFINER; }
+ | SQL_SYM SECURITY_SYM INVOKER_SYM
+ { Lex->create_view_suid= VIEW_SUID_INVOKER; }
+ ;
+
+view_tail:
+ view_suid VIEW_SYM table_ident
+ {
+ LEX *lex= thd->lex;
+ lex->sql_command= SQLCOM_CREATE_VIEW;
+ /* first table in list is target VIEW name */
+ if (!lex->select_lex.add_table_to_list(thd, $3, NULL,
+ TL_OPTION_UPDATING,
+ TL_IGNORE,
+ MDL_EXCLUSIVE))
+ MYSQL_YYABORT;
+ lex->query_tables->open_strategy= TABLE_LIST::OPEN_STUB;
+ }
+ view_list_opt AS view_select
+ ;
+
+view_list_opt:
+ /* empty */
+ {}
+ | '(' view_list ')'
+ ;
+
+view_list:
+ ident
+ {
+ Lex->view_list.push_back((LEX_STRING*)
+ sql_memdup(&$1, sizeof(LEX_STRING)));
+ }
+ | view_list ',' ident
+ {
+ Lex->view_list.push_back((LEX_STRING*)
+ sql_memdup(&$3, sizeof(LEX_STRING)));
+ }
+ ;
+
+view_select:
+ {
+ LEX *lex= Lex;
+ lex->parsing_options.allows_variable= FALSE;
+ lex->parsing_options.allows_select_into= FALSE;
+ lex->parsing_options.allows_select_procedure= FALSE;
+ lex->parsing_options.allows_derived= FALSE;
+ lex->create_view_select.str= (char *) YYLIP->get_cpp_ptr();
+ }
+ view_select_aux view_check_option
+ {
+ LEX *lex= Lex;
+ uint len= YYLIP->get_cpp_ptr() - lex->create_view_select.str;
+ void *create_view_select= thd->memdup(lex->create_view_select.str, len);
+ lex->create_view_select.length= len;
+ lex->create_view_select.str= (char *) create_view_select;
+ trim_whitespace(thd->charset(), &lex->create_view_select);
+ lex->parsing_options.allows_variable= TRUE;
+ lex->parsing_options.allows_select_into= TRUE;
+ lex->parsing_options.allows_select_procedure= TRUE;
+ lex->parsing_options.allows_derived= TRUE;
+ }
+ ;
+
+view_select_aux:
+ SELECT_SYM select_init2
+ | '(' select_paren ')' union_opt
+ ;
+
+view_check_option:
+ /* empty */
+ { Lex->create_view_check= VIEW_CHECK_NONE; }
+ | WITH CHECK_SYM OPTION
+ { Lex->create_view_check= VIEW_CHECK_CASCADED; }
+ | WITH CASCADED CHECK_SYM OPTION
+ { Lex->create_view_check= VIEW_CHECK_CASCADED; }
+ | WITH LOCAL_SYM CHECK_SYM OPTION
+ { Lex->create_view_check= VIEW_CHECK_LOCAL; }
+ ;
+
+/**************************************************************************
+
+ CREATE TRIGGER statement parts.
+
+**************************************************************************/
+
+trigger_tail:
+ TRIGGER_SYM
+ remember_name
+ sp_name
+ trg_action_time
+ trg_event
+ ON
+ remember_name /* $7 */
+ { /* $8 */
+ Lex->raw_trg_on_table_name_begin= YYLIP->get_tok_start();
+ }
+ table_ident /* $9 */
+ FOR_SYM
+ remember_name /* $11 */
+ { /* $12 */
+ Lex->raw_trg_on_table_name_end= YYLIP->get_tok_start();
+ }
+ EACH_SYM
+ ROW_SYM
+ { /* $15 */
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+ sp_head *sp;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "TRIGGER");
+ MYSQL_YYABORT;
+ }
+
+ if (!(sp= new sp_head()))
+ MYSQL_YYABORT;
+ sp->reset_thd_mem_root(thd);
+ sp->init(lex);
+ sp->m_type= TYPE_ENUM_TRIGGER;
+ sp->init_sp_name(thd, $3);
+ lex->stmt_definition_begin= $2;
+ lex->ident.str= $7;
+ lex->ident.length= $11 - $7;
+
+ lex->sphead= sp;
+ lex->spname= $3;
+
+ bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
+ lex->sphead->m_chistics= &lex->sp_chistics;
+ lex->sphead->set_body_start(thd, lip->get_cpp_ptr());
+ }
+ sp_proc_stmt /* $16 */
+ { /* $17 */
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+
+ lex->sql_command= SQLCOM_CREATE_TRIGGER;
+ sp->set_stmt_end(thd);
+ sp->restore_thd_mem_root(thd);
+
+ if (sp->is_not_allowed_in_function("trigger"))
+ MYSQL_YYABORT;
+
+ /*
+ We have to do it after parsing trigger body, because some of
+ sp_proc_stmt alternatives are not saving/restoring LEX, so
+ lex->query_tables can be wiped out.
+ */
+ if (!lex->select_lex.add_table_to_list(thd, $9,
+ (LEX_STRING*) 0,
+ TL_OPTION_UPDATING,
+ TL_READ_NO_INSERT,
+ MDL_SHARED_NO_WRITE))
+ MYSQL_YYABORT;
+ }
+ ;
+
+/**************************************************************************
+
+ CREATE FUNCTION | PROCEDURE statements parts.
+
+**************************************************************************/
+
+udf_tail:
+ AGGREGATE_SYM remember_name FUNCTION_SYM ident
+ RETURNS_SYM udf_type SONAME_SYM TEXT_STRING_sys
+ {
+ LEX *lex= thd->lex;
+ if (is_native_function(thd, & $4))
+ {
+ my_error(ER_NATIVE_FCT_NAME_COLLISION, MYF(0),
+ $4.str);
+ MYSQL_YYABORT;
+ }
+ lex->sql_command = SQLCOM_CREATE_FUNCTION;
+ lex->udf.type= UDFTYPE_AGGREGATE;
+ lex->stmt_definition_begin= $2;
+ lex->udf.name = $4;
+ lex->udf.returns=(Item_result) $6;
+ lex->udf.dl=$8.str;
+ }
+ | remember_name FUNCTION_SYM ident
+ RETURNS_SYM udf_type SONAME_SYM TEXT_STRING_sys
+ {
+ LEX *lex= thd->lex;
+ if (is_native_function(thd, & $3))
+ {
+ my_error(ER_NATIVE_FCT_NAME_COLLISION, MYF(0),
+ $3.str);
+ MYSQL_YYABORT;
+ }
+ lex->sql_command = SQLCOM_CREATE_FUNCTION;
+ lex->udf.type= UDFTYPE_FUNCTION;
+ lex->stmt_definition_begin= $1;
+ lex->udf.name = $3;
+ lex->udf.returns=(Item_result) $5;
+ lex->udf.dl=$7.str;
+ }
+ ;
+
+sf_tail:
+ remember_name /* $1 */
+ FUNCTION_SYM /* $2 */
+ sp_name /* $3 */
+ '(' /* $4 */
+ { /* $5 */
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+ sp_head *sp;
+ const char* tmp_param_begin;
+
+ lex->stmt_definition_begin= $1;
+ lex->spname= $3;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "FUNCTION");
+ MYSQL_YYABORT;
+ }
+ /* Order is important here: new - reset - init */
+ sp= new sp_head();
+ if (sp == NULL)
+ MYSQL_YYABORT;
+ sp->reset_thd_mem_root(thd);
+ sp->init(lex);
+ sp->init_sp_name(thd, lex->spname);
+
+ sp->m_type= TYPE_ENUM_FUNCTION;
+ lex->sphead= sp;
+
+ tmp_param_begin= lip->get_cpp_tok_start();
+ tmp_param_begin++;
+ lex->sphead->m_param_begin= tmp_param_begin;
+ }
+ sp_fdparam_list /* $6 */
+ ')' /* $7 */
+ { /* $8 */
+ Lex->sphead->m_param_end= YYLIP->get_cpp_tok_start();
+ }
+ RETURNS_SYM /* $9 */
+ { /* $10 */
+ LEX *lex= Lex;
+ lex->charset= NULL;
+ lex->length= lex->dec= NULL;
+ lex->interval_list.empty();
+ lex->type= 0;
+ lex->vcol_info= 0;
+ }
+ type_with_opt_collate /* $11 */
+ { /* $12 */
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+ /*
+ This was disabled in 5.1.12. See bug #20701
+ When collation support in SP is implemented, then this test
+ should be removed.
+ */
+ if (($11 == MYSQL_TYPE_STRING || $11 == MYSQL_TYPE_VARCHAR)
+ && (lex->type & BINCMP_FLAG))
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "return value collation");
+ MYSQL_YYABORT;
+ }
+
+ if (sp->fill_field_definition(thd, lex,
+ (enum enum_field_types) $11,
+ &sp->m_return_field_def))
+ MYSQL_YYABORT;
+
+ bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
+ }
+ sp_c_chistics /* $13 */
+ { /* $14 */
+ LEX *lex= thd->lex;
+ Lex_input_stream *lip= YYLIP;
+
+ lex->sphead->m_chistics= &lex->sp_chistics;
+ lex->sphead->set_body_start(thd, lip->get_cpp_tok_start());
+ }
+ sp_proc_stmt /* $15 */
+ {
+ LEX *lex= thd->lex;
+ sp_head *sp= lex->sphead;
+
+ if (sp->is_not_allowed_in_function("function"))
+ MYSQL_YYABORT;
+
+ lex->sql_command= SQLCOM_CREATE_SPFUNCTION;
+ sp->set_stmt_end(thd);
+ if (!(sp->m_flags & sp_head::HAS_RETURN))
+ {
+ my_error(ER_SP_NORETURN, MYF(0), sp->m_qname.str);
+ MYSQL_YYABORT;
+ }
+ if (is_native_function(thd, & sp->m_name))
+ {
+ /*
+ This warning will be printed when
+ [1] A client query is parsed,
+ [2] A stored function is loaded by db_load_routine.
+ Printing the warning for [2] is intentional, to cover the
+ following scenario:
+ - A user define a SF 'foo' using MySQL 5.N
+ - An application uses select foo(), and works.
+ - MySQL 5.{N+1} defines a new native function 'foo', as
+ part of a new feature.
+ - MySQL 5.{N+1} documentation is updated, and should mention
+ that there is a potential incompatible change in case of
+ existing stored function named 'foo'.
+ - The user deploys 5.{N+1}. At this point, 'select foo()'
+ means something different, and the user code is most likely
+ broken (it's only safe if the code is 'select db.foo()').
+ With a warning printed when the SF is loaded (which has to occur
+ before the call), the warning will provide a hint explaining
+ the root cause of a later failure of 'select foo()'.
+ With no warning printed, the user code will fail with no
+ apparent reason.
+ Printing a warning each time db_load_routine is executed for
+ an ambiguous function is annoying, since that can happen a lot,
+ but in practice should not happen unless there *are* name
+ collisions.
+ If a collision exists, it should not be silenced but fixed.
+ */
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_NOTE,
+ ER_NATIVE_FCT_NAME_COLLISION,
+ ER(ER_NATIVE_FCT_NAME_COLLISION),
+ sp->m_name.str);
+ }
+ sp->restore_thd_mem_root(thd);
+ }
+ ;
+
+sp_tail:
+ PROCEDURE_SYM remember_name sp_name
+ {
+ LEX *lex= Lex;
+ sp_head *sp;
+
+ if (lex->sphead)
+ {
+ my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "PROCEDURE");
+ MYSQL_YYABORT;
+ }
+
+ lex->stmt_definition_begin= $2;
+
+ /* Order is important here: new - reset - init */
+ sp= new sp_head();
+ if (sp == NULL)
+ MYSQL_YYABORT;
+ sp->reset_thd_mem_root(thd);
+ sp->init(lex);
+ sp->m_type= TYPE_ENUM_PROCEDURE;
+ sp->init_sp_name(thd, $3);
+
+ lex->sphead= sp;
+ }
+ '('
+ {
+ const char* tmp_param_begin;
+
+ tmp_param_begin= YYLIP->get_cpp_tok_start();
+ tmp_param_begin++;
+ Lex->sphead->m_param_begin= tmp_param_begin;
+ }
+ sp_pdparam_list
+ ')'
+ {
+ LEX *lex= thd->lex;
+
+ lex->sphead->m_param_end= YYLIP->get_cpp_tok_start();
+ bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
+ }
+ sp_c_chistics
+ {
+ LEX *lex= thd->lex;
+
+ lex->sphead->m_chistics= &lex->sp_chistics;
+ lex->sphead->set_body_start(thd, YYLIP->get_cpp_tok_start());
+ }
+ sp_proc_stmt
+ {
+ LEX *lex= Lex;
+ sp_head *sp= lex->sphead;
+
+ sp->set_stmt_end(thd);
+ lex->sql_command= SQLCOM_CREATE_PROCEDURE;
+ sp->restore_thd_mem_root(thd);
+ }
+ ;
+
+/*************************************************************************/
+
+xa:
+ XA_SYM begin_or_start xid opt_join_or_resume
+ {
+ Lex->sql_command = SQLCOM_XA_START;
+ }
+ | XA_SYM END xid opt_suspend
+ {
+ Lex->sql_command = SQLCOM_XA_END;
+ }
+ | XA_SYM PREPARE_SYM xid
+ {
+ Lex->sql_command = SQLCOM_XA_PREPARE;
+ }
+ | XA_SYM COMMIT_SYM xid opt_one_phase
+ {
+ Lex->sql_command = SQLCOM_XA_COMMIT;
+ }
+ | XA_SYM ROLLBACK_SYM xid
+ {
+ Lex->sql_command = SQLCOM_XA_ROLLBACK;
+ }
+ | XA_SYM RECOVER_SYM
+ {
+ Lex->sql_command = SQLCOM_XA_RECOVER;
+ }
+ ;
+
+xid:
+ text_string
+ {
+ MYSQL_YYABORT_UNLESS($1->length() <= MAXGTRIDSIZE);
+ if (!(Lex->xid=(XID *)thd->alloc(sizeof(XID))))
+ MYSQL_YYABORT;
+ Lex->xid->set(1L, $1->ptr(), $1->length(), 0, 0);
+ }
+ | text_string ',' text_string
+ {
+ MYSQL_YYABORT_UNLESS($1->length() <= MAXGTRIDSIZE && $3->length() <= MAXBQUALSIZE);
+ if (!(Lex->xid=(XID *)thd->alloc(sizeof(XID))))
+ MYSQL_YYABORT;
+ Lex->xid->set(1L, $1->ptr(), $1->length(), $3->ptr(), $3->length());
+ }
+ | text_string ',' text_string ',' ulong_num
+ {
+ MYSQL_YYABORT_UNLESS($1->length() <= MAXGTRIDSIZE && $3->length() <= MAXBQUALSIZE);
+ if (!(Lex->xid=(XID *)thd->alloc(sizeof(XID))))
+ MYSQL_YYABORT;
+ Lex->xid->set($5, $1->ptr(), $1->length(), $3->ptr(), $3->length());
+ }
+ ;
+
+begin_or_start:
+ BEGIN_SYM {}
+ | START_SYM {}
+ ;
+
+opt_join_or_resume:
+ /* nothing */ { Lex->xa_opt=XA_NONE; }
+ | JOIN_SYM { Lex->xa_opt=XA_JOIN; }
+ | RESUME_SYM { Lex->xa_opt=XA_RESUME; }
+ ;
+
+opt_one_phase:
+ /* nothing */ { Lex->xa_opt=XA_NONE; }
+ | ONE_SYM PHASE_SYM { Lex->xa_opt=XA_ONE_PHASE; }
+ ;
+
+opt_suspend:
+ /* nothing */
+ { Lex->xa_opt=XA_NONE; }
+ | SUSPEND_SYM
+ { Lex->xa_opt=XA_SUSPEND; }
+ opt_migrate
+ ;
+
+opt_migrate:
+ /* nothing */ {}
+ | FOR_SYM MIGRATE_SYM { Lex->xa_opt=XA_FOR_MIGRATE; }
+ ;
+
+install:
+ INSTALL_SYM PLUGIN_SYM ident SONAME_SYM TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_INSTALL_PLUGIN;
+ lex->comment= $3;
+ lex->ident= $5;
+ }
+ | INSTALL_SYM SONAME_SYM TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_INSTALL_PLUGIN;
+ lex->comment= null_lex_str;
+ lex->ident= $3;
+ }
+ ;
+
+uninstall:
+ UNINSTALL_SYM PLUGIN_SYM ident
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_UNINSTALL_PLUGIN;
+ lex->comment= $3;
+ }
+ | UNINSTALL_SYM SONAME_SYM TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_UNINSTALL_PLUGIN;
+ lex->comment= null_lex_str;
+ lex->ident= $3;
+ }
+ ;
+
+/* Avoid compiler warning from sql_yacc.cc where yyerrlab1 is not used */
+keep_gcc_happy:
+ IMPOSSIBLE_ACTION
+ {
+ YYERROR;
+ }
+
+/**
+ @} (end of group Parser)
+*/
diff --git a/sql/strfunc.cc b/sql/strfunc.cc
new file mode 100644
index 00000000000..b8100e05ce5
--- /dev/null
+++ b/sql/strfunc.cc
@@ -0,0 +1,402 @@
+/* Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+/* Some useful string utility functions used by the MySQL server */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "strfunc.h"
+#include "sql_class.h"
+#include "typelib.h" // TYPELIB
+#include "m_ctype.h" // my_charset_latin1
+#include "mysqld.h" // system_charset_info
+
+/*
+ Return bitmap for strings used in a set
+
+ SYNOPSIS
+ find_set()
+ lib Strings in set
+ str Strings of set-strings separated by ','
+ err_pos If error, set to point to start of wrong set string
+ err_len If error, set to the length of wrong set string
+ set_warning Set to 1 if some string in set couldn't be used
+
+ NOTE
+ We delete all end space from str before comparison
+
+ RETURN
+ bitmap of all sets found in x.
+ set_warning is set to 1 if there was any sets that couldn't be set
+*/
+
+static const char field_separator=',';
+
+ulonglong find_set(TYPELIB *lib, const char *str, uint length, CHARSET_INFO *cs,
+ char **err_pos, uint *err_len, bool *set_warning)
+{
+ CHARSET_INFO *strip= cs ? cs : &my_charset_latin1;
+ const char *end= str + strip->cset->lengthsp(strip, str, length);
+ ulonglong found= 0;
+ *err_pos= 0; // No error yet
+ *err_len= 0;
+ if (str != end)
+ {
+ const char *start= str;
+ for (;;)
+ {
+ const char *pos= start;
+ uint var_len;
+ int mblen= 1;
+
+ if (cs && cs->mbminlen > 1)
+ {
+ for ( ; pos < end; pos+= mblen)
+ {
+ my_wc_t wc;
+ if ((mblen= cs->cset->mb_wc(cs, &wc, (const uchar *) pos,
+ (const uchar *) end)) < 1)
+ mblen= 1; // Not to hang on a wrong multibyte sequence
+ if (wc == (my_wc_t) field_separator)
+ break;
+ }
+ }
+ else
+ for (; pos != end && *pos != field_separator; pos++) ;
+ var_len= (uint) (pos - start);
+ uint find= cs ? find_type2(lib, start, var_len, cs) :
+ find_type(lib, start, var_len, (bool) 0);
+ if (!find && *err_len == 0) // report the first error with length > 0
+ {
+ *err_pos= (char*) start;
+ *err_len= var_len;
+ *set_warning= 1;
+ }
+ else
+ found|= 1ULL << (find - 1);
+ if (pos >= end)
+ break;
+ start= pos + mblen;
+ }
+ }
+ return found;
+}
+
+/*
+ Function to find a string in a TYPELIB
+ (similar to find_type() of mysys/typelib.c)
+
+ SYNOPSIS
+ find_type()
+ lib TYPELIB (struct of pointer to values + count)
+ find String to find
+ length Length of string to find
+ part_match Allow part matching of value
+
+ RETURN
+ 0 error
+ > 0 position in TYPELIB->type_names +1
+*/
+
+uint find_type(const TYPELIB *lib, const char *find, uint length,
+ bool part_match)
+{
+ uint found_count=0, found_pos=0;
+ const char *end= find+length;
+ const char *i;
+ const char *j;
+ for (uint pos=0 ; (j=lib->type_names[pos++]) ; )
+ {
+ for (i=find ; i != end &&
+ my_toupper(system_charset_info,*i) ==
+ my_toupper(system_charset_info,*j) ; i++, j++) ;
+ if (i == end)
+ {
+ if (! *j)
+ return(pos);
+ found_count++;
+ found_pos= pos;
+ }
+ }
+ return(found_count == 1 && part_match ? found_pos : 0);
+}
+
+
+/*
+ Find a string in a list of strings according to collation
+
+ SYNOPSIS
+ find_type2()
+ lib TYPELIB (struct of pointer to values + count)
+ x String to find
+ length String length
+ cs Character set + collation to use for comparison
+
+ NOTES
+
+ RETURN
+ 0 No matching value
+ >0 Offset+1 in typelib for matched string
+*/
+
+uint find_type2(const TYPELIB *typelib, const char *x, uint length,
+ CHARSET_INFO *cs)
+{
+ int pos;
+ const char *j;
+ DBUG_ENTER("find_type2");
+ DBUG_PRINT("enter",("x: '%.*s' lib: 0x%lx", length, x, (long) typelib));
+
+ if (!typelib->count)
+ {
+ DBUG_PRINT("exit",("no count"));
+ DBUG_RETURN(0);
+ }
+
+ for (pos=0 ; (j=typelib->type_names[pos]) ; pos++)
+ {
+ if (!my_strnncoll(cs, (const uchar*) x, length,
+ (const uchar*) j, typelib->type_lengths[pos]))
+ DBUG_RETURN(pos+1);
+ }
+ DBUG_PRINT("exit",("Couldn't find type"));
+ DBUG_RETURN(0);
+} /* find_type */
+
+
+/*
+ Un-hex all elements in a typelib
+
+ SYNOPSIS
+ unhex_type2()
+ interval TYPELIB (struct of pointer to values + lengths + count)
+
+ NOTES
+
+ RETURN
+ N/A
+*/
+
+void unhex_type2(TYPELIB *interval)
+{
+ for (uint pos= 0; pos < interval->count; pos++)
+ {
+ char *from, *to;
+ for (from= to= (char*) interval->type_names[pos]; *from; )
+ {
+ /*
+ Note, hexchar_to_int(*from++) doesn't work
+ one some compilers, e.g. IRIX. Looks like a compiler
+ bug in inline functions in combination with arguments
+ that have a side effect. So, let's use from[0] and from[1]
+ and increment 'from' by two later.
+ */
+
+ *to++= (char) (hexchar_to_int(from[0]) << 4) +
+ hexchar_to_int(from[1]);
+ from+= 2;
+ }
+ interval->type_lengths[pos] /= 2;
+ }
+}
+
+
+/*
+ Check if the first word in a string is one of the ones in TYPELIB
+
+ SYNOPSIS
+ check_word()
+ lib TYPELIB
+ val String to check
+ end End of input
+ end_of_word Store value of last used byte here if we found word
+
+ RETURN
+ 0 No matching value
+ > 1 lib->type_names[#-1] matched
+ end_of_word will point to separator character/end in 'val'
+*/
+
+uint check_word(TYPELIB *lib, const char *val, const char *end,
+ const char **end_of_word)
+{
+ int res;
+ const char *ptr;
+
+ /* Fiend end of word */
+ for (ptr= val ; ptr < end && my_isalpha(&my_charset_latin1, *ptr) ; ptr++)
+ ;
+ if ((res=find_type(lib, val, (uint) (ptr - val), 1)) > 0)
+ *end_of_word= ptr;
+ return res;
+}
+
+
+/*
+ Converts a string between character sets
+
+ SYNOPSIS
+ strconvert()
+ from_cs source character set
+ from source, a null terminated string
+ to destination buffer
+ to_length destination buffer length
+
+ NOTES
+ 'to' is always terminated with a '\0' character.
+ If there is no enough space to convert whole string,
+ only prefix is converted, and terminated with '\0'.
+
+ RETURN VALUES
+ result string length
+*/
+
+
+uint strconvert(CHARSET_INFO *from_cs, const char *from, uint from_length,
+ CHARSET_INFO *to_cs, char *to, uint to_length, uint *errors)
+{
+ int cnvres;
+ my_wc_t wc;
+ char *to_start= to;
+ uchar *to_end= (uchar*) to + to_length - 1;
+ const uchar *from_end= (const uchar*) from + from_length;
+ my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc;
+ my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb;
+ uint error_count= 0;
+
+ while (1)
+ {
+ if ((cnvres= (*mb_wc)(from_cs, &wc,
+ (uchar*) from, from_end)) > 0)
+ {
+ if (!wc)
+ break;
+ from+= cnvres;
+ }
+ else if (cnvres == MY_CS_ILSEQ)
+ {
+ error_count++;
+ from++;
+ wc= '?';
+ }
+ else
+ break; // Impossible char.
+
+outp:
+
+ if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0)
+ to+= cnvres;
+ else if (cnvres == MY_CS_ILUNI && wc != '?')
+ {
+ error_count++;
+ wc= '?';
+ goto outp;
+ }
+ else
+ break;
+ }
+ *to= '\0';
+ *errors= error_count;
+ return (uint32) (to - to_start);
+
+}
+
+
+/*
+ Searches for a LEX_STRING in an LEX_STRING array.
+
+ SYNOPSIS
+ find_string_in_array()
+ heap The array
+ needle The string to search for
+
+ NOTE
+ The last LEX_STRING in the array should have str member set to NULL
+
+ RETURN VALUES
+ -1 Not found
+ >=0 Ordinal position
+*/
+
+int find_string_in_array(LEX_STRING * const haystack, LEX_STRING * const needle,
+ CHARSET_INFO * const cs)
+{
+ const LEX_STRING *pos;
+ for (pos= haystack; pos->str; pos++)
+ if (!cs->coll->strnncollsp(cs, (uchar *) pos->str, pos->length,
+ (uchar *) needle->str, needle->length, 0))
+ {
+ return (pos - haystack);
+ }
+ return -1;
+}
+
+
+char *set_to_string(THD *thd, LEX_STRING *result, ulonglong set,
+ const char *lib[])
+{
+ char buff[STRING_BUFFER_USUAL_SIZE*8];
+ String tmp(buff, sizeof(buff), &my_charset_latin1);
+ LEX_STRING unused;
+
+ if (!result)
+ result= &unused;
+
+ tmp.length(0);
+
+ for (uint i= 0; set; i++, set >>= 1)
+ if (set & 1) {
+ tmp.append(lib[i]);
+ tmp.append(',');
+ }
+
+ if (tmp.length())
+ {
+ result->str= thd->strmake(tmp.ptr(), tmp.length()-1);
+ result->length= tmp.length()-1;
+ }
+ else
+ {
+ result->str= const_cast<char*>("");
+ result->length= 0;
+ }
+ return result->str;
+}
+
+char *flagset_to_string(THD *thd, LEX_STRING *result, ulonglong set,
+ const char *lib[])
+{
+ char buff[STRING_BUFFER_USUAL_SIZE*8];
+ String tmp(buff, sizeof(buff), &my_charset_latin1);
+ LEX_STRING unused;
+
+ if (!result) result= &unused;
+
+ tmp.length(0);
+
+ // note that the last element is always "default", and it's ignored below
+ for (uint i= 0; lib[i+1]; i++, set >>= 1)
+ {
+ tmp.append(lib[i]);
+ tmp.append(set & 1 ? "=on," : "=off,");
+ }
+
+ result->str= thd->strmake(tmp.ptr(), tmp.length()-1);
+ result->length= tmp.length()-1;
+
+ return result->str;
+}
+
diff --git a/sql/strfunc.h b/sql/strfunc.h
new file mode 100644
index 00000000000..7b031710c76
--- /dev/null
+++ b/sql/strfunc.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef STRFUNC_INCLUDED
+#define STRFUNC_INCLUDED
+
+#include "my_global.h" /* ulonglong, uint */
+
+typedef struct st_typelib TYPELIB;
+
+ulonglong find_set(TYPELIB *lib, const char *x, uint length, CHARSET_INFO *cs,
+ char **err_pos, uint *err_len, bool *set_warning);
+ulonglong find_set_from_flags(TYPELIB *lib, uint default_name,
+ ulonglong cur_set, ulonglong default_set,
+ const char *str, uint length, CHARSET_INFO *cs,
+ char **err_pos, uint *err_len, bool *set_warning);
+uint find_type(const TYPELIB *lib, const char *find, uint length,
+ bool part_match);
+uint find_type2(const TYPELIB *lib, const char *find, uint length,
+ CHARSET_INFO *cs);
+void unhex_type2(TYPELIB *lib);
+uint check_word(TYPELIB *lib, const char *val, const char *end,
+ const char **end_of_word);
+int find_string_in_array(LEX_STRING * const haystack, LEX_STRING * const needle,
+ CHARSET_INFO * const cs);
+char *flagset_to_string(THD *thd, LEX_STRING *result, ulonglong set,
+ const char *lib[]);
+char *set_to_string(THD *thd, LEX_STRING *result, ulonglong set,
+ const char *lib[]);
+
+/*
+ These functions were protected by INNODB_COMPATIBILITY_HOOKS
+ */
+uint strconvert(CHARSET_INFO *from_cs, const char *from, uint from_length,
+ CHARSET_INFO *to_cs, char *to, uint to_length, uint *errors);
+
+#endif /* STRFUNC_INCLUDED */
diff --git a/sql/structs.h b/sql/structs.h
new file mode 100644
index 00000000000..ee61b8d3b3a
--- /dev/null
+++ b/sql/structs.h
@@ -0,0 +1,521 @@
+#ifndef STRUCTS_INCLUDED
+#define STRUCTS_INCLUDED
+
+/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+
+/* The old structures from unireg */
+
+#include "sql_plugin.h" /* plugin_ref */
+#include "sql_const.h" /* MAX_REFLENGTH */
+#include "my_time.h" /* enum_mysql_timestamp_type */
+#include "thr_lock.h" /* thr_lock_type */
+#include "my_base.h" /* ha_rows, ha_key_alg */
+#include <mysql_com.h> /* USERNAME_LENGTH */
+
+struct TABLE;
+class Field;
+class Index_statistics;
+
+class THD;
+
+typedef struct st_date_time_format {
+ uchar positions[8];
+ char time_separator; /* Separator between hour and minute */
+ uint flag; /* For future */
+ LEX_STRING format;
+} DATE_TIME_FORMAT;
+
+
+typedef struct st_keyfile_info { /* used with ha_info() */
+ uchar ref[MAX_REFLENGTH]; /* Pointer to current row */
+ uchar dupp_ref[MAX_REFLENGTH]; /* Pointer to dupp row */
+ uint ref_length; /* Length of ref (1-8) */
+ uint block_size; /* index block size */
+ File filenr; /* (uniq) filenr for table */
+ ha_rows records; /* Records i datafilen */
+ ha_rows deleted; /* Deleted records */
+ ulonglong data_file_length; /* Length off data file */
+ ulonglong max_data_file_length; /* Length off data file */
+ ulonglong index_file_length;
+ ulonglong max_index_file_length;
+ ulonglong delete_length; /* Free bytes */
+ ulonglong auto_increment_value;
+ int errkey,sortkey; /* Last errorkey and sorted by */
+ time_t create_time; /* When table was created */
+ time_t check_time;
+ time_t update_time;
+ ulong mean_rec_length; /* physical reclength */
+} KEYFILE_INFO;
+
+
+typedef struct st_key_part_info { /* Info about a key part */
+ Field *field;
+ uint offset; /* offset in record (from 0) */
+ uint null_offset; /* Offset to null_bit in record */
+ /* Length of key part in bytes, excluding NULL flag and length bytes */
+ uint16 length;
+ /*
+ Number of bytes required to store the keypart value. This may be
+ different from the "length" field as it also counts
+ - possible NULL-flag byte (see HA_KEY_NULL_LENGTH)
+ - possible HA_KEY_BLOB_LENGTH bytes needed to store actual value length.
+ */
+ uint16 store_length;
+ uint16 key_type;
+ /* Fieldnr begins counting from 1 */
+ uint16 fieldnr; /* Fieldnum in UNIREG */
+ uint16 key_part_flag; /* 0 or HA_REVERSE_SORT */
+ uint8 type;
+ uint8 null_bit; /* Position to null_bit */
+} KEY_PART_INFO ;
+
+class engine_option_value;
+struct ha_index_option_struct;
+
+typedef struct st_key {
+ uint key_length; /* Tot length of key */
+ ulong flags; /* dupp key and pack flags */
+ uint user_defined_key_parts; /* How many key_parts */
+ uint usable_key_parts; /* Should normally be = user_defined_key_parts */
+ uint ext_key_parts; /* Number of key parts in extended key */
+ ulong ext_key_flags; /* Flags for extended key */
+ key_part_map ext_key_part_map; /* Bitmap of pk key parts in extension */
+ uint block_size;
+ uint name_length;
+ enum ha_key_alg algorithm;
+ /*
+ The flag is on if statistical data for the index prefixes
+ has to be taken from the system statistical tables.
+ */
+ bool is_statistics_from_stat_tables;
+ /*
+ Note that parser is used when the table is opened for use, and
+ parser_name is used when the table is being created.
+ */
+ union
+ {
+ plugin_ref parser; /* Fulltext [pre]parser */
+ LEX_STRING *parser_name; /* Fulltext [pre]parser name */
+ };
+ KEY_PART_INFO *key_part;
+ char *name; /* Name of key */
+ /* Unique name for cache; db + \0 + table_name + \0 + key_name + \0 */
+ uchar *cache_name;
+ /*
+ Array of AVG(#records with the same field value) for 1st ... Nth key part.
+ 0 means 'not known'.
+ For temporary heap tables this member is NULL.
+ */
+ ulong *rec_per_key;
+
+ /*
+ This structure is used for statistical data on the index
+ that has been read from the statistical table index_stat
+ */
+ Index_statistics *read_stats;
+ /*
+ This structure is used for statistical data on the index that
+ is collected by the function collect_statistics_for_table
+ */
+ Index_statistics *collected_stats;
+
+ union {
+ int bdb_return_if_eq;
+ } handler;
+ TABLE *table;
+ LEX_STRING comment;
+ /** reference to the list of options or NULL */
+ engine_option_value *option_list;
+ ha_index_option_struct *option_struct; /* structure with parsed options */
+
+ double actual_rec_per_key(uint i);
+
+} KEY;
+
+
+struct st_join_table;
+
+typedef struct st_reginfo { /* Extra info about reg */
+ struct st_join_table *join_tab; /* Used by SELECT() */
+ enum thr_lock_type lock_type; /* How database is used */
+ bool not_exists_optimize;
+ /*
+ TRUE <=> range optimizer found that there is no rows satisfying
+ table conditions.
+ */
+ bool impossible_range;
+} REGINFO;
+
+
+/*
+ Originally MySQL used MYSQL_TIME structure inside server only, but since
+ 4.1 it's exported to user in the new client API. Define aliases for
+ new names to keep existing code simple.
+*/
+
+typedef enum enum_mysql_timestamp_type timestamp_type;
+
+
+typedef struct {
+ ulong year,month,day,hour;
+ ulonglong minute,second,second_part;
+ bool neg;
+} INTERVAL;
+
+
+typedef struct st_known_date_time_format {
+ const char *format_name;
+ const char *date_format;
+ const char *datetime_format;
+ const char *time_format;
+} KNOWN_DATE_TIME_FORMAT;
+
+extern const char *show_comp_option_name[];
+
+typedef int *(*update_var)(THD *, struct st_mysql_show_var *);
+
+typedef struct st_lex_user {
+ LEX_STRING user, host, password, plugin, auth;
+ bool is_role() { return user.str[0] && !host.str[0]; }
+ void set_lex_string(LEX_STRING *l, char *buf)
+ {
+ if (is_role())
+ *l= user;
+ else
+ l->length= strxmov(l->str= buf, user.str, "@", host.str, NullS) - buf;
+ }
+} LEX_USER;
+
+/*
+ This structure specifies the maximum amount of resources which
+ can be consumed by each account. Zero value of a member means
+ there is no limit.
+*/
+typedef struct user_resources {
+ /* Maximum number of queries/statements per hour. */
+ uint questions;
+ /*
+ Maximum number of updating statements per hour (which statements are
+ updating is defined by sql_command_flags array).
+ */
+ uint updates;
+ /* Maximum number of connections established per hour. */
+ uint conn_per_hour;
+ /*
+ Maximum number of concurrent connections. If -1 then no new
+ connections allowed
+ */
+ int user_conn;
+ /*
+ Values of this enum and specified_limits member are used by the
+ parser to store which user limits were specified in GRANT statement.
+ */
+ enum {QUERIES_PER_HOUR= 1, UPDATES_PER_HOUR= 2, CONNECTIONS_PER_HOUR= 4,
+ USER_CONNECTIONS= 8};
+ uint specified_limits;
+} USER_RESOURCES;
+
+
+/*
+ This structure is used for counting resources consumed and for checking
+ them against specified user limits.
+*/
+typedef struct user_conn {
+ /*
+ Pointer to user+host key (pair separated by '\0') defining the entity
+ for which resources are counted (By default it is user account thus
+ priv_user/priv_host pair is used. If --old-style-user-limits option
+ is enabled, resources are counted for each user+host separately).
+ */
+ char *user;
+ /* Pointer to host part of the key. */
+ char *host;
+ /**
+ The moment of time when per hour counters were reset last time
+ (i.e. start of "hour" for conn_per_hour, updates, questions counters).
+ */
+ ulonglong reset_utime;
+ /* Total length of the key. */
+ uint len;
+ /* Current amount of concurrent connections for this account. */
+ int connections;
+ /*
+ Current number of connections per hour, number of updating statements
+ per hour and total number of statements per hour for this account.
+ */
+ uint conn_per_hour, updates, questions;
+ /* Maximum amount of resources which account is allowed to consume. */
+ USER_RESOURCES user_resources;
+} USER_CONN;
+
+typedef struct st_user_stats
+{
+ char user[MY_MAX(USERNAME_LENGTH, LIST_PROCESS_HOST_LEN) + 1];
+ // Account name the user is mapped to when this is a user from mapped_user.
+ // Otherwise, the same value as user.
+ char priv_user[MY_MAX(USERNAME_LENGTH, LIST_PROCESS_HOST_LEN) + 1];
+ uint user_name_length;
+ uint total_connections;
+ uint concurrent_connections;
+ time_t connected_time; // in seconds
+ double busy_time; // in seconds
+ double cpu_time; // in seconds
+ ulonglong bytes_received;
+ ulonglong bytes_sent;
+ ulonglong binlog_bytes_written;
+ ha_rows rows_read, rows_sent;
+ ha_rows rows_updated, rows_deleted, rows_inserted;
+ ulonglong select_commands, update_commands, other_commands;
+ ulonglong commit_trans, rollback_trans;
+ ulonglong denied_connections, lost_connections;
+ ulonglong access_denied_errors;
+ ulonglong empty_queries;
+} USER_STATS;
+
+/* Lookup function for hash tables with USER_STATS entries */
+extern "C" uchar *get_key_user_stats(USER_STATS *user_stats, size_t *length,
+ my_bool not_used __attribute__((unused)));
+
+/* Free all memory for a hash table with USER_STATS entries */
+extern void free_user_stats(USER_STATS* user_stats);
+
+/* Intialize an instance of USER_STATS */
+extern void
+init_user_stats(USER_STATS *user_stats,
+ const char *user,
+ size_t user_length,
+ const char *priv_user,
+ uint total_connections,
+ uint concurrent_connections,
+ time_t connected_time,
+ double busy_time,
+ double cpu_time,
+ ulonglong bytes_received,
+ ulonglong bytes_sent,
+ ulonglong binlog_bytes_written,
+ ha_rows rows_sent,
+ ha_rows rows_read,
+ ha_rows rows_inserted,
+ ha_rows rows_deleted,
+ ha_rows rows_updated,
+ ulonglong select_commands,
+ ulonglong update_commands,
+ ulonglong other_commands,
+ ulonglong commit_trans,
+ ulonglong rollback_trans,
+ ulonglong denied_connections,
+ ulonglong lost_connections,
+ ulonglong access_denied_errors,
+ ulonglong empty_queries);
+
+/* Increment values of an instance of USER_STATS */
+extern void
+add_user_stats(USER_STATS *user_stats,
+ uint total_connections,
+ uint concurrent_connections,
+ time_t connected_time,
+ double busy_time,
+ double cpu_time,
+ ulonglong bytes_received,
+ ulonglong bytes_sent,
+ ulonglong binlog_bytes_written,
+ ha_rows rows_sent,
+ ha_rows rows_read,
+ ha_rows rows_inserted,
+ ha_rows rows_deleted,
+ ha_rows rows_updated,
+ ulonglong select_commands,
+ ulonglong update_commands,
+ ulonglong other_commands,
+ ulonglong commit_trans,
+ ulonglong rollback_trans,
+ ulonglong denied_connections,
+ ulonglong lost_connections,
+ ulonglong access_denied_errors,
+ ulonglong empty_queries);
+
+typedef struct st_table_stats
+{
+ char table[NAME_LEN * 2 + 2]; // [db] + '\0' + [table] + '\0'
+ uint table_name_length;
+ ulonglong rows_read, rows_changed;
+ ulonglong rows_changed_x_indexes;
+ /* Stores enum db_type, but forward declarations cannot be done */
+ int engine_type;
+} TABLE_STATS;
+
+typedef struct st_index_stats
+{
+ // [db] + '\0' + [table] + '\0' + [index] + '\0'
+ char index[NAME_LEN * 3 + 3];
+ uint index_name_length; /* Length of 'index' */
+ ulonglong rows_read;
+} INDEX_STATS;
+
+
+ /* Bits in form->update */
+#define REG_MAKE_DUPP 1 /* Make a copy of record when read */
+#define REG_NEW_RECORD 2 /* Write a new record if not found */
+#define REG_UPDATE 4 /* Uppdate record */
+#define REG_DELETE 8 /* Delete found record */
+#define REG_PROG 16 /* User is updating database */
+#define REG_CLEAR_AFTER_WRITE 32
+#define REG_MAY_BE_UPDATED 64
+#define REG_AUTO_UPDATE 64 /* Used in D-forms for scroll-tables */
+#define REG_OVERWRITE 128
+#define REG_SKIP_DUP 256
+
+ /* Bits in form->status */
+#define STATUS_NO_RECORD (1+2) /* Record isn't usably */
+#define STATUS_GARBAGE 1
+#define STATUS_NOT_FOUND 2 /* No record in database when needed */
+#define STATUS_NO_PARENT 4 /* Parent record wasn't found */
+#define STATUS_NOT_READ 8 /* Record isn't read */
+#define STATUS_UPDATED 16 /* Record is updated by formula */
+#define STATUS_NULL_ROW 32 /* table->null_row is set */
+#define STATUS_DELETED 64
+
+/*
+ Such interval is "discrete": it is the set of
+ { auto_inc_interval_min + k * increment,
+ 0 <= k <= (auto_inc_interval_values-1) }
+ Where "increment" is maintained separately by the user of this class (and is
+ currently only thd->variables.auto_increment_increment).
+ It mustn't derive from Sql_alloc, because SET INSERT_ID needs to
+ allocate memory which must stay allocated for use by the next statement.
+*/
+class Discrete_interval {
+private:
+ ulonglong interval_min;
+ ulonglong interval_values;
+ ulonglong interval_max; // excluded bound. Redundant.
+public:
+ Discrete_interval *next; // used when linked into Discrete_intervals_list
+ void replace(ulonglong start, ulonglong val, ulonglong incr)
+ {
+ interval_min= start;
+ interval_values= val;
+ interval_max= (val == ULONGLONG_MAX) ? val : start + val * incr;
+ }
+ Discrete_interval(ulonglong start, ulonglong val, ulonglong incr) :
+ next(NULL) { replace(start, val, incr); };
+ Discrete_interval() : next(NULL) { replace(0, 0, 0); };
+ ulonglong minimum() const { return interval_min; };
+ ulonglong values() const { return interval_values; };
+ ulonglong maximum() const { return interval_max; };
+ /*
+ If appending [3,5] to [1,2], we merge both in [1,5] (they should have the
+ same increment for that, user of the class has to ensure that). That is
+ just a space optimization. Returns 0 if merge succeeded.
+ */
+ bool merge_if_contiguous(ulonglong start, ulonglong val, ulonglong incr)
+ {
+ if (interval_max == start)
+ {
+ if (val == ULONGLONG_MAX)
+ {
+ interval_values= interval_max= val;
+ }
+ else
+ {
+ interval_values+= val;
+ interval_max= start + val * incr;
+ }
+ return 0;
+ }
+ return 1;
+ };
+};
+
+/* List of Discrete_interval objects */
+class Discrete_intervals_list {
+private:
+ Discrete_interval *head;
+ Discrete_interval *tail;
+ /*
+ When many intervals are provided at the beginning of the execution of a
+ statement (in a replication slave or SET INSERT_ID), "current" points to
+ the interval being consumed by the thread now (so "current" goes from
+ "head" to "tail" then to NULL).
+ */
+ Discrete_interval *current;
+ uint elements; // number of elements
+ void set_members(Discrete_interval *h, Discrete_interval *t,
+ Discrete_interval *c, uint el)
+ {
+ head= h;
+ tail= t;
+ current= c;
+ elements= el;
+ }
+ void operator=(Discrete_intervals_list &); /* prevent use of these */
+ Discrete_intervals_list(const Discrete_intervals_list &);
+
+public:
+ Discrete_intervals_list() : head(NULL), current(NULL), elements(0) {};
+ void empty_no_free()
+ {
+ set_members(NULL, NULL, NULL, 0);
+ }
+ void empty()
+ {
+ for (Discrete_interval *i= head; i;)
+ {
+ Discrete_interval *next= i->next;
+ delete i;
+ i= next;
+ }
+ empty_no_free();
+ }
+ void copy_shallow(const Discrete_intervals_list * dli)
+ {
+ head= dli->get_head();
+ tail= dli->get_tail();
+ current= dli->get_current();
+ elements= dli->nb_elements();
+ }
+ void swap (Discrete_intervals_list * dli)
+ {
+ Discrete_interval *h, *t, *c;
+ uint el;
+ h= dli->get_head();
+ t= dli->get_tail();
+ c= dli->get_current();
+ el= dli->nb_elements();
+ dli->copy_shallow(this);
+ set_members(h, t, c, el);
+ }
+ const Discrete_interval* get_next()
+ {
+ Discrete_interval *tmp= current;
+ if (current != NULL)
+ current= current->next;
+ return tmp;
+ }
+ ~Discrete_intervals_list() { empty(); };
+ bool append(ulonglong start, ulonglong val, ulonglong incr);
+ bool append(Discrete_interval *interval);
+ ulonglong minimum() const { return (head ? head->minimum() : 0); };
+ ulonglong maximum() const { return (head ? tail->maximum() : 0); };
+ uint nb_elements() const { return elements; }
+ Discrete_interval* get_head() const { return head; };
+ Discrete_interval* get_tail() const { return tail; };
+ Discrete_interval* get_current() const { return current; };
+};
+
+#endif /* STRUCTS_INCLUDED */
diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
new file mode 100644
index 00000000000..94466db5fd9
--- /dev/null
+++ b/sql/sys_vars.cc
@@ -0,0 +1,4816 @@
+/* Copyright (c) 2002, 2015, Oracle and/or its affiliates.
+ Copyright (c) 2012, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/**
+ @file
+ Definitions of all server's session or global variables.
+
+ How to add new variables:
+
+ 1. copy one of the existing variables, and edit the declaration.
+ 2. if you need special behavior on assignment or additional checks
+ use ON_CHECK and ON_UPDATE callbacks.
+ 3. *Don't* add new Sys_var classes or uncle Occam will come
+ with his razor to haunt you at nights
+
+ Note - all storage engine variables (for example myisam_whatever)
+ should go into the corresponding storage engine sources
+ (for example in storage/myisam/ha_myisam.cc) !
+*/
+
+#include "sql_plugin.h" // Includes my_global.h
+#include "sql_priv.h"
+#include "sql_class.h" // set_var.h: THD
+#include "sys_vars.h"
+
+#include "events.h"
+#include <thr_alarm.h>
+#include "slave.h"
+#include "rpl_mi.h"
+#include "transaction.h"
+#include "mysqld.h"
+#include "lock.h"
+#include "sql_time.h" // known_date_time_formats
+#include "sql_acl.h" // SUPER_ACL,
+ // mysql_user_table_is_in_short_password_format
+#include "derror.h" // read_texts
+#include "sql_base.h" // close_cached_tables
+#include "hostname.h" // host_cache_size
+#include <myisam.h>
+#include "log_slow.h"
+#include "debug_sync.h" // DEBUG_SYNC
+#include "sql_show.h"
+
+#include "log_event.h"
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+#include "../storage/perfschema/pfs_server.h"
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+#include "threadpool.h"
+#include "sql_repl.h"
+#include "opt_range.h"
+#include "rpl_parallel.h"
+
+/*
+ The rule for this file: everything should be 'static'. When a sys_var
+ variable or a function from this file is - in very rare cases - needed
+ elsewhere it should be explicitly declared 'export' here to show that it's
+ not a mistakenly forgotten 'static' keyword.
+*/
+#define export /* not static */
+
+#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
+
+static Sys_var_mybool Sys_pfs_enabled(
+ "performance_schema",
+ "Enable the performance schema.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_enabled),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static Sys_var_long Sys_pfs_events_waits_history_long_size(
+ "performance_schema_events_waits_history_long_size",
+ "Number of rows in EVENTS_WAITS_HISTORY_LONG."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY
+ GLOBAL_VAR(pfs_param.m_events_waits_history_long_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_events_waits_history_size(
+ "performance_schema_events_waits_history_size",
+ "Number of rows per thread in EVENTS_WAITS_HISTORY."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_waits_history_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_cond_classes(
+ "performance_schema_max_cond_classes",
+ "Maximum number of condition instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_cond_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT(PFS_MAX_COND_CLASS), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_cond_instances(
+ "performance_schema_max_cond_instances",
+ "Maximum number of instrumented condition objects."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_cond_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_file_classes(
+ "performance_schema_max_file_classes",
+ "Maximum number of file instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_file_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT(PFS_MAX_FILE_CLASS), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_file_handles(
+ "performance_schema_max_file_handles",
+ "Maximum number of opened instrumented files.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_file_handle_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024),
+ DEFAULT(PFS_MAX_FILE_HANDLE), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_file_instances(
+ "performance_schema_max_file_instances",
+ "Maximum number of instrumented files."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_file_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_sockets(
+ "performance_schema_max_socket_instances",
+ "Maximum number of opened instrumented sockets."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_socket_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_socket_classes(
+ "performance_schema_max_socket_classes",
+ "Maximum number of socket instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_socket_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT(PFS_MAX_SOCKET_CLASS),
+ BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_mutex_classes(
+ "performance_schema_max_mutex_classes",
+ "Maximum number of mutex instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_mutex_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT(PFS_MAX_MUTEX_CLASS), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_mutex_instances(
+ "performance_schema_max_mutex_instances",
+ "Maximum number of instrumented MUTEX objects."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_mutex_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 100*1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_rwlock_classes(
+ "performance_schema_max_rwlock_classes",
+ "Maximum number of rwlock instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_rwlock_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT(PFS_MAX_RWLOCK_CLASS), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_rwlock_instances(
+ "performance_schema_max_rwlock_instances",
+ "Maximum number of instrumented RWLOCK objects."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_rwlock_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 100*1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_table_handles(
+ "performance_schema_max_table_handles",
+ "Maximum number of opened instrumented tables."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_table_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_table_instances(
+ "performance_schema_max_table_instances",
+ "Maximum number of instrumented tables."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_table_share_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_thread_classes(
+ "performance_schema_max_thread_classes",
+ "Maximum number of thread instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_thread_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT(PFS_MAX_THREAD_CLASS), BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_max_thread_instances(
+ "performance_schema_max_thread_instances",
+ "Maximum number of instrumented threads."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_thread_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_setup_actors_size(
+ "performance_schema_setup_actors_size",
+ "Maximum number of rows in SETUP_ACTORS.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_setup_actor_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024),
+ DEFAULT(PFS_MAX_SETUP_ACTOR),
+ BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_setup_objects_size(
+ "performance_schema_setup_objects_size",
+ "Maximum number of rows in SETUP_OBJECTS.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_setup_object_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024),
+ DEFAULT(PFS_MAX_SETUP_OBJECT),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_accounts_size(
+ "performance_schema_accounts_size",
+ "Maximum number of instrumented user@host accounts."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_account_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_hosts_size(
+ "performance_schema_hosts_size",
+ "Maximum number of instrumented hosts."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_host_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_users_size(
+ "performance_schema_users_size",
+ "Maximum number of instrumented users."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_user_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_pfs_max_stage_classes(
+ "performance_schema_max_stage_classes",
+ "Maximum number of stage instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_stage_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT(PFS_MAX_STAGE_CLASS),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_events_stages_history_long_size(
+ "performance_schema_events_stages_history_long_size",
+ "Number of rows in EVENTS_STAGES_HISTORY_LONG."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_stages_history_long_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_events_stages_history_size(
+ "performance_schema_events_stages_history_size",
+ "Number of rows per thread in EVENTS_STAGES_HISTORY."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_stages_history_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+/**
+ Variable performance_schema_max_statement_classes.
+ The default number of statement classes is the sum of:
+ - COM_END for all regular "statement/com/...",
+ - 1 for "statement/com/new_packet", for unknown enum_server_command
+ - 1 for "statement/com/Error", for invalid enum_server_command
+ - SQLCOM_END for all regular "statement/sql/...",
+ - 1 for "statement/sql/error", for invalid enum_sql_command
+ - 1 for "statement/rpl/relay_log", for replicated statements.
+*/
+static Sys_var_ulong Sys_pfs_max_statement_classes(
+ "performance_schema_max_statement_classes",
+ "Maximum number of statement instruments.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_statement_class_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
+ DEFAULT((ulong) SQLCOM_END + (ulong) COM_END + 4),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_events_statements_history_long_size(
+ "performance_schema_events_statements_history_long_size",
+ "Number of rows in EVENTS_STATEMENTS_HISTORY_LONG."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_statements_history_long_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_events_statements_history_size(
+ "performance_schema_events_statements_history_size",
+ "Number of rows per thread in EVENTS_STATEMENTS_HISTORY."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_statements_history_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_digest_size(
+ "performance_schema_digests_size",
+ "Size of the statement digest."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_digest_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 200),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_pfs_connect_attrs_size(
+ "performance_schema_session_connect_attrs_size",
+ "Size of session attribute string buffer per thread."
+ " Use 0 to disable, -1 for automated sizing.",
+ PARSED_EARLY READ_ONLY
+ GLOBAL_VAR(pfs_param.m_session_connect_attrs_sizing),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024 * 1024),
+ DEFAULT(-1),
+ BLOCK_SIZE(1));
+
+#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */
+
+static Sys_var_ulong Sys_auto_increment_increment(
+ "auto_increment_increment",
+ "Auto-increment columns are incremented by this",
+ SESSION_VAR(auto_increment_increment),
+ CMD_LINE(OPT_ARG),
+ VALID_RANGE(1, 65535), DEFAULT(1), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, IN_BINLOG);
+
+static Sys_var_ulong Sys_auto_increment_offset(
+ "auto_increment_offset",
+ "Offset added to Auto-increment columns. Used when "
+ "auto-increment-increment != 1",
+ SESSION_VAR(auto_increment_offset),
+ CMD_LINE(OPT_ARG),
+ VALID_RANGE(1, 65535), DEFAULT(1), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, IN_BINLOG);
+
+static Sys_var_mybool Sys_automatic_sp_privileges(
+ "automatic_sp_privileges",
+ "Creating and dropping stored procedures alters ACLs",
+ GLOBAL_VAR(sp_automatic_privileges),
+ CMD_LINE(OPT_ARG), DEFAULT(TRUE));
+
+static Sys_var_ulong Sys_back_log(
+ "back_log", "The number of outstanding connection requests "
+ "MySQL can have. This comes into play when the main MySQL thread "
+ "gets very many connection requests in a very short time",
+ READ_ONLY GLOBAL_VAR(back_log), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 65535), DEFAULT(150), BLOCK_SIZE(1));
+
+static Sys_var_charptr Sys_basedir(
+ "basedir", "Path to installation directory. All paths are "
+ "usually resolved relative to this",
+ READ_ONLY GLOBAL_VAR(mysql_home_ptr), CMD_LINE(REQUIRED_ARG, 'b'),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_ulonglong Sys_binlog_cache_size(
+ "binlog_cache_size", "The size of the transactional cache for "
+ "updates to transactional engines for the binary log. "
+ "If you often use transactions containing many statements, "
+ "you can increase this to get more performance",
+ GLOBAL_VAR(binlog_cache_size),
+ CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(IO_SIZE, SIZE_T_MAX), DEFAULT(32768), BLOCK_SIZE(IO_SIZE));
+
+static Sys_var_ulonglong Sys_binlog_stmt_cache_size(
+ "binlog_stmt_cache_size", "The size of the statement cache for "
+ "updates to non-transactional engines for the binary log. "
+ "If you often use statements updating a great number of rows, "
+ "you can increase this to get more performance",
+ GLOBAL_VAR(binlog_stmt_cache_size),
+ CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(IO_SIZE, SIZE_T_MAX), DEFAULT(32768), BLOCK_SIZE(IO_SIZE));
+
+/*
+ Some variables like @sql_log_bin and @binlog_format change how/if binlogging
+ is done. We must not change them inside a running transaction or statement,
+ otherwise the event group eventually written to the binlog may become
+ incomplete or otherwise garbled.
+
+ This function does the appropriate check.
+
+ It returns true if an error is caused by incorrect usage, false if ok.
+*/
+static bool
+error_if_in_trans_or_substatement(THD *thd, int in_substatement_error,
+ int in_transaction_error)
+{
+ if (thd->in_sub_stmt)
+ {
+ my_error(in_substatement_error, MYF(0));
+ return true;
+ }
+
+ if (thd->in_active_multi_stmt_transaction())
+ {
+ my_error(in_transaction_error, MYF(0));
+ return true;
+ }
+
+ return false;
+}
+
+static bool check_has_super(sys_var *self, THD *thd, set_var *var)
+{
+ DBUG_ASSERT(self->scope() != sys_var::GLOBAL);// don't abuse check_has_super()
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (!(thd->security_ctx->master_access & SUPER_ACL))
+ {
+ my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER");
+ return true;
+ }
+#endif
+ return false;
+}
+static bool binlog_format_check(sys_var *self, THD *thd, set_var *var)
+{
+ if (check_has_super(self, thd, var))
+ return true;
+
+ if (var->type == OPT_GLOBAL)
+ return false;
+
+ /*
+ If RBR and open temporary tables, their CREATE TABLE may not be in the
+ binlog, so we can't toggle to SBR in this connection.
+
+ If binlog_format=MIXED, there are open temporary tables, and an unsafe
+ statement is executed, then subsequent statements are logged in row
+ format and hence changes to temporary tables may be lost. So we forbid
+ switching @@SESSION.binlog_format from MIXED to STATEMENT when there are
+ open temp tables and we are logging in row format.
+ */
+ if (thd->temporary_tables && var->type == OPT_SESSION &&
+ var->save_result.ulonglong_value == BINLOG_FORMAT_STMT &&
+ ((thd->variables.binlog_format == BINLOG_FORMAT_MIXED &&
+ thd->is_current_stmt_binlog_format_row()) ||
+ thd->variables.binlog_format == BINLOG_FORMAT_ROW))
+ {
+ my_error(ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR, MYF(0));
+ return true;
+ }
+
+ if (error_if_in_trans_or_substatement(thd,
+ ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT,
+ ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT))
+ return true;
+
+ return false;
+}
+
+static bool fix_binlog_format_after_update(sys_var *self, THD *thd,
+ enum_var_type type)
+{
+ if (type == OPT_SESSION)
+ thd->reset_current_stmt_binlog_format_row();
+ return false;
+}
+
+static Sys_var_enum Sys_binlog_format(
+ "binlog_format", "What form of binary logging the master will "
+ "use: either ROW for row-based binary logging, STATEMENT "
+ "for statement-based binary logging, or MIXED. MIXED is statement-"
+ "based binary logging except for those statements where only row-"
+ "based is correct: those which involve user-defined functions (i.e. "
+ "UDFs) or the UUID() function; for those, row-based binary logging is "
+ "automatically used. If NDBCLUSTER is enabled and binlog-format is "
+ "MIXED, the format switches to row-based and back implicitly per each "
+ "query accessing an NDBCLUSTER table",
+ SESSION_VAR(binlog_format), CMD_LINE(REQUIRED_ARG, OPT_BINLOG_FORMAT),
+ binlog_format_names, DEFAULT(BINLOG_FORMAT_STMT),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(binlog_format_check),
+ ON_UPDATE(fix_binlog_format_after_update));
+
+static bool binlog_direct_check(sys_var *self, THD *thd, set_var *var)
+{
+ if (check_has_super(self, thd, var))
+ return true;
+
+ if (var->type == OPT_GLOBAL)
+ return false;
+
+ if (error_if_in_trans_or_substatement(thd,
+ ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT,
+ ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT))
+ return true;
+
+ return false;
+}
+
+static Sys_var_mybool Sys_binlog_direct(
+ "binlog_direct_non_transactional_updates",
+ "Causes updates to non-transactional engines using statement format to "
+ "be written directly to binary log. Before using this option make sure "
+ "that there are no dependencies between transactional and "
+ "non-transactional tables such as in the statement INSERT INTO t_myisam "
+ "SELECT * FROM t_innodb; otherwise, slaves may diverge from the master.",
+ SESSION_VAR(binlog_direct_non_trans_update),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(binlog_direct_check));
+
+static Sys_var_ulonglong Sys_bulk_insert_buff_size(
+ "bulk_insert_buffer_size", "Size of tree cache used in bulk "
+ "insert optimisation. Note that this is a limit per thread!",
+ SESSION_VAR(bulk_insert_buff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, SIZE_T_MAX), DEFAULT(8192*1024), BLOCK_SIZE(1));
+
+static Sys_var_charptr Sys_character_sets_dir(
+ "character_sets_dir", "Directory where character sets are",
+ READ_ONLY GLOBAL_VAR(charsets_dir), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static bool check_not_null(sys_var *self, THD *thd, set_var *var)
+{
+ return var->value && var->value->is_null();
+}
+static bool check_charset(sys_var *self, THD *thd, set_var *var)
+{
+ if (!var->value)
+ return false;
+
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ if (var->value->result_type() == STRING_RESULT)
+ {
+ String str(buff, sizeof(buff), system_charset_info), *res;
+ if (!(res= var->value->val_str(&str)))
+ var->save_result.ptr= NULL;
+ else
+ {
+ ErrConvString err(res); /* Get utf8 '\0' terminated string */
+ if (!(var->save_result.ptr= get_charset_by_csname(err.ptr(),
+ MY_CS_PRIMARY,
+ MYF(0))) &&
+ !(var->save_result.ptr= get_old_charset_by_name(err.ptr())))
+ {
+ my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), err.ptr());
+ return true;
+ }
+ }
+ }
+ else // INT_RESULT
+ {
+ int csno= (int)var->value->val_int();
+ if (!(var->save_result.ptr= get_charset(csno, MYF(0))))
+ {
+ my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), llstr(csno, buff));
+ return true;
+ }
+ }
+ return false;
+}
+static bool check_charset_not_null(sys_var *self, THD *thd, set_var *var)
+{
+ return check_charset(self, thd, var) || check_not_null(self, thd, var);
+}
+static Sys_var_struct Sys_character_set_system(
+ "character_set_system", "The character set used by the server "
+ "for storing identifiers",
+ READ_ONLY GLOBAL_VAR(system_charset_info), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, csname), DEFAULT(0));
+
+static Sys_var_struct Sys_character_set_server(
+ "character_set_server", "The default character set",
+ SESSION_VAR(collation_server), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_charset_not_null));
+
+static bool check_charset_db(sys_var *self, THD *thd, set_var *var)
+{
+ if (check_charset_not_null(self, thd, var))
+ return true;
+ if (!var->value) // = DEFAULT
+ var->save_result.ptr= thd->db_charset;
+ return false;
+}
+static Sys_var_struct Sys_character_set_database(
+ "character_set_database",
+ "The character set used by the default database",
+ SESSION_VAR(collation_database), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_charset_db));
+
+static bool check_cs_client(sys_var *self, THD *thd, set_var *var)
+{
+ if (check_charset_not_null(self, thd, var))
+ return true;
+
+ // Currently, UCS-2 cannot be used as a client character set
+ if (((CHARSET_INFO *)(var->save_result.ptr))->mbminlen > 1)
+ return true;
+
+ return false;
+}
+static bool fix_thd_charset(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type == OPT_SESSION)
+ thd->update_charset();
+ return false;
+}
+static Sys_var_struct Sys_character_set_client(
+ "character_set_client", "The character set for statements "
+ "that arrive from the client",
+ SESSION_VAR(character_set_client), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_cs_client),
+ ON_UPDATE(fix_thd_charset));
+
+static Sys_var_struct Sys_character_set_connection(
+ "character_set_connection", "The character set used for "
+ "literals that do not have a character set introducer and for "
+ "number-to-string conversion",
+ SESSION_VAR(collation_connection), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_charset_not_null),
+ ON_UPDATE(fix_thd_charset));
+
+static Sys_var_struct Sys_character_set_results(
+ "character_set_results", "The character set used for returning "
+ "query results to the client",
+ SESSION_VAR(character_set_results), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_charset));
+
+static Sys_var_struct Sys_character_set_filesystem(
+ "character_set_filesystem", "The filesystem character set",
+ SESSION_VAR(character_set_filesystem), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, csname), DEFAULT(&character_set_filesystem),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_charset_not_null),
+ ON_UPDATE(fix_thd_charset));
+
+static const char *completion_type_names[]= {"NO_CHAIN", "CHAIN", "RELEASE", 0};
+static Sys_var_enum Sys_completion_type(
+ "completion_type", "The transaction completion type, one of "
+ "NO_CHAIN, CHAIN, RELEASE",
+ SESSION_VAR(completion_type), CMD_LINE(REQUIRED_ARG),
+ completion_type_names, DEFAULT(0));
+
+static bool check_collation_not_null(sys_var *self, THD *thd, set_var *var)
+{
+ if (!var->value)
+ return false;
+
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ if (var->value->result_type() == STRING_RESULT)
+ {
+ String str(buff, sizeof(buff), system_charset_info), *res;
+ if (!(res= var->value->val_str(&str)))
+ var->save_result.ptr= NULL;
+ else
+ {
+ ErrConvString err(res); /* Get utf8 '\0'-terminated string */
+ if (!(var->save_result.ptr= get_charset_by_name(err.ptr(), MYF(0))))
+ {
+ my_error(ER_UNKNOWN_COLLATION, MYF(0), err.ptr());
+ return true;
+ }
+ }
+ }
+ else // INT_RESULT
+ {
+ int csno= (int)var->value->val_int();
+ if (!(var->save_result.ptr= get_charset(csno, MYF(0))))
+ {
+ my_error(ER_UNKNOWN_COLLATION, MYF(0), llstr(csno, buff));
+ return true;
+ }
+ }
+ return check_not_null(self, thd, var);
+}
+static Sys_var_struct Sys_collation_connection(
+ "collation_connection", "The collation of the connection "
+ "character set",
+ SESSION_VAR(collation_connection), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, name), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_collation_not_null),
+ ON_UPDATE(fix_thd_charset));
+
+static bool check_collation_db(sys_var *self, THD *thd, set_var *var)
+{
+ if (check_collation_not_null(self, thd, var))
+ return true;
+ if (!var->value) // = DEFAULT
+ var->save_result.ptr= thd->db_charset;
+ return false;
+}
+static Sys_var_struct Sys_collation_database(
+ "collation_database", "The collation of the database "
+ "character set",
+ SESSION_VAR(collation_database), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, name), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_collation_db));
+
+static Sys_var_struct Sys_collation_server(
+ "collation_server", "The server default collation",
+ SESSION_VAR(collation_server), NO_CMD_LINE,
+ offsetof(CHARSET_INFO, name), DEFAULT(&default_charset_info),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_collation_not_null));
+
+static const char *concurrent_insert_names[]= {"NEVER", "AUTO", "ALWAYS", 0};
+static Sys_var_enum Sys_concurrent_insert(
+ "concurrent_insert", "Use concurrent insert with MyISAM. Possible "
+ "values are NEVER, AUTO, ALWAYS",
+ GLOBAL_VAR(myisam_concurrent_insert), CMD_LINE(OPT_ARG),
+ concurrent_insert_names, DEFAULT(1));
+
+static Sys_var_ulong Sys_connect_timeout(
+ "connect_timeout",
+ "The number of seconds the mysqld server is waiting for a connect "
+ "packet before responding with 'Bad handshake'",
+ GLOBAL_VAR(connect_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(2, LONG_TIMEOUT), DEFAULT(CONNECT_TIMEOUT), BLOCK_SIZE(1));
+
+static Sys_var_charptr Sys_datadir(
+ "datadir", "Path to the database root directory",
+ READ_ONLY GLOBAL_VAR(mysql_real_data_home_ptr),
+ CMD_LINE(REQUIRED_ARG, 'h'), IN_FS_CHARSET, DEFAULT(mysql_real_data_home));
+
+#ifndef DBUG_OFF
+static Sys_var_dbug Sys_dbug(
+ "debug", "Built-in DBUG debugger", sys_var::SESSION,
+ CMD_LINE(OPT_ARG, '#'), DEFAULT(""), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_has_super), ON_UPDATE(0),
+ DEPRECATED("'@@debug_dbug'"));
+
+static Sys_var_dbug Sys_debug_dbug(
+ "debug_dbug", "Built-in DBUG debugger", sys_var::SESSION,
+ CMD_LINE(OPT_ARG, '#'), DEFAULT(""), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_has_super));
+#endif
+
+/**
+ @todo
+ When updating myisam_delay_key_write, we should do a 'flush tables'
+ of all MyISAM tables to ensure that they are reopen with the
+ new attribute.
+*/
+export bool fix_delay_key_write(sys_var *self, THD *thd, enum_var_type type)
+{
+ switch (delay_key_write_options) {
+ case DELAY_KEY_WRITE_NONE:
+ myisam_delay_key_write=0;
+ ha_open_options&= ~HA_OPEN_DELAY_KEY_WRITE;
+ break;
+ case DELAY_KEY_WRITE_ON:
+ myisam_delay_key_write=1;
+ ha_open_options&= ~HA_OPEN_DELAY_KEY_WRITE;
+ break;
+ case DELAY_KEY_WRITE_ALL:
+ myisam_delay_key_write=1;
+ ha_open_options|= HA_OPEN_DELAY_KEY_WRITE;
+ break;
+ }
+#ifdef WITH_ARIA_STORAGE_ENGINE
+ maria_delay_key_write= myisam_delay_key_write;
+#endif
+ return false;
+}
+static const char *delay_key_write_names[]= { "OFF", "ON", "ALL", NullS };
+static Sys_var_enum Sys_delay_key_write(
+ "delay_key_write", "Type of DELAY_KEY_WRITE",
+ GLOBAL_VAR(delay_key_write_options), CMD_LINE(OPT_ARG),
+ delay_key_write_names, DEFAULT(DELAY_KEY_WRITE_ON),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_delay_key_write));
+
+static Sys_var_ulong Sys_delayed_insert_limit(
+ "delayed_insert_limit",
+ "After inserting delayed_insert_limit rows, the INSERT DELAYED "
+ "handler will check if there are any SELECT statements pending. "
+ "If so, it allows these to execute before continuing",
+ GLOBAL_VAR(delayed_insert_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(DELAYED_LIMIT), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_delayed_insert_timeout(
+ "delayed_insert_timeout",
+ "How long a INSERT DELAYED thread should wait for INSERT statements "
+ "before terminating",
+ GLOBAL_VAR(delayed_insert_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(DELAYED_WAIT_TIMEOUT),
+ BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_delayed_queue_size(
+ "delayed_queue_size",
+ "What size queue (in rows) should be allocated for handling INSERT "
+ "DELAYED. If the queue becomes full, any client that does INSERT "
+ "DELAYED will wait until there is room in the queue again",
+ GLOBAL_VAR(delayed_queue_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(DELAYED_QUEUE_SIZE), BLOCK_SIZE(1));
+
+#ifdef HAVE_EVENT_SCHEDULER
+static const char *event_scheduler_names[]= { "OFF", "ON", "DISABLED", NullS };
+static bool event_scheduler_check(sys_var *self, THD *thd, set_var *var)
+{
+ /* DISABLED is only accepted on the command line */
+ if (var->save_result.ulonglong_value == Events::EVENTS_DISABLED)
+ return true;
+ /*
+ If the scheduler was disabled because there are no/bad
+ system tables, produce a more meaningful error message
+ than ER_OPTION_PREVENTS_STATEMENT
+ */
+ if (Events::check_if_system_tables_error())
+ return true;
+ if (Events::opt_event_scheduler == Events::EVENTS_DISABLED)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
+ "--event-scheduler=DISABLED or --skip-grant-tables");
+ return true;
+ }
+ return false;
+}
+static bool event_scheduler_update(sys_var *self, THD *thd, enum_var_type type)
+{
+ int err_no= 0;
+ uint opt_event_scheduler_value= Events::opt_event_scheduler;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ /*
+ Events::start() is heavyweight. In particular it creates a new THD,
+ which takes LOCK_global_system_variables internally.
+ Thus we have to release it here.
+ We need to re-take it before returning, though.
+
+ Note that since we release LOCK_global_system_variables before calling
+ start/stop, there is a possibility that the server variable
+ can become out of sync with the real event scheduler state.
+
+ This can happen with two concurrent statments if the first gets
+ interrupted after start/stop but before retaking
+ LOCK_global_system_variables. However, this problem should be quite
+ rare and it's difficult to avoid it without opening up possibilities
+ for deadlocks. See bug#51160.
+ */
+ bool ret= opt_event_scheduler_value == Events::EVENTS_ON
+ ? Events::start(&err_no)
+ : Events::stop();
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ if (ret)
+ {
+ Events::opt_event_scheduler= Events::EVENTS_OFF;
+ my_error(ER_EVENT_SET_VAR_ERROR, MYF(0), err_no);
+ }
+ return ret;
+}
+
+static Sys_var_enum Sys_event_scheduler(
+ "event_scheduler", "Enable the event scheduler. Possible values are "
+ "ON, OFF, and DISABLED (keep the event scheduler completely "
+ "deactivated, it cannot be activated run-time)",
+ GLOBAL_VAR(Events::opt_event_scheduler), CMD_LINE(OPT_ARG),
+ event_scheduler_names, DEFAULT(Events::EVENTS_OFF),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(event_scheduler_check), ON_UPDATE(event_scheduler_update));
+#endif
+
+static Sys_var_ulong Sys_expire_logs_days(
+ "expire_logs_days",
+ "If non-zero, binary logs will be purged after expire_logs_days "
+ "days; possible purges happen at startup and at binary log rotation",
+ GLOBAL_VAR(expire_logs_days),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 99), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_mybool Sys_flush(
+ "flush", "Flush MyISAM tables to disk between SQL commands",
+ GLOBAL_VAR(myisam_flush),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static Sys_var_ulong Sys_flush_time(
+ "flush_time",
+ "A dedicated thread is created to flush all tables at the "
+ "given interval",
+ GLOBAL_VAR(flush_time),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, LONG_TIMEOUT),
+ DEFAULT(0), BLOCK_SIZE(1));
+
+static bool check_ftb_syntax(sys_var *self, THD *thd, set_var *var)
+{
+ return ft_boolean_check_syntax_string((uchar*)
+ (var->save_result.string_value.str));
+}
+static bool query_cache_flush(sys_var *self, THD *thd, enum_var_type type)
+{
+#ifdef HAVE_QUERY_CACHE
+ query_cache.flush();
+#endif /* HAVE_QUERY_CACHE */
+ return false;
+}
+/// @todo make SESSION_VAR (usability enhancement and a fix for a race condition)
+static Sys_var_charptr Sys_ft_boolean_syntax(
+ "ft_boolean_syntax", "List of operators for "
+ "MATCH ... AGAINST ( ... IN BOOLEAN MODE)",
+ GLOBAL_VAR(ft_boolean_syntax),
+ CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET,
+ DEFAULT(DEFAULT_FTB_SYNTAX), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(check_ftb_syntax), ON_UPDATE(query_cache_flush));
+
+static Sys_var_ulong Sys_ft_max_word_len(
+ "ft_max_word_len",
+ "The maximum length of the word to be included in a FULLTEXT index. "
+ "Note: FULLTEXT indexes must be rebuilt after changing this variable",
+ READ_ONLY GLOBAL_VAR(ft_max_word_len), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(10, HA_FT_MAXCHARLEN), DEFAULT(HA_FT_MAXCHARLEN),
+ BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_ft_min_word_len(
+ "ft_min_word_len",
+ "The minimum length of the word to be included in a FULLTEXT index. "
+ "Note: FULLTEXT indexes must be rebuilt after changing this variable",
+ READ_ONLY GLOBAL_VAR(ft_min_word_len), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, HA_FT_MAXCHARLEN), DEFAULT(4), BLOCK_SIZE(1));
+
+/// @todo make it an updatable SESSION_VAR
+static Sys_var_ulong Sys_ft_query_expansion_limit(
+ "ft_query_expansion_limit",
+ "Number of best matches to use for query expansion",
+ READ_ONLY GLOBAL_VAR(ft_query_expansion_limit),
+ CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 1000), DEFAULT(20), BLOCK_SIZE(1));
+
+static Sys_var_charptr Sys_ft_stopword_file(
+ "ft_stopword_file",
+ "Use stopwords from this file instead of built-in list",
+ READ_ONLY GLOBAL_VAR(ft_stopword_file), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_mybool Sys_ignore_builtin_innodb(
+ "ignore_builtin_innodb",
+ "Disable initialization of builtin InnoDB plugin",
+ READ_ONLY GLOBAL_VAR(opt_ignore_builtin_innodb),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static bool check_init_string(sys_var *self, THD *thd, set_var *var)
+{
+ if (var->save_result.string_value.str == 0)
+ {
+ var->save_result.string_value.str= const_cast<char*>("");
+ var->save_result.string_value.length= 0;
+ }
+ return false;
+}
+static PolyLock_rwlock PLock_sys_init_connect(&LOCK_sys_init_connect);
+static Sys_var_lexstring Sys_init_connect(
+ "init_connect", "Command(s) that are executed for each "
+ "new connection (unless the user has SUPER privilege)",
+ GLOBAL_VAR(opt_init_connect), CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET,
+ DEFAULT(""), &PLock_sys_init_connect, NOT_IN_BINLOG,
+ ON_CHECK(check_init_string));
+
+#ifdef HAVE_REPLICATION
+static bool check_master_connection(sys_var *self, THD *thd, set_var *var)
+{
+ LEX_STRING tmp;
+ tmp.str= var->save_result.string_value.str;
+ tmp.length= var->save_result.string_value.length;
+ if (!tmp.str || check_master_connection_name(&tmp))
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(ME_JUST_WARNING),
+ var->var->name.str);
+ return true;
+ }
+ return false;
+}
+
+static Sys_var_session_lexstring Sys_default_master_connection(
+ "default_master_connection",
+ "Master connection to use for all slave variables and slave commands",
+ SESSION_ONLY(default_master_connection),
+ NO_CMD_LINE, IN_SYSTEM_CHARSET,
+ DEFAULT(""), MAX_CONNECTION_NAME, ON_CHECK(check_master_connection),
+ ON_UPDATE(0));
+#endif
+
+static Sys_var_charptr Sys_init_file(
+ "init_file", "Read SQL commands from this file at startup",
+ READ_ONLY GLOBAL_VAR(opt_init_file),
+#ifdef DISABLE_GRANT_OPTIONS
+ NO_CMD_LINE,
+#else
+ CMD_LINE(REQUIRED_ARG),
+#endif
+ IN_FS_CHARSET, DEFAULT(0));
+
+static PolyLock_rwlock PLock_sys_init_slave(&LOCK_sys_init_slave);
+static Sys_var_lexstring Sys_init_slave(
+ "init_slave", "Command(s) that are executed by a slave server "
+ "each time the SQL thread starts", GLOBAL_VAR(opt_init_slave),
+ CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET,
+ DEFAULT(""), &PLock_sys_init_slave,
+ NOT_IN_BINLOG, ON_CHECK(check_init_string));
+
+static Sys_var_ulong Sys_interactive_timeout(
+ "interactive_timeout",
+ "The number of seconds the server waits for activity on an interactive "
+ "connection before closing it",
+ SESSION_VAR(net_interactive_timeout),
+ CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(NET_WAIT_TIMEOUT), BLOCK_SIZE(1));
+
+static Sys_var_ulonglong Sys_join_buffer_size(
+ "join_buffer_size",
+ "The size of the buffer that is used for joins",
+ SESSION_VAR(join_buff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(128, SIZE_T_MAX), DEFAULT(128*1024), BLOCK_SIZE(128));
+
+static Sys_var_keycache Sys_key_buffer_size(
+ "key_buffer_size", "The size of the buffer used for "
+ "index blocks for MyISAM tables. Increase this to get better index "
+ "handling (for all reads and multiple writes) to as much as you can "
+ "afford",
+ KEYCACHE_VAR(param_buff_size),
+ CMD_LINE(REQUIRED_ARG, OPT_KEY_BUFFER_SIZE),
+ VALID_RANGE(0, SIZE_T_MAX), DEFAULT(KEY_CACHE_SIZE),
+ BLOCK_SIZE(IO_SIZE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_buffer_size));
+
+static Sys_var_keycache Sys_key_cache_block_size(
+ "key_cache_block_size", "The default size of key cache blocks",
+ KEYCACHE_VAR(param_block_size),
+ CMD_LINE(REQUIRED_ARG, OPT_KEY_CACHE_BLOCK_SIZE),
+ VALID_RANGE(512, 1024*16), DEFAULT(KEY_CACHE_BLOCK_SIZE),
+ BLOCK_SIZE(512), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(resize_keycache));
+
+static Sys_var_keycache Sys_key_cache_division_limit(
+ "key_cache_division_limit",
+ "The minimum percentage of warm blocks in key cache",
+ KEYCACHE_VAR(param_division_limit),
+ CMD_LINE(REQUIRED_ARG, OPT_KEY_CACHE_DIVISION_LIMIT),
+ VALID_RANGE(1, 100), DEFAULT(100),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(change_keycache_param));
+
+static Sys_var_keycache Sys_key_cache_age_threshold(
+ "key_cache_age_threshold", "This characterizes the number of "
+ "hits a hot block has to be untouched until it is considered aged "
+ "enough to be downgraded to a warm block. This specifies the "
+ "percentage ratio of that number of hits to the total number of "
+ "blocks in key cache",
+ KEYCACHE_VAR(param_age_threshold),
+ CMD_LINE(REQUIRED_ARG, OPT_KEY_CACHE_AGE_THRESHOLD),
+ VALID_RANGE(100, UINT_MAX), DEFAULT(300),
+ BLOCK_SIZE(100), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(change_keycache_param));
+
+static Sys_var_keycache Sys_key_cache_file_hash_size(
+ "key_cache_file_hash_size",
+ "Number of hash buckets for open and changed files. If you have a lot of MyISAM "
+ "files open you should increase this for faster flush of changes. A good "
+ "value is probably 1/10 of number of possible open MyISAM files.",
+ KEYCACHE_VAR(changed_blocks_hash_size),
+ CMD_LINE(REQUIRED_ARG, OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE),
+ VALID_RANGE(128, 16384), DEFAULT(512),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(resize_keycache));
+
+static Sys_var_mybool Sys_large_files_support(
+ "large_files_support",
+ "Whether mysqld was compiled with options for large file support",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(opt_large_files),
+ NO_CMD_LINE, DEFAULT(sizeof(my_off_t) > 4));
+
+static Sys_var_uint Sys_large_page_size(
+ "large_page_size",
+ "If large page support is enabled, this shows the size of memory pages",
+ READ_ONLY GLOBAL_VAR(opt_large_page_size), NO_CMD_LINE,
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_mybool Sys_large_pages(
+ "large_pages", "Enable support for large pages",
+ READ_ONLY GLOBAL_VAR(opt_large_pages),
+ IF_WIN(NO_CMD_LINE, CMD_LINE(OPT_ARG)), DEFAULT(FALSE));
+
+static Sys_var_charptr Sys_language(
+ "lc_messages_dir", "Directory where error messages are",
+ READ_ONLY GLOBAL_VAR(lc_messages_dir_ptr), CMD_LINE(REQUIRED_ARG, 'L'),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_mybool Sys_local_infile(
+ "local_infile", "Enable LOAD DATA LOCAL INFILE",
+ GLOBAL_VAR(opt_local_infile), CMD_LINE(OPT_ARG), DEFAULT(TRUE));
+
+static Sys_var_ulong Sys_lock_wait_timeout(
+ "lock_wait_timeout",
+ "Timeout in seconds to wait for a lock before returning an error.",
+ SESSION_VAR(lock_wait_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(LONG_TIMEOUT), BLOCK_SIZE(1));
+
+#ifdef HAVE_MLOCKALL
+static Sys_var_mybool Sys_locked_in_memory(
+ "locked_in_memory",
+ "Whether mysqld was locked in memory with --memlock",
+ READ_ONLY GLOBAL_VAR(locked_in_memory), NO_CMD_LINE, DEFAULT(FALSE));
+#endif
+
+/* this says NO_CMD_LINE, as command-line option takes a string, not a bool */
+static Sys_var_mybool Sys_log_bin(
+ "log_bin", "Whether the binary log is enabled",
+ READ_ONLY GLOBAL_VAR(opt_bin_log), NO_CMD_LINE, DEFAULT(FALSE));
+
+static Sys_var_mybool Sys_trust_function_creators(
+ "log_bin_trust_function_creators",
+ "If set to FALSE (the default), then when --log-bin is used, creation "
+ "of a stored function (or trigger) is allowed only to users having the "
+ "SUPER privilege and only if this stored function (trigger) may not "
+ "break binary logging. Note that if ALL connections to this server "
+ "ALWAYS use row-based binary logging, the security issues do not "
+ "exist and the binary logging cannot break, so you can safely set "
+ "this to TRUE",
+ GLOBAL_VAR(trust_function_creators),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static Sys_var_charptr Sys_log_error(
+ "log_error",
+ "Log errors to file (instead of stdout). If file name is not specified "
+ "then 'datadir'/'log-basename'.err or the 'pid-file' path with extension "
+ ".err is used",
+ READ_ONLY GLOBAL_VAR(log_error_file_ptr),
+ CMD_LINE(OPT_ARG, OPT_LOG_ERROR),
+ IN_FS_CHARSET, DEFAULT(disabled_my_option));
+
+static Sys_var_mybool Sys_log_queries_not_using_indexes(
+ "log_queries_not_using_indexes",
+ "Log queries that are executed without benefit of any index to the "
+ "slow log if it is open",
+ GLOBAL_VAR(opt_log_queries_not_using_indexes),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static Sys_var_ulong Sys_log_warnings(
+ "log_warnings",
+ "Log some not critical warnings to the general log file."
+ "Value can be between 0 and 11. Higher values mean more verbosity",
+ SESSION_VAR(log_warnings),
+ CMD_LINE(OPT_ARG, 'W'),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(1), BLOCK_SIZE(1));
+
+static bool update_cached_long_query_time(sys_var *self, THD *thd,
+ enum_var_type type)
+{
+ if (type == OPT_SESSION)
+ thd->variables.long_query_time=
+ double2ulonglong(thd->variables.long_query_time_double * 1e6);
+ else
+ global_system_variables.long_query_time=
+ double2ulonglong(global_system_variables.long_query_time_double * 1e6);
+ return false;
+}
+
+static Sys_var_double Sys_long_query_time(
+ "long_query_time",
+ "Log all queries that have taken more than long_query_time seconds "
+ "to execute to file. The argument will be treated as a decimal value "
+ "with microsecond precision",
+ SESSION_VAR(long_query_time_double),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, LONG_TIMEOUT), DEFAULT(10),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_cached_long_query_time));
+
+static bool fix_low_prio_updates(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type == OPT_SESSION)
+ thd->update_lock_default= (thd->variables.low_priority_updates ?
+ TL_WRITE_LOW_PRIORITY : TL_WRITE);
+ else
+ thr_upgraded_concurrent_insert_lock=
+ (global_system_variables.low_priority_updates ?
+ TL_WRITE_LOW_PRIORITY : TL_WRITE);
+ return false;
+}
+static Sys_var_mybool Sys_low_priority_updates(
+ "low_priority_updates",
+ "INSERT/DELETE/UPDATE has lower priority than selects",
+ SESSION_VAR(low_priority_updates),
+ CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_low_prio_updates));
+
+static Sys_var_mybool Sys_lower_case_file_system(
+ "lower_case_file_system",
+ "Case sensitivity of file names on the file system where the "
+ "data directory is located",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(lower_case_file_system),
+ NO_CMD_LINE,
+ DEFAULT(FALSE));
+
+static Sys_var_uint Sys_lower_case_table_names(
+ "lower_case_table_names",
+ "If set to 1 table names are stored in lowercase on disk and table "
+ "names will be case-insensitive. Should be set to 2 if you are using "
+ "a case insensitive file system",
+ READ_ONLY GLOBAL_VAR(lower_case_table_names),
+ CMD_LINE(OPT_ARG, OPT_LOWER_CASE_TABLE_NAMES),
+ VALID_RANGE(0, 2),
+#ifdef FN_NO_CASE_SENSE
+ DEFAULT(1),
+#else
+ DEFAULT(0),
+#endif
+ BLOCK_SIZE(1));
+
+static bool session_readonly(sys_var *self, THD *thd, set_var *var)
+{
+ if (var->type == OPT_GLOBAL)
+ return false;
+ my_error(ER_VARIABLE_IS_READONLY, MYF(0), "SESSION",
+ self->name.str, "GLOBAL");
+ return true;
+}
+
+static bool check_max_allowed_packet(sys_var *self, THD *thd, set_var *var)
+{
+ longlong val;
+ if (session_readonly(self, thd, var))
+ return true;
+
+ val= var->save_result.ulonglong_value;
+ if (val < (longlong) global_system_variables.net_buffer_length)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_BELOW_LIMIT, ER(WARN_OPTION_BELOW_LIMIT),
+ "max_allowed_packet", "net_buffer_length");
+ }
+ return false;
+}
+
+
+static Sys_var_ulong Sys_max_allowed_packet(
+ "max_allowed_packet",
+ "Max packet length to send to or receive from the server",
+ SESSION_VAR(max_allowed_packet), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, 1024*1024*1024), DEFAULT(1024*1024),
+ BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_max_allowed_packet));
+
+static Sys_var_ulong Sys_slave_max_allowed_packet(
+ "slave_max_allowed_packet",
+ "The maximum packet length to sent successfully from the master to slave.",
+ GLOBAL_VAR(slave_max_allowed_packet), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, MAX_MAX_ALLOWED_PACKET),
+ DEFAULT(MAX_MAX_ALLOWED_PACKET),
+ BLOCK_SIZE(1024));
+
+static Sys_var_ulonglong Sys_max_binlog_cache_size(
+ "max_binlog_cache_size",
+ "Sets the total size of the transactional cache",
+ GLOBAL_VAR(max_binlog_cache_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(IO_SIZE, SIZE_T_MAX),
+ DEFAULT((SIZE_T_MAX/IO_SIZE)*IO_SIZE),
+ BLOCK_SIZE(IO_SIZE));
+
+static Sys_var_ulonglong Sys_max_binlog_stmt_cache_size(
+ "max_binlog_stmt_cache_size",
+ "Sets the total size of the statement cache",
+ GLOBAL_VAR(max_binlog_stmt_cache_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(IO_SIZE, SIZE_T_MAX),
+ DEFAULT((SIZE_T_MAX/IO_SIZE)*IO_SIZE),
+ BLOCK_SIZE(IO_SIZE));
+
+static bool fix_max_binlog_size(sys_var *self, THD *thd, enum_var_type type)
+{
+ mysql_bin_log.set_max_size(max_binlog_size);
+ return false;
+}
+static Sys_var_ulong Sys_max_binlog_size(
+ "max_binlog_size",
+ "Binary log will be rotated automatically when the size exceeds this "
+ "value.",
+ GLOBAL_VAR(max_binlog_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(IO_SIZE, 1024*1024L*1024L), DEFAULT(1024*1024L*1024L),
+ BLOCK_SIZE(IO_SIZE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_max_binlog_size));
+
+static bool fix_max_connections(sys_var *self, THD *thd, enum_var_type type)
+{
+#ifndef EMBEDDED_LIBRARY
+ resize_thr_alarm(max_connections + extra_max_connections +
+ global_system_variables.max_insert_delayed_threads + 10);
+#endif
+ return false;
+}
+
+// Default max_connections of 151 is larger than Apache's default max
+// children, to avoid "too many connections" error in a common setup
+static Sys_var_ulong Sys_max_connections(
+ "max_connections", "The number of simultaneous clients allowed",
+ PARSED_EARLY GLOBAL_VAR(max_connections), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 100000),
+ DEFAULT(MAX_CONNECTIONS_DEFAULT), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_max_connections));
+
+static Sys_var_ulong Sys_max_connect_errors(
+ "max_connect_errors",
+ "If there is more than this number of interrupted connections from "
+ "a host this host will be blocked from further connections",
+ GLOBAL_VAR(max_connect_errors), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(MAX_CONNECT_ERRORS),
+ BLOCK_SIZE(1));
+
+static Sys_var_long Sys_max_digest_length(
+ "max_digest_length", "Maximum length considered for digest text.",
+ PARSED_EARLY READ_ONLY GLOBAL_VAR(max_digest_length),
+ CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 1024 * 1024), DEFAULT(1024), BLOCK_SIZE(1));
+
+static bool check_max_delayed_threads(sys_var *self, THD *thd, set_var *var)
+{
+ return var->type != OPT_GLOBAL &&
+ var->save_result.ulonglong_value != 0 &&
+ var->save_result.ulonglong_value !=
+ global_system_variables.max_insert_delayed_threads;
+}
+
+// Alias for max_delayed_threads
+static Sys_var_ulong Sys_max_insert_delayed_threads(
+ "max_insert_delayed_threads",
+ "Don't start more than this number of threads to handle INSERT "
+ "DELAYED statements. If set to zero INSERT DELAYED will be not used",
+ SESSION_VAR(max_insert_delayed_threads),
+ NO_CMD_LINE, VALID_RANGE(0, 16384), DEFAULT(20),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_max_delayed_threads), ON_UPDATE(fix_max_connections));
+
+static Sys_var_ulong Sys_max_delayed_threads(
+ "max_delayed_threads",
+ "Don't start more than this number of threads to handle INSERT "
+ "DELAYED statements. If set to zero INSERT DELAYED will be not used",
+ SESSION_VAR(max_insert_delayed_threads),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 16384), DEFAULT(20),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_max_delayed_threads), ON_UPDATE(fix_max_connections));
+
+static Sys_var_ulong Sys_max_error_count(
+ "max_error_count",
+ "Max number of errors/warnings to store for a statement",
+ SESSION_VAR(max_error_count), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 65535), DEFAULT(DEFAULT_ERROR_COUNT), BLOCK_SIZE(1));
+
+static Sys_var_ulonglong Sys_max_heap_table_size(
+ "max_heap_table_size",
+ "Don't allow creation of heap tables bigger than this",
+ SESSION_VAR(max_heap_table_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(16384, (ulonglong)~(intptr)0), DEFAULT(16*1024*1024),
+ BLOCK_SIZE(1024));
+
+static Sys_var_ulong Sys_metadata_locks_cache_size(
+ "metadata_locks_cache_size", "Size of unused metadata locks cache",
+ READ_ONLY GLOBAL_VAR(mdl_locks_cache_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 1024*1024), DEFAULT(MDL_LOCKS_CACHE_SIZE_DEFAULT),
+ BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_metadata_locks_hash_instances(
+ "metadata_locks_hash_instances", "Number of metadata locks hash instances",
+ READ_ONLY GLOBAL_VAR(mdl_locks_hash_partitions), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 1024), DEFAULT(MDL_LOCKS_HASH_PARTITIONS_DEFAULT),
+ BLOCK_SIZE(1));
+
+/*
+ "pseudo_thread_id" variable used in the test suite to detect 32/64bit
+ systems. If you change it to something else then ulong then fix the tests
+ in mysql-test/include/have_32bit.inc and have_64bit.inc.
+*/
+static Sys_var_ulong Sys_pseudo_thread_id(
+ "pseudo_thread_id",
+ "This variable is for internal server use",
+ SESSION_ONLY(pseudo_thread_id),
+ NO_CMD_LINE, VALID_RANGE(0, ULONG_MAX), DEFAULT(0),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, IN_BINLOG,
+ ON_CHECK(check_has_super));
+
+static bool
+check_gtid_domain_id(sys_var *self, THD *thd, set_var *var)
+{
+ if (check_has_super(self, thd, var))
+ return true;
+ if (var->type != OPT_GLOBAL &&
+ error_if_in_trans_or_substatement(thd,
+ ER_STORED_FUNCTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO,
+ ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO))
+ return true;
+
+ return false;
+}
+
+
+static Sys_var_uint Sys_gtid_domain_id(
+ "gtid_domain_id",
+ "Used with global transaction ID to identify logically independent "
+ "replication streams. When events can propagate through multiple "
+ "parallel paths (for example multiple masters), each independent "
+ "source server must use a distinct domain_id. For simple tree-shaped "
+ "replication topologies, it can be left at its default, 0.",
+ SESSION_VAR(gtid_domain_id),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, UINT_MAX32), DEFAULT(0),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_gtid_domain_id));
+
+
+static bool check_gtid_seq_no(sys_var *self, THD *thd, set_var *var)
+{
+ uint32 domain_id, server_id;
+ uint64 seq_no;
+
+ if (check_has_super(self, thd, var))
+ return true;
+ if (error_if_in_trans_or_substatement(thd,
+ ER_STORED_FUNCTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO,
+ ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO))
+ return true;
+
+ domain_id= thd->variables.gtid_domain_id;
+ server_id= thd->variables.server_id;
+ seq_no= (uint64)var->value->val_uint();
+ DBUG_EXECUTE_IF("ignore_set_gtid_seq_no_check", return 0;);
+ if (opt_gtid_strict_mode && opt_bin_log &&
+ mysql_bin_log.check_strict_gtid_sequence(domain_id, server_id, seq_no))
+ return true;
+
+ return false;
+}
+
+
+static Sys_var_ulonglong Sys_gtid_seq_no(
+ "gtid_seq_no",
+ "Internal server usage, for replication with global transaction id. "
+ "When set, next event group logged to the binary log will use this "
+ "sequence number, not generate a new one, thus allowing to preserve "
+ "master's GTID in slave's binlog.",
+ SESSION_ONLY(gtid_seq_no),
+ NO_CMD_LINE, VALID_RANGE(0, ULONGLONG_MAX), DEFAULT(0),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_gtid_seq_no));
+
+
+#ifdef HAVE_REPLICATION
+static unsigned char opt_gtid_binlog_pos_dummy;
+static Sys_var_gtid_binlog_pos Sys_gtid_binlog_pos(
+ "gtid_binlog_pos", "Last GTID logged to the binary log, per replication"
+ "domain",
+ READ_ONLY GLOBAL_VAR(opt_gtid_binlog_pos_dummy), NO_CMD_LINE);
+
+
+uchar *
+Sys_var_gtid_binlog_pos::global_value_ptr(THD *thd, LEX_STRING *base)
+{
+ char buf[128];
+ String str(buf, sizeof(buf), system_charset_info);
+ char *p;
+
+ str.length(0);
+ if ((opt_bin_log && mysql_bin_log.append_state_pos(&str)) ||
+ !(p= thd->strmake(str.ptr(), str.length())))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return NULL;
+ }
+
+ return (uchar *)p;
+}
+
+
+static unsigned char opt_gtid_current_pos_dummy;
+static Sys_var_gtid_current_pos Sys_gtid_current_pos(
+ "gtid_current_pos", "Current GTID position of the server. Per "
+ "replication domain, this is either the last GTID replicated by a "
+ "slave thread, or the GTID logged to the binary log, whichever is "
+ "most recent.",
+ READ_ONLY GLOBAL_VAR(opt_gtid_current_pos_dummy), NO_CMD_LINE);
+
+
+uchar *
+Sys_var_gtid_current_pos::global_value_ptr(THD *thd, LEX_STRING *base)
+{
+ String str;
+ char *p;
+
+ str.length(0);
+ if (rpl_append_gtid_state(&str, true) ||
+ !(p= thd->strmake(str.ptr(), str.length())))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return NULL;
+ }
+
+ return (uchar *)p;
+}
+
+
+bool
+Sys_var_gtid_slave_pos::do_check(THD *thd, set_var *var)
+{
+ String str, *res;
+ bool running;
+
+ DBUG_ASSERT(var->type == OPT_GLOBAL);
+
+ if (rpl_load_gtid_slave_state(thd))
+ {
+ my_error(ER_CANNOT_LOAD_SLAVE_GTID_STATE, MYF(0), "mysql",
+ rpl_gtid_slave_state_table_name.str);
+ return true;
+ }
+
+ mysql_mutex_lock(&LOCK_active_mi);
+ running= master_info_index->give_error_if_slave_running();
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (running)
+ return true;
+ if (!(res= var->value->val_str(&str)))
+ return true;
+ if (thd->in_active_multi_stmt_transaction())
+ {
+ my_error(ER_CANT_DO_THIS_DURING_AN_TRANSACTION, MYF(0));
+ return true;
+ }
+ if (rpl_gtid_pos_check(thd, &((*res)[0]), res->length()))
+ return true;
+
+ if (!(var->save_result.string_value.str=
+ thd->strmake(res->ptr(), res->length())))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return true;
+ }
+ var->save_result.string_value.length= res->length();
+ return false;
+}
+
+
+bool
+Sys_var_gtid_slave_pos::global_update(THD *thd, set_var *var)
+{
+ bool err;
+
+ DBUG_ASSERT(var->type == OPT_GLOBAL);
+
+ if (!var->value)
+ {
+ my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str);
+ return true;
+ }
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index->give_error_if_slave_running())
+ err= true;
+ else
+ err= rpl_gtid_pos_update(thd, var->save_result.string_value.str,
+ var->save_result.string_value.length);
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ return err;
+}
+
+
+uchar *
+Sys_var_gtid_slave_pos::global_value_ptr(THD *thd, LEX_STRING *base)
+{
+ String str;
+ char *p;
+
+ str.length(0);
+ /*
+ If the mysql.rpl_slave_pos table could not be loaded, then we cannot
+ easily automatically try to reload it here - we may be inside a statement
+ that already has tables locked and so opening more tables is problematic.
+
+ But if the table is not loaded (eg. missing mysql_upgrade_db or some such),
+ then the slave state must be empty anyway.
+ */
+ if ((rpl_global_gtid_slave_state.loaded &&
+ rpl_append_gtid_state(&str, false)) ||
+ !(p= thd->strmake(str.ptr(), str.length())))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return NULL;
+ }
+
+ return (uchar *)p;
+}
+
+
+static unsigned char opt_gtid_slave_pos_dummy;
+static Sys_var_gtid_slave_pos Sys_gtid_slave_pos(
+ "gtid_slave_pos",
+ "The list of global transaction IDs that were last replicated on the "
+ "server, one for each replication domain.",
+ GLOBAL_VAR(opt_gtid_slave_pos_dummy), NO_CMD_LINE);
+
+
+static Sys_var_mybool Sys_gtid_strict_mode(
+ "gtid_strict_mode",
+ "Enforce strict seq_no ordering of events in the binary log. Slave "
+ "stops with an error if it encounters an event that would cause it to "
+ "generate an out-of-order binlog if executed.",
+ GLOBAL_VAR(opt_gtid_strict_mode),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+
+struct gtid_binlog_state_data { rpl_gtid *list; uint32 list_len; };
+
+bool
+Sys_var_gtid_binlog_state::do_check(THD *thd, set_var *var)
+{
+ String str, *res;
+ struct gtid_binlog_state_data *data;
+ rpl_gtid *list;
+ uint32 list_len;
+
+ DBUG_ASSERT(var->type == OPT_GLOBAL);
+
+ if (!(res= var->value->val_str(&str)))
+ return true;
+ if (thd->in_active_multi_stmt_transaction())
+ {
+ my_error(ER_CANT_DO_THIS_DURING_AN_TRANSACTION, MYF(0));
+ return true;
+ }
+ if (!mysql_bin_log.is_open())
+ {
+ my_error(ER_FLUSH_MASTER_BINLOG_CLOSED, MYF(0));
+ return true;
+ }
+ if (!mysql_bin_log.is_empty_state())
+ {
+ my_error(ER_BINLOG_MUST_BE_EMPTY, MYF(0));
+ return true;
+ }
+ if (res->length() == 0)
+ list= NULL;
+ else if (!(list= gtid_parse_string_to_list(res->ptr(), res->length(),
+ &list_len)))
+ {
+ my_error(ER_INCORRECT_GTID_STATE, MYF(0));
+ return true;
+ }
+ if (!(data= (gtid_binlog_state_data *)my_malloc(sizeof(*data), MYF(0))))
+ {
+ my_free(list);
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return true;
+ }
+ data->list= list;
+ data->list_len= list_len;
+ var->save_result.ptr= data;
+ return false;
+}
+
+
+bool
+Sys_var_gtid_binlog_state::global_update(THD *thd, set_var *var)
+{
+ bool res;
+
+ DBUG_ASSERT(var->type == OPT_GLOBAL);
+
+ if (!var->value)
+ {
+ my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str);
+ return true;
+ }
+
+ struct gtid_binlog_state_data *data=
+ (struct gtid_binlog_state_data *)var->save_result.ptr;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ res= (0 != reset_master(thd, data->list, data->list_len));
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ my_free(data->list);
+ my_free(data);
+ return res;
+}
+
+
+uchar *
+Sys_var_gtid_binlog_state::global_value_ptr(THD *thd, LEX_STRING *base)
+{
+ char buf[512];
+ String str(buf, sizeof(buf), system_charset_info);
+ char *p;
+
+ str.length(0);
+ if ((opt_bin_log && mysql_bin_log.append_state(&str)) ||
+ !(p= thd->strmake(str.ptr(), str.length())))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return NULL;
+ }
+
+ return (uchar *)p;
+}
+
+
+static unsigned char opt_gtid_binlog_state_dummy;
+static Sys_var_gtid_binlog_state Sys_gtid_binlog_state(
+ "gtid_binlog_state",
+ "The internal GTID state of the binlog, used to keep track of all "
+ "GTIDs ever logged to the binlog.",
+ GLOBAL_VAR(opt_gtid_binlog_state_dummy), NO_CMD_LINE);
+
+
+static Sys_var_last_gtid Sys_last_gtid(
+ "last_gtid", "The GTID of the last commit (if binlogging was enabled), "
+ "or the empty string if none.",
+ READ_ONLY sys_var::ONLY_SESSION, NO_CMD_LINE);
+
+
+uchar *
+Sys_var_last_gtid::session_value_ptr(THD *thd, LEX_STRING *base)
+{
+ char buf[10+1+10+1+20+1];
+ String str(buf, sizeof(buf), system_charset_info);
+ char *p;
+ bool first= true;
+
+ str.length(0);
+ if ((thd->last_commit_gtid.seq_no > 0 &&
+ rpl_slave_state_tostring_helper(&str, &thd->last_commit_gtid, &first)) ||
+ !(p= thd->strmake(str.ptr(), str.length())))
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ return NULL;
+ }
+
+ return (uchar *)p;
+}
+
+
+static bool
+check_slave_parallel_threads(sys_var *self, THD *thd, set_var *var)
+{
+ bool running;
+
+ mysql_mutex_lock(&LOCK_active_mi);
+ running= master_info_index->give_error_if_slave_running();
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (running)
+ return true;
+
+ return false;
+}
+
+static bool
+fix_slave_parallel_threads(sys_var *self, THD *thd, enum_var_type type)
+{
+ bool err;
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ err= master_info_index->give_error_if_slave_running();
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+
+ return err;
+}
+
+
+static Sys_var_ulong Sys_slave_parallel_threads(
+ "slave_parallel_threads",
+ "If non-zero, number of threads to spawn to apply in parallel events "
+ "on the slave that were group-committed on the master or were logged "
+ "with GTID in different replication domains. Note that these threads "
+ "are in addition to the IO and SQL threads, which are always created "
+ "by a replication slave",
+ GLOBAL_VAR(opt_slave_parallel_threads), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0,16383), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(check_slave_parallel_threads),
+ ON_UPDATE(fix_slave_parallel_threads));
+
+
+static bool
+check_slave_domain_parallel_threads(sys_var *self, THD *thd, set_var *var)
+{
+ bool running;
+
+ mysql_mutex_lock(&LOCK_active_mi);
+ running= master_info_index->give_error_if_slave_running();
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (running)
+ return true;
+
+ return false;
+}
+
+static bool
+fix_slave_domain_parallel_threads(sys_var *self, THD *thd, enum_var_type type)
+{
+ bool running;
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ running= master_info_index->give_error_if_slave_running();
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+
+ return running ? true : false;
+}
+
+
+static Sys_var_ulong Sys_slave_domain_parallel_threads(
+ "slave_domain_parallel_threads",
+ "Maximum number of parallel threads to use on slave for events in a "
+ "single replication domain. When using multiple domains, this can be "
+ "used to limit a single domain from grabbing all threads and thus "
+ "stalling other domains. The default of 0 means to allow a domain to "
+ "grab as many threads as it wants, up to the value of "
+ "slave_parallel_threads.",
+ GLOBAL_VAR(opt_slave_domain_parallel_threads), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0,16383), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(check_slave_domain_parallel_threads),
+ ON_UPDATE(fix_slave_domain_parallel_threads));
+
+
+static Sys_var_ulong Sys_slave_parallel_max_queued(
+ "slave_parallel_max_queued",
+ "Limit on how much memory SQL threads should use per parallel "
+ "replication thread when reading ahead in the relay log looking for "
+ "opportunities for parallel replication. Only used when "
+ "--slave-parallel-threads > 0.",
+ GLOBAL_VAR(opt_slave_parallel_max_queued), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0,2147483647), DEFAULT(131072), BLOCK_SIZE(1));
+
+
+static bool
+check_gtid_ignore_duplicates(sys_var *self, THD *thd, set_var *var)
+{
+ bool running;
+
+ mysql_mutex_lock(&LOCK_active_mi);
+ running= master_info_index->give_error_if_slave_running();
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (running)
+ return true;
+
+ return false;
+}
+
+static bool
+fix_gtid_ignore_duplicates(sys_var *self, THD *thd, enum_var_type type)
+{
+ bool running;
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ running= master_info_index->give_error_if_slave_running();
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+
+ return running ? true : false;
+}
+
+
+static Sys_var_mybool Sys_gtid_ignore_duplicates(
+ "gtid_ignore_duplicates",
+ "When set, different master connections in multi-source replication are "
+ "allowed to receive and process event groups with the same GTID (when "
+ "using GTID mode). Only one will be applied, any others will be "
+ "ignored. Within a given replication domain, just the sequence number "
+ "will be used to decide whether a given GTID has been already applied; "
+ "this means it is the responsibility of the user to ensure that GTID "
+ "sequence numbers are strictly increasing.",
+ GLOBAL_VAR(opt_gtid_ignore_duplicates), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(check_gtid_ignore_duplicates),
+ ON_UPDATE(fix_gtid_ignore_duplicates));
+#endif
+
+
+static Sys_var_ulong Sys_binlog_commit_wait_count(
+ "binlog_commit_wait_count",
+ "If non-zero, binlog write will wait at most binlog_commit_wait_usec "
+ "microseconds for at least this many commits to queue up for group "
+ "commit to the binlog. This can reduce I/O on the binlog and provide "
+ "increased opportunity for parallel apply on the slave, but too high "
+ "a value will decrease commit throughput.",
+ GLOBAL_VAR(opt_binlog_commit_wait_count), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1));
+
+
+static Sys_var_ulong Sys_binlog_commit_wait_usec(
+ "binlog_commit_wait_usec",
+ "Maximum time, in microseconds, to wait for more commits to queue up "
+ "for binlog group commit. Only takes effect if the value of "
+ "binlog_commit_wait_count is non-zero.",
+ GLOBAL_VAR(opt_binlog_commit_wait_usec), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, ULONG_MAX), DEFAULT(100000), BLOCK_SIZE(1));
+
+
+static bool fix_max_join_size(sys_var *self, THD *thd, enum_var_type type)
+{
+ SV *sv= type == OPT_GLOBAL ? &global_system_variables : &thd->variables;
+ if (sv->max_join_size == HA_POS_ERROR)
+ sv->option_bits|= OPTION_BIG_SELECTS;
+ else
+ sv->option_bits&= ~OPTION_BIG_SELECTS;
+ return false;
+}
+static Sys_var_harows Sys_max_join_size(
+ "max_join_size",
+ "Joins that are probably going to read more than max_join_size "
+ "records return an error",
+ SESSION_VAR(max_join_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, HA_POS_ERROR), DEFAULT(HA_POS_ERROR), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_max_join_size));
+
+static Sys_var_ulong Sys_max_seeks_for_key(
+ "max_seeks_for_key",
+ "Limit assumed max number of seeks when looking up rows based on a key",
+ SESSION_VAR(max_seeks_for_key), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(UINT_MAX), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_max_length_for_sort_data(
+ "max_length_for_sort_data",
+ "Max number of bytes in sorted records",
+ SESSION_VAR(max_length_for_sort_data), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(4, 8192*1024L), DEFAULT(1024), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_max_long_data_size(
+ "max_long_data_size",
+ "The maximum BLOB length to send to server from "
+ "mysql_send_long_data API. Deprecated option; "
+ "use max_allowed_packet instead.",
+ READ_ONLY GLOBAL_VAR(max_long_data_size),
+ CMD_LINE(REQUIRED_ARG, OPT_MAX_LONG_DATA_SIZE),
+ VALID_RANGE(1024, UINT_MAX32), DEFAULT(1024*1024),
+ BLOCK_SIZE(1));
+
+static PolyLock_mutex PLock_prepared_stmt_count(&LOCK_prepared_stmt_count);
+static Sys_var_ulong Sys_max_prepared_stmt_count(
+ "max_prepared_stmt_count",
+ "Maximum number of prepared statements in the server",
+ GLOBAL_VAR(max_prepared_stmt_count), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 1024*1024), DEFAULT(16382), BLOCK_SIZE(1),
+ &PLock_prepared_stmt_count);
+
+static Sys_var_ulong Sys_max_sort_length(
+ "max_sort_length",
+ "The number of bytes to use when sorting BLOB or TEXT values (only "
+ "the first max_sort_length bytes of each value are used; the rest "
+ "are ignored)",
+ SESSION_VAR(max_sort_length), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(4, 8192*1024L), DEFAULT(1024), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_max_sp_recursion_depth(
+ "max_sp_recursion_depth",
+ "Maximum stored procedure recursion depth",
+ SESSION_VAR(max_sp_recursion_depth), CMD_LINE(OPT_ARG),
+ VALID_RANGE(0, 255), DEFAULT(0), BLOCK_SIZE(1));
+
+
+static bool if_checking_enabled(sys_var *self, THD *thd, set_var *var)
+{
+ if (session_readonly(self, thd, var))
+ return true;
+
+ if (!max_user_connections_checking)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--max-user-connections=0");
+ return true;
+ }
+
+ return false;
+}
+// non-standard session_value_ptr() here
+static Sys_var_max_user_conn Sys_max_user_connections(
+ "max_user_connections",
+ "The maximum number of active connections for a single user "
+ "(0 = no limit)",
+ SESSION_VAR(max_user_connections), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(-1, INT_MAX), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(if_checking_enabled));
+
+static Sys_var_ulong Sys_max_tmp_tables(
+ "max_tmp_tables",
+ "Maximum number of temporary tables a client can keep open at a time",
+ SESSION_VAR(max_tmp_tables), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(32), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_max_write_lock_count(
+ "max_write_lock_count",
+ "After this many write locks, allow some read locks to run in between",
+ GLOBAL_VAR(max_write_lock_count), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(UINT_MAX), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_min_examined_row_limit(
+ "min_examined_row_limit",
+ "Don't write queries to slow log that examine fewer rows "
+ "than that",
+ SESSION_VAR(min_examined_row_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+
+#ifdef _WIN32
+static Sys_var_mybool Sys_named_pipe(
+ "named_pipe", "Enable the named pipe (NT)",
+ READ_ONLY GLOBAL_VAR(opt_enable_named_pipe), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+#endif
+
+
+static bool check_net_buffer_length(sys_var *self, THD *thd, set_var *var)
+{
+ longlong val;
+ if (session_readonly(self, thd, var))
+ return true;
+
+ val= var->save_result.ulonglong_value;
+ if (val > (longlong) global_system_variables.max_allowed_packet)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_OPTION_BELOW_LIMIT, ER(WARN_OPTION_BELOW_LIMIT),
+ "max_allowed_packet", "net_buffer_length");
+ }
+ return false;
+}
+static Sys_var_ulong Sys_net_buffer_length(
+ "net_buffer_length",
+ "Buffer length for TCP/IP and socket communication",
+ SESSION_VAR(net_buffer_length), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, 1024*1024), DEFAULT(16384), BLOCK_SIZE(1024),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_net_buffer_length));
+
+static bool fix_net_read_timeout(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type != OPT_GLOBAL)
+ my_net_set_read_timeout(&thd->net, thd->variables.net_read_timeout);
+ return false;
+}
+static Sys_var_ulong Sys_net_read_timeout(
+ "net_read_timeout",
+ "Number of seconds to wait for more data from a connection before "
+ "aborting the read",
+ SESSION_VAR(net_read_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(NET_READ_TIMEOUT), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_net_read_timeout));
+
+static bool fix_net_write_timeout(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type != OPT_GLOBAL)
+ my_net_set_write_timeout(&thd->net, thd->variables.net_write_timeout);
+ return false;
+}
+static Sys_var_ulong Sys_net_write_timeout(
+ "net_write_timeout",
+ "Number of seconds to wait for a block to be written to a connection "
+ "before aborting the write",
+ SESSION_VAR(net_write_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(NET_WRITE_TIMEOUT), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_net_write_timeout));
+
+static bool fix_net_retry_count(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type != OPT_GLOBAL)
+ thd->net.retry_count=thd->variables.net_retry_count;
+ return false;
+}
+static Sys_var_ulong Sys_net_retry_count(
+ "net_retry_count",
+ "If a read on a communication port is interrupted, retry this "
+ "many times before giving up",
+ SESSION_VAR(net_retry_count), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(MYSQLD_NET_RETRY_COUNT),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_net_retry_count));
+
+static Sys_var_mybool Sys_old_mode(
+ "old", "Use compatible behavior from previous MariaDB version. See also --old-mode",
+ SESSION_VAR(old_mode), CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static Sys_var_mybool Sys_old_alter_table(
+ "old_alter_table", "Use old, non-optimized alter table",
+ SESSION_VAR(old_alter_table), CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static bool check_old_passwords(sys_var *self, THD *thd, set_var *var)
+{
+ return mysql_user_table_is_in_short_password_format;
+}
+static Sys_var_mybool Sys_old_passwords(
+ "old_passwords",
+ "Use old password encryption method (needed for 4.0 and older clients)",
+ SESSION_VAR(old_passwords), CMD_LINE(OPT_ARG), DEFAULT(FALSE),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_old_passwords));
+
+static Sys_var_ulong Sys_open_files_limit(
+ "open_files_limit",
+ "If this is not 0, then mysqld will use this value to reserve file "
+ "descriptors to use with setrlimit(). If this value is 0 then mysqld "
+ "will reserve max_connections*5 or max_connections + table_cache*2 "
+ "(whichever is larger) number of file descriptors",
+ READ_ONLY GLOBAL_VAR(open_files_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, OS_FILE_LIMIT), DEFAULT(0), BLOCK_SIZE(1));
+
+/// @todo change to enum
+static Sys_var_ulong Sys_optimizer_prune_level(
+ "optimizer_prune_level",
+ "Controls the heuristic(s) applied during query optimization to prune "
+ "less-promising partial plans from the optimizer search space. "
+ "Meaning: 0 - do not apply any heuristic, thus perform exhaustive "
+ "search; 1 - prune plans based on number of retrieved rows",
+ SESSION_VAR(optimizer_prune_level), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 1), DEFAULT(1), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_optimizer_selectivity_sampling_limit(
+ "optimizer_selectivity_sampling_limit",
+ "Controls number of record samples to check condition selectivity",
+ SESSION_VAR(optimizer_selectivity_sampling_limit),
+ CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(SELECTIVITY_SAMPLING_THRESHOLD, UINT_MAX),
+ DEFAULT(SELECTIVITY_SAMPLING_LIMIT), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_optimizer_use_condition_selectivity(
+ "optimizer_use_condition_selectivity",
+ "Controls selectivity of which conditions the optimizer takes into "
+ "account to calculate cardinality of a partial join when it searches "
+ "for the best execution plan "
+ "Meaning: "
+ "1 - use selectivity of index backed range conditions to calculate "
+ "the cardinality of a partial join if the last joined table is "
+ "accessed by full table scan or an index scan, "
+ "2 - use selectivity of index backed range conditions to calculate "
+ "the cardinality of a partial join in any case, "
+ "3 - additionally always use selectivity of range conditions that are "
+ "not backed by any index to calculate the cardinality of a partial join, "
+ "4 - use histograms to calculate selectivity of range conditions that "
+ "are not backed by any index to calculate the cardinality of "
+ "a partial join."
+ "5 - additionally use selectivity of certain non-range predicates "
+ "calculated on record samples",
+ SESSION_VAR(optimizer_use_condition_selectivity), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 5), DEFAULT(1), BLOCK_SIZE(1));
+
+/** Warns about deprecated value 63 */
+static bool fix_optimizer_search_depth(sys_var *self, THD *thd,
+ enum_var_type type)
+{
+ SV *sv= type == OPT_GLOBAL ? &global_system_variables : &thd->variables;
+ if (sv->optimizer_search_depth == MAX_TABLES+2)
+ WARN_DEPRECATED(thd, 10, 1, "optimizer-search-depth=63",
+ "a search depth less than 63");
+ return false;
+}
+
+static Sys_var_ulong Sys_optimizer_search_depth(
+ "optimizer_search_depth",
+ "Maximum depth of search performed by the query optimizer. Values "
+ "larger than the number of relations in a query result in better "
+ "query plans, but take longer to compile a query. Values smaller "
+ "than the number of tables in a relation result in faster "
+ "optimization, but may produce very bad query plans. If set to 0, "
+ "the system will automatically pick a reasonable value; if set to "
+ "63, the optimizer will switch to the original find_best search. "
+ "NOTE: The value 63 and its associated behaviour is deprecated",
+ SESSION_VAR(optimizer_search_depth), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, MAX_TABLES+2), DEFAULT(MAX_TABLES+1), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_optimizer_search_depth));
+
+/* this is used in the sigsegv handler */
+export const char *optimizer_switch_names[]=
+{
+ "index_merge","index_merge_union","index_merge_sort_union",
+ "index_merge_intersection","index_merge_sort_intersection",
+ "engine_condition_pushdown",
+ "index_condition_pushdown",
+ "derived_merge", "derived_with_keys",
+ "firstmatch","loosescan","materialization","in_to_exists","semijoin",
+ "partial_match_rowid_merge",
+ "partial_match_table_scan",
+ "subquery_cache",
+ "mrr",
+ "mrr_cost_based",
+ "mrr_sort_keys",
+ "outer_join_with_cache",
+ "semijoin_with_cache",
+ "join_cache_incremental",
+ "join_cache_hashed",
+ "join_cache_bka",
+ "optimize_join_buffer_size",
+ "table_elimination",
+ "extended_keys",
+ "exists_to_in",
+ "default", NullS
+};
+/** propagates changes to @@engine_condition_pushdown */
+static bool fix_optimizer_switch(sys_var *self, THD *thd,
+ enum_var_type type)
+{
+ SV *sv= (type == OPT_GLOBAL) ? &global_system_variables : &thd->variables;
+ sv->engine_condition_pushdown=
+ MY_TEST(sv->optimizer_switch & OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN);
+ return false;
+}
+static Sys_var_flagset Sys_optimizer_switch(
+ "optimizer_switch",
+ "optimizer_switch=option=val[,option=val...], where option is one of {"
+ "derived_merge, "
+ "derived_with_keys, "
+ "firstmatch, "
+ "in_to_exists, "
+ "engine_condition_pushdown, "
+ "index_condition_pushdown, "
+ "index_merge, "
+ "index_merge_intersection, "
+ "index_merge_sort_intersection, "
+ "index_merge_sort_union, "
+ "index_merge_union, "
+ "join_cache_bka, "
+ "join_cache_hashed, "
+ "join_cache_incremental, "
+ "loosescan, "
+ "materialization, "
+ "mrr, "
+ "mrr_cost_based, "
+ "mrr_sort_keys, "
+ "optimize_join_buffer_size, "
+ "outer_join_with_cache, "
+ "partial_match_rowid_merge, "
+ "partial_match_table_scan, "
+ "semijoin, "
+ "semijoin_with_cache, "
+ "subquery_cache, "
+ "table_elimination, "
+ "extended_keys, "
+ "exists_to_in "
+ "} and val is one of {on, off, default}",
+ SESSION_VAR(optimizer_switch), CMD_LINE(REQUIRED_ARG),
+ optimizer_switch_names, DEFAULT(OPTIMIZER_SWITCH_DEFAULT),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL),
+ ON_UPDATE(fix_optimizer_switch));
+
+static Sys_var_charptr Sys_pid_file(
+ "pid_file", "Pid file used by safe_mysqld",
+ READ_ONLY GLOBAL_VAR(pidfile_name_ptr), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_plugin_dir(
+ "plugin_dir", "Directory for plugins",
+ READ_ONLY GLOBAL_VAR(opt_plugin_dir_ptr), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_uint Sys_port(
+ "port",
+ "Port number to use for connection or 0 to default to, "
+ "my.cnf, $MYSQL_TCP_PORT, "
+#if MYSQL_PORT_DEFAULT == 0
+ "/etc/services, "
+#endif
+ "built-in default (" STRINGIFY_ARG(MYSQL_PORT) "), whatever comes first",
+ READ_ONLY GLOBAL_VAR(mysqld_port), CMD_LINE(REQUIRED_ARG, 'P'),
+ VALID_RANGE(0, UINT_MAX32), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_preload_buff_size(
+ "preload_buffer_size",
+ "The size of the buffer that is allocated when preloading indexes",
+ SESSION_VAR(preload_buff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, 1024*1024*1024), DEFAULT(32768), BLOCK_SIZE(1));
+
+static Sys_var_uint Sys_protocol_version(
+ "protocol_version",
+ "The version of the client/server protocol used by the MySQL server",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(protocol_version), NO_CMD_LINE,
+ VALID_RANGE(0, ~0), DEFAULT(PROTOCOL_VERSION), BLOCK_SIZE(1));
+
+static Sys_var_proxy_user Sys_proxy_user(
+ "proxy_user", "The proxy user account name used when logging in",
+ IN_SYSTEM_CHARSET);
+
+static Sys_var_external_user Sys_exterenal_user(
+ "external_user", "The external user account used when logging in",
+ IN_SYSTEM_CHARSET);
+
+static Sys_var_ulong Sys_read_buff_size(
+ "read_buffer_size",
+ "Each thread that does a sequential scan allocates a buffer of "
+ "this size for each table it scans. If you do many sequential scans, "
+ "you may want to increase this value",
+ SESSION_VAR(read_buff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(IO_SIZE*2, INT_MAX32), DEFAULT(128*1024),
+ BLOCK_SIZE(IO_SIZE));
+
+static bool check_read_only(sys_var *self, THD *thd, set_var *var)
+{
+ /* Prevent self dead-lock */
+ if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction())
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ return true;
+ }
+ return false;
+}
+static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type)
+{
+ bool result= true;
+ my_bool new_read_only= read_only; // make a copy before releasing a mutex
+ DBUG_ENTER("sys_var_opt_readonly::update");
+
+ if (read_only == FALSE || read_only == opt_readonly)
+ {
+ opt_readonly= read_only;
+ DBUG_RETURN(false);
+ }
+
+ if (check_read_only(self, thd, 0)) // just in case
+ goto end;
+
+ if (thd->global_read_lock.is_acquired())
+ {
+ /*
+ This connection already holds the global read lock.
+ This can be the case with:
+ - FLUSH TABLES WITH READ LOCK
+ - SET GLOBAL READ_ONLY = 1
+ */
+ opt_readonly= read_only;
+ DBUG_RETURN(false);
+ }
+
+ /*
+ READ_ONLY=1 prevents write locks from being taken on tables and
+ blocks transactions from committing. We therefore should make sure
+ that no such events occur while setting the read_only variable.
+ This is a 2 step process:
+ [1] lock_global_read_lock()
+ Prevents connections from obtaining new write locks on
+ tables. Note that we can still have active rw transactions.
+ [2] make_global_read_lock_block_commit()
+ Prevents transactions from committing.
+ */
+
+ read_only= opt_readonly;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ if (thd->global_read_lock.lock_global_read_lock(thd))
+ goto end_with_mutex_unlock;
+
+ if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd)))
+ goto end_with_read_lock;
+
+ /* Change the opt_readonly system variable, safe because the lock is held */
+ opt_readonly= new_read_only;
+ result= false;
+
+ end_with_read_lock:
+ /* Release the lock */
+ thd->global_read_lock.unlock_global_read_lock(thd);
+ end_with_mutex_unlock:
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ end:
+ read_only= opt_readonly;
+ DBUG_RETURN(result);
+}
+
+
+/**
+ The read_only boolean is always equal to the opt_readonly boolean except
+ during fix_read_only(); when that function is entered, opt_readonly is
+ the pre-update value and read_only is the post-update value.
+ fix_read_only() compares them and runs needed operations for the
+ transition (especially when transitioning from false to true) and
+ synchronizes both booleans in the end.
+*/
+static Sys_var_mybool Sys_readonly(
+ "read_only",
+ "Make all non-temporary tables read-only, with the exception for "
+ "replication (slave) threads and users with the SUPER privilege",
+ GLOBAL_VAR(read_only), CMD_LINE(OPT_ARG), DEFAULT(FALSE),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_read_only), ON_UPDATE(fix_read_only));
+
+// Small lower limit to be able to test MRR
+static Sys_var_ulong Sys_read_rnd_buff_size(
+ "read_rnd_buffer_size",
+ "When reading rows in sorted order after a sort, the rows are read "
+ "through this buffer to avoid a disk seeks",
+ SESSION_VAR(read_rnd_buff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, INT_MAX32), DEFAULT(256*1024), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_div_precincrement(
+ "div_precision_increment", "Precision of the result of '/' "
+ "operator will be increased on that value",
+ SESSION_VAR(div_precincrement), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, DECIMAL_MAX_SCALE), DEFAULT(4), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_rpl_recovery_rank(
+ "rpl_recovery_rank", "Unused, will be removed",
+ GLOBAL_VAR(rpl_recovery_rank), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0),
+ DEPRECATED(""));
+
+static Sys_var_ulong Sys_range_alloc_block_size(
+ "range_alloc_block_size",
+ "Allocation block size for storing ranges during optimization",
+ SESSION_VAR(range_alloc_block_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(RANGE_ALLOC_BLOCK_SIZE, UINT_MAX),
+ DEFAULT(RANGE_ALLOC_BLOCK_SIZE), BLOCK_SIZE(1024));
+
+static Sys_var_ulong Sys_multi_range_count(
+ "multi_range_count", "Ignored. Use mrr_buffer_size instead",
+ SESSION_VAR(multi_range_count), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, ULONG_MAX), DEFAULT(256), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0),
+ DEPRECATED("'@@mrr_buffer_size'"));
+
+static bool fix_thd_mem_root(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type != OPT_GLOBAL)
+ reset_root_defaults(thd->mem_root,
+ thd->variables.query_alloc_block_size,
+ thd->variables.query_prealloc_size);
+ return false;
+}
+static Sys_var_ulong Sys_query_alloc_block_size(
+ "query_alloc_block_size",
+ "Allocation block size for query parsing and execution",
+ SESSION_VAR(query_alloc_block_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, UINT_MAX), DEFAULT(QUERY_ALLOC_BLOCK_SIZE),
+ BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_thd_mem_root));
+
+static Sys_var_ulong Sys_query_prealloc_size(
+ "query_prealloc_size",
+ "Persistent buffer for query parsing and execution",
+ SESSION_VAR(query_prealloc_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(QUERY_ALLOC_PREALLOC_SIZE, UINT_MAX),
+ DEFAULT(QUERY_ALLOC_PREALLOC_SIZE),
+ BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_thd_mem_root));
+
+#ifdef HAVE_SMEM
+static Sys_var_mybool Sys_shared_memory(
+ "shared_memory", "Enable the shared memory",
+ READ_ONLY GLOBAL_VAR(opt_enable_shared_memory), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+static Sys_var_charptr Sys_shared_memory_base_name(
+ "shared_memory_base_name", "Base name of shared memory",
+ READ_ONLY GLOBAL_VAR(shared_memory_base_name), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+#endif
+
+// this has to be NO_CMD_LINE as the command-line option has a different name
+static Sys_var_mybool Sys_skip_external_locking(
+ "skip_external_locking", "Don't use system (external) locking",
+ READ_ONLY GLOBAL_VAR(my_disable_locking), NO_CMD_LINE, DEFAULT(TRUE));
+
+static Sys_var_mybool Sys_skip_networking(
+ "skip_networking", "Don't allow connection with TCP/IP",
+ READ_ONLY GLOBAL_VAR(opt_disable_networking), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+static Sys_var_mybool Sys_skip_name_resolve(
+ "skip_name_resolve",
+ "Don't resolve hostnames. All hostnames are IP's or 'localhost'.",
+ READ_ONLY GLOBAL_VAR(opt_skip_name_resolve),
+ CMD_LINE(OPT_ARG, OPT_SKIP_RESOLVE),
+ DEFAULT(FALSE));
+
+static Sys_var_mybool Sys_skip_show_database(
+ "skip_show_database", "Don't allow 'SHOW DATABASE' commands",
+ READ_ONLY GLOBAL_VAR(opt_skip_show_db), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+static Sys_var_charptr Sys_socket(
+ "socket", "Socket file to use for connection",
+ READ_ONLY GLOBAL_VAR(mysqld_unix_port), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+/*
+ thread_concurrency is a no-op on all platforms since
+ MySQL 5.1. It will be removed in the context of
+ WL#5265
+*/
+static Sys_var_ulong Sys_thread_concurrency(
+ "thread_concurrency",
+ "Permits the application to give the threads system a hint for "
+ "the desired number of threads that should be run at the same time."
+ "This variable has no effect, and is deprecated. "
+ "It will be removed in a future release.",
+ READ_ONLY GLOBAL_VAR(concurrency),
+ CMD_LINE(REQUIRED_ARG, OPT_THREAD_CONCURRENCY),
+ VALID_RANGE(1, 512), DEFAULT(DEFAULT_CONCURRENCY), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0),
+ DEPRECATED(""));
+
+static Sys_var_ulonglong Sys_thread_stack(
+ "thread_stack", "The stack size for each thread",
+ READ_ONLY GLOBAL_VAR(my_thread_stack_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(128*1024, ULONGLONG_MAX), DEFAULT(DEFAULT_THREAD_STACK),
+ BLOCK_SIZE(1024));
+
+static Sys_var_charptr Sys_tmpdir(
+ "tmpdir", "Path for temporary files. Several paths may "
+ "be specified, separated by a "
+#if defined(__WIN__)
+ "semicolon (;)"
+#else
+ "colon (:)"
+#endif
+ ", in this case they are used in a round-robin fashion",
+ READ_ONLY GLOBAL_VAR(opt_mysql_tmpdir), CMD_LINE(REQUIRED_ARG, 't'),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static bool fix_trans_mem_root(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type != OPT_GLOBAL)
+ reset_root_defaults(&thd->transaction.mem_root,
+ thd->variables.trans_alloc_block_size,
+ thd->variables.trans_prealloc_size);
+ return false;
+}
+static Sys_var_ulong Sys_trans_alloc_block_size(
+ "transaction_alloc_block_size",
+ "Allocation block size for transactions to be stored in binary log",
+ SESSION_VAR(trans_alloc_block_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, 128 * 1024 * 1024), DEFAULT(QUERY_ALLOC_BLOCK_SIZE),
+ BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_trans_mem_root));
+
+static Sys_var_ulong Sys_trans_prealloc_size(
+ "transaction_prealloc_size",
+ "Persistent buffer for transactions to be stored in binary log",
+ SESSION_VAR(trans_prealloc_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, 128 * 1024 * 1024), DEFAULT(TRANS_ALLOC_PREALLOC_SIZE),
+ BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_trans_mem_root));
+
+static const char *thread_handling_names[]=
+{
+ "one-thread-per-connection", "no-threads",
+#ifdef HAVE_POOL_OF_THREADS
+ "pool-of-threads",
+#endif
+ 0
+};
+
+#if defined (_WIN32) && defined (HAVE_POOL_OF_THREADS)
+/* Windows is using OS threadpool, so we're pretty sure it works well */
+#define DEFAULT_THREAD_HANDLING 2
+#else
+#define DEFAULT_THREAD_HANDLING 0
+#endif
+
+static Sys_var_enum Sys_thread_handling(
+ "thread_handling",
+ "Define threads usage for handling queries, one of "
+ "one-thread-per-connection, no-threads"
+#ifdef HAVE_POOL_OF_THREADS
+ ", pool-of-threads"
+#endif
+ , READ_ONLY GLOBAL_VAR(thread_handling), CMD_LINE(REQUIRED_ARG),
+ thread_handling_names,
+ DEFAULT(DEFAULT_THREAD_HANDLING)
+ );
+
+#ifdef HAVE_QUERY_CACHE
+static bool check_query_cache_size(sys_var *self, THD *thd, set_var *var)
+{
+ if (global_system_variables.query_cache_type == 0 &&
+ var->value && var->value->val_int() != 0)
+ {
+ my_error(ER_QUERY_CACHE_DISABLED, MYF(0));
+ return true;
+ }
+
+ return false;
+}
+static bool fix_query_cache_size(sys_var *self, THD *thd, enum_var_type type)
+{
+ ulong new_cache_size= query_cache.resize(query_cache_size);
+ /*
+ Note: query_cache_size is a global variable reflecting the
+ requested cache size. See also query_cache_size_arg
+ */
+ if (query_cache_size != new_cache_size)
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARN_QC_RESIZE, ER(ER_WARN_QC_RESIZE),
+ query_cache_size, new_cache_size);
+
+ query_cache_size= new_cache_size;
+ return false;
+}
+static bool fix_query_cache_limit(sys_var *self, THD *thd, enum_var_type type)
+{
+ query_cache.result_size_limit(query_cache_limit);
+ return false;
+}
+static Sys_var_ulonglong Sys_query_cache_size(
+ "query_cache_size",
+ "The memory allocated to store results from old queries",
+ GLOBAL_VAR(query_cache_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1024),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_query_cache_size),
+ ON_UPDATE(fix_query_cache_size));
+
+static Sys_var_ulong Sys_query_cache_limit(
+ "query_cache_limit",
+ "Don't cache results that are bigger than this",
+ GLOBAL_VAR(query_cache_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(1024*1024), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL),
+ ON_UPDATE(fix_query_cache_limit));
+
+static bool fix_qcache_min_res_unit(sys_var *self, THD *thd, enum_var_type type)
+{
+ query_cache_min_res_unit=
+ query_cache.set_min_res_unit(query_cache_min_res_unit);
+ return false;
+}
+static Sys_var_ulong Sys_query_cache_min_res_unit(
+ "query_cache_min_res_unit",
+ "The minimum size for blocks allocated by the query cache",
+ GLOBAL_VAR(query_cache_min_res_unit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(QUERY_CACHE_MIN_RESULT_DATA_SIZE),
+ BLOCK_SIZE(8), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_qcache_min_res_unit));
+
+static const char *query_cache_type_names[]= { "OFF", "ON", "DEMAND", 0 };
+static bool check_query_cache_type(sys_var *self, THD *thd, set_var *var)
+{
+ if (query_cache.is_disable_in_progress())
+ {
+ my_error(ER_QUERY_CACHE_IS_DISABLED, MYF(0));
+ return true;
+ }
+ if (var->type != OPT_GLOBAL &&
+ global_system_variables.query_cache_type == 0 &&
+ var->value->val_int() != 0)
+ {
+ my_error(ER_QUERY_CACHE_IS_GLOBALY_DISABLED, MYF(0));
+ return true;
+ }
+
+ return false;
+}
+static bool fix_query_cache_type(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type != OPT_GLOBAL)
+ return false;
+
+ if (global_system_variables.query_cache_type != 0 &&
+ query_cache.is_disabled())
+ {
+ /* if disabling in progress variable will not be set */
+ DBUG_ASSERT(!query_cache.is_disable_in_progress());
+ /* Enable query cache because it was disabled */
+ fix_query_cache_size(0, thd, type);
+ }
+ else if (global_system_variables.query_cache_type == 0)
+ query_cache.disable_query_cache(thd);
+ return false;
+}
+static Sys_var_enum Sys_query_cache_type(
+ "query_cache_type",
+ "OFF = Don't cache or retrieve results. ON = Cache all results "
+ "except SELECT SQL_NO_CACHE ... queries. DEMAND = Cache only "
+ "SELECT SQL_CACHE ... queries",
+ SESSION_VAR(query_cache_type), CMD_LINE(REQUIRED_ARG),
+ query_cache_type_names, DEFAULT(1), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_query_cache_type),
+ ON_UPDATE(fix_query_cache_type));
+
+static Sys_var_mybool Sys_query_cache_wlock_invalidate(
+ "query_cache_wlock_invalidate",
+ "Invalidate queries in query cache on LOCK for write",
+ SESSION_VAR(query_cache_wlock_invalidate), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+#endif /* HAVE_QUERY_CACHE */
+
+static Sys_var_mybool Sys_secure_auth(
+ "secure_auth",
+ "Disallow authentication for accounts that have old (pre-4.1) "
+ "passwords",
+ GLOBAL_VAR(opt_secure_auth), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+static Sys_var_charptr Sys_secure_file_priv(
+ "secure_file_priv",
+ "Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files "
+ "within specified directory",
+ PREALLOCATED READ_ONLY GLOBAL_VAR(opt_secure_file_priv),
+ CMD_LINE(REQUIRED_ARG), IN_FS_CHARSET, DEFAULT(0));
+
+static bool fix_server_id(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type == OPT_GLOBAL)
+ {
+ server_id_supplied = 1;
+ thd->variables.server_id= global_system_variables.server_id;
+ /*
+ Historically, server_id was a global variable that is exported to
+ plugins. Now it is a session variable, and lives in the
+ global_system_variables struct, but we still need to export the
+ value for reading to plugins for backwards compatibility reasons.
+ */
+ ::server_id= global_system_variables.server_id;
+ }
+ return false;
+}
+static Sys_var_ulong Sys_server_id(
+ "server_id",
+ "Uniquely identifies the server instance in the community of "
+ "replication partners",
+ SESSION_VAR(server_id), CMD_LINE(REQUIRED_ARG, OPT_SERVER_ID),
+ VALID_RANGE(0, UINT_MAX32), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(check_has_super), ON_UPDATE(fix_server_id));
+
+static Sys_var_mybool Sys_slave_compressed_protocol(
+ "slave_compressed_protocol",
+ "Use compression on master/slave protocol",
+ GLOBAL_VAR(opt_slave_compressed_protocol), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+#ifdef HAVE_REPLICATION
+static const char *slave_exec_mode_names[]= {"STRICT", "IDEMPOTENT", 0};
+static Sys_var_enum Slave_exec_mode(
+ "slave_exec_mode",
+ "How replication events should be executed. Legal values "
+ "are STRICT (default) and IDEMPOTENT. In IDEMPOTENT mode, "
+ "replication will not stop for operations that are idempotent. "
+ "For example, in row based replication attempts to delete rows that "
+ "doesn't exist will be ignored. "
+ "In STRICT mode, replication will stop on any unexpected difference "
+ "between the master and the slave",
+ GLOBAL_VAR(slave_exec_mode_options), CMD_LINE(REQUIRED_ARG),
+ slave_exec_mode_names, DEFAULT(SLAVE_EXEC_MODE_STRICT));
+
+static Sys_var_enum Slave_ddl_exec_mode(
+ "slave_ddl_exec_mode",
+ "How replication events should be executed. Legal values "
+ "are STRICT and IDEMPOTENT (default). In IDEMPOTENT mode, "
+ "replication will not stop for DDL operations that are idempotent. "
+ "This means that CREATE TABLE is treated as CREATE TABLE OR REPLACE and "
+ "DROP TABLE is treated as DROP TABLE IF EXISTS.",
+ GLOBAL_VAR(slave_ddl_exec_mode_options), CMD_LINE(REQUIRED_ARG),
+ slave_exec_mode_names, DEFAULT(SLAVE_EXEC_MODE_IDEMPOTENT));
+
+#ifdef RBR_TRIGGERS
+static const char *slave_run_triggers_for_rbr_names[]=
+ {"NO", "YES", "LOGGING", 0};
+static Sys_var_enum Slave_run_triggers_for_rbr(
+ "slave_run_triggers_for_rbr",
+ "Modes for how triggers in row-base replication on slave side will be "
+ "executed. Legal values are NO (default), YES and LOGGING. NO means "
+ "that trigger for RBR will not be running on slave. YES and LOGGING "
+ "means that triggers will be running on slave, if there was not "
+ "triggers running on the master for the statement. LOGGING also means "
+ "results of that the executed triggers work will be written to "
+ "the binlog.",
+ GLOBAL_VAR(slave_run_triggers_for_rbr), CMD_LINE(REQUIRED_ARG),
+ slave_run_triggers_for_rbr_names,
+ DEFAULT(SLAVE_RUN_TRIGGERS_FOR_RBR_NO));
+#endif //RBR_TRIGGERS
+
+static const char *slave_type_conversions_name[]= {"ALL_LOSSY", "ALL_NON_LOSSY", 0};
+static Sys_var_set Slave_type_conversions(
+ "slave_type_conversions",
+ "Set of slave type conversions that are enabled. Legal values are:"
+ " ALL_LOSSY to enable lossy conversions and"
+ " ALL_NON_LOSSY to enable non-lossy conversions."
+ " If the variable is assigned the empty set, no conversions are"
+ " allowed and it is expected that the types match exactly.",
+ GLOBAL_VAR(slave_type_conversions_options), CMD_LINE(REQUIRED_ARG),
+ slave_type_conversions_name,
+ DEFAULT(0));
+
+static Sys_var_mybool Sys_slave_sql_verify_checksum(
+ "slave_sql_verify_checksum",
+ "Force checksum verification of replication events after reading them "
+ "from relay log. Note: Events are always checksum-verified by slave on "
+ "receiving them from the network before writing them to the relay log",
+ GLOBAL_VAR(opt_slave_sql_verify_checksum), CMD_LINE(OPT_ARG),
+ DEFAULT(TRUE));
+
+static Sys_var_mybool Sys_master_verify_checksum(
+ "master_verify_checksum",
+ "Force checksum verification of logged events in the binary log before "
+ "sending them to slaves or printing them in the output of "
+ "SHOW BINLOG EVENTS",
+ GLOBAL_VAR(opt_master_verify_checksum), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+/* These names must match RPL_SKIP_XXX #defines in slave.h. */
+static const char *replicate_events_marked_for_skip_names[]= {
+ "replicate", "filter_on_slave", "filter_on_master", 0
+};
+
+bool
+Sys_var_replicate_events_marked_for_skip::global_update(THD *thd, set_var *var)
+{
+ bool result= true; // Assume error
+ DBUG_ENTER("Sys_var_replicate_events_marked_for_skip::global_update");
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (!master_info_index->give_error_if_slave_running())
+ result= Sys_var_enum::global_update(thd, var);
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ DBUG_RETURN(result);
+}
+
+static Sys_var_replicate_events_marked_for_skip Replicate_events_marked_for_skip
+ ("replicate_events_marked_for_skip",
+ "Whether the slave should replicate events that were created with "
+ "@@skip_replication=1 on the master. Default REPLICATE (no events are "
+ "skipped). Other values are FILTER_ON_SLAVE (events will be sent by the "
+ "master but ignored by the slave) and FILTER_ON_MASTER (events marked with "
+ "@@skip_replication=1 will be filtered on the master and never be sent to "
+ "the slave).",
+ GLOBAL_VAR(opt_replicate_events_marked_for_skip), CMD_LINE(REQUIRED_ARG),
+ replicate_events_marked_for_skip_names, DEFAULT(RPL_SKIP_REPLICATE),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG);
+#endif
+
+
+static Sys_var_ulong Sys_slow_launch_time(
+ "slow_launch_time",
+ "If creating the thread takes longer than this value (in seconds), "
+ "the Slow_launch_threads counter will be incremented",
+ GLOBAL_VAR(slow_launch_time), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, LONG_TIMEOUT), DEFAULT(2), BLOCK_SIZE(1));
+
+static Sys_var_ulonglong Sys_sort_buffer(
+ "sort_buffer_size",
+ "Each thread that needs to do a sort allocates a buffer of this size",
+ SESSION_VAR(sortbuff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(MIN_SORT_MEMORY, SIZE_T_MAX), DEFAULT(MAX_SORT_MEMORY),
+ BLOCK_SIZE(1));
+
+export ulonglong expand_sql_mode(ulonglong sql_mode)
+{
+ if (sql_mode & MODE_ANSI)
+ {
+ /*
+ Note that we dont set
+ MODE_NO_KEY_OPTIONS | MODE_NO_TABLE_OPTIONS | MODE_NO_FIELD_OPTIONS
+ to allow one to get full use of MySQL in this mode.
+
+ MODE_ONLY_FULL_GROUP_BY was removed from ANSI mode because it is
+ currently overly restrictive (see BUG#8510).
+ */
+ sql_mode|= (MODE_REAL_AS_FLOAT | MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
+ MODE_IGNORE_SPACE);
+ }
+ if (sql_mode & MODE_ORACLE)
+ sql_mode|= (MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
+ MODE_IGNORE_SPACE |
+ MODE_NO_KEY_OPTIONS | MODE_NO_TABLE_OPTIONS |
+ MODE_NO_FIELD_OPTIONS | MODE_NO_AUTO_CREATE_USER);
+ if (sql_mode & MODE_MSSQL)
+ sql_mode|= (MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
+ MODE_IGNORE_SPACE |
+ MODE_NO_KEY_OPTIONS | MODE_NO_TABLE_OPTIONS |
+ MODE_NO_FIELD_OPTIONS);
+ if (sql_mode & MODE_POSTGRESQL)
+ sql_mode|= (MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
+ MODE_IGNORE_SPACE |
+ MODE_NO_KEY_OPTIONS | MODE_NO_TABLE_OPTIONS |
+ MODE_NO_FIELD_OPTIONS);
+ if (sql_mode & MODE_DB2)
+ sql_mode|= (MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
+ MODE_IGNORE_SPACE |
+ MODE_NO_KEY_OPTIONS | MODE_NO_TABLE_OPTIONS |
+ MODE_NO_FIELD_OPTIONS);
+ if (sql_mode & MODE_MAXDB)
+ sql_mode|= (MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
+ MODE_IGNORE_SPACE |
+ MODE_NO_KEY_OPTIONS | MODE_NO_TABLE_OPTIONS |
+ MODE_NO_FIELD_OPTIONS | MODE_NO_AUTO_CREATE_USER);
+ if (sql_mode & MODE_MYSQL40)
+ sql_mode|= MODE_HIGH_NOT_PRECEDENCE;
+ if (sql_mode & MODE_MYSQL323)
+ sql_mode|= MODE_HIGH_NOT_PRECEDENCE;
+ if (sql_mode & MODE_TRADITIONAL)
+ sql_mode|= (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES |
+ MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE |
+ MODE_ERROR_FOR_DIVISION_BY_ZERO | MODE_NO_AUTO_CREATE_USER |
+ MODE_NO_ENGINE_SUBSTITUTION);
+ return sql_mode;
+}
+static bool check_sql_mode(sys_var *self, THD *thd, set_var *var)
+{
+ var->save_result.ulonglong_value=
+ expand_sql_mode(var->save_result.ulonglong_value);
+ return false;
+}
+static bool fix_sql_mode(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type != OPT_GLOBAL)
+ {
+ /* Update thd->server_status */
+ if (thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)
+ thd->server_status|= SERVER_STATUS_NO_BACKSLASH_ESCAPES;
+ else
+ thd->server_status&= ~SERVER_STATUS_NO_BACKSLASH_ESCAPES;
+ }
+ return false;
+}
+/*
+ WARNING: When adding new SQL modes don't forget to update the
+ tables definitions that stores it's value (ie: mysql.event, mysql.proc)
+*/
+static const char *sql_mode_names[]=
+{
+ "REAL_AS_FLOAT", "PIPES_AS_CONCAT", "ANSI_QUOTES", "IGNORE_SPACE",
+ "IGNORE_BAD_TABLE_OPTIONS",
+ "ONLY_FULL_GROUP_BY", "NO_UNSIGNED_SUBTRACTION", "NO_DIR_IN_CREATE",
+ "POSTGRESQL", "ORACLE", "MSSQL", "DB2", "MAXDB", "NO_KEY_OPTIONS",
+ "NO_TABLE_OPTIONS", "NO_FIELD_OPTIONS", "MYSQL323", "MYSQL40", "ANSI",
+ "NO_AUTO_VALUE_ON_ZERO", "NO_BACKSLASH_ESCAPES", "STRICT_TRANS_TABLES",
+ "STRICT_ALL_TABLES", "NO_ZERO_IN_DATE", "NO_ZERO_DATE",
+ "ALLOW_INVALID_DATES", "ERROR_FOR_DIVISION_BY_ZERO", "TRADITIONAL",
+ "NO_AUTO_CREATE_USER", "HIGH_NOT_PRECEDENCE", "NO_ENGINE_SUBSTITUTION",
+ "PAD_CHAR_TO_FULL_LENGTH",
+ 0
+};
+export bool sql_mode_string_representation(THD *thd, ulonglong sql_mode,
+ LEX_STRING *ls)
+{
+ set_to_string(thd, ls, sql_mode, sql_mode_names);
+ return ls->str == 0;
+}
+/*
+ sql_mode should *not* be IN_BINLOG: even though it is written to the binlog,
+ the slave ignores the MODE_NO_DIR_IN_CREATE variable, so slave's value
+ differs from master's (see log_event.cc: Query_log_event::do_apply_event()).
+*/
+static Sys_var_set Sys_sql_mode(
+ "sql_mode",
+ "Syntax: sql-mode=mode[,mode[,mode...]]. See the manual for the "
+ "complete list of valid sql modes",
+ SESSION_VAR(sql_mode), CMD_LINE(REQUIRED_ARG),
+ sql_mode_names, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_sql_mode), ON_UPDATE(fix_sql_mode));
+
+static const char *old_mode_names[]=
+{
+ "NO_DUP_KEY_WARNINGS_WITH_IGNORE",
+ "NO_PROGRESS_INFO",
+ "ZERO_DATE_TIME_CAST",
+ 0
+};
+
+export bool old_mode_string_representation(THD *thd, ulonglong sql_mode,
+ LEX_STRING *ls)
+{
+ set_to_string(thd, ls, sql_mode, old_mode_names);
+ return ls->str == 0;
+}
+/*
+ sql_mode should *not* be IN_BINLOG as the slave can't remember this
+ anyway on restart.
+*/
+static Sys_var_set Sys_old_behavior(
+ "old_mode",
+ "Used to emulate old behavior from earlier MariaDB or MySQL versions. "
+ "Syntax: old_mode=mode[,mode[,mode...]]. "
+ "See the manual for the complete list of valid old modes",
+ SESSION_VAR(old_behavior), CMD_LINE(REQUIRED_ARG),
+ old_mode_names, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG);
+
+#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
+#define SSL_OPT(X) CMD_LINE(REQUIRED_ARG,X)
+#else
+#define SSL_OPT(X) NO_CMD_LINE
+#endif
+
+static Sys_var_charptr Sys_ssl_ca(
+ "ssl_ca",
+ "CA file in PEM format (check OpenSSL docs, implies --ssl)",
+ READ_ONLY GLOBAL_VAR(opt_ssl_ca), SSL_OPT(OPT_SSL_CA),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_ssl_capath(
+ "ssl_capath",
+ "CA directory (check OpenSSL docs, implies --ssl)",
+ READ_ONLY GLOBAL_VAR(opt_ssl_capath), SSL_OPT(OPT_SSL_CAPATH),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_ssl_cert(
+ "ssl_cert", "X509 cert in PEM format (implies --ssl)",
+ READ_ONLY GLOBAL_VAR(opt_ssl_cert), SSL_OPT(OPT_SSL_CERT),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_ssl_cipher(
+ "ssl_cipher", "SSL cipher to use (implies --ssl)",
+ READ_ONLY GLOBAL_VAR(opt_ssl_cipher), SSL_OPT(OPT_SSL_CIPHER),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_ssl_key(
+ "ssl_key", "X509 key in PEM format (implies --ssl)",
+ READ_ONLY GLOBAL_VAR(opt_ssl_key), SSL_OPT(OPT_SSL_KEY),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_ssl_crl(
+ "ssl_crl",
+ "CRL file in PEM format (check OpenSSL docs, implies --ssl)",
+ READ_ONLY GLOBAL_VAR(opt_ssl_crl), SSL_OPT(OPT_SSL_CRL),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_ssl_crlpath(
+ "ssl_crlpath",
+ "CRL directory (check OpenSSL docs, implies --ssl)",
+ READ_ONLY GLOBAL_VAR(opt_ssl_crlpath), SSL_OPT(OPT_SSL_CRLPATH),
+ IN_FS_CHARSET, DEFAULT(0));
+
+
+// why ENUM and not BOOL ?
+static const char *updatable_views_with_limit_names[]= {"NO", "YES", 0};
+static Sys_var_enum Sys_updatable_views_with_limit(
+ "updatable_views_with_limit",
+ "YES = Don't issue an error message (warning only) if a VIEW without "
+ "presence of a key of the underlying table is used in queries with a "
+ "LIMIT clause for updating. NO = Prohibit update of a VIEW, which "
+ "does not contain a key of the underlying table and the query uses "
+ "a LIMIT clause (usually get from GUI tools)",
+ SESSION_VAR(updatable_views_with_limit), CMD_LINE(REQUIRED_ARG),
+ updatable_views_with_limit_names, DEFAULT(TRUE));
+
+static Sys_var_mybool Sys_sync_frm(
+ "sync_frm", "Sync .frm files to disk on creation",
+ GLOBAL_VAR(opt_sync_frm), CMD_LINE(OPT_ARG),
+ DEFAULT(TRUE));
+
+static char *system_time_zone_ptr;
+static Sys_var_charptr Sys_system_time_zone(
+ "system_time_zone", "The server system time zone",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(system_time_zone_ptr),
+ NO_CMD_LINE,
+ IN_SYSTEM_CHARSET, DEFAULT(system_time_zone));
+
+static Sys_var_ulong Sys_table_def_size(
+ "table_definition_cache",
+ "The number of cached table definitions",
+ GLOBAL_VAR(tdc_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(TABLE_DEF_CACHE_MIN, 512*1024),
+ DEFAULT(TABLE_DEF_CACHE_DEFAULT), BLOCK_SIZE(1));
+
+
+static bool fix_table_open_cache(sys_var *, THD *, enum_var_type)
+{
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ tc_purge();
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ return false;
+}
+
+
+static Sys_var_ulong Sys_table_cache_size(
+ "table_open_cache", "The number of cached open tables",
+ GLOBAL_VAR(tc_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 512*1024), DEFAULT(TABLE_OPEN_CACHE_DEFAULT),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_table_open_cache));
+
+static Sys_var_ulong Sys_thread_cache_size(
+ "thread_cache_size",
+ "How many threads we should keep in a cache for reuse",
+ GLOBAL_VAR(thread_cache_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 16384), DEFAULT(0), BLOCK_SIZE(1));
+
+#ifdef HAVE_POOL_OF_THREADS
+static bool fix_tp_max_threads(sys_var *, THD *, enum_var_type)
+{
+#ifdef _WIN32
+ tp_set_max_threads(threadpool_max_threads);
+#endif
+ return false;
+}
+
+
+#ifdef _WIN32
+static bool fix_tp_min_threads(sys_var *, THD *, enum_var_type)
+{
+ tp_set_min_threads(threadpool_min_threads);
+ return false;
+}
+#endif
+
+
+#ifndef _WIN32
+static bool check_threadpool_size(sys_var *self, THD *thd, set_var *var)
+{
+ ulonglong v= var->save_result.ulonglong_value;
+ if (v > threadpool_max_size)
+ {
+ var->save_result.ulonglong_value= threadpool_max_size;
+ return throw_bounds_warning(thd, self->name.str, true, true, v);
+ }
+ return false;
+}
+
+
+static bool fix_threadpool_size(sys_var*, THD*, enum_var_type)
+{
+ tp_set_threadpool_size(threadpool_size);
+ return false;
+}
+
+
+static bool fix_threadpool_stall_limit(sys_var*, THD*, enum_var_type)
+{
+ tp_set_threadpool_stall_limit(threadpool_stall_limit);
+ return false;
+}
+#endif
+
+#ifdef _WIN32
+static Sys_var_uint Sys_threadpool_min_threads(
+ "thread_pool_min_threads",
+ "Minimum number of threads in the thread pool.",
+ GLOBAL_VAR(threadpool_min_threads), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 256), DEFAULT(1), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_tp_min_threads)
+ );
+#else
+static Sys_var_uint Sys_threadpool_idle_thread_timeout(
+ "thread_pool_idle_timeout",
+ "Timeout in seconds for an idle thread in the thread pool."
+ "Worker thread will be shut down after timeout",
+ GLOBAL_VAR(threadpool_idle_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(60), BLOCK_SIZE(1)
+);
+static Sys_var_uint Sys_threadpool_oversubscribe(
+ "thread_pool_oversubscribe",
+ "How many additional active worker threads in a group are allowed.",
+ GLOBAL_VAR(threadpool_oversubscribe), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 1000), DEFAULT(3), BLOCK_SIZE(1)
+);
+static Sys_var_uint Sys_threadpool_size(
+ "thread_pool_size",
+ "Number of thread groups in the pool. "
+ "This parameter is roughly equivalent to maximum number of concurrently "
+ "executing threads (threads in a waiting state do not count as executing).",
+ GLOBAL_VAR(threadpool_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, MAX_THREAD_GROUPS), DEFAULT(my_getncpus()), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_threadpool_size),
+ ON_UPDATE(fix_threadpool_size)
+);
+static Sys_var_uint Sys_threadpool_stall_limit(
+ "thread_pool_stall_limit",
+ "Maximum query execution time in milliseconds,"
+ "before an executing non-yielding thread is considered stalled."
+ "If a worker thread is stalled, additional worker thread "
+ "may be created to handle remaining clients.",
+ GLOBAL_VAR(threadpool_stall_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(10, UINT_MAX), DEFAULT(500), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_threadpool_stall_limit)
+);
+#endif /* !WIN32 */
+static Sys_var_uint Sys_threadpool_max_threads(
+ "thread_pool_max_threads",
+ "Maximum allowed number of worker threads in the thread pool",
+ GLOBAL_VAR(threadpool_max_threads), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 65536), DEFAULT(500), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_tp_max_threads)
+);
+#endif /* HAVE_POOL_OF_THREADS */
+
+/**
+ Can't change the 'next' tx_isolation if we are already in a
+ transaction.
+*/
+
+static bool check_tx_isolation(sys_var *self, THD *thd, set_var *var)
+{
+ if (var->type == OPT_DEFAULT && thd->in_active_multi_stmt_transaction())
+ {
+ DBUG_ASSERT(thd->in_multi_stmt_transaction_mode());
+ my_error(ER_CANT_CHANGE_TX_CHARACTERISTICS, MYF(0));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+// NO_CMD_LINE - different name of the option
+static Sys_var_tx_isolation Sys_tx_isolation(
+ "tx_isolation", "Default transaction isolation level",
+ SESSION_VAR(tx_isolation), NO_CMD_LINE,
+ tx_isolation_names, DEFAULT(ISO_REPEATABLE_READ),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_isolation));
+
+
+/**
+ Can't change the tx_read_only state if we are already in a
+ transaction.
+*/
+
+static bool check_tx_read_only(sys_var *self, THD *thd, set_var *var)
+{
+ if (var->type == OPT_DEFAULT && thd->in_active_multi_stmt_transaction())
+ {
+ DBUG_ASSERT(thd->in_multi_stmt_transaction_mode());
+ my_error(ER_CANT_CHANGE_TX_CHARACTERISTICS, MYF(0));
+ return true;
+ }
+ return false;
+}
+
+
+bool Sys_var_tx_read_only::session_update(THD *thd, set_var *var)
+{
+ if (var->type == OPT_SESSION && Sys_var_mybool::session_update(thd, var))
+ return true;
+ if (var->type == OPT_DEFAULT || !thd->in_active_multi_stmt_transaction())
+ {
+ // @see Sys_var_tx_isolation::session_update() above for the rules.
+ thd->tx_read_only= var->save_result.ulonglong_value;
+ }
+ return false;
+}
+
+
+static Sys_var_tx_read_only Sys_tx_read_only(
+ "tx_read_only", "Set default transaction access mode to read only.",
+ SESSION_VAR(tx_read_only), NO_CMD_LINE, DEFAULT(0),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_read_only));
+
+static Sys_var_ulonglong Sys_tmp_table_size(
+ "tmp_table_size",
+ "If an internal in-memory temporary table exceeds this size, MySQL "
+ "will automatically convert it to an on-disk MyISAM or Aria table",
+ SESSION_VAR(tmp_table_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1024, (ulonglong)~(intptr)0), DEFAULT(16*1024*1024),
+ BLOCK_SIZE(1));
+
+static Sys_var_mybool Sys_timed_mutexes(
+ "timed_mutexes",
+ "Specify whether to time mutexes. Deprecated, has no effect.",
+ GLOBAL_VAR(timed_mutexes), CMD_LINE(OPT_ARG), DEFAULT(0),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), ON_UPDATE(NULL),
+ DEPRECATED(""));
+
+static char *server_version_ptr;
+static Sys_var_charptr Sys_version(
+ "version", "Server version",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(server_version_ptr),
+ NO_CMD_LINE,
+ IN_SYSTEM_CHARSET, DEFAULT(server_version));
+
+static char *server_version_comment_ptr;
+static Sys_var_charptr Sys_version_comment(
+ "version_comment", "version_comment",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(server_version_comment_ptr),
+ NO_CMD_LINE,
+ IN_SYSTEM_CHARSET, DEFAULT(MYSQL_COMPILATION_COMMENT));
+
+static char *server_version_compile_machine_ptr;
+static Sys_var_charptr Sys_version_compile_machine(
+ "version_compile_machine", "version_compile_machine",
+ READ_ONLY SHOW_VALUE_IN_HELP
+ GLOBAL_VAR(server_version_compile_machine_ptr), NO_CMD_LINE,
+ IN_SYSTEM_CHARSET, DEFAULT(MACHINE_TYPE));
+
+static char *server_version_compile_os_ptr;
+static Sys_var_charptr Sys_version_compile_os(
+ "version_compile_os", "version_compile_os",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(server_version_compile_os_ptr),
+ NO_CMD_LINE,
+ IN_SYSTEM_CHARSET, DEFAULT(SYSTEM_TYPE));
+
+static char *malloc_library;
+static Sys_var_charptr Sys_malloc_library(
+ "version_malloc_library", "Version of the used malloc library",
+ READ_ONLY SHOW_VALUE_IN_HELP GLOBAL_VAR(malloc_library), NO_CMD_LINE,
+ IN_SYSTEM_CHARSET, DEFAULT(MALLOC_LIBRARY));
+
+static Sys_var_ulong Sys_net_wait_timeout(
+ "wait_timeout",
+ "The number of seconds the server waits for activity on a "
+ "connection before closing it",
+ SESSION_VAR(net_wait_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, IF_WIN(INT_MAX32/1000, LONG_TIMEOUT)),
+ DEFAULT(NET_WAIT_TIMEOUT), BLOCK_SIZE(1));
+
+static Sys_var_plugin Sys_default_storage_engine(
+ "default_storage_engine", "The default storage engine for new tables",
+ SESSION_VAR(table_plugin), NO_CMD_LINE,
+ MYSQL_STORAGE_ENGINE_PLUGIN, DEFAULT(&default_storage_engine),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_not_null));
+
+// Alias for @@default_storage_engine
+static Sys_var_plugin Sys_storage_engine(
+ "storage_engine", "Alias for @@default_storage_engine. Deprecated",
+ SESSION_VAR(table_plugin), NO_CMD_LINE,
+ MYSQL_STORAGE_ENGINE_PLUGIN, DEFAULT(&default_storage_engine),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_not_null));
+
+#if defined(ENABLED_DEBUG_SYNC)
+/*
+ Variable can be set for the session only.
+
+ This could be changed later. Then we need to have a global array of
+ actions in addition to the thread local ones. SET GLOBAL would
+ manage the global array, SET [SESSION] the local array. A sync point
+ would need to look for a local and a global action. Setting and
+ executing of global actions need to be protected by a mutex.
+
+ The purpose of global actions could be to allow synchronizing with
+ connectionless threads that cannot execute SET statements.
+*/
+static Sys_var_debug_sync Sys_debug_sync(
+ "debug_sync", "Debug Sync Facility",
+ sys_var::ONLY_SESSION, NO_CMD_LINE,
+ DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_has_super));
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
+/**
+ "time_format" "date_format" "datetime_format"
+
+ the following three variables are unused, and the source of confusion
+ (bug reports like "I've changed date_format, but date format hasn't changed.
+ I've made them read-only, to alleviate the situation somewhat.
+
+ @todo make them NO_CMD_LINE ?
+*/
+static Sys_var_charptr Sys_date_format(
+ "date_format", "The DATE format (ignored)",
+ READ_ONLY GLOBAL_VAR(global_date_format.format.str),
+ CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET,
+ DEFAULT(known_date_time_formats[ISO_FORMAT].date_format));
+
+static Sys_var_charptr Sys_datetime_format(
+ "datetime_format", "The DATETIME format (ignored)",
+ READ_ONLY GLOBAL_VAR(global_datetime_format.format.str),
+ CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET,
+ DEFAULT(known_date_time_formats[ISO_FORMAT].datetime_format));
+
+static Sys_var_charptr Sys_time_format(
+ "time_format", "The TIME format (ignored)",
+ READ_ONLY GLOBAL_VAR(global_time_format.format.str),
+ CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET,
+ DEFAULT(known_date_time_formats[ISO_FORMAT].time_format));
+
+static bool fix_autocommit(sys_var *self, THD *thd, enum_var_type type)
+{
+ if (type == OPT_GLOBAL)
+ {
+ if (global_system_variables.option_bits & OPTION_AUTOCOMMIT)
+ global_system_variables.option_bits&= ~OPTION_NOT_AUTOCOMMIT;
+ else
+ global_system_variables.option_bits|= OPTION_NOT_AUTOCOMMIT;
+ return false;
+ }
+
+ if (test_all_bits(thd->variables.option_bits,
+ (OPTION_AUTOCOMMIT | OPTION_NOT_AUTOCOMMIT)))
+ {
+ // activating autocommit
+ if (trans_commit_stmt(thd) || trans_commit(thd))
+ {
+ thd->variables.option_bits&= ~OPTION_AUTOCOMMIT;
+ return true;
+ }
+ /*
+ Don't close thread tables or release metadata locks: if we do so, we
+ risk releasing locks/closing tables of expressions used to assign
+ other variables, as in:
+ set @var=my_stored_function1(), @@autocommit=1, @var2=(select MY_MAX(a)
+ from my_table), ...
+ The locks will be released at statement end anyway, as SET
+ statement that assigns autocommit is marked to commit
+ transaction implicitly at the end (@sa stmt_causes_implicitcommit()).
+ */
+ thd->variables.option_bits&=
+ ~(OPTION_BEGIN | OPTION_KEEP_LOG | OPTION_NOT_AUTOCOMMIT |
+ OPTION_GTID_BEGIN);
+ thd->transaction.all.modified_non_trans_table= false;
+ thd->server_status|= SERVER_STATUS_AUTOCOMMIT;
+ return false;
+ }
+
+ if ((thd->variables.option_bits &
+ (OPTION_AUTOCOMMIT |OPTION_NOT_AUTOCOMMIT)) == 0)
+ {
+ // disabling autocommit
+ thd->transaction.all.modified_non_trans_table= false;
+ thd->server_status&= ~SERVER_STATUS_AUTOCOMMIT;
+ thd->variables.option_bits|= OPTION_NOT_AUTOCOMMIT;
+ return false;
+ }
+
+ return false; // autocommit value wasn't changed
+}
+
+static Sys_var_bit Sys_autocommit(
+ "autocommit", "autocommit",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_AUTOCOMMIT, DEFAULT(TRUE),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_autocommit));
+export sys_var *Sys_autocommit_ptr= &Sys_autocommit; // for sql_yacc.yy
+
+static Sys_var_mybool Sys_big_tables(
+ "big_tables", "Allow big result sets by saving all "
+ "temporary sets on file (Solves most 'table full' errors)",
+ SESSION_VAR(big_tables), CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static Sys_var_bit Sys_big_selects(
+ "sql_big_selects", "sql_big_selects",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_BIG_SELECTS,
+ DEFAULT(FALSE));
+
+static Sys_var_bit Sys_log_off(
+ "sql_log_off", "sql_log_off",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_LOG_OFF,
+ DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_has_super));
+
+/**
+ This function sets the session variable thd->variables.sql_log_bin
+ to reflect changes to @@session.sql_log_bin.
+
+ @param[IN] self A pointer to the sys_var, i.e. Sys_log_binlog.
+ @param[IN] type The type either session or global.
+
+ @return @c FALSE.
+*/
+static bool fix_sql_log_bin_after_update(sys_var *self, THD *thd,
+ enum_var_type type)
+{
+ DBUG_ASSERT(type == OPT_SESSION);
+
+ if (thd->variables.sql_log_bin)
+ thd->variables.option_bits |= OPTION_BIN_LOG;
+ else
+ thd->variables.option_bits &= ~OPTION_BIN_LOG;
+
+ return FALSE;
+}
+
+/**
+ This function checks if the sql_log_bin can be changed,
+ what is possible if:
+ - the user is a super user;
+ - the set is not called from within a function/trigger;
+ - there is no on-going transaction.
+
+ @param[IN] self A pointer to the sys_var, i.e. Sys_log_binlog.
+ @param[IN] var A pointer to the set_var created by the parser.
+
+ @return @c FALSE if the change is allowed, otherwise @c TRUE.
+*/
+static bool check_sql_log_bin(sys_var *self, THD *thd, set_var *var)
+{
+ if (check_has_super(self, thd, var))
+ return TRUE;
+
+ if (var->type == OPT_GLOBAL)
+ {
+ my_error(ER_INCORRECT_GLOBAL_LOCAL_VAR, MYF(0), self->name.str, "SESSION");
+ return TRUE;
+ }
+
+ if (error_if_in_trans_or_substatement(thd,
+ ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN,
+ ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN))
+ return TRUE;
+
+ return FALSE;
+}
+
+static Sys_var_mybool Sys_log_binlog(
+ "sql_log_bin", "Controls whether logging to the binary log is done",
+ SESSION_VAR(sql_log_bin), NO_CMD_LINE, DEFAULT(TRUE),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_sql_log_bin),
+ ON_UPDATE(fix_sql_log_bin_after_update));
+
+static Sys_var_bit Sys_sql_warnings(
+ "sql_warnings", "sql_warnings",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_WARNINGS,
+ DEFAULT(FALSE));
+
+static Sys_var_bit Sys_sql_notes(
+ "sql_notes", "sql_notes",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_SQL_NOTES,
+ DEFAULT(TRUE));
+
+static Sys_var_bit Sys_auto_is_null(
+ "sql_auto_is_null", "sql_auto_is_null",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_AUTO_IS_NULL,
+ DEFAULT(FALSE), NO_MUTEX_GUARD, IN_BINLOG);
+
+static Sys_var_bit Sys_safe_updates(
+ "sql_safe_updates", "sql_safe_updates",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_SAFE_UPDATES,
+ DEFAULT(FALSE));
+
+static Sys_var_bit Sys_buffer_results(
+ "sql_buffer_result", "sql_buffer_result",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_BUFFER_RESULT,
+ DEFAULT(FALSE));
+
+static Sys_var_bit Sys_quote_show_create(
+ "sql_quote_show_create", "sql_quote_show_create",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_QUOTE_SHOW_CREATE,
+ DEFAULT(TRUE));
+
+static Sys_var_bit Sys_foreign_key_checks(
+ "foreign_key_checks", "foreign_key_checks",
+ SESSION_VAR(option_bits), NO_CMD_LINE,
+ REVERSE(OPTION_NO_FOREIGN_KEY_CHECKS),
+ DEFAULT(TRUE), NO_MUTEX_GUARD, IN_BINLOG);
+
+static Sys_var_bit Sys_unique_checks(
+ "unique_checks", "unique_checks",
+ SESSION_VAR(option_bits), NO_CMD_LINE,
+ REVERSE(OPTION_RELAXED_UNIQUE_CHECKS),
+ DEFAULT(TRUE), NO_MUTEX_GUARD, IN_BINLOG);
+
+#ifdef ENABLED_PROFILING
+static Sys_var_bit Sys_profiling(
+ "profiling", "profiling",
+ SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_PROFILING,
+ DEFAULT(FALSE));
+
+static Sys_var_ulong Sys_profiling_history_size(
+ "profiling_history_size", "Limit of query profiling memory",
+ SESSION_VAR(profiling_history_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 100), DEFAULT(15), BLOCK_SIZE(1));
+#endif
+
+/*
+ When this is set by a connection, binlogged events will be marked with a
+ corresponding flag. The slave can be configured to not replicate events
+ so marked.
+ In the binlog dump thread on the master, this variable is re-used for a
+ related purpose: The slave sets this flag when connecting to the master to
+ request that the master filter out (ie. not send) any events with the flag
+ set, thus saving network traffic on events that would be ignored by the
+ slave anyway.
+*/
+static bool check_skip_replication(sys_var *self, THD *thd, set_var *var)
+{
+ /*
+ We must not change @@skip_replication in the middle of a transaction or
+ statement, as that could result in only part of the transaction / statement
+ being replicated.
+ (This would be particularly serious if we were to replicate eg.
+ Rows_log_event without Table_map_log_event or transactional updates without
+ the COMMIT).
+ */
+ if (error_if_in_trans_or_substatement(thd,
+ ER_STORED_FUNCTION_PREVENTS_SWITCH_SKIP_REPLICATION,
+ ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SKIP_REPLICATION))
+ return 1;
+
+ return 0;
+}
+
+static Sys_var_bit Sys_skip_replication(
+ "skip_replication", "skip_replication",
+ SESSION_ONLY(option_bits), NO_CMD_LINE, OPTION_SKIP_REPLICATION,
+ DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_skip_replication));
+
+static Sys_var_harows Sys_select_limit(
+ "sql_select_limit",
+ "The maximum number of rows to return from SELECT statements",
+ SESSION_VAR(select_limit), NO_CMD_LINE,
+ VALID_RANGE(0, HA_POS_ERROR), DEFAULT(HA_POS_ERROR), BLOCK_SIZE(1));
+
+static bool update_timestamp(THD *thd, set_var *var)
+{
+ if (var->value)
+ {
+ my_hrtime_t hrtime = { hrtime_from_time(var->save_result.double_value) };
+ thd->set_time(hrtime);
+ }
+ else // SET timestamp=DEFAULT
+ thd->user_time.val= 0;
+ return false;
+}
+static double read_timestamp(THD *thd)
+{
+ return thd->start_time +
+ thd->start_time_sec_part/(double)TIME_SECOND_PART_FACTOR;
+}
+static Sys_var_session_special_double Sys_timestamp(
+ "timestamp", "Set the time for this client",
+ sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, TIMESTAMP_MAX_VALUE),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_timestamp), ON_READ(read_timestamp));
+
+static bool update_last_insert_id(THD *thd, set_var *var)
+{
+ if (!var->value)
+ {
+ my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str);
+ return true;
+ }
+ thd->first_successful_insert_id_in_prev_stmt=
+ var->save_result.ulonglong_value;
+ return false;
+}
+static ulonglong read_last_insert_id(THD *thd)
+{
+ return (ulonglong) thd->read_first_successful_insert_id_in_prev_stmt();
+}
+static Sys_var_session_special Sys_last_insert_id(
+ "last_insert_id", "The value to be returned from LAST_INSERT_ID()",
+ sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, ULONGLONG_MAX), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_last_insert_id), ON_READ(read_last_insert_id));
+
+// alias for last_insert_id(), Sybase-style
+static Sys_var_session_special Sys_identity(
+ "identity", "Synonym for the last_insert_id variable",
+ sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, ULONGLONG_MAX), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_last_insert_id), ON_READ(read_last_insert_id));
+
+/*
+ insert_id should *not* be marked as written to the binlog (i.e., it
+ should *not* be IN_BINLOG), because we want any statement that
+ refers to insert_id explicitly to be unsafe. (By "explicitly", we
+ mean using @@session.insert_id, whereas insert_id is used
+ "implicitly" when NULL value is inserted into an auto_increment
+ column).
+
+ We want statements referring explicitly to @@session.insert_id to be
+ unsafe, because insert_id is modified internally by the slave sql
+ thread when NULL values are inserted in an AUTO_INCREMENT column.
+ This modification interfers with the value of the
+ @@session.insert_id variable if @@session.insert_id is referred
+ explicitly by an insert statement (as is seen by executing "SET
+ @@session.insert_id=0; CREATE TABLE t (a INT, b INT KEY
+ AUTO_INCREMENT); INSERT INTO t(a) VALUES (@@session.insert_id);" in
+ statement-based logging mode: t will be different on master and
+ slave).
+*/
+static bool update_insert_id(THD *thd, set_var *var)
+{
+ if (!var->value)
+ {
+ my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str);
+ return true;
+ }
+ thd->force_one_auto_inc_interval(var->save_result.ulonglong_value);
+ return false;
+}
+
+static ulonglong read_insert_id(THD *thd)
+{
+ return thd->auto_inc_intervals_forced.minimum();
+}
+static Sys_var_session_special Sys_insert_id(
+ "insert_id", "The value to be used by the following INSERT "
+ "or ALTER TABLE statement when inserting an AUTO_INCREMENT value",
+ sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, ULONGLONG_MAX), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_insert_id), ON_READ(read_insert_id));
+
+static bool update_rand_seed1(THD *thd, set_var *var)
+{
+ if (!var->value)
+ {
+ my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str);
+ return true;
+ }
+ thd->rand.seed1= (ulong) var->save_result.ulonglong_value;
+ return false;
+}
+static ulonglong read_rand_seed(THD *thd)
+{
+ return 0;
+}
+static Sys_var_session_special Sys_rand_seed1(
+ "rand_seed1", "Sets the internal state of the RAND() "
+ "generator for replication purposes",
+ sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, ULONG_MAX), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_rand_seed1), ON_READ(read_rand_seed));
+
+static bool update_rand_seed2(THD *thd, set_var *var)
+{
+ if (!var->value)
+ {
+ my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str);
+ return true;
+ }
+ thd->rand.seed2= (ulong) var->save_result.ulonglong_value;
+ return false;
+}
+static Sys_var_session_special Sys_rand_seed2(
+ "rand_seed2", "Sets the internal state of the RAND() "
+ "generator for replication purposes",
+ sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, ULONG_MAX), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(update_rand_seed2), ON_READ(read_rand_seed));
+
+static ulonglong read_error_count(THD *thd)
+{
+ return thd->get_stmt_da()->error_count();
+}
+// this really belongs to the SHOW STATUS
+static Sys_var_session_special Sys_error_count(
+ "error_count", "The number of errors that resulted from the "
+ "last statement that generated messages",
+ READ_ONLY sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, ULONGLONG_MAX), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), ON_READ(read_error_count));
+
+static ulonglong read_warning_count(THD *thd)
+{
+ return thd->get_stmt_da()->warn_count();
+}
+// this really belongs to the SHOW STATUS
+static Sys_var_session_special Sys_warning_count(
+ "warning_count", "The number of errors, warnings, and notes "
+ "that resulted from the last statement that generated messages",
+ READ_ONLY sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, ULONGLONG_MAX), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), ON_READ(read_warning_count));
+
+static Sys_var_ulong Sys_default_week_format(
+ "default_week_format",
+ "The default week format used by WEEK() functions",
+ SESSION_VAR(default_week_format), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 7), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_ulonglong Sys_group_concat_max_len(
+ "group_concat_max_len",
+ "The maximum length of the result of function GROUP_CONCAT()",
+ SESSION_VAR(group_concat_max_len), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(4, SIZE_T_MAX), DEFAULT(1024), BLOCK_SIZE(1));
+
+static char *glob_hostname_ptr;
+static Sys_var_charptr Sys_hostname(
+ "hostname", "Server host name",
+ READ_ONLY GLOBAL_VAR(glob_hostname_ptr), NO_CMD_LINE,
+ IN_FS_CHARSET, DEFAULT(glob_hostname));
+
+#ifndef EMBEDDED_LIBRARY
+static Sys_var_charptr Sys_repl_report_host(
+ "report_host",
+ "Hostname or IP of the slave to be reported to the master during "
+ "slave registration. Will appear in the output of SHOW SLAVE HOSTS. "
+ "Leave unset if you do not want the slave to register itself with the "
+ "master. Note that it is not sufficient for the master to simply read "
+ "the IP of the slave off the socket once the slave connects. Due to "
+ "NAT and other routing issues, that IP may not be valid for connecting "
+ "to the slave from the master or other hosts",
+ READ_ONLY GLOBAL_VAR(report_host), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_repl_report_user(
+ "report_user",
+ "The account user name of the slave to be reported to the master "
+ "during slave registration",
+ READ_ONLY GLOBAL_VAR(report_user), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_repl_report_password(
+ "report_password",
+ "The account password of the slave to be reported to the master "
+ "during slave registration",
+ READ_ONLY GLOBAL_VAR(report_password), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_uint Sys_repl_report_port(
+ "report_port",
+ "Port for connecting to slave reported to the master during slave "
+ "registration. Set it only if the slave is listening on a non-default "
+ "port or if you have a special tunnel from the master or other clients "
+ "to the slave. If not sure, leave this option unset",
+ READ_ONLY GLOBAL_VAR(report_port), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+#endif
+
+static Sys_var_mybool Sys_keep_files_on_create(
+ "keep_files_on_create",
+ "Don't overwrite stale .MYD and .MYI even if no directory is specified",
+ SESSION_VAR(keep_files_on_create), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+static char *license;
+static Sys_var_charptr Sys_license(
+ "license", "The type of license the server has",
+ READ_ONLY GLOBAL_VAR(license), NO_CMD_LINE, IN_SYSTEM_CHARSET,
+ DEFAULT(STRINGIFY_ARG(LICENSE)));
+
+static bool check_log_path(sys_var *self, THD *thd, set_var *var)
+{
+ if (!var->value)
+ return false; // DEFAULT is ok
+
+ if (!var->save_result.string_value.str)
+ return true;
+
+ if (var->save_result.string_value.length > FN_REFLEN)
+ { // path is too long
+ my_error(ER_PATH_LENGTH, MYF(0), self->name.str);
+ return true;
+ }
+
+ char path[FN_REFLEN];
+ size_t path_length= unpack_filename(path, var->save_result.string_value.str);
+
+ if (!path_length)
+ return true;
+
+ if (!is_filename_allowed(var->save_result.string_value.str,
+ var->save_result.string_value.length, TRUE))
+ {
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0),
+ self->name.str, var->save_result.string_value.str);
+ return true;
+ }
+
+ MY_STAT f_stat;
+
+ if (my_stat(path, &f_stat, MYF(0)))
+ {
+ if (!MY_S_ISREG(f_stat.st_mode) || !(f_stat.st_mode & MY_S_IWRITE))
+ return true; // not a regular writable file
+ return false;
+ }
+
+ (void) dirname_part(path, var->save_result.string_value.str, &path_length);
+
+ if (var->save_result.string_value.length - path_length >= FN_LEN)
+ { // filename is too long
+ my_error(ER_PATH_LENGTH, MYF(0), self->name.str);
+ return true;
+ }
+
+ if (!path_length) // no path is good path (remember, relative to datadir)
+ return false;
+
+ if (my_access(path, (F_OK|W_OK)))
+ return true; // directory is not writable
+
+ return false;
+}
+static bool fix_log(char** logname, const char* default_logname,
+ const char*ext, bool enabled, void (*reopen)(char*))
+{
+ if (!*logname) // SET ... = DEFAULT
+ {
+ make_default_log_name(logname, ext, false);
+ if (!*logname)
+ return true;
+ }
+ logger.lock_exclusive();
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ if (enabled)
+ reopen(*logname);
+ logger.unlock();
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ return false;
+}
+static void reopen_general_log(char* name)
+{
+ logger.get_log_file_handler()->close(0);
+ logger.get_log_file_handler()->open_query_log(name);
+}
+static bool fix_general_log_file(sys_var *self, THD *thd, enum_var_type type)
+{
+ return fix_log(&opt_logname, opt_log_basename, ".log", opt_log,
+ reopen_general_log);
+}
+static Sys_var_charptr Sys_general_log_path(
+ "general_log_file", "Log connections and queries to given file",
+ PREALLOCATED GLOBAL_VAR(opt_logname), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_log_path), ON_UPDATE(fix_general_log_file));
+
+static void reopen_slow_log(char* name)
+{
+ logger.get_slow_log_file_handler()->close(0);
+ logger.get_slow_log_file_handler()->open_slow_log(name);
+}
+static bool fix_slow_log_file(sys_var *self, THD *thd, enum_var_type type)
+{
+ return fix_log(&opt_slow_logname, opt_log_basename, "-slow.log",
+ opt_slow_log, reopen_slow_log);
+}
+static Sys_var_charptr Sys_slow_log_path(
+ "slow_query_log_file", "Log slow queries to given log file. "
+ "Defaults logging to 'hostname'-slow.log. Must be enabled to activate "
+ "other slow log options",
+ PREALLOCATED GLOBAL_VAR(opt_slow_logname), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_log_path), ON_UPDATE(fix_slow_log_file));
+
+static Sys_var_have Sys_have_compress(
+ "have_compress", "have_compress",
+ READ_ONLY GLOBAL_VAR(have_compress), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_crypt(
+ "have_crypt", "have_crypt",
+ READ_ONLY GLOBAL_VAR(have_crypt), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_dlopen(
+ "have_dynamic_loading", "have_dynamic_loading",
+ READ_ONLY GLOBAL_VAR(have_dlopen), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_geometry(
+ "have_geometry", "have_geometry",
+ READ_ONLY GLOBAL_VAR(have_geometry), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_openssl(
+ "have_openssl", "have_openssl",
+ READ_ONLY GLOBAL_VAR(have_openssl), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_profiling(
+ "have_profiling", "have_profiling",
+ READ_ONLY GLOBAL_VAR(have_profiling), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_query_cache(
+ "have_query_cache", "have_query_cache",
+ READ_ONLY GLOBAL_VAR(have_query_cache), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_rtree_keys(
+ "have_rtree_keys", "have_rtree_keys",
+ READ_ONLY GLOBAL_VAR(have_rtree_keys), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_ssl(
+ "have_ssl", "have_ssl",
+ READ_ONLY GLOBAL_VAR(have_ssl), NO_CMD_LINE);
+
+static Sys_var_have Sys_have_symlink(
+ "have_symlink", "have_symlink",
+ READ_ONLY GLOBAL_VAR(have_symlink), NO_CMD_LINE);
+
+static bool fix_log_state(sys_var *self, THD *thd, enum_var_type type);
+static Sys_var_mybool Sys_general_log(
+ "general_log", "Log connections and queries to a table or log file. "
+ "Defaults logging to a file 'hostname'.log or a table mysql.general_log"
+ "if --log-output=TABLE is used",
+ GLOBAL_VAR(opt_log), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_log_state));
+
+static Sys_var_mybool Sys_slow_query_log(
+ "slow_query_log",
+ "Log slow queries to a table or log file. Defaults logging to a file "
+ "'hostname'-slow.log or a table mysql.slow_log if --log-output=TABLE is "
+ "used. Must be enabled to activate other slow log options",
+ GLOBAL_VAR(opt_slow_log), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(fix_log_state));
+
+static bool fix_log_state(sys_var *self, THD *thd, enum_var_type type)
+{
+ bool res;
+ my_bool *UNINIT_VAR(newvalptr), newval, UNINIT_VAR(oldval);
+ uint UNINIT_VAR(log_type);
+
+ if (self == &Sys_general_log)
+ {
+ newvalptr= &opt_log;
+ oldval= logger.get_log_file_handler()->is_open();
+ log_type= QUERY_LOG_GENERAL;
+ }
+ else if (self == &Sys_slow_query_log)
+ {
+ newvalptr= &opt_slow_log;
+ oldval= logger.get_slow_log_file_handler()->is_open();
+ log_type= QUERY_LOG_SLOW;
+ }
+ else
+ DBUG_ASSERT(FALSE);
+
+ newval= *newvalptr;
+ if (oldval == newval)
+ return false;
+
+ *newvalptr= oldval; // [de]activate_log_handler works that way (sigh)
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ if (!newval)
+ {
+ logger.deactivate_log_handler(thd, log_type);
+ res= false;
+ }
+ else
+ res= logger.activate_log_handler(thd, log_type);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ return res;
+}
+
+static bool check_not_empty_set(sys_var *self, THD *thd, set_var *var)
+{
+ return var->save_result.ulonglong_value == 0;
+}
+static bool fix_log_output(sys_var *self, THD *thd, enum_var_type type)
+{
+ logger.lock_exclusive();
+ logger.init_slow_log(log_output_options);
+ logger.init_general_log(log_output_options);
+ logger.unlock();
+ return false;
+}
+
+static const char *log_output_names[] = { "NONE", "FILE", "TABLE", NULL};
+
+static Sys_var_set Sys_log_output(
+ "log_output", "Syntax: log-output=value[,value...], "
+ "where \"value\" could be TABLE, FILE or NONE",
+ GLOBAL_VAR(log_output_options), CMD_LINE(REQUIRED_ARG),
+ log_output_names, DEFAULT(LOG_FILE), NO_MUTEX_GUARD, NOT_IN_BINLOG,
+ ON_CHECK(check_not_empty_set), ON_UPDATE(fix_log_output));
+
+#ifdef HAVE_REPLICATION
+static Sys_var_mybool Sys_log_slave_updates(
+ "log_slave_updates", "Tells the slave to log the updates from "
+ "the slave thread to the binary log. You will need to turn it on if "
+ "you plan to daisy-chain the slaves",
+ READ_ONLY GLOBAL_VAR(opt_log_slave_updates), CMD_LINE(OPT_ARG),
+ DEFAULT(0));
+
+static Sys_var_charptr Sys_relay_log(
+ "relay_log", "The location and name to use for relay logs",
+ READ_ONLY GLOBAL_VAR(opt_relay_logname), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_relay_log_index(
+ "relay_log_index", "The location and name to use for the file "
+ "that keeps a list of the last relay logs",
+ READ_ONLY GLOBAL_VAR(opt_relaylog_index_name), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_charptr Sys_relay_log_info_file(
+ "relay_log_info_file", "The location and name of the file that "
+ "remembers where the SQL replication thread is in the relay logs",
+ READ_ONLY GLOBAL_VAR(relay_log_info_file), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_mybool Sys_relay_log_purge(
+ "relay_log_purge", "if disabled - do not purge relay logs. "
+ "if enabled - purge them as soon as they are no more needed",
+ GLOBAL_VAR(relay_log_purge), CMD_LINE(OPT_ARG), DEFAULT(TRUE));
+
+static Sys_var_mybool Sys_relay_log_recovery(
+ "relay_log_recovery", "Enables automatic relay log recovery "
+ "right after the database startup, which means that the IO Thread "
+ "starts re-fetching from the master right after the last transaction "
+ "processed",
+ GLOBAL_VAR(relay_log_recovery), CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+
+bool Sys_var_rpl_filter::global_update(THD *thd, set_var *var)
+{
+ bool result= true; // Assume error
+ Master_info *mi;
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+
+ if (!var->base.length) // no base name
+ {
+ mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_ERROR);
+ }
+ else // has base name
+ {
+ mi= master_info_index->
+ get_master_info(&var->base,
+ Sql_condition::WARN_LEVEL_WARN);
+ }
+
+ if (mi)
+ {
+ if (mi->rli.slave_running)
+ {
+ my_error(ER_SLAVE_MUST_STOP, MYF(0),
+ mi->connection_name.length,
+ mi->connection_name.str);
+ result= true;
+ }
+ else
+ {
+ result= set_filter_value(var->save_result.string_value.str, mi);
+ }
+ }
+
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ return result;
+}
+
+bool Sys_var_rpl_filter::set_filter_value(const char *value, Master_info *mi)
+{
+ bool status= true;
+ Rpl_filter* rpl_filter= mi ? mi->rpl_filter : global_rpl_filter;
+
+ switch (opt_id) {
+ case OPT_REPLICATE_DO_DB:
+ status= rpl_filter->set_do_db(value);
+ break;
+ case OPT_REPLICATE_DO_TABLE:
+ status= rpl_filter->set_do_table(value);
+ break;
+ case OPT_REPLICATE_IGNORE_DB:
+ status= rpl_filter->set_ignore_db(value);
+ break;
+ case OPT_REPLICATE_IGNORE_TABLE:
+ status= rpl_filter->set_ignore_table(value);
+ break;
+ case OPT_REPLICATE_WILD_DO_TABLE:
+ status= rpl_filter->set_wild_do_table(value);
+ break;
+ case OPT_REPLICATE_WILD_IGNORE_TABLE:
+ status= rpl_filter->set_wild_ignore_table(value);
+ break;
+ }
+
+ return status;
+}
+
+uchar *Sys_var_rpl_filter::global_value_ptr(THD *thd, LEX_STRING *base)
+{
+ char buf[256];
+ String tmp(buf, sizeof(buf), &my_charset_bin);
+ uchar *ret;
+ Master_info *mi;
+ Rpl_filter *rpl_filter;
+
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (!base->length) // no base name
+ {
+ mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_ERROR);
+ }
+ else // has base name
+ {
+ mi= master_info_index->
+ get_master_info(base,
+ Sql_condition::WARN_LEVEL_WARN);
+ }
+ mysql_mutex_lock(&LOCK_global_system_variables);
+
+ if (!mi)
+ {
+ mysql_mutex_unlock(&LOCK_active_mi);
+ return 0;
+ }
+ rpl_filter= mi->rpl_filter;
+ tmp.length(0);
+
+ switch (opt_id) {
+ case OPT_REPLICATE_DO_DB:
+ rpl_filter->get_do_db(&tmp);
+ break;
+ case OPT_REPLICATE_DO_TABLE:
+ rpl_filter->get_do_table(&tmp);
+ break;
+ case OPT_REPLICATE_IGNORE_DB:
+ rpl_filter->get_ignore_db(&tmp);
+ break;
+ case OPT_REPLICATE_IGNORE_TABLE:
+ rpl_filter->get_ignore_table(&tmp);
+ break;
+ case OPT_REPLICATE_WILD_DO_TABLE:
+ rpl_filter->get_wild_do_table(&tmp);
+ break;
+ case OPT_REPLICATE_WILD_IGNORE_TABLE:
+ rpl_filter->get_wild_ignore_table(&tmp);
+ break;
+ }
+
+ ret= (uchar *) thd->strmake(tmp.ptr(), tmp.length());
+ mysql_mutex_unlock(&LOCK_active_mi);
+
+ return ret;
+}
+
+static Sys_var_rpl_filter Sys_replicate_do_db(
+ "replicate_do_db", OPT_REPLICATE_DO_DB,
+ "Tell the slave to restrict replication to updates of tables "
+ "whose names appear in the comma-separated list. For "
+ "statement-based replication, only the default database (that "
+ "is, the one selected by USE) is considered, not any explicitly "
+ "mentioned tables in the query. For row-based replication, the "
+ "actual names of table(s) being updated are checked.");
+
+static Sys_var_rpl_filter Sys_replicate_do_table(
+ "replicate_do_table", OPT_REPLICATE_DO_TABLE,
+ "Tells the slave to restrict replication to tables in the "
+ "comma-separated list.");
+
+static Sys_var_rpl_filter Sys_replicate_ignore_db(
+ "replicate_ignore_db", OPT_REPLICATE_IGNORE_DB,
+ "Tell the slave to restrict replication to updates of tables "
+ "whose names do not appear in the comma-separated list. For "
+ "statement-based replication, only the default database (that "
+ "is, the one selected by USE) is considered, not any explicitly "
+ "mentioned tables in the query. For row-based replication, the "
+ "actual names of table(s) being updated are checked.");
+
+static Sys_var_rpl_filter Sys_replicate_ignore_table(
+ "replicate_ignore_table", OPT_REPLICATE_IGNORE_TABLE,
+ "Tells the slave thread not to replicate any statement that "
+ "updates the specified table, even if any other tables might be "
+ "updated by the same statement.");
+
+static Sys_var_rpl_filter Sys_replicate_wild_do_table(
+ "replicate_wild_do_table", OPT_REPLICATE_WILD_DO_TABLE,
+ "Tells the slave thread to restrict replication to statements "
+ "where any of the updated tables match the specified database "
+ "and table name patterns.");
+
+static Sys_var_rpl_filter Sys_replicate_wild_ignore_table(
+ "replicate_wild_ignore_table", OPT_REPLICATE_WILD_IGNORE_TABLE,
+ "Tells the slave thread to not replicate to the tables that "
+ "match the given wildcard pattern.");
+
+static Sys_var_charptr Sys_slave_load_tmpdir(
+ "slave_load_tmpdir", "The location where the slave should put "
+ "its temporary files when replicating a LOAD DATA INFILE command",
+ READ_ONLY GLOBAL_VAR(slave_load_tmpdir), CMD_LINE(REQUIRED_ARG),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_uint Sys_slave_net_timeout(
+ "slave_net_timeout", "Number of seconds to wait for more data "
+ "from any master/slave connection before aborting the read",
+ GLOBAL_VAR(slave_net_timeout), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(SLAVE_NET_TIMEOUT), BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(0));
+
+
+/*
+ Access a multi_source variable
+ Return 0 + warning if it doesn't exist
+*/
+
+ulonglong Sys_var_multi_source_ulonglong::
+get_master_info_ulonglong_value(THD *thd, ptrdiff_t offset)
+{
+ Master_info *mi;
+ ulonglong res= 0; // Default value
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_WARN);
+ if (mi)
+ {
+ mysql_mutex_lock(&mi->rli.data_lock);
+ res= *((ulonglong*) (((uchar*) mi) + master_info_offset));
+ mysql_mutex_unlock(&mi->rli.data_lock);
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ return res;
+}
+
+
+bool update_multi_source_variable(sys_var *self_var, THD *thd,
+ enum_var_type type)
+{
+ Sys_var_multi_source_ulonglong *self= (Sys_var_multi_source_ulonglong*) self_var;
+ bool result= true;
+ Master_info *mi;
+
+ if (type == OPT_GLOBAL)
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ mysql_mutex_lock(&LOCK_active_mi);
+ mi= master_info_index->
+ get_master_info(&thd->variables.default_master_connection,
+ Sql_condition::WARN_LEVEL_ERROR);
+ if (mi)
+ {
+ mysql_mutex_lock(&mi->rli.run_lock);
+ mysql_mutex_lock(&mi->rli.data_lock);
+ result= self->update_variable(thd, mi);
+ mysql_mutex_unlock(&mi->rli.data_lock);
+ mysql_mutex_unlock(&mi->rli.run_lock);
+ }
+ mysql_mutex_unlock(&LOCK_active_mi);
+ if (type == OPT_GLOBAL)
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ return result;
+}
+
+static bool update_slave_skip_counter(sys_var *self, THD *thd, Master_info *mi)
+{
+ if (mi->rli.slave_running)
+ {
+ my_error(ER_SLAVE_MUST_STOP, MYF(0), mi->connection_name.length,
+ mi->connection_name.str);
+ return true;
+ }
+ /* The value was stored temporarily in thd */
+ mi->rli.slave_skip_counter= thd->variables.slave_skip_counter;
+ return false;
+}
+
+static Sys_var_multi_source_ulonglong Sys_slave_skip_counter(
+ "sql_slave_skip_counter", "Skip the next N events from the master log",
+ SESSION_VAR(slave_skip_counter), NO_CMD_LINE,
+ MASTER_INFO_VAR(rli.slave_skip_counter),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1),
+ ON_UPDATE(update_slave_skip_counter));
+
+static bool update_max_relay_log_size(sys_var *self, THD *thd, Master_info *mi)
+{
+ mi->rli.max_relay_log_size= thd->variables.max_relay_log_size;
+ mi->rli.relay_log.set_max_size(mi->rli.max_relay_log_size);
+ return false;
+}
+
+static Sys_var_multi_source_ulonglong Sys_max_relay_log_size(
+ "max_relay_log_size",
+ "relay log will be rotated automatically when the size exceeds this "
+ "value. If 0 are startup, it's set to max_binlog_size",
+ SESSION_VAR(max_relay_log_size), CMD_LINE(REQUIRED_ARG),
+ MASTER_INFO_VAR(rli.max_relay_log_size),
+ VALID_RANGE(0, 1024L*1024*1024), DEFAULT(0), BLOCK_SIZE(IO_SIZE),
+ ON_UPDATE(update_max_relay_log_size));
+
+static Sys_var_charptr Sys_slave_skip_errors(
+ "slave_skip_errors", "Tells the slave thread to continue "
+ "replication when a query event returns an error from the "
+ "provided list",
+ READ_ONLY GLOBAL_VAR(opt_slave_skip_errors), CMD_LINE(REQUIRED_ARG),
+ IN_SYSTEM_CHARSET, DEFAULT(0));
+
+static Sys_var_ulonglong Sys_relay_log_space_limit(
+ "relay_log_space_limit", "Maximum space to use for all relay logs",
+ READ_ONLY GLOBAL_VAR(relay_log_space_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, ULONGLONG_MAX), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_uint Sys_sync_relaylog_period(
+ "sync_relay_log", "Synchronously flush relay log to disk after "
+ "every #th event. Use 0 (default) to disable synchronous flushing",
+ GLOBAL_VAR(sync_relaylog_period), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_uint Sys_sync_relayloginfo_period(
+ "sync_relay_log_info", "Synchronously flush relay log info "
+ "to disk after every #th transaction. Use 0 (default) to disable "
+ "synchronous flushing",
+ GLOBAL_VAR(sync_relayloginfo_period), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+#endif
+
+static Sys_var_uint Sys_sync_binlog_period(
+ "sync_binlog", "Synchronously flush binary log to disk after "
+ "every #th event. Use 0 (default) to disable synchronous flushing",
+ GLOBAL_VAR(sync_binlog_period), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_uint Sys_sync_masterinfo_period(
+ "sync_master_info", "Synchronously flush master info to disk "
+ "after every #th event. Use 0 (default) to disable synchronous flushing",
+ GLOBAL_VAR(sync_masterinfo_period), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+
+#ifdef HAVE_REPLICATION
+static Sys_var_ulong Sys_slave_trans_retries(
+ "slave_transaction_retries", "Number of times the slave SQL "
+ "thread will retry a transaction in case it failed with a deadlock "
+ "or elapsed lock wait timeout, before giving up and stopping",
+ GLOBAL_VAR(slave_trans_retries), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(10), BLOCK_SIZE(1));
+#endif
+
+static bool check_locale(sys_var *self, THD *thd, set_var *var)
+{
+ if (!var->value)
+ return false;
+
+ MY_LOCALE *locale;
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ if (var->value->result_type() == INT_RESULT)
+ {
+ int lcno= (int)var->value->val_int();
+ if (!(locale= my_locale_by_number(lcno)))
+ {
+ my_error(ER_UNKNOWN_LOCALE, MYF(0), llstr(lcno, buff));
+ return true;
+ }
+ if (check_not_null(self, thd, var))
+ return true;
+ }
+ else // STRING_RESULT
+ {
+ String str(buff, sizeof(buff), system_charset_info), *res;
+ if (!(res=var->value->val_str(&str)))
+ return true;
+ else if (!(locale= my_locale_by_name(res->c_ptr_safe())))
+ {
+ ErrConvString err(res);
+ my_error(ER_UNKNOWN_LOCALE, MYF(0), err.ptr());
+ return true;
+ }
+ }
+
+ var->save_result.ptr= locale;
+
+ if (!locale->errmsgs->errmsgs)
+ {
+ bool res;
+ mysql_mutex_lock(&LOCK_error_messages);
+ res= (!locale->errmsgs->errmsgs &&
+ read_texts(ERRMSG_FILE, locale->errmsgs->language,
+ &locale->errmsgs->errmsgs,
+ ER_ERROR_LAST - ER_ERROR_FIRST + 1));
+ mysql_mutex_unlock(&LOCK_error_messages);
+ if (res)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
+ "Can't process error message file for locale '%s'",
+ locale->name);
+ return true;
+ }
+ }
+ status_var_increment(thd->status_var.feature_locale);
+ return false;
+}
+
+static Sys_var_struct Sys_lc_messages(
+ "lc_messages", "Set the language used for the error messages",
+ SESSION_VAR(lc_messages), NO_CMD_LINE,
+ my_offsetof(MY_LOCALE, name), DEFAULT(&my_default_lc_messages),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_locale));
+
+static Sys_var_struct Sys_lc_time_names(
+ "lc_time_names", "Set the language used for the month "
+ "names and the days of the week",
+ SESSION_VAR(lc_time_names), NO_CMD_LINE,
+ my_offsetof(MY_LOCALE, name), DEFAULT(&my_default_lc_time_names),
+ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_locale));
+
+static Sys_var_tz Sys_time_zone(
+ "time_zone", "time_zone",
+ SESSION_VAR(time_zone), NO_CMD_LINE,
+ DEFAULT(&default_tz), NO_MUTEX_GUARD, IN_BINLOG);
+
+static bool fix_host_cache_size(sys_var *, THD *, enum_var_type)
+{
+ hostname_cache_resize((uint) host_cache_size);
+ return false;
+}
+
+static Sys_var_ulong Sys_host_cache_size(
+ "host_cache_size",
+ "How many host names should be cached to avoid resolving.",
+ GLOBAL_VAR(host_cache_size),
+ CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 65536),
+ DEFAULT(HOST_CACHE_SIZE),
+ BLOCK_SIZE(1),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL),
+ ON_UPDATE(fix_host_cache_size));
+
+static Sys_var_charptr Sys_ignore_db_dirs(
+ "ignore_db_dirs",
+ "Specifies a directory to add to the ignore list when collecting "
+ "database names from the datadir. Put a blank argument to reset "
+ "the list accumulated so far.",
+ READ_ONLY GLOBAL_VAR(opt_ignore_db_dirs),
+ CMD_LINE(REQUIRED_ARG, OPT_IGNORE_DB_DIRECTORY),
+ IN_FS_CHARSET, DEFAULT(0));
+
+static Sys_var_ulong Sys_sp_cache_size(
+ "stored_program_cache",
+ "The soft upper limit for number of cached stored routines for "
+ "one connection.",
+ GLOBAL_VAR(stored_program_cache_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 512 * 1024), DEFAULT(256), BLOCK_SIZE(1));
+
+export const char *plugin_maturity_names[]=
+{ "unknown", "experimental", "alpha", "beta", "gamma", "stable", 0 };
+static Sys_var_enum Sys_plugin_maturity(
+ "plugin_maturity",
+ "The lowest desirable plugin maturity "
+ "(unknown, experimental, alpha, beta, gamma, or stable). "
+ "Plugins less mature than that will not be installed or loaded.",
+ READ_ONLY GLOBAL_VAR(plugin_maturity), CMD_LINE(REQUIRED_ARG),
+ plugin_maturity_names, DEFAULT(MariaDB_PLUGIN_MATURITY_UNKNOWN));
+
+static Sys_var_ulong Sys_deadlock_search_depth_short(
+ "deadlock_search_depth_short",
+ "Short search depth for the two-step deadlock detection",
+ SESSION_VAR(wt_deadlock_search_depth_short), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 32), DEFAULT(4), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_deadlock_search_depth_long(
+ "deadlock_search_depth_long",
+ "Long search depth for the two-step deadlock detection",
+ SESSION_VAR(wt_deadlock_search_depth_long), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 33), DEFAULT(15), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_deadlock_timeout_depth_short(
+ "deadlock_timeout_short",
+ "Short timeout for the two-step deadlock detection (in microseconds)",
+ SESSION_VAR(wt_timeout_short), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(10000), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_deadlock_timeout_depth_long(
+ "deadlock_timeout_long",
+ "Long timeout for the two-step deadlock detection (in microseconds)",
+ SESSION_VAR(wt_timeout_long), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(50000000), BLOCK_SIZE(1));
+
+#ifndef DBUG_OFF
+static Sys_var_ulong Sys_debug_crc_break(
+ "debug_crc_break",
+ "Call my_debug_put_break_here() if crc matches this number (for debug)",
+ GLOBAL_VAR(my_crc_dbug_check), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1));
+#endif
+
+static Sys_var_uint Sys_extra_port(
+ "extra_port",
+ "Extra port number to use for tcp connections in a "
+ "one-thread-per-connection manner. 0 means don't use another port",
+ READ_ONLY GLOBAL_VAR(mysqld_extra_port), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX32), DEFAULT(0), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_extra_max_connections(
+ "extra_max_connections", "The number of connections on extra-port",
+ GLOBAL_VAR(extra_max_connections), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, 100000), DEFAULT(1), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_max_connections));
+
+#ifdef SAFE_MUTEX
+static Sys_var_mybool Sys_mutex_deadlock_detector(
+ "mutex_deadlock_detector", "Enable checking of wrong mutex usage",
+ READ_ONLY GLOBAL_VAR(safe_mutex_deadlock_detector),
+ CMD_LINE(OPT_ARG), DEFAULT(TRUE));
+#endif
+
+static Sys_var_keycache Sys_key_cache_segments(
+ "key_cache_segments", "The number of segments in a key cache",
+ KEYCACHE_VAR(param_partitions),
+ CMD_LINE(REQUIRED_ARG, OPT_KEY_CACHE_PARTITIONS),
+ VALID_RANGE(0, MAX_KEY_CACHE_PARTITIONS),
+ DEFAULT(DEFAULT_KEY_CACHE_PARTITIONS),
+ BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+ ON_UPDATE(repartition_keycache));
+
+static const char *log_slow_filter_names[]=
+{ "admin", "filesort", "filesort_on_disk", "full_join", "full_scan",
+ "query_cache", "query_cache_miss", "tmp_table", "tmp_table_on_disk", 0
+};
+static Sys_var_set Sys_log_slow_filter(
+ "log_slow_filter",
+ "Log only certain types of queries. Multiple "
+ "flags can be specified, separated by commas. Valid values are admin, "
+ "slave, filesort, filesort_on_disk, full_join, full_scan, query_cache, "
+ "query_cache_miss, tmp_table, tmp_table_on_disk",
+ SESSION_VAR(log_slow_filter), CMD_LINE(REQUIRED_ARG),
+ log_slow_filter_names,
+ DEFAULT(MAX_SET(array_elements(log_slow_filter_names)-1)));
+
+static const char *default_regex_flags_names[]=
+{
+ "DOTALL", // (?s) . matches anything including NL
+ "DUPNAMES", // (?J) Allow duplicate names for subpatterns
+ "EXTENDED", // (?x) Ignore white space and # comments
+ "EXTRA", // (?X) extra features (e.g. error on unknown escape character)
+ "MULTILINE", // (?m) ^ and $ match newlines within data
+ "UNGREEDY", // (?U) Invert greediness of quantifiers
+ 0
+};
+static const int default_regex_flags_to_pcre[]=
+{
+ PCRE_DOTALL,
+ PCRE_DUPNAMES,
+ PCRE_EXTENDED,
+ PCRE_EXTRA,
+ PCRE_MULTILINE,
+ PCRE_UNGREEDY,
+ 0
+};
+int default_regex_flags_pcre(const THD *thd)
+{
+ ulonglong src= thd->variables.default_regex_flags;
+ int i, res;
+ for (i= res= 0; default_regex_flags_to_pcre[i]; i++)
+ {
+ if (src & (1 << i))
+ res|= default_regex_flags_to_pcre[i];
+ }
+ return res;
+}
+static Sys_var_set Sys_default_regex_flags(
+ "default_regex_flags",
+ "Default flags for the regex library. "
+ "Syntax: default-regex-flags='[flag[,flag[,flag...]]]'. "
+ "See the manual for the complete list of valid flags",
+ SESSION_VAR(default_regex_flags), CMD_LINE(REQUIRED_ARG),
+ default_regex_flags_names,
+ DEFAULT(0));
+
+static Sys_var_ulong Sys_log_slow_rate_limit(
+ "log_slow_rate_limit",
+ "Write to slow log every #th slow query. Set to 1 to log everything. "
+ "Increase it to reduce the size of the slow or the performance impact "
+ "of slow logging",
+ SESSION_VAR(log_slow_rate_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(1, UINT_MAX), DEFAULT(1), BLOCK_SIZE(1));
+
+static const char *log_slow_verbosity_names[]= { "innodb", "query_plan",
+ "explain", 0 };
+static Sys_var_set Sys_log_slow_verbosity(
+ "log_slow_verbosity",
+ "log-slow-verbosity=[value[,value ...]] where value is one of "
+ "'innodb', 'query_plan', 'explain' ",
+ SESSION_VAR(log_slow_verbosity), CMD_LINE(REQUIRED_ARG),
+ log_slow_verbosity_names, DEFAULT(LOG_SLOW_VERBOSITY_INIT));
+
+static Sys_var_ulong Sys_join_cache_level(
+ "join_cache_level",
+ "Controls what join operations can be executed with join buffers. Odd "
+ "numbers are used for plain join buffers while even numbers are used "
+ "for linked buffers",
+ SESSION_VAR(join_cache_level), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 8), DEFAULT(2), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_mrr_buffer_size(
+ "mrr_buffer_size",
+ "Size of buffer to use when using MRR with range access",
+ SESSION_VAR(mrr_buff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(IO_SIZE*2, INT_MAX32), DEFAULT(256*1024), BLOCK_SIZE(1));
+
+static Sys_var_ulong Sys_rowid_merge_buff_size(
+ "rowid_merge_buff_size",
+ "The size of the buffers used [NOT] IN evaluation via partial matching",
+ SESSION_VAR(rowid_merge_buff_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, ((ulonglong)~(intptr)0)/2), DEFAULT(8*1024*1024),
+ BLOCK_SIZE(1));
+
+static Sys_var_mybool Sys_userstat(
+ "userstat",
+ "Enables statistics gathering for USER_STATISTICS, CLIENT_STATISTICS, "
+ "INDEX_STATISTICS and TABLE_STATISTICS tables in the INFORMATION_SCHEMA",
+ GLOBAL_VAR(opt_userstat_running),
+ CMD_LINE(OPT_ARG), DEFAULT(FALSE));
+
+static Sys_var_mybool Sys_binlog_annotate_row_events(
+ "binlog_annotate_row_events",
+ "Tells the master to annotate RBR events with the statement that "
+ "caused these events",
+ SESSION_VAR(binlog_annotate_row_events), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+#ifdef HAVE_REPLICATION
+static Sys_var_mybool Sys_replicate_annotate_row_events(
+ "replicate_annotate_row_events",
+ "Tells the slave to write annotate rows events received from the master "
+ "to its own binary log. Ignored if log_slave_updates is not set",
+ READ_ONLY GLOBAL_VAR(opt_replicate_annotate_row_events),
+ CMD_LINE(OPT_ARG), DEFAULT(0));
+#endif
+
+static Sys_var_ulonglong Sys_join_buffer_space_limit(
+ "join_buffer_space_limit",
+ "The limit of the space for all join buffers used by a query",
+ SESSION_VAR(join_buff_space_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(2048, ULONGLONG_MAX), DEFAULT(16*128*1024),
+ BLOCK_SIZE(2048));
+
+static Sys_var_ulong Sys_progress_report_time(
+ "progress_report_time",
+ "Seconds between sending progress reports to the client for "
+ "time-consuming statements. Set to 0 to disable progress reporting.",
+ SESSION_VAR(progress_report_time), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(5), BLOCK_SIZE(1));
+
+const char *use_stat_tables_modes[] =
+ {"NEVER", "COMPLEMENTARY", "PREFERABLY", 0};
+static Sys_var_enum Sys_optimizer_use_stat_tables(
+ "use_stat_tables",
+ "Specifies how to use system statistics tables. Possible values are "
+ "NEVER, COMPLEMENTARY, PREFERABLY",
+ SESSION_VAR(use_stat_tables), CMD_LINE(REQUIRED_ARG),
+ use_stat_tables_modes, DEFAULT(0));
+
+static Sys_var_ulong Sys_histogram_size(
+ "histogram_size",
+ "Number of bytes used for a histogram. "
+ "If set to 0, no histograms are created by ANALYZE.",
+ SESSION_VAR(histogram_size), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, 255), DEFAULT(0), BLOCK_SIZE(1));
+
+extern const char *histogram_types[];
+static Sys_var_enum Sys_histogram_type(
+ "histogram_type",
+ "Specifies type of the histograms created by ANALYZE. "
+ "Possible values are: "
+ "SINGLE_PREC_HB - single precision height-balanced, "
+ "DOUBLE_PREC_HB - double precision height-balanced.",
+ SESSION_VAR(histogram_type), CMD_LINE(REQUIRED_ARG),
+ histogram_types, DEFAULT(0));
+
+static Sys_var_mybool Sys_no_thread_alarm(
+ "debug_no_thread_alarm",
+ "Disable system thread alarm calls. Disabling it may be useful "
+ "in debugging or testing, never do it in production",
+ READ_ONLY GLOBAL_VAR(my_disable_thr_alarm), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+static Sys_var_mybool Sys_query_cache_strip_comments(
+ "query_cache_strip_comments",
+ "Strip all comments from a query before storing it "
+ "in the query cache",
+ SESSION_VAR(query_cache_strip_comments), CMD_LINE(OPT_ARG),
+ DEFAULT(FALSE));
+
+static ulonglong in_transaction(THD *thd)
+{
+ return MY_TEST(thd->in_active_multi_stmt_transaction());
+}
+static Sys_var_session_special Sys_in_transaction(
+ "in_transaction", "Whether there is an active transaction",
+ READ_ONLY sys_var::ONLY_SESSION, NO_CMD_LINE,
+ VALID_RANGE(0, 1), BLOCK_SIZE(1), NO_MUTEX_GUARD,
+ NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), ON_READ(in_transaction));
+
+#ifndef DBUG_OFF
+static Sys_var_ulong Sys_debug_binlog_fsync_sleep(
+ "debug_binlog_fsync_sleep",
+ "Extra sleep (in microseconds) to add to binlog fsync(), for debugging",
+ GLOBAL_VAR(opt_binlog_dbug_fsync_sleep),
+ CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1));
+#endif
+
+static Sys_var_harows Sys_expensive_subquery_limit(
+ "expensive_subquery_limit",
+ "The maximum number of rows a subquery may examine in order to be "
+ "executed during optimization and used for constant optimization",
+ SESSION_VAR(expensive_subquery_limit), CMD_LINE(REQUIRED_ARG),
+ VALID_RANGE(0, HA_POS_ERROR), DEFAULT(100), BLOCK_SIZE(1));
+
+static bool check_pseudo_slave_mode(sys_var *self, THD *thd, set_var *var)
+{
+ longlong previous_val= thd->variables.pseudo_slave_mode;
+ longlong val= (longlong) var->save_result.ulonglong_value;
+ bool rli_fake= false;
+
+#ifndef EMBEDDED_LIBRARY
+ rli_fake= thd->rli_fake ? true : false;
+#endif
+
+ if (rli_fake)
+ {
+ if (!val)
+ {
+#ifndef EMBEDDED_LIBRARY
+ delete thd->rli_fake;
+ thd->rli_fake= NULL;
+ delete thd->rgi_fake;
+ thd->rgi_fake= NULL;
+#endif
+ }
+ else if (previous_val && val)
+ goto ineffective;
+ else if (!previous_val && val)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_VALUE_FOR_VAR,
+ "'pseudo_slave_mode' is already ON.");
+ }
+ else
+ {
+ if (!previous_val && !val)
+ goto ineffective;
+ else if (previous_val && !val)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_VALUE_FOR_VAR,
+ "Slave applier execution mode not active, "
+ "statement ineffective.");
+ }
+ goto end;
+
+ineffective:
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WRONG_VALUE_FOR_VAR,
+ "'pseudo_slave_mode' change was ineffective.");
+
+end:
+ return FALSE;
+}
+static Sys_var_mybool Sys_pseudo_slave_mode(
+ "pseudo_slave_mode",
+ "SET pseudo_slave_mode= 0,1 are commands that mysqlbinlog "
+ "adds to beginning and end of binary log dumps. While zero "
+ "value indeed disables, the actual enabling of the slave "
+ "applier execution mode is done implicitly when a "
+ "Format_description_event is sent through the session.",
+ SESSION_ONLY(pseudo_slave_mode), NO_CMD_LINE, DEFAULT(FALSE),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_pseudo_slave_mode));
+
diff --git a/sql/sys_vars.h b/sql/sys_vars.h
new file mode 100644
index 00000000000..36067c50cc1
--- /dev/null
+++ b/sql/sys_vars.h
@@ -0,0 +1,2288 @@
+/* Copyright (c) 2002, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2013, Monty Program Ab.
+
+ 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 */
+
+/**
+ @file
+ "private" interface to sys_var - server configuration variables.
+
+ This header is included only by the file that contains declarations
+ of sys_var variables (sys_vars.cc).
+*/
+
+#include "sys_vars_shared.h"
+#include <my_getopt.h>
+#include <my_bit.h>
+#include <my_dir.h>
+#include "keycaches.h"
+#include "strfunc.h"
+#include "tztime.h" // my_tz_find, my_tz_SYSTEM, struct Time_zone
+#include "rpl_mi.h" // For Multi-Source Replication
+
+/*
+ a set of mostly trivial (as in f(X)=X) defines below to make system variable
+ declarations more readable
+*/
+#define VALID_RANGE(X,Y) X,Y
+#define DEFAULT(X) X
+#define BLOCK_SIZE(X) X
+#define GLOBAL_VAR(X) sys_var::GLOBAL, (((char*)&(X))-(char*)&global_system_variables), sizeof(X)
+#define SESSION_VAR(X) sys_var::SESSION, offsetof(SV, X), sizeof(((SV *)0)->X)
+#define SESSION_ONLY(X) sys_var::ONLY_SESSION, offsetof(SV, X), sizeof(((SV *)0)->X)
+#define NO_CMD_LINE CMD_LINE(NO_ARG, -1)
+/*
+ the define below means that there's no *second* mutex guard,
+ LOCK_global_system_variables always guards all system variables
+*/
+#define NO_MUTEX_GUARD ((PolyLock*)0)
+#define IN_BINLOG sys_var::SESSION_VARIABLE_IN_BINLOG
+#define NOT_IN_BINLOG sys_var::VARIABLE_NOT_IN_BINLOG
+#define ON_READ(X) X
+#define ON_CHECK(X) X
+#define ON_UPDATE(X) X
+#define READ_ONLY sys_var::READONLY+
+// this means that Sys_var_charptr initial value was malloc()ed
+#define PREALLOCATED sys_var::ALLOCATED+
+#define PARSED_EARLY sys_var::PARSE_EARLY+
+#define SHOW_VALUE_IN_HELP sys_var::SHOW_VALUE_IN_HELP+
+
+/*
+ Sys_var_bit meaning is reversed, like in
+ @@foreign_key_checks <-> OPTION_NO_FOREIGN_KEY_CHECKS
+*/
+#define REVERSE(X) ~(X)
+#define DEPRECATED(X) X
+
+#define session_var(THD, TYPE) (*(TYPE*)session_var_ptr(THD))
+#define global_var(TYPE) (*(TYPE*)global_var_ptr())
+
+#if SIZEOF_OFF_T > 4 && defined(BIG_TABLES)
+#define GET_HA_ROWS GET_ULL
+#else
+#define GET_HA_ROWS GET_ULONG
+#endif
+
+/*
+ special assert for sysvars. Tells the name of the variable,
+ and fails even in non-debug builds.
+
+ It is supposed to be used *only* in Sys_var* constructors,
+ and has name_arg hard-coded to prevent incorrect usage.
+*/
+#define SYSVAR_ASSERT(X) \
+ while(!(X)) \
+ { \
+ fprintf(stderr, "Sysvar '%s' failed '%s'\n", name_arg, #X); \
+ DBUG_ABORT(); \
+ exit(255); \
+ }
+
+enum charset_enum {IN_SYSTEM_CHARSET, IN_FS_CHARSET};
+
+static const char *bool_values[3]= {"OFF", "ON", 0};
+TYPELIB bool_typelib={ array_elements(bool_values)-1, "", bool_values, 0 };
+
+/**
+ A small wrapper class to pass getopt arguments as a pair
+ to the Sys_var_* constructors. It improves type safety and helps
+ to catch errors in the argument order.
+*/
+struct CMD_LINE
+{
+ int id;
+ enum get_opt_arg_type arg_type;
+ CMD_LINE(enum get_opt_arg_type getopt_arg_type, int getopt_id=0)
+ : id(getopt_id), arg_type(getopt_arg_type) {}
+};
+
+/**
+ Sys_var_integer template is used to generate Sys_var_* classes
+ for variables that represent the value as an integer number.
+ They are Sys_var_uint, Sys_var_ulong, Sys_var_harows, Sys_var_ulonglong,
+ Sys_var_int.
+
+ An integer variable has a minimal and maximal values, and a "block_size"
+ (any valid value of the variable must be divisible by the block_size).
+
+ Class specific constructor arguments: min, max, block_size
+ Backing store: int, uint, ulong, ha_rows, ulonglong, depending on the class
+*/
+template <typename T, ulong ARGT, enum enum_mysql_show_type SHOWT>
+class Sys_var_integer: public sys_var
+{
+public:
+ Sys_var_integer(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ T min_val, T max_val, T def_val, uint block_size, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOWT, def_val, lock, binlog_status_arg,
+ on_check_func, on_update_func, substitute)
+ {
+ option.var_type= ARGT;
+ option.min_value= min_val;
+ option.max_value= max_val;
+ option.block_size= block_size;
+ option.u_max_value= (uchar**)max_var_ptr();
+ if (max_var_ptr())
+ *max_var_ptr()= max_val;
+
+ global_var(T)= def_val;
+ SYSVAR_ASSERT(size == sizeof(T));
+ SYSVAR_ASSERT(min_val < max_val);
+ SYSVAR_ASSERT(min_val <= def_val);
+ SYSVAR_ASSERT(max_val >= def_val);
+ SYSVAR_ASSERT(block_size > 0);
+ SYSVAR_ASSERT(def_val % block_size == 0);
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ my_bool fixed= FALSE, unused;
+ longlong v= var->value->val_int();
+
+ if ((ARGT == GET_HA_ROWS) || (ARGT == GET_UINT) ||
+ (ARGT == GET_ULONG) || (ARGT == GET_ULL))
+ {
+ ulonglong uv;
+
+ /*
+ if the value is signed and negative,
+ and a variable is unsigned, it is set to zero
+ */
+ if ((fixed= (!var->value->unsigned_flag && v < 0)))
+ uv= 0;
+ else
+ uv= v;
+
+ var->save_result.ulonglong_value=
+ getopt_ull_limit_value(uv, &option, &unused);
+
+ if (max_var_ptr() && (T)var->save_result.ulonglong_value > *max_var_ptr())
+ var->save_result.ulonglong_value= *max_var_ptr();
+
+ fixed= fixed || var->save_result.ulonglong_value != uv;
+ }
+ else
+ {
+ /*
+ if the value is unsigned and has the highest bit set
+ and a variable is signed, it is set to max signed value
+ */
+ if ((fixed= (var->value->unsigned_flag && v < 0)))
+ v= LONGLONG_MAX;
+
+ var->save_result.longlong_value=
+ getopt_ll_limit_value(v, &option, &unused);
+
+ if (max_var_ptr() && (T)var->save_result.longlong_value > *max_var_ptr())
+ var->save_result.longlong_value= *max_var_ptr();
+
+ fixed= fixed || var->save_result.longlong_value != v;
+ }
+ return throw_bounds_warning(thd, name.str, fixed,
+ var->value->unsigned_flag, v);
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, T)= static_cast<T>(var->save_result.ulonglong_value);
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(T)= static_cast<T>(var->save_result.ulonglong_value);
+ return false;
+ }
+ bool check_update_type(Item_result type)
+ { return type != INT_RESULT; }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= (ulonglong)*(T*)global_value_ptr(thd, 0); }
+ void global_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= option.def_value; }
+ private:
+ T *max_var_ptr()
+ {
+ return scope() == SESSION ? (T*)(((uchar*)&max_system_variables) + offset)
+ : 0;
+ }
+};
+
+typedef Sys_var_integer<int, GET_INT, SHOW_SINT> Sys_var_int;
+typedef Sys_var_integer<uint, GET_UINT, SHOW_UINT> Sys_var_uint;
+typedef Sys_var_integer<ulong, GET_ULONG, SHOW_ULONG> Sys_var_ulong;
+typedef Sys_var_integer<ha_rows, GET_HA_ROWS, SHOW_HA_ROWS> Sys_var_harows;
+typedef Sys_var_integer<ulonglong, GET_ULL, SHOW_ULONGLONG> Sys_var_ulonglong;
+typedef Sys_var_integer<long, GET_LONG, SHOW_SLONG> Sys_var_long;
+
+
+/**
+ Helper class for variables that take values from a TYPELIB
+*/
+class Sys_var_typelib: public sys_var
+{
+protected:
+ TYPELIB typelib;
+public:
+ Sys_var_typelib(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off,
+ CMD_LINE getopt,
+ SHOW_TYPE show_val_type_arg, const char *values[],
+ ulonglong def_val, PolyLock *lock,
+ enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func, on_update_function on_update_func,
+ const char *substitute)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, show_val_type_arg, def_val, lock,
+ binlog_status_arg, on_check_func,
+ on_update_func, substitute)
+ {
+ for (typelib.count= 0; values[typelib.count]; typelib.count++) /*no-op */;
+ typelib.name="";
+ typelib.type_names= values;
+ typelib.type_lengths= 0; // only used by Fields_enum and Field_set
+ option.typelib= &typelib;
+ }
+ bool do_check(THD *thd, set_var *var) // works for enums and my_bool
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), system_charset_info), *res;
+
+ if (var->value->result_type() == STRING_RESULT)
+ {
+ if (!(res=var->value->val_str(&str)))
+ return true;
+ else
+ if (!(var->save_result.ulonglong_value=
+ find_type(&typelib, res->ptr(), res->length(), false)))
+ return true;
+ else
+ var->save_result.ulonglong_value--;
+ }
+ else
+ {
+ longlong tmp=var->value->val_int();
+ if (tmp < 0 || tmp >= typelib.count)
+ return true;
+ else
+ var->save_result.ulonglong_value= tmp;
+ }
+
+ return false;
+ }
+ bool check_update_type(Item_result type)
+ { return type != INT_RESULT && type != STRING_RESULT; }
+};
+
+/**
+ The class for ENUM variables - variables that take one value from a fixed
+ list of values.
+
+ Class specific constructor arguments:
+ char* values[] - 0-terminated list of strings of valid values
+
+ Backing store: uint
+
+ @note
+ Do *not* use "enum FOO" variables as a backing store, there is no
+ guarantee that sizeof(enum FOO) == sizeof(uint), there is no guarantee
+ even that sizeof(enum FOO) == sizeof(enum BAR)
+*/
+class Sys_var_enum: public Sys_var_typelib
+{
+public:
+ Sys_var_enum(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ const char *values[], uint def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : Sys_var_typelib(name_arg, comment, flag_args, off, getopt,
+ SHOW_CHAR, values, def_val, lock,
+ binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ option.var_type= GET_ENUM;
+ global_var(ulong)= def_val;
+ SYSVAR_ASSERT(def_val < typelib.count);
+ SYSVAR_ASSERT(size == sizeof(ulong));
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, ulong)= static_cast<ulong>(var->save_result.ulonglong_value);
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(ulong)= static_cast<ulong>(var->save_result.ulonglong_value);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= global_var(ulong); }
+ void global_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= option.def_value; }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ { return (uchar*)typelib.type_names[session_var(thd, ulong)]; }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ { return (uchar*)typelib.type_names[global_var(ulong)]; }
+};
+
+/**
+ The class for boolean variables - a variant of ENUM variables
+ with the fixed list of values of { OFF , ON }
+
+ Backing store: my_bool
+*/
+class Sys_var_mybool: public Sys_var_typelib
+{
+public:
+ Sys_var_mybool(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ my_bool def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : Sys_var_typelib(name_arg, comment, flag_args, off, getopt,
+ SHOW_MY_BOOL, bool_values, def_val, lock,
+ binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ option.var_type= GET_BOOL;
+ global_var(my_bool)= def_val;
+ SYSVAR_ASSERT(def_val < 2);
+ SYSVAR_ASSERT(getopt.arg_type == OPT_ARG || getopt.id == -1);
+ SYSVAR_ASSERT(size == sizeof(my_bool));
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, my_bool)= var->save_result.ulonglong_value != 0;
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(my_bool)= var->save_result.ulonglong_value != 0;
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= (ulonglong)*(my_bool *)global_value_ptr(thd, 0); }
+ void global_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= option.def_value; }
+};
+
+/**
+ The class for string variables. The string can be in character_set_filesystem
+ or in character_set_system. The string can be allocated with my_malloc()
+ or not. The state of the initial value is specified in the constructor,
+ after that it's managed automatically. The value of NULL is supported.
+
+ Class specific constructor arguments:
+ enum charset_enum is_os_charset_arg
+
+ Backing store: char*
+
+ @note
+ This class supports only GLOBAL variables, because THD on destruction
+ does not destroy individual members of SV, there's no way to free
+ allocated string variables for every thread.
+*/
+class Sys_var_charptr: public sys_var
+{
+public:
+ Sys_var_charptr(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ enum charset_enum is_os_charset_arg,
+ const char *def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR_PTR, (intptr)def_val,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ is_os_charset= is_os_charset_arg == IN_FS_CHARSET;
+ /*
+ use GET_STR_ALLOC - if ALLOCATED it must be *always* allocated,
+ otherwise (GET_STR) you'll never know whether to free it or not.
+ (think of an exit because of an error right after my_getopt)
+ */
+ option.var_type= (flags & ALLOCATED) ? GET_STR_ALLOC : GET_STR;
+ global_var(const char*)= def_val;
+ SYSVAR_ASSERT(scope() == GLOBAL);
+ SYSVAR_ASSERT(size == sizeof(char *));
+ }
+ void cleanup()
+ {
+ if (flags & ALLOCATED)
+ {
+ my_free(global_var(char*));
+ global_var(char *)= NULL;
+ }
+ flags&= ~ALLOCATED;
+ }
+ static bool do_string_check(THD *thd, set_var *var, CHARSET_INFO *charset)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE], buff2[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), charset);
+ String str2(buff2, sizeof(buff2), charset), *res;
+
+ if (!(res=var->value->val_str(&str)))
+ var->save_result.string_value.str= 0;
+ else
+ {
+ uint32 unused;
+ if (String::needs_conversion(res->length(), res->charset(),
+ charset, &unused))
+ {
+ uint errors;
+ str2.copy(res->ptr(), res->length(), res->charset(), charset,
+ &errors);
+ res=&str2;
+
+ }
+ var->save_result.string_value.str= thd->strmake(res->ptr(), res->length());
+ var->save_result.string_value.length= res->length();
+ }
+
+ return false;
+ }
+ bool do_check(THD *thd, set_var *var)
+ { return do_string_check(thd, var, charset(thd)); }
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ char *new_val, *ptr= var->save_result.string_value.str;
+ size_t len=var->save_result.string_value.length;
+ if (ptr)
+ {
+ new_val= (char*)my_memdup(ptr, len+1, MYF(MY_WME));
+ if (!new_val) return true;
+ new_val[len]=0;
+ }
+ else
+ new_val= 0;
+ if (flags & ALLOCATED)
+ my_free(global_var(char*));
+ flags|= ALLOCATED;
+ global_var(char*)= new_val;
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { DBUG_ASSERT(FALSE); }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ char *ptr= (char*)(intptr)option.def_value;
+ var->save_result.string_value.str= ptr;
+ var->save_result.string_value.length= ptr ? strlen(ptr) : 0;
+ }
+ bool check_update_type(Item_result type)
+ { return type != STRING_RESULT; }
+};
+
+
+class Sys_var_proxy_user: public sys_var
+{
+public:
+ Sys_var_proxy_user(const char *name_arg,
+ const char *comment, enum charset_enum is_os_charset_arg)
+ : sys_var(&all_sys_vars, name_arg, comment,
+ sys_var::READONLY+sys_var::ONLY_SESSION, 0, -1,
+ NO_ARG, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG,
+ NULL, NULL, NULL)
+ {
+ is_os_charset= is_os_charset_arg == IN_FS_CHARSET;
+ option.var_type= GET_STR;
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { DBUG_ASSERT(FALSE); }
+ void global_save_default(THD *thd, set_var *var)
+ { DBUG_ASSERT(FALSE); }
+ bool check_update_type(Item_result type)
+ { return true; }
+protected:
+ virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return thd->security_ctx->proxy_user[0] ?
+ (uchar *) &(thd->security_ctx->proxy_user[0]) : NULL;
+ }
+};
+
+class Sys_var_external_user : public Sys_var_proxy_user
+{
+public:
+ Sys_var_external_user(const char *name_arg, const char *comment_arg,
+ enum charset_enum is_os_charset_arg)
+ : Sys_var_proxy_user (name_arg, comment_arg, is_os_charset_arg)
+ {}
+
+protected:
+ virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar*)thd->security_ctx->external_user;
+ }
+};
+
+class Master_info;
+class Sys_var_rpl_filter: public sys_var
+{
+private:
+ int opt_id;
+
+public:
+ Sys_var_rpl_filter(const char *name, int getopt_id, const char *comment)
+ : sys_var(&all_sys_vars, name, comment, sys_var::GLOBAL, 0, -1,
+ NO_ARG, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG,
+ NULL, NULL, NULL), opt_id(getopt_id)
+ {
+ option.var_type= GET_STR | GET_ASK_ADDR;
+ }
+
+ bool do_check(THD *thd, set_var *var)
+ {
+ return Sys_var_charptr::do_string_check(thd, var, charset(thd));
+ }
+ bool check_update_type(Item_result type)
+ { return type != STRING_RESULT; }
+
+ void session_save_default(THD *thd, set_var *var)
+ { DBUG_ASSERT(FALSE); }
+
+ void global_save_default(THD *thd, set_var *var)
+ { DBUG_ASSERT(FALSE); }
+
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+
+ bool global_update(THD *thd, set_var *var);
+
+protected:
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base);
+ bool set_filter_value(const char *value, Master_info *mi);
+};
+
+/**
+ The class for string variables. Useful for strings that aren't necessarily
+ \0-terminated. Otherwise the same as Sys_var_charptr.
+
+ Class specific constructor arguments:
+ enum charset_enum is_os_charset_arg
+
+ Backing store: LEX_STRING
+
+ @note
+ Behaves exactly as Sys_var_charptr, only the backing store is different.
+*/
+class Sys_var_lexstring: public Sys_var_charptr
+{
+public:
+ Sys_var_lexstring(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ enum charset_enum is_os_charset_arg,
+ const char *def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : Sys_var_charptr(name_arg, comment, flag_args, off, sizeof(char*),
+ getopt, is_os_charset_arg, def_val, lock, binlog_status_arg,
+ on_check_func, on_update_func, substitute)
+ {
+ global_var(LEX_STRING).length= strlen(def_val);
+ SYSVAR_ASSERT(size == sizeof(LEX_STRING));
+ *const_cast<SHOW_TYPE*>(&show_val_type)= SHOW_LEX_STRING;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ if (Sys_var_charptr::global_update(thd, var))
+ return true;
+ global_var(LEX_STRING).length= var->save_result.string_value.length;
+ return false;
+ }
+};
+
+
+/*
+ A LEX_STRING stored only in thd->variables
+ Only to be used for small buffers
+*/
+
+class Sys_var_session_lexstring: public sys_var
+{
+ size_t max_length;
+public:
+ Sys_var_session_lexstring(const char *name_arg,
+ const char *comment, int flag_args,
+ ptrdiff_t off, size_t size, CMD_LINE getopt,
+ enum charset_enum is_os_charset_arg,
+ const char *def_val, size_t max_length_arg,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, (intptr)def_val,
+ 0, VARIABLE_NOT_IN_BINLOG, on_check_func, on_update_func,
+ 0),max_length(max_length_arg)
+ {
+ option.var_type= GET_NO_ARG;
+ SYSVAR_ASSERT(scope() == ONLY_SESSION)
+ *const_cast<SHOW_TYPE*>(&show_val_type)= SHOW_LEX_STRING;
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), system_charset_info), *res;
+
+ if (!(res=var->value->val_str(&str)))
+ {
+ var->save_result.string_value.str= 0; /* NULL */
+ var->save_result.string_value.length= 0;
+ }
+ else
+ {
+ if (res->length() > max_length)
+ {
+ my_error(ER_WRONG_STRING_LENGTH, MYF(0),
+ res->ptr(), name.str, (int) max_length);
+ return true;
+ }
+ var->save_result.string_value.str= thd->strmake(res->ptr(),
+ res->length());
+ var->save_result.string_value.length= res->length();
+ }
+ return false;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ LEX_STRING *tmp= &session_var(thd, LEX_STRING);
+ tmp->length= var->save_result.string_value.length;
+ /* Store as \0 terminated string (just to be safe) */
+ strmake(tmp->str, var->save_result.string_value.str, tmp->length);
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ char *ptr= (char*)(intptr)option.def_value;
+ var->save_result.string_value.str= ptr;
+ var->save_result.string_value.length= strlen(ptr);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar*) &session_var(thd, LEX_STRING);
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(FALSE);
+ return NULL;
+ }
+ bool check_update_type(Item_result type)
+ { return type != STRING_RESULT; }
+};
+
+
+#ifndef DBUG_OFF
+/**
+ @@session.dbug and @@global.dbug variables.
+
+ @@dbug variable differs from other variables in one aspect:
+ if its value is not assigned in the session, it "points" to the global
+ value, and so when the global value is changed, the change
+ immediately takes effect in the session.
+
+ This semantics is intentional, to be able to debug one session from
+ another.
+*/
+class Sys_var_dbug: public sys_var
+{
+public:
+ Sys_var_dbug(const char *name_arg,
+ const char *comment, int flag_args,
+ CMD_LINE getopt,
+ const char *def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, 0, getopt.id,
+ getopt.arg_type, SHOW_CHAR, (intptr)def_val,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ { option.var_type= GET_NO_ARG; }
+ bool do_check(THD *thd, set_var *var)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), system_charset_info), *res;
+
+ if (!(res=var->value->val_str(&str)))
+ var->save_result.string_value.str= const_cast<char*>("");
+ else
+ var->save_result.string_value.str= thd->strmake(res->ptr(), res->length());
+ return false;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ const char *val= var->save_result.string_value.str;
+ if (!var->value)
+ DBUG_POP();
+ else
+ DBUG_SET(val);
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ const char *val= var->save_result.string_value.str;
+ DBUG_SET_INITIAL(val);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ char *ptr= (char*)(intptr)option.def_value;
+ var->save_result.string_value.str= ptr;
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ char buf[256];
+ DBUG_EXPLAIN(buf, sizeof(buf));
+ return (uchar*) thd->strdup(buf);
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ char buf[256];
+ DBUG_EXPLAIN_INITIAL(buf, sizeof(buf));
+ return (uchar*) thd->strdup(buf);
+ }
+ bool check_update_type(Item_result type)
+ { return type != STRING_RESULT; }
+};
+#endif
+
+#define KEYCACHE_VAR(X) GLOBAL_VAR(dflt_key_cache_var.X)
+#define keycache_var_ptr(KC, OFF) (((uchar*)(KC))+(OFF))
+#define keycache_var(KC, OFF) (*(ulonglong*)keycache_var_ptr(KC, OFF))
+typedef bool (*keycache_update_function)(THD *, KEY_CACHE *, ptrdiff_t, ulonglong);
+
+/**
+ The class for keycache_* variables. Supports structured names,
+ keycache_name.variable_name.
+
+ Class specific constructor arguments:
+ everything derived from Sys_var_ulonglong
+
+ Backing store: ulonglong
+
+ @note these variables can be only GLOBAL
+*/
+class Sys_var_keycache: public Sys_var_ulonglong
+{
+ keycache_update_function keycache_update;
+public:
+ Sys_var_keycache(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ ulonglong min_val, ulonglong max_val, ulonglong def_val,
+ uint block_size, PolyLock *lock,
+ enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func,
+ keycache_update_function on_update_func,
+ const char *substitute=0)
+ : Sys_var_ulonglong(name_arg, comment, flag_args, off, size,
+ getopt, min_val, max_val, def_val,
+ block_size, lock, binlog_status_arg, on_check_func, 0,
+ substitute),
+ keycache_update(on_update_func)
+ {
+ option.var_type|= GET_ASK_ADDR;
+ option.value= (uchar**)1; // crash me, please
+ // fix an offset from global_system_variables to be an offset in KEY_CACHE
+ offset= global_var_ptr() - (uchar*)dflt_key_cache;
+ SYSVAR_ASSERT(scope() == GLOBAL);
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ ulonglong new_value= var->save_result.ulonglong_value;
+ LEX_STRING *base_name= &var->base;
+ KEY_CACHE *key_cache;
+
+ /* If no basename, assume it's for the key cache named 'default' */
+ if (!base_name->length)
+ base_name= &default_key_cache_base;
+
+ key_cache= get_key_cache(base_name);
+
+ if (!key_cache)
+ { // Key cache didn't exists */
+ if (!new_value) // Tried to delete cache
+ return false; // Ok, nothing to do
+ if (!(key_cache= create_key_cache(base_name->str, base_name->length)))
+ return true;
+ }
+
+ /**
+ Abort if some other thread is changing the key cache
+ @todo This should be changed so that we wait until the previous
+ assignment is done and then do the new assign
+ */
+ if (key_cache->in_init)
+ return true;
+
+ return keycache_update(thd, key_cache, offset, new_value);
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ KEY_CACHE *key_cache= get_key_cache(base);
+ if (!key_cache)
+ key_cache= &zero_key_cache;
+ return keycache_var_ptr(key_cache, offset);
+ }
+};
+
+static bool update_buffer_size(THD *thd, KEY_CACHE *key_cache,
+ ptrdiff_t offset, ulonglong new_value)
+{
+ bool error= false;
+ DBUG_ASSERT(offset == offsetof(KEY_CACHE, param_buff_size));
+
+ if (new_value == 0)
+ {
+ if (key_cache == dflt_key_cache)
+ {
+ my_error(ER_WARN_CANT_DROP_DEFAULT_KEYCACHE, MYF(0));
+ return true;
+ }
+
+ if (key_cache->key_cache_inited) // If initied
+ {
+ /*
+ Move tables using this key cache to the default key cache
+ and clear the old key cache.
+ */
+ key_cache->in_init= 1;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ key_cache->param_buff_size= 0;
+ ha_resize_key_cache(key_cache);
+ ha_change_key_cache(key_cache, dflt_key_cache);
+ /*
+ We don't delete the key cache as some running threads my still be in
+ the key cache code with a pointer to the deleted (empty) key cache
+ */
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ key_cache->in_init= 0;
+ }
+ return error;
+ }
+
+ key_cache->param_buff_size= new_value;
+
+ /* If key cache didn't exist initialize it, else resize it */
+ key_cache->in_init= 1;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ if (!key_cache->key_cache_inited)
+ error= ha_init_key_cache(0, key_cache, 0);
+ else
+ error= ha_resize_key_cache(key_cache);
+
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ key_cache->in_init= 0;
+
+ return error;
+}
+
+static bool update_keycache(THD *thd, KEY_CACHE *key_cache,
+ ptrdiff_t offset, ulonglong new_value,
+ int (*func)(KEY_CACHE *))
+{
+ bool error= false;
+ DBUG_ASSERT(offset != offsetof(KEY_CACHE, param_buff_size));
+
+ keycache_var(key_cache, offset)= new_value;
+
+ key_cache->in_init= 1;
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+ error= func(key_cache);
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ key_cache->in_init= 0;
+
+ return error;
+}
+
+static bool resize_keycache(THD *thd, KEY_CACHE *key_cache,
+ ptrdiff_t offset, ulonglong new_value)
+{
+ return update_keycache(thd, key_cache, offset, new_value,
+ ha_resize_key_cache);
+}
+
+static bool change_keycache_param(THD *thd, KEY_CACHE *key_cache,
+ ptrdiff_t offset, ulonglong new_value)
+{
+ return update_keycache(thd, key_cache, offset, new_value,
+ ha_change_key_cache_param);
+}
+
+static bool repartition_keycache(THD *thd, KEY_CACHE *key_cache,
+ ptrdiff_t offset, ulonglong new_value)
+{
+ return update_keycache(thd, key_cache, offset, new_value,
+ ha_repartition_key_cache);
+}
+
+
+/**
+ The class for floating point variables
+
+ Class specific constructor arguments: min, max
+
+ Backing store: double
+*/
+class Sys_var_double: public sys_var
+{
+public:
+ Sys_var_double(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ double min_val, double max_val, double def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_DOUBLE,
+ (longlong) getopt_double2ulonglong(def_val),
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ option.var_type= GET_DOUBLE;
+ option.min_value= (longlong) getopt_double2ulonglong(min_val);
+ option.max_value= (longlong) getopt_double2ulonglong(max_val);
+ global_var(double)= (double)option.def_value;
+ SYSVAR_ASSERT(min_val < max_val);
+ SYSVAR_ASSERT(min_val <= def_val);
+ SYSVAR_ASSERT(max_val >= def_val);
+ SYSVAR_ASSERT(size == sizeof(double));
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ my_bool fixed;
+ double v= var->value->val_real();
+ var->save_result.double_value= getopt_double_limit_value(v, &option, &fixed);
+
+ return throw_bounds_warning(thd, name.str, fixed, v);
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, double)= var->save_result.double_value;
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(double)= var->save_result.double_value;
+ return false;
+ }
+ bool check_update_type(Item_result type)
+ {
+ return type != INT_RESULT && type != REAL_RESULT && type != DECIMAL_RESULT;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.double_value= global_var(double); }
+ void global_save_default(THD *thd, set_var *var)
+ { var->save_result.double_value= getopt_ulonglong2double(option.def_value); }
+};
+
+/**
+ The class for the @max_user_connections.
+ It's derived from Sys_var_uint, but non-standard session value
+ requires a new class.
+
+ Class specific constructor arguments:
+ everything derived from Sys_var_uint
+
+ Backing store: uint
+*/
+class Sys_var_max_user_conn: public Sys_var_int
+{
+public:
+ Sys_var_max_user_conn(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ int min_val, int max_val, int def_val,
+ uint block_size, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : Sys_var_int(name_arg, comment, SESSION, off, size, getopt,
+ min_val, max_val, def_val, block_size,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ { }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ if (thd->user_connect && thd->user_connect->user_resources.user_conn)
+ return (uchar*) &(thd->user_connect->user_resources.user_conn);
+ return global_value_ptr(thd, base);
+ }
+};
+
+// overflow-safe (1 << X)-1
+#define MAX_SET(X) ((((1UL << ((X)-1))-1) << 1) | 1)
+
+/**
+ The class for flagset variables - a variant of SET that allows in-place
+ editing (turning on/off individual bits). String representations looks like
+ a "flag=val,flag=val,...". Example: @@optimizer_switch
+
+ Class specific constructor arguments:
+ char* values[] - 0-terminated list of strings of valid values
+
+ Backing store: ulonglong
+
+ @note
+ the last value in the values[] array should
+ *always* be the string "default".
+*/
+class Sys_var_flagset: public Sys_var_typelib
+{
+public:
+ Sys_var_flagset(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ const char *values[], ulonglong def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : Sys_var_typelib(name_arg, comment, flag_args, off, getopt,
+ SHOW_CHAR, values, def_val, lock,
+ binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ option.var_type= GET_FLAGSET;
+ global_var(ulonglong)= def_val;
+ SYSVAR_ASSERT(typelib.count > 1);
+ SYSVAR_ASSERT(typelib.count <= 65);
+ SYSVAR_ASSERT(def_val < MAX_SET(typelib.count));
+ SYSVAR_ASSERT(strcmp(values[typelib.count-1], "default") == 0);
+ SYSVAR_ASSERT(size == sizeof(ulonglong));
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), system_charset_info), *res;
+ ulonglong default_value, current_value;
+ if (var->type == OPT_GLOBAL)
+ {
+ default_value= option.def_value;
+ current_value= global_var(ulonglong);
+ }
+ else
+ {
+ default_value= global_var(ulonglong);
+ current_value= session_var(thd, ulonglong);
+ }
+
+ if (var->value->result_type() == STRING_RESULT)
+ {
+ if (!(res=var->value->val_str(&str)))
+ return true;
+ else
+ {
+ char *error;
+ uint error_len;
+
+ var->save_result.ulonglong_value=
+ find_set_from_flags(&typelib,
+ typelib.count,
+ current_value,
+ default_value,
+ res->ptr(), res->length(),
+ &error, &error_len);
+ if (error)
+ {
+ ErrConvString err(error, error_len, res->charset());
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name.str, err.ptr());
+ return true;
+ }
+ }
+ }
+ else
+ {
+ longlong tmp=var->value->val_int();
+ if ((tmp < 0 && ! var->value->unsigned_flag)
+ || (ulonglong)tmp > MAX_SET(typelib.count))
+ return true;
+ else
+ var->save_result.ulonglong_value= tmp;
+ }
+
+ return false;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, ulonglong)= var->save_result.ulonglong_value;
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(ulonglong)= var->save_result.ulonglong_value;
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= global_var(ulonglong); }
+ void global_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= option.def_value; }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar*)flagset_to_string(thd, 0, session_var(thd, ulonglong),
+ typelib.type_names);
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar*)flagset_to_string(thd, 0, global_var(ulonglong),
+ typelib.type_names);
+ }
+};
+
+/**
+ The class for SET variables - variables taking zero or more values
+ from the given list. Example: @@sql_mode
+
+ Class specific constructor arguments:
+ char* values[] - 0-terminated list of strings of valid values
+
+ Backing store: ulonglong
+*/
+class Sys_var_set: public Sys_var_typelib
+{
+public:
+ Sys_var_set(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ const char *values[], ulonglong def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : Sys_var_typelib(name_arg, comment, flag_args, off, getopt,
+ SHOW_CHAR, values, def_val, lock,
+ binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ option.var_type= GET_SET;
+ global_var(ulonglong)= def_val;
+ SYSVAR_ASSERT(typelib.count > 0);
+ SYSVAR_ASSERT(typelib.count <= 64);
+ SYSVAR_ASSERT(def_val <= MAX_SET(typelib.count));
+ SYSVAR_ASSERT(size == sizeof(ulonglong));
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), system_charset_info), *res;
+
+ if (var->value->result_type() == STRING_RESULT)
+ {
+ if (!(res=var->value->val_str(&str)))
+ return true;
+ else
+ {
+ char *error;
+ uint error_len;
+ bool not_used;
+
+ var->save_result.ulonglong_value=
+ find_set(&typelib, res->ptr(), res->length(), NULL,
+ &error, &error_len, &not_used);
+ /*
+ note, we only issue an error if error_len > 0.
+ That is even while empty (zero-length) values are considered
+ errors by find_set(), these errors are ignored here
+ */
+ if (error_len)
+ {
+ ErrConvString err(error, error_len, res->charset());
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name.str, err.ptr());
+ return true;
+ }
+ }
+ }
+ else
+ {
+ longlong tmp=var->value->val_int();
+ if ((tmp < 0 && ! var->value->unsigned_flag)
+ || (ulonglong)tmp > MAX_SET(typelib.count))
+ return true;
+ else
+ var->save_result.ulonglong_value= tmp;
+ }
+
+ return false;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, ulonglong)= var->save_result.ulonglong_value;
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(ulonglong)= var->save_result.ulonglong_value;
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= global_var(ulonglong); }
+ void global_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= option.def_value; }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar*)set_to_string(thd, 0, session_var(thd, ulonglong),
+ typelib.type_names);
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar*)set_to_string(thd, 0, global_var(ulonglong),
+ typelib.type_names);
+ }
+};
+
+/**
+ The class for variables which value is a plugin.
+ Example: @@default_storage_engine
+
+ Class specific constructor arguments:
+ int plugin_type_arg (for example MYSQL_STORAGE_ENGINE_PLUGIN)
+
+ Backing store: plugin_ref
+
+ @note
+ these variables don't support command-line equivalents, any such
+ command-line options should be added manually to my_long_options in mysqld.cc
+*/
+class Sys_var_plugin: public sys_var
+{
+ int plugin_type;
+public:
+ Sys_var_plugin(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ int plugin_type_arg, char **def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, (intptr)def_val,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute),
+ plugin_type(plugin_type_arg)
+ {
+ option.var_type= GET_STR;
+ SYSVAR_ASSERT(size == sizeof(plugin_ref));
+ SYSVAR_ASSERT(getopt.id == -1); // force NO_CMD_LINE
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff,sizeof(buff), system_charset_info), *res;
+ if (!(res=var->value->val_str(&str)))
+ var->save_result.plugin= NULL;
+ else
+ {
+ const LEX_STRING pname= { const_cast<char*>(res->ptr()), res->length() };
+ plugin_ref plugin;
+
+ // special code for storage engines (e.g. to handle historical aliases)
+ if (plugin_type == MYSQL_STORAGE_ENGINE_PLUGIN)
+ plugin= ha_resolve_by_name(thd, &pname);
+ else
+ plugin= my_plugin_lock_by_name(thd, &pname, plugin_type);
+ if (!plugin)
+ {
+ // historically different error code
+ if (plugin_type == MYSQL_STORAGE_ENGINE_PLUGIN)
+ {
+ ErrConvString err(res);
+ my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), err.ptr());
+ }
+ return true;
+ }
+ var->save_result.plugin= plugin;
+ }
+ return false;
+ }
+ void do_update(plugin_ref *valptr, plugin_ref newval)
+ {
+ plugin_ref oldval= *valptr;
+ if (oldval != newval)
+ {
+ *valptr= my_plugin_lock(NULL, newval);
+ plugin_unlock(NULL, oldval);
+ }
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ do_update((plugin_ref*)session_var_ptr(thd),
+ var->save_result.plugin);
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ do_update((plugin_ref*)global_var_ptr(),
+ var->save_result.plugin);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ plugin_ref plugin= global_var(plugin_ref);
+ var->save_result.plugin= my_plugin_lock(thd, plugin);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ LEX_STRING pname;
+ char **default_value= reinterpret_cast<char**>(option.def_value);
+ pname.str= *default_value;
+ pname.length= strlen(pname.str);
+
+ plugin_ref plugin;
+ if (plugin_type == MYSQL_STORAGE_ENGINE_PLUGIN)
+ plugin= ha_resolve_by_name(thd, &pname);
+ else
+ plugin= my_plugin_lock_by_name(thd, &pname, plugin_type);
+ DBUG_ASSERT(plugin);
+
+ var->save_result.plugin= my_plugin_lock(thd, plugin);
+ }
+ bool check_update_type(Item_result type)
+ { return type != STRING_RESULT; }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ plugin_ref plugin= session_var(thd, plugin_ref);
+ return (uchar*)(plugin ? thd->strmake(plugin_name(plugin)->str,
+ plugin_name(plugin)->length) : 0);
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ plugin_ref plugin= global_var(plugin_ref);
+ return (uchar*)(plugin ? thd->strmake(plugin_name(plugin)->str,
+ plugin_name(plugin)->length) : 0);
+ }
+};
+
+#if defined(ENABLED_DEBUG_SYNC)
+/**
+ The class for @@debug_sync session-only variable
+*/
+class Sys_var_debug_sync :public sys_var
+{
+public:
+ Sys_var_debug_sync(const char *name_arg,
+ const char *comment, int flag_args,
+ CMD_LINE getopt,
+ const char *def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, 0, getopt.id,
+ getopt.arg_type, SHOW_CHAR, (intptr)def_val,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ SYSVAR_ASSERT(scope() == ONLY_SESSION);
+ option.var_type= GET_NO_ARG;
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ String str(buff, sizeof(buff), system_charset_info), *res;
+
+ if (!(res=var->value->val_str(&str)))
+ var->save_result.string_value.str= const_cast<char*>("");
+ else
+ var->save_result.string_value.str= thd->strmake(res->ptr(), res->length());
+ return false;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ extern bool debug_sync_update(THD *thd, char *val_str);
+ return debug_sync_update(thd, var->save_result.string_value.str);
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ var->save_result.string_value.str= const_cast<char*>("");
+ var->save_result.string_value.length= 0;
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ extern uchar *debug_sync_value_ptr(THD *thd);
+ return debug_sync_value_ptr(thd);
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(FALSE);
+ return 0;
+ }
+ bool check_update_type(Item_result type)
+ { return type != STRING_RESULT; }
+};
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
+
+/**
+ The class for bit variables - a variant of boolean that stores the value
+ in a bit.
+
+ Class specific constructor arguments:
+ ulonglong bitmask_arg - the mask for the bit to set in the ulonglong
+ backing store
+
+ Backing store: ulonglong
+
+ @note
+ This class supports the "reverse" semantics, when the value of the bit
+ being 0 corresponds to the value of variable being set. To activate it
+ use REVERSE(bitmask) instead of simply bitmask in the constructor.
+
+ @note
+ variables of this class cannot be set from the command line as
+ my_getopt does not support bits.
+*/
+class Sys_var_bit: public Sys_var_typelib
+{
+ ulonglong bitmask;
+ bool reverse_semantics;
+ void set(uchar *ptr, ulonglong value)
+ {
+ if ((value != 0) ^ reverse_semantics)
+ (*(ulonglong *)ptr)|= bitmask;
+ else
+ (*(ulonglong *)ptr)&= ~bitmask;
+ }
+public:
+ Sys_var_bit(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ ulonglong bitmask_arg, my_bool def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : Sys_var_typelib(name_arg, comment, flag_args, off, getopt,
+ SHOW_MY_BOOL, bool_values, def_val, lock,
+ binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ option.var_type= GET_BOOL;
+ reverse_semantics= my_count_bits(bitmask_arg) > 1;
+ bitmask= reverse_semantics ? ~bitmask_arg : bitmask_arg;
+ set(global_var_ptr(), def_val);
+ SYSVAR_ASSERT(def_val < 2);
+ SYSVAR_ASSERT(getopt.id == -1); // force NO_CMD_LINE
+ SYSVAR_ASSERT(size == sizeof(ulonglong));
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ set(session_var_ptr(thd), var->save_result.ulonglong_value);
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ set(global_var_ptr(), var->save_result.ulonglong_value);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= global_var(ulonglong) & bitmask; }
+ void global_save_default(THD *thd, set_var *var)
+ { var->save_result.ulonglong_value= option.def_value; }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ thd->sys_var_tmp.my_bool_value= reverse_semantics ^
+ ((session_var(thd, ulonglong) & bitmask) != 0);
+ return (uchar*) &thd->sys_var_tmp.my_bool_value;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ thd->sys_var_tmp.my_bool_value= reverse_semantics ^
+ ((global_var(ulonglong) & bitmask) != 0);
+ return (uchar*) &thd->sys_var_tmp.my_bool_value;
+ }
+};
+
+/**
+ The class for variables that have a special meaning for a session,
+ such as @@timestamp or @@rnd_seed1, their values typically cannot be read
+ from SV structure, and a special "read" callback is provided.
+
+ Class specific constructor arguments:
+ everything derived from Sys_var_ulonglong
+ session_special_read_function read_func_arg
+
+ Backing store: ulonglong
+
+ @note
+ These variables are session-only, global or command-line equivalents
+ are not supported as they're generally meaningless.
+*/
+class Sys_var_session_special: public Sys_var_ulonglong
+{
+ typedef bool (*session_special_update_function)(THD *thd, set_var *var);
+ typedef ulonglong (*session_special_read_function)(THD *thd);
+
+ session_special_read_function read_func;
+ session_special_update_function update_func;
+public:
+ Sys_var_session_special(const char *name_arg,
+ const char *comment, int flag_args,
+ CMD_LINE getopt,
+ ulonglong min_val, ulonglong max_val, uint block_size,
+ PolyLock *lock, enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func,
+ session_special_update_function update_func_arg,
+ session_special_read_function read_func_arg,
+ const char *substitute=0)
+ : Sys_var_ulonglong(name_arg, comment, flag_args, 0,
+ sizeof(ulonglong), getopt, min_val,
+ max_val, 0, block_size, lock, binlog_status_arg, on_check_func, 0,
+ substitute),
+ read_func(read_func_arg), update_func(update_func_arg)
+ {
+ SYSVAR_ASSERT(scope() == ONLY_SESSION);
+ SYSVAR_ASSERT(getopt.id == -1); // NO_CMD_LINE, because the offset is fake
+ }
+ bool session_update(THD *thd, set_var *var)
+ { return update_func(thd, var); }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->value= 0; }
+ void global_save_default(THD *thd, set_var *var)
+ { DBUG_ASSERT(FALSE); }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ thd->sys_var_tmp.ulonglong_value= read_func(thd);
+ return (uchar*) &thd->sys_var_tmp.ulonglong_value;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(FALSE);
+ return 0;
+ }
+};
+
+
+class Sys_var_session_special_double: public Sys_var_double
+{
+ typedef bool (*session_special_update_function)(THD *thd, set_var *var);
+ typedef double (*session_special_read_function)(THD *thd);
+
+ session_special_read_function read_func;
+ session_special_update_function update_func;
+public:
+ Sys_var_session_special_double(const char *name_arg,
+ const char *comment, int flag_args,
+ CMD_LINE getopt,
+ double min_val, double max_val,
+ PolyLock *lock, enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func,
+ session_special_update_function update_func_arg,
+ session_special_read_function read_func_arg,
+ const char *substitute=0)
+ : Sys_var_double(name_arg, comment, flag_args, 0,
+ sizeof(double), getopt, min_val,
+ max_val, 0, lock, binlog_status_arg, on_check_func, 0,
+ substitute),
+ read_func(read_func_arg), update_func(update_func_arg)
+ {
+ SYSVAR_ASSERT(scope() == ONLY_SESSION);
+ SYSVAR_ASSERT(getopt.id == -1); // NO_CMD_LINE, because the offset is fake
+ }
+ bool session_update(THD *thd, set_var *var)
+ { return update_func(thd, var); }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->value= 0; }
+ void global_save_default(THD *thd, set_var *var)
+ { DBUG_ASSERT(FALSE); }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ thd->sys_var_tmp.double_value= read_func(thd);
+ return (uchar*) &thd->sys_var_tmp.double_value;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(FALSE);
+ return 0;
+ }
+};
+
+
+/**
+ The class for read-only variables that show whether a particular
+ feature is supported by the server. Example: have_compression
+
+ Backing store: enum SHOW_COMP_OPTION
+
+ @note
+ These variables are necessarily read-only, only global, and have no
+ command-line equivalent.
+*/
+class Sys_var_have: public sys_var
+{
+public:
+ Sys_var_have(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, 0,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ SYSVAR_ASSERT(scope() == GLOBAL);
+ SYSVAR_ASSERT(getopt.id == -1);
+ SYSVAR_ASSERT(lock == 0);
+ SYSVAR_ASSERT(binlog_status_arg == VARIABLE_NOT_IN_BINLOG);
+ SYSVAR_ASSERT(is_readonly());
+ SYSVAR_ASSERT(on_update == 0);
+ SYSVAR_ASSERT(size == sizeof(enum SHOW_COMP_OPTION));
+ }
+ bool do_check(THD *thd, set_var *var) {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(FALSE);
+ return true;
+ }
+ void session_save_default(THD *thd, set_var *var) { }
+ void global_save_default(THD *thd, set_var *var) { }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(FALSE);
+ return 0;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar*)show_comp_option_name[global_var(enum SHOW_COMP_OPTION)];
+ }
+ bool check_update_type(Item_result type) { return false; }
+};
+
+/**
+ Generic class for variables for storing entities that are internally
+ represented as structures, have names, and possibly can be referred to by
+ numbers. Examples: character sets, collations, locales,
+
+ Class specific constructor arguments:
+ ptrdiff_t name_offset - offset of the 'name' field in the structure
+
+ Backing store: void*
+
+ @note
+ As every such a structure requires special treatment from my_getopt,
+ these variables don't support command-line equivalents, any such
+ command-line options should be added manually to my_long_options in mysqld.cc
+*/
+class Sys_var_struct: public sys_var
+{
+ ptrdiff_t name_offset; // offset to the 'name' property in the structure
+public:
+ Sys_var_struct(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ ptrdiff_t name_off, void *def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, (intptr)def_val,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute),
+ name_offset(name_off)
+ {
+ option.var_type= GET_STR;
+ /*
+ struct variables are special on the command line - often (e.g. for
+ charsets) the name cannot be immediately resolved, but only after all
+ options (in particular, basedir) are parsed.
+
+ thus all struct command-line options should be added manually
+ to my_long_options in mysqld.cc
+ */
+ SYSVAR_ASSERT(getopt.id == -1);
+ SYSVAR_ASSERT(size == sizeof(void *));
+ }
+ bool do_check(THD *thd, set_var *var)
+ { return false; }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, const void*)= var->save_result.ptr;
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(const void*)= var->save_result.ptr;
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ { var->save_result.ptr= global_var(void*); }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ void **default_value= reinterpret_cast<void**>(option.def_value);
+ var->save_result.ptr= *default_value;
+ }
+ bool check_update_type(Item_result type)
+ { return type != INT_RESULT && type != STRING_RESULT; }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ uchar *ptr= session_var(thd, uchar*);
+ return ptr ? *(uchar**)(ptr+name_offset) : 0;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ uchar *ptr= global_var(uchar*);
+ return ptr ? *(uchar**)(ptr+name_offset) : 0;
+ }
+};
+
+/**
+ The class for variables that store time zones
+
+ Backing store: Time_zone*
+
+ @note
+ Time zones cannot be supported directly by my_getopt, thus
+ these variables don't support command-line equivalents, any such
+ command-line options should be added manually to my_long_options in mysqld.cc
+*/
+class Sys_var_tz: public sys_var
+{
+public:
+ Sys_var_tz(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ Time_zone **def_val, PolyLock *lock=0,
+ enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG,
+ on_check_function on_check_func=0,
+ on_update_function on_update_func=0,
+ const char *substitute=0)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, (intptr)def_val,
+ lock, binlog_status_arg, on_check_func, on_update_func,
+ substitute)
+ {
+ SYSVAR_ASSERT(getopt.id == -1);
+ SYSVAR_ASSERT(size == sizeof(Time_zone *));
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ char buff[MAX_TIME_ZONE_NAME_LENGTH];
+ String str(buff, sizeof(buff), &my_charset_latin1);
+ String *res= var->value->val_str(&str);
+
+ if (!res)
+ return true;
+
+ if (!(var->save_result.time_zone= my_tz_find(thd, res)))
+ {
+ ErrConvString err(res);
+ my_error(ER_UNKNOWN_TIME_ZONE, MYF(0), err.ptr());
+ return true;
+ }
+ return false;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ session_var(thd, Time_zone*)= var->save_result.time_zone;
+ return false;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ global_var(Time_zone*)= var->save_result.time_zone;
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ var->save_result.time_zone= global_var(Time_zone*);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ var->save_result.time_zone=
+ *(Time_zone**)(intptr)option.def_value;
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ /*
+ This is an ugly fix for replication: we don't replicate properly queries
+ invoking system variables' values to update tables; but
+ CONVERT_TZ(,,@@session.time_zone) is so popular that we make it
+ replicable (i.e. we tell the binlog code to store the session
+ timezone). If it's the global value which was used we can't replicate
+ (binlog code stores session value only).
+ */
+ thd->time_zone_used= 1;
+ return (uchar *)(session_var(thd, Time_zone*)->get_name()->ptr());
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return (uchar *)(global_var(Time_zone*)->get_name()->ptr());
+ }
+ bool check_update_type(Item_result type)
+ { return type != STRING_RESULT; }
+};
+
+/**
+ Special implementation for transaction isolation, that
+ distingushes between
+
+ SET GLOBAL TRANSACTION ISOLATION (stored in global_system_variables)
+ SET SESSION TRANSACTION ISOLATION (stored in thd->variables)
+ SET TRANSACTION ISOLATION (stored in thd->tx_isolation)
+
+ where the last statement sets isolation level for the next transaction only
+*/
+class Sys_var_tx_isolation: public Sys_var_enum
+{
+public:
+ Sys_var_tx_isolation(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ const char *values[], uint def_val, PolyLock *lock,
+ enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func)
+ :Sys_var_enum(name_arg, comment, flag_args, off, size, getopt,
+ values, def_val, lock, binlog_status_arg, on_check_func)
+ {}
+ bool session_update(THD *thd, set_var *var)
+ {
+ if (var->type == OPT_SESSION && Sys_var_enum::session_update(thd, var))
+ return TRUE;
+ if (var->type == OPT_DEFAULT || !thd->in_active_multi_stmt_transaction())
+ thd->tx_isolation= (enum_tx_isolation) var->save_result.ulonglong_value;
+ return FALSE;
+ }
+};
+
+
+/**
+ Class representing the tx_read_only system variable for setting
+ default transaction access mode.
+
+ Note that there is a special syntax - SET TRANSACTION READ ONLY
+ (or READ WRITE) that sets the access mode for the next transaction
+ only.
+*/
+
+class Sys_var_tx_read_only: public Sys_var_mybool
+{
+public:
+ Sys_var_tx_read_only(const char *name_arg, const char *comment, int flag_args,
+ ptrdiff_t off, size_t size, CMD_LINE getopt,
+ my_bool def_val, PolyLock *lock,
+ enum binlog_status_enum binlog_status_arg,
+ on_check_function on_check_func)
+ :Sys_var_mybool(name_arg, comment, flag_args, off, size, getopt,
+ def_val, lock, binlog_status_arg, on_check_func)
+ {}
+ virtual bool session_update(THD *thd, set_var *var);
+};
+
+/*
+ Class for replicate_events_marked_for_skip.
+ We need a custom update function that ensures the slave is stopped when
+ the update is happening.
+*/
+class Sys_var_replicate_events_marked_for_skip: public Sys_var_enum
+{
+public:
+ Sys_var_replicate_events_marked_for_skip(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ const char *values[], uint def_val, PolyLock *lock,
+ enum binlog_status_enum binlog_status_arg)
+ :Sys_var_enum(name_arg, comment, flag_args, off, size, getopt,
+ values, def_val, lock, binlog_status_arg)
+ {}
+ bool global_update(THD *thd, set_var *var);
+};
+
+/*
+ Class for handing multi-source replication variables
+ Variable values are store in Master_info, but to make it possible to
+ access variable without locks we also store it thd->variables.
+ These can be used as GLOBAL or SESSION, but both points to the same
+ variable. This is to make things compatible with MySQL 5.5 where variables
+ like sql_slave_skip_counter are GLOBAL.
+*/
+
+#define MASTER_INFO_VAR(X) my_offsetof(Master_info, X), sizeof(((Master_info *)0x10)->X)
+class Sys_var_multi_source_ulonglong;
+class Master_info;
+
+typedef bool (*on_multi_source_update_function)(sys_var *self, THD *thd,
+ Master_info *mi);
+bool update_multi_source_variable(sys_var *self,
+ THD *thd, enum_var_type type);
+
+
+class Sys_var_multi_source_ulonglong :public Sys_var_ulonglong
+{
+ ptrdiff_t master_info_offset;
+ on_multi_source_update_function update_multi_source_variable_func;
+public:
+ Sys_var_multi_source_ulonglong(const char *name_arg,
+ const char *comment, int flag_args,
+ ptrdiff_t off, size_t size,
+ CMD_LINE getopt,
+ ptrdiff_t master_info_offset_arg,
+ size_t master_info_arg_size,
+ ulonglong min_val, ulonglong max_val,
+ ulonglong def_val, uint block_size,
+ on_multi_source_update_function on_update_func)
+ :Sys_var_ulonglong(name_arg, comment, flag_args, off, size,
+ getopt, min_val, max_val, def_val, block_size,
+ 0, VARIABLE_NOT_IN_BINLOG, 0, update_multi_source_variable),
+ master_info_offset(master_info_offset_arg),
+ update_multi_source_variable_func(on_update_func)
+ {
+ SYSVAR_ASSERT(master_info_arg_size == size);
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ return session_update(thd, var);
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ /* Use value given in variable declaration */
+ global_save_default(thd, var);
+ }
+ uchar *session_value_ptr(THD *thd,LEX_STRING *base)
+ {
+ ulonglong *tmp, res;
+ tmp= (ulonglong*) (((uchar*)&(thd->variables)) + offset);
+ res= get_master_info_ulonglong_value(thd, master_info_offset);
+ *tmp= res;
+ return (uchar*) tmp;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ return session_value_ptr(thd, base);
+ }
+ ulonglong get_master_info_ulonglong_value(THD *thd, ptrdiff_t offset);
+ bool update_variable(THD *thd, Master_info *mi)
+ {
+ return update_multi_source_variable_func(this, thd, mi);
+ }
+};
+
+
+/**
+ Class for @@global.gtid_current_pos.
+*/
+class Sys_var_gtid_current_pos: public sys_var
+{
+public:
+ Sys_var_gtid_current_pos(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG,
+ NULL, NULL, NULL)
+ {
+ option.var_type= GET_STR;
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool check_update_type(Item_result type) {
+ DBUG_ASSERT(false);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(false);
+ return NULL;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base);
+};
+
+
+/**
+ Class for @@global.gtid_binlog_pos.
+*/
+class Sys_var_gtid_binlog_pos: public sys_var
+{
+public:
+ Sys_var_gtid_binlog_pos(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG,
+ NULL, NULL, NULL)
+ {
+ option.var_type= GET_STR;
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool check_update_type(Item_result type) {
+ DBUG_ASSERT(false);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(false);
+ return NULL;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base);
+};
+
+
+/**
+ Class for @@global.gtid_slave_pos.
+*/
+class Sys_var_gtid_slave_pos: public sys_var
+{
+public:
+ Sys_var_gtid_slave_pos(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG,
+ NULL, NULL, NULL)
+ {
+ option.var_type= GET_STR;
+ }
+ bool do_check(THD *thd, set_var *var);
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var);
+ bool check_update_type(Item_result type) { return type != STRING_RESULT; }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ /* Record the attempt to use default so we can error. */
+ var->value= 0;
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(false);
+ return NULL;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base);
+};
+
+
+/**
+ Class for @@global.gtid_binlog_state.
+*/
+class Sys_var_gtid_binlog_state: public sys_var
+{
+public:
+ Sys_var_gtid_binlog_state(const char *name_arg,
+ const char *comment, int flag_args, ptrdiff_t off, size_t size,
+ CMD_LINE getopt)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id,
+ getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG,
+ NULL, NULL, NULL)
+ {
+ option.var_type= GET_STR;
+ }
+ bool do_check(THD *thd, set_var *var);
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var);
+ bool check_update_type(Item_result type) { return type != STRING_RESULT; }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ /* Record the attempt to use default so we can error. */
+ var->value= 0;
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(false);
+ return NULL;
+ }
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base);
+};
+
+
+/**
+ Class for @@session.last_gtid.
+*/
+class Sys_var_last_gtid: public sys_var
+{
+public:
+ Sys_var_last_gtid(const char *name_arg,
+ const char *comment, int flag_args, CMD_LINE getopt)
+ : sys_var(&all_sys_vars, name_arg, comment, flag_args, 0, getopt.id,
+ getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG,
+ NULL, NULL, NULL)
+ {
+ option.var_type= GET_STR;
+ }
+ bool do_check(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool session_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool global_update(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ return true;
+ }
+ bool check_update_type(Item_result type) {
+ DBUG_ASSERT(false);
+ return false;
+ }
+ void session_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ void global_save_default(THD *thd, set_var *var)
+ {
+ DBUG_ASSERT(false);
+ }
+ uchar *session_value_ptr(THD *thd, LEX_STRING *base);
+ uchar *global_value_ptr(THD *thd, LEX_STRING *base)
+ {
+ DBUG_ASSERT(false);
+ return NULL;
+ }
+};
diff --git a/sql/sys_vars_shared.h b/sql/sys_vars_shared.h
new file mode 100644
index 00000000000..ff050f63064
--- /dev/null
+++ b/sql/sys_vars_shared.h
@@ -0,0 +1,86 @@
+#ifndef SYS_VARS_SHARED_INCLUDED
+#define SYS_VARS_SHARED_INCLUDED
+
+/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+/**
+ @file
+ "protected" interface to sys_var - server configuration variables.
+
+ This header is included by files implementing support and utility
+ functions of sys_var's (set_var.cc) and files implementing
+ classes in the sys_var hierarchy (sql_plugin.cc)
+*/
+
+#include <sql_priv.h>
+#include "set_var.h"
+
+extern bool throw_bounds_warning(THD *thd, const char *name,
+ bool fixed, bool is_unsigned, longlong v);
+extern bool throw_bounds_warning(THD *thd, const char *name, bool fixed,
+ double v);
+extern sys_var *intern_find_sys_var(const char *str, uint length);
+
+extern sys_var_chain all_sys_vars;
+
+/** wrapper to hide a mutex and an rwlock under a common interface */
+class PolyLock
+{
+public:
+ virtual void rdlock()= 0;
+ virtual void wrlock()= 0;
+ virtual void unlock()= 0;
+ virtual ~PolyLock() {}
+};
+
+class PolyLock_mutex: public PolyLock
+{
+ mysql_mutex_t *mutex;
+public:
+ PolyLock_mutex(mysql_mutex_t *arg): mutex(arg) {}
+ void rdlock() { mysql_mutex_lock(mutex); }
+ void wrlock() { mysql_mutex_lock(mutex); }
+ void unlock() { mysql_mutex_unlock(mutex); }
+};
+
+class PolyLock_rwlock: public PolyLock
+{
+ mysql_rwlock_t *rwlock;
+public:
+ PolyLock_rwlock(mysql_rwlock_t *arg): rwlock(arg) {}
+ void rdlock() { mysql_rwlock_rdlock(rwlock); }
+ void wrlock() { mysql_rwlock_wrlock(rwlock); }
+ void unlock() { mysql_rwlock_unlock(rwlock); }
+};
+
+class AutoWLock
+{
+ PolyLock *lock;
+public:
+ AutoWLock(PolyLock *l) : lock(l) { if (lock) lock->wrlock(); }
+ ~AutoWLock() { if (lock) lock->unlock(); }
+};
+
+class AutoRLock
+{
+ PolyLock *lock;
+public:
+ AutoRLock(PolyLock *l) : lock(l) { if (lock) lock->rdlock(); }
+ ~AutoRLock() { if (lock) lock->unlock(); }
+};
+
+
+#endif /* SYS_VARS_SHARED_INCLUDED */
diff --git a/sql/table.cc b/sql/table.cc
new file mode 100644
index 00000000000..053269ab435
--- /dev/null
+++ b/sql/table.cc
@@ -0,0 +1,7210 @@
+/* Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+ Copyright (c) 2008, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+/* Some general useful functions */
+
+#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h" // REQUIRED: for other includes
+#include "table.h"
+#include "key.h" // find_ref_key
+#include "sql_table.h" // build_table_filename,
+ // primary_key_name
+#include "sql_trigger.h"
+#include "sql_parse.h" // free_items
+#include "strfunc.h" // unhex_type2
+#include "sql_partition.h" // mysql_unpack_partition,
+ // fix_partition_func, partition_info
+#include "sql_acl.h" // *_ACL, acl_getroot_no_password
+#include "sql_base.h"
+#include "create_options.h"
+#include <m_ctype.h>
+#include "my_md5.h"
+#include "my_bit.h"
+#include "sql_select.h"
+#include "sql_derived.h"
+#include "sql_statistics.h"
+#include "discover.h"
+#include "mdl.h" // MDL_wait_for_graph_visitor
+
+/* INFORMATION_SCHEMA name */
+LEX_STRING INFORMATION_SCHEMA_NAME= {C_STRING_WITH_LEN("information_schema")};
+
+/* PERFORMANCE_SCHEMA name */
+LEX_STRING PERFORMANCE_SCHEMA_DB_NAME= {C_STRING_WITH_LEN("performance_schema")};
+
+/* MYSQL_SCHEMA name */
+LEX_STRING MYSQL_SCHEMA_NAME= {C_STRING_WITH_LEN("mysql")};
+
+/* GENERAL_LOG name */
+LEX_STRING GENERAL_LOG_NAME= {C_STRING_WITH_LEN("general_log")};
+
+/* SLOW_LOG name */
+LEX_STRING SLOW_LOG_NAME= {C_STRING_WITH_LEN("slow_log")};
+
+/*
+ Keyword added as a prefix when parsing the defining expression for a
+ virtual column read from the column definition saved in the frm file
+*/
+LEX_STRING parse_vcol_keyword= { C_STRING_WITH_LEN("PARSE_VCOL_EXPR ") };
+
+ /* Functions defined in this file */
+
+static void fix_type_pointers(const char ***array, TYPELIB *point_to_type,
+ uint types, char **names);
+static uint find_field(Field **fields, uchar *record, uint start, uint length);
+
+inline bool is_system_table_name(const char *name, uint length);
+
+/**************************************************************************
+ Object_creation_ctx implementation.
+**************************************************************************/
+
+Object_creation_ctx *Object_creation_ctx::set_n_backup(THD *thd)
+{
+ Object_creation_ctx *backup_ctx;
+ DBUG_ENTER("Object_creation_ctx::set_n_backup");
+
+ backup_ctx= create_backup_ctx(thd);
+ change_env(thd);
+
+ DBUG_RETURN(backup_ctx);
+}
+
+void Object_creation_ctx::restore_env(THD *thd, Object_creation_ctx *backup_ctx)
+{
+ if (!backup_ctx)
+ return;
+
+ backup_ctx->change_env(thd);
+
+ delete backup_ctx;
+}
+
+/**************************************************************************
+ Default_object_creation_ctx implementation.
+**************************************************************************/
+
+Default_object_creation_ctx::Default_object_creation_ctx(THD *thd)
+ : m_client_cs(thd->variables.character_set_client),
+ m_connection_cl(thd->variables.collation_connection)
+{ }
+
+Default_object_creation_ctx::Default_object_creation_ctx(
+ CHARSET_INFO *client_cs, CHARSET_INFO *connection_cl)
+ : m_client_cs(client_cs),
+ m_connection_cl(connection_cl)
+{ }
+
+Object_creation_ctx *
+Default_object_creation_ctx::create_backup_ctx(THD *thd) const
+{
+ return new Default_object_creation_ctx(thd);
+}
+
+void Default_object_creation_ctx::change_env(THD *thd) const
+{
+ thd->variables.character_set_client= m_client_cs;
+ thd->variables.collation_connection= m_connection_cl;
+
+ thd->update_charset();
+}
+
+/**************************************************************************
+ View_creation_ctx implementation.
+**************************************************************************/
+
+View_creation_ctx *View_creation_ctx::create(THD *thd)
+{
+ View_creation_ctx *ctx= new (thd->mem_root) View_creation_ctx(thd);
+
+ return ctx;
+}
+
+/*************************************************************************/
+
+View_creation_ctx * View_creation_ctx::create(THD *thd,
+ TABLE_LIST *view)
+{
+ View_creation_ctx *ctx= new (thd->mem_root) View_creation_ctx(thd);
+
+ /* Throw a warning if there is NULL cs name. */
+
+ if (!view->view_client_cs_name.str ||
+ !view->view_connection_cl_name.str)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_VIEW_NO_CREATION_CTX,
+ ER(ER_VIEW_NO_CREATION_CTX),
+ (const char *) view->db,
+ (const char *) view->table_name);
+
+ ctx->m_client_cs= system_charset_info;
+ ctx->m_connection_cl= system_charset_info;
+
+ return ctx;
+ }
+
+ /* Resolve cs names. Throw a warning if there is unknown cs name. */
+
+ bool invalid_creation_ctx;
+
+ invalid_creation_ctx= resolve_charset(view->view_client_cs_name.str,
+ system_charset_info,
+ &ctx->m_client_cs);
+
+ invalid_creation_ctx= resolve_collation(view->view_connection_cl_name.str,
+ system_charset_info,
+ &ctx->m_connection_cl) ||
+ invalid_creation_ctx;
+
+ if (invalid_creation_ctx)
+ {
+ sql_print_warning("View '%s'.'%s': there is unknown charset/collation "
+ "names (client: '%s'; connection: '%s').",
+ (const char *) view->db,
+ (const char *) view->table_name,
+ (const char *) view->view_client_cs_name.str,
+ (const char *) view->view_connection_cl_name.str);
+
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_VIEW_INVALID_CREATION_CTX,
+ ER(ER_VIEW_INVALID_CREATION_CTX),
+ (const char *) view->db,
+ (const char *) view->table_name);
+ }
+
+ return ctx;
+}
+
+/*************************************************************************/
+
+/* Get column name from column hash */
+
+static uchar *get_field_name(Field **buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= (uint) strlen((*buff)->field_name);
+ return (uchar*) (*buff)->field_name;
+}
+
+
+/*
+ Returns pointer to '.frm' extension of the file name.
+
+ SYNOPSIS
+ fn_rext()
+ name file name
+
+ DESCRIPTION
+ Checks file name part starting with the rightmost '.' character,
+ and returns it if it is equal to '.frm'.
+
+ TODO
+ It is a good idea to get rid of this function modifying the code
+ to garantee that the functions presently calling fn_rext() always
+ get arguments in the same format: either with '.frm' or without '.frm'.
+
+ RETURN VALUES
+ Pointer to the '.frm' extension. If there is no extension,
+ or extension is not '.frm', pointer at the end of file name.
+*/
+
+char *fn_rext(char *name)
+{
+ char *res= strrchr(name, '.');
+ if (res && !strcmp(res, reg_ext))
+ return res;
+ return name + strlen(name);
+}
+
+TABLE_CATEGORY get_table_category(const LEX_STRING *db, const LEX_STRING *name)
+{
+ DBUG_ASSERT(db != NULL);
+ DBUG_ASSERT(name != NULL);
+
+ if (is_infoschema_db(db->str, db->length))
+ return TABLE_CATEGORY_INFORMATION;
+
+ if ((db->length == PERFORMANCE_SCHEMA_DB_NAME.length) &&
+ (my_strcasecmp(system_charset_info,
+ PERFORMANCE_SCHEMA_DB_NAME.str,
+ db->str) == 0))
+ return TABLE_CATEGORY_PERFORMANCE;
+
+ if ((db->length == MYSQL_SCHEMA_NAME.length) &&
+ (my_strcasecmp(system_charset_info,
+ MYSQL_SCHEMA_NAME.str,
+ db->str) == 0))
+ {
+ if (is_system_table_name(name->str, name->length))
+ return TABLE_CATEGORY_SYSTEM;
+
+ if ((name->length == GENERAL_LOG_NAME.length) &&
+ (my_strcasecmp(system_charset_info,
+ GENERAL_LOG_NAME.str,
+ name->str) == 0))
+ return TABLE_CATEGORY_LOG;
+
+ if ((name->length == SLOW_LOG_NAME.length) &&
+ (my_strcasecmp(system_charset_info,
+ SLOW_LOG_NAME.str,
+ name->str) == 0))
+ return TABLE_CATEGORY_LOG;
+ }
+
+ return TABLE_CATEGORY_USER;
+}
+
+
+/*
+ Allocate and setup a TABLE_SHARE structure
+
+ SYNOPSIS
+ alloc_table_share()
+ TABLE_LIST Take database and table name from there
+ key Table cache key (db \0 table_name \0...)
+ key_length Length of key
+
+ RETURN
+ 0 Error (out of memory)
+ # Share
+*/
+
+TABLE_SHARE *alloc_table_share(const char *db, const char *table_name,
+ const char *key, uint key_length)
+{
+ MEM_ROOT mem_root;
+ TABLE_SHARE *share;
+ char *key_buff, *path_buff;
+ char path[FN_REFLEN];
+ uint path_length;
+ DBUG_ENTER("alloc_table_share");
+ DBUG_PRINT("enter", ("table: '%s'.'%s'", db, table_name));
+
+ path_length= build_table_filename(path, sizeof(path) - 1,
+ db, table_name, "", 0);
+ init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0));
+ if (multi_alloc_root(&mem_root,
+ &share, sizeof(*share),
+ &key_buff, key_length,
+ &path_buff, path_length + 1,
+ NULL))
+ {
+ bzero((char*) share, sizeof(*share));
+
+ share->set_table_cache_key(key_buff, key, key_length);
+
+ share->path.str= path_buff;
+ share->path.length= path_length;
+ strmov(share->path.str, path);
+ share->normalized_path.str= share->path.str;
+ share->normalized_path.length= path_length;
+ share->table_category= get_table_category(& share->db, & share->table_name);
+ share->open_errno= ENOENT;
+ share->cached_row_logging_check= -1;
+
+ init_sql_alloc(&share->stats_cb.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0));
+
+ memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
+ mysql_mutex_init(key_TABLE_SHARE_LOCK_share,
+ &share->LOCK_share, MY_MUTEX_INIT_SLOW);
+ mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data,
+ &share->LOCK_ha_data, MY_MUTEX_INIT_FAST);
+ tdc_init_share(share);
+ }
+ DBUG_RETURN(share);
+}
+
+
+/*
+ Initialize share for temporary tables
+
+ SYNOPSIS
+ init_tmp_table_share()
+ thd thread handle
+ share Share to fill
+ key Table_cache_key, as generated from tdc_create_key.
+ must start with db name.
+ key_length Length of key
+ table_name Table name
+ path Path to file (possible in lower case) without .frm
+
+ NOTES
+ This is different from alloc_table_share() because temporary tables
+ don't have to be shared between threads or put into the table def
+ cache, so we can do some things notable simpler and faster
+
+ If table is not put in thd->temporary_tables (happens only when
+ one uses OPEN TEMPORARY) then one can specify 'db' as key and
+ use key_length= 0 as neither table_cache_key or key_length will be used).
+*/
+
+void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
+ uint key_length, const char *table_name,
+ const char *path)
+{
+ DBUG_ENTER("init_tmp_table_share");
+ DBUG_PRINT("enter", ("table: '%s'.'%s'", key, table_name));
+
+ bzero((char*) share, sizeof(*share));
+ /*
+ This can't be MY_THREAD_SPECIFIC for slaves as they are freed
+ during cleanup() from Relay_log_info::close_temporary_tables()
+ */
+ init_sql_alloc(&share->mem_root, TABLE_ALLOC_BLOCK_SIZE, 0,
+ MYF(thd->slave_thread ? 0 : MY_THREAD_SPECIFIC));
+ share->table_category= TABLE_CATEGORY_TEMPORARY;
+ share->tmp_table= INTERNAL_TMP_TABLE;
+ share->db.str= (char*) key;
+ share->db.length= strlen(key);
+ share->table_cache_key.str= (char*) key;
+ share->table_cache_key.length= key_length;
+ share->table_name.str= (char*) table_name;
+ share->table_name.length= strlen(table_name);
+ share->path.str= (char*) path;
+ share->normalized_path.str= (char*) path;
+ share->path.length= share->normalized_path.length= strlen(path);
+ share->frm_version= FRM_VER_TRUE_VARCHAR;
+
+ share->cached_row_logging_check= -1;
+
+ /*
+ table_map_id is also used for MERGE tables to suppress repeated
+ compatibility checks.
+ */
+ share->table_map_id= (ulong) thd->query_id;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release resources (plugins) used by the share and free its memory.
+ TABLE_SHARE is self-contained -- it's stored in its own MEM_ROOT.
+ Free this MEM_ROOT.
+*/
+
+void TABLE_SHARE::destroy()
+{
+ uint idx;
+ KEY *info_it;
+ DBUG_ENTER("TABLE_SHARE::destroy");
+ DBUG_PRINT("info", ("db: %s table: %s", db.str, table_name.str));
+
+ if (ha_share)
+ {
+ delete ha_share;
+ ha_share= NULL; // Safety
+ }
+
+ free_root(&stats_cb.mem_root, MYF(0));
+ stats_cb.stats_can_be_read= FALSE;
+ stats_cb.stats_is_read= FALSE;
+ stats_cb.histograms_can_be_read= FALSE;
+ stats_cb.histograms_are_read= FALSE;
+
+ /* The mutexes are initialized only for shares that are part of the TDC */
+ if (tmp_table == NO_TMP_TABLE)
+ {
+ mysql_mutex_destroy(&LOCK_share);
+ mysql_mutex_destroy(&LOCK_ha_data);
+ tdc_deinit_share(this);
+ }
+ my_hash_free(&name_hash);
+
+ plugin_unlock(NULL, db_plugin);
+ db_plugin= NULL;
+
+ /* Release fulltext parsers */
+ info_it= key_info;
+ for (idx= keys; idx; idx--, info_it++)
+ {
+ if (info_it->flags & HA_USES_PARSER)
+ {
+ plugin_unlock(NULL, info_it->parser);
+ info_it->flags= 0;
+ }
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ plugin_unlock(NULL, default_part_plugin);
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
+
+ PSI_CALL_release_table_share(m_psi);
+
+ /*
+ Make a copy since the share is allocated in its own root,
+ and free_root() updates its argument after freeing the memory.
+ */
+ MEM_ROOT own_root= mem_root;
+ free_root(&own_root, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Free table share and memory used by it
+
+ SYNOPSIS
+ free_table_share()
+ share Table share
+*/
+
+void free_table_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("free_table_share");
+ DBUG_PRINT("enter", ("table: %s.%s", share->db.str, share->table_name.str));
+ share->destroy();
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Return TRUE if a table name matches one of the system table names.
+ Currently these are:
+
+ help_category, help_keyword, help_relation, help_topic,
+ proc, event
+ time_zone, time_zone_leap_second, time_zone_name, time_zone_transition,
+ time_zone_transition_type
+
+ This function trades accuracy for speed, so may return false
+ positives. Presumably mysql.* database is for internal purposes only
+ and should not contain user tables.
+*/
+
+inline bool is_system_table_name(const char *name, uint length)
+{
+ CHARSET_INFO *ci= system_charset_info;
+
+ return (
+ /* mysql.proc table */
+ (length == 4 &&
+ my_tolower(ci, name[0]) == 'p' &&
+ my_tolower(ci, name[1]) == 'r' &&
+ my_tolower(ci, name[2]) == 'o' &&
+ my_tolower(ci, name[3]) == 'c') ||
+
+ (length > 4 &&
+ (
+ /* one of mysql.help* tables */
+ (my_tolower(ci, name[0]) == 'h' &&
+ my_tolower(ci, name[1]) == 'e' &&
+ my_tolower(ci, name[2]) == 'l' &&
+ my_tolower(ci, name[3]) == 'p') ||
+
+ /* one of mysql.time_zone* tables */
+ (my_tolower(ci, name[0]) == 't' &&
+ my_tolower(ci, name[1]) == 'i' &&
+ my_tolower(ci, name[2]) == 'm' &&
+ my_tolower(ci, name[3]) == 'e') ||
+
+ /* one of mysql.*_stat tables, but not mysql.innodb* tables*/
+ ((my_tolower(ci, name[length-5]) == 's' &&
+ my_tolower(ci, name[length-4]) == 't' &&
+ my_tolower(ci, name[length-3]) == 'a' &&
+ my_tolower(ci, name[length-2]) == 't' &&
+ my_tolower(ci, name[length-1]) == 's') &&
+ !(my_tolower(ci, name[0]) == 'i' &&
+ my_tolower(ci, name[1]) == 'n' &&
+ my_tolower(ci, name[2]) == 'n' &&
+ my_tolower(ci, name[3]) == 'o')) ||
+
+ /* mysql.event table */
+ (my_tolower(ci, name[0]) == 'e' &&
+ my_tolower(ci, name[1]) == 'v' &&
+ my_tolower(ci, name[2]) == 'e' &&
+ my_tolower(ci, name[3]) == 'n' &&
+ my_tolower(ci, name[4]) == 't')
+ )
+ )
+ );
+}
+
+
+/*
+ Read table definition from a binary / text based .frm file
+
+ SYNOPSIS
+ open_table_def()
+ thd Thread handler
+ share Fill this with table definition
+ db_flags Bit mask of the following flags: OPEN_VIEW
+
+ NOTES
+ This function is called when the table definition is not cached in
+ table definition cache
+ The data is returned in 'share', which is alloced by
+ alloc_table_share().. The code assumes that share is initialized.
+*/
+
+enum open_frm_error open_table_def(THD *thd, TABLE_SHARE *share, uint flags)
+{
+ bool error_given= false;
+ File file;
+ uchar *buf;
+ uchar head[FRM_HEADER_SIZE];
+ char path[FN_REFLEN];
+ size_t frmlen, read_length;
+ DBUG_ENTER("open_table_def");
+ DBUG_PRINT("enter", ("table: '%s'.'%s' path: '%s'", share->db.str,
+ share->table_name.str, share->normalized_path.str));
+
+ share->error= OPEN_FRM_OPEN_ERROR;
+
+ strxmov(path, share->normalized_path.str, reg_ext, NullS);
+ if (flags & GTS_FORCE_DISCOVERY)
+ {
+ DBUG_ASSERT(flags & GTS_TABLE);
+ DBUG_ASSERT(flags & GTS_USE_DISCOVERY);
+ mysql_file_delete_with_symlink(key_file_frm, path, MYF(0));
+ file= -1;
+ }
+ else
+ file= mysql_file_open(key_file_frm, path, O_RDONLY | O_SHARE, MYF(0));
+
+ if (file < 0)
+ {
+ if ((flags & GTS_TABLE) && (flags & GTS_USE_DISCOVERY))
+ {
+ ha_discover_table(thd, share);
+ error_given= true;
+ }
+ goto err_not_open;
+ }
+
+ if (mysql_file_read(file, head, sizeof(head), MYF(MY_NABP)))
+ {
+ share->error = my_errno == HA_ERR_FILE_TOO_SHORT
+ ? OPEN_FRM_CORRUPTED : OPEN_FRM_READ_ERROR;
+ goto err;
+ }
+
+ if (memcmp(head, STRING_WITH_LEN("TYPE=VIEW\n")) == 0)
+ {
+ share->is_view= 1;
+ share->error= flags & GTS_VIEW ? OPEN_FRM_OK : OPEN_FRM_NOT_A_TABLE;
+ goto err;
+ }
+ if (!is_binary_frm_header(head))
+ {
+ /* No handling of text based files yet */
+ share->error = OPEN_FRM_CORRUPTED;
+ goto err;
+ }
+ if (!(flags & GTS_TABLE))
+ {
+ share->error = OPEN_FRM_NOT_A_VIEW;
+ goto err;
+ }
+
+ frmlen= uint4korr(head+10);
+ set_if_smaller(frmlen, FRM_MAX_SIZE); // safety
+
+ if (!(buf= (uchar*)my_malloc(frmlen, MYF(MY_THREAD_SPECIFIC|MY_WME))))
+ goto err;
+
+ memcpy(buf, head, sizeof(head));
+
+ read_length= mysql_file_read(file, buf + sizeof(head),
+ frmlen - sizeof(head), MYF(MY_WME));
+ if (read_length == 0 || read_length == (size_t)-1)
+ {
+ share->error = OPEN_FRM_READ_ERROR;
+ my_free(buf);
+ goto err;
+ }
+ mysql_file_close(file, MYF(MY_WME));
+
+ frmlen= read_length + sizeof(head);
+
+ share->init_from_binary_frm_image(thd, false, buf, frmlen);
+ error_given= true; // init_from_binary_frm_image has already called my_error()
+ my_free(buf);
+
+ goto err_not_open;
+
+err:
+ mysql_file_close(file, MYF(MY_WME));
+
+err_not_open:
+ if (share->error && !error_given)
+ {
+ share->open_errno= my_errno;
+ open_table_error(share, share->error, share->open_errno);
+ }
+
+ DBUG_RETURN(share->error);
+}
+
+static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end,
+ uint keys, KEY *keyinfo,
+ uint new_frm_ver, uint &ext_key_parts,
+ TABLE_SHARE *share, uint len,
+ KEY *first_keyinfo, char* &keynames)
+{
+ uint i, j, n_length;
+ KEY_PART_INFO *key_part= NULL;
+ ulong *rec_per_key= NULL;
+ KEY_PART_INFO *first_key_part= NULL;
+ uint first_key_parts= 0;
+
+ if (!keys)
+ {
+ if (!(keyinfo = (KEY*) alloc_root(&share->mem_root, len)))
+ return 1;
+ bzero((char*) keyinfo, len);
+ key_part= reinterpret_cast<KEY_PART_INFO*> (keyinfo);
+ }
+
+ /*
+ If share->use_ext_keys is set to TRUE we assume that any key
+ can be extended by the components of the primary key whose
+ definition is read first from the frm file.
+ For each key only those fields of the assumed primary key are
+ added that are not included in the proper key definition.
+ If after all it turns out that there is no primary key the
+ added components are removed from each key.
+
+ When in the future we support others schemes of extending of
+ secondary keys with components of the primary key we'll have
+ to change the type of this flag for an enumeration type.
+ */
+
+ for (i=0 ; i < keys ; i++, keyinfo++)
+ {
+ if (new_frm_ver >= 3)
+ {
+ if (strpos + 8 >= frm_image_end)
+ return 1;
+ keyinfo->flags= (uint) uint2korr(strpos) ^ HA_NOSAME;
+ keyinfo->key_length= (uint) uint2korr(strpos+2);
+ keyinfo->user_defined_key_parts= (uint) strpos[4];
+ keyinfo->algorithm= (enum ha_key_alg) strpos[5];
+ keyinfo->block_size= uint2korr(strpos+6);
+ strpos+=8;
+ }
+ else
+ {
+ if (strpos + 4 >= frm_image_end)
+ return 1;
+ keyinfo->flags= ((uint) strpos[0]) ^ HA_NOSAME;
+ keyinfo->key_length= (uint) uint2korr(strpos+1);
+ keyinfo->user_defined_key_parts= (uint) strpos[3];
+ keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+ strpos+=4;
+ }
+
+ if (i == 0)
+ {
+ ext_key_parts+= (share->use_ext_keys ? first_keyinfo->user_defined_key_parts*(keys-1) : 0);
+ n_length=keys * sizeof(KEY) + ext_key_parts * sizeof(KEY_PART_INFO);
+ if (!(keyinfo= (KEY*) alloc_root(&share->mem_root,
+ n_length + len)))
+ return 1;
+ bzero((char*) keyinfo,n_length);
+ share->key_info= keyinfo;
+ key_part= reinterpret_cast<KEY_PART_INFO*> (keyinfo + keys);
+
+ if (!(rec_per_key= (ulong*) alloc_root(&share->mem_root,
+ sizeof(ulong) * ext_key_parts)))
+ return 1;
+ first_key_part= key_part;
+ first_key_parts= first_keyinfo->user_defined_key_parts;
+ keyinfo->flags= first_keyinfo->flags;
+ keyinfo->key_length= first_keyinfo->key_length;
+ keyinfo->user_defined_key_parts= first_keyinfo->user_defined_key_parts;
+ keyinfo->algorithm= first_keyinfo->algorithm;
+ if (new_frm_ver >= 3)
+ keyinfo->block_size= first_keyinfo->block_size;
+ }
+
+ keyinfo->key_part= key_part;
+ keyinfo->rec_per_key= rec_per_key;
+ for (j=keyinfo->user_defined_key_parts ; j-- ; key_part++)
+ {
+ if (strpos + (new_frm_ver >= 1 ? 9 : 7) >= frm_image_end)
+ return 1;
+ *rec_per_key++=0;
+ key_part->fieldnr= (uint16) (uint2korr(strpos) & FIELD_NR_MASK);
+ key_part->offset= (uint) uint2korr(strpos+2)-1;
+ key_part->key_type= (uint) uint2korr(strpos+5);
+ // key_part->field= (Field*) 0; // Will be fixed later
+ if (new_frm_ver >= 1)
+ {
+ key_part->key_part_flag= *(strpos+4);
+ key_part->length= (uint) uint2korr(strpos+7);
+ strpos+=9;
+ }
+ else
+ {
+ key_part->length= *(strpos+4);
+ key_part->key_part_flag=0;
+ if (key_part->length > 128)
+ {
+ key_part->length&=127; /* purecov: inspected */
+ key_part->key_part_flag=HA_REVERSE_SORT; /* purecov: inspected */
+ }
+ strpos+=7;
+ }
+ key_part->store_length=key_part->length;
+ }
+
+ /*
+ Add primary key to end of extended keys for non unique keys for
+ storage engines that supports it.
+ */
+ keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
+ keyinfo->ext_key_flags= keyinfo->flags;
+ keyinfo->ext_key_part_map= 0;
+ if (share->use_ext_keys && i && !(keyinfo->flags & HA_NOSAME))
+ {
+ for (j= 0;
+ j < first_key_parts && keyinfo->ext_key_parts < MAX_REF_PARTS;
+ j++)
+ {
+ uint key_parts= keyinfo->user_defined_key_parts;
+ KEY_PART_INFO* curr_key_part= keyinfo->key_part;
+ KEY_PART_INFO* curr_key_part_end= curr_key_part+key_parts;
+ for ( ; curr_key_part < curr_key_part_end; curr_key_part++)
+ {
+ if (curr_key_part->fieldnr == first_key_part[j].fieldnr)
+ break;
+ }
+ if (curr_key_part == curr_key_part_end)
+ {
+ *key_part++= first_key_part[j];
+ *rec_per_key++= 0;
+ keyinfo->ext_key_parts++;
+ keyinfo->ext_key_part_map|= 1 << j;
+ }
+ }
+ if (j == first_key_parts)
+ keyinfo->ext_key_flags= keyinfo->flags | HA_EXT_NOSAME;
+ }
+ share->ext_key_parts+= keyinfo->ext_key_parts;
+ }
+ keynames=(char*) key_part;
+ strpos+= strnmov(keynames, (char *) strpos, frm_image_end - strpos) - keynames;
+ if (*strpos++) // key names are \0-terminated
+ return 1;
+
+ //reading index comments
+ for (keyinfo= share->key_info, i=0; i < keys; i++, keyinfo++)
+ {
+ if (keyinfo->flags & HA_USES_COMMENT)
+ {
+ if (strpos + 2 >= frm_image_end)
+ return 1;
+ keyinfo->comment.length= uint2korr(strpos);
+ strpos+= 2;
+
+ if (strpos + keyinfo->comment.length >= frm_image_end)
+ return 1;
+ keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos,
+ keyinfo->comment.length);
+ strpos+= keyinfo->comment.length;
+ }
+ DBUG_ASSERT(MY_TEST(keyinfo->flags & HA_USES_COMMENT) ==
+ (keyinfo->comment.length > 0));
+ }
+
+ share->keys= keys; // do it *after* all key_info's are initialized
+
+ return 0;
+}
+
+
+/** ensures that the enum value (read from frm) is within limits
+
+ if not - issues a warning and resets the value to 0
+ (that is, 0 is assumed to be a default value)
+*/
+
+static uint enum_value_with_check(THD *thd, TABLE_SHARE *share,
+ const char *name, uint value, uint limit)
+{
+ if (value < limit)
+ return value;
+
+ sql_print_warning("%s.frm: invalid value %d for the field %s",
+ share->normalized_path.str, value, name);
+ return 0;
+}
+
+
+/**
+ Check if a collation has changed number
+
+ @param mysql_version
+ @param current collation number
+
+ @retval new collation number (same as current collation number of no change)
+*/
+
+static uint upgrade_collation(ulong mysql_version, uint cs_number)
+{
+ if (mysql_version >= 50300 && mysql_version <= 50399)
+ {
+ switch (cs_number) {
+ case 149: return MY_PAGE2_COLLATION_ID_UCS2; // ucs2_crotian_ci
+ case 213: return MY_PAGE2_COLLATION_ID_UTF8; // utf8_crotian_ci
+ }
+ }
+ if ((mysql_version >= 50500 && mysql_version <= 50599) ||
+ (mysql_version >= 100000 && mysql_version <= 100005))
+ {
+ switch (cs_number) {
+ case 149: return MY_PAGE2_COLLATION_ID_UCS2; // ucs2_crotian_ci
+ case 213: return MY_PAGE2_COLLATION_ID_UTF8; // utf8_crotian_ci
+ case 214: return MY_PAGE2_COLLATION_ID_UTF32; // utf32_croatian_ci
+ case 215: return MY_PAGE2_COLLATION_ID_UTF16; // utf16_croatian_ci
+ case 245: return MY_PAGE2_COLLATION_ID_UTF8MB4;// utf8mb4_croatian_ci
+ }
+ }
+ return cs_number;
+}
+
+
+/**
+ Read data from a binary .frm file image into a TABLE_SHARE
+
+ @note
+ frm bytes at the following offsets are unused in MariaDB 10.0:
+
+ 8..9 (used to be the number of "form names")
+ 28..29 (used to be key_info_length)
+
+ They're still set, for compatibility reasons, but never read.
+
+ 42..46 are unused since 5.0 (were for RAID support)
+ Also, there're few unused bytes in forminfo.
+
+*/
+
+int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write,
+ const uchar *frm_image,
+ size_t frm_length)
+{
+ TABLE_SHARE *share= this;
+ uint new_frm_ver, field_pack_length, new_field_pack_flag;
+ uint interval_count, interval_parts, read_length, int_length;
+ uint db_create_options, keys, key_parts, n_length;
+ uint com_length, null_bit_pos;
+ uint extra_rec_buf_length;
+ uint i;
+ bool use_hash;
+ char *keynames, *names, *comment_pos;
+ const uchar *forminfo, *extra2;
+ const uchar *frm_image_end = frm_image + frm_length;
+ uchar *record, *null_flags, *null_pos;
+ const uchar *disk_buff, *strpos;
+ ulong pos, record_offset;
+ ulong rec_buff_length;
+ handler *handler_file= 0;
+ KEY *keyinfo;
+ KEY_PART_INFO *key_part= NULL;
+ Field **field_ptr, *reg_field;
+ const char **interval_array;
+ enum legacy_db_type legacy_db_type;
+ my_bitmap_map *bitmaps;
+ bool null_bits_are_used;
+ uint vcol_screen_length, UNINIT_VAR(options_len);
+ char *vcol_screen_pos;
+ const uchar *options= 0;
+ KEY first_keyinfo;
+ uint len;
+ uint ext_key_parts= 0;
+ plugin_ref se_plugin= 0;
+ keyinfo= &first_keyinfo;
+ share->ext_key_parts= 0;
+ MEM_ROOT **root_ptr, *old_root;
+ DBUG_ENTER("TABLE_SHARE::init_from_binary_frm_image");
+
+ root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC);
+ old_root= *root_ptr;
+ *root_ptr= &share->mem_root;
+
+ if (write && write_frm_image(frm_image, frm_length))
+ goto err;
+
+ if (frm_length < FRM_HEADER_SIZE + FRM_FORMINFO_SIZE)
+ goto err;
+
+ new_field_pack_flag= frm_image[27];
+ new_frm_ver= (frm_image[2] - FRM_VER);
+ field_pack_length= new_frm_ver < 2 ? 11 : 17;
+
+ /* Length of the MariaDB extra2 segment in the form file. */
+ len = uint2korr(frm_image+4);
+ extra2= frm_image + 64;
+
+ if (*extra2 != '/') // old frm had '/' there
+ {
+ const uchar *e2end= extra2 + len;
+ while (extra2 + 3 < e2end)
+ {
+ uchar type= *extra2++;
+ size_t length= *extra2++;
+ if (!length)
+ {
+ if (extra2 + 2 >= e2end)
+ goto err;
+ length= uint2korr(extra2);
+ extra2+= 2;
+ if (length < 256)
+ goto err;
+ }
+ if (extra2 + length > e2end)
+ goto err;
+ switch (type) {
+ case EXTRA2_TABLEDEF_VERSION:
+ if (tabledef_version.str) // see init_from_sql_statement_string()
+ {
+ if (length != tabledef_version.length ||
+ memcmp(extra2, tabledef_version.str, length))
+ goto err;
+ }
+ else
+ {
+ tabledef_version.length= length;
+ tabledef_version.str= (uchar*)memdup_root(&mem_root, extra2, length);
+ if (!tabledef_version.str)
+ goto err;
+ }
+ break;
+ case EXTRA2_ENGINE_TABLEOPTS:
+ if (options)
+ goto err;
+ /* remember but delay parsing until we have read fields and keys */
+ options= extra2;
+ options_len= length;
+ break;
+ case EXTRA2_DEFAULT_PART_ENGINE:
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ {
+ LEX_STRING name= { (char*)extra2, length };
+ share->default_part_plugin= ha_resolve_by_name(NULL, &name);
+ if (!share->default_part_plugin)
+ goto err;
+ }
+#endif
+ break;
+ default:
+ /* abort frm parsing if it's an unknown but important extra2 value */
+ if (type >= EXTRA2_ENGINE_IMPORTANT)
+ goto err;
+ }
+ extra2+= length;
+ }
+ if (extra2 != e2end)
+ goto err;
+ }
+
+ if (frm_length < FRM_HEADER_SIZE + len ||
+ !(pos= uint4korr(frm_image + FRM_HEADER_SIZE + len)))
+ goto err;
+
+ forminfo= frm_image + pos;
+ if (forminfo + FRM_FORMINFO_SIZE >= frm_image_end)
+ goto err;
+
+ share->frm_version= frm_image[2];
+ /*
+ Check if .frm file created by MySQL 5.0. In this case we want to
+ display CHAR fields as CHAR and not as VARCHAR.
+ We do it this way as we want to keep the old frm version to enable
+ MySQL 4.1 to read these files.
+ */
+ if (share->frm_version == FRM_VER_TRUE_VARCHAR -1 && frm_image[33] == 5)
+ share->frm_version= FRM_VER_TRUE_VARCHAR;
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (frm_image[61] && !share->default_part_plugin)
+ {
+ enum legacy_db_type db_type= (enum legacy_db_type) (uint) frm_image[61];
+ share->default_part_plugin=
+ ha_lock_engine(NULL, ha_checktype(thd, db_type, 1, 0));
+ if (!share->default_part_plugin)
+ goto err;
+ }
+#endif
+ legacy_db_type= (enum legacy_db_type) (uint) frm_image[3];
+ /*
+ if the storage engine is dynamic, no point in resolving it by its
+ dynamically allocated legacy_db_type. We will resolve it later by name.
+ */
+ if (legacy_db_type > DB_TYPE_UNKNOWN &&
+ legacy_db_type < DB_TYPE_FIRST_DYNAMIC)
+ se_plugin= ha_lock_engine(NULL, ha_checktype(thd, legacy_db_type, 0, 0));
+ share->db_create_options= db_create_options= uint2korr(frm_image+30);
+ share->db_options_in_use= share->db_create_options;
+ share->mysql_version= uint4korr(frm_image+51);
+ share->null_field_first= 0;
+ if (!frm_image[32]) // New frm file in 3.23
+ {
+ uint cs_org= (((uint) frm_image[41]) << 8) + (uint) frm_image[38];
+ uint cs_new= upgrade_collation(share->mysql_version, cs_org);
+ if (cs_org != cs_new)
+ share->incompatible_version|= HA_CREATE_USED_CHARSET;
+
+ share->avg_row_length= uint4korr(frm_image+34);
+ share->transactional= (ha_choice)
+ enum_value_with_check(thd, share, "transactional", frm_image[39] & 3, HA_CHOICE_MAX);
+ share->page_checksum= (ha_choice)
+ enum_value_with_check(thd, share, "page_checksum", (frm_image[39] >> 2) & 3, HA_CHOICE_MAX);
+ share->row_type= (enum row_type)
+ enum_value_with_check(thd, share, "row_format", frm_image[40], ROW_TYPE_MAX);
+
+ if (cs_new && !(share->table_charset= get_charset(cs_new, MYF(MY_WME))))
+ goto err;
+ share->null_field_first= 1;
+ share->stats_sample_pages= uint2korr(frm_image+42);
+ share->stats_auto_recalc= (enum_stats_auto_recalc)(frm_image[44]);
+ }
+ if (!share->table_charset)
+ {
+ /* unknown charset in frm_image[38] or pre-3.23 frm */
+ if (use_mb(default_charset_info))
+ {
+ /* Warn that we may be changing the size of character columns */
+ sql_print_warning("'%s' had no or invalid character set, "
+ "and default character set is multi-byte, "
+ "so character column sizes may have changed",
+ share->path.str);
+ }
+ share->table_charset= default_charset_info;
+ }
+
+ share->db_record_offset= 1;
+ share->max_rows= uint4korr(frm_image+18);
+ share->min_rows= uint4korr(frm_image+22);
+
+ /* Read keyinformation */
+ disk_buff= frm_image + uint2korr(frm_image+6);
+
+ if (disk_buff + 6 >= frm_image_end)
+ goto err;
+
+ if (disk_buff[0] & 0x80)
+ {
+ keys= (disk_buff[1] << 7) | (disk_buff[0] & 0x7f);
+ share->key_parts= key_parts= uint2korr(disk_buff+2);
+ }
+ else
+ {
+ keys= disk_buff[0];
+ share->key_parts= key_parts= disk_buff[1];
+ }
+ share->keys_for_keyread.init(0);
+ share->keys_in_use.init(keys);
+ ext_key_parts= key_parts;
+
+ len= (uint) uint2korr(disk_buff+4);
+
+ share->reclength = uint2korr(frm_image+16);
+ share->stored_rec_length= share->reclength;
+ if (frm_image[26] == 1)
+ share->system= 1; /* one-record-database */
+
+ record_offset= (ulong) (uint2korr(frm_image+6)+
+ ((uint2korr(frm_image+14) == 0xffff ?
+ uint4korr(frm_image+47) : uint2korr(frm_image+14))));
+
+ if (record_offset + share->reclength >= frm_length)
+ goto err;
+
+ if ((n_length= uint4korr(frm_image+55)))
+ {
+ /* Read extra data segment */
+ const uchar *next_chunk, *buff_end;
+ DBUG_PRINT("info", ("extra segment size is %u bytes", n_length));
+ next_chunk= frm_image + record_offset + share->reclength;
+ buff_end= next_chunk + n_length;
+
+ if (buff_end >= frm_image_end)
+ goto err;
+
+ share->connect_string.length= uint2korr(next_chunk);
+ if (!(share->connect_string.str= strmake_root(&share->mem_root,
+ (char*) next_chunk + 2,
+ share->connect_string.
+ length)))
+ {
+ goto err;
+ }
+ next_chunk+= share->connect_string.length + 2;
+ if (next_chunk + 2 < buff_end)
+ {
+ uint str_db_type_length= uint2korr(next_chunk);
+ LEX_STRING name;
+ name.str= (char*) next_chunk + 2;
+ name.length= str_db_type_length;
+
+ plugin_ref tmp_plugin= ha_resolve_by_name(thd, &name);
+ if (tmp_plugin != NULL && !plugin_equals(tmp_plugin, se_plugin))
+ {
+ if (se_plugin)
+ {
+ /* bad file, legacy_db_type did not match the name */
+ sql_print_warning("%s.frm is inconsistent: engine typecode %d, engine name %s (%d)",
+ share->normalized_path.str, legacy_db_type,
+ plugin_name(tmp_plugin)->str,
+ ha_legacy_type(plugin_data(tmp_plugin, handlerton *)));
+ }
+ /*
+ tmp_plugin is locked with a local lock.
+ we unlock the old value of se_plugin before
+ replacing it with a globally locked version of tmp_plugin
+ */
+ plugin_unlock(NULL, se_plugin);
+ se_plugin= plugin_lock(NULL, tmp_plugin);
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ else if (str_db_type_length == 9 &&
+ !strncmp((char *) next_chunk + 2, "partition", 9))
+ {
+ /*
+ Use partition handler
+ tmp_plugin is locked with a local lock.
+ we unlock the old value of se_plugin before
+ replacing it with a globally locked version of tmp_plugin
+ */
+ /* Check if the partitioning engine is ready */
+ if (!plugin_is_ready(&name, MYSQL_STORAGE_ENGINE_PLUGIN))
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
+ "--skip-partition");
+ goto err;
+ }
+ plugin_unlock(NULL, se_plugin);
+ se_plugin= ha_lock_engine(NULL, partition_hton);
+ }
+#endif
+ else if (!tmp_plugin)
+ {
+ /* purecov: begin inspected */
+ name.str[name.length]=0;
+ my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), name.str);
+ goto err;
+ /* purecov: end */
+ }
+ next_chunk+= str_db_type_length + 2;
+ }
+
+ share->set_use_ext_keys_flag(plugin_hton(se_plugin)->flags & HTON_SUPPORTS_EXTENDED_KEYS);
+
+ if (create_key_infos(disk_buff + 6, frm_image_end, keys, keyinfo,
+ new_frm_ver, ext_key_parts,
+ share, len, &first_keyinfo, keynames))
+ goto err;
+
+ if (next_chunk + 5 < buff_end)
+ {
+ uint32 partition_info_str_len = uint4korr(next_chunk);
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if ((share->partition_info_buffer_size=
+ share->partition_info_str_len= partition_info_str_len))
+ {
+ if (!(share->partition_info_str= (char*)
+ memdup_root(&share->mem_root, next_chunk + 4,
+ partition_info_str_len + 1)))
+ {
+ goto err;
+ }
+ }
+#else
+ if (partition_info_str_len)
+ {
+ DBUG_PRINT("info", ("WITH_PARTITION_STORAGE_ENGINE is not defined"));
+ goto err;
+ }
+#endif
+ next_chunk+= 5 + partition_info_str_len;
+ }
+ if (share->mysql_version >= 50110 && next_chunk < buff_end)
+ {
+ /* New auto_partitioned indicator introduced in 5.1.11 */
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ share->auto_partitioned= *next_chunk;
+#endif
+ next_chunk++;
+ }
+ keyinfo= share->key_info;
+ for (i= 0; i < keys; i++, keyinfo++)
+ {
+ if (keyinfo->flags & HA_USES_PARSER)
+ {
+ LEX_STRING parser_name;
+ if (next_chunk >= buff_end)
+ {
+ DBUG_PRINT("error",
+ ("fulltext key uses parser that is not defined in .frm"));
+ goto err;
+ }
+ parser_name.str= (char*) next_chunk;
+ parser_name.length= strlen((char*) next_chunk);
+ next_chunk+= parser_name.length + 1;
+ keyinfo->parser= my_plugin_lock_by_name(NULL, &parser_name,
+ MYSQL_FTPARSER_PLUGIN);
+ if (! keyinfo->parser)
+ {
+ my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), parser_name.str);
+ goto err;
+ }
+ }
+ }
+
+ if (forminfo[46] == (uchar)255)
+ {
+ //reading long table comment
+ if (next_chunk + 2 > buff_end)
+ {
+ DBUG_PRINT("error",
+ ("long table comment is not defined in .frm"));
+ goto err;
+ }
+ share->comment.length = uint2korr(next_chunk);
+ if (! (share->comment.str= strmake_root(&share->mem_root,
+ (char*)next_chunk + 2, share->comment.length)))
+ {
+ goto err;
+ }
+ next_chunk+= 2 + share->comment.length;
+ }
+
+ DBUG_ASSERT(next_chunk <= buff_end);
+
+ if (share->db_create_options & HA_OPTION_TEXT_CREATE_OPTIONS_legacy)
+ {
+ if (options)
+ goto err;
+ options_len= uint4korr(next_chunk);
+ options= next_chunk + 4;
+ next_chunk+= options_len + 4;
+ }
+ DBUG_ASSERT(next_chunk <= buff_end);
+ }
+ else
+ {
+ if (create_key_infos(disk_buff + 6, frm_image_end, keys, keyinfo,
+ new_frm_ver, ext_key_parts,
+ share, len, &first_keyinfo, keynames))
+ goto err;
+ }
+
+ share->key_block_size= uint2korr(frm_image+62);
+
+ if (share->db_plugin && !plugin_equals(share->db_plugin, se_plugin))
+ goto err; // wrong engine (someone changed the frm under our feet?)
+
+ extra_rec_buf_length= uint2korr(frm_image+59);
+ rec_buff_length= ALIGN_SIZE(share->reclength + 1 + extra_rec_buf_length);
+ share->rec_buff_length= rec_buff_length;
+ if (!(record= (uchar *) alloc_root(&share->mem_root,
+ rec_buff_length)))
+ goto err; /* purecov: inspected */
+ share->default_values= record;
+ memcpy(record, frm_image + record_offset, share->reclength);
+
+ disk_buff= frm_image + pos + FRM_FORMINFO_SIZE;
+
+ share->fields= uint2korr(forminfo+258);
+ pos= uint2korr(forminfo+260); /* Length of all screens */
+ n_length= uint2korr(forminfo+268);
+ interval_count= uint2korr(forminfo+270);
+ interval_parts= uint2korr(forminfo+272);
+ int_length= uint2korr(forminfo+274);
+ share->null_fields= uint2korr(forminfo+282);
+ com_length= uint2korr(forminfo+284);
+ vcol_screen_length= uint2korr(forminfo+286);
+ share->vfields= 0;
+ share->default_fields= 0;
+ share->stored_fields= share->fields;
+ if (forminfo[46] != (uchar)255)
+ {
+ share->comment.length= (int) (forminfo[46]);
+ share->comment.str= strmake_root(&share->mem_root, (char*) forminfo+47,
+ share->comment.length);
+ }
+
+ DBUG_PRINT("info",("i_count: %d i_parts: %d index: %d n_length: %d int_length: %d com_length: %d vcol_screen_length: %d", interval_count,interval_parts, keys,n_length,int_length, com_length, vcol_screen_length));
+
+
+ if (!(field_ptr = (Field **)
+ alloc_root(&share->mem_root,
+ (uint) ((share->fields+1)*sizeof(Field*)+
+ interval_count*sizeof(TYPELIB)+
+ (share->fields+interval_parts+
+ keys+3)*sizeof(char *)+
+ (n_length+int_length+com_length+
+ vcol_screen_length)))))
+ goto err; /* purecov: inspected */
+
+ share->field= field_ptr;
+ read_length=(uint) (share->fields * field_pack_length +
+ pos+ (uint) (n_length+int_length+com_length+
+ vcol_screen_length));
+ strpos= disk_buff+pos;
+
+ share->intervals= (TYPELIB*) (field_ptr+share->fields+1);
+ interval_array= (const char **) (share->intervals+interval_count);
+ names= (char*) (interval_array+share->fields+interval_parts+keys+3);
+ if (!interval_count)
+ share->intervals= 0; // For better debugging
+ memcpy((char*) names, strpos+(share->fields*field_pack_length),
+ (uint) (n_length+int_length));
+ comment_pos= names+(n_length+int_length);
+ memcpy(comment_pos, disk_buff+read_length-com_length-vcol_screen_length,
+ com_length);
+ vcol_screen_pos= names+(n_length+int_length+com_length);
+ memcpy(vcol_screen_pos, disk_buff+read_length-vcol_screen_length,
+ vcol_screen_length);
+
+ fix_type_pointers(&interval_array, &share->fieldnames, 1, &names);
+ if (share->fieldnames.count != share->fields)
+ goto err;
+ fix_type_pointers(&interval_array, share->intervals, interval_count,
+ &names);
+
+ {
+ /* Set ENUM and SET lengths */
+ TYPELIB *interval;
+ for (interval= share->intervals;
+ interval < share->intervals + interval_count;
+ interval++)
+ {
+ uint count= (uint) (interval->count + 1) * sizeof(uint);
+ if (!(interval->type_lengths= (uint *) alloc_root(&share->mem_root,
+ count)))
+ goto err;
+ for (count= 0; count < interval->count; count++)
+ {
+ char *val= (char*) interval->type_names[count];
+ interval->type_lengths[count]= strlen(val);
+ }
+ interval->type_lengths[count]= 0;
+ }
+ }
+
+ if (keynames)
+ fix_type_pointers(&interval_array, &share->keynames, 1, &keynames);
+
+ /* Allocate handler */
+ if (!(handler_file= get_new_handler(share, thd->mem_root,
+ plugin_hton(se_plugin))))
+ goto err;
+
+ if (handler_file->set_ha_share_ref(&share->ha_share))
+ goto err;
+
+ record= share->default_values-1; /* Fieldstart = 1 */
+ null_bits_are_used= share->null_fields != 0;
+ if (share->null_field_first)
+ {
+ null_flags= null_pos= record+1;
+ null_bit_pos= (db_create_options & HA_OPTION_PACK_RECORD) ? 0 : 1;
+ /*
+ null_bytes below is only correct under the condition that
+ there are no bit fields. Correct values is set below after the
+ table struct is initialized
+ */
+ share->null_bytes= (share->null_fields + null_bit_pos + 7) / 8;
+ }
+#ifndef WE_WANT_TO_SUPPORT_VERY_OLD_FRM_FILES
+ else
+ {
+ share->null_bytes= (share->null_fields+7)/8;
+ null_flags= null_pos= record + 1 + share->reclength - share->null_bytes;
+ null_bit_pos= 0;
+ }
+#endif
+
+ use_hash= share->fields >= MAX_FIELDS_BEFORE_HASH;
+ if (use_hash)
+ use_hash= !my_hash_init(&share->name_hash,
+ system_charset_info,
+ share->fields,0,0,
+ (my_hash_get_key) get_field_name,0,0);
+
+ for (i=0 ; i < share->fields; i++, strpos+=field_pack_length, field_ptr++)
+ {
+ uint pack_flag, interval_nr, unireg_type, recpos, field_length;
+ uint vcol_info_length=0;
+ uint vcol_expr_length=0;
+ enum_field_types field_type;
+ CHARSET_INFO *charset=NULL;
+ Field::geometry_type geom_type= Field::GEOM_GEOMETRY;
+ LEX_STRING comment;
+ Virtual_column_info *vcol_info= 0;
+ bool fld_stored_in_db= TRUE;
+
+ if (new_frm_ver >= 3)
+ {
+ /* new frm file in 4.1 */
+ field_length= uint2korr(strpos+3);
+ recpos= uint3korr(strpos+5);
+ pack_flag= uint2korr(strpos+8);
+ unireg_type= (uint) strpos[10];
+ interval_nr= (uint) strpos[12];
+ uint comment_length=uint2korr(strpos+15);
+ field_type=(enum_field_types) (uint) strpos[13];
+
+ /* charset and geometry_type share the same byte in frm */
+ if (field_type == MYSQL_TYPE_GEOMETRY)
+ {
+#ifdef HAVE_SPATIAL
+ geom_type= (Field::geometry_type) strpos[14];
+ charset= &my_charset_bin;
+#else
+ goto err;
+#endif
+ }
+ else
+ {
+ uint cs_org= strpos[14] + (((uint) strpos[11]) << 8);
+ uint cs_new= upgrade_collation(share->mysql_version, cs_org);
+ if (cs_org != cs_new)
+ share->incompatible_version|= HA_CREATE_USED_CHARSET;
+ if (!cs_new)
+ charset= &my_charset_bin;
+ else if (!(charset= get_charset(cs_new, MYF(0))))
+ {
+ const char *csname= get_charset_name((uint) cs_new);
+ char tmp[10];
+ if (!csname || csname[0] =='?')
+ {
+ my_snprintf(tmp, sizeof(tmp), "#%d", cs_new);
+ csname= tmp;
+ }
+ my_printf_error(ER_UNKNOWN_COLLATION,
+ "Unknown collation '%s' in table '%-.64s' definition",
+ MYF(0), csname, share->table_name.str);
+ goto err;
+ }
+ }
+
+ if ((uchar)field_type == (uchar)MYSQL_TYPE_VIRTUAL)
+ {
+ DBUG_ASSERT(interval_nr); // Expect non-null expression
+ /*
+ The interval_id byte in the .frm file stores the length of the
+ expression statement for a virtual column.
+ */
+ vcol_info_length= interval_nr;
+ interval_nr= 0;
+ }
+
+ if (!comment_length)
+ {
+ comment.str= (char*) "";
+ comment.length=0;
+ }
+ else
+ {
+ comment.str= (char*) comment_pos;
+ comment.length= comment_length;
+ comment_pos+= comment_length;
+ }
+
+ if (vcol_info_length)
+ {
+ /*
+ Get virtual column data stored in the .frm file as follows:
+ byte 1 = 1 | 2
+ byte 2 = sql_type
+ byte 3 = flags (as of now, 0 - no flags, 1 - field is physically stored)
+ [byte 4] = optional interval_id for sql_type (only if byte 1 == 2)
+ next byte ... = virtual column expression (text data)
+ */
+ vcol_info= new Virtual_column_info();
+ bool opt_interval_id= (uint)vcol_screen_pos[0] == 2;
+ field_type= (enum_field_types) (uchar) vcol_screen_pos[1];
+ if (opt_interval_id)
+ interval_nr= (uint)vcol_screen_pos[3];
+ else if ((uint)vcol_screen_pos[0] != 1)
+ goto err;
+
+ fld_stored_in_db= (bool) (uint) vcol_screen_pos[2];
+ vcol_expr_length= vcol_info_length -
+ (uint)(FRM_VCOL_HEADER_SIZE(opt_interval_id));
+ if (!(vcol_info->expr_str.str=
+ (char *)memdup_root(&share->mem_root,
+ vcol_screen_pos +
+ (uint) FRM_VCOL_HEADER_SIZE(opt_interval_id),
+ vcol_expr_length)))
+ goto err;
+ if (opt_interval_id)
+ interval_nr= (uint) vcol_screen_pos[3];
+ vcol_info->expr_str.length= vcol_expr_length;
+ vcol_screen_pos+= vcol_info_length;
+ share->vfields++;
+ }
+ }
+ else
+ {
+ field_length= (uint) strpos[3];
+ recpos= uint2korr(strpos+4),
+ pack_flag= uint2korr(strpos+6);
+ pack_flag&= ~FIELDFLAG_NO_DEFAULT; // Safety for old files
+ unireg_type= (uint) strpos[8];
+ interval_nr= (uint) strpos[10];
+
+ /* old frm file */
+ field_type= (enum_field_types) f_packtype(pack_flag);
+ if (f_is_binary(pack_flag))
+ {
+ /*
+ Try to choose the best 4.1 type:
+ - for 4.0 "CHAR(N) BINARY" or "VARCHAR(N) BINARY"
+ try to find a binary collation for character set.
+ - for other types (e.g. BLOB) just use my_charset_bin.
+ */
+ if (!f_is_blob(pack_flag))
+ {
+ // 3.23 or 4.0 string
+ if (!(charset= get_charset_by_csname(share->table_charset->csname,
+ MY_CS_BINSORT, MYF(0))))
+ charset= &my_charset_bin;
+ }
+ else
+ charset= &my_charset_bin;
+ }
+ else
+ charset= share->table_charset;
+ bzero((char*) &comment, sizeof(comment));
+ }
+
+ if (interval_nr && charset->mbminlen > 1)
+ {
+ /* Unescape UCS2 intervals from HEX notation */
+ TYPELIB *interval= share->intervals + interval_nr - 1;
+ unhex_type2(interval);
+ }
+
+#ifndef TO_BE_DELETED_ON_PRODUCTION
+ if (field_type == MYSQL_TYPE_NEWDECIMAL && !share->mysql_version)
+ {
+ /*
+ Fix pack length of old decimal values from 5.0.3 -> 5.0.4
+ The difference is that in the old version we stored precision
+ in the .frm table while we now store the display_length
+ */
+ uint decimals= f_decimals(pack_flag);
+ field_length= my_decimal_precision_to_length(field_length,
+ decimals,
+ f_is_dec(pack_flag) == 0);
+ sql_print_error("Found incompatible DECIMAL field '%s' in %s; "
+ "Please do \"ALTER TABLE '%s' FORCE\" to fix it!",
+ share->fieldnames.type_names[i], share->table_name.str,
+ share->table_name.str);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_CRASHED_ON_USAGE,
+ "Found incompatible DECIMAL field '%s' in %s; "
+ "Please do \"ALTER TABLE '%s' FORCE\" to fix it!",
+ share->fieldnames.type_names[i],
+ share->table_name.str,
+ share->table_name.str);
+ share->crashed= 1; // Marker for CHECK TABLE
+ }
+#endif
+
+ *field_ptr= reg_field=
+ make_field(share, record+recpos,
+ (uint32) field_length,
+ null_pos, null_bit_pos,
+ pack_flag,
+ field_type,
+ charset,
+ geom_type,
+ (Field::utype) MTYP_TYPENR(unireg_type),
+ (interval_nr ?
+ share->intervals+interval_nr-1 :
+ (TYPELIB*) 0),
+ share->fieldnames.type_names[i]);
+ if (!reg_field) // Not supported field type
+ goto err;
+
+
+ reg_field->field_index= i;
+ reg_field->comment=comment;
+ reg_field->vcol_info= vcol_info;
+ reg_field->stored_in_db= fld_stored_in_db;
+ if (field_type == MYSQL_TYPE_BIT && !f_bit_as_char(pack_flag))
+ {
+ null_bits_are_used= 1;
+ if ((null_bit_pos+= field_length & 7) > 7)
+ {
+ null_pos++;
+ null_bit_pos-= 8;
+ }
+ }
+ if (!(reg_field->flags & NOT_NULL_FLAG))
+ {
+ if (!(null_bit_pos= (null_bit_pos + 1) & 7))
+ null_pos++;
+ }
+ if (f_no_default(pack_flag))
+ reg_field->flags|= NO_DEFAULT_VALUE_FLAG;
+
+ if (reg_field->unireg_check == Field::NEXT_NUMBER)
+ share->found_next_number_field= field_ptr;
+
+ if (use_hash && my_hash_insert(&share->name_hash, (uchar*) field_ptr))
+ goto err;
+ if (!reg_field->stored_in_db)
+ {
+ share->stored_fields--;
+ if (share->stored_rec_length>=recpos)
+ share->stored_rec_length= recpos-1;
+ }
+ if (reg_field->has_insert_default_function() ||
+ reg_field->has_update_default_function())
+ ++share->default_fields;
+ }
+ *field_ptr=0; // End marker
+ /* Sanity checks: */
+ DBUG_ASSERT(share->fields>=share->stored_fields);
+ DBUG_ASSERT(share->reclength>=share->stored_rec_length);
+
+ /* Fix key->name and key_part->field */
+ if (key_parts)
+ {
+ uint add_first_key_parts= 0;
+ longlong ha_option= handler_file->ha_table_flags();
+ keyinfo= share->key_info;
+ uint primary_key= my_strcasecmp(system_charset_info, share->keynames.type_names[0],
+ primary_key_name) ? MAX_KEY : 0;
+
+ if (primary_key >= MAX_KEY && keyinfo->flags & HA_NOSAME)
+ {
+ /*
+ If the UNIQUE key doesn't have NULL columns and is not a part key
+ declare this as a primary key.
+ */
+ primary_key= 0;
+ key_part= keyinfo->key_part;
+ for (i=0 ; i < keyinfo->user_defined_key_parts ;i++)
+ {
+ DBUG_ASSERT(key_part[i].fieldnr > 0);
+ // Table field corresponding to the i'th key part.
+ Field *table_field= share->field[key_part[i].fieldnr - 1];
+
+ /*
+ If the key column is of NOT NULL BLOB type, then it
+ will definitly have key prefix. And if key part prefix size
+ is equal to the BLOB column max size, then we can promote
+ it to primary key.
+ */
+ if (!table_field->real_maybe_null() &&
+ table_field->type() == MYSQL_TYPE_BLOB &&
+ table_field->field_length == key_part[i].length)
+ continue;
+
+ if (table_field->real_maybe_null() ||
+ table_field->key_length() != key_part[i].length)
+ {
+ primary_key= MAX_KEY; // Can't be used
+ break;
+ }
+ }
+ }
+
+ if (share->use_ext_keys)
+ {
+ if (primary_key >= MAX_KEY)
+ {
+ add_first_key_parts= 0;
+ share->set_use_ext_keys_flag(FALSE);
+ }
+ else
+ {
+ add_first_key_parts= first_keyinfo.user_defined_key_parts;
+ /*
+ Do not add components of the primary key starting from
+ the major component defined over the beginning of a field.
+ */
+ for (i= 0; i < first_keyinfo.user_defined_key_parts; i++)
+ {
+ uint fieldnr= keyinfo[0].key_part[i].fieldnr;
+ if (share->field[fieldnr-1]->key_length() !=
+ keyinfo[0].key_part[i].length)
+ {
+ add_first_key_parts= i;
+ break;
+ }
+ }
+ }
+ }
+
+ for (uint key=0 ; key < keys ; key++,keyinfo++)
+ {
+ uint usable_parts= 0;
+ keyinfo->name=(char*) share->keynames.type_names[key];
+ keyinfo->name_length= strlen(keyinfo->name);
+ keyinfo->cache_name=
+ (uchar*) alloc_root(&share->mem_root,
+ share->table_cache_key.length+
+ keyinfo->name_length + 1);
+ if (keyinfo->cache_name) // If not out of memory
+ {
+ uchar *pos= keyinfo->cache_name;
+ memcpy(pos, share->table_cache_key.str, share->table_cache_key.length);
+ memcpy(pos + share->table_cache_key.length, keyinfo->name,
+ keyinfo->name_length+1);
+ }
+
+ if (ext_key_parts > share->key_parts && key)
+ {
+ KEY_PART_INFO *new_key_part= (keyinfo-1)->key_part +
+ (keyinfo-1)->ext_key_parts;
+
+ /*
+ Do not extend the key that contains a component
+ defined over the beginning of a field.
+ */
+ for (i= 0; i < keyinfo->user_defined_key_parts; i++)
+ {
+ uint fieldnr= keyinfo->key_part[i].fieldnr;
+ if (share->field[fieldnr-1]->key_length() !=
+ keyinfo->key_part[i].length)
+ {
+ add_first_key_parts= 0;
+ break;
+ }
+ }
+
+ if (add_first_key_parts < keyinfo->ext_key_parts-keyinfo->user_defined_key_parts)
+ {
+ share->ext_key_parts-= keyinfo->ext_key_parts;
+ key_part_map ext_key_part_map= keyinfo->ext_key_part_map;
+ keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
+ keyinfo->ext_key_flags= keyinfo->flags;
+ keyinfo->ext_key_part_map= 0;
+ for (i= 0; i < add_first_key_parts; i++)
+ {
+ if (ext_key_part_map & 1<<i)
+ {
+ keyinfo->ext_key_part_map|= 1<<i;
+ keyinfo->ext_key_parts++;
+ }
+ }
+ share->ext_key_parts+= keyinfo->ext_key_parts;
+ }
+ if (new_key_part != keyinfo->key_part)
+ {
+ memmove(new_key_part, keyinfo->key_part,
+ sizeof(KEY_PART_INFO) * keyinfo->ext_key_parts);
+ keyinfo->key_part= new_key_part;
+ }
+ }
+
+ /* Fix fulltext keys for old .frm files */
+ if (share->key_info[key].flags & HA_FULLTEXT)
+ share->key_info[key].algorithm= HA_KEY_ALG_FULLTEXT;
+
+ key_part= keyinfo->key_part;
+ uint key_parts= share->use_ext_keys ? keyinfo->ext_key_parts :
+ keyinfo->user_defined_key_parts;
+ for (i=0; i < key_parts; key_part++, i++)
+ {
+ Field *field;
+ if (new_field_pack_flag <= 1)
+ key_part->fieldnr= (uint16) find_field(share->field,
+ share->default_values,
+ (uint) key_part->offset,
+ (uint) key_part->length);
+ if (!key_part->fieldnr)
+ goto err;
+
+ field= key_part->field= share->field[key_part->fieldnr-1];
+ key_part->type= field->key_type();
+ if (field->null_ptr)
+ {
+ key_part->null_offset=(uint) ((uchar*) field->null_ptr -
+ share->default_values);
+ key_part->null_bit= field->null_bit;
+ key_part->store_length+=HA_KEY_NULL_LENGTH;
+ keyinfo->flags|=HA_NULL_PART_KEY;
+ keyinfo->key_length+= HA_KEY_NULL_LENGTH;
+ }
+ if (field->type() == MYSQL_TYPE_BLOB ||
+ field->real_type() == MYSQL_TYPE_VARCHAR ||
+ field->type() == MYSQL_TYPE_GEOMETRY)
+ {
+ if (field->type() == MYSQL_TYPE_BLOB ||
+ field->type() == MYSQL_TYPE_GEOMETRY)
+ key_part->key_part_flag|= HA_BLOB_PART;
+ else
+ key_part->key_part_flag|= HA_VAR_LENGTH_PART;
+ key_part->store_length+=HA_KEY_BLOB_LENGTH;
+ keyinfo->key_length+= HA_KEY_BLOB_LENGTH;
+ }
+ if (field->type() == MYSQL_TYPE_BIT)
+ key_part->key_part_flag|= HA_BIT_PART;
+
+ if (i == 0 && key != primary_key)
+ field->flags |= (((keyinfo->flags & HA_NOSAME) &&
+ (keyinfo->user_defined_key_parts == 1)) ?
+ UNIQUE_KEY_FLAG : MULTIPLE_KEY_FLAG);
+ if (i == 0)
+ field->key_start.set_bit(key);
+ if (field->key_length() == key_part->length &&
+ !(field->flags & BLOB_FLAG))
+ {
+ if (handler_file->index_flags(key, i, 0) & HA_KEYREAD_ONLY)
+ {
+ share->keys_for_keyread.set_bit(key);
+ field->part_of_key.set_bit(key);
+ if (i < keyinfo->user_defined_key_parts)
+ field->part_of_key_not_clustered.set_bit(key);
+ }
+ if (handler_file->index_flags(key, i, 1) & HA_READ_ORDER)
+ field->part_of_sortkey.set_bit(key);
+ }
+ if (!(key_part->key_part_flag & HA_REVERSE_SORT) &&
+ usable_parts == i)
+ usable_parts++; // For FILESORT
+ field->flags|= PART_KEY_FLAG;
+ if (key == primary_key)
+ {
+ field->flags|= PRI_KEY_FLAG;
+ /*
+ If this field is part of the primary key and all keys contains
+ the primary key, then we can use any key to find this column
+ */
+ if (ha_option & HA_PRIMARY_KEY_IN_READ_INDEX)
+ {
+ if (field->key_length() == key_part->length &&
+ !(field->flags & BLOB_FLAG))
+ field->part_of_key= share->keys_in_use;
+ if (field->part_of_sortkey.is_set(key))
+ field->part_of_sortkey= share->keys_in_use;
+ }
+ }
+ if (field->key_length() != key_part->length)
+ {
+#ifndef TO_BE_DELETED_ON_PRODUCTION
+ if (field->type() == MYSQL_TYPE_NEWDECIMAL)
+ {
+ /*
+ Fix a fatal error in decimal key handling that causes crashes
+ on Innodb. We fix it by reducing the key length so that
+ InnoDB never gets a too big key when searching.
+ This allows the end user to do an ALTER TABLE to fix the
+ error.
+ */
+ keyinfo->key_length-= (key_part->length - field->key_length());
+ key_part->store_length-= (uint16)(key_part->length -
+ field->key_length());
+ key_part->length= (uint16)field->key_length();
+ sql_print_error("Found wrong key definition in %s; "
+ "Please do \"ALTER TABLE '%s' FORCE \" to fix it!",
+ share->table_name.str,
+ share->table_name.str);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_CRASHED_ON_USAGE,
+ "Found wrong key definition in %s; "
+ "Please do \"ALTER TABLE '%s' FORCE\" to fix "
+ "it!",
+ share->table_name.str,
+ share->table_name.str);
+ share->crashed= 1; // Marker for CHECK TABLE
+ continue;
+ }
+#endif
+ key_part->key_part_flag|= HA_PART_KEY_SEG;
+ }
+ if (field->real_maybe_null())
+ key_part->key_part_flag|= HA_NULL_PART;
+ /*
+ Sometimes we can compare key parts for equality with memcmp.
+ But not always.
+ */
+ if (!(key_part->key_part_flag & (HA_BLOB_PART | HA_VAR_LENGTH_PART |
+ HA_BIT_PART)) &&
+ key_part->type != HA_KEYTYPE_FLOAT &&
+ key_part->type == HA_KEYTYPE_DOUBLE)
+ key_part->key_part_flag|= HA_CAN_MEMCMP;
+ }
+ keyinfo->usable_key_parts= usable_parts; // Filesort
+
+ set_if_bigger(share->max_key_length,keyinfo->key_length+
+ keyinfo->user_defined_key_parts);
+ share->total_key_length+= keyinfo->key_length;
+ /*
+ MERGE tables do not have unique indexes. But every key could be
+ an unique index on the underlying MyISAM table. (Bug #10400)
+ */
+ if ((keyinfo->flags & HA_NOSAME) ||
+ (ha_option & HA_ANY_INDEX_MAY_BE_UNIQUE))
+ set_if_bigger(share->max_unique_length,keyinfo->key_length);
+ }
+ if (primary_key < MAX_KEY &&
+ (share->keys_in_use.is_set(primary_key)))
+ {
+ share->primary_key= primary_key;
+ /*
+ If we are using an integer as the primary key then allow the user to
+ refer to it as '_rowid'
+ */
+ if (share->key_info[primary_key].user_defined_key_parts == 1)
+ {
+ Field *field= share->key_info[primary_key].key_part[0].field;
+ if (field && field->result_type() == INT_RESULT)
+ {
+ /* note that fieldnr here (and rowid_field_offset) starts from 1 */
+ share->rowid_field_offset= (share->key_info[primary_key].key_part[0].
+ fieldnr);
+ }
+ }
+ }
+ else
+ share->primary_key = MAX_KEY; // we do not have a primary key
+ }
+ else
+ share->primary_key= MAX_KEY;
+ if (new_field_pack_flag <= 1)
+ {
+ /* Old file format with default as not null */
+ uint null_length= (share->null_fields+7)/8;
+ bfill(share->default_values + (null_flags - (uchar*) record),
+ null_length, 255);
+ }
+
+ if (options)
+ {
+ DBUG_ASSERT(options_len);
+ if (engine_table_options_frm_read(options, options_len, share))
+ goto err;
+ }
+ if (parse_engine_table_options(thd, handler_file->partition_ht(), share))
+ goto err;
+
+ if (share->found_next_number_field)
+ {
+ reg_field= *share->found_next_number_field;
+ if ((int) (share->next_number_index= (uint)
+ find_ref_key(share->key_info, keys,
+ share->default_values, reg_field,
+ &share->next_number_key_offset,
+ &share->next_number_keypart)) < 0)
+ goto err; // Wrong field definition
+ reg_field->flags |= AUTO_INCREMENT_FLAG;
+ }
+
+ if (share->blob_fields)
+ {
+ Field **ptr;
+ uint k, *save;
+
+ /* Store offsets to blob fields to find them fast */
+ if (!(share->blob_field= save=
+ (uint*) alloc_root(&share->mem_root,
+ (uint) (share->blob_fields* sizeof(uint)))))
+ goto err;
+ for (k=0, ptr= share->field ; *ptr ; ptr++, k++)
+ {
+ if ((*ptr)->flags & BLOB_FLAG)
+ (*save++)= k;
+ }
+ }
+
+ /*
+ the correct null_bytes can now be set, since bitfields have been taken
+ into account
+ */
+ share->null_bytes= (null_pos - (uchar*) null_flags +
+ (null_bit_pos + 7) / 8);
+ share->last_null_bit_pos= null_bit_pos;
+ share->null_bytes_for_compare= null_bits_are_used ? share->null_bytes : 0;
+ share->can_cmp_whole_record= (share->blob_fields == 0 &&
+ share->varchar_fields == 0);
+
+ share->column_bitmap_size= bitmap_buffer_size(share->fields);
+
+ if (!(bitmaps= (my_bitmap_map*) alloc_root(&share->mem_root,
+ share->column_bitmap_size)))
+ goto err;
+ my_bitmap_init(&share->all_set, bitmaps, share->fields, FALSE);
+ bitmap_set_all(&share->all_set);
+
+ delete handler_file;
+#ifndef DBUG_OFF
+ if (use_hash)
+ (void) my_hash_check(&share->name_hash);
+#endif
+
+ share->db_plugin= se_plugin;
+ share->error= OPEN_FRM_OK;
+ thd->status_var.opened_shares++;
+ *root_ptr= old_root;
+ DBUG_RETURN(0);
+
+ err:
+ share->error= OPEN_FRM_CORRUPTED;
+ share->open_errno= my_errno;
+ delete handler_file;
+ plugin_unlock(0, se_plugin);
+ my_hash_free(&share->name_hash);
+
+ if (!thd->is_error())
+ open_table_error(share, OPEN_FRM_CORRUPTED, share->open_errno);
+
+ *root_ptr= old_root;
+ DBUG_RETURN(HA_ERR_NOT_A_TABLE);
+}
+
+
+static bool sql_unusable_for_discovery(THD *thd, handlerton *engine,
+ const char *sql)
+{
+ LEX *lex= thd->lex;
+ HA_CREATE_INFO *create_info= &lex->create_info;
+
+ // ... not CREATE TABLE
+ if (lex->sql_command != SQLCOM_CREATE_TABLE)
+ return 1;
+ // ... create like
+ if (create_info->options & HA_LEX_CREATE_TABLE_LIKE)
+ return 1;
+ // ... create select
+ if (lex->select_lex.item_list.elements)
+ return 1;
+ // ... temporary
+ if (create_info->tmp_table())
+ return 1;
+ // ... if exists
+ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ return 1;
+
+ // XXX error out or rather ignore the following:
+ // ... partitioning
+ if (lex->part_info)
+ return 1;
+ // ... union
+ if (create_info->used_fields & HA_CREATE_USED_UNION)
+ return 1;
+ // ... index/data directory
+ if (create_info->data_file_name || create_info->index_file_name)
+ return 1;
+ // ... engine
+ if (create_info->db_type && create_info->db_type != engine)
+ return 1;
+
+ return 0;
+}
+
+int TABLE_SHARE::init_from_sql_statement_string(THD *thd, bool write,
+ const char *sql, size_t sql_length)
+{
+ ulonglong saved_mode= thd->variables.sql_mode;
+ CHARSET_INFO *old_cs= thd->variables.character_set_client;
+ Parser_state parser_state;
+ bool error;
+ char *sql_copy;
+ handler *file;
+ LEX *old_lex;
+ Query_arena *arena, backup;
+ LEX tmp_lex;
+ KEY *unused1;
+ uint unused2;
+ handlerton *hton= plugin_hton(db_plugin);
+ LEX_CUSTRING frm= {0,0};
+
+ DBUG_ENTER("TABLE_SHARE::init_from_sql_statement_string");
+
+ /*
+ Ouch. Parser may *change* the string it's working on.
+ Currently (2013-02-26) it is used to permanently disable
+ conditional comments.
+ Anyway, let's copy the caller's string...
+ */
+ if (!(sql_copy= thd->strmake(sql, sql_length)))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+ if (parser_state.init(thd, sql_copy, sql_length))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+ thd->variables.sql_mode= MODE_NO_ENGINE_SUBSTITUTION | MODE_NO_DIR_IN_CREATE;
+ thd->variables.character_set_client= system_charset_info;
+ tmp_disable_binlog(thd);
+ old_lex= thd->lex;
+ thd->lex= &tmp_lex;
+
+ arena= thd->stmt_arena;
+ if (arena->is_conventional())
+ arena= 0;
+ else
+ thd->set_n_backup_active_arena(arena, &backup);
+
+ lex_start(thd);
+
+ if ((error= parse_sql(thd, & parser_state, NULL) ||
+ sql_unusable_for_discovery(thd, hton, sql_copy)))
+ goto ret;
+
+ thd->lex->create_info.db_type= hton;
+
+ if (tabledef_version.str)
+ thd->lex->create_info.tabledef_version= tabledef_version;
+
+ promote_first_timestamp_column(&thd->lex->alter_info.create_list);
+ file= mysql_create_frm_image(thd, db.str, table_name.str,
+ &thd->lex->create_info, &thd->lex->alter_info,
+ C_ORDINARY_CREATE, &unused1, &unused2, &frm);
+ error|= file == 0;
+ delete file;
+
+ if (frm.str)
+ {
+ option_list= 0; // cleanup existing options ...
+ option_struct= 0; // ... if it's an assisted discovery
+ error= init_from_binary_frm_image(thd, write, frm.str, frm.length);
+ }
+
+ret:
+ my_free(const_cast<uchar*>(frm.str));
+ lex_end(thd->lex);
+ thd->lex= old_lex;
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ reenable_binlog(thd);
+ thd->variables.sql_mode= saved_mode;
+ thd->variables.character_set_client= old_cs;
+ if (thd->is_error() || error)
+ {
+ thd->clear_error();
+ my_error(ER_SQL_DISCOVER_ERROR, MYF(0),
+ plugin_name(db_plugin)->str, db.str, table_name.str,
+ sql_copy);
+ DBUG_RETURN(HA_ERR_GENERIC);
+ }
+ DBUG_RETURN(0);
+}
+
+bool TABLE_SHARE::write_frm_image(const uchar *frm, size_t len)
+{
+ return writefrm(normalized_path.str, db.str, table_name.str, false, frm, len);
+}
+
+
+bool TABLE_SHARE::read_frm_image(const uchar **frm, size_t *len)
+{
+ if (IF_PARTITIONING(partition_info_str, 0)) // cannot discover a partition
+ {
+ DBUG_ASSERT(db_type()->discover_table == 0);
+ return 1;
+ }
+
+ if (frm_image)
+ {
+ *frm= frm_image->str;
+ *len= frm_image->length;
+ frm_image->str= 0; // pass the ownership to the caller
+ frm_image= 0;
+ return 0;
+ }
+ return readfrm(normalized_path.str, frm, len);
+}
+
+
+void TABLE_SHARE::free_frm_image(const uchar *frm)
+{
+ if (frm)
+ my_free(const_cast<uchar*>(frm));
+}
+
+
+/*
+ @brief
+ Clear GET_FIXED_FIELDS_FLAG in all fields of a table
+
+ @param
+ table The table for whose fields the flags are to be cleared
+
+ @note
+ This routine is used for error handling purposes.
+
+ @return
+ none
+*/
+
+static void clear_field_flag(TABLE *table)
+{
+ Field **ptr;
+ DBUG_ENTER("clear_field_flag");
+
+ for (ptr= table->field; *ptr; ptr++)
+ (*ptr)->flags&= (~GET_FIXED_FIELDS_FLAG);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ @brief
+ Perform semantic analysis of the defining expression for a virtual column
+
+ @param
+ thd The thread object
+ @param
+ table The table containing the virtual column
+ @param
+ vcol_field The virtual field whose defining expression is to be analyzed
+
+ @details
+ The function performs semantic analysis of the defining expression for
+ the virtual column vcol_field. The expression is used to compute the
+ values of this column.
+
+ @note
+ The function exploits the fact that the fix_fields method sets the flag
+ GET_FIXED_FIELDS_FLAG for all fields in the item tree.
+ This flag must always be unset before returning from this function
+ since it is used for other purposes as well.
+
+ @retval
+ TRUE An error occurred, something was wrong with the function
+ @retval
+ FALSE Otherwise
+*/
+
+bool fix_vcol_expr(THD *thd,
+ TABLE *table,
+ Field *vcol_field)
+{
+ Virtual_column_info *vcol_info= vcol_field->vcol_info;
+ Item* func_expr= vcol_info->expr_item;
+ bool result= TRUE;
+ TABLE_LIST tables;
+ int error= 0;
+ const char *save_where;
+ Field **ptr, *field;
+ enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
+ DBUG_ASSERT(func_expr);
+ DBUG_ENTER("fix_vcol_expr");
+
+ thd->mark_used_columns= MARK_COLUMNS_NONE;
+
+ save_where= thd->where;
+ thd->where= "virtual column function";
+
+ /* Fix fields referenced to by the virtual column function */
+ if (!func_expr->fixed)
+ error= func_expr->fix_fields(thd, &vcol_info->expr_item);
+ /* fix_fields could change the expression */
+ func_expr= vcol_info->expr_item;
+ /* Number of columns will be checked later */
+
+ if (unlikely(error))
+ {
+ DBUG_PRINT("info",
+ ("Field in virtual column expression does not belong to the table"));
+ goto end;
+ }
+ thd->where= save_where;
+ if (unlikely(func_expr->result_type() == ROW_RESULT))
+ {
+ my_error(ER_ROW_EXPR_FOR_VCOL, MYF(0));
+ goto end;
+ }
+#ifdef PARANOID
+ /*
+ Walk through the Item tree checking if all items are valid
+ to be part of the virtual column
+ */
+ error= func_expr->walk(&Item::check_vcol_func_processor, 0, NULL);
+ if (error)
+ {
+ my_error(ER_VIRTUAL_COLUMN_FUNCTION_IS_NOT_ALLOWED, MYF(0), field_name);
+ goto end;
+ }
+#endif
+ if (unlikely(func_expr->const_item()))
+ {
+ my_error(ER_CONST_EXPR_IN_VCOL, MYF(0));
+ goto end;
+ }
+ /* Ensure that this virtual column is not based on another virtual field. */
+ ptr= table->field;
+ while ((field= *(ptr++)))
+ {
+ if ((field->flags & GET_FIXED_FIELDS_FLAG) &&
+ (field->vcol_info))
+ {
+ my_error(ER_VCOL_BASED_ON_VCOL, MYF(0));
+ goto end;
+ }
+ }
+ result= FALSE;
+
+end:
+
+ /* Clear GET_FIXED_FIELDS_FLAG for the fields of the table */
+ clear_field_flag(table);
+
+ table->get_fields_in_item_tree= FALSE;
+ thd->mark_used_columns= save_mark_used_columns;
+ table->map= 0; //Restore old value
+
+ DBUG_RETURN(result);
+}
+
+/*
+ @brief
+ Unpack the definition of a virtual column from its linear representation
+
+ @param
+ thd The thread object
+ @param
+ mem_root The mem_root object where to allocated memory
+ @param
+ table The table containing the virtual column
+ @param
+ field The field for the virtual
+ @param
+ vcol_expr The string representation of the defining expression
+ @param[out]
+ error_reported The flag to inform the caller that no other error
+ messages are to be generated
+
+ @details
+ The function takes string representation 'vcol_expr' of the defining
+ expression for the virtual field 'field' of the table 'table' and
+ parses it, building an item object for it. The pointer to this item is
+ placed into in field->vcol_info.expr_item. After this the function performs
+ semantic analysis of the item by calling the the function fix_vcol_expr.
+ Since the defining expression is part of the table definition the item for
+ it is created in table->memroot within the special arena TABLE::expr_arena.
+
+ @note
+ Before passing 'vcol_expr" to the parser the function embraces it in
+ parenthesis and prepands it a special keyword.
+
+ @retval
+ FALSE If a success
+ @retval
+ TRUE Otherwise
+*/
+bool unpack_vcol_info_from_frm(THD *thd,
+ MEM_ROOT *mem_root,
+ TABLE *table,
+ Field *field,
+ LEX_STRING *vcol_expr,
+ bool *error_reported)
+{
+ bool rc;
+ char *vcol_expr_str;
+ int str_len;
+ CHARSET_INFO *old_character_set_client;
+ Query_arena *backup_stmt_arena_ptr;
+ Query_arena backup_arena;
+ Query_arena *vcol_arena= 0;
+ Parser_state parser_state;
+ LEX *old_lex= thd->lex;
+ LEX lex;
+ DBUG_ENTER("unpack_vcol_info_from_frm");
+ DBUG_ASSERT(vcol_expr);
+
+ old_character_set_client= thd->variables.character_set_client;
+ backup_stmt_arena_ptr= thd->stmt_arena;
+
+ /*
+ Step 1: Construct the input string for the parser.
+ The string to be parsed has to be of the following format:
+ "PARSE_VCOL_EXPR (<expr_string_from_frm>)".
+ */
+
+ if (!(vcol_expr_str= (char*) alloc_root(mem_root,
+ vcol_expr->length +
+ parse_vcol_keyword.length + 3)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ memcpy(vcol_expr_str,
+ (char*) parse_vcol_keyword.str,
+ parse_vcol_keyword.length);
+ str_len= parse_vcol_keyword.length;
+ memcpy(vcol_expr_str + str_len, "(", 1);
+ str_len++;
+ memcpy(vcol_expr_str + str_len,
+ (char*) vcol_expr->str,
+ vcol_expr->length);
+ str_len+= vcol_expr->length;
+ memcpy(vcol_expr_str + str_len, ")", 1);
+ str_len++;
+ memcpy(vcol_expr_str + str_len, "\0", 1);
+ str_len++;
+
+ if (parser_state.init(thd, vcol_expr_str, str_len))
+ goto err;
+
+ /*
+ Step 2: Setup thd for parsing.
+ */
+ vcol_arena= table->expr_arena;
+ if (!vcol_arena)
+ {
+ /*
+ We need to use CONVENTIONAL_EXECUTION here to ensure that
+ any new items created by fix_fields() are not reverted.
+ */
+ Query_arena expr_arena(mem_root,
+ Query_arena::STMT_CONVENTIONAL_EXECUTION);
+ if (!(vcol_arena= (Query_arena *) alloc_root(mem_root,
+ sizeof(Query_arena))))
+ goto err;
+ *vcol_arena= expr_arena;
+ table->expr_arena= vcol_arena;
+ }
+ thd->set_n_backup_active_arena(vcol_arena, &backup_arena);
+ thd->stmt_arena= vcol_arena;
+
+ if (init_lex_with_single_table(thd, table, &lex))
+ goto err;
+
+ thd->lex->parse_vcol_expr= TRUE;
+
+ /*
+ Step 3: Use the parser to build an Item object from vcol_expr_str.
+ */
+ if (parse_sql(thd, &parser_state, NULL))
+ {
+ goto err;
+ }
+ /* From now on use vcol_info generated by the parser. */
+ field->vcol_info= thd->lex->vcol_info;
+
+ /* Validate the Item tree. */
+ if (fix_vcol_expr(thd, table, field))
+ {
+ *error_reported= TRUE;
+ field->vcol_info= 0;
+ goto err;
+ }
+ rc= FALSE;
+ goto end;
+
+err:
+ rc= TRUE;
+ thd->free_items();
+end:
+ thd->stmt_arena= backup_stmt_arena_ptr;
+ if (vcol_arena)
+ thd->restore_active_arena(vcol_arena, &backup_arena);
+ end_lex_with_single_table(thd, table, old_lex);
+ thd->variables.character_set_client= old_character_set_client;
+
+ DBUG_RETURN(rc);
+}
+
+/*
+ Read data from a binary .frm file from MySQL 3.23 - 5.0 into TABLE_SHARE
+*/
+
+/*
+ Open a table based on a TABLE_SHARE
+
+ SYNOPSIS
+ open_table_from_share()
+ thd Thread handler
+ share Table definition
+ alias Alias for table
+ db_stat open flags (for example HA_OPEN_KEYFILE|
+ HA_OPEN_RNDFILE..) can be 0 (example in
+ ha_example_table)
+ prgflag READ_ALL etc..
+ ha_open_flags HA_OPEN_ABORT_IF_LOCKED etc..
+ outparam result table
+
+ RETURN VALUES
+ 0 ok
+ 1 Error (see open_table_error)
+ 2 Error (see open_table_error)
+ 3 Wrong data in .frm file
+ 4 Error (see open_table_error)
+ 5 Error (see open_table_error: charset unavailable)
+ 7 Table definition has changed in engine
+*/
+
+enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share,
+ const char *alias, uint db_stat, uint prgflag,
+ uint ha_open_flags, TABLE *outparam,
+ bool is_create_table)
+{
+ enum open_frm_error error;
+ uint records, i, bitmap_size;
+ bool error_reported= FALSE;
+ uchar *record, *bitmaps;
+ Field **field_ptr, **UNINIT_VAR(vfield_ptr), **UNINIT_VAR(dfield_ptr);
+ uint8 save_context_analysis_only= thd->lex->context_analysis_only;
+ DBUG_ENTER("open_table_from_share");
+ DBUG_PRINT("enter",("name: '%s.%s' form: 0x%lx", share->db.str,
+ share->table_name.str, (long) outparam));
+
+ thd->lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_VIEW; // not a view
+
+ error= OPEN_FRM_ERROR_ALREADY_ISSUED; // for OOM errors below
+ bzero((char*) outparam, sizeof(*outparam));
+ outparam->in_use= thd;
+ outparam->s= share;
+ outparam->db_stat= db_stat;
+ outparam->write_row_record= NULL;
+
+ if (share->incompatible_version &&
+ !(ha_open_flags & (HA_OPEN_FOR_ALTER | HA_OPEN_FOR_REPAIR)))
+ {
+ /* one needs to run mysql_upgrade on the table */
+ error= OPEN_FRM_NEEDS_REBUILD;
+ goto err;
+ }
+ init_sql_alloc(&outparam->mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0));
+
+ if (outparam->alias.copy(alias, strlen(alias), table_alias_charset))
+ goto err;
+ outparam->quick_keys.init();
+ outparam->covering_keys.init();
+ outparam->merge_keys.init();
+ outparam->keys_in_use_for_query.init();
+
+ /* Allocate handler */
+ outparam->file= 0;
+ if (!(prgflag & OPEN_FRM_FILE_ONLY))
+ {
+ if (!(outparam->file= get_new_handler(share, &outparam->mem_root,
+ share->db_type())))
+ goto err;
+
+ if (outparam->file->set_ha_share_ref(&share->ha_share))
+ goto err;
+ }
+ else
+ {
+ DBUG_ASSERT(!db_stat);
+ }
+
+ outparam->reginfo.lock_type= TL_UNLOCK;
+ outparam->current_lock= F_UNLCK;
+ records=0;
+ if ((db_stat & HA_OPEN_KEYFILE) || (prgflag & DELAYED_OPEN))
+ records=1;
+ if (prgflag & (READ_ALL+EXTRA_RECORD))
+ records++;
+
+ if (!(record= (uchar*) alloc_root(&outparam->mem_root,
+ share->rec_buff_length * records)))
+ goto err; /* purecov: inspected */
+
+ if (records == 0)
+ {
+ /* We are probably in hard repair, and the buffers should not be used */
+ outparam->record[0]= outparam->record[1]= share->default_values;
+ }
+ else
+ {
+ outparam->record[0]= record;
+ if (records > 1)
+ outparam->record[1]= record+ share->rec_buff_length;
+ else
+ outparam->record[1]= outparam->record[0]; // Safety
+ }
+
+#ifdef HAVE_valgrind
+ /*
+ We need this because when we read var-length rows, we are not updating
+ bytes after end of varchar
+ */
+ if (records > 1)
+ {
+ memcpy(outparam->record[0], share->default_values, share->rec_buff_length);
+ memcpy(outparam->record[1], share->default_values, share->null_bytes);
+ if (records > 2)
+ memcpy(outparam->record[1], share->default_values,
+ share->rec_buff_length);
+ }
+#endif
+
+ if (!(field_ptr = (Field **) alloc_root(&outparam->mem_root,
+ (uint) ((share->fields+1)*
+ sizeof(Field*)))))
+ goto err; /* purecov: inspected */
+
+ outparam->field= field_ptr;
+
+ record= (uchar*) outparam->record[0]-1; /* Fieldstart = 1 */
+ if (share->null_field_first)
+ outparam->null_flags= (uchar*) record+1;
+ else
+ outparam->null_flags= (uchar*) (record+ 1+ share->reclength -
+ share->null_bytes);
+
+ /* Setup copy of fields from share, but use the right alias and record */
+ for (i=0 ; i < share->fields; i++, field_ptr++)
+ {
+ if (!((*field_ptr)= share->field[i]->clone(&outparam->mem_root, outparam)))
+ goto err;
+ }
+ (*field_ptr)= 0; // End marker
+
+ if (share->found_next_number_field)
+ outparam->found_next_number_field=
+ outparam->field[(uint) (share->found_next_number_field - share->field)];
+
+ /* Fix key->name and key_part->field */
+ if (share->key_parts)
+ {
+ KEY *key_info, *key_info_end;
+ KEY_PART_INFO *key_part;
+ uint n_length;
+ n_length= share->keys*sizeof(KEY) + share->ext_key_parts*sizeof(KEY_PART_INFO);
+ if (!(key_info= (KEY*) alloc_root(&outparam->mem_root, n_length)))
+ goto err;
+ outparam->key_info= key_info;
+ key_part= (reinterpret_cast<KEY_PART_INFO*>(key_info+share->keys));
+
+ memcpy(key_info, share->key_info, sizeof(*key_info)*share->keys);
+ memcpy(key_part, share->key_info[0].key_part, (sizeof(*key_part) *
+ share->ext_key_parts));
+
+ for (key_info_end= key_info + share->keys ;
+ key_info < key_info_end ;
+ key_info++)
+ {
+ KEY_PART_INFO *key_part_end;
+
+ key_info->table= outparam;
+ key_info->key_part= key_part;
+
+ key_part_end= key_part + (share->use_ext_keys ? key_info->ext_key_parts :
+ key_info->user_defined_key_parts) ;
+ for ( ; key_part < key_part_end; key_part++)
+ {
+ Field *field= key_part->field= outparam->field[key_part->fieldnr - 1];
+
+ if (field->key_length() != key_part->length &&
+ !(field->flags & BLOB_FLAG))
+ {
+ /*
+ We are using only a prefix of the column as a key:
+ Create a new field for the key part that matches the index
+ */
+ field= key_part->field=field->new_field(&outparam->mem_root,
+ outparam, 0);
+ field->field_length= key_part->length;
+ }
+ }
+ if (!share->use_ext_keys)
+ key_part+= key_info->ext_key_parts - key_info->user_defined_key_parts;
+ }
+ }
+
+ /*
+ Process virtual and default columns, if any.
+ */
+ if (share->vfields)
+ {
+ if (!(vfield_ptr = (Field **) alloc_root(&outparam->mem_root,
+ (uint) ((share->vfields+1)*
+ sizeof(Field*)))))
+ goto err;
+
+ outparam->vfield= vfield_ptr;
+ }
+
+ if (share->default_fields)
+ {
+ if (!(dfield_ptr = (Field **) alloc_root(&outparam->mem_root,
+ (uint) ((share->default_fields+1)*
+ sizeof(Field*)))))
+ goto err;
+
+ outparam->default_field= dfield_ptr;
+ }
+
+ if (share->vfields || share->default_fields)
+ {
+ /* Reuse the same loop both for virtual and default fields. */
+ for (field_ptr= outparam->field; *field_ptr; field_ptr++)
+ {
+ if (share->vfields && (*field_ptr)->vcol_info)
+ {
+ if (unpack_vcol_info_from_frm(thd,
+ &outparam->mem_root,
+ outparam,
+ *field_ptr,
+ &(*field_ptr)->vcol_info->expr_str,
+ &error_reported))
+ {
+ error= OPEN_FRM_CORRUPTED;
+ goto err;
+ }
+ *(vfield_ptr++)= *field_ptr;
+ }
+ if (share->default_fields &&
+ ((*field_ptr)->has_insert_default_function() ||
+ (*field_ptr)->has_update_default_function()))
+ *(dfield_ptr++)= *field_ptr;
+ }
+ if (share->vfields)
+ *vfield_ptr= 0; // End marker
+ if (share->default_fields)
+ *dfield_ptr= 0; // End marker
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (share->partition_info_str_len && outparam->file)
+ {
+ /*
+ In this execution we must avoid calling thd->change_item_tree since
+ we might release memory before statement is completed. We do this
+ by changing to a new statement arena. As part of this arena we also
+ set the memory root to be the memory root of the table since we
+ call the parser and fix_fields which both can allocate memory for
+ item objects. We keep the arena to ensure that we can release the
+ free_list when closing the table object.
+ SEE Bug #21658
+ */
+
+ Query_arena *backup_stmt_arena_ptr= thd->stmt_arena;
+ Query_arena backup_arena;
+ Query_arena part_func_arena(&outparam->mem_root,
+ Query_arena::STMT_INITIALIZED);
+ thd->set_n_backup_active_arena(&part_func_arena, &backup_arena);
+ thd->stmt_arena= &part_func_arena;
+ bool tmp;
+ bool work_part_info_used;
+
+ tmp= mysql_unpack_partition(thd, share->partition_info_str,
+ share->partition_info_str_len,
+ outparam, is_create_table,
+ plugin_hton(share->default_part_plugin),
+ &work_part_info_used);
+ if (tmp)
+ {
+ thd->stmt_arena= backup_stmt_arena_ptr;
+ thd->restore_active_arena(&part_func_arena, &backup_arena);
+ goto partititon_err;
+ }
+ outparam->part_info->is_auto_partitioned= share->auto_partitioned;
+ DBUG_PRINT("info", ("autopartitioned: %u", share->auto_partitioned));
+ /*
+ We should perform the fix_partition_func in either local or
+ caller's arena depending on work_part_info_used value.
+ */
+ if (!work_part_info_used)
+ tmp= fix_partition_func(thd, outparam, is_create_table);
+ thd->stmt_arena= backup_stmt_arena_ptr;
+ thd->restore_active_arena(&part_func_arena, &backup_arena);
+ if (!tmp)
+ {
+ if (work_part_info_used)
+ tmp= fix_partition_func(thd, outparam, is_create_table);
+ }
+ outparam->part_info->item_free_list= part_func_arena.free_list;
+partititon_err:
+ if (tmp)
+ {
+ if (is_create_table)
+ {
+ /*
+ During CREATE/ALTER TABLE it is ok to receive errors here.
+ It is not ok if it happens during the opening of an frm
+ file as part of a normal query.
+ */
+ error_reported= TRUE;
+ }
+ goto err;
+ }
+ }
+#endif
+
+ /* Check virtual columns against table's storage engine. */
+ if (share->vfields &&
+ (outparam->file &&
+ !(outparam->file->ha_table_flags() & HA_CAN_VIRTUAL_COLUMNS)))
+ {
+ my_error(ER_UNSUPPORTED_ENGINE_FOR_VIRTUAL_COLUMNS, MYF(0),
+ plugin_name(share->db_plugin)->str);
+ error_reported= TRUE;
+ goto err;
+ }
+
+ /* Allocate bitmaps */
+
+ bitmap_size= share->column_bitmap_size;
+ if (!(bitmaps= (uchar*) alloc_root(&outparam->mem_root, bitmap_size*6)))
+ goto err;
+ my_bitmap_init(&outparam->def_read_set,
+ (my_bitmap_map*) bitmaps, share->fields, FALSE);
+ my_bitmap_init(&outparam->def_write_set,
+ (my_bitmap_map*) (bitmaps+bitmap_size), share->fields, FALSE);
+ my_bitmap_init(&outparam->def_vcol_set,
+ (my_bitmap_map*) (bitmaps+bitmap_size*2), share->fields, FALSE);
+ my_bitmap_init(&outparam->tmp_set,
+ (my_bitmap_map*) (bitmaps+bitmap_size*3), share->fields, FALSE);
+ my_bitmap_init(&outparam->eq_join_set,
+ (my_bitmap_map*) (bitmaps+bitmap_size*4), share->fields, FALSE);
+ my_bitmap_init(&outparam->cond_set,
+ (my_bitmap_map*) (bitmaps+bitmap_size*5), share->fields, FALSE);
+ outparam->default_column_bitmaps();
+
+ outparam->cond_selectivity= 1.0;
+
+ /* The table struct is now initialized; Open the table */
+ if (db_stat)
+ {
+ if (db_stat & HA_OPEN_TEMPORARY)
+ ha_open_flags|= HA_OPEN_TMP_TABLE;
+ else if ((db_stat & HA_WAIT_IF_LOCKED) ||
+ (specialflag & SPECIAL_WAIT_IF_LOCKED))
+ ha_open_flags|= HA_OPEN_WAIT_IF_LOCKED;
+ else if (db_stat & (HA_ABORT_IF_LOCKED | HA_GET_INFO))
+ ha_open_flags|= HA_OPEN_ABORT_IF_LOCKED;
+ else
+ ha_open_flags|= HA_OPEN_IGNORE_IF_LOCKED;
+
+ int ha_err= outparam->file->ha_open(outparam, share->normalized_path.str,
+ (db_stat & HA_READ_ONLY ? O_RDONLY : O_RDWR),
+ ha_open_flags);
+ if (ha_err)
+ {
+ share->open_errno= ha_err;
+ /* Set a flag if the table is crashed and it can be auto. repaired */
+ share->crashed= (outparam->file->auto_repair(ha_err) &&
+ !(ha_open_flags & HA_OPEN_FOR_REPAIR));
+ outparam->file->print_error(ha_err, MYF(0));
+ error_reported= TRUE;
+
+ if (ha_err == HA_ERR_TABLE_DEF_CHANGED)
+ error= OPEN_FRM_DISCOVER;
+
+ /*
+ We're here, because .frm file was successfully opened.
+
+ But if the table doesn't exist in the engine and the engine
+ supports discovery, we force rediscover to discover
+ the fact that table doesn't in fact exist and remove
+ the stray .frm file.
+ */
+ if (share->db_type()->discover_table &&
+ (ha_err == ENOENT || ha_err == HA_ERR_NO_SUCH_TABLE))
+ error= OPEN_FRM_DISCOVER;
+
+ goto err;
+ }
+ }
+
+#if defined(HAVE_valgrind) && !defined(DBUG_OFF)
+ bzero((char*) bitmaps, bitmap_size*3);
+#endif
+
+ if (share->table_category == TABLE_CATEGORY_LOG)
+ {
+ outparam->no_replicate= TRUE;
+ }
+ else if (outparam->file)
+ {
+ handler::Table_flags flags= outparam->file->ha_table_flags();
+ outparam->no_replicate= ! MY_TEST(flags & (HA_BINLOG_STMT_CAPABLE
+ | HA_BINLOG_ROW_CAPABLE))
+ || MY_TEST(flags & HA_HAS_OWN_BINLOGGING);
+ }
+ else
+ {
+ outparam->no_replicate= FALSE;
+ }
+
+ /* Increment the opened_tables counter, only when open flags set. */
+ if (db_stat)
+ thd->status_var.opened_tables++;
+
+ thd->lex->context_analysis_only= save_context_analysis_only;
+ DBUG_RETURN (OPEN_FRM_OK);
+
+ err:
+ if (! error_reported)
+ open_table_error(share, error, my_errno);
+ delete outparam->file;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (outparam->part_info)
+ free_items(outparam->part_info->item_free_list);
+#endif
+ outparam->file= 0; // For easier error checking
+ outparam->db_stat=0;
+ thd->lex->context_analysis_only= save_context_analysis_only;
+ free_root(&outparam->mem_root, MYF(0)); // Safe to call on bzero'd root
+ outparam->alias.free();
+ DBUG_RETURN (error);
+}
+
+
+/*
+ Free information allocated by openfrm
+
+ SYNOPSIS
+ closefrm()
+ table TABLE object to free
+ free_share Is 1 if we also want to free table_share
+*/
+
+int closefrm(register TABLE *table, bool free_share)
+{
+ int error=0;
+ DBUG_ENTER("closefrm");
+ DBUG_PRINT("enter", ("table: 0x%lx", (long) table));
+
+ if (table->db_stat)
+ {
+ if (table->s->deleting)
+ table->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
+ error=table->file->ha_close();
+ }
+ table->alias.free();
+ if (table->expr_arena)
+ table->expr_arena->free_items();
+ if (table->field)
+ {
+ for (Field **ptr=table->field ; *ptr ; ptr++)
+ {
+ delete *ptr;
+ }
+ table->field= 0;
+ }
+ delete table->file;
+ table->file= 0; /* For easier errorchecking */
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (table->part_info)
+ {
+ /* Allocated through table->mem_root, freed below */
+ free_items(table->part_info->item_free_list);
+ table->part_info->item_free_list= 0;
+ table->part_info= 0;
+ }
+#endif
+ if (free_share)
+ {
+ if (table->s->tmp_table == NO_TMP_TABLE)
+ tdc_release_share(table->s);
+ else
+ free_table_share(table->s);
+ }
+ free_root(&table->mem_root, MYF(0));
+ DBUG_RETURN(error);
+}
+
+
+/* Deallocate temporary blob storage */
+
+void free_blobs(register TABLE *table)
+{
+ uint *ptr, *end;
+ for (ptr= table->s->blob_field, end=ptr + table->s->blob_fields ;
+ ptr != end ;
+ ptr++)
+ {
+ /*
+ Reduced TABLE objects which are used by row-based replication for
+ type conversion might have some fields missing. Skip freeing BLOB
+ buffers for such missing fields.
+ */
+ if (table->field[*ptr])
+ ((Field_blob*) table->field[*ptr])->free();
+ }
+}
+
+
+/**
+ Reclaim temporary blob storage which is bigger than
+ a threshold.
+
+ @param table A handle to the TABLE object containing blob fields
+ @param size The threshold value.
+
+*/
+
+void free_field_buffers_larger_than(TABLE *table, uint32 size)
+{
+ uint *ptr, *end;
+ for (ptr= table->s->blob_field, end=ptr + table->s->blob_fields ;
+ ptr != end ;
+ ptr++)
+ {
+ Field_blob *blob= (Field_blob*) table->field[*ptr];
+ if (blob->get_field_buffer_size() > size)
+ blob->free();
+ }
+}
+
+/* error message when opening a form file */
+
+void open_table_error(TABLE_SHARE *share, enum open_frm_error error,
+ int db_errno)
+{
+ char buff[FN_REFLEN];
+ const myf errortype= ME_ERROR+ME_WAITTANG; // Write fatals error to log
+ DBUG_ENTER("open_table_error");
+ DBUG_PRINT("info", ("error: %d db_errno: %d", error, db_errno));
+
+ switch (error) {
+ case OPEN_FRM_OPEN_ERROR:
+ /*
+ Test if file didn't exists. We have to also test for EINVAL as this
+ may happen on windows when opening a file with a not legal file name
+ */
+ if (db_errno == ENOENT || db_errno == EINVAL)
+ my_error(ER_NO_SUCH_TABLE, MYF(0), share->db.str, share->table_name.str);
+ else
+ {
+ strxmov(buff, share->normalized_path.str, reg_ext, NullS);
+ my_error((db_errno == EMFILE) ? ER_CANT_OPEN_FILE : ER_FILE_NOT_FOUND,
+ errortype, buff, db_errno);
+ }
+ break;
+ case OPEN_FRM_OK:
+ DBUG_ASSERT(0); // open_table_error() is never called for this one
+ break;
+ case OPEN_FRM_ERROR_ALREADY_ISSUED:
+ break;
+ case OPEN_FRM_NOT_A_VIEW:
+ my_error(ER_WRONG_OBJECT, MYF(0), share->db.str,
+ share->table_name.str, "VIEW");
+ break;
+ case OPEN_FRM_NOT_A_TABLE:
+ my_error(ER_WRONG_OBJECT, MYF(0), share->db.str,
+ share->table_name.str, "TABLE");
+ break;
+ case OPEN_FRM_DISCOVER:
+ DBUG_ASSERT(0); // open_table_error() is never called for this one
+ break;
+ case OPEN_FRM_CORRUPTED:
+ strxmov(buff, share->normalized_path.str, reg_ext, NullS);
+ my_error(ER_NOT_FORM_FILE, errortype, buff);
+ break;
+ case OPEN_FRM_READ_ERROR:
+ strxmov(buff, share->normalized_path.str, reg_ext, NullS);
+ my_error(ER_ERROR_ON_READ, errortype, buff, db_errno);
+ break;
+ case OPEN_FRM_NEEDS_REBUILD:
+ strxnmov(buff, sizeof(buff)-1,
+ share->db.str, ".", share->table_name.str, NullS);
+ my_error(ER_TABLE_NEEDS_REBUILD, errortype, buff);
+ break;
+ }
+ DBUG_VOID_RETURN;
+} /* open_table_error */
+
+
+ /*
+ ** fix a str_type to a array type
+ ** typeparts separated with some char. differents types are separated
+ ** with a '\0'
+ */
+
+static void
+fix_type_pointers(const char ***array, TYPELIB *point_to_type, uint types,
+ char **names)
+{
+ char *type_name, *ptr;
+ char chr;
+
+ ptr= *names;
+ while (types--)
+ {
+ point_to_type->name=0;
+ point_to_type->type_names= *array;
+
+ if ((chr= *ptr)) /* Test if empty type */
+ {
+ while ((type_name=strchr(ptr+1,chr)) != NullS)
+ {
+ *((*array)++) = ptr+1;
+ *type_name= '\0'; /* End string */
+ ptr=type_name;
+ }
+ ptr+=2; /* Skip end mark and last 0 */
+ }
+ else
+ ptr++;
+ point_to_type->count= (uint) (*array - point_to_type->type_names);
+ point_to_type++;
+ *((*array)++)= NullS; /* End of type */
+ }
+ *names=ptr; /* Update end */
+ return;
+} /* fix_type_pointers */
+
+
+TYPELIB *typelib(MEM_ROOT *mem_root, List<String> &strings)
+{
+ TYPELIB *result= (TYPELIB*) alloc_root(mem_root, sizeof(TYPELIB));
+ if (!result)
+ return 0;
+ result->count=strings.elements;
+ result->name="";
+ uint nbytes= (sizeof(char*) + sizeof(uint)) * (result->count + 1);
+ if (!(result->type_names= (const char**) alloc_root(mem_root, nbytes)))
+ return 0;
+ result->type_lengths= (uint*) (result->type_names + result->count + 1);
+ List_iterator<String> it(strings);
+ String *tmp;
+ for (uint i=0; (tmp=it++) ; i++)
+ {
+ result->type_names[i]= tmp->ptr();
+ result->type_lengths[i]= tmp->length();
+ }
+ result->type_names[result->count]= 0; // End marker
+ result->type_lengths[result->count]= 0;
+ return result;
+}
+
+
+/*
+ Search after a field with given start & length
+ If an exact field isn't found, return longest field with starts
+ at right position.
+
+ NOTES
+ This is needed because in some .frm fields 'fieldnr' was saved wrong
+
+ RETURN
+ 0 error
+ # field number +1
+*/
+
+static uint find_field(Field **fields, uchar *record, uint start, uint length)
+{
+ Field **field;
+ uint i, pos;
+
+ pos= 0;
+ for (field= fields, i=1 ; *field ; i++,field++)
+ {
+ if ((*field)->offset(record) == start)
+ {
+ if ((*field)->key_length() == length)
+ return (i);
+ if (!pos || fields[pos-1]->pack_length() <
+ (*field)->pack_length())
+ pos= i;
+ }
+ }
+ return (pos);
+}
+
+
+/*
+ Store an SQL quoted string.
+
+ SYNOPSIS
+ append_unescaped()
+ res result String
+ pos string to be quoted
+ length it's length
+
+ NOTE
+ This function works correctly with utf8 or single-byte charset strings.
+ May fail with some multibyte charsets though.
+*/
+
+void append_unescaped(String *res, const char *pos, uint length)
+{
+ const char *end= pos+length;
+ res->append('\'');
+
+ for (; pos != end ; pos++)
+ {
+#if defined(USE_MB) && MYSQL_VERSION_ID < 40100
+ uint mblen;
+ if (use_mb(default_charset_info) &&
+ (mblen= my_ismbchar(default_charset_info, pos, end)))
+ {
+ res->append(pos, mblen);
+ pos+= mblen;
+ continue;
+ }
+#endif
+
+ switch (*pos) {
+ case 0: /* Must be escaped for 'mysql' */
+ res->append('\\');
+ res->append('0');
+ break;
+ case '\n': /* Must be escaped for logs */
+ res->append('\\');
+ res->append('n');
+ break;
+ case '\r':
+ res->append('\\'); /* This gives better readability */
+ res->append('r');
+ break;
+ case '\\':
+ res->append('\\'); /* Because of the sql syntax */
+ res->append('\\');
+ break;
+ case '\'':
+ res->append('\''); /* Because of the sql syntax */
+ res->append('\'');
+ break;
+ default:
+ res->append(*pos);
+ break;
+ }
+ }
+ res->append('\'');
+}
+
+
+void prepare_frm_header(THD *thd, uint reclength, uchar *fileinfo,
+ HA_CREATE_INFO *create_info, uint keys, KEY *key_info)
+{
+ ulong key_comment_total_bytes= 0;
+ uint i;
+ DBUG_ENTER("prepare_frm_header");
+
+ /* Fix this when we have new .frm files; Current limit is 4G rows (TODO) */
+ if (create_info->max_rows > UINT_MAX32)
+ create_info->max_rows= UINT_MAX32;
+ if (create_info->min_rows > UINT_MAX32)
+ create_info->min_rows= UINT_MAX32;
+
+ uint key_length, tmp_key_length, tmp, csid;
+ bzero((char*) fileinfo, FRM_HEADER_SIZE);
+ /* header */
+ fileinfo[0]=(uchar) 254;
+ fileinfo[1]= 1;
+ fileinfo[2]= FRM_VER + 3 + MY_TEST(create_info->varchar);
+
+ fileinfo[3]= (uchar) ha_legacy_type(
+ ha_checktype(thd,ha_legacy_type(create_info->db_type),0,0));
+
+ /*
+ Keep in sync with pack_keys() in unireg.cc
+ For each key:
+ 8 bytes for the key header
+ 9 bytes for each key-part (MAX_REF_PARTS)
+ NAME_LEN bytes for the name
+ 1 byte for the NAMES_SEP_CHAR (before the name)
+ For all keys:
+ 6 bytes for the header
+ 1 byte for the NAMES_SEP_CHAR (after the last name)
+ 9 extra bytes (padding for safety? alignment?)
+ */
+ for (i= 0; i < keys; i++)
+ {
+ DBUG_ASSERT(MY_TEST(key_info[i].flags & HA_USES_COMMENT) ==
+ (key_info[i].comment.length > 0));
+ if (key_info[i].flags & HA_USES_COMMENT)
+ key_comment_total_bytes += 2 + key_info[i].comment.length;
+ }
+
+ key_length= keys * (8 + MAX_REF_PARTS * 9 + NAME_LEN + 1) + 16
+ + key_comment_total_bytes;
+
+ int2store(fileinfo+8,1);
+ tmp_key_length= (key_length < 0xffff) ? key_length : 0xffff;
+ int2store(fileinfo+14,tmp_key_length);
+ int2store(fileinfo+16,reclength);
+ int4store(fileinfo+18,create_info->max_rows);
+ int4store(fileinfo+22,create_info->min_rows);
+ /* fileinfo[26] is set in mysql_create_frm() */
+ fileinfo[27]=2; // Use long pack-fields
+ /* fileinfo[28 & 29] is set to key_info_length in mysql_create_frm() */
+ create_info->table_options|=HA_OPTION_LONG_BLOB_PTR; // Use portable blob pointers
+ int2store(fileinfo+30,create_info->table_options);
+ fileinfo[32]=0; // No filename anymore
+ fileinfo[33]=5; // Mark for 5.0 frm file
+ int4store(fileinfo+34,create_info->avg_row_length);
+ csid= (create_info->default_table_charset ?
+ create_info->default_table_charset->number : 0);
+ fileinfo[38]= (uchar) csid;
+ fileinfo[39]= (uchar) ((uint) create_info->transactional |
+ ((uint) create_info->page_checksum << 2));
+ fileinfo[40]= (uchar) create_info->row_type;
+ /* Bytes 41-46 were for RAID support; now reused for other purposes */
+ fileinfo[41]= (uchar) (csid >> 8);
+ int2store(fileinfo+42, create_info->stats_sample_pages & 0xffff);
+ fileinfo[44]= (uchar) create_info->stats_auto_recalc;
+ fileinfo[45]= 0;
+ fileinfo[46]= 0;
+ int4store(fileinfo+47, key_length);
+ tmp= MYSQL_VERSION_ID; // Store to avoid warning from int4store
+ int4store(fileinfo+51, tmp);
+ int4store(fileinfo+55, create_info->extra_size);
+ /*
+ 59-60 is reserved for extra_rec_buf_length,
+ 61 for default_part_db_type
+ */
+ int2store(fileinfo+62, create_info->key_block_size);
+ DBUG_VOID_RETURN;
+} /* prepare_fileinfo */
+
+
+void update_create_info_from_table(HA_CREATE_INFO *create_info, TABLE *table)
+{
+ TABLE_SHARE *share= table->s;
+ DBUG_ENTER("update_create_info_from_table");
+
+ create_info->max_rows= share->max_rows;
+ create_info->min_rows= share->min_rows;
+ create_info->table_options= share->db_create_options;
+ create_info->avg_row_length= share->avg_row_length;
+ create_info->row_type= share->row_type;
+ create_info->default_table_charset= share->table_charset;
+ create_info->table_charset= 0;
+ create_info->comment= share->comment;
+ create_info->transactional= share->transactional;
+ create_info->page_checksum= share->page_checksum;
+ create_info->option_list= share->option_list;
+
+ DBUG_VOID_RETURN;
+}
+
+int
+rename_file_ext(const char * from,const char * to,const char * ext)
+{
+ char from_b[FN_REFLEN],to_b[FN_REFLEN];
+ (void) strxmov(from_b,from,ext,NullS);
+ (void) strxmov(to_b,to,ext,NullS);
+ return mysql_file_rename(key_file_frm, from_b, to_b, MYF(0));
+}
+
+
+/*
+ Allocate string field in MEM_ROOT and return it as String
+
+ SYNOPSIS
+ get_field()
+ mem MEM_ROOT for allocating
+ field Field for retrieving of string
+ res result String
+
+ RETURN VALUES
+ 1 string is empty
+ 0 all ok
+*/
+
+bool get_field(MEM_ROOT *mem, Field *field, String *res)
+{
+ char buff[MAX_FIELD_WIDTH], *to;
+ String str(buff,sizeof(buff),&my_charset_bin);
+ uint length;
+
+ field->val_str(&str);
+ if (!(length= str.length()))
+ {
+ res->length(0);
+ return 1;
+ }
+ if (!(to= strmake_root(mem, str.ptr(), length)))
+ length= 0; // Safety fix
+ res->set(to, length, field->charset());
+ return 0;
+}
+
+
+/*
+ Allocate string field in MEM_ROOT and return it as NULL-terminated string
+
+ SYNOPSIS
+ get_field()
+ mem MEM_ROOT for allocating
+ field Field for retrieving of string
+
+ RETURN VALUES
+ NullS string is empty
+ # pointer to NULL-terminated string value of field
+*/
+
+char *get_field(MEM_ROOT *mem, Field *field)
+{
+ char buff[MAX_FIELD_WIDTH], *to;
+ String str(buff,sizeof(buff),&my_charset_bin);
+ uint length;
+
+ field->val_str(&str);
+ length= str.length();
+ if (!length || !(to= (char*) alloc_root(mem,length+1)))
+ return NullS;
+ memcpy(to,str.ptr(),(uint) length);
+ to[length]=0;
+ return to;
+}
+
+/*
+ DESCRIPTION
+ given a buffer with a key value, and a map of keyparts
+ that are present in this value, returns the length of the value
+*/
+uint calculate_key_len(TABLE *table, uint key, const uchar *buf,
+ key_part_map keypart_map)
+{
+ /* works only with key prefixes */
+ DBUG_ASSERT(((keypart_map + 1) & keypart_map) == 0);
+
+ KEY *key_info= table->s->key_info+key;
+ KEY_PART_INFO *key_part= key_info->key_part;
+ KEY_PART_INFO *end_key_part= key_part + table->actual_n_key_parts(key_info);
+ uint length= 0;
+
+ while (key_part < end_key_part && keypart_map)
+ {
+ length+= key_part->store_length;
+ keypart_map >>= 1;
+ key_part++;
+ }
+ return length;
+}
+
+#ifndef DBUG_OFF
+/**
+ Verifies that database/table name is in lowercase, when it should be
+
+ This is supposed to be used only inside DBUG_ASSERT()
+*/
+bool ok_for_lower_case_names(const char *name)
+{
+ if (!lower_case_table_names || !name)
+ return true;
+
+ char buf[SAFE_NAME_LEN];
+ strmake_buf(buf, name);
+ my_casedn_str(files_charset_info, buf);
+ return strcmp(name, buf) == 0;
+}
+#endif
+
+/*
+ Check if database name is valid
+
+ SYNPOSIS
+ check_db_name()
+ org_name Name of database
+
+ NOTES
+ If lower_case_table_names is set to 1 then database name is converted
+ to lower case
+
+ RETURN
+ 0 ok
+ 1 error
+*/
+
+bool check_db_name(LEX_STRING *org_name)
+{
+ char *name= org_name->str;
+ uint name_length= org_name->length;
+ bool check_for_path_chars;
+
+ if ((check_for_path_chars= check_mysql50_prefix(name)))
+ {
+ name+= MYSQL50_TABLE_NAME_PREFIX_LENGTH;
+ name_length-= MYSQL50_TABLE_NAME_PREFIX_LENGTH;
+ }
+
+ if (!name_length || name_length > NAME_LEN)
+ return 1;
+
+ if (lower_case_table_names == 1 && name != any_db)
+ {
+ org_name->length= name_length= my_casedn_str(files_charset_info, name);
+ if (check_for_path_chars)
+ org_name->length+= MYSQL50_TABLE_NAME_PREFIX_LENGTH;
+ }
+ if (db_name_is_in_ignore_db_dirs_list(name))
+ return 1;
+
+ return check_table_name(name, name_length, check_for_path_chars);
+}
+
+
+/*
+ Allow anything as a table name, as long as it doesn't contain an
+ ' ' at the end
+ returns 1 on error
+*/
+
+bool check_table_name(const char *name, size_t length, bool check_for_path_chars)
+{
+ // name length in symbols
+ size_t name_length= 0;
+ const char *end= name+length;
+
+
+ if (!check_for_path_chars &&
+ (check_for_path_chars= check_mysql50_prefix(name)))
+ {
+ name+= MYSQL50_TABLE_NAME_PREFIX_LENGTH;
+ length-= MYSQL50_TABLE_NAME_PREFIX_LENGTH;
+ }
+
+ if (!length || length > NAME_LEN)
+ return 1;
+#if defined(USE_MB) && defined(USE_MB_IDENT)
+ bool last_char_is_space= FALSE;
+#else
+ if (name[length-1]==' ')
+ return 1;
+#endif
+
+ while (name != end)
+ {
+#if defined(USE_MB) && defined(USE_MB_IDENT)
+ last_char_is_space= my_isspace(system_charset_info, *name);
+ if (use_mb(system_charset_info))
+ {
+ int len=my_ismbchar(system_charset_info, name, end);
+ if (len)
+ {
+ name+= len;
+ name_length++;
+ continue;
+ }
+ }
+#endif
+ if (check_for_path_chars &&
+ (*name == '/' || *name == '\\' || *name == '~' || *name == FN_EXTCHAR))
+ return 1;
+ name++;
+ name_length++;
+ }
+#if defined(USE_MB) && defined(USE_MB_IDENT)
+ return last_char_is_space || (name_length > NAME_CHAR_LEN);
+#else
+ return FALSE;
+#endif
+}
+
+
+bool check_column_name(const char *name)
+{
+ // name length in symbols
+ size_t name_length= 0;
+ bool last_char_is_space= TRUE;
+
+ while (*name)
+ {
+#if defined(USE_MB) && defined(USE_MB_IDENT)
+ last_char_is_space= my_isspace(system_charset_info, *name);
+ if (use_mb(system_charset_info))
+ {
+ int len=my_ismbchar(system_charset_info, name,
+ name+system_charset_info->mbmaxlen);
+ if (len)
+ {
+ name += len;
+ name_length++;
+ continue;
+ }
+ }
+#else
+ last_char_is_space= *name==' ';
+ if (*name == '\377')
+ return 1;
+#endif
+ name++;
+ name_length++;
+ }
+ /* Error if empty or too long column name */
+ return last_char_is_space || (name_length > NAME_CHAR_LEN);
+}
+
+
+/**
+ Checks whether a table is intact. Should be done *just* after the table has
+ been opened.
+
+ @param[in] table The table to check
+ @param[in] table_f_count Expected number of columns in the table
+ @param[in] table_def Expected structure of the table (column name
+ and type)
+
+ @retval FALSE OK
+ @retval TRUE There was an error. An error message is output
+ to the error log. We do not push an error
+ message into the error stack because this
+ function is currently only called at start up,
+ and such errors never reach the user.
+*/
+
+bool
+Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def)
+{
+ uint i;
+ my_bool error= FALSE;
+ const TABLE_FIELD_TYPE *field_def= table_def->field;
+ DBUG_ENTER("table_check_intact");
+ DBUG_PRINT("info",("table: %s expected_count: %d",
+ table->alias.c_ptr(), table_def->count));
+
+ /* Whether the table definition has already been validated. */
+ if (table->s->table_field_def_cache == table_def)
+ DBUG_RETURN(FALSE);
+
+ if (table->s->fields != table_def->count)
+ {
+ DBUG_PRINT("info", ("Column count has changed, checking the definition"));
+
+ /* previous MySQL version */
+ if (MYSQL_VERSION_ID > table->s->mysql_version)
+ {
+ report_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE,
+ ER(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE),
+ table->alias.c_ptr(), table_def->count, table->s->fields,
+ static_cast<int>(table->s->mysql_version),
+ MYSQL_VERSION_ID);
+ DBUG_RETURN(TRUE);
+ }
+ else if (MYSQL_VERSION_ID == table->s->mysql_version)
+ {
+ report_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2,
+ ER(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2),
+ table->s->db.str, table->s->table_name.str,
+ table_def->count, table->s->fields);
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ Something has definitely changed, but we're running an older
+ version of MySQL with new system tables.
+ Let's check column definitions. If a column was added at
+ the end of the table, then we don't care much since such change
+ is backward compatible.
+ */
+ }
+ char buffer[1024];
+ for (i=0 ; i < table_def->count; i++, field_def++)
+ {
+ String sql_type(buffer, sizeof(buffer), system_charset_info);
+ sql_type.length(0);
+ /* Allocate min 256 characters at once */
+ sql_type.extra_allocation(256);
+ if (i < table->s->fields)
+ {
+ Field *field= table->field[i];
+
+ if (strncmp(field->field_name, field_def->name.str,
+ field_def->name.length))
+ {
+ /*
+ Name changes are not fatal, we use ordinal numbers to access columns.
+ Still this can be a sign of a tampered table, output an error
+ to the error log.
+ */
+ report_error(0, "Incorrect definition of table %s.%s: "
+ "expected column '%s' at position %d, found '%s'.",
+ table->s->db.str, table->alias.c_ptr(),
+ field_def->name.str, i,
+ field->field_name);
+ }
+ field->sql_type(sql_type);
+ /*
+ Generally, if column types don't match, then something is
+ wrong.
+
+ However, we only compare column definitions up to the
+ length of the original definition, since we consider the
+ following definitions compatible:
+
+ 1. DATETIME and DATETIM
+ 2. INT(11) and INT(11
+ 3. SET('one', 'two') and SET('one', 'two', 'more')
+
+ For SETs or ENUMs, if the same prefix is there it's OK to
+ add more elements - they will get higher ordinal numbers and
+ the new table definition is backward compatible with the
+ original one.
+ */
+ if (strncmp(sql_type.c_ptr_safe(), field_def->type.str,
+ field_def->type.length - 1))
+ {
+ report_error(0, "Incorrect definition of table %s.%s: "
+ "expected column '%s' at position %d to have type "
+ "%s, found type %s.", table->s->db.str,
+ table->alias.c_ptr(),
+ field_def->name.str, i, field_def->type.str,
+ sql_type.c_ptr_safe());
+ error= TRUE;
+ }
+ else if (field_def->cset.str && !field->has_charset())
+ {
+ report_error(0, "Incorrect definition of table %s.%s: "
+ "expected the type of column '%s' at position %d "
+ "to have character set '%s' but the type has no "
+ "character set.", table->s->db.str,
+ table->alias.c_ptr(),
+ field_def->name.str, i, field_def->cset.str);
+ error= TRUE;
+ }
+ else if (field_def->cset.str &&
+ strcmp(field->charset()->csname, field_def->cset.str))
+ {
+ report_error(0, "Incorrect definition of table %s.%s: "
+ "expected the type of column '%s' at position %d "
+ "to have character set '%s' but found "
+ "character set '%s'.", table->s->db.str,
+ table->alias.c_ptr(),
+ field_def->name.str, i, field_def->cset.str,
+ field->charset()->csname);
+ error= TRUE;
+ }
+ }
+ else
+ {
+ report_error(0, "Incorrect definition of table %s.%s: "
+ "expected column '%s' at position %d to have type %s "
+ " but the column is not found.",
+ table->s->db.str, table->alias.c_ptr(),
+ field_def->name.str, i, field_def->type.str);
+ error= TRUE;
+ }
+ }
+
+ if (table_def->primary_key_parts)
+ {
+ if (table->s->primary_key == MAX_KEY)
+ {
+ report_error(0, "Incorrect definition of table %s.%s: "
+ "missing primary key.", table->s->db.str,
+ table->alias.c_ptr());
+ error= TRUE;
+ }
+ else
+ {
+ KEY *pk= &table->s->key_info[table->s->primary_key];
+ if (pk->user_defined_key_parts != table_def->primary_key_parts)
+ {
+ report_error(0, "Incorrect definition of table %s.%s: "
+ "Expected primary key to have %u columns, but instead "
+ "found %u columns.", table->s->db.str,
+ table->alias.c_ptr(), table_def->primary_key_parts,
+ pk->user_defined_key_parts);
+ error= TRUE;
+ }
+ else
+ {
+ for (i= 0; i < pk->user_defined_key_parts; ++i)
+ {
+ if (table_def->primary_key_columns[i] + 1 != pk->key_part[i].fieldnr)
+ {
+ report_error(0, "Incorrect definition of table %s.%s: Expected "
+ "primary key part %u to refer to column %u, but "
+ "instead found column %u.", table->s->db.str,
+ table->alias.c_ptr(), i + 1,
+ table_def->primary_key_columns[i] + 1,
+ pk->key_part[i].fieldnr);
+ error= TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ if (! error)
+ table->s->table_field_def_cache= table_def;
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Traverse portion of wait-for graph which is reachable through edge
+ represented by this flush ticket in search for deadlocks.
+
+ @retval TRUE A deadlock is found. A victim is remembered
+ by the visitor.
+ @retval FALSE Success, no deadlocks.
+*/
+
+bool Wait_for_flush::accept_visitor(MDL_wait_for_graph_visitor *gvisitor)
+{
+ return m_share->visit_subgraph(this, gvisitor);
+}
+
+
+uint Wait_for_flush::get_deadlock_weight() const
+{
+ return m_deadlock_weight;
+}
+
+
+/**
+ Traverse portion of wait-for graph which is reachable through this
+ table share in search for deadlocks.
+
+ @param waiting_ticket Ticket representing wait for this share.
+ @param dvisitor Deadlock detection visitor.
+
+ @retval TRUE A deadlock is found. A victim is remembered
+ by the visitor.
+ @retval FALSE No deadlocks, it's OK to begin wait.
+*/
+
+bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush,
+ MDL_wait_for_graph_visitor *gvisitor)
+{
+ TABLE *table;
+ MDL_context *src_ctx= wait_for_flush->get_ctx();
+ bool result= TRUE;
+
+ /*
+ To protect all_tables list from being concurrently modified
+ while we are iterating through it we increment tdc.all_tables_refs.
+ This does not introduce deadlocks in the deadlock detector
+ because we won't try to acquire tdc.LOCK_table_share while
+ holding a write-lock on MDL_lock::m_rwlock.
+ */
+ mysql_mutex_lock(&tdc.LOCK_table_share);
+ tdc.all_tables_refs++;
+ mysql_mutex_unlock(&tdc.LOCK_table_share);
+
+ All_share_tables_list::Iterator tables_it(tdc.all_tables);
+
+ /*
+ In case of multiple searches running in parallel, avoid going
+ over the same loop twice and shortcut the search.
+ Do it after taking the lock to weed out unnecessary races.
+ */
+ if (src_ctx->m_wait.get_status() != MDL_wait::EMPTY)
+ {
+ result= FALSE;
+ goto end;
+ }
+
+ if (gvisitor->enter_node(src_ctx))
+ goto end;
+
+ while ((table= tables_it++))
+ {
+ DBUG_ASSERT(table->in_use && tdc.flushed);
+ if (gvisitor->inspect_edge(&table->in_use->mdl_context))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ tables_it.rewind();
+ while ((table= tables_it++))
+ {
+ DBUG_ASSERT(table->in_use && tdc.flushed);
+ if (table->in_use->mdl_context.visit_subgraph(gvisitor))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ result= FALSE;
+
+end_leave_node:
+ gvisitor->leave_node(src_ctx);
+
+end:
+ mysql_mutex_lock(&tdc.LOCK_table_share);
+ if (!--tdc.all_tables_refs)
+ mysql_cond_broadcast(&tdc.COND_release);
+ mysql_mutex_unlock(&tdc.LOCK_table_share);
+
+ return result;
+}
+
+
+/**
+ Wait until the subject share is removed from the table
+ definition cache and make sure it's destroyed.
+
+ @param mdl_context MDL context for thread which is going to wait.
+ @param abstime Timeout for waiting as absolute time value.
+ @param deadlock_weight Weight of this wait for deadlock detector.
+
+ @pre LOCK_table_share is locked, the share is marked for flush and
+ this connection does not reference the share.
+ LOCK_table_share will be unlocked temporarily during execution.
+
+ It may happen that another FLUSH TABLES thread marked this share
+ for flush, but didn't yet purge it from table definition cache.
+ In this case we may start waiting for a table share that has no
+ references (ref_count == 0). We do this with assumption that this
+ another FLUSH TABLES thread is about to purge this share.
+
+ @retval FALSE - Success.
+ @retval TRUE - Error (OOM, deadlock, timeout, etc...).
+*/
+
+bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime,
+ uint deadlock_weight)
+{
+ MDL_context *mdl_context= &thd->mdl_context;
+ Wait_for_flush ticket(mdl_context, this, deadlock_weight);
+ MDL_wait::enum_wait_status wait_status;
+
+ mysql_mutex_assert_owner(&tdc.LOCK_table_share);
+ DBUG_ASSERT(tdc.flushed);
+
+ tdc.m_flush_tickets.push_front(&ticket);
+
+ mdl_context->m_wait.reset_status();
+
+ mysql_mutex_unlock(&tdc.LOCK_table_share);
+
+ mdl_context->will_wait_for(&ticket);
+
+ mdl_context->find_deadlock();
+
+ wait_status= mdl_context->m_wait.timed_wait(thd, abstime, TRUE,
+ &stage_waiting_for_table_flush);
+
+ mdl_context->done_waiting_for();
+
+ mysql_mutex_lock(&tdc.LOCK_table_share);
+
+ tdc.m_flush_tickets.remove(&ticket);
+
+ if (tdc.m_flush_tickets.is_empty() && tdc.ref_count == 0)
+ {
+ /*
+ If our thread was the last one using the share,
+ we must destroy it here.
+ */
+ mysql_mutex_unlock(&tdc.LOCK_table_share);
+ destroy();
+ }
+ else
+ mysql_mutex_unlock(&tdc.LOCK_table_share);
+
+
+ /*
+ In cases when our wait was aborted by KILL statement,
+ a deadlock or a timeout, the share might still be referenced,
+ so we don't delete it. Note, that we can't determine this
+ condition by checking wait_status alone, since, for example,
+ a timeout can happen after all references to the table share
+ were released, but before the share is removed from the
+ cache and we receive the notification. This is why
+ we first destroy the share, and then look at
+ wait_status.
+ */
+ switch (wait_status)
+ {
+ case MDL_wait::GRANTED:
+ return FALSE;
+ case MDL_wait::VICTIM:
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ return TRUE;
+ case MDL_wait::TIMEOUT:
+ my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
+ return TRUE;
+ case MDL_wait::KILLED:
+ return TRUE;
+ default:
+ DBUG_ASSERT(0);
+ return TRUE;
+ }
+}
+
+
+/**
+ Initialize TABLE instance (newly created, or coming either from table
+ cache or THD::temporary_tables list) and prepare it for further use
+ during statement execution. Set the 'alias' attribute from the specified
+ TABLE_LIST element. Remember the TABLE_LIST element in the
+ TABLE::pos_in_table_list member.
+
+ @param thd Thread context.
+ @param tl TABLE_LIST element.
+*/
+
+void TABLE::init(THD *thd, TABLE_LIST *tl)
+{
+ DBUG_ASSERT(s->tdc.ref_count > 0 || s->tmp_table != NO_TMP_TABLE);
+
+ if (thd->lex->need_correct_ident())
+ alias_name_used= my_strcasecmp(table_alias_charset,
+ s->table_name.str,
+ tl->alias);
+ /* Fix alias if table name changes. */
+ if (strcmp(alias.c_ptr(), tl->alias))
+ alias.copy(tl->alias, strlen(tl->alias), alias.charset());
+
+ tablenr= thd->current_tablenr++;
+ used_fields= 0;
+ const_table= 0;
+ null_row= 0;
+ maybe_null= 0;
+ force_index= 0;
+ force_index_order= 0;
+ force_index_group= 0;
+ status= STATUS_NO_RECORD;
+ insert_values= 0;
+ fulltext_searched= 0;
+ file->ft_handler= 0;
+ reginfo.impossible_range= 0;
+ created= TRUE;
+ cond_selectivity= 1.0;
+ cond_selectivity_sampling_explain= NULL;
+#ifdef HAVE_REPLICATION
+ /* used in RBR Triggers */
+ master_had_triggers= 0;
+#endif
+
+ /* Catch wrong handling of the auto_increment_field_not_null. */
+ DBUG_ASSERT(!auto_increment_field_not_null);
+ auto_increment_field_not_null= FALSE;
+
+ pos_in_table_list= tl;
+
+ clear_column_bitmaps();
+ for (Field **f_ptr= field ; *f_ptr ; f_ptr++)
+ {
+ (*f_ptr)->next_equal_field= NULL;
+ (*f_ptr)->cond_selectivity= 1.0;
+ }
+
+ DBUG_ASSERT(key_read == 0);
+
+ /* mark the record[0] uninitialized */
+ TRASH(record[0], s->reclength);
+
+ /*
+ Initialize the null marker bits, to ensure that if we are doing a read
+ of only selected columns (like in keyread), all null markers are
+ initialized.
+ */
+ memset(record[0], 255, s->null_bytes);
+ memset(record[1], 255, s->null_bytes);
+
+ /* Tables may be reused in a sub statement. */
+ DBUG_ASSERT(!file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
+}
+
+
+/*
+ Create Item_field for each column in the table.
+
+ SYNPOSIS
+ TABLE::fill_item_list()
+ item_list a pointer to an empty list used to store items
+
+ DESCRIPTION
+ Create Item_field object for each column in the table and
+ initialize it with the corresponding Field. New items are
+ created in the current THD memory root.
+
+ RETURN VALUE
+ 0 success
+ 1 out of memory
+*/
+
+bool TABLE::fill_item_list(List<Item> *item_list) const
+{
+ /*
+ All Item_field's created using a direct pointer to a field
+ are fixed in Item_field constructor.
+ */
+ for (Field **ptr= field; *ptr; ptr++)
+ {
+ Item_field *item= new Item_field(*ptr);
+ if (!item || item_list->push_back(item))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ Reset an existing list of Item_field items to point to the
+ Fields of this table.
+
+ SYNPOSIS
+ TABLE::fill_item_list()
+ item_list a non-empty list with Item_fields
+
+ DESCRIPTION
+ This is a counterpart of fill_item_list used to redirect
+ Item_fields to the fields of a newly created table.
+ The caller must ensure that number of items in the item_list
+ is the same as the number of columns in the table.
+*/
+
+void TABLE::reset_item_list(List<Item> *item_list) const
+{
+ List_iterator_fast<Item> it(*item_list);
+ for (Field **ptr= field; *ptr; ptr++)
+ {
+ Item_field *item_field= (Item_field*) it++;
+ DBUG_ASSERT(item_field != 0);
+ item_field->reset_field(*ptr);
+ }
+}
+
+/*
+ calculate md5 of query
+
+ SYNOPSIS
+ TABLE_LIST::calc_md5()
+ buffer buffer for md5 writing
+*/
+
+void TABLE_LIST::calc_md5(char *buffer)
+{
+ uchar digest[16];
+ compute_md5_hash((char*) digest, select_stmt.str,
+ select_stmt.length);
+ sprintf((char *) buffer,
+ "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+ digest[0], digest[1], digest[2], digest[3],
+ digest[4], digest[5], digest[6], digest[7],
+ digest[8], digest[9], digest[10], digest[11],
+ digest[12], digest[13], digest[14], digest[15]);
+}
+
+
+/**
+ @brief
+ Create field translation for mergeable derived table/view.
+
+ @param thd Thread handle
+
+ @details
+ Create field translation for mergeable derived table/view.
+
+ @return FALSE ok.
+ @return TRUE an error occur.
+*/
+
+bool TABLE_LIST::create_field_translation(THD *thd)
+{
+ Item *item;
+ Field_translator *transl;
+ SELECT_LEX *select= get_single_select();
+ List_iterator_fast<Item> it(select->item_list);
+ uint field_count= 0;
+ Query_arena *arena, backup;
+ bool res= FALSE;
+ DBUG_ENTER("TABLE_LIST::create_field_translation");
+
+ if (thd->stmt_arena->is_conventional() ||
+ thd->stmt_arena->is_stmt_prepare_or_first_sp_execute())
+ {
+ /* initialize lists */
+ used_items.empty();
+ persistent_used_items.empty();
+ }
+ else
+ {
+ /*
+ Copy the list created by natural join procedure because the procedure
+ will not be repeated.
+ */
+ used_items= persistent_used_items;
+ }
+
+ if (field_translation)
+ {
+ /*
+ Update items in the field translation after view have been prepared.
+ It's needed because some items in the select list, like IN subselects,
+ might be substituted for optimized ones.
+ */
+ if (is_view() && get_unit()->prepared && !field_translation_updated)
+ {
+ while ((item= it++))
+ {
+ field_translation[field_count++].item= item;
+ }
+ field_translation_updated= TRUE;
+ }
+
+ DBUG_RETURN(FALSE);
+ }
+
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ /* Create view fields translation table */
+
+ if (!(transl=
+ (Field_translator*)(thd->stmt_arena->
+ alloc(select->item_list.elements *
+ sizeof(Field_translator)))))
+ {
+ res= TRUE;
+ goto exit;
+ }
+
+ while ((item= it++))
+ {
+ DBUG_ASSERT(item->name && item->name[0]);
+ transl[field_count].name= thd->strdup(item->name);
+ transl[field_count++].item= item;
+ }
+ field_translation= transl;
+ field_translation_end= transl + field_count;
+ /* It's safe to cache this table for prepared statements */
+ cacheable_table= 1;
+
+exit:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ @brief
+ Create field translation for mergeable derived table/view.
+
+ @param thd Thread handle
+
+ @details
+ Create field translation for mergeable derived table/view.
+
+ @return FALSE ok.
+ @return TRUE an error occur.
+*/
+
+bool TABLE_LIST::setup_underlying(THD *thd)
+{
+ DBUG_ENTER("TABLE_LIST::setup_underlying");
+
+ if (!view || (!field_translation && merge_underlying_list))
+ {
+ SELECT_LEX *select= get_single_select();
+
+ if (create_field_translation(thd))
+ DBUG_RETURN(TRUE);
+
+ /* full text function moving to current select */
+ if (select->ftfunc_list->elements)
+ {
+ Item_func_match *ifm;
+ SELECT_LEX *current_select= thd->lex->current_select;
+ List_iterator_fast<Item_func_match>
+ li(*(select_lex->ftfunc_list));
+ while ((ifm= li++))
+ current_select->ftfunc_list->push_front(ifm);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Prepare where expression of derived table/view
+
+ SYNOPSIS
+ TABLE_LIST::prep_where()
+ thd - thread handler
+ conds - condition of this JOIN
+ no_where_clause - do not build WHERE or ON outer qwery do not need it
+ (it is INSERT), we do not need conds if this flag is set
+
+ NOTE: have to be called befor CHECK OPTION preparation, because it makes
+ fix_fields for view WHERE clause
+
+ RETURN
+ FALSE - OK
+ TRUE - error
+*/
+
+bool TABLE_LIST::prep_where(THD *thd, Item **conds,
+ bool no_where_clause)
+{
+ DBUG_ENTER("TABLE_LIST::prep_where");
+ bool res= FALSE;
+
+ for (TABLE_LIST *tbl= merge_underlying_list; tbl; tbl= tbl->next_local)
+ {
+ if (tbl->is_view_or_derived() &&
+ tbl->prep_where(thd, conds, no_where_clause))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (where)
+ {
+ if (where->fixed)
+ where->update_used_tables();
+ if (!where->fixed && where->fix_fields(thd, &where))
+ {
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ check that it is not VIEW in which we insert with INSERT SELECT
+ (in this case we can't add view WHERE condition to main SELECT_LEX)
+ */
+ if (!no_where_clause && !where_processed)
+ {
+ TABLE_LIST *tbl= this;
+ Query_arena *arena= thd->stmt_arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test
+
+ /* Go up to join tree and try to find left join */
+ for (; tbl; tbl= tbl->embedding)
+ {
+ if (tbl->outer_join)
+ {
+ /*
+ Store WHERE condition to ON expression for outer join, because
+ we can't use WHERE to correctly execute left joins on VIEWs and
+ this expression will not be moved to WHERE condition (i.e. will
+ be clean correctly for PS/SP)
+ */
+ tbl->on_expr= and_conds(tbl->on_expr,
+ where->copy_andor_structure(thd));
+ break;
+ }
+ }
+ if (tbl == 0)
+ {
+ if (*conds && !(*conds)->fixed)
+ res= (*conds)->fix_fields(thd, conds);
+ if (!res)
+ *conds= and_conds(*conds, where->copy_andor_structure(thd));
+ if (*conds && !(*conds)->fixed && !res)
+ res= (*conds)->fix_fields(thd, conds);
+ }
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ where_processed= TRUE;
+ }
+ }
+
+ DBUG_RETURN(res);
+}
+
+/**
+ Check that table/view is updatable and if it has single
+ underlying tables/views it is also updatable
+
+ @return Result of the check.
+*/
+
+bool TABLE_LIST::single_table_updatable()
+{
+ if (!updatable)
+ return false;
+ if (view_tables && view_tables->elements == 1)
+ {
+ /*
+ We need to check deeply only single table views. Multi-table views
+ will be turned to multi-table updates and then checked by leaf tables
+ */
+ return view_tables->head()->single_table_updatable();
+ }
+ return true;
+}
+
+
+/*
+ Merge ON expressions for a view
+
+ SYNOPSIS
+ merge_on_conds()
+ thd thread handle
+ table table for the VIEW
+ is_cascaded TRUE <=> merge ON expressions from underlying views
+
+ DESCRIPTION
+ This function returns the result of ANDing the ON expressions
+ of the given view and all underlying views. The ON expressions
+ of the underlying views are added only if is_cascaded is TRUE.
+
+ RETURN
+ Pointer to the built expression if there is any.
+ Otherwise and in the case of a failure NULL is returned.
+*/
+
+static Item *
+merge_on_conds(THD *thd, TABLE_LIST *table, bool is_cascaded)
+{
+ DBUG_ENTER("merge_on_conds");
+
+ Item *cond= NULL;
+ DBUG_PRINT("info", ("alias: %s", table->alias));
+ if (table->on_expr)
+ cond= table->on_expr->copy_andor_structure(thd);
+ if (!table->view)
+ DBUG_RETURN(cond);
+ for (TABLE_LIST *tbl= (TABLE_LIST*)table->view->select_lex.table_list.first;
+ tbl;
+ tbl= tbl->next_local)
+ {
+ if (tbl->view && !is_cascaded)
+ continue;
+ cond= and_conds(cond, merge_on_conds(thd, tbl, is_cascaded));
+ }
+ DBUG_RETURN(cond);
+}
+
+
+/*
+ Prepare check option expression of table
+
+ SYNOPSIS
+ TABLE_LIST::prep_check_option()
+ thd - thread handler
+ check_opt_type - WITH CHECK OPTION type (VIEW_CHECK_NONE,
+ VIEW_CHECK_LOCAL, VIEW_CHECK_CASCADED)
+ we use this parameter instead of direct check of
+ effective_with_check to change type of underlying
+ views to VIEW_CHECK_CASCADED if outer view have
+ such option and prevent processing of underlying
+ view check options if outer view have just
+ VIEW_CHECK_LOCAL option.
+
+ NOTE
+ This method builds check option condition to use it later on
+ every call (usual execution or every SP/PS call).
+ This method have to be called after WHERE preparation
+ (TABLE_LIST::prep_where)
+
+ RETURN
+ FALSE - OK
+ TRUE - error
+*/
+
+bool TABLE_LIST::prep_check_option(THD *thd, uint8 check_opt_type)
+{
+ DBUG_ENTER("TABLE_LIST::prep_check_option");
+ bool is_cascaded= check_opt_type == VIEW_CHECK_CASCADED;
+ TABLE_LIST *merge_underlying_list= view->select_lex.get_table_list();
+ for (TABLE_LIST *tbl= merge_underlying_list; tbl; tbl= tbl->next_local)
+ {
+ /* see comment of check_opt_type parameter */
+ if (tbl->view && tbl->prep_check_option(thd, (is_cascaded ?
+ VIEW_CHECK_CASCADED :
+ VIEW_CHECK_NONE)))
+ DBUG_RETURN(TRUE);
+ }
+
+ if (check_opt_type && !check_option_processed)
+ {
+ Query_arena *arena= thd->stmt_arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test
+
+ if (where)
+ {
+ check_option= where->copy_andor_structure(thd);
+ }
+ if (is_cascaded)
+ {
+ for (TABLE_LIST *tbl= merge_underlying_list; tbl; tbl= tbl->next_local)
+ {
+ if (tbl->check_option)
+ check_option= and_conds(check_option, tbl->check_option);
+ }
+ }
+ check_option= and_conds(check_option,
+ merge_on_conds(thd, this, is_cascaded));
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ check_option_processed= TRUE;
+
+ }
+
+ if (check_option)
+ {
+ const char *save_where= thd->where;
+ thd->where= "check option";
+ if ((!check_option->fixed &&
+ check_option->fix_fields(thd, &check_option)) ||
+ check_option->check_cols(1))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ thd->where= save_where;
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Hide errors which show view underlying table information.
+ There are currently two mechanisms at work that handle errors for views,
+ this one and a more general mechanism based on an Internal_error_handler,
+ see Show_create_error_handler. The latter handles errors encountered during
+ execution of SHOW CREATE VIEW, while the mechanism using this method is
+ handles SELECT from views. The two methods should not clash.
+
+ @param[in,out] thd thread handler
+
+ @pre This method can be called only if there is an error.
+*/
+
+void TABLE_LIST::hide_view_error(THD *thd)
+{
+ if (thd->killed || thd->get_internal_handler())
+ return;
+ /* Hide "Unknown column" or "Unknown function" error */
+ DBUG_ASSERT(thd->is_error());
+ switch (thd->get_stmt_da()->sql_errno()) {
+ case ER_BAD_FIELD_ERROR:
+ case ER_SP_DOES_NOT_EXIST:
+ case ER_FUNC_INEXISTENT_NAME_COLLISION:
+ case ER_PROCACCESS_DENIED_ERROR:
+ case ER_COLUMNACCESS_DENIED_ERROR:
+ case ER_TABLEACCESS_DENIED_ERROR:
+ case ER_TABLE_NOT_LOCKED:
+ case ER_NO_SUCH_TABLE:
+ {
+ TABLE_LIST *top= top_table();
+ thd->clear_error();
+ my_error(ER_VIEW_INVALID, MYF(0),
+ top->view_db.str, top->view_name.str);
+ break;
+ }
+
+ case ER_NO_DEFAULT_FOR_FIELD:
+ {
+ TABLE_LIST *top= top_table();
+ thd->clear_error();
+ // TODO: make correct error message
+ my_error(ER_NO_DEFAULT_FOR_VIEW_FIELD, MYF(0),
+ top->view_db.str, top->view_name.str);
+ break;
+ }
+ }
+}
+
+
+/*
+ Find underlying base tables (TABLE_LIST) which represent given
+ table_to_find (TABLE)
+
+ SYNOPSIS
+ TABLE_LIST::find_underlying_table()
+ table_to_find table to find
+
+ RETURN
+ 0 table is not found
+ found table reference
+*/
+
+TABLE_LIST *TABLE_LIST::find_underlying_table(TABLE *table_to_find)
+{
+ /* is this real table and table which we are looking for? */
+ if (table == table_to_find && view == 0)
+ return this;
+ if (!view)
+ return 0;
+
+ for (TABLE_LIST *tbl= view->select_lex.get_table_list();
+ tbl;
+ tbl= tbl->next_local)
+ {
+ TABLE_LIST *result;
+ if ((result= tbl->find_underlying_table(table_to_find)))
+ return result;
+ }
+ return 0;
+}
+
+/*
+ cleanup items belonged to view fields translation table
+
+ SYNOPSIS
+ TABLE_LIST::cleanup_items()
+*/
+
+void TABLE_LIST::cleanup_items()
+{
+ if (!field_translation)
+ return;
+
+ for (Field_translator *transl= field_translation;
+ transl < field_translation_end;
+ transl++)
+ transl->item->walk(&Item::cleanup_processor, 0, 0);
+}
+
+
+/*
+ check CHECK OPTION condition
+
+ SYNOPSIS
+ TABLE_LIST::view_check_option()
+ ignore_failure ignore check option fail
+
+ RETURN
+ VIEW_CHECK_OK OK
+ VIEW_CHECK_ERROR FAILED
+ VIEW_CHECK_SKIP FAILED, but continue
+*/
+
+int TABLE_LIST::view_check_option(THD *thd, bool ignore_failure)
+{
+ if (check_option && check_option->val_int() == 0)
+ {
+ TABLE_LIST *main_view= top_table();
+ if (ignore_failure)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_VIEW_CHECK_FAILED, ER(ER_VIEW_CHECK_FAILED),
+ main_view->view_db.str, main_view->view_name.str);
+ return(VIEW_CHECK_SKIP);
+ }
+ my_error(ER_VIEW_CHECK_FAILED, MYF(0), main_view->view_db.str,
+ main_view->view_name.str);
+ return(VIEW_CHECK_ERROR);
+ }
+ return(VIEW_CHECK_OK);
+}
+
+
+/*
+ Find table in underlying tables by mask and check that only this
+ table belong to given mask
+
+ SYNOPSIS
+ TABLE_LIST::check_single_table()
+ table_arg reference on variable where to store found table
+ (should be 0 on call, to find table, or point to table for
+ unique test)
+ map bit mask of tables
+ view_arg view for which we are looking table
+
+ RETURN
+ FALSE table not found or found only one
+ TRUE found several tables
+*/
+
+bool TABLE_LIST::check_single_table(TABLE_LIST **table_arg,
+ table_map map,
+ TABLE_LIST *view_arg)
+{
+ if (!select_lex)
+ return FALSE;
+ DBUG_ASSERT(is_merged_derived());
+ for (TABLE_LIST *tbl= get_single_select()->get_table_list();
+ tbl;
+ tbl= tbl->next_local)
+ {
+ /*
+ Merged view has also temporary table attached (in 5.2 if it has table
+ then it was real table), so we have filter such temporary tables out
+ by checking that it is not merged view
+ */
+ if (tbl->table &&
+ !(tbl->is_view() &&
+ tbl->is_merged_derived()))
+ {
+ if (tbl->table->map & map)
+ {
+ if (*table_arg)
+ return TRUE;
+ *table_arg= tbl;
+ tbl->check_option= view_arg->check_option;
+ }
+ }
+ else if (tbl->check_single_table(table_arg, map, view_arg))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Set insert_values buffer
+
+ SYNOPSIS
+ set_insert_values()
+ mem_root memory pool for allocating
+
+ RETURN
+ FALSE - OK
+ TRUE - out of memory
+*/
+
+bool TABLE_LIST::set_insert_values(MEM_ROOT *mem_root)
+{
+ DBUG_ENTER("set_insert_values");
+ if (table)
+ {
+ DBUG_PRINT("info", ("setting insert_value for table"));
+ if (!table->insert_values &&
+ !(table->insert_values= (uchar *)alloc_root(mem_root,
+ table->s->rec_buff_length)))
+ DBUG_RETURN(TRUE);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("setting insert_value for view"));
+ DBUG_ASSERT(is_view_or_derived() && is_merged_derived());
+ for (TABLE_LIST *tbl= (TABLE_LIST*)view->select_lex.table_list.first;
+ tbl;
+ tbl= tbl->next_local)
+ if (tbl->set_insert_values(mem_root))
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Test if this is a leaf with respect to name resolution.
+
+ SYNOPSIS
+ TABLE_LIST::is_leaf_for_name_resolution()
+
+ DESCRIPTION
+ A table reference is a leaf with respect to name resolution if
+ it is either a leaf node in a nested join tree (table, view,
+ schema table, subquery), or an inner node that represents a
+ NATURAL/USING join, or a nested join with materialized join
+ columns.
+
+ RETURN
+ TRUE if a leaf, FALSE otherwise.
+*/
+bool TABLE_LIST::is_leaf_for_name_resolution()
+{
+ return (is_merged_derived() || is_natural_join || is_join_columns_complete ||
+ !nested_join);
+}
+
+
+/*
+ Retrieve the first (left-most) leaf in a nested join tree with
+ respect to name resolution.
+
+ SYNOPSIS
+ TABLE_LIST::first_leaf_for_name_resolution()
+
+ DESCRIPTION
+ Given that 'this' is a nested table reference, recursively walk
+ down the left-most children of 'this' until we reach a leaf
+ table reference with respect to name resolution.
+
+ IMPLEMENTATION
+ The left-most child of a nested table reference is the last element
+ in the list of children because the children are inserted in
+ reverse order.
+
+ RETURN
+ If 'this' is a nested table reference - the left-most child of
+ the tree rooted in 'this',
+ else return 'this'
+*/
+
+TABLE_LIST *TABLE_LIST::first_leaf_for_name_resolution()
+{
+ TABLE_LIST *cur_table_ref;
+ NESTED_JOIN *cur_nested_join;
+ LINT_INIT(cur_table_ref);
+
+ if (is_leaf_for_name_resolution())
+ return this;
+ DBUG_ASSERT(nested_join);
+
+ for (cur_nested_join= nested_join;
+ cur_nested_join;
+ cur_nested_join= cur_table_ref->nested_join)
+ {
+ List_iterator_fast<TABLE_LIST> it(cur_nested_join->join_list);
+ cur_table_ref= it++;
+ /*
+ If the current nested join is a RIGHT JOIN, the operands in
+ 'join_list' are in reverse order, thus the first operand is
+ already at the front of the list. Otherwise the first operand
+ is in the end of the list of join operands.
+ */
+ if (!(cur_table_ref->outer_join & JOIN_TYPE_RIGHT))
+ {
+ TABLE_LIST *next;
+ while ((next= it++))
+ cur_table_ref= next;
+ }
+ if (cur_table_ref->is_leaf_for_name_resolution())
+ break;
+ }
+ return cur_table_ref;
+}
+
+
+/*
+ Retrieve the last (right-most) leaf in a nested join tree with
+ respect to name resolution.
+
+ SYNOPSIS
+ TABLE_LIST::last_leaf_for_name_resolution()
+
+ DESCRIPTION
+ Given that 'this' is a nested table reference, recursively walk
+ down the right-most children of 'this' until we reach a leaf
+ table reference with respect to name resolution.
+
+ IMPLEMENTATION
+ The right-most child of a nested table reference is the first
+ element in the list of children because the children are inserted
+ in reverse order.
+
+ RETURN
+ - If 'this' is a nested table reference - the right-most child of
+ the tree rooted in 'this',
+ - else - 'this'
+*/
+
+TABLE_LIST *TABLE_LIST::last_leaf_for_name_resolution()
+{
+ TABLE_LIST *cur_table_ref= this;
+ NESTED_JOIN *cur_nested_join;
+
+ if (is_leaf_for_name_resolution())
+ return this;
+ DBUG_ASSERT(nested_join);
+
+ for (cur_nested_join= nested_join;
+ cur_nested_join;
+ cur_nested_join= cur_table_ref->nested_join)
+ {
+ cur_table_ref= cur_nested_join->join_list.head();
+ /*
+ If the current nested is a RIGHT JOIN, the operands in
+ 'join_list' are in reverse order, thus the last operand is in the
+ end of the list.
+ */
+ if ((cur_table_ref->outer_join & JOIN_TYPE_RIGHT))
+ {
+ List_iterator_fast<TABLE_LIST> it(cur_nested_join->join_list);
+ TABLE_LIST *next;
+ cur_table_ref= it++;
+ while ((next= it++))
+ cur_table_ref= next;
+ }
+ if (cur_table_ref->is_leaf_for_name_resolution())
+ break;
+ }
+ return cur_table_ref;
+}
+
+
+/*
+ Register access mode which we need for underlying tables
+
+ SYNOPSIS
+ register_want_access()
+ want_access Acess which we require
+*/
+
+void TABLE_LIST::register_want_access(ulong want_access)
+{
+ /* Remove SHOW_VIEW_ACL, because it will be checked during making view */
+ want_access&= ~SHOW_VIEW_ACL;
+ if (belong_to_view)
+ {
+ grant.want_privilege= want_access;
+ if (table)
+ table->grant.want_privilege= want_access;
+ }
+ if (!view)
+ return;
+ for (TABLE_LIST *tbl= view->select_lex.get_table_list();
+ tbl;
+ tbl= tbl->next_local)
+ tbl->register_want_access(want_access);
+}
+
+
+/*
+ Load security context information for this view
+
+ SYNOPSIS
+ TABLE_LIST::prepare_view_security_context()
+ thd [in] thread handler
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+bool TABLE_LIST::prepare_view_security_context(THD *thd)
+{
+ DBUG_ENTER("TABLE_LIST::prepare_view_security_context");
+ DBUG_PRINT("enter", ("table: %s", alias));
+
+ DBUG_ASSERT(!prelocking_placeholder && view);
+ if (view_suid)
+ {
+ DBUG_PRINT("info", ("This table is suid view => load contest"));
+ DBUG_ASSERT(view && view_sctx);
+ if (acl_getroot(view_sctx, definer.user.str, definer.host.str,
+ definer.host.str, thd->db))
+ {
+ if ((thd->lex->sql_command == SQLCOM_SHOW_CREATE) ||
+ (thd->lex->sql_command == SQLCOM_SHOW_FIELDS))
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_NO_SUCH_USER,
+ ER(ER_NO_SUCH_USER),
+ definer.user.str, definer.host.str);
+ }
+ else
+ {
+ if (thd->security_ctx->master_access & SUPER_ACL)
+ {
+ my_error(ER_NO_SUCH_USER, MYF(0), definer.user.str, definer.host.str);
+
+ }
+ else
+ {
+ if (thd->password == 2)
+ my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
+ thd->security_ctx->priv_user,
+ thd->security_ctx->priv_host);
+ else
+ my_error(ER_ACCESS_DENIED_ERROR, MYF(0),
+ thd->security_ctx->priv_user,
+ thd->security_ctx->priv_host,
+ (thd->password ? ER(ER_YES) : ER(ER_NO)));
+ }
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ DBUG_RETURN(FALSE);
+
+}
+#endif
+
+
+/*
+ Find security context of current view
+
+ SYNOPSIS
+ TABLE_LIST::find_view_security_context()
+ thd [in] thread handler
+
+*/
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+Security_context *TABLE_LIST::find_view_security_context(THD *thd)
+{
+ Security_context *sctx;
+ TABLE_LIST *upper_view= this;
+ DBUG_ENTER("TABLE_LIST::find_view_security_context");
+
+ DBUG_ASSERT(view);
+ while (upper_view && !upper_view->view_suid)
+ {
+ DBUG_ASSERT(!upper_view->prelocking_placeholder);
+ upper_view= upper_view->referencing_view;
+ }
+ if (upper_view)
+ {
+ DBUG_PRINT("info", ("Securety context of view %s will be used",
+ upper_view->alias));
+ sctx= upper_view->view_sctx;
+ DBUG_ASSERT(sctx);
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Current global context will be used"));
+ sctx= thd->security_ctx;
+ }
+ DBUG_RETURN(sctx);
+}
+#endif
+
+
+/*
+ Prepare security context and load underlying tables priveleges for view
+
+ SYNOPSIS
+ TABLE_LIST::prepare_security()
+ thd [in] thread handler
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+bool TABLE_LIST::prepare_security(THD *thd)
+{
+ List_iterator_fast<TABLE_LIST> tb(*view_tables);
+ TABLE_LIST *tbl;
+ DBUG_ENTER("TABLE_LIST::prepare_security");
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *save_security_ctx= thd->security_ctx;
+
+ DBUG_ASSERT(!prelocking_placeholder);
+ if (prepare_view_security_context(thd))
+ DBUG_RETURN(TRUE);
+ thd->security_ctx= find_view_security_context(thd);
+ while ((tbl= tb++))
+ {
+ DBUG_ASSERT(tbl->referencing_view);
+ char *local_db, *local_table_name;
+ if (tbl->view)
+ {
+ local_db= tbl->view_db.str;
+ local_table_name= tbl->view_name.str;
+ }
+ else
+ {
+ local_db= tbl->db;
+ local_table_name= tbl->table_name;
+ }
+ fill_effective_table_privileges(thd, &tbl->grant, local_db,
+ local_table_name);
+ if (tbl->table)
+ tbl->table->grant= grant;
+ }
+ thd->security_ctx= save_security_ctx;
+#else
+ while ((tbl= tb++))
+ tbl->grant.privilege= ~NO_ACCESS;
+#endif
+ DBUG_RETURN(FALSE);
+}
+
+#ifndef DBUG_OFF
+void TABLE_LIST::set_check_merged()
+{
+ DBUG_ASSERT(derived);
+ /*
+ It is not simple to check all, but at least this should be checked:
+ this select is not excluded or the exclusion came from above.
+ */
+ DBUG_ASSERT(!derived->first_select()->exclude_from_table_unique_test ||
+ derived->outer_select()->
+ exclude_from_table_unique_test);
+}
+#endif
+
+void TABLE_LIST::set_check_materialized()
+{
+ DBUG_ENTER("TABLE_LIST::set_check_materialized");
+ SELECT_LEX_UNIT *derived= this->derived;
+ if (view)
+ derived= &view->unit;
+ DBUG_ASSERT(derived);
+ if (!derived->first_select()->exclude_from_table_unique_test)
+ derived->set_unique_exclude();
+ else
+ {
+ /*
+ The subtree should be already excluded
+ */
+ DBUG_ASSERT(!derived->first_select()->first_inner_unit() ||
+ derived->first_select()->first_inner_unit()->first_select()->
+ exclude_from_table_unique_test);
+ }
+ DBUG_VOID_RETURN;
+}
+
+TABLE *TABLE_LIST::get_real_join_table()
+{
+ TABLE_LIST *tbl= this;
+ while (tbl->table == NULL || tbl->table->reginfo.join_tab == NULL)
+ {
+ if ((tbl->view == NULL && tbl->derived == NULL) ||
+ tbl->is_materialized_derived())
+ break;
+ /* we do not support merging of union yet */
+ DBUG_ASSERT(tbl->view == NULL ||
+ tbl->view->select_lex.next_select() == NULL);
+ DBUG_ASSERT(tbl->derived == NULL ||
+ tbl->derived->first_select()->next_select() == NULL);
+
+ {
+ List_iterator_fast<TABLE_LIST>
+ ti(tbl->view != NULL ?
+ tbl->view->select_lex.top_join_list :
+ tbl->derived->first_select()->top_join_list);
+ for (;;)
+ {
+ tbl= NULL;
+ /*
+ Find left table in outer join on this level
+ (the list is reverted).
+ */
+ for (TABLE_LIST *t= ti++; t; t= ti++)
+ tbl= t;
+ if (!tbl)
+ return NULL; // view/derived with no tables
+ if (!tbl->nested_join)
+ break;
+ /* go deeper if we've found nested join */
+ ti= tbl->nested_join->join_list;
+ }
+ }
+ }
+
+ return tbl->table;
+}
+
+
+Natural_join_column::Natural_join_column(Field_translator *field_param,
+ TABLE_LIST *tab)
+{
+ DBUG_ASSERT(tab->field_translation);
+ view_field= field_param;
+ table_field= NULL;
+ table_ref= tab;
+ is_common= FALSE;
+}
+
+
+Natural_join_column::Natural_join_column(Item_field *field_param,
+ TABLE_LIST *tab)
+{
+ DBUG_ASSERT(tab->table == field_param->field->table);
+ table_field= field_param;
+ view_field= NULL;
+ table_ref= tab;
+ is_common= FALSE;
+}
+
+
+const char *Natural_join_column::name()
+{
+ if (view_field)
+ {
+ DBUG_ASSERT(table_field == NULL);
+ return view_field->name;
+ }
+
+ return table_field->field_name;
+}
+
+
+Item *Natural_join_column::create_item(THD *thd)
+{
+ if (view_field)
+ {
+ DBUG_ASSERT(table_field == NULL);
+ return create_view_field(thd, table_ref, &view_field->item,
+ view_field->name);
+ }
+ return table_field;
+}
+
+
+Field *Natural_join_column::field()
+{
+ if (view_field)
+ {
+ DBUG_ASSERT(table_field == NULL);
+ return NULL;
+ }
+ return table_field->field;
+}
+
+
+const char *Natural_join_column::table_name()
+{
+ DBUG_ASSERT(table_ref);
+ return table_ref->alias;
+}
+
+
+const char *Natural_join_column::db_name()
+{
+ if (view_field)
+ return table_ref->view_db.str;
+
+ /*
+ Test that TABLE_LIST::db is the same as TABLE_SHARE::db to
+ ensure consistency. An exception are I_S schema tables, which
+ are inconsistent in this respect.
+ */
+ DBUG_ASSERT(!strcmp(table_ref->db,
+ table_ref->table->s->db.str) ||
+ (table_ref->schema_table &&
+ is_infoschema_db(table_ref->table->s->db.str,
+ table_ref->table->s->db.length)) ||
+ table_ref->is_materialized_derived());
+ return table_ref->db;
+}
+
+
+GRANT_INFO *Natural_join_column::grant()
+{
+/* if (view_field)
+ return &(table_ref->grant);
+ return &(table_ref->table->grant);*/
+ /*
+ Have to check algorithm because merged derived also has
+ field_translation.
+ */
+//if (table_ref->effective_algorithm == DTYPE_ALGORITHM_MERGE)
+ if (table_ref->is_merged_derived())
+ return &(table_ref->grant);
+ return &(table_ref->table->grant);
+}
+
+
+void Field_iterator_view::set(TABLE_LIST *table)
+{
+ DBUG_ASSERT(table->field_translation);
+ view= table;
+ ptr= table->field_translation;
+ array_end= table->field_translation_end;
+}
+
+
+const char *Field_iterator_table::name()
+{
+ return (*ptr)->field_name;
+}
+
+
+Item *Field_iterator_table::create_item(THD *thd)
+{
+ SELECT_LEX *select= thd->lex->current_select;
+
+ Item_field *item= new Item_field(thd, &select->context, *ptr);
+ if (item && thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY &&
+ !thd->lex->in_sum_func && select->cur_pos_in_select_list != UNDEF_POS)
+ {
+ select->non_agg_fields.push_back(item);
+ item->marker= select->cur_pos_in_select_list;
+ select->set_non_agg_field_used(true);
+ }
+ return item;
+}
+
+
+const char *Field_iterator_view::name()
+{
+ return ptr->name;
+}
+
+
+Item *Field_iterator_view::create_item(THD *thd)
+{
+ return create_view_field(thd, view, &ptr->item, ptr->name);
+}
+
+Item *create_view_field(THD *thd, TABLE_LIST *view, Item **field_ref,
+ const char *name)
+{
+ bool save_wrapper= thd->lex->select_lex.no_wrap_view_item;
+ Item *field= *field_ref;
+ DBUG_ENTER("create_view_field");
+
+ if (view->schema_table_reformed)
+ {
+ /*
+ Translation table items are always Item_fields and already fixed
+ ('mysql_schema_table' function). So we can return directly the
+ field. This case happens only for 'show & where' commands.
+ */
+ DBUG_ASSERT(field && field->fixed);
+ DBUG_RETURN(field);
+ }
+
+ DBUG_ASSERT(field);
+ thd->lex->current_select->no_wrap_view_item= TRUE;
+ if (!field->fixed)
+ {
+ if (field->fix_fields(thd, field_ref))
+ {
+ thd->lex->current_select->no_wrap_view_item= save_wrapper;
+ DBUG_RETURN(0);
+ }
+ field= *field_ref;
+ }
+ thd->lex->current_select->no_wrap_view_item= save_wrapper;
+ if (save_wrapper)
+ {
+ DBUG_RETURN(field);
+ }
+ Item *item= new Item_direct_view_ref(&view->view->select_lex.context,
+ field_ref, view->alias,
+ name, view);
+ /*
+ Force creation of nullable item for the result tmp table for outer joined
+ views/derived tables.
+ */
+ if (view->table && view->table->maybe_null)
+ item->maybe_null= TRUE;
+ /* Save item in case we will need to fall back to materialization. */
+ view->used_items.push_front(item);
+ DBUG_RETURN(item);
+}
+
+
+void Field_iterator_natural_join::set(TABLE_LIST *table_ref)
+{
+ DBUG_ASSERT(table_ref->join_columns);
+ column_ref_it.init(*(table_ref->join_columns));
+ cur_column_ref= column_ref_it++;
+}
+
+
+void Field_iterator_natural_join::next()
+{
+ cur_column_ref= column_ref_it++;
+ DBUG_ASSERT(!cur_column_ref || ! cur_column_ref->table_field ||
+ cur_column_ref->table_ref->table ==
+ cur_column_ref->table_field->field->table);
+}
+
+
+void Field_iterator_table_ref::set_field_iterator()
+{
+ DBUG_ENTER("Field_iterator_table_ref::set_field_iterator");
+ /*
+ If the table reference we are iterating over is a natural join, or it is
+ an operand of a natural join, and TABLE_LIST::join_columns contains all
+ the columns of the join operand, then we pick the columns from
+ TABLE_LIST::join_columns, instead of the orginial container of the
+ columns of the join operator.
+ */
+ if (table_ref->is_join_columns_complete)
+ {
+ /* Necesary, but insufficient conditions. */
+ DBUG_ASSERT(table_ref->is_natural_join ||
+ table_ref->nested_join ||
+ (table_ref->join_columns &&
+ /* This is a merge view. */
+ ((table_ref->field_translation &&
+ table_ref->join_columns->elements ==
+ (ulong)(table_ref->field_translation_end -
+ table_ref->field_translation)) ||
+ /* This is stored table or a tmptable view. */
+ (!table_ref->field_translation &&
+ table_ref->join_columns->elements ==
+ table_ref->table->s->fields))));
+ field_it= &natural_join_it;
+ DBUG_PRINT("info",("field_it for '%s' is Field_iterator_natural_join",
+ table_ref->alias));
+ }
+ /* This is a merge view, so use field_translation. */
+ else if (table_ref->field_translation)
+ {
+ DBUG_ASSERT(table_ref->is_merged_derived());
+ field_it= &view_field_it;
+ DBUG_PRINT("info", ("field_it for '%s' is Field_iterator_view",
+ table_ref->alias));
+ }
+ /* This is a base table or stored view. */
+ else
+ {
+ DBUG_ASSERT(table_ref->table || table_ref->view);
+ field_it= &table_field_it;
+ DBUG_PRINT("info", ("field_it for '%s' is Field_iterator_table",
+ table_ref->alias));
+ }
+ field_it->set(table_ref);
+ DBUG_VOID_RETURN;
+}
+
+
+void Field_iterator_table_ref::set(TABLE_LIST *table)
+{
+ DBUG_ASSERT(table);
+ first_leaf= table->first_leaf_for_name_resolution();
+ last_leaf= table->last_leaf_for_name_resolution();
+ DBUG_ASSERT(first_leaf && last_leaf);
+ table_ref= first_leaf;
+ set_field_iterator();
+}
+
+
+void Field_iterator_table_ref::next()
+{
+ /* Move to the next field in the current table reference. */
+ field_it->next();
+ /*
+ If all fields of the current table reference are exhausted, move to
+ the next leaf table reference.
+ */
+ if (field_it->end_of_fields() && table_ref != last_leaf)
+ {
+ table_ref= table_ref->next_name_resolution_table;
+ DBUG_ASSERT(table_ref);
+ set_field_iterator();
+ }
+}
+
+
+const char *Field_iterator_table_ref::get_table_name()
+{
+ if (table_ref->view)
+ return table_ref->view_name.str;
+ else if (table_ref->is_natural_join)
+ return natural_join_it.column_ref()->table_name();
+
+ DBUG_ASSERT(!strcmp(table_ref->table_name,
+ table_ref->table->s->table_name.str));
+ return table_ref->table_name;
+}
+
+
+const char *Field_iterator_table_ref::get_db_name()
+{
+ if (table_ref->view)
+ return table_ref->view_db.str;
+ else if (table_ref->is_natural_join)
+ return natural_join_it.column_ref()->db_name();
+
+ /*
+ Test that TABLE_LIST::db is the same as TABLE_SHARE::db to
+ ensure consistency. An exception are I_S schema tables, which
+ are inconsistent in this respect.
+ */
+ DBUG_ASSERT(!strcmp(table_ref->db, table_ref->table->s->db.str) ||
+ (table_ref->schema_table &&
+ is_infoschema_db(table_ref->table->s->db.str,
+ table_ref->table->s->db.length)));
+
+ return table_ref->db;
+}
+
+
+GRANT_INFO *Field_iterator_table_ref::grant()
+{
+ if (table_ref->view)
+ return &(table_ref->grant);
+ else if (table_ref->is_natural_join)
+ return natural_join_it.column_ref()->grant();
+ return &(table_ref->table->grant);
+}
+
+
+/*
+ Create new or return existing column reference to a column of a
+ natural/using join.
+
+ SYNOPSIS
+ Field_iterator_table_ref::get_or_create_column_ref()
+ parent_table_ref the parent table reference over which the
+ iterator is iterating
+
+ DESCRIPTION
+ Create a new natural join column for the current field of the
+ iterator if no such column was created, or return an already
+ created natural join column. The former happens for base tables or
+ views, and the latter for natural/using joins. If a new field is
+ created, then the field is added to 'parent_table_ref' if it is
+ given, or to the original table referene of the field if
+ parent_table_ref == NULL.
+
+ NOTES
+ This method is designed so that when a Field_iterator_table_ref
+ walks through the fields of a table reference, all its fields
+ are created and stored as follows:
+ - If the table reference being iterated is a stored table, view or
+ natural/using join, store all natural join columns in a list
+ attached to that table reference.
+ - If the table reference being iterated is a nested join that is
+ not natural/using join, then do not materialize its result
+ fields. This is OK because for such table references
+ Field_iterator_table_ref iterates over the fields of the nested
+ table references (recursively). In this way we avoid the storage
+ of unnecessay copies of result columns of nested joins.
+
+ RETURN
+ # Pointer to a column of a natural join (or its operand)
+ NULL No memory to allocate the column
+*/
+
+Natural_join_column *
+Field_iterator_table_ref::get_or_create_column_ref(THD *thd, TABLE_LIST *parent_table_ref)
+{
+ Natural_join_column *nj_col;
+ bool is_created= TRUE;
+ uint field_count;
+ TABLE_LIST *add_table_ref= parent_table_ref ?
+ parent_table_ref : table_ref;
+ LINT_INIT(field_count);
+
+ if (field_it == &table_field_it)
+ {
+ /* The field belongs to a stored table. */
+ Field *tmp_field= table_field_it.field();
+ Item_field *tmp_item=
+ new Item_field(thd, &thd->lex->current_select->context, tmp_field);
+ if (!tmp_item)
+ return NULL;
+ nj_col= new Natural_join_column(tmp_item, table_ref);
+ field_count= table_ref->table->s->fields;
+ }
+ else if (field_it == &view_field_it)
+ {
+ /* The field belongs to a merge view or information schema table. */
+ Field_translator *translated_field= view_field_it.field_translator();
+ nj_col= new Natural_join_column(translated_field, table_ref);
+ field_count= table_ref->field_translation_end -
+ table_ref->field_translation;
+ }
+ else
+ {
+ /*
+ The field belongs to a NATURAL join, therefore the column reference was
+ already created via one of the two constructor calls above. In this case
+ we just return the already created column reference.
+ */
+ DBUG_ASSERT(table_ref->is_join_columns_complete);
+ is_created= FALSE;
+ nj_col= natural_join_it.column_ref();
+ DBUG_ASSERT(nj_col);
+ }
+ DBUG_ASSERT(!nj_col->table_field ||
+ nj_col->table_ref->table == nj_col->table_field->field->table);
+
+ /*
+ If the natural join column was just created add it to the list of
+ natural join columns of either 'parent_table_ref' or to the table
+ reference that directly contains the original field.
+ */
+ if (is_created)
+ {
+ /* Make sure not all columns were materialized. */
+ DBUG_ASSERT(!add_table_ref->is_join_columns_complete);
+ if (!add_table_ref->join_columns)
+ {
+ /* Create a list of natural join columns on demand. */
+ if (!(add_table_ref->join_columns= new List<Natural_join_column>))
+ return NULL;
+ add_table_ref->is_join_columns_complete= FALSE;
+ }
+ add_table_ref->join_columns->push_back(nj_col);
+ /*
+ If new fields are added to their original table reference, mark if
+ all fields were added. We do it here as the caller has no easy way
+ of knowing when to do it.
+ If the fields are being added to parent_table_ref, then the caller
+ must take care to mark when all fields are created/added.
+ */
+ if (!parent_table_ref &&
+ add_table_ref->join_columns->elements == field_count)
+ add_table_ref->is_join_columns_complete= TRUE;
+ }
+
+ return nj_col;
+}
+
+
+/*
+ Return an existing reference to a column of a natural/using join.
+
+ SYNOPSIS
+ Field_iterator_table_ref::get_natural_column_ref()
+
+ DESCRIPTION
+ The method should be called in contexts where it is expected that
+ all natural join columns are already created, and that the column
+ being retrieved is a Natural_join_column.
+
+ RETURN
+ # Pointer to a column of a natural join (or its operand)
+ NULL No memory to allocate the column
+*/
+
+Natural_join_column *
+Field_iterator_table_ref::get_natural_column_ref()
+{
+ Natural_join_column *nj_col;
+
+ DBUG_ASSERT(field_it == &natural_join_it);
+ /*
+ The field belongs to a NATURAL join, therefore the column reference was
+ already created via one of the two constructor calls above. In this case
+ we just return the already created column reference.
+ */
+ nj_col= natural_join_it.column_ref();
+ DBUG_ASSERT(nj_col &&
+ (!nj_col->table_field ||
+ nj_col->table_ref->table == nj_col->table_field->field->table));
+ return nj_col;
+}
+
+/*****************************************************************************
+ Functions to handle column usage bitmaps (read_set, write_set etc...)
+*****************************************************************************/
+
+/* Reset all columns bitmaps */
+
+void TABLE::clear_column_bitmaps()
+{
+ /*
+ Reset column read/write usage. It's identical to:
+ bitmap_clear_all(&table->def_read_set);
+ bitmap_clear_all(&table->def_write_set);
+ bitmap_clear_all(&table->def_vcol_set);
+ */
+ bzero((char*) def_read_set.bitmap, s->column_bitmap_size*3);
+ column_bitmaps_set(&def_read_set, &def_write_set, &def_vcol_set);
+}
+
+
+/*
+ Tell handler we are going to call position() and rnd_pos() later.
+
+ NOTES:
+ This is needed for handlers that uses the primary key to find the
+ row. In this case we have to extend the read bitmap with the primary
+ key fields.
+*/
+
+void TABLE::prepare_for_position()
+{
+ DBUG_ENTER("TABLE::prepare_for_position");
+
+ if ((file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
+ s->primary_key < MAX_KEY)
+ {
+ mark_columns_used_by_index_no_reset(s->primary_key, read_set);
+ /* signal change */
+ file->column_bitmaps_signal();
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Mark that only fields from one key is used
+
+ NOTE:
+ This changes the bitmap to use the tmp bitmap
+ After this, you can't access any other columns in the table until
+ bitmaps are reset, for example with TABLE::clear_column_bitmaps()
+ or TABLE::restore_column_maps_after_mark_index()
+*/
+
+void TABLE::mark_columns_used_by_index(uint index)
+{
+ MY_BITMAP *bitmap= &tmp_set;
+ DBUG_ENTER("TABLE::mark_columns_used_by_index");
+
+ enable_keyread();
+ bitmap_clear_all(bitmap);
+ mark_columns_used_by_index_no_reset(index, bitmap);
+ column_bitmaps_set(bitmap, bitmap);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Add fields used by a specified index to the table's read_set.
+
+ NOTE:
+ The original state can be restored with
+ restore_column_maps_after_mark_index().
+*/
+
+void TABLE::add_read_columns_used_by_index(uint index)
+{
+ MY_BITMAP *bitmap= &tmp_set;
+ DBUG_ENTER("TABLE::add_read_columns_used_by_index");
+
+ enable_keyread();
+ bitmap_copy(bitmap, read_set);
+ mark_columns_used_by_index_no_reset(index, bitmap);
+ column_bitmaps_set(bitmap, write_set);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Restore to use normal column maps after key read
+
+ NOTES
+ This reverse the change done by mark_columns_used_by_index
+
+ WARNING
+ For this to work, one must have the normal table maps in place
+ when calling mark_columns_used_by_index
+*/
+
+void TABLE::restore_column_maps_after_mark_index()
+{
+ DBUG_ENTER("TABLE::restore_column_maps_after_mark_index");
+
+ disable_keyread();
+ default_column_bitmaps();
+ file->column_bitmaps_signal();
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ mark columns used by key, but don't reset other fields
+*/
+
+void TABLE::mark_columns_used_by_index_no_reset(uint index,
+ MY_BITMAP *bitmap)
+{
+ KEY_PART_INFO *key_part= key_info[index].key_part;
+ KEY_PART_INFO *key_part_end= (key_part +
+ key_info[index].user_defined_key_parts);
+ for (;key_part != key_part_end; key_part++)
+ {
+ bitmap_set_bit(bitmap, key_part->fieldnr-1);
+ if (key_part->field->vcol_info &&
+ key_part->field->vcol_info->expr_item)
+ key_part->field->vcol_info->
+ expr_item->walk(&Item::register_field_in_bitmap,
+ 1, (uchar *) bitmap);
+ }
+}
+
+
+/*
+ Mark auto-increment fields as used fields in both read and write maps
+
+ NOTES
+ This is needed in insert & update as the auto-increment field is
+ always set and sometimes read.
+*/
+
+void TABLE::mark_auto_increment_column()
+{
+ DBUG_ASSERT(found_next_number_field);
+ /*
+ We must set bit in read set as update_auto_increment() is using the
+ store() to check overflow of auto_increment values
+ */
+ bitmap_set_bit(read_set, found_next_number_field->field_index);
+ bitmap_set_bit(write_set, found_next_number_field->field_index);
+ if (s->next_number_keypart)
+ mark_columns_used_by_index_no_reset(s->next_number_index, read_set);
+ file->column_bitmaps_signal();
+}
+
+
+/*
+ Mark columns needed for doing an delete of a row
+
+ DESCRIPTON
+ Some table engines don't have a cursor on the retrieve rows
+ so they need either to use the primary key or all columns to
+ be able to delete a row.
+
+ If the engine needs this, the function works as follows:
+ - If primary key exits, mark the primary key columns to be read.
+ - If not, mark all columns to be read
+
+ If the engine has HA_REQUIRES_KEY_COLUMNS_FOR_DELETE, we will
+ mark all key columns as 'to-be-read'. This allows the engine to
+ loop over the given record to find all keys and doesn't have to
+ retrieve the row again.
+*/
+
+void TABLE::mark_columns_needed_for_delete()
+{
+ if (triggers)
+ triggers->mark_fields_used(TRG_EVENT_DELETE);
+ if (file->ha_table_flags() & HA_REQUIRES_KEY_COLUMNS_FOR_DELETE)
+ {
+ Field **reg_field;
+ for (reg_field= field ; *reg_field ; reg_field++)
+ {
+ if ((*reg_field)->flags & PART_KEY_FLAG)
+ bitmap_set_bit(read_set, (*reg_field)->field_index);
+ }
+ file->column_bitmaps_signal();
+ }
+ if (file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_DELETE)
+ {
+ /*
+ If the handler has no cursor capabilites, we have to read either
+ the primary key, the hidden primary key or all columns to be
+ able to do an delete
+ */
+ if (s->primary_key == MAX_KEY)
+ file->use_hidden_primary_key();
+ else
+ {
+ mark_columns_used_by_index_no_reset(s->primary_key, read_set);
+ file->column_bitmaps_signal();
+ }
+ }
+}
+
+
+/*
+ Mark columns needed for doing an update of a row
+
+ DESCRIPTON
+ Some engines needs to have all columns in an update (to be able to
+ build a complete row). If this is the case, we mark all not
+ updated columns to be read.
+
+ If this is no the case, we do like in the delete case and mark
+ if neeed, either the primary key column or all columns to be read.
+ (see mark_columns_needed_for_delete() for details)
+
+ If the engine has HA_REQUIRES_KEY_COLUMNS_FOR_DELETE, we will
+ mark all USED key columns as 'to-be-read'. This allows the engine to
+ loop over the given record to find all changed keys and doesn't have to
+ retrieve the row again.
+*/
+
+void TABLE::mark_columns_needed_for_update()
+{
+ DBUG_ENTER("mark_columns_needed_for_update");
+ if (triggers)
+ triggers->mark_fields_used(TRG_EVENT_UPDATE);
+ if (file->ha_table_flags() & HA_REQUIRES_KEY_COLUMNS_FOR_DELETE)
+ {
+ /* Mark all used key columns for read */
+ Field **reg_field;
+ for (reg_field= field ; *reg_field ; reg_field++)
+ {
+ /* Merge keys is all keys that had a column refered to in the query */
+ if (merge_keys.is_overlapping((*reg_field)->part_of_key))
+ bitmap_set_bit(read_set, (*reg_field)->field_index);
+ }
+ file->column_bitmaps_signal();
+ }
+ if (file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_DELETE)
+ {
+ /*
+ If the handler has no cursor capabilites, we have to read either
+ the primary key, the hidden primary key or all columns to be
+ able to do an update
+ */
+ if (s->primary_key == MAX_KEY)
+ file->use_hidden_primary_key();
+ else
+ {
+ mark_columns_used_by_index_no_reset(s->primary_key, read_set);
+ file->column_bitmaps_signal();
+ }
+ }
+ /* Mark all virtual columns needed for update */
+ mark_virtual_columns_for_write(FALSE);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Mark columns the handler needs for doing an insert
+
+ For now, this is used to mark fields used by the trigger
+ as changed.
+*/
+
+void TABLE::mark_columns_needed_for_insert()
+{
+ if (triggers)
+ {
+ /*
+ We don't need to mark columns which are used by ON DELETE and
+ ON UPDATE triggers, which may be invoked in case of REPLACE or
+ INSERT ... ON DUPLICATE KEY UPDATE, since before doing actual
+ row replacement or update write_record() will mark all table
+ fields as used.
+ */
+ triggers->mark_fields_used(TRG_EVENT_INSERT);
+ }
+ if (found_next_number_field)
+ mark_auto_increment_column();
+ /* Mark virtual columns for insert */
+ mark_virtual_columns_for_write(TRUE);
+}
+
+
+/*
+ @brief Mark a column as virtual used by the query
+
+ @param field the field for the column to be marked
+
+ @details
+ The function marks the column for 'field' as virtual (computed)
+ in the bitmap vcol_set.
+ If the column is marked for the first time the expression to compute
+ the column is traversed and all columns that are occurred there are
+ marked in the read_set of the table.
+
+ @retval
+ TRUE if column is marked for the first time
+ @retval
+ FALSE otherwise
+*/
+
+bool TABLE::mark_virtual_col(Field *field)
+{
+ bool res;
+ DBUG_ASSERT(field->vcol_info);
+ if (!(res= bitmap_fast_test_and_set(vcol_set, field->field_index)))
+ {
+ Item *vcol_item= field->vcol_info->expr_item;
+ DBUG_ASSERT(vcol_item);
+ vcol_item->walk(&Item::register_field_in_read_map, 1, (uchar *) 0);
+ }
+ return res;
+}
+
+
+/*
+ @brief Mark virtual columns for update/insert commands
+
+ @param insert_fl <-> virtual columns are marked for insert command
+
+ @details
+ The function marks virtual columns used in a update/insert commands
+ in the vcol_set bitmap.
+ For an insert command a virtual column is always marked in write_set if
+ it is a stored column.
+ If a virtual column is from write_set it is always marked in vcol_set.
+ If a stored virtual column is not from write_set but it is computed
+ through columns from write_set it is also marked in vcol_set, and,
+ besides, it is added to write_set.
+
+ @return void
+
+ @note
+ Let table t1 have columns a,b,c and let column c be a stored virtual
+ column computed through columns a and b. Then for the query
+ UPDATE t1 SET a=1
+ column c will be placed into vcol_set and into write_set while
+ column b will be placed into read_set.
+ If column c was a virtual column, but not a stored virtual column
+ then it would not be added to any of the sets. Column b would not
+ be added to read_set either.
+*/
+
+void TABLE::mark_virtual_columns_for_write(bool insert_fl)
+{
+ Field **vfield_ptr, *tmp_vfield;
+ bool bitmap_updated= FALSE;
+
+ if (!vfield)
+ return;
+
+ if (!vfield)
+ return;
+
+ for (vfield_ptr= vfield; *vfield_ptr; vfield_ptr++)
+ {
+ tmp_vfield= *vfield_ptr;
+ if (bitmap_is_set(write_set, tmp_vfield->field_index))
+ bitmap_updated= mark_virtual_col(tmp_vfield);
+ else if (tmp_vfield->stored_in_db)
+ {
+ bool mark_fl= insert_fl;
+ if (!mark_fl)
+ {
+ MY_BITMAP *save_read_set;
+ Item *vcol_item= tmp_vfield->vcol_info->expr_item;
+ DBUG_ASSERT(vcol_item);
+ bitmap_clear_all(&tmp_set);
+ save_read_set= read_set;
+ read_set= &tmp_set;
+ vcol_item->walk(&Item::register_field_in_read_map, 1, (uchar *) 0);
+ read_set= save_read_set;
+ bitmap_intersect(&tmp_set, write_set);
+ mark_fl= !bitmap_is_clear_all(&tmp_set);
+ }
+ if (mark_fl)
+ {
+ bitmap_set_bit(write_set, tmp_vfield->field_index);
+ mark_virtual_col(tmp_vfield);
+ bitmap_updated= TRUE;
+ }
+ }
+ }
+ if (bitmap_updated)
+ file->column_bitmaps_signal();
+}
+
+
+/**
+ Check if a table has a default function either for INSERT or UPDATE-like
+ operation
+ @retval true there is a default function
+ @retval false there is no default function
+*/
+
+bool TABLE::has_default_function(bool is_update)
+{
+ Field **dfield_ptr, *dfield;
+ bool res= false;
+ for (dfield_ptr= default_field; *dfield_ptr; dfield_ptr++)
+ {
+ dfield= (*dfield_ptr);
+ if (is_update)
+ res= dfield->has_update_default_function();
+ else
+ res= dfield->has_insert_default_function();
+ if (res)
+ return res;
+ }
+ return res;
+}
+
+
+/**
+ Add all fields that have a default function to the table write set.
+*/
+
+void TABLE::mark_default_fields_for_write()
+{
+ Field **dfield_ptr, *dfield;
+ enum_sql_command cmd= in_use->lex->sql_command;
+ for (dfield_ptr= default_field; *dfield_ptr; dfield_ptr++)
+ {
+ dfield= (*dfield_ptr);
+ if (((sql_command_flags[cmd] & CF_INSERTS_DATA) &&
+ dfield->has_insert_default_function()) ||
+ ((sql_command_flags[cmd] & CF_UPDATES_DATA) &&
+ dfield->has_update_default_function()))
+ bitmap_set_bit(write_set, dfield->field_index);
+ }
+}
+
+
+/**
+ @brief
+ Allocate space for keys
+
+ @param key_count number of keys to allocate additionally
+
+ @details
+ The function allocates memory to fit additionally 'key_count' keys
+ for this table.
+
+ @return FALSE space was successfully allocated
+ @return TRUE an error occur
+*/
+
+bool TABLE::alloc_keys(uint key_count)
+{
+ key_info= (KEY*) alloc_root(&mem_root, sizeof(KEY)*(s->keys+key_count));
+ if (s->keys)
+ memmove(key_info, s->key_info, sizeof(KEY)*s->keys);
+ s->key_info= key_info;
+ max_keys= s->keys+key_count;
+ return !(key_info);
+}
+
+
+/**
+ @brief
+ Populate a KEY_PART_INFO structure with the data related to a field entry.
+
+ @param key_part_info The structure to fill.
+ @param field The field entry that represents the key part.
+ @param fleldnr The number of the field, count starting from 1.
+
+ TODO: This method does not make use of any table specific fields. It
+ could be refactored to act as a constructor for KEY_PART_INFO instead.
+*/
+
+void TABLE::create_key_part_by_field(KEY_PART_INFO *key_part_info,
+ Field *field, uint fieldnr)
+{
+ DBUG_ASSERT(field->field_index + 1 == (int)fieldnr);
+ key_part_info->null_bit= field->null_bit;
+ key_part_info->null_offset= (uint) (field->null_ptr -
+ (uchar*) record[0]);
+ key_part_info->field= field;
+ key_part_info->fieldnr= fieldnr;
+ key_part_info->offset= field->offset(record[0]);
+ /*
+ field->key_length() accounts for the raw length of the field, excluding
+ any metadata such as length of field or the NULL flag.
+ */
+ key_part_info->length= (uint16) field->key_length();
+ key_part_info->key_part_flag= 0;
+ /* TODO:
+ The below method of computing the key format length of the
+ key part is a copy/paste from opt_range.cc, and table.cc.
+ This should be factored out, e.g. as a method of Field.
+ In addition it is not clear if any of the Field::*_length
+ methods is supposed to compute the same length. If so, it
+ might be reused.
+ */
+ key_part_info->store_length= key_part_info->length;
+
+ /*
+ The total store length of the key part is the raw length of the field +
+ any metadata information, such as its length for strings and/or the null
+ flag.
+ */
+ if (field->real_maybe_null())
+ {
+ key_part_info->store_length+= HA_KEY_NULL_LENGTH;
+ }
+ if (field->type() == MYSQL_TYPE_BLOB ||
+ field->type() == MYSQL_TYPE_GEOMETRY ||
+ field->real_type() == MYSQL_TYPE_VARCHAR)
+ {
+ key_part_info->store_length+= HA_KEY_BLOB_LENGTH;
+ key_part_info->key_part_flag|=
+ field->type() == MYSQL_TYPE_BLOB ? HA_BLOB_PART: HA_VAR_LENGTH_PART;
+ }
+
+ key_part_info->type= (uint8) field->key_type();
+ key_part_info->key_type =
+ ((ha_base_keytype) key_part_info->type == HA_KEYTYPE_TEXT ||
+ (ha_base_keytype) key_part_info->type == HA_KEYTYPE_VARTEXT1 ||
+ (ha_base_keytype) key_part_info->type == HA_KEYTYPE_VARTEXT2) ?
+ 0 : FIELDFLAG_BINARY;
+}
+
+
+/**
+ @brief
+ Check validity of a possible key for the derived table
+
+ @param key the number of the key
+ @param key_parts number of components of the key
+ @param next_field_no the call-back function that returns the number of
+ the field used as the next component of the key
+ @param arg the argument for the above function
+
+ @details
+ The function checks whether a possible key satisfies the constraints
+ imposed on the keys of any temporary table.
+
+ @return TRUE if the key is valid
+ @return FALSE otherwise
+*/
+
+bool TABLE::check_tmp_key(uint key, uint key_parts,
+ uint (*next_field_no) (uchar *), uchar *arg)
+{
+ Field **reg_field;
+ uint i;
+ uint key_len= 0;
+
+ for (i= 0; i < key_parts; i++)
+ {
+ uint fld_idx= next_field_no(arg);
+ reg_field= field + fld_idx;
+ uint fld_store_len= (uint16) (*reg_field)->key_length();
+ if ((*reg_field)->real_maybe_null())
+ fld_store_len+= HA_KEY_NULL_LENGTH;
+ if ((*reg_field)->type() == MYSQL_TYPE_BLOB ||
+ (*reg_field)->real_type() == MYSQL_TYPE_VARCHAR ||
+ (*reg_field)->type() == MYSQL_TYPE_GEOMETRY)
+ fld_store_len+= HA_KEY_BLOB_LENGTH;
+ key_len+= fld_store_len;
+ }
+ /*
+ We use MI_MAX_KEY_LENGTH (myisam's default) below because it is
+ smaller than MAX_KEY_LENGTH (heap's default) and it's unknown whether
+ myisam or heap will be used for the temporary table.
+ */
+ return key_len <= MI_MAX_KEY_LENGTH;
+}
+
+/**
+ @brief
+ Add one key to a temporary table
+
+ @param key the number of the key
+ @param key_parts number of components of the key
+ @param next_field_no the call-back function that returns the number of
+ the field used as the next component of the key
+ @param arg the argument for the above function
+ @param unique TRUE <=> it is a unique index
+
+ @details
+ The function adds a new key to the table that is assumed to be a temporary
+ table. At each its invocation the call-back function must return
+ the number of the field that is used as the next component of this key.
+
+ @return FALSE is a success
+ @return TRUE if a failure
+
+*/
+
+bool TABLE::add_tmp_key(uint key, uint key_parts,
+ uint (*next_field_no) (uchar *), uchar *arg,
+ bool unique)
+{
+ DBUG_ASSERT(key < max_keys);
+
+ char buf[NAME_CHAR_LEN];
+ KEY* keyinfo;
+ Field **reg_field;
+ uint i;
+
+ bool key_start= TRUE;
+ KEY_PART_INFO* key_part_info=
+ (KEY_PART_INFO*) alloc_root(&mem_root, sizeof(KEY_PART_INFO)*key_parts);
+ if (!key_part_info)
+ return TRUE;
+ keyinfo= key_info + key;
+ keyinfo->key_part= key_part_info;
+ keyinfo->usable_key_parts= keyinfo->user_defined_key_parts = key_parts;
+ keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
+ keyinfo->key_length=0;
+ keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+ keyinfo->flags= HA_GENERATED_KEY;
+ keyinfo->ext_key_flags= keyinfo->flags;
+ keyinfo->is_statistics_from_stat_tables= FALSE;
+ if (unique)
+ keyinfo->flags|= HA_NOSAME;
+ sprintf(buf, "key%i", key);
+ if (!(keyinfo->name= strdup_root(&mem_root, buf)))
+ return TRUE;
+ keyinfo->rec_per_key= (ulong*) alloc_root(&mem_root,
+ sizeof(ulong)*key_parts);
+ if (!keyinfo->rec_per_key)
+ return TRUE;
+ bzero(keyinfo->rec_per_key, sizeof(ulong)*key_parts);
+ keyinfo->read_stats= NULL;
+ keyinfo->collected_stats= NULL;
+
+ for (i= 0; i < key_parts; i++)
+ {
+ uint fld_idx= next_field_no(arg);
+ reg_field= field + fld_idx;
+ if (key_start)
+ (*reg_field)->key_start.set_bit(key);
+ (*reg_field)->part_of_key.set_bit(key);
+ create_key_part_by_field(key_part_info, *reg_field, fld_idx+1);
+ keyinfo->key_length += key_part_info->store_length;
+ (*reg_field)->flags|= PART_KEY_FLAG;
+ key_start= FALSE;
+ key_part_info++;
+ }
+
+ set_if_bigger(s->max_key_length, keyinfo->key_length);
+ s->keys++;
+ return FALSE;
+}
+
+/*
+ @brief
+ Drop all indexes except specified one.
+
+ @param key_to_save the key to save
+
+ @details
+ Drop all indexes on this table except 'key_to_save'. The saved key becomes
+ key #0. Memory occupied by key parts of dropped keys are freed.
+ If the 'key_to_save' is negative then all keys are freed.
+*/
+
+void TABLE::use_index(int key_to_save)
+{
+ uint i= 1;
+ DBUG_ASSERT(!created && key_to_save < (int)s->keys);
+ if (key_to_save >= 0)
+ /* Save the given key. */
+ memmove(key_info, key_info + key_to_save, sizeof(KEY));
+ else
+ /* Drop all keys; */
+ i= 0;
+
+ s->keys= i;
+}
+
+/*
+ Return TRUE if the table is filled at execution phase
+
+ (and so, the optimizer must not do anything that depends on the contents of
+ the table, like range analysis or constant table detection)
+*/
+
+bool TABLE::is_filled_at_execution()
+{
+ /*
+ pos_in_table_list == NULL for internal temporary tables because they
+ do not have a corresponding table reference. Such tables are filled
+ during execution.
+ */
+ return MY_TEST(!pos_in_table_list ||
+ pos_in_table_list->jtbm_subselect ||
+ pos_in_table_list->is_active_sjm());
+}
+
+
+/**
+ @brief
+ Get actual number of key components
+
+ @param keyinfo
+
+ @details
+ The function calculates actual number of key components, possibly including
+ components of extended keys, taken into consideration by the optimizer for the
+ key described by the parameter keyinfo.
+
+ @return number of considered key components
+*/
+
+uint TABLE::actual_n_key_parts(KEY *keyinfo)
+{
+ return optimizer_flag(in_use, OPTIMIZER_SWITCH_EXTENDED_KEYS) ?
+ keyinfo->ext_key_parts : keyinfo->user_defined_key_parts;
+}
+
+
+/**
+ @brief
+ Get actual key flags for a table key
+
+ @param keyinfo
+
+ @details
+ The function finds out actual key flags taken into consideration by the
+ optimizer for the key described by the parameter keyinfo.
+
+ @return actual key flags
+*/
+
+ulong TABLE::actual_key_flags(KEY *keyinfo)
+{
+ return optimizer_flag(in_use, OPTIMIZER_SWITCH_EXTENDED_KEYS) ?
+ keyinfo->ext_key_flags : keyinfo->flags;
+}
+
+
+/*
+ Cleanup this table for re-execution.
+
+ SYNOPSIS
+ TABLE_LIST::reinit_before_use()
+*/
+
+void TABLE_LIST::reinit_before_use(THD *thd)
+{
+ /*
+ Reset old pointers to TABLEs: they are not valid since the tables
+ were closed in the end of previous prepare or execute call.
+ */
+ table= 0;
+ /* Reset is_schema_table_processed value(needed for I_S tables */
+ schema_table_state= NOT_PROCESSED;
+
+ TABLE_LIST *embedded; /* The table at the current level of nesting. */
+ TABLE_LIST *parent_embedding= this; /* The parent nested table reference. */
+ do
+ {
+ embedded= parent_embedding;
+ if (embedded->prep_on_expr)
+ embedded->on_expr= embedded->prep_on_expr->copy_andor_structure(thd);
+ parent_embedding= embedded->embedding;
+ }
+ while (parent_embedding &&
+ parent_embedding->nested_join->join_list.head() == embedded);
+
+ mdl_request.ticket= NULL;
+}
+
+
+/*
+ Return subselect that contains the FROM list this table is taken from
+
+ SYNOPSIS
+ TABLE_LIST::containing_subselect()
+
+ RETURN
+ Subselect item for the subquery that contains the FROM list
+ this table is taken from if there is any
+ 0 - otherwise
+
+*/
+
+Item_subselect *TABLE_LIST::containing_subselect()
+{
+ return (select_lex ? select_lex->master_unit()->item : 0);
+}
+
+/*
+ Compiles the tagged hints list and fills up the bitmasks.
+
+ SYNOPSIS
+ process_index_hints()
+ table the TABLE to operate on.
+
+ DESCRIPTION
+ The parser collects the index hints for each table in a "tagged list"
+ (TABLE_LIST::index_hints). Using the information in this tagged list
+ this function sets the members TABLE::keys_in_use_for_query,
+ TABLE::keys_in_use_for_group_by, TABLE::keys_in_use_for_order_by,
+ TABLE::force_index, TABLE::force_index_order,
+ TABLE::force_index_group and TABLE::covering_keys.
+
+ Current implementation of the runtime does not allow mixing FORCE INDEX
+ and USE INDEX, so this is checked here. Then the FORCE INDEX list
+ (if non-empty) is appended to the USE INDEX list and a flag is set.
+
+ Multiple hints of the same kind are processed so that each clause
+ is applied to what is computed in the previous clause.
+ For example:
+ USE INDEX (i1) USE INDEX (i2)
+ is equivalent to
+ USE INDEX (i1,i2)
+ and means "consider only i1 and i2".
+
+ Similarly
+ USE INDEX () USE INDEX (i1)
+ is equivalent to
+ USE INDEX (i1)
+ and means "consider only the index i1"
+
+ It is OK to have the same index several times, e.g. "USE INDEX (i1,i1)" is
+ not an error.
+
+ Different kind of hints (USE/FORCE/IGNORE) are processed in the following
+ order:
+ 1. All indexes in USE (or FORCE) INDEX are added to the mask.
+ 2. All IGNORE INDEX
+
+ e.g. "USE INDEX i1, IGNORE INDEX i1, USE INDEX i1" will not use i1 at all
+ as if we had "USE INDEX i1, USE INDEX i1, IGNORE INDEX i1".
+
+ As an optimization if there is a covering index, and we have
+ IGNORE INDEX FOR GROUP/ORDER, and this index is used for the JOIN part,
+ then we have to ignore the IGNORE INDEX FROM GROUP/ORDER.
+
+ RETURN VALUE
+ FALSE no errors found
+ TRUE found and reported an error.
+*/
+bool TABLE_LIST::process_index_hints(TABLE *tbl)
+{
+ /* initialize the result variables */
+ tbl->keys_in_use_for_query= tbl->keys_in_use_for_group_by=
+ tbl->keys_in_use_for_order_by= tbl->s->keys_in_use;
+
+ /* index hint list processing */
+ if (index_hints)
+ {
+ key_map index_join[INDEX_HINT_FORCE + 1];
+ key_map index_order[INDEX_HINT_FORCE + 1];
+ key_map index_group[INDEX_HINT_FORCE + 1];
+ Index_hint *hint;
+ int type;
+ bool have_empty_use_join= FALSE, have_empty_use_order= FALSE,
+ have_empty_use_group= FALSE;
+ List_iterator <Index_hint> iter(*index_hints);
+
+ /* initialize temporary variables used to collect hints of each kind */
+ for (type= INDEX_HINT_IGNORE; type <= INDEX_HINT_FORCE; type++)
+ {
+ index_join[type].clear_all();
+ index_order[type].clear_all();
+ index_group[type].clear_all();
+ }
+
+ /* iterate over the hints list */
+ while ((hint= iter++))
+ {
+ uint pos;
+
+ /* process empty USE INDEX () */
+ if (hint->type == INDEX_HINT_USE && !hint->key_name.str)
+ {
+ if (hint->clause & INDEX_HINT_MASK_JOIN)
+ {
+ index_join[hint->type].clear_all();
+ have_empty_use_join= TRUE;
+ }
+ if (hint->clause & INDEX_HINT_MASK_ORDER)
+ {
+ index_order[hint->type].clear_all();
+ have_empty_use_order= TRUE;
+ }
+ if (hint->clause & INDEX_HINT_MASK_GROUP)
+ {
+ index_group[hint->type].clear_all();
+ have_empty_use_group= TRUE;
+ }
+ continue;
+ }
+
+ /*
+ Check if an index with the given name exists and get his offset in
+ the keys bitmask for the table
+ */
+ if (tbl->s->keynames.type_names == 0 ||
+ (pos= find_type(&tbl->s->keynames, hint->key_name.str,
+ hint->key_name.length, 1)) <= 0)
+ {
+ my_error(ER_KEY_DOES_NOT_EXITS, MYF(0), hint->key_name.str, alias);
+ return 1;
+ }
+
+ pos--;
+
+ /* add to the appropriate clause mask */
+ if (hint->clause & INDEX_HINT_MASK_JOIN)
+ index_join[hint->type].set_bit (pos);
+ if (hint->clause & INDEX_HINT_MASK_ORDER)
+ index_order[hint->type].set_bit (pos);
+ if (hint->clause & INDEX_HINT_MASK_GROUP)
+ index_group[hint->type].set_bit (pos);
+ }
+
+ /* cannot mix USE INDEX and FORCE INDEX */
+ if ((!index_join[INDEX_HINT_FORCE].is_clear_all() ||
+ !index_order[INDEX_HINT_FORCE].is_clear_all() ||
+ !index_group[INDEX_HINT_FORCE].is_clear_all()) &&
+ (!index_join[INDEX_HINT_USE].is_clear_all() || have_empty_use_join ||
+ !index_order[INDEX_HINT_USE].is_clear_all() || have_empty_use_order ||
+ !index_group[INDEX_HINT_USE].is_clear_all() || have_empty_use_group))
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), index_hint_type_name[INDEX_HINT_USE],
+ index_hint_type_name[INDEX_HINT_FORCE]);
+ return 1;
+ }
+
+ /* process FORCE INDEX as USE INDEX with a flag */
+ if (!index_order[INDEX_HINT_FORCE].is_clear_all())
+ {
+ tbl->force_index_order= TRUE;
+ index_order[INDEX_HINT_USE].merge(index_order[INDEX_HINT_FORCE]);
+ }
+
+ if (!index_group[INDEX_HINT_FORCE].is_clear_all())
+ {
+ tbl->force_index_group= TRUE;
+ index_group[INDEX_HINT_USE].merge(index_group[INDEX_HINT_FORCE]);
+ }
+
+ /*
+ TODO: get rid of tbl->force_index (on if any FORCE INDEX is specified) and
+ create tbl->force_index_join instead.
+ Then use the correct force_index_XX instead of the global one.
+ */
+ if (!index_join[INDEX_HINT_FORCE].is_clear_all() ||
+ tbl->force_index_group || tbl->force_index_order)
+ {
+ tbl->force_index= TRUE;
+ index_join[INDEX_HINT_USE].merge(index_join[INDEX_HINT_FORCE]);
+ }
+
+ /* apply USE INDEX */
+ if (!index_join[INDEX_HINT_USE].is_clear_all() || have_empty_use_join)
+ tbl->keys_in_use_for_query.intersect(index_join[INDEX_HINT_USE]);
+ if (!index_order[INDEX_HINT_USE].is_clear_all() || have_empty_use_order)
+ tbl->keys_in_use_for_order_by.intersect (index_order[INDEX_HINT_USE]);
+ if (!index_group[INDEX_HINT_USE].is_clear_all() || have_empty_use_group)
+ tbl->keys_in_use_for_group_by.intersect (index_group[INDEX_HINT_USE]);
+
+ /* apply IGNORE INDEX */
+ tbl->keys_in_use_for_query.subtract (index_join[INDEX_HINT_IGNORE]);
+ tbl->keys_in_use_for_order_by.subtract (index_order[INDEX_HINT_IGNORE]);
+ tbl->keys_in_use_for_group_by.subtract (index_group[INDEX_HINT_IGNORE]);
+ }
+
+ /* make sure covering_keys don't include indexes disabled with a hint */
+ tbl->covering_keys.intersect(tbl->keys_in_use_for_query);
+ return 0;
+}
+
+
+size_t max_row_length(TABLE *table, const uchar *data)
+{
+ TABLE_SHARE *table_s= table->s;
+ size_t length= table_s->reclength + 2 * table_s->fields;
+ uint *const beg= table_s->blob_field;
+ uint *const end= beg + table_s->blob_fields;
+
+ for (uint *ptr= beg ; ptr != end ; ++ptr)
+ {
+ Field_blob* const blob= (Field_blob*) table->field[*ptr];
+ length+= blob->get_length((const uchar*)
+ (data + blob->offset(table->record[0]))) +
+ HA_KEY_BLOB_LENGTH;
+ }
+ return length;
+}
+
+
+/**
+ Helper function which allows to allocate metadata lock request
+ objects for all elements of table list.
+*/
+
+void init_mdl_requests(TABLE_LIST *table_list)
+{
+ for ( ; table_list ; table_list= table_list->next_global)
+ table_list->mdl_request.init(MDL_key::TABLE,
+ table_list->db, table_list->table_name,
+ table_list->lock_type >= TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ,
+ MDL_TRANSACTION);
+}
+
+
+/**
+ Update TABLE::const_key_parts for single table UPDATE/DELETE query
+
+ @param conds WHERE clause expression
+
+ @retval TRUE error (OOM)
+ @retval FALSE success
+
+ @note
+ Set const_key_parts bits if key fields are equal to constants in
+ the WHERE expression.
+*/
+
+bool TABLE::update_const_key_parts(COND *conds)
+{
+ bzero((char*) const_key_parts, sizeof(key_part_map) * s->keys);
+
+ if (conds == NULL)
+ return FALSE;
+
+ for (uint index= 0; index < s->keys; index++)
+ {
+ KEY_PART_INFO *keyinfo= key_info[index].key_part;
+ KEY_PART_INFO *keyinfo_end= keyinfo + key_info[index].user_defined_key_parts;
+
+ for (key_part_map part_map= (key_part_map)1;
+ keyinfo < keyinfo_end;
+ keyinfo++, part_map<<= 1)
+ {
+ if (const_expression_in_where(conds, NULL, keyinfo->field))
+ const_key_parts[index]|= part_map;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ Test if the order list consists of simple field expressions
+
+ @param order Linked list of ORDER BY arguments
+
+ @return TRUE if @a order is empty or consist of simple field expressions
+*/
+
+bool is_simple_order(ORDER *order)
+{
+ for (ORDER *ord= order; ord; ord= ord->next)
+ {
+ if (ord->item[0]->real_item()->type() != Item::FIELD_ITEM)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ @brief Compute values for virtual columns used in query
+
+ @param thd Thread handle
+ @param table The TABLE object
+ @param vcol_update_mode Specifies what virtual column are computed
+
+ @details
+ The function computes the values of the virtual columns of the table and
+ stores them in the table record buffer.
+ If vcol_update_mode is set to VCOL_UPDATE_ALL then all virtual column are
+ computed. Otherwise, only fields from vcol_set are computed: all of them,
+ if vcol_update_mode is set to VCOL_UPDATE_FOR_WRITE, and, only those with
+ the stored_in_db flag set to false, if vcol_update_mode is equal to
+ VCOL_UPDATE_FOR_READ.
+
+ @retval
+ 0 Success
+ @retval
+ >0 Error occurred when storing a virtual field value
+*/
+
+int update_virtual_fields(THD *thd, TABLE *table,
+ enum enum_vcol_update_mode vcol_update_mode)
+{
+ DBUG_ENTER("update_virtual_fields");
+ Field **vfield_ptr, *vfield;
+ int error __attribute__ ((unused))= 0;
+ DBUG_ASSERT(table && table->vfield);
+
+ thd->reset_arena_for_cached_items(table->expr_arena);
+ /* Iterate over virtual fields in the table */
+ for (vfield_ptr= table->vfield; *vfield_ptr; vfield_ptr++)
+ {
+ vfield= (*vfield_ptr);
+ DBUG_ASSERT(vfield->vcol_info && vfield->vcol_info->expr_item);
+ if ((bitmap_is_set(table->vcol_set, vfield->field_index) &&
+ (vcol_update_mode == VCOL_UPDATE_FOR_WRITE || !vfield->stored_in_db)) ||
+ vcol_update_mode == VCOL_UPDATE_ALL)
+ {
+ /* Compute the actual value of the virtual fields */
+ error= vfield->vcol_info->expr_item->save_in_field(vfield, 0);
+ DBUG_PRINT("info", ("field '%s' - updated", vfield->field_name));
+ }
+ else
+ {
+ DBUG_PRINT("info", ("field '%s' - skipped", vfield->field_name));
+ }
+ }
+ thd->reset_arena_for_cached_items(0);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Update all DEFAULT and/or ON INSERT fields.
+
+ @details
+ Compute and set the default value of all fields with a default function.
+ There are two kinds of default functions - one is used for INSERT-like
+ operations, the other for UPDATE-like operations. Depending on the field
+ definition and the current operation one or the other kind of update
+ function is evaluated.
+
+ @retval
+ 0 Success
+ @retval
+ >0 Error occurred when storing a virtual field value
+*/
+
+int TABLE::update_default_fields()
+{
+ DBUG_ENTER("update_default_fields");
+ Field **dfield_ptr, *dfield;
+ int res= 0;
+ enum_sql_command cmd= in_use->lex->sql_command;
+
+ DBUG_ASSERT(default_field);
+
+ /* Iterate over fields with default functions in the table */
+ for (dfield_ptr= default_field; *dfield_ptr; dfield_ptr++)
+ {
+ dfield= (*dfield_ptr);
+ /*
+ If an explicit default value for a filed overrides the default,
+ do not update the field with its automatic default value.
+ */
+ if (!(dfield->flags & HAS_EXPLICIT_VALUE))
+ {
+ if (sql_command_flags[cmd] & CF_INSERTS_DATA)
+ res= dfield->evaluate_insert_default_function();
+ if (sql_command_flags[cmd] & CF_UPDATES_DATA)
+ res= dfield->evaluate_update_default_function();
+ if (res)
+ DBUG_RETURN(res);
+ }
+ }
+ DBUG_RETURN(res);
+}
+
+void TABLE::reset_default_fields()
+{
+ if (default_field)
+ for (Field **df= default_field; *df; df++)
+ (*df)->flags&= ~HAS_EXPLICIT_VALUE;
+}
+
+/*
+ Prepare triggers for INSERT-like statement.
+
+ SYNOPSIS
+ prepare_triggers_for_insert_stmt_or_event()
+
+ NOTE
+ Prepare triggers for INSERT-like statement by marking fields
+ used by triggers and inform handlers that batching of UPDATE/DELETE
+ cannot be done if there are BEFORE UPDATE/DELETE triggers.
+*/
+
+void TABLE::prepare_triggers_for_insert_stmt_or_event()
+{
+ if (triggers)
+ {
+ if (triggers->has_triggers(TRG_EVENT_DELETE,
+ TRG_ACTION_AFTER))
+ {
+ /*
+ The table has AFTER DELETE triggers that might access to
+ subject table and therefore might need delete to be done
+ immediately. So we turn-off the batching.
+ */
+ (void) file->extra(HA_EXTRA_DELETE_CANNOT_BATCH);
+ }
+ if (triggers->has_triggers(TRG_EVENT_UPDATE,
+ TRG_ACTION_AFTER))
+ {
+ /*
+ The table has AFTER UPDATE triggers that might access to subject
+ table and therefore might need update to be done immediately.
+ So we turn-off the batching.
+ */
+ (void) file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH);
+ }
+ }
+}
+
+
+bool TABLE::prepare_triggers_for_delete_stmt_or_event()
+{
+ if (triggers &&
+ triggers->has_triggers(TRG_EVENT_DELETE,
+ TRG_ACTION_AFTER))
+ {
+ /*
+ The table has AFTER DELETE triggers that might access to subject table
+ and therefore might need delete to be done immediately. So we turn-off
+ the batching.
+ */
+ (void) file->extra(HA_EXTRA_DELETE_CANNOT_BATCH);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+bool TABLE::prepare_triggers_for_update_stmt_or_event()
+{
+ if (triggers &&
+ triggers->has_triggers(TRG_EVENT_UPDATE,
+ TRG_ACTION_AFTER))
+ {
+ /*
+ The table has AFTER UPDATE triggers that might access to subject
+ table and therefore might need update to be done immediately.
+ So we turn-off the batching.
+ */
+ (void) file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ @brief Reset const_table flag
+
+ @detail
+ Reset const_table flag for this table. If this table is a merged derived
+ table/view the flag is recursively reseted for all tables of the underlying
+ select.
+*/
+
+void TABLE_LIST::reset_const_table()
+{
+ table->const_table= 0;
+ if (is_merged_derived())
+ {
+ SELECT_LEX *select_lex= get_unit()->first_select();
+ TABLE_LIST *tl;
+ List_iterator<TABLE_LIST> ti(select_lex->leaf_tables);
+ while ((tl= ti++))
+ tl->reset_const_table();
+ }
+}
+
+
+/*
+ @brief Run derived tables/view handling phases on underlying select_lex.
+
+ @param lex LEX for this thread
+ @param phases derived tables/views handling phases to run
+ (set of DT_XXX constants)
+ @details
+ This function runs this derived table through specified 'phases'.
+ Underlying tables of this select are handled prior to this derived.
+ 'lex' is passed as an argument to called functions.
+
+ @return TRUE on error
+ @return FALSE ok
+*/
+
+bool TABLE_LIST::handle_derived(LEX *lex, uint phases)
+{
+ SELECT_LEX_UNIT *unit;
+ DBUG_ENTER("handle_derived");
+ if ((unit= get_unit()))
+ {
+ for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
+ if (sl->handle_derived(lex, phases))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(mysql_handle_single_derived(lex, this, phases));
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief
+ Return unit of this derived table/view
+
+ @return reference to a unit if it's a derived table/view.
+ @return 0 when it's not a derived table/view.
+*/
+
+st_select_lex_unit *TABLE_LIST::get_unit()
+{
+ return (view ? &view->unit : derived);
+}
+
+
+/**
+ @brief
+ Return select_lex of this derived table/view
+
+ @return select_lex of this derived table/view.
+ @return 0 when it's not a derived table.
+*/
+
+st_select_lex *TABLE_LIST::get_single_select()
+{
+ SELECT_LEX_UNIT *unit= get_unit();
+ return (unit ? unit->first_select() : 0);
+}
+
+
+/**
+ @brief
+ Attach a join table list as a nested join to this TABLE_LIST.
+
+ @param join_list join table list to attach
+
+ @details
+ This function wraps 'join_list' into a nested_join of this table, thus
+ turning it to a nested join leaf.
+*/
+
+void TABLE_LIST::wrap_into_nested_join(List<TABLE_LIST> &join_list)
+{
+ TABLE_LIST *tl;
+ /*
+ Walk through derived table top list and set 'embedding' to point to
+ the nesting table.
+ */
+ nested_join->join_list.empty();
+ List_iterator_fast<TABLE_LIST> li(join_list);
+ nested_join->join_list= join_list;
+ while ((tl= li++))
+ {
+ tl->embedding= this;
+ tl->join_list= &nested_join->join_list;
+ }
+}
+
+
+/**
+ @brief
+ Initialize this derived table/view
+
+ @param thd Thread handle
+
+ @details
+ This function makes initial preparations of this derived table/view for
+ further processing:
+ if it's a derived table this function marks it either as mergeable or
+ materializable
+ creates temporary table for name resolution purposes
+ creates field translation for mergeable derived table/view
+
+ @return TRUE an error occur
+ @return FALSE ok
+*/
+
+bool TABLE_LIST::init_derived(THD *thd, bool init_view)
+{
+ SELECT_LEX *first_select= get_single_select();
+ SELECT_LEX_UNIT *unit= get_unit();
+
+ if (!unit)
+ return FALSE;
+ /*
+ Check whether we can merge this derived table into main select.
+ Depending on the result field translation will or will not
+ be created.
+ */
+ TABLE_LIST *first_table= (TABLE_LIST *) first_select->table_list.first;
+ if (first_select->table_list.elements > 1 ||
+ (first_table && first_table->is_multitable()))
+ set_multitable();
+
+ unit->derived= this;
+ if (init_view && !view)
+ {
+ /* This is all what we can do for a derived table for now. */
+ set_derived();
+ }
+
+ if (!is_view())
+ {
+ /* A subquery might be forced to be materialized due to a side-effect. */
+ if (!is_materialized_derived() && first_select->is_mergeable() &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_DERIVED_MERGE) &&
+ !(thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI))
+ set_merged_derived();
+ else
+ set_materialized_derived();
+ }
+ /*
+ Derived tables/view are materialized prior to UPDATE, thus we can skip
+ them from table uniqueness check
+ */
+ if (is_materialized_derived())
+ {
+ set_check_materialized();
+ }
+
+ /*
+ Create field translation for mergeable derived tables/views.
+ For derived tables field translation can be created only after
+ unit is prepared so all '*' are get unrolled.
+ */
+ if (is_merged_derived())
+ {
+ if (is_view() || unit->prepared)
+ create_field_translation(thd);
+ }
+
+ return FALSE;
+}
+
+
+/**
+ @brief
+ Retrieve number of rows in the table
+
+ @details
+ Retrieve number of rows in the table referred by this TABLE_LIST and
+ store it in the table's stats.records variable. If this TABLE_LIST refers
+ to a materialized derived table/view then the estimated number of rows of
+ the derived table/view is used instead.
+
+ @return 0 ok
+ @return non zero error
+*/
+
+int TABLE_LIST::fetch_number_of_rows()
+{
+ int error= 0;
+ if (jtbm_subselect)
+ return 0;
+ if (is_materialized_derived() && !fill_me)
+
+ {
+ table->file->stats.records= ((select_union*)derived->result)->records;
+ set_if_bigger(table->file->stats.records, 2);
+ table->used_stat_records= table->file->stats.records;
+ }
+ else
+ error= table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
+ return error;
+}
+
+/*
+ Procedure of keys generation for result tables of materialized derived
+ tables/views.
+
+ A key is generated for each equi-join pair derived table-another table.
+ Each generated key consists of fields of derived table used in equi-join.
+ Example:
+
+ SELECT * FROM (SELECT * FROM t1 GROUP BY 1) tt JOIN
+ t1 ON tt.f1=t1.f3 and tt.f2.=t1.f4;
+ In this case for the derived table tt one key will be generated. It will
+ consist of two parts f1 and f2.
+ Example:
+
+ SELECT * FROM (SELECT * FROM t1 GROUP BY 1) tt JOIN
+ t1 ON tt.f1=t1.f3 JOIN
+ t2 ON tt.f2=t2.f4;
+ In this case for the derived table tt two keys will be generated.
+ One key over f1 field, and another key over f2 field.
+ Currently optimizer may choose to use only one such key, thus the second
+ one will be dropped after range optimizer is finished.
+ See also JOIN::drop_unused_derived_keys function.
+ Example:
+
+ SELECT * FROM (SELECT * FROM t1 GROUP BY 1) tt JOIN
+ t1 ON tt.f1=a_function(t1.f3);
+ In this case for the derived table tt one key will be generated. It will
+ consist of one field - f1.
+*/
+
+
+
+/*
+ @brief
+ Change references to underlying items of a merged derived table/view
+ for fields in derived table's result table.
+
+ @return FALSE ok
+ @return TRUE Out of memory
+*/
+bool TABLE_LIST::change_refs_to_fields()
+{
+ List_iterator<Item> li(used_items);
+ Item_direct_ref *ref;
+ Field_iterator_view field_it;
+ THD *thd= table->in_use;
+ DBUG_ASSERT(is_merged_derived());
+
+ if (!used_items.elements)
+ return FALSE;
+
+ materialized_items= (Item**)thd->calloc(sizeof(void*) * table->s->fields);
+
+ while ((ref= (Item_direct_ref*)li++))
+ {
+ uint idx;
+ Item *orig_item= *ref->ref;
+ field_it.set(this);
+ for (idx= 0; !field_it.end_of_fields(); field_it.next(), idx++)
+ {
+ if (field_it.item() == orig_item)
+ break;
+ }
+ DBUG_ASSERT(!field_it.end_of_fields());
+ if (!materialized_items[idx])
+ {
+ materialized_items[idx]= new Item_field(table->field[idx]);
+ if (!materialized_items[idx])
+ return TRUE;
+ }
+ /*
+ We need to restore the pointers after the execution of the
+ prepared statement.
+ */
+ thd->change_item_tree((Item **)&ref->ref,
+ (Item*)(materialized_items + idx));
+ }
+
+ return FALSE;
+}
+
+
+void TABLE_LIST::set_lock_type(THD *thd, enum thr_lock_type lock)
+{
+ if (check_stack_overrun(thd, STACK_MIN_SIZE, (uchar *)&lock))
+ return;
+ /* we call it only when table is opened and it is "leaf" table*/
+ DBUG_ASSERT(table);
+ lock_type= lock;
+ /* table->file->get_table() can be 0 for derived tables */
+ if (table->file && table->file->get_table())
+ table->file->set_lock_type(lock);
+ if (is_merged_derived())
+ {
+ for (TABLE_LIST *table= get_single_select()->get_table_list();
+ table;
+ table= table->next_local)
+ {
+ table->set_lock_type(thd, lock);
+ }
+ }
+}
+
+uint TABLE_SHARE::actual_n_key_parts(THD *thd)
+{
+ return use_ext_keys &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_EXTENDED_KEYS) ?
+ ext_key_parts : key_parts;
+}
+
+
+double KEY::actual_rec_per_key(uint i)
+{
+ if (rec_per_key == 0)
+ return 0;
+ return (is_statistics_from_stat_tables ?
+ read_stats->get_avg_frequency(i) : (double) rec_per_key[i]);
+}
+
diff --git a/sql/table.h b/sql/table.h
new file mode 100644
index 00000000000..c63f648f9d0
--- /dev/null
+++ b/sql/table.h
@@ -0,0 +1,2645 @@
+#ifndef TABLE_INCLUDED
+#define TABLE_INCLUDED
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2014, SkySQL Ab.
+
+ 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 "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_plist.h"
+#include "sql_list.h" /* Sql_alloc */
+#include "mdl.h"
+#include "datadict.h"
+#include "sql_string.h" /* String */
+
+#ifndef MYSQL_CLIENT
+
+#include "hash.h" /* HASH */
+#include "handler.h" /* row_type, ha_choice, handler */
+#include "mysql_com.h" /* enum_field_types */
+#include "thr_lock.h" /* thr_lock_type */
+#include "filesort_utils.h"
+
+/* Structs that defines the TABLE */
+
+class Item; /* Needed by ORDER */
+class Item_subselect;
+class Item_field;
+class GRANT_TABLE;
+class st_select_lex_unit;
+class st_select_lex;
+class partition_info;
+class COND_EQUAL;
+class Security_context;
+struct TABLE_LIST;
+class ACL_internal_schema_access;
+class ACL_internal_table_access;
+class Field;
+class Table_statistics;
+
+/*
+ Used to identify NESTED_JOIN structures within a join (applicable only to
+ structures that have not been simplified away and embed more the one
+ element)
+*/
+typedef ulonglong nested_join_map;
+
+
+#define tmp_file_prefix "#sql" /**< Prefix for tmp tables */
+#define tmp_file_prefix_length 4
+#define TMP_TABLE_KEY_EXTRA 8
+
+/**
+ Enumerate possible types of a table from re-execution
+ standpoint.
+ TABLE_LIST class has a member of this type.
+ At prepared statement prepare, this member is assigned a value
+ as of the current state of the database. Before (re-)execution
+ of a prepared statement, we check that the value recorded at
+ prepare matches the type of the object we obtained from the
+ table definition cache.
+
+ @sa check_and_update_table_version()
+ @sa Execute_observer
+ @sa Prepared_statement::reprepare()
+*/
+
+enum enum_table_ref_type
+{
+ /** Initial value set by the parser */
+ TABLE_REF_NULL= 0,
+ TABLE_REF_VIEW,
+ TABLE_REF_BASE_TABLE,
+ TABLE_REF_I_S_TABLE,
+ TABLE_REF_TMP_TABLE
+};
+
+
+/*************************************************************************/
+
+/**
+ Object_creation_ctx -- interface for creation context of database objects
+ (views, stored routines, events, triggers). Creation context -- is a set
+ of attributes, that should be fixed at the creation time and then be used
+ each time the object is parsed or executed.
+*/
+
+class Object_creation_ctx
+{
+public:
+ Object_creation_ctx *set_n_backup(THD *thd);
+
+ void restore_env(THD *thd, Object_creation_ctx *backup_ctx);
+
+protected:
+ Object_creation_ctx() {}
+ virtual Object_creation_ctx *create_backup_ctx(THD *thd) const = 0;
+
+ virtual void change_env(THD *thd) const = 0;
+
+public:
+ virtual ~Object_creation_ctx()
+ { }
+};
+
+/*************************************************************************/
+
+/**
+ Default_object_creation_ctx -- default implementation of
+ Object_creation_ctx.
+*/
+
+class Default_object_creation_ctx : public Object_creation_ctx
+{
+public:
+ CHARSET_INFO *get_client_cs()
+ {
+ return m_client_cs;
+ }
+
+ CHARSET_INFO *get_connection_cl()
+ {
+ return m_connection_cl;
+ }
+
+protected:
+ Default_object_creation_ctx(THD *thd);
+
+ Default_object_creation_ctx(CHARSET_INFO *client_cs,
+ CHARSET_INFO *connection_cl);
+
+protected:
+ virtual Object_creation_ctx *create_backup_ctx(THD *thd) const;
+
+ virtual void change_env(THD *thd) const;
+
+protected:
+ /**
+ client_cs stores the value of character_set_client session variable.
+ The only character set attribute is used.
+
+ Client character set is included into query context, because we save
+ query in the original character set, which is client character set. So,
+ in order to parse the query properly we have to switch client character
+ set on parsing.
+ */
+ CHARSET_INFO *m_client_cs;
+
+ /**
+ connection_cl stores the value of collation_connection session
+ variable. Both character set and collation attributes are used.
+
+ Connection collation is included into query context, becase it defines
+ the character set and collation of text literals in internal
+ representation of query (item-objects).
+ */
+ CHARSET_INFO *m_connection_cl;
+};
+
+class Query_arena;
+
+/*************************************************************************/
+
+/**
+ View_creation_ctx -- creation context of view objects.
+*/
+
+class View_creation_ctx : public Default_object_creation_ctx,
+ public Sql_alloc
+{
+public:
+ static View_creation_ctx *create(THD *thd);
+
+ static View_creation_ctx *create(THD *thd,
+ TABLE_LIST *view);
+
+private:
+ View_creation_ctx(THD *thd)
+ : Default_object_creation_ctx(thd)
+ { }
+};
+
+/*************************************************************************/
+
+/* Order clause list element */
+
+typedef int (*fast_field_copier)(Field *to, Field *from);
+
+
+typedef struct st_order {
+ struct st_order *next;
+ Item **item; /* Point at item in select fields */
+ Item *item_ptr; /* Storage for initial item */
+ /*
+ Reference to the function we are trying to optimize copy to
+ a temporary table
+ */
+ fast_field_copier fast_field_copier_func;
+ /* Field for which above optimizer function setup */
+ Field *fast_field_copier_setup;
+ int counter; /* position in SELECT list, correct
+ only if counter_used is true*/
+ bool asc; /* true if ascending */
+ bool free_me; /* true if item isn't shared */
+ bool in_field_list; /* true if in select field list */
+ bool counter_used; /* parameter was counter of columns */
+ Field *field; /* If tmp-table group */
+ char *buff; /* If tmp-table group */
+ table_map used; /* NOTE: the below is only set to 0 but is still used by eq_ref_table */
+ table_map depend_map;
+} ORDER;
+
+/**
+ State information for internal tables grants.
+ This structure is part of the TABLE_LIST, and is updated
+ during the ACL check process.
+ @sa GRANT_INFO
+*/
+struct st_grant_internal_info
+{
+ /** True if the internal lookup by schema name was done. */
+ bool m_schema_lookup_done;
+ /** Cached internal schema access. */
+ const ACL_internal_schema_access *m_schema_access;
+ /** True if the internal lookup by table name was done. */
+ bool m_table_lookup_done;
+ /** Cached internal table access. */
+ const ACL_internal_table_access *m_table_access;
+};
+typedef struct st_grant_internal_info GRANT_INTERNAL_INFO;
+
+/**
+ @brief The current state of the privilege checking process for the current
+ user, SQL statement and SQL object.
+
+ @details The privilege checking process is divided into phases depending on
+ the level of the privilege to be checked and the type of object to be
+ accessed. Due to the mentioned scattering of privilege checking
+ functionality, it is necessary to keep track of the state of the
+ process. This information is stored in privilege, want_privilege, and
+ orig_want_privilege.
+
+ A GRANT_INFO also serves as a cache of the privilege hash tables. Relevant
+ members are grant_table and version.
+ */
+typedef struct st_grant_info
+{
+ /**
+ @brief A copy of the privilege information regarding the current host,
+ database, object and user.
+
+ @details The version of this copy is found in GRANT_INFO::version.
+ */
+ GRANT_TABLE *grant_table_user;
+ GRANT_TABLE *grant_table_role;
+ /**
+ @brief Used for cache invalidation when caching privilege information.
+
+ @details The privilege information is stored on disk, with dedicated
+ caches residing in memory: table-level and column-level privileges,
+ respectively, have their own dedicated caches.
+
+ The GRANT_INFO works as a level 1 cache with this member updated to the
+ current value of the global variable @c grant_version (@c static variable
+ in sql_acl.cc). It is updated Whenever the GRANT_INFO is refreshed from
+ the level 2 cache. The level 2 cache is the @c column_priv_hash structure
+ (@c static variable in sql_acl.cc)
+
+ @see grant_version
+ */
+ uint version;
+ /**
+ @brief The set of privileges that the current user has fulfilled for a
+ certain host, database, and object.
+
+ @details This field is continually updated throughout the access checking
+ process. In each step the "wanted privilege" is checked against the
+ fulfilled privileges. When/if the intersection of these sets is empty,
+ access is granted.
+
+ The set is implemented as a bitmap, with the bits defined in sql_acl.h.
+ */
+ ulong privilege;
+ /**
+ @brief the set of privileges that the current user needs to fulfil in
+ order to carry out the requested operation.
+ */
+ ulong want_privilege;
+ /**
+ Stores the requested access acl of top level tables list. Is used to
+ check access rights to the underlying tables of a view.
+ */
+ ulong orig_want_privilege;
+ /** The grant state for internal tables. */
+ GRANT_INTERNAL_INFO m_internal;
+} GRANT_INFO;
+
+enum tmp_table_type
+{
+ NO_TMP_TABLE, NON_TRANSACTIONAL_TMP_TABLE, TRANSACTIONAL_TMP_TABLE,
+ INTERNAL_TMP_TABLE, SYSTEM_TMP_TABLE
+};
+enum release_type { RELEASE_NORMAL, RELEASE_WAIT_FOR_DROP };
+
+enum enum_vcol_update_mode
+{
+ VCOL_UPDATE_FOR_READ= 0,
+ VCOL_UPDATE_FOR_WRITE,
+ VCOL_UPDATE_ALL
+};
+
+class Filesort_info
+{
+ /// Buffer for sorting keys.
+ Filesort_buffer filesort_buffer;
+
+public:
+ IO_CACHE *io_cache; /* If sorted through filesort */
+ uchar *buffpek; /* Buffer for buffpek structures */
+ uint buffpek_len; /* Max number of buffpeks in the buffer */
+ uchar *addon_buf; /* Pointer to a buffer if sorted with fields */
+ size_t addon_length; /* Length of the buffer */
+ struct st_sort_addon_field *addon_field; /* Pointer to the fields info */
+ void (*unpack)(struct st_sort_addon_field *, uchar *, uchar *); /* To unpack back */
+ uchar *record_pointers; /* If sorted in memory */
+ ha_rows found_records; /* How many records in sort */
+
+ /** Sort filesort_buffer */
+ void sort_buffer(Sort_param *param, uint count)
+ { filesort_buffer.sort_buffer(param, count); }
+
+ /**
+ Accessors for Filesort_buffer (which @c).
+ */
+ uchar *get_record_buffer(uint idx)
+ { return filesort_buffer.get_record_buffer(idx); }
+
+ uchar **get_sort_keys()
+ { return filesort_buffer.get_sort_keys(); }
+
+ uchar **alloc_sort_buffer(uint num_records, uint record_length)
+ { return filesort_buffer.alloc_sort_buffer(num_records, record_length); }
+
+ bool check_sort_buffer_properties(uint num_records, uint record_length)
+ {
+ return filesort_buffer.check_sort_buffer_properties(num_records,
+ record_length);
+ }
+
+ void free_sort_buffer()
+ { filesort_buffer.free_sort_buffer(); }
+
+ void init_record_pointers()
+ { filesort_buffer.init_record_pointers(); }
+
+ size_t sort_buffer_size() const
+ { return filesort_buffer.sort_buffer_size(); }
+};
+
+
+class Field_blob;
+class Table_triggers_list;
+
+/**
+ Category of table found in the table share.
+*/
+enum enum_table_category
+{
+ /**
+ Unknown value.
+ */
+ TABLE_UNKNOWN_CATEGORY=0,
+
+ /**
+ Temporary table.
+ The table is visible only in the session.
+ Therefore,
+ - FLUSH TABLES WITH READ LOCK
+ - SET GLOBAL READ_ONLY = ON
+ do not apply to this table.
+ Note that LOCK TABLE t FOR READ/WRITE
+ can be used on temporary tables.
+ Temporary tables are not part of the table cache.
+ */
+ TABLE_CATEGORY_TEMPORARY=1,
+
+ /**
+ User table.
+ These tables do honor:
+ - LOCK TABLE t FOR READ/WRITE
+ - FLUSH TABLES WITH READ LOCK
+ - SET GLOBAL READ_ONLY = ON
+ User tables are cached in the table cache.
+ */
+ TABLE_CATEGORY_USER=2,
+
+ /**
+ System table, maintained by the server.
+ These tables do honor:
+ - LOCK TABLE t FOR READ/WRITE
+ - FLUSH TABLES WITH READ LOCK
+ - SET GLOBAL READ_ONLY = ON
+ Typically, writes to system tables are performed by
+ the server implementation, not explicitly be a user.
+ System tables are cached in the table cache.
+ */
+ TABLE_CATEGORY_SYSTEM=3,
+
+ /**
+ Information schema tables.
+ These tables are an interface provided by the system
+ to inspect the system metadata.
+ These tables do *not* honor:
+ - LOCK TABLE t FOR READ/WRITE
+ - FLUSH TABLES WITH READ LOCK
+ - SET GLOBAL READ_ONLY = ON
+ as there is no point in locking explicitly
+ an INFORMATION_SCHEMA table.
+ Nothing is directly written to information schema tables.
+ Note that this value is not used currently,
+ since information schema tables are not shared,
+ but implemented as session specific temporary tables.
+ */
+ /*
+ TODO: Fixing the performance issues of I_S will lead
+ to I_S tables in the table cache, which should use
+ this table type.
+ */
+ TABLE_CATEGORY_INFORMATION=4,
+
+ /**
+ Log tables.
+ These tables are an interface provided by the system
+ to inspect the system logs.
+ These tables do *not* honor:
+ - LOCK TABLE t FOR READ/WRITE
+ - FLUSH TABLES WITH READ LOCK
+ - SET GLOBAL READ_ONLY = ON
+ as there is no point in locking explicitly
+ a LOG table.
+ An example of LOG tables are:
+ - mysql.slow_log
+ - mysql.general_log,
+ which *are* updated even when there is either
+ a GLOBAL READ LOCK or a GLOBAL READ_ONLY in effect.
+ User queries do not write directly to these tables
+ (there are exceptions for log tables).
+ The server implementation perform writes.
+ Log tables are cached in the table cache.
+ */
+ TABLE_CATEGORY_LOG=5,
+
+ /**
+ Performance schema tables.
+ These tables are an interface provided by the system
+ to inspect the system performance data.
+ These tables do *not* honor:
+ - LOCK TABLE t FOR READ/WRITE
+ - FLUSH TABLES WITH READ LOCK
+ - SET GLOBAL READ_ONLY = ON
+ as there is no point in locking explicitly
+ a PERFORMANCE_SCHEMA table.
+ An example of PERFORMANCE_SCHEMA tables are:
+ - performance_schema.*
+ which *are* updated (but not using the handler interface)
+ even when there is either
+ a GLOBAL READ LOCK or a GLOBAL READ_ONLY in effect.
+ User queries do not write directly to these tables
+ (there are exceptions for SETUP_* tables).
+ The server implementation perform writes.
+ Performance tables are cached in the table cache.
+ */
+ TABLE_CATEGORY_PERFORMANCE=6
+};
+typedef enum enum_table_category TABLE_CATEGORY;
+
+TABLE_CATEGORY get_table_category(const LEX_STRING *db,
+ const LEX_STRING *name);
+
+
+struct TABLE_share;
+struct All_share_tables;
+
+typedef struct st_table_field_type
+{
+ LEX_STRING name;
+ LEX_STRING type;
+ LEX_STRING cset;
+} TABLE_FIELD_TYPE;
+
+
+typedef struct st_table_field_def
+{
+ uint count;
+ const TABLE_FIELD_TYPE *field;
+ uint primary_key_parts;
+ const uint *primary_key_columns;
+} TABLE_FIELD_DEF;
+
+
+class Table_check_intact
+{
+protected:
+ virtual void report_error(uint code, const char *fmt, ...)= 0;
+
+public:
+ Table_check_intact() {}
+ virtual ~Table_check_intact() {}
+
+ /** Checks whether a table is intact. */
+ bool check(TABLE *table, const TABLE_FIELD_DEF *table_def);
+};
+
+
+/**
+ Class representing the fact that some thread waits for table
+ share to be flushed. Is used to represent information about
+ such waits in MDL deadlock detector.
+*/
+
+class Wait_for_flush : public MDL_wait_for_subgraph
+{
+ MDL_context *m_ctx;
+ TABLE_SHARE *m_share;
+ uint m_deadlock_weight;
+public:
+ Wait_for_flush(MDL_context *ctx_arg, TABLE_SHARE *share_arg,
+ uint deadlock_weight_arg)
+ : m_ctx(ctx_arg), m_share(share_arg),
+ m_deadlock_weight(deadlock_weight_arg)
+ {}
+
+ MDL_context *get_ctx() const { return m_ctx; }
+
+ virtual bool accept_visitor(MDL_wait_for_graph_visitor *dvisitor);
+
+ virtual uint get_deadlock_weight() const;
+
+ /**
+ Pointers for participating in the list of waiters for table share.
+ */
+ Wait_for_flush *next_in_share;
+ Wait_for_flush **prev_in_share;
+};
+
+
+typedef I_P_List <Wait_for_flush,
+ I_P_List_adapter<Wait_for_flush,
+ &Wait_for_flush::next_in_share,
+ &Wait_for_flush::prev_in_share> >
+ Wait_for_flush_list;
+
+
+enum open_frm_error {
+ OPEN_FRM_OK = 0,
+ OPEN_FRM_OPEN_ERROR,
+ OPEN_FRM_READ_ERROR,
+ OPEN_FRM_CORRUPTED,
+ OPEN_FRM_DISCOVER,
+ OPEN_FRM_ERROR_ALREADY_ISSUED,
+ OPEN_FRM_NOT_A_VIEW,
+ OPEN_FRM_NOT_A_TABLE,
+ OPEN_FRM_NEEDS_REBUILD
+};
+
+/**
+ Control block to access table statistics loaded
+ from persistent statistical tables
+*/
+
+struct TABLE_STATISTICS_CB
+{
+ MEM_ROOT mem_root; /* MEM_ROOT to allocate statistical data for the table */
+ Table_statistics *table_stats; /* Structure to access the statistical data */
+ bool stats_can_be_read; /* Memory for statistical data is allocated */
+ bool stats_is_read; /* Statistical data for table has been read
+ from statistical tables */
+ bool histograms_can_be_read;
+ bool histograms_are_read;
+};
+
+
+/**
+ This structure is shared between different table objects. There is one
+ instance of table share per one table in the database.
+*/
+
+struct TABLE_SHARE
+{
+ TABLE_SHARE() {} /* Remove gcc warning */
+
+ /** Category of this table. */
+ TABLE_CATEGORY table_category;
+
+ /* hash of field names (contains pointers to elements of field array) */
+ HASH name_hash; /* hash of field names */
+ MEM_ROOT mem_root;
+ TYPELIB keynames; /* Pointers to keynames */
+ TYPELIB fieldnames; /* Pointer to fieldnames */
+ TYPELIB *intervals; /* pointer to interval info */
+ mysql_mutex_t LOCK_ha_data; /* To protect access to ha_data */
+ mysql_mutex_t LOCK_share; /* To protect TABLE_SHARE */
+
+ typedef I_P_List <TABLE, TABLE_share> TABLE_list;
+ typedef I_P_List <TABLE, All_share_tables> All_share_tables_list;
+ struct
+ {
+ /**
+ Protects ref_count, m_flush_tickets, all_tables, free_tables, flushed,
+ all_tables_refs.
+ */
+ mysql_mutex_t LOCK_table_share;
+ mysql_cond_t COND_release;
+ TABLE_SHARE *next, **prev; /* Link to unused shares */
+ uint ref_count; /* How many TABLE objects uses this */
+ uint all_tables_refs; /* Number of refs to all_tables */
+ /**
+ List of tickets representing threads waiting for the share to be flushed.
+ */
+ Wait_for_flush_list m_flush_tickets;
+ /*
+ Doubly-linked (back-linked) lists of used and unused TABLE objects
+ for this share.
+ */
+ All_share_tables_list all_tables;
+ TABLE_list free_tables;
+ ulong version;
+ bool flushed;
+ } tdc;
+
+ LEX_CUSTRING tabledef_version;
+
+ engine_option_value *option_list; /* text options for table */
+ ha_table_option_struct *option_struct; /* structure with parsed options */
+
+ /* The following is copied to each TABLE on OPEN */
+ Field **field;
+ Field **found_next_number_field;
+ KEY *key_info; /* data of keys in database */
+ uint *blob_field; /* Index to blobs in Field arrray*/
+
+ TABLE_STATISTICS_CB stats_cb;
+
+ uchar *default_values; /* row with default values */
+ LEX_STRING comment; /* Comment about table */
+ CHARSET_INFO *table_charset; /* Default charset of string fields */
+
+ MY_BITMAP all_set;
+ /*
+ Key which is used for looking-up table in table cache and in the list
+ of thread's temporary tables. Has the form of:
+ "database_name\0table_name\0" + optional part for temporary tables.
+
+ Note that all three 'table_cache_key', 'db' and 'table_name' members
+ must be set (and be non-zero) for tables in table cache. They also
+ should correspond to each other.
+ To ensure this one can use set_table_cache() methods.
+ */
+ LEX_STRING table_cache_key;
+ LEX_STRING db; /* Pointer to db */
+ LEX_STRING table_name; /* Table name (for open) */
+ LEX_STRING path; /* Path to .frm file (from datadir) */
+ LEX_STRING normalized_path; /* unpack_filename(path) */
+ LEX_STRING connect_string;
+
+ /*
+ Set of keys in use, implemented as a Bitmap.
+ Excludes keys disabled by ALTER TABLE ... DISABLE KEYS.
+ */
+ key_map keys_in_use;
+ key_map keys_for_keyread;
+ ha_rows min_rows, max_rows; /* create information */
+ ulong avg_row_length; /* create information */
+ ulong mysql_version; /* 0 if .frm is created before 5.0 */
+ ulong reclength; /* Recordlength */
+ /* Stored record length. No generated-only virtual fields are included */
+ ulong stored_rec_length;
+
+ plugin_ref db_plugin; /* storage engine plugin */
+ inline handlerton *db_type() const /* table_type for handler */
+ {
+ return is_view ? view_pseudo_hton :
+ db_plugin ? plugin_hton(db_plugin) : NULL;
+ }
+ enum row_type row_type; /* How rows are stored */
+ enum tmp_table_type tmp_table;
+
+ /** Transactional or not. */
+ enum ha_choice transactional;
+ /** Per-page checksums or not. */
+ enum ha_choice page_checksum;
+
+ uint key_block_size; /* create key_block_size, if used */
+ uint stats_sample_pages; /* number of pages to sample during
+ stats estimation, if used, otherwise 0. */
+ enum_stats_auto_recalc stats_auto_recalc; /* Automatic recalc of stats. */
+ uint null_bytes, last_null_bit_pos;
+ /*
+ Same as null_bytes, except that if there is only a 'delete-marker' in
+ the record then this value is 0.
+ */
+ uint null_bytes_for_compare;
+ uint fields; /* Number of fields */
+ /* Number of stored fields, generated-only virtual fields are not included */
+ uint stored_fields;
+ uint rec_buff_length; /* Size of table->record[] buffer */
+ uint keys, key_parts;
+ uint ext_key_parts; /* Total number of key parts in extended keys */
+ uint max_key_length, max_unique_length, total_key_length;
+ uint uniques; /* Number of UNIQUE index */
+ uint null_fields; /* number of null fields */
+ uint blob_fields; /* number of blob fields */
+ uint varchar_fields; /* number of varchar fields */
+ uint db_create_options; /* Create options from database */
+ uint db_options_in_use; /* Options in use */
+ uint db_record_offset; /* if HA_REC_IN_SEQ */
+ uint rowid_field_offset; /* Field_nr +1 to rowid field */
+ /* Primary key index number, used in TABLE::key_info[] */
+ uint primary_key;
+ uint next_number_index; /* autoincrement key number */
+ uint next_number_key_offset; /* autoinc keypart offset in a key */
+ uint next_number_keypart; /* autoinc keypart number in a key */
+ enum open_frm_error error; /* error from open_table_def() */
+ uint open_errno; /* error from open_table_def() */
+ uint column_bitmap_size;
+ uchar frm_version;
+ uint vfields; /* Number of computed (virtual) fields */
+ uint default_fields; /* Number of default fields */
+ bool use_ext_keys; /* Extended keys can be used */
+ bool null_field_first;
+ bool system; /* Set if system table (one record) */
+ bool crypted; /* If .frm file is crypted */
+ bool crashed;
+ bool is_view;
+ bool deleting; /* going to delete this table */
+ bool can_cmp_whole_record;
+ bool table_creation_was_logged;
+ ulong table_map_id; /* for row-based replication */
+
+ /*
+ Things that are incompatible between the stored version and the
+ current version. This is a set of HA_CREATE... bits that can be used
+ to modify create_info->used_fields for ALTER TABLE.
+ */
+ ulong incompatible_version;
+
+ /*
+ Cache for row-based replication table share checks that does not
+ need to be repeated. Possible values are: -1 when cache value is
+ not calculated yet, 0 when table *shall not* be replicated, 1 when
+ table *may* be replicated.
+ */
+ int cached_row_logging_check;
+
+ /* Name of the tablespace used for this table */
+ char *tablespace;
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ /* filled in when reading from frm */
+ bool auto_partitioned;
+ char *partition_info_str;
+ uint partition_info_str_len;
+ uint partition_info_buffer_size;
+ plugin_ref default_part_plugin;
+#endif
+
+ /**
+ Cache the checked structure of this table.
+
+ The pointer data is used to describe the structure that
+ a instance of the table must have. Each element of the
+ array specifies a field that must exist on the table.
+
+ The pointer is cached in order to perform the check only
+ once -- when the table is loaded from the disk.
+ */
+ const TABLE_FIELD_DEF *table_field_def_cache;
+
+ /** Main handler's share */
+ Handler_share *ha_share;
+
+ /** Instrumentation for this table share. */
+ PSI_table_share *m_psi;
+
+ /*
+ Set share's table cache key and update its db and table name appropriately.
+
+ SYNOPSIS
+ set_table_cache_key()
+ key_buff Buffer with already built table cache key to be
+ referenced from share.
+ key_length Key length.
+
+ NOTES
+ Since 'key_buff' buffer will be referenced from share it should has same
+ life-time as share itself.
+ This method automatically ensures that TABLE_SHARE::table_name/db have
+ appropriate values by using table cache key as their source.
+ */
+
+ void set_table_cache_key(char *key_buff, uint key_length)
+ {
+ table_cache_key.str= key_buff;
+ table_cache_key.length= key_length;
+ /*
+ Let us use the fact that the key is "db/0/table_name/0" + optional
+ part for temporary tables.
+ */
+ db.str= table_cache_key.str;
+ db.length= strlen(db.str);
+ table_name.str= db.str + db.length + 1;
+ table_name.length= strlen(table_name.str);
+ }
+
+
+ /*
+ Set share's table cache key and update its db and table name appropriately.
+
+ SYNOPSIS
+ set_table_cache_key()
+ key_buff Buffer to be used as storage for table cache key
+ (should be at least key_length bytes).
+ key Value for table cache key.
+ key_length Key length.
+
+ NOTE
+ Since 'key_buff' buffer will be used as storage for table cache key
+ it should has same life-time as share itself.
+ */
+
+ void set_table_cache_key(char *key_buff, const char *key, uint key_length)
+ {
+ memcpy(key_buff, key, key_length);
+ set_table_cache_key(key_buff, key_length);
+ }
+
+ inline bool honor_global_locks()
+ {
+ return ((table_category == TABLE_CATEGORY_USER)
+ || (table_category == TABLE_CATEGORY_SYSTEM));
+ }
+
+ inline bool require_write_privileges()
+ {
+ return (table_category == TABLE_CATEGORY_LOG);
+ }
+
+ inline ulong get_table_def_version()
+ {
+ return table_map_id;
+ }
+
+ /**
+ Convert unrelated members of TABLE_SHARE to one enum
+ representing its type.
+
+ @todo perhaps we need to have a member instead of a function.
+ */
+ enum enum_table_ref_type get_table_ref_type() const
+ {
+ if (is_view)
+ return TABLE_REF_VIEW;
+ switch (tmp_table) {
+ case NO_TMP_TABLE:
+ return TABLE_REF_BASE_TABLE;
+ case SYSTEM_TMP_TABLE:
+ return TABLE_REF_I_S_TABLE;
+ default:
+ return TABLE_REF_TMP_TABLE;
+ }
+ }
+ /**
+ Return a table metadata version.
+ * for base tables and views, we return table_map_id.
+ It is assigned from a global counter incremented for each
+ new table loaded into the table definition cache (TDC).
+ * for temporary tables it's table_map_id again. But for
+ temporary tables table_map_id is assigned from
+ thd->query_id. The latter is assigned from a thread local
+ counter incremented for every new SQL statement. Since
+ temporary tables are thread-local, each temporary table
+ gets a unique id.
+ * for everything else (e.g. information schema tables),
+ the version id is zero.
+
+ This choice of version id is a large compromise
+ to have a working prepared statement validation in 5.1. In
+ future version ids will be persistent, as described in WL#4180.
+
+ Let's try to explain why and how this limited solution allows
+ to validate prepared statements.
+
+ Firstly, sets (in mathematical sense) of version numbers
+ never intersect for different table types. Therefore,
+ version id of a temporary table is never compared with
+ a version id of a view, and vice versa.
+
+ Secondly, for base tables and views, we know that each DDL flushes
+ the respective share from the TDC. This ensures that whenever
+ a table is altered or dropped and recreated, it gets a new
+ version id.
+ Unfortunately, since elements of the TDC are also flushed on
+ LRU basis, this choice of version ids leads to false positives.
+ E.g. when the TDC size is too small, we may have a SELECT
+ * FROM INFORMATION_SCHEMA.TABLES flush all its elements, which
+ in turn will lead to a validation error and a subsequent
+ reprepare of all prepared statements. This is
+ considered acceptable, since as long as prepared statements are
+ automatically reprepared, spurious invalidation is only
+ a performance hit. Besides, no better simple solution exists.
+
+ For temporary tables, using thd->query_id ensures that if
+ a temporary table was altered or recreated, a new version id is
+ assigned. This suits validation needs very well and will perhaps
+ never change.
+
+ Metadata of information schema tables never changes.
+ Thus we can safely assume 0 for a good enough version id.
+
+ Finally, by taking into account table type, we always
+ track that a change has taken place when a view is replaced
+ with a base table, a base table is replaced with a temporary
+ table and so on.
+
+ @sa TABLE_LIST::is_table_ref_id_equal()
+ */
+ ulong get_table_ref_version() const
+ {
+ return (tmp_table == SYSTEM_TMP_TABLE) ? 0 : table_map_id;
+ }
+
+ bool visit_subgraph(Wait_for_flush *waiting_ticket,
+ MDL_wait_for_graph_visitor *gvisitor);
+
+ bool wait_for_old_version(THD *thd, struct timespec *abstime,
+ uint deadlock_weight);
+ /** Release resources and free memory occupied by the table share. */
+ void destroy();
+
+ void set_use_ext_keys_flag(bool fl)
+ {
+ use_ext_keys= fl;
+ }
+
+ uint actual_n_key_parts(THD *thd);
+
+ LEX_CUSTRING *frm_image; ///< only during CREATE TABLE (@sa ha_create_table)
+
+ /*
+ populates TABLE_SHARE from the table description in the binary frm image.
+ if 'write' is true, this frm image is also written into a corresponding
+ frm file, that serves as a persistent metadata cache to avoid
+ discovering the table over and over again
+ */
+ int init_from_binary_frm_image(THD *thd, bool write,
+ const uchar *frm_image, size_t frm_length);
+
+ /*
+ populates TABLE_SHARE from the table description, specified as the
+ complete CREATE TABLE sql statement.
+ if 'write' is true, this frm image is also written into a corresponding
+ frm file, that serves as a persistent metadata cache to avoid
+ discovering the table over and over again
+ */
+ int init_from_sql_statement_string(THD *thd, bool write,
+ const char *sql, size_t sql_length);
+ /*
+ writes the frm image to an frm file, corresponding to this table
+ */
+ bool write_frm_image(const uchar *frm_image, size_t frm_length);
+
+ bool write_frm_image(void)
+ { return frm_image ? write_frm_image(frm_image->str, frm_image->length) : 0; }
+
+ /*
+ returns an frm image for this table.
+ the memory is allocated and must be freed later
+ */
+ bool read_frm_image(const uchar **frm_image, size_t *frm_length);
+
+ /* frees the memory allocated in read_frm_image */
+ void free_frm_image(const uchar *frm);
+};
+
+
+/* Information for one open table */
+enum index_hint_type
+{
+ INDEX_HINT_IGNORE,
+ INDEX_HINT_USE,
+ INDEX_HINT_FORCE
+};
+
+struct st_cond_statistic;
+
+#define CHECK_ROW_FOR_NULLS_TO_REJECT (1 << 0)
+#define REJECT_ROW_DUE_TO_NULL_FIELDS (1 << 1)
+
+/* Bitmap of table's fields */
+typedef Bitmap<MAX_FIELDS> Field_map;
+
+struct TABLE
+{
+ TABLE() {} /* Remove gcc warning */
+
+ TABLE_SHARE *s;
+ handler *file;
+ TABLE *next, *prev;
+
+private:
+ /**
+ Links for the list of all TABLE objects for this share.
+ Declared as private to avoid direct manipulation with those objects.
+ One should use methods of I_P_List template instead.
+ */
+ TABLE *share_all_next, **share_all_prev;
+ friend struct All_share_tables;
+
+public:
+
+ THD *in_use; /* Which thread uses this */
+ /* Time when table was released to table cache. Valid for unused tables. */
+ ulonglong tc_time;
+ Field **field; /* Pointer to fields */
+
+ uchar *record[2]; /* Pointer to records */
+ uchar *write_row_record; /* Used as optimisation in
+ THD::write_row */
+ uchar *insert_values; /* used by INSERT ... UPDATE */
+ /*
+ Map of keys that can be used to retrieve all data from this table
+ needed by the query without reading the row.
+ */
+ key_map covering_keys;
+ key_map quick_keys, merge_keys,intersect_keys;
+ /*
+ A set of keys that can be used in the query that references this
+ table.
+
+ All indexes disabled on the table's TABLE_SHARE (see TABLE::s) will be
+ subtracted from this set upon instantiation. Thus for any TABLE t it holds
+ that t.keys_in_use_for_query is a subset of t.s.keys_in_use. Generally we
+ must not introduce any new keys here (see setup_tables).
+
+ The set is implemented as a bitmap.
+ */
+ key_map keys_in_use_for_query;
+ /* Map of keys that can be used to calculate GROUP BY without sorting */
+ key_map keys_in_use_for_group_by;
+ /* Map of keys that can be used to calculate ORDER BY without sorting */
+ key_map keys_in_use_for_order_by;
+ KEY *key_info; /* data of keys in database */
+
+ Field *next_number_field; /* Set if next_number is activated */
+ Field *found_next_number_field; /* Set on open */
+ Field **vfield; /* Pointer to virtual fields*/
+ /* Fields that are updated automatically on INSERT or UPDATE. */
+ Field **default_field;
+
+ /* Table's triggers, 0 if there are no of them */
+ Table_triggers_list *triggers;
+ TABLE_LIST *pos_in_table_list;/* Element referring to this table */
+ /* Position in thd->locked_table_list under LOCK TABLES */
+ TABLE_LIST *pos_in_locked_tables;
+ ORDER *group;
+ String alias; /* alias or table name */
+ uchar *null_flags;
+ MY_BITMAP def_read_set, def_write_set, def_vcol_set, tmp_set;
+ MY_BITMAP eq_join_set; /* used to mark equi-joined fields */
+ MY_BITMAP cond_set; /* used to mark fields from sargable conditions*/
+ MY_BITMAP *read_set, *write_set, *vcol_set; /* Active column sets */
+ /*
+ The ID of the query that opened and is using this table. Has different
+ meanings depending on the table type.
+
+ Temporary tables:
+
+ table->query_id is set to thd->query_id for the duration of a statement
+ and is reset to 0 once it is closed by the same statement. A non-zero
+ table->query_id means that a statement is using the table even if it's
+ not the current statement (table is in use by some outer statement).
+
+ Non-temporary tables:
+
+ Under pre-locked or LOCK TABLES mode: query_id is set to thd->query_id
+ for the duration of a statement and is reset to 0 once it is closed by
+ the same statement. A non-zero query_id is used to control which tables
+ in the list of pre-opened and locked tables are actually being used.
+ */
+ query_id_t query_id;
+
+ /*
+ This structure is used for statistical data on the table that
+ is collected by the function collect_statistics_for_table
+ */
+ Table_statistics *collected_stats;
+
+ /* The estimate of the number of records in the table used by optimizer */
+ ha_rows used_stat_records;
+
+ /*
+ For each key that has quick_keys.is_set(key) == TRUE: estimate of #records
+ and max #key parts that range access would use.
+ */
+ ha_rows quick_rows[MAX_KEY];
+
+ /*
+ Bitmaps of key parts that =const for the duration of join execution. If
+ we're in a subquery, then the constant may be different across subquery
+ re-executions.
+ */
+ key_part_map const_key_parts[MAX_KEY];
+
+ uint quick_key_parts[MAX_KEY];
+ uint quick_n_ranges[MAX_KEY];
+
+ /*
+ Estimate of number of records that satisfy SARGable part of the table
+ condition, or table->file->records if no SARGable condition could be
+ constructed.
+ This value is used by join optimizer as an estimate of number of records
+ that will pass the table condition (condition that depends on fields of
+ this table and constants)
+ */
+ ha_rows quick_condition_rows;
+
+ double cond_selectivity;
+ List<st_cond_statistic> *cond_selectivity_sampling_explain;
+
+ table_map map; /* ID bit of table (1,2,4,8,16...) */
+
+ uint lock_position; /* Position in MYSQL_LOCK.table */
+ uint lock_data_start; /* Start pos. in MYSQL_LOCK.locks */
+ uint lock_count; /* Number of locks */
+ uint tablenr,used_fields;
+ uint temp_pool_slot; /* Used by intern temp tables */
+ uint status; /* What's in record[0] */
+ uint db_stat; /* mode of file as in handler.h */
+ /* number of select if it is derived table */
+ uint derived_select_number;
+ /*
+ 0 or JOIN_TYPE_{LEFT|RIGHT}. Currently this is only compared to 0.
+ If maybe_null !=0, this table is inner w.r.t. some outer join operation,
+ and null_row may be true.
+ */
+ uint maybe_null;
+ int current_lock; /* Type of lock on table */
+ bool copy_blobs; /* copy_blobs when storing */
+ /*
+ Set if next_number_field is in the UPDATE fields of INSERT ... ON DUPLICATE
+ KEY UPDATE.
+ */
+ bool next_number_field_updated;
+
+ /*
+ If true, the current table row is considered to have all columns set to
+ NULL, including columns declared as "not null" (see maybe_null).
+ */
+ bool null_row;
+ /*
+ No rows that contain null values can be placed into this table.
+ Currently this flag can be set to true only for a temporary table
+ that used to store the result of materialization of a subquery.
+ */
+ bool no_rows_with_nulls;
+ /*
+ This field can contain two bit flags:
+ CHECK_ROW_FOR_NULLS_TO_REJECT
+ REJECT_ROW_DUE_TO_NULL_FIELDS
+ The first flag is set for the dynamic contexts where it is prohibited
+ to write any null into the table.
+ The second flag is set only if the first flag is set on.
+ The informs the outer scope that there was an attept to write null
+ into a field of the table in the context where it is prohibited.
+ This flag should be set off as soon as the first flag is set on.
+ Currently these flags are used only the tables tno_rows_with_nulls set
+ to true.
+ */
+ uint8 null_catch_flags;
+
+ /*
+ TODO: Each of the following flags take up 8 bits. They can just as easily
+ be put into one single unsigned long and instead of taking up 18
+ bytes, it would take up 4.
+ */
+ bool force_index;
+
+ /**
+ Flag set when the statement contains FORCE INDEX FOR ORDER BY
+ See TABLE_LIST::process_index_hints().
+ */
+ bool force_index_order;
+
+ /**
+ Flag set when the statement contains FORCE INDEX FOR GROUP BY
+ See TABLE_LIST::process_index_hints().
+ */
+ bool force_index_group;
+ /*
+ TRUE<=> this table was created with create_tmp_table(... distinct=TRUE..)
+ call
+ */
+ bool distinct;
+ bool const_table,no_rows, used_for_duplicate_elimination;
+ /**
+ Forces DYNAMIC Aria row format for internal temporary tables.
+ */
+ bool keep_row_order;
+
+ /**
+ If set, the optimizer has found that row retrieval should access index
+ tree only.
+ */
+ bool key_read;
+ bool no_keyread;
+ /**
+ If set, indicate that the table is not replicated by the server.
+ */
+ bool locked_by_logger;
+ bool no_replicate;
+ bool locked_by_name;
+ bool fulltext_searched;
+ bool no_cache;
+ /* To signal that the table is associated with a HANDLER statement */
+ bool open_by_handler;
+ /*
+ To indicate that a non-null value of the auto_increment field
+ was provided by the user or retrieved from the current record.
+ Used only in the MODE_NO_AUTO_VALUE_ON_ZERO mode.
+ */
+ bool auto_increment_field_not_null;
+ bool insert_or_update; /* Can be used by the handler */
+ bool alias_name_used; /* true if table_name is alias */
+ bool get_fields_in_item_tree; /* Signal to fix_field */
+ bool m_needs_reopen;
+ bool created; /* For tmp tables. TRUE <=> tmp table was actually created.*/
+#ifdef HAVE_REPLICATION
+ /* used in RBR Triggers */
+ bool master_had_triggers;
+#endif
+
+ REGINFO reginfo; /* field connections */
+ MEM_ROOT mem_root;
+ GRANT_INFO grant;
+ Filesort_info sort;
+ /*
+ The arena which the items for expressions from the table definition
+ are associated with.
+ Currently only the items of the expressions for virtual columns are
+ associated with this arena.
+ TODO: To attach the partitioning expressions to this arena.
+ */
+ Query_arena *expr_arena;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info; /* Partition related information */
+ /* If true, all partitions have been pruned away */
+ bool all_partitions_pruned_away;
+#endif
+ uint max_keys; /* Size of allocated key_info array. */
+ bool stats_is_read; /* Persistent statistics is read for the table */
+ bool histograms_are_read;
+ MDL_ticket *mdl_ticket;
+
+ void init(THD *thd, TABLE_LIST *tl);
+ bool fill_item_list(List<Item> *item_list) const;
+ void reset_item_list(List<Item> *item_list) const;
+ void clear_column_bitmaps(void);
+ void prepare_for_position(void);
+ void mark_columns_used_by_index_no_reset(uint index, MY_BITMAP *map);
+ void mark_columns_used_by_index(uint index);
+ void add_read_columns_used_by_index(uint index);
+ void restore_column_maps_after_mark_index();
+ void mark_auto_increment_column(void);
+ void mark_columns_needed_for_update(void);
+ void mark_columns_needed_for_delete(void);
+ void mark_columns_needed_for_insert(void);
+ bool mark_virtual_col(Field *field);
+ void mark_virtual_columns_for_write(bool insert_fl);
+ void mark_default_fields_for_write();
+ bool has_default_function(bool is_update);
+ inline void column_bitmaps_set(MY_BITMAP *read_set_arg,
+ MY_BITMAP *write_set_arg)
+ {
+ read_set= read_set_arg;
+ write_set= write_set_arg;
+ if (file)
+ file->column_bitmaps_signal();
+ }
+ inline void column_bitmaps_set(MY_BITMAP *read_set_arg,
+ MY_BITMAP *write_set_arg,
+ MY_BITMAP *vcol_set_arg)
+ {
+ read_set= read_set_arg;
+ write_set= write_set_arg;
+ vcol_set= vcol_set_arg;
+ if (file)
+ file->column_bitmaps_signal();
+ }
+ inline void column_bitmaps_set_no_signal(MY_BITMAP *read_set_arg,
+ MY_BITMAP *write_set_arg)
+ {
+ read_set= read_set_arg;
+ write_set= write_set_arg;
+ }
+ inline void column_bitmaps_set_no_signal(MY_BITMAP *read_set_arg,
+ MY_BITMAP *write_set_arg,
+ MY_BITMAP *vcol_set_arg)
+ {
+ read_set= read_set_arg;
+ write_set= write_set_arg;
+ vcol_set= vcol_set_arg;
+ }
+ inline void use_all_columns()
+ {
+ column_bitmaps_set(&s->all_set, &s->all_set);
+ }
+ inline void default_column_bitmaps()
+ {
+ read_set= &def_read_set;
+ write_set= &def_write_set;
+ vcol_set= &def_vcol_set;
+ }
+ /** Should this instance of the table be reopened? */
+ inline bool needs_reopen()
+ { return !db_stat || m_needs_reopen; }
+
+ bool alloc_keys(uint key_count);
+ bool check_tmp_key(uint key, uint key_parts,
+ uint (*next_field_no) (uchar *), uchar *arg);
+ bool add_tmp_key(uint key, uint key_parts,
+ uint (*next_field_no) (uchar *), uchar *arg,
+ bool unique);
+ void create_key_part_by_field(KEY_PART_INFO *key_part_info,
+ Field *field, uint fieldnr);
+ void use_index(int key_to_save);
+ void set_table_map(table_map map_arg, uint tablenr_arg)
+ {
+ map= map_arg;
+ tablenr= tablenr_arg;
+ }
+ inline void enable_keyread()
+ {
+ DBUG_ENTER("enable_keyread");
+ DBUG_ASSERT(key_read == 0);
+ key_read= 1;
+ file->extra(HA_EXTRA_KEYREAD);
+ DBUG_VOID_RETURN;
+ }
+ /*
+ Returns TRUE if the table is filled at execution phase (and so, the
+ optimizer must not do anything that depends on the contents of the table,
+ like range analysis or constant table detection)
+ */
+ bool is_filled_at_execution();
+ inline void disable_keyread()
+ {
+ DBUG_ENTER("disable_keyread");
+ if (key_read)
+ {
+ key_read= 0;
+ file->extra(HA_EXTRA_NO_KEYREAD);
+ }
+ DBUG_VOID_RETURN;
+ }
+
+ bool update_const_key_parts(COND *conds);
+ uint actual_n_key_parts(KEY *keyinfo);
+ ulong actual_key_flags(KEY *keyinfo);
+ int update_default_fields();
+ void reset_default_fields();
+ inline ha_rows stat_records() { return used_stat_records; }
+
+ void prepare_triggers_for_insert_stmt_or_event();
+ bool prepare_triggers_for_delete_stmt_or_event();
+ bool prepare_triggers_for_update_stmt_or_event();
+};
+
+
+/**
+ Helper class which specifies which members of TABLE are used for
+ participation in the list of used/unused TABLE objects for the share.
+*/
+
+struct TABLE_share
+{
+ static inline TABLE **next_ptr(TABLE *l)
+ {
+ return &l->next;
+ }
+ static inline TABLE ***prev_ptr(TABLE *l)
+ {
+ return (TABLE ***) &l->prev;
+ }
+};
+
+
+struct All_share_tables
+{
+ static inline TABLE **next_ptr(TABLE *l)
+ {
+ return &l->share_all_next;
+ }
+ static inline TABLE ***prev_ptr(TABLE *l)
+ {
+ return &l->share_all_prev;
+ }
+};
+
+
+enum enum_schema_table_state
+{
+ NOT_PROCESSED= 0,
+ PROCESSED_BY_CREATE_SORT_INDEX,
+ PROCESSED_BY_JOIN_EXEC
+};
+
+typedef struct st_foreign_key_info
+{
+ LEX_STRING *foreign_id;
+ LEX_STRING *foreign_db;
+ LEX_STRING *foreign_table;
+ LEX_STRING *referenced_db;
+ LEX_STRING *referenced_table;
+ LEX_STRING *update_method;
+ LEX_STRING *delete_method;
+ LEX_STRING *referenced_key_name;
+ List<LEX_STRING> foreign_fields;
+ List<LEX_STRING> referenced_fields;
+} FOREIGN_KEY_INFO;
+
+#define MY_I_S_MAYBE_NULL 1
+#define MY_I_S_UNSIGNED 2
+
+
+#define SKIP_OPEN_TABLE 0 // do not open table
+#define OPEN_FRM_ONLY 1 // open FRM file only
+#define OPEN_FULL_TABLE 2 // open FRM,MYD, MYI files
+
+typedef struct st_field_info
+{
+ /**
+ This is used as column name.
+ */
+ const char* field_name;
+ /**
+ For string-type columns, this is the maximum number of
+ characters. Otherwise, it is the 'display-length' for the column.
+ */
+ uint field_length;
+ /**
+ This denotes data type for the column. For the most part, there seems to
+ be one entry in the enum for each SQL data type, although there seem to
+ be a number of additional entries in the enum.
+ */
+ enum enum_field_types field_type;
+ int value;
+ /**
+ This is used to set column attributes. By default, columns are @c NOT
+ @c NULL and @c SIGNED, and you can deviate from the default
+ by setting the appopriate flags. You can use either one of the flags
+ @c MY_I_S_MAYBE_NULL and @cMY_I_S_UNSIGNED or
+ combine them using the bitwise or operator @c |. Both flags are
+ defined in table.h.
+ */
+ uint field_flags; // Field atributes(maybe_null, signed, unsigned etc.)
+ const char* old_name;
+ /**
+ This should be one of @c SKIP_OPEN_TABLE,
+ @c OPEN_FRM_ONLY or @c OPEN_FULL_TABLE.
+ */
+ uint open_method;
+} ST_FIELD_INFO;
+
+
+struct TABLE_LIST;
+typedef class Item COND;
+
+typedef struct st_schema_table
+{
+ const char* table_name;
+ ST_FIELD_INFO *fields_info;
+ /* Create information_schema table */
+ TABLE *(*create_table) (THD *thd, TABLE_LIST *table_list);
+ /* Fill table with data */
+ int (*fill_table) (THD *thd, TABLE_LIST *tables, COND *cond);
+ /* Handle fileds for old SHOW */
+ int (*old_format) (THD *thd, struct st_schema_table *schema_table);
+ int (*process_table) (THD *thd, TABLE_LIST *tables, TABLE *table,
+ bool res, LEX_STRING *db_name, LEX_STRING *table_name);
+ int idx_field1, idx_field2;
+ bool hidden;
+ uint i_s_requested_object; /* the object we need to open(TABLE | VIEW) */
+} ST_SCHEMA_TABLE;
+
+
+/*
+ Types of derived tables. The ending part is a bitmap of phases that are
+ applicable to a derived table of the type.
+*/
+#define DTYPE_ALGORITHM_UNDEFINED 0
+#define DTYPE_VIEW 1
+#define DTYPE_TABLE 2
+#define DTYPE_MERGE 4
+#define DTYPE_MATERIALIZE 8
+#define DTYPE_MULTITABLE 16
+#define DTYPE_MASK 19
+
+/*
+ Phases of derived tables/views handling, see sql_derived.cc
+ Values are used as parts of a bitmap attached to derived table types.
+*/
+#define DT_INIT 1
+#define DT_PREPARE 2
+#define DT_OPTIMIZE 4
+#define DT_MERGE 8
+#define DT_MERGE_FOR_INSERT 16
+#define DT_CREATE 32
+#define DT_FILL 64
+#define DT_REINIT 128
+#define DT_PHASES 8
+/* Phases that are applicable to all derived tables. */
+#define DT_COMMON (DT_INIT + DT_PREPARE + DT_REINIT + DT_OPTIMIZE)
+/* Phases that are applicable only to materialized derived tables. */
+#define DT_MATERIALIZE (DT_CREATE + DT_FILL)
+
+#define DT_PHASES_MERGE (DT_COMMON | DT_MERGE | DT_MERGE_FOR_INSERT)
+#define DT_PHASES_MATERIALIZE (DT_COMMON | DT_MATERIALIZE)
+
+#define VIEW_ALGORITHM_UNDEFINED 0
+#define VIEW_ALGORITHM_MERGE (DTYPE_VIEW | DTYPE_MERGE)
+#define VIEW_ALGORITHM_TMPTABLE (DTYPE_VIEW | DTYPE_MATERIALIZE)
+
+/*
+ View algorithm values as stored in the FRM. Values differ from in-memory
+ representation for backward compatibility.
+*/
+
+#define VIEW_ALGORITHM_UNDEFINED_FRM 0
+#define VIEW_ALGORITHM_MERGE_FRM 1
+#define VIEW_ALGORITHM_TMPTABLE_FRM 2
+
+#define JOIN_TYPE_LEFT 1
+#define JOIN_TYPE_RIGHT 2
+#define JOIN_TYPE_OUTER 4 /* Marker that this is an outer join */
+
+#define VIEW_SUID_INVOKER 0
+#define VIEW_SUID_DEFINER 1
+#define VIEW_SUID_DEFAULT 2
+
+/* view WITH CHECK OPTION parameter options */
+#define VIEW_CHECK_NONE 0
+#define VIEW_CHECK_LOCAL 1
+#define VIEW_CHECK_CASCADED 2
+
+/* result of view WITH CHECK OPTION parameter check */
+#define VIEW_CHECK_OK 0
+#define VIEW_CHECK_ERROR 1
+#define VIEW_CHECK_SKIP 2
+
+/** The threshold size a blob field buffer before it is freed */
+#define MAX_TDC_BLOB_SIZE 65536
+
+class select_union;
+class TMP_TABLE_PARAM;
+
+Item *create_view_field(THD *thd, TABLE_LIST *view, Item **field_ref,
+ const char *name);
+
+struct Field_translator
+{
+ Item *item;
+ const char *name;
+};
+
+
+/*
+ Column reference of a NATURAL/USING join. Since column references in
+ joins can be both from views and stored tables, may point to either a
+ Field (for tables), or a Field_translator (for views).
+*/
+
+class Natural_join_column: public Sql_alloc
+{
+public:
+ Field_translator *view_field; /* Column reference of merge view. */
+ Item_field *table_field; /* Column reference of table or temp view. */
+ TABLE_LIST *table_ref; /* Original base table/view reference. */
+ /*
+ True if a common join column of two NATURAL/USING join operands. Notice
+ that when we have a hierarchy of nested NATURAL/USING joins, a column can
+ be common at some level of nesting but it may not be common at higher
+ levels of nesting. Thus this flag may change depending on at which level
+ we are looking at some column.
+ */
+ bool is_common;
+public:
+ Natural_join_column(Field_translator *field_param, TABLE_LIST *tab);
+ Natural_join_column(Item_field *field_param, TABLE_LIST *tab);
+ const char *name();
+ Item *create_item(THD *thd);
+ Field *field();
+ const char *table_name();
+ const char *db_name();
+ GRANT_INFO *grant();
+};
+
+
+/**
+ Type of table which can be open for an element of table list.
+*/
+
+enum enum_open_type
+{
+ OT_TEMPORARY_OR_BASE= 0, OT_TEMPORARY_ONLY, OT_BASE_ONLY
+};
+
+
+class SJ_MATERIALIZATION_INFO;
+class Index_hint;
+class Item_in_subselect;
+
+
+/*
+ Table reference in the FROM clause.
+
+ These table references can be of several types that correspond to
+ different SQL elements. Below we list all types of TABLE_LISTs with
+ the necessary conditions to determine when a TABLE_LIST instance
+ belongs to a certain type.
+
+ 1) table (TABLE_LIST::view == NULL)
+ - base table
+ (TABLE_LIST::derived == NULL)
+ - FROM-clause subquery - TABLE_LIST::table is a temp table
+ (TABLE_LIST::derived != NULL)
+ - information schema table
+ (TABLE_LIST::schema_table != NULL)
+ NOTICE: for schema tables TABLE_LIST::field_translation may be != NULL
+ 2) view (TABLE_LIST::view != NULL)
+ - merge (TABLE_LIST::effective_algorithm == VIEW_ALGORITHM_MERGE)
+ also (TABLE_LIST::field_translation != NULL)
+ - tmptable (TABLE_LIST::effective_algorithm == VIEW_ALGORITHM_TMPTABLE)
+ also (TABLE_LIST::field_translation == NULL)
+ 2.5) TODO: Add derived tables description here
+ 3) nested table reference (TABLE_LIST::nested_join != NULL)
+ - table sequence - e.g. (t1, t2, t3)
+ TODO: how to distinguish from a JOIN?
+ - general JOIN
+ TODO: how to distinguish from a table sequence?
+ - NATURAL JOIN
+ (TABLE_LIST::natural_join != NULL)
+ - JOIN ... USING
+ (TABLE_LIST::join_using_fields != NULL)
+ - semi-join nest (sj_on_expr!= NULL && sj_subq_pred!=NULL)
+ 4) jtbm semi-join (jtbm_subselect != NULL)
+*/
+
+struct LEX;
+class Index_hint;
+struct TABLE_LIST
+{
+ TABLE_LIST() {} /* Remove gcc warning */
+
+ /**
+ Prepare TABLE_LIST that consists of one table instance to use in
+ open_and_lock_tables
+ */
+ inline void init_one_table(const char *db_name_arg,
+ size_t db_length_arg,
+ const char *table_name_arg,
+ size_t table_name_length_arg,
+ const char *alias_arg,
+ enum thr_lock_type lock_type_arg)
+ {
+ bzero((char*) this, sizeof(*this));
+ db= (char*) db_name_arg;
+ db_length= db_length_arg;
+ table_name= (char*) table_name_arg;
+ table_name_length= table_name_length_arg;
+ alias= (char*) (alias_arg ? alias_arg : table_name_arg);
+ lock_type= lock_type_arg;
+ mdl_request.init(MDL_key::TABLE, db, table_name,
+ (lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ,
+ MDL_TRANSACTION);
+ }
+
+ /*
+ List of tables local to a subquery (used by SQL_I_List). Considers
+ views as leaves (unlike 'next_leaf' below). Created at parse time
+ in st_select_lex::add_table_to_list() -> table_list.link_in_list().
+ */
+ TABLE_LIST *next_local;
+ /* link in a global list of all queries tables */
+ TABLE_LIST *next_global, **prev_global;
+ char *db, *alias, *table_name, *schema_table_name;
+ char *option; /* Used by cache index */
+ Item *on_expr; /* Used with outer join */
+
+ Item *sj_on_expr;
+ /*
+ (Valid only for semi-join nests) Bitmap of tables that are within the
+ semi-join (this is different from bitmap of all nest's children because
+ tables that were pulled out of the semi-join nest remain listed as
+ nest's children).
+ */
+ table_map sj_inner_tables;
+ /* Number of IN-compared expressions */
+ uint sj_in_exprs;
+
+ /* If this is a non-jtbm semi-join nest: corresponding subselect predicate */
+ Item_in_subselect *sj_subq_pred;
+
+ table_map original_subq_pred_used_tables;
+
+ /* If this is a jtbm semi-join object: corresponding subselect predicate */
+ Item_in_subselect *jtbm_subselect;
+ /* TODO: check if this can be joined with tablenr_exec */
+ uint jtbm_table_no;
+
+ SJ_MATERIALIZATION_INFO *sj_mat_info;
+
+ /*
+ The structure of ON expression presented in the member above
+ can be changed during certain optimizations. This member
+ contains a snapshot of AND-OR structure of the ON expression
+ made after permanent transformations of the parse tree, and is
+ used to restore ON clause before every reexecution of a prepared
+ statement or stored procedure.
+ */
+ Item *prep_on_expr;
+ COND_EQUAL *cond_equal; /* Used with outer join */
+ /*
+ During parsing - left operand of NATURAL/USING join where 'this' is
+ the right operand. After parsing (this->natural_join == this) iff
+ 'this' represents a NATURAL or USING join operation. Thus after
+ parsing 'this' is a NATURAL/USING join iff (natural_join != NULL).
+ */
+ TABLE_LIST *natural_join;
+ /*
+ True if 'this' represents a nested join that is a NATURAL JOIN.
+ For one of the operands of 'this', the member 'natural_join' points
+ to the other operand of 'this'.
+ */
+ bool is_natural_join;
+ /* Field names in a USING clause for JOIN ... USING. */
+ List<String> *join_using_fields;
+ /*
+ Explicitly store the result columns of either a NATURAL/USING join or
+ an operand of such a join.
+ */
+ List<Natural_join_column> *join_columns;
+ /* TRUE if join_columns contains all columns of this table reference. */
+ bool is_join_columns_complete;
+
+ /*
+ List of nodes in a nested join tree, that should be considered as
+ leaves with respect to name resolution. The leaves are: views,
+ top-most nodes representing NATURAL/USING joins, subqueries, and
+ base tables. All of these TABLE_LIST instances contain a
+ materialized list of columns. The list is local to a subquery.
+ */
+ TABLE_LIST *next_name_resolution_table;
+ /* Index names in a "... JOIN ... USE/IGNORE INDEX ..." clause. */
+ List<Index_hint> *index_hints;
+ TABLE *table; /* opened table */
+ uint table_id; /* table id (from binlog) for opened table */
+ /*
+ select_result for derived table to pass it from table creation to table
+ filling procedure
+ */
+ select_union *derived_result;
+ /* Stub used for materialized derived tables. */
+ table_map map; /* ID bit of table (1,2,4,8,16...) */
+ table_map get_map()
+ {
+ return jtbm_subselect? table_map(1) << jtbm_table_no : table->map;
+ }
+ uint get_tablenr()
+ {
+ return jtbm_subselect? jtbm_table_no : table->tablenr;
+ }
+ void set_tablenr(uint new_tablenr)
+ {
+ if (jtbm_subselect)
+ {
+ jtbm_table_no= new_tablenr;
+ }
+ if (table)
+ {
+ table->tablenr= new_tablenr;
+ table->map= table_map(1) << new_tablenr;
+ }
+ }
+ /*
+ Reference from aux_tables to local list entry of main select of
+ multi-delete statement:
+ delete t1 from t2,t1 where t1.a<'B' and t2.b=t1.b;
+ here it will be reference of first occurrence of t1 to second (as you
+ can see this lists can't be merged)
+ */
+ TABLE_LIST *correspondent_table;
+ /**
+ @brief Normally, this field is non-null for anonymous derived tables only.
+
+ @details This field is set to non-null for
+
+ - Anonymous derived tables, In this case it points to the SELECT_LEX_UNIT
+ representing the derived table. E.g. for a query
+
+ @verbatim SELECT * FROM (SELECT a FROM t1) b @endverbatim
+
+ For the @c TABLE_LIST representing the derived table @c b, @c derived
+ points to the SELECT_LEX_UNIT representing the result of the query within
+ parenteses.
+
+ - Views. This is set for views with @verbatim ALGORITHM = TEMPTABLE
+ @endverbatim by mysql_make_view().
+
+ @note Inside views, a subquery in the @c FROM clause is not allowed.
+ @note Do not use this field to separate views/base tables/anonymous
+ derived tables. Use TABLE_LIST::is_anonymous_derived_table().
+ */
+ st_select_lex_unit *derived; /* SELECT_LEX_UNIT of derived table */
+ ST_SCHEMA_TABLE *schema_table; /* Information_schema table */
+ st_select_lex *schema_select_lex;
+ /*
+ True when the view field translation table is used to convert
+ schema table fields for backwards compatibility with SHOW command.
+ */
+ bool schema_table_reformed;
+ TMP_TABLE_PARAM *schema_table_param;
+ /* link to select_lex where this table was used */
+ st_select_lex *select_lex;
+ LEX *view; /* link on VIEW lex for merging */
+ Field_translator *field_translation; /* array of VIEW fields */
+ /* pointer to element after last one in translation table above */
+ Field_translator *field_translation_end;
+ bool field_translation_updated;
+ /*
+ List (based on next_local) of underlying tables of this view. I.e. it
+ does not include the tables of subqueries used in the view. Is set only
+ for merged views.
+ */
+ TABLE_LIST *merge_underlying_list;
+ /*
+ - 0 for base tables
+ - in case of the view it is the list of all (not only underlying
+ tables but also used in subquery ones) tables of the view.
+ */
+ List<TABLE_LIST> *view_tables;
+ /* most upper view this table belongs to */
+ TABLE_LIST *belong_to_view;
+ /* A derived table this table belongs to */
+ TABLE_LIST *belong_to_derived;
+ /*
+ The view directly referencing this table
+ (non-zero only for merged underlying tables of a view).
+ */
+ TABLE_LIST *referencing_view;
+
+ table_map view_used_tables;
+ table_map map_exec;
+ /* TODO: check if this can be joined with jtbm_table_no */
+ uint tablenr_exec;
+ uint maybe_null_exec;
+
+ /* Ptr to parent MERGE table list item. See top comment in ha_myisammrg.cc */
+ TABLE_LIST *parent_l;
+ /*
+ Security context (non-zero only for tables which belong
+ to view with SQL SECURITY DEFINER)
+ */
+ Security_context *security_ctx;
+ /*
+ This view security context (non-zero only for views with
+ SQL SECURITY DEFINER)
+ */
+ Security_context *view_sctx;
+ bool allowed_show;
+ Item *where; /* VIEW WHERE clause condition */
+ Item *check_option; /* WITH CHECK OPTION condition */
+ LEX_STRING select_stmt; /* text of (CREATE/SELECT) statement */
+ LEX_STRING md5; /* md5 of query text */
+ LEX_STRING source; /* source of CREATE VIEW */
+ LEX_STRING view_db; /* saved view database */
+ LEX_STRING view_name; /* saved view name */
+ LEX_STRING timestamp; /* GMT time stamp of last operation */
+ st_lex_user definer; /* definer of view */
+ ulonglong file_version; /* version of file's field set */
+ ulonglong mariadb_version; /* version of server on creation */
+ ulonglong updatable_view; /* VIEW can be updated */
+ /**
+ @brief The declared algorithm, if this is a view.
+ @details One of
+ - VIEW_ALGORITHM_UNDEFINED
+ - VIEW_ALGORITHM_TMPTABLE
+ - VIEW_ALGORITHM_MERGE
+ @to do Replace with an enum
+ */
+ ulonglong algorithm;
+ ulonglong view_suid; /* view is suid (TRUE dy default) */
+ ulonglong with_check; /* WITH CHECK OPTION */
+ /*
+ effective value of WITH CHECK OPTION (differ for temporary table
+ algorithm)
+ */
+ uint8 effective_with_check;
+ /**
+ @brief The view algorithm that is actually used, if this is a view.
+ @details One of
+ - VIEW_ALGORITHM_UNDEFINED
+ - VIEW_ALGORITHM_TMPTABLE
+ - VIEW_ALGORITHM_MERGE
+ @to do Replace with an enum
+ */
+ uint8 derived_type;
+ GRANT_INFO grant;
+ /* data need by some engines in query cache*/
+ ulonglong engine_data;
+ /* call back function for asking handler about caching in query cache */
+ qc_engine_callback callback_func;
+ thr_lock_type lock_type;
+ uint outer_join; /* Which join type */
+ uint shared; /* Used in multi-upd */
+ size_t db_length;
+ size_t table_name_length;
+ bool updatable; /* VIEW/TABLE can be updated now */
+ bool straight; /* optimize with prev table */
+ bool updating; /* for replicate-do/ignore table */
+ bool force_index; /* prefer index over table scan */
+ bool ignore_leaves; /* preload only non-leaf nodes */
+ table_map dep_tables; /* tables the table depends on */
+ table_map on_expr_dep_tables; /* tables on expression depends on */
+ struct st_nested_join *nested_join; /* if the element is a nested join */
+ TABLE_LIST *embedding; /* nested join containing the table */
+ List<TABLE_LIST> *join_list;/* join list the table belongs to */
+ bool lifted; /* set to true when the table is moved to
+ the upper level at the parsing stage */
+ bool cacheable_table; /* stop PS caching */
+ /* used in multi-upd/views privilege check */
+ bool table_in_first_from_clause;
+ /**
+ Specifies which kind of table should be open for this element
+ of table list.
+ */
+ enum enum_open_type open_type;
+ /* TRUE if this merged view contain auto_increment field */
+ bool contain_auto_increment;
+ bool compact_view_format; /* Use compact format for SHOW CREATE VIEW */
+ /* view where processed */
+ bool where_processed;
+ /* TRUE <=> VIEW CHECK OPTION expression has been processed */
+ bool check_option_processed;
+ /* FRMTYPE_ERROR if any type is acceptable */
+ enum frm_type_enum required_type;
+ handlerton *db_type; /* table_type for handler */
+ char timestamp_buffer[20]; /* buffer for timestamp (19+1) */
+ /*
+ This TABLE_LIST object is just placeholder for prelocking, it will be
+ used for implicit LOCK TABLES only and won't be used in real statement.
+ */
+ bool prelocking_placeholder;
+ /**
+ Indicates that if TABLE_LIST object corresponds to the table/view
+ which requires special handling.
+ */
+ enum enum_open_strategy
+ {
+ /* Normal open. */
+ OPEN_NORMAL= 0,
+ /* Associate a table share only if the the table exists. */
+ OPEN_IF_EXISTS,
+ /* Don't associate a table share. */
+ OPEN_STUB
+ } open_strategy;
+ /* For transactional locking. */
+ int lock_timeout; /* NOWAIT or WAIT [X] */
+ bool lock_transactional; /* If transactional lock requested. */
+ /** TRUE if an alias for this table was specified in the SQL. */
+ bool is_alias;
+ /** TRUE if the table is referred to in the statement using a fully
+ qualified name (<db_name>.<table_name>).
+ */
+ bool is_fqtn;
+
+ bool deleting; /* going to delete this table */
+
+ /* TRUE <=> derived table should be filled right after optimization. */
+ bool fill_me;
+ /* TRUE <=> view/DT is merged. */
+ /* TODO: replace with derived_type */
+ bool merged;
+ bool merged_for_insert;
+ /* TRUE <=> don't prepare this derived table/view as it should be merged.*/
+ bool skip_prepare_derived;
+
+ /*
+ Items created by create_view_field and collected to change them in case
+ of materialization of the view/derived table
+ */
+ List<Item> used_items;
+ /* Sublist (tail) of persistent used_items */
+ List<Item> persistent_used_items;
+ Item **materialized_items;
+
+ /* View creation context. */
+
+ View_creation_ctx *view_creation_ctx;
+
+ /*
+ Attributes to save/load view creation context in/from frm-file.
+
+ Ther are required only to be able to use existing parser to load
+ view-definition file. As soon as the parser parsed the file, view
+ creation context is initialized and the attributes become redundant.
+
+ These attributes MUST NOT be used for any purposes but the parsing.
+ */
+
+ LEX_STRING view_client_cs_name;
+ LEX_STRING view_connection_cl_name;
+
+ /*
+ View definition (SELECT-statement) in the UTF-form.
+ */
+
+ LEX_STRING view_body_utf8;
+
+ /* End of view definition context. */
+
+ /**
+ Indicates what triggers we need to pre-load for this TABLE_LIST
+ when opening an associated TABLE. This is filled after
+ the parsed tree is created.
+ */
+ uint8 trg_event_map;
+ /* TRUE <=> this table is a const one and was optimized away. */
+ bool optimized_away;
+
+ uint i_s_requested_object;
+ bool has_db_lookup_value;
+ bool has_table_lookup_value;
+ uint table_open_method;
+ enum enum_schema_table_state schema_table_state;
+
+ MDL_request mdl_request;
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ /* List to carry partition names from PARTITION (...) clause in statement */
+ List<String> *partition_names;
+#endif /* WITH_PARTITION_STORAGE_ENGINE */
+
+ void calc_md5(char *buffer);
+ int view_check_option(THD *thd, bool ignore_failure);
+ bool create_field_translation(THD *thd);
+ bool setup_underlying(THD *thd);
+ void cleanup_items();
+ bool placeholder()
+ {
+ return derived || view || schema_table || !table;
+ }
+ void print(THD *thd, table_map eliminated_tables, String *str,
+ enum_query_type query_type);
+ bool check_single_table(TABLE_LIST **table, table_map map,
+ TABLE_LIST *view);
+ bool set_insert_values(MEM_ROOT *mem_root);
+ void hide_view_error(THD *thd);
+ TABLE_LIST *find_underlying_table(TABLE *table);
+ TABLE_LIST *first_leaf_for_name_resolution();
+ TABLE_LIST *last_leaf_for_name_resolution();
+ /**
+ @brief
+ Find the bottom in the chain of embedded table VIEWs.
+
+ @detail
+ This is used for single-table UPDATE/DELETE when they are modifying a
+ single-table VIEW.
+ */
+ TABLE_LIST *find_table_for_update()
+ {
+ TABLE_LIST *tbl= this;
+ while(!tbl->is_multitable() && tbl->single_table_updatable() &&
+ tbl->merge_underlying_list)
+ {
+ tbl= tbl->merge_underlying_list;
+ }
+ return tbl;
+ }
+ TABLE *get_real_join_table();
+ bool is_leaf_for_name_resolution();
+ inline TABLE_LIST *top_table()
+ { return belong_to_view ? belong_to_view : this; }
+ inline bool prepare_check_option(THD *thd)
+ {
+ bool res= FALSE;
+ if (effective_with_check)
+ res= prep_check_option(thd, effective_with_check);
+ return res;
+ }
+ inline bool prepare_where(THD *thd, Item **conds,
+ bool no_where_clause)
+ {
+ if (!view || is_merged_derived())
+ return prep_where(thd, conds, no_where_clause);
+ return FALSE;
+ }
+
+ void register_want_access(ulong want_access);
+ bool prepare_security(THD *thd);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *find_view_security_context(THD *thd);
+ bool prepare_view_security_context(THD *thd);
+#endif
+ /*
+ Cleanup for re-execution in a prepared statement or a stored
+ procedure.
+ */
+ void reinit_before_use(THD *thd);
+ Item_subselect *containing_subselect();
+
+ /*
+ Compiles the tagged hints list and fills up TABLE::keys_in_use_for_query,
+ TABLE::keys_in_use_for_group_by, TABLE::keys_in_use_for_order_by,
+ TABLE::force_index and TABLE::covering_keys.
+ */
+ bool process_index_hints(TABLE *table);
+
+ /**
+ Compare the version of metadata from the previous execution
+ (if any) with values obtained from the current table
+ definition cache element.
+
+ @sa check_and_update_table_version()
+ */
+ inline
+ bool is_table_ref_id_equal(TABLE_SHARE *s) const
+ {
+ return (m_table_ref_type == s->get_table_ref_type() &&
+ m_table_ref_version == s->get_table_ref_version());
+ }
+
+ /**
+ Record the value of metadata version of the corresponding
+ table definition cache element in this parse tree node.
+
+ @sa check_and_update_table_version()
+ */
+ inline
+ void set_table_ref_id(TABLE_SHARE *s)
+ { set_table_ref_id(s->get_table_ref_type(), s->get_table_ref_version()); }
+
+ inline
+ void set_table_ref_id(enum_table_ref_type table_ref_type_arg,
+ ulong table_ref_version_arg)
+ {
+ m_table_ref_type= table_ref_type_arg;
+ m_table_ref_version= table_ref_version_arg;
+ }
+
+ /* Set of functions returning/setting state of a derived table/view. */
+ inline bool is_non_derived()
+ {
+ return (!derived_type);
+ }
+ inline bool is_view_or_derived()
+ {
+ return (derived_type);
+ }
+ inline bool is_view()
+ {
+ return (derived_type & DTYPE_VIEW);
+ }
+ inline bool is_derived()
+ {
+ return (derived_type & DTYPE_TABLE);
+ }
+ inline void set_view()
+ {
+ derived_type= DTYPE_VIEW;
+ }
+ inline void set_derived()
+ {
+ derived_type= DTYPE_TABLE;
+ }
+ inline bool is_merged_derived()
+ {
+ return (derived_type & DTYPE_MERGE);
+ }
+ inline void set_merged_derived()
+ {
+ DBUG_ENTER("set_merged_derived");
+ derived_type= ((derived_type & DTYPE_MASK) |
+ DTYPE_TABLE | DTYPE_MERGE);
+ set_check_merged();
+ DBUG_VOID_RETURN;
+ }
+ inline bool is_materialized_derived()
+ {
+ return (derived_type & DTYPE_MATERIALIZE);
+ }
+ void set_materialized_derived()
+ {
+ DBUG_ENTER("set_materialized_derived");
+ derived_type= ((derived_type & (derived ? DTYPE_MASK : DTYPE_VIEW)) |
+ DTYPE_TABLE | DTYPE_MATERIALIZE);
+ set_check_materialized();
+ DBUG_VOID_RETURN;
+ }
+ inline bool is_multitable()
+ {
+ return (derived_type & DTYPE_MULTITABLE);
+ }
+ inline void set_multitable()
+ {
+ derived_type|= DTYPE_MULTITABLE;
+ }
+ void reset_const_table();
+ bool handle_derived(LEX *lex, uint phases);
+
+ /**
+ @brief True if this TABLE_LIST represents an anonymous derived table,
+ i.e. the result of a subquery.
+ */
+ bool is_anonymous_derived_table() const { return derived && !view; }
+
+ /**
+ @brief Returns the name of the database that the referenced table belongs
+ to.
+ */
+ char *get_db_name() const { return view != NULL ? view_db.str : db; }
+
+ /**
+ @brief Returns the name of the table that this TABLE_LIST represents.
+
+ @details The unqualified table name or view name for a table or view,
+ respectively.
+ */
+ char *get_table_name() const { return view != NULL ? view_name.str : table_name; }
+ bool is_active_sjm();
+ bool is_jtbm() { return MY_TEST(jtbm_subselect != NULL); }
+ st_select_lex_unit *get_unit();
+ st_select_lex *get_single_select();
+ void wrap_into_nested_join(List<TABLE_LIST> &join_list);
+ bool init_derived(THD *thd, bool init_view);
+ int fetch_number_of_rows();
+ bool change_refs_to_fields();
+
+ bool single_table_updatable();
+
+ bool is_inner_table_of_outer_join()
+ {
+ for (TABLE_LIST *tbl= this; tbl; tbl= tbl->embedding)
+ {
+ if (tbl->outer_join)
+ return true;
+ }
+ return false;
+ }
+ void set_lock_type(THD* thd, enum thr_lock_type lock);
+
+private:
+ bool prep_check_option(THD *thd, uint8 check_opt_type);
+ bool prep_where(THD *thd, Item **conds, bool no_where_clause);
+ void set_check_materialized();
+#ifndef DBUG_OFF
+ void set_check_merged();
+#else
+ inline void set_check_merged() {}
+#endif
+ /** See comments for set_table_ref_id() */
+ enum enum_table_ref_type m_table_ref_type;
+ /** See comments for set_table_ref_id() */
+ ulong m_table_ref_version;
+};
+
+class Item;
+
+/*
+ Iterator over the fields of a generic table reference.
+*/
+
+class Field_iterator: public Sql_alloc
+{
+public:
+ Field_iterator() {} /* Remove gcc warning */
+ virtual ~Field_iterator() {}
+ virtual void set(TABLE_LIST *)= 0;
+ virtual void next()= 0;
+ virtual bool end_of_fields()= 0; /* Return 1 at end of list */
+ virtual const char *name()= 0;
+ virtual Item *create_item(THD *)= 0;
+ virtual Field *field()= 0;
+};
+
+
+/*
+ Iterator over the fields of a base table, view with temporary
+ table, or subquery.
+*/
+
+class Field_iterator_table: public Field_iterator
+{
+ Field **ptr;
+public:
+ Field_iterator_table() :ptr(0) {}
+ void set(TABLE_LIST *table) { ptr= table->table->field; }
+ void set_table(TABLE *table) { ptr= table->field; }
+ void next() { ptr++; }
+ bool end_of_fields() { return *ptr == 0; }
+ const char *name();
+ Item *create_item(THD *thd);
+ Field *field() { return *ptr; }
+};
+
+
+/* Iterator over the fields of a merge view. */
+
+class Field_iterator_view: public Field_iterator
+{
+ Field_translator *ptr, *array_end;
+ TABLE_LIST *view;
+public:
+ Field_iterator_view() :ptr(0), array_end(0) {}
+ void set(TABLE_LIST *table);
+ void next() { ptr++; }
+ bool end_of_fields() { return ptr == array_end; }
+ const char *name();
+ Item *create_item(THD *thd);
+ Item **item_ptr() {return &ptr->item; }
+ Field *field() { return 0; }
+ inline Item *item() { return ptr->item; }
+ Field_translator *field_translator() { return ptr; }
+};
+
+
+/*
+ Field_iterator interface to the list of materialized fields of a
+ NATURAL/USING join.
+*/
+
+class Field_iterator_natural_join: public Field_iterator
+{
+ List_iterator_fast<Natural_join_column> column_ref_it;
+ Natural_join_column *cur_column_ref;
+public:
+ Field_iterator_natural_join() :cur_column_ref(NULL) {}
+ ~Field_iterator_natural_join() {}
+ void set(TABLE_LIST *table);
+ void next();
+ bool end_of_fields() { return !cur_column_ref; }
+ const char *name() { return cur_column_ref->name(); }
+ Item *create_item(THD *thd) { return cur_column_ref->create_item(thd); }
+ Field *field() { return cur_column_ref->field(); }
+ Natural_join_column *column_ref() { return cur_column_ref; }
+};
+
+
+/*
+ Generic iterator over the fields of an arbitrary table reference.
+
+ DESCRIPTION
+ This class unifies the various ways of iterating over the columns
+ of a table reference depending on the type of SQL entity it
+ represents. If such an entity represents a nested table reference,
+ this iterator encapsulates the iteration over the columns of the
+ members of the table reference.
+
+ IMPLEMENTATION
+ The implementation assumes that all underlying NATURAL/USING table
+ references already contain their result columns and are linked into
+ the list TABLE_LIST::next_name_resolution_table.
+*/
+
+class Field_iterator_table_ref: public Field_iterator
+{
+ TABLE_LIST *table_ref, *first_leaf, *last_leaf;
+ Field_iterator_table table_field_it;
+ Field_iterator_view view_field_it;
+ Field_iterator_natural_join natural_join_it;
+ Field_iterator *field_it;
+ void set_field_iterator();
+public:
+ Field_iterator_table_ref() :field_it(NULL) {}
+ void set(TABLE_LIST *table);
+ void next();
+ bool end_of_fields()
+ { return (table_ref == last_leaf && field_it->end_of_fields()); }
+ const char *name() { return field_it->name(); }
+ const char *get_table_name();
+ const char *get_db_name();
+ GRANT_INFO *grant();
+ Item *create_item(THD *thd) { return field_it->create_item(thd); }
+ Field *field() { return field_it->field(); }
+ Natural_join_column *get_or_create_column_ref(THD *thd, TABLE_LIST *parent_table_ref);
+ Natural_join_column *get_natural_column_ref();
+};
+
+
+typedef struct st_nested_join
+{
+ List<TABLE_LIST> join_list; /* list of elements in the nested join */
+ /*
+ Bitmap of tables within this nested join (including those embedded within
+ its children), including tables removed by table elimination.
+ */
+ table_map used_tables;
+ table_map not_null_tables; /* tables that rejects nulls */
+ /**
+ Used for pointing out the first table in the plan being covered by this
+ join nest. It is used exclusively within make_outerjoin_info().
+ */
+ struct st_join_table *first_nested;
+ /*
+ Used to count tables in the nested join in 2 isolated places:
+ 1. In make_outerjoin_info().
+ 2. check_interleaving_with_nj/restore_prev_nj_state (these are called
+ by the join optimizer.
+ Before each use the counters are zeroed by reset_nj_counters.
+ */
+ uint counter;
+ /*
+ Number of elements in join_list that were not (or contain table(s) that
+ weren't) removed by table elimination.
+ */
+ uint n_tables;
+ nested_join_map nj_map; /* Bit used to identify this nested join*/
+ /*
+ (Valid only for semi-join nests) Bitmap of tables outside the semi-join
+ that are used within the semi-join's ON condition.
+ */
+ table_map sj_depends_on;
+ /* Outer non-trivially correlated tables */
+ table_map sj_corr_tables;
+ List<Item> sj_outer_expr_list;
+ /**
+ True if this join nest node is completely covered by the query execution
+ plan. This means two things.
+
+ 1. All tables on its @c join_list are covered by the plan.
+
+ 2. All child join nest nodes are fully covered.
+ */
+ bool is_fully_covered() const { return n_tables == counter; }
+} NESTED_JOIN;
+
+
+typedef struct st_changed_table_list
+{
+ struct st_changed_table_list *next;
+ char *key;
+ uint32 key_length;
+} CHANGED_TABLE_LIST;
+
+
+typedef struct st_open_table_list{
+ struct st_open_table_list *next;
+ char *db,*table;
+ uint32 in_use,locked;
+} OPEN_TABLE_LIST;
+
+
+static inline my_bitmap_map *tmp_use_all_columns(TABLE *table,
+ MY_BITMAP *bitmap)
+{
+ my_bitmap_map *old= bitmap->bitmap;
+ bitmap->bitmap= table->s->all_set.bitmap;
+ return old;
+}
+
+
+static inline void tmp_restore_column_map(MY_BITMAP *bitmap,
+ my_bitmap_map *old)
+{
+ bitmap->bitmap= old;
+}
+
+/* The following is only needed for debugging */
+
+static inline my_bitmap_map *dbug_tmp_use_all_columns(TABLE *table,
+ MY_BITMAP *bitmap)
+{
+#ifndef DBUG_OFF
+ return tmp_use_all_columns(table, bitmap);
+#else
+ return 0;
+#endif
+}
+
+static inline void dbug_tmp_restore_column_map(MY_BITMAP *bitmap,
+ my_bitmap_map *old)
+{
+#ifndef DBUG_OFF
+ tmp_restore_column_map(bitmap, old);
+#endif
+}
+
+
+/*
+ Variant of the above : handle both read and write sets.
+ Provide for the possiblity of the read set being the same as the write set
+*/
+static inline void dbug_tmp_use_all_columns(TABLE *table,
+ my_bitmap_map **save,
+ MY_BITMAP *read_set,
+ MY_BITMAP *write_set)
+{
+#ifndef DBUG_OFF
+ save[0]= read_set->bitmap;
+ save[1]= write_set->bitmap;
+ (void) tmp_use_all_columns(table, read_set);
+ (void) tmp_use_all_columns(table, write_set);
+#endif
+}
+
+
+static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set,
+ MY_BITMAP *write_set,
+ my_bitmap_map **old)
+{
+#ifndef DBUG_OFF
+ tmp_restore_column_map(read_set, old[0]);
+ tmp_restore_column_map(write_set, old[1]);
+#endif
+}
+
+bool ok_for_lower_case_names(const char *names);
+
+enum get_table_share_flags {
+ GTS_TABLE = 1,
+ GTS_VIEW = 2,
+ GTS_NOLOCK = 4,
+ GTS_USE_DISCOVERY = 8,
+ GTS_FORCE_DISCOVERY = 16
+};
+
+size_t max_row_length(TABLE *table, const uchar *data);
+
+void init_mdl_requests(TABLE_LIST *table_list);
+
+enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share,
+ const char *alias, uint db_stat, uint prgflag,
+ uint ha_open_flags, TABLE *outparam,
+ bool is_create_table);
+bool unpack_vcol_info_from_frm(THD *thd, MEM_ROOT *mem_root,
+ TABLE *table, Field *field,
+ LEX_STRING *vcol_expr, bool *error_reported);
+TABLE_SHARE *alloc_table_share(const char *db, const char *table_name,
+ const char *key, uint key_length);
+void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
+ uint key_length,
+ const char *table_name, const char *path);
+void free_table_share(TABLE_SHARE *share);
+enum open_frm_error open_table_def(THD *thd, TABLE_SHARE *share,
+ uint flags = GTS_TABLE);
+
+void open_table_error(TABLE_SHARE *share, enum open_frm_error error,
+ int db_errno);
+void update_create_info_from_table(HA_CREATE_INFO *info, TABLE *form);
+bool check_and_convert_db_name(LEX_STRING *db, bool preserve_lettercase);
+bool check_db_name(LEX_STRING *db);
+bool check_column_name(const char *name);
+bool check_table_name(const char *name, size_t length, bool check_for_path_chars);
+int rename_file_ext(const char * from,const char * to,const char * ext);
+char *get_field(MEM_ROOT *mem, Field *field);
+bool get_field(MEM_ROOT *mem, Field *field, class String *res);
+
+bool validate_comment_length(THD *thd, LEX_STRING *comment, size_t max_len,
+ uint err_code, const char *name);
+
+int closefrm(TABLE *table, bool free_share);
+void free_blobs(TABLE *table);
+void free_field_buffers_larger_than(TABLE *table, uint32 size);
+ulong get_form_pos(File file, uchar *head, TYPELIB *save_names);
+void append_unescaped(String *res, const char *pos, uint length);
+void prepare_frm_header(THD *thd, uint reclength, uchar *fileinfo,
+ HA_CREATE_INFO *create_info, uint keys, KEY *key_info);
+char *fn_rext(char *name);
+
+/* Check that the integer is in the internal */
+static inline int set_zone(int nr,int min_zone,int max_zone)
+{
+ if (nr <= min_zone)
+ return min_zone;
+ if (nr >= max_zone)
+ return max_zone;
+ return nr;
+}
+
+/* performance schema */
+extern LEX_STRING PERFORMANCE_SCHEMA_DB_NAME;
+
+extern LEX_STRING GENERAL_LOG_NAME;
+extern LEX_STRING SLOW_LOG_NAME;
+
+/* information schema */
+extern LEX_STRING INFORMATION_SCHEMA_NAME;
+extern LEX_STRING MYSQL_SCHEMA_NAME;
+
+inline bool is_infoschema_db(const char *name, size_t len)
+{
+ return (INFORMATION_SCHEMA_NAME.length == len &&
+ !my_strcasecmp(system_charset_info,
+ INFORMATION_SCHEMA_NAME.str, name));
+}
+
+inline bool is_infoschema_db(const char *name)
+{
+ return !my_strcasecmp(system_charset_info,
+ INFORMATION_SCHEMA_NAME.str, name);
+}
+
+TYPELIB *typelib(MEM_ROOT *mem_root, List<String> &strings);
+
+/**
+ return true if the table was created explicitly.
+*/
+inline bool is_user_table(TABLE * table)
+{
+ const char *name= table->s->table_name.str;
+ return strncmp(name, tmp_file_prefix, tmp_file_prefix_length);
+}
+
+inline void mark_as_null_row(TABLE *table)
+{
+ table->null_row=1;
+ table->status|=STATUS_NULL_ROW;
+ bfill(table->null_flags,table->s->null_bytes,255);
+}
+
+bool is_simple_order(ORDER *order);
+
+#endif /* MYSQL_CLIENT */
+
+#endif /* TABLE_INCLUDED */
diff --git a/sql/table_cache.cc b/sql/table_cache.cc
new file mode 100644
index 00000000000..097f37d26d8
--- /dev/null
+++ b/sql/table_cache.cc
@@ -0,0 +1,1220 @@
+/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2011 Monty Program Ab
+ Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation
+
+ 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 */
+
+/**
+ @file
+ Table definition cache and table cache implementation.
+
+ Table definition cache actions:
+ - add new TABLE_SHARE object to cache (tdc_acquire_share())
+ - acquire TABLE_SHARE object from cache (tdc_acquire_share())
+ - release TABLE_SHARE object to cache (tdc_release_share())
+ - purge unused TABLE_SHARE objects from cache (tdc_purge())
+ - remove TABLE_SHARE object from cache (tdc_remove_table())
+ - get number of TABLE_SHARE objects in cache (tdc_records())
+
+ Table cache actions:
+ - add new TABLE object to cache (tc_add_table())
+ - acquire TABLE object from cache (tc_acquire_table())
+ - release TABLE object to cache (tc_release_table())
+ - purge unused TABLE objects from cache (tc_purge())
+ - purge unused TABLE objects of a table from cache (tdc_remove_table())
+ - get number of TABLE objects in cache (tc_records())
+
+ Dependencies:
+ - intern_close_table(): frees TABLE object
+ - kill_delayed_threads_for_table()
+ - close_cached_tables(): flush tables on shutdown
+ - alloc_table_share()
+ - free_table_share()
+
+ Table cache invariants:
+ - TABLE_SHARE::free_tables shall not contain objects with TABLE::in_use != 0
+ - TABLE_SHARE::free_tables shall not receive new objects if
+ TABLE_SHARE::tdc.flushed is true
+*/
+
+#include "my_global.h"
+#include "hash.h"
+#include "table.h"
+#include "sql_base.h"
+
+/** Configuration. */
+ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */
+ulong tc_size; /**< Table cache threshold for LRU eviction. */
+
+/** Data collections. */
+static HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */
+/** Collection of unused TABLE_SHARE objects. */
+static TABLE_SHARE *oldest_unused_share, end_of_unused_share;
+
+static int64 tdc_version; /* Increments on each reload */
+static int64 last_table_id;
+static bool tdc_inited;
+
+static int32 tc_count; /**< Number of TABLE objects in table cache. */
+
+
+/**
+ Protects unused shares list.
+
+ TABLE_SHARE::tdc.prev
+ TABLE_SHARE::tdc.next
+ oldest_unused_share
+ end_of_unused_share
+*/
+
+static mysql_mutex_t LOCK_unused_shares;
+static mysql_rwlock_t LOCK_tdc; /**< Protects tdc_hash. */
+my_atomic_rwlock_t LOCK_tdc_atomics; /**< Protects tdc_version. */
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share;
+static PSI_mutex_info all_tc_mutexes[]=
+{
+ { &key_LOCK_unused_shares, "LOCK_unused_shares", PSI_FLAG_GLOBAL },
+ { &key_TABLE_SHARE_LOCK_table_share, "TABLE_SHARE::tdc.LOCK_table_share", 0 }
+};
+
+static PSI_rwlock_key key_rwlock_LOCK_tdc;
+static PSI_rwlock_info all_tc_rwlocks[]=
+{
+ { &key_rwlock_LOCK_tdc, "LOCK_tdc", PSI_FLAG_GLOBAL }
+};
+
+
+static PSI_cond_key key_TABLE_SHARE_COND_release;
+static PSI_cond_info all_tc_conds[]=
+{
+ { &key_TABLE_SHARE_COND_release, "TABLE_SHARE::tdc.COND_release", 0 }
+};
+
+
+static void init_tc_psi_keys(void)
+{
+ const char *category= "sql";
+ int count;
+
+ count= array_elements(all_tc_mutexes);
+ mysql_mutex_register(category, all_tc_mutexes, count);
+
+ count= array_elements(all_tc_rwlocks);
+ mysql_rwlock_register(category, all_tc_rwlocks, count);
+
+ count= array_elements(all_tc_conds);
+ mysql_cond_register(category, all_tc_conds, count);
+}
+#endif
+
+
+/*
+ Auxiliary routines for manipulating with per-share all/unused lists
+ and tc_count counter.
+ Responsible for preserving invariants between those lists, counter
+ and TABLE::in_use member.
+ In fact those routines implement sort of implicit table cache as
+ part of table definition cache.
+*/
+
+
+/**
+ Get number of TABLE objects (used and unused) in table cache.
+*/
+
+uint tc_records(void)
+{
+ uint count;
+ my_atomic_rwlock_rdlock(&LOCK_tdc_atomics);
+ count= my_atomic_load32(&tc_count);
+ my_atomic_rwlock_rdunlock(&LOCK_tdc_atomics);
+ return count;
+}
+
+
+/**
+ Remove TABLE object from table cache.
+
+ - decrement tc_count
+ - remove object from TABLE_SHARE::tdc.all_tables
+*/
+
+static void tc_remove_table(TABLE *table)
+{
+ my_atomic_rwlock_wrlock(&LOCK_tdc_atomics);
+ my_atomic_add32(&tc_count, -1);
+ my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics);
+ table->s->tdc.all_tables.remove(table);
+}
+
+
+/**
+ Wait for MDL deadlock detector to complete traversing tdc.all_tables.
+
+ Must be called before updating TABLE_SHARE::tdc.all_tables.
+*/
+
+static void tc_wait_for_mdl_deadlock_detector(TABLE_SHARE *share)
+{
+ while (share->tdc.all_tables_refs)
+ mysql_cond_wait(&share->tdc.COND_release, &share->tdc.LOCK_table_share);
+}
+
+
+/**
+ Get last element of tdc.free_tables.
+*/
+
+static TABLE *tc_free_tables_back(TABLE_SHARE *share)
+{
+ TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables);
+ TABLE *entry, *last= 0;
+ while ((entry= it++))
+ last= entry;
+ return last;
+}
+
+
+/**
+ Free all unused TABLE objects.
+
+ While locked:
+ - remove unused objects from TABLE_SHARE::tdc.free_tables and
+ TABLE_SHARE::tdc.all_tables
+ - decrement tc_count
+
+ While unlocked:
+ - free resources related to unused objects
+
+ @note This is called by 'handle_manager' when one wants to
+ periodicly flush all not used tables.
+*/
+
+void tc_purge(bool mark_flushed)
+{
+ TABLE_SHARE *share;
+ TABLE *table;
+ TDC_iterator tdc_it;
+ TABLE_SHARE::TABLE_list purge_tables;
+
+ tdc_it.init();
+ while ((share= tdc_it.next()))
+ {
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ tc_wait_for_mdl_deadlock_detector(share);
+
+ if (mark_flushed)
+ share->tdc.flushed= true;
+ while ((table= share->tdc.free_tables.pop_front()))
+ {
+ tc_remove_table(table);
+ purge_tables.push_front(table);
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ }
+ tdc_it.deinit();
+
+ while ((table= purge_tables.pop_front()))
+ intern_close_table(table);
+}
+
+
+/**
+ Add new TABLE object to table cache.
+
+ @pre TABLE object is used by caller.
+
+ Added object cannot be evicted or acquired.
+
+ While locked:
+ - add object to TABLE_SHARE::tdc.all_tables
+ - increment tc_count
+ - evict LRU object from table cache if we reached threshold
+
+ While unlocked:
+ - free evicted object
+*/
+
+void tc_add_table(THD *thd, TABLE *table)
+{
+ bool need_purge;
+ DBUG_ASSERT(table->in_use == thd);
+ mysql_mutex_lock(&table->s->tdc.LOCK_table_share);
+ tc_wait_for_mdl_deadlock_detector(table->s);
+ table->s->tdc.all_tables.push_front(table);
+ mysql_mutex_unlock(&table->s->tdc.LOCK_table_share);
+
+ /* If we have too many TABLE instances around, try to get rid of them */
+ my_atomic_rwlock_wrlock(&LOCK_tdc_atomics);
+ need_purge= my_atomic_add32(&tc_count, 1) >= (int32) tc_size;
+ my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics);
+
+ if (need_purge)
+ {
+ TABLE_SHARE *purge_share= 0;
+ TABLE_SHARE *share;
+ TABLE *entry;
+ ulonglong UNINIT_VAR(purge_time);
+ TDC_iterator tdc_it;
+
+ tdc_it.init();
+ while ((share= tdc_it.next()))
+ {
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ if ((entry= tc_free_tables_back(share)) &&
+ (!purge_share || entry->tc_time < purge_time))
+ {
+ purge_share= share;
+ purge_time= entry->tc_time;
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ }
+
+ if (purge_share)
+ {
+ mysql_mutex_lock(&purge_share->tdc.LOCK_table_share);
+ tc_wait_for_mdl_deadlock_detector(purge_share);
+ tdc_it.deinit();
+ /*
+ It may happen that oldest table was acquired meanwhile. In this case
+ just go ahead, number of objects in table cache will normalize
+ eventually.
+ */
+ if ((entry= tc_free_tables_back(purge_share)) &&
+ entry->tc_time == purge_time)
+ {
+ entry->s->tdc.free_tables.remove(entry);
+ tc_remove_table(entry);
+ mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share);
+ intern_close_table(entry);
+ }
+ else
+ mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share);
+ }
+ else
+ tdc_it.deinit();
+ }
+}
+
+
+/**
+ Acquire TABLE object from table cache.
+
+ @pre share must be protected against removal.
+
+ Acquired object cannot be evicted or acquired again.
+
+ While locked:
+ - pop object from TABLE_SHARE::tdc.free_tables
+
+ While unlocked:
+ - mark object used by thd
+
+ @return TABLE object, or NULL if no unused objects.
+*/
+
+static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share)
+{
+ TABLE *table;
+
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ table= share->tdc.free_tables.pop_front();
+ if (table)
+ {
+ DBUG_ASSERT(!table->in_use);
+ table->in_use= thd;
+ /* The ex-unused table must be fully functional. */
+ DBUG_ASSERT(table->db_stat && table->file);
+ /* The children must be detached from the table. */
+ DBUG_ASSERT(!table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ return table;
+}
+
+
+/**
+ Release TABLE object to table cache.
+
+ @pre object is used by caller.
+
+ Released object may be evicted or acquired again.
+
+ While locked:
+ - if object is marked for purge, decrement tc_count
+ - add object to TABLE_SHARE::tdc.free_tables
+ - evict LRU object from table cache if we reached threshold
+
+ While unlocked:
+ - mark object not in use by any thread
+ - free evicted/purged object
+
+ @note Another thread may mark share for purge any moment (even
+ after version check). It means to-be-purged object may go to
+ unused lists. This other thread is expected to call tc_purge(),
+ which is synchronized with us on TABLE_SHARE::tdc.LOCK_table_share.
+
+ @return
+ @retval true object purged
+ @retval false object released
+*/
+
+bool tc_release_table(TABLE *table)
+{
+ DBUG_ASSERT(table->in_use);
+ DBUG_ASSERT(table->file);
+
+ if (table->needs_reopen() || tc_records() > tc_size)
+ {
+ mysql_mutex_lock(&table->s->tdc.LOCK_table_share);
+ goto purge;
+ }
+
+ table->tc_time= my_interval_timer();
+
+ mysql_mutex_lock(&table->s->tdc.LOCK_table_share);
+ if (table->s->tdc.flushed)
+ goto purge;
+ /*
+ in_use doesn't really need mutex protection, but must be reset after
+ checking tdc.flushed and before this table appears in free_tables.
+ Resetting in_use is needed only for print_cached_tables() and
+ list_open_tables().
+ */
+ table->in_use= 0;
+ /* Add table to the list of unused TABLE objects for this share. */
+ table->s->tdc.free_tables.push_front(table);
+ mysql_mutex_unlock(&table->s->tdc.LOCK_table_share);
+ return false;
+
+purge:
+ tc_wait_for_mdl_deadlock_detector(table->s);
+ tc_remove_table(table);
+ mysql_mutex_unlock(&table->s->tdc.LOCK_table_share);
+ table->in_use= 0;
+ intern_close_table(table);
+ return true;
+}
+
+
+extern "C" uchar *tdc_key(const uchar *record, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ TABLE_SHARE *entry= (TABLE_SHARE*) record;
+ *length= entry->table_cache_key.length;
+ return (uchar*) entry->table_cache_key.str;
+}
+
+
+/**
+ Delete share from hash and free share object.
+
+ @return
+ @retval 0 Success
+ @retval 1 Share is referenced
+*/
+
+static int tdc_delete_share_from_hash(TABLE_SHARE *share)
+{
+ DBUG_ENTER("tdc_delete_share_from_hash");
+ mysql_rwlock_wrlock(&LOCK_tdc);
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ if (--share->tdc.ref_count)
+ {
+ mysql_cond_broadcast(&share->tdc.COND_release);
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ mysql_rwlock_unlock(&LOCK_tdc);
+ DBUG_RETURN(1);
+ }
+ my_hash_delete(&tdc_hash, (uchar*) share);
+ /* Notify PFS early, while still locked. */
+ PSI_CALL_release_table_share(share->m_psi);
+ share->m_psi= 0;
+ mysql_rwlock_unlock(&LOCK_tdc);
+
+ if (share->tdc.m_flush_tickets.is_empty())
+ {
+ /* No threads are waiting for this share to be flushed, destroy it. */
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ free_table_share(share);
+ }
+ else
+ {
+ Wait_for_flush_list::Iterator it(share->tdc.m_flush_tickets);
+ Wait_for_flush *ticket;
+ while ((ticket= it++))
+ (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED);
+ /*
+ If there are threads waiting for this share to be flushed,
+ the last one to receive the notification will destroy the
+ share. At this point the share is removed from the table
+ definition cache, so is OK to proceed here without waiting
+ for this thread to do the work.
+ */
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Initialize table definition cache.
+
+ @retval 0 Success
+ @retval !0 Error
+*/
+
+int tdc_init(void)
+{
+ DBUG_ENTER("tdc_init");
+#ifdef HAVE_PSI_INTERFACE
+ init_tc_psi_keys();
+#endif
+ tdc_inited= true;
+ mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares,
+ MY_MUTEX_INIT_FAST);
+ mysql_rwlock_init(key_rwlock_LOCK_tdc, &LOCK_tdc);
+ my_atomic_rwlock_init(&LOCK_tdc_atomics);
+ oldest_unused_share= &end_of_unused_share;
+ end_of_unused_share.tdc.prev= &oldest_unused_share;
+ tdc_version= 1L; /* Increments on each reload */
+ DBUG_RETURN(my_hash_init(&tdc_hash, &my_charset_bin, tdc_size, 0, 0, tdc_key,
+ 0, 0));
+}
+
+
+/**
+ Notify table definition cache that process of shutting down server
+ has started so it has to keep number of TABLE and TABLE_SHARE objects
+ minimal in order to reduce number of references to pluggable engines.
+*/
+
+void tdc_start_shutdown(void)
+{
+ DBUG_ENTER("table_def_start_shutdown");
+ if (tdc_inited)
+ {
+ /*
+ Ensure that TABLE and TABLE_SHARE objects which are created for
+ tables that are open during process of plugins' shutdown are
+ immediately released. This keeps number of references to engine
+ plugins minimal and allows shutdown to proceed smoothly.
+ */
+ tdc_size= 0;
+ tc_size= 0;
+ /* Free all cached but unused TABLEs and TABLE_SHAREs. */
+ close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Deinitialize table definition cache.
+*/
+
+void tdc_deinit(void)
+{
+ DBUG_ENTER("tdc_deinit");
+ if (tdc_inited)
+ {
+ tdc_inited= false;
+ my_hash_free(&tdc_hash);
+ my_atomic_rwlock_destroy(&LOCK_tdc_atomics);
+ mysql_rwlock_destroy(&LOCK_tdc);
+ mysql_mutex_destroy(&LOCK_unused_shares);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get number of cached table definitions.
+
+ @return Number of cached table definitions
+*/
+
+ulong tdc_records(void)
+{
+ ulong records;
+ DBUG_ENTER("tdc_records");
+ mysql_rwlock_rdlock(&LOCK_tdc);
+ records= tdc_hash.records;
+ mysql_rwlock_unlock(&LOCK_tdc);
+ DBUG_RETURN(records);
+}
+
+
+void tdc_purge(bool all)
+{
+ DBUG_ENTER("tdc_purge");
+ while (all || tdc_records() > tdc_size)
+ {
+ TABLE_SHARE *share;
+
+ mysql_mutex_lock(&LOCK_unused_shares);
+ if (!oldest_unused_share->tdc.next)
+ {
+ mysql_mutex_unlock(&LOCK_unused_shares);
+ break;
+ }
+
+ share= oldest_unused_share;
+ *share->tdc.prev= share->tdc.next;
+ share->tdc.next->tdc.prev= share->tdc.prev;
+ /* Concurrent thread may start using share again, reset prev and next. */
+ share->tdc.prev= 0;
+ share->tdc.next= 0;
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ share->tdc.ref_count++;
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ mysql_mutex_unlock(&LOCK_unused_shares);
+
+ tdc_delete_share_from_hash(share);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Prepeare table share for use with table definition cache.
+*/
+
+void tdc_init_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("tdc_init_share");
+ mysql_mutex_init(key_TABLE_SHARE_LOCK_table_share,
+ &share->tdc.LOCK_table_share, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(key_TABLE_SHARE_COND_release, &share->tdc.COND_release, 0);
+ share->tdc.m_flush_tickets.empty();
+ share->tdc.all_tables.empty();
+ share->tdc.free_tables.empty();
+ tdc_assign_new_table_id(share);
+ share->tdc.version= tdc_refresh_version();
+ share->tdc.flushed= false;
+ share->tdc.all_tables_refs= 0;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release table definition cache specific resources of table share.
+*/
+
+void tdc_deinit_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("tdc_deinit_share");
+ DBUG_ASSERT(share->tdc.ref_count == 0);
+ DBUG_ASSERT(share->tdc.m_flush_tickets.is_empty());
+ DBUG_ASSERT(share->tdc.all_tables.is_empty());
+ DBUG_ASSERT(share->tdc.free_tables.is_empty());
+ DBUG_ASSERT(share->tdc.all_tables_refs == 0);
+ mysql_cond_destroy(&share->tdc.COND_release);
+ mysql_mutex_destroy(&share->tdc.LOCK_table_share);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Lock table share.
+
+ Find table share with given db.table_name in table definition cache. Return
+ locked table share if found.
+
+ Locked table share means:
+ - table share is protected against removal from table definition cache
+ - no other thread can acquire/release table share
+
+ Caller is expected to unlock table share with tdc_unlock_share().
+
+ @retval 0 Share not found
+ @retval !0 Pointer to locked table share
+*/
+
+TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+
+ DBUG_ENTER("tdc_lock_share");
+ key_length= tdc_create_key(key, db, table_name);
+
+ mysql_rwlock_rdlock(&LOCK_tdc);
+ TABLE_SHARE* share= (TABLE_SHARE*) my_hash_search(&tdc_hash,
+ (uchar*) key, key_length);
+ if (share && !share->error)
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ else
+ share= 0;
+ mysql_rwlock_unlock(&LOCK_tdc);
+ DBUG_RETURN(share);
+}
+
+
+/**
+ Unlock share locked by tdc_lock_share().
+*/
+
+void tdc_unlock_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("tdc_unlock_share");
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Get TABLE_SHARE for a table.
+
+ tdc_acquire_share()
+ thd Thread handle
+ table_list Table that should be opened
+ key Table cache key
+ key_length Length of key
+ flags operation: what to open table or view
+
+ IMPLEMENTATION
+ Get a table definition from the table definition cache.
+ If it doesn't exist, create a new from the table definition file.
+
+ RETURN
+ 0 Error
+ # Share for table
+*/
+
+TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name,
+ const char *key, uint key_length,
+ my_hash_value_type hash_value, uint flags,
+ TABLE **out_table)
+{
+ TABLE_SHARE *share;
+ bool was_unused;
+ DBUG_ENTER("tdc_acquire_share");
+
+ mysql_rwlock_rdlock(&LOCK_tdc);
+ share= (TABLE_SHARE*) my_hash_search_using_hash_value(&tdc_hash, hash_value,
+ (uchar*) key,
+ key_length);
+ if (!share)
+ {
+ TABLE_SHARE *new_share;
+ mysql_rwlock_unlock(&LOCK_tdc);
+
+ if (!(new_share= alloc_table_share(db, table_name, key, key_length)))
+ DBUG_RETURN(0);
+ new_share->error= OPEN_FRM_OPEN_ERROR;
+
+ mysql_rwlock_wrlock(&LOCK_tdc);
+ share= (TABLE_SHARE*) my_hash_search_using_hash_value(&tdc_hash, hash_value,
+ (uchar*) key,
+ key_length);
+ if (!share)
+ {
+ bool need_purge;
+
+ share= new_share;
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ if (my_hash_insert(&tdc_hash, (uchar*) share))
+ {
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ mysql_rwlock_unlock(&LOCK_tdc);
+ free_table_share(share);
+ DBUG_RETURN(0);
+ }
+ need_purge= tdc_hash.records > tdc_size;
+ mysql_rwlock_unlock(&LOCK_tdc);
+
+ /* note that tdc_acquire_share() *always* uses discovery */
+ open_table_def(thd, share, flags | GTS_USE_DISCOVERY);
+ share->tdc.ref_count++;
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+
+ if (share->error)
+ {
+ tdc_delete_share_from_hash(share);
+ DBUG_RETURN(0);
+ }
+ else if (need_purge)
+ tdc_purge(false);
+ if (out_table)
+ *out_table= 0;
+ share->m_psi= PSI_CALL_get_table_share(false, share);
+ goto end;
+ }
+ free_table_share(new_share);
+ }
+
+ /* cannot force discovery of a cached share */
+ DBUG_ASSERT(!(flags & GTS_FORCE_DISCOVERY));
+
+ if (out_table && (flags & GTS_TABLE))
+ {
+ if ((*out_table= tc_acquire_table(thd, share)))
+ {
+ mysql_rwlock_unlock(&LOCK_tdc);
+ DBUG_ASSERT(!(flags & GTS_NOLOCK));
+ DBUG_ASSERT(!share->error);
+ DBUG_ASSERT(!share->is_view);
+ DBUG_RETURN(share);
+ }
+ }
+
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ mysql_rwlock_unlock(&LOCK_tdc);
+
+ /*
+ We found an existing table definition. Return it if we didn't get
+ an error when reading the table definition from file.
+ */
+ if (share->error)
+ {
+ open_table_error(share, share->error, share->open_errno);
+ goto err;
+ }
+
+ if (share->is_view && !(flags & GTS_VIEW))
+ {
+ open_table_error(share, OPEN_FRM_NOT_A_TABLE, ENOENT);
+ goto err;
+ }
+ if (!share->is_view && !(flags & GTS_TABLE))
+ {
+ open_table_error(share, OPEN_FRM_NOT_A_VIEW, ENOENT);
+ goto err;
+ }
+
+ was_unused= !share->tdc.ref_count;
+ share->tdc.ref_count++;
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ if (was_unused)
+ {
+ mysql_mutex_lock(&LOCK_unused_shares);
+ if (share->tdc.prev)
+ {
+ /*
+ Share was not used before and it was in the old_unused_share list
+ Unlink share from this list
+ */
+ DBUG_PRINT("info", ("Unlinking from not used list"));
+ *share->tdc.prev= share->tdc.next;
+ share->tdc.next->tdc.prev= share->tdc.prev;
+ share->tdc.next= 0;
+ share->tdc.prev= 0;
+ }
+ mysql_mutex_unlock(&LOCK_unused_shares);
+ }
+
+end:
+ DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
+ (ulong) share, share->tdc.ref_count));
+ if (flags & GTS_NOLOCK)
+ {
+ tdc_release_share(share);
+ /*
+ if GTS_NOLOCK is requested, the returned share pointer cannot be used,
+ the share it points to may go away any moment.
+ But perhaps the caller is only interested to know whether a share or
+ table existed?
+ Let's return an invalid pointer here to catch dereferencing attempts.
+ */
+ share= (TABLE_SHARE*) 1;
+ }
+ DBUG_RETURN(share);
+
+err:
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Release table share acquired by tdc_acquire_share().
+*/
+
+void tdc_release_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("tdc_release_share");
+
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ DBUG_PRINT("enter",
+ ("share: 0x%lx table: %s.%s ref_count: %u version: %lu",
+ (ulong) share, share->db.str, share->table_name.str,
+ share->tdc.ref_count, share->tdc.version));
+ DBUG_ASSERT(share->tdc.ref_count);
+
+ if (share->tdc.ref_count > 1)
+ {
+ share->tdc.ref_count--;
+ if (!share->is_view)
+ mysql_cond_broadcast(&share->tdc.COND_release);
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ DBUG_VOID_RETURN;
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+
+ mysql_mutex_lock(&LOCK_unused_shares);
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ if (share->tdc.flushed)
+ {
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ mysql_mutex_unlock(&LOCK_unused_shares);
+ tdc_delete_share_from_hash(share);
+ DBUG_VOID_RETURN;
+ }
+ if (--share->tdc.ref_count)
+ {
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ mysql_mutex_unlock(&LOCK_unused_shares);
+ DBUG_VOID_RETURN;
+ }
+ /* Link share last in used_table_share list */
+ DBUG_PRINT("info", ("moving share to unused list"));
+ DBUG_ASSERT(share->tdc.next == 0);
+ share->tdc.prev= end_of_unused_share.tdc.prev;
+ *end_of_unused_share.tdc.prev= share;
+ end_of_unused_share.tdc.prev= &share->tdc.next;
+ share->tdc.next= &end_of_unused_share;
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ mysql_mutex_unlock(&LOCK_unused_shares);
+
+ /* Delete the least used share to preserve LRU order. */
+ tdc_purge(false);
+ DBUG_VOID_RETURN;
+}
+
+
+static TABLE_SHARE *tdc_delete_share(const char *db, const char *table_name)
+{
+ TABLE_SHARE *share;
+ DBUG_ENTER("tdc_delete_share");
+
+ while ((share= tdc_lock_share(db, table_name)))
+ {
+ share->tdc.ref_count++;
+ if (share->tdc.ref_count > 1)
+ {
+ tdc_unlock_share(share);
+ DBUG_RETURN(share);
+ }
+ tdc_unlock_share(share);
+
+ mysql_mutex_lock(&LOCK_unused_shares);
+ if (share->tdc.prev)
+ {
+ *share->tdc.prev= share->tdc.next;
+ share->tdc.next->tdc.prev= share->tdc.prev;
+ /* Concurrent thread may start using share again, reset prev and next. */
+ share->tdc.prev= 0;
+ share->tdc.next= 0;
+ }
+ mysql_mutex_unlock(&LOCK_unused_shares);
+
+ if (!tdc_delete_share_from_hash(share))
+ break;
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Remove all or some (depending on parameter) instances of TABLE and
+ TABLE_SHARE from the table definition cache.
+
+ @param thd Thread context
+ @param remove_type Type of removal:
+ TDC_RT_REMOVE_ALL - remove all TABLE instances and
+ TABLE_SHARE instance. There
+ should be no used TABLE objects
+ and caller should have exclusive
+ metadata lock on the table.
+ TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances
+ except those that belong to
+ this thread. There should be
+ no TABLE objects used by other
+ threads and caller should have
+ exclusive metadata lock on the
+ table.
+ TDC_RT_REMOVE_UNUSED - remove all unused TABLE
+ instances (if there are no
+ used instances will also
+ remove TABLE_SHARE).
+ TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE -
+ remove all TABLE instances
+ except those that belong to
+ this thread, but don't mark
+ TABLE_SHARE as old. There
+ should be no TABLE objects
+ used by other threads and
+ caller should have exclusive
+ metadata lock on the table.
+ @param db Name of database
+ @param table_name Name of table
+ @param kill_delayed_threads If TRUE, kill INSERT DELAYED threads
+
+ @note It assumes that table instances are already not used by any
+ (other) thread (this should be achieved by using meta-data locks).
+*/
+
+bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
+ const char *db, const char *table_name,
+ bool kill_delayed_threads)
+{
+ TABLE *table;
+ TABLE_SHARE *share;
+ bool found= false;
+ DBUG_ENTER("tdc_remove_table");
+ DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type));
+
+ DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
+ thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
+ MDL_EXCLUSIVE));
+
+ if ((share= tdc_delete_share(db, table_name)))
+ {
+ I_P_List <TABLE, TABLE_share> purge_tables;
+ uint my_refs= 1;
+
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ tc_wait_for_mdl_deadlock_detector(share);
+ /*
+ Mark share flushed in order to ensure that it gets
+ automatically deleted once it is no longer referenced.
+
+ Note that code in TABLE_SHARE::wait_for_old_version() assumes that
+ marking share flushed is followed by purge of unused table
+ shares.
+ */
+ if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE)
+ share->tdc.flushed= true;
+
+ while ((table= share->tdc.free_tables.pop_front()))
+ {
+ tc_remove_table(table);
+ purge_tables.push_front(table);
+ }
+ if (kill_delayed_threads)
+ kill_delayed_threads_for_table(share);
+
+ if (remove_type == TDC_RT_REMOVE_NOT_OWN ||
+ remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE)
+ {
+ TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
+ while ((table= it++))
+ {
+ my_refs++;
+ DBUG_ASSERT(table->in_use == thd);
+ }
+ }
+ DBUG_ASSERT(share->tdc.all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL);
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+
+ while ((table= purge_tables.pop_front()))
+ intern_close_table(table);
+
+ if (remove_type != TDC_RT_REMOVE_UNUSED)
+ {
+ /*
+ Even though current thread holds exclusive metadata lock on this share
+ (asserted above), concurrent FLUSH TABLES threads may be in process of
+ closing unused table instances belonging to this share. E.g.:
+ thr1 (FLUSH TABLES): table= share->tdc.free_tables.pop_front();
+ thr1 (FLUSH TABLES): share->tdc.all_tables.remove(table);
+ thr2 (ALTER TABLE): tdc_remove_table();
+ thr1 (FLUSH TABLES): intern_close_table(table);
+
+ Current remove type assumes that all table instances (except for those
+ that are owned by current thread) must be closed before
+ thd_remove_table() returns. Wait for such tables now.
+
+ intern_close_table() decrements ref_count and signals COND_release. When
+ ref_count drops down to number of references owned by current thread
+ waiting is completed.
+
+ Unfortunately TABLE_SHARE::wait_for_old_version() cannot be used here
+ because it waits for all table instances, whereas we have to wait only
+ for those that are not owned by current thread.
+ */
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ while (share->tdc.ref_count > my_refs)
+ mysql_cond_wait(&share->tdc.COND_release, &share->tdc.LOCK_table_share);
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
+ }
+
+ tdc_release_share(share);
+
+ found= true;
+ }
+ DBUG_ASSERT(found || remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE);
+ DBUG_RETURN(found);
+}
+
+
+/**
+ Check if table's share is being removed from the table definition
+ cache and, if yes, wait until the flush is complete.
+
+ @param thd Thread context.
+ @param table_list Table which share should be checked.
+ @param timeout Timeout for waiting.
+ @param deadlock_weight Weight of this wait for deadlock detector.
+
+ @retval 0 Success. Share is up to date or has been flushed.
+ @retval 1 Error (OOM, was killed, the wait resulted
+ in a deadlock or timeout). Reported.
+*/
+
+int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name,
+ ulong wait_timeout, uint deadlock_weight,
+ ulong refresh_version)
+{
+ TABLE_SHARE *share;
+ int res= FALSE;
+
+ if ((share= tdc_lock_share(db, table_name)))
+ {
+ if (share->tdc.flushed && refresh_version > share->tdc.version)
+ {
+ struct timespec abstime;
+ set_timespec(abstime, wait_timeout);
+ res= share->wait_for_old_version(thd, &abstime, deadlock_weight);
+ }
+ else
+ tdc_unlock_share(share);
+ }
+ return res;
+}
+
+
+ulong tdc_refresh_version(void)
+{
+ my_atomic_rwlock_rdlock(&LOCK_tdc_atomics);
+ ulong v= my_atomic_load64(&tdc_version);
+ my_atomic_rwlock_rdunlock(&LOCK_tdc_atomics);
+ return v;
+}
+
+
+ulong tdc_increment_refresh_version(void)
+{
+ my_atomic_rwlock_wrlock(&LOCK_tdc_atomics);
+ ulong v= my_atomic_add64(&tdc_version, 1);
+ my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics);
+ DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", v));
+ return v + 1;
+}
+
+
+/**
+ Initialize table definition cache iterator.
+*/
+
+void TDC_iterator::init(void)
+{
+ DBUG_ENTER("TDC_iterator::init");
+ idx= 0;
+ mysql_rwlock_rdlock(&LOCK_tdc);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Deinitialize table definition cache iterator.
+*/
+
+void TDC_iterator::deinit(void)
+{
+ DBUG_ENTER("TDC_iterator::deinit");
+ mysql_rwlock_unlock(&LOCK_tdc);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get next TABLE_SHARE object from table definition cache.
+
+ Object is protected against removal from table definition cache.
+
+ @note Returned TABLE_SHARE is not guaranteed to be fully initialized:
+ tdc_acquire_share() added new share, but didn't open it yet. If caller
+ needs fully initializer share, it must lock table share mutex.
+*/
+
+TABLE_SHARE *TDC_iterator::next(void)
+{
+ TABLE_SHARE *share= 0;
+ DBUG_ENTER("TDC_iterator::next");
+ if (idx < tdc_hash.records)
+ {
+ share= (TABLE_SHARE*) my_hash_element(&tdc_hash, idx);
+ idx++;
+ }
+ DBUG_RETURN(share);
+}
+
+
+/*
+ Function to assign a new table map id to a table share.
+
+ PARAMETERS
+
+ share - Pointer to table share structure
+
+ DESCRIPTION
+
+ We are intentionally not checking that share->mutex is locked
+ since this function should only be called when opening a table
+ share and before it is entered into the table definition cache
+ (meaning that it cannot be fetched by another thread, even
+ accidentally).
+
+ PRE-CONDITION(S)
+
+ share is non-NULL
+ last_table_id_lock initialized (tdc_inited)
+
+ POST-CONDITION(S)
+
+ share->table_map_id is given a value that with a high certainty is
+ not used by any other table (the only case where a table id can be
+ reused is on wrap-around, which means more than 4 billion table
+ share opens have been executed while one table was open all the
+ time).
+
+ share->table_map_id is not ~0UL.
+*/
+
+void tdc_assign_new_table_id(TABLE_SHARE *share)
+{
+ ulong tid;
+ DBUG_ENTER("assign_new_table_id");
+ DBUG_ASSERT(share);
+ DBUG_ASSERT(tdc_inited);
+
+ /*
+ There is one reserved number that cannot be used. Remember to
+ change this when 6-byte global table id's are introduced.
+ */
+ do
+ {
+ my_atomic_rwlock_wrlock(&LOCK_tdc_atomics);
+ tid= my_atomic_add64(&last_table_id, 1);
+ my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics);
+ } while (unlikely(tid == ~0UL));
+
+ share->table_map_id= tid;
+ DBUG_PRINT("info", ("table_id= %lu", share->table_map_id));
+ DBUG_VOID_RETURN;
+}
diff --git a/sql/table_cache.h b/sql/table_cache.h
new file mode 100644
index 00000000000..ea3822f9f68
--- /dev/null
+++ b/sql/table_cache.h
@@ -0,0 +1,137 @@
+/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2011 Monty Program Ab
+ Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation
+
+ 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 */
+
+
+enum enum_tdc_remove_table_type
+{
+ TDC_RT_REMOVE_ALL,
+ TDC_RT_REMOVE_NOT_OWN,
+ TDC_RT_REMOVE_UNUSED,
+ TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE
+};
+
+extern ulong tdc_size;
+extern ulong tc_size;
+
+extern int tdc_init(void);
+extern void tdc_start_shutdown(void);
+extern void tdc_deinit(void);
+extern ulong tdc_records(void);
+extern void tdc_purge(bool all);
+extern void tdc_init_share(TABLE_SHARE *share);
+extern void tdc_deinit_share(TABLE_SHARE *share);
+extern TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name);
+extern void tdc_unlock_share(TABLE_SHARE *share);
+extern TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db,
+ const char *table_name,
+ const char *key, uint key_length,
+ my_hash_value_type hash_value,
+ uint flags, TABLE **out_table);
+extern void tdc_release_share(TABLE_SHARE *share);
+extern bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
+ const char *db, const char *table_name,
+ bool kill_delayed_threads);
+extern int tdc_wait_for_old_version(THD *thd, const char *db,
+ const char *table_name,
+ ulong wait_timeout, uint deadlock_weight,
+ ulong refresh_version= ULONG_MAX);
+extern ulong tdc_refresh_version(void);
+extern ulong tdc_increment_refresh_version(void);
+extern void tdc_assign_new_table_id(TABLE_SHARE *share);
+
+extern uint tc_records(void);
+extern void tc_purge(bool mark_flushed= false);
+extern void tc_add_table(THD *thd, TABLE *table);
+extern bool tc_release_table(TABLE *table);
+
+/**
+ Create a table cache key for non-temporary table.
+
+ @param key Buffer for key (must be at least NAME_LEN*2+2 bytes).
+ @param db Database name.
+ @param table_name Table name.
+
+ @return Length of key.
+*/
+
+inline uint tdc_create_key(char *key, const char *db, const char *table_name)
+{
+ /*
+ In theory caller should ensure that both db and table_name are
+ not longer than NAME_LEN bytes. In practice we play safe to avoid
+ buffer overruns.
+ */
+ return (uint) (strmake(strmake(key, db, NAME_LEN) + 1, table_name,
+ NAME_LEN) - key + 1);
+}
+
+/**
+ Convenience helper: call tdc_acquire_share() without out_table.
+*/
+
+static inline TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db,
+ const char *table_name,
+ const char *key,
+ uint key_length, uint flags)
+{
+ return tdc_acquire_share(thd, db, table_name, key, key_length,
+ my_hash_sort(&my_charset_bin, (uchar*) key,
+ key_length), flags, 0);
+}
+
+
+/**
+ Convenience helper: call tdc_acquire_share() without precomputed cache key.
+*/
+
+static inline TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db,
+ const char *table_name, uint flags)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ key_length= tdc_create_key(key, db, table_name);
+ return tdc_acquire_share(thd, db, table_name, key, key_length, flags);
+}
+
+
+/**
+ Convenience helper: call tdc_acquire_share() reusing the MDL cache key.
+
+ @note lifetime of the returned TABLE_SHARE is limited by the
+ lifetime of the TABLE_LIST object!!!
+*/
+
+uint get_table_def_key(const TABLE_LIST *table_list, const char **key);
+
+static inline TABLE_SHARE *tdc_acquire_share_shortlived(THD *thd, TABLE_LIST *tl,
+ uint flags)
+{
+ const char *key;
+ uint key_length= get_table_def_key(tl, &key);
+ return tdc_acquire_share(thd, tl->db, tl->table_name, key, key_length,
+ tl->mdl_request.key.tc_hash_value(), flags, 0);
+}
+
+
+class TDC_iterator
+{
+ ulong idx;
+public:
+ void init(void);
+ void deinit(void);
+ TABLE_SHARE *next(void);
+};
diff --git a/sql/thr_malloc.cc b/sql/thr_malloc.cc
new file mode 100644
index 00000000000..9786f1a6942
--- /dev/null
+++ b/sql/thr_malloc.cc
@@ -0,0 +1,148 @@
+/*
+ Copyright (c) 2000, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+
+/* Mallocs for used in threads */
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "thr_malloc.h"
+#include "sql_class.h"
+
+extern "C" {
+ void sql_alloc_error_handler(void)
+ {
+ THD *thd= current_thd;
+ if (thd)
+ {
+ if (! thd->is_error())
+ {
+ /*
+ This thread is Out Of Memory.
+ An OOM condition is a fatal error.
+ It should not be caught by error handlers in stored procedures.
+ Also, recording that SQL condition in the condition area could
+ cause more memory allocations, which in turn could raise more
+ OOM conditions, causing recursion in the error handling code itself.
+ As a result, my_error() should not be invoked, and the
+ thread diagnostics area is set to an error status directly.
+ Note that Diagnostics_area::set_error_status() is safe,
+ since it does not call any memory allocation routines.
+ The visible result for a client application will be:
+ - a query fails with an ER_OUT_OF_RESOURCES error,
+ returned in the error packet.
+ - SHOW ERROR/SHOW WARNINGS may be empty.
+ */
+ thd->get_stmt_da()->set_error_status(ER_OUT_OF_RESOURCES);
+ }
+ }
+
+ /* Skip writing to the error log to avoid mtr complaints */
+ DBUG_EXECUTE_IF("simulate_out_of_memory", return;);
+
+ sql_print_error("%s", ER(ER_OUT_OF_RESOURCES));
+
+ }
+}
+
+void init_sql_alloc(MEM_ROOT *mem_root, uint block_size, uint pre_alloc,
+ myf my_flags)
+{
+ init_alloc_root(mem_root, block_size, pre_alloc, my_flags);
+ mem_root->error_handler=sql_alloc_error_handler;
+}
+
+
+#ifndef MYSQL_CLIENT
+void *sql_alloc(size_t Size)
+{
+ MEM_ROOT *root= *my_pthread_getspecific_ptr(MEM_ROOT**,THR_MALLOC);
+ return alloc_root(root,Size);
+}
+#endif
+
+
+void *sql_calloc(size_t size)
+{
+ void *ptr;
+ if ((ptr=sql_alloc(size)))
+ bzero(ptr,size);
+ return ptr;
+}
+
+
+char *sql_strdup(const char *str)
+{
+ size_t len= strlen(str)+1;
+ char *pos;
+ if ((pos= (char*) sql_alloc(len)))
+ memcpy(pos,str,len);
+ return pos;
+}
+
+
+char *sql_strmake(const char *str, size_t len)
+{
+ char *pos;
+ if ((pos= (char*) sql_alloc(len+1)))
+ {
+ memcpy(pos,str,len);
+ pos[len]=0;
+ }
+ return pos;
+}
+
+
+void* sql_memdup(const void *ptr, size_t len)
+{
+ void *pos;
+ if ((pos= sql_alloc(len)))
+ memcpy(pos,ptr,len);
+ return pos;
+}
+
+
+char *sql_strmake_with_convert(const char *str, size_t arg_length,
+ CHARSET_INFO *from_cs,
+ size_t max_res_length,
+ CHARSET_INFO *to_cs, size_t *result_length)
+{
+ char *pos;
+ size_t new_length= to_cs->mbmaxlen*arg_length;
+ max_res_length--; // Reserve place for end null
+
+ set_if_smaller(new_length, max_res_length);
+ if (!(pos= (char*) sql_alloc(new_length+1)))
+ return pos; // Error
+
+ if ((from_cs == &my_charset_bin) || (to_cs == &my_charset_bin))
+ {
+ // Safety if to_cs->mbmaxlen > 0
+ new_length= MY_MIN(arg_length, max_res_length);
+ memcpy(pos, str, new_length);
+ }
+ else
+ {
+ uint dummy_errors;
+ new_length= copy_and_convert((char*) pos, new_length, to_cs, str,
+ arg_length, from_cs, &dummy_errors);
+ }
+ pos[new_length]= 0;
+ *result_length= new_length;
+ return pos;
+}
+
diff --git a/sql/thr_malloc.h b/sql/thr_malloc.h
new file mode 100644
index 00000000000..0b17c5cdaf1
--- /dev/null
+++ b/sql/thr_malloc.h
@@ -0,0 +1,35 @@
+/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef THR_MALLOC_INCLUDED
+#define THR_MALLOC_INCLUDED
+
+#include "my_global.h" // uint, size_t
+
+typedef struct st_mem_root MEM_ROOT;
+
+void init_sql_alloc(MEM_ROOT *root, uint block_size, uint pre_alloc_size,
+ myf my_flags);
+void *sql_alloc(size_t);
+void *sql_calloc(size_t);
+char *sql_strdup(const char *str);
+char *sql_strmake(const char *str, size_t len);
+void *sql_memdup(const void * ptr, size_t size);
+char *sql_strmake_with_convert(const char *str, size_t arg_length,
+ CHARSET_INFO *from_cs,
+ size_t max_res_length,
+ CHARSET_INFO *to_cs, size_t *result_length);
+
+#endif /* THR_MALLOC_INCLUDED */
diff --git a/sql/threadpool.h b/sql/threadpool.h
new file mode 100644
index 00000000000..c080e5ba343
--- /dev/null
+++ b/sql/threadpool.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 2012 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#define MAX_THREAD_GROUPS 100000
+
+/* Threadpool parameters */
+extern uint threadpool_min_threads; /* Minimum threads in pool */
+extern uint threadpool_idle_timeout; /* Shutdown idle worker threads after this timeout */
+extern uint threadpool_size; /* Number of parallel executing threads */
+extern uint threadpool_max_size;
+extern uint threadpool_stall_limit; /* time interval in 10 ms units for stall checks*/
+extern uint threadpool_max_threads; /* Maximum threads in pool */
+extern uint threadpool_oversubscribe; /* Maximum active threads in group */
+
+
+
+/* Common thread pool routines, suitable for different implementations */
+extern void threadpool_remove_connection(THD *thd);
+extern int threadpool_process_request(THD *thd);
+extern int threadpool_add_connection(THD *thd);
+
+/*
+ Functions used by scheduler.
+ OS-specific implementations are in
+ threadpool_unix.cc or threadpool_win.cc
+*/
+extern bool tp_init();
+extern void tp_add_connection(THD*);
+extern void tp_wait_begin(THD *, int);
+extern void tp_wait_end(THD*);
+extern void tp_post_kill_notification(THD *thd);
+extern void tp_end(void);
+
+/* Used in SHOW for threadpool_idle_thread_count */
+extern int tp_get_idle_thread_count();
+
+/*
+ Threadpool statistics
+*/
+struct TP_STATISTICS
+{
+ /* Current number of worker thread. */
+ volatile int32 num_worker_threads;
+};
+
+extern TP_STATISTICS tp_stats;
+
+
+/* Functions to set threadpool parameters */
+extern void tp_set_min_threads(uint val);
+extern void tp_set_max_threads(uint val);
+extern void tp_set_threadpool_size(uint val);
+extern void tp_set_threadpool_stall_limit(uint val);
+
+/* Activate threadpool scheduler */
+extern void tp_scheduler(void);
+
+extern int show_threadpool_idle_threads(THD *thd, SHOW_VAR *var, char *buff);
diff --git a/sql/threadpool_common.cc b/sql/threadpool_common.cc
new file mode 100644
index 00000000000..9e0cb07b86c
--- /dev/null
+++ b/sql/threadpool_common.cc
@@ -0,0 +1,282 @@
+/* Copyright (C) 2012 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <my_global.h>
+#include <violite.h>
+#include <sql_priv.h>
+#include <sql_class.h>
+#include <my_pthread.h>
+#include <scheduler.h>
+#include <sql_connect.h>
+#include <sql_audit.h>
+#include <debug_sync.h>
+#include <threadpool.h>
+
+
+/* Threadpool parameters */
+
+uint threadpool_min_threads;
+uint threadpool_idle_timeout;
+uint threadpool_size;
+uint threadpool_max_size;
+uint threadpool_stall_limit;
+uint threadpool_max_threads;
+uint threadpool_oversubscribe;
+
+/* Stats */
+TP_STATISTICS tp_stats;
+
+
+extern "C" pthread_key(struct st_my_thread_var*, THR_KEY_mysys);
+extern bool do_command(THD*);
+
+/*
+ Worker threads contexts, and THD contexts.
+ =========================================
+
+ Both worker threads and connections have their sets of thread local variables
+ At the moment it is mysys_var (this has specific data for dbug, my_error and
+ similar goodies), and PSI per-client structure.
+
+ Whenever query is executed following needs to be done:
+
+ 1. Save worker thread context.
+ 2. Change TLS variables to connection specific ones using thread_attach(THD*).
+ This function does some additional work , e.g setting up
+ thread_stack/thread_ends_here pointers.
+ 3. Process query
+ 4. Restore worker thread context.
+
+ Connection login and termination follows similar schema w.r.t saving and
+ restoring contexts.
+
+ For both worker thread, and for the connection, mysys variables are created
+ using my_thread_init() and freed with my_thread_end().
+
+*/
+struct Worker_thread_context
+{
+ PSI_thread *psi_thread;
+ st_my_thread_var* mysys_var;
+
+ void save()
+ {
+#ifdef HAVE_PSI_INTERFACE
+ psi_thread= PSI_server?PSI_server->get_thread():0;
+#endif
+ mysys_var= (st_my_thread_var *)pthread_getspecific(THR_KEY_mysys);
+ }
+
+ void restore()
+ {
+#ifdef HAVE_PSI_INTERFACE
+ if (PSI_server)
+ PSI_server->set_thread(psi_thread);
+#endif
+ pthread_setspecific(THR_KEY_mysys,mysys_var);
+ pthread_setspecific(THR_THD, 0);
+ pthread_setspecific(THR_MALLOC, 0);
+ }
+};
+
+
+/*
+ Attach/associate the connection with the OS thread,
+*/
+static bool thread_attach(THD* thd)
+{
+ pthread_setspecific(THR_KEY_mysys,thd->mysys_var);
+ thd->thread_stack=(char*)&thd;
+ thd->store_globals();
+#ifdef HAVE_PSI_INTERFACE
+ if (PSI_server)
+ PSI_server->set_thread(thd->event_scheduler.m_psi);
+#endif
+ return 0;
+}
+
+
+int threadpool_add_connection(THD *thd)
+{
+ int retval=1;
+ Worker_thread_context worker_context;
+ worker_context.save();
+
+ /*
+ Create a new connection context: mysys_thread_var and PSI thread
+ Store them in THD.
+ */
+
+ pthread_setspecific(THR_KEY_mysys, 0);
+ my_thread_init();
+ thd->mysys_var= (st_my_thread_var *)pthread_getspecific(THR_KEY_mysys);
+ if (!thd->mysys_var)
+ {
+ /* Out of memory? */
+ worker_context.restore();
+ return 1;
+ }
+
+ /* Create new PSI thread for use with the THD. */
+#ifdef HAVE_PSI_INTERFACE
+ if (PSI_server)
+ {
+ thd->event_scheduler.m_psi =
+ PSI_server->new_thread(key_thread_one_connection, thd, thd->thread_id);
+ }
+#endif
+
+
+ /* Login. */
+ thread_attach(thd);
+ ulonglong now= microsecond_interval_timer();
+ thd->prior_thr_create_utime= now;
+ thd->start_utime= now;
+ thd->thr_create_utime= now;
+
+ if (!setup_connection_thread_globals(thd))
+ {
+ if (!login_connection(thd))
+ {
+ prepare_new_connection_state(thd);
+
+ /*
+ Check if THD is ok, as prepare_new_connection_state()
+ can fail, for example if init command failed.
+ */
+ if (thd_is_connection_alive(thd))
+ {
+ retval= 0;
+ thd->net.reading_or_writing= 1;
+ thd->skip_wait_timeout= true;
+ }
+ }
+ }
+ worker_context.restore();
+ return retval;
+}
+
+
+void threadpool_remove_connection(THD *thd)
+{
+
+ Worker_thread_context worker_context;
+ worker_context.save();
+
+ thread_attach(thd);
+ thd->net.reading_or_writing= 0;
+
+ end_connection(thd);
+ close_connection(thd, 0);
+
+ unlink_thd(thd);
+ mysql_cond_broadcast(&COND_thread_count);
+
+ /*
+ Free resources associated with this connection:
+ mysys thread_var and PSI thread.
+ */
+ my_thread_end();
+
+ worker_context.restore();
+}
+
+/**
+ Process a single client request or a single batch.
+*/
+int threadpool_process_request(THD *thd)
+{
+ int retval= 0;
+ Worker_thread_context worker_context;
+ worker_context.save();
+
+ thread_attach(thd);
+
+ if (thd->killed >= KILL_CONNECTION)
+ {
+ /*
+ killed flag was set by timeout handler
+ or KILL command. Return error.
+ */
+ retval= 1;
+ goto end;
+ }
+
+
+ /*
+ In the loop below, the flow is essentially the copy of thead-per-connections
+ logic, see do_handle_one_connection() in sql_connect.c
+
+ The goal is to execute a single query, thus the loop is normally executed
+ only once. However for SSL connections, it can be executed multiple times
+ (SSL can preread and cache incoming data, and vio->has_data() checks if it
+ was the case).
+ */
+ for(;;)
+ {
+ Vio *vio;
+ thd->net.reading_or_writing= 0;
+ mysql_audit_release(thd);
+
+ if ((retval= do_command(thd)) != 0)
+ goto end;
+
+ if (!thd_is_connection_alive(thd))
+ {
+ retval= 1;
+ goto end;
+ }
+
+ vio= thd->net.vio;
+ if (!vio->has_data(vio))
+ {
+ /* More info on this debug sync is in sql_parse.cc*/
+ DEBUG_SYNC(thd, "before_do_command_net_read");
+ thd->net.reading_or_writing= 1;
+ goto end;
+ }
+ }
+
+end:
+ worker_context.restore();
+ return retval;
+}
+
+
+static scheduler_functions tp_scheduler_functions=
+{
+ 0, // max_threads
+ NULL,
+ NULL,
+ tp_init, // init
+ NULL, // init_new_connection_thread
+ tp_add_connection, // add_connection
+ tp_wait_begin, // thd_wait_begin
+ tp_wait_end, // thd_wait_end
+ post_kill_notification, // post_kill_notification
+ NULL, // end_thread
+ tp_end // end
+};
+
+void pool_of_threads_scheduler(struct scheduler_functions *func,
+ ulong *arg_max_connections,
+ uint *arg_connection_count)
+{
+ *func = tp_scheduler_functions;
+ func->max_threads= threadpool_max_threads;
+ func->max_connections= arg_max_connections;
+ func->connection_count= arg_connection_count;
+ scheduler_init();
+}
diff --git a/sql/threadpool_unix.cc b/sql/threadpool_unix.cc
new file mode 100644
index 00000000000..68c032fb67b
--- /dev/null
+++ b/sql/threadpool_unix.cc
@@ -0,0 +1,1685 @@
+/* Copyright (C) 2012 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <my_global.h>
+#include <violite.h>
+#include <sql_priv.h>
+#include <sql_class.h>
+#include <my_pthread.h>
+#include <scheduler.h>
+
+#ifdef HAVE_POOL_OF_THREADS
+
+#include <sql_connect.h>
+#include <mysqld.h>
+#include <debug_sync.h>
+#include <time.h>
+#include <sql_plist.h>
+#include <threadpool.h>
+#include <time.h>
+#ifdef __linux__
+#include <sys/epoll.h>
+typedef struct epoll_event native_event;
+#elif defined(HAVE_KQUEUE)
+#include <sys/event.h>
+typedef struct kevent native_event;
+#elif defined (__sun)
+#include <port.h>
+typedef port_event_t native_event;
+#else
+#error threadpool is not available on this platform
+#endif
+
+/** Maximum number of native events a listener can read in one go */
+#define MAX_EVENTS 1024
+
+/** Indicates that threadpool was initialized*/
+static bool threadpool_started= false;
+
+/*
+ Define PSI Keys for performance schema.
+ We have a mutex per group, worker threads, condition per worker thread,
+ and timer thread with its own mutex and condition.
+*/
+
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_group_mutex;
+static PSI_mutex_key key_timer_mutex;
+static PSI_mutex_info mutex_list[]=
+{
+ { &key_group_mutex, "group_mutex", 0},
+ { &key_timer_mutex, "timer_mutex", PSI_FLAG_GLOBAL}
+};
+
+static PSI_cond_key key_worker_cond;
+static PSI_cond_key key_timer_cond;
+static PSI_cond_info cond_list[]=
+{
+ { &key_worker_cond, "worker_cond", 0},
+ { &key_timer_cond, "timer_cond", PSI_FLAG_GLOBAL}
+};
+
+static PSI_thread_key key_worker_thread;
+static PSI_thread_key key_timer_thread;
+static PSI_thread_info thread_list[] =
+{
+ {&key_worker_thread, "worker_thread", 0},
+ {&key_timer_thread, "timer_thread", PSI_FLAG_GLOBAL}
+};
+
+/* Macro to simplify performance schema registration */
+#define PSI_register(X) \
+ if(PSI_server) PSI_server->register_ ## X("threadpool", X ## _list, array_elements(X ## _list))
+#else
+#define PSI_register(X) /* no-op */
+#endif
+
+
+struct thread_group_t;
+
+/* Per-thread structure for workers */
+struct worker_thread_t
+{
+ ulonglong event_count; /* number of request handled by this thread */
+ thread_group_t* thread_group;
+ worker_thread_t *next_in_list;
+ worker_thread_t **prev_in_list;
+
+ mysql_cond_t cond;
+ bool woken;
+};
+
+typedef I_P_List<worker_thread_t, I_P_List_adapter<worker_thread_t,
+ &worker_thread_t::next_in_list,
+ &worker_thread_t::prev_in_list>
+ >
+worker_list_t;
+
+struct connection_t
+{
+
+ THD *thd;
+ thread_group_t *thread_group;
+ connection_t *next_in_queue;
+ connection_t **prev_in_queue;
+ ulonglong abs_wait_timeout;
+ bool logged_in;
+ bool bound_to_poll_descriptor;
+ bool waiting;
+};
+
+typedef I_P_List<connection_t,
+ I_P_List_adapter<connection_t,
+ &connection_t::next_in_queue,
+ &connection_t::prev_in_queue>,
+ I_P_List_null_counter,
+ I_P_List_fast_push_back<connection_t> >
+connection_queue_t;
+
+struct thread_group_t
+{
+ mysql_mutex_t mutex;
+ connection_queue_t queue;
+ worker_list_t waiting_threads;
+ worker_thread_t *listener;
+ pthread_attr_t *pthread_attr;
+ int pollfd;
+ int thread_count;
+ int active_thread_count;
+ int connection_count;
+ /* Stats for the deadlock detection timer routine.*/
+ int io_event_count;
+ int queue_event_count;
+ ulonglong last_thread_creation_time;
+ int shutdown_pipe[2];
+ bool shutdown;
+ bool stalled;
+
+} MY_ALIGNED(512);
+
+static thread_group_t *all_groups;
+static uint group_count;
+static int32 shutdown_group_count;
+
+/**
+ Used for printing "pool blocked" message, see
+ print_pool_blocked_message();
+*/
+static ulonglong pool_block_start;
+
+/* Global timer for all groups */
+struct pool_timer_t
+{
+ mysql_mutex_t mutex;
+ mysql_cond_t cond;
+ volatile uint64 current_microtime;
+ volatile uint64 next_timeout_check;
+ int tick_interval;
+ bool shutdown;
+};
+
+static pool_timer_t pool_timer;
+
+static void queue_put(thread_group_t *thread_group, connection_t *connection);
+static int wake_thread(thread_group_t *thread_group);
+static void handle_event(connection_t *connection);
+static int wake_or_create_thread(thread_group_t *thread_group);
+static int create_worker(thread_group_t *thread_group);
+static void *worker_main(void *param);
+static void check_stall(thread_group_t *thread_group);
+static void connection_abort(connection_t *connection);
+static void set_wait_timeout(connection_t *connection);
+static void set_next_timeout_check(ulonglong abstime);
+static void print_pool_blocked_message(bool);
+
+/**
+ Asynchronous network IO.
+
+ We use native edge-triggered network IO multiplexing facility.
+ This maps to different APIs on different Unixes.
+
+ Supported are currently Linux with epoll, Solaris with event ports,
+ OSX and BSD with kevent. All those API's are used with one-shot flags
+ (the event is signalled once client has written something into the socket,
+ then socket is removed from the "poll-set" until the command is finished,
+ and we need to re-arm/re-register socket)
+
+ No implementation for poll/select/AIO is currently provided.
+
+ The API closely resembles all of the above mentioned platform APIs
+ and consists of following functions.
+
+ - io_poll_create()
+ Creates an io_poll descriptor
+ On Linux: epoll_create()
+
+ - io_poll_associate_fd(int poll_fd, int fd, void *data)
+ Associate file descriptor with io poll descriptor
+ On Linux : epoll_ctl(..EPOLL_CTL_ADD))
+
+ - io_poll_disassociate_fd(int pollfd, int fd)
+ Associate file descriptor with io poll descriptor
+ On Linux: epoll_ctl(..EPOLL_CTL_DEL)
+
+
+ - io_poll_start_read(int poll_fd,int fd, void *data)
+ The same as io_poll_associate_fd(), but cannot be used before
+ io_poll_associate_fd() was called.
+ On Linux : epoll_ctl(..EPOLL_CTL_MOD)
+
+ - io_poll_wait (int pollfd, native_event *native_events, int maxevents,
+ int timeout_ms)
+
+ wait until one or more descriptors added with io_poll_associate_fd()
+ or io_poll_start_read() becomes readable. Data associated with
+ descriptors can be retrieved from native_events array, using
+ native_event_get_userdata() function.
+
+
+ On Linux: epoll_wait()
+*/
+
+#if defined (__linux__)
+#ifndef EPOLLRDHUP
+/* Early 2.6 kernel did not have EPOLLRDHUP */
+#define EPOLLRDHUP 0
+#endif
+static int io_poll_create()
+{
+ return epoll_create(1);
+}
+
+
+int io_poll_associate_fd(int pollfd, int fd, void *data)
+{
+ struct epoll_event ev;
+ ev.data.u64= 0; /* Keep valgrind happy */
+ ev.data.ptr= data;
+ ev.events= EPOLLIN|EPOLLET|EPOLLERR|EPOLLRDHUP|EPOLLONESHOT;
+ return epoll_ctl(pollfd, EPOLL_CTL_ADD, fd, &ev);
+}
+
+
+
+int io_poll_start_read(int pollfd, int fd, void *data)
+{
+ struct epoll_event ev;
+ ev.data.u64= 0; /* Keep valgrind happy */
+ ev.data.ptr= data;
+ ev.events= EPOLLIN|EPOLLET|EPOLLERR|EPOLLRDHUP|EPOLLONESHOT;
+ return epoll_ctl(pollfd, EPOLL_CTL_MOD, fd, &ev);
+}
+
+int io_poll_disassociate_fd(int pollfd, int fd)
+{
+ struct epoll_event ev;
+ return epoll_ctl(pollfd, EPOLL_CTL_DEL, fd, &ev);
+}
+
+
+/*
+ Wrapper around epoll_wait.
+ NOTE - in case of EINTR, it restarts with original timeout. Since we use
+ either infinite or 0 timeouts, this is not critical
+*/
+int io_poll_wait(int pollfd, native_event *native_events, int maxevents,
+ int timeout_ms)
+{
+ int ret;
+ do
+ {
+ ret = epoll_wait(pollfd, native_events, maxevents, timeout_ms);
+ }
+ while(ret == -1 && errno == EINTR);
+ return ret;
+}
+
+
+static void *native_event_get_userdata(native_event *event)
+{
+ return event->data.ptr;
+}
+
+#elif defined(HAVE_KQUEUE)
+
+/*
+ NetBSD is incompatible with other BSDs , last parameter in EV_SET macro
+ (udata, user data) needs to be intptr_t, whereas it needs to be void*
+ everywhere else.
+*/
+
+#ifdef __NetBSD__
+#define MY_EV_SET(a, b, c, d, e, f, g) EV_SET(a, b, c, d, e, f, (intptr_t)g)
+#else
+#define MY_EV_SET(a, b, c, d, e, f, g) EV_SET(a, b, c, d, e, f, g)
+#endif
+
+
+int io_poll_create()
+{
+ return kqueue();
+}
+
+int io_poll_start_read(int pollfd, int fd, void *data)
+{
+ struct kevent ke;
+ MY_EV_SET(&ke, fd, EVFILT_READ, EV_ADD|EV_ONESHOT,
+ 0, 0, data);
+ return kevent(pollfd, &ke, 1, 0, 0, 0);
+}
+
+
+int io_poll_associate_fd(int pollfd, int fd, void *data)
+{
+ struct kevent ke;
+ MY_EV_SET(&ke, fd, EVFILT_READ, EV_ADD|EV_ONESHOT,
+ 0, 0, data);
+ return io_poll_start_read(pollfd,fd, data);
+}
+
+
+int io_poll_disassociate_fd(int pollfd, int fd)
+{
+ struct kevent ke;
+ MY_EV_SET(&ke,fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
+ return kevent(pollfd, &ke, 1, 0, 0, 0);
+}
+
+
+int io_poll_wait(int pollfd, struct kevent *events, int maxevents, int timeout_ms)
+{
+ struct timespec ts;
+ int ret;
+ if (timeout_ms >= 0)
+ {
+ ts.tv_sec= timeout_ms/1000;
+ ts.tv_nsec= (timeout_ms%1000)*1000000;
+ }
+ do
+ {
+ ret= kevent(pollfd, 0, 0, events, maxevents,
+ (timeout_ms >= 0)?&ts:NULL);
+ }
+ while (ret == -1 && errno == EINTR);
+ return ret;
+}
+
+static void* native_event_get_userdata(native_event *event)
+{
+ return (void *)event->udata;
+}
+
+#elif defined (__sun)
+
+static int io_poll_create()
+{
+ return port_create();
+}
+
+int io_poll_start_read(int pollfd, int fd, void *data)
+{
+ return port_associate(pollfd, PORT_SOURCE_FD, fd, POLLIN, data);
+}
+
+static int io_poll_associate_fd(int pollfd, int fd, void *data)
+{
+ return io_poll_start_read(pollfd, fd, data);
+}
+
+int io_poll_disassociate_fd(int pollfd, int fd)
+{
+ return port_dissociate(pollfd, PORT_SOURCE_FD, fd);
+}
+
+int io_poll_wait(int pollfd, native_event *events, int maxevents, int timeout_ms)
+{
+ struct timespec ts;
+ int ret;
+ uint_t nget= 1;
+ if (timeout_ms >= 0)
+ {
+ ts.tv_sec= timeout_ms/1000;
+ ts.tv_nsec= (timeout_ms%1000)*1000000;
+ }
+ do
+ {
+ ret= port_getn(pollfd, events, maxevents, &nget,
+ (timeout_ms >= 0)?&ts:NULL);
+ }
+ while (ret == -1 && errno == EINTR);
+ DBUG_ASSERT(nget < INT_MAX);
+ return (int)nget;
+}
+
+static void* native_event_get_userdata(native_event *event)
+{
+ return event->portev_user;
+}
+#endif
+
+
+/* Dequeue element from a workqueue */
+
+static connection_t *queue_get(thread_group_t *thread_group)
+{
+ DBUG_ENTER("queue_get");
+ thread_group->queue_event_count++;
+ connection_t *c= thread_group->queue.front();
+ if (c)
+ {
+ thread_group->queue.remove(c);
+ }
+ DBUG_RETURN(c);
+}
+
+
+/*
+ Handle wait timeout :
+ Find connections that have been idle for too long and kill them.
+ Also, recalculate time when next timeout check should run.
+*/
+
+static void timeout_check(pool_timer_t *timer)
+{
+ DBUG_ENTER("timeout_check");
+
+ mysql_mutex_lock(&LOCK_thread_count);
+ I_List_iterator<THD> it(threads);
+
+ /* Reset next timeout check, it will be recalculated in the loop below */
+ my_atomic_fas64((volatile int64*)&timer->next_timeout_check, ULONGLONG_MAX);
+
+ THD *thd;
+ while ((thd=it++))
+ {
+ if (thd->net.reading_or_writing != 1)
+ continue;
+
+ connection_t *connection= (connection_t *)thd->event_scheduler.data;
+ if (!connection)
+ {
+ /*
+ Connection does not have scheduler data. This happens for example
+ if THD belongs to a different scheduler, that is listening to extra_port.
+ */
+ continue;
+ }
+
+ if(connection->abs_wait_timeout < timer->current_microtime)
+ {
+ /* Wait timeout exceeded, kill connection. */
+ mysql_mutex_lock(&thd->LOCK_thd_data);
+ thd->killed = KILL_CONNECTION;
+ post_kill_notification(thd);
+ mysql_mutex_unlock(&thd->LOCK_thd_data);
+ }
+ else
+ {
+ set_next_timeout_check(connection->abs_wait_timeout);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_thread_count);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Timer thread.
+
+ Periodically, check if one of the thread groups is stalled. Stalls happen if
+ events are not being dequeued from the queue, or from the network, Primary
+ reason for stall can be a lengthy executing non-blocking request. It could
+ also happen that thread is waiting but wait_begin/wait_end is forgotten by
+ storage engine. Timer thread will create a new thread in group in case of
+ a stall.
+
+ Besides checking for stalls, timer thread is also responsible for terminating
+ clients that have been idle for longer than wait_timeout seconds.
+
+ TODO: Let the timer sleep for long time if there is no work to be done.
+ Currently it wakes up rather often on and idle server.
+*/
+
+static void* timer_thread(void *param)
+{
+ uint i;
+ pool_timer_t* timer=(pool_timer_t *)param;
+
+ my_thread_init();
+ DBUG_ENTER("timer_thread");
+ timer->next_timeout_check= ULONGLONG_MAX;
+ timer->current_microtime= microsecond_interval_timer();
+
+ for(;;)
+ {
+ struct timespec ts;
+ int err;
+
+ set_timespec_nsec(ts,timer->tick_interval*1000000);
+ mysql_mutex_lock(&timer->mutex);
+ err= mysql_cond_timedwait(&timer->cond, &timer->mutex, &ts);
+ if (timer->shutdown)
+ {
+ mysql_mutex_unlock(&timer->mutex);
+ break;
+ }
+ if (err == ETIMEDOUT)
+ {
+ timer->current_microtime= microsecond_interval_timer();
+
+ /* Check stalls in thread groups */
+ for (i= 0; i < threadpool_max_size; i++)
+ {
+ if(all_groups[i].connection_count)
+ check_stall(&all_groups[i]);
+ }
+
+ /* Check if any client exceeded wait_timeout */
+ if (timer->next_timeout_check <= timer->current_microtime)
+ timeout_check(timer);
+ }
+ mysql_mutex_unlock(&timer->mutex);
+ }
+
+ mysql_mutex_destroy(&timer->mutex);
+ my_thread_end();
+ return NULL;
+}
+
+
+
+void check_stall(thread_group_t *thread_group)
+{
+ if (mysql_mutex_trylock(&thread_group->mutex) != 0)
+ {
+ /* Something happens. Don't disturb */
+ return;
+ }
+
+ /*
+ Check if listener is present. If not, check whether any IO
+ events were dequeued since last time. If not, this means
+ listener is either in tight loop or thd_wait_begin()
+ was forgotten. Create a new worker(it will make itself listener).
+ */
+ if (!thread_group->listener && !thread_group->io_event_count)
+ {
+ wake_or_create_thread(thread_group);
+ mysql_mutex_unlock(&thread_group->mutex);
+ return;
+ }
+
+ /* Reset io event count */
+ thread_group->io_event_count= 0;
+
+ /*
+ Check whether requests from the workqueue are being dequeued.
+
+ The stall detection and resolution works as follows:
+
+ 1. There is a counter thread_group->queue_event_count for the number of
+ events removed from the queue. Timer resets the counter to 0 on each run.
+ 2. Timer determines stall if this counter remains 0 since last check
+ and the queue is not empty.
+ 3. Once timer determined a stall it sets thread_group->stalled flag and
+ wakes and idle worker (or creates a new one, subject to throttling).
+ 4. The stalled flag is reset, when an event is dequeued.
+
+ Q : Will this handling lead to an unbound growth of threads, if queue
+ stalls permanently?
+ A : No. If queue stalls permanently, it is an indication for many very long
+ simultaneous queries. The maximum number of simultanoues queries is
+ max_connections, further we have threadpool_max_threads limit, upon which no
+ worker threads are created. So in case there is a flood of very long
+ queries, threadpool would slowly approach thread-per-connection behavior.
+ NOTE:
+ If long queries never wait, creation of the new threads is done by timer,
+ so it is slower than in real thread-per-connection. However if long queries
+ do wait and indicate that via thd_wait_begin/end callbacks, thread creation
+ will be faster.
+ */
+ if (!thread_group->queue.is_empty() && !thread_group->queue_event_count)
+ {
+ thread_group->stalled= true;
+ wake_or_create_thread(thread_group);
+ }
+
+ /* Reset queue event count */
+ thread_group->queue_event_count= 0;
+
+ mysql_mutex_unlock(&thread_group->mutex);
+}
+
+
+static void start_timer(pool_timer_t* timer)
+{
+ pthread_t thread_id;
+ DBUG_ENTER("start_timer");
+ mysql_mutex_init(key_timer_mutex,&timer->mutex, NULL);
+ mysql_cond_init(key_timer_cond, &timer->cond, NULL);
+ timer->shutdown = false;
+ mysql_thread_create(key_timer_thread,&thread_id, NULL, timer_thread, timer);
+ DBUG_VOID_RETURN;
+}
+
+
+static void stop_timer(pool_timer_t *timer)
+{
+ DBUG_ENTER("stop_timer");
+ mysql_mutex_lock(&timer->mutex);
+ timer->shutdown = true;
+ mysql_cond_signal(&timer->cond);
+ mysql_mutex_unlock(&timer->mutex);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Poll for socket events and distribute them to worker threads
+ In many case current thread will handle single event itself.
+
+ @return a ready connection, or NULL on shutdown
+*/
+static connection_t * listener(worker_thread_t *current_thread,
+ thread_group_t *thread_group)
+{
+ DBUG_ENTER("listener");
+ connection_t *retval= NULL;
+
+ for(;;)
+ {
+ native_event ev[MAX_EVENTS];
+ int cnt;
+
+ if (thread_group->shutdown)
+ break;
+
+ cnt = io_poll_wait(thread_group->pollfd, ev, MAX_EVENTS, -1);
+
+ if (cnt <=0)
+ {
+ DBUG_ASSERT(thread_group->shutdown);
+ break;
+ }
+
+ mysql_mutex_lock(&thread_group->mutex);
+
+ if (thread_group->shutdown)
+ {
+ mysql_mutex_unlock(&thread_group->mutex);
+ break;
+ }
+
+ thread_group->io_event_count += cnt;
+
+ /*
+ We got some network events and need to make decisions : whether
+ listener hould handle events and whether or not any wake worker
+ threads so they can handle events.
+
+ Q1 : Should listener handle an event itself, or put all events into
+ queue and let workers handle the events?
+
+ Solution :
+ Generally, listener that handles events itself is preferable. We do not
+ want listener thread to change its state from waiting to running too
+ often, Since listener has just woken from poll, it better uses its time
+ slice and does some work. Besides, not handling events means they go to
+ the queue, and often to wake another worker must wake up to handle the
+ event. This is not good, as we want to avoid wakeups.
+
+ The downside of listener that also handles queries is that we can
+ potentially leave thread group for long time not picking the new
+ network events. It is not a major problem, because this stall will be
+ detected sooner or later by the timer thread. Still, relying on timer
+ is not always good, because it may "tick" too slow (large timer_interval)
+
+ We use following strategy to solve this problem - if queue was not empty
+ we suspect flood of network events and listener stays, Otherwise, it
+ handles a query.
+
+
+ Q2: If queue is not empty, how many workers to wake?
+
+ Solution:
+ We generally try to keep one thread per group active (threads handling
+ queries are considered active, unless they stuck in inside some "wait")
+ Thus, we will wake only one worker, and only if there is not active
+ threads currently,and listener is not going to handle a query. When we
+ don't wake, we hope that currently active threads will finish fast and
+ handle the queue. If this does not happen, timer thread will detect stall
+ and wake a worker.
+
+ NOTE: Currently nothing is done to detect or prevent long queuing times.
+ A solutionc for the future would be to give up "one active thread per
+ group" principle, if events stay in the queue for too long, and just wake
+ more workers.
+ */
+
+ bool listener_picks_event= thread_group->queue.is_empty();
+
+ /*
+ If listener_picks_event is set, listener thread will handle first event,
+ and put the rest into the queue. If listener_pick_event is not set, all
+ events go to the queue.
+ */
+ for(int i=(listener_picks_event)?1:0; i < cnt ; i++)
+ {
+ connection_t *c= (connection_t *)native_event_get_userdata(&ev[i]);
+ thread_group->queue.push_back(c);
+ }
+
+ if (listener_picks_event)
+ {
+ /* Handle the first event. */
+ retval= (connection_t *)native_event_get_userdata(&ev[0]);
+ mysql_mutex_unlock(&thread_group->mutex);
+ break;
+ }
+
+ if(thread_group->active_thread_count==0)
+ {
+ /* We added some work items to queue, now wake a worker. */
+ if(wake_thread(thread_group))
+ {
+ /*
+ Wake failed, hence groups has no idle threads. Now check if there are
+ any threads in the group except listener.
+ */
+ if(thread_group->thread_count == 1)
+ {
+ /*
+ Currently there is no worker thread in the group, as indicated by
+ thread_count == 1 (this means listener is the only one thread in
+ the group).
+ The queue is not empty, and listener is not going to handle
+ events. In order to drain the queue, we create a worker here.
+ Alternatively, we could just rely on timer to detect stall, and
+ create thread, but waiting for timer would be an inefficient and
+ pointless delay.
+ */
+ create_worker(thread_group);
+ }
+ }
+ }
+ mysql_mutex_unlock(&thread_group->mutex);
+ }
+
+ DBUG_RETURN(retval);
+}
+
+/**
+ Adjust thread counters in group or global
+ whenever thread is created or is about to exit
+
+ @param thread_group
+ @param count - 1, when new thread is created
+ -1, when thread is about to exit
+*/
+
+static void add_thread_count(thread_group_t *thread_group, int32 count)
+{
+ thread_group->thread_count += count;
+ /* worker starts out and end in "active" state */
+ thread_group->active_thread_count += count;
+ my_atomic_add32(&tp_stats.num_worker_threads, count);
+}
+
+
+/**
+ Creates a new worker thread.
+ thread_mutex must be held when calling this function
+
+ NOTE: in rare cases, the number of threads can exceed
+ threadpool_max_threads, because we need at least 2 threads
+ per group to prevent deadlocks (one listener + one worker)
+*/
+
+static int create_worker(thread_group_t *thread_group)
+{
+ pthread_t thread_id;
+ bool max_threads_reached= false;
+ int err;
+
+ DBUG_ENTER("create_worker");
+ if (tp_stats.num_worker_threads >= (int)threadpool_max_threads
+ && thread_group->thread_count >= 2)
+ {
+ err= 1;
+ max_threads_reached= true;
+ goto end;
+ }
+
+
+ err= mysql_thread_create(key_worker_thread, &thread_id,
+ thread_group->pthread_attr, worker_main, thread_group);
+ if (!err)
+ {
+ thread_group->last_thread_creation_time=microsecond_interval_timer();
+ thread_created++;
+ add_thread_count(thread_group, 1);
+ }
+ else
+ {
+ my_errno= errno;
+ }
+
+end:
+ if (err)
+ print_pool_blocked_message(max_threads_reached);
+ else
+ pool_block_start= 0; /* Reset pool blocked timer, if it was set */
+
+ DBUG_RETURN(err);
+}
+
+
+/**
+ Calculate microseconds throttling delay for thread creation.
+
+ The value depends on how many threads are already in the group:
+ small number of threads means no delay, the more threads the larger
+ the delay.
+
+ The actual values were not calculated using any scientific methods.
+ They just look right, and behave well in practice.
+
+ TODO: Should throttling depend on thread_pool_stall_limit?
+*/
+static ulonglong microsecond_throttling_interval(thread_group_t *thread_group)
+{
+ int count= thread_group->thread_count;
+
+ if (count < 4)
+ return 0;
+
+ if (count < 8)
+ return 50*1000;
+
+ if(count < 16)
+ return 100*1000;
+
+ return 200*1000;
+}
+
+
+/**
+ Wakes a worker thread, or creates a new one.
+
+ Worker creation is throttled, so we avoid too many threads
+ to be created during the short time.
+*/
+static int wake_or_create_thread(thread_group_t *thread_group)
+{
+ DBUG_ENTER("wake_or_create_thread");
+
+ if (thread_group->shutdown)
+ DBUG_RETURN(0);
+
+ if (wake_thread(thread_group) == 0)
+ DBUG_RETURN(0);
+
+ if (thread_group->thread_count > thread_group->connection_count)
+ DBUG_RETURN(-1);
+
+
+ if (thread_group->active_thread_count == 0)
+ {
+ /*
+ We're better off creating a new thread here with no delay, either there
+ are no workers at all, or they all are all blocking and there was no
+ idle thread to wakeup. Smells like a potential deadlock or very slowly
+ executing requests, e.g sleeps or user locks.
+ */
+ DBUG_RETURN(create_worker(thread_group));
+ }
+
+ ulonglong now = microsecond_interval_timer();
+ ulonglong time_since_last_thread_created =
+ (now - thread_group->last_thread_creation_time);
+
+ /* Throttle thread creation. */
+ if (time_since_last_thread_created >
+ microsecond_throttling_interval(thread_group))
+ {
+ DBUG_RETURN(create_worker(thread_group));
+ }
+
+ DBUG_RETURN(-1);
+}
+
+
+
+int thread_group_init(thread_group_t *thread_group, pthread_attr_t* thread_attr)
+{
+ DBUG_ENTER("thread_group_init");
+ thread_group->pthread_attr = thread_attr;
+ mysql_mutex_init(key_group_mutex, &thread_group->mutex, NULL);
+ thread_group->pollfd= -1;
+ thread_group->shutdown_pipe[0]= -1;
+ thread_group->shutdown_pipe[1]= -1;
+ thread_group->queue.empty();
+ DBUG_RETURN(0);
+}
+
+
+void thread_group_destroy(thread_group_t *thread_group)
+{
+ mysql_mutex_destroy(&thread_group->mutex);
+ if (thread_group->pollfd != -1)
+ {
+ close(thread_group->pollfd);
+ thread_group->pollfd= -1;
+ }
+ for(int i=0; i < 2; i++)
+ {
+ if(thread_group->shutdown_pipe[i] != -1)
+ {
+ close(thread_group->shutdown_pipe[i]);
+ thread_group->shutdown_pipe[i]= -1;
+ }
+ }
+ if (my_atomic_add32(&shutdown_group_count, -1) == 1)
+ my_free(all_groups);
+}
+
+/**
+ Wake sleeping thread from waiting list
+*/
+
+static int wake_thread(thread_group_t *thread_group)
+{
+ DBUG_ENTER("wake_thread");
+ worker_thread_t *thread = thread_group->waiting_threads.front();
+ if(thread)
+ {
+ thread->woken= true;
+ thread_group->waiting_threads.remove(thread);
+ mysql_cond_signal(&thread->cond);
+ DBUG_RETURN(0);
+ }
+ DBUG_RETURN(1); /* no thread in waiter list => missed wakeup */
+}
+
+
+/**
+ Initiate shutdown for thread group.
+
+ The shutdown is asynchronous, we only care to wake all threads in here, so
+ they can finish. We do not wait here until threads terminate. Final cleanup
+ of the group (thread_group_destroy) will be done by the last exiting threads.
+*/
+
+static void thread_group_close(thread_group_t *thread_group)
+{
+ DBUG_ENTER("thread_group_close");
+
+ mysql_mutex_lock(&thread_group->mutex);
+ if (thread_group->thread_count == 0)
+ {
+ mysql_mutex_unlock(&thread_group->mutex);
+ thread_group_destroy(thread_group);
+ DBUG_VOID_RETURN;
+ }
+
+ thread_group->shutdown= true;
+ thread_group->listener= NULL;
+
+ if (pipe(thread_group->shutdown_pipe))
+ {
+ DBUG_VOID_RETURN;
+ }
+
+ /* Wake listener */
+ if (io_poll_associate_fd(thread_group->pollfd,
+ thread_group->shutdown_pipe[0], NULL))
+ {
+ DBUG_VOID_RETURN;
+ }
+ char c= 0;
+ if (write(thread_group->shutdown_pipe[1], &c, 1) < 0)
+ DBUG_VOID_RETURN;
+
+ /* Wake all workers. */
+ while(wake_thread(thread_group) == 0)
+ {
+ }
+
+ mysql_mutex_unlock(&thread_group->mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Add work to the queue. Maybe wake a worker if they all sleep.
+
+ Currently, this function is only used when new connections need to
+ perform login (this is done in worker threads).
+
+*/
+
+static void queue_put(thread_group_t *thread_group, connection_t *connection)
+{
+ DBUG_ENTER("queue_put");
+
+ mysql_mutex_lock(&thread_group->mutex);
+ thread_group->queue.push_back(connection);
+
+ if (thread_group->active_thread_count == 0)
+ wake_or_create_thread(thread_group);
+
+ mysql_mutex_unlock(&thread_group->mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Prevent too many threads executing at the same time,if the workload is
+ not CPU bound.
+*/
+
+static bool too_many_threads(thread_group_t *thread_group)
+{
+ return (thread_group->active_thread_count >= 1+(int)threadpool_oversubscribe
+ && !thread_group->stalled);
+}
+
+
+/**
+ Retrieve a connection with pending event.
+
+ Pending event in our case means that there is either a pending login request
+ (if connection is not yet logged in), or there are unread bytes on the socket.
+
+ If there are no pending events currently, thread will wait.
+ If timeout specified in abstime parameter passes, the function returns NULL.
+
+ @param current_thread - current worker thread
+ @param thread_group - current thread group
+ @param abstime - absolute wait timeout
+
+ @return
+ connection with pending event.
+ NULL is returned if timeout has expired,or on shutdown.
+*/
+
+connection_t *get_event(worker_thread_t *current_thread,
+ thread_group_t *thread_group, struct timespec *abstime)
+{
+ DBUG_ENTER("get_event");
+ connection_t *connection = NULL;
+ int err=0;
+
+ mysql_mutex_lock(&thread_group->mutex);
+ DBUG_ASSERT(thread_group->active_thread_count >= 0);
+
+ for(;;)
+ {
+ bool oversubscribed = too_many_threads(thread_group);
+ if (thread_group->shutdown)
+ break;
+
+ /* Check if queue is not empty */
+ if (!oversubscribed)
+ {
+ connection = queue_get(thread_group);
+ if(connection)
+ break;
+ }
+
+ /* If there is currently no listener in the group, become one. */
+ if(!thread_group->listener)
+ {
+ thread_group->listener= current_thread;
+ thread_group->active_thread_count--;
+ mysql_mutex_unlock(&thread_group->mutex);
+
+ connection = listener(current_thread, thread_group);
+
+ mysql_mutex_lock(&thread_group->mutex);
+ thread_group->active_thread_count++;
+ /* There is no listener anymore, it just returned. */
+ thread_group->listener= NULL;
+ break;
+ }
+
+ /*
+ Last thing we try before going to sleep is to
+ pick a single event via epoll, without waiting (timeout 0)
+ */
+ if (!oversubscribed)
+ {
+ native_event nev;
+ if (io_poll_wait(thread_group->pollfd,&nev,1, 0) == 1)
+ {
+ thread_group->io_event_count++;
+ connection = (connection_t *)native_event_get_userdata(&nev);
+ break;
+ }
+ }
+
+ /* And now, finally sleep */
+ current_thread->woken = false; /* wake() sets this to true */
+
+ /*
+ Add current thread to the head of the waiting list and wait.
+ It is important to add thread to the head rather than tail
+ as it ensures LIFO wakeup order (hot caches, working inactivity timeout)
+ */
+ thread_group->waiting_threads.push_front(current_thread);
+
+ thread_group->active_thread_count--;
+ if (abstime)
+ {
+ err = mysql_cond_timedwait(&current_thread->cond, &thread_group->mutex,
+ abstime);
+ }
+ else
+ {
+ err = mysql_cond_wait(&current_thread->cond, &thread_group->mutex);
+ }
+ thread_group->active_thread_count++;
+
+ if (!current_thread->woken)
+ {
+ /*
+ Thread was not signalled by wake(), it might be a spurious wakeup or
+ a timeout. Anyhow, we need to remove ourselves from the list now.
+ If thread was explicitly woken, than caller removed us from the list.
+ */
+ thread_group->waiting_threads.remove(current_thread);
+ }
+
+ if (err)
+ break;
+ }
+
+ thread_group->stalled= false;
+ mysql_mutex_unlock(&thread_group->mutex);
+
+ DBUG_RETURN(connection);
+}
+
+
+
+/**
+ Tells the pool that worker starts waiting on IO, lock, condition,
+ sleep() or similar.
+*/
+
+void wait_begin(thread_group_t *thread_group)
+{
+ DBUG_ENTER("wait_begin");
+ mysql_mutex_lock(&thread_group->mutex);
+ thread_group->active_thread_count--;
+
+ DBUG_ASSERT(thread_group->active_thread_count >=0);
+ DBUG_ASSERT(thread_group->connection_count > 0);
+
+ if ((thread_group->active_thread_count == 0) &&
+ (thread_group->queue.is_empty() || !thread_group->listener))
+ {
+ /*
+ Group might stall while this thread waits, thus wake
+ or create a worker to prevent stall.
+ */
+ wake_or_create_thread(thread_group);
+ }
+
+ mysql_mutex_unlock(&thread_group->mutex);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Tells the pool has finished waiting.
+*/
+
+void wait_end(thread_group_t *thread_group)
+{
+ DBUG_ENTER("wait_end");
+ mysql_mutex_lock(&thread_group->mutex);
+ thread_group->active_thread_count++;
+ mysql_mutex_unlock(&thread_group->mutex);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Allocate/initialize a new connection structure.
+*/
+
+connection_t *alloc_connection(THD *thd)
+{
+ DBUG_ENTER("alloc_connection");
+
+ connection_t* connection = (connection_t *)my_malloc(sizeof(connection_t),0);
+ if (connection)
+ {
+ connection->thd = thd;
+ connection->waiting= false;
+ connection->logged_in= false;
+ connection->bound_to_poll_descriptor= false;
+ connection->abs_wait_timeout= ULONGLONG_MAX;
+ }
+ DBUG_RETURN(connection);
+}
+
+
+
+/**
+ Add a new connection to thread pool..
+*/
+
+void tp_add_connection(THD *thd)
+{
+ DBUG_ENTER("tp_add_connection");
+
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+ connection_t *connection= alloc_connection(thd);
+ if (connection)
+ {
+ thd->event_scheduler.data= connection;
+
+ /* Assign connection to a group. */
+ thread_group_t *group=
+ &all_groups[thd->thread_id%group_count];
+
+ connection->thread_group=group;
+
+ mysql_mutex_lock(&group->mutex);
+ group->connection_count++;
+ mysql_mutex_unlock(&group->mutex);
+
+ /*
+ Add connection to the work queue.Actual logon
+ will be done by a worker thread.
+ */
+ queue_put(group, connection);
+ }
+ else
+ {
+ /* Allocation failed */
+ threadpool_remove_connection(thd);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Terminate connection.
+*/
+
+static void connection_abort(connection_t *connection)
+{
+ DBUG_ENTER("connection_abort");
+ thread_group_t *group= connection->thread_group;
+
+ threadpool_remove_connection(connection->thd);
+
+ mysql_mutex_lock(&group->mutex);
+ group->connection_count--;
+ mysql_mutex_unlock(&group->mutex);
+
+ my_free(connection);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ MySQL scheduler callback: wait begin
+*/
+
+void tp_wait_begin(THD *thd, int type)
+{
+ DBUG_ENTER("tp_wait_begin");
+ DBUG_ASSERT(thd);
+ connection_t *connection = (connection_t *)thd->event_scheduler.data;
+ if (connection)
+ {
+ DBUG_ASSERT(!connection->waiting);
+ connection->waiting= true;
+ wait_begin(connection->thread_group);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ MySQL scheduler callback: wait end
+*/
+
+void tp_wait_end(THD *thd)
+{
+ DBUG_ENTER("tp_wait_end");
+ DBUG_ASSERT(thd);
+
+ connection_t *connection = (connection_t *)thd->event_scheduler.data;
+ if (connection)
+ {
+ DBUG_ASSERT(connection->waiting);
+ connection->waiting = false;
+ wait_end(connection->thread_group);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+static void set_next_timeout_check(ulonglong abstime)
+{
+ DBUG_ENTER("set_next_timeout_check");
+ while(abstime < pool_timer.next_timeout_check)
+ {
+ longlong old= (longlong)pool_timer.next_timeout_check;
+ my_atomic_cas64((volatile int64*)&pool_timer.next_timeout_check,
+ &old, abstime);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set wait timeout for connection.
+*/
+
+static void set_wait_timeout(connection_t *c)
+{
+ DBUG_ENTER("set_wait_timeout");
+ /*
+ Calculate wait deadline for this connection.
+ Instead of using microsecond_interval_timer() which has a syscall
+ overhead, use pool_timer.current_microtime and take
+ into account that its value could be off by at most
+ one tick interval.
+ */
+
+ c->abs_wait_timeout= pool_timer.current_microtime +
+ 1000LL*pool_timer.tick_interval +
+ 1000000LL*c->thd->variables.net_wait_timeout;
+
+ set_next_timeout_check(c->abs_wait_timeout);
+ DBUG_VOID_RETURN;
+}
+
+
+
+/**
+ Handle a (rare) special case,where connection needs to
+ migrate to a different group because group_count has changed
+ after thread_pool_size setting.
+*/
+
+static int change_group(connection_t *c,
+ thread_group_t *old_group,
+ thread_group_t *new_group)
+{
+ int ret= 0;
+ int fd= mysql_socket_getfd(c->thd->net.vio->mysql_socket);
+
+ DBUG_ASSERT(c->thread_group == old_group);
+
+ /* Remove connection from the old group. */
+ mysql_mutex_lock(&old_group->mutex);
+ if (c->bound_to_poll_descriptor)
+ {
+ io_poll_disassociate_fd(old_group->pollfd,fd);
+ c->bound_to_poll_descriptor= false;
+ }
+ c->thread_group->connection_count--;
+ mysql_mutex_unlock(&old_group->mutex);
+
+ /* Add connection to the new group. */
+ mysql_mutex_lock(&new_group->mutex);
+ c->thread_group= new_group;
+ new_group->connection_count++;
+ /* Ensure that there is a listener in the new group. */
+ if (!new_group->thread_count)
+ ret= create_worker(new_group);
+ mysql_mutex_unlock(&new_group->mutex);
+ return ret;
+}
+
+
+static int start_io(connection_t *connection)
+{
+ int fd = mysql_socket_getfd(connection->thd->net.vio->mysql_socket);
+
+ /*
+ Usually, connection will stay in the same group for the entire
+ connection's life. However, we do allow group_count to
+ change at runtime, which means in rare cases when it changes is
+ connection should need to migrate to another group, this ensures
+ to ensure equal load between groups.
+
+ So we recalculate in which group the connection should be, based
+ on thread_id and current group count, and migrate if necessary.
+ */
+ thread_group_t *group =
+ &all_groups[connection->thd->thread_id%group_count];
+
+ if (group != connection->thread_group)
+ {
+ if (change_group(connection, connection->thread_group, group))
+ return -1;
+ }
+
+ /*
+ Bind to poll descriptor if not yet done.
+ */
+ if (!connection->bound_to_poll_descriptor)
+ {
+ connection->bound_to_poll_descriptor= true;
+ return io_poll_associate_fd(group->pollfd, fd, connection);
+ }
+
+ return io_poll_start_read(group->pollfd, fd, connection);
+}
+
+
+
+static void handle_event(connection_t *connection)
+{
+
+ DBUG_ENTER("handle_event");
+ int err;
+
+ if (!connection->logged_in)
+ {
+ err= threadpool_add_connection(connection->thd);
+ connection->logged_in= true;
+ }
+ else
+ {
+ err= threadpool_process_request(connection->thd);
+ }
+
+ if(err)
+ goto end;
+
+ set_wait_timeout(connection);
+ err= start_io(connection);
+
+end:
+ if (err)
+ connection_abort(connection);
+
+ DBUG_VOID_RETURN;
+}
+
+
+
+/**
+ Worker thread's main
+*/
+
+static void *worker_main(void *param)
+{
+
+ worker_thread_t this_thread;
+ pthread_detach_this_thread();
+ my_thread_init();
+
+ DBUG_ENTER("worker_main");
+
+ thread_group_t *thread_group = (thread_group_t *)param;
+
+ /* Init per-thread structure */
+ mysql_cond_init(key_worker_cond, &this_thread.cond, NULL);
+ this_thread.thread_group= thread_group;
+ this_thread.event_count=0;
+
+ /* Run event loop */
+ for(;;)
+ {
+ connection_t *connection;
+ struct timespec ts;
+ set_timespec(ts,threadpool_idle_timeout);
+ connection = get_event(&this_thread, thread_group, &ts);
+ if (!connection)
+ break;
+ this_thread.event_count++;
+ handle_event(connection);
+ }
+
+ /* Thread shutdown: cleanup per-worker-thread structure. */
+ mysql_cond_destroy(&this_thread.cond);
+
+ bool last_thread; /* last thread in group exits */
+ mysql_mutex_lock(&thread_group->mutex);
+ add_thread_count(thread_group, -1);
+ last_thread= ((thread_group->thread_count == 0) && thread_group->shutdown);
+ mysql_mutex_unlock(&thread_group->mutex);
+
+ /* Last thread in group exits and pool is terminating, destroy group.*/
+ if (last_thread)
+ thread_group_destroy(thread_group);
+
+ my_thread_end();
+ return NULL;
+}
+
+
+bool tp_init()
+{
+ DBUG_ENTER("tp_init");
+ threadpool_max_size= MY_MAX(threadpool_size, 128);
+ all_groups= (thread_group_t *)
+ my_malloc(sizeof(thread_group_t) * threadpool_max_size, MYF(MY_WME|MY_ZEROFILL));
+ if (!all_groups)
+ {
+ threadpool_max_size= 0;
+ DBUG_RETURN(1);
+ }
+ threadpool_started= true;
+ scheduler_init();
+
+ for (uint i= 0; i < threadpool_max_size; i++)
+ {
+ thread_group_init(&all_groups[i], get_connection_attrib());
+ }
+ tp_set_threadpool_size(threadpool_size);
+ if(group_count == 0)
+ {
+ /* Something went wrong */
+ sql_print_error("Can't set threadpool size to %d",threadpool_size);
+ DBUG_RETURN(1);
+ }
+ PSI_register(mutex);
+ PSI_register(cond);
+ PSI_register(thread);
+
+ pool_timer.tick_interval= threadpool_stall_limit;
+ start_timer(&pool_timer);
+ DBUG_RETURN(0);
+}
+
+
+void tp_end()
+{
+ DBUG_ENTER("tp_end");
+
+ if (!threadpool_started)
+ DBUG_VOID_RETURN;
+
+ stop_timer(&pool_timer);
+ shutdown_group_count= threadpool_max_size;
+ for (uint i= 0; i < threadpool_max_size; i++)
+ {
+ thread_group_close(&all_groups[i]);
+ }
+ threadpool_started= false;
+ DBUG_VOID_RETURN;
+}
+
+
+/** Ensure that poll descriptors are created when threadpool_size changes */
+
+void tp_set_threadpool_size(uint size)
+{
+ bool success= true;
+ if (!threadpool_started)
+ return;
+
+ for(uint i=0; i< size; i++)
+ {
+ thread_group_t *group= &all_groups[i];
+ mysql_mutex_lock(&group->mutex);
+ if (group->pollfd == -1)
+ {
+ group->pollfd= io_poll_create();
+ success= (group->pollfd >= 0);
+ if(!success)
+ {
+ sql_print_error("io_poll_create() failed, errno=%d\n", errno);
+ break;
+ }
+ }
+ mysql_mutex_unlock(&all_groups[i].mutex);
+ if (!success)
+ {
+ group_count= i;
+ return;
+ }
+ }
+ group_count= size;
+}
+
+void tp_set_threadpool_stall_limit(uint limit)
+{
+ if (!threadpool_started)
+ return;
+ mysql_mutex_lock(&(pool_timer.mutex));
+ pool_timer.tick_interval= limit;
+ mysql_mutex_unlock(&(pool_timer.mutex));
+ mysql_cond_signal(&(pool_timer.cond));
+}
+
+
+/**
+ Calculate number of idle/waiting threads in the pool.
+
+ Sum idle threads over all groups.
+ Don't do any locking, it is not required for stats.
+*/
+
+int tp_get_idle_thread_count()
+{
+ int sum=0;
+ for (uint i= 0; i < threadpool_max_size && all_groups[i].pollfd >= 0; i++)
+ {
+ sum+= (all_groups[i].thread_count - all_groups[i].active_thread_count);
+ }
+ return sum;
+}
+
+
+/* Report threadpool problems */
+
+/**
+ Delay in microseconds, after which "pool blocked" message is printed.
+ (30 sec == 30 Mio usec)
+*/
+#define BLOCK_MSG_DELAY 30*1000000
+
+#define MAX_THREADS_REACHED_MSG \
+"Threadpool could not create additional thread to handle queries, because the \
+number of allowed threads was reached. Increasing 'thread_pool_max_threads' \
+parameter can help in this situation.\n \
+If 'extra_port' parameter is set, you can still connect to the database with \
+superuser account (it must be TCP connection using extra_port as TCP port) \
+and troubleshoot the situation. \
+A likely cause of pool blocks are clients that lock resources for long time. \
+'show processlist' or 'show engine innodb status' can give additional hints."
+
+#define CREATE_THREAD_ERROR_MSG "Can't create threads in threadpool (errno=%d)."
+
+/**
+ Write a message when blocking situation in threadpool occurs.
+ The message is written only when pool blocks for BLOCK_MSG_DELAY (30) seconds.
+ It will be just a single message for each blocking situation (to prevent
+ log flood).
+*/
+
+static void print_pool_blocked_message(bool max_threads_reached)
+{
+ ulonglong now;
+ static bool msg_written;
+
+ now= microsecond_interval_timer();
+ if (pool_block_start == 0)
+ {
+ pool_block_start= now;
+ msg_written = false;
+ return;
+ }
+
+ if (now > pool_block_start + BLOCK_MSG_DELAY && !msg_written)
+ {
+ if (max_threads_reached)
+ sql_print_error(MAX_THREADS_REACHED_MSG);
+ else
+ sql_print_error(CREATE_THREAD_ERROR_MSG, my_errno);
+
+ sql_print_information("Threadpool has been blocked for %u seconds\n",
+ (uint)((now- pool_block_start)/1000000));
+ /* avoid reperated messages for the same blocking situation */
+ msg_written= true;
+ }
+}
+
+#endif /* HAVE_POOL_OF_THREADS */
diff --git a/sql/threadpool_win.cc b/sql/threadpool_win.cc
new file mode 100644
index 00000000000..9cef1af272c
--- /dev/null
+++ b/sql/threadpool_win.cc
@@ -0,0 +1,747 @@
+/* Copyright (C) 2012 Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+
+#define _WIN32_WINNT 0x0601
+
+#include <my_global.h>
+#include <violite.h>
+#include <sql_priv.h>
+#include <sql_class.h>
+#include <my_pthread.h>
+#include <scheduler.h>
+#include <sql_connect.h>
+#include <mysqld.h>
+#include <debug_sync.h>
+#include <threadpool.h>
+#include <windows.h>
+
+
+/*
+ Threadpool API is not available on XP. We still want to compile a single
+ version on Windows, but use the latest functionality if available.
+ We cannot use threadpool functionality directly, since executable won't
+ start on XP and loader will complain about missing symbols.
+
+ We solve using the usual way it is done on Windows, i.e with dynamic loading.
+ We'll need to load a lot of function, and make this less painful with the
+ WEAK_SYMBOL macro below
+*/
+
+/*
+ WEAK_SYMBOL(return_type, function_name, argument_type1,..,argument_typeN)
+
+ Declare and load function pointer from kernel32. The name of the static
+ variable that holds the function pointer is my_<original function name>
+ This should be combined with
+ #define <original function name> my_<original function name>
+ so that one could use Widows APIs transparently, without worrying whether
+ they are present in a particular version or not.
+
+ Of course, prior to use of any function there should be a check for correct
+ Windows version, or check whether function pointer is not NULL.
+*/
+#define WEAK_SYMBOL(return_type, function, ...) \
+ typedef return_type (WINAPI *pFN_##function)(__VA_ARGS__); \
+ static pFN_##function my_##function = (pFN_##function) \
+ (GetProcAddress(GetModuleHandle("kernel32"),#function))
+
+WEAK_SYMBOL(VOID, CancelThreadpoolIo, PTP_IO);
+#define CancelThreadpoolIo my_CancelThreadpoolIo
+
+WEAK_SYMBOL(VOID, CloseThreadpool, PTP_POOL);
+#define CloseThreadpool my_CloseThreadpool
+
+WEAK_SYMBOL(VOID, CloseThreadpoolIo, PTP_IO);
+#define CloseThreadpoolIo my_CloseThreadpoolIo
+
+WEAK_SYMBOL(VOID, CloseThreadpoolTimer,PTP_TIMER);
+#define CloseThreadpoolTimer my_CloseThreadpoolTimer
+
+WEAK_SYMBOL(VOID, CloseThreadpoolWait,PTP_WAIT);
+#define CloseThreadpoolWait my_CloseThreadpoolWait
+
+WEAK_SYMBOL(PTP_POOL, CreateThreadpool,PVOID);
+#define CreateThreadpool my_CreateThreadpool
+
+WEAK_SYMBOL(PTP_IO, CreateThreadpoolIo, HANDLE, PTP_WIN32_IO_CALLBACK, PVOID ,
+ PTP_CALLBACK_ENVIRON);
+#define CreateThreadpoolIo my_CreateThreadpoolIo
+
+WEAK_SYMBOL(PTP_TIMER, CreateThreadpoolTimer, PTP_TIMER_CALLBACK ,
+ PVOID pv, PTP_CALLBACK_ENVIRON pcbe);
+#define CreateThreadpoolTimer my_CreateThreadpoolTimer
+
+WEAK_SYMBOL(PTP_WAIT, CreateThreadpoolWait, PTP_WAIT_CALLBACK, PVOID,
+ PTP_CALLBACK_ENVIRON);
+#define CreateThreadpoolWait my_CreateThreadpoolWait
+
+WEAK_SYMBOL(VOID, DisassociateCurrentThreadFromCallback, PTP_CALLBACK_INSTANCE);
+#define DisassociateCurrentThreadFromCallback my_DisassociateCurrentThreadFromCallback
+
+WEAK_SYMBOL(DWORD, FlsAlloc, PFLS_CALLBACK_FUNCTION);
+#define FlsAlloc my_FlsAlloc
+
+WEAK_SYMBOL(PVOID, FlsGetValue, DWORD);
+#define FlsGetValue my_FlsGetValue
+
+WEAK_SYMBOL(BOOL, FlsSetValue, DWORD, PVOID);
+#define FlsSetValue my_FlsSetValue
+
+WEAK_SYMBOL(VOID, SetThreadpoolThreadMaximum, PTP_POOL, DWORD);
+#define SetThreadpoolThreadMaximum my_SetThreadpoolThreadMaximum
+
+WEAK_SYMBOL(BOOL, SetThreadpoolThreadMinimum, PTP_POOL, DWORD);
+#define SetThreadpoolThreadMinimum my_SetThreadpoolThreadMinimum
+
+WEAK_SYMBOL(VOID, SetThreadpoolTimer, PTP_TIMER, PFILETIME,DWORD,DWORD);
+#define SetThreadpoolTimer my_SetThreadpoolTimer
+
+WEAK_SYMBOL(VOID, SetThreadpoolWait, PTP_WAIT,HANDLE,PFILETIME);
+#define SetThreadpoolWait my_SetThreadpoolWait
+
+WEAK_SYMBOL(VOID, StartThreadpoolIo, PTP_IO);
+#define StartThreadpoolIo my_StartThreadpoolIo
+
+WEAK_SYMBOL(VOID, WaitForThreadpoolIoCallbacks,PTP_IO, BOOL);
+#define WaitForThreadpoolIoCallbacks my_WaitForThreadpoolIoCallbacks
+
+WEAK_SYMBOL(VOID, WaitForThreadpoolTimerCallbacks, PTP_TIMER, BOOL);
+#define WaitForThreadpoolTimerCallbacks my_WaitForThreadpoolTimerCallbacks
+
+WEAK_SYMBOL(VOID, WaitForThreadpoolWaitCallbacks, PTP_WAIT, BOOL);
+#define WaitForThreadpoolWaitCallbacks my_WaitForThreadpoolWaitCallbacks
+
+WEAK_SYMBOL(BOOL, SetFileCompletionNotificationModes, HANDLE, UCHAR);
+#define SetFileCompletionNotificationModes my_SetFileCompletionNotificationModes
+
+WEAK_SYMBOL(BOOL, TrySubmitThreadpoolCallback, PTP_SIMPLE_CALLBACK pfns,
+ PVOID pv,PTP_CALLBACK_ENVIRON pcbe);
+#define TrySubmitThreadpoolCallback my_TrySubmitThreadpoolCallback
+
+WEAK_SYMBOL(PTP_WORK, CreateThreadpoolWork, PTP_WORK_CALLBACK pfnwk, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+#define CreateThreadpoolWork my_CreateThreadpoolWork
+
+WEAK_SYMBOL(VOID, SubmitThreadpoolWork,PTP_WORK pwk);
+#define SubmitThreadpoolWork my_SubmitThreadpoolWork
+
+WEAK_SYMBOL(VOID, CloseThreadpoolWork, PTP_WORK pwk);
+#define CloseThreadpoolWork my_CloseThreadpoolWork
+
+WEAK_SYMBOL(BOOL, CallbackMayRunLong, PTP_CALLBACK_INSTANCE pci);
+#define CallbackMayRunLong my_CallbackMayRunLong
+
+#if _MSC_VER >= 1600
+/* Stack size manipulation available only on Win7+ /declarations in VS10 */
+WEAK_SYMBOL(BOOL, SetThreadpoolStackInformation, PTP_POOL,
+ PTP_POOL_STACK_INFORMATION);
+#define SetThreadpoolStackInformation my_SetThreadpoolStackInformation
+#else /* _MSC_VER < 1600 */
+#define SetThreadpoolCallbackPriority(env,prio)
+typedef enum _TP_CALLBACK_PRIORITY {
+ TP_CALLBACK_PRIORITY_HIGH,
+ TP_CALLBACK_PRIORITY_NORMAL,
+ TP_CALLBACK_PRIORITY_LOW,
+ TP_CALLBACK_PRIORITY_INVALID
+} TP_CALLBACK_PRIORITY;
+#endif
+
+
+/* Log a warning */
+static void tp_log_warning(const char *msg, const char *fct)
+{
+ sql_print_warning("Threadpool: %s. %s failed (last error %d)",msg, fct,
+ GetLastError());
+}
+
+
+PTP_POOL pool;
+DWORD fls;
+
+static bool skip_completion_port_on_success = false;
+
+/*
+ Threadpool callbacks.
+
+ io_completion_callback - handle client request
+ timer_callback - handle wait timeout (kill connection)
+ shm_read_callback, shm_close_callback - shared memory stuff
+ login_callback - user login (submitted as threadpool work)
+
+*/
+
+static void CALLBACK timer_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID context, PTP_TIMER timer);
+
+static void CALLBACK io_completion_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID context, PVOID overlapped, ULONG io_result, ULONG_PTR nbytes, PTP_IO io);
+
+static void CALLBACK shm_read_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID Context, PTP_WAIT wait,TP_WAIT_RESULT wait_result);
+
+static void CALLBACK shm_close_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID Context, PTP_WAIT wait,TP_WAIT_RESULT wait_result);
+
+static void check_thread_init();
+
+/* Get current time as Windows time */
+static ulonglong now()
+{
+ ulonglong current_time;
+ GetSystemTimeAsFileTime((PFILETIME)&current_time);
+ return current_time;
+}
+
+/*
+ Connection structure, encapsulates THD + structures for asynchronous
+ IO and pool.
+*/
+
+struct connection_t
+{
+ THD *thd;
+ HANDLE handle;
+ OVERLAPPED overlapped;
+ /* absolute time for wait timeout (as Windows time) */
+ volatile ulonglong timeout;
+ TP_CALLBACK_ENVIRON callback_environ;
+ PTP_IO io;
+ PTP_TIMER timer;
+ PTP_WAIT shm_read;
+ /* Callback instance, used to inform treadpool about long callbacks */
+ PTP_CALLBACK_INSTANCE callback_instance;
+ bool logged_in;
+};
+
+
+void init_connection(connection_t *connection)
+{
+ connection->logged_in = false;
+ connection->handle= 0;
+ connection->io= 0;
+ connection->shm_read= 0;
+ connection->timer= 0;
+ connection->logged_in = false;
+ connection->timeout= ULONGLONG_MAX;
+ connection->callback_instance= 0;
+ memset(&connection->overlapped, 0, sizeof(OVERLAPPED));
+ InitializeThreadpoolEnvironment(&connection->callback_environ);
+ SetThreadpoolCallbackPool(&connection->callback_environ, pool);
+ connection->thd = 0;
+}
+
+
+int init_io(connection_t *connection, THD *thd)
+{
+ connection->thd= thd;
+ Vio *vio = thd->net.vio;
+ switch(vio->type)
+ {
+ case VIO_TYPE_SSL:
+ case VIO_TYPE_TCPIP:
+ connection->handle= (HANDLE)mysql_socket_getfd(connection->thd->net.vio->mysql_socket);
+ break;
+ case VIO_TYPE_NAMEDPIPE:
+ connection->handle= (HANDLE)vio->hPipe;
+ break;
+ case VIO_TYPE_SHARED_MEMORY:
+ connection->shm_read= CreateThreadpoolWait(shm_read_callback, connection,
+ &connection->callback_environ);
+ if (!connection->shm_read)
+ {
+ tp_log_warning("Allocation failed", "CreateThreadpoolWait");
+ return -1;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ if (connection->handle)
+ {
+ /* Performance tweaks (s. MSDN documentation)*/
+ UCHAR flags= FILE_SKIP_SET_EVENT_ON_HANDLE;
+ if (skip_completion_port_on_success)
+ {
+ flags |= FILE_SKIP_COMPLETION_PORT_ON_SUCCESS;
+ }
+ (void)SetFileCompletionNotificationModes(connection->handle, flags);
+
+ /* Assign io completion callback */
+ connection->io= CreateThreadpoolIo(connection->handle,
+ io_completion_callback, connection, &connection->callback_environ);
+ if(!connection->io)
+ {
+ tp_log_warning("Allocation failed", "CreateThreadpoolWait");
+ return -1;
+ }
+ }
+ connection->timer= CreateThreadpoolTimer(timer_callback, connection,
+ &connection->callback_environ);
+ if (!connection->timer)
+ {
+ tp_log_warning("Allocation failed", "CreateThreadpoolWait");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ Start asynchronous read
+*/
+int start_io(connection_t *connection, PTP_CALLBACK_INSTANCE instance)
+{
+ /* Start async read */
+ DWORD num_bytes = 0;
+ static char c;
+ WSABUF buf;
+ buf.buf= &c;
+ buf.len= 0;
+ DWORD flags=0;
+ DWORD last_error= 0;
+
+ int retval;
+ Vio *vio= connection->thd->net.vio;
+
+ if (vio->type == VIO_TYPE_SHARED_MEMORY)
+ {
+ SetThreadpoolWait(connection->shm_read, vio->event_server_wrote, NULL);
+ return 0;
+ }
+ if (vio->type == VIO_CLOSED)
+ {
+ return -1;
+ }
+
+ DBUG_ASSERT(vio->type == VIO_TYPE_TCPIP ||
+ vio->type == VIO_TYPE_SSL ||
+ vio->type == VIO_TYPE_NAMEDPIPE);
+
+ OVERLAPPED *overlapped= &connection->overlapped;
+ PTP_IO io= connection->io;
+ StartThreadpoolIo(io);
+
+ if (vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SSL)
+ {
+ /* Start async io (sockets). */
+ if (WSARecv(mysql_socket_getfd(vio->mysql_socket) , &buf, 1, &num_bytes, &flags,
+ overlapped, NULL) == 0)
+ {
+ retval= last_error= 0;
+ }
+ else
+ {
+ retval= -1;
+ last_error= WSAGetLastError();
+ }
+ }
+ else
+ {
+ /* Start async io (named pipe) */
+ if (ReadFile(vio->hPipe, &c, 0, &num_bytes ,overlapped))
+ {
+ retval= last_error= 0;
+ }
+ else
+ {
+ retval= -1;
+ last_error= GetLastError();
+ }
+ }
+
+ if (retval == 0 || last_error == ERROR_MORE_DATA)
+ {
+ /*
+ IO successfully finished (synchronously).
+ If skip_completion_port_on_success is set, we need to handle it right
+ here, because completion callback would not be executed by the pool.
+ */
+ if(skip_completion_port_on_success)
+ {
+ CancelThreadpoolIo(io);
+ io_completion_callback(instance, connection, overlapped, last_error,
+ num_bytes, io);
+ }
+ return 0;
+ }
+
+ if(last_error == ERROR_IO_PENDING)
+ {
+ return 0;
+ }
+
+ /* Some error occured */
+ CancelThreadpoolIo(io);
+ return -1;
+}
+
+
+int login(connection_t *connection, PTP_CALLBACK_INSTANCE instance)
+{
+ if (threadpool_add_connection(connection->thd) == 0
+ && init_io(connection, connection->thd) == 0
+ && start_io(connection, instance) == 0)
+ {
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ Recalculate wait timeout, maybe reset timer.
+*/
+void set_wait_timeout(connection_t *connection, ulonglong old_timeout)
+{
+ ulonglong new_timeout = now() +
+ 10000000LL*connection->thd->variables.net_wait_timeout;
+
+ if (new_timeout < old_timeout)
+ {
+ SetThreadpoolTimer(connection->timer, (PFILETIME) &new_timeout, 0, 1000);
+ }
+ connection->timeout = new_timeout;
+}
+
+
+/* Connection destructor */
+void destroy_connection(connection_t *connection, PTP_CALLBACK_INSTANCE instance)
+{
+ if (instance)
+ DisassociateCurrentThreadFromCallback(instance);
+ if (connection->io)
+ {
+ WaitForThreadpoolIoCallbacks(connection->io, TRUE);
+ CloseThreadpoolIo(connection->io);
+ }
+
+ if(connection->shm_read)
+ {
+ WaitForThreadpoolWaitCallbacks(connection->shm_read, TRUE);
+ CloseThreadpoolWait(connection->shm_read);
+ }
+
+ if(connection->timer)
+ {
+ SetThreadpoolTimer(connection->timer, 0, 0, 0);
+ WaitForThreadpoolTimerCallbacks(connection->timer, TRUE);
+ CloseThreadpoolTimer(connection->timer);
+ }
+
+ if (connection->thd)
+ {
+ threadpool_remove_connection(connection->thd);
+ }
+
+ DestroyThreadpoolEnvironment(&connection->callback_environ);
+}
+
+
+
+/*
+ This function should be called first whenever a callback is invoked in the
+ threadpool, does my_thread_init() if not yet done
+*/
+extern ulong thread_created;
+static void check_thread_init()
+{
+ if (FlsGetValue(fls) == NULL)
+ {
+ FlsSetValue(fls, (void *)1);
+ thread_created++;
+ InterlockedIncrement((volatile long *)&tp_stats.num_worker_threads);
+ }
+}
+
+
+/*
+ Decrement number of threads when a thread exits .
+ On Windows, FlsAlloc() provides the thread destruction callbacks.
+*/
+static VOID WINAPI thread_destructor(void *data)
+{
+ if(data)
+ {
+ InterlockedDecrement((volatile long *)&tp_stats.num_worker_threads);
+ }
+}
+
+
+/* Scheduler callback : init */
+bool tp_init(void)
+{
+ fls= FlsAlloc(thread_destructor);
+ pool= CreateThreadpool(NULL);
+ if(!pool)
+ {
+ sql_print_error("Can't create threadpool. "
+ "CreateThreadpool() failed with %d. Likely cause is memory pressure",
+ GetLastError());
+ exit(1);
+ }
+
+ if (threadpool_max_threads)
+ {
+ SetThreadpoolThreadMaximum(pool,threadpool_max_threads);
+ }
+
+ if (threadpool_min_threads)
+ {
+ if (!SetThreadpoolThreadMinimum(pool, threadpool_min_threads))
+ {
+ tp_log_warning( "Can't set threadpool minimum threads",
+ "SetThreadpoolThreadMinimum");
+ }
+ }
+
+ /*
+ Control stack size (OS must be Win7 or later, plus corresponding SDK)
+ */
+#if _MSC_VER >=1600
+ if (SetThreadpoolStackInformation)
+ {
+ TP_POOL_STACK_INFORMATION stackinfo;
+ stackinfo.StackCommit = 0;
+ stackinfo.StackReserve = (SIZE_T)my_thread_stack_size;
+ if (!SetThreadpoolStackInformation(pool, &stackinfo))
+ {
+ tp_log_warning("Can't set threadpool stack size",
+ "SetThreadpoolStackInformation");
+ }
+ }
+#endif
+
+ return 0;
+}
+
+
+/**
+ Scheduler callback : Destroy the scheduler.
+*/
+void tp_end(void)
+{
+ if(pool)
+ {
+ SetThreadpoolThreadMaximum(pool, 0);
+ CloseThreadpool(pool);
+ }
+}
+
+
+/*
+ Handle read completion/notification.
+*/
+static VOID CALLBACK io_completion_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID context, PVOID overlapped, ULONG io_result, ULONG_PTR nbytes, PTP_IO io)
+{
+ if(instance)
+ {
+ check_thread_init();
+ }
+
+ connection_t *connection = (connection_t*)context;
+
+ if (io_result != ERROR_SUCCESS)
+ goto error;
+
+ THD *thd= connection->thd;
+ ulonglong old_timeout = connection->timeout;
+ connection->timeout = ULONGLONG_MAX;
+ connection->callback_instance= instance;
+ if (threadpool_process_request(connection->thd))
+ goto error;
+
+ set_wait_timeout(connection, old_timeout);
+ if(start_io(connection, instance))
+ goto error;
+
+ return;
+
+error:
+ /* Some error has occured. */
+
+ destroy_connection(connection, instance);
+ free(connection);
+}
+
+
+/* Simple callback for login */
+static void CALLBACK login_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID context, PTP_WORK work)
+{
+ if(instance)
+ {
+ check_thread_init();
+ }
+
+ connection_t *connection =(connection_t *)context;
+ if (login(connection, instance) != 0)
+ {
+ destroy_connection(connection, instance);
+ free(connection);
+ }
+}
+
+/*
+ Timer callback.
+ Invoked when connection times out (wait_timeout)
+*/
+static VOID CALLBACK timer_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID parameter, PTP_TIMER timer)
+{
+ check_thread_init();
+
+ connection_t *con= (connection_t*)parameter;
+ ulonglong timeout= con->timeout;
+
+ if (timeout <= now())
+ {
+ con->thd->killed = KILL_CONNECTION;
+ if(con->thd->net.vio)
+ vio_shutdown(con->thd->net.vio, SD_BOTH);
+ }
+ else if(timeout != ULONGLONG_MAX)
+ {
+ /*
+ Reset timer.
+ There is a tiny possibility of a race condition, since the value of timeout
+ could have changed to smaller value in the thread doing io callback.
+
+ Given the relative unimportance of the wait timeout, we accept race
+ condition.
+ */
+ SetThreadpoolTimer(timer, (PFILETIME)&timeout, 0, 1000);
+ }
+}
+
+
+/*
+ Shared memory read callback.
+ Invoked when read event is set on connection.
+*/
+static void CALLBACK shm_read_callback(PTP_CALLBACK_INSTANCE instance,
+ PVOID context, PTP_WAIT wait,TP_WAIT_RESULT wait_result)
+{
+ connection_t *con= (connection_t *)context;
+ /* Disarm wait. */
+ SetThreadpoolWait(wait, NULL, NULL);
+
+ /*
+ This is an autoreset event, and one wakeup is eaten already by threadpool,
+ and the current state is "not set". Thus we need to reset the event again,
+ or vio_read will hang.
+ */
+ HANDLE h = con->thd->net.vio->event_server_wrote;
+ SetEvent(h);
+ io_completion_callback(instance, context, NULL, 0, 0 , 0);
+}
+
+
+/*
+ Notify the thread pool about a new connection.
+ NOTE: LOCK_thread_count is locked on entry. This function must unlock it.
+*/
+void tp_add_connection(THD *thd)
+{
+ threads.append(thd);
+ mysql_mutex_unlock(&LOCK_thread_count);
+
+ connection_t *con = (connection_t *)malloc(sizeof(connection_t));
+ if(!con)
+ {
+ tp_log_warning("Allocation failed", "tp_add_connection");
+ threadpool_remove_connection(thd);
+ return;
+ }
+
+ init_connection(con);
+ con->thd= thd;
+ thd->event_scheduler.data= con;
+
+ /* Try to login asynchronously, using threads in the pool */
+ PTP_WORK wrk = CreateThreadpoolWork(login_callback,con, &con->callback_environ);
+ if (wrk)
+ {
+ SubmitThreadpoolWork(wrk);
+ CloseThreadpoolWork(wrk);
+ }
+ else
+ {
+ /* Likely memory pressure */
+ login_callback(NULL, con, NULL); /* deletes connection if something goes wrong */
+ }
+}
+
+
+/**
+ Sets the number of idle threads the thread pool maintains in anticipation of new
+ requests.
+*/
+void tp_set_min_threads(uint val)
+{
+ if (pool)
+ SetThreadpoolThreadMinimum(pool, val);
+}
+
+void tp_set_max_threads(uint val)
+{
+ if (pool)
+ SetThreadpoolThreadMaximum(pool, val);
+}
+
+void tp_wait_begin(THD *thd, int type)
+{
+ DBUG_ASSERT(thd);
+
+ /*
+ Signal to the threadpool whenever callback can run long. Currently, binlog
+ waits are a good candidate, its waits are really long
+ */
+ if (type == THD_WAIT_BINLOG)
+ {
+ connection_t *connection= (connection_t *)thd->event_scheduler.data;
+ if(connection && connection->callback_instance)
+ {
+ CallbackMayRunLong(connection->callback_instance);
+ /*
+ Reset instance, to avoid calling CallbackMayRunLong twice within
+ the same callback (it is an error according to docs).
+ */
+ connection->callback_instance= 0;
+ }
+ }
+}
+
+void tp_wait_end(THD *thd)
+{
+ /* Do we need to do anything ? */
+}
+
+
+/**
+ Number of idle threads in pool.
+ This info is not available in Windows implementation,
+ thus function always returns 0.
+*/
+int tp_get_idle_thread_count()
+{
+ return 0;
+}
+
diff --git a/sql/transaction.cc b/sql/transaction.cc
new file mode 100644
index 00000000000..a70c075e142
--- /dev/null
+++ b/sql/transaction.cc
@@ -0,0 +1,915 @@
+/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "transaction.h"
+#include "rpl_handler.h"
+#include "debug_sync.h" // DEBUG_SYNC
+#include "sql_acl.h"
+
+/* Conditions under which the transaction state must not change. */
+static bool trans_check(THD *thd)
+{
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ DBUG_ENTER("trans_check");
+
+ /*
+ Always commit statement transaction before manipulating with
+ the normal one.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+
+ if (unlikely(thd->in_sub_stmt))
+ my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
+ if (xa_state != XA_NOTR)
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ else
+ DBUG_RETURN(FALSE);
+
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Mark a XA transaction as rollback-only if the RM unilaterally
+ rolled back the transaction branch.
+
+ @note If a rollback was requested by the RM, this function sets
+ the appropriate rollback error code and transits the state
+ to XA_ROLLBACK_ONLY.
+
+ @return TRUE if transaction was rolled back or if the transaction
+ state is XA_ROLLBACK_ONLY. FALSE otherwise.
+*/
+static bool xa_trans_rolled_back(XID_STATE *xid_state)
+{
+ if (xid_state->rm_error)
+ {
+ switch (xid_state->rm_error) {
+ case ER_LOCK_WAIT_TIMEOUT:
+ my_error(ER_XA_RBTIMEOUT, MYF(0));
+ break;
+ case ER_LOCK_DEADLOCK:
+ my_error(ER_XA_RBDEADLOCK, MYF(0));
+ break;
+ default:
+ my_error(ER_XA_RBROLLBACK, MYF(0));
+ }
+ xid_state->xa_state= XA_ROLLBACK_ONLY;
+ }
+
+ return (xid_state->xa_state == XA_ROLLBACK_ONLY);
+}
+
+
+/**
+ Rollback the active XA transaction.
+
+ @note Resets rm_error before calling ha_rollback(), so
+ the thd->transaction.xid structure gets reset
+ by ha_rollback() / THD::transaction::cleanup().
+
+ @return TRUE if the rollback failed, FALSE otherwise.
+*/
+
+static bool xa_trans_force_rollback(THD *thd)
+{
+ /*
+ We must reset rm_error before calling ha_rollback(),
+ so thd->transaction.xid structure gets reset
+ by ha_rollback()/THD::transaction::cleanup().
+ */
+ thd->transaction.xid_state.rm_error= 0;
+ if (ha_rollback_trans(thd, true))
+ {
+ my_error(ER_XAER_RMERR, MYF(0));
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ Begin a new transaction.
+
+ @note Beginning a transaction implicitly commits any current
+ transaction and releases existing locks.
+
+ @param thd Current thread
+ @param flags Transaction flags
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_begin(THD *thd, uint flags)
+{
+ int res= FALSE;
+ DBUG_ENTER("trans_begin");
+
+ if (trans_check(thd))
+ DBUG_RETURN(TRUE);
+
+ thd->locked_tables_list.unlock_locked_tables(thd);
+
+ DBUG_ASSERT(!thd->locked_tables_mode);
+
+ if (thd->in_multi_stmt_transaction_mode() ||
+ (thd->variables.option_bits & OPTION_TABLE_LOCK))
+ {
+ thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
+ thd->server_status&=
+ ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
+ DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
+ res= MY_TEST(ha_commit_trans(thd, TRUE));
+ }
+
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
+
+ /*
+ The following set should not be needed as the flag should always be 0
+ when we come here. We should at some point change this to an assert.
+ */
+ thd->transaction.all.modified_non_trans_table= FALSE;
+ thd->has_waiter= false;
+ thd->waiting_on_group_commit= false;
+
+ if (res)
+ DBUG_RETURN(TRUE);
+
+ /*
+ Release transactional metadata locks only after the
+ transaction has been committed.
+ */
+ thd->mdl_context.release_transactional_locks();
+
+ // The RO/RW options are mutually exclusive.
+ DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) &&
+ (flags & MYSQL_START_TRANS_OPT_READ_WRITE)));
+ if (flags & MYSQL_START_TRANS_OPT_READ_ONLY)
+ thd->tx_read_only= true;
+ else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE)
+ {
+ /*
+ Explicitly starting a RW transaction when the server is in
+ read-only mode, is not allowed unless the user has SUPER priv.
+ Implicitly starting a RW transaction is allowed for backward
+ compatibility.
+ */
+ const bool user_is_super=
+ MY_TEST(thd->security_ctx->master_access & SUPER_ACL);
+ if (opt_readonly && !user_is_super)
+ {
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
+ DBUG_RETURN(true);
+ }
+ thd->tx_read_only= false;
+ }
+
+ thd->variables.option_bits|= OPTION_BEGIN;
+ thd->server_status|= SERVER_STATUS_IN_TRANS;
+ if (thd->tx_read_only)
+ thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY;
+ DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS"));
+
+ /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */
+ if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
+ res= ha_start_consistent_snapshot(thd);
+
+ DBUG_RETURN(MY_TEST(res));
+}
+
+
+/**
+ Commit the current transaction, making its changes permanent.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_commit(THD *thd)
+{
+ int res;
+ DBUG_ENTER("trans_commit");
+
+ if (trans_check(thd))
+ DBUG_RETURN(TRUE);
+
+ thd->server_status&=
+ ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
+ DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
+ res= ha_commit_trans(thd, TRUE);
+ /*
+ if res is non-zero, then ha_commit_trans has rolled back the
+ transaction, so the hooks for rollback will be called.
+ */
+ if (res)
+ (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE));
+ else
+ (void) RUN_HOOK(transaction, after_commit, (thd, FALSE));
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
+ thd->transaction.all.modified_non_trans_table= FALSE;
+ thd->lex->start_transaction_opt= 0;
+
+ DBUG_RETURN(MY_TEST(res));
+}
+
+
+/**
+ Implicitly commit the current transaction.
+
+ @note A implicit commit does not releases existing table locks.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_commit_implicit(THD *thd)
+{
+ bool res= FALSE;
+ DBUG_ENTER("trans_commit_implicit");
+
+ if (trans_check(thd))
+ DBUG_RETURN(TRUE);
+
+ if (thd->variables.option_bits & OPTION_GTID_BEGIN)
+ DBUG_PRINT("error", ("OPTION_GTID_BEGIN is set. "
+ "Master and slave will have different GTID values"));
+
+ if (thd->in_multi_stmt_transaction_mode() ||
+ (thd->variables.option_bits & OPTION_TABLE_LOCK))
+ {
+ /* Safety if one did "drop table" on locked tables */
+ if (!thd->locked_tables_mode)
+ thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
+ thd->server_status&=
+ ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
+ DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
+ res= MY_TEST(ha_commit_trans(thd, TRUE));
+ }
+
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
+ thd->transaction.all.modified_non_trans_table= FALSE;
+
+ /*
+ Upon implicit commit, reset the current transaction
+ isolation level and access mode. We do not care about
+ @@session.completion_type since it's documented
+ to not have any effect on implicit commit.
+ */
+ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+ thd->tx_read_only= thd->variables.tx_read_only;
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Rollback the current transaction, canceling its changes.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_rollback(THD *thd)
+{
+ int res;
+ DBUG_ENTER("trans_rollback");
+
+ if (trans_check(thd))
+ DBUG_RETURN(TRUE);
+
+ thd->server_status&=
+ ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
+ DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
+ res= ha_rollback_trans(thd, TRUE);
+ (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE));
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
+ /* Reset the binlog transaction marker */
+ thd->variables.option_bits&= ~OPTION_GTID_BEGIN;
+ thd->transaction.all.modified_non_trans_table= FALSE;
+ thd->lex->start_transaction_opt= 0;
+
+ DBUG_RETURN(MY_TEST(res));
+}
+
+
+/**
+ Implicitly rollback the current transaction, typically
+ after deadlock was discovered.
+
+ @param thd Current thread
+
+ @retval False Success
+ @retval True Failure
+
+ @note ha_rollback_low() which is indirectly called by this
+ function will mark XA transaction for rollback by
+ setting appropriate RM error status if there was
+ transaction rollback request.
+*/
+
+bool trans_rollback_implicit(THD *thd)
+{
+ int res;
+ DBUG_ENTER("trans_rollback_implict");
+
+ /*
+ Always commit/rollback statement transaction before manipulating
+ with the normal one.
+ Don't perform rollback in the middle of sub-statement, wait till
+ its end.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty() && !thd->in_sub_stmt);
+
+ thd->server_status&= ~SERVER_STATUS_IN_TRANS;
+ DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
+ res= ha_rollback_trans(thd, true);
+ /*
+ We don't reset OPTION_BEGIN flag below to simulate implicit start
+ of new transacton in @@autocommit=1 mode. This is necessary to
+ preserve backward compatibility.
+ */
+ thd->variables.option_bits&= ~(OPTION_KEEP_LOG);
+ thd->transaction.all.modified_non_trans_table= false;
+
+ /* Rollback should clear transaction_rollback_request flag. */
+ DBUG_ASSERT(! thd->transaction_rollback_request);
+
+ DBUG_RETURN(MY_TEST(res));
+}
+
+
+/**
+ Commit the single statement transaction.
+
+ @note Note that if the autocommit is on, then the following call
+ inside InnoDB will commit or rollback the whole transaction
+ (= the statement). The autocommit mechanism built into InnoDB
+ is based on counting locks, but if the user has used LOCK
+ TABLES then that mechanism does not know to do the commit.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_commit_stmt(THD *thd)
+{
+ DBUG_ENTER("trans_commit_stmt");
+ int res= FALSE;
+ /*
+ We currently don't invoke commit/rollback at end of
+ a sub-statement. In future, we perhaps should take
+ a savepoint for each nested statement, and release the
+ savepoint when statement has succeeded.
+ */
+ DBUG_ASSERT(! thd->in_sub_stmt);
+
+ if (thd->transaction.stmt.ha_list)
+ {
+ res= ha_commit_trans(thd, FALSE);
+ if (! thd->in_active_multi_stmt_transaction())
+ {
+ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+ thd->tx_read_only= thd->variables.tx_read_only;
+ }
+ }
+
+ /*
+ if res is non-zero, then ha_commit_trans has rolled back the
+ transaction, so the hooks for rollback will be called.
+ */
+ if (res)
+ (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE));
+ else
+ (void) RUN_HOOK(transaction, after_commit, (thd, FALSE));
+
+ thd->transaction.stmt.reset();
+
+ DBUG_RETURN(MY_TEST(res));
+}
+
+
+/**
+ Rollback the single statement transaction.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+bool trans_rollback_stmt(THD *thd)
+{
+ DBUG_ENTER("trans_rollback_stmt");
+
+ /*
+ We currently don't invoke commit/rollback at end of
+ a sub-statement. In future, we perhaps should take
+ a savepoint for each nested statement, and release the
+ savepoint when statement has succeeded.
+ */
+ DBUG_ASSERT(! thd->in_sub_stmt);
+
+ if (thd->transaction.stmt.ha_list)
+ {
+ ha_rollback_trans(thd, FALSE);
+ if (! thd->in_active_multi_stmt_transaction())
+ {
+ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+ thd->tx_read_only= thd->variables.tx_read_only;
+ }
+ }
+
+ (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE));
+
+ thd->transaction.stmt.reset();
+
+ DBUG_RETURN(FALSE);
+}
+
+/* Find a named savepoint in the current transaction. */
+static SAVEPOINT **
+find_savepoint(THD *thd, LEX_STRING name)
+{
+ SAVEPOINT **sv= &thd->transaction.savepoints;
+
+ while (*sv)
+ {
+ if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length,
+ (uchar *) (*sv)->name, (*sv)->length) == 0)
+ break;
+ sv= &(*sv)->prev;
+ }
+
+ return sv;
+}
+
+
+/**
+ Set a named transaction savepoint.
+
+ @param thd Current thread
+ @param name Savepoint name
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_savepoint(THD *thd, LEX_STRING name)
+{
+ SAVEPOINT **sv, *newsv;
+ DBUG_ENTER("trans_savepoint");
+
+ if (!(thd->in_multi_stmt_transaction_mode() || thd->in_sub_stmt) ||
+ !opt_using_transactions)
+ DBUG_RETURN(FALSE);
+
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ if (xa_state != XA_NOTR && xa_state != XA_ACTIVE)
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ DBUG_RETURN(TRUE);
+ }
+
+ sv= find_savepoint(thd, name);
+
+ if (*sv) /* old savepoint of the same name exists */
+ {
+ newsv= *sv;
+ ha_release_savepoint(thd, *sv);
+ *sv= (*sv)->prev;
+ }
+ else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction.mem_root,
+ savepoint_alloc_size)) == NULL)
+ {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ newsv->name= strmake_root(&thd->transaction.mem_root, name.str, name.length);
+ newsv->length= name.length;
+
+ /*
+ if we'll get an error here, don't add new savepoint to the list.
+ we'll lose a little bit of memory in transaction mem_root, but it'll
+ be free'd when transaction ends anyway
+ */
+ if (ha_savepoint(thd, newsv))
+ DBUG_RETURN(TRUE);
+
+ newsv->prev= thd->transaction.savepoints;
+ thd->transaction.savepoints= newsv;
+
+ /*
+ Remember locks acquired before the savepoint was set.
+ They are used as a marker to only release locks acquired after
+ the setting of this savepoint.
+ Note: this works just fine if we're under LOCK TABLES,
+ since mdl_savepoint() is guaranteed to be beyond
+ the last locked table. This allows to release some
+ locks acquired during LOCK TABLES.
+ */
+ newsv->mdl_savepoint= thd->mdl_context.mdl_savepoint();
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Rollback a transaction to the named savepoint.
+
+ @note Modifications that the current transaction made to
+ rows after the savepoint was set are undone in the
+ rollback.
+
+ @note Savepoints that were set at a later time than the
+ named savepoint are deleted.
+
+ @param thd Current thread
+ @param name Savepoint name
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name)
+{
+ int res= FALSE;
+ SAVEPOINT *sv= *find_savepoint(thd, name);
+ DBUG_ENTER("trans_rollback_to_savepoint");
+
+ if (sv == NULL)
+ {
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ if (xa_state != XA_NOTR)
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ DBUG_RETURN(TRUE);
+ }
+
+ /**
+ Checking whether it is safe to release metadata locks acquired after
+ savepoint, if rollback to savepoint is successful.
+
+ Whether it is safe to release MDL after rollback to savepoint depends
+ on storage engines participating in transaction:
+
+ - InnoDB doesn't release any row-locks on rollback to savepoint so it
+ is probably a bad idea to release MDL as well.
+ - Binary log implementation in some cases (e.g when non-transactional
+ tables involved) may choose not to remove events added after savepoint
+ from transactional cache, but instead will write them to binary
+ log accompanied with ROLLBACK TO SAVEPOINT statement. Since the real
+ write happens at the end of transaction releasing MDL on tables
+ mentioned in these events (i.e. acquired after savepoint and before
+ rollback ot it) can break replication, as concurrent DROP TABLES
+ statements will be able to drop these tables before events will get
+ into binary log,
+
+ For backward-compatibility reasons we always release MDL if binary
+ logging is off.
+ */
+ bool mdl_can_safely_rollback_to_savepoint=
+ (!(mysql_bin_log.is_open() && thd->variables.sql_log_bin) ||
+ ha_rollback_to_savepoint_can_release_mdl(thd));
+
+ if (ha_rollback_to_savepoint(thd, sv))
+ res= TRUE;
+ else if (((thd->variables.option_bits & OPTION_KEEP_LOG) ||
+ thd->transaction.all.modified_non_trans_table) &&
+ !thd->slave_thread)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_WARNING_NOT_COMPLETE_ROLLBACK,
+ ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
+
+ thd->transaction.savepoints= sv;
+
+ if (!res && mdl_can_safely_rollback_to_savepoint)
+ thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint);
+
+ DBUG_RETURN(MY_TEST(res));
+}
+
+
+/**
+ Remove the named savepoint from the set of savepoints of
+ the current transaction.
+
+ @note No commit or rollback occurs. It is an error if the
+ savepoint does not exist.
+
+ @param thd Current thread
+ @param name Savepoint name
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_release_savepoint(THD *thd, LEX_STRING name)
+{
+ int res= FALSE;
+ SAVEPOINT *sv= *find_savepoint(thd, name);
+ DBUG_ENTER("trans_release_savepoint");
+
+ if (sv == NULL)
+ {
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (ha_release_savepoint(thd, sv))
+ res= TRUE;
+
+ thd->transaction.savepoints= sv->prev;
+
+ DBUG_RETURN(MY_TEST(res));
+}
+
+
+/**
+ Starts an XA transaction with the given xid value.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_xa_start(THD *thd)
+{
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ DBUG_ENTER("trans_xa_start");
+
+ if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_RESUME)
+ {
+ bool not_equal= !thd->transaction.xid_state.xid.eq(thd->lex->xid);
+ if (not_equal)
+ my_error(ER_XAER_NOTA, MYF(0));
+ else
+ thd->transaction.xid_state.xa_state= XA_ACTIVE;
+ DBUG_RETURN(not_equal);
+ }
+
+ /* TODO: JOIN is not supported yet. */
+ if (thd->lex->xa_opt != XA_NONE)
+ my_error(ER_XAER_INVAL, MYF(0));
+ else if (xa_state != XA_NOTR)
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ else if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction())
+ my_error(ER_XAER_OUTSIDE, MYF(0));
+ else if (!trans_begin(thd))
+ {
+ DBUG_ASSERT(thd->transaction.xid_state.xid.is_null());
+ thd->transaction.xid_state.xa_state= XA_ACTIVE;
+ thd->transaction.xid_state.rm_error= 0;
+ thd->transaction.xid_state.xid.set(thd->lex->xid);
+ if (xid_cache_insert(&thd->transaction.xid_state))
+ {
+ thd->transaction.xid_state.xa_state= XA_NOTR;
+ thd->transaction.xid_state.xid.null();
+ trans_rollback(thd);
+ DBUG_RETURN(true);
+ }
+ DBUG_RETURN(FALSE);
+ }
+
+ DBUG_RETURN(TRUE);
+}
+
+
+/**
+ Put a XA transaction in the IDLE state.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_xa_end(THD *thd)
+{
+ DBUG_ENTER("trans_xa_end");
+
+ /* TODO: SUSPEND and FOR MIGRATE are not supported yet. */
+ if (thd->lex->xa_opt != XA_NONE)
+ my_error(ER_XAER_INVAL, MYF(0));
+ else if (thd->transaction.xid_state.xa_state != XA_ACTIVE)
+ my_error(ER_XAER_RMFAIL, MYF(0),
+ xa_state_names[thd->transaction.xid_state.xa_state]);
+ else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
+ my_error(ER_XAER_NOTA, MYF(0));
+ else if (!xa_trans_rolled_back(&thd->transaction.xid_state))
+ thd->transaction.xid_state.xa_state= XA_IDLE;
+
+ DBUG_RETURN(thd->is_error() ||
+ thd->transaction.xid_state.xa_state != XA_IDLE);
+}
+
+
+/**
+ Put a XA transaction in the PREPARED state.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_xa_prepare(THD *thd)
+{
+ DBUG_ENTER("trans_xa_prepare");
+
+ if (thd->transaction.xid_state.xa_state != XA_IDLE)
+ my_error(ER_XAER_RMFAIL, MYF(0),
+ xa_state_names[thd->transaction.xid_state.xa_state]);
+ else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
+ my_error(ER_XAER_NOTA, MYF(0));
+ else if (ha_prepare(thd))
+ {
+ xid_cache_delete(&thd->transaction.xid_state);
+ thd->transaction.xid_state.xa_state= XA_NOTR;
+ my_error(ER_XA_RBROLLBACK, MYF(0));
+ }
+ else
+ thd->transaction.xid_state.xa_state= XA_PREPARED;
+
+ DBUG_RETURN(thd->is_error() ||
+ thd->transaction.xid_state.xa_state != XA_PREPARED);
+}
+
+
+/**
+ Commit and terminate the a XA transaction.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_xa_commit(THD *thd)
+{
+ bool res= TRUE;
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ DBUG_ENTER("trans_xa_commit");
+
+ if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
+ {
+ /*
+ xid_state.in_thd is always true beside of xa recovery procedure.
+ Note, that there is no race condition here between xid_cache_search
+ and xid_cache_delete, since we always delete our own XID
+ (thd->lex->xid == thd->transaction.xid_state.xid).
+ The only case when thd->lex->xid != thd->transaction.xid_state.xid
+ and xid_state->in_thd == 0 is in the function
+ xa_cache_insert(XID, xa_states), which is called before starting
+ client connections, and thus is always single-threaded.
+ */
+ XID_STATE *xs= xid_cache_search(thd->lex->xid);
+ res= !xs || xs->in_thd;
+ if (res)
+ my_error(ER_XAER_NOTA, MYF(0));
+ else
+ {
+ res= xa_trans_rolled_back(xs);
+ ha_commit_or_rollback_by_xid(thd->lex->xid, !res);
+ xid_cache_delete(xs);
+ }
+ DBUG_RETURN(res);
+ }
+
+ if (xa_trans_rolled_back(&thd->transaction.xid_state))
+ {
+ xa_trans_force_rollback(thd);
+ res= thd->is_error();
+ }
+ else if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_ONE_PHASE)
+ {
+ int r= ha_commit_trans(thd, TRUE);
+ if ((res= MY_TEST(r)))
+ my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0));
+ }
+ else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE)
+ {
+ MDL_request mdl_request;
+
+ /*
+ Acquire metadata lock which will ensure that COMMIT is blocked
+ by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in
+ progress blocks FTWRL).
+
+ We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does.
+ */
+ mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_TRANSACTION);
+
+ if (thd->mdl_context.acquire_lock(&mdl_request,
+ thd->variables.lock_wait_timeout))
+ {
+ ha_rollback_trans(thd, TRUE);
+ my_error(ER_XAER_RMERR, MYF(0));
+ }
+ else
+ {
+ DEBUG_SYNC(thd, "trans_xa_commit_after_acquire_commit_lock");
+
+ res= MY_TEST(ha_commit_one_phase(thd, 1));
+ if (res)
+ my_error(ER_XAER_RMERR, MYF(0));
+ }
+ }
+ else
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ DBUG_RETURN(TRUE);
+ }
+
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
+ thd->transaction.all.modified_non_trans_table= FALSE;
+ thd->server_status&=
+ ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
+ DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
+ xid_cache_delete(&thd->transaction.xid_state);
+ thd->transaction.xid_state.xa_state= XA_NOTR;
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Roll back and terminate a XA transaction.
+
+ @param thd Current thread
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool trans_xa_rollback(THD *thd)
+{
+ bool res= TRUE;
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ DBUG_ENTER("trans_xa_rollback");
+
+ if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
+ {
+ XID_STATE *xs= xid_cache_search(thd->lex->xid);
+ if (!xs || xs->in_thd)
+ my_error(ER_XAER_NOTA, MYF(0));
+ else
+ {
+ xa_trans_rolled_back(xs);
+ ha_commit_or_rollback_by_xid(thd->lex->xid, 0);
+ xid_cache_delete(xs);
+ }
+ DBUG_RETURN(thd->get_stmt_da()->is_error());
+ }
+
+ if (xa_state != XA_IDLE && xa_state != XA_PREPARED && xa_state != XA_ROLLBACK_ONLY)
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ DBUG_RETURN(TRUE);
+ }
+
+ res= xa_trans_force_rollback(thd);
+
+ thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
+ thd->transaction.all.modified_non_trans_table= FALSE;
+ thd->server_status&=
+ ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
+ DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
+ xid_cache_delete(&thd->transaction.xid_state);
+ thd->transaction.xid_state.xa_state= XA_NOTR;
+
+ DBUG_RETURN(res);
+}
diff --git a/sql/transaction.h b/sql/transaction.h
new file mode 100644
index 00000000000..54b25f1de2a
--- /dev/null
+++ b/sql/transaction.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+#ifndef TRANSACTION_H
+#define TRANSACTION_H
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include <my_global.h>
+#include <m_string.h>
+
+class THD;
+
+bool trans_begin(THD *thd, uint flags= 0);
+bool trans_commit(THD *thd);
+bool trans_commit_implicit(THD *thd);
+bool trans_rollback(THD *thd);
+bool trans_rollback_implicit(THD *thd);
+
+bool trans_commit_stmt(THD *thd);
+bool trans_rollback_stmt(THD *thd);
+
+bool trans_savepoint(THD *thd, LEX_STRING name);
+bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name);
+bool trans_release_savepoint(THD *thd, LEX_STRING name);
+
+bool trans_xa_start(THD *thd);
+bool trans_xa_end(THD *thd);
+bool trans_xa_prepare(THD *thd);
+bool trans_xa_commit(THD *thd);
+bool trans_xa_rollback(THD *thd);
+
+#endif /* TRANSACTION_H */
diff --git a/sql/tzfile.h b/sql/tzfile.h
new file mode 100644
index 00000000000..4feba612b36
--- /dev/null
+++ b/sql/tzfile.h
@@ -0,0 +1,142 @@
+#ifndef TZFILE_INCLUDED
+#define TZFILE_INCLUDED
+
+/* Copyright (c) 2004, 2006, 2007 MySQL AB, 2009 Sun Microsystems, Inc.
+ Use is subject to license terms.
+
+ 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 */
+
+/*
+ This file is based on public domain code from ftp://elsie.ncih.nist.gov/
+ Initial source code is in the public domain, so clarified as of
+ 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
+*/
+
+/*
+ Information about time zone files.
+*/
+
+#ifndef TZDIR
+#define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */
+#endif /* !defined TZDIR */
+
+/*
+ Each file begins with. . .
+*/
+
+#define TZ_MAGIC "TZif"
+
+struct tzhead {
+ uchar tzh_magic[4]; /* TZ_MAGIC */
+ uchar tzh_reserved[16]; /* reserved for future use */
+ uchar tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
+ uchar tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
+ uchar tzh_leapcnt[4]; /* coded number of leap seconds */
+ uchar tzh_timecnt[4]; /* coded number of transition times */
+ uchar tzh_typecnt[4]; /* coded number of local time types */
+ uchar tzh_charcnt[4]; /* coded number of abbr. chars */
+};
+
+/*
+ . . .followed by. . .
+
+ tzh_timecnt (char [4])s coded transition times a la time(2)
+ tzh_timecnt (unsigned char)s types of local time starting at above
+ tzh_typecnt repetitions of
+ one (char [4]) coded UTC offset in seconds
+ one (unsigned char) used to set tm_isdst
+ one (unsigned char) that's an abbreviation list index
+ tzh_charcnt (char)s '\0'-terminated zone abbreviations
+ tzh_leapcnt repetitions of
+ one (char [4]) coded leap second transition times
+ one (char [4]) total correction after above
+ tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition
+ time is standard time, if FALSE,
+ transition time is wall clock time
+ if absent, transition times are
+ assumed to be wall clock time
+ tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition
+ time is UTC, if FALSE,
+ transition time is local time
+ if absent, transition times are
+ assumed to be local time
+*/
+
+/*
+ In the current implementation, we refuse to deal with files that
+ exceed any of the limits below.
+*/
+
+#ifndef TZ_MAX_TIMES
+/*
+ The TZ_MAX_TIMES value below is enough to handle a bit more than a
+ year's worth of solar time (corrected daily to the nearest second) or
+ 138 years of Pacific Presidential Election time
+ (where there are three time zone transitions every fourth year).
+*/
+#define TZ_MAX_TIMES 370
+#endif /* !defined TZ_MAX_TIMES */
+
+#ifndef TZ_MAX_TYPES
+#ifdef SOLAR
+#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
+#else
+/*
+ Must be at least 14 for Europe/Riga as of Jan 12 1995,
+ as noted by Earl Chew <earl@hpato.aus.hp.com>.
+*/
+#define TZ_MAX_TYPES 20 /* Maximum number of local time types */
+#endif /* defined SOLAR */
+#endif /* !defined TZ_MAX_TYPES */
+
+#ifndef TZ_MAX_CHARS
+#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
+ /* (limited by what unsigned chars can hold) */
+#endif /* !defined TZ_MAX_CHARS */
+
+#ifndef TZ_MAX_LEAPS
+#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
+#endif /* !defined TZ_MAX_LEAPS */
+
+#ifndef TZ_MAX_REV_RANGES
+#ifdef SOLAR
+/* Solar (Asia/RiyadhXX) zones need significantly bigger TZ_MAX_REV_RANGES */
+#define TZ_MAX_REV_RANGES (TZ_MAX_TIMES*2+TZ_MAX_LEAPS*2+2)
+#else
+#define TZ_MAX_REV_RANGES (TZ_MAX_TIMES+TZ_MAX_LEAPS+2)
+#endif
+#endif
+
+#define SECS_PER_MIN 60
+#define MINS_PER_HOUR 60
+#define HOURS_PER_DAY 24
+#define DAYS_PER_WEEK 7
+#define DAYS_PER_NYEAR 365
+#define DAYS_PER_LYEAR 366
+#define SECS_PER_HOUR (SECS_PER_MIN * MINS_PER_HOUR)
+#define SECS_PER_DAY ((long) SECS_PER_HOUR * HOURS_PER_DAY)
+#define MONS_PER_YEAR 12
+
+#define TM_YEAR_BASE 1900
+
+#define EPOCH_YEAR 1970
+
+/*
+ Accurate only for the past couple of centuries,
+ that will probably do.
+*/
+
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+
+#endif
diff --git a/sql/tztime.cc b/sql/tztime.cc
new file mode 100644
index 00000000000..d3b4fec6335
--- /dev/null
+++ b/sql/tztime.cc
@@ -0,0 +1,2976 @@
+/*
+ Copyright (c) 2004, 2010, Oracle and/or its affiliates.
+
+ 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 */
+
+/*
+ Most of the following code and structures were derived from
+ public domain code from ftp://elsie.nci.nih.gov/pub
+ (We will refer to this code as to elsie-code further.)
+*/
+
+/*
+ We should not include sql_priv.h in mysql_tzinfo_to_sql utility since
+ it creates unsolved link dependencies on some platforms.
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#if !defined(TZINFO2SQL) && !defined(TESTTIME)
+#include "sql_priv.h"
+#include "unireg.h"
+#include "tztime.h"
+#include "sql_time.h" // localtime_to_TIME
+#include "sql_base.h" // open_system_tables_for_read,
+ // close_system_tables
+#else
+#include <my_time.h>
+#include "tztime.h"
+#include <my_sys.h>
+#include <mysql_version.h>
+#include <my_getopt.h>
+#endif
+
+#include "tzfile.h"
+#include <m_string.h>
+#include <my_dir.h>
+#include <mysql/psi/mysql_file.h>
+#include "lock.h" // MYSQL_LOCK_IGNORE_FLUSH,
+ // MYSQL_LOCK_IGNORE_TIMEOUT
+
+/*
+ Now we don't use abbreviations in server but we will do this in future.
+*/
+#if defined(TZINFO2SQL) || defined(TESTTIME)
+#define ABBR_ARE_USED
+#else
+#if !defined(DBUG_OFF)
+/* Let use abbreviations for debug purposes */
+#undef ABBR_ARE_USED
+#define ABBR_ARE_USED
+#endif /* !defined(DBUG_OFF) */
+#endif /* defined(TZINFO2SQL) || defined(TESTTIME) */
+
+#define PROGRAM_VERSION "1.1"
+
+/* Structure describing local time type (e.g. Moscow summer time (MSD)) */
+typedef struct ttinfo
+{
+ long tt_gmtoff; // Offset from UTC in seconds
+ uint tt_isdst; // Is daylight saving time or not. Used to set tm_isdst
+#ifdef ABBR_ARE_USED
+ uint tt_abbrind; // Index of start of abbreviation for this time type.
+#endif
+ /*
+ We don't use tt_ttisstd and tt_ttisgmt members of original elsie-code
+ struct since we don't support POSIX-style TZ descriptions in variables.
+ */
+} TRAN_TYPE_INFO;
+
+/* Structure describing leap-second corrections. */
+typedef struct lsinfo
+{
+ my_time_t ls_trans; // Transition time
+ long ls_corr; // Correction to apply
+} LS_INFO;
+
+/*
+ Structure with information describing ranges of my_time_t shifted to local
+ time (my_time_t + offset). Used for local MYSQL_TIME -> my_time_t conversion.
+ See comments for TIME_to_gmt_sec() for more info.
+*/
+typedef struct revtinfo
+{
+ long rt_offset; // Offset of local time from UTC in seconds
+ uint rt_type; // Type of period 0 - Normal period. 1 - Spring time-gap
+} REVT_INFO;
+
+#ifdef TZNAME_MAX
+#define MY_TZNAME_MAX TZNAME_MAX
+#endif
+#ifndef TZNAME_MAX
+#define MY_TZNAME_MAX 255
+#endif
+
+/*
+ Structure which fully describes time zone which is
+ described in our db or in zoneinfo files.
+*/
+typedef struct st_time_zone_info
+{
+ uint leapcnt; // Number of leap-second corrections
+ uint timecnt; // Number of transitions between time types
+ uint typecnt; // Number of local time types
+ uint charcnt; // Number of characters used for abbreviations
+ uint revcnt; // Number of transition descr. for TIME->my_time_t conversion
+ /* The following are dynamical arrays are allocated in MEM_ROOT */
+ my_time_t *ats; // Times of transitions between time types
+ uchar *types; // Local time types for transitions
+ TRAN_TYPE_INFO *ttis; // Local time types descriptions
+#ifdef ABBR_ARE_USED
+ /* Storage for local time types abbreviations. They are stored as ASCIIZ */
+ char *chars;
+#endif
+ /*
+ Leap seconds corrections descriptions, this array is shared by
+ all time zones who use leap seconds.
+ */
+ LS_INFO *lsis;
+ /*
+ Starting points and descriptions of shifted my_time_t (my_time_t + offset)
+ ranges on which shifted my_time_t -> my_time_t mapping is linear or undefined.
+ Used for tm -> my_time_t conversion.
+ */
+ my_time_t *revts;
+ REVT_INFO *revtis;
+ /*
+ Time type which is used for times smaller than first transition or if
+ there are no transitions at all.
+ */
+ TRAN_TYPE_INFO *fallback_tti;
+
+} TIME_ZONE_INFO;
+
+
+static my_bool prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage);
+
+
+#if defined(TZINFO2SQL) || defined(TESTTIME)
+
+/*
+ Load time zone description from zoneinfo (TZinfo) file.
+
+ SYNOPSIS
+ tz_load()
+ name - path to zoneinfo file
+ sp - TIME_ZONE_INFO structure to fill
+
+ RETURN VALUES
+ 0 - Ok
+ 1 - Error
+*/
+static my_bool
+tz_load(const char *name, TIME_ZONE_INFO *sp, MEM_ROOT *storage)
+{
+ uchar *p;
+ int read_from_file;
+ uint i;
+ MYSQL_FILE *file;
+
+ if (!(file= mysql_file_fopen(0, name, O_RDONLY|O_BINARY, MYF(MY_WME))))
+ return 1;
+ {
+ union
+ {
+ struct tzhead tzhead;
+ uchar buf[sizeof(struct tzhead) + sizeof(my_time_t) * TZ_MAX_TIMES +
+ TZ_MAX_TIMES + sizeof(TRAN_TYPE_INFO) * TZ_MAX_TYPES +
+#ifdef ABBR_ARE_USED
+ MY_MAX(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1))) +
+#endif
+ sizeof(LS_INFO) * TZ_MAX_LEAPS];
+ } u;
+ uint ttisstdcnt;
+ uint ttisgmtcnt;
+ char *tzinfo_buf;
+
+ read_from_file= mysql_file_fread(file, u.buf, sizeof(u.buf), MYF(MY_WME));
+
+ if (mysql_file_fclose(file, MYF(MY_WME)) != 0)
+ return 1;
+
+ if (read_from_file < (int)sizeof(struct tzhead))
+ return 1;
+
+ ttisstdcnt= int4net(u.tzhead.tzh_ttisgmtcnt);
+ ttisgmtcnt= int4net(u.tzhead.tzh_ttisstdcnt);
+ sp->leapcnt= int4net(u.tzhead.tzh_leapcnt);
+ sp->timecnt= int4net(u.tzhead.tzh_timecnt);
+ sp->typecnt= int4net(u.tzhead.tzh_typecnt);
+ sp->charcnt= int4net(u.tzhead.tzh_charcnt);
+ p= u.tzhead.tzh_charcnt + sizeof(u.tzhead.tzh_charcnt);
+ if (sp->leapcnt > TZ_MAX_LEAPS ||
+ sp->typecnt == 0 || sp->typecnt > TZ_MAX_TYPES ||
+ sp->timecnt > TZ_MAX_TIMES ||
+ sp->charcnt > TZ_MAX_CHARS ||
+ (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
+ (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
+ return 1;
+ if ((uint)(read_from_file - (p - u.buf)) <
+ sp->timecnt * 4 + /* ats */
+ sp->timecnt + /* types */
+ sp->typecnt * (4 + 2) + /* ttinfos */
+ sp->charcnt + /* chars */
+ sp->leapcnt * (4 + 4) + /* lsinfos */
+ ttisstdcnt + /* ttisstds */
+ ttisgmtcnt) /* ttisgmts */
+ return 1;
+
+ if (!(tzinfo_buf= (char *)alloc_root(storage,
+ ALIGN_SIZE(sp->timecnt *
+ sizeof(my_time_t)) +
+ ALIGN_SIZE(sp->timecnt) +
+ ALIGN_SIZE(sp->typecnt *
+ sizeof(TRAN_TYPE_INFO)) +
+#ifdef ABBR_ARE_USED
+ ALIGN_SIZE(sp->charcnt+1) +
+#endif
+ sp->leapcnt * sizeof(LS_INFO))))
+ return 1;
+
+ sp->ats= (my_time_t *)tzinfo_buf;
+ tzinfo_buf+= ALIGN_SIZE(sp->timecnt * sizeof(my_time_t));
+ sp->types= (uchar *)tzinfo_buf;
+ tzinfo_buf+= ALIGN_SIZE(sp->timecnt);
+ sp->ttis= (TRAN_TYPE_INFO *)tzinfo_buf;
+ tzinfo_buf+= ALIGN_SIZE(sp->typecnt * sizeof(TRAN_TYPE_INFO));
+#ifdef ABBR_ARE_USED
+ sp->chars= tzinfo_buf;
+ tzinfo_buf+= ALIGN_SIZE(sp->charcnt+1);
+#endif
+ sp->lsis= (LS_INFO *)tzinfo_buf;
+
+ for (i= 0; i < sp->timecnt; i++, p+= 4)
+ sp->ats[i]= int4net(p);
+
+ for (i= 0; i < sp->timecnt; i++)
+ {
+ sp->types[i]= (uchar) *p++;
+ if (sp->types[i] >= sp->typecnt)
+ return 1;
+ }
+ for (i= 0; i < sp->typecnt; i++)
+ {
+ TRAN_TYPE_INFO * ttisp;
+
+ ttisp= &sp->ttis[i];
+ ttisp->tt_gmtoff= int4net(p);
+ p+= 4;
+ ttisp->tt_isdst= (uchar) *p++;
+ if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
+ return 1;
+ ttisp->tt_abbrind= (uchar) *p++;
+ if (ttisp->tt_abbrind > sp->charcnt)
+ return 1;
+ }
+ for (i= 0; i < sp->charcnt; i++)
+ sp->chars[i]= *p++;
+ sp->chars[i]= '\0'; /* ensure '\0' at end */
+ for (i= 0; i < sp->leapcnt; i++)
+ {
+ LS_INFO *lsisp;
+
+ lsisp= &sp->lsis[i];
+ lsisp->ls_trans= int4net(p);
+ p+= 4;
+ lsisp->ls_corr= int4net(p);
+ p+= 4;
+ }
+ /*
+ Since we don't support POSIX style TZ definitions in variables we
+ don't read further like glibc or elsie code.
+ */
+ }
+
+ return prepare_tz_info(sp, storage);
+}
+#endif /* defined(TZINFO2SQL) || defined(TESTTIME) */
+
+
+/*
+ Finish preparation of time zone description for use in TIME_to_gmt_sec()
+ and gmt_sec_to_TIME() functions.
+
+ SYNOPSIS
+ prepare_tz_info()
+ sp - pointer to time zone description
+ storage - pointer to MEM_ROOT where arrays for map allocated
+
+ DESCRIPTION
+ First task of this function is to find fallback time type which will
+ be used if there are no transitions or we have moment in time before
+ any transitions.
+ Second task is to build "shifted my_time_t" -> my_time_t map used in
+ MYSQL_TIME -> my_time_t conversion.
+ Note: See description of TIME_to_gmt_sec() function first.
+ In order to perform MYSQL_TIME -> my_time_t conversion we need to build table
+ which defines "shifted by tz offset and leap seconds my_time_t" ->
+ my_time_t function wich is almost the same (except ranges of ambiguity)
+ as reverse function to piecewise linear function used for my_time_t ->
+ "shifted my_time_t" conversion and which is also specified as table in
+ zoneinfo file or in our db (It is specified as start of time type ranges
+ and time type offsets). So basic idea is very simple - let us iterate
+ through my_time_t space from one point of discontinuity of my_time_t ->
+ "shifted my_time_t" function to another and build our approximation of
+ reverse function. (Actually we iterate through ranges on which
+ my_time_t -> "shifted my_time_t" is linear function).
+
+ RETURN VALUES
+ 0 Ok
+ 1 Error
+*/
+static my_bool
+prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage)
+{
+ my_time_t cur_t= MY_TIME_T_MIN;
+ my_time_t cur_l, end_t, end_l;
+ my_time_t cur_max_seen_l= MY_TIME_T_MIN;
+ long cur_offset, cur_corr, cur_off_and_corr;
+ uint next_trans_idx, next_leap_idx;
+ uint i;
+ /*
+ Temporary arrays where we will store tables. Needed because
+ we don't know table sizes ahead. (Well we can estimate their
+ upper bound but this will take extra space.)
+ */
+ my_time_t revts[TZ_MAX_REV_RANGES];
+ REVT_INFO revtis[TZ_MAX_REV_RANGES];
+
+ LINT_INIT(end_l);
+
+ /*
+ Let us setup fallback time type which will be used if we have not any
+ transitions or if we have moment of time before first transition.
+ We will find first non-DST local time type and use it (or use first
+ local time type if all of them are DST types).
+ */
+ for (i= 0; i < sp->typecnt && sp->ttis[i].tt_isdst; i++)
+ /* no-op */ ;
+ if (i == sp->typecnt)
+ i= 0;
+ sp->fallback_tti= &(sp->ttis[i]);
+
+
+ /*
+ Let us build shifted my_time_t -> my_time_t map.
+ */
+ sp->revcnt= 0;
+
+ /* Let us find initial offset */
+ if (sp->timecnt == 0 || cur_t < sp->ats[0])
+ {
+ /*
+ If we have not any transitions or t is before first transition we are using
+ already found fallback time type which index is already in i.
+ */
+ next_trans_idx= 0;
+ }
+ else
+ {
+ /* cur_t == sp->ats[0] so we found transition */
+ i= sp->types[0];
+ next_trans_idx= 1;
+ }
+
+ cur_offset= sp->ttis[i].tt_gmtoff;
+
+
+ /* let us find leap correction... unprobable, but... */
+ for (next_leap_idx= 0; next_leap_idx < sp->leapcnt &&
+ cur_t >= sp->lsis[next_leap_idx].ls_trans;
+ ++next_leap_idx)
+ continue;
+
+ if (next_leap_idx > 0)
+ cur_corr= sp->lsis[next_leap_idx - 1].ls_corr;
+ else
+ cur_corr= 0;
+
+ /* Iterate trough t space */
+ while (sp->revcnt < TZ_MAX_REV_RANGES - 1)
+ {
+ cur_off_and_corr= cur_offset - cur_corr;
+
+ /*
+ We assuming that cur_t could be only overflowed downwards,
+ we also assume that end_t won't be overflowed in this case.
+ */
+ if (cur_off_and_corr < 0 &&
+ cur_t < MY_TIME_T_MIN - cur_off_and_corr)
+ cur_t= MY_TIME_T_MIN - cur_off_and_corr;
+
+ cur_l= cur_t + cur_off_and_corr;
+
+ /*
+ Let us choose end_t as point before next time type change or leap
+ second correction.
+ */
+ end_t= MY_MIN((next_trans_idx < sp->timecnt) ? sp->ats[next_trans_idx] - 1:
+ MY_TIME_T_MAX,
+ (next_leap_idx < sp->leapcnt) ?
+ sp->lsis[next_leap_idx].ls_trans - 1: MY_TIME_T_MAX);
+ /*
+ again assuming that end_t can be overlowed only in positive side
+ we also assume that end_t won't be overflowed in this case.
+ */
+ if (cur_off_and_corr > 0 &&
+ end_t > MY_TIME_T_MAX - cur_off_and_corr)
+ end_t= MY_TIME_T_MAX - cur_off_and_corr;
+
+ end_l= end_t + cur_off_and_corr;
+
+
+ if (end_l > cur_max_seen_l)
+ {
+ /* We want special handling in the case of first range */
+ if (cur_max_seen_l == MY_TIME_T_MIN)
+ {
+ revts[sp->revcnt]= cur_l;
+ revtis[sp->revcnt].rt_offset= cur_off_and_corr;
+ revtis[sp->revcnt].rt_type= 0;
+ sp->revcnt++;
+ cur_max_seen_l= end_l;
+ }
+ else
+ {
+ if (cur_l > cur_max_seen_l + 1)
+ {
+ /* We have a spring time-gap and we are not at the first range */
+ revts[sp->revcnt]= cur_max_seen_l + 1;
+ revtis[sp->revcnt].rt_offset= revtis[sp->revcnt-1].rt_offset;
+ revtis[sp->revcnt].rt_type= 1;
+ sp->revcnt++;
+ if (sp->revcnt == TZ_MAX_TIMES + TZ_MAX_LEAPS + 1)
+ break; /* That was too much */
+ cur_max_seen_l= cur_l - 1;
+ }
+
+ /* Assume here end_l > cur_max_seen_l (because end_l>=cur_l) */
+
+ revts[sp->revcnt]= cur_max_seen_l + 1;
+ revtis[sp->revcnt].rt_offset= cur_off_and_corr;
+ revtis[sp->revcnt].rt_type= 0;
+ sp->revcnt++;
+ cur_max_seen_l= end_l;
+ }
+ }
+
+ if (end_t == MY_TIME_T_MAX ||
+ ((cur_off_and_corr > 0) &&
+ (end_t >= MY_TIME_T_MAX - cur_off_and_corr)))
+ /* end of t space */
+ break;
+
+ cur_t= end_t + 1;
+
+ /*
+ Let us find new offset and correction. Because of our choice of end_t
+ cur_t can only be point where new time type starts or/and leap
+ correction is performed.
+ */
+ if (sp->timecnt != 0 && cur_t >= sp->ats[0]) /* else reuse old offset */
+ if (next_trans_idx < sp->timecnt &&
+ cur_t == sp->ats[next_trans_idx])
+ {
+ /* We are at offset point */
+ cur_offset= sp->ttis[sp->types[next_trans_idx]].tt_gmtoff;
+ ++next_trans_idx;
+ }
+
+ if (next_leap_idx < sp->leapcnt &&
+ cur_t == sp->lsis[next_leap_idx].ls_trans)
+ {
+ /* we are at leap point */
+ cur_corr= sp->lsis[next_leap_idx].ls_corr;
+ ++next_leap_idx;
+ }
+ }
+
+ /* check if we have had enough space */
+ if (sp->revcnt == TZ_MAX_REV_RANGES - 1)
+ return 1;
+
+ /* set maximum end_l as finisher */
+ revts[sp->revcnt]= end_l;
+
+ /* Allocate arrays of proper size in sp and copy result there */
+ if (!(sp->revts= (my_time_t *)alloc_root(storage,
+ sizeof(my_time_t) * (sp->revcnt + 1))) ||
+ !(sp->revtis= (REVT_INFO *)alloc_root(storage,
+ sizeof(REVT_INFO) * sp->revcnt)))
+ return 1;
+
+ memcpy(sp->revts, revts, sizeof(my_time_t) * (sp->revcnt + 1));
+ memcpy(sp->revtis, revtis, sizeof(REVT_INFO) * sp->revcnt);
+
+ return 0;
+}
+
+
+#if !defined(TZINFO2SQL)
+
+static const uint mon_lengths[2][MONS_PER_YEAR]=
+{
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
+
+static const uint mon_starts[2][MONS_PER_YEAR]=
+{
+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
+ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
+};
+
+static const uint year_lengths[2]=
+{
+ DAYS_PER_NYEAR, DAYS_PER_LYEAR
+};
+
+#define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400)
+
+
+/*
+ Converts time from my_time_t representation (seconds in UTC since Epoch)
+ to broken down representation using given local time zone offset.
+
+ SYNOPSIS
+ sec_to_TIME()
+ tmp - pointer to structure for broken down representation
+ t - my_time_t value to be converted
+ offset - local time zone offset
+
+ DESCRIPTION
+ Convert my_time_t with offset to MYSQL_TIME struct. Differs from timesub
+ (from elsie code) because doesn't contain any leap correction and
+ TM_GMTOFF and is_dst setting and contains some MySQL specific
+ initialization. Funny but with removing of these we almost have
+ glibc's offtime function.
+*/
+static void
+sec_to_TIME(MYSQL_TIME * tmp, my_time_t t, long offset)
+{
+ long days;
+ long rem;
+ int y;
+ int yleap;
+ const uint *ip;
+
+ days= (long) (t / SECS_PER_DAY);
+ rem= (long) (t % SECS_PER_DAY);
+
+ /*
+ We do this as separate step after dividing t, because this
+ allows us handle times near my_time_t bounds without overflows.
+ */
+ rem+= offset;
+ while (rem < 0)
+ {
+ rem+= SECS_PER_DAY;
+ days--;
+ }
+ while (rem >= SECS_PER_DAY)
+ {
+ rem -= SECS_PER_DAY;
+ days++;
+ }
+ tmp->hour= (uint)(rem / SECS_PER_HOUR);
+ rem= rem % SECS_PER_HOUR;
+ tmp->minute= (uint)(rem / SECS_PER_MIN);
+ /*
+ A positive leap second requires a special
+ representation. This uses "... ??:59:60" et seq.
+ */
+ tmp->second= (uint)(rem % SECS_PER_MIN);
+
+ y= EPOCH_YEAR;
+ while (days < 0 || days >= (long)year_lengths[yleap= isleap(y)])
+ {
+ int newy;
+
+ newy= y + days / DAYS_PER_NYEAR;
+ if (days < 0)
+ newy--;
+ days-= (newy - y) * DAYS_PER_NYEAR +
+ LEAPS_THRU_END_OF(newy - 1) -
+ LEAPS_THRU_END_OF(y - 1);
+ y= newy;
+ }
+ tmp->year= y;
+
+ ip= mon_lengths[yleap];
+ for (tmp->month= 0; days >= (long) ip[tmp->month]; tmp->month++)
+ days= days - (long) ip[tmp->month];
+ tmp->month++;
+ tmp->day= (uint)(days + 1);
+
+ /* filling MySQL specific MYSQL_TIME members */
+ tmp->neg= 0; tmp->second_part= 0;
+ tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
+}
+
+
+/*
+ Find time range wich contains given my_time_t value
+
+ SYNOPSIS
+ find_time_range()
+ t - my_time_t value for which we looking for range
+ range_boundaries - sorted array of range starts.
+ higher_bound - number of ranges
+
+ DESCRIPTION
+ Performs binary search for range which contains given my_time_t value.
+ It has sense if number of ranges is greater than zero and my_time_t value
+ is greater or equal than beginning of first range. It also assumes that
+ t belongs to some range specified or end of last is MY_TIME_T_MAX.
+
+ With this localtime_r on real data may takes less time than with linear
+ search (I've seen 30% speed up).
+
+ RETURN VALUE
+ Index of range to which t belongs
+*/
+static uint
+find_time_range(my_time_t t, const my_time_t *range_boundaries,
+ uint higher_bound)
+{
+ uint i, lower_bound= 0;
+
+ /*
+ Function will work without this assertion but result would be meaningless.
+ */
+ DBUG_ASSERT(higher_bound > 0 && t >= range_boundaries[0]);
+
+ /*
+ Do binary search for minimal interval which contain t. We preserve:
+ range_boundaries[lower_bound] <= t < range_boundaries[higher_bound]
+ invariant and decrease this higher_bound - lower_bound gap twice
+ times on each step.
+ */
+
+ while (higher_bound - lower_bound > 1)
+ {
+ i= (lower_bound + higher_bound) >> 1;
+ if (range_boundaries[i] <= t)
+ lower_bound= i;
+ else
+ higher_bound= i;
+ }
+ return lower_bound;
+}
+
+/*
+ Find local time transition for given my_time_t.
+
+ SYNOPSIS
+ find_transition_type()
+ t - my_time_t value to be converted
+ sp - pointer to struct with time zone description
+
+ RETURN VALUE
+ Pointer to structure in time zone description describing
+ local time type for given my_time_t.
+*/
+static
+const TRAN_TYPE_INFO *
+find_transition_type(my_time_t t, const TIME_ZONE_INFO *sp)
+{
+ if (unlikely(sp->timecnt == 0 || t < sp->ats[0]))
+ {
+ /*
+ If we have not any transitions or t is before first transition let
+ us use fallback time type.
+ */
+ return sp->fallback_tti;
+ }
+
+ /*
+ Do binary search for minimal interval between transitions which
+ contain t. With this localtime_r on real data may takes less
+ time than with linear search (I've seen 30% speed up).
+ */
+ return &(sp->ttis[sp->types[find_time_range(t, sp->ats, sp->timecnt)]]);
+}
+
+
+/*
+ Converts time in my_time_t representation (seconds in UTC since Epoch) to
+ broken down MYSQL_TIME representation in local time zone.
+
+ SYNOPSIS
+ gmt_sec_to_TIME()
+ tmp - pointer to structure for broken down represenatation
+ sec_in_utc - my_time_t value to be converted
+ sp - pointer to struct with time zone description
+
+ TODO
+ We can improve this function by creating joined array of transitions and
+ leap corrections. This will require adding extra field to TRAN_TYPE_INFO
+ for storing number of "extra" seconds to minute occured due to correction
+ (60th and 61st second, look how we calculate them as "hit" in this
+ function).
+ Under realistic assumptions about frequency of transitions the same array
+ can be used fot MYSQL_TIME -> my_time_t conversion. For this we need to
+ implement tweaked binary search which will take into account that some
+ MYSQL_TIME has two matching my_time_t ranges and some of them have none.
+*/
+static void
+gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t sec_in_utc, const TIME_ZONE_INFO *sp)
+{
+ const TRAN_TYPE_INFO *ttisp;
+ const LS_INFO *lp;
+ long corr= 0;
+ int hit= 0;
+ int i;
+
+ /*
+ Find proper transition (and its local time type) for our sec_in_utc value.
+ Funny but again by separating this step in function we receive code
+ which very close to glibc's code. No wonder since they obviously use
+ the same base and all steps are sensible.
+ */
+ ttisp= find_transition_type(sec_in_utc, sp);
+
+ /*
+ Let us find leap correction for our sec_in_utc value and number of extra
+ secs to add to this minute.
+ This loop is rarely used because most users will use time zones without
+ leap seconds, and even in case when we have such time zone there won't
+ be many iterations (we have about 22 corrections at this moment (2004)).
+ */
+ for ( i= sp->leapcnt; i-- > 0; )
+ {
+ lp= &sp->lsis[i];
+ if (sec_in_utc >= lp->ls_trans)
+ {
+ if (sec_in_utc == lp->ls_trans)
+ {
+ hit= ((i == 0 && lp->ls_corr > 0) ||
+ lp->ls_corr > sp->lsis[i - 1].ls_corr);
+ if (hit)
+ {
+ while (i > 0 &&
+ sp->lsis[i].ls_trans == sp->lsis[i - 1].ls_trans + 1 &&
+ sp->lsis[i].ls_corr == sp->lsis[i - 1].ls_corr + 1)
+ {
+ hit++;
+ i--;
+ }
+ }
+ }
+ corr= lp->ls_corr;
+ break;
+ }
+ }
+
+ sec_to_TIME(tmp, sec_in_utc, ttisp->tt_gmtoff - corr);
+
+ tmp->second+= hit;
+}
+
+
+/*
+ Converts local time in broken down representation to local
+ time zone analog of my_time_t represenation.
+
+ SYNOPSIS
+ sec_since_epoch()
+ year, mon, mday, hour, min, sec - broken down representation.
+
+ DESCRIPTION
+ Converts time in broken down representation to my_time_t representation
+ ignoring time zone. Note that we cannot convert back some valid _local_
+ times near ends of my_time_t range because of my_time_t overflow. But we
+ ignore this fact now since MySQL will never pass such argument.
+
+ RETURN VALUE
+ Seconds since epoch time representation.
+*/
+static my_time_t
+sec_since_epoch(int year, int mon, int mday, int hour, int min ,int sec)
+{
+ /* Guard against my_time_t overflow(on system with 32 bit my_time_t) */
+ DBUG_ASSERT(!(year == TIMESTAMP_MAX_YEAR && mon == 1 && mday > 17));
+#ifndef WE_WANT_TO_HANDLE_UNORMALIZED_DATES
+ /*
+ It turns out that only whenever month is normalized or unnormalized
+ plays role.
+ */
+ DBUG_ASSERT(mon > 0 && mon < 13);
+ long days= year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR +
+ LEAPS_THRU_END_OF(year - 1) -
+ LEAPS_THRU_END_OF(EPOCH_YEAR - 1);
+ days+= mon_starts[isleap(year)][mon - 1];
+#else
+ long norm_month= (mon - 1) % MONS_PER_YEAR;
+ long a_year= year + (mon - 1)/MONS_PER_YEAR - (int)(norm_month < 0);
+ long days= a_year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR +
+ LEAPS_THRU_END_OF(a_year - 1) -
+ LEAPS_THRU_END_OF(EPOCH_YEAR - 1);
+ days+= mon_starts[isleap(a_year)]
+ [norm_month + (norm_month < 0 ? MONS_PER_YEAR : 0)];
+#endif
+ days+= mday - 1;
+
+ return ((days * HOURS_PER_DAY + hour) * MINS_PER_HOUR + min) *
+ SECS_PER_MIN + sec;
+}
+
+/*
+ Converts local time in broken down MYSQL_TIME representation to my_time_t
+ representation.
+
+ SYNOPSIS
+ TIME_to_gmt_sec()
+ t - pointer to structure for broken down represenatation
+ sp - pointer to struct with time zone description
+ error_code - 0, if the conversion was successful;
+ ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
+ which is out of TIMESTAMP range;
+ ER_WARN_INVALID_TIMESTAMP, if t represents value which
+ doesn't exists (falls into the spring time-gap).
+
+ DESCRIPTION
+ This is mktime analog for MySQL. It is essentially different
+ from mktime (or hypotetical my_mktime) because:
+ - It has no idea about tm_isdst member so if it
+ has two answers it will give the smaller one
+ - If we are in spring time gap then it will return
+ beginning of the gap
+ - It can give wrong results near the ends of my_time_t due to
+ overflows, but we are safe since in MySQL we will never
+ call this function for such dates (its restriction for year
+ between 1970 and 2038 gives us several days of reserve).
+ - By default it doesn't support un-normalized input. But if
+ sec_since_epoch() function supports un-normalized dates
+ then this function should handle un-normalized input right,
+ altough it won't normalize structure TIME.
+
+ Traditional approach to problem of conversion from broken down
+ representation to time_t is iterative. Both elsie's and glibc
+ implementation try to guess what time_t value should correspond to
+ this broken-down value. They perform localtime_r function on their
+ guessed value and then calculate the difference and try to improve
+ their guess. Elsie's code guesses time_t value in bit by bit manner,
+ Glibc's code tries to add difference between broken-down value
+ corresponding to guess and target broken-down value to current guess.
+ It also uses caching of last found correction... So Glibc's approach
+ is essentially faster but introduces some undetermenism (in case if
+ is_dst member of broken-down representation (tm struct) is not known
+ and we have two possible answers).
+
+ We use completely different approach. It is better since it is both
+ faster than iterative implementations and fully determenistic. If you
+ look at my_time_t to MYSQL_TIME conversion then you'll find that it consist
+ of two steps:
+ The first is calculating shifted my_time_t value and the second - TIME
+ calculation from shifted my_time_t value (well it is a bit simplified
+ picture). The part in which we are interested in is my_time_t -> shifted
+ my_time_t conversion. It is piecewise linear function which is defined
+ by combination of transition times as break points and times offset
+ as changing function parameter. The possible inverse function for this
+ converison would be ambiguos but with MySQL's restrictions we can use
+ some function which is the same as inverse function on unambigiuos
+ ranges and coincides with one of branches of inverse function in
+ other ranges. Thus we just need to build table which will determine
+ this shifted my_time_t -> my_time_t conversion similar to existing
+ (my_time_t -> shifted my_time_t table). We do this in
+ prepare_tz_info function.
+
+ TODO
+ If we can even more improve this function. For doing this we will need to
+ build joined map of transitions and leap corrections for gmt_sec_to_TIME()
+ function (similar to revts/revtis). Under realistic assumptions about
+ frequency of transitions we can use the same array for TIME_to_gmt_sec().
+ We need to implement special version of binary search for this. Such step
+ will be beneficial to CPU cache since we will decrease data-set used for
+ conversion twice.
+
+ RETURN VALUE
+ Seconds in UTC since Epoch.
+ 0 in case of error.
+*/
+
+static my_time_t
+TIME_to_gmt_sec(const MYSQL_TIME *t, const TIME_ZONE_INFO *sp, uint *error_code)
+{
+ my_time_t local_t;
+ uint saved_seconds;
+ uint i;
+ int shift= 0;
+ DBUG_ENTER("TIME_to_gmt_sec");
+
+ if (!validate_timestamp_range(t))
+ {
+ *error_code= ER_WARN_DATA_OUT_OF_RANGE;
+ DBUG_RETURN(0);
+ }
+
+ *error_code= 0;
+
+ /* We need this for correct leap seconds handling */
+ if (t->second < SECS_PER_MIN)
+ saved_seconds= 0;
+ else
+ saved_seconds= t->second;
+
+ /*
+ NOTE: to convert full my_time_t range we do a shift of the
+ boundary dates here to avoid overflow of my_time_t.
+ We use alike approach in my_system_gmt_sec().
+
+ However in that function we also have to take into account
+ overflow near 0 on some platforms. That's because my_system_gmt_sec
+ uses localtime_r(), which doesn't work with negative values correctly
+ on platforms with unsigned time_t (QNX). Here we don't use localtime()
+ => we negative values of local_t are ok.
+ */
+
+ if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && t->day > 4)
+ {
+ /*
+ We will pass (t->day - shift) to sec_since_epoch(), and
+ want this value to be a positive number, so we shift
+ only dates > 4.01.2038 (to avoid owerflow).
+ */
+ shift= 2;
+ }
+
+
+ local_t= sec_since_epoch(t->year, t->month, (t->day - shift),
+ t->hour, t->minute,
+ saved_seconds ? 0 : t->second);
+
+ /* We have at least one range */
+ DBUG_ASSERT(sp->revcnt >= 1);
+
+ if (local_t < sp->revts[0] || local_t > sp->revts[sp->revcnt])
+ {
+ /*
+ This means that source time can't be represented as my_time_t due to
+ limited my_time_t range.
+ */
+ *error_code= ER_WARN_DATA_OUT_OF_RANGE;
+ DBUG_RETURN(0);
+ }
+
+ /* binary search for our range */
+ i= find_time_range(local_t, sp->revts, sp->revcnt);
+
+ /*
+ As there are no offset switches at the end of TIMESTAMP range,
+ we could simply check for overflow here (and don't need to bother
+ about DST gaps etc)
+ */
+ if (shift)
+ {
+ if (local_t > (my_time_t) (TIMESTAMP_MAX_VALUE - shift * SECS_PER_DAY +
+ sp->revtis[i].rt_offset - saved_seconds))
+ {
+ *error_code= ER_WARN_DATA_OUT_OF_RANGE;
+ DBUG_RETURN(0); /* my_time_t overflow */
+ }
+ local_t+= shift * SECS_PER_DAY;
+ }
+
+ if (sp->revtis[i].rt_type)
+ {
+ /*
+ Oops! We are in spring time gap.
+ May be we should return error here?
+ Now we are returning my_time_t value corresponding to the
+ beginning of the gap.
+ */
+ *error_code= ER_WARN_INVALID_TIMESTAMP;
+ local_t= sp->revts[i] - sp->revtis[i].rt_offset + saved_seconds;
+ }
+ else
+ local_t= local_t - sp->revtis[i].rt_offset + saved_seconds;
+
+ /* check for TIMESTAMP_MAX_VALUE was already done above */
+ if (local_t < TIMESTAMP_MIN_VALUE)
+ {
+ local_t= 0;
+ *error_code= ER_WARN_DATA_OUT_OF_RANGE;
+ }
+
+ DBUG_RETURN(local_t);
+}
+
+
+/*
+ End of elsie derived code.
+*/
+#endif /* !defined(TZINFO2SQL) */
+
+
+#if !defined(TESTTIME) && !defined(TZINFO2SQL)
+
+/*
+ String with names of SYSTEM time zone.
+*/
+static const String tz_SYSTEM_name("SYSTEM", 6, &my_charset_latin1);
+
+
+/*
+ Instance of this class represents local time zone used on this system
+ (specified by TZ environment variable or via any other system mechanism).
+ It uses system functions (localtime_r, my_system_gmt_sec) for conversion
+ and is always available. Because of this it is used by default - if there
+ were no explicit time zone specified. On the other hand because of this
+ conversion methods provided by this class is significantly slower and
+ possibly less multi-threaded-friendly than corresponding Time_zone_db
+ methods so the latter should be preffered there it is possible.
+*/
+class Time_zone_system : public Time_zone
+{
+public:
+ Time_zone_system() {} /* Remove gcc warning */
+ virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const;
+ virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
+ virtual const String * get_name() const;
+};
+
+
+/*
+ Converts local time in system time zone in MYSQL_TIME representation
+ to its my_time_t representation.
+
+ SYNOPSIS
+ TIME_to_gmt_sec()
+ t - pointer to MYSQL_TIME structure with local time in
+ broken-down representation.
+ error_code - 0, if the conversion was successful;
+ ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
+ which is out of TIMESTAMP range;
+ ER_WARN_INVALID_TIMESTAMP, if t represents value which
+ doesn't exists (falls into the spring time-gap).
+
+ DESCRIPTION
+ This method uses system function (localtime_r()) for conversion
+ local time in system time zone in MYSQL_TIME structure to its my_time_t
+ representation. Unlike the same function for Time_zone_db class
+ it it won't handle unnormalized input properly. Still it will
+ return lowest possible my_time_t in case of ambiguity or if we
+ provide time corresponding to the time-gap.
+
+ You should call my_init_time() function before using this function.
+
+ RETURN VALUE
+ Corresponding my_time_t value or 0 in case of error
+*/
+my_time_t
+Time_zone_system::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
+{
+ long not_used;
+ return my_system_gmt_sec(t, &not_used, error_code);
+}
+
+
+/*
+ Converts time from UTC seconds since Epoch (my_time_t) representation
+ to system local time zone broken-down representation.
+
+ SYNOPSIS
+ gmt_sec_to_TIME()
+ tmp - pointer to MYSQL_TIME structure to fill-in
+ t - my_time_t value to be converted
+
+ NOTE
+ We assume that value passed to this function will fit into time_t range
+ supported by localtime_r. This conversion is putting restriction on
+ TIMESTAMP range in MySQL. If we can get rid of SYSTEM time zone at least
+ for interaction with client then we can extend TIMESTAMP range down to
+ the 1902 easily.
+*/
+void
+Time_zone_system::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
+{
+ struct tm tmp_tm;
+ time_t tmp_t= (time_t)t;
+
+ localtime_r(&tmp_t, &tmp_tm);
+ localtime_to_TIME(tmp, &tmp_tm);
+ tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
+ adjust_leap_second(tmp);
+}
+
+
+/*
+ Get name of time zone
+
+ SYNOPSIS
+ get_name()
+
+ RETURN VALUE
+ Name of time zone as String
+*/
+const String *
+Time_zone_system::get_name() const
+{
+ return &tz_SYSTEM_name;
+}
+
+
+/*
+ Instance of this class represents UTC time zone. It uses system gmtime_r
+ function for conversions and is always available. It is used only for
+ my_time_t -> MYSQL_TIME conversions in various UTC_... functions, it is not
+ intended for MYSQL_TIME -> my_time_t conversions and shouldn't be exposed to user.
+*/
+class Time_zone_utc : public Time_zone
+{
+public:
+ Time_zone_utc() {} /* Remove gcc warning */
+ virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
+ uint *error_code) const;
+ virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
+ virtual const String * get_name() const;
+};
+
+
+/*
+ Convert UTC time from MYSQL_TIME representation to its my_time_t representation.
+
+ DESCRIPTION
+ Since Time_zone_utc is used only internally for my_time_t -> TIME
+ conversions, this function of Time_zone interface is not implemented for
+ this class and should not be called.
+
+ RETURN VALUE
+ 0
+*/
+my_time_t
+Time_zone_utc::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
+{
+ /* Should be never called */
+ DBUG_ASSERT(0);
+ *error_code= ER_WARN_DATA_OUT_OF_RANGE;
+ return 0;
+}
+
+
+/*
+ Converts time from UTC seconds since Epoch (my_time_t) representation
+ to broken-down representation (also in UTC).
+
+ SYNOPSIS
+ gmt_sec_to_TIME()
+ tmp - pointer to MYSQL_TIME structure to fill-in
+ t - my_time_t value to be converted
+
+ NOTE
+ See note for apropriate Time_zone_system method.
+*/
+void
+Time_zone_utc::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
+{
+ struct tm tmp_tm;
+ time_t tmp_t= (time_t)t;
+ gmtime_r(&tmp_t, &tmp_tm);
+ localtime_to_TIME(tmp, &tmp_tm);
+ tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
+ adjust_leap_second(tmp);
+}
+
+
+/*
+ Get name of time zone
+
+ SYNOPSIS
+ get_name()
+
+ DESCRIPTION
+ Since Time_zone_utc is used only internally by SQL's UTC_* functions it
+ is not accessible directly, and hence this function of Time_zone
+ interface is not implemented for this class and should not be called.
+
+ RETURN VALUE
+ 0
+*/
+const String *
+Time_zone_utc::get_name() const
+{
+ /* Should be never called */
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+
+/*
+ Instance of this class represents some time zone which is
+ described in mysql.time_zone family of tables.
+*/
+class Time_zone_db : public Time_zone
+{
+public:
+ Time_zone_db(TIME_ZONE_INFO *tz_info_arg, const String * tz_name_arg);
+ virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const;
+ virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
+ virtual const String * get_name() const;
+private:
+ TIME_ZONE_INFO *tz_info;
+ const String *tz_name;
+};
+
+
+/*
+ Initializes object representing time zone described by mysql.time_zone
+ tables.
+
+ SYNOPSIS
+ Time_zone_db()
+ tz_info_arg - pointer to TIME_ZONE_INFO structure which is filled
+ according to db or other time zone description
+ (for example by my_tz_init()).
+ Several Time_zone_db instances can share one
+ TIME_ZONE_INFO structure.
+ tz_name_arg - name of time zone.
+*/
+Time_zone_db::Time_zone_db(TIME_ZONE_INFO *tz_info_arg,
+ const String *tz_name_arg):
+ tz_info(tz_info_arg), tz_name(tz_name_arg)
+{
+}
+
+
+/*
+ Converts local time in time zone described from TIME
+ representation to its my_time_t representation.
+
+ SYNOPSIS
+ TIME_to_gmt_sec()
+ t - pointer to MYSQL_TIME structure with local time
+ in broken-down representation.
+ error_code - 0, if the conversion was successful;
+ ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
+ which is out of TIMESTAMP range;
+ ER_WARN_INVALID_TIMESTAMP, if t represents value which
+ doesn't exists (falls into the spring time-gap).
+
+ DESCRIPTION
+ Please see ::TIME_to_gmt_sec for function description and
+ parameter restrictions.
+
+ RETURN VALUE
+ Corresponding my_time_t value or 0 in case of error
+*/
+my_time_t
+Time_zone_db::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
+{
+ return ::TIME_to_gmt_sec(t, tz_info, error_code);
+}
+
+
+/*
+ Converts time from UTC seconds since Epoch (my_time_t) representation
+ to local time zone described in broken-down representation.
+
+ SYNOPSIS
+ gmt_sec_to_TIME()
+ tmp - pointer to MYSQL_TIME structure to fill-in
+ t - my_time_t value to be converted
+*/
+void
+Time_zone_db::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
+{
+ ::gmt_sec_to_TIME(tmp, t, tz_info);
+ adjust_leap_second(tmp);
+}
+
+
+/*
+ Get name of time zone
+
+ SYNOPSIS
+ get_name()
+
+ RETURN VALUE
+ Name of time zone as ASCIIZ-string
+*/
+const String *
+Time_zone_db::get_name() const
+{
+ return tz_name;
+}
+
+
+/*
+ Instance of this class represents time zone which
+ was specified as offset from UTC.
+*/
+class Time_zone_offset : public Time_zone
+{
+public:
+ Time_zone_offset(long tz_offset_arg);
+ virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
+ uint *error_code) const;
+ virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
+ virtual const String * get_name() const;
+ /*
+ This have to be public because we want to be able to access it from
+ my_offset_tzs_get_key() function
+ */
+ long offset;
+private:
+ /* Extra reserve because of snprintf */
+ char name_buff[7+16];
+ String name;
+};
+
+
+/*
+ Initializes object representing time zone described by its offset from UTC.
+
+ SYNOPSIS
+ Time_zone_offset()
+ tz_offset_arg - offset from UTC in seconds.
+ Positive for direction to east.
+*/
+Time_zone_offset::Time_zone_offset(long tz_offset_arg):
+ offset(tz_offset_arg)
+{
+ uint hours= abs((int)(offset / SECS_PER_HOUR));
+ uint minutes= abs((int)(offset % SECS_PER_HOUR / SECS_PER_MIN));
+ ulong length= my_snprintf(name_buff, sizeof(name_buff), "%s%02d:%02d",
+ (offset>=0) ? "+" : "-", hours, minutes);
+ name.set(name_buff, length, &my_charset_latin1);
+}
+
+
+/*
+ Converts local time in time zone described as offset from UTC
+ from MYSQL_TIME representation to its my_time_t representation.
+
+ SYNOPSIS
+ TIME_to_gmt_sec()
+ t - pointer to MYSQL_TIME structure with local time
+ in broken-down representation.
+ error_code - 0, if the conversion was successful;
+ ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
+ which is out of TIMESTAMP range;
+ ER_WARN_INVALID_TIMESTAMP, if t represents value which
+ doesn't exists (falls into the spring time-gap).
+
+ RETURN VALUE
+ Corresponding my_time_t value or 0 in case of error.
+*/
+
+my_time_t
+Time_zone_offset::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
+{
+ my_time_t local_t;
+ int shift= 0;
+
+ /*
+ Check timestamp range.we have to do this as calling function relies on
+ us to make all validation checks here.
+ */
+ if (!validate_timestamp_range(t))
+ {
+ *error_code= ER_WARN_DATA_OUT_OF_RANGE;
+ return 0;
+ }
+ *error_code= 0;
+
+ /*
+ Do a temporary shift of the boundary dates to avoid
+ overflow of my_time_t if the time value is near it's
+ maximum range
+ */
+ if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && t->day > 4)
+ shift= 2;
+
+ local_t= sec_since_epoch(t->year, t->month, (t->day - shift),
+ t->hour, t->minute, t->second) -
+ offset;
+
+ if (shift)
+ {
+ /* Add back the shifted time */
+ local_t+= shift * SECS_PER_DAY;
+ }
+
+ if (local_t >= TIMESTAMP_MIN_VALUE && local_t <= TIMESTAMP_MAX_VALUE)
+ return local_t;
+
+ /* range error*/
+ *error_code= ER_WARN_DATA_OUT_OF_RANGE;
+ return 0;
+}
+
+
+/*
+ Converts time from UTC seconds since Epoch (my_time_t) representation
+ to local time zone described as offset from UTC and in broken-down
+ representation.
+
+ SYNOPSIS
+ gmt_sec_to_TIME()
+ tmp - pointer to MYSQL_TIME structure to fill-in
+ t - my_time_t value to be converted
+*/
+void
+Time_zone_offset::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
+{
+ sec_to_TIME(tmp, t, offset);
+}
+
+
+/*
+ Get name of time zone
+
+ SYNOPSIS
+ get_name()
+
+ RETURN VALUE
+ Name of time zone as pointer to String object
+*/
+const String *
+Time_zone_offset::get_name() const
+{
+ return &name;
+}
+
+
+static Time_zone_utc tz_UTC;
+static Time_zone_system tz_SYSTEM;
+static Time_zone_offset tz_OFFSET0(0);
+
+Time_zone *my_tz_OFFSET0= &tz_OFFSET0;
+Time_zone *my_tz_UTC= &tz_UTC;
+Time_zone *my_tz_SYSTEM= &tz_SYSTEM;
+
+static HASH tz_names;
+static HASH offset_tzs;
+static MEM_ROOT tz_storage;
+
+/*
+ These mutex protects offset_tzs and tz_storage.
+ These protection needed only when we are trying to set
+ time zone which is specified as offset, and searching for existing
+ time zone in offset_tzs or creating if it didn't existed before in
+ tz_storage. So contention is low.
+*/
+static mysql_mutex_t tz_LOCK;
+static bool tz_inited= 0;
+
+/*
+ This two static variables are inteded for holding info about leap seconds
+ shared by all time zones.
+*/
+static uint tz_leapcnt= 0;
+static LS_INFO *tz_lsis= 0;
+
+/*
+ Shows whenever we have found time zone tables during start-up.
+ Used for avoiding of putting those tables to global table list
+ for queries that use time zone info.
+*/
+static bool time_zone_tables_exist= 1;
+
+
+/*
+ Names of tables (with their lengths) that are needed
+ for dynamical loading of time zone descriptions.
+*/
+
+static const LEX_STRING tz_tables_names[MY_TZ_TABLES_COUNT]=
+{
+ { C_STRING_WITH_LEN("time_zone_name")},
+ { C_STRING_WITH_LEN("time_zone")},
+ { C_STRING_WITH_LEN("time_zone_transition_type")},
+ { C_STRING_WITH_LEN("time_zone_transition")}
+};
+
+/* Name of database to which those tables belong. */
+
+static const LEX_STRING tz_tables_db_name= { C_STRING_WITH_LEN("mysql")};
+
+
+class Tz_names_entry: public Sql_alloc
+{
+public:
+ String name;
+ Time_zone *tz;
+};
+
+
+/*
+ We are going to call both of these functions from C code so
+ they should obey C calling conventions.
+*/
+
+extern "C" uchar *
+my_tz_names_get_key(Tz_names_entry *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= entry->name.length();
+ return (uchar*) entry->name.ptr();
+}
+
+extern "C" uchar *
+my_offset_tzs_get_key(Time_zone_offset *entry,
+ size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= sizeof(long);
+ return (uchar*) &entry->offset;
+}
+
+
+/*
+ Prepare table list with time zone related tables from preallocated array.
+
+ SYNOPSIS
+ tz_init_table_list()
+ tz_tabs - pointer to preallocated array of MY_TZ_TABLES_COUNT
+ TABLE_LIST objects
+
+ DESCRIPTION
+ This function prepares list of TABLE_LIST objects which can be used
+ for opening of time zone tables from preallocated array.
+*/
+
+static void
+tz_init_table_list(TABLE_LIST *tz_tabs)
+{
+ bzero(tz_tabs, sizeof(TABLE_LIST) * MY_TZ_TABLES_COUNT);
+
+ for (int i= 0; i < MY_TZ_TABLES_COUNT; i++)
+ {
+ tz_tabs[i].alias= tz_tabs[i].table_name= tz_tables_names[i].str;
+ tz_tabs[i].table_name_length= tz_tables_names[i].length;
+ tz_tabs[i].db= tz_tables_db_name.str;
+ tz_tabs[i].db_length= tz_tables_db_name.length;
+ tz_tabs[i].lock_type= TL_READ;
+
+ if (i != MY_TZ_TABLES_COUNT - 1)
+ tz_tabs[i].next_global= tz_tabs[i].next_local= &tz_tabs[i+1];
+ if (i != 0)
+ tz_tabs[i].prev_global= &tz_tabs[i-1].next_global;
+ }
+}
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_tz_LOCK;
+
+static PSI_mutex_info all_tz_mutexes[]=
+{
+ { & key_tz_LOCK, "tz_LOCK", PSI_FLAG_GLOBAL}
+};
+
+static void init_tz_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_tz_mutexes);
+ PSI_server->register_mutex(category, all_tz_mutexes, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+
+/*
+ Initialize time zone support infrastructure.
+
+ SYNOPSIS
+ my_tz_init()
+ thd - current thread object
+ default_tzname - default time zone or 0 if none.
+ bootstrap - indicates whenever we are in bootstrap mode
+
+ DESCRIPTION
+ This function will init memory structures needed for time zone support,
+ it will register mandatory SYSTEM time zone in them. It will try to open
+ mysql.time_zone* tables and load information about default time zone and
+ information which further will be shared among all time zones loaded.
+ If system tables with time zone descriptions don't exist it won't fail
+ (unless default_tzname is time zone from tables). If bootstrap parameter
+ is true then this routine assumes that we are in bootstrap mode and won't
+ load time zone descriptions unless someone specifies default time zone
+ which is supposedly stored in those tables.
+ It'll also set default time zone if it is specified.
+
+ RETURN VALUES
+ 0 - ok
+ 1 - Error
+*/
+my_bool
+my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap)
+{
+ THD *thd;
+ TABLE_LIST tz_tables[1+MY_TZ_TABLES_COUNT];
+ TABLE *table;
+ Tz_names_entry *tmp_tzname;
+ my_bool return_val= 1;
+ char db[]= "mysql";
+ int res;
+ DBUG_ENTER("my_tz_init");
+
+#ifdef HAVE_PSI_INTERFACE
+ init_tz_psi_keys();
+#endif
+
+ /*
+ To be able to run this from boot, we allocate a temporary THD
+ */
+ if (!(thd= new THD))
+ DBUG_RETURN(1);
+ thd->thread_stack= (char*) &thd;
+ thd->store_globals();
+
+ /* Init all memory structures that require explicit destruction */
+ if (my_hash_init(&tz_names, &my_charset_latin1, 20,
+ 0, 0, (my_hash_get_key) my_tz_names_get_key, 0, 0))
+ {
+ sql_print_error("Fatal error: OOM while initializing time zones");
+ goto end;
+ }
+ if (my_hash_init(&offset_tzs, &my_charset_latin1, 26, 0, 0,
+ (my_hash_get_key)my_offset_tzs_get_key, 0, 0))
+ {
+ sql_print_error("Fatal error: OOM while initializing time zones");
+ my_hash_free(&tz_names);
+ goto end;
+ }
+ init_sql_alloc(&tz_storage, 32 * 1024, 0, MYF(0));
+ mysql_mutex_init(key_tz_LOCK, &tz_LOCK, MY_MUTEX_INIT_FAST);
+ tz_inited= 1;
+
+ /* Add 'SYSTEM' time zone to tz_names hash */
+ if (!(tmp_tzname= new (&tz_storage) Tz_names_entry()))
+ {
+ sql_print_error("Fatal error: OOM while initializing time zones");
+ goto end_with_cleanup;
+ }
+ tmp_tzname->name.set(STRING_WITH_LEN("SYSTEM"), &my_charset_latin1);
+ tmp_tzname->tz= my_tz_SYSTEM;
+ if (my_hash_insert(&tz_names, (const uchar *)tmp_tzname))
+ {
+ sql_print_error("Fatal error: OOM while initializing time zones");
+ goto end_with_cleanup;
+ }
+
+ if (bootstrap)
+ {
+ /* If we are in bootstrap mode we should not load time zone tables */
+ return_val= time_zone_tables_exist= 0;
+ goto end_with_setting_default_tz;
+ }
+
+ /*
+ After this point all memory structures are inited and we even can live
+ without time zone description tables. Now try to load information about
+ leap seconds shared by all time zones.
+ */
+
+ thd->set_db(db, sizeof(db)-1);
+ bzero((char*) &tz_tables[0], sizeof(TABLE_LIST));
+ tz_tables[0].alias= tz_tables[0].table_name=
+ (char*)"time_zone_leap_second";
+ tz_tables[0].table_name_length= 21;
+ tz_tables[0].db= db;
+ tz_tables[0].db_length= sizeof(db)-1;
+ tz_tables[0].lock_type= TL_READ;
+
+ tz_init_table_list(tz_tables+1);
+ tz_tables[0].next_global= tz_tables[0].next_local= &tz_tables[1];
+ tz_tables[1].prev_global= &tz_tables[0].next_global;
+ init_mdl_requests(tz_tables);
+
+ /*
+ We need to open only mysql.time_zone_leap_second, but we try to
+ open all time zone tables to see if they exist.
+ */
+ if (open_and_lock_tables(thd, tz_tables, FALSE,
+ MYSQL_OPEN_IGNORE_FLUSH | MYSQL_LOCK_IGNORE_TIMEOUT))
+ {
+ sql_print_warning("Can't open and lock time zone table: %s "
+ "trying to live without them",
+ thd->get_stmt_da()->message());
+ /* We will try emulate that everything is ok */
+ return_val= time_zone_tables_exist= 0;
+ goto end_with_setting_default_tz;
+ }
+
+ for (TABLE_LIST *tl= tz_tables; tl; tl= tl->next_global)
+ {
+ tl->table->use_all_columns();
+ /* Force close at the end of the function to free memory. */
+ tl->table->m_needs_reopen= TRUE;
+ }
+
+ /*
+ Now we are going to load leap seconds descriptions that are shared
+ between all time zones that use them. We are using index for getting
+ records in proper order. Since we share the same MEM_ROOT between
+ all time zones we just allocate enough memory for it first.
+ */
+ if (!(tz_lsis= (LS_INFO*) alloc_root(&tz_storage,
+ sizeof(LS_INFO) * TZ_MAX_LEAPS)))
+ {
+ sql_print_error("Fatal error: Out of memory while loading "
+ "mysql.time_zone_leap_second table");
+ goto end_with_close;
+ }
+
+ table= tz_tables[0].table;
+
+ if (table->file->ha_index_init(0, 1))
+ goto end_with_close;
+
+ table->use_all_columns();
+ tz_leapcnt= 0;
+
+ res= table->file->ha_index_first(table->record[0]);
+
+ while (!res)
+ {
+ if (tz_leapcnt + 1 > TZ_MAX_LEAPS)
+ {
+ sql_print_error("Fatal error: While loading mysql.time_zone_leap_second"
+ " table: too much leaps");
+ table->file->ha_index_end();
+ goto end_with_close;
+ }
+
+ tz_lsis[tz_leapcnt].ls_trans= (my_time_t)table->field[0]->val_int();
+ tz_lsis[tz_leapcnt].ls_corr= (long)table->field[1]->val_int();
+
+ tz_leapcnt++;
+
+ DBUG_PRINT("info",
+ ("time_zone_leap_second table: tz_leapcnt: %u tt_time: %lu offset: %ld",
+ tz_leapcnt, (ulong) tz_lsis[tz_leapcnt-1].ls_trans,
+ tz_lsis[tz_leapcnt-1].ls_corr));
+
+ res= table->file->ha_index_next(table->record[0]);
+ }
+
+ (void)table->file->ha_index_end();
+
+ if (res != HA_ERR_END_OF_FILE)
+ {
+ sql_print_error("Fatal error: Error while loading "
+ "mysql.time_zone_leap_second table");
+ goto end_with_close;
+ }
+
+ /*
+ Loading of info about leap seconds succeeded
+ */
+
+ return_val= 0;
+
+
+end_with_setting_default_tz:
+ /* If we have default time zone try to load it */
+ if (default_tzname)
+ {
+ String tmp_tzname2(default_tzname, &my_charset_latin1);
+ /*
+ Time zone tables may be open here, and my_tz_find() may open
+ most of them once more, but this is OK for system tables open
+ for READ.
+ */
+ if (!(global_system_variables.time_zone= my_tz_find(thd, &tmp_tzname2)))
+ {
+ sql_print_error("Fatal error: Illegal or unknown default time zone '%s'",
+ default_tzname);
+ return_val= 1;
+ }
+ }
+
+end_with_close:
+ if (time_zone_tables_exist)
+ close_mysql_tables(thd);
+
+end_with_cleanup:
+
+ /* if there were error free time zone describing structs */
+ if (return_val)
+ my_tz_free();
+end:
+ delete thd;
+ if (org_thd)
+ org_thd->store_globals(); /* purecov: inspected */
+ else
+ {
+ /* Remember that we don't have a THD */
+ set_current_thd(0);
+ my_pthread_setspecific_ptr(THR_MALLOC, 0);
+ }
+
+ default_tz= default_tz_name ? global_system_variables.time_zone
+ : my_tz_SYSTEM;
+
+ DBUG_RETURN(return_val);
+}
+
+
+/*
+ Free resources used by time zone support infrastructure.
+
+ SYNOPSIS
+ my_tz_free()
+*/
+
+void my_tz_free()
+{
+ if (tz_inited)
+ {
+ tz_inited= 0;
+ mysql_mutex_destroy(&tz_LOCK);
+ my_hash_free(&offset_tzs);
+ my_hash_free(&tz_names);
+ free_root(&tz_storage, MYF(0));
+ }
+}
+
+
+/*
+ Load time zone description from system tables.
+
+ SYNOPSIS
+ tz_load_from_open_tables()
+ tz_name - name of time zone that should be loaded.
+ tz_tables - list of tables from which time zone description
+ should be loaded
+
+ DESCRIPTION
+ This function will try to load information about time zone specified
+ from the list of the already opened and locked tables (first table in
+ tz_tables should be time_zone_name, next time_zone, then
+ time_zone_transition_type and time_zone_transition should be last).
+ It will also update information in hash used for time zones lookup.
+
+ RETURN VALUES
+ Returns pointer to newly created Time_zone object or 0 in case of error.
+
+*/
+
+static Time_zone*
+tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables)
+{
+ TABLE *table= 0;
+ TIME_ZONE_INFO *tz_info= NULL;
+ Tz_names_entry *tmp_tzname;
+ Time_zone *return_val= 0;
+ int res;
+ uint tzid, ttid;
+ my_time_t ttime;
+ char buff[MAX_FIELD_WIDTH];
+ uchar keybuff[32];
+ Field *field;
+ String abbr(buff, sizeof(buff), &my_charset_latin1);
+ char *alloc_buff= NULL;
+ char *tz_name_buff= NULL;
+ /*
+ Temporary arrays that are used for loading of data for filling
+ TIME_ZONE_INFO structure
+ */
+ my_time_t ats[TZ_MAX_TIMES];
+ uchar types[TZ_MAX_TIMES];
+ TRAN_TYPE_INFO ttis[TZ_MAX_TYPES];
+#ifdef ABBR_ARE_USED
+ char chars[MY_MAX(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))];
+#endif
+ /*
+ Used as a temporary tz_info until we decide that we actually want to
+ allocate and keep the tz info and tz name in tz_storage.
+ */
+ TIME_ZONE_INFO tmp_tz_info;
+ memset(&tmp_tz_info, 0, sizeof(TIME_ZONE_INFO));
+
+ DBUG_ENTER("tz_load_from_open_tables");
+
+ /*
+ Let us find out time zone id by its name (there is only one index
+ and it is specifically for this purpose).
+ */
+ table= tz_tables->table;
+ tz_tables= tz_tables->next_local;
+ table->field[0]->store(tz_name->ptr(), tz_name->length(),
+ &my_charset_latin1);
+ if (table->file->ha_index_init(0, 1))
+ goto end;
+
+ if (table->file->ha_index_read_map(table->record[0], table->field[0]->ptr,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT))
+ {
+#ifdef EXTRA_DEBUG
+ /*
+ Most probably user has mistyped time zone name, so no need to bark here
+ unless we need it for debugging.
+ */
+ sql_print_error("Can't find description of time zone '%.*s'",
+ tz_name->length(), tz_name->ptr());
+#endif
+ goto end;
+ }
+
+ tzid= (uint)table->field[1]->val_int();
+
+ (void)table->file->ha_index_end();
+
+ /*
+ Now we need to lookup record in mysql.time_zone table in order to
+ understand whenever this timezone uses leap seconds (again we are
+ using the only index in this table).
+ */
+ table= tz_tables->table;
+ tz_tables= tz_tables->next_local;
+ field= table->field[0];
+ field->store((longlong) tzid, TRUE);
+ DBUG_ASSERT(field->key_length() <= sizeof(keybuff));
+ field->get_key_image(keybuff,
+ MY_MIN(field->key_length(), sizeof(keybuff)),
+ Field::itRAW);
+ if (table->file->ha_index_init(0, 1))
+ goto end;
+
+ if (table->file->ha_index_read_map(table->record[0], keybuff,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT))
+ {
+ sql_print_error("Can't find description of time zone '%u'", tzid);
+ goto end;
+ }
+
+ /* If Uses_leap_seconds == 'Y' */
+ if (table->field[1]->val_int() == 1)
+ {
+ tmp_tz_info.leapcnt= tz_leapcnt;
+ tmp_tz_info.lsis= tz_lsis;
+ }
+
+ (void)table->file->ha_index_end();
+
+ /*
+ Now we will iterate through records for out time zone in
+ mysql.time_zone_transition_type table. Because we want records
+ only for our time zone guess what are we doing?
+ Right - using special index.
+ */
+ table= tz_tables->table;
+ tz_tables= tz_tables->next_local;
+ field= table->field[0];
+ field->store((longlong) tzid, TRUE);
+ DBUG_ASSERT(field->key_length() <= sizeof(keybuff));
+ field->get_key_image(keybuff,
+ MY_MIN(field->key_length(), sizeof(keybuff)),
+ Field::itRAW);
+ if (table->file->ha_index_init(0, 1))
+ goto end;
+
+ res= table->file->ha_index_read_map(table->record[0], keybuff,
+ (key_part_map)1, HA_READ_KEY_EXACT);
+ while (!res)
+ {
+ ttid= (uint)table->field[1]->val_int();
+
+ if (ttid >= TZ_MAX_TYPES)
+ {
+ sql_print_error("Error while loading time zone description from "
+ "mysql.time_zone_transition_type table: too big "
+ "transition type id");
+ goto end;
+ }
+
+ ttis[ttid].tt_gmtoff= (long)table->field[2]->val_int();
+ ttis[ttid].tt_isdst= (table->field[3]->val_int() > 0);
+
+#ifdef ABBR_ARE_USED
+ // FIXME should we do something with duplicates here ?
+ table->field[4]->val_str(&abbr, &abbr);
+ if (tmp_tz_info.charcnt + abbr.length() + 1 > sizeof(chars))
+ {
+ sql_print_error("Error while loading time zone description from "
+ "mysql.time_zone_transition_type table: not enough "
+ "room for abbreviations");
+ goto end;
+ }
+ ttis[ttid].tt_abbrind= tmp_tz_info.charcnt;
+ memcpy(chars + tmp_tz_info.charcnt, abbr.ptr(), abbr.length());
+ tmp_tz_info.charcnt+= abbr.length();
+ chars[tmp_tz_info.charcnt]= 0;
+ tmp_tz_info.charcnt++;
+
+ DBUG_PRINT("info",
+ ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
+ "abbr='%s' tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff,
+ chars + ttis[ttid].tt_abbrind, ttis[ttid].tt_isdst));
+#else
+ DBUG_PRINT("info",
+ ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
+ "tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff, ttis[ttid].tt_isdst));
+#endif
+
+ /* ttid is increasing because we are reading using index */
+ DBUG_ASSERT(ttid >= tmp_tz_info.typecnt);
+
+ tmp_tz_info.typecnt= ttid + 1;
+
+ res= table->file->ha_index_next_same(table->record[0], keybuff, 4);
+ }
+
+ if (res != HA_ERR_END_OF_FILE)
+ {
+ sql_print_error("Error while loading time zone description from "
+ "mysql.time_zone_transition_type table");
+ goto end;
+ }
+
+ (void)table->file->ha_index_end();
+
+
+ /*
+ At last we are doing the same thing for records in
+ mysql.time_zone_transition table. Here we additionally need records
+ in ascending order by index scan also satisfies us.
+ */
+ table= tz_tables->table;
+ table->field[0]->store((longlong) tzid, TRUE);
+ if (table->file->ha_index_init(0, 1))
+ goto end;
+
+ res= table->file->ha_index_read_map(table->record[0], keybuff,
+ (key_part_map)1, HA_READ_KEY_EXACT);
+ while (!res)
+ {
+ ttime= (my_time_t)table->field[1]->val_int();
+ ttid= (uint)table->field[2]->val_int();
+
+ if (tmp_tz_info.timecnt + 1 > TZ_MAX_TIMES)
+ {
+ sql_print_error("Error while loading time zone description from "
+ "mysql.time_zone_transition table: "
+ "too much transitions");
+ goto end;
+ }
+ if (ttid + 1 > tmp_tz_info.typecnt)
+ {
+ sql_print_error("Error while loading time zone description from "
+ "mysql.time_zone_transition table: "
+ "bad transition type id");
+ goto end;
+ }
+
+ ats[tmp_tz_info.timecnt]= ttime;
+ types[tmp_tz_info.timecnt]= ttid;
+ tmp_tz_info.timecnt++;
+
+ DBUG_PRINT("info",
+ ("time_zone_transition table: tz_id: %u tt_time: %lu tt_id: %u",
+ tzid, (ulong) ttime, ttid));
+
+ res= table->file->ha_index_next_same(table->record[0], keybuff, 4);
+ }
+
+ /*
+ We have to allow HA_ERR_KEY_NOT_FOUND because some time zones
+ for example UTC have no transitons.
+ */
+ if (res != HA_ERR_END_OF_FILE && res != HA_ERR_KEY_NOT_FOUND)
+ {
+ sql_print_error("Error while loading time zone description from "
+ "mysql.time_zone_transition table");
+ goto end;
+ }
+
+ (void)table->file->ha_index_end();
+ table= 0;
+
+ /*
+ Let us check how correct our time zone description is. We don't check for
+ tz->timecnt < 1 since it is ok for GMT.
+ */
+ if (tmp_tz_info.typecnt < 1)
+ {
+ sql_print_error("loading time zone without transition types");
+ goto end;
+ }
+
+ /* Allocate memory for the timezone info and timezone name in tz_storage. */
+ if (!(alloc_buff= (char*) alloc_root(&tz_storage, sizeof(TIME_ZONE_INFO) +
+ tz_name->length() + 1)))
+ {
+ sql_print_error("Out of memory while loading time zone description");
+ return 0;
+ }
+
+ /* Move the temporary tz_info into the allocated area */
+ tz_info= (TIME_ZONE_INFO *)alloc_buff;
+ memcpy(tz_info, &tmp_tz_info, sizeof(TIME_ZONE_INFO));
+ tz_name_buff= alloc_buff + sizeof(TIME_ZONE_INFO);
+ /*
+ By writing zero to the end we guarantee that we can call ptr()
+ instead of c_ptr() for time zone name.
+ */
+ strmake(tz_name_buff, tz_name->ptr(), tz_name->length());
+
+ /*
+ Now we will allocate memory and init TIME_ZONE_INFO structure.
+ */
+ if (!(alloc_buff= (char*) alloc_root(&tz_storage,
+ ALIGN_SIZE(sizeof(my_time_t) *
+ tz_info->timecnt) +
+ ALIGN_SIZE(tz_info->timecnt) +
+#ifdef ABBR_ARE_USED
+ ALIGN_SIZE(tz_info->charcnt) +
+#endif
+ sizeof(TRAN_TYPE_INFO) *
+ tz_info->typecnt)))
+ {
+ sql_print_error("Out of memory while loading time zone description");
+ goto end;
+ }
+
+ tz_info->ats= (my_time_t *) alloc_buff;
+ memcpy(tz_info->ats, ats, tz_info->timecnt * sizeof(my_time_t));
+ alloc_buff+= ALIGN_SIZE(sizeof(my_time_t) * tz_info->timecnt);
+ tz_info->types= (uchar *)alloc_buff;
+ memcpy(tz_info->types, types, tz_info->timecnt);
+ alloc_buff+= ALIGN_SIZE(tz_info->timecnt);
+#ifdef ABBR_ARE_USED
+ tz_info->chars= alloc_buff;
+ memcpy(tz_info->chars, chars, tz_info->charcnt);
+ alloc_buff+= ALIGN_SIZE(tz_info->charcnt);
+#endif
+ tz_info->ttis= (TRAN_TYPE_INFO *)alloc_buff;
+ memcpy(tz_info->ttis, ttis, tz_info->typecnt * sizeof(TRAN_TYPE_INFO));
+
+ /* Build reversed map. */
+ if (prepare_tz_info(tz_info, &tz_storage))
+ {
+ sql_print_error("Unable to build mktime map for time zone");
+ goto end;
+ }
+
+
+ if (!(tmp_tzname= new (&tz_storage) Tz_names_entry()) ||
+ !(tmp_tzname->tz= new (&tz_storage) Time_zone_db(tz_info,
+ &(tmp_tzname->name))) ||
+ (tmp_tzname->name.set(tz_name_buff, tz_name->length(),
+ &my_charset_latin1),
+ my_hash_insert(&tz_names, (const uchar *)tmp_tzname)))
+ {
+ sql_print_error("Out of memory while loading time zone");
+ goto end;
+ }
+
+ /*
+ Loading of time zone succeeded
+ */
+ return_val= tmp_tzname->tz;
+
+end:
+
+ if (table && table->file->inited)
+ (void) table->file->ha_index_end();
+
+ DBUG_RETURN(return_val);
+}
+
+
+/*
+ Parse string that specifies time zone as offset from UTC.
+
+ SYNOPSIS
+ str_to_offset()
+ str - pointer to string which contains offset
+ length - length of string
+ offset - out parameter for storing found offset in seconds.
+
+ DESCRIPTION
+ This function parses string which contains time zone offset
+ in form similar to '+10:00' and converts found value to
+ seconds from UTC form (east is positive).
+
+ RETURN VALUE
+ 0 - Ok
+ 1 - String doesn't contain valid time zone offset
+*/
+my_bool
+str_to_offset(const char *str, uint length, long *offset)
+{
+ const char *end= str + length;
+ my_bool negative;
+ ulong number_tmp;
+ long offset_tmp;
+
+ if (length < 4)
+ return 1;
+
+ if (*str == '+')
+ negative= 0;
+ else if (*str == '-')
+ negative= 1;
+ else
+ return 1;
+ str++;
+
+ number_tmp= 0;
+
+ while (str < end && my_isdigit(&my_charset_latin1, *str))
+ {
+ number_tmp= number_tmp*10 + *str - '0';
+ str++;
+ }
+
+ if (str + 1 >= end || *str != ':')
+ return 1;
+ str++;
+
+ offset_tmp = number_tmp * MINS_PER_HOUR; number_tmp= 0;
+
+ while (str < end && my_isdigit(&my_charset_latin1, *str))
+ {
+ number_tmp= number_tmp * 10 + *str - '0';
+ str++;
+ }
+
+ if (str != end)
+ return 1;
+
+ offset_tmp= (offset_tmp + number_tmp) * SECS_PER_MIN;
+
+ if (negative)
+ offset_tmp= -offset_tmp;
+
+ /*
+ Check if offset is in range prescribed by standard
+ (from -12:59 to 13:00).
+ */
+
+ if (number_tmp > 59 || offset_tmp < -13 * SECS_PER_HOUR + 1 ||
+ offset_tmp > 13 * SECS_PER_HOUR)
+ return 1;
+
+ *offset= offset_tmp;
+
+ return 0;
+}
+
+
+/*
+ Get Time_zone object for specified time zone.
+
+ SYNOPSIS
+ my_tz_find()
+ thd - pointer to thread THD structure
+ name - time zone specification
+
+ DESCRIPTION
+ This function checks if name is one of time zones described in db,
+ predefined SYSTEM time zone or valid time zone specification as
+ offset from UTC (In last case it will create proper Time_zone_offset
+ object if there were not any.). If name is ok it returns corresponding
+ Time_zone object.
+
+ Clients of this function are not responsible for releasing resources
+ occupied by returned Time_zone object so they can just forget pointers
+ to Time_zone object if they are not needed longer.
+
+ Other important property of this function: if some Time_zone found once
+ it will be for sure found later, so this function can also be used for
+ checking if proper Time_zone object exists (and if there will be error
+ it will be reported during first call).
+
+ If name pointer is 0 then this function returns 0 (this allows to pass 0
+ values as parameter without additional external check and this property
+ is used by @@time_zone variable handling code).
+
+ It will perform lookup in system tables (mysql.time_zone*),
+ opening and locking them, and closing afterwards. It won't perform
+ such lookup if no time zone describing tables were found during
+ server start up.
+
+ RETURN VALUE
+ Pointer to corresponding Time_zone object. 0 - in case of bad time zone
+ specification or other error.
+
+*/
+Time_zone *
+my_tz_find(THD *thd, const String *name)
+{
+ Tz_names_entry *tmp_tzname;
+ Time_zone *result_tz= 0;
+ long offset;
+ DBUG_ENTER("my_tz_find");
+ DBUG_PRINT("enter", ("time zone name='%s'",
+ name ? ((String *)name)->c_ptr_safe() : "NULL"));
+
+ if (!name || name->is_empty())
+ DBUG_RETURN(0);
+
+ mysql_mutex_lock(&tz_LOCK);
+
+ if (!str_to_offset(name->ptr(), name->length(), &offset))
+ {
+ if (!(result_tz= (Time_zone_offset *)my_hash_search(&offset_tzs,
+ (const uchar *)&offset,
+ sizeof(long))))
+ {
+ DBUG_PRINT("info", ("Creating new Time_zone_offset object"));
+
+ if (!(result_tz= new (&tz_storage) Time_zone_offset(offset)) ||
+ my_hash_insert(&offset_tzs, (const uchar *) result_tz))
+ {
+ result_tz= 0;
+ sql_print_error("Fatal error: Out of memory "
+ "while setting new time zone");
+ }
+ }
+ }
+ else
+ {
+ result_tz= 0;
+ if ((tmp_tzname= (Tz_names_entry *)my_hash_search(&tz_names,
+ (const uchar *)
+ name->ptr(),
+ name->length())))
+ result_tz= tmp_tzname->tz;
+ else if (time_zone_tables_exist)
+ {
+ TABLE_LIST tz_tables[MY_TZ_TABLES_COUNT];
+ Open_tables_backup open_tables_state_backup;
+
+ tz_init_table_list(tz_tables);
+ init_mdl_requests(tz_tables);
+ if (!open_system_tables_for_read(thd, tz_tables,
+ &open_tables_state_backup))
+ {
+ result_tz= tz_load_from_open_tables(name, tz_tables);
+ close_system_tables(thd, &open_tables_state_backup);
+ }
+ }
+ }
+
+ mysql_mutex_unlock(&tz_LOCK);
+
+ if (result_tz && result_tz != my_tz_SYSTEM && result_tz != my_tz_UTC)
+ status_var_increment(thd->status_var.feature_timezone);
+
+ DBUG_RETURN(result_tz);
+}
+
+
+/**
+ Convert leap seconds into non-leap
+
+ This function will convert the leap seconds added by the OS to
+ non-leap seconds, e.g. 23:59:59, 23:59:60 -> 23:59:59, 00:00:01 ...
+ This check is not checking for years on purpose : although it's not a
+ complete check this way it doesn't require looking (and having installed)
+ the leap seconds table.
+
+ @param[in,out] broken down time structure as filled in by the OS
+*/
+
+void Time_zone::adjust_leap_second(MYSQL_TIME *t)
+{
+ if (t->second == 60 || t->second == 61)
+ t->second= 59;
+}
+
+#endif /* !defined(TESTTIME) && !defined(TZINFO2SQL) */
+
+
+#ifdef TZINFO2SQL
+/*
+ This code belongs to mysql_tzinfo_to_sql converter command line utility.
+ This utility should be used by db admin for populating mysql.time_zone
+ tables.
+*/
+
+/*
+ Print info about time zone described by TIME_ZONE_INFO struct as
+ SQL statements populating mysql.time_zone* tables.
+
+ SYNOPSIS
+ print_tz_as_sql()
+ tz_name - name of time zone
+ sp - structure describing time zone
+*/
+void
+print_tz_as_sql(const char* tz_name, const TIME_ZONE_INFO *sp)
+{
+ uint i;
+
+ /* Here we assume that all time zones have same leap correction tables */
+ printf("INSERT INTO time_zone (Use_leap_seconds) VALUES ('%s');\n",
+ sp->leapcnt ? "Y" : "N");
+ printf("SET @time_zone_id= LAST_INSERT_ID();\n");
+ printf("INSERT INTO time_zone_name (Name, Time_zone_id) VALUES \
+('%s', @time_zone_id);\n", tz_name);
+
+ if (sp->timecnt)
+ {
+ printf("INSERT INTO time_zone_transition \
+(Time_zone_id, Transition_time, Transition_type_id) VALUES\n");
+ for (i= 0; i < sp->timecnt; i++)
+ printf("%s(@time_zone_id, %ld, %u)\n", (i == 0 ? " " : ","), sp->ats[i],
+ (uint)sp->types[i]);
+ printf(";\n");
+ }
+
+ printf("INSERT INTO time_zone_transition_type \
+(Time_zone_id, Transition_type_id, Offset, Is_DST, Abbreviation) VALUES\n");
+
+ for (i= 0; i < sp->typecnt; i++)
+ printf("%s(@time_zone_id, %u, %ld, %d, '%s')\n", (i == 0 ? " " : ","), i,
+ sp->ttis[i].tt_gmtoff, sp->ttis[i].tt_isdst,
+ sp->chars + sp->ttis[i].tt_abbrind);
+ printf(";\n");
+}
+
+
+/*
+ Print info about leap seconds in time zone as SQL statements
+ populating mysql.time_zone_leap_second table.
+
+ SYNOPSIS
+ print_tz_leaps_as_sql()
+ sp - structure describing time zone
+*/
+void
+print_tz_leaps_as_sql(const TIME_ZONE_INFO *sp)
+{
+ uint i;
+
+ /*
+ We are assuming that there are only one list of leap seconds
+ For all timezones.
+ */
+ printf("TRUNCATE TABLE time_zone_leap_second;\n");
+
+ if (sp->leapcnt)
+ {
+ printf("INSERT INTO time_zone_leap_second \
+(Transition_time, Correction) VALUES\n");
+ for (i= 0; i < sp->leapcnt; i++)
+ printf("%s(%ld, %ld)\n", (i == 0 ? " " : ","),
+ sp->lsis[i].ls_trans, sp->lsis[i].ls_corr);
+ printf(";\n");
+ }
+
+ printf("ALTER TABLE time_zone_leap_second ORDER BY Transition_time;\n");
+}
+
+
+/*
+ Some variables used as temporary or as parameters
+ in recursive scan_tz_dir() code.
+*/
+TIME_ZONE_INFO tz_info;
+MEM_ROOT tz_storage;
+char fullname[FN_REFLEN + 1];
+char *root_name_end;
+
+/*
+ known file types that exist in the zoneinfo directory that are safe to
+ silently skip
+*/
+const char *known_extensions[]= {
+ ".tab",
+ NullS
+};
+
+
+/*
+ Recursively scan zoneinfo directory and print all found time zone
+ descriptions as SQL.
+
+ SYNOPSIS
+ scan_tz_dir()
+ name_end - pointer to end of path to directory to be searched.
+ symlink_recursion_level How many symlink directory levels are used
+ verbose >0 if we should print warnings
+
+ DESCRIPTION
+ This auxiliary recursive function also uses several global
+ variables as in parameters and for storing temporary values.
+
+ fullname - path to directory that should be scanned.
+ root_name_end - pointer to place in fullname where part with
+ path to initial directory ends.
+ current_tz_id - last used time zone id
+
+ RETURN VALUE
+ 0 - Ok, 1 - Fatal error
+
+*/
+my_bool
+scan_tz_dir(char * name_end, uint symlink_recursion_level, uint verbose)
+{
+ MY_DIR *cur_dir;
+ char *name_end_tmp;
+ uint i;
+
+ /* Sort directory data, to pass mtr tests on different platforms. */
+ if (!(cur_dir= my_dir(fullname, MYF(MY_WANT_STAT|MY_WANT_SORT))))
+ return 1;
+
+ name_end= strmake(name_end, "/", FN_REFLEN - (name_end - fullname));
+
+ for (i= 0; i < cur_dir->number_of_files; i++)
+ {
+ if (cur_dir->dir_entry[i].name[0] != '.')
+ {
+ name_end_tmp= strmake(name_end, cur_dir->dir_entry[i].name,
+ FN_REFLEN - (name_end - fullname));
+
+ if (MY_S_ISDIR(cur_dir->dir_entry[i].mystat->st_mode))
+ {
+ my_bool is_symlink;
+ if ((is_symlink= my_is_symlink(fullname)) &&
+ symlink_recursion_level > 0)
+ {
+ /*
+ The timezone definition data in some Linux distributions
+ (e.g. the "timezone-data-2013f" package in Gentoo)
+ may have synlimks like:
+ /usr/share/zoneinfo/posix/ -> /usr/share/zoneinfo/,
+ so the same timezone files are available under two names
+ (e.g. "CET" and "posix/CET").
+
+ We allow one level of symlink recursion for backward
+ compatibility with earlier timezone data packages that have
+ duplicate copies of the same timezone files inside the root
+ directory and the "posix" subdirectory (instead of symlinking).
+ This makes "posix/CET" still available, but helps to avoid
+ following such symlinks infinitely:
+ /usr/share/zoneinfo/posix/posix/posix/.../posix/
+ */
+
+ /*
+ This is a normal case and not critical. only print warning if
+ verbose mode is choosen.
+ */
+ if (verbose > 0)
+ {
+ fflush(stdout);
+ fprintf(stderr, "Warning: Skipping directory '%s': "
+ "to avoid infinite symlink recursion.\n", fullname);
+ }
+ continue;
+ }
+ if (scan_tz_dir(name_end_tmp, symlink_recursion_level + is_symlink,
+ verbose))
+ {
+ my_dirend(cur_dir);
+ return 1;
+ }
+ }
+ else if (MY_S_ISREG(cur_dir->dir_entry[i].mystat->st_mode))
+ {
+ init_alloc_root(&tz_storage, 32768, 0, MYF(MY_THREAD_SPECIFIC));
+ if (!tz_load(fullname, &tz_info, &tz_storage))
+ print_tz_as_sql(root_name_end + 1, &tz_info);
+ else
+ {
+ /*
+ Some systems (like debian, opensuse etc) have description
+ files (.tab). We skip these silently if verbose is > 0
+ */
+ const char *current_ext= fn_ext(fullname);
+ my_bool known_ext= 0;
+
+ for (const char **ext= known_extensions ; *ext ; ext++)
+ {
+ if (!strcmp(*ext, current_ext))
+ {
+ known_ext= 1;
+ break;
+ }
+ }
+ if (verbose > 0 || !known_ext)
+ {
+ fflush(stdout);
+ fprintf(stderr,
+ "Warning: Unable to load '%s' as time zone. Skipping it.\n",
+ fullname);
+ }
+ }
+ free_root(&tz_storage, MYF(0));
+ }
+ else
+ {
+ fflush(stdout);
+ fprintf(stderr, "Warning: '%s' is not regular file or directory\n",
+ fullname);
+ }
+ }
+ }
+
+ my_dirend(cur_dir);
+
+ return 0;
+}
+
+
+my_bool opt_leap, opt_verbose;
+
+static const char *load_default_groups[]=
+{ "mysql_tzinfo_to_sql", 0};
+
+static struct my_option my_long_options[] =
+{
+ {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG,
+ 0, 0, 0, 0, 0, 0},
+#ifdef DBUG_OFF
+ {"debug", '#', "This is a non-debug version. Catch this and exit",
+ 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
+#else
+ {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.",
+ 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
+#endif
+ {"leap", 'l', "Print the leap second information from the given time zone file. By convention, when --leap is used the next argument is the timezonefile",
+ &opt_leap, &opt_leap, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"verbose", 'v', "Write non critical warnings",
+ &opt_verbose, &opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"version", 'V', "Output version information and exit.",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+ { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+};
+
+
+C_MODE_START
+static my_bool get_one_option(int optid, const struct my_option *,
+ char *argument);
+C_MODE_END
+
+static void print_version(void)
+{
+ printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname, PROGRAM_VERSION,
+ MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE);
+}
+
+static void print_usage(void)
+{
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, " %s [options] timezonedir\n", my_progname);
+ fprintf(stderr, " %s [options] timezonefile timezonename\n", my_progname);
+ print_defaults("my",load_default_groups);
+ puts("");
+ my_print_help(my_long_options);
+ my_print_variables(my_long_options);
+}
+
+
+static my_bool
+get_one_option(int optid, const struct my_option *opt, char *argument)
+{
+ switch(optid) {
+ case '#':
+#ifndef DBUG_OFF
+ DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysq_tzinfo_to_sql.trace");
+#endif
+ break;
+ case '?':
+ print_version();
+ puts("");
+ print_usage();
+ exit(0);
+ case 'V':
+ print_version();
+ exit(0);
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ char **default_argv;
+ MY_INIT(argv[0]);
+
+ if (load_defaults("my",load_default_groups,&argc,&argv))
+ exit(1);
+
+ default_argv= argv;
+
+ if ((handle_options(&argc, &argv, my_long_options, get_one_option)))
+ exit(1);
+
+ if ((argc != 1 && argc != 2) || (opt_leap && argc != 1))
+ {
+ print_usage();
+ free_defaults(default_argv);
+ return 1;
+ }
+ if (argc == 1 && !opt_leap)
+ {
+ /* Argument is timezonedir */
+
+ root_name_end= strmake_buf(fullname, argv[0]);
+
+ printf("TRUNCATE TABLE time_zone;\n");
+ printf("TRUNCATE TABLE time_zone_name;\n");
+ printf("TRUNCATE TABLE time_zone_transition;\n");
+ printf("TRUNCATE TABLE time_zone_transition_type;\n");
+
+ if (scan_tz_dir(root_name_end, 0, opt_verbose))
+ {
+ fflush(stdout);
+ fprintf(stderr,
+ "There were fatal errors during processing "
+ "of zoneinfo directory '%s'\n", fullname);
+ return 1;
+ }
+
+ printf("ALTER TABLE time_zone_transition "
+ "ORDER BY Time_zone_id, Transition_time;\n");
+ printf("ALTER TABLE time_zone_transition_type "
+ "ORDER BY Time_zone_id, Transition_type_id;\n");
+ }
+ else
+ {
+ /*
+ First argument is timezonefile.
+ The second is timezonename if opt_leap is not given
+ */
+ init_alloc_root(&tz_storage, 32768, 0, MYF(0));
+
+ if (tz_load(argv[0], &tz_info, &tz_storage))
+ {
+ fflush(stdout);
+ fprintf(stderr, "Problems with zoneinfo file '%s'\n", argv[0]);
+ return 1;
+ }
+ if (opt_leap)
+ print_tz_leaps_as_sql(&tz_info);
+ else
+ print_tz_as_sql(argv[1], &tz_info);
+
+ free_root(&tz_storage, MYF(0));
+ }
+
+ free_defaults(default_argv);
+ my_end(0);
+ return 0;
+}
+
+#endif /* defined(TZINFO2SQL) */
+
+
+#ifdef TESTTIME
+
+/*
+ Some simple brute-force test wich allowed to catch a pair of bugs.
+ Also can provide interesting facts about system's time zone support
+ implementation.
+*/
+
+#ifndef CHAR_BIT
+#define CHAR_BIT 8
+#endif
+
+#ifndef TYPE_BIT
+#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
+#endif
+
+#ifndef TYPE_SIGNED
+#define TYPE_SIGNED(type) (((type) -1) < 0)
+#endif
+
+my_bool
+is_equal_TIME_tm(const TIME* time_arg, const struct tm * tm_arg)
+{
+ return (time_arg->year == (uint)tm_arg->tm_year+TM_YEAR_BASE) &&
+ (time_arg->month == (uint)tm_arg->tm_mon+1) &&
+ (time_arg->day == (uint)tm_arg->tm_mday) &&
+ (time_arg->hour == (uint)tm_arg->tm_hour) &&
+ (time_arg->minute == (uint)tm_arg->tm_min) &&
+ (time_arg->second == (uint)tm_arg->tm_sec) &&
+ time_arg->second_part == 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ my_bool localtime_negative;
+ TIME_ZONE_INFO tz_info;
+ struct tm tmp;
+ MYSQL_TIME time_tmp;
+ time_t t, t1, t2;
+ char fullname[FN_REFLEN+1];
+ char *str_end;
+ MEM_ROOT tz_storage;
+
+ MY_INIT(argv[0]);
+
+ init_alloc_root(&tz_storage, 32768, MYF(0));
+
+ /* let us set some well known timezone */
+ setenv("TZ", "MET", 1);
+ tzset();
+
+ /* Some initial time zone related system info */
+ printf("time_t: %s %u bit\n", TYPE_SIGNED(time_t) ? "signed" : "unsigned",
+ (uint)TYPE_BIT(time_t));
+ if (TYPE_SIGNED(time_t))
+ {
+ t= -100;
+ localtime_negative= MY_TEST(localtime_r(&t, &tmp) != 0);
+ printf("localtime_r %s negative params \
+ (time_t=%d is %d-%d-%d %d:%d:%d)\n",
+ (localtime_negative ? "supports" : "doesn't support"), (int)t,
+ TM_YEAR_BASE + tmp.tm_year, tmp.tm_mon + 1, tmp.tm_mday,
+ tmp.tm_hour, tmp.tm_min, tmp.tm_sec);
+
+ printf("mktime %s negative results (%d)\n",
+ (t == mktime(&tmp) ? "doesn't support" : "supports"),
+ (int)mktime(&tmp));
+ }
+
+ tmp.tm_year= 103; tmp.tm_mon= 2; tmp.tm_mday= 30;
+ tmp.tm_hour= 2; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= -1;
+ t= mktime(&tmp);
+ printf("mktime returns %s for spring time gap (%d)\n",
+ (t != (time_t)-1 ? "something" : "error"), (int)t);
+
+ tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1;
+ tmp.tm_hour= 0; tmp.tm_min= 0; tmp.tm_sec= 0; tmp.tm_isdst= 0;
+ t= mktime(&tmp);
+ printf("mktime returns %s for non existing date (%d)\n",
+ (t != (time_t)-1 ? "something" : "error"), (int)t);
+
+ tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1;
+ tmp.tm_hour= 25; tmp.tm_min=0; tmp.tm_sec=0; tmp.tm_isdst=1;
+ t= mktime(&tmp);
+ printf("mktime %s unnormalized input (%d)\n",
+ (t != (time_t)-1 ? "handles" : "doesn't handle"), (int)t);
+
+ tmp.tm_year= 103; tmp.tm_mon= 9; tmp.tm_mday= 26;
+ tmp.tm_hour= 0; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= 1;
+ mktime(&tmp);
+ tmp.tm_hour= 2; tmp.tm_isdst= -1;
+ t= mktime(&tmp);
+ tmp.tm_hour= 4; tmp.tm_isdst= 0;
+ mktime(&tmp);
+ tmp.tm_hour= 2; tmp.tm_isdst= -1;
+ t1= mktime(&tmp);
+ printf("mktime is %s (%d %d)\n",
+ (t == t1 ? "determenistic" : "is non-determenistic"),
+ (int)t, (int)t1);
+
+ /* Let us load time zone description */
+ str_end= strmake_buf(fullname, TZDIR);
+ strmake(str_end, "/MET", FN_REFLEN - (str_end - fullname));
+
+ if (tz_load(fullname, &tz_info, &tz_storage))
+ {
+ printf("Unable to load time zone info from '%s'\n", fullname);
+ free_root(&tz_storage, MYF(0));
+ return 1;
+ }
+
+ printf("Testing our implementation\n");
+
+ if (TYPE_SIGNED(time_t) && localtime_negative)
+ {
+ for (t= -40000; t < 20000; t++)
+ {
+ localtime_r(&t, &tmp);
+ gmt_sec_to_TIME(&time_tmp, (my_time_t)t, &tz_info);
+ if (!is_equal_TIME_tm(&time_tmp, &tmp))
+ {
+ printf("Problem with negative time_t = %d\n", (int)t);
+ free_root(&tz_storage, MYF(0));
+ return 1;
+ }
+ }
+ printf("gmt_sec_to_TIME = localtime for time_t in [-40000,20000) range\n");
+ }
+
+ for (t= 1000000000; t < 1100000000; t+= 13)
+ {
+ localtime_r(&t,&tmp);
+ gmt_sec_to_TIME(&time_tmp, (my_time_t)t, &tz_info);
+
+ if (!is_equal_TIME_tm(&time_tmp, &tmp))
+ {
+ printf("Problem with time_t = %d\n", (int)t);
+ free_root(&tz_storage, MYF(0));
+ return 1;
+ }
+ }
+ printf("gmt_sec_to_TIME = localtime for time_t in [1000000000,1100000000) range\n");
+
+ my_init_time();
+
+ /*
+ Be careful here! my_system_gmt_sec doesn't fully handle unnormalized
+ dates.
+ */
+ for (time_tmp.year= 1980; time_tmp.year < 2010; time_tmp.year++)
+ {
+ for (time_tmp.month= 1; time_tmp.month < 13; time_tmp.month++)
+ {
+ for (time_tmp.day= 1;
+ time_tmp.day < mon_lengths[isleap(time_tmp.year)][time_tmp.month-1];
+ time_tmp.day++)
+ {
+ for (time_tmp.hour= 0; time_tmp.hour < 24; time_tmp.hour++)
+ {
+ for (time_tmp.minute= 0; time_tmp.minute < 60; time_tmp.minute+= 5)
+ {
+ for (time_tmp.second=0; time_tmp.second<60; time_tmp.second+=25)
+ {
+ long not_used;
+ uint not_used_2;
+ t= (time_t)my_system_gmt_sec(&time_tmp, &not_used, &not_used_2);
+ t1= (time_t)TIME_to_gmt_sec(&time_tmp, &tz_info, &not_used_2);
+ if (t != t1)
+ {
+ /*
+ We need special handling during autumn since my_system_gmt_sec
+ prefers greater time_t values (in MET) for ambiguity.
+ And BTW that is a bug which should be fixed !!!
+ */
+ tmp.tm_year= time_tmp.year - TM_YEAR_BASE;
+ tmp.tm_mon= time_tmp.month - 1;
+ tmp.tm_mday= time_tmp.day;
+ tmp.tm_hour= time_tmp.hour;
+ tmp.tm_min= time_tmp.minute;
+ tmp.tm_sec= time_tmp.second;
+ tmp.tm_isdst= 1;
+
+ t2= mktime(&tmp);
+
+ if (t1 == t2)
+ continue;
+
+ printf("Problem: %u/%u/%u %u:%u:%u with times t=%d, t1=%d\n",
+ time_tmp.year, time_tmp.month, time_tmp.day,
+ time_tmp.hour, time_tmp.minute, time_tmp.second,
+ (int)t,(int)t1);
+
+ free_root(&tz_storage, MYF(0));
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ printf("TIME_to_gmt_sec = my_system_gmt_sec for test range\n");
+
+ free_root(&tz_storage, MYF(0));
+ return 0;
+}
+
+#endif /* defined(TESTTIME) */
diff --git a/sql/tztime.h b/sql/tztime.h
new file mode 100644
index 00000000000..eb7d85c48b2
--- /dev/null
+++ b/sql/tztime.h
@@ -0,0 +1,94 @@
+#ifndef TZTIME_INCLUDED
+#define TZTIME_INCLUDED
+
+/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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 */
+
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class interface */
+#endif
+
+#include "my_time.h" /* my_time_t */
+#include "mysql_time.h" /* MYSQL_TIME */
+#include "sql_list.h" /* Sql_alloc */
+#include "sql_string.h" /* String */
+
+class THD;
+
+#if !defined(TESTTIME) && !defined(TZINFO2SQL)
+
+class THD;
+
+/**
+ This class represents abstract time zone and provides
+ basic interface for MYSQL_TIME <-> my_time_t conversion.
+ Actual time zones which are specified by DB, or via offset
+ or use system functions are its descendants.
+*/
+class Time_zone: public Sql_alloc
+{
+public:
+ Time_zone() {} /* Remove gcc warning */
+ /**
+ Converts local time in broken down MYSQL_TIME representation to
+ my_time_t (UTC seconds since Epoch) represenation.
+ Returns 0 in case of error. May set error_code to ER_WARN_DATA_OUT_OF_RANGE
+ or ER_WARN_INVALID_TIMESTAMP, see TIME_to_timestamp())
+ */
+ virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
+ uint *error_code) const = 0;
+ /**
+ Converts time in my_time_t representation to local time in
+ broken down MYSQL_TIME representation.
+ */
+ virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const = 0;
+ /**
+ Because of constness of String returned by get_name() time zone name
+ have to be already zeroended to be able to use String::ptr() instead
+ of c_ptr().
+ */
+ virtual const String * get_name() const = 0;
+
+ /**
+ We need this only for surpressing warnings, objects of this type are
+ allocated on MEM_ROOT and should not require destruction.
+ */
+ virtual ~Time_zone() {};
+
+protected:
+ static inline void adjust_leap_second(MYSQL_TIME *t);
+};
+
+extern Time_zone * my_tz_UTC;
+extern Time_zone * my_tz_SYSTEM;
+extern Time_zone * my_tz_OFFSET0;
+extern Time_zone * my_tz_find(THD *thd, const String *name);
+extern my_bool my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap);
+extern void my_tz_free();
+extern my_time_t sec_since_epoch_TIME(MYSQL_TIME *t);
+
+/**
+ Number of elements in table list produced by my_tz_get_table_list()
+ (this table list contains tables which are needed for dynamical loading
+ of time zone descriptions). Actually it is imlementation detail that
+ should not be used anywhere outside of tztime.h and tztime.cc.
+*/
+
+static const int MY_TZ_TABLES_COUNT= 4;
+
+
+#endif /* !defined(TESTTIME) && !defined(TZINFO2SQL) */
+#endif /* TZTIME_INCLUDED */
diff --git a/sql/udf_example.c b/sql/udf_example.c
new file mode 100644
index 00000000000..a48801d1c4a
--- /dev/null
+++ b/sql/udf_example.c
@@ -0,0 +1,1152 @@
+/*
+ Copyright (c) 2000, 2014, Oracle and/or its affiliates.
+
+ 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 */
+
+/*
+** example file of UDF (user definable functions) that are dynamicly loaded
+** into the standard mysqld core.
+**
+** The functions name, type and shared library is saved in the new system
+** table 'func'. To be able to create new functions one must have write
+** privilege for the database 'mysql'. If one starts MySQL with
+** --skip-grant, then UDF initialization will also be skipped.
+**
+** Syntax for the new commands are:
+** create function <function_name> returns {string|real|integer}
+** soname <name_of_shared_library>
+** drop function <function_name>
+**
+** Each defined function may have a xxxx_init function and a xxxx_deinit
+** function. The init function should alloc memory for the function
+** and tell the main function about the max length of the result
+** (for string functions), number of decimals (for double functions) and
+** if the result may be a null value.
+**
+** If a function sets the 'error' argument to 1 the function will not be
+** called anymore and mysqld will return NULL for all calls to this copy
+** of the function.
+**
+** All strings arguments to functions are given as string pointer + length
+** to allow handling of binary data.
+** Remember that all functions must be thread safe. This means that one is not
+** allowed to alloc any global or static variables that changes!
+** If one needs memory one should alloc this in the init function and free
+** this on the __deinit function.
+**
+** Note that the init and __deinit functions are only called once per
+** SQL statement while the value function may be called many times
+**
+** Function 'metaphon' returns a metaphon string of the string argument.
+** This is something like a soundex string, but it's more tuned for English.
+**
+** Function 'myfunc_double' returns summary of codes of all letters
+** of arguments divided by summary length of all its arguments.
+**
+** Function 'myfunc_int' returns summary length of all its arguments.
+**
+** Function 'sequence' returns an sequence starting from a certain number.
+**
+** Function 'myfunc_argument_name' returns name of argument.
+**
+** On the end is a couple of functions that converts hostnames to ip and
+** vice versa.
+**
+** A dynamicly loadable file should be compiled shared.
+** (something like: gcc -shared -o my_func.so myfunc.cc).
+** You can easily get all switches right by doing:
+** cd sql ; make udf_example.o
+** Take the compile line that make writes, remove the '-c' near the end of
+** the line and add -shared -o udf_example.so to the end of the compile line.
+** The resulting library (udf_example.so) should be copied to some dir
+** searched by ld. (/usr/lib ?)
+** If you are using gcc, then you should be able to create the udf_example.so
+** by simply doing 'make udf_example.so'.
+**
+** After the library is made one must notify mysqld about the new
+** functions with the commands:
+**
+** CREATE FUNCTION metaphon RETURNS STRING SONAME "udf_example.so";
+** CREATE FUNCTION myfunc_double RETURNS REAL SONAME "udf_example.so";
+** CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME "udf_example.so";
+** CREATE FUNCTION sequence RETURNS INTEGER SONAME "udf_example.so";
+** CREATE FUNCTION lookup RETURNS STRING SONAME "udf_example.so";
+** CREATE FUNCTION reverse_lookup RETURNS STRING SONAME "udf_example.so";
+** CREATE AGGREGATE FUNCTION avgcost RETURNS REAL SONAME "udf_example.so";
+** CREATE FUNCTION myfunc_argument_name RETURNS STRING SONAME "udf_example.so";
+**
+** After this the functions will work exactly like native MySQL functions.
+** Functions should be created only once.
+**
+** The functions can be deleted by:
+**
+** DROP FUNCTION metaphon;
+** DROP FUNCTION myfunc_double;
+** DROP FUNCTION myfunc_int;
+** DROP FUNCTION lookup;
+** DROP FUNCTION reverse_lookup;
+** DROP FUNCTION avgcost;
+** DROP FUNCTION myfunc_argument_name;
+**
+** The CREATE FUNCTION and DROP FUNCTION update the func@mysql table. All
+** Active function will be reloaded on every restart of server
+** (if --skip-grant-tables is not given)
+**
+** If you ge problems with undefined symbols when loading the shared
+** library, you should verify that mysqld is compiled with the -rdynamic
+** option.
+**
+** If you can't get AGGREGATES to work, check that you have the column
+** 'type' in the mysql.func table. If not, run 'mysql_upgrade'.
+**
+*/
+
+#ifdef STANDARD
+/* STANDARD is defined, don't use any mysql functions */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef __WIN__
+typedef unsigned __int64 ulonglong; /* Microsofts 64 bit types */
+typedef __int64 longlong;
+#else
+typedef unsigned long long ulonglong;
+typedef long long longlong;
+#endif /*__WIN__*/
+#else
+#include <my_global.h>
+#include <my_sys.h>
+#if defined(MYSQL_SERVER)
+#include <m_string.h> /* To get strmov() */
+#else
+/* when compiled as standalone */
+#include <string.h>
+#define strmov(a,b) stpcpy(a,b)
+#define bzero(a,b) memset(a,0,b)
+#endif
+#endif
+#include <mysql.h>
+#include <ctype.h>
+
+#ifdef _WIN32
+/* inet_aton needs winsock library */
+#pragma comment(lib, "ws2_32")
+#endif
+
+#ifdef HAVE_DLOPEN
+
+#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
+static pthread_mutex_t LOCK_hostname;
+#endif
+
+/* These must be right or mysqld will not find the symbol! */
+
+my_bool metaphon_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
+void metaphon_deinit(UDF_INIT *initid);
+char *metaphon(UDF_INIT *initid, UDF_ARGS *args, char *result,
+ unsigned long *length, char *is_null, char *error);
+my_bool myfunc_double_init(UDF_INIT *, UDF_ARGS *args, char *message);
+double myfunc_double(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
+ char *error);
+my_bool myfunc_int_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
+longlong myfunc_int(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
+ char *error);
+my_bool sequence_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
+ void sequence_deinit(UDF_INIT *initid);
+longlong sequence(UDF_INIT *initid, UDF_ARGS *args, char *is_null,
+ char *error);
+my_bool avgcost_init( UDF_INIT* initid, UDF_ARGS* args, char* message );
+void avgcost_deinit( UDF_INIT* initid );
+void avgcost_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
+void avgcost_clear( UDF_INIT* initid, char* is_null, char *error );
+void avgcost_add( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
+double avgcost( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
+my_bool is_const_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
+char *is_const(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long
+ *length, char *is_null, char *error);
+
+
+/*************************************************************************
+** Example of init function
+** Arguments:
+** initid Points to a structure that the init function should fill.
+** This argument is given to all other functions.
+** my_bool maybe_null 1 if function can return NULL
+** Default value is 1 if any of the arguments
+** is declared maybe_null.
+** unsigned int decimals Number of decimals.
+** Default value is max decimals in any of the
+** arguments.
+** unsigned int max_length Length of string result.
+** The default value for integer functions is 21
+** The default value for real functions is 13+
+** default number of decimals.
+** The default value for string functions is
+** the longest string argument.
+** char *ptr; A pointer that the function can use.
+**
+** args Points to a structure which contains:
+** unsigned int arg_count Number of arguments
+** enum Item_result *arg_type Types for each argument.
+** Types are STRING_RESULT, REAL_RESULT
+** and INT_RESULT.
+** char **args Pointer to constant arguments.
+** Contains 0 for not constant argument.
+** unsigned long *lengths; max string length for each argument
+** char *maybe_null Information of which arguments
+** may be NULL
+**
+** message Error message that should be passed to the user on fail.
+** The message buffer is MYSQL_ERRMSG_SIZE big, but one should
+** try to keep the error message less than 80 bytes long!
+**
+** This function should return 1 if something goes wrong. In this case
+** message should contain something useful!
+**************************************************************************/
+
+#define MAXMETAPH 8
+
+my_bool metaphon_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
+{
+ if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
+ {
+ strcpy(message,"Wrong arguments to metaphon; Use the source");
+ return 1;
+ }
+ initid->max_length=MAXMETAPH;
+ return 0;
+}
+
+/****************************************************************************
+** Deinit function. This should free all resources allocated by
+** this function.
+** Arguments:
+** initid Return value from xxxx_init
+****************************************************************************/
+
+
+void metaphon_deinit(UDF_INIT *initid __attribute__((unused)))
+{
+}
+
+/***************************************************************************
+** UDF string function.
+** Arguments:
+** initid Structure filled by xxx_init
+** args The same structure as to xxx_init. This structure
+** contains values for all parameters.
+** Note that the functions MUST check and convert all
+** to the type it wants! Null values are represented by
+** a NULL pointer
+** result Possible buffer to save result. At least 255 byte long.
+** length Pointer to length of the above buffer. In this the function
+** should save the result length
+** is_null If the result is null, one should store 1 here.
+** error If something goes fatally wrong one should store 1 here.
+**
+** This function should return a pointer to the result string.
+** Normally this is 'result' but may also be an alloced string.
+***************************************************************************/
+
+/* Character coding array */
+static char codes[26] = {
+ 1,16,4,16,9,2,4,16,9,2,0,2,2,2,1,4,0,2,4,4,1,0,0,0,8,0
+ /* A B C D E F G H I J K L M N O P Q R S T U V W X Y Z*/
+ };
+
+/*--- Macros to access character coding array -------------*/
+
+#define ISVOWEL(x) (codes[(x) - 'A'] & 1) /* AEIOU */
+
+ /* Following letters are not changed */
+#define NOCHANGE(x) (codes[(x) - 'A'] & 2) /* FJLMNR */
+
+ /* These form diphthongs when preceding H */
+#define AFFECTH(x) (codes[(x) - 'A'] & 4) /* CGPST */
+
+ /* These make C and G soft */
+#define MAKESOFT(x) (codes[(x) - 'A'] & 8) /* EIY */
+
+ /* These prevent GH from becoming F */
+#define NOGHTOF(x) (codes[(x) - 'A'] & 16) /* BDH */
+
+
+char *metaphon(UDF_INIT *initid __attribute__((unused)),
+ UDF_ARGS *args, char *result, unsigned long *length,
+ char *is_null, char *error __attribute__((unused)))
+{
+ const char *word=args->args[0];
+ const char *w_end;
+ char *org_result;
+ char *n, *n_start, *n_end; /* pointers to string */
+ char *metaph_end; /* pointers to end of metaph */
+ char ntrans[32]; /* word with uppercase letters */
+ int KSflag; /* state flag for X to KS */
+
+ if (!word) /* Null argument */
+ {
+ /* The length is expected to be zero when the argument is NULL. */
+ assert(args->lengths[0] == 0);
+ *is_null=1;
+ return 0;
+ }
+
+ w_end=word+args->lengths[0];
+ org_result=result;
+
+ /*--------------------------------------------------------
+ * Copy word to internal buffer, dropping non-alphabetic
+ * characters and converting to uppercase.
+ *-------------------------------------------------------*/
+
+ for (n = ntrans + 1, n_end = ntrans + sizeof(ntrans)-2;
+ word != w_end && n < n_end; word++ )
+ if ( isalpha ( *word ))
+ *n++ = toupper ( *word );
+
+ if ( n == ntrans + 1 ) /* return empty string if 0 bytes */
+ {
+ *length=0;
+ return result;
+ }
+ n_end = n; /* set n_end to end of string */
+ ntrans[0] = 'Z'; /* ntrans[0] should be a neutral char */
+ n[0]=n[1]=0; /* pad with nulls */
+ n = ntrans + 1; /* assign pointer to start */
+
+ /*------------------------------------------------------------
+ * check for all prefixes:
+ * PN KN GN AE WR WH and X at start.
+ *----------------------------------------------------------*/
+
+ switch ( *n ) {
+ case 'P':
+ case 'K':
+ case 'G':
+ if ( n[1] == 'N')
+ *n++ = 0;
+ break;
+ case 'A':
+ if ( n[1] == 'E')
+ *n++ = 0;
+ break;
+ case 'W':
+ if ( n[1] == 'R' )
+ *n++ = 0;
+ else
+ if ( *(n + 1) == 'H')
+ {
+ n[1] = *n;
+ *n++ = 0;
+ }
+ break;
+ case 'X':
+ *n = 'S';
+ break;
+ }
+
+ /*------------------------------------------------------------
+ * Now, loop step through string, stopping at end of string
+ * or when the computed metaph is MAXMETAPH characters long
+ *----------------------------------------------------------*/
+
+ KSflag = 0; /* state flag for KS translation */
+
+ for (metaph_end = result + MAXMETAPH, n_start = n;
+ n < n_end && result < metaph_end; n++ )
+ {
+
+ if ( KSflag )
+ {
+ KSflag = 0;
+ *result++ = *n;
+ }
+ else
+ {
+ /* drop duplicates except for CC */
+ if ( *( n - 1 ) == *n && *n != 'C' )
+ continue;
+
+ /* check for F J L M N R or first letter vowel */
+ if ( NOCHANGE ( *n ) ||
+ ( n == n_start && ISVOWEL ( *n )))
+ *result++ = *n;
+ else
+ switch ( *n ) {
+ case 'B': /* check for -MB */
+ if ( n < n_end || *( n - 1 ) != 'M' )
+ *result++ = *n;
+ break;
+
+ case 'C': /* C = X ("sh" sound) in CH and CIA */
+ /* = S in CE CI and CY */
+ /* dropped in SCI SCE SCY */
+ /* else K */
+ if ( *( n - 1 ) != 'S' ||
+ !MAKESOFT ( n[1]))
+ {
+ if ( n[1] == 'I' && n[2] == 'A' )
+ *result++ = 'X';
+ else
+ if ( MAKESOFT ( n[1]))
+ *result++ = 'S';
+ else
+ if ( n[1] == 'H' )
+ *result++ = (( n == n_start &&
+ !ISVOWEL ( n[2])) ||
+ *( n - 1 ) == 'S' ) ?
+ (char)'K' : (char)'X';
+ else
+ *result++ = 'K';
+ }
+ break;
+
+ case 'D': /* J before DGE, DGI, DGY, else T */
+ *result++ =
+ ( n[1] == 'G' &&
+ MAKESOFT ( n[2])) ?
+ (char)'J' : (char)'T';
+ break;
+
+ case 'G': /* complicated, see table in text */
+ if (( n[1] != 'H' || ISVOWEL ( n[2]))
+ && (
+ n[1] != 'N' ||
+ (
+ (n + 1) < n_end &&
+ (
+ n[2] != 'E' ||
+ *( n + 3 ) != 'D'
+ )
+ )
+ )
+ && (
+ *( n - 1 ) != 'D' ||
+ !MAKESOFT ( n[1])
+ )
+ )
+ *result++ =
+ ( MAKESOFT ( *( n + 1 )) &&
+ n[2] != 'G' ) ?
+ (char)'J' : (char)'K';
+ else
+ if ( n[1] == 'H' &&
+ !NOGHTOF( *( n - 3 )) &&
+ *( n - 4 ) != 'H')
+ *result++ = 'F';
+ break;
+
+ case 'H': /* H if before a vowel and not after */
+ /* C, G, P, S, T */
+
+ if ( !AFFECTH ( *( n - 1 )) &&
+ ( !ISVOWEL ( *( n - 1 )) ||
+ ISVOWEL ( n[1])))
+ *result++ = 'H';
+ break;
+
+ case 'K': /* K = K, except dropped after C */
+ if ( *( n - 1 ) != 'C')
+ *result++ = 'K';
+ break;
+
+ case 'P': /* PH = F, else P = P */
+ *result++ = *( n + 1 ) == 'H'
+ ? (char)'F' : (char)'P';
+ break;
+ case 'Q': /* Q = K (U after Q is already gone */
+ *result++ = 'K';
+ break;
+
+ case 'S': /* SH, SIO, SIA = X ("sh" sound) */
+ *result++ = ( n[1] == 'H' ||
+ ( *(n + 1) == 'I' &&
+ ( n[2] == 'O' ||
+ n[2] == 'A'))) ?
+ (char)'X' : (char)'S';
+ break;
+
+ case 'T': /* TIO, TIA = X ("sh" sound) */
+ /* TH = 0, ("th" sound ) */
+ if ( *( n + 1 ) == 'I' && ( n[2] == 'O'
+ || n[2] == 'A') )
+ *result++ = 'X';
+ else
+ if ( n[1] == 'H' )
+ *result++ = '0';
+ else
+ if ( *( n + 1) != 'C' || n[2] != 'H')
+ *result++ = 'T';
+ break;
+
+ case 'V': /* V = F */
+ *result++ = 'F';
+ break;
+
+ case 'W': /* only exist if a vowel follows */
+ case 'Y':
+ if ( ISVOWEL ( n[1]))
+ *result++ = *n;
+ break;
+
+ case 'X': /* X = KS, except at start */
+ if ( n == n_start )
+ *result++ = 'S';
+ else
+ {
+ *result++ = 'K'; /* insert K, then S */
+ KSflag = 1; /* this flag will cause S to be
+ inserted on next pass thru loop */
+ }
+ break;
+
+ case 'Z':
+ *result++ = 'S';
+ break;
+ }
+ }
+ }
+ *length= (unsigned long) (result - org_result);
+ return org_result;
+}
+
+
+/***************************************************************************
+** UDF double function.
+** Arguments:
+** initid Structure filled by xxx_init
+** args The same structure as to xxx_init. This structure
+** contains values for all parameters.
+** Note that the functions MUST check and convert all
+** to the type it wants! Null values are represented by
+** a NULL pointer
+** is_null If the result is null, one should store 1 here.
+** error If something goes fatally wrong one should store 1 here.
+**
+** This function should return the result.
+***************************************************************************/
+
+my_bool myfunc_double_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
+{
+ uint i;
+
+ if (!args->arg_count)
+ {
+ strcpy(message,"myfunc_double must have at least one argument");
+ return 1;
+ }
+ /*
+ ** As this function wants to have everything as strings, force all arguments
+ ** to strings.
+ */
+ for (i=0 ; i < args->arg_count; i++)
+ args->arg_type[i]=STRING_RESULT;
+ initid->maybe_null=1; /* The result may be null */
+ initid->decimals=2; /* We want 2 decimals in the result */
+ initid->max_length=6; /* 3 digits + . + 2 decimals */
+ return 0;
+}
+
+
+double myfunc_double(UDF_INIT *initid __attribute__((unused)), UDF_ARGS *args,
+ char *is_null, char *error __attribute__((unused)))
+{
+ unsigned long val = 0;
+ unsigned long v = 0;
+ uint i, j;
+
+ for (i = 0; i < args->arg_count; i++)
+ {
+ if (args->args[i] == NULL)
+ continue;
+ val += args->lengths[i];
+ for (j=args->lengths[i] ; j-- > 0 ;)
+ v += args->args[i][j];
+ }
+ if (val)
+ return (double) v/ (double) val;
+ *is_null=1;
+ return 0.0;
+}
+
+
+/***************************************************************************
+** UDF long long function.
+** Arguments:
+** initid Return value from xxxx_init
+** args The same structure as to xxx_init. This structure
+** contains values for all parameters.
+** Note that the functions MUST check and convert all
+** to the type it wants! Null values are represented by
+** a NULL pointer
+** is_null If the result is null, one should store 1 here.
+** error If something goes fatally wrong one should store 1 here.
+**
+** This function should return the result as a long long
+***************************************************************************/
+
+/* This function returns the sum of all arguments */
+
+longlong myfunc_int(UDF_INIT *initid __attribute__((unused)), UDF_ARGS *args,
+ char *is_null __attribute__((unused)),
+ char *error __attribute__((unused)))
+{
+ longlong val = 0;
+ uint i;
+
+ for (i = 0; i < args->arg_count; i++)
+ {
+ if (args->args[i] == NULL)
+ continue;
+ switch (args->arg_type[i]) {
+ case STRING_RESULT: /* Add string lengths */
+ val += args->lengths[i];
+ break;
+ case INT_RESULT: /* Add numbers */
+ val += *((longlong*) args->args[i]);
+ break;
+ case REAL_RESULT: /* Add numers as longlong */
+ val += (longlong) *((double*) args->args[i]);
+ break;
+ default:
+ break;
+ }
+ }
+ return val;
+}
+
+/*
+ At least one of _init/_deinit is needed unless the server is started
+ with --allow_suspicious_udfs.
+*/
+my_bool myfunc_int_init(UDF_INIT *initid __attribute__((unused)),
+ UDF_ARGS *args __attribute__((unused)),
+ char *message __attribute__((unused)))
+{
+ return 0;
+}
+
+/*
+ Simple example of how to get a sequences starting from the first argument
+ or 1 if no arguments have been given
+*/
+
+my_bool sequence_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
+{
+ if (args->arg_count > 1)
+ {
+ strmov(message,"This function takes none or 1 argument");
+ return 1;
+ }
+ if (args->arg_count)
+ args->arg_type[0]= INT_RESULT; /* Force argument to int */
+
+ if (!(initid->ptr=(char*) malloc(sizeof(longlong))))
+ {
+ strmov(message,"Couldn't allocate memory");
+ return 1;
+ }
+ bzero(initid->ptr,sizeof(longlong));
+ /*
+ sequence() is a non-deterministic function : it has different value
+ even if called with the same arguments.
+ */
+ initid->const_item=0;
+ return 0;
+}
+
+void sequence_deinit(UDF_INIT *initid)
+{
+ if (initid->ptr)
+ free(initid->ptr);
+}
+
+longlong sequence(UDF_INIT *initid __attribute__((unused)), UDF_ARGS *args,
+ char *is_null __attribute__((unused)),
+ char *error __attribute__((unused)))
+{
+ ulonglong val=0;
+ if (args->arg_count)
+ val= *((longlong*) args->args[0]);
+ return ++*((longlong*) initid->ptr) + val;
+}
+
+
+/****************************************************************************
+** Some functions that handles IP and hostname conversions
+** The orignal function was from Zeev Suraski.
+**
+** CREATE FUNCTION lookup RETURNS STRING SONAME "udf_example.so";
+** CREATE FUNCTION reverse_lookup RETURNS STRING SONAME "udf_example.so";
+**
+****************************************************************************/
+
+#ifdef __WIN__
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif
+
+my_bool lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
+void lookup_deinit(UDF_INIT *initid);
+char *lookup(UDF_INIT *initid, UDF_ARGS *args, char *result,
+ unsigned long *length, char *null_value, char *error);
+my_bool reverse_lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
+void reverse_lookup_deinit(UDF_INIT *initid);
+char *reverse_lookup(UDF_INIT *initid, UDF_ARGS *args, char *result,
+ unsigned long *length, char *null_value, char *error);
+
+
+/****************************************************************************
+** lookup IP for an hostname.
+**
+** This code assumes that gethostbyname_r exists and inet_ntoa() is thread
+** safe (As it is in Solaris)
+****************************************************************************/
+
+
+my_bool lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
+{
+ if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
+ {
+ strmov(message,"Wrong arguments to lookup; Use the source");
+ return 1;
+ }
+ initid->max_length=11;
+ initid->maybe_null=1;
+#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
+ (void) pthread_mutex_init(&LOCK_hostname,MY_MUTEX_INIT_SLOW);
+#endif
+ return 0;
+}
+
+void lookup_deinit(UDF_INIT *initid __attribute__((unused)))
+{
+#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
+ (void) pthread_mutex_destroy(&LOCK_hostname);
+#endif
+}
+
+char *lookup(UDF_INIT *initid __attribute__((unused)), UDF_ARGS *args,
+ char *result, unsigned long *res_length, char *null_value,
+ char *error __attribute__((unused)))
+{
+ uint length;
+ char name_buff[256];
+ struct hostent *hostent;
+#if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
+ int tmp_errno;
+ char hostname_buff[2048];
+ struct hostent tmp_hostent;
+#endif
+ struct in_addr in;
+
+ if (!args->args[0] || !(length=args->lengths[0]))
+ {
+ *null_value=1;
+ return 0;
+ }
+ if (length >= sizeof(name_buff))
+ length=sizeof(name_buff)-1;
+ memcpy(name_buff,args->args[0],length);
+ name_buff[length]=0;
+#if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
+ if (!(hostent=gethostbyname_r(name_buff,&tmp_hostent,hostname_buff,
+ sizeof(hostname_buff), &tmp_errno)))
+ {
+ *null_value=1;
+ return 0;
+ }
+#else
+ pthread_mutex_lock(&LOCK_hostname);
+ if (!(hostent= gethostbyname((char*) name_buff)))
+ {
+ pthread_mutex_unlock(&LOCK_hostname);
+ *null_value= 1;
+ return 0;
+ }
+ pthread_mutex_unlock(&LOCK_hostname);
+#endif
+ memcpy(&in, *hostent->h_addr_list, sizeof(in.s_addr));
+ *res_length= (ulong) (strmov(result, inet_ntoa(in)) - result);
+ return result;
+}
+
+
+/****************************************************************************
+** return hostname for an IP number.
+** The functions can take as arguments a string "xxx.xxx.xxx.xxx" or
+** four numbers.
+****************************************************************************/
+
+my_bool reverse_lookup_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
+{
+ if (args->arg_count == 1)
+ args->arg_type[0]= STRING_RESULT;
+ else if (args->arg_count == 4)
+ args->arg_type[0]=args->arg_type[1]=args->arg_type[2]=args->arg_type[3]=
+ INT_RESULT;
+ else
+ {
+ strmov(message,
+ "Wrong number of arguments to reverse_lookup; Use the source");
+ return 1;
+ }
+ initid->max_length=32;
+ initid->maybe_null=1;
+#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
+ (void) pthread_mutex_init(&LOCK_hostname,MY_MUTEX_INIT_SLOW);
+#endif
+ return 0;
+}
+
+void reverse_lookup_deinit(UDF_INIT *initid __attribute__((unused)))
+{
+#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_SOLARIS_STYLE_GETHOST)
+ (void) pthread_mutex_destroy(&LOCK_hostname);
+#endif
+}
+
+char *reverse_lookup(UDF_INIT *initid __attribute__((unused)), UDF_ARGS *args,
+ char *result, unsigned long *res_length,
+ char *null_value, char *error __attribute__((unused)))
+{
+#if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
+ char name_buff[256];
+ struct hostent tmp_hostent;
+ int tmp_errno;
+#endif
+ struct hostent *hp;
+ unsigned long taddr;
+ uint length;
+
+ if (args->arg_count == 4)
+ {
+ if (!args->args[0] || !args->args[1] ||!args->args[2] ||!args->args[3])
+ {
+ *null_value=1;
+ return 0;
+ }
+ sprintf(result,"%d.%d.%d.%d",
+ (int) *((longlong*) args->args[0]),
+ (int) *((longlong*) args->args[1]),
+ (int) *((longlong*) args->args[2]),
+ (int) *((longlong*) args->args[3]));
+ }
+ else
+ { /* string argument */
+ if (!args->args[0]) /* Return NULL for NULL values */
+ {
+ *null_value=1;
+ return 0;
+ }
+ length=args->lengths[0];
+ if (length >= (uint) *res_length-1)
+ length=(uint) *res_length;
+ memcpy(result,args->args[0],length);
+ result[length]=0;
+ }
+
+ taddr = inet_addr(result);
+ if (taddr == (unsigned long) -1L)
+ {
+ *null_value=1;
+ return 0;
+ }
+#if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST)
+ if (!(hp=gethostbyaddr_r((char*) &taddr,sizeof(taddr), AF_INET,
+ &tmp_hostent, name_buff,sizeof(name_buff),
+ &tmp_errno)))
+ {
+ *null_value=1;
+ return 0;
+ }
+#else
+ pthread_mutex_lock(&LOCK_hostname);
+ if (!(hp= gethostbyaddr((char*) &taddr, sizeof(taddr), AF_INET)))
+ {
+ pthread_mutex_unlock(&LOCK_hostname);
+ *null_value= 1;
+ return 0;
+ }
+ pthread_mutex_unlock(&LOCK_hostname);
+#endif
+ *res_length=(ulong) (strmov(result,hp->h_name) - result);
+ return result;
+}
+
+/*
+** Syntax for the new aggregate commands are:
+** create aggregate function <function_name> returns {string|real|integer}
+** soname <name_of_shared_library>
+**
+** Syntax for avgcost: avgcost( t.quantity, t.price )
+** with t.quantity=integer, t.price=double
+** (this example is provided by Andreas F. Bobak <bobak@relog.ch>)
+*/
+
+
+struct avgcost_data
+{
+ ulonglong count;
+ longlong totalquantity;
+ double totalprice;
+};
+
+
+/*
+** Average Cost Aggregate Function.
+*/
+my_bool
+avgcost_init( UDF_INIT* initid, UDF_ARGS* args, char* message )
+{
+ struct avgcost_data* data;
+
+ if (args->arg_count != 2)
+ {
+ strcpy(
+ message,
+ "wrong number of arguments: AVGCOST() requires two arguments"
+ );
+ return 1;
+ }
+
+ if ((args->arg_type[0] != INT_RESULT) || (args->arg_type[1] != REAL_RESULT) )
+ {
+ strcpy(
+ message,
+ "wrong argument type: AVGCOST() requires an INT and a REAL"
+ );
+ return 1;
+ }
+
+ /*
+ ** force arguments to double.
+ */
+ /*args->arg_type[0] = REAL_RESULT;
+ args->arg_type[1] = REAL_RESULT;*/
+
+ initid->maybe_null = 0; /* The result may be null */
+ initid->decimals = 4; /* We want 4 decimals in the result */
+ initid->max_length = 20; /* 6 digits + . + 10 decimals */
+
+ if (!(data = (struct avgcost_data*) malloc(sizeof(struct avgcost_data))))
+ {
+ strmov(message,"Couldn't allocate memory");
+ return 1;
+ }
+ data->totalquantity = 0;
+ data->totalprice = 0.0;
+
+ initid->ptr = (char*)data;
+
+ return 0;
+}
+
+void
+avgcost_deinit( UDF_INIT* initid )
+{
+ free(initid->ptr);
+}
+
+
+/* This is only for MySQL 4.0 compability */
+void
+avgcost_reset(UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* message)
+{
+ avgcost_clear(initid, is_null, message);
+ avgcost_add(initid, args, is_null, message);
+}
+
+/* This is needed to get things to work in MySQL 4.1.1 and above */
+
+void
+avgcost_clear(UDF_INIT* initid, char* is_null __attribute__((unused)),
+ char* message __attribute__((unused)))
+{
+ struct avgcost_data* data = (struct avgcost_data*)initid->ptr;
+ data->totalprice= 0.0;
+ data->totalquantity= 0;
+ data->count= 0;
+}
+
+
+void
+avgcost_add(UDF_INIT* initid, UDF_ARGS* args,
+ char* is_null __attribute__((unused)),
+ char* message __attribute__((unused)))
+{
+ if (args->args[0] && args->args[1])
+ {
+ struct avgcost_data* data = (struct avgcost_data*)initid->ptr;
+ longlong quantity = *((longlong*)args->args[0]);
+ longlong newquantity = data->totalquantity + quantity;
+ double price = *((double*)args->args[1]);
+
+ data->count++;
+
+ if ( ((data->totalquantity >= 0) && (quantity < 0))
+ || ((data->totalquantity < 0) && (quantity > 0)) )
+ {
+ /*
+ ** passing from + to - or from - to +
+ */
+ if ( ((quantity < 0) && (newquantity < 0))
+ || ((quantity > 0) && (newquantity > 0)) )
+ {
+ data->totalprice = price * (double)newquantity;
+ }
+ /*
+ ** sub q if totalq > 0
+ ** add q if totalq < 0
+ */
+ else
+ {
+ price = data->totalprice / (double)data->totalquantity;
+ data->totalprice = price * (double)newquantity;
+ }
+ data->totalquantity = newquantity;
+ }
+ else
+ {
+ data->totalquantity += quantity;
+ data->totalprice += price * (double)quantity;
+ }
+
+ if (data->totalquantity == 0)
+ data->totalprice = 0.0;
+ }
+}
+
+
+double
+avgcost( UDF_INIT* initid, UDF_ARGS* args __attribute__((unused)),
+ char* is_null, char* error __attribute__((unused)))
+{
+ struct avgcost_data* data = (struct avgcost_data*)initid->ptr;
+ if (!data->count || !data->totalquantity)
+ {
+ *is_null = 1;
+ return 0.0;
+ }
+
+ *is_null = 0;
+ return data->totalprice/(double)data->totalquantity;
+}
+
+my_bool myfunc_argument_name_init(UDF_INIT *initid, UDF_ARGS *args,
+ char *message);
+char *myfunc_argument_name(UDF_INIT *initid, UDF_ARGS *args, char *result,
+ unsigned long *length, char *null_value,
+ char *error);
+
+my_bool myfunc_argument_name_init(UDF_INIT *initid, UDF_ARGS *args,
+ char *message)
+{
+ if (args->arg_count != 1)
+ {
+ strmov(message,"myfunc_argument_name_init accepts only one argument");
+ return 1;
+ }
+ initid->max_length= args->attribute_lengths[0];
+ initid->maybe_null= 1;
+ initid->const_item= 1;
+ return 0;
+}
+
+char *myfunc_argument_name(UDF_INIT *initid __attribute__((unused)),
+ UDF_ARGS *args, char *result,
+ unsigned long *length, char *null_value,
+ char *error __attribute__((unused)))
+{
+ if (!args->attributes[0])
+ {
+ *null_value= 1;
+ return 0;
+ }
+ (*length)--; /* space for ending \0 (for debugging purposes) */
+ if (*length > args->attribute_lengths[0])
+ *length= args->attribute_lengths[0];
+ memcpy(result, args->attributes[0], *length);
+ result[*length]= 0;
+ return result;
+}
+
+
+
+my_bool is_const_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
+{
+ if (args->arg_count != 1)
+ {
+ strmov(message, "IS_CONST accepts only one argument");
+ return 1;
+ }
+ initid->ptr= (char*)((args->args[0] != NULL) ? 1UL : 0);
+ return 0;
+}
+
+char * is_const(UDF_INIT *initid, UDF_ARGS *args __attribute__((unused)),
+ char *result, unsigned long *length,
+ char *is_null, char *error __attribute__((unused)))
+{
+ if (initid->ptr != 0) {
+ sprintf(result, "const");
+ } else {
+ sprintf(result, "not const");
+ }
+ *is_null= 0;
+ *length= (uint) strlen(result);
+ return result;
+}
+
+
+
+my_bool check_const_len_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
+{
+ if (args->arg_count != 1)
+ {
+ strmov(message, "CHECK_CONST_LEN accepts only one argument");
+ return 1;
+ }
+ if (args->args[0] == 0)
+ {
+ initid->ptr= (char*)"Not constant";
+ }
+ else if(strlen(args->args[0]) == args->lengths[0])
+ {
+ initid->ptr= (char*)"Correct length";
+ }
+ else
+ {
+ initid->ptr= (char*)"Wrong length";
+ }
+ initid->max_length = 100;
+ return 0;
+}
+
+char * check_const_len(UDF_INIT *initid, UDF_ARGS *args __attribute__((unused)),
+ char *result, unsigned long *length,
+ char *is_null, char *error __attribute__((unused)))
+{
+ strmov(result, initid->ptr);
+ *length= (uint) strlen(result);
+ *is_null= 0;
+ return result;
+}
+
+
+#endif /* HAVE_DLOPEN */
diff --git a/sql/udf_example.def b/sql/udf_example.def
new file mode 100644
index 00000000000..41150b24e8f
--- /dev/null
+++ b/sql/udf_example.def
@@ -0,0 +1,29 @@
+LIBRARY udf_example
+VERSION 1.0
+EXPORTS
+ lookup
+ lookup_init
+ lookup_deinit
+ reverse_lookup
+ reverse_lookup_init
+ reverse_lookup_deinit
+ metaphon_init
+ metaphon_deinit
+ metaphon
+ myfunc_double_init
+ myfunc_double
+ myfunc_int_init
+ myfunc_int
+ sequence_init
+ sequence_deinit
+ sequence
+ avgcost_init
+ avgcost_deinit
+ avgcost_reset
+ avgcost_add
+ avgcost_clear
+ avgcost
+ is_const
+ is_const_init
+ check_const_len
+ check_const_len_init
diff --git a/sql/uniques.cc b/sql/uniques.cc
new file mode 100644
index 00000000000..c755293035b
--- /dev/null
+++ b/sql/uniques.cc
@@ -0,0 +1,788 @@
+/* Copyright (c) 2001, 2010, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2015, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/*
+ Function to handle quick removal of duplicates
+ This code is used when doing multi-table deletes to find the rows in
+ reference tables that needs to be deleted.
+
+ The basic idea is as follows:
+
+ Store first all strings in a binary tree, ignoring duplicates.
+ When the tree uses more memory than 'max_heap_table_size',
+ write the tree (in sorted order) out to disk and start with a new tree.
+ When all data has been generated, merge the trees (removing any found
+ duplicates).
+
+ The unique entries will be returned in sort order, to ensure that we do the
+ deletes in disk order.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_sort.h"
+#include "queues.h" // QUEUE
+#include "my_tree.h" // element_count
+#include "sql_class.h" // Unique
+
+int unique_write_to_file(uchar* key, element_count count, Unique *unique)
+{
+ /*
+ Use unique->size (size of element stored in the tree) and not
+ unique->tree.size_of_element. The latter is different from unique->size
+ when tree implementation chooses to store pointer to key in TREE_ELEMENT
+ (instead of storing the element itself there)
+ */
+ return my_b_write(&unique->file, key, unique->size) ? 1 : 0;
+}
+
+int unique_write_to_file_with_count(uchar* key, element_count count, Unique *unique)
+{
+ return my_b_write(&unique->file, key, unique->size) ||
+ my_b_write(&unique->file, &count, sizeof(element_count)) ? 1 : 0;
+}
+
+int unique_write_to_ptrs(uchar* key, element_count count, Unique *unique)
+{
+ memcpy(unique->record_pointers, key, unique->size);
+ unique->record_pointers+=unique->size;
+ return 0;
+}
+
+int unique_intersect_write_to_ptrs(uchar* key, element_count count, Unique *unique)
+{
+ if (count >= unique->min_dupl_count)
+ {
+ memcpy(unique->record_pointers, key, unique->size);
+ unique->record_pointers+=unique->size;
+ }
+ else
+ unique->filtered_out_elems++;
+ return 0;
+}
+
+
+Unique::Unique(qsort_cmp2 comp_func, void * comp_func_fixed_arg,
+ uint size_arg, ulonglong max_in_memory_size_arg,
+ uint min_dupl_count_arg)
+ :max_in_memory_size(max_in_memory_size_arg),
+ record_pointers(NULL),
+ size(size_arg),
+ elements(0)
+{
+ min_dupl_count= min_dupl_count_arg;
+ full_size= size;
+ if (min_dupl_count_arg)
+ full_size+= sizeof(element_count);
+ with_counters= MY_TEST(min_dupl_count_arg);
+ my_b_clear(&file);
+ init_tree(&tree, (ulong) (max_in_memory_size / 16), 0, size, comp_func,
+ NULL, comp_func_fixed_arg, MYF(MY_THREAD_SPECIFIC));
+ /* If the following fail's the next add will also fail */
+ my_init_dynamic_array(&file_ptrs, sizeof(BUFFPEK), 16, 16,
+ MYF(MY_THREAD_SPECIFIC));
+ /*
+ If you change the following, change it in get_max_elements function, too.
+ */
+ max_elements= (ulong) (max_in_memory_size /
+ ALIGN_SIZE(sizeof(TREE_ELEMENT)+size));
+ (void) open_cached_file(&file, mysql_tmpdir,TEMP_PREFIX, DISK_BUFFER_SIZE,
+ MYF(MY_WME));
+}
+
+
+/*
+ Calculate log2(n!)
+
+ NOTES
+ Stirling's approximate formula is used:
+
+ n! ~= sqrt(2*M_PI*n) * (n/M_E)^n
+
+ Derivation of formula used for calculations is as follows:
+
+ log2(n!) = log(n!)/log(2) = log(sqrt(2*M_PI*n)*(n/M_E)^n) / log(2) =
+
+ = (log(2*M_PI*n)/2 + n*log(n/M_E)) / log(2).
+*/
+
+inline double log2_n_fact(double x)
+{
+ return (log(2*M_PI*x)/2 + x*log(x/M_E)) / M_LN2;
+}
+
+
+/*
+ Calculate cost of merge_buffers function call for given sequence of
+ input stream lengths and store the number of rows in result stream in *last.
+
+ SYNOPSIS
+ get_merge_buffers_cost()
+ buff_elems Array of #s of elements in buffers
+ elem_size Size of element stored in buffer
+ first Pointer to first merged element size
+ last Pointer to last merged element size
+
+ RETURN
+ Cost of merge_buffers operation in disk seeks.
+
+ NOTES
+ It is assumed that no rows are eliminated during merge.
+ The cost is calculated as
+
+ cost(read_and_write) + cost(merge_comparisons).
+
+ All bytes in the sequences is read and written back during merge so cost
+ of disk io is 2*elem_size*total_buf_elems/IO_SIZE (2 is for read + write)
+
+ For comparisons cost calculations we assume that all merged sequences have
+ the same length, so each of total_buf_size elements will be added to a sort
+ heap with (n_buffers-1) elements. This gives the comparison cost:
+
+ total_buf_elems* log2(n_buffers) / TIME_FOR_COMPARE_ROWID;
+*/
+
+static double get_merge_buffers_cost(uint *buff_elems, uint elem_size,
+ uint *first, uint *last,
+ uint compare_factor)
+{
+ uint total_buf_elems= 0;
+ for (uint *pbuf= first; pbuf <= last; pbuf++)
+ total_buf_elems+= *pbuf;
+ *last= total_buf_elems;
+
+ size_t n_buffers= last - first + 1;
+
+ /* Using log2(n)=log(n)/log(2) formula */
+ return 2*((double)total_buf_elems*elem_size) / IO_SIZE +
+ total_buf_elems*log((double) n_buffers) / (compare_factor * M_LN2);
+}
+
+
+/*
+ Calculate cost of merging buffers into one in Unique::get, i.e. calculate
+ how long (in terms of disk seeks) the two calls
+ merge_many_buffs(...);
+ merge_buffers(...);
+ will take.
+
+ SYNOPSIS
+ get_merge_many_buffs_cost()
+ buffer buffer space for temporary data, at least
+ Unique::get_cost_calc_buff_size bytes
+ maxbuffer # of full buffers
+ max_n_elems # of elements in first maxbuffer buffers
+ last_n_elems # of elements in last buffer
+ elem_size size of buffer element
+
+ NOTES
+ maxbuffer+1 buffers are merged, where first maxbuffer buffers contain
+ max_n_elems elements each and last buffer contains last_n_elems elements.
+
+ The current implementation does a dumb simulation of merge_many_buffs
+ function actions.
+
+ RETURN
+ Cost of merge in disk seeks.
+*/
+
+static double get_merge_many_buffs_cost(uint *buffer,
+ uint maxbuffer, uint max_n_elems,
+ uint last_n_elems, int elem_size,
+ uint compare_factor)
+{
+ register int i;
+ double total_cost= 0.0;
+ uint *buff_elems= buffer; /* #s of elements in each of merged sequences */
+
+ /*
+ Set initial state: first maxbuffer sequences contain max_n_elems elements
+ each, last sequence contains last_n_elems elements.
+ */
+ for (i = 0; i < (int)maxbuffer; i++)
+ buff_elems[i]= max_n_elems;
+ buff_elems[maxbuffer]= last_n_elems;
+
+ /*
+ Do it exactly as merge_many_buff function does, calling
+ get_merge_buffers_cost to get cost of merge_buffers.
+ */
+ if (maxbuffer >= MERGEBUFF2)
+ {
+ while (maxbuffer >= MERGEBUFF2)
+ {
+ uint lastbuff= 0;
+ for (i = 0; i <= (int) maxbuffer - MERGEBUFF*3/2; i += MERGEBUFF)
+ {
+ total_cost+=get_merge_buffers_cost(buff_elems, elem_size,
+ buff_elems + i,
+ buff_elems + i + MERGEBUFF-1,
+ compare_factor);
+ lastbuff++;
+ }
+ total_cost+=get_merge_buffers_cost(buff_elems, elem_size,
+ buff_elems + i,
+ buff_elems + maxbuffer,
+ compare_factor);
+ maxbuffer= lastbuff;
+ }
+ }
+
+ /* Simulate final merge_buff call. */
+ total_cost += get_merge_buffers_cost(buff_elems, elem_size,
+ buff_elems, buff_elems + maxbuffer,
+ compare_factor);
+ return total_cost;
+}
+
+
+/*
+ Calculate cost of using Unique for processing nkeys elements of size
+ key_size using max_in_memory_size memory.
+
+ SYNOPSIS
+ Unique::get_use_cost()
+ buffer space for temporary data, use Unique::get_cost_calc_buff_size
+ to get # bytes needed.
+ nkeys #of elements in Unique
+ key_size size of each elements in bytes
+ max_in_memory_size amount of memory Unique will be allowed to use
+ compare_factor used to calculate cost of one comparison
+ write_fl if the result must be saved written to disk
+ in_memory_elems OUT estimate of the number of elements in memory
+ if disk is not used
+
+ RETURN
+ Cost in disk seeks.
+
+ NOTES
+ cost(using_unqiue) =
+ cost(create_trees) + (see #1)
+ cost(merge) + (see #2)
+ cost(read_result) (see #3)
+
+ 1. Cost of trees creation
+ For each Unique::put operation there will be 2*log2(n+1) elements
+ comparisons, where n runs from 1 tree_size (we assume that all added
+ elements are different). Together this gives:
+
+ n_compares = 2*(log2(2) + log2(3) + ... + log2(N+1)) = 2*log2((N+1)!)
+
+ then cost(tree_creation) = n_compares*ROWID_COMPARE_COST;
+
+ Total cost of creating trees:
+ (n_trees - 1)*max_size_tree_cost + non_max_size_tree_cost.
+
+ Approximate value of log2(N!) is calculated by log2_n_fact function.
+
+ 2. Cost of merging.
+ If only one tree is created by Unique no merging will be necessary.
+ Otherwise, we model execution of merge_many_buff function and count
+ #of merges. (The reason behind this is that number of buffers is small,
+ while size of buffers is big and we don't want to loose precision with
+ O(x)-style formula)
+
+ 3. If only one tree is created by Unique no disk io will happen.
+ Otherwise, ceil(key_len*n_keys) disk seeks are necessary. We assume
+ these will be random seeks.
+*/
+
+double Unique::get_use_cost(uint *buffer, size_t nkeys, uint key_size,
+ ulonglong max_in_memory_size,
+ uint compare_factor,
+ bool intersect_fl, bool *in_memory)
+{
+ size_t max_elements_in_tree;
+ size_t last_tree_elems;
+ int n_full_trees; /* number of trees in unique - 1 */
+ double result;
+
+ max_elements_in_tree= ((size_t) max_in_memory_size /
+ ALIGN_SIZE(sizeof(TREE_ELEMENT)+key_size));
+
+ n_full_trees= nkeys / max_elements_in_tree;
+ last_tree_elems= nkeys % max_elements_in_tree;
+
+ /* Calculate cost of creating trees */
+ result= 2*log2_n_fact(last_tree_elems + 1.0);
+ if (n_full_trees)
+ result+= n_full_trees * log2_n_fact(max_elements_in_tree + 1.0);
+ result /= compare_factor;
+
+ DBUG_PRINT("info",("unique trees sizes: %u=%u*%u + %u", (uint)nkeys,
+ (uint)n_full_trees,
+ (uint)(n_full_trees?max_elements_in_tree:0),
+ (uint)last_tree_elems));
+
+ if (in_memory)
+ *in_memory= !n_full_trees;
+
+ if (!n_full_trees)
+ return result;
+
+ /*
+ There is more then one tree and merging is necessary.
+ First, add cost of writing all trees to disk, assuming that all disk
+ writes are sequential.
+ */
+ result += DISK_SEEK_BASE_COST * n_full_trees *
+ ceil(((double) key_size)*max_elements_in_tree / IO_SIZE);
+ result += DISK_SEEK_BASE_COST * ceil(((double) key_size)*last_tree_elems / IO_SIZE);
+
+ /* Cost of merge */
+ if (intersect_fl)
+ key_size+= sizeof(element_count);
+ double merge_cost= get_merge_many_buffs_cost(buffer, n_full_trees,
+ max_elements_in_tree,
+ last_tree_elems, key_size,
+ compare_factor);
+ result += merge_cost;
+ /*
+ Add cost of reading the resulting sequence, assuming there were no
+ duplicate elements.
+ */
+ result += ceil((double)key_size*nkeys/IO_SIZE);
+
+ return result;
+}
+
+Unique::~Unique()
+{
+ close_cached_file(&file);
+ delete_tree(&tree);
+ delete_dynamic(&file_ptrs);
+}
+
+
+ /* Write tree to disk; clear tree */
+bool Unique::flush()
+{
+ BUFFPEK file_ptr;
+ elements+= tree.elements_in_tree;
+ file_ptr.count=tree.elements_in_tree;
+ file_ptr.file_pos=my_b_tell(&file);
+
+ tree_walk_action action= min_dupl_count ?
+ (tree_walk_action) unique_write_to_file_with_count :
+ (tree_walk_action) unique_write_to_file;
+ if (tree_walk(&tree, action,
+ (void*) this, left_root_right) ||
+ insert_dynamic(&file_ptrs, (uchar*) &file_ptr))
+ return 1;
+ delete_tree(&tree);
+ return 0;
+}
+
+
+/*
+ Clear the tree and the file.
+ You must call reset() if you want to reuse Unique after walk().
+*/
+
+void
+Unique::reset()
+{
+ reset_tree(&tree);
+ /*
+ If elements != 0, some trees were stored in the file (see how
+ flush() works). Note, that we can not count on my_b_tell(&file) == 0
+ here, because it can return 0 right after walk(), and walk() does not
+ reset any Unique member.
+ */
+ if (elements)
+ {
+ reset_dynamic(&file_ptrs);
+ reinit_io_cache(&file, WRITE_CACHE, 0L, 0, 1);
+ }
+ elements= 0;
+ tree.flag= 0;
+}
+
+/*
+ The comparison function, passed to queue_init() in merge_walk() and in
+ merge_buffers() when the latter is called from Uniques::get() must
+ use comparison function of Uniques::tree, but compare members of struct
+ BUFFPEK.
+*/
+
+C_MODE_START
+
+static int buffpek_compare(void *arg, uchar *key_ptr1, uchar *key_ptr2)
+{
+ BUFFPEK_COMPARE_CONTEXT *ctx= (BUFFPEK_COMPARE_CONTEXT *) arg;
+ return ctx->key_compare(ctx->key_compare_arg,
+ *((uchar **) key_ptr1), *((uchar **)key_ptr2));
+}
+
+C_MODE_END
+
+
+inline
+element_count get_counter_from_merged_element(void *ptr, uint ofs)
+{
+ element_count cnt;
+ memcpy((uchar *) &cnt, (uchar *) ptr + ofs, sizeof(element_count));
+ return cnt;
+}
+
+
+inline
+void put_counter_into_merged_element(void *ptr, uint ofs, element_count cnt)
+{
+ memcpy((uchar *) ptr + ofs, (uchar *) &cnt, sizeof(element_count));
+}
+
+
+/*
+ DESCRIPTION
+
+ Function is very similar to merge_buffers, but instead of writing sorted
+ unique keys to the output file, it invokes walk_action for each key.
+ This saves I/O if you need to pass through all unique keys only once.
+
+ SYNOPSIS
+ merge_walk()
+ All params are 'IN' (but see comment for begin, end):
+ merge_buffer buffer to perform cached piece-by-piece loading
+ of trees; initially the buffer is empty
+ merge_buffer_size size of merge_buffer. Must be aligned with
+ key_length
+ key_length size of tree element; key_length * (end - begin)
+ must be less or equal than merge_buffer_size.
+ begin pointer to BUFFPEK struct for the first tree.
+ end pointer to BUFFPEK struct for the last tree;
+ end > begin and [begin, end) form a consecutive
+ range. BUFFPEKs structs in that range are used and
+ overwritten in merge_walk().
+ walk_action element visitor. Action is called for each unique
+ key.
+ walk_action_arg argument to walk action. Passed to it on each call.
+ compare elements comparison function
+ compare_arg comparison function argument
+ file file with all trees dumped. Trees in the file
+ must contain sorted unique values. Cache must be
+ initialized in read mode.
+ with counters take into account counters for equal merged
+ elements
+ RETURN VALUE
+ 0 ok
+ <> 0 error
+*/
+
+static bool merge_walk(uchar *merge_buffer, ulong merge_buffer_size,
+ uint key_length, BUFFPEK *begin, BUFFPEK *end,
+ tree_walk_action walk_action, void *walk_action_arg,
+ qsort_cmp2 compare, void *compare_arg,
+ IO_CACHE *file, bool with_counters)
+{
+ BUFFPEK_COMPARE_CONTEXT compare_context = { compare, compare_arg };
+ QUEUE queue;
+ if (end <= begin ||
+ merge_buffer_size < (ulong) (key_length * (end - begin + 1)) ||
+ init_queue(&queue, (uint) (end - begin), offsetof(BUFFPEK, key), 0,
+ buffpek_compare, &compare_context, 0, 0))
+ return 1;
+ /* we need space for one key when a piece of merge buffer is re-read */
+ merge_buffer_size-= key_length;
+ uchar *save_key_buff= merge_buffer + merge_buffer_size;
+ uint max_key_count_per_piece= (uint) (merge_buffer_size/(end-begin) /
+ key_length);
+ /* if piece_size is aligned reuse_freed_buffer will always hit */
+ uint piece_size= max_key_count_per_piece * key_length;
+ uint bytes_read; /* to hold return value of read_to_buffer */
+ BUFFPEK *top;
+ int res= 1;
+ uint cnt_ofs= key_length - (with_counters ? sizeof(element_count) : 0);
+ element_count cnt;
+ /*
+ Invariant: queue must contain top element from each tree, until a tree
+ is not completely walked through.
+ Here we're forcing the invariant, inserting one element from each tree
+ to the queue.
+ */
+ for (top= begin; top != end; ++top)
+ {
+ top->base= merge_buffer + (top - begin) * piece_size;
+ top->max_keys= max_key_count_per_piece;
+ bytes_read= read_to_buffer(file, top, key_length);
+ if (bytes_read == (uint) (-1))
+ goto end;
+ DBUG_ASSERT(bytes_read);
+ queue_insert(&queue, (uchar *) top);
+ }
+ top= (BUFFPEK *) queue_top(&queue);
+ while (queue.elements > 1)
+ {
+ /*
+ Every iteration one element is removed from the queue, and one is
+ inserted by the rules of the invariant. If two adjacent elements on
+ the top of the queue are not equal, biggest one is unique, because all
+ elements in each tree are unique. Action is applied only to unique
+ elements.
+ */
+ void *old_key= top->key;
+ /*
+ read next key from the cache or from the file and push it to the
+ queue; this gives new top.
+ */
+ top->key+= key_length;
+ if (--top->mem_count)
+ queue_replace_top(&queue);
+ else /* next piece should be read */
+ {
+ /* save old_key not to overwrite it in read_to_buffer */
+ memcpy(save_key_buff, old_key, key_length);
+ old_key= save_key_buff;
+ bytes_read= read_to_buffer(file, top, key_length);
+ if (bytes_read == (uint) (-1))
+ goto end;
+ else if (bytes_read > 0) /* top->key, top->mem_count are reset */
+ queue_replace_top(&queue); /* in read_to_buffer */
+ else
+ {
+ /*
+ Tree for old 'top' element is empty: remove it from the queue and
+ give all its memory to the nearest tree.
+ */
+ queue_remove_top(&queue);
+ reuse_freed_buff(&queue, top, key_length);
+ }
+ }
+ top= (BUFFPEK *) queue_top(&queue);
+ /* new top has been obtained; if old top is unique, apply the action */
+ if (compare(compare_arg, old_key, top->key))
+ {
+ cnt= with_counters ?
+ get_counter_from_merged_element(old_key, cnt_ofs) : 1;
+ if (walk_action(old_key, cnt, walk_action_arg))
+ goto end;
+ }
+ else if (with_counters)
+ {
+ cnt= get_counter_from_merged_element(top->key, cnt_ofs);
+ cnt+= get_counter_from_merged_element(old_key, cnt_ofs);
+ put_counter_into_merged_element(top->key, cnt_ofs, cnt);
+ }
+ }
+ /*
+ Applying walk_action to the tail of the last tree: this is safe because
+ either we had only one tree in the beginning, either we work with the
+ last tree in the queue.
+ */
+ do
+ {
+ do
+ {
+
+ cnt= with_counters ?
+ get_counter_from_merged_element(top->key, cnt_ofs) : 1;
+ if (walk_action(top->key, cnt, walk_action_arg))
+ goto end;
+ top->key+= key_length;
+ }
+ while (--top->mem_count);
+ bytes_read= read_to_buffer(file, top, key_length);
+ if (bytes_read == (uint) (-1))
+ goto end;
+ }
+ while (bytes_read);
+ res= 0;
+end:
+ delete_queue(&queue);
+ return res;
+}
+
+
+/*
+ DESCRIPTION
+ Walks consecutively through all unique elements:
+ if all elements are in memory, then it simply invokes 'tree_walk', else
+ all flushed trees are loaded to memory piece-by-piece, pieces are
+ sorted, and action is called for each unique value.
+ Note: so as merging resets file_ptrs state, this method can change
+ internal Unique state to undefined: if you want to reuse Unique after
+ walk() you must call reset() first!
+ SYNOPSIS
+ Unique:walk()
+ All params are 'IN':
+ table parameter for the call of the merge method
+ action function-visitor, typed in include/my_tree.h
+ function is called for each unique element
+ arg argument for visitor, which is passed to it on each call
+ RETURN VALUE
+ 0 OK
+ <> 0 error
+ */
+
+bool Unique::walk(TABLE *table, tree_walk_action action, void *walk_action_arg)
+{
+ int res= 0;
+ uchar *merge_buffer;
+
+ if (elements == 0) /* the whole tree is in memory */
+ return tree_walk(&tree, action, walk_action_arg, left_root_right);
+
+ table->sort.found_records=elements+tree.elements_in_tree;
+ /* flush current tree to the file to have some memory for merge buffer */
+ if (flush())
+ return 1;
+ if (flush_io_cache(&file) || reinit_io_cache(&file, READ_CACHE, 0L, 0, 0))
+ return 1;
+ size_t buff_sz= (max_in_memory_size / full_size + 1) * full_size;
+ if (!(merge_buffer = (uchar *)my_malloc(buff_sz, MYF(MY_THREAD_SPECIFIC|MY_WME))))
+ return 1;
+ if (buff_sz < full_size * (file_ptrs.elements + 1UL))
+ res= merge(table, merge_buffer, buff_sz >= full_size * MERGEBUFF2) ;
+
+ if (!res)
+ {
+ res= merge_walk(merge_buffer, (ulong) max_in_memory_size, full_size,
+ (BUFFPEK *) file_ptrs.buffer,
+ (BUFFPEK *) file_ptrs.buffer + file_ptrs.elements,
+ action, walk_action_arg,
+ tree.compare, tree.custom_arg, &file, with_counters);
+ }
+ my_free(merge_buffer);
+ return res;
+}
+
+
+/*
+ DESCRIPTION
+ Perform multi-pass sort merge of the elements accessed through table->sort,
+ using the buffer buff as the merge buffer. The last pass is not performed
+ if without_last_merge is TRUE.
+ SYNOPSIS
+ Unique:merge()
+ All params are 'IN':
+ table the parameter to access sort context
+ buff merge buffer
+ without_last_merge TRUE <=> do not perform the last merge
+ RETURN VALUE
+ 0 OK
+ <> 0 error
+ */
+
+bool Unique::merge(TABLE *table, uchar *buff, bool without_last_merge)
+{
+ IO_CACHE *outfile= table->sort.io_cache;
+ BUFFPEK *file_ptr= (BUFFPEK*) file_ptrs.buffer;
+ uint maxbuffer= file_ptrs.elements - 1;
+ my_off_t save_pos;
+ bool error= 1;
+
+ /* Open cached file if it isn't open */
+ if (!outfile)
+ outfile= table->sort.io_cache= (IO_CACHE*) my_malloc(sizeof(IO_CACHE),
+ MYF(MY_THREAD_SPECIFIC|MY_ZEROFILL));
+ if (!outfile ||
+ (! my_b_inited(outfile) &&
+ open_cached_file(outfile,mysql_tmpdir,TEMP_PREFIX,READ_RECORD_BUFFER,
+ MYF(MY_WME))))
+ return 1;
+ reinit_io_cache(outfile,WRITE_CACHE,0L,0,0);
+
+ Sort_param sort_param;
+ bzero((char*) &sort_param,sizeof(sort_param));
+ sort_param.max_rows= elements;
+ sort_param.sort_form= table;
+ sort_param.rec_length= sort_param.sort_length= sort_param.ref_length=
+ full_size;
+ sort_param.min_dupl_count= min_dupl_count;
+ sort_param.res_length= 0;
+ sort_param.max_keys_per_buffer=
+ (uint) (max_in_memory_size / sort_param.sort_length);
+ sort_param.not_killable= 1;
+
+ sort_param.unique_buff= buff +(sort_param.max_keys_per_buffer *
+ sort_param.sort_length);
+
+ sort_param.compare= (qsort2_cmp) buffpek_compare;
+ sort_param.cmp_context.key_compare= tree.compare;
+ sort_param.cmp_context.key_compare_arg= tree.custom_arg;
+
+ /* Merge the buffers to one file, removing duplicates */
+ if (merge_many_buff(&sort_param,buff,file_ptr,&maxbuffer,&file))
+ goto err;
+ if (flush_io_cache(&file) ||
+ reinit_io_cache(&file,READ_CACHE,0L,0,0))
+ goto err;
+ sort_param.res_length= sort_param.rec_length-
+ (min_dupl_count ? sizeof(min_dupl_count) : 0);
+ if (without_last_merge)
+ {
+ file_ptrs.elements= maxbuffer+1;
+ return 0;
+ }
+ if (merge_index(&sort_param, buff, file_ptr, maxbuffer, &file, outfile))
+ goto err;
+ error= 0;
+err:
+ if (flush_io_cache(outfile))
+ error= 1;
+
+ /* Setup io_cache for reading */
+ save_pos= outfile->pos_in_file;
+ if (reinit_io_cache(outfile,READ_CACHE,0L,0,0))
+ error= 1;
+ outfile->end_of_file=save_pos;
+ return error;
+}
+
+
+/*
+ Modify the TABLE element so that when one calls init_records()
+ the rows will be read in priority order.
+*/
+
+bool Unique::get(TABLE *table)
+{
+ bool rc= 1;
+ uchar *sort_buffer= NULL;
+ table->sort.found_records= elements+tree.elements_in_tree;
+
+ if (my_b_tell(&file) == 0)
+ {
+ /* Whole tree is in memory; Don't use disk if you don't need to */
+ if ((record_pointers=table->sort.record_pointers= (uchar*)
+ my_malloc(size * tree.elements_in_tree, MYF(MY_THREAD_SPECIFIC))))
+ {
+ tree_walk_action action= min_dupl_count ?
+ (tree_walk_action) unique_intersect_write_to_ptrs :
+ (tree_walk_action) unique_write_to_ptrs;
+ filtered_out_elems= 0;
+ (void) tree_walk(&tree, action,
+ this, left_root_right);
+ table->sort.found_records-= filtered_out_elems;
+ return 0;
+ }
+ }
+ /* Not enough memory; Save the result to file && free memory used by tree */
+ if (flush())
+ return 1;
+ size_t buff_sz= (max_in_memory_size / full_size + 1) * full_size;
+ if (!(sort_buffer= (uchar*) my_malloc(buff_sz, MYF(MY_THREAD_SPECIFIC|MY_WME))))
+ return 1;
+
+ if (merge(table, sort_buffer, FALSE))
+ goto err;
+ rc= 0;
+
+err:
+ my_free(sort_buffer);
+ return rc;
+}
diff --git a/sql/unireg.cc b/sql/unireg.cc
new file mode 100644
index 00000000000..12d3c265a86
--- /dev/null
+++ b/sql/unireg.cc
@@ -0,0 +1,979 @@
+/*
+ Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2013, Monty Program Ab.
+
+ 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
+*/
+
+/*
+ Functions to create a unireg form-file from a FIELD and a fieldname-fieldinfo
+ struct.
+ In the following functions FIELD * is an ordinary field-structure with
+ the following exeptions:
+ sc_length,typepos,row,kol,dtype,regnr and field need not to be set.
+ str is a (long) to record position where 0 is the first position.
+*/
+
+#include <my_global.h>
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_partition.h" // struct partition_info
+#include "sql_class.h" // THD, Internal_error_handler
+#include "create_options.h"
+#include "discover.h"
+#include <m_ctype.h>
+
+#define FCOMP 17 /* Bytes for a packed field */
+
+/* threshold for safe_alloca */
+#define ALLOCA_THRESHOLD 2048
+
+static uint pack_keys(uchar *,uint, KEY *, ulong);
+static bool pack_header(THD *, uchar *, List<Create_field> &, uint, ulong, handler *);
+static uint get_interval_id(uint *,List<Create_field> &, Create_field *);
+static bool pack_fields(uchar *, List<Create_field> &, ulong);
+static size_t packed_fields_length(List<Create_field> &);
+static bool make_empty_rec(THD *, uchar *, uint, List<Create_field> &, uint, ulong);
+
+/*
+ write the length as
+ if ( 0 < length <= 255) one byte
+ if (256 < length <= 65535) zero byte, then two bytes, low-endian
+*/
+static uchar *extra2_write_len(uchar *pos, size_t len)
+{
+ if (len <= 255)
+ *pos++= len;
+ else
+ {
+ /*
+ At the moment we support options_len up to 64K.
+ We can easily extend it in the future, if the need arises.
+ */
+ DBUG_ASSERT(len <= 65535);
+ int2store(pos + 1, len);
+ pos+= 3;
+ }
+ return pos;
+}
+
+static uchar *extra2_write(uchar *pos, enum extra2_frm_value_type type,
+ LEX_STRING *str)
+{
+ *pos++ = type;
+ pos= extra2_write_len(pos, str->length);
+ memcpy(pos, str->str, str->length);
+ return pos + str->length;
+}
+
+static uchar *extra2_write(uchar *pos, enum extra2_frm_value_type type,
+ LEX_CUSTRING *str)
+{
+ return extra2_write(pos, type, reinterpret_cast<LEX_STRING *>(str));
+}
+
+/**
+ Create a frm (table definition) file
+
+ @param thd Thread handler
+ @param table Name of table
+ @param create_info create info parameters
+ @param create_fields Fields to create
+ @param keys number of keys to create
+ @param key_info Keys to create
+ @param db_file Handler to use.
+
+ @return the generated frm image as a LEX_CUSTRING,
+ or null LEX_CUSTRING (str==0) in case of an error.
+*/
+
+LEX_CUSTRING build_frm_image(THD *thd, const char *table,
+ HA_CREATE_INFO *create_info,
+ List<Create_field> &create_fields,
+ uint keys, KEY *key_info, handler *db_file)
+{
+ LEX_STRING str_db_type;
+ uint reclength, key_info_length, i;
+ ulong key_buff_length;
+ ulong filepos, data_offset;
+ uint options_len;
+ uchar fileinfo[FRM_HEADER_SIZE],forminfo[FRM_FORMINFO_SIZE];
+ const partition_info *part_info= IF_PARTITIONING(thd->work_part_info, 0);
+ int error;
+ uchar *frm_ptr, *pos;
+ LEX_CUSTRING frm= {0,0};
+ DBUG_ENTER("build_frm_image");
+
+ /* If fixed row records, we need one bit to check for deleted rows */
+ if (!(create_info->table_options & HA_OPTION_PACK_RECORD))
+ create_info->null_bits++;
+ data_offset= (create_info->null_bits + 7) / 8;
+
+ error= pack_header(thd, forminfo, create_fields, create_info->table_options,
+ data_offset, db_file);
+
+ if (error)
+ DBUG_RETURN(frm);
+
+ reclength=uint2korr(forminfo+266);
+
+ /* Calculate extra data segment length */
+ str_db_type= *hton_name(create_info->db_type);
+ /* str_db_type */
+ create_info->extra_size= (2 + str_db_type.length +
+ 2 + create_info->connect_string.length);
+ /*
+ Partition:
+ Length of partition info = 4 byte
+ Potential NULL byte at end of partition info string = 1 byte
+ Indicator if auto-partitioned table = 1 byte
+ => Total 6 byte
+ */
+ create_info->extra_size+= 6;
+ if (part_info)
+ create_info->extra_size+= part_info->part_info_len;
+
+ for (i= 0; i < keys; i++)
+ {
+ if (key_info[i].parser_name)
+ create_info->extra_size+= key_info[i].parser_name->length + 1;
+ }
+
+ options_len= engine_table_options_frm_length(create_info->option_list,
+ create_fields,
+ keys, key_info);
+ DBUG_PRINT("info", ("Options length: %u", options_len));
+
+ if (validate_comment_length(thd, &create_info->comment, TABLE_COMMENT_MAXLEN,
+ ER_TOO_LONG_TABLE_COMMENT, table))
+ DBUG_RETURN(frm);
+ /*
+ If table comment is longer than TABLE_COMMENT_INLINE_MAXLEN bytes,
+ store the comment in an extra segment (up to TABLE_COMMENT_MAXLEN bytes).
+ Pre 5.5, the limit was 60 characters, with no extra segment-handling.
+ */
+ if (create_info->comment.length > TABLE_COMMENT_INLINE_MAXLEN)
+ {
+ forminfo[46]=255;
+ create_info->extra_size+= 2 + create_info->comment.length;
+ }
+ else
+ {
+ strmake((char*) forminfo+47, create_info->comment.str ?
+ create_info->comment.str : "", create_info->comment.length);
+ forminfo[46]=(uchar) create_info->comment.length;
+ }
+
+ if (!create_info->tabledef_version.str)
+ {
+ uchar *to= (uchar*) thd->alloc(MY_UUID_SIZE);
+ if (unlikely(!to))
+ DBUG_RETURN(frm);
+ my_uuid(to);
+ create_info->tabledef_version.str= to;
+ create_info->tabledef_version.length= MY_UUID_SIZE;
+ }
+ DBUG_ASSERT(create_info->tabledef_version.length > 0);
+ DBUG_ASSERT(create_info->tabledef_version.length <= 255);
+
+ prepare_frm_header(thd, reclength, fileinfo, create_info, keys, key_info);
+
+ /* one byte for a type, one or three for a length */
+ uint extra2_size= 1 + 1 + create_info->tabledef_version.length;
+ if (options_len)
+ extra2_size+= 1 + (options_len > 255 ? 3 : 1) + options_len;
+
+ if (part_info)
+ extra2_size+= 1 + 1 + hton_name(part_info->default_engine_type)->length;
+
+ key_buff_length= uint4korr(fileinfo+47);
+
+ frm.length= FRM_HEADER_SIZE; // fileinfo;
+ frm.length+= extra2_size + 4; // mariadb extra2 frm segment
+
+ int2store(fileinfo+4, extra2_size);
+ int2store(fileinfo+6, frm.length); // Position to key information
+ frm.length+= key_buff_length;
+ frm.length+= reclength; // row with default values
+ frm.length+= create_info->extra_size;
+
+ filepos= frm.length;
+ frm.length+= FRM_FORMINFO_SIZE; // forminfo
+ frm.length+= packed_fields_length(create_fields);
+
+ if (frm.length > FRM_MAX_SIZE)
+ {
+ my_error(ER_TABLE_DEFINITION_TOO_BIG, MYF(0), table);
+ DBUG_RETURN(frm);
+ }
+
+ frm_ptr= (uchar*) my_malloc(frm.length, MYF(MY_WME | MY_ZEROFILL |
+ MY_THREAD_SPECIFIC));
+ if (!frm_ptr)
+ DBUG_RETURN(frm);
+
+ /* write the extra2 segment */
+ pos = frm_ptr + 64;
+ compile_time_assert(EXTRA2_TABLEDEF_VERSION != '/');
+ pos= extra2_write(pos, EXTRA2_TABLEDEF_VERSION,
+ &create_info->tabledef_version);
+
+ if (part_info)
+ pos= extra2_write(pos, EXTRA2_DEFAULT_PART_ENGINE,
+ hton_name(part_info->default_engine_type));
+
+ if (options_len)
+ {
+ *pos++= EXTRA2_ENGINE_TABLEOPTS;
+ pos= extra2_write_len(pos, options_len);
+ pos= engine_table_options_frm_image(pos, create_info->option_list,
+ create_fields, keys, key_info);
+ }
+
+ int4store(pos, filepos); // end of the extra2 segment
+ pos+= 4;
+
+ DBUG_ASSERT(pos == frm_ptr + uint2korr(fileinfo+6));
+ key_info_length= pack_keys(pos, keys, key_info, data_offset);
+
+ int2store(forminfo+2, frm.length - filepos);
+ int4store(fileinfo+10, frm.length);
+ fileinfo[26]= (uchar) MY_TEST((create_info->max_rows == 1) &&
+ (create_info->min_rows == 1) && (keys == 0));
+ int2store(fileinfo+28,key_info_length);
+
+ if (part_info)
+ {
+ fileinfo[61]= (uchar) ha_legacy_type(part_info->default_engine_type);
+ DBUG_PRINT("info", ("part_db_type = %d", fileinfo[61]));
+ }
+
+ int2store(fileinfo+59,db_file->extra_rec_buf_length());
+
+ memcpy(frm_ptr, fileinfo, FRM_HEADER_SIZE);
+
+ pos+= key_buff_length;
+ if (make_empty_rec(thd, pos, create_info->table_options, create_fields,
+ reclength, data_offset))
+ goto err;
+
+ pos+= reclength;
+ int2store(pos, create_info->connect_string.length);
+ pos+= 2;
+ memcpy(pos, create_info->connect_string.str, create_info->connect_string.length);
+ pos+= create_info->connect_string.length;
+ int2store(pos, str_db_type.length);
+ pos+= 2;
+ memcpy(pos, str_db_type.str, str_db_type.length);
+ pos+= str_db_type.length;
+
+ if (part_info)
+ {
+ char auto_partitioned= part_info->is_auto_partitioned ? 1 : 0;
+ int4store(pos, part_info->part_info_len);
+ pos+= 4;
+ memcpy(pos, part_info->part_info_string, part_info->part_info_len + 1);
+ pos+= part_info->part_info_len + 1;
+ *pos++= auto_partitioned;
+ }
+ else
+ {
+ pos+= 6;
+ }
+
+ for (i= 0; i < keys; i++)
+ {
+ if (key_info[i].parser_name)
+ {
+ memcpy(pos, key_info[i].parser_name->str, key_info[i].parser_name->length + 1);
+ pos+= key_info[i].parser_name->length + 1;
+ }
+ }
+ if (forminfo[46] == (uchar)255) // New style MySQL 5.5 table comment
+ {
+ int2store(pos, create_info->comment.length);
+ pos+=2;
+ memcpy(pos, create_info->comment.str, create_info->comment.length);
+ pos+= create_info->comment.length;
+ }
+
+ memcpy(frm_ptr + filepos, forminfo, 288);
+ if (pack_fields(frm_ptr + filepos + 288, create_fields, data_offset))
+ goto err;
+
+ {
+ /*
+ Restore all UCS2 intervals.
+ HEX representation of them is not needed anymore.
+ */
+ List_iterator<Create_field> it(create_fields);
+ Create_field *field;
+ while ((field=it++))
+ {
+ if (field->save_interval)
+ {
+ field->interval= field->save_interval;
+ field->save_interval= 0;
+ }
+ }
+ }
+
+ frm.str= frm_ptr;
+ DBUG_RETURN(frm);
+
+err:
+ my_free(frm_ptr);
+ DBUG_RETURN(frm);
+}
+
+
+/**
+ Create a frm (table definition) file and the tables
+
+ @param thd Thread handler
+ @param frm Binary frm image of the table to create
+ @param path Name of file (including database, without .frm)
+ @param db Data base name
+ @param table_name Table name
+ @param create_info create info parameters
+ @param file Handler to use or NULL if only frm needs to be created
+
+ @retval 0 ok
+ @retval 1 error
+*/
+
+int rea_create_table(THD *thd, LEX_CUSTRING *frm,
+ const char *path, const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info, handler *file,
+ bool no_ha_create_table)
+{
+ DBUG_ENTER("rea_create_table");
+
+ // TODO don't write frm for temp tables
+ if (no_ha_create_table || create_info->tmp_table())
+ {
+ if (writefrm(path, db, table_name, true, frm->str, frm->length))
+ goto err_frm;
+ }
+
+ if (thd->variables.keep_files_on_create)
+ create_info->options|= HA_CREATE_KEEP_FILES;
+
+ if (file->ha_create_partitioning_metadata(path, NULL, CHF_CREATE_FLAG))
+ goto err_part;
+
+ if (!no_ha_create_table)
+ {
+ if (ha_create_table(thd, path, db, table_name, create_info, frm))
+ goto err_part;
+ }
+
+ DBUG_RETURN(0);
+
+err_part:
+ file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG);
+err_frm:
+ deletefrm(path);
+ DBUG_RETURN(1);
+} /* rea_create_table */
+
+
+/* Pack keyinfo and keynames to keybuff for save in form-file. */
+
+static uint pack_keys(uchar *keybuff, uint key_count, KEY *keyinfo,
+ ulong data_offset)
+{
+ uint key_parts,length;
+ uchar *pos, *keyname_pos;
+ KEY *key,*end;
+ KEY_PART_INFO *key_part,*key_part_end;
+ DBUG_ENTER("pack_keys");
+
+ pos=keybuff+6;
+ key_parts=0;
+ for (key=keyinfo,end=keyinfo+key_count ; key != end ; key++)
+ {
+ int2store(pos, (key->flags ^ HA_NOSAME));
+ int2store(pos+2,key->key_length);
+ pos[4]= (uchar) key->user_defined_key_parts;
+ pos[5]= (uchar) key->algorithm;
+ int2store(pos+6, key->block_size);
+ pos+=8;
+ key_parts+=key->user_defined_key_parts;
+ DBUG_PRINT("loop", ("flags: %lu key_parts: %d key_part: 0x%lx",
+ key->flags, key->user_defined_key_parts,
+ (long) key->key_part));
+ for (key_part=key->key_part,key_part_end=key_part+key->user_defined_key_parts ;
+ key_part != key_part_end ;
+ key_part++)
+
+ {
+ uint offset;
+ DBUG_PRINT("loop",("field: %d startpos: %lu length: %d",
+ key_part->fieldnr, key_part->offset + data_offset,
+ key_part->length));
+ int2store(pos,key_part->fieldnr+1+FIELD_NAME_USED);
+ offset= (uint) (key_part->offset+data_offset+1);
+ int2store(pos+2, offset);
+ pos[4]=0; // Sort order
+ int2store(pos+5,key_part->key_type);
+ int2store(pos+7,key_part->length);
+ pos+=9;
+ }
+ }
+ /* Save keynames */
+ keyname_pos=pos;
+ *pos++=(uchar) NAMES_SEP_CHAR;
+ for (key=keyinfo ; key != end ; key++)
+ {
+ uchar *tmp=(uchar*) strmov((char*) pos,key->name);
+ *tmp++= (uchar) NAMES_SEP_CHAR;
+ *tmp=0;
+ pos=tmp;
+ }
+ *(pos++)=0;
+ for (key=keyinfo,end=keyinfo+key_count ; key != end ; key++)
+ {
+ if (key->flags & HA_USES_COMMENT)
+ {
+ int2store(pos, key->comment.length);
+ uchar *tmp= (uchar*)strnmov((char*) pos+2,key->comment.str,
+ key->comment.length);
+ pos= tmp;
+ }
+ }
+
+ if (key_count > 127 || key_parts > 127)
+ {
+ keybuff[0]= (key_count & 0x7f) | 0x80;
+ keybuff[1]= key_count >> 7;
+ int2store(keybuff+2,key_parts);
+ }
+ else
+ {
+ keybuff[0]=(uchar) key_count;
+ keybuff[1]=(uchar) key_parts;
+ keybuff[2]= keybuff[3]= 0;
+ }
+ length=(uint) (pos-keyname_pos);
+ int2store(keybuff+4,length);
+ DBUG_RETURN((uint) (pos-keybuff));
+} /* pack_keys */
+
+
+/* Make formheader */
+
+static bool pack_header(THD *thd, uchar *forminfo,
+ List<Create_field> &create_fields,
+ uint table_options, ulong data_offset, handler *file)
+{
+ uint length,int_count,int_length,no_empty, int_parts;
+ uint time_stamp_pos,null_fields;
+ ulong reclength, totlength, n_length, com_length, vcol_info_length;
+ DBUG_ENTER("pack_header");
+
+ if (create_fields.elements > MAX_FIELDS)
+ {
+ my_message(ER_TOO_MANY_FIELDS, ER(ER_TOO_MANY_FIELDS), MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ totlength= 0L;
+ reclength= data_offset;
+ no_empty=int_count=int_parts=int_length=time_stamp_pos=null_fields=0;
+ com_length=vcol_info_length=0;
+ n_length=2L;
+
+ /* Check fields */
+ List_iterator<Create_field> it(create_fields);
+ Create_field *field;
+ while ((field=it++))
+ {
+ if (validate_comment_length(thd, &field->comment, COLUMN_COMMENT_MAXLEN,
+ ER_TOO_LONG_FIELD_COMMENT, field->field_name))
+ DBUG_RETURN(1);
+
+ if (field->vcol_info)
+ {
+ uint col_expr_maxlen= field->virtual_col_expr_maxlen();
+ uint tmp_len= my_charpos(system_charset_info,
+ field->vcol_info->expr_str.str,
+ field->vcol_info->expr_str.str +
+ field->vcol_info->expr_str.length,
+ col_expr_maxlen);
+
+ if (tmp_len < field->vcol_info->expr_str.length)
+ {
+ my_error(ER_WRONG_STRING_LENGTH, MYF(0),
+ field->vcol_info->expr_str.str,"VIRTUAL COLUMN EXPRESSION",
+ col_expr_maxlen);
+ DBUG_RETURN(1);
+ }
+ /*
+ Sum up the length of the expression string and the length of the
+ mandatory header to the total length of info on the defining
+ expressions saved in the frm file for virtual columns.
+ */
+ vcol_info_length+= field->vcol_info->expr_str.length+
+ FRM_VCOL_HEADER_SIZE(field->interval);
+ }
+
+ totlength+= field->length;
+ com_length+= field->comment.length;
+ if (MTYP_TYPENR(field->unireg_check) == Field::NOEMPTY ||
+ field->unireg_check & MTYP_NOEMPTY_BIT)
+ {
+ field->unireg_check= (Field::utype) ((uint) field->unireg_check |
+ MTYP_NOEMPTY_BIT);
+ no_empty++;
+ }
+ /*
+ We mark first TIMESTAMP field with NOW() in DEFAULT or ON UPDATE
+ as auto-update field.
+ */
+ if (field->sql_type == MYSQL_TYPE_TIMESTAMP &&
+ MTYP_TYPENR(field->unireg_check) != Field::NONE &&
+ !time_stamp_pos)
+ time_stamp_pos= (uint) field->offset+ (uint) data_offset + 1;
+ length=field->pack_length;
+ if ((uint) field->offset+ (uint) data_offset+ length > reclength)
+ reclength=(uint) (field->offset+ data_offset + length);
+ n_length+= (ulong) strlen(field->field_name)+1;
+ field->interval_id=0;
+ field->save_interval= 0;
+ if (field->interval)
+ {
+ uint old_int_count=int_count;
+
+ if (field->charset->mbminlen > 1)
+ {
+ /*
+ Escape UCS2 intervals using HEX notation to avoid
+ problems with delimiters between enum elements.
+ As the original representation is still needed in
+ the function make_empty_rec to create a record of
+ filled with default values it is saved in save_interval
+ The HEX representation is created from this copy.
+ */
+ field->save_interval= field->interval;
+ field->interval= (TYPELIB*) sql_alloc(sizeof(TYPELIB));
+ *field->interval= *field->save_interval;
+ field->interval->type_names=
+ (const char **) sql_alloc(sizeof(char*) *
+ (field->interval->count+1));
+ field->interval->type_names[field->interval->count]= 0;
+ field->interval->type_lengths=
+ (uint *) sql_alloc(sizeof(uint) * field->interval->count);
+
+ for (uint pos= 0; pos < field->interval->count; pos++)
+ {
+ char *dst;
+ const char *src= field->save_interval->type_names[pos];
+ uint hex_length;
+ length= field->save_interval->type_lengths[pos];
+ hex_length= length * 2;
+ field->interval->type_lengths[pos]= hex_length;
+ field->interval->type_names[pos]= dst= (char*) sql_alloc(hex_length +
+ 1);
+ octet2hex(dst, src, length);
+ }
+ }
+
+ field->interval_id=get_interval_id(&int_count,create_fields,field);
+ if (old_int_count != int_count)
+ {
+ for (const char **pos=field->interval->type_names ; *pos ; pos++)
+ int_length+=(uint) strlen(*pos)+1; // field + suffix prefix
+ int_parts+=field->interval->count+1;
+ }
+ }
+ if (f_maybe_null(field->pack_flag))
+ null_fields++;
+ }
+ int_length+=int_count*2; // 255 prefix + 0 suffix
+
+ /* Save values in forminfo */
+ if (reclength > (ulong) file->max_record_length())
+ {
+ my_error(ER_TOO_BIG_ROWSIZE, MYF(0), static_cast<long>(file->max_record_length()));
+ DBUG_RETURN(1);
+ }
+ /* Hack to avoid bugs with small static rows in MySQL */
+ reclength=MY_MAX(file->min_record_length(table_options),reclength);
+ if ((ulong) create_fields.elements*FCOMP+FRM_FORMINFO_SIZE+
+ n_length+int_length+com_length+vcol_info_length > 65535L ||
+ int_count > 255)
+ {
+ my_message(ER_TOO_MANY_FIELDS, ER(ER_TOO_MANY_FIELDS), MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ bzero((char*)forminfo,FRM_FORMINFO_SIZE);
+ length=(create_fields.elements*FCOMP+FRM_FORMINFO_SIZE+n_length+int_length+
+ com_length+vcol_info_length);
+ int2store(forminfo,length);
+ forminfo[256] = 0;
+ int2store(forminfo+258,create_fields.elements);
+ int2store(forminfo+260,0);
+ int2store(forminfo+262,totlength);
+ int2store(forminfo+264,no_empty);
+ int2store(forminfo+266,reclength);
+ int2store(forminfo+268,n_length);
+ int2store(forminfo+270,int_count);
+ int2store(forminfo+272,int_parts);
+ int2store(forminfo+274,int_length);
+ int2store(forminfo+276,time_stamp_pos);
+ int2store(forminfo+278,80); /* Columns needed */
+ int2store(forminfo+280,22); /* Rows needed */
+ int2store(forminfo+282,null_fields);
+ int2store(forminfo+284,com_length);
+ int2store(forminfo+286,vcol_info_length);
+ DBUG_RETURN(0);
+} /* pack_header */
+
+
+/* get each unique interval each own id */
+static uint get_interval_id(uint *int_count,List<Create_field> &create_fields,
+ Create_field *last_field)
+{
+ List_iterator<Create_field> it(create_fields);
+ Create_field *field;
+ TYPELIB *interval=last_field->interval;
+
+ while ((field=it++) != last_field)
+ {
+ if (field->interval_id && field->interval->count == interval->count)
+ {
+ const char **a,**b;
+ for (a=field->interval->type_names, b=interval->type_names ;
+ *a && !strcmp(*a,*b);
+ a++,b++) ;
+
+ if (! *a)
+ {
+ return field->interval_id; // Re-use last interval
+ }
+ }
+ }
+ return ++*int_count; // New unique interval
+}
+
+
+static size_t packed_fields_length(List<Create_field> &create_fields)
+{
+ Create_field *field;
+ size_t length= 0;
+ DBUG_ENTER("packed_fields_length");
+
+ List_iterator<Create_field> it(create_fields);
+ uint int_count=0;
+ while ((field=it++))
+ {
+ if (field->interval_id > int_count)
+ {
+ int_count= field->interval_id;
+ length++;
+ for (int i=0; field->interval->type_names[i]; i++)
+ {
+ length+= field->interval->type_lengths[i];
+ length++;
+ }
+ length++;
+ }
+ if (field->vcol_info)
+ {
+ length+= field->vcol_info->expr_str.length +
+ FRM_VCOL_HEADER_SIZE(field->interval);
+ }
+ length+= FCOMP;
+ length+= strlen(field->field_name)+1;
+ length+= field->comment.length;
+ }
+ length++;
+ length++;
+ DBUG_RETURN(length);
+}
+
+/* Save fields, fieldnames and intervals */
+
+static bool pack_fields(uchar *buff, List<Create_field> &create_fields,
+ ulong data_offset)
+{
+ uint int_count, comment_length= 0, vcol_info_length=0;
+ Create_field *field;
+ DBUG_ENTER("pack_fields");
+
+ /* Write field info */
+ List_iterator<Create_field> it(create_fields);
+ int_count=0;
+ while ((field=it++))
+ {
+ uint recpos;
+ uint cur_vcol_expr_len= 0;
+ int2store(buff+3, field->length);
+ /* The +1 is here becasue the col offset in .frm file have offset 1 */
+ recpos= field->offset+1 + (uint) data_offset;
+ int3store(buff+5,recpos);
+ int2store(buff+8,field->pack_flag);
+ DBUG_ASSERT(field->unireg_check < 256);
+ buff[10]= (uchar) field->unireg_check;
+ buff[12]= (uchar) field->interval_id;
+ buff[13]= (uchar) field->sql_type;
+ if (field->sql_type == MYSQL_TYPE_GEOMETRY)
+ {
+ buff[11]= 0;
+ buff[14]= (uchar) field->geom_type;
+#ifndef HAVE_SPATIAL
+ DBUG_ASSERT(0); // Should newer happen
+#endif
+ }
+ else if (field->charset)
+ {
+ buff[11]= (uchar) (field->charset->number >> 8);
+ buff[14]= (uchar) field->charset->number;
+ }
+ else
+ {
+ buff[11]= buff[14]= 0; // Numerical
+ }
+ if (field->vcol_info)
+ {
+ /*
+ Use the interval_id place in the .frm file to store the length of
+ the additional data saved for the virtual field
+ */
+ buff[12]= cur_vcol_expr_len= field->vcol_info->expr_str.length +
+ FRM_VCOL_HEADER_SIZE(field->interval);
+ vcol_info_length+= cur_vcol_expr_len;
+ buff[13]= (uchar) MYSQL_TYPE_VIRTUAL;
+ }
+ int2store(buff+15, field->comment.length);
+ comment_length+= field->comment.length;
+ set_if_bigger(int_count,field->interval_id);
+ buff+= FCOMP;
+ }
+
+ /* Write fieldnames */
+ *buff++= NAMES_SEP_CHAR;
+ it.rewind();
+ while ((field=it++))
+ {
+ buff= (uchar*)strmov((char*) buff, field->field_name);
+ *buff++=NAMES_SEP_CHAR;
+ }
+ *buff++= 0;
+
+ /* Write intervals */
+ if (int_count)
+ {
+ it.rewind();
+ int_count=0;
+ while ((field=it++))
+ {
+ if (field->interval_id > int_count)
+ {
+ unsigned char sep= 0;
+ unsigned char occ[256];
+ uint i;
+ unsigned char *val= NULL;
+
+ bzero(occ, sizeof(occ));
+
+ for (i=0; (val= (unsigned char*) field->interval->type_names[i]); i++)
+ for (uint j = 0; j < field->interval->type_lengths[i]; j++)
+ occ[(unsigned int) (val[j])]= 1;
+
+ if (!occ[(unsigned char)NAMES_SEP_CHAR])
+ sep= (unsigned char) NAMES_SEP_CHAR;
+ else if (!occ[(unsigned int)','])
+ sep= ',';
+ else
+ {
+ for (uint i=1; i<256; i++)
+ {
+ if(!occ[i])
+ {
+ sep= i;
+ break;
+ }
+ }
+
+ if(!sep) /* disaster, enum uses all characters, none left as separator */
+ {
+ my_message(ER_WRONG_FIELD_TERMINATORS,ER(ER_WRONG_FIELD_TERMINATORS),
+ MYF(0));
+ DBUG_RETURN(1);
+ }
+ }
+
+ int_count= field->interval_id;
+ *buff++= sep;
+ for (int i=0; field->interval->type_names[i]; i++)
+ {
+ memcpy(buff, field->interval->type_names[i], field->interval->type_lengths[i]);
+ buff+= field->interval->type_lengths[i];
+ *buff++= sep;
+ }
+ *buff++= 0;
+
+ }
+ }
+ }
+ if (comment_length)
+ {
+ it.rewind();
+ while ((field=it++))
+ {
+ memcpy(buff, field->comment.str, field->comment.length);
+ buff+= field->comment.length;
+ }
+ }
+ if (vcol_info_length)
+ {
+ it.rewind();
+ while ((field=it++))
+ {
+ /*
+ Pack each virtual field as follows:
+ byte 1 = interval_id == 0 ? 1 : 2
+ byte 2 = sql_type
+ byte 3 = flags (as of now, 0 - no flags, 1 - field is physically stored)
+ [byte 4] = possible interval_id for sql_type
+ next byte ... = virtual column expression (text data)
+ */
+ if (field->vcol_info && field->vcol_info->expr_str.length)
+ {
+ *buff++= (uchar) (1 + MY_TEST(field->interval));
+ *buff++= (uchar) field->sql_type;
+ *buff++= (uchar) field->stored_in_db;
+ if (field->interval)
+ *buff++= (uchar) field->interval_id;
+ memcpy(buff, field->vcol_info->expr_str.str, field->vcol_info->expr_str.length);
+ buff+= field->vcol_info->expr_str.length;
+ }
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/* save an empty record on start of formfile */
+
+static bool make_empty_rec(THD *thd, uchar *buff, uint table_options,
+ List<Create_field> &create_fields,
+ uint reclength, ulong data_offset)
+{
+ int error= 0;
+ Field::utype type;
+ uint null_count;
+ uchar *null_pos;
+ TABLE table;
+ TABLE_SHARE share;
+ Create_field *field;
+ enum_check_fields old_count_cuted_fields= thd->count_cuted_fields;
+ DBUG_ENTER("make_empty_rec");
+
+ /* We need a table to generate columns for default values */
+ bzero((char*) &table, sizeof(table));
+ bzero((char*) &share, sizeof(share));
+ table.s= &share;
+
+ table.in_use= thd;
+
+ null_count=0;
+ if (!(table_options & HA_OPTION_PACK_RECORD))
+ {
+ null_count++; // Need one bit for delete mark
+ *buff|= 1;
+ }
+ null_pos= buff;
+
+ List_iterator<Create_field> it(create_fields);
+ thd->count_cuted_fields= CHECK_FIELD_WARN; // To find wrong default values
+ while ((field=it++))
+ {
+ /*
+ regfield don't have to be deleted as it's allocated with sql_alloc()
+ */
+ Field *regfield= make_field(&share,
+ buff+field->offset + data_offset,
+ field->length,
+ null_pos + null_count / 8,
+ null_count & 7,
+ field->pack_flag,
+ field->sql_type,
+ field->charset,
+ field->geom_type,
+ field->unireg_check,
+ field->save_interval ? field->save_interval :
+ field->interval,
+ field->field_name);
+ if (!regfield)
+ {
+ error= 1;
+ goto err; // End of memory
+ }
+
+ /* save_in_field() will access regfield->table->in_use */
+ regfield->init(&table);
+
+ if (!(field->flags & NOT_NULL_FLAG))
+ {
+ *regfield->null_ptr|= regfield->null_bit;
+ null_count++;
+ }
+
+ if (field->sql_type == MYSQL_TYPE_BIT && !f_bit_as_char(field->pack_flag))
+ null_count+= field->length & 7;
+
+ type= (Field::utype) MTYP_TYPENR(field->unireg_check);
+
+ if (field->def)
+ {
+ int res= field->def->save_in_field(regfield, 1);
+ /* If not ok or warning of level 'note' */
+ if (res != 0 && res != 3)
+ {
+ my_error(ER_INVALID_DEFAULT, MYF(0), regfield->field_name);
+ error= 1;
+ delete regfield; //To avoid memory leak
+ goto err;
+ }
+ }
+ else if (regfield->real_type() == MYSQL_TYPE_ENUM &&
+ (field->flags & NOT_NULL_FLAG))
+ {
+ regfield->set_notnull();
+ regfield->store((longlong) 1, TRUE);
+ }
+ else if (type == Field::YES) // Old unireg type
+ regfield->store(ER(ER_YES),(uint) strlen(ER(ER_YES)),system_charset_info);
+ else if (type == Field::NO) // Old unireg type
+ regfield->store(ER(ER_NO), (uint) strlen(ER(ER_NO)),system_charset_info);
+ else
+ regfield->reset();
+ }
+ DBUG_ASSERT(data_offset == ((null_count + 7) / 8));
+
+ /*
+ We need to set the unused bits to 1. If the number of bits is a multiple
+ of 8 there are no unused bits.
+ */
+ if (null_count & 7)
+ *(null_pos + null_count / 8)|= ~(((uchar) 1 << (null_count & 7)) - 1);
+
+err:
+ thd->count_cuted_fields= old_count_cuted_fields;
+ DBUG_RETURN(error);
+} /* make_empty_rec */
diff --git a/sql/unireg.h b/sql/unireg.h
new file mode 100644
index 00000000000..2d51aa39fd4
--- /dev/null
+++ b/sql/unireg.h
@@ -0,0 +1,215 @@
+#ifndef UNIREG_INCLUDED
+#define UNIREG_INCLUDED
+
+/*
+ Copyright (c) 2000, 2011, Oracle and/or its affiliates.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
+
+
+#include <mysql_version.h> /* FRM_VER */
+
+/* Extra functions used by unireg library */
+
+#ifndef NO_ALARM_LOOP
+#define NO_ALARM_LOOP /* lib5 and popen can't use alarm */
+#endif
+
+/* These paths are converted to other systems (WIN95) before use */
+
+#define LANGUAGE "english/"
+#define ERRMSG_FILE "errmsg.sys"
+#define TEMP_PREFIX "MY"
+#define LOG_PREFIX "ML"
+#define PROGDIR "bin/"
+#ifndef MYSQL_DATADIR
+#define MYSQL_DATADIR "data/"
+#endif
+#ifndef SHAREDIR
+#define SHAREDIR "share/"
+#endif
+#ifndef PLUGINDIR
+#define PLUGINDIR "lib/plugin"
+#endif
+
+#define CURRENT_THD_ERRMSGS current_thd->variables.lc_messages->errmsgs->errmsgs
+#define DEFAULT_ERRMSGS my_default_lc_messages->errmsgs->errmsgs
+
+#define ER(X) CURRENT_THD_ERRMSGS[(X) - ER_ERROR_FIRST]
+#define ER_DEFAULT(X) DEFAULT_ERRMSGS[(X) - ER_ERROR_FIRST]
+#define ER_SAFE(X) (((X) >= ER_ERROR_FIRST && (X) <= ER_ERROR_LAST) ? ER(X) : "Invalid error code")
+#define ER_THD(thd,X) ((thd)->variables.lc_messages->errmsgs->errmsgs[(X) - \
+ ER_ERROR_FIRST])
+#define ER_THD_OR_DEFAULT(thd,X) ((thd) ? ER_THD(thd, X) : ER_DEFAULT(X))
+
+#define ME_INFO (ME_HOLDTANG+ME_OLDWIN+ME_NOREFRESH)
+#define ME_ERROR (ME_BELL+ME_OLDWIN+ME_NOREFRESH)
+#define MYF_RW MYF(MY_WME+MY_NABP) /* Vid my_read & my_write */
+
+#define SPECIAL_USE_LOCKS 1 /* Lock used databases */
+#define SPECIAL_NO_NEW_FUNC 2 /* Skip new functions */
+#define SPECIAL_SKIP_SHOW_DB 4 /* Don't allow 'show db' */
+#define SPECIAL_WAIT_IF_LOCKED 8 /* Wait if locked database */
+#define SPECIAL_SAME_DB_NAME 16 /* form name = file name */
+#define SPECIAL_ENGLISH 32 /* English error messages */
+#define SPECIAL_NO_RESOLVE 64 /* Don't use gethostname */
+#define SPECIAL_NO_PRIOR 128 /* Obsolete */
+#define SPECIAL_BIG_SELECTS 256 /* Don't use heap tables */
+#define SPECIAL_NO_HOST_CACHE 512 /* Don't cache hosts */
+#define SPECIAL_SHORT_LOG_FORMAT 1024
+#define SPECIAL_SAFE_MODE 2048
+#define SPECIAL_LOG_QUERIES_NOT_USING_INDEXES 4096 /* Obsolete */
+
+ /* Extern defines */
+#define store_record(A,B) memcpy((A)->B,(A)->record[0],(size_t) (A)->s->reclength)
+#define restore_record(A,B) memcpy((A)->record[0],(A)->B,(size_t) (A)->s->reclength)
+#define cmp_record(A,B) memcmp((A)->record[0],(A)->B,(size_t) (A)->s->reclength)
+#define empty_record(A) { \
+ restore_record((A),s->default_values); \
+ bfill((A)->null_flags,(A)->s->null_bytes,255);\
+ }
+
+ /* Defines for use with openfrm, openprt and openfrd */
+
+#define READ_ALL 1 /* openfrm: Read all parameters */
+#define CHANGE_FRM 2 /* openfrm: open .frm as O_RDWR */
+#define READ_KEYINFO 4 /* L{s nyckeldata fr}n filen */
+#define EXTRA_RECORD 8 /* Reserve space for an extra record */
+#define DONT_OPEN_TABLES 8 /* Don't open database-files (frd) */
+#define DONT_OPEN_MASTER_REG 16 /* Don't open first reg-file (prt) */
+#define EXTRA_LONG_RECORD 16 /* Plats f|r dubbel s|k-record */
+#define COMPUTE_TYPES 32 /* Kontrollera type f|r f{ltena */
+#define SEARCH_PRG 64 /* S|k efter registret i 'prg_dev' */
+#define READ_USED_NAMES 128 /* L{s anv{nda formul{rnamn */
+#define DONT_GIVE_ERROR 256 /* Don't do frm_error on openfrm */
+#define READ_SCREENS 1024 /* Read screens, info and helpfile */
+#define DELAYED_OPEN 4096 /* Open table later */
+#define OPEN_VIEW 8192 /* Allow open on view */
+#define OPEN_VIEW_NO_PARSE 16384 /* Open frm only if it's a view,
+ but do not parse view itself */
+/**
+ This flag is used in function get_all_tables() which fills
+ I_S tables with data which are retrieved from frm files and storage engine
+ The flag means that we need to open FRM file only to get necessary data.
+*/
+#define OPEN_FRM_FILE_ONLY 32768
+/**
+ This flag is used in function get_all_tables() which fills
+ I_S tables with data which are retrieved from frm files and storage engine
+ The flag means that we need to process tables only to get necessary data.
+ Views are not processed.
+*/
+#define OPEN_TABLE_ONLY OPEN_FRM_FILE_ONLY*2
+/**
+ This flag is used in function get_all_tables() which fills
+ I_S tables with data which are retrieved from frm files and storage engine
+ The flag means that we need to process views only to get necessary data.
+ Tables are not processed.
+*/
+#define OPEN_VIEW_ONLY OPEN_TABLE_ONLY*2
+/**
+ This flag is used in function get_all_tables() which fills
+ I_S tables with data which are retrieved from frm files and storage engine.
+ The flag means that we need to open a view using
+ open_normal_and_derived_tables() function.
+*/
+#define OPEN_VIEW_FULL OPEN_VIEW_ONLY*2
+/**
+ This flag is used in function get_all_tables() which fills
+ I_S tables with data which are retrieved from frm files and storage engine.
+ The flag means that I_S table uses optimization algorithm.
+*/
+#define OPTIMIZE_I_S_TABLE OPEN_VIEW_FULL*2
+/**
+ This flag is used to instruct tdc_open_view() to check metadata version.
+*/
+#define CHECK_METADATA_VERSION OPEN_TRIGGER_ONLY*2
+
+/*
+ The flag means that we need to process trigger files only.
+*/
+#define OPEN_TRIGGER_ONLY OPTIMIZE_I_S_TABLE*2
+
+#define SC_INFO_LENGTH 4 /* Form format constant */
+#define TE_INFO_LENGTH 3
+#define MTYP_NOEMPTY_BIT 128
+
+/*
+ Minimum length pattern before Turbo Boyer-Moore is used
+ for SELECT "text" LIKE "%pattern%", excluding the two
+ wildcards in class Item_func_like.
+*/
+#define MIN_TURBOBM_PATTERN_LEN 3
+
+/*
+ Defines for binary logging.
+ Do not decrease the value of BIN_LOG_HEADER_SIZE.
+ Do not even increase it before checking code.
+*/
+
+#define BIN_LOG_HEADER_SIZE 4
+
+#define DEFAULT_KEY_CACHE_NAME "default"
+
+
+/* Include prototypes for unireg */
+
+#include "mysqld_error.h"
+#include "structs.h" /* All structs we need */
+#include "sql_list.h" /* List<> */
+#include "field.h" /* Create_field */
+
+/*
+ Types of values in the MariaDB extra2 frm segment.
+ Each value is written as
+ type: 1 byte
+ length: 1 byte (1..255) or \0 and 2 bytes.
+ binary value of the 'length' bytes.
+
+ Older MariaDB servers can ignore values of unknown types if
+ the type code is less than 128 (EXTRA2_ENGINE_IMPORTANT).
+ Otherwise older (but newer than 10.0.1) servers are required
+ to report an error.
+*/
+enum extra2_frm_value_type {
+ EXTRA2_TABLEDEF_VERSION=0,
+ EXTRA2_DEFAULT_PART_ENGINE=1,
+
+#define EXTRA2_ENGINE_IMPORTANT 128
+
+ EXTRA2_ENGINE_TABLEOPTS=128,
+};
+
+int rea_create_table(THD *thd, LEX_CUSTRING *frm,
+ const char *path, const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info, handler *file,
+ bool no_ha_create_table);
+LEX_CUSTRING build_frm_image(THD *thd, const char *table,
+ HA_CREATE_INFO *create_info,
+ List<Create_field> &create_fields,
+ uint keys, KEY *key_info, handler *db_file);
+
+#define FRM_HEADER_SIZE 64
+#define FRM_FORMINFO_SIZE 288
+#define FRM_MAX_SIZE (512*1024)
+
+static inline bool is_binary_frm_header(uchar *head)
+{
+ return head[0] == 254
+ && head[1] == 1
+ && head[2] >= FRM_VER
+ && head[2] <= FRM_VER+4;
+}
+
+#endif
diff --git a/sql/winservice.c b/sql/winservice.c
new file mode 100644
index 00000000000..1cf9f8d7823
--- /dev/null
+++ b/sql/winservice.c
@@ -0,0 +1,300 @@
+/*
+ Copyright (c) 2011, 2012, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/*
+ Get Properties of an existing mysqld Windows service
+*/
+
+#include <windows.h>
+#include <winsvc.h>
+#include "winservice.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <shellapi.h>
+
+/*
+ Get version from an executable file
+*/
+void get_file_version(const char *path, int *major, int *minor, int *patch)
+{
+ DWORD version_handle;
+ char *ver= 0;
+ VS_FIXEDFILEINFO info;
+ UINT len;
+ DWORD size;
+ void *p;
+ *major= *minor= *patch= 0;
+
+ size= GetFileVersionInfoSize(path, &version_handle);
+ if (size == 0)
+ return;
+ ver= (char *)malloc(size);
+ if(!GetFileVersionInfo(path, version_handle, size, ver))
+ goto end;
+
+ if(!VerQueryValue(ver,"\\",&p,&len))
+ goto end;
+ memcpy(&info,p ,sizeof(VS_FIXEDFILEINFO));
+
+ *major= (info.dwFileVersionMS & 0xFFFF0000) >> 16;
+ *minor= (info.dwFileVersionMS & 0x0000FFFF);
+ *patch= (info.dwFileVersionLS & 0xFFFF0000) >> 16;
+end:
+ free(ver);
+}
+
+void normalize_path(char *path, size_t size)
+{
+ char buf[MAX_PATH];
+ if (*path== '"')
+ {
+ char *p;
+ strcpy_s(buf, MAX_PATH, path+1);
+ p= strchr(buf, '"');
+ if (p)
+ *p=0;
+ }
+ else
+ strcpy_s(buf, MAX_PATH, path);
+ GetFullPathName(buf, MAX_PATH, buf, NULL);
+ strcpy_s(path, size, buf);
+}
+
+/*
+ Exclusion rules.
+
+ Some hardware manufacturers deliver systems with own preinstalled MySQL copy
+ and services. We do not want to mess up with these installations. We will
+ just ignore such services, pretending it is not MySQL.
+
+ ´@return
+ TRUE, if this service should be excluded from UI lists etc (OEM install)
+ FALSE otherwise.
+*/
+BOOL exclude_service(mysqld_service_properties *props)
+{
+ static const char* exclude_patterns[] =
+ {
+ "common files\\dell\\mysql\\bin\\", /* Dell's private installation */
+ NULL
+ };
+ int i;
+ char buf[MAX_PATH];
+
+ /* Convert mysqld path to lower case, rules for paths are case-insensitive. */
+ memcpy(buf, props->mysqld_exe, sizeof(props->mysqld_exe));
+ _strlwr(buf);
+
+ for(i= 0; exclude_patterns[i]; i++)
+ {
+ if (strstr(buf, exclude_patterns[i]))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/*
+ Retrieve some properties from windows mysqld service binary path.
+ We're interested in ini file location and datadir, and also in version of
+ the data. We tolerate missing mysqld.exe.
+
+ Note that this function carefully avoids using mysql libraries (e.g dbug),
+ since it is used in unusual environments (windows installer, MFC), where we
+ do not have much control over how threads are created and destroyed, so we
+ cannot assume MySQL thread initilization here.
+*/
+int get_mysql_service_properties(const wchar_t *bin_path,
+ mysqld_service_properties *props)
+{
+ int numargs;
+ wchar_t mysqld_path[MAX_PATH + 4];
+ wchar_t *file_part;
+ wchar_t **args= NULL;
+ int retval= 1;
+ BOOL have_inifile;
+
+ props->datadir[0]= 0;
+ props->inifile[0]= 0;
+ props->mysqld_exe[0]= 0;
+ props->version_major= 0;
+ props->version_minor= 0;
+ props->version_patch= 0;
+
+ args= CommandLineToArgvW(bin_path, &numargs);
+ if(numargs == 2)
+ {
+ /*
+ There are rare cases where service config does not have
+ --defaults-filein the binary parth . There services were registered with
+ plain mysqld --install, the data directory is next to "bin" in this case.
+ Service name (second parameter) must be MySQL.
+ */
+ if(wcscmp(args[1], L"MySQL") != 0)
+ goto end;
+ have_inifile= FALSE;
+ }
+ else if(numargs == 3)
+ {
+ have_inifile= TRUE;
+ }
+ else
+ {
+ goto end;
+ }
+
+ if(have_inifile && wcsncmp(args[1], L"--defaults-file=", 16) != 0)
+ goto end;
+
+ GetFullPathNameW(args[0], MAX_PATH, mysqld_path, &file_part);
+
+ if(wcsstr(mysqld_path, L".exe") == NULL)
+ wcscat(mysqld_path, L".exe");
+
+ if(wcsicmp(file_part, L"mysqld.exe") != 0 &&
+ wcsicmp(file_part, L"mysqld-debug.exe") != 0 &&
+ wcsicmp(file_part, L"mysqld-nt.exe") != 0)
+ {
+ /* The service executable is not mysqld. */
+ goto end;
+ }
+
+ wcstombs(props->mysqld_exe, mysqld_path, MAX_PATH);
+ /* If mysqld.exe exists, try to get its version from executable */
+ if (GetFileAttributes(props->mysqld_exe) != INVALID_FILE_ATTRIBUTES)
+ {
+ get_file_version(props->mysqld_exe, &props->version_major,
+ &props->version_minor, &props->version_patch);
+ }
+
+ if (have_inifile)
+ {
+ /* We have --defaults-file in service definition. */
+ wcstombs(props->inifile, args[1]+16, MAX_PATH);
+ normalize_path(props->inifile, MAX_PATH);
+ if (GetFileAttributes(props->inifile) != INVALID_FILE_ATTRIBUTES)
+ {
+ GetPrivateProfileString("mysqld", "datadir", NULL, props->datadir, MAX_PATH,
+ props->inifile);
+ }
+ else
+ {
+ /*
+ Service will start even with invalid .ini file, using lookup for
+ datadir relative to mysqld.exe. This is equivalent to the case no ini
+ file used.
+ */
+ props->inifile[0]= 0;
+ have_inifile= FALSE;
+ }
+ }
+
+ if(!have_inifile)
+ {
+ /*
+ Hard, although a rare case, we're guessing datadir and defaults-file.
+ On Windows, defaults-file is traditionally install-root\my.ini
+ and datadir is install-root\data
+ */
+ char install_root[MAX_PATH];
+ int i;
+ char *p;
+
+ /*
+ Get the install root(parent of bin directory where mysqld.exe)
+ is located.
+ */
+ strcpy_s(install_root, MAX_PATH, props->mysqld_exe);
+ for (i=0; i< 2; i++)
+ {
+ p= strrchr(install_root, '\\');
+ if(!p)
+ goto end;
+ *p= 0;
+ }
+
+ /* Look for my.ini, my.cnf in the install root */
+ sprintf_s(props->inifile, MAX_PATH, "%s\\my.ini", install_root);
+ if (GetFileAttributes(props->inifile) == INVALID_FILE_ATTRIBUTES)
+ {
+ sprintf_s(props->inifile, MAX_PATH, "%s\\my.cnf", install_root);
+ }
+ if (GetFileAttributes(props->inifile) != INVALID_FILE_ATTRIBUTES)
+ {
+ /* Ini file found, get datadir from there */
+ GetPrivateProfileString("mysqld", "datadir", NULL, props->datadir,
+ MAX_PATH, props->inifile);
+ }
+ else
+ {
+ /* No ini file */
+ props->inifile[0]= 0;
+ }
+
+ /* Try datadir in install directory.*/
+ if (props->datadir[0] == 0)
+ {
+ sprintf_s(props->datadir, MAX_PATH, "%s\\data", install_root);
+ }
+ }
+
+ if (props->datadir[0])
+ {
+ normalize_path(props->datadir, MAX_PATH);
+ /* Check if datadir really exists */
+ if (GetFileAttributes(props->datadir) == INVALID_FILE_ATTRIBUTES)
+ goto end;
+ }
+ else
+ {
+ /* There is no datadir in ini file, bail out.*/
+ goto end;
+ }
+
+ /*
+ If version could not be determined so far, try mysql_upgrade_info in
+ database directory.
+ */
+ if(props->version_major == 0)
+ {
+ char buf[MAX_PATH];
+ FILE *mysql_upgrade_info;
+
+ sprintf_s(buf, MAX_PATH, "%s\\mysql_upgrade_info", props->datadir);
+ mysql_upgrade_info= fopen(buf, "r");
+ if(mysql_upgrade_info)
+ {
+ if (fgets(buf, MAX_PATH, mysql_upgrade_info))
+ {
+ int major,minor,patch;
+ if (sscanf(buf, "%d.%d.%d", &major, &minor, &patch) == 3)
+ {
+ props->version_major= major;
+ props->version_minor= minor;
+ props->version_patch= patch;
+ }
+ }
+ }
+ }
+
+ if (!exclude_service(props))
+ retval = 0;
+end:
+ LocalFree((HLOCAL)args);
+ return retval;
+}
diff --git a/sql/winservice.h b/sql/winservice.h
new file mode 100644
index 00000000000..fca7b129de5
--- /dev/null
+++ b/sql/winservice.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (c) 2011, 2012, Monty Program Ab
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/*
+ Extract properties of a windows service binary path
+*/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <windows.h>
+typedef struct mysqld_service_properties_st
+{
+ char mysqld_exe[MAX_PATH];
+ char inifile[MAX_PATH];
+ char datadir[MAX_PATH];
+ int version_major;
+ int version_minor;
+ int version_patch;
+} mysqld_service_properties;
+
+extern int get_mysql_service_properties(const wchar_t *bin_path,
+ mysqld_service_properties *props);
+
+#ifdef __cplusplus
+}
+#endif